1 #pragma ident "%Z%%M% %I% %E% SMI" 2 3 /* 4 * util/support/plugins.c 5 * 6 * Copyright 2006 by the Massachusetts Institute of Technology. 7 * All Rights Reserved. 8 * 9 * Export of this software from the United States of America may 10 * require a specific license from the United States Government. 11 * It is the responsibility of any person or organization contemplating 12 * export to obtain such a license before exporting. 13 * 14 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 15 * distribute this software and its documentation for any purpose and 16 * without fee is hereby granted, provided that the above copyright 17 * notice appear in all copies and that both that copyright notice and 18 * this permission notice appear in supporting documentation, and that 19 * the name of M.I.T. not be used in advertising or publicity pertaining 20 * to distribution of the software without specific, written prior 21 * permission. Furthermore if you modify this software you must label 22 * your software as modified software and not distribute it in such a 23 * fashion that it might be confused with the original M.I.T. software. 24 * M.I.T. makes no representations about the suitability of 25 * this software for any purpose. It is provided "as is" without express 26 * or implied warranty. 27 * 28 * 29 * Plugin module support, and shims around dlopen/whatever. 30 */ 31 32 #include "k5-plugin.h" 33 #if USE_DLOPEN 34 #include <dlfcn.h> 35 #endif 36 #if USE_CFBUNDLE 37 #include <CoreFoundation/CoreFoundation.h> 38 #endif 39 #include <stdio.h> 40 #include <sys/types.h> 41 #ifdef HAVE_SYS_STAT_H 42 #include <sys/stat.h> 43 #endif 44 #ifdef HAVE_SYS_PARAM_H 45 #include <sys/param.h> 46 #endif 47 #include <errno.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #ifdef HAVE_UNISTD_H 51 #include <unistd.h> 52 #endif 53 54 #include <stdarg.h> 55 /*ARGSUSED*/ 56 static void Tprintf (const char *fmt, ...) 57 { 58 #ifdef DEBUG 59 va_list va; 60 va_start (va, fmt); 61 vfprintf (stderr, fmt, va); 62 va_end (va); 63 #endif 64 } 65 66 struct plugin_file_handle { 67 #if USE_DLOPEN 68 void *dlhandle; 69 #endif 70 #if USE_CFBUNDLE 71 CFBundleRef bundle; 72 #endif 73 #if !defined (USE_DLOPEN) && !defined (USE_CFBUNDLE) 74 char dummy; 75 #endif 76 }; 77 78 /*ARGSUSED2*/ 79 long KRB5_CALLCONV 80 krb5int_open_plugin (const char *filepath, struct plugin_file_handle **h, struct errinfo *ep) 81 { 82 long err = 0; 83 struct stat statbuf; 84 struct plugin_file_handle *htmp = NULL; 85 int got_plugin = 0; 86 87 if (!err) { 88 if (stat (filepath, &statbuf) < 0) { 89 Tprintf ("stat(%s): %s\n", filepath, strerror (errno)); 90 err = errno; 91 } 92 } 93 94 if (!err) { 95 htmp = calloc (1, sizeof (*htmp)); /* calloc initializes ptrs to NULL */ 96 if (htmp == NULL) { err = errno; } 97 } 98 99 #if USE_DLOPEN 100 if (!err && (statbuf.st_mode & S_IFMT) == S_IFREG) { 101 void *handle = NULL; 102 #ifdef RTLD_GROUP 103 #define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL | RTLD_GROUP) 104 #else 105 #define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL) 106 #endif 107 108 if (!err) { 109 handle = dlopen(filepath, PLUGIN_DLOPEN_FLAGS); 110 if (handle == NULL) { 111 const char *e = dlerror(); 112 Tprintf ("dlopen(%s): %s\n", filepath, e); 113 err = ENOENT; /* XXX */ 114 krb5int_set_error (ep, err, "%s", e); 115 } 116 } 117 118 if (!err) { 119 got_plugin = 1; 120 htmp->dlhandle = handle; 121 handle = NULL; 122 } 123 124 if (handle != NULL) { dlclose (handle); } 125 } 126 #endif 127 128 #if USE_CFBUNDLE 129 if (!err && (statbuf.st_mode & S_IFMT) == S_IFDIR) { 130 CFStringRef pluginPath = NULL; 131 CFURLRef pluginURL = NULL; 132 CFBundleRef pluginBundle = NULL; 133 134 if (!err) { 135 pluginPath = CFStringCreateWithCString (kCFAllocatorDefault, filepath, 136 kCFStringEncodingASCII); 137 if (pluginPath == NULL) { err = ENOMEM; } 138 } 139 140 if (!err) { 141 pluginURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, pluginPath, 142 kCFURLPOSIXPathStyle, true); 143 if (pluginURL == NULL) { err = ENOMEM; } 144 } 145 146 if (!err) { 147 pluginBundle = CFBundleCreate (kCFAllocatorDefault, pluginURL); 148 if (pluginBundle == NULL) { err = ENOENT; } /* XXX need better error */ 149 } 150 151 if (!err) { 152 if (!CFBundleIsExecutableLoaded (pluginBundle)) { 153 int loaded = CFBundleLoadExecutable (pluginBundle); 154 if (!loaded) { err = ENOENT; } /* XXX need better error */ 155 } 156 } 157 158 if (!err) { 159 got_plugin = 1; 160 htmp->bundle = pluginBundle; 161 pluginBundle = NULL; /* htmp->bundle takes ownership */ 162 } 163 164 if (pluginBundle != NULL) { CFRelease (pluginBundle); } 165 if (pluginURL != NULL) { CFRelease (pluginURL); } 166 if (pluginPath != NULL) { CFRelease (pluginPath); } 167 } 168 #endif 169 170 if (!err && !got_plugin) { 171 err = ENOENT; /* no plugin or no way to load plugins */ 172 } 173 174 if (!err) { 175 *h = htmp; 176 htmp = NULL; /* h takes ownership */ 177 } 178 179 if (htmp != NULL) { free (htmp); } 180 181 return err; 182 } 183 184 /*ARGSUSED*/ 185 static long 186 krb5int_get_plugin_sym (struct plugin_file_handle *h, 187 const char *csymname, int isfunc, void **ptr, 188 struct errinfo *ep) 189 { 190 long err = 0; 191 void *sym = NULL; 192 193 #if USE_DLOPEN 194 if (!err && !sym && (h->dlhandle != NULL)) { 195 /* XXX Do we need to add a leading "_" to the symbol name on any 196 modern platforms? */ 197 sym = dlsym (h->dlhandle, csymname); 198 if (sym == NULL) { 199 const char *e = dlerror (); /* XXX copy and save away */ 200 Tprintf ("dlsym(%s): %s\n", csymname, e); 201 err = ENOENT; /* XXX */ 202 krb5int_set_error(ep, err, "%s", e); 203 } 204 } 205 #endif 206 207 #if USE_CFBUNDLE 208 if (!err && !sym && (h->bundle != NULL)) { 209 CFStringRef cfsymname = NULL; 210 211 if (!err) { 212 cfsymname = CFStringCreateWithCString (kCFAllocatorDefault, csymname, 213 kCFStringEncodingASCII); 214 if (cfsymname == NULL) { err = ENOMEM; } 215 } 216 217 if (!err) { 218 if (isfunc) { 219 sym = CFBundleGetFunctionPointerForName (h->bundle, cfsymname); 220 } else { 221 sym = CFBundleGetDataPointerForName (h->bundle, cfsymname); 222 } 223 if (sym == NULL) { err = ENOENT; } /* XXX */ 224 } 225 226 if (cfsymname != NULL) { CFRelease (cfsymname); } 227 } 228 #endif 229 230 if (!err && (sym == NULL)) { 231 err = ENOENT; /* unimplemented */ 232 } 233 234 if (!err) { 235 *ptr = sym; 236 } 237 238 return err; 239 } 240 241 long KRB5_CALLCONV 242 krb5int_get_plugin_data (struct plugin_file_handle *h, const char *csymname, 243 void **ptr, struct errinfo *ep) 244 { 245 return krb5int_get_plugin_sym (h, csymname, 0, ptr, ep); 246 } 247 248 long KRB5_CALLCONV 249 krb5int_get_plugin_func (struct plugin_file_handle *h, const char *csymname, 250 void (**ptr)(), struct errinfo *ep) 251 { 252 void *dptr = NULL; 253 long err = krb5int_get_plugin_sym (h, csymname, 1, &dptr, ep); 254 if (!err) { 255 /* Cast function pointers to avoid code duplication */ 256 *ptr = (void (*)()) dptr; 257 } 258 return err; 259 } 260 261 void KRB5_CALLCONV 262 krb5int_close_plugin (struct plugin_file_handle *h) 263 { 264 #if USE_DLOPEN 265 if (h->dlhandle != NULL) { dlclose(h->dlhandle); } 266 #endif 267 #if USE_CFBUNDLE 268 /* Do not call CFBundleUnloadExecutable because it's not ref counted. 269 * CFRelease will unload the bundle if the internal refcount goes to zero. */ 270 if (h->bundle != NULL) { CFRelease (h->bundle); } 271 #endif 272 free (h); 273 } 274 275 /* autoconf docs suggest using this preference order */ 276 #if HAVE_DIRENT_H || USE_DIRENT_H 277 #include <dirent.h> 278 #define NAMELEN(D) strlen((D)->d_name) 279 #else 280 #define dirent direct 281 #define NAMELEN(D) ((D)->d->namlen) 282 #if HAVE_SYS_NDIR_H 283 # include <sys/ndir.h> 284 #elif HAVE_SYS_DIR_H 285 # include <sys/dir.h> 286 #elif HAVE_NDIR_H 287 # include <ndir.h> 288 #endif 289 #endif 290 291 292 #ifdef HAVE_STRERROR_R 293 #define ERRSTR(ERR, BUF) \ 294 (strerror_r (ERR, BUF, sizeof(BUF)) == 0 ? BUF : strerror (ERR)) 295 #else 296 #define ERRSTR(ERR, BUF) \ 297 (strerror (ERR)) 298 #endif 299 300 static long 301 krb5int_plugin_file_handle_array_init (struct plugin_file_handle ***harray) 302 { 303 long err = 0; 304 305 *harray = calloc (1, sizeof (**harray)); /* calloc initializes to NULL */ 306 if (*harray == NULL) { err = errno; } 307 308 return err; 309 } 310 311 static long 312 krb5int_plugin_file_handle_array_add (struct plugin_file_handle ***harray, int *count, 313 struct plugin_file_handle *p) 314 { 315 long err = 0; 316 struct plugin_file_handle **newharray = NULL; 317 int newcount = *count + 1; 318 319 newharray = realloc (*harray, ((newcount + 1) * sizeof (**harray))); /* +1 for NULL */ 320 if (newharray == NULL) { 321 err = errno; 322 } else { 323 newharray[newcount - 1] = p; 324 newharray[newcount] = NULL; 325 *count = newcount; 326 *harray = newharray; 327 } 328 329 return err; 330 } 331 332 static void 333 krb5int_plugin_file_handle_array_free (struct plugin_file_handle **harray) 334 { 335 if (harray != NULL) { 336 int i; 337 for (i = 0; harray[i] != NULL; i++) { 338 krb5int_close_plugin (harray[i]); 339 } 340 free (harray); 341 } 342 } 343 344 #if TARGET_OS_MAC 345 #define FILEEXTS { "", ".bundle", ".so", NULL } 346 #elif defined(_WIN32) 347 #define FILEEXTS { "", ".dll", NULL } 348 #else 349 #define FILEEXTS { "", ".so", NULL } 350 #endif 351 352 353 static void 354 krb5int_free_plugin_filenames (char **filenames) 355 { 356 if (filenames != NULL) { 357 int i; 358 for (i = 0; filenames[i] != NULL; i++) { 359 free (filenames[i]); 360 } 361 free (filenames); 362 } 363 } 364 365 366 static long 367 krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames) 368 { 369 long err = 0; 370 static const char *const fileexts[] = FILEEXTS; 371 char **tempnames = NULL; 372 int i; 373 374 if (!err) { 375 size_t count = 0; 376 for (i = 0; filebases[i] != NULL; i++, count++); 377 for (i = 0; fileexts[i] != NULL; i++, count++); 378 tempnames = calloc (count, sizeof (char *)); 379 if (tempnames == NULL) { err = errno; } 380 } 381 382 if (!err) { 383 int j; 384 for (i = 0; !err && (filebases[i] != NULL); i++) { 385 size_t baselen = strlen (filebases[i]); 386 for (j = 0; !err && (fileexts[j] != NULL); j++) { 387 size_t len = baselen + strlen (fileexts[j]) + 2; /* '.' + NULL */ 388 tempnames[i+j] = malloc (len * sizeof (char)); 389 if (tempnames[i+j] == NULL) { 390 err = errno; 391 } else { 392 /*LINTED*/ 393 sprintf (tempnames[i+j], "%s%s", filebases[i], fileexts[j]); 394 } 395 } 396 } 397 } 398 399 if (!err) { 400 *filenames = tempnames; 401 tempnames = NULL; 402 } 403 404 if (tempnames != NULL) { krb5int_free_plugin_filenames (tempnames); } 405 406 return err; 407 } 408 409 410 /* Takes a NULL-terminated list of directories. If filebases is NULL, filebases is ignored 411 * all plugins in the directories are loaded. If filebases is a NULL-terminated array of names, 412 * only plugins in the directories with those name (plus any platform extension) are loaded. */ 413 414 long KRB5_CALLCONV 415 krb5int_open_plugin_dirs (const char * const *dirnames, 416 const char * const *filebases, 417 struct plugin_dir_handle *dirhandle, 418 struct errinfo *ep) 419 { 420 long err = 0; 421 struct plugin_file_handle **h = NULL; 422 int count = 0; 423 char **filenames = NULL; 424 int i; 425 426 if (!err) { 427 err = krb5int_plugin_file_handle_array_init (&h); 428 } 429 430 if (!err && (filebases != NULL)) { 431 err = krb5int_get_plugin_filenames (filebases, &filenames); 432 } 433 434 for (i = 0; !err && dirnames[i] != NULL; i++) { 435 size_t dirnamelen = strlen (dirnames[i]) + 1; /* '/' */ 436 if (filenames != NULL) { 437 /* load plugins with names from filenames from each directory */ 438 int j; 439 440 for (j = 0; !err && filenames[j] != NULL; j++) { 441 struct plugin_file_handle *handle = NULL; 442 char *filepath = NULL; 443 444 if (!err) { 445 filepath = malloc (dirnamelen + strlen (filenames[j]) + 1); /* NULL */ 446 if (filepath == NULL) { 447 err = errno; 448 } else { 449 /*LINTED*/ 450 sprintf (filepath, "%s/%s", dirnames[i], filenames[j]); 451 } 452 } 453 454 if (krb5int_open_plugin (filepath, &handle, ep) == 0) { 455 err = krb5int_plugin_file_handle_array_add (&h, &count, handle); 456 if (!err) { handle = NULL; } /* h takes ownership */ 457 } 458 459 if (filepath != NULL) { free (filepath); } 460 if (handle != NULL) { krb5int_close_plugin (handle); } 461 } 462 } else { 463 /* load all plugins in each directory */ 464 #ifndef _WIN32 465 DIR *dir = NULL; 466 467 if (!err) { 468 dir = opendir(dirnames[i]); 469 if (dir == NULL) { 470 err = errno; 471 Tprintf ("-> error %d/%s\n", err, strerror (err)); 472 } 473 } 474 475 while (!err) { 476 struct dirent *d = NULL; 477 char *filepath = NULL; 478 struct plugin_file_handle *handle = NULL; 479 480 d = readdir (dir); 481 if (d == NULL) { break; } 482 483 if ((strcmp (d->d_name, ".") == 0) || 484 (strcmp (d->d_name, "..") == 0)) { 485 continue; 486 } 487 488 if (!err) { 489 int len = NAMELEN (d); 490 filepath = malloc (dirnamelen + len + 1); /* NULL */ 491 if (filepath == NULL) { 492 err = errno; 493 } else { 494 /*LINTED*/ 495 sprintf (filepath, "%s/%*s", dirnames[i], len, d->d_name); 496 } 497 } 498 499 if (!err) { 500 if (krb5int_open_plugin (filepath, &handle, ep) == 0) { 501 err = krb5int_plugin_file_handle_array_add (&h, &count, handle); 502 if (!err) { handle = NULL; } /* h takes ownership */ 503 } 504 } 505 506 if (filepath != NULL) { free (filepath); } 507 if (handle != NULL) { krb5int_close_plugin (handle); } 508 } 509 510 if (dir != NULL) { closedir (dir); } 511 #else 512 /* Until a Windows implementation of this code is implemented */ 513 err = ENOENT; 514 #endif /* _WIN32 */ 515 } 516 } 517 518 if (err == ENOENT) { 519 err = 0; /* ran out of plugins -- do nothing */ 520 } 521 522 if (!err) { 523 dirhandle->files = h; 524 h = NULL; /* dirhandle->files takes ownership */ 525 } 526 527 if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); } 528 if (h != NULL) { krb5int_plugin_file_handle_array_free (h); } 529 530 return err; 531 } 532 533 void KRB5_CALLCONV 534 krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle) 535 { 536 if (dirhandle->files != NULL) { 537 int i; 538 for (i = 0; dirhandle->files[i] != NULL; i++) { 539 krb5int_close_plugin (dirhandle->files[i]); 540 } 541 free (dirhandle->files); 542 dirhandle->files = NULL; 543 } 544 } 545 546 void KRB5_CALLCONV 547 krb5int_free_plugin_dir_data (void **ptrs) 548 { 549 /* Nothing special to be done per pointer. */ 550 free(ptrs); 551 } 552 553 long KRB5_CALLCONV 554 krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle, 555 const char *symname, 556 void ***ptrs, 557 struct errinfo *ep) 558 { 559 long err = 0; 560 void **p = NULL; 561 int count = 0; 562 563 /* XXX Do we need to add a leading "_" to the symbol name on any 564 modern platforms? */ 565 566 Tprintf("get_plugin_data_sym(%s)\n", symname); 567 568 if (!err) { 569 p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */ 570 if (p == NULL) { err = errno; } 571 } 572 573 if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) { 574 int i = 0; 575 576 for (i = 0; !err && (dirhandle->files[i] != NULL); i++) { 577 void *sym = NULL; 578 579 if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) { 580 void **newp = NULL; 581 582 count++; 583 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */ 584 if (newp == NULL) { 585 err = errno; 586 } else { 587 p = newp; 588 p[count - 1] = sym; 589 p[count] = NULL; 590 } 591 } 592 } 593 } 594 595 if (!err) { 596 *ptrs = p; 597 p = NULL; /* ptrs takes ownership */ 598 } 599 600 if (p != NULL) { free (p); } 601 602 return err; 603 } 604 605 void KRB5_CALLCONV 606 krb5int_free_plugin_dir_func (void (**ptrs)(void)) 607 { 608 /* Nothing special to be done per pointer. */ 609 free(ptrs); 610 } 611 612 long KRB5_CALLCONV 613 krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle, 614 const char *symname, 615 void (***ptrs)(void), 616 struct errinfo *ep) 617 { 618 long err = 0; 619 void (**p)() = NULL; 620 int count = 0; 621 622 /* XXX Do we need to add a leading "_" to the symbol name on any 623 modern platforms? */ 624 625 Tprintf("get_plugin_data_sym(%s)\n", symname); 626 627 if (!err) { 628 p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */ 629 if (p == NULL) { err = errno; } 630 } 631 632 if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) { 633 int i = 0; 634 635 for (i = 0; !err && (dirhandle->files[i] != NULL); i++) { 636 void (*sym)() = NULL; 637 638 if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) { 639 void (**newp)() = NULL; 640 641 count++; 642 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */ 643 if (newp == NULL) { 644 err = errno; 645 } else { 646 p = newp; 647 p[count - 1] = sym; 648 p[count] = NULL; 649 } 650 } 651 } 652 } 653 654 if (!err) { 655 *ptrs = p; 656 p = NULL; /* ptrs takes ownership */ 657 } 658 659 if (p != NULL) { free (p); } 660 661 return err; 662 } 663