]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Qualify short hostnames when not using DNS 1010/head
authorGreg Hudson <ghudson@mit.edu>
Sat, 30 Nov 2019 01:39:38 +0000 (20:39 -0500)
committerGreg Hudson <ghudson@mit.edu>
Fri, 6 Dec 2019 16:33:09 +0000 (11:33 -0500)
When DNS forward canonicalization is turned off or fails, qualify
single-component hostnames with the first DNS search domain.  Add the
qualify_shortname relation to override this suffix.

For one of the tests we need to disable qualification, which is
accomplished with an empty value.  Adjust k5test.py to correctly emit
empty values when writing profiles.

ticket: 8855 (new)

doc/admin/conf_files/krb5_conf.rst
src/include/k5-int.h
src/lib/krb5/os/dnsglue.c
src/lib/krb5/os/os-proto.h
src/lib/krb5/os/sn2princ.c
src/tests/gssapi/t_ccselect.py
src/tests/t_sn2princ.py
src/util/k5test.py

index f3142e2cf947a9e538c22b18361f357e6ae5a55a..d7687efcaa154e04cb6bcc7f4bb263fccda8f398 100644 (file)
@@ -314,6 +314,15 @@ The libdefaults section may contain any of the following relations:
     If this flag is true, initial tickets will be proxiable by
     default, if allowed by the KDC.  The default value is false.
 
+**qualify_shortname**
+    If this string is set, it determines the domain suffix for
+    single-component hostnames when DNS canonicalization is not used
+    (either because **dns_canonicalize_hostname** is false or because
+    forward canonicalization failed).  The default value is the first
+    search domain of the system's DNS configuration.  To disable
+    qualification of shortnames, set this relation to the empty string
+    with ``qualify_shortname = ""``.  (New in release 1.18.)
+
 **rdns**
     If this flag is true, reverse name lookup will be used in addition
     to forward name lookup to canonicalizing hostnames for use in
index 1d78c0297bb170f593a932898c669636b72610f9..9616b24bf6e2abf0e1240fa180c026327f277f8c 100644 (file)
@@ -281,6 +281,7 @@ typedef unsigned char   u_char;
 #define KRB5_CONF_PLUGIN_BASE_DIR              "plugin_base_dir"
 #define KRB5_CONF_PREFERRED_PREAUTH_TYPES      "preferred_preauth_types"
 #define KRB5_CONF_PROXIABLE                    "proxiable"
+#define KRB5_CONF_QUALIFY_SHORTNAME            "qualify_shortname"
 #define KRB5_CONF_RDNS                         "rdns"
 #define KRB5_CONF_REALMS                       "realms"
 #define KRB5_CONF_REALM_TRY_DOMAINS            "realm_try_domains"
index 59ff929638c56ac160b8de7d8db3078f0a94f978..e35ca9d76c634ac27038b603407d747f9eff0b79 100644 (file)
@@ -71,6 +71,7 @@ static int initparse(struct krb5int_dns_state *);
  * Define macros to use the best available DNS search functions.  INIT_HANDLE()
  * returns true if handle initialization is successful, false if it is not.
  * SEARCH() returns the length of the response or -1 on error.
+ * PRIMARY_DOMAIN() returns the first search domain in allocated memory.
  * DECLARE_HANDLE() must be used last in the declaration list since it may
  * evaluate to nothing.
  */
@@ -81,6 +82,7 @@ static int initparse(struct krb5int_dns_state *);
 #define DECLARE_HANDLE(h) dns_handle_t h
 #define INIT_HANDLE(h) ((h = dns_open(NULL)) != NULL)
 #define SEARCH(h, n, c, t, a, l) dns_search(h, n, c, t, a, l, NULL, NULL)
+#define PRIMARY_DOMAIN(h) dns_search_list_domain(h, 0)
 #define DESTROY_HANDLE(h) dns_free(h)
 
 #elif HAVE_RES_NINIT && HAVE_RES_NSEARCH
@@ -89,6 +91,7 @@ static int initparse(struct krb5int_dns_state *);
 #define DECLARE_HANDLE(h) struct __res_state h
 #define INIT_HANDLE(h) (memset(&h, 0, sizeof(h)), res_ninit(&h) == 0)
 #define SEARCH(h, n, c, t, a, l) res_nsearch(&h, n, c, t, a, l)
