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 *
xasprintf(const char * fmt,...)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 *
xstrdup(const char * str)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
mkdirp(const char * dir)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 *
normalize_path(const char * str)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 **
split_paths(const char * str)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 *
expand_path(const char * template)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 **
expand_paths(const char * const * templates)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 *
unexpand_path(const char * path)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
free_cert(struct cert * cert)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
certcmp(const struct cert * a,const struct cert * b)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
free_certs(struct cert_tree * tree)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 *
find_cert(struct cert_tree * haystack,X509 * x509)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
filecmp(const struct file * a,const struct file * b)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
lexisort(const struct dirent ** d1,const struct dirent ** d2)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
read_cert(const char * path,struct cert_tree * tree,struct cert_tree * exclude)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
read_certs(const char * path,struct cert_tree * tree,struct cert_tree * exclude)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
write_certs(const char * dir,struct cert_tree * tree)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
write_bundle(const char * dir,const char * file,struct cert_tree * tree)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
load_trusted(bool all,struct cert_tree * exclude)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
load_untrusted(bool all)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
save_trusted(void)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
save_untrusted(void)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
save_bundle(void)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
save_all(void)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
list_certs(struct cert_tree * tree)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
certctl_list(int argc,char ** argv __unused)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
certctl_untrusted(int argc,char ** argv __unused)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
certctl_rehash(int argc,char ** argv __unused)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
certctl_trust(int argc,char ** argv)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
certctl_untrust(int argc,char ** argv)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
set_defaults(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
usage(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
main(int argc,char * argv[])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