]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/gssd/krb5_util.c
gssd: don't krb5_free_context if krb5_init_context fails
[nfs-utils.git] / utils / gssd / krb5_util.c
index 6af286975c445f01c38904fc3c64b9248d784632..4befa72a5b1f6fb9c496a705602e57a0e30e1f00 100644 (file)
 
 */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE
 #endif
-#include "config.h"
+
 #include <sys/param.h>
 #include <rpc/rpc.h>
 #include <sys/stat.h>
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <unistd.h>
 #include <string.h>
 #include <dirent.h>
+#include <netdb.h>
+#include <ctype.h>
 #include <errno.h>
 #include <time.h>
 #include <gssapi/gssapi.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;
 
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+int limit_to_legacy_enctypes = 0;
+#endif
+
 /*==========================*/
 /*===  Internal routines ===*/
 /*==========================*/
 
 static int select_krb5_ccache(const struct dirent *d);
-static int gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d);
+static int gssd_find_existing_krb5_ccache(uid_t uid, char *dirname,
+               const char **cctype, struct dirent **d);
 static int gssd_get_single_krb5_cred(krb5_context context,
-               krb5_keytab kt, struct gssd_k5_kt_princ *ple);
-static int gssd_have_realm_ple(void *realm);
-static int gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt,
-               char *kt_name);
+               krb5_keytab kt, struct gssd_k5_kt_princ *ple, int nocache);
+static int query_krb5_ccache(const char* cred_cache, char **ret_princname,
+               char **ret_realm);
 
 /*
  * Called from the scandir function to weed out potential krb5
@@ -158,18 +168,18 @@ select_krb5_ccache(const struct dirent *d)
 }
 
 /*
- * Look in the ccachedir for files that look like they
+ * Look in directory "dirname" for files that look like they
  * are Kerberos Credential Cache files for a given UID.  Return
  * non-zero and the dirent pointer for the entry most likely to be
  * what we want. Otherwise, return zero and no dirent pointer.
  * The caller is responsible for freeing the dirent if one is returned.
  *
- * Returns:
- *     0 => could not find an existing entry
- *     1 => found an existing entry
+ * Returns 0 if a valid-looking entry was found and a non-zero error
+ * code otherwise.
  */
 static int
-gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
+gssd_find_existing_krb5_ccache(uid_t uid, char *dirname,
+                              const char **cctype, struct dirent **d)
 {
        struct dirent **namelist;
        int n;
@@ -177,42 +187,83 @@ gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
        int found = 0;
        struct dirent *best_match_dir = NULL;
        struct stat best_match_stat, tmp_stat;
+       char buf[1030];
+       char *princname = NULL;
+       char *realm = NULL;
+       int score, best_match_score = 0, err = -EACCES;
 
        memset(&best_match_stat, 0, sizeof(best_match_stat));
+       *cctype = NULL;
        *d = NULL;
-       n = scandir(ccachedir, &namelist, select_krb5_ccache, 0);
+       n = scandir(dirname, &namelist, select_krb5_ccache, 0);
        if (n < 0) {
-               perror("scandir looking for krb5 credentials caches");
+               printerr(1, "Error doing scandir on directory '%s': %s\n",
+                       dirname, strerror(errno));
        }
        else if (n > 0) {
                char statname[1024];
                for (i = 0; i < n; i++) {
-                       printerr(3, "CC file '%s' being considered\n",
-                                namelist[i]->d_name);
                        snprintf(statname, sizeof(statname),
-                                "%s/%s", ccachedir, namelist[i]->d_name);
+                                "%s/%s", dirname, namelist[i]->d_name);
+                       printerr(3, "CC '%s' being considered, "
+                                "with preferred realm '%s'\n",
+                                statname, preferred_realm ?
+                                       preferred_realm : "<none selected>");
                        if (lstat(statname, &tmp_stat)) {
-                               printerr(0, "Error doing stat on file '%s'\n",
+                               printerr(0, "Error doing stat on '%s'\n",
                                         statname);
                                free(namelist[i]);
                                continue;
                        }
                        /* Only pick caches owned by the user (uid) */
                        if (tmp_stat.st_uid != uid) {
-                               printerr(3, "'%s' owned by %u, not %u\n",
+                               printerr(3, "CC '%s' owned by %u, not %u\n",
                                         statname, tmp_stat.st_uid, uid);
                                free(namelist[i]);
                                continue;
                        }
-                       if (!S_ISREG(tmp_stat.st_mode)) {
-                               printerr(3, "'%s' is not a regular file\n",
+                       if (!S_ISREG(tmp_stat.st_mode) &&
+                           !S_ISDIR(tmp_stat.st_mode)) {
+                               printerr(3, "CC '%s' is not a regular "
+                                        "file or directory\n",
                                         statname);
                                free(namelist[i]);
                                continue;
                        }
-                       printerr(3, "CC file '%s' matches owner check and has "
-                                "mtime of %u\n",
-                                namelist[i]->d_name, tmp_stat.st_mtime);
+                       if (uid == 0 && !root_uses_machine_creds && 
+                               strstr(namelist[i]->d_name, "_machine_")) {
+                               printerr(3, "CC '%s' not available to root\n",
+                                        statname);
+                               free(namelist[i]);
+                               continue;
+                       }
+                       if (S_ISDIR(tmp_stat.st_mode)) {
+                               *cctype = "DIR";
+                       } else
+                       if (S_ISREG(tmp_stat.st_mode)) {
+                               *cctype = "FILE";
+                       } else {
+                               continue;
+                       }
+                       snprintf(buf, sizeof(buf), "%s:%s/%s", *cctype,
+                                dirname, namelist[i]->d_name);
+                       if (!query_krb5_ccache(buf, &princname, &realm)) {
+                               printerr(3, "CC '%s' is expired or corrupt\n",
+                                        buf);
+                               free(namelist[i]);
+                               err = -EKEYEXPIRED;
+                               continue;
+                       }
+
+                       score = 0;
+                       if (preferred_realm &&
+                                       strcmp(realm, preferred_realm) == 0) 
+                               score++;
+
+                       printerr(3, "CC '%s'(%s@%s) passed all checks and"
+                                   " has mtime of %u\n",
+                                buf, princname, realm, 
+                                tmp_stat.st_mtime);
                        /*
                         * if more than one match is found, return the most
                         * recent (the one with the latest mtime), and
@@ -221,90 +272,49 @@ gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
                        if (!found) {
                                best_match_dir = namelist[i];
                                best_match_stat = tmp_stat;
+                               best_match_score = score;
                                found++;
                        }
                        else {
                                /*
-                                * If the current match has an mtime later
+                                * If current score is higher than best match 
+                                * score, we use the current match. Otherwise,
+                                * if the current match has an mtime later
                                 * than the one we are looking at, then use
                                 * the current match.  Otherwise, we still
                                 * have the best match.
                                 */
-                               if (tmp_stat.st_mtime >
-                                           best_match_stat.st_mtime) {
+                               if (best_match_score < score ||
+                                   (best_match_score == score && 
+                                      tmp_stat.st_mtime >
+                                           best_match_stat.st_mtime)) {
                                        free(best_match_dir);
                                        best_match_dir = namelist[i];
                                        best_match_stat = tmp_stat;
+                                       best_match_score = score;
                                }
                                else {
                                        free(namelist[i]);
                                }
-                               printerr(3, "CC file '%s' is our "
+                               printerr(3, "CC '%s:%s/%s' is our "
                                            "current best match "
                                            "with mtime of %u\n",
+                                        cctype, dirname,
                                         best_match_dir->d_name,
                                         best_match_stat.st_mtime);
                        }
+                       free(princname);
+                       free(realm);
                }
                free(namelist);
        }
-       if (found)
-       {
+       if (found) {
                *d = best_match_dir;
+               return 0;
        }
-       return found;
-}
-
-
-#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
-/*
- * this routine obtains a credentials handle via gss_acquire_cred()
- * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
- * types negotiated.
- *
- * XXX Should call some function to determine the enctypes supported
- * by the kernel. (Only need to do that once!)
- *
- * Returns:
- *     0 => all went well
- *     -1 => there was an error
- */
-
-int
-limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
-{
-       u_int maj_stat, min_stat;
-       gss_cred_id_t credh;
-       gss_OID_set_desc  desired_mechs;
-       krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC };
-       int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
-
-       /* We only care about getting a krb5 cred */
-       desired_mechs.count = 1;
-       desired_mechs.elements = &krb5oid;
-
-       maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
-                                   &desired_mechs, GSS_C_INITIATE,
-                                   &credh, NULL, NULL);
-
-       if (maj_stat != GSS_S_COMPLETE) {
-               pgsserr("gss_acquire_cred",
-                       maj_stat, min_stat, &krb5oid);
-               return -1;
-       }
-
-       maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid,
-                                            num_enctypes, &enctypes);
-       if (maj_stat != GSS_S_COMPLETE) {
-               pgsserr("gss_set_allowable_enctypes",
-                       maj_stat, min_stat, &krb5oid);
-               return -1;
-       }
-       sec->cred = credh;
 
-       return 0;
+       return err;
 }
-#endif /* HAVE_SET_ALLOWABLE_ENCTYPES */
 
 /*
  * Obtain credentials via a key in the keytab given
@@ -319,9 +329,15 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
 static int
 gssd_get_single_krb5_cred(krb5_context context,
                          krb5_keytab kt,
-                         struct gssd_k5_kt_princ *ple)
+                         struct gssd_k5_kt_princ *ple,
+                         int nocache)
 {
+#if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
+       krb5_get_init_creds_opt *init_opts = NULL;
+#else
        krb5_get_init_creds_opt options;
+#endif
+       krb5_get_init_creds_opt *opts;
        krb5_creds my_creds;
        krb5_ccache ccache = NULL;
        char kt_name[BUFSIZ];
@@ -329,10 +345,12 @@ gssd_get_single_krb5_cred(krb5_context context,
        int code;
        time_t now = time(0);
        char *cache_type;
+       char *pname = NULL;
+       char *k5err = NULL;
 
        memset(&my_creds, 0, sizeof(my_creds));
 
-       if (ple->ccname && ple->endtime > now) {
+       if (ple->ccname && ple->endtime > now && !nocache) {
                printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
                         ple->ccname, ple->endtime);
                code = 0;
@@ -345,29 +363,44 @@ gssd_get_single_krb5_cred(krb5_context context,
                goto out;
        }
 
+       if ((krb5_unparse_name(context, ple->princ, &pname)))
+               pname = NULL;
+
+#if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
+       code = krb5_get_init_creds_opt_alloc(context, &init_opts);
+       if (code) {
+               k5err = gssd_k5_err_msg(context, code);
+               printerr(0, "ERROR: %s allocating gic options\n", k5err);
+               goto out;
+       }
+       if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1))
+               printerr(1, "WARNING: Unable to set option for addressless "
+                        "tickets.  May have problems behind a NAT.\n");
+#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(init_opts, 5*60);
+#endif
+       opts = init_opts;
+
+#else  /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS */
+
        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, NULL, &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);
-#ifdef HAVE_KRB5
-               if (pname) krb5_free_unparsed_name(context, pname);
-#else
-               if (pname) free(pname);
+       opts = &options;
 #endif
+
+       if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
+                                              kt, 0, NULL, opts))) {
+               k5err = gssd_k5_err_msg(context, code);
+               printerr(1, "WARNING: %s while getting initial ticket for "
+                        "principal '%s' using keytab '%s'\n", k5err,
+                        pname ? pname : "<unparsable>", kt_name);
                goto out;
        }
 
