/*-
* Copyright (c) 2011-2020 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Martin Husemann, Christos Zoulas and Mindaugas Rasiukevicius.
*
* 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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 <err.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __NetBSD__
#include <vis.h>
#endif
#include "npfctl.h"
#define YYSTACKSIZE 4096
int yystarttoken;
const char * yyfilename;
extern int yylineno, yycolumn;
extern int yylex(int);
void
yyerror(const char *fmt, ...)
{
extern int yyleng;
extern char *yytext;
char *msg, *context = estrndup(yytext, yyleng);
bool eol = (*context == '\n');
va_list ap;
va_start(ap, fmt);
vasprintf(&msg, fmt, ap);
va_end(ap);
fprintf(stderr, "%s:%d:%d: %s", yyfilename,
yylineno - (int)eol, yycolumn, msg);
if (!eol) {
#ifdef __NetBSD__
size_t len = strlen(context);
char *dst = ecalloc(1, len * 4 + 1);
strvisx(dst, context, len, VIS_WHITE|VIS_CSTYLE);
context = dst;
#endif
fprintf(stderr, " near '%s'", context);
}
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
%}
/*
* No conflicts allowed. Keep it this way.
*/
%expect 0
%expect-rr 0
/*
* Depending on the mode of operation, set a different start symbol.
* Workaround yacc limitation by passing the start token.
*/
%start input
%token RULE_ENTRY_TOKEN MAP_ENTRY_TOKEN
%lex-param { int yystarttoken }
/*
* General tokens.
*/
%token ALG
%token ALGO
%token ALL
%token ANY
%token APPLY
%token ARROWBOTH
%token ARROWLEFT
%token ARROWRIGHT
%token BLOCK
%token CDB
%token CONST
%token CURLY_CLOSE
%token CURLY_OPEN
%token CODE
%token COLON
%token COMMA
%token DEFAULT
%token TDYNAMIC
%token TSTATIC
%token EQ
%token EXCL_MARK
%token TFILE
%token FLAGS
%token FROM
%token GROUP
%token HASH
%token ICMPTYPE
%token ID
%token IFADDRS
%token IN
%token INET4
%token INET6
%token INTERFACE
%token INVALID
%token IPHASH
%token IPSET
%token LPM
%token MAP
%token NO_PORTS
%token MINUS
%token NAME
%token NETMAP
%token NPT66
%token ON
%token OFF
%token OUT
%token PAR_CLOSE
%token PAR_OPEN
%token PASS
%token PCAP_FILTER
%token PORT
%token PROCEDURE
%token PROTO
%token FAMILY
%token FINAL
%token FORW
%token RETURN
%token RETURNICMP
%token RETURNRST
%token ROUNDROBIN
%token RULESET
%token SEPLINE
%token SET
%token SLASH
%token STATEFUL
%token STATEFUL_ALL
%token TABLE
%token TCP
%token TO
%token TREE
%token TYPE
%token <num> ICMP
%token <num> ICMP6
%token <num> HEX
%token <str> IDENTIFIER
%token <str> IPV4ADDR
%token <str> IPV6ADDR
%token <num> NUM
%token <fpnum> FPNUM
%token <str> STRING
%token <str> PARAM
%token <str> TABLE_ID
%token <str> VAR_ID
%type <str> addr some_name table_store dynamic_ifaddrs
%type <str> proc_param_val opt_apply ifname on_ifname ifref
%type <num> port opt_final number afamily opt_family
%type <num> block_or_pass rule_dir group_dir block_opts
%type <num> maybe_not opt_stateful icmp_type table_type
%type <num> map_sd map_algo map_flags map_type
%type <num> param_val
%type <var> static_ifaddrs filt_addr_element
%type <var> filt_port filt_port_list port_range icmp_type_and_code
%type <var> filt_addr addr_and_mask tcp_flags tcp_flags_and_mask
%type <var> procs proc_call proc_param_list proc_param
%type <var> element list_elems list value filt_addr_list
%type <var> opt_proto proto proto_elems
%type <addrport> mapseg
%type <filtopts> filt_opts all_or_filt_opts
%type <optproto> rawproto
%type <rulegroup> group_opts
%union {
char * str;
unsigned long num;
double fpnum;
npfvar_t * var;
addr_port_t addrport;
filt_opts_t filtopts;
opt_proto_t optproto;
rule_group_t rulegroup;
}
%%
input
: lines
| RULE_ENTRY_TOKEN rule
| MAP_ENTRY_TOKEN map
;
lines
: lines SEPLINE line
| line
;
line
: vardef
| table
| map
| group
| rproc
| alg
| set
|
;
alg
: ALG STRING
{
npfctl_build_alg($2);
}
;
param_val
: number { $$ = $1; }
| ON { $$ = true; }
| OFF { $$ = false; }
;
set
: SET PARAM param_val {
npfctl_setparam($2, $3);
}
;
/*
* A value - an element or a list of elements.
* Can be assigned to a variable or used inline.
*/
vardef
: VAR_ID EQ value
{
npfvar_add($3, $1);
}
;
value
: element
| list
;
list
: CURLY_OPEN list_elems CURLY_CLOSE
{
$$ = $2;
}
;
list_elems
: list_elems COMMA element
{
npfvar_add_elements($1, $3);
}
| element
;
element
: IDENTIFIER
{
$$ = npfvar_create_from_string(NPFVAR_IDENTIFIER, $1);
}
| STRING
{
$$ = npfvar_create_from_string(NPFVAR_STRING, $1);
}
| number MINUS number
{
$$ = npfctl_parse_port_range($1, $3);
}
| number
{
$$ = npfvar_create_element(NPFVAR_NUM, &$1, sizeof($1));
}
| VAR_ID
{
$$ = npfvar_create_from_string(NPFVAR_VAR_ID, $1);
}
| TABLE_ID { $$ = npfctl_parse_table_id($1); }
| dynamic_ifaddrs { $$ = npfctl_ifnet_table($1); }
| static_ifaddrs { $$ = $1; }
| addr_and_mask { $$ = $1; }
;
/*
* Table definition.
*/
table
: TABLE TABLE_ID TYPE table_type table_store
{
npfctl_build_table($2, $4, $5);
}
;
table_type
: IPSET { $$ = NPF_TABLE_IPSET; }
| HASH
{
warnx("warning - table type \"hash\" is deprecated and may be "
"deleted in\nthe future; please use the \"ipset\" type "
"instead.");
$$ = NPF_TABLE_IPSET;
}
| LPM { $$ = NPF_TABLE_LPM; }
| TREE
{
warnx("warning - table type \"tree\" is deprecated and may be "
"deleted in\nthe future; please use the \"lpm\" type "
"instead.");
$$ = NPF_TABLE_LPM;
}
| CONST { $$ = NPF_TABLE_CONST; }
| CDB
{
warnx("warning -- table type \"cdb\" is deprecated and may be "
"deleted in\nthe future; please use the \"const\" type "
"instead.");
$$ = NPF_TABLE_CONST;
}
;
table_store
: TFILE STRING { $$ = $2; }
| TDYNAMIC
{
warnx("warning - the \"dynamic\" keyword for tables is obsolete");
$$ = NULL;
}
| { $$ = NULL; }
;
/*
* Map definition.
*/
map_sd
: TSTATIC { $$ = NPFCTL_NAT_STATIC; }
| TDYNAMIC { $$ = NPFCTL_NAT_DYNAMIC; }
| { $$ = NPFCTL_NAT_DYNAMIC; }
;
map_algo
: ALGO NETMAP { $$ = NPF_ALGO_NETMAP; }
| ALGO IPHASH { $$ = NPF_ALGO_IPHASH; }
| ALGO ROUNDROBIN { $$ = NPF_ALGO_RR; }
| ALGO NPT66 { $$ = NPF_ALGO_NPT66; }
| { $$ = 0; }
;
map_flags
: NO_PORTS { $$ = NPF_NAT_PORTS; }
| { $$ = 0; }
;
map_type
: ARROWBOTH { $$ = NPF_NATIN | NPF_NATOUT; }
| ARROWLEFT { $$ = NPF_NATIN; }
| ARROWRIGHT { $$ = NPF_NATOUT; }
;
mapseg
: filt_addr filt_port
{
$$.ap_netaddr = $1;
$$.ap_portrange = $2;
}
;
map
: MAP ifref map_sd map_algo map_flags mapseg map_type mapseg
PASS opt_family opt_proto all_or_filt_opts
{
npfctl_build_natseg($3, $7, $5, $2, &$6, &$8, $11, &$12, $4);
}
| MAP ifref map_sd map_algo map_flags mapseg map_type mapseg
{
npfctl_build_natseg($3, $7, $5, $2, &$6, &$8, NULL, NULL, $4);
}
| MAP ifref map_sd map_algo map_flags proto mapseg map_type mapseg
{
npfctl_build_natseg($3, $8, $5, $2, &$7, &$9, $6, NULL, $4);
}
| MAP RULESET group_opts
{
npfctl_build_maprset($3.rg_name, $3.rg_attr, $3.rg_ifname);
}
;
/*
* Rule procedure definition and its parameters.
*/
rproc
: PROCEDURE STRING CURLY_OPEN procs CURLY_CLOSE
{
npfctl_build_rproc($2, $4);
}
;
procs
: procs SEPLINE proc_call
{
$$ = npfvar_add_elements($1, $3);
}
| proc_call { $$ = $1; }
;
proc_call
: IDENTIFIER COLON proc_param_list
{
proc_call_t pc;
pc.pc_name = estrdup($1);
pc.pc_opts = $3;
$$ = npfvar_create_element(NPFVAR_PROC, &pc, sizeof(pc));
}
| { $$ = NULL; }
;
proc_param_list
: proc_param_list COMMA proc_param
{
$$ = npfvar_add_elements($1, $3);
}
| proc_param { $$ = $1; }
| { $$ = NULL; }
;
proc_param
: some_name proc_param_val
{
proc_param_t pp;
pp.pp_param = estrdup($1);
pp.pp_value = $2 ? estrdup($2) : NULL;
$$ = npfvar_create_element(NPFVAR_PROC_PARAM, &pp, sizeof(pp));
}
;
proc_param_val
: some_name { $$ = $1; }
| number { (void)asprintf(&$$, "%ld", $1); }
| FPNUM { (void)asprintf(&$$, "%lf", $1); }
| { $$ = NULL; }
;
/*
* Group and dynamic ruleset definition.
*/
group
: GROUP group_opts
{
/* Build a group. Increase the nesting level. */
npfctl_build_group($2.rg_name, $2.rg_attr,
$2.rg_ifname, $2.rg_default);
}
ruleset_block
{
/* Decrease the nesting level. */
npfctl_build_group_end();
}
;
ruleset
: RULESET group_opts
{
/* Ruleset is a dynamic group. */
npfctl_build_group($2.rg_name, $2.rg_attr | NPF_RULE_DYNAMIC,
$2.rg_ifname, $2.rg_default);
npfctl_build_group_end();
}
;
group_dir
: FORW { $$ = NPF_RULE_FORW; }
| rule_dir
;
group_opts
: DEFAULT
{
memset(&$$, 0, sizeof(rule_group_t));
$$.rg_default = true;
}
| STRING group_dir on_ifname
{
memset(&$$, 0, sizeof(rule_group_t));
$$.rg_name = $1;
$$.rg_attr = $2;
$$.rg_ifname = $3;
}
;
ruleset_block
: CURLY_OPEN ruleset_def CURLY_CLOSE
;
ruleset_def
: ruleset_def SEPLINE rule_group
| rule_group
;
rule_group
: rule
| group
| ruleset
|
;
/*
* Rule and misc.
*/
rule
: block_or_pass opt_stateful rule_dir opt_final on_ifname
opt_family opt_proto all_or_filt_opts opt_apply
{
npfctl_build_rule($1 | $2 | $3 | $4, $5,
$6, $7, &$8, NULL, $9);
}
| block_or_pass opt_stateful rule_dir opt_final on_ifname
PCAP_FILTER STRING opt_apply
{
npfctl_build_rule($1 | $2 | $3 | $4, $5,
AF_UNSPEC, NULL, NULL, $7, $8);
}
;
block_or_pass
: BLOCK block_opts { $$ = $2; }
| PASS { $$ = NPF_RULE_PASS; }
;
rule_dir
: IN { $$ = NPF_RULE_IN; }
| OUT { $$ = NPF_RULE_OUT; }
| { $$ = NPF_RULE_IN | NPF_RULE_OUT; }
;
opt_final
: FINAL { $$ = NPF_RULE_FINAL; }
| { $$ = 0; }
;
on_ifname
: ON ifref { $$ = $2; }
| { $$ = NULL; }
;
afamily
: INET4 { $$ = AF_INET; }
| INET6 { $$ = AF_INET6; }
;
maybe_not
: EXCL_MARK { $$ = true; }
| { $$ = false; }
;
opt_family
: FAMILY afamily { $$ = $2; }
| { $$ = AF_UNSPEC; }
;
rawproto
: TCP tcp_flags_and_mask
{
$$.op_proto = IPPROTO_TCP;
$$.op_opts = $2;
}
| ICMP icmp_type_and_code
{
$$.op_proto = IPPROTO_ICMP;
$$.op_opts = $2;
}
| ICMP6 icmp_type_and_code
{
$$.op_proto = IPPROTO_ICMPV6;
$$.op_opts = $2;
}
| some_name
{
$$.op_proto = npfctl_protono($1);
$$.op_opts = NULL;
}
| number
{
$$.op_proto = $1;
$$.op_opts = NULL;
}
;
proto_elems
: proto_elems COMMA rawproto
{
npfvar_t *pvar = npfvar_create_element(
NPFVAR_PROTO, &$3, sizeof($3));
$$ = npfvar_add_elements($1, pvar);
}
| rawproto
{
$$ = npfvar_create_element(NPFVAR_PROTO, &$1, sizeof($1));
}
;
proto
: PROTO rawproto
{
$$ = npfvar_create_element(NPFVAR_PROTO, &$2, sizeof($2));
}
| PROTO CURLY_OPEN proto_elems CURLY_CLOSE
{
$$ = $3;
}
;
opt_proto
: proto { $$ = $1; }
| { $$ = NULL; }
;
all_or_filt_opts
: ALL
{
$$.fo_finvert = false;
$$.fo_from.ap_netaddr = NULL;
$$.fo_from.ap_portrange = NULL;
$$.fo_tinvert = false;
$$.fo_to.ap_netaddr = NULL;
$$.fo_to.ap_portrange = NULL;
}
| filt_opts { $$ = $1; }
;
opt_stateful
: STATEFUL { $$ = NPF_RULE_STATEFUL; }
| STATEFUL_ALL { $$ = NPF_RULE_STATEFUL | NPF_RULE_GSTATEFUL; }
| { $$ = 0; }
;
opt_apply
: APPLY STRING { $$ = $2; }
| { $$ = NULL; }
;
block_opts
: RETURNRST { $$ = NPF_RULE_RETRST; }
| RETURNICMP { $$ = NPF_RULE_RETICMP; }
| RETURN { $$ = NPF_RULE_RETRST | NPF_RULE_RETICMP; }
| { $$ = 0; }
;
filt_opts
: FROM maybe_not filt_addr filt_port TO maybe_not filt_addr filt_port
{
$$.fo_finvert = $2;
$$.fo_from.ap_netaddr = $3;
$$.fo_from.ap_portrange = $4;
$$.fo_tinvert = $6;
$$.fo_to.ap_netaddr = $7;
$$.fo_to.ap_portrange = $8;
}
| FROM maybe_not filt_addr filt_port
{
$$.fo_finvert = $2;
$$.fo_from.ap_netaddr = $3;
$$.fo_from.ap_portrange = $4;
$$.fo_tinvert = false;
$$.fo_to.ap_netaddr = NULL;
$$.fo_to.ap_portrange = NULL;
}
| TO maybe_not filt_addr filt_port
{
$$.fo_finvert = false;
$$.fo_from.ap_netaddr = NULL;
$$.fo_from.ap_portrange = NULL;
$$.fo_tinvert = $2;
$$.fo_to.ap_netaddr = $3;
$$.fo_to.ap_portrange = $4;
}
;
filt_addr_list
: filt_addr_list COMMA filt_addr_element
{
npfvar_add_elements($1, $3);
}
| filt_addr_element
;
filt_addr
: CURLY_OPEN filt_addr_list CURLY_CLOSE
{
$$ = $2;
}
| filt_addr_element { $$ = $1; }
| ANY { $$ = NULL; }
;
addr_and_mask
: addr SLASH number
{
$$ = npfctl_parse_fam_addr_mask($1, NULL, &$3);
}
| addr SLASH addr
{
$$ = npfctl_parse_fam_addr_mask($1, $3, NULL);
}
| addr
{
$$ = npfctl_parse_fam_addr_mask($1, NULL, NULL);
}
;
filt_addr_element
: addr_and_mask { assert($1 != NULL); $$ = $1; }
| static_ifaddrs
{
if (npfvar_get_count($1) != 1)
yyerror("multiple interfaces are not supported");
ifnet_addr_t *ifna = npfvar_get_data($1, NPFVAR_INTERFACE, 0);
$$ = ifna->ifna_addrs;
}
| dynamic_ifaddrs { $$ = npfctl_ifnet_table($1); }
| TABLE_ID { $$ = npfctl_parse_table_id($1); }
| VAR_ID
{
npfvar_t *vp = npfvar_lookup($1);
int type = npfvar_get_type(vp, 0);
ifnet_addr_t *ifna;
again:
switch (type) {
case NPFVAR_IDENTIFIER:
case NPFVAR_STRING:
vp = npfctl_parse_ifnet(npfvar_expand_string(vp),
AF_UNSPEC);
type = npfvar_get_type(vp, 0);
goto again;
case NPFVAR_FAM:
case NPFVAR_TABLE:
$$ = vp;
break;
case NPFVAR_INTERFACE:
$$ = NULL;
for (u_int i = 0; i < npfvar_get_count(vp); i++) {
ifna = npfvar_get_data(vp, type, i);
$$ = npfvar_add_elements($$, ifna->ifna_addrs);
}
break;
case -1:
yyerror("undefined variable '%s'", $1);
break;
default:
yyerror("wrong variable '%s' type '%s' for address "
"or interface", $1, npfvar_type(type));
break;
}
}
;
addr
: IPV4ADDR { $$ = $1; }
| IPV6ADDR { $$ = $1; }
;
filt_port
: PORT CURLY_OPEN filt_port_list CURLY_CLOSE
{
$$ = npfctl_parse_port_range_variable(NULL, $3);
}
| PORT port_range { $$ = $2; }
| { $$ = NULL; }
;
filt_port_list
: filt_port_list COMMA port_range
{
npfvar_add_elements($1, $3);
}
| port_range
;
port_range
: port /* just port */
{
$$ = npfctl_parse_port_range($1, $1);
}
| port MINUS port /* port from-to */
{
$$ = npfctl_parse_port_range($1, $3);
}
| VAR_ID
{
npfvar_t *vp = npfvar_lookup($1);
$$ = npfctl_parse_port_range_variable($1, vp);
}
;
port
: number { $$ = $1; }
| IDENTIFIER { $$ = npfctl_portno($1); }
| STRING { $$ = npfctl_portno($1); }
;
icmp_type_and_code
: ICMPTYPE icmp_type
{
$$ = npfctl_parse_icmp($<num>0, $2, -1);
}
| ICMPTYPE icmp_type CODE number
{
$$ = npfctl_parse_icmp($<num>0, $2, $4);
}
| ICMPTYPE icmp_type CODE IDENTIFIER
{
$$ = npfctl_parse_icmp($<num>0, $2,
npfctl_icmpcode($<num>0, $2, $4));
}
| ICMPTYPE icmp_type CODE VAR_ID
{
char *s = npfvar_expand_string(npfvar_lookup($4));
$$ = npfctl_parse_icmp($<num>0, $2,
npfctl_icmpcode($<num>0, $2, s));
}
| { $$ = NULL; }
;
tcp_flags_and_mask
: FLAGS tcp_flags SLASH tcp_flags
{
npfvar_add_elements($2, $4);
$$ = $2;
}
| FLAGS tcp_flags
{
if (npfvar_get_count($2) != 1)
yyerror("multiple tcpflags are not supported");
char *s = npfvar_get_data($2, NPFVAR_TCPFLAG, 0);
npfvar_add_elements($2, npfctl_parse_tcpflag(s));
$$ = $2;
}
| { $$ = NULL; }
;
tcp_flags
: IDENTIFIER { $$ = npfctl_parse_tcpflag($1); }
;
icmp_type
: number { $$ = $1; }
| IDENTIFIER { $$ = npfctl_icmptype($<num>-1, $1); }
| VAR_ID
{
char *s = npfvar_expand_string(npfvar_lookup($1));
$$ = npfctl_icmptype($<num>-1, s);
}
;
ifname
: some_name
{
npfctl_note_interface($1);
$$ = $1;
}
| VAR_ID
{
npfvar_t *vp = npfvar_lookup($1);
const int type = npfvar_get_type(vp, 0);
ifnet_addr_t *ifna;
const char *name;
unsigned *tid;
bool ifaddr;
switch (type) {
case NPFVAR_STRING:
case NPFVAR_IDENTIFIER:
$$ = npfvar_expand_string(vp);
break;
case NPFVAR_INTERFACE:
if (npfvar_get_count(vp) != 1)
yyerror(
"multiple interfaces are not supported");
ifna = npfvar_get_data(vp, type, 0);
$$ = ifna->ifna_name;
break;
case NPFVAR_TABLE:
tid = npfvar_get_data(vp, type, 0);
name = npfctl_table_getname(npfctl_config_ref(),
*tid, &ifaddr);
if (!ifaddr) {
yyerror("variable '%s' references a table "
"%s instead of an interface", $1, name);
}
$$ = estrdup(name);
break;
case -1:
yyerror("undefined variable '%s' for interface", $1);
break;
default:
yyerror("wrong variable '%s' type '%s' for interface",
$1, npfvar_type(type));
break;
}
npfctl_note_interface($$);
}
;
static_ifaddrs
: afamily PAR_OPEN ifname PAR_CLOSE
{
$$ = npfctl_parse_ifnet($3, $1);
}
;
dynamic_ifaddrs
: IFADDRS PAR_OPEN ifname PAR_CLOSE
{
$$ = $3;
}
;
ifref
: ifname
| dynamic_ifaddrs
| static_ifaddrs
{
ifnet_addr_t *ifna;
if (npfvar_get_count($1) != 1) {
yyerror("multiple interfaces are not supported");
}
ifna = npfvar_get_data($1, NPFVAR_INTERFACE, 0);
npfctl_note_interface(ifna->ifna_name);
$$ = ifna->ifna_name;
}
;
number
: HEX { $$ = $1; }
| NUM { $$ = $1; }
;
some_name
: IDENTIFIER { $$ = $1; }
| STRING { $$ = $1; }
;
%%