]> git.armaanb.net Git - opendoas.git/blob - parse.y
simplify makefile
[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 int nrules;
56 static int 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
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 = 63;
104                                 else
105                                         maxrules *= 2;
106                                 if (!(rules = reallocarray(rules, maxrules,
107                                     sizeof(*rules))))
108                                         errx(1, "can't allocate rules");
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                 } | TSETENV '{' strlist '}' {
154                         $$.options = 0;
155                         $$.envlist = $3.strlist;
156                 } ;
157
158 strlist:        /* empty */ {
159                         if (!($$.strlist = calloc(1, sizeof(char *))))
160                                 errx(1, "can't allocate strlist");
161                 } | strlist TSTRING {
162                         int nstr = arraylen($1.strlist);
163                         if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
164                             sizeof(char *))))
165                                 errx(1, "can't allocate strlist");
166                         $$.strlist[nstr] = $2.str;
167                         $$.strlist[nstr + 1] = NULL;
168                 } ;
169
170
171 ident:          TSTRING {
172                         $$.str = $1.str;
173                 } ;
174
175 target:         /* optional */ {
176                         $$.str = NULL;
177                 } | TAS TSTRING {
178                         $$.str = $2.str;
179                 } ;
180
181 cmd:            /* optional */ {
182                         $$.cmd = NULL;
183                         $$.cmdargs = NULL;
184                 } | TCMD TSTRING args {
185                         $$.cmd = $2.str;
186                         $$.cmdargs = $3.cmdargs;
187                 } ;
188
189 args:           /* empty */ {
190                         $$.cmdargs = NULL;
191                 } | TARGS strlist {
192                         $$.cmdargs = $2.strlist;
193                 } ;
194
195 %%
196
197 void
198 yyerror(const char *fmt, ...)
199 {
200         va_list va;
201
202         fprintf(stderr, "doas: ");
203         va_start(va, fmt);
204         vfprintf(stderr, fmt, va);
205         va_end(va);
206         fprintf(stderr, " at line %d\n", yylval.lineno + 1);
207         parse_errors++;
208 }
209
210 static struct keyword {
211         const char *word;
212         int token;
213 } keywords[] = {
214         { "deny", TDENY },
215         { "permit", TPERMIT },
216         { "as", TAS },
217         { "cmd", TCMD },
218         { "args", TARGS },
219         { "nopass", TNOPASS },
220         { "nolog", TNOLOG },
221         { "persist", TPERSIST },
222         { "keepenv", TKEEPENV },
223         { "setenv", TSETENV },
224 };
225
226 int
227 yylex(void)
228 {
229         char buf[1024], *ebuf, *p, *str;
230         int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
231
232         p = buf;
233         ebuf = buf + sizeof(buf);
234
235 repeat:
236         /* skip whitespace first */
237         for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
238                 yylval.colno++;
239
240         /* check for special one-character constructions */
241         switch (c) {
242                 case '\n':
243                         yylval.colno = 0;
244                         yylval.lineno++;
245                         /* FALLTHROUGH */
246                 case '{':
247                 case '}':
248                         return c;
249                 case '#':
250                         /* skip comments; NUL is allowed; no continuation */
251                         while ((c = getc(yyfp)) != '\n')
252                                 if (c == EOF)
253                                         return 0;
254                         yylval.colno = 0;
255                         yylval.lineno++;
256                         return c;
257                 case EOF:
258                         return 0;
259         }
260
261         /* parsing next word */
262         for (;; c = getc(yyfp), yylval.colno++) {
263                 switch (c) {
264                 case '\0':
265                         yyerror("unallowed character NUL in column %d",
266                             yylval.colno + 1);
267                         escape = 0;
268                         continue;
269                 case '\\':
270                         escape = !escape;
271                         if (escape)
272                                 continue;
273                         break;
274                 case '\n':
275                         if (quotes)
276                                 yyerror("unterminated quotes in column %d",
277                                     qpos + 1);
278                         if (escape) {
279                                 nonkw = 1;
280                                 escape = 0;
281                                 continue;
282                         }
283                         goto eow;
284                 case EOF:
285                         if (escape)
286                                 yyerror("unterminated escape in column %d",
287                                     yylval.colno);
288                         if (quotes)
289                                 yyerror("unterminated quotes in column %d",
290                                     qpos + 1);
291                         goto eow;
292                         /* FALLTHROUGH */
293                 case '{':
294                 case '}':
295                 case '#':
296                 case ' ':
297                 case '\t':
298                         if (!escape && !quotes)
299                                 goto eow;
300                         break;
301                 case '"':
302                         if (!escape) {
303                                 quotes = !quotes;
304                                 if (quotes) {
305                                         nonkw = 1;
306                                         qpos = yylval.colno;
307                                 }
308                                 continue;
309                         }
310                 }
311                 *p++ = c;
312                 if (p == ebuf)
313                         yyerror("too long line");
314                 escape = 0;
315         }
316
317 eow:
318         *p = 0;
319         if (c != EOF)
320                 ungetc(c, yyfp);
321         if (p == buf) {
322                 /*
323                  * There could be a number of reasons for empty buffer,
324                  * and we handle all of them here, to avoid cluttering
325                  * the main loop.
326                  */
327                 if (c == EOF)
328                         return 0;
329                 else if (qpos == -1)    /* accept, e.g., empty args: cmd foo args "" */
330                         goto repeat;
331         }
332         if (!nonkw) {
333                 size_t i;
334                 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
335                         if (strcmp(buf, keywords[i].word) == 0)
336                                 return keywords[i].token;
337                 }
338         }
339         if ((str = strdup(buf)) == NULL)
340                 err(1, "%s", __func__);
341         yylval.str = str;
342         return TSTRING;
343 }