PSARC/2015/144 Kerberos 1.13 Delivery to Userland
19153034 Add MIT Kerberos to the Userland Consolidation
#
# This patch provides support in kerberos for root acquiring a default
# cred via either a root or host service principal keys in the keytab if
# root doesn't have a cred already.
#
# This is Solaris specific behavior that MIT will not take upstream.
# Patch source: in-house
#
diff -u -r old/src/include/k5-int.h new/src/include/k5-int.h
--- old/src/include/k5-int.h 2015-06-03 18:20:34.239623602 -0500
+++ new/src/include/k5-int.h 2015-06-04 12:29:26.540947840 -0500
@@ -2294,4 +2294,6 @@
/* Define a shorter internal name for krb5_set_error_message. */
#define k5_setmsg krb5_set_error_message
+uid_t krb5_getuid();
+
#endif /* _KRB5_INT_H */
Only in new/src/lib/gssapi/krb5: .init_sec_context.c.swp
diff -u -r old/src/lib/gssapi/krb5/init_sec_context.c new/src/lib/gssapi/krb5/init_sec_context.c
--- old/src/lib/gssapi/krb5/init_sec_context.c 2015-05-08 18:27:02.000000000 -0500
+++ new/src/lib/gssapi/krb5/init_sec_context.c 2015-06-08 12:44:54.041616737 -0500
@@ -104,6 +104,11 @@
#endif
#include <stdlib.h>
#include <assert.h>
+#include <ctype.h>
+
+static OM_uint32 get_default_cred(OM_uint32 *, void *, gss_cred_id_t *);
+krb5_error_code krb5_kt_find_realm(krb5_context, krb5_keytab, krb5_principal,
+ krb5_data *);
/*
* $Id$
@@ -960,8 +965,11 @@
/* verify the credential, or use the default */
/*SUPPRESS 29*/
if (claimant_cred_handle == GSS_C_NO_CREDENTIAL) {
- major_status = kg_get_defcred(minor_status, &defcred);
+ major_status = get_default_cred(minor_status, context,
+ (gss_cred_id_t *)&defcred);
+
if (major_status && GSS_ERROR(major_status)) {
+ save_error_info(*minor_status, context);
if (*context_handle == GSS_C_NO_CONTEXT)
krb5_free_context(context);
return(major_status);
@@ -1097,3 +1105,441 @@
time_rec,
&exts);
}
+
+/* Solaris Kerberos specific routines start */
+
+#define ROOT_UID 0
+#define KRB5_DEFAULT_LIFE 60*60*10
+
+extern int
+safechown(const char *src, uid_t uid, gid_t gid, int mode);
+
+/* Solaris Kerberos */
+static OM_uint32
+load_root_cred_using_keytab(OM_uint32 *minor_status,
+ krb5_context context,
+ const char *name,
+ krb5_int32 type)
+{
+ krb5_creds my_creds;
+ krb5_principal me = NULL;
+ krb5_principal server = NULL;
+ krb5_error_code code;
+ krb5_ccache ccache = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_timestamp now;
+ krb5_deltat lifetime = KRB5_DEFAULT_LIFE; /* -l option */
+ krb5_get_init_creds_opt opt;
+ krb5_data tgtname = {
+ 0,
+ KRB5_TGS_NAME_SIZE,
+ KRB5_TGS_NAME
+ };
+ char *svcname = NULL;
+ size_t kt_size;
+ krb5_boolean kt_is_file;
+
+ if (!name)
+ return (GSS_S_FAILURE);
+
+ memset((char *)&my_creds, 0, sizeof(my_creds));
+
+ if (code = krb5_kt_default(context, &keytab)) {
+ *minor_status = code;
+ return (GSS_S_FAILURE);
+ }
+
+ if (code = krb5_kt_have_content(context, keytab)) {
+ *minor_status = code;
+ return (GSS_S_FAILURE);
+ }
+
+ if (type == KRB5_NT_SRV_HST)
+ code = krb5_sname_to_principal(context, NULL, name, type, &me);
+ else
+ code = krb5_parse_name(context, name, &me);
+
+ if (code == 0 && krb5_is_referral_realm(&me->realm)) {
+ krb5_data realm;
+ code = krb5_kt_find_realm(context, keytab, me, &realm);
+ if (code == 0) {
+ krb5_free_data_contents(context, &me->realm);
+ me->realm.length = realm.length;
+ me->realm.data = realm.data;
+ } else {
+ /* Try to set a useful error message */
+ char *princ = NULL;
+ krb5_error_code ret;
+ ret = krb5_unparse_name(context, me, &princ);
+
+ krb5_set_error_message(context, code,
+ _("Failed to find realm for %s in keytab"),
+ ret == 0 ? princ : "unknown");
+ if (princ)
+ krb5_free_unparsed_name(context, princ);
+ }
+ }
+
+ if (code) {
+ (void) krb5_kt_close(context, keytab);
+ krb5_free_principal(context, me);
+ *minor_status = code;
+ return (GSS_S_FAILURE);
+ }
+
+ my_creds.client = me;
+
+ if ((code = krb5_build_principal_ext(context, &server,
+ krb5_princ_realm(context, me)->length,
+ krb5_princ_realm(context, me)->data,
+ tgtname.length, tgtname.data,
+ krb5_princ_realm(context, me)->length,
+ krb5_princ_realm(context, me)->data,
+ 0))) {
+ *minor_status = code;
+ krb5_free_cred_contents(context, &my_creds);
+ (void) krb5_kt_close(context, keytab);
+ return (GSS_S_FAILURE);
+ }
+
+ my_creds.server = server;
+ my_creds.times.starttime = 0; /* start timer
+ * when request
+ * gets to KDC
+ */
+ if ((code = krb5_timeofday(context, &now))) {
+ *minor_status = code;
+ krb5_free_cred_contents(context, &my_creds);
+ (void) krb5_kt_close(context, keytab);
+ return (GSS_S_FAILURE);
+ }
+ my_creds.times.endtime = now + lifetime;
+ my_creds.times.renew_till = 0;
+
+ memset(&opt, 0, sizeof (opt));
+ krb5_get_init_creds_opt_init(&opt);
+ krb5_get_init_creds_opt_set_tkt_life(&opt, lifetime);
+
+ code = krb5_unparse_name(context, server, &svcname);
+ if (code != 0) {
+ *minor_status = code;
+ krb5_free_cred_contents(context, &my_creds);
+ (void) krb5_kt_close(context, keytab);
+ return (GSS_S_FAILURE);
+ }
+ /*
+ * Evidently (sigh), on success, krb5_get_init_creds_keytab
+ * changes the my_creds princ ptrs so we need to free those
+ * princs (me&server) as well as freeing all of my_creds contents.
+ */
+ code = krb5_get_init_creds_keytab(context,
+ &my_creds, me, keytab,
+ 0, svcname, &opt);
+
+ (void) krb5_kt_close(context, keytab);
+
+ if (svcname != NULL) {
+ free(svcname);
+ svcname = NULL;
+ }
+ if (code) {
+ *minor_status = code;
+ krb5_free_cred_contents(context, &my_creds);
+ return (GSS_S_FAILURE);
+ }
+
+ krb5_free_principal(context, server);
+ server = NULL;
+
+ code = krb5_cc_resolve (context,
+ krb5_cc_default_name(context),
+ &ccache);
+ if (code != 0) {
+ *minor_status = code;
+ krb5_free_cred_contents(context, &my_creds);
+ return (GSS_S_FAILURE);
+ }
+
+ code = krb5_cc_initialize (context, ccache, me);
+ krb5_free_principal(context, me);
+ me = NULL;
+ if (code != 0) {
+ *minor_status = code;
+ krb5_free_cred_contents(context, &my_creds);
+ (void) krb5_cc_close(context, ccache);
+ return (GSS_S_FAILURE);
+ }
+
+ code = krb5_cc_store_cred(context, ccache,
+ &my_creds);
+ krb5_free_cred_contents(context, &my_creds);
+ (void) krb5_cc_close(context, ccache);
+
+ if (code) {
+ *minor_status = code;
+ return (GSS_S_FAILURE);
+ }
+
+ return (GSS_S_COMPLETE);
+}
+
+static OM_uint32
+renew_ccache(OM_uint32 *minor_status, krb5_context context, uid_t uid)
+{
+ krb5_principal me;
+ krb5_principal server;
+ krb5_creds creds;
+ krb5_creds tmpcreds;
+ krb5_creds *out_creds;
+ krb5_error_code code;
+ krb5_ccache ccache = NULL;
+ const char *ccache_name = NULL;
+ int options = 0;
+ krb5_data tgtname = {
+ 0,
+ KRB5_TGS_NAME_SIZE,
+ KRB5_TGS_NAME
+ };
+ gid_t gid = getgid();
+
+ memset((char *)&creds, 0, sizeof(creds));
+ memset((char *)&tmpcreds, 0, sizeof(creds));
+
+ if ((code = krb5_cc_default(context, &ccache))) {
+ *minor_status = code;
+ return (GSS_S_FAILURE);
+ }
+
+ if ((code = krb5_cc_get_principal(context, ccache, &me)) != 0) {
+ *minor_status = code;
+ (void) krb5_cc_close(context, ccache);
+ return (GSS_S_FAILURE);
+ }
+
+ creds.client = me;
+
+ if((code = krb5_build_principal_ext(context, &server,
+ krb5_princ_realm(context, me)->length,
+ krb5_princ_realm(context, me)->data,
+ tgtname.length, tgtname.data,
+ krb5_princ_realm(context, me)->length,
+ krb5_princ_realm(context, me)->data,
+ 0))) {
+ krb5_free_principal(context, me);
+ (void) krb5_cc_close(context, ccache);
+ *minor_status = code;
+ return (GSS_S_FAILURE);
+ }
+
+ creds.server = server;
+ creds.ticket_flags = TKT_FLG_RENEWABLE;
+
+ if ((krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_FLAGS,
+ &creds, &tmpcreds))) {
+ (void) krb5_cc_close(context, ccache);
+ return (KDC_ERR_BADOPTION);
+ }
+
+ creds.ticket_flags = 0;
+ code = krb5_get_credentials_renew(context, options, ccache,
+ &creds, &out_creds);
+ krb5_free_cred_contents(context, &creds);
+ krb5_free_cred_contents(context, &tmpcreds);
+
+ if (code) {
+ *minor_status = code;
+ (void) krb5_cc_close(context, ccache);
+ return (GSS_S_FAILURE);
+ }
+
+ krb5_free_creds(context, out_creds);
+
+ ccache_name = krb5_cc_default_name(context);
+ if (ccache_name == NULL) {
+ *minor_status = KRB5_FCC_INTERNAL;
+ (void) krb5_cc_close(context, ccache);
+ return (GSS_S_FAILURE);
+ }
+
+ if (strncmp(ccache_name, "FILE:", strlen("FILE:")) == 0) {
+ ccache_name += strlen("FILE:");
+ code = safechown(ccache_name, uid, gid, -1);
+ if (code == -1) {
+ (void) krb5_cc_destroy(context, ccache);
+ *minor_status = code;
+ return (GSS_S_FAILURE);
+ }
+ }
+
+ (void) krb5_cc_close(context, ccache);
+
+ return (GSS_S_COMPLETE);
+}
+
+/* Solaris Kerberos */
+#define SAM_ACCOUNT_LEN 17 /* 15:hostname + 1:$ + 1:\0 */
+krb5_error_code
+get_sam_account_name(char **name)
+{
+ char *p, localname[SAM_ACCOUNT_LEN];
+
+ if (name == NULL)
+ return (EINVAL);
+
+ if (gethostname(localname, SAM_ACCOUNT_LEN) != 0)
+ return (errno);
+
+ localname[SAM_ACCOUNT_LEN - 2] = '\0';
+
+ if ((p = strchr(localname, '.')) != NULL)
+ *p = '\0';
+
+ for (p = localname; *p; p++)
+ *p = toupper(*p);
+
+ (void) strlcat(localname, "$", SAM_ACCOUNT_LEN);
+
+ *name = strdup(localname);
+ if (*name == NULL)
+ return (ENOMEM);
+
+ return (0);
+}
+
+/*
+ * Solaris Kerberos:
+ * We enforce a minimum refresh time on the root cred. This avoids problems for
+ * the higher level communication protocol for having valid creds and
+ * setting up a valid context, only to have it expire before or while
+ * it is being used. For non root users we don't care since we do not refresh
+ * there creds, they get what they can get.
+ */
+#define MIN_REFRESH_TIME 300
+#define MIN_RENEW_TIME 1500
+
+/* get_default_cred() must be called with the krb5_mutex lock held */
+static OM_uint32
+get_default_cred(OM_uint32 *minor_status, void *ct, gss_cred_id_t *cred_handle)
+{
+ krb5_timestamp now;
+ krb5_gss_cred_id_t cred;
+ OM_uint32 major;
+ OM_uint32 mntmp;
+ /*
+ * Solaris Kerberos
+ * Use krb5_getuid() to select the mechanism to obtain the uid.
+ */
+ uid_t uid = krb5_getuid();
+ krb5_context context = (krb5_context)ct;
+
+ /* Get the default cred for user */
+ if (((major = kg_get_defcred(minor_status, cred_handle)) != NULL) &&
+ GSS_ERROR(major)) {
+
+ /* If we're not root we're done */
+ if (uid != ROOT_UID)
+ return (major);
+
+ /*
+ * Try and get root's cred in the cache using keytab.
+ *
+ * First try "root", then try "host", and finally try the Security
+ * Account Manager (SAM) for MS AD interop.
+ */
+ major = load_root_cred_using_keytab(minor_status,
+ context, "root", KRB5_NT_SRV_HST);
+
+ if (major != GSS_S_COMPLETE)
+ major = load_root_cred_using_keytab(minor_status,
+ context, "host",
+ KRB5_NT_SRV_HST);
+
+ if (major != GSS_S_COMPLETE) {
+ char *name;
+ krb5_error_code code;
+
+ code = get_sam_account_name(&name);
+ if (code == 0) {
+ major = load_root_cred_using_keytab(minor_status,
+ context, name,
+ KRB5_NT_PRINCIPAL);
+ free(name);
+ }
+ }
+
+ if (major != GSS_S_COMPLETE)
+ return (major);
+
+ /* We should have valid tgt now in the cache, so get it. */
+ major = kg_get_defcred(minor_status, cred_handle);
+
+ return (major);
+ }
+
+ /* We've got a gss cred handle that is a kerberos cred handle. */
+ cred = (krb5_gss_cred_id_t)*cred_handle;
+
+ /* If we can't get the time, assume the worst. */
+ if (krb5_timeofday(context, &now)) {
+ (void) krb5_gss_release_cred(&mntmp, cred_handle);
+ return (GSS_S_CREDENTIALS_EXPIRED);
+ }
+
+ /*
+ * Try and get root's cred in the cache using keytab.
+ *
+ * First try "root", then try "host", and finally try the Security
+ * Account Manager (SAM) for MS AD interop.
+ */
+ /* If root's cred has expired re-get it */
+ if (cred->expire && (cred->expire < now + MIN_REFRESH_TIME) &&
+ (uid == ROOT_UID)) {
+ (void) krb5_gss_release_cred(&mntmp, cred_handle);
+
+ major = load_root_cred_using_keytab(minor_status,
+ context, "root", KRB5_NT_SRV_HST);
+
+ if (major != GSS_S_COMPLETE)
+ major = load_root_cred_using_keytab(minor_status,
+ context, "host",
+ KRB5_NT_SRV_HST);
+
+ if (major != GSS_S_COMPLETE) {
+ char *name;
+ krb5_error_code code;
+
+ code = get_sam_account_name(&name);
+ if (code == 0) {
+ major = load_root_cred_using_keytab(minor_status,
+ context, name,
+ KRB5_NT_PRINCIPAL);
+ free(name);
+ }
+ }
+
+ if (major != GSS_S_COMPLETE)
+ return (major);
+
+ major = kg_get_defcred(minor_status, cred_handle);
+ if (major != GSS_S_COMPLETE)
+ return (major);
+
+ /* Any body else is SOL unless we can renew their credential cache */
+ } else if (cred->expire && (cred->expire < now + MIN_RENEW_TIME) &&
+ (cred->expire > now)) {
+ (void) krb5_gss_release_cred(&mntmp, cred_handle);
+
+ major = renew_ccache(minor_status, context, uid);
+ if ((major != GSS_S_COMPLETE) &&
+ (major != KDC_ERR_BADOPTION))
+ return (major);
+
+ major = kg_get_defcred(minor_status, cred_handle);
+ if (major != GSS_S_COMPLETE)
+ return (major);
+
+ }
+
+ /* Otherwise we got non expired creds */
+ return (GSS_S_COMPLETE);
+}
diff -u -r old/src/lib/krb5/keytab/Makefile.in new/src/lib/krb5/keytab/Makefile.in
--- old/src/lib/krb5/keytab/Makefile.in 2015-06-03 18:20:34.099323499 -0500
+++ new/src/lib/krb5/keytab/Makefile.in 2015-06-04 11:49:24.702959055 -0500
@@ -13,6 +13,7 @@
ktremove.o \
ktfns.o \
kt_file.o \
+ kt_findrealm.o \
kt_memory.o \
kt_srvtab.o \
read_servi.o \
@@ -26,6 +27,7 @@
$(OUTPRE)ktremove.$(OBJEXT) \
$(OUTPRE)ktfns.$(OBJEXT) \
$(OUTPRE)kt_file.$(OBJEXT) \
+ $(OUTPRE)kt_findrealm.$(OBJEXT) \
$(OUTPRE)kt_memory.$(OBJEXT) \
$(OUTPRE)kt_srvtab.$(OBJEXT) \
$(OUTPRE)read_servi.$(OBJEXT) \
@@ -39,6 +41,7 @@
$(srcdir)/ktremove.c \
$(srcdir)/ktfns.c \
$(srcdir)/kt_file.c \
+ $(srcdir)/kt_findrealm.c \
$(srcdir)/kt_memory.c \
$(srcdir)/kt_srvtab.c \
$(srcdir)/read_servi.c \
diff -u -r old/src/lib/krb5/os/expand_path.c new/src/lib/krb5/os/expand_path.c
--- old/src/lib/krb5/os/expand_path.c 2015-05-08 18:27:02.000000000 -0500
+++ new/src/lib/krb5/os/expand_path.c 2015-06-04 12:26:15.614110414 -0500
@@ -291,7 +291,7 @@
expand_userid(krb5_context context, PTYPE param, const char *postfix,
char **str)
{
- if (asprintf(str, "%lu", (unsigned long)getuid()) < 0)
+ if (asprintf(str, "%lu", (unsigned long)krb5_getuid()) < 0)
return ENOMEM;
return 0;
}
diff -u -r old/src/lib/krb5/os/Makefile.in new/src/lib/krb5/os/Makefile.in
--- old/src/lib/krb5/os/Makefile.in 2015-05-08 18:27:02.000000000 -0500
+++ new/src/lib/krb5/os/Makefile.in 2015-06-04 12:00:15.668542036 -0500
@@ -20,6 +20,7 @@
gen_port.o \
genaddrs.o \
gen_rname.o \
+ getuid.o \
hostaddr.o \
hostrealm.o \
hostrealm_dns.o \
@@ -44,6 +45,7 @@
read_msg.o \
read_pwd.o \
realm_dom.o \
+ safechown.o \
sendto_kdc.o \
sn2princ.o \
thread_safe.o \
@@ -66,6 +68,7 @@
$(OUTPRE)gen_port.$(OBJEXT) \
$(OUTPRE)genaddrs.$(OBJEXT) \
$(OUTPRE)gen_rname.$(OBJEXT) \
+ $(OUTPRE)getuid.$(OBJEXT) \
$(OUTPRE)hostaddr.$(OBJEXT) \
$(OUTPRE)hostrealm.$(OBJEXT) \
$(OUTPRE)hostrealm_dns.$(OBJEXT) \
@@ -90,6 +93,7 @@
$(OUTPRE)read_msg.$(OBJEXT) \
$(OUTPRE)read_pwd.$(OBJEXT) \
$(OUTPRE)realm_dom.$(OBJEXT) \
+ $(OUTPRE)safechown.$(OBJEXT) \
$(OUTPRE)sendto_kdc.$(OBJEXT) \
$(OUTPRE)sn2princ.$(OBJEXT) \
$(OUTPRE)thread_safe.$(OBJEXT) \
@@ -112,6 +116,7 @@
$(srcdir)/gen_port.c \
$(srcdir)/genaddrs.c \
$(srcdir)/gen_rname.c \
+ $(srcdir)/getuid.c \
$(srcdir)/hostaddr.c \
$(srcdir)/hostrealm.c \
$(srcdir)/hostrealm_dns.c \
@@ -136,6 +141,7 @@
$(srcdir)/read_pwd.c \
$(srcdir)/realm_dom.c \
$(srcdir)/port2ip.c \
+ $(srcdir)/safechown.c \
$(srcdir)/sendto_kdc.c \
$(srcdir)/sn2princ.c \
$(srcdir)/thread_safe.c \