parse.y revision 1.2
1/*      $OpenBSD: parse.y,v 1.2 2001/07/16 22:09:55 markus Exp $ */
2
3/*
4 * Copyright (c) 2001 Markus Friedl.  All rights reserved.
5 * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27%{
28#include <sys/types.h>
29#include <sys/socket.h>
30#include <sys/ioctl.h>
31#include <net/if.h>
32#include <netinet/in.h>
33#include <netinet/in_systm.h>
34#include <netinet/ip.h>
35#include <netinet/ip_icmp.h>
36#include <net/pfvar.h>
37#include <arpa/inet.h>
38
39#include <stdio.h>
40#include <netdb.h>
41#include <errno.h>
42#include <string.h>
43#include <ctype.h>
44#include <err.h>
45
46#include "pfctl_parser.h"
47
48static struct pfctl *pf = NULL;
49static FILE *fin = NULL;
50static int debug = 0;
51static int lineno = 1;
52static int errors = 0;
53static int natmode = 0;
54
55static int proto = 0;	/* this is a synthesysed attribute */
56
57int			 rule_consistent(struct pf_rule *);
58int			 yyparse(void);
59struct pf_rule_addr	*new_addr(void);
60u_int32_t		 ipmask(u_int8_t);
61
62%}
63%union {
64	u_int32_t		number;
65	int			i;
66	char			*string;
67	struct pf_rule_addr	*addr;
68	struct {
69		struct pf_rule_addr	*src, *dst;
70	}			addr2;
71	struct {
72		char		*string;
73		int		not;
74	}			iface;
75	struct {
76		u_int8_t	b1;
77		u_int8_t	b2;
78		u_int16_t	w;
79	}			b;
80	struct {
81		int		a;
82		int		b;
83		int		t;
84	}			range;
85}
86%token	PASS BLOCK SCRUB RETURN IN OUT LOG LOGALL QUICK ON FROM TO FLAGS
87%token	RETURNRST RETURNICMP PROTO ALL ANY ICMPTYPE CODE KEEP STATE PORT
88%token	RDR NAT ARROW
89%token	<string> STRING
90%token	<number> NUMBER
91%token	<i>	PORTUNARY PORTBINARY
92%type	<addr>	ipportspec ipspec host portspec
93%type	<addr2>	fromto
94%type	<iface> iface
95%type	<number> address port icmptype
96%type	<i>	direction log quick keep proto
97%type	<b>	action icmpspec flags blockspec
98%type	<range>	dport rport
99%%
100
101ruleset:	/* empty */
102		| ruleset '\n'
103		| ruleset pfrule '\n'
104		| ruleset natrule '\n'
105		| ruleset rdrrule '\n'
106		;
107
108pfrule: 	action direction log quick iface proto fromto flags icmpspec keep
109		{
110			struct pf_rule r;
111
112			if (natmode)
113				errx(1, "line %d: filter rule in nat mode",
114				    lineno);
115
116			memset(&r, 0, sizeof(r));
117
118			r.action = $1.b1;
119			if ($1.b2)
120				r.return_rst = 1;
121			else
122				r.return_icmp = $1.w;
123			r.direction = $2;
124			r.log = $3;
125			r.quick = $4;
126			if ($5.string)
127				memcpy(r.ifname, $5.string, sizeof(r.ifname));
128			r.proto = $6;
129			proto = 0;	/* reset syntesysed attribute */
130
131			memcpy(&r.src, $7.src, sizeof(r.src));
132			free($7.src);
133			memcpy(&r.dst, $7.dst, sizeof(r.dst));
134			free($7.dst);
135
136			r.flags = $8.b1;
137			r.flagset = $8.b2;
138			r.type = $9.b1;
139			r.code = $9.b2;
140			r.keep_state = $10;
141
142			if (rule_consistent(&r) < 0)
143				yyerror("skipping rule due to errors");
144			else
145				pfctl_add_rule(pf, &r);
146		}
147		;
148
149action: 	PASS			{ $$.b1 = PF_PASS; }
150		| BLOCK blockspec	{ $$ = $2; $$.b1 = PF_DROP; }
151		| SCRUB			{ $$.b1 = PF_SCRUB; }
152		;
153
154blockspec:				{ $$.b2 = 0; $$.w = 0; }
155		| RETURNRST		{ $$.b2 = 1; $$.w = 0;}
156		| RETURNICMP		{
157			$$.b2 = 0;
158			$$.w = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT;
159		}
160		| RETURNICMP '(' STRING ')'	{
161			struct icmpcodeent *ic;
162
163			ic = geticmpcodebyname(ICMP_UNREACH, $3);
164			if (ic == NULL)
165				errx(1, "line %d: unknown icmp code %s",
166				    lineno, $3);
167			$$.b2 = 0;
168			$$.w = (ic->type << 8) | ic->code;
169		}
170		;
171
172direction:	IN			{ $$ = PF_IN; }
173		| OUT			{ $$ = PF_OUT; }
174		;
175
176log:					{ $$ = 0; }
177		| LOG			{ $$ = 1; }
178		| LOGALL		{ $$ = 2; }
179		;
180
181quick:					{ $$ = 0; }
182		| QUICK			{ $$ = 1; }
183		;
184
185iface:					{ $$.string = NULL; }
186		| ON STRING		{ $$.string = strdup($2); }
187		| ON '!' STRING		{ $$.string = strdup($3); $$.not = 1;}
188		;
189
190proto:					{ proto = $$ = natmode ? IPPROTO_TCP : 0; }
191		| PROTO NUMBER		{
192			struct protoent *p;
193
194			p = getprotobynumber($2);
195			if (p == NULL)
196				errx(1, "line %d: unknown protocol %d", lineno,
197				    $2);
198			proto = $$ = p->p_proto;
199		}
200		| PROTO STRING		{
201			struct protoent *p;
202
203			p = getprotobyname($2);
204			if (p == NULL)
205				errx(1, "line %d: unknown protocol %s", lineno,
206				    $2);
207			proto = $$ = p->p_proto;
208		}
209		;
210
211fromto:		ALL			{
212			$$.src = new_addr();
213			$$.dst = new_addr();
214		}
215		| FROM ipportspec TO ipportspec {
216			$$.src = $2;
217			$$.dst = $4;
218		}
219		;
220
221ipportspec:	ipspec			{ $$ = $1; }
222		| ipspec portspec		{
223			$$ = $1;
224			if ($2) {
225				$$->port[0] = $2->port[0];
226				$$->port[1] = $2->port[1];
227				$$->port_op = $2->port_op;
228				free($2);
229			}
230		}
231		;
232
233ipspec:		ANY			{ $$ = new_addr(); }
234		| '!' host		{ $$ = $2; $$->not = 1; }
235		| host			{ $$ = $1; }
236		;
237
238host:		address 		{
239			$$ = new_addr();
240			$$->addr = $1;
241			$$->mask = 0xffffffff;
242		}
243		|
244		address '/' NUMBER	{
245			$$ = new_addr();
246			$$->addr = $1;
247			$$->mask = ipmask($3);
248		}
249		;
250
251address:	STRING {
252			struct hostent *hp;
253
254			if (inet_pton(AF_INET, $1, &$$) != 1) {
255				if ((hp = gethostbyname($1)) == NULL)
256					errx(1, "line %d: cannot resolve %s",
257					    lineno, $1);
258				memcpy(&$$, hp->h_addr, sizeof(u_int32_t));
259			}
260		}
261		| NUMBER '.' NUMBER '.' NUMBER '.' NUMBER {
262                        $$ = (htonl(($1 << 24) | ($3 << 16) | ($5 << 8) | $7));
263                }
264		;
265
266portspec:	PORT PORTUNARY port	{
267			$$ = new_addr();
268			$$->port_op = $2;
269			$$->port[0] = $3;
270			$$->port[1] = $3;
271		}
272		| PORT port PORTBINARY port	{
273			$$ = new_addr();
274			$$->port[0] = $2;
275			$$->port_op = $3;
276			$$->port[1] = $4;
277		}
278		;
279
280port:		NUMBER			{ $$ = htons($1); }
281		| STRING		{
282			struct servent *s = NULL;
283
284			/* use synthesysed attribute */
285			if (proto)
286				s = getservbyname($1,
287				    proto == IPPROTO_TCP ? "tcp" : "udp");
288			$$ = (s == NULL) ? 0 : s->s_port;
289		}
290		;
291
292flags:					{ $$.b1 = 0; $$.b2 = 0; }
293		| FLAGS STRING		{
294			int f;
295
296			if ((f = parse_flags($2)) < 0)
297				errx(1, "line %d: bad flags %s", lineno, $2);
298			$$.b1 = f;
299			$$.b2 = 63;
300		}
301		| FLAGS STRING "/" STRING	{
302			int f;
303
304			if ((f = parse_flags($2)) < 0)
305				errx(1, "line %d: bad flags %s", lineno, $2);
306			$$.b1 = f;
307			if ((f = parse_flags($4)) < 0)
308				errx(1, "line %d: bad flags %s", lineno, $4);
309			$$.b2 = f;
310		}
311		;
312
313icmpspec:				{ $$.b1 = 0; $$.b2 = 0; }
314		| ICMPTYPE icmptype	{ $$.b1 = $2; $$.b2 = 0; }
315		| ICMPTYPE icmptype CODE NUMBER	{
316			$$.b1 = $2;
317			$$.b2 = $4 + 1;
318		}
319		| ICMPTYPE icmptype CODE STRING	{
320			struct icmpcodeent *ic;
321
322			$$.b1 = $2;
323			ic = geticmpcodebyname($2, $4);
324			if (ic == NULL)
325				errx(1, "line %d: unknown icmp-code %s",
326				    lineno, $4);
327			$$.b2 = ic->code + 1;
328		}
329		;
330
331icmptype:	STRING			{
332			struct icmptypeent *te;
333
334			te = geticmptypebyname($1);
335			if (te == NULL)
336				errx(1, "line %d: unknown icmp-type %s",
337				    lineno, $1);
338			$$ = te->type + 1;
339		}
340		| NUMBER		{ $$ = $1 + 1; }
341		;
342
343
344keep:					{ $$ = 0; }
345		| KEEP STATE		{ $$ = 1; }
346		;
347
348natrule:	NAT iface proto FROM ipspec TO ipspec ARROW address
349		{
350			struct pf_nat nat;
351
352			if (!natmode)
353				errx(1, "line %d: nat rule in filter mode",
354				    lineno);
355
356			memset(&nat, 0, sizeof(nat));
357
358			if ($2.string) {
359				memcpy(nat.ifname, $2.string,
360				    sizeof(nat.ifname));
361				nat.ifnot = $2.not;
362			}
363			nat.proto = $3;
364			proto = 0;	/* reset syntesysed attribute */
365
366			nat.saddr = $5->addr;
367			nat.smask = $5->mask;
368			nat.snot  = $5->not;
369			free($5);
370
371			nat.daddr = $7->addr;
372			nat.dmask = $7->mask;
373			nat.dnot  = $7->not;
374			free($7);
375
376			nat.raddr = $9;
377
378			pfctl_add_nat(pf, &nat);
379		}
380		;
381
382rdrrule:	RDR iface proto FROM ipspec TO ipspec dport ARROW address rport
383		{
384			struct pf_rdr rdr;
385
386			if (!natmode)
387				errx(1, "line %d: nat rule in filter mode",
388				    lineno);
389
390			memset(&rdr, 0, sizeof(rdr));
391
392			if ($2.string) {
393				memcpy(rdr.ifname, $2.string,
394				    sizeof(rdr.ifname));
395				rdr.ifnot = $2.not;
396			}
397			rdr.proto = $3;
398			proto = 0;	/* reset syntesysed attribute */
399
400			rdr.saddr = $5->addr;
401			rdr.smask = $5->mask;
402			rdr.snot  = $5->not;
403			free($5);
404
405			rdr.daddr = $7->addr;
406			rdr.dmask = $7->mask;
407			rdr.dnot  = $7->not;
408			free($7);
409
410			rdr.dport  = $8.a;
411			rdr.dport2 = $8.b;
412			rdr.opts  |= $8.t;
413
414			rdr.raddr = $10;
415
416			rdr.rport  = $11.a;
417			rdr.opts  |= $11.t;
418
419			pfctl_add_rdr(pf, &rdr);
420		}
421		;
422
423dport:		PORT port			{
424			$$.a = $2;
425			$$.b = $$.t = 0;
426		}
427		| PORT port ':' port		{
428			$$.a = $2;
429			$$.b = $4;
430			$$.t = PF_DPORT_RANGE;
431		}
432		;
433
434rport:		PORT port			{
435			$$.a = $2;
436			$$.b = $$.t = 0;
437		}
438		| PORT port ':' '*'		{
439			$$.a = $2;
440			$$.b = 0;
441			$$.t = PF_RPORT_RANGE;
442		}
443		;
444
445%%
446
447int
448yyerror(char *s)
449{
450	errors = 1;
451	warnx("%s near line %d", s, lineno);
452	return 0;
453}
454
455int
456rule_consistent(struct pf_rule *r)
457{
458	int problems = 0;
459
460	if (r->action == PF_SCRUB) {
461                if (r->quick) {
462			yyerror("quick does not apply to scrub");
463			problems++;
464		}
465                if (r->keep_state) {
466			yyerror("keep state does not apply to scrub");
467			problems++;
468		}
469		if (r->src.port_op) {
470			yyerror("src port does not apply to scrub");
471			problems++;
472		}
473		if (r->dst.port_op) {
474			yyerror("dst port does not apply to scrub");
475			problems++;
476		}
477		if (r->type || r->code) {
478			yyerror("icmp-type/code does not apply to scrub");
479			problems++;
480		}
481	}
482	if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP &&
483	    (r->src.port_op || r->dst.port_op)) {
484		yyerror("ports do only apply to tcp/udp");
485		problems++;
486	}
487	if (r->proto != IPPROTO_ICMP && (r->type || r->code)) {
488		yyerror("icmp-type/code does only apply to icmp");
489		problems++;
490	}
491	return -problems;
492}
493
494int
495lookup(char *s)
496{
497	int i;
498	struct keywords {
499		char	*k_name;
500		int	 k_val;
501	} keywords[] = {
502		{ "all",	ALL},
503		{ "any",	ANY},
504		{ "block",	BLOCK},
505		{ "code",	CODE},
506		{ "flags",	FLAGS},
507		{ "from",	FROM},
508		{ "icmp-type",	ICMPTYPE},
509		{ "in",		IN},
510		{ "keep",	KEEP},
511		{ "log",	LOG},
512		{ "log-all",	LOGALL},
513		{ "nat",	NAT},
514		{ "on",		ON},
515		{ "out",	OUT},
516		{ "pass",	PASS},
517		{ "port",	PORT},
518		{ "proto",	PROTO},
519		{ "quick",	QUICK},
520		{ "rdr",	RDR},
521		{ "return",	RETURN},
522		{ "return-icmp",RETURNICMP},
523		{ "return-rst",	RETURNRST},
524		{ "scrub",	SCRUB},
525		{ "state",	STATE},
526		{ "to",		TO},
527		{ NULL, 0 },
528	};
529
530	for (i = 0; keywords[i].k_name != NULL; i++) {
531		if (strcmp(s, keywords[i].k_name) == 0) {
532			if (debug > 1)
533				fprintf(stderr, "%s: %d\n", s,
534				    keywords[i].k_val);
535			return keywords[i].k_val;
536		}
537	}
538	if (debug > 1)
539		fprintf(stderr, "string: %s\n", s);
540	return STRING;
541}
542
543int
544yylex(void)
545{
546	char *p, buf[8096];
547	int c, next;
548	int token;
549
550	while ((c = getc(fin)) == ' ' || c == '\t')
551		;
552	if (c == '#')
553		while ((c = getc(fin)) != '\n' && c != EOF)
554			;
555	if (c == '-') {
556		next = getc(fin);
557		if (next == '>')
558			return ARROW;
559		ungetc(next, fin);
560	}
561	switch (c) {
562	case '=':
563		yylval.i = PF_OP_EQ;
564		return PORTUNARY;
565	case '!':
566		next = getc(fin);
567		if (next == '=') {
568			yylval.i = PF_OP_NE;
569			return PORTUNARY;
570		}
571		ungetc(next, fin);
572		break;
573	case '<':
574		next = getc(fin);
575		if (next == '>') {
576			yylval.i = PF_OP_GL;
577			return PORTBINARY;
578		} else  if (next == '=') {
579			yylval.i = PF_OP_LE;
580		} else {
581			yylval.i = PF_OP_LT;
582			ungetc(next, fin);
583		}
584		return PORTUNARY;
585		break;
586	case '>':
587		next = getc(fin);
588		if (next == '<') {
589			yylval.i = PF_OP_GL;
590			return PORTBINARY;
591		} else  if (next == '=') {
592			yylval.i = PF_OP_GE;
593		} else {
594			yylval.i = PF_OP_GT;
595			ungetc(next, fin);
596		}
597		return PORTUNARY;
598		break;
599	}
600	if (isdigit(c)) {
601		yylval.number = 0;
602		do {
603			yylval.number *= 10;
604			yylval.number += c - '0';
605		} while ((c = getc(fin)) != EOF && isdigit(c));
606		ungetc(c, fin);
607		if (debug > 1)
608			fprintf(stderr, "number: %d\n", yylval.number);
609		return NUMBER;
610	}
611
612#define allowed_in_string(x) \
613	isalnum(x) || \
614	( ispunct(x) && \
615	x != '(' && \
616	x != ')' && \
617	x != '<' && \
618	x != '>' && \
619	x != '!' && \
620	x != '=' && \
621	x != '/' && \
622	x != '#' )
623
624	if (isalnum(c)) {
625		p = buf;
626		do {
627			*p++ = c;
628			if (p-buf >= sizeof buf)
629				errx(1, "line %d: string too long", lineno);
630		} while ((c = getc(fin)) != EOF && (allowed_in_string(c)));
631		ungetc(c, fin);
632		*p = '\0';
633		token = lookup(buf);
634		yylval.string = strdup(buf);
635		return token;
636	}
637	if (c == '\n')
638		lineno++;
639	if (c == EOF)
640		return 0;
641	return c;
642}
643
644int
645parse_rules(FILE *input, struct pfctl *xpf)
646{
647	natmode = 0;
648	fin = input;
649	pf = xpf;
650	errors = 0;
651	yyparse();
652	return errors ? -1 : 0;
653}
654
655int
656parse_nat(FILE *input, struct pfctl *xpf)
657{
658	natmode = 1;
659	fin = input;
660	pf = xpf;
661	errors = 0;
662	yyparse();
663	return errors ? -1 : 0;
664}
665
666u_int32_t
667ipmask(u_int8_t b)
668{
669	u_int32_t m = 0;
670	int i;
671
672	for (i = 31; i > 31-b; --i)
673		m |= (1 << i);
674	return (htonl(m));
675}
676
677struct pf_rule_addr *
678new_addr(void)
679{
680	struct pf_rule_addr *ra;
681
682	ra = malloc(sizeof(struct pf_rule_addr));
683	if (ra == NULL)
684		errx(1, "new_addr: malloc failed: %s", strerror(errno));
685	memset(ra, 0, sizeof(*ra));
686	return ra;
687}
688