/*	$OpenBSD: parse.y,v 1.487 2025/12/06 09:48:30 phessler Exp $ */

/*
 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
 * Copyright (c) 2001 Markus Friedl.  All rights reserved.
 * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
 * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
 * Copyright (c) 2016, 2017 Job Snijders <job@openbsd.org>
 * Copyright (c) 2016 Peter Hessler <phessler@openbsd.org>
 * Copyright (c) 2017, 2018 Sebastian Benoit <benno@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

%{
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip_ipsp.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <endian.h>
#include <err.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

#include "bgpd.h"
#include "session.h"
#include "rde.h"
#include "log.h"

#define MACRO_NAME_LEN		128

TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
static struct file {
	TAILQ_ENTRY(file)	 entry;
	FILE			*stream;
	char			*name;
	size_t			 ungetpos;
	size_t			 ungetsize;
	u_char			*ungetbuf;
	int			 eof_reached;
	int			 lineno;
	int			 errors;
} *file, *topfile;
struct file	*pushfile(const char *, int);
int		 popfile(void);
int		 check_file_secrecy(int, const char *);
int		 yyparse(void);
int		 yylex(void);
int		 yyerror(const char *, ...)
    __attribute__((__format__ (printf, 1, 2)))
    __attribute__((__nonnull__ (1)));
int		 kw_cmp(const void *, const void *);
int		 lookup(char *);
int		 igetc(void);
int		 lgetc(int);
void		 lungetc(int);
int		 findeol(void);
int		 expand_macro(void);

TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
struct sym {
	TAILQ_ENTRY(sym)	 entry;
	int			 used;
	int			 persist;
	char			*nam;
	char			*val;
};
int		 symset(const char *, const char *, int);
char		*symget(const char *);

struct filter_rib_l {
	struct filter_rib_l	*next;
	char			 name[PEER_DESCR_LEN];
};

struct filter_peers_l {
	struct filter_peers_l	*next;
	struct filter_peers	 p;
};

struct filter_prefix_l {
	struct filter_prefix_l	*next;
	struct filter_prefix	 p;
};

struct filter_prefixlen {
	enum comp_ops		op;
	int			len_min;
	int			len_max;
};

struct filter_as_l {
	struct filter_as_l	*next;
	struct filter_as	 a;
};

struct filter_match_l {
	struct filter_match	 m;
	struct filter_prefix_l	*prefix_l;
	struct filter_as_l	*as_l;
	struct filter_prefixset	*prefixset;
} fmopts;

struct aspa_tas_l {
	struct aspa_tas_l	*next;
	uint32_t		 as;
	uint32_t		 num;
};

struct flowspec_context {
	uint8_t			*components[FLOWSPEC_TYPE_MAX];
	uint16_t		 complen[FLOWSPEC_TYPE_MAX];
	uint8_t			 aid;
	uint8_t			 type;
	uint8_t			 addr_type;
};

struct peer	*alloc_peer(void);
struct peer	*new_peer(void);
struct peer	*new_group(void);
int		 add_mrtconfig(enum mrt_type, char *, int, struct peer *,
		    char *);
struct rde_rib	*add_rib(char *);
struct rde_rib	*find_rib(char *);
int		 rib_add_fib(struct rde_rib *, u_int);
int		 get_id(struct peer *);
int		 merge_prefixspec(struct filter_prefix *,
		    struct filter_prefixlen *);
int		 expand_rule(struct filter_rule *, struct filter_rib_l *,
		    struct filter_peers_l *, struct filter_match_l *,
		    struct filter_set_head *);
int		 str2key(char *, char *, size_t);
int		 neighbor_consistent(struct peer *);
int		 merge_filterset(struct filter_set_head *, struct filter_set *);
void		 optimize_filters(struct filter_head *);
struct filter_rule	*get_rule(enum action_types);

int		 parsecommunity(struct community *, int, char *);
int		 parseextcommunity(struct community *, char *,
		    char *);
static int	 new_as_set(char *);
static void	 add_as_set(uint32_t);
static void	 done_as_set(void);
static struct prefixset	*new_prefix_set(char *, int);
static void	 add_roa_set(struct prefixset_item *, uint32_t, uint8_t,
		    time_t);
static struct rtr_config	*get_rtr(struct bgpd_addr *);
static int	 insert_rtr(struct rtr_config *);
static int	 merge_aspa_set(uint32_t, struct aspa_tas_l *, time_t);
static int	 map_tos(char *, int *);
static int	 getservice(char *);
static int	 parse_flags(char *);
static struct flowspec_config	*flow_to_flowspec(struct flowspec_context *);
static void	 flow_free(struct flowspec_context *);
static int	 push_prefix(struct bgpd_addr *, uint8_t);
static int	 push_binop(uint8_t, long long);
static int	 push_unary_numop(enum comp_ops, long long);
static int	 push_binary_numop(enum comp_ops, long long, long long);
static int	 geticmptypebyname(char *, uint8_t);
static int	 geticmpcodebyname(u_long, char *, uint8_t);
static int	 merge_auth_conf(struct auth_config *, struct auth_config *);

static struct bgpd_config	*conf;
static struct network_head	*netconf;
static struct peer_head		*new_peers, *cur_peers;
static struct rtr_config_head	*cur_rtrs;
static struct peer		*curpeer;
static struct peer		*curgroup;
static struct rde_rib		*currib;
static struct l3vpn		*curvpn;
static struct prefixset		*curpset, *curoset;
static struct roa_tree		*curroatree;
static struct rtr_config	*currtr;
static struct filter_head	*filter_l;
static struct filter_head	*peerfilter_l;
static struct filter_head	*groupfilter_l;
static struct filter_rule	*curpeer_filter[2];
static struct filter_rule	*curgroup_filter[2];
static struct flowspec_context	*curflow;
static int			 noexpires;

typedef struct {
	union {
		long long		 number;
		char			*string;
		struct bgpd_addr	 addr;
		uint8_t			 u8;
		struct filter_rib_l	*filter_rib;
		struct filter_peers_l	*filter_peers;
		struct filter_match_l	 filter_match;
		struct filter_prefixset	*filter_prefixset;
		struct filter_prefix_l	*filter_prefix;
		struct filter_as_l	*filter_as;
		struct filter_set	*filter_set;
		struct filter_set_head	*filter_set_head;
		struct aspa_tas_l	*aspa_elm;
		struct {
			struct bgpd_addr	prefix;
			uint8_t			len;
		}			prefix;
		struct filter_prefixlen	prefixlen;
		struct prefixset_item	*prefixset_item;
		struct auth_config	authconf;
		struct {
			enum auth_enc_alg	enc_alg;
			uint8_t			enc_key_len;
			char			enc_key[IPSEC_ENC_KEY_LEN];
		}			encspec;
	} v;
	int lineno;
} YYSTYPE;

%}

%token	AS ROUTERID HOLDTIME YMIN LISTEN ON FIBUPDATE FIBPRIORITY RTABLE
%token	NONE UNICAST VPN RD EXPORT EXPORTTRGT IMPORTTRGT DEFAULTROUTE
%token	RDE RIB EVALUATE IGNORE COMPARE RTR PORT MINVERSION STALETIME
%token	GROUP NEIGHBOR NETWORK
%token	EBGP IBGP
%token	FLOWSPEC PROTO FLAGS FRAGMENT TOS LENGTH ICMPTYPE CODE
%token	LOCALAS REMOTEAS DESCR LOCALADDR MULTIHOP PASSIVE MAXPREFIX RESTART
%token	ANNOUNCE REFRESH AS4BYTE CONNECTRETRY ENHANCED ADDPATH EXTENDED
%token	SEND RECV PLUS POLICY ROLE GRACEFUL NOTIFICATION MESSAGE
%token	DEMOTE ENFORCE NEIGHBORAS ASOVERRIDE REFLECTOR DEPEND DOWN
%token	DUMP IN OUT SOCKET RESTRICTED
%token	LOG TRANSPARENT FILTERED
%token	TCP MD5SIG PASSWORD KEY TTLSECURITY
%token	ALLOW DENY MATCH
%token	QUICK
%token	FROM TO ANY
%token	CONNECTED STATIC
%token	COMMUNITY EXTCOMMUNITY LARGECOMMUNITY DELETE
%token	MAXCOMMUNITIES MAXEXTCOMMUNITIES MAXLARGECOMMUNITIES
%token	PREFIX PREFIXLEN PREFIXSET
%token	ASPASET ROASET ORIGINSET OVS AVS EXPIRES
%token	ASSET SOURCEAS TRANSITAS PEERAS PROVIDERAS CUSTOMERAS MAXASLEN MAXASSEQ
%token	SET LOCALPREF MED METRIC NEXTHOP REJECT BLACKHOLE NOMODIFY SELF
%token	PREPEND_SELF PREPEND_PEER PFTABLE WEIGHT RTLABEL ORIGIN PRIORITY
%token	ERROR INCLUDE
%token	IPSEC ESP AH SPI IKE
%token	IPV4 IPV6 EVPN
%token	QUALIFY VIA
%token	NE LE GE XRANGE LONGER MAXLEN MAX
%token	<v.string>		STRING
%token	<v.number>		NUMBER
%type	<v.number>		asnumber as4number as4number_any optnumber
%type	<v.number>		espah af safi restart origincode nettype
%type	<v.number>		yesno inout restricted expires
%type	<v.number>		yesnoenforce enforce
%type	<v.number>		validity aspa_validity
%type	<v.number>		addpathextra addpathmax
%type	<v.number>		port proto_item tos length flag icmptype
%type	<v.string>		string
%type	<v.addr>		address
%type	<v.prefix>		prefix addrspec
%type	<v.prefixset_item>	prefixset_item
%type	<v.u8>			action quick direction delete community
%type	<v.filter_rib>		filter_rib_h filter_rib_l filter_rib
%type	<v.filter_peers>	filter_peer filter_peer_l filter_peer_h
%type	<v.filter_match>	filter_match filter_elm filter_match_h
%type	<v.filter_as>		filter_as filter_as_l filter_as_h
%type	<v.filter_as>		filter_as_t filter_as_t_l filter_as_l_h
%type	<v.prefixlen>		prefixlenop
%type	<v.filter_set>		filter_set_opt
%type	<v.filter_set_head>	filter_set filter_set_l
%type	<v.filter_prefix>	filter_prefix filter_prefix_l filter_prefix_h
%type	<v.filter_prefix>	filter_prefix_m
%type	<v.u8>			unaryop equalityop binaryop filter_as_type
%type	<v.authconf>		authconf
%type	<v.encspec>		encspec
%type	<v.aspa_elm>		aspa_tas aspa_tas_l
%%

grammar		: /* empty */
		| grammar '\n'
		| grammar varset '\n'
		| grammar include '\n'
		| grammar as_set '\n'
		| grammar prefixset '\n'
		| grammar roa_set '\n'
		| grammar aspa_set '\n'
		| grammar origin_set '\n'
		| grammar rtr '\n'
		| grammar rib '\n'
		| grammar network '\n'
		| grammar flowspec '\n'
		| grammar mrtdump '\n'
		| grammar conf_main '\n'
		| grammar l3vpn '\n'
		| grammar neighbor '\n'
		| grammar group '\n'
		| grammar filterrule '\n'
		| grammar error '\n'		{ file->errors++; }
		;

asnumber	: NUMBER			{
			/*
			 * According to iana 65535 and 4294967295 are reserved
			 * but enforcing this is not duty of the parser.
			 */
			if ($1 < 0 || $1 > UINT_MAX) {
				yyerror("AS too big: max %u", UINT_MAX);
				YYERROR;
			}
		}

as4number	: STRING			{
			const char	*errstr;
			char		*dot;
			uint32_t	 uvalh = 0, uval;

			if ((dot = strchr($1, '.')) != NULL) {
				*dot++ = '\0';
				uvalh = strtonum($1, 0, USHRT_MAX, &errstr);
				if (errstr) {
					yyerror("number %s is %s", $1, errstr);
					free($1);
					YYERROR;
				}
				uval = strtonum(dot, 0, USHRT_MAX, &errstr);
				if (errstr) {
					yyerror("number %s is %s", dot, errstr);
					free($1);
					YYERROR;
				}
				free($1);
			} else {
				yyerror("AS %s is bad", $1);
				free($1);
				YYERROR;
			}
			if (uvalh == 0 && (uval == AS_TRANS || uval == 0)) {
				yyerror("AS %u is reserved and may not be used",
				    uval);
				YYERROR;
			}
			$$ = uval | (uvalh << 16);
		}
		| asnumber {
			if ($1 == AS_TRANS || $1 == 0) {
				yyerror("AS %u is reserved and may not be used",
				    (uint32_t)$1);
				YYERROR;
			}
			$$ = $1;
		}
		;

as4number_any	: STRING			{
			const char	*errstr;
			char		*dot;
			uint32_t	 uvalh = 0, uval;

			if ((dot = strchr($1, '.')) != NULL) {
				*dot++ = '\0';
				uvalh = strtonum($1, 0, USHRT_MAX, &errstr);
				if (errstr) {
					yyerror("number %s is %s", $1, errstr);
					free($1);
					YYERROR;
				}
				uval = strtonum(dot, 0, USHRT_MAX, &errstr);
				if (errstr) {
					yyerror("number %s is %s", dot, errstr);
					free($1);
					YYERROR;
				}
				free($1);
			} else {
				yyerror("AS %s is bad", $1);
				free($1);
				YYERROR;
			}
			$$ = uval | (uvalh << 16);
		}
		| asnumber {
			$$ = $1;
		}
		;

string		: string STRING			{
			if (asprintf(&$$, "%s %s", $1, $2) == -1)
				fatal("string: asprintf");
			free($1);
			free($2);
		}
		| STRING
		;

yesno		: STRING			{
			if (!strcmp($1, "yes"))
				$$ = 1;
			else if (!strcmp($1, "no"))
				$$ = 0;
			else {
				yyerror("syntax error, "
				    "either yes or no expected");
				free($1);
				YYERROR;
			}
			free($1);
		}
		;

varset		: STRING '=' string		{
			char *s = $1;
			if (strlen($1) >= MACRO_NAME_LEN) {
				yyerror("macro name to long, max %d characters",
				    MACRO_NAME_LEN - 1);
				free($1);
				free($3);
				YYERROR;
			}
			do {
				if (isalnum((unsigned char)*s) || *s == '_')
					continue;
				yyerror("macro name can only contain "
					    "alphanumerics and '_'");
				free($1);
				free($3);
				YYERROR;
			} while (*++s);

			if (cmd_opts & BGPD_OPT_VERBOSE)
				printf("%s = \"%s\"\n", $1, $3);
			if (symset($1, $3, 0) == -1)
				fatal("cannot store variable");
			free($1);
			free($3);
		}
		;

include		: INCLUDE STRING		{
			struct file	*nfile;

			if ((nfile = pushfile($2, 1)) == NULL) {
				yyerror("failed to include file %s", $2);
				free($2);
				YYERROR;
			}
			free($2);

			file = nfile;
			lungetc('\n');
		}
		;

as_set		: ASSET STRING '{' optnl	{
			if (strlen($2) >= SET_NAME_LEN) {
				yyerror("as-set name %s too long", $2);
				free($2);
				YYERROR;
			}
			if (new_as_set($2) != 0) {
				free($2);
				YYERROR;
			}
			free($2);
		} as_set_l optnl '}' {
			done_as_set();
		}
		| ASSET STRING '{' optnl '}'	{
			if (new_as_set($2) != 0) {
				free($2);
				YYERROR;
			}
			free($2);
		}

as_set_l	: as4number_any			{ add_as_set($1); }
		| as_set_l comma as4number_any	{ add_as_set($3); }

prefixset	: PREFIXSET STRING '{' optnl		{
			if ((curpset = new_prefix_set($2, 0)) == NULL) {
				free($2);
				YYERROR;
			}
			free($2);
		} prefixset_l optnl '}'			{
			SIMPLEQ_INSERT_TAIL(&conf->prefixsets, curpset, entry);
			curpset = NULL;
		}
		| PREFIXSET STRING '{' optnl '}'	{
			if ((curpset = new_prefix_set($2, 0)) == NULL) {
				free($2);
				YYERROR;
			}
			free($2);
			SIMPLEQ_INSERT_TAIL(&conf->prefixsets, curpset, entry);
			curpset = NULL;
		}

prefixset_l	: prefixset_item			{
			struct prefixset_item	*psi;
			if ($1->p.op != OP_NONE)
				curpset->sflags |= PREFIXSET_FLAG_OPS;
			psi = RB_INSERT(prefixset_tree, &curpset->psitems, $1);
			if (psi != NULL) {
				if (cmd_opts & BGPD_OPT_VERBOSE2)
					log_warnx("warning: duplicate entry in "
					    "prefixset \"%s\" for %s/%u",
					    curpset->name,
					    log_addr(&$1->p.addr), $1->p.len);
				free($1);
			}
		}
		| prefixset_l comma prefixset_item	{
			struct prefixset_item	*psi;
			if ($3->p.op != OP_NONE)
				curpset->sflags |= PREFIXSET_FLAG_OPS;
			psi = RB_INSERT(prefixset_tree, &curpset->psitems, $3);
			if (psi != NULL) {
				if (cmd_opts & BGPD_OPT_VERBOSE2)
					log_warnx("warning: duplicate entry in "
					    "prefixset \"%s\" for %s/%u",
					    curpset->name,
					    log_addr(&$3->p.addr), $3->p.len);
				free($3);
			}
		}
		;

prefixset_item	: prefix prefixlenop			{
			if ($2.op != OP_NONE && $2.op != OP_RANGE) {
				yyerror("unsupported prefixlen operation in "
				    "prefix-set");
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(*$$))) == NULL)
				fatal(NULL);
			memcpy(&$$->p.addr, &$1.prefix, sizeof($$->p.addr));
			$$->p.len = $1.len;
			if (merge_prefixspec(&$$->p, &$2) == -1) {
				free($$);
				YYERROR;
			}
		}
		;

roa_set		: ROASET '{' optnl		{
			curroatree = &conf->roa;
		} roa_set_l optnl '}'			{
			curroatree = NULL;
		}
		| ROASET '{' optnl '}'		/* nothing */
		;

origin_set	: ORIGINSET STRING '{' optnl		{
			if ((curoset = new_prefix_set($2, 1)) == NULL) {
				free($2);
				YYERROR;
			}
			curroatree = &curoset->roaitems;
			noexpires = 1;
			free($2);
		} roa_set_l optnl '}'			{
			SIMPLEQ_INSERT_TAIL(&conf->originsets, curoset, entry);
			curoset = NULL;
			curroatree = NULL;
			noexpires = 0;
		}
		| ORIGINSET STRING '{' optnl '}'		{
			if ((curoset = new_prefix_set($2, 1)) == NULL) {
				free($2);
				YYERROR;
			}
			free($2);
			SIMPLEQ_INSERT_TAIL(&conf->originsets, curoset, entry);
			curoset = NULL;
			curroatree = NULL;
		}
		;

expires		: /* empty */	{
			$$ = 0;
		}
		| EXPIRES NUMBER	{
			if (noexpires) {
				yyerror("syntax error, expires not allowed");
				YYERROR;
			}
			$$ = $2;
		}

roa_set_l	: prefixset_item SOURCEAS as4number_any	expires		{
			if ($1->p.len_min != $1->p.len) {
				yyerror("unsupported prefixlen operation in "
				    "roa-set");
				free($1);
				YYERROR;
			}
			add_roa_set($1, $3, $1->p.len_max, $4);
			free($1);
		}
		| roa_set_l comma prefixset_item SOURCEAS as4number_any	expires {
			if ($3->p.len_min != $3->p.len) {
				yyerror("unsupported prefixlen operation in "
				    "roa-set");
				free($3);
				YYERROR;
			}
			add_roa_set($3, $5, $3->p.len_max, $6);
			free($3);
		}
		;

aspa_set	: ASPASET '{' optnl aspa_set_l optnl '}'
		| ASPASET '{' optnl '}'
		;

aspa_set_l	: aspa_elm
		| aspa_set_l comma aspa_elm
		;

aspa_elm	: CUSTOMERAS as4number expires PROVIDERAS '{' optnl
		    aspa_tas_l optnl '}' {
			int rv;
			struct aspa_tas_l *a, *n;

			rv = merge_aspa_set($2, $7, $3);

			for (a = $7; a != NULL; a = n) {
				n = a->next;
				free(a);
			}

			if (rv == -1)
				YYERROR;
		}
		;

aspa_tas_l	: aspa_tas			{ $$ = $1; }
		| aspa_tas_l comma aspa_tas	{
			$3->next = $1;
			$3->num = $1->num + 1;
			$$ = $3;
		}
		;

aspa_tas	: as4number_any {
			if (($$ = calloc(1, sizeof(*$$))) == NULL)
				fatal(NULL);
			$$->as = $1;
			$$->num = 1;
		}
		| as4number_any af {
			if (($$ = calloc(1, sizeof(*$$))) == NULL)
				fatal(NULL);
			$$->as = $1;
			$$->num = 1;
		}
		;

rtr		: RTR address	{
			currtr = get_rtr(&$2);
			currtr->remote_port = RTR_PORT;
			if (insert_rtr(currtr) == -1) {
				free(currtr);
				YYERROR;
			}
			currtr = NULL;
		}
		| RTR address	{
			currtr = get_rtr(&$2);
			currtr->remote_port = RTR_PORT;
		} '{' optnl rtropt_l optnl '}' {
			if (insert_rtr(currtr) == -1) {
				free(currtr);
				YYERROR;
			}
			currtr = NULL;
		}
		;

rtropt_l	: rtropt
		| rtropt_l optnl rtropt
		;

rtropt		: DESCR STRING		{
			if (strlcpy(currtr->descr, $2,
			    sizeof(currtr->descr)) >=
			    sizeof(currtr->descr)) {
				yyerror("descr \"%s\" too long: max %zu",
				    $2, sizeof(currtr->descr) - 1);
				free($2);
				YYERROR;
			}
			free($2);
		}
		| LOCALADDR address	{
			if ($2.aid != currtr->remote_addr.aid) {
				yyerror("Bad address family %s for "
				    "local-addr", aid2str($2.aid));
				YYERROR;
			}
			currtr->local_addr = $2;
		}
		| PORT port {
			currtr->remote_port = $2;
		}
		| MINVERSION NUMBER {
			if ($2 < 0 || $2 > RTR_MAX_VERSION) {
				yyerror("min-version must be between %u and %u",
				    0, RTR_MAX_VERSION);
				YYERROR;
			}
			currtr->min_version = $2;
		}
		| authconf {
			if (merge_auth_conf(&currtr->auth, &$1) == 0)
				YYERROR;
		}
		;

