Index: head/sys/kgssapi/gss_impl.c =================================================================== --- head/sys/kgssapi/gss_impl.c (revision 344401) +++ head/sys/kgssapi/gss_impl.c (revision 344402) @@ -1,337 +1,346 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ * Authors: Doug Rabson * Developed with Red Inc: Alfred Perlstein * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gssd.h" #include "kgss_if.h" MALLOC_DEFINE(M_GSSAPI, "GSS-API", "GSS-API"); /* * Syscall hooks */ static struct syscall_helper_data gssd_syscalls[] = { SYSCALL_INIT_HELPER(gssd_syscall), SYSCALL_INIT_LAST }; struct kgss_mech_list kgss_mechs; CLIENT *kgss_gssd_handle; struct mtx kgss_gssd_lock; static int kgss_load(void) { int error; LIST_INIT(&kgss_mechs); error = syscall_helper_register(gssd_syscalls, SY_THR_STATIC_KLD); if (error != 0) return (error); return (0); } static void kgss_unload(void) { syscall_helper_unregister(gssd_syscalls); } int sys_gssd_syscall(struct thread *td, struct gssd_syscall_args *uap) { struct sockaddr_un sun; struct netconfig *nconf; char path[MAXPATHLEN]; int error; CLIENT *cl, *oldcl; error = priv_check(td, PRIV_NFS_DAEMON); if (error) return (error); error = copyinstr(uap->path, path, sizeof(path), NULL); if (error) return (error); if (strlen(path) + 1 > sizeof(sun.sun_path)) return (EINVAL); if (path[0] != '\0') { sun.sun_family = AF_LOCAL; strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); sun.sun_len = SUN_LEN(&sun); nconf = getnetconfigent("local"); cl = clnt_reconnect_create(nconf, (struct sockaddr *) &sun, GSSD, GSSDVERS, RPC_MAXDATASIZE, RPC_MAXDATASIZE); + /* + * The number of retries defaults to INT_MAX, which effectively + * means an infinite, uninterruptable loop. Limiting it to + * five retries keeps it from running forever. + */ + if (cl != NULL) { + int retry_count = 5; + CLNT_CONTROL(cl, CLSET_RETRIES, &retry_count); + } } else cl = NULL; mtx_lock(&kgss_gssd_lock); oldcl = kgss_gssd_handle; kgss_gssd_handle = cl; mtx_unlock(&kgss_gssd_lock); if (oldcl != NULL) { CLNT_CLOSE(oldcl); CLNT_RELEASE(oldcl); } return (0); } int kgss_oid_equal(const gss_OID oid1, const gss_OID oid2) { if (oid1 == oid2) return (1); if (!oid1 || !oid2) return (0); if (oid1->length != oid2->length) return (0); if (memcmp(oid1->elements, oid2->elements, oid1->length)) return (0); return (1); } void kgss_install_mech(gss_OID mech_type, const char *name, struct kobj_class *cls) { struct kgss_mech *km; km = malloc(sizeof(struct kgss_mech), M_GSSAPI, M_WAITOK); km->km_mech_type = mech_type; km->km_mech_name = name; km->km_class = cls; LIST_INSERT_HEAD(&kgss_mechs, km, km_link); } void kgss_uninstall_mech(gss_OID mech_type) { struct kgss_mech *km; LIST_FOREACH(km, &kgss_mechs, km_link) { if (kgss_oid_equal(km->km_mech_type, mech_type)) { LIST_REMOVE(km, km_link); free(km, M_GSSAPI); return; } } } gss_OID kgss_find_mech_by_name(const char *name) { struct kgss_mech *km; LIST_FOREACH(km, &kgss_mechs, km_link) { if (!strcmp(km->km_mech_name, name)) { return (km->km_mech_type); } } return (GSS_C_NO_OID); } const char * kgss_find_mech_by_oid(const gss_OID oid) { struct kgss_mech *km; LIST_FOREACH(km, &kgss_mechs, km_link) { if (kgss_oid_equal(km->km_mech_type, oid)) { return (km->km_mech_name); } } return (NULL); } gss_ctx_id_t kgss_create_context(gss_OID mech_type) { struct kgss_mech *km; gss_ctx_id_t ctx; LIST_FOREACH(km, &kgss_mechs, km_link) { if (kgss_oid_equal(km->km_mech_type, mech_type)) break; } if (!km) return (NULL); ctx = (gss_ctx_id_t) kobj_create(km->km_class, M_GSSAPI, M_WAITOK); KGSS_INIT(ctx); return (ctx); } void kgss_delete_context(gss_ctx_id_t ctx, gss_buffer_t output_token) { KGSS_DELETE(ctx, output_token); kobj_delete((kobj_t) ctx, M_GSSAPI); } OM_uint32 kgss_transfer_context(gss_ctx_id_t ctx) { struct export_sec_context_res res; struct export_sec_context_args args; enum clnt_stat stat; OM_uint32 maj_stat; if (!kgss_gssd_handle) return (GSS_S_FAILURE); args.ctx = ctx->handle; bzero(&res, sizeof(res)); stat = gssd_export_sec_context_1(&args, &res, kgss_gssd_handle); if (stat != RPC_SUCCESS) { return (GSS_S_FAILURE); } maj_stat = KGSS_IMPORT(ctx, res.format, &res.interprocess_token); ctx->handle = 0; xdr_free((xdrproc_t) xdr_export_sec_context_res, &res); return (maj_stat); } void kgss_copy_buffer(const gss_buffer_t from, gss_buffer_t to) { to->length = from->length; if (from->length) { to->value = malloc(from->length, M_GSSAPI, M_WAITOK); bcopy(from->value, to->value, from->length); } else { to->value = NULL; } } /* * Acquire the kgss_gssd_handle and return it with a reference count, * if it is available. */ CLIENT * kgss_gssd_client(void) { CLIENT *cl; mtx_lock(&kgss_gssd_lock); cl = kgss_gssd_handle; if (cl != NULL) CLNT_ACQUIRE(cl); mtx_unlock(&kgss_gssd_lock); return (cl); } /* * Kernel module glue */ static int kgssapi_modevent(module_t mod, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: rpc_gss_entries.rpc_gss_refresh_auth = rpc_gss_refresh_auth; rpc_gss_entries.rpc_gss_secfind = rpc_gss_secfind; rpc_gss_entries.rpc_gss_secpurge = rpc_gss_secpurge; rpc_gss_entries.rpc_gss_seccreate = rpc_gss_seccreate; rpc_gss_entries.rpc_gss_set_defaults = rpc_gss_set_defaults; rpc_gss_entries.rpc_gss_max_data_length = rpc_gss_max_data_length; rpc_gss_entries.rpc_gss_get_error = rpc_gss_get_error; rpc_gss_entries.rpc_gss_mech_to_oid = rpc_gss_mech_to_oid; rpc_gss_entries.rpc_gss_oid_to_mech = rpc_gss_oid_to_mech; rpc_gss_entries.rpc_gss_qop_to_num = rpc_gss_qop_to_num; rpc_gss_entries.rpc_gss_get_mechanisms = rpc_gss_get_mechanisms; rpc_gss_entries.rpc_gss_get_versions = rpc_gss_get_versions; rpc_gss_entries.rpc_gss_is_installed = rpc_gss_is_installed; rpc_gss_entries.rpc_gss_set_svc_name = rpc_gss_set_svc_name; rpc_gss_entries.rpc_gss_clear_svc_name = rpc_gss_clear_svc_name; rpc_gss_entries.rpc_gss_getcred = rpc_gss_getcred; rpc_gss_entries.rpc_gss_set_callback = rpc_gss_set_callback; rpc_gss_entries.rpc_gss_clear_callback = rpc_gss_clear_callback; rpc_gss_entries.rpc_gss_get_principal_name = rpc_gss_get_principal_name; rpc_gss_entries.rpc_gss_svc_max_data_length = rpc_gss_svc_max_data_length; mtx_init(&kgss_gssd_lock, "kgss_gssd_lock", NULL, MTX_DEF); error = kgss_load(); break; case MOD_UNLOAD: kgss_unload(); mtx_destroy(&kgss_gssd_lock); /* * Unloading of the kgssapi module is not currently supported. * If somebody wants this, we would need to keep track of * currently executing threads and make sure the count is 0. */ /* FALLTHROUGH */ default: error = EOPNOTSUPP; } return (error); } static moduledata_t kgssapi_mod = { "kgssapi", kgssapi_modevent, NULL, }; DECLARE_MODULE(kgssapi, kgssapi_mod, SI_SUB_VFS, SI_ORDER_ANY); MODULE_DEPEND(kgssapi, krpc, 1, 1, 1); MODULE_VERSION(kgssapi, 1); Index: head/usr.sbin/gssd/gssd.c =================================================================== --- head/usr.sbin/gssd/gssd.c (revision 344401) +++ head/usr.sbin/gssd/gssd.c (revision 344402) @@ -1,1294 +1,1295 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ * Authors: Doug Rabson * Developed with Red Inc: Alfred Perlstein * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #ifndef WITHOUT_KERBEROS #include #endif #include #include #include #include #include #include #include #include #include #include #include "gssd.h" #ifndef _PATH_GSS_MECH #define _PATH_GSS_MECH "/etc/gss/mech" #endif #ifndef _PATH_GSSDSOCK #define _PATH_GSSDSOCK "/var/run/gssd.sock" #endif #define GSSD_CREDENTIAL_CACHE_FILE "/tmp/krb5cc_gssd" struct gss_resource { LIST_ENTRY(gss_resource) gr_link; uint64_t gr_id; /* identifier exported to kernel */ void* gr_res; /* GSS-API resource pointer */ }; LIST_HEAD(gss_resource_list, gss_resource) gss_resources; int gss_resource_count; uint32_t gss_next_id; uint32_t gss_start_time; int debug_level; static char ccfile_dirlist[PATH_MAX + 1], ccfile_substring[NAME_MAX + 1]; static char pref_realm[1024]; static int verbose; static int use_old_des; static int hostbased_initiator_cred; #ifndef WITHOUT_KERBEROS /* 1.2.752.43.13.14 */ static gss_OID_desc gss_krb5_set_allowable_enctypes_x_desc = {6, (void *) "\x2a\x85\x70\x2b\x0d\x0e"}; static gss_OID GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X = &gss_krb5_set_allowable_enctypes_x_desc; static gss_OID_desc gss_krb5_mech_oid_x_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; static gss_OID GSS_KRB5_MECH_OID_X = &gss_krb5_mech_oid_x_desc; #endif static void gssd_load_mech(void); static int find_ccache_file(const char *, uid_t, char *); static int is_a_valid_tgt_cache(const char *, uid_t, int *, time_t *); static void gssd_verbose_out(const char *, ...); #ifndef WITHOUT_KERBEROS static krb5_error_code gssd_get_cc_from_keytab(const char *); static OM_uint32 gssd_get_user_cred(OM_uint32 *, uid_t, gss_cred_id_t *); #endif void gssd_terminate(int); extern void gssd_1(struct svc_req *rqstp, SVCXPRT *transp); extern int gssd_syscall(char *path); int main(int argc, char **argv) { /* * We provide an RPC service on a local-domain socket. The * kernel's GSS-API code will pass what it can't handle * directly to us. */ struct sockaddr_un sun; int fd, oldmask, ch, debug; SVCXPRT *xprt; /* * Initialize the credential cache file name substring and the * search directory list. */ strlcpy(ccfile_substring, "krb5cc_", sizeof(ccfile_substring)); ccfile_dirlist[0] = '\0'; pref_realm[0] = '\0'; debug = 0; verbose = 0; while ((ch = getopt(argc, argv, "dhovs:c:r:")) != -1) { switch (ch) { case 'd': debug_level++; break; case 'h': #ifndef WITHOUT_KERBEROS /* * Enable use of a host based initiator credential * in the default keytab file. */ hostbased_initiator_cred = 1; #else errx(1, "This option not available when built" " without MK_KERBEROS\n"); #endif break; case 'o': #ifndef WITHOUT_KERBEROS /* * Force use of DES and the old type of GSSAPI token. */ use_old_des = 1; #else errx(1, "This option not available when built" " without MK_KERBEROS\n"); #endif break; case 'v': verbose = 1; break; case 's': #ifndef WITHOUT_KERBEROS /* * Set the directory search list. This enables use of * find_ccache_file() to search the directories for a * suitable credentials cache file. */ strlcpy(ccfile_dirlist, optarg, sizeof(ccfile_dirlist)); #else errx(1, "This option not available when built" " without MK_KERBEROS\n"); #endif break; case 'c': /* * Specify a non-default credential cache file * substring. */ strlcpy(ccfile_substring, optarg, sizeof(ccfile_substring)); break; case 'r': /* * Set the preferred realm for the credential cache tgt. */ strlcpy(pref_realm, optarg, sizeof(pref_realm)); break; default: fprintf(stderr, "usage: %s [-d] [-s dir-list] [-c file-substring]" " [-r preferred-realm]\n", argv[0]); exit(1); break; } } gssd_load_mech(); if (!debug_level) { if (daemon(0, 0) != 0) err(1, "Can't daemonize"); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGHUP, SIG_IGN); } signal(SIGTERM, gssd_terminate); + signal(SIGPIPE, gssd_terminate); memset(&sun, 0, sizeof sun); sun.sun_family = AF_LOCAL; unlink(_PATH_GSSDSOCK); strcpy(sun.sun_path, _PATH_GSSDSOCK); sun.sun_len = SUN_LEN(&sun); fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (fd < 0) { if (debug_level == 0) { syslog(LOG_ERR, "Can't create local gssd socket"); exit(1); } err(1, "Can't create local gssd socket"); } oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO); if (bind(fd, (struct sockaddr *) &sun, sun.sun_len) < 0) { if (debug_level == 0) { syslog(LOG_ERR, "Can't bind local gssd socket"); exit(1); } err(1, "Can't bind local gssd socket"); } umask(oldmask); if (listen(fd, SOMAXCONN) < 0) { if (debug_level == 0) { syslog(LOG_ERR, "Can't listen on local gssd socket"); exit(1); } err(1, "Can't listen on local gssd socket"); } xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE); if (!xprt) { if (debug_level == 0) { syslog(LOG_ERR, "Can't create transport for local gssd socket"); exit(1); } err(1, "Can't create transport for local gssd socket"); } if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL)) { if (debug_level == 0) { syslog(LOG_ERR, "Can't register service for local gssd socket"); exit(1); } err(1, "Can't register service for local gssd socket"); } LIST_INIT(&gss_resources); gss_next_id = 1; gss_start_time = time(0); gssd_syscall(_PATH_GSSDSOCK); svc_run(); gssd_syscall(""); return (0); } static void gssd_load_mech(void) { FILE *fp; char buf[256]; char *p; char *name, *oid, *lib, *kobj; fp = fopen(_PATH_GSS_MECH, "r"); if (!fp) return; while (fgets(buf, sizeof(buf), fp)) { if (*buf == '#') continue; p = buf; name = strsep(&p, "\t\n "); if (p) while (isspace(*p)) p++; oid = strsep(&p, "\t\n "); if (p) while (isspace(*p)) p++; lib = strsep(&p, "\t\n "); if (p) while (isspace(*p)) p++; kobj = strsep(&p, "\t\n "); if (!name || !oid || !lib || !kobj) continue; if (strcmp(kobj, "-")) { /* * Attempt to load the kernel module if its * not already present. */ if (modfind(kobj) < 0) { if (kldload(kobj) < 0) { fprintf(stderr, "%s: can't find or load kernel module %s for %s\n", getprogname(), kobj, name); } } } } fclose(fp); } static void * gssd_find_resource(uint64_t id) { struct gss_resource *gr; if (!id) return (NULL); LIST_FOREACH(gr, &gss_resources, gr_link) if (gr->gr_id == id) return (gr->gr_res); return (NULL); } static uint64_t gssd_make_resource(void *res) { struct gss_resource *gr; if (!res) return (0); gr = malloc(sizeof(struct gss_resource)); if (!gr) return (0); gr->gr_id = (gss_next_id++) + ((uint64_t) gss_start_time << 32); gr->gr_res = res; LIST_INSERT_HEAD(&gss_resources, gr, gr_link); gss_resource_count++; if (debug_level > 1) printf("%d resources allocated\n", gss_resource_count); return (gr->gr_id); } static void gssd_delete_resource(uint64_t id) { struct gss_resource *gr; LIST_FOREACH(gr, &gss_resources, gr_link) { if (gr->gr_id == id) { LIST_REMOVE(gr, gr_link); free(gr); gss_resource_count--; if (debug_level > 1) printf("%d resources allocated\n", gss_resource_count); return; } } } static void gssd_verbose_out(const char *fmt, ...) { va_list ap; if (verbose != 0) { va_start(ap, fmt); if (debug_level == 0) vsyslog(LOG_INFO | LOG_DAEMON, fmt, ap); else vfprintf(stderr, fmt, ap); va_end(ap); } } bool_t gssd_null_1_svc(void *argp, void *result, struct svc_req *rqstp) { gssd_verbose_out("gssd_null: done\n"); return (TRUE); } bool_t gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *result, struct svc_req *rqstp) { gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; gss_name_t name = GSS_C_NO_NAME; char ccname[PATH_MAX + 5 + 1], *cp, *cp2; int gotone, gotcred; OM_uint32 min_stat; #ifndef WITHOUT_KERBEROS gss_buffer_desc principal_desc; char enctype[sizeof(uint32_t)]; int key_enctype; OM_uint32 maj_stat; #endif memset(result, 0, sizeof(*result)); if (hostbased_initiator_cred != 0 && argp->cred != 0 && argp->uid == 0) { /* * These credentials are for a host based initiator name * in a keytab file, which should now have credentials * in /tmp/krb5cc_gssd, because gss_acquire_cred() did * the equivalent of "kinit -k". */ snprintf(ccname, sizeof(ccname), "FILE:%s", GSSD_CREDENTIAL_CACHE_FILE); } else if (ccfile_dirlist[0] != '\0' && argp->cred == 0) { /* * For the "-s" case and no credentials provided as an * argument, search the directory list for an appropriate * credential cache file. If the search fails, return failure. */ gotone = 0; cp = ccfile_dirlist; do { cp2 = strchr(cp, ':'); if (cp2 != NULL) *cp2 = '\0'; gotone = find_ccache_file(cp, argp->uid, ccname); if (gotone != 0) break; if (cp2 != NULL) *cp2++ = ':'; cp = cp2; } while (cp != NULL && *cp != '\0'); if (gotone == 0) { result->major_status = GSS_S_CREDENTIALS_EXPIRED; gssd_verbose_out("gssd_init_sec_context: -s no" " credential cache file found for uid=%d\n", (int)argp->uid); return (TRUE); } } else { /* * If there wasn't a "-s" option or the credentials have * been provided as an argument, do it the old way. * When credentials are provided, the uid should be root. */ if (argp->cred != 0 && argp->uid != 0) { if (debug_level == 0) syslog(LOG_ERR, "gss_init_sec_context:" " cred for non-root"); else fprintf(stderr, "gss_init_sec_context:" " cred for non-root\n"); } snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d", (int) argp->uid); } setenv("KRB5CCNAME", ccname, TRUE); if (argp->cred) { cred = gssd_find_resource(argp->cred); if (!cred) { result->major_status = GSS_S_CREDENTIALS_EXPIRED; gssd_verbose_out("gssd_init_sec_context: cred" " resource not found\n"); return (TRUE); } } if (argp->ctx) { ctx = gssd_find_resource(argp->ctx); if (!ctx) { result->major_status = GSS_S_CONTEXT_EXPIRED; gssd_verbose_out("gssd_init_sec_context: context" " resource not found\n"); return (TRUE); } } if (argp->name) { name = gssd_find_resource(argp->name); if (!name) { result->major_status = GSS_S_BAD_NAME; gssd_verbose_out("gssd_init_sec_context: name" " resource not found\n"); return (TRUE); } } gotcred = 0; #ifndef WITHOUT_KERBEROS if (use_old_des != 0) { if (cred == GSS_C_NO_CREDENTIAL) { /* Acquire a credential for the uid. */ maj_stat = gssd_get_user_cred(&min_stat, argp->uid, &cred); if (maj_stat == GSS_S_COMPLETE) gotcred = 1; else gssd_verbose_out("gssd_init_sec_context: " "get user cred failed uid=%d major=0x%x " "minor=%d\n", (int)argp->uid, (unsigned int)maj_stat, (int)min_stat); } if (cred != GSS_C_NO_CREDENTIAL) { key_enctype = ETYPE_DES_CBC_CRC; enctype[0] = (key_enctype >> 24) & 0xff; enctype[1] = (key_enctype >> 16) & 0xff; enctype[2] = (key_enctype >> 8) & 0xff; enctype[3] = key_enctype & 0xff; principal_desc.length = sizeof(enctype); principal_desc.value = enctype; result->major_status = gss_set_cred_option( &result->minor_status, &cred, GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X, &principal_desc); gssd_verbose_out("gssd_init_sec_context: set allowable " "enctype major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); if (result->major_status != GSS_S_COMPLETE) { if (gotcred != 0) gss_release_cred(&min_stat, &cred); return (TRUE); } } } #endif result->major_status = gss_init_sec_context(&result->minor_status, cred, &ctx, name, argp->mech_type, argp->req_flags, argp->time_req, argp->input_chan_bindings, &argp->input_token, &result->actual_mech_type, &result->output_token, &result->ret_flags, &result->time_rec); gssd_verbose_out("gssd_init_sec_context: done major=0x%x minor=%d" " uid=%d\n", (unsigned int)result->major_status, (int)result->minor_status, (int)argp->uid); if (gotcred != 0) gss_release_cred(&min_stat, &cred); if (result->major_status == GSS_S_COMPLETE || result->major_status == GSS_S_CONTINUE_NEEDED) { if (argp->ctx) result->ctx = argp->ctx; else result->ctx = gssd_make_resource(ctx); } return (TRUE); } bool_t gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, accept_sec_context_res *result, struct svc_req *rqstp) { gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; gss_name_t src_name; gss_cred_id_t delegated_cred_handle; memset(result, 0, sizeof(*result)); if (argp->ctx) { ctx = gssd_find_resource(argp->ctx); if (!ctx) { result->major_status = GSS_S_CONTEXT_EXPIRED; gssd_verbose_out("gssd_accept_sec_context: ctx" " resource not found\n"); return (TRUE); } } if (argp->cred) { cred = gssd_find_resource(argp->cred); if (!cred) { result->major_status = GSS_S_CREDENTIALS_EXPIRED; gssd_verbose_out("gssd_accept_sec_context: cred" " resource not found\n"); return (TRUE); } } memset(result, 0, sizeof(*result)); result->major_status = gss_accept_sec_context(&result->minor_status, &ctx, cred, &argp->input_token, argp->input_chan_bindings, &src_name, &result->mech_type, &result->output_token, &result->ret_flags, &result->time_rec, &delegated_cred_handle); gssd_verbose_out("gssd_accept_sec_context: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); if (result->major_status == GSS_S_COMPLETE || result->major_status == GSS_S_CONTINUE_NEEDED) { if (argp->ctx) result->ctx = argp->ctx; else result->ctx = gssd_make_resource(ctx); result->src_name = gssd_make_resource(src_name); result->delegated_cred_handle = gssd_make_resource(delegated_cred_handle); } return (TRUE); } bool_t gssd_delete_sec_context_1_svc(delete_sec_context_args *argp, delete_sec_context_res *result, struct svc_req *rqstp) { gss_ctx_id_t ctx = gssd_find_resource(argp->ctx); if (ctx) { result->major_status = gss_delete_sec_context( &result->minor_status, &ctx, &result->output_token); gssd_delete_resource(argp->ctx); } else { result->major_status = GSS_S_COMPLETE; result->minor_status = 0; } gssd_verbose_out("gssd_delete_sec_context: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); return (TRUE); } bool_t gssd_export_sec_context_1_svc(export_sec_context_args *argp, export_sec_context_res *result, struct svc_req *rqstp) { gss_ctx_id_t ctx = gssd_find_resource(argp->ctx); if (ctx) { result->major_status = gss_export_sec_context( &result->minor_status, &ctx, &result->interprocess_token); result->format = KGSS_HEIMDAL_1_1; gssd_delete_resource(argp->ctx); } else { result->major_status = GSS_S_FAILURE; result->minor_status = 0; result->interprocess_token.length = 0; result->interprocess_token.value = NULL; } gssd_verbose_out("gssd_export_sec_context: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); return (TRUE); } bool_t gssd_import_name_1_svc(import_name_args *argp, import_name_res *result, struct svc_req *rqstp) { gss_name_t name; result->major_status = gss_import_name(&result->minor_status, &argp->input_name_buffer, argp->input_name_type, &name); gssd_verbose_out("gssd_import_name: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); if (result->major_status == GSS_S_COMPLETE) result->output_name = gssd_make_resource(name); else result->output_name = 0; return (TRUE); } bool_t gssd_canonicalize_name_1_svc(canonicalize_name_args *argp, canonicalize_name_res *result, struct svc_req *rqstp) { gss_name_t name = gssd_find_resource(argp->input_name); gss_name_t output_name; memset(result, 0, sizeof(*result)); if (!name) { result->major_status = GSS_S_BAD_NAME; return (TRUE); } result->major_status = gss_canonicalize_name(&result->minor_status, name, argp->mech_type, &output_name); gssd_verbose_out("gssd_canonicalize_name: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); if (result->major_status == GSS_S_COMPLETE) result->output_name = gssd_make_resource(output_name); else result->output_name = 0; return (TRUE); } bool_t gssd_export_name_1_svc(export_name_args *argp, export_name_res *result, struct svc_req *rqstp) { gss_name_t name = gssd_find_resource(argp->input_name); memset(result, 0, sizeof(*result)); if (!name) { result->major_status = GSS_S_BAD_NAME; gssd_verbose_out("gssd_export_name: name resource not found\n"); return (TRUE); } result->major_status = gss_export_name(&result->minor_status, name, &result->exported_name); gssd_verbose_out("gssd_export_name: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); return (TRUE); } bool_t gssd_release_name_1_svc(release_name_args *argp, release_name_res *result, struct svc_req *rqstp) { gss_name_t name = gssd_find_resource(argp->input_name); if (name) { result->major_status = gss_release_name(&result->minor_status, &name); gssd_delete_resource(argp->input_name); } else { result->major_status = GSS_S_COMPLETE; result->minor_status = 0; } gssd_verbose_out("gssd_release_name: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); return (TRUE); } bool_t gssd_pname_to_uid_1_svc(pname_to_uid_args *argp, pname_to_uid_res *result, struct svc_req *rqstp) { gss_name_t name = gssd_find_resource(argp->pname); uid_t uid; char buf[1024], *bufp; struct passwd pwd, *pw; size_t buflen; int error; static size_t buflen_hint = 1024; memset(result, 0, sizeof(*result)); if (name) { result->major_status = gss_pname_to_uid(&result->minor_status, name, argp->mech, &uid); if (result->major_status == GSS_S_COMPLETE) { result->uid = uid; buflen = buflen_hint; for (;;) { pw = NULL; bufp = buf; if (buflen > sizeof(buf)) bufp = malloc(buflen); if (bufp == NULL) break; error = getpwuid_r(uid, &pwd, bufp, buflen, &pw); if (error != ERANGE) break; if (buflen > sizeof(buf)) free(bufp); buflen += 1024; if (buflen > buflen_hint) buflen_hint = buflen; } if (pw) { int len = NGROUPS; int groups[NGROUPS]; result->gid = pw->pw_gid; getgrouplist(pw->pw_name, pw->pw_gid, groups, &len); result->gidlist.gidlist_len = len; result->gidlist.gidlist_val = mem_alloc(len * sizeof(int)); memcpy(result->gidlist.gidlist_val, groups, len * sizeof(int)); gssd_verbose_out("gssd_pname_to_uid: mapped" " to uid=%d, gid=%d\n", (int)result->uid, (int)result->gid); } else { result->gid = 65534; result->gidlist.gidlist_len = 0; result->gidlist.gidlist_val = NULL; gssd_verbose_out("gssd_pname_to_uid: mapped" " to uid=%d, but no groups\n", (int)result->uid); } if (bufp != NULL && buflen > sizeof(buf)) free(bufp); } else gssd_verbose_out("gssd_pname_to_uid: failed major=0x%x" " minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); } else { result->major_status = GSS_S_BAD_NAME; result->minor_status = 0; gssd_verbose_out("gssd_pname_to_uid: no name\n"); } return (TRUE); } bool_t gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struct svc_req *rqstp) { gss_name_t desired_name = GSS_C_NO_NAME; gss_cred_id_t cred; char ccname[PATH_MAX + 5 + 1], *cp, *cp2; int gotone; #ifndef WITHOUT_KERBEROS gss_buffer_desc namebuf; uint32_t minstat; krb5_error_code kret; #endif memset(result, 0, sizeof(*result)); if (argp->desired_name) { desired_name = gssd_find_resource(argp->desired_name); if (!desired_name) { result->major_status = GSS_S_BAD_NAME; gssd_verbose_out("gssd_acquire_cred: no desired name" " found\n"); return (TRUE); } } #ifndef WITHOUT_KERBEROS if (hostbased_initiator_cred != 0 && argp->desired_name != 0 && argp->uid == 0 && argp->cred_usage == GSS_C_INITIATE) { /* This is a host based initiator name in the keytab file. */ snprintf(ccname, sizeof(ccname), "FILE:%s", GSSD_CREDENTIAL_CACHE_FILE); setenv("KRB5CCNAME", ccname, TRUE); result->major_status = gss_display_name(&result->minor_status, desired_name, &namebuf, NULL); gssd_verbose_out("gssd_acquire_cred: desired name for host " "based initiator cred major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); if (result->major_status != GSS_S_COMPLETE) return (TRUE); if (namebuf.length > PATH_MAX + 5) { result->minor_status = 0; result->major_status = GSS_S_FAILURE; return (TRUE); } memcpy(ccname, namebuf.value, namebuf.length); ccname[namebuf.length] = '\0'; if ((cp = strchr(ccname, '@')) != NULL) *cp = '/'; kret = gssd_get_cc_from_keytab(ccname); gssd_verbose_out("gssd_acquire_cred: using keytab entry for " "%s, kerberos ret=%d\n", ccname, (int)kret); gss_release_buffer(&minstat, &namebuf); if (kret != 0) { result->minor_status = kret; result->major_status = GSS_S_FAILURE; return (TRUE); } } else #endif /* !WITHOUT_KERBEROS */ if (ccfile_dirlist[0] != '\0' && argp->desired_name == 0) { /* * For the "-s" case and no name provided as an * argument, search the directory list for an appropriate * credential cache file. If the search fails, return failure. */ gotone = 0; cp = ccfile_dirlist; do { cp2 = strchr(cp, ':'); if (cp2 != NULL) *cp2 = '\0'; gotone = find_ccache_file(cp, argp->uid, ccname); if (gotone != 0) break; if (cp2 != NULL) *cp2++ = ':'; cp = cp2; } while (cp != NULL && *cp != '\0'); if (gotone == 0) { result->major_status = GSS_S_CREDENTIALS_EXPIRED; gssd_verbose_out("gssd_acquire_cred: no cred cache" " file found\n"); return (TRUE); } setenv("KRB5CCNAME", ccname, TRUE); } else { /* * If there wasn't a "-s" option or the name has * been provided as an argument, do it the old way. * When a name is provided, it will normally exist in the * default keytab file and the uid will be root. */ if (argp->desired_name != 0 && argp->uid != 0) { if (debug_level == 0) syslog(LOG_ERR, "gss_acquire_cred:" " principal_name for non-root"); else fprintf(stderr, "gss_acquire_cred:" " principal_name for non-root\n"); } snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d", (int) argp->uid); setenv("KRB5CCNAME", ccname, TRUE); } result->major_status = gss_acquire_cred(&result->minor_status, desired_name, argp->time_req, argp->desired_mechs, argp->cred_usage, &cred, &result->actual_mechs, &result->time_rec); gssd_verbose_out("gssd_acquire_cred: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); if (result->major_status == GSS_S_COMPLETE) result->output_cred = gssd_make_resource(cred); else result->output_cred = 0; return (TRUE); } bool_t gssd_set_cred_option_1_svc(set_cred_option_args *argp, set_cred_option_res *result, struct svc_req *rqstp) { gss_cred_id_t cred = gssd_find_resource(argp->cred); memset(result, 0, sizeof(*result)); if (!cred) { result->major_status = GSS_S_CREDENTIALS_EXPIRED; gssd_verbose_out("gssd_set_cred: no credentials\n"); return (TRUE); } result->major_status = gss_set_cred_option(&result->minor_status, &cred, argp->option_name, &argp->option_value); gssd_verbose_out("gssd_set_cred: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); return (TRUE); } bool_t gssd_release_cred_1_svc(release_cred_args *argp, release_cred_res *result, struct svc_req *rqstp) { gss_cred_id_t cred = gssd_find_resource(argp->cred); if (cred) { result->major_status = gss_release_cred(&result->minor_status, &cred); gssd_delete_resource(argp->cred); } else { result->major_status = GSS_S_COMPLETE; result->minor_status = 0; } gssd_verbose_out("gssd_release_cred: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); return (TRUE); } bool_t gssd_display_status_1_svc(display_status_args *argp, display_status_res *result, struct svc_req *rqstp) { result->message_context = argp->message_context; result->major_status = gss_display_status(&result->minor_status, argp->status_value, argp->status_type, argp->mech_type, &result->message_context, &result->status_string); gssd_verbose_out("gssd_display_status: done major=0x%x minor=%d\n", (unsigned int)result->major_status, (int)result->minor_status); return (TRUE); } int gssd_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result) { /* * We don't use XDR to free the results - anything which was * allocated came from GSS-API. We use xdr_result to figure * out what to do. */ OM_uint32 junk; if (xdr_result == (xdrproc_t) xdr_init_sec_context_res) { init_sec_context_res *p = (init_sec_context_res *) result; gss_release_buffer(&junk, &p->output_token); } else if (xdr_result == (xdrproc_t) xdr_accept_sec_context_res) { accept_sec_context_res *p = (accept_sec_context_res *) result; gss_release_buffer(&junk, &p->output_token); } else if (xdr_result == (xdrproc_t) xdr_delete_sec_context_res) { delete_sec_context_res *p = (delete_sec_context_res *) result; gss_release_buffer(&junk, &p->output_token); } else if (xdr_result == (xdrproc_t) xdr_export_sec_context_res) { export_sec_context_res *p = (export_sec_context_res *) result; if (p->interprocess_token.length) memset(p->interprocess_token.value, 0, p->interprocess_token.length); gss_release_buffer(&junk, &p->interprocess_token); } else if (xdr_result == (xdrproc_t) xdr_export_name_res) { export_name_res *p = (export_name_res *) result; gss_release_buffer(&junk, &p->exported_name); } else if (xdr_result == (xdrproc_t) xdr_acquire_cred_res) { acquire_cred_res *p = (acquire_cred_res *) result; gss_release_oid_set(&junk, &p->actual_mechs); } else if (xdr_result == (xdrproc_t) xdr_pname_to_uid_res) { pname_to_uid_res *p = (pname_to_uid_res *) result; if (p->gidlist.gidlist_val) free(p->gidlist.gidlist_val); } else if (xdr_result == (xdrproc_t) xdr_display_status_res) { display_status_res *p = (display_status_res *) result; gss_release_buffer(&junk, &p->status_string); } return (TRUE); } /* * Search a directory for the most likely candidate to be used as the * credential cache for a uid. If successful, return 1 and fill the * file's path id into "rpath". Otherwise, return 0. */ static int find_ccache_file(const char *dirpath, uid_t uid, char *rpath) { DIR *dirp; struct dirent *dp; struct stat sb; time_t exptime, oexptime; int gotone, len, rating, orating; char namepath[PATH_MAX + 5 + 1]; char retpath[PATH_MAX + 5 + 1]; dirp = opendir(dirpath); if (dirp == NULL) return (0); gotone = 0; orating = 0; oexptime = 0; while ((dp = readdir(dirp)) != NULL) { len = snprintf(namepath, sizeof(namepath), "%s/%s", dirpath, dp->d_name); if (len < sizeof(namepath) && (hostbased_initiator_cred == 0 || strcmp(namepath, GSSD_CREDENTIAL_CACHE_FILE) != 0) && strstr(dp->d_name, ccfile_substring) != NULL && lstat(namepath, &sb) >= 0 && sb.st_uid == uid && S_ISREG(sb.st_mode)) { len = snprintf(namepath, sizeof(namepath), "FILE:%s/%s", dirpath, dp->d_name); if (len < sizeof(namepath) && is_a_valid_tgt_cache(namepath, uid, &rating, &exptime) != 0) { if (gotone == 0 || rating > orating || (rating == orating && exptime > oexptime)) { orating = rating; oexptime = exptime; strcpy(retpath, namepath); gotone = 1; } } } } closedir(dirp); if (gotone != 0) { strcpy(rpath, retpath); return (1); } return (0); } /* * Try to determine if the file is a valid tgt cache file. * Check that the file has a valid tgt for a principal. * If it does, return 1, otherwise return 0. * It also returns a "rating" and the expiry time for the TGT, when found. * This "rating" is higher based on heuristics that make it more * likely to be the correct credential cache file to use. It can * be used by the caller, along with expiry time, to select from * multiple credential cache files. */ static int is_a_valid_tgt_cache(const char *filepath, uid_t uid, int *retrating, time_t *retexptime) { #ifndef WITHOUT_KERBEROS krb5_context context; krb5_principal princ; krb5_ccache ccache; krb5_error_code retval; krb5_cc_cursor curse; krb5_creds krbcred; int gotone, orating, rating, ret; struct passwd *pw; char *cp, *cp2, *pname; time_t exptime; /* Find a likely name for the uid principal. */ pw = getpwuid(uid); /* * Do a bunch of krb5 library stuff to try and determine if * this file is a credentials cache with an appropriate TGT * in it. */ retval = krb5_init_context(&context); if (retval != 0) return (0); retval = krb5_cc_resolve(context, filepath, &ccache); if (retval != 0) { krb5_free_context(context); return (0); } ret = 0; orating = 0; exptime = 0; retval = krb5_cc_start_seq_get(context, ccache, &curse); if (retval == 0) { while ((retval = krb5_cc_next_cred(context, ccache, &curse, &krbcred)) == 0) { gotone = 0; rating = 0; retval = krb5_unparse_name(context, krbcred.server, &pname); if (retval == 0) { cp = strchr(pname, '/'); if (cp != NULL) { *cp++ = '\0'; if (strcmp(pname, "krbtgt") == 0 && krbcred.times.endtime > time(NULL) ) { gotone = 1; /* * Test to see if this is a * tgt for cross-realm auth. * Rate it higher, if it is not. */ cp2 = strchr(cp, '@'); if (cp2 != NULL) { *cp2++ = '\0'; if (strcmp(cp, cp2) == 0) rating++; } } } free(pname); } if (gotone != 0) { retval = krb5_unparse_name(context, krbcred.client, &pname); if (retval == 0) { cp = strchr(pname, '@'); if (cp != NULL) { *cp++ = '\0'; if (pw != NULL && strcmp(pname, pw->pw_name) == 0) rating++; if (strchr(pname, '/') == NULL) rating++; if (pref_realm[0] != '\0' && strcmp(cp, pref_realm) == 0) rating++; } } free(pname); if (rating > orating) { orating = rating; exptime = krbcred.times.endtime; } else if (rating == orating && krbcred.times.endtime > exptime) exptime = krbcred.times.endtime; ret = 1; } krb5_free_cred_contents(context, &krbcred); } krb5_cc_end_seq_get(context, ccache, &curse); } krb5_cc_close(context, ccache); krb5_free_context(context); if (ret != 0) { *retrating = orating; *retexptime = exptime; } return (ret); #else /* WITHOUT_KERBEROS */ return (0); #endif /* !WITHOUT_KERBEROS */ } #ifndef WITHOUT_KERBEROS /* * This function attempts to do essentially a "kinit -k" for the principal * name provided as the argument, so that there will be a TGT in the * credential cache. */ static krb5_error_code gssd_get_cc_from_keytab(const char *name) { krb5_error_code ret, opt_ret, princ_ret, cc_ret, kt_ret, cred_ret; krb5_context context; krb5_principal principal; krb5_keytab kt; krb5_creds cred; krb5_get_init_creds_opt *opt; krb5_deltat start_time = 0; krb5_ccache ccache; ret = krb5_init_context(&context); if (ret != 0) return (ret); opt_ret = cc_ret = kt_ret = cred_ret = 1; /* anything non-zero */ princ_ret = ret = krb5_parse_name(context, name, &principal); if (ret == 0) opt_ret = ret = krb5_get_init_creds_opt_alloc(context, &opt); if (ret == 0) cc_ret = ret = krb5_cc_default(context, &ccache); if (ret == 0) ret = krb5_cc_initialize(context, ccache, principal); if (ret == 0) { krb5_get_init_creds_opt_set_default_flags(context, "gssd", krb5_principal_get_realm(context, principal), opt); kt_ret = ret = krb5_kt_default(context, &kt); } if (ret == 0) cred_ret = ret = krb5_get_init_creds_keytab(context, &cred, principal, kt, start_time, NULL, opt); if (ret == 0) ret = krb5_cc_store_cred(context, ccache, &cred); if (kt_ret == 0) krb5_kt_close(context, kt); if (cc_ret == 0) krb5_cc_close(context, ccache); if (opt_ret == 0) krb5_get_init_creds_opt_free(context, opt); if (princ_ret == 0) krb5_free_principal(context, principal); if (cred_ret == 0) krb5_free_cred_contents(context, &cred); krb5_free_context(context); return (ret); } /* * Acquire a gss credential for a uid. */ static OM_uint32 gssd_get_user_cred(OM_uint32 *min_statp, uid_t uid, gss_cred_id_t *credp) { gss_buffer_desc principal_desc; gss_name_t name; OM_uint32 maj_stat, min_stat; gss_OID_set mechlist; struct passwd *pw; pw = getpwuid(uid); if (pw == NULL) { *min_statp = 0; return (GSS_S_FAILURE); } /* * The mechanism must be set to KerberosV for acquisition * of credentials to work reliably. */ maj_stat = gss_create_empty_oid_set(min_statp, &mechlist); if (maj_stat != GSS_S_COMPLETE) return (maj_stat); maj_stat = gss_add_oid_set_member(min_statp, GSS_KRB5_MECH_OID_X, &mechlist); if (maj_stat != GSS_S_COMPLETE) { gss_release_oid_set(&min_stat, &mechlist); return (maj_stat); } principal_desc.value = (void *)pw->pw_name; principal_desc.length = strlen(pw->pw_name); maj_stat = gss_import_name(min_statp, &principal_desc, GSS_C_NT_USER_NAME, &name); if (maj_stat != GSS_S_COMPLETE) { gss_release_oid_set(&min_stat, &mechlist); return (maj_stat); } /* Acquire the credentials. */ maj_stat = gss_acquire_cred(min_statp, name, 0, mechlist, GSS_C_INITIATE, credp, NULL, NULL); gss_release_name(&min_stat, &name); gss_release_oid_set(&min_stat, &mechlist); return (maj_stat); } #endif /* !WITHOUT_KERBEROS */ void gssd_terminate(int sig __unused) { #ifndef WITHOUT_KERBEROS if (hostbased_initiator_cred != 0) unlink(GSSD_CREDENTIAL_CACHE_FILE); #endif gssd_syscall(""); exit(0); }