Blame SOURCES/Support-host-based-GSS-initiator-names.patch

cb4cef
From e33835c4b6c6ce71757e9f659db03afa4bfd9a9a Mon Sep 17 00:00:00 2001
cb4cef
From: Greg Hudson <ghudson@mit.edu>
cb4cef
Date: Fri, 15 Jan 2021 13:51:34 -0500
cb4cef
Subject: [PATCH] Support host-based GSS initiator names
cb4cef
cb4cef
When checking if we can get initial credentials in the GSS krb5 mech,
cb4cef
use krb5_kt_have_match() to support fallback iteration.  When scanning
cb4cef
the ccache or getting initial credentials, rewrite cred->name->princ
cb4cef
to the canonical client name.  When a name check is necessary (such as
cb4cef
when the caller specifies both a name and ccache), use a new internal
cb4cef
API k5_sname_compare() to support fallback iteration.  Add fallback
cb4cef
iteration to krb5_cc_cache_match() to allow host-based names to be
cb4cef
canonicalized against the cache collection.
cb4cef
cb4cef
Create and store the matching principal for acceptor names in
cb4cef
acquire_accept_cred() so that it isn't affected by changes in
cb4cef
cred->name->princ during acquire_init_cred().
cb4cef
cb4cef
ticket: 8978 (new)
cb4cef
(cherry picked from commit c374ab40dd059a5938ffc0440d87457ac5da3a46)
cb4cef
---
cb4cef
 src/include/k5-int.h                     |  9 +++
cb4cef
 src/include/k5-trace.h                   |  3 +
cb4cef
 src/lib/gssapi/krb5/accept_sec_context.c | 15 +---
cb4cef
 src/lib/gssapi/krb5/acquire_cred.c       | 89 ++++++++++++++----------
cb4cef
 src/lib/gssapi/krb5/gssapiP_krb5.h       |  1 +
cb4cef
 src/lib/gssapi/krb5/rel_cred.c           |  1 +
cb4cef
 src/lib/krb5/ccache/cccursor.c           | 57 +++++++++++----
cb4cef
 src/lib/krb5/libkrb5.exports             |  1 +
cb4cef
 src/lib/krb5/os/sn2princ.c               | 23 +++++-
cb4cef
 src/lib/krb5_32.def                      |  1 +
cb4cef
 src/tests/gssapi/t_client_keytab.py      | 44 ++++++++++++
cb4cef
 src/tests/gssapi/t_credstore.py          | 32 +++++++++
cb4cef
 12 files changed, 214 insertions(+), 62 deletions(-)