conf_main	: AS as4number		{
			conf->as = $2;
			if ($2 > USHRT_MAX)
				conf->short_as = AS_TRANS;
			else
				conf->short_as = $2;
		}
		| AS as4number asnumber {
			conf->as = $2;
			conf->short_as = $3;
		}
		| ROUTERID address		{
			if ($2.aid != AID_INET) {
				yyerror("router-id must be an IPv4 address");
				YYERROR;
			}
			conf->bgpid = ntohl($2.v4.s_addr);
		}
		| HOLDTIME NUMBER	{
			if ($2 < MIN_HOLDTIME || $2 > USHRT_MAX) {
				yyerror("holdtime must be between %u and %u",
				    MIN_HOLDTIME, USHRT_MAX);
				YYERROR;
			}
			conf->holdtime = $2;
		}
		| HOLDTIME YMIN NUMBER	{
			if ($3 < MIN_HOLDTIME || $3 > USHRT_MAX) {
				yyerror("holdtime must be between %u and %u",
				    MIN_HOLDTIME, USHRT_MAX);
				YYERROR;
			}
			conf->min_holdtime = $3;
		}
		| STALETIME NUMBER	{
			if ($2 < MIN_HOLDTIME || $2 > USHRT_MAX) {
				yyerror("staletime must be between %u and %u",
				    MIN_HOLDTIME, USHRT_MAX);
				YYERROR;
			}
			conf->staletime = $2;
		}
		| LISTEN ON address	{
			struct listen_addr	*la;
			struct sockaddr		*sa;

			if ((la = calloc(1, sizeof(struct listen_addr))) ==
			    NULL)
				fatal("parse conf_main listen on calloc");

			la->fd = -1;
			la->reconf = RECONF_REINIT;
			sa = addr2sa(&$3, BGP_PORT, &la->sa_len);
			memcpy(&la->sa, sa, la->sa_len);
			TAILQ_INSERT_TAIL(conf->listen_addrs, la, entry);
		}
		| LISTEN ON address PORT port	{
			struct listen_addr	*la;
			struct sockaddr		*sa;

			if ((la = calloc(1, sizeof(struct listen_addr))) ==
			    NULL)
				fatal("parse conf_main listen on calloc");

			la->fd = -1;
			la->reconf = RECONF_REINIT;
			sa = addr2sa(&$3, $5, &la->sa_len);
			memcpy(&la->sa, sa, la->sa_len);
			TAILQ_INSERT_TAIL(conf->listen_addrs, la, entry);
		}
		| FIBPRIORITY NUMBER		{
			if (!kr_check_prio($2)) {
				yyerror("fib-priority %lld out of range", $2);
				YYERROR;
			}
			conf->fib_priority = $2;
		}
		| FIBUPDATE yesno		{
			struct rde_rib *rr;
			rr = find_rib("Loc-RIB");
			if (rr == NULL)
				fatalx("RTABLE cannot find the main RIB!");

			if ($2 == 0)
				rr->flags |= F_RIB_NOFIBSYNC;
			else
				rr->flags &= ~F_RIB_NOFIBSYNC;
		}
		| TRANSPARENT yesno	{
			if ($2 == 1)
				conf->flags |= BGPD_FLAG_DECISION_TRANS_AS;
			else
				conf->flags &= ~BGPD_FLAG_DECISION_TRANS_AS;
		}
		| REJECT ASSET yesno	{
			if ($3 == 1)
				conf->flags &= ~BGPD_FLAG_PERMIT_AS_SET;
			else
				conf->flags |= BGPD_FLAG_PERMIT_AS_SET;
		}
		| LOG STRING		{
			if (!strcmp($2, "updates"))
				conf->log |= BGPD_LOG_UPDATES;
			else {
				free($2);
				YYERROR;
			}
			free($2);
		}
		| DUMP STRING STRING optnumber		{
			int action;

			if ($4 < 0 || $4 > INT_MAX) {
				yyerror("bad timeout");
				free($2);
				free($3);
				YYERROR;
			}
			if (!strcmp($2, "table"))
				action = MRT_TABLE_DUMP;
			else if (!strcmp($2, "table-mp"))
				action = MRT_TABLE_DUMP_MP;
			else if (!strcmp($2, "table-v2"))
				action = MRT_TABLE_DUMP_V2;
			else {
				yyerror("unknown mrt dump type");
				free($2);
				free($3);
				YYERROR;
			}
			free($2);
			if (add_mrtconfig(action, $3, $4, NULL, NULL) == -1) {
				free($3);
				YYERROR;
			}
			free($3);
		}
		| DUMP RIB STRING STRING STRING optnumber		{
			int action;

			if ($6 < 0 || $6 > INT_MAX) {
				yyerror("bad timeout");
				free($3);
				free($4);
				free($5);
				YYERROR;
			}
			if (!strcmp($4, "table"))
				action = MRT_TABLE_DUMP;
			else if (!strcmp($4, "table-mp"))
				action = MRT_TABLE_DUMP_MP;
			else if (!strcmp($4, "table-v2"))
				action = MRT_TABLE_DUMP_V2;
			else {
				yyerror("unknown mrt dump type");
				free($3);
				free($4);
				free($5);
				YYERROR;
			}
			free($4);
			if (add_mrtconfig(action, $5, $6, NULL, $3) == -1) {
				free($3);
				free($5);
				YYERROR;
			}
			free($3);
			free($5);
		}
		| RDE STRING EVALUATE		{
			if (!strcmp($2, "route-age"))
				conf->flags |= BGPD_FLAG_DECISION_ROUTEAGE;
			else {
				yyerror("unknown route decision type");
				free($2);
				YYERROR;
			}
			free($2);
		}
		| RDE STRING IGNORE		{
			if (!strcmp($2, "route-age"))
				conf->flags &= ~BGPD_FLAG_DECISION_ROUTEAGE;
			else {
				yyerror("unknown route decision type");
				free($2);
				YYERROR;
			}
			free($2);
		}
		| RDE MED COMPARE STRING	{
			if (!strcmp($4, "always"))
				conf->flags |= BGPD_FLAG_DECISION_MED_ALWAYS;
			else if (!strcmp($4, "strict"))
				conf->flags &= ~BGPD_FLAG_DECISION_MED_ALWAYS;
			else {
				yyerror("rde med compare: "
				    "unknown setting \"%s\"", $4);
				free($4);
				YYERROR;
			}
			free($4);
		}
		| RDE EVALUATE STRING {
			if (!strcmp($3, "all"))
				conf->flags |= BGPD_FLAG_DECISION_ALL_PATHS;
			else if (!strcmp($3, "default"))
				conf->flags &= ~BGPD_FLAG_DECISION_ALL_PATHS;
			else {
				yyerror("rde evaluate: "
				    "unknown setting \"%s\"", $3);
				free($3);
				YYERROR;
			}
			free($3);
		}
		| RDE RIB STRING INCLUDE FILTERED {
			if (strcmp($3, "Loc-RIB") != 0) {
				yyerror("include filtered only supported in "
				    "Loc-RIB");
				YYERROR;
			}
			conf->filtered_in_locrib = 1;
		}
		| NEXTHOP QUALIFY VIA STRING	{
			if (!strcmp($4, "bgp"))
				conf->flags |= BGPD_FLAG_NEXTHOP_BGP;
			else if (!strcmp($4, "default"))
				conf->flags |= BGPD_FLAG_NEXTHOP_DEFAULT;
			else {
				yyerror("nexthop depend on: "
				    "unknown setting \"%s\"", $4);
				free($4);
				YYERROR;
			}
			free($4);
		}
		| RTABLE NUMBER {
			struct rde_rib *rr;
			if ($2 > RT_TABLEID_MAX) {
				yyerror("rtable %llu too big: max %u", $2,
				    RT_TABLEID_MAX);
				YYERROR;
			}
			if (!ktable_exists($2, NULL)) {
				yyerror("rtable id %lld does not exist", $2);
				YYERROR;
			}
			rr = find_rib("Loc-RIB");
			if (rr == NULL)
				fatalx("RTABLE cannot find the main RIB!");
			rr->rtableid = $2;
		}
		| CONNECTRETRY NUMBER {
			if ($2 > USHRT_MAX || $2 < 1) {
				yyerror("invalid connect-retry");
				YYERROR;
			}
			conf->connectretry = $2;
		}
		| SOCKET STRING	restricted {
			if (strlen($2) >=
			    sizeof(((struct sockaddr_un *)0)->sun_path)) {
				yyerror("socket path too long");
				YYERROR;
			}
			if ($3) {
				free(conf->rcsock);
				conf->rcsock = $2;
			} else {
				free(conf->csock);
				conf->csock = $2;
			}
		}
		;

rib		: RDE RIB STRING {
			if ((currib = add_rib($3)) == NULL) {
				free($3);
				YYERROR;
			}
			free($3);
		} ribopts {
			currib = NULL;
		}

ribopts		: fibupdate
		| RTABLE NUMBER fibupdate {
			if ($2 > RT_TABLEID_MAX) {
				yyerror("rtable %llu too big: max %u", $2,
				    RT_TABLEID_MAX);
				YYERROR;
			}
			if (rib_add_fib(currib, $2) == -1)
				YYERROR;
		}
		| yesno EVALUATE {
			if ($1) {
				yyerror("bad rde rib definition");
				YYERROR;
			}
			currib->flags |= F_RIB_NOEVALUATE;
		}
		;

fibupdate	: /* empty */
		| FIBUPDATE yesno {
			if ($2 == 0)
				currib->flags |= F_RIB_NOFIBSYNC;
			else
				currib->flags &= ~F_RIB_NOFIBSYNC;
		}
		;

mrtdump		: DUMP STRING inout STRING optnumber	{
			int action;

			if ($5 < 0 || $5 > INT_MAX) {
				yyerror("bad timeout");
				free($2);
				free($4);
				YYERROR;
			}
			if (!strcmp($2, "all"))
				action = $3 ? MRT_ALL_IN : MRT_ALL_OUT;
			else if (!strcmp($2, "updates"))
				action = $3 ? MRT_UPDATE_IN : MRT_UPDATE_OUT;
			else {
				yyerror("unknown mrt msg dump type");
				free($2);
				free($4);
				YYERROR;
			}
			if (add_mrtconfig(action, $4, $5, curpeer, NULL) ==
			    -1) {
				free($2);
				free($4);
				YYERROR;
			}
			free($2);
			free($4);
		}
		;

network		: NETWORK prefix filter_set	{
			struct network	*n, *m;

			if ((n = calloc(1, sizeof(struct network))) == NULL)
				fatal("new_network");
			memcpy(&n->net.prefix, &$2.prefix,
			    sizeof(n->net.prefix));
			n->net.prefixlen = $2.len;
			filterset_move($3, &n->net.attrset);
			free($3);
			TAILQ_FOREACH(m, netconf, entry) {
				if (n->net.type == m->net.type &&
				    n->net.prefixlen == m->net.prefixlen &&
				    prefix_compare(&n->net.prefix,
				    &m->net.prefix, n->net.prefixlen) == 0)
					yyerror("duplicate prefix "
					    "in network statement");
			}

			TAILQ_INSERT_TAIL(netconf, n, entry);
		}
		| NETWORK PREFIXSET STRING filter_set	{
			struct prefixset *ps;
			struct network	*n;
			if ((ps = find_prefixset($3, &conf->prefixsets))
			    == NULL) {
				yyerror("prefix-set '%s' not defined", $3);
				free($3);
				filterset_free($4);
				free($4);
				YYERROR;
			}
			if (ps->sflags & PREFIXSET_FLAG_OPS) {
				yyerror("prefix-set %s has prefixlen operators "
				    "and cannot be used in network statements.",
				    ps->name);
				free($3);
				filterset_free($4);
				free($4);
				YYERROR;
			}
			if ((n = calloc(1, sizeof(struct network))) == NULL)
				fatal("new_network");
			strlcpy(n->net.psname, ps->name, sizeof(n->net.psname));
			filterset_move($4, &n->net.attrset);
			n->net.type = NETWORK_PREFIXSET;
			TAILQ_INSERT_TAIL(netconf, n, entry);
			free($3);
			free($4);
		}
		| NETWORK af RTLABEL STRING filter_set	{
			struct network	*n;

			if ((n = calloc(1, sizeof(struct network))) == NULL)
				fatal("new_network");
			if (afi2aid($2, SAFI_UNICAST, &n->net.prefix.aid) ==
			    -1) {
				yyerror("unknown address family");
				filterset_free($5);
				free($5);
				YYERROR;
			}
			n->net.type = NETWORK_RTLABEL;
			n->net.rtlabel = rtlabel_name2id($4);
			filterset_move($5, &n->net.attrset);
			free($5);

			TAILQ_INSERT_TAIL(netconf, n, entry);
		}
		| NETWORK af PRIORITY NUMBER filter_set	{
			struct network	*n;
			if (!kr_check_prio($4)) {
				yyerror("priority %lld out of range", $4);
				YYERROR;
			}

			if ((n = calloc(1, sizeof(struct network))) == NULL)
				fatal("new_network");
			if (afi2aid($2, SAFI_UNICAST, &n->net.prefix.aid) ==
			    -1) {
				yyerror("unknown address family");
				filterset_free($5);
				free($5);
				YYERROR;
			}
			n->net.type = NETWORK_PRIORITY;
			n->net.priority = $4;
			filterset_move($5, &n->net.attrset);
			free($5);

			TAILQ_INSERT_TAIL(netconf, n, entry);
		}
		| NETWORK af nettype filter_set	{
			struct network	*n;

			if ((n = calloc(1, sizeof(struct network))) == NULL)
				fatal("new_network");
			if (afi2aid($2, SAFI_UNICAST, &n->net.prefix.aid) ==
			    -1) {
				yyerror("unknown address family");
				filterset_free($4);
				free($4);
				YYERROR;
			}
			n->net.type = $3 ? NETWORK_STATIC : NETWORK_CONNECTED;
			filterset_move($4, &n->net.attrset);
			free($4);

			TAILQ_INSERT_TAIL(netconf, n, entry);
		}
		;

flowspec	: FLOWSPEC af {
			if ((curflow = calloc(1, sizeof(*curflow))) == NULL)
				fatal("new_flowspec");
			curflow->aid = $2;
		} flow_rules filter_set {
			struct flowspec_config *f;

			f = flow_to_flowspec(curflow);
			if (f == NULL) {
				yyerror("out of memory");
				free($5);
				flow_free(curflow);
				curflow = NULL;
				YYERROR;
			}
			filterset_move($5, &f->attrset);
			free($5);
			flow_free(curflow);
			curflow = NULL;

			if (RB_INSERT(flowspec_tree, &conf->flowspecs, f) !=
			    NULL) {
				yyerror("duplicate flowspec definition");
				flowspec_free(f);
				YYERROR;
			}
		}
		;

proto		: PROTO proto_item
		| PROTO '{' optnl proto_list optnl '}'
		;

proto_list	: proto_item				{
			curflow->type = FLOWSPEC_TYPE_PROTO;
			if (push_unary_numop(OP_EQ, $1) == -1)
				YYERROR;
		}
		| proto_list comma proto_item		{
			curflow->type = FLOWSPEC_TYPE_PROTO;
			if (push_unary_numop(OP_EQ, $3) == -1)
				YYERROR;
		}
		;

proto_item	: STRING				{
			struct protoent *p;

			p = getprotobyname($1);
			if (p == NULL) {
				yyerror("unknown protocol %s", $1);
				free($1);
				YYERROR;
			}
			$$ = p->p_proto;
			free($1);
		}
		| NUMBER				{
			if ($1 < 0 || $1 > 255) {
				yyerror("protocol outside range");
				YYERROR;
			}
			$$ = $1;
		}
		;

from		: FROM {
			curflow->type = FLOWSPEC_TYPE_SRC_PORT;
			curflow->addr_type = FLOWSPEC_TYPE_SOURCE;
		} ipportspec
		;

to		: TO {
			curflow->type = FLOWSPEC_TYPE_DST_PORT;
			curflow->addr_type = FLOWSPEC_TYPE_DEST;
		} ipportspec
		;

ipportspec	: ipspec
		| ipspec PORT portspec
		| PORT portspec
		;

ipspec		: ANY
		| prefix			{
			if (push_prefix(&$1.prefix, $1.len) == -1)
				YYERROR;
		}
		;

portspec	: port_item
		| '{' optnl port_list optnl '}'
		;

port_list	: port_item
		| port_list comma port_item
		;

port_item	: port				{
			if (push_unary_numop(OP_EQ, $1) == -1)
				YYERROR;
		}
		| unaryop port			{
			if (push_unary_numop($1, $2) == -1)
				YYERROR;
		}
		| port binaryop port		{
			if (push_binary_numop($2, $1, $3))
				YYERROR;
		}
		;

port		: NUMBER			{
			if ($1 < 1 || $1 > USHRT_MAX) {
				yyerror("port must be between %u and %u",
				    1, USHRT_MAX);
				YYERROR;
			}
			$$ = $1;
		}
		| STRING			{
			if (($$ = getservice($1)) == -1) {
				yyerror("unknown port '%s'", $1);
				free($1);
				YYERROR;
			}
			free($1);
		}
		;

flow_rules	: /* empty */
		| flow_rules_l
		;

flow_rules_l	: flowrule
		| flow_rules_l flowrule
		;

flowrule	: from
		| to
		| FLAGS {
			curflow->type = FLOWSPEC_TYPE_TCP_FLAGS;
		} flags
		| FRAGMENT {
			curflow->type = FLOWSPEC_TYPE_FRAG;
		} flags;
		| icmpspec
		| LENGTH lengthspec {
			curflow->type = FLOWSPEC_TYPE_PKT_LEN;
		}
		| proto
		| TOS tos {
			curflow->type = FLOWSPEC_TYPE_DSCP;
			if (push_unary_numop(OP_EQ, $2 >> 2) == -1)
				YYERROR;
		}
		;

flags		: flag '/' flag			{
			if (($1 & $3) != $1) {
				yyerror("bad flag combination, "
				    "check bit not in mask");
				YYERROR;
			}
			if (push_binop(FLOWSPEC_OP_BIT_MATCH, $1) == -1)
				YYERROR;
			/* check if extra mask op is needed */
			if ($3 & ~$1) {
				if (push_binop(FLOWSPEC_OP_BIT_NOT |
				    FLOWSPEC_OP_AND, $3 & ~$1) == -1)
					YYERROR;
			}
		}
		| '/' flag			{
			if (push_binop(FLOWSPEC_OP_BIT_NOT, $2) == -1)
				YYERROR;
		}
		| flag				{
			if (push_binop(0, $1) == -1)
				YYERROR;
		}
		| ANY		/* nothing */
		;

flag		: STRING {
			if (($$ = parse_flags($1)) < 0) {
				yyerror("bad flags %s", $1);
				free($1);
				YYERROR;
			}
			free($1);
		}
		;

icmpspec	: ICMPTYPE icmp_item
		| ICMPTYPE '{' optnl icmp_list optnl '}'
		;

icmp_list	: icmp_item
		| icmp_list comma icmp_item
		;

icmp_item	: icmptype			{
			curflow->type = FLOWSPEC_TYPE_ICMP_TYPE;
			if (push_unary_numop(OP_EQ, $1) == -1)
				YYERROR;
		}
		| icmptype CODE STRING {
			int code;

			if ((code = geticmpcodebyname($1, $3, curflow->aid)) ==
			    -1) {
				yyerror("unknown icmp-code %s", $3);
				free($3);
				YYERROR;
			}
			free($3);

			curflow->type = FLOWSPEC_TYPE_ICMP_TYPE;
			if (push_unary_numop(OP_EQ, $1) == -1)
				YYERROR;
			curflow->type = FLOWSPEC_TYPE_ICMP_CODE;
			if (push_unary_numop(OP_EQ, code) == -1)
				YYERROR;
		}
		| icmptype CODE NUMBER {
			if ($3 < 0 || $3 > 255) {
				yyerror("illegal icmp-code %lld", $3);
				YYERROR;
			}
			curflow->type = FLOWSPEC_TYPE_ICMP_TYPE;
			if (push_unary_numop(OP_EQ, $1) == -1)
				YYERROR;
			curflow->type = FLOWSPEC_TYPE_ICMP_CODE;
			if (push_unary_numop(OP_EQ, $3) == -1)
				YYERROR;
		}
		;

icmptype	: STRING {
			int type;

			if ((type = geticmptypebyname($1, curflow->aid)) ==
			    -1) {
				yyerror("unknown icmp-type %s", $1);
				free($1);
				YYERROR;
			}
			$$ = type;
			free($1);
		}
		| NUMBER {
			if ($1 < 0 || $1 > 255) {
				yyerror("illegal icmp-type %lld", $1);
				YYERROR;
			}
			$$ = $1;
		}
		;

tos		: STRING		{
			int val;
			char *end;

			if (map_tos($1, &val))
				$$ = val;
			else if ($1[0] == '0' && $1[1] == 'x') {
				errno = 0;
				$$ = strtoul($1, &end, 16);
				if (errno || *end != '\0')
					$$ = 256;
			} else
				$$ = 256;
			if ($$ < 0 || $$ > 255) {
				yyerror("illegal tos value %s", $1);
				free($1);
				YYERROR;
			}
			free($1);
		}
		| NUMBER		{
			if ($$ < 0 || $$ > 255) {
				yyerror("illegal tos value %lld", $1);
				YYERROR;
			}
			$$ = $1;
		}
		;

lengthspec	: length_item
		| '{' optnl length_list optnl '}'
		;

length_list	: length_item
		| length_list comma length_item
		;

length_item	: length			{
			if (push_unary_numop(OP_EQ, $1) == -1)
				YYERROR;
		}
		| unaryop length		{
			if (push_unary_numop($1, $2) == -1)
				YYERROR;
		}
		| length binaryop length	{
			if (push_binary_numop($2, $1, $3) == -1)
				YYERROR;
		}
		;

length		: NUMBER			{
			if ($$ < 0 || $$ > USHRT_MAX) {
				yyerror("illegal ptk length value %lld", $1);
				YYERROR;
			}
			$$ = $1;
		}

inout		: IN		{ $$ = 1; }
		| OUT		{ $$ = 0; }
		;

restricted	: /* empty */	{ $$ = 0; }
		| RESTRICTED	{ $$ = 1; }
		;

address		: STRING		{
			uint8_t	len;

			if (!host($1, &$$, &len)) {
				yyerror("could not parse address spec \"%s\"",
				    $1);
				free($1);
				YYERROR;
			}
			free($1);

			if (($$.aid == AID_INET && len != 32) ||
			    ($$.aid == AID_INET6 && len != 128)) {
				/* unreachable */
				yyerror("got prefixlen %u, expected %u",
				    len, $$.aid == AID_INET ? 32 : 128);
				YYERROR;
			}
		}
		;

