xref: /freebsd/usr.sbin/certctl/certctl.c (revision 03221b189a48a509c1bc9adb8331638ae3eac065)
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