2014-03-14 15:29:44 +04:00
|
|
|
/* $NetBSD: npf_parse.y,v 1.34 2014/03/14 11:29:45 rmind Exp $ */
|
2012-01-09 01:34:21 +04:00
|
|
|
|
|
|
|
/*-
|
2014-02-13 07:34:40 +04:00
|
|
|
* Copyright (c) 2011-2014 The NetBSD Foundation, Inc.
|
2012-01-09 01:34:21 +04:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
2013-09-20 07:03:52 +04:00
|
|
|
* by Martin Husemann, Christos Zoulas and Mindaugas Rasiukevicius.
|
2012-01-09 01:34:21 +04:00
|
|
|
*
|
|
|
|
* 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 <stdio.h>
|
|
|
|
#include <err.h>
|
|
|
|
#include <vis.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
|
|
|
|
#include "npfctl.h"
|
|
|
|
|
2012-08-12 07:35:13 +04:00
|
|
|
#define YYSTACKSIZE 4096
|
|
|
|
|
2013-02-09 07:35:31 +04:00
|
|
|
int yyparsetarget;
|
2012-01-09 01:34:21 +04:00
|
|
|
const char * yyfilename;
|
|
|
|
|
|
|
|
extern int yylineno, yycolumn;
|
|
|
|
extern int yylex(void);
|
|
|
|
|
|
|
|
void
|
|
|
|
yyerror(const char *fmt, ...)
|
|
|
|
{
|
|
|
|
extern int yyleng;
|
|
|
|
extern char *yytext;
|
|
|
|
|
2012-11-06 03:47:12 +04:00
|
|
|
char *msg, *context = estrndup(yytext, yyleng);
|
2012-10-03 03:38:52 +04:00
|
|
|
bool eol = (*context == '\n');
|
2012-01-09 01:34:21 +04:00
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
vasprintf(&msg, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
2012-10-03 03:38:52 +04:00
|
|
|
fprintf(stderr, "%s:%d:%d: %s", yyfilename,
|
|
|
|
yylineno - (int)eol, yycolumn, msg);
|
|
|
|
if (!eol) {
|
|
|
|
size_t len = strlen(context);
|
2012-11-16 02:20:27 +04:00
|
|
|
char *dst = ecalloc(1, len * 4 + 1);
|
2012-10-03 03:38:52 +04:00
|
|
|
|
|
|
|
strvisx(dst, context, len, VIS_WHITE|VIS_CSTYLE);
|
|
|
|
fprintf(stderr, " near '%s'", dst);
|
|
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
2012-01-09 01:34:21 +04:00
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2013-02-09 07:35:31 +04:00
|
|
|
#define CHECK_PARSER_FILE \
|
|
|
|
if (yyparsetarget != NPFCTL_PARSE_FILE) \
|
|
|
|
yyerror("rule must be in the group");
|
|
|
|
|
|
|
|
#define CHECK_PARSER_STRING \
|
|
|
|
if (yyparsetarget != NPFCTL_PARSE_STRING) \
|
|
|
|
yyerror("invalid rule syntax");
|
|
|
|
|
2012-01-09 01:34:21 +04:00
|
|
|
%}
|
|
|
|
|
2013-03-20 04:29:46 +04:00
|
|
|
%token ALG
|
2014-02-13 07:34:40 +04:00
|
|
|
%token ALGO
|
2012-01-09 01:34:21 +04:00
|
|
|
%token ALL
|
|
|
|
%token ANY
|
|
|
|
%token APPLY
|
2012-06-16 03:24:08 +04:00
|
|
|
%token ARROWBOTH
|
|
|
|
%token ARROWLEFT
|
|
|
|
%token ARROWRIGHT
|
2012-01-09 01:34:21 +04:00
|
|
|
%token BLOCK
|
2014-02-06 06:51:28 +04:00
|
|
|
%token CDB
|
2012-01-09 01:34:21 +04:00
|
|
|
%token CURLY_CLOSE
|
|
|
|
%token CURLY_OPEN
|
|
|
|
%token CODE
|
|
|
|
%token COLON
|
|
|
|
%token COMMA
|
|
|
|
%token DEFAULT
|
|
|
|
%token TDYNAMIC
|
2012-06-16 03:24:08 +04:00
|
|
|
%token TSTATIC
|
2012-01-09 01:34:21 +04:00
|
|
|
%token EQ
|
|
|
|
%token TFILE
|
|
|
|
%token FLAGS
|
|
|
|
%token FROM
|
|
|
|
%token GROUP
|
|
|
|
%token HASH
|
|
|
|
%token ICMPTYPE
|
|
|
|
%token ID
|
|
|
|
%token IN
|
2013-11-19 04:28:41 +04:00
|
|
|
%token INET4
|
2012-01-09 01:34:21 +04:00
|
|
|
%token INET6
|
|
|
|
%token INTERFACE
|
2012-06-16 03:24:08 +04:00
|
|
|
%token MAP
|
2012-01-09 01:34:21 +04:00
|
|
|
%token MINUS
|
|
|
|
%token NAME
|
2014-02-13 07:34:40 +04:00
|
|
|
%token NPT66
|
2012-01-09 01:34:21 +04:00
|
|
|
%token ON
|
|
|
|
%token OUT
|
|
|
|
%token PAR_CLOSE
|
|
|
|
%token PAR_OPEN
|
|
|
|
%token PASS
|
2013-09-20 07:03:52 +04:00
|
|
|
%token PCAP_FILTER
|
2012-01-09 01:34:21 +04:00
|
|
|
%token PORT
|
|
|
|
%token PROCEDURE
|
|
|
|
%token PROTO
|
|
|
|
%token FAMILY
|
2012-05-31 01:30:07 +04:00
|
|
|
%token FINAL
|
2013-02-09 07:35:31 +04:00
|
|
|
%token FORW
|
2012-01-09 01:34:21 +04:00
|
|
|
%token RETURN
|
|
|
|
%token RETURNICMP
|
|
|
|
%token RETURNRST
|
2013-03-18 06:17:49 +04:00
|
|
|
%token RULESET
|
2012-01-09 01:34:21 +04:00
|
|
|
%token SEPLINE
|
|
|
|
%token SLASH
|
2012-05-31 01:30:07 +04:00
|
|
|
%token STATEFUL
|
2014-03-14 15:29:44 +04:00
|
|
|
%token STATEFUL_ENDS
|
2012-01-09 01:34:21 +04:00
|
|
|
%token TABLE
|
|
|
|
%token TCP
|
|
|
|
%token TO
|
|
|
|
%token TREE
|
|
|
|
%token TYPE
|
2012-07-20 01:52:29 +04:00
|
|
|
%token <num> ICMP
|
|
|
|
%token <num> ICMP6
|
2012-01-09 01:34:21 +04:00
|
|
|
|
|
|
|
%token <num> HEX
|
|
|
|
%token <str> IDENTIFIER
|
|
|
|
%token <str> IPV4ADDR
|
|
|
|
%token <str> IPV6ADDR
|
|
|
|
%token <num> NUM
|
2012-09-16 17:47:41 +04:00
|
|
|
%token <fpnum> FPNUM
|
2012-01-09 01:34:21 +04:00
|
|
|
%token <str> STRING
|
|
|
|
%token <str> TABLE_ID
|
|
|
|
%token <str> VAR_ID
|
|
|
|
|
2013-11-19 04:28:41 +04:00
|
|
|
%type <str> addr, some_name, table_store
|
|
|
|
%type <str> proc_param_val, opt_apply, ifname, on_ifname, ifref
|
2013-11-08 04:38:26 +04:00
|
|
|
%type <num> port, opt_final, number, afamily, opt_family
|
2013-09-20 07:03:52 +04:00
|
|
|
%type <num> block_or_pass, rule_dir, group_dir, block_opts
|
2014-02-13 07:34:40 +04:00
|
|
|
%type <num> opt_stateful, icmp_type, table_type
|
|
|
|
%type <num> map_sd, map_algo, map_type
|
2013-11-19 04:28:41 +04:00
|
|
|
%type <var> ifaddrs, addr_or_ifaddr, port_range, icmp_type_and_code
|
2012-01-09 01:34:21 +04:00
|
|
|
%type <var> filt_addr, addr_and_mask, tcp_flags, tcp_flags_and_mask
|
2012-09-16 17:47:41 +04:00
|
|
|
%type <var> procs, proc_call, proc_param_list, proc_param
|
2013-11-19 04:28:41 +04:00
|
|
|
%type <var> element, list_elems, list, value
|
2012-06-16 03:24:08 +04:00
|
|
|
%type <addrport> mapseg
|
2012-01-09 01:34:21 +04:00
|
|
|
%type <filtopts> filt_opts, all_or_filt_opts
|
|
|
|
%type <optproto> opt_proto
|
2013-09-20 07:03:52 +04:00
|
|
|
%type <rulegroup> group_opts
|
2012-01-09 01:34:21 +04:00
|
|
|
|
|
|
|
%union {
|
|
|
|
char * str;
|
|
|
|
unsigned long num;
|
2012-09-16 17:47:41 +04:00
|
|
|
double fpnum;
|
2012-11-27 00:34:28 +04:00
|
|
|
npfvar_t * var;
|
2012-06-16 03:24:08 +04:00
|
|
|
addr_port_t addrport;
|
2012-01-09 01:34:21 +04:00
|
|
|
filt_opts_t filtopts;
|
|
|
|
opt_proto_t optproto;
|
|
|
|
rule_group_t rulegroup;
|
|
|
|
}
|
|
|
|
|
|
|
|
%%
|
|
|
|
|
|
|
|
input
|
2013-02-09 07:35:31 +04:00
|
|
|
: { CHECK_PARSER_FILE } lines
|
|
|
|
| { CHECK_PARSER_STRING } rule
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
|
|
|
lines
|
|
|
|
: line SEPLINE lines
|
|
|
|
| line
|
|
|
|
;
|
|
|
|
|
|
|
|
line
|
2013-11-19 01:39:03 +04:00
|
|
|
: vardef
|
2012-01-09 01:34:21 +04:00
|
|
|
| table
|
2012-06-16 03:24:08 +04:00
|
|
|
| map
|
2012-01-09 01:34:21 +04:00
|
|
|
| group
|
|
|
|
| rproc
|
2013-03-20 04:29:46 +04:00
|
|
|
| alg
|
2012-01-09 01:34:21 +04:00
|
|
|
|
|
|
|
|
;
|
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
alg
|
|
|
|
: ALG STRING
|
|
|
|
{
|
|
|
|
npfctl_build_alg($2);
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A value - an element or a list of elements.
|
|
|
|
* Can be assigned to a variable or used inline.
|
|
|
|
*/
|
|
|
|
|
|
|
|
vardef
|
2013-11-19 04:28:41 +04:00
|
|
|
: VAR_ID EQ value
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2013-11-19 04:28:41 +04:00
|
|
|
npfvar_add($3, $1);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
value
|
|
|
|
: element
|
|
|
|
| list
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
list
|
2012-01-09 01:34:21 +04:00
|
|
|
: CURLY_OPEN list_elems CURLY_CLOSE
|
2013-11-19 04:28:41 +04:00
|
|
|
{
|
|
|
|
$$ = $2;
|
|
|
|
}
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
|
|
|
list_elems
|
2013-11-19 01:39:03 +04:00
|
|
|
: element COMMA list_elems
|
2013-11-19 04:28:41 +04:00
|
|
|
{
|
|
|
|
npfvar_add_elements($1, $3);
|
|
|
|
}
|
2013-11-19 01:39:03 +04:00
|
|
|
| element
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
element
|
2012-01-09 01:34:21 +04:00
|
|
|
: IDENTIFIER
|
|
|
|
{
|
2013-11-19 04:28:41 +04:00
|
|
|
$$ = npfvar_create_from_string(NPFVAR_IDENTIFIER, $1);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
| STRING
|
|
|
|
{
|
2013-11-19 04:28:41 +04:00
|
|
|
$$ = npfvar_create_from_string(NPFVAR_STRING, $1);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
2013-05-09 23:12:03 +04:00
|
|
|
| number MINUS number
|
2012-02-27 01:50:05 +04:00
|
|
|
{
|
2013-11-19 04:28:41 +04:00
|
|
|
$$ = npfctl_parse_port_range($1, $3);
|
2012-02-27 01:50:05 +04:00
|
|
|
}
|
2013-05-09 23:12:03 +04:00
|
|
|
| number
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2013-11-19 04:28:41 +04:00
|
|
|
$$ = npfvar_create_element(NPFVAR_NUM, &$1, sizeof($1));
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
| VAR_ID
|
|
|
|
{
|
2013-11-19 04:28:41 +04:00
|
|
|
$$ = npfvar_create_from_string(NPFVAR_VAR_ID, $1);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
2013-11-19 04:28:41 +04:00
|
|
|
| TABLE_ID { $$ = npfctl_parse_table_id($1); }
|
|
|
|
| ifaddrs { $$ = $1; }
|
|
|
|
| addr_and_mask { $$ = $1; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
/*
|
|
|
|
* Table definition.
|
|
|
|
*/
|
|
|
|
|
2012-01-09 01:34:21 +04:00
|
|
|
table
|
|
|
|
: TABLE TABLE_ID TYPE table_type table_store
|
|
|
|
{
|
|
|
|
npfctl_build_table($2, $4, $5);
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
table_type
|
|
|
|
: HASH { $$ = NPF_TABLE_HASH; }
|
2012-01-15 04:49:47 +04:00
|
|
|
| TREE { $$ = NPF_TABLE_TREE; }
|
2014-02-06 06:51:28 +04:00
|
|
|
| CDB { $$ = NPF_TABLE_CDB; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
|
|
|
table_store
|
|
|
|
: TDYNAMIC { $$ = NULL; }
|
|
|
|
| TFILE STRING { $$ = $2; }
|
|
|
|
;
|
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
/*
|
|
|
|
* Map definition.
|
|
|
|
*/
|
|
|
|
|
2012-06-16 03:24:08 +04:00
|
|
|
map_sd
|
|
|
|
: TSTATIC { $$ = NPFCTL_NAT_STATIC; }
|
|
|
|
| TDYNAMIC { $$ = NPFCTL_NAT_DYNAMIC; }
|
|
|
|
| { $$ = NPFCTL_NAT_DYNAMIC; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2014-02-13 07:34:40 +04:00
|
|
|
map_algo
|
|
|
|
: ALGO NPT66 { $$ = NPF_ALGO_NPT66; }
|
|
|
|
| { $$ = 0; }
|
|
|
|
;
|
|
|
|
|
2012-06-16 03:24:08 +04:00
|
|
|
map_type
|
|
|
|
: ARROWBOTH { $$ = NPF_NATIN | NPF_NATOUT; }
|
|
|
|
| ARROWLEFT { $$ = NPF_NATIN; }
|
|
|
|
| ARROWRIGHT { $$ = NPF_NATOUT; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2012-06-16 03:24:08 +04:00
|
|
|
mapseg
|
2013-11-19 04:28:41 +04:00
|
|
|
: addr_or_ifaddr port_range
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2012-06-16 03:24:08 +04:00
|
|
|
$$.ap_netaddr = $1;
|
|
|
|
$$.ap_portrange = $2;
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
2012-06-16 03:24:08 +04:00
|
|
|
map
|
2014-02-13 07:34:40 +04:00
|
|
|
: MAP ifref map_sd map_algo mapseg map_type mapseg PASS filt_opts
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2014-02-13 07:34:40 +04:00
|
|
|
npfctl_build_natseg($3, $6, $2, &$5, &$7, &$9, $4);
|
2012-06-16 03:24:08 +04:00
|
|
|
}
|
2014-02-13 07:34:40 +04:00
|
|
|
| MAP ifref map_sd map_algo mapseg map_type mapseg
|
2012-06-16 03:24:08 +04:00
|
|
|
{
|
2014-02-13 07:34:40 +04:00
|
|
|
npfctl_build_natseg($3, $6, $2, &$5, &$7, NULL, $4);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
2013-09-20 07:03:52 +04:00
|
|
|
| MAP RULESET group_opts
|
2013-03-18 06:17:49 +04:00
|
|
|
{
|
2013-11-08 04:38:26 +04:00
|
|
|
npfctl_build_maprset($3.rg_name, $3.rg_attr, $3.rg_ifname);
|
2013-03-18 06:17:49 +04:00
|
|
|
}
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
/*
|
|
|
|
* Rule procedure definition and its parameters.
|
|
|
|
*/
|
|
|
|
|
2012-01-09 01:34:21 +04:00
|
|
|
rproc
|
|
|
|
: PROCEDURE STRING CURLY_OPEN procs CURLY_CLOSE
|
|
|
|
{
|
|
|
|
npfctl_build_rproc($2, $4);
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
procs
|
2012-09-16 17:47:41 +04:00
|
|
|
: proc_call SEPLINE procs
|
|
|
|
{
|
|
|
|
$$ = npfvar_add_elements($1, $3);
|
|
|
|
}
|
|
|
|
| proc_call { $$ = $1; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2012-09-16 17:47:41 +04:00
|
|
|
proc_call
|
|
|
|
: IDENTIFIER COLON proc_param_list
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2012-09-16 17:47:41 +04:00
|
|
|
proc_call_t pc;
|
2012-01-09 01:34:21 +04:00
|
|
|
|
2012-11-06 03:47:12 +04:00
|
|
|
pc.pc_name = estrdup($1);
|
2012-09-16 17:47:41 +04:00
|
|
|
pc.pc_opts = $3;
|
2013-11-19 04:28:41 +04:00
|
|
|
|
|
|
|
$$ = npfvar_create_element(NPFVAR_PROC, &pc, sizeof(pc));
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
2013-11-19 04:28:41 +04:00
|
|
|
| { $$ = NULL; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2012-09-16 17:47:41 +04:00
|
|
|
proc_param_list
|
|
|
|
: proc_param COMMA proc_param_list
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
|
|
|
$$ = npfvar_add_elements($1, $3);
|
|
|
|
}
|
2012-09-16 17:47:41 +04:00
|
|
|
| proc_param { $$ = $1; }
|
2012-01-09 01:34:21 +04:00
|
|
|
| { $$ = NULL; }
|
|
|
|
;
|
|
|
|
|
2012-09-16 17:47:41 +04:00
|
|
|
proc_param
|
|
|
|
: some_name proc_param_val
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2012-09-16 17:47:41 +04:00
|
|
|
proc_param_t pp;
|
2012-01-09 01:34:21 +04:00
|
|
|
|
2012-11-06 03:47:12 +04:00
|
|
|
pp.pp_param = estrdup($1);
|
|
|
|
pp.pp_value = $2 ? estrdup($2) : NULL;
|
2013-11-19 04:28:41 +04:00
|
|
|
|
|
|
|
$$ = npfvar_create_element(NPFVAR_PROC_PARAM, &pp, sizeof(pp));
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
2012-09-16 17:47:41 +04:00
|
|
|
proc_param_val
|
|
|
|
: some_name { $$ = $1; }
|
2013-05-09 23:12:03 +04:00
|
|
|
| number { (void)asprintf(&$$, "%ld", $1); }
|
2012-09-16 17:47:41 +04:00
|
|
|
| FPNUM { (void)asprintf(&$$, "%lf", $1); }
|
|
|
|
| { $$ = NULL; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
/*
|
|
|
|
* Group and dynamic ruleset definition.
|
|
|
|
*/
|
|
|
|
|
2012-01-09 01:34:21 +04:00
|
|
|
group
|
2013-09-20 07:03:52 +04:00
|
|
|
: GROUP group_opts
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2013-11-19 04:28:41 +04:00
|
|
|
/* Build a group. Increase the nesting level. */
|
2013-09-20 07:03:52 +04:00
|
|
|
npfctl_build_group($2.rg_name, $2.rg_attr,
|
2013-11-08 04:38:26 +04:00
|
|
|
$2.rg_ifname, $2.rg_default);
|
2013-02-09 07:35:31 +04:00
|
|
|
}
|
|
|
|
ruleset_block
|
|
|
|
{
|
|
|
|
/* Decrease the nesting level. */
|
|
|
|
npfctl_build_group_end();
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
2013-03-18 06:17:49 +04:00
|
|
|
ruleset
|
2013-09-20 07:03:52 +04:00
|
|
|
: RULESET group_opts
|
2013-03-18 06:17:49 +04:00
|
|
|
{
|
|
|
|
/* Ruleset is a dynamic group. */
|
2013-09-20 07:03:52 +04:00
|
|
|
npfctl_build_group($2.rg_name, $2.rg_attr | NPF_RULE_DYNAMIC,
|
2013-11-08 04:38:26 +04:00
|
|
|
$2.rg_ifname, $2.rg_default);
|
2013-03-18 06:17:49 +04:00
|
|
|
npfctl_build_group_end();
|
|
|
|
}
|
2013-09-20 07:03:52 +04:00
|
|
|
;
|
2013-03-18 06:17:49 +04:00
|
|
|
|
2013-09-20 07:03:52 +04:00
|
|
|
group_dir
|
|
|
|
: FORW { $$ = NPF_RULE_FORW; }
|
|
|
|
| rule_dir
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-09-20 07:03:52 +04:00
|
|
|
group_opts
|
2012-01-09 01:34:21 +04:00
|
|
|
: DEFAULT
|
|
|
|
{
|
2013-02-09 07:35:31 +04:00
|
|
|
memset(&$$, 0, sizeof(rule_group_t));
|
|
|
|
$$.rg_default = true;
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
2013-11-08 04:38:26 +04:00
|
|
|
| STRING group_dir on_ifname
|
2013-02-09 07:35:31 +04:00
|
|
|
{
|
|
|
|
memset(&$$, 0, sizeof(rule_group_t));
|
2013-09-20 07:03:52 +04:00
|
|
|
$$.rg_name = $1;
|
|
|
|
$$.rg_attr = $2;
|
2013-11-08 04:38:26 +04:00
|
|
|
$$.rg_ifname = $3;
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
2013-02-09 07:35:31 +04:00
|
|
|
ruleset_block
|
2013-03-18 06:17:49 +04:00
|
|
|
: CURLY_OPEN ruleset_def CURLY_CLOSE
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-03-18 06:17:49 +04:00
|
|
|
ruleset_def
|
|
|
|
: rule_group SEPLINE ruleset_def
|
2013-02-09 07:35:31 +04:00
|
|
|
| rule_group
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-02-09 07:35:31 +04:00
|
|
|
rule_group
|
|
|
|
: rule
|
|
|
|
| group
|
2013-03-18 06:17:49 +04:00
|
|
|
| ruleset
|
2013-02-09 07:35:31 +04:00
|
|
|
|
|
2013-05-20 00:45:34 +04:00
|
|
|
;
|
2013-02-09 07:35:31 +04:00
|
|
|
|
2013-11-19 01:39:03 +04:00
|
|
|
/*
|
|
|
|
* Rule and misc.
|
|
|
|
*/
|
|
|
|
|
2012-01-09 01:34:21 +04:00
|
|
|
rule
|
2013-11-08 04:38:26 +04:00
|
|
|
: block_or_pass opt_stateful rule_dir opt_final on_ifname
|
2012-11-27 00:34:28 +04:00
|
|
|
opt_family opt_proto all_or_filt_opts opt_apply
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2012-05-31 01:30:07 +04:00
|
|
|
npfctl_build_rule($1 | $2 | $3 | $4, $5,
|
2013-09-20 07:03:52 +04:00
|
|
|
$6, &$7, &$8, NULL, $9);
|
|
|
|
}
|
2013-11-08 04:38:26 +04:00
|
|
|
| block_or_pass opt_stateful rule_dir opt_final on_ifname
|
2013-09-20 07:03:52 +04:00
|
|
|
PCAP_FILTER STRING opt_apply
|
|
|
|
{
|
|
|
|
npfctl_build_rule($1 | $2 | $3 | $4, $5,
|
|
|
|
AF_UNSPEC, NULL, NULL, $7, $8);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
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; }
|
|
|
|
;
|
|
|
|
|
2012-05-31 01:30:07 +04:00
|
|
|
opt_final
|
|
|
|
: FINAL { $$ = NPF_RULE_FINAL; }
|
2012-01-09 01:34:21 +04:00
|
|
|
| { $$ = 0; }
|
|
|
|
;
|
|
|
|
|
2013-11-08 04:38:26 +04:00
|
|
|
on_ifname
|
2013-11-19 04:28:41 +04:00
|
|
|
: ON ifref { $$ = $2; }
|
2013-11-08 04:38:26 +04:00
|
|
|
| { $$ = NULL; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2012-11-27 00:34:28 +04:00
|
|
|
afamily
|
2013-11-19 04:28:41 +04:00
|
|
|
: INET4 { $$ = AF_INET; }
|
2012-11-27 00:34:28 +04:00
|
|
|
| INET6 { $$ = AF_INET6; }
|
|
|
|
;
|
|
|
|
|
2012-07-02 03:21:06 +04:00
|
|
|
opt_family
|
2012-11-27 00:34:28 +04:00
|
|
|
: FAMILY afamily { $$ = $2; }
|
2012-07-02 03:21:06 +04:00
|
|
|
| { $$ = AF_UNSPEC; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
|
|
|
opt_proto
|
|
|
|
: PROTO TCP tcp_flags_and_mask
|
|
|
|
{
|
|
|
|
$$.op_proto = IPPROTO_TCP;
|
|
|
|
$$.op_opts = $3;
|
|
|
|
}
|
|
|
|
| PROTO ICMP icmp_type_and_code
|
|
|
|
{
|
|
|
|
$$.op_proto = IPPROTO_ICMP;
|
|
|
|
$$.op_opts = $3;
|
|
|
|
}
|
2012-07-20 01:52:29 +04:00
|
|
|
| PROTO ICMP6 icmp_type_and_code
|
|
|
|
{
|
|
|
|
$$.op_proto = IPPROTO_ICMPV6;
|
|
|
|
$$.op_opts = $3;
|
|
|
|
}
|
2012-07-02 03:21:06 +04:00
|
|
|
| PROTO some_name
|
|
|
|
{
|
|
|
|
$$.op_proto = npfctl_protono($2);
|
|
|
|
$$.op_opts = NULL;
|
|
|
|
}
|
2013-05-09 23:12:03 +04:00
|
|
|
| PROTO number
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2012-07-02 03:21:06 +04:00
|
|
|
$$.op_proto = $2;
|
2012-01-09 01:34:21 +04:00
|
|
|
$$.op_opts = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
{
|
|
|
|
$$.op_proto = -1;
|
|
|
|
$$.op_opts = NULL;
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
all_or_filt_opts
|
|
|
|
: ALL
|
|
|
|
{
|
2012-06-16 03:24:08 +04:00
|
|
|
$$.fo_from.ap_netaddr = NULL;
|
|
|
|
$$.fo_from.ap_portrange = NULL;
|
|
|
|
$$.fo_to.ap_netaddr = NULL;
|
|
|
|
$$.fo_to.ap_portrange = NULL;
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
| filt_opts { $$ = $1; }
|
|
|
|
;
|
|
|
|
|
2012-05-31 01:30:07 +04:00
|
|
|
opt_stateful
|
2012-07-02 03:21:06 +04:00
|
|
|
: STATEFUL { $$ = NPF_RULE_STATEFUL; }
|
2014-03-14 15:29:44 +04:00
|
|
|
| STATEFUL_ENDS { $$ = NPF_RULE_STATEFUL | NPF_RULE_MULTIENDS; }
|
2012-01-09 01:34:21 +04:00
|
|
|
| { $$ = 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 filt_addr port_range TO filt_addr port_range
|
|
|
|
{
|
2012-06-16 03:24:08 +04:00
|
|
|
$$.fo_from.ap_netaddr = $2;
|
|
|
|
$$.fo_from.ap_portrange = $3;
|
|
|
|
$$.fo_to.ap_netaddr = $5;
|
|
|
|
$$.fo_to.ap_portrange = $6;
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
| FROM filt_addr port_range
|
|
|
|
{
|
2012-06-16 03:24:08 +04:00
|
|
|
$$.fo_from.ap_netaddr = $2;
|
|
|
|
$$.fo_from.ap_portrange = $3;
|
|
|
|
$$.fo_to.ap_netaddr = NULL;
|
|
|
|
$$.fo_to.ap_portrange = NULL;
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
| TO filt_addr port_range
|
|
|
|
{
|
2012-06-16 03:24:08 +04:00
|
|
|
$$.fo_from.ap_netaddr = NULL;
|
|
|
|
$$.fo_from.ap_portrange = NULL;
|
|
|
|
$$.fo_to.ap_netaddr = $2;
|
|
|
|
$$.fo_to.ap_portrange = $3;
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
filt_addr
|
2013-11-19 04:28:41 +04:00
|
|
|
: addr_or_ifaddr { $$ = $1; }
|
2012-02-27 01:14:50 +04:00
|
|
|
| TABLE_ID { $$ = npfctl_parse_table_id($1); }
|
|
|
|
| ANY { $$ = NULL; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
|
|
|
addr_and_mask
|
2013-05-09 23:12:03 +04:00
|
|
|
: addr SLASH number
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
|
|
|
$$ = 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);
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
2013-11-19 04:28:41 +04:00
|
|
|
addr_or_ifaddr
|
2012-07-02 03:21:06 +04:00
|
|
|
: addr_and_mask
|
|
|
|
{
|
|
|
|
assert($1 != NULL);
|
|
|
|
$$ = $1;
|
|
|
|
}
|
2013-11-19 04:28:41 +04:00
|
|
|
| ifaddrs
|
2012-02-27 01:14:50 +04:00
|
|
|
{
|
2012-11-27 00:34:28 +04:00
|
|
|
ifnet_addr_t *ifna = npfvar_get_data($1, NPFVAR_INTERFACE, 0);
|
|
|
|
$$ = ifna->ifna_addrs;
|
2012-02-27 01:14:50 +04:00
|
|
|
}
|
|
|
|
| VAR_ID
|
|
|
|
{
|
|
|
|
npfvar_t *vp = npfvar_lookup($1);
|
2013-03-11 03:11:26 +04:00
|
|
|
int type = npfvar_get_type(vp, 0);
|
2012-11-27 00:34:28 +04:00
|
|
|
ifnet_addr_t *ifna;
|
2012-02-27 01:14:50 +04:00
|
|
|
|
2013-03-11 03:11:26 +04:00
|
|
|
again:
|
2012-02-27 01:14:50 +04:00
|
|
|
switch (type) {
|
2013-03-11 03:11:26 +04:00
|
|
|
case NPFVAR_IDENTIFIER:
|
|
|
|
case NPFVAR_STRING:
|
|
|
|
vp = npfctl_parse_ifnet(npfvar_expand_string(vp),
|
|
|
|
AF_UNSPEC);
|
|
|
|
type = npfvar_get_type(vp, 0);
|
|
|
|
goto again;
|
2012-02-27 01:14:50 +04:00
|
|
|
case NPFVAR_FAM:
|
|
|
|
$$ = vp;
|
|
|
|
break;
|
2012-11-27 00:34:28 +04:00
|
|
|
case NPFVAR_INTERFACE:
|
2014-02-17 04:45:24 +04:00
|
|
|
$$ = 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);
|
|
|
|
}
|
2012-11-27 00:34:28 +04:00
|
|
|
break;
|
2012-02-27 01:14:50 +04:00
|
|
|
case -1:
|
2012-11-27 00:34:28 +04:00
|
|
|
yyerror("undefined variable '%s'", $1);
|
2012-02-27 01:14:50 +04:00
|
|
|
break;
|
|
|
|
default:
|
2012-11-27 00:34:28 +04:00
|
|
|
yyerror("wrong variable '%s' type '%s' for address "
|
|
|
|
"or interface", $1, npfvar_type(type));
|
2012-02-27 01:14:50 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
|
|
|
addr
|
|
|
|
: IPV4ADDR { $$ = $1; }
|
|
|
|
| IPV6ADDR { $$ = $1; }
|
|
|
|
;
|
|
|
|
|
|
|
|
port_range
|
|
|
|
: PORT port /* just port */
|
|
|
|
{
|
|
|
|
$$ = npfctl_parse_port_range($2, $2);
|
|
|
|
}
|
2012-06-16 03:24:08 +04:00
|
|
|
| PORT port MINUS port /* port from-to */
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
|
|
|
$$ = npfctl_parse_port_range($2, $4);
|
|
|
|
}
|
2012-06-16 03:24:08 +04:00
|
|
|
| PORT VAR_ID
|
|
|
|
{
|
2012-02-27 01:50:05 +04:00
|
|
|
$$ = npfctl_parse_port_range_variable($2);
|
|
|
|
}
|
2012-01-09 01:34:21 +04:00
|
|
|
|
|
|
|
|
{
|
|
|
|
$$ = NULL;
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
port
|
2013-05-09 23:12:03 +04:00
|
|
|
: number { $$ = $1; }
|
2012-01-09 01:34:21 +04:00
|
|
|
| IDENTIFIER { $$ = npfctl_portno($1); }
|
2013-03-11 04:09:07 +04:00
|
|
|
| STRING { $$ = npfctl_portno($1); }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
|
|
|
icmp_type_and_code
|
|
|
|
: ICMPTYPE icmp_type
|
|
|
|
{
|
2012-07-20 01:52:29 +04:00
|
|
|
$$ = npfctl_parse_icmp($<num>0, $2, -1);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
2013-05-09 23:12:03 +04:00
|
|
|
| ICMPTYPE icmp_type CODE number
|
2012-01-09 01:34:21 +04:00
|
|
|
{
|
2012-07-20 01:52:29 +04:00
|
|
|
$$ = npfctl_parse_icmp($<num>0, $2, $4);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
| ICMPTYPE icmp_type CODE IDENTIFIER
|
|
|
|
{
|
2012-11-27 00:34:28 +04:00
|
|
|
$$ = npfctl_parse_icmp($<num>0, $2,
|
|
|
|
npfctl_icmpcode($<num>0, $2, $4));
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
| ICMPTYPE icmp_type CODE VAR_ID
|
|
|
|
{
|
|
|
|
char *s = npfvar_expand_string(npfvar_lookup($4));
|
2012-11-27 00:34:28 +04:00
|
|
|
$$ = npfctl_parse_icmp($<num>0, $2,
|
|
|
|
npfctl_icmpcode($<num>0, $2, s));
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
2013-09-19 05:04:45 +04:00
|
|
|
| { $$ = NULL; }
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
|
|
|
tcp_flags_and_mask
|
|
|
|
: FLAGS tcp_flags SLASH tcp_flags
|
|
|
|
{
|
|
|
|
npfvar_add_elements($2, $4);
|
|
|
|
$$ = $2;
|
|
|
|
}
|
|
|
|
| FLAGS tcp_flags
|
|
|
|
{
|
|
|
|
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
|
2013-05-09 23:12:03 +04:00
|
|
|
: number { $$ = $1; }
|
2012-07-20 01:52:29 +04:00
|
|
|
| IDENTIFIER { $$ = npfctl_icmptype($<num>-1, $1); }
|
2012-01-09 01:34:21 +04:00
|
|
|
| VAR_ID
|
|
|
|
{
|
|
|
|
char *s = npfvar_expand_string(npfvar_lookup($1));
|
2012-07-20 01:52:29 +04:00
|
|
|
$$ = npfctl_icmptype($<num>-1, s);
|
2012-01-09 01:34:21 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
2013-11-19 04:28:41 +04:00
|
|
|
ifname
|
|
|
|
: some_name
|
2013-03-11 03:11:26 +04:00
|
|
|
{
|
2013-11-19 04:28:41 +04:00
|
|
|
npfctl_note_interface($1);
|
2013-03-11 03:11:26 +04:00
|
|
|
$$ = $1;
|
|
|
|
}
|
|
|
|
| VAR_ID
|
|
|
|
{
|
|
|
|
npfvar_t *vp = npfvar_lookup($1);
|
|
|
|
const int type = npfvar_get_type(vp, 0);
|
2013-11-19 04:28:41 +04:00
|
|
|
ifnet_addr_t *ifna;
|
2013-03-11 03:11:26 +04:00
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case NPFVAR_STRING:
|
|
|
|
case NPFVAR_IDENTIFIER:
|
|
|
|
$$ = npfvar_expand_string(vp);
|
|
|
|
break;
|
2013-11-19 04:28:41 +04:00
|
|
|
case NPFVAR_INTERFACE:
|
|
|
|
ifna = npfvar_get_data(vp, type, 0);
|
|
|
|
$$ = ifna->ifna_name;
|
|
|
|
break;
|
2013-03-11 03:11:26 +04:00
|
|
|
case -1:
|
|
|
|
yyerror("undefined variable '%s' for interface", $1);
|
|
|
|
break;
|
|
|
|
default:
|
2013-11-19 04:28:41 +04:00
|
|
|
yyerror("wrong variable '%s' type '%s' for interface",
|
2013-03-11 03:11:26 +04:00
|
|
|
$1, npfvar_type(type));
|
|
|
|
break;
|
|
|
|
}
|
2013-11-19 04:28:41 +04:00
|
|
|
npfctl_note_interface($$);
|
2013-03-11 03:11:26 +04:00
|
|
|
}
|
|
|
|
;
|
|
|
|
|
2013-11-19 04:28:41 +04:00
|
|
|
ifaddrs
|
|
|
|
: afamily PAR_OPEN ifname PAR_CLOSE
|
2012-11-27 00:34:28 +04:00
|
|
|
{
|
|
|
|
$$ = npfctl_parse_ifnet($3, $1);
|
|
|
|
}
|
2013-03-11 03:11:26 +04:00
|
|
|
;
|
2012-11-27 00:34:28 +04:00
|
|
|
|
2013-11-19 04:28:41 +04:00
|
|
|
ifref
|
|
|
|
: ifname
|
|
|
|
| ifaddrs
|
2012-11-27 00:34:28 +04:00
|
|
|
{
|
|
|
|
ifnet_addr_t *ifna = npfvar_get_data($1, NPFVAR_INTERFACE, 0);
|
2013-11-08 04:38:26 +04:00
|
|
|
npfctl_note_interface(ifna->ifna_name);
|
|
|
|
$$ = ifna->ifna_name;
|
2012-11-27 00:34:28 +04:00
|
|
|
}
|
2012-01-09 01:34:21 +04:00
|
|
|
;
|
|
|
|
|
2013-05-09 23:12:03 +04:00
|
|
|
number
|
|
|
|
: HEX { $$ = $1; }
|
|
|
|
| NUM { $$ = $1; }
|
|
|
|
;
|
|
|
|
|
2012-07-02 03:21:06 +04:00
|
|
|
some_name
|
2012-01-09 01:34:21 +04:00
|
|
|
: IDENTIFIER { $$ = $1; }
|
|
|
|
| STRING { $$ = $1; }
|
|
|
|
;
|
|
|
|
|
|
|
|
%%
|