f1e535bb81
Adjust patches as appropriate
294 lines
10 KiB
Diff
294 lines
10 KiB
Diff
From 632575ab12fc5d6c9bdc83cb8200fb8f4f422b83 Mon Sep 17 00:00:00 2001
|
|
From: Robbie Harwood <rharwood@redhat.com>
|
|
Date: Wed, 23 Aug 2017 17:25:17 -0400
|
|
Subject: [PATCH] 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@mit.edu: minor edits]
|
|
|
|
ticket: 8613 (new)
|
|
(cherry picked from commit a4ddc6cf576b4155e6b994307902567f26f752b2)
|
|
---
|
|
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(+)
|
|
create mode 100644 src/lib/krb5/ccache/ccselect_hostname.c
|
|
|
|
diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst
|
|
index 1d9bc9e34..9c1ee94a4 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 5ac870728..f84cf793e 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 ee9b5e0e9..d920367ce 100644
|
|
--- a/src/lib/krb5/ccache/cc-int.h
|
|
+++ b/src/lib/krb5/ccache/cc-int.h
|
|
@@ -123,6 +123,10 @@ k5_cccol_force_unlock(void);
|
|
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 ee4b83a9b..393d39733 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 000000000..475cfabae
|
|
--- /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 668a2cc62..3503f9269 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@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'))
|
|
|