Files
heimdal/lib/krb5/aname_to_localname.c
Nicolas Williams f4ba41ebdd Pluggable libheimbase interface for DBs and misc libheimbase enhancements
[Code reviewed by Love Hörnquist Åstrand <lha@kth.se>]

    Added heim_db_*() entry points for dealing with databases, and
    make krb5_aname_to_localname() use it.

    The following enhancements to libheimbase are included:

     - Add heim_data_t and heim_string_t "reference" variants to
       avoid memory copies of potentially large data/strings.

       See heim_data_ref_create() and heim_string_ref_create().

     - Added enhancements to heim_array_t to allow their use for
       queues and stacks, and to improve performance.  See
       heim_array_insert_value().

     - Added XPath-like accessors for heim_object_t.  See
       heim_path_get(), heim_path_copy(), heim_path_create(), and
       heim_path_delete().  These are used extensively in the DB
       framework's generic composition of ACID support and in the
       test_base program

     - Made libheimbase more consistent with Core Foundation naming
       conventions.  See heim_{dict, array}_{get, copy}_value() and
       heim_path_{get, copy}().

     - Added functionality to and fixed bugs in base/json.c:
        - heim_serialize();
        - depth limit for JSON parsing (for DoS protection);
        - pretty-printing;
        - JSON compliance (see below);
        - flag options for parsing and serializing; these are needed
          because of impedance mismatches between heim_object_t and
          JSON (e.g., heim_dict_t allows non-string keys, but JSON
          does not; heimbase supports binary data, while JSON does
          not).

     - Added heim_error_enomem().

     - Enhanced the test_base program to test new functionality and
       to use heim_path*() to better test JSON encoding.  This
       includes some fuzz testing of JSON parsing, and running the
       test under valgrind.

     - Started to add doxygen documentation for libheimbase (but doc
       build for libheimbase is still incomplete).

    Note that there's still some incomplete JSON support:

     - JSON string quoting is not fully implemented;

     - libheimbase lacks support for real numbers, while JSON has
       it -- otherwise libheimbase is a superset of JSON,
       specifically in that any heim_object_t can be a key for an
       associative array.

    The following DB backends are supported natively:

     - "sorted-text", a binary search of sorted (in C locale), flat
       text files;

     - "json", a backend that stores DB contents serialized as JSON
       (this is intended for configuration-like contents).

    The DB framework supports:

     - multiple key/value tables per-DB
     - ACID transactions

    The DB framework also natively implements ACID transactions for
    any DB backends that a) do not provide transactions natively, b)
    do provide lock/unlock/sync methods (even on Windows).  This
    includes autocommit of DB updates outside transactions.

    Future DB enhancements may include:

     - add backends for various DB types (BDB, CDB, MDB, ...);

     - make libhdb use heim_db_t;

     - add a command-line tool for interfacing to databases via
       libheimbase (e.g., to get/set/delete values, create/copy/
       backup DBs, inspect history, check integrity);

     - framework-level transaction logging (with redo and undo
       logging), for generic incremental replication;

     - framework-level DB integrity checking.

       We could store a MAC of the XOR of a hash function applied to
       {key, value} for every entry in the DB, then use this to check
       DB integrity incrementally during incremental replication, as
       well as for the whole DB.
2012-02-05 16:26:32 -06:00

446 lines
12 KiB
C