@@ -381,232 +414,618 @@ gssd_get_single_krb5_cred(krb5_context context,
            cache_type = "FILE";
        snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
                cache_type,
-               GSSD_DEFAULT_CRED_DIR, GSSD_DEFAULT_CRED_PREFIX,
+               ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX,
                GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
        ple->endtime = my_creds.times.endtime;
+       if (ple->ccname != NULL)
+               free(ple->ccname);
        ple->ccname = strdup(cc_name);
        if (ple->ccname == NULL) {
                printerr(0, "ERROR: no storage to duplicate credentials "
-                           "cache name\n");
+                           "cache name '%s'\n", cc_name);
                code = ENOMEM;
                goto out;
        }
        if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
+               k5err = gssd_k5_err_msg(context, code);
                printerr(0, "ERROR: %s while opening credential cache '%s'\n",
-                        error_message(code), cc_name);
+                        k5err, cc_name);
                goto out;
        }
        if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
+               k5err = gssd_k5_err_msg(context, code);
                printerr(0, "ERROR: %s while initializing credential "
-                        "cache '%s'\n", error_message(code), cc_name);
+                        "cache '%s'\n", k5err, cc_name);
                goto out;
        }
        if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
+               k5err = gssd_k5_err_msg(context, code);
                printerr(0, "ERROR: %s while storing credentials in '%s'\n",
-                        error_message(code), cc_name);
+                        k5err, cc_name);
                goto out;
        }
 
        code = 0;
-       printerr(1, "Using (machine) credentials cache: '%s'\n", cc_name);
+       printerr(2, "Successfully obtained machine credentials for "
+                "principal '%s' stored in ccache '%s'\n", pname, cc_name);
   out:
+#if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
+       if (init_opts)
+               krb5_get_init_creds_opt_free(context, init_opts);
+#endif
+       if (pname)
+               k5_free_unparsed_name(context, pname);
        if (ccache)
                krb5_cc_close(context, ccache);
        krb5_free_cred_contents(context, &my_creds);
+       free(k5err);
        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
+ * Depending on the version of Kerberos, we either need to use
+ * a private function, or simply set the environment variable.
  */
-static int
-gssd_have_realm_ple(void *r)
+static void
+gssd_set_krb5_ccache_name(char *ccname)
 {
-       struct gssd_k5_kt_princ *ple;
-#ifdef HAVE_KRB5
-       krb5_data *realm = (krb5_data *)r;
+#ifdef USE_GSS_KRB5_CCACHE_NAME
+       u_int   maj_stat, min_stat;
+
+       printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
+                ccname);
+       maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
+       if (maj_stat != GSS_S_COMPLETE) {
+               printerr(0, "WARNING: gss_krb5_ccache_name with "
+                       "name '%s' failed (%s)\n",
+                       ccname, error_message(min_stat));
+       }
 #else
-       char *realm = (char *)r;
+       /*
+        * 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.)
+        */
+       printerr(2, "using environment variable to select krb5 ccache %s\n",
+                ccname);
+       setenv("KRB5CCNAME", ccname, 1);
 #endif