+#define PRIMARY_DOMAIN(h) strdup(h.dnsrch[0])
 #if HAVE_RES_NDESTROY
 #define DESTROY_HANDLE(h) res_ndestroy(&h)
 #else
@@ -101,6 +104,7 @@ static int initparse(struct krb5int_dns_state *);
 #define DECLARE_HANDLE(h)
 #define INIT_HANDLE(h) (res_init() == 0)
 #define SEARCH(h, n, c, t, a, l) res_search(n, c, t, a, l)
+#define PRIMARY_DOMAIN(h) strdup(_res.defdname)
 #define DESTROY_HANDLE(h)
 
 #endif
@@ -433,6 +437,12 @@ cleanup:
     return ret;
 }
 
+char *
+k5_primary_domain()
+{
+    return NULL;
+}
+
 #else /* _WIN32 */
 
 krb5_error_code
@@ -485,5 +495,18 @@ errout:
     return retval;
 }
 
+char *
+k5_primary_domain()
+{
+    char *domain;
+    DECLARE_HANDLE(h);
+
+    if (!INIT_HANDLE(h))
+        return NULL;
+    domain = PRIMARY_DOMAIN(h);
+    DESTROY_HANDLE(h);
+    return domain;
+}
+
 #endif /* not _WIN32 */
 #endif /* KRB5_DNS_LOOKUP */
index 066d3022185790a93139f8541d8b98df659bbce2..a16a34b7439282d267f041ca2cb4dbe679c37940 100644 (file)
@@ -136,6 +136,8 @@ k5_make_uri_query(krb5_context context, const krb5_data *realm,
 krb5_error_code k5_try_realm_txt_rr(krb5_context context, const char *prefix,
                                     const char *name, char **realm);
 
+char *k5_primary_domain(void);
+
 int _krb5_use_dns_realm (krb5_context);
 int _krb5_use_dns_kdc (krb5_context);
 int _krb5_conf_boolean (const char *);
index 98d2600aa3fce429b3cb7670adff44268e71d860..a51761d0cc1da26531abcf30c331bca1e5f34b3b 100644 (file)
@@ -50,15 +50,47 @@ use_reverse_dns(krb5_context context)
                               &value);
     if (ret)
         return DEFAULT_RDNS_LOOKUP;
+
     return value;
 }
 
+/* Append a domain suffix to host and return the result in allocated memory.
+ * Return NULL if no suffix is configured or on failure. */
+static char *
+qualify_shortname(krb5_context context, const char *host)
+{
+    krb5_error_code ret;
+    char *fqdn = NULL, *prof_domain = NULL, *os_domain = NULL;
+    const char *domain;
+
+    ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
+                             KRB5_CONF_QUALIFY_SHORTNAME, NULL, NULL,
+                             &prof_domain);
+    if (ret)
+        return NULL;
+
+#ifdef KRB5_DNS_LOOKUP
+    if (prof_domain == NULL)
+        os_domain = k5_primary_domain();
+#endif
+
+    domain = (prof_domain != NULL) ? prof_domain : os_domain;
+    if (domain != NULL && *domain != '\0') {
+        if (asprintf(&fqdn, "%s.%s", host, domain) < 0)
+            fqdn = NULL;
+    }
+
+    profile_release_string(prof_domain);
+    free(os_domain);
+    return fqdn;
+}
+
 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;
+    char namebuf[NI_MAXHOST], *qualified = NULL, *copy, *p;
     int err;
     const char *canonhost;
     krb5_boolean use_dns;
@@ -90,6 +122,14 @@ k5_expand_hostname(krb5_context context, const char *host,
         }
     }
 
+    /* If we didn't use DNS and the name is just one component, try to add a
+     * domain suffix. */
+    if (canonhost == host && strchr(host, '.') == NULL) {
+        qualified = qualify_shortname(context, host);
+        if (qualified != NULL)
+            canonhost = qualified;
+    }
+
     copy = strdup(canonhost);
     if (copy == NULL)
         goto cleanup;
@@ -113,6 +153,7 @@ cleanup:
     /* We only return success or ENOMEM. */
     if (ai != NULL)
         freeaddrinfo(ai);
+    free(qualified);
     return (*canonhost_out == NULL) ? ENOMEM : 0;
 }
 
index 423a00af2e3f31c2a36722a0482f4ceb1565c3bf..c93be672d75c195fb2036d8c71fd7dea8b7ec552 100755 (executable)
@@ -24,8 +24,9 @@ from k5test import *
 
 # Create two independent realms (no cross-realm TGTs).  For the
 # fallback realm tests we need to control the precise server hostname,