cb4cef
cb4cef
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
cb4cef
index efb523689..46f2ce2d3 100644
cb4cef
--- a/src/include/k5-int.h
cb4cef
+++ b/src/include/k5-int.h
cb4cef
@@ -2411,4 +2411,13 @@ void k5_change_error_message_code(krb5_context ctx, krb5_error_code oldcode,
cb4cef
 #define k5_prependmsg krb5_prepend_error_message
cb4cef
 #define k5_wrapmsg krb5_wrap_error_message
cb4cef
 
cb4cef
+/*
cb4cef
+ * Like krb5_principal_compare(), but with canonicalization of sname if
cb4cef
+ * fallback is enabled.  This function should be avoided if multiple matches
cb4cef
+ * are required, since repeated canonicalization is inefficient.
cb4cef
+ */
cb4cef
+krb5_boolean
cb4cef
+k5_sname_compare(krb5_context context, krb5_const_principal sname,
cb4cef
+                 krb5_const_principal princ);
cb4cef
+
cb4cef
 #endif /* _KRB5_INT_H */
cb4cef
diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h
cb4cef
index b3e039dc8..79b5a7a85 100644
cb4cef
--- a/src/include/k5-trace.h
cb4cef
+++ b/src/include/k5-trace.h
cb4cef
@@ -105,6 +105,9 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
cb4cef
 
cb4cef
 #endif /* DISABLE_TRACING */
cb4cef
 
cb4cef
+#define TRACE_CC_CACHE_MATCH(c, princ, ret)                             \
cb4cef
+    TRACE(c, "Matching {princ} in collection with result: {kerr}",      \
cb4cef
+          princ, ret)
cb4cef
 #define TRACE_CC_DESTROY(c, cache)                      \
cb4cef
     TRACE(c, "Destroying ccache {ccache}", cache)
cb4cef
 #define TRACE_CC_GEN_NEW(c, cache)                                      \
cb4cef
diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c
cb4cef
index fcf2c2152..a1d7e0d96 100644
cb4cef
--- a/src/lib/gssapi/krb5/accept_sec_context.c
cb4cef
+++ b/src/lib/gssapi/krb5/accept_sec_context.c
cb4cef
@@ -683,7 +683,6 @@ kg_accept_krb5(minor_status, context_handle,
cb4cef
     krb5_flags ap_req_options = 0;
cb4cef
     krb5_enctype negotiated_etype;
cb4cef
     krb5_authdata_context ad_context = NULL;
cb4cef
-    krb5_principal accprinc = NULL;
cb4cef
     krb5_ap_req *request = NULL;
cb4cef
 
cb4cef
     code = krb5int_accessor (&kaccess, KRB5INT_ACCESS_VERSION);
cb4cef
@@ -849,17 +848,9 @@ kg_accept_krb5(minor_status, context_handle,
cb4cef
         }
cb4cef
     }
cb4cef
 
cb4cef
-    if (!cred->default_identity) {
cb4cef
-        if ((code = kg_acceptor_princ(context, cred->name, &accprinc))) {
cb4cef
-            major_status = GSS_S_FAILURE;
cb4cef
-            goto fail;
cb4cef
-        }
cb4cef
-    }
cb4cef
-
cb4cef
-    code = krb5_rd_req_decoded(context, &auth_context, request, accprinc,
cb4cef
-                               cred->keytab, &ap_req_options, NULL);
cb4cef
-
cb4cef
-    krb5_free_principal(context, accprinc);
cb4cef
+    code = krb5_rd_req_decoded(context, &auth_context, request,
cb4cef
+                               cred->acceptor_mprinc, cred->keytab,
cb4cef
+                               &ap_req_options, NULL);
cb4cef
     if (code) {
cb4cef
         major_status = GSS_S_FAILURE;
cb4cef
         goto fail;
cb4cef
diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c
cb4cef
index 632ee7def..e226a0269 100644
cb4cef
--- a/src/lib/gssapi/krb5/acquire_cred.c
cb4cef
+++ b/src/lib/gssapi/krb5/acquire_cred.c
cb4cef
@@ -123,11 +123,11 @@ gss_krb5int_register_acceptor_identity(OM_uint32 *minor_status,
cb4cef
 /* Try to verify that keytab contains at least one entry for name.  Return 0 if
cb4cef
  * it does, KRB5_KT_NOTFOUND if it doesn't, or another error as appropriate. */
cb4cef
 static krb5_error_code
cb4cef
-check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name)
cb4cef
+check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name,
cb4cef
+             krb5_principal mprinc)
cb4cef
 {
cb4cef
     krb5_error_code code;
cb4cef
     krb5_keytab_entry ent;
cb4cef
-    krb5_principal accprinc = NULL;
cb4cef
     char *princname;
cb4cef
 
cb4cef
     if (name->service == NULL) {
cb4cef
@@ -141,21 +141,15 @@ check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name)
cb4cef
     if (kt->ops->start_seq_get == NULL)
cb4cef
         return 0;
cb4cef
 
cb4cef
-    /* Get the partial principal for the acceptor name. */
cb4cef
-    code = kg_acceptor_princ(context, name, &accprinc);
cb4cef
-    if (code)
cb4cef
-        return code;
cb4cef
-
cb4cef
-    /* Scan the keytab for host-based entries matching accprinc. */
cb4cef
-    code = k5_kt_have_match(context, kt, accprinc);
cb4cef
+    /* Scan the keytab for host-based entries matching mprinc. */
cb4cef
+    code = k5_kt_have_match(context, kt, mprinc);
cb4cef
     if (code == KRB5_KT_NOTFOUND) {
cb4cef
-        if (krb5_unparse_name(context, accprinc, &princname) == 0) {
cb4cef
+        if (krb5_unparse_name(context, mprinc, &princname) == 0) {
cb4cef
             k5_setmsg(context, code, _("No key table entry found matching %s"),
cb4cef
                       princname);
cb4cef
             free(princname);
cb4cef
         }
cb4cef
     }
cb4cef
-    krb5_free_principal(context, accprinc);
cb4cef
     return code;
cb4cef
 }
cb4cef
 
cb4cef
@@ -202,8 +196,14 @@ acquire_accept_cred(krb5_context context, OM_uint32 *minor_status,
cb4cef
     }
cb4cef
 
cb4cef
     if (cred->name != NULL) {
cb4cef
+        code = kg_acceptor_princ(context, cred->name, &cred->acceptor_mprinc);
cb4cef
+        if (code) {
cb4cef
+            major = GSS_S_FAILURE;
cb4cef
+            goto cleanup;
cb4cef
+        }
cb4cef
+
cb4cef
         /* Make sure we have keys matching the desired name in the keytab. */
cb4cef
-        code = check_keytab(context, kt, cred->name);
cb4cef
+        code = check_keytab(context, kt, cred->name, cred->acceptor_mprinc);
cb4cef
         if (code) {
cb4cef
             if (code == KRB5_KT_NOTFOUND) {
cb4cef
                 k5_change_error_message_code(context, code, KG_KEYTAB_NOMATCH);
cb4cef
@@ -324,7 +324,6 @@ static krb5_boolean
cb4cef
 can_get_initial_creds(krb5_context context, krb5_gss_cred_id_rec *cred)
cb4cef
 {
cb4cef
     krb5_error_code code;
cb4cef
-    krb5_keytab_entry entry;
cb4cef
 
cb4cef
     if (cred->password != NULL)
cb4cef
         return TRUE;
cb4cef
@@ -336,20 +335,21 @@ can_get_initial_creds(krb5_context context, krb5_gss_cred_id_rec *cred)
cb4cef
     if (cred->name == NULL)
cb4cef
         return !krb5_kt_have_content(context, cred->client_keytab);
cb4cef
 
cb4cef
-    /* Check if we have a keytab key for the client principal. */
cb4cef
-    code = krb5_kt_get_entry(context, cred->client_keytab, cred->name->princ,
cb4cef
-                             0, 0, &entry);
cb4cef
-    if (code) {
cb4cef
-        krb5_clear_error_message(context);
cb4cef
-        return FALSE;
cb4cef
-    }
cb4cef
-    krb5_free_keytab_entry_contents(context, &entry);
cb4cef
-    return TRUE;
cb4cef
+    /*
cb4cef
+     * Check if we have a keytab key for the client principal.  This is a bit
cb4cef
+     * more permissive than we really want because krb5_kt_have_match()
cb4cef
+     * supports wildcarding and obeys ignore_acceptor_hostname, but that should
cb4cef
+     * generally be harmless.
cb4cef
+     */
cb4cef
+    code = k5_kt_have_match(context, cred->client_keytab, cred->name->princ);
cb4cef
+    return code == 0;
cb4cef
 }
cb4cef
 
cb4cef
-/* Scan cred->ccache for name, expiry time, impersonator, refresh time. */
cb4cef
+/* Scan cred->ccache for name, expiry time, impersonator, refresh time.  If
cb4cef
+ * check_name is true, verify the cache name against the credential name. */
cb4cef
 static krb5_error_code
cb4cef
-scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred)
cb4cef
+scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred,
cb4cef
+            krb5_boolean check_name)
cb4cef
 {
cb4cef
     krb5_error_code code;
cb4cef
     krb5_ccache ccache = cred->ccache;
cb4cef
@@ -365,23 +365,31 @@ scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred)
cb4cef
     if (code)
cb4cef
         return code;
cb4cef
 
cb4cef
-    /* Credentials cache principal must match the initiator name. */
cb4cef
     code = krb5_cc_get_principal(context, ccache, &ccache_princ);
cb4cef
     if (code != 0)
cb4cef
         goto cleanup;
cb4cef
-    if (cred->name != NULL &&
cb4cef
-        !krb5_principal_compare(context, ccache_princ, cred->name->princ)) {
cb4cef
-        code = KG_CCACHE_NOMATCH;
cb4cef
-        goto cleanup;
cb4cef
-    }
cb4cef
 
cb4cef
-    /* Save the ccache principal as the credential name if not already set. */
cb4cef
-    if (!cred->name) {
cb4cef
+    if (cred->name == NULL) {
cb4cef
+        /* Save the ccache principal as the credential name. */
cb4cef
         code = kg_init_name(context, ccache_princ, NULL, NULL, NULL,
cb4cef
                             KG_INIT_NAME_NO_COPY, &cred->name);
cb4cef
         if (code)
cb4cef
             goto cleanup;
cb4cef
         ccache_princ = NULL;
cb4cef
+    } else {
cb4cef
+        /* Check against the desired name if needed. */
cb4cef
+        if (check_name) {
cb4cef
+            if (!k5_sname_compare(context, cred->name->princ, ccache_princ)) {
cb4cef
+                code = KG_CCACHE_NOMATCH;
cb4cef
+                goto cleanup;
cb4cef
+            }
cb4cef
+        }
cb4cef
+
cb4cef
+        /* Replace the credential name principal with the canonical client
cb4cef
+         * principal, retaining acceptor_mprinc if set. */
cb4cef
+        krb5_free_principal(context, cred->name->princ);
cb4cef
+        cred->name->princ = ccache_princ;
cb4cef
+        ccache_princ = NULL;
cb4cef
     }
cb4cef
 
cb4cef
     assert(cred->name->princ != NULL);
cb4cef
@@ -447,7 +455,7 @@ get_cache_for_name(krb5_context context, krb5_gss_cred_id_rec *cred)
cb4cef
     assert(cred->name != NULL && cred->ccache == NULL);
cb4cef
 #ifdef USE_LEASH
cb4cef
     code = get_ccache_leash(context, cred->name->princ, &cred->ccache);
cb4cef
-    return code ? code : scan_ccache(context, cred);
cb4cef
+    return code ? code : scan_ccache(context, cred, TRUE);
cb4cef
 #else
cb4cef
     /* Check first whether we can acquire tickets, to avoid overwriting the
cb4cef
      * extended error message from krb5_cc_cache_match. */
cb4cef
@@ -456,7 +464,7 @@ get_cache_for_name(krb5_context context, krb5_gss_cred_id_rec *cred)
cb4cef
     /* Look for an existing cache for the client principal. */
cb4cef
     code = krb5_cc_cache_match(context, cred->name->princ, &cred->ccache);
cb4cef
     if (code == 0)
cb4cef
-        return scan_ccache(context, cred);
cb4cef
+        return scan_ccache(context, cred, FALSE);
cb4cef
     if (code != KRB5_CC_NOTFOUND || !can_get)
cb4cef
         return code;
cb4cef
     krb5_clear_error_message(context);
cb4cef
@@ -633,6 +641,13 @@ get_initial_cred(krb5_context context, const struct verify_params *verify,
cb4cef
     kg_cred_set_initial_refresh(context, cred, &creds.times);
cb4cef
     cred->have_tgt = TRUE;
cb4cef
     cred->expire = creds.times.endtime;
cb4cef
+
cb4cef
+    /* Steal the canonical client principal name from creds and save it in the
cb4cef
+     * credential name, retaining acceptor_mprinc if set. */
cb4cef
+    krb5_free_principal(context, cred->name->princ);
cb4cef
+    cred->name->princ = creds.client;
cb4cef
+    creds.client = NULL;
cb4cef
+
cb4cef
     krb5_free_cred_contents(context, &creds);
cb4cef
 cleanup:
cb4cef
     krb5_get_init_creds_opt_free(context, opt);
cb4cef
@@ -721,7 +736,7 @@ acquire_init_cred(krb5_context context, OM_uint32 *minor_status,
cb4cef
 
cb4cef
     if (cred->ccache != NULL) {
cb4cef
         /* The caller specified a ccache; check what's in it. */
cb4cef
-        code = scan_ccache(context, cred);
cb4cef
+        code = scan_ccache(context, cred, TRUE);
cb4cef
         if (code == KRB5_FCC_NOFILE) {
cb4cef
             /* See if we can get initial creds.  If the caller didn't specify
cb4cef
              * a name, pick one from the client keytab. */
cb4cef
@@ -984,7 +999,7 @@ kg_cred_resolve(OM_uint32 *minor_status, krb5_context context,
cb4cef
             }
cb4cef
         }
cb4cef
         if (cred->ccache != NULL) {
cb4cef
-            code = scan_ccache(context, cred);
cb4cef
+            code = scan_ccache(context, cred, FALSE);
cb4cef
             if (code)
cb4cef
                 goto kerr;
cb4cef
         }
cb4cef
@@ -996,7 +1011,7 @@ kg_cred_resolve(OM_uint32 *minor_status, krb5_context context,
cb4cef
         code = krb5int_cc_default(context, &cred->ccache);
cb4cef
         if (code)
cb4cef
             goto kerr;
cb4cef
-        code = scan_ccache(context, cred);
cb4cef
+        code = scan_ccache(context, cred, FALSE);
cb4cef
         if (code == KRB5_FCC_NOFILE) {
cb4cef
             /* Default ccache doesn't exist; fall through to client keytab. */
cb4cef
             krb5_cc_close(context, cred->ccache);
cb4cef
diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h
cb4cef
index 3bacdcd35..fd7abbd77 100644
cb4cef
--- a/src/lib/gssapi/krb5/gssapiP_krb5.h
cb4cef
+++ b/src/lib/gssapi/krb5/gssapiP_krb5.h
cb4cef
@@ -175,6 +175,7 @@ typedef struct _krb5_gss_cred_id_rec {
cb4cef
     /* name/type of credential */
cb4cef
     gss_cred_usage_t usage;
cb4cef
     krb5_gss_name_t name;
cb4cef
+    krb5_principal acceptor_mprinc;
cb4cef
     krb5_principal impersonator;
cb4cef
     unsigned int default_identity : 1;
cb4cef
     unsigned int iakerb_mech : 1;
cb4cef
diff --git a/src/lib/gssapi/krb5/rel_cred.c b/src/lib/gssapi/krb5/rel_cred.c
cb4cef
index a9515daf7..0da6c1b95 100644
cb4cef
--- a/src/lib/gssapi/krb5/rel_cred.c
cb4cef
+++ b/src/lib/gssapi/krb5/rel_cred.c
cb4cef
@@ -72,6 +72,7 @@ krb5_gss_release_cred(minor_status, cred_handle)
cb4cef
     if (cred->name)
cb4cef
         kg_release_name(context, &cred->name);
cb4cef
 
cb4cef
+    krb5_free_principal(context, cred->acceptor_mprinc);
cb4cef
     krb5_free_principal(context, cred->impersonator);
cb4cef
 
cb4cef
     if (cred->req_enctypes)
cb4cef
diff --git a/src/lib/krb5/ccache/cccursor.c b/src/lib/krb5/ccache/cccursor.c
cb4cef
index 8f5872116..760216d05 100644
cb4cef
--- a/src/lib/krb5/ccache/cccursor.c
cb4cef
+++ b/src/lib/krb5/ccache/cccursor.c
cb4cef
@@ -30,6 +30,7 @@
cb4cef
 
cb4cef
 #include "cc-int.h"
cb4cef
 #include "../krb/int-proto.h"
cb4cef
+#include "../os/os-proto.h"
cb4cef
 
cb4cef
 #include <assert.h>
cb4cef
 
cb4cef
@@ -141,18 +142,18 @@ krb5_cccol_cursor_free(krb5_context context,
cb4cef
     return 0;
cb4cef
 }
cb4cef
 
cb4cef
-krb5_error_code KRB5_CALLCONV
cb4cef
-krb5_cc_cache_match(krb5_context context, krb5_principal client,
cb4cef
-                    krb5_ccache *cache_out)
cb4cef
+static krb5_error_code
cb4cef
+match_caches(krb5_context context, krb5_const_principal client,
cb4cef
+             krb5_ccache *cache_out)
cb4cef
 {
cb4cef
     krb5_error_code ret;
cb4cef
     krb5_cccol_cursor cursor;
cb4cef
     krb5_ccache cache = NULL;
cb4cef
     krb5_principal princ;
cb4cef
-    char *name;
cb4cef
     krb5_boolean eq;
cb4cef
 
cb4cef
     *cache_out = NULL;
cb4cef
+
cb4cef
     ret = krb5_cccol_cursor_new(context, &cursor);
cb4cef
     if (ret)
cb4cef
         return ret;
cb4cef
@@ -169,20 +170,52 @@ krb5_cc_cache_match(krb5_context context, krb5_principal client,
cb4cef
         krb5_cc_close(context, cache);
cb4cef
     }
cb4cef
     krb5_cccol_cursor_free(context, &cursor);
cb4cef
+
cb4cef
     if (ret)
cb4cef
         return ret;
cb4cef
-    if (cache == NULL) {
cb4cef
-        ret = krb5_unparse_name(context, client, &name);
cb4cef
-        if (ret == 0) {
cb4cef
-            k5_setmsg(context, KRB5_CC_NOTFOUND,
cb4cef
+    if (cache == NULL)
cb4cef
+        return KRB5_CC_NOTFOUND;
cb4cef
+
cb4cef
+    *cache_out = cache;
cb4cef
+    return 0;
cb4cef
+}
cb4cef
+
cb4cef
+krb5_error_code KRB5_CALLCONV
cb4cef
+krb5_cc_cache_match(krb5_context context, krb5_principal client,
cb4cef
+                    krb5_ccache *cache_out)
cb4cef
+{
cb4cef
+    krb5_error_code ret;
cb4cef
+    struct canonprinc iter = { client, .subst_defrealm = TRUE };
cb4cef
+    krb5_const_principal canonprinc = NULL;
cb4cef
+    krb5_ccache cache = NULL;
cb4cef
+    char *name;
cb4cef
+
cb4cef
+    *cache_out = NULL;
cb4cef
+
cb4cef
+    while ((ret = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
cb4cef
+           canonprinc != NULL) {
cb4cef
+        ret = match_caches(context, canonprinc, &cache);
cb4cef
+        if (ret != KRB5_CC_NOTFOUND)
cb4cef
+            break;
cb4cef
+    }
cb4cef
+    free_canonprinc(&iter);
cb4cef
+
cb4cef
+    if (ret == 0 && canonprinc == NULL) {
cb4cef
+        ret = KRB5_CC_NOTFOUND;
cb4cef
+        if (krb5_unparse_name(context, client, &name) == 0) {
cb4cef
+            k5_setmsg(context, ret,
cb4cef
                       _("Can't find client principal %s in cache collection"),
cb4cef
                       name);
cb4cef
             krb5_free_unparsed_name(context, name);
cb4cef
         }
cb4cef
-        ret = KRB5_CC_NOTFOUND;
cb4cef
-    } else
cb4cef
-        *cache_out = cache;
cb4cef
-    return ret;
cb4cef
+    }
cb4cef
+
cb4cef
+    TRACE_CC_CACHE_MATCH(context, client, ret);
cb4cef
+    if (ret)
cb4cef
+        return ret;
cb4cef
+
cb4cef
+    *cache_out = cache;
cb4cef
+    return 0;
cb4cef
 }
cb4cef
 
cb4cef
 /* Store the error state for code from context into errsave, but only if code
cb4cef
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
cb4cef
index adbfa332b..df6e2ffbe 100644
cb4cef
--- a/src/lib/krb5/libkrb5.exports
cb4cef
+++ b/src/lib/krb5/libkrb5.exports
cb4cef
@@ -181,6 +181,7 @@ k5_size_authdata_context
cb4cef
 k5_size_context
cb4cef
 k5_size_keyblock
cb4cef
 k5_size_principal
cb4cef
+k5_sname_compare
cb4cef
 k5_unmarshal_cred
cb4cef
 k5_unmarshal_princ
cb4cef
 k5_unwrap_cammac_svc
cb4cef
diff --git a/src/lib/krb5/os/sn2princ.c b/src/lib/krb5/os/sn2princ.c
cb4cef
index 8b7214189..c99b7da17 100644
cb4cef
--- a/src/lib/krb5/os/sn2princ.c
cb4cef
+++ b/src/lib/krb5/os/sn2princ.c
cb4cef
@@ -277,7 +277,8 @@ k5_canonprinc(krb5_context context, struct canonprinc *iter,
cb4cef
 
cb4cef
     /* If we're not doing fallback, the input principal is canonical. */
cb4cef
     if (context->dns_canonicalize_hostname != CANONHOST_FALLBACK ||
cb4cef
-        iter->princ->type != KRB5_NT_SRV_HST || iter->princ->length != 2) {
cb4cef
+        iter->princ->type != KRB5_NT_SRV_HST || iter->princ->length != 2 ||
cb4cef
+        iter->princ->data[1].length == 0) {
cb4cef
         *princ_out = (step == 1) ? iter->princ : NULL;
cb4cef
         return 0;
cb4cef
     }
cb4cef
@@ -288,6 +289,26 @@ k5_canonprinc(krb5_context context, struct canonprinc *iter,
cb4cef
     return canonicalize_princ(context, iter, step == 2, princ_out);
cb4cef
 }
cb4cef
 
cb4cef
+krb5_boolean
cb4cef
+k5_sname_compare(krb5_context context, krb5_const_principal sname,
cb4cef
+                 krb5_const_principal princ)
cb4cef
+{
cb4cef
+    krb5_error_code ret;
cb4cef
+    struct canonprinc iter = { sname, .subst_defrealm = TRUE };
cb4cef
+    krb5_const_principal canonprinc = NULL;
cb4cef
+    krb5_boolean match = FALSE;
cb4cef
+
cb4cef
+    while ((ret = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
cb4cef
+           canonprinc != NULL) {
cb4cef
+        if (krb5_principal_compare(context, canonprinc, princ)) {
cb4cef
+            match = TRUE;
cb4cef
+            break;
cb4cef
+        }
cb4cef
+    }
cb4cef
+    free_canonprinc(&iter);
cb4cef
+    return match;
cb4cef
+}
cb4cef
+
cb4cef
 krb5_error_code KRB5_CALLCONV
cb4cef
 krb5_sname_to_principal(krb5_context context, const char *hostname,
cb4cef
                         const char *sname, krb5_int32 type,
cb4cef
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
cb4cef
index 60b8dd311..cf690dbe4 100644
cb4cef
--- a/src/lib/krb5_32.def
cb4cef
+++ b/src/lib/krb5_32.def
cb4cef
@@ -507,3 +507,4 @@ EXPORTS
cb4cef
 ; new in 1.20
cb4cef
 	krb5_marshal_credentials			@472
cb4cef
 	krb5_unmarshal_credentials			@473
cb4cef
+	k5_sname_compare				@474 ; PRIVATE GSSAPI
cb4cef
diff --git a/src/tests/gssapi/t_client_keytab.py b/src/tests/gssapi/t_client_keytab.py
cb4cef
index 7847b3ecd..9a61d53b8 100755
cb4cef
--- a/src/tests/gssapi/t_client_keytab.py
cb4cef
+++ b/src/tests/gssapi/t_client_keytab.py
cb4cef
@@ -141,5 +141,49 @@ msgs = ('Getting initial credentials for user/admin@KRBTEST.COM',
cb4cef
         '/Matching credential not found')
cb4cef
 realm.run(['./t_ccselect', phost], expected_code=1,
cb4cef
           expected_msg='Ticket expired', expected_trace=msgs)
cb4cef
+realm.run([kdestroy, '-A'])
cb4cef
+
cb4cef
+# Test 19: host-based initiator name
cb4cef
+mark('host-based initiator name')
cb4cef
+hsvc = 'h:svc@' + hostname
cb4cef
+svcprinc = 'svc/%s@%s' % (hostname, realm.realm)
cb4cef
+realm.addprinc(svcprinc)
cb4cef
+realm.extract_keytab(svcprinc, realm.client_keytab)
cb4cef
+# On the first run we match against the keytab while getting tickets,
cb4cef
+# substituting the default realm.
cb4cef
+msgs = ('/Can\'t find client principal svc/%s@ in' % hostname,
cb4cef
+        'Getting initial credentials for svc/%s@' % hostname,
cb4cef
+        'Found entries for %s in keytab' % svcprinc,
cb4cef
+        'Retrieving %s from FILE:%s' % (svcprinc, realm.client_keytab),
cb4cef
+        'Storing %s -> %s in' % (svcprinc, realm.krbtgt_princ),
cb4cef
+        'Retrieving %s -> %s from' % (svcprinc, realm.krbtgt_princ),
cb4cef
+        'authenticator for %s -> %s' % (svcprinc, realm.host_princ))
cb4cef
+realm.run(['./t_ccselect', phost, hsvc], expected_trace=msgs)
cb4cef
+# On the second run we match against the collection.
cb4cef
+msgs = ('Matching svc/%s@ in collection with result: 0' % hostname,
cb4cef
+        'Getting credentials %s -> %s' % (svcprinc, realm.host_princ),
cb4cef
+        'authenticator for %s -> %s' % (svcprinc, realm.host_princ))
cb4cef
+realm.run(['./t_ccselect', phost, hsvc], expected_trace=msgs)
cb4cef
+realm.run([kdestroy, '-A'])
cb4cef
+
cb4cef
+# Test 20: host-based initiator name with fallback
cb4cef
+mark('host-based fallback initiator name')
cb4cef
+canonname = canonicalize_hostname(hostname)
cb4cef
+if canonname != hostname:
cb4cef
+    hfsvc = 'h:fsvc@' + hostname
cb4cef
+    canonprinc = 'fsvc/%s@%s' % (canonname, realm.realm)
cb4cef
+    realm.addprinc(canonprinc)
cb4cef
+    realm.extract_keytab(canonprinc, realm.client_keytab)
cb4cef
+    msgs = ('/Can\'t find client principal fsvc/%s@ in' % hostname,
cb4cef
+            'Found entries for %s in keytab' % canonprinc,
cb4cef
+            'authenticator for %s -> %s' % (canonprinc, realm.host_princ))
cb4cef
+    realm.run(['./t_ccselect', phost, hfsvc], expected_trace=msgs)
cb4cef
+    msgs = ('Matching fsvc/%s@ in collection with result: 0' % hostname,
cb4cef
+            'Getting credentials %s -> %s' % (canonprinc, realm.host_princ))
cb4cef
+    realm.run(['./t_ccselect', phost, hfsvc], expected_trace=msgs)
cb4cef
+    realm.run([kdestroy, '-A'])
cb4cef
+else:
cb4cef
+    skipped('GSS initiator name fallback test',
cb4cef
+            '%s does not canonicalize to a different name' % hostname)
cb4cef
 
cb4cef
 success('Client keytab tests')
cb4cef
diff --git a/src/tests/gssapi/t_credstore.py b/src/tests/gssapi/t_credstore.py
cb4cef
index c11975bf5..9be57bb82 100644
cb4cef
--- a/src/tests/gssapi/t_credstore.py
cb4cef
+++ b/src/tests/gssapi/t_credstore.py
cb4cef
@@ -15,6 +15,38 @@ msgs = ('Storing %s -> %s in %s' % (service_cs, realm.krbtgt_princ,
cb4cef
 realm.run(['./t_credstore', '-s', 'p:' + service_cs, 'ccache', storagecache,
cb4cef
            'keytab', servicekeytab], expected_trace=msgs)
cb4cef
 
cb4cef
+mark('matching')
cb4cef
+scc = 'FILE:' + os.path.join(realm.testdir, 'service_cache')
cb4cef
+realm.kinit(realm.host_princ, flags=['-k', '-c', scc])
cb4cef
+realm.run(['./t_credstore', '-i', 'p:' + realm.host_princ, 'ccache', scc])
cb4cef
+realm.run(['./t_credstore', '-i', 'h:host', 'ccache', scc])
cb4cef
+realm.run(['./t_credstore', '-i', 'h:host@' + hostname, 'ccache', scc])
cb4cef
+realm.run(['./t_credstore', '-i', 'p:wrong', 'ccache', scc],
cb4cef
+          expected_code=1, expected_msg='does not match desired name')
cb4cef
+realm.run(['./t_credstore', '-i', 'h:host@-nomatch-', 'ccache', scc],
cb4cef
+          expected_code=1, expected_msg='does not match desired name')
cb4cef
+realm.run(['./t_credstore', '-i', 'h:svc', 'ccache', scc],
cb4cef
+          expected_code=1, expected_msg='does not match desired name')
cb4cef
+
cb4cef
+mark('matching (fallback)')
cb4cef
+canonname = canonicalize_hostname(hostname)
cb4cef
+if canonname != hostname:
cb4cef
+    canonprinc = 'host/%s@%s' % (canonname, realm.realm)
cb4cef
+    realm.addprinc(canonprinc)
cb4cef
+    realm.extract_keytab(canonprinc, realm.keytab)
cb4cef
+    realm.kinit(canonprinc, flags=['-k', '-c', scc])
cb4cef
+    realm.run(['./t_credstore', '-i', 'h:host', 'ccache', scc])
cb4cef
+    realm.run(['./t_credstore', '-i', 'h:host@' + hostname, 'ccache', scc])
cb4cef
+    realm.run(['./t_credstore', '-i', 'h:host@' + canonname, 'ccache', scc])
cb4cef
+    realm.run(['./t_credstore', '-i', 'p:' + canonprinc, 'ccache', scc])
cb4cef
+    realm.run(['./t_credstore', '-i', 'p:' + realm.host_princ, 'ccache', scc],
cb4cef
+              expected_code=1, expected_msg='does not match desired name')
cb4cef
+    realm.run(['./t_credstore', '-i', 'h:host@-nomatch-', 'ccache', scc],
cb4cef
+              expected_code=1, expected_msg='does not match desired name')
cb4cef
+else:
cb4cef
+    skipped('fallback matching test',
cb4cef
+            '%s does not canonicalize to a different name' % hostname)
cb4cef
+
cb4cef
 mark('rcache')
cb4cef
 # t_credstore -r should produce a replay error normally, but not with
cb4cef
 # rcache set to "none:".