+}
+
+/*
+ * Given a principal, find a matching ple structure
+ */
+static struct gssd_k5_kt_princ *
+find_ple_by_princ(krb5_context context, krb5_principal princ)
+{
+       struct gssd_k5_kt_princ *ple;
+
+       for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
+               if (krb5_principal_compare(context, ple->princ, princ))
+                       return ple;
+       }
+       /* no match found */
+       return NULL;
+}
+
+/*
+ * Create, initialize, and add a new ple structure to the global list
+ */
+static struct gssd_k5_kt_princ *
+new_ple(krb5_context context, krb5_principal princ)
+{
+       struct gssd_k5_kt_princ *ple = NULL, *p;
+       krb5_error_code code;
+       char *default_realm;
+       int is_default_realm = 0;
+
+       ple = malloc(sizeof(struct gssd_k5_kt_princ));
+       if (ple == NULL)
+               goto outerr;
+       memset(ple, 0, sizeof(*ple));
 
-       for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
 #ifdef HAVE_KRB5
-               if ((realm->length == strlen(ple->realm)) &&
-                   (strncmp(realm->data, ple->realm, realm->length) == 0)) {
+       ple->realm = strndup(princ->realm.data,
+                            princ->realm.length);
 #else
-               if (strcmp(realm, ple->realm) == 0) {
+       ple->realm = strdup(princ->realm);
 #endif
-                   return 1;
-               }
+       if (ple->realm == NULL)
+               goto outerr;
+       code = krb5_copy_principal(context, princ, &ple->princ);
+       if (code)
+               goto outerr;
+
+       /*
+        * Add new entry onto the list (if this is the default
+        * realm, always add to the front of the list)
+        */
+
+       code = krb5_get_default_realm(context, &default_realm);
+       if (code == 0) {
+               if (strcmp(ple->realm, default_realm) == 0)
+                       is_default_realm = 1;
+               k5_free_default_realm(context, default_realm);
+       }
+
+       if (is_default_realm) {
+               ple->next = gssd_k5_kt_princ_list;
+               gssd_k5_kt_princ_list = ple;
+       } else {
+               p = gssd_k5_kt_princ_list;
+               while (p != NULL && p->next != NULL)
+                       p = p->next;
+               if (p == NULL)
+                       gssd_k5_kt_princ_list = ple;
+               else
+                       p->next = ple;
+       }
+
+       return ple;
+outerr:
+       if (ple) {
+               if (ple->realm)
+                       free(ple->realm);
+               free(ple);
        }
+       return NULL;
+}
+
+/*
+ * Given a principal, find an existing ple structure, or create one
+ */
+static struct gssd_k5_kt_princ *
+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! */
+
+       ple = find_ple_by_princ(context, princ);
+       if (ple == NULL) {
+               ple = new_ple(context, princ);
+       }
+
+       return ple;
+}
+
+/*
+ * Given a (possibly unqualified) hostname,
+ * return the fully qualified (lower-case!) hostname
+ */
+static int
+get_full_hostname(const char *inhost, char *outhost, int outhostlen)
+{
+       struct addrinfo *addrs = NULL;
+       struct addrinfo hints;
+       int retval;
+       char *c;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_family = PF_UNSPEC;
+       hints.ai_flags = AI_CANONNAME;
+
+       /* Get full target hostname */
+       retval = getaddrinfo(inhost, NULL, &hints, &addrs);
+       if (retval) {
+               printerr(1, "%s while getting full hostname for '%s'\n",
+                        gai_strerror(retval), inhost);
+               goto out;
+       }
+       strncpy(outhost, addrs->ai_canonname, outhostlen);
+       freeaddrinfo(addrs);
+       for (c = outhost; *c != '\0'; c++)
+           *c = tolower(*c);
+
+       printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
+       retval = 0;
+out:
+       return retval;
+}
+
+/* 
+ * If principal matches the given realm and service name,
+ * and has *any* instance (hostname), return 1.
+ * Otherwise return 0, indicating no match.
+ */
+#ifdef HAVE_KRB5
+static int
+realm_and_service_match(krb5_principal p, const char *realm, const char *service)
+{
+       /* Must have two components */
+       if (p->length != 2)
+               return 0;
+
+       if ((strlen(realm) == p->realm.length)
+           && (strncmp(realm, p->realm.data, p->realm.length) == 0)
+           && (strlen(service) == p->data[0].length)
+           && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
+               return 1;
+
+       return 0;
+}
+#else
+static int
+realm_and_service_match(krb5_context context, krb5_principal p,
+                       const char *realm, const char *service)
+{
+       const char *name, *inst;
+
+       if (p->name.name_string.len != 2)
+               return 0;
+
+       name = krb5_principal_get_comp_string(context, p, 0);
+       inst = krb5_principal_get_comp_string(context, p, 1);
+       if (name == NULL || inst == NULL)
+               return 0;
+       if ((strcmp(realm, p->realm) == 0)
+           && (strcmp(service, name) == 0))
+               return 1;
+
        return 0;
 }
+#endif
 
 /*
- * Process the given keytab file and create a list of principals we
- * might use to perform mount operations.
+ * Search the given keytab file looking for an entry with the given
+ * service name and realm, ignoring hostname (instance).
  *
  * Returns:
- *     0 => Sucess
- *     nonzero => Error
+ *     0 => No error
+ *     non-zero => An error occurred
+ *
+ * If a keytab entry is found, "found" is set to one, and the keytab
+ * entry is returned in "kte".  Otherwise, "found" is zero, and the
+ * value of "kte" is unpredictable.
  */
 static int
-gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt, char *kt_name)
+gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
+                       const char *realm, const char *service,
+                       int *found, krb5_keytab_entry *kte)
 {
        krb5_kt_cursor cursor;
-       krb5_keytab_entry kte;
        krb5_error_code code;
        struct gssd_k5_kt_princ *ple;
-       int retval = -1;
+       int retval = -1, status;
+       char kt_name[BUFSIZ];
+       char *pname;
+       char *k5err = NULL;
+
+       if (found == NULL) {
+               retval = EINVAL;
+               goto out;
+       }
+       *found = 0;
 
        /*
         * 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 we might want to use it as machine credentials.  If so,
+        * save info in the global principal list (gssd_k5_kt_princ_list).
         */
+       if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
+               k5err = gssd_k5_err_msg(context, code);
+               printerr(0, "ERROR: %s attempting to get keytab name\n", k5err);
+               retval = code;
+               goto out;
+       }
        if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
