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