1/* $NetBSD: plugin.c,v 1.3 2023/06/19 21:41:44 christos Exp $ */ 2 3/* 4 * Copyright (c) 2006 - 2007 Kungliga Tekniska H��gskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include "krb5_locl.h" 37 38#ifdef HAVE_DLFCN_H 39#include <dlfcn.h> 40#endif 41#include <dirent.h> 42 43struct krb5_plugin { 44 void *symbol; 45 struct krb5_plugin *next; 46}; 47 48struct plugin { 49 enum { DSO, SYMBOL } type; 50 union { 51 struct { 52 char *path; 53 void *dsohandle; 54 } dso; 55 struct { 56 enum krb5_plugin_type type; 57 char *name; 58 char *symbol; 59 } symbol; 60 } u; 61 struct plugin *next; 62}; 63 64static HEIMDAL_MUTEX plugin_mutex = HEIMDAL_MUTEX_INITIALIZER; 65static struct plugin *registered = NULL; 66 67/** 68 * Register a plugin symbol name of specific type. 69 * @param context a Keberos context 70 * @param type type of plugin symbol 71 * @param name name of plugin symbol 72 * @param symbol a pointer to the named symbol 73 * @return In case of error a non zero error com_err error is returned 74 * and the Kerberos error string is set. 75 * 76 * @ingroup krb5_support 77 */ 78 79KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 80krb5_plugin_register(krb5_context context, 81 enum krb5_plugin_type type, 82 const char *name, 83 void *symbol) 84{ 85 struct plugin *e; 86 87 HEIMDAL_MUTEX_lock(&plugin_mutex); 88 89 /* check for duplicates */ 90 for (e = registered; e != NULL; e = e->next) { 91 if (e->type == SYMBOL && 92 strcmp(e->u.symbol.name, name) == 0 && 93 e->u.symbol.type == type && e->u.symbol.symbol == symbol) { 94 HEIMDAL_MUTEX_unlock(&plugin_mutex); 95 return 0; 96 } 97 } 98 99 e = calloc(1, sizeof(*e)); 100 if (e == NULL) { 101 HEIMDAL_MUTEX_unlock(&plugin_mutex); 102 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 103 return ENOMEM; 104 } 105 e->type = SYMBOL; 106 e->u.symbol.type = type; 107 e->u.symbol.name = strdup(name); 108 if (e->u.symbol.name == NULL) { 109 HEIMDAL_MUTEX_unlock(&plugin_mutex); 110 free(e); 111 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 112 return ENOMEM; 113 } 114 e->u.symbol.symbol = symbol; 115 116 e->next = registered; 117 registered = e; 118 HEIMDAL_MUTEX_unlock(&plugin_mutex); 119 120 return 0; 121} 122 123static krb5_error_code 124add_symbol(krb5_context context, struct krb5_plugin **list, void *symbol) 125{ 126 struct krb5_plugin *e; 127 128 e = calloc(1, sizeof(*e)); 129 if (e == NULL) { 130 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 131 return ENOMEM; 132 } 133 e->symbol = symbol; 134 e->next = *list; 135 *list = e; 136 return 0; 137} 138 139KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 140_krb5_plugin_find(krb5_context context, 141 enum krb5_plugin_type type, 142 const char *name, 143 struct krb5_plugin **list) 144{ 145 struct plugin *e; 146 krb5_error_code ret; 147 148 *list = NULL; 149 150 HEIMDAL_MUTEX_lock(&plugin_mutex); 151 152 for (ret = 0, e = registered; e != NULL; e = e->next) { 153 switch(e->type) { 154 case DSO: { 155 void *sym; 156 if (e->u.dso.dsohandle == NULL) 157 continue; 158 sym = dlsym(e->u.dso.dsohandle, name); 159 if (sym) 160 ret = add_symbol(context, list, sym); 161 break; 162 } 163 case SYMBOL: 164 if (strcmp(e->u.symbol.name, name) == 0 && e->u.symbol.type == type) 165 ret = add_symbol(context, list, e->u.symbol.symbol); 166 break; 167 } 168 if (ret) { 169 _krb5_plugin_free(*list); 170 *list = NULL; 171 } 172 } 173 174 HEIMDAL_MUTEX_unlock(&plugin_mutex); 175 if (ret) 176 return ret; 177 178 if (*list == NULL) { 179 krb5_set_error_message(context, ENOENT, "Did not find a plugin for %s", name); 180 return ENOENT; 181 } 182 183 return 0; 184} 185 186KRB5_LIB_FUNCTION void KRB5_LIB_CALL 187_krb5_plugin_free(struct krb5_plugin *list) 188{ 189 struct krb5_plugin *next; 190 while (list) { 191 next = list->next; 192 free(list); 193 list = next; 194 } 195} 196/* 197 * module - dict of { 198 * ModuleName = [ 199 * plugin = object{ 200 * array = { ptr, ctx } 201 * } 202 * ] 203 * } 204 */ 205 206static heim_dict_t modules; 207 208struct plugin2 { 209 heim_string_t path; 210 void *dsohandle; 211 heim_dict_t names; 212}; 213 214static void 215plug_dealloc(void *ptr) 216{ 217 struct plugin2 *p = ptr; 218 heim_release(p->path); 219 heim_release(p->names); 220 if (p->dsohandle) 221 dlclose(p->dsohandle); 222} 223 224static char * 225resolve_origin(const char *di) 226{ 227#ifdef HAVE_DLADDR 228 Dl_info dl_info; 229 const char *dname; 230 char *path, *p; 231#endif 232 233 if (strncmp(di, "$ORIGIN/", sizeof("$ORIGIN/") - 1) && 234 strcmp(di, "$ORIGIN")) 235 return strdup(di); 236 237#ifndef HAVE_DLADDR 238 return strdup(LIBDIR "/plugin/krb5"); 239#else /* !HAVE_DLADDR */ 240 di += sizeof("$ORIGIN") - 1; 241 242 if (dladdr(_krb5_load_plugins, &dl_info) == 0) 243 return strdup(LIBDIR "/plugin/krb5"); 244 245 dname = dl_info.dli_fname; 246#ifdef _WIN32 247 p = strrchr(dname, '\\'); 248 if (p == NULL) 249#endif 250 p = strrchr(dname, '/'); 251 if (p) { 252 if (asprintf(&path, "%.*s%s", (int) (p - dname), dname, di) == -1) 253 return NULL; 254 } else { 255 if (asprintf(&path, "%s%s", dname, di) == -1) 256 return NULL; 257 } 258 259 return path; 260#endif /* !HAVE_DLADDR */ 261} 262 263 264/** 265 * Load plugins (new system) for the given module @name (typically 266 * "krb5") from the given directory @paths. 267 * 268 * Inputs: 269 * 270 * @context A krb5_context 271 * @name Name of plugin module (typically "krb5") 272 * @paths Array of directory paths where to look 273 */ 274KRB5_LIB_FUNCTION void KRB5_LIB_CALL 275_krb5_load_plugins(krb5_context context, const char *name, const char **paths) 276{ 277#ifdef HAVE_DLOPEN 278 heim_string_t s = heim_string_create(name); 279 heim_dict_t module; 280 struct dirent *entry; 281 krb5_error_code ret; 282 const char **di; 283 char *dirname = NULL; 284 DIR *d; 285#ifdef _WIN32 286 const char * plugin_prefix; 287 size_t plugin_prefix_len; 288 289 if (asprintf(&plugin_prefix, "plugin_%s_", name) == -1) 290 return; 291 plugin_prefix_len = (plugin_prefix ? strlen(plugin_prefix) : 0); 292#endif 293 294 HEIMDAL_MUTEX_lock(&plugin_mutex); 295 296 if (modules == NULL) { 297 modules = heim_dict_create(11); 298 if (modules == NULL) { 299 HEIMDAL_MUTEX_unlock(&plugin_mutex); 300 return; 301 } 302 } 303 304 module = heim_dict_copy_value(modules, s); 305 if (module == NULL) { 306 module = heim_dict_create(11); 307 if (module == NULL) { 308 HEIMDAL_MUTEX_unlock(&plugin_mutex); 309 heim_release(s); 310 return; 311 } 312 heim_dict_set_value(modules, s, module); 313 } 314 heim_release(s); 315 316 for (di = paths; *di != NULL; di++) { 317 free(dirname); 318 dirname = resolve_origin(*di); 319 if (dirname == NULL) 320 continue; 321 d = opendir(dirname); 322 if (d == NULL) 323 continue; 324 rk_cloexec_dir(d); 325 326 while ((entry = readdir(d)) != NULL) { 327 char *n = entry->d_name; 328 char *path = NULL; 329 heim_string_t spath; 330 struct plugin2 *p; 331 332 /* skip . and .. */ 333 if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) 334 continue; 335 336 ret = 0; 337#ifdef _WIN32 338 /* 339 * On Windows, plugins must be loaded from the same directory as 340 * heimdal.dll (typically the assembly directory) and must have 341 * the name form "plugin_<module>_<name>.dll". 342 */ 343 { 344 char *ext; 345 346 if (strnicmp(n, plugin_prefix, plugin_prefix_len)) 347 continue; 348 ext = strrchr(n, '.'); 349 if (ext == NULL || stricmp(ext, ".dll")) 350 continue; 351 352 ret = asprintf(&path, "%s\\%s", dirname, n); 353 if (ret < 0 || path == NULL) 354 continue; 355 } 356#endif 357#ifdef __APPLE__ 358 { /* support loading bundles on MacOS */ 359 size_t len = strlen(n); 360 if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0) 361 ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dirname, n, (int)(len - 7), n); 362 } 363#endif 364 if (ret < 0 || path == NULL) 365 ret = asprintf(&path, "%s/%s", dirname, n); 366 367 if (ret < 0 || path == NULL) 368 continue; 369 370 spath = heim_string_create(n); 371 if (spath == NULL) { 372 free(path); 373 continue; 374 } 375 376 /* check if already cached */ 377 p = heim_dict_copy_value(module, spath); 378 if (p == NULL) { 379 p = heim_alloc(sizeof(*p), "krb5-plugin", plug_dealloc); 380 if (p) 381 p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY); 382 383 if (p && p->dsohandle) { 384 p->path = heim_retain(spath); 385 p->names = heim_dict_create(11); 386 heim_dict_set_value(module, spath, p); 387 } 388 } 389 heim_release(p); 390 heim_release(spath); 391 free(path); 392 } 393 closedir(d); 394 } 395 free(dirname); 396 HEIMDAL_MUTEX_unlock(&plugin_mutex); 397 heim_release(module); 398#ifdef _WIN32 399 if (plugin_prefix) 400 free(plugin_prefix); 401#endif 402#endif /* HAVE_DLOPEN */ 403} 404 405/** 406 * Unload plugins (new system) 407 */ 408KRB5_LIB_FUNCTION void KRB5_LIB_CALL 409_krb5_unload_plugins(krb5_context context, const char *name) 410{ 411 HEIMDAL_MUTEX_lock(&plugin_mutex); 412 heim_release(modules); 413 modules = NULL; 414 HEIMDAL_MUTEX_unlock(&plugin_mutex); 415} 416 417/* 418 * 419 */ 420 421struct common_plugin_method { 422 int version; 423 krb5_error_code (*init)(krb5_context, void **); 424 void (*fini)(void *); 425}; 426 427struct plug { 428 void *dataptr; 429 void *ctx; 430}; 431 432static void 433plug_free(void *ptr) 434{ 435 struct plug *pl = ptr; 436 if (pl->dataptr) { 437 struct common_plugin_method *cpm = pl->dataptr; 438 cpm->fini(pl->ctx); 439 } 440} 441 442struct iter_ctx { 443 krb5_context context; 444 heim_string_t n; 445 const char *name; 446 int min_version; 447 int flags; 448 heim_array_t result; 449 krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *); 450 void *userctx; 451 krb5_error_code ret; 452}; 453 454static void 455search_modules(heim_object_t key, heim_object_t value, void *ctx) 456{ 457 struct iter_ctx *s = ctx; 458 struct plugin2 *p = value; 459 struct plug *pl = heim_dict_copy_value(p->names, s->n); 460 struct common_plugin_method *cpm; 461 462 if (pl == NULL) { 463 if (p->dsohandle == NULL) 464 return; 465 466 pl = heim_alloc(sizeof(*pl), "struct-plug", plug_free); 467 468 cpm = pl->dataptr = dlsym(p->dsohandle, s->name); 469 if (cpm) { 470 int ret; 471 472 ret = cpm->init(s->context, &pl->ctx); 473 if (ret) 474 cpm = pl->dataptr = NULL; 475 } 476 heim_dict_set_value(p->names, s->n, pl); 477 } else { 478 cpm = pl->dataptr; 479 } 480 481 if (cpm && cpm->version >= s->min_version) 482 heim_array_append_value(s->result, pl); 483 heim_release(pl); 484} 485 486static void 487eval_results(heim_object_t value, void *ctx, int *stop) 488{ 489 struct plug *pl = value; 490 struct iter_ctx *s = ctx; 491 492 if (s->ret != KRB5_PLUGIN_NO_HANDLE) 493 return; 494 495 s->ret = s->func(s->context, pl->dataptr, pl->ctx, s->userctx); 496 if (s->ret != KRB5_PLUGIN_NO_HANDLE 497 && !(s->flags & KRB5_PLUGIN_INVOKE_ALL)) 498 *stop = 1; 499} 500 501/** 502 * Run plugins for the given @module (e.g., "krb5") and @name (e.g., 503 * "kuserok"). Specifically, the @func is invoked once per-plugin with 504 * four arguments: the @context, the plugin symbol value (a pointer to a 505 * struct whose first three fields are the same as struct common_plugin_method), 506 * a context value produced by the plugin's init method, and @userctx. 507 * 508 * @func should unpack arguments for a plugin function and invoke it 509 * with arguments taken from @userctx. @func should save plugin 510 * outputs, if any, in @userctx. 511 * 512 * All loaded and registered plugins are invoked via @func until @func 513 * returns something other than KRB5_PLUGIN_NO_HANDLE. Plugins that 514 * have nothing to do for the given arguments should return 515 * KRB5_PLUGIN_NO_HANDLE. 516 * 517 * Inputs: 518 * 519 * @context A krb5_context 520 * @module Name of module (typically "krb5") 521 * @name Name of pluggable interface (e.g., "kuserok") 522 * @min_version Lowest acceptable plugin minor version number 523 * @flags Flags (none defined at this time) 524 * @userctx Callback data for the callback function @func 525 * @func A callback function, invoked once per-plugin 526 * 527 * Outputs: None, other than the return value and such outputs as are 528 * gathered by @func. 529 */ 530KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 531_krb5_plugin_run_f(krb5_context context, 532 const char *module, 533 const char *name, 534 int min_version, 535 int flags, 536 void *userctx, 537 krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *)) 538{ 539 heim_string_t m = heim_string_create(module); 540 heim_dict_t dict; 541 void *plug_ctx; 542 struct common_plugin_method *cpm; 543 struct iter_ctx s; 544 struct krb5_plugin *registered_plugins = NULL; 545 struct krb5_plugin *p; 546 547 /* Get registered plugins */ 548 (void) _krb5_plugin_find(context, PLUGIN_TYPE_DATA, name, ®istered_plugins); 549 550 HEIMDAL_MUTEX_lock(&plugin_mutex); 551 552 s.context = context; 553 s.name = name; 554 s.n = heim_string_create(name); 555 s.flags = flags; 556 s.min_version = min_version; 557 s.result = heim_array_create(); 558 s.func = func; 559 s.userctx = userctx; 560 s.ret = KRB5_PLUGIN_NO_HANDLE; 561 562 /* Get loaded plugins */ 563 dict = heim_dict_copy_value(modules, m); 564 heim_release(m); 565 566 /* Add loaded plugins to s.result array */ 567 if (dict) 568 heim_dict_iterate_f(dict, &s, search_modules); 569 570 /* We don't need to hold plugin_mutex during plugin invocation */ 571 HEIMDAL_MUTEX_unlock(&plugin_mutex); 572 573 /* Invoke registered plugins (old system) */ 574 for (p = registered_plugins; p; p = p->next) { 575 /* 576 * XXX This is the wrong way to handle registered plugins, as we 577 * call init/fini on each invocation! We do this because we 578 * have nowhere in the struct plugin registered list to store 579 * the context allocated by the plugin's init function. (But at 580 * least we do call init/fini!) 581 * 582 * What we should do is adapt the old plugin system to the new 583 * one and change how we register plugins so that we use the new 584 * struct plug to keep track of their context structures, that 585 * way we can init once, invoke many times, then fini. 586 */ 587 cpm = (struct common_plugin_method *)p->symbol; 588 s.ret = cpm->init(context, &plug_ctx); 589 if (s.ret) 590 continue; 591 s.ret = s.func(s.context, p->symbol, plug_ctx, s.userctx); 592 cpm->fini(plug_ctx); 593 if (s.ret != KRB5_PLUGIN_NO_HANDLE && 594 !(flags & KRB5_PLUGIN_INVOKE_ALL)) 595 break; 596 } 597 _krb5_plugin_free(registered_plugins); 598 599 /* Invoke loaded plugins (new system) */ 600 if (s.ret == KRB5_PLUGIN_NO_HANDLE) 601 heim_array_iterate_f(s.result, &s, eval_results); 602 603 heim_release(s.result); 604 heim_release(s.n); 605 heim_release(dict); 606 607 return s.ret; 608} 609