]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/gssd/krb5_util.c
gssd: on krb5 upcall, have gssd send a more granular error code
[nfs-utils.git] / utils / gssd / krb5_util.c
index 0589cd87e8c3237a76c7ca14fcafc4e3e1b78fa9..1295f5776eb78bc6e6154d8e80ca111fbbe8c3a2 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 "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 */
@@ -131,10 +134,12 @@ struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL;
 /*==========================*/
 
 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,
+               struct dirent **d);
 static int gssd_get_single_krb5_cred(krb5_context context,
-               krb5_keytab kt, struct gssd_k5_kt_princ *ple);
-
+               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
@@ -159,18 +164,17 @@ 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, struct dirent **d)
 {
        struct dirent **namelist;
        int n;
@@ -178,20 +182,29 @@ 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));
        *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 file '%s' being considered, "
+                                "with preferred realm '%s'\n",
+                                statname, preferred_realm ?
+                                       preferred_realm : "<none selected>");
+                       snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, 
+                                       namelist[i]->d_name);
                        if (lstat(statname, &tmp_stat)) {
                                printerr(0, "Error doing stat on file '%s'\n",
                                         statname);
@@ -200,20 +213,34 @@ gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
                        }
                        /* Only pick caches owned by the user (uid) */
                        if (tmp_stat.st_uid != uid) {
-                               printerr(3, "'%s' owned by %u, not %u\n",
+                               printerr(3, "CC file '%s' owned by %u, not %u\n",
                                         statname, tmp_stat.st_uid, uid);
                                free(namelist[i]);
                                continue;
                        }
                        if (!S_ISREG(tmp_stat.st_mode)) {
-                               printerr(3, "'%s' is not a regular file\n",
+                               printerr(3, "CC file '%s' is not a regular file\n",
+                                        statname);
+                               free(namelist[i]);
+                               continue;
+                       }
+                       if (!query_krb5_ccache(buf, &princname, &realm)) {
+                               printerr(3, "CC file '%s' is expired or corrupt\n",
                                         statname);
                                free(namelist[i]);
+                               err = -EKEYEXPIRED;
                                continue;
                        }
-                       printerr(3, "CC file '%s' matches owner check and has "
-                                "mtime of %u\n",
-                                namelist[i]->d_name, tmp_stat.st_mtime);
+
+                       score = 0;
+                       if (preferred_realm &&
+                                       strcmp(realm, preferred_realm) == 0) 
+                               score++;
+
+                       printerr(3, "CC file '%s'(%s@%s) passed all checks and"
+                                   " has mtime of %u\n",
+                                statname, princname, realm, 
+                                tmp_stat.st_mtime);
                        /*
                         * if more than one match is found, return the most
                         * recent (the one with the latest mtime), and
@@ -222,38 +249,47 @@ 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 file '%s/%s' is our "
                                            "current best match "
                                            "with mtime of %u\n",
-                                        best_match_dir->d_name,
+                                        dirname, best_match_dir->d_name,
                                         best_match_stat.st_mtime);
                        }
+                       free(princname);
+                       free(realm);
                }
                free(namelist);
        }
-       if (found)
-       {
+       if (found) {
                *d = best_match_dir;
+               return 0;
        }
-       return found;
+
+       return err;
 }
 
 
@@ -291,8 +327,9 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
                                    &credh, NULL, NULL);
 
        if (maj_stat != GSS_S_COMPLETE) {
-               pgsserr("gss_acquire_cred",
-                       maj_stat, min_stat, &krb5oid);
+               if (get_verbosity() > 0)
+                       pgsserr("gss_acquire_cred",
+                               maj_stat, min_stat, &krb5oid);
                return -1;
        }
 
@@ -323,7 +360,8 @@ 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;
@@ -339,10 +377,11 @@ gssd_get_single_krb5_cred(krb5_context context,
        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;
@@ -361,12 +400,12 @@ gssd_get_single_krb5_cred(krb5_context context,
 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
        code = krb5_get_init_creds_opt_alloc(context, &init_opts);
        if (code) {
-               printerr(0, "ERROR: %s allocating gic options\n",
-                        gssd_k5_err_msg(context, 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(0, "WARNING: Unable to set option for addressless "
+               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!) */
@@ -389,9 +428,9 @@ gssd_get_single_krb5_cred(krb5_context context,
 
        if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
                                               kt, 0, NULL, opts))) {
-               printerr(0, "WARNING: %s while getting initial ticket for "
-                        "principal '%s' using keytab '%s'\n",
-                        gssd_k5_err_msg(context, code),
+               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;
        }
@@ -406,7 +445,7 @@ gssd_get_single_krb5_cred(krb5_context context,
            cache_type = "FILE";
        snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
                cache_type,
-               ccachedir, 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)
@@ -419,19 +458,21 @@ gssd_get_single_krb5_cred(krb5_context context,
                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",
-                        gssd_k5_err_msg(context, 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", gssd_k5_err_msg(context, 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",
-                        gssd_k5_err_msg(context, code), cc_name);
+                        k5err, cc_name);
                goto out;
        }
 
