#
# This patch provides support in kerberos for root acquiring a default cred via
# either a root, host service principal or sam account name keys in the keytab
# if root doesn't have a cred already. Note that if root has a client keytab
# provisioned then that will be used instead.
#
# This is Solaris specific behavior that MIT will not take upstream.
# Patch source: in-house
#
@@ -77,6 +77,7 @@
#else
#include <strings.h>
#endif
+#include <ctype.h>
#ifdef USE_LEASH
#ifdef _WIN64
@@ -88,6 +89,9 @@ static void (*pLeash_AcquireInitialTicketsIfNeeded)(krb5_context,krb5_principal,
static HANDLE hLeashDLL = INVALID_HANDLE_VALUE;
#endif
+/* for solaris root fallback check */
+static char defktname[BUFSIZ];
+
#ifndef LEAN_CLIENT
k5_mutex_t gssint_krb5_keytab_lock = K5_MUTEX_PARTIAL_INITIALIZER;
static char *krb5_gss_keytab = NULL;
@@ -590,6 +594,151 @@ kg_cred_set_initial_refresh(krb5_context context, krb5_gss_cred_id_rec *cred,
set_refresh_time(context, cred->ccache, refresh);
}
+#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);
+}
+
+krb5_error_code krb5_kt_find_realm(krb5_context, krb5_keytab, krb5_principal,
+ krb5_data *);
+
+static krb5_error_code
+get_root_initcred_keytab(krb5_context context,
+ krb5_creds *kcreds,
+ krb5_gss_cred_id_rec *gsscred,
+ const char *name,
+ krb5_int32 type,
+ krb5_get_init_creds_opt *opt)
+{
+ krb5_principal client_princ;
+ krb5_error_code code;
+
+ if (type == KRB5_NT_SRV_HST) {
+ code = krb5_sname_to_principal(context, NULL, name, type,
+ &client_princ);
+ } else {
+ /* Assuming KRB5_NT_PRINCIPAL */
+ code = krb5_parse_name(context, name, &client_princ);
+ }
+ if (code)
+ return code;
+
+ if (krb5_is_referral_realm(&client_princ->realm)) {
+ krb5_data realm;
+
+ code = krb5_kt_find_realm(context, gsscred->client_keytab,
+ client_princ, &realm);
+ if (code == 0) {
+ krb5_free_data_contents(context, &client_princ->realm);
+ client_princ->realm.length = realm.length;
+ client_princ->realm.data = realm.data;
+ } else {
+ /* Try to set a useful error message */
+ char *princ_name = NULL;
+ char kt_name[BUFSIZ];
+
+ (void) krb5_unparse_name(context, client_princ, &princ_name);
+ (void) krb5_kt_get_name(context, gsscred->client_keytab, kt_name,
+ BUFSIZ);
+ krb5_set_error_message(context, code,
+ _("Failed to find realm for %s in "
+ "keytab %s"),
+ princ_name != NULL ? princ_name : "unknown",
+ kt_name);
+ krb5_free_unparsed_name(context, princ_name);
+ }
+ }
+ if (code)
+ goto cleanup;
+
+ code = krb5_get_init_creds_keytab(context, kcreds, client_princ,
+ gsscred->client_keytab, 0, NULL, opt);
+ if (code == 0) {
+ /* set the gsscred name to that of the princ for which an init cred was
+ * acquired. */
+ if (gsscred->name != NULL)
+ (void) kg_release_name(context, &gsscred->name);
+
+ code = kg_init_name(context, client_princ, NULL, NULL, NULL,
+ KG_INIT_NAME_NO_COPY, &gsscred->name);
+ /* Since KG_INIT_NAME_NO_COPY is set do not free client_princ if
+ * kg_init_name succeeds. */
+ if (code == 0)
+ return 0;
+ else
+ krb5_free_cred_contents(context, kcreds);
+ }
+
+cleanup:
+ krb5_free_principal(context, client_princ);
+ return code;
+}
+
+/*
+ * This implements long time Solaris behavior where processes running as root
+ * will try to acquire an init cred via the default/system keytab. The root,
+ * host and SAM princs are tried in that order until one succeeds or they all
+ * fail.
+ */
+static krb5_error_code
+root_init_cred_kt_fallback(krb5_context context,
+ krb5_creds *kcreds,
+ krb5_gss_cred_id_rec *gsscred,
+ krb5_get_init_creds_opt *opt)
+{
+ char *sam_name = NULL;
+ krb5_error_code code;
+
+ /* Try the root/<FQDN> service princ in system keytab */
+ code = get_root_initcred_keytab(context, kcreds, gsscred, "root",
+ KRB5_NT_SRV_HST, opt);
+ if (code == 0)
+ goto out;
+
+ /* Try the host/<FQDN> service princ in system keytab if the root princ
+ * wasn't found */
+ code = get_root_initcred_keytab(context, kcreds, gsscred, "host",
+ KRB5_NT_SRV_HST, opt);
+ if (code == 0)
+ goto out;
+
+ /* Try the SAM account princ in system keytab if the host service princ
+ * wasn't found for MS interop sake */
+ code = get_sam_account_name(&sam_name);
+ if (code)
+ goto out;
+
+ code = get_root_initcred_keytab(context, kcreds, gsscred, sam_name,
+ KRB5_NT_PRINCIPAL, opt);
+ free(sam_name);
+out:
+ return code;
+}
+
/* Get initial credentials using the supplied password or client keytab. */
static krb5_error_code
get_initial_cred(krb5_context context, krb5_gss_cred_id_rec *cred)
@@ -609,8 +758,41 @@ get_initial_cred(krb5_context context, krb5_gss_cred_id_rec *cred)
cred->password, NULL, NULL, 0,
NULL, opt);
} else if (cred->client_keytab != NULL) {
- code = krb5_get_init_creds_keytab(context, &creds, cred->name->princ,
- cred->client_keytab, 0, NULL, opt);
+ if (krb5_getuid() == 0) {
+ char clientktname[BUFSIZ];
+
+ /* assuming we only need to get the default keytab name once */
+ if (defktname[0] == '\0') {
+ code = krb5_kt_default_name(context, defktname,
+ sizeof(defktname));
+ if (code)
+ goto cleanup;
+ }
+
+ code = krb5_kt_get_name(context, cred->client_keytab, clientktname,
+ sizeof(clientktname));
+ if (code)
+ goto cleanup;
+
+ /*
+ * If the client keytab name is the same as the system default
+ * keytab and we are root then we need to use the Solaris root
+ * fallback behavior in root_init_cred_kt_fallback().
+ */
+ if (strcmp(defktname, clientktname) == 0) {
+ code = root_init_cred_kt_fallback(context, &creds, cred, opt);
+ } else {
+ code = krb5_get_init_creds_keytab(context, &creds,
+ cred->name->princ,
+ cred->client_keytab, 0, NULL,
+ opt);
+ }
+ } else {
+ code = krb5_get_init_creds_keytab(context, &creds,
+ cred->name->princ,
+ cred->client_keytab, 0, NULL,
+ opt);
+ }
} else {
code = KRB5_KT_NOTFOUND;
}
@@ -700,6 +882,23 @@ acquire_init_cred(krb5_context context,
krb5_clear_error_message(context);
code = 0;
}
+ /*
+ * The logic below is involved in providing support for Solaris
+ * behavior where root processes will fall back to acquiring an initial
+ * cred via the system/default keytab. The idea is that if the
+ * client_keytab could not be resolved or it doesn't exist then set the
+ * client_keytab field to the system/default keytab.
+ */
+ if (krb5_getuid() == 0) {
+ if (cred->client_keytab == NULL ||
+ krb5_kt_have_content(context, cred->client_keytab) != 0) {
+
+ if (cred->client_keytab != NULL)
+ krb5_kt_close(context, cred->client_keytab);
+
+ code = krb5_kt_default(context, &cred->client_keytab);
+ }
+ }
}
if (code)
goto error;
@@ -13,6 +13,7 @@ STLIBOBJS= \
ktfns.o \
+ kt_findrealm.o \
@@ -26,6 +27,7 @@ OBJS= \
$(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 @@ SRCS= \
$(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 \