start using the configuration file and delete command line arguments:
-a agreement -> agreement url ...
-c certdir -> domain certificate "path"
-f accountkey -> account key "path"
-k domainkey -> domain key "path"
-s authority -> sign with "name"
new argument:
-f configfile
the changes needed to use the new configuration are local to main.c for now.
While the configuration could be passed directly to netproc(), keyproc() etc,
the diff is smaller this way.
This also removes the multidir (-m) mode for now - specify different paths in
each domain {} block instead.
diff --git usr.sbin/acme-client/Makefile usr.sbin/acme-client/Makefile
index 55e0b0e..eae13ed 100644
--- usr.sbin/acme-client/Makefile
+++ usr.sbin/acme-client/Makefile
@@ -13,6 +13,6 @@ CFLAGS+= -W -Wall -I${.CURDIR}
CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
CFLAGS+= -Wmissing-declarations
CFLAGS+= -Wshadow -Wpointer-arith
-CFLAGS+= -Wsign-compare
+CFLAGS+= -Wsign-compare -Wunused
.include <bsd.prog.mk>
diff --git usr.sbin/acme-client/acme-client.1 usr.sbin/acme-client/acme-client.1
index 526c11f..6f38573 100644
--- usr.sbin/acme-client/acme-client.1
+++ usr.sbin/acme-client/acme-client.1
@@ -22,15 +22,10 @@
.Nd ACME client
.Sh SYNOPSIS
.Nm acme-client
-.Op Fl bFmNnrv
-.Op Fl a Ar agreement
+.Op Fl bFNnrv
.Op Fl C Ar challengedir
-.Op Fl c Ar certdir
-.Op Fl f Ar accountkey
-.Op Fl k Ar domainkey
-.Op Fl s Ar authority
+.Op Fl f Ar configfile
.Ar domain
-.Op Ar altnames
.Sh DESCRIPTION
The
.Nm
@@ -39,8 +34,6 @@ Automatic Certificate Management Environment (ACME) client.
.Pp
The options are as follows:
.Bl -tag -width Ds
-.It Fl a Ar agreement
-Use an alternative user agreement URL.
.It Fl b
Back up all certificates in the certificate directory.
This only happens if a remove or replace operation is possible.
@@ -58,67 +51,21 @@ Any given backup uses the same Epoch time for all three
certificates.
If there are no certificates in place, this option does nothing.
.It Fl C Ar challengedir
The directory to register challenges.
-.It Fl c Ar certdir
-The directory to store public certificates.
.It Fl F
Force updating the certificate signature even if it's too soon.
-.It Fl f Ar accountkey
-The account private key.
-This was either made with a previous client or with
-.Fl n .
-.It Fl k Ar domainkey
-The private key for the domain.
-This may also be created with
-.Fl N .
-.It Fl m
-Append
-.Ar domain
-to all default paths except the challenge path
-.Pq i.e. those that are overridden by Fl c , k , f .
-Thus,
-.Ar foo.com
-as the initial domain would make the default domain private key into
-.Pa /etc/ssl/acme/private/foo.com/privkey.pem .
-This is useful in setups with multiple domain sets.
+.It Fl f Ar configfile
+Specify an alternative configuration file.
.It Fl N
Create a new RSA domain key if one does not already exist.
.It Fl n
Create a new RSA account key if one does not already exist.
.It Fl r
Revoke the X509 certificate found in the certificates.
-.It Fl s Ar authority
-ACME
-.Ar authority
-to talk to.
-Currently the following authorities are available:
-.Pp
-.Bl -tag -width "letsencrypt-staging" -compact
-.It Cm letsencrypt
-Let's Encrypt authority
-.It Cm letsencrypt-staging
-Let's Encrypt staging authority
-.El
-.Pp
-The default is
-.Cm letsencrypt .
.It Fl v
Verbose operation.
Specify twice to also trace communication and data transfers.
.It Ar domain
The domain name.
-The only difference between this and
-.Ar altnames
-is that it's put into the certificate's
-.Li CN
-field and it uses the
-.Qq main
-domain when specifying
-.Fl m .
-.It Ar altnames
-Alternative names
-.Pq Dq SAN
-for the domain name.
-The number of SAN entries is limited to 100 or so.
.El
.Pp
Public certificates are by default placed in
@@ -175,7 +122,7 @@ as in the
.Sx Challenges
section:
.Pp
-.Dl # acme-client -vNn foo.com www.foo.com smtp.foo.com
+.Dl # acme-client -vNn www.foo.com
.Pp
A daily
.Xr cron 8
@@ -183,7 +130,7 @@ job can renew the certificates:
.Bd -literal -offset indent
#! /bin/sh
-acme-client foo.com www.foo.com smtp.foo.com
+acme-client www.foo.com
if [ $? -eq 0 ]
then
diff --git usr.sbin/acme-client/chngproc.c usr.sbin/acme-client/chngproc.c
index 4cb7f33..3e931da 100644
--- usr.sbin/acme-client/chngproc.c
+++ usr.sbin/acme-client/chngproc.c
@@ -27,7 +27,7 @@
#include "extern.h"
int
-chngproc(int netsock, const char *root, int remote)
+chngproc(int netsock, const char *root)
{
char *tok = NULL, *th = NULL, *fmt = NULL, **fs = NULL;
size_t i, fsz = 0;
@@ -35,6 +35,7 @@ chngproc(int netsock, const char *root, int remote)
long lval;
enum chngop op;
void *pp;
+ int remote = 0; /* XXX maybe remove */
if (chroot(root) == -1) {
warn("chroot");
diff --git usr.sbin/acme-client/extern.h usr.sbin/acme-client/extern.h
index 55d0153..797c425 100644
--- usr.sbin/acme-client/extern.h
+++ usr.sbin/acme-client/extern.h
@@ -17,6 +17,8 @@
#ifndef EXTERN_H
#define EXTERN_H
+#include "parse.h"
+
#define MAX_SERVERS_DNS 8
#define CERT_PEM "cert.pem"
@@ -145,14 +147,6 @@ enum comm {
COMM__MAX
};
-struct authority {
- char *name;
- char *agreement;
- char *caurl;
-};
-
-extern struct authority authorities[];
-
/*
* This contains the URI and token of an ACME-issued challenge.
* A challenge consists of a token, which we must present on the
@@ -187,15 +181,16 @@ __BEGIN_DECLS
*/
int acctproc(int, const char *, int);
int certproc(int, int);
-int chngproc(int, const char *, int);
+int chngproc(int, const char *);
int dnsproc(int);
int revokeproc(int, const char *,
int, int, const char *const *, size_t);
int fileproc(int, int, const char *);
int keyproc(int, const char *,
const char **, size_t, int);
-int netproc(int, int, int, int, int, int, int, int, int,
- const char *const *, size_t, const char *);
+int netproc(int, int, int, int, int, int, int, int,
+ struct authority_c *, const char *const *,
+ size_t, const char *);
/*
* Debugging functions.
diff --git usr.sbin/acme-client/main.c usr.sbin/acme-client/main.c
index 7252cdb..0d467f1 100644
--- usr.sbin/acme-client/main.c
+++ usr.sbin/acme-client/main.c
@@ -28,127 +28,69 @@
#include "extern.h"
#include "parse.h"
-#define SSL_DIR "/etc/ssl/acme"
-#define SSL_PRIV_DIR "/etc/ssl/acme/private"
-#define ETC_DIR "/etc/acme"
#define WWW_DIR "/var/www/acme"
-#define PRIVKEY_FILE "privkey.pem"
-
-struct authority authorities[] = {
-#define DEFAULT_AUTHORITY 0
- {"letsencrypt",
- "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
- "https://acme-v01.api.letsencrypt.org/directory"},
- {"letsencrypt-staging",
- "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
- "https://acme-staging.api.letsencrypt.org/directory"},
-};
-
-/*
- * Wrap around asprintf(3), which sometimes nullifies the input values,
- * sometimes not, but always returns <0 on error.
- * Returns NULL on failure or the pointer on success.
- */
-static char *
-doasprintf(const char *fmt, ...)
-{
- int c;
- char *cp;
- va_list ap;
-
- va_start(ap, fmt);
- c = vasprintf(&cp, fmt, ap);
- va_end(ap);
- return (c < 0 ? NULL : cp);
-}
+#define CONF_FILE "/etc/acme-client.conf"
int
main(int argc, char *argv[])
{
- const char *domain, *agreement = NULL, **alts = NULL;
+ const char **alts = NULL;
char *certdir = NULL, *acctkey = NULL, *chngdir = NULL;
- char *keyfile = NULL;
+ char *auth = NULL, *agreement = 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 newacct = 0, remote = 0, backup = 0;
- int force = 0, multidir = 0, newkey = 0;
+ int backup = 0;
+ int force = 0;
int c, rc, revocate = 0;
- int authority = DEFAULT_AUTHORITY;
+ int popts = 0;
pid_t pids[COMP__MAX];
extern int verbose;
extern enum comp proccomp;
size_t i, altsz, ne;
- while (-1 != (c = getopt(argc, argv, "bFmnNrs:tva:f:c:C:k:")))
+ struct acme_conf *conf = NULL;
+ struct authority_c *authority = NULL;
+ struct domain_c *domain = NULL;
+ struct altname_c *ac;
+
+ while (-1 != (c = getopt(argc, argv, "bFnNrvf:C:")))
switch (c) {
- case 'a':
- agreement = optarg;
- break;
case 'b':
backup = 1;
break;
- case 'c':
- free(certdir);
- if (NULL == (certdir = strdup(optarg)))
- err(EXIT_FAILURE, "strdup");
- break;
case 'C':
free(chngdir);
if (NULL == (chngdir = strdup(optarg)))
err(EXIT_FAILURE, "strdup");
break;
case 'f':
- free(acctkey);
- if (NULL == (acctkey = strdup(optarg)))
+ if (NULL == (conffile = strdup(optarg)))
err(EXIT_FAILURE, "strdup");
break;
case 'F':
force = 1;
break;
- case 'k':
- free(keyfile);
- if (NULL == (keyfile = strdup(optarg)))
- err(EXIT_FAILURE, "strdup");
- break;
- case 'm':
- multidir = 1;
- break;
case 'n':
- newacct = 1;
+ popts |= ACME_OPT_NEWACCT;
break;
case 'N':
- newkey = 1;
+ popts |= ACME_OPT_NEWDKEY;
break;
case 'r':
revocate = 1;
break;
- case 's':
- authority = -1;
- for (i = 0; i < nitems(authorities); i++) {
- if (strcmp(authorities[i].name, optarg) == 0) {
- authority = i;
- break;
- }
- }
- if (-1 == authority)
- errx(EXIT_FAILURE, "unknown acme authority");
- break;
- case 't':
- /*
- / Undocumented feature.
- * Don't use it.
- */
- remote = 1;
- break;
case 'v':
verbose = verbose ? 2 : 1;
+ popts |= ACME_OPT_VERBOSE;
break;
default:
goto usage;
}
- if (NULL == agreement)
- agreement = authorities[authority].agreement;
+ /* parse config file */
+ if ((conf = parse_config(conffile, popts)) == NULL)
+ exit(EXIT_FAILURE);
argc -= optind;
argv += optind;
@@ -163,9 +105,13 @@ main(int argc, char *argv[])
errx(EXIT_FAILURE, "%s: bad domain syntax", argv[i]);
}
- domain = argv[0];
+ if ((domain = domain_find(conf, argv[0])) == NULL)
+ errx(EXIT_FAILURE, "domain %s not found\n", argv[0]);
+
argc--;
argv++;
+ if (argc != 0)
+ goto usage;
if (getuid() != 0)
errx(EXIT_FAILURE, "must be run as root");
@@ -173,51 +119,54 @@ main(int argc, char *argv[])
/*
* Now we allocate our directories and file paths IFF we haven't
* specified them on the command-line.
- * If we're in "multidir" (-m) mode, we use our initial domain
- * name when specifying the prefixes.
- * Otherwise, we put them all in a known location.
*/
- if (NULL == certdir)
- certdir = multidir ?
- doasprintf(SSL_DIR "/%s", domain) :
- strdup(SSL_DIR);
- if (NULL == keyfile)
- keyfile = multidir ?
- doasprintf(SSL_PRIV_DIR "/%s/"
- PRIVKEY_FILE, domain) :
- strdup(SSL_PRIV_DIR "/" PRIVKEY_FILE);
- if (NULL == acctkey)
- acctkey = multidir ?
- doasprintf(ETC_DIR "/%s/"
- PRIVKEY_FILE, domain) :
- strdup(ETC_DIR "/" PRIVKEY_FILE);
+ certdir = domain->cert;
+
+ 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\n");
+ } else {
+ authority = authority_find(conf, auth);
+ if (authority == NULL)
+ errx(EXIT_FAILURE, "authority %s not found\n", auth);
+ }
+
+ agreement = authority->agreement;
+ acctkey = authority->account;
+
+ if (NULL == acctkey) {
+ /* XXX replace with existance check in parse.y */
+ err(EXIT_FAILURE, "no account key in config?");
+ }
if (NULL == chngdir)
chngdir = strdup(WWW_DIR);
- if (NULL == certdir || NULL == keyfile ||
- NULL == acctkey || NULL == chngdir)
+ if (NULL == chngdir)
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 (-1 == access(certdir, R_OK)) {
- warnx("%s: -c directory must exist", certdir);
+ warnx("%s: cert directory must exist", certdir);
ne++;
}
- if (!newkey && -1 == access(keyfile, R_OK)) {
- warnx("%s: -k file must exist", keyfile);
+ if (!(popts & ACME_OPT_NEWDKEY) && -1 == access(domain->key, R_OK)) {
+ warnx("%s: -k file must exist", domain->key);
ne++;
- } else if (newkey && -1 != access(keyfile, R_OK)) {
- dodbg("%s: domain key exists (not creating)", keyfile);
- newkey = 0;
+ } else if ((popts & ACME_OPT_NEWDKEY) && -1 != access(domain->key,
R_OK)) {
+ dodbg("%s: domain key exists (not creating)", domain->key);
+ popts |= ACME_OPT_NEWDKEY;
}
if (-1 == access(chngdir, R_OK)) {
@@ -225,26 +174,27 @@ main(int argc, char *argv[])
ne++;
}
- if (!newacct && -1 == access(acctkey, R_OK)) {
+ if (!(popts & ACME_OPT_NEWACCT) && -1 == access(acctkey, R_OK)) {
warnx("%s: -f file must exist", acctkey);
ne++;
- } else if (newacct && -1 != access(acctkey, R_OK)) {
+ } else if ((popts & ACME_OPT_NEWACCT) && -1 != access(acctkey, R_OK)) {
dodbg("%s: account key exists (not creating)", acctkey);
- newacct = 0;
+ popts |= ACME_OPT_NEWACCT;
}
if (ne > 0)
exit(EXIT_FAILURE);
/* Set the zeroth altname as our domain. */
-
- altsz = argc + 1;
+ altsz = domain->altname_count + 1;
alts = calloc(altsz, sizeof(char *));
if (NULL == alts)
err(EXIT_FAILURE, "calloc");
- alts[0] = domain;
- for (i = 0; i < (size_t)argc; i++)
- alts[i + 1] = argv[i];
+ alts[0] = domain->domain;
+ i = 1;
+ /* XXX get rid of alts[] later */
+ LIST_FOREACH(ac, &domain->altname_list, entry)
+ alts[i++] = ac->domain;
/*
* Open channels between our components.
@@ -283,7 +233,7 @@ main(int argc, char *argv[])
c = netproc(key_fds[1], acct_fds[1],
chng_fds[1], cert_fds[1],
dns_fds[1], rvk_fds[1],
- newacct, revocate, authority,
+ (popts & ACME_OPT_NEWACCT), revocate, authority,
(const char *const *)alts, altsz,
agreement);
free(alts);
@@ -311,8 +261,8 @@ main(int argc, char *argv[])
close(chng_fds[0]);
close(file_fds[0]);
close(file_fds[1]);
- c = keyproc(key_fds[0], keyfile,
- (const char **)alts, altsz, newkey);
+ c = keyproc(key_fds[0], domain->key,
+ (const char **)alts, altsz, (popts & ACME_OPT_NEWDKEY));
free(alts);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
@@ -333,7 +283,7 @@ main(int argc, char *argv[])
close(chng_fds[0]);
close(file_fds[0]);
close(file_fds[1]);
- c = acctproc(acct_fds[0], acctkey, newacct);
+ c = acctproc(acct_fds[0], acctkey, (popts & ACME_OPT_NEWACCT));
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
@@ -352,7 +302,7 @@ main(int argc, char *argv[])
close(rvk_fds[0]);
close(file_fds[0]);
close(file_fds[1]);
- c = chngproc(chng_fds[0], chngdir, remote);
+ c = chngproc(chng_fds[0], chngdir);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
@@ -447,8 +397,6 @@ main(int argc, char *argv[])
checkexit(pids[COMP_DNS], COMP_DNS) +
checkexit(pids[COMP_REVOKE], COMP_REVOKE);
- free(certdir);
- free(keyfile);
free(acctkey);
free(chngdir);
free(alts);
@@ -456,11 +404,8 @@ main(int argc, char *argv[])
(2 == c ? EXIT_SUCCESS : 2));
usage:
fprintf(stderr,
- "usage: acme-client [-bFmnNrv] [-a agreement] [-C challengedir]\n"
- " [-c certdir] [-f accountkey] [-k domainkey]\n"
- " [-s authority] domain [altnames...]\n");
- free(certdir);
- free(keyfile);
+ "usage: acme-client [-bFnNrv] [-C challengedir]\n"
+ " [-f file] domain\n");
free(acctkey);
free(chngdir);
return (EXIT_FAILURE);
diff --git usr.sbin/acme-client/netproc.c usr.sbin/acme-client/netproc.c
index 24ab4bf..9e5a208 100644
--- usr.sbin/acme-client/netproc.c
+++ usr.sbin/acme-client/netproc.c
@@ -25,6 +25,7 @@
#include "http.h"
#include "extern.h"
+#include "parse.h"
#define RETRY_DELAY 5
#define RETRY_MAX 10
@@ -565,8 +566,8 @@ dofullchain(struct conn *c, const char *addr)
*/
int
netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
- int newacct, int revocate, int authority, const char *const *alts,
- size_t altsz, const char *agreement)
+ int newacct, int revocate, struct authority_c *authority,
+ const char *const *alts,size_t altsz, const char *agreement)
{
int rc = 0;
size_t i;
@@ -643,7 +644,7 @@ netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int
rfd,
c.dfd = dfd;
c.fd = afd;
- c.na = authorities[authority].caurl;
+ c.na = authority->api;
/*
* Look up the domain of the ACME server.
diff --git usr.sbin/acme-client/parse.h usr.sbin/acme-client/parse.h
index c727b85..008a29b 100644
--- usr.sbin/acme-client/parse.h
+++ usr.sbin/acme-client/parse.h
@@ -38,6 +38,7 @@ struct authority_c {
struct domain_c {
LIST_ENTRY(domain_c) entry;
LIST_HEAD(, altname_c) altname_list;
+ int altname_count;
char *domain;
char *key;
char *cert;
@@ -55,6 +56,8 @@ struct keyfile {
};
#define ACME_OPT_VERBOSE 0x00000001
+#define ACME_OPT_NEWACCT 0x00000002
+#define ACME_OPT_NEWDKEY 0x00000004
struct acme_conf {
int opts;
diff --git usr.sbin/acme-client/parse.y usr.sbin/acme-client/parse.y
index f562853..ef3a88b 100644
--- usr.sbin/acme-client/parse.y
+++ usr.sbin/acme-client/parse.y
@@ -63,7 +63,7 @@ struct authority_c *conf_new_authority(struct acme_conf *,
char *);
struct domain_c *conf_new_domain(struct acme_conf *, char *);
struct keyfile *conf_new_keyfile(struct acme_conf *, char *);
void clear_config(struct acme_conf *xconf);
-int conf_check_file(char *);
+int conf_check_file(char *, int);
TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
struct sym {
@@ -215,10 +215,6 @@ authorityoptsl : AGREEMENT URL STRING {
}
if ((s = strdup($3)) == NULL)
err(EXIT_FAILURE, "strdup");
- if (!conf_check_file(s)) {
- free(s);
- YYERROR;
- }
auth->account = s;
}
;
@@ -256,6 +252,11 @@ domainoptsl : ALTERNATIVE NAMES '{' altname_l '}'
}
if ((s = strdup($3)) == NULL)
err(EXIT_FAILURE, "strdup");
+ if (!conf_check_file(s,
+ (conf->opts & ACME_OPT_NEWDKEY))) {
+ free(s);
+ YYERROR;
+ }
if (((void *)conf_new_keyfile(conf, s)) == NULL) {
free(s);
yyerror("domain key file already used");
@@ -264,7 +265,7 @@ domainoptsl : ALTERNATIVE NAMES '{' altname_l '}'
domain->key = s;
}
| DOMAIN CERT STRING {
- char *s;
+ char *s;
if (domain->cert != NULL) {
yyerror("duplicate cert");
YYERROR;
@@ -318,6 +319,7 @@ altname : STRING {
}
ac->domain = s;
LIST_INSERT_HEAD(&domain->altname_list, ac, entry);
+ domain->altname_count++;
/*
* XXX we could check if altname is duplicate
* or identical to domain->domain
@@ -602,7 +604,7 @@ nodigits:
x != '!' && x != '=' && x != '#' && \
x != ','))
- if (isalnum(c) || c == ':' || c == '_' || c == '/') {
+ if (isalnum(c) || c == ':' || c == '_') {
do {
*p++ = c;
if ((unsigned)(p-buf) >= sizeof(buf)) {
@@ -917,7 +919,7 @@ domain_valid(const char *cp)
}
int
-conf_check_file(char *s)
+conf_check_file(char *s, int dontstat)
{
struct stat st;
@@ -925,6 +927,8 @@ conf_check_file(char *s)
warnx("%s: not an absolute path", s);
return (0);
}
+ if (dontstat)
+ return (1);
if (stat(s, &st)) {
warn("cannot stat %s", s);
return (0);