1 /*- 2 * Copyright (c) 2023-2025 Dag-Erling Smørgrav <des@FreeBSD.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 #include <sys/sysctl.h> 8 #include <sys/stat.h> 9 #include <sys/tree.h> 10 11 #include <dirent.h> 12 #include <err.h> 13 #include <errno.h> 14 #include <fcntl.h> 15 #include <fts.h> 16 #include <paths.h> 17 #include <stdbool.h> 18 #include <stdio.h> 19 #include <stdlib.h> 20 #include <string.h> 21 #include <unistd.h> 22 23 #include <openssl/ssl.h> 24 25 #define info(fmt, ...) \ 26 do { \ 27 if (verbose) \ 28 fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ 29 } while (0) 30 31 static char * 32 xasprintf(const char *fmt, ...) 33 { 34 va_list ap; 35 char *str; 36 int ret; 37 38 va_start(ap, fmt); 39 ret = vasprintf(&str, fmt, ap); 40 va_end(ap); 41 if (ret < 0 || str == NULL) 42 err(1, NULL); 43 return (str); 44 } 45 46 static char * 47 xstrdup(const char *str) 48 { 49 char *dup; 50 51 if ((dup = strdup(str)) == NULL) 52 err(1, NULL); 53 return (dup); 54 } 55 56 static void usage(void); 57 58 static bool dryrun; 59 static bool longnames; 60 static bool nobundle; 61 static bool unprivileged; 62 static bool verbose; 63 64 static const char *localbase; 65 static const char *destdir; 66 static const char *distbase; 67 static const char *metalog; 68 69 static const char *uname = "root"; 70 static const char *gname = "wheel"; 71 72 static const char *const default_trusted_paths[] = { 73 "/usr/share/certs/trusted", 74 "%L/share/certs/trusted", 75 "%L/share/certs", 76 NULL 77 }; 78 static char **trusted_paths; 79 80 static const char *const default_untrusted_paths[] = { 81 "/usr/share/certs/untrusted", 82 "%L/share/certs/untrusted", 83 NULL 84 }; 85 static char **untrusted_paths; 86 87 static char *trusted_dest; 88 static char *untrusted_dest; 89 static char *bundle_dest; 90 91 #define SSL_PATH "/etc/ssl" 92 #define TRUSTED_DIR "certs" 93 #define TRUSTED_PATH SSL_PATH "/" TRUSTED_DIR 94 #define UNTRUSTED_DIR "untrusted" 95 #define UNTRUSTED_PATH SSL_PATH "/" UNTRUSTED_DIR 96 #define LEGACY_DIR "blacklisted" 97 #define LEGACY_PATH SSL_PATH "/" LEGACY_DIR 98 #define BUNDLE_FILE "cert.pem" 99 #define BUNDLE_PATH SSL_PATH "/" BUNDLE_FILE 100 101 static FILE *mlf; 102 103 /* 104 * Create a directory and its parents as needed. 105 */ 106 static void 107 mkdirp(const char *dir) 108 { 109 struct stat sb; 110 const char *sep; 111 char *parent; 112 113 if (stat(dir, &sb) == 0) 114 return; 115 if ((sep = strrchr(dir, '/')) != NULL) { 116 parent = xasprintf("%.*s", (int)(sep - dir), dir); 117 mkdirp(parent); 118 free(parent); 119 } 120 info("creating %s", dir); 121 if (mkdir(dir, 0755) != 0) 122 err(1, "mkdir %s", dir); 123 } 124 125 /* 126 * Remove duplicate and trailing slashes from a path. 127 */ 128 static char * 129 normalize_path(const char *str) 130 { 131 char *buf, *dst; 132 133 if ((buf = malloc(strlen(str) + 1)) == NULL) 134 err(1, NULL); 135 for (dst = buf; *str != '\0'; dst++) { 136 if ((*dst = *str++) == '/') { 137 while (*str == '/') 138 str++; 139 if (*str == '\0') 140 break; 141 } 142 } 143 *dst = '\0'; 144 return (buf); 145 } 146 147 /* 148 * Split a colon-separated list into a NULL-terminated array. 149 */ 150 static char ** 151 split_paths(const char *str) 152 { 153 char **paths; 154 const char *p, *q; 155 unsigned int i, n; 156 157 for (p = str, n = 1; *p; p++) { 158 if (*p == ':') 159 n++; 160 } 161 if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) 162 err(1, NULL); 163 for (p = q = str, i = 0; i < n; i++, p = q + 1) { 164 q = strchrnul(p, ':'); 165 if ((paths[i] = strndup(p, q - p)) == NULL) 166 err(1, NULL); 167 } 168 return (paths); 169 } 170 171 /* 172 * Expand %L into LOCALBASE and prefix DESTDIR and DISTBASE as needed. 173 */ 174 static char * 175 expand_path(const char *template) 176 { 177 if (template[0] == '%' && template[1] == 'L') 178 return (xasprintf("%s%s%s", destdir, localbase, template + 2)); 179 return (xasprintf("%s%s%s", destdir, distbase, template)); 180 } 181 182 /* 183 * Expand an array of paths. 184 */ 185 static char ** 186 expand_paths(const char *const *templates) 187 { 188 char **paths; 189 unsigned int i, n; 190 191 for (n = 0; templates[n] != NULL; n++) 192 continue; 193 if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) 194 err(1, NULL); 195 for (i = 0; i < n; i++) 196 paths[i] = expand_path(templates[i]); 197 return (paths); 198 } 199 200 /* 201 * If destdir is a prefix of path, returns a pointer to the rest of path, 202 * otherwise returns path. 203 * 204 * Note that this intentionally does not strip distbase from the path! 205 * Unlike destdir, distbase is expected to be included in the metalog. 206 */ 207 static const char * 208 unexpand_path(const char *path) 209 { 210 const char *p = path; 211 const char *q = destdir; 212 213 while (*p && *p == *q) { 214 p++; 215 q++; 216 } 217 return (*q == '\0' && *p == '/' ? p : path); 218 } 219 220 /* 221 * X509 certificate in a rank-balanced tree. 222 */ 223 struct cert { 224 RB_ENTRY(cert) entry; 225 unsigned long hash; 226 char *name; 227 X509 *x509; 228 char *path; 229 }; 230 231 static void 232 free_cert(struct cert *cert) 233 { 234 free(cert->name); 235 X509_free(cert->x509); 236 free(cert->path); 237 free(cert); 238 } 239 240 static int 241 certcmp(const struct cert *a, const struct cert *b) 242 { 243 return (X509_cmp(a->x509, b->x509)); 244 } 245 246 RB_HEAD(cert_tree, cert); 247 static struct cert_tree trusted = RB_INITIALIZER(&trusted); 248 static struct cert_tree untrusted = RB_INITIALIZER(&untrusted); 249 RB_GENERATE_STATIC(cert_tree, cert, entry, certcmp); 250 251 static void 252 free_certs(struct cert_tree *tree) 253 { 254 struct cert *cert, *tmp; 255 256 RB_FOREACH_SAFE(cert, cert_tree, tree, tmp) { 257 RB_REMOVE(cert_tree, tree, cert); 258 free_cert(cert); 259 } 260 } 261 262 static struct cert * 263 find_cert(struct cert_tree *haystack, X509 *x509) 264 { 265 struct cert needle = { .x509 = x509 }; 266 267 return (RB_FIND(cert_tree, haystack, &needle)); 268 } 269 270 /* 271 * File containing a certificate in a rank-balanced tree sorted by 272 * certificate hash and disambiguating counter. This is needed because 273 * the certificate hash function is prone to collisions, necessitating a 274 * counter to distinguish certificates that hash to the same value. 275 */ 276 struct file { 277 RB_ENTRY(file) entry; 278 const struct cert *cert; 279 unsigned int c; 280 }; 281 282 static int 283 filecmp(const struct file *a, const struct file *b) 284 { 285 if (a->cert->hash > b->cert->hash) 286 return (1); 287 if (a->cert->hash < b->cert->hash) 288 return (-1); 289 return (a->c - b->c); 290 } 291 292 RB_HEAD(file_tree, file); 293 RB_GENERATE_STATIC(file_tree, file, entry, filecmp); 294 295 /* 296 * Lexicographical sort for scandir(). 297 */ 298 static int 299 lexisort(const struct dirent **d1, const struct dirent **d2) 300 { 301 return (strcmp((*d1)->d_name, (*d2)->d_name)); 302 } 303 304 /* 305 * Read certificate(s) from a single file and insert them into a tree. 306 * Ignore certificates that already exist in the tree. If exclude is not 307 * null, also ignore certificates that exist in exclude. 308 * 309 * Returns the number certificates added to the tree, or -1 on failure. 310 */ 311 static int 312 read_cert(const char *path, struct cert_tree *tree, struct cert_tree *exclude) 313 { 314 FILE *f; 315 X509 *x509; 316 X509_NAME *name; 317 struct cert *cert; 318 unsigned long hash; 319 int len, ni, no; 320 321 if ((f = fopen(path, "r")) == NULL) { 322 warn("%s", path); 323 return (-1); 324 } 325 for (ni = no = 0; 326 (x509 = PEM_read_X509(f, NULL, NULL, NULL)) != NULL; 327 ni++) { 328 hash = X509_subject_name_hash(x509); 329 if (exclude && find_cert(exclude, x509)) { 330 info("%08lx: excluded", hash); 331 X509_free(x509); 332 continue; 333 } 334 if (find_cert(tree, x509)) { 335 info("%08lx: duplicate", hash); 336 X509_free(x509); 337 continue; 338 } 339 if ((cert = calloc(1, sizeof(*cert))) == NULL) 340 err(1, NULL); 341 cert->x509 = x509; 342 name = X509_get_subject_name(x509); 343 cert->hash = X509_NAME_hash_ex(name, NULL, NULL, NULL); 344 len = X509_NAME_get_text_by_NID(name, NID_commonName, 345 NULL, 0); 346 if (len > 0) { 347 if ((cert->name = malloc(len + 1)) == NULL) 348 err(1, NULL); 349 X509_NAME_get_text_by_NID(name, NID_commonName, 350 cert->name, len + 1); 351 } else { 352 /* fallback for certificates without CN */ 353 cert->name = X509_NAME_oneline(name, NULL, 0); 354 } 355 cert->path = xstrdup(unexpand_path(path)); 356 if (RB_INSERT(cert_tree, tree, cert) != NULL) 357 errx(1, "unexpected duplicate"); 358 info("%08lx: %s", cert->hash, cert->name); 359 no++; 360 } 361 /* 362 * ni is the number of certificates we found in the file. 363 * no is the number of certificates that weren't already in our 364 * tree or on the exclusion list. 365 */ 366 if (ni == 0) 367 warnx("%s: no valid certificates found", path); 368 fclose(f); 369 return (no); 370 } 371 372 /* 373 * Load all certificates found in the specified path into a tree, 374 * optionally excluding those that already exist in a different tree. 375 * 376 * Returns the number of certificates added to the tree, or -1 on failure. 377 */ 378 static int 379 read_certs(const char *path, struct cert_tree *tree, struct cert_tree *exclude) 380 { 381 struct stat sb; 382 char *paths[] = { (char *)(uintptr_t)path, NULL }; 383 FTS *fts; 384 FTSENT *ent; 385 int fts_options = FTS_LOGICAL | FTS_NOCHDIR; 386 int ret, total = 0; 387 388 if (stat(path, &sb) != 0) { 389 return (-1); 390 } else if (!S_ISDIR(sb.st_mode)) { 391 errno = ENOTDIR; 392 return (-1); 393 } 394 if ((fts = fts_open(paths, fts_options, NULL)) == NULL) 395 err(1, "fts_open()"); 396 while ((ent = fts_read(fts)) != NULL) { 397 if (ent->fts_info != FTS_F) { 398 if (ent->fts_info == FTS_ERR) 399 warnc(ent->fts_errno, "fts_read()"); 400 continue; 401 } 402 info("found %s", ent->fts_path); 403 ret = read_cert(ent->fts_path, tree, exclude); 404 if (ret > 0) 405 total += ret; 406 } 407 fts_close(fts); 408 return (total); 409 } 410 411 /* 412 * Save the contents of a cert tree to disk. 413 * 414 * Returns 0 on success and -1 on failure. 415 */ 416 static int 417 write_certs(const char *dir, struct cert_tree *tree) 418 { 419 struct file_tree files = RB_INITIALIZER(&files); 420 struct cert *cert; 421 struct file *file, *tmp; 422 struct dirent **dents, **ent; 423 char *path, *tmppath = NULL; 424 FILE *f; 425 mode_t mode = 0444; 426 int cmp, d, fd, ndents, ret = 0; 427 428 /* 429 * Start by generating unambiguous file names for each certificate 430 * and storing them in lexicographical order 431 */ 432 RB_FOREACH(cert, cert_tree, tree) { 433 if ((file = calloc(1, sizeof(*file))) == NULL) 434 err(1, NULL); 435 file->cert = cert; 436 for (file->c = 0; file->c < INT_MAX; file->c++) 437 if (RB_INSERT(file_tree, &files, file) == NULL) 438 break; 439 if (file->c == INT_MAX) 440 errx(1, "unable to disambiguate %08lx", cert->hash); 441 free(cert->path); 442 cert->path = xasprintf("%08lx.%d", cert->hash, file->c); 443 } 444 /* 445 * Open and scan the directory. 446 */ 447 if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0 || 448 #ifdef BOOTSTRAPPING 449 (ndents = scandir(dir, &dents, NULL, lexisort)) 450 #else 451 (ndents = fdscandir(d, &dents, NULL, lexisort)) 452 #endif 453 < 0) 454 err(1, "%s", dir); 455 /* 456 * Iterate over the directory listing and the certificate listing 457 * in parallel. If the directory listing gets ahead of the 458 * certificate listing, we need to write the current certificate 459 * and advance the certificate listing. If the certificate 460 * listing is ahead of the directory listing, we need to delete 461 * the current file and advance the directory listing. If they 462 * are neck and neck, we have a match and could in theory compare 463 * the two, but in practice it's faster to just replace the 464 * current file with the current certificate (and advance both). 465 */ 466 ent = dents; 467 file = RB_MIN(file_tree, &files); 468 for (;;) { 469 if (ent < dents + ndents) { 470 /* skip directories */ 471 if ((*ent)->d_type == DT_DIR) { 472 free(*ent++); 473 continue; 474 } 475 if (file != NULL) { 476 /* compare current dirent to current cert */ 477 path = file->cert->path; 478 cmp = strcmp((*ent)->d_name, path); 479 } else { 480 /* trailing files in directory */ 481 path = NULL; 482 cmp = -1; 483 } 484 } else { 485 if (file != NULL) { 486 /* trailing certificates */ 487 path = file->cert->path; 488 cmp = 1; 489 } else { 490 /* end of both lists */ 491 path = NULL; 492 break; 493 } 494 } 495 if (cmp < 0) { 496 /* a file on disk with no matching certificate */ 497 info("removing %s/%s", dir, (*ent)->d_name); 498 if (!dryrun) 499 (void)unlinkat(d, (*ent)->d_name, 0); 500 free(*ent++); 501 continue; 502 } 503 if (cmp == 0) { 504 /* a file on disk with a matching certificate */ 505 info("replacing %s/%s", dir, (*ent)->d_name); 506 if (dryrun) { 507 fd = open(_PATH_DEVNULL, O_WRONLY); 508 } else { 509 tmppath = xasprintf(".%s", path); 510 fd = openat(d, tmppath, 511 O_CREAT | O_WRONLY | O_TRUNC, mode); 512 if (!unprivileged && fd >= 0) 513 (void)fchmod(fd, mode); 514 } 515 free(*ent++); 516 } else { 517 /* a certificate with no matching file */ 518 info("writing %s/%s", dir, path); 519 if (dryrun) { 520 fd = open(_PATH_DEVNULL, O_WRONLY); 521 } else { 522 tmppath = xasprintf(".%s", path); 523 fd = openat(d, tmppath, 524 O_CREAT | O_WRONLY | O_EXCL, mode); 525 } 526 } 527 /* write the certificate */ 528 if (fd < 0 || 529 (f = fdopen(fd, "w")) == NULL || 530 !PEM_write_X509(f, file->cert->x509)) { 531 if (tmppath != NULL && fd >= 0) { 532 int serrno = errno; 533 (void)unlinkat(d, tmppath, 0); 534 errno = serrno; 535 } 536 err(1, "%s/%s", dir, tmppath ? tmppath : path); 537 } 538 /* rename temp file if applicable */ 539 if (tmppath != NULL) { 540 if (ret == 0 && renameat(d, tmppath, d, path) != 0) { 541 warn("%s/%s", dir, path); 542 ret = -1; 543 } 544 if (ret != 0) 545 (void)unlinkat(d, tmppath, 0); 546 free(tmppath); 547 tmppath = NULL; 548 } 549 fflush(f); 550 /* emit metalog */ 551 if (mlf != NULL) { 552 fprintf(mlf, ".%s/%s type=file " 553 "uname=%s gname=%s mode=%#o size=%ld\n", 554 unexpand_path(dir), path, 555 uname, gname, mode, ftell(f)); 556 } 557 fclose(f); 558 /* advance certificate listing */ 559 tmp = RB_NEXT(file_tree, &files, file); 560 RB_REMOVE(file_tree, &files, file); 561 free(file); 562 file = tmp; 563 } 564 free(dents); 565 close(d); 566 return (ret); 567 } 568 569 /* 570 * Save all certs in a tree to a single file (bundle). 571 * 572 * Returns 0 on success and -1 on failure. 573 */ 574 static int 575 write_bundle(const char *dir, const char *file, struct cert_tree *tree) 576 { 577 struct cert *cert; 578 char *tmpfile = NULL; 579 FILE *f; 580 int d, fd, ret = 0; 581 mode_t mode = 0444; 582 583 if (dir != NULL) { 584 if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0) 585 err(1, "%s", dir); 586 } else { 587 dir = "."; 588 d = AT_FDCWD; 589 } 590 info("writing %s/%s", dir, file); 591 if (dryrun) { 592 fd = open(_PATH_DEVNULL, O_WRONLY); 593 } else { 594 tmpfile = xasprintf(".%s", file); 595 fd = openat(d, tmpfile, O_WRONLY | O_CREAT | O_EXCL, mode); 596 } 597 if (fd < 0 || (f = fdopen(fd, "w")) == NULL) { 598 if (tmpfile != NULL && fd >= 0) { 599 int serrno = errno; 600 (void)unlinkat(d, tmpfile, 0); 601 errno = serrno; 602 } 603 err(1, "%s/%s", dir, tmpfile ? tmpfile : file); 604 } 605 RB_FOREACH(cert, cert_tree, tree) { 606 if (!PEM_write_X509(f, cert->x509)) { 607 warn("%s/%s", dir, tmpfile ? tmpfile : file); 608 ret = -1; 609 break; 610 } 611 } 612 if (tmpfile != NULL) { 613 if (ret == 0 && renameat(d, tmpfile, d, file) != 0) { 614 warn("%s/%s", dir, file); 615 ret = -1; 616 } 617 if (ret != 0) 618 (void)unlinkat(d, tmpfile, 0); 619 free(tmpfile); 620 } 621 if (ret == 0 && mlf != NULL) { 622 fprintf(mlf, 623 ".%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n", 624 unexpand_path(dir), file, uname, gname, mode, ftell(f)); 625 } 626 fclose(f); 627 if (d != AT_FDCWD) 628 close(d); 629 return (ret); 630 } 631 632 /* 633 * Load trusted certificates. 634 * 635 * Returns the number of certificates loaded. 636 */ 637 static unsigned int 638 load_trusted(bool all, struct cert_tree *exclude) 639 { 640 unsigned int i, n; 641 int ret; 642 643 /* load external trusted certs */ 644 for (i = n = 0; all && trusted_paths[i] != NULL; i++) { 645 ret = read_certs(trusted_paths[i], &trusted, exclude); 646 if (ret > 0) 647 n += ret; 648 } 649 650 /* load installed trusted certs */ 651 ret = read_certs(trusted_dest, &trusted, exclude); 652 if (ret > 0) 653 n += ret; 654 655 info("%d trusted certificates found", n); 656 return (n); 657 } 658 659 /* 660 * Load untrusted certificates. 661 * 662 * Returns the number of certificates loaded. 663 */ 664 static unsigned int 665 load_untrusted(bool all) 666 { 667 char *path; 668 unsigned int i, n; 669 int ret; 670 671 /* load external untrusted certs */ 672 for (i = n = 0; all && untrusted_paths[i] != NULL; i++) { 673 ret = read_certs(untrusted_paths[i], &untrusted, NULL); 674 if (ret > 0) 675 n += ret; 676 } 677 678 /* load installed untrusted certs */ 679 ret = read_certs(untrusted_dest, &untrusted, NULL); 680 if (ret > 0) 681 n += ret; 682 683 /* load legacy untrusted certs */ 684 path = expand_path(LEGACY_PATH); 685 ret = read_certs(path, &untrusted, NULL); 686 if (ret > 0) { 687 warnx("certificates found in legacy directory %s", 688 path); 689 n += ret; 690 } else if (ret == 0) { 691 warnx("legacy directory %s can safely be deleted", 692 path); 693 } 694 free(path); 695 696 info("%d untrusted certificates found", n); 697 return (n); 698 } 699 700 /* 701 * Save trusted certificates. 702 * 703 * Returns 0 on success and -1 on failure. 704 */ 705 static int 706 save_trusted(void) 707 { 708 int ret; 709 710 mkdirp(trusted_dest); 711 ret = write_certs(trusted_dest, &trusted); 712 return (ret); 713 } 714 715 /* 716 * Save untrusted certificates. 717 * 718 * Returns 0 on success and -1 on failure. 719 */ 720 static int 721 save_untrusted(void) 722 { 723 int ret; 724 725 mkdirp(untrusted_dest); 726 ret = write_certs(untrusted_dest, &untrusted); 727 return (ret); 728 } 729 730 /* 731 * Save certificate bundle. 732 * 733 * Returns 0 on success and -1 on failure. 734 */ 735 static int 736 save_bundle(void) 737 { 738 char *dir, *file, *sep; 739 int ret; 740 741 if ((sep = strrchr(bundle_dest, '/')) == NULL) { 742 dir = NULL; 743 file = bundle_dest; 744 } else { 745 dir = xasprintf("%.*s", (int)(sep - bundle_dest), bundle_dest); 746 file = sep + 1; 747 mkdirp(dir); 748 } 749 ret = write_bundle(dir, file, &trusted); 750 free(dir); 751 return (ret); 752 } 753 754 /* 755 * Save everything. 756 * 757 * Returns 0 on success and -1 on failure. 758 */ 759 static int 760 save_all(void) 761 { 762 int ret = 0; 763 764 ret |= save_untrusted(); 765 ret |= save_trusted(); 766 if (!nobundle) 767 ret |= save_bundle(); 768 return (ret); 769 } 770 771 /* 772 * List the contents of a certificate tree. 773 */ 774 static void 775 list_certs(struct cert_tree *tree) 776 { 777 struct cert *cert; 778 char *path, *name; 779 780 RB_FOREACH(cert, cert_tree, tree) { 781 path = longnames ? NULL : strrchr(cert->path, '/'); 782 name = longnames ? NULL : strrchr(cert->name, '='); 783 printf("%s\t%s\n", path ? path + 1 : cert->path, 784 name ? name + 1 : cert->name); 785 } 786 } 787 788 /* 789 * Load installed trusted certificates, then list them. 790 * 791 * Returns 0 on success and -1 on failure. 792 */ 793 static int 794 certctl_list(int argc, char **argv __unused) 795 { 796 if (argc > 1) 797 usage(); 798 /* load trusted certificates */ 799 load_trusted(false, NULL); 800 /* list them */ 801 list_certs(&trusted); 802 free_certs(&trusted); 803 return (0); 804 } 805 806 /* 807 * Load installed untrusted certificates, then list them. 808 * 809 * Returns 0 on success and -1 on failure. 810 */ 811 static int 812 certctl_untrusted(int argc, char **argv __unused) 813 { 814 if (argc > 1) 815 usage(); 816 /* load untrusted certificates */ 817 load_untrusted(false); 818 /* list them */ 819 list_certs(&untrusted); 820 free_certs(&untrusted); 821 return (0); 822 } 823 824 /* 825 * Load trusted and untrusted certificates from all sources, then 826 * regenerate both the hashed directories and the bundle. 827 * 828 * Returns 0 on success and -1 on failure. 829 */ 830 static int 831 certctl_rehash(int argc, char **argv __unused) 832 { 833 int ret; 834 835 if (argc > 1) 836 usage(); 837 838 if (unprivileged && (mlf = fopen(metalog, "a")) == NULL) { 839 warn("%s", metalog); 840 return (-1); 841 } 842 843 /* load untrusted certs first */ 844 load_untrusted(true); 845 846 /* load trusted certs, excluding any that are already untrusted */ 847 load_trusted(true, &untrusted); 848 849 /* save everything */ 850 ret = save_all(); 851 852 /* clean up */ 853 free_certs(&untrusted); 854 free_certs(&trusted); 855 if (mlf != NULL) 856 fclose(mlf); 857 return (ret); 858 } 859 860 /* 861 * Manually add one or more certificates to the list of trusted certificates. 862 * 863 * Returns 0 on success and -1 on failure. 864 */ 865 static int 866 certctl_trust(int argc, char **argv) 867 { 868 struct cert_tree extra = RB_INITIALIZER(&extra); 869 struct cert *cert, *other, *tmp; 870 unsigned int n; 871 int i, ret; 872 873 if (argc < 2) 874 usage(); 875 876 /* load untrusted certs first */ 877 load_untrusted(true); 878 879 /* load trusted certs, excluding any that are already untrusted */ 880 load_trusted(true, &untrusted); 881 882 /* now load the additional trusted certificates */ 883 n = 0; 884 for (i = 1; i < argc; i++) { 885 ret = read_cert(argv[i], &extra, &trusted); 886 if (ret > 0) 887 n += ret; 888 } 889 if (n == 0) { 890 warnx("no new trusted certificates found"); 891 free_certs(&untrusted); 892 free_certs(&trusted); 893 free_certs(&extra); 894 return (0); 895 } 896 897 /* 898 * For each new trusted cert, move it from the extra list to the 899 * trusted list, then check if a matching certificate exists on 900 * the untrusted list. If that is the case, warn the user, then 901 * remove the matching certificate from the untrusted list. 902 */ 903 RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) { 904 RB_REMOVE(cert_tree, &extra, cert); 905 RB_INSERT(cert_tree, &trusted, cert); 906 if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) { 907 warnx("%s was previously untrusted", cert->name); 908 RB_REMOVE(cert_tree, &untrusted, other); 909 free_cert(other); 910 } 911 } 912 913 /* save everything */ 914 ret = save_all(); 915 916 /* clean up */ 917 free_certs(&untrusted); 918 free_certs(&trusted); 919 return (ret); 920 } 921 922 /* 923 * Manually add one or more certificates to the list of untrusted 924 * certificates. 925 * 926 * Returns 0 on success and -1 on failure. 927 */ 928 static int 929 certctl_untrust(int argc, char **argv) 930 { 931 unsigned int n; 932 int i, ret; 933 934 if (argc < 2) 935 usage(); 936 937 /* load untrusted certs first */ 938 load_untrusted(true); 939 940 /* now load the additional untrusted certificates */ 941 n = 0; 942 for (i = 1; i < argc; i++) { 943 ret = read_cert(argv[i], &untrusted, NULL); 944 if (ret > 0) 945 n += ret; 946 } 947 if (n == 0) { 948 warnx("no new untrusted certificates found"); 949 free_certs(&untrusted); 950 return (0); 951 } 952 953 /* load trusted certs, excluding any that are already untrusted */ 954 load_trusted(true, &untrusted); 955 956 /* save everything */ 957 ret = save_all(); 958 959 /* clean up */ 960 free_certs(&untrusted); 961 free_certs(&trusted); 962 return (ret); 963 } 964 965 static void 966 set_defaults(void) 967 { 968 const char *value; 969 char *str; 970 size_t len; 971 972 if (localbase == NULL && 973 (localbase = getenv("LOCALBASE")) == NULL) { 974 if ((str = malloc((len = PATH_MAX) + 1)) == NULL) 975 err(1, NULL); 976 while (sysctlbyname("user.localbase", str, &len, NULL, 0) < 0) { 977 if (errno != ENOMEM) 978 err(1, "sysctl(user.localbase)"); 979 if ((str = realloc(str, len + 1)) == NULL) 980 err(1, NULL); 981 } 982 str[len] = '\0'; 983 localbase = str; 984 } 985 986 if (destdir == NULL && 987 (destdir = getenv("DESTDIR")) == NULL) 988 destdir = ""; 989 destdir = normalize_path(destdir); 990 991 if (distbase == NULL && 992 (distbase = getenv("DISTBASE")) == NULL) 993 distbase = ""; 994 if (*distbase != '\0' && *distbase != '/') 995 errx(1, "DISTBASE=%s does not begin with a slash", distbase); 996 distbase = normalize_path(distbase); 997 998 if (unprivileged && metalog == NULL && 999 (metalog = getenv("METALOG")) == NULL) 1000 metalog = xasprintf("%s/METALOG", destdir); 1001 1002 if (!verbose) { 1003 if ((value = getenv("CERTCTL_VERBOSE")) != NULL) { 1004 if (value[0] != '\0') { 1005 verbose = true; 1006 } 1007 } 1008 } 1009 1010 if ((value = getenv("TRUSTPATH")) != NULL) 1011 trusted_paths = split_paths(value); 1012 else 1013 trusted_paths = expand_paths(default_trusted_paths); 1014 1015 if ((value = getenv("UNTRUSTPATH")) != NULL) 1016 untrusted_paths = split_paths(value); 1017 else 1018 untrusted_paths = expand_paths(default_untrusted_paths); 1019 1020 if ((value = getenv("TRUSTDESTDIR")) != NULL || 1021 (value = getenv("CERTDESTDIR")) != NULL) 1022 trusted_dest = normalize_path(value); 1023 else 1024 trusted_dest = expand_path(TRUSTED_PATH); 1025 1026 if ((value = getenv("UNTRUSTDESTDIR")) != NULL) 1027 untrusted_dest = normalize_path(value); 1028 else 1029 untrusted_dest = expand_path(UNTRUSTED_PATH); 1030 1031 if ((value = getenv("BUNDLE")) != NULL) 1032 bundle_dest = normalize_path(value); 1033 else 1034 bundle_dest = expand_path(BUNDLE_PATH); 1035 1036 info("localbase:\t%s", localbase); 1037 info("destdir:\t%s", destdir); 1038 info("distbase:\t%s", distbase); 1039 info("unprivileged:\t%s", unprivileged ? "true" : "false"); 1040 info("verbose:\t%s", verbose ? "true" : "false"); 1041 } 1042 1043 typedef int (*main_t)(int, char **); 1044 1045 static struct { 1046 const char *name; 1047 main_t func; 1048 } commands[] = { 1049 { "list", certctl_list }, 1050 { "untrusted", certctl_untrusted }, 1051 { "rehash", certctl_rehash }, 1052 { "untrust", certctl_untrust }, 1053 { "trust", certctl_trust }, 1054 { 0 }, 1055 }; 1056 1057 static void 1058 usage(void) 1059 { 1060 fprintf(stderr, "usage: certctl [-lv] [-D destdir] [-d distbase] list\n" 1061 " certctl [-lv] [-D destdir] [-d distbase] untrusted\n" 1062 " certctl [-BnUv] [-D destdir] [-d distbase] [-M metalog] rehash\n" 1063 " certctl [-nv] [-D destdir] [-d distbase] untrust <file>\n" 1064 " certctl [-nv] [-D destdir] [-d distbase] trust <file>\n"); 1065 exit(1); 1066 } 1067 1068 int 1069 main(int argc, char *argv[]) 1070 { 1071 const char *command; 1072 int opt; 1073 1074 while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1) 1075 switch (opt) { 1076 case 'B': 1077 nobundle = true; 1078 break; 1079 case 'c': 1080 /* ignored for compatibility */ 1081 break; 1082 case 'D': 1083 destdir = optarg; 1084 break; 1085 case 'd': 1086 distbase = optarg; 1087 break; 1088 case 'g': 1089 gname = optarg; 1090 break; 1091 case 'l': 1092 longnames = true; 1093 break; 1094 case 'L': 1095 localbase = optarg; 1096 break; 1097 case 'M': 1098 metalog = optarg; 1099 break; 1100 case 'n': 1101 dryrun = true; 1102 break; 1103 case 'o': 1104 uname = optarg; 1105 break; 1106 case 'U': 1107 unprivileged = true; 1108 break; 1109 case 'v': 1110 verbose = true; 1111 break; 1112 default: 1113 usage(); 1114 } 1115 1116 argc -= optind; 1117 argv += optind; 1118 1119 if (argc < 1) 1120 usage(); 1121 1122 command = *argv; 1123 1124 if ((nobundle || unprivileged || metalog != NULL) && 1125 strcmp(command, "rehash") != 0) 1126 usage(); 1127 if (!unprivileged && metalog != NULL) { 1128 warnx("-M may only be used in conjunction with -U"); 1129 usage(); 1130 } 1131 1132 set_defaults(); 1133 1134 for (unsigned i = 0; commands[i].name != NULL; i++) 1135 if (strcmp(command, commands[i].name) == 0) 1136 exit(!!commands[i].func(argc, argv)); 1137 usage(); 1138 } 1139