Annotation of src/usr.bin/doas/parse.y, Revision 1.16
1.16 ! djm 1: /* $OpenBSD: parse.y,v 1.15 2016/04/27 02:35:55 gsoares 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;
1.16 ! djm 38: const char **setenvlist;
1.1 tedu 39: };
40: const char *str;
41: };
1.10 zhuk 42: int lineno;
43: int colno;
1.1 tedu 44: } yystype;
45: #define YYSTYPE yystype
46:
47: FILE *yyfp;
48:
49: struct rule **rules;
50: int nrules, maxrules;
1.10 zhuk 51: int parse_errors = 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.16 ! djm 60: %token TNOPASS 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;
1.16 ! djm 79: r->setenvlist = $1.setenvlist;
1.1 tedu 80: r->ident = $2.str;
81: r->target = $3.str;
1.7 zhuk 82: r->cmd = $4.cmd;
83: r->cmdargs = $4.cmdargs;
1.1 tedu 84: if (nrules == maxrules) {
85: if (maxrules == 0)
86: maxrules = 63;
87: else
88: maxrules *= 2;
1.6 benno 89: if (!(rules = reallocarray(rules, maxrules,
90: sizeof(*rules))))
1.1 tedu 91: errx(1, "can't allocate rules");
92: }
93: rules[nrules++] = r;
94: } ;
95:
96: action: TPERMIT options {
97: $$.action = PERMIT;
98: $$.options = $2.options;
99: $$.envlist = $2.envlist;
1.16 ! djm 100: $$.setenvlist = $2.setenvlist;
1.1 tedu 101: } | TDENY {
102: $$.action = DENY;
103: } ;
104:
105: options: /* none */
106: | options option {
107: $$.options = $1.options | $2.options;
108: $$.envlist = $1.envlist;
109: if ($2.envlist) {
1.10 zhuk 110: if ($$.envlist) {
111: yyerror("can't have two keepenv sections");
112: YYERROR;
113: } else
1.1 tedu 114: $$.envlist = $2.envlist;
115: }
1.16 ! djm 116: $$.setenvlist = $1.setenvlist;
! 117: if ($2.setenvlist) {
! 118: if ($$.setenvlist) {
! 119: yyerror("can't have two setenv sections");
! 120: YYERROR;
! 121: } else
! 122: $$.setenvlist = $2.setenvlist;
! 123: }
1.1 tedu 124: } ;
1.16 ! djm 125:
1.1 tedu 126: option: TNOPASS {
127: $$.options = NOPASS;
128: } | TKEEPENV {
129: $$.options = KEEPENV;
130: } | TKEEPENV '{' envlist '}' {
131: $$.options = KEEPENV;
132: $$.envlist = $3.envlist;
1.16 ! djm 133: } | TSETENV '{' setenvlist '}' {
! 134: $$.options = SETENV;
! 135: $$.setenvlist = $3.setenvlist;
1.1 tedu 136: } ;
137:
138: envlist: /* empty */ {
139: if (!($$.envlist = calloc(1, sizeof(char *))))
140: errx(1, "can't allocate envlist");
141: } | envlist TSTRING {
142: int nenv = arraylen($1.envlist);
1.6 benno 143: if (!($$.envlist = reallocarray($1.envlist, nenv + 2,
144: sizeof(char *))))
1.1 tedu 145: errx(1, "can't allocate envlist");
146: $$.envlist[nenv] = $2.str;
147: $$.envlist[nenv + 1] = NULL;
148: }
149:
1.16 ! djm 150: setenvlist: /* empty */ {
! 151: if (!($$.setenvlist = calloc(1, sizeof(char *))))
! 152: errx(1, "can't allocate setenvlist");
! 153: } | setenvlist TSTRING '=' TSTRING {
! 154: int nenv = arraylen($1.setenvlist);
! 155: char *cp = NULL;
! 156:
! 157: if (*$2.str == '\0' || strchr($2.str, '=') != NULL) {
! 158: yyerror("invalid setenv expression");
! 159: YYERROR;
! 160: }
! 161: if (!($$.setenvlist = reallocarray($1.setenvlist,
! 162: nenv + 2, sizeof(char *))))
! 163: errx(1, "can't allocate envlist");
! 164: $$.setenvlist[nenv] = NULL;
! 165: if (asprintf(&cp, "%s=%s", $2.str, $4.str) <= 0 ||
! 166: cp == NULL)
! 167: errx(1,"asprintf failed");
! 168: $$.setenvlist[nenv] = cp;
! 169: $$.setenvlist[nenv + 1] = NULL;
! 170: }
! 171:
1.1 tedu 172:
173: ident: TSTRING {
174: $$.str = $1.str;
175: } ;
176:
177: target: /* optional */ {
178: $$.str = NULL;
179: } | TAS TSTRING {
180: $$.str = $2.str;
181: } ;
182:
183: cmd: /* optional */ {
1.7 zhuk 184: $$.cmd = NULL;
185: $$.cmdargs = NULL;
186: } | TCMD TSTRING args {
187: $$.cmd = $2.str;
188: $$.cmdargs = $3.cmdargs;
189: } ;
190:
191: args: /* empty */ {
192: $$.cmdargs = NULL;
193: } | TARGS argslist {
194: $$.cmdargs = $2.cmdargs;
195: } ;
196:
197: argslist: /* empty */ {
198: if (!($$.cmdargs = calloc(1, sizeof(char *))))
199: errx(1, "can't allocate args");
200: } | argslist TSTRING {
201: int nargs = arraylen($1.cmdargs);
1.11 deraadt 202: if (!($$.cmdargs = reallocarray($1.cmdargs, nargs + 2,
203: sizeof(char *))))
1.7 zhuk 204: errx(1, "can't allocate args");
205: $$.cmdargs[nargs] = $2.str;
206: $$.cmdargs[nargs + 1] = NULL;
1.1 tedu 207: } ;
208:
209: %%
210:
211: void
212: yyerror(const char *fmt, ...)
213: {
214: va_list va;
215:
1.15 gsoares 216: fprintf(stderr, "doas: ");
1.1 tedu 217: va_start(va, fmt);
1.10 zhuk 218: vfprintf(stderr, fmt, va);
219: va_end(va);
220: fprintf(stderr, " at line %d\n", yylval.lineno + 1);
221: parse_errors++;
1.1 tedu 222: }
223:
224: struct keyword {
225: const char *word;
226: int token;
227: } keywords[] = {
228: { "deny", TDENY },
229: { "permit", TPERMIT },
230: { "as", TAS },
231: { "cmd", TCMD },
1.7 zhuk 232: { "args", TARGS },
1.1 tedu 233: { "nopass", TNOPASS },
234: { "keepenv", TKEEPENV },
1.16 ! djm 235: { "setenv", TSETENV },
1.1 tedu 236: };
237:
238: int
239: yylex(void)
240: {
241: char buf[1024], *ebuf, *p, *str;
1.10 zhuk 242: int i, c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
1.1 tedu 243:
244: p = buf;
245: ebuf = buf + sizeof(buf);
1.9 zhuk 246:
1.5 benno 247: repeat:
1.9 zhuk 248: /* skip whitespace first */
249: for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
1.10 zhuk 250: yylval.colno++;
1.9 zhuk 251:
252: /* check for special one-character constructions */
1.1 tedu 253: switch (c) {
1.9 zhuk 254: case '\n':
1.10 zhuk 255: yylval.colno = 0;
256: yylval.lineno++;
1.9 zhuk 257: /* FALLTHROUGH */
258: case '{':
259: case '}':
1.16 ! djm 260: case '=':
1.9 zhuk 261: return c;
262: case '#':
263: /* skip comments; NUL is allowed; no continuation */
264: while ((c = getc(yyfp)) != '\n')
265: if (c == EOF)
1.14 tedu 266: goto eof;
1.10 zhuk 267: yylval.colno = 0;
268: yylval.lineno++;
1.9 zhuk 269: return c;
270: case EOF:
1.14 tedu 271: goto eof;
1.1 tedu 272: }
1.9 zhuk 273:
274: /* parsing next word */
1.10 zhuk 275: for (;; c = getc(yyfp), yylval.colno++) {
1.3 zhuk 276: switch (c) {
1.9 zhuk 277: case '\0':
1.11 deraadt 278: yyerror("unallowed character NUL in column %d",
279: yylval.colno + 1);
1.9 zhuk 280: escape = 0;
281: continue;
282: case '\\':
283: escape = !escape;
284: if (escape)
285: continue;
286: break;
1.3 zhuk 287: case '\n':
1.9 zhuk 288: if (quotes)
1.10 zhuk 289: yyerror("unterminated quotes in column %d",
290: qpos + 1);
1.9 zhuk 291: if (escape) {
292: nonkw = 1;
293: escape = 0;
1.12 mikeb 294: yylval.colno = 0;
295: yylval.lineno++;
1.9 zhuk 296: continue;
297: }
298: goto eow;
299: case EOF:
300: if (escape)
1.10 zhuk 301: yyerror("unterminated escape in column %d",
302: yylval.colno);
1.9 zhuk 303: if (quotes)
1.10 zhuk 304: yyerror("unterminated quotes in column %d",
305: qpos + 1);
306: goto eow;
1.9 zhuk 307: /* FALLTHROUGH */
1.3 zhuk 308: case '{':
309: case '}':
310: case '#':
311: case ' ':
312: case '\t':
1.16 ! djm 313: case '=':
1.9 zhuk 314: if (!escape && !quotes)
315: goto eow;
316: break;
317: case '"':
318: if (!escape) {
319: quotes = !quotes;
320: if (quotes) {
321: nonkw = 1;
1.10 zhuk 322: qpos = yylval.colno;
1.9 zhuk 323: }
324: continue;
325: }
1.3 zhuk 326: }
1.1 tedu 327: *p++ = c;
1.13 tedu 328: if (p == ebuf) {
1.10 zhuk 329: yyerror("too long line");
1.13 tedu 330: p = buf;
331: }
1.9 zhuk 332: escape = 0;
1.1 tedu 333: }
1.9 zhuk 334:
1.3 zhuk 335: eow:
1.1 tedu 336: *p = 0;
337: if (c != EOF)
338: ungetc(c, yyfp);
1.9 zhuk 339: if (p == buf) {
340: /*
1.11 deraadt 341: * There could be a number of reasons for empty buffer,
342: * and we handle all of them here, to avoid cluttering
343: * the main loop.
1.9 zhuk 344: */
345: if (c == EOF)
1.14 tedu 346: goto eof;
1.10 zhuk 347: else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
1.9 zhuk 348: goto repeat;
349: }
350: if (!nonkw) {
351: for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
352: if (strcmp(buf, keywords[i].word) == 0)
353: return keywords[i].token;
354: }
1.1 tedu 355: }
356: if ((str = strdup(buf)) == NULL)
357: err(1, "strdup");
358: yylval.str = str;
359: return TSTRING;
1.14 tedu 360:
361: eof:
362: if (ferror(yyfp))
363: yyerror("input error reading config");
364: return 0;
1.1 tedu 365: }