aboutsummaryrefslogtreecommitdiff
path: root/httpd/parse.y
diff options
context:
space:
mode:
Diffstat (limited to 'httpd/parse.y')
-rw-r--r--httpd/parse.y2062
1 files changed, 2062 insertions, 0 deletions
diff --git a/httpd/parse.y b/httpd/parse.y
new file mode 100644
index 0000000..7e48aba
--- /dev/null
+++ b/httpd/parse.y
@@ -0,0 +1,2062 @@
+/* $OpenBSD: parse.y,v 1.65 2015/02/12 04:40:23 jsing Exp $ */
+
+/*
+ * Copyright (c) 2007 - 2015 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/ioctl.h>
+#include <sys/sockio.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <ifaddrs.h>
+#include <syslog.h>
+
+#include "httpd.h"
+#include "http.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);
+int popfile(void);
+int check_file_secrecy(int, const char *);
+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);
+
+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 *);
+
+struct httpd *conf = NULL;
+static int errors = 0;
+static int loadcfg = 0;
+uint32_t last_server_id = 0;
+uint32_t last_auth_id = 0;
+
+static struct server *srv = NULL, *parentsrv = NULL;
+static struct server_config *srv_conf = NULL;
+struct serverlist servers;
+struct media_type media;
+
+struct address *host_v4(const char *);
+struct address *host_v6(const char *);
+int host_dns(const char *, struct addresslist *,
+ int, struct portrange *, const char *, int);
+int host_if(const char *, struct addresslist *,
+ int, struct portrange *, const char *, int);
+int host(const char *, struct addresslist *,
+ int, struct portrange *, const char *, int);
+void host_free(struct addresslist *);
+struct server *server_inherit(struct server *, const char *,
+ struct server_config *);
+int getservice(char *);
+int is_if_in_group(const char *, const char *);
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ struct timeval tv;
+ struct portrange port;
+ struct auth auth;
+ struct {
+ struct sockaddr_storage ss;
+ char name[HOST_NAME_MAX+1];
+ } addr;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON
+%token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LISTEN
+%token LOCATION LOG LOGDIR MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS
+%token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT
+%token TLS TYPES
+%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.port> port
+%type <v.number> opttls
+%type <v.tv> timeout
+%type <v.string> numberstring optstring
+%type <v.auth> authopts
+
+%%
+
+grammar : /* empty */
+ | grammar include '\n'
+ | grammar '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar server '\n'
+ | grammar types '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 0)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ lungetc('\n');
+ }
+ ;
+
+varset : STRING '=' STRING {
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+opttls : /*empty*/ { $$ = 0; }
+ | TLS { $$ = 1; }
+ ;
+
+main : PREFORK NUMBER {
+ if (loadcfg)
+ break;
+ if ($2 <= 0 || $2 > SERVER_MAXPROC) {
+ yyerror("invalid number of preforked "
+ "servers: %lld", $2);
+ YYERROR;
+ }
+ conf->sc_prefork_server = $2;
+ }
+ | CHROOT STRING {
+ conf->sc_chroot = $2;
+ }
+ | LOGDIR STRING {
+ conf->sc_logdir = $2;
+ }
+ ;
+
+server : SERVER STRING {
+ struct server *s;
+
+ if (!loadcfg) {
+ free($2);
+ YYACCEPT;
+ }
+
+ if ((s = calloc(1, sizeof (*s))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(s->srv_conf.name, $2,
+ sizeof(s->srv_conf.name)) >=
+ sizeof(s->srv_conf.name)) {
+ yyerror("server name truncated");
+ free($2);
+ free(s);
+ YYERROR;
+ }
+ free($2);
+
+ strlcpy(s->srv_conf.root, HTTPD_DOCROOT,
+ sizeof(s->srv_conf.root));
+ strlcpy(s->srv_conf.index, HTTPD_INDEX,
+ sizeof(s->srv_conf.index));
+ strlcpy(s->srv_conf.accesslog, HTTPD_ACCESS_LOG,
+ sizeof(s->srv_conf.accesslog));
+ strlcpy(s->srv_conf.errorlog, HTTPD_ERROR_LOG,
+ sizeof(s->srv_conf.errorlog));
+ s->srv_conf.id = ++last_server_id;
+ s->srv_conf.parent_id = s->srv_conf.id;
+ s->srv_s = -1;
+ s->srv_conf.timeout.tv_sec = SERVER_TIMEOUT;
+ s->srv_conf.maxrequests = SERVER_MAXREQUESTS;
+ s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY;
+ s->srv_conf.flags |= SRVFLAG_LOG;
+ s->srv_conf.logformat = LOG_FORMAT_COMMON;
+ s->srv_conf.tls_protocols = TLS_PROTOCOLS_ALL;
+ if ((s->srv_conf.tls_cert_file =
+ strdup(HTTPD_TLS_CERT)) == NULL)
+ fatal("out of memory");
+ if ((s->srv_conf.tls_key_file =
+ strdup(HTTPD_TLS_KEY)) == NULL)
+ fatal("out of memory");
+ strlcpy(s->srv_conf.tls_ciphers,
+ HTTPD_TLS_CIPHERS,
+ sizeof(s->srv_conf.tls_ciphers));
+ strlcpy(s->srv_conf.tls_dhe_params,
+ HTTPD_TLS_DHE_PARAMS,
+ sizeof(s->srv_conf.tls_dhe_params));
+ strlcpy(s->srv_conf.tls_ecdhe_curve,
+ HTTPD_TLS_ECDHE_CURVE,
+ sizeof(s->srv_conf.tls_ecdhe_curve));
+
+ if (last_server_id == INT_MAX) {
+ yyerror("too many servers defined");
+ free(s);
+ YYERROR;
+ }
+ srv = s;
+ srv_conf = &srv->srv_conf;
+
+ SPLAY_INIT(&srv->srv_clients);
+ TAILQ_INIT(&srv->srv_hosts);
+
+ TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry);
+ } '{' optnl serveropts_l '}' {
+ struct server *s = NULL, *sn;
+ struct server_config *a, *b;
+
+ srv_conf = &srv->srv_conf;
+
+ TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+ if ((s->srv_conf.flags &
+ SRVFLAG_LOCATION) == 0 &&
+ strcmp(s->srv_conf.name,
+ srv->srv_conf.name) == 0 &&
+ s->srv_conf.port == srv->srv_conf.port &&
+ sockaddr_cmp(
+ (struct sockaddr *)&s->srv_conf.ss,
+ (struct sockaddr *)&srv->srv_conf.ss,
+ s->srv_conf.prefixlen) == 0)
+ break;
+ }
+ if (s != NULL) {
+ yyerror("server \"%s\" defined twice",
+ srv->srv_conf.name);
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYABORT;
+ }
+
+ if (srv->srv_conf.ss.ss_family == AF_UNSPEC) {
+ yyerror("listen address not specified");
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYERROR;
+ }
+
+ if ((srv->srv_conf.flags & SRVFLAG_TLS) &&
+ srv->srv_conf.tls_protocols == 0) {
+ yyerror("no TLS protocols");
+ free(srv);
+ YYERROR;
+ }
+
+ if (server_tls_load_keypair(srv) == -1) {
+ yyerror("failed to load public/private keys "
+ "for server %s", srv->srv_conf.name);
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYERROR;
+ }
+
+ DPRINTF("adding server \"%s[%u]\"",
+ srv->srv_conf.name, srv->srv_conf.id);
+
+ TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
+
+ /*
+ * Add aliases and additional listen addresses as
+ * individual servers.
+ */
+ TAILQ_FOREACH(a, &srv->srv_hosts, entry) {
+ /* listen address */
+ if (a->ss.ss_family == AF_UNSPEC)
+ continue;
+ TAILQ_FOREACH(b, &srv->srv_hosts, entry) {
+ /* alias name */
+ if (*b->name == '\0' ||
+ (b == &srv->srv_conf && b == a))
+ continue;
+
+ if ((sn = server_inherit(srv,
+ b->name, a)) == NULL) {
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYABORT;
+ }
+
+ DPRINTF("adding server \"%s[%u]\"",
+ sn->srv_conf.name, sn->srv_conf.id);
+
+ TAILQ_INSERT_TAIL(conf->sc_servers,
+ sn, srv_entry);
+ }
+ }
+
+ /* Remove temporary aliases */
+ TAILQ_FOREACH_SAFE(a, &srv->srv_hosts, entry, b) {
+ TAILQ_REMOVE(&srv->srv_hosts, a, entry);
+ if (a == &srv->srv_conf)
+ continue;
+ serverconfig_free(a);
+ free(a);
+ }
+
+ srv = NULL;
+ srv_conf = NULL;
+ }
+ ;
+
+serveropts_l : serveropts_l serveroptsl nl
+ | serveroptsl optnl
+ ;
+
+serveroptsl : LISTEN ON STRING opttls port {
+ struct addresslist al;
+ struct address *h;
+ struct server_config *s_conf, *alias = NULL;
+
+ if (parentsrv != NULL) {
+ yyerror("listen %s inside location", $3);
+ free($3);
+ YYERROR;
+ }
+
+ if (srv->srv_conf.ss.ss_family != AF_UNSPEC) {
+ if ((alias = calloc(1,
+ sizeof(*alias))) == NULL)
+ fatal("out of memory");
+
+ /* Add as an alias */
+ s_conf = alias;
+ } else
+ s_conf = &srv->srv_conf;
+
+ TAILQ_INIT(&al);
+ if (host($3, &al, 1, &$5, NULL, -1) <= 0) {
+ yyerror("invalid listen ip: %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ h = TAILQ_FIRST(&al);
+ memcpy(&s_conf->ss, &h->ss, sizeof(s_conf->ss));
+ s_conf->port = h->port.val[0];
+ s_conf->prefixlen = h->prefixlen;
+ host_free(&al);
+
+ if ($4) {
+ s_conf->flags |= SRVFLAG_TLS;
+ }
+
+ if (alias != NULL) {
+ TAILQ_INSERT_TAIL(&srv->srv_hosts,
+ alias, entry);
+ }
+ }
+ | ALIAS STRING {
+ struct server_config *alias;
+
+ if (parentsrv != NULL) {
+ yyerror("alias inside location");
+ free($2);
+ YYERROR;
+ }
+
+ if ((alias = calloc(1, sizeof(*alias))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(alias->name, $2, sizeof(alias->name)) >=
+ sizeof(alias->name)) {
+ yyerror("server alias truncated");
+ free($2);
+ free(alias);
+ YYERROR;
+ }
+ free($2);
+
+ TAILQ_INSERT_TAIL(&srv->srv_hosts, alias, entry);
+ }
+ | tcpip {
+ if (parentsrv != NULL) {
+ yyerror("tcp flags inside location");
+ YYERROR;
+ }
+ }
+ | connection {
+ if (parentsrv != NULL) {
+ yyerror("connection options inside location");
+ YYERROR;
+ }
+ }
+ | tls {
+ if (parentsrv != NULL) {
+ yyerror("tls configuration inside location");
+ YYERROR;
+ }
+ }
+ | root
+ | directory
+ | logformat
+ | fastcgi
+ | authenticate
+ | filter
+ | LOCATION STRING {
+ struct server *s;
+
+ if (srv->srv_conf.ss.ss_family == AF_UNSPEC) {
+ yyerror("listen address not specified");
+ free($2);
+ YYERROR;
+ }
+
+ if (parentsrv != NULL) {
+ yyerror("location %s inside location", $2);
+ free($2);
+ YYERROR;
+ }
+
+ if (!loadcfg) {
+ free($2);
+ YYACCEPT;
+ }
+
+ if ((s = calloc(1, sizeof (*s))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(s->srv_conf.location, $2,
+ sizeof(s->srv_conf.location)) >=
+ sizeof(s->srv_conf.location)) {
+ yyerror("server location truncated");
+ free($2);
+ free(s);
+ YYERROR;
+ }
+ free($2);
+
+ if (strlcpy(s->srv_conf.name, srv->srv_conf.name,
+ sizeof(s->srv_conf.name)) >=
+ sizeof(s->srv_conf.name)) {
+ yyerror("server name truncated");
+ free(s);
+ YYERROR;
+ }
+
+ s->srv_conf.id = ++last_server_id;
+ /* A location entry uses the parent id */
+ s->srv_conf.parent_id = srv->srv_conf.id;
+ s->srv_conf.flags = SRVFLAG_LOCATION;
+ s->srv_s = -1;
+ memcpy(&s->srv_conf.ss, &srv->srv_conf.ss,
+ sizeof(s->srv_conf.ss));
+ s->srv_conf.port = srv->srv_conf.port;
+ s->srv_conf.prefixlen = srv->srv_conf.prefixlen;
+
+ if (last_server_id == INT_MAX) {
+ yyerror("too many servers/locations defined");
+ free(s);
+ YYERROR;
+ }
+ parentsrv = srv;
+ srv = s;
+ srv_conf = &srv->srv_conf;
+ SPLAY_INIT(&srv->srv_clients);
+ } '{' optnl serveropts_l '}' {
+ struct server *s = NULL;
+
+ TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+ if ((s->srv_conf.flags & SRVFLAG_LOCATION) &&
+ s->srv_conf.id == srv_conf->id &&
+ strcmp(s->srv_conf.location,
+ srv_conf->location) == 0)
+ break;
+ }
+ if (s != NULL) {
+ yyerror("location \"%s\" defined twice",
+ srv->srv_conf.location);
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYABORT;
+ }
+
+ DPRINTF("adding location \"%s\" for \"%s[%u]\"",
+ srv->srv_conf.location,
+ srv->srv_conf.name, srv->srv_conf.id);
+
+ TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
+
+ srv = parentsrv;
+ srv_conf = &parentsrv->srv_conf;
+ parentsrv = NULL;
+ }
+ | include
+ ;
+
+fastcgi : NO FCGI {
+ srv_conf->flags &= ~SRVFLAG_FCGI;
+ srv_conf->flags |= SRVFLAG_NO_FCGI;
+ }
+ | FCGI {
+ srv_conf->flags &= ~SRVFLAG_NO_FCGI;
+ srv_conf->flags |= SRVFLAG_FCGI;
+ }
+ | FCGI {
+ srv_conf->flags &= ~SRVFLAG_NO_FCGI;
+ srv_conf->flags |= SRVFLAG_FCGI;
+ } '{' optnl fcgiflags_l '}'
+ | FCGI {
+ srv_conf->flags &= ~SRVFLAG_NO_FCGI;
+ srv_conf->flags |= SRVFLAG_FCGI;
+ } fcgiflags
+ ;
+
+fcgiflags_l : fcgiflags optcommanl fcgiflags_l
+ | fcgiflags optnl
+ ;
+
+fcgiflags : SOCKET STRING {
+ if (strlcpy(srv_conf->socket, $2,
+ sizeof(srv_conf->socket)) >=
+ sizeof(srv_conf->socket)) {
+ yyerror("fastcgi socket too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ srv_conf->flags |= SRVFLAG_SOCKET;
+ }
+ ;
+
+connection : CONNECTION '{' optnl conflags_l '}'
+ | CONNECTION conflags
+ ;
+
+conflags_l : conflags optcommanl conflags_l
+ | conflags optnl
+ ;
+
+conflags : TIMEOUT timeout {
+ memcpy(&srv_conf->timeout, &$2,
+ sizeof(struct timeval));
+ }
+ | MAXIMUM REQUESTS NUMBER {
+ srv_conf->maxrequests = $3;
+ }
+ | MAXIMUM REQUEST BODY NUMBER {
+ srv_conf->maxrequestbody = $4;
+ }
+ ;
+
+tls : TLS '{' optnl tlsopts_l '}'
+ | TLS tlsopts
+ ;
+
+tlsopts_l : tlsopts optcommanl tlsopts_l
+ | tlsopts optnl
+ ;
+
+tlsopts : CERTIFICATE STRING {
+ free(srv_conf->tls_cert_file);
+ if ((srv_conf->tls_cert_file = strdup($2)) == NULL)
+ fatal("out of memory");
+ free($2);
+ }
+ | KEY STRING {
+ free(srv_conf->tls_key_file);
+ if ((srv_conf->tls_key_file = strdup($2)) == NULL)
+ fatal("out of memory");
+ free($2);
+ }
+ | CIPHERS STRING {
+ if (strlcpy(srv_conf->tls_ciphers, $2,
+ sizeof(srv_conf->tls_ciphers)) >=
+ sizeof(srv_conf->tls_ciphers)) {
+ yyerror("ciphers too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | DHE STRING {
+ if (strlcpy(srv_conf->tls_dhe_params, $2,
+ sizeof(srv_conf->tls_dhe_params)) >=
+ sizeof(srv_conf->tls_dhe_params)) {
+ yyerror("dhe too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | ECDHE STRING {
+ if (strlcpy(srv_conf->tls_ecdhe_curve, $2,
+ sizeof(srv_conf->tls_ecdhe_curve)) >=
+ sizeof(srv_conf->tls_ecdhe_curve)) {
+ yyerror("ecdhe too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | PROTOCOLS STRING {
+ if (tls_config_parse_protocols(
+ &srv_conf->tls_protocols, $2) != 0) {
+ yyerror("invalid TLS protocols");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+root : ROOT rootflags
+ | ROOT '{' optnl rootflags_l '}'
+ ;
+
+rootflags_l : rootflags optcommanl rootflags_l
+ | rootflags optnl
+ ;
+
+rootflags : STRING {
+ if (strlcpy(srv->srv_conf.root, $1,
+ sizeof(srv->srv_conf.root)) >=
+ sizeof(srv->srv_conf.root)) {
+ yyerror("document root too long");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ srv->srv_conf.flags |= SRVFLAG_ROOT;
+ }
+ | STRIP NUMBER {
+ if ($2 < 0 || $2 > INT_MAX) {
+ yyerror("invalid strip number");
+ YYERROR;
+ }
+ srv->srv_conf.strip = $2;
+ }
+ ;
+
+authenticate : NO AUTHENTICATE {
+ srv->srv_conf.flags |= SRVFLAG_NO_AUTH;
+ }
+ | AUTHENTICATE authopts {
+ struct auth *auth;
+
+ if ((auth = auth_add(conf->sc_auth, &$2)) == NULL) {
+ yyerror("failed to add auth");
+ YYERROR;
+ }
+
+ if (auth->auth_id == 0) {
+ /* New htpasswd, get new Id */
+ auth->auth_id = ++last_auth_id;
+ if (last_auth_id == INT_MAX) {
+ yyerror("too many auth ids defined");
+ auth_free(conf->sc_auth, auth);
+ YYERROR;
+ }
+ }
+
+ srv->srv_conf.auth_id = auth->auth_id;
+ srv->srv_conf.flags |= SRVFLAG_AUTH;
+ }
+ ;
+
+authopts : STRING WITH STRING {
+ if (strlcpy(srv->srv_conf.auth_realm, $1,
+ sizeof(srv->srv_conf.auth_realm)) >=
+ sizeof(srv->srv_conf.auth_realm)) {
+ yyerror("basic auth realm name too long");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ if (strlcpy($$.auth_htpasswd, $3,
+ sizeof($$.auth_htpasswd)) >=
+ sizeof($$.auth_htpasswd)) {
+ yyerror("password file name too long");
+ free($3);
+ YYERROR;
+ }
+ free($3);
+
+ }
+ | WITH STRING {
+ if (strlcpy($$.auth_htpasswd, $2,
+ sizeof($$.auth_htpasswd)) >=
+ sizeof($$.auth_htpasswd)) {
+ yyerror("password file name too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ };
+
+directory : DIRECTORY dirflags
+ | DIRECTORY '{' optnl dirflags_l '}'
+ ;
+
+dirflags_l : dirflags optcommanl dirflags_l
+ | dirflags optnl
+ ;
+
+dirflags : INDEX STRING {
+ if (strlcpy(srv_conf->index, $2,
+ sizeof(srv_conf->index)) >=
+ sizeof(srv_conf->index)) {
+ yyerror("index file too long");
+ free($2);
+ YYERROR;
+ }
+ srv_conf->flags &= ~SRVFLAG_NO_INDEX;
+ srv_conf->flags |= SRVFLAG_INDEX;
+ free($2);
+ }
+ | NO INDEX {
+ srv_conf->flags &= ~SRVFLAG_INDEX;
+ srv_conf->flags |= SRVFLAG_NO_INDEX;
+ }
+ | AUTO INDEX {
+ srv_conf->flags &= ~SRVFLAG_NO_AUTO_INDEX;
+ srv_conf->flags |= SRVFLAG_AUTO_INDEX;
+ }
+ | NO AUTO INDEX {
+ srv_conf->flags &= ~SRVFLAG_AUTO_INDEX;
+ srv_conf->flags |= SRVFLAG_NO_AUTO_INDEX;
+ }
+ ;
+
+
+logformat : LOG logflags
+ | LOG '{' optnl logflags_l '}'
+ | NO LOG {
+ srv_conf->flags &= ~SRVFLAG_LOG;
+ srv_conf->flags |= SRVFLAG_NO_LOG;
+ }
+ ;
+
+logflags_l : logflags optcommanl logflags_l
+ | logflags optnl
+ ;
+
+logflags : STYLE logstyle
+ | SYSLOG {
+ srv_conf->flags &= ~SRVFLAG_NO_SYSLOG;
+ srv_conf->flags |= SRVFLAG_SYSLOG;
+ }
+ | NO SYSLOG {
+ srv_conf->flags &= ~SRVFLAG_SYSLOG;
+ srv_conf->flags |= SRVFLAG_NO_SYSLOG;
+ }
+ | ACCESS STRING {
+ if (strlcpy(srv_conf->accesslog, $2,
+ sizeof(srv_conf->accesslog)) >=
+ sizeof(srv_conf->accesslog)) {
+ yyerror("access log name too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ srv_conf->flags |= SRVFLAG_ACCESS_LOG;
+ }
+ | ERR STRING {
+ if (strlcpy(srv_conf->errorlog, $2,
+ sizeof(srv_conf->errorlog)) >=
+ sizeof(srv_conf->errorlog)) {
+ yyerror("error log name too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ srv_conf->flags |= SRVFLAG_ERROR_LOG;
+ }
+ ;
+
+logstyle : COMMON {
+ srv_conf->flags &= ~SRVFLAG_NO_LOG;
+ srv_conf->flags |= SRVFLAG_LOG;
+ srv_conf->logformat = LOG_FORMAT_COMMON;
+ }
+ | COMBINED {
+ srv_conf->flags &= ~SRVFLAG_NO_LOG;
+ srv_conf->flags |= SRVFLAG_LOG;
+ srv_conf->logformat = LOG_FORMAT_COMBINED;
+ }
+ | CONNECTION {
+ srv_conf->flags &= ~SRVFLAG_NO_LOG;
+ srv_conf->flags |= SRVFLAG_LOG;
+ srv_conf->logformat = LOG_FORMAT_CONNECTION;
+ }
+ ;
+
+filter : block RETURN NUMBER optstring {
+ if ($3 <= 0 || server_httperror_byid($3) == NULL) {
+ yyerror("invalid return code: %lld", $3);
+ free($4);
+ YYERROR;
+ }
+ srv_conf->return_code = $3;
+
+ if ($4 != NULL) {
+ /* Only for 3xx redirection headers */
+ if ($3 < 300 || $3 > 399) {
+ yyerror("invalid return code for "
+ "location URI");
+ free($4);
+ YYERROR;
+ }
+ srv_conf->return_uri = $4;
+ srv_conf->return_uri_len = strlen($4) + 1;
+ }
+ }
+ | block DROP {
+ /* No return code, silently drop the connection */
+ srv_conf->return_code = 0;
+ }
+ | block {
+ /* Forbidden */
+ srv_conf->return_code = 403;
+ }
+ | PASS {
+ srv_conf->flags &= ~SRVFLAG_BLOCK;
+ srv_conf->flags |= SRVFLAG_NO_BLOCK;
+ }
+ ;
+
+block : BLOCK {
+ srv_conf->flags &= ~SRVFLAG_NO_BLOCK;
+ srv_conf->flags |= SRVFLAG_BLOCK;
+ }
+ ;
+
+optstring : /* empty */ { $$ = NULL; }
+ | STRING { $$ = $1; }
+ ;
+
+tcpip : TCP '{' optnl tcpflags_l '}'
+ | TCP tcpflags
+ ;
+
+tcpflags_l : tcpflags optcommanl tcpflags_l
+ | tcpflags optnl
+ ;
+
+tcpflags : SACK { srv_conf->tcpflags |= TCPFLAG_SACK; }
+ | NO SACK { srv_conf->tcpflags |= TCPFLAG_NSACK; }
+ | NODELAY {
+ srv_conf->tcpflags |= TCPFLAG_NODELAY;
+ }
+ | NO NODELAY {
+ srv_conf->tcpflags |= TCPFLAG_NNODELAY;
+ }
+ | BACKLOG NUMBER {
+ if ($2 < 0 || $2 > SERVER_MAX_CLIENTS) {
+ yyerror("invalid backlog: %lld", $2);
+ YYERROR;
+ }
+ srv_conf->tcpbacklog = $2;
+ }
+ | SOCKET BUFFER NUMBER {
+ srv_conf->tcpflags |= TCPFLAG_BUFSIZ;
+ if ((srv_conf->tcpbufsiz = $3) < 0) {
+ yyerror("invalid socket buffer size: %lld", $3);
+ YYERROR;
+ }
+ }
+ | IP STRING NUMBER {
+ if ($3 < 0) {
+ yyerror("invalid ttl: %lld", $3);
+ free($2);
+ YYERROR;
+ }
+ if (strcasecmp("ttl", $2) == 0) {
+ srv_conf->tcpflags |= TCPFLAG_IPTTL;
+ srv_conf->tcpipttl = $3;
+ } else if (strcasecmp("minttl", $2) == 0) {
+ srv_conf->tcpflags |= TCPFLAG_IPMINTTL;
+ srv_conf->tcpipminttl = $3;
+ } else {
+ yyerror("invalid TCP/IP flag: %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+types : TYPES '{' optnl mediaopts_l '}'
+ ;
+
+mediaopts_l : mediaopts_l mediaoptsl nl
+ | mediaoptsl nl
+ ;
+
+mediaoptsl : STRING '/' STRING {
+ if (strlcpy(media.media_type, $1,
+ sizeof(media.media_type)) >=
+ sizeof(media.media_type) ||
+ strlcpy(media.media_subtype, $3,
+ sizeof(media.media_subtype)) >=
+ sizeof(media.media_subtype)) {
+ yyerror("media type too long");
+ free($1);
+ free($3);
+ YYERROR;
+ }
+ free($1);
+ free($3);
+ } medianames_l optsemicolon
+ | include
+ ;
+
+medianames_l : medianames_l medianamesl
+ | medianamesl
+ ;
+
+medianamesl : numberstring {
+ if (strlcpy(media.media_name, $1,
+ sizeof(media.media_name)) >=
+ sizeof(media.media_name)) {
+ yyerror("media name too long");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+
+ if (!loadcfg)
+ break;
+
+ if (media_add(conf->sc_mediatypes, &media) == NULL) {
+ yyerror("failed to add media type");
+ YYERROR;
+ }
+ }
+ ;
+
+port : PORT NUMBER {
+ if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
+ yyerror("invalid port: %lld", $2);
+ YYERROR;
+ }
+ $$.val[0] = htons($2);
+ }
+ | PORT STRING {
+ int val;
+
+ if ((val = getservice($2)) == -1) {
+ yyerror("invalid port: %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ $$.val[0] = val;
+ }
+ ;
+
+timeout : NUMBER
+ {
+ if ($1 < 0) {
+ yyerror("invalid timeout: %lld", $1);
+ YYERROR;
+ }
+ $$.tv_sec = $1;
+ $$.tv_usec = 0;
+ }
+ ;
+
+numberstring : NUMBER {
+ char *s;
+ if (asprintf(&s, "%lld", $1) == -1) {
+ yyerror("asprintf: number");
+ YYERROR;
+ }
+ $$ = s;
+ }
+ | STRING
+ ;
+
+optsemicolon : ';'
+ |
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+optcommanl : ',' optnl
+ | nl
+ ;
+
+nl : '\n' optnl
+ ;
+
+%%
+
+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)
+ fatalx("yyerror vasprintf");
+ va_end(ap);
+ logit(LOG_CRIT, "%s:%d: %s", 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[] = {
+ { "access", ACCESS },
+ { "alias", ALIAS },
+ { "authenticate", AUTHENTICATE},
+ { "auto", AUTO },
+ { "backlog", BACKLOG },
+ { "block", BLOCK },
+ { "body", BODY },
+ { "buffer", BUFFER },
+ { "certificate", CERTIFICATE },
+ { "chroot", CHROOT },
+ { "ciphers", CIPHERS },
+ { "combined", COMBINED },
+ { "common", COMMON },
+ { "connection", CONNECTION },
+ { "dhe", DHE },
+ { "directory", DIRECTORY },
+ { "drop", DROP },
+ { "ecdhe", ECDHE },
+ { "error", ERR },
+ { "fastcgi", FCGI },
+ { "include", INCLUDE },
+ { "index", INDEX },
+ { "ip", IP },
+ { "key", KEY },
+ { "listen", LISTEN },
+ { "location", LOCATION },
+ { "log", LOG },
+ { "logdir", LOGDIR },
+ { "max", MAXIMUM },
+ { "no", NO },
+ { "nodelay", NODELAY },
+ { "on", ON },
+ { "pass", PASS },
+ { "port", PORT },
+ { "prefork", PREFORK },
+ { "protocols", PROTOCOLS },
+ { "request", REQUEST },
+ { "requests", REQUESTS },
+ { "return", RETURN },
+ { "root", ROOT },
+ { "sack", SACK },
+ { "server", SERVER },
+ { "socket", SOCKET },
+ { "strip", STRIP },
+ { "style", STYLE },
+ { "syslog", SYSLOG },
+ { "tcp", TCP },
+ { "timeout", TIMEOUT },
+ { "tls", TLS },
+ { "types", TYPES },
+ { "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(1, "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 != '=' && 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(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+ struct stat st;
+
+ if (fstat(fd, &st)) {
+ log_warn("cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ log_warnx("%s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+ log_warnx("%s: group writable or world read/writable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+ log_warn("%s: malloc", __func__);
+ return (NULL);
+ }
+ if ((nfile->name = strdup(name)) == NULL) {
+ log_warn("%s: malloc", __func__);
+ free(nfile);
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ log_warn("%s: %s", __func__, nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ } else if (secret &&
+ check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+ fclose(nfile->stream);
+ 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);
+}
+
+int
+parse_config(const char *filename, struct httpd *x_conf)
+{
+ struct sym *sym, *next;
+
+ conf = x_conf;
+ if (config_init(conf) == -1) {
+ log_warn("%s: cannot initialize configuration", __func__);
+ return (-1);
+ }
+
+ errors = 0;
+
+ if ((file = pushfile(filename, 0)) == NULL)
+ return (-1);
+
+ topfile = file;
+ setservent(1);
+
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ endservent();
+ endprotoent();
+
+ /* Free macros */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entry);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ return (errors ? -1 : 0);
+}
+
+int
+load_config(const char *filename, struct httpd *x_conf)
+{
+ struct sym *sym, *next;
+ struct http_mediatype mediatypes[] = MEDIA_TYPES;
+ struct media_type m;
+ int i;
+
+ conf = x_conf;
+ conf->sc_flags = 0;
+
+ loadcfg = 1;
+ errors = 0;
+ last_server_id = 0;
+ last_auth_id = 0;
+
+ srv = NULL;
+
+ if ((file = pushfile(filename, 0)) == NULL)
+ return (-1);
+
+ topfile = file;
+ setservent(1);
+
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ endservent();
+ endprotoent();
+
+ /* 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->sc_opts & HTTPD_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 (TAILQ_EMPTY(conf->sc_servers)) {
+ log_warnx("no actions, nothing to do");
+ errors++;
+ }
+
+ if (RB_EMPTY(conf->sc_mediatypes)) {
+ /* Add default media types */
+ for (i = 0; mediatypes[i].media_name != NULL; i++) {
+ (void)strlcpy(m.media_name, mediatypes[i].media_name,
+ sizeof(m.media_name));
+ (void)strlcpy(m.media_type, mediatypes[i].media_type,
+ sizeof(m.media_type));
+ (void)strlcpy(m.media_subtype,
+ mediatypes[i].media_subtype,
+ sizeof(m.media_subtype));
+ m.media_encoding = NULL;
+
+ if (media_add(conf->sc_mediatypes, &m) == NULL) {
+ log_warnx("failed to add default media \"%s\"",
+ m.media_name);
+ errors++;
+ }
+ }
+ }
+
+ return (errors ? -1 : 0);
+}
+
+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(1, "cmdline_symset: malloc");
+
+ (void)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 address *
+host_v4(const char *s)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct address *h;
+
+ memset(&ina, 0, sizeof(ina));
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+ if (sain->sin_addr.s_addr == INADDR_ANY)
+ h->prefixlen = 0; /* 0.0.0.0 address */
+ else
+ h->prefixlen = -1; /* host address */
+ return (h);
+}
+
+struct address *
+host_v6(const char *s)
+{
+ struct addrinfo hints, *res;
+ struct sockaddr_in6 *sa_in6;
+ struct address *h = NULL;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM; /* dummy */
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(s, "0", &hints, &res) == 0) {
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sa_in6 = (struct sockaddr_in6 *)&h->ss;
+ sa_in6->sin6_len = sizeof(struct sockaddr_in6);
+ sa_in6->sin6_family = AF_INET6;
+ memcpy(&sa_in6->sin6_addr,
+ &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
+ sizeof(sa_in6->sin6_addr));
+ sa_in6->sin6_scope_id =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+ if (memcmp(&sa_in6->sin6_addr, &in6addr_any,
+ sizeof(sa_in6->sin6_addr)) == 0)
+ h->prefixlen = 0; /* any address */
+ else
+ h->prefixlen = -1; /* host address */
+ freeaddrinfo(res);
+ }
+
+ return (h);
+}
+
+int
+host_dns(const char *s, struct addresslist *al, int max,
+ struct portrange *port, const char *ifname, int ipproto)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ if ((cnt = host_if(s, al, max, port, ifname, ipproto)) != 0)
+ return (cnt);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ hints.ai_flags = AI_ADDRCONFIG;
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("%s: could not parse \"%s\": %s", __func__, s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < max; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+
+ if (port != NULL)
+ memcpy(&h->port, port, sizeof(h->port));
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname))
+ log_warnx("%s: interface name truncated",
+ __func__);
+ freeaddrinfo(res0);
+ free(h);
+ return (-1);
+ }
+ if (ipproto != -1)
+ h->ipproto = ipproto;
+ h->ss.ss_family = res->ai_family;
+ h->prefixlen = -1; /* host address */
+
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (cnt == max && res) {
+ log_warnx("%s: %s resolves to more than %d hosts", __func__,
+ s, max);
+ }
+ freeaddrinfo(res0);
+ return (cnt);
+}
+
+int
+host_if(const char *s, struct addresslist *al, int max,
+ struct portrange *port, const char *ifname, int ipproto)
+{
+ struct ifaddrs *ifap, *p;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+ int cnt = 0, af;
+
+ if (getifaddrs(&ifap) == -1)
+ fatal("getifaddrs");
+
+ /* First search for IPv4 addresses */
+ af = AF_INET;
+
+ nextaf:
+ for (p = ifap; p != NULL && cnt < max; p = p->ifa_next) {
+ if (p->ifa_addr->sa_family != af ||
+ (strcmp(s, p->ifa_name) != 0 &&
+ !is_if_in_group(p->ifa_name, s)))
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal("calloc");
+
+ if (port != NULL)
+ memcpy(&h->port, port, sizeof(h->port));
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname))
+ log_warnx("%s: interface name truncated",
+ __func__);
+ freeifaddrs(ifap);
+ return (-1);
+ }
+ if (ipproto != -1)
+ h->ipproto = ipproto;
+ h->ss.ss_family = af;
+ h->prefixlen = -1; /* host address */
+
+ if (af == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ p->ifa_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ p->ifa_addr)->sin6_addr, sizeof(struct in6_addr));
+ sin6->sin6_scope_id = ((struct sockaddr_in6 *)
+ p->ifa_addr)->sin6_scope_id;
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (af == AF_INET) {
+ /* Next search for IPv6 addresses */
+ af = AF_INET6;
+ goto nextaf;
+ }
+
+ if (cnt > max) {
+ log_warnx("%s: %s resolves to more than %d hosts", __func__,
+ s, max);
+ }
+ freeifaddrs(ifap);
+ return (cnt);
+}
+
+int
+host(const char *s, struct addresslist *al, int max,
+ struct portrange *port, const char *ifname, int ipproto)
+{
+ struct address *h;
+
+ if (strcmp("*", s) == 0)
+ s = "0.0.0.0";
+
+ h = host_v4(s);
+
+ /* IPv6 address? */
+ if (h == NULL)
+ h = host_v6(s);
+
+ if (h != NULL) {
+ if (port != NULL)
+ memcpy(&h->port, port, sizeof(h->port));
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname)) {
+ log_warnx("%s: interface name truncated",
+ __func__);
+ free(h);
+ return (-1);
+ }
+ }
+ if (ipproto != -1)
+ h->ipproto = ipproto;
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ return (1);
+ }
+
+ return (host_dns(s, al, max, port, ifname, ipproto));
+}
+
+void
+host_free(struct addresslist *al)
+{
+ struct address *h;
+
+ while ((h = TAILQ_FIRST(al)) != NULL) {
+ TAILQ_REMOVE(al, h, entry);
+ free(h);
+ }
+}
+
+struct server *
+server_inherit(struct server *src, const char *name,
+ struct server_config *addr)
+{
+ struct server *dst, *s, *dstl;
+
+ if ((dst = calloc(1, sizeof(*dst))) == NULL)
+ fatal("out of memory");
+
+ /* Copy the source server and assign a new Id */
+ memcpy(&dst->srv_conf, &src->srv_conf, sizeof(dst->srv_conf));
+ if ((dst->srv_conf.tls_cert_file =
+ strdup(src->srv_conf.tls_cert_file)) == NULL)
+ fatal("out of memory");
+ if ((dst->srv_conf.tls_key_file =
+ strdup(src->srv_conf.tls_key_file)) == NULL)
+ fatal("out of memory");
+ dst->srv_conf.tls_cert = NULL;
+ dst->srv_conf.tls_key = NULL;
+
+ if (src->srv_conf.return_uri != NULL &&
+ (dst->srv_conf.return_uri =
+ strdup(src->srv_conf.return_uri)) == NULL)
+ fatal("out of memory");
+
+ dst->srv_conf.id = ++last_server_id;
+ dst->srv_conf.parent_id = dst->srv_conf.id;
+ dst->srv_s = -1;
+
+ if (last_server_id == INT_MAX) {
+ yyerror("too many servers defined");
+ serverconfig_free(&dst->srv_conf);
+ free(dst);
+ return (NULL);
+ }
+
+ /* Now set alias and listen address */
+ strlcpy(dst->srv_conf.name, name, sizeof(dst->srv_conf.name));
+ memcpy(&dst->srv_conf.ss, &addr->ss, sizeof(dst->srv_conf.ss));
+ dst->srv_conf.port = addr->port;
+ dst->srv_conf.prefixlen = addr->prefixlen;
+ if (addr->flags & SRVFLAG_TLS)
+ dst->srv_conf.flags |= SRVFLAG_TLS;
+ else
+ dst->srv_conf.flags &= ~SRVFLAG_TLS;
+
+ if (server_tls_load_keypair(dst) == -1) {
+ yyerror("failed to load public/private keys "
+ "for server %s", dst->srv_conf.name);
+ serverconfig_free(&dst->srv_conf);
+ free(dst);
+ return (NULL);
+ }
+
+ /* Check if the new server already exists */
+ TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+ if ((s->srv_conf.flags &
+ SRVFLAG_LOCATION) == 0 &&
+ strcmp(s->srv_conf.name,
+ dst->srv_conf.name) == 0 &&
+ s->srv_conf.port == dst->srv_conf.port &&
+ sockaddr_cmp(
+ (struct sockaddr *)&s->srv_conf.ss,
+ (struct sockaddr *)&dst->srv_conf.ss,
+ s->srv_conf.prefixlen) == 0)
+ break;
+ }
+ if (s != NULL) {
+ yyerror("server \"%s\" defined twice",
+ dst->srv_conf.name);
+ serverconfig_free(&dst->srv_conf);
+ free(dst);
+ return (NULL);
+ }
+
+ /* Copy all the locations of the source server */
+ TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+ if (!(s->srv_conf.flags & SRVFLAG_LOCATION &&
+ s->srv_conf.parent_id == src->srv_conf.parent_id))
+ continue;
+
+ if ((dstl = calloc(1, sizeof(*dstl))) == NULL)
+ fatal("out of memory");
+
+ memcpy(&dstl->srv_conf, &s->srv_conf, sizeof(dstl->srv_conf));
+ strlcpy(dstl->srv_conf.name, name, sizeof(dstl->srv_conf.name));
+
+ /* Copy the new Id and listen address */
+ dstl->srv_conf.id = ++last_server_id;
+ dstl->srv_conf.parent_id = dst->srv_conf.id;
+ memcpy(&dstl->srv_conf.ss, &addr->ss,
+ sizeof(dstl->srv_conf.ss));
+ dstl->srv_conf.port = addr->port;
+ dstl->srv_conf.prefixlen = addr->prefixlen;
+ dstl->srv_s = -1;
+
+ DPRINTF("adding location \"%s\" for \"%s[%u]\"",
+ dstl->srv_conf.location,
+ dstl->srv_conf.name, dstl->srv_conf.id);
+
+ TAILQ_INSERT_TAIL(conf->sc_servers, dstl, srv_entry);
+ }
+
+ return (dst);
+}
+
+int
+getservice(char *n)
+{
+ struct servent *s;
+ const char *errstr;
+ long long llval;
+
+ llval = strtonum(n, 0, UINT16_MAX, &errstr);
+ if (errstr) {
+ s = getservbyname(n, "tcp");
+ if (s == NULL)
+ s = getservbyname(n, "udp");
+ if (s == NULL) {
+ yyerror("unknown port %s", n);
+ return (-1);
+ }
+ return (s->s_port);
+ }
+
+ return (htons((u_short)llval));
+}
+
+int
+is_if_in_group(const char *ifname, const char *groupname)
+{
+ unsigned int len;
+ struct ifgroupreq ifgr;
+ struct ifg_req *ifg;
+ int s;
+ int ret = 0;
+
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+
+ memset(&ifgr, 0, sizeof(ifgr));
+ if (strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ) >= IFNAMSIZ)
+ err(1, "IFNAMSIZ");
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) {
+ if (errno == EINVAL || errno == ENOTTY)
+ goto end;
+ err(1, "SIOCGIFGROUP");
+ }
+
+ len = ifgr.ifgr_len;
+ ifgr.ifgr_groups =
+ (struct ifg_req *)calloc(len / sizeof(struct ifg_req),
+ sizeof(struct ifg_req));
+ if (ifgr.ifgr_groups == NULL)
+ err(1, "getifgroups");
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGROUP");
+
+ ifg = ifgr.ifgr_groups;
+ for (; ifg && len >= sizeof(struct ifg_req); ifg++) {
+ len -= sizeof(struct ifg_req);
+ if (strcmp(ifg->ifgrq_group, groupname) == 0) {
+ ret = 1;
+ break;
+ }
+ }
+ free(ifgr.ifgr_groups);
+
+end:
+ close(s);
+ return (ret);
+}