Make gss-krb5 use cache collection
authorGreg Hudson <ghudson@mit.edu>
Mon, 5 Sep 2011 16:35:40 +0000 (16:35 +0000)
committerGreg Hudson <ghudson@mit.edu>
Mon, 5 Sep 2011 16:35:40 +0000 (16:35 +0000)
For default credentials, defer ccache resolution until we need the
information.  If this happens in init_sec_context when we have the
target principal in hand, use krb5_cc_select() to pick a cache.  If
the target principal is not known, use the default cache.

For credentials with a specified principal, use krb5_cc_cache_match()
to find the cache.  If no cache is found and a password is specified,
create a new cache within the collection to hold the new credentials,
if the default cache type supports a collection.

ticket: 6958

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25160 dc483132-0cff-0310-8789-dd5450dbe970

src/lib/gssapi/krb5/acquire_cred.c
src/lib/gssapi/krb5/gssapiP_krb5.h
src/lib/gssapi/krb5/iakerb.c
src/lib/gssapi/krb5/init_sec_context.c
src/lib/gssapi/krb5/inq_cred.c
src/lib/gssapi/krb5/s4u_gss_glue.c
src/tests/gssapi/Makefile.in
src/tests/gssapi/deps
src/tests/gssapi/t_ccselect.c [new file with mode: 0644]
src/tests/gssapi/t_ccselect.py [new file with mode: 0644]