prefix		: STRING '/' NUMBER	{
			char	*s;
			if ($3 < 0 || $3 > 128) {
				yyerror("bad prefixlen %lld", $3);
				free($1);
				YYERROR;
			}
			if (asprintf(&s, "%s/%lld", $1, $3) == -1)
				fatal(NULL);
			free($1);

			if (!host(s, &$$.prefix, &$$.len)) {
				yyerror("could not parse address \"%s\"", s);
				free(s);
				YYERROR;
			}
			free(s);
		}
		| NUMBER '/' NUMBER	{
			char	*s;

			/* does not match IPv6 */
			if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 32) {
				yyerror("bad prefix %lld/%lld", $1, $3);
				YYERROR;
			}
			if (asprintf(&s, "%lld/%lld", $1, $3) == -1)
				fatal(NULL);

			if (!host(s, &$$.prefix, &$$.len)) {
				yyerror("could not parse address \"%s\"", s);
				free(s);
				YYERROR;
			}
			free(s);
		}
		;

addrspec	: address	{
			memcpy(&$$.prefix, &$1, sizeof(struct bgpd_addr));
			if ($$.prefix.aid == AID_INET)
				$$.len = 32;
			else
				$$.len = 128;
		}
		| prefix
		;

optnumber	: /* empty */		{ $$ = 0; }
		| NUMBER
		;

l3vpn		: VPN STRING ON STRING			{
			u_int rdomain, label;

			if (get_mpe_config($4, &rdomain, &label) == -1) {
				if ((cmd_opts & BGPD_OPT_NOACTION) == 0) {
					yyerror("troubles getting config of %s",
					    $4);
					free($4);
					free($2);
					YYERROR;
				}
			}

			if (!(curvpn = calloc(1, sizeof(struct l3vpn))))
				fatal(NULL);
			strlcpy(curvpn->ifmpe, $4, IFNAMSIZ);

			if (strlcpy(curvpn->descr, $2,
			    sizeof(curvpn->descr)) >=
			    sizeof(curvpn->descr)) {
				yyerror("descr \"%s\" too long: max %zu",
				    $2, sizeof(curvpn->descr) - 1);
				free($2);
				free($4);
				free(curvpn);
				curvpn = NULL;
				YYERROR;
			}
			free($2);
			free($4);

			TAILQ_INIT(&curvpn->import);
			TAILQ_INIT(&curvpn->export);
			TAILQ_INIT(&curvpn->net_l);
			curvpn->label = label;
			curvpn->rtableid = rdomain;
			netconf = &curvpn->net_l;
		} '{' l3vpnopts_l '}'	{
			/* insert into list */
			SIMPLEQ_INSERT_TAIL(&conf->l3vpns, curvpn, entry);
			curvpn = NULL;
			netconf = &conf->networks;
		}
		;

l3vpnopts_l	: /* empty */
		| l3vpnopts_l '\n'
		| l3vpnopts_l l3vpnopts '\n'
		| l3vpnopts_l error '\n'
		;

l3vpnopts	: RD STRING {
			struct community	ext;

			memset(&ext, 0, sizeof(ext));
			if (parseextcommunity(&ext, "rt", $2) == -1) {
				free($2);
				YYERROR;
			}
			free($2);
			/*
			 * RD is almost encoded like an ext-community,
			 * but only almost so convert here.
			 */
			if (community_to_rd(&ext, &curvpn->rd) == -1) {
				yyerror("bad encoding of rd");
				YYERROR;
			}
		}
		| EXPORTTRGT STRING STRING	{
			struct filter_set	*set;

			if ((set = calloc(1, sizeof(struct filter_set))) ==
			    NULL)
				fatal(NULL);
			set->type = ACTION_SET_COMMUNITY;
			if (parseextcommunity(&set->action.community,
			    $2, $3) == -1) {
				free($3);
				free($2);
				free(set);
				YYERROR;
			}
			free($3);
			free($2);
			TAILQ_INSERT_TAIL(&curvpn->export, set, entry);
		}
		| IMPORTTRGT STRING STRING	{
			struct filter_set	*set;

			if ((set = calloc(1, sizeof(struct filter_set))) ==
			    NULL)
				fatal(NULL);
			set->type = ACTION_SET_COMMUNITY;
			if (parseextcommunity(&set->action.community,
			    $2, $3) == -1) {
				free($3);
				free($2);
				free(set);
				YYERROR;
			}
			free($3);
			free($2);
			TAILQ_INSERT_TAIL(&curvpn->import, set, entry);
		}
		| FIBUPDATE yesno		{
			if ($2 == 0)
				curvpn->flags |= F_RIB_NOFIBSYNC;
			else
				curvpn->flags &= ~F_RIB_NOFIBSYNC;
		}
		| network
		;

neighbor	: { curpeer = new_peer(); }
		    NEIGHBOR addrspec {
			memcpy(&curpeer->conf.remote_addr, &$3.prefix,
			    sizeof(curpeer->conf.remote_addr));
			curpeer->conf.remote_masklen = $3.len;
			if (($3.prefix.aid == AID_INET && $3.len != 32) ||
			    ($3.prefix.aid == AID_INET6 && $3.len != 128))
				curpeer->conf.template = 1;
			if (get_id(curpeer)) {
				yyerror("get_id failed");
				YYERROR;
			}
		}
		    peeropts_h {
			uint8_t		aid;

			if (curpeer_filter[0] != NULL)
				TAILQ_INSERT_TAIL(peerfilter_l,
				    curpeer_filter[0], entry);
			if (curpeer_filter[1] != NULL)
				TAILQ_INSERT_TAIL(peerfilter_l,
				    curpeer_filter[1], entry);
			curpeer_filter[0] = NULL;
			curpeer_filter[1] = NULL;

			/*
			 * Check if any MP capa is set, if none is set and
			 * and the default AID was not disabled via none then
			 * enable it. Finally fixup the disabled AID.
			 */
			for (aid = AID_MIN; aid < AID_MAX; aid++) {
				if (curpeer->conf.capabilities.mp[aid] > 0)
					break;
			}
			if (aid == AID_MAX &&
			    curpeer->conf.capabilities.mp[
			    curpeer->conf.remote_addr.aid] != -1)
				curpeer->conf.capabilities.mp[
				    curpeer->conf.remote_addr.aid] = 1;
			for (aid = AID_MIN; aid < AID_MAX; aid++) {
				if (curpeer->conf.capabilities.mp[aid] == -1)
					curpeer->conf.capabilities.mp[aid] = 0;
			}

			if (neighbor_consistent(curpeer) == -1) {
				free(curpeer);
				YYERROR;
			}
			if (RB_INSERT(peer_head, new_peers, curpeer) != NULL)
				fatalx("%s: peer tree is corrupt", __func__);
			curpeer = curgroup;
		}
		;

group		: GROUP string			{
			curgroup = curpeer = new_group();
			if (strlcpy(curgroup->conf.group, $2,
			    sizeof(curgroup->conf.group)) >=
			    sizeof(curgroup->conf.group)) {
				yyerror("group name \"%s\" too long: max %zu",
				    $2, sizeof(curgroup->conf.group) - 1);
				free($2);
				free(curgroup);
				YYERROR;
			}
			free($2);
			if (get_id(curgroup)) {
				yyerror("get_id failed");
				free(curgroup);
				YYERROR;
			}
		} '{' groupopts_l '}'		{
			if (curgroup_filter[0] != NULL)
				TAILQ_INSERT_TAIL(groupfilter_l,
				    curgroup_filter[0], entry);
			if (curgroup_filter[1] != NULL)
				TAILQ_INSERT_TAIL(groupfilter_l,
				    curgroup_filter[1], entry);
			curgroup_filter[0] = NULL;
			curgroup_filter[1] = NULL;

			free(curgroup);
			curgroup = NULL;
		}
		;

groupopts_l	: /* empty */
		| groupopts_l '\n'
		| groupopts_l peeropts '\n'
		| groupopts_l neighbor '\n'
		| groupopts_l error '\n'
		;

addpathextra	: /* empty */		{ $$ = 0; }
		| PLUS NUMBER		{
			if ($2 < 1 || $2 > USHRT_MAX) {
				yyerror("additional paths must be between "
				    "%u and %u", 1, USHRT_MAX);
				YYERROR;
			}
			$$ = $2;
		}
		;

addpathmax	: /* empty */		{ $$ = 0; }
		| MAX NUMBER		{
			if ($2 < 1 || $2 > USHRT_MAX) {
				yyerror("maximum additional paths must be "
				    "between %u and %u", 1, USHRT_MAX);
				YYERROR;
			}
			$$ = $2;
		}
		;

peeropts_h	: '{' '\n' peeropts_l '}'
		| '{' peeropts '}'
		| /* empty */
		;

peeropts_l	: /* empty */
		| peeropts_l '\n'
		| peeropts_l peeropts '\n'
		| peeropts_l error '\n'
		;

peeropts	: REMOTEAS as4number	{
			curpeer->conf.remote_as = $2;
		}
		| LOCALAS as4number	{
			curpeer->conf.local_as = $2;
			if ($2 > USHRT_MAX)
				curpeer->conf.local_short_as = AS_TRANS;
			else
				curpeer->conf.local_short_as = $2;
		}
		| LOCALAS as4number asnumber {
			curpeer->conf.local_as = $2;
			curpeer->conf.local_short_as = $3;
		}
		| DESCR string		{
			if (strlcpy(curpeer->conf.descr, $2,
			    sizeof(curpeer->conf.descr)) >=
			    sizeof(curpeer->conf.descr)) {
				yyerror("descr \"%s\" too long: max %zu",
				    $2, sizeof(curpeer->conf.descr) - 1);
				free($2);
				YYERROR;
			}
			free($2);
		}
		| LOCALADDR address	{
			if ($2.aid == AID_INET)
				memcpy(&curpeer->conf.local_addr_v4, &$2,
				    sizeof(curpeer->conf.local_addr_v4));
			else if ($2.aid == AID_INET6)
				memcpy(&curpeer->conf.local_addr_v6, &$2,
				    sizeof(curpeer->conf.local_addr_v6));
			else {
				yyerror("Unsupported address family %s for "
				    "local-addr", aid2str($2.aid));
				YYERROR;
			}
		}
		| yesno LOCALADDR	{
			if ($1) {
				yyerror("bad local-address definition");
				YYERROR;
			}
			memset(&curpeer->conf.local_addr_v4, 0,
			    sizeof(curpeer->conf.local_addr_v4));
			memset(&curpeer->conf.local_addr_v6, 0,
			    sizeof(curpeer->conf.local_addr_v6));
		}
		| MULTIHOP NUMBER	{
			if ($2 < 2 || $2 > 255) {
				yyerror("invalid multihop distance %lld", $2);
				YYERROR;
			}
			curpeer->conf.distance = $2;
		}
		| PASSIVE		{
			curpeer->conf.passive = 1;
		}
		| DOWN			{
			curpeer->conf.down = 1;
		}
		| DOWN STRING		{
			curpeer->conf.down = 1;
			if (strlcpy(curpeer->conf.reason, $2,
				sizeof(curpeer->conf.reason)) >=
				sizeof(curpeer->conf.reason)) {
				    yyerror("shutdown reason too long");
				    free($2);
				    YYERROR;
			}
			free($2);
		}
		| RIB STRING	{
			if (!find_rib($2)) {
				yyerror("rib \"%s\" does not exist.", $2);
				free($2);
				YYERROR;
			}
			if (strlcpy(curpeer->conf.rib, $2,
			    sizeof(curpeer->conf.rib)) >=
			    sizeof(curpeer->conf.rib)) {
				yyerror("rib name \"%s\" too long: max %zu",
				    $2, sizeof(curpeer->conf.rib) - 1);
				free($2);
				YYERROR;
			}
			free($2);
		}
		| HOLDTIME NUMBER	{
			if ($2 < MIN_HOLDTIME || $2 > USHRT_MAX) {
				yyerror("holdtime must be between %u and %u",
				    MIN_HOLDTIME, USHRT_MAX);
				YYERROR;
			}
			curpeer->conf.holdtime = $2;
		}
		| HOLDTIME YMIN NUMBER	{
			if ($3 < MIN_HOLDTIME || $3 > USHRT_MAX) {
				yyerror("holdtime must be between %u and %u",
				    MIN_HOLDTIME, USHRT_MAX);
				YYERROR;
			}
			curpeer->conf.min_holdtime = $3;
		}
		| STALETIME NUMBER	{
			if ($2 < MIN_HOLDTIME || $2 > USHRT_MAX) {
				yyerror("staletime must be between %u and %u",
				    MIN_HOLDTIME, USHRT_MAX);
				YYERROR;
			}
			curpeer->conf.staletime = $2;
		}
		| ANNOUNCE af safi enforce {
			uint8_t		aid, safi;
			uint16_t	afi;

			if ($3 == SAFI_NONE) {
				for (aid = AID_MIN; aid < AID_MAX; aid++) {
					if (aid2afi(aid, &afi, &safi) == -1 ||
					    afi != $2)
						continue;
					curpeer->conf.capabilities.mp[aid] = -1;
				}
			} else {
				if (afi2aid($2, $3, &aid) == -1) {
					yyerror("unknown AFI/SAFI pair");
					YYERROR;
				}
				if ($4)
					curpeer->conf.capabilities.mp[aid] = 2;
				else
					curpeer->conf.capabilities.mp[aid] = 1;
			}
		}
		| ANNOUNCE EVPN enforce {
			if ($3)
				curpeer->conf.capabilities.mp[AID_EVPN] = 2;
			else
				curpeer->conf.capabilities.mp[AID_EVPN] = 1;
		}
		| ANNOUNCE REFRESH yesnoenforce {
			curpeer->conf.capabilities.refresh = $3;
		}
		| ANNOUNCE ENHANCED REFRESH yesnoenforce {
			curpeer->conf.capabilities.enhanced_rr = $4;
		}
		| ANNOUNCE RESTART yesnoenforce {
			curpeer->conf.capabilities.grestart.restart = $3;
		}
		| ANNOUNCE GRACEFUL NOTIFICATION yesno {
			curpeer->conf.capabilities.grestart.grnotification = $4;
		}
		| ANNOUNCE AS4BYTE yesnoenforce {
			curpeer->conf.capabilities.as4byte = $3;
		}
		| ANNOUNCE ADDPATH RECV yesnoenforce {
			int8_t *ap = curpeer->conf.capabilities.add_path;
			uint8_t i;

			for (i = AID_MIN; i < AID_MAX; i++) {
				if ($4) {
					if ($4 == 2)
						ap[i] |= CAPA_AP_RECV_ENFORCE;
					ap[i] |= CAPA_AP_RECV;
				} else
					ap[i] &= ~CAPA_AP_RECV;
			}
		}
		| ANNOUNCE ADDPATH SEND STRING addpathextra addpathmax enforce {
			int8_t *ap = curpeer->conf.capabilities.add_path;
			enum addpath_mode mode;
			u_int8_t i;

			if (!strcmp($4, "no")) {
				free($4);
				if ($5 != 0 || $6 != 0 || $7 != 0) {
					yyerror("no additional option allowed "
					    "for 'add-path send no'");
					YYERROR;
				}
				mode = ADDPATH_EVAL_NONE;
			} else if (!strcmp($4, "all")) {
				free($4);
				if ($5 != 0 || $6 != 0) {
					yyerror("no additional option allowed "
					    "for 'add-path send all'");
					YYERROR;
				}
				mode = ADDPATH_EVAL_ALL;
			} else if (!strcmp($4, "best")) {
				free($4);
				mode = ADDPATH_EVAL_BEST;
			} else if (!strcmp($4, "ecmp")) {
				free($4);
				mode = ADDPATH_EVAL_ECMP;
			} else if (!strcmp($4, "as-wide-best")) {
				free($4);
				mode = ADDPATH_EVAL_AS_WIDE;
			} else {
				yyerror("announce add-path send: "
				    "unknown mode \"%s\"", $4);
				free($4);
				YYERROR;
			}
			for (i = AID_MIN; i < AID_MAX; i++) {
				if (mode != ADDPATH_EVAL_NONE) {
					if ($7)
						ap[i] |= CAPA_AP_SEND_ENFORCE;
					ap[i] |= CAPA_AP_SEND;
				} else
					ap[i] &= ~CAPA_AP_SEND;
			}
			curpeer->conf.eval.mode = mode;
			curpeer->conf.eval.extrapaths = $5;
			curpeer->conf.eval.maxpaths = $6;
		}
		| ANNOUNCE POLICY yesnoenforce {
			curpeer->conf.capabilities.policy = $3;
		}
		| ANNOUNCE EXTENDED MESSAGE yesnoenforce {
			curpeer->conf.capabilities.ext_msg = $4;
		}
		| ANNOUNCE EXTENDED NEXTHOP yesnoenforce {
			curpeer->conf.capabilities.ext_nh[AID_VPN_IPv4] =
			    curpeer->conf.capabilities.ext_nh[AID_INET] = $4;
		}
		| ROLE STRING {
			if (strcmp($2, "provider") == 0) {
				curpeer->conf.role = ROLE_PROVIDER;
			} else if (strcmp($2, "rs") == 0) {
				curpeer->conf.role = ROLE_RS;
			} else if (strcmp($2, "rs-client") == 0) {
				curpeer->conf.role = ROLE_RS_CLIENT;
			} else if (strcmp($2, "customer") == 0) {
				curpeer->conf.role = ROLE_CUSTOMER;
			} else if (strcmp($2, "peer") == 0) {
				curpeer->conf.role = ROLE_PEER;
			} else {
				yyerror("syntax error, one of none, provider, "
				    "rs, rs-client, customer, peer expected");
				free($2);
				YYERROR;
			}
			free($2);
		}
		| ROLE NONE {
			curpeer->conf.role = ROLE_NONE;
		}
		| EXPORT NONE {
			curpeer->conf.export_type = EXPORT_NONE;
		}
		| EXPORT DEFAULTROUTE {
			curpeer->conf.export_type = EXPORT_DEFAULT_ROUTE;
		}
		| ENFORCE NEIGHBORAS yesno {
			if ($3)
				curpeer->conf.enforce_as = ENFORCE_AS_ON;
			else
				curpeer->conf.enforce_as = ENFORCE_AS_OFF;
		}
		| ENFORCE LOCALAS yesno {
			if ($3)
				curpeer->conf.enforce_local_as = ENFORCE_AS_ON;
			else
				curpeer->conf.enforce_local_as = ENFORCE_AS_OFF;
		}
		| ASOVERRIDE yesno {
			if ($2) {
				struct filter_rule	*r;
				struct filter_set	*s;

				if ((s = calloc(1, sizeof(struct filter_set)))
				    == NULL)
					fatal(NULL);
				s->type = ACTION_SET_AS_OVERRIDE;

				r = get_rule(s->type);
				if (merge_filterset(&r->set, s) == -1)
					YYERROR;
			}
		}
		| MAXPREFIX NUMBER restart {
			if ($2 < 0 || $2 > UINT_MAX) {
				yyerror("bad maximum number of prefixes");
				YYERROR;
			}
			curpeer->conf.max_prefix = $2;
			curpeer->conf.max_prefix_restart = $3;
		}
		| MAXPREFIX NUMBER OUT restart {
			if ($2 < 0 || $2 > UINT_MAX) {
				yyerror("bad maximum number of prefixes");
				YYERROR;
			}
			curpeer->conf.max_out_prefix = $2;
			curpeer->conf.max_out_prefix_restart = $4;
		}
		| authconf {
			if (merge_auth_conf(&curpeer->auth_conf, &$1) == 0)
				YYERROR;
		}
		| TTLSECURITY yesno	{
			curpeer->conf.ttlsec = $2;
		}
		| SET filter_set_opt	{
			struct filter_rule	*r;

			r = get_rule($2->type);
			if (merge_filterset(&r->set, $2) == -1)
				YYERROR;
		}
		| SET '{' optnl filter_set_l optnl '}'	{
			struct filter_rule	*r;
			struct filter_set	*s;

			while ((s = TAILQ_FIRST($4)) != NULL) {
				TAILQ_REMOVE($4, s, entry);
				r = get_rule(s->type);
				if (merge_filterset(&r->set, s) == -1)
					YYERROR;
			}
			free($4);
		}
		| mrtdump
		| REFLECTOR		{
			if ((conf->flags & BGPD_FLAG_REFLECTOR) &&
			    conf->clusterid != 0) {
				yyerror("only one route reflector "
				    "cluster allowed");
				YYERROR;
			}
			conf->flags |= BGPD_FLAG_REFLECTOR;
			curpeer->conf.reflector_client = 1;
		}
		| REFLECTOR address	{
			if ($2.aid != AID_INET) {
				yyerror("route reflector cluster-id must be "
				    "an IPv4 address");
				YYERROR;
			}
			if ((conf->flags & BGPD_FLAG_REFLECTOR) &&
			    conf->clusterid != ntohl($2.v4.s_addr)) {
				yyerror("only one route reflector "
				    "cluster allowed");
				YYERROR;
			}
			conf->flags |= BGPD_FLAG_REFLECTOR;
			curpeer->conf.reflector_client = 1;
			conf->clusterid = ntohl($2.v4.s_addr);
		}
		| DEPEND ON STRING	{
			if (strlcpy(curpeer->conf.if_depend, $3,
			    sizeof(curpeer->conf.if_depend)) >=
			    sizeof(curpeer->conf.if_depend)) {
				yyerror("interface name \"%s\" too long: "
				    "max %zu", $3,
				    sizeof(curpeer->conf.if_depend) - 1);
				free($3);
				YYERROR;
			}
			free($3);
		}
		| DEMOTE STRING		{
#ifdef HAVE_CARP
			if (strlcpy(curpeer->conf.demote_group, $2,
			    sizeof(curpeer->conf.demote_group)) >=
			    sizeof(curpeer->conf.demote_group)) {
				yyerror("demote group name \"%s\" too long: "
				    "max %zu", $2,
				    sizeof(curpeer->conf.demote_group) - 1);
				free($2);
				YYERROR;
			}
			free($2);
			if (carp_demote_init(curpeer->conf.demote_group,
			    cmd_opts & BGPD_OPT_FORCE_DEMOTE) == -1) {
				yyerror("error initializing group \"%s\"",
				    curpeer->conf.demote_group);
				YYERROR;
			}
#else
			yyerror("carp demote not supported");
			free($2);
			YYERROR;
#endif
		}
		| TRANSPARENT yesno	{
			if ($2 == 1)
				curpeer->conf.flags |= PEERFLAG_TRANS_AS;
			else
				curpeer->conf.flags &= ~PEERFLAG_TRANS_AS;
		}
		| LOG STRING		{
			if (!strcmp($2, "updates"))
				curpeer->conf.flags |= PEERFLAG_LOG_UPDATES;
			else if (!strcmp($2, "no"))
				curpeer->conf.flags &= ~PEERFLAG_LOG_UPDATES;
			else {
				free($2);
				YYERROR;
			}
			free($2);
		}
		| REJECT ASSET yesno	{
			if ($3 == 1)
				curpeer->conf.flags &= ~PEERFLAG_PERMIT_AS_SET;
			else
				curpeer->conf.flags |= PEERFLAG_PERMIT_AS_SET;
		}
		| PORT port {
			curpeer->conf.remote_port = $2;
		}
		| RDE EVALUATE STRING {
			if (!strcmp($3, "all"))
				curpeer->conf.flags |= PEERFLAG_EVALUATE_ALL;
			else if (!strcmp($3, "default"))
				curpeer->conf.flags &= ~PEERFLAG_EVALUATE_ALL;
			else {
				yyerror("rde evaluate: "
				    "unknown setting \"%s\"", $3);
				free($3);
				YYERROR;
			}
			free($3);
		}
		;

