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