root/sbin/ipsecctl/parse.y
/*      $OpenBSD: parse.y,v 1.184 2025/04/30 03:54:09 tb 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) 2004, 2005 Hans-Joerg Hoexer <hshoexer@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/ioctl.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip_ipsp.h>
#include <arpa/inet.h>

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

#include "ipsecctl.h"

TAILQ_HEAD(files, file)          files = TAILQ_HEAD_INITIALIZER(files);
static struct file {
        TAILQ_ENTRY(file)        entry;
        FILE                    *stream;
        char                    *name;
        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              yywarn(const char *, ...)
    __attribute__((__format__ (printf, 1, 2)))
    __attribute__((__nonnull__ (1)));
int              kw_cmp(const void *, const void *);
int              lookup(char *);
int              lgetc(int);
int              lungetc(int);
int              findeol(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 *);
int              cmdline_symset(char *);

#define KEYSIZE_LIMIT   1024

static struct ipsecctl  *ipsec = NULL;
static int               debug = 0;

const struct ipsec_xf authxfs[] = {
        { "unknown",            AUTHXF_UNKNOWN,         0,      0 },
        { "none",               AUTHXF_NONE,            0,      0 },
        { "hmac-md5",           AUTHXF_HMAC_MD5,        16,     0 },
        { "hmac-ripemd160",     AUTHXF_HMAC_RIPEMD160,  20,     0 },
        { "hmac-sha1",          AUTHXF_HMAC_SHA1,       20,     0 },
        { "hmac-sha2-256",      AUTHXF_HMAC_SHA2_256,   32,     0 },
        { "hmac-sha2-384",      AUTHXF_HMAC_SHA2_384,   48,     0 },
        { "hmac-sha2-512",      AUTHXF_HMAC_SHA2_512,   64,     0 },
        { NULL,                 0,                      0,      0 },
};

const struct ipsec_xf encxfs[] = {
        { "unknown",            ENCXF_UNKNOWN,          0,      0,      0, 0 },
        { "none",               ENCXF_NONE,             0,      0,      0, 0 },
        { "3des-cbc",           ENCXF_3DES_CBC,         24,     24,     0, 0 },
        { "aes",                ENCXF_AES,              16,     32,     0, 0 },
        { "aes-128",            ENCXF_AES_128,          16,     16,     0, 0 },
        { "aes-192",            ENCXF_AES_192,          24,     24,     0, 0 },
        { "aes-256",            ENCXF_AES_256,          32,     32,     0, 0 },
        { "aesctr",             ENCXF_AESCTR,           16+4,   32+4,   0, 1 },
        { "aes-128-ctr",        ENCXF_AES_128_CTR,      16+4,   16+4,   0, 1 },
        { "aes-192-ctr",        ENCXF_AES_192_CTR,      24+4,   24+4,   0, 1 },
        { "aes-256-ctr",        ENCXF_AES_256_CTR,      32+4,   32+4,   0, 1 },
        { "aes-128-gcm",        ENCXF_AES_128_GCM,      16+4,   16+4,   1, 1 },
        { "aes-192-gcm",        ENCXF_AES_192_GCM,      24+4,   24+4,   1, 1 },
        { "aes-256-gcm",        ENCXF_AES_256_GCM,      32+4,   32+4,   1, 1 },
        { "aes-128-gmac",       ENCXF_AES_128_GMAC,     16+4,   16+4,   1, 1 },
        { "aes-192-gmac",       ENCXF_AES_192_GMAC,     24+4,   24+4,   1, 1 },
        { "aes-256-gmac",       ENCXF_AES_256_GMAC,     32+4,   32+4,   1, 1 },
        { "blowfish",           ENCXF_BLOWFISH,         5,      56,     0, 0 },
        { "cast128",            ENCXF_CAST128,          5,      16,     0, 0 },
        { "chacha20-poly1305",  ENCXF_CHACHA20_POLY1305, 32+4,  32+4,   1, 1 },
        { "null",               ENCXF_NULL,             0,      0,      0, 0 },
        { NULL,                 0,                      0,      0,      0, 0 },
};

const struct ipsec_xf compxfs[] = {
        { "unknown",            COMPXF_UNKNOWN,         0,      0 },
        { "deflate",            COMPXF_DEFLATE,         0,      0 },
        { NULL,                 0,                      0,      0 },
};

const struct ipsec_xf groupxfs[] = {
        { "unknown",            GROUPXF_UNKNOWN,        0,      0 },
        { "none",               GROUPXF_NONE,           0,      0 },
        { "modp768",            GROUPXF_1,              768,    0 },
        { "grp1",               GROUPXF_1,              768,    0 },
        { "modp1024",           GROUPXF_2,              1024,   0 },
        { "grp2",               GROUPXF_2,              1024,   0 },
        { "modp1536",           GROUPXF_5,              1536,   0 },
        { "grp5",               GROUPXF_5,              1536,   0 },
        { "modp2048",           GROUPXF_14,             2048,   0 },
        { "grp14",              GROUPXF_14,             2048,   0 },
        { "modp3072",           GROUPXF_15,             3072,   0 },
        { "grp15",              GROUPXF_15,             3072,   0 },
        { "modp4096",           GROUPXF_16,             4096,   0 },
        { "grp16",              GROUPXF_16,             4096,   0 },
        { "modp6144",           GROUPXF_17,             6144,   0 },
        { "grp17",              GROUPXF_17,             6144,   0 },
        { "modp8192",           GROUPXF_18,             8192,   0 },
        { "grp18",              GROUPXF_18,             8192,   0 },
        { "ecp256",             GROUPXF_19,             256,    0 },
        { "grp19",              GROUPXF_19,             256,    0 },
        { "ecp384",             GROUPXF_20,             384,    0 },
        { "grp20",              GROUPXF_20,             384,    0 },
        { "ecp521",             GROUPXF_21,             521,    0 },
        { "grp21",              GROUPXF_21,             521,    0 },
        { "ecp224",             GROUPXF_26,             224,    0 },
        { "grp26",              GROUPXF_26,             224,    0 },
        { "bp224",              GROUPXF_27,             224,    0 },
        { "grp27",              GROUPXF_27,             224,    0 },
        { "bp256",              GROUPXF_28,             256,    0 },
        { "grp28",              GROUPXF_28,             256,    0 },
        { "bp384",              GROUPXF_29,             384,    0 },
        { "grp29",              GROUPXF_29,             384,    0 },
        { "bp512",              GROUPXF_30,             512,    0 },
        { "grp30",              GROUPXF_30,             512,    0 },
        { NULL,                 0,                      0,      0 },
};

int                      atoul(char *, u_long *);
int                      atospi(char *, u_int32_t *);
u_int8_t                 x2i(unsigned char *);
struct ipsec_key        *parsekey(unsigned char *, size_t);
struct ipsec_key        *parsekeyfile(char *);
struct ipsec_addr_wrap  *host(const char *);
struct ipsec_addr_wrap  *host_v6(const char *, int);
struct ipsec_addr_wrap  *host_v4(const char *, int);
struct ipsec_addr_wrap  *host_dns(const char *, int);
struct ipsec_addr_wrap  *host_if(const char *, int);
struct ipsec_addr_wrap  *host_any(void);
void                     ifa_load(void);
int                      ifa_exists(const char *);
struct ipsec_addr_wrap  *ifa_lookup(const char *ifa_name);
struct ipsec_addr_wrap  *ifa_grouplookup(const char *);
void                     set_ipmask(struct ipsec_addr_wrap *, u_int8_t);
const struct ipsec_xf   *parse_xf(const char *, const struct ipsec_xf *);
struct ipsec_lifetime   *parse_life(const char *);
struct ipsec_transforms *copytransforms(const struct ipsec_transforms *);
struct ipsec_lifetime   *copylife(const struct ipsec_lifetime *);
struct ipsec_auth       *copyipsecauth(const struct ipsec_auth *);
struct ike_auth         *copyikeauth(const struct ike_auth *);
struct ipsec_key        *copykey(struct ipsec_key *);
struct ipsec_addr_wrap  *copyhost(const struct ipsec_addr_wrap *);
char                    *copytag(const char *);
struct ipsec_rule       *copyrule(struct ipsec_rule *);
int                      validate_af(struct ipsec_addr_wrap *,
                             struct ipsec_addr_wrap *);
int                      validate_sa(u_int32_t, u_int8_t,
                             struct ipsec_transforms *, struct ipsec_key *,
                             struct ipsec_key *, u_int8_t);
struct ipsec_rule       *create_sa(u_int8_t, u_int8_t, struct ipsec_hosts *,
                             u_int32_t, u_int8_t, u_int16_t,
                             struct ipsec_transforms *,
                             struct ipsec_key *, struct ipsec_key *);
struct ipsec_rule       *reverse_sa(struct ipsec_rule *, u_int32_t,
                             struct ipsec_key *, struct ipsec_key *);
struct ipsec_rule       *create_sabundle(struct ipsec_addr_wrap *, u_int8_t,
                             u_int32_t, struct ipsec_addr_wrap *, u_int8_t,
                             u_int32_t);
struct ipsec_rule       *create_flow(u_int8_t, u_int8_t, struct ipsec_hosts *,
                             u_int8_t, char *, char *, u_int8_t);
int                      set_rule_peers(struct ipsec_rule *r,
                             struct ipsec_hosts *peers);
void                     expand_any(struct ipsec_addr_wrap *);
int                      expand_rule(struct ipsec_rule *, struct ipsec_hosts *,
                             u_int8_t, u_int32_t, struct ipsec_key *,
                             struct ipsec_key *, char *);
struct ipsec_rule       *reverse_rule(struct ipsec_rule *);
struct ipsec_rule       *create_ike(u_int8_t, struct ipsec_hosts *,
                             struct ike_mode *, struct ike_mode *, u_int8_t,
                             u_int8_t, u_int8_t, char *, char *,
                             struct ike_auth *, char *);
int                      add_sabundle(struct ipsec_rule *, char *);
int                      get_id_type(char *);

struct ipsec_transforms *ipsec_transforms;

typedef struct {
        union {
                int64_t          number;
                uint32_t         unit;
                u_int8_t         ikemode;
                u_int8_t         dir;
                u_int8_t         satype;        /* encapsulating prococol */
                u_int8_t         proto;         /* encapsulated protocol */
                u_int8_t         tmode;
                char            *string;
                u_int16_t        port;
                struct ipsec_hosts hosts;
                struct ipsec_hosts peers;
                struct ipsec_addr_wrap *anyhost;
                struct ipsec_addr_wrap *singlehost;
                struct ipsec_addr_wrap *host;
                struct {
                        char *srcid;
                        char *dstid;
                } ids;
                char            *id;
                u_int8_t         type;
                struct ike_auth  ikeauth;
                struct {
                        u_int32_t       spiout;
                        u_int32_t       spiin;
                } spis;
                struct {
                        u_int8_t        encap;
                        u_int16_t       port;
                } udpencap;
                struct {
                        struct ipsec_key *keyout;
                        struct ipsec_key *keyin;
                } authkeys;
                struct {
                        struct ipsec_key *keyout;
                        struct ipsec_key *keyin;
                } enckeys;
                struct {
                        struct ipsec_key *keyout;
                        struct ipsec_key *keyin;
                } keys;
                struct ipsec_transforms *transforms;
                struct ipsec_lifetime   *life;
                struct ike_mode         *mode;
        } v;
        int lineno;
} YYSTYPE;

