diff --git a/aclocal/libevent.m4 b/aclocal/libevent.m4 index b5ac00ff..0ebcb524 100644 --- a/aclocal/libevent.m4 +++ b/aclocal/libevent.m4 @@ -4,9 +4,13 @@ AC_DEFUN([AC_LIBEVENT], [ dnl Check for libevent, but do not add -levent to LIBS AC_CHECK_LIB([event], [event_dispatch], [LIBEVENT=-levent], [AC_MSG_ERROR([libevent not found.])]) + AC_CHECK_LIB([event_core], [event_base_dispatch], [LIBEVENT=-levent_core], + [AC_MSG_ERROR([libevent2 not found.])]) AC_SUBST(LIBEVENT) AC_CHECK_HEADERS([event.h], , [AC_MSG_ERROR([libevent headers not found.])]) + AC_CHECK_HEADERS([event2/event.h], , + [AC_MSG_ERROR([libevent headers not found.])]) ])dnl diff --git a/utils/gssd/gss_names.c b/utils/gssd/gss_names.c index 2a7f3a13..982b96f4 100644 --- a/utils/gssd/gss_names.c +++ b/utils/gssd/gss_names.c @@ -110,10 +110,12 @@ get_hostbased_client_name(gss_name_t client_name, gss_OID mech, /* For Kerberos, transform the NT_KRB5_PRINCIPAL name to * an NT_HOSTBASED_SERVICE name */ if (g_OID_equal(&krb5oid, mech)) { - if (get_krb5_hostbased_name(&name, &cname) == 0) - *hostbased_name = cname; + if (get_krb5_hostbased_name(&name, &cname) != 0) + goto out_rel_buf; + *hostbased_name = cname; } else { printerr(1, "WARNING: unknown/unsupport mech OID\n"); + goto out_rel_buf; } res = 0; diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c index af66ed62..6f461fdf 100644 --- a/utils/gssd/gssd.c +++ b/utils/gssd/gssd.c @@ -64,7 +64,7 @@ #include #include #include -#include +#include #include "gssd.h" #include "err_util.h" @@ -77,7 +77,7 @@ static char *pipefs_path = GSSD_PIPEFS_DIR; static DIR *pipefs_dir; static int pipefs_fd; static int inotify_fd; -struct event inotify_ev; +struct event *inotify_ev; char *keytabfile = GSSD_DEFAULT_KEYTAB_FILE; char **ccachesearch; @@ -90,9 +90,9 @@ char *ccachedir = NULL; /* Avoid DNS reverse lookups on server names */ static bool avoid_dns = true; static bool use_gssproxy = false; -int thread_started = false; -pthread_mutex_t pmutex = PTHREAD_MUTEX_INITIALIZER; -pthread_cond_t pcond = PTHREAD_COND_INITIALIZER; +pthread_mutex_t clp_lock = PTHREAD_MUTEX_INITIALIZER; +static bool signal_received = false; +static struct event_base *evbase = NULL; TAILQ_HEAD(topdir_list_head, topdir) topdir_list; @@ -359,20 +359,28 @@ out: free(port); } +/* Actually frees clp and fields that might be used from other + * threads if was last reference. + */ static void -gssd_destroy_client(struct clnt_info *clp) +gssd_free_client(struct clnt_info *clp) { - if (clp->krb5_fd >= 0) { + int refcnt; + + pthread_mutex_lock(&clp_lock); + refcnt = --clp->refcount; + pthread_mutex_unlock(&clp_lock); + if (refcnt > 0) + return; + + printerr(3, "freeing client %s\n", clp->relpath); + + if (clp->krb5_fd >= 0) close(clp->krb5_fd); - event_del(&clp->krb5_ev); - } - if (clp->gssd_fd >= 0) { + if (clp->gssd_fd >= 0) close(clp->gssd_fd); - event_del(&clp->gssd_ev); - } - inotify_rm_watch(inotify_fd, clp->wd); free(clp->relpath); free(clp->servicename); free(clp->servername); @@ -380,6 +388,30 @@ gssd_destroy_client(struct clnt_info *clp) free(clp); } +/* Called when removing from clnt_list to tear down event handling. + * Will then free clp if was last reference. + */ +static void +gssd_destroy_client(struct clnt_info *clp) +{ + printerr(3, "destroying client %s\n", clp->relpath); + + if (clp->krb5_ev) { + event_del(clp->krb5_ev); + event_free(clp->krb5_ev); + clp->krb5_ev = NULL; + } + + if (clp->gssd_ev) { + event_del(clp->gssd_ev); + event_free(clp->gssd_ev); + clp->gssd_ev = NULL; + } + + inotify_rm_watch(inotify_fd, clp->wd); + gssd_free_client(clp); +} + static void gssd_scan(void); static int @@ -416,11 +448,21 @@ static struct clnt_upcall_info *alloc_upcall_info(struct clnt_info *clp) info = malloc(sizeof(struct clnt_upcall_info)); if (info == NULL) return NULL; + + pthread_mutex_lock(&clp_lock); + clp->refcount++; + pthread_mutex_unlock(&clp_lock); info->clp = clp; return info; } +void free_upcall_info(struct clnt_upcall_info *info) +{ + gssd_free_client(info->clp); + free(info); +} + /* For each upcall read the upcall info into the buffer, then create a * thread in a detached state so that resources are released back into * the system without the need for a join. @@ -438,13 +480,13 @@ gssd_clnt_gssd_cb(int UNUSED(fd), short UNUSED(which), void *data) info->lbuflen = read(clp->gssd_fd, info->lbuf, sizeof(info->lbuf)); if (info->lbuflen <= 0 || info->lbuf[info->lbuflen-1] != '\n') { printerr(0, "WARNING: %s: failed reading request\n", __func__); - free(info); + free_upcall_info(info); return; } info->lbuf[info->lbuflen-1] = 0; if (start_upcall_thread(handle_gssd_upcall, info)) - free(info); + free_upcall_info(info); } static void @@ -461,12 +503,12 @@ gssd_clnt_krb5_cb(int UNUSED(fd), short UNUSED(which), void *data) sizeof(info->uid)) < (ssize_t)sizeof(info->uid)) { printerr(0, "WARNING: %s: failed reading uid from krb5 " "upcall pipe: %s\n", __func__, strerror(errno)); - free(info); + free_upcall_info(info); return; } if (start_upcall_thread(handle_krb5_upcall, info)) - free(info); + free_upcall_info(info); } static struct clnt_info * @@ -501,6 +543,7 @@ gssd_get_clnt(struct topdir *tdi, const char *name) clp->name = clp->relpath + strlen(tdi->name) + 1; clp->krb5_fd = -1; clp->gssd_fd = -1; + clp->refcount = 1; TAILQ_INSERT_HEAD(&tdi->clnt_list, clp, list); return clp; @@ -535,15 +578,15 @@ gssd_scan_clnt(struct clnt_info *clp) clp->krb5_fd = openat(clntfd, "krb5", O_RDWR | O_NONBLOCK); if (gssd_was_closed && clp->gssd_fd >= 0) { - event_set(&clp->gssd_ev, clp->gssd_fd, EV_READ | EV_PERSIST, - gssd_clnt_gssd_cb, clp); - event_add(&clp->gssd_ev, NULL); + clp->gssd_ev = event_new(evbase, clp->gssd_fd, EV_READ | EV_PERSIST, + gssd_clnt_gssd_cb, clp); + event_add(clp->gssd_ev, NULL); } if (krb5_was_closed && clp->krb5_fd >= 0) { - event_set(&clp->krb5_ev, clp->krb5_fd, EV_READ | EV_PERSIST, - gssd_clnt_krb5_cb, clp); - event_add(&clp->krb5_ev, NULL); + clp->krb5_ev = event_new(evbase, clp->krb5_fd, EV_READ | EV_PERSIST, + gssd_clnt_krb5_cb, clp); + event_add(clp->krb5_ev, NULL); } if (clp->krb5_fd == -1 && clp->gssd_fd == -1) @@ -649,7 +692,7 @@ gssd_scan_topdir(const char *name) if (clp->scanned) continue; - printerr(3, "destroying client %s\n", clp->relpath); + printerr(3, "orphaned client %s\n", clp->relpath); saveprev = clp->list.tqe_prev; TAILQ_REMOVE(&tdi->clnt_list, clp, list); gssd_destroy_client(clp); @@ -746,12 +789,16 @@ gssd_inotify_clnt(struct topdir *tdi, struct clnt_info *clp, const struct inotif } else if (ev->mask & IN_DELETE) { if (!strcmp(ev->name, "gssd") && clp->gssd_fd >= 0) { close(clp->gssd_fd); - event_del(&clp->gssd_ev); + event_del(clp->gssd_ev); + event_free(clp->gssd_ev); + clp->gssd_ev = NULL; clp->gssd_fd = -1; } else if (!strcmp(ev->name, "krb5") && clp->krb5_fd >= 0) { close(clp->krb5_fd); - event_del(&clp->krb5_ev); + event_del(clp->krb5_ev); + event_free(clp->krb5_ev); + clp->krb5_ev = NULL; clp->krb5_fd = -1; } @@ -824,10 +871,15 @@ found: static void sig_die(int signal) { - if (root_uses_machine_creds) - gssd_destroy_krb5_machine_creds(); + if (signal_received) { + gssd_destroy_krb5_principals(root_uses_machine_creds); + printerr(1, "forced exiting on signal %d\n", signal); + exit(0); + } + + signal_received = true; printerr(1, "exiting on signal %d\n", signal); - exit(0); + event_base_loopexit(evbase, NULL); } static void @@ -884,9 +936,10 @@ main(int argc, char *argv[]) int rpc_verbosity = 0; int opt; int i; + int rc; extern char *optarg; char *progname; - struct event sighup_ev; + struct event *sighup_ev; read_gss_conf(); @@ -1025,7 +1078,11 @@ main(int argc, char *argv[]) if (gssd_check_mechs() != 0) errx(1, "Problem with gssapi library"); - event_init(); + evbase = event_base_new(); + if (!evbase) { + printerr(0, "ERROR: failed to create event base\n"); + exit(EXIT_FAILURE); + } pipefs_dir = opendir(pipefs_path); if (!pipefs_dir) { @@ -1047,18 +1104,43 @@ main(int argc, char *argv[]) signal(SIGINT, sig_die); signal(SIGTERM, sig_die); - signal_set(&sighup_ev, SIGHUP, gssd_scan_cb, NULL); - signal_add(&sighup_ev, NULL); - event_set(&inotify_ev, inotify_fd, EV_READ | EV_PERSIST, gssd_inotify_cb, NULL); - event_add(&inotify_ev, NULL); + sighup_ev = evsignal_new(evbase, SIGHUP, gssd_scan_cb, NULL); + evsignal_add(sighup_ev, NULL); + inotify_ev = event_new(evbase, inotify_fd, EV_READ | EV_PERSIST, + gssd_inotify_cb, NULL); + event_add(inotify_ev, NULL); TAILQ_INIT(&topdir_list); gssd_scan(); daemon_ready(); - event_dispatch(); + rc = event_base_dispatch(evbase); - printerr(0, "ERROR: event_dispatch() returned!\n"); - return EXIT_FAILURE; -} + printerr(0, "event_dispatch() returned %i!\n", rc); + + gssd_destroy_krb5_principals(root_uses_machine_creds); + + while (!TAILQ_EMPTY(&topdir_list)) { + struct topdir *tdi = TAILQ_FIRST(&topdir_list); + TAILQ_REMOVE(&topdir_list, tdi, list); + while (!TAILQ_EMPTY(&tdi->clnt_list)) { + struct clnt_info *clp = TAILQ_FIRST(&tdi->clnt_list); + TAILQ_REMOVE(&tdi->clnt_list, clp, list); + gssd_destroy_client(clp); + } + free(tdi); + } + + event_free(inotify_ev); + event_free(sighup_ev); + event_base_free(evbase); + + close(inotify_fd); + close(pipefs_fd); + closedir(pipefs_dir); + free(preferred_realm); + free(ccachesearch); + + return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/utils/gssd/gssd.h b/utils/gssd/gssd.h index f4f59754..1e8c58d4 100644 --- a/utils/gssd/gssd.h +++ b/utils/gssd/gssd.h @@ -62,13 +62,10 @@ extern int root_uses_machine_creds; extern unsigned int context_timeout; extern unsigned int rpc_timeout; extern char *preferred_realm; -extern pthread_mutex_t ple_lock; -extern pthread_cond_t pcond; -extern pthread_mutex_t pmutex; -extern int thread_started; struct clnt_info { TAILQ_ENTRY(clnt_info) list; + int refcount; int wd; bool scanned; char *name; @@ -79,9 +76,9 @@ struct clnt_info { int vers; char *protocol; int krb5_fd; - struct event krb5_ev; + struct event *krb5_ev; int gssd_fd; - struct event gssd_ev; + struct event *gssd_ev; struct sockaddr_storage addr; }; @@ -94,6 +91,7 @@ struct clnt_upcall_info { void handle_krb5_upcall(struct clnt_upcall_info *clp); void handle_gssd_upcall(struct clnt_upcall_info *clp); +void free_upcall_info(struct clnt_upcall_info *info); #endif /* _RPC_GSSD_H_ */ diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c index bfcf3f09..ae3ebe81 100644 --- a/utils/gssd/gssd_proc.c +++ b/utils/gssd/gssd_proc.c @@ -149,9 +149,10 @@ do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, char *buf = NULL, *p = NULL, *end = NULL; unsigned int timeout = context_timeout; unsigned int buf_size = 0; + pthread_t tid = pthread_self(); - printerr(2, "doing downcall: lifetime_rec=%u acceptor=%.*s\n", - lifetime_rec, acceptor->length, acceptor->value); + printerr(2, "do_downcall(0x%x): lifetime_rec=%u acceptor=%.*s\n", + tid, lifetime_rec, acceptor->length, acceptor->value); buf_size = sizeof(uid) + sizeof(timeout) + sizeof(pd->pd_seq_win) + sizeof(pd->pd_ctx_hndl.length) + pd->pd_ctx_hndl.length + sizeof(context_token->length) + context_token->length + @@ -177,7 +178,7 @@ do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, return; out_err: free(buf); - printerr(1, "Failed to write downcall!\n"); + printerr(1, "do_downcall(0x%x): Failed to write downcall!\n", tid); return; } @@ -231,7 +232,7 @@ populate_port(struct sockaddr *sa, const socklen_t salen, switch (sa->sa_family) { case AF_INET: if (s4->sin_port != 0) { - printerr(2, "DEBUG: port already set to %d\n", + printerr(4, "DEBUG: port already set to %d\n", ntohs(s4->sin_port)); return 1; } @@ -239,7 +240,7 @@ populate_port(struct sockaddr *sa, const socklen_t salen, #ifdef IPV6_SUPPORTED case AF_INET6: if (s6->sin6_port != 0) { - printerr(2, "DEBUG: port already set to %d\n", + printerr(4, "DEBUG: port already set to %d\n", ntohs(s6->sin6_port)); return 1; } @@ -544,7 +545,7 @@ krb5_use_machine_creds(struct clnt_info *clp, uid_t uid, uid, tgtname); do { - gssd_refresh_krb5_machine_credential(clp->servername, NULL, + gssd_refresh_krb5_machine_credential(clp->servername, service, srchost); /* * Get a list of credential cache names and try each @@ -726,7 +727,7 @@ handle_krb5_upcall(struct clnt_upcall_info *info) printerr(2, "\n%s: uid %d (%s)\n", __func__, info->uid, clp->relpath); process_krb5_upcall(clp, info->uid, clp->krb5_fd, NULL, NULL, NULL); - free(info); + free_upcall_info(info); } void @@ -743,8 +744,10 @@ handle_gssd_upcall(struct clnt_upcall_info *info) char *enctypes = NULL; char *upcall_str; char *pbuf = info->lbuf; + pthread_t tid = pthread_self(); - printerr(2, "%s: '%s' (%s)\n", __func__, info->lbuf, clp->relpath); + printerr(2, "\n%s(0x%x): '%s' (%s)\n", __func__, tid, + info->lbuf, clp->relpath); upcall_str = strdup(info->lbuf); if (upcall_str == NULL) { @@ -826,6 +829,6 @@ handle_gssd_upcall(struct clnt_upcall_info *info) out: free(upcall_str); out_nomem: - free(info); + free_upcall_info(info); return; } diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c index 26e51edf..d675c3a4 100644 --- a/utils/gssd/krb5_util.c +++ b/utils/gssd/krb5_util.c @@ -126,9 +126,28 @@ #include "gss_util.h" #include "krb5_util.h" +/* + * List of principals from our keytab that we + * will try to use to obtain credentials + * (known as a principal list entry (ple)) + */ +struct gssd_k5_kt_princ { + struct gssd_k5_kt_princ *next; + // Only protect against deletion, not modification + int refcount; + // Only set during creation in new_ple() + krb5_principal princ; + char *realm; + // Modified during usage by gssd_get_single_krb5_cred() + char *ccname; + krb5_timestamp endtime; +}; + + /* Global list of principals/cache file names for machine credentials */ -struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL; -pthread_mutex_t ple_lock = PTHREAD_MUTEX_INITIALIZER; +static struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL; +/* This mutex protects list modification & ple->ccname */ +static pthread_mutex_t ple_lock = PTHREAD_MUTEX_INITIALIZER; #ifdef HAVE_SET_ALLOWABLE_ENCTYPES int limit_to_legacy_enctypes = 0; @@ -146,6 +165,18 @@ static int gssd_get_single_krb5_cred(krb5_context context, static int query_krb5_ccache(const char* cred_cache, char **ret_princname, char **ret_realm); +static void release_ple(krb5_context context, struct gssd_k5_kt_princ *ple) +{ + if (--ple->refcount) + return; + + printerr(3, "freeing cached principal (ccname=%s, realm=%s)\n", ple->ccname, ple->realm); + krb5_free_principal(context, ple->princ); + free(ple->ccname); + free(ple->realm); + free(ple); +} + /* * Called from the scandir function to weed out potential krb5 * credentials cache files @@ -352,12 +383,15 @@ gssd_get_single_krb5_cred(krb5_context context, * 300 because clock skew must be within 300sec for kerberos */ now += 300; + pthread_mutex_lock(&ple_lock); if (ple->ccname && ple->endtime > now && !nocache) { printerr(3, "INFO: Credentials in CC '%s' are good until %d\n", ple->ccname, ple->endtime); code = 0; + pthread_mutex_unlock(&ple_lock); goto out; } + pthread_mutex_unlock(&ple_lock); if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) { printerr(0, "ERROR: Unable to get keytab name in " @@ -410,6 +444,7 @@ gssd_get_single_krb5_cred(krb5_context context, * Initialize cache file which we're going to be using */ + pthread_mutex_lock(&ple_lock); if (use_memcache) cache_type = "MEMORY"; else @@ -419,15 +454,18 @@ gssd_get_single_krb5_cred(krb5_context context, ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX, GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm); ple->endtime = my_creds.times.endtime; - if (ple->ccname != NULL) + if (ple->ccname == NULL || strcmp(ple->ccname, cc_name) != 0) { free(ple->ccname); - ple->ccname = strdup(cc_name); - if (ple->ccname == NULL) { - printerr(0, "ERROR: no storage to duplicate credentials " - "cache name '%s'\n", cc_name); - code = ENOMEM; - goto out; + ple->ccname = strdup(cc_name); + if (ple->ccname == NULL) { + printerr(0, "ERROR: no storage to duplicate credentials " + "cache name '%s'\n", cc_name); + code = ENOMEM; + pthread_mutex_unlock(&ple_lock); + goto out; + } } + pthread_mutex_unlock(&ple_lock); 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", @@ -465,6 +503,7 @@ gssd_get_single_krb5_cred(krb5_context context, /* * Given a principal, find a matching ple structure + * Called with mutex held */ static struct gssd_k5_kt_princ * find_ple_by_princ(krb5_context context, krb5_principal princ) @@ -481,6 +520,7 @@ find_ple_by_princ(krb5_context context, krb5_principal princ) /* * Create, initialize, and add a new ple structure to the global list + * Called with mutex held */ static struct gssd_k5_kt_princ * new_ple(krb5_context context, krb5_principal princ) @@ -532,6 +572,7 @@ new_ple(krb5_context context, krb5_principal princ) p->next = ple; } + ple->refcount = 1; return ple; outerr: if (ple) { @@ -550,13 +591,14 @@ 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! */ - pthread_mutex_lock(&ple_lock); ple = find_ple_by_princ(context, princ); if (ple == NULL) { ple = new_ple(context, princ); } + if (ple != NULL) { + ple->refcount++; + } pthread_mutex_unlock(&ple_lock); return ple; @@ -721,6 +763,8 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, retval = ENOMEM; k5_free_kt_entry(context, kte); } else { + release_ple(context, ple); + ple = NULL; retval = 0; *found = 1; } @@ -796,12 +840,12 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, /* Compute the active directory machine name HOST$ */ krb5_appdefault_string(context, "nfs", NULL, "ad_principal_name", notsetstr, &adhostoverride); - if (strcmp(adhostoverride, notsetstr) != 0) { - printerr (1, - "AD host string overridden with \"%s\" from appdefaults\n", - adhostoverride); - /* No overflow: Windows cannot handle strings longer than 19 chars */ - strcpy(myhostad, adhostoverride); + if (adhostoverride && strcmp(adhostoverride, notsetstr) != 0) { + printerr(1, + "AD host string overridden with \"%s\" from appdefaults\n", + adhostoverride); + /* No overflow: Windows cannot handle strings longer than 19 chars */ + strcpy(myhostad, adhostoverride); } else { strcpy(myhostad, myhostname); for (i = 0; myhostad[i] != 0; ++i) { @@ -928,7 +972,7 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, tried_upper = 1; } } else { - printerr(3, "Success getting keytab entry for '%s'\n",spn); + printerr(2, "Success getting keytab entry for '%s'\n",spn); retval = 0; goto out; } @@ -1053,6 +1097,93 @@ err_cache: return (*ret_princname && *ret_realm); } +/* + * Obtain (or refresh if necessary) Kerberos machine credentials + * If a ple is passed in, it's reference will be released + */ +static int +gssd_refresh_krb5_machine_credential_internal(char *hostname, + struct gssd_k5_kt_princ *ple, + char *service, char *srchost) +{ + krb5_error_code code = 0; + krb5_context context; + krb5_keytab kt = NULL;; + int retval = 0; + char *k5err = NULL; + const char *svcnames[] = { "$", "root", "nfs", "host", NULL }; + + printerr(2, "%s: hostname=%s ple=%p service=%s srchost=%s\n", + __func__, hostname, ple, service, srchost); + + /* + * 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", + __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", + __func__, k5err, keytabfile); + goto out_free_context; + } + + if (ple == NULL) { + krb5_keytab_entry kte; + + code = find_keytab_entry(context, kt, srchost, hostname, + &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_free_kt; + } + + 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_free_kt; + } + } + retval = gssd_get_single_krb5_cred(context, kt, ple, 0); +out_free_kt: + krb5_kt_close(context, kt); +out_free_context: + if (ple) + release_ple(context, ple); + krb5_free_context(context); +out: + free(k5err); + return retval; +} + /*==========================*/ /*=== External routines ===*/ /*==========================*/ @@ -1146,37 +1277,56 @@ gssd_get_krb5_machine_cred_list(char ***list) goto out; } - /* Need to serialize list if we ever become multi-threaded! */ - + pthread_mutex_lock(&ple_lock); 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, - NULL, NULL); - if (retval) - continue; - if (i + 1 > listsize) { - listsize += listinc; - l = (char **) - realloc(l, listsize * sizeof(char *)); - if (l == NULL) { - retval = ENOMEM; - goto out; - } - } - if ((l[i++] = strdup(ple->ccname)) == NULL) { + if (!ple->ccname) + continue; + + /* Take advantage of the fact we only remove the ple + * from the list during shutdown. If it's modified + * concurrently at worst we'll just miss a new entry + * before the current ple + * + * gssd_refresh_krb5_machine_credential_internal() will + * release the ple refcount + */ + ple->refcount++; + pthread_mutex_unlock(&ple_lock); + /* Make sure cred is up-to-date before returning it */ + retval = gssd_refresh_krb5_machine_credential_internal(NULL, ple, + NULL, NULL); + pthread_mutex_lock(&ple_lock); + if (gssd_k5_kt_princ_list == NULL) { + /* Looks like we did shutdown... abort */ + l[i] = NULL; + gssd_free_krb5_machine_cred_list(l); + retval = ENOMEM; + goto out_lock; + } + if (retval) + continue; + if (i + 1 > listsize) { + listsize += listinc; + l = (char **) + realloc(l, listsize * sizeof(char *)); + if (l == NULL) { retval = ENOMEM; - goto out; + goto out_lock; } } + if ((l[i++] = strdup(ple->ccname)) == NULL) { + retval = ENOMEM; + goto out_lock; + } } if (i > 0) { l[i] = NULL; *list = l; retval = 0; - goto out; } else free((void *)l); +out_lock: + pthread_mutex_unlock(&ple_lock); out: return retval; } @@ -1201,7 +1351,7 @@ gssd_free_krb5_machine_cred_list(char **list) * Called upon exit. Destroys machine credentials. */ void -gssd_destroy_krb5_machine_creds(void) +gssd_destroy_krb5_principals(int destroy_machine_creds) { krb5_context context; krb5_error_code code = 0; @@ -1213,33 +1363,38 @@ gssd_destroy_krb5_machine_creds(void) if (code) { k5err = gssd_k5_err_msg(NULL, code); printerr(0, "ERROR: %s while initializing krb5\n", k5err); - goto out; + free(k5err); + return; } - for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) { - 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", k5err, - ple->ccname); - krb5_free_string(context, k5err); - k5err = NULL; - continue; - } + pthread_mutex_lock(&ple_lock); + while (gssd_k5_kt_princ_list) { + ple = gssd_k5_kt_princ_list; + gssd_k5_kt_princ_list = ple->next; - if ((code = krb5_cc_destroy(context, ccache))) { - k5err = gssd_k5_err_msg(context, code); - printerr(0, "WARNING: %s while destroying credential " - "cache '%s'\n", k5err, ple->ccname); - krb5_free_string(context, k5err); - k5err = NULL; + if (destroy_machine_creds && ple->ccname) { + 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", k5err, + ple->ccname); + free(k5err); + k5err = NULL; + } + + if (!code && (code = krb5_cc_destroy(context, ccache))) { + k5err = gssd_k5_err_msg(context, code); + printerr(0, "WARNING: %s while destroying credential " + "cache '%s'\n", k5err, ple->ccname); + free(k5err); + k5err = NULL; + } } + + release_ple(context, ple); } + pthread_mutex_unlock(&ple_lock); krb5_free_context(context); - out: - krb5_free_string(context, k5err); } /* @@ -1247,83 +1402,10 @@ gssd_destroy_krb5_machine_creds(void) */ int gssd_refresh_krb5_machine_credential(char *hostname, - struct gssd_k5_kt_princ *ple, char *service, char *srchost) { - krb5_error_code code = 0; - krb5_context context; - krb5_keytab kt = NULL;; - int retval = 0; - char *k5err = NULL; - const char *svcnames[] = { "$", "root", "nfs", "host", NULL }; - - printerr(2, "%s: hostname=%s ple=%p service=%s srchost=%s\n", - __func__, hostname, ple, service, srchost); - - /* - * 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", - __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", - __func__, k5err, keytabfile); - goto out_free_context; - } - - if (ple == NULL) { - krb5_keytab_entry kte; - - code = find_keytab_entry(context, kt, srchost, hostname, - &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_free_kt; - } - - 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_free_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: - krb5_free_string(context, k5err); - return retval; + return gssd_refresh_krb5_machine_credential_internal(hostname, NULL, + service, srchost); } /* diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h index b000b444..2415205a 100644 --- a/utils/gssd/krb5_util.h +++ b/utils/gssd/krb5_util.h @@ -9,27 +9,13 @@ #include "gss_oids.h" #endif -/* - * List of principals from our keytab that we - * will try to use to obtain credentials - * (known as a principal list entry (ple)) - */ -struct gssd_k5_kt_princ { - struct gssd_k5_kt_princ *next; - krb5_principal princ; - char *ccname; - char *realm; - krb5_timestamp endtime; -}; - int gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname); int gssd_get_krb5_machine_cred_list(char ***list); void gssd_free_krb5_machine_cred_list(char **list); -void gssd_destroy_krb5_machine_creds(void); +void gssd_destroy_krb5_principals(int destroy_machine_creds); int gssd_refresh_krb5_machine_credential(char *hostname, - struct gssd_k5_kt_princ *ple, char *service, char *srchost); char *gssd_k5_err_msg(krb5_context context, krb5_error_code code); void gssd_k5_get_default_realm(char **def_realm);