@@ -448,6 +489,7 @@ gssd_get_single_krb5_cred(krb5_context context,
        if (ccache)
                krb5_cc_close(context, ccache);
        krb5_free_cred_contents(context, &my_creds);
+       free(k5err);
        return (code);
 }
 
@@ -599,7 +641,7 @@ get_full_hostname(const char *inhost, char *outhost, int outhostlen)
        /* Get full target hostname */
        retval = getaddrinfo(inhost, NULL, &hints, &addrs);
        if (retval) {
-               printerr(0, "%s while getting full hostname for '%s'\n",
+               printerr(1, "%s while getting full hostname for '%s'\n",
                         gai_strerror(retval), inhost);
                goto out;
        }
@@ -671,6 +713,7 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
        int retval = -1;
        char kt_name[BUFSIZ];
        char *pname;
+       char *k5err = NULL;
 
        if (found == NULL) {
                retval = EINVAL;
@@ -684,15 +727,15 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
         * save info in the global principal list (gssd_k5_kt_princ_list).
         */
        if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
-               printerr(0, "ERROR: %s attempting to get keytab name\n",
-                        gssd_k5_err_msg(context, code));
+               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",
-                       gssd_k5_err_msg(context, code), kt_name);
+                           "for keytab '%s'\n", k5err, kt_name);
                retval = code;
                goto out;
        }
@@ -700,9 +743,10 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
        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",
-                                gssd_k5_err_msg(context, code));
+                                k5err);
                        k5_free_kt_entry(context, kte);
                        continue;
                }
@@ -736,13 +780,14 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
        }
 
        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",
-                        gssd_k5_err_msg(context, code), kt_name);
+                           "keytab '%s'\n", k5err, kt_name);
        }
 
        retval = 0;
   out:
+       free(k5err);
        return retval;
 }
 
@@ -753,15 +798,15 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
  */
 static int
 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
-                 krb5_keytab_entry *kte)
+                 krb5_keytab_entry *kte, const char **svcnames)
 {
        krb5_error_code code;
-       const char *svcnames[] = { "root", "nfs", "host", NULL };
        char **realmnames = NULL;
        char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
        int i, j, retval;
        char *default_realm = NULL;
        char *realm;
+       char *k5err = NULL;
        int tried_all = 0, tried_default = 0;
        krb5_principal princ;
 
@@ -775,8 +820,8 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
        /* Get full local hostname */
        retval = gethostname(myhostname, sizeof(myhostname));
        if (retval) {
-               printerr(1, "%s while getting local hostname\n",
-                        gssd_k5_err_msg(context, retval));
+               k5err = gssd_k5_err_msg(context, retval);
+               printerr(1, "%s while getting local hostname\n", k5err);
                goto out;
        }
        retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
@@ -786,8 +831,8 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
        code = krb5_get_default_realm(context, &default_realm);
        if (code) {
                retval = code;
-               printerr(1, "%s while getting default realm name\n",
-                        gssd_k5_err_msg(context, code));
+               k5err = gssd_k5_err_msg(context, code);
+               printerr(1, "%s while getting default realm name\n", k5err);
                goto out;
        }
 
@@ -799,8 +844,9 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
         */
        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",
-                        gssd_k5_err_msg(context, code), targethostname);
+                        k5err, targethostname);
                retval = code;
                goto out;
        }
@@ -831,19 +877,19 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
                                                        myhostname,
                                                        NULL);
                        if (code) {
+                               k5err = gssd_k5_err_msg(context, code);
                                printerr(1, "%s while building principal for "
-                                        "'%s/%s@%s'\n",
-                                        gssd_k5_err_msg(context, code),
-                                        svcnames[j], myhostname, realm);
+                                        "'%s/%s@%s'\n", k5err, svcnames[j],
+                                        myhostname, realm);
                                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/%s@%s'\n",
