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