No objection in principle.. although since some of us depend on this we might either need warning and/or a small period of overlap where the old stuff works and then we can move to the new stuff without things blowing up.
On Sun, Jan 1, 2017 at 1:59 PM, Sebastian Benoit <[email protected]> wrote: > 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); > >
