From 632575ab12fc5d6c9bdc83cb8200fb8f4f422b83 Mon Sep 17 00:00:00 2001 From: Robbie Harwood 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 +#include + +/* 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'))