xref: /freebsd/crypto/krb5/src/lib/krb5/os/localauth.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/localauth.c - Authorize access to local accounts */
3 /*
4  * Copyright (C) 2013 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "k5-int.h"
34 #include "os-proto.h"
35 #include <krb5/localauth_plugin.h>
36 
37 struct localauth_module_handle {
38     struct krb5_localauth_vtable_st vt;
39     krb5_localauth_moddata data;
40 };
41 
42 static krb5_error_code
43 localauth_auth_to_local_initvt(krb5_context context, int maj_ver, int min_ver,
44                                krb5_plugin_vtable vtable);
45 static krb5_error_code
46 localauth_default_initvt(krb5_context context, int maj_ver, int min_ver,
47                          krb5_plugin_vtable vtable);
48 
49 /* Release a list of localauth module handles. */
50 static void
free_handles(krb5_context context,struct localauth_module_handle ** handles)51 free_handles(krb5_context context, struct localauth_module_handle **handles)
52 {
53     struct localauth_module_handle *h, **hp;
54 
55     if (handles == NULL)
56         return;
57     for (hp = handles; *hp != NULL; hp++) {
58         h = *hp;
59         if (h->vt.fini != NULL)
60             h->vt.fini(context, h->data);
61         free(h);
62     }
63     free(handles);
64 }
65 
66 /* Find a localauth module whose an2ln_types contains a match for type. */
67 static struct localauth_module_handle *
find_typed_module(struct localauth_module_handle ** handles,const char * type)68 find_typed_module(struct localauth_module_handle **handles, const char *type)
69 {
70     struct localauth_module_handle **hp, *h;
71     const char **tp;
72 
73     for (hp = handles; *hp != NULL; hp++) {
74         h = *hp;
75         for (tp = h->vt.an2ln_types; tp != NULL && *tp != NULL; tp++) {
76             if (strcmp(*tp, type) == 0)
77                 return h;
78         }
79     }
80     return NULL;
81 }
82 
83 /* Generate a trace log and return 1 if the an2ln_types of handle conflicts
84  * with any of the handles in list.  Return 0 otherwise. */
85 static int
check_conflict(krb5_context context,struct localauth_module_handle ** list,struct localauth_module_handle * handle)86 check_conflict(krb5_context context, struct localauth_module_handle **list,
87                struct localauth_module_handle *handle)
88 {
89     struct localauth_module_handle *conflict;
90     const char **tp;
91 
92     for (tp = handle->vt.an2ln_types; tp != NULL && *tp != NULL; tp++) {
93         conflict = find_typed_module(list, *tp);
94         if (conflict != NULL) {
95             TRACE_LOCALAUTH_INIT_CONFLICT(context, *tp, handle->vt.name,
96                                           conflict->vt.name);
97             return 1;
98         }
99     }
100     return 0;
101 }
102 
103 /* Get the registered localauth modules including all built-in modules, in the
104  * proper order. */
105 static krb5_error_code
get_modules(krb5_context context,krb5_plugin_initvt_fn ** modules_out)106 get_modules(krb5_context context, krb5_plugin_initvt_fn **modules_out)
107 {
108     krb5_error_code ret;
109     const int intf = PLUGIN_INTERFACE_LOCALAUTH;
110 
111     *modules_out = NULL;
112 
113     /* Register built-in modules. */
114     ret = k5_plugin_register(context, intf, "default",
115                              localauth_default_initvt);
116     if (ret)
117         return ret;
118     ret = k5_plugin_register(context, intf, "rule", localauth_rule_initvt);
119     if (ret)
120         return ret;
121     ret = k5_plugin_register(context, intf, "names", localauth_names_initvt);
122     if (ret)
123         return ret;
124     ret = k5_plugin_register(context, intf, "auth_to_local",
125                              localauth_auth_to_local_initvt);
126     if (ret)
127         return ret;
128     ret = k5_plugin_register(context, intf, "k5login",
129                              localauth_k5login_initvt);
130     if (ret)
131         return ret;
132     ret = k5_plugin_register(context, intf, "an2ln", localauth_an2ln_initvt);
133     if (ret)
134         return ret;
135 
136     ret = k5_plugin_load_all(context, intf, modules_out);
137     if (ret)
138         return ret;
139 
140     return 0;
141 }
142 
143 /* Initialize context->localauth_handles with a list of module handles. */
144 static krb5_error_code
load_localauth_modules(krb5_context context)145 load_localauth_modules(krb5_context context)
146 {
147     krb5_error_code ret;
148     struct localauth_module_handle **list = NULL, *handle;
149     krb5_plugin_initvt_fn *modules = NULL, *mod;
150     size_t count;
151 
152     ret = get_modules(context, &modules);
153     if (ret != 0)
154         goto cleanup;
155 
156     /* Allocate a large enough list of handles. */
157     for (count = 0; modules[count] != NULL; count++);
158     list = k5calloc(count + 1, sizeof(*list), &ret);
159     if (list == NULL)
160         goto cleanup;
161 
162     /* Initialize each module, ignoring ones that fail. */
163     count = 0;
164     for (mod = modules; *mod != NULL; mod++) {
165         handle = k5alloc(sizeof(*handle), &ret);
166         if (handle == NULL)
167             goto cleanup;
168         ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&handle->vt);
169         if (ret != 0) {
170             TRACE_LOCALAUTH_VTINIT_FAIL(context, ret);
171             free(handle);
172             continue;
173         }
174 
175         if (check_conflict(context, list, handle))
176             continue;
177 
178         handle->data = NULL;
179         if (handle->vt.init != NULL) {
180             ret = handle->vt.init(context, &handle->data);
181             if (ret != 0) {
182                 TRACE_LOCALAUTH_INIT_FAIL(context, handle->vt.name, ret);
183                 free(handle);
184                 continue;
185             }
186         }
187         list[count++] = handle;
188         list[count] = NULL;
189     }
190     list[count] = NULL;
191 
192     ret = 0;
193     context->localauth_handles = list;
194     list = NULL;
195 
196 cleanup:
197     k5_plugin_free_modules(context, modules);
198     free_handles(context, list);
199     return ret;
200 }
201 
202 /* Invoke a module's userok method, if it has one. */
203 static krb5_error_code
userok(krb5_context context,struct localauth_module_handle * h,krb5_const_principal aname,const char * lname)204 userok(krb5_context context, struct localauth_module_handle *h,
205        krb5_const_principal aname, const char *lname)
206 {
207     if (h->vt.userok == NULL)
208         return KRB5_PLUGIN_NO_HANDLE;
209     return h->vt.userok(context, h->data, aname, lname);
210 }
211 
212 /* Invoke a module's an2ln method, if it has one. */
213 static krb5_error_code
an2ln(krb5_context context,struct localauth_module_handle * h,const char * type,const char * residual,krb5_const_principal aname,char ** lname_out)214 an2ln(krb5_context context, struct localauth_module_handle *h,
215       const char *type, const char *residual, krb5_const_principal aname,
216       char **lname_out)
217 {
218     if (h->vt.an2ln == NULL)
219         return KRB5_LNAME_NOTRANS;
220     return h->vt.an2ln(context, h->data, type, residual, aname, lname_out);
221 }
222 
223 /* Invoke a module's free_string method. */
224 static void
free_lname(krb5_context context,struct localauth_module_handle * h,char * str)225 free_lname(krb5_context context, struct localauth_module_handle *h, char *str)
226 {
227     h->vt.free_string(context, h->data, str);
228 }
229 
230 /* Parse a TYPE or TYPE:residual string into its components. */
231 static krb5_error_code
parse_mapping_value(const char * value,char ** type_out,char ** residual_out)232 parse_mapping_value(const char *value, char **type_out, char **residual_out)
233 {
234     krb5_error_code ret;
235     const char *p;
236     char *type, *residual;
237 
238     *type_out = NULL;
239     *residual_out = NULL;
240     p = strchr(value, ':');
241     if (p == NULL) {
242         type = strdup(value);
243         if (type == NULL)
244             return ENOMEM;
245         residual = NULL;
246     } else {
247         type = k5memdup0(value, p - value, &ret);
248         if (type == NULL)
249             return ret;
250         residual = strdup(p + 1);
251         if (residual == NULL) {
252             free(type);
253             return ENOMEM;
254         }
255     }
256     *type_out = type;
257     *residual_out = residual;
258     return 0;
259 }
260 
261 /* Apply the default an2ln method, which translates name@defaultrealm or
262  * name/defaultrealm@defaultrealm to name. */
263 static krb5_error_code
an2ln_default(krb5_context context,krb5_localauth_moddata data,const char * type,const char * residual,krb5_const_principal aname,char ** lname_out)264 an2ln_default(krb5_context context, krb5_localauth_moddata data,
265               const char *type, const char *residual,
266               krb5_const_principal aname, char **lname_out)
267 {
268     krb5_error_code ret;
269     char *def_realm;
270 
271     *lname_out = NULL;
272 
273     ret = krb5_get_default_realm(context, &def_realm);
274     if (ret)
275         return KRB5_LNAME_NOTRANS;
276 
277     if (!data_eq_string(aname->realm, def_realm)) {
278         ret = KRB5_LNAME_NOTRANS;
279     } else if (aname->length == 2) {
280         /* Allow a second component if it is the local realm. */
281         if (!data_eq_string(aname->data[1], def_realm))
282             ret = KRB5_LNAME_NOTRANS;
283     } else if (aname->length != 1) {
284         ret = KRB5_LNAME_NOTRANS;
285     }
286     free(def_realm);
287     if (ret)
288         return ret;
289 
290     *lname_out = k5memdup0(aname->data[0].data, aname->data[0].length, &ret);
291     return ret;
292 }
293 
294 /*
295  * Perform aname-to-lname translation using the auth_to_local values in the
296  * default realm's profile section.  If no values exist, fall back to the
297  * default method.
298  */
299 static krb5_error_code
an2ln_auth_to_local(krb5_context context,krb5_localauth_moddata data,const char * type_arg,const char * residual_arg,krb5_const_principal aname,char ** lname_out)300 an2ln_auth_to_local(krb5_context context, krb5_localauth_moddata data,
301                     const char *type_arg, const char *residual_arg,
302                     krb5_const_principal aname, char **lname_out)
303 {
304     krb5_error_code ret;
305     struct localauth_module_handle *h;
306     char *realm = NULL, **mapping_values = NULL, *type, *residual, *lname;
307     const char *hierarchy[4];
308     size_t i;
309 
310     *lname_out = NULL;
311 
312     /* Fetch the profile values for realms-><defaultrealm>->auth_to_local. */
313     ret = krb5_get_default_realm(context, &realm);
314     if (ret)
315         return KRB5_LNAME_NOTRANS;
316     hierarchy[0] = KRB5_CONF_REALMS;
317     hierarchy[1] = realm;
318     hierarchy[2] = KRB5_CONF_AUTH_TO_LOCAL;
319     hierarchy[3] = NULL;
320     ret = profile_get_values(context->profile, hierarchy, &mapping_values);
321     if (ret) {
322         /* Use default method if there are no auth_to_local values. */
323         ret = an2ln_default(context, data, NULL, NULL, aname, lname_out);
324         goto cleanup;
325     }
326 
327     ret = KRB5_LNAME_NOTRANS;
328     for (i = 0; mapping_values[i] != NULL && ret == KRB5_LNAME_NOTRANS; i++) {
329         ret = parse_mapping_value(mapping_values[i], &type, &residual);
330         if (ret)
331             goto cleanup;
332         h = find_typed_module(context->localauth_handles, type);
333         if (h != NULL) {
334             ret = an2ln(context, h, type, residual, aname, &lname);
335             if (ret == 0) {
336                 *lname_out = strdup(lname);
337                 if (*lname_out == NULL)
338                     ret = ENOMEM;
339                 free_lname(context, h, lname);
340             }
341         } else {
342             ret = KRB5_CONFIG_BADFORMAT;
343         }
344         free(type);
345         free(residual);
346     }
347 
348 cleanup:
349     free(realm);
350     profile_free_list(mapping_values);
351     return ret;
352 }
353 
354 static void
freestr(krb5_context context,krb5_localauth_moddata data,char * str)355 freestr(krb5_context context, krb5_localauth_moddata data, char *str)
356 {
357     free(str);
358 }
359 
360 static krb5_error_code
localauth_auth_to_local_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)361 localauth_auth_to_local_initvt(krb5_context context, int maj_ver, int min_ver,
362                                krb5_plugin_vtable vtable)
363 {
364     krb5_localauth_vtable vt = (krb5_localauth_vtable)vtable;
365 
366     vt->name = "auth_to_local";
367     vt->an2ln = an2ln_auth_to_local;
368     vt->free_string = freestr;
369     return 0;
370 }
371 
372 static krb5_error_code
localauth_default_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)373 localauth_default_initvt(krb5_context context, int maj_ver, int min_ver,
374                          krb5_plugin_vtable vtable)
375 {
376     krb5_localauth_vtable vt = (krb5_localauth_vtable)vtable;
377     static const char *types[] = { "DEFAULT", NULL };
378 
379     vt->name = "default";
380     vt->an2ln_types = types;
381     vt->an2ln = an2ln_default;
382     vt->free_string = freestr;
383     return 0;
384 }
385 
386 krb5_boolean KRB5_CALLCONV
krb5_kuserok(krb5_context context,krb5_principal aname,const char * lname)387 krb5_kuserok(krb5_context context, krb5_principal aname, const char *lname)
388 {
389     krb5_error_code ret;
390     struct localauth_module_handle **hp;
391     krb5_boolean accepted = FALSE;
392 
393     if (context->localauth_handles == NULL && load_localauth_modules(context))
394         return FALSE;
395 
396     /* If any module denies access, return false immediately.  Otherwise,
397      * consult all modules and return true if one of them allows access. */
398     for (hp = context->localauth_handles; *hp != NULL; hp++) {
399         ret = userok(context, *hp, aname, lname);
400         if (ret == 0)
401             accepted = TRUE;
402         else if (ret != KRB5_PLUGIN_NO_HANDLE)
403             return FALSE;
404     }
405     return accepted;
406 }
407 
408 krb5_error_code KRB5_CALLCONV
krb5_aname_to_localname(krb5_context context,krb5_const_principal aname,int lnsize,char * lname_out)409 krb5_aname_to_localname(krb5_context context, krb5_const_principal aname,
410                         int lnsize, char *lname_out)
411 {
412     krb5_error_code ret;
413     struct localauth_module_handle **hp;
414     char *lname;
415     size_t sz;
416 
417     if (context->localauth_handles == NULL) {
418         ret = load_localauth_modules(context);
419         if (ret)
420             return ret;
421     }
422 
423     for (hp = context->localauth_handles; *hp != NULL; hp++) {
424         if ((*hp)->vt.an2ln_types != NULL)
425             continue;
426         ret = an2ln(context, *hp, NULL, NULL, aname, &lname);
427         if (ret == 0) {
428             sz = strlcpy(lname_out, lname, lnsize);
429             free_lname(context, *hp, lname);
430             return sz >= (size_t)lnsize ? KRB5_CONFIG_NOTENUFSPACE : 0;
431         } else if (ret != KRB5_LNAME_NOTRANS) {
432             return ret;
433         }
434     }
435     return KRB5_LNAME_NOTRANS;
436 }
437 
438 void
k5_localauth_free_context(krb5_context context)439 k5_localauth_free_context(krb5_context context)
440 {
441     free_handles(context, context->localauth_handles);
442     context->localauth_handles = NULL;
443 }
444