Annotation of src/usr.bin/doas/parse.y, Revision 1.21
1.21 ! tedu 1: /* $OpenBSD: parse.y,v 1.20 2016/09/02 18:12:30 tedu Exp $ */
1.1 tedu 2: /*
3: * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17:
18: %{
19: #include <sys/types.h>
20: #include <ctype.h>
21: #include <unistd.h>
22: #include <stdint.h>
23: #include <stdarg.h>
24: #include <stdio.h>
25: #include <string.h>
26: #include <err.h>
27:
28: #include "doas.h"
29:
30: typedef struct {
31: union {
32: struct {
33: int action;
34: int options;
1.7 zhuk 35: const char *cmd;
36: const char **cmdargs;
1.1 tedu 37: const char **envlist;
38: };
39: const char *str;
40: };
1.10 zhuk 41: int lineno;
42: int colno;
1.1 tedu 43: } yystype;
44: #define YYSTYPE yystype
45:
46: FILE *yyfp;
47:
48: struct rule **rules;
49: int nrules, maxrules;
1.10 zhuk 50: int parse_errors = 0;
1.19 tedu 51: int obsolete_warned = 0;
1.1 tedu 52:
1.4 nicm 53: void yyerror(const char *, ...);
54: int yylex(void);
55: int yyparse(void);
56:
1.1 tedu 57: %}
58:
1.7 zhuk 59: %token TPERMIT TDENY TAS TCMD TARGS
1.20 tedu 60: %token TNOPASS TPERSIST TKEEPENV TSETENV
1.1 tedu 61: %token TSTRING
62:
63: %%
64:
65: grammar: /* empty */
66: | grammar '\n'
67: | grammar rule '\n'
1.10 zhuk 68: | error '\n'
1.1 tedu 69: ;
70:
71: rule: action ident target cmd {
72: struct rule *r;
73: r = calloc(1, sizeof(*r));
1.2 nicm 74: if (!r)
75: errx(1, "can't allocate rule");
1.1 tedu 76: r->action = $1.action;
77: r->options = $1.options;
78: r->envlist = $1.envlist;
79: r->ident = $2.str;
80: r->target = $3.str;
1.7 zhuk 81: r->cmd = $4.cmd;
82: r->cmdargs = $4.cmdargs;
1.1 tedu 83: if (nrules == maxrules) {
84: if (maxrules == 0)
85: maxrules = 63;
86: else
87: maxrules *= 2;
1.6 benno 88: if (!(rules = reallocarray(rules, maxrules,
89: sizeof(*rules))))
1.1 tedu 90: errx(1, "can't allocate rules");
91: }
92: rules[nrules++] = r;
93: } ;
94:
95: action: TPERMIT options {
96: $$.action = PERMIT;
97: $$.options = $2.options;
98: $$.envlist = $2.envlist;
99: } | TDENY {
100: $$.action = DENY;
1.19 tedu 101: $$.options = 0;
102: $$.envlist = NULL;
1.1 tedu 103: } ;
104:
1.19 tedu 105: options: /* none */ {
106: $$.options = 0;
107: $$.envlist = NULL;
108: } | options option {
1.1 tedu 109: $$.options = $1.options | $2.options;
110: $$.envlist = $1.envlist;
1.21 ! tedu 111: if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) {
! 112: yyerror("can't combine nopass and persist");
! 113: YYERROR;
! 114: }
1.1 tedu 115: if ($2.envlist) {
1.10 zhuk 116: if ($$.envlist) {
1.19 tedu 117: yyerror("can't have two setenv sections");
1.10 zhuk 118: YYERROR;
119: } else
1.1 tedu 120: $$.envlist = $2.envlist;
121: }
122: } ;
123: option: TNOPASS {
124: $$.options = NOPASS;
1.19 tedu 125: $$.envlist = NULL;
1.20 tedu 126: } | TPERSIST {
127: $$.options = PERSIST;
128: $$.envlist = NULL;
1.1 tedu 129: } | TKEEPENV {
130: $$.options = KEEPENV;
1.19 tedu 131: $$.envlist = NULL;
1.1 tedu 132: } | TKEEPENV '{' envlist '}' {
1.19 tedu 133: $$.options = 0;
134: if (!obsolete_warned) {
135: warnx("keepenv with list is obsolete");
136: obsolete_warned = 1;
137: }
138: $$.envlist = $3.envlist;
139: } | TSETENV '{' envlist '}' {
140: $$.options = 0;
1.1 tedu 141: $$.envlist = $3.envlist;
142: } ;
143:
144: envlist: /* empty */ {
145: if (!($$.envlist = calloc(1, sizeof(char *))))
146: errx(1, "can't allocate envlist");
147: } | envlist TSTRING {
148: int nenv = arraylen($1.envlist);
1.6 benno 149: if (!($$.envlist = reallocarray($1.envlist, nenv + 2,
150: sizeof(char *))))
1.1 tedu 151: errx(1, "can't allocate envlist");
152: $$.envlist[nenv] = $2.str;
153: $$.envlist[nenv + 1] = NULL;
1.18 tedu 154: }
1.16 djm 155:
156:
1.1 tedu 157: ident: TSTRING {
158: $$.str = $1.str;
159: } ;
160:
161: target: /* optional */ {
162: $$.str = NULL;
163: } | TAS TSTRING {
164: $$.str = $2.str;
165: } ;
166:
167: cmd: /* optional */ {
1.7 zhuk 168: $$.cmd = NULL;
169: $$.cmdargs = NULL;
170: } | TCMD TSTRING args {
171: $$.cmd = $2.str;
172: $$.cmdargs = $3.cmdargs;
173: } ;
174:
175: args: /* empty */ {
176: $$.cmdargs = NULL;
177: } | TARGS argslist {
178: $$.cmdargs = $2.cmdargs;
179: } ;
180:
181: argslist: /* empty */ {
182: if (!($$.cmdargs = calloc(1, sizeof(char *))))
183: errx(1, "can't allocate args");
184: } | argslist TSTRING {
185: int nargs = arraylen($1.cmdargs);
1.11 deraadt 186: if (!($$.cmdargs = reallocarray($1.cmdargs, nargs + 2,
187: sizeof(char *))))
1.7 zhuk 188: errx(1, "can't allocate args");
189: $$.cmdargs[nargs] = $2.str;
190: $$.cmdargs[nargs + 1] = NULL;
1.1 tedu 191: } ;
192:
193: %%
194:
195: void
196: yyerror(const char *fmt, ...)
197: {
198: va_list va;
199:
1.15 gsoares 200: fprintf(stderr, "doas: ");
1.1 tedu 201: va_start(va, fmt);
1.10 zhuk 202: vfprintf(stderr, fmt, va);
203: va_end(va);
204: fprintf(stderr, " at line %d\n", yylval.lineno + 1);
205: parse_errors++;
1.1 tedu 206: }
207:
208: struct keyword {
209: const char *word;
210: int token;
211: } keywords[] = {
212: { "deny", TDENY },
213: { "permit", TPERMIT },
214: { "as", TAS },
215: { "cmd", TCMD },
1.7 zhuk 216: { "args", TARGS },
1.1 tedu 217: { "nopass", TNOPASS },
1.20 tedu 218: { "persist", TPERSIST },
1.1 tedu 219: { "keepenv", TKEEPENV },
1.19 tedu 220: { "setenv", TSETENV },
1.1 tedu 221: };
222:
223: int
224: yylex(void)
225: {
226: char buf[1024], *ebuf, *p, *str;
1.10 zhuk 227: int i, c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
1.1 tedu 228:
229: p = buf;
230: ebuf = buf + sizeof(buf);
1.9 zhuk 231:
1.5 benno 232: repeat:
1.9 zhuk 233: /* skip whitespace first */
234: for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
1.10 zhuk 235: yylval.colno++;
1.9 zhuk 236:
237: /* check for special one-character constructions */
1.1 tedu 238: switch (c) {
1.9 zhuk 239: case '\n':
1.10 zhuk 240: yylval.colno = 0;
241: yylval.lineno++;
1.9 zhuk 242: /* FALLTHROUGH */
243: case '{':
244: case '}':
245: return c;
246: case '#':
247: /* skip comments; NUL is allowed; no continuation */
248: while ((c = getc(yyfp)) != '\n')
249: if (c == EOF)
1.14 tedu 250: goto eof;
1.10 zhuk 251: yylval.colno = 0;
252: yylval.lineno++;
1.9 zhuk 253: return c;
254: case EOF:
1.14 tedu 255: goto eof;
1.1 tedu 256: }
1.9 zhuk 257:
258: /* parsing next word */
1.10 zhuk 259: for (;; c = getc(yyfp), yylval.colno++) {
1.3 zhuk 260: switch (c) {
1.9 zhuk 261: case '\0':
1.11 deraadt 262: yyerror("unallowed character NUL in column %d",
263: yylval.colno + 1);
1.9 zhuk 264: escape = 0;
265: continue;
266: case '\\':
267: escape = !escape;
268: if (escape)
269: continue;
270: break;
1.3 zhuk 271: case '\n':
1.9 zhuk 272: if (quotes)
1.10 zhuk 273: yyerror("unterminated quotes in column %d",
274: qpos + 1);
1.9 zhuk 275: if (escape) {
276: nonkw = 1;
277: escape = 0;
1.12 mikeb 278: yylval.colno = 0;
279: yylval.lineno++;
1.9 zhuk 280: continue;
281: }
282: goto eow;
283: case EOF:
284: if (escape)
1.10 zhuk 285: yyerror("unterminated escape in column %d",
286: yylval.colno);
1.9 zhuk 287: if (quotes)
1.10 zhuk 288: yyerror("unterminated quotes in column %d",
289: qpos + 1);
290: goto eow;
1.9 zhuk 291: /* FALLTHROUGH */
1.3 zhuk 292: case '{':
293: case '}':
294: case '#':
295: case ' ':
296: case '\t':
1.9 zhuk 297: if (!escape && !quotes)
298: goto eow;
299: break;
300: case '"':
301: if (!escape) {
302: quotes = !quotes;
303: if (quotes) {
304: nonkw = 1;
1.10 zhuk 305: qpos = yylval.colno;
1.9 zhuk 306: }
307: continue;
308: }
1.3 zhuk 309: }
1.1 tedu 310: *p++ = c;
1.13 tedu 311: if (p == ebuf) {
1.10 zhuk 312: yyerror("too long line");
1.13 tedu 313: p = buf;
314: }
1.9 zhuk 315: escape = 0;
1.1 tedu 316: }
1.9 zhuk 317:
1.3 zhuk 318: eow:
1.1 tedu 319: *p = 0;
320: if (c != EOF)
321: ungetc(c, yyfp);
1.9 zhuk 322: if (p == buf) {
323: /*
1.11 deraadt 324: * There could be a number of reasons for empty buffer,
325: * and we handle all of them here, to avoid cluttering
326: * the main loop.
1.9 zhuk 327: */
328: if (c == EOF)
1.14 tedu 329: goto eof;
1.10 zhuk 330: else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
1.9 zhuk 331: goto repeat;
332: }
333: if (!nonkw) {
334: for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
335: if (strcmp(buf, keywords[i].word) == 0)
336: return keywords[i].token;
337: }
1.1 tedu 338: }
339: if ((str = strdup(buf)) == NULL)
340: err(1, "strdup");
341: yylval.str = str;
342: return TSTRING;
1.14 tedu 343:
344: eof:
345: if (ferror(yyfp))
346: yyerror("input error reading config");
347: return 0;
1.1 tedu 348: }