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