/* $OpenBSD: parse.y,v 1.148 2024/11/04 02:44:28 dlg Exp $ */ /* * Copyright (c) 2019 Tobias Heider * Copyright (c) 2010-2013 Reyk Floeter * Copyright (c) 2004, 2005 Hans-Joerg Hoexer * Copyright (c) 2002, 2003, 2004 Henning Brauer * 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. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iked.h" #include "ikev2.h" #include "eap.h" TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); static struct file { TAILQ_ENTRY(file) entry; FILE *stream; char *name; size_t ungetpos; size_t ungetsize; u_char *ungetbuf; int eof_reached; int lineno; int errors; } *file, *topfile; struct file *pushfile(const char *, int); int popfile(void); int check_file_secrecy(int, const char *); int yyparse(void); int yylex(void); int yyerror(const char *, ...) __attribute__((__format__ (printf, 1, 2))) __attribute__((__nonnull__ (1))); int kw_cmp(const void *, const void *); int lookup(char *); int igetc(void); int lgetc(int); void lungetc(int); int findeol(void); 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 *); #define KEYSIZE_LIMIT 1024 static struct iked *env = NULL; static int debug = 0; static int rules = 0; static int passive = 0; static int decouple = 0; static int mobike = 1; static int enforcesingleikesa = 0; static int stickyaddress = 0; static int fragmentation = 0; static int vendorid = 1; static int dpd_interval = IKED_IKE_SA_ALIVE_TIMEOUT; static char *ocsp_url = NULL; static long ocsp_tolerate = 0; static long ocsp_maxage = -1; static int cert_partial_chain = 0; static struct iked_radopts radauth, radacct; struct iked_transform ikev2_default_ike_transforms[] = { { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_CBC, 256 }, { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_CBC, 192 }, { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_CBC, 128 }, { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_3DES }, { IKEV2_XFORMTYPE_PRF, IKEV2_XFORMPRF_HMAC_SHA2_256 }, { IKEV2_XFORMTYPE_PRF, IKEV2_XFORMPRF_HMAC_SHA2_384 }, { IKEV2_XFORMTYPE_PRF, IKEV2_XFORMPRF_HMAC_SHA2_512 }, { IKEV2_XFORMTYPE_PRF, IKEV2_XFORMPRF_HMAC_SHA1 }, { IKEV2_XFORMTYPE_INTEGR, IKEV2_XFORMAUTH_HMAC_SHA2_256_128 }, { IKEV2_XFORMTYPE_INTEGR, IKEV2_XFORMAUTH_HMAC_SHA2_384_192 }, { IKEV2_XFORMTYPE_INTEGR, IKEV2_XFORMAUTH_HMAC_SHA2_512_256 }, { IKEV2_XFORMTYPE_INTEGR, IKEV2_XFORMAUTH_HMAC_SHA1_96 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_CURVE25519 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_ECP_521 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_ECP_384 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_ECP_256 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_4096 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_3072 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_2048 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_1536 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_1024 }, { 0 } }; size_t ikev2_default_nike_transforms = ((sizeof(ikev2_default_ike_transforms) / sizeof(ikev2_default_ike_transforms[0])) - 1); struct iked_transform ikev2_default_ike_transforms_noauth[] = { { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_GCM_16, 128 }, { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_GCM_16, 256 }, { IKEV2_XFORMTYPE_PRF, IKEV2_XFORMPRF_HMAC_SHA2_256 }, { IKEV2_XFORMTYPE_PRF, IKEV2_XFORMPRF_HMAC_SHA2_384 }, { IKEV2_XFORMTYPE_PRF, IKEV2_XFORMPRF_HMAC_SHA2_512 }, { IKEV2_XFORMTYPE_PRF, IKEV2_XFORMPRF_HMAC_SHA1 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_CURVE25519 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_ECP_521 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_ECP_384 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_ECP_256 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_4096 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_3072 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_2048 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_1536 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_MODP_1024 }, { 0 } }; size_t ikev2_default_nike_transforms_noauth = ((sizeof(ikev2_default_ike_transforms_noauth) / sizeof(ikev2_default_ike_transforms_noauth[0])) - 1); struct iked_transform ikev2_default_esp_transforms[] = { { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_CBC, 256 }, { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_CBC, 192 }, { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_CBC, 128 }, { IKEV2_XFORMTYPE_INTEGR, IKEV2_XFORMAUTH_HMAC_SHA2_256_128 }, { IKEV2_XFORMTYPE_INTEGR, IKEV2_XFORMAUTH_HMAC_SHA2_384_192 }, { IKEV2_XFORMTYPE_INTEGR, IKEV2_XFORMAUTH_HMAC_SHA2_512_256 }, { IKEV2_XFORMTYPE_INTEGR, IKEV2_XFORMAUTH_HMAC_SHA1_96 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_NONE }, { IKEV2_XFORMTYPE_ESN, IKEV2_XFORMESN_ESN }, { IKEV2_XFORMTYPE_ESN, IKEV2_XFORMESN_NONE }, { 0 } }; size_t ikev2_default_nesp_transforms = ((sizeof(ikev2_default_esp_transforms) / sizeof(ikev2_default_esp_transforms[0])) - 1); struct iked_transform ikev2_default_esp_transforms_noauth[] = { { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_GCM_16, 128 }, { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_GCM_16, 256 }, { IKEV2_XFORMTYPE_DH, IKEV2_XFORMDH_NONE }, { IKEV2_XFORMTYPE_ESN, IKEV2_XFORMESN_ESN }, { IKEV2_XFORMTYPE_ESN, IKEV2_XFORMESN_NONE }, { 0 } }; size_t ikev2_default_nesp_transforms_noauth = ((sizeof(ikev2_default_esp_transforms_noauth) / sizeof(ikev2_default_esp_transforms_noauth[0])) - 1); const struct ipsec_xf authxfs[] = { { "hmac-md5", IKEV2_XFORMAUTH_HMAC_MD5_96, 16 }, { "hmac-sha1", IKEV2_XFORMAUTH_HMAC_SHA1_96, 20 }, { "hmac-sha2-256", IKEV2_XFORMAUTH_HMAC_SHA2_256_128, 32 }, { "hmac-sha2-384", IKEV2_XFORMAUTH_HMAC_SHA2_384_192, 48 }, { "hmac-sha2-512", IKEV2_XFORMAUTH_HMAC_SHA2_512_256, 64 }, { NULL } }; const struct ipsec_xf prfxfs[] = { { "hmac-md5", IKEV2_XFORMPRF_HMAC_MD5, 16 }, { "hmac-sha1", IKEV2_XFORMPRF_HMAC_SHA1, 20 }, { "hmac-sha2-256", IKEV2_XFORMPRF_HMAC_SHA2_256, 32 }, { "hmac-sha2-384", IKEV2_XFORMPRF_HMAC_SHA2_384, 48 }, { "hmac-sha2-512", IKEV2_XFORMPRF_HMAC_SHA2_512, 64 }, { NULL } }; const struct ipsec_xf *encxfs = NULL; const struct ipsec_xf ikeencxfs[] = { { "3des", IKEV2_XFORMENCR_3DES, 24 }, { "3des-cbc", IKEV2_XFORMENCR_3DES, 24 }, { "aes-128", IKEV2_XFORMENCR_AES_CBC, 16, 16 }, { "aes-192", IKEV2_XFORMENCR_AES_CBC, 24, 24 }, { "aes-256", IKEV2_XFORMENCR_AES_CBC, 32, 32 }, { "aes-128-gcm", IKEV2_XFORMENCR_AES_GCM_16, 16, 16, 4, 1 }, { "aes-256-gcm", IKEV2_XFORMENCR_AES_GCM_16, 32, 32, 4, 1 }, { "aes-128-gcm-12", IKEV2_XFORMENCR_AES_GCM_12, 16, 16, 4, 1 }, { "aes-256-gcm-12", IKEV2_XFORMENCR_AES_GCM_12, 32, 32, 4, 1 }, { NULL } }; const struct ipsec_xf ipsecencxfs[] = { { "3des", IKEV2_XFORMENCR_3DES, 24 }, { "3des-cbc", IKEV2_XFORMENCR_3DES, 24 }, { "aes-128", IKEV2_XFORMENCR_AES_CBC, 16, 16 }, { "aes-192", IKEV2_XFORMENCR_AES_CBC, 24, 24 }, { "aes-256", IKEV2_XFORMENCR_AES_CBC, 32, 32 }, { "aes-128-ctr", IKEV2_XFORMENCR_AES_CTR, 16, 16, 4 }, { "aes-192-ctr", IKEV2_XFORMENCR_AES_CTR, 24, 24, 4 }, { "aes-256-ctr", IKEV2_XFORMENCR_AES_CTR, 32, 32, 4 }, { "aes-128-gcm", IKEV2_XFORMENCR_AES_GCM_16, 16, 16, 4, 1 }, { "aes-192-gcm", IKEV2_XFORMENCR_AES_GCM_16, 24, 24, 4, 1 }, { "aes-256-gcm", IKEV2_XFORMENCR_AES_GCM_16, 32, 32, 4, 1 }, { "aes-128-gmac", IKEV2_XFORMENCR_NULL_AES_GMAC, 16, 16, 4, 1 }, { "aes-192-gmac", IKEV2_XFORMENCR_NULL_AES_GMAC, 24, 24, 4, 1 }, { "aes-256-gmac", IKEV2_XFORMENCR_NULL_AES_GMAC, 32, 32, 4, 1 }, { "blowfish", IKEV2_XFORMENCR_BLOWFISH, 20, 20 }, { "cast", IKEV2_XFORMENCR_CAST, 16, 16 }, { "chacha20-poly1305", IKEV2_XFORMENCR_CHACHA20_POLY1305, 32, 32, 4, 1 }, { "null", IKEV2_XFORMENCR_NULL, 0, 0 }, { NULL } }; const struct ipsec_xf groupxfs[] = { { "none", IKEV2_XFORMDH_NONE }, { "modp768", IKEV2_XFORMDH_MODP_768 }, { "grp1", IKEV2_XFORMDH_MODP_768 }, { "modp1024", IKEV2_XFORMDH_MODP_1024 }, { "grp2", IKEV2_XFORMDH_MODP_1024 }, { "modp1536", IKEV2_XFORMDH_MODP_1536 }, { "grp5", IKEV2_XFORMDH_MODP_1536 }, { "modp2048", IKEV2_XFORMDH_MODP_2048 }, { "grp14", IKEV2_XFORMDH_MODP_2048 }, { "modp3072", IKEV2_XFORMDH_MODP_3072 }, { "grp15", IKEV2_XFORMDH_MODP_3072 }, { "modp4096", IKEV2_XFORMDH_MODP_4096 }, { "grp16", IKEV2_XFORMDH_MODP_4096 }, { "modp6144", IKEV2_XFORMDH_MODP_6144 }, { "grp17", IKEV2_XFORMDH_MODP_6144 }, { "modp8192", IKEV2_XFORMDH_MODP_8192 }, { "grp18", IKEV2_XFORMDH_MODP_8192 }, { "ecp256", IKEV2_XFORMDH_ECP_256 }, { "grp19", IKEV2_XFORMDH_ECP_256 }, { "ecp384", IKEV2_XFORMDH_ECP_384 }, { "grp20", IKEV2_XFORMDH_ECP_384 }, { "ecp521", IKEV2_XFORMDH_ECP_521 }, { "grp21", IKEV2_XFORMDH_ECP_521 }, { "ecp192", IKEV2_XFORMDH_ECP_192 }, { "grp25", IKEV2_XFORMDH_ECP_192 }, { "ecp224", IKEV2_XFORMDH_ECP_224 }, { "grp26", IKEV2_XFORMDH_ECP_224 }, { "brainpool224", IKEV2_XFORMDH_BRAINPOOL_P224R1 }, { "grp27", IKEV2_XFORMDH_BRAINPOOL_P224R1 }, { "brainpool256", IKEV2_XFORMDH_BRAINPOOL_P256R1 }, { "grp28", IKEV2_XFORMDH_BRAINPOOL_P256R1 }, { "brainpool384", IKEV2_XFORMDH_BRAINPOOL_P384R1 }, { "grp29", IKEV2_XFORMDH_BRAINPOOL_P384R1 }, { "brainpool512", IKEV2_XFORMDH_BRAINPOOL_P512R1 }, { "grp30", IKEV2_XFORMDH_BRAINPOOL_P512R1 }, { "curve25519", IKEV2_XFORMDH_CURVE25519 }, { "grp31", IKEV2_XFORMDH_CURVE25519 }, { "sntrup761x25519", IKEV2_XFORMDH_X_SNTRUP761X25519 }, { NULL } }; const struct ipsec_xf esnxfs[] = { { "esn", IKEV2_XFORMESN_ESN }, { "noesn", IKEV2_XFORMESN_NONE }, { NULL } }; const struct ipsec_xf methodxfs[] = { { "none", IKEV2_AUTH_NONE }, { "rsa", IKEV2_AUTH_RSA_SIG }, { "ecdsa256", IKEV2_AUTH_ECDSA_256 }, { "ecdsa384", IKEV2_AUTH_ECDSA_384 }, { "ecdsa521", IKEV2_AUTH_ECDSA_521 }, { "rfc7427", IKEV2_AUTH_SIG }, { "signature", IKEV2_AUTH_SIG_ANY }, { NULL } }; const struct ipsec_xf saxfs[] = { { "esp", IKEV2_SAPROTO_ESP }, { "ah", IKEV2_SAPROTO_AH }, { NULL } }; const struct ipsec_xf cpxfs[] = { { "address", IKEV2_CFG_INTERNAL_IP4_ADDRESS, AF_INET }, { "netmask", IKEV2_CFG_INTERNAL_IP4_NETMASK, AF_INET }, { "name-server", IKEV2_CFG_INTERNAL_IP4_DNS, AF_INET }, { "netbios-server", IKEV2_CFG_INTERNAL_IP4_NBNS, AF_INET }, { "dhcp-server", IKEV2_CFG_INTERNAL_IP4_DHCP, AF_INET }, { "address", IKEV2_CFG_INTERNAL_IP6_ADDRESS, AF_INET6 }, { "name-server", IKEV2_CFG_INTERNAL_IP6_DNS, AF_INET6 }, { "netbios-server", IKEV2_CFG_INTERNAL_IP6_NBNS, AF_INET6 }, { "dhcp-server", IKEV2_CFG_INTERNAL_IP6_DHCP, AF_INET6 }, { "protected-subnet", IKEV2_CFG_INTERNAL_IP4_SUBNET, AF_INET }, { "protected-subnet", IKEV2_CFG_INTERNAL_IP6_SUBNET, AF_INET6 }, { "access-server", IKEV2_CFG_INTERNAL_IP4_SERVER, AF_INET }, { "access-server", IKEV2_CFG_INTERNAL_IP6_SERVER, AF_INET6 }, { NULL } }; const struct iked_lifetime deflifetime = { IKED_LIFETIME_BYTES, IKED_LIFETIME_SECONDS }; #define IPSEC_ADDR_ANY (0x1) #define IPSEC_ADDR_DYNAMIC (0x2) struct ipsec_addr_wrap { struct sockaddr_storage address; uint8_t mask; int netaddress; sa_family_t af; unsigned int type; unsigned int action; uint16_t port; char *name; struct ipsec_addr_wrap *next; struct ipsec_addr_wrap *tail; struct ipsec_addr_wrap *srcnat; }; struct ipsec_hosts { struct ipsec_addr_wrap *src; struct ipsec_addr_wrap *dst; }; struct ipsec_filters { char *tag; unsigned int tap; }; void copy_sockaddrtoipa(struct ipsec_addr_wrap *, struct sockaddr *); struct ipsec_addr_wrap *host(const char *); struct ipsec_addr_wrap *host_ip(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); struct ipsec_addr_wrap *host_dynamic(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 *, int); const struct ipsec_xf *parse_xf(const char *, unsigned int, const struct ipsec_xf *); void copy_transforms(unsigned int, const struct ipsec_xf **, unsigned int, struct iked_transform **, unsigned int *, struct iked_transform *, size_t); int create_ike(char *, int, struct ipsec_addr_wrap *, int, struct ipsec_hosts *, struct ipsec_hosts *, struct ipsec_mode *, struct ipsec_mode *, uint8_t, unsigned int, char *, char *, uint32_t, struct iked_lifetime *, struct iked_auth *, struct ipsec_filters *, struct ipsec_addr_wrap *, char *); int create_user(const char *, const char *); int get_id_type(char *); uint8_t x2i(unsigned char *); int parsekey(unsigned char *, size_t, struct iked_auth *); int parsekeyfile(char *, struct iked_auth *); void iaw_free(struct ipsec_addr_wrap *); static int create_flow(struct iked_policy *pol, int, struct ipsec_addr_wrap *ipa, struct ipsec_addr_wrap *ipb); static int expand_flows(struct iked_policy *, int, struct ipsec_addr_wrap *, struct ipsec_addr_wrap *); static struct ipsec_addr_wrap * expand_keyword(struct ipsec_addr_wrap *); struct iked_radserver * create_radserver(const char *, u_short, const char *); struct ipsec_transforms *ipsec_transforms; struct ipsec_filters *ipsec_filters; struct ipsec_mode *ipsec_mode; /* interface lookup routintes */ struct ipsec_addr_wrap *iftab; typedef struct { union { int64_t number; unsigned int ikemode; uint8_t dir; uint8_t satype; uint8_t accounting; char *string; uint16_t port; struct ipsec_hosts *hosts; struct ipsec_hosts peers; struct ipsec_addr_wrap *anyhost; struct ipsec_addr_wrap *host; struct ipsec_addr_wrap *cfg; struct ipsec_addr_wrap *proto; struct { char *srcid; char *dstid; } ids; char *id; uint8_t type; struct iked_lifetime lifetime; struct iked_auth ikeauth; struct iked_auth ikekey; struct ipsec_transforms *transforms; struct ipsec_filters *filters; struct ipsec_mode *mode; struct { uint32_t vendorid; uint8_t attrtype; } radattr; } v; int lineno; } YYSTYPE; %} %token FROM ESP AH IN PEER ON OUT TO SRCID DSTID PSK PORT %token FILENAME AUTHXF PRFXF ENCXF ERROR IKEV2 IKESA CHILDSA ESN NOESN %token PASSIVE ACTIVE ANY TAG TAP PROTO LOCAL GROUP NAME CONFIG EAP USER %token IKEV1 FLOW SA TCPMD5 TUNNEL TRANSPORT COUPLE DECOUPLE SET %token INCLUDE LIFETIME BYTES INET INET6 QUICK SKIP DEFAULT %token IPCOMP OCSP IKELIFETIME MOBIKE NOMOBIKE RDOMAIN %token FRAGMENTATION NOFRAGMENTATION DPD_CHECK_INTERVAL %token ENFORCESINGLEIKESA NOENFORCESINGLEIKESA %token STICKYADDRESS NOSTICKYADDRESS %token VENDORID NOVENDORID %token TOLERATE MAXAGE DYNAMIC %token CERTPARTIALCHAIN %token REQUEST IFACE %token RADIUS ACCOUNTING SERVER SECRET MAX_TRIES MAX_FAILOVERS %token CLIENT DAE LISTEN ON NATT %token STRING %token NUMBER %type string %type satype %type proto proto_list protoval %type hosts hosts_list %type port %type portval af rdomain hexdecnumber %type peers %type anyhost %type host host_spec %type ids %type id %type transforms %type filters %type ikeflags %type ikematch ikemode ipcomp tmode natt_force %type ikeauth %type keyspec %type ike_sas child_sas %type lifetime %type byte_spec time_spec ikelifetime %type name iface %type cfg ikecfg ikecfgvals %type transform_esn %type accounting %type radattr %% grammar : /* empty */ | grammar include '\n' | grammar '\n' | grammar set '\n' | grammar user '\n' | grammar ikev2rule '\n' | grammar radius '\n' | grammar varset '\n' | grammar otherrule skipline '\n' | grammar error '\n' { file->errors++; } ; comma : ',' | /* empty */ ; include : INCLUDE STRING { struct file *nfile; if ((nfile = pushfile($2, 1)) == NULL) { yyerror("failed to include file %s", $2); free($2); YYERROR; } free($2); file = nfile; lungetc('\n'); } ; set : SET ACTIVE { passive = 0; } | SET PASSIVE { passive = 1; } | SET COUPLE { decouple = 0; } | SET DECOUPLE { decouple = 1; } | SET FRAGMENTATION { fragmentation = 1; } | SET NOFRAGMENTATION { fragmentation = 0; } | SET MOBIKE { mobike = 1; } | SET NOMOBIKE { mobike = 0; } | SET VENDORID { vendorid = 1; } | SET NOVENDORID { vendorid = 0; } | SET ENFORCESINGLEIKESA { enforcesingleikesa = 1; } | SET NOENFORCESINGLEIKESA { enforcesingleikesa = 0; } | SET STICKYADDRESS { stickyaddress = 1; } | SET NOSTICKYADDRESS { stickyaddress = 0; } | SET OCSP STRING { ocsp_url = $3; } | SET OCSP STRING TOLERATE time_spec { ocsp_url = $3; ocsp_tolerate = $5; } | SET OCSP STRING TOLERATE time_spec MAXAGE time_spec { ocsp_url = $3; ocsp_tolerate = $5; ocsp_maxage = $7; } | SET CERTPARTIALCHAIN { cert_partial_chain = 1; } | SET DPD_CHECK_INTERVAL NUMBER { if ($3 < 0) { yyerror("timeout outside range"); YYERROR; } dpd_interval = $3; } ; user : USER STRING STRING { if (create_user($2, $3) == -1) YYERROR; free($2); freezero($3, strlen($3)); } ; ikev2rule : IKEV2 name ikeflags satype af proto rdomain hosts_list peers ike_sas child_sas ids ikelifetime lifetime ikeauth ikecfg iface filters { if (create_ike($2, $5, $6, $7, $8, &$9, $10, $11, $4, $3, $12.srcid, $12.dstid, $13, &$14, &$15, $18, $16, $17) == -1) { yyerror("create_ike failed"); YYERROR; } } ; ikecfg : /* empty */ { $$ = NULL; } | ikecfgvals { $$ = $1; } ; ikecfgvals : cfg { $$ = $1; } | ikecfgvals cfg { if ($2 == NULL) $$ = $1; else if ($1 == NULL) $$ = $2; else { $1->tail->next = $2; $1->tail = $2->tail; $$ = $1; } } ; cfg : CONFIG STRING host_spec { const struct ipsec_xf *xf; if ((xf = parse_xf($2, $3->af, cpxfs)) == NULL) { yyerror("not a valid ikecfg option"); free($2); free($3); YYERROR; } free($2); $$ = $3; $$->type = xf->id; $$->action = IKEV2_CP_REPLY; /* XXX */ } | REQUEST STRING anyhost { const struct ipsec_xf *xf; if ((xf = parse_xf($2, $3->af, cpxfs)) == NULL) { yyerror("not a valid ikecfg option"); free($2); free($3); YYERROR; } free($2); $$ = $3; $$->type = xf->id; $$->action = IKEV2_CP_REQUEST; /* XXX */ } ; name : /* empty */ { $$ = NULL; } | STRING { $$ = $1; } satype : /* empty */ { $$ = IKEV2_SAPROTO_ESP; } | ESP { $$ = IKEV2_SAPROTO_ESP; } | AH { $$ = IKEV2_SAPROTO_AH; } ; af : /* empty */ { $$ = AF_UNSPEC; } | INET { $$ = AF_INET; } | INET6 { $$ = AF_INET6; } ; proto : /* empty */ { $$ = NULL; } | PROTO protoval { $$ = $2; } | PROTO '{' proto_list '}' { $$ = $3; } ; proto_list : protoval { $$ = $1; } | proto_list comma protoval { if ($3 == NULL) $$ = $1; else if ($1 == NULL) $$ = $3; else { $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; } } ; protoval : STRING { struct protoent *p; p = getprotobyname($1); if (p == NULL) { yyerror("unknown protocol: %s", $1); YYERROR; } if (($$ = calloc(1, sizeof(*$$))) == NULL) err(1, "protoval: calloc"); $$->type = p->p_proto; $$->tail = $$; free($1); } | NUMBER { if ($1 > 255 || $1 < 0) { yyerror("protocol outside range"); YYERROR; } if (($$ = calloc(1, sizeof(*$$))) == NULL) err(1, "protoval: calloc"); $$->type = $1; $$->tail = $$; } ; rdomain : /* empty */ { $$ = -1; } | RDOMAIN NUMBER { if ($2 > 255 || $2 < 0) { yyerror("rdomain outside range"); YYERROR; } $$ = $2; } hosts_list : hosts { $$ = $1; } | hosts_list comma hosts { if ($3 == NULL) $$ = $1; else if ($1 == NULL) $$ = $3; else { $1->src->tail->next = $3->src; $1->src->tail = $3->src->tail; $1->dst->tail->next = $3->dst; $1->dst->tail = $3->dst->tail; $$ = $1; free($3); } } ; 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; } } if (($$ = calloc(1, sizeof(*$$))) == NULL) err(1, "hosts: calloc"); $$->src = $2; $$->src->port = $3; $$->dst = $5; $$->dst->port = $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; } } if (($$ = calloc(1, sizeof(*$$))) == NULL) err(1, "hosts: calloc"); $$->src = $5; $$->src->port = $6; $$->dst = $2; $$->dst->port = $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; } free($1); } | NUMBER { if ($1 > USHRT_MAX || $1 < 0) { yyerror("port outside range"); YYERROR; } $$ = htons($1); } ; peers : /* empty */ { $$.dst = NULL; $$.src = NULL; } | PEER anyhost LOCAL anyhost { $$.dst = $2; $$.src = $4; } | LOCAL anyhost PEER anyhost { $$.dst = $4; $$.src = $2; } | PEER anyhost { $$.dst = $2; $$.src = NULL; } | LOCAL anyhost { $$.dst = NULL; $$.src = $2; } ; anyhost : host_spec { $$ = $1; } | ANY { $$ = host_any(); } 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 (($1->af != AF_UNSPEC) && ($3->af != AF_UNSPEC) && ($3->af != $1->af)) { yyerror("Flow NAT address family mismatch"); YYERROR; } $$ = $1; $$->srcnat = $3; } | ANY { $$ = host_any(); } | DYNAMIC { $$ = host_dynamic(); } ; 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; } ; id : STRING { $$ = $1; } ; transforms : { if ((ipsec_transforms = calloc(1, sizeof(struct ipsec_transforms))) == NULL) err(1, "transforms: calloc"); } transforms_l { $$ = ipsec_transforms; } | /* empty */ { $$ = NULL; } ; transforms_l : transforms_l transform | transform ; transform : AUTHXF STRING { const struct ipsec_xf **xfs = ipsec_transforms->authxf; size_t nxfs = ipsec_transforms->nauthxf; xfs = recallocarray(xfs, nxfs, nxfs + 1, sizeof(struct ipsec_xf *)); if (xfs == NULL) err(1, "transform: recallocarray"); if ((xfs[nxfs] = parse_xf($2, 0, authxfs)) == NULL) { yyerror("%s not a valid transform", $2); YYERROR; } free($2); ipsec_transforms->authxf = xfs; ipsec_transforms->nauthxf++; } | ENCXF STRING { const struct ipsec_xf **xfs = ipsec_transforms->encxf; size_t nxfs = ipsec_transforms->nencxf; xfs = recallocarray(xfs, nxfs, nxfs + 1, sizeof(struct ipsec_xf *)); if (xfs == NULL) err(1, "transform: recallocarray"); if ((xfs[nxfs] = parse_xf($2, 0, encxfs)) == NULL) { yyerror("%s not a valid transform", $2); YYERROR; } free($2); ipsec_transforms->encxf = xfs; ipsec_transforms->nencxf++; } | PRFXF STRING { const struct ipsec_xf **xfs = ipsec_transforms->prfxf; size_t nxfs = ipsec_transforms->nprfxf; xfs = recallocarray(xfs, nxfs, nxfs + 1, sizeof(struct ipsec_xf *)); if (xfs == NULL) err(1, "transform: recallocarray"); if ((xfs[nxfs] = parse_xf($2, 0, prfxfs)) == NULL) { yyerror("%s not a valid transform", $2); YYERROR; } free($2); ipsec_transforms->prfxf = xfs; ipsec_transforms->nprfxf++; } | GROUP STRING { const struct ipsec_xf **xfs = ipsec_transforms->groupxf; size_t nxfs = ipsec_transforms->ngroupxf; xfs = recallocarray(xfs, nxfs, nxfs + 1, sizeof(struct ipsec_xf *)); if (xfs == NULL) err(1, "transform: recallocarray"); if ((xfs[nxfs] = parse_xf($2, 0, groupxfs)) == NULL) { yyerror("%s not a valid transform", $2); YYERROR; } free($2); ipsec_transforms->groupxf = xfs; ipsec_transforms->ngroupxf++; } | transform_esn { const struct ipsec_xf **xfs = ipsec_transforms->esnxf; size_t nxfs = ipsec_transforms->nesnxf; xfs = recallocarray(xfs, nxfs, nxfs + 1, sizeof(struct ipsec_xf *)); if (xfs == NULL) err(1, "transform: recallocarray"); if ((xfs[nxfs] = parse_xf($1, 0, esnxfs)) == NULL) { yyerror("%s not a valid transform", $1); YYERROR; } ipsec_transforms->esnxf = xfs; ipsec_transforms->nesnxf++; } ; transform_esn : ESN { $$ = "esn"; } | NOESN { $$ = "noesn"; } ; ike_sas : { if ((ipsec_mode = calloc(1, sizeof(struct ipsec_mode))) == NULL) err(1, "ike_sas: calloc"); } ike_sas_l { $$ = ipsec_mode; } | /* empty */ { $$ = NULL; } ; ike_sas_l : ike_sas_l ike_sa | ike_sa ; ike_sa : IKESA { if ((ipsec_mode->xfs = recallocarray(ipsec_mode->xfs, ipsec_mode->nxfs, ipsec_mode->nxfs + 1, sizeof(struct ipsec_transforms *))) == NULL) err(1, "ike_sa: recallocarray"); ipsec_mode->nxfs++; encxfs = ikeencxfs; } transforms { ipsec_mode->xfs[ipsec_mode->nxfs - 1] = $3; } ; child_sas : { if ((ipsec_mode = calloc(1, sizeof(struct ipsec_mode))) == NULL) err(1, "child_sas: calloc"); } child_sas_l { $$ = ipsec_mode; } | /* empty */ { $$ = NULL; } ; child_sas_l : child_sas_l child_sa | child_sa ; child_sa : CHILDSA { if ((ipsec_mode->xfs = recallocarray(ipsec_mode->xfs, ipsec_mode->nxfs, ipsec_mode->nxfs + 1, sizeof(struct ipsec_transforms *))) == NULL) err(1, "child_sa: recallocarray"); ipsec_mode->nxfs++; encxfs = ipsecencxfs; } transforms { ipsec_mode->xfs[ipsec_mode->nxfs - 1] = $3; } ; ikeflags : ikematch ikemode ipcomp tmode natt_force { $$ = $1 | $2 | $3 | $4 | $5; } ; ikematch : /* empty */ { $$ = 0; } | QUICK { $$ = IKED_POLICY_QUICK; } | SKIP { $$ = IKED_POLICY_SKIP; } | DEFAULT { $$ = IKED_POLICY_DEFAULT; } ; ikemode : /* empty */ { $$ = IKED_POLICY_PASSIVE; } | PASSIVE { $$ = IKED_POLICY_PASSIVE; } | ACTIVE { $$ = IKED_POLICY_ACTIVE; } ; ipcomp : /* empty */ { $$ = 0; } | IPCOMP { $$ = IKED_POLICY_IPCOMP; } ; tmode : /* empty */ { $$ = 0; } | TUNNEL { $$ = 0; } | TRANSPORT { $$ = IKED_POLICY_TRANSPORT; } ; natt_force : /* empty */ { $$ = 0; } | NATT { $$ = IKED_POLICY_NATT_FORCE; } ; ikeauth : /* empty */ { $$.auth_method = IKEV2_AUTH_SIG_ANY; /* default */ $$.auth_eap = 0; $$.auth_length = 0; } | PSK keyspec { memcpy(&$$, &$2, sizeof($$)); $$.auth_method = IKEV2_AUTH_SHARED_KEY_MIC; $$.auth_eap = 0; explicit_bzero(&$2, sizeof($2)); } | EAP RADIUS { $$.auth_method = IKEV2_AUTH_SIG_ANY; $$.auth_eap = EAP_TYPE_RADIUS; $$.auth_length = 0; } | EAP STRING { unsigned int i; for (i = 0; i < strlen($2); i++) if ($2[i] == '-') $2[i] = '_'; if (strcasecmp("mschap_v2", $2) == 0) $$.auth_eap = EAP_TYPE_MSCHAP_V2; else if (strcasecmp("radius", $2) == 0) $$.auth_eap = EAP_TYPE_RADIUS; else { yyerror("unsupported EAP method: %s", $2); free($2); YYERROR; } free($2); $$.auth_method = IKEV2_AUTH_SIG_ANY; $$.auth_length = 0; } | STRING { const struct ipsec_xf *xf; if ((xf = parse_xf($1, 0, methodxfs)) == NULL || xf->id == IKEV2_AUTH_NONE) { yyerror("not a valid authentication mode"); free($1); YYERROR; } free($1); $$.auth_method = xf->id; $$.auth_eap = 0; $$.auth_length = 0; } ; byte_spec : NUMBER { $$ = $1; } | STRING { uint64_t bytes = 0; char unit = 0; if (sscanf($1, "%llu%c", &bytes, &unit) != 2) { yyerror("invalid byte specification: %s", $1); YYERROR; } free($1); switch (toupper((unsigned char)unit)) { case 'K': bytes *= 1024; break; case 'M': bytes *= 1024 * 1024; break; case 'G': bytes *= 1024 * 1024 * 1024; break; default: yyerror("invalid byte unit"); YYERROR; } $$ = bytes; } ; time_spec : NUMBER { $$ = $1; } | STRING { uint64_t seconds = 0; char unit = 0; if (sscanf($1, "%llu%c", &seconds, &unit) != 2) { yyerror("invalid time specification: %s", $1); YYERROR; } free($1); switch (tolower((unsigned char)unit)) { case 'm': seconds *= 60; break; case 'h': seconds *= 60 * 60; break; default: yyerror("invalid time unit"); YYERROR; } $$ = seconds; } ; lifetime : /* empty */ { $$ = deflifetime; } | LIFETIME time_spec { $$.lt_seconds = $2; $$.lt_bytes = deflifetime.lt_bytes; } | LIFETIME time_spec BYTES byte_spec { $$.lt_seconds = $2; $$.lt_bytes = $4; } ; ikelifetime : /* empty */ { $$ = 0; } | IKELIFETIME time_spec { $$ = $2; } keyspec : STRING { uint8_t *hex; bzero(&$$, sizeof($$)); hex = $1; if (strncmp(hex, "0x", 2) == 0) { hex += 2; if (parsekey(hex, strlen(hex), &$$) != 0) { free($1); YYERROR; } } else { if (strlen($1) > sizeof($$.auth_data)) { yyerror("psk too long"); free($1); YYERROR; } strlcpy($$.auth_data, $1, sizeof($$.auth_data)); $$.auth_length = strlen($1); } freezero($1, strlen($1)); } | FILENAME STRING { if (parsekeyfile($2, &$$) != 0) { free($2); YYERROR; } free($2); } ; filters : { if ((ipsec_filters = calloc(1, sizeof(struct ipsec_filters))) == NULL) err(1, "filters: calloc"); } filters_l { $$ = ipsec_filters; } | /* empty */ { $$ = NULL; } ; filters_l : filters_l filter | filter ; filter : TAG STRING { ipsec_filters->tag = $2; } | TAP STRING { const char *errstr = NULL; size_t len; len = strcspn($2, "0123456789"); if (strlen("enc") != len || strncmp("enc", $2, len) != 0) { yyerror("invalid tap interface name: %s", $2); free($2); YYERROR; } ipsec_filters->tap = strtonum($2 + len, 0, UINT_MAX, &errstr); free($2); if (errstr != NULL) { yyerror("invalid tap interface unit: %s", errstr); YYERROR; } } ; iface : { $$ = NULL; } | IFACE STRING { $$ = $2; } string : string STRING { if (asprintf(&$$, "%s %s", $1, $2) == -1) err(1, "string: asprintf"); free($1); free($2); } | STRING ; radius : RADIUS accounting SERVER STRING port SECRET STRING { int ret, gai_err; struct addrinfo hints, *ai; u_short port; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; if ((gai_err = getaddrinfo($4, NULL, &hints, &ai)) != 0) { yyerror("could not parse the address: %s: %s", $4, gai_strerror(gai_err)); free($4); explicit_bzero($7, strlen($7)); free($7); YYERROR; } port = $5; if (port == 0) port = htons((!$2)? RADIUS_DEFAULT_PORT : RADIUS_ACCT_DEFAULT_PORT); socket_af(ai->ai_addr, port); if ((ret = config_setradserver(env, ai->ai_addr, ai->ai_addrlen, $7, $2)) != 0) { yyerror("could not set radius server"); free($4); explicit_bzero($7, strlen($7)); free($7); YYERROR; } explicit_bzero($7, strlen($7)); freeaddrinfo(ai); free($4); free($7); } | RADIUS accounting MAX_TRIES NUMBER { if ($4 <= 0) { yyerror("max-tries must a positive value"); YYERROR; } if ($2) radacct.max_tries = $4; else radauth.max_tries = $4; } | RADIUS accounting MAX_FAILOVERS NUMBER { if ($4 < 0) { yyerror("max-failovers must be 0 or a " "positive value"); YYERROR; } if ($2) radacct.max_failovers = $4; else radauth.max_failovers = $4; } | RADIUS CONFIG af STRING radattr { const struct ipsec_xf *xf; int af, cfgtype; af = $3; if (af == AF_UNSPEC) af = AF_INET; if (strcmp($4, "none") == 0) cfgtype = 0; else { if ((xf = parse_xf($4, af, cpxfs)) == NULL || xf->id == IKEV2_CFG_INTERNAL_IP4_SUBNET || xf->id == IKEV2_CFG_INTERNAL_IP6_SUBNET) { yyerror("not a valid ikecfg option"); free($4); YYERROR; } cfgtype = xf->id; } free($4); config_setradcfgmap(env, cfgtype, $5.vendorid, $5.attrtype); } | RADIUS DAE LISTEN ON STRING port { int ret, gai_err; struct addrinfo hints, *ai; u_short port; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; if ((gai_err = getaddrinfo($5, NULL, &hints, &ai)) != 0) { yyerror("could not parse the address: %s: %s", $5, gai_strerror(gai_err)); free($5); YYERROR; } port = $6; if (port == 0) port = htons(RADIUS_DAE_DEFAULT_PORT); socket_af(ai->ai_addr, port); if ((ret = config_setraddae(env, ai->ai_addr, ai->ai_addrlen)) != 0) { yyerror("could not set radius server"); free($5); YYERROR; } freeaddrinfo(ai); free($5); } | RADIUS DAE CLIENT STRING SECRET STRING { int gai_err; struct addrinfo hints, *ai; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; if ((gai_err = getaddrinfo($4, NULL, &hints, &ai)) != 0) { yyerror("could not parse the address: %s: %s", $4, gai_strerror(gai_err)); free($4); explicit_bzero($6, strlen($6)); free($6); YYERROR; } config_setradclient(env, ai->ai_addr, ai->ai_addrlen, $6); free($4); explicit_bzero($6, strlen($6)); free($6); freeaddrinfo(ai); } ; radattr : hexdecnumber hexdecnumber { if ($1 < 0 || 0xffffffL < $1) { yyerror("vendor-id must be in 0-0xffffff"); YYERROR; } if ($2 < 0 || 256 <= $2) { yyerror("attribute type must be in 0-255"); YYERROR; } $$.vendorid = $1; $$.attrtype = $2; } | hexdecnumber { if ($1 < 0 || 256 <= $1) { yyerror("attribute type must be in 0-255"); YYERROR; } $$.vendorid = 0; $$.attrtype = $1; } hexdecnumber : STRING { const char *errstr; char *ep; uintmax_t ul; if ($1[0] == '0' && $1[1] == 'x' && isxdigit($1[2])) { ul = strtoumax($1 + 2, &ep, 16); if (*ep != '\0') { yyerror("`%s' is not a number", $1); free($1); YYERROR; } if (ul == UINTMAX_MAX || ul > UINT64_MAX) { yyerror("`%s' is out-of-range", $1); free($1); YYERROR; } $$ = ul; } else { $$ = strtonum($1, 0, UINT64_MAX, &errstr); if (errstr != NULL) { yyerror("`%s' is %s", $1, errstr); free($1); YYERROR; } } free($1); } | NUMBER ; accounting : { $$ = 0; } | ACCOUNTING { $$ = 1; } ; varset : STRING '=' string { char *s = $1; log_debug("%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); } ; /* * ignore IKEv1/manual keying rules in ipsec.conf */ otherrule : IKEV1 | sarule | FLOW | TCPMD5 ; /* manual keying SAs might start with the following keywords */ sarule : SA | FROM | TO | TUNNEL | TRANSPORT ; /* ignore everything to the end of the line */ skipline : { int c; while ((c = lgetc(0)) != '\n' && c != EOF) ; /* nothing */ if (c == '\n') lungetc(c); } ; %% struct keywords { const char *k_name; int k_val; }; void copy_sockaddrtoipa(struct ipsec_addr_wrap *ipa, struct sockaddr *sa) { if (sa->sa_family == AF_INET6) memcpy(&ipa->address, sa, sizeof(struct sockaddr_in6)); else if (sa->sa_family == AF_INET) memcpy(&ipa->address, sa, sizeof(struct sockaddr_in)); else warnx("unhandled af %d", sa->sa_family); } 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 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[] = { { "accounting", ACCOUNTING }, { "active", ACTIVE }, { "ah", AH }, { "any", ANY }, { "auth", AUTHXF }, { "bytes", BYTES }, { "cert_partial_chain", CERTPARTIALCHAIN }, { "childsa", CHILDSA }, { "client", CLIENT }, { "config", CONFIG }, { "couple", COUPLE }, { "dae", DAE }, { "decouple", DECOUPLE }, { "default", DEFAULT }, { "dpd_check_interval", DPD_CHECK_INTERVAL }, { "dstid", DSTID }, { "dynamic", DYNAMIC }, { "eap", EAP }, { "enc", ENCXF }, { "enforcesingleikesa", ENFORCESINGLEIKESA }, { "esn", ESN }, { "esp", ESP }, { "file", FILENAME }, { "flow", FLOW }, { "fragmentation", FRAGMENTATION }, { "from", FROM }, { "group", GROUP }, { "iface", IFACE }, { "ike", IKEV1 }, { "ikelifetime", IKELIFETIME }, { "ikesa", IKESA }, { "ikev2", IKEV2 }, { "include", INCLUDE }, { "inet", INET }, { "inet6", INET6 }, { "ipcomp", IPCOMP }, { "lifetime", LIFETIME }, { "listen", LISTEN }, { "local", LOCAL }, { "max-failovers", MAX_FAILOVERS}, { "max-tries", MAX_TRIES }, { "maxage", MAXAGE }, { "mobike", MOBIKE }, { "name", NAME }, { "natt", NATT }, { "noenforcesingleikesa", NOENFORCESINGLEIKESA }, { "noesn", NOESN }, { "nofragmentation", NOFRAGMENTATION }, { "nomobike", NOMOBIKE }, { "nostickyaddress", NOSTICKYADDRESS }, { "novendorid", NOVENDORID }, { "ocsp", OCSP }, { "on", ON }, { "passive", PASSIVE }, { "peer", PEER }, { "port", PORT }, { "prf", PRFXF }, { "proto", PROTO }, { "psk", PSK }, { "quick", QUICK }, { "radius", RADIUS }, { "rdomain", RDOMAIN }, { "request", REQUEST }, { "sa", SA }, { "secret", SECRET }, { "server", SERVER }, { "set", SET }, { "skip", SKIP }, { "srcid", SRCID }, { "stickyaddress", STICKYADDRESS }, { "tag", TAG }, { "tap", TAP }, { "tcpmd5", TCPMD5 }, { "to", TO }, { "tolerate", TOLERATE }, { "transport", TRANSPORT }, { "tunnel", TUNNEL }, { "user", USER }, { "vendorid", VENDORID } }; 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 START_EXPAND 1 #define DONE_EXPAND 2 static int expanding; int igetc(void) { int c; while (1) { if (file->ungetpos > 0) c = file->ungetbuf[--file->ungetpos]; else c = getc(file->stream); if (c == START_EXPAND) expanding = 1; else if (c == DONE_EXPAND) expanding = 0; else break; } return (c); } int lgetc(int quotec) { int c, next; if (quotec) { if ((c = igetc()) == EOF) { yyerror("reached end of file while parsing " "quoted string"); if (file == topfile || popfile() == EOF) return (EOF); return (quotec); } return (c); } while ((c = igetc()) == '\\') { next = igetc(); if (next != '\n') { c = next; break; } yylval.lineno = file->lineno; file->lineno++; } while (c == EOF) { /* * Fake EOL when hit EOF for the first time. This gets line * count right if last line in included file is syntactically * invalid and has no newline. */ if (file->eof_reached == 0) { file->eof_reached = 1; return ('\n'); } while (c == EOF) { if (file == topfile || popfile() == EOF) return (EOF); c = igetc(); } } return (c); } void lungetc(int c) { if (c == EOF) return; if (file->ungetpos >= file->ungetsize) { void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); if (p == NULL) err(1, "lungetc"); file->ungetbuf = p; file->ungetsize *= 2; } file->ungetbuf[file->ungetpos++] = c; } int findeol(void) { int c; /* skip to either EOF or the first real EOL */ while (1) { c = lgetc(0); if (c == '\n') { file->lineno++; break; } if (c == EOF) break; } return (ERROR); } int 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 == '$' && !expanding) { 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()); } p = val + strlen(val) - 1; lungetc(DONE_EXPAND); while (p >= val) { lungetc((unsigned char)*p); p--; } lungetc(START_EXPAND); 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 = TAILQ_EMPTY(&files) ? 1 : 0; nfile->ungetsize = 16; nfile->ungetbuf = malloc(nfile->ungetsize); if (nfile->ungetbuf == NULL) { warn("%s", __func__); fclose(nfile->stream); free(nfile->name); free(nfile); return (NULL); } TAILQ_INSERT_TAIL(&files, nfile, entry); return (nfile); } int popfile(void) { struct file *prev; if ((prev = TAILQ_PREV(file, files, entry)) != NULL) prev->errors += file->errors; TAILQ_REMOVE(&files, file, entry); fclose(file->stream); free(file->name); free(file->ungetbuf); free(file); file = prev; return (file ? 0 : EOF); } int parse_config(const char *filename, struct iked *x_env) { struct sym *sym; int errors = 0; env = x_env; rules = 0; if ((file = pushfile(filename, 1)) == NULL) return (-1); topfile = file; free(ocsp_url); mobike = 1; enforcesingleikesa = stickyaddress = 0; cert_partial_chain = decouple = passive = 0; ocsp_tolerate = 0; ocsp_url = NULL; ocsp_maxage = -1; fragmentation = 0; dpd_interval = IKED_IKE_SA_ALIVE_TIMEOUT; decouple = passive = 0; ocsp_url = NULL; radauth.max_tries = 3; radauth.max_failovers = 0; radacct.max_tries = 3; radacct.max_failovers = 0; if (env->sc_opts & IKED_OPT_PASSIVE) passive = 1; yyparse(); errors = file->errors; popfile(); env->sc_passive = passive ? 1 : 0; env->sc_decoupled = decouple ? 1 : 0; env->sc_mobike = mobike; env->sc_enforcesingleikesa = enforcesingleikesa; env->sc_stickyaddress = stickyaddress; env->sc_frag = fragmentation; env->sc_alive_timeout = dpd_interval; env->sc_ocsp_url = ocsp_url; env->sc_ocsp_tolerate = ocsp_tolerate; env->sc_ocsp_maxage = ocsp_maxage; env->sc_cert_partial_chain = cert_partial_chain; env->sc_vendorid = vendorid; env->sc_radauth = radauth; env->sc_radacct = radacct; if (!rules) log_warnx("%s: no valid configuration rules found", filename); else log_debug("%s: loaded %d configuration rules", filename, rules); /* Free macros and check which have not been used. */ while ((sym = TAILQ_FIRST(&symhead))) { if (!sym->used) log_debug("warning: macro '%s' not " "used\n", sym->nam); free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entry); free(sym); } iaw_free(iftab); iftab = NULL; 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); } uint8_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 ((uint8_t)strtoul(ss, NULL, 16)); } int parsekey(unsigned char *hexkey, size_t len, struct iked_auth *auth) { unsigned int i; bzero(auth, sizeof(*auth)); if ((len / 2) > sizeof(auth->auth_data)) return (-1); auth->auth_length = len / 2; for (i = 0; i < auth->auth_length; i++) auth->auth_data[i] = x2i(hexkey + 2 * i); return (0); } int parsekeyfile(char *filename, struct iked_auth *auth) { struct stat sb; int fd, ret; unsigned char *hex; if ((fd = open(filename, O_RDONLY)) == -1) err(1, "open %s", filename); if (check_file_secrecy(fd, filename) == -1) exit(1); if (fstat(fd, &sb) == -1) 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, "parsekeyfile: calloc"); if (read(fd, hex, sb.st_size) < sb.st_size) err(1, "parsekeyfile: read"); close(fd); ret = parsekey(hex, sb.st_size, auth); free(hex); return (ret); } int get_id_type(char *string) { struct in6_addr ia; if (string == NULL) return (IKEV2_ID_NONE); if (*string == '/') return (IKEV2_ID_ASN1_DN); else if (inet_pton(AF_INET, string, &ia) == 1) return (IKEV2_ID_IPV4); else if (inet_pton(AF_INET6, string, &ia) == 1) return (IKEV2_ID_IPV6); else if (strchr(string, '@')) return (IKEV2_ID_UFQDN); else return (IKEV2_ID_FQDN); } struct ipsec_addr_wrap * host(const char *s) { struct ipsec_addr_wrap *ipa = NULL; int mask = -1; char *p, *ps; const char *errstr; if ((ps = strdup(s)) == NULL) err(1, "%s: strdup", __func__); if ((p = strchr(ps, '/')) != NULL) { mask = strtonum(p+1, 0, 128, &errstr); if (errstr) { fprintf(stderr, "netmask is %s: %s\n", errstr, p); goto error; } p[0] = '\0'; } if ((ipa = host_if(ps, mask)) == NULL && (ipa = host_ip(ps, mask)) == NULL && (ipa = host_dns(ps, mask)) == NULL) fprintf(stderr, "no IP address found for %s\n", s); error: free(ps); return (ipa); } struct ipsec_addr_wrap * host_ip(const char *s, int mask) { struct ipsec_addr_wrap *ipa = NULL; struct addrinfo hints, *res; char hbuf[NI_MAXHOST]; bzero(&hints, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; /*dummy*/ hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(s, NULL, &hints, &res)) return (NULL); if (res->ai_next) err(1, "%s: %s expanded to multiple item", __func__, s); ipa = calloc(1, sizeof(struct ipsec_addr_wrap)); if (ipa == NULL) err(1, "%s", __func__); ipa->af = res->ai_family; copy_sockaddrtoipa(ipa, res->ai_addr); ipa->next = NULL; ipa->tail = ipa; set_ipmask(ipa, mask); if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST)) { errx(1, "could not get a numeric hostname"); } if (mask > -1) { ipa->netaddress = 1; if (asprintf(&ipa->name, "%s/%d", hbuf, mask) == -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_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; hints.ai_flags = AI_ADDRCONFIG; 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__); copy_sockaddrtoipa(ipa, res->ai_addr); 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; ipa->type = IPSEC_ADDR_ANY; return (ipa); } struct ipsec_addr_wrap * host_dynamic(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->tail = ipa; ipa->type = IPSEC_ADDR_DYNAMIC; return (ipa); } void ifa_load(void) { struct ifaddrs *ifap, *ifa; struct ipsec_addr_wrap *n = NULL, *h = NULL; struct sockaddr_in *sa_in; struct sockaddr_in6 *sa_in6; if (getifaddrs(&ifap) == -1) 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) { sa_in = (struct sockaddr_in *)ifa->ifa_addr; memcpy(&n->address, sa_in, sizeof(*sa_in)); sa_in = (struct sockaddr_in *)ifa->ifa_netmask; n->mask = mask2prefixlen((struct sockaddr *)sa_in); } else if (n->af == AF_INET6) { sa_in6 = (struct sockaddr_in6 *)ifa->ifa_addr; memcpy(&n->address, sa_in6, sizeof(*sa_in6)); sa_in6 = (struct sockaddr_in6 *)ifa->ifa_netmask; n->mask = mask2prefixlen6((struct sockaddr *)sa_in6); } 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 wether 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; struct sockaddr_in6 *in6; uint8_t *s6; 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: in6 = (struct sockaddr_in6 *)&n->address; s6 = (uint8_t *)&in6->sin6_addr.s6_addr; /* route/show.c and bgpd/util.c give KAME credit */ if (IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr)) { uint16_t tmp16; /* for now we can not handle link local, * therefore bail for now */ free(n->name); free(n); continue; memcpy(&tmp16, &s6[2], sizeof(tmp16)); /* use this when we support link-local * n->??.scopeid = ntohs(tmp16); */ s6[2] = 0; s6[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, int b) { if (b == -1) address->mask = address->af == AF_INET ? 32 : 128; else address->mask = b; } const struct ipsec_xf * parse_xf(const char *name, unsigned int length, const struct ipsec_xf xfs[]) { int i; for (i = 0; xfs[i].name != NULL; i++) { if (strncmp(name, xfs[i].name, strlen(name))) continue; if (length == 0 || length == xfs[i].length) return &xfs[i]; } return (NULL); } int encxf_noauth(unsigned int id) { int i; for (i = 0; ikeencxfs[i].name != NULL; i++) if (ikeencxfs[i].id == id) return ikeencxfs[i].noauth; return (0); } size_t keylength_xf(unsigned int saproto, unsigned int type, unsigned int id) { int i; const struct ipsec_xf *xfs; switch (type) { case IKEV2_XFORMTYPE_ENCR: if (saproto == IKEV2_SAPROTO_IKE) xfs = ikeencxfs; else xfs = ipsecencxfs; break; case IKEV2_XFORMTYPE_INTEGR: xfs = authxfs; break; default: return (0); } for (i = 0; xfs[i].name != NULL; i++) { if (xfs[i].id == id) return (xfs[i].length * 8); } return (0); } size_t noncelength_xf(unsigned int type, unsigned int id) { const struct ipsec_xf *xfs = ipsecencxfs; int i; if (type != IKEV2_XFORMTYPE_ENCR) return (0); for (i = 0; xfs[i].name != NULL; i++) if (xfs[i].id == id) return (xfs[i].nonce * 8); return (0); } void copy_transforms(unsigned int type, const struct ipsec_xf **xfs, unsigned int nxfs, struct iked_transform **dst, unsigned int *ndst, struct iked_transform *src, size_t nsrc) { unsigned int i; struct iked_transform *a, *b; const struct ipsec_xf *xf; if (nxfs) { for (i = 0; i < nxfs; i++) { xf = xfs[i]; *dst = recallocarray(*dst, *ndst, *ndst + 1, sizeof(struct iked_transform)); if (*dst == NULL) err(1, "%s", __func__); b = *dst + (*ndst)++; b->xform_type = type; b->xform_id = xf->id; b->xform_keylength = xf->length * 8; b->xform_length = xf->keylength * 8; } return; } for (i = 0; i < nsrc; i++) { a = src + i; if (a->xform_type != type) continue; *dst = recallocarray(*dst, *ndst, *ndst + 1, sizeof(struct iked_transform)); if (*dst == NULL) err(1, "%s", __func__); b = *dst + (*ndst)++; memcpy(b, a, sizeof(*b)); } } int create_ike(char *name, int af, struct ipsec_addr_wrap *ipproto, int rdomain, struct ipsec_hosts *hosts, struct ipsec_hosts *peers, struct ipsec_mode *ike_sa, struct ipsec_mode *ipsec_sa, uint8_t saproto, unsigned int flags, char *srcid, char *dstid, uint32_t ikelifetime, struct iked_lifetime *lt, struct iked_auth *authtype, struct ipsec_filters *filter, struct ipsec_addr_wrap *ikecfg, char *iface) { char idstr[IKED_ID_SIZE]; struct ipsec_addr_wrap *ipa, *ipb, *ipp; struct iked_auth *ikeauth; struct iked_policy pol; struct iked_proposal *p, *ptmp; struct iked_transform *xf; unsigned int i, j, xfi, noauth, auth; unsigned int ikepropid = 1, ipsecpropid = 1; struct iked_flow *flow, *ftmp; static unsigned int policy_id = 0; struct iked_cfg *cfg; int ret = -1; bzero(&pol, sizeof(pol)); bzero(idstr, sizeof(idstr)); pol.pol_id = ++policy_id; pol.pol_certreqtype = env->sc_certreqtype; pol.pol_af = af; pol.pol_saproto = saproto; for (i = 0, ipp = ipproto; ipp; ipp = ipp->next, i++) { if (i >= IKED_IPPROTO_MAX) { yyerror("too many protocols"); return (-1); } pol.pol_ipproto[i] = ipp->type; pol.pol_nipproto++; } pol.pol_flags = flags; pol.pol_rdomain = rdomain; memcpy(&pol.pol_auth, authtype, sizeof(struct iked_auth)); explicit_bzero(authtype, sizeof(*authtype)); if (name != NULL) { if (strlcpy(pol.pol_name, name, sizeof(pol.pol_name)) >= sizeof(pol.pol_name)) { yyerror("name too long"); return (-1); } } else { snprintf(pol.pol_name, sizeof(pol.pol_name), "policy%d", policy_id); } if (iface != NULL) { /* sec(4) */ if (strncmp("sec", iface, strlen("sec")) == 0) pol.pol_flags |= IKED_POLICY_ROUTING; pol.pol_iface = if_nametoindex(iface); if (pol.pol_iface == 0) { yyerror("invalid iface"); return (-1); } } if (srcid) { pol.pol_localid.id_type = get_id_type(srcid); pol.pol_localid.id_length = strlen(srcid); if (strlcpy((char *)pol.pol_localid.id_data, srcid, IKED_ID_SIZE) >= IKED_ID_SIZE) { yyerror("srcid too long"); return (-1); } } if (dstid) { pol.pol_peerid.id_type = get_id_type(dstid); pol.pol_peerid.id_length = strlen(dstid); if (strlcpy((char *)pol.pol_peerid.id_data, dstid, IKED_ID_SIZE) >= IKED_ID_SIZE) { yyerror("dstid too long"); return (-1); } } if (filter != NULL) { if (filter->tag) strlcpy(pol.pol_tag, filter->tag, sizeof(pol.pol_tag)); pol.pol_tap = filter->tap; } if (peers == NULL) { if (pol.pol_flags & IKED_POLICY_ACTIVE) { yyerror("active mode requires peer specification"); return (-1); } pol.pol_flags |= IKED_POLICY_DEFAULT|IKED_POLICY_SKIP; } if (peers && peers->src && peers->dst && (peers->src->af != AF_UNSPEC) && (peers->dst->af != AF_UNSPEC) && (peers->src->af != peers->dst->af)) fatalx("create_ike: peer address family mismatch"); if (peers && (pol.pol_af != AF_UNSPEC) && ((peers->src && (peers->src->af != AF_UNSPEC) && (peers->src->af != pol.pol_af)) || (peers->dst && (peers->dst->af != AF_UNSPEC) && (peers->dst->af != pol.pol_af)))) fatalx("create_ike: policy address family mismatch"); ipa = ipb = NULL; if (peers) { if (peers->src) ipa = peers->src; if (peers->dst) ipb = peers->dst; if (ipa == NULL && ipb == NULL) { if (hosts->src && hosts->src->next == NULL) ipa = hosts->src; if (hosts->dst && hosts->dst->next == NULL) ipb = hosts->dst; } } if (ipa == NULL && ipb == NULL) { yyerror("could not get local/peer specification"); return (-1); } if (pol.pol_flags & IKED_POLICY_ACTIVE) { if (ipb == NULL || ipb->netaddress || (ipa != NULL && ipa->netaddress)) { yyerror("active mode requires local/peer address"); return (-1); } } if (ipa) { memcpy(&pol.pol_local.addr, &ipa->address, sizeof(ipa->address)); pol.pol_local.addr_af = ipa->af; pol.pol_local.addr_mask = ipa->mask; pol.pol_local.addr_net = ipa->netaddress; if (pol.pol_af == AF_UNSPEC) pol.pol_af = ipa->af; } if (ipb) { memcpy(&pol.pol_peer.addr, &ipb->address, sizeof(ipb->address)); pol.pol_peer.addr_af = ipb->af; pol.pol_peer.addr_mask = ipb->mask; pol.pol_peer.addr_net = ipb->netaddress; if (pol.pol_af == AF_UNSPEC) pol.pol_af = ipb->af; } if (ikelifetime) pol.pol_rekey = ikelifetime; if (lt) pol.pol_lifetime = *lt; else pol.pol_lifetime = deflifetime; TAILQ_INIT(&pol.pol_proposals); RB_INIT(&pol.pol_flows); if (ike_sa == NULL || ike_sa->nxfs == 0) { /* AES-GCM proposal */ if ((p = calloc(1, sizeof(*p))) == NULL) err(1, "%s", __func__); p->prop_id = ikepropid++; p->prop_protoid = IKEV2_SAPROTO_IKE; p->prop_nxforms = ikev2_default_nike_transforms_noauth; p->prop_xforms = ikev2_default_ike_transforms_noauth; TAILQ_INSERT_TAIL(&pol.pol_proposals, p, prop_entry); pol.pol_nproposals++; /* Non GCM proposal */ if ((p = calloc(1, sizeof(*p))) == NULL) err(1, "%s", __func__); p->prop_id = ikepropid++; p->prop_protoid = IKEV2_SAPROTO_IKE; p->prop_nxforms = ikev2_default_nike_transforms; p->prop_xforms = ikev2_default_ike_transforms; TAILQ_INSERT_TAIL(&pol.pol_proposals, p, prop_entry); pol.pol_nproposals++; } else { for (i = 0; i < ike_sa->nxfs; i++) { noauth = auth = 0; for (j = 0; j < ike_sa->xfs[i]->nencxf; j++) { if (ike_sa->xfs[i]->encxf[j]->noauth) noauth++; else auth++; } for (j = 0; j < ike_sa->xfs[i]->ngroupxf; j++) { if (ike_sa->xfs[i]->groupxf[j]->id == IKEV2_XFORMDH_NONE) { yyerror("IKE group can not be \"none\"."); goto done; } } if (ike_sa->xfs[i]->nauthxf) auth++; if (ike_sa->xfs[i]->nesnxf) { yyerror("cannot use ESN with ikesa."); goto done; } if (noauth && noauth != ike_sa->xfs[i]->nencxf) { yyerror("cannot mix encryption transforms with " "implicit and non-implicit authentication"); goto done; } if (noauth && ike_sa->xfs[i]->nauthxf) { yyerror("authentication is implicit for given " "encryption transforms"); goto done; } if (!auth) { if ((p = calloc(1, sizeof(*p))) == NULL) err(1, "%s", __func__); xf = NULL; xfi = 0; copy_transforms(IKEV2_XFORMTYPE_ENCR, ike_sa->xfs[i]->encxf, ike_sa->xfs[i]->nencxf, &xf, &xfi, ikev2_default_ike_transforms_noauth, ikev2_default_nike_transforms_noauth); copy_transforms(IKEV2_XFORMTYPE_DH, ike_sa->xfs[i]->groupxf, ike_sa->xfs[i]->ngroupxf, &xf, &xfi, ikev2_default_ike_transforms_noauth, ikev2_default_nike_transforms_noauth); copy_transforms(IKEV2_XFORMTYPE_PRF, ike_sa->xfs[i]->prfxf, ike_sa->xfs[i]->nprfxf, &xf, &xfi, ikev2_default_ike_transforms_noauth, ikev2_default_nike_transforms_noauth); p->prop_id = ikepropid++; p->prop_protoid = IKEV2_SAPROTO_IKE; p->prop_xforms = xf; p->prop_nxforms = xfi; TAILQ_INSERT_TAIL(&pol.pol_proposals, p, prop_entry); pol.pol_nproposals++; } if (!noauth) { if ((p = calloc(1, sizeof(*p))) == NULL) err(1, "%s", __func__); xf = NULL; xfi = 0; copy_transforms(IKEV2_XFORMTYPE_INTEGR, ike_sa->xfs[i]->authxf, ike_sa->xfs[i]->nauthxf, &xf, &xfi, ikev2_default_ike_transforms, ikev2_default_nike_transforms); copy_transforms(IKEV2_XFORMTYPE_ENCR, ike_sa->xfs[i]->encxf, ike_sa->xfs[i]->nencxf, &xf, &xfi, ikev2_default_ike_transforms, ikev2_default_nike_transforms); copy_transforms(IKEV2_XFORMTYPE_DH, ike_sa->xfs[i]->groupxf, ike_sa->xfs[i]->ngroupxf, &xf, &xfi, ikev2_default_ike_transforms, ikev2_default_nike_transforms); copy_transforms(IKEV2_XFORMTYPE_PRF, ike_sa->xfs[i]->prfxf, ike_sa->xfs[i]->nprfxf, &xf, &xfi, ikev2_default_ike_transforms, ikev2_default_nike_transforms); p->prop_id = ikepropid++; p->prop_protoid = IKEV2_SAPROTO_IKE; p->prop_xforms = xf; p->prop_nxforms = xfi; TAILQ_INSERT_TAIL(&pol.pol_proposals, p, prop_entry); pol.pol_nproposals++; } } } if (ipsec_sa == NULL || ipsec_sa->nxfs == 0) { if ((p = calloc(1, sizeof(*p))) == NULL) err(1, "%s", __func__); p->prop_id = ipsecpropid++; p->prop_protoid = saproto; p->prop_nxforms = ikev2_default_nesp_transforms_noauth; p->prop_xforms = ikev2_default_esp_transforms_noauth; TAILQ_INSERT_TAIL(&pol.pol_proposals, p, prop_entry); pol.pol_nproposals++; if ((p = calloc(1, sizeof(*p))) == NULL) err(1, "%s", __func__); p->prop_id = ipsecpropid++; p->prop_protoid = saproto; p->prop_nxforms = ikev2_default_nesp_transforms; p->prop_xforms = ikev2_default_esp_transforms; TAILQ_INSERT_TAIL(&pol.pol_proposals, p, prop_entry); pol.pol_nproposals++; } else { for (i = 0; i < ipsec_sa->nxfs; i++) { noauth = auth = 0; for (j = 0; j < ipsec_sa->xfs[i]->nencxf; j++) { if (ipsec_sa->xfs[i]->encxf[j]->noauth) noauth++; else auth++; } if (ipsec_sa->xfs[i]->nauthxf) auth++; if (noauth && noauth != ipsec_sa->xfs[i]->nencxf) { yyerror("cannot mix encryption transforms with " "implicit and non-implicit authentication"); goto done; } if (noauth && ipsec_sa->xfs[i]->nauthxf) { yyerror("authentication is implicit for given " "encryption transforms"); goto done; } if (!auth) { if ((p = calloc(1, sizeof(*p))) == NULL) err(1, "%s", __func__); xf = NULL; xfi = 0; copy_transforms(IKEV2_XFORMTYPE_ENCR, ipsec_sa->xfs[i]->encxf, ipsec_sa->xfs[i]->nencxf, &xf, &xfi, ikev2_default_esp_transforms_noauth, ikev2_default_nesp_transforms_noauth); copy_transforms(IKEV2_XFORMTYPE_DH, ipsec_sa->xfs[i]->groupxf, ipsec_sa->xfs[i]->ngroupxf, &xf, &xfi, ikev2_default_esp_transforms_noauth, ikev2_default_nesp_transforms_noauth); copy_transforms(IKEV2_XFORMTYPE_ESN, ipsec_sa->xfs[i]->esnxf, ipsec_sa->xfs[i]->nesnxf, &xf, &xfi, ikev2_default_esp_transforms_noauth, ikev2_default_nesp_transforms_noauth); p->prop_id = ipsecpropid++; p->prop_protoid = saproto; p->prop_xforms = xf; p->prop_nxforms = xfi; TAILQ_INSERT_TAIL(&pol.pol_proposals, p, prop_entry); pol.pol_nproposals++; } if (!noauth) { if ((p = calloc(1, sizeof(*p))) == NULL) err(1, "%s", __func__); xf = NULL; xfi = 0; copy_transforms(IKEV2_XFORMTYPE_INTEGR, ipsec_sa->xfs[i]->authxf, ipsec_sa->xfs[i]->nauthxf, &xf, &xfi, ikev2_default_esp_transforms, ikev2_default_nesp_transforms); copy_transforms(IKEV2_XFORMTYPE_ENCR, ipsec_sa->xfs[i]->encxf, ipsec_sa->xfs[i]->nencxf, &xf, &xfi, ikev2_default_esp_transforms, ikev2_default_nesp_transforms); copy_transforms(IKEV2_XFORMTYPE_DH, ipsec_sa->xfs[i]->groupxf, ipsec_sa->xfs[i]->ngroupxf, &xf, &xfi, ikev2_default_esp_transforms, ikev2_default_nesp_transforms); copy_transforms(IKEV2_XFORMTYPE_ESN, ipsec_sa->xfs[i]->esnxf, ipsec_sa->xfs[i]->nesnxf, &xf, &xfi, ikev2_default_esp_transforms, ikev2_default_nesp_transforms); p->prop_id = ipsecpropid++; p->prop_protoid = saproto; p->prop_xforms = xf; p->prop_nxforms = xfi; TAILQ_INSERT_TAIL(&pol.pol_proposals, p, prop_entry); pol.pol_nproposals++; } } } for (ipa = hosts->src, ipb = hosts->dst; ipa && ipb; ipa = ipa->next, ipb = ipb->next) { for (j = 0; j < pol.pol_nipproto; j++) if (expand_flows(&pol, pol.pol_ipproto[j], ipa, ipb)) fatalx("create_ike: invalid flow"); if (pol.pol_nipproto == 0) if (expand_flows(&pol, 0, ipa, ipb)) fatalx("create_ike: invalid flow"); } for (j = 0, ipa = ikecfg; ipa; ipa = ipa->next, j++) { if (j >= IKED_CFG_MAX) break; cfg = &pol.pol_cfg[j]; pol.pol_ncfg++; cfg->cfg_action = ipa->action; cfg->cfg_type = ipa->type; memcpy(&cfg->cfg.address.addr, &ipa->address, sizeof(ipa->address)); cfg->cfg.address.addr_mask = ipa->mask; cfg->cfg.address.addr_net = ipa->netaddress; cfg->cfg.address.addr_af = ipa->af; } if (dstid) strlcpy(idstr, dstid, sizeof(idstr)); else if (!pol.pol_peer.addr_net) strlcpy(idstr, print_addr(&pol.pol_peer.addr), sizeof(idstr)); ikeauth = &pol.pol_auth; switch (ikeauth->auth_method) { case IKEV2_AUTH_RSA_SIG: pol.pol_certreqtype = IKEV2_CERT_RSA_KEY; break; case IKEV2_AUTH_ECDSA_256: case IKEV2_AUTH_ECDSA_384: case IKEV2_AUTH_ECDSA_521: pol.pol_certreqtype = IKEV2_CERT_ECDSA; break; default: pol.pol_certreqtype = IKEV2_CERT_NONE; break; } log_debug("%s: using %s for peer %s", __func__, print_xf(ikeauth->auth_method, 0, methodxfs), idstr); config_setpolicy(env, &pol, PROC_IKEV2); config_setflow(env, &pol, PROC_IKEV2); rules++; ret = 0; done: if (ike_sa) { for (i = 0; i < ike_sa->nxfs; i++) { free(ike_sa->xfs[i]->authxf); free(ike_sa->xfs[i]->encxf); free(ike_sa->xfs[i]->groupxf); free(ike_sa->xfs[i]->prfxf); free(ike_sa->xfs[i]); } free(ike_sa->xfs); free(ike_sa); } if (ipsec_sa) { for (i = 0; i < ipsec_sa->nxfs; i++) { free(ipsec_sa->xfs[i]->authxf); free(ipsec_sa->xfs[i]->encxf); free(ipsec_sa->xfs[i]->groupxf); free(ipsec_sa->xfs[i]->prfxf); free(ipsec_sa->xfs[i]->esnxf); free(ipsec_sa->xfs[i]); } free(ipsec_sa->xfs); free(ipsec_sa); } TAILQ_FOREACH_SAFE(p, &pol.pol_proposals, prop_entry, ptmp) { if (p->prop_xforms != ikev2_default_ike_transforms && p->prop_xforms != ikev2_default_ike_transforms_noauth && p->prop_xforms != ikev2_default_esp_transforms && p->prop_xforms != ikev2_default_esp_transforms_noauth) free(p->prop_xforms); free(p); } if (peers != NULL) { iaw_free(peers->src); iaw_free(peers->dst); /* peers is static, cannot be freed */ } if (hosts != NULL) { iaw_free(hosts->src); iaw_free(hosts->dst); free(hosts); } iaw_free(ikecfg); iaw_free(ipproto); RB_FOREACH_SAFE(flow, iked_flows, &pol.pol_flows, ftmp) { RB_REMOVE(iked_flows, &pol.pol_flows, flow); free(flow); } free(name); free(srcid); free(dstid); return (ret); } static int create_flow(struct iked_policy *pol, int proto, struct ipsec_addr_wrap *ipa, struct ipsec_addr_wrap *ipb) { struct iked_flow *flow; struct ipsec_addr_wrap *ippn; if (ipa->af != ipb->af) { yyerror("cannot mix different address families."); return (-1); } if ((flow = calloc(1, sizeof(struct iked_flow))) == NULL) fatalx("%s: failed to alloc flow.", __func__); memcpy(&flow->flow_src.addr, &ipa->address, sizeof(ipa->address)); flow->flow_src.addr_af = ipa->af; flow->flow_src.addr_mask = ipa->mask; flow->flow_src.addr_net = ipa->netaddress; flow->flow_src.addr_port = ipa->port; memcpy(&flow->flow_dst.addr, &ipb->address, sizeof(ipb->address)); flow->flow_dst.addr_af = ipb->af; flow->flow_dst.addr_mask = ipb->mask; flow->flow_dst.addr_net = ipb->netaddress; flow->flow_dst.addr_port = ipb->port; ippn = ipa->srcnat; if (ippn) { memcpy(&flow->flow_prenat.addr, &ippn->address, sizeof(ippn->address)); flow->flow_prenat.addr_af = ippn->af; flow->flow_prenat.addr_mask = ippn->mask; flow->flow_prenat.addr_net = ippn->netaddress; } else { flow->flow_prenat.addr_af = 0; } flow->flow_dir = IPSP_DIRECTION_OUT; flow->flow_ipproto = proto; flow->flow_saproto = pol->pol_saproto; flow->flow_rdomain = pol->pol_rdomain; if (RB_INSERT(iked_flows, &pol->pol_flows, flow) == NULL) pol->pol_nflows++; else { warnx("create_ike: duplicate flow"); free(flow); } return (0); } static int expand_flows(struct iked_policy *pol, int proto, struct ipsec_addr_wrap *src, struct ipsec_addr_wrap *dst) { struct ipsec_addr_wrap *ipa = NULL, *ipb = NULL; int ret = -1; int srcaf, dstaf; srcaf = src->af; dstaf = dst->af; if (src->af == AF_UNSPEC && dst->af == AF_UNSPEC) { /* Need both IPv4 and IPv6 flows */ src->af = dst->af = AF_INET; ipa = expand_keyword(src); ipb = expand_keyword(dst); if (!ipa || !ipb) goto done; if (create_flow(pol, proto, ipa, ipb)) goto done; iaw_free(ipa); iaw_free(ipb); src->af = dst->af = AF_INET6; ipa = expand_keyword(src); ipb = expand_keyword(dst); if (!ipa || !ipb) goto done; if (create_flow(pol, proto, ipa, ipb)) goto done; } else if (src->af == AF_UNSPEC) { src->af = dst->af; ipa = expand_keyword(src); if (!ipa) goto done; if (create_flow(pol, proto, ipa, dst)) goto done; } else if (dst->af == AF_UNSPEC) { dst->af = src->af; ipa = expand_keyword(dst); if (!ipa) goto done; if (create_flow(pol, proto, src, ipa)) goto done; } else if (create_flow(pol, proto, src, dst)) goto done; ret = 0; done: src->af = srcaf; dst->af = dstaf; iaw_free(ipa); iaw_free(ipb); return (ret); } static struct ipsec_addr_wrap * expand_keyword(struct ipsec_addr_wrap *ip) { switch(ip->af) { case AF_INET: switch(ip->type) { case IPSEC_ADDR_ANY: return (host("0.0.0.0/0")); case IPSEC_ADDR_DYNAMIC: return (host("0.0.0.0")); } break; case AF_INET6: switch(ip->type) { case IPSEC_ADDR_ANY: return (host("::/0")); case IPSEC_ADDR_DYNAMIC: return (host("::")); } } return (NULL); } int create_user(const char *user, const char *pass) { struct iked_user usr; bzero(&usr, sizeof(usr)); if (*user == '\0' || (strlcpy(usr.usr_name, user, sizeof(usr.usr_name)) >= sizeof(usr.usr_name))) { yyerror("invalid user name"); return (-1); } if (*pass == '\0' || (strlcpy(usr.usr_pass, pass, sizeof(usr.usr_pass)) >= sizeof(usr.usr_pass))) { yyerror("invalid password"); explicit_bzero(&usr, sizeof usr); /* zap partial password */ return (-1); } config_setuser(env, &usr, PROC_IKEV2); rules++; explicit_bzero(&usr, sizeof usr); return (0); } void iaw_free(struct ipsec_addr_wrap *head) { struct ipsec_addr_wrap *n, *cur; if (head == NULL) return; for (n = head; n != NULL; ) { cur = n; n = n->next; if (cur->srcnat != NULL) { free(cur->srcnat->name); free(cur->srcnat); } free(cur->name); free(cur); } }