From 17c16a33968c75894fca1751be78098ef68a6f3d Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Mon, 27 Sep 2010 03:39:22 +0000
Subject: [PATCH 117/120] Add gss_krb5_import_cred

Add gss_krb5_import_cred from Heimdal; allows krb5 creds to be
acquired from a keytab or ccache into a GSSAPI credential without
using global process or thread variables.

Merged from the users/lhoward/import-cred branch.

ticket: 6785

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24356 dc483132-0cff-0310-8789-dd5450dbe970
---
 src/lib/gssapi/generic/gssapi_ext.h          |   5 +-
 src/lib/gssapi/krb5/acquire_cred.c           | 480 ++++++++++++++++-----------
 src/lib/gssapi/krb5/copy_ccache.c            |   5 +-
 src/lib/gssapi/krb5/gssapiP_krb5.h           |  33 +-
 src/lib/gssapi/krb5/gssapi_krb5.c            |  24 +-
 src/lib/gssapi/krb5/gssapi_krb5.hin          |   7 +
 src/lib/gssapi/krb5/krb5_gss_glue.c          |  58 +++-
 src/lib/gssapi/krb5/set_allowable_enctypes.c |  13 +-
 src/lib/gssapi/libgssapi_krb5.exports        |   4 +-
 src/lib/gssapi/mechglue/g_set_cred_option.c  | 194 +++++++++--
 src/lib/gssapi/mechglue/mglueP.h             |   2 +-
 src/lib/gssapi/spnego/gssapiP_spnego.h       |   2 +-
 src/lib/gssapi/spnego/spnego_mech.c          |  24 +-
 src/lib/krb5/libkrb5.exports                 |   1 +
 src/tests/gssapi/t_gssexts.c                 | 104 ++++--
 15 files changed, 660 insertions(+), 296 deletions(-)

diff --git a/src/lib/gssapi/generic/gssapi_ext.h b/src/lib/gssapi/generic/gssapi_ext.h
index 8e5c7c9..d68b6cd 100644
--- a/src/lib/gssapi/generic/gssapi_ext.h
+++ b/src/lib/gssapi/generic/gssapi_ext.h
@@ -115,10 +115,9 @@ OM_uint32 KRB5_CALLCONV gss_set_sec_context_option
 	 const gss_OID /*desired_object*/,
 	 const gss_buffer_t /*value*/);
 
-/* XXX do these really belong in this header? */
-OM_uint32 KRB5_CALLCONV gssspi_set_cred_option
+OM_uint32 KRB5_CALLCONV gss_set_cred_option
 	(OM_uint32 * /*minor_status*/,
-	 gss_cred_id_t /*cred*/,
+	 gss_cred_id_t * /*cred*/,
 	 const gss_OID /*desired_object*/,
 	 const gss_buffer_t /*value*/);
 
diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c
index 91e3a7d..1095362 100644
--- a/src/lib/gssapi/krb5/acquire_cred.c
+++ b/src/lib/gssapi/krb5/acquire_cred.c
@@ -1,6 +1,6 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
- * Copyright 2000, 2007, 2008 by the Massachusetts Institute of Technology.
+ * Copyright 2000, 2007-2010 by the Massachusetts Institute of Technology.
  * All Rights Reserved.
  *
  * Export of this software from the United States of America may
