xref: /freebsd/crypto/krb5/src/lib/krb5/krb/plugin.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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