restart		: /* nada */		{ $$ = 0; }
		| RESTART NUMBER	{
			if ($2 < 1 || $2 > USHRT_MAX) {
				yyerror("restart out of range. 1 to %u minutes",
				    USHRT_MAX);
				YYERROR;
			}
			$$ = $2;
		}
		;

af		: IPV4	{ $$ = AFI_IPv4; }
		| IPV6	{ $$ = AFI_IPv6; }
		;

safi		: NONE		{ $$ = SAFI_NONE; }
		| UNICAST	{ $$ = SAFI_UNICAST; }
		| VPN		{ $$ = SAFI_MPLSVPN; }
		| FLOWSPEC	{ $$ = SAFI_FLOWSPEC; }
		;

nettype		: STATIC	{ $$ = 1; }
		| CONNECTED	{ $$ = 0; }
		;

authconf	: TCP MD5SIG PASSWORD string {
			memset(&$$, 0, sizeof($$));
			if (strlcpy($$.md5key, $4, sizeof($$.md5key)) >=
			    sizeof($$.md5key)) {
				yyerror("tcp md5sig password too long: max %zu",
				    sizeof($$.md5key) - 1);
				free($4);
				YYERROR;
			}
			$$.method = AUTH_MD5SIG;
			$$.md5key_len = strlen($4);
			free($4);
		}
		| TCP MD5SIG KEY string {
			memset(&$$, 0, sizeof($$));
			if (str2key($4, $$.md5key, sizeof($$.md5key)) == -1) {
				free($4);
				YYERROR;
			}
			$$.method = AUTH_MD5SIG;
			$$.md5key_len = strlen($4) / 2;
			free($4);
		}
		| IPSEC espah IKE {
			memset(&$$, 0, sizeof($$));
			if ($2)
				$$.method = AUTH_IPSEC_IKE_ESP;
			else
				$$.method = AUTH_IPSEC_IKE_AH;
		}
		| IPSEC espah inout SPI NUMBER STRING STRING encspec {
			enum auth_alg	auth_alg;
			uint8_t		keylen;

			memset(&$$, 0, sizeof($$));
			if (!strcmp($6, "sha1")) {
				auth_alg = AUTH_AALG_SHA1HMAC;
				keylen = 20;
			} else if (!strcmp($6, "md5")) {
				auth_alg = AUTH_AALG_MD5HMAC;
				keylen = 16;
			} else {
				yyerror("unknown auth algorithm \"%s\"", $6);
				free($6);
				free($7);
				YYERROR;
			}
			free($6);

			if (strlen($7) / 2 != keylen) {
				yyerror("auth key len: must be %u bytes, "
				    "is %zu bytes", keylen, strlen($7) / 2);
				free($7);
				YYERROR;
			}

			if ($2)
				$$.method = AUTH_IPSEC_MANUAL_ESP;
			else {
				if ($8.enc_alg) {
					yyerror("\"ipsec ah\" doesn't take "
					    "encryption keys");
					free($7);
					YYERROR;
				}
				$$.method = AUTH_IPSEC_MANUAL_AH;
			}

			if ($5 <= SPI_RESERVED_MAX || $5 > UINT_MAX) {
				yyerror("bad spi number %lld", $5);
				free($7);
				YYERROR;
			}

			if ($3 == 1) {
				if (str2key($7, $$.auth_key_in,
				    sizeof($$.auth_key_in)) == -1) {
					free($7);
					YYERROR;
				}
				$$.spi_in = $5;
				$$.auth_alg_in = auth_alg;
				$$.enc_alg_in = $8.enc_alg;
				memcpy(&$$.enc_key_in, &$8.enc_key,
				    sizeof($$.enc_key_in));
				$$.enc_keylen_in = $8.enc_key_len;
				$$.auth_keylen_in = keylen;
			} else {
				if (str2key($7, $$.auth_key_out,
				    sizeof($$.auth_key_out)) == -1) {
					free($7);
					YYERROR;
				}
				$$.spi_out = $5;
				$$.auth_alg_out = auth_alg;
				$$.enc_alg_out = $8.enc_alg;
				memcpy(&$$.enc_key_out, &$8.enc_key,
				    sizeof($$.enc_key_out));
				$$.enc_keylen_out = $8.enc_key_len;
				$$.auth_keylen_out = keylen;
			}
			free($7);
		}
		;

espah		: ESP		{ $$ = 1; }
		| AH		{ $$ = 0; }
		;

encspec		: /* nada */	{
			memset(&$$, 0, sizeof($$));
		}
		| STRING STRING {
			memset(&$$, 0, sizeof($$));
			if (!strcmp($1, "3des") || !strcmp($1, "3des-cbc")) {
				$$.enc_alg = AUTH_EALG_3DESCBC;
				$$.enc_key_len = 21; /* XXX verify */
			} else if (!strcmp($1, "aes") ||
			    !strcmp($1, "aes-128-cbc")) {
				$$.enc_alg = AUTH_EALG_AES;
				$$.enc_key_len = 16;
			} else {
				yyerror("unknown enc algorithm \"%s\"", $1);
				free($1);
				free($2);
				YYERROR;
			}
			free($1);

			if (strlen($2) / 2 != $$.enc_key_len) {
				yyerror("enc key length wrong: should be %u "
				    "bytes, is %zu bytes",
				    $$.enc_key_len * 2, strlen($2));
				free($2);
				YYERROR;
			}

			if (str2key($2, $$.enc_key, sizeof($$.enc_key)) == -1) {
				free($2);
				YYERROR;
			}
			free($2);
		}
		;

filterrule	: action quick filter_rib_h direction filter_peer_h
				filter_match_h filter_set
		{
			struct filter_rule	 r;
			struct filter_rib_l	 *rb, *rbnext;

			memset(&r, 0, sizeof(r));
			r.action = $1;
			r.quick = $2;
			r.dir = $4;
			if ($3) {
				if (r.dir != DIR_IN) {
					yyerror("rib only allowed on \"from\" "
					    "rules.");

					for (rb = $3; rb != NULL; rb = rbnext) {
						rbnext = rb->next;
						free(rb);
					}
					YYERROR;
				}
			}
			if (expand_rule(&r, $3, $5, &$6, $7) == -1)
				YYERROR;
		}
		;

action		: ALLOW		{ $$ = ACTION_ALLOW; }
		| DENY		{ $$ = ACTION_DENY; }
		| MATCH		{ $$ = ACTION_NONE; }
		;

quick		: /* empty */	{ $$ = 0; }
		| QUICK		{ $$ = 1; }
		;

direction	: FROM		{ $$ = DIR_IN; }
		| TO		{ $$ = DIR_OUT; }
		;

filter_rib_h	: /* empty */			{ $$ = NULL; }
		| RIB filter_rib		{ $$ = $2; }
		| RIB '{' optnl filter_rib_l optnl '}'	{ $$ = $4; }

filter_rib_l	: filter_rib			{ $$ = $1; }
		| filter_rib_l comma filter_rib	{
			$3->next = $1;
			$$ = $3;
		}
		;

filter_rib	: STRING	{
			if (!find_rib($1)) {
				yyerror("rib \"%s\" does not exist.", $1);
				free($1);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_rib_l))) ==
			    NULL)
				fatal(NULL);
			$$->next = NULL;
			if (strlcpy($$->name, $1, sizeof($$->name)) >=
			    sizeof($$->name)) {
				yyerror("rib name \"%s\" too long: "
				    "max %zu", $1, sizeof($$->name) - 1);
				free($1);
				free($$);
				YYERROR;
			}
			free($1);
		}
		;

filter_peer_h	: filter_peer
		| '{' optnl filter_peer_l optnl '}'	{ $$ = $3; }
		;

filter_peer_l	: filter_peer				{ $$ = $1; }
		| filter_peer_l comma filter_peer	{
			$3->next = $1;
			$$ = $3;
		}
		;

filter_peer	: ANY		{
			if (($$ = calloc(1, sizeof(struct filter_peers_l))) ==
			    NULL)
				fatal(NULL);
			$$->p.peerid = $$->p.groupid = 0;
			$$->next = NULL;
		}
		| address	{
			struct peer *p;

			if (($$ = calloc(1, sizeof(struct filter_peers_l))) ==
			    NULL)
				fatal(NULL);
			$$->p.remote_as = $$->p.groupid = $$->p.peerid = 0;
			$$->next = NULL;
			RB_FOREACH(p, peer_head, new_peers)
				if (!memcmp(&p->conf.remote_addr,
				    &$1, sizeof(p->conf.remote_addr))) {
					$$->p.peerid = p->conf.id;
					break;
				}
			if ($$->p.peerid == 0) {
				yyerror("no such peer: %s", log_addr(&$1));
				free($$);
				YYERROR;
			}
		}
		| AS as4number	{
			if (($$ = calloc(1, sizeof(struct filter_peers_l))) ==
			    NULL)
				fatal(NULL);
			$$->p.groupid = $$->p.peerid = 0;
			$$->p.remote_as = $2;
		}
		| GROUP STRING	{
			struct peer *p;

			if (($$ = calloc(1, sizeof(struct filter_peers_l))) ==
			    NULL)
				fatal(NULL);
			$$->p.remote_as = $$->p.peerid = 0;
			$$->next = NULL;
			RB_FOREACH(p, peer_head, new_peers)
				if (!strcmp(p->conf.group, $2)) {
					$$->p.groupid = p->conf.groupid;
					break;
				}
			if ($$->p.groupid == 0) {
				yyerror("no such group: \"%s\"", $2);
				free($2);
				free($$);
				YYERROR;
			}
			free($2);
		}
		| EBGP {
			if (($$ = calloc(1, sizeof(struct filter_peers_l))) ==
			    NULL)
				fatal(NULL);
			$$->p.ebgp = 1;
		}
		| IBGP {
			if (($$ = calloc(1, sizeof(struct filter_peers_l))) ==
			    NULL)
				fatal(NULL);
			$$->p.ibgp = 1;
		}
		;

filter_prefix_h	: IPV4 prefixlenop			 {
			if ($2.op == OP_NONE) {
				$2.op = OP_RANGE;
				$2.len_min = 0;
				$2.len_max = -1;
			}
			if (($$ = calloc(1, sizeof(struct filter_prefix_l))) ==
			    NULL)
				fatal(NULL);
			$$->p.addr.aid = AID_INET;
			if (merge_prefixspec(&$$->p, &$2) == -1) {
				free($$);
				YYERROR;
			}
		}
		| IPV6 prefixlenop			{
			if ($2.op == OP_NONE) {
				$2.op = OP_RANGE;
				$2.len_min = 0;
				$2.len_max = -1;
			}
			if (($$ = calloc(1, sizeof(struct filter_prefix_l))) ==
			    NULL)
				fatal(NULL);
			$$->p.addr.aid = AID_INET6;
			if (merge_prefixspec(&$$->p, &$2) == -1) {
				free($$);
				YYERROR;
			}
		}
		| PREFIX filter_prefix			{ $$ = $2; }
		| PREFIX '{' filter_prefix_m '}'	{ $$ = $3; }
		;

filter_prefix_m	: filter_prefix_l
		| '{' filter_prefix_l '}'		{ $$ = $2; }
		| '{' filter_prefix_l '}' filter_prefix_m
		{
			struct filter_prefix_l	*p;

			/* merge, both can be lists */
			for (p = $2; p != NULL && p->next != NULL; p = p->next)
				;	/* nothing */
			if (p != NULL)
				p->next = $4;
			$$ = $2;
		}

filter_prefix_l	: filter_prefix			{ $$ = $1; }
		| filter_prefix_l comma filter_prefix	{
			$3->next = $1;
			$$ = $3;
		}
		;

filter_prefix	: prefix prefixlenop			{
			if (($$ = calloc(1, sizeof(struct filter_prefix_l))) ==
			    NULL)
				fatal(NULL);
			memcpy(&$$->p.addr, &$1.prefix,
			    sizeof($$->p.addr));
			$$->p.len = $1.len;

			if (merge_prefixspec(&$$->p, &$2) == -1) {
				free($$);
				YYERROR;
			}
		}
		;

filter_as_h	: filter_as_t
		| '{' filter_as_t_l '}'		{ $$ = $2; }
		;

filter_as_t_l	: filter_as_t
		| filter_as_t_l comma filter_as_t		{
			struct filter_as_l	*a;

			/* merge, both can be lists */
			for (a = $1; a != NULL && a->next != NULL; a = a->next)
				;	/* nothing */
			if (a != NULL)
				a->next = $3;
			$$ = $1;
		}
		;

filter_as_t	: filter_as_type filter_as			{
			$$ = $2;
			$$->a.type = $1;
		}
		| filter_as_type '{' filter_as_l_h '}'	{
			struct filter_as_l	*a;

			$$ = $3;
			for (a = $$; a != NULL; a = a->next)
				a->a.type = $1;
		}
		| filter_as_type ASSET STRING {
			if (as_sets_lookup(&conf->as_sets, $3) == NULL) {
				yyerror("as-set \"%s\" not defined", $3);
				free($3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_as_l))) ==
			    NULL)
				fatal(NULL);
			$$->a.type = $1;
			$$->a.flags = AS_FLAG_AS_SET_NAME;
			if (strlcpy($$->a.name, $3, sizeof($$->a.name)) >=
			    sizeof($$->a.name)) {
				yyerror("as-set name \"%s\" too long: "
				    "max %zu", $3, sizeof($$->a.name) - 1);
				free($3);
				free($$);
				YYERROR;
			}
			free($3);
		}
		;

filter_as_l_h	: filter_as_l
		| '{' filter_as_l '}'			{ $$ = $2; }
		| '{' filter_as_l '}' filter_as_l_h
		{
			struct filter_as_l	*a;

			/* merge, both can be lists */
			for (a = $2; a != NULL && a->next != NULL; a = a->next)
				;	/* nothing */
			if (a != NULL)
				a->next = $4;
			$$ = $2;
		}
		;

filter_as_l	: filter_as
		| filter_as_l comma filter_as	{
			$3->next = $1;
			$$ = $3;
		}
		;

filter_as	: as4number_any		{
			if (($$ = calloc(1, sizeof(struct filter_as_l))) ==
			    NULL)
				fatal(NULL);
			$$->a.as_min = $1;
			$$->a.as_max = $1;
			$$->a.op = OP_EQ;
		}
		| NEIGHBORAS		{
			if (($$ = calloc(1, sizeof(struct filter_as_l))) ==
			    NULL)
				fatal(NULL);
			$$->a.flags = AS_FLAG_NEIGHBORAS;
		}
		| equalityop as4number_any	{
			if (($$ = calloc(1, sizeof(struct filter_as_l))) ==
			    NULL)
				fatal(NULL);
			$$->a.op = $1;
			$$->a.as_min = $2;
			$$->a.as_max = $2;
		}
		| as4number_any binaryop as4number_any {
			if (($$ = calloc(1, sizeof(struct filter_as_l))) ==
			    NULL)
				fatal(NULL);
			if ($1 >= $3) {
				yyerror("start AS is bigger than end");
				YYERROR;
			}
			$$->a.op = $2;
			$$->a.as_min = $1;
			$$->a.as_max = $3;
		}
		;

filter_match_h	: /* empty */			{
			memset(&$$, 0, sizeof($$));
		}
		| {
			memset(&fmopts, 0, sizeof(fmopts));
		}
		    filter_match		{
			memcpy(&$$, &fmopts, sizeof($$));
		}
		;

filter_match	: filter_elm
		| filter_match filter_elm
		;

filter_elm	: filter_prefix_h	{
			if (fmopts.prefix_l != NULL) {
				yyerror("\"prefix\" already specified");
				YYERROR;
			}
			if (fmopts.m.prefixset.name[0] != '\0') {
				yyerror("\"prefix-set\" already specified, "
				    "cannot be used with \"prefix\" in the "
				    "same filter rule");
				YYERROR;
			}
			fmopts.prefix_l = $1;
		}
		| filter_as_h		{
			if (fmopts.as_l != NULL) {
				yyerror("AS filters already specified");
				YYERROR;
			}
			fmopts.as_l = $1;
		}
		| MAXASLEN NUMBER	{
			if (fmopts.m.aslen.type != ASLEN_NONE) {
				yyerror("AS length filters already specified");
				YYERROR;
			}
			if ($2 < 0 || $2 > UINT_MAX) {
				yyerror("bad max-as-len %lld", $2);
				YYERROR;
			}
			fmopts.m.aslen.type = ASLEN_MAX;
			fmopts.m.aslen.aslen = $2;
		}
		| MAXASSEQ NUMBER	{
			if (fmopts.m.aslen.type != ASLEN_NONE) {
				yyerror("AS length filters already specified");
				YYERROR;
			}
			if ($2 < 0 || $2 > UINT_MAX) {
				yyerror("bad max-as-seq %lld", $2);
				YYERROR;
			}
			fmopts.m.aslen.type = ASLEN_SEQ;
			fmopts.m.aslen.aslen = $2;
		}
		| community STRING	{
			int i;
			for (i = 0; i < MAX_COMM_MATCH; i++) {
				if (fmopts.m.community[i].flags == 0)
					break;
			}
			if (i >= MAX_COMM_MATCH) {
				yyerror("too many \"community\" filters "
				    "specified");
				free($2);
				YYERROR;
			}
			if (parsecommunity(&fmopts.m.community[i], $1, $2) ==
			    -1) {
				free($2);
				YYERROR;
			}
			free($2);
		}
		| EXTCOMMUNITY STRING STRING {
			int i;
			for (i = 0; i < MAX_COMM_MATCH; i++) {
				if (fmopts.m.community[i].flags == 0)
					break;
			}
			if (i >= MAX_COMM_MATCH) {
				yyerror("too many \"community\" filters "
				    "specified");
				free($2);
				free($3);
				YYERROR;
			}
			if (parseextcommunity(&fmopts.m.community[i],
			    $2, $3) == -1) {
				free($2);
				free($3);
				YYERROR;
			}
			free($2);
			free($3);
		}
		| EXTCOMMUNITY OVS STRING {
			int i;
			for (i = 0; i < MAX_COMM_MATCH; i++) {
				if (fmopts.m.community[i].flags == 0)
					break;
			}
			if (i >= MAX_COMM_MATCH) {
				yyerror("too many \"community\" filters "
				    "specified");
				free($3);
				YYERROR;
			}
			if (parseextcommunity(&fmopts.m.community[i],
			    "ovs", $3) == -1) {
				free($3);
				YYERROR;
			}
			free($3);
		}
		| MAXCOMMUNITIES NUMBER {
			if ($2 < 0 || $2 > INT16_MAX) {
				yyerror("bad max-comunities %lld", $2);
				YYERROR;
			}
			if (fmopts.m.maxcomm != 0) {
				yyerror("%s already specified",
				    "max-communities");
				YYERROR;
			}
			/*
			 * Offset by 1 since 0 means not used.
			 * The match function then uses >= to compensate.
			 */
			fmopts.m.maxcomm = $2 + 1;
		}
		| MAXEXTCOMMUNITIES NUMBER {
			if ($2 < 0 || $2 > INT16_MAX) {
				yyerror("bad max-ext-communities %lld", $2);
				YYERROR;
			}
			if (fmopts.m.maxextcomm != 0) {
				yyerror("%s already specified",
				    "max-ext-communities");
				YYERROR;
			}
			fmopts.m.maxextcomm = $2 + 1;
		}
		| MAXLARGECOMMUNITIES NUMBER {
			if ($2 < 0 || $2 > INT16_MAX) {
				yyerror("bad max-large-communities %lld", $2);
				YYERROR;
			}
			if (fmopts.m.maxlargecomm != 0) {
				yyerror("%s already specified",
				    "max-large-communities");
				YYERROR;
			}
			fmopts.m.maxlargecomm = $2 + 1;
		}
		| NEXTHOP address	{
			if (fmopts.m.nexthop.flags) {
				yyerror("nexthop already specified");
				YYERROR;
			}
			fmopts.m.nexthop.addr = $2;
			fmopts.m.nexthop.flags = FILTER_NEXTHOP_ADDR;
		}
		| NEXTHOP NEIGHBOR	{
			if (fmopts.m.nexthop.flags) {
				yyerror("nexthop already specified");
				YYERROR;
			}
			fmopts.m.nexthop.flags = FILTER_NEXTHOP_NEIGHBOR;
		}
		| PREFIXSET STRING prefixlenop {
			struct prefixset *ps;
			if (fmopts.prefix_l != NULL) {
				yyerror("\"prefix\" already specified, cannot "
				    "be used with \"prefix-set\" in the same "
				    "filter rule");
				free($2);
				YYERROR;
			}
			if (fmopts.m.prefixset.name[0] != '\0') {
				yyerror("prefix-set filter already specified");
				free($2);
				YYERROR;
			}
			if ((ps = find_prefixset($2, &conf->prefixsets))
			    == NULL) {
				yyerror("prefix-set '%s' not defined", $2);
				free($2);
				YYERROR;
			}
			if (strlcpy(fmopts.m.prefixset.name, $2,
			    sizeof(fmopts.m.prefixset.name)) >=
			    sizeof(fmopts.m.prefixset.name)) {
				yyerror("prefix-set name too long");
				free($2);
				YYERROR;
			}
			if (!($3.op == OP_NONE ||
			    ($3.op == OP_RANGE &&
			     $3.len_min == -1 && $3.len_max == -1))) {
				yyerror("prefix-sets can only use option "
				    "or-longer");
				free($2);
				YYERROR;
			}
			if ($3.op == OP_RANGE &&
			    ps->sflags & PREFIXSET_FLAG_OPS) {
				yyerror("prefix-set %s contains prefixlen "
				    "operators and cannot be used with an "
				    "or-longer filter", $2);
				free($2);
				YYERROR;
			}
			if ($3.op == OP_RANGE && $3.len_min == -1 &&
			    $3.len_min == -1)
				fmopts.m.prefixset.flags |=
				    PREFIXSET_FLAG_LONGER;
			fmopts.m.prefixset.flags |= PREFIXSET_FLAG_FILTER;
			free($2);
		}
		| ORIGINSET STRING {
			if (fmopts.m.originset.name[0] != '\0') {
				yyerror("origin-set filter already specified");
				free($2);
				YYERROR;
			}
			if (find_prefixset($2, &conf->originsets) == NULL) {
				yyerror("origin-set '%s' not defined", $2);
				free($2);
				YYERROR;
			}
			if (strlcpy(fmopts.m.originset.name, $2,
			    sizeof(fmopts.m.originset.name)) >=
			    sizeof(fmopts.m.originset.name)) {
				yyerror("origin-set name too long");
				free($2);
				YYERROR;
			}
			free($2);
		}
		| OVS validity		{
			if (fmopts.m.ovs.is_set) {
				yyerror("ovs filter already specified");
				YYERROR;
			}
			fmopts.m.ovs.validity = $2;
			fmopts.m.ovs.is_set = 1;
		}
		| AVS aspa_validity		{
			if (fmopts.m.avs.is_set) {
				yyerror("avs filter already specified");
				YYERROR;
			}
			fmopts.m.avs.validity = $2;
			fmopts.m.avs.is_set = 1;
		}
		;

