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