krb5 commit: Add dns_canonicalize_hostname=fallback support

Greg Hudson ghudson at mit.edu
Fri Dec 21 12:41:07 EST 2018


https://github.com/krb5/krb5/commit/6c20cb1c89acaa03db897182a3b28d5f8f284907
commit 6c20cb1c89acaa03db897182a3b28d5f8f284907
Author: Simo Sorce <simo at redhat.com>
Date:   Tue Dec 4 15:22:55 2018 -0500

    Add dns_canonicalize_hostname=fallback support
    
    Turn dns_canonicalize_hostname into a tristate variable, allowing the
    value "fallback" as well as the true/false booleans.  If it is set to
    fallback, delay DNS canonicalization and attempt it only in
    krb5_get_credentials() if the KDC responds that the requested server
    principal name is unknown.
    
    [ghudson at mit.edu: added TGS tests; refactored code; edited commit
    message and documentation]
    
    ticket: 8765 (new)

 doc/admin/conf_files/krb5_conf.rst |    4 ++
 src/include/k5-int.h               |    8 +++-
 src/include/k5-trace.h             |    3 +
 src/lib/krb5/krb/get_creds.c       |   79 +++++++++++++++++++++++++++++++----
 src/lib/krb5/krb/init_ctx.c        |   27 ++++++++++++-
 src/lib/krb5/krb/t_copy_context.c  |    2 +-
 src/lib/krb5/os/os-proto.h         |    4 ++
 src/lib/krb5/os/sn2princ.c         |   19 +++++++--
 src/tests/gcred.c                  |    5 ++-
 src/tests/t_sn2princ.py            |   34 +++++++++++++++-
 10 files changed, 167 insertions(+), 18 deletions(-)

diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst
index 7b4389f..e9f7e8c 100644
--- a/doc/admin/conf_files/krb5_conf.rst
+++ b/doc/admin/conf_files/krb5_conf.rst
@@ -201,6 +201,10 @@ The libdefaults section may contain any of the following relations:
     means that short hostnames will not be canonicalized to
     fully-qualified hostnames.  The default value is true.
 
