diff options
author | 2016-09-18 20:18:25 +0000 | |
---|---|---|
committer | 2016-09-18 20:18:25 +0000 | |
commit | 3943d84017a73defaa0dcb984006f23dc840db9c (patch) | |
tree | e5e1b3494df202a8fcf43851e654b9bc1653ce59 | |
parent | Convert imxccm(4) and imxiomuxc(4) to attach using the fdt. Use the "early" (diff) | |
download | wireguard-openbsd-3943d84017a73defaa0dcb984006f23dc840db9c.tar.xz wireguard-openbsd-3943d84017a73defaa0dcb984006f23dc840db9c.zip |
add a config file parser to acme-client (unused at the moment, so that
it can be worked on in the tree).
ok florian@ deraadt@
-rw-r--r-- | etc/Makefile | 15 | ||||
-rw-r--r-- | etc/acme-client.conf | 21 | ||||
-rw-r--r-- | usr.sbin/acme-client/Makefile | 9 | ||||
-rw-r--r-- | usr.sbin/acme-client/acme-client.conf.5 | 163 | ||||
-rw-r--r-- | usr.sbin/acme-client/main.c | 20 | ||||
-rw-r--r-- | usr.sbin/acme-client/parse.h | 76 | ||||
-rw-r--r-- | usr.sbin/acme-client/parse.y | 941 |
7 files changed, 1215 insertions, 30 deletions
diff --git a/etc/Makefile b/etc/Makefile index f3c1dfbb105..bbaa38f44c2 100644 --- a/etc/Makefile +++ b/etc/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.432 2016/09/11 19:44:32 natano Exp $ +# $OpenBSD: Makefile,v 1.433 2016/09/18 20:18:25 benno Exp $ TZDIR= /usr/share/zoneinfo LOCALTIME= Canada/Mountain @@ -30,16 +30,15 @@ kernels: bootblocks ${ALL_KERNELS} # -rw-r--r-- BINOWN= root BINGRP= wheel -MUTABLE=changelist daily etc.${MACHINE}/disktab etc.${MACHINE}/login.conf \ - ftpusers gettytab group ksh.kshrc locate.rc mailer.conf \ - moduli monthly netstart networks newsyslog.conf ntpd.conf \ +MUTABLE=acme-client.conf changelist daily etc.${MACHINE}/disktab \ + etc.${MACHINE}/login.conf ftpusers gettytab group ksh.kshrc locate.rc \ + mailer.conf moduli monthly netstart networks newsyslog.conf ntpd.conf \ pf.os protocols rc rc.conf rpc services shells syslog.conf weekly # -rw-r--r-- -EXAMPLES=chio.conf dhclient.conf dhcpd.conf exports \ - httpd.conf ifstated.conf inetd.conf \ - man.conf mixerctl.conf mrouted.conf \ - ntpd.conf pkg.conf printcap rbootd.conf \ +EXAMPLES=acme-client.conf chio.conf dhclient.conf dhcpd.conf exports \ + httpd.conf ifstated.conf inetd.conf man.conf mixerctl.conf \ + mrouted.conf ntpd.conf pkg.conf printcap rbootd.conf \ remote sensorsd.conf wsconsctl.conf # -rw------- diff --git a/etc/acme-client.conf b/etc/acme-client.conf new file mode 100644 index 00000000000..2a4baa2aebc --- /dev/null +++ b/etc/acme-client.conf @@ -0,0 +1,21 @@ +# +# $OpenBSD: acme-client.conf,v 1.1 2016/09/18 20:18:25 benno Exp $ +# +authority letsencrypt { + agreement url https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf + api url "https://acme-v01.api.letsencrypt.org/directory" + account key /etc/ssl/private/my-acme.key +} + +authority letsencrypt-staging { + agreement url https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf + api url https://acme-staging.api.letsencrypt.org/directory + account key /etc/ssl/private/my-acme-stage.key +} + +domain example.com { + alternative names { secure.example.com } + domain key /etc/ssl/private/example.com.key + domain certificate /etc/ssl/example.com.crt + sign with letsencrypt +} diff --git a/usr.sbin/acme-client/Makefile b/usr.sbin/acme-client/Makefile index 73af613eb46..55e0b0ed99a 100644 --- a/usr.sbin/acme-client/Makefile +++ b/usr.sbin/acme-client/Makefile @@ -1,14 +1,15 @@ -# $OpenBSD: Makefile,v 1.5 2016/09/05 13:31:42 deraadt Exp $ +# $OpenBSD: Makefile,v 1.6 2016/09/18 20:18:25 benno Exp $ PROG= acme-client SRCS= acctproc.c base64.c certproc.c chngproc.c dbg.c dnsproc.c SRCS+= fileproc.c http.c jsmn.c json.c keyproc.c main.c netproc.c -SRCS+= revokeproc.c rsa.c util.c -MAN= acme-client.1 +SRCS+= parse.y revokeproc.c rsa.c util.c + +MAN= acme-client.1 acme-client.conf.5 LDADD= -ltls -lssl -lcrypto DPADD= ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} -CFLAGS+= -W -Wall +CFLAGS+= -W -Wall -I${.CURDIR} CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations CFLAGS+= -Wshadow -Wpointer-arith diff --git a/usr.sbin/acme-client/acme-client.conf.5 b/usr.sbin/acme-client/acme-client.conf.5 new file mode 100644 index 00000000000..9f2c42d829b --- /dev/null +++ b/usr.sbin/acme-client/acme-client.conf.5 @@ -0,0 +1,163 @@ +.\" $OpenBSD: acme-client.conf.5,v 1.1 2016/09/18 20:18:25 benno Exp $ +.\" +.\" Copyright (c) 2005 Esben Norby <norby@openbsd.org> +.\" Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org> +.\" Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> +.\" Copyright (c) 2002 Daniel Hartmeier <dhartmei@openbsd.org> +.\" +.\" 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 AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. +.\" +.Dd $Mdocdate: September 18 2016 $ +.Dt ACME-CLIENT.CONF 5 +.Os +.Sh NAME +.Nm acme-client.conf +.Nd Automatic Certificate Management Environment (ACME) client +configuration file +.Sh DESCRIPTION +.Xr acme-client 8 +implements the Automatic Certificate Management Environment (ACME) protocol. +.Sh SECTIONS +The +.Nm +config file is divided into three main sections. +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Authorities +TLS authorities that can be contacted via ACME. +.It Sy Domains +Domains that the user wants to receive TLS-Certificates for. +.El +.Pp +Additional configuration files can be included with the +.Ic include +keyword, for example: +.Bd -literal -offset indent +include "/etc/acme-client.sub.conf" +.Ed +.Pp +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: +the comment is effective until the end of the entire block. +.Pp +Argument names not beginning with a letter, digit, underscore or '/' +must be quoted. +.Pp +.Sh MACROS +Macros can be defined that will later be expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters. +Macro names may not be reserved words. +Macros are not expanded inside quotes. +.Pp +For example: +.Bd -literal -offset indent +le="letsencrypt" +domain example.com { + sign with $le +} +.Ed +.Pp +.Sh AUTHORITIES +The configured certificate authorities. +.Pp +Each authority section starts with a declaration of the name identifying a +certificate authority. +.Pp +.Bl -tag -width Ds +.It Ic authority Ar name Brq ... +The +.Ar name +is is a string used to reference this certificate authority. +.El +.Pp +It is followed by a block of options that is enclosed in curly brackets: +.Bl -tag -width Ds +.It Ic agreement url Ar url +Specify the +.Ar url +of a contract under which the certificates are supplied by the certificate +authority. +.It Ic api url Ar url +Specify the +.Ar url +under which the ACME API is reachable. +.It Ic account key Ar file +Specify a +.Ar file +used to identify the user of this CA. +.El +.Pp +An example authority block: +.Bd -literal -offset indent +authority letsencrypt { + agreement url https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf + api url "https://acme-v01.api.letsencrypt.org/directory" + account key /etc/ssl/private/my-acme.key +} +.Ed +.Pp +.Sh DOMAINS +The domains that are configured to obtain SSL certificates through ACME. +.Pp +.Bl -tag -width Ds +.It Ic domain Ar name Brq ... +Each domain section begins with the +.Ic domain +keyword followed by the domain name. +.El +.Pp +It is followed by a block of options that is enclosed in curly brackets: +.Bl -tag -width Ds +.It Ic alternative names Brq ... +Specify a list of alternative names the certificate will be valid for. +.It Ic domain key Ar file +The private key file for which the certificate will be obtained. +.It Ic domain certificate Ar file +The filename of the certificate that will be issued. +With +.It Ic sign with Ar authority +the certificate authority (as declared above in the +.Sx AUTHORITIES +section) to use for this domain is selected. +.El +.Pp +An example domain declaration looks like this: +.Bd -literal -offset indent +domain example.com { + alternative names { secure.example.com } + domain key /etc/ssl/private/example.com.key + domain certificate /etc/ssl/example.com.crt + sign with letsencrypt +} +.Ed +.Pp +.Sh FILES +.Bl -tag -width "/etc/acme-client.conf" -compact +.It Pa /etc/acme-client.conf +.Xr acme-client 8 +configuration file +.El +.Sh SEE ALSO +.Xr acme-client 8 , +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 6.1 . diff --git a/usr.sbin/acme-client/main.c b/usr.sbin/acme-client/main.c index 42cfc84114f..7252cdbd606 100644 --- a/usr.sbin/acme-client/main.c +++ b/usr.sbin/acme-client/main.c @@ -1,4 +1,4 @@ -/* $Id: main.c,v 1.13 2016/09/13 17:13:37 deraadt Exp $ */ +/* $Id: main.c,v 1.14 2016/09/18 20:18:25 benno Exp $ */ /* * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -26,6 +26,7 @@ #include <unistd.h> #include "extern.h" +#include "parse.h" #define SSL_DIR "/etc/ssl/acme" #define SSL_PRIV_DIR "/etc/ssl/acme/private" @@ -44,23 +45,6 @@ struct authority authorities[] = { }; /* - * This isn't RFC1035 compliant, but does the bare minimum in making - * sure that we don't get bogus domain names on the command line, which - * might otherwise screw up our directory structure. - * Returns zero on failure, non-zero on success. - */ -static int -domain_valid(const char *cp) -{ - - for (; '\0' != *cp; cp++) - if (!('.' == *cp || '-' == *cp || - '_' == *cp || isalnum((int)*cp))) - return (0); - return (1); -} - -/* * 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. diff --git a/usr.sbin/acme-client/parse.h b/usr.sbin/acme-client/parse.h new file mode 100644 index 00000000000..c727b85a37a --- /dev/null +++ b/usr.sbin/acme-client/parse.h @@ -0,0 +1,76 @@ +/* $OpenBSD: parse.h,v 1.1 2016/09/18 20:18:25 benno Exp $ */ +/* + * Copyright (c) 2016 Sebastian Benoit <benno@openbsd.org> + * + * 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 AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. + */ +#ifndef PARSE_H +#define PARSE_H + +#include <sys/queue.h> + +#define AUTH_MAXLEN 120 /* max lenght of an authority_c name */ +#define DOMAIN_MAXLEN 255 /* max len of a domain name (rfc2181) */ + +/* + * XXX other size limits needed? + * limit all paths to PATH_MAX + */ + +struct authority_c { + LIST_ENTRY(authority_c) entry; + char *name; + char *agreement; + char *api; + char *account; +}; + +struct domain_c { + LIST_ENTRY(domain_c) entry; + LIST_HEAD(, altname_c) altname_list; + char *domain; + char *key; + char *cert; + char *auth; +}; + +struct altname_c { + LIST_ENTRY(altname_c) entry; + char *domain; +}; + +struct keyfile { + LIST_ENTRY(keyfile) entry; + char *name; +}; + +#define ACME_OPT_VERBOSE 0x00000001 + +struct acme_conf { + int opts; + LIST_HEAD(, authority_c) authority_list; + LIST_HEAD(, domain_c) domain_list; + LIST_HEAD(, keyfile) used_key_list; +}; + +struct acme_conf *parse_config(const char *, int); +int cmdline_symset(char *); + +/* use these to find a authority or domain by name */ +struct authority_c *authority_find(struct acme_conf *, char *); +struct authority_c *authority_find0(struct acme_conf *); +struct domain_c *domain_find(struct acme_conf *, char *); + +int domain_valid(const char *); + +#endif /* PARSE_H */ diff --git a/usr.sbin/acme-client/parse.y b/usr.sbin/acme-client/parse.y new file mode 100644 index 00000000000..4b77ccea5f5 --- /dev/null +++ b/usr.sbin/acme-client/parse.y @@ -0,0 +1,941 @@ +/* $OpenBSD: parse.y,v 1.1 2016/09/18 20:18:25 benno Exp $ */ + +/* + * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2016 Sebastian Benoit <benno@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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 AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 <ctype.h> +#include <err.h> +#include <limits.h> +#include <sys/queue.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "parse.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *); +int popfile(void); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +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 *); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +static struct acme_conf *conf; +static struct authority_c *auth; +static struct domain_c *domain; +static int errors = 0; + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; + +%} + +%token AUTHORITY AGREEMENT URL API ACCOUNT +%token DOMAIN ALTERNATIVE NAMES CERT KEY SIGN WITH +%token YES NO +%token INCLUDE +%token ERROR +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.string> string + +%% + +grammar : /* empty */ + | grammar include '\n' + | grammar varset '\n' + | grammar '\n' + | grammar authority '\n' + | grammar domain '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) { + free($1); + free($2); + yyerror("string: asprintf"); + YYERROR; + } + free($1); + free($2); + } + | STRING + ; + +varset : STRING '=' string { + char *s = $1; + if (conf->opts & ACME_OPT_VERBOSE) + printf("%s = \"%s\"\n", $1, $3); + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + errx(EXIT_FAILURE, "cannot store variable"); + free($1); + free($3); + } + ; + +optnl : '\n' optnl + | + ; + +nl : '\n' optnl /* one newline or more */ + ; + +comma : ',' + | /*empty*/ + ; + +authority : AUTHORITY STRING { + char *s; + if ((s = strdup($2)) == NULL) + err(EXIT_FAILURE, "strdup"); + if ((auth = conf_new_authority(conf, s)) == NULL) { + free(s); + yyerror("authority already defined"); + YYERROR; + } + } '{' optnl authorityopts_l '}' { + /* XXX enforce minimum config here */ + auth = NULL; + } + ; + +authorityopts_l : authorityopts_l authorityoptsl nl + | authorityoptsl optnl + ; + +authorityoptsl : AGREEMENT URL STRING { + char *s; + if (auth->agreement != NULL) { + yyerror("duplicate agreement"); + YYERROR; + } + if ((s = strdup($3)) == NULL) + err(EXIT_FAILURE, "strdup"); + auth->agreement = s; + } + | API URL STRING { + char *s; + if (auth->api != NULL) { + yyerror("duplicate api"); + YYERROR; + } + if ((s = strdup($3)) == NULL) + err(EXIT_FAILURE, "strdup"); + auth->api = s; + } + | ACCOUNT KEY STRING { + char *s; + if (auth->account != NULL) { + yyerror("duplicate account"); + YYERROR; + } + if ((s = strdup($3)) == NULL) + err(EXIT_FAILURE, "strdup"); + if (!conf_check_file(s)) { + free(s); + YYERROR; + } + auth->account = s; + } + ; + +domain : DOMAIN STRING { + char *s; + if ((s = strdup($2)) == NULL) + err(EXIT_FAILURE, "strdup"); + if (!domain_valid(s)) { + free(s); + yyerror("%s: bad domain syntax", s); + YYERROR; + } + if ((domain = conf_new_domain(conf, s)) == NULL) { + free(s); + yyerror("domain already defined"); + YYERROR; + } + } '{' optnl domainopts_l '}' { + /* XXX enforce minimum config here */ + domain = NULL; + } + ; + +domainopts_l : domainopts_l domainoptsl nl + | domainoptsl optnl + ; + +domainoptsl : ALTERNATIVE NAMES '{' altname_l '}' + | DOMAIN KEY STRING { + char *s; + if (domain->key != NULL) { + yyerror("duplicate key"); + YYERROR; + } + if ((s = strdup($3)) == NULL) + err(EXIT_FAILURE, "strdup"); + if (((void *)conf_new_keyfile(conf, s)) == NULL) { + free(s); + yyerror("domain key file already used"); + YYERROR; + } + domain->key = s; + } + | DOMAIN CERT STRING { + char *s; + if (domain->cert != NULL) { + yyerror("duplicate cert"); + YYERROR; + } + if ((s = strdup($3)) == NULL) + err(EXIT_FAILURE, "strdup"); + if (s[0] != '/') { + free(s); + yyerror("not an absolute path"); + YYERROR; + } + if (((void *)conf_new_keyfile(conf, s)) == NULL) { + free(s); + yyerror("domain cert file already used"); + YYERROR; + } + domain->cert = s; + } + | SIGN WITH STRING { + char *s; + if (domain->auth != NULL) { + yyerror("duplicate use"); + YYERROR; + } + if ((s = strdup($3)) == NULL) + err(EXIT_FAILURE, "strdup"); + if (authority_find(conf, s) == NULL) { + yyerror("use: unknown authority"); + YYERROR; + } + domain->auth = s; + } + ; + +altname_l : altname comma altname_l + | altname + ; + +altname : STRING { + char *s; + struct altname_c *ac; + if (!domain_valid($1)) { + yyerror("bad domain syntax"); + YYERROR; + } + if ((ac = calloc(1, sizeof(struct altname_c))) == NULL) + err(EXIT_FAILURE, "calloc"); + if ((s = strdup($1)) == NULL) { + free(ac); + err(EXIT_FAILURE, "strdup"); + } + ac->domain = s; + LIST_INSERT_HEAD(&domain->altname_list, ac, entry); + /* + * XXX we could check if altname is duplicate + * or identical to domain->domain + */ + } + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + err(EXIT_FAILURE, "yyerror vasprintf"); + va_end(ap); + fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + {"account", ACCOUNT}, + {"agreement", AGREEMENT}, + {"alternative", ALTERNATIVE}, + {"api", API}, + {"authority", AUTHORITY}, + {"certificate", CERT}, + {"domain", DOMAIN}, + {"include", INCLUDE}, + {"key", KEY}, + {"names", NAMES}, + {"sign", SIGN}, + {"url", URL}, + {"with", WITH}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +u_char *parsebuf; +int parseindex; +u_char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + u_char buf[8096]; + u_char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(EXIT_FAILURE, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '/') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) { + if ((yylval.v.string = strdup(buf)) == NULL) + err(EXIT_FAILURE, "yylex: strdup"); + } + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +struct file * +pushfile(const char *name) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + warn("malloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + warn("strdup"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +struct acme_conf * +parse_config(const char *filename, int opts) +{ + struct sym *sym, *next; + + if ((conf = calloc(1, sizeof(struct acme_conf))) == NULL) + err(EXIT_FAILURE, "parse_config"); + conf->opts = opts; + + if ((file = pushfile(filename)) == NULL) { + free(conf); + return (NULL); + } + topfile = file; + + LIST_INIT(&conf->authority_list); + LIST_INIT(&conf->domain_list); + + yyparse(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entry); + if ((conf->opts & ACME_OPT_VERBOSE) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (errors) { + clear_config(conf); + return (NULL); + } + + return (conf); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(EXIT_FAILURE, "cmdline_symset: malloc"); + + strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +struct authority_c * +conf_new_authority(struct acme_conf *c, char *s) +{ + struct authority_c *a; + + a = authority_find(c, s); + if (a) + return (NULL); + if ((a = calloc(1, sizeof(struct authority_c))) == NULL) + err(EXIT_FAILURE, "calloc"); + LIST_INSERT_HEAD(&c->authority_list, a, entry); + + a->name = s; + return (a); +} + +struct authority_c * +authority_find(struct acme_conf *c, char *s) +{ + struct authority_c *a; + + LIST_FOREACH(a, &c->authority_list, entry) { + if (strncmp(a->name, s, AUTH_MAXLEN) == 0) { + return (a); + } + } + return (NULL); +} + +struct authority_c * +authority_find0(struct acme_conf *c) +{ + struct authority_c *a, *b; + a = b = NULL; + + LIST_FOREACH(a, &c->authority_list, entry) + b = a; + + return (b); +} + +struct domain_c * +conf_new_domain(struct acme_conf *c, char *s) +{ + struct domain_c *d; + + d = domain_find(c, s); + if (d) + return (NULL); + if ((d = calloc(1, sizeof(struct domain_c))) == NULL) + err(EXIT_FAILURE, "calloc"); + LIST_INSERT_HEAD(&c->domain_list, d, entry); + + d->domain = s; + LIST_INIT(&d->altname_list); + + return (d); +} + +struct domain_c * +domain_find(struct acme_conf *c, char *s) +{ + struct domain_c *d; + + LIST_FOREACH(d, &c->domain_list, entry) { + if (strncmp(d->domain, s, DOMAIN_MAXLEN) == 0) { + return (d); + } + } + return (NULL); +} + +struct keyfile * +conf_new_keyfile(struct acme_conf *c, char *s) +{ + struct keyfile *k; + + LIST_FOREACH(k, &c->used_key_list, entry) { + if (strncmp(k->name, s, PATH_MAX) == 0) { + return (NULL); + } + } + + if ((k = calloc(1, sizeof(struct keyfile))) == NULL) + err(EXIT_FAILURE, "calloc"); + LIST_INSERT_HEAD(&c->used_key_list, k, entry); + + k->name = s; + return (k); +} + +void +clear_config(struct acme_conf *xconf) +{ + struct authority_c *a; + struct domain_c *d; + struct altname_c *ac; + + while ((a = LIST_FIRST(&xconf->authority_list)) != NULL) { + LIST_REMOVE(a, entry); + free(a); + } + while ((d = LIST_FIRST(&xconf->domain_list)) != NULL) { + while ((ac = LIST_FIRST(&d->altname_list)) != NULL) { + LIST_REMOVE(ac, entry); + free(ac); + } + LIST_REMOVE(d, entry); + free(d); + } + free(xconf); +} + +/* + * This isn't RFC1035 compliant, but does the bare minimum in making + * sure that we don't get bogus domain names on the command line, which + * might otherwise screw up our directory structure. + * Returns zero on failure, non-zero on success. + */ +int +domain_valid(const char *cp) +{ + + for ( ; '\0' != *cp; cp++) + if (!('.' == *cp || '-' == *cp || + '_' == *cp || isalnum((int)*cp))) + return (0); + return (1); +} + +int +conf_check_file(char *s) +{ + struct stat st; + + if (s[0] != '/') { + warnx("%s: not an absolute path", s); + return (0); + } + if (stat(s, &st)) { + warn("cannot stat %s", s); + return (0); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + warnx("%s: owner not root or current user", s); + return (0); + } + if (st.st_mode & (S_IRWXG | S_IRWXO)) { + warnx("%s: group read/writable or world read/writable", s); + return (0); + } + return (1); +} |