-                                        gssd_k5_err_msg(context, code),
-                                        svcnames[j], myhostname, realm);
+                                        "'%s/%s@%s'\n", k5err, svcnames[j],
+                                        myhostname, realm);
                        } else {
                                printerr(3, "Success getting keytab entry for "
                                         "'%s/%s@%s'\n",
@@ -878,9 +924,98 @@ out:
                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;
+}
+
 /*==========================*/
 /*===  External routines ===*/
 /*==========================*/
@@ -890,29 +1025,29 @@ out:
  * 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 *dirname)
 {
        char                    buf[MAX_NETOBJ_SZ];
        struct dirent           *d;
+       int                     err;
 
        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);
-       }
-       else
-               snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
-                       ccachedir, GSSD_DEFAULT_CRED_PREFIX, uid);
+       err = gssd_find_existing_krb5_ccache(uid, dirname, &d);
+       if (err)
+               return err;
+
+       snprintf(buf, sizeof(buf), "FILE:%s/%s", 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;
 }
 
 /*
@@ -961,7 +1096,8 @@ gssd_get_krb5_machine_cred_list(char ***list)
        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);
+                       retval = gssd_refresh_krb5_machine_credential(NULL, ple,
+                               NULL);
                        if (retval)
                                continue;
                        if (i + 1 > listsize) {
@@ -1015,11 +1151,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",
-                        gssd_k5_err_msg(NULL, code));
+               k5err = gssd_k5_err_msg(NULL, code);
+               printerr(0, "ERROR: %s while initializing krb5\n", k5err);
                goto out;
        }
 
@@ -1027,19 +1164,21 @@ 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",
-                                gssd_k5_err_msg(context, 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",
-                                gssd_k5_err_msg(context, code), ple->ccname);
+                                   "cache '%s'\n", k5err, ple->ccname);
                }
        }
   out:
+       free(k5err);
        krb5_free_context(context);
 }
 
@@ -1048,35 +1187,47 @@ gssd_destroy_krb5_machine_creds(void)
  */
 int
 gssd_refresh_krb5_machine_credential(char *hostname,
-                                    struct gssd_k5_kt_princ *ple)
+                                    struct gssd_k5_kt_princ *ple, 
+                                        char *service)
 {
        krb5_error_code code = 0;
        krb5_context context;
        krb5_keytab kt = NULL;;
        int retval = 0;
+       char *k5err = NULL;
+       const char *svcnames[4] = { "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",
-                        __FUNCTION__, gssd_k5_err_msg(NULL, code));
+                        __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",
-                        __FUNCTION__, gssd_k5_err_msg(context, code),
-                        keytabfile);
+                        __func__, k5err, keytabfile);
                goto out;
        }
 
        if (ple == NULL) {
                krb5_keytab_entry kte;
 
-               code = find_keytab_entry(context, kt, hostname, &kte);
+               code = find_keytab_entry(context, kt, hostname, &kte, svcnames);
                if (code) {
                        printerr(0, "ERROR: %s: no usable keytab entry found "
                                 "in keytab %s for connection with host %s\n",
@@ -1101,33 +1252,55 @@ gssd_refresh_krb5_machine_credential(char *hostname,
                        goto out;
                }
        }
-       retval = gssd_get_single_krb5_cred(context, kt, ple);
+       retval = gssd_get_single_krb5_cred(context, kt, ple, 0);
 out:
        if (kt)
                krb5_kt_close(context, kt);
        krb5_free_context(context);
+       free(k5err);
        return retval;
 }
 
 /*
  * A common routine for getting the Kerberos error message
  */
-const char *
+char *
 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
 {
-       const char *msg = NULL;
+       const char *origmsg;
+       char *msg = NULL;
+
 #if HAVE_KRB5_GET_ERROR_MESSAGE
-       if (context != NULL)
-               msg = krb5_get_error_message(context, code);
+       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 error_message(code);
+       return strdup(error_message(code));
 #else
        if (context != NULL)
-               return krb5_get_err_text(context, code);
+               return strdup(krb5_get_err_text(context, code));
        else
-               return error_message(code);
+               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);
+}