opendoas

A portable version of the OpenBSD `doas` command
git clone https://pi.duncano.de/git/opendoas.git
Log | Files | Refs | README | LICENSE

commit 2b0167bb7af792320ca8c2655297598891004633
parent a2614ebbd28ea611e48596589f90c87ab60bf4d2
Author: Vadim Zhukov <zhuk@openbsd.org>
Date:   Wed, 22 Jul 2015 20:15:24 +0000

Implement quoting support in doas.conf. Now you can pass environment
variables and arguments with almost any values.

As a bonus, doas will now point to exact place where syntax error occured
most of times; there is some room for improvement, though.

okay tedu@

Diffstat:
doas.conf.5 | 22++++++++++++++++++----
parse.y | 117++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
2 files changed, 104 insertions(+), 35 deletions(-)

diff --git a/doas.conf.5 b/doas.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: doas.conf.5,v 1.8 2015/07/21 11:04:06 zhuk Exp $ +.\" $OpenBSD: doas.conf.5,v 1.9 2015/07/22 06:30:12 jmc Exp $ .\" .\"Copyright (c) 2015 Ted Unangst <tedu@openbsd.org> .\" @@ -13,7 +13,7 @@ .\"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. -.Dd $Mdocdate: July 21 2015 $ +.Dd $Mdocdate: July 22 2015 $ .Dt DOAS.CONF 5 .Os .Sh NAME @@ -82,11 +82,25 @@ alone means that command should be run without any arguments. .Pp The last matching rule determines the action taken. .Pp -The current line can be extended over multiple lines using a backslash -.Pq Sq \e . Comments can be put anywhere in the file using a hash mark .Pq Sq # , and extend to the end of the current line. +.Pp +The following quoting rules apply: +.Bl -dash +.It +The text between a pair of double quotes +.Pq Sq \&" +is taken as is. +.It +The backslash +.Pq Sq \e +escapes next character, including new line character, outside comment; +as a result, comments may not be extended over multiple lines. +.It +If quotes or backslash are used in the word, this word won't be +considered a keyword. +.El .Sh EXAMPLES The following example permits users in group wsrc to build ports, wheel to execute commands as root while keeping the environment diff --git a/parse.y b/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.7 2015/07/21 11:04:06 zhuk Exp $ */ +/* $OpenBSD: parse.y,v 1.8 2015/07/21 16:12:04 tedu Exp $ */ /* * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org> * @@ -190,59 +190,114 @@ struct keyword { int yylex(void) { + static int colno = 1, lineno = 1; + char buf[1024], *ebuf, *p, *str; - int i, c, next; + int i, c, quotes = 0, escape = 0, qpos = 0, nonkw = 0; p = buf; ebuf = buf + sizeof(buf); + repeat: - c = getc(yyfp); + /* skip whitespace first */ + for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp)) + 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': - case '{': - case '}': - return c; - case '#': - while ((c = getc(yyfp)) != '\n' && c != EOF) - ; /* skip comments */ - if (c == EOF) + case '\n': + colno = 1; + lineno++; + /* FALLTHROUGH */ + case '{': + case '}': + return c; + case '#': + /* skip comments; NUL is allowed; no continuation */ + while ((c = getc(yyfp)) != '\n') + if (c == EOF) + return 0; + colno = 1; + lineno++; + return c; + case EOF: return 0; - return c; - case EOF: - return 0; } - while (1) { + + /* parsing next word */ + for (;; c = getc(yyfp), colno++) { switch (c) { + case '\0': + yyerror("unallowed character NUL at " + "line %d, column %d", lineno, colno); + escape = 0; + continue; + case '\\': + escape = !escape; + if (escape) + continue; + break; case '\n': + if (quotes) + yyerror("unterminated quotes at line %d, column %d", + lineno, qpos); + if (escape) { + nonkw = 1; + escape = 0; + continue; + } + goto eow; + case EOF: + if (escape) + yyerror("unterminated escape at line %d, column %d", + lineno, colno - 1); + if (quotes) + yyerror("unterminated quotes at line %d, column %d", + lineno, qpos); + /* 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 = colno; + } + continue; + } } *p++ = c; if (p == ebuf) - yyerror("too much stuff"); - c = getc(yyfp); + yyerror("too long line %d", lineno); + 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) /* 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");