prefixlenop	: /* empty */		{ memset(&$$, 0, sizeof($$)); }
		| LONGER				{
			memset(&$$, 0, sizeof($$));
			$$.op = OP_RANGE;
			$$.len_min = -1;
			$$.len_max = -1;
		}
		| MAXLEN NUMBER				{
			memset(&$$, 0, sizeof($$));
			if ($2 < 0 || $2 > 128) {
				yyerror("prefixlen must be >= 0 and <= 128");
				YYERROR;
			}

			$$.op = OP_RANGE;
			$$.len_min = -1;
			$$.len_max = $2;
		}
		| PREFIXLEN unaryop NUMBER		{
			int min, max;

			memset(&$$, 0, sizeof($$));
			if ($3 < 0 || $3 > 128) {
				yyerror("prefixlen must be >= 0 and <= 128");
				YYERROR;
			}
			/*
			 * convert the unary operation into the equivalent
			 * range check
			 */
			$$.op = OP_RANGE;

			switch ($2) {
			case OP_NE:
				$$.op = $2;
			case OP_EQ:
				min = max = $3;
				break;
			case OP_LT:
				if ($3 == 0) {
					yyerror("prefixlen must be > 0");
					YYERROR;
				}
				$3 -= 1;
			case OP_LE:
				min = -1;
				max = $3;
				break;
			case OP_GT:
				$3 += 1;
			case OP_GE:
				min = $3;
				max = -1;
				break;
			default:
				yyerror("unknown prefixlen operation");
				YYERROR;
			}
			$$.len_min = min;
			$$.len_max = max;
		}
		| PREFIXLEN NUMBER binaryop NUMBER	{
			memset(&$$, 0, sizeof($$));
			if ($2 < 0 || $2 > 128 || $4 < 0 || $4 > 128) {
				yyerror("prefixlen must be < 128");
				YYERROR;
			}
			if ($2 > $4) {
				yyerror("start prefixlen is bigger than end");
				YYERROR;
			}
			$$.op = $3;
			$$.len_min = $2;
			$$.len_max = $4;
		}
		;

filter_as_type	: AS		{ $$ = AS_ALL; }
		| SOURCEAS	{ $$ = AS_SOURCE; }
		| TRANSITAS	{ $$ = AS_TRANSIT; }
		| PEERAS	{ $$ = AS_PEER; }
		;

filter_set	: /* empty */	{ $$ = NULL; }
		| SET filter_set_opt	{
			if (($$ = calloc(1, sizeof(struct filter_set_head))) ==
			    NULL)
				fatal(NULL);
			TAILQ_INIT($$);
			TAILQ_INSERT_TAIL($$, $2, entry);
		}
		| SET '{' optnl filter_set_l optnl '}'	{ $$ = $4; }
		;

filter_set_l	: filter_set_l comma filter_set_opt	{
			$$ = $1;
			if (merge_filterset($$, $3) == 1)
				YYERROR;
		}
		| filter_set_opt {
			if (($$ = calloc(1, sizeof(struct filter_set_head))) ==
			    NULL)
				fatal(NULL);
			TAILQ_INIT($$);
			TAILQ_INSERT_TAIL($$, $1, entry);
		}
		;

community	: COMMUNITY		{ $$ = COMMUNITY_TYPE_BASIC; }
		| LARGECOMMUNITY	{ $$ = COMMUNITY_TYPE_LARGE; }
		;

delete		: /* empty */	{ $$ = 0; }
		| DELETE	{ $$ = 1; }
		;

enforce		: /* empty */	{ $$ = 0; }
		| ENFORCE	{ $$ = 2; }
		;

yesnoenforce	: yesno		{ $$ = $1; }
		| ENFORCE	{ $$ = 2; }
		;