%}

%token  FLOW FROM ESP AH IN PEER ON OUT TO SRCID DSTID RSA PSK TCPMD5 SPI
%token  AUTHKEY ENCKEY FILENAME AUTHXF ENCXF ERROR IKE MAIN QUICK AGGRESSIVE
%token  PASSIVE ACTIVE ANY IPIP IPCOMP COMPXF TUNNEL TRANSPORT DYNAMIC LIFETIME
%token  TYPE DENY BYPASS LOCAL PROTO USE ACQUIRE REQUIRE DONTACQ GROUP PORT TAG
%token  INCLUDE BUNDLE UDPENCAP INTERFACE
%token  <v.string>              STRING
%token  <v.number>              NUMBER
%type   <v.unit>                iface
%type   <v.string>              string
%type   <v.dir>                 dir
%type   <v.satype>              satype
%type   <v.proto>               proto
%type   <v.number>              protoval
%type   <v.tmode>               tmode
%type   <v.hosts>               hosts
%type   <v.port>                port
%type   <v.number>              portval
%type   <v.peers>               peers
%type   <v.anyhost>             anyhost
%type   <v.singlehost>          singlehost
%type   <v.host>                host host_list host_spec
%type   <v.ids>                 ids
%type   <v.id>                  id
%type   <v.spis>                spispec
%type   <v.udpencap>            udpencap
%type   <v.authkeys>            authkeyspec
%type   <v.enckeys>             enckeyspec
%type   <v.string>              bundlestring
%type   <v.keys>                keyspec
%type   <v.transforms>          transforms
%type   <v.ikemode>             ikemode
%type   <v.ikeauth>             ikeauth
%type   <v.type>                type
%type   <v.life>                lifetime
%type   <v.mode>                phase1mode phase2mode
%type   <v.string>              tag
%%

grammar         : /* empty */
                | grammar include '\n'
                | grammar '\n'
                | grammar ikerule '\n'
                | grammar flowrule '\n'
                | grammar sarule '\n'
                | grammar tcpmd5rule '\n'
                | grammar varset '\n'
                | grammar error '\n'            { file->errors++; }
                ;

comma           : ','
                | /* empty */
                ;

