From afa859b029d9cd15604ce7d5f88b5a205ea4c774 Mon Sep 17 00:00:00 2001 From: Lukas Hejtmanek Date: Tue, 15 Jul 2008 10:12:39 -0400 Subject: [PATCH] The rpc.gssd scans for any suitable kerberos ticket. In cross-realm environment this may not be the desired behaviour. Therefore a new option, -R preferred realm, is presented so that the rpc.gssd prefers tickets from this realm. By default, the default realm is preferred. Signed-off-by: Lukas Hejtmanek Signed-off-by: Kevin Coffman Signed-off-by: Steve Dickson --- utils/gssd/gssd.c | 11 ++- utils/gssd/gssd.h | 1 + utils/gssd/gssd.man | 7 ++ utils/gssd/krb5_util.c | 160 +++++++++++++++++++++++++++++++++++++---- utils/gssd/krb5_util.h | 1 + 5 files changed, 165 insertions(+), 15 deletions(-) diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c index 2e6f316..6d8f3b9 100644 --- a/utils/gssd/gssd.c +++ b/utils/gssd/gssd.c @@ -61,6 +61,7 @@ char *ccachesearch[GSSD_MAX_CCACHE_SEARCH + 1]; int use_memcache = 0; int root_uses_machine_creds = 1; unsigned int context_timeout = 0; +char *preferred_realm = NULL; void sig_die(int signal) @@ -83,7 +84,7 @@ sig_hup(int signal) static void usage(char *progname) { - fprintf(stderr, "usage: %s [-f] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout]\n", + fprintf(stderr, "usage: %s [-f] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm]\n", progname); exit(1); } @@ -100,7 +101,7 @@ main(int argc, char *argv[]) char *progname; memset(ccachesearch, 0, sizeof(ccachesearch)); - while ((opt = getopt(argc, argv, "fvrmnMp:k:d:t:")) != -1) { + while ((opt = getopt(argc, argv, "fvrmnMp:k:d:t:R:")) != -1) { switch (opt) { case 'f': fg = 1; @@ -138,6 +139,9 @@ main(int argc, char *argv[]) case 't': context_timeout = atoi(optarg); break; + case 'R': + preferred_realm = strdup(optarg); + break; default: usage(argv[0]); break; @@ -150,6 +154,9 @@ main(int argc, char *argv[]) ccachesearch[i++] = strtok(NULL, ":"); } while (ccachesearch[i-1] != NULL && i < GSSD_MAX_CCACHE_SEARCH); + if (preferred_realm == NULL) + gssd_k5_get_default_realm(&preferred_realm); + snprintf(pipefs_nfsdir, sizeof(pipefs_nfsdir), "%s/%s", pipefs_dir, GSSD_SERVICE_NAME); if (pipefs_nfsdir[sizeof(pipefs_nfsdir)-1] != '\0') diff --git a/utils/gssd/gssd.h b/utils/gssd/gssd.h index aef14cf..082039a 100644 --- a/utils/gssd/gssd.h +++ b/utils/gssd/gssd.h @@ -66,6 +66,7 @@ extern char *ccachesearch[]; extern int use_memcache; extern int root_uses_machine_creds; extern unsigned int context_timeout; +extern char *preferred_realm; TAILQ_HEAD(clnt_list_head, clnt_info) clnt_list; diff --git a/utils/gssd/gssd.man b/utils/gssd/gssd.man index e4f68f9..0a23cd6 100644 --- a/utils/gssd/gssd.man +++ b/utils/gssd/gssd.man @@ -87,6 +87,13 @@ Increases the verbosity of the output (can be specified multiple times). If the rpcsec_gss library supports setting debug level, increases the verbosity of the output (can be specified multiple times). .TP +.B -R realm +Kerberos tickets from this +.I realm +will be preferred when scanning available credentials cache files to be +used to create a context. By default, the default realm, as configured +in the Kerberos configuration file, is preferred. +.TP .B -t timeout Timeout, in seconds, for kernel gss contexts. This option allows you to force new kernel contexts to be negotiated after diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c index 4a4d10b..77814bc 100644 --- a/utils/gssd/krb5_util.c +++ b/utils/gssd/krb5_util.c @@ -135,7 +135,8 @@ static int gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d); static int gssd_get_single_krb5_cred(krb5_context context, krb5_keytab kt, struct gssd_k5_kt_princ *ple); - +static int query_krb5_ccache(const char* cred_cache, char **ret_princname, + char **ret_realm); /* * Called from the scandir function to weed out potential krb5 @@ -179,6 +180,10 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) int found = 0; struct dirent *best_match_dir = NULL; struct stat best_match_stat, tmp_stat; + char buf[1030]; + char *princname = NULL; + char *realm = NULL; + int score, best_match_score = 0; memset(&best_match_stat, 0, sizeof(best_match_stat)); *d = NULL; @@ -190,10 +195,14 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) else if (n > 0) { char statname[1024]; for (i = 0; i < n; i++) { - printerr(3, "CC file '%s' being considered\n", - namelist[i]->d_name); snprintf(statname, sizeof(statname), "%s/%s", dirname, namelist[i]->d_name); + printerr(3, "CC file '%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", statname); @@ -202,20 +211,33 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) } /* Only pick caches owned by the user (uid) */ if (tmp_stat.st_uid != uid) { - printerr(3, "'%s' owned by %u, not %u\n", + printerr(3, "CC file '%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, "'%s' is not a regular file\n", + printerr(3, "CC file '%s' is not a regular file\n", + statname); + free(namelist[i]); + continue; + } + if (!query_krb5_ccache(buf, &princname, &realm)) { + printerr(3, "CC file '%s' is expired or corrupt\n", statname); free(namelist[i]); continue; } - printerr(3, "CC file '%s' matches owner check and has " - "mtime of %u\n", - namelist[i]->d_name, tmp_stat.st_mtime); + + score = 0; + if (preferred_realm && + strcmp(realm, preferred_realm) == 0) + score++; + + printerr(3, "CC file '%s'(%s@%s) passed all checks and" + " has mtime of %u\n", + statname, princname, realm, + tmp_stat.st_mtime); /* * if more than one match is found, return the most * recent (the one with the latest mtime), and @@ -224,30 +246,38 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) if (!found) { best_match_dir = namelist[i]; best_match_stat = tmp_stat; + best_match_score = score; found++; } else { /* - * If the current match has an mtime later + * If current score is higher than best match + * score, we use the current match. Otherwise, + * if the current match has an mtime later * than the one we are looking at, then use * the current match. Otherwise, we still * have the best match. */ - if (tmp_stat.st_mtime > - best_match_stat.st_mtime) { + if (best_match_score < score || + (best_match_score == score && + tmp_stat.st_mtime > + best_match_stat.st_mtime)) { free(best_match_dir); best_match_dir = namelist[i]; best_match_stat = tmp_stat; + best_match_score = score; } else { free(namelist[i]); } - printerr(3, "CC file '%s' is our " + printerr(3, "CC file '%s/%s' is our " "current best match " "with mtime of %u\n", - best_match_dir->d_name, + dirname, best_match_dir->d_name, best_match_stat.st_mtime); } + free(princname); + free(realm); } free(namelist); } @@ -884,6 +914,94 @@ out: return retval; } + +static inline int data_is_equal(krb5_data d1, krb5_data d2) +{ + return (d1.length == d2.length + && memcmp(d1.data, d2.data, d1.length) == 0); +} + +static int +check_for_tgt(krb5_context context, krb5_ccache ccache, + krb5_principal principal) +{ + krb5_error_code ret; + krb5_creds creds; + krb5_cc_cursor cur; + int found = 0; + + ret = krb5_cc_start_seq_get(context, ccache, &cur); + if (ret) + return 0; + + while (!found && + (ret = krb5_cc_next_cred(context, ccache, &cur, &creds)) == 0) { + if (creds.server->length == 2 && + data_is_equal(creds.server->realm, + principal->realm) && + creds.server->data[0].length == 6 && + memcmp(creds.server->data[0].data, + "krbtgt", 6) == 0 && + data_is_equal(creds.server->data[1], + principal->realm) && + creds.times.endtime > time(NULL)) + found = 1; + krb5_free_cred_contents(context, &creds); + } + krb5_cc_end_seq_get(context, ccache, &cur); + + return found; +} + +static int +query_krb5_ccache(const char* cred_cache, char **ret_princname, + char **ret_realm) +{ + krb5_error_code ret; + krb5_context context; + krb5_ccache ccache; + krb5_principal principal; + int found = 0; + char *str = NULL; + char *princstring; + + ret = krb5_init_context(&context); + if (ret) + return 0; + + if(!cred_cache || krb5_cc_resolve(context, cred_cache, &ccache)) + goto err_cache; + + if (krb5_cc_set_flags(context, ccache, 0)) + goto err_princ; + + ret = krb5_cc_get_principal(context, ccache, &principal); + if (ret) + goto err_princ; + + found = check_for_tgt(context, ccache, principal); + if (found) { + ret = krb5_unparse_name(context, principal, &princstring); + if (ret == 0) { + if ((str = strchr(princstring, '@')) != NULL) { + *str = '\0'; + *ret_princname = strdup(princstring); + *ret_realm = strdup(str+1); + } + k5_free_unparsed_name(context, princstring); + } else { + found = 0; + } + } + krb5_free_principal(context, principal); +err_princ: + krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE); + krb5_cc_close(context, ccache); +err_cache: + krb5_free_context(context); + return found; +} + /*==========================*/ /*=== External routines ===*/ /*==========================*/ @@ -1134,3 +1252,19 @@ gssd_k5_err_msg(krb5_context context, krb5_error_code code) return error_message(code); #endif } + +/* + * Return default Kerberos realm + */ +void +gssd_k5_get_default_realm(char **def_realm) +{ + krb5_context context; + + if (krb5_init_context(&context)) + return; + + krb5_get_default_realm(context, def_realm); + + krb5_free_context(context); +} diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h index addae1c..4b2da6b 100644 --- a/utils/gssd/krb5_util.h +++ b/utils/gssd/krb5_util.h @@ -27,6 +27,7 @@ int gssd_refresh_krb5_machine_credential(char *hostname, struct gssd_k5_kt_princ *ple); const char * gssd_k5_err_msg(krb5_context context, krb5_error_code code); +void gssd_k5_get_default_realm(char **def_realm); #ifdef HAVE_SET_ALLOWABLE_ENCTYPES int limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid); -- 2.39.2