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 }