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