summaryrefslogtreecommitdiffstats
path: root/utils/gssd/krb5_util.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/gssd/krb5_util.c')
-rw-r--r--utils/gssd/krb5_util.c809
1 files changed, 809 insertions, 0 deletions
diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
new file mode 100644
index 0000000..2dcc2ee
--- /dev/null
+++ b/utils/gssd/krb5_util.c
@@ -0,0 +1,809 @@
+/*
+ * Adapted in part from MIT Kerberos 5-1.2.1 slave/kprop.c and from
+ * http://docs.sun.com/?p=/doc/816-1331/6m7oo9sms&a=view
+ *
+ * Copyright (c) 2002-2004 The Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Andy Adamson <andros@umich.edu>
+ * J. Bruce Fields <bfields@umich.edu>
+ * Marius Aamodt Eriksen <marius@umich.edu>
+ * Kevin Coffman <kwc@umich.edu>
+ */
+
+/*
+ * slave/kprop.c
+ *
+ * Copyright 1990,1991 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * Copyright 1994 by OpenVision Technologies, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appears in all copies and
+ * that both that copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of OpenVision not be used
+ * in advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. OpenVision makes no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+ * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+/*
+ krb5_util.c
+
+ Copyright (c) 2004 The Regents of the University of Michigan.
+ All rights reserved.
+
+ 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.
+ 3. Neither the name of the University nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``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 REGENTS 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.
+
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include "config.h"
+#include <sys/param.h>
+#include <rpc/rpc.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <time.h>
+#include <gssapi/gssapi.h>
+#ifdef USE_PRIVATE_KRB5_FUNCTIONS
+#include <gssapi/gssapi_krb5.h>
+#endif
+#include <krb5.h>
+#include <rpc/auth_gss.h>
+
+#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;
+
+/*==========================*/
+/*=== Internal routines ===*/
+/*==========================*/
+
+static int select_krb5_ccache(const struct dirent *d);
+static int gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d);
+static int gssd_get_single_krb5_cred(krb5_context context,
+ krb5_keytab kt, struct gssd_k5_kt_princ *ple);
+static int gssd_have_realm_ple(krb5_data *realm);
+static int gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt,
+ char *kt_name);
+
+/*
+ * Called from the scandir function to weed out potential krb5
+ * credentials cache files
+ *
+ * Returns:
+ * 0 => don't select this one
+ * 1 => select this one
+ */
+static int
+select_krb5_ccache(const struct dirent *d)
+{
+ /* Don't consider anything but regular files. (No symlinks, etc.) */
+ if (d->d_type != DT_REG)
+ return 0;
+
+ if (strstr(d->d_name, GSSD_DEFAULT_CRED_PREFIX))
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Look in the GSSD_DEFAULT_CRED_DIR 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.
+ *
+ * Returns:
+ * 0 => could not find an existing entry
+ * 1 => found an existing entry
+ */
+static int
+gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
+{
+ struct dirent **namelist;
+ int n;
+ int i;
+ int found = 0;
+ struct dirent *best_match_dir = NULL;
+ struct stat best_match_stat, tmp_stat;
+
+ *d = NULL;
+ n = scandir(GSSD_DEFAULT_CRED_DIR, &namelist, select_krb5_ccache, 0);
+ if (n < 0) {
+ perror("scandir looking for krb5 credentials caches");
+ }
+ else if (n > 0) {
+ char substring[128];
+ char statname[1024];
+ snprintf(substring, sizeof(substring), "_%d", uid);
+ for (i = 0; i < n; i++) {
+ printerr(3, "CC file '%s' being considered\n",
+ namelist[i]->d_name);
+ if (strstr(namelist[i]->d_name, substring)) {
+ snprintf(statname, sizeof(statname),
+ "%s/%s", GSSD_DEFAULT_CRED_DIR,
+ namelist[i]->d_name);
+ if (stat(statname, &tmp_stat)) {
+ printerr(0, "Error doing stat "
+ "on file '%s'\n",
+ statname);
+ continue;
+ }
+ printerr(3, "CC file '%s' matches "
+ "name check and has "
+ "mtime of %u\n",
+ namelist[i]->d_name,
+ tmp_stat.st_mtime);
+ /* if more than one match is found,
+ * return the most recent (the one
+ * with the latest mtime),
+ * and don't free the dirent */
+ if (!found) {
+ best_match_dir = namelist[i];
+ best_match_stat = tmp_stat;
+ found++;
+ }
+ else {
+ /*
+ * 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) {
+ free(best_match_dir);
+ best_match_dir = namelist[i];
+ best_match_stat = tmp_stat;
+ }
+ else {
+ free(namelist[i]);
+ }
+ printerr(3, "CC file '%s' is our "
+ "current best match "
+ "with mtime of %u\n",
+ best_match_dir->d_name,
+ best_match_stat.st_mtime);
+ }
+ }
+ else
+ free(namelist[i]);
+ }
+ free(namelist);
+ }
+ if (found)
+ {
+ *d = best_match_dir;
+ }
+ 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;
+/* krb5_enctype enctypes[] = {ENCTYPE_DES3_CBC_SHA1};
+ ENCTYPE_ARCFOUR_HMAC, */
+ krb5_enctype enctypes[] = {ENCTYPE_DES3_CBC_SHA1,
+ ENCTYPE_DES_CBC_MD5,
+ ENCTYPE_DES_CBC_CRC};
+ int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
+
+ maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
+ GSS_C_NULL_OID_SET, GSS_C_INITIATE,
+ &credh, NULL, NULL);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ printerr(0, "WARNING: error from gss_acquire_cred "
+ "for user with uid %d (%s)\n",
+ uid, error_message(min_stat));
+ return -1;
+ }
+
+ maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid,
+ num_enctypes, &enctypes);
+ if (maj_stat != GSS_S_COMPLETE) {
+ printerr(0, "WARNING: error from gss_set_allowable_enctypes "
+ "for user with uid %d (%s)\n",
+ uid, error_message(min_stat));
+ return -1;
+ }
+ sec->cred = credh;
+
+ return 0;
+}
+#endif /* HAVE_SET_ALLOWABLE_ENCTYPES */
+
+/*
+ * Obtain credentials via a key in the keytab given
+ * a keytab handle and a gssd_k5_kt_princ structure.
+ * Checks to see if current credentials are expired,
+ * if not, uses the keytab to obtain new credentials.
+ *
+ * Returns:
+ * 0 => success (or credentials have not expired)
+ * nonzero => error
+ */
+static int
+gssd_get_single_krb5_cred(krb5_context context,
+ krb5_keytab kt,
+ struct gssd_k5_kt_princ *ple)
+{
+ krb5_get_init_creds_opt options;
+ krb5_creds my_creds;
+ krb5_ccache ccache = NULL;
+ char kt_name[BUFSIZ];
+ char cc_name[BUFSIZ];
+ int code;
+ time_t now = time(0);
+
+ memset(&my_creds, 0, sizeof(my_creds));
+
+ if (ple->ccname && ple->endtime > now) {
+ printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
+ ple->ccname, ple->endtime);
+ code = 0;
+ goto out;
+ }
+
+ if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
+ printerr(0, "ERROR: Unable to get keytab name in "
+ "gssd_get_single_krb5_cred\n");
+ goto out;
+ }
+
+ krb5_get_init_creds_opt_init(&options);
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+
+#ifdef TEST_SHORT_LIFETIME
+ /* set a short lifetime (for debugging only!) */
+ printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
+ krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
+#endif
+ if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
+ kt, 0, 0, &options))) {
+ char *pname;
+ if ((krb5_unparse_name(context, ple->princ, &pname))) {
+ pname = NULL;
+ }
+ printerr(0, "WARNING: %s while getting initial ticket for "
+ "principal '%s' from keytab '%s'\n",
+ error_message(code),
+ pname ? pname : "<unparsable>", kt_name);
+ if (pname) krb5_free_unparsed_name(context, pname);
+ goto out;
+ }
+
+ /*
+ * Initialize cache file which we're going to be using
+ */
+
+ snprintf(cc_name, sizeof(cc_name), "FILE:%s/%s%s_%s",
+ GSSD_DEFAULT_CRED_DIR, GSSD_DEFAULT_CRED_PREFIX,
+ GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
+ ple->endtime = my_creds.times.endtime;
+ ple->ccname = strdup(cc_name);
+ if (ple->ccname == NULL) {
+ printerr(0, "ERROR: no storage to duplicate credentials "
+ "cache name\n");
+ code = ENOMEM;
+ goto out;
+ }
+ if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
+ printerr(0, "ERROR: %s while opening credential cache '%s'\n",
+ error_message(code), cc_name);
+ goto out;
+ }
+ if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
+ printerr(0, "ERROR: %s while initializing credential "
+ "cache '%s'\n", error_message(code), cc_name);
+ goto out;
+ }
+ if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
+ printerr(0, "ERROR: %s while storing credentials in '%s'\n",
+ error_message(code), cc_name);
+ goto out;
+ }
+
+ code = 0;
+ printerr(1, "Using (machine) credentials cache: '%s'\n", cc_name);
+ out:
+ if (ccache)
+ krb5_cc_close(context, ccache);
+ krb5_free_cred_contents(context, &my_creds);
+ return (code);
+}
+
+/*
+ * Determine if we already have a ple for the given realm
+ *
+ * Returns:
+ * 0 => no ple found for given realm
+ * 1 => found ple for given realm
+ */
+static int
+gssd_have_realm_ple(krb5_data *realm)
+{
+ struct gssd_k5_kt_princ *ple;
+
+ for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ if ((realm->length == strlen(ple->realm)) &&
+ (strncmp(realm->data, ple->realm, realm->length) == 0)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Process the given keytab file and create a list of principals we
+ * might use to perform mount operations.
+ *
+ * Returns:
+ * 0 => Sucess
+ * nonzero => Error
+ */
+static int
+gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt, char *kt_name)
+{
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kte;
+ krb5_error_code code;
+ struct gssd_k5_kt_princ *ple;
+ int retval = -1;
+
+ /*
+ * Look through each entry in the keytab file and determine
+ * if we might want to use it later to do a mount. If so,
+ * save info in the global principal list
+ * (gssd_k5_kt_princ_list).
+ * Note: (ple == principal list entry)
+ */
+ if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
+ printerr(0, "ERROR: %s while beginning keytab scan "
+ "for keytab '%s'\n",
+ error_message(code), kt_name);
+ retval = code;
+ goto out;
+ }
+
+ while ((code = krb5_kt_next_entry(context, kt, &kte, &cursor)) == 0) {
+ char *pname;
+ if ((code = krb5_unparse_name(context, kte.principal,
+ &pname))) {
+ printerr(0, "WARNING: Skipping keytab entry because "
+ "we failed to unparse principal name: %s\n",
+ error_message(code));
+ continue;
+ }
+ printerr(2, "Processing keytab entry for principal '%s'\n",
+ pname);
+ if ( (kte.principal->data[0].length == GSSD_SERVICE_NAME_LEN) &&
+ (strncmp(kte.principal->data[0].data, GSSD_SERVICE_NAME,
+ GSSD_SERVICE_NAME_LEN) == 0) &&
+ (!gssd_have_realm_ple(&kte.principal->realm)) ) {
+ printerr(2, "We will use this entry (%s)\n", pname);
+ ple = malloc(sizeof(struct gssd_k5_kt_princ));
+ if (ple == NULL) {
+ printerr(0, "ERROR: could not allocate storage "
+ "for principal list entry\n");
+ krb5_free_unparsed_name(context, pname);
+ retval = ENOMEM;
+ goto out;
+ }
+ /* These will be filled in later */
+ ple->next = NULL;
+ ple->ccname = NULL;
+ ple->endtime = 0;
+ if ((ple->realm =
+ strndup(kte.principal->realm.data,
+ kte.principal->realm.length))
+ == NULL) {
+ printerr(0, "ERROR: %s while copying realm to "
+ "principal list entry\n",
+ "not enough memory");
+ krb5_free_unparsed_name(context, pname);
+ retval = ENOMEM;
+ goto out;
+ }
+ if ((code = krb5_copy_principal(context,
+ kte.principal, &ple->princ))) {
+ printerr(0, "ERROR: %s while copying principal "
+ "to principal list entry\n",
+ error_message(code));
+ krb5_free_unparsed_name(context, pname);
+ retval = code;
+ goto out;
+ }
+ if (gssd_k5_kt_princ_list == NULL)
+ gssd_k5_kt_princ_list = ple;
+ else {
+ ple->next = gssd_k5_kt_princ_list;
+ gssd_k5_kt_princ_list = ple;
+ }
+ }
+ else {
+ printerr(2, "We will NOT use this entry (%s)\n",
+ pname);
+ }
+ krb5_free_unparsed_name(context, pname);
+ }
+
+ if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
+ printerr(0, "WARNING: %s while ending keytab scan for "
+ "keytab '%s'\n",
+ error_message(code), kt_name);
+ }
+
+ retval = 0;
+ out:
+ return retval;
+}
+
+
+/*==========================*/
+/*=== External routines ===*/
+/*==========================*/
+
+/*
+ * Attempt to find the best match for a credentials cache file
+ * given only a UID. We really need more information, but we
+ * do the best we can.
+ *
+ * Returns:
+ * void
+ */
+void
+gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername)
+{
+ char buf[MAX_NETOBJ_SZ];
+#ifdef HAVE_GSS_KRB5_CCACHE_NAME
+ u_int min_stat;
+#endif
+ struct dirent *d;
+
+ 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, &d)) {
+ snprintf(buf, sizeof(buf), "FILE:%s/%s",
+ GSSD_DEFAULT_CRED_DIR, d->d_name);
+ free(d);
+ }
+ else
+ snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
+ GSSD_DEFAULT_CRED_DIR,
+ GSSD_DEFAULT_CRED_PREFIX, uid);
+ printerr(2, "using %s as credentials cache for client with "
+ "uid %u for server %s\n", buf, uid, servername);
+#ifdef HAVE_GSS_KRB5_CCACHE_NAME
+ gss_krb5_ccache_name(&min_stat, buf, NULL);
+#else
+ /*
+ * Set the KRB5CCNAME environment variable to tell the krb5 code
+ * which credentials cache to use. (Instead of using the private
+ * function above for which there is no generic gssapi
+ * equivalent.)
+ */
+ setenv("KRB5CCNAME", buf, 1);
+#endif
+}
+
+/*
+ * Let the gss code know where to find the machine credentials ccache.
+ *
+ * Returns:
+ * void
+ */
+void
+gssd_setup_krb5_machine_gss_ccache(char *ccname)
+{
+#ifdef HAVE_GSS_KRB5_CCACHE_NAME
+ u_int min_stat;
+#endif
+ printerr(2, "using %s as credentials cache for machine creds\n",
+ ccname);
+#ifdef HAVE_GSS_KRB5_CCACHE_NAME
+ gss_krb5_ccache_name(&min_stat, ccname, NULL);
+#else
+ /*
+ * Set the KRB5CCNAME environment variable to tell the krb5 code
+ * which credentials cache to use. (Instead of using the private
+ * function above for which there is no generic gssapi
+ * equivalent.)
+ */
+ setenv("KRB5CCNAME", ccname, 1);
+#endif
+}
+
+/*
+ * The first time through this routine, go through the keytab and
+ * determine which keys we will try to use as machine credentials.
+ * Every time through this routine, try to obtain credentials using
+ * the keytab entries selected the first time through.
+ *
+ * Returns:
+ * 0 => obtained one or more credentials
+ * nonzero => error
+ *
+ */
+
+int
+gssd_refresh_krb5_machine_creds(void)
+{
+ krb5_context context = NULL;
+ krb5_keytab kt = NULL;;
+ krb5_error_code code;
+ int retval = -1;
+ struct gssd_k5_kt_princ *ple;
+ int gotone = 0;
+ static int processed_keytab = 0;
+
+
+ code = krb5_init_context(&context);
+ if (code) {
+ printerr(0, "ERROR: %s while initializing krb5 in "
+ "gssd_refresh_krb5_machine_creds\n",
+ error_message(code));
+ retval = code;
+ goto out;
+ }
+
+ printerr(1, "Using keytab file '%s'\n", keytabfile);
+
+ if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
+ printerr(0, "ERROR: %s while resolving keytab '%s'\n",
+ error_message(code), keytabfile);
+ goto out;
+ }
+
+ /* Only go through the keytab file once. Only print messages once. */
+ if (gssd_k5_kt_princ_list == NULL && !processed_keytab) {
+ processed_keytab = 1;
+ gssd_process_krb5_keytab(context, kt, keytabfile);
+ if (gssd_k5_kt_princ_list == NULL) {
+ printerr(0, "ERROR: No usable keytab entries found in "
+ "keytab '%s'\n", keytabfile);
+ printerr(0, "Do you have a valid keytab entry for "
+ "%s/<your.host>@<YOUR.REALM> in "
+ "keytab file %s ?\n",
+ GSSD_SERVICE_NAME, keytabfile);
+ printerr(0, "Continuing without (machine) credentials "
+ "- nfs4 mounts with Kerberos will fail\n");
+ }
+ }
+
+ /*
+ * If we don't have any keytab entries we liked, then we have a problem
+ */
+ if (gssd_k5_kt_princ_list == NULL) {
+ retval = ENOENT;
+ goto out;
+ }
+
+ /*
+ * Now go through the list of saved entries and get initial
+ * credentials for them (We can't do this while making the
+ * list because it messes up the keytab iteration cursor
+ * when we use the keytab to get credentials.)
+ */
+ for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ if ((gssd_get_single_krb5_cred(context, kt, ple)) == 0) {
+ gotone++;
+ }
+ }
+ if (!gotone) {
+ printerr(0, "ERROR: No usable machine credentials obtained\n");
+ goto out;
+ }
+
+ retval = 0;
+ out:
+ if (kt) krb5_kt_close(context, kt);
+ krb5_free_context(context);
+
+ return retval;
+}
+
+
+/*
+ * Return an array of pointers to names of credential cache files
+ * which can be used to try to create gss contexts with a server.
+ *
+ * Returns:
+ * 0 => list is attached
+ * nonzero => error
+ */
+int
+gssd_get_krb5_machine_cred_list(char ***list)
+{
+ char **l;
+ int listinc = 10;
+ int listsize = listinc;
+ int i = 0;
+ int retval;
+ struct gssd_k5_kt_princ *ple;
+
+ /* Assume failure */
+ retval = -1;
+ *list = (char **) NULL;
+
+ /* Refresh machine credentials */
+ if ((retval = gssd_refresh_krb5_machine_creds())) {
+ goto out;
+ }
+
+ if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
+ retval = ENOMEM;
+ goto out;
+ }
+
+ for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ if (ple->ccname) {
+ 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) {
+ retval = ENOMEM;
+ goto out;
+ }
+ }
+ }
+ if (i > 0) {
+ l[i] = NULL;
+ *list = l;
+ retval = 0;
+ goto out;
+ }
+ out:
+ return retval;
+}
+
+/*
+ * Frees the list of names returned in get_krb5_machine_cred_list()
+ */
+void
+gssd_free_krb5_machine_cred_list(char **list)
+{
+ char **n;
+
+ if (list == NULL)
+ return;
+ for (n = list; n && *n; n++) {
+ free(*n);
+ }
+ free(list);
+}
+
+/*
+ * Called upon exit. Destroys machine credentials.
+ */
+void
+gssd_destroy_krb5_machine_creds(void)
+{
+ krb5_context context;
+ krb5_error_code code = 0;
+ krb5_ccache ccache;
+ struct gssd_k5_kt_princ *ple;
+
+ code = krb5_init_context(&context);
+ if (code) {
+ printerr(0, "ERROR: %s while initializing krb5\n",
+ error_message(code));
+ goto out;
+ }
+
+ for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ if (!ple->ccname)
+ continue;
+ if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
+ printerr(0, "WARNING: %s while resolving credential "
+ "cache '%s' for destruction\n",
+ error_message(code), ple->ccname);
+ continue;
+ }
+
+ if ((code = krb5_cc_destroy(context, ccache))) {
+ printerr(0, "WARNING: %s while destroying credential "
+ "cache '%s'\n",
+ error_message(code), ple->ccname);
+ }
+ }
+ out:
+ krb5_free_context(context);
+}
+