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