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