src/sbin/ipsecctl/parse.y

2952 lines
65 KiB
Plaintext

/* $OpenBSD: parse.y,v 1.183 2023/08/07 04:10:08 dlg 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 },
{ "ecp192", GROUPXF_25, 192, 0 },
{ "grp25", GROUPXF_25, 192, 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;
}