X-Git-Url: https://git.armaanb.net/?p=opendoas.git;a=blobdiff_plain;f=parse.y;h=b5ba234db7e3f6f1f3abf1e90c1eaf74ddd2b53d;hp=f1e90ab9039fc555fd05e4a0acf6890d922888fd;hb=HEAD;hpb=f5e3a415df7177b7b3a26464341ade4337a1c0b2 diff --git a/parse.y b/parse.y index f1e90ab..b5ba234 100644 --- a/parse.y +++ b/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.5 2015/07/19 22:09:08 benno Exp $ */ +/* $OpenBSD: parse.y,v 1.10 2015/07/24 06:36:42 zhuk Exp $ */ /* * Copyright (c) 2015 Ted Unangst * @@ -16,14 +16,19 @@ */ %{ +#include "config.h" + #include #include -#include -#include +#include #include #include +#include +#include #include -#include +#include + +#include "openbsd.h" #include "doas.h" @@ -32,26 +37,45 @@ typedef struct { struct { int action; int options; + const char *cmd; + const char **cmdargs; const char **envlist; }; + const char **strlist; const char *str; }; + int lineno; + int colno; } yystype; #define YYSTYPE yystype FILE *yyfp; struct rule **rules; -int nrules, maxrules; +size_t nrules; +static size_t maxrules; + +int parse_errors = 0; -void yyerror(const char *, ...); -int yylex(void); -int yyparse(void); +static void yyerror(const char *, ...); +static int yylex(void); + +static size_t +arraylen(const char **arr) +{ + size_t cnt = 0; + + while (*arr) { + cnt++; + arr++; + } + return cnt; +} %} -%token TPERMIT TDENY TAS TCMD -%token TNOPASS TKEEPENV +%token TPERMIT TDENY TAS TCMD TARGS +%token TNOPASS TNOLOG TPERSIST TKEEPENV TSETENV TINSULT %token TSTRING %% @@ -59,6 +83,7 @@ int yyparse(void); grammar: /* empty */ | grammar '\n' | grammar rule '\n' + | error '\n' ; rule: action ident target cmd { @@ -71,15 +96,16 @@ rule: action ident target cmd { r->envlist = $1.envlist; r->ident = $2.str; r->target = $3.str; - r->cmd = $4.str; + r->cmd = $4.cmd; + r->cmdargs = $4.cmdargs; if (nrules == maxrules) { if (maxrules == 0) - maxrules = 63; - else - maxrules *= 2; - if (!(rules = reallocarray(rules, maxrules, - sizeof(*rules)))) + maxrules = 32; + rules = reallocarray(rules, maxrules, + 2 * sizeof(*rules)); + if (!rules) errx(1, "can't allocate rules"); + maxrules *= 2; } rules[nrules++] = r; } ; @@ -90,39 +116,59 @@ action: TPERMIT options { $$.envlist = $2.envlist; } | TDENY { $$.action = DENY; + $$.options = 0; + $$.envlist = NULL; } ; -options: /* none */ - | options option { +options: /* none */ { + $$.options = 0; + $$.envlist = NULL; + } | options option { $$.options = $1.options | $2.options; $$.envlist = $1.envlist; + if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) { + yyerror("can't combine nopass and persist"); + YYERROR; + } if ($2.envlist) { - if ($$.envlist) - errx(1, "can't have two keepenv sections"); - else + if ($$.envlist) { + yyerror("can't have two setenv sections"); + YYERROR; + } else $$.envlist = $2.envlist; } } ; option: TNOPASS { $$.options = NOPASS; + $$.envlist = NULL; + } | TNOLOG { + $$.options = NOLOG; + $$.envlist = NULL; + } | TPERSIST { + $$.options = PERSIST; + $$.envlist = NULL; } | TKEEPENV { $$.options = KEEPENV; - } | TKEEPENV '{' envlist '}' { - $$.options = KEEPENV; - $$.envlist = $3.envlist; + $$.envlist = NULL; + } | TINSULT { + $$.options = INSULT; + $$.envlist = NULL; + } | TSETENV '{' strlist '}' { + $$.options = 0; + $$.envlist = $3.strlist; } ; -envlist: /* empty */ { - if (!($$.envlist = calloc(1, sizeof(char *)))) - errx(1, "can't allocate envlist"); - } | envlist TSTRING { - int nenv = arraylen($1.envlist); - if (!($$.envlist = reallocarray($1.envlist, nenv + 2, +strlist: /* empty */ { + if (!($$.strlist = calloc(1, sizeof(char *)))) + errx(1, "can't allocate strlist"); + } | strlist TSTRING { + int nstr = arraylen($1.strlist); + if (!($$.strlist = reallocarray($1.strlist, nstr + 2, sizeof(char *)))) - errx(1, "can't allocate envlist"); - $$.envlist[nenv] = $2.str; - $$.envlist[nenv + 1] = NULL; - } + errx(1, "can't allocate strlist"); + $$.strlist[nstr] = $2.str; + $$.strlist[nstr + 1] = NULL; + } ; ident: TSTRING { @@ -136,9 +182,17 @@ target: /* optional */ { } ; cmd: /* optional */ { - $$.str = NULL; - } | TCMD TSTRING { - $$.str = $2.str; + $$.cmd = NULL; + $$.cmdargs = NULL; + } | TCMD TSTRING args { + $$.cmd = $2.str; + $$.cmdargs = $3.cmdargs; + } ; + +args: /* empty */ { + $$.cmdargs = NULL; + } | TARGS strlist { + $$.cmdargs = $2.strlist; } ; %% @@ -148,11 +202,15 @@ yyerror(const char *fmt, ...) { va_list va; + fprintf(stderr, "doas: "); va_start(va, fmt); - verrx(1, fmt, va); + vfprintf(stderr, fmt, va); + va_end(va); + fprintf(stderr, " at line %d\n", yylval.lineno + 1); + parse_errors++; } -struct keyword { +static struct keyword { const char *word; int token; } keywords[] = { @@ -160,69 +218,139 @@ struct keyword { { "permit", TPERMIT }, { "as", TAS }, { "cmd", TCMD }, + { "args", TARGS }, { "nopass", TNOPASS }, + { "nolog", TNOLOG }, + { "persist", TPERSIST }, { "keepenv", TKEEPENV }, + { "setenv", TSETENV }, + { "insult", TINSULT }, }; int yylex(void) { char buf[1024], *ebuf, *p, *str; - int i, c, next; + int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0; + size_t i; p = buf; ebuf = buf + sizeof(buf); + repeat: - c = getc(yyfp); + /* skip whitespace first */ + for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp)) + yylval.colno++; + + /* check for special one-character constructions */ switch (c) { - case ' ': - case '\t': - goto repeat; /* skip spaces */ - case '\\': - next = getc(yyfp); - if (next == '\n') - goto repeat; - else - c = next; case '\n': + yylval.colno = 0; + yylval.lineno++; + /* FALLTHROUGH */ case '{': case '}': return c; case '#': - while ((c = getc(yyfp)) != '\n' && c != EOF) - ; /* skip comments */ - if (c == EOF) - return 0; + /* 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: - return 0; + goto eof; } - while (1) { + + /* 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': - case EOF: - goto eow; + 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 much stuff"); - c = getc(yyfp); + if (p == ebuf) { + yyerror("too long line"); + p = buf; + } + escape = 0; } + eow: *p = 0; if (c != EOF) ungetc(c, yyfp); - for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) { - if (strcmp(buf, keywords[i].word) == 0) - return keywords[i].token; + 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"); + err(1, "%s", __func__); yylval.str = str; return TSTRING; + +eof: + if (ferror(yyfp)) + yyerror("input error reading config"); + return 0; }