X-Git-Url: https://git.armaanb.net/?a=blobdiff_plain;f=parse.y;h=e4a041a374164133761a77c8f3f1e27f09c7556a;hb=025db698803cbd722444ba2745ead9a5c51efcb4;hp=1b3a54261e908e95f11f3707fe4420bff70c20ef;hpb=d22ea3dc8bd52907291af8b41b33260ca74a1e69;p=opendoas.git diff --git a/parse.y b/parse.y index 1b3a542..e4a041a 100644 --- a/parse.y +++ b/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.1 2015/07/16 20:44:21 tedu Exp $ */ +/* $OpenBSD: parse.y,v 1.10 2015/07/24 06:36:42 zhuk Exp $ */ /* * Copyright (c) 2015 Ted Unangst * @@ -18,12 +18,15 @@ %{ #include #include -#include -#include +#include #include #include +#include +#include #include -#include +#include + +#include "openbsd.h" #include "doas.h" @@ -32,22 +35,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; +int nrules; +static int maxrules; + +int parse_errors = 0; + +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 TPERSIST TKEEPENV TSETENV %token TSTRING %% @@ -55,6 +81,7 @@ int nrules, maxrules; grammar: /* empty */ | grammar '\n' | grammar rule '\n' + | error '\n' ; rule: action ident target cmd { @@ -67,13 +94,15 @@ 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)))) + if (!(rules = reallocarray(rules, maxrules, + sizeof(*rules)))) errx(1, "can't allocate rules"); } rules[nrules++] = r; @@ -85,38 +114,53 @@ 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; + } | TPERSIST { + $$.options = PERSIST; + $$.envlist = NULL; } | TKEEPENV { $$.options = KEEPENV; - } | TKEEPENV '{' envlist '}' { - $$.options = KEEPENV; - $$.envlist = $3.envlist; + $$.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, sizeof(char *)))) - errx(1, "can't allocate envlist"); - $$.envlist[nenv] = $2.str; - $$.envlist[nenv + 1] = NULL; - } +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 strlist"); + $$.strlist[nstr] = $2.str; + $$.strlist[nstr + 1] = NULL; + } ; ident: TSTRING { @@ -130,9 +174,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; } ; %% @@ -142,15 +194,15 @@ yyerror(const char *fmt, ...) { va_list va; - va_start(va, fmt); fprintf(stderr, "doas: "); + va_start(va, fmt); vfprintf(stderr, fmt, va); - fprintf(stderr, "\n"); va_end(va); - exit(1); + fprintf(stderr, " at line %d\n", yylval.lineno + 1); + parse_errors++; } -struct keyword { +static struct keyword { const char *word; int token; } keywords[] = { @@ -158,55 +210,128 @@ struct keyword { { "permit", TPERMIT }, { "as", TAS }, { "cmd", TCMD }, + { "args", TARGS }, { "nopass", TNOPASS }, + { "persist", TPERSIST }, { "keepenv", TKEEPENV }, + { "setenv", TSETENV }, }; int yylex(void) { char buf[1024], *ebuf, *p, *str; - int i, c; + int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0; p = buf; ebuf = buf + sizeof(buf); - while ((c = getc(yyfp)) == ' ' || c == '\t') - ; /* skip spaces */ + +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 '#': - 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) + return 0; + yylval.colno = 0; + yylval.lineno++; return c; case EOF: return 0; - case ':': - *p++ = c; - c = getc(yyfp); + } + + /* 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; - default: + case '\n': + if (quotes) + yyerror("unterminated quotes in column %d", + qpos + 1); + if (escape) { + nonkw = 1; + escape = 0; + 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; - } - while (isalnum(c)) { + 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); + yyerror("too long line"); + 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) + return 0; + else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */ + goto repeat; + } + if (!nonkw) { + size_t i; + 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; }