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)(void), 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 (*)(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 size_t i; 296 297 if (harray != NULL) { 298 for (i = 0; harray[i] != NULL; i++) { 299 krb5int_close_plugin (harray[i]); 300 } 301 free (harray); 302 } 303 } 304 305 #if TARGET_OS_MAC 306 #define FILEEXTS { "", ".bundle", ".dylib", ".so", NULL } 307 #elif defined(_WIN32) 308 #define FILEEXTS { "", ".dll", NULL } 309 #else 310 #define FILEEXTS { "", ".so", NULL } 311 #endif 312 313 314 static void 315 krb5int_free_plugin_filenames (char **filenames) 316 { 317 size_t i; 318 319 if (filenames != NULL) { 320 for (i = 0; filenames[i] != NULL; i++) { 321 free (filenames[i]); 322 } 323 free (filenames); 324 } 325 } 326 327 328 static long 329 krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames) 330 { 331 long err = 0; 332 static const char *const fileexts[] = FILEEXTS; 333 char **tempnames = NULL; 334 size_t bases_count = 0; 335 size_t exts_count = 0; 336 size_t i; 337 338 if (!filebases) { err = EINVAL; } 339 if (!filenames) { err = EINVAL; } 340 341 if (!err) { 342 for (i = 0; filebases[i]; i++) { bases_count++; } 343 for (i = 0; fileexts[i]; i++) { exts_count++; } 344 tempnames = calloc ((bases_count * exts_count)+1, sizeof (char *)); 345 if (!tempnames) { err = ENOMEM; } 346 } 347 348 if (!err) { 349 size_t j; 350 for (i = 0; !err && filebases[i]; i++) { 351 for (j = 0; !err && fileexts[j]; j++) { 352 if (asprintf(&tempnames[(i*exts_count)+j], "%s%s", 353 filebases[i], fileexts[j]) < 0) { 354 tempnames[(i*exts_count)+j] = NULL; 355 err = ENOMEM; 356 } 357 } 358 } 359 tempnames[bases_count * exts_count] = NULL; /* NUL-terminate */ 360 } 361 362 if (!err) { 363 *filenames = tempnames; 364 tempnames = NULL; 365 } 366 367 krb5int_free_plugin_filenames(tempnames); 368 369 return err; 370 } 371 372 373 /* Takes a NULL-terminated list of directories. If filebases is NULL, filebases is ignored 374 * all plugins in the directories are loaded. If filebases is a NULL-terminated array of names, 375 * only plugins in the directories with those name (plus any platform extension) are loaded. */ 376 377 long KRB5_CALLCONV 378 krb5int_open_plugin_dirs (const char * const *dirnames, 379 const char * const *filebases, 380 struct plugin_dir_handle *dirhandle, 381 struct errinfo *ep) 382 { 383 long err = 0; 384 struct plugin_file_handle **h = NULL; 385 size_t count = 0; 386 char **filenames = NULL; 387 size_t i; 388 389 if (!err) { 390 err = krb5int_plugin_file_handle_array_init (&h); 391 } 392 393 if (!err && (filebases != NULL)) { 394 err = krb5int_get_plugin_filenames (filebases, &filenames); 395 } 396 397 for (i = 0; !err && dirnames[i] != NULL; i++) { 398 if (filenames != NULL) { 399 /* load plugins with names from filenames from each directory */ 400 size_t j; 401 402 for (j = 0; !err && filenames[j] != NULL; j++) { 403 struct plugin_file_handle *handle = NULL; 404 char *filepath = NULL; 405 406 if (!err) 407 err = k5_path_join(dirnames[i], filenames[j], &filepath); 408 409 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) { 410 err = krb5int_plugin_file_handle_array_add (&h, &count, handle); 411 if (!err) 412 handle = NULL; /* h takes ownership */ 413 } 414 415 free(filepath); 416 if (handle != NULL) { krb5int_close_plugin (handle); } 417 } 418 } else { 419 char **fnames = NULL; 420 size_t j; 421 422 err = k5_dir_filenames(dirnames[i], &fnames); 423 for (j = 0; !err && fnames[j] != NULL; j++) { 424 char *filepath = NULL; 425 struct plugin_file_handle *handle = NULL; 426 427 if (strcmp(fnames[j], ".") == 0 || 428 strcmp(fnames[j], "..") == 0) 429 continue; 430 431 err = k5_path_join(dirnames[i], fnames[j], &filepath); 432 433 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) { 434 err = krb5int_plugin_file_handle_array_add(&h, &count, 435 handle); 436 if (!err) 437 handle = NULL; /* h takes ownership */ 438 } 439 440 free(filepath); 441 if (handle != NULL) 442 krb5int_close_plugin(handle); 443 } 444 445 k5_free_filenames(fnames); 446 } 447 } 448 449 if (err == ENOENT) { 450 err = 0; /* ran out of plugins -- do nothing */ 451 } 452 453 if (!err) { 454 dirhandle->files = h; 455 h = NULL; /* dirhandle->files takes ownership */ 456 } 457 458 if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); } 459 if (h != NULL) { krb5int_plugin_file_handle_array_free (h); } 460 461 return err; 462 } 463 464 void KRB5_CALLCONV 465 krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle) 466 { 467 size_t i; 468 469 if (dirhandle->files != NULL) { 470 for (i = 0; dirhandle->files[i] != NULL; i++) { 471 krb5int_close_plugin (dirhandle->files[i]); 472 } 473 free (dirhandle->files); 474 dirhandle->files = NULL; 475 } 476 } 477 478 void KRB5_CALLCONV 479 krb5int_free_plugin_dir_data (void **ptrs) 480 { 481 /* Nothing special to be done per pointer. */ 482 free(ptrs); 483 } 484 485 long KRB5_CALLCONV 486 krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle, 487 const char *symname, 488 void ***ptrs, 489 struct errinfo *ep) 490 { 491 long err = 0; 492 void **p = NULL; 493 size_t count = 0; 494 495 /* XXX Do we need to add a leading "_" to the symbol name on any 496 modern platforms? */ 497 498 Tprintf("get_plugin_data_sym(%s)\n", symname); 499 500 if (!err) { 501 p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */ 502 if (p == NULL) { err = ENOMEM; } 503 } 504 505 if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) { 506 size_t i = 0; 507 508 for (i = 0; !err && (dirhandle->files[i] != NULL); i++) { 509 void *sym = NULL; 510 511 if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) { 512 void **newp = NULL; 513 514 count++; 515 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */ 516 if (newp == NULL) { 517 err = ENOMEM; 518 } else { 519 p = newp; 520 p[count - 1] = sym; 521 p[count] = NULL; 522 } 523 } 524 } 525 } 526 527 if (!err) { 528 *ptrs = p; 529 p = NULL; /* ptrs takes ownership */ 530 } 531 532 free(p); 533 534 return err; 535 } 536 537 void KRB5_CALLCONV 538 krb5int_free_plugin_dir_func (void (**ptrs)(void)) 539 { 540 /* Nothing special to be done per pointer. */ 541 free(ptrs); 542 } 543 544 long KRB5_CALLCONV 545 krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle, 546 const char *symname, 547 void (***ptrs)(void), 548 struct errinfo *ep) 549 { 550 long err = 0; 551 void (**p)(void) = NULL; 552 size_t count = 0; 553 554 /* XXX Do we need to add a leading "_" to the symbol name on any 555 modern platforms? */ 556 557 Tprintf("get_plugin_data_sym(%s)\n", symname); 558 559 if (!err) { 560 p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */ 561 if (p == NULL) { err = ENOMEM; } 562 } 563 564 if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) { 565 size_t i = 0; 566 567 for (i = 0; !err && (dirhandle->files[i] != NULL); i++) { 568 void (*sym)(void) = NULL; 569 570 if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) { 571 void (**newp)(void) = NULL; 572 573 count++; 574 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */ 575 if (newp == NULL) { 576 err = ENOMEM; 577 } else { 578 p = newp; 579 p[count - 1] = sym; 580 p[count] = NULL; 581 } 582 } 583 } 584 } 585 586 if (!err) { 587 *ptrs = p; 588 p = NULL; /* ptrs takes ownership */ 589 } 590 591 free(p); 592 593 return err; 594 } 595