@@ -131,56 +131,55 @@ gss_krb5int_register_acceptor_identity(OM_uint32 *minor_status,
 }
 
 /* get credentials corresponding to a key in the krb5 keytab.
-   If the default name is requested, return the name in output_name.
-   If output_name is non-NULL, the caller will use or free it, regardless
-   of the return value.
    If successful, set the keytab-specific fields in cred
 */
 
 static OM_uint32
 acquire_accept_cred(krb5_context context,
                     OM_uint32 *minor_status,
-                    krb5_gss_name_t desired_name,
-                    krb5_gss_name_t *output_name,
+                    krb5_principal desired_princ,
+                    krb5_keytab req_keytab,
                     krb5_gss_cred_id_rec *cred)
 {
     krb5_error_code code;
-    krb5_principal princ;
     krb5_keytab kt;
     krb5_keytab_entry entry;
 
-    *output_name = NULL;
-    cred->keytab = NULL;
+    assert(cred->keytab == NULL);
 
-    /* open the default keytab */
+    if (req_keytab != NULL) {
+        char ktname[BUFSIZ];
 
-    code = gss_krb5int_initialize_library();
-    if (code != 0) {
-        *minor_status = code;
-        return GSS_S_FAILURE;
-    }
-    code = k5_mutex_lock(&gssint_krb5_keytab_lock);
-    if (code) {
-        *minor_status = code;
-        return GSS_S_FAILURE;
-    }
-    if (krb5_gss_keytab != NULL) {
-        code = krb5_kt_resolve(context, krb5_gss_keytab, &kt);
-        k5_mutex_unlock(&gssint_krb5_keytab_lock);
+        /* Duplicate keytab handle */
+        code = krb5_kt_get_name(context, req_keytab, ktname, sizeof(ktname));
+        if (code) {
+            *minor_status = code;
+            return GSS_S_CRED_UNAVAIL;
+        }
+        code = krb5_kt_resolve(context, ktname, &kt);
     } else {
-        k5_mutex_unlock(&gssint_krb5_keytab_lock);
-        code = krb5_kt_default(context, &kt);
+        code = k5_mutex_lock(&gssint_krb5_keytab_lock);
+        if (code) {
+            *minor_status = code;
+            return GSS_S_FAILURE;
+        }
+        if (krb5_gss_keytab != NULL) {
+            code = krb5_kt_resolve(context, krb5_gss_keytab, &kt);
+            k5_mutex_unlock(&gssint_krb5_keytab_lock);
+        } else {
+            k5_mutex_unlock(&gssint_krb5_keytab_lock);
+            code = krb5_kt_default(context, &kt);
+        }
     }
-
     if (code) {
         *minor_status = code;
-        return(GSS_S_CRED_UNAVAIL);
+        return GSS_S_CRED_UNAVAIL;
     }
 
-    if (desired_name != NULL) {
-        princ = desired_name->princ;
-        if ((code = krb5_kt_get_entry(context, kt, princ, 0, 0, &entry))) {
-            (void) krb5_kt_close(context, kt);
+    if (desired_princ != NULL) {
+        code = krb5_kt_get_entry(context, kt, desired_princ, 0, 0, &entry);
+        if (code) {
+            krb5_kt_close(context, kt);
             if (code == KRB5_KT_NOTFOUND) {
                 char *errstr = (char *)krb5_get_error_message(context, code);
                 krb5_set_error_message(context, KG_KEYTAB_NOMATCH, "%s", errstr);
@@ -188,46 +187,50 @@ acquire_accept_cred(krb5_context context,
                 *minor_status = KG_KEYTAB_NOMATCH;
             } else
                 *minor_status = code;
-            return(GSS_S_CRED_UNAVAIL);
+            return GSS_S_CRED_UNAVAIL;
         }
         krb5_kt_free_entry(context, &entry);
 
-        /* Open the replay cache for this principal. */
-        if ((code = krb5_get_server_rcache(context,
-                                           krb5_princ_component(context, princ, 0),
-                                           &cred->rcache))) {
+        assert(cred->name == NULL);
+        code = kg_init_name(context, desired_princ, NULL,
+                            KG_INIT_NAME_INTERN, &cred->name);
+        if (code) {
             *minor_status = code;
-            return(GSS_S_FAILURE);
+            return GSS_S_FAILURE;
         }
 
+        /* Open the replay cache for this principal. */
+        code = krb5_get_server_rcache(context,
+                                      krb5_princ_component(context, desired_princ, 0),
+                                      &cred->rcache);
+        if (code) {
+            *minor_status = code;
+            return GSS_S_FAILURE;
+        }
     }
 
-/* hooray.  we made it */
-
     cred->keytab = kt;
 
-    return(GSS_S_COMPLETE);
+    return GSS_S_COMPLETE;
 }
 #endif /* LEAN_CLIENT */
 
 /* get credentials corresponding to the default credential cache.
-   If the default name is requested, return the name in output_name.
-   If output_name is non-NULL, the caller will use or free it, regardless
-   of the return value.
    If successful, set the ccache-specific fields in cred.
 */
 
 static OM_uint32
 acquire_init_cred(krb5_context context,
                   OM_uint32 *minor_status,
-                  krb5_gss_name_t desired_name,
-                  krb5_gss_name_t *output_name,
+                  krb5_ccache req_ccache,
+                  krb5_principal desired_princ,
                   gss_buffer_t password,
                   krb5_gss_cred_id_rec *cred)
 {
     krb5_error_code code;
     krb5_ccache ccache;
-    krb5_principal princ = NULL, tmp_princ;
+    krb5_principal ccache_princ = NULL, tmp_princ;
+    krb5_const_principal cred_princ = NULL;
     krb5_cc_cursor cur;
     krb5_creds creds;
     int got_endtime;
@@ -239,16 +242,16 @@ acquire_init_cred(krb5_context context,
     /* load the GSS ccache name into the kg_context */
 
     if (GSS_ERROR(kg_sync_ccache_name(context, minor_status)))
-        return(GSS_S_FAILURE);
+        return GSS_S_FAILURE;
 
     /* check to see if the caller provided a ccache name if so
      * we will just use that and not search the cache collection */
     if (GSS_ERROR(kg_caller_provided_ccache_name (minor_status, &caller_provided_ccache_name))) {
-        return(GSS_S_FAILURE);
+        return GSS_S_FAILURE;
     }
 
 #if defined(USE_KIM) || defined(USE_LEASH)
-    if (desired_name && !caller_provided_ccache_name) {
+    if (desired_princ && !caller_provided_ccache_name && !req_ccache) {
 #if defined(USE_KIM)
         kim_error err = KIM_NO_ERROR;
         kim_ccache kimccache = NULL;
@@ -257,7 +260,7 @@ acquire_init_cred(krb5_context context,
 
         err = kim_identity_create_from_krb5_principal (&identity,
                                                        context,
-                                                       desired_name->princ);
+                                                       desired_princ);
 
         if (!err) {
             err = kim_ccache_create_from_client_identity (&kimccache, identity);
@@ -291,7 +294,7 @@ acquire_init_cred(krb5_context context,
 
         if (err) {
             *minor_status = err;
-            return(GSS_S_CRED_UNAVAIL);
+            return GSS_S_CRED_UNAVAIL;
         }
 
 #elif defined(USE_LEASH)
@@ -305,80 +308,99 @@ acquire_init_cred(krb5_context context,
 
         if ( pLeash_AcquireInitialTicketsIfNeeded ) {
             char ccname[256]="";
-            pLeash_AcquireInitialTicketsIfNeeded(context, desired_name->princ, ccname, sizeof(ccname));
+            pLeash_AcquireInitialTicketsIfNeeded(context, desired_princ, ccname, sizeof(ccname));
             if (!ccname[0]) {
                 *minor_status = KRB5_CC_NOTFOUND;
-                return(GSS_S_CRED_UNAVAIL);
+                return GSS_S_CRED_UNAVAIL;
             }
 
             if ((code = krb5_cc_resolve (context, ccname, &ccache))) {
                 *minor_status = code;
-                return(GSS_S_CRED_UNAVAIL);
+                return GSS_S_CRED_UNAVAIL;
             }
         } else {
             /* leash dll not available, open the default credential cache */
 
             if ((code = krb5int_cc_default(context, &ccache))) {
                 *minor_status = code;
-                return(GSS_S_CRED_UNAVAIL);
+                return GSS_S_CRED_UNAVAIL;
             }
         }
 #endif /* USE_LEASH */
     } else
 #endif /* USE_KIM || USE_LEASH */
     {
-        /* open the default credential cache */
-
-        if ((code = krb5int_cc_default(context, &ccache))) {
+        if (req_ccache != NULL) {
+            /* Duplicate ccache handle */
+            code = krb5_cc_dup(context, req_ccache, &ccache);
+        } else {
+            /* Open the default credential cache */
+            code = krb5int_cc_default(context, &ccache);
+        }
+        if (code != 0) {
             *minor_status = code;
-            return(GSS_S_CRED_UNAVAIL);
+            return GSS_S_CRED_UNAVAIL;
         }
     }
 
     /* turn off OPENCLOSE mode while extensive frobbing is going on */
     code = krb5_cc_set_flags(context, ccache, 0);
     if (code == KRB5_FCC_NOFILE &&
-        password != GSS_C_NO_BUFFER && desired_name != NULL) {
+        password != GSS_C_NO_BUFFER && desired_princ != NULL) {
         /* We will get initial creds later. */
-        code = krb5_cc_initialize(context, ccache, desired_name->princ);
+        code = krb5_cc_initialize(context, ccache, desired_princ);
         if (code == 0)
             code = krb5_cc_set_flags(context, ccache, 0);
     }
     if (code != 0) {
-        (void)krb5_cc_close(context, ccache);
+        krb5_cc_close(context, ccache);
         *minor_status = code;
-        return(GSS_S_CRED_UNAVAIL);
+        return GSS_S_CRED_UNAVAIL;
     }
 
-    /* get out the principal name and see if it matches */
-    code = krb5_cc_get_principal(context, ccache, &princ);
+    /*
+     * Credentials cache principal must match either the acceptor principal
+     * name or the desired_princ argument (they may be the same).
+     */
+    if (cred->name != NULL && desired_princ == NULL)
+        desired_princ = cred->name->princ;
+
+    code = krb5_cc_get_principal(context, ccache, &ccache_princ);
     if (code != 0) {
-        (void)krb5_cc_close(context, ccache);
+        krb5_cc_close(context, ccache);
         *minor_status = code;
-        return(GSS_S_FAILURE);
+        return GSS_S_FAILURE;
     }
 
-    if (desired_name != NULL) {
-        if (!krb5_principal_compare(context, princ, desired_name->princ)) {
-            (void)krb5_free_principal(context, princ);
-            (void)krb5_cc_close(context, ccache);
+    if (desired_princ != NULL) {
+        if (!krb5_principal_compare(context, ccache_princ, desired_princ)) {
+            krb5_free_principal(context, ccache_princ);
+            krb5_cc_close(context, ccache);
             *minor_status = KG_CCACHE_NOMATCH;
-            return(GSS_S_CRED_UNAVAIL);
+            return GSS_S_CRED_UNAVAIL;
         }
-        (void)krb5_free_principal(context, princ);
-        princ = desired_name->princ;
-    } else {
-        if ((code = kg_init_name(context, princ, NULL,
+    }
+
+    /*
+     * If we are acquiring initiator-only default credentials, then set
+     * cred->name to the credentials cache principal name.
+     */
+    if (cred->name == NULL) {
+        if ((code = kg_init_name(context, ccache_princ, NULL,
                                  KG_INIT_NAME_NO_COPY | KG_INIT_NAME_INTERN,
-                                 output_name))) {
-            (void)krb5_free_principal(context, princ);
-            (void)krb5_cc_close(context, ccache);
+                                 &cred->name))) {
+            krb5_free_principal(context, ccache_princ);
+            krb5_cc_close(context, ccache);
             *minor_status = code;
-            return(GSS_S_FAILURE);
+            return GSS_S_FAILURE;
         }
-        /* princ is now owned by output_name, it need not be freed here */
+    } else {
+        krb5_free_principal(context, ccache_princ);
     }
 
+    assert(cred->name->princ != NULL);
+    cred_princ = cred->name->princ;
+
     if (password != GSS_C_NO_BUFFER) {
         /* stash the password for later */
         password_data.length = password->length;
@@ -387,7 +409,7 @@ acquire_init_cred(krb5_context context,
         code = krb5int_copy_data_contents_add0(context, &password_data,
                                                &cred->password);
         if (code != 0) {
-            (void)krb5_cc_close(context, ccache);
+            krb5_cc_close(context, ccache);
             *minor_status = code;
             return GSS_S_FAILURE;
         }
@@ -395,7 +417,7 @@ acquire_init_cred(krb5_context context,
         /* restore the OPENCLOSE flag */
         code = krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
         if (code != 0) {
-            (void)krb5_cc_close(context, ccache);
+            krb5_cc_close(context, ccache);
             *minor_status = code;
             return GSS_S_FAILURE;
         }
@@ -407,9 +429,9 @@ acquire_init_cred(krb5_context context,
     /* iterate over the ccache, find the tgt */
 
     if ((code = krb5_cc_start_seq_get(context, ccache, &cur))) {
-        (void)krb5_cc_close(context, ccache);
+        krb5_cc_close(context, ccache);
         *minor_status = code;
-        return(GSS_S_FAILURE);
+        return GSS_S_FAILURE;
     }
 
     /* this is hairy.  If there's a tgt for the principal's local realm
@@ -419,16 +441,16 @@ acquire_init_cred(krb5_context context,
     got_endtime = 0;
 
     code = krb5_build_principal_ext(context, &tmp_princ,
-                                    krb5_princ_realm(context, princ)->length,
-                                    krb5_princ_realm(context, princ)->data,
+                                    krb5_princ_realm(context, cred_princ)->length,
+                                    krb5_princ_realm(context, cred_princ)->data,
                                     KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
-                                    krb5_princ_realm(context, princ)->length,
-                                    krb5_princ_realm(context, princ)->data,
+                                    krb5_princ_realm(context, cred_princ)->length,
+                                    krb5_princ_realm(context, cred_princ)->data,
                                     0);
     if (code) {
-        (void)krb5_cc_close(context, ccache);
+        krb5_cc_close(context, ccache);
         *minor_status = code;
-        return(GSS_S_FAILURE);
+        return GSS_S_FAILURE;
     }
     while (!(code = krb5_cc_next_cred(context, ccache, &cur, &creds))) {
         if (krb5_principal_compare(context, tmp_princ, creds.server)) {
@@ -449,27 +471,27 @@ acquire_init_cred(krb5_context context,
 
     if (code && code != KRB5_CC_END) {
         /* this means some error occurred reading the ccache */
-        (void)krb5_cc_end_seq_get(context, ccache, &cur);
-        (void)krb5_cc_close(context, ccache);
+        krb5_cc_end_seq_get(context, ccache, &cur);
+        krb5_cc_close(context, ccache);
         *minor_status = code;
-        return(GSS_S_FAILURE);
+        return GSS_S_FAILURE;
     } else if (! got_endtime) {
         /* this means the ccache was entirely empty */
-        (void)krb5_cc_end_seq_get(context, ccache, &cur);
-        (void)krb5_cc_close(context, ccache);
+        krb5_cc_end_seq_get(context, ccache, &cur);
+        krb5_cc_close(context, ccache);
         *minor_status = KG_EMPTY_CCACHE;
-        return(GSS_S_FAILURE);
+        return GSS_S_FAILURE;
     } else {
         /* this means that we found an endtime to use. */
         if ((code = krb5_cc_end_seq_get(context, ccache, &cur))) {
-            (void)krb5_cc_close(context, ccache);
+            krb5_cc_close(context, ccache);
             *minor_status = code;
-            return(GSS_S_FAILURE);
+            return GSS_S_FAILURE;
         }
         if ((code = krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE))) {
-            (void)krb5_cc_close(context, ccache);
+            krb5_cc_close(context, ccache);
             *minor_status = code;
-            return(GSS_S_FAILURE);
+            return GSS_S_FAILURE;
         }
     }
 
@@ -477,26 +499,37 @@ acquire_init_cred(krb5_context context,
 
     cred->ccache = ccache;
     /* minor_status is set while we are iterating over the ccache */
-    return(GSS_S_COMPLETE);
+    return GSS_S_COMPLETE;
 }
 
-/*ARGSUSED*/
-static OM_uint32
-acquire_cred(minor_status, desired_name, password, time_req,
-             cred_usage, output_cred_handle, time_rec, req_iakerb)
-    OM_uint32 *minor_status;
-    const gss_name_t desired_name;
-    const gss_buffer_t password;
+struct acquire_cred_args {
+    gss_name_t desired_name;
+    gss_buffer_t password;
     OM_uint32 time_req;
+    gss_OID_set desired_mechs;
     gss_cred_usage_t cred_usage;
-    gss_cred_id_t *output_cred_handle;
-    OM_uint32 *time_rec;
-    int req_iakerb;
+    krb5_keytab keytab;
+    krb5_ccache ccache;
+    int iakerb;
+};
+
+/*ARGSUSED*/
+static OM_uint32
+acquire_cred(OM_uint32 *minor_status,
+             const struct acquire_cred_args *args,
+             gss_cred_id_t *output_cred_handle,
+             OM_uint32 *time_rec)
 {
     krb5_context context = NULL;
     krb5_gss_cred_id_t cred = NULL;
     OM_uint32 ret;
     krb5_error_code code = 0;
+    krb5_principal desired_princ = NULL;
+
+    /* make sure all outputs are valid */
+    *output_cred_handle = GSS_C_NO_CREDENTIAL;
+    if (time_rec)
+        *time_rec = 0;
 
     code = gss_krb5int_initialize_library();
     if (code)
@@ -506,31 +539,15 @@ acquire_cred(minor_status, desired_name, password, time_req,
     if (code)
         goto krb_error_out;
 
-    /* make sure all outputs are valid */
-
-    *output_cred_handle = NULL;
-    if (time_rec)
-        *time_rec = 0;
-
-    /* validate the name */
-
-    /*SUPPRESS 29*/
-    if ((desired_name != GSS_C_NO_NAME) &&
-        (! kg_validate_name(desired_name))) {
-        code = G_VALIDATE_FAILED;
-        goto krb_error_out;
-    }
-
     /* create the gss cred structure */
     cred = k5alloc(sizeof(krb5_gss_cred_id_rec), &code);
-    if (code != 0)
+    if (cred == NULL)
         goto krb_error_out;
 
-    cred->usage = cred_usage;
+    cred->usage = args->cred_usage;
     cred->name = NULL;
-    cred->iakerb_mech = req_iakerb;
-    cred->default_identity = (desired_name == GSS_C_NO_NAME);
-
+    cred->iakerb_mech = args->iakerb;
+    cred->default_identity = (args->desired_name == GSS_C_NO_NAME);
 #ifndef LEAN_CLIENT
     cred->keytab = NULL;
 #endif /* LEAN_CLIENT */
@@ -540,58 +557,50 @@ acquire_cred(minor_status, desired_name, password, time_req,
     if (code)
         goto krb_error_out;
 
-    /* Note that we don't need to lock this GSSAPI credential record
-       here, because no other thread can gain access to it until we
-       return it.  */
-
-    if ((cred_usage != GSS_C_INITIATE) &&
-        (cred_usage != GSS_C_ACCEPT) &&
-        (cred_usage != GSS_C_BOTH)) {
+    switch (args->cred_usage) {
+    case GSS_C_INITIATE:
+    case GSS_C_ACCEPT:
+    case GSS_C_BOTH:
+        break;
+    default:
+        ret = GSS_S_FAILURE;
         *minor_status = (OM_uint32) G_BAD_USAGE;
         goto error_out;
     }
 
-    /* if requested, acquire credentials for accepting */
-    /* this will fill in cred->name if the desired_name is not specified */
+    if (args->desired_name != GSS_C_NO_NAME)
+        desired_princ = ((krb5_gss_name_t)args->desired_name)->princ;
+
 #ifndef LEAN_CLIENT
-    if ((cred_usage == GSS_C_ACCEPT) ||
-        (cred_usage == GSS_C_BOTH))
-        if ((ret = acquire_accept_cred(context, minor_status,
-                                       (krb5_gss_name_t)desired_name,
-                                       &cred->name, cred))
-            != GSS_S_COMPLETE) {
+    /*
+     * If requested, acquire credentials for accepting. This will fill
+     * in cred->name if desired_princ is specified.
+     */
+    if (args->cred_usage == GSS_C_ACCEPT || args->cred_usage == GSS_C_BOTH) {
+        ret = acquire_accept_cred(context, minor_status,
+                                  desired_princ,
+                                  args->keytab, cred);
+        if (ret != GSS_S_COMPLETE)
             goto error_out;
-        }
+    }
 #endif /* LEAN_CLIENT */
 
-    /* if requested, acquire credentials for initiation */
-    /* this will fill in cred->name if it wasn't set above, and
-       the desired_name is not specified */
-
-    if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) {
-        ret = acquire_init_cred(context, minor_status,
-                                cred->name ?
-                                    cred->name : (krb5_gss_name_t)desired_name,
-                                &cred->name, password, cred);
+    /*
+     * If requested, acquire credentials for initiation. This will fill
+     * in cred->name if it wasn't set above.
+     */
+    if (args->cred_usage == GSS_C_INITIATE || args->cred_usage == GSS_C_BOTH) {
+        ret = acquire_init_cred(context, minor_status, args->ccache,
+                                desired_princ, args->password, cred);
         if (ret != GSS_S_COMPLETE)
             goto error_out;
     }
 
-    /* if the princ wasn't filled in already, fill it in now */
-
-    if (!cred->name && (desired_name != GSS_C_NO_NAME)) {
-        code = kg_duplicate_name(context,
-                                 (krb5_gss_name_t)desired_name,
-                                 0, &cred->name);
-        if (code != 0)
-            goto krb_error_out;
-    }
+    assert(cred->default_identity || cred->name != NULL);
 
     /*** at this point, the cred structure has been completely created */
 
-    /* compute time_rec */
-
-    if (cred_usage == GSS_C_ACCEPT) {
+    if (args->cred_usage == GSS_C_ACCEPT) {
         if (time_rec)
             *time_rec = GSS_C_INDEFINITE;
     } else {
@@ -605,20 +614,16 @@ acquire_cred(minor_status, desired_name, password, time_req,
             *time_rec = (cred->tgt_expire > now) ? (cred->tgt_expire - now) : 0;
     }
 
-    /* intern the credential handle */
-
-    if (! kg_save_cred_id((gss_cred_id_t) cred)) {
+    if (!kg_save_cred_id((gss_cred_id_t)cred)) {
         ret = GSS_S_FAILURE;
         goto error_out;
     }
 
-    /* return success */
-
     *minor_status = 0;
     *output_cred_handle = (gss_cred_id_t) cred;
 
     krb5_free_context(context);
-    return(GSS_S_COMPLETE);
+    return GSS_S_COMPLETE;
 
 krb_error_out:
     *minor_status = code;
@@ -627,10 +632,10 @@ krb_error_out:
 error_out:
     if (cred != NULL) {
         if (cred->ccache)
-            (void)krb5_cc_close(context, cred->ccache);
+            krb5_cc_close(context, cred->ccache);
 #ifndef LEAN_CLIENT
         if (cred->keytab)
-            (void)krb5_kt_close(context, cred->keytab);
+            krb5_kt_close(context, cred->keytab);
 #endif /* LEAN_CLIENT */
         if (cred->name)
             kg_release_name(context, 0, &cred->name);
@@ -644,7 +649,7 @@ error_out:
 
 OM_uint32
 gss_krb5int_set_cred_rcache(OM_uint32 *minor_status,
-                            gss_cred_id_t cred_handle,
+                            gss_cred_id_t *cred_handle,
                             const gss_OID desired_oid,
                             const gss_buffer_t value)
 {
@@ -660,10 +665,7 @@ gss_krb5int_set_cred_rcache(OM_uint32 *minor_status,
 
     rcache = (krb5_rcache)value->value;
 
-    if (cred_handle == GSS_C_NO_CREDENTIAL)
-        return GSS_S_NO_CRED;
-
-    cred = (krb5_gss_cred_id_t)cred_handle;
+    cred = (krb5_gss_cred_id_t)*cred_handle;
 
     code = krb5_gss_init_context(&context);
     if (code) {
@@ -705,8 +707,21 @@ krb5_gss_acquire_cred(minor_status, desired_name, time_req,
     gss_OID_set *actual_mechs;
     OM_uint32 *time_rec;
 {
-    return acquire_cred(minor_status, desired_name, GSS_C_NO_BUFFER,
-                        time_req, cred_usage, output_cred_handle, time_rec, 0);
+    struct acquire_cred_args args;
+
+    if (desired_name && !kg_validate_name(desired_name)) {
+        *minor_status = G_VALIDATE_FAILED;
+        return GSS_S_FAILURE;
+    }
+
+    memset(&args, 0, sizeof(args));
+    args.desired_name = desired_name;
+    args.time_req = time_req;
+    args.desired_mechs = desired_mechs;
+    args.cred_usage = cred_usage;
+    args.iakerb = 0;
+
+    return acquire_cred(minor_status, &args, output_cred_handle, time_rec);
 }
 
 OM_uint32
@@ -722,8 +737,21 @@ iakerb_gss_acquire_cred(minor_status, desired_name, time_req,
     gss_OID_set *actual_mechs;
     OM_uint32 *time_rec;
 {
-    return acquire_cred(minor_status, desired_name, GSS_C_NO_BUFFER,
-                        time_req, cred_usage, output_cred_handle, time_rec, 1);
+    struct acquire_cred_args args;
+
+    if (desired_name && !kg_validate_name(desired_name)) {
+        *minor_status = G_VALIDATE_FAILED;
+        return GSS_S_FAILURE;
+    }
+
+    memset(&args, 0, sizeof(args));
+    args.desired_name = desired_name;
+    args.time_req = time_req;
+    args.desired_mechs = desired_mechs;
+    args.cred_usage = cred_usage;
+    args.iakerb = 1;
+
+    return acquire_cred(minor_status, &args, output_cred_handle, time_rec);
 }
 
 OM_uint32
@@ -737,8 +765,22 @@ krb5_gss_acquire_cred_with_password(OM_uint32 *minor_status,
                                     gss_OID_set *actual_mechs,
                                     OM_uint32 *time_rec)
 {
-    return acquire_cred(minor_status, desired_name, password,
-                        time_req, cred_usage, output_cred_handle, time_rec, 0);
+    struct acquire_cred_args args;
+
+    if (desired_name && !kg_validate_name(desired_name)) {
+        *minor_status = G_VALIDATE_FAILED;
+        return GSS_S_FAILURE;
+    }
+
+    memset(&args, 0, sizeof(args));
+    args.desired_name = desired_name;
+    args.password = password;
+    args.time_req = time_req;
+    args.desired_mechs = desired_mechs;
+    args.cred_usage = cred_usage;
+    args.iakerb = 0;
+
+    return acquire_cred(minor_status, &args, output_cred_handle, time_rec);
 }
 
 OM_uint32
@@ -752,6 +794,64 @@ iakerb_gss_acquire_cred_with_password(OM_uint32 *minor_status,
                                       gss_OID_set *actual_mechs,
                                       OM_uint32 *time_rec)
 {
-    return acquire_cred(minor_status, desired_name, password,
-                        time_req, cred_usage, output_cred_handle, time_rec, 1);
+    struct acquire_cred_args args;
+
+    if (desired_name && !kg_validate_name(desired_name)) {
+        *minor_status = G_VALIDATE_FAILED;
+        return GSS_S_FAILURE;
+    }
+
+    memset(&args, 0, sizeof(args));
+    args.desired_name = desired_name;
+    args.password = password;
+    args.time_req = time_req;
+    args.desired_mechs = desired_mechs;
+    args.cred_usage = cred_usage;
+    args.iakerb = 1;
+
+    return acquire_cred(minor_status, &args, output_cred_handle, time_rec);
+}
+
+OM_uint32
+gss_krb5int_import_cred(OM_uint32 *minor_status,
+                        gss_cred_id_t *cred_handle,
+                        const gss_OID desired_oid,
+                        const gss_buffer_t value)
+{
+    struct krb5_gss_import_cred_req *req;
+    struct acquire_cred_args args;
+    krb5_gss_name_rec name;
+    OM_uint32 time_rec;
+
+    assert(value->length == sizeof(*req));
+
+    if (value->length != sizeof(*req))
+        return GSS_S_FAILURE;
+
+    req = (struct krb5_gss_import_cred_req *)value->value;
+
+    memset(&args, 0, sizeof(args));
+
+    if (req->keytab_principal) {
+        memset(&name, 0, sizeof(name));
+        name.princ = req->keytab_principal;
+        args.desired_name = (gss_name_t)&name;
+    }
+
+    args.ccache = req->id;
+    args.keytab = req->keytab;
+
+    if (req->id && req->keytab)
+        args.cred_usage = GSS_C_BOTH;
+    else if (req->id)
+        args.cred_usage = GSS_C_INITIATE;
+    else if (req->keytab)
+        args.cred_usage = GSS_C_ACCEPT;
+    else {
+        *minor_status = EINVAL;
+        return GSS_S_FAILURE;
+    }
+
+    return acquire_cred(minor_status, &args, cred_handle, &time_rec);
 }
+
diff --git a/src/lib/gssapi/krb5/copy_ccache.c b/src/lib/gssapi/krb5/copy_ccache.c
index 632c866..efc6807 100644
--- a/src/lib/gssapi/krb5/copy_ccache.c
+++ b/src/lib/gssapi/krb5/copy_ccache.c
@@ -3,7 +3,7 @@
 
 OM_uint32 KRB5_CALLCONV
 gss_krb5int_copy_ccache(OM_uint32 *minor_status,
-                        gss_cred_id_t cred_handle,
+                        gss_cred_id_t *cred_handle,
                         const gss_OID desired_object,
                         const gss_buffer_t value)
 {
@@ -22,8 +22,7 @@ gss_krb5int_copy_ccache(OM_uint32 *minor_status,
     out_ccache = (krb5_ccache)value->value;
 
     /* cred handle will have been validated by gssspi_set_cred_option() */
-
-    k5creds = (krb5_gss_cred_id_t) cred_handle;
+    k5creds = (krb5_gss_cred_id_t) *cred_handle;
     code = k5_mutex_lock(&k5creds->lock);
     if (code) {
         *minor_status = code;
diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h
index 573d315..fc74ff1 100644
--- a/src/lib/gssapi/krb5/gssapiP_krb5.h
+++ b/src/lib/gssapi/krb5/gssapiP_krb5.h
@@ -1007,7 +1007,7 @@ OM_uint32 KRB5_CALLCONV gss_krb5int_get_tkt_flags
 
 OM_uint32 KRB5_CALLCONV gss_krb5int_copy_ccache
 (OM_uint32 *minor_status,
- gss_cred_id_t cred_handle,
+ gss_cred_id_t *cred_handle,
  const gss_OID desired_oid,
  const gss_buffer_t value);
 
@@ -1025,6 +1025,12 @@ OM_uint32 KRB5_CALLCONV gss_krb5int_ccache_name
   const gss_OID,
   const gss_buffer_t);
 
+#define GSS_KRB5_INQ_SSPI_SESSION_KEY_OID_LENGTH 11
+#define GSS_KRB5_INQ_SSPI_SESSION_KEY_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x05"
+
+OM_uint32
+gss_krb5int_inq_session_key(OM_uint32 *, const gss_ctx_id_t, const gss_OID, gss_buffer_set_t *);
+
 #define GSS_KRB5_SET_ALLOWABLE_ENCTYPES_OID_LENGTH 11
 #define GSS_KRB5_SET_ALLOWABLE_ENCTYPES_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x04"
 
@@ -1033,15 +1039,9 @@ struct krb5_gss_set_allowable_enctypes_req {
     krb5_enctype *ktypes;
 };
 
-#define GSS_KRB5_INQ_SSPI_SESSION_KEY_OID_LENGTH 11
-#define GSS_KRB5_INQ_SSPI_SESSION_KEY_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x05"
-
-OM_uint32
-gss_krb5int_inq_session_key(OM_uint32 *, const gss_ctx_id_t, const gss_OID, gss_buffer_set_t *);
-
 OM_uint32 KRB5_CALLCONV
 gss_krb5int_set_allowable_enctypes(OM_uint32 *minor_status,
-                                   gss_cred_id_t cred,
+                                   gss_cred_id_t *cred,
                                    const gss_OID desired_oid,
                                    const gss_buffer_t value);
 
@@ -1091,7 +1091,7 @@ gss_krb5int_extract_authz_data_from_sec_context(OM_uint32 *minor_status,
 #define GSS_KRB5_SET_CRED_RCACHE_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0b"
 
 OM_uint32
-gss_krb5int_set_cred_rcache(OM_uint32 *, gss_cred_id_t, const gss_OID, const gss_buffer_t);
+gss_krb5int_set_cred_rcache(OM_uint32 *, gss_cred_id_t *, const gss_OID, const gss_buffer_t);
 
 #define GSS_KRB5_EXTRACT_AUTHTIME_FROM_SEC_CONTEXT_OID_LENGTH 11
 #define GSS_KRB5_EXTRACT_AUTHTIME_FROM_SEC_CONTEXT_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0c"
@@ -1102,6 +1102,21 @@ gss_krb5int_extract_authtime_from_sec_context(OM_uint32 *,
                                               const gss_OID,
                                               gss_buffer_set_t *);
 
+#define GSS_KRB5_IMPORT_CRED_OID_LENGTH 11
+#define GSS_KRB5_IMPORT_CRED_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0d"
+
+struct krb5_gss_import_cred_req {
+    krb5_ccache id;
+    krb5_principal keytab_principal;
+    krb5_keytab keytab;
+};
+
+OM_uint32 KRB5_CALLCONV
+gss_krb5int_import_cred(OM_uint32 *minor_status,
+                        gss_cred_id_t *cred,
+                        const gss_OID desired_oid,
+                        const gss_buffer_t value);
+
 #ifdef _GSS_STATIC_LINK
 int gss_krb5int_lib_init(void);
 void gss_krb5int_lib_fini(void);
diff --git a/src/lib/gssapi/krb5/gssapi_krb5.c b/src/lib/gssapi/krb5/gssapi_krb5.c
index ba1dbeb..8b074d6 100644
--- a/src/lib/gssapi/krb5/gssapi_krb5.c
+++ b/src/lib/gssapi/krb5/gssapi_krb5.c
@@ -510,7 +510,7 @@ krb5_gss_set_sec_context_option (OM_uint32 *minor_status,
  */
 static struct {
     gss_OID_desc oid;
-    OM_uint32 (*func)(OM_uint32 *, gss_cred_id_t, const gss_OID, const gss_buffer_t);
+    OM_uint32 (*func)(OM_uint32 *, gss_cred_id_t *, const gss_OID, const gss_buffer_t);
 } krb5_gssspi_set_cred_option_ops[] = {
     {
         {GSS_KRB5_COPY_CCACHE_OID_LENGTH, GSS_KRB5_COPY_CCACHE_OID},
@@ -524,11 +524,15 @@ static struct {
         {GSS_KRB5_SET_CRED_RCACHE_OID_LENGTH, GSS_KRB5_SET_CRED_RCACHE_OID},
         gss_krb5int_set_cred_rcache
     },
+    {
+        {GSS_KRB5_IMPORT_CRED_OID_LENGTH, GSS_KRB5_IMPORT_CRED_OID},
+        gss_krb5int_import_cred
+    },
 };
 
 static OM_uint32
 krb5_gssspi_set_cred_option(OM_uint32 *minor_status,
-                            gss_cred_id_t cred_handle,
+                            gss_cred_id_t *cred_handle,
                             const gss_OID desired_object,
                             const gss_buffer_t value)
 {
@@ -538,19 +542,19 @@ krb5_gssspi_set_cred_option(OM_uint32 *minor_status,
     if (minor_status == NULL)
         return GSS_S_CALL_INACCESSIBLE_WRITE;
 
-    *minor_status = 0;
+    if (cred_handle == NULL)
+        return GSS_S_CALL_INACCESSIBLE_WRITE;
 
-    if (cred_handle == GSS_C_NO_CREDENTIAL) {
-        *minor_status = (OM_uint32)KRB5_NOCREDS_SUPPLIED;
-        return GSS_S_NO_CRED;
-    }
+    *minor_status = 0;
 
     if (desired_object == GSS_C_NO_OID)
         return GSS_S_CALL_INACCESSIBLE_READ;
 
-    major_status = krb5_gss_validate_cred(minor_status, cred_handle);
-    if (GSS_ERROR(major_status))
-        return major_status;
+    if (*cred_handle != GSS_C_NO_CREDENTIAL) {
+        major_status = krb5_gss_validate_cred(minor_status, *cred_handle);
+        if (GSS_ERROR(major_status))
+            return major_status;
+    }
 
     for (i = 0; i < sizeof(krb5_gssspi_set_cred_option_ops)/
              sizeof(krb5_gssspi_set_cred_option_ops[0]); i++) {
diff --git a/src/lib/gssapi/krb5/gssapi_krb5.hin b/src/lib/gssapi/krb5/gssapi_krb5.hin
index ce96454..c4e5a76 100644
--- a/src/lib/gssapi/krb5/gssapi_krb5.hin
+++ b/src/lib/gssapi/krb5/gssapi_krb5.hin
@@ -282,6 +282,13 @@ gss_krb5_set_cred_rcache(OM_uint32 *minor_status,
 OM_uint32 KRB5_CALLCONV
 gsskrb5_extract_authtime_from_sec_context(OM_uint32 *, gss_ctx_id_t, krb5_timestamp *);
 
+OM_uint32 KRB5_CALLCONV
+gss_krb5_import_cred(OM_uint32 *minor_status,
+                     krb5_ccache id,
+                     krb5_principal keytab_principal,
+                     krb5_keytab keytab,
+                     gss_cred_id_t *cred);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/src/lib/gssapi/krb5/krb5_gss_glue.c b/src/lib/gssapi/krb5/krb5_gss_glue.c
index 0d87f90..f974aae 100644
--- a/src/lib/gssapi/krb5/krb5_gss_glue.c
+++ b/src/lib/gssapi/krb5/krb5_gss_glue.c
@@ -108,10 +108,44 @@ gss_krb5_copy_ccache(OM_uint32 *minor_status,
     req_buffer.value = out_ccache;
     req_buffer.length = sizeof(out_ccache);
 
-    major_status = gssspi_set_cred_option(minor_status,
-                                          cred_handle,
-                                          (gss_OID)&req_oid,
-                                          &req_buffer);
+    major_status = gss_set_cred_option(minor_status,
+                                       &cred_handle,
+                                       (gss_OID)&req_oid,
+                                       &req_buffer);
+
+    return major_status;
+}
+
+OM_uint32 KRB5_CALLCONV
+gss_krb5_import_cred(OM_uint32 *minor_status,
+                     krb5_ccache id,
+                     krb5_principal keytab_principal,
+                     krb5_keytab keytab,
+                     gss_cred_id_t *cred)
+{
+    static const gss_OID_desc req_oid = {
+        GSS_KRB5_IMPORT_CRED_OID_LENGTH,
+        GSS_KRB5_IMPORT_CRED_OID };
+    OM_uint32 major_status;
+    struct krb5_gss_import_cred_req req;
+    gss_buffer_desc req_buffer;
+
+    if (cred == NULL)
+        return GSS_S_CALL_INACCESSIBLE_WRITE;
+
+    *cred = GSS_C_NO_CREDENTIAL;
+
+    req.id = id;
+    req.keytab_principal = keytab_principal;
+    req.keytab = keytab;
+
+    req_buffer.value = &req;
+    req_buffer.length = sizeof(req);
+
+    major_status = gss_set_cred_option(minor_status,
+                                       cred,
+                                       (gss_OID)&req_oid,
+                                       &req_buffer);
 
     return major_status;
 }
@@ -189,10 +223,10 @@ gss_krb5_set_allowable_enctypes(OM_uint32 *minor_status,
     req_buffer.length = sizeof(req);
     req_buffer.value = &req;
 
-    major_status = gssspi_set_cred_option(minor_status,
-                                          cred,
-                                          (gss_OID)&req_oid,
-                                          &req_buffer);
+    major_status = gss_set_cred_option(minor_status,
+                                       &cred,
+                                       (gss_OID)&req_oid,
+                                       &req_buffer);
 
     return major_status;
 }
@@ -363,10 +397,10 @@ gss_krb5_set_cred_rcache(OM_uint32 *minor_status,
     req_buffer.length = sizeof(rcache);
     req_buffer.value = rcache;
 
-    major_status = gssspi_set_cred_option(minor_status,
-                                          cred,
-                                          (gss_OID)&req_oid,
-                                          &req_buffer);
+    major_status = gss_set_cred_option(minor_status,
+                                       &cred,
+                                       (gss_OID)&req_oid,
+                                       &req_buffer);
 
     return major_status;
 }
diff --git a/src/lib/gssapi/krb5/set_allowable_enctypes.c b/src/lib/gssapi/krb5/set_allowable_enctypes.c
index cdfc965..de1c5a4 100644
--- a/src/lib/gssapi/krb5/set_allowable_enctypes.c
+++ b/src/lib/gssapi/krb5/set_allowable_enctypes.c
@@ -61,7 +61,7 @@
 
 OM_uint32 KRB5_CALLCONV
 gss_krb5int_set_allowable_enctypes(OM_uint32 *minor_status,
-                                   gss_cred_id_t cred_handle,
+                                   gss_cred_id_t *cred_handle,
                                    const gss_OID desired_oid,
                                    const gss_buffer_t value)
 {
@@ -81,16 +81,7 @@ gss_krb5int_set_allowable_enctypes(OM_uint32 *minor_status,
     req = (struct krb5_gss_set_allowable_enctypes_req *)value->value;
 
     /* verify and valildate cred handle */
-    if (cred_handle == GSS_C_NO_CREDENTIAL) {
-        kerr = KRB5_NOCREDS_SUPPLIED;
-        goto error_out;
-    }
-    major_status = krb5_gss_validate_cred(&temp_status, cred_handle);
-    if (GSS_ERROR(major_status)) {
-        kerr = temp_status;
-        goto error_out;
-    }
-    cred = (krb5_gss_cred_id_t) cred_handle;
+    cred = (krb5_gss_cred_id_t) *cred_handle;
 
     if (req->ktypes) {
         for (i = 0; i < req->num_ktypes && req->ktypes[i]; i++) {
diff --git a/src/lib/gssapi/libgssapi_krb5.exports b/src/lib/gssapi/libgssapi_krb5.exports
index 5b00fc2..707fe52 100644
--- a/src/lib/gssapi/libgssapi_krb5.exports
+++ b/src/lib/gssapi/libgssapi_krb5.exports
@@ -46,8 +46,9 @@ gss_inquire_sec_context_by_oid
 gss_krb5_ccache_name
 gss_krb5_copy_ccache
 gss_krb5_export_lucid_sec_context
-gss_krb5_get_tkt_flags
 gss_krb5_free_lucid_sec_context
+gss_krb5_get_tkt_flags
+gss_krb5_import_cred
 gss_krb5_set_allowable_enctypes
 gss_krb5_set_cred_rcache
 gss_krb5int_make_seal_token_v3
@@ -99,6 +100,7 @@ gss_wrap_aead
 gss_wrap_iov
 gss_wrap_iov_length
 gss_wrap_size_limit
+gss_set_cred_option
 gssspi_set_cred_option
 gssspi_mech_invoke
 krb5_gss_dbg_client_expcreds
diff --git a/src/lib/gssapi/mechglue/g_set_cred_option.c b/src/lib/gssapi/mechglue/g_set_cred_option.c
index ff388d9..7bb73ed 100644
--- a/src/lib/gssapi/mechglue/g_set_cred_option.c
+++ b/src/lib/gssapi/mechglue/g_set_cred_option.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 by the Massachusetts Institute of Technology.
+ * Copyright 2008-2010 by the Massachusetts Institute of Technology.
  * All Rights Reserved.
  *
  * Export of this software from the United States of America may
@@ -35,11 +35,92 @@
 #include <string.h>
 #include <time.h>
 
+static OM_uint32
+alloc_union_cred(OM_uint32 *minor_status,
+		 gss_mechanism mech,
+		 gss_cred_id_t mech_cred,
+		 gss_union_cred_t *pcred)
+{
+    OM_uint32		status;
+    OM_uint32		temp_minor_status;
+    gss_union_cred_t	cred = NULL;
+    gss_name_t		mech_name = GSS_C_NO_NAME;
+
+    *pcred = NULL;
+
+    if (mech->gss_inquire_cred == NULL) {
+	status = GSS_S_BAD_MECH;
+	goto cleanup;
+    }
+
+    status = GSS_S_FAILURE;
+
+    cred = calloc(1, sizeof(*cred));
+    if (cred == NULL) {
+	*minor_status = ENOMEM;
+	goto cleanup;
+    }
+
+    cred->loopback = cred;
+    cred->count = 1;
+
+    cred->cred_array = calloc(cred->count, sizeof(gss_cred_id_t));
+    if (cred->cred_array == NULL) {
+	*minor_status = ENOMEM;
+	goto cleanup;
+    }
+    cred->cred_array[0] = mech_cred;
+
+    status = generic_gss_copy_oid(minor_status,
+                                  &mech->mech_type,
+                                  &cred->mechs_array);
+    if (status != GSS_S_COMPLETE)
+        goto cleanup;
+
+    cred->auxinfo.creation_time = (OM_uint32)time(NULL);
+
+    status = mech->gss_inquire_cred(minor_status,
+				    mech_cred,
+				    &mech_name,
+				    &cred->auxinfo.time_rec,
+				    &cred->auxinfo.cred_usage,
+				    NULL);
+    if (status != GSS_S_COMPLETE)
+	goto cleanup;
+
+    status = mech->gss_display_name(minor_status,
+				    mech_name,
+				    &cred->auxinfo.name,
+				    &cred->auxinfo.name_type);
+    if (status != GSS_S_COMPLETE)
+	goto cleanup;
+
+    status = GSS_S_COMPLETE;
+    *pcred = cred;
+
+cleanup:
+    if (status != GSS_S_COMPLETE)
+	gss_release_cred(&temp_minor_status, (gss_cred_id_t *)&cred);
+    mech->gss_release_name(&temp_minor_status, &mech_name);
+
+    return status;
+}
+
+/*
+ * This differs from gssspi_set_cred_option() as shipped in 1.7, in that
+ * it can return a cred handle. To denote this change we have changed the
+ * name of the function from gssspi_set_cred_option() to gss_set_cred_option().
+ * However, the dlsym() entry point is still gssspi_set_cred_option(). This
+ * fixes a separate issue, namely that a dynamically loaded mechanism could
+ * not itself call set_cred_option() without calling its own implementation
+ * instead of the mechanism glue's. (This is useful where a mechanism wishes
+ * to export a mechanism-specific API that is a wrapper around this function.)
+ */
 OM_uint32 KRB5_CALLCONV
-gssspi_set_cred_option(OM_uint32 *minor_status,
-	               gss_cred_id_t cred_handle,
-	               const gss_OID desired_object,
-	               const gss_buffer_t value)
+gss_set_cred_option(OM_uint32 *minor_status,
+	            gss_cred_id_t *cred_handle,
+	            const gss_OID desired_object,
+	            const gss_buffer_t value)
 {
     gss_union_cred_t	union_cred;
     gss_mechanism	mech;
@@ -51,42 +132,95 @@ gssspi_set_cred_option(OM_uint32 *minor_status,
     if (minor_status == NULL)
 	return GSS_S_CALL_INACCESSIBLE_WRITE;
 
-    if (cred_handle == GSS_C_NO_CREDENTIAL)
-	return GSS_S_CALL_INACCESSIBLE_READ | GSS_S_NO_CRED;
+    if (cred_handle == NULL)
+	return GSS_S_CALL_INACCESSIBLE_WRITE;
 
     *minor_status = 0;
 
-    union_cred = (gss_union_cred_t) cred_handle;
-
     status = GSS_S_UNAVAILABLE;
 
-    for (i = 0; i < union_cred->count; i++) {
-	mech = gssint_get_mechanism(&union_cred->mechs_array[i]);
-	if (mech == NULL) {
-	    status = GSS_S_BAD_MECH;
-	    break;
-	}
+    if (*cred_handle == GSS_C_NO_CREDENTIAL) {
+	gss_cred_id_t mech_cred = GSS_C_NO_CREDENTIAL;
 
-	if (mech->gssspi_set_cred_option == NULL) {
-	    continue;
-	}
+	/*
+	 * We need to give a mechanism the opportunity to allocate a
+	 * credentials handle. Unfortunately this does mean that only
+	 * the default mechanism can allocate a credentials handle.
+	 */
+        mech = gssint_get_mechanism(NULL);
+        if (mech == NULL)
+            return GSS_S_BAD_MECH;
+
+	if (mech->gssspi_set_cred_option == NULL)
+	    return GSS_S_UNAVAILABLE;
 
-	mech_status = (mech->gssspi_set_cred_option)(&mech_minor_status,
-						union_cred->cred_array[i],
-						desired_object,
-						value);
-        if (mech_status == GSS_S_UNAVAILABLE) {
-            continue;
-        }
-        else {
-            status = mech_status;
-            *minor_status = mech_minor_status;
-        }
+	status = mech->gssspi_set_cred_option(minor_status,
+					      &mech_cred,
+					      desired_object,
+					      value);
 	if (status != GSS_S_COMPLETE) {
 	    map_error(minor_status, mech);
-	    break;
+	    return status;
+	}
+
+	if (mech_cred != GSS_C_NO_CREDENTIAL) {
+	    status = alloc_union_cred(minor_status,
+				      mech,
+				      mech_cred,
+				      &union_cred);
+	    if (status != GSS_S_COMPLETE)
+		return status;
+	    *cred_handle = (gss_cred_id_t)union_cred;
+	}
+    } else {
+	union_cred = (gss_union_cred_t)*cred_handle;
+
+	for (i = 0; i < union_cred->count; i++) {
+	    mech = gssint_get_mechanism(&union_cred->mechs_array[i]);
+	    if (mech == NULL) {
+		status = GSS_S_BAD_MECH;
+		break;
+	    }
+
+	    if (mech->gssspi_set_cred_option == NULL)
+		continue;
+
+	    mech_status = mech->gssspi_set_cred_option(&mech_minor_status,
+						       &union_cred->cred_array[i],
+						       desired_object,
+						       value);
+	    if (mech_status == GSS_S_UNAVAILABLE)
+		continue;
+	    else {
+		status = mech_status;
+		*minor_status = mech_minor_status;
+	    }
+	    if (status != GSS_S_COMPLETE) {
+		map_error(minor_status, mech);
+		break;
+	    }
 	}
     }
 
     return status;
 }
+
+/*
+ * Provide this for backward ABI compatibility, but remove it from the
+ * header.
+ */
+OM_uint32 KRB5_CALLCONV
+gssspi_set_cred_option(OM_uint32 *minor_status,
+	               gss_cred_id_t cred,
+	               const gss_OID desired_object,
+	               const gss_buffer_t value);
+
+OM_uint32 KRB5_CALLCONV
+gssspi_set_cred_option(OM_uint32 *minor_status,
+	               gss_cred_id_t cred,
+	               const gss_OID desired_object,
+	               const gss_buffer_t value)
+{
+    return gss_set_cred_option(minor_status, &cred,
+                               desired_object, value);
+}
diff --git a/src/lib/gssapi/mechglue/mglueP.h b/src/lib/gssapi/mechglue/mglueP.h
index f219290..da427f4 100644
--- a/src/lib/gssapi/mechglue/mglueP.h
+++ b/src/lib/gssapi/mechglue/mglueP.h
@@ -396,7 +396,7 @@ typedef struct gss_config {
 	OM_uint32       (*gssspi_set_cred_option)
     	(
     		    OM_uint32 *,	/* minor_status */
-    		    gss_cred_id_t,      /* cred_handle */
+    		    gss_cred_id_t *,    /* cred_handle */
     		    const gss_OID,      /* OID */
     		    const gss_buffer_t	/* value */
     		    );
diff --git a/src/lib/gssapi/spnego/gssapiP_spnego.h b/src/lib/gssapi/spnego/gssapiP_spnego.h
index d72c85d..e146508 100644
--- a/src/lib/gssapi/spnego/gssapiP_spnego.h
+++ b/src/lib/gssapi/spnego/gssapiP_spnego.h
@@ -361,7 +361,7 @@ OM_uint32
 spnego_gss_set_cred_option
 (
 	OM_uint32 *minor_status,
-	gss_cred_id_t cred_handle,
+	gss_cred_id_t *cred_handle,
 	const gss_OID desired_object,
 	const gss_buffer_t value
 );
diff --git a/src/lib/gssapi/spnego/spnego_mech.c b/src/lib/gssapi/spnego/spnego_mech.c
index 62e7099..4e93917 100644
--- a/src/lib/gssapi/spnego/spnego_mech.c
+++ b/src/lib/gssapi/spnego/spnego_mech.c
@@ -2198,18 +2198,38 @@ spnego_gss_inquire_cred_by_oid(
 OM_uint32
 spnego_gss_set_cred_option(
 		OM_uint32 *minor_status,
-		gss_cred_id_t cred_handle,
+		gss_cred_id_t *cred_handle,
 		const gss_OID desired_object,
 		const gss_buffer_t value)
 {
 	OM_uint32 ret;
+	OM_uint32 tmp_minor_status;
 	spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle;
 	gss_cred_id_t mcred;
+
 	mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
+
 	ret = gssspi_set_cred_option(minor_status,
-				     mcred,
+				     &mcred,
 				     desired_object,
 				     value);
+	if (ret == GSS_S_COMPLETE && spcred == NULL) {
+		/*
+		 * If the mechanism allocated a new credential handle, then
+		 * we need to wrap it up in an SPNEGO credential handle.
+		 */
+
+		spcred = malloc(sizeof(spnego_gss_cred_id_rec));
+		if (spcred == NULL) {
+			gss_release_cred(&tmp_minor_status, &mcred);
+			*minor_status = ENOMEM;
+			return (GSS_S_FAILURE);
+		}
+		spcred->mcred = mcred;
+		spcred->neg_mechs = GSS_C_NULL_OID_SET;
+		*cred_handle = (gss_cred_id_t)spcred;
+	}
+
 	return (ret);
 }
 
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index fedecf6..e59acfe 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -176,6 +176,7 @@ krb5_cc_default
 krb5_cc_default_name
 krb5_cc_destroy
 krb5_cc_dfl_ops
+krb5_cc_dup
 krb5_cc_end_seq_get
 krb5_cc_file_ops
 krb5_cc_gen_new
diff --git a/src/tests/gssapi/t_gssexts.c b/src/tests/gssapi/t_gssexts.c
index 959378a..ed14755 100644
--- a/src/tests/gssapi/t_gssexts.c
+++ b/src/tests/gssapi/t_gssexts.c
@@ -288,6 +288,82 @@ initAcceptSecContext(OM_uint32 *minor,
     return major;
 }
 
+static OM_uint32
+getDefaultCred(OM_uint32 *minor,
+               const char *keytab_name,
+               gss_OID_set mechs,
+               gss_cred_id_t *impersonator_cred_handle)
+{
+    OM_uint32 major = GSS_S_FAILURE, tmp_minor;
+
+    if (keytab_name) {
+        krb5_error_code code;
+        krb5_context context = NULL;
+        krb5_keytab keytab = NULL;
+        krb5_principal keytab_principal = NULL;
+        krb5_ccache ccache = NULL;
+
+        code = krb5_init_context(&context);
+        if (code) {
+            displayStatus("krb5_init_context", major, code);
+            return major;
+        }
+
+        code = krb5_kt_resolve(context, keytab_name, &keytab);
+        if (code) {
+            displayStatus("krb5_kt_resolve", major, code);
+            goto out;
+        }
+
+        code = krb5_cc_default(context, &ccache);
+        if (code) {
+            displayStatus("krb5_cc_default", major, code);
+            goto out;
+        }
+
+        code = krb5_cc_get_principal(context, ccache, &keytab_principal);
+        if (code) {
+            displayStatus("krb5_cc_get_principal", major, code);
+            goto out;
+        }
+
+        major = gss_krb5_import_cred(minor,
+                                     ccache,
+                                     keytab_principal,
+                                     keytab,
+                                     impersonator_cred_handle);
+        if (GSS_ERROR(major)) {
+            displayStatus("gss_krb5_import_cred", major, minor);
+            goto out;
+        }
+
+    out:
+        if (code)
+            *minor = code;
+        krb5_free_principal(context, keytab_principal);
+        krb5_cc_close(context, ccache);
+        krb5_kt_close(context, keytab);
+        krb5_free_context(context);
+     } else {
+        gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
+
+        major = gss_acquire_cred(minor,
+                                 GSS_C_NO_NAME,
+                                 GSS_C_INDEFINITE,
+                                 mechs,
+                                 GSS_C_BOTH,
+                                 impersonator_cred_handle,
+                                 &actual_mechs,
+                                 NULL);
+        if (GSS_ERROR(major)) {
+            displayStatus("gss_acquire_cred", major, minor);
+        }
+        (void) gss_release_oid_set(&tmp_minor, &actual_mechs);
+    }
+
+    return major;
+}
+
 int main(int argc, char *argv[])
 {
     OM_uint32 minor, major;
@@ -338,34 +414,16 @@ int main(int argc, char *argv[])
         target = GSS_C_NO_NAME;
     }
 
-    if (argc > 3) {
-        major = krb5_gss_register_acceptor_identity(argv[3]);
-        if (GSS_ERROR(major)) {
-            displayStatus("krb5_gss_register_acceptor_identity",
-                          major, minor);
-            goto out;
-        }
-    }
-
     mechs.elements = use_spnego ? (gss_OID)&spnego_mech :
                                   (gss_OID)gss_mech_krb5;
     mechs.count = 1;
 
-    /* get default cred */
-    major = gss_acquire_cred(&minor,
-                             GSS_C_NO_NAME,
-                             GSS_C_INDEFINITE,
-                             &mechs,
-                             GSS_C_BOTH,
-                             &impersonator_cred_handle,
-                             &actual_mechs,
-                             NULL);
-    if (GSS_ERROR(major)) {
-        displayStatus("gss_acquire_cred", major, minor);
+    major = getDefaultCred(&minor,
+                           argc > 3 ? argv[3] : NULL,
+                           &mechs,
+                           &impersonator_cred_handle);
+    if (GSS_ERROR(major))
         goto out;
-    }
-
-    (void) gss_release_oid_set(&minor, &actual_mechs);
 
     printf("Protocol transition tests follow\n");
     printf("-----------------------------------\n\n");
-- 
2.6.6

