diff options
-rw-r--r-- | sbin/ipsecctl/Makefile | 16 | ||||
-rw-r--r-- | sbin/ipsecctl/ipsec.conf.5 | 117 | ||||
-rw-r--r-- | sbin/ipsecctl/ipsecctl.8 | 36 | ||||
-rw-r--r-- | sbin/ipsecctl/ipsecctl.c | 414 | ||||
-rw-r--r-- | sbin/ipsecctl/ipsecctl.h | 88 | ||||
-rw-r--r-- | sbin/ipsecctl/parse.y | 739 | ||||
-rw-r--r-- | sbin/ipsecctl/pfkey.c | 401 |
7 files changed, 1811 insertions, 0 deletions
diff --git a/sbin/ipsecctl/Makefile b/sbin/ipsecctl/Makefile new file mode 100644 index 00000000000..cf171012ce4 --- /dev/null +++ b/sbin/ipsecctl/Makefile @@ -0,0 +1,16 @@ +# $Id: Makefile,v 1.1 2005/04/04 22:19:50 hshoexer Exp $ + +PROG= ipsecctl +MAN= ipsecctl.8 ipsec.conf.5 + +SRCS= ipsecctl.c pfkey.c parse.y + +CFLAGS+= -Wall -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare + +YFLAGS= + +.include <bsd.prog.mk> diff --git a/sbin/ipsecctl/ipsec.conf.5 b/sbin/ipsecctl/ipsec.conf.5 new file mode 100644 index 00000000000..4ef7288e2d3 --- /dev/null +++ b/sbin/ipsecctl/ipsec.conf.5 @@ -0,0 +1,117 @@ +.\" $Id: ipsec.conf.5,v 1.1 2005/04/04 22:19:50 hshoexer Exp $ +.\" +.\" Copyright (c) 2004 Mathieu Sauve-Frankel All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd July 7, 2004 +.Dt IPSEC.CONF 5 +.Os +.Sh NAME +.Nm ipsec.conf +.Nd IPsec configuration file +.Sh DESCRIPTION +.Nm +.Xr ipsec 4 +.Sh EXAMPLES +.Bd -literal +.Ed +.Sh GRAMMAR +.Nm +in BNF: +.Bd -literal +line = ( flow-rule | group-rule ) + +flow-rule = "flow" [( ah-rule | esp-rule | ipip-rule | ipcomp-rule )] + +group-rule = "group" "{" flow-rule, flow-rule, ... "}" + +ah-rule = "ah" [ "transport" ] [ ( "in" | "out" ) ] [ "log" ] + [ "on" ifspec ] hosts [ peers ] [ spi ] [ xform ] [ key ] + [ tag ] + +ipip-rule = "ipip" [ ( "in" | "out" ) ] [ "log" ] + [ "on" ifspec ] hosts [ peers ] [ spi ] [ tag ] + +esp-rule = "esp" [ "transport" ] [ ( "in" | "out" ) ] [ "log" ] + [ "on" ifspec ] hosts [ peers ] [ spi ] [ xform ] [ key ] + [ tag ] + +ipcomp-rule = "ipcomp" [ ( "in" | "out" ) ] [ "log" ] [ "on" ifspec ] + hosts [ peers ] [ cpi ] + +hosts = "from" [ + [ port ] "to" ( "any" | "self" | host ) [ port ] + +cpi = "cpi" cpispec + +cpispec = ( number | number:number ) + +spi = "spi" spispec + +spispec = ( number | number:number ) + +peers = "peer" + +xform = "xform" xformspec + +xformspec = ( tranform | transform:transform ) + +transform = ( enc | enc-auth | auth ) + +auth = ( "md5" | "rmd160" | "sha1" | "sha2-256" | "sha2-384" | + "sha2-512" ) + +enc = ( "aes" | "blowfish" | "cast" | "des" | "3des" | "skipjack" ) + +key = "key" string + +ipspec = "any" | host | "{" host-list "}" + +host = [ "!" ] ( address [ "/" mask-bits ] | "<" string ">" ) + +address = ( interface-name | "(" interface-name ")" | hostname | + ipv4-dotted-quad | ipv6-coloned-hex ) + +.Ed +.\" The following requests should be uncommented and used where appropriate. +.\" This next request is for sections 2, 3, and 9 function return values only. +.\" .Sh RETURN VALUES +.\" This next request is for sections 1, 6, 7 & 8 only. +.\" .Sh ENVIRONMENT +.\" .Sh FILES +.\" .Sh EXAMPLES +.\" This next request is for sections 1, 4, 6, and 8 only. +.\" .Sh DIAGNOSTICS +.\" The next request is for sections 2, 3, and 9 error and signal handling only. +.\" .Sh ERRORS +.\" .Sh SEE ALSO +.\" .Xr foobar 1 +.\" .Sh STANDARDS +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 3.7 +.\" .Sh CAVEATS +.\" .Sh BUGS diff --git a/sbin/ipsecctl/ipsecctl.8 b/sbin/ipsecctl/ipsecctl.8 new file mode 100644 index 00000000000..f12049fb263 --- /dev/null +++ b/sbin/ipsecctl/ipsecctl.8 @@ -0,0 +1,36 @@ +.\" $Id: ipsecctl.8,v 1.1 2005/04/04 22:19:50 hshoexer Exp $ +.\" +.\" The following requests are required for all man pages. +.Dd Month DD, YYYY +.Dt NAME SECTION# +.Os +.Sh NAME +.Nm program +.Nd one line about what it does +.Sh SYNOPSIS +.\" For a program: program [-abc] file ... +.Nm program +.Op Fl abc +.Ar +.Sh DESCRIPTION +The +.Nm +utility processes files ... +.\" The following requests should be uncommented and used where appropriate. +.\" This next request is for sections 2, 3, and 9 function return values only. +.\" .Sh RETURN VALUES +.\" This next request is for sections 1, 6, 7 & 8 only. +.\" .Sh ENVIRONMENT +.\" .Sh FILES +.\" .Sh EXAMPLES +.\" This next request is for sections 1, 4, 6, and 8 only. +.\" .Sh DIAGNOSTICS +.\" The next request is for sections 2, 3, and 9 error and signal handling only. +.\" .Sh ERRORS +.\" .Sh SEE ALSO +.\" .Xr foobar 1 +.\" .Sh STANDARDS +.\" .Sh HISTORY +.\" .Sh AUTHORS +.\" .Sh CAVEATS +.\" .Sh BUGS diff --git a/sbin/ipsecctl/ipsecctl.c b/sbin/ipsecctl/ipsecctl.c new file mode 100644 index 00000000000..25c15585075 --- /dev/null +++ b/sbin/ipsecctl/ipsecctl.c @@ -0,0 +1,414 @@ +/* $Id: ipsecctl.c,v 1.1 2005/04/04 22:19:50 hshoexer Exp $ */ +/* + * Copyright (c) 2004, 2005 Hans-Joerg Hoexer <hshoexer@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/queue.h> +#include <sys/stat.h> +#include <net/pfkeyv2.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/ip_ipsp.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include "ipsecctl.h" + +int ipsecctl_rules(char *, int); +FILE *ipsecctl_fopen(const char *, const char *); +int ipsecctl_commit(struct ipsecctl *); +int ipsecctl_add_rule(struct ipsecctl *, struct ipsec_rule *); +void ipsecctl_print_addr(struct ipsec_addr *); +void ipsecctl_print_rule(struct ipsec_rule *, int); +int ipsecctl_flush(int); +void ipsecctl_get_rules(struct ipsecctl *); +void ipsecctl_show(int); +void usage(void); + +const char *infile; /* Used by parse.y */ + +int +ipsecctl_rules(char *filename, int opts) +{ + FILE *fin; + struct ipsecctl ipsec; + int error = 0; + + memset(&ipsec, 0, sizeof(ipsec)); + ipsec.opts = opts; + TAILQ_INIT(&ipsec.rule_queue); + + if (strcmp(filename, "-") == 0) { + fin = stdin; + infile = "stdin"; + } else { + if ((fin = ipsecctl_fopen(filename, "r")) == NULL) { + warn("%s", filename); + return 1; + } + infile = filename; + } + + if (parse_rules(fin, &ipsec) < 0) { + warnx("Syntax error in config file: ipsec rules not loaded"); + error = 1; + } + if (((opts & IPSECCTL_OPT_NOACTION) == 0) && (error == 0)) + if (ipsecctl_commit(&ipsec)) + err(1, NULL); + + return error; +} + +FILE * +ipsecctl_fopen(const char *name, const char *mode) +{ + struct stat st; + FILE *fp; + + fp = fopen(name, mode); + if (fp == NULL) + return NULL; + + if (fstat(fileno(fp), &st)) { + fclose(fp); + return NULL; + } + if (S_ISDIR(st.st_mode)) { + fclose(fp); + errno = EISDIR; + return NULL; + } + return fp; +} + +int +ipsecctl_commit(struct ipsecctl *ipsec) +{ + struct ipsec_rule *rp; + + if (pfkey_init() == -1) + errx(1, "failed to open PF_KEY socket"); + + while ((rp = TAILQ_FIRST(&ipsec->rule_queue))) { + TAILQ_REMOVE(&ipsec->rule_queue, rp, entries); + + if (pfkey_ipsec_establish(rp) == -1) + warnx("failed to add rule %d", rp->nr); + + free(rp->src); + free(rp->dst); + free(rp->peer); + if (rp->auth.srcid) + free(rp->auth.srcid); + if (rp->auth.dstid) + free(rp->auth.dstid); + free(rp); + } + + return 0; +} + +int +ipsecctl_add_rule(struct ipsecctl *ipsec, struct ipsec_rule *r) +{ + TAILQ_INSERT_TAIL(&ipsec->rule_queue, r, entries); + + if ((ipsec->opts & IPSECCTL_OPT_VERBOSE) && !(ipsec->opts & + IPSECCTL_OPT_SHOW)) + ipsecctl_print_rule(r, ipsec->opts & IPSECCTL_OPT_VERBOSE2); + + return 0; +} + +void +ipsecctl_print_addr(struct ipsec_addr *ipa) +{ + u_int32_t mask; + char buf[48]; + + if (ipa == NULL) { + printf("?"); + return; + } + if (inet_ntop(ipa->af, &ipa->v4, buf, sizeof(buf)) == NULL) + printf("?"); + else + printf("%s", buf); + + if (ipa->v4mask.mask32 != 0xffffffff) { + mask = ntohl(ipa->v4mask.mask32); + if (mask == 0) + printf("/0"); + else + printf("/%d", 32 - ffs((int) mask) + 1); + } +} + +void +ipsecctl_print_rule(struct ipsec_rule *r, int verbose) +{ + static const char *direction[] = {"?", "in", "out"}; + static const char *proto[] = {"?", "esp", "ah"}; + static const char *auth[] = {"?", "psk", "rsa"}; + + if (verbose) + printf("@%d ", r->nr); + + printf("flow %s %s", proto[r->proto], direction[r->direction]); + printf(" from "); + ipsecctl_print_addr(r->src); + printf(" to "); + ipsecctl_print_addr(r->dst); + printf(" peer "); + ipsecctl_print_addr(r->peer); + + if (r->auth.srcid) + printf(" srcid %s", r->auth.srcid); + if (r->auth.dstid) + printf(" dstid %s", r->auth.dstid); + + if (r->auth.type > 0) + printf(" %s", auth[r->auth.type]); + + printf("\n"); +} + +int +ipsecctl_flush(int opts) +{ + if (opts & IPSECCTL_OPT_NOACTION) + return 0; + + if (pfkey_init() == -1) + errx(1, "failed to open PF_KEY socket"); + + pfkey_ipsec_flush(); + + return 0; +} + +void +ipsecctl_get_rules(struct ipsecctl *ipsec) +{ + struct ipsec_policy *ipo; + struct ipsec_rule *rule; + int mib[4]; + size_t need; + char *buf, *lim, *next; + + mib[0] = CTL_NET; + mib[1] = PF_KEY; + mib[2] = PF_KEY_V2; + mib[3] = NET_KEY_SPD_DUMP; + + if (sysctl(mib, 4, NULL, &need, NULL, 0) == -1) + err(1, "sysctl"); + + if (need == 0) + return; + if ((buf = malloc(need)) == NULL) + err(1, "malloc"); + if (sysctl(mib, 4, buf, &need, NULL, 0) == -1) + err(1, "sysctl"); + + lim = buf + need; + for (next = buf; next < lim; next += sizeof(struct ipsec_policy)) { + ipo = (struct ipsec_policy *)next; + + /* + * We only want static policies and are not interrested in + * policies attached to sockets. + */ + if (ipo->ipo_flags & IPSP_POLICY_SOCKET) + continue; + + rule = calloc(1, sizeof(struct ipsec_rule)); + if (rule == NULL) + err(1, "malloc"); + rule->nr = ipsec->rule_nr++; + + /* Source and destination. */ + if (ipo->ipo_addr.sen_type == SENT_IP4) { + rule->src = calloc(1, sizeof(struct ipsec_addr)); + if (rule->src == NULL) + err(1, "calloc"); + rule->src->af = AF_INET; + + bcopy(&ipo->ipo_addr.sen_ip_src.s_addr, &rule->src->v4, + sizeof(struct in_addr)); + bcopy(&ipo->ipo_mask.sen_ip_src.s_addr, + &rule->src->v4mask.mask, sizeof(struct in_addr)); + + rule->dst = calloc(1, sizeof(struct ipsec_addr)); + if (rule->dst == NULL) + err(1, "calloc"); + rule->dst->af = AF_INET; + + bcopy(&ipo->ipo_addr.sen_ip_dst.s_addr, &rule->dst->v4, + sizeof(struct in_addr)); + bcopy(&ipo->ipo_mask.sen_ip_dst.s_addr, + &rule->dst->v4mask.mask, sizeof(struct in_addr)); + } else + warnx("unsupported encapsulation policy type %d", + ipo->ipo_addr.sen_type); + + /* IPsec gateway. */ + if (ipo->ipo_dst.sa.sa_family == AF_INET) { + rule->peer = calloc(1, sizeof(struct ipsec_addr)); + if (rule->peer == NULL) + err(1, "calloc"); + rule->peer->af = AF_INET; + + bcopy(&((struct sockaddr_in *)&ipo->ipo_dst.sa)->sin_addr, + &rule->peer->v4, sizeof(struct in_addr)); + + /* No netmask for peer. */ + memset(&rule->peer->v4mask, 0xff, sizeof(u_int32_t)); + + if (ipo->ipo_sproto == IPPROTO_ESP) + rule->proto = IPSEC_ESP; + else if (ipo->ipo_sproto == IPPROTO_AH) + rule->proto = IPSEC_AH; + else { + rule->proto = PROTO_UNKNWON; + warnx("unsupported protocol %d", + ipo->ipo_sproto); + } + + if (ipo->ipo_addr.sen_direction == IPSP_DIRECTION_OUT) + rule->direction = IPSEC_OUT; + else if (ipo->ipo_addr.sen_direction == IPSP_DIRECTION_IN) + rule->direction = IPSEC_IN; + else { + rule->direction = DIRECTION_UNKNOWN; + warnx("bogus direction %d", + ipo->ipo_addr.sen_direction); + } + } else + warnx("unsupported address family %d", + ipo->ipo_dst.sa.sa_family); + + ipsecctl_add_rule(ipsec, rule); + } +} + +void +ipsecctl_show(int opts) +{ + struct ipsecctl ipsec; + struct ipsec_rule *rp; + + memset(&ipsec, 0, sizeof(ipsec)); + ipsec.opts = opts; + TAILQ_INIT(&ipsec.rule_queue); + + ipsecctl_get_rules(&ipsec); + + while ((rp = TAILQ_FIRST(&ipsec.rule_queue))) { + TAILQ_REMOVE(&ipsec.rule_queue, rp, entries); + + ipsecctl_print_rule(rp, ipsec.opts & IPSECCTL_OPT_VERBOSE2); + + free(rp->src); + free(rp->dst); + free(rp->peer); + free(rp); + } + + return; +} + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-Fhnvs] [-f file]\n", __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int error = 0; + int ch; + int opts = 0; + char *rulesopt = NULL; + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, "f:Fhnvs")) != -1) { + switch (ch) { + case 'f': + rulesopt = optarg; + break; + + case 'F': + opts |= IPSECCTL_OPT_FLUSH; + break; + + case 'n': + opts |= IPSECCTL_OPT_NOACTION; + break; + + case 'v': + if (opts & IPSECCTL_OPT_VERBOSE) + opts |= IPSECCTL_OPT_VERBOSE2; + opts |= IPSECCTL_OPT_VERBOSE; + break; + + case 's': + opts |= IPSECCTL_OPT_SHOW; + break; + + case 'h': + /* FALLTHROUGH */ + default: + usage(); + /* NOTREACHED */ + } + } + + if (argc != optind) { + warnx("unknown command line argument: %s ...", argv[optind]); + usage(); + /* NOTREACHED */ + } + if (opts & IPSECCTL_OPT_FLUSH) + if (ipsecctl_flush(opts)) + error = 1; + + if (rulesopt != NULL) + if (ipsecctl_rules(rulesopt, opts)) + error = 1; + + if (opts & IPSECCTL_OPT_SHOW) + ipsecctl_show(opts); + + exit(error); +} diff --git a/sbin/ipsecctl/ipsecctl.h b/sbin/ipsecctl/ipsecctl.h new file mode 100644 index 00000000000..f11a862a730 --- /dev/null +++ b/sbin/ipsecctl/ipsecctl.h @@ -0,0 +1,88 @@ +/* $Id: ipsecctl.h,v 1.1 2005/04/04 22:19:50 hshoexer Exp $ */ +/* + * Copyright (c) 2004, 2005 Hans-Joerg Hoexer <hshoexer@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _IPSECCTL_H_ +#define _IPSECCTL_H_ + +#define IPSECCTL_OPT_DISABLE 0x0001 +#define IPSECCTL_OPT_ENABLE 0x0002 +#define IPSECCTL_OPT_NOACTION 0x0004 +#define IPSECCTL_OPT_VERBOSE 0x0010 +#define IPSECCTL_OPT_VERBOSE2 0x0020 +#define IPSECCTL_OPT_SHOW 0x0040 +#define IPSECCTL_OPT_FLUSH 0x0100 + +enum { + DIRECTION_UNKNOWN, IPSEC_IN, IPSEC_OUT, IPSEC_INOUT +}; +enum { + PROTO_UNKNWON, IPSEC_ESP, IPSEC_AH, IPSEC_COMP +}; +enum { + AUTH_UNKNOWN, AUTH_PSK, AUTH_RSA +}; +enum { + ID_UNKNOWN, ID_PREFIX, ID_FQDN, ID_UFQDN +}; + +struct ipsec_addr { + struct in_addr v4; + union { + struct in_addr mask; + u_int32_t mask32; + } v4mask; + int netaddress; + sa_family_t af; +}; + +struct ipsec_auth { + char *srcid; + char *dstid; + u_int8_t idtype; + u_int16_t type; +}; + +/* Complete state of one rule. */ +struct ipsec_rule { + struct ipsec_addr *src; + struct ipsec_addr *dst; + struct ipsec_addr *peer; + struct ipsec_auth auth; + + u_int8_t proto; + u_int8_t direction; + u_int32_t nr; + + TAILQ_ENTRY(ipsec_rule) entries; +}; + +TAILQ_HEAD(ipsec_rule_queue, ipsec_rule); + +struct ipsecctl { + u_int32_t rule_nr; + int opts; + struct ipsec_rule_queue rule_queue; +}; + +int parse_rules(FILE *, struct ipsecctl *); +int ipsecctl_add_rule(struct ipsecctl * ipsec, struct ipsec_rule *); +void ipsecctl_get_rules(struct ipsecctl *); +int pfkey_ipsec_establish(struct ipsec_rule *); +int pfkey_ipsec_flush(void); +int pfkey_init(void); + +#endif /* _IPSECCTL_H_ */ diff --git a/sbin/ipsecctl/parse.y b/sbin/ipsecctl/parse.y new file mode 100644 index 00000000000..9475a1927bd --- /dev/null +++ b/sbin/ipsecctl/parse.y @@ -0,0 +1,739 @@ +/* $Id: parse.y,v 1.1 2005/04/04 22:19:50 hshoexer Exp $ */ + +/* + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * Copyright (c) 2004, 2005 Hans-Joerg Hoexer <hshoexer@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> + +#include "ipsecctl.h" + +static struct ipsecctl *ipsec = NULL; +static FILE *fin = NULL; +static int lineno = 1; +static int errors = 0; +static int debug = 0; + +int yyerror(const char *, ...); +int yyparse(void); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(FILE *); +int lungetc(int); +int findeol(void); +int yylex(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entries; + int used; + int persist; + char *nam; + char *val; +}; + +int symset(const char *, const char *, int); +int cmdline_symset(char *); +char *symget(const char *); +int atoul(char *, u_long *); +struct ipsec_addr *host(const char *); +struct ipsec_addr *copyhost(const struct ipsec_addr *); +struct ipsec_rule *create_rule(u_int8_t, struct ipsec_addr *, struct + ipsec_addr *, struct ipsec_addr *, u_int8_t, + char *, char *, u_int16_t); +struct ipsec_rule *reverse_rule(struct ipsec_rule *); + +typedef struct { + union { + u_int32_t number; + u_int8_t dir; + char *string; + int log; + u_int8_t protocol; + struct { + struct ipsec_addr *src; + struct ipsec_addr *dst; + } hosts; + struct ipsec_addr *peer; + struct ipsec_addr *host; + struct { + char *srcid; + char *dstid; + } ids; + char *id; + u_int16_t authtype; + } v; + int lineno; +} YYSTYPE; + +%} + +%token FLOW FROM ESP AH IN PEER ON OUT TO SRCID DSTID RSA PSK +%token ERROR +%token <v.string> STRING +%type <v.dir> dir +%type <v.protocol> protocol +%type <v.number> number +%type <v.hosts> hosts +%type <v.peer> peer +%type <v.host> host +%type <v.ids> ids +%type <v.id> id +%type <v.authtype> authtype +%% + +grammar : /* empty */ + | grammar '\n' + | grammar flowrule '\n' + | grammar error '\n' { errors++; } + ; + +number : STRING { + unsigned long ulval; + + if (atoul($1, &ulval) == -1) { + yyerror("%s is not a number", $1); + free($1); + YYERROR; + } else + $$ = ulval; + free($1); + } + +flowrule : FLOW ipsecrule { } + ; + +ipsecrule : protocol dir hosts peer ids authtype { + struct ipsec_rule *r; + + r = create_rule($2, $3.src, $3.dst, $4, $1, $5.srcid, + $5.dstid, $6); + if (r == NULL) + YYERROR; + r->nr = ipsec->rule_nr++; + + if (ipsecctl_add_rule(ipsec, r)) + errx(1, "esprule: ipsecctl_add_rule"); + + /* Create and add reverse rule. */ + if ($2 == IPSEC_INOUT) { + r = reverse_rule(r); + r->nr = ipsec->rule_nr++; + + if (ipsecctl_add_rule(ipsec, r)) + errx(1, "esprule: ipsecctl_add_rule"); + } + } + ; + +protocol : /* empty */ { $$ = IPSEC_ESP; } + | ESP { $$ = IPSEC_ESP; } + | AH { $$ = IPSEC_AH; } + ; + +dir : /* empty */ { $$ = IPSEC_INOUT; } + | IN { $$ = IPSEC_IN; } + | OUT { $$ = IPSEC_OUT; } + ; + +hosts : FROM host TO host { + $$.src = $2; + $$.dst = $4; + } + ; + +peer : /* empty */ { $$ = NULL; } + | PEER STRING { + if (($$ = host($2)) == NULL) { + free($2); + yyerror("could not parse host specification"); + YYERROR; + } + free($2); + } + ; + +host : STRING { + if (($$ = host($1)) == NULL) { + free($1); + yyerror("could not parse host specification"); + YYERROR; + } + free($1); + } + | STRING '/' number { + char *buf; + + if (asprintf(&buf, "%s/%u", $1, $3) == -1) + err(1, "host: asprintf"); + free($1); + if (($$ = host(buf)) == NULL) { + free(buf); + yyerror("could not parse host specification"); + YYERROR; + } + free(buf); + } + ; + +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; } + ; + +authtype : /* empty */ { $$ = 0; } + | RSA { $$ = AUTH_RSA; } + | PSK { $$ = AUTH_PSK; } + ; +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + extern char *infile; + + errors = 1; + va_start(ap, fmt); + fprintf(stderr, "%s: %d: ", infile, yyval.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[] = { + { "ah", AH}, + { "dstid", DSTID}, + { "esp", ESP}, + { "flow", FLOW}, + { "from", FROM}, + { "in", IN}, + { "out", OUT}, + { "peer", PEER}, + { "psk", PSK}, + { "rsa", RSA}, + { "srcid", SRCID}, + { "to", TO}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) { + if (debug > 1) + fprintf(stderr, "%s: %d\n", s, p->k_val); + return (p->k_val); + } else { + if (debug > 1) + fprintf(stderr, "string: %s\n", s); + return (STRING); + } +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(FILE *f) +{ + 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]); + + while ((c = getc(f)) == '\\') { + next = getc(f); + if (next != '\n') { + if (isspace(next)) + yyerror("whitespace after \\"); + ungetc(next, f); + break; + } + yylval.lineno = lineno; + lineno++; + } + if (c == '\t' || c == ' ') { + /* Compress blanks to a single space. */ + do { + c = getc(f); + } while (c == '\t' || c == ' '); + ungetc(c, f); + c = ' '; + } + + 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; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(fin); + if (c == '\n') { + lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int endc, c; + int token; + +top: + p = buf; + while ((c = lgetc(fin)) == ' ') + ; /* nothing */ + + yylval.lineno = lineno; + if (c == '#') + while ((c = lgetc(fin)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = (char)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 '"': + endc = c; + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + if (c == endc) { + *p = '\0'; + break; + } + if (c == '\n') { + lineno++; + continue; + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#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(fin)) != 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 = lineno; + lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +parse_rules(FILE *input, struct ipsecctl *ipsecx) +{ + struct sym *sym, *next; + + ipsec = ipsecx; + fin = input; + lineno = 1; + errors = 0; + + yyparse(); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entries); + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entries); + free(sym); + } + + return (errors ? -1 : 0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entries)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entries); + 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, entries); + 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, entries) + 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); +} + +struct ipsec_addr * +host(const char *s) +{ + struct ipsec_addr *ipa; + int i, bits = 32; + + /* XXX for now only AF_INET. */ + + ipa = calloc(1, sizeof(struct ipsec_addr)); + if (ipa == NULL) + err(1, "calloc"); + + if (strrchr(s, '/') != NULL) { + bits = inet_net_pton(AF_INET, s, &ipa->v4, sizeof(ipa->v4)); + if (bits == -1 || bits > 32) { + free(ipa); + return(NULL); + } + } else { + if (inet_pton(AF_INET, s, &ipa->v4) != 1) { + free(ipa); + return NULL; + } + } + + memset(&ipa->v4mask, 0, sizeof(ipa->v4mask)); + if (bits == 32) { + ipa->v4mask.mask32 = 0xffffffff; + ipa->netaddress = 0; + } else { + for (i = 31; i > 31 - bits; i--) + ipa->v4mask.mask32 |= (1 << i); + ipa->v4mask.mask32 = htonl(ipa->v4mask.mask32); + ipa->netaddress = 1; + } + + ipa->af = AF_INET; + + return ipa; +} + +struct ipsec_addr * +copyhost(const struct ipsec_addr *src) +{ + struct ipsec_addr *dst; + + dst = calloc(1, sizeof(struct ipsec_addr)); + if (dst == NULL) + err(1, "calloc"); + + memcpy(dst, src, sizeof(struct ipsec_addr)); + return dst; +} + +struct ipsec_rule * +create_rule(u_int8_t dir, struct ipsec_addr *src, struct ipsec_addr *dst, + struct ipsec_addr *peer, u_int8_t proto, char *srcid, char *dstid, + u_int16_t authtype) +{ + struct ipsec_rule *r; + + r = calloc(1, sizeof(struct ipsec_rule)); + if (r == NULL) + err(1, "calloc"); + + if (dir == IPSEC_INOUT) + r->direction = IPSEC_OUT; + else + r->direction = dir; + + r->src = src; + r->dst = dst; + + if (peer == NULL) { + /* Set peer to remote host. Must be a host address. */ + if (r->direction == IPSEC_IN) { + if (r->src->netaddress) { + yyerror("no peer specified"); + goto errout; + } + r->peer = copyhost(r->src); + } else { + if (r->dst->netaddress) { + yyerror("no peer specified"); + goto errout; + } + r->peer = copyhost(r->dst); + } + } else + r->peer = peer; + + r->proto = proto; + r->auth.srcid = srcid; + r->auth.dstid = dstid; + r->auth.idtype = ID_FQDN; /* XXX For now only FQDN. */ +#ifdef notyet + r->auth.type = authtype; +#endif + + return r; + +errout: + free(r); + if (srcid) + free(srcid); + if (dstid) + free(dstid); + free(src); + free(dst); + + return NULL; +} + +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, "calloc"); + + if (rule->direction == (u_int8_t)IPSEC_OUT) + reverse->direction = (u_int8_t)IPSEC_IN; + else + reverse->direction = (u_int8_t)IPSEC_OUT; + + reverse->src = copyhost(rule->dst); + reverse->dst = copyhost(rule->src); + reverse->peer = copyhost(rule->peer); + reverse->proto = (u_int8_t)rule->proto; + + if (rule->auth.dstid && (reverse->auth.srcid = + strdup(rule->auth.dstid)) == NULL) + err(1, "strdup"); + if (rule->auth.srcid && (reverse->auth.dstid = + strdup(rule->auth.srcid)) == NULL) + err(1, "strdup"); + reverse->auth.idtype = rule->auth.idtype; + reverse->auth.type = rule->auth.type; + + return reverse; +} diff --git a/sbin/ipsecctl/pfkey.c b/sbin/ipsecctl/pfkey.c new file mode 100644 index 00000000000..38a8aebf07b --- /dev/null +++ b/sbin/ipsecctl/pfkey.c @@ -0,0 +1,401 @@ +/* $Id: pfkey.c,v 1.1 2005/04/04 22:19:50 hshoexer Exp $ */ +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2003, 2004 Markus Friedl <markus@openbsd.org> + * Copyright (c) 2004, 2005 Hans-Joerg Hoexer <hshoexer@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/uio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip_ipsp.h> +#include <net/pfkeyv2.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "ipsecctl.h" + +#define PFKEYV2_CHUNK sizeof(u_int64_t) +#define ROUNDUP(x) (((x) + (PFKEYV2_CHUNK - 1)) & ~(PFKEYV2_CHUNK - 1)) +#define IOV_CNT 20 + +static int fd; +static u_int32_t sadb_msg_seq = 1; + +static int pfkey_flow(int, u_int8_t, u_int8_t, u_int8_t, struct ipsec_addr *, + struct ipsec_addr *, struct ipsec_addr *, + struct ipsec_auth); +static int pfkey_reply(int); +int pfkey_ipsec_flush(void); +int pfkey_ipsec_establish(struct ipsec_rule *); +int pfkey_init(void); + +static int +pfkey_flow(int sd, u_int8_t satype, u_int8_t action, u_int8_t direction, + struct ipsec_addr *src, struct ipsec_addr *dst, struct ipsec_addr *peer, + struct ipsec_auth auth) +{ + struct sadb_msg smsg; + struct sadb_address sa_src, sa_dst, sa_peer, sa_smask, sa_dmask; + struct sadb_protocol sa_flowtype, sa_protocol; + struct sadb_ident *sa_srcid, *sa_dstid; + struct sockaddr_storage ssrc, sdst, speer, smask, dmask; + struct iovec iov[IOV_CNT]; + ssize_t n; + int iov_cnt, len, ret = 0; + + sa_srcid = sa_dstid = NULL; + + bzero(&ssrc, sizeof(ssrc)); + bzero(&smask, sizeof(smask)); + switch (src->af) { + case AF_INET: + ((struct sockaddr_in *)&ssrc)->sin_addr = src->v4; + ssrc.ss_len = sizeof(struct sockaddr_in); + ssrc.ss_family = AF_INET; + ((struct sockaddr_in *)&smask)->sin_addr = src->v4mask.mask; + break; + case AF_INET6: + default: + warnx("unsupported address family %d", src->af); + return -1; + } + smask.ss_family = ssrc.ss_family; + smask.ss_len = ssrc.ss_len; + + bzero(&sdst, sizeof(sdst)); + bzero(&dmask, sizeof(dmask)); + switch (dst->af) { + case AF_INET: + ((struct sockaddr_in *)&sdst)->sin_addr = dst->v4; + sdst.ss_len = sizeof(struct sockaddr_in); + sdst.ss_family = AF_INET; + ((struct sockaddr_in *)&dmask)->sin_addr = dst->v4mask.mask; + break; + case AF_INET6: + default: + warnx("unsupported address family %d", dst->af); + return -1; + } + dmask.ss_family = sdst.ss_family; + dmask.ss_len = sdst.ss_len; + + bzero(&speer, sizeof(speer)); + switch (peer->af) { + case AF_INET: + ((struct sockaddr_in *)&speer)->sin_addr = peer->v4; + speer.ss_len = sizeof(struct sockaddr_in); + speer.ss_family = AF_INET; + break; + case AF_INET6: + default: + warnx("unsupported address family %d", peer->af); + return -1; + } + + bzero(&smsg, sizeof(smsg)); + smsg.sadb_msg_version = PF_KEY_V2; + smsg.sadb_msg_seq = sadb_msg_seq++; + smsg.sadb_msg_pid = getpid(); + smsg.sadb_msg_len = sizeof(smsg) / 8; + smsg.sadb_msg_type = action; + smsg.sadb_msg_satype = satype; + + bzero(&sa_flowtype, sizeof(sa_flowtype)); + sa_flowtype.sadb_protocol_exttype = SADB_X_EXT_FLOW_TYPE; + sa_flowtype.sadb_protocol_len = sizeof(sa_flowtype) / 8; + sa_flowtype.sadb_protocol_direction = direction; + sa_flowtype.sadb_protocol_proto = SADB_X_FLOW_TYPE_REQUIRE; + + bzero(&sa_protocol, sizeof(sa_protocol)); + sa_protocol.sadb_protocol_exttype = SADB_X_EXT_PROTOCOL; + sa_protocol.sadb_protocol_len = sizeof(sa_protocol) / 8; + sa_protocol.sadb_protocol_direction = 0; + sa_protocol.sadb_protocol_proto = IPPROTO_IP; + + bzero(&sa_src, sizeof(sa_src)); + sa_src.sadb_address_exttype = SADB_X_EXT_SRC_FLOW; + sa_src.sadb_address_len = (sizeof(sa_src) + ROUNDUP(ssrc.ss_len)) / 8; + + bzero(&sa_smask, sizeof(sa_smask)); + sa_smask.sadb_address_exttype = SADB_X_EXT_SRC_MASK; + sa_smask.sadb_address_len = + (sizeof(sa_smask) + ROUNDUP(smask.ss_len)) / 8; + + bzero(&sa_dst, sizeof(sa_dst)); + sa_dst.sadb_address_exttype = SADB_X_EXT_DST_FLOW; + sa_dst.sadb_address_len = (sizeof(sa_dst) + ROUNDUP(sdst.ss_len)) / 8; + + bzero(&sa_dmask, sizeof(sa_dmask)); + sa_dmask.sadb_address_exttype = SADB_X_EXT_DST_MASK; + sa_dmask.sadb_address_len = + (sizeof(sa_dmask) + ROUNDUP(dmask.ss_len)) / 8; + + bzero(&sa_peer, sizeof(sa_peer)); + sa_peer.sadb_address_exttype = SADB_EXT_ADDRESS_DST; + sa_peer.sadb_address_len = + (sizeof(sa_peer) + ROUNDUP(speer.ss_len)) / 8; + + if (auth.srcid) { + len = ROUNDUP(strlen(auth.srcid) + 1) + sizeof(*sa_srcid); + + sa_srcid = calloc(len, sizeof(u_int8_t)); + if (sa_srcid == NULL) + err(1, "calloc"); + + sa_srcid->sadb_ident_type = auth.idtype; + sa_srcid->sadb_ident_len = len / 8; + sa_srcid->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC; + + strlcpy((char *)(sa_srcid + 1), auth.srcid, + ROUNDUP(strlen(auth.srcid) + 1)); + } + if (auth.dstid) { + len = ROUNDUP(strlen(auth.dstid) + 1) + sizeof(*sa_dstid); + + sa_dstid = calloc(len, sizeof(u_int8_t)); + if (sa_dstid == NULL) + err(1, "calloc"); + + sa_dstid->sadb_ident_type = auth.idtype; + sa_dstid->sadb_ident_len = len / 8; + sa_dstid->sadb_ident_exttype = SADB_EXT_IDENTITY_DST; + + strlcpy((char *)(sa_dstid + 1), auth.dstid, + ROUNDUP(strlen(auth.dstid) + 1)); + } + + iov_cnt = 0; + + /* header */ + iov[iov_cnt].iov_base = &smsg; + iov[iov_cnt].iov_len = sizeof(smsg); + iov_cnt++; + + /* remote peer */ + iov[iov_cnt].iov_base = &sa_peer; + iov[iov_cnt].iov_len = sizeof(sa_peer); + iov_cnt++; + iov[iov_cnt].iov_base = &speer; + iov[iov_cnt].iov_len = ROUNDUP(speer.ss_len); + smsg.sadb_msg_len += sa_peer.sadb_address_len; + iov_cnt++; + + /* add flow type */ + iov[iov_cnt].iov_base = &sa_flowtype; + iov[iov_cnt].iov_len = sizeof(sa_flowtype); + smsg.sadb_msg_len += sa_flowtype.sadb_protocol_len; + iov_cnt++; + + /* add protocol */ + iov[iov_cnt].iov_base = &sa_protocol; + iov[iov_cnt].iov_len = sizeof(sa_protocol); + smsg.sadb_msg_len += sa_protocol.sadb_protocol_len; + iov_cnt++; + + /* add flow masks */ + iov[iov_cnt].iov_base = &sa_smask; + iov[iov_cnt].iov_len = sizeof(sa_smask); + iov_cnt++; + iov[iov_cnt].iov_base = &smask; + iov[iov_cnt].iov_len = ROUNDUP(smask.ss_len); + smsg.sadb_msg_len += sa_smask.sadb_address_len; + iov_cnt++; + + iov[iov_cnt].iov_base = &sa_dmask; + iov[iov_cnt].iov_len = sizeof(sa_dmask); + iov_cnt++; + iov[iov_cnt].iov_base = &dmask; + iov[iov_cnt].iov_len = ROUNDUP(dmask.ss_len); + smsg.sadb_msg_len += sa_dmask.sadb_address_len; + iov_cnt++; + + /* dest addr */ + iov[iov_cnt].iov_base = &sa_dst; + iov[iov_cnt].iov_len = sizeof(sa_dst); + iov_cnt++; + iov[iov_cnt].iov_base = &sdst; + iov[iov_cnt].iov_len = ROUNDUP(sdst.ss_len); + smsg.sadb_msg_len += sa_dst.sadb_address_len; + iov_cnt++; + + /* src addr */ + iov[iov_cnt].iov_base = &sa_src; + iov[iov_cnt].iov_len = sizeof(sa_src); + iov_cnt++; + iov[iov_cnt].iov_base = &ssrc; + iov[iov_cnt].iov_len = ROUNDUP(ssrc.ss_len); + smsg.sadb_msg_len += sa_src.sadb_address_len; + iov_cnt++; + + if (sa_srcid) { + /* src identity */ + iov[iov_cnt].iov_base = sa_srcid; + iov[iov_cnt].iov_len = sa_srcid->sadb_ident_len * 8; + smsg.sadb_msg_len += sa_srcid->sadb_ident_len; + iov_cnt++; + } + if (sa_dstid) { + /* dst identity */ + iov[iov_cnt].iov_base = sa_dstid; + iov[iov_cnt].iov_len = sa_dstid->sadb_ident_len * 8; + smsg.sadb_msg_len += sa_dstid->sadb_ident_len; + iov_cnt++; + } + len = smsg.sadb_msg_len * 8; + if ((n = writev(sd, iov, iov_cnt)) == -1) { + warn("writev failed"); + ret = -1; + goto out; + } + if (n != len) { + warnx("short write"); + ret = -1; + } + +out: + if (sa_srcid) + free(sa_srcid); + if (sa_dstid) + free(sa_dstid); + + return ret; +} + +static int +pfkey_reply(int sd) +{ + struct sadb_msg hdr; + ssize_t len; + u_int8_t *data; + + if (recv(sd, &hdr, sizeof(hdr), MSG_PEEK) != sizeof(hdr)) { + warnx("short read"); + return -1; + } + if (hdr.sadb_msg_errno != 0) { + errno = hdr.sadb_msg_errno; + if (errno == ESRCH) + return 0; + else { + warn("PF_KEY returned error"); + return -1; + } + } + len = hdr.sadb_msg_len * PFKEYV2_CHUNK; + if ((data = malloc(len)) == NULL) + err(1, NULL); + if (read(sd, data, len) != len) { + warn("PF_KEY short read"); + bzero(data, len); + free(data); + return -1; + } + bzero(data, len); + free(data); + + return 0; +} + +int +pfkey_ipsec_establish(struct ipsec_rule *r) +{ + u_int8_t satype; + u_int8_t direction; + + switch (r->proto) { + case IPSEC_ESP: + satype = SADB_SATYPE_ESP; + break; + case IPSEC_AH: + satype = SADB_SATYPE_AH; + break; + case IPSEC_COMP: + default: + return -1; + } + + switch (r->direction) { + case IPSEC_IN: + direction = IPSP_DIRECTION_IN; + break; + case IPSEC_OUT: + direction = IPSP_DIRECTION_OUT; + break; + default: + return -1; + } + + if (pfkey_flow(fd, satype, SADB_X_ADDFLOW, direction, r->src, r->dst, + r->peer, r->auth) < 0) + return -1; + if (pfkey_reply(fd) < 0) + return -1; + + return 0; +} + +int +pfkey_ipsec_flush(void) +{ + struct sadb_msg smsg; + struct iovec iov[IOV_CNT]; + ssize_t n; + int iov_cnt, len; + + bzero(&smsg, sizeof(smsg)); + smsg.sadb_msg_version = PF_KEY_V2; + smsg.sadb_msg_seq = sadb_msg_seq++; + smsg.sadb_msg_pid = getpid(); + smsg.sadb_msg_len = sizeof(smsg) / 8; + smsg.sadb_msg_type = SADB_FLUSH; + smsg.sadb_msg_satype = SADB_SATYPE_UNSPEC; + + iov_cnt = 0; + + iov[iov_cnt].iov_base = &smsg; + iov[iov_cnt].iov_len = sizeof(smsg); + iov_cnt++; + + len = smsg.sadb_msg_len * 8; + if ((n = writev(fd, iov, iov_cnt)) == -1) { + warn("writev failed"); + return -1; + } + if (n != len) { + warnx("short write"); + return -1; + } + if (pfkey_reply(fd) < 0) + return -1; + + return 0; +} + +int +pfkey_init(void) +{ + if ((fd = socket(PF_KEY, SOCK_RAW, PF_KEY_V2)) == -1) + err(1, "failed to open PF_KEY socket"); + + return 0; +} |