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(®_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