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