X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=utils%2Fgssd%2Fkrb5_util.c;h=20b55b37c0f38405794be18f4bebd8ac089dccd2;hp=d4ee631b1a00be5549b99ca281d0ac47e91286a5;hb=128bca853fc6df20a87d4d3dfe12c1b77204d673;hpb=533e5a05a172306462ac6c11d222a18fe562ec6d diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c index d4ee631..20b55b3 100644 --- a/utils/gssd/krb5_util.c +++ b/utils/gssd/krb5_util.c @@ -91,10 +91,14 @@ */ +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include "config.h" + #include #include #include @@ -120,21 +124,24 @@ #include "gssd.h" #include "err_util.h" #include "gss_util.h" -#include "gss_oids.h" #include "krb5_util.h" /* Global list of principals/cache file names for machine credentials */ struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL; +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES +int limit_to_legacy_enctypes = 0; +#endif + /*==========================*/ /*=== Internal routines ===*/ /*==========================*/ static int select_krb5_ccache(const struct dirent *d); static int gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, - struct dirent **d); + const char **cctype, struct dirent **d); static int gssd_get_single_krb5_cred(krb5_context context, - krb5_keytab kt, struct gssd_k5_kt_princ *ple); + krb5_keytab kt, struct gssd_k5_kt_princ *ple, int nocache); static int query_krb5_ccache(const char* cred_cache, char **ret_princname, char **ret_realm); @@ -162,17 +169,17 @@ select_krb5_ccache(const struct dirent *d) /* * Look in directory "dirname" for files that look like they - * are Kerberos Credential Cache files for a given UID. Return - * non-zero and the dirent pointer for the entry most likely to be - * what we want. Otherwise, return zero and no dirent pointer. - * The caller is responsible for freeing the dirent if one is returned. + * are Kerberos Credential Cache files for a given UID. * - * Returns: - * 0 => could not find an existing entry - * 1 => found an existing entry + * Returns 0 if a valid-looking entry is found. "*cctype" is + * set to the name of the cache type. A pointer to the dirent + * is planted in "*d". Caller must free "*d" with free(3). + * + * Otherwise, a negative errno is returned. */ static int -gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) +gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, + const char **cctype, struct dirent **d) { struct dirent **namelist; int n; @@ -183,9 +190,10 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) char buf[1030]; char *princname = NULL; char *realm = NULL; - int score, best_match_score = 0; + int score, best_match_score = 0, err = -EACCES; memset(&best_match_stat, 0, sizeof(best_match_stat)); + *cctype = NULL; *d = NULL; n = scandir(dirname, &namelist, select_krb5_ccache, 0); if (n < 0) { @@ -197,46 +205,64 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) for (i = 0; i < n; i++) { snprintf(statname, sizeof(statname), "%s/%s", dirname, namelist[i]->d_name); - printerr(3, "CC file '%s' being considered, " + printerr(3, "CC '%s' being considered, " "with preferred realm '%s'\n", statname, preferred_realm ? preferred_realm : ""); - snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, - namelist[i]->d_name); if (lstat(statname, &tmp_stat)) { - printerr(0, "Error doing stat on file '%s'\n", + printerr(0, "Error doing stat on '%s'\n", statname); free(namelist[i]); continue; } /* Only pick caches owned by the user (uid) */ if (tmp_stat.st_uid != uid) { - printerr(3, "CC file '%s' owned by %u, not %u\n", + printerr(3, "CC '%s' owned by %u, not %u\n", statname, tmp_stat.st_uid, uid); free(namelist[i]); continue; } - if (!S_ISREG(tmp_stat.st_mode)) { - printerr(3, "CC file '%s' is not a regular file\n", + if (!S_ISREG(tmp_stat.st_mode) && + !S_ISDIR(tmp_stat.st_mode)) { + printerr(3, "CC '%s' is not a regular " + "file or directory\n", statname); free(namelist[i]); continue; } - if (!query_krb5_ccache(buf, &princname, &realm)) { - printerr(3, "CC file '%s' is expired or corrupt\n", + if (uid == 0 && !root_uses_machine_creds && + strstr(namelist[i]->d_name, "_machine_")) { + printerr(3, "CC '%s' not available to root\n", statname); free(namelist[i]); continue; } + if (S_ISDIR(tmp_stat.st_mode)) { + *cctype = "DIR"; + } else + if (S_ISREG(tmp_stat.st_mode)) { + *cctype = "FILE"; + } else { + continue; + } + snprintf(buf, sizeof(buf), "%s:%s/%s", *cctype, + dirname, namelist[i]->d_name); + if (!query_krb5_ccache(buf, &princname, &realm)) { + printerr(3, "CC '%s' is expired or corrupt\n", + buf); + free(namelist[i]); + err = -EKEYEXPIRED; + continue; + } score = 0; if (preferred_realm && strcmp(realm, preferred_realm) == 0) score++; - printerr(3, "CC file '%s'(%s@%s) passed all checks and" + printerr(3, "CC '%s'(%s@%s) passed all checks and" " has mtime of %u\n", - statname, princname, realm, + buf, princname, realm, tmp_stat.st_mtime); /* * if more than one match is found, return the most @@ -270,10 +296,11 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) else { free(namelist[i]); } - printerr(3, "CC file '%s/%s' is our " + printerr(3, "CC '%s:%s/%s' is our " "current best match " "with mtime of %u\n", - dirname, best_match_dir->d_name, + cctype, dirname, + best_match_dir->d_name, best_match_stat.st_mtime); } free(princname); @@ -281,67 +308,13 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) } free(namelist); } - if (found) - { + if (found) { *d = best_match_dir; + return 0; } - return found; -} - - -#ifdef HAVE_SET_ALLOWABLE_ENCTYPES -/* - * this routine obtains a credentials handle via gss_acquire_cred() - * then calls gss_krb5_set_allowable_enctypes() to limit the encryption - * types negotiated. - * - * XXX Should call some function to determine the enctypes supported - * by the kernel. (Only need to do that once!) - * - * Returns: - * 0 => all went well - * -1 => there was an error - */ - -int -limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid) -{ - u_int maj_stat, min_stat; - gss_cred_id_t credh; - gss_OID_set_desc desired_mechs; - krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC, - ENCTYPE_DES_CBC_MD5, - ENCTYPE_DES_CBC_MD4 }; - int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]); - - /* We only care about getting a krb5 cred */ - desired_mechs.count = 1; - desired_mechs.elements = &krb5oid; - - maj_stat = gss_acquire_cred(&min_stat, NULL, 0, - &desired_mechs, GSS_C_INITIATE, - &credh, NULL, NULL); - - if (maj_stat != GSS_S_COMPLETE) { - if (get_verbosity() > 0) - pgsserr("gss_acquire_cred", - maj_stat, min_stat, &krb5oid); - return -1; - } - - maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid, - num_enctypes, &enctypes); - if (maj_stat != GSS_S_COMPLETE) { - pgsserr("gss_set_allowable_enctypes", - maj_stat, min_stat, &krb5oid); - gss_release_cred(&min_stat, &credh); - return -1; - } - sec->cred = credh; - return 0; + return err; } -#endif /* HAVE_SET_ALLOWABLE_ENCTYPES */ /* * Obtain credentials via a key in the keytab given @@ -356,7 +329,8 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid) static int gssd_get_single_krb5_cred(krb5_context context, krb5_keytab kt, - struct gssd_k5_kt_princ *ple) + struct gssd_k5_kt_princ *ple, + int nocache) { #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS krb5_get_init_creds_opt *init_opts = NULL; @@ -372,10 +346,16 @@ gssd_get_single_krb5_cred(krb5_context context, time_t now = time(0); char *cache_type; char *pname = NULL; + char *k5err = NULL; memset(&my_creds, 0, sizeof(my_creds)); - if (ple->ccname && ple->endtime > now) { + /* + * Workaround for clock skew among NFS server, NFS client and KDC + * 300 because clock skew must be within 300sec for kerberos + */ + now += 300; + if (ple->ccname && ple->endtime > now && !nocache) { printerr(2, "INFO: Credentials in CC '%s' are good until %d\n", ple->ccname, ple->endtime); code = 0; @@ -394,8 +374,8 @@ gssd_get_single_krb5_cred(krb5_context context, #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS code = krb5_get_init_creds_opt_alloc(context, &init_opts); if (code) { - printerr(0, "ERROR: %s allocating gic options\n", - gssd_k5_err_msg(context, code)); + k5err = gssd_k5_err_msg(context, code); + printerr(0, "ERROR: %s allocating gic options\n", k5err); goto out; } if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1)) @@ -422,9 +402,9 @@ gssd_get_single_krb5_cred(krb5_context context, if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ, kt, 0, NULL, opts))) { + k5err = gssd_k5_err_msg(context, code); printerr(1, "WARNING: %s while getting initial ticket for " - "principal '%s' using keytab '%s'\n", - gssd_k5_err_msg(context, code), + "principal '%s' using keytab '%s'\n", k5err, pname ? pname : "", kt_name); goto out; } @@ -452,19 +432,21 @@ gssd_get_single_krb5_cred(krb5_context context, goto out; } if ((code = krb5_cc_resolve(context, cc_name, &ccache))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "ERROR: %s while opening credential cache '%s'\n", - gssd_k5_err_msg(context, code), cc_name); + k5err, cc_name); goto out; } if ((code = krb5_cc_initialize(context, ccache, ple->princ))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "ERROR: %s while initializing credential " - "cache '%s'\n", gssd_k5_err_msg(context, code), - cc_name); + "cache '%s'\n", k5err, cc_name); goto out; } if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "ERROR: %s while storing credentials in '%s'\n", - gssd_k5_err_msg(context, code), cc_name); + k5err, cc_name); goto out; } @@ -481,6 +463,7 @@ gssd_get_single_krb5_cred(krb5_context context, if (ccache) krb5_cc_close(context, ccache); krb5_free_cred_contents(context, &my_creds); + free(k5err); return (code); } @@ -652,24 +635,32 @@ out: * and has *any* instance (hostname), return 1. * Otherwise return 0, indicating no match. */ +#ifdef HAVE_KRB5 static int -realm_and_service_match(krb5_context context, krb5_principal p, - const char *realm, const char *service) +realm_and_service_match(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; + + return 0; +} #else +static int +realm_and_service_match(krb5_context context, krb5_principal p, + const char *realm, const char *service) +{ 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) @@ -677,9 +668,10 @@ realm_and_service_match(krb5_context context, krb5_principal p, if ((strcmp(realm, p->realm) == 0) && (strcmp(service, name) == 0)) return 1; -#endif + return 0; } +#endif /* * Search the given keytab file looking for an entry with the given @@ -701,9 +693,10 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, krb5_kt_cursor cursor; krb5_error_code code; struct gssd_k5_kt_princ *ple; - int retval = -1; + int retval = -1, status; char kt_name[BUFSIZ]; char *pname; + char *k5err = NULL; if (found == NULL) { retval = EINVAL; @@ -717,15 +710,15 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, * 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", - gssd_k5_err_msg(context, code)); + k5err = gssd_k5_err_msg(context, code); + printerr(0, "ERROR: %s attempting to get keytab name\n", k5err); retval = code; goto out; } if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "ERROR: %s while beginning keytab scan " - "for keytab '%s'\n", - gssd_k5_err_msg(context, code), kt_name); + "for keytab '%s'\n", k5err, kt_name); retval = code; goto out; } @@ -733,17 +726,22 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) { if ((code = krb5_unparse_name(context, kte->principal, &pname))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "WARNING: Skipping keytab entry because " "we failed to unparse principal name: %s\n", - gssd_k5_err_msg(context, code)); + k5err); 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))) { +#ifdef HAVE_KRB5 + status = realm_and_service_match(kte->principal, realm, service); +#else + status = realm_and_service_match(context, kte->principal, realm, service); +#endif + if (status) { printerr(4, "We WILL use this entry (%s)\n", pname); ple = get_ple_by_princ(context, kte->principal); /* @@ -769,49 +767,63 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, } if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "WARNING: %s while ending keytab scan for " - "keytab '%s'\n", - gssd_k5_err_msg(context, code), kt_name); + "keytab '%s'\n", k5err, kt_name); } retval = 0; out: + free(k5err); return retval; } /* - * Find a keytab entry to use for a given target hostname. + * Find a keytab entry to use for a given target realm. * Tries to find the most appropriate keytab to use given the * name of the host we are trying to connect with. + * + * Note: the tgtname contains a hostname in the realm that we + * are authenticating to. It may, or may not be the same as + * the server hostname. */ static int -find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, - krb5_keytab_entry *kte) +find_keytab_entry(krb5_context context, krb5_keytab kt, const char *tgtname, + krb5_keytab_entry *kte, const char **svcnames) { krb5_error_code code; - const char *svcnames[] = { "root", "nfs", "host", NULL }; char **realmnames = NULL; char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST]; + char myhostad[NI_MAXHOST+1]; int i, j, retval; char *default_realm = NULL; char *realm; + char *k5err = NULL; int tried_all = 0, tried_default = 0; krb5_principal princ; /* Get full target hostname */ - retval = get_full_hostname(hostname, targethostname, + retval = get_full_hostname(tgtname, 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", - gssd_k5_err_msg(context, retval)); + if (gethostname(myhostname, sizeof(myhostname)) == -1) { + retval = errno; + k5err = gssd_k5_err_msg(context, retval); + printerr(1, "%s while getting local hostname\n", k5err); goto out; } + + /* Compute the active directory machine name HOST$ */ + strcpy(myhostad, myhostname); + for (i = 0; myhostad[i] != 0; ++i) + myhostad[i] = toupper(myhostad[i]); + myhostad[i] = '$'; + myhostad[i+1] = 0; + retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname)); if (retval) goto out; @@ -819,8 +831,8 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, code = krb5_get_default_realm(context, &default_realm); if (code) { retval = code; - printerr(1, "%s while getting default realm name\n", - gssd_k5_err_msg(context, code)); + k5err = gssd_k5_err_msg(context, code); + printerr(1, "%s while getting default realm name\n", k5err); goto out; } @@ -832,8 +844,9 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, */ code = krb5_get_host_realm(context, targethostname, &realmnames); if (code) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n", - gssd_k5_err_msg(context, code), targethostname); + k5err, targethostname); retval = code; goto out; } @@ -855,32 +868,47 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, 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", - gssd_k5_err_msg(context, code), + char spn[300]; + + /* + * The special svcname "$" means 'try the active + * directory machine account' + */ + if (strcmp(svcnames[j],"$") == 0) { + snprintf(spn, sizeof(spn), "%s@%s", myhostad, realm); + code = krb5_build_principal_ext(context, &princ, + strlen(realm), + realm, + strlen(myhostad), + myhostad, + NULL); + } else { + snprintf(spn, sizeof(spn), "%s/%s@%s", svcnames[j], myhostname, realm); + code = krb5_build_principal_ext(context, &princ, + strlen(realm), + realm, + strlen(svcnames[j]), + svcnames[j], + strlen(myhostname), + myhostname, + NULL); + } + + if (code) { + k5err = gssd_k5_err_msg(context, code); + printerr(1, "%s while building principal for '%s'\n", + k5err, spn); 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", - gssd_k5_err_msg(context, code), - svcnames[j], myhostname, realm); + k5err = gssd_k5_err_msg(context, code); + printerr(3, "%s while getting keytab entry for '%s'\n", + k5err, spn); } else { - printerr(3, "Success getting keytab entry for " - "'%s/%s@%s'\n", - svcnames[j], myhostname, realm); + printerr(3, "Success getting keytab entry for '%s'\n",spn); retval = 0; goto out; } @@ -892,6 +920,8 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, */ for (j = 0; svcnames[j] != NULL; j++) { int found = 0; + if (strcmp(svcnames[j],"$") == 0) + continue; code = gssd_search_krb5_keytab(context, kt, realm, svcnames[j], &found, kte); if (!code && found) { @@ -911,6 +941,7 @@ out: k5_free_default_realm(context, default_realm); if (realmnames) krb5_free_host_realm(context, realmnames); + free(k5err); return retval; } @@ -1011,25 +1042,48 @@ err_cache: * given only a UID. We really need more information, but we * do the best we can. * - * Returns: - * 0 => a ccache was found - * 1 => no ccache was found + * Returns 0 if a ccache was found, or a negative errno otherwise. */ int -gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname) +gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirpattern) { - char buf[MAX_NETOBJ_SZ]; + char buf[MAX_NETOBJ_SZ], dirname[PATH_MAX]; + const char *cctype; struct dirent *d; + int err, i, j; printerr(2, "getting credentials for client with uid %u for " "server %s\n", uid, servername); - memset(buf, 0, sizeof(buf)); - if (gssd_find_existing_krb5_ccache(uid, dirname, &d)) { - snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name); - free(d); + + for (i = 0, j = 0; dirpattern[i] != '\0'; i++) { + switch (dirpattern[i]) { + case '%': + switch (dirpattern[i + 1]) { + case '%': + dirname[j++] = dirpattern[i]; + i++; + break; + case 'U': + j += sprintf(dirname + j, "%lu", + (unsigned long) uid); + i++; + break; + } + break; + default: + dirname[j++] = dirpattern[i]; + break; + } } - else - return 1; + dirname[j] = '\0'; + + err = gssd_find_existing_krb5_ccache(uid, dirname, &cctype, &d); + if (err) + return err; + + snprintf(buf, sizeof(buf), "%s:%s/%s", cctype, dirname, d->d_name); + free(d); + printerr(2, "using %s as credentials cache for client with " "uid %u for server %s\n", buf, uid, servername); gssd_set_krb5_ccache_name(buf); @@ -1082,7 +1136,8 @@ gssd_get_krb5_machine_cred_list(char ***list) for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) { if (ple->ccname) { /* Make sure cred is up-to-date before returning it */ - retval = gssd_refresh_krb5_machine_credential(NULL, ple); + retval = gssd_refresh_krb5_machine_credential(NULL, ple, + NULL, NULL); if (retval) continue; if (i + 1 > listsize) { @@ -1136,11 +1191,12 @@ gssd_destroy_krb5_machine_creds(void) krb5_error_code code = 0; krb5_ccache ccache; struct gssd_k5_kt_princ *ple; + char *k5err = NULL; code = krb5_init_context(&context); if (code) { - printerr(0, "ERROR: %s while initializing krb5\n", - gssd_k5_err_msg(NULL, code)); + k5err = gssd_k5_err_msg(NULL, code); + printerr(0, "ERROR: %s while initializing krb5\n", k5err); goto out; } @@ -1148,20 +1204,22 @@ gssd_destroy_krb5_machine_creds(void) if (!ple->ccname) continue; if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "WARNING: %s while resolving credential " - "cache '%s' for destruction\n", - gssd_k5_err_msg(context, code), ple->ccname); + "cache '%s' for destruction\n", k5err, + ple->ccname); continue; } if ((code = krb5_cc_destroy(context, ccache))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "WARNING: %s while destroying credential " - "cache '%s'\n", - gssd_k5_err_msg(context, code), ple->ccname); + "cache '%s'\n", k5err, ple->ccname); } } - out: krb5_free_context(context); + out: + free(k5err); } /* @@ -1169,41 +1227,57 @@ gssd_destroy_krb5_machine_creds(void) */ int gssd_refresh_krb5_machine_credential(char *hostname, - struct gssd_k5_kt_princ *ple) + struct gssd_k5_kt_princ *ple, + char *service, + char *tgtname) { krb5_error_code code = 0; krb5_context context; krb5_keytab kt = NULL;; int retval = 0; + char *k5err = NULL; + const char *svcnames[5] = { "$", "root", "nfs", "host", NULL }; + /* + * If a specific service name was specified, use it. + * Otherwise, use the default list. + */ + if (service != NULL && strcmp(service, "*") != 0) { + svcnames[0] = service; + svcnames[1] = NULL; + } if (hostname == NULL && ple == NULL) return EINVAL; code = krb5_init_context(&context); if (code) { + k5err = gssd_k5_err_msg(NULL, code); printerr(0, "ERROR: %s: %s while initializing krb5 context\n", - __FUNCTION__, gssd_k5_err_msg(NULL, code)); + __func__, k5err); retval = code; goto out; } if ((code = krb5_kt_resolve(context, keytabfile, &kt))) { + k5err = gssd_k5_err_msg(context, code); printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n", - __FUNCTION__, gssd_k5_err_msg(context, code), - keytabfile); - goto out; + __func__, k5err, keytabfile); + goto out_free_context; } if (ple == NULL) { krb5_keytab_entry kte; - code = find_keytab_entry(context, kt, hostname, &kte); + if (tgtname == NULL) + tgtname = hostname; + + code = find_keytab_entry(context, kt, tgtname, &kte, svcnames); 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; + goto out_free_kt; } ple = get_ple_by_princ(context, kte.principal); @@ -1219,37 +1293,44 @@ gssd_refresh_krb5_machine_credential(char *hostname, __FUNCTION__, pname ? pname : "", hostname); if (pname) k5_free_unparsed_name(context, pname); - goto out; + goto out_free_kt; } } - retval = gssd_get_single_krb5_cred(context, kt, ple); -out: - if (kt) - krb5_kt_close(context, kt); + retval = gssd_get_single_krb5_cred(context, kt, ple, 0); +out_free_kt: + krb5_kt_close(context, kt); +out_free_context: krb5_free_context(context); +out: + free(k5err); return retval; } /* * A common routine for getting the Kerberos error message */ -const char * +char * gssd_k5_err_msg(krb5_context context, krb5_error_code code) { - const char *msg = NULL; + const char *origmsg; + char *msg = NULL; + #if HAVE_KRB5_GET_ERROR_MESSAGE - if (context != NULL) - msg = krb5_get_error_message(context, code); + if (context != NULL) { + origmsg = krb5_get_error_message(context, code); + msg = strdup(origmsg); + krb5_free_error_message(context, origmsg); + } #endif if (msg != NULL) return msg; #if HAVE_KRB5 - return error_message(code); + return strdup(error_message(code)); #else if (context != NULL) - return krb5_get_err_text(context, code); + return strdup(krb5_get_err_text(context, code)); else - return error_message(code); + return strdup(error_message(code)); #endif } @@ -1268,3 +1349,68 @@ gssd_k5_get_default_realm(char **def_realm) krb5_free_context(context); } + +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES +/* + * this routine obtains a credentials handle via gss_acquire_cred() + * then calls gss_krb5_set_allowable_enctypes() to limit the encryption + * types negotiated. + * + * XXX Should call some function to determine the enctypes supported + * by the kernel. (Only need to do that once!) + * + * Returns: + * 0 => all went well + * -1 => there was an error + */ + +int +limit_krb5_enctypes(struct rpc_gss_sec *sec) +{ + u_int maj_stat, min_stat; + gss_cred_id_t credh; + gss_OID_set_desc desired_mechs; + krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC, + ENCTYPE_DES_CBC_MD5, + ENCTYPE_DES_CBC_MD4 }; + int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]); + extern int num_krb5_enctypes; + extern krb5_enctype *krb5_enctypes; + + /* We only care about getting a krb5 cred */ + desired_mechs.count = 1; + desired_mechs.elements = &krb5oid; + + maj_stat = gss_acquire_cred(&min_stat, NULL, 0, + &desired_mechs, GSS_C_INITIATE, + &credh, NULL, NULL); + + if (maj_stat != GSS_S_COMPLETE) { + if (get_verbosity() > 0) + pgsserr("gss_acquire_cred", + maj_stat, min_stat, &krb5oid); + return -1; + } + + /* + * If we failed for any reason to produce global + * list of supported enctypes, use local default here. + */ + if (krb5_enctypes == NULL || limit_to_legacy_enctypes) + maj_stat = gss_set_allowable_enctypes(&min_stat, credh, + &krb5oid, num_enctypes, enctypes); + else + maj_stat = gss_set_allowable_enctypes(&min_stat, credh, + &krb5oid, num_krb5_enctypes, krb5_enctypes); + + if (maj_stat != GSS_S_COMPLETE) { + pgsserr("gss_set_allowable_enctypes", + maj_stat, min_stat, &krb5oid); + gss_release_cred(&min_stat, &credh); + return -1; + } + sec->cred = credh; + + return 0; +} +#endif /* HAVE_SET_ALLOWABLE_ENCTYPES */