components/krb5/patches/037-root-defcred.patch
author Will Fiveash <will.fiveash@oracle.com>
Wed, 24 Feb 2016 10:43:57 -0600
changeset 5490 9bf0bc57423a
child 6599 1d033832c5e7
permissions -rw-r--r--
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 \