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