+               k5err = gssd_k5_err_msg(context, code);
                printerr(0, "ERROR: %s while beginning keytab scan "
-                           "for keytab '%s'\n",
-                       error_message(code), kt_name);
+                           "for keytab '%s'\n", k5err, 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,
+       while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
+               if ((code = krb5_unparse_name(context, kte->principal,
                                              &pname))) {
+                       k5err = gssd_k5_err_msg(context, code);
                        printerr(0, "WARNING: Skipping keytab entry because "
-                                   "we failed to unparse principal name: %s\n",
-                                error_message(code));
+                                "we failed to unparse principal name: %s\n",
+                                k5err);
+                       k5_free_kt_entry(context, kte);
                        continue;
                }
-               printerr(2, "Processing keytab entry for principal '%s'\n",
+               printerr(4, "Processing keytab entry for principal '%s'\n",
                         pname);
+               /* Use the first matching keytab entry found */
 #ifdef HAVE_KRB5
-               if ( (kte.principal->data[0].length == GSSD_SERVICE_NAME_LEN) &&
-                    (strncmp(kte.principal->data[0].data, GSSD_SERVICE_NAME,
-                             GSSD_SERVICE_NAME_LEN) == 0) &&
+               status = realm_and_service_match(kte->principal, realm, service);
 #else
-               if ( (strlen(kte.principal->name.name_string.val[0]) == GSSD_SERVICE_NAME_LEN) &&
-                    (strncmp(kte.principal->name.name_string.val[0], GSSD_SERVICE_NAME,
-                             GSSD_SERVICE_NAME_LEN) == 0) &&
-                             
+               status = realm_and_service_match(context, kte->principal, realm, service);
 #endif
-                    (!gssd_have_realm_ple((void *)&kte.principal->realm)) ) {
-                       printerr(2, "We will use this entry (%s)\n", pname);
-                       ple = malloc(sizeof(struct gssd_k5_kt_princ));
+               if (status) {
+                       printerr(4, "We WILL use this entry (%s)\n", pname);
+                       ple = get_ple_by_princ(context, kte->principal);
+                       /*
+                        * Return, don't free, keytab entry if
+                        * we were successful!
+                        */
                        if (ple == NULL) {
-                               printerr(0, "ERROR: could not allocate storage "
-                                           "for principal list entry\n");
-#ifdef HAVE_KRB5
-                               krb5_free_unparsed_name(context, pname);
-#else
-                               free(pname);
-#endif
                                retval = ENOMEM;
-                               goto out;
-                       }
-                       /* These will be filled in later */
-                       ple->next = NULL;
-                       ple->ccname = NULL;
-                       ple->endtime = 0;
-                       if ((ple->realm =
-#ifdef HAVE_KRB5
-                               strndup(kte.principal->realm.data,
-                                       kte.principal->realm.length))
-#else
-                               strdup(kte.principal->realm))
-#endif
-                                       == NULL) {
-                               printerr(0, "ERROR: %s while copying realm to "
-                                           "principal list entry\n",
-                                        "not enough memory");
-#ifdef HAVE_KRB5
-                               krb5_free_unparsed_name(context, pname);
-#else
-                               free(pname);
-#endif
-                               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));
-#ifdef HAVE_KRB5
-                               krb5_free_unparsed_name(context, pname);
-#else
-                               free(pname);
-#endif
-                               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;
+                               k5_free_kt_entry(context, kte);
+                       } else {
+                               retval = 0;
+                               *found = 1;
                        }
+                       k5_free_unparsed_name(context, pname);
+                       break;
                }
                else {
-                       printerr(2, "We will NOT use this entry (%s)\n",
+                       printerr(4, "We will NOT use this entry (%s)\n",
                                pname);
                }
-#ifdef HAVE_KRB5
-               krb5_free_unparsed_name(context, pname);
-#else
-               free(pname);
-#endif
+               k5_free_unparsed_name(context, pname);
+               k5_free_kt_entry(context, kte);
        }
 
        if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
+               k5err = gssd_k5_err_msg(context, code);
                printerr(0, "WARNING: %s while ending keytab scan for "
-                           "keytab '%s'\n",
-                        error_message(code), kt_name);
+                           "keytab '%s'\n", k5err, kt_name);
        }
 
        retval = 0;
   out:
+       free(k5err);
        return retval;
 }
 
 /*
- * Depending on the version of Kerberos, we either need to use
- * a private function, or simply set the environment variable.
+ * Find a keytab entry to use for a given target realm.
+ * Tries to find the most appropriate keytab to use given the
+ * name of the host we are trying to connect with.
+ *
+ * Note: the tgtname contains a hostname in the realm that we
+ * are authenticating to. It may, or may not be the same as
+ * the server hostname.
  */
