Make krb5_aname_to_localname() use the libheimbase binary search functions

This commit is contained in:
Nicolas Williams
2011-11-29 11:17:50 -06:00
parent 659c761213
commit f468ed4759
4 changed files with 358 additions and 190 deletions

View File

@@ -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) \

53
lib/krb5/an2ln_plugin.h Normal file
View File

@@ -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 */

View File

@@ -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(&reg_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]
* <realm-name> = {
* auth_to_local_names = {
* <unparsed-principal-name> = <username>
* }
* }
*
* 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(&reg_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;
}

View File

@@ -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