xref: /openbsd-src/usr.sbin/acme-client/main.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$Id: main.c,v 1.14 2016/09/18 20:18:25 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 <stdarg.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #include "extern.h"
29 #include "parse.h"
30 
31 #define SSL_DIR "/etc/ssl/acme"
32 #define SSL_PRIV_DIR "/etc/ssl/acme/private"
33 #define ETC_DIR "/etc/acme"
34 #define WWW_DIR "/var/www/acme"
35 #define PRIVKEY_FILE "privkey.pem"
36 
37 struct authority authorities[] = {
38 #define	DEFAULT_AUTHORITY 0
39 	{"letsencrypt",
40 	    "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
41 	    "https://acme-v01.api.letsencrypt.org/directory"},
42 	{"letsencrypt-staging",
43 	    "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
44 	    "https://acme-staging.api.letsencrypt.org/directory"},
45 };
46 
47 /*
48  * Wrap around asprintf(3), which sometimes nullifies the input values,
49  * sometimes not, but always returns <0 on error.
50  * Returns NULL on failure or the pointer on success.
51  */
52 static char *
53 doasprintf(const char *fmt, ...)
54 {
55 	int	 c;
56 	char	*cp;
57 	va_list	 ap;
58 
59 	va_start(ap, fmt);
60 	c = vasprintf(&cp, fmt, ap);
61 	va_end(ap);
62 	return (c < 0 ? NULL : cp);
63 }
64 
65 int
66 main(int argc, char *argv[])
67 {
68 	const char	 *domain, *agreement = NULL, **alts = NULL;
69 	char		 *certdir = NULL, *acctkey = NULL, *chngdir = NULL;
70 	char		 *keyfile = NULL;
71 	int		  key_fds[2], acct_fds[2], chng_fds[2], cert_fds[2];
72 	int		  file_fds[2], dns_fds[2], rvk_fds[2];
73 	int		  newacct = 0, remote = 0, backup = 0;
74 	int		  force = 0, multidir = 0, newkey = 0;
75 	int		  c, rc, revocate = 0;
76 	int		  authority = DEFAULT_AUTHORITY;
77 	pid_t		  pids[COMP__MAX];
78 	extern int	  verbose;
79 	extern enum comp  proccomp;
80 	size_t		  i, altsz, ne;
81 
82 	while (-1 != (c = getopt(argc, argv, "bFmnNrs:tva:f:c:C:k:")))
83 		switch (c) {
84 		case 'a':
85 			agreement = optarg;
86 			break;
87 		case 'b':
88 			backup = 1;
89 			break;
90 		case 'c':
91 			free(certdir);
92 			if (NULL == (certdir = strdup(optarg)))
93 				err(EXIT_FAILURE, "strdup");
94 			break;
95 		case 'C':
96 			free(chngdir);
97 			if (NULL == (chngdir = strdup(optarg)))
98 				err(EXIT_FAILURE, "strdup");
99 			break;
100 		case 'f':
101 			free(acctkey);
102 			if (NULL == (acctkey = strdup(optarg)))
103 				err(EXIT_FAILURE, "strdup");
104 			break;
105 		case 'F':
106 			force = 1;
107 			break;
108 		case 'k':
109 			free(keyfile);
110 			if (NULL == (keyfile = strdup(optarg)))
111 				err(EXIT_FAILURE, "strdup");
112 			break;
113 		case 'm':
114 			multidir = 1;
115 			break;
116 		case 'n':
117 			newacct = 1;
118 			break;
119 		case 'N':
120 			newkey = 1;
121 			break;
122 		case 'r':
123 			revocate = 1;
124 			break;
125 		case 's':
126 			authority = -1;
127 			for (i = 0; i < nitems(authorities); i++) {
128 				if (strcmp(authorities[i].name, optarg) == 0) {
129 					authority = i;
130 					break;
131 				}
132 			}
133 			if (-1 == authority)
134 				errx(EXIT_FAILURE, "unknown acme authority");
135 			break;
136 		case 't':
137 			/*
138 			 / Undocumented feature.
139 			 * Don't use it.
140 			 */
141 			remote = 1;
142 			break;
143 		case 'v':
144 			verbose = verbose ? 2 : 1;
145 			break;
146 		default:
147 			goto usage;
148 		}
149 
150 	if (NULL == agreement)
151 		agreement = authorities[authority].agreement;
152 
153 	argc -= optind;
154 	argv += optind;
155 	if (0 == argc)
156 		goto usage;
157 
158 	/* Make sure that the domains are sane. */
159 
160 	for (i = 0; i < (size_t)argc; i++) {
161 		if (domain_valid(argv[i]))
162 			continue;
163 		errx(EXIT_FAILURE, "%s: bad domain syntax", argv[i]);
164 	}
165 
166 	domain = argv[0];
167 	argc--;
168 	argv++;
169 
170 	if (getuid() != 0)
171 		errx(EXIT_FAILURE, "must be run as root");
172 
173 	/*
174 	 * Now we allocate our directories and file paths IFF we haven't
175 	 * specified them on the command-line.
176 	 * If we're in "multidir" (-m) mode, we use our initial domain
177 	 * name when specifying the prefixes.
178 	 * Otherwise, we put them all in a known location.
179 	 */
180 
181 	if (NULL == certdir)
182 		certdir = multidir ?
183 			doasprintf(SSL_DIR "/%s", domain) :
184 			strdup(SSL_DIR);
185 	if (NULL == keyfile)
186 		keyfile = multidir ?
187 			doasprintf(SSL_PRIV_DIR "/%s/"
188 				PRIVKEY_FILE, domain) :
189 			strdup(SSL_PRIV_DIR "/" PRIVKEY_FILE);
190 	if (NULL == acctkey)
191 		acctkey = multidir ?
192 			doasprintf(ETC_DIR "/%s/"
193 				PRIVKEY_FILE, domain) :
194 			strdup(ETC_DIR "/" PRIVKEY_FILE);
195 	if (NULL == chngdir)
196 		chngdir = strdup(WWW_DIR);
197 
198 	if (NULL == certdir || NULL == keyfile ||
199 	    NULL == acctkey || NULL == chngdir)
200 		err(EXIT_FAILURE, "strdup");
201 
202 	/*
203 	 * Do some quick checks to see if our paths exist.
204 	 * This will be done in the children, but we might as well check
205 	 * now before the fork.
206 	 */
207 
208 	ne = 0;
209 
210 	if (-1 == access(certdir, R_OK)) {
211 		warnx("%s: -c directory must exist", certdir);
212 		ne++;
213 	}
214 
215 	if (!newkey && -1 == access(keyfile, R_OK)) {
216 		warnx("%s: -k file must exist", keyfile);
217 		ne++;
218 	} else if (newkey && -1 != access(keyfile, R_OK)) {
219 		dodbg("%s: domain key exists (not creating)", keyfile);
220 		newkey = 0;
221 	}
222 
223 	if (-1 == access(chngdir, R_OK)) {
224 		warnx("%s: -C directory must exist", chngdir);
225 		ne++;
226 	}
227 
228 	if (!newacct && -1 == access(acctkey, R_OK)) {
229 		warnx("%s: -f file must exist", acctkey);
230 		ne++;
231 	} else if (newacct && -1 != access(acctkey, R_OK)) {
232 		dodbg("%s: account key exists (not creating)", acctkey);
233 		newacct = 0;
234 	}
235 
236 	if (ne > 0)
237 		exit(EXIT_FAILURE);
238 
239 	/* Set the zeroth altname as our domain. */
240 
241 	altsz = argc + 1;
242 	alts = calloc(altsz, sizeof(char *));
243 	if (NULL == alts)
244 		err(EXIT_FAILURE, "calloc");
245 	alts[0] = domain;
246 	for (i = 0; i < (size_t)argc; i++)
247 		alts[i + 1] = argv[i];
248 
249 	/*
250 	 * Open channels between our components.
251 	 */
252 
253 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, key_fds))
254 		err(EXIT_FAILURE, "socketpair");
255 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, acct_fds))
256 		err(EXIT_FAILURE, "socketpair");
257 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, chng_fds))
258 		err(EXIT_FAILURE, "socketpair");
259 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, cert_fds))
260 		err(EXIT_FAILURE, "socketpair");
261 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, file_fds))
262 		err(EXIT_FAILURE, "socketpair");
263 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, dns_fds))
264 		err(EXIT_FAILURE, "socketpair");
265 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, rvk_fds))
266 		err(EXIT_FAILURE, "socketpair");
267 
268 	/* Start with the network-touching process. */
269 
270 	if (-1 == (pids[COMP_NET] = fork()))
271 		err(EXIT_FAILURE, "fork");
272 
273 	if (0 == pids[COMP_NET]) {
274 		proccomp = COMP_NET;
275 		close(key_fds[0]);
276 		close(acct_fds[0]);
277 		close(chng_fds[0]);
278 		close(cert_fds[0]);
279 		close(file_fds[0]);
280 		close(file_fds[1]);
281 		close(dns_fds[0]);
282 		close(rvk_fds[0]);
283 		c = netproc(key_fds[1], acct_fds[1],
284 		    chng_fds[1], cert_fds[1],
285 		    dns_fds[1], rvk_fds[1],
286 		    newacct, revocate, authority,
287 		    (const char *const *)alts, altsz,
288 		    agreement);
289 		free(alts);
290 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
291 	}
292 
293 	close(key_fds[1]);
294 	close(acct_fds[1]);
295 	close(chng_fds[1]);
296 	close(cert_fds[1]);
297 	close(dns_fds[1]);
298 	close(rvk_fds[1]);
299 
300 	/* Now the key-touching component. */
301 
302 	if (-1 == (pids[COMP_KEY] = fork()))
303 		err(EXIT_FAILURE, "fork");
304 
305 	if (0 == pids[COMP_KEY]) {
306 		proccomp = COMP_KEY;
307 		close(cert_fds[0]);
308 		close(dns_fds[0]);
309 		close(rvk_fds[0]);
310 		close(acct_fds[0]);
311 		close(chng_fds[0]);
312 		close(file_fds[0]);
313 		close(file_fds[1]);
314 		c = keyproc(key_fds[0], keyfile,
315 		    (const char **)alts, altsz, newkey);
316 		free(alts);
317 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
318 	}
319 
320 	close(key_fds[0]);
321 
322 	/* The account-touching component. */
323 
324 	if (-1 == (pids[COMP_ACCOUNT] = fork()))
325 		err(EXIT_FAILURE, "fork");
326 
327 	if (0 == pids[COMP_ACCOUNT]) {
328 		proccomp = COMP_ACCOUNT;
329 		free(alts);
330 		close(cert_fds[0]);
331 		close(dns_fds[0]);
332 		close(rvk_fds[0]);
333 		close(chng_fds[0]);
334 		close(file_fds[0]);
335 		close(file_fds[1]);
336 		c = acctproc(acct_fds[0], acctkey, newacct);
337 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
338 	}
339 
340 	close(acct_fds[0]);
341 
342 	/* The challenge-accepting component. */
343 
344 	if (-1 == (pids[COMP_CHALLENGE] = fork()))
345 		err(EXIT_FAILURE, "fork");
346 
347 	if (0 == pids[COMP_CHALLENGE]) {
348 		proccomp = COMP_CHALLENGE;
349 		free(alts);
350 		close(cert_fds[0]);
351 		close(dns_fds[0]);
352 		close(rvk_fds[0]);
353 		close(file_fds[0]);
354 		close(file_fds[1]);
355 		c = chngproc(chng_fds[0], chngdir, remote);
356 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
357 	}
358 
359 	close(chng_fds[0]);
360 
361 	/* The certificate-handling component. */
362 
363 	if (-1 == (pids[COMP_CERT] = fork()))
364 		err(EXIT_FAILURE, "fork");
365 
366 	if (0 == pids[COMP_CERT]) {
367 		proccomp = COMP_CERT;
368 		free(alts);
369 		close(dns_fds[0]);
370 		close(rvk_fds[0]);
371 		close(file_fds[1]);
372 		c = certproc(cert_fds[0], file_fds[0]);
373 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
374 	}
375 
376 	close(cert_fds[0]);
377 	close(file_fds[0]);
378 
379 	/* The certificate-handling component. */
380 
381 	if (-1 == (pids[COMP_FILE] = fork()))
382 		err(EXIT_FAILURE, "fork");
383 
384 	if (0 == pids[COMP_FILE]) {
385 		proccomp = COMP_FILE;
386 		free(alts);
387 		close(dns_fds[0]);
388 		close(rvk_fds[0]);
389 		c = fileproc(file_fds[1], backup, certdir);
390 		/*
391 		 * This is different from the other processes in that it
392 		 * can return 2 if the certificates were updated.
393 		 */
394 		exit(c > 1 ? 2 : (c ? EXIT_SUCCESS : EXIT_FAILURE));
395 	}
396 
397 	close(file_fds[1]);
398 
399 	/* The DNS lookup component. */
400 
401 	if (-1 == (pids[COMP_DNS] = fork()))
402 		err(EXIT_FAILURE, "fork");
403 
404 	if (0 == pids[COMP_DNS]) {
405 		proccomp = COMP_DNS;
406 		free(alts);
407 		close(rvk_fds[0]);
408 		c = dnsproc(dns_fds[0]);
409 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
410 	}
411 
412 	close(dns_fds[0]);
413 
414 	/* The expiration component. */
415 
416 	if (-1 == (pids[COMP_REVOKE] = fork()))
417 		err(EXIT_FAILURE, "fork");
418 
419 	if (0 == pids[COMP_REVOKE]) {
420 		proccomp = COMP_REVOKE;
421 		c = revokeproc(rvk_fds[0], certdir, force, revocate,
422 		    (const char *const *)alts, altsz);
423 		free(alts);
424 		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
425 	}
426 
427 	close(rvk_fds[0]);
428 
429 	/* Jail: sandbox, file-system, user. */
430 
431 	if (pledge("stdio", NULL) == -1) {
432 		warn("pledge");
433 		exit(EXIT_FAILURE);
434 	}
435 
436 	/*
437 	 * Collect our subprocesses.
438 	 * Require that they both have exited cleanly.
439 	 */
440 
441 	rc = checkexit(pids[COMP_KEY], COMP_KEY) +
442 	    checkexit(pids[COMP_CERT], COMP_CERT) +
443 	    checkexit(pids[COMP_NET], COMP_NET) +
444 	    checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) +
445 	    checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) +
446 	    checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) +
447 	    checkexit(pids[COMP_DNS], COMP_DNS) +
448 	    checkexit(pids[COMP_REVOKE], COMP_REVOKE);
449 
450 	free(certdir);
451 	free(keyfile);
452 	free(acctkey);
453 	free(chngdir);
454 	free(alts);
455 	return (COMP__MAX != rc ? EXIT_FAILURE :
456 	    (2 == c ? EXIT_SUCCESS : 2));
457 usage:
458 	fprintf(stderr,
459 	    "usage: acme-client [-bFmnNrv] [-a agreement] [-C challengedir]\n"
460 	    "                   [-c certdir] [-f accountkey] [-k domainkey]\n"
461 	    "                   [-s authority] domain [altnames...]\n");
462 	free(certdir);
463 	free(keyfile);
464 	free(acctkey);
465 	free(chngdir);
466 	return (EXIT_FAILURE);
467 }
468