-static void
-gssd_set_krb5_ccache_name(char *ccname)
+static int
+find_keytab_entry(krb5_context context, krb5_keytab kt, const char *tgtname,
+                 krb5_keytab_entry *kte, const char **svcnames)
 {
-#ifdef USE_GSS_KRB5_CCACHE_NAME
-       u_int   maj_stat, min_stat;
+       krb5_error_code code;
+       char **realmnames = NULL;
+       char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
+       char myhostad[NI_MAXHOST+1];
+       int i, j, retval;
+       char *default_realm = NULL;
+       char *realm;
+       char *k5err = NULL;
+       int tried_all = 0, tried_default = 0;
+       krb5_principal princ;
+
+
+       /* Get full target hostname */
+       retval = get_full_hostname(tgtname, targethostname,
+                                  sizeof(targethostname));
+       if (retval)
+               goto out;
 
-       printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
-                ccname);
-       maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
-       if (maj_stat != GSS_S_COMPLETE) {
-               printerr(0, "WARNING: gss_krb5_ccache_name with "
-                       "name '%s' failed (%s)\n",
-                       ccname, error_message(min_stat));
+       /* Get full local hostname */
+       retval = gethostname(myhostname, sizeof(myhostname));
+       if (retval) {
+               k5err = gssd_k5_err_msg(context, retval);
+               printerr(1, "%s while getting local hostname\n", k5err);
+               goto out;
        }
-#else
+
+       /* Compute the active directory machine name HOST$ */
+       strcpy(myhostad, myhostname);
+       for (i = 0; myhostad[i] != 0; ++i)
+               myhostad[i] = toupper(myhostad[i]);
+       myhostad[i] = '$';
+       myhostad[i+1] = 0;
+
+       retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
+       if (retval)
+               goto out;
+
+       code = krb5_get_default_realm(context, &default_realm);
+       if (code) {
+               retval = code;
+               k5err = gssd_k5_err_msg(context, code);
+               printerr(1, "%s while getting default realm name\n", k5err);
+               goto out;
+       }
+
        /*
-        * 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.)
+        * Get the realm name(s) for the target hostname.
+        * In reality, this function currently only returns a
+        * single realm, but we code with the assumption that
+        * someday it may actually return a list.
         */
-       printerr(2, "using environment variable to select krb5 ccache %s\n",
-                ccname);
-       setenv("KRB5CCNAME", ccname, 1);
-#endif
+       code = krb5_get_host_realm(context, targethostname, &realmnames);
+       if (code) {
+               k5err = gssd_k5_err_msg(context, code);
+               printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
+                        k5err, targethostname);
+               retval = code;
+               goto out;
+       }
+
+       /*
+        * Try the "appropriate" realm first, and if nothing found for that
+        * realm, try the default realm (if it hasn't already been tried).
+        */
+       i = 0;
+       realm = realmnames[i];
+       while (1) {
+               if (realm == NULL) {
+                       tried_all = 1;
+                       if (!tried_default)
+                               realm = default_realm;
+               }
+               if (tried_all && tried_default)
+                       break;
+               if (strcmp(realm, default_realm) == 0)
+                       tried_default = 1;
+               for (j = 0; svcnames[j] != NULL; j++) {
+                       char spn[300];
+
+                       /*
+                        * The special svcname "$" means 'try the active
+                        * directory machine account'
+                        */
+                       if (strcmp(svcnames[j],"$") == 0) {
+                               snprintf(spn, sizeof(spn), "%s@%s", myhostad, realm);
+                               code = krb5_build_principal_ext(context, &princ,
+                                                               strlen(realm),
+                                                               realm,
+                                                               strlen(myhostad),
+                                                               myhostad,
+                                                               NULL);
+                       } else {
+                               snprintf(spn, sizeof(spn), "%s/%s@%s",
+                                        svcnames[j], myhostname, realm);
+                               code = krb5_build_principal_ext(context, &princ,
+                                                               strlen(realm),
+                                                               realm,
+                                                               strlen(svcnames[j]),
+                                                               svcnames[j],
+                                                               strlen(myhostname),
+                                                               myhostname,
+                                                               NULL);
+                       }
+
+                       if (code) {
+                               k5err = gssd_k5_err_msg(context, code);
+                               printerr(1, "%s while building principal for '%s'\n",
+                                        k5err, spn);
+                               continue;
+                       }
+                       code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
+                       krb5_free_principal(context, princ);
+                       if (code) {
+                               k5err = gssd_k5_err_msg(context, code);
+                               printerr(3, "%s while getting keytab entry for '%s'\n",
+                                        k5err, spn);
+                       } else {
+                               printerr(3, "Success getting keytab entry for '%s'\n",spn);
+                               retval = 0;
+                               goto out;
+                       }
+                       retval = code;
+               }
+               /*
+                * Nothing found with our hostname instance, now look for
+                * names with any instance (they must have an instance)
+                */
+               for (j = 0; svcnames[j] != NULL; j++) {
+                       int found = 0;
+                       if (strcmp(svcnames[j],"$") == 0)
+                               continue;
+                       code = gssd_search_krb5_keytab(context, kt, realm,
+                                                      svcnames[j], &found, kte);
+                       if (!code && found) {
+                               printerr(3, "Success getting keytab entry for "
+                                        "%s/*@%s\n", svcnames[j], realm);
+                               retval = 0;
+                               goto out;
+                       }
+               }
+               if (!tried_all) {
+                       i++;
+                       realm = realmnames[i];
+               }
+       }
+out:
+       if (default_realm)
+               k5_free_default_realm(context, default_realm);
+       if (realmnames)
+               krb5_free_host_realm(context, realmnames);
+       free(k5err);
+       return retval;
+}
+
+
+static inline int data_is_equal(krb5_data d1, krb5_data d2)
+{
+       return (d1.length == d2.length
+               && memcmp(d1.data, d2.data, d1.length) == 0);
+}
+
+static int
+check_for_tgt(krb5_context context, krb5_ccache ccache,
+             krb5_principal principal)
+{
+       krb5_error_code ret;
+       krb5_creds creds;
+       krb5_cc_cursor cur;
+       int found = 0;
+
+       ret = krb5_cc_start_seq_get(context, ccache, &cur);
+       if (ret) 
+               return 0;
+
+       while (!found &&
+               (ret = krb5_cc_next_cred(context, ccache, &cur, &creds)) == 0) {
+               if (creds.server->length == 2 &&
+                               data_is_equal(creds.server->realm,
+                                             principal->realm) &&
+                               creds.server->data[0].length == 6 &&
+                               memcmp(creds.server->data[0].data,
+                                               "krbtgt", 6) == 0 &&
+                               data_is_equal(creds.server->data[1],
+                                             principal->realm) &&
+                               creds.times.endtime > time(NULL))
+                       found = 1;
+               krb5_free_cred_contents(context, &creds);
+       }
+       krb5_cc_end_seq_get(context, ccache, &cur);
+
+       return found;
+}
+
+static int
+query_krb5_ccache(const char* cred_cache, char **ret_princname,
+                 char **ret_realm)
+{
+       krb5_error_code ret;
+       krb5_context context;
+       krb5_ccache ccache;
+       krb5_principal principal;
+       int found = 0;
+       char *str = NULL;
+       char *princstring;
+
+       ret = krb5_init_context(&context);
+       if (ret) 
+               return 0;
+
+       if(!cred_cache || krb5_cc_resolve(context, cred_cache, &ccache))
+               goto err_cache;
+
+       if (krb5_cc_set_flags(context, ccache, 0))
+               goto err_princ;
+
+       ret = krb5_cc_get_principal(context, ccache, &principal);
+       if (ret) 
+               goto err_princ;
+
+       found = check_for_tgt(context, ccache, principal);
+       if (found) {
+               ret = krb5_unparse_name(context, principal, &princstring);
+               if (ret == 0) {
+                   if ((str = strchr(princstring, '@')) != NULL) {
+                           *str = '\0';
+                           *ret_princname = strdup(princstring);
+                           *ret_realm = strdup(str+1);
+                   }
+                   k5_free_unparsed_name(context, princstring);
+               } else {
+                       found = 0;
+               }
+       }
+       krb5_free_principal(context, principal);
+err_princ:
+       krb5_cc_set_flags(context, ccache,  KRB5_TC_OPENCLOSE);
+       krb5_cc_close(context, ccache);
+err_cache:
+       krb5_free_context(context);
+       return found;
 }
 
 /*==========================*/
