Blob


1 %{
3 /*
4 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
5 *
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.
9 *
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.
17 */
19 #include <ctype.h>
20 #include <errno.h>
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #include "gmid.h"
28 FILE *yyfp;
30 typedef struct {
31 union {
32 char *str;
33 int num;
34 } v;
35 int lineno;
36 int colno;
37 } yystype;
38 #define YYSTYPE yystype
40 /*
41 * #define YYDEBUG 1
42 * int yydebug = 1;
43 */
45 /*
46 * The idea behind this implementation of macros is from rad/parse.y
47 */
48 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
49 struct sym {
50 TAILQ_ENTRY(sym) entry;
51 int used;
52 int persist;
53 char *name;
54 char *val;
55 };
57 struct vhost *host;
58 struct location *loc;
60 static int goterror;
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 *);
82 %}
84 /* for bison: */
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
92 %token TERR
94 %token <v.str> TSTRING
95 %token <v.num> TNUM
96 %token <v.num> TBOOL
98 %type <v.str> string
100 %%
102 conf : /* empty */
103 | conf var
104 | conf option
105 | conf vhost
108 string : string TSTRING {
109 if (asprintf(&$$, "%s%s", $1, $2) == -1) {
110 free($1);
111 free($2);
112 yyerror("string: asprintf: %s", strerror(errno));
113 YYERROR;
115 free($1);
116 free($2);
118 | TSTRING
121 var : TSTRING '=' string {
122 char *s = $1;
123 while (*s++) {
124 if (isspace(*s)) {
125 yyerror("macro name cannot contain "
126 "whitespaces");
127 free($1);
128 free($3);
129 YYERROR;
132 symset($1, $3, 0);
133 free($1);
134 free($3);
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 {
151 host = new_vhost();
152 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
154 loc = new_location();
155 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
157 loc->match = xstrdup("*");
158 host->domain = $2;
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,
164 $2);
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 */
175 | servopts servopt
178 servopt : TALIAS string {
179 struct alist *a;
181 a = xcalloc(1, sizeof(*a));
182 a->alias = $2;
183 if (TAILQ_EMPTY(&host->aliases))
184 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
185 else
186 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
188 | TCERT string {
189 only_once(host->cert, "cert");
190 host->cert = ensure_absolute_path($2);
192 | TCGI string {
193 only_once(host->cgi, "cgi");
194 /* drop the starting '/', if any */
195 if (*$2 == '/')
196 memmove($2, $2+1, strlen($2));
197 host->cgi = $2;
199 | TENTRYPOINT string {
200 only_once(host->entrypoint, "entrypoint");
201 while (*$2 == '/')
202 memmove($2, $2+1, strlen($2));
203 host->entrypoint = $2;
205 | TENV string string {
206 add_param($2, $3, 1);
208 | TKEY string {
209 only_once(host->key, "key");
210 host->key = ensure_absolute_path($2);
212 | TPARAM string string {
213 add_param($2, $3, 0);
215 | locopt
218 locations : /* empty */
219 | locations location
222 location : TLOCATION { advance_loc(); } string '{' locopts '}' {
223 /* drop the starting '/' if any */
224 if (*$3 == '/')
225 memmove($3, $3+1, strlen($3));
226 loc->match = $3;
228 | error '}'
231 locopts : /* empty */
232 | locopts locopt
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);
248 | TBLOCK {
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;
257 | TFASTCGI fastcgi
258 | TINDEX string {
259 only_once(loc->index, "index");
260 loc->index = $2;
262 | TLANG string {
263 only_once(loc->lang, "lang");
264 loc->lang = $2;
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);
272 free($4);
274 | TROOT string {
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);
285 | string {
286 only_oncei(loc->fcgi, "fastcgi");
287 loc->fcgi = fastcgi_conf($1, NULL, NULL);
289 | TTCP string TNUM {
290 char *c;
291 if (asprintf(&c, "%d", $3) == -1)
292 err(1, "asprintf");
293 only_oncei(loc->fcgi, "fastcgi");
294 loc->fcgi = fastcgi_conf($2, c, NULL);
296 | TTCP string {
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);
306 %%
308 static struct vhost *
309 new_vhost(void)
311 return xcalloc(1, sizeof(struct vhost));
314 static struct location *
315 new_location(void)
317 struct location *l;
319 l = xcalloc(1, sizeof(*l));
320 l->dirfd = -1;
321 l->fcgi = -1;
322 return l;
325 void
326 yyerror(const char *msg, ...)
328 va_list ap;
330 goterror = 1;
332 va_start(ap, msg);
333 fprintf(stderr, "%s:%d: ", config_path, yylval.lineno);
334 vfprintf(stderr, msg, ap);
335 fprintf(stderr, "\n");
336 va_end(ap);
339 static struct keyword {
340 const char *word;
341 int token;
342 } keywords[] = {
343 {"alias", TALIAS},
344 {"auto", TAUTO},
345 {"block", TBLOCK},
346 {"ca", TCA},
347 {"cert", TCERT},
348 {"cgi", TCGI},
349 {"chroot", TCHROOT},
350 {"client", TCLIENT},
351 {"default", TDEFAULT},
352 {"entrypoint", TENTRYPOINT},
353 {"env", TENV},
354 {"fastcgi", TFASTCGI},
355 {"index", TINDEX},
356 {"ipv6", TIPV6},
357 {"key", TKEY},
358 {"lang", TLANG},
359 {"location", TLOCATION},
360 {"log", TLOG},
361 {"mime", TMIME},
362 {"param", TPARAM},
363 {"port", TPORT},
364 {"prefork", TPREFORK},
365 {"protocols", TPROTOCOLS},
366 {"require", TREQUIRE},
367 {"return", TRETURN},
368 {"root", TROOT},
369 {"server", TSERVER},
370 {"spawn", TSPAWN},
371 {"strip", TSTRIP},
372 {"tcp", TTCP},
373 {"type", TTYPE},
374 {"user", TUSER},
375 };
377 /*
378 * Taken an adapted from doas' parse.y
379 */
380 static int
381 yylex(void)
383 char buf[8096], *ebuf, *p, *str, *v, *val;
384 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
385 size_t i, len;
387 p = buf;
388 ebuf = buf + sizeof(buf);
390 repeat:
391 /* skip whitespace first */
392 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
393 yylval.colno++;
394 if (c == '\n') {
395 yylval.lineno++;
396 yylval.colno = 0;
400 /* check for special one-character constructions */
401 switch (c) {
402 case '{':
403 case '}':
404 return c;
405 case '#':
406 /* skip comments; NUL is allowed; no continuation */
407 while ((c = getc(yyfp)) != '\n')
408 if (c == EOF)
409 goto eof;
410 yylval.colno = 0;
411 yylval.lineno++;
412 goto repeat;
413 case '=':
414 return c;
415 case EOF:
416 goto eof;
419 /* parsing next word */
420 for (;; c = getc(yyfp), yylval.colno++) {
421 switch (c) {
422 case '\0':
423 yyerror("unallowed character NULL in column %d",
424 yylval.colno+1);
425 escape = 0;
426 continue;
427 case '\\':
428 escape = !escape;
429 if (escape)
430 continue;
431 break;
433 /* expand macros in-place */
434 case '$':
435 if (!escape && !quotes) {
436 v = p;
437 while (1) {
438 if ((c = getc(yyfp)) == EOF) {
439 yyerror("EOF during macro expansion");
440 return 0;
442 if (p + 1 >= ebuf - 1) {
443 yyerror("string too long");
444 return 0;
446 if (isalnum(c) || c == '_') {
447 *p++ = c;
448 continue;
450 *p = 0;
451 break;
453 p = v;
454 if ((val = symget(p)) == NULL) {
455 yyerror("macro '%s' not defined", v);
456 return TERR;
458 len = strlen(val);
459 if (p + len >= ebuf - 1) {
460 yyerror("after macro-expansion, "
461 "string too long");
462 return TERR;
464 *p = '\0';
465 strlcat(p, val, ebuf - p);
466 p += len;
467 nonkw = 1;
468 goto eow;
470 break;
471 case '\n':
472 if (quotes)
473 yyerror("unterminated quotes in column %d",
474 yylval.colno+1);
475 if (escape) {
476 nonkw = 1;
477 escape = 0;
478 yylval.colno = 0;
479 yylval.lineno++;
481 goto eow;
482 case EOF:
483 if (escape)
484 yyerror("unterminated escape in column %d",
485 yylval.colno);
486 if (quotes)
487 yyerror("unterminated quotes in column %d",
488 qpos+1);
489 goto eow;
490 case '{':
491 case '}':
492 case '#':
493 case ' ':
494 case '\t':
495 if (!escape && !quotes)
496 goto eow;
497 break;
498 case '"':
499 if (!escape) {
500 quotes = !quotes;
501 if (quotes) {
502 nonkw = 1;
503 qpos = yylval.colno;
505 continue;
508 *p++ = c;
509 if (p == ebuf) {
510 yyerror("line too long");
511 p = buf;
513 escape = 0;
516 eow:
517 *p = 0;
518 if (c != EOF)
519 ungetc(c, yyfp);
520 if (p == buf) {
521 /*
522 * There could be a number of reason for empty buffer,
523 * and we handle all of them here, to avoid cluttering
524 * the main loop.
525 */
526 if (c == EOF)
527 goto eof;
528 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
529 goto repeat;
531 if (!nonkw) {
532 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) {
533 if (!strcmp(buf, keywords[i].word))
534 return keywords[i].token;
537 c = *buf;
538 if (!nonkw && (c == '-' || isdigit(c))) {
539 yylval.v.num = parse_portno(buf);
540 return TNUM;
542 if (!nonkw && !strcmp(buf, "on")) {
543 yylval.v.num = 1;
544 return TBOOL;
546 if (!nonkw && !strcmp(buf, "off")) {
547 yylval.v.num = 0;
548 return TBOOL;
550 if ((str = strdup(buf)) == NULL)
551 err(1, "%s", __func__);
552 yylval.v.str = str;
553 return TSTRING;
555 eof:
556 if (ferror(yyfp))
557 yyerror("input error reading config");
558 return 0;
561 int
562 parse_portno(const char *p)
564 const char *errstr;
565 int n;
567 n = strtonum(p, 0, UINT16_MAX, &errstr);
568 if (errstr != NULL)
569 yyerror("port number is %s: %s", errstr, p);
570 return n;
573 void
574 parse_conf(const char *path)
576 struct sym *sym, *next;
578 config_path = path;
579 if ((yyfp = fopen(path, "r")) == NULL)
580 err(1, "cannot open config: %s", path);
581 yyparse();
582 fclose(yyfp);
584 if (goterror)
585 exit(1);
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 */
593 if (!sym->persist) {
594 free(sym->name);
595 free(sym->val);
596 TAILQ_REMOVE(&symhead, sym, entry);
597 free(sym);
602 char *
603 ensure_absolute_path(char *path)
605 if (path == NULL || *path != '/')
606 yyerror("not an absolute path: %s", path);
607 return path;
610 int
611 check_block_code(int n)
613 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
614 yyerror("invalid block code %d", n);
615 return n;
618 char *
619 check_block_fmt(char *fmt)
621 char *s;
623 for (s = fmt; *s; ++s) {
624 if (*s != '%')
625 continue;
626 switch (*++s) {
627 case '%':
628 case 'p':
629 case 'q':
630 case 'P':
631 case 'N':
632 break;
633 default:
634 yyerror("invalid format specifier %%%c", *s);
638 return fmt;
641 int
642 check_strip_no(int n)
644 if (n <= 0)
645 yyerror("invalid strip number %d", n);
646 return n;
649 int
650 check_prefork_num(int n)
652 if (n <= 0 || n >= PROC_MAX)
653 yyerror("invalid prefork number %d", n);
654 return n;
657 void
658 advance_loc(void)
660 loc = new_location();
661 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
664 void
665 only_once(const void *ptr, const char *name)
667 if (ptr != NULL)
668 yyerror("`%s' specified more than once", name);
671 void
672 only_oncei(int i, const char *name)
674 if (i != -1)
675 yyerror("`%s' specified more than once", name);
678 int
679 fastcgi_conf(char *path, char *port, char *prog)
681 struct fcgi *f;
682 int i;
684 for (i = 0; i < FCGI_MAX; ++i) {
685 f = &fcgi[i];
687 if (f->path == NULL) {
688 f->id = i;
689 f->path = path;
690 f->port = port;
691 f->prog = prog;
692 return i;
695 /* XXX: what to do with prog? */
696 if (!strcmp(f->path, path) &&
697 ((port == NULL && f->port == NULL) ||
698 !strcmp(f->port, port))) {
699 free(path);
700 free(port);
701 return i;
705 yyerror("too much `fastcgi' rules defined.");
706 return -1;
709 void
710 add_param(char *name, char *val, int env)
712 struct envlist *e;
713 struct envhead *h;
715 if (env)
716 h = &host->env;
717 else
718 h = &host->params;
720 e = xcalloc(1, sizeof(*e));
721 e->name = name;
722 e->value = val;
723 if (TAILQ_EMPTY(h))
724 TAILQ_INSERT_HEAD(h, e, envs);
725 else
726 TAILQ_INSERT_TAIL(h, e, envs);
729 int
730 symset(const char *name, const char *val, int persist)
732 struct sym *sym;
734 TAILQ_FOREACH(sym, &symhead, entry) {
735 if (!strcmp(name, sym->name))
736 break;
739 if (sym != NULL) {
740 if (sym->persist)
741 return 0;
742 else {
743 free(sym->name);
744 free(sym->val);
745 TAILQ_REMOVE(&symhead, sym, entry);
746 free(sym);
750 sym = xcalloc(1, sizeof(*sym));
751 sym->name = xstrdup(name);
752 sym->val = xstrdup(val);
753 sym->used = 0;
754 sym->persist = persist;
756 TAILQ_INSERT_TAIL(&symhead, sym, entry);
757 return 0;
760 int
761 cmdline_symset(char *s)
763 char *sym, *val;
764 int ret;
766 if ((val = strrchr(s, '=')) == NULL)
767 return -1;
768 sym = xcalloc(1, val - s + 1);
769 memcpy(sym, s, val - s);
770 ret = symset(sym, val + 1, 1);
771 free(sym);
772 return ret;
775 char *
776 symget(const char *name)
778 struct sym *sym;
780 TAILQ_FOREACH(sym, &symhead, entry) {
781 if (!strcmp(name, sym->name)) {
782 sym->used = 1;
783 return sym->val;
787 return NULL;