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
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 { add_mime(&conf.mime, $2, $3); }
142 | TPORT TNUM { conf.port = $2; }
143 | TPREFORK TNUM { conf.prefork = check_prefork_num($2); }
144 | TPROTOCOLS string {
145 if (tls_config_parse_protocols(&conf.protos, $2) == -1)
146 yyerror("invalid protocols string \"%s\"", $2);
148 | TUSER string { conf.user = $2; }
151 vhost : TSERVER string {
152 host = new_vhost();
153 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
155 loc = new_location();
156 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
158 loc->match = xstrdup("*");
159 host->domain = $2;
161 if (strstr($2, "xn--") != NULL) {
162 warnx("%s:%d:%d \"%s\" looks like punycode: "
163 "you should use the decoded hostname.",
164 config_path, yylval.lineno+1, yylval.colno,
165 $2);
167 } '{' servopts locations '}' {
169 if (host->cert == NULL || host->key == NULL)
170 yyerror("invalid vhost definition: %s", $2);
172 | error '}' { yyerror("error in server directive"); }
175 servopts : /* empty */
176 | servopts servopt
179 servopt : TALIAS string {
180 struct alist *a;
182 a = xcalloc(1, sizeof(*a));
183 a->alias = $2;
184 if (TAILQ_EMPTY(&host->aliases))
185 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
186 else
187 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
189 | TCERT string {
190 only_once(host->cert, "cert");
191 host->cert = ensure_absolute_path($2);
193 | TCGI string {
194 only_once(host->cgi, "cgi");
195 /* drop the starting '/', if any */
196 if (*$2 == '/')
197 memmove($2, $2+1, strlen($2));
198 host->cgi = $2;
200 | TENTRYPOINT string {
201 only_once(host->entrypoint, "entrypoint");
202 while (*$2 == '/')
203 memmove($2, $2+1, strlen($2));
204 host->entrypoint = $2;
206 | TENV string string {
207 add_param($2, $3, 1);
209 | TKEY string {
210 only_once(host->key, "key");
211 host->key = ensure_absolute_path($2);
213 | TPARAM string string {
214 add_param($2, $3, 0);
216 | locopt
219 locations : /* empty */
220 | locations location
223 location : TLOCATION { advance_loc(); } string '{' locopts '}' {
224 /* drop the starting '/' if any */
225 if (*$3 == '/')
226 memmove($3, $3+1, strlen($3));
227 loc->match = $3;
229 | error '}'
232 locopts : /* empty */
233 | locopts locopt
236 locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
237 | TBLOCK TRETURN TNUM string {
238 only_once(loc->block_fmt, "block");
239 loc->block_fmt = check_block_fmt($4);
240 loc->block_code = check_block_code($3);
242 | TBLOCK TRETURN TNUM {
243 only_once(loc->block_fmt, "block");
244 loc->block_fmt = xstrdup("temporary failure");
245 loc->block_code = check_block_code($3);
246 if ($3 >= 30 && $3 < 40)
247 yyerror("missing `meta' for block return %d", $3);
249 | TBLOCK {
250 only_once(loc->block_fmt, "block");
251 loc->block_fmt = xstrdup("temporary failure");
252 loc->block_code = 40;
254 | TDEFAULT TTYPE string {
255 only_once(loc->default_mime, "default type");
256 loc->default_mime = $3;
258 | TFASTCGI fastcgi
259 | TINDEX string {
260 only_once(loc->index, "index");
261 loc->index = $2;
263 | TLANG string {
264 only_once(loc->lang, "lang");
265 loc->lang = $2;
267 | TLOG TBOOL { loc->disable_log = !$2; }
268 | TREQUIRE TCLIENT TCA string {
269 only_once(loc->reqca, "require client ca");
270 ensure_absolute_path($4);
271 if ((loc->reqca = load_ca($4)) == NULL)
272 yyerror("couldn't load ca cert: %s", $4);
273 free($4);
275 | TROOT string {
276 only_once(loc->dir, "root");
277 loc->dir = ensure_absolute_path($2);
279 | TSTRIP TNUM { loc->strip = check_strip_no($2); }
282 fastcgi : TSPAWN string {
283 only_oncei(loc->fcgi, "fastcgi");
284 loc->fcgi = fastcgi_conf(NULL, NULL, $2);
286 | string {
287 only_oncei(loc->fcgi, "fastcgi");
288 loc->fcgi = fastcgi_conf($1, NULL, NULL);
290 | TTCP string TNUM {
291 char *c;
292 if (asprintf(&c, "%d", $3) == -1)
293 err(1, "asprintf");
294 only_oncei(loc->fcgi, "fastcgi");
295 loc->fcgi = fastcgi_conf($2, c, NULL);
297 | TTCP string {
298 only_oncei(loc->fcgi, "fastcgi");
299 loc->fcgi = fastcgi_conf($2, xstrdup("9000"), NULL);
301 | TTCP string string {
302 only_oncei(loc->fcgi, "fastcgi");
303 loc->fcgi = fastcgi_conf($2, $3, NULL);
307 %%
309 static struct vhost *
310 new_vhost(void)
312 return xcalloc(1, sizeof(struct vhost));
315 static struct location *
316 new_location(void)
318 struct location *l;
320 l = xcalloc(1, sizeof(*l));
321 l->dirfd = -1;
322 l->fcgi = -1;
323 return l;
326 void
327 yyerror(const char *msg, ...)
329 va_list ap;
331 goterror = 1;
333 va_start(ap, msg);
334 fprintf(stderr, "%s:%d: ", config_path, yylval.lineno);
335 vfprintf(stderr, msg, ap);
336 fprintf(stderr, "\n");
337 va_end(ap);
340 static struct keyword {
341 const char *word;
342 int token;
343 } keywords[] = {
344 /* these MUST be sorted */
345 {"alias", TALIAS},
346 {"auto", TAUTO},
347 {"block", TBLOCK},
348 {"ca", TCA},
349 {"cert", TCERT},
350 {"cgi", TCGI},
351 {"chroot", TCHROOT},
352 {"client", TCLIENT},
353 {"default", TDEFAULT},
354 {"entrypoint", TENTRYPOINT},
355 {"env", TENV},
356 {"fastcgi", TFASTCGI},
357 {"index", TINDEX},
358 {"ipv6", TIPV6},
359 {"key", TKEY},
360 {"lang", TLANG},
361 {"location", TLOCATION},
362 {"log", TLOG},
363 {"mime", TMIME},
364 {"param", TPARAM},
365 {"port", TPORT},
366 {"prefork", TPREFORK},
367 {"protocols", TPROTOCOLS},
368 {"require", TREQUIRE},
369 {"return", TRETURN},
370 {"root", TROOT},
371 {"server", TSERVER},
372 {"spawn", TSPAWN},
373 {"strip", TSTRIP},
374 {"tcp", TTCP},
375 {"type", TTYPE},
376 {"user", TUSER},
377 };
379 int
380 kw_cmp(const void *k, const void *e)
382 return strcmp(k, ((struct keyword *)e)->word);
385 /*
386 * Taken an adapted from doas' parse.y
387 */
388 static int
389 yylex(void)
391 struct keyword *kw;
392 char buf[8096], *ebuf, *p, *str, *v, *val;
393 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
394 size_t len;
396 p = buf;
397 ebuf = buf + sizeof(buf);
399 repeat:
400 /* skip whitespace first */
401 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
402 yylval.colno++;
403 if (c == '\n') {
404 yylval.lineno++;
405 yylval.colno = 0;
409 /* check for special one-character constructions */
410 switch (c) {
411 case '{':
412 case '}':
413 return c;
414 case '#':
415 /* skip comments; NUL is allowed; no continuation */
416 while ((c = getc(yyfp)) != '\n')
417 if (c == EOF)
418 goto eof;
419 yylval.colno = 0;
420 yylval.lineno++;
421 goto repeat;
422 case '=':
423 return c;
424 case EOF:
425 goto eof;
428 /* parsing next word */
429 for (;; c = getc(yyfp), yylval.colno++) {
430 switch (c) {
431 case '\0':
432 yyerror("unallowed character NULL in column %d",
433 yylval.colno+1);
434 escape = 0;
435 continue;
436 case '\\':
437 escape = !escape;
438 if (escape)
439 continue;
440 break;
442 /* expand macros in-place */
443 case '$':
444 if (!escape && !quotes) {
445 v = p;
446 while (1) {
447 if ((c = getc(yyfp)) == EOF) {
448 yyerror("EOF during macro expansion");
449 return 0;
451 if (p + 1 >= ebuf - 1) {
452 yyerror("string too long");
453 return 0;
455 if (isalnum(c) || c == '_') {
456 *p++ = c;
457 continue;
459 *p = 0;
460 break;
462 p = v;
463 if ((val = symget(p)) == NULL) {
464 yyerror("macro '%s' not defined", v);
465 return TERR;
467 len = strlen(val);
468 if (p + len >= ebuf - 1) {
469 yyerror("after macro-expansion, "
470 "string too long");
471 return TERR;
473 *p = '\0';
474 strlcat(p, val, ebuf - p);
475 p += len;
476 nonkw = 1;
477 goto eow;
479 break;
480 case '\n':
481 if (quotes)
482 yyerror("unterminated quotes in column %d",
483 yylval.colno+1);
484 if (escape) {
485 nonkw = 1;
486 escape = 0;
487 yylval.colno = 0;
488 yylval.lineno++;
490 goto eow;
491 case EOF:
492 if (escape)
493 yyerror("unterminated escape in column %d",
494 yylval.colno);
495 if (quotes)
496 yyerror("unterminated quotes in column %d",
497 qpos+1);
498 goto eow;
499 case '{':
500 case '}':
501 case '#':
502 case ' ':
503 case '\t':
504 if (!escape && !quotes)
505 goto eow;
506 break;
507 case '"':
508 if (!escape) {
509 quotes = !quotes;
510 if (quotes) {
511 nonkw = 1;
512 qpos = yylval.colno;
514 continue;
517 *p++ = c;
518 if (p == ebuf) {
519 yyerror("line too long");
520 p = buf;
522 escape = 0;
525 eow:
526 *p = 0;
527 if (c != EOF)
528 ungetc(c, yyfp);
529 if (p == buf) {
530 /*
531 * There could be a number of reason for empty buffer,
532 * and we handle all of them here, to avoid cluttering
533 * the main loop.
534 */
535 if (c == EOF)
536 goto eof;
537 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
538 goto repeat;
540 if (!nonkw) {
541 kw = bsearch(buf, keywords, sizeof(keywords)/sizeof(keywords[0]),
542 sizeof(keywords[0]), kw_cmp);
543 if (kw != NULL)
544 return kw->token;
546 c = *buf;
547 if (!nonkw && (c == '-' || isdigit(c))) {
548 yylval.v.num = parse_portno(buf);
549 return TNUM;
551 if (!nonkw && !strcmp(buf, "on")) {
552 yylval.v.num = 1;
553 return TBOOL;
555 if (!nonkw && !strcmp(buf, "off")) {
556 yylval.v.num = 0;
557 return TBOOL;
559 if ((str = strdup(buf)) == NULL)
560 err(1, "%s", __func__);
561 yylval.v.str = str;
562 return TSTRING;
564 eof:
565 if (ferror(yyfp))
566 yyerror("input error reading config");
567 return 0;
570 int
571 parse_portno(const char *p)
573 const char *errstr;
574 int n;
576 n = strtonum(p, 0, UINT16_MAX, &errstr);
577 if (errstr != NULL)
578 yyerror("port number is %s: %s", errstr, p);
579 return n;
582 void
583 parse_conf(const char *path)
585 struct sym *sym, *next;
587 config_path = path;
588 if ((yyfp = fopen(path, "r")) == NULL)
589 err(1, "cannot open config: %s", path);
590 yyparse();
591 fclose(yyfp);
593 if (goterror)
594 exit(1);
596 if (TAILQ_FIRST(&hosts)->domain == NULL)
597 errx(1, "no vhost defined in %s", path);
599 /* free unused macros */
600 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
601 /* TODO: warn if !sym->used */
602 if (!sym->persist) {
603 free(sym->name);
604 free(sym->val);
605 TAILQ_REMOVE(&symhead, sym, entry);
606 free(sym);
611 char *
612 ensure_absolute_path(char *path)
614 if (path == NULL || *path != '/')
615 yyerror("not an absolute path: %s", path);
616 return path;
619 int
620 check_block_code(int n)
622 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
623 yyerror("invalid block code %d", n);
624 return n;
627 char *
628 check_block_fmt(char *fmt)
630 char *s;
632 for (s = fmt; *s; ++s) {
633 if (*s != '%')
634 continue;
635 switch (*++s) {
636 case '%':
637 case 'p':
638 case 'q':
639 case 'P':
640 case 'N':
641 break;
642 default:
643 yyerror("invalid format specifier %%%c", *s);
647 return fmt;
650 int
651 check_strip_no(int n)
653 if (n <= 0)
654 yyerror("invalid strip number %d", n);
655 return n;
658 int
659 check_prefork_num(int n)
661 if (n <= 0 || n >= PROC_MAX)
662 yyerror("invalid prefork number %d", n);
663 return n;
666 void
667 advance_loc(void)
669 loc = new_location();
670 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
673 void
674 only_once(const void *ptr, const char *name)
676 if (ptr != NULL)
677 yyerror("`%s' specified more than once", name);
680 void
681 only_oncei(int i, const char *name)
683 if (i != -1)
684 yyerror("`%s' specified more than once", name);
687 int
688 fastcgi_conf(char *path, char *port, char *prog)
690 struct fcgi *f;
691 int i;
693 for (i = 0; i < FCGI_MAX; ++i) {
694 f = &fcgi[i];
696 if (f->path == NULL) {
697 f->id = i;
698 f->path = path;
699 f->port = port;
700 f->prog = prog;
701 return i;
704 /* XXX: what to do with prog? */
705 if (!strcmp(f->path, path) &&
706 ((port == NULL && f->port == NULL) ||
707 !strcmp(f->port, port))) {
708 free(path);
709 free(port);
710 return i;
714 yyerror("too much `fastcgi' rules defined.");
715 return -1;
718 void
719 add_param(char *name, char *val, int env)
721 struct envlist *e;
722 struct envhead *h;
724 if (env)
725 h = &host->env;
726 else
727 h = &host->params;
729 e = xcalloc(1, sizeof(*e));
730 e->name = name;
731 e->value = val;
732 if (TAILQ_EMPTY(h))
733 TAILQ_INSERT_HEAD(h, e, envs);
734 else
735 TAILQ_INSERT_TAIL(h, e, envs);
738 int
739 symset(const char *name, const char *val, int persist)
741 struct sym *sym;
743 TAILQ_FOREACH(sym, &symhead, entry) {
744 if (!strcmp(name, sym->name))
745 break;
748 if (sym != NULL) {
749 if (sym->persist)
750 return 0;
751 else {
752 free(sym->name);
753 free(sym->val);
754 TAILQ_REMOVE(&symhead, sym, entry);
755 free(sym);
759 sym = xcalloc(1, sizeof(*sym));
760 sym->name = xstrdup(name);
761 sym->val = xstrdup(val);
762 sym->used = 0;
763 sym->persist = persist;
765 TAILQ_INSERT_TAIL(&symhead, sym, entry);
766 return 0;
769 int
770 cmdline_symset(char *s)
772 char *sym, *val;
773 int ret;
775 if ((val = strrchr(s, '=')) == NULL)
776 return -1;
777 sym = xcalloc(1, val - s + 1);
778 memcpy(sym, s, val - s);
779 ret = symset(sym, val + 1, 1);
780 free(sym);
781 return ret;
784 char *
785 symget(const char *name)
787 struct sym *sym;
789 TAILQ_FOREACH(sym, &symhead, entry) {
790 if (!strcmp(name, sym->name)) {
791 sym->used = 1;
792 return sym->val;
796 return NULL;