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