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 %%
100 conf : /* empty */
101 | conf var
102 | conf option
103 | conf vhost
106 var : TSTRING '=' TSTRING {
107 char *s = $1;
108 while (*s++) {
109 if (isspace(*s)) {
110 yyerror("macro name cannot contain "
111 "whitespaces");
112 free($1);
113 free($3);
114 YYERROR;
117 symset($1, $3, 0);
118 free($1);
119 free($3);
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 {
136 host = new_vhost();
137 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
139 loc = new_location();
140 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
142 loc->match = xstrdup("*");
143 host->domain = $2;
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,
149 $2);
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 */
160 | servopts servopt
163 servopt : TALIAS TSTRING {
164 struct alist *a;
166 a = xcalloc(1, sizeof(*a));
167 a->alias = $2;
168 if (TAILQ_EMPTY(&host->aliases))
169 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
170 else
171 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
173 | TCERT TSTRING {
174 only_once(host->cert, "cert");
175 host->cert = ensure_absolute_path($2);
177 | TCGI TSTRING {
178 only_once(host->cgi, "cgi");
179 /* drop the starting '/', if any */
180 if (*$2 == '/')
181 memmove($2, $2+1, strlen($2));
182 host->cgi = $2;
184 | TENTRYPOINT TSTRING {
185 only_once(host->entrypoint, "entrypoint");
186 while (*$2 == '/')
187 memmove($2, $2+1, strlen($2));
188 host->entrypoint = $2;
190 | TENV TSTRING TSTRING {
191 add_param($2, $3, 1);
193 | TKEY TSTRING {
194 only_once(host->key, "key");
195 host->key = ensure_absolute_path($2);
197 | TPARAM TSTRING TSTRING {
198 add_param($2, $3, 0);
200 | locopt
203 locations : /* empty */
204 | locations location
207 location : TLOCATION { advance_loc(); } TSTRING '{' locopts '}' {
208 /* drop the starting '/' if any */
209 if (*$3 == '/')
210 memmove($3, $3+1, strlen($3));
211 loc->match = $3;
213 | error '}'
216 locopts : /* empty */
217 | locopts locopt
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);
233 | TBLOCK {
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;
242 | TFASTCGI fastcgi
243 | TINDEX TSTRING {
244 only_once(loc->index, "index");
245 loc->index = $2;
247 | TLANG TSTRING {
248 only_once(loc->lang, "lang");
249 loc->lang = $2;
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);
257 free($4);
259 | TROOT TSTRING {
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);
270 | TSTRING {
271 only_oncei(loc->fcgi, "fastcgi");
272 loc->fcgi = fastcgi_conf($1, NULL, NULL);
274 | TTCP TSTRING TNUM {
275 char *c;
276 if (asprintf(&c, "%d", $3) == -1)
277 err(1, "asprintf");
278 only_oncei(loc->fcgi, "fastcgi");
279 loc->fcgi = fastcgi_conf($2, c, NULL);
281 | TTCP TSTRING {
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);
291 %%
293 static struct vhost *
294 new_vhost(void)
296 return xcalloc(1, sizeof(struct vhost));
299 static struct location *
300 new_location(void)
302 struct location *l;
304 l = xcalloc(1, sizeof(*l));
305 l->dirfd = -1;
306 l->fcgi = -1;
307 return l;
310 void
311 yyerror(const char *msg, ...)
313 va_list ap;
315 goterror = 1;
317 va_start(ap, msg);
318 fprintf(stderr, "%s:%d: ", config_path, yylval.lineno);
319 vfprintf(stderr, msg, ap);
320 fprintf(stderr, "\n");
321 va_end(ap);
324 static struct keyword {
325 const char *word;
326 int token;
327 } keywords[] = {
328 {"alias", TALIAS},
329 {"auto", TAUTO},
330 {"block", TBLOCK},
331 {"ca", TCA},
332 {"cert", TCERT},
333 {"cgi", TCGI},
334 {"chroot", TCHROOT},
335 {"client", TCLIENT},
336 {"default", TDEFAULT},
337 {"entrypoint", TENTRYPOINT},
338 {"env", TENV},
339 {"fastcgi", TFASTCGI},
340 {"index", TINDEX},
341 {"ipv6", TIPV6},
342 {"key", TKEY},
343 {"lang", TLANG},
344 {"location", TLOCATION},
345 {"log", TLOG},
346 {"mime", TMIME},
347 {"param", TPARAM},
348 {"port", TPORT},
349 {"prefork", TPREFORK},
350 {"protocols", TPROTOCOLS},
351 {"require", TREQUIRE},
352 {"return", TRETURN},
353 {"root", TROOT},
354 {"server", TSERVER},
355 {"spawn", TSPAWN},
356 {"strip", TSTRIP},
357 {"tcp", TTCP},
358 {"type", TTYPE},
359 {"user", TUSER},
360 };
362 /*
363 * Taken an adapted from doas' parse.y
364 */
365 static int
366 yylex(void)
368 char buf[8096], *ebuf, *p, *str, *v, *val;
369 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
370 size_t i, len;
372 p = buf;
373 ebuf = buf + sizeof(buf);
375 repeat:
376 /* skip whitespace first */
377 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
378 yylval.colno++;
379 if (c == '\n') {
380 yylval.lineno++;
381 yylval.colno = 0;
385 /* check for special one-character constructions */
386 switch (c) {
387 case '{':
388 case '}':
389 return c;
390 case '#':
391 /* skip comments; NUL is allowed; no continuation */
392 while ((c = getc(yyfp)) != '\n')
393 if (c == EOF)
394 goto eof;
395 yylval.colno = 0;
396 yylval.lineno++;
397 goto repeat;
398 case '=':
399 return c;
400 case EOF:
401 goto eof;
404 top:
405 /* parsing next word */
406 for (;; c = getc(yyfp), yylval.colno++) {
407 switch (c) {
408 case '\0':
409 yyerror("unallowed character NULL in column %d",
410 yylval.colno+1);
411 escape = 0;
412 continue;
413 case '\\':
414 escape = !escape;
415 if (escape)
416 continue;
417 break;
419 /* expand macros in-place */
420 case '$':
421 if (!escape) {
422 v = p;
423 while (1) {
424 if ((c = getc(yyfp)) == EOF) {
425 yyerror("EOF during macro expansion");
426 return 0;
428 if (p + 1 >= ebuf - 1) {
429 yyerror("string too long");
430 return 0;
432 if (isalnum(c) || c == '_') {
433 *p++ = c;
434 continue;
436 *p = 0;
437 ungetc(c, yyfp);
438 break;
440 p = v;
441 if ((val = symget(p)) == NULL) {
442 yyerror("macro '%s' not defined", v);
443 goto top;
445 len = strlen(val);
446 if (p + len >= ebuf - 1) {
447 yyerror("after macro-expansion, "
448 "string too long");
449 goto top;
451 *p = '\0';
452 strlcat(p, val, ebuf - p);
453 p += len;
454 goto top;
456 break;
457 case '\n':
458 if (quotes)
459 yyerror("unterminated quotes in column %d",
460 yylval.colno+1);
461 if (escape) {
462 nonkw = 1;
463 escape = 0;
464 yylval.colno = 0;
465 yylval.lineno++;
467 goto eow;
468 case EOF:
469 if (escape)
470 yyerror("unterminated escape in column %d",
471 yylval.colno);
472 if (quotes)
473 yyerror("unterminated quotes in column %d",
474 qpos+1);
475 goto eow;
476 case '{':
477 case '}':
478 case '#':
479 case ' ':
480 case '\t':
481 if (!escape && !quotes)
482 goto eow;
483 break;
484 case '"':
485 if (!escape) {
486 quotes = !quotes;
487 if (quotes) {
488 nonkw = 1;
489 qpos = yylval.colno;
491 continue;
494 *p++ = c;
495 if (p == ebuf) {
496 yyerror("line too long");
497 p = buf;
499 escape = 0;
502 eow:
503 *p = 0;
504 if (c != EOF)
505 ungetc(c, yyfp);
506 if (p == buf) {
507 /*
508 * There could be a number of reason for empty buffer,
509 * and we handle all of them here, to avoid cluttering
510 * the main loop.
511 */
512 if (c == EOF)
513 goto eof;
514 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
515 goto repeat;
517 if (!nonkw) {
518 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) {
519 if (!strcmp(buf, keywords[i].word))
520 return keywords[i].token;
523 c = *buf;
524 if (!nonkw && (c == '-' || isdigit(c))) {
525 yylval.v.num = parse_portno(buf);
526 return TNUM;
528 if (!nonkw && !strcmp(buf, "on")) {
529 yylval.v.num = 1;
530 return TBOOL;
532 if (!nonkw && !strcmp(buf, "off")) {
533 yylval.v.num = 0;
534 return TBOOL;
536 if ((str = strdup(buf)) == NULL)
537 err(1, "%s", __func__);
538 yylval.v.str = str;
539 return TSTRING;
541 eof:
542 if (ferror(yyfp))
543 yyerror("input error reading config");
544 return 0;
547 int
548 parse_portno(const char *p)
550 const char *errstr;
551 int n;
553 n = strtonum(p, 0, UINT16_MAX, &errstr);
554 if (errstr != NULL)
555 yyerror("port number is %s: %s", errstr, p);
556 return n;
559 void
560 parse_conf(const char *path)
562 struct sym *sym, *next;
564 config_path = path;
565 if ((yyfp = fopen(path, "r")) == NULL)
566 err(1, "cannot open config: %s", path);
567 yyparse();
568 fclose(yyfp);
570 if (goterror)
571 exit(1);
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 */
579 if (!sym->persist) {
580 free(sym->name);
581 free(sym->val);
582 TAILQ_REMOVE(&symhead, sym, entry);
583 free(sym);
588 char *
589 ensure_absolute_path(char *path)
591 if (path == NULL || *path != '/')
592 yyerror("not an absolute path: %s", path);
593 return path;
596 int
597 check_block_code(int n)
599 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
600 yyerror("invalid block code %d", n);
601 return n;
604 char *
605 check_block_fmt(char *fmt)
607 char *s;
609 for (s = fmt; *s; ++s) {
610 if (*s != '%')
611 continue;
612 switch (*++s) {
613 case '%':
614 case 'p':
615 case 'q':
616 case 'P':
617 case 'N':
618 break;
619 default:
620 yyerror("invalid format specifier %%%c", *s);
624 return fmt;
627 int
628 check_strip_no(int n)
630 if (n <= 0)
631 yyerror("invalid strip number %d", n);
632 return n;
635 int
636 check_prefork_num(int n)
638 if (n <= 0 || n >= PROC_MAX)
639 yyerror("invalid prefork number %d", n);
640 return n;
643 void
644 advance_loc(void)
646 loc = new_location();
647 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
650 void
651 only_once(const void *ptr, const char *name)
653 if (ptr != NULL)
654 yyerror("`%s' specified more than once", name);
657 void
658 only_oncei(int i, const char *name)
660 if (i != -1)
661 yyerror("`%s' specified more than once", name);
664 int
665 fastcgi_conf(char *path, char *port, char *prog)
667 struct fcgi *f;
668 int i;
670 for (i = 0; i < FCGI_MAX; ++i) {
671 f = &fcgi[i];
673 if (f->path == NULL) {
674 f->id = i;
675 f->path = path;
676 f->port = port;
677 f->prog = prog;
678 return i;
681 /* XXX: what to do with prog? */
682 if (!strcmp(f->path, path) &&
683 ((port == NULL && f->port == NULL) ||
684 !strcmp(f->port, port))) {
685 free(path);
686 free(port);
687 return i;
691 yyerror("too much `fastcgi' rules defined.");
692 return -1;
695 void
696 add_param(char *name, char *val, int env)
698 struct envlist *e;
699 struct envhead *h;
701 if (env)
702 h = &host->env;
703 else
704 h = &host->params;
706 e = xcalloc(1, sizeof(*e));
707 e->name = name;
708 e->value = val;
709 if (TAILQ_EMPTY(h))
710 TAILQ_INSERT_HEAD(h, e, envs);
711 else
712 TAILQ_INSERT_TAIL(h, e, envs);
715 int
716 symset(const char *name, const char *val, int persist)
718 struct sym *sym;
720 TAILQ_FOREACH(sym, &symhead, entry) {
721 if (!strcmp(name, sym->name))
722 break;
725 if (sym != NULL) {
726 if (sym->persist)
727 return 0;
728 else {
729 free(sym->name);
730 free(sym->val);
731 TAILQ_REMOVE(&symhead, sym, entry);
732 free(sym);
736 sym = xcalloc(1, sizeof(*sym));
737 sym->name = xstrdup(name);
738 sym->val = xstrdup(val);
739 sym->used = 0;
740 sym->persist = persist;
742 TAILQ_INSERT_TAIL(&symhead, sym, entry);
743 return 0;
746 int
747 cmdline_symset(char *s)
749 char *sym, *val;
750 int ret;
752 if ((val = strrchr(s, '=')) == NULL)
753 return -1;
754 sym = xcalloc(1, val - s + 1);
755 memcpy(sym, s, val - s);
756 ret = symset(sym, val + 1, 1);
757 free(sym);
758 return ret;
761 char *
762 symget(const char *name)
764 struct sym *sym;
766 TAILQ_FOREACH(sym, &symhead, entry) {
767 if (!strcmp(name, sym->name)) {
768 sym->used = 1;
769 return sym->val;
773 return NULL;