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