include         : INCLUDE STRING                {
                        struct file     *nfile;

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

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

tcpmd5rule      : TCPMD5 hosts spispec authkeyspec      {
                        struct ipsec_rule       *r;

                        r = create_sa(IPSEC_TCPMD5, IPSEC_TRANSPORT, &$2,
                            $3.spiout, 0, 0, NULL, $4.keyout, NULL);
                        if (r == NULL)
                                YYERROR;

                        if (expand_rule(r, NULL, 0, $3.spiin, $4.keyin, NULL,
                            NULL))
                                errx(1, "tcpmd5rule: expand_rule");
                }
                ;

sarule          : satype tmode hosts spispec udpencap transforms authkeyspec
                    enckeyspec bundlestring {
                        struct ipsec_rule       *r;

                        r = create_sa($1, $2, &$3, $4.spiout, $5.encap, $5.port,
                            $6, $7.keyout, $8.keyout);
                        if (r == NULL)
                                YYERROR;

                        if (expand_rule(r, NULL, 0, $4.spiin, $7.keyin,
                            $8.keyin, $9))
                                errx(1, "sarule: expand_rule");
                }
                ;

flowrule        : FLOW satype dir proto hosts peers ids type {
                        struct ipsec_rule       *r;

                        r = create_flow($3, $4, &$5, $2, $7.srcid,
                            $7.dstid, $8);
                        if (r == NULL)
                                YYERROR;

                        if (expand_rule(r, &$6, $3, 0, NULL, NULL, NULL))
                                errx(1, "flowrule: expand_rule");
                }
                ;

ikerule         : IKE ikemode satype tmode proto hosts peers
                    phase1mode phase2mode ids ikeauth tag {
                        struct ipsec_rule       *r;

                        r = create_ike($5, &$6, $8, $9, $3, $4, $2,
                            $10.srcid, $10.dstid, &$11, $12);
                        if (r == NULL)
                                YYERROR;

                        if (expand_rule(r, &$7, 0, 0, NULL, NULL, NULL))
                                errx(1, "ikerule: expand_rule");
                }

                /* ike interface sec0 local $h_self peer $h_s2s1 ... */
                | IKE ikemode iface peers
                    phase1mode phase2mode ids ikeauth {
                        uint8_t                  proto = 0; // IPPROTO_IPIP;
                        struct ipsec_hosts       hosts;
                        struct ike_mode         *phase1mode = $5;
                        struct ike_mode         *phase2mode = $6;
                        uint8_t                  satype = IPSEC_ESP;
                        uint8_t                  tmode = IPSEC_TUNNEL;
                        uint8_t                  mode = $2;
                        struct ike_auth         *authtype = &$8;
                        char                    *tag = NULL;

                        struct ipsec_rule       *r;

                        hosts.src = host_v4("0.0.0.0/0", 1);
                        hosts.sport = htons(0);
                        hosts.dst = host_v4("0.0.0.0/0", 1);
                        hosts.dport = htons(0);

                        r = create_ike(proto, &hosts, phase1mode, phase2mode,
                            satype, tmode, mode, $7.srcid, $7.dstid,
                            authtype, tag);
                        if (r == NULL) {
                                YYERROR;
                        }

                        r->flags |= IPSEC_RULE_F_IFACE;
                        r->iface = $3;

                        if (expand_rule(r, &$4, 0, 0, NULL, NULL, NULL))
                                errx(1, "ikerule: expand interface rule");

                }
                ;

satype          : /* empty */                   { $$ = IPSEC_ESP; }
                | ESP                           { $$ = IPSEC_ESP; }
                | AH                            { $$ = IPSEC_AH; }
                | IPCOMP                        { $$ = IPSEC_IPCOMP; }
                | IPIP                          { $$ = IPSEC_IPIP; }
                ;

proto           : /* empty */                   { $$ = 0; }
                | PROTO protoval                { $$ = $2; }
                | PROTO ESP                     { $$ = IPPROTO_ESP; }
                | PROTO AH                      { $$ = IPPROTO_AH; }
                ;

protoval        : STRING                        {
                        struct protoent *p;

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

tmode           : /* empty */                   { $$ = IPSEC_TUNNEL; }
                | TUNNEL                        { $$ = IPSEC_TUNNEL; }
                | TRANSPORT                     { $$ = IPSEC_TRANSPORT; }
                ;

dir             : /* empty */                   { $$ = IPSEC_INOUT; }
                | IN                            { $$ = IPSEC_IN; }
                | OUT                           { $$ = IPSEC_OUT; }
                ;

hosts           : FROM host port TO host port           {
                        struct ipsec_addr_wrap *ipa;
                        for (ipa = $5; ipa; ipa = ipa->next) {
                                if (ipa->srcnat) {
                                        yyerror("no flow NAT support for"
                                            " destination network: %s", ipa->name);
                                        YYERROR;
                                }
                        }
                        $$.src = $2;
                        $$.sport = $3;
                        $$.dst = $5;
                        $$.dport = $6;
                }
                | TO host port FROM host port           {
                        struct ipsec_addr_wrap *ipa;
                        for (ipa = $2; ipa; ipa = ipa->next) {
                                if (ipa->srcnat) {
                                        yyerror("no flow NAT support for"
                                            " destination network: %s", ipa->name);
                                        YYERROR;
                                }
                        }
                        $$.src = $5;
                        $$.sport = $6;
                        $$.dst = $2;
                        $$.dport = $3;
                }
                ;

port            : /* empty */                           { $$ = 0; }
                | PORT portval                          { $$ = $2; }
                ;

portval         : STRING                                {
                        struct servent *s;

                        if ((s = getservbyname($1, "tcp")) != NULL ||
                            (s = getservbyname($1, "udp")) != NULL) {
                                $$ = s->s_port;
                        } else {
                                yyerror("unknown port: %s", $1);
                                YYERROR;
                        }
                }
                | NUMBER                                {
                        if ($1 > USHRT_MAX || $1 < 0) {
                                yyerror("port outside range");
                                YYERROR;
                        }
                        $$ = htons($1);
                }
                ;

peers           : /* empty */                           {
                        $$.dst = NULL;
                        $$.src = NULL;
                }
                | PEER anyhost LOCAL singlehost         {
                        $$.dst = $2;
                        $$.src = $4;
                }
                | LOCAL singlehost PEER anyhost         {
                        $$.dst = $4;
                        $$.src = $2;
                }
                | PEER anyhost                          {
                        $$.dst = $2;
                        $$.src = NULL;
                }
                | LOCAL singlehost                      {
                        $$.dst = NULL;
                        $$.src = $2;
                }
                ;

anyhost         : singlehost                    { $$ = $1; }
                | ANY                           {
                        $$ = host_any();
                }

singlehost      : /* empty */                   { $$ = NULL; }
                | STRING                        {
                        if (($$ = host($1)) == NULL) {
                                free($1);
                                yyerror("could not parse host specification");
                                YYERROR;
                        }
                        free($1);
                }
                ;

host_list       : host                          { $$ = $1; }
                | host_list comma host          {
                        if ($3 == NULL)
                                $$ = $1;
                        else if ($1 == NULL)
                                $$ = $3;
                        else {
                                $1->tail->next = $3;
                                $1->tail = $3->tail;
                                $$ = $1;
                        }
                }
                ;

host_spec       : STRING                        {
                        if (($$ = host($1)) == NULL) {
                                free($1);
                                yyerror("could not parse host specification");
                                YYERROR;
                        }
                        free($1);
                }
                | STRING '/' NUMBER             {
                        char    *buf;

                        if (asprintf(&buf, "%s/%lld", $1, $3) == -1)
                                err(1, "host: asprintf");
                        free($1);
                        if (($$ = host(buf)) == NULL)   {
                                free(buf);
                                yyerror("could not parse host specification");
                                YYERROR;
                        }
                        free(buf);
                }
                ;

host            : host_spec                     { $$ = $1; }
                | host_spec '(' host_spec ')'   {
                        if ($3->af != $1->af) {
                                yyerror("Flow NAT address family mismatch");
                                YYERROR;
                        }
                        $$ = $1;
                        $$->srcnat = $3;
                }
                | ANY                           {
                        $$ = host_any();
                }
                | '{' host_list '}'             { $$ = $2; }
                ;

ids             : /* empty */                   {
                        $$.srcid = NULL;
                        $$.dstid = NULL;
                }
                | SRCID id DSTID id             {
                        $$.srcid = $2;
                        $$.dstid = $4;
                }
                | SRCID id                      {
                        $$.srcid = $2;
                        $$.dstid = NULL;
                }
                | DSTID id                      {
                        $$.srcid = NULL;
                        $$.dstid = $2;
                }
                ;

type            : /* empty */                   {
                        $$ = TYPE_UNKNOWN;
                }
                | TYPE USE                      {
                        $$ = TYPE_USE;
                }
                | TYPE ACQUIRE                  {
                        $$ = TYPE_ACQUIRE;
                }
                | TYPE REQUIRE                  {
                        $$ = TYPE_REQUIRE;
                }
                | TYPE DENY                     {
                        $$ = TYPE_DENY;
                }
                | TYPE BYPASS                   {
                        $$ = TYPE_BYPASS;
                }
                | TYPE DONTACQ                  {
                        $$ = TYPE_DONTACQ;
                }
                ;

id              : STRING                        { $$ = $1; }
                ;

spispec         : SPI STRING                    {
                        u_int32_t        spi;
                        char            *p = strchr($2, ':');

                        if (p != NULL) {
                                *p++ = 0;

                                if (atospi(p, &spi) == -1) {
                                        free($2);
                                        YYERROR;
                                }
                                $$.spiin = spi;
                        } else
                                $$.spiin = 0;

                        if (atospi($2, &spi) == -1) {
                                free($2);
                                YYERROR;
                        }
                        $$.spiout = spi;


                        free($2);
                }
                | SPI NUMBER                    {
                        if ($2 > UINT_MAX || $2 < 0) {
                                yyerror("%lld not a valid spi", $2);
                                YYERROR;
                        }
                        if ($2 >= SPI_RESERVED_MIN && $2 <= SPI_RESERVED_MAX) {
                                yyerror("%lld within reserved spi range", $2);
                                YYERROR;
                        }

                        $$.spiin = 0;
                        $$.spiout = $2;
                }
                ;

udpencap        : /* empty */                           {
                        $$.encap = 0;
                }
                | UDPENCAP                              {
                        $$.encap = 1;
                        $$.port = 0;
                }
                | UDPENCAP PORT NUMBER                  {
                        $$.encap = 1;
                        $$.port = $3;
                }
                ;

transforms      :                                       {
                        if ((ipsec_transforms = calloc(1,
                            sizeof(struct ipsec_transforms))) == NULL)
                                err(1, "transforms: calloc");
                }
                    transforms_l
                        { $$ = ipsec_transforms; }
                | /* empty */                           {
                        if (($$ = calloc(1,
                            sizeof(struct ipsec_transforms))) == NULL)
                                err(1, "transforms: calloc");
                }
                ;

transforms_l    : transforms_l transform
                | transform
                ;

transform       : AUTHXF STRING                 {
                        if (ipsec_transforms->authxf)
                                yyerror("auth already set");
                        else {
                                ipsec_transforms->authxf = parse_xf($2,
                                    authxfs);
                                if (!ipsec_transforms->authxf)
                                        yyerror("%s not a valid transform", $2);
                        }
                }
                | ENCXF STRING                  {
                        if (ipsec_transforms->encxf)
                                yyerror("enc already set");
                        else {
                                ipsec_transforms->encxf = parse_xf($2, encxfs);
                                if (!ipsec_transforms->encxf)
                                        yyerror("%s not a valid transform", $2);
                        }
                }
                | COMPXF STRING                 {
                        if (ipsec_transforms->compxf)
                                yyerror("comp already set");
                        else {
                                ipsec_transforms->compxf = parse_xf($2,
                                    compxfs);
                                if (!ipsec_transforms->compxf)
                                        yyerror("%s not a valid transform", $2);
                        }
                }
                | GROUP STRING                  {
                        if (ipsec_transforms->groupxf)
                                yyerror("group already set");
                        else {
                                ipsec_transforms->groupxf = parse_xf($2,
                                    groupxfs);
                                if (!ipsec_transforms->groupxf)
                                        yyerror("%s not a valid transform", $2);
                        }
                }
                ;

phase1mode      : /* empty */   {
                        struct ike_mode         *p1;

                        /* We create just an empty main mode */
                        if ((p1 = calloc(1, sizeof(struct ike_mode))) == NULL)
                                err(1, "phase1mode: calloc");
                        p1->ike_exch = IKE_MM;
                        $$ = p1;
                }
                | MAIN transforms lifetime              {
                        struct ike_mode *p1;

                        if ((p1 = calloc(1, sizeof(struct ike_mode))) == NULL)
                                err(1, "phase1mode: calloc");
                        p1->xfs = $2;
                        p1->life = $3;
                        p1->ike_exch = IKE_MM;
                        $$ = p1;
                }
                | AGGRESSIVE transforms lifetime        {
                        struct ike_mode *p1;

                        if ((p1 = calloc(1, sizeof(struct ike_mode))) == NULL)
                                err(1, "phase1mode: calloc");
                        p1->xfs = $2;
                        p1->life = $3;
                        p1->ike_exch = IKE_AM;
                        $$ = p1;
                }
                ;

phase2mode      : /* empty */   {
                        struct ike_mode         *p2;

                        /* We create just an empty quick mode */
                        if ((p2 = calloc(1, sizeof(struct ike_mode))) == NULL)
                                err(1, "phase2mode: calloc");
                        p2->ike_exch = IKE_QM;
                        $$ = p2;
                }
                | QUICK transforms lifetime     {
                        struct ike_mode *p2;

                        if ((p2 = calloc(1, sizeof(struct ike_mode))) == NULL)
                                err(1, "phase2mode: calloc");
                        p2->xfs = $2;
                        p2->life = $3;
                        p2->ike_exch = IKE_QM;
                        $$ = p2;
                }
                ;

lifetime        : /* empty */                   {
                        struct ipsec_lifetime *life;

                        /* We create just an empty transform */
                        if ((life = calloc(1, sizeof(struct ipsec_lifetime)))
                            == NULL)
                                err(1, "life: calloc");
                        life->lt_seconds = -1;
                        life->lt_bytes = -1;
                        $$ = life;
                }
                | LIFETIME NUMBER               {
                        struct ipsec_lifetime *life;

                        if ((life = calloc(1, sizeof(struct ipsec_lifetime)))
                            == NULL)
                                err(1, "life: calloc");
                        life->lt_seconds = $2;
                        life->lt_bytes = -1;
                        $$ = life;
                }
                | LIFETIME STRING               {
                        $$ = parse_life($2);
                }
                ;

authkeyspec     : /* empty */                   {
                        $$.keyout = NULL;
                        $$.keyin = NULL;
                }
                | AUTHKEY keyspec               {
                        $$.keyout = $2.keyout;
                        $$.keyin = $2.keyin;
                }
                ;

enckeyspec      : /* empty */                   {
                        $$.keyout = NULL;
                        $$.keyin = NULL;
                }
                | ENCKEY keyspec                {
                        $$.keyout = $2.keyout;
                        $$.keyin = $2.keyin;
                }
                ;

bundlestring    : /* empty */                   { $$ = NULL; }
                | BUNDLE STRING                 { $$ = $2; }
                ;

keyspec         : STRING                        {
                        unsigned char   *hex;
                        unsigned char   *p = strchr($1, ':');

                        if (p != NULL ) {
                                *p++ = 0;

                                if (!strncmp(p, "0x", 2))
                                        p += 2;
                                $$.keyin = parsekey(p, strlen(p));
                        } else
                                $$.keyin = NULL;

                        hex = $1;
                        if (!strncmp(hex, "0x", 2))
                                hex += 2;
                        $$.keyout = parsekey(hex, strlen(hex));

                        free($1);
                }
                | FILENAME STRING               {
                        unsigned char   *p = strchr($2, ':');

                        if (p != NULL) {
                                *p++ = 0;
                                $$.keyin = parsekeyfile(p);
                        }
                        $$.keyout = parsekeyfile($2);
                        free($2);
                }
                ;

ikemode         : /* empty */                   { $$ = IKE_ACTIVE; }
                | PASSIVE                       { $$ = IKE_PASSIVE; }
                | DYNAMIC                       { $$ = IKE_DYNAMIC; }
                | ACTIVE                        { $$ = IKE_ACTIVE; }
                ;

ikeauth         : /* empty */                   {
                        $$.type = IKE_AUTH_RSA;
                        $$.string = NULL;
                }
                | RSA                           {
                        $$.type = IKE_AUTH_RSA;
                        $$.string = NULL;
                }
                | PSK STRING                    {
                        $$.type = IKE_AUTH_PSK;
                        if (($$.string = strdup($2)) == NULL)
                                err(1, "ikeauth: strdup");
                }
                ;

tag             : /* empty */
                {
                        $$ = NULL;
                }
                | TAG STRING
                {
                        $$ = $2;
                }
                ;

iface           : INTERFACE STRING              {
                        static const char prefix[] = "sec";
                        const char *errstr = NULL;
                        size_t len, plen;

                        plen = strlen(prefix);
                        len = strlen($2);

                        if (len <= plen || memcmp($2, prefix, plen) != 0) {
                                yyerror("invalid %s interface name", prefix);
                                free($2);
                                YYERROR;
                        }

                        $$ = strtonum($2 + plen, 0, UINT_MAX, &errstr);
                        free($2);
                        if (errstr != NULL) {
                                yyerror("invalid %s interface unit: %s",
                                    prefix, errstr);
                                YYERROR;
                        }
                }
                ;

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

varset          : STRING '=' string
                {
                        char *s = $1;
                        if (ipsec->opts & IPSECCTL_OPT_VERBOSE)
                                printf("%s = \"%s\"\n", $1, $3);
                        while (*s++) {
                                if (isspace((unsigned char)*s)) {
                                        yyerror("macro name cannot contain "
                                            "whitespace");
                                        free($1);
                                        free($3);
                                        YYERROR;
                                }
                        }
                        if (symset($1, $3, 0) == -1)
                                err(1, "cannot store variable");
                        free($1);
                        free($3);
                }
                ;

%%

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

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

        file->errors++;
        va_start(ap, fmt);
        fprintf(stderr, "%s: %d: ", file->name, yylval.lineno);
        vfprintf(stderr, fmt, ap);
        fprintf(stderr, "\n");
        va_end(ap);
        return (0);
}

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

        va_start(ap, fmt);
        fprintf(stderr, "%s: %d: ", file->name, yylval.lineno);
        vfprintf(stderr, fmt, ap);
        fprintf(stderr, "\n");
        va_end(ap);
        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[] = {
                { "acquire",            ACQUIRE },
                { "active",             ACTIVE },
                { "aggressive",         AGGRESSIVE },
                { "ah",                 AH },
                { "any",                ANY },
                { "auth",               AUTHXF },
                { "authkey",            AUTHKEY },
                { "bundle",             BUNDLE },
                { "bypass",             BYPASS },
                { "comp",               COMPXF },
                { "deny",               DENY },
                { "dontacq",            DONTACQ },
                { "dstid",              DSTID },
                { "dynamic",            DYNAMIC },
                { "enc",                ENCXF },
                { "enckey",             ENCKEY },
                { "esp",                ESP },
                { "file",               FILENAME },
                { "flow",               FLOW },
                { "from",               FROM },
                { "group",              GROUP },
                { "ike",                IKE },
                { "in",                 IN },
                { "include",            INCLUDE },
                { "interface",          INTERFACE },
                { "ipcomp",             IPCOMP },
                { "ipip",               IPIP },
                { "lifetime",           LIFETIME },
                { "local",              LOCAL },
                { "main",               MAIN },
                { "out",                OUT },
                { "passive",            PASSIVE },
                { "peer",               PEER },
                { "port",               PORT },
                { "proto",              PROTO },
                { "psk",                PSK },
                { "quick",              QUICK },
                { "require",            REQUIRE },
                { "rsa",                RSA },
                { "spi",                SPI },
                { "srcid",              SRCID },
                { "tag",                TAG },
                { "tcpmd5",             TCPMD5 },
                { "to",                 TO },
                { "transport",          TRANSPORT },
                { "tunnel",             TUNNEL },
                { "type",               TYPE },
                { "udpencap",           UDPENCAP },
                { "use",                USE }
        };
        const struct keywords   *p;

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

        if (p) {
                if (debug > 1)
                        fprintf(stderr, "%s: %d\n", s, p->k_val);
                return (p->k_val);
        } else {
                if (debug > 1)
                        fprintf(stderr, "string: %s\n", s);
                return (STRING);
        }
}

