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