]> git.armaanb.net Git - opendoas.git/blob - parse.y
envlist and arglist are both string lists; simplify ok benno
[opendoas.git] / parse.y
1 /* $OpenBSD: parse.y,v 1.10 2015/07/24 06:36:42 zhuk 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 <err.h>
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <stdint.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.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                 };
42                 const char **strlist;
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;
54 static int maxrules;
55
56 int parse_errors = 0;
57
58 static void yyerror(const char *, ...);
59 static int yylex(void);
60
61 static size_t
62 arraylen(const char **arr)
63 {
64         size_t cnt = 0;
65
66         while (*arr) {
67                 cnt++;
68                 arr++;
69         }
70         return cnt;
71 }
72
73 %}
74
75 %token TPERMIT TDENY TAS TCMD TARGS
76 %token TNOPASS TPERSIST TKEEPENV TSETENV
77 %token TSTRING
78
79 %%
80
81 grammar:        /* empty */
82                 | grammar '\n'
83                 | grammar rule '\n'
84                 | error '\n'
85                 ;
86
87 rule:           action ident target cmd {
88                         struct rule *r;
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 = 63;
102                                 else
103                                         maxrules *= 2;
104                                 if (!(rules = reallocarray(rules, maxrules,
105                                     sizeof(*rules))))
106                                         errx(1, "can't allocate rules");
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                 } | TPERSIST {
143                         $$.options = PERSIST;
144                         $$.envlist = NULL;
145                 } | TKEEPENV {
146                         $$.options = KEEPENV;
147                         $$.envlist = NULL;
148                 } | TSETENV '{' strlist '}' {
149                         $$.options = 0;
150                         $$.envlist = $3.strlist;
151                 } ;
152
153 strlist:        /* empty */ {
154                         if (!($$.strlist = calloc(1, sizeof(char *))))
155                                 errx(1, "can't allocate strlist");
156                 } | strlist TSTRING {
157                         int nstr = arraylen($1.strlist);
158                         if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
159                             sizeof(char *))))
160                                 errx(1, "can't allocate strlist");
161                         $$.strlist[nstr] = $2.str;
162                         $$.strlist[nstr + 1] = NULL;
163                 } ;
164
165
166 ident:          TSTRING {
167                         $$.str = $1.str;
168                 } ;
169
170 target:         /* optional */ {
171                         $$.str = NULL;
172                 } | TAS TSTRING {
173                         $$.str = $2.str;
174                 } ;
175
176 cmd:            /* optional */ {
177                         $$.cmd = NULL;
178                         $$.cmdargs = NULL;
179                 } | TCMD TSTRING args {
180                         $$.cmd = $2.str;
181                         $$.cmdargs = $3.cmdargs;
182                 } ;
183
184 args:           /* empty */ {
185                         $$.cmdargs = NULL;
186                 } | TARGS strlist {
187                         $$.cmdargs = $2.strlist;
188                 } ;
189
190 %%
191
192 void
193 yyerror(const char *fmt, ...)
194 {
195         va_list va;
196
197         va_start(va, fmt);
198         vfprintf(stderr, fmt, va);
199         va_end(va);
200         fprintf(stderr, " at line %d\n", yylval.lineno + 1);
201         parse_errors++;
202 }
203
204 static struct keyword {
205         const char *word;
206         int token;
207 } keywords[] = {
208         { "deny", TDENY },
209         { "permit", TPERMIT },
210         { "as", TAS },
211         { "cmd", TCMD },
212         { "args", TARGS },
213         { "nopass", TNOPASS },
214         { "persist", TPERSIST },
215         { "keepenv", TKEEPENV },
216         { "setenv", TSETENV },
217 };
218
219 int
220 yylex(void)
221 {
222         char buf[1024], *ebuf, *p, *str;
223         int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
224
225         p = buf;
226         ebuf = buf + sizeof(buf);
227
228 repeat:
229         /* skip whitespace first */
230         for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
231                 yylval.colno++;
232
233         /* check for special one-character constructions */
234         switch (c) {
235                 case '\n':
236                         yylval.colno = 0;
237                         yylval.lineno++;
238                         /* FALLTHROUGH */
239                 case '{':
240                 case '}':
241                         return c;
242                 case '#':
243                         /* skip comments; NUL is allowed; no continuation */
244                         while ((c = getc(yyfp)) != '\n')
245                                 if (c == EOF)
246                                         return 0;
247                         yylval.colno = 0;
248                         yylval.lineno++;
249                         return c;
250                 case EOF:
251                         return 0;
252         }
253
254         /* parsing next word */
255         for (;; c = getc(yyfp), yylval.colno++) {
256                 switch (c) {
257                 case '\0':
258                         yyerror("unallowed character NUL in column %d",
259                             yylval.colno + 1);
260                         escape = 0;
261                         continue;
262                 case '\\':
263                         escape = !escape;
264                         if (escape)
265                                 continue;
266                         break;
267                 case '\n':
268                         if (quotes)
269                                 yyerror("unterminated quotes in column %d",
270                                     qpos + 1);
271                         if (escape) {
272                                 nonkw = 1;
273                                 escape = 0;
274                                 continue;
275                         }
276                         goto eow;
277                 case EOF:
278                         if (escape)
279                                 yyerror("unterminated escape in column %d",
280                                     yylval.colno);
281                         if (quotes)
282                                 yyerror("unterminated quotes in column %d",
283                                     qpos + 1);
284                         goto eow;
285                         /* FALLTHROUGH */
286                 case '{':
287                 case '}':
288                 case '#':
289                 case ' ':
290                 case '\t':
291                         if (!escape && !quotes)
292                                 goto eow;
293                         break;
294                 case '"':
295                         if (!escape) {
296                                 quotes = !quotes;
297                                 if (quotes) {
298                                         nonkw = 1;
299                                         qpos = yylval.colno;
300                                 }
301                                 continue;
302                         }
303                 }
304                 *p++ = c;
305                 if (p == ebuf)
306                         yyerror("too long line");
307                 escape = 0;
308         }
309
310 eow:
311         *p = 0;
312         if (c != EOF)
313                 ungetc(c, yyfp);
314         if (p == buf) {
315                 /*
316                  * There could be a number of reasons for empty buffer,
317                  * and we handle all of them here, to avoid cluttering
318                  * the main loop.
319                  */
320                 if (c == EOF)
321                         return 0;
322                 else if (qpos == -1)    /* accept, e.g., empty args: cmd foo args "" */
323                         goto repeat;
324         }
325         if (!nonkw) {
326                 size_t i;
327                 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
328                         if (strcmp(buf, keywords[i].word) == 0)
329                                 return keywords[i].token;
330                 }
331         }
332         if ((str = strdup(buf)) == NULL)
333                 err(1, "strdup");
334         yylval.str = str;
335         return TSTRING;
336 }