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