xref: /openbsd-src/usr.sbin/acme-client/main.c (revision d7c05bd5f83de097d211cbdbff6f9049fbd68185)
1*d7c05bd5Sclaudio /*	$Id: main.c,v 1.56 2024/06/19 13:13:25 claudio Exp $ */
2de579d12Sflorian /*
3de579d12Sflorian  * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4de579d12Sflorian  *
5de579d12Sflorian  * Permission to use, copy, modify, and distribute this software for any
6de579d12Sflorian  * purpose with or without fee is hereby granted, provided that the above
7de579d12Sflorian  * copyright notice and this permission notice appear in all copies.
8de579d12Sflorian  *
9de579d12Sflorian  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10de579d12Sflorian  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11de579d12Sflorian  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12de579d12Sflorian  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13de579d12Sflorian  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14de579d12Sflorian  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15de579d12Sflorian  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16de579d12Sflorian  */
17de579d12Sflorian 
18de579d12Sflorian #include <sys/socket.h>
19de579d12Sflorian 
20de579d12Sflorian #include <ctype.h>
21de579d12Sflorian #include <err.h>
2262492c74Sflorian #include <libgen.h>
235b337cd6Sflorian #include <locale.h>
24*d7c05bd5Sclaudio #include <signal.h>
25de579d12Sflorian #include <stdarg.h>
26de579d12Sflorian #include <stdio.h>
27de579d12Sflorian #include <stdlib.h>
28de579d12Sflorian #include <string.h>
29de579d12Sflorian #include <unistd.h>
30de579d12Sflorian 
31de579d12Sflorian #include "extern.h"
323943d840Sbenno #include "parse.h"
33de579d12Sflorian 
34a44942e7Sflorian #define WWW_DIR "/var/www/acme"
35383e31e9Sbenno #define CONF_FILE "/etc/acme-client.conf"
36de579d12Sflorian 
373a92740dSflorian int		 verbose;
383a92740dSflorian enum comp 	 proccomp;
393a92740dSflorian 
40de579d12Sflorian int
main(int argc,char * argv[])41de579d12Sflorian main(int argc, char *argv[])
42de579d12Sflorian {
43383e31e9Sbenno 	const char	 **alts = NULL;
4461075b4cSflorian 	char		 *certdir = NULL;
450b28b247Sflorian 	char		 *chngdir = NULL, *auth = NULL;
46383e31e9Sbenno 	char		 *conffile = CONF_FILE;
4747c124d1Sbenno 	char		 *tmps, *tmpsd;
487bce6888Sderaadt 	int		  key_fds[2], acct_fds[2], chng_fds[2], cert_fds[2];
497bce6888Sderaadt 	int		  file_fds[2], dns_fds[2], rvk_fds[2];
50383e31e9Sbenno 	int		  force = 0;
517bce6888Sderaadt 	int		  c, rc, revocate = 0;
52383e31e9Sbenno 	int		  popts = 0;
53de579d12Sflorian 	pid_t		  pids[COMP__MAX];
54de579d12Sflorian 	size_t		  i, altsz, ne;
55de579d12Sflorian 
56383e31e9Sbenno 	struct acme_conf	*conf = NULL;
57383e31e9Sbenno 	struct authority_c	*authority = NULL;
58383e31e9Sbenno 	struct domain_c		*domain = NULL;
59383e31e9Sbenno 	struct altname_c	*ac;
60383e31e9Sbenno 
615b337cd6Sflorian 	if (setlocale(LC_CTYPE, "C") == NULL)
625b337cd6Sflorian 		errx(1, "setlocale");
635b337cd6Sflorian 
642570ecd0Sflorian 	while ((c = getopt(argc, argv, "Fnrvf:")) != -1)
65de579d12Sflorian 		switch (c) {
6638a8ff7fSbenno 		case 'F':
6738a8ff7fSbenno 			force = 1;
6838a8ff7fSbenno 			break;
6938a8ff7fSbenno 		case 'f':
7038a8ff7fSbenno 			if ((conffile = strdup(optarg)) == NULL)
7138a8ff7fSbenno 				err(EXIT_FAILURE, "strdup");
7238a8ff7fSbenno 			break;
7338a8ff7fSbenno 		case 'n':
7438a8ff7fSbenno 			popts |= ACME_OPT_CHECK;
7538a8ff7fSbenno 			break;
76a8699559Sderaadt 		case 'r':
77ee27a5e1Sderaadt 			revocate = 1;
78de579d12Sflorian 			break;
79a8699559Sderaadt 		case 'v':
80de579d12Sflorian 			verbose = verbose ? 2 : 1;
81383e31e9Sbenno 			popts |= ACME_OPT_VERBOSE;
82de579d12Sflorian 			break;
83de579d12Sflorian 		default:
84de579d12Sflorian 			goto usage;
85de579d12Sflorian 		}
86de579d12Sflorian 
87d27a8d23Sflorian 	if (getuid() != 0)
88d27a8d23Sflorian 		errx(EXIT_FAILURE, "must be run as root");
89d27a8d23Sflorian 
90383e31e9Sbenno 	/* parse config file */
91383e31e9Sbenno 	if ((conf = parse_config(conffile, popts)) == NULL)
92a7313564Sbenno 		return EXIT_FAILURE;
93a8bb3d0cSflorian 
94de579d12Sflorian 	argc -= optind;
95de579d12Sflorian 	argv += optind;
967cd8f039Sjsing 	if (argc != 1)
97de579d12Sflorian 		goto usage;
98de579d12Sflorian 
9987f5451dSbenno 	if ((domain = domain_find_handle(conf, argv[0])) == NULL)
10069180610Sderaadt 		errx(EXIT_FAILURE, "domain %s not found", argv[0]);
101383e31e9Sbenno 
102de579d12Sflorian 	argc--;
103de579d12Sflorian 	argv++;
104de579d12Sflorian 
10561075b4cSflorian 	/*
10661075b4cSflorian 	 * The parser enforces that at least cert or fullchain is set.
10761075b4cSflorian 	 * XXX Test if cert, chain and fullchain have the same dirname?
10861075b4cSflorian 	 */
10947c124d1Sbenno 	tmps = domain->cert ? domain->cert : domain->fullchain;
11047c124d1Sbenno 	if ((tmps = strdup(tmps)) == NULL)
111d40ebfdcSflorian 		err(EXIT_FAILURE, "strdup");
11247c124d1Sbenno 	if ((tmpsd = dirname(tmps)) == NULL)
113d40ebfdcSflorian 		err(EXIT_FAILURE, "dirname");
11447c124d1Sbenno 	if ((certdir = strdup(tmpsd)) == NULL)
11547c124d1Sbenno 		err(EXIT_FAILURE, "strdup");
11647c124d1Sbenno 	free(tmps);
117571e53b1Sbenno 	tmps = tmpsd = NULL;
118d40ebfdcSflorian 
119383e31e9Sbenno 
12061075b4cSflorian 	/* chain or fullchain can be relative paths according */
12161075b4cSflorian 	if (domain->chain && domain->chain[0] != '/') {
12261075b4cSflorian 		if (asprintf(&tmps, "%s/%s", certdir, domain->chain) == -1)
12361075b4cSflorian 			err(EXIT_FAILURE, "asprintf");
12461075b4cSflorian 		free(domain->chain);
12561075b4cSflorian 		domain->chain = tmps;
12661075b4cSflorian 		tmps = NULL;
12733febeb9Sflorian 	}
12861075b4cSflorian 	if (domain->fullchain && domain->fullchain[0] != '/') {
12961075b4cSflorian 		if (asprintf(&tmps, "%s/%s", certdir, domain->fullchain) == -1)
13061075b4cSflorian 			err(EXIT_FAILURE, "asprintf");
13161075b4cSflorian 		free(domain->fullchain);
13261075b4cSflorian 		domain->fullchain = tmps;
13361075b4cSflorian 		tmps = NULL;
13470bcb874Sbenno 	}
13570bcb874Sbenno 
136383e31e9Sbenno 	if ((auth = domain->auth) == NULL) {
137383e31e9Sbenno 		/* use the first authority from the config as default XXX */
138383e31e9Sbenno 		authority = authority_find0(conf);
139383e31e9Sbenno 		if (authority == NULL)
14069180610Sderaadt 			errx(EXIT_FAILURE, "no authorities configured");
141383e31e9Sbenno 	} else {
142383e31e9Sbenno 		authority = authority_find(conf, auth);
143383e31e9Sbenno 		if (authority == NULL)
14469180610Sderaadt 			errx(EXIT_FAILURE, "authority %s not found", auth);
145383e31e9Sbenno 	}
146383e31e9Sbenno 
14737731077Sbenno 	if ((chngdir = domain->challengedir) == NULL)
14837731077Sbenno 		if ((chngdir = strdup(WWW_DIR)) == NULL)
149de579d12Sflorian 			err(EXIT_FAILURE, "strdup");
150de579d12Sflorian 
151de579d12Sflorian 	/*
152de579d12Sflorian 	 * Do some quick checks to see if our paths exist.
153de579d12Sflorian 	 * This will be done in the children, but we might as well check
154de579d12Sflorian 	 * now before the fork.
155383e31e9Sbenno 	 * XXX maybe use conf_check_file() from parse.y
156de579d12Sflorian 	 */
157de579d12Sflorian 
158de579d12Sflorian 	ne = 0;
159de579d12Sflorian 
1607cd8f039Sjsing 	if (access(certdir, R_OK) == -1) {
161383e31e9Sbenno 		warnx("%s: cert directory must exist", certdir);
162de579d12Sflorian 		ne++;
163de579d12Sflorian 	}
164de579d12Sflorian 
1657cd8f039Sjsing 	if (access(chngdir, R_OK) == -1) {
1666c0ff37dSbenno 		warnx("%s: challenge directory must exist", chngdir);
167de579d12Sflorian 		ne++;
168de579d12Sflorian 	}
169de579d12Sflorian 
170de579d12Sflorian 	if (ne > 0)
171a7313564Sbenno 		return EXIT_FAILURE;
172de579d12Sflorian 
1733298b855Sbenno 	if (popts & ACME_OPT_CHECK)
174a7313564Sbenno 		return EXIT_SUCCESS;
1753298b855Sbenno 
176de579d12Sflorian 	/* Set the zeroth altname as our domain. */
177383e31e9Sbenno 	altsz = domain->altname_count + 1;
178de579d12Sflorian 	alts = calloc(altsz, sizeof(char *));
1797cd8f039Sjsing 	if (alts == NULL)
180de579d12Sflorian 		err(EXIT_FAILURE, "calloc");
181383e31e9Sbenno 	alts[0] = domain->domain;
182383e31e9Sbenno 	i = 1;
183383e31e9Sbenno 	/* XXX get rid of alts[] later */
184221ac2aaSbenno 	TAILQ_FOREACH(ac, &domain->altname_list, entry)
185383e31e9Sbenno 		alts[i++] = ac->domain;
186de579d12Sflorian 
187de579d12Sflorian 	/*
188de579d12Sflorian 	 * Open channels between our components.
189de579d12Sflorian 	 */
190de579d12Sflorian 
1917cd8f039Sjsing 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, key_fds) == -1)
192de579d12Sflorian 		err(EXIT_FAILURE, "socketpair");
1937cd8f039Sjsing 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, acct_fds) == -1)
194de579d12Sflorian 		err(EXIT_FAILURE, "socketpair");
1957cd8f039Sjsing 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, chng_fds) == -1)
196de579d12Sflorian 		err(EXIT_FAILURE, "socketpair");
1977cd8f039Sjsing 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, cert_fds) == -1)
198de579d12Sflorian 		err(EXIT_FAILURE, "socketpair");
1997cd8f039Sjsing 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, file_fds) == -1)
200de579d12Sflorian 		err(EXIT_FAILURE, "socketpair");
2017cd8f039Sjsing 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, dns_fds) == -1)
202de579d12Sflorian 		err(EXIT_FAILURE, "socketpair");
2037cd8f039Sjsing 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, rvk_fds) == -1)
204de579d12Sflorian 		err(EXIT_FAILURE, "socketpair");
205de579d12Sflorian 
206*d7c05bd5Sclaudio 	signal(SIGPIPE, SIG_IGN);
207*d7c05bd5Sclaudio 
208de579d12Sflorian 	/* Start with the network-touching process. */
209de579d12Sflorian 
2107cd8f039Sjsing 	if ((pids[COMP_NET] = fork()) == -1)
211de579d12Sflorian 		err(EXIT_FAILURE, "fork");
212de579d12Sflorian 
2137cd8f039Sjsing 	if (pids[COMP_NET] == 0) {
214de579d12Sflorian 		proccomp = COMP_NET;
215de579d12Sflorian 		close(key_fds[0]);
216de579d12Sflorian 		close(acct_fds[0]);
217de579d12Sflorian 		close(chng_fds[0]);
218de579d12Sflorian 		close(cert_fds[0]);
219de579d12Sflorian 		close(file_fds[0]);
220de579d12Sflorian 		close(file_fds[1]);
221de579d12Sflorian 		close(dns_fds[0]);
222de579d12Sflorian 		close(rvk_fds[0]);
223de579d12Sflorian 		c = netproc(key_fds[1], acct_fds[1],
224de579d12Sflorian 		    chng_fds[1], cert_fds[1],
225de579d12Sflorian 		    dns_fds[1], rvk_fds[1],
2267b00f4e9Sflorian 		    revocate, authority,
2270b28b247Sflorian 		    (const char *const *)alts, altsz);
228de579d12Sflorian 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
229de579d12Sflorian 	}
230de579d12Sflorian 
231de579d12Sflorian 	close(key_fds[1]);
232de579d12Sflorian 	close(acct_fds[1]);
233de579d12Sflorian 	close(chng_fds[1]);
234de579d12Sflorian 	close(cert_fds[1]);
235de579d12Sflorian 	close(dns_fds[1]);
236de579d12Sflorian 	close(rvk_fds[1]);
237de579d12Sflorian 
238de579d12Sflorian 	/* Now the key-touching component. */
239de579d12Sflorian 
2407cd8f039Sjsing 	if ((pids[COMP_KEY] = fork()) == -1)
241de579d12Sflorian 		err(EXIT_FAILURE, "fork");
242de579d12Sflorian 
2437cd8f039Sjsing 	if (pids[COMP_KEY] == 0) {
244de579d12Sflorian 		proccomp = COMP_KEY;
245de579d12Sflorian 		close(cert_fds[0]);
246de579d12Sflorian 		close(dns_fds[0]);
247de579d12Sflorian 		close(rvk_fds[0]);
248de579d12Sflorian 		close(acct_fds[0]);
249de579d12Sflorian 		close(chng_fds[0]);
250de579d12Sflorian 		close(file_fds[0]);
251de579d12Sflorian 		close(file_fds[1]);
252383e31e9Sbenno 		c = keyproc(key_fds[0], domain->key,
25365a104faSflorian 		    (const char **)alts, altsz,
25465a104faSflorian 		    domain->keytype);
255de579d12Sflorian 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
256de579d12Sflorian 	}
257de579d12Sflorian 
258de579d12Sflorian 	close(key_fds[0]);
259de579d12Sflorian 
260de579d12Sflorian 	/* The account-touching component. */
261de579d12Sflorian 
2627cd8f039Sjsing 	if ((pids[COMP_ACCOUNT] = fork()) == -1)
263de579d12Sflorian 		err(EXIT_FAILURE, "fork");
264de579d12Sflorian 
2657cd8f039Sjsing 	if (pids[COMP_ACCOUNT] == 0) {
266de579d12Sflorian 		proccomp = COMP_ACCOUNT;
267de579d12Sflorian 		close(cert_fds[0]);
268de579d12Sflorian 		close(dns_fds[0]);
269de579d12Sflorian 		close(rvk_fds[0]);
270de579d12Sflorian 		close(chng_fds[0]);
271de579d12Sflorian 		close(file_fds[0]);
272de579d12Sflorian 		close(file_fds[1]);
2734f8b772fSflorian 		c = acctproc(acct_fds[0], authority->account,
2744f8b772fSflorian 		    authority->keytype);
275de579d12Sflorian 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
276de579d12Sflorian 	}
277de579d12Sflorian 
278de579d12Sflorian 	close(acct_fds[0]);
279de579d12Sflorian 
280de579d12Sflorian 	/* The challenge-accepting component. */
281de579d12Sflorian 
2827cd8f039Sjsing 	if ((pids[COMP_CHALLENGE] = fork()) == -1)
283de579d12Sflorian 		err(EXIT_FAILURE, "fork");
284de579d12Sflorian 
2857cd8f039Sjsing 	if (pids[COMP_CHALLENGE] == 0) {
286de579d12Sflorian 		proccomp = COMP_CHALLENGE;
287de579d12Sflorian 		close(cert_fds[0]);
288de579d12Sflorian 		close(dns_fds[0]);
289de579d12Sflorian 		close(rvk_fds[0]);
290de579d12Sflorian 		close(file_fds[0]);
291de579d12Sflorian 		close(file_fds[1]);
292383e31e9Sbenno 		c = chngproc(chng_fds[0], chngdir);
293de579d12Sflorian 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
294de579d12Sflorian 	}
295de579d12Sflorian 
296de579d12Sflorian 	close(chng_fds[0]);
297de579d12Sflorian 
298de579d12Sflorian 	/* The certificate-handling component. */
299de579d12Sflorian 
3007cd8f039Sjsing 	if ((pids[COMP_CERT] = fork()) == -1)
301de579d12Sflorian 		err(EXIT_FAILURE, "fork");
302de579d12Sflorian 
3037cd8f039Sjsing 	if (pids[COMP_CERT] == 0) {
304de579d12Sflorian 		proccomp = COMP_CERT;
305de579d12Sflorian 		close(dns_fds[0]);
306de579d12Sflorian 		close(rvk_fds[0]);
307de579d12Sflorian 		close(file_fds[1]);
308de579d12Sflorian 		c = certproc(cert_fds[0], file_fds[0]);
309de579d12Sflorian 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
310de579d12Sflorian 	}
311de579d12Sflorian 
312de579d12Sflorian 	close(cert_fds[0]);
313de579d12Sflorian 	close(file_fds[0]);
314de579d12Sflorian 
315de579d12Sflorian 	/* The certificate-handling component. */
316de579d12Sflorian 
3177cd8f039Sjsing 	if ((pids[COMP_FILE] = fork()) == -1)
318de579d12Sflorian 		err(EXIT_FAILURE, "fork");
319de579d12Sflorian 
3207cd8f039Sjsing 	if (pids[COMP_FILE] == 0) {
321de579d12Sflorian 		proccomp = COMP_FILE;
322de579d12Sflorian 		close(dns_fds[0]);
323de579d12Sflorian 		close(rvk_fds[0]);
32461075b4cSflorian 		c = fileproc(file_fds[1], certdir, domain->cert, domain->chain,
32561075b4cSflorian 		    domain->fullchain);
326de579d12Sflorian 		/*
327de579d12Sflorian 		 * This is different from the other processes in that it
328de579d12Sflorian 		 * can return 2 if the certificates were updated.
329de579d12Sflorian 		 */
33055599e87Sderaadt 		exit(c > 1 ? 2 : (c ? EXIT_SUCCESS : EXIT_FAILURE));
331de579d12Sflorian 	}
332de579d12Sflorian 
333de579d12Sflorian 	close(file_fds[1]);
334de579d12Sflorian 
335de579d12Sflorian 	/* The DNS lookup component. */
336de579d12Sflorian 
3377cd8f039Sjsing 	if ((pids[COMP_DNS] = fork()) == -1)
338de579d12Sflorian 		err(EXIT_FAILURE, "fork");
339de579d12Sflorian 
3407cd8f039Sjsing 	if (pids[COMP_DNS] == 0) {
341de579d12Sflorian 		proccomp = COMP_DNS;
342de579d12Sflorian 		close(rvk_fds[0]);
343de579d12Sflorian 		c = dnsproc(dns_fds[0]);
344de579d12Sflorian 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
345de579d12Sflorian 	}
346de579d12Sflorian 
347de579d12Sflorian 	close(dns_fds[0]);
348de579d12Sflorian 
349de579d12Sflorian 	/* The expiration component. */
350de579d12Sflorian 
3517cd8f039Sjsing 	if ((pids[COMP_REVOKE] = fork()) == -1)
352de579d12Sflorian 		err(EXIT_FAILURE, "fork");
353de579d12Sflorian 
3547cd8f039Sjsing 	if (pids[COMP_REVOKE] == 0) {
355de579d12Sflorian 		proccomp = COMP_REVOKE;
35661075b4cSflorian 		c = revokeproc(rvk_fds[0], domain->cert != NULL ? domain->cert :
35761075b4cSflorian 		    domain->fullchain, force, revocate,
358de579d12Sflorian 		    (const char *const *)alts, altsz);
359de579d12Sflorian 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
360de579d12Sflorian 	}
361de579d12Sflorian 
362de579d12Sflorian 	close(rvk_fds[0]);
363de579d12Sflorian 
364de579d12Sflorian 	/* Jail: sandbox, file-system, user. */
365de579d12Sflorian 
36630f35414Sbenno 	if (pledge("stdio", NULL) == -1)
36730f35414Sbenno 		err(EXIT_FAILURE, "pledge");
368de579d12Sflorian 
369de579d12Sflorian 	/*
370de579d12Sflorian 	 * Collect our subprocesses.
371de579d12Sflorian 	 * Require that they both have exited cleanly.
372de579d12Sflorian 	 */
373de579d12Sflorian 
374de579d12Sflorian 	rc = checkexit(pids[COMP_KEY], COMP_KEY) +
375de579d12Sflorian 	    checkexit(pids[COMP_CERT], COMP_CERT) +
376de579d12Sflorian 	    checkexit(pids[COMP_NET], COMP_NET) +
377de579d12Sflorian 	    checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) +
378de579d12Sflorian 	    checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) +
379de579d12Sflorian 	    checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) +
380de579d12Sflorian 	    checkexit(pids[COMP_DNS], COMP_DNS) +
381de579d12Sflorian 	    checkexit(pids[COMP_REVOKE], COMP_REVOKE);
382de579d12Sflorian 
38334335c11Sjsing 	return rc != COMP__MAX ? EXIT_FAILURE : (c == 2 ? EXIT_SUCCESS : 2);
384de579d12Sflorian usage:
385bbca2207Sderaadt 	fprintf(stderr,
38687f5451dSbenno 	    "usage: acme-client [-Fnrv] [-f configfile] handle\n");
38734335c11Sjsing 	return EXIT_FAILURE;
388de579d12Sflorian }
389