#define MAXPUSHBACK     128

char    *parsebuf;
int      parseindex;
char     pushback_buffer[MAXPUSHBACK];
int      pushback_index = 0;

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

        if (parsebuf) {
                /* Read character from the parsebuffer instead of input. */
                if (parseindex >= 0) {
                        c = (unsigned char)parsebuf[parseindex++];
                        if (c != '\0')
                                return (c);
                        parsebuf = NULL;
                } else
                        parseindex++;
        }

        if (pushback_index)
                return ((unsigned char)pushback_buffer[--pushback_index]);

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

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

        while (c == EOF) {
                if (file == topfile || popfile() == EOF)
                        return (EOF);
                c = getc(file->stream);
        }
        return (c);
}

int
lungetc(int c)
{
        if (c == EOF)
                return (EOF);
        if (parsebuf) {
                parseindex--;
                if (parseindex >= 0)
                        return (c);
        }
        if (pushback_index + 1 >= MAXPUSHBACK)
                return (EOF);
        pushback_buffer[pushback_index++] = c;
        return (c);
}

int
findeol(void)
{
        int     c;

        parsebuf = NULL;

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

int
yylex(void)
{
        char     buf[8096];
        char    *p, *val;
        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 == '$' && parsebuf == NULL) {
                while (1) {
                        if ((c = lgetc(0)) == EOF)
                                return (0);

                        if (p + 1 >= buf + sizeof(buf) - 1) {
                                yyerror("string too long");
                                return (findeol());
                        }
                        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 (findeol());
                }
                parsebuf = val;
                parseindex = 0;
                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");
                                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)
                        err(1, "%s", __func__);
                return (STRING);
        }

#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 {
                        *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)
                                err(1, "%s", __func__);
                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)) {
                warn("cannot stat %s", fname);
                return (-1);
        }
        if (st.st_uid != 0 && st.st_uid != getuid()) {
                warnx("%s: owner not root or current user", fname);
                return (-1);
        }
        if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
                warnx("%s: group writable or world read/writable", fname);
                return (-1);
        }
        return (0);
}

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

        if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
                warn("%s", __func__);
                return (NULL);
        }
        if ((nfile->name = strdup(name)) == NULL) {
                warn("%s", __func__);
                free(nfile);
                return (NULL);
        }
        if (TAILQ_FIRST(&files) == NULL && strcmp(nfile->name, "-") == 0) {
                nfile->stream = stdin;
                free(nfile->name);
                if ((nfile->name = strdup("stdin")) == NULL) {
                        warn("%s", __func__);
                        free(nfile);
                        return (NULL);
                }
        } else if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
                warn("%s: %s", __func__, nfile->name);
                free(nfile->name);
                free(nfile);
                return (NULL);
        } else if (secret &&
            check_file_secrecy(fileno(nfile->stream), nfile->name)) {
                fclose(nfile->stream);
                free(nfile->name);
                free(nfile);
                return (NULL);
        }
        nfile->lineno = 1;
        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);
        file = prev;

        return (file ? 0 : EOF);
}

int
parse_rules(const char *filename, struct ipsecctl *ipsecx)
{
        struct sym      *sym;
        int              errors = 0;

        ipsec = ipsecx;

        if ((file = pushfile(filename, 1)) == NULL) {
                return (-1);
        }
        topfile = file;

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

        /* Free macros and check which have not been used. */
        while ((sym = TAILQ_FIRST(&symhead))) {
                if ((ipsec->opts & IPSECCTL_OPT_VERBOSE2) && !sym->used)
                        fprintf(stderr, "warning: macro '%s' not "
                            "used\n", sym->nam);
                free(sym->nam);
                free(sym->val);
                TAILQ_REMOVE(&symhead, sym, entry);
                free(sym);
        }

        return (errors ? -1 : 0);
}

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)
                err(1, "%s", __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);
}

int
atoul(char *s, u_long *ulvalp)
{
        u_long   ulval;
        char    *ep;

        errno = 0;
        ulval = strtoul(s, &ep, 0);
        if (s[0] == '\0' || *ep != '\0')
                return (-1);
        if (errno == ERANGE && ulval == ULONG_MAX)
                return (-1);
        *ulvalp = ulval;
        return (0);
}

int
atospi(char *s, u_int32_t *spivalp)
{
        unsigned long   ulval;

        if (atoul(s, &ulval) == -1)
                return (-1);
        if (ulval > UINT_MAX) {
                yyerror("%lu not a valid spi", ulval);
                return (-1);
        }
        if (ulval >= SPI_RESERVED_MIN && ulval <= SPI_RESERVED_MAX) {
                yyerror("%lu within reserved spi range", ulval);
                return (-1);
        }
        *spivalp = ulval;
        return (0);
}

u_int8_t
x2i(unsigned char *s)
{
        char    ss[3];

        ss[0] = s[0];
        ss[1] = s[1];
        ss[2] = 0;

        if (!isxdigit(s[0]) || !isxdigit(s[1])) {
                yyerror("keys need to be specified in hex digits");
                return (-1);
        }
        return ((u_int8_t)strtoul(ss, NULL, 16));
}

struct ipsec_key *
parsekey(unsigned char *hexkey, size_t len)
{
        struct ipsec_key *key;
        int               i;

        key = calloc(1, sizeof(struct ipsec_key));
        if (key == NULL)
                err(1, "%s", __func__);

        key->len = len / 2;
        key->data = calloc(key->len, sizeof(u_int8_t));
        if (key->data == NULL)
                err(1, "%s", __func__);

        for (i = 0; i < (int)key->len; i++)
                key->data[i] = x2i(hexkey + 2 * i);

        return (key);
}

struct ipsec_key *
parsekeyfile(char *filename)
{
        struct stat      sb;
        int              fd;
        unsigned char   *hex;

        if ((fd = open(filename, O_RDONLY)) < 0)
                err(1, "open %s", filename);
        if (fstat(fd, &sb) < 0)
                err(1, "parsekeyfile: stat %s", filename);
        if ((sb.st_size > KEYSIZE_LIMIT) || (sb.st_size == 0))
                errx(1, "%s: key too %s", filename, sb.st_size ? "large" :
                    "small");
        if ((hex = calloc(sb.st_size, sizeof(unsigned char))) == NULL)
                err(1, "%s", __func__);
        if (read(fd, hex, sb.st_size) < sb.st_size)
                err(1, "parsekeyfile: read");
        close(fd);
        return (parsekey(hex, sb.st_size));
}

