1/* 2 * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * 3. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#include "krb5_locl.h" 35 36#ifdef HAVE_DLFCN_H 37#include <dlfcn.h> 38#endif 39#include <dirent.h> 40 41struct krb5_plugin { 42 void *symbol; 43 struct krb5_plugin *next; 44}; 45 46struct plugin { 47 enum { DSO, SYMBOL } type; 48 union { 49 struct { 50 char *path; 51 void *dsohandle; 52 } dso; 53 struct { 54 enum krb5_plugin_type type; 55 char *name; 56 char *symbol; 57 } symbol; 58 } u; 59 struct plugin *next; 60}; 61 62static HEIMDAL_MUTEX plugin_mutex = HEIMDAL_MUTEX_INITIALIZER; 63static struct plugin *registered = NULL; 64static int plugins_needs_scan = 1; 65 66#if TARGET_IPHONE_SIMULATOR 67#define PLUGIN_PREFIX "%{IPHONE_SIMULATOR_ROOT}" 68#else 69#define PLUGIN_PREFIX "" 70#endif 71 72 73static const char *sysplugin_dirs[] = { 74 PLUGIN_PREFIX LIBDIR "/plugin/krb5", 75#ifdef __APPLE__ 76 PLUGIN_PREFIX "/Library/KerberosPlugins/KerberosFrameworkPlugins", 77 PLUGIN_PREFIX "/System/Library/KerberosPlugins/KerberosFrameworkPlugins", 78#endif 79 NULL 80}; 81 82/* 83 * 84 */ 85 86void * 87_krb5_plugin_get_symbol(struct krb5_plugin *p) 88{ 89 return p->symbol; 90} 91 92struct krb5_plugin * 93_krb5_plugin_get_next(struct krb5_plugin *p) 94{ 95 return p->next; 96} 97 98/* 99 * 100 */ 101 102#ifdef HAVE_DLOPEN 103 104static krb5_error_code 105loadlib(krb5_context context, char *path) 106{ 107 struct plugin *e; 108 109 e = calloc(1, sizeof(*e)); 110 if (e == NULL) { 111 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 112 free(path); 113 return ENOMEM; 114 } 115 116#ifndef RTLD_LAZY 117#define RTLD_LAZY 0 118#endif 119#ifndef RTLD_LOCAL 120#define RTLD_LOCAL 0 121#endif 122 e->type = DSO; 123 /* ignore error from dlopen, and just keep it as negative cache entry */ 124 e->u.dso.dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY); 125 e->u.dso.path = path; 126 127 e->next = registered; 128 registered = e; 129 130 return 0; 131} 132#endif /* HAVE_DLOPEN */ 133 134/** 135 * Register a plugin symbol name of specific type. 136 * @param context a Keberos context 137 * @param type type of plugin symbol 138 * @param name name of plugin symbol 139 * @param symbol a pointer to the named symbol 140 * @return In case of error a non zero error com_err error is returned 141 * and the Kerberos error string is set. 142 * 143 * @ingroup krb5_support 144 */ 145 146KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 147krb5_plugin_register(krb5_context context, 148 enum krb5_plugin_type type, 149 const char *name, 150 void *symbol) 151{ 152 struct plugin *e; 153 154 HEIMDAL_MUTEX_lock(&plugin_mutex); 155 156 /* check for duplicates */ 157 for (e = registered; e != NULL; e = e->next) { 158 if (e->type == SYMBOL && 159 strcmp(e->u.symbol.name, name) == 0 && 160 e->u.symbol.type == type && e->u.symbol.symbol == symbol) { 161 HEIMDAL_MUTEX_unlock(&plugin_mutex); 162 return 0; 163 } 164 } 165 166 e = calloc(1, sizeof(*e)); 167 if (e == NULL) { 168 HEIMDAL_MUTEX_unlock(&plugin_mutex); 169 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 170 return ENOMEM; 171 } 172 e->type = SYMBOL; 173 e->u.symbol.type = type; 174 e->u.symbol.name = strdup(name); 175 if (e->u.symbol.name == NULL) { 176 HEIMDAL_MUTEX_unlock(&plugin_mutex); 177 free(e); 178 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 179 return ENOMEM; 180 } 181 e->u.symbol.symbol = symbol; 182 183 e->next = registered; 184 registered = e; 185 HEIMDAL_MUTEX_unlock(&plugin_mutex); 186 187 return 0; 188} 189 190static int 191is_valid_plugin_filename(const char * n) 192{ 193 if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) 194 return 0; 195 196#ifdef _WIN32 197 /* On Windows, we only attempt to load .dll files as plug-ins. */ 198 { 199 const char * ext; 200 201 ext = strrchr(n, '.'); 202 if (ext == NULL) 203 return 0; 204 205 return !stricmp(ext, ".dll"); 206 } 207#else 208 return 1; 209#endif 210} 211 212static void 213trim_trailing_slash(char * path) 214{ 215 size_t l; 216 217 l = strlen(path); 218 while (l > 0 && (path[l - 1] == '/' 219#ifdef BACKSLASH_PATH_DELIM 220 || path[l - 1] == '\\' 221#endif 222 )) { 223 path[--l] = '\0'; 224 } 225} 226 227static krb5_error_code 228load_plugins(krb5_context context) 229{ 230 struct plugin *e; 231 krb5_error_code ret; 232 char **dirs = NULL, **di; 233 struct dirent *entry; 234 char *path; 235 DIR *d = NULL; 236 237 if (!plugins_needs_scan) 238 return 0; 239 plugins_needs_scan = 0; 240 241#ifdef HAVE_DLOPEN 242 243 dirs = krb5_config_get_strings(context, NULL, "libdefaults", 244 "plugin_dir", NULL); 245 if (dirs == NULL) 246 dirs = rk_UNCONST(sysplugin_dirs); 247 248 for (di = dirs; *di != NULL; di++) { 249 char * dir = *di; 250 251 if (_krb5_expand_path_tokens(context, *di, &dir)) 252 goto next_dir; 253 254 trim_trailing_slash(dir); 255 256 d = opendir(dir); 257 258 if (d == NULL) 259 goto next_dir; 260 261 rk_cloexec_dir(d); 262 263 while ((entry = readdir(d)) != NULL) { 264 char *n = entry->d_name; 265 266 /* skip . and .. */ 267 if (!is_valid_plugin_filename(n)) 268 continue; 269 270 path = NULL; 271 ret = 0; 272#ifdef __APPLE__ 273 { /* support loading bundles on MacOS */ 274 size_t len = strlen(n); 275 if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0) { 276 ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dir, n, (int)(len - 7), n); 277 /* 278 * Check if its a flat bundle 279 */ 280 if (ret == 0 && access(path, X_OK) != 0) { 281 ret = errno; 282 free(path); 283 path = NULL; 284 } 285 } 286 } 287#endif 288 if (ret < 0 || path == NULL) 289 ret = asprintf(&path, "%s/%s", dir, n); 290 291 if (ret < 0 || path == NULL) { 292 ret = ENOMEM; 293 krb5_set_error_message(context, ret, "malloc: out of memory"); 294 return ret; 295 } 296 297 /* check if already tried */ 298 for (e = registered; e != NULL; e = e->next) 299 if (e->type == DSO && strcmp(e->u.dso.path, path) == 0) 300 break; 301 if (e) { 302 free(path); 303 } else { 304 loadlib(context, path); /* store or frees path */ 305 } 306 } 307 closedir(d); 308 309 next_dir: 310 if (dir != *di) 311 free(dir); 312 } 313 if (dirs != rk_UNCONST(sysplugin_dirs)) 314 krb5_config_free_strings(dirs); 315#endif /* HAVE_DLOPEN */ 316 return 0; 317} 318 319static krb5_error_code 320add_symbol(krb5_context context, struct krb5_plugin **list, void *symbol) 321{ 322 struct krb5_plugin *e; 323 324 e = calloc(1, sizeof(*e)); 325 if (e == NULL) { 326 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 327 return ENOMEM; 328 } 329 e->symbol = symbol; 330 e->next = *list; 331 *list = e; 332 return 0; 333} 334 335krb5_error_code 336_krb5_plugin_find(krb5_context context, 337 enum krb5_plugin_type type, 338 const char *name, 339 struct krb5_plugin **list) 340{ 341 struct plugin *e; 342 krb5_error_code ret; 343 344 *list = NULL; 345 346 HEIMDAL_MUTEX_lock(&plugin_mutex); 347 348 load_plugins(context); 349 350 for (ret = 0, e = registered; e != NULL; e = e->next) { 351 switch(e->type) { 352 case DSO: { 353 void *sym; 354 if (e->u.dso.dsohandle == NULL) 355 continue; 356 sym = dlsym(e->u.dso.dsohandle, name); 357 if (sym) 358 ret = add_symbol(context, list, sym); 359 break; 360 } 361 case SYMBOL: 362 if (strcmp(e->u.symbol.name, name) == 0 && e->u.symbol.type == type) 363 ret = add_symbol(context, list, e->u.symbol.symbol); 364 break; 365 } 366 if (ret) { 367 _krb5_plugin_free(*list); 368 *list = NULL; 369 } 370 } 371 372 HEIMDAL_MUTEX_unlock(&plugin_mutex); 373 if (ret) 374 return ret; 375 376 if (*list == NULL) { 377 krb5_set_error_message(context, ENOENT, "Did not find a plugin for %s", name); 378 return ENOENT; 379 } 380 381 return 0; 382} 383 384void 385_krb5_plugin_free(struct krb5_plugin *list) 386{ 387 struct krb5_plugin *next; 388 while (list) { 389 next = list->next; 390 free(list); 391 list = next; 392 } 393} 394 395/* 396 * module - dict of { 397 * ModuleName = [ 398 * plugin = object{ 399 * array = { ptr, ctx } 400 * } 401 * ] 402 * } 403 */ 404 405static heim_dict_t modules; 406 407struct plugin2 { 408 struct heim_base_uniq base; 409 heim_string_t path; 410 void *dsohandle; 411 heim_dict_t names; 412}; 413 414static void 415plug_dealloc(void *ptr) 416{ 417 struct plugin2 *p = ptr; 418 heim_release(p->path); 419 heim_release(p->names); 420 if (p->dsohandle) 421 dlclose(p->dsohandle); 422} 423 424 425void 426krb5_load_plugins(krb5_context context, const char *name, const char **paths) 427{ 428#ifdef HAVE_DLOPEN 429 heim_string_t s = heim_string_create(name); 430 heim_dict_t module; 431 struct dirent *entry; 432 krb5_error_code ret; 433 const char **di; 434 DIR *d = NULL; 435 436 HEIMDAL_MUTEX_lock(&plugin_mutex); 437 438 if (modules == NULL) { 439 modules = heim_dict_create(11); 440 if (modules == NULL) { 441 HEIMDAL_MUTEX_unlock(&plugin_mutex); 442 return; 443 } 444 } 445 446 module = heim_dict_copy_value(modules, s); 447 if (module == NULL) { 448 module = heim_dict_create(11); 449 if (module == NULL) { 450 HEIMDAL_MUTEX_unlock(&plugin_mutex); 451 heim_release(s); 452 return; 453 } 454 heim_dict_set_value(modules, s, module); 455 } 456 heim_release(s); 457 458 for (di = paths; *di != NULL; di++) { 459 char *dir = NULL; 460 461 if (_krb5_expand_path_tokens(context, *di, &dir)) 462 goto next_dir; 463 464 trim_trailing_slash(dir); 465 466 d = opendir(dir); 467 if (d == NULL) 468 goto next_dir; 469 rk_cloexec_dir(d); 470 471 while ((entry = readdir(d)) != NULL) { 472 char *n = entry->d_name; 473 char *path = NULL; 474 heim_string_t spath; 475 struct plugin2 *p; 476 477 /* skip . and .. */ 478 if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) 479 continue; 480 481 ret = 0; 482#ifdef __APPLE__ 483 { /* support loading bundles on MacOS */ 484 size_t len = strlen(n); 485 if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0) 486 ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dir, n, (int)(len - 7), n); 487 /* 488 * Check if its a flat bundle 489 */ 490 if (ret == 0 && access(path, X_OK) != 0) { 491 ret = errno; 492 free(path); 493 path = NULL; 494 } 495 } 496#endif 497 if (ret < 0 || path == NULL) 498 ret = asprintf(&path, "%s/%s", dir, n); 499 500 if (ret < 0 || path == NULL) 501 goto next_dir; 502 503 spath = heim_string_create(n); 504 if (spath == NULL) { 505 free(path); 506 goto next_dir; 507 } 508 509 /* check if already cached */ 510 p = heim_dict_copy_value(module, spath); 511 if (p == NULL) { 512 p = heim_uniq_alloc(sizeof(*p), "krb5-plugin", plug_dealloc); 513 if (p) 514 p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY); 515 516 if (p && p->dsohandle) { 517 p->path = heim_retain(spath); 518 p->names = heim_dict_create(11); 519 heim_dict_set_value(module, spath, p); 520 } 521 } 522 heim_release(spath); 523 heim_release(p); 524 free(path); 525 } 526 527 next_dir: 528 if (d) { 529 closedir(d); 530 d = NULL; 531 } 532 if (dir) 533 free(dir); 534 } 535 heim_release(module); 536 HEIMDAL_MUTEX_unlock(&plugin_mutex); 537#endif /* HAVE_DLOPEN */ 538} 539 540void 541_krb5_unload_plugins(krb5_context context, const char *name) 542{ 543 HEIMDAL_MUTEX_lock(&plugin_mutex); 544 heim_release(modules); 545 modules = NULL; 546 HEIMDAL_MUTEX_unlock(&plugin_mutex); 547} 548 549/* 550 * 551 */ 552 553struct common_plugin_method { 554 int version; 555 krb5_error_code (*init)(krb5_context, void **); 556 void (*fini)(void *); 557}; 558 559struct plug { 560 struct heim_base_uniq base; 561 void *dataptr; 562 void *ctx; 563}; 564 565static void 566plug_free(void *ptr) 567{ 568 struct plug *pl = ptr; 569 if (pl->dataptr) { 570 struct common_plugin_method *cpm = pl->dataptr; 571 cpm->fini(pl->ctx); 572 } 573} 574 575struct iter_ctx { 576 krb5_context context; 577 heim_string_t n; 578 const char *name; 579 int min_version; 580 heim_array_t result; 581 krb5_error_code (*func)(krb5_context, const void *, void *, void *); 582 void *userctx; 583 krb5_error_code ret; 584}; 585 586static void 587search_modules(heim_object_t key, heim_object_t value, void *ctx) 588{ 589 struct iter_ctx *s = ctx; 590 struct plugin2 *p = value; 591 struct plug *pl = heim_dict_copy_value(p->names, s->n); 592 struct common_plugin_method *cpm; 593 594 if (pl == NULL) { 595 if (p->dsohandle == NULL) 596 return; 597 598 pl = heim_uniq_alloc(sizeof(*pl), "struct-plug", plug_free); 599 600 cpm = pl->dataptr = dlsym(p->dsohandle, s->name); 601 if (cpm) { 602 int ret; 603 604 ret = cpm->init(s->context, &pl->ctx); 605 if (ret) 606 cpm = pl->dataptr = NULL; 607 } 608 heim_dict_set_value(p->names, s->n, pl); 609 } else { 610 cpm = pl->dataptr; 611 } 612 613 if (cpm && cpm->version >= s->min_version) 614 heim_array_append_value(s->result, pl); 615 616 heim_release(pl); 617} 618 619static void 620eval_results(heim_object_t value, int *stop, void *ctx) 621{ 622 struct plug *pl = value; 623 struct iter_ctx *s = ctx; 624 625 s->ret = s->func(s->context, pl->dataptr, pl->ctx, s->userctx); 626 if (s->ret != KRB5_PLUGIN_NO_HANDLE) 627 *stop = 1; 628} 629 630krb5_error_code 631krb5_plugin_run_f(krb5_context context, 632 const char *module, 633 const char *name, 634 int min_version, 635 int flags, 636 void *userctx, 637 krb5_error_code (*func)(krb5_context, const void *, void *, void *)) 638{ 639 heim_string_t m = heim_string_create(module); 640 heim_dict_t dict; 641 struct iter_ctx s; 642 643 HEIMDAL_MUTEX_lock(&plugin_mutex); 644 645 dict = heim_dict_copy_value(modules, m); 646 heim_release(m); 647 if (dict == NULL) { 648 HEIMDAL_MUTEX_unlock(&plugin_mutex); 649 return KRB5_PLUGIN_NO_HANDLE; 650 } 651 652 s.context = context; 653 s.name = name; 654 s.n = heim_string_create(name); 655 s.min_version = min_version; 656 s.result = heim_array_create(); 657 s.func = func; 658 s.userctx = userctx; 659 660 heim_dict_iterate_f(dict, &s, search_modules); 661 662 heim_release(dict); 663 664 HEIMDAL_MUTEX_unlock(&plugin_mutex); 665 666 s.ret = KRB5_PLUGIN_NO_HANDLE; 667 668 heim_array_iterate_f(s.result, &s, eval_results); 669 670 heim_release(s.result); 671 heim_release(s.n); 672 673 return s.ret; 674} 675