-# so turn off DNS canonicalization.
-conf = {'libdefaults': {'dns_canonicalize_hostname': 'false'}}
+# so turn off DNS canonicalization and shortname qualification.
+conf = {'libdefaults': {'dns_canonicalize_hostname': 'false',
+                        'qualify_shortname': ''}}
 r1 = K5Realm(create_user=False, krb5_conf=conf)
 r2 = K5Realm(create_user=False, krb5_conf=conf, realm='KRBTEST2.COM',
              portbase=62000, testdir=os.path.join(r1.testdir, 'r2'))
index fe435a2d58733316b285879f9e3fea4156cc71bb..26dcb91c2bededc7b64b4a31eaf38d75914e3cb0 100755 (executable)
@@ -6,7 +6,8 @@ conf = {'domain_realm': {'kerberos.org': 'R1',
                          'example.com': 'R2',
                          'mit.edu': 'R3'}}
 no_rdns_conf = {'libdefaults': {'rdns': 'false'}}
-no_canon_conf = {'libdefaults': {'dns_canonicalize_hostname': 'false'}}
+no_canon_conf = {'libdefaults': {'dns_canonicalize_hostname': 'false',
+                                 'qualify_shortname': 'example.com'}}
 fallback_canon_conf = {'libdefaults':
                        {'rdns': 'false',
                         'dns_canonicalize_hostname': 'fallback'}}
@@ -62,12 +63,15 @@ testu('Example.COM:xyZ', 'Example.COM:xyZ', 'R2')
 testu('example.com.::123', 'example.com.::123', '')
 
 # With dns_canonicalize_hostname=false, we downcase and remove
-# trailing dots but do not canonicalize the hostname.  Trailers do not
-# get downcased.
+# trailing dots but do not canonicalize the hostname.
+# Single-component names are qualified with the configured suffix
+# (defaulting to the first OS search domain, but Python cannot easily
+# retrieve that value so we don't test it).  Trailers do not get
+# downcased.
 mark('dns_canonicalize_host=false')
 testnc('ptr-mismatch.kerberos.org', 'ptr-mismatch.kerberos.org', 'R1')
 testnc('Example.COM', 'example.com', 'R2')
-testnc('abcde', 'abcde', '')
+testnc('abcde', 'abcde.example.com', 'R2')
 testnc('example.com.:123', 'example.com:123', 'R2')
 testnc('Example.COM:xyZ', 'example.com:xyZ', 'R2')
 testnc('example.com.::123', 'example.com.::123', '')
index 89ebacd349b99fc18fd89f58ea6426624ed9b074..78f42c733dbc95b465c9e48ba4a1a43938561e0d 100644 (file)
@@ -962,22 +962,24 @@ class K5Realm(object):
     def _subst_cfg_value(self, value):
         global buildtop, srctop, hostname
         template = string.Template(value)
-        return template.substitute(realm=self.realm,
-                                   testdir=self.testdir,
-                                   buildtop=buildtop,
-                                   srctop=srctop,
-                                   plugins=plugins,
-                                   hostname=hostname,
-                                   port0=self.portbase,
-                                   port1=self.portbase + 1,
-                                   port2=self.portbase + 2,
-                                   port3=self.portbase + 3,
-                                   port4=self.portbase + 4,
-                                   port5=self.portbase + 5,
-                                   port6=self.portbase + 6,
-                                   port7=self.portbase + 7,
-                                   port8=self.portbase + 8,
-                                   port9=self.portbase + 9)
+        subst = template.substitute(realm=self.realm,
+                                    testdir=self.testdir,
+                                    buildtop=buildtop,
+                                    srctop=srctop,
+                                    plugins=plugins,
+                                    hostname=hostname,
+                                    port0=self.portbase,
+                                    port1=self.portbase + 1,
+                                    port2=self.portbase + 2,
+                                    port3=self.portbase + 3,
+                                    port4=self.portbase + 4,
+                                    port5=self.portbase + 5,
+                                    port6=self.portbase + 6,
+                                    port7=self.portbase + 7,
+                                    port8=self.portbase + 8,
+                                    port9=self.portbase + 9)
+        # Empty values must be quoted to avoid a syntax error.
+        return subst if subst else '""'
 
     def _create_acl(self):
         global hostname