@@ -618,29 +1037,52 @@ gssd_set_krb5_ccache_name(char *ccname)
  * given only a UID.  We really need more information, but we
  * do the best we can.
  *
- * Returns:
- *     void
+ * Returns 0 if a ccache was found, and a non-zero error code otherwise.
  */
-void
-gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername)
+int
+gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirpattern)
 {
-       char                    buf[MAX_NETOBJ_SZ];
+       char                    buf[MAX_NETOBJ_SZ], dirname[PATH_MAX];
+       const char              *cctype;
        struct dirent           *d;
+       int                     err, i, j;
 
        printerr(2, "getting credentials for client with uid %u for "
                    "server %s\n", uid, servername);
-       memset(buf, 0, sizeof(buf));
-       if (gssd_find_existing_krb5_ccache(uid, &d)) {
-               snprintf(buf, sizeof(buf), "FILE:%s/%s",
-                       ccachedir, d->d_name);
-               free(d);
+
+       for (i = 0, j = 0; dirpattern[i] != '\0'; i++) {
+               switch (dirpattern[i]) {
+               case '%':
+                       switch (dirpattern[i + 1]) {
+                       case '%':
+                               dirname[j++] = dirpattern[i];
+                               i++;
+                               break;
+                       case 'U':
+                               j += sprintf(dirname + j, "%lu",
+                                            (unsigned long) uid);
+                               i++;
+                               break;
+                       }
+                       break;
+               default:
+                       dirname[j++] = dirpattern[i];
+                       break;
+               }
        }
-       else
-               snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
-                       ccachedir, GSSD_DEFAULT_CRED_PREFIX, uid);
+       dirname[j] = '\0';
+
+       err = gssd_find_existing_krb5_ccache(uid, dirname, &cctype, &d);
+       if (err)
+               return err;
+
+       snprintf(buf, sizeof(buf), "%s:%s/%s", cctype, dirname, d->d_name);
+       free(d);
+
        printerr(2, "using %s as credentials cache for client with "
                    "uid %u for server %s\n", buf, uid, servername);
        gssd_set_krb5_ccache_name(buf);
+       return err;
 }
 
 /*
@@ -657,96 +1099,6 @@ gssd_setup_krb5_machine_gss_ccache(char *ccname)
        gssd_set_krb5_ccache_name(ccname);
 }
 
-/*
- * 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.
@@ -769,18 +1121,20 @@ gssd_get_krb5_machine_cred_list(char ***list)
        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;
        }
 
+       /* Need to serialize list if we ever become multi-threaded! */
+
        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 **)
@@ -832,11 +1186,12 @@ gssd_destroy_krb5_machine_creds(void)
        krb5_error_code code = 0;
        krb5_ccache ccache;
        struct gssd_k5_kt_princ *ple;
+       char *k5err = NULL;
 
        code = krb5_init_context(&context);
        if (code) {
-               printerr(0, "ERROR: %s while initializing krb5\n",
-                        error_message(code));
+               k5err = gssd_k5_err_msg(NULL, code);
+               printerr(0, "ERROR: %s while initializing krb5\n", k5err);
                goto out;
        }
 
@@ -844,19 +1199,213 @@ gssd_destroy_krb5_machine_creds(void)
                if (!ple->ccname)
                        continue;
                if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