+    If this option is set to ``fallback`` (new in release 1.18), DNS
+    canonicalization will only be performed the server hostname is not
+    found with the original name when requesting credentials.
+
 **dns_lookup_kdc**
     Indicate whether DNS SRV records should be used to locate the KDCs
     and other servers for a realm, if they are not listed in the
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 6522422..aceae36 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -1158,6 +1158,12 @@ k5_plugin_register_dyn(krb5_context context, int interface_id,
 void
 k5_plugin_free_context(krb5_context context);
 
+enum dns_canonhost {
+    CANONHOST_FALSE = 0,
+    CANONHOST_TRUE = 1,
+    CANONHOST_FALLBACK = 2
+};
+
 struct _kdb5_dal_handle;        /* private, in kdb5.h */
 typedef struct _kdb5_dal_handle kdb5_dal_handle;
 struct _kdb_log_context;
@@ -1221,7 +1227,7 @@ struct _krb5_context {
 
     krb5_boolean allow_weak_crypto;
     krb5_boolean ignore_acceptor_hostname;
-    krb5_boolean dns_canonicalize_hostname;
+    enum dns_canonhost dns_canonicalize_hostname;
 
     krb5_trace_callback trace_callback;
     void *trace_callback_data;
diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h
index 2aa379b..f3ed6a4 100644
--- a/src/include/k5-trace.h
+++ b/src/include/k5-trace.h
@@ -191,6 +191,9 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
 #define TRACE_FAST_REQUIRED(c)                                  \
     TRACE(c, "Using FAST due to KRB5_FAST_REQUIRED flag")
 
+#define TRACE_GET_CREDS_FALLBACK(c, hostname)                           \
+    TRACE(c, "Falling back to canonicalized server hostname {str}", hostname)
+
 #define TRACE_GIC_PWD_CHANGED(c)                                \
     TRACE(c, "Getting initial TGT with changed password")
 #define TRACE_GIC_PWD_CHANGEPW(c, tries)                                \
diff --git a/src/lib/krb5/krb/get_creds.c b/src/lib/krb5/krb/get_creds.c
index 69900ad..0a04d68 100644
--- a/src/lib/krb5/krb/get_creds.c
+++ b/src/lib/krb5/krb/get_creds.c
@@ -39,6 +39,7 @@
 
 #include "k5-int.h"
 #include "int-proto.h"
+#include "os-proto.h"
 #include "fast.h"
 
 /*
@@ -1249,6 +1250,26 @@ krb5_tkt_creds_step(krb5_context context, krb5_tkt_creds_context ctx,
         return EINVAL;
 }
 
+static krb5_error_code
+try_get_creds(krb5_context context, krb5_flags options, krb5_ccache ccache,
+              krb5_creds *in_creds, krb5_creds *creds_out)
+{
+    krb5_error_code code;
+    krb5_tkt_creds_context ctx = NULL;
+
+    code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx);
+    if (code)
+        goto cleanup;
+    code = krb5_tkt_creds_get(context, ctx);
+    if (code)
+        goto cleanup;
+    code = krb5_tkt_creds_get_creds(context, ctx, creds_out);
+
+cleanup:
+    krb5_tkt_creds_free(context, ctx);
+    return code;
+}
+
 krb5_error_code KRB5_CALLCONV
 krb5_get_credentials(krb5_context context, krb5_flags options,
                      krb5_ccache ccache, krb5_creds *in_creds,
@@ -1256,7 +1277,10 @@ krb5_get_credentials(krb5_context context, krb5_flags options,
 {
     krb5_error_code code;
     krb5_creds *ncreds = NULL;
-    krb5_tkt_creds_context ctx = NULL;
+    krb5_creds canon_creds, store_creds;
+    krb5_principal_data canon_server;
+    krb5_data canon_components[2];
+    char *hostname = NULL, *canon_hostname = NULL;
 
     *out_creds = NULL;
 
@@ -1265,22 +1289,59 @@ krb5_get_credentials(krb5_context context, krb5_flags options,
     if (ncreds == NULL)
         goto cleanup;
 
-    /* Make and execute a krb5_tkt_creds context to get the credential. */
-    code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx);
-    if (code != 0)
+    code = try_get_creds(context, options, ccache, in_creds, ncreds);
+    if (!code) {
+        *out_creds = ncreds;
+        return 0;
+    }
+
+    /* Possibly try again with the canonicalized hostname, if the server is
+     * host-based and we are configured for fallback canonicalization. */
+    if (code != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
         goto cleanup;
-    code = krb5_tkt_creds_get(context, ctx);
-    if (code != 0)
+    if (context->dns_canonicalize_hostname != CANONHOST_FALLBACK)
         goto cleanup;
-    code = krb5_tkt_creds_get_creds(context, ctx, ncreds);
-    if (code != 0)
+    if (in_creds->server->type != KRB5_NT_SRV_HST ||
+        in_creds->server->length != 2)
         goto cleanup;
 
+    hostname = k5memdup0(in_creds->server->data[1].data,
+                         in_creds->server->data[1].length, &code);
+    if (hostname == NULL)
+        goto cleanup;
+    code = k5_expand_hostname(context, hostname, TRUE, &canon_hostname);
+    if (code)
+        goto cleanup;
+
+    TRACE_GET_CREDS_FALLBACK(context, canon_hostname);
+
+    /* Make shallow copies of in_creds and its server to alter the hostname. */
+    canon_components[0] = in_creds->server->data[0];
+    canon_components[1] = string2data(canon_hostname);
+    canon_server = *in_creds->server;
+    canon_server.data = canon_components;
+    canon_creds = *in_creds;
+    canon_creds.server = &canon_server;
+
+    code = try_get_creds(context, options | KRB5_GC_NO_STORE, ccache,
+                         &canon_creds, ncreds);
+    if (code)
+        goto cleanup;
+
+    if (!(options & KRB5_GC_NO_STORE)) {
+        /* Store the creds under the originally requested server name.  The
+         * ccache layer will also store them under the ticket server name. */
+        store_creds = *ncreds;
+        store_creds.server = in_creds->server;
+        (void)krb5_cc_store_cred(context, ccache, &store_creds);
+    }
+
     *out_creds = ncreds;
     ncreds = NULL;
 
 cleanup:
+    free(hostname);
+    free(canon_hostname);
     krb5_free_creds(context, ncreds);
-    krb5_tkt_creds_free(context, ctx);
     return code;
 }
diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c
index 947e504..d263d5c 100644
--- a/src/lib/krb5/krb/init_ctx.c
+++ b/src/lib/krb5/krb/init_ctx.c
@@ -101,6 +101,30 @@ get_boolean(krb5_context ctx, const char *name, int def_val, int *boolean_out)
     return retval;
 }
 
