Annotation of src/usr.bin/doas/parse.y, Revision 1.9
1.9 ! zhuk 1: /* $OpenBSD: parse.y,v 1.8 2015/07/21 16:12:04 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: };
41: } yystype;
42: #define YYSTYPE yystype
43:
44: FILE *yyfp;
45:
46: struct rule **rules;
47: int nrules, maxrules;
48:
1.4 nicm 49: void yyerror(const char *, ...);
50: int yylex(void);
51: int yyparse(void);
52:
1.1 tedu 53: %}
54:
1.7 zhuk 55: %token TPERMIT TDENY TAS TCMD TARGS
1.1 tedu 56: %token TNOPASS TKEEPENV
57: %token TSTRING
58:
59: %%
60:
61: grammar: /* empty */
62: | grammar '\n'
63: | grammar rule '\n'
64: ;
65:
66: rule: action ident target cmd {
67: struct rule *r;
68: r = calloc(1, sizeof(*r));
1.2 nicm 69: if (!r)
70: errx(1, "can't allocate rule");
1.1 tedu 71: r->action = $1.action;
72: r->options = $1.options;
73: r->envlist = $1.envlist;
74: r->ident = $2.str;
75: r->target = $3.str;
1.7 zhuk 76: r->cmd = $4.cmd;
77: r->cmdargs = $4.cmdargs;
1.1 tedu 78: if (nrules == maxrules) {
79: if (maxrules == 0)
80: maxrules = 63;
81: else
82: maxrules *= 2;
1.6 benno 83: if (!(rules = reallocarray(rules, maxrules,
84: sizeof(*rules))))
1.1 tedu 85: errx(1, "can't allocate rules");
86: }
87: rules[nrules++] = r;
88: } ;
89:
90: action: TPERMIT options {
91: $$.action = PERMIT;
92: $$.options = $2.options;
93: $$.envlist = $2.envlist;
94: } | TDENY {
95: $$.action = DENY;
96: } ;
97:
98: options: /* none */
99: | options option {
100: $$.options = $1.options | $2.options;
101: $$.envlist = $1.envlist;
102: if ($2.envlist) {
103: if ($$.envlist)
104: errx(1, "can't have two keepenv sections");
105: else
106: $$.envlist = $2.envlist;
107: }
108: } ;
109: option: TNOPASS {
110: $$.options = NOPASS;
111: } | TKEEPENV {
112: $$.options = KEEPENV;
113: } | TKEEPENV '{' envlist '}' {
114: $$.options = KEEPENV;
115: $$.envlist = $3.envlist;
116: } ;
117:
118: envlist: /* empty */ {
119: if (!($$.envlist = calloc(1, sizeof(char *))))
120: errx(1, "can't allocate envlist");
121: } | envlist TSTRING {
122: int nenv = arraylen($1.envlist);
1.6 benno 123: if (!($$.envlist = reallocarray($1.envlist, nenv + 2,
124: sizeof(char *))))
1.1 tedu 125: errx(1, "can't allocate envlist");
126: $$.envlist[nenv] = $2.str;
127: $$.envlist[nenv + 1] = NULL;
128: }
129:
130:
131: ident: TSTRING {
132: $$.str = $1.str;
133: } ;
134:
135: target: /* optional */ {
136: $$.str = NULL;
137: } | TAS TSTRING {
138: $$.str = $2.str;
139: } ;
140:
141: cmd: /* optional */ {
1.7 zhuk 142: $$.cmd = NULL;
143: $$.cmdargs = NULL;
144: } | TCMD TSTRING args {
145: $$.cmd = $2.str;
146: $$.cmdargs = $3.cmdargs;
147: } ;
148:
149: args: /* empty */ {
150: $$.cmdargs = NULL;
151: } | TARGS argslist {
152: $$.cmdargs = $2.cmdargs;
153: } ;
154:
155: argslist: /* empty */ {
156: if (!($$.cmdargs = calloc(1, sizeof(char *))))
157: errx(1, "can't allocate args");
158: } | argslist TSTRING {
159: int nargs = arraylen($1.cmdargs);
160: if (!($$.cmdargs = reallocarray($1.cmdargs, nargs + 2, sizeof(char *))))
161: errx(1, "can't allocate args");
162: $$.cmdargs[nargs] = $2.str;
163: $$.cmdargs[nargs + 1] = NULL;
1.1 tedu 164: } ;
165:
166: %%
167:
168: void
169: yyerror(const char *fmt, ...)
170: {
171: va_list va;
172:
173: va_start(va, fmt);
1.4 nicm 174: verrx(1, fmt, va);
1.1 tedu 175: }
176:
177: struct keyword {
178: const char *word;
179: int token;
180: } keywords[] = {
181: { "deny", TDENY },
182: { "permit", TPERMIT },
183: { "as", TAS },
184: { "cmd", TCMD },
1.7 zhuk 185: { "args", TARGS },
1.1 tedu 186: { "nopass", TNOPASS },
187: { "keepenv", TKEEPENV },
188: };
189:
190: int
191: yylex(void)
192: {
1.9 ! zhuk 193: static int colno = 1, lineno = 1;
! 194:
1.1 tedu 195: char buf[1024], *ebuf, *p, *str;
1.9 ! zhuk 196: int i, c, quotes = 0, escape = 0, qpos = 0, nonkw = 0;
1.1 tedu 197:
198: p = buf;
199: ebuf = buf + sizeof(buf);
1.9 ! zhuk 200:
1.5 benno 201: repeat:
1.9 ! zhuk 202: /* skip whitespace first */
! 203: for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
! 204: colno++;
! 205:
! 206: /* check for special one-character constructions */
1.1 tedu 207: switch (c) {
1.9 ! zhuk 208: case '\n':
! 209: colno = 1;
! 210: lineno++;
! 211: /* FALLTHROUGH */
! 212: case '{':
! 213: case '}':
! 214: return c;
! 215: case '#':
! 216: /* skip comments; NUL is allowed; no continuation */
! 217: while ((c = getc(yyfp)) != '\n')
! 218: if (c == EOF)
! 219: return 0;
! 220: colno = 1;
! 221: lineno++;
! 222: return c;
! 223: case EOF:
1.1 tedu 224: return 0;
225: }
1.9 ! zhuk 226:
! 227: /* parsing next word */
! 228: for (;; c = getc(yyfp), colno++) {
1.3 zhuk 229: switch (c) {
1.9 ! zhuk 230: case '\0':
! 231: yyerror("unallowed character NUL at "
! 232: "line %d, column %d", lineno, colno);
! 233: escape = 0;
! 234: continue;
! 235: case '\\':
! 236: escape = !escape;
! 237: if (escape)
! 238: continue;
! 239: break;
1.3 zhuk 240: case '\n':
1.9 ! zhuk 241: if (quotes)
! 242: yyerror("unterminated quotes at line %d, column %d",
! 243: lineno, qpos);
! 244: if (escape) {
! 245: nonkw = 1;
! 246: escape = 0;
! 247: continue;
! 248: }
! 249: goto eow;
! 250: case EOF:
! 251: if (escape)
! 252: yyerror("unterminated escape at line %d, column %d",
! 253: lineno, colno - 1);
! 254: if (quotes)
! 255: yyerror("unterminated quotes at line %d, column %d",
! 256: lineno, qpos);
! 257: /* FALLTHROUGH */
1.3 zhuk 258: case '{':
259: case '}':
260: case '#':
261: case ' ':
262: case '\t':
1.9 ! zhuk 263: if (!escape && !quotes)
! 264: goto eow;
! 265: break;
! 266: case '"':
! 267: if (!escape) {
! 268: quotes = !quotes;
! 269: if (quotes) {
! 270: nonkw = 1;
! 271: qpos = colno;
! 272: }
! 273: continue;
! 274: }
1.3 zhuk 275: }
1.1 tedu 276: *p++ = c;
277: if (p == ebuf)
1.9 ! zhuk 278: yyerror("too long line %d", lineno);
! 279: escape = 0;
1.1 tedu 280: }
1.9 ! zhuk 281:
1.3 zhuk 282: eow:
1.1 tedu 283: *p = 0;
284: if (c != EOF)
285: ungetc(c, yyfp);
1.9 ! zhuk 286: if (p == buf) {
! 287: /*
! 288: * There could be a number of reasons for empty buffer, and we handle
! 289: * all of them here, to avoid cluttering the main loop.
! 290: */
! 291: if (c == EOF)
! 292: return 0;
! 293: else if (!qpos) /* accept, e.g., empty args: cmd foo args "" */
! 294: goto repeat;
! 295: }
! 296: if (!nonkw) {
! 297: for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
! 298: if (strcmp(buf, keywords[i].word) == 0)
! 299: return keywords[i].token;
! 300: }
1.1 tedu 301: }
302: if ((str = strdup(buf)) == NULL)
303: err(1, "strdup");
304: yylval.str = str;
305: return TSTRING;
306: }