From: Greg Hudson Date: Mon, 5 Sep 2011 16:33:49 +0000 (+0000) Subject: Add krb5_cc_select() API and pluggable interface X-Git-Tag: krb5-1.10-alpha1~217 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=02536734d9443f62b75c3df97b4079fbe3d3e6cf;p=krb5.git Add krb5_cc_select() API and pluggable interface The interface has two built-in modules. The realm module guesses a cache based on the server realm if it is known. The k5identity module (Unix only) chooses a client principal based on rules in a .k5identity file in the user's homedir. ticket: 6957 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25158 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/src/gen-manpages/Makefile.in b/src/gen-manpages/Makefile.in index 803b5046d..7a356b3d5 100644 --- a/src/gen-manpages/Makefile.in +++ b/src/gen-manpages/Makefile.in @@ -6,3 +6,7 @@ install:: $(INSTALL_DATA) $(srcdir)/kerberos.M ${DESTDIR}$(CLIENT_MANDIR)/kerberos.1 $(INSTALL_DATA) $(srcdir)/k5login.M ${DESTDIR}$(FILE_MANDIR)/.k5login.5 $(INSTALL_DATA) $(srcdir)/k5login.M ${DESTDIR}$(FILE_MANDIR)/k5login.5 + $(INSTALL_DATA) $(srcdir)/k5identity.M \ + ${DESTDIR}$(FILE_MANDIR)/k5identity.5 + $(INSTALL_DATA) $(srcdir)/dot.k5identity.M \ + ${DESTDIR}$(FILE_MANDIR)/.k5identity.5 diff --git a/src/gen-manpages/dot.k5identity.M b/src/gen-manpages/dot.k5identity.M new file mode 100644 index 000000000..8af572af1 --- /dev/null +++ b/src/gen-manpages/dot.k5identity.M @@ -0,0 +1 @@ +.so man5/k5identity.5 diff --git a/src/gen-manpages/k5identity.M b/src/gen-manpages/k5identity.M new file mode 100644 index 000000000..8161eaec3 --- /dev/null +++ b/src/gen-manpages/k5identity.M @@ -0,0 +1,57 @@ +.TH .K5LOGIN 5 +.SH NAME +\&.k5identity \- Kerberos V5 client principal selection rules +.SH DESCRIPTION +The \fB.k5identity\fP file, which resides in a user's home directory, +contains a list of rules for selecting a client principals based on +the server being accessed. These rules are used to choose a +credential cache within the cache collection when possible. +.PP +Blank lines and lines beginning with '#' are ignored. Each line has +the form: +.PP +.RS +\fIprincipal\fP \fIfield\fP=\fIvalue\fP ... +.RE +.PP +If the server principal meets all of the \fIfield\fP constraints, then +\fIprincipal\fP is chosen as the client principal. The following +fields are recognized: +.TP +.B realm +If the realm of the server principal is known, it is matched against +\fIvalue\fP, which may be a pattern using shell wildcards. For +host-based server principals, the realm will generally only be known +if there is a domain_realm section in krb5.conf with a mapping for the +hostname. +.TP +.B service +If the server principal is a host-based principal, its service +component is matched against \fIvalue\fP, which may be a pattern using +shell wildcards. +.TP +.B host +If the server principal is a host-based principal, its hostname +component is converted to lower case and matched against \fIvalue\fP, +which may be a pattern using shell wildcards. +.PP +If the server principal matches the constraints of multiple lines in +the \fB.k5identity\fP file, the principal from the first matching line +is used. If no line matches, credentials will be selected some other +way, such as the realm heuristic or the current primary cache. +.SH EXAMPLE +The following example \fB.k5identity\fP file selects the client +principal alice@KRBTEST.COM if the server principal is within that +realm, the principal alice/root@EXAMPLE.COM if the server host is +within a servers subdomain, and the principal alice/mail@EXAMPLE.COM +when accessing the IMAP service on mail.example.com. +.PP +.RS +.nf +alice@KRBTEST.COM realm=KRBTEST.COM +alice/root@EXAMPLE.COM host=*.servers.example.com +alice/mail@EXAMPLE.COM host=mail.example.com service=imap +.fi +.RE +.SH SEE ALSO +kerberos(1), krb5.conf(5) diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 0e82ce8a9..075cec8c7 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -1378,7 +1378,8 @@ struct plugin_interface { #define PLUGIN_INTERFACE_KADM5_HOOK 1 #define PLUGIN_INTERFACE_CLPREAUTH 2 #define PLUGIN_INTERFACE_KDCPREAUTH 3 -#define PLUGIN_NUM_INTERFACES 4 +#define PLUGIN_INTERFACE_CCSELECT 4 +#define PLUGIN_NUM_INTERFACES 5 /* Retrieve the plugin module of type interface_id and name modname, * storing the result into module. */ @@ -1418,6 +1419,7 @@ struct _kdb5_dal_handle; /* private, in kdb5.h */ typedef struct _kdb5_dal_handle kdb5_dal_handle; struct _kdb_log_context; typedef struct krb5_preauth_context_st krb5_preauth_context; +struct ccselect_module_handle; struct _krb5_context { krb5_magic magic; krb5_enctype *in_tkt_etypes; @@ -1458,6 +1460,9 @@ struct _krb5_context { /* preauth module stuff */ krb5_preauth_context *preauth_context; + /* cache module stuff */ + struct ccselect_module_handle **ccselect_handles; + /* error detail info */ struct errinfo err; diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h index 9b4c6095b..8bf88e417 100644 --- a/src/include/k5-trace.h +++ b/src/include/k5-trace.h @@ -119,7 +119,29 @@ #define TRACE_CC_STORE_TKT(c, cache, creds) \ TRACE(c, (c, "Also storing {creds} based on ticket", creds)) -#define TRACE_FAST_ARMOR_CCACHE(c, ccache_name) \ +#define TRACE_CCSELECT_VTINIT_FAIL(c, ret) \ + TRACE(c, (c, "ccselect module failed to init vtable: {kerr}", ret)) +#define TRACE_CCSELECT_INIT_FAIL(c, name, ret) \ + TRACE(c, (c, "ccselect module {str} failed to init: {kerr}", name, ret)) +#define TRACE_CCSELECT_MODCHOICE(c, name, server, cache, princ) \ + TRACE(c, (c, "ccselect module {str} chose cache {ccache} with client " \ + "principal {princ} for server principal {princ}", name, cache, \ + princ, server)) +#define TRACE_CCSELECT_MODNOTFOUND(c, name, server, princ) \ + TRACE(c, (c, "ccselect module {str} chose client principal {princ} " \ + "for server principal {princ} but found no cache", name, princ, \ + server)) +#define TRACE_CCSELECT_MODFAIL(c, name, ret, server) \ + TRACE(c, (c, "ccselect module {str} yielded error {kerr} for server " \ + "principal {princ}", name, ret, server)) +#define TRACE_CCSELECT_NOTFOUND(c, server) \ + TRACE(c, (c, "ccselect can't find appropriate cache for server " \ + "principal {princ}", server)) +#define TRACE_CCSELECT_DEFAULT(c, cache, server) \ + TRACE(c, (c, "ccselect choosing default cache {ccache} for server " \ + "principal {princ}", cache, server)) + +#define TRACE_FAST_ARMOR_CCACHE(c, ccache_name) \ TRACE(c, (c, "FAST armor ccache: {str}", ccache_name)) #define TRACE_FAST_ARMOR_CCACHE_KEY(c, keyblock) \ TRACE(c, (c, "Armor ccache sesion key: {keyblock}", keyblock)) diff --git a/src/include/krb5/ccselect_plugin.h b/src/include/krb5/ccselect_plugin.h new file mode 100644 index 000000000..ab3270bbd --- /dev/null +++ b/src/include/krb5/ccselect_plugin.h @@ -0,0 +1,105 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright (C) 2011 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +/* + * Declarations for credential cache selection module implementors. + * + * The ccselect pluggable interface currently has only one supported major + * version, which is 1. Major version 1 has a current minor version number of + * 1. + * + * Password quality plugin modules should define a function named + * ccselect__initvt, matching the signature: + * + * krb5_error_code + * ccselect_modname_initvt(krb5_context context, int maj_ver, int min_ver, + * krb5_plugin_vtable vtable); + * + * The initvt function should: + * + * - Check that the supplied maj_ver number is supported by the module, or + * return KRB5_PLUGIN_VER_NOTSUPP if it is not. + * + * - Cast the vtable pointer as appropriate for maj_ver: + * maj_ver == 1: Cast to krb5_ccselect_vtable + * + * - Initialize the methods of the vtable, stopping as appropriate for the + * supplied min_ver. Optional methods may be left uninitialized. + * + * Memory for the vtable is allocated by the caller, not by the module. + */ + +#ifndef KRB5_CCSELECT_PLUGIN_H +#define KRB5_CCSELECT_PLUGIN_H + +#include +#include + +/* An abstract type for password quality module data. */ +typedef struct krb5_ccselect_moddata_st *krb5_ccselect_moddata; + +#define KRB5_CCSELECT_PRIORITY_AUTHORITATIVE 2 +#define KRB5_CCSELECT_PRIORITY_HEURISTIC 1 + +/*** Method type declarations ***/ + +/* + * Mandatory: Initialize module data and set *priority_out to one of the + * KRB5_CCSELECT_PRIORITY constants above. Authoritative modules will be + * consulted before heuristic ones. + */ +typedef krb5_error_code +(*krb5_ccselect_init_fn)(krb5_context context, krb5_ccselect_moddata *data_out, + int *priority_out); + +/* + * Mandatory: Select a cache based on a server principal. Return 0 on success, + * with *cache_out set to the selected cache and *princ_out set to its default + * principal. Return KRB5_PLUGIN_NO_HANDLE to defer to other modules. Return + * KRB5_CC_NOTFOUND with *princ_out set if the client principal can be + * authoritatively determined but no cache exists for it. Return other errors + * as appropriate. + */ +typedef krb5_error_code +(*krb5_ccselect_choose_fn)(krb5_context context, krb5_ccselect_moddata data, + krb5_principal server, krb5_ccache *cache_out, + krb5_principal *princ_out); + +/* Optional: Release resources used by module data. */ +typedef void +(*krb5_ccselect_fini_fn)(krb5_context context, krb5_ccselect_moddata data); + +/*** vtable declarations **/ + +/* Password quality plugin vtable for major version 1. */ +typedef struct krb5_ccselect_vtable_st { + const char *name; /* Mandatory: name of module. */ + krb5_ccselect_init_fn init; + krb5_ccselect_choose_fn choose; + krb5_ccselect_fini_fn fini; + /* Minor version 1 ends here. */ +} *krb5_ccselect_vtable; + +#endif /* KRB5_CCSELECT_PLUGIN_H */ diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index d1a7c83c6..049bf91f1 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -4453,6 +4453,42 @@ krb5_error_code KRB5_CALLCONV krb5_cc_cache_match(krb5_context context, krb5_principal client, krb5_ccache *cache_out); +/** + * Select a credential cache to use with a server principal. + * + * @param [in] context Library context + * @param [in] server Server principal + * @param [out] cache_out Credential cache handle + * @param [out] princ_out Client principal + * + * Select a cache within the collection containing credentials most appropriate + * for use with @a server, according to configured rules and heuristics. + * + * Use krb5_cc_close() to release @a cache_out when it is no longer needed. + * Use krb5_free_principal() to release @a princ_out when it is no longer + * needed. Note that @a princ_out is set in some error conditions. + * + * @return + * If an appropriate cache is found, 0 is returned, @a cache_out is set to the + * selected cache, and @a princ_out is set to the default principal of that + * cache. + * + * If the appropriate client principal can be authoritatively determined but + * the cache collection contains no credentials for that principal, then + * KRB5_CC_NOTFOUND is returned, @a cache_out is set to NULL, and @a princ_out + * is set to the appropriate client principal. + * + * If no configured mechanism can determine the appropriate cache or principal, + * KRB5_CC_NOTFOUND is returned and @a cache_out and @a princ_out are set to + * NULL. + * + * Any other error code indicates a fatal error in the processing of a cache + * selection mechanism. + */ +krb5_error_code KRB5_CALLCONV +krb5_cc_select(krb5_context context, krb5_principal server, + krb5_ccache *cache_out, krb5_principal *princ_out); + /* krb5_free.c */ /** * Free the storage assigned to a principal. diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in index 00aaae7d6..a09415106 100644 --- a/src/lib/krb5/ccache/Makefile.in +++ b/src/lib/krb5/ccache/Makefile.in @@ -24,6 +24,9 @@ STLIBOBJS= \ cccursor.o \ ccdefault.o \ ccdefops.o \ + ccselect.o \ + ccselect_k5identity.o \ + ccselect_realm.o \ cc_dir.o \ cc_retr.o \ cc_file.o \ @@ -37,6 +40,9 @@ OBJS= $(OUTPRE)ccbase.$(OBJEXT) \ $(OUTPRE)cccursor.$(OBJEXT) \ $(OUTPRE)ccdefault.$(OBJEXT) \ $(OUTPRE)ccdefops.$(OBJEXT) \ + $(OUTPRE)ccselect.$(OBJEXT) \ + $(OUTPRE)ccselect_k5identity.$(OBJEXT) \ + $(OUTPRE)ccselect_realm.$(OBJEXT) \ $(OUTPRE)cc_dir.$(OBJEXT) \ $(OUTPRE)cc_retr.$(OBJEXT) \ $(OUTPRE)cc_file.$(OBJEXT) \ @@ -50,6 +56,9 @@ SRCS= $(srcdir)/ccbase.c \ $(srcdir)/cccursor.c \ $(srcdir)/ccdefault.c \ $(srcdir)/ccdefops.c \ + $(srcdir)/ccselect.c \ + $(srcdir)/ccselect_k5identity.c \ + $(srcdir)/ccselect_realm.c \ $(srcdir)/cc_dir.c \ $(srcdir)/cc_retr.c \ $(srcdir)/cc_file.c \ diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h index aeb1447ce..9c24f20cc 100644 --- a/src/lib/krb5/ccache/cc-int.h +++ b/src/lib/krb5/ccache/cc-int.h @@ -115,4 +115,12 @@ k5_cccol_force_unlock(void); krb5_error_code krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id); +krb5_error_code +ccselect_realm_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + +krb5_error_code +ccselect_k5identity_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + #endif /* __KRB5_CCACHE_H__ */ diff --git a/src/lib/krb5/ccache/ccselect.c b/src/lib/krb5/ccache/ccselect.c new file mode 100644 index 000000000..235c0c6a4 --- /dev/null +++ b/src/lib/krb5/ccache/ccselect.c @@ -0,0 +1,179 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/ccselect.c - krb5_cc_select API and module loader */ +/* + * Copyright (C) 2011 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include "k5-int.h" +#include "cc-int.h" +#include +#include "../krb/int-proto.h" + +struct ccselect_module_handle { + struct krb5_ccselect_vtable_st vt; + krb5_ccselect_moddata data; + int priority; +}; + +static void +free_handles(krb5_context context, struct ccselect_module_handle **handles) +{ + struct ccselect_module_handle *h, **hp; + + if (handles == NULL) + return; + for (hp = handles; *hp != NULL; hp++) { + h = *hp; + if (h->vt.fini) + h->vt.fini(context, h->data); + free(h); + } + free(handles); +} + +static krb5_error_code +load_modules(krb5_context context) +{ + krb5_error_code ret; + struct ccselect_module_handle **list = NULL, *handle; + krb5_plugin_initvt_fn *modules = NULL, *mod; + size_t count; + +#ifndef _WIN32 + ret = k5_plugin_register(context, PLUGIN_INTERFACE_CCSELECT, "k5identity", + ccselect_k5identity_initvt); + if (ret != 0) + goto cleanup; +#endif + + ret = k5_plugin_register(context, PLUGIN_INTERFACE_CCSELECT, "realm", + ccselect_realm_initvt); + if (ret != 0) + goto cleanup; + + ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CCSELECT, &modules); + if (ret != 0) + goto cleanup; + + /* Allocate a large enough list of handles. */ + for (count = 0; modules[count] != NULL; count++); + list = k5alloc((count + 1) * sizeof(*list), &ret); + if (list == NULL) + goto cleanup; + + /* Initialize each module, ignoring ones that fail. */ + count = 0; + for (mod = modules; *mod != NULL; mod++) { + handle = k5alloc(sizeof(*handle), &ret); + if (handle == NULL) + goto cleanup; + ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&handle->vt); + if (ret != 0) { /* Failed vtable init is non-fatal. */ + TRACE_CCSELECT_VTINIT_FAIL(context, ret); + free(handle); + continue; + } + handle->data = NULL; + ret = handle->vt.init(context, &handle->data, &handle->priority); + if (ret != 0) { /* Failed initialization is non-fatal. */ + TRACE_CCSELECT_INIT_FAIL(context, handle->vt.name, ret); + free(handle); + continue; + } + list[count++] = handle; + list[count] = NULL; + } + list[count] = NULL; + + ret = 0; + context->ccselect_handles = list; + list = NULL; + +cleanup: + k5_plugin_free_modules(context, modules); + free_handles(context, list); + return ret; +} + +static krb5_error_code +choose(krb5_context context, struct ccselect_module_handle *h, + krb5_principal server, krb5_ccache *cache_out, + krb5_principal *princ_out) +{ + return h->vt.choose(context, h->data, server, cache_out, princ_out); +} + +krb5_error_code KRB5_CALLCONV +krb5_cc_select(krb5_context context, krb5_principal server, + krb5_ccache *cache_out, krb5_principal *princ_out) +{ + krb5_error_code ret; + int priority; + struct ccselect_module_handle **hp, *h; + krb5_ccache cache; + krb5_principal princ; + + *cache_out = NULL; + *princ_out = NULL; + + if (context->ccselect_handles == NULL) { + ret = load_modules(context); + if (ret) + return ret; + } + + /* Consult authoritative modules first, then heuristic ones. */ + for (priority = KRB5_CCSELECT_PRIORITY_AUTHORITATIVE; + priority >= KRB5_CCSELECT_PRIORITY_HEURISTIC; priority--) { + for (hp = context->ccselect_handles; *hp != NULL; hp++) { + h = *hp; + if (h->priority != priority) + continue; + ret = choose(context, h, server, &cache, &princ); + if (ret == 0) { + TRACE_CCSELECT_MODCHOICE(context, h->vt.name, server, cache, + princ); + *cache_out = cache; + *princ_out = princ; + return 0; + } else if (ret == KRB5_CC_NOTFOUND) { + TRACE_CCSELECT_MODNOTFOUND(context, h->vt.name, server, princ); + *princ_out = princ; + return ret; + } else if (ret != KRB5_PLUGIN_NO_HANDLE) { + TRACE_CCSELECT_MODFAIL(context, h->vt.name, ret, server); + return ret; + } + } + } + + TRACE_CCSELECT_NOTFOUND(context, server); + return KRB5_CC_NOTFOUND; +} + +void +k5_ccselect_free_context(krb5_context context) +{ + free_handles(context, context->ccselect_handles); + context->ccselect_handles = NULL; +} diff --git a/src/lib/krb5/ccache/ccselect_k5identity.c b/src/lib/krb5/ccache/ccselect_k5identity.c new file mode 100644 index 000000000..adf0fad26 --- /dev/null +++ b/src/lib/krb5/ccache/ccselect_k5identity.c @@ -0,0 +1,211 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/ccselect_k5identity.c - k5identity ccselect module */ +/* + * Copyright (C) 2011 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include "k5-int.h" +#include "cc-int.h" +#include +#include + +#ifndef _WIN32 + +#include + +static krb5_error_code +k5identity_init(krb5_context context, krb5_ccselect_moddata *data_out, + int *priority_out) +{ + *data_out = NULL; + *priority_out = KRB5_CCSELECT_PRIORITY_AUTHORITATIVE; + return 0; +} + +/* Match data (folded to lowercase if fold_case is set) against pattern. */ +static krb5_boolean +fnmatch_data(const char *pattern, krb5_data *data, krb5_boolean fold_case) +{ + char *str, *p; + int res; + + str = malloc(data->length + 1); + if (str == NULL) + return FALSE; + memcpy(str, data->data, data->length); + str[data->length] = '\0'; + + if (fold_case) { + for (p = str; *p != '\0'; p++) { + if (isupper((unsigned char)*p)) + *p = tolower((unsigned char)*p); + } + } + + res = fnmatch(pattern, str, 0); + free(str); + return (res == 0); +} + +/* Return true if server satisfies the constraint given by name and value. */ +static krb5_boolean +check_constraint(krb5_context context, const char *name, const char *value, + krb5_principal server) +{ + if (strcmp(name, "realm") == 0) { + return fnmatch_data(value, &server->realm, FALSE); + } else if (strcmp(name, "service") == 0) { + return (server->type == KRB5_NT_SRV_HST && server->length >= 2 && + fnmatch_data(value, &server->data[0], FALSE)); + } else if (strcmp(name, "host") == 0) { + return (server->type == KRB5_NT_SRV_HST && server->length >= 2 && + fnmatch_data(value, &server->data[1], TRUE)); + } + /* Assume unrecognized constraints are critical. */ + return FALSE; +} + +/* + * If line begins with a valid principal and server matches the constraints + * listed afterwards, set *princ_out to the client principal described in line + * and return true. Otherwise return false. May destructively affect line. + */ +static krb5_boolean +parse_line(krb5_context context, char *line, krb5_principal server, + krb5_principal *princ_out) +{ + const char *whitespace = " \t\r\n"; + char *princ, *princ_end, *field, *field_end, *sep; + + *princ_out = NULL; + + /* Find the bounds of the principal. */ + princ = line + strspn(line, whitespace); + if (*princ == '#') + return FALSE; + princ_end = princ + strcspn(princ, whitespace); + if (princ_end == princ) + return FALSE; + + /* Check all constraints. */ + field = princ_end + strspn(princ_end, whitespace); + while (*field != '\0') { + field_end = field + strcspn(field, whitespace); + if (*field_end != '\0') + *field_end++ = '\0'; + sep = strchr(field, '='); + if (sep == NULL) /* Malformed line. */ + return FALSE; + *sep = '\0'; + if (!check_constraint(context, field, sep + 1, server)) + return FALSE; + field = field_end + strspn(field_end, whitespace); + } + + *princ_end = '\0'; + return (krb5_parse_name(context, princ, princ_out) == 0); +} + +/* Determine the current user's homedir. Allow HOME to override the result for + * non-secure profiles; otherwise, use the euid's homedir from passwd. */ +static char * +get_homedir(krb5_context context) +{ + const char *homedir = NULL; + char pwbuf[BUFSIZ]; + struct passwd pwx, *pwd; + + if (!context->profile_secure) + homedir = getenv("HOME"); + + if (homedir == NULL) { + if (k5_getpwuid_r(geteuid(), &pwx, pwbuf, sizeof(pwbuf), &pwd) != 0) + return NULL; + homedir = pwd->pw_dir; + } + + return strdup(homedir); +} + +static krb5_error_code +k5identity_choose(krb5_context context, krb5_ccselect_moddata data, + krb5_principal server, krb5_ccache *cache_out, + krb5_principal *princ_out) +{ + krb5_error_code ret; + krb5_principal princ = NULL; + char *filename, *homedir; + FILE *fp; + char buf[256]; + + *cache_out = NULL; + *princ_out = NULL; + + /* Open the .k5identity file. */ + homedir = get_homedir(context); + if (homedir == NULL) + return KRB5_PLUGIN_NO_HANDLE; + ret = k5_path_join(homedir, ".k5identity", &filename); + free(homedir); + if (ret) + return ret; + fp = fopen(filename, "r"); + free(filename); + if (fp == NULL) + return KRB5_PLUGIN_NO_HANDLE; + + /* Look for a line with constraints matched by server. */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (parse_line(context, buf, server, &princ)) + break; + } + fclose(fp); + if (princ == NULL) + return KRB5_PLUGIN_NO_HANDLE; + + /* Look for a ccache with the appropriate client principal. If we don't + * find out, set *princ_out to indicate the desired client principal. */ + ret = krb5_cc_cache_match(context, princ, cache_out); + if (ret == 0 || ret == KRB5_CC_NOTFOUND) + *princ_out = princ; + else + krb5_free_principal(context, princ); + return ret; +} + +krb5_error_code +ccselect_k5identity_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 = "k5identity"; + vt->init = k5identity_init; + vt->choose = k5identity_choose; + return 0; +} + +#endif /* not _WIN32 */ diff --git a/src/lib/krb5/ccache/ccselect_realm.c b/src/lib/krb5/ccache/ccselect_realm.c new file mode 100644 index 000000000..3a4b4af13 --- /dev/null +++ b/src/lib/krb5/ccache/ccselect_realm.c @@ -0,0 +1,95 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/ccselect_realm.c - realm ccselect module */ +/* + * Copyright (C) 2011 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include "k5-int.h" +#include "cc-int.h" +#include + +static krb5_error_code +realm_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 +realm_choose(krb5_context context, krb5_ccselect_moddata data, + krb5_principal server, krb5_ccache *cache_out, + krb5_principal *princ_out) +{ + krb5_error_code ret; + krb5_cccol_cursor cursor; + krb5_ccache cache; + krb5_principal princ; + + *cache_out = NULL; + *princ_out = NULL; + + if (krb5_is_referral_realm(&server->realm)) + return KRB5_PLUGIN_NO_HANDLE; + + /* Scan the collection for a cache with a client principal in the same + * realm as the server principal. */ + ret = krb5_cccol_cursor_new(context, &cursor); + if (ret) + return ret; + while ((ret = krb5_cccol_cursor_next(context, cursor, &cache)) == 0 && + cache != NULL) { + ret = krb5_cc_get_principal(context, cache, &princ); + if (ret == 0) { + if (data_eq(princ->realm, server->realm)) + break; + krb5_free_principal(context, princ); + } + krb5_cc_close(context, cache); + } + krb5_cccol_cursor_free(context, &cursor); + if (ret) + return ret; + + if (cache == NULL) + return KRB5_PLUGIN_NO_HANDLE; + *cache_out = cache; + *princ_out = princ; + return 0; +} + +krb5_error_code +ccselect_realm_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 = "realm"; + vt->init = realm_init; + vt->choose = realm_choose; + return 0; +} diff --git a/src/lib/krb5/ccache/deps b/src/lib/krb5/ccache/deps index a5a6de203..0c4f411a1 100644 --- a/src/lib/krb5/ccache/deps +++ b/src/lib/krb5/ccache/deps @@ -56,6 +56,53 @@ ccdefops.so ccdefops.po $(OUTPRE)ccdefops.$(OBJEXT): \ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ $(top_srcdir)/include/krb5/preauth_plugin.h $(top_srcdir)/include/port-sockets.h \ $(top_srcdir)/include/socket-utils.h ccdefops.c fcc.h +ccselect.so ccselect.po $(OUTPRE)ccselect.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(srcdir)/../krb/int-proto.h $(top_srcdir)/include/k5-buf.h \ + $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \ + $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \ + $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/ccselect_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/krb5/preauth_plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h cc-int.h ccselect.c +ccselect_k5identity.so ccselect_k5identity.po $(OUTPRE)ccselect_k5identity.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/ccselect_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/krb5/preauth_plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + cc-int.h ccselect_k5identity.c +ccselect_realm.so ccselect_realm.po $(OUTPRE)ccselect_realm.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/ccselect_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/krb5/preauth_plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + cc-int.h ccselect_realm.c +cc_dir.so cc_dir.po $(OUTPRE)cc_dir.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ + $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \ + $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \ + $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/krb5/preauth_plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + cc-int.h cc_dir.c cc_retr.so cc_retr.po $(OUTPRE)cc_retr.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../krb/int-proto.h \ @@ -135,4 +182,12 @@ t_cc.so t_cc.po $(OUTPRE)t_cc.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ t_cc.c t_cccursor.so t_cccursor.po $(OUTPRE)t_cccursor.$(OBJEXT): \ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ - $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h t_cccursor.c + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/krb5/preauth_plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h t_cccursor.c diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c index f244d566e..40c9f9562 100644 --- a/src/lib/krb5/krb/init_ctx.c +++ b/src/lib/krb5/krb/init_ctx.c @@ -288,6 +288,7 @@ krb5_free_context(krb5_context ctx) ctx->trace_callback(ctx, NULL, ctx->trace_callback_data); #endif + k5_ccselect_free_context(ctx); k5_plugin_free_context(ctx); free(ctx->plugin_base_dir); diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h index 336c04571..9b975ab4a 100644 --- a/src/lib/krb5/krb/int-proto.h +++ b/src/lib/krb5/krb/int-proto.h @@ -178,4 +178,7 @@ krb5int_mk_setpw_req(krb5_context context, krb5_auth_context auth_context, krb5_data *ap_req, krb5_principal targetprinc, char *passwd, krb5_data *packet); +void +k5_ccselect_free_context(krb5_context context); + #endif /* KRB5_INT_FUNC_PROTO__ */ diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index a5b05b5fb..cff9d396d 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -107,6 +107,7 @@ initialize_krb5_error_table initialize_k5e1_error_table initialize_kv5m_error_table initialize_prof_error_table +k5_ccselect_free_context k5_free_serverlist k5_kt_get_principal k5_locate_kdc @@ -207,6 +208,7 @@ krb5_cc_remove_cred krb5_cc_resolve krb5_cc_retrieve_cred krb5_cc_retrieve_cred_default +krb5_cc_select krb5_cc_set_config krb5_cc_set_default_name krb5_cc_set_flags diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def index 0afa6e499..17d15b076 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -417,3 +417,4 @@ EXPORTS krb5_cc_support_switch @391 krb5_cc_switch @392 krb5_free_string @393 + krb5_cc_select @394