int
get_id_type(char *string)
{
        struct in6_addr ia;

        if (string == NULL)
                return (ID_UNKNOWN);

        if (inet_pton(AF_INET, string, &ia) == 1)
                return (ID_IPV4);
        else if (inet_pton(AF_INET6, string, &ia) == 1)
                return (ID_IPV6);
        else if (strchr(string, '@'))
                return (ID_UFQDN);
        else
                return (ID_FQDN);
}

struct ipsec_addr_wrap *
host(const char *s)
{
        struct ipsec_addr_wrap  *ipa = NULL;
        int                      mask, cont = 1;
        char                    *p, *q, *ps;

        if ((p = strrchr(s, '/')) != NULL) {
                errno = 0;
                mask = strtol(p + 1, &q, 0);
                if (errno == ERANGE || !q || *q || mask > 128 || q == (p + 1))
                        errx(1, "host: invalid netmask '%s'", p);
                if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL)
                        err(1, "%s", __func__);
                strlcpy(ps, s, strlen(s) - strlen(p) + 1);
        } else {
                if ((ps = strdup(s)) == NULL)
                        err(1, "%s", __func__);
                mask = -1;
        }

        /* Does interface with this name exist? */
        if (cont && (ipa = host_if(ps, mask)) != NULL)
                cont = 0;

        /* IPv4 address? */
        if (cont && (ipa = host_v4(s, mask == -1 ? 32 : mask)) != NULL)
                cont = 0;

        /* IPv6 address? */
        if (cont && (ipa = host_v6(ps, mask == -1 ? 128 : mask)) != NULL)
                cont = 0;

        /* dns lookup */
        if (cont && mask == -1 && (ipa = host_dns(s, mask)) != NULL)
                cont = 0;
        free(ps);

        if (ipa == NULL || cont == 1) {
                fprintf(stderr, "no IP address found for %s\n", s);
                return (NULL);
        }
        return (ipa);
}

struct ipsec_addr_wrap *
host_v6(const char *s, int prefixlen)
{
        struct ipsec_addr_wrap  *ipa = NULL;
        struct addrinfo          hints, *res;
        char                     hbuf[NI_MAXHOST];

        bzero(&hints, sizeof(struct addrinfo));
        hints.ai_family = AF_INET6;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_NUMERICHOST;
        if (getaddrinfo(s, NULL, &hints, &res))
                return (NULL);
        if (res->ai_next)
                err(1, "host_v6: numeric hostname expanded to multiple item");

        ipa = calloc(1, sizeof(struct ipsec_addr_wrap));
        if (ipa == NULL)
                err(1, "%s", __func__);
        ipa->af = res->ai_family;
        memcpy(&ipa->address.v6,
            &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
            sizeof(struct in6_addr));
        if (prefixlen > 128)
                prefixlen = 128;
        ipa->next = NULL;
        ipa->tail = ipa;

        set_ipmask(ipa, prefixlen);
        if (getnameinfo(res->ai_addr, res->ai_addrlen,
            hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST)) {
                errx(1, "could not get a numeric hostname");
        }

        if (prefixlen != 128) {
                ipa->netaddress = 1;
                if (asprintf(&ipa->name, "%s/%d", hbuf, prefixlen) == -1)
                        err(1, "%s", __func__);
        } else {
                if ((ipa->name = strdup(hbuf)) == NULL)
                        err(1, "%s", __func__);
        }

        freeaddrinfo(res);

        return (ipa);
}

struct ipsec_addr_wrap *
host_v4(const char *s, int mask)
{
        struct ipsec_addr_wrap  *ipa = NULL;
        struct in_addr           ina;
        int                      bits = 32;

        bzero(&ina, sizeof(struct in_addr));
        if (strrchr(s, '/') != NULL) {
                if ((bits = inet_net_pton(AF_INET, s, &ina, sizeof(ina))) == -1)
                        return (NULL);
        } else {
                if (inet_pton(AF_INET, s, &ina) != 1)
                        return (NULL);
        }

        ipa = calloc(1, sizeof(struct ipsec_addr_wrap));
        if (ipa == NULL)
                err(1, "%s", __func__);

        ipa->address.v4 = ina;
        ipa->name = strdup(s);
        if (ipa->name == NULL)
                err(1, "%s", __func__);
        ipa->af = AF_INET;
        ipa->next = NULL;
        ipa->tail = ipa;

        set_ipmask(ipa, bits);
        if (strrchr(s, '/') != NULL)
                ipa->netaddress = 1;

        return (ipa);
}

struct ipsec_addr_wrap *
host_dns(const char *s, int mask)
{
        struct ipsec_addr_wrap  *ipa = NULL, *head = NULL;
        struct addrinfo          hints, *res0, *res;
        int                      error;
        char                     hbuf[NI_MAXHOST];

        bzero(&hints, sizeof(struct addrinfo));
        hints.ai_family = PF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        error = getaddrinfo(s, NULL, &hints, &res0);
        if (error)
                return (NULL);

        for (res = res0; res; res = res->ai_next) {
                if (res->ai_family != AF_INET && res->ai_family != AF_INET6)
                        continue;

                ipa = calloc(1, sizeof(struct ipsec_addr_wrap));
                if (ipa == NULL)
                        err(1, "%s", __func__);
                switch (res->ai_family) {
                case AF_INET:
                        memcpy(&ipa->address.v4,
                            &((struct sockaddr_in *)res->ai_addr)->sin_addr,
                            sizeof(struct in_addr));
                        break;
                case AF_INET6:
                        /* XXX we do not support scoped IPv6 address yet */
                        if (((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id) {
                                free(ipa);
                                continue;
                        }
                        memcpy(&ipa->address.v6,
                            &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
                            sizeof(struct in6_addr));
                        break;
                }
                error = getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
                    sizeof(hbuf), NULL, 0, NI_NUMERICHOST);
                if (error)
                        err(1, "host_dns: getnameinfo");
                ipa->name = strdup(hbuf);
                if (ipa->name == NULL)
                        err(1, "%s", __func__);
                ipa->af = res->ai_family;
                ipa->next = NULL;
                ipa->tail = ipa;
                if (head == NULL)
                        head = ipa;
                else {
                        head->tail->next = ipa;
                        head->tail = ipa;
                }

                /*
                 * XXX for now, no netmask support for IPv6.
                 * but since there's no way to specify address family, once you
                 * have IPv6 address on a host, you cannot use dns/netmask
                 * syntax.
                 */
                if (ipa->af == AF_INET)
                        set_ipmask(ipa, mask == -1 ? 32 : mask);
                else
                        if (mask != -1)
                                err(1, "host_dns: cannot apply netmask "
                                    "on non-IPv4 address");
        }
        freeaddrinfo(res0);

        return (head);
}

struct ipsec_addr_wrap *
host_if(const char *s, int mask)
{
        struct ipsec_addr_wrap *ipa = NULL;

        if (ifa_exists(s))
                ipa = ifa_lookup(s);

        return (ipa);
}

struct ipsec_addr_wrap *
host_any(void)
{
        struct ipsec_addr_wrap  *ipa;

        ipa = calloc(1, sizeof(struct ipsec_addr_wrap));
        if (ipa == NULL)
                err(1, "%s", __func__);
        ipa->af = AF_UNSPEC;
        ipa->netaddress = 1;
        ipa->tail = ipa;
        return (ipa);
}

/* interface lookup routintes */

struct ipsec_addr_wrap  *iftab;

void
ifa_load(void)
{
        struct ifaddrs          *ifap, *ifa;
        struct ipsec_addr_wrap  *n = NULL, *h = NULL;

        if (getifaddrs(&ifap) < 0)
                err(1, "ifa_load: getifaddrs");

        for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
                if (ifa->ifa_addr == NULL ||
                    !(ifa->ifa_addr->sa_family == AF_INET ||
                    ifa->ifa_addr->sa_family == AF_INET6 ||
                    ifa->ifa_addr->sa_family == AF_LINK))
                        continue;
                n = calloc(1, sizeof(struct ipsec_addr_wrap));
                if (n == NULL)
                        err(1, "%s", __func__);
                n->af = ifa->ifa_addr->sa_family;
                if ((n->name = strdup(ifa->ifa_name)) == NULL)
                        err(1, "%s", __func__);
                if (n->af == AF_INET) {
                        n->af = AF_INET;
                        memcpy(&n->address.v4, &((struct sockaddr_in *)
                            ifa->ifa_addr)->sin_addr,
                            sizeof(struct in_addr));
                        memcpy(&n->mask.v4, &((struct sockaddr_in *)
                            ifa->ifa_netmask)->sin_addr,
                            sizeof(struct in_addr));
                } else if (n->af == AF_INET6) {
                        n->af = AF_INET6;
                        memcpy(&n->address.v6, &((struct sockaddr_in6 *)
                            ifa->ifa_addr)->sin6_addr,
                            sizeof(struct in6_addr));
                        memcpy(&n->mask.v6, &((struct sockaddr_in6 *)
                            ifa->ifa_netmask)->sin6_addr,
                            sizeof(struct in6_addr));
                }
                n->next = NULL;
                n->tail = n;
                if (h == NULL)
                        h = n;
                else {
                        h->tail->next = n;
                        h->tail = n;
                }
        }

        iftab = h;
        freeifaddrs(ifap);
}

int
ifa_exists(const char *ifa_name)
{
        struct ipsec_addr_wrap  *n;
        struct ifgroupreq        ifgr;
        int                      s;

        if (iftab == NULL)
                ifa_load();

        /* check whether this is a group */
        if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
                err(1, "ifa_exists: socket");
        bzero(&ifgr, sizeof(ifgr));
        strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name));
        if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == 0) {
                close(s);
                return (1);
        }
        close(s);

        for (n = iftab; n; n = n->next) {
                if (n->af == AF_LINK && !strncmp(n->name, ifa_name,
                    IFNAMSIZ))
                        return (1);
        }

        return (0);
}

