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 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