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