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