GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/acme-client/main.c Lines: 0 230 0.0 %
Date: 2017-11-13 Branches: 0 140 0.0 %

Line Branch Exec Source
1
/*	$Id: main.c,v 1.35 2017/05/27 08:31:08 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
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, *agreement = 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
	agreement = authority->agreement;
158
	acctkey = authority->account;
159
160
	if (acctkey == NULL) {
161
		/* XXX replace with existance check in parse.y */
162
		err(EXIT_FAILURE, "no account key in config?");
163
	}
164
	if (domain->challengedir == NULL)
165
		chngdir = strdup(WWW_DIR);
166
	else
167
		chngdir = domain->challengedir;
168
169
	if (chngdir == NULL)
170
		err(EXIT_FAILURE, "strdup");
171
172
	/*
173
	 * Do some quick checks to see if our paths exist.
174
	 * This will be done in the children, but we might as well check
175
	 * now before the fork.
176
	 * XXX maybe use conf_check_file() from parse.y
177
	 */
178
179
	ne = 0;
180
181
	if (access(certdir, R_OK) == -1) {
182
		warnx("%s: cert directory must exist", certdir);
183
		ne++;
184
	}
185
186
	if (!(popts & ACME_OPT_NEWDKEY) && access(domain->key, R_OK) == -1) {
187
		warnx("%s: domain key file must exist", domain->key);
188
		ne++;
189
	} else if ((popts & ACME_OPT_NEWDKEY) && access(domain->key, R_OK) != -1) {
190
		dodbg("%s: domain key exists (not creating)", domain->key);
191
		popts &= ~ACME_OPT_NEWDKEY;
192
	}
193
194
	if (access(chngdir, R_OK) == -1) {
195
		warnx("%s: challenge directory must exist", chngdir);
196
		ne++;
197
	}
198
199
	if (!(popts & ACME_OPT_NEWACCT) && access(acctkey, R_OK) == -1) {
200
		warnx("%s: account key file must exist", acctkey);
201
		ne++;
202
	} else if ((popts & ACME_OPT_NEWACCT) && access(acctkey, R_OK) != -1) {
203
		dodbg("%s: account key exists (not creating)", acctkey);
204
		popts &= ~ACME_OPT_NEWACCT;
205
	}
206
207
	if (ne > 0)
208
		exit(EXIT_FAILURE);
209
210
	if (popts & ACME_OPT_CHECK)
211
		exit(EXIT_SUCCESS);
212
213
	/* Set the zeroth altname as our domain. */
214
	altsz = domain->altname_count + 1;
215
	alts = calloc(altsz, sizeof(char *));
216
	if (alts == NULL)
217
		err(EXIT_FAILURE, "calloc");
218
	alts[0] = domain->domain;
219
	i = 1;
220
	/* XXX get rid of alts[] later */
221
	TAILQ_FOREACH(ac, &domain->altname_list, entry)
222
		alts[i++] = ac->domain;
223
224
	/*
225
	 * Open channels between our components.
226
	 */
227
228
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, key_fds) == -1)
229
		err(EXIT_FAILURE, "socketpair");
230
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, acct_fds) == -1)
231
		err(EXIT_FAILURE, "socketpair");
232
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, chng_fds) == -1)
233
		err(EXIT_FAILURE, "socketpair");
234
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, cert_fds) == -1)
235
		err(EXIT_FAILURE, "socketpair");
236
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, file_fds) == -1)
237
		err(EXIT_FAILURE, "socketpair");
238
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, dns_fds) == -1)
239
		err(EXIT_FAILURE, "socketpair");
240
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, rvk_fds) == -1)
241
		err(EXIT_FAILURE, "socketpair");
242
243
	/* Start with the network-touching process. */
244
245
	if ((pids[COMP_NET] = fork()) == -1)
246
		err(EXIT_FAILURE, "fork");
247
248
	if (pids[COMP_NET] == 0) {
249
		proccomp = COMP_NET;
250
		close(key_fds[0]);
251
		close(acct_fds[0]);
252
		close(chng_fds[0]);
253
		close(cert_fds[0]);
254
		close(file_fds[0]);
255
		close(file_fds[1]);
256
		close(dns_fds[0]);
257
		close(rvk_fds[0]);
258
		c = netproc(key_fds[1], acct_fds[1],
259
		    chng_fds[1], cert_fds[1],
260
		    dns_fds[1], rvk_fds[1],
261
		    (popts & ACME_OPT_NEWACCT), revocate, authority,
262
		    (const char *const *)alts, altsz,
263
		    agreement);
264
		free(alts);
265
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
266
	}
267
268
	close(key_fds[1]);
269
	close(acct_fds[1]);
270
	close(chng_fds[1]);
271
	close(cert_fds[1]);
272
	close(dns_fds[1]);
273
	close(rvk_fds[1]);
274
275
	/* Now the key-touching component. */
276
277
	if ((pids[COMP_KEY] = fork()) == -1)
278
		err(EXIT_FAILURE, "fork");
