/* * Copyright (c) 2016 Martijn van Duren * Copyright (c) 2015 Ted Unangst * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ %{ #include #include #include #include #include #include #include #include #include #include #include #include "vias.h" typedef struct { union { struct { int action; int options; const char **files; }; const char *str; }; int lineno; int colno; } yystype; #define YYSTYPE yystype FILE *yyfp; struct rule **rules; int nrules, maxrules; int parse_errors = 0; int obsolete_warned = 0; void yyerror(const char *, ...); int yylex(void); int yyparse(void); %} %token TPERMIT TDENY TAS TEDIT %token TNOPASS TPERSIST %token TSTRING %% grammar: /* empty */ | grammar '\n' | grammar rule '\n' | error '\n' ; rule: action ident target edit { struct rule *r; if ((r = calloc(1, sizeof(*r))) == NULL) err(1, NULL); r->action = $1.action; r->options = $1.options; r->ident = $2.str; r->target = $3.str; r->files = $4.files; if (nrules == maxrules) { if (maxrules == 0) maxrules = 63; else maxrules *= 2; if ((rules = reallocarray(rules, maxrules, sizeof(*rules))) == NULL) err(1, NULL); } rules[nrules++] = r; } ; action: TPERMIT options { $$.action = PERMIT; $$.options = $2.options; } | TDENY { $$.action = DENY; $$.options = 0; } ; options: /* none */ { $$.options = 0; } | options option { $$.options = $1.options | $2.options; if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) { yyerror("can't combine nopass and persist"); YYERROR; } } ; option: TNOPASS { $$.options = NOPASS; } | TPERSIST { $$.options = PERSIST; }; ident: TSTRING { $$.str = $1.str; } ; target: /* optional */ { $$.str = NULL; } | TAS TSTRING { $$.str = $2.str; } ; edit: /* none */ { $$.files = NULL; } | TEDIT files { if (arraylen($2.files)) $$.files = $2.files; else { free($2.files); $$.files = NULL; } } ; files: /* empty */ { if (($$.files = calloc(1, sizeof(char *))) == NULL) err(1, NULL); } | files TSTRING { int nargs = arraylen($1.files); char *str; int strl, skip = 0; if ($2.str[0] != '/') { warnx("File %s can't be relative", $2.str); skip = 1; } if ((str = realpath($2.str, NULL)) == NULL && errno != ENOENT) { warn("Problem verifying %s", $2.str); skip = 1; } if (str != NULL && strcmp($2.str, str)) { strl = strlen(str); if ((str = reallocarray(str, strl + 2, sizeof(*str))) == NULL) err(1, NULL); str[strl] = '/'; str[strl+1] = '\0'; if (strcmp($2.str, str)) { warnx("file %s needs to be a resolved " "path", $2.str); skip = 1; } } free(str); if (!skip) { if (($$.files = reallocarray($1.files, nargs + 2, sizeof(char *))) == NULL) err(1, NULL); $$.files[nargs] = $2.str; $$.files[nargs + 1] = NULL; } } ; %% void yyerror(const char *fmt, ...) { va_list va; fprintf(stderr, "vias: "); va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); fprintf(stderr, " at line %d\n", yylval.lineno + 1); parse_errors++; } struct keyword { const char *word; int token; } keywords[] = { { "deny", TDENY }, { "permit", TPERMIT }, { "as", TAS }, { "nopass", TNOPASS }, { "persist", TPERSIST }, { "edit", TEDIT }, }; int yylex(void) { char buf[1024], *ebuf, *p, *str; int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0; size_t i; p = buf; ebuf = buf + sizeof(buf); repeat: /* skip whitespace first */ for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp)) yylval.colno++; /* check for special one-character constructions */ switch (c) { case '\n': yylval.colno = 0; yylval.lineno++; /* FALLTHROUGH */ case '{': case '}': return c; case '#': /* skip comments; NUL is allowed; no continuation */ while ((c = getc(yyfp)) != '\n') if (c == EOF) goto eof; yylval.colno = 0; yylval.lineno++; return c; case EOF: goto eof; } /* parsing next word */ for (;; c = getc(yyfp), yylval.colno++) { switch (c) { case '\0': yyerror("unallowed character NUL in column %d", yylval.colno + 1); escape = 0; continue; case '\\': escape = !escape; if (escape) continue; break; case '\n': if (quotes) yyerror("unterminated quotes in column %d", qpos + 1); if (escape) { nonkw = 1; escape = 0; yylval.colno = 0; yylval.lineno++; continue; } goto eow; case EOF: if (escape) yyerror("unterminated escape in column %d", yylval.colno); if (quotes) yyerror("unterminated quotes in column %d", qpos + 1); goto eow; /* FALLTHROUGH */ case '{': case '}': case '#': case ' ': case '\t': if (!escape && !quotes) goto eow; break; case '"': if (!escape) { quotes = !quotes; if (quotes) { nonkw = 1; qpos = yylval.colno; } continue; } } *p++ = c; if (p == ebuf) { yyerror("too long line"); p = buf; } escape = 0; } eow: *p = 0; if (c != EOF) ungetc(c, yyfp); if (p == buf) { /* * There could be a number of reasons for empty buffer, * and we handle all of them here, to avoid cluttering * the main loop. */ if (c == EOF) goto eof; else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */ goto repeat; } if (!nonkw) { for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) { if (strcmp(buf, keywords[i].word) == 0) return keywords[i].token; } } if ((str = strdup(buf)) == NULL) err(1, "strdup"); yylval.str = str; return TSTRING; eof: if (ferror(yyfp)) yyerror("input error reading config"); return 0; }