main.c revision 1.55
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
36int		 verbose;
37enum comp 	 proccomp;
38
39int
40main(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);
381usage:
382	fprintf(stderr,
383	    "usage: acme-client [-Fnrv] [-f configfile] handle\n");
384	return EXIT_FAILURE;
385}
386