1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* util/support/plugins.c - Plugin module support functions */ 3 /* 4 * Copyright 2006, 2008 by the Massachusetts Institute of Technology. 5 * All Rights Reserved. 6 * 7 * Export of this software from the United States of America may 8 * require a specific license from the United States Government. 9 * It is the responsibility of any person or organization contemplating 10 * export to obtain such a license before exporting. 11 * 12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 13 * distribute this software and its documentation for any purpose and 14 * without fee is hereby granted, provided that the above copyright 15 * notice appear in all copies and that both that copyright notice and 16 * this permission notice appear in supporting documentation, and that 17 * the name of M.I.T. not be used in advertising or publicity pertaining 18 * to distribution of the software without specific, written prior 19 * permission. Furthermore if you modify this software you must label 20 * your software as modified software and not distribute it in such a 21 * fashion that it might be confused with the original M.I.T. software. 22 * M.I.T. makes no representations about the suitability of 23 * this software for any purpose. It is provided "as is" without express 24 * or implied warranty. 25 */ 26 27 #include "k5-platform.h" 28 #include "k5-plugin.h" 29 #if USE_DLOPEN 30 #include <dlfcn.h> 31 #endif 32 33 #if USE_DLOPEN 34 #ifdef RTLD_GROUP 35 #define GROUP RTLD_GROUP 36 #else 37 #define GROUP 0 38 #endif 39 #ifdef RTLD_NODELETE 40 #define NODELETE RTLD_NODELETE 41 #else 42 #define NODELETE 0 43 #endif 44 #define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL | GROUP | NODELETE) 45 #endif 46 47 /* 48 * glibc bug 11941, fixed in release 2.25, can cause an assertion failure in 49 * dlclose() on process exit. Our workaround is to leak dlopen() handles 50 * (which doesn't typically manifest in leak detection tools because the 51 * handles are still reachable via a global table in libdl). Because we 52 * dlopen() with RTLD_NODELETE, we weren't going to unload the plugin objects 53 * anyway. 54 */ 55 #ifdef __GLIBC_PREREQ 56 #if ! __GLIBC_PREREQ(2, 25) 57 #define dlclose(x) 58 #endif 59 #endif 60 61 #include <stdarg.h> 62 static void Tprintf (const char *fmt, ...) 63 { 64 #ifdef DEBUG 65 va_list va; 66 va_start (va, fmt); 67 vfprintf (stderr, fmt, va); 68 va_end (va); 69 #endif 70 } 71 72 struct plugin_file_handle { 73 #if defined(USE_DLOPEN) 74 void *dlhandle; 75 #elif defined(_WIN32) 76 HMODULE module; 77 #else 78 char dummy; 79 #endif 80 }; 81 82 #if defined(USE_DLOPEN) 83 84 static long 85 open_plugin_dlfcn(struct plugin_file_handle *h, const char *filename, 86 struct errinfo *ep) 87 { 88 const char *e; 89 90 h->dlhandle = dlopen(filename, PLUGIN_DLOPEN_FLAGS); 91 if (h->dlhandle == NULL) { 92 e = dlerror(); 93 if (e == NULL) 94 e = _("unknown failure"); 95 Tprintf("dlopen(%s): %s\n", filename, e); 96 k5_set_error(ep, ENOENT, _("unable to load plugin [%s]: %s"), 97 filename, e); 98 return ENOENT; 99 } 100 return 0; 101 } 102 #define open_plugin open_plugin_dlfcn 103 104 static long 105 get_sym_dlfcn(struct plugin_file_handle *h, const char *csymname, 106 void **sym_out, struct errinfo *ep) 107 { 108 const char *e; 109 110 if (h->dlhandle == NULL) 111 return ENOENT; 112 *sym_out = dlsym(h->dlhandle, csymname); 113 if (*sym_out == NULL) { 114 e = dlerror(); 115 if (e == NULL) 116 e = _("unknown failure"); 117 Tprintf("dlsym(%s): %s\n", csymname, e); 118 k5_set_error(ep, ENOENT, "%s", e); 119 return ENOENT; 120 } 121 return 0; 122 } 123 #define get_sym get_sym_dlfcn 124 125 static void 126 close_plugin_dlfcn(struct plugin_file_handle *h) 127 { 128 if (h->dlhandle != NULL) 129 dlclose(h->dlhandle); 130 } 131 #define close_plugin close_plugin_dlfcn 132 133 #elif defined(_WIN32) 134 135 static long 136 open_plugin_win32(struct plugin_file_handle *h, const char *filename, 137 struct errinfo *ep) 138 { 139 h->module = LoadLibrary(filename); 140 if (h == NULL) { 141 Tprintf("Unable to load dll: %s\n", filename); 142 k5_set_error(ep, ENOENT, _("unable to load DLL [%s]"), filename); 143 return ENOENT; 144 } 145 return 0; 146 } 147 #define open_plugin open_plugin_win32 148 149 static long 150 get_sym_win32(struct plugin_file_handle *h, const char *csymname, 151 void **sym_out, struct errinfo *ep) 152 { 153 LPVOID lpMsgBuf; 154 DWORD dw; 155 156 if (h->module == NULL) 157 return ENOENT; 158 *sym_out = GetProcAddress(h->module, csymname); 159 if (*sym_out == NULL) { 160 Tprintf("GetProcAddress(%s): %i\n", csymname, GetLastError()); 161 dw = GetLastError(); 162 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 163 FORMAT_MESSAGE_FROM_SYSTEM, 164 NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 165 (LPTSTR)&lpMsgBuf, 0, NULL)) { 166 k5_set_error(ep, ENOENT, _("unable to get DLL Symbol: %s"), 167 (char *)lpMsgBuf); 168 LocalFree(lpMsgBuf); 169 } 170 return ENOENT; 171 } 172 return 0; 173 } 174 #define get_sym get_sym_win32 175 176 static void 177 close_plugin_win32(struct plugin_file_handle *h) 178 { 179 if (h->module != NULL) 180 FreeLibrary(h->module); 181 } 182 #define close_plugin close_plugin_win32 183 184 #else 185 186 static long 187 open_plugin_dummy(struct plugin_file_handle *h, const char *filename, 188 struct errinfo *ep) 189 { 190 k5_set_error(ep, ENOENT, _("plugin loading unavailable")); 191 return ENOENT; 192 } 193 #define open_plugin open_plugin_dummy 194 195 static long 196 get_sym_dummy(struct plugin_file_handle *h, const char *csymname, 197 void **sym_out, struct errinfo *ep) 198 { 199 return ENOENT; 200 } 201 #define get_sym get_sym_dummy 202 203 static void 204 close_plugin_dummy(struct plugin_file_handle *h) 205 { 206 } 207 #define close_plugin close_plugin_dummy 208 209 #endif 210 211 long KRB5_CALLCONV 212 krb5int_open_plugin(const char *filename, 213 struct plugin_file_handle **handle_out, struct errinfo *ep) 214 { 215 long ret; 216 struct plugin_file_handle *h; 217 218 *handle_out = NULL; 219 220 h = calloc(1, sizeof(*h)); 221 if (h == NULL) 222 return ENOMEM; 223 224 ret = open_plugin(h, filename, ep); 225 if (ret) { 226 free(h); 227 return ret; 228 } 229 230 *handle_out = h; 231 return 0; 232 } 233 234 long KRB5_CALLCONV 235 krb5int_get_plugin_data(struct plugin_file_handle *h, const char *csymname, 236 void **sym_out, struct errinfo *ep) 237 { 238 return get_sym(h, csymname, sym_out, ep); 239 } 240 241 long KRB5_CALLCONV 242 krb5int_get_plugin_func(struct plugin_file_handle *h, const char *csymname, 243 void (**sym_out)(), struct errinfo *ep) 244 { 245 void *dptr = NULL; 246 long ret = get_sym(h, csymname, &dptr, ep); 247 248 if (!ret) 249 *sym_out = (void (*)())dptr; 250 return ret; 251 } 252 253 void KRB5_CALLCONV 254 krb5int_close_plugin (struct plugin_file_handle *h) 255 { 256 close_plugin(h); 257 free(h); 258 } 259 260 static long 261 krb5int_plugin_file_handle_array_init (struct plugin_file_handle ***harray) 262 { 263 long err = 0; 264 265 *harray = calloc (1, sizeof (**harray)); /* calloc initializes to NULL */ 266 if (*harray == NULL) { err = ENOMEM; } 267 268 return err; 269 } 270 271 static long 272 krb5int_plugin_file_handle_array_add (struct plugin_file_handle ***harray, size_t *count, 273 struct plugin_file_handle *p) 274 { 275 long err = 0; 276 struct plugin_file_handle **newharray = NULL; 277 size_t newcount = *count + 1; 278 279 newharray = realloc (*harray, ((newcount + 1) * sizeof (**harray))); /* +1 for NULL */ 280 if (newharray == NULL) { 281 err = ENOMEM; 282 } else { 283 newharray[newcount - 1] = p; 284 newharray[newcount] = NULL; 285 *count = newcount; 286 *harray = newharray; 287 } 288 289 return err; 290 } 291 292 static void 293 krb5int_plugin_file_handle_array_free (struct plugin_file_handle **harray) 294 { 295 if (harray != NULL) { 296 int i; 297 for (i = 0; harray[i] != NULL; i++) { 298 krb5int_close_plugin (harray[i]); 299 } 300 free (harray); 301 } 302 } 303 304 #if TARGET_OS_MAC 305 #define FILEEXTS { "", ".bundle", ".dylib", ".so", NULL } 306 #elif defined(_WIN32) 307 #define FILEEXTS { "", ".dll", NULL } 308 #else 309 #define FILEEXTS { "", ".so", NULL } 310 #endif 311 312 313 static void 314 krb5int_free_plugin_filenames (char **filenames) 315 { 316 if (filenames != NULL) { 317 int i; 318 for (i = 0; filenames[i] != NULL; i++) { 319 free (filenames[i]); 320 } 321 free (filenames); 322 } 323 } 324 325 326 static long 327 krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames) 328 { 329 long err = 0; 330 static const char *const fileexts[] = FILEEXTS; 331 char **tempnames = NULL; 332 size_t bases_count = 0; 333 size_t exts_count = 0; 334 size_t i; 335 336 if (!filebases) { err = EINVAL; } 337 if (!filenames) { err = EINVAL; } 338 339 if (!err) { 340 for (i = 0; filebases[i]; i++) { bases_count++; } 341 for (i = 0; fileexts[i]; i++) { exts_count++; } 342 tempnames = calloc ((bases_count * exts_count)+1, sizeof (char *)); 343 if (!tempnames) { err = ENOMEM; } 344 } 345 346 if (!err) { 347 size_t j; 348 for (i = 0; !err && filebases[i]; i++) { 349 for (j = 0; !err && fileexts[j]; j++) { 350 if (asprintf(&tempnames[(i*exts_count)+j], "%s%s", 351 filebases[i], fileexts[j]) < 0) { 352 tempnames[(i*exts_count)+j] = NULL; 353 err = ENOMEM; 354 } 355 } 356 } 357 tempnames[bases_count * exts_count] = NULL; /* NUL-terminate */ 358 } 359 360 if (!err) { 361 *filenames = tempnames; 362 tempnames = NULL; 363 } 364 365 krb5int_free_plugin_filenames(tempnames); 366 367 return err; 368 } 369 370 371 /* Takes a NULL-terminated list of directories. If filebases is NULL, filebases is ignored 372 * all plugins in the directories are loaded. If filebases is a NULL-terminated array of names, 373 * only plugins in the directories with those name (plus any platform extension) are loaded. */ 374 375 long KRB5_CALLCONV 376 krb5int_open_plugin_dirs (const char * const *dirnames, 377 const char * const *filebases, 378 struct plugin_dir_handle *dirhandle, 379 struct errinfo *ep) 380 { 381 long err = 0; 382 struct plugin_file_handle **h = NULL; 383 size_t count = 0; 384 char **filenames = NULL; 385 int i; 386 387 if (!err) { 388 err = krb5int_plugin_file_handle_array_init (&h); 389 } 390 391 if (!err && (filebases != NULL)) { 392 err = krb5int_get_plugin_filenames (filebases, &filenames); 393 } 394 395 for (i = 0; !err && dirnames[i] != NULL; i++) { 396 if (filenames != NULL) { 397 /* load plugins with names from filenames from each directory */ 398 int j; 399 400 for (j = 0; !err && filenames[j] != NULL; j++) { 401 struct plugin_file_handle *handle = NULL; 402 char *filepath = NULL; 403 404 if (!err) { 405 if (asprintf(&filepath, "%s/%s", dirnames[i], filenames[j]) < 0) { 406 filepath = NULL; 407 err = ENOMEM; 408 } 409 } 410 411 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) { 412 err = krb5int_plugin_file_handle_array_add (&h, &count, handle); 413 if (!err) 414 handle = NULL; /* h takes ownership */ 415 } 416 417 free(filepath); 418 if (handle != NULL) { krb5int_close_plugin (handle); } 419 } 420 } else { 421 char **fnames = NULL; 422 int j; 423 424 err = k5_dir_filenames(dirnames[i], &fnames); 425 for (j = 0; !err && fnames[j] != NULL; j++) { 426 char *filepath = NULL; 427 struct plugin_file_handle *handle = NULL; 428 429 if (strcmp(fnames[j], ".") == 0 || 430 strcmp(fnames[j], "..") == 0) 431 continue; 432 433 if (asprintf(&filepath, "%s/%s", dirnames[i], fnames[j]) < 0) { 434 filepath = NULL; 435 err = ENOMEM; 436 } 437 438 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) { 439 err = krb5int_plugin_file_handle_array_add(&h, &count, 440 handle); 441 if (!err) 442 handle = NULL; /* h takes ownership */ 443 } 444 445 free(filepath); 446 if (handle != NULL) 447 krb5int_close_plugin(handle); 448 } 449 450 k5_free_filenames(fnames); 451 } 452 } 453 454 if (err == ENOENT) { 455 err = 0; /* ran out of plugins -- do nothing */ 456 } 457 458 if (!err) { 459 dirhandle->files = h; 460 h = NULL; /* dirhandle->files takes ownership */ 461 } 462 463 if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); } 464 if (h != NULL) { krb5int_plugin_file_handle_array_free (h); } 465 466 return err; 467 } 468 469 void KRB5_CALLCONV 470 krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle) 471 { 472 if (dirhandle->files != NULL) { 473 int i; 474 for (i = 0; dirhandle->files[i] != NULL; i++) { 475 krb5int_close_plugin (dirhandle->files[i]); 476 } 477 free (dirhandle->files); 478 dirhandle->files = NULL; 479 } 480 } 481 482 void KRB5_CALLCONV 483 krb5int_free_plugin_dir_data (void **ptrs) 484 { 485 /* Nothing special to be done per pointer. */ 486 free(ptrs); 487 } 488 489 long KRB5_CALLCONV 490 krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle, 491 const char *symname, 492 void ***ptrs, 493 struct errinfo *ep) 494 { 495 long err = 0; 496 void **p = NULL; 497 size_t count = 0; 498 499 /* XXX Do we need to add a leading "_" to the symbol name on any 500 modern platforms? */ 501 502 Tprintf("get_plugin_data_sym(%s)\n", symname); 503 504 if (!err) { 505 p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */ 506 if (p == NULL) { err = ENOMEM; } 507 } 508 509 if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) { 510 int i = 0; 511 512 for (i = 0; !err && (dirhandle->files[i] != NULL); i++) { 513 void *sym = NULL; 514 515 if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) { 516 void **newp = NULL; 517 518 count++; 519 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */ 520 if (newp == NULL) { 521 err = ENOMEM; 522 } else { 523 p = newp; 524 p[count - 1] = sym; 525 p[count] = NULL; 526 } 527 } 528 } 529 } 530 531 if (!err) { 532 *ptrs = p; 533 p = NULL; /* ptrs takes ownership */ 534 } 535 536 free(p); 537 538 return err; 539 } 540 541 void KRB5_CALLCONV 542 krb5int_free_plugin_dir_func (void (**ptrs)(void)) 543 { 544 /* Nothing special to be done per pointer. */ 545 free(ptrs); 546 } 547 548 long KRB5_CALLCONV 549 krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle, 550 const char *symname, 551 void (***ptrs)(void), 552 struct errinfo *ep) 553 { 554 long err = 0; 555 void (**p)() = NULL; 556 size_t count = 0; 557 558 /* XXX Do we need to add a leading "_" to the symbol name on any 559 modern platforms? */ 560 561 Tprintf("get_plugin_data_sym(%s)\n", symname); 562 563 if (!err) { 564 p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */ 565 if (p == NULL) { err = ENOMEM; } 566 } 567 568 if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) { 569 int i = 0; 570 571 for (i = 0; !err && (dirhandle->files[i] != NULL); i++) { 572 void (*sym)() = NULL; 573 574 if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) { 575 void (**newp)() = NULL; 576 577 count++; 578 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */ 579 if (newp == NULL) { 580 err = ENOMEM; 581 } else { 582 p = newp; 583 p[count - 1] = sym; 584 p[count] = NULL; 585 } 586 } 587 } 588 } 589 590 if (!err) { 591 *ptrs = p; 592 p = NULL; /* ptrs takes ownership */ 593 } 594 595 free(p); 596 597 return err; 598 } 599