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