From aea02876e7b4fbabe0c290dfaa68d09671b75696 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Tue, 25 Oct 2011 00:43:41 -0500 Subject: [PATCH] Initial aname2lname plugin patch based on code from Love Included is a default plugin that searches a sorted text file where every line is of the form: [] If the username is missing in a matching line then an error is returned. If a matching line is not found then the next plugin will be allowed to run, if any. --- lib/krb5/aname_to_localname.c | 267 +++++++++++++++++++++++++++++++++- lib/krb5/krb5.conf.5 | 11 ++ 2 files changed, 276 insertions(+), 2 deletions(-) diff --git a/lib/krb5/aname_to_localname.c b/lib/krb5/aname_to_localname.c index 7bfd861da..9cfb5a309 100644 --- a/lib/krb5/aname_to_localname.c +++ b/lib/krb5/aname_to_localname.c @@ -33,18 +33,274 @@ #include "krb5_locl.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 */ +static krb5_error_code +an2ln_def_plug_init(krb5_context context, void **ctx) +{ + *ctx = NULL; + return 0; +} + +static void +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, + set_result_f set_res_f, void *set_res_ctx) +{ + krb5_error_code ret; + const char *an2ln_db_fname; + char *fdata = 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; + + an2ln_db_fname = krb5_config_get_string(context, NULL, "libdefaults", + "aname2lname-text-db", NULL); + if (an2ln_db_fname) + return KRB5_PLUGIN_NO_HANDLE; + + ret = krb5_unparse_name(context, princ, &unparsed); + if (ret) + return ret; + + fd = open(an2ln_db_fname, O_RDONLY); + if (fd == -1) { + ret = KRB5_PLUGIN_NO_HANDLE; + goto cleanup; + } + + if (fstat(fd, &st) == -1 || st.st_size == 0) { + 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; + 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; + } + } + +cleanup: + if (fd != -1) + close(fd); + free(unparsed); + free(fdata); + return ret; +} + +krb5plugin_an2ln_ftable an2ln_def_plug = { + 0, + an2ln_def_plug_init, + an2ln_def_plug_fini, + an2ln_def_plug_an2ln, +}; + +struct plctx { + krb5_const_principal aname; + heim_string_t luser; +}; + +static krb5_error_code KRB5_LIB_CALL +set_res(void *userctx, const char *res) +{ + struct plctx *plctx = userctx; + plctx->luser = heim_string_create(res); + if (plctx->luser == NULL) + return ENOMEM; + return 0; +} + +static krb5_error_code KRB5_LIB_CALL +plcallback(krb5_context context, + const void *plug, void *plugctx, void *userctx) +{ + const krb5plugin_an2ln_ftable *locate = plug; + struct plctx *plctx = userctx; + + if (plctx->luser) + return 0; + + return locate->an2ln(plugctx, context, plctx->aname, set_res, plctx); +} + +static krb5_error_code +an2lnplugin(krb5_context context, krb5_const_principal aname, heim_string_t *ures) +{ + struct plctx ctx; + + ctx.aname = aname; + ctx.luser = NULL; + + _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_AN2LN, + KRB5_PLUGIN_AN2LN_VERSION_0, + 0, &ctx, plcallback); + + if (ctx.luser == NULL) + return KRB5_NO_LOCALNAME; + + *ures = 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, + 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 heim_base_once_t reg_def_plugins = HEIM_BASE_ONCE_INIT; krb5_error_code ret; krb5_realm *lrealms, *r; + heim_string_t ures = NULL; 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); if (ret) return ret; @@ -78,13 +334,20 @@ krb5_aname_to_localname (krb5_context context, if (!userok) return KRB5_NO_LOCALNAME; - } else - return KRB5_NO_LOCALNAME; + } else { + ret = an2lnplugin(context, aname, &ures); + if (ret) + return ret; + res = heim_string_get_utf8(ures); + } len = strlen (res); if (len >= lnsize) return ERANGE; strlcpy (lname, res, lnsize); + if (ures) + heim_release(ures); + return 0; } diff --git a/lib/krb5/krb5.conf.5 b/lib/krb5/krb5.conf.5 index 519172463..1f9195441 100644 --- a/lib/krb5/krb5.conf.5 +++ b/lib/krb5/krb5.conf.5 @@ -260,6 +260,17 @@ If set to "ignore", the framework will ignore any the server input to .Xr krb5_rd_req 3, this is very useful when the GSS-API server input the wrong server name into the gss_accept_sec_context call. +.It Li aname2lname-text-db = Va filename +The named file must be a sorted (in increasing order) text file where +every line consists of an unparsed principal name optionally followed by +whitespace and a username. The aname2lname function will do a binary +search on this file, if configured, looking for lines that match the +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