summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sbin/ipsecctl/Makefile16
-rw-r--r--sbin/ipsecctl/ipsec.conf.5117
-rw-r--r--sbin/ipsecctl/ipsecctl.836
-rw-r--r--sbin/ipsecctl/ipsecctl.c414
-rw-r--r--sbin/ipsecctl/ipsecctl.h88
-rw-r--r--sbin/ipsecctl/parse.y739
-rw-r--r--sbin/ipsecctl/pfkey.c401
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;
+}