struct ipsec_addr_wrap *
ifa_grouplookup(const char *ifa_name)
{
        struct ifg_req          *ifg;
        struct ifgroupreq        ifgr;
        int                      s;
        size_t                   len;
        struct ipsec_addr_wrap  *n, *h = NULL, *hn;

        if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
                err(1, "socket");
        bzero(&ifgr, sizeof(ifgr));
        strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name));
        if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) {
                close(s);
                return (NULL);
        }

        len = ifgr.ifgr_len;
        if ((ifgr.ifgr_groups = calloc(1, len)) == NULL)
                err(1, "%s", __func__);
        if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1)
                err(1, "ioctl");

        for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req);
            ifg++) {
                len -= sizeof(struct ifg_req);
                if ((n = ifa_lookup(ifg->ifgrq_member)) == NULL)
                        continue;
                if (h == NULL)
                        h = n;
                else {
                        for (hn = h; hn->next != NULL; hn = hn->next)
                                ;       /* nothing */
                        hn->next = n;
                        n->tail = hn;
                }
        }
        free(ifgr.ifgr_groups);
        close(s);

        return (h);
}

struct ipsec_addr_wrap *
ifa_lookup(const char *ifa_name)
{
        struct ipsec_addr_wrap  *p = NULL, *h = NULL, *n = NULL;

        if (iftab == NULL)
                ifa_load();

        if ((n = ifa_grouplookup(ifa_name)) != NULL)
                return (n);

        for (p = iftab; p; p = p->next) {
                if (p->af != AF_INET && p->af != AF_INET6)
                        continue;
                if (strncmp(p->name, ifa_name, IFNAMSIZ))
                        continue;
                n = calloc(1, sizeof(struct ipsec_addr_wrap));
                if (n == NULL)
                        err(1, "%s", __func__);
                memcpy(n, p, sizeof(struct ipsec_addr_wrap));
                if ((n->name = strdup(p->name)) == NULL)
                        err(1, "%s", __func__);
                switch (n->af) {
                case AF_INET:
                        set_ipmask(n, 32);
                        break;
                case AF_INET6:
                        /* route/show.c and bgpd/util.c give KAME credit */
                        if (IN6_IS_ADDR_LINKLOCAL(&n->address.v6)) {
                                u_int16_t tmp16;
                                /* for now we can not handle link local,
                                 * therefore bail for now
                                 */
                                free(n);
                                continue;

                                memcpy(&tmp16, &n->address.v6.s6_addr[2],
                                    sizeof(tmp16));
                                /* use this when we support link-local
                                 * n->??.scopeid = ntohs(tmp16);
                                 */
                                n->address.v6.s6_addr[2] = 0;
                                n->address.v6.s6_addr[3] = 0;
                        }
                        set_ipmask(n, 128);
                        break;
                }

                n->next = NULL;
                n->tail = n;
                if (h == NULL)
                        h = n;
                else {
                        h->tail->next = n;
                        h->tail = n;
                }
        }

        return (h);
}

void
set_ipmask(struct ipsec_addr_wrap *address, u_int8_t b)
{
        struct ipsec_addr       *ipa;
        int                      i, j = 0;

        ipa = &address->mask;
        bzero(ipa, sizeof(struct ipsec_addr));

        while (b >= 32) {
                ipa->addr32[j++] = 0xffffffff;
                b -= 32;
        }
        for (i = 31; i > 31 - b; --i)
                ipa->addr32[j] |= (1 << i);
        if (b)
                ipa->addr32[j] = htonl(ipa->addr32[j]);
}

const struct ipsec_xf *
parse_xf(const char *name, const struct ipsec_xf xfs[])
{
        int             i;

        for (i = 0; xfs[i].name != NULL; i++) {
                if (strncmp(name, xfs[i].name, strlen(name)))
                        continue;
                return &xfs[i];
        }
        return (NULL);
}

struct ipsec_lifetime *
parse_life(const char *value)
{
        struct ipsec_lifetime   *life;
        int                     ret;
        int                     seconds = 0;
        char                    unit = 0;

        ret = sscanf(value, "%d%c", &seconds, &unit);
        if (ret == 2) {
                switch (tolower((unsigned char)unit)) {
                case 'm':
                        seconds *= 60;
                        break;
                case 'h':
                        seconds *= 60 * 60;
                        break;
                default:
                        err(1, "invalid time unit");
                }
        } else if (ret != 1)
                err(1, "invalid time specification: %s", value);

        life = calloc(1, sizeof(struct ipsec_lifetime));
        if (life == NULL)
                err(1, "%s", __func__);

        life->lt_seconds = seconds;
        life->lt_bytes = -1;

        return (life);
}

struct ipsec_transforms *
copytransforms(const struct ipsec_transforms *xfs)
{
        struct ipsec_transforms *newxfs;

        if (xfs == NULL)
                return (NULL);

        newxfs = calloc(1, sizeof(struct ipsec_transforms));
        if (newxfs == NULL)
                err(1, "%s", __func__);

        memcpy(newxfs, xfs, sizeof(struct ipsec_transforms));
        return (newxfs);
}

struct ipsec_lifetime *
copylife(const struct ipsec_lifetime *life)
{
        struct ipsec_lifetime *newlife;

        if (life == NULL)
                return (NULL);

        newlife = calloc(1, sizeof(struct ipsec_lifetime));
        if (newlife == NULL)
                err(1, "%s", __func__);

        memcpy(newlife, life, sizeof(struct ipsec_lifetime));
        return (newlife);
}

struct ipsec_auth *
copyipsecauth(const struct ipsec_auth *auth)
{
        struct ipsec_auth       *newauth;

        if (auth == NULL)
                return (NULL);

        if ((newauth = calloc(1, sizeof(struct ipsec_auth))) == NULL)
                err(1, "%s", __func__);
        if (auth->srcid &&
            asprintf(&newauth->srcid, "%s", auth->srcid) == -1)
                err(1, "%s", __func__);
        if (auth->dstid &&
            asprintf(&newauth->dstid, "%s", auth->dstid) == -1)
                err(1, "%s", __func__);

        newauth->srcid_type = auth->srcid_type;
        newauth->dstid_type = auth->dstid_type;
        newauth->type = auth->type;

        return (newauth);
}

struct ike_auth *
copyikeauth(const struct ike_auth *auth)
{
        struct ike_auth *newauth;

        if (auth == NULL)
                return (NULL);

        if ((newauth = calloc(1, sizeof(struct ike_auth))) == NULL)
                err(1, "%s", __func__);
        if (auth->string &&
            asprintf(&newauth->string, "%s", auth->string) == -1)
                err(1, "%s", __func__);

        newauth->type = auth->type;

        return (newauth);
}

struct ipsec_key *
copykey(struct ipsec_key *key)
{
        struct ipsec_key        *newkey;

        if (key == NULL)
                return (NULL);

        if ((newkey = calloc(1, sizeof(struct ipsec_key))) == NULL)
                err(1, "%s", __func__);
        if ((newkey->data = calloc(key->len, sizeof(u_int8_t))) == NULL)
                err(1, "%s", __func__);
        memcpy(newkey->data, key->data, key->len);
        newkey->len = key->len;

        return (newkey);
}

struct ipsec_addr_wrap *
copyhost(const struct ipsec_addr_wrap *src)
{
        struct ipsec_addr_wrap *dst;

        if (src == NULL)
                return (NULL);

        dst = calloc(1, sizeof(struct ipsec_addr_wrap));
        if (dst == NULL)
                err(1, "%s", __func__);

        memcpy(dst, src, sizeof(struct ipsec_addr_wrap));

        if (src->name != NULL && (dst->name = strdup(src->name)) == NULL)
                err(1, "%s", __func__);

        return dst;
}

char *
copytag(const char *src)
{
        char *tag;

        if (src == NULL)
                return (NULL);
        if ((tag = strdup(src)) == NULL)
                err(1, "%s", __func__);

        return (tag);
}

struct ipsec_rule *
copyrule(struct ipsec_rule *rule)
{
        struct ipsec_rule       *r;

        if ((r = calloc(1, sizeof(struct ipsec_rule))) == NULL)
                err(1, "%s", __func__);

        r->src = copyhost(rule->src);
        r->dst = copyhost(rule->dst);
        r->local = copyhost(rule->local);
        r->peer = copyhost(rule->peer);
        r->auth = copyipsecauth(rule->auth);
        r->ikeauth = copyikeauth(rule->ikeauth);
        r->xfs = copytransforms(rule->xfs);
        r->p1xfs = copytransforms(rule->p1xfs);
        r->p2xfs = copytransforms(rule->p2xfs);
        r->p1life = copylife(rule->p1life);
        r->p2life = copylife(rule->p2life);
        r->authkey = copykey(rule->authkey);
        r->enckey = copykey(rule->enckey);
        r->tag = copytag(rule->tag);

        r->flags = rule->flags;
        r->p1ie = rule->p1ie;
        r->p2ie = rule->p2ie;
        r->type = rule->type;
        r->satype = rule->satype;
        r->proto = rule->proto;
        r->tmode = rule->tmode;
        r->direction = rule->direction;
        r->flowtype = rule->flowtype;
        r->sport = rule->sport;
        r->dport = rule->dport;
        r->ikemode = rule->ikemode;
        r->spi = rule->spi;
        r->udpencap = rule->udpencap;
        r->udpdport = rule->udpdport;
        r->nr = rule->nr;
        r->iface = rule->iface;

        return (r);
}

int
validate_af(struct ipsec_addr_wrap *src, struct ipsec_addr_wrap *dst)
{
        struct ipsec_addr_wrap *ta;
        u_int8_t src_v4 = 0;
        u_int8_t dst_v4 = 0;
        u_int8_t src_v6 = 0;
        u_int8_t dst_v6 = 0;

        for (ta = src; ta; ta = ta->next) {
                if (ta->af == AF_INET)
                        src_v4 = 1;
                if (ta->af == AF_INET6)
                        src_v6 = 1;
                if (ta->af == AF_UNSPEC)
                        return 0;
                if (src_v4 && src_v6)
                        break;
        }
        for (ta = dst; ta; ta = ta->next) {
                if (ta->af == AF_INET)
                        dst_v4 = 1;
                if (ta->af == AF_INET6)
                        dst_v6 = 1;
                if (ta->af == AF_UNSPEC)
                        return 0;
                if (dst_v4 && dst_v6)
                        break;
        }
        if (src_v4 != dst_v4 && src_v6 != dst_v6)
                return (1);

        return (0);
}


