Coverage Report

Created: 2025-12-08 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}