index 50c8ef4ad2ccdead64ef17a027f4f1dbbc683483..c815b3590a6e3c632cfc089ce0a74ee719820757 100644 (file)
@@ -368,12 +368,25 @@ prep_ccache(krb5_context context, krb5_gss_cred_id_rec *cred,
     krb5_error_code code;
     krb5_principal ccache_princ;
     krb5_data password_data = make_data(password->value, password->length);
+    krb5_boolean eq;
+    const char *cctype;
+    krb5_ccache newcache = NULL;
 
     /* Check the ccache principal or initialize a new cache. */
     code = krb5_cc_get_principal(context, ccache, &ccache_princ);
     if (code == 0) {
-        if (!krb5_principal_compare(context, ccache_princ, desired_princ))
-            return KG_CCACHE_NOMATCH;
+        eq = krb5_principal_compare(context, ccache_princ, desired_princ);
+        krb5_free_principal(context, ccache_princ);
+        if (!eq) {
+            cctype = krb5_cc_get_type(context, ccache);
+            if (krb5_cc_support_switch(context, cctype)) {
+                /* Make a new ccache within the collection. */
+                code = krb5_cc_new_unique(context, cctype, NULL, &newcache);
+                if (code)
+                    return code;
+            } else
+                return KG_CCACHE_NOMATCH;
+        }
     } else if (code == KRB5_FCC_NOFILE) {
         /* Cache file does not exist; create and initialize one. */
         code = krb5_cc_initialize(context, ccache, desired_princ);
@@ -396,7 +409,11 @@ prep_ccache(krb5_context context, krb5_gss_cred_id_rec *cred,
     if (code)
         return code;
 
-    cred->ccache = ccache;
+    if (newcache) {
+        krb5_cc_close(context, ccache);
+        cred->ccache = newcache;
+    } else
+        cred->ccache = ccache;
     return 0;
 }
 
@@ -523,7 +540,13 @@ acquire_init_cred(krb5_context context,
 #elif defined(USE_LEASH)
         code = get_ccache_leash(context, desired_princ, &ccache);
 #else
-        code = 0;
+        code = krb5_cc_cache_match(context, desired_princ, &ccache);
+        if (code == KRB5_CC_NOTFOUND && password != GSS_C_NO_BUFFER) {
+            /* Grab the default ccache for now; if it's not empty, prep_ccache
+             * will create a new one of the default type or error out. */
+            krb5_clear_error_message(context);
+            code = krb5_cc_default(context, &ccache);
+        }
 #endif
     } else
         code = 0;
@@ -531,25 +554,26 @@ acquire_init_cred(krb5_context context,
         *minor_status = code;
         return GSS_S_CRED_UNAVAIL;
     }
-    if (ccache == NULL) {
-        code = krb5int_cc_default(context, &ccache);
+
+    if (ccache != NULL) {
+        if (password != GSS_C_NO_BUFFER && desired_princ != NULL)
+            code = prep_ccache(context, cred, ccache, desired_princ, password);
+        else
+            code = scan_ccache(context, cred, ccache, desired_princ);
         if (code != 0) {
+            krb5_cc_close(context, ccache);
             *minor_status = code;
             return GSS_S_CRED_UNAVAIL;
         }
+        cred->ccache = ccache;
     }
 
-    if (password != GSS_C_NO_BUFFER && desired_princ != NULL)
-        code = prep_ccache(context, cred, ccache, desired_princ, password);
-    else
-        code = scan_ccache(context, cred, ccache, desired_princ);
-    if (code != 0) {
-        krb5_cc_close(context, ccache);
-        *minor_status = code;
-        return GSS_S_CRED_UNAVAIL;
-    }
+    /*
+     * If the caller specified no ccache and no desired principal, leave
+     * cred->ccache and cred->name NULL.  They will be resolved later by
+     * kg_cred_resolve(), possibly using the target principal name.
+     */
 
-    cred->ccache = ccache;
     *minor_status = 0;
     return GSS_S_COMPLETE;
 }
@@ -692,6 +716,67 @@ error_out:
     return ret;
 }
 
+/*
+ * Resolve the name and ccache for an initiator credential if it has not yet
+ * been done.  If specified, use the target name to pick an appropriate ccache
+ * within the collection.  Validates cred_handle and leaves it locked on
+ * success.
+ */
+OM_uint32
+kg_cred_resolve(OM_uint32 *minor_status, krb5_context context,
+                gss_cred_id_t cred_handle, gss_name_t target_name)
+{
+    OM_uint32 maj;
+    krb5_error_code code;
+    krb5_gss_cred_id_t cred = (krb5_gss_cred_id_t)cred_handle;
+    krb5_gss_name_t tname = (krb5_gss_name_t)target_name;
+    krb5_ccache ccache = NULL;
+    krb5_principal client_princ = NULL;
+
+    *minor_status = 0;
+
+    maj = krb5_gss_validate_cred_1(minor_status, cred_handle, context);
+    if (maj != 0)
+        return maj;
+    k5_mutex_assert_locked(&cred->lock);
+
+    if (cred->ccache != NULL || cred->usage == GSS_C_ACCEPT)
+        return GSS_S_COMPLETE;
+
+    /* Pick a credential cache. */
+    if (tname != NULL) {
+        code = krb5_cc_select(context, tname->princ, &ccache, &client_princ);
+        if (code && code != KRB5_CC_NOTFOUND)
+            goto kerr;
+    }
+    if (ccache == NULL) {
+        /*
+         * Ideally we would get credentials for client_princ if it is set.  At
+         * the moment, we just get the default ccache (obtaining credentials if
+         * the platform supports it) and check it against client_princ below.
+         */
+        code = krb5int_cc_default(context, &ccache);
+        if (code)
+            goto kerr;
+    }
+
+    code = scan_ccache(context, cred, ccache, client_princ);
+    if (code) {
+        krb5_cc_close(context, ccache);
+        goto kerr;
+    }
+
+    krb5_free_principal(context, client_princ);
+    return GSS_S_COMPLETE;
+
+kerr:
+    krb5_free_principal(context, client_princ);
+    k5_mutex_unlock(&cred->lock);
+    save_error_info(code, context);
+    *minor_status = code;
+    return GSS_S_CRED_UNAVAIL;
+}
+
 OM_uint32
 gss_krb5int_set_cred_rcache(OM_uint32 *minor_status,
                             gss_cred_id_t *cred_handle,
index f7aab88b82d055b2dad86755f2af921cce3112e5..08155e820f029b860065808c93d71951b7725053 100644 (file)
@@ -478,6 +478,10 @@ krb5_to_gss_cred(krb5_context context,
                  krb5_creds *creds,
                  krb5_gss_cred_id_t *out_cred);
 
+OM_uint32
+kg_cred_resolve(OM_uint32 *minor_status, krb5_context context,
+                gss_cred_id_t cred_handle, gss_name_t target_name);
+
 /** declarations of internal name mechanism functions **/
 
 OM_uint32 KRB5_CALLCONV krb5_gss_acquire_cred
index e0aede9fb2d94e928909f2410d6a4558342d57c5..84a96ac1bf04ac11416a7b6ae587693b4b3a52c4 100644 (file)
@@ -914,16 +914,7 @@ iakerb_gss_init_sec_context(OM_uint32 *minor_status,
 
     kname = (krb5_gss_name_t)target_name;
 
-    if (claimant_cred_handle != GSS_C_NO_CREDENTIAL) {
-        major_status = krb5_gss_validate_cred_1(minor_status,
-                                                claimant_cred_handle,
-                                                ctx->k5c);
-        if (GSS_ERROR(major_status))
-            goto cleanup;
-
-        cred_locked = TRUE;
-        kcred = (krb5_gss_cred_id_t)claimant_cred_handle;
-    } else {
+    if (claimant_cred_handle == GSS_C_NO_CREDENTIAL) {
         major_status = krb5_gss_acquire_cred(minor_status, NULL,
                                              GSS_C_INDEFINITE,
                                              GSS_C_NULL_OID_SET,
@@ -931,9 +922,16 @@ iakerb_gss_init_sec_context(OM_uint32 *minor_status,
                                              &defcred, NULL, NULL);
         if (GSS_ERROR(major_status))
             goto cleanup;
-        kcred = (krb5_gss_cred_id_t)defcred;
+        claimant_cred_handle = defcred;
     }
 
+    major_status = kg_cred_resolve(minor_status, ctx->k5c,
+                                   claimant_cred_handle, target_name);
+    if (GSS_ERROR(major_status))
+        goto cleanup;
+    cred_locked = TRUE;
+    kcred = (krb5_gss_cred_id_t)claimant_cred_handle;
+
     major_status = GSS_S_FAILURE;
 
     if (initialContextToken) {
index 0133bf084b559a118ae81f88b0c0419fc7f1ef73..d62822e2d4bb17c038937026d684a3b25e0ae161 100644 (file)
@@ -930,6 +930,7 @@ krb5_gss_init_sec_context_ext(
     krb5_gss_ctx_ext_t exts)
 {
     krb5_context context;
+    gss_cred_id_t defcred = GSS_C_NO_CREDENTIAL;
     krb5_gss_cred_id_t cred;
     krb5_error_code kerr;
     OM_uint32 major_status;
@@ -961,30 +962,25 @@ krb5_gss_init_sec_context_ext(
     /* verify the credential, or use the default */
     /*SUPPRESS 29*/
     if (claimant_cred_handle == GSS_C_NO_CREDENTIAL) {
-        major_status = kg_get_defcred(minor_status, (gss_cred_id_t *)&cred);
+        major_status = kg_get_defcred(minor_status, &defcred);
         if (major_status && GSS_ERROR(major_status)) {
             if (*context_handle == GSS_C_NO_CONTEXT)
                 krb5_free_context(context);
             return(major_status);
         }
-    } else {
-        major_status = krb5_gss_validate_cred(minor_status, claimant_cred_handle);
-        if (GSS_ERROR(major_status)) {
-            save_error_info(*minor_status, context);
-            if (*context_handle == GSS_C_NO_CONTEXT)
-                krb5_free_context(context);
-            return(major_status);
-        }
-        cred = (krb5_gss_cred_id_t) claimant_cred_handle;
+        claimant_cred_handle = defcred;
     }
-    kerr = k5_mutex_lock(&cred->lock);
-    if (kerr) {
-        if (claimant_cred_handle == GSS_C_NO_CREDENTIAL)
-            krb5_gss_release_cred(minor_status, (gss_cred_id_t *)&cred);
-        krb5_free_context(context);
-        *minor_status = kerr;
-        return GSS_S_FAILURE;
+
+    major_status = kg_cred_resolve(minor_status, context, claimant_cred_handle,
+                                   target_name);
+    if (GSS_ERROR(major_status)) {
+        save_error_info(*minor_status, context);
+        krb5_gss_release_cred(&tmp_min_stat, &defcred);
+        if (*context_handle == GSS_C_NO_CONTEXT)
+            krb5_free_context(context);
+        return(major_status);
     }
+    cred = (krb5_gss_cred_id_t)claimant_cred_handle;
 
     /* verify the mech_type */
 
@@ -998,8 +994,7 @@ krb5_gss_init_sec_context_ext(
         mech_type = (gss_OID) gss_mech_iakerb;
     } else {
         k5_mutex_unlock(&cred->lock);
-        if (claimant_cred_handle == GSS_C_NO_CREDENTIAL)
-            krb5_gss_release_cred(minor_status, (gss_cred_id_t *)&cred);
+        krb5_gss_release_cred(minor_status, &defcred);
         *minor_status = 0;
         if (*context_handle == GSS_C_NO_CONTEXT)
             krb5_free_context(context);
@@ -1036,9 +1031,7 @@ krb5_gss_init_sec_context_ext(
            too.  */
     }
 
-    if (claimant_cred_handle == GSS_C_NO_CREDENTIAL)
-        krb5_gss_release_cred(&tmp_min_stat, (gss_cred_id_t *)&cred);
-
+    krb5_gss_release_cred(&tmp_min_stat, &defcred);
     return(major_status);
 }
 
index cc24e7a90388d14384894cde7c55696d737c5153..f523a545cfe4db588d81823b8f3b0469c358d216 100644 (file)
@@ -83,14 +83,14 @@ krb5_gss_inquire_cred(minor_status, cred_handle, name, lifetime_ret,
     gss_OID_set *mechanisms;
 {
     krb5_context context;
-    krb5_gss_cred_id_t cred;
+    krb5_gss_cred_id_t defcred = GSS_C_NO_CREDENTIAL, cred;
     krb5_error_code code;
     krb5_timestamp now;
     krb5_deltat lifetime;
     krb5_gss_name_t ret_name;
     krb5_principal princ;
     gss_OID_set mechs;
-    OM_uint32 ret;
+    OM_uint32 major, tmpmin, ret;
 
     ret = GSS_S_FAILURE;
     ret_name = NULL;
@@ -104,39 +104,31 @@ krb5_gss_inquire_cred(minor_status, cred_handle, name, lifetime_ret,
     if (name) *name = NULL;
     if (mechanisms) *mechanisms = NULL;
 
+    if ((code = krb5_timeofday(context, &now))) {
+        *minor_status = code;
+        ret = GSS_S_FAILURE;
+        goto fail;
+    }
+
     /* check for default credential */
     /*SUPPRESS 29*/
     if (cred_handle == GSS_C_NO_CREDENTIAL) {
-        OM_uint32 major;
-
-        if ((major = kg_get_defcred(minor_status, (gss_cred_id_t *)&cred)) &&
-            GSS_ERROR(major)) {
-            krb5_free_context(context);
-            return(major);
-        }
-    } else {
-        OM_uint32 major;
-
-        major = krb5_gss_validate_cred(minor_status, cred_handle);
+        major = kg_get_defcred(minor_status, &defcred);
         if (GSS_ERROR(major)) {
             krb5_free_context(context);
             return(major);
         }
-        cred = (krb5_gss_cred_id_t) cred_handle;
+        cred_handle = defcred;
     }
 
-    if ((code = krb5_timeofday(context, &now))) {
-        *minor_status = code;
-        ret = GSS_S_FAILURE;
-        goto fail;
+    major = krb5_gss_validate_cred(minor_status, cred_handle);
+    if (GSS_ERROR(major)) {
+        krb5_gss_release_cred(minor_status, &defcred);
+        krb5_free_context(context);
+        return(major);
     }
+    cred = (krb5_gss_cred_id_t)cred_handle;
 
-    code = k5_mutex_lock(&cred->lock);
-    if (code != 0) {
-        *minor_status = code;
-        ret = GSS_S_FAILURE;
-        goto fail;
-    }
     if (cred->tgt_expire > 0) {
         if ((lifetime = cred->tgt_expire - now) < 0)
             lifetime = 0;
@@ -161,7 +153,6 @@ krb5_gss_inquire_cred(minor_status, cred_handle, name, lifetime_ret,
                 code = 0;
         }
         if (code) {
-            k5_mutex_unlock(&cred->lock);
             *minor_status = code;
             save_error_info(*minor_status, context);
             ret = GSS_S_FAILURE;
@@ -178,7 +169,6 @@ krb5_gss_inquire_cred(minor_status, cred_handle, name, lifetime_ret,
             GSS_ERROR(ret = generic_gss_add_oid_set_member(minor_status,
                                                            gss_mech_krb5,
                                                            &mechs))) {
-            k5_mutex_unlock(&cred->lock);
             if (ret_name)
                 kg_release_name(context, &ret_name);
             /* *minor_status set above */
@@ -210,11 +200,8 @@ krb5_gss_inquire_cred(minor_status, cred_handle, name, lifetime_ret,
     *minor_status = 0;
     return((lifetime == 0)?GSS_S_CREDENTIALS_EXPIRED:GSS_S_COMPLETE);
 fail:
-    if (cred_handle == GSS_C_NO_CREDENTIAL) {
-        OM_uint32 tmp_min_stat;
-
-        krb5_gss_release_cred(&tmp_min_stat, (gss_cred_id_t *)&cred);
-    }
+    k5_mutex_unlock(&cred->lock);
+    krb5_gss_release_cred(&tmpmin, &defcred);
     krb5_free_context(context);
     return ret;
 }
index 70fbfbce6a1d4d8919b278d673882f625760cad0..4ac2ce301593ee84830d3fd754aad01a310a2c92 100644 (file)
@@ -144,9 +144,8 @@ krb5_gss_acquire_cred_impersonate_name(OM_uint32 *minor_status,
         return GSS_S_FAILURE;
     }
 
-    major_status = krb5_gss_validate_cred_1(minor_status,
-                                            impersonator_cred_handle,
-                                            context);
+    major_status = kg_cred_resolve(minor_status, context,
+                                   impersonator_cred_handle, NULL);
     if (GSS_ERROR(major_status)) {
         krb5_free_context(context);
         return major_status;
index 2bf390106400ed3e966bc54a36a015f2901d1181..5b6765b59d60fa07d75af89420a88aa10bcca258 100644 (file)
@@ -4,21 +4,24 @@ DEFINES = -DUSE_AUTOCONF_H
 PROG_LIBPATH=-L$(TOPLIBD)
 PROG_RPATH=$(KRB5_LIBDIR)
 
-SRCS=  $(srcdir)/t_accname.c $(srcdir)/t_imp_cred.c $(srcdir)/t_imp_name.c \
-       $(srcdir)/t_s4u.c $(srcdir)/t_namingexts.c $(srcdir)/t_gssexts.c \
-       $(srcdir)/t_saslname.c
+SRCS=  $(srcdir)/t_accname.c $(srcdir)/t_ccselect.c $(srcdir)/t_imp_cred.c \
+       $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c $(srcdir)/t_namingexts.c \
+       $(srcdir)/t_gssexts.c $(srcdir)/t_saslname.c
 
-OBJS=  t_accname.o t_imp_cred.o t_imp_name.o t_s4u.o t_namingexts.o \
-       t_gssexts.o t_spnego.o t_saslname.o
+OBJS=  t_accname.o t_ccselect.o t_imp_cred.o t_imp_name.o t_s4u.o \
+       t_namingexts.o t_gssexts.o t_spnego.o t_saslname.o
 
-all:: t_accname t_imp_cred t_imp_name t_s4u t_namingexts t_gssexts t_spnego \
-       t_saslname
+all:: t_accname t_ccselect t_imp_cred t_imp_name t_s4u t_namingexts t_gssexts \
+       t_spnego t_saslname
 
-check-pytests:: t_accname t_imp_cred t_spnego
+check-pytests:: t_accname t_ccselect t_imp_cred t_spnego
        $(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS)
+       $(RUNPYTEST) $(srcdir)/t_ccselect.py $(PYTESTFLAGS)
 
 t_accname: t_accname.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
        $(CC_LINK) -o t_accname t_accname.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
+t_ccselect: t_ccselect.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+       $(CC_LINK) -o t_ccselect t_ccselect.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
 t_imp_cred: t_imp_cred.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
        $(CC_LINK) -o t_imp_cred t_imp_cred.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
 t_imp_name: t_imp_name.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
index 8838e240d76fea0a1e8510eb0228cc57318eface..a87f6e0e0a8a2e8bc6f223707a3cfa14a029decd 100644 (file)
@@ -5,6 +5,10 @@ $(OUTPRE)t_accname.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \
   $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_krb5.h \
   $(BUILDTOP)/include/krb5/krb5.h $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h \
   t_accname.c
+$(OUTPRE)t_ccselect.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \
+  $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_krb5.h \
+  $(BUILDTOP)/include/krb5/krb5.h $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h \
+  t_ccselect.c
 $(OUTPRE)t_imp_cred.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
   $(BUILDTOP)/include/gssapi/gssapi.h $(BUILDTOP)/include/gssapi/gssapi_ext.h \
   $(BUILDTOP)/include/gssapi/gssapi_krb5.h $(BUILDTOP)/include/krb5/krb5.h \
diff --git a/src/tests/gssapi/t_ccselect.c b/src/tests/gssapi/t_ccselect.c
new file mode 100644 (file)
index 0000000..8acf045
--- /dev/null
@@ -0,0 +1,170 @@
+/* -*- mode: c; indent-tabs-mode: nil -*- */
+/* tests/gssapi/t_ccselect.c - Test program for GSSAPI cred selection */
+/*
+ * Copyright 2011 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gssapi/gssapi_krb5.h>
+
+/*
+ * Test program for client credential selection, intended to be run from a
+ * Python test script.  Performs a one-token
+ * gss_init_sec_context/gss_accept_sec_context exchange, optionally with a
+ * specified principal as the initiator name, a specified principal name as
+ * target name, the default acceptor cred.  If the exchange is successful,
+ * prints the initiator name as seen by the acceptor.  If any call is
+ * unsuccessful, displays an error message.  Exits with status 0 if all
+ * operations are successful, or 1 if not.
+ *
+ * Usage: ./t_ccselect [targetprinc|gss:service@host] [initiatorprinc|-]
+ */
+
+static void
+display_status_1(const char *m, OM_uint32 code, int type)
+{
+    OM_uint32 maj_stat, min_stat;
+    gss_buffer_desc msg;
+    OM_uint32 msg_ctx;
+
+    msg_ctx = 0;
+    while (1) {
+        maj_stat = gss_display_status(&min_stat, code,
+                                      type, GSS_C_NULL_OID,
+                                      &msg_ctx, &msg);
+        fprintf(stderr, "%s: %s\n", m, (char *)msg.value);
+        (void) gss_release_buffer(&min_stat, &msg);
+
+        if (!msg_ctx)
+            break;
+     }
+}
+
+static void
+gsserr(const char *msg, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+     display_status_1(msg, maj_stat, GSS_C_GSS_CODE);
+     display_status_1(msg, min_stat, GSS_C_MECH_CODE);
+     exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+    OM_uint32 minor, major;
+    gss_cred_id_t initiator_cred;
+    gss_buffer_desc buf;
+    gss_name_t target_name, initiator_name = GSS_C_NO_NAME;
+    gss_name_t real_initiator_name;
+    gss_buffer_desc token, tmp, namebuf;
+    gss_ctx_id_t initiator_context = GSS_C_NO_CONTEXT;
+    gss_ctx_id_t acceptor_context = GSS_C_NO_CONTEXT;
+
+    if (argc < 2 || argc > 3) {
+        fprintf(stderr, "Usage: %s targetprinc [initiatorprinc|-]\n", argv[0]);
+        return 1;
+    }
+
+    /* Import the target name. */
+    if (strncmp(argv[1], "gss:", 4) == 0) {
+        /* Import as host-based service. */
+        buf.value = argv[1] + 4;
+        buf.length = strlen((char *)buf.value);
+        major = gss_import_name(&minor, &buf,
+                                (gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
+                                &target_name);
+    } else {
+        /* Import as krb5 principal name. */
+        buf.value = argv[1];
+        buf.length = strlen((char *)buf.value);
+        major = gss_import_name(&minor, &buf,
+                                (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME,
+                                &target_name);
+    }
+    if (GSS_ERROR(major))
+        gsserr("gss_import_name(target_name)", major, minor);
+
+    /* Import the initiator name as a krb5 principal and get creds, maybe. */
+    if (argc >= 3) {
+        if (strcmp(argv[2], "-") != 0) {
+            buf.value = argv[2];
+            buf.length = strlen((char *)buf.value);
+            major = gss_import_name(&minor, &buf,
+                                    (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME,
+                                    &initiator_name);
+            if (GSS_ERROR(major))
+                gsserr("gss_import_name(initiator_name)", major, minor);
+        }
+
+        /* Get acceptor cred. */
+        major = gss_acquire_cred(&minor, initiator_name, GSS_C_INDEFINITE,
+                                 GSS_C_NO_OID_SET, GSS_C_INITIATE,
+                                 &initiator_cred, NULL, NULL);
+        if (GSS_ERROR(major))
+            gsserr("gss_acquire_cred", major, minor);
+    }
+
+
+    /* Create krb5 initiator context and get the first token. */
+    token.value = NULL;
+    token.length = 0;
+    major = gss_init_sec_context(&minor, initiator_cred, &initiator_context,
+                                 target_name, (gss_OID)gss_mech_krb5,
+                                 GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
+                                 GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS,
+                                 GSS_C_NO_BUFFER, NULL, &token, NULL, NULL);
+    if (GSS_ERROR(major))
+        gsserr("gss_init_sec_context", major, minor);
+
+    /* Pass the token to gss_accept_sec_context. */
+    tmp.value = NULL;
+    tmp.length = 0;
+    major = gss_accept_sec_context(&minor, &acceptor_context,
+                                   GSS_C_NO_CREDENTIAL, &token,
+                                   GSS_C_NO_CHANNEL_BINDINGS,
+                                   &real_initiator_name, NULL, &tmp,
+                                   NULL, NULL, NULL);
+    if (major != GSS_S_COMPLETE)
+        gsserr("gss_accept_sec_context", major, minor);
+
+    namebuf.value = NULL;
+    namebuf.length = 0;
+    major = gss_display_name(&minor, real_initiator_name, &namebuf, NULL);
+    if (GSS_ERROR(major))
+        gsserr("gss_display_name(initiator)", major, minor);
+    printf("%.*s\n", (int)namebuf.length, (char *)namebuf.value);
+
+    (void)gss_release_name(&minor, &target_name);
+    (void)gss_release_name(&minor, &initiator_name);
+    (void)gss_release_name(&minor, &real_initiator_name);
+    (void)gss_release_cred(&minor, &initiator_cred);
+    (void)gss_delete_sec_context(&minor, &initiator_context, NULL);
+    (void)gss_delete_sec_context(&minor, &acceptor_context, NULL);
+    (void)gss_release_buffer(&minor, &token);
+    (void)gss_release_buffer(&minor, &tmp);
+    (void)gss_release_buffer(&minor, &namebuf);
+    return 0;
+}
diff --git a/src/tests/gssapi/t_ccselect.py b/src/tests/gssapi/t_ccselect.py
new file mode 100644 (file)
index 0000000..2722873
--- /dev/null
@@ -0,0 +1,122 @@
+# Copyright (C) 2011 by the Massachusetts Institute of Technology.
+# All rights reserved.
+
+# Export of this software from the United States of America may
+#   require a specific license from the United States Government.
+#   It is the responsibility of any person or organization contemplating
+#   export to obtain such a license before exporting.
+#
+# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+# distribute this software and its documentation for any purpose and
+# without fee is hereby granted, provided that the above copyright
+# notice appear in all copies and that both that copyright notice and
+# this permission notice appear in supporting documentation, and that
+# the name of M.I.T. not be used in advertising or publicity pertaining
+# to distribution of the software without specific, written prior
+# permission.  Furthermore if you modify this software you must label
+# your software as modified software and not distribute it in such a
+# fashion that it might be confused with the original M.I.T. software.
+# M.I.T. makes no representations about the suitability of
+# this software for any purpose.  It is provided "as is" without express
+# or implied warranty.
+
+#!/usr/bin/python
+from k5test import *
+
+# Create two independent realms (no cross-realm TGTs).
+r1 = K5Realm(start_kadmind=False, create_user=False)
+r2 = K5Realm(start_kadmind=False, create_user=False, realm='KRBTEST2.COM',
+             testdir=os.path.join(r1.testdir, 'r2'), portbase=62000)
+
+# gsserver specifies the target as a GSS name.  The resulting
+# principal will have the host-based type, but the realm won't be
+# known before the client cache is selected (since k5test realms have
+# no domain-realm mapping by default).
+gssserver = 'gss:host@' + hostname
+
+# refserver specifies the target as a principal in the referral realm.
+# The principal won't be treated as a host principal by the
+# .k5identity rules since it has unknown type.
+refserver = 'host/' + hostname + '@'
+
+# Make each realm's keytab contain entries for both realm's servers.
+#r1.run_as_client(['/bin/sh', '-c', '(echo rkt %s; echo wkt %s) | %s' %
+#                  (r2.keytab, r1.keytab, ktutil)])
+#r1.run_as_client(['/bin/sh', '-c', '(echo rkt %s; echo wkt %s) | %s' %
+#                  (r1.keytab, r2.keytab, ktutil)])
+
+# Make a directory collection and use it for client commands in both realms.
+ccdir = os.path.join(r1.testdir, 'cc')
+ccname = 'DIR:' + ccdir
+os.mkdir(ccdir)
+r1.env_client['KRB5CCNAME'] = ccname
+r2.env_client['KRB5CCNAME'] = ccname
+
+# Use .k5identity from testdir and not from the tester's homedir.
+r1.env_client['HOME'] = r1.testdir
+r2.env_client['HOME'] = r1.testdir
+
+# Create two users in r1 and one in r2.
+alice='alice@KRBTEST.COM'
+bob='bob@KRBTEST.COM'
+zaphod='zaphod@KRBTEST2.COM'
+r1.addprinc(alice, password('alice'))
+r1.addprinc(bob, password('bob'))
+r2.addprinc(zaphod, password('zaphod'))
+
+# Get tickets for one user in each realm (zaphod will be primary).
+r1.kinit(alice, password('alice'))
+r2.kinit(zaphod, password('zaphod'))
+
+# Check that we can find a cache for a specified client principal.
+output = r1.run_as_client(['./t_ccselect', r1.host_princ, alice])
+if output != (alice + '\n'):
+    fail('alice not chosen when specified')
+output = r2.run_as_client(['./t_ccselect', r2.host_princ, zaphod])
+if output != (zaphod + '\n'):
+    fail('zaphod not chosen when specified')
+
+# Check that we can guess a cache based on the service realm.
+output = r1.run_as_client(['./t_ccselect', r1.host_princ])
+if output != (alice + '\n'):
+    fail('alice not chosen as default initiator cred for server in r1')
+output = r1.run_as_client(['./t_ccselect', r1.host_princ, '-'])
+if output != (alice + '\n'):
+    fail('alice not chosen as default initiator name for server in r1')
+output = r2.run_as_client(['./t_ccselect', r2.host_princ])
+if output != (zaphod + '\n'):
+    fail('zaphod not chosen as default initiator cred for server in r1')
+output = r2.run_as_client(['./t_ccselect', r2.host_princ, '-'])
+if output != (zaphod + '\n'):
+    fail('zaphod not chosen as default initiator name for server in r1')
+
+# Check that primary cache is used if server realm is unknown.
+output = r2.run_as_client(['./t_ccselect', gssserver])
+if output != (zaphod + '\n'):
+    fail('zaphod not chosen via primary cache for unknown server realm')
+r1.run_as_client(['./t_ccselect', gssserver], expected_code=1)
+
+# Get a second cred in r1 (bob will be primary).
+r1.kinit(bob, password('bob'))
+
+# Try some cache selections using .k5identity.
+k5id = open(os.path.join(r1.testdir, '.k5identity'), 'w')
+k5id.write('%s realm=%s\n' % (alice, r1.realm))
+k5id.write('%s service=ho*t host=%s\n' % (zaphod, hostname))
+k5id.write('noprinc service=bogus')
+k5id.close()
+output = r1.run_as_client(['./t_ccselect', r1.host_princ])
+if output != (alice + '\n'):
+    fail('alice not chosen via .k5identity realm line.')
+output = r2.run_as_client(['./t_ccselect', gssserver])
+if output != (zaphod + '\n'):
+    fail('zaphod not chosen via .k5identity service/host line.')
+output = r1.run_as_client(['./t_ccselect', refserver])
+if output != (bob + '\n'):
+    fail('bob not chosen via primary cache when no .k5identity line matches.')
+output = r1.run_as_client(['./t_ccselect', 'gss:bogus@' + hostname],
+                          expected_code=1)
+if 'does not match desired' not in output:
+    fail('Expected error not seen when k5identity selects bad principal.')
+
+success('GSSAPI credential selection tests.')