#include <sys/socket.h>
#include <ctype.h>
#include <err.h>
#include <libgen.h>
#include <locale.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "extern.h"
#include "parse.h"
#define WWW_DIR "/var/www/acme"
#define CONF_FILE "/etc/acme-client.conf"
int verbose;
enum comp proccomp;
int
main(int argc, char *argv[])
{
char *certdir = NULL;
char *chngdir = NULL, *auth = NULL;
char *conffile = CONF_FILE;
char *tmps, *tmpsd;
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];
size_t ne;
struct acme_conf *conf = NULL;
struct authority_c *authority = NULL;
struct domain_c *domain = NULL;
struct altname_c *ac;
if (setlocale(LC_CTYPE, "C") == NULL)
errx(1, "setlocale");
while ((c = getopt(argc, argv, "Fnrvf:")) != -1)
switch (c) {
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");
if ((conf = parse_config(conffile, popts)) == NULL)
return EXIT_FAILURE;
argc -= optind;
argv += optind;
if (argc != 1)
goto usage;
if ((domain = domain_find_handle(conf, argv[0])) == NULL)
errx(EXIT_FAILURE, "domain %s not found", argv[0]);
argc--;
argv++;
tmps = domain->cert ? domain->cert : domain->fullchain;
if ((tmps = strdup(tmps)) == NULL)
err(EXIT_FAILURE, "strdup");
if ((tmpsd = dirname(tmps)) == NULL)
err(EXIT_FAILURE, "dirname");
if ((certdir = strdup(tmpsd)) == NULL)
err(EXIT_FAILURE, "strdup");
free(tmps);
tmps = tmpsd = NULL;
if (domain->chain && domain->chain[0] != '/') {
if (asprintf(&tmps, "%s/%s", certdir, domain->chain) == -1)
err(EXIT_FAILURE, "asprintf");
free(domain->chain);
domain->chain = tmps;
tmps = NULL;
}
if (domain->fullchain && domain->fullchain[0] != '/') {
if (asprintf(&tmps, "%s/%s", certdir, domain->fullchain) == -1)
err(EXIT_FAILURE, "asprintf");
free(domain->fullchain);
domain->fullchain = tmps;
tmps = NULL;
}
if ((auth = domain->auth) == NULL) {
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);
}
if ((chngdir = domain->challengedir) == NULL)
if ((chngdir = strdup(WWW_DIR)) == NULL)
err(EXIT_FAILURE, "strdup");
ne = 0;
if (access(certdir, R_OK) == -1) {
warnx("%s: cert directory must exist", certdir);
ne++;
}
if (access(chngdir, R_OK) == -1) {
warnx("%s: challenge directory must exist", chngdir);
ne++;
}
if (ne > 0)
return EXIT_FAILURE;
if (popts & ACME_OPT_CHECK)
return EXIT_SUCCESS;
ac = calloc(1, sizeof(struct altname_c));
if (ac == NULL)
err(EXIT_FAILURE, "calloc");
ac->domain = domain->domain;
ac->idtype = domain->idtype;
TAILQ_INSERT_HEAD(&domain->altname_list, ac, entry);
domain->altname_count++;
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");
signal(SIGPIPE, SIG_IGN);
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],
revocate, authority, domain);
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]);
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], domain);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
close(key_fds[0]);
if ((pids[COMP_ACCOUNT] = fork()) == -1)
err(EXIT_FAILURE, "fork");
if (pids[COMP_ACCOUNT] == 0) {
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], authority->account,
authority->keytype);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
close(acct_fds[0]);
if ((pids[COMP_CHALLENGE] = fork()) == -1)
err(EXIT_FAILURE, "fork");
if (pids[COMP_CHALLENGE] == 0) {
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);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
close(chng_fds[0]);
if ((pids[COMP_CERT] = fork()) == -1)
err(EXIT_FAILURE, "fork");
if (pids[COMP_CERT] == 0) {
proccomp = COMP_CERT;
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]);
if ((pids[COMP_FILE] = fork()) == -1)
err(EXIT_FAILURE, "fork");
if (pids[COMP_FILE] == 0) {
proccomp = COMP_FILE;
close(dns_fds[0]);
close(rvk_fds[0]);
c = fileproc(file_fds[1], certdir, domain->cert, domain->chain,
domain->fullchain);
exit(c > 1 ? 2 : (c ? EXIT_SUCCESS : EXIT_FAILURE));
}
close(file_fds[1]);
if ((pids[COMP_DNS] = fork()) == -1)
err(EXIT_FAILURE, "fork");
if (pids[COMP_DNS] == 0) {
proccomp = COMP_DNS;
close(rvk_fds[0]);
c = dnsproc(dns_fds[0]);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
close(dns_fds[0]);
if ((pids[COMP_REVOKE] = fork()) == -1)
err(EXIT_FAILURE, "fork");
if (pids[COMP_REVOKE] == 0) {
proccomp = COMP_REVOKE;
c = revokeproc(rvk_fds[0], domain->cert != NULL ? domain->cert :
domain->fullchain, force, revocate, domain);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
close(rvk_fds[0]);
if (pledge("stdio", NULL) == -1)
err(EXIT_FAILURE, "pledge");
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);
return rc != COMP__MAX ? EXIT_FAILURE : (c == 2 ? EXIT_SUCCESS : 2);
usage:
fprintf(stderr,
"usage: acme-client [-Fnrv] [-f configfile] handle\n");
return EXIT_FAILURE;
}