X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=utils%2Fgssd%2Fkrb5_util.c;h=20396e06d8c22fac0901dbb1a55ea8df7776e08b;hp=7ef690bfabcafc7ea5d353ddb3d85122b6accbae;hb=6904f7b7f4f91d52c4bd783e5588e870d32ff021;hpb=72e1cf8784fbcb1bffa28b08f663ea9469634590 diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c index 7ef690b..20396e0 100644 --- a/utils/gssd/krb5_util.c +++ b/utils/gssd/krb5_util.c @@ -103,8 +103,11 @@ #include #include +#include #include #include +#include +#include #include #include #include @@ -135,6 +138,7 @@ static int gssd_have_realm_ple(void *realm); static int gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt, char *kt_name); + /* * Called from the scandir function to weed out potential krb5 * credentials cache files @@ -585,6 +589,403 @@ gssd_set_krb5_ccache_name(char *ccname) #endif } +/* + * Given a principal, find a matching ple structure + */ +static struct gssd_k5_kt_princ * +find_ple_by_princ(krb5_context context, krb5_principal princ) +{ + struct gssd_k5_kt_princ *ple; + + for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) { + if (krb5_principal_compare(context, ple->princ, princ)) + return ple; + } + /* no match found */ + return NULL; +} + +/* + * Create, initialize, and add a new ple structure to the global list + */ +static struct gssd_k5_kt_princ * +new_ple(krb5_context context, krb5_principal princ) +{ + struct gssd_k5_kt_princ *ple = NULL, *p; + krb5_error_code code; + char *default_realm; + int is_default_realm = 0; + + ple = malloc(sizeof(struct gssd_k5_kt_princ)); + if (ple == NULL) + goto outerr; + memset(ple, 0, sizeof(*ple)); + +#ifdef HAVE_KRB5 + ple->realm = strndup(princ->realm.data, + princ->realm.length); +#else + ple->realm = strdup(princ->realm); +#endif + if (ple->realm == NULL) + goto outerr; + code = krb5_copy_principal(context, princ, &ple->princ); + if (code) + goto outerr; + + /* + * Add new entry onto the list (if this is the default + * realm, always add to the front of the list) + */ + + code = krb5_get_default_realm(context, &default_realm); + if (code == 0) { + if (strcmp(ple->realm, default_realm) == 0) + is_default_realm = 1; + k5_free_default_realm(context, default_realm); + } + + if (is_default_realm) { + ple->next = gssd_k5_kt_princ_list; + gssd_k5_kt_princ_list = ple; + } else { + p = gssd_k5_kt_princ_list; + while (p != NULL && p->next != NULL) + p = p->next; + if (p == NULL) + gssd_k5_kt_princ_list = ple; + else + p->next = ple; + } + + return ple; +outerr: + if (ple) { + if (ple->realm) + free(ple->realm); + free(ple); + } + return NULL; +} + +/* + * Given a principal, find an existing ple structure, or create one + */ +static struct gssd_k5_kt_princ * +get_ple_by_princ(krb5_context context, krb5_principal princ) +{ + struct gssd_k5_kt_princ *ple; + + /* Need to serialize list if we ever become multi-threaded! */ + + ple = find_ple_by_princ(context, princ); + if (ple == NULL) { + ple = new_ple(context, princ); + } + + return ple; +} + +/* + * Given a (possibly unqualified) hostname, + * return the fully qualified (lower-case!) hostname + */ +static int +get_full_hostname(const char *inhost, char *outhost, int outhostlen) +{ + struct addrinfo *addrs = NULL; + struct addrinfo hints; + int retval; + char *c; + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_CANONNAME; + + /* Get full target hostname */ + retval = getaddrinfo(inhost, NULL, &hints, &addrs); + if (retval) { + printerr(0, "%s while getting full hostname for '%s'\n", + gai_strerror(retval), inhost); + goto out; + } + strncpy(outhost, addrs->ai_canonname, outhostlen); + freeaddrinfo(addrs); + for (c = outhost; *c != '\0'; c++) + *c = tolower(*c); + + printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost); + retval = 0; +out: + return retval; +} + +/* + * If principal matches the given realm and service name, + * and has *any* instance (hostname), return 1. + * Otherwise return 0, indicating no match. + */ +static int +realm_and_service_match(krb5_context context, krb5_principal p, + const char *realm, const char *service) +{ +#ifdef HAVE_KRB5 + /* Must have two components */ + if (p->length != 2) + return 0; + if ((strlen(realm) == p->realm.length) + && (strncmp(realm, p->realm.data, p->realm.length) == 0) + && (strlen(service) == p->data[0].length) + && (strncmp(service, p->data[0].data, p->data[0].length) == 0)) + return 1; +#else + const char *name, *inst; + + if (p->name.name_string.len != 2) + return 0; + name = krb5_principal_get_comp_string(context, p, 0); + inst = krb5_principal_get_comp_string(context, p, 1); + if (name == NULL || inst == NULL) + return 0; + if ((strcmp(realm, p->realm) == 0) + && (strcmp(service, name) == 0)) + return 1; +#endif + return 0; +} + +/* + * Search the given keytab file looking for an entry with the given + * service name and realm, ignoring hostname (instance). + * + * Returns: + * 0 => No error + * non-zero => An error occurred + * + * If a keytab entry is found, "found" is set to one, and the keytab + * entry is returned in "kte". Otherwise, "found" is zero, and the + * value of "kte" is unpredictable. + */ +static int +gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, + const char *realm, const char *service, + int *found, krb5_keytab_entry *kte) +{ + krb5_kt_cursor cursor; + krb5_error_code code; + struct gssd_k5_kt_princ *ple; + int retval = -1; + char kt_name[BUFSIZ]; + char *pname; + + if (found == NULL) { + retval = EINVAL; + goto out; + } + *found = 0; + + /* + * Look through each entry in the keytab file and determine + * if we might want to use it as machine credentials. If so, + * save info in the global principal list (gssd_k5_kt_princ_list). + */ + if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) { + printerr(0, "ERROR: %s attempting to get keytab name\n", + error_message(code)); + retval = code; + goto out; + } + if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) { + printerr(0, "ERROR: %s while beginning keytab scan " + "for keytab '%s'\n", + error_message(code), kt_name); + retval = code; + goto out; + } + + while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) { + if ((code = krb5_unparse_name(context, kte->principal, + &pname))) { + printerr(0, "WARNING: Skipping keytab entry because " + "we failed to unparse principal name: %s\n", + error_message(code)); + k5_free_kt_entry(context, kte); + continue; + } + printerr(4, "Processing keytab entry for principal '%s'\n", + pname); + /* Use the first matching keytab entry found */ + if ((realm_and_service_match(context, kte->principal, realm, + service))) { + printerr(4, "We WILL use this entry (%s)\n", pname); + ple = get_ple_by_princ(context, kte->principal); + /* + * Return, don't free, keytab entry if + * we were successful! + */ + if (ple == NULL) { + retval = ENOMEM; + k5_free_kt_entry(context, kte); + } else { + retval = 0; + *found = 1; + } + k5_free_unparsed_name(context, pname); + break; + } + else { + printerr(4, "We will NOT use this entry (%s)\n", + pname); + } + k5_free_unparsed_name(context, pname); + k5_free_kt_entry(context, kte); + } + + if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) { + printerr(0, "WARNING: %s while ending keytab scan for " + "keytab '%s'\n", + error_message(code), kt_name); + } + + retval = 0; + out: + return retval; +} + +/* + * Find a keytab entry to use for a given target hostname. + * Tries to find the most appropriate keytab to use given the + * name of the host we are trying to connect with. + */ +static int +find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, + krb5_keytab_entry *kte) +{ + krb5_error_code code; + const char *svcnames[] = { "root", "nfs", "host", NULL }; + char **realmnames = NULL; + char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST]; + int i, j, retval; + char *default_realm = NULL; + char *realm; + int tried_all = 0, tried_default = 0; + krb5_principal princ; + + + /* Get full target hostname */ + retval = get_full_hostname(hostname, targethostname, + sizeof(targethostname)); + if (retval) + goto out; + + /* Get full local hostname */ + retval = gethostname(myhostname, sizeof(myhostname)); + if (retval) { + printerr(1, "%s while getting local hostname\n", + error_message(retval)); + goto out; + } + retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname)); + if (retval) + goto out; + + code = krb5_get_default_realm(context, &default_realm); + if (code) { + retval = code; + printerr(1, "%s while getting default realm name\n", + error_message(code)); + goto out; + } + + /* + * Get the realm name(s) for the target hostname. + * In reality, this function currently only returns a + * single realm, but we code with the assumption that + * someday it may actually return a list. + */ + code = krb5_get_host_realm(context, targethostname, &realmnames); + if (code) { + printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n", + error_message(code), targethostname); + retval = code; + goto out; + } + + /* + * Try the "appropriate" realm first, and if nothing found for that + * realm, try the default realm (if it hasn't already been tried). + */ + i = 0; + realm = realmnames[i]; + while (1) { + if (realm == NULL) { + tried_all = 1; + if (!tried_default) + realm = default_realm; + } + if (tried_all && tried_default) + break; + if (strcmp(realm, default_realm) == 0) + tried_default = 1; + for (j = 0; svcnames[j] != NULL; j++) { + code = krb5_build_principal_ext(context, &princ, + strlen(realm), + realm, + strlen(svcnames[j]), + svcnames[j], + strlen(myhostname), + myhostname, + NULL); + if (code) { + printerr(1, "%s while building principal for " + "'%s/%s@%s'\n", error_message(code), + svcnames[j], myhostname, realm); + continue; + } + code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte); + krb5_free_principal(context, princ); + if (code) { + printerr(3, "%s while getting keytab entry for " + "'%s/%s@%s'\n", error_message(code), + svcnames[j], myhostname, realm); + } else { + printerr(3, "Success getting keytab entry for " + "'%s/%s@%s'\n", + svcnames[j], myhostname, realm); + retval = 0; + goto out; + } + retval = code; + } + /* + * Nothing found with our hostname instance, now look for + * names with any instance (they must have an instance) + */ + for (j = 0; svcnames[j] != NULL; j++) { + int found = 0; + code = gssd_search_krb5_keytab(context, kt, realm, + svcnames[j], &found, kte); + if (!code && found) { + printerr(3, "Success getting keytab entry for " + "%s/*@%s\n", svcnames[j], realm); + retval = 0; + goto out; + } + } + if (!tried_all) { + i++; + realm = realmnames[i]; + } + } +out: + if (default_realm) + k5_free_default_realm(context, default_realm); + if (realmnames) + krb5_free_host_realm(context, realmnames); + return retval; +} + /*==========================*/ /*=== External routines ===*/ /*==========================*/ @@ -836,3 +1237,68 @@ gssd_destroy_krb5_machine_creds(void) krb5_free_context(context); } +/* + * Obtain (or refresh if necessary) Kerberos machine credentials + */ +int +gssd_refresh_krb5_machine_credential(char *hostname, + struct gssd_k5_kt_princ *ple) +{ + krb5_error_code code = 0; + krb5_context context; + krb5_keytab kt = NULL;; + int retval = 0; + + if (hostname == NULL && ple == NULL) + return EINVAL; + + code = krb5_init_context(&context); + if (code) { + printerr(0, "ERROR: %s: %s while initializing krb5 context\n", + __FUNCTION__, error_message(code)); + retval = code; + goto out; + } + + if ((code = krb5_kt_resolve(context, keytabfile, &kt))) { + printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n", + __FUNCTION__, error_message(code), keytabfile); + goto out; + } + + if (ple == NULL) { + krb5_keytab_entry kte; + + code = find_keytab_entry(context, kt, hostname, &kte); + if (code) { + printerr(0, "ERROR: %s: no usable keytab entry found " + "in keytab %s for connection with host %s\n", + __FUNCTION__, keytabfile, hostname); + retval = code; + goto out; + } + + ple = get_ple_by_princ(context, kte.principal); + k5_free_kt_entry(context, &kte); + if (ple == NULL) { + char *pname; + if ((krb5_unparse_name(context, kte.principal, &pname))) { + pname = NULL; + } + printerr(0, "ERROR: %s: Could not locate or create " + "ple struct for principal %s for connection " + "with host %s\n", + __FUNCTION__, pname ? pname : "", + hostname); + if (pname) k5_free_unparsed_name(context, pname); + goto out; + } + } + retval = gssd_get_single_krb5_cred(context, kt, ple); +out: + if (kt) + krb5_kt_close(context, kt); + krb5_free_context(context); + return retval; +} +