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