Home | History | Annotate | Line # | Download | only in krb5
      1 /*	$NetBSD: aname_to_localname.c,v 1.2 2017/01/28 21:31:49 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997 - 1999, 2002 - 2003 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  *
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  *
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * 3. Neither the name of the Institute nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include <string.h>
     37 #include "krb5_locl.h"
     38 #include "an2ln_plugin.h"
     39 #include "db_plugin.h"
     40 
     41 /* Default plugin (DB using binary search of sorted text file) follows */
     42 static krb5_error_code KRB5_LIB_CALL an2ln_def_plug_init(krb5_context, void **);
     43 static void KRB5_LIB_CALL an2ln_def_plug_fini(void *);
     44 static krb5_error_code KRB5_LIB_CALL an2ln_def_plug_an2ln(void *, krb5_context, const char *,
     45 					    krb5_const_principal, set_result_f,
     46 					    void *);
     47 
     48 static krb5plugin_an2ln_ftable an2ln_def_plug = {
     49     0,
     50     an2ln_def_plug_init,
     51     an2ln_def_plug_fini,
     52     an2ln_def_plug_an2ln,
     53 };
     54 
     55 /* Plugin engine code follows */
     56 struct plctx {
     57     krb5_const_principal aname;
     58     heim_string_t luser;
     59     const char *rule;
     60 };
     61 
     62 static krb5_error_code KRB5_LIB_CALL
     63 set_res(void *userctx, const char *res)
     64 {
     65     struct plctx *plctx = userctx;
     66     plctx->luser = heim_string_create(res);
     67     if (plctx->luser == NULL)
     68 	return ENOMEM;
     69     return 0;
     70 }
     71 
     72 static krb5_error_code KRB5_LIB_CALL
     73 plcallback(krb5_context context,
     74 	   const void *plug, void *plugctx, void *userctx)
     75 {
     76     const krb5plugin_an2ln_ftable *locate = plug;
     77     struct plctx *plctx = userctx;
     78 
     79     if (plctx->luser)
     80 	return 0;
     81 
     82     return locate->an2ln(plugctx, context, plctx->rule, plctx->aname, set_res, plctx);
     83 }
     84 
     85 static krb5_error_code
     86 an2ln_plugin(krb5_context context, const char *rule, krb5_const_principal aname,
     87 	     size_t lnsize, char *lname)
     88 {
     89     krb5_error_code ret;
     90     struct plctx ctx;
     91 
     92     ctx.rule = rule;
     93     ctx.aname = aname;
     94     ctx.luser = NULL;
     95 
     96     /*
     97      * Order of plugin invocation is non-deterministic, but there should
     98      * really be no more than one plugin that can handle any given kind
     99      * rule, so the effect should be deterministic anyways.
    100      */
    101     ret = _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_AN2LN,
    102 			     KRB5_PLUGIN_AN2LN_VERSION_0, 0, &ctx, plcallback);
    103     if (ret != 0) {
    104 	heim_release(ctx.luser);
    105 	return ret;
    106     }
    107 
    108     if (ctx.luser == NULL)
    109 	return KRB5_PLUGIN_NO_HANDLE;
    110 
    111     if (strlcpy(lname, heim_string_get_utf8(ctx.luser), lnsize) >= lnsize)
    112 	ret = KRB5_CONFIG_NOTENUFSPACE;
    113 
    114     heim_release(ctx.luser);
    115     return ret;
    116 }
    117 
    118 static void
    119 reg_def_plugins_once(void *ctx)
    120 {
    121     krb5_context context = ctx;
    122 
    123     krb5_plugin_register(context, PLUGIN_TYPE_DATA, KRB5_PLUGIN_AN2LN,
    124                          &an2ln_def_plug);
    125 }
    126 
    127 static int
    128 princ_realm_is_default(krb5_context context,
    129 		       krb5_const_principal aname)
    130 {
    131     krb5_error_code ret;
    132     krb5_realm *lrealms = NULL;
    133     krb5_realm *r;
    134     int valid;
    135 
    136     ret = krb5_get_default_realms(context, &lrealms);
    137     if (ret)
    138 	return 0;
    139 
    140     valid = 0;
    141     for (r = lrealms; *r != NULL; ++r) {
    142 	if (strcmp (*r, aname->realm) == 0) {
    143 	    valid = 1;
    144 	    break;
    145 	}
    146     }
    147     krb5_free_host_realm (context, lrealms);
    148     return valid;
    149 }
    150 
    151 /*
    152  * This function implements MIT's auth_to_local_names configuration for
    153  * configuration compatibility.  Specifically:
    154  *
    155  * [realms]
    156  *     <realm-name> = {
    157  *         auth_to_local_names = {
    158  *             <unparsed-principal-name> = <username>
    159  *         }
    160  *     }
    161  *
    162  * If multiple usernames are configured then the last one is taken.
    163  *
    164  * The configuration can only be expected to hold a relatively small
    165  * number of mappings.  For lots of mappings use a DB.
    166  */
    167 static krb5_error_code
    168 an2ln_local_names(krb5_context context,
    169 		  krb5_const_principal aname,
    170 		  size_t lnsize,
    171 		  char *lname)
    172 {
    173     krb5_error_code ret;
    174     char *unparsed;
    175     char **values;
    176     char *res;
    177     size_t i;
    178 
    179     if (!princ_realm_is_default(context, aname))
    180 	return KRB5_PLUGIN_NO_HANDLE;
    181 
    182     ret = krb5_unparse_name_flags(context, aname,
    183 				  KRB5_PRINCIPAL_UNPARSE_NO_REALM,
    184 				  &unparsed);
    185     if (ret)
    186 	return ret;
    187 
    188     ret = KRB5_PLUGIN_NO_HANDLE;
    189     values = krb5_config_get_strings(context, NULL, "realms", aname->realm,
    190 				     "auth_to_local_names", unparsed, NULL);
    191     free(unparsed);
    192     if (!values)
    193 	return ret;
    194     /* Take the last value, just like MIT */
    195     for (res = NULL, i = 0; values[i]; i++)
    196 	res = values[i];
    197     if (res) {
    198 	ret = 0;
    199 	if (strlcpy(lname, res, lnsize) >= lnsize)
    200 	    ret = KRB5_CONFIG_NOTENUFSPACE;
    201 
    202 	if (!*res || strcmp(res, ":") == 0)
    203 	    ret = KRB5_NO_LOCALNAME;
    204     }
    205 
    206     krb5_config_free_strings(values);
    207     return ret;
    208 }
    209 
    210 /*
    211  * Heimdal's default aname2lname mapping.
    212  */
    213 static krb5_error_code
    214 an2ln_default(krb5_context context,
    215 	      char *rule,
    216 	      krb5_const_principal aname,
    217 	      size_t lnsize, char *lname)
    218 {
    219     krb5_error_code ret;
    220     const char *res;
    221     int root_princs_ok;
    222 
    223     if (strcmp(rule, "NONE") == 0)
    224 	return KRB5_NO_LOCALNAME;
    225 
    226     if (strcmp(rule, "DEFAULT") == 0)
    227 	root_princs_ok = 0;
    228     else if (strcmp(rule, "HEIMDAL_DEFAULT") == 0)
    229 	root_princs_ok = 1;
    230     else
    231 	return KRB5_PLUGIN_NO_HANDLE;
    232 
    233     if (!princ_realm_is_default(context, aname))
    234 	return KRB5_PLUGIN_NO_HANDLE;
    235 
    236     if (aname->name.name_string.len == 1) {
    237 	/*
    238 	 * One component principal names in default realm -> the one
    239 	 * component is the username.
    240 	 */
    241 	res = aname->name.name_string.val[0];
    242     } else if (root_princs_ok && aname->name.name_string.len == 2 &&
    243 	       strcmp (aname->name.name_string.val[1], "root") == 0) {
    244 	/*
    245 	 * Two-component principal names in default realm where the
    246 	 * first component is "root" -> root IFF the principal is in
    247 	 * root's .k5login (or whatever krb5_kuserok() does).
    248 	 */
    249 	krb5_principal rootprinc;
    250 	krb5_boolean userok;
    251 
    252 	res = "root";
    253 
    254 	ret = krb5_copy_principal(context, aname, &rootprinc);
    255 	if (ret)
    256 	    return ret;
    257 
    258 	userok = _krb5_kuserok(context, rootprinc, res, FALSE);
    259 	krb5_free_principal(context, rootprinc);
    260 	if (!userok)
    261 	    return KRB5_NO_LOCALNAME;
    262     } else {
    263 	return KRB5_PLUGIN_NO_HANDLE;
    264     }
    265 
    266     if (strlcpy(lname, res, lnsize) >= lnsize)
    267 	return KRB5_CONFIG_NOTENUFSPACE;
    268 
    269     return 0;
    270 }
    271 
    272 /**
    273  * Map a principal name to a local username.
    274  *
    275  * Returns 0 on success, KRB5_NO_LOCALNAME if no mapping was found, or
    276  * some Kerberos or system error.
    277  *
    278  * Inputs:
    279  *
    280  * @param context    A krb5_context
    281  * @param aname      A principal name
    282  * @param lnsize     The size of the buffer into which the username will be written
    283  * @param lname      The buffer into which the username will be written
    284  *
    285  * @ingroup krb5_support
    286  */
    287 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    288 krb5_aname_to_localname(krb5_context context,
    289 			krb5_const_principal aname,
    290 			size_t lnsize,
    291 			char *lname)
    292 {
    293     static heim_base_once_t reg_def_plugins = HEIM_BASE_ONCE_INIT;
    294     krb5_error_code ret;
    295     krb5_realm realm;
    296     size_t i;
    297     char **rules = NULL;
    298     char *rule;
    299 
    300     if (lnsize)
    301 	lname[0] = '\0';
    302 
    303     heim_base_once_f(&reg_def_plugins, context, reg_def_plugins_once);
    304 
    305     /* Try MIT's auth_to_local_names config first */
    306     ret = an2ln_local_names(context, aname, lnsize, lname);
    307     if (ret != KRB5_PLUGIN_NO_HANDLE)
    308 	return ret;
    309 
    310     ret = krb5_get_default_realm(context, &realm);
    311     if (ret)
    312 	return ret;
    313 
    314     rules = krb5_config_get_strings(context, NULL, "realms", realm,
    315 				    "auth_to_local", NULL);
    316     krb5_xfree(realm);
    317     if (!rules) {
    318 	/* Heimdal's default rule */
    319 	ret = an2ln_default(context, "HEIMDAL_DEFAULT", aname, lnsize, lname);
    320 	if (ret == KRB5_PLUGIN_NO_HANDLE)
    321 	    return KRB5_NO_LOCALNAME;
    322 	return ret;
    323     }
    324 
    325     /*
    326      * MIT rules.
    327      *
    328      * Note that RULEs and DBs only have white-list functionality,
    329      * thus RULEs and DBs that we don't understand we simply ignore.
    330      *
    331      * This means that plugins that implement black-lists are
    332      * dangerous: if a black-list plugin isn't found, the black-list
    333      * won't be enforced.  But black-lists are dangerous anyways.
    334      */
    335     for (ret = KRB5_PLUGIN_NO_HANDLE, i = 0; rules[i]; i++) {
    336 	rule = rules[i];
    337 
    338 	/* Try NONE, DEFAULT, and HEIMDAL_DEFAULT rules */
    339 	ret = an2ln_default(context, rule, aname, lnsize, lname);
    340 	if (ret == KRB5_PLUGIN_NO_HANDLE)
    341 	    /* Try DB, RULE, ... plugins */
    342 	    ret = an2ln_plugin(context, rule, aname, lnsize, lname);
    343 
    344 	if (ret == 0 && lnsize && !lname[0])
    345 	    continue; /* Success but no lname?!  lies! */
    346 	else if (ret != KRB5_PLUGIN_NO_HANDLE)
    347 	    break;
    348     }
    349 
    350     if (ret == KRB5_PLUGIN_NO_HANDLE) {
    351 	if (lnsize)
    352 	    lname[0] = '\0';
    353 	ret = KRB5_NO_LOCALNAME;
    354     }
    355 
    356     krb5_config_free_strings(rules);
    357     return ret;
    358 }
    359 
    360 static krb5_error_code KRB5_LIB_CALL
    361 an2ln_def_plug_init(krb5_context context, void **ctx)
    362 {
    363     *ctx = NULL;
    364     return 0;
    365 }
    366 
    367 static void KRB5_LIB_CALL
    368 an2ln_def_plug_fini(void *ctx)
    369 {
    370 }
    371 
    372 static heim_base_once_t sorted_text_db_init_once = HEIM_BASE_ONCE_INIT;
    373 
    374 static void
    375 sorted_text_db_init_f(void *arg)
    376 {
    377     (void) heim_db_register("sorted-text", NULL, &heim_sorted_text_file_dbtype);
    378 }
    379 
    380 static krb5_error_code KRB5_LIB_CALL
    381 an2ln_def_plug_an2ln(void *plug_ctx, krb5_context context,
    382 		     const char *rule,
    383 		     krb5_const_principal aname,
    384 		     set_result_f set_res_f, void *set_res_ctx)
    385 {
    386     krb5_error_code ret;
    387     const char *an2ln_db_fname;
    388     heim_db_t dbh = NULL;
    389     heim_dict_t db_options;
    390     heim_data_t k, v;
    391     heim_error_t error;
    392     char *unparsed = NULL;
    393     char *value = NULL;
    394 
    395     _krb5_load_db_plugins(context);
    396     heim_base_once_f(&sorted_text_db_init_once, NULL, sorted_text_db_init_f);
    397 
    398     if (strncmp(rule, "DB:", strlen("DB:")) != 0)
    399 	return KRB5_PLUGIN_NO_HANDLE;
    400 
    401     an2ln_db_fname = &rule[strlen("DB:")];
    402     if (!*an2ln_db_fname)
    403 	return KRB5_PLUGIN_NO_HANDLE;
    404 
    405     ret = krb5_unparse_name(context, aname, &unparsed);
    406     if (ret)
    407 	return ret;
    408 
    409     db_options = heim_dict_create(11);
    410     if (db_options != NULL)
    411 	heim_dict_set_value(db_options, HSTR("read-only"),
    412 			    heim_number_create(1));
    413     dbh = heim_db_create(NULL, an2ln_db_fname, db_options, &error);
    414     if (dbh == NULL) {
    415 	krb5_set_error_message(context, heim_error_get_code(error),
    416 			       N_("Couldn't open aname2lname-text-db", ""));
    417 	ret = KRB5_PLUGIN_NO_HANDLE;
    418 	goto cleanup;
    419     }
    420 
    421     /* Binary search; file should be sorted (in C locale) */
    422     k = heim_data_ref_create(unparsed, strlen(unparsed), NULL);
    423     if (k == NULL) {
    424 	ret = krb5_enomem(context);
    425         goto cleanup;
    426     }
    427     v = heim_db_copy_value(dbh, NULL, k, &error);
    428     heim_release(k);
    429     if (v == NULL && error != NULL) {
    430 	krb5_set_error_message(context, heim_error_get_code(error),
    431 			       N_("Lookup in aname2lname-text-db failed", ""));
    432 	ret = heim_error_get_code(error);
    433 	goto cleanup;
    434     } else if (v == NULL) {
    435 	ret = KRB5_PLUGIN_NO_HANDLE;
    436 	goto cleanup;
    437     } else {
    438 	/* found */
    439 	if (heim_data_get_length(v) == 0) {
    440 	    krb5_set_error_message(context, ret,
    441 				   N_("Principal mapped to empty username", ""));
    442 	    ret = KRB5_NO_LOCALNAME;
    443 	    goto cleanup;
    444 	}
    445         value = strndup(heim_data_get_ptr(v), heim_data_get_length(v));
    446 	heim_release(v);
    447         if (value == NULL) {
    448             ret = krb5_enomem(context);
    449             goto cleanup;
    450         }
    451 	ret = set_res_f(set_res_ctx, value);
    452     }
    453 
    454 cleanup:
    455     heim_release(dbh);
    456     free(unparsed);
    457     free(value);
    458     return ret;
    459 }
    460 
    461