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 int kw_cmp(const void *, const void *);
67 static int yylex(void);
68 int parse_portno(const char*);
69 void parse_conf(const char*);
70 char *ensure_absolute_path(char*);
71 int check_block_code(int);
72 char *check_block_fmt(char*);
73 int check_strip_no(int);
74 int check_prefork_num(int);
75 void advance_loc(void);
76 void only_once(const void*, const char*);
77 void only_oncei(int, const char*);
78 int fastcgi_conf(char *, char *, char *);
79 void add_param(char *, char *, int);
80 int symset(const char *, const char *, int);
81 char *symget(const char *);
83 %}
85 /* for bison: */
86 /* %define parse.error verbose */
88 %token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TCHROOT TUSER TSERVER
89 %token TPREFORK TLOCATION TCERT TKEY TROOT TCGI TENV TLANG TLOG TINDEX TAUTO
90 %token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS TTCP
91 %token TFASTCGI TSPAWN TPARAM TMAP TTOEXT TARROW
93 %token TERR
95 %token <v.str> TSTRING
96 %token <v.num> TNUM
97 %token <v.num> TBOOL
99 %type <v.str> string
101 %%
103 conf : /* empty */
104 | conf var
105 | conf option
106 | conf vhost
109 string : string TSTRING {
110 if (asprintf(&$$, "%s%s", $1, $2) == -1) {
111 free($1);
112 free($2);
113 yyerror("string: asprintf: %s", strerror(errno));
114 YYERROR;
116 free($1);
117 free($2);
119 | TSTRING
122 var : TSTRING '=' string {
123 char *s = $1;
124 while (*s++) {
125 if (isspace(*s)) {
126 yyerror("macro name cannot contain "
127 "whitespaces");
128 free($1);
129 free($3);
130 YYERROR;
133 symset($1, $3, 0);
134 free($1);
135 free($3);
139 option : TCHROOT string { conf.chroot = $2; }
140 | TIPV6 TBOOL { conf.ipv6 = $2; }
141 | TMIME TSTRING string {
142 fprintf(stderr, "%s:%d: `mime MIME EXT' is deprecated and "
143 "will be removed in a future version, "
144 "please use the new syntax: `map MIME to-ext EXT'",
145 config_path, yylval.lineno+1);
146 add_mime(&conf.mime, $2, $3);
148 | TMAP string TTOEXT string { add_mime(&conf.mime, $2, $4); }
149 | TPORT TNUM { conf.port = $2; }
150 | TPREFORK TNUM { conf.prefork = check_prefork_num($2); }
151 | TPROTOCOLS string {
152 if (tls_config_parse_protocols(&conf.protos, $2) == -1)
153 yyerror("invalid protocols string \"%s\"", $2);
155 | TUSER string { conf.user = $2; }
158 vhost : TSERVER string {
159 host = new_vhost();
160 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
162 loc = new_location();
163 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
165 loc->match = xstrdup("*");
166 host->domain = $2;
168 if (strstr($2, "xn--") != NULL) {
169 warnx("%s:%d:%d \"%s\" looks like punycode: "
170 "you should use the decoded hostname.",
171 config_path, yylval.lineno+1, yylval.colno,
172 $2);
174 } '{' servopts locations '}' {
176 if (host->cert == NULL || host->key == NULL)
177 yyerror("invalid vhost definition: %s", $2);
179 | error '}' { yyerror("error in server directive"); }
182 servopts : /* empty */
183 | servopts servopt
186 servopt : TALIAS string {
187 struct alist *a;
189 a = xcalloc(1, sizeof(*a));
190 a->alias = $2;
191 if (TAILQ_EMPTY(&host->aliases))
192 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
193 else
194 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
196 | TCERT string {
197 only_once(host->cert, "cert");
198 host->cert = ensure_absolute_path($2);
200 | TCGI string {
201 only_once(host->cgi, "cgi");
202 /* drop the starting '/', if any */
203 if (*$2 == '/')
204 memmove($2, $2+1, strlen($2));
205 host->cgi = $2;
207 | TENTRYPOINT string {
208 only_once(host->entrypoint, "entrypoint");
209 while (*$2 == '/')
210 memmove($2, $2+1, strlen($2));
211 host->entrypoint = $2;
213 | TENV string TARROW string {
214 add_param($2, $4, 1);
216 | TKEY string {
217 only_once(host->key, "key");
218 host->key = ensure_absolute_path($2);
220 | TPARAM string TARROW string {
221 add_param($2, $4, 0);
223 | locopt
226 locations : /* empty */
227 | locations location
230 location : TLOCATION { advance_loc(); } string '{' locopts '}' {
231 /* drop the starting '/' if any */
232 if (*$3 == '/')
233 memmove($3, $3+1, strlen($3));
234 loc->match = $3;
236 | error '}'
239 locopts : /* empty */
240 | locopts locopt
243 locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
244 | TBLOCK TRETURN TNUM string {
245 only_once(loc->block_fmt, "block");
246 loc->block_fmt = check_block_fmt($4);
247 loc->block_code = check_block_code($3);
249 | TBLOCK TRETURN TNUM {
250 only_once(loc->block_fmt, "block");
251 loc->block_fmt = xstrdup("temporary failure");
252 loc->block_code = check_block_code($3);
253 if ($3 >= 30 && $3 < 40)
254 yyerror("missing `meta' for block return %d", $3);
256 | TBLOCK {
257 only_once(loc->block_fmt, "block");
258 loc->block_fmt = xstrdup("temporary failure");
259 loc->block_code = 40;
261 | TDEFAULT TTYPE string {
262 only_once(loc->default_mime, "default type");
263 loc->default_mime = $3;
265 | TFASTCGI fastcgi
266 | TINDEX string {
267 only_once(loc->index, "index");
268 loc->index = $2;
270 | TLANG string {
271 only_once(loc->lang, "lang");
272 loc->lang = $2;
274 | TLOG TBOOL { loc->disable_log = !$2; }
275 | TREQUIRE TCLIENT TCA string {
276 only_once(loc->reqca, "require client ca");
277 ensure_absolute_path($4);
278 if ((loc->reqca = load_ca($4)) == NULL)
279 yyerror("couldn't load ca cert: %s", $4);
280 free($4);
282 | TROOT string {
283 only_once(loc->dir, "root");
284 loc->dir = ensure_absolute_path($2);
286 | TSTRIP TNUM { loc->strip = check_strip_no($2); }
289 fastcgi : TSPAWN string {
290 only_oncei(loc->fcgi, "fastcgi");
291 loc->fcgi = fastcgi_conf(NULL, NULL, $2);
293 | string {
294 only_oncei(loc->fcgi, "fastcgi");
295 loc->fcgi = fastcgi_conf($1, NULL, NULL);
297 | TTCP string TPORT TNUM {
298 char *c;
299 if (asprintf(&c, "%d", $4) == -1)
300 err(1, "asprintf");
301 only_oncei(loc->fcgi, "fastcgi");
302 loc->fcgi = fastcgi_conf($2, c, NULL);
304 | TTCP string {
305 only_oncei(loc->fcgi, "fastcgi");
306 loc->fcgi = fastcgi_conf($2, xstrdup("9000"), NULL);
308 | TTCP string TPORT string {
309 only_oncei(loc->fcgi, "fastcgi");
310 loc->fcgi = fastcgi_conf($2, $4, NULL);
314 %%
316 static struct vhost *
317 new_vhost(void)
319 return xcalloc(1, sizeof(struct vhost));
322 static struct location *
323 new_location(void)
325 struct location *l;
327 l = xcalloc(1, sizeof(*l));
328 l->dirfd = -1;
329 l->fcgi = -1;
330 return l;
333 void
334 yyerror(const char *msg, ...)
336 va_list ap;
338 goterror = 1;
340 va_start(ap, msg);
341 fprintf(stderr, "%s:%d: ", config_path, yylval.lineno);
342 vfprintf(stderr, msg, ap);
343 fprintf(stderr, "\n");
344 va_end(ap);
347 static struct keyword {
348 const char *word;
349 int token;
350 } keywords[] = {
351 /* these MUST be sorted */
352 {"alias", TALIAS},
353 {"auto", TAUTO},
354 {"block", TBLOCK},
355 {"ca", TCA},
356 {"cert", TCERT},
357 {"cgi", TCGI},
358 {"chroot", TCHROOT},
359 {"client", TCLIENT},
360 {"default", TDEFAULT},
361 {"entrypoint", TENTRYPOINT},
362 {"env", TENV},
363 {"fastcgi", TFASTCGI},
364 {"index", TINDEX},
365 {"ipv6", TIPV6},
366 {"key", TKEY},
367 {"lang", TLANG},
368 {"location", TLOCATION},
369 {"log", TLOG},
370 {"map", TMAP},
371 {"mime", TMIME},
372 {"param", TPARAM},
373 {"port", TPORT},
374 {"prefork", TPREFORK},
375 {"protocols", TPROTOCOLS},
376 {"require", TREQUIRE},
377 {"return", TRETURN},
378 {"root", TROOT},
379 {"server", TSERVER},
380 {"spawn", TSPAWN},
381 {"strip", TSTRIP},
382 {"tcp", TTCP},
383 {"to-ext", TTOEXT},
384 {"type", TTYPE},
385 {"user", TUSER},
386 };
388 int
389 kw_cmp(const void *k, const void *e)
391 return strcmp(k, ((struct keyword *)e)->word);
394 /*
395 * Taken an adapted from doas' parse.y
396 */
397 static int
398 yylex(void)
400 struct keyword *kw;
401 char buf[8096], *ebuf, *p, *str, *v, *val;
402 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
403 size_t len;
405 p = buf;
406 ebuf = buf + sizeof(buf);
408 repeat:
409 /* skip whitespace first */
410 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
411 yylval.colno++;
412 if (c == '\n') {
413 yylval.lineno++;
414 yylval.colno = 0;
418 /* check for special one-character constructions */
419 switch (c) {
420 case '{':
421 case '}':
422 return c;
423 case '#':
424 /* skip comments; NUL is allowed; no continuation */
425 while ((c = getc(yyfp)) != '\n')
426 if (c == EOF)
427 goto eof;
428 yylval.colno = 0;
429 yylval.lineno++;
430 goto repeat;
431 case '=':
432 if ((c = getc(yyfp)) == '>')
433 return TARROW;
434 ungetc(c, yyfp);
435 return '=';
436 case EOF:
437 goto eof;
440 /* parsing next word */
441 for (;; c = getc(yyfp), yylval.colno++) {
442 switch (c) {
443 case '\0':
444 yyerror("unallowed character NULL in column %d",
445 yylval.colno+1);
446 escape = 0;
447 continue;
448 case '\\':
449 escape = !escape;
450 if (escape)
451 continue;
452 break;
454 /* expand macros in-place */
455 case '$':
456 if (!escape && !quotes) {
457 v = p;
458 while (1) {
459 if ((c = getc(yyfp)) == EOF) {
460 yyerror("EOF during macro expansion");
461 return 0;
463 if (p + 1 >= ebuf - 1) {
464 yyerror("string too long");
465 return 0;
467 if (isalnum(c) || c == '_') {
468 *p++ = c;
469 continue;
471 *p = 0;
472 break;
474 p = v;
475 if ((val = symget(p)) == NULL) {
476 yyerror("macro '%s' not defined", v);
477 return TERR;
479 len = strlen(val);
480 if (p + len >= ebuf - 1) {
481 yyerror("after macro-expansion, "
482 "string too long");
483 return TERR;
485 *p = '\0';
486 strlcat(p, val, ebuf - p);
487 p += len;
488 nonkw = 1;
489 goto eow;
491 break;
492 case '\n':
493 if (quotes)
494 yyerror("unterminated quotes in column %d",
495 yylval.colno+1);
496 if (escape) {
497 nonkw = 1;
498 escape = 0;
499 yylval.colno = 0;
500 yylval.lineno++;
502 goto eow;
503 case EOF:
504 if (escape)
505 yyerror("unterminated escape in column %d",
506 yylval.colno);
507 if (quotes)
508 yyerror("unterminated quotes in column %d",
509 qpos+1);
510 goto eow;
511 case '{':
512 case '}':
513 case '#':
514 case ' ':
515 case '\t':
516 if (!escape && !quotes)
517 goto eow;
518 break;
519 case '"':
520 if (!escape) {
521 quotes = !quotes;
522 if (quotes) {
523 nonkw = 1;
524 qpos = yylval.colno;
526 continue;
529 *p++ = c;
530 if (p == ebuf) {
531 yyerror("line too long");
532 p = buf;
534 escape = 0;
537 eow:
538 *p = 0;
539 if (c != EOF)
540 ungetc(c, yyfp);
541 if (p == buf) {
542 /*
543 * There could be a number of reason for empty buffer,
544 * and we handle all of them here, to avoid cluttering
545 * the main loop.
546 */
547 if (c == EOF)
548 goto eof;
549 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
550 goto repeat;
552 if (!nonkw) {
553 kw = bsearch(buf, keywords, sizeof(keywords)/sizeof(keywords[0]),
554 sizeof(keywords[0]), kw_cmp);
555 if (kw != NULL)
556 return kw->token;
558 c = *buf;
559 if (!nonkw && (c == '-' || isdigit(c))) {
560 yylval.v.num = parse_portno(buf);
561 return TNUM;
563 if (!nonkw && !strcmp(buf, "on")) {
564 yylval.v.num = 1;
565 return TBOOL;
567 if (!nonkw && !strcmp(buf, "off")) {
568 yylval.v.num = 0;
569 return TBOOL;
571 if ((str = strdup(buf)) == NULL)
572 err(1, "%s", __func__);
573 yylval.v.str = str;
574 return TSTRING;
576 eof:
577 if (ferror(yyfp))
578 yyerror("input error reading config");
579 return 0;
582 int
583 parse_portno(const char *p)
585 const char *errstr;
586 int n;
588 n = strtonum(p, 0, UINT16_MAX, &errstr);
589 if (errstr != NULL)
590 yyerror("port number is %s: %s", errstr, p);
591 return n;
594 void
595 parse_conf(const char *path)
597 struct sym *sym, *next;
599 config_path = path;
600 if ((yyfp = fopen(path, "r")) == NULL)
601 err(1, "cannot open config: %s", path);
602 yyparse();
603 fclose(yyfp);
605 if (goterror)
606 exit(1);
608 if (TAILQ_FIRST(&hosts)->domain == NULL)
609 errx(1, "no vhost defined in %s", path);
611 /* free unused macros */
612 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
613 /* TODO: warn if !sym->used */
614 if (!sym->persist) {
615 free(sym->name);
616 free(sym->val);
617 TAILQ_REMOVE(&symhead, sym, entry);
618 free(sym);
623 char *
624 ensure_absolute_path(char *path)
626 if (path == NULL || *path != '/')
627 yyerror("not an absolute path: %s", path);
628 return path;
631 int
632 check_block_code(int n)
634 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
635 yyerror("invalid block code %d", n);
636 return n;
639 char *
640 check_block_fmt(char *fmt)
642 char *s;
644 for (s = fmt; *s; ++s) {
645 if (*s != '%')
646 continue;
647 switch (*++s) {
648 case '%':
649 case 'p':
650 case 'q':
651 case 'P':
652 case 'N':
653 break;
654 default:
655 yyerror("invalid format specifier %%%c", *s);
659 return fmt;
662 int
663 check_strip_no(int n)
665 if (n <= 0)
666 yyerror("invalid strip number %d", n);
667 return n;
670 int
671 check_prefork_num(int n)
673 if (n <= 0 || n >= PROC_MAX)
674 yyerror("invalid prefork number %d", n);
675 return n;
678 void
679 advance_loc(void)
681 loc = new_location();
682 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
685 void
686 only_once(const void *ptr, const char *name)
688 if (ptr != NULL)
689 yyerror("`%s' specified more than once", name);
692 void
693 only_oncei(int i, const char *name)
695 if (i != -1)
696 yyerror("`%s' specified more than once", name);
699 int
700 fastcgi_conf(char *path, char *port, char *prog)
702 struct fcgi *f;
703 int i;
705 for (i = 0; i < FCGI_MAX; ++i) {
706 f = &fcgi[i];
708 if (f->path == NULL) {
709 f->id = i;
710 f->path = path;
711 f->port = port;
712 f->prog = prog;
713 return i;
716 /* XXX: what to do with prog? */
717 if (!strcmp(f->path, path) &&
718 ((port == NULL && f->port == NULL) ||
719 !strcmp(f->port, port))) {
720 free(path);
721 free(port);
722 return i;
726 yyerror("too much `fastcgi' rules defined.");
727 return -1;
730 void
731 add_param(char *name, char *val, int env)
733 struct envlist *e;
734 struct envhead *h;
736 if (env)
737 h = &host->env;
738 else
739 h = &host->params;
741 e = xcalloc(1, sizeof(*e));
742 e->name = name;
743 e->value = val;
744 if (TAILQ_EMPTY(h))
745 TAILQ_INSERT_HEAD(h, e, envs);
746 else
747 TAILQ_INSERT_TAIL(h, e, envs);
750 int
751 symset(const char *name, const char *val, int persist)
753 struct sym *sym;
755 TAILQ_FOREACH(sym, &symhead, entry) {
756 if (!strcmp(name, sym->name))
757 break;
760 if (sym != NULL) {
761 if (sym->persist)
762 return 0;
763 else {
764 free(sym->name);
765 free(sym->val);
766 TAILQ_REMOVE(&symhead, sym, entry);
767 free(sym);
771 sym = xcalloc(1, sizeof(*sym));
772 sym->name = xstrdup(name);
773 sym->val = xstrdup(val);
774 sym->used = 0;
775 sym->persist = persist;
777 TAILQ_INSERT_TAIL(&symhead, sym, entry);
778 return 0;
781 int
782 cmdline_symset(char *s)
784 char *sym, *val;
785 int ret;
787 if ((val = strrchr(s, '=')) == NULL)
788 return -1;
789 sym = xcalloc(1, val - s + 1);
790 memcpy(sym, s, val - s);
791 ret = symset(sym, val + 1, 1);
792 free(sym);
793 return ret;
796 char *
797 symget(const char *name)
799 struct sym *sym;
801 TAILQ_FOREACH(sym, &symhead, entry) {
802 if (!strcmp(name, sym->name)) {
803 sym->used = 1;
804 return sym->val;
808 return NULL;