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 static int yylex(void);
67 int parse_portno(const char*);
68 void parse_conf(const char*);
69 char *ensure_absolute_path(char*);
70 int check_block_code(int);
71 char *check_block_fmt(char*);
72 int check_strip_no(int);
73 int check_prefork_num(int);
74 void advance_loc(void);
75 void only_once(const void*, const char*);
76 void only_oncei(int, const char*);
77 int fastcgi_conf(char *, char *, char *);
78 void add_param(char *, char *, int);
79 int symset(const char *, const char *, int);
80 char *symget(const char *);
85 /* %define parse.error verbose */
87 %token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TCHROOT TUSER TSERVER
88 %token TPREFORK TLOCATION TCERT TKEY TROOT TCGI TENV TLANG TLOG TINDEX TAUTO
89 %token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS TTCP
90 %token TFASTCGI TSPAWN TPARAM
94 %token <v.str> TSTRING
106 var : TSTRING '=' TSTRING {
110 yyerror("macro name cannot contain "
123 option : TCHROOT TSTRING { conf.chroot = $2; }
124 | TIPV6 TBOOL { conf.ipv6 = $2; }
125 | TMIME TSTRING TSTRING { add_mime(&conf.mime, $2, $3); }
126 | TPORT TNUM { conf.port = $2; }
127 | TPREFORK TNUM { conf.prefork = check_prefork_num($2); }
128 | TPROTOCOLS TSTRING {
129 if (tls_config_parse_protocols(&conf.protos, $2) == -1)
130 yyerror("invalid protocols string \"%s\"", $2);
132 | TUSER TSTRING { conf.user = $2; }
135 vhost : TSERVER TSTRING {
137 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
139 loc = new_location();
140 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
142 loc->match = xstrdup("*");
145 if (strstr($2, "xn--") != NULL) {
146 warnx("%s:%d:%d \"%s\" looks like punycode: "
147 "you should use the decoded hostname.",
148 config_path, yylval.lineno+1, yylval.colno,
151 } '{' servopts locations '}' {
153 if (host->cert == NULL || host->key == NULL)
154 yyerror("invalid vhost definition: %s", $2);
156 | error '}' { yyerror("error in server directive"); }
159 servopts : /* empty */
163 servopt : TALIAS TSTRING {
166 a = xcalloc(1, sizeof(*a));
168 if (TAILQ_EMPTY(&host->aliases))
169 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
171 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
174 only_once(host->cert, "cert");
175 host->cert = ensure_absolute_path($2);
178 only_once(host->cgi, "cgi");
179 /* drop the starting '/', if any */
181 memmove($2, $2+1, strlen($2));
184 | TENTRYPOINT TSTRING {
185 only_once(host->entrypoint, "entrypoint");
187 memmove($2, $2+1, strlen($2));
188 host->entrypoint = $2;
190 | TENV TSTRING TSTRING {
191 add_param($2, $3, 1);
194 only_once(host->key, "key");
195 host->key = ensure_absolute_path($2);
197 | TPARAM TSTRING TSTRING {
198 add_param($2, $3, 0);
203 locations : /* empty */
207 location : TLOCATION { advance_loc(); } TSTRING '{' locopts '}' {
208 /* drop the starting '/' if any */
210 memmove($3, $3+1, strlen($3));
216 locopts : /* empty */
220 locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
221 | TBLOCK TRETURN TNUM TSTRING {
222 only_once(loc->block_fmt, "block");
223 loc->block_fmt = check_block_fmt($4);
224 loc->block_code = check_block_code($3);
226 | TBLOCK TRETURN TNUM {
227 only_once(loc->block_fmt, "block");
228 loc->block_fmt = xstrdup("temporary failure");
229 loc->block_code = check_block_code($3);
230 if ($3 >= 30 && $3 < 40)
231 yyerror("missing `meta' for block return %d", $3);
234 only_once(loc->block_fmt, "block");
235 loc->block_fmt = xstrdup("temporary failure");
236 loc->block_code = 40;
238 | TDEFAULT TTYPE TSTRING {
239 only_once(loc->default_mime, "default type");
240 loc->default_mime = $3;
244 only_once(loc->index, "index");
248 only_once(loc->lang, "lang");
251 | TLOG TBOOL { loc->disable_log = !$2; }
252 | TREQUIRE TCLIENT TCA TSTRING {
253 only_once(loc->reqca, "require client ca");
254 ensure_absolute_path($4);
255 if ((loc->reqca = load_ca($4)) == NULL)
256 yyerror("couldn't load ca cert: %s", $4);
260 only_once(loc->dir, "root");
261 loc->dir = ensure_absolute_path($2);
263 | TSTRIP TNUM { loc->strip = check_strip_no($2); }
266 fastcgi : TSPAWN TSTRING {
267 only_oncei(loc->fcgi, "fastcgi");
268 loc->fcgi = fastcgi_conf(NULL, NULL, $2);
271 only_oncei(loc->fcgi, "fastcgi");
272 loc->fcgi = fastcgi_conf($1, NULL, NULL);
274 | TTCP TSTRING TNUM {
276 if (asprintf(&c, "%d", $3) == -1)
278 only_oncei(loc->fcgi, "fastcgi");
279 loc->fcgi = fastcgi_conf($2, c, NULL);
282 only_oncei(loc->fcgi, "fastcgi");
283 loc->fcgi = fastcgi_conf($2, xstrdup("9000"), NULL);
285 | TTCP TSTRING TSTRING {
286 only_oncei(loc->fcgi, "fastcgi");
287 loc->fcgi = fastcgi_conf($2, $3, NULL);
293 static struct vhost *
296 return xcalloc(1, sizeof(struct vhost));
299 static struct location *
304 l = xcalloc(1, sizeof(*l));
311 yyerror(const char *msg, ...)
318 fprintf(stderr, "%s:%d: ", config_path, yylval.lineno);
319 vfprintf(stderr, msg, ap);
320 fprintf(stderr, "\n");
324 static struct keyword {
336 {"default", TDEFAULT},
337 {"entrypoint", TENTRYPOINT},
339 {"fastcgi", TFASTCGI},
344 {"location", TLOCATION},
349 {"prefork", TPREFORK},
350 {"protocols", TPROTOCOLS},
351 {"require", TREQUIRE},
363 * Taken an adapted from doas' parse.y
368 char buf[8096], *ebuf, *p, *str, *v, *val;
369 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
373 ebuf = buf + sizeof(buf);
376 /* skip whitespace first */
377 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
385 /* check for special one-character constructions */
391 /* skip comments; NUL is allowed; no continuation */
392 while ((c = getc(yyfp)) != '\n')
405 /* parsing next word */
406 for (;; c = getc(yyfp), yylval.colno++) {
409 yyerror("unallowed character NULL in column %d",
419 /* expand macros in-place */
424 if ((c = getc(yyfp)) == EOF) {
425 yyerror("EOF during macro expansion");
428 if (p + 1 >= ebuf - 1) {
429 yyerror("string too long");
432 if (isalnum(c) || c == '_') {
441 if ((val = symget(p)) == NULL) {
442 yyerror("macro '%s' not defined", v);
446 if (p + len >= ebuf - 1) {
447 yyerror("after macro-expansion, "
452 strlcat(p, val, ebuf - p);
459 yyerror("unterminated quotes in column %d",
470 yyerror("unterminated escape in column %d",
473 yyerror("unterminated quotes in column %d",
481 if (!escape && !quotes)
496 yyerror("line too long");
508 * There could be a number of reason for empty buffer,
509 * and we handle all of them here, to avoid cluttering
514 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
518 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) {
519 if (!strcmp(buf, keywords[i].word))
520 return keywords[i].token;
524 if (!nonkw && (c == '-' || isdigit(c))) {
525 yylval.v.num = parse_portno(buf);
528 if (!nonkw && !strcmp(buf, "on")) {
532 if (!nonkw && !strcmp(buf, "off")) {
536 if ((str = strdup(buf)) == NULL)
537 err(1, "%s", __func__);
543 yyerror("input error reading config");
548 parse_portno(const char *p)
553 n = strtonum(p, 0, UINT16_MAX, &errstr);
555 yyerror("port number is %s: %s", errstr, p);
560 parse_conf(const char *path)
562 struct sym *sym, *next;
565 if ((yyfp = fopen(path, "r")) == NULL)
566 err(1, "cannot open config: %s", path);
573 if (TAILQ_FIRST(&hosts)->domain == NULL)
574 errx(1, "no vhost defined in %s", path);
576 /* free unused macros */
577 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
578 /* TODO: warn if !sym->used */
582 TAILQ_REMOVE(&symhead, sym, entry);
589 ensure_absolute_path(char *path)
591 if (path == NULL || *path != '/')
592 yyerror("not an absolute path: %s", path);
597 check_block_code(int n)
599 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
600 yyerror("invalid block code %d", n);
605 check_block_fmt(char *fmt)
609 for (s = fmt; *s; ++s) {
620 yyerror("invalid format specifier %%%c", *s);
628 check_strip_no(int n)
631 yyerror("invalid strip number %d", n);
636 check_prefork_num(int n)
638 if (n <= 0 || n >= PROC_MAX)
639 yyerror("invalid prefork number %d", n);
646 loc = new_location();
647 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
651 only_once(const void *ptr, const char *name)
654 yyerror("`%s' specified more than once", name);
658 only_oncei(int i, const char *name)
661 yyerror("`%s' specified more than once", name);
665 fastcgi_conf(char *path, char *port, char *prog)
670 for (i = 0; i < FCGI_MAX; ++i) {
673 if (f->path == NULL) {
681 /* XXX: what to do with prog? */
682 if (!strcmp(f->path, path) &&
683 ((port == NULL && f->port == NULL) ||
684 !strcmp(f->port, port))) {
691 yyerror("too much `fastcgi' rules defined.");
696 add_param(char *name, char *val, int env)
706 e = xcalloc(1, sizeof(*e));
710 TAILQ_INSERT_HEAD(h, e, envs);
712 TAILQ_INSERT_TAIL(h, e, envs);
716 symset(const char *name, const char *val, int persist)
720 TAILQ_FOREACH(sym, &symhead, entry) {
721 if (!strcmp(name, sym->name))
731 TAILQ_REMOVE(&symhead, sym, entry);
736 sym = xcalloc(1, sizeof(*sym));
737 sym->name = xstrdup(name);
738 sym->val = xstrdup(val);
740 sym->persist = persist;
742 TAILQ_INSERT_TAIL(&symhead, sym, entry);
747 cmdline_symset(char *s)
752 if ((val = strrchr(s, '=')) == NULL)
754 sym = xcalloc(1, val - s + 1);
755 memcpy(sym, s, val - s);
756 ret = symset(sym, val + 1, 1);
762 symget(const char *name)
766 TAILQ_FOREACH(sym, &symhead, entry) {
767 if (!strcmp(name, sym->name)) {