/* $Id: main.c,v 1.41 2018/07/30 09:59:03 benno Exp $ */ /* * Copyright (c) 2016 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef __VMS #include "wcme_config.h" #endif #include #include #include #include #include #include #include #include #include #include "extern.h" #ifndef WCME_IDIOM #include "parse.h" #define WWW_DIR "/var/www/acme" #define CONF_FILE "/etc/acme-client.conf" #else /* WCME_IDIOM */ #define SS$_NORMAL 1 #define SS$_NOPRIV 36 #define SS$_ABORT 44 #define SS$_BUGCHECK 676 #define SS$_OPINCOMPL 724 char* CertManAuthorityApi (); char* doasprintf (char*, ...); char* UtilOds2FileName (char*, char*, char*); #endif /* WCME_IDIOM */ int main(int argc, char *argv[]) { const char **alts = NULL; char *certdir = NULL, *certfile = NULL; char *chainfile = NULL, *fullchainfile = NULL; char *acctkey = NULL; char *chngdir = NULL, *auth = NULL; char *conffile = CONF_FILE; int key_fds[2], acct_fds[2], chng_fds[2], cert_fds[2]; int file_fds[2], dns_fds[2], rvk_fds[2]; int force = 0; int c, rc, revocate = 0; int popts = 0; pid_t pids[COMP__MAX]; extern int verbose; extern enum comp proccomp; size_t i, altsz, ne; struct acme_conf *conf = NULL; struct authority_c *authority = NULL; struct domain_c *domain = NULL; struct altname_c *ac; #ifdef WCME_IDIOM /********/ /* WCME */ /********/ /* /API= non-default URL of CA (e.g. Let's Encrypt) /CERTIFY= certify the domain name and optional alt names /CHECK= test the specified facility /FORCE force update of certificate /LIST list the certificates being managed /MANAGE run the once-per-day certificate management /NAMES= (internal use) domain name then comma/space alt names /PROCESS= (internal use) name/purpose of subprocess /REGISTER generate new account & domain keys and register /REVOKE revoke the certificate /VERBOSE be a bit chattier /VERSION display software version /WCME= for backward compatibility with version 1 */ int cnt, nargc = 0, newacct = 0, newdkey = 0, pnargc = 0; char *cptr, *sptr, *zptr, *keyfile = NULL; char api [256] = "", certman [1024] = "", names [1024] = "", process [16] = ""; char *nargv [16]; struct authority_c authc; /* always be a bit chatty */ verbose = 1; #ifdef WCME_DBG_MAIN doddbg("main() %d |%s|%s|%s|", argc, argv[0], argv[1], argv[2]); #endif /* ensure SYSPRV starts disabled and is only enabled as required */ UtilMereMortal(); if (!UtilInteractive()) { /* not designed to operate in CGIplus environment */ if (getenv ("CGIPLUSEOF") != NULL) exit (SS$_ABORT); if ((getenv("WWW_SERVER_SOFTWARE") != NULL && getenv("WWW_REQUEST_METHOD") != NULL) || (getenv("SERVER_SOFTWARE") != NULL && getenv("REQUEST_METHOD") != NULL)) { ScriptProcess (argc, argv); /* shouldn't */ exit (SS$_BUGCHECK); } if (argc > 1 && !strncasecmp(argv[1],"/OVERSEER",5)) { OverseerProcess (argc, argv); /* shouldn't */ exit (SS$_BUGCHECK); } /* each subprocess needs an independent C-RTL I/O buffer */ if (cptr = getenv ("WCME_LOG_FILE")) OverseerSubprocessLog (cptr); } /* interactive user must possess SYSPRV */ if (!UtilHaveSysPrv()) exit (SS$_NOPRIV); /* /wcme=.. activates various actions at the command-line */ if (argc > 1) { if (!strncasecmp(argv[1],"/VERSION",5)) { DisplayVersion (); exit (SS$_NORMAL); } } /* defined in wcme_config.h */ certdir = SSL_DIR; chngdir = WWW_DIR; acctkey = ACCT_KEY; keyfile = PRIV_KEY; /* /api=.. may override this default */ authority = &authc; authority->api = CertManAuthorityApi(); /* build our own argument list (first is the image name) */ nargv[nargc++] = argv[0]; for (cnt = 1; cnt < argc; cnt++) { if (!strncasecmp(argv[cnt],"/API=",4)) { for (cptr = argv[cnt]; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; if (*cptr == '\"') cptr++; zptr = (sptr = api) + sizeof(api)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (*(sptr-1) == '\"') sptr--; *sptr = '\0'; if (api[0]) authority->api = api; } else if (!strncasecmp(argv[cnt],"/CERTIFY=",7)) { /* include the "certify=" string for CertManCLI() */ cptr = argv[cnt] + 1; zptr = (sptr = certman) + sizeof(certman)-32; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } else if (!strncasecmp(argv[cnt],"/CHECK=",7)) { /* include the "check=" string for CertManCLI() */ cptr = argv[cnt] + 1; zptr = (sptr = certman) + sizeof(certman)-32; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } else if (!strncasecmp(argv[cnt],"/FORCE",4)) force = 1; else if (!strncasecmp(argv[cnt],"/LIST",4)) strcpy (certman, "list"); else if (!strncasecmp(argv[cnt],"/MANAGE",4)) strcpy (certman, "manage"); else if (!strncasecmp(argv[cnt],"/NAMES=",4)) { for (cptr = argv[cnt]; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; if (*cptr == '\"') cptr++; zptr = (sptr = names) + sizeof(names)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (*(sptr-1) == '\"') sptr--; *sptr = '\0'; } else if (!strncasecmp(argv[cnt],"/PROCESS=",4)) { pnargc = nargc; for (cptr = argv[cnt]; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; if (*cptr == '\"') cptr++; zptr = (sptr = process) + sizeof(process)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (*(sptr-1) == '\"') sptr--; *sptr = '\0'; } else if (!strncasecmp(argv[cnt],"/REGISTER",4)) newacct = newdkey = 1; else if (!strncasecmp(argv[cnt],"/REVOKE",4)) revocate = 1; else if (!strncasecmp(argv[cnt],"/VERBOSE",4)) verbose = 2; else if (!strncasecmp(argv[cnt],"/WCME=",6)) { cptr = argv[cnt] + 6; zptr = (sptr = certman) + sizeof(certman)-32; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } nargv[nargc++] = argv[cnt]; } if (certman[0]) { #ifdef WCME_DBG_MAIN doddbg("certman |%s|", certman); #endif if (force) strcat (certman, " /force"); if (revocate) strcat (certman, " /revoke"); CertManCli (certman); /* shouldn't */ exit (SS$_BUGCHECK); } if (!pnargc) { /* no process name, allocate space for one */ pnargc = nargc++; nargv[pnargc] = "/process="; } /* ensure there is space for furk() to copy in the process name */ cptr = calloc (1, strlen(nargv[pnargc])+16); strcpy (cptr, nargv[pnargc]); nargv[pnargc] = cptr; if (nargc > 15) errx (EXIT_FAILURE, "exceeded nargc"); /* terminate our argument list */ nargv[nargc] = NULL; #ifdef WCME_DBG_MAIN for (cnt = 0; cnt < nargc; cnt++) dodbg("nargv[%d] |%s|", cnt, nargv[cnt]); #endif UtilSetPrn (process); /* establish the base number of the IPC socket sequence */ cnt = pairofsockets (NULL); acct_fds[0] = cnt++; acct_fds[1] = cnt++; cert_fds[0] = cnt++; cert_fds[1] = cnt++; chng_fds[0] = cnt++; chng_fds[1] = cnt++; dns_fds[0] = cnt++; dns_fds[1] = cnt++; file_fds[0] = cnt++; file_fds[1] = cnt++; key_fds[0] = cnt++; key_fds[1] = cnt++; rvk_fds[0] = cnt++; rvk_fds[1] = cnt++; /* parse out each of the names to be certified */ altsz = 0; alts = calloc (128, sizeof(char*)); for (cptr = names; *cptr; *cptr++ = '\0') { while (*cptr && (isspace(*cptr) || *cptr == ',')) cptr++; if (!*cptr) break; alts[altsz++] = cptr; if (altsz > 127) errx (EXIT_FAILURE, "exceeded altsz"); while (*cptr && !(isspace(*cptr) || *cptr == ',')) cptr++; } #ifdef WCME_DBG_MAIN for (cnt = 0; cnt < altsz; cnt++) dodbg("alts[%d] |%s|", cnt, alts[cnt]); #endif /* without a domain we're just querying the CA */ if (altsz) { /* first site (host name) becomes the domain */ char tmp [256]; sprintf (tmp, "cert_%s", (char*)alts[0]); certfile = UtilOds2FileName (NULL, tmp, ".pem"); sprintf (tmp, "chain_%s", (char*)alts[0]); chainfile = UtilOds2FileName (NULL, tmp, ".pem"); sprintf (tmp, "fullchain_%s", (char*)alts[0]); fullchainfile = UtilOds2FileName (NULL, tmp, ".pem"); } /****************/ /* subprocesses */ /****************/ if (!strcmp (process, "netproc")) { proccomp = COMP_NET; close (key_fds[0]); close (acct_fds[0]); close (chng_fds[0]); close (cert_fds[0]); close (file_fds[0]); close (file_fds[1]); close (dns_fds[0]); close (rvk_fds[0]); c = netproc (key_fds[1], acct_fds[1], chng_fds[1], cert_fds[1], dns_fds[1], rvk_fds[1], newacct, revocate, authority, (const char *const *)alts, altsz); goto out; } if (!strcmp (process, "keyproc")) { proccomp = COMP_KEY; close (cert_fds[0]); close (dns_fds[0]); close (rvk_fds[0]); close (acct_fds[0]); close (chng_fds[0]); close (file_fds[0]); close (file_fds[1]); c = keyproc (key_fds[0], keyfile, (const char **)alts, altsz, newdkey); goto out; } if (!strcmp (process, "acctproc")) { if (!altsz && !newacct) acctkey = NULL; proccomp = COMP_ACCOUNT; close (cert_fds[0]); close (dns_fds[0]); close (rvk_fds[0]); close (chng_fds[0]); close (file_fds[0]); close (file_fds[1]); c = acctproc (acct_fds[0], acctkey, newacct); goto out; } if (!strcmp (process, "chngproc")) { proccomp = COMP_CHALLENGE; close (cert_fds[0]); close (dns_fds[0]); close (rvk_fds[0]); close (file_fds[0]); close (file_fds[1]); c = chngproc (chng_fds[0], chngdir); goto out; } if (!strcmp (process, "certproc")) { proccomp = COMP_CERT; close (dns_fds[0]); close (rvk_fds[0]); close (file_fds[1]); c = certproc (cert_fds[0], file_fds[0]); goto out; } if (!strcmp (process, "fileproc")) { proccomp = COMP_FILE; close(dns_fds[0]); close(rvk_fds[0]); c = fileproc (file_fds[1], certdir, certfile, chainfile, fullchainfile); goto out; } if (!strcmp (process, "dnsproc")) { proccomp = COMP_DNS; close (rvk_fds[0]); c = dnsproc (dns_fds[0]); goto out; } if (!strcmp (process, "revproc")) { proccomp = COMP_REVOKE; c = revokeproc (rvk_fds[0], certdir, /* the reverse of the original */ fullchainfile != NULL ? fullchainfile : certfile, force, revocate, (const char *const *)alts, altsz); goto out; } if (process[0]) errx(EXIT_FAILURE, "%s: unknown process", process); /********/ /* main */ /********/ /* do some quick checks to see if our paths exist */ ne = 0; if (altsz) { /* without a domain we're just querying the CA */ if (access(certdir, R_OK) == -1) { warnx("%s: cert directory must exist", certdir); ne++; } if (!newdkey && access(keyfile, R_OK) == -1) { warnx("%s: domain key file must exist", keyfile); ne++; } else if (newdkey && access(keyfile, R_OK) != -1) { dodbg("%s: domain key exists (not creating)", keyfile); newdkey = 0; } if (!newacct && access(acctkey, R_OK) == -1) { warnx("%s: account key file must exist", acctkey); ne++; } else if (newacct && access(acctkey, R_OK) != -1) { dodbg("%s: account key exists (not creating)", acctkey); newacct = 0; } if (access(chngdir, R_OK) == -1) { warnx("%s: challenge directory must exist", chngdir); ne++; } if (ne > 0) goto out; } pairofsockets(NULL); if (pairofsockets(acct_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (pairofsockets(cert_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (pairofsockets(chng_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (pairofsockets(dns_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (pairofsockets(file_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (pairofsockets(key_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (pairofsockets(rvk_fds) == -1) err(EXIT_FAILURE, "socketpair"); if ((pids[COMP_NET] = furk ("netproc", nargv)) == -1) err(EXIT_FAILURE, "furk() netproc"); close (key_fds[1]); close (acct_fds[1]); close (chng_fds[1]); close (cert_fds[1]); close (dns_fds[1]); close (rvk_fds[1]); if ((pids[COMP_KEY] = furk ("keyproc", nargv)) == -1) err(EXIT_FAILURE, "furk() keyproc"); close (key_fds[0]); if ((pids[COMP_ACCOUNT] = furk ("acctproc", nargv)) == -1) err(EXIT_FAILURE, "furk() acctproc"); close (acct_fds[0]); if ((pids[COMP_CHALLENGE] = furk ("chngproc", nargv)) == -1) err(EXIT_FAILURE, "furk() chngproc"); close (chng_fds[0]); if ((pids[COMP_CERT] = furk ("certproc", nargv)) == -1) err(EXIT_FAILURE, "furk() certproc"); close (cert_fds[0]); close (file_fds[0]); if ((pids[COMP_FILE] = furk ("fileproc", nargv)) == -1) err(EXIT_FAILURE, "furk() fileproc"); close (file_fds[1]); if ((pids[COMP_DNS] = furk ("dnsproc", nargv)) == -1) err(EXIT_FAILURE, "furk() dnsproc"); close (dns_fds[0]); if ((pids[COMP_REVOKE] = furk ("revproc", nargv)) == -1) err(EXIT_FAILURE, "furk() revproc"); close (rvk_fds[0]); /* * Collect our subprocesses. * Require that they both have exited cleanly. */ rc = checkexit(pids[COMP_KEY], COMP_KEY) + checkexit(pids[COMP_CERT], COMP_CERT) + checkexit(pids[COMP_NET], COMP_NET) + checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) + checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) + checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) + checkexit(pids[COMP_DNS], COMP_DNS) + checkexit(pids[COMP_REVOKE], COMP_REVOKE); if (rc == COMP__MAX && c != 2) c = 1; out: doddbg ("exiting %d", c); if (c == 1 || c == 2) exit (SS$_NORMAL); else exit (SS$_OPINCOMPL); #else /* WCME_IDIOM */ /**************/ /* NOT (wcme) */ /**************/ while ((c = getopt(argc, argv, "ADFnrvf:")) != -1) switch (c) { case 'A': popts |= ACME_OPT_NEWACCT; break; case 'D': popts |= ACME_OPT_NEWDKEY; break; case 'F': force = 1; break; case 'f': if ((conffile = strdup(optarg)) == NULL) err(EXIT_FAILURE, "strdup"); break; case 'n': popts |= ACME_OPT_CHECK; break; case 'r': revocate = 1; break; case 'v': verbose = verbose ? 2 : 1; popts |= ACME_OPT_VERBOSE; break; default: goto usage; } if (getuid() != 0) errx(EXIT_FAILURE, "must be run as root"); /* parse config file */ if ((conf = parse_config(conffile, popts)) == NULL) return EXIT_FAILURE; argc -= optind; argv += optind; if (argc != 1) goto usage; if ((domain = domain_find(conf, argv[0])) == NULL) errx(EXIT_FAILURE, "domain %s not found", argv[0]); argc--; argv++; if (domain->cert != NULL) { if ((certdir = dirname(domain->cert)) != NULL) { if ((certdir = strdup(certdir)) == NULL) err(EXIT_FAILURE, "strdup"); } else err(EXIT_FAILURE, "dirname"); } else { /* the parser enforces that at least cert or fullchain is set */ if ((certdir = dirname(domain->fullchain)) != NULL) { if ((certdir = strdup(certdir)) == NULL) err(EXIT_FAILURE, "strdup"); } else err(EXIT_FAILURE, "dirname"); } if (domain->cert != NULL) { if ((certfile = basename(domain->cert)) != NULL) { if ((certfile = strdup(certfile)) == NULL) err(EXIT_FAILURE, "strdup"); } else err(EXIT_FAILURE, "basename"); } if (domain->chain != NULL) { if ((chainfile = basename(domain->chain)) != NULL) { if ((chainfile = strdup(chainfile)) == NULL) err(EXIT_FAILURE, "strdup"); } else err(EXIT_FAILURE, "basename"); } if (domain->fullchain != NULL) { if ((fullchainfile = basename(domain->fullchain)) != NULL) { if ((fullchainfile = strdup(fullchainfile)) == NULL) err(EXIT_FAILURE, "strdup"); } else err(EXIT_FAILURE, "basename"); } if ((auth = domain->auth) == NULL) { /* use the first authority from the config as default XXX */ authority = authority_find0(conf); if (authority == NULL) errx(EXIT_FAILURE, "no authorities configured"); } else { authority = authority_find(conf, auth); if (authority == NULL) errx(EXIT_FAILURE, "authority %s not found", auth); } acctkey = authority->account; if (acctkey == NULL) { /* XXX replace with existance check in parse.y */ err(EXIT_FAILURE, "no account key in config?"); } if ((chngdir = domain->challengedir) == NULL) if ((chngdir = strdup(WWW_DIR)) == NULL) err(EXIT_FAILURE, "strdup"); /* * Do some quick checks to see if our paths exist. * This will be done in the children, but we might as well check * now before the fork. * XXX maybe use conf_check_file() from parse.y */ ne = 0; if (access(certdir, R_OK) == -1) { warnx("%s: cert directory must exist", certdir); ne++; } if (!(popts & ACME_OPT_NEWDKEY) && access(domain->key, R_OK) == -1) { warnx("%s: domain key file must exist", domain->key); ne++; } else if ((popts & ACME_OPT_NEWDKEY) && access(domain->key, R_OK) != -1) { dodbg("%s: domain key exists (not creating)", domain->key); popts &= ~ACME_OPT_NEWDKEY; } if (access(chngdir, R_OK) == -1) { warnx("%s: challenge directory must exist", chngdir); ne++; } if (!(popts & ACME_OPT_NEWACCT) && access(acctkey, R_OK) == -1) { warnx("%s: account key file must exist", acctkey); ne++; } else if ((popts & ACME_OPT_NEWACCT) && access(acctkey, R_OK) != -1) { dodbg("%s: account key exists (not creating)", acctkey); popts &= ~ACME_OPT_NEWACCT; } if (ne > 0) return EXIT_FAILURE; if (popts & ACME_OPT_CHECK) return EXIT_SUCCESS; /* Set the zeroth altname as our domain. */ altsz = domain->altname_count + 1; alts = calloc(altsz, sizeof(char *)); if (alts == NULL) err(EXIT_FAILURE, "calloc"); alts[0] = domain->domain; i = 1; /* XXX get rid of alts[] later */ TAILQ_FOREACH(ac, &domain->altname_list, entry) alts[i++] = ac->domain; /* * Open channels between our components. */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, key_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, acct_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, chng_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, cert_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, file_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, dns_fds) == -1) err(EXIT_FAILURE, "socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, rvk_fds) == -1) err(EXIT_FAILURE, "socketpair"); /* Start with the network-touching process. */ if ((pids[COMP_NET] = fork()) == -1) err(EXIT_FAILURE, "fork"); if (pids[COMP_NET] == 0) { proccomp = COMP_NET; close(key_fds[0]); close(acct_fds[0]); close(chng_fds[0]); close(cert_fds[0]); close(file_fds[0]); close(file_fds[1]); close(dns_fds[0]); close(rvk_fds[0]); c = netproc(key_fds[1], acct_fds[1], chng_fds[1], cert_fds[1], dns_fds[1], rvk_fds[1], (popts & ACME_OPT_NEWACCT), revocate, authority, (const char *const *)alts, altsz); free(alts); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } close(key_fds[1]); close(acct_fds[1]); close(chng_fds[1]); close(cert_fds[1]); close(dns_fds[1]); close(rvk_fds[1]); /* Now the key-touching component. */ if ((pids[COMP_KEY] = fork()) == -1) err(EXIT_FAILURE, "fork"); if (pids[COMP_KEY] == 0) { proccomp = COMP_KEY; close(cert_fds[0]); close(dns_fds[0]); close(rvk_fds[0]); close(acct_fds[0]); close(chng_fds[0]); close(file_fds[0]); close(file_fds[1]); c = keyproc(key_fds[0], keyfile, (const char **)alts, altsz, (popts & ACME_OPT_NEWDKEY)); free(alts); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } close(key_fds[0]); /* The account-touching component. */ if ((pids[COMP_ACCOUNT] = fork()) == -1) err(EXIT_FAILURE, "fork"); if (pids[COMP_ACCOUNT] == 0) { proccomp = COMP_ACCOUNT; free(alts); close(cert_fds[0]); close(dns_fds[0]); close(rvk_fds[0]); close(chng_fds[0]); close(file_fds[0]); close(file_fds[1]); c = acctproc(acct_fds[0], acctkey, (popts & ACME_OPT_NEWACCT)); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } close(acct_fds[0]); /* The challenge-accepting component. */ if ((pids[COMP_CHALLENGE] = fork()) == -1) err(EXIT_FAILURE, "fork"); if (pids[COMP_CHALLENGE] == 0) { proccomp = COMP_CHALLENGE; free(alts); close(cert_fds[0]); close(dns_fds[0]); close(rvk_fds[0]); close(file_fds[0]); close(file_fds[1]); c = chngproc(chng_fds[0], chngdir); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } close(chng_fds[0]); /* The certificate-handling component. */ if ((pids[COMP_CERT] = fork()) == -1) err(EXIT_FAILURE, "fork"); if (pids[COMP_CERT] == 0) { proccomp = COMP_CERT; free(alts); close(dns_fds[0]); close(rvk_fds[0]); close(file_fds[1]); c = certproc(cert_fds[0], file_fds[0]); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } close(cert_fds[0]); close(file_fds[0]); /* The certificate-handling component. */ if ((pids[COMP_FILE] = fork()) == -1) err(EXIT_FAILURE, "fork"); if (pids[COMP_FILE] == 0) { proccomp = COMP_FILE; free(alts); close(dns_fds[0]); close(rvk_fds[0]); c = fileproc(file_fds[1], certdir, certfile, chainfile, fullchainfile); /* * This is different from the other processes in that it * can return 2 if the certificates were updated. */ exit(c > 1 ? 2 : (c ? EXIT_SUCCESS : EXIT_FAILURE)); } close(file_fds[1]); /* The DNS lookup component. */ if ((pids[COMP_DNS] = fork()) == -1) err(EXIT_FAILURE, "fork"); if (pids[COMP_DNS] == 0) { proccomp = COMP_DNS; free(alts); close(rvk_fds[0]); c = dnsproc(dns_fds[0]); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } close(dns_fds[0]); /* The expiration component. */ if ((pids[COMP_REVOKE] = fork()) == -1) err(EXIT_FAILURE, "fork"); if (pids[COMP_REVOKE] == 0) { proccomp = COMP_REVOKE; c = revokeproc(rvk_fds[0], certdir, certfile != NULL ? certfile : fullchainfile, force, revocate, (const char *const *)alts, altsz); free(alts); exit(c ? EXIT_SUCCESS : EXIT_FAILURE); } close(rvk_fds[0]); /* Jail: sandbox, file-system, user. */ if (pledge("stdio", NULL) == -1) err(EXIT_FAILURE, "pledge"); /* * Collect our subprocesses. * Require that they both have exited cleanly. */ rc = checkexit(pids[COMP_KEY], COMP_KEY) + checkexit(pids[COMP_CERT], COMP_CERT) + checkexit(pids[COMP_NET], COMP_NET) + checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) + checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) + checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) + checkexit(pids[COMP_DNS], COMP_DNS) + checkexit(pids[COMP_REVOKE], COMP_REVOKE); free(alts); return rc != COMP__MAX ? EXIT_FAILURE : (c == 2 ? EXIT_SUCCESS : 2); usage: fprintf(stderr, "usage: acme-client [-ADFnrv] [-f configfile] domain\n"); return EXIT_FAILURE; #endif /* WCME_IDIOM */ }