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