279
280
	if (pids[COMP_KEY] == 0) {
281
		proccomp = COMP_KEY;
282
		close(cert_fds[0]);
283
		close(dns_fds[0]);
284
		close(rvk_fds[0]);
285
		close(acct_fds[0]);
286
		close(chng_fds[0]);
287
		close(file_fds[0]);
288
		close(file_fds[1]);
289
		c = keyproc(key_fds[0], domain->key,
290
		    (const char **)alts, altsz, (popts & ACME_OPT_NEWDKEY));
291
		free(alts);
292
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
293
	}
294
295
	close(key_fds[0]);
296
297
	/* The account-touching component. */
298
299
	if ((pids[COMP_ACCOUNT] = fork()) == -1)
300
		err(EXIT_FAILURE, "fork");
301
302
	if (pids[COMP_ACCOUNT] == 0) {
303
		proccomp = COMP_ACCOUNT;
304
		free(alts);
305
		close(cert_fds[0]);
306
		close(dns_fds[0]);
307
		close(rvk_fds[0]);
308
		close(chng_fds[0]);
309
		close(file_fds[0]);
310
		close(file_fds[1]);
311
		c = acctproc(acct_fds[0], acctkey, (popts & ACME_OPT_NEWACCT));
312
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
313
	}
314
315
	close(acct_fds[0]);
316
317
	/* The challenge-accepting component. */
318
319
	if ((pids[COMP_CHALLENGE] = fork()) == -1)
320
		err(EXIT_FAILURE, "fork");
321
322
	if (pids[COMP_CHALLENGE] == 0) {
323
		proccomp = COMP_CHALLENGE;
324
		free(alts);
325
		close(cert_fds[0]);
326
		close(dns_fds[0]);
327
		close(rvk_fds[0]);
328
		close(file_fds[0]);
329
		close(file_fds[1]);
330
		c = chngproc(chng_fds[0], chngdir);
331
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
332
	}
333
334
	close(chng_fds[0]);
335
336
	/* The certificate-handling component. */
337
338
	if ((pids[COMP_CERT] = fork()) == -1)
339
		err(EXIT_FAILURE, "fork");
340
341
	if (pids[COMP_CERT] == 0) {
342
		proccomp = COMP_CERT;
343
		free(alts);
344
		close(dns_fds[0]);
345
		close(rvk_fds[0]);
346
		close(file_fds[1]);
347
		c = certproc(cert_fds[0], file_fds[0]);
348
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
349
	}
350
351
	close(cert_fds[0]);
352
	close(file_fds[0]);
353
354
	/* The certificate-handling component. */
355
356
	if ((pids[COMP_FILE] = fork()) == -1)
357
		err(EXIT_FAILURE, "fork");
358
359
	if (pids[COMP_FILE] == 0) {
360
		proccomp = COMP_FILE;
361
		free(alts);
362
		close(dns_fds[0]);
363
		close(rvk_fds[0]);
364
		c = fileproc(file_fds[1], certdir, certfile, chainfile,
365
		    fullchainfile);
366
		/*
367
		 * This is different from the other processes in that it
368
		 * can return 2 if the certificates were updated.
369
		 */
370
		exit(c > 1 ? 2 : (c ? EXIT_SUCCESS : EXIT_FAILURE));
371
	}
372
373
	close(file_fds[1]);
374
375
	/* The DNS lookup component. */
376
377
	if ((pids[COMP_DNS] = fork()) == -1)
378
		err(EXIT_FAILURE, "fork");
379
380
	if (pids[COMP_DNS] == 0) {
381
		proccomp = COMP_DNS;
382
		free(alts);
383
		close(rvk_fds[0]);
384
		c = dnsproc(dns_fds[0]);
385
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
386
	}
387
388
	close(dns_fds[0]);
389
390
	/* The expiration component. */
391
392
	if ((pids[COMP_REVOKE] = fork()) == -1)
393
		err(EXIT_FAILURE, "fork");
394
395
	if (pids[COMP_REVOKE] == 0) {
396
		proccomp = COMP_REVOKE;
397
		c = revokeproc(rvk_fds[0], certdir,
398
		    certfile != NULL ? certfile : fullchainfile,
399
		    force, revocate,
400
		    (const char *const *)alts, altsz);
401
		free(alts);
402
		exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
403
	}
404
405
	close(rvk_fds[0]);
406
407
	/* Jail: sandbox, file-system, user. */
408
409
	if (pledge("stdio flock rpath cpath wpath", NULL) == -1) {
410
		warn("pledge");
411
		exit(EXIT_FAILURE);
412
	}
413
414
	/*
415
	 * Collect our subprocesses.
416
	 * Require that they both have exited cleanly.
417
	 */
418
419
	rc = checkexit(pids[COMP_KEY], COMP_KEY) +
420
	    checkexit(pids[COMP_CERT], COMP_CERT) +
421
	    checkexit(pids[COMP_NET], COMP_NET) +
422
	    checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) +
423
	    checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) +
424
	    checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) +
425
	    checkexit(pids[COMP_DNS], COMP_DNS) +
426
	    checkexit(pids[COMP_REVOKE], COMP_REVOKE);
427
428
	free(alts);
429
	return rc != COMP__MAX ? EXIT_FAILURE : (c == 2 ? EXIT_SUCCESS : 2);
430
usage:
431
	fprintf(stderr,
432
	    "usage: acme-client [-ADFnrv] [-f configfile] domain\n");
433
	return EXIT_FAILURE;
434
}