doas

https://man.openbsd.org/doas.1
git clone anongit@rnpnr.xyz:doas.git
Log | Files | Refs | Feed

parse.y (7081B)


      1 /* $OpenBSD: parse.y,v 1.31 2022/03/22 20:36:49 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 <limits.h>
     22 #include <unistd.h>
     23 #include <stdlib.h>
     24 #include <stdint.h>
     25 #include <stdarg.h>
     26 #include <stdio.h>
     27 #include <string.h>
     28 #include <err.h>
     29 
     30 #include "doas.h"
     31 
     32 typedef struct {
     33 	union {
     34 		struct {
     35 			int action;
     36 			int options;
     37 			const char *cmd;
     38 			const char **cmdargs;
     39 			const char **envlist;
     40 		};
     41 		const char **strlist;
     42 		const char *str;
     43 	};
     44 	unsigned int lineno;
     45 	unsigned int colno;
     46 } yystype;
     47 #define YYSTYPE yystype
     48 
     49 FILE *yyfp;
     50 
     51 struct rule **rules;
     52 size_t nrules;
     53 static size_t maxrules;
     54 
     55 int parse_error = 0;
     56 
     57 static void yyerror(const char *, ...);
     58 static int yylex(void);
     59 
     60 static size_t
     61 arraylen(const char **arr)
     62 {
     63 	size_t cnt = 0;
     64 
     65 	while (*arr) {
     66 		cnt++;
     67 		arr++;
     68 	}
     69 	return cnt;
     70 }
     71 
     72 %}
     73 
     74 %token TPERMIT TDENY TAS TCMD TARGS
     75 %token TNOPASS TNOLOG TPERSIST TKEEPENV TSETENV
     76 %token TSTRING
     77 
     78 %%
     79 
     80 grammar:	/* empty */
     81 		| grammar '\n'
     82 		| grammar rule '\n'
     83 		| error '\n'
     84 		;
     85 
     86 rule:		action ident target cmd {
     87 			struct rule *r;
     88 
     89 			r = calloc(1, sizeof(*r));
     90 			if (!r)
     91 				errx(1, "can't allocate rule");
     92 			r->action = $1.action;
     93 			r->options = $1.options;
     94 			r->envlist = $1.envlist;
     95 			r->ident = $2.str;
     96 			r->target = $3.str;
     97 			r->cmd = $4.cmd;
     98 			r->cmdargs = $4.cmdargs;
     99 			if (nrules == maxrules) {
    100 				if (maxrules == 0)
    101 					maxrules = 32;
    102 				rules = reallocarray(rules, maxrules,
    103 				    2 * sizeof(*rules));
    104 				if (!rules)
    105 					errx(1, "can't allocate rules");
    106 				maxrules *= 2;
    107 			}
    108 			rules[nrules++] = r;
    109 		} ;
    110 
    111 action:		TPERMIT options {
    112 			$$.action = PERMIT;
    113 			$$.options = $2.options;
    114 			$$.envlist = $2.envlist;
    115 		} | TDENY {
    116 			$$.action = DENY;
    117 			$$.options = 0;
    118 			$$.envlist = NULL;
    119 		} ;
    120 
    121 options:	/* none */ {
    122 			$$.options = 0;
    123 			$$.envlist = NULL;
    124 		} | options option {
    125 			$$.options = $1.options | $2.options;
    126 			$$.envlist = $1.envlist;
    127 			if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) {
    128 				yyerror("can't combine nopass and persist");
    129 				YYERROR;
    130 			}
    131 			if ($2.envlist) {
    132 				if ($$.envlist) {
    133 					yyerror("can't have two setenv sections");
    134 					YYERROR;
    135 				} else
    136 					$$.envlist = $2.envlist;
    137 			}
    138 		} ;
    139 option:		TNOPASS {
    140 			$$.options = NOPASS;
    141 			$$.envlist = NULL;
    142 		} | TNOLOG {
    143 			$$.options = NOLOG;
    144 			$$.envlist = NULL;
    145 		} | TPERSIST {
    146 			$$.options = PERSIST;
    147 			$$.envlist = NULL;
    148 		} | TKEEPENV {
    149 			$$.options = KEEPENV;
    150 			$$.envlist = NULL;
    151 		} | TSETENV '{' strlist '}' {
    152 			$$.options = 0;
    153 			$$.envlist = $3.strlist;
    154 		} ;
    155 
    156 strlist:	/* empty */ {
    157 			if (!($$.strlist = calloc(1, sizeof(char *))))
    158 				errx(1, "can't allocate strlist");
    159 		} | strlist TSTRING {
    160 			int nstr = arraylen($1.strlist);
    161 
    162 			if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
    163 			    sizeof(char *))))
    164 				errx(1, "can't allocate strlist");
    165 			$$.strlist[nstr] = $2.str;
    166 			$$.strlist[nstr + 1] = NULL;
    167 		} ;
    168 
    169 
    170 ident:		TSTRING {
    171 			$$.str = $1.str;
    172 		} ;
    173 
    174 target:		/* optional */ {
    175 			$$.str = NULL;
    176 		} | TAS TSTRING {
    177 			$$.str = $2.str;
    178 		} ;
    179 
    180 cmd:		/* optional */ {
    181 			$$.cmd = NULL;
    182 			$$.cmdargs = NULL;
    183 		} | TCMD TSTRING args {
    184 			$$.cmd = $2.str;
    185 			$$.cmdargs = $3.cmdargs;
    186 		} ;
    187 
    188 args:		/* empty */ {
    189 			$$.cmdargs = NULL;
    190 		} | TARGS strlist {
    191 			$$.cmdargs = $2.strlist;
    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 %lu\n", yylval.lineno + 1);
    206 	parse_error = 1;
    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 	{ "nolog", TNOLOG },
    220 	{ "persist", TPERSIST },
    221 	{ "keepenv", TKEEPENV },
    222 	{ "setenv", TSETENV },
    223 };
    224 
    225 int
    226 yylex(void)
    227 {
    228 	char buf[1024], *ebuf, *p, *str;
    229 	int c, quoted = 0, quotes = 0, qerr = 0, escape = 0, nonkw = 0;
    230 	unsigned long qpos = 0;
    231 	size_t i;
    232 
    233 	p = buf;
    234 	ebuf = buf + sizeof(buf);
    235 
    236 repeat:
    237 	/* skip whitespace first */
    238 	for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
    239 		yylval.colno++;
    240 
    241 	/* check for special one-character constructions */
    242 	switch (c) {
    243 		case '\n':
    244 			yylval.colno = 0;
    245 			yylval.lineno++;
    246 			/* FALLTHROUGH */
    247 		case '{':
    248 		case '}':
    249 			return c;
    250 		case '#':
    251 			/* skip comments; NUL is allowed; no continuation */
    252 			while ((c = getc(yyfp)) != '\n')
    253 				if (c == EOF)
    254 					goto eof;
    255 			yylval.colno = 0;
    256 			yylval.lineno++;
    257 			return c;
    258 		case EOF:
    259 			goto eof;
    260 	}
    261 
    262 	/* parsing next word */
    263 	for (;; c = getc(yyfp), yylval.colno++) {
    264 		switch (c) {
    265 		case '\0':
    266 			yyerror("unallowed character NUL in column %lu",
    267 			    yylval.colno + 1);
    268 			escape = 0;
    269 			continue;
    270 		case '\\':
    271 			escape = !escape;
    272 			if (escape)
    273 				continue;
    274 			break;
    275 		case '\n':
    276 			if (quotes && !qerr) {
    277 				yyerror("unterminated quotes in column %lu",
    278 				    qpos + 1);
    279 				qerr = 1;
    280 			}
    281 			if (escape) {
    282 				nonkw = 1;
    283 				escape = 0;
    284 				yylval.colno = ULONG_MAX;
    285 				yylval.lineno++;
    286 				continue;
    287 			}
    288 			goto eow;
    289 		case EOF:
    290 			if (escape)
    291 				yyerror("unterminated escape in column %lu",
    292 				    yylval.colno);
    293 			if (quotes && !qerr)
    294 				yyerror("unterminated quotes in column %lu",
    295 				    qpos + 1);
    296 			goto eow;
    297 		case '{':
    298 		case '}':
    299 		case '#':
    300 		case ' ':
    301 		case '\t':
    302 			if (!escape && !quotes)
    303 				goto eow;
    304 			break;
    305 		case '"':
    306 			if (!escape) {
    307 				quoted = 1;
    308 				quotes = !quotes;
    309 				if (quotes) {
    310 					nonkw = 1;
    311 					qerr = 0;
    312 					qpos = yylval.colno;
    313 				}
    314 				continue;
    315 			}
    316 		}
    317 		*p++ = c;
    318 		if (p == ebuf) {
    319 			yyerror("too long line");
    320 			p = buf;
    321 		}
    322 		escape = 0;
    323 	}
    324 
    325 eow:
    326 	*p = 0;
    327 	if (c != EOF)
    328 		ungetc(c, yyfp);
    329 	if (p == buf) {
    330 		/*
    331 		 * There could be a number of reasons for empty buffer,
    332 		 * and we handle all of them here, to avoid cluttering
    333 		 * the main loop.
    334 		 */
    335 		if (c == EOF)
    336 			goto eof;
    337 		else if (!quoted)    /* accept, e.g., empty args: cmd foo args "" */
    338 			goto repeat;
    339 	}
    340 	if (!nonkw) {
    341 		for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
    342 			if (strcmp(buf, keywords[i].word) == 0)
    343 				return keywords[i].token;
    344 		}
    345 	}
    346 	if ((str = strdup(buf)) == NULL)
    347 		err(1, "%s", __func__);
    348 	yylval.str = str;
    349 	return TSTRING;
    350 
    351 eof:
    352 	if (ferror(yyfp))
    353 		yyerror("input error reading config");
    354 	return 0;
    355 }