int
validate_sa(u_int32_t spi, u_int8_t satype, struct ipsec_transforms *xfs,
    struct ipsec_key *authkey, struct ipsec_key *enckey, u_int8_t tmode)
{
        /* Sanity checks */
        if (spi == 0) {
                yyerror("no SPI specified");
                return (0);
        }
        if (satype == IPSEC_AH) {
                if (!xfs) {
                        yyerror("no transforms specified");
                        return (0);
                }
                if (!xfs->authxf)
                        xfs->authxf = &authxfs[AUTHXF_HMAC_SHA2_256];
                if (xfs->encxf) {
                        yyerror("ah does not provide encryption");
                        return (0);
                }
                if (xfs->compxf) {
                        yyerror("ah does not provide compression");
                        return (0);
                }
        }
        if (satype == IPSEC_ESP) {
                if (!xfs) {
                        yyerror("no transforms specified");
                        return (0);
                }
                if (xfs->compxf) {
                        yyerror("esp does not provide compression");
                        return (0);
                }
                if (!xfs->encxf)
                        xfs->encxf = &encxfs[ENCXF_AES];
                if (xfs->encxf->nostatic) {
                        yyerror("%s is disallowed with static keys",
                            xfs->encxf->name);
                        return 0;
                }
                if (xfs->encxf->noauth && xfs->authxf) {
                        yyerror("authentication is implicit for %s",
                            xfs->encxf->name);
                        return (0);
                } else if (!xfs->encxf->noauth && !xfs->authxf)
                        xfs->authxf = &authxfs[AUTHXF_HMAC_SHA2_256];
        }
        if (satype == IPSEC_IPCOMP) {
                if (!xfs) {
                        yyerror("no transform specified");
                        return (0);
                }
                if (xfs->authxf || xfs->encxf) {
                        yyerror("no encryption or authentication with ipcomp");
                        return (0);
                }
                if (!xfs->compxf)
                        xfs->compxf = &compxfs[COMPXF_DEFLATE];
        }
        if (satype == IPSEC_IPIP) {
                if (!xfs) {
                        yyerror("no transform specified");
                        return (0);
                }
                if (xfs->authxf || xfs->encxf || xfs->compxf) {
                        yyerror("no encryption, authentication or compression"
                            " with ipip");
                        return (0);
                }
        }
        if (satype == IPSEC_TCPMD5 && authkey == NULL && tmode !=
            IPSEC_TRANSPORT) {
                yyerror("authentication key needed for tcpmd5");
                return (0);
        }
        if (xfs && xfs->authxf) {
                if (!authkey && xfs->authxf != &authxfs[AUTHXF_NONE]) {
                        yyerror("no authentication key specified");
                        return (0);
                }
                if (authkey && authkey->len != xfs->authxf->keymin) {
                        yyerror("wrong authentication key length, needs to be "
                            "%zu bits", xfs->authxf->keymin * 8);
                        return (0);
                }
        }
        if (xfs && xfs->encxf) {
                if (!enckey && xfs->encxf != &encxfs[ENCXF_NULL]) {
                        yyerror("no encryption key specified");
                        return (0);
                }
                if (enckey) {
                        if (enckey->len < xfs->encxf->keymin) {
                                yyerror("encryption key too short (%zu bits), "
                                    "minimum %zu bits", enckey->len * 8,
                                    xfs->encxf->keymin * 8);
                                return (0);
                        }
                        if (xfs->encxf->keymax < enckey->len) {
                                yyerror("encryption key too long (%zu bits), "
                                    "maximum %zu bits", enckey->len * 8,
                                    xfs->encxf->keymax * 8);
                                return (0);
                        }
                }
        }

        return 1;
}

int
add_sabundle(struct ipsec_rule *r, char *bundle)
{
        struct ipsec_rule       *rp, *last, *sabundle;
        int                      found = 0;

        TAILQ_FOREACH(rp, &ipsec->bundle_queue, bundle_entry) {
                if ((strcmp(rp->src->name, r->src->name) == 0) &&
                    (strcmp(rp->dst->name, r->dst->name) == 0) &&
                    (strcmp(rp->bundle, bundle) == 0)) {
                        found = 1;
                        break;
                }
        }
        if (found) {
                last = TAILQ_LAST(&rp->dst_bundle_queue, dst_bundle_queue);
                TAILQ_INSERT_TAIL(&rp->dst_bundle_queue, r, dst_bundle_entry);

                sabundle = create_sabundle(last->dst, last->satype, last->spi,
                    r->dst, r->satype, r->spi);
                if (sabundle == NULL)
                        return (1);
                sabundle->nr = ipsec->rule_nr++;
                if (ipsecctl_add_rule(ipsec, sabundle))
                        return (1);
        } else {
                TAILQ_INSERT_TAIL(&ipsec->bundle_queue, r, bundle_entry);
                TAILQ_INIT(&r->dst_bundle_queue);
                TAILQ_INSERT_TAIL(&r->dst_bundle_queue, r, dst_bundle_entry);
                r->bundle = bundle;
        }

        return (0);
}

struct ipsec_rule *
create_sa(u_int8_t satype, u_int8_t tmode, struct ipsec_hosts *hosts,
    u_int32_t spi, u_int8_t udpencap, u_int16_t udpdport,
    struct ipsec_transforms *xfs, struct ipsec_key *authkey, struct ipsec_key *enckey)
{
        struct ipsec_rule *r;

        if (validate_sa(spi, satype, xfs, authkey, enckey, tmode) == 0)
                return (NULL);

        r = calloc(1, sizeof(struct ipsec_rule));
        if (r == NULL)
                err(1, "%s", __func__);

        r->type |= RULE_SA;
        r->satype = satype;
        r->tmode = tmode;
        r->src = hosts->src;
        r->dst = hosts->dst;
        r->spi = spi;
        r->udpencap = udpencap;
        r->udpdport = udpdport;
        r->xfs = xfs;
        r->authkey = authkey;
        r->enckey = enckey;

        return r;
}

struct ipsec_rule *
reverse_sa(struct ipsec_rule *rule, u_int32_t spi, struct ipsec_key *authkey,
    struct ipsec_key *enckey)
{
        struct ipsec_rule *reverse;

        if (validate_sa(spi, rule->satype, rule->xfs, authkey, enckey,
            rule->tmode) == 0)
                return (NULL);

        reverse = calloc(1, sizeof(struct ipsec_rule));
        if (reverse == NULL)
                err(1, "%s", __func__);

        reverse->type |= RULE_SA;
        reverse->satype = rule->satype;
        reverse->tmode = rule->tmode;
        reverse->src = copyhost(rule->dst);
        reverse->dst = copyhost(rule->src);
        reverse->spi = spi;
        reverse->udpencap = rule->udpencap;
        reverse->udpdport = rule->udpdport;
        reverse->xfs = copytransforms(rule->xfs);
        reverse->authkey = authkey;
        reverse->enckey = enckey;

        return (reverse);
}

struct ipsec_rule *
create_sabundle(struct ipsec_addr_wrap *dst, u_int8_t proto, u_int32_t spi,
    struct ipsec_addr_wrap *dst2, u_int8_t proto2, u_int32_t spi2)
{
        struct ipsec_rule *r;

        r = calloc(1, sizeof(struct ipsec_rule));
        if (r == NULL)
                err(1, "%s", __func__);

        r->type |= RULE_BUNDLE;

        r->dst = copyhost(dst);
        r->dst2 = copyhost(dst2);
        r->proto = proto;
        r->proto2 = proto2;
        r->spi = spi;
        r->spi2 = spi2;
        r->satype = proto;

        return (r);
}

struct ipsec_rule *
create_flow(u_int8_t dir, u_int8_t proto, struct ipsec_hosts *hosts,
    u_int8_t satype, char *srcid, char *dstid, u_int8_t type)
{
        struct ipsec_rule *r;

        r = calloc(1, sizeof(struct ipsec_rule));
        if (r == NULL)
                err(1, "%s", __func__);

        r->type |= RULE_FLOW;

        if (dir == IPSEC_INOUT)
                r->direction = IPSEC_OUT;
        else
                r->direction = dir;

        r->satype = satype;
        r->proto = proto;
        r->src = hosts->src;
        r->sport = hosts->sport;
        r->dst = hosts->dst;
        r->dport = hosts->dport;
        if ((hosts->sport != 0 || hosts->dport != 0) &&
            (proto != IPPROTO_TCP && proto != IPPROTO_UDP)) {
                yyerror("no protocol supplied with source/destination ports");
                goto errout;
        }

        switch (satype) {
        case IPSEC_IPCOMP:
        case IPSEC_IPIP:
                if (type == TYPE_UNKNOWN)
                        type = TYPE_USE;
                break;
        default:
                if (type == TYPE_UNKNOWN)
                        type = TYPE_REQUIRE;
                break;
        }               

        r->flowtype = type;
        if (type == TYPE_DENY || type == TYPE_BYPASS)
                return (r);

        r->auth = calloc(1, sizeof(struct ipsec_auth));
        if (r->auth == NULL)
                err(1, "%s", __func__);
        r->auth->srcid = srcid;
        r->auth->dstid = dstid;
        r->auth->srcid_type = get_id_type(srcid);
        r->auth->dstid_type = get_id_type(dstid);
        return r;

errout:
        free(r);
        if (srcid)
                free(srcid);
        if (dstid)
                free(dstid);
        free(hosts->src);
        hosts->src = NULL;
        free(hosts->dst);
        hosts->dst = NULL;

        return NULL;
}

void
expand_any(struct ipsec_addr_wrap *ipa_in)
{
        struct ipsec_addr_wrap *oldnext, *ipa;

        for (ipa = ipa_in; ipa; ipa = ipa->next) {
                if (ipa->af != AF_UNSPEC)
                        continue;
                oldnext = ipa->next;

                ipa->af = AF_INET;
                ipa->netaddress = 1;
                if ((ipa->name = strdup("0.0.0.0/0")) == NULL)
                        err(1, "%s", __func__);

                ipa->next = calloc(1, sizeof(struct ipsec_addr_wrap));
                if (ipa->next == NULL)
                        err(1, "%s", __func__);
                ipa->next->af = AF_INET6;
                ipa->next->netaddress = 1;
                if ((ipa->next->name = strdup("::/0")) == NULL)
                        err(1, "%s", __func__);

                ipa->next->next = oldnext;
        }
}
         
