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 */
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
set_res(void * userctx,const char * res)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
plcallback(krb5_context context,const void * plug,void * plugctx,void * userctx)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
an2ln_plugin(krb5_context context,const char * rule,krb5_const_principal aname,size_t lnsize,char * lname)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
reg_def_plugins_once(void * ctx)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
princ_realm_is_default(krb5_context context,krb5_const_principal aname)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
an2ln_local_names(krb5_context context,krb5_const_principal aname,size_t lnsize,char * lname)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
an2ln_default(krb5_context context,char * rule,krb5_const_principal aname,size_t lnsize,char * lname)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
krb5_aname_to_localname(krb5_context context,krb5_const_principal aname,size_t lnsize,char * lname)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
an2ln_def_plug_init(krb5_context context,void ** ctx)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
an2ln_def_plug_fini(void * ctx)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
sorted_text_db_init_f(void * arg)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
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)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