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