filter_set_opt	: LOCALPREF NUMBER		{
			if ($2 < -INT_MAX || $2 > UINT_MAX) {
				yyerror("bad localpref %lld", $2);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			if ($2 >= 0) {
				$$->type = ACTION_SET_LOCALPREF;
				$$->action.metric = $2;
			} else {
				$$->type = ACTION_SET_RELATIVE_LOCALPREF;
				$$->action.relative = $2;
			}
		}
		| LOCALPREF '+' NUMBER		{
			if ($3 < 0 || $3 > INT_MAX) {
				yyerror("bad localpref +%lld", $3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_RELATIVE_LOCALPREF;
			$$->action.relative = $3;
		}
		| LOCALPREF '-' NUMBER		{
			if ($3 < 0 || $3 > INT_MAX) {
				yyerror("bad localpref -%lld", $3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_RELATIVE_LOCALPREF;
			$$->action.relative = -$3;
		}
		| MED NUMBER			{
			if ($2 < -INT_MAX || $2 > UINT_MAX) {
				yyerror("bad metric %lld", $2);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			if ($2 >= 0) {
				$$->type = ACTION_SET_MED;
				$$->action.metric = $2;
			} else {
				$$->type = ACTION_SET_RELATIVE_MED;
				$$->action.relative = $2;
			}
		}
		| MED '+' NUMBER		{
			if ($3 < 0 || $3 > INT_MAX) {
				yyerror("bad metric +%lld", $3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_RELATIVE_MED;
			$$->action.relative = $3;
		}
		| MED '-' NUMBER		{
			if ($3 < 0 || $3 > INT_MAX) {
				yyerror("bad metric -%lld", $3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_RELATIVE_MED;
			$$->action.relative = -$3;
		}
		| METRIC NUMBER			{	/* alias for MED */
			if ($2 < -INT_MAX || $2 > UINT_MAX) {
				yyerror("bad metric %lld", $2);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			if ($2 >= 0) {
				$$->type = ACTION_SET_MED;
				$$->action.metric = $2;
			} else {
				$$->type = ACTION_SET_RELATIVE_MED;
				$$->action.relative = $2;
			}
		}
		| METRIC '+' NUMBER		{
			if ($3 < 0 || $3 > INT_MAX) {
				yyerror("bad metric +%lld", $3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_RELATIVE_MED;
			$$->action.metric = $3;
		}
		| METRIC '-' NUMBER		{
			if ($3 < 0 || $3 > INT_MAX) {
				yyerror("bad metric -%lld", $3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_RELATIVE_MED;
			$$->action.relative = -$3;
		}
		| WEIGHT NUMBER			{
			if ($2 < -INT_MAX || $2 > UINT_MAX) {
				yyerror("bad weight %lld", $2);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			if ($2 > 0) {
				$$->type = ACTION_SET_WEIGHT;
				$$->action.metric = $2;
			} else {
				$$->type = ACTION_SET_RELATIVE_WEIGHT;
				$$->action.relative = $2;
			}
		}
		| WEIGHT '+' NUMBER		{
			if ($3 < 0 || $3 > INT_MAX) {
				yyerror("bad weight +%lld", $3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_RELATIVE_WEIGHT;
			$$->action.relative = $3;
		}
		| WEIGHT '-' NUMBER		{
			if ($3 < 0 || $3 > INT_MAX) {
				yyerror("bad weight -%lld", $3);
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_RELATIVE_WEIGHT;
			$$->action.relative = -$3;
		}
		| NEXTHOP address		{
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_NEXTHOP;
			memcpy(&$$->action.nexthop, &$2,
			    sizeof($$->action.nexthop));
		}
		| NEXTHOP BLACKHOLE		{
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_NEXTHOP_BLACKHOLE;
		}
		| NEXTHOP REJECT		{
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_NEXTHOP_REJECT;
		}
		| NEXTHOP NOMODIFY		{
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_NEXTHOP_NOMODIFY;
		}
		| NEXTHOP SELF		{
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_NEXTHOP_SELF;
		}
		| PREPEND_SELF NUMBER		{
			if ($2 < 0 || $2 > 128) {
				yyerror("bad number of prepends");
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_PREPEND_SELF;
			$$->action.prepend = $2;
		}
		| PREPEND_PEER NUMBER		{
			if ($2 < 0 || $2 > 128) {
				yyerror("bad number of prepends");
				YYERROR;
			}
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_PREPEND_PEER;
			$$->action.prepend = $2;
		}
		| ASOVERRIDE			{
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_AS_OVERRIDE;
		}
		| PFTABLE STRING		{
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_PFTABLE;
			if (!(cmd_opts & BGPD_OPT_NOACTION) &&
			    pftable_exists($2) != 0) {
				yyerror("pftable name does not exist");
				free($2);
				free($$);
				YYERROR;
			}
			if (strlcpy($$->action.pftable, $2,
			    sizeof($$->action.pftable)) >=
			    sizeof($$->action.pftable)) {
				yyerror("pftable name too long");
				free($2);
				free($$);
				YYERROR;
			}
			if (pftable_add($2) != 0) {
				yyerror("Couldn't register table");
				free($2);
				free($$);
				YYERROR;
			}
			free($2);
		}
		| RTLABEL STRING		{
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_RTLABEL;
			if (strlcpy($$->action.rtlabel, $2,
			    sizeof($$->action.rtlabel)) >=
			    sizeof($$->action.rtlabel)) {
				yyerror("rtlabel name too long");
				free($2);
				free($$);
				YYERROR;
			}
			free($2);
		}
		| community delete STRING	{
			uint8_t f1, f2, f3;

			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			if ($2)
				$$->type = ACTION_DEL_COMMUNITY;
			else
				$$->type = ACTION_SET_COMMUNITY;

			if (parsecommunity(&$$->action.community, $1, $3) ==
			    -1) {
				free($3);
				free($$);
				YYERROR;
			}
			free($3);
			/* Don't allow setting of any match */
			f1 = $$->action.community.flags >> 8;
			f2 = $$->action.community.flags >> 16;
			f3 = $$->action.community.flags >> 24;
			if (!$2 && (f1 == COMMUNITY_ANY ||
			    f2 == COMMUNITY_ANY || f3 == COMMUNITY_ANY)) {
				yyerror("'*' is not allowed in set community");
				free($$);
				YYERROR;
			}
		}
		| EXTCOMMUNITY delete STRING STRING {
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			if ($2)
				$$->type = ACTION_DEL_COMMUNITY;
			else
				$$->type = ACTION_SET_COMMUNITY;

			if (parseextcommunity(&$$->action.community,
			    $3, $4) == -1) {
				free($3);
				free($4);
				free($$);
				YYERROR;
			}
			free($3);
			free($4);
		}
		| EXTCOMMUNITY delete OVS STRING {
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			if ($2)
				$$->type = ACTION_DEL_COMMUNITY;
			else
				$$->type = ACTION_SET_COMMUNITY;

			if (parseextcommunity(&$$->action.community,
			    "ovs", $4) == -1) {
				free($4);
				free($$);
				YYERROR;
			}
			free($4);
		}
		| ORIGIN origincode {
			if (($$ = calloc(1, sizeof(struct filter_set))) == NULL)
				fatal(NULL);
			$$->type = ACTION_SET_ORIGIN;
			$$->action.origin = $2;
		}
		;

origincode	: STRING	{
			if (!strcmp($1, "egp"))
				$$ = ORIGIN_EGP;
			else if (!strcmp($1, "igp"))
				$$ = ORIGIN_IGP;
			else if (!strcmp($1, "incomplete"))
				$$ = ORIGIN_INCOMPLETE;
			else {
				yyerror("unknown origin \"%s\"", $1);
				free($1);
				YYERROR;
			}
			free($1);
		};

validity	: STRING	{
			if (!strcmp($1, "not-found"))
				$$ = ROA_NOTFOUND;
			else if (!strcmp($1, "invalid"))
				$$ = ROA_INVALID;
			else if (!strcmp($1, "valid"))
				$$ = ROA_VALID;
			else {
				yyerror("unknown roa validity \"%s\"", $1);
				free($1);
				YYERROR;
			}
			free($1);
		};

aspa_validity	: STRING	{
			if (!strcmp($1, "unknown"))
				$$ = ASPA_UNKNOWN;
			else if (!strcmp($1, "invalid"))
				$$ = ASPA_INVALID;
			else if (!strcmp($1, "valid"))
				$$ = ASPA_VALID;
			else {
				yyerror("unknown aspa validity \"%s\"", $1);
				free($1);
				YYERROR;
			}
			free($1);
		};

optnl		: /* empty */
		| '\n' optnl
		;

comma		: /* empty */
		| ','
		| '\n' optnl
		| ',' '\n' optnl
		;

unaryop		: '='		{ $$ = OP_EQ; }
		| NE		{ $$ = OP_NE; }
		| LE		{ $$ = OP_LE; }
		| '<'		{ $$ = OP_LT; }
		| GE		{ $$ = OP_GE; }
		| '>'		{ $$ = OP_GT; }
		;

equalityop	: '='		{ $$ = OP_EQ; }
		| NE		{ $$ = OP_NE; }
		;

binaryop	: '-'		{ $$ = OP_RANGE; }
		| XRANGE	{ $$ = OP_XRANGE; }
		;

%%

struct keywords {
	const char	*k_name;
	int		 k_val;
};

int
yyerror(const char *fmt, ...)
{
	va_list		 ap;
	char		*msg;

	file->errors++;
	va_start(ap, fmt);
	if (vasprintf(&msg, fmt, ap) == -1)
		fatalx("yyerror vasprintf");
	va_end(ap);
	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
	free(msg);
	return (0);
}

int
kw_cmp(const void *k, const void *e)
{
	return (strcmp(k, ((const struct keywords *)e)->k_name));
}

int
lookup(char *s)
{
	/* this has to be sorted always */
	static const struct keywords keywords[] = {
		{ "AS",			AS },
		{ "EVPN",		EVPN },
		{ "IPv4",		IPV4 },
		{ "IPv6",		IPV6 },
		{ "add-path",		ADDPATH },
		{ "ah",			AH },
		{ "allow",		ALLOW },
		{ "announce",		ANNOUNCE },
		{ "any",		ANY },
		{ "as-4byte",		AS4BYTE },
		{ "as-override",	ASOVERRIDE },
		{ "as-set",		ASSET },
		{ "aspa-set",		ASPASET },
		{ "avs",		AVS },
		{ "blackhole",		BLACKHOLE },
		{ "community",		COMMUNITY },
		{ "compare",		COMPARE },
		{ "connect-retry",	CONNECTRETRY },
		{ "connected",		CONNECTED },
		{ "customer-as",	CUSTOMERAS },
		{ "default-route",	DEFAULTROUTE },
		{ "delete",		DELETE },
		{ "demote",		DEMOTE },
		{ "deny",		DENY },
		{ "depend",		DEPEND },
		{ "descr",		DESCR },
		{ "down",		DOWN },
		{ "dump",		DUMP },
		{ "ebgp",		EBGP },
		{ "enforce",		ENFORCE },
		{ "enhanced",		ENHANCED },
		{ "esp",		ESP },
		{ "evaluate",		EVALUATE },
		{ "expires",		EXPIRES },
		{ "export",		EXPORT },
		{ "export-target",	EXPORTTRGT },
		{ "ext-community",	EXTCOMMUNITY },
		{ "extended",		EXTENDED },
		{ "fib-priority",	FIBPRIORITY },
		{ "fib-update",		FIBUPDATE },
		{ "filtered",		FILTERED },
		{ "flags",		FLAGS },
		{ "flowspec",		FLOWSPEC },
		{ "fragment",		FRAGMENT },
		{ "from",		FROM },
		{ "graceful",		GRACEFUL },
		{ "group",		GROUP },
		{ "holdtime",		HOLDTIME },
		{ "ibgp",		IBGP },
		{ "ignore",		IGNORE },
		{ "ike",		IKE },
		{ "import-target",	IMPORTTRGT },
		{ "in",			IN },
		{ "include",		INCLUDE },
		{ "inet",		IPV4 },
		{ "inet6",		IPV6 },
		{ "ipsec",		IPSEC },
		{ "key",		KEY },
		{ "large-community",	LARGECOMMUNITY },
		{ "listen",		LISTEN },
		{ "local-address",	LOCALADDR },
		{ "local-as",		LOCALAS },
		{ "localpref",		LOCALPREF },
		{ "log",		LOG },
		{ "match",		MATCH },
		{ "max",		MAX },
		{ "max-as-len",		MAXASLEN },
		{ "max-as-seq",		MAXASSEQ },
		{ "max-communities",	MAXCOMMUNITIES },
		{ "max-ext-communities",	MAXEXTCOMMUNITIES },
		{ "max-large-communities",	MAXLARGECOMMUNITIES },
		{ "max-prefix",		MAXPREFIX },
		{ "maxlen",		MAXLEN },
		{ "md5sig",		MD5SIG },
		{ "med",		MED },
		{ "message",		MESSAGE },
		{ "metric",		METRIC },
		{ "min",		YMIN },
		{ "min-version",	MINVERSION },
		{ "multihop",		MULTIHOP },
		{ "neighbor",		NEIGHBOR },
		{ "neighbor-as",	NEIGHBORAS },
		{ "network",		NETWORK },
		{ "nexthop",		NEXTHOP },
		{ "no-modify",		NOMODIFY },
		{ "none",		NONE },
		{ "notification",	NOTIFICATION },
		{ "on",			ON },
		{ "or-longer",		LONGER },
		{ "origin",		ORIGIN },
		{ "origin-set",		ORIGINSET },
		{ "out",		OUT },
		{ "ovs",		OVS },
		{ "passive",		PASSIVE },
		{ "password",		PASSWORD },
		{ "peer-as",		PEERAS },
		{ "pftable",		PFTABLE },
		{ "plus",		PLUS },
		{ "policy",		POLICY },
		{ "port",		PORT },
		{ "prefix",		PREFIX },
		{ "prefix-set",		PREFIXSET },
		{ "prefixlen",		PREFIXLEN },
		{ "prepend-neighbor",	PREPEND_PEER },
		{ "prepend-self",	PREPEND_SELF },
		{ "priority",		PRIORITY },
		{ "proto",		PROTO },
		{ "provider-as",	PROVIDERAS },
		{ "qualify",		QUALIFY },
		{ "quick",		QUICK },
		{ "rd",			RD },
		{ "rde",		RDE },
		{ "recv",		RECV },
		{ "refresh",		REFRESH },
		{ "reject",		REJECT },
		{ "remote-as",		REMOTEAS },
		{ "restart",		RESTART },
		{ "restricted",		RESTRICTED },
		{ "rib",		RIB },
		{ "roa-set",		ROASET },
		{ "role",		ROLE },
		{ "route-reflector",	REFLECTOR },
		{ "router-id",		ROUTERID },
		{ "rtable",		RTABLE },
		{ "rtlabel",		RTLABEL },
		{ "rtr",		RTR },
		{ "self",		SELF },
		{ "send",		SEND },
		{ "set",		SET },
		{ "socket",		SOCKET },
		{ "source-as",		SOURCEAS },
		{ "spi",		SPI },
		{ "staletime",		STALETIME },
		{ "static",		STATIC },
		{ "tcp",		TCP },
		{ "to",			TO },
		{ "tos",		TOS },
		{ "transit-as",		TRANSITAS },
		{ "transparent-as",	TRANSPARENT },
		{ "ttl-security",	TTLSECURITY },
		{ "unicast",		UNICAST },
		{ "via",		VIA },
		{ "vpn",		VPN },
		{ "weight",		WEIGHT },
	};
	const struct keywords	*p;

	p = bsearch(s, keywords, nitems(keywords), sizeof(keywords[0]), kw_cmp);

	if (p)
		return (p->k_val);
	else
		return (STRING);
}

#define START_EXPAND	1
#define DONE_EXPAND	2

static int	expanding;

int
igetc(void)
{
	int	c;

	while (1) {
		if (file->ungetpos > 0)
			c = file->ungetbuf[--file->ungetpos];
		else
			c = getc(file->stream);

		if (c == START_EXPAND)
			expanding = 1;
		else if (c == DONE_EXPAND)
			expanding = 0;
		else
			break;
	}
	return (c);
}

int
lgetc(int quotec)
{
	int		c, next;

	if (quotec) {
		if ((c = igetc()) == EOF) {
			yyerror("reached end of file while parsing "
			    "quoted string");
			if (file == topfile || popfile() == EOF)
				return (EOF);
			return (quotec);
		}
		return (c);
	}

	while ((c = igetc()) == '\\') {
		next = igetc();
		if (next != '\n') {
			c = next;
			break;
		}
		yylval.lineno = file->lineno;
		file->lineno++;
	}

	if (c == EOF) {
		/*
		 * Fake EOL when hit EOF for the first time. This gets line
		 * count right if last line in included file is syntactically
		 * invalid and has no newline.
		 */
		if (file->eof_reached == 0) {
			file->eof_reached = 1;
			return ('\n');
		}
		while (c == EOF) {
			if (file == topfile || popfile() == EOF)
				return (EOF);
			c = igetc();
		}
	}
	return (c);
}

void
lungetc(int c)
{
	if (c == EOF)
		return;

	if (file->ungetpos >= file->ungetsize) {
		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
		if (p == NULL)
			err(1, "lungetc");
		file->ungetbuf = p;
		file->ungetsize *= 2;
	}
	file->ungetbuf[file->ungetpos++] = c;
}

int
findeol(void)
{
	int	c;

	/* skip to either EOF or the first real EOL */
	while (1) {
		c = lgetc(0);
		if (c == '\n') {
			file->lineno++;
			break;
		}
		if (c == EOF)
			break;
	}
	return (ERROR);
}

int
expand_macro(void)
{
	char	 buf[MACRO_NAME_LEN];
	char	*p, *val;
	int	 c;

	p = buf;
	while (1) {
		if ((c = lgetc('$')) == EOF)
			return (ERROR);
		if (p + 1 >= buf + sizeof(buf) - 1) {
			yyerror("macro name too long");
			return (ERROR);
		}
		if (isalnum(c) || c == '_') {
			*p++ = c;
			continue;
		}
		*p = '\0';
		lungetc(c);
		break;
	}
	val = symget(buf);
	if (val == NULL) {
		yyerror("macro '%s' not defined", buf);
		return (ERROR);
	}
	p = val + strlen(val) - 1;
	lungetc(DONE_EXPAND);
	while (p >= val) {
		lungetc((unsigned char)*p);
		p--;
	}
	lungetc(START_EXPAND);
	return (0);
}

int
yylex(void)
{
	char	 buf[8096];
	char	*p;
	int	 quotec, next, c;
	int	 token;

top:
	p = buf;
	while ((c = lgetc(0)) == ' ' || c == '\t')
		; /* nothing */

	yylval.lineno = file->lineno;
	if (c == '#')
		while ((c = lgetc(0)) != '\n' && c != EOF)
			; /* nothing */
	if (c == '$' && !expanding) {
		c = expand_macro();
		if (c != 0)
			return (c);
		goto top;
	}

	switch (c) {
	case '\'':
	case '"':
		quotec = c;
		while (1) {
			if ((c = lgetc(quotec)) == EOF)
				return (0);
			if (c == '\n') {
				file->lineno++;
				continue;
			} else if (c == '\\') {
				if ((next = lgetc(quotec)) == EOF)
					return (0);
				if (next == quotec || next == ' ' ||
				    next == '\t')
					c = next;
				else if (next == '\n') {
					file->lineno++;
					continue;
				} else
					lungetc(next);
			} else if (c == quotec) {
				*p = '\0';
				break;
			} else if (c == '\0') {
				yyerror("syntax error: unterminated quote");
				return (findeol());
			}
			if (p + 1 >= buf + sizeof(buf) - 1) {
				yyerror("string too long");
				return (findeol());
			}
			*p++ = c;
		}
		yylval.v.string = strdup(buf);
		if (yylval.v.string == NULL)
			fatal("yylex: strdup");
		return (STRING);
	case '!':
		next = lgetc(0);
		if (next == '=')
			return (NE);
		lungetc(next);
		break;
	case '<':
		next = lgetc(0);
		if (next == '=')
			return (LE);
		lungetc(next);
		break;
	case '>':
		next = lgetc(0);
		if (next == '<')
			return (XRANGE);
		else if (next == '=')
			return (GE);
		lungetc(next);
		break;
	}

#define allowed_to_end_number(x) \
	(isspace(x) || x == ')' || x == ',' || x == '/' || x == '}' || x == '=')

	if (c == '-' || isdigit(c)) {
		do {
			*p++ = c;
			if ((size_t)(p-buf) >= sizeof(buf)) {
				yyerror("string too long");
				return (findeol());
			}
		} while ((c = lgetc(0)) != EOF && isdigit(c));
		lungetc(c);
		if (p == buf + 1 && buf[0] == '-')
			goto nodigits;
		if (c == EOF || allowed_to_end_number(c)) {
			const char *errstr = NULL;

			*p = '\0';
			yylval.v.number = strtonum(buf, LLONG_MIN,
			    LLONG_MAX, &errstr);
			if (errstr) {
				yyerror("\"%s\" invalid number: %s",
				    buf, errstr);
				return (findeol());
			}
			return (NUMBER);
		} else {
nodigits:
			while (p > buf + 1)
				lungetc((unsigned char)*--p);
			c = (unsigned char)*--p;
			if (c == '-')
				return (c);
		}
	}

#define allowed_in_string(x) \
	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
	x != '{' && x != '}' && x != '<' && x != '>' && \
	x != '!' && x != '=' && x != '/' && x != '#' && \
	x != ','))

	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
		do {
			if (c == '$' && !expanding) {
				c = expand_macro();
				if (c != 0)
					return (c);
			} else
				*p++ = c;

			if ((size_t)(p-buf) >= sizeof(buf)) {
				yyerror("string too long");
				return (findeol());
			}
		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
		lungetc(c);
		*p = '\0';
		if ((token = lookup(buf)) == STRING)
			if ((yylval.v.string = strdup(buf)) == NULL)
				fatal("yylex: strdup");
		return (token);
	}
	if (c == '\n') {
		yylval.lineno = file->lineno;
		file->lineno++;
	}
	if (c == EOF)
		return (0);
	return (c);
}

int
check_file_secrecy(int fd, const char *fname)
{
	struct stat	st;

	if (fstat(fd, &st)) {
		log_warn("cannot stat %s", fname);
		return (-1);
	}
	return (0);
}

struct file *
pushfile(const char *name, int secret)
{
	struct file	*nfile;

	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
		log_warn("%s", __func__);
		return (NULL);
	}
	if ((nfile->name = strdup(name)) == NULL) {
		log_warn("%s", __func__);
		free(nfile);
		return (NULL);
	}
	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
		log_warn("%s: %s", __func__, nfile->name);
		free(nfile->name);
		free(nfile);
		return (NULL);
	}
	if (secret &&
	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
		fclose(nfile->stream);
		free(nfile->name);
		free(nfile);
		return (NULL);
	}
	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
	nfile->ungetsize = 16;
	nfile->ungetbuf = malloc(nfile->ungetsize);
	if (nfile->ungetbuf == NULL) {
		log_warn("%s", __func__);
		fclose(nfile->stream);
		free(nfile->name);
		free(nfile);
		return (NULL);
	}
	TAILQ_INSERT_TAIL(&files, nfile, entry);
	return (nfile);
}

int
popfile(void)
{
	struct file	*prev;

	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
		prev->errors += file->errors;

	TAILQ_REMOVE(&files, file, entry);
	fclose(file->stream);
	free(file->name);
	free(file->ungetbuf);
	free(file);
	file = prev;
	return (file ? 0 : EOF);
}

static void
init_config(struct bgpd_config *c)
{
	u_int rdomid;

	c->min_holdtime = MIN_HOLDTIME;
	c->holdtime = INTERVAL_HOLD;
	c->staletime = INTERVAL_STALE;
	c->connectretry = INTERVAL_CONNECTRETRY;
	c->bgpid = get_bgpid();
	c->fib_priority = kr_default_prio();
	c->default_tableid = getrtable();
	if (!ktable_exists(c->default_tableid, &rdomid))
		fatalx("current routing table %u does not exist",
		    c->default_tableid);
	if (rdomid != c->default_tableid)
		fatalx("current routing table %u is not a routing domain",
		    c->default_tableid);

	if (asprintf(&c->csock, "%s.%d", SOCKET_NAME, c->default_tableid) == -1)
		fatal(NULL);
}

struct bgpd_config *
parse_config(const char *filename, struct peer_head *ph,
    struct rtr_config_head *rh)
{
	struct sym		*sym, *next;
	struct rde_rib		*rr;
	struct network		*n;
	int			 errors = 0;

	conf = new_config();
	init_config(conf);

	if ((filter_l = calloc(1, sizeof(struct filter_head))) == NULL)
		fatal(NULL);
	if ((peerfilter_l = calloc(1, sizeof(struct filter_head))) == NULL)
		fatal(NULL);
	if ((groupfilter_l = calloc(1, sizeof(struct filter_head))) == NULL)
		fatal(NULL);
	TAILQ_INIT(filter_l);
	TAILQ_INIT(peerfilter_l);
	TAILQ_INIT(groupfilter_l);

	curpeer = NULL;
	curgroup = NULL;

	cur_peers = ph;
	cur_rtrs = rh;
	new_peers = &conf->peers;
	netconf = &conf->networks;

	if ((rr = add_rib("Adj-RIB-In")) == NULL)
		fatal("add_rib failed");
	rr->flags = F_RIB_NOFIB | F_RIB_NOEVALUATE;
	if ((rr = add_rib("Loc-RIB")) == NULL)
		fatal("add_rib failed");
	rib_add_fib(rr, conf->default_tableid);
	rr->flags = F_RIB_LOCAL;

	if ((file = pushfile(filename, 1)) == NULL)
		goto errors;
	topfile = file;

	yyparse();
	errors = file->errors;
	popfile();

	/* check that we dont try to announce our own routes */
	TAILQ_FOREACH(n, netconf, entry)
		if (n->net.priority == conf->fib_priority) {
			errors++;
			logit(LOG_CRIT, "network priority %d == fib-priority "
			    "%d is not allowed.",
			    n->net.priority, conf->fib_priority);
		}

	/* Free macros and check which have not been used. */
	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
		if ((cmd_opts & BGPD_OPT_VERBOSE2) && !sym->used)
			fprintf(stderr, "warning: macro \"%s\" not "
			    "used\n", sym->nam);
		if (!sym->persist) {
			free(sym->nam);
			free(sym->val);
			TAILQ_REMOVE(&symhead, sym, entry);
			free(sym);
		}
	}

	if (!conf->as) {
		log_warnx("configuration error: AS not given");
		errors++;
	}

	/* clear the globals */
	curpeer = NULL;
	curgroup = NULL;
	cur_peers = NULL;
	new_peers = NULL;
	netconf = NULL;
	curflow = NULL;

	if (errors) {
errors:
		while ((rr = SIMPLEQ_FIRST(&ribnames)) != NULL) {
			SIMPLEQ_REMOVE_HEAD(&ribnames, entry);
			free(rr);
		}

		filterlist_free(filter_l);
		filterlist_free(peerfilter_l);
		filterlist_free(groupfilter_l);

		free_config(conf);
		return (NULL);
	}

	/* Create default listeners if none where specified. */
	if (TAILQ_EMPTY(conf->listen_addrs)) {
		struct listen_addr *la;

		if ((la = calloc(1, sizeof(struct listen_addr))) == NULL)
			fatal("setup_listeners calloc");
		la->fd = -1;
		la->flags = DEFAULT_LISTENER;
		la->reconf = RECONF_REINIT;
		la->sa_len = sizeof(struct sockaddr_in);
		((struct sockaddr_in *)&la->sa)->sin_family = AF_INET;
		((struct sockaddr_in *)&la->sa)->sin_addr.s_addr =
		    htonl(INADDR_ANY);
		((struct sockaddr_in *)&la->sa)->sin_port = htons(BGP_PORT);
		TAILQ_INSERT_TAIL(conf->listen_addrs, la, entry);

		if ((la = calloc(1, sizeof(struct listen_addr))) == NULL)
			fatal("setup_listeners calloc");
		la->fd = -1;
		la->flags = DEFAULT_LISTENER;
		la->reconf = RECONF_REINIT;
		la->sa_len = sizeof(struct sockaddr_in6);
		((struct sockaddr_in6 *)&la->sa)->sin6_family = AF_INET6;
		((struct sockaddr_in6 *)&la->sa)->sin6_port = htons(BGP_PORT);
		TAILQ_INSERT_TAIL(conf->listen_addrs, la, entry);
	}

	/* update clusterid in case it was not set explicitly */
	if ((conf->flags & BGPD_FLAG_REFLECTOR) && conf->clusterid == 0)
		conf->clusterid = conf->bgpid;

	/*
	 * Concatenate filter list and static group and peer filtersets
	 * together. Static group sets come first then peer sets
	 * last normal filter rules.
	 */
	TAILQ_CONCAT(conf->filters, groupfilter_l, entry);
	TAILQ_CONCAT(conf->filters, peerfilter_l, entry);
	TAILQ_CONCAT(conf->filters, filter_l, entry);

	optimize_filters(conf->filters);

	free(filter_l);
	free(peerfilter_l);
	free(groupfilter_l);

	return (conf);
}

int
symset(const char *nam, const char *val, int persist)
{
	struct sym	*sym;

	TAILQ_FOREACH(sym, &symhead, entry) {
		if (strcmp(nam, sym->nam) == 0)
			break;
	}

	if (sym != NULL) {
		if (sym->persist == 1)
			return (0);
		else {
			free(sym->nam);
			free(sym->val);
			TAILQ_REMOVE(&symhead, sym, entry);
			free(sym);
		}
	}
	if ((sym = calloc(1, sizeof(*sym))) == NULL)
		return (-1);

	sym->nam = strdup(nam);
	if (sym->nam == NULL) {
		free(sym);
		return (-1);
	}
	sym->val = strdup(val);
	if (sym->val == NULL) {
		free(sym->nam);
		free(sym);
		return (-1);
	}
	sym->used = 0;
	sym->persist = persist;
	TAILQ_INSERT_TAIL(&symhead, sym, entry);
	return (0);
}

int
cmdline_symset(char *s)
{
	char	*sym, *val;
	int	ret;

	if ((val = strrchr(s, '=')) == NULL)
		return (-1);
	sym = strndup(s, val - s);
	if (sym == NULL)
		fatal("%s: strndup", __func__);
	ret = symset(sym, val + 1, 1);
	free(sym);

	return (ret);
}

char *
symget(const char *nam)
{
	struct sym	*sym;

	TAILQ_FOREACH(sym, &symhead, entry) {
		if (strcmp(nam, sym->nam) == 0) {
			sym->used = 1;
			return (sym->val);
		}
	}
	return (NULL);
}

static int
cmpcommunity(struct community *a, struct community *b)
{
	if (a->flags > b->flags)
		return 1;
	if (a->flags < b->flags)
		return -1;
	if (a->data1 > b->data1)
		return 1;
	if (a->data1 < b->data1)
		return -1;
	if (a->data2 > b->data2)
		return 1;
	if (a->data2 < b->data2)
		return -1;
	if (a->data3 > b->data3)
		return 1;
	if (a->data3 < b->data3)
		return -1;
	return 0;
}

static int
getcommunity(char *s, int large, uint32_t *val, uint32_t *flag)
{
	long long	 max = USHRT_MAX;
	const char	*errstr;

	*flag = 0;
	*val = 0;
	if (strcmp(s, "*") == 0) {
		*flag = COMMUNITY_ANY;
		return 0;
	} else if (strcmp(s, "neighbor-as") == 0) {
		*flag = COMMUNITY_NEIGHBOR_AS;
		return 0;
	} else if (strcmp(s, "local-as") == 0) {
		*flag = COMMUNITY_LOCAL_AS;
		return 0;
	}
	if (large)
		max = UINT_MAX;
	*val = strtonum(s, 0, max, &errstr);
	if (errstr) {
		yyerror("Community %s is %s (max: %lld)", s, errstr, max);
		return -1;
	}
	return 0;
}

static void
setcommunity(struct community *c, uint32_t as, uint32_t data,
    uint32_t asflag, uint32_t dataflag)
{
	c->flags = COMMUNITY_TYPE_BASIC;
	c->flags |= asflag << 8;
	c->flags |= dataflag << 16;
	c->data1 = as;
	c->data2 = data;
	c->data3 = 0;
}

static int
parselargecommunity(struct community *c, char *s)
{
	char *p, *q;
	uint32_t dflag1, dflag2, dflag3;

	if ((p = strchr(s, ':')) == NULL) {
		yyerror("Bad community syntax");
		return (-1);
	}
	*p++ = 0;

	if ((q = strchr(p, ':')) == NULL) {
		yyerror("Bad community syntax");
		return (-1);
	}
	*q++ = 0;

	if (getcommunity(s, 1, &c->data1, &dflag1) == -1 ||
	    getcommunity(p, 1, &c->data2, &dflag2) == -1 ||
	    getcommunity(q, 1, &c->data3, &dflag3) == -1)
		return (-1);
	c->flags = COMMUNITY_TYPE_LARGE;
	c->flags |= dflag1 << 8;
	c->flags |= dflag2 << 16;
	c->flags |= dflag3 << 24;
	return (0);
}

int
parsecommunity(struct community *c, int type, char *s)
{
	char *p;
	uint32_t as, data, asflag, dataflag;

	if (type == COMMUNITY_TYPE_LARGE)
		return parselargecommunity(c, s);

	/* Well-known communities */
	if (strcasecmp(s, "GRACEFUL_SHUTDOWN") == 0) {
		setcommunity(c, COMMUNITY_WELLKNOWN,
		    COMMUNITY_GRACEFUL_SHUTDOWN, 0, 0);
		return (0);
	} else if (strcasecmp(s, "NO_EXPORT") == 0) {
		setcommunity(c, COMMUNITY_WELLKNOWN,
		    COMMUNITY_NO_EXPORT, 0, 0);
		return (0);
	} else if (strcasecmp(s, "NO_ADVERTISE") == 0) {
		setcommunity(c, COMMUNITY_WELLKNOWN,
		    COMMUNITY_NO_ADVERTISE, 0, 0);
		return (0);
	} else if (strcasecmp(s, "NO_EXPORT_SUBCONFED") == 0) {
		setcommunity(c, COMMUNITY_WELLKNOWN,
		    COMMUNITY_NO_EXPSUBCONFED, 0, 0);
		return (0);
	} else if (strcasecmp(s, "NO_PEER") == 0) {
		setcommunity(c, COMMUNITY_WELLKNOWN,
		    COMMUNITY_NO_PEER, 0, 0);
		return (0);
	} else if (strcasecmp(s, "BLACKHOLE") == 0) {
		setcommunity(c, COMMUNITY_WELLKNOWN,
		    COMMUNITY_BLACKHOLE, 0, 0);
		return (0);
	}

	if ((p = strchr(s, ':')) == NULL) {
		yyerror("Bad community syntax");
		return (-1);
	}
	*p++ = 0;

	if (getcommunity(s, 0, &as, &asflag) == -1 ||
	    getcommunity(p, 0, &data, &dataflag) == -1)
		return (-1);
	setcommunity(c, as, data, asflag, dataflag);
	return (0);
}

static int
parsesubtype(char *name, int *type, int *subtype)
{
	const struct ext_comm_pairs *cp;
	int found = 0;

	for (cp = iana_ext_comms; cp->subname != NULL; cp++) {
		if (strcmp(name, cp->subname) == 0) {
			if (found == 0) {
				*type = cp->type;
				*subtype = cp->subtype;
			}
			found++;
		}
	}
	if (found > 1)
		*type = -1;
	return (found);
}

static int
parseextvalue(int type, char *s, uint32_t *v, uint32_t *flag)
{
	const char	*errstr;
	char		*p;
	struct in_addr	 ip;
	uint32_t	 uvalh, uval;

	if (type != -1) {
		/* nothing */
	} else if (strcmp(s, "neighbor-as") == 0) {
		*flag = COMMUNITY_NEIGHBOR_AS;
		*v = 0;
		return EXT_COMMUNITY_TRANS_TWO_AS;
	} else if (strcmp(s, "local-as") == 0) {
		*flag = COMMUNITY_LOCAL_AS;
		*v = 0;
		return EXT_COMMUNITY_TRANS_TWO_AS;
	} else if ((p = strchr(s, '.')) == NULL) {
		/* AS_PLAIN number (4 or 2 byte) */
		strtonum(s, 0, USHRT_MAX, &errstr);
		if (errstr == NULL)
			type = EXT_COMMUNITY_TRANS_TWO_AS;
		else
			type = EXT_COMMUNITY_TRANS_FOUR_AS;
	} else if (strchr(p + 1, '.') == NULL) {
		/* AS_DOT number (4-byte) */
		type = EXT_COMMUNITY_TRANS_FOUR_AS;
	} else {
		/* more than one dot -> IP address */
		type = EXT_COMMUNITY_TRANS_IPV4;
	}

	switch (type & EXT_COMMUNITY_VALUE) {
	case EXT_COMMUNITY_TRANS_TWO_AS:
		uval = strtonum(s, 0, USHRT_MAX, &errstr);
		if (errstr) {
			yyerror("Bad ext-community %s is %s", s, errstr);
			return (-1);
		}
		*v = uval;
		break;
	case EXT_COMMUNITY_TRANS_FOUR_AS:
		if ((p = strchr(s, '.')) == NULL) {
			uval = strtonum(s, 0, UINT_MAX, &errstr);
			if (errstr) {
				yyerror("Bad ext-community %s is %s", s,
				    errstr);
				return (-1);
			}
			*v = uval;
			break;
		}
		*p++ = '\0';
		uvalh = strtonum(s, 0, USHRT_MAX, &errstr);
		if (errstr) {
			yyerror("Bad ext-community %s is %s", s, errstr);
			return (-1);
		}
		uval = strtonum(p, 0, USHRT_MAX, &errstr);
		if (errstr) {
			yyerror("Bad ext-community %s is %s", p, errstr);
			return (-1);
		}
		*v = uval | (uvalh << 16);
		break;
	case EXT_COMMUNITY_TRANS_IPV4:
		if (inet_pton(AF_INET, s, &ip) != 1) {
			yyerror("Bad ext-community %s not parseable", s);
			return (-1);
		}
		*v = ntohl(ip.s_addr);
		break;
	default:
		fatalx("%s: unexpected type %d", __func__, type);
	}
	return (type);
}

int
parseextcommunity(struct community *c, char *t, char *s)
{
	const struct ext_comm_pairs *cp;
	char		*p, *ep;
	uint64_t	 ullval;
	uint32_t	 uval, uval2, dflag1 = 0, dflag2 = 0;
	int		 type = 0, subtype = 0;

	if (strcmp(t, "*") == 0 && strcmp(s, "*") == 0) {
		c->flags = COMMUNITY_TYPE_EXT;
		c->flags |= COMMUNITY_ANY << 24;
		return (0);
	}
	if (parsesubtype(t, &type, &subtype) == 0) {
		yyerror("Bad ext-community unknown type");
		return (-1);
	}

	switch (type) {
	case EXT_COMMUNITY_TRANS_TWO_AS:
	case EXT_COMMUNITY_TRANS_FOUR_AS:
	case EXT_COMMUNITY_TRANS_IPV4:
	case EXT_COMMUNITY_GEN_TWO_AS:
	case EXT_COMMUNITY_GEN_FOUR_AS:
	case EXT_COMMUNITY_GEN_IPV4:
	case -1:
		if (strcmp(s, "*") == 0) {
			dflag1 = COMMUNITY_ANY;
			break;
		}
		if ((p = strchr(s, ':')) == NULL) {
			yyerror("Bad ext-community %s", s);
			return (-1);
		}
		*p++ = '\0';
		if ((type = parseextvalue(type, s, &uval, &dflag1)) == -1)
			return (-1);

		switch (type) {
		case EXT_COMMUNITY_TRANS_TWO_AS:
		case EXT_COMMUNITY_GEN_TWO_AS:
			if (getcommunity(p, 1, &uval2, &dflag2) == -1)
				return (-1);
			break;
		case EXT_COMMUNITY_TRANS_IPV4:
		case EXT_COMMUNITY_TRANS_FOUR_AS:
		case EXT_COMMUNITY_GEN_IPV4:
		case EXT_COMMUNITY_GEN_FOUR_AS:
			if (getcommunity(p, 0, &uval2, &dflag2) == -1)
				return (-1);
			break;
		default:
			fatalx("parseextcommunity: unexpected result");
		}

		c->data1 = uval;
		c->data2 = uval2;
		break;
	case EXT_COMMUNITY_TRANS_OPAQUE:
	case EXT_COMMUNITY_TRANS_EVPN:
		if (strcmp(s, "*") == 0) {
			dflag1 = COMMUNITY_ANY;
			break;
		}
		errno = 0;
		ullval = strtoull(s, &ep, 0);
		if (s[0] == '\0' || *ep != '\0') {
			yyerror("Bad ext-community bad value");
			return (-1);
		}
		if (errno == ERANGE && ullval > EXT_COMMUNITY_OPAQUE_MAX) {
			yyerror("Bad ext-community value too big");
			return (-1);
		}
		c->data1 = ullval >> 32;
		c->data2 = ullval;
		break;
	case EXT_COMMUNITY_NON_TRANS_OPAQUE:
		if (subtype == EXT_COMMUNITY_SUBTYPE_OVS) {
			if (strcmp(s, "valid") == 0) {
				c->data2 = EXT_COMMUNITY_OVS_VALID;
				break;
			} else if (strcmp(s, "invalid") == 0) {
				c->data2 = EXT_COMMUNITY_OVS_INVALID;
				break;
			} else if (strcmp(s, "not-found") == 0) {
				c->data2 = EXT_COMMUNITY_OVS_NOTFOUND;
				break;
			} else if (strcmp(s, "*") == 0) {
				dflag1 = COMMUNITY_ANY;
				break;
			}
		}
		yyerror("Bad ext-community %s", s);
		return (-1);
	}

	c->data3 = type << 8 | subtype;

	/* special handling of ext-community rt * since type is not known */
	if (dflag1 == COMMUNITY_ANY && type == -1) {
		c->flags = COMMUNITY_TYPE_EXT;
		c->flags |= dflag1 << 8;
		return (0);
	}

	/* verify type/subtype combo */
	for (cp = iana_ext_comms; cp->subname != NULL; cp++) {
		if (cp->type == type && cp->subtype == subtype) {
			c->flags = COMMUNITY_TYPE_EXT;
			c->flags |= dflag1 << 8;
			c->flags |= dflag2 << 16;
			return (0);
		}
	}

	yyerror("Bad ext-community bad format for type");
	return (-1);
}

struct peer *
alloc_peer(void)
{
	struct peer	*p;

	if ((p = calloc(1, sizeof(struct peer))) == NULL)
		fatal("new_peer");

	/* some sane defaults */
	p->state = STATE_NONE;
	p->reconf_action = RECONF_REINIT;
	p->conf.distance = 1;
	p->conf.export_type = EXPORT_UNSET;
	p->conf.capabilities.refresh = 1;
	p->conf.capabilities.grestart.restart = 1;
	p->conf.capabilities.as4byte = 1;
	p->conf.capabilities.policy = 1;
	p->conf.local_as = conf->as;
	p->conf.local_short_as = conf->short_as;
	p->conf.remote_port = BGP_PORT;

	if (conf->flags & BGPD_FLAG_DECISION_TRANS_AS)
		p->conf.flags |= PEERFLAG_TRANS_AS;
	if (conf->flags & BGPD_FLAG_DECISION_ALL_PATHS)
		p->conf.flags |= PEERFLAG_EVALUATE_ALL;
	if (conf->flags & BGPD_FLAG_PERMIT_AS_SET)
		p->conf.flags |= PEERFLAG_PERMIT_AS_SET;

	return (p);
}

struct peer *
new_peer(void)
{
	struct peer		*p;

	p = alloc_peer();

	if (curgroup != NULL) {
		p->conf = curgroup->conf;
		p->auth_conf = curgroup->auth_conf;
		p->conf.groupid = curgroup->conf.id;
	}
	return (p);
}

struct peer *
new_group(void)
{
	return (alloc_peer());
}

int
add_mrtconfig(enum mrt_type type, char *name, int timeout, struct peer *p,
    char *rib)
{
	struct mrt	*m, *n;

	LIST_FOREACH(m, conf->mrt, entry) {
		if ((rib && strcmp(rib, m->rib)) ||
		    (!rib && *m->rib))
			continue;
		if (p == NULL) {
			if (m->peer_id != 0 || m->group_id != 0)
				continue;
		} else {
			if (m->peer_id != p->conf.id ||
			    m->group_id != p->conf.groupid)
				continue;
		}
		if (m->type == type) {
			yyerror("only one mrtdump per type allowed.");
			return (-1);
		}
	}

	if ((n = calloc(1, sizeof(struct mrt_config))) == NULL)
		fatal("add_mrtconfig");

	n->type = type;
	n->state = MRT_STATE_OPEN;
	if (strlcpy(MRT2MC(n)->name, name, sizeof(MRT2MC(n)->name)) >=
	    sizeof(MRT2MC(n)->name)) {
		yyerror("filename \"%s\" too long: max %zu",
		    name, sizeof(MRT2MC(n)->name) - 1);
		free(n);
		return (-1);
	}
	TAILQ_INIT(&MRT2MC(n)->timer);
	MRT2MC(n)->ReopenTimerInterval = timeout;
	if (p != NULL) {
		if (curgroup == p) {
			n->peer_id = 0;
			n->group_id = p->conf.id;
		} else {
			n->peer_id = p->conf.id;
			n->group_id = p->conf.groupid;
		}
	}
	if (rib) {
		if (!find_rib(rib)) {
			yyerror("rib \"%s\" does not exist.", rib);
			free(n);
			return (-1);
		}
		if (strlcpy(n->rib, rib, sizeof(n->rib)) >=
		    sizeof(n->rib)) {
			yyerror("rib name \"%s\" too long: max %zu",
			    name, sizeof(n->rib) - 1);
			free(n);
			return (-1);
		}
	}

	LIST_INSERT_HEAD(conf->mrt, n, entry);

	return (0);
}

struct rde_rib *
add_rib(char *name)
{
	struct rde_rib	*rr;

	if ((rr = find_rib(name)) == NULL) {
		if ((rr = calloc(1, sizeof(*rr))) == NULL) {
			log_warn("add_rib");
			return (NULL);
		}
		if (strlcpy(rr->name, name, sizeof(rr->name)) >=
		    sizeof(rr->name)) {
			yyerror("rib name \"%s\" too long: max %zu",
			    name, sizeof(rr->name) - 1);
			free(rr);
			return (NULL);
		}
		rr->flags = F_RIB_NOFIB;
		SIMPLEQ_INSERT_TAIL(&ribnames, rr, entry);
	}
	return (rr);
}

struct rde_rib *
find_rib(char *name)
{
	struct rde_rib	*rr;

	SIMPLEQ_FOREACH(rr, &ribnames, entry) {
		if (!strcmp(rr->name, name))
			return (rr);
	}
	return (NULL);
}

int
rib_add_fib(struct rde_rib *rr, u_int rtableid)
{
	u_int	rdom;

	if (!ktable_exists(rtableid, &rdom)) {
		yyerror("rtable id %u does not exist", rtableid);
		return (-1);
	}
	/*
	 * conf->default_tableid is also a rdomain because that is checked
	 * in init_config()
	 */
	if (rdom != conf->default_tableid) {
		log_warnx("rtable %u does not belong to rdomain %u",
		    rtableid, conf->default_tableid);
		return (-1);
	}
	rr->rtableid = rtableid;
	rr->flags &= ~F_RIB_NOFIB;
	return (0);
}

struct prefixset *
find_prefixset(char *name, struct prefixset_head *p)
{
	struct prefixset *ps;

	SIMPLEQ_FOREACH(ps, p, entry) {
		if (!strcmp(ps->name, name))
			return (ps);
	}
	return (NULL);
}

int
get_id(struct peer *newpeer)
{
	static uint32_t id = PEER_ID_STATIC_MIN;
	struct peer	*p = NULL;

	/* check if the peer already existed before */
	if (newpeer->conf.remote_addr.aid) {
		/* neighbor */
		if (cur_peers)
			RB_FOREACH(p, peer_head, cur_peers)
				if (p->conf.remote_masklen ==
				    newpeer->conf.remote_masklen &&
				    memcmp(&p->conf.remote_addr,
				    &newpeer->conf.remote_addr,
				    sizeof(p->conf.remote_addr)) == 0)
					break;
		if (p) {
			newpeer->conf.id = p->conf.id;
			return (0);
		}
	} else {
		/* group */
		if (cur_peers)
			RB_FOREACH(p, peer_head, cur_peers)
				if (strcmp(p->conf.group,
				    newpeer->conf.group) == 0)
					break;
		if (p) {
			newpeer->conf.id = p->conf.groupid;
			return (0);
		}
	}

	/* else new one */
	if (id < PEER_ID_STATIC_MAX) {
		newpeer->conf.id = id++;
		return (0);
	}

	return (-1);
}

int
merge_prefixspec(struct filter_prefix *p, struct filter_prefixlen *pl)
{
	uint8_t max_len = 0;

	switch (p->addr.aid) {
	case AID_INET:
	case AID_VPN_IPv4:
		max_len = 32;
		break;
	case AID_INET6:
	case AID_VPN_IPv6:
		max_len = 128;
		break;
	}

	if (pl->op == OP_NONE) {
		p->len_min = p->len_max = p->len;
		return (0);
	}

	if (pl->len_min == -1)
		pl->len_min = p->len;
	if (pl->len_max == -1)
		pl->len_max = max_len;

	if (pl->len_max > max_len) {
		yyerror("prefixlen %d too big, limit %d",
		    pl->len_max, max_len);
		return (-1);
	}
	if (pl->len_min > pl->len_max) {
		yyerror("prefixlen %d too big, limit %d",
		    pl->len_min, pl->len_max);
		return (-1);
	}
	if (pl->len_min < p->len) {
		yyerror("prefixlen %d smaller than prefix, limit %d",
		    pl->len_min, p->len);
		return (-1);
	}

	p->op = pl->op;
	p->len_min = pl->len_min;
	p->len_max = pl->len_max;
	return (0);
}

int
expand_rule(struct filter_rule *rule, struct filter_rib_l *rib,
    struct filter_peers_l *peer, struct filter_match_l *match,
    struct filter_set_head *set)
{
	struct filter_rule	*r;
	struct filter_rib_l	*rb, *rbnext;
	struct filter_peers_l	*p, *pnext;
	struct filter_prefix_l	*prefix, *prefix_next;
	struct filter_as_l	*a, *anext;
	struct filter_set	*s;

	rb = rib;
	do {
		p = peer;
		do {
			a = match->as_l;
			do {
				prefix = match->prefix_l;
				do {
					if ((r = calloc(1,
					    sizeof(struct filter_rule))) ==
						 NULL) {
						log_warn("expand_rule");
						return (-1);
					}

					memcpy(r, rule, sizeof(*r));
					memcpy(&r->match, match,
					    sizeof(struct filter_match));
					filterset_copy(set, &r->set);

					if (rb != NULL)
						strlcpy(r->rib, rb->name,
						    sizeof(r->rib));

					if (p != NULL)
						memcpy(&r->peer, &p->p,
						    sizeof(r->peer));

					if (prefix != NULL)
						memcpy(&r->match.prefix,
						    &prefix->p,
						    sizeof(r->match.prefix));

					if (a != NULL)
						memcpy(&r->match.as, &a->a,
						    sizeof(struct filter_as));

					TAILQ_INSERT_TAIL(filter_l, r, entry);

					if (prefix != NULL)
						prefix = prefix->next;
				} while (prefix != NULL);

				if (a != NULL)
					a = a->next;
			} while (a != NULL);

			if (p != NULL)
				p = p->next;
		} while (p != NULL);

		if (rb != NULL)
			rb = rb->next;
	} while (rb != NULL);

	for (rb = rib; rb != NULL; rb = rbnext) {
		rbnext = rb->next;
		free(rb);
	}

	for (p = peer; p != NULL; p = pnext) {
		pnext = p->next;
		free(p);
	}

	for (a = match->as_l; a != NULL; a = anext) {
		anext = a->next;
		free(a);
	}

	for (prefix = match->prefix_l; prefix != NULL; prefix = prefix_next) {
		prefix_next = prefix->next;
		free(prefix);
	}

	if (set != NULL) {
		while ((s = TAILQ_FIRST(set)) != NULL) {
			TAILQ_REMOVE(set, s, entry);
			free(s);
		}
		free(set);
	}

	return (0);
}

static int
h2i(char c)
{
	if (c >= '0' && c <= '9')
		return c - '0';
	else if (c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	else if (c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	else
		return -1;
}

int
str2key(char *s, char *dest, size_t max_len)
{
	size_t	i;

	if (strlen(s) / 2 > max_len) {
		yyerror("key too long");
		return (-1);
	}

	if (strlen(s) % 2) {
		yyerror("key must be of even length");
		return (-1);
	}

	for (i = 0; i < strlen(s) / 2; i++) {
		int hi, lo;

		hi = h2i(s[2 * i]);
		lo = h2i(s[2 * i + 1]);
		if (hi == -1 || lo == -1) {
			yyerror("key must be specified in hex");
			return (-1);
		}
		dest[i] = (hi << 4) | lo;
	}

	return (0);
}

int
neighbor_consistent(struct peer *p)
{
	struct bgpd_addr *local_addr;
	struct peer *xp;

	switch (p->conf.remote_addr.aid) {
	case AID_INET:
		local_addr = &p->conf.local_addr_v4;
		break;
	case AID_INET6:
		local_addr = &p->conf.local_addr_v6;
		break;
	default:
		yyerror("Bad address family for remote-addr");
		return (-1);
	}

	/* with any form of ipsec local-address is required */
	if ((p->auth_conf.method == AUTH_IPSEC_IKE_ESP ||
	    p->auth_conf.method == AUTH_IPSEC_IKE_AH ||
	    p->auth_conf.method == AUTH_IPSEC_MANUAL_ESP ||
	    p->auth_conf.method == AUTH_IPSEC_MANUAL_AH) &&
	    local_addr->aid == AID_UNSPEC) {
		yyerror("neighbors with any form of IPsec configured "
		    "need local-address to be specified");
		return (-1);
	}

	/* with static keying we need both directions */
	if ((p->auth_conf.method == AUTH_IPSEC_MANUAL_ESP ||
	    p->auth_conf.method == AUTH_IPSEC_MANUAL_AH) &&
	    (!p->auth_conf.spi_in || !p->auth_conf.spi_out)) {
		yyerror("with manual keyed IPsec, SPIs and keys "
		    "for both directions are required");
		return (-1);
	}

	if (!conf->as) {
		yyerror("AS needs to be given before neighbor definitions");
		return (-1);
	}

	/* set default values if they where undefined */
	p->conf.ebgp = (p->conf.remote_as != conf->as);
	if (p->conf.enforce_as == ENFORCE_AS_UNDEF)
		p->conf.enforce_as = p->conf.ebgp ?
		    ENFORCE_AS_ON : ENFORCE_AS_OFF;
	if (p->conf.enforce_local_as == ENFORCE_AS_UNDEF)
		p->conf.enforce_local_as = ENFORCE_AS_ON;

	if (p->conf.remote_as == 0 && !p->conf.template) {
		yyerror("peer AS may not be zero");
		return (-1);
	}

	/* EBGP neighbors are not allowed in route reflector clusters */
	if (p->conf.reflector_client && p->conf.ebgp) {
		yyerror("EBGP neighbors are not allowed in route "
		    "reflector clusters");
		return (-1);
	}

	/* BGP role and RFC 9234 role are only valid for EBGP neighbors */
	if (!p->conf.ebgp) {
		p->conf.role = ROLE_NONE;
		p->conf.capabilities.policy = 0;
	} else if (p->conf.role == ROLE_NONE) {
		/* no role, no policy capability */
		p->conf.capabilities.policy = 0;
	}

	/* check for duplicate peer definitions */
	RB_FOREACH(xp, peer_head, new_peers)
		if (xp->conf.remote_masklen ==
		    p->conf.remote_masklen &&
		    memcmp(&xp->conf.remote_addr,
		    &p->conf.remote_addr,
		    sizeof(p->conf.remote_addr)) == 0)
			break;
	if (xp != NULL) {
		char *descr = log_fmt_peer(&p->conf);
		yyerror("duplicate %s", descr);
		free(descr);
		return (-1);
	}

	return (0);
}

static void
filterset_add(struct filter_set_head *sh, struct filter_set *s)
{
	struct filter_set	*t;

	TAILQ_FOREACH(t, sh, entry) {
		if (s->type < t->type) {
			TAILQ_INSERT_BEFORE(t, s, entry);
			return;
		}
		if (s->type == t->type) {
			switch (s->type) {
			case ACTION_SET_COMMUNITY:
			case ACTION_DEL_COMMUNITY:
				switch (cmpcommunity(&s->action.community,
				    &t->action.community)) {
				case -1:
					TAILQ_INSERT_BEFORE(t, s, entry);
					return;
				case 0:
					break;
				case 1:
					continue;
				}
				break;
			case ACTION_SET_NEXTHOP:
				/* only last nexthop per AF matters */
				if (s->action.nexthop.aid <
				    t->action.nexthop.aid) {
					TAILQ_INSERT_BEFORE(t, s, entry);
					return;
				} else if (s->action.nexthop.aid ==
				    t->action.nexthop.aid) {
					t->action.nexthop = s->action.nexthop;
					break;
				}
				continue;
			case ACTION_SET_NEXTHOP_BLACKHOLE:
			case ACTION_SET_NEXTHOP_REJECT:
			case ACTION_SET_NEXTHOP_NOMODIFY:
			case ACTION_SET_NEXTHOP_SELF:
				/* set it only once */
				break;
			case ACTION_SET_LOCALPREF:
			case ACTION_SET_MED:
			case ACTION_SET_WEIGHT:
				/* only last set matters */
				t->action.metric = s->action.metric;
				break;
			case ACTION_SET_RELATIVE_LOCALPREF:
			case ACTION_SET_RELATIVE_MED:
			case ACTION_SET_RELATIVE_WEIGHT:
				/* sum all relative numbers */
				t->action.relative += s->action.relative;
				break;
			case ACTION_SET_ORIGIN:
				/* only last set matters */
				t->action.origin = s->action.origin;
				break;
			case ACTION_PFTABLE:
				/* only last set matters */
				strlcpy(t->action.pftable, s->action.pftable,
				    sizeof(t->action.pftable));
				break;
			case ACTION_RTLABEL:
				/* only last set matters */
				strlcpy(t->action.rtlabel, s->action.rtlabel,
				    sizeof(t->action.rtlabel));
				break;
			default:
				break;
			}
			free(s);
			return;
		}
	}

	TAILQ_INSERT_TAIL(sh, s, entry);
}

int
merge_filterset(struct filter_set_head *sh, struct filter_set *s)
{
	struct filter_set	*t;

	TAILQ_FOREACH(t, sh, entry) {
		/*
		 * need to cycle across the full list because even
		 * if types are not equal filterset_cmp() may return 0.
		 */
		if (filterset_cmp(s, t) == 0) {
			if (s->type == ACTION_SET_COMMUNITY)
				yyerror("community is already set");
			else if (s->type == ACTION_DEL_COMMUNITY)
				yyerror("community will already be deleted");
			else
				yyerror("redefining set parameter %s",
				    filterset_name(s->type));
			return (-1);
		}
	}

	filterset_add(sh, s);
	return (0);
}

static int
filter_equal(struct filter_rule *fa, struct filter_rule *fb)
{
	if (fa == NULL || fb == NULL)
		return 0;
	if (fa->action != fb->action || fa->quick != fb->quick ||
	    fa->dir != fb->dir)
		return 0;
	if (memcmp(&fa->peer, &fb->peer, sizeof(fa->peer)))
		return 0;
	if (memcmp(&fa->match, &fb->match, sizeof(fa->match)))
		return 0;

	return 1;
}

/* do a basic optimization by folding equal rules together */
void
optimize_filters(struct filter_head *fh)
{
	struct filter_rule *r, *nr;

	TAILQ_FOREACH_SAFE(r, fh, entry, nr) {
		while (filter_equal(r, nr)) {
			struct filter_set	*t;

			while ((t = TAILQ_FIRST(&nr->set)) != NULL) {
				TAILQ_REMOVE(&nr->set, t, entry);
				filterset_add(&r->set, t);
			}

			TAILQ_REMOVE(fh, nr, entry);
			free(nr);
			nr = TAILQ_NEXT(r, entry);
		}
	}
}

struct filter_rule *
get_rule(enum action_types type)
{
	struct filter_rule	*r;
	int			 out;

	switch (type) {
	case ACTION_SET_PREPEND_SELF:
	case ACTION_SET_NEXTHOP_NOMODIFY:
	case ACTION_SET_NEXTHOP_SELF:
		out = 1;
		break;
	default:
		out = 0;
		break;
	}
	r = (curpeer == curgroup) ? curgroup_filter[out] : curpeer_filter[out];
	if (r == NULL) {
		if ((r = calloc(1, sizeof(struct filter_rule))) == NULL)
			fatal(NULL);
		r->quick = 0;
		r->dir = out ? DIR_OUT : DIR_IN;
		r->action = ACTION_NONE;
		TAILQ_INIT(&r->set);
		if (curpeer == curgroup) {
			/* group */
			r->peer.groupid = curgroup->conf.id;
			curgroup_filter[out] = r;
		} else {
			/* peer */
			r->peer.peerid = curpeer->conf.id;
			curpeer_filter[out] = r;
		}
	}
	return (r);
}

struct set_table *curset;
static int
new_as_set(char *name)
{
	struct as_set *aset;

	if (as_sets_lookup(&conf->as_sets, name) != NULL) {
		yyerror("as-set \"%s\" already exists", name);
		return -1;
	}

	aset = as_sets_new(&conf->as_sets, name, 0, sizeof(uint32_t));
	if (aset == NULL)
		fatal(NULL);

	curset = aset->set;
	return 0;
}

static void
add_as_set(uint32_t as)
{
	if (curset == NULL)
		fatalx("%s: bad mojo jojo", __func__);

	if (set_add(curset, &as, 1) != 0)
		fatal(NULL);
}

static void
done_as_set(void)
{
	curset = NULL;
}

static struct prefixset *
new_prefix_set(char *name, int is_roa)
{
	const char *type = "prefix-set";
	struct prefixset_head *sets = &conf->prefixsets;
	struct prefixset *pset;

	if (is_roa) {
		type = "origin-set";
		sets = &conf->originsets;
	}

	if (find_prefixset(name, sets) != NULL) {
		yyerror("%s \"%s\" already exists", type, name);
		return NULL;
	}
	if ((pset = calloc(1, sizeof(*pset))) == NULL)
		fatal("prefixset");
	if (strlcpy(pset->name, name, sizeof(pset->name)) >=
	    sizeof(pset->name)) {
		yyerror("%s \"%s\" too long: max %zu", type,
		    name, sizeof(pset->name) - 1);
		free(pset);
		return NULL;
	}
	RB_INIT(&pset->psitems);
	RB_INIT(&pset->roaitems);
	return pset;
}

static void
add_roa_set(struct prefixset_item *npsi, uint32_t as, uint8_t max,
    time_t expires)
{
	struct roa *roa, *r;

	if ((roa = calloc(1, sizeof(*roa))) == NULL)
		fatal("add_roa_set");

	roa->aid = npsi->p.addr.aid;
	roa->prefixlen = npsi->p.len;
	roa->maxlen = max;
	roa->asnum = as;
	roa->expires = expires;
	switch (roa->aid) {
	case AID_INET:
		roa->prefix.inet = npsi->p.addr.v4;
		break;
	case AID_INET6:
		roa->prefix.inet6 = npsi->p.addr.v6;
		break;
	default:
		fatalx("Bad address family for roa_set address");
	}

	r = RB_INSERT(roa_tree, curroatree, roa);
	if (r != NULL) {
		/* just ignore duplicates */
		if (r->expires != 0 && expires != 0 && expires > r->expires)
			r->expires = expires;
		free(roa);
	}
}

static struct rtr_config *
get_rtr(struct bgpd_addr *addr)
{
	struct rtr_config *n;

	n = calloc(1, sizeof(*n));
	if (n == NULL) {
		yyerror("out of memory");
		return NULL;
	}

	n->remote_addr = *addr;
	strlcpy(n->descr, log_addr(addr), sizeof(currtr->descr));

	return n;
}

static int
insert_rtr(struct rtr_config *new)
{
	static uint32_t id;
	struct rtr_config *r;

	if (id == UINT32_MAX) {
		yyerror("out of rtr session IDs");
		return -1;
	}

	SIMPLEQ_FOREACH(r, &conf->rtrs, entry)
		if (memcmp(&r->remote_addr, &new->remote_addr,
		    sizeof(r->remote_addr)) == 0 &&
		    r->remote_port == new->remote_port) {
			yyerror("duplicate rtr session to %s:%u",
			    log_addr(&new->remote_addr), new->remote_port);
			return -1;
		}

	if (cur_rtrs)
		SIMPLEQ_FOREACH(r, cur_rtrs, entry)
			if (memcmp(&r->remote_addr, &new->remote_addr,
			    sizeof(r->remote_addr)) == 0 &&
			    r->remote_port == new->remote_port) {
				new->id = r->id;
				break;
			}

	if (new->id == 0)
		new->id = ++id;

	SIMPLEQ_INSERT_TAIL(&conf->rtrs, new, entry);

	return 0;
}

static int
merge_aspa_set(uint32_t as, struct aspa_tas_l *tas, time_t expires)
{
	struct aspa_set	*aspa, needle = { .as = as };
	uint32_t i, num, *newtas;

	aspa = RB_FIND(aspa_tree, &conf->aspa, &needle);
	if (aspa == NULL) {
		if ((aspa = calloc(1, sizeof(*aspa))) == NULL) {
			yyerror("out of memory");
			return -1;
		}
		aspa->as = as;
		aspa->expires = expires;
		RB_INSERT(aspa_tree, &conf->aspa, aspa);
	}

	if (MAX_ASPA_SPAS_COUNT - aspa->num <= tas->num) {
		yyerror("too many providers for customer-as %u", as);
		return -1;
	}
	num = aspa->num + tas->num;
	newtas = recallocarray(aspa->tas, aspa->num, num, sizeof(uint32_t));
	if (newtas == NULL) {
		yyerror("out of memory");
		return -1;
	}
	/* fill starting at the end since the tas list is reversed */
	if (num > 0) {
		for (i = num - 1; tas; tas = tas->next, i--)
			newtas[i] = tas->as;
	}

	aspa->num = num;
	aspa->tas = newtas;

	/* take the longest expiry time, same logic as for ROA entries */
	if (aspa->expires != 0 && expires != 0 && expires > aspa->expires)
		aspa->expires = expires;

	return 0;
}

static int
kw_casecmp(const void *k, const void *e)
{
	return (strcasecmp(k, ((const struct keywords *)e)->k_name));
}

static int
map_tos(char *s, int *val)
{
	/* DiffServ Codepoints and other TOS mappings */
	const struct keywords	 toswords[] = {
		{ "af11",		IPTOS_DSCP_AF11 },
		{ "af12",		IPTOS_DSCP_AF12 },
		{ "af13",		IPTOS_DSCP_AF13 },
		{ "af21",		IPTOS_DSCP_AF21 },
		{ "af22",		IPTOS_DSCP_AF22 },
		{ "af23",		IPTOS_DSCP_AF23 },
		{ "af31",		IPTOS_DSCP_AF31 },
		{ "af32",		IPTOS_DSCP_AF32 },
		{ "af33",		IPTOS_DSCP_AF33 },
		{ "af41",		IPTOS_DSCP_AF41 },
		{ "af42",		IPTOS_DSCP_AF42 },
		{ "af43",		IPTOS_DSCP_AF43 },
		{ "critical",		IPTOS_PREC_CRITIC_ECP },
		{ "cs0",		IPTOS_DSCP_CS0 },
		{ "cs1",		IPTOS_DSCP_CS1 },
		{ "cs2",		IPTOS_DSCP_CS2 },
		{ "cs3",		IPTOS_DSCP_CS3 },
		{ "cs4",		IPTOS_DSCP_CS4 },
		{ "cs5",		IPTOS_DSCP_CS5 },
		{ "cs6",		IPTOS_DSCP_CS6 },
		{ "cs7",		IPTOS_DSCP_CS7 },
		{ "ef",			IPTOS_DSCP_EF },
		{ "inetcontrol",	IPTOS_PREC_INTERNETCONTROL },
		{ "lowdelay",		IPTOS_LOWDELAY },
		{ "netcontrol",		IPTOS_PREC_NETCONTROL },
		{ "reliability",	IPTOS_RELIABILITY },
		{ "throughput",		IPTOS_THROUGHPUT },
		{ "va",			IPTOS_DSCP_VA },
	};
	const struct keywords	*p;

	p = bsearch(s, toswords, nitems(toswords), sizeof(toswords[0]),
	    kw_casecmp);

	if (p) {
		*val = p->k_val;
		return (1);
	}
	return (0);
}

static int
getservice(char *n)
{
	struct servent	*s;

	s = getservbyname(n, "tcp");
	if (s == NULL)
		s = getservbyname(n, "udp");
	if (s == NULL)
		return -1;
	return s->s_port;
}

static int
parse_flags(char *s)
{
	const char *flags = FLOWSPEC_TCP_FLAG_STRING;
	char *p, *q;
	uint8_t f = 0;

	if (curflow->type == FLOWSPEC_TYPE_FRAG) {
		if (curflow->aid == AID_INET)
			flags = FLOWSPEC_FRAG_STRING4;
		else
			flags = FLOWSPEC_FRAG_STRING6;
	}

	for (p = s; *p; p++) {
		if ((q = strchr(flags, *p)) == NULL)
			return -1;
		f |= 1 << (q - flags);
	}
	return (f ? f : 0xff);
}

static void
component_finish(int type, uint8_t *data, int len)
{
	uint8_t *last;
	int i;

	switch (type) {
	case FLOWSPEC_TYPE_DEST:
	case FLOWSPEC_TYPE_SOURCE:
		/* nothing to do */
		return;
	default:
		break;
	}

	i = 0;
	do {
		last = data + i;
		i += FLOWSPEC_OP_LEN(*last) + 1;
	} while (i < len);
	*last |= FLOWSPEC_OP_EOL;
}

static struct flowspec_config *
flow_to_flowspec(struct flowspec_context *ctx)
{
	struct flowspec_config *f;
	int i, len = 0;
	uint8_t aid;

	switch (ctx->aid) {
	case AID_INET:
		aid = AID_FLOWSPECv4;
		break;
	case AID_INET6:
		aid = AID_FLOWSPECv6;
		break;
	default:
		return NULL;
	}

	for (i = FLOWSPEC_TYPE_MIN; i < FLOWSPEC_TYPE_MAX; i++)
		if (ctx->components[i] != NULL)
			len += ctx->complen[i] + 1;

	f = flowspec_alloc(aid, len);
	if (f == NULL)
		return NULL;

	len = 0;
	for (i = FLOWSPEC_TYPE_MIN; i < FLOWSPEC_TYPE_MAX; i++)
		if (ctx->components[i] != NULL) {
			f->flow->data[len++] = i;
			component_finish(i, ctx->components[i],
			    ctx->complen[i]);
			memcpy(f->flow->data + len, ctx->components[i],
			    ctx->complen[i]);
			len += ctx->complen[i];
		}

	return f;
}

static void
flow_free(struct flowspec_context *ctx)
{
	int i;

	for (i = 0; i < FLOWSPEC_TYPE_MAX; i++)
		free(ctx->components[i]);
	free(ctx);
}

static int
push_prefix(struct bgpd_addr *addr, uint8_t len)
{
	void *data;
	uint8_t *comp;
	int complen, l;

	if (curflow->components[curflow->addr_type] != NULL) {
		yyerror("flowspec address already set");
		return -1;
	}

	if (curflow->aid != addr->aid) {
		yyerror("wrong address family for flowspec address");
		return -1;
	}

	switch (curflow->aid) {
	case AID_INET:
		complen = PREFIX_SIZE(len);
		data = &addr->v4;
		break;
	case AID_INET6:
		/* IPv6 includes an offset byte */
		complen = PREFIX_SIZE(len) + 1;
		data = &addr->v6;
		break;
	default:
		yyerror("unsupported address family for flowspec address");
		return -1;
	}
	comp = malloc(complen);
	if (comp == NULL) {
		yyerror("out of memory");
		return -1;
	}

	l = 0;
	comp[l++] = len;
	if (curflow->aid == AID_INET6)
		comp[l++] = 0;
	memcpy(comp + l, data, complen - l);

	curflow->complen[curflow->addr_type] = complen;
	curflow->components[curflow->addr_type] = comp;

	return 0;
}

static int
push_binop(uint8_t binop, long long val)
{
	uint8_t *comp;
	int complen;
	uint8_t u8;

	if (val < 0 || val > 0xff) {
		yyerror("unsupported value for flowspec bin_op");
		return -1;
	}
	u8 = val;

	complen = curflow->complen[curflow->type];
	comp = realloc(curflow->components[curflow->type],
	    complen + 2);
	if (comp == NULL) {
		yyerror("out of memory");
		return -1;
	}

	comp[complen++] = binop;
	comp[complen++] = u8;
	curflow->complen[curflow->type] = complen;
	curflow->components[curflow->type] = comp;

	return 0;
}

static uint8_t
component_numop(enum comp_ops op, int and, int len)
{
	uint8_t flag = 0;

	switch (op) {
	case OP_EQ:
		flag |= FLOWSPEC_OP_NUM_EQ;
		break;
	case OP_NE:
		flag |= FLOWSPEC_OP_NUM_NOT;
		break;
	case OP_LE:
		flag |= FLOWSPEC_OP_NUM_LE;
		break;
	case OP_LT:
		flag |= FLOWSPEC_OP_NUM_LT;
		break;
	case OP_GE:
		flag |= FLOWSPEC_OP_NUM_GE;
		break;
	case OP_GT:
		flag |= FLOWSPEC_OP_NUM_GT;
		break;
	default:
		fatalx("unsupported op");
	}

	switch (len) {
	case 2:
		flag |= 1 << FLOWSPEC_OP_LEN_SHIFT;
		break;
	case 4:
		flag |= 2 << FLOWSPEC_OP_LEN_SHIFT;
		break;
	case 8:
		flag |= 3 << FLOWSPEC_OP_LEN_SHIFT;
		break;
	}

	if (and)
		flag |= FLOWSPEC_OP_AND;

	return flag;
}

static int
push_numop(enum comp_ops op, int and, long long val)
{
	uint8_t *comp;
	void *data;
	uint32_t u32;
	uint16_t u16;
	uint8_t u8;
	int len, complen;

	if (val < 0 || val > 0xffffffff) {
		yyerror("unsupported value for flowspec num_op");
		return -1;
	} else if (val <= 255) {
		len = 1;
		u8 = val;
		data = &u8;
	} else if (val <= 0xffff) {
		len = 2;
		u16 = htons(val);
		data = &u16;
	} else {
		len = 4;
		u32 = htonl(val);
		data = &u32;
	}

	complen = curflow->complen[curflow->type];
	comp = realloc(curflow->components[curflow->type],
	    complen + len + 1);
	if (comp == NULL) {
		yyerror("out of memory");
		return -1;
	}

	comp[complen++] = component_numop(op, and, len);
	memcpy(comp + complen, data, len);
	complen += len;
	curflow->complen[curflow->type] = complen;
	curflow->components[curflow->type] = comp;

	return 0;
}

static int
push_unary_numop(enum comp_ops op, long long val)
{
	return push_numop(op, 0, val);
}

static int
push_binary_numop(enum comp_ops op, long long min, long long max)
{
	switch (op) {
	case OP_RANGE:
		if (push_numop(OP_GE, 0, min) == -1)
			return -1;
		return push_numop(OP_LE, 1, max);
	case OP_XRANGE:
		if (push_numop(OP_LT, 0, min) == -1)
			return -1;
		return push_numop(OP_GT, 0, max);
	default:
		yyerror("unsupported binary flowspec num_op");
		return -1;
	}
}

struct icmptypeent {
	const char *name;
	u_int8_t type;
};

struct icmpcodeent {
	const char *name;
	u_int8_t type;
	u_int8_t code;
};

static const struct icmptypeent icmp_type[] = {
	{ "echoreq",	ICMP_ECHO },
	{ "echorep",	ICMP_ECHOREPLY },
	{ "unreach",	ICMP_UNREACH },
	{ "squench",	ICMP_SOURCEQUENCH },
	{ "redir",	ICMP_REDIRECT },
	{ "althost",	ICMP_ALTHOSTADDR },
	{ "routeradv",	ICMP_ROUTERADVERT },
	{ "routersol",	ICMP_ROUTERSOLICIT },
	{ "timex",	ICMP_TIMXCEED },
	{ "paramprob",	ICMP_PARAMPROB },
	{ "timereq",	ICMP_TSTAMP },
	{ "timerep",	ICMP_TSTAMPREPLY },
	{ "inforeq",	ICMP_IREQ },
	{ "inforep",	ICMP_IREQREPLY },
	{ "maskreq",	ICMP_MASKREQ },
	{ "maskrep",	ICMP_MASKREPLY },
	{ "trace",	ICMP_TRACEROUTE },
	{ "dataconv",	ICMP_DATACONVERR },
	{ "mobredir",	ICMP_MOBILE_REDIRECT },
	{ "ipv6-where",	ICMP_IPV6_WHEREAREYOU },
	{ "ipv6-here",	ICMP_IPV6_IAMHERE },
	{ "mobregreq",	ICMP_MOBILE_REGREQUEST },
	{ "mobregrep",	ICMP_MOBILE_REGREPLY },
	{ "skip",	ICMP_SKIP },
	{ "photuris",	ICMP_PHOTURIS },
};

static const struct icmptypeent icmp6_type[] = {
	{ "unreach",	ICMP6_DST_UNREACH },
	{ "toobig",	ICMP6_PACKET_TOO_BIG },
	{ "timex",	ICMP6_TIME_EXCEEDED },
	{ "paramprob",	ICMP6_PARAM_PROB },
	{ "echoreq",	ICMP6_ECHO_REQUEST },
	{ "echorep",	ICMP6_ECHO_REPLY },
	{ "groupqry",	ICMP6_MEMBERSHIP_QUERY },
	{ "listqry",	MLD_LISTENER_QUERY },
	{ "grouprep",	ICMP6_MEMBERSHIP_REPORT },
	{ "listenrep",	MLD_LISTENER_REPORT },
	{ "groupterm",	ICMP6_MEMBERSHIP_REDUCTION },
	{ "listendone", MLD_LISTENER_DONE },
	{ "routersol",	ND_ROUTER_SOLICIT },
	{ "routeradv",	ND_ROUTER_ADVERT },
	{ "neighbrsol", ND_NEIGHBOR_SOLICIT },
	{ "neighbradv", ND_NEIGHBOR_ADVERT },
	{ "redir",	ND_REDIRECT },
	{ "routrrenum", ICMP6_ROUTER_RENUMBERING },
	{ "wrureq",	ICMP6_WRUREQUEST },
	{ "wrurep",	ICMP6_WRUREPLY },
	{ "fqdnreq",	ICMP6_FQDN_QUERY },
	{ "fqdnrep",	ICMP6_FQDN_REPLY },
	{ "niqry",	ICMP6_NI_QUERY },
	{ "nirep",	ICMP6_NI_REPLY },
	{ "mtraceresp",	MLD_MTRACE_RESP },
	{ "mtrace",	MLD_MTRACE },
	{ "listenrepv2", MLDV2_LISTENER_REPORT },
};

static const struct icmpcodeent icmp_code[] = {
	{ "net-unr",		ICMP_UNREACH,	ICMP_UNREACH_NET },
	{ "host-unr",		ICMP_UNREACH,	ICMP_UNREACH_HOST },
	{ "proto-unr",		ICMP_UNREACH,	ICMP_UNREACH_PROTOCOL },
	{ "port-unr",		ICMP_UNREACH,	ICMP_UNREACH_PORT },
	{ "needfrag",		ICMP_UNREACH,	ICMP_UNREACH_NEEDFRAG },
	{ "srcfail",		ICMP_UNREACH,	ICMP_UNREACH_SRCFAIL },
	{ "net-unk",		ICMP_UNREACH,	ICMP_UNREACH_NET_UNKNOWN },
	{ "host-unk",		ICMP_UNREACH,	ICMP_UNREACH_HOST_UNKNOWN },
	{ "isolate",		ICMP_UNREACH,	ICMP_UNREACH_ISOLATED },
	{ "net-prohib",		ICMP_UNREACH,	ICMP_UNREACH_NET_PROHIB },
	{ "host-prohib",	ICMP_UNREACH,	ICMP_UNREACH_HOST_PROHIB },
	{ "net-tos",		ICMP_UNREACH,	ICMP_UNREACH_TOSNET },
	{ "host-tos",		ICMP_UNREACH,	ICMP_UNREACH_TOSHOST },
	{ "filter-prohib",	ICMP_UNREACH,	ICMP_UNREACH_FILTER_PROHIB },
	{ "host-preced",	ICMP_UNREACH,	ICMP_UNREACH_HOST_PRECEDENCE },
	{ "cutoff-preced",	ICMP_UNREACH,	ICMP_UNREACH_PRECEDENCE_CUTOFF },
	{ "redir-net",		ICMP_REDIRECT,	ICMP_REDIRECT_NET },
	{ "redir-host",		ICMP_REDIRECT,	ICMP_REDIRECT_HOST },
	{ "redir-tos-net",	ICMP_REDIRECT,	ICMP_REDIRECT_TOSNET },
	{ "redir-tos-host",	ICMP_REDIRECT,	ICMP_REDIRECT_TOSHOST },
	{ "normal-adv",		ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL },
	{ "common-adv",		ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON },
	{ "transit",		ICMP_TIMXCEED,	ICMP_TIMXCEED_INTRANS },
	{ "reassemb",		ICMP_TIMXCEED,	ICMP_TIMXCEED_REASS },
	{ "badhead",		ICMP_PARAMPROB,	ICMP_PARAMPROB_ERRATPTR },
	{ "optmiss",		ICMP_PARAMPROB,	ICMP_PARAMPROB_OPTABSENT },
	{ "badlen",		ICMP_PARAMPROB,	ICMP_PARAMPROB_LENGTH },
	{ "unknown-ind",	ICMP_PHOTURIS,	ICMP_PHOTURIS_UNKNOWN_INDEX },
	{ "auth-fail",		ICMP_PHOTURIS,	ICMP_PHOTURIS_AUTH_FAILED },
	{ "decrypt-fail",	ICMP_PHOTURIS,	ICMP_PHOTURIS_DECRYPT_FAILED },
};

static const struct icmpcodeent icmp6_code[] = {
	{ "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN },
	{ "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE },
	{ "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE },
	{ "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR },
	{ "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT },
	{ "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT },
	{ "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY },
	{ "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER },
	{ "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER },
	{ "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK },
	{ "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER },
};

static int
geticmptypebyname(char *w, uint8_t aid)
{
	size_t	i;

	switch (aid) {
	case AID_INET:
		for (i = 0; i < nitems(icmp_type); i++) {
			if (!strcmp(w, icmp_type[i].name))
				return (icmp_type[i].type);
		}
		break;
	case AID_INET6:
		for (i = 0; i < nitems(icmp6_type); i++) {
			if (!strcmp(w, icmp6_type[i].name))
				return (icmp6_type[i].type);
		}
		break;
	}
	return -1;
}

static int
geticmpcodebyname(u_long type, char *w, uint8_t aid)
{
	size_t	i;

	switch (aid) {
	case AID_INET:
		for (i = 0; i < nitems(icmp_code); i++) {
			if (type == icmp_code[i].type &&
			    !strcmp(w, icmp_code[i].name))
				return (icmp_code[i].code);
		}
		break;
	case AID_INET6:
		for (i = 0; i < nitems(icmp6_code); i++) {
			if (type == icmp6_code[i].type &&
			    !strcmp(w, icmp6_code[i].name))
				return (icmp6_code[i].code);
		}
		break;
	}
	return -1;
}

static int
merge_auth_conf(struct auth_config *to, struct auth_config *from)
{
	if (to->method != 0) {
		/* extra magic for manual ipsec rules */
		if (to->method == from->method &&
		    (to->method == AUTH_IPSEC_MANUAL_ESP ||
		    to->method == AUTH_IPSEC_MANUAL_AH)) {
			if (to->spi_in == 0 && from->spi_in != 0) {
				to->spi_in = from->spi_in;
				to->auth_alg_in = from->auth_alg_in;
				to->enc_alg_in = from->enc_alg_in;
				memcpy(to->enc_key_in, from->enc_key_in,
				    sizeof(to->enc_key_in));
				to->enc_keylen_in = from->enc_keylen_in;
				to->auth_keylen_in = from->auth_keylen_in;
				return 1;
			} else if (to->spi_out == 0 && from->spi_out != 0) {
				to->spi_out = from->spi_out;
				to->auth_alg_out = from->auth_alg_out;
				to->enc_alg_out = from->enc_alg_out;
				memcpy(to->enc_key_out, from->enc_key_out,
				    sizeof(to->enc_key_out));
				to->enc_keylen_out = from->enc_keylen_out;
				to->auth_keylen_out = from->auth_keylen_out;
				return 1;
			}
		}
		yyerror("auth method cannot be redefined");
		return 0;
	}
	*to = *from;
	return 1;
}

