xref: /openbsd-src/usr.bin/doas/parse.y (revision ce8e0faab26a848cb1842e1d9e17e9611a0896b3)
1*ce8e0faaSderaadt /* $OpenBSD: parse.y,v 1.31 2022/03/22 20:36:49 deraadt Exp $ */
27bfbda14Stedu /*
37bfbda14Stedu  * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
47bfbda14Stedu  *
57bfbda14Stedu  * Permission to use, copy, modify, and distribute this software for any
67bfbda14Stedu  * purpose with or without fee is hereby granted, provided that the above
77bfbda14Stedu  * copyright notice and this permission notice appear in all copies.
87bfbda14Stedu  *
97bfbda14Stedu  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
107bfbda14Stedu  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
117bfbda14Stedu  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
127bfbda14Stedu  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
137bfbda14Stedu  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
147bfbda14Stedu  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
157bfbda14Stedu  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
167bfbda14Stedu  */
177bfbda14Stedu 
187bfbda14Stedu %{
197bfbda14Stedu #include <sys/types.h>
207bfbda14Stedu #include <ctype.h>
21cbc92769Stobias #include <limits.h>
227bfbda14Stedu #include <unistd.h>
237bfbda14Stedu #include <stdint.h>
247bfbda14Stedu #include <stdarg.h>
257bfbda14Stedu #include <stdio.h>
267bfbda14Stedu #include <string.h>
277bfbda14Stedu #include <err.h>
287bfbda14Stedu 
297bfbda14Stedu #include "doas.h"
307bfbda14Stedu 
317bfbda14Stedu typedef struct {
327bfbda14Stedu 	union {
337bfbda14Stedu 		struct {
347bfbda14Stedu 			int action;
357bfbda14Stedu 			int options;
36cb7cef4cSzhuk 			const char *cmd;
37cb7cef4cSzhuk 			const char **cmdargs;
387bfbda14Stedu 			const char **envlist;
397bfbda14Stedu 		};
401a05de5dStedu 		const char **strlist;
417bfbda14Stedu 		const char *str;
427bfbda14Stedu 	};
43cbc92769Stobias 	unsigned long lineno;
44cbc92769Stobias 	unsigned long colno;
457bfbda14Stedu } yystype;
467bfbda14Stedu #define YYSTYPE yystype
477bfbda14Stedu 
487bfbda14Stedu FILE *yyfp;
497bfbda14Stedu 
507bfbda14Stedu struct rule **rules;
51618b6875Smillert size_t nrules;
52618b6875Smillert static size_t maxrules;
537bfbda14Stedu 
54cbc92769Stobias int parse_error = 0;
552bab682cSderaadt 
562bab682cSderaadt static void yyerror(const char *, ...);
572bab682cSderaadt static int yylex(void);
586ced1a25Snicm 
5907b2eb22Stedu static size_t
arraylen(const char ** arr)6007b2eb22Stedu arraylen(const char **arr)
6107b2eb22Stedu {
6207b2eb22Stedu 	size_t cnt = 0;
6307b2eb22Stedu 
6407b2eb22Stedu 	while (*arr) {
6507b2eb22Stedu 		cnt++;
6607b2eb22Stedu 		arr++;
6707b2eb22Stedu 	}
6807b2eb22Stedu 	return cnt;
6907b2eb22Stedu }
7007b2eb22Stedu 
717bfbda14Stedu %}
727bfbda14Stedu 
73cb7cef4cSzhuk %token TPERMIT TDENY TAS TCMD TARGS
74d4bf2b56Skn %token TNOPASS TNOLOG TPERSIST TKEEPENV TSETENV
757bfbda14Stedu %token TSTRING
767bfbda14Stedu 
777bfbda14Stedu %%
787bfbda14Stedu 
797bfbda14Stedu grammar:	/* empty */
807bfbda14Stedu 		| grammar '\n'
817bfbda14Stedu 		| grammar rule '\n'
827c3b0d20Szhuk 		| error '\n'
837bfbda14Stedu 		;
847bfbda14Stedu 
857bfbda14Stedu rule:		action ident target cmd {
867bfbda14Stedu 			struct rule *r;
87*ce8e0faaSderaadt 
887bfbda14Stedu 			r = calloc(1, sizeof(*r));
899b960859Snicm 			if (!r)
909b960859Snicm 				errx(1, "can't allocate rule");
917bfbda14Stedu 			r->action = $1.action;
927bfbda14Stedu 			r->options = $1.options;
937bfbda14Stedu 			r->envlist = $1.envlist;
947bfbda14Stedu 			r->ident = $2.str;
957bfbda14Stedu 			r->target = $3.str;
96cb7cef4cSzhuk 			r->cmd = $4.cmd;
97cb7cef4cSzhuk 			r->cmdargs = $4.cmdargs;
987bfbda14Stedu 			if (nrules == maxrules) {
997bfbda14Stedu 				if (maxrules == 0)
100618b6875Smillert 					maxrules = 32;
101618b6875Smillert 				rules = reallocarray(rules, maxrules,
102618b6875Smillert 				    2 * sizeof(*rules));
103618b6875Smillert 				if (!rules)
1047bfbda14Stedu 					errx(1, "can't allocate rules");
105618b6875Smillert 				maxrules *= 2;
1067bfbda14Stedu 			}
1077bfbda14Stedu 			rules[nrules++] = r;
1087bfbda14Stedu 		} ;
1097bfbda14Stedu 
1107bfbda14Stedu action:		TPERMIT options {
1117bfbda14Stedu 			$$.action = PERMIT;
1127bfbda14Stedu 			$$.options = $2.options;
1137bfbda14Stedu 			$$.envlist = $2.envlist;
1147bfbda14Stedu 		} | TDENY {
1157bfbda14Stedu 			$$.action = DENY;
116ab7e7725Stedu 			$$.options = 0;
117ab7e7725Stedu 			$$.envlist = NULL;
1187bfbda14Stedu 		} ;
1197bfbda14Stedu 
120ab7e7725Stedu options:	/* none */ {
121ab7e7725Stedu 			$$.options = 0;
122ab7e7725Stedu 			$$.envlist = NULL;
123ab7e7725Stedu 		} | options option {
1247bfbda14Stedu 			$$.options = $1.options | $2.options;
1257bfbda14Stedu 			$$.envlist = $1.envlist;
1268dda54eeStedu 			if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) {
1278dda54eeStedu 				yyerror("can't combine nopass and persist");
1288dda54eeStedu 				YYERROR;
1298dda54eeStedu 			}
1307bfbda14Stedu 			if ($2.envlist) {
1317c3b0d20Szhuk 				if ($$.envlist) {
132ab7e7725Stedu 					yyerror("can't have two setenv sections");
1337c3b0d20Szhuk 					YYERROR;
1347c3b0d20Szhuk 				} else
1357bfbda14Stedu 					$$.envlist = $2.envlist;
1367bfbda14Stedu 			}
1377bfbda14Stedu 		} ;
1387bfbda14Stedu option:		TNOPASS {
1397bfbda14Stedu 			$$.options = NOPASS;
140ab7e7725Stedu 			$$.envlist = NULL;
141d4bf2b56Skn 		} | TNOLOG {
142d4bf2b56Skn 			$$.options = NOLOG;
143d4bf2b56Skn 			$$.envlist = NULL;
1440a39d05fStedu 		} | TPERSIST {
1450a39d05fStedu 			$$.options = PERSIST;
1460a39d05fStedu 			$$.envlist = NULL;
1477bfbda14Stedu 		} | TKEEPENV {
1487bfbda14Stedu 			$$.options = KEEPENV;
149ab7e7725Stedu 			$$.envlist = NULL;
1501a05de5dStedu 		} | TSETENV '{' strlist '}' {
151ab7e7725Stedu 			$$.options = 0;
1521a05de5dStedu 			$$.envlist = $3.strlist;
1537bfbda14Stedu 		} ;
1547bfbda14Stedu 
1551a05de5dStedu strlist:	/* empty */ {
1561a05de5dStedu 			if (!($$.strlist = calloc(1, sizeof(char *))))
1571a05de5dStedu 				errx(1, "can't allocate strlist");
1581a05de5dStedu 		} | strlist TSTRING {
1591a05de5dStedu 			int nstr = arraylen($1.strlist);
160*ce8e0faaSderaadt 
1611a05de5dStedu 			if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
162c79a7a63Sbenno 			    sizeof(char *))))
1631a05de5dStedu 				errx(1, "can't allocate strlist");
1641a05de5dStedu 			$$.strlist[nstr] = $2.str;
1651a05de5dStedu 			$$.strlist[nstr + 1] = NULL;
166d023f658Stedu 		} ;
16706bf2893Sdjm 
16806bf2893Sdjm 
1697bfbda14Stedu ident:		TSTRING {
1707bfbda14Stedu 			$$.str = $1.str;
1717bfbda14Stedu 		} ;
1727bfbda14Stedu 
1737bfbda14Stedu target:		/* optional */ {
1747bfbda14Stedu 			$$.str = NULL;
1757bfbda14Stedu 		} | TAS TSTRING {
1767bfbda14Stedu 			$$.str = $2.str;
1777bfbda14Stedu 		} ;
1787bfbda14Stedu 
1797bfbda14Stedu cmd:		/* optional */ {
180cb7cef4cSzhuk 			$$.cmd = NULL;
181cb7cef4cSzhuk 			$$.cmdargs = NULL;
182cb7cef4cSzhuk 		} | TCMD TSTRING args {
183cb7cef4cSzhuk 			$$.cmd = $2.str;
184cb7cef4cSzhuk 			$$.cmdargs = $3.cmdargs;
185cb7cef4cSzhuk 		} ;
186cb7cef4cSzhuk 
187cb7cef4cSzhuk args:		/* empty */ {
188cb7cef4cSzhuk 			$$.cmdargs = NULL;
1891a05de5dStedu 		} | TARGS strlist {
1901a05de5dStedu 			$$.cmdargs = $2.strlist;
1917bfbda14Stedu 		} ;
1927bfbda14Stedu 
1937bfbda14Stedu %%
1947bfbda14Stedu 
1957bfbda14Stedu void
1967bfbda14Stedu yyerror(const char *fmt, ...)
1977bfbda14Stedu {
1987bfbda14Stedu 	va_list va;
1997bfbda14Stedu 
2000a57f0e3Sgsoares 	fprintf(stderr, "doas: ");
2017bfbda14Stedu 	va_start(va, fmt);
2027c3b0d20Szhuk 	vfprintf(stderr, fmt, va);
2037c3b0d20Szhuk 	va_end(va);
204cbc92769Stobias 	fprintf(stderr, " at line %lu\n", yylval.lineno + 1);
205cbc92769Stobias 	parse_error = 1;
2067bfbda14Stedu }
2077bfbda14Stedu 
2082bab682cSderaadt static struct keyword {
2097bfbda14Stedu 	const char *word;
2107bfbda14Stedu 	int token;
2117bfbda14Stedu } keywords[] = {
2127bfbda14Stedu 	{ "deny", TDENY },
2137bfbda14Stedu 	{ "permit", TPERMIT },
2147bfbda14Stedu 	{ "as", TAS },
2157bfbda14Stedu 	{ "cmd", TCMD },
216cb7cef4cSzhuk 	{ "args", TARGS },
2177bfbda14Stedu 	{ "nopass", TNOPASS },
218d4bf2b56Skn 	{ "nolog", TNOLOG },
2190a39d05fStedu 	{ "persist", TPERSIST },
2207bfbda14Stedu 	{ "keepenv", TKEEPENV },
221ab7e7725Stedu 	{ "setenv", TSETENV },
2227bfbda14Stedu };
2237bfbda14Stedu 
2247bfbda14Stedu int
yylex(void)2257bfbda14Stedu yylex(void)
2267bfbda14Stedu {
2277bfbda14Stedu 	char buf[1024], *ebuf, *p, *str;
228cbc92769Stobias 	int c, quoted = 0, quotes = 0, qerr = 0, escape = 0, nonkw = 0;
229cbc92769Stobias 	unsigned long qpos = 0;
230618b6875Smillert 	size_t i;
2317bfbda14Stedu 
2327bfbda14Stedu 	p = buf;
2337bfbda14Stedu 	ebuf = buf + sizeof(buf);
234bc1c10e1Szhuk 
235c3de7d61Sbenno repeat:
236bc1c10e1Szhuk 	/* skip whitespace first */
237bc1c10e1Szhuk 	for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
2387c3b0d20Szhuk 		yylval.colno++;
239bc1c10e1Szhuk 
240bc1c10e1Szhuk 	/* check for special one-character constructions */
2417bfbda14Stedu 	switch (c) {
2427bfbda14Stedu 		case '\n':
2437c3b0d20Szhuk 			yylval.colno = 0;
2447c3b0d20Szhuk 			yylval.lineno++;
245bc1c10e1Szhuk 			/* FALLTHROUGH */
2467bfbda14Stedu 		case '{':
2477bfbda14Stedu 		case '}':
2487bfbda14Stedu 			return c;
2497bfbda14Stedu 		case '#':
250bc1c10e1Szhuk 			/* skip comments; NUL is allowed; no continuation */
251bc1c10e1Szhuk 			while ((c = getc(yyfp)) != '\n')
2527bfbda14Stedu 				if (c == EOF)
25380453096Stedu 					goto eof;
2547c3b0d20Szhuk 			yylval.colno = 0;
2557c3b0d20Szhuk 			yylval.lineno++;
2567bfbda14Stedu 			return c;
2577bfbda14Stedu 		case EOF:
25880453096Stedu 			goto eof;
2597bfbda14Stedu 	}
260bc1c10e1Szhuk 
261bc1c10e1Szhuk 	/* parsing next word */
2627c3b0d20Szhuk 	for (;; c = getc(yyfp), yylval.colno++) {
263f83af921Szhuk 		switch (c) {
264bc1c10e1Szhuk 		case '\0':
265cbc92769Stobias 			yyerror("unallowed character NUL in column %lu",
26622ac959bSderaadt 			    yylval.colno + 1);
267bc1c10e1Szhuk 			escape = 0;
268bc1c10e1Szhuk 			continue;
269bc1c10e1Szhuk 		case '\\':
270bc1c10e1Szhuk 			escape = !escape;
271bc1c10e1Szhuk 			if (escape)
272bc1c10e1Szhuk 				continue;
273bc1c10e1Szhuk 			break;
274f83af921Szhuk 		case '\n':
275cbc92769Stobias 			if (quotes && !qerr) {
276cbc92769Stobias 				yyerror("unterminated quotes in column %lu",
2777c3b0d20Szhuk 				    qpos + 1);
278cbc92769Stobias 				qerr = 1;
279cbc92769Stobias 			}
280bc1c10e1Szhuk 			if (escape) {
281bc1c10e1Szhuk 				nonkw = 1;
282bc1c10e1Szhuk 				escape = 0;
283cbc92769Stobias 				yylval.colno = ULONG_MAX;
284a9033b92Smikeb 				yylval.lineno++;
285bc1c10e1Szhuk 				continue;
286bc1c10e1Szhuk 			}
287bc1c10e1Szhuk 			goto eow;
288bc1c10e1Szhuk 		case EOF:
289bc1c10e1Szhuk 			if (escape)
290cbc92769Stobias 				yyerror("unterminated escape in column %lu",
2917c3b0d20Szhuk 				    yylval.colno);
292cbc92769Stobias 			if (quotes && !qerr)
293cbc92769Stobias 				yyerror("unterminated quotes in column %lu",
2947c3b0d20Szhuk 				    qpos + 1);
2957c3b0d20Szhuk 			goto eow;
296f83af921Szhuk 		case '{':
297f83af921Szhuk 		case '}':
298f83af921Szhuk 		case '#':
299f83af921Szhuk 		case ' ':
300f83af921Szhuk 		case '\t':
301bc1c10e1Szhuk 			if (!escape && !quotes)
302f83af921Szhuk 				goto eow;
303bc1c10e1Szhuk 			break;
304bc1c10e1Szhuk 		case '"':
305bc1c10e1Szhuk 			if (!escape) {
306cbc92769Stobias 				quoted = 1;
307bc1c10e1Szhuk 				quotes = !quotes;
308bc1c10e1Szhuk 				if (quotes) {
309bc1c10e1Szhuk 					nonkw = 1;
310cbc92769Stobias 					qerr = 0;
3117c3b0d20Szhuk 					qpos = yylval.colno;
312bc1c10e1Szhuk 				}
313bc1c10e1Szhuk 				continue;
314bc1c10e1Szhuk 			}
315f83af921Szhuk 		}
3167bfbda14Stedu 		*p++ = c;
317402eeae3Stedu 		if (p == ebuf) {
3187c3b0d20Szhuk 			yyerror("too long line");
319402eeae3Stedu 			p = buf;
320402eeae3Stedu 		}
321bc1c10e1Szhuk 		escape = 0;
3227bfbda14Stedu 	}
323bc1c10e1Szhuk 
324f83af921Szhuk eow:
3257bfbda14Stedu 	*p = 0;
3267bfbda14Stedu 	if (c != EOF)
3277bfbda14Stedu 		ungetc(c, yyfp);
328bc1c10e1Szhuk 	if (p == buf) {
329bc1c10e1Szhuk 		/*
33022ac959bSderaadt 		 * There could be a number of reasons for empty buffer,
33122ac959bSderaadt 		 * and we handle all of them here, to avoid cluttering
33222ac959bSderaadt 		 * the main loop.
333bc1c10e1Szhuk 		 */
334bc1c10e1Szhuk 		if (c == EOF)
33580453096Stedu 			goto eof;
336cbc92769Stobias 		else if (!quoted)    /* accept, e.g., empty args: cmd foo args "" */
337bc1c10e1Szhuk 			goto repeat;
338bc1c10e1Szhuk 	}
339bc1c10e1Szhuk 	if (!nonkw) {
3407bfbda14Stedu 		for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
3417bfbda14Stedu 			if (strcmp(buf, keywords[i].word) == 0)
3427bfbda14Stedu 				return keywords[i].token;
3437bfbda14Stedu 		}
344bc1c10e1Szhuk 	}
3457bfbda14Stedu 	if ((str = strdup(buf)) == NULL)
346a062aa9dSkrw 		err(1, "%s", __func__);
3477bfbda14Stedu 	yylval.str = str;
3487bfbda14Stedu 	return TSTRING;
34980453096Stedu 
35080453096Stedu eof:
35180453096Stedu 	if (ferror(yyfp))
35280453096Stedu 		yyerror("input error reading config");
35380453096Stedu 	return 0;
3547bfbda14Stedu }
355