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