File: [local] / src / usr.sbin / acme-client / main.c (download)
Revision 1.55, Thu May 5 19:51:35 2022 UTC (2 years ago) by florian
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, HEAD Changes since 1.54: +5 -1 lines
Check that the challenge token which is turned into a filename is
base64url encoded.
We have only the challenge directory unveil(2)'ed so funny business
like ../ will not work, but we shouldn't generate garbage filenames
that someone else might trip over either.
Pointed out and diff by Ali Farzanrad (ali_farzanrad AT riseup.net)
OK beck
|
/* $Id: main.c,v 1.55 2022/05/05 19:51:35 florian Exp $ */
/*
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
*
* 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.
*/
#include <sys/socket.h>
#include <ctype.h>
#include <err.h>
#include <libgen.h>
#include <locale.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[])
{
const char **alts = NULL;
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 i, altsz, 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");
/* 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_handle(conf, argv[0])) == NULL)
errx(EXIT_FAILURE, "domain %s not found", argv[0]);
argc--;
argv++;
/*
* The parser enforces that at least cert or fullchain is set.
* XXX Test if cert, chain and fullchain have the same dirname?
*/
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;
/* chain or fullchain can be relative paths according */
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) {
/* 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);
}
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 (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;
/* 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],
revocate, authority,
(const char *const *)alts, altsz);
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], domain->key,
(const char **)alts, altsz,
domain->keytype);
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;
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]);
/* The challenge-accepting component. */
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]);
/* The certificate-handling component. */
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]);
/* The certificate-handling component. */
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);
/*
* 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;
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], domain->cert != NULL ? domain->cert :
domain->fullchain, force, revocate,
(const char *const *)alts, altsz);
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);
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;
}