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