diff --git a/lib/krb5/Makefile.am b/lib/krb5/Makefile.am index 2df7d5b36..fc3310292 100644 --- a/lib/krb5/Makefile.am +++ b/lib/krb5/Makefile.am @@ -111,6 +111,7 @@ dist_libkrb5_la_SOURCES = \ acl.c \ add_et_list.c \ addr_families.c \ + an2ln_plugin.h \ aname_to_localname.c \ appdefault.c \ asn1_glue.c \ @@ -339,7 +340,7 @@ nodist_include_HEADERS = krb5_err.h heim_err.h k524_err.h # XXX use nobase_include_HEADERS = krb5/locate_plugin.h krb5dir = $(includedir)/krb5 -krb5_HEADERS = locate_plugin.h send_to_kdc_plugin.h ccache_plugin.h +krb5_HEADERS = locate_plugin.h send_to_kdc_plugin.h ccache_plugin.h an2ln_plugin.h build_HEADERZ = \ $(krb5_HEADERS) \ diff --git a/lib/krb5/an2ln_plugin.h b/lib/krb5/an2ln_plugin.h new file mode 100644 index 000000000..e1cd3ee20 --- /dev/null +++ b/lib/krb5/an2ln_plugin.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2006 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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. + */ + +/* $Id$ */ + +#ifndef HEIMDAL_KRB5_AN2LN_PLUGIN_H +#define HEIMDAL_KRB5_AN2LN_PLUGIN_H 1 + +#define KRB5_PLUGIN_AN2LN "an2ln" +#define KRB5_PLUGIN_AN2LN_VERSION_0 0 + +typedef krb5_error_code (*set_result_f)(void *, const char *); + +typedef struct krb5plugin_an2ln_ftable_desc { + int minor_version; + krb5_error_code (*init)(krb5_context, void **); + void (*fini)(void *); + krb5_error_code (*an2ln)(void *, krb5_context, const char *, + krb5_const_principal, set_result_f, void *); +} krb5plugin_an2ln_ftable; + +#endif /* HEIMDAL_KRB5_AN2LN_PLUGIN_H */ + diff --git a/lib/krb5/aname_to_localname.c b/lib/krb5/aname_to_localname.c index 9cfb5a309..6defbea53 100644 --- a/lib/krb5/aname_to_localname.c +++ b/lib/krb5/aname_to_localname.c @@ -32,20 +32,9 @@ */ #include "krb5_locl.h" +#include "an2ln_plugin.h" -#define KRB5_PLUGIN_AN2LN "an2ln" -#define KRB5_PLUGIN_AN2LN_VERSION_0 0 - -typedef krb5_error_code (*set_result_f)(void *, const char *); - -typedef struct krb5plugin_an2ln_ftable_desc { - int minor_version; - krb5_error_code (*init)(krb5_context, void **); - void (*fini)(void *); - krb5_error_code (*an2ln)(void *, krb5_context, krb5_const_principal, set_result_f, void *); -} krb5plugin_an2ln_ftable; - -/* Default plugin follows */ +/* Default plugin (DB using binary search of sorted text file) follows */ static krb5_error_code an2ln_def_plug_init(krb5_context context, void **ctx) { @@ -58,164 +47,75 @@ an2ln_def_plug_fini(void *ctx) { } -/* Find a non-quoted new-line */ -static char * -find_line(char *buf, size_t i, size_t right) -{ - for (; i < right; i++) { - /* Seek a two non-quote char sequence */ - if (buf[i] != '\\' && (i + 1) < right && buf[i + 1] != '\\') { - /* Seek a non-quoted new-line */ - for (i += 1; i < right; i++) { - if (buf[i] == '\n') - break; - if (buf[i] == '\\' && (i + 1) < right && buf[i + 1] != '\n') - i++; /* skip quoted char */ - } - break; - } - } - - if (buf[i] == '\n' && (i + 1) < right) - return &buf[i + 1]; - return NULL; -} - static krb5_error_code an2ln_def_plug_an2ln(void *plug_ctx, krb5_context context, - krb5_const_principal princ, + const char *rule, + krb5_const_principal aname, set_result_f set_res_f, void *set_res_ctx) { krb5_error_code ret; const char *an2ln_db_fname; - char *fdata = NULL; + const char *ext; + bsearch_file_handle bfh = NULL; char *unparsed = NULL; - char *cp; - char *p; - char *u; - int fd = -1; - int cmp; - size_t sz, l, r, i, k; - struct stat st; + char *value = NULL; - an2ln_db_fname = krb5_config_get_string(context, NULL, "libdefaults", - "aname2lname-text-db", NULL); - if (an2ln_db_fname) + if (strncmp(rule, "DB:", strlen("DB:") != 0)) return KRB5_PLUGIN_NO_HANDLE; - ret = krb5_unparse_name(context, princ, &unparsed); + /* + * This plugin implements a binary search of a sorted text file + * (sorted in the C locale). We really need to know that the file + * is text, so we implement a trivial heuristic: the file name must + * end in .txt. + */ + an2ln_db_fname = &rule[strlen("DB:")]; + if (!*an2ln_db_fname) + return KRB5_PLUGIN_NO_HANDLE; + if (strlen(an2ln_db_fname) < (strlen(".txt") + 1)) + return KRB5_PLUGIN_NO_HANDLE; + ext = strrchr(an2ln_db_fname, '.'); + if (!ext || strcmp(ext, ".txt") != 0) + return KRB5_PLUGIN_NO_HANDLE; + + ret = krb5_unparse_name(context, aname, &unparsed); if (ret) return ret; - fd = open(an2ln_db_fname, O_RDONLY); - if (fd == -1) { + ret = __bsearch_file_open(an2ln_db_fname, 0, 0, &bfh, NULL); + if (ret) { + krb5_set_error_message(context, ret, + N_("Couldn't open aname2lname-text-db", "")); ret = KRB5_PLUGIN_NO_HANDLE; goto cleanup; } - if (fstat(fd, &st) == -1 || st.st_size == 0) { + /* Binary search; file should be sorted (in C locale) */ + ret = __bsearch_file(bfh, unparsed, &value, NULL, NULL, NULL); + if (ret > 0) { + krb5_set_error_message(context, ret, + N_("Couldn't map principal name to username", "")); ret = KRB5_PLUGIN_NO_HANDLE; goto cleanup; - } - - /* - * This is a dead-simple DB, so simple that we read the whole file - * in and do the search in memory. This means that in 32-bit - * processes we can't handle large files. But this should not be a - * large file anyways, else use another plugin. - */ - sz = (size_t)st.st_size; - if (st.st_size != (off_t)sz) { - ret = E2BIG; + } else if (ret < 0) { + ret = KRB5_PLUGIN_NO_HANDLE; goto cleanup; - } - - fdata = malloc(sz + 1); - if (fdata == NULL) { - ret = krb5_enomem(context); - goto cleanup; - } - if (read(fd, fdata, sz) < sz) { - krb5_set_error_message(context, errno, "read: reading aname2lname DB"); - ret = errno; - goto cleanup; - } - fdata[sz] = '\0'; - close(fd); - fd = -1; - - /* Binary search; file should be sorted */ - for (l = 0, r = sz, i = sz >> 1; i > l && i < r; ) { - heim_assert(i > 0 && i < sz, "invalid aname2lname db index"); - - /* fdata[i] is likely in the middle of a line; find the next line */ - cp = find_line(fdata, i, r); - if (cp == NULL) { - /* - * No new line found to the right; search to the left then - * (this isn't optimal, but it's simple) - */ - r = i; - i = (r - l) >> 1; - } - i = cp - fdata; - heim_assert(i > l && i < r, "invalid aname2lname db index"); - - /* Got a line; check it */ - - /* Search for and split on unquoted whitespace */ - for (p = &fdata[i], u = NULL, k = i; k < r; k++) { - if (fdata[k] == '\\') { - k++; - continue; - } - /* The one concession to CRLF here */ - if (fdata[k] == '\r' || fdata[k] == '\n') { - fdata[k] = '\0'; - break; - } - if (isspace(fdata[k])) { - fdata[k] = '\0'; - for (; k < r; k++) { - if (fdata[k] == '\\') { - k++; - continue; - } - if (fdata[k] == '\n') - fdata[k] = '\0'; - while (isspace(fdata[k])) - k++; - break; - } - u = &fdata[k]; - break; - } - } - - cmp = strcmp(p, unparsed); - if (cmp < 0) { - /* search left */ - r = i; - i = (r - l) >> 1; - } else if (cmp > 0) { - /* search right */ - l = i; - i = (r - l) >> 1; - } else { - /* match! */ - if (u == NULL) - ret = KRB5_NO_LOCALNAME; - else - ret = set_res_f(set_res_ctx, u); - break; + } else { + /* ret == 0 -> found */ + if (!value || !*value) { + krb5_set_error_message(context, ret, + N_("Principal mapped to empty username", "")); + ret = KRB5_NO_LOCALNAME; + goto cleanup; } + ret = set_res_f(set_res_ctx, value); } cleanup: - if (fd != -1) - close(fd); + if (bfh) + __bsearch_file_close(&bfh); free(unparsed); - free(fdata); + free(value); return ret; } @@ -226,9 +126,11 @@ krb5plugin_an2ln_ftable an2ln_def_plug = { an2ln_def_plug_an2ln, }; +/* Plugin engine code follows */ struct plctx { krb5_const_principal aname; heim_string_t luser; + const char *rule; }; static krb5_error_code KRB5_LIB_CALL @@ -251,59 +153,64 @@ plcallback(krb5_context context, if (plctx->luser) return 0; - return locate->an2ln(plugctx, context, plctx->aname, set_res, plctx); + return locate->an2ln(plugctx, context, plctx->rule, plctx->aname, set_res, plctx); } static krb5_error_code -an2lnplugin(krb5_context context, krb5_const_principal aname, heim_string_t *ures) +an2ln_plugin(krb5_context context, const char *rule, krb5_const_principal aname, + size_t lnsize, char *lname) { + krb5_error_code ret; struct plctx ctx; + ctx.rule = rule; ctx.aname = aname; ctx.luser = NULL; - _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_AN2LN, - KRB5_PLUGIN_AN2LN_VERSION_0, - 0, &ctx, plcallback); - + /* + * Order of plugin invocation is non-deterministic, but there should + * really be no more than one plugin that can handle any given kind + * rule, so the effect should be deterministic anyways. + */ + ret = _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_AN2LN, + KRB5_PLUGIN_AN2LN_VERSION_0, 0, &ctx, plcallback); + if (ret != 0) { + heim_release(ctx.luser); + return ret; + } + if (ctx.luser == NULL) - return KRB5_NO_LOCALNAME; + return KRB5_PLUGIN_NO_HANDLE; - *ures = ctx.luser; + if (strlcpy(lname, heim_string_get_utf8(ctx.luser), lnsize) >= lnsize) + ret = KRB5_CONFIG_NOTENUFSPACE; + heim_release(ctx.luser); return 0; } - static void reg_def_plugins_once(void *ctx) { krb5_error_code ret; krb5_context context = ctx; - ret = krb5_plugin_register(context, PLUGIN_TYPE_FUNC, + ret = krb5_plugin_register(context, PLUGIN_TYPE_DATA, KRB5_PLUGIN_AN2LN, &an2ln_def_plug); } -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_aname_to_localname (krb5_context context, - krb5_const_principal aname, - size_t lnsize, - char *lname) +static int +princ_realm_is_default(krb5_context context, + krb5_const_principal aname) { - static heim_base_once_t reg_def_plugins = HEIM_BASE_ONCE_INIT; krb5_error_code ret; - krb5_realm *lrealms, *r; - heim_string_t ures = NULL; + krb5_realm *lrealms = NULL; + krb5_realm *r; int valid; - size_t len; - const char *res; - heim_base_once_f(®_def_plugins, context, reg_def_plugins_once); - - ret = krb5_get_default_realms (context, &lrealms); + ret = krb5_get_default_realms(context, &lrealms); if (ret) - return ret; + return 0; valid = 0; for (r = lrealms; *r != NULL; ++r) { @@ -313,13 +220,96 @@ krb5_aname_to_localname (krb5_context context, } } krb5_free_host_realm (context, lrealms); - if (valid == 0) - return KRB5_NO_LOCALNAME; + return valid; +} - if (aname->name.name_string.len == 1) +/* + * This function implements MIT's auth_to_local_names configuration for + * configuration compatibility. Specifically: + * + * [realms] + * = { + * auth_to_local_names = { + * = + * } + * } + * + * If multiple usernames are configured then the last one is taken. + * + * The configuration can only be expected to hold a relatively small + * number of mappings. For lots of mappings use a DB. + */ +static krb5_error_code +an2ln_local_names(krb5_context context, + krb5_const_principal aname, + size_t lnsize, + char *lname) +{ + krb5_error_code ret; + char *unparsed; + char **values; + char *res; + size_t i; + + if (!princ_realm_is_default(context, aname)) + return KRB5_PLUGIN_NO_HANDLE; + + ret = krb5_unparse_name_flags(context, aname, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, + &unparsed); + if (ret) + return ret; + + ret = KRB5_PLUGIN_NO_HANDLE; + values = krb5_config_get_strings(context, NULL, "realms", aname->realm, + "auth_to_local_names", unparsed, NULL); + free(unparsed); + if (!values) + return ret; + /* Take the last value, just like MIT */ + for (res = NULL, i = 0; values[i]; i++) + res = values[i]; + if (res) { + ret = 0; + if (strlcpy(lname, res, lnsize) >= lnsize) + ret = KRB5_CONFIG_NOTENUFSPACE; + + if (!*res || strcmp(res, ":") == 0) + ret = KRB5_NO_LOCALNAME; + } + + krb5_config_free_strings(values); + return ret; +} + +/* + * Heimdal's default aname2lname mapping. + */ +static krb5_error_code +an2ln_default(krb5_context context, + int root_princs_ok, + krb5_const_principal aname, + size_t lnsize, char *lname) +{ + krb5_error_code ret; + const char *res; + + if (!princ_realm_is_default(context, aname)) + return KRB5_PLUGIN_NO_HANDLE; + + if (aname->name.name_string.len == 1) { + /* + * One component principal names in default realm -> the one + * component is the username. + */ res = aname->name.name_string.val[0]; - else if (aname->name.name_string.len == 2 - && strcmp (aname->name.name_string.val[1], "root") == 0) { + } else if (aname->name.name_string.len == 2 && + strcmp (aname->name.name_string.val[1], "root") == 0) { + /* + * Two-component principal names in default realm where the + * first component is "root" -> root IFF the principal is in + * root's .k5login (or whatever krb5_kuserok() does). + */ krb5_principal rootprinc; krb5_boolean userok; @@ -333,21 +323,91 @@ krb5_aname_to_localname (krb5_context context, krb5_free_principal(context, rootprinc); if (!userok) return KRB5_NO_LOCALNAME; - } else { - ret = an2lnplugin(context, aname, &ures); - if (ret) - return ret; - res = heim_string_get_utf8(ures); + return KRB5_PLUGIN_NO_HANDLE; } - len = strlen (res); - if (len >= lnsize) - return ERANGE; - strlcpy (lname, res, lnsize); - - if (ures) - heim_release(ures); + if (strlcpy(lname, res, lnsize) >= lnsize) + return KRB5_CONFIG_NOTENUFSPACE; return 0; } + +/** + * Map a principal name to a local username. + * + * Returns 0 on success, KRB5_NO_LOCALNAME if no mapping was found, or + * some Kerberos or system error. + * + * Inputs: + * + * @context A krb5_context + * @aname A principal name + * @lnsize The size of the buffer into which the username will be written + * @lname The buffer into which the username will be written + */ +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_aname_to_localname(krb5_context context, + krb5_const_principal aname, + size_t lnsize, + char *lname) +{ + static heim_base_once_t reg_def_plugins = HEIM_BASE_ONCE_INIT; + krb5_error_code ret; + size_t i; + char **rules = NULL; + char *rule; + + if (lnsize) + lname[0] = '\0'; + + heim_base_once_f(®_def_plugins, context, reg_def_plugins_once); + + /* Try MIT's auth_to_local_names config first */ + ret = an2ln_local_names(context, aname, lnsize, lname); + if (ret != KRB5_PLUGIN_NO_HANDLE) + return ret; + + rules = krb5_config_get_strings(context, NULL, "realms", aname->realm, + "auth_to_local", NULL); + if (!rules) { + /* Heimdal's default rule */ + ret = an2ln_default(context, 1, aname, lnsize, lname); + if (ret == KRB5_PLUGIN_NO_HANDLE) + return KRB5_NO_LOCALNAME; + return ret; + } + + /* MIT rules */ + for (ret = KRB5_PLUGIN_NO_HANDLE, i = 0; rules[i]; i++) { + rule = rules[i]; + if (!*rule || strcmp(rule, "NONE") == 0) + break; + else if (strcmp(rule, "HEIMDAL_DEFAULT") == 0) + ret = an2ln_default(context, 1, aname, lnsize, lname); + else if (strcmp(rule, "DEFAULT") == 0) + ret = an2ln_default(context, 0, aname, lnsize, lname); + else + /* Let the plugins handle DBs and RULEs and anything else*/ + ret = an2ln_plugin(context, rule, aname, lnsize, lname); + + if (ret == 0 && lnsize && lname[0]) + break; + /* + * Note that RULEs and DBs only have white-list functionality, + * thus RULEs and DBs that we don't understand we simply ignore. + * + * This means that plugins that implement black-lists are + * dangerous: if a black-list plugin isn't found, the black-list + * won't be enforced. But black-lists are dangerous anyways. + */ + if (ret != KRB5_PLUGIN_NO_HANDLE) + break; + } + + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = KRB5_NO_LOCALNAME; + + krb5_config_free_strings(rules); + return ret; +} diff --git a/lib/krb5/krb5.conf.5 b/lib/krb5/krb5.conf.5 index 1f9195441..6430eefcb 100644 --- a/lib/krb5/krb5.conf.5 +++ b/lib/krb5/krb5.conf.5 @@ -269,8 +269,6 @@ given principal name, and if found the given username will be used, or, if the username is missing, an error will be returned. If the file doesn't exist, or if no matching line is found then other plugins will be allowed to run. Note: large -.Va aname2lname-text-db -files are not supported on small memory systems/processes. .It Li name_canon_rules = Va rules One or more name canonicalization rules. Each rule consists of one or more tokens separated by colon (':'). The first token must be a rule @@ -363,6 +361,62 @@ See .It Li tgs_require_subkey a boolan variable that defaults to false. Old DCE secd (pre 1.1) might need this to be true. +.It Li auth_to_local_names = { +.Bl -tag -width "xxx" -offset indent +.It Va principal_name = Va username +The given +.Va principal_name +will be mapped to the given +.Va username +if the +.Va REALM +is a default realm. +.El +.It Li } +.It Li auth_to_local = HEIMDAL_DEFAULT +Use the Heimdal default principal to username mapping. +Applies to principals from the +.Va REALM +if and only if +.Va REALM +is a default realm. +.It Li auth_to_local = DEFAULT +Use the MIT default principal to username mapping. +Applies to principals from the +.Va REALM +if and only if +.Va REALM +is a default realm. +.It Li auth_to_local = DB:/path/to/db.txt +Use a binary search of the given DB. The DB must be a flat-text +file sortedf in the "C" locale, with each record being a line +(separated by either LF or CRLF) consisting of a principal name +followed by whitespace followed by a username. +Applies to principals from the +.Va REALM +if and only if +.Va REALM +is a default realm. +.It Li auth_to_local = DB:/path/to/db +Use the given DB, if there's a plugin for it. +Applies to principals from the +.Va REALM +if and only if +.Va REALM +is a default realm. +.It Li auth_to_local = RULE:... +Use the given rule, if there's a plugin for it. +Applies to principals from the +.Va REALM +if and only if +.Va REALM +is a default realm. +.It Li auth_to_local = NONE +No additional principal to username mapping is done. Note that +.Va auth_to_local_names +and any preceding +.Va auth_to_local +rules have precedence. .El .It Li } .El