1*d7c05bd5Sclaudio /* $Id: main.c,v 1.56 2024/06/19 13:13:25 claudio Exp $ */
2de579d12Sflorian /*
3de579d12Sflorian * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4de579d12Sflorian *
5de579d12Sflorian * Permission to use, copy, modify, and distribute this software for any
6de579d12Sflorian * purpose with or without fee is hereby granted, provided that the above
7de579d12Sflorian * copyright notice and this permission notice appear in all copies.
8de579d12Sflorian *
9de579d12Sflorian * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10de579d12Sflorian * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11de579d12Sflorian * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12de579d12Sflorian * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13de579d12Sflorian * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14de579d12Sflorian * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15de579d12Sflorian * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16de579d12Sflorian */
17de579d12Sflorian
18de579d12Sflorian #include <sys/socket.h>
19de579d12Sflorian
20de579d12Sflorian #include <ctype.h>
21de579d12Sflorian #include <err.h>
2262492c74Sflorian #include <libgen.h>
235b337cd6Sflorian #include <locale.h>
24*d7c05bd5Sclaudio #include <signal.h>
25de579d12Sflorian #include <stdarg.h>
26de579d12Sflorian #include <stdio.h>
27de579d12Sflorian #include <stdlib.h>
28de579d12Sflorian #include <string.h>
29de579d12Sflorian #include <unistd.h>
30de579d12Sflorian
31de579d12Sflorian #include "extern.h"
323943d840Sbenno #include "parse.h"
33de579d12Sflorian
34a44942e7Sflorian #define WWW_DIR "/var/www/acme"
35383e31e9Sbenno #define CONF_FILE "/etc/acme-client.conf"
36de579d12Sflorian
373a92740dSflorian int verbose;
383a92740dSflorian enum comp proccomp;
393a92740dSflorian
40de579d12Sflorian int
main(int argc,char * argv[])41de579d12Sflorian main(int argc, char *argv[])
42de579d12Sflorian {
43383e31e9Sbenno const char **alts = NULL;
4461075b4cSflorian char *certdir = NULL;
450b28b247Sflorian char *chngdir = NULL, *auth = NULL;
46383e31e9Sbenno char *conffile = CONF_FILE;
4747c124d1Sbenno char *tmps, *tmpsd;
487bce6888Sderaadt int key_fds[2], acct_fds[2], chng_fds[2], cert_fds[2];
497bce6888Sderaadt int file_fds[2], dns_fds[2], rvk_fds[2];
50383e31e9Sbenno int force = 0;
517bce6888Sderaadt int c, rc, revocate = 0;
52383e31e9Sbenno int popts = 0;
53de579d12Sflorian pid_t pids[COMP__MAX];
54de579d12Sflorian size_t i, altsz, ne;
55de579d12Sflorian
56383e31e9Sbenno struct acme_conf *conf = NULL;
57383e31e9Sbenno struct authority_c *authority = NULL;
58383e31e9Sbenno struct domain_c *domain = NULL;
59383e31e9Sbenno struct altname_c *ac;
60383e31e9Sbenno
615b337cd6Sflorian if (setlocale(LC_CTYPE, "C") == NULL)
625b337cd6Sflorian errx(1, "setlocale");
635b337cd6Sflorian
642570ecd0Sflorian while ((c = getopt(argc, argv, "Fnrvf:")) != -1)
65de579d12Sflorian switch (c) {
6638a8ff7fSbenno case 'F':
6738a8ff7fSbenno force = 1;
6838a8ff7fSbenno break;
6938a8ff7fSbenno case 'f':
7038a8ff7fSbenno if ((conffile = strdup(optarg)) == NULL)
7138a8ff7fSbenno err(EXIT_FAILURE, "strdup");
7238a8ff7fSbenno break;
7338a8ff7fSbenno case 'n':
7438a8ff7fSbenno popts |= ACME_OPT_CHECK;
7538a8ff7fSbenno break;
76a8699559Sderaadt case 'r':
77ee27a5e1Sderaadt revocate = 1;
78de579d12Sflorian break;
79a8699559Sderaadt case 'v':
80de579d12Sflorian verbose = verbose ? 2 : 1;
81383e31e9Sbenno popts |= ACME_OPT_VERBOSE;
82de579d12Sflorian break;
83de579d12Sflorian default:
84de579d12Sflorian goto usage;
85de579d12Sflorian }
86de579d12Sflorian
87d27a8d23Sflorian if (getuid() != 0)
88d27a8d23Sflorian errx(EXIT_FAILURE, "must be run as root");
89d27a8d23Sflorian
90383e31e9Sbenno /* parse config file */
91383e31e9Sbenno if ((conf = parse_config(conffile, popts)) == NULL)
92a7313564Sbenno return EXIT_FAILURE;
93a8bb3d0cSflorian
94de579d12Sflorian argc -= optind;
95de579d12Sflorian argv += optind;
967cd8f039Sjsing if (argc != 1)
97de579d12Sflorian goto usage;
98de579d12Sflorian
9987f5451dSbenno if ((domain = domain_find_handle(conf, argv[0])) == NULL)
10069180610Sderaadt errx(EXIT_FAILURE, "domain %s not found", argv[0]);
101383e31e9Sbenno
102de579d12Sflorian argc--;
103de579d12Sflorian argv++;
104de579d12Sflorian
10561075b4cSflorian /*
10661075b4cSflorian * The parser enforces that at least cert or fullchain is set.
10761075b4cSflorian * XXX Test if cert, chain and fullchain have the same dirname?
10861075b4cSflorian */
10947c124d1Sbenno tmps = domain->cert ? domain->cert : domain->fullchain;
11047c124d1Sbenno if ((tmps = strdup(tmps)) == NULL)
111d40ebfdcSflorian err(EXIT_FAILURE, "strdup");
11247c124d1Sbenno if ((tmpsd = dirname(tmps)) == NULL)
113d40ebfdcSflorian err(EXIT_FAILURE, "dirname");
11447c124d1Sbenno if ((certdir = strdup(tmpsd)) == NULL)
11547c124d1Sbenno err(EXIT_FAILURE, "strdup");
11647c124d1Sbenno free(tmps);
117571e53b1Sbenno tmps = tmpsd = NULL;
118d40ebfdcSflorian
119383e31e9Sbenno
12061075b4cSflorian /* chain or fullchain can be relative paths according */
12161075b4cSflorian if (domain->chain && domain->chain[0] != '/') {
12261075b4cSflorian if (asprintf(&tmps, "%s/%s", certdir, domain->chain) == -1)
12361075b4cSflorian err(EXIT_FAILURE, "asprintf");
12461075b4cSflorian free(domain->chain);
12561075b4cSflorian domain->chain = tmps;
12661075b4cSflorian tmps = NULL;
12733febeb9Sflorian }
12861075b4cSflorian if (domain->fullchain && domain->fullchain[0] != '/') {
12961075b4cSflorian if (asprintf(&tmps, "%s/%s", certdir, domain->fullchain) == -1)
13061075b4cSflorian err(EXIT_FAILURE, "asprintf");
13161075b4cSflorian free(domain->fullchain);
13261075b4cSflorian domain->fullchain = tmps;
13361075b4cSflorian tmps = NULL;
13470bcb874Sbenno }
13570bcb874Sbenno
136383e31e9Sbenno if ((auth = domain->auth) == NULL) {
137383e31e9Sbenno /* use the first authority from the config as default XXX */
138383e31e9Sbenno authority = authority_find0(conf);
139383e31e9Sbenno if (authority == NULL)
14069180610Sderaadt errx(EXIT_FAILURE, "no authorities configured");
141383e31e9Sbenno } else {
142383e31e9Sbenno authority = authority_find(conf, auth);
143383e31e9Sbenno if (authority == NULL)
14469180610Sderaadt errx(EXIT_FAILURE, "authority %s not found", auth);
145383e31e9Sbenno }
146383e31e9Sbenno
14737731077Sbenno if ((chngdir = domain->challengedir) == NULL)
14837731077Sbenno if ((chngdir = strdup(WWW_DIR)) == NULL)
149de579d12Sflorian err(EXIT_FAILURE, "strdup");
150de579d12Sflorian
151de579d12Sflorian /*
152de579d12Sflorian * Do some quick checks to see if our paths exist.
153de579d12Sflorian * This will be done in the children, but we might as well check
154de579d12Sflorian * now before the fork.
155383e31e9Sbenno * XXX maybe use conf_check_file() from parse.y
156de579d12Sflorian */
157de579d12Sflorian
158de579d12Sflorian ne = 0;
159de579d12Sflorian
1607cd8f039Sjsing if (access(certdir, R_OK) == -1) {
161383e31e9Sbenno warnx("%s: cert directory must exist", certdir);
162de579d12Sflorian ne++;
163de579d12Sflorian }
164de579d12Sflorian
1657cd8f039Sjsing if (access(chngdir, R_OK) == -1) {
1666c0ff37dSbenno warnx("%s: challenge directory must exist", chngdir);
167de579d12Sflorian ne++;
168de579d12Sflorian }
169de579d12Sflorian
170de579d12Sflorian if (ne > 0)
171a7313564Sbenno return EXIT_FAILURE;
172de579d12Sflorian
1733298b855Sbenno if (popts & ACME_OPT_CHECK)
174a7313564Sbenno return EXIT_SUCCESS;
1753298b855Sbenno
176de579d12Sflorian /* Set the zeroth altname as our domain. */
177383e31e9Sbenno altsz = domain->altname_count + 1;
178de579d12Sflorian alts = calloc(altsz, sizeof(char *));
1797cd8f039Sjsing if (alts == NULL)
180de579d12Sflorian err(EXIT_FAILURE, "calloc");
181383e31e9Sbenno alts[0] = domain->domain;
182383e31e9Sbenno i = 1;
183383e31e9Sbenno /* XXX get rid of alts[] later */
184221ac2aaSbenno TAILQ_FOREACH(ac, &domain->altname_list, entry)
185383e31e9Sbenno alts[i++] = ac->domain;
186de579d12Sflorian
187de579d12Sflorian /*
188de579d12Sflorian * Open channels between our components.
189de579d12Sflorian */
190de579d12Sflorian
1917cd8f039Sjsing if (socketpair(AF_UNIX, SOCK_STREAM, 0, key_fds) == -1)
192de579d12Sflorian err(EXIT_FAILURE, "socketpair");
1937cd8f039Sjsing if (socketpair(AF_UNIX, SOCK_STREAM, 0, acct_fds) == -1)
194de579d12Sflorian err(EXIT_FAILURE, "socketpair");
1957cd8f039Sjsing if (socketpair(AF_UNIX, SOCK_STREAM, 0, chng_fds) == -1)
196de579d12Sflorian err(EXIT_FAILURE, "socketpair");
1977cd8f039Sjsing if (socketpair(AF_UNIX, SOCK_STREAM, 0, cert_fds) == -1)
198de579d12Sflorian err(EXIT_FAILURE, "socketpair");
1997cd8f039Sjsing if (socketpair(AF_UNIX, SOCK_STREAM, 0, file_fds) == -1)
200de579d12Sflorian err(EXIT_FAILURE, "socketpair");
2017cd8f039Sjsing if (socketpair(AF_UNIX, SOCK_STREAM, 0, dns_fds) == -1)
202de579d12Sflorian err(EXIT_FAILURE, "socketpair");
2037cd8f039Sjsing if (socketpair(AF_UNIX, SOCK_STREAM, 0, rvk_fds) == -1)
204de579d12Sflorian err(EXIT_FAILURE, "socketpair");
205de579d12Sflorian
206*d7c05bd5Sclaudio signal(SIGPIPE, SIG_IGN);
207*d7c05bd5Sclaudio
208de579d12Sflorian /* Start with the network-touching process. */
209de579d12Sflorian
2107cd8f039Sjsing if ((pids[COMP_NET] = fork()) == -1)
211de579d12Sflorian err(EXIT_FAILURE, "fork");
212de579d12Sflorian
2137cd8f039Sjsing if (pids[COMP_NET] == 0) {
214de579d12Sflorian proccomp = COMP_NET;
215de579d12Sflorian close(key_fds[0]);
216de579d12Sflorian close(acct_fds[0]);
217de579d12Sflorian close(chng_fds[0]);
218de579d12Sflorian close(cert_fds[0]);
219de579d12Sflorian close(file_fds[0]);
220de579d12Sflorian close(file_fds[1]);
221de579d12Sflorian close(dns_fds[0]);
222de579d12Sflorian close(rvk_fds[0]);
223de579d12Sflorian c = netproc(key_fds[1], acct_fds[1],
224de579d12Sflorian chng_fds[1], cert_fds[1],
225de579d12Sflorian dns_fds[1], rvk_fds[1],
2267b00f4e9Sflorian revocate, authority,
2270b28b247Sflorian (const char *const *)alts, altsz);
228de579d12Sflorian exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
229de579d12Sflorian }
230de579d12Sflorian
231de579d12Sflorian close(key_fds[1]);
232de579d12Sflorian close(acct_fds[1]);
233de579d12Sflorian close(chng_fds[1]);
234de579d12Sflorian close(cert_fds[1]);
235de579d12Sflorian close(dns_fds[1]);
236de579d12Sflorian close(rvk_fds[1]);
237de579d12Sflorian
238de579d12Sflorian /* Now the key-touching component. */
239de579d12Sflorian
2407cd8f039Sjsing if ((pids[COMP_KEY] = fork()) == -1)
241de579d12Sflorian err(EXIT_FAILURE, "fork");
242de579d12Sflorian
2437cd8f039Sjsing if (pids[COMP_KEY] == 0) {
244de579d12Sflorian proccomp = COMP_KEY;
245de579d12Sflorian close(cert_fds[0]);
246de579d12Sflorian close(dns_fds[0]);
247de579d12Sflorian close(rvk_fds[0]);
248de579d12Sflorian close(acct_fds[0]);
249de579d12Sflorian close(chng_fds[0]);
250de579d12Sflorian close(file_fds[0]);
251de579d12Sflorian close(file_fds[1]);
252383e31e9Sbenno c = keyproc(key_fds[0], domain->key,
25365a104faSflorian (const char **)alts, altsz,
25465a104faSflorian domain->keytype);
255de579d12Sflorian exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
256de579d12Sflorian }
257de579d12Sflorian
258de579d12Sflorian close(key_fds[0]);
259de579d12Sflorian
260de579d12Sflorian /* The account-touching component. */
261de579d12Sflorian
2627cd8f039Sjsing if ((pids[COMP_ACCOUNT] = fork()) == -1)
263de579d12Sflorian err(EXIT_FAILURE, "fork");
264de579d12Sflorian
2657cd8f039Sjsing if (pids[COMP_ACCOUNT] == 0) {
266de579d12Sflorian proccomp = COMP_ACCOUNT;
267de579d12Sflorian close(cert_fds[0]);
268de579d12Sflorian close(dns_fds[0]);
269de579d12Sflorian close(rvk_fds[0]);
270de579d12Sflorian close(chng_fds[0]);
271de579d12Sflorian close(file_fds[0]);
272de579d12Sflorian close(file_fds[1]);
2734f8b772fSflorian c = acctproc(acct_fds[0], authority->account,
2744f8b772fSflorian authority->keytype);
275de579d12Sflorian exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
276de579d12Sflorian }
277de579d12Sflorian
278de579d12Sflorian close(acct_fds[0]);
279de579d12Sflorian
280de579d12Sflorian /* The challenge-accepting component. */
281de579d12Sflorian
2827cd8f039Sjsing if ((pids[COMP_CHALLENGE] = fork()) == -1)
283de579d12Sflorian err(EXIT_FAILURE, "fork");
284de579d12Sflorian
2857cd8f039Sjsing if (pids[COMP_CHALLENGE] == 0) {
286de579d12Sflorian proccomp = COMP_CHALLENGE;
287de579d12Sflorian close(cert_fds[0]);
288de579d12Sflorian close(dns_fds[0]);
289de579d12Sflorian close(rvk_fds[0]);
290de579d12Sflorian close(file_fds[0]);
291de579d12Sflorian close(file_fds[1]);
292383e31e9Sbenno c = chngproc(chng_fds[0], chngdir);
293de579d12Sflorian exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
294de579d12Sflorian }
295de579d12Sflorian
296de579d12Sflorian close(chng_fds[0]);
297de579d12Sflorian
298de579d12Sflorian /* The certificate-handling component. */
299de579d12Sflorian
3007cd8f039Sjsing if ((pids[COMP_CERT] = fork()) == -1)
301de579d12Sflorian err(EXIT_FAILURE, "fork");
302de579d12Sflorian
3037cd8f039Sjsing if (pids[COMP_CERT] == 0) {
304de579d12Sflorian proccomp = COMP_CERT;
305de579d12Sflorian close(dns_fds[0]);
306de579d12Sflorian close(rvk_fds[0]);
307de579d12Sflorian close(file_fds[1]);
308de579d12Sflorian c = certproc(cert_fds[0], file_fds[0]);
309de579d12Sflorian exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
310de579d12Sflorian }
311de579d12Sflorian
312de579d12Sflorian close(cert_fds[0]);
313de579d12Sflorian close(file_fds[0]);
314de579d12Sflorian
315de579d12Sflorian /* The certificate-handling component. */
316de579d12Sflorian
3177cd8f039Sjsing if ((pids[COMP_FILE] = fork()) == -1)
318de579d12Sflorian err(EXIT_FAILURE, "fork");
319de579d12Sflorian
3207cd8f039Sjsing if (pids[COMP_FILE] == 0) {
321de579d12Sflorian proccomp = COMP_FILE;
322de579d12Sflorian close(dns_fds[0]);
323de579d12Sflorian close(rvk_fds[0]);
32461075b4cSflorian c = fileproc(file_fds[1], certdir, domain->cert, domain->chain,
32561075b4cSflorian domain->fullchain);
326de579d12Sflorian /*
327de579d12Sflorian * This is different from the other processes in that it
328de579d12Sflorian * can return 2 if the certificates were updated.
329de579d12Sflorian */
33055599e87Sderaadt exit(c > 1 ? 2 : (c ? EXIT_SUCCESS : EXIT_FAILURE));
331de579d12Sflorian }
332de579d12Sflorian
333de579d12Sflorian close(file_fds[1]);
334de579d12Sflorian
335de579d12Sflorian /* The DNS lookup component. */
336de579d12Sflorian
3377cd8f039Sjsing if ((pids[COMP_DNS] = fork()) == -1)
338de579d12Sflorian err(EXIT_FAILURE, "fork");
339de579d12Sflorian
3407cd8f039Sjsing if (pids[COMP_DNS] == 0) {
341de579d12Sflorian proccomp = COMP_DNS;
342de579d12Sflorian close(rvk_fds[0]);
343de579d12Sflorian c = dnsproc(dns_fds[0]);
344de579d12Sflorian exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
345de579d12Sflorian }
346de579d12Sflorian
347de579d12Sflorian close(dns_fds[0]);
348de579d12Sflorian
349de579d12Sflorian /* The expiration component. */
350de579d12Sflorian
3517cd8f039Sjsing if ((pids[COMP_REVOKE] = fork()) == -1)
352de579d12Sflorian err(EXIT_FAILURE, "fork");
353de579d12Sflorian
3547cd8f039Sjsing if (pids[COMP_REVOKE] == 0) {
355de579d12Sflorian proccomp = COMP_REVOKE;
35661075b4cSflorian c = revokeproc(rvk_fds[0], domain->cert != NULL ? domain->cert :
35761075b4cSflorian domain->fullchain, force, revocate,
358de579d12Sflorian (const char *const *)alts, altsz);
359de579d12Sflorian exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
360de579d12Sflorian }
361de579d12Sflorian
362de579d12Sflorian close(rvk_fds[0]);
363de579d12Sflorian
364de579d12Sflorian /* Jail: sandbox, file-system, user. */
365de579d12Sflorian
36630f35414Sbenno if (pledge("stdio", NULL) == -1)
36730f35414Sbenno err(EXIT_FAILURE, "pledge");
368de579d12Sflorian
369de579d12Sflorian /*
370de579d12Sflorian * Collect our subprocesses.
371de579d12Sflorian * Require that they both have exited cleanly.
372de579d12Sflorian */
373de579d12Sflorian
374de579d12Sflorian rc = checkexit(pids[COMP_KEY], COMP_KEY) +
375de579d12Sflorian checkexit(pids[COMP_CERT], COMP_CERT) +
376de579d12Sflorian checkexit(pids[COMP_NET], COMP_NET) +
377de579d12Sflorian checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) +
378de579d12Sflorian checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) +
379de579d12Sflorian checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) +
380de579d12Sflorian checkexit(pids[COMP_DNS], COMP_DNS) +
381de579d12Sflorian checkexit(pids[COMP_REVOKE], COMP_REVOKE);
382de579d12Sflorian
38334335c11Sjsing return rc != COMP__MAX ? EXIT_FAILURE : (c == 2 ? EXIT_SUCCESS : 2);
384de579d12Sflorian usage:
385bbca2207Sderaadt fprintf(stderr,
38687f5451dSbenno "usage: acme-client [-Fnrv] [-f configfile] handle\n");
38734335c11Sjsing return EXIT_FAILURE;
388de579d12Sflorian }
389