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