summaryrefslogtreecommitdiff
path: root/sbin/pfctl/parse.y
diff options
context:
space:
mode:
authorMarkus Friedl <markus@cvs.openbsd.org>2001-07-16 21:09:39 +0000
committerMarkus Friedl <markus@cvs.openbsd.org>2001-07-16 21:09:39 +0000
commit1defaba8181da172c09956573344866905b6214b (patch)
treeb9ae985615de9fa158424e408b68aa2de238ac60 /sbin/pfctl/parse.y
parent277e157f9ed157245f3a3521f9ec960c5e2a59f7 (diff)
add a yacc parser for pf.conf and nat.conf, with help from mickey@,
plus: -n now turns off all operations, and just parses the conf files ok deraadt@
Diffstat (limited to 'sbin/pfctl/parse.y')
-rw-r--r--sbin/pfctl/parse.y673
1 files changed, 673 insertions, 0 deletions
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
new file mode 100644
index 00000000000..fc9bcd79f3f
--- /dev/null
+++ b/sbin/pfctl/parse.y
@@ -0,0 +1,673 @@
+/* $OpenBSD: parse.y,v 1.1 2001/07/16 21:09:37 markus Exp $ */
+
+/*
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+%{
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <err.h>
+
+#include "pfctl_parser.h"
+
+static struct pfctl *pf = NULL;
+static FILE *fin = NULL;
+static int debug = 0;
+static int lineno = 1;
+static int errors = 0;
+static int natmode = 0;
+
+static int proto = 0; /* this is a synthesysed attribute */
+
+int rule_consistent(struct pf_rule *);
+int yyparse(void);
+struct pf_rule_addr *new_addr(void);
+u_int32_t ipmask(u_int8_t);
+
+%}
+%union {
+ u_int32_t number;
+ int i;
+ char *string;
+ struct pf_rule_addr *addr;
+ struct {
+ struct pf_rule_addr *src, *dst;
+ } addr2;
+ struct {
+ char *string;
+ int not;
+ } iface;
+ struct {
+ u_int8_t b1;
+ u_int8_t b2;
+ u_int16_t w;
+ } b;
+ struct {
+ int a;
+ int b;
+ int t;
+ } range;
+}
+%token PASS BLOCK SCRUB RETURN IN OUT LOG LOGALL QUICK ON FROM TO FLAGS
+%token RETURNRST RETURNICMP PROTO ALL ANY ICMPTYPE CODE KEEP STATE PORT
+%token RDR NAT ARROW
+%token <string> STRING
+%token <number> NUMBER
+%token <i> PORTUNARY PORTBINARY
+%type <addr> ipportspec ipspec host portspec
+%type <addr2> fromto
+%type <iface> iface
+%type <number> address port icmptype
+%type <i> direction log quick keep proto
+%type <b> action icmpspec flags blockspec
+%type <range> dport rport
+%%
+
+ruleset: /* empty */
+ | ruleset '\n'
+ | ruleset pfrule '\n'
+ | ruleset natrule '\n'
+ | ruleset rdrrule '\n'
+ ;
+
+pfrule: action direction log quick iface proto fromto flags icmpspec keep
+ {
+ struct pf_rule r;
+
+ if (natmode)
+ errx(1, "line %d: filter rule in nat mode",
+ lineno);
+
+ memset(&r, 0, sizeof(r));
+
+ r.action = $1.b1;
+ if ($1.b2)
+ r.return_rst = 1;
+ else
+ r.return_icmp = $1.w;
+ r.direction = $2;
+ r.log = $3;
+ r.quick = $4;
+ if ($5.string)
+ memcpy(r.ifname, $5.string, sizeof(r.ifname));
+ r.proto = $6;
+ proto = 0; /* reset syntesysed attribute */
+
+ memcpy(&r.src, $7.src, sizeof(r.src));
+ free($7.src);
+ memcpy(&r.dst, $7.dst, sizeof(r.dst));
+ free($7.dst);
+
+ r.flags = $8.b1;
+ r.flagset = $8.b2;
+ r.type = $9.b1;
+ r.code = $9.b2;
+ r.keep_state = $10;
+
+ if (rule_consistent(&r) < 0)
+ yyerror("skipping rule due to errors");
+ else
+ pfctl_add_rule(pf, &r);
+ }
+ ;
+
+action: PASS { $$.b1 = PF_PASS; }
+ | BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; }
+ | SCRUB { $$.b1 = PF_SCRUB; }
+ ;
+
+blockspec: { $$.b2 = 0; $$.w = 0; }
+ | RETURNRST { $$.b2 = 1; $$.w = 0;}
+ | RETURNICMP {
+ $$.b2 = 0;
+ $$.w = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT;
+ }
+ | RETURNICMP '(' STRING ')' {
+ struct icmpcodeent *ic;
+
+ ic = geticmpcodebyname(ICMP_UNREACH, $3);
+ if (ic == NULL)
+ errx(1, "line %d: unknown icmp code %s",
+ lineno, $3);
+ $$.b2 = 0;
+ $$.w = (ic->type << 8) | ic->code;
+ }
+ ;
+
+direction: IN { $$ = PF_IN; }
+ | OUT { $$ = PF_OUT; }
+ ;
+
+log: { $$ = 0; }
+ | LOG { $$ = 1; }
+ | LOGALL { $$ = 2; }
+ ;
+
+quick: { $$ = 0; }
+ | QUICK { $$ = 1; }
+ ;
+
+iface: { $$.string = NULL; }
+ | ON STRING { $$.string = strdup($2); }
+ | ON '!' STRING { $$.string = strdup($3); $$.not = 1;}
+ ;
+
+proto: { proto = $$ = natmode ? IPPROTO_TCP : 0; }
+ | PROTO NUMBER {
+ struct protoent *p;
+
+ p = getprotobynumber($2);
+ if (p == NULL)
+ errx(1, "line %d: unknown protocol %d", lineno,
+ $2);
+ proto = $$ = p->p_proto;
+ }
+ | PROTO STRING {
+ struct protoent *p;
+
+ p = getprotobyname($2);
+ if (p == NULL)
+ errx(1, "line %d: unknown protocol %s", lineno,
+ $2);
+ proto = $$ = p->p_proto;
+ }
+ ;
+
+fromto: ALL {
+ $$.src = new_addr();
+ $$.dst = new_addr();
+ }
+ | FROM ipportspec TO ipportspec {
+ $$.src = $2;
+ $$.dst = $4;
+ }
+ ;
+
+ipportspec: ipspec { $$ = $1; }
+ | ipspec portspec {
+ $$ = $1;
+ if ($2) {
+ $$->port[0] = $2->port[0];
+ $$->port[1] = $2->port[1];
+ $$->port_op = $2->port_op;
+ free($2);
+ }
+ }
+ ;
+
+ipspec: ANY { $$ = new_addr(); }
+ | '!' host { $$ = $2; $$->not = 1; }
+ | host { $$ = $1; }
+ ;
+
+host: address {
+ $$ = new_addr();
+ $$->addr = $1;
+ $$->mask = 0xffffffff;
+ }
+ |
+ address '/' NUMBER {
+ $$ = new_addr();
+ $$->addr = $1;
+ $$->mask = ipmask($3);
+ }
+ ;
+
+address: STRING {
+ struct hostent *hp;
+
+ if (inet_pton(AF_INET, $1, &$$) != 1) {
+ if ((hp = gethostbyname($1)) == NULL)
+ errx(1, "line %d: cannot resolve %s",
+ lineno, $1);
+ memcpy(&$$, hp->h_addr, sizeof(u_int32_t));
+ }
+ }
+ | NUMBER '.' NUMBER '.' NUMBER '.' NUMBER {
+ $$ = (htonl(($1 << 24) | ($3 << 16) | ($5 << 8) | $7));
+ }
+ ;
+
+portspec: PORT PORTUNARY port {
+ $$ = new_addr();
+ $$->port_op = $2;
+ $$->port[0] = $3;
+ $$->port[1] = $3;
+ }
+ | PORT port PORTBINARY port {
+ $$ = new_addr();
+ $$->port[0] = $2;
+ $$->port_op = $3;
+ $$->port[1] = $4;
+ }
+ ;
+
+port: NUMBER { $$ = htons($1); }
+ | STRING {
+ struct servent *s = NULL;
+
+ /* use synthesysed attribute */
+ if (proto)
+ s = getservbyname($1,
+ proto == IPPROTO_TCP ? "tcp" : "udp");
+ $$ = (s == NULL) ? 0 : s->s_port;
+ }
+ ;
+
+flags: { $$.b1 = 0; $$.b2 = 0; }
+ | FLAGS STRING { $$.b1 = parse_flags($2); $$.b2 = 63; }
+ | FLAGS STRING "/" STRING {
+ $$.b1 = parse_flags($2);
+ $$.b2 = parse_flags($4);
+ }
+ ;
+
+icmpspec: { $$.b1 = 0; $$.b2 = 0; }
+ | ICMPTYPE icmptype { $$.b1 = $2; $$.b2 = 0; }
+ | ICMPTYPE icmptype CODE NUMBER {
+ $$.b1 = $2;
+ $$.b2 = $4 + 1;
+ }
+ | ICMPTYPE icmptype CODE STRING {
+ struct icmpcodeent *ic;
+
+ $$.b1 = $2;
+ ic = geticmpcodebyname($2, $4);
+ if (ic == NULL)
+ errx(1, "line %d: unknown icmp-code %s",
+ lineno, $4);
+ $$.b2 = ic->code + 1;
+ }
+ ;
+
+icmptype: STRING {
+ struct icmptypeent *te;
+
+ te = geticmptypebyname($1);
+ if (te == NULL)
+ errx(1, "line %d: unknown icmp-type %s",
+ lineno, $1);
+ $$ = te->type + 1;
+ }
+ | NUMBER { $$ = $1 + 1; }
+ ;
+
+
+keep: { $$ = 0; }
+ | KEEP STATE { $$ = 1; }
+ ;
+
+natrule: NAT iface proto FROM ipspec TO ipspec ARROW address
+ {
+ struct pf_nat nat;
+
+ if (!natmode)
+ errx(1, "line %d: nat rule in filter mode",
+ lineno);
+
+ memset(&nat, 0, sizeof(nat));
+
+ if ($2.string) {
+ memcpy(nat.ifname, $2.string,
+ sizeof(nat.ifname));
+ nat.ifnot = $2.not;
+ }
+ nat.proto = $3;
+ proto = 0; /* reset syntesysed attribute */
+
+ nat.saddr = $5->addr;
+ nat.smask = $5->mask;
+ nat.snot = $5->not;
+ free($5);
+
+ nat.daddr = $7->addr;
+ nat.dmask = $7->mask;
+ nat.dnot = $7->not;
+ free($7);
+
+ nat.raddr = $9;
+
+ pfctl_add_nat(pf, &nat);
+ }
+ ;
+
+rdrrule: RDR iface proto FROM ipspec TO ipspec dport ARROW address rport
+ {
+ struct pf_rdr rdr;
+
+ if (!natmode)
+ errx(1, "line %d: nat rule in filter mode",
+ lineno);
+
+ memset(&rdr, 0, sizeof(rdr));
+
+ if ($2.string) {
+ memcpy(rdr.ifname, $2.string,
+ sizeof(rdr.ifname));
+ rdr.ifnot = $2.not;
+ }
+ rdr.proto = $3;
+ proto = 0; /* reset syntesysed attribute */
+
+ rdr.saddr = $5->addr;
+ rdr.smask = $5->mask;
+ rdr.snot = $5->not;
+ free($5);
+
+ rdr.daddr = $7->addr;
+ rdr.dmask = $7->mask;
+ rdr.dnot = $7->not;
+ free($7);
+
+ rdr.dport = $8.a;
+ rdr.dport2 = $8.b;
+ rdr.opts |= $8.t;
+
+ rdr.raddr = $10;
+
+ rdr.rport = $11.a;
+ rdr.opts |= $11.t;
+
+ pfctl_add_rdr(pf, &rdr);
+ }
+ ;
+
+dport: PORT port {
+ $$.a = $2;
+ $$.b = $$.t = 0;
+ }
+ | PORT port ':' port {
+ $$.a = $2;
+ $$.b = $4;
+ $$.t = PF_DPORT_RANGE;
+ }
+ ;
+
+rport: PORT port {
+ $$.a = $2;
+ $$.b = $$.t = 0;
+ }
+ | PORT port ':' '*' {
+ $$.a = $2;
+ $$.b = 0;
+ $$.t = PF_RPORT_RANGE;
+ }
+ ;
+
+%%
+
+int
+yyerror(char *s)
+{
+ errors = 1;
+ warnx("%s near line %d", s, lineno);
+ return 0;
+}
+
+int
+rule_consistent(struct pf_rule *r)
+{
+ int problems = 0;
+
+ if (r->action == PF_SCRUB) {
+ if (r->quick) {
+ yyerror("quick does not apply to scrub");
+ problems++;
+ }
+ if (r->keep_state) {
+ yyerror("keep state does not apply to scrub");
+ problems++;
+ }
+ if (r->src.port_op) {
+ yyerror("src port does not apply to scrub");
+ problems++;
+ }
+ if (r->dst.port_op) {
+ yyerror("dst port does not apply to scrub");
+ problems++;
+ }
+ if (r->type || r->code) {
+ yyerror("icmp-type/code does not apply to scrub");
+ problems++;
+ }
+ }
+ if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP &&
+ (r->src.port_op || r->dst.port_op)) {
+ yyerror("ports do only apply to tcp/udp");
+ problems++;
+ }
+ if (r->proto != IPPROTO_ICMP && (r->type || r->code)) {
+ yyerror("icmp-type/code does only apply to icmp");
+ problems++;
+ }
+ return -problems;
+}
+
+int
+lookup(char *s)
+{
+ int i;
+ struct keywords {
+ char *k_name;
+ int k_val;
+ } keywords[] = {
+ { "all", ALL},
+ { "any", ANY},
+ { "block", BLOCK},
+ { "code", CODE},
+ { "flags", FLAGS},
+ { "from", FROM},
+ { "icmp-type", ICMPTYPE},
+ { "in", IN},
+ { "keep", KEEP},
+ { "log", LOG},
+ { "log-all", LOGALL},
+ { "nat", NAT},
+ { "on", ON},
+ { "out", OUT},
+ { "pass", PASS},
+ { "port", PORT},
+ { "proto", PROTO},
+ { "quick", QUICK},
+ { "rdr", RDR},
+ { "return", RETURN},
+ { "return-icmp",RETURNICMP},
+ { "return-rst", RETURNRST},
+ { "scrub", SCRUB},
+ { "state", STATE},
+ { "to", TO},
+ { NULL, 0 },
+ };
+
+ for (i = 0; keywords[i].k_name != NULL; i++) {
+ if (strcmp(s, keywords[i].k_name) == 0) {
+ if (debug > 1)
+ fprintf(stderr, "%s: %d\n", s,
+ keywords[i].k_val);
+ return keywords[i].k_val;
+ }
+ }
+ if (debug > 1)
+ fprintf(stderr, "string: %s\n", s);
+ return STRING;
+}
+
+int
+yylex(void)
+{
+ char *p, buf[8096];
+ int c, next;
+ int token;
+
+ while ((c = getc(fin)) == ' ' || c == '\t')
+ ;
+ if (c == '#')
+ while ((c = getc(fin)) != '\n' && c != EOF)
+ ;
+ if (c == '-') {
+ next = getc(fin);
+ if (next == '>')
+ return ARROW;
+ ungetc(next, fin);
+ }
+ switch (c) {
+ case '=':
+ yylval.i = PF_OP_EQ;
+ return PORTUNARY;
+ case '!':
+ next = getc(fin);
+ if (next == '=') {
+ yylval.i = PF_OP_NE;
+ return PORTUNARY;
+ }
+ ungetc(next, fin);
+ break;
+ case '<':
+ next = getc(fin);
+ if (next == '>') {
+ yylval.i = PF_OP_GL;
+ return PORTBINARY;
+ } else if (next == '=') {
+ yylval.i = PF_OP_LE;
+ } else {
+ yylval.i = PF_OP_LT;
+ ungetc(next, fin);
+ }
+ return PORTUNARY;
+ break;
+ case '>':
+ next = getc(fin);
+ if (next == '<') {
+ yylval.i = PF_OP_GL;
+ return PORTBINARY;
+ } else if (next == '=') {
+ yylval.i = PF_OP_GE;
+ } else {
+ yylval.i = PF_OP_GT;
+ ungetc(next, fin);
+ }
+ return PORTUNARY;
+ break;
+ }
+ if (isdigit(c)) {
+ yylval.number = 0;
+ do {
+ yylval.number *= 10;
+ yylval.number += c - '0';
+ } while ((c = getc(fin)) != EOF && isdigit(c));
+ ungetc(c, fin);
+ if (debug > 1)
+ fprintf(stderr, "number: %d\n", yylval.number);
+ return NUMBER;
+ }
+
+#define allowed_in_string(x) \
+ isalnum(x) || \
+ ( ispunct(x) && \
+ x != '(' && \
+ x != ')' && \
+ x != '<' && \
+ x != '>' && \
+ x != '!' && \
+ x != '=' && \
+ x != '#' )
+
+ if (isalnum(c)) {
+ p = buf;
+ do {
+ *p++ = c;
+ if (p-buf >= sizeof buf)
+ errx(1, "line %d: string too long", lineno);
+ } while ((c = getc(fin)) != EOF && (allowed_in_string(c)));
+ ungetc(c, fin);
+ *p = '\0';
+ token = lookup(buf);
+ yylval.string = strdup(buf);
+ return token;
+ }
+ if (c == '\n')
+ lineno++;
+ if (c == EOF)
+ return 0;
+ return c;
+}
+
+int
+parse_rules(FILE *input, struct pfctl *xpf)
+{
+ natmode = 0;
+ fin = input;
+ pf = xpf;
+ errors = 0;
+ yyparse();
+ return errors ? -1 : 0;
+}
+
+int
+parse_nat(FILE *input, struct pfctl *xpf)
+{
+ natmode = 1;
+ fin = input;
+ pf = xpf;
+ errors = 0;
+ yyparse();
+ return errors ? -1 : 0;
+}
+
+u_int32_t
+ipmask(u_int8_t b)
+{
+ u_int32_t m = 0;
+ int i;
+
+ for (i = 31; i > 31-b; --i)
+ m |= (1 << i);
+ return (htonl(m));
+}
+
+struct pf_rule_addr *
+new_addr(void)
+{
+ struct pf_rule_addr *ra;
+
+ ra = malloc(sizeof(struct pf_rule_addr));
+ if (ra == NULL)
+ errx(1, "new_addr: malloc failed: %s", strerror(errno));
+ memset(ra, 0, sizeof(*ra));
+ return ra;
+}