1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/plugin.c - Plugin framework functions */
3 /*
4 * Copyright (C) 2010 by the Massachusetts Institute of Technology.
5 * All rights reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26
27 #include "k5-int.h"
28
29 /*
30 * A plugin_mapping structure maps a module name to a built-in or dynamic
31 * module. modname is always present; the other three fields can be in four
32 * different states:
33 *
34 * - If dyn_path and dyn_handle are null but module is set, the mapping is to a
35 * built-in module.
36 * - If dyn_path is set but dyn_handle and module are null, the mapping is to a
37 * dynamic module which hasn't been loaded yet.
38 * - If all three fields are set, the mapping is to a dynamic module which has
39 * been loaded and is ready to use.
40 * - If all three fields are null, the mapping is to a dynamic module which
41 * failed to load and should be ignored.
42 */
43 struct plugin_mapping {
44 char *modname;
45 char *dyn_path;
46 struct plugin_file_handle *dyn_handle;
47 krb5_plugin_initvt_fn module;
48 };
49
50 const char *interface_names[] = {
51 "pwqual",
52 "kadm5_hook",
53 "clpreauth",
54 "kdcpreauth",
55 "ccselect",
56 "localauth",
57 "hostrealm",
58 "audit",
59 "tls",
60 "kdcauthdata",
61 "certauth",
62 "kadm5_auth",
63 "kdcpolicy",
64 };
65
66 /* Return the context's interface structure for id, or NULL if invalid. */
67 static inline struct plugin_interface *
get_interface(krb5_context context,int id)68 get_interface(krb5_context context, int id)
69 {
70 if (context == NULL || id < 0 || id >= PLUGIN_NUM_INTERFACES)
71 return NULL;
72 return &context->plugins[id];
73 }
74
75 /* Release the memory associated with the mapping list entry map. */
76 static void
free_plugin_mapping(struct plugin_mapping * map)77 free_plugin_mapping(struct plugin_mapping *map)
78 {
79 if (map == NULL)
80 return;
81 free(map->modname);
82 free(map->dyn_path);
83 if (map->dyn_handle != NULL)
84 krb5int_close_plugin(map->dyn_handle);
85 free(map);
86 }
87
88 static void
free_mapping_list(struct plugin_mapping ** list)89 free_mapping_list(struct plugin_mapping **list)
90 {
91 struct plugin_mapping **mp;
92
93 for (mp = list; mp != NULL && *mp != NULL; mp++)
94 free_plugin_mapping(*mp);
95 free(list);
96 }
97
98 /* Construct a plugin mapping object. path may be NULL (for a built-in
99 * module), or may be relative to the plugin base directory. */
100 static krb5_error_code
make_plugin_mapping(krb5_context context,const char * name,size_t namelen,const char * path,krb5_plugin_initvt_fn module,struct plugin_mapping ** map_out)101 make_plugin_mapping(krb5_context context, const char *name, size_t namelen,
102 const char *path, krb5_plugin_initvt_fn module,
103 struct plugin_mapping **map_out)
104 {
105 krb5_error_code ret;
106 struct plugin_mapping *map = NULL;
107
108 /* Create the mapping entry. */
109 map = k5alloc(sizeof(*map), &ret);
110 if (map == NULL)
111 return ret;
112
113 map->modname = k5memdup0(name, namelen, &ret);
114 if (map->modname == NULL)
115 goto oom;
116 if (path != NULL) {
117 if (k5_path_join(context->plugin_base_dir, path, &map->dyn_path))
118 goto oom;
119 }
120 map->module = module;
121 *map_out = map;
122 return 0;
123
124 oom:
125 free_plugin_mapping(map);
126 return ENOMEM;
127 }
128
129 /*
130 * Register a mapping from modname to either dyn_path (for an auto-registered
131 * dynamic module) or to module (for a builtin module). dyn_path may be
132 * relative to the plugin base directory.
133 */
134 static krb5_error_code
register_module(krb5_context context,struct plugin_interface * interface,const char * modname,const char * dyn_path,krb5_plugin_initvt_fn module)135 register_module(krb5_context context, struct plugin_interface *interface,
136 const char *modname, const char *dyn_path,
137 krb5_plugin_initvt_fn module)
138 {
139 struct plugin_mapping **list;
140 size_t count;
141
142 /* Allocate list space for another element and a terminator. */
143 list = interface->modules;
144 for (count = 0; list != NULL && list[count] != NULL; count++);
145 list = realloc(interface->modules, (count + 2) * sizeof(*list));
146 if (list == NULL)
147 return ENOMEM;
148 list[count] = list[count + 1] = NULL;
149 interface->modules = list;
150
151 /* Create a new mapping structure and add it to the list. */
152 return make_plugin_mapping(context, modname, strlen(modname), dyn_path,
153 module, &list[count]);
154 }
155
156 /* Parse a profile module string of the form "modname:modpath" into a mapping
157 * entry. */
158 static krb5_error_code
parse_modstr(krb5_context context,const char * modstr,struct plugin_mapping ** map_out)159 parse_modstr(krb5_context context, const char *modstr,
160 struct plugin_mapping **map_out)
161 {
162 const char *sep;
163
164 *map_out = NULL;
165
166 sep = strchr(modstr, ':');
167 if (sep == NULL) {
168 k5_setmsg(context, KRB5_PLUGIN_BAD_MODULE_SPEC,
169 _("Invalid module specifier %s"), modstr);
170 return KRB5_PLUGIN_BAD_MODULE_SPEC;
171 }
172
173 return make_plugin_mapping(context, modstr, sep - modstr, sep + 1, NULL,
174 map_out);
175 }
176
177 /* Return true if value is found in list. */
178 static krb5_boolean
find_in_list(char ** list,const char * value)179 find_in_list(char **list, const char *value)
180 {
181 for (; *list != NULL; list++) {
182 if (strcmp(*list, value) == 0)
183 return TRUE;
184 }
185 return FALSE;
186 }
187
188 /* Get the list of values for the profile variable varname in the section for
189 * interface id, or NULL if no values are set. */
190 static krb5_error_code
get_profile_var(krb5_context context,int id,const char * varname,char *** out)191 get_profile_var(krb5_context context, int id, const char *varname, char ***out)
192 {
193 krb5_error_code ret;
194 const char *path[4];
195
196 *out = NULL;
197 path[0] = KRB5_CONF_PLUGINS;
198 path[1] = interface_names[id];
199 path[2] = varname;
200 path[3] = NULL;
201 ret = profile_get_values(context->profile, path, out);
202 return (ret == PROF_NO_RELATION) ? 0 : ret;
203 }
204
205 /* Expand *list_inout to contain the mappings from modstrs, followed by the
206 * existing built-in module mappings. */
207 static krb5_error_code
make_full_list(krb5_context context,char ** modstrs,struct plugin_mapping *** list_inout)208 make_full_list(krb5_context context, char **modstrs,
209 struct plugin_mapping ***list_inout)
210 {
211 krb5_error_code ret = 0;
212 size_t count, pos, i, j;
213 struct plugin_mapping **list, **mp;
214 char **mod;
215
216 /* Allocate space for all of the modules plus a null terminator. */
217 for (count = 0; modstrs[count] != NULL; count++);
218 for (mp = *list_inout; mp != NULL && *mp != NULL; mp++, count++);
219 list = calloc(count + 1, sizeof(*list));
220 if (list == NULL)
221 return ENOMEM;
222
223 /* Parse each profile module entry and store it in the list. */
224 for (mod = modstrs, pos = 0; *mod != NULL; mod++, pos++) {
225 ret = parse_modstr(context, *mod, &list[pos]);
226 if (ret != 0) {
227 free_mapping_list(list);
228 return ret;
229 }
230 }
231
232 /* Cannibalize the old list of built-in modules. */
233 for (mp = *list_inout; mp != NULL && *mp != NULL; mp++, pos++)
234 list[pos] = *mp;
235 assert(pos == count);
236
237 /* Filter out duplicates, preferring earlier entries to later ones. */
238 for (i = 0, pos = 0; i < count; i++) {
239 for (j = 0; j < pos; j++) {
240 if (strcmp(list[i]->modname, list[j]->modname) == 0) {
241 free_plugin_mapping(list[i]);
242 break;
243 }
244 }
245 if (j == pos)
246 list[pos++] = list[i];
247 }
248 list[pos] = NULL;
249
250 free(*list_inout);
251 *list_inout = list;
252 return 0;
253 }
254
255 /* Remove any entries from list which match values in disabled. */
256 static void
remove_disabled_modules(struct plugin_mapping ** list,char ** disable)257 remove_disabled_modules(struct plugin_mapping **list, char **disable)
258 {
259 struct plugin_mapping **in, **out;
260
261 out = list;
262 for (in = list; *in != NULL; in++) {
263 if (find_in_list(disable, (*in)->modname))
264 free_plugin_mapping(*in);
265 else
266 *out++ = *in;
267 }
268 *out = NULL;
269 }
270
271 /* Modify list to include only the entries matching strings in enable, in
272 * the order they are listed there. */
273 static void
filter_enabled_modules(struct plugin_mapping ** list,char ** enable)274 filter_enabled_modules(struct plugin_mapping **list, char **enable)
275 {
276 size_t count, i, pos = 0;
277 struct plugin_mapping *tmp;
278
279 /* Count the number of existing entries. */
280 for (count = 0; list[count] != NULL; count++);
281
282 /* For each string in enable, look for a matching module. */
283 for (; *enable != NULL; enable++) {
284 for (i = pos; i < count; i++) {
285 if (strcmp(list[i]->modname, *enable) == 0) {
286 /* Swap the matching module into the next result position. */
287 tmp = list[pos];
288 list[pos++] = list[i];
289 list[i] = tmp;
290 break;
291 }
292 }
293 }
294
295 /* Free all mappings which didn't match and terminate the list. */
296 for (i = pos; i < count; i++)
297 free_plugin_mapping(list[i]);
298 list[pos] = NULL;
299 }
300
301 /* Ensure that a plugin interface is configured. id must be valid. */
302 static krb5_error_code
configure_interface(krb5_context context,int id)303 configure_interface(krb5_context context, int id)
304 {
305 krb5_error_code ret;
306 struct plugin_interface *interface = &context->plugins[id];
307 char **modstrs = NULL, **enable = NULL, **disable = NULL;
308
309 if (interface->configured)
310 return 0;
311
312 /* Detect consistency errors when plugin interfaces are added. */
313 assert(sizeof(interface_names) / sizeof(*interface_names) ==
314 PLUGIN_NUM_INTERFACES);
315
316 /* Get profile variables for this interface. */
317 ret = get_profile_var(context, id, KRB5_CONF_MODULE, &modstrs);
318 if (ret)
319 goto cleanup;
320 ret = get_profile_var(context, id, KRB5_CONF_DISABLE, &disable);
321 if (ret)
322 goto cleanup;
323 ret = get_profile_var(context, id, KRB5_CONF_ENABLE_ONLY, &enable);
324 if (ret)
325 goto cleanup;
326
327 /* Create the full list of dynamic and built-in modules. */
328 if (modstrs != NULL) {
329 ret = make_full_list(context, modstrs, &interface->modules);
330 if (ret)
331 goto cleanup;
332 }
333
334 /* Remove disabled modules. */
335 if (disable != NULL)
336 remove_disabled_modules(interface->modules, disable);
337
338 /* Filter and re-order the list according to enable-modules. */
339 if (enable != NULL)
340 filter_enabled_modules(interface->modules, enable);
341
342 cleanup:
343 profile_free_list(modstrs);
344 profile_free_list(enable);
345 profile_free_list(disable);
346 return ret;
347 }
348
349 /* If map is for a dynamic module which hasn't been loaded yet, attempt to load
350 * it. Only try to load a module once. */
351 static void
load_if_needed(krb5_context context,struct plugin_mapping * map,const char * iname)352 load_if_needed(krb5_context context, struct plugin_mapping *map,
353 const char *iname)
354 {
355 krb5_error_code ret;
356 char *symname = NULL;
357 struct plugin_file_handle *handle = NULL;
358 void (*initvt_fn)(void);
359
360 if (map->module != NULL || map->dyn_path == NULL)
361 return;
362 if (asprintf(&symname, "%s_%s_initvt", iname, map->modname) < 0)
363 return;
364
365 ret = krb5int_open_plugin(map->dyn_path, &handle, &context->err);
366 if (ret) {
367 TRACE_PLUGIN_LOAD_FAIL(context, map->modname, ret);
368 goto err;
369 }
370
371 ret = krb5int_get_plugin_func(handle, symname, &initvt_fn, &context->err);
372 if (ret) {
373 TRACE_PLUGIN_LOOKUP_FAIL(context, map->modname, ret);
374 goto err;
375 }
376
377 free(symname);
378 map->dyn_handle = handle;
379 map->module = (krb5_plugin_initvt_fn)initvt_fn;
380 return;
381
382 err:
383 /* Clean up, and also null out map->dyn_path so we don't try again. */
384 if (handle != NULL)
385 krb5int_close_plugin(handle);
386 free(symname);
387 free(map->dyn_path);
388 map->dyn_path = NULL;
389 }
390
391 krb5_error_code
k5_plugin_load(krb5_context context,int interface_id,const char * modname,krb5_plugin_initvt_fn * module)392 k5_plugin_load(krb5_context context, int interface_id, const char *modname,
393 krb5_plugin_initvt_fn *module)
394 {
395 krb5_error_code ret;
396 struct plugin_interface *interface = get_interface(context, interface_id);
397 struct plugin_mapping **mp, *map;
398
399 if (interface == NULL)
400 return EINVAL;
401 ret = configure_interface(context, interface_id);
402 if (ret != 0)
403 return ret;
404 for (mp = interface->modules; mp != NULL && *mp != NULL; mp++) {
405 map = *mp;
406 if (strcmp(map->modname, modname) == 0) {
407 load_if_needed(context, map, interface_names[interface_id]);
408 if (map->module != NULL) {
409 *module = map->module;
410 return 0;
411 }
412 break;
413 }
414 }
415 k5_setmsg(context, KRB5_PLUGIN_NAME_NOTFOUND,
416 _("Could not find %s plugin module named '%s'"),
417 interface_names[interface_id], modname);
418 return KRB5_PLUGIN_NAME_NOTFOUND;
419 }
420
421 krb5_error_code
k5_plugin_load_all(krb5_context context,int interface_id,krb5_plugin_initvt_fn ** modules)422 k5_plugin_load_all(krb5_context context, int interface_id,
423 krb5_plugin_initvt_fn **modules)
424 {
425 krb5_error_code ret;
426 struct plugin_interface *interface = get_interface(context, interface_id);
427 struct plugin_mapping **mp, *map;
428 krb5_plugin_initvt_fn *list;
429 size_t count;
430
431 if (interface == NULL)
432 return EINVAL;
433 ret = configure_interface(context, interface_id);
434 if (ret != 0)
435 return ret;
436
437 /* Count the modules and allocate a list to hold them. */
438 mp = interface->modules;
439 for (count = 0; mp != NULL && mp[count] != NULL; count++);
440 list = calloc(count + 1, sizeof(*list));
441 if (list == NULL)
442 return ENOMEM;
443
444 /* Place each module's initvt function into list. */
445 count = 0;
446 for (mp = interface->modules; mp != NULL && *mp != NULL; mp++) {
447 map = *mp;
448 load_if_needed(context, map, interface_names[interface_id]);
449 if (map->module != NULL)
450 list[count++] = map->module;
451 }
452
453 *modules = list;
454 return 0;
455 }
456
457 void
k5_plugin_free_modules(krb5_context context,krb5_plugin_initvt_fn * modules)458 k5_plugin_free_modules(krb5_context context, krb5_plugin_initvt_fn *modules)
459 {
460 free(modules);
461 }
462
463 krb5_error_code
k5_plugin_register(krb5_context context,int interface_id,const char * modname,krb5_plugin_initvt_fn module)464 k5_plugin_register(krb5_context context, int interface_id, const char *modname,
465 krb5_plugin_initvt_fn module)
466 {
467 struct plugin_interface *interface = get_interface(context, interface_id);
468
469 if (interface == NULL)
470 return EINVAL;
471
472 /* Disallow registering plugins after load. We may need to reconsider
473 * this, but it simplifies the design. */
474 if (interface->configured)
475 return EINVAL;
476
477 return register_module(context, interface, modname, NULL, module);
478 }
479
480 krb5_error_code
k5_plugin_register_dyn(krb5_context context,int interface_id,const char * modname,const char * modsubdir)481 k5_plugin_register_dyn(krb5_context context, int interface_id,
482 const char *modname, const char *modsubdir)
483 {
484 krb5_error_code ret;
485 struct plugin_interface *interface = get_interface(context, interface_id);
486 char *fname, *path;
487
488 /* Disallow registering plugins after load. */
489 if (interface == NULL || interface->configured)
490 return EINVAL;
491
492 if (asprintf(&fname, "%s%s", modname, PLUGIN_EXT) < 0)
493 return ENOMEM;
494 ret = k5_path_join(modsubdir, fname, &path);
495 free(fname);
496 if (ret)
497 return ret;
498 ret = register_module(context, interface, modname, path, NULL);
499 free(path);
500 return ret;
501 }
502
503 void
k5_plugin_free_context(krb5_context context)504 k5_plugin_free_context(krb5_context context)
505 {
506 int i;
507
508 for (i = 0; i < PLUGIN_NUM_INTERFACES; i++)
509 free_mapping_list(context->plugins[i].modules);
510 memset(context->plugins, 0, sizeof(context->plugins));
511 }
512