krb5 commit: Add hostname-based ccselect module

Greg Hudson ghudson at mit.edu
Tue Sep 5 00:19:17 EDT 2017


https://github.com/krb5/krb5/commit/a4ddc6cf576b4155e6b994307902567f26f752b2
commit a4ddc6cf576b4155e6b994307902567f26f752b2
Author: Robbie Harwood <rharwood at redhat.com>
Date:   Wed Aug 23 17:25:17 2017 -0400

    Add hostname-based ccselect module
    
    The hostname module selects the ccache whose realm is the longest
    parent domain tail of the uppercase server hostname.
    
    [ghudson at mit.edu: minor edits]
    
    ticket: 8613 (new)

 doc/admin/conf_files/krb5_conf.rst      |    4 +
 src/lib/krb5/ccache/Makefile.in         |    3 +
 src/lib/krb5/ccache/cc-int.h            |    4 +
 src/lib/krb5/ccache/ccselect.c          |    5 +
 src/lib/krb5/ccache/ccselect_hostname.c |  146 +++++++++++++++++++++++++++++++
 src/tests/gssapi/t_ccselect.py          |    9 ++
 6 files changed, 171 insertions(+), 0 deletions(-)

diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst
index fbcf192..4ed9832 100644
--- a/doc/admin/conf_files/krb5_conf.rst
+++ b/doc/admin/conf_files/krb5_conf.rst
@@ -745,6 +745,10 @@ disabled with the disable tag):
     Uses the service realm to guess an appropriate cache from the
     collection
 
+**hostname**
+    If the service principal is host-based, uses the service hostname
+    to guess an appropriate cache from the collection
+
 .. _pwqual:
 
 pwqual interface
diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in
index 5ac8707..f84cf79 100644
--- a/src/lib/krb5/ccache/Makefile.in
+++ b/src/lib/krb5/ccache/Makefile.in
@@ -34,6 +34,7 @@ STLIBOBJS= \
 	ccdefops.o \
 	ccmarshal.o \
 	ccselect.o \
+	ccselect_hostname.o \
 	ccselect_k5identity.o \
 	ccselect_realm.o \
 	cc_dir.o \
@@ -52,6 +53,7 @@ OBJS=	$(OUTPRE)ccbase.$(OBJEXT) \
 	$(OUTPRE)ccdefops.$(OBJEXT) \
 	$(OUTPRE)ccmarshal.$(OBJEXT) \
 	$(OUTPRE)ccselect.$(OBJEXT) \
+	$(OUTPRE)ccselect_hostname.$(OBJEXT) \
 	$(OUTPRE)ccselect_k5identity.$(OBJEXT) \
 	$(OUTPRE)ccselect_realm.$(OBJEXT) \
 	$(OUTPRE)cc_dir.$(OBJEXT) \
@@ -70,6 +72,7 @@ SRCS=	$(srcdir)/ccbase.c \
 	$(srcdir)/ccdefops.c \
 	$(srcdir)/ccmarshal.c \
 	$(srcdir)/ccselect.c \
+	$(srcdir)/ccselect_hostname.c \
 	$(srcdir)/ccselect_k5identity.c \
 	$(srcdir)/ccselect_realm.c \
 	$(srcdir)/cc_dir.c \
diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h
index ee9b5e0..d920367 100644
--- a/src/lib/krb5/ccache/cc-int.h
+++ b/src/lib/krb5/ccache/cc-int.h
@@ -124,6 +124,10 @@ krb5_error_code
 krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id);
 
 krb5_error_code
+ccselect_hostname_initvt(krb5_context context, int maj_ver, int min_ver,
+                         krb5_plugin_vtable vtable);
+
+krb5_error_code
 ccselect_realm_initvt(krb5_context context, int maj_ver, int min_ver,
                       krb5_plugin_vtable vtable);
 
diff --git a/src/lib/krb5/ccache/ccselect.c b/src/lib/krb5/ccache/ccselect.c
index df2115c..6c360e1 100644
--- a/src/lib/krb5/ccache/ccselect.c
+++ b/src/lib/krb5/ccache/ccselect.c
@@ -71,6 +71,11 @@ load_modules(krb5_context context)
     if (ret != 0)
         goto cleanup;
 
+    ret = k5_plugin_register(context, PLUGIN_INTERFACE_CCSELECT, "hostname",
+                             ccselect_hostname_initvt);
+    if (ret != 0)
+        goto cleanup;
+
     ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CCSELECT, &modules);
     if (ret != 0)
         goto cleanup;
