1 /* $NetBSD: plugin.c,v 1.1.1.2 2014/04/24 12:45:51 pettai 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 43 struct krb5_plugin { 44 void *symbol; 45 struct krb5_plugin *next; 46 }; 47 48 struct 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 64 static HEIMDAL_MUTEX plugin_mutex = HEIMDAL_MUTEX_INITIALIZER; 65 static struct plugin *registered = NULL; 66 static int plugins_needs_scan = 1; 67 68 static const char *sysplugin_dirs[] = { 69 LIBDIR "/plugin/krb5", 70 #ifdef __APPLE__ 71 "/System/Library/KerberosPlugins/KerberosFrameworkPlugins", 72 #endif 73 NULL 74 }; 75 76 /* 77 * 78 */ 79 80 void * 81 _krb5_plugin_get_symbol(struct krb5_plugin *p) 82 { 83 return p->symbol; 84 } 85 86 struct krb5_plugin * 87 _krb5_plugin_get_next(struct krb5_plugin *p) 88 { 89 return p->next; 90 } 91 92 /* 93 * 94 */ 95 96 #ifdef HAVE_DLOPEN 97 98 static krb5_error_code 99 loadlib(krb5_context context, char *path) 100 { 101 struct plugin *e; 102 103 e = calloc(1, sizeof(*e)); 104 if (e == NULL) { 105 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 106 free(path); 107 return ENOMEM; 108 } 109 110 #ifndef RTLD_LAZY 111 #define RTLD_LAZY 0 112 #endif 113 #ifndef RTLD_LOCAL 114 #define RTLD_LOCAL 0 115 #endif 116 e->type = DSO; 117 /* ignore error from dlopen, and just keep it as negative cache entry */ 118 e->u.dso.dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY); 119 e->u.dso.path = path; 120 121 e->next = registered; 122 registered = e; 123 124 return 0; 125 } 126 #endif /* HAVE_DLOPEN */ 127 128 /** 129 * Register a plugin symbol name of specific type. 130 * @param context a Keberos context 131 * @param type type of plugin symbol 132 * @param name name of plugin symbol 133 * @param symbol a pointer to the named symbol 134 * @return In case of error a non zero error com_err error is returned 135 * and the Kerberos error string is set. 136 * 137 * @ingroup krb5_support 138 */ 139 140 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 141 krb5_plugin_register(krb5_context context, 142 enum krb5_plugin_type type, 143 const char *name, 144 void *symbol) 145 { 146 struct plugin *e; 147 148 HEIMDAL_MUTEX_lock(&plugin_mutex); 149 150 /* check for duplicates */ 151 for (e = registered; e != NULL; e = e->next) { 152 if (e->type == SYMBOL && 153 strcmp(e->u.symbol.name, name) == 0 && 154 e->u.symbol.type == type && e->u.symbol.symbol == symbol) { 155 HEIMDAL_MUTEX_unlock(&plugin_mutex); 156 return 0; 157 } 158 } 159 160 e = calloc(1, sizeof(*e)); 161 if (e == NULL) { 162 HEIMDAL_MUTEX_unlock(&plugin_mutex); 163 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 164 return ENOMEM; 165 } 166 e->type = SYMBOL; 167 e->u.symbol.type = type; 168 e->u.symbol.name = strdup(name); 169 if (e->u.symbol.name == NULL) { 170 HEIMDAL_MUTEX_unlock(&plugin_mutex); 171 free(e); 172 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 173 return ENOMEM; 174 } 175 e->u.symbol.symbol = symbol; 176 177 e->next = registered; 178 registered = e; 179 HEIMDAL_MUTEX_unlock(&plugin_mutex); 180 181 return 0; 182 } 183 184 static int 185 is_valid_plugin_filename(const char * n) 186 { 187 if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) 188 return 0; 189 190 #ifdef _WIN32 191 /* On Windows, we only attempt to load .dll files as plug-ins. */ 192 { 193 const char * ext; 194 195 ext = strrchr(n, '.'); 196 if (ext == NULL) 197 return 0; 198 199 return !stricmp(ext, ".dll"); 200 } 201 #else 202 return 1; 203 #endif 204 } 205 206 static void 207 trim_trailing_slash(char * path) 208 { 209 size_t l; 210 211 l = strlen(path); 212 while (l > 0 && (path[l - 1] == '/' 213 #ifdef BACKSLASH_PATH_DELIM 214 || path[l - 1] == '\\' 215 #endif 216 )) { 217 path[--l] = '\0'; 218 } 219 } 220 221 static krb5_error_code 222 load_plugins(krb5_context context) 223 { 224 struct plugin *e; 225 krb5_error_code ret; 226 char **dirs = NULL, **di; 227 struct dirent *entry; 228 char *path; 229 DIR *d = NULL; 230 231 if (!plugins_needs_scan) 232 return 0; 233 plugins_needs_scan = 0; 234 235 #ifdef HAVE_DLOPEN 236 237 dirs = krb5_config_get_strings(context, NULL, "libdefaults", 238 "plugin_dir", NULL); 239 if (dirs == NULL) 240 dirs = rk_UNCONST(sysplugin_dirs); 241 242 for (di = dirs; *di != NULL; di++) { 243 char * dir = *di; 244 245 #ifdef KRB5_USE_PATH_TOKENS 246 if (_krb5_expand_path_tokens(context, *di, &dir)) 247 goto next_dir; 248 #endif 249 250 trim_trailing_slash(dir); 251 252 d = opendir(dir); 253 254 if (d == NULL) 255 goto next_dir; 256 257 rk_cloexec_dir(d); 258 259 while ((entry = readdir(d)) != NULL) { 260 char *n = entry->d_name; 261 262 /* skip . and .. */ 263 if (!is_valid_plugin_filename(n)) 264 continue; 265 266 path = NULL; 267 ret = 0; 268 #ifdef __APPLE__ 269 { /* support loading bundles on MacOS */ 270 size_t len = strlen(n); 271 if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0) 272 ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dir, n, (int)(len - 7), n); 273 } 274 #endif 275 if (ret < 0 || path == NULL) 276 ret = asprintf(&path, "%s/%s", dir, n); 277 278 if (ret < 0 || path == NULL) { 279 ret = ENOMEM; 280 krb5_set_error_message(context, ret, "malloc: out of memory"); 281 return ret; 282 } 283 284 /* check if already tried */ 285 for (e = registered; e != NULL; e = e->next) 286 if (e->type == DSO && strcmp(e->u.dso.path, path) == 0) 287 break; 288 if (e) { 289 free(path); 290 } else { 291 loadlib(context, path); /* store or frees path */ 292 } 293 } 294 closedir(d); 295 296 next_dir: 297 if (dir != *di) 298 free(dir); 299 } 300 if (dirs != rk_UNCONST(sysplugin_dirs)) 301 krb5_config_free_strings(dirs); 302 #endif /* HAVE_DLOPEN */ 303 return 0; 304 } 305 306 static krb5_error_code 307 add_symbol(krb5_context context, struct krb5_plugin **list, void *symbol) 308 { 309 struct krb5_plugin *e; 310 311 e = calloc(1, sizeof(*e)); 312 if (e == NULL) { 313 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 314 return ENOMEM; 315 } 316 e->symbol = symbol; 317 e->next = *list; 318 *list = e; 319 return 0; 320 } 321 322 krb5_error_code 323 _krb5_plugin_find(krb5_context context, 324 enum krb5_plugin_type type, 325 const char *name, 326 struct krb5_plugin **list) 327 { 328 struct plugin *e; 329 krb5_error_code ret; 330 331 *list = NULL; 332 333 HEIMDAL_MUTEX_lock(&plugin_mutex); 334 335 load_plugins(context); 336 337 for (ret = 0, e = registered; e != NULL; e = e->next) { 338 switch(e->type) { 339 case DSO: { 340 void *sym; 341 if (e->u.dso.dsohandle == NULL) 342 continue; 343 sym = dlsym(e->u.dso.dsohandle, name); 344 if (sym) 345 ret = add_symbol(context, list, sym); 346 break; 347 } 348 case SYMBOL: 349 if (strcmp(e->u.symbol.name, name) == 0 && e->u.symbol.type == type) 350 ret = add_symbol(context, list, e->u.symbol.symbol); 351 break; 352 } 353 if (ret) { 354 _krb5_plugin_free(*list); 355 *list = NULL; 356 } 357 } 358 359 HEIMDAL_MUTEX_unlock(&plugin_mutex); 360 if (ret) 361 return ret; 362 363 if (*list == NULL) { 364 krb5_set_error_message(context, ENOENT, "Did not find a plugin for %s", name); 365 return ENOENT; 366 } 367 368 return 0; 369 } 370 371 void 372 _krb5_plugin_free(struct krb5_plugin *list) 373 { 374 struct krb5_plugin *next; 375 while (list) { 376 next = list->next; 377 free(list); 378 list = next; 379 } 380 } 381 /* 382 * module - dict of { 383 * ModuleName = [ 384 * plugin = object{ 385 * array = { ptr, ctx } 386 * } 387 * ] 388 * } 389 */ 390 391 static heim_dict_t modules; 392 393 struct plugin2 { 394 heim_string_t path; 395 void *dsohandle; 396 heim_dict_t names; 397 }; 398 399 static void 400 plug_dealloc(void *ptr) 401 { 402 struct plugin2 *p = ptr; 403 heim_release(p->path); 404 heim_release(p->names); 405 if (p->dsohandle) 406 dlclose(p->dsohandle); 407 } 408 409 410 void 411 _krb5_load_plugins(krb5_context context, const char *name, const char **paths) 412 { 413 #ifdef HAVE_DLOPEN 414 heim_string_t s = heim_string_create(name); 415 heim_dict_t module; 416 struct dirent *entry; 417 krb5_error_code ret; 418 const char **di; 419 DIR *d; 420 421 HEIMDAL_MUTEX_lock(&plugin_mutex); 422 423 if (modules == NULL) { 424 modules = heim_dict_create(11); 425 if (modules == NULL) { 426 HEIMDAL_MUTEX_unlock(&plugin_mutex); 427 return; 428 } 429 } 430 431 module = heim_dict_copy_value(modules, s); 432 if (module == NULL) { 433 module = heim_dict_create(11); 434 if (module == NULL) { 435 HEIMDAL_MUTEX_unlock(&plugin_mutex); 436 heim_release(s); 437 return; 438 } 439 heim_dict_add_value(modules, s, module); 440 } 441 heim_release(s); 442 443 for (di = paths; *di != NULL; di++) { 444 d = opendir(*di); 445 if (d == NULL) 446 continue; 447 rk_cloexec_dir(d); 448 449 while ((entry = readdir(d)) != NULL) { 450 char *n = entry->d_name; 451 char *path = NULL; 452 heim_string_t spath; 453 struct plugin2 *p; 454 455 /* skip . and .. */ 456 if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) 457 continue; 458 459 ret = 0; 460 #ifdef __APPLE__ 461 { /* support loading bundles on MacOS */ 462 size_t len = strlen(n); 463 if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0) 464 ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", *di, n, (int)(len - 7), n); 465 } 466 #endif 467 if (ret < 0 || path == NULL) 468 ret = asprintf(&path, "%s/%s", *di, n); 469 470 if (ret < 0 || path == NULL) 471 continue; 472 473 spath = heim_string_create(n); 474 if (spath == NULL) { 475 free(path); 476 continue; 477 } 478 479 /* check if already cached */ 480 p = heim_dict_copy_value(module, spath); 481 if (p == NULL) { 482 p = heim_alloc(sizeof(*p), "krb5-plugin", plug_dealloc); 483 if (p) 484 p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY); 485 486 if (p->dsohandle) { 487 p->path = heim_retain(spath); 488 p->names = heim_dict_create(11); 489 heim_dict_add_value(module, spath, p); 490 } 491 } 492 heim_release(spath); 493 heim_release(p); 494 free(path); 495 } 496 closedir(d); 497 } 498 heim_release(module); 499 HEIMDAL_MUTEX_unlock(&plugin_mutex); 500 #endif /* HAVE_DLOPEN */ 501 } 502 503 void 504 _krb5_unload_plugins(krb5_context context, const char *name) 505 { 506 HEIMDAL_MUTEX_lock(&plugin_mutex); 507 heim_release(modules); 508 modules = NULL; 509 HEIMDAL_MUTEX_unlock(&plugin_mutex); 510 } 511 512 /* 513 * 514 */ 515 516 struct common_plugin_method { 517 int version; 518 krb5_error_code (*init)(krb5_context, void **); 519 void (*fini)(void *); 520 }; 521 522 struct plug { 523 void *dataptr; 524 void *ctx; 525 }; 526 527 static void 528 plug_free(void *ptr) 529 { 530 struct plug *pl = ptr; 531 if (pl->dataptr) { 532 struct common_plugin_method *cpm = pl->dataptr; 533 cpm->fini(pl->ctx); 534 } 535 } 536 537 struct iter_ctx { 538 krb5_context context; 539 heim_string_t n; 540 const char *name; 541 int min_version; 542 heim_array_t result; 543 krb5_error_code (*func)(krb5_context, const void *, void *, void *); 544 void *userctx; 545 krb5_error_code ret; 546 }; 547 548 static void 549 search_modules(void *ctx, heim_object_t key, heim_object_t value) 550 { 551 struct iter_ctx *s = ctx; 552 struct plugin2 *p = value; 553 struct plug *pl = heim_dict_copy_value(p->names, s->n); 554 struct common_plugin_method *cpm; 555 556 if (pl == NULL) { 557 if (p->dsohandle == NULL) 558 return; 559 560 pl = heim_alloc(sizeof(*pl), "struct-plug", plug_free); 561 562 cpm = pl->dataptr = dlsym(p->dsohandle, s->name); 563 if (cpm) { 564 int ret; 565 566 ret = cpm->init(s->context, &pl->ctx); 567 if (ret) 568 cpm = pl->dataptr = NULL; 569 } 570 heim_dict_add_value(p->names, s->n, pl); 571 } else { 572 cpm = pl->dataptr; 573 } 574 575 if (cpm && cpm->version >= s->min_version) 576 heim_array_append_value(s->result, pl); 577 578 heim_release(pl); 579 } 580 581 static void 582 eval_results(heim_object_t value, void *ctx) 583 { 584 struct plug *pl = value; 585 struct iter_ctx *s = ctx; 586 587 if (s->ret != KRB5_PLUGIN_NO_HANDLE) 588 return; 589 590 s->ret = s->func(s->context, pl->dataptr, pl->ctx, s->userctx); 591 } 592 593 krb5_error_code 594 _krb5_plugin_run_f(krb5_context context, 595 const char *module, 596 const char *name, 597 int min_version, 598 int flags, 599 void *userctx, 600 krb5_error_code (*func)(krb5_context, const void *, void *, void *)) 601 { 602 heim_string_t m = heim_string_create(module); 603 heim_dict_t dict; 604 struct iter_ctx s; 605 606 HEIMDAL_MUTEX_lock(&plugin_mutex); 607 608 dict = heim_dict_copy_value(modules, m); 609 heim_release(m); 610 if (dict == NULL) { 611 HEIMDAL_MUTEX_unlock(&plugin_mutex); 612 return KRB5_PLUGIN_NO_HANDLE; 613 } 614 615 s.context = context; 616 s.name = name; 617 s.n = heim_string_create(name); 618 s.min_version = min_version; 619 s.result = heim_array_create(); 620 s.func = func; 621 s.userctx = userctx; 622 623 heim_dict_iterate_f(dict, search_modules, &s); 624 625 heim_release(dict); 626 627 HEIMDAL_MUTEX_unlock(&plugin_mutex); 628 629 s.ret = KRB5_PLUGIN_NO_HANDLE; 630 631 heim_array_iterate_f(s.result, eval_results, &s); 632 633 heim_release(s.result); 634 heim_release(s.n); 635 636 return s.ret; 637 } 638