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 H��gskolan
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 */
42static krb5_error_code KRB5_LIB_CALL an2ln_def_plug_init(krb5_context, void **);
43static void KRB5_LIB_CALL an2ln_def_plug_fini(void *);
44static 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
48static 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 */
56struct plctx {
57    krb5_const_principal aname;
58    heim_string_t luser;
59    const char *rule;
60};
61
62static krb5_error_code KRB5_LIB_CALL
63set_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
72static krb5_error_code KRB5_LIB_CALL
73plcallback(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
85static krb5_error_code
86an2ln_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
118static void
119reg_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
127static int
128princ_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 */
167static krb5_error_code
168an2ln_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 */
213static krb5_error_code
214an2ln_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 */
287KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
288krb5_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
360static krb5_error_code KRB5_LIB_CALL
361an2ln_def_plug_init(krb5_context context, void **ctx)
362{
363    *ctx = NULL;
364    return 0;
365}
366
367static void KRB5_LIB_CALL
368an2ln_def_plug_fini(void *ctx)
369{
370}
371
372static heim_base_once_t sorted_text_db_init_once = HEIM_BASE_ONCE_INIT;
373
374static void
375sorted_text_db_init_f(void *arg)
376{
377    (void) heim_db_register("sorted-text", NULL, &heim_sorted_text_file_dbtype);
378}
379
380static krb5_error_code KRB5_LIB_CALL
381an2ln_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
454cleanup:
455    heim_release(dbh);
456    free(unparsed);
457    free(value);
458    return ret;
459}
460
461