diff --git a/src/lib/krb5/ccache/ccselect_hostname.c b/src/lib/krb5/ccache/ccselect_hostname.c
new file mode 100644
index 0000000..475cfab
--- /dev/null
+++ b/src/lib/krb5/ccache/ccselect_hostname.c
@@ -0,0 +1,146 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccselect_hostname.c - hostname ccselect module */
+/*
+ * Copyright (C) 2017 by Red Hat, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+#include <ctype.h>
+#include <krb5/ccselect_plugin.h>
+
+/* Swap a and b, using tmp as an intermediate. */
+#define SWAP(a, b, tmp)                         \
+    tmp = a;                                    \
+    a = b;                                      \
+    b = tmp;
+
+static krb5_error_code
+hostname_init(krb5_context context, krb5_ccselect_moddata *data_out,
+              int *priority_out)
+{
+    *data_out = NULL;
+    *priority_out = KRB5_CCSELECT_PRIORITY_HEURISTIC;
+    return 0;
+}
+
+static krb5_error_code
+hostname_choose(krb5_context context, krb5_ccselect_moddata data,
+                krb5_principal server, krb5_ccache *ccache_out,
+                krb5_principal *princ_out)
+{
+    krb5_error_code ret;
+    char *p, *host = NULL;
+    size_t hostlen;
+    krb5_cccol_cursor col_cursor;
+    krb5_ccache ccache, tmp_ccache, best_ccache = NULL;
+    krb5_principal princ, tmp_princ, best_princ = NULL;
+    krb5_data domain;
+
+    *ccache_out = NULL;
+    *princ_out = NULL;
+
+    if (server->type != KRB5_NT_SRV_HST || server->length < 2)
+        return KRB5_PLUGIN_NO_HANDLE;
+
+    /* Compute upper-case hostname. */
+    hostlen = server->data[1].length;
+    host = k5memdup0(server->data[1].data, hostlen, &ret);
+    if (host == NULL)
+        return ret;
+    for (p = host; *p != '\0'; p++) {
+        if (islower(*p))
+            *p = toupper(*p);
+    }
+
+    /* Scan the collection for a cache with a client principal whose realm is
+     * the longest tail of the server hostname. */
+    ret = krb5_cccol_cursor_new(context, &col_cursor);
+    if (ret)
+        goto done;
+
+    for (ret = krb5_cccol_cursor_next(context, col_cursor, &ccache);
+         ret == 0 && ccache != NULL;
+         ret = krb5_cccol_cursor_next(context, col_cursor, &ccache)) {
+        ret = krb5_cc_get_principal(context, ccache, &princ);
+        if (ret) {
+            krb5_cc_close(context, ccache);
+            break;
+        }
+
+        /* Check for a longer match than we have. */
+        domain = make_data(host, hostlen);
+        while (best_princ == NULL ||
+               best_princ->realm.length < domain.length) {
+            if (data_eq(princ->realm, domain)) {
+                SWAP(best_ccache, ccache, tmp_ccache);
+                SWAP(best_princ, princ, tmp_princ);
+                break;
+            }
+
+            /* Try the next parent domain. */
+            p = memchr(domain.data, '.', domain.length);
+            if (p == NULL)
+                break;
+            domain = make_data(p + 1, hostlen - (p + 1 - host));
+        }
+
+        if (ccache != NULL)
+            krb5_cc_close(context, ccache);
+        krb5_free_principal(context, princ);
+    }
+
+    krb5_cccol_cursor_free(context, &col_cursor);
+
+    if (best_ccache != NULL) {
+        *ccache_out = best_ccache;
+        *princ_out = best_princ;
+    } else {
+        ret = KRB5_PLUGIN_NO_HANDLE;
+    }
+
+done:
+    free(host);
+    return ret;
+}
+
+krb5_error_code
+ccselect_hostname_initvt(krb5_context context, int maj_ver, int min_ver,
+                         krb5_plugin_vtable vtable)
+{
+    krb5_ccselect_vtable vt;
+
+    if (maj_ver != 1)
+        return KRB5_PLUGIN_VER_NOTSUPP;
+    vt = (krb5_ccselect_vtable)vtable;
+    vt->name = "hostname";
+    vt->init = hostname_init;
+    vt->choose = hostname_choose;
+    return 0;
+}
diff --git a/src/tests/gssapi/t_ccselect.py b/src/tests/gssapi/t_ccselect.py
index 668a2cc..3503f92 100755
--- a/src/tests/gssapi/t_ccselect.py
+++ b/src/tests/gssapi/t_ccselect.py
@@ -33,6 +33,7 @@ host1 = 'p:' + r1.host_princ
 host2 = 'p:' + r2.host_princ
 foo = 'foo.krbtest.com'
 foo2 = 'foo.krbtest2.com'
+foobar = "foo.bar.krbtest.com"
 
 # These strings specify the target as a GSS name.  The resulting
 # principal will have the host-based type, with the referral realm
@@ -42,6 +43,7 @@ foo2 = 'foo.krbtest2.com'
 # single component.
 gssserver = 'h:host@' + foo
 gssserver2 = 'h:host@' + foo2
+gssserver_bar = 'h:host@' + foobar
 gsslocal = 'h:host at localhost'
 
 # refserver specifies the target as a principal in the referral realm.
@@ -77,10 +79,12 @@ r1.addprinc('host/localhost')
 r2.addprinc('host/localhost')
 r1.addprinc('host/' + foo)
 r2.addprinc('host/' + foo2)
+r1.addprinc('host/' + foobar)
 r1.extract_keytab('host/localhost', r1.keytab)
 r2.extract_keytab('host/localhost', r2.keytab)
 r1.extract_keytab('host/' + foo, r1.keytab)
 r2.extract_keytab('host/' + foo2, r2.keytab)
+r1.extract_keytab('host/' + foobar, r1.keytab)
 
 # Get tickets for one user in each realm (zaphod will be primary).
 r1.kinit(alice, password('alice'))
@@ -128,6 +132,11 @@ output = r2.run(['./t_ccselect', gsslocal])
 if output != (zaphod + '\n'):
     fail('zaphod not chosen via default realm fallback')
 
+# Check that realm ccselect fallback works correctly
+r1.run(['./t_ccselect', gssserver_bar], expected_msg=alice)
+r2.kinit(zaphod, password('zaphod'))
+r1.run(['./t_ccselect', gssserver_bar], expected_msg=alice)
+
 # Get a second cred in r1 (bob will be primary).
 r1.kinit(bob, password('bob'))
 


More information about the cvs-krb5 mailing list