int
set_rule_peers(struct ipsec_rule *r, struct ipsec_hosts *peers)
{
        if (r->type == RULE_FLOW &&
            (r->flowtype == TYPE_DENY || r->flowtype == TYPE_BYPASS))
                return (0);

        r->local = copyhost(peers->src);
        r->peer = copyhost(peers->dst);
        if (r->peer == NULL) {
                /* Set peer to remote host.  Must be a host address. */
                if (r->direction == IPSEC_IN) {
                        if (!r->src->netaddress)
                                r->peer = copyhost(r->src);
                } else {
                        if (!r->dst->netaddress)
                                r->peer = copyhost(r->dst);
                }
        }
        if (r->type == RULE_FLOW && r->peer == NULL) {
                yyerror("no peer specified for destination %s",
                    r->dst->name);
                return (1);
        }
        if (r->peer != NULL && r->peer->af == AF_UNSPEC) {
                /* If peer has been specified as any, use the default peer. */
                free(r->peer);
                r->peer = NULL;
        }
        if (r->type == RULE_IKE && r->peer == NULL) {
                /*
                 * Check if the default peer is consistent for all
                 * rules.  Only warn to avoid breaking existing configs.
                 */
                static struct ipsec_rule *pdr = NULL;

                if (pdr == NULL) {
                        /* Remember first default peer rule for comparison. */
                        pdr = r;
                } else {
                        /* The new default peer must create the same config. */
                        if ((pdr->local == NULL && r->local != NULL) ||
                            (pdr->local != NULL && r->local == NULL) ||
                            (pdr->local != NULL && r->local != NULL &&
                            strcmp(pdr->local->name, r->local->name)))
                                yywarn("default peer local mismatch");
                        if (pdr->ikeauth->type != r->ikeauth->type)
                                yywarn("default peer phase 1 auth mismatch");
                        if (pdr->ikeauth->type == IKE_AUTH_PSK &&
                            r->ikeauth->type == IKE_AUTH_PSK &&
                            strcmp(pdr->ikeauth->string, r->ikeauth->string))
                                yywarn("default peer psk mismatch");
                        if (pdr->p1ie != r->p1ie)
                                yywarn("default peer phase 1 mode mismatch");
                        /*
                         * Transforms have ADD insted of SET so they may be
                         * different and are not checked here.
                         */
                        if ((pdr->auth->srcid == NULL &&
                            r->auth->srcid != NULL) ||
                            (pdr->auth->srcid != NULL &&
                            r->auth->srcid == NULL) ||
                            (pdr->auth->srcid != NULL &&
                            r->auth->srcid != NULL &&
                            strcmp(pdr->auth->srcid, r->auth->srcid)))
                                yywarn("default peer srcid mismatch");
                        if ((pdr->auth->dstid == NULL &&
                            r->auth->dstid != NULL) ||
                            (pdr->auth->dstid != NULL &&
                            r->auth->dstid == NULL) ||
                            (pdr->auth->dstid != NULL &&
                            r->auth->dstid != NULL &&
                            strcmp(pdr->auth->dstid, r->auth->dstid)))
                                yywarn("default peer dstid mismatch");
                }
        }
        return (0);
}

int
expand_rule(struct ipsec_rule *rule, struct ipsec_hosts *peers,
    u_int8_t direction, u_int32_t spi, struct ipsec_key *authkey,
    struct ipsec_key *enckey, char *bundle)
{
        struct ipsec_rule       *r, *revr;
        struct ipsec_addr_wrap  *src, *dst;
        int added = 0, ret = 1;

        if (validate_af(rule->src, rule->dst)) {
                yyerror("source/destination address families do not match");
                goto errout;
        }
        expand_any(rule->src);
        expand_any(rule->dst);
        for (src = rule->src; src; src = src->next) {
                for (dst = rule->dst; dst; dst = dst->next) {
                        if (src->af != dst->af)
                                continue;
                        r = copyrule(rule);

                        r->src = copyhost(src);
                        r->dst = copyhost(dst);

                        if (peers && set_rule_peers(r, peers)) {
                                ipsecctl_free_rule(r);
                                goto errout;
                        }

                        r->nr = ipsec->rule_nr++;
                        if (ipsecctl_add_rule(ipsec, r))
                                goto out;
                        if (bundle && add_sabundle(r, bundle))
                                goto out;

                        if (direction == IPSEC_INOUT) {
                                /* Create and add reverse flow rule. */
                                revr = reverse_rule(r);
                                if (revr == NULL)
                                        goto out;

                                revr->nr = ipsec->rule_nr++;
                                if (ipsecctl_add_rule(ipsec, revr))
                                        goto out;
                                if (bundle && add_sabundle(revr, bundle))
                                        goto out;
                        } else if (spi != 0 || authkey || enckey) {
                                /* Create and add reverse sa rule. */
                                revr = reverse_sa(r, spi, authkey, enckey);
                                if (revr == NULL)
                                        goto out;

                                revr->nr = ipsec->rule_nr++;
                                if (ipsecctl_add_rule(ipsec, revr))
                                        goto out;
                                if (bundle && add_sabundle(revr, bundle))
                                        goto out;
                        }
                        added++;
                }
        }
        if (!added)
                yyerror("rule expands to no valid combination");
 errout:
        ret = 0;
        ipsecctl_free_rule(rule);
 out:
        if (peers) {
                if (peers->src)
                        free(peers->src);
                if (peers->dst)
                        free(peers->dst);
        }
        return (ret);
}

struct ipsec_rule *
reverse_rule(struct ipsec_rule *rule)
{
        struct ipsec_rule *reverse;

        reverse = calloc(1, sizeof(struct ipsec_rule));
        if (reverse == NULL)
                err(1, "%s", __func__);

        reverse->type |= RULE_FLOW;

        /* Reverse direction */
        if (rule->direction == (u_int8_t)IPSEC_OUT)
                reverse->direction = (u_int8_t)IPSEC_IN;
        else
                reverse->direction = (u_int8_t)IPSEC_OUT;

        reverse->flowtype = rule->flowtype;
        reverse->src = copyhost(rule->dst);
        reverse->dst = copyhost(rule->src);
        reverse->sport = rule->dport;
        reverse->dport = rule->sport;
        if (rule->local)
                reverse->local = copyhost(rule->local);
        if (rule->peer)
                reverse->peer = copyhost(rule->peer);
        reverse->satype = rule->satype;
        reverse->proto = rule->proto;

        if (rule->auth) {
                reverse->auth = calloc(1, sizeof(struct ipsec_auth));
                if (reverse->auth == NULL)
                        err(1, "%s", __func__);
                if (rule->auth->dstid && (reverse->auth->dstid =
                    strdup(rule->auth->dstid)) == NULL)
                        err(1, "%s", __func__);
                if (rule->auth->srcid && (reverse->auth->srcid =
                    strdup(rule->auth->srcid)) == NULL)
                        err(1, "%s", __func__);
                reverse->auth->srcid_type = rule->auth->srcid_type;
                reverse->auth->dstid_type = rule->auth->dstid_type;
                reverse->auth->type = rule->auth->type;
        }

        return reverse;
}

struct ipsec_rule *
create_ike(u_int8_t proto, struct ipsec_hosts *hosts,
    struct ike_mode *phase1mode, struct ike_mode *phase2mode, u_int8_t satype,
    u_int8_t tmode, u_int8_t mode, char *srcid, char *dstid,
    struct ike_auth *authtype, char *tag)
{
        struct ipsec_rule *r;

        r = calloc(1, sizeof(struct ipsec_rule));
        if (r == NULL)
                err(1, "%s", __func__);

        r->type = RULE_IKE;

        r->proto = proto;
        r->src = hosts->src;
        r->sport = hosts->sport;
        r->dst = hosts->dst;
        r->dport = hosts->dport;
        if ((hosts->sport != 0 || hosts->dport != 0) &&
            (proto != IPPROTO_TCP && proto != IPPROTO_UDP)) {
                yyerror("no protocol supplied with source/destination ports");
                goto errout;
        }

        r->satype = satype;
        r->tmode = tmode;
        r->ikemode = mode;
        if (phase1mode) {
                r->p1xfs = phase1mode->xfs;
                r->p1life = phase1mode->life;
                r->p1ie = phase1mode->ike_exch;
        } else {
                r->p1ie = IKE_MM;
        }
        if (phase2mode) {
                if (phase2mode->xfs && phase2mode->xfs->encxf &&
                    phase2mode->xfs->encxf->noauth &&
                    phase2mode->xfs->authxf) {
                        yyerror("authentication is implicit for %s",
                            phase2mode->xfs->encxf->name);
                        goto errout;
                }
                r->p2xfs = phase2mode->xfs;
                r->p2life = phase2mode->life;
                r->p2ie = phase2mode->ike_exch;
        } else {
                r->p2ie = IKE_QM;
        }

        r->auth = calloc(1, sizeof(struct ipsec_auth));
        if (r->auth == NULL)
                err(1, "%s", __func__);
        r->auth->srcid = srcid;
        r->auth->dstid = dstid;
        r->auth->srcid_type = get_id_type(srcid);
        r->auth->dstid_type = get_id_type(dstid);
        r->ikeauth = calloc(1, sizeof(struct ike_auth));
        if (r->ikeauth == NULL)
                err(1, "%s", __func__);
        r->ikeauth->type = authtype->type;
        r->ikeauth->string = authtype->string;
        r->tag = tag;

        return (r);

errout:
        free(r);
        free(hosts->src);
        hosts->src = NULL;
        free(hosts->dst);
        hosts->dst = NULL;
        if (phase1mode) {
                free(phase1mode->xfs);
                phase1mode->xfs = NULL;
                free(phase1mode->life);
                phase1mode->life = NULL;
        }
        if (phase2mode) {
                free(phase2mode->xfs);
                phase2mode->xfs = NULL;
                free(phase2mode->life);
                phase2mode->life = NULL;
        }
        if (srcid)
                free(srcid);
        if (dstid)
                free(dstid);
        return NULL;
}