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 top:
420 /* parsing next word */
421 for (;; c = getc(yyfp), yylval.colno++) {
422 switch (c) {
423 case '\0':
424 yyerror("unallowed character NULL in column %d",
425 yylval.colno+1);
426 escape = 0;
427 continue;
428 case '\\':
429 escape = !escape;
430 if (escape)
431 continue;
432 break;
434 /* expand macros in-place */
435 case '$':
436 if (!escape) {
437 v = p;
438 while (1) {
439 if ((c = getc(yyfp)) == EOF) {
440 yyerror("EOF during macro expansion");
441 return 0;
443 if (p + 1 >= ebuf - 1) {
444 yyerror("string too long");
445 return 0;
447 if (isalnum(c) || c == '_') {
448 *p++ = c;
449 continue;
451 *p = 0;
452 ungetc(c, yyfp);
453 break;
455 p = v;
456 if ((val = symget(p)) == NULL) {
457 yyerror("macro '%s' not defined", v);
458 goto top;
460 len = strlen(val);
461 if (p + len >= ebuf - 1) {
462 yyerror("after macro-expansion, "
463 "string too long");
464 goto top;
466 *p = '\0';
467 strlcat(p, val, ebuf - p);
468 p += len;
469 goto top;
471 break;
472 case '\n':
473 if (quotes)
474 yyerror("unterminated quotes in column %d",
475 yylval.colno+1);
476 if (escape) {
477 nonkw = 1;
478 escape = 0;
479 yylval.colno = 0;
480 yylval.lineno++;
482 goto eow;
483 case EOF:
484 if (escape)
485 yyerror("unterminated escape in column %d",
486 yylval.colno);
487 if (quotes)
488 yyerror("unterminated quotes in column %d",
489 qpos+1);
490 goto eow;
491 case '{':
492 case '}':
493 case '#':
494 case ' ':
495 case '\t':
496 if (!escape && !quotes)
497 goto eow;
498 break;
499 case '"':
500 if (!escape) {
501 quotes = !quotes;
502 if (quotes) {
503 nonkw = 1;
504 qpos = yylval.colno;
506 continue;
509 *p++ = c;
510 if (p == ebuf) {
511 yyerror("line too long");
512 p = buf;
514 escape = 0;
517 eow:
518 *p = 0;
519 if (c != EOF)
520 ungetc(c, yyfp);
521 if (p == buf) {
522 /*
523 * There could be a number of reason for empty buffer,
524 * and we handle all of them here, to avoid cluttering
525 * the main loop.
526 */
527 if (c == EOF)
528 goto eof;
529 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
530 goto repeat;
532 if (!nonkw) {
533 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) {
534 if (!strcmp(buf, keywords[i].word))
535 return keywords[i].token;
538 c = *buf;
539 if (!nonkw && (c == '-' || isdigit(c))) {
540 yylval.v.num = parse_portno(buf);
541 return TNUM;
543 if (!nonkw && !strcmp(buf, "on")) {
544 yylval.v.num = 1;
545 return TBOOL;
547 if (!nonkw && !strcmp(buf, "off")) {
548 yylval.v.num = 0;
549 return TBOOL;
551 if ((str = strdup(buf)) == NULL)
552 err(1, "%s", __func__);
553 yylval.v.str = str;
554 return TSTRING;
556 eof:
557 if (ferror(yyfp))
558 yyerror("input error reading config");
559 return 0;
562 int
563 parse_portno(const char *p)
565 const char *errstr;
566 int n;
568 n = strtonum(p, 0, UINT16_MAX, &errstr);
569 if (errstr != NULL)
570 yyerror("port number is %s: %s", errstr, p);
571 return n;
574 void
575 parse_conf(const char *path)
577 struct sym *sym, *next;
579 config_path = path;
580 if ((yyfp = fopen(path, "r")) == NULL)
581 err(1, "cannot open config: %s", path);
582 yyparse();
583 fclose(yyfp);
585 if (goterror)
586 exit(1);
588 if (TAILQ_FIRST(&hosts)->domain == NULL)
589 errx(1, "no vhost defined in %s", path);
591 /* free unused macros */
592 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
593 /* TODO: warn if !sym->used */
594 if (!sym->persist) {
595 free(sym->name);
596 free(sym->val);
597 TAILQ_REMOVE(&symhead, sym, entry);
598 free(sym);
603 char *
604 ensure_absolute_path(char *path)
606 if (path == NULL || *path != '/')
607 yyerror("not an absolute path: %s", path);
608 return path;
611 int
612 check_block_code(int n)
614 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
615 yyerror("invalid block code %d", n);
616 return n;
619 char *
620 check_block_fmt(char *fmt)
622 char *s;
624 for (s = fmt; *s; ++s) {
625 if (*s != '%')
626 continue;
627 switch (*++s) {
628 case '%':
629 case 'p':
630 case 'q':
631 case 'P':
632 case 'N':
633 break;
634 default:
635 yyerror("invalid format specifier %%%c", *s);
639 return fmt;
642 int
643 check_strip_no(int n)
645 if (n <= 0)
646 yyerror("invalid strip number %d", n);
647 return n;
650 int
651 check_prefork_num(int n)
653 if (n <= 0 || n >= PROC_MAX)
654 yyerror("invalid prefork number %d", n);
655 return n;
658 void
659 advance_loc(void)
661 loc = new_location();
662 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
665 void
666 only_once(const void *ptr, const char *name)
668 if (ptr != NULL)
669 yyerror("`%s' specified more than once", name);
672 void
673 only_oncei(int i, const char *name)
675 if (i != -1)
676 yyerror("`%s' specified more than once", name);
679 int
680 fastcgi_conf(char *path, char *port, char *prog)
682 struct fcgi *f;
683 int i;
685 for (i = 0; i < FCGI_MAX; ++i) {
686 f = &fcgi[i];
688 if (f->path == NULL) {
689 f->id = i;
690 f->path = path;
691 f->port = port;
692 f->prog = prog;
693 return i;
696 /* XXX: what to do with prog? */
697 if (!strcmp(f->path, path) &&
698 ((port == NULL && f->port == NULL) ||
699 !strcmp(f->port, port))) {
700 free(path);
701 free(port);
702 return i;
706 yyerror("too much `fastcgi' rules defined.");
707 return -1;
710 void
711 add_param(char *name, char *val, int env)
713 struct envlist *e;
714 struct envhead *h;
716 if (env)
717 h = &host->env;
718 else
719 h = &host->params;
721 e = xcalloc(1, sizeof(*e));
722 e->name = name;
723 e->value = val;
724 if (TAILQ_EMPTY(h))
725 TAILQ_INSERT_HEAD(h, e, envs);
726 else
727 TAILQ_INSERT_TAIL(h, e, envs);
730 int
731 symset(const char *name, const char *val, int persist)
733 struct sym *sym;
735 TAILQ_FOREACH(sym, &symhead, entry) {
736 if (!strcmp(name, sym->name))
737 break;
740 if (sym != NULL) {
741 if (sym->persist)
742 return 0;
743 else {
744 free(sym->name);
745 free(sym->val);
746 TAILQ_REMOVE(&symhead, sym, entry);
747 free(sym);
751 sym = xcalloc(1, sizeof(*sym));
752 sym->name = xstrdup(name);
753 sym->val = xstrdup(val);
754 sym->used = 0;
755 sym->persist = persist;
757 TAILQ_INSERT_TAIL(&symhead, sym, entry);
758 return 0;
761 int
762 cmdline_symset(char *s)
764 char *sym, *val;
765 int ret;
767 if ((val = strrchr(s, '=')) == NULL)
768 return -1;
769 sym = xcalloc(1, val - s + 1);
770 memcpy(sym, s, val - s);
771 ret = symset(sym, val + 1, 1);
772 free(sym);
773 return ret;
776 char *
777 symget(const char *name)
779 struct sym *sym;
781 TAILQ_FOREACH(sym, &symhead, entry) {
782 if (!strcmp(name, sym->name)) {
783 sym->used = 1;
784 return sym->val;
788 return NULL;