/*
* Copyright (c) 1997 - 1999, 2002 - 2003 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.
*/
#include "krb5_locl.h"
#include "an2ln_plugin.h"
#include "db_plugin.h"
/* Default plugin (DB using binary search of sorted text file) follows */
static krb5_error_code an2ln_def_plug_init(krb5_context, void **);
static void an2ln_def_plug_fini(void *);
static krb5_error_code an2ln_def_plug_an2ln(void *, krb5_context, const char *,
krb5_const_principal, set_result_f,
void *);
static krb5plugin_an2ln_ftable an2ln_def_plug = {
0,
an2ln_def_plug_init,
an2ln_def_plug_fini,
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
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->rule, plctx->aname, set_res, plctx);
}
static krb5_error_code
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;
/*
* 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_PLUGIN_NO_HANDLE;
if (strlcpy(lname, heim_string_get_utf8(ctx.luser), lnsize) >= lnsize)
ret = KRB5_CONFIG_NOTENUFSPACE;
heim_release(ctx.luser);
return ret;
}
static void
reg_def_plugins_once(void *ctx)
{
krb5_error_code ret;
krb5_context context = ctx;
ret = krb5_plugin_register(context, PLUGIN_TYPE_DATA,
KRB5_PLUGIN_AN2LN, &an2ln_def_plug);
}
static int
princ_realm_is_default(krb5_context context,
krb5_const_principal aname)
{
krb5_error_code ret;
krb5_realm *lrealms = NULL;
krb5_realm *r;
int valid;
ret = krb5_get_default_realms(context, &lrealms);
if (ret)
return 0;
valid = 0;
for (r = lrealms; *r != NULL; ++r) {
if (strcmp (*r, aname->realm) == 0) {
valid = 1;
break;
}
}
krb5_free_host_realm (context, lrealms);
return valid;
}
/*
* 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,
char *rule,
krb5_const_principal aname,
size_t lnsize, char *lname)
{
krb5_error_code ret;
const char *res;
int root_princs_ok;
if (strcmp(rule, "NONE") == 0)
return KRB5_NO_LOCALNAME;
if (strcmp(rule, "DEFAULT") == 0)
root_princs_ok = 0;
else if (strcmp(rule, "HEIMDAL_DEFAULT") == 0)
root_princs_ok = 1;
else
return KRB5_PLUGIN_NO_HANDLE;
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 (root_princs_ok && 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;
res = "root";
ret = krb5_copy_principal(context, aname, &rootprinc);
if (ret)
return ret;
userok = _krb5_kuserok(context, rootprinc, res, FALSE);
krb5_free_principal(context, rootprinc);
if (!userok)
return KRB5_NO_LOCALNAME;
} else {
return KRB5_PLUGIN_NO_HANDLE;
}
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:
*
* @param context A krb5_context
* @param aname A principal name
* @param lnsize The size of the buffer into which the username will be written
* @param lname The buffer into which the username will be written
*
* @ingroup krb5_support
*/
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, "HEIMDAL_DEFAULT", aname, lnsize, lname);
if (ret == KRB5_PLUGIN_NO_HANDLE)
return KRB5_NO_LOCALNAME;
return ret;
}
/*
* MIT rules.
*
* 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.
*/
for (ret = KRB5_PLUGIN_NO_HANDLE, i = 0; rules[i]; i++) {
rule = rules[i];
/* Try NONE, DEFAULT, and HEIMDAL_DEFAULT rules */
ret = an2ln_default(context, rule, aname, lnsize, lname);
if (ret == KRB5_PLUGIN_NO_HANDLE)
/* Try DB, RULE, ... plugins */
ret = an2ln_plugin(context, rule, aname, lnsize, lname);
if (ret == 0 && lnsize && !lname[0])
continue; /* Success but no lname?! lies! */
else if (ret != KRB5_PLUGIN_NO_HANDLE)
break;
}
if (ret == KRB5_PLUGIN_NO_HANDLE) {
if (lnsize)
lname[0] = '\0';
ret = KRB5_NO_LOCALNAME;
}
krb5_config_free_strings(rules);
return ret;
}
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)
{
}
static heim_base_once_t sorted_text_db_init_once = HEIM_BASE_ONCE_INIT;
static void
sorted_text_db_init_f(void *arg)
{
(void) heim_db_register("sorted-text", NULL, &heim_sorted_text_file_dbtype);
}
static krb5_error_code
an2ln_def_plug_an2ln(void *plug_ctx, krb5_context context,
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;
heim_db_t dbh = NULL;
heim_dict_t db_options;
heim_data_t k, v;
heim_error_t error;
char *unparsed = NULL;
char *value = NULL;
_krb5_load_db_plugins(context);
heim_base_once_f(&sorted_text_db_init_once, NULL, sorted_text_db_init_f);
if (strncmp(rule, "DB:", strlen("DB:") != 0))
return KRB5_PLUGIN_NO_HANDLE;
an2ln_db_fname = &rule[strlen("DB:")];
if (!*an2ln_db_fname)
return KRB5_PLUGIN_NO_HANDLE;
ret = krb5_unparse_name(context, aname, &unparsed);
if (ret)
return ret;
db_options = heim_dict_create(11);
if (db_options != NULL)
heim_dict_set_value(db_options, HSTR("read-only"),
heim_number_create(1));
dbh = heim_db_create(NULL, an2ln_db_fname, db_options, &error);
if (dbh == NULL) {
krb5_set_error_message(context, heim_error_get_code(error),
N_("Couldn't open aname2lname-text-db", ""));
ret = KRB5_PLUGIN_NO_HANDLE;
goto cleanup;
}
/* Binary search; file should be sorted (in C locale) */
k = heim_data_ref_create(unparsed, strlen(unparsed), NULL);
if (k == NULL)
return krb5_enomem(context);
v = heim_db_copy_value(dbh, NULL, k, &error);
heim_release(k);
if (v == NULL && error != NULL) {
krb5_set_error_message(context, heim_error_get_code(error),
N_("Lookup in aname2lname-text-db failed", ""));
ret = heim_error_get_code(error);
goto cleanup;
} else if (v == NULL) {
ret = KRB5_PLUGIN_NO_HANDLE;
goto cleanup;
} else {
/* found */
if (heim_data_get_length(v) == 0) {
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, heim_data_get_ptr(v));
heim_release(v);
}
cleanup:
heim_release(dbh);
free(unparsed);
free(value);
return ret;
}