Blob


1 /*
2 * Copyright (c) 2016 Martijn van Duren <martijn@openbsd.org>
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 */
18 %{
19 #include <sys/stat.h>
20 #include <sys/types.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <libgen.h>
25 #include <unistd.h>
26 #include <stdint.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <err.h>
32 #include "vias.h"
34 typedef struct {
35 union {
36 struct {
37 int action;
38 int options;
39 const char **files;
40 };
41 const char *str;
42 };
43 int lineno;
44 int colno;
45 } yystype;
46 #define YYSTYPE yystype
48 FILE *yyfp;
50 struct rule **rules;
51 int nrules, maxrules;
52 int parse_errors = 0;
53 int obsolete_warned = 0;
55 void yyerror(const char *, ...);
56 int yylex(void);
57 int yyparse(void);
59 %}
61 %token TPERMIT TDENY TAS TEDIT
62 %token TNOPASS TPERSIST
63 %token TSTRING
65 %%
67 grammar: /* empty */
68 | grammar '\n'
69 | grammar rule '\n'
70 | error '\n'
71 ;
73 rule: action ident target edit {
74 struct rule *r;
75 if ((r = calloc(1, sizeof(*r))) == NULL)
76 err(1, NULL);
77 r->action = $1.action;
78 r->options = $1.options;
79 r->ident = $2.str;
80 r->target = $3.str;
81 r->files = $4.files;
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))) == NULL)
89 err(1, NULL);
90 }
91 rules[nrules++] = r;
92 } ;
94 action: TPERMIT options {
95 $$.action = PERMIT;
96 $$.options = $2.options;
97 } | TDENY {
98 $$.action = DENY;
99 $$.options = 0;
100 } ;
102 options: /* none */ {
103 $$.options = 0;
104 } | options option {
105 $$.options = $1.options | $2.options;
106 if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) {
107 yyerror("can't combine nopass and persist");
108 YYERROR;
110 } ;
111 option: TNOPASS {
112 $$.options = NOPASS;
113 } | TPERSIST {
114 $$.options = PERSIST;
115 };
117 ident: TSTRING {
118 $$.str = $1.str;
119 } ;
121 target: /* optional */ {
122 $$.str = NULL;
123 } | TAS TSTRING {
124 $$.str = $2.str;
125 } ;
127 edit: /* none */ {
128 $$.files = NULL;
129 } | TEDIT files {
130 if (arraylen($2.files))
131 $$.files = $2.files;
132 else {
133 free($2.files);
134 $$.files = NULL;
136 } ;
138 files: /* empty */ {
139 if (($$.files = calloc(1, sizeof(char *))) == NULL)
140 err(1, NULL);
141 } | files TSTRING {
142 int nargs = arraylen($1.files);
143 char *str;
144 int strl, skip = 0;
145 if ($2.str[0] != '/') {
146 warnx("File %s can't be relative", $2.str);
147 skip = 1;
149 if ((str = realpath($2.str, NULL)) == NULL &&
150 errno != ENOENT) {
151 warn("Problem verifying %s", $2.str);
152 skip = 1;
154 if (str != NULL && strcmp($2.str, str)) {
155 strl = strlen(str);
156 if ((str = reallocarray(str, strl + 2,
157 sizeof(*str))) == NULL)
158 err(1, NULL);
159 str[strl] = '/';
160 str[strl+1] = '\0';
161 if (strcmp($2.str, str)) {
162 warnx("file %s needs to be a resolved "
163 "path", $2.str);
164 skip = 1;
167 free(str);
168 if (!skip) {
169 if (($$.files = reallocarray($1.files, nargs + 2,
170 sizeof(char *))) == NULL)
171 err(1, NULL);
172 $$.files[nargs] = $2.str;
173 $$.files[nargs + 1] = NULL;
175 } ;
177 %%
179 void
180 yyerror(const char *fmt, ...)
182 va_list va;
184 fprintf(stderr, "vias: ");
185 va_start(va, fmt);
186 vfprintf(stderr, fmt, va);
187 va_end(va);
188 fprintf(stderr, " at line %d\n", yylval.lineno + 1);
189 parse_errors++;
192 struct keyword {
193 const char *word;
194 int token;
195 } keywords[] = {
196 { "deny", TDENY },
197 { "permit", TPERMIT },
198 { "as", TAS },
199 { "nopass", TNOPASS },
200 { "persist", TPERSIST },
201 { "edit", TEDIT },
202 };
204 int
205 yylex(void)
207 char buf[1024], *ebuf, *p, *str;
208 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
209 size_t i;
211 p = buf;
212 ebuf = buf + sizeof(buf);
214 repeat:
215 /* skip whitespace first */
216 for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
217 yylval.colno++;
219 /* check for special one-character constructions */
220 switch (c) {
221 case '\n':
222 yylval.colno = 0;
223 yylval.lineno++;
224 /* FALLTHROUGH */
225 case '{':
226 case '}':
227 return c;
228 case '#':
229 /* skip comments; NUL is allowed; no continuation */
230 while ((c = getc(yyfp)) != '\n')
231 if (c == EOF)
232 goto eof;
233 yylval.colno = 0;
234 yylval.lineno++;
235 return c;
236 case EOF:
237 goto eof;
240 /* parsing next word */
241 for (;; c = getc(yyfp), yylval.colno++) {
242 switch (c) {
243 case '\0':
244 yyerror("unallowed character NUL in column %d",
245 yylval.colno + 1);
246 escape = 0;
247 continue;
248 case '\\':
249 escape = !escape;
250 if (escape)
251 continue;
252 break;
253 case '\n':
254 if (quotes)
255 yyerror("unterminated quotes in column %d",
256 qpos + 1);
257 if (escape) {
258 nonkw = 1;
259 escape = 0;
260 yylval.colno = 0;
261 yylval.lineno++;
262 continue;
264 goto eow;
265 case EOF:
266 if (escape)
267 yyerror("unterminated escape in column %d",
268 yylval.colno);
269 if (quotes)
270 yyerror("unterminated quotes in column %d",
271 qpos + 1);
272 goto eow;
273 /* FALLTHROUGH */
274 case '{':
275 case '}':
276 case '#':
277 case ' ':
278 case '\t':
279 if (!escape && !quotes)
280 goto eow;
281 break;
282 case '"':
283 if (!escape) {
284 quotes = !quotes;
285 if (quotes) {
286 nonkw = 1;
287 qpos = yylval.colno;
289 continue;
292 *p++ = c;
293 if (p == ebuf) {
294 yyerror("too long line");
295 p = buf;
297 escape = 0;
300 eow:
301 *p = 0;
302 if (c != EOF)
303 ungetc(c, yyfp);
304 if (p == buf) {
305 /*
306 * There could be a number of reasons for empty buffer,
307 * and we handle all of them here, to avoid cluttering
308 * the main loop.
309 */
310 if (c == EOF)
311 goto eof;
312 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
313 goto repeat;
315 if (!nonkw) {
316 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
317 if (strcmp(buf, keywords[i].word) == 0)
318 return keywords[i].token;
321 if ((str = strdup(buf)) == NULL)
322 err(1, "strdup");
323 yylval.str = str;
324 return TSTRING;
326 eof:
327 if (ferror(yyfp))
328 yyerror("input error reading config");
329 return 0;