/* $OpenBSD: parse.y,v 1.169 2017/10/27 08:29:32 mpi Exp $ */ /* * 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. * Copyright (c) 2004, 2005 Hans-Joerg Hoexer * * 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 "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; 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 }, { "lzs", COMPXF_LZS, 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, 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; 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 { 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 %token STRING %token NUMBER %type string %type dir %type satype %type proto %type protoval %type tmode %type hosts %type port %type portval %type peers %type anyhost %type singlehost %type host host_list host_spec %type ids %type id %type spispec %type authkeyspec %type enckeyspec %type bundlestring %type keyspec %type transforms %type ikemode %type ikeauth %type type %type lifetime %type phase1mode phase2mode %type 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, 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 transforms authkeyspec enckeyspec bundlestring { struct ipsec_rule *r; r = create_sa($1, $2, &$3, $4.spiout, $5, $6.keyout, $7.keyout); if (r == NULL) YYERROR; if (expand_rule(r, NULL, 0, $4.spiin, $6.keyin, $7.keyin, $8)) 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"); } ; 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; } ; 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; } ; 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"); 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 }, { "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 }, { "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 u_char *parsebuf; int parseindex; u_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 = parsebuf[parseindex++]; if (c != '\0') return (c); parsebuf = NULL; } else parseindex++; } if (pushback_index) return (pushback_buffer[--pushback_index]); if (quotec) { if ((c = getc(file->stream)) == EOF) { yyerror("reached end of file while parsing quoted string"); if (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 (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 < MAXPUSHBACK-1) return (pushback_buffer[pushback_index++] = c); else return (EOF); } int findeol(void) { int c; parsebuf = NULL; /* skip to either EOF or the first real EOL */ while (1) { if (pushback_index) c = pushback_buffer[--pushback_index]; else c = lgetc(0); if (c == '\n') { file->lineno++; break; } if (c == EOF) break; } return (ERROR); } int yylex(void) { u_char buf[8096]; u_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 || c == ' ' || c == '\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, "yylex: strdup"); return (STRING); } #define allowed_to_end_number(x) \ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') if (c == '-' || isdigit(c)) { do { *p++ = c; if ((unsigned)(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(*--p); c = *--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 ((unsigned)(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, "yylex: strdup"); return (token); } if (c == '\n') { yylval.lineno = file->lineno; file->lineno++; } if (c == EOF) return (0); return (c); } int check_file_secrecy(int fd, const char *fname) { struct stat st; if (fstat(fd, &st)) { 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("malloc"); return (NULL); } if ((nfile->name = strdup(name)) == NULL) { warn("malloc"); 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("strdup"); free(nfile); return (NULL); } } else if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { warn("%s", 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 (0); } return (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); } 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; size_t len; if ((val = strrchr(s, '=')) == NULL) return (-1); len = strlen(s) - strlen(val) + 1; if ((sym = malloc(len)) == NULL) err(1, "cmdline_symset: malloc"); strlcpy(sym, s, len); 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, "parsekey: calloc"); key->len = len / 2; key->data = calloc(key->len, sizeof(u_int8_t)); if (key->data == NULL) err(1, "parsekey: calloc"); 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, "parsekeyfile: calloc"); 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, "host: calloc"); strlcpy(ps, s, strlen(s) - strlen(p) + 1); } else { if ((ps = strdup(s)) == NULL) err(1, "host: strdup"); 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, "host_v6: calloc"); 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, "host_v6: asprintf"); } else { if ((ipa->name = strdup(hbuf)) == NULL) err(1, "host_v6: strdup"); } 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, "host_v4: calloc"); ipa->address.v4 = ina; ipa->name = strdup(s); if (ipa->name == NULL) err(1, "host_v4: strdup"); 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, "host_dns: calloc"); 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, "host_dns: strdup"); 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, "host_any: calloc"); 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->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, "ifa_load: calloc"); n->af = ifa->ifa_addr->sa_family; if ((n->name = strdup(ifa->ifa_name)) == NULL) err(1, "ifa_load: strdup"); 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, "calloc"); 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, "ifa_lookup: calloc"); memcpy(n, p, sizeof(struct ipsec_addr_wrap)); if ((n->name = strdup(p->name)) == NULL) err(1, "ifa_lookup: strdup"); 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, "calloc"); 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, "copytransforms: calloc"); 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, "copylife: calloc"); 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, "calloc"); if (auth->srcid && asprintf(&newauth->srcid, "%s", auth->srcid) == -1) err(1, "asprintf"); if (auth->dstid && asprintf(&newauth->dstid, "%s", auth->dstid) == -1) err(1, "asprintf"); 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, "calloc"); if (auth->string && asprintf(&newauth->string, "%s", auth->string) == -1) err(1, "asprintf"); 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, "calloc"); if ((newkey->data = calloc(key->len, sizeof(u_int8_t))) == NULL) err(1, "calloc"); 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, "copyhost: calloc"); memcpy(dst, src, sizeof(struct ipsec_addr_wrap)); if (src->name != NULL && (dst->name = strdup(src->name)) == NULL) err(1, "copyhost: strdup"); return dst; } char * copytag(const char *src) { char *tag; if (src == NULL) return (NULL); if ((tag = strdup(src)) == NULL) err(1, "copytag: strdup"); 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, "calloc"); 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->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->nr = rule->nr; 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, 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, "create_sa: calloc"); r->type |= RULE_SA; r->satype = satype; r->tmode = tmode; r->src = hosts->src; r->dst = hosts->dst; r->spi = spi; 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, "reverse_sa: calloc"); 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->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, "create_sabundle: calloc"); 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, "create_flow: calloc"); 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, "create_flow: calloc"); 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, "expand_any: strdup"); ipa->next = calloc(1, sizeof(struct ipsec_addr_wrap)); if (ipa->next == NULL) err(1, "expand_any: calloc"); ipa->next->af = AF_INET6; ipa->next->netaddress = 1; if ((ipa->next->name = strdup("::/0")) == NULL) err(1, "expand_any: strdup"); 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, "reverse_rule: calloc"); 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, "reverse_rule: calloc"); if (rule->auth->dstid && (reverse->auth->dstid = strdup(rule->auth->dstid)) == NULL) err(1, "reverse_rule: strdup"); if (rule->auth->srcid && (reverse->auth->srcid = strdup(rule->auth->srcid)) == NULL) err(1, "reverse_rule: strdup"); 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, "create_ike: calloc"); 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, "create_ike: calloc"); 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, "create_ike: calloc"); 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; }