xref: /openbsd-src/usr.sbin/acme-client/main.c (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1 /*	$Id: main.c,v 1.55 2022/05/05 19:51:35 florian Exp $ */
2 /*
3  * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/socket.h>
19 
20 #include <ctype.h>
21 #include <err.h>
22 #include <libgen.h>
23 #include <locale.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "extern.h"
31 #include "parse.h"
32 
33 #define WWW_DIR "/var/www/acme"
34 #define CONF_FILE "/etc/acme-client.conf"
35 
36 int		 verbose;
37 enum comp 	 proccomp;
38 
39 int
40 main(int argc, char *argv[])
41 {
42 	const char	 **alts = NULL;
43 	char		 *certdir = NULL;
44 	char		 *chngdir = NULL, *auth = NULL;
45 	char		 *conffile = CONF_FILE;
46 	char		 *tmps, *tmpsd;
47 	int		  key_fds[2], acct_fds[2], chng_fds[2], cert_fds[2];
48 	int		  file_fds[2], dns_fds[2], rvk_fds[2];
49 	int		  force = 0;
50 	int		  c, rc, revocate = 0;
51 	int		  popts = 0;
52 	pid_t		  pids[COMP__MAX];
53 	size_t		  i, altsz, ne;
54 
55 	struct acme_conf	*conf = NULL;
56 	struct authority_c	*authority = NULL;
57 	struct domain_c		*domain = NULL;
58 	struct altname_c	*ac;
59 
60 	if (setlocale(LC_CTYPE, "C") == NULL)
61 		errx(1, "setlocale");
62 
63 	while ((c = getopt(argc, argv, "Fnrvf:")) != -1)
64 		switch (c) {
65 		case 'F':
66 			force = 1;
67 			break;
68 		case 'f':
69 			if ((conffile = strdup(optarg)) == NULL)
70 				err(EXIT_FAILURE, "strdup");
71 			break;
72 		case 'n':
73 			popts |= ACME_OPT_CHECK;
74 			break;
75 		case 'r':
76 			revocate = 1;
77 			break;
78 		case 'v':
79 			verbose = verbose ? 2 : 1;
80 			popts |= ACME_OPT_VERBOSE;
81 			break;
82 		default:
83 			goto usage;
84 		}
85 
86 	if (getuid() != 0)
87 		errx(EXIT_FAILURE, "must be run as root");
88 
89 	/* parse config file */
90 	if ((conf = parse_config(conffile, popts)) == NULL)
91 		return EXIT_FAILURE;
92 
93 	argc -= optind;
94 	argv += optind;
95 	if (argc != 1)
96 		goto usage;
97 
98 	if ((domain = domain_find_handle(conf, argv[0])) == NULL)
99 		errx(EXIT_FAILURE, "domain %s not found", argv[0]);
100 
101 	argc--;
102 	argv++;
103 
104 	/*
105 	 * The parser enforces that at least cert or fullchain is set.
106 	 * XXX Test if cert, chain and fullchain have the same dirname?
107 	 */
108 	tmps = domain->cert ? domain->cert : domain->fullchain;
109 	if ((tmps = strdup(tmps)) == NULL)
110 		err(EXIT_FAILURE, "strdup");
111 	if ((tmpsd = dirname(tmps)) == NULL)
112 		err(EXIT_FAILURE, "dirname");
113 	if ((certdir = strdup(tmpsd)) == NULL)
114 		err(EXIT_FAILURE, "strdup");
115 	free(tmps);
116 	tmps = tmpsd = NULL;
117 
118 
119 	/* chain or fullchain can be relative paths according */
120 	if (domain->chain && domain->chain[0] != '/') {
121 		if (asprintf(&tmps, "%s/%s", certdir, domain->chain) == -1)
122 			err(EXIT_FAILURE, "asprintf");
123 		free(domain->chain);
124 		domain->chain = tmps;
125 		tmps = NULL;
126 	}
127 	if (domain->fullchain && domain->fullchain[0] != '/') {
128 		if (asprintf(&tmps, "%s/%s", certdir, domain->fullchain) == -1)
129 			err(EXIT_FAILURE, "asprintf");
130 		free(domain->fullchain);
131 		domain->fullchain = tmps;
132 		tmps = NULL;
133 	}
134 
135 	if ((auth = domain->auth) == NULL) {
136 		/* use the first authority from the config as default XXX */
137 		authority = authority_find0(conf);
138 		if (authority == NULL)
139 			errx(EXIT_FAILURE, "no authorities configured");
140 	} else {
141 		authority = authority_find(conf, auth);
142 		if (authority == NULL)
143 			errx(EXIT_FAILURE, "authority %s not found", auth);
144 	}
145 
146 	if ((chngdir = domain->challengedir) == NULL)
147 		if ((chngdir = strdup(WWW_DIR)) == NULL)
148 			err(EXIT_FAILURE, "strdup");
149 
150 	/*
151 	 * Do some quick checks to see if our paths exist.
152 	 * This will be done in the children, but we might as well check
153 	 * now before the fork.
154 	 * XXX maybe use conf_check_file() from parse.y
155 	 */
156 
157 	ne = 0;
158 
159 	if (access(certdir, R_OK) == -1) {
160 		warnx("%s: cert directory must exist", certdir);
161 		ne++;
162 	}
163 
164 	if (access(chngdir, R_OK) == -1) {
165 		warnx("%s: challenge directory must exist", chngdir);
166 		ne++;
167 	}
168 
169 	if (ne > 0)
170 		return EXIT_FAILURE;
171 
172 	if (popts & ACME_OPT_CHECK)
173 		return EXIT_SUCCESS;
174 
175 	/* Set the zeroth altname as our domain. */
176 	altsz = domain->altname_count + 1;
177 	alts = calloc(altsz, sizeof(char *));
178 	if (alts == NULL)
179 		err(EXIT_FAILURE, "calloc");
180 	alts[0] = domain->domain;
181 	i = 1;
182 	/* XXX get rid of alts[] later */
183 	TAILQ_FOREACH(ac, &domain->altname_list, entry)
184 		alts[i++] = ac->domain;
185 
186 	/*
187 	 * Open channels between our components.
188 	 */
189 
190 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, key_fds) == -1)
191 		err(EXIT_FAILURE, "socketpair");
192 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, acct_fds) == -1)
193 		err(EXIT_FAILURE, "socketpair");
194 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, chng_fds) == -1)
195 		err(EXIT_FAILURE, "socketpair");
196 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, cert_fds) == -1)
197 		err(EXIT_FAILURE, "socketpair");
198 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, file_fds) == -1)
199 		err(EXIT_FAILURE, "socketpair");
200 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, dns_fds) == -1)
201 		err(EXIT_FAILURE, "socketpair");
202 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, rvk_fds) == -1)
203 		err(EXIT_FAILURE, "socketpair");
204 
205 	/* Start with the network-touching process. */
206 
207 	if ((pids[COMP_NET] = fork()) == -1)
208 		err(EXIT_FAILURE, "fork");
209 
210 	if (pids[COMP_NET] == 0) {
211 		proccomp = COMP_NET;
212 		close(key_fds[0]);
213 		close(acct_fds[0]);
214 		close(chng_fds[0]);
215 		close(cert_fds[0]);
216 		close(file_fds[0]);
217 		close(file_fds[1]);
218 		close(dns_fds[0]);
219 		close(rvk_fds[0]);
220 		c = netproc(key_fds[1], acct_fds[1],
221 		    chng_fds[1], cert_fds[1],
222 		    dns_fds[1], rvk_fds[1],
223 		    revocate, authority,
224 		    (const char *const *)alts, altsz);
225 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
226 	}
227 
228 	close(key_fds[1]);
229 	close(acct_fds[1]);
230 	close(chng_fds[1]);
231 	close(cert_fds[1]);
232 	close(dns_fds[1]);
233 	close(rvk_fds[1]);
234 
235 	/* Now the key-touching component. */
236 
237 	if ((pids[COMP_KEY] = fork()) == -1)
238 		err(EXIT_FAILURE, "fork");
239 
240 	if (pids[COMP_KEY] == 0) {
241 		proccomp = COMP_KEY;
242 		close(cert_fds[0]);
243 		close(dns_fds[0]);
244 		close(rvk_fds[0]);
245 		close(acct_fds[0]);
246 		close(chng_fds[0]);
247 		close(file_fds[0]);
248 		close(file_fds[1]);
249 		c = keyproc(key_fds[0], domain->key,
250 		    (const char **)alts, altsz,
251 		    domain->keytype);
252 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
253 	}
254 
255 	close(key_fds[0]);
256 
257 	/* The account-touching component. */
258 
259 	if ((pids[COMP_ACCOUNT] = fork()) == -1)
260 		err(EXIT_FAILURE, "fork");
261 
262 	if (pids[COMP_ACCOUNT] == 0) {
263 		proccomp = COMP_ACCOUNT;
264 		close(cert_fds[0]);
265 		close(dns_fds[0]);
266 		close(rvk_fds[0]);
267 		close(chng_fds[0]);
268 		close(file_fds[0]);
269 		close(file_fds[1]);
270 		c = acctproc(acct_fds[0], authority->account,
271 		    authority->keytype);
272 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
273 	}
274 
275 	close(acct_fds[0]);
276 
277 	/* The challenge-accepting component. */
278 
279 	if ((pids[COMP_CHALLENGE] = fork()) == -1)
280 		err(EXIT_FAILURE, "fork");
281 
282 	if (pids[COMP_CHALLENGE] == 0) {
283 		proccomp = COMP_CHALLENGE;
284 		close(cert_fds[0]);
285 		close(dns_fds[0]);
286 		close(rvk_fds[0]);
287 		close(file_fds[0]);
288 		close(file_fds[1]);
289 		c = chngproc(chng_fds[0], chngdir);
290 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
291 	}
292 
293 	close(chng_fds[0]);
294 
295 	/* The certificate-handling component. */
296 
297 	if ((pids[COMP_CERT] = fork()) == -1)
298 		err(EXIT_FAILURE, "fork");
299 
300 	if (pids[COMP_CERT] == 0) {
301 		proccomp = COMP_CERT;
302 		close(dns_fds[0]);
303 		close(rvk_fds[0]);
304 		close(file_fds[1]);
305 		c = certproc(cert_fds[0], file_fds[0]);
306 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
307 	}
308 
309 	close(cert_fds[0]);
310 	close(file_fds[0]);
311 
312 	/* The certificate-handling component. */
313 
314 	if ((pids[COMP_FILE] = fork()) == -1)
315 		err(EXIT_FAILURE, "fork");
316 
317 	if (pids[COMP_FILE] == 0) {
318 		proccomp = COMP_FILE;
319 		close(dns_fds[0]);
320 		close(rvk_fds[0]);
321 		c = fileproc(file_fds[1], certdir, domain->cert, domain->chain,
322 		    domain->fullchain);
323 		/*
324 		 * This is different from the other processes in that it
325 		 * can return 2 if the certificates were updated.
326 		 */
327 		exit(c > 1 ? 2 : (c ? EXIT_SUCCESS : EXIT_FAILURE));
328 	}
329 
330 	close(file_fds[1]);
331 
332 	/* The DNS lookup component. */
333 
334 	if ((pids[COMP_DNS] = fork()) == -1)
335 		err(EXIT_FAILURE, "fork");
336 
337 	if (pids[COMP_DNS] == 0) {
338 		proccomp = COMP_DNS;
339 		close(rvk_fds[0]);
340 		c = dnsproc(dns_fds[0]);
341 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
342 	}
343 
344 	close(dns_fds[0]);
345 
346 	/* The expiration component. */
347 
348 	if ((pids[COMP_REVOKE] = fork()) == -1)
349 		err(EXIT_FAILURE, "fork");
350 
351 	if (pids[COMP_REVOKE] == 0) {
352 		proccomp = COMP_REVOKE;
353 		c = revokeproc(rvk_fds[0], domain->cert != NULL ? domain->cert :
354 		    domain->fullchain, force, revocate,
355 		    (const char *const *)alts, altsz);
356 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
357 	}
358 
359 	close(rvk_fds[0]);
360 
361 	/* Jail: sandbox, file-system, user. */
362 
363 	if (pledge("stdio", NULL) == -1)
364 		err(EXIT_FAILURE, "pledge");
365 
366 	/*
367 	 * Collect our subprocesses.
368 	 * Require that they both have exited cleanly.
369 	 */
370 
371 	rc = checkexit(pids[COMP_KEY], COMP_KEY) +
372 	    checkexit(pids[COMP_CERT], COMP_CERT) +
373 	    checkexit(pids[COMP_NET], COMP_NET) +
374 	    checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) +
375 	    checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) +
376 	    checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) +
377 	    checkexit(pids[COMP_DNS], COMP_DNS) +
378 	    checkexit(pids[COMP_REVOKE], COMP_REVOKE);
379 
380 	return rc != COMP__MAX ? EXIT_FAILURE : (c == 2 ? EXIT_SUCCESS : 2);
381 usage:
382 	fprintf(stderr,
383 	    "usage: acme-client [-Fnrv] [-f configfile] handle\n");
384 	return EXIT_FAILURE;
385 }
386