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
108 string : string TSTRING {
109 if (asprintf(&$$, "%s%s", $1, $2) == -1) {
112 yyerror("string: asprintf: %s", strerror(errno));
121 var : TSTRING '=' string {
125 yyerror("macro name cannot contain "
138 option : TCHROOT string { conf.chroot = $2; }
139 | TIPV6 TBOOL { conf.ipv6 = $2; }
140 | TMIME TSTRING string { add_mime(&conf.mime, $2, $3); }
141 | TPORT TNUM { conf.port = $2; }
142 | TPREFORK TNUM { conf.prefork = check_prefork_num($2); }
143 | TPROTOCOLS string {
144 if (tls_config_parse_protocols(&conf.protos, $2) == -1)
145 yyerror("invalid protocols string \"%s\"", $2);
147 | TUSER string { conf.user = $2; }
150 vhost : TSERVER string {
152 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
154 loc = new_location();
155 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
157 loc->match = xstrdup("*");
160 if (strstr($2, "xn--") != NULL) {
161 warnx("%s:%d:%d \"%s\" looks like punycode: "
162 "you should use the decoded hostname.",
163 config_path, yylval.lineno+1, yylval.colno,
166 } '{' servopts locations '}' {
168 if (host->cert == NULL || host->key == NULL)
169 yyerror("invalid vhost definition: %s", $2);
171 | error '}' { yyerror("error in server directive"); }
174 servopts : /* empty */
178 servopt : TALIAS string {
181 a = xcalloc(1, sizeof(*a));
183 if (TAILQ_EMPTY(&host->aliases))
184 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
186 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
189 only_once(host->cert, "cert");
190 host->cert = ensure_absolute_path($2);
193 only_once(host->cgi, "cgi");
194 /* drop the starting '/', if any */
196 memmove($2, $2+1, strlen($2));
199 | TENTRYPOINT string {
200 only_once(host->entrypoint, "entrypoint");
202 memmove($2, $2+1, strlen($2));
203 host->entrypoint = $2;
205 | TENV string string {
206 add_param($2, $3, 1);
209 only_once(host->key, "key");
210 host->key = ensure_absolute_path($2);
212 | TPARAM string string {
213 add_param($2, $3, 0);
218 locations : /* empty */
222 location : TLOCATION { advance_loc(); } string '{' locopts '}' {
223 /* drop the starting '/' if any */
225 memmove($3, $3+1, strlen($3));
231 locopts : /* empty */
235 locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
236 | TBLOCK TRETURN TNUM string {
237 only_once(loc->block_fmt, "block");
238 loc->block_fmt = check_block_fmt($4);
239 loc->block_code = check_block_code($3);
241 | TBLOCK TRETURN TNUM {
242 only_once(loc->block_fmt, "block");
243 loc->block_fmt = xstrdup("temporary failure");
244 loc->block_code = check_block_code($3);
245 if ($3 >= 30 && $3 < 40)
246 yyerror("missing `meta' for block return %d", $3);
249 only_once(loc->block_fmt, "block");
250 loc->block_fmt = xstrdup("temporary failure");
251 loc->block_code = 40;
253 | TDEFAULT TTYPE string {
254 only_once(loc->default_mime, "default type");
255 loc->default_mime = $3;
259 only_once(loc->index, "index");
263 only_once(loc->lang, "lang");
266 | TLOG TBOOL { loc->disable_log = !$2; }
267 | TREQUIRE TCLIENT TCA string {
268 only_once(loc->reqca, "require client ca");
269 ensure_absolute_path($4);
270 if ((loc->reqca = load_ca($4)) == NULL)
271 yyerror("couldn't load ca cert: %s", $4);
275 only_once(loc->dir, "root");
276 loc->dir = ensure_absolute_path($2);
278 | TSTRIP TNUM { loc->strip = check_strip_no($2); }
281 fastcgi : TSPAWN string {
282 only_oncei(loc->fcgi, "fastcgi");
283 loc->fcgi = fastcgi_conf(NULL, NULL, $2);
286 only_oncei(loc->fcgi, "fastcgi");
287 loc->fcgi = fastcgi_conf($1, NULL, NULL);
291 if (asprintf(&c, "%d", $3) == -1)
293 only_oncei(loc->fcgi, "fastcgi");
294 loc->fcgi = fastcgi_conf($2, c, NULL);
297 only_oncei(loc->fcgi, "fastcgi");
298 loc->fcgi = fastcgi_conf($2, xstrdup("9000"), NULL);
300 | TTCP string string {
301 only_oncei(loc->fcgi, "fastcgi");
302 loc->fcgi = fastcgi_conf($2, $3, NULL);
308 static struct vhost *
311 return xcalloc(1, sizeof(struct vhost));
314 static struct location *
319 l = xcalloc(1, sizeof(*l));
326 yyerror(const char *msg, ...)
333 fprintf(stderr, "%s:%d: ", config_path, yylval.lineno);
334 vfprintf(stderr, msg, ap);
335 fprintf(stderr, "\n");
339 static struct keyword {
351 {"default", TDEFAULT},
352 {"entrypoint", TENTRYPOINT},
354 {"fastcgi", TFASTCGI},
359 {"location", TLOCATION},
364 {"prefork", TPREFORK},
365 {"protocols", TPROTOCOLS},
366 {"require", TREQUIRE},
378 * Taken an adapted from doas' parse.y
383 char buf[8096], *ebuf, *p, *str, *v, *val;
384 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
388 ebuf = buf + sizeof(buf);
391 /* skip whitespace first */
392 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
400 /* check for special one-character constructions */
406 /* skip comments; NUL is allowed; no continuation */
407 while ((c = getc(yyfp)) != '\n')
419 /* parsing next word */
420 for (;; c = getc(yyfp), yylval.colno++) {
423 yyerror("unallowed character NULL in column %d",
433 /* expand macros in-place */
435 if (!escape && !quotes) {
438 if ((c = getc(yyfp)) == EOF) {
439 yyerror("EOF during macro expansion");
442 if (p + 1 >= ebuf - 1) {
443 yyerror("string too long");
446 if (isalnum(c) || c == '_') {
454 if ((val = symget(p)) == NULL) {
455 yyerror("macro '%s' not defined", v);
459 if (p + len >= ebuf - 1) {
460 yyerror("after macro-expansion, "
465 strlcat(p, val, ebuf - p);
473 yyerror("unterminated quotes in column %d",
484 yyerror("unterminated escape in column %d",
487 yyerror("unterminated quotes in column %d",
495 if (!escape && !quotes)
510 yyerror("line too long");
522 * There could be a number of reason for empty buffer,
523 * and we handle all of them here, to avoid cluttering
528 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
532 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) {
533 if (!strcmp(buf, keywords[i].word))
534 return keywords[i].token;
538 if (!nonkw && (c == '-' || isdigit(c))) {
539 yylval.v.num = parse_portno(buf);
542 if (!nonkw && !strcmp(buf, "on")) {
546 if (!nonkw && !strcmp(buf, "off")) {
550 if ((str = strdup(buf)) == NULL)
551 err(1, "%s", __func__);
557 yyerror("input error reading config");
562 parse_portno(const char *p)
567 n = strtonum(p, 0, UINT16_MAX, &errstr);
569 yyerror("port number is %s: %s", errstr, p);
574 parse_conf(const char *path)
576 struct sym *sym, *next;
579 if ((yyfp = fopen(path, "r")) == NULL)
580 err(1, "cannot open config: %s", path);
587 if (TAILQ_FIRST(&hosts)->domain == NULL)
588 errx(1, "no vhost defined in %s", path);
590 /* free unused macros */
591 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
592 /* TODO: warn if !sym->used */
596 TAILQ_REMOVE(&symhead, sym, entry);
603 ensure_absolute_path(char *path)
605 if (path == NULL || *path != '/')
606 yyerror("not an absolute path: %s", path);
611 check_block_code(int n)
613 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
614 yyerror("invalid block code %d", n);
619 check_block_fmt(char *fmt)
623 for (s = fmt; *s; ++s) {
634 yyerror("invalid format specifier %%%c", *s);
642 check_strip_no(int n)
645 yyerror("invalid strip number %d", n);
650 check_prefork_num(int n)
652 if (n <= 0 || n >= PROC_MAX)
653 yyerror("invalid prefork number %d", n);
660 loc = new_location();
661 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
665 only_once(const void *ptr, const char *name)
668 yyerror("`%s' specified more than once", name);
672 only_oncei(int i, const char *name)
675 yyerror("`%s' specified more than once", name);
679 fastcgi_conf(char *path, char *port, char *prog)
684 for (i = 0; i < FCGI_MAX; ++i) {
687 if (f->path == NULL) {
695 /* XXX: what to do with prog? */
696 if (!strcmp(f->path, path) &&
697 ((port == NULL && f->port == NULL) ||
698 !strcmp(f->port, port))) {
705 yyerror("too much `fastcgi' rules defined.");
710 add_param(char *name, char *val, int env)
720 e = xcalloc(1, sizeof(*e));
724 TAILQ_INSERT_HEAD(h, e, envs);
726 TAILQ_INSERT_TAIL(h, e, envs);
730 symset(const char *name, const char *val, int persist)
734 TAILQ_FOREACH(sym, &symhead, entry) {
735 if (!strcmp(name, sym->name))
745 TAILQ_REMOVE(&symhead, sym, entry);
750 sym = xcalloc(1, sizeof(*sym));
751 sym->name = xstrdup(name);
752 sym->val = xstrdup(val);
754 sym->persist = persist;
756 TAILQ_INSERT_TAIL(&symhead, sym, entry);
761 cmdline_symset(char *s)
766 if ((val = strrchr(s, '=')) == NULL)
768 sym = xcalloc(1, val - s + 1);
769 memcpy(sym, s, val - s);
770 ret = symset(sym, val + 1, 1);
776 symget(const char *name)
780 TAILQ_FOREACH(sym, &symhead, entry) {
781 if (!strcmp(name, sym->name)) {