+static krb5_error_code
+get_tristate(krb5_context ctx, const char *name, const char *third_option,
+             int third_option_val, int def_val, int *val_out)
+{
+    krb5_error_code retval;
+    char *str;
+    int match;
+
+    retval = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS, name,
+                                 NULL, def_val, val_out);
+    if (retval != PROF_BAD_BOOLEAN)
+        return retval;
+    retval = profile_get_string(ctx->profile, KRB5_CONF_LIBDEFAULTS, name,
+                                NULL, NULL, &str);
+    if (retval)
+        return retval;
+    match = (strcasecmp(third_option, str) == 0);
+    free(str);
+    if (!match)
+        return EINVAL;
+    *val_out = third_option_val;
+    return 0;
+}
+
 krb5_error_code KRB5_CALLCONV
 krb5_init_context(krb5_context *context)
 {
@@ -213,7 +237,8 @@ krb5_init_context_profile(profile_t profile, krb5_flags flags,
         goto cleanup;
     ctx->ignore_acceptor_hostname = tmp;
 
-    retval = get_boolean(ctx, KRB5_CONF_DNS_CANONICALIZE_HOSTNAME, 1, &tmp);
+    retval = get_tristate(ctx, KRB5_CONF_DNS_CANONICALIZE_HOSTNAME, "fallback",
+                          CANONHOST_FALLBACK, 1, &tmp);
     if (retval)
         goto cleanup;
     ctx->dns_canonicalize_hostname = tmp;
diff --git a/src/lib/krb5/krb/t_copy_context.c b/src/lib/krb5/krb/t_copy_context.c
index fa810be..a6e48cd 100644
--- a/src/lib/krb5/krb/t_copy_context.c
+++ b/src/lib/krb5/krb/t_copy_context.c
@@ -145,7 +145,7 @@ main(int argc, char **argv)
     ctx->udp_pref_limit = 2345;
     ctx->use_conf_ktypes = TRUE;
     ctx->ignore_acceptor_hostname = TRUE;
-    ctx->dns_canonicalize_hostname = FALSE;
+    ctx->dns_canonicalize_hostname = CANONHOST_FALSE;
     free(ctx->plugin_base_dir);
     check((ctx->plugin_base_dir = strdup("/a/b/c/d")) != NULL);
 
diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h
index 634e82d..066d302 100644
--- a/src/lib/krb5/os/os-proto.h
+++ b/src/lib/krb5/os/os-proto.h
@@ -83,6 +83,10 @@ struct sendto_callback_info {
     void *data;
 };
 
+krb5_error_code k5_expand_hostname(krb5_context context, const char *host,
+                                   krb5_boolean is_fallback,
+                                   char **canonhost_out);
+
 krb5_error_code k5_locate_server(krb5_context, const krb5_data *realm,
                                  struct serverlist *serverlist,
                                  enum locate_service_type svc,
diff --git a/src/lib/krb5/os/sn2princ.c b/src/lib/krb5/os/sn2princ.c
index 5932fd9..98d2600 100644
--- a/src/lib/krb5/os/sn2princ.c
+++ b/src/lib/krb5/os/sn2princ.c
@@ -53,19 +53,23 @@ use_reverse_dns(krb5_context context)
     return value;
 }
 
-krb5_error_code KRB5_CALLCONV
-krb5_expand_hostname(krb5_context context, const char *host,
-                     char **canonhost_out)
+krb5_error_code
+k5_expand_hostname(krb5_context context, const char *host,
+                   krb5_boolean is_fallback, char **canonhost_out)
 {
     struct addrinfo *ai = NULL, hint;
     char namebuf[NI_MAXHOST], *copy, *p;
     int err;
     const char *canonhost;
+    krb5_boolean use_dns;
 
     *canonhost_out = NULL;
 
     canonhost = host;
-    if (context->dns_canonicalize_hostname) {
+    use_dns = (context->dns_canonicalize_hostname == CANONHOST_TRUE ||
+               (is_fallback &&
+                context->dns_canonicalize_hostname == CANONHOST_FALLBACK));
+    if (use_dns) {
         /* Try a forward lookup of the hostname. */
         memset(&hint, 0, sizeof(hint));
         hint.ai_flags = AI_CANONNAME;
@@ -112,6 +116,13 @@ cleanup:
     return (*canonhost_out == NULL) ? ENOMEM : 0;
 }
 
+krb5_error_code KRB5_CALLCONV
+krb5_expand_hostname(krb5_context context, const char *host,
+                     char **canonhost_out)
+{
+    return k5_expand_hostname(context, host, FALSE, canonhost_out);
+}
+
 /* If hostname appears to have a :port or :instance trailer (used in MSSQLSvc
  * principals), return a pointer to the separator.  Otherwise return NULL. */
 static const char *
diff --git a/src/tests/gcred.c b/src/tests/gcred.c
index b14e4fc..cac524c 100644
--- a/src/tests/gcred.c
+++ b/src/tests/gcred.c
@@ -66,6 +66,7 @@ main(int argc, char **argv)
     krb5_principal client, server;
     krb5_ccache ccache;
     krb5_creds in_creds, *creds;
+    krb5_ticket *ticket;
     krb5_flags options = 0;
     char *name;
     int c;
@@ -102,9 +103,11 @@ main(int argc, char **argv)
     in_creds.client = client;
     in_creds.server = server;
     check(krb5_get_credentials(ctx, options, ccache, &in_creds, &creds));
-    check(krb5_unparse_name(ctx, creds->server, &name));
+    check(krb5_decode_ticket(&creds->ticket, &ticket));
+    check(krb5_unparse_name(ctx, ticket->server, &name));
     printf("%s\n", name);
 
+    krb5_free_ticket(ctx, ticket);
     krb5_free_unparsed_name(ctx, name);
     krb5_free_creds(ctx, creds);
     krb5_free_principal(ctx, client);
diff --git a/src/tests/t_sn2princ.py b/src/tests/t_sn2princ.py
index 1ffda51..fe435a2 100755
--- a/src/tests/t_sn2princ.py
+++ b/src/tests/t_sn2princ.py
@@ -7,10 +7,15 @@ conf = {'domain_realm': {'kerberos.org': 'R1',
                          'mit.edu': 'R3'}}
 no_rdns_conf = {'libdefaults': {'rdns': 'false'}}
 no_canon_conf = {'libdefaults': {'dns_canonicalize_hostname': 'false'}}
+fallback_canon_conf = {'libdefaults':
+                       {'rdns': 'false',
+                        'dns_canonicalize_hostname': 'fallback'}}
 
-realm = K5Realm(create_kdb=False, krb5_conf=conf)
+realm = K5Realm(realm='R1', create_host=False, krb5_conf=conf)
 no_rdns = realm.special_env('no_rdns', False, krb5_conf=no_rdns_conf)
 no_canon = realm.special_env('no_canon', False, krb5_conf=no_canon_conf)
+fallback_canon = realm.special_env('fallback_canon', False,
+                                   krb5_conf=fallback_canon_conf)
 
 def testbase(host, nametype, princhost, princrealm, env=None):
     # Run the sn2princ harness with a specified host and name type and
@@ -37,6 +42,10 @@ def testu(host, princhost, princrealm):
     # Test with the unknown name type.
     testbase(host, 'unknown', princhost, princrealm)
 
+def testfc(host, princhost, princrealm):
+    # Test with the host-based name type with canonicalization fallback.
+    testbase(host, 'srv-hst', princhost, princrealm, env=fallback_canon)
+
 # With the unknown principal type, we do not canonicalize or downcase,
 # but we do remove a trailing period and look up the realm.
 mark('unknown type')
@@ -71,6 +80,29 @@ if offline:
 oname = 'ptr-mismatch.kerberos.org'
 fname = 'www.kerberos.org'
 
+# Test fallback canonicalization krb5_sname_to_principal() results
+# (same as dns_canonicalize_hostname=false).
+mark('dns_canonicalize_host=fallback')
+testfc(oname, oname, 'R1')
+
+# Test fallback canonicalization in krb5_get_credentials().
+oprinc = 'host/' + oname
+fprinc = 'host/' + fname
+shutil.copy(realm.ccache, realm.ccache + '.save')
+realm.addprinc(fprinc)
+# oprinc doesn't exist, so we get the canonicalized fprinc as a fallback.
+msgs = ('Falling back to canonicalized server hostname ' + fname,)
+realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon,
+          expected_msg=fprinc, expected_trace=msgs)
+realm.addprinc(oprinc)
+# oprinc now exists, but we still get the fprinc ticket from the cache.
+realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon,
+          expected_msg=fprinc)
+# Without the cached result, we sould get oprinc in preference to fprinc.
+os.rename(realm.ccache + '.save', realm.ccache)
+realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon,
+          expected_msg=oprinc)
+
 # Verify forward resolution before testing for it.
 try:
     ai = socket.getaddrinfo(oname, None, 0, 0, 0, socket.AI_CANONNAME)


More information about the cvs-krb5 mailing list