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