+                       k5err = gssd_k5_err_msg(context, code);
                        printerr(0, "WARNING: %s while resolving credential "
-                                   "cache '%s' for destruction\n",
-                                error_message(code), ple->ccname);
+                                   "cache '%s' for destruction\n", k5err,
+                                   ple->ccname);
                        continue;
                }
 
                if ((code = krb5_cc_destroy(context, ccache))) {
+                       k5err = gssd_k5_err_msg(context, code);
                        printerr(0, "WARNING: %s while destroying credential "
-                                   "cache '%s'\n",
-                                error_message(code), ple->ccname);
+                                   "cache '%s'\n", k5err, ple->ccname);
                }
        }
+       krb5_free_context(context);
   out:
+       free(k5err);
+}
+
+/*
+ * Obtain (or refresh if necessary) Kerberos machine credentials
+ */
+int
+gssd_refresh_krb5_machine_credential(char *hostname,
+                                    struct gssd_k5_kt_princ *ple, 
+                                        char *service,
+                                        char *tgtname)
+{
+       krb5_error_code code = 0;
+       krb5_context context;
+       krb5_keytab kt = NULL;;
+       int retval = 0;
+       char *k5err = NULL;
+       const char *svcnames[5] = { "$", "root", "nfs", "host", NULL };
+
+       /*
+        * If a specific service name was specified, use it.
+        * Otherwise, use the default list.
+        */
+       if (service != NULL && strcmp(service, "*") != 0) {
+               svcnames[0] = service;
+               svcnames[1] = NULL;
+       }
+       if (hostname == NULL && ple == NULL)
+               return EINVAL;
+
+       code = krb5_init_context(&context);
+       if (code) {
+               k5err = gssd_k5_err_msg(NULL, code);
+               printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
+                        __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;
+
+               if (tgtname == NULL)
+                       tgtname = hostname;
+
+               code = find_keytab_entry(context, kt, tgtname, &kte, svcnames);
+               if (code) {
+                       printerr(0, "ERROR: %s: no usable keytab entry found "
+                                "in keytab %s for connection with host %s\n",
+                                __FUNCTION__, keytabfile, hostname);
+                       retval = code;
+                       goto out_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 : "<unparsable>",
+                                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:
+       free(k5err);
+       return retval;
+}
+
+/*
+ * A common routine for getting the Kerberos error message
+ */
+char *
+gssd_k5_err_msg(krb5_context context, krb5_error_code code)
+{
+       const char *origmsg;
+       char *msg = NULL;
+
+#if HAVE_KRB5_GET_ERROR_MESSAGE
+       if (context != NULL) {
+               origmsg = krb5_get_error_message(context, code);
+               msg = strdup(origmsg);
+               krb5_free_error_message(context, origmsg);
+       }
+#endif
+       if (msg != NULL)
+               return msg;
+#if HAVE_KRB5
+       return strdup(error_message(code));
+#else
+       if (context != NULL)
+               return strdup(krb5_get_err_text(context, code));
+       else
+               return strdup(error_message(code));
+#endif
+}
+
+/*
+ * Return default Kerberos realm
+ */
+void
+gssd_k5_get_default_realm(char **def_realm)
+{
+       krb5_context context;
+
+       if (krb5_init_context(&context))
+               return;
+
+       krb5_get_default_realm(context, def_realm);
+
        krb5_free_context(context);
 }
 
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+/*
+ * this routine obtains a credentials handle via gss_acquire_cred()
+ * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
+ * types negotiated.
+ *
+ * XXX Should call some function to determine the enctypes supported
+ * by the kernel. (Only need to do that once!)
+ *
+ * Returns:
+ *     0 => all went well
+ *     -1 => there was an error
+ */
+
+int
+limit_krb5_enctypes(struct rpc_gss_sec *sec)
+{
+       u_int maj_stat, min_stat;
+       gss_cred_id_t credh;
+       gss_OID_set_desc  desired_mechs;
+       krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
+                                   ENCTYPE_DES_CBC_MD5,
+                                   ENCTYPE_DES_CBC_MD4 };
+       int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
+       extern int num_krb5_enctypes;
+       extern krb5_enctype *krb5_enctypes;
+
+       /* We only care about getting a krb5 cred */
+       desired_mechs.count = 1;
+       desired_mechs.elements = &krb5oid;
+
+       maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
+                                   &desired_mechs, GSS_C_INITIATE,
+                                   &credh, NULL, NULL);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               if (get_verbosity() > 0)
+                       pgsserr("gss_acquire_cred",
+                               maj_stat, min_stat, &krb5oid);
+               return -1;
+       }
+
+       /*
+        * If we failed for any reason to produce global
+        * list of supported enctypes, use local default here.
+        */
+       if (krb5_enctypes == NULL || limit_to_legacy_enctypes)
+               maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
+                                       &krb5oid, num_enctypes, enctypes);
+       else
+               maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
+                                       &krb5oid, num_krb5_enctypes, krb5_enctypes);
+
+       if (maj_stat != GSS_S_COMPLETE) {
+               pgsserr("gss_set_allowable_enctypes",
+                       maj_stat, min_stat, &krb5oid);
+               gss_release_cred(&min_stat, &credh);
+               return -1;
+       }
+       sec->cred = credh;
+
+       return 0;
+}
+#endif /* HAVE_SET_ALLOWABLE_ENCTYPES */