/src/krb5/src/lib/krb5/krb/plugin.c
Line | Count | Source |
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 * |
68 | | get_interface(krb5_context context, int id) |
69 | 16.6k | { |
70 | 16.6k | if (context == NULL || id < 0 || id >= PLUGIN_NUM_INTERFACES) |
71 | 0 | return NULL; |
72 | 16.6k | return &context->plugins[id]; |
73 | 16.6k | } |
74 | | |
75 | | /* Release the memory associated with the mapping list entry map. */ |
76 | | static void |
77 | | free_plugin_mapping(struct plugin_mapping *map) |
78 | 13.3k | { |
79 | 13.3k | if (map == NULL) |
80 | 0 | return; |
81 | 13.3k | free(map->modname); |
82 | 13.3k | free(map->dyn_path); |
83 | 13.3k | if (map->dyn_handle != NULL) |
84 | 0 | krb5int_close_plugin(map->dyn_handle); |
85 | 13.3k | free(map); |
86 | 13.3k | } |
87 | | |
88 | | static void |
89 | | free_mapping_list(struct plugin_mapping **list) |
90 | 181k | { |
91 | 181k | struct plugin_mapping **mp; |
92 | | |
93 | 195k | for (mp = list; mp != NULL && *mp != NULL; mp++) |
94 | 13.3k | free_plugin_mapping(*mp); |
95 | 181k | free(list); |
96 | 181k | } |
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 |
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 | 13.3k | { |
105 | 13.3k | krb5_error_code ret; |
106 | 13.3k | struct plugin_mapping *map = NULL; |
107 | | |
108 | | /* Create the mapping entry. */ |
109 | 13.3k | map = k5alloc(sizeof(*map), &ret); |
110 | 13.3k | if (map == NULL) |
111 | 0 | return ret; |
112 | | |
113 | 13.3k | map->modname = k5memdup0(name, namelen, &ret); |
114 | 13.3k | if (map->modname == NULL) |
115 | 0 | goto oom; |
116 | 13.3k | if (path != NULL) { |
117 | 0 | if (k5_path_join(context->plugin_base_dir, path, &map->dyn_path)) |
118 | 0 | goto oom; |
119 | 0 | } |
120 | 13.3k | map->module = module; |
121 | 13.3k | *map_out = map; |
122 | 13.3k | return 0; |
123 | | |
124 | 0 | oom: |
125 | 0 | free_plugin_mapping(map); |
126 | 0 | return ENOMEM; |
127 | 13.3k | } |
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 |
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 | 13.3k | { |
139 | 13.3k | struct plugin_mapping **list; |
140 | 13.3k | size_t count; |
141 | | |
142 | | /* Allocate list space for another element and a terminator. */ |
143 | 13.3k | list = interface->modules; |
144 | 33.3k | for (count = 0; list != NULL && list[count] != NULL; count++); |
145 | 13.3k | list = realloc(interface->modules, (count + 2) * sizeof(*list)); |
146 | 13.3k | if (list == NULL) |
147 | 0 | return ENOMEM; |
148 | 13.3k | list[count] = list[count + 1] = NULL; |
149 | 13.3k | interface->modules = list; |
150 | | |
151 | | /* Create a new mapping structure and add it to the list. */ |
152 | 13.3k | return make_plugin_mapping(context, modname, strlen(modname), dyn_path, |
153 | 13.3k | module, &list[count]); |
154 | 13.3k | } |
155 | | |
156 | | /* Parse a profile module string of the form "modname:modpath" into a mapping |
157 | | * entry. */ |
158 | | static krb5_error_code |
159 | | parse_modstr(krb5_context context, const char *modstr, |
160 | | struct plugin_mapping **map_out) |
161 | 0 | { |
162 | 0 | const char *sep; |
163 | |
|
164 | 0 | *map_out = NULL; |
165 | |
|
166 | 0 | sep = strchr(modstr, ':'); |
167 | 0 | if (sep == NULL) { |
168 | 0 | k5_setmsg(context, KRB5_PLUGIN_BAD_MODULE_SPEC, |
169 | 0 | _("Invalid module specifier %s"), modstr); |
170 | 0 | return KRB5_PLUGIN_BAD_MODULE_SPEC; |
171 | 0 | } |
172 | | |
173 | 0 | return make_plugin_mapping(context, modstr, sep - modstr, sep + 1, NULL, |
174 | 0 | map_out); |
175 | 0 | } |
176 | | |
177 | | /* Return true if value is found in list. */ |
178 | | static krb5_boolean |
179 | | find_in_list(char **list, const char *value) |
180 | 0 | { |
181 | 0 | for (; *list != NULL; list++) { |
182 | 0 | if (strcmp(*list, value) == 0) |
183 | 0 | return TRUE; |
184 | 0 | } |
185 | 0 | return FALSE; |
186 | 0 | } |
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 |
191 | | get_profile_var(krb5_context context, int id, const char *varname, char ***out) |
192 | 9.99k | { |
193 | 9.99k | krb5_error_code ret; |
194 | 9.99k | const char *path[4]; |
195 | | |
196 | 9.99k | *out = NULL; |
197 | 9.99k | path[0] = KRB5_CONF_PLUGINS; |
198 | 9.99k | path[1] = interface_names[id]; |
199 | 9.99k | path[2] = varname; |
200 | 9.99k | path[3] = NULL; |
201 | 9.99k | ret = profile_get_values(context->profile, path, out); |
202 | 9.99k | return (ret == PROF_NO_RELATION) ? 0 : ret; |
203 | 9.99k | } |
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 |
208 | | make_full_list(krb5_context context, char **modstrs, |
209 | | struct plugin_mapping ***list_inout) |
210 | 0 | { |
211 | 0 | krb5_error_code ret = 0; |
212 | 0 | size_t count, pos, i, j; |
213 | 0 | struct plugin_mapping **list, **mp; |
214 | 0 | char **mod; |
215 | | |
216 | | /* Allocate space for all of the modules plus a null terminator. */ |
217 | 0 | for (count = 0; modstrs[count] != NULL; count++); |
218 | 0 | for (mp = *list_inout; mp != NULL && *mp != NULL; mp++, count++); |
219 | 0 | list = calloc(count + 1, sizeof(*list)); |
220 | 0 | if (list == NULL) |
221 | 0 | return ENOMEM; |
222 | | |
223 | | /* Parse each profile module entry and store it in the list. */ |
224 | 0 | for (mod = modstrs, pos = 0; *mod != NULL; mod++, pos++) { |
225 | 0 | ret = parse_modstr(context, *mod, &list[pos]); |
226 | 0 | if (ret != 0) { |
227 | 0 | free_mapping_list(list); |
228 | 0 | return ret; |
229 | 0 | } |
230 | 0 | } |
231 | | |
232 | | /* Cannibalize the old list of built-in modules. */ |
233 | 0 | for (mp = *list_inout; mp != NULL && *mp != NULL; mp++, pos++) |
234 | 0 | list[pos] = *mp; |
235 | 0 | assert(pos == count); |
236 | | |
237 | | /* Filter out duplicates, preferring earlier entries to later ones. */ |
238 | 0 | for (i = 0, pos = 0; i < count; i++) { |
239 | 0 | for (j = 0; j < pos; j++) { |
240 | 0 | if (strcmp(list[i]->modname, list[j]->modname) == 0) { |
241 | 0 | free_plugin_mapping(list[i]); |
242 | 0 | break; |
243 | 0 | } |
244 | 0 | } |
245 | 0 | if (j == pos) |
246 | 0 | list[pos++] = list[i]; |
247 | 0 | } |
248 | 0 | list[pos] = NULL; |
249 | |
|
250 | 0 | free(*list_inout); |
251 | 0 | *list_inout = list; |
252 | 0 | return 0; |
253 | 0 | } |
254 | | |
255 | | /* Remove any entries from list which match values in disabled. */ |
256 | | static void |
257 | | remove_disabled_modules(struct plugin_mapping **list, char **disable) |
258 | 0 | { |
259 | 0 | struct plugin_mapping **in, **out; |
260 | |
|
261 | 0 | out = list; |
262 | 0 | for (in = list; *in != NULL; in++) { |
263 | 0 | if (find_in_list(disable, (*in)->modname)) |
264 | 0 | free_plugin_mapping(*in); |
265 | 0 | else |
266 | 0 | *out++ = *in; |
267 | 0 | } |
268 | 0 | *out = NULL; |
269 | 0 | } |
270 | | |
271 | | /* Modify list to include only the entries matching strings in enable, in |
272 | | * the order they are listed there. */ |
273 | | static void |
274 | | filter_enabled_modules(struct plugin_mapping **list, char **enable) |
275 | 0 | { |
276 | 0 | size_t count, i, pos = 0; |
277 | 0 | struct plugin_mapping *tmp; |
278 | | |
279 | | /* Count the number of existing entries. */ |
280 | 0 | for (count = 0; list[count] != NULL; count++); |
281 | | |
282 | | /* For each string in enable, look for a matching module. */ |
283 | 0 | for (; *enable != NULL; enable++) { |
284 | 0 | for (i = pos; i < count; i++) { |
285 | 0 | if (strcmp(list[i]->modname, *enable) == 0) { |
286 | | /* Swap the matching module into the next result position. */ |
287 | 0 | tmp = list[pos]; |
288 | 0 | list[pos++] = list[i]; |
289 | 0 | list[i] = tmp; |
290 | 0 | break; |
291 | 0 | } |
292 | 0 | } |
293 | 0 | } |
294 | | |
295 | | /* Free all mappings which didn't match and terminate the list. */ |
296 | 0 | for (i = pos; i < count; i++) |
297 | 0 | free_plugin_mapping(list[i]); |
298 | 0 | list[pos] = NULL; |
299 | 0 | } |
300 | | |
301 | | /* Ensure that a plugin interface is configured. id must be valid. */ |
302 | | static krb5_error_code |
303 | | configure_interface(krb5_context context, int id) |
304 | 3.33k | { |
305 | 3.33k | krb5_error_code ret; |
306 | 3.33k | struct plugin_interface *interface = &context->plugins[id]; |
307 | 3.33k | char **modstrs = NULL, **enable = NULL, **disable = NULL; |
308 | | |
309 | 3.33k | if (interface->configured) |
310 | 0 | return 0; |
311 | | |
312 | | /* Detect consistency errors when plugin interfaces are added. */ |
313 | 3.33k | assert(sizeof(interface_names) / sizeof(*interface_names) == |
314 | 3.33k | PLUGIN_NUM_INTERFACES); |
315 | | |
316 | | /* Get profile variables for this interface. */ |
317 | 3.33k | ret = get_profile_var(context, id, KRB5_CONF_MODULE, &modstrs); |
318 | 3.33k | if (ret) |
319 | 0 | goto cleanup; |
320 | 3.33k | ret = get_profile_var(context, id, KRB5_CONF_DISABLE, &disable); |
321 | 3.33k | if (ret) |
322 | 0 | goto cleanup; |
323 | 3.33k | ret = get_profile_var(context, id, KRB5_CONF_ENABLE_ONLY, &enable); |
324 | 3.33k | if (ret) |
325 | 0 | goto cleanup; |
326 | | |
327 | | /* Create the full list of dynamic and built-in modules. */ |
328 | 3.33k | if (modstrs != NULL) { |
329 | 0 | ret = make_full_list(context, modstrs, &interface->modules); |
330 | 0 | if (ret) |
331 | 0 | goto cleanup; |
332 | 0 | } |
333 | | |
334 | | /* Remove disabled modules. */ |
335 | 3.33k | if (disable != NULL) |
336 | 0 | remove_disabled_modules(interface->modules, disable); |
337 | | |
338 | | /* Filter and re-order the list according to enable-modules. */ |
339 | 3.33k | if (enable != NULL) |
340 | 0 | filter_enabled_modules(interface->modules, enable); |
341 | | |
342 | 3.33k | cleanup: |
343 | 3.33k | profile_free_list(modstrs); |
344 | 3.33k | profile_free_list(enable); |
345 | 3.33k | profile_free_list(disable); |
346 | 3.33k | return ret; |
347 | 3.33k | } |
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 |
352 | | load_if_needed(krb5_context context, struct plugin_mapping *map, |
353 | | const char *iname) |
354 | 13.3k | { |
355 | 13.3k | krb5_error_code ret; |
356 | 13.3k | char *symname = NULL; |
357 | 13.3k | struct plugin_file_handle *handle = NULL; |
358 | 13.3k | void (*initvt_fn)(void); |
359 | | |
360 | 13.3k | if (map->module != NULL || map->dyn_path == NULL) |
361 | 13.3k | return; |
362 | 0 | if (asprintf(&symname, "%s_%s_initvt", iname, map->modname) < 0) |
363 | 0 | return; |
364 | | |
365 | 0 | ret = krb5int_open_plugin(map->dyn_path, &handle, &context->err); |
366 | 0 | if (ret) { |
367 | 0 | TRACE_PLUGIN_LOAD_FAIL(context, map->modname, ret); |
368 | 0 | goto err; |
369 | 0 | } |
370 | | |
371 | 0 | ret = krb5int_get_plugin_func(handle, symname, &initvt_fn, &context->err); |
372 | 0 | if (ret) { |
373 | 0 | TRACE_PLUGIN_LOOKUP_FAIL(context, map->modname, ret); |
374 | 0 | goto err; |
375 | 0 | } |
376 | | |
377 | 0 | free(symname); |
378 | 0 | map->dyn_handle = handle; |
379 | 0 | map->module = (krb5_plugin_initvt_fn)initvt_fn; |
380 | 0 | return; |
381 | | |
382 | 0 | err: |
383 | | /* Clean up, and also null out map->dyn_path so we don't try again. */ |
384 | 0 | if (handle != NULL) |
385 | 0 | krb5int_close_plugin(handle); |
386 | 0 | free(symname); |
387 | 0 | free(map->dyn_path); |
388 | 0 | map->dyn_path = NULL; |
389 | 0 | } |
390 | | |
391 | | krb5_error_code |
392 | | k5_plugin_load(krb5_context context, int interface_id, const char *modname, |
393 | | krb5_plugin_initvt_fn *module) |
394 | 0 | { |
395 | 0 | krb5_error_code ret; |
396 | 0 | struct plugin_interface *interface = get_interface(context, interface_id); |
397 | 0 | struct plugin_mapping **mp, *map; |
398 | |
|
399 | 0 | if (interface == NULL) |
400 | 0 | return EINVAL; |
401 | 0 | ret = configure_interface(context, interface_id); |
402 | 0 | if (ret != 0) |
403 | 0 | return ret; |
404 | 0 | for (mp = interface->modules; mp != NULL && *mp != NULL; mp++) { |
405 | 0 | map = *mp; |
406 | 0 | if (strcmp(map->modname, modname) == 0) { |
407 | 0 | load_if_needed(context, map, interface_names[interface_id]); |
408 | 0 | if (map->module != NULL) { |
409 | 0 | *module = map->module; |
410 | 0 | return 0; |
411 | 0 | } |
412 | 0 | break; |
413 | 0 | } |
414 | 0 | } |
415 | 0 | k5_setmsg(context, KRB5_PLUGIN_NAME_NOTFOUND, |
416 | 0 | _("Could not find %s plugin module named '%s'"), |
417 | 0 | interface_names[interface_id], modname); |
418 | 0 | return KRB5_PLUGIN_NAME_NOTFOUND; |
419 | 0 | } |
420 | | |
421 | | krb5_error_code |
422 | | k5_plugin_load_all(krb5_context context, int interface_id, |
423 | | krb5_plugin_initvt_fn **modules) |
424 | 3.33k | { |
425 | 3.33k | krb5_error_code ret; |
426 | 3.33k | struct plugin_interface *interface = get_interface(context, interface_id); |
427 | 3.33k | struct plugin_mapping **mp, *map; |
428 | 3.33k | krb5_plugin_initvt_fn *list; |
429 | 3.33k | size_t count; |
430 | | |
431 | 3.33k | if (interface == NULL) |
432 | 0 | return EINVAL; |
433 | 3.33k | ret = configure_interface(context, interface_id); |
434 | 3.33k | if (ret != 0) |
435 | 0 | return ret; |
436 | | |
437 | | /* Count the modules and allocate a list to hold them. */ |
438 | 3.33k | mp = interface->modules; |
439 | 16.6k | for (count = 0; mp != NULL && mp[count] != NULL; count++); |
440 | 3.33k | list = calloc(count + 1, sizeof(*list)); |
441 | 3.33k | if (list == NULL) |
442 | 0 | return ENOMEM; |
443 | | |
444 | | /* Place each module's initvt function into list. */ |
445 | 3.33k | count = 0; |
446 | 16.6k | for (mp = interface->modules; mp != NULL && *mp != NULL; mp++) { |
447 | 13.3k | map = *mp; |
448 | 13.3k | load_if_needed(context, map, interface_names[interface_id]); |
449 | 13.3k | if (map->module != NULL) |
450 | 13.3k | list[count++] = map->module; |
451 | 13.3k | } |
452 | | |
453 | 3.33k | *modules = list; |
454 | 3.33k | return 0; |
455 | 3.33k | } |
456 | | |
457 | | void |
458 | | k5_plugin_free_modules(krb5_context context, krb5_plugin_initvt_fn *modules) |
459 | 3.33k | { |
460 | 3.33k | free(modules); |
461 | 3.33k | } |
462 | | |
463 | | krb5_error_code |
464 | | k5_plugin_register(krb5_context context, int interface_id, const char *modname, |
465 | | krb5_plugin_initvt_fn module) |
466 | 13.3k | { |
467 | 13.3k | struct plugin_interface *interface = get_interface(context, interface_id); |
468 | | |
469 | 13.3k | if (interface == NULL) |
470 | 0 | return EINVAL; |
471 | | |
472 | | /* Disallow registering plugins after load. We may need to reconsider |
473 | | * this, but it simplifies the design. */ |
474 | 13.3k | if (interface->configured) |
475 | 0 | return EINVAL; |
476 | | |
477 | 13.3k | return register_module(context, interface, modname, NULL, module); |
478 | 13.3k | } |
479 | | |
480 | | krb5_error_code |
481 | | k5_plugin_register_dyn(krb5_context context, int interface_id, |
482 | | const char *modname, const char *modsubdir) |
483 | 0 | { |
484 | 0 | krb5_error_code ret; |
485 | 0 | struct plugin_interface *interface = get_interface(context, interface_id); |
486 | 0 | char *fname, *path; |
487 | | |
488 | | /* Disallow registering plugins after load. */ |
489 | 0 | if (interface == NULL || interface->configured) |
490 | 0 | return EINVAL; |
491 | | |
492 | 0 | if (asprintf(&fname, "%s%s", modname, PLUGIN_EXT) < 0) |
493 | 0 | return ENOMEM; |
494 | 0 | ret = k5_path_join(modsubdir, fname, &path); |
495 | 0 | free(fname); |
496 | 0 | if (ret) |
497 | 0 | return ret; |
498 | 0 | ret = register_module(context, interface, modname, path, NULL); |
499 | 0 | free(path); |
500 | 0 | return ret; |
501 | 0 | } |
502 | | |
503 | | void |
504 | | k5_plugin_free_context(krb5_context context) |
505 | 13.9k | { |
506 | 13.9k | int i; |
507 | | |
508 | 195k | for (i = 0; i < PLUGIN_NUM_INTERFACES; i++) |
509 | 181k | free_mapping_list(context->plugins[i].modules); |
510 | 13.9k | memset(context->plugins, 0, sizeof(context->plugins)); |
511 | 13.9k | } |