4 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
38 #define YYSTYPE yystype
46 * The idea behind this implementation of macros is from rad/parse.y
48 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
50 TAILQ_ENTRY(sym) entry;
62 static struct vhost *new_vhost(void);
63 static struct location *new_location(void);
65 void yyerror(const char*, ...);
66 int kw_cmp(const void *, const void *);
67 static int yylex(void);
68 int parse_portno(const char*);
69 void parse_conf(const char*);
70 char *ensure_absolute_path(char*);
71 int check_block_code(int);
72 char *check_block_fmt(char*);
73 int check_strip_no(int);
74 int check_prefork_num(int);
75 void advance_loc(void);
76 void only_once(const void*, const char*);
77 void only_oncei(int, const char*);
78 int fastcgi_conf(char *, char *, char *);
79 void add_param(char *, char *, int);
80 int symset(const char *, const char *, int);
81 char *symget(const char *);
86 /* %define parse.error verbose */
88 %token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TCHROOT TUSER TSERVER
89 %token TPREFORK TLOCATION TCERT TKEY TROOT TCGI TENV TLANG TLOG TINDEX TAUTO
90 %token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS TTCP
91 %token TFASTCGI TSPAWN TPARAM
95 %token <v.str> TSTRING
109 string : string TSTRING {
110 if (asprintf(&$$, "%s%s", $1, $2) == -1) {
113 yyerror("string: asprintf: %s", strerror(errno));
122 var : TSTRING '=' string {
126 yyerror("macro name cannot contain "
139 option : TCHROOT string { conf.chroot = $2; }
140 | TIPV6 TBOOL { conf.ipv6 = $2; }
141 | TMIME TSTRING string { add_mime(&conf.mime, $2, $3); }
142 | TPORT TNUM { conf.port = $2; }
143 | TPREFORK TNUM { conf.prefork = check_prefork_num($2); }
144 | TPROTOCOLS string {
145 if (tls_config_parse_protocols(&conf.protos, $2) == -1)
146 yyerror("invalid protocols string \"%s\"", $2);
148 | TUSER string { conf.user = $2; }
151 vhost : TSERVER string {
153 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
155 loc = new_location();
156 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
158 loc->match = xstrdup("*");
161 if (strstr($2, "xn--") != NULL) {
162 warnx("%s:%d:%d \"%s\" looks like punycode: "
163 "you should use the decoded hostname.",
164 config_path, yylval.lineno+1, yylval.colno,
167 } '{' servopts locations '}' {
169 if (host->cert == NULL || host->key == NULL)
170 yyerror("invalid vhost definition: %s", $2);
172 | error '}' { yyerror("error in server directive"); }
175 servopts : /* empty */
179 servopt : TALIAS string {
182 a = xcalloc(1, sizeof(*a));
184 if (TAILQ_EMPTY(&host->aliases))
185 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
187 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
190 only_once(host->cert, "cert");
191 host->cert = ensure_absolute_path($2);
194 only_once(host->cgi, "cgi");
195 /* drop the starting '/', if any */
197 memmove($2, $2+1, strlen($2));
200 | TENTRYPOINT string {
201 only_once(host->entrypoint, "entrypoint");
203 memmove($2, $2+1, strlen($2));
204 host->entrypoint = $2;
206 | TENV string string {
207 add_param($2, $3, 1);
210 only_once(host->key, "key");
211 host->key = ensure_absolute_path($2);
213 | TPARAM string string {
214 add_param($2, $3, 0);
219 locations : /* empty */
223 location : TLOCATION { advance_loc(); } string '{' locopts '}' {
224 /* drop the starting '/' if any */
226 memmove($3, $3+1, strlen($3));
232 locopts : /* empty */
236 locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
237 | TBLOCK TRETURN TNUM string {
238 only_once(loc->block_fmt, "block");
239 loc->block_fmt = check_block_fmt($4);
240 loc->block_code = check_block_code($3);
242 | TBLOCK TRETURN TNUM {
243 only_once(loc->block_fmt, "block");
244 loc->block_fmt = xstrdup("temporary failure");
245 loc->block_code = check_block_code($3);
246 if ($3 >= 30 && $3 < 40)
247 yyerror("missing `meta' for block return %d", $3);
250 only_once(loc->block_fmt, "block");
251 loc->block_fmt = xstrdup("temporary failure");
252 loc->block_code = 40;
254 | TDEFAULT TTYPE string {
255 only_once(loc->default_mime, "default type");
256 loc->default_mime = $3;
260 only_once(loc->index, "index");
264 only_once(loc->lang, "lang");
267 | TLOG TBOOL { loc->disable_log = !$2; }
268 | TREQUIRE TCLIENT TCA string {
269 only_once(loc->reqca, "require client ca");
270 ensure_absolute_path($4);
271 if ((loc->reqca = load_ca($4)) == NULL)
272 yyerror("couldn't load ca cert: %s", $4);
276 only_once(loc->dir, "root");
277 loc->dir = ensure_absolute_path($2);
279 | TSTRIP TNUM { loc->strip = check_strip_no($2); }
282 fastcgi : TSPAWN string {
283 only_oncei(loc->fcgi, "fastcgi");
284 loc->fcgi = fastcgi_conf(NULL, NULL, $2);
287 only_oncei(loc->fcgi, "fastcgi");
288 loc->fcgi = fastcgi_conf($1, NULL, NULL);
292 if (asprintf(&c, "%d", $3) == -1)
294 only_oncei(loc->fcgi, "fastcgi");
295 loc->fcgi = fastcgi_conf($2, c, NULL);
298 only_oncei(loc->fcgi, "fastcgi");
299 loc->fcgi = fastcgi_conf($2, xstrdup("9000"), NULL);
301 | TTCP string string {
302 only_oncei(loc->fcgi, "fastcgi");
303 loc->fcgi = fastcgi_conf($2, $3, NULL);
309 static struct vhost *
312 return xcalloc(1, sizeof(struct vhost));
315 static struct location *
320 l = xcalloc(1, sizeof(*l));
327 yyerror(const char *msg, ...)
334 fprintf(stderr, "%s:%d: ", config_path, yylval.lineno);
335 vfprintf(stderr, msg, ap);
336 fprintf(stderr, "\n");
340 static struct keyword {
344 /* these MUST be sorted */
353 {"default", TDEFAULT},
354 {"entrypoint", TENTRYPOINT},
356 {"fastcgi", TFASTCGI},
361 {"location", TLOCATION},
366 {"prefork", TPREFORK},
367 {"protocols", TPROTOCOLS},
368 {"require", TREQUIRE},
380 kw_cmp(const void *k, const void *e)
382 return strcmp(k, ((struct keyword *)e)->word);
386 * Taken an adapted from doas' parse.y
392 char buf[8096], *ebuf, *p, *str, *v, *val;
393 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
397 ebuf = buf + sizeof(buf);
400 /* skip whitespace first */
401 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
409 /* check for special one-character constructions */
415 /* skip comments; NUL is allowed; no continuation */
416 while ((c = getc(yyfp)) != '\n')
428 /* parsing next word */
429 for (;; c = getc(yyfp), yylval.colno++) {
432 yyerror("unallowed character NULL in column %d",
442 /* expand macros in-place */
444 if (!escape && !quotes) {
447 if ((c = getc(yyfp)) == EOF) {
448 yyerror("EOF during macro expansion");
451 if (p + 1 >= ebuf - 1) {
452 yyerror("string too long");
455 if (isalnum(c) || c == '_') {
463 if ((val = symget(p)) == NULL) {
464 yyerror("macro '%s' not defined", v);
468 if (p + len >= ebuf - 1) {
469 yyerror("after macro-expansion, "
474 strlcat(p, val, ebuf - p);
482 yyerror("unterminated quotes in column %d",
493 yyerror("unterminated escape in column %d",
496 yyerror("unterminated quotes in column %d",
504 if (!escape && !quotes)
519 yyerror("line too long");
531 * There could be a number of reason for empty buffer,
532 * and we handle all of them here, to avoid cluttering
537 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
541 kw = bsearch(buf, keywords, sizeof(keywords)/sizeof(keywords[0]),
542 sizeof(keywords[0]), kw_cmp);
547 if (!nonkw && (c == '-' || isdigit(c))) {
548 yylval.v.num = parse_portno(buf);
551 if (!nonkw && !strcmp(buf, "on")) {
555 if (!nonkw && !strcmp(buf, "off")) {
559 if ((str = strdup(buf)) == NULL)
560 err(1, "%s", __func__);
566 yyerror("input error reading config");
571 parse_portno(const char *p)
576 n = strtonum(p, 0, UINT16_MAX, &errstr);
578 yyerror("port number is %s: %s", errstr, p);
583 parse_conf(const char *path)
585 struct sym *sym, *next;
588 if ((yyfp = fopen(path, "r")) == NULL)
589 err(1, "cannot open config: %s", path);
596 if (TAILQ_FIRST(&hosts)->domain == NULL)
597 errx(1, "no vhost defined in %s", path);
599 /* free unused macros */
600 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
601 /* TODO: warn if !sym->used */
605 TAILQ_REMOVE(&symhead, sym, entry);
612 ensure_absolute_path(char *path)
614 if (path == NULL || *path != '/')
615 yyerror("not an absolute path: %s", path);
620 check_block_code(int n)
622 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
623 yyerror("invalid block code %d", n);
628 check_block_fmt(char *fmt)
632 for (s = fmt; *s; ++s) {
643 yyerror("invalid format specifier %%%c", *s);
651 check_strip_no(int n)
654 yyerror("invalid strip number %d", n);
659 check_prefork_num(int n)
661 if (n <= 0 || n >= PROC_MAX)
662 yyerror("invalid prefork number %d", n);
669 loc = new_location();
670 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
674 only_once(const void *ptr, const char *name)
677 yyerror("`%s' specified more than once", name);
681 only_oncei(int i, const char *name)
684 yyerror("`%s' specified more than once", name);
688 fastcgi_conf(char *path, char *port, char *prog)
693 for (i = 0; i < FCGI_MAX; ++i) {
696 if (f->path == NULL) {
704 /* XXX: what to do with prog? */
705 if (!strcmp(f->path, path) &&
706 ((port == NULL && f->port == NULL) ||
707 !strcmp(f->port, port))) {
714 yyerror("too much `fastcgi' rules defined.");
719 add_param(char *name, char *val, int env)
729 e = xcalloc(1, sizeof(*e));
733 TAILQ_INSERT_HEAD(h, e, envs);
735 TAILQ_INSERT_TAIL(h, e, envs);
739 symset(const char *name, const char *val, int persist)
743 TAILQ_FOREACH(sym, &symhead, entry) {
744 if (!strcmp(name, sym->name))
754 TAILQ_REMOVE(&symhead, sym, entry);
759 sym = xcalloc(1, sizeof(*sym));
760 sym->name = xstrdup(name);
761 sym->val = xstrdup(val);
763 sym->persist = persist;
765 TAILQ_INSERT_TAIL(&symhead, sym, entry);
770 cmdline_symset(char *s)
775 if ((val = strrchr(s, '=')) == NULL)
777 sym = xcalloc(1, val - s + 1);
778 memcpy(sym, s, val - s);
779 ret = symset(sym, val + 1, 1);
785 symget(const char *name)
789 TAILQ_FOREACH(sym, &symhead, entry) {
790 if (!strcmp(name, sym->name)) {