src/sbin/isakmpd/pf_key_v2.c

3370 lines
88 KiB
C

/* $OpenBSD: pf_key_v2.c,v 1.205 2023/08/07 04:01:30 dlg Exp $ */
/* $EOM: pf_key_v2.c,v 1.79 2000/12/12 00:33:19 niklas Exp $ */
/*
* Copyright (c) 1999, 2000, 2001 Niklas Hallqvist. All rights reserved.
* Copyright (c) 1999, 2000, 2001 Angelos D. Keromytis. All rights reserved.
* Copyright (c) 2001 Håkan Olsson. 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.
*
* 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.
*/
/*
* This code was written under funding by Ericsson Radio Systems.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <net/pfkeyv2.h>
#include <netinet/in.h>
#include <netinet/ip_ipsp.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <errno.h>
#include <bitstring.h>
#include "cert.h"
#include "conf.h"
#include "connection.h"
#include "exchange.h"
#include "ipsec.h"
#include "ipsec_num.h"
#include "key.h"
#include "log.h"
#include "pf_key_v2.h"
#include "sa.h"
#include "timer.h"
#include "transport.h"
#include "ui.h"
#include "util.h"
#include "policy.h"
#include "udp_encap.h"
#define IN6_IS_ADDR_FULL(a) \
((*(u_int32_t *)(void *)(&(a)->s6_addr[0]) == 0xffffffff) && \
(*(u_int32_t *)(void *)(&(a)->s6_addr[4]) == 0xffffffff) && \
(*(u_int32_t *)(void *)(&(a)->s6_addr[8]) == 0xffffffff) && \
(*(u_int32_t *)(void *)(&(a)->s6_addr[12]) == 0xffffffff))
#define ADDRESS_MAX sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"
/*
* PF_KEY v2 always work with 64-bit entities and aligns on 64-bit boundaries.
*/
#define PF_KEY_V2_CHUNK 8
#define PF_KEY_V2_ROUND(x) \
(((x) + PF_KEY_V2_CHUNK - 1) & ~(PF_KEY_V2_CHUNK - 1))
/* How many microseconds we will wait for a reply from the PF_KEY socket. */
#define PF_KEY_REPLY_TIMEOUT 1000
struct pf_key_v2_node {
TAILQ_ENTRY(pf_key_v2_node) link;
void *seg;
size_t sz;
int cnt;
u_int16_t type;
u_int8_t flags;
};
TAILQ_HEAD(pf_key_v2_msg, pf_key_v2_node);
#define PF_KEY_V2_NODE_MALLOCED 1
#define PF_KEY_V2_NODE_MARK 2
/* Used to derive "unique" connection identifiers. */
int connection_seq = 0;
static u_int8_t *pf_key_v2_convert_id(u_int8_t *, int, size_t *, int *);
static struct pf_key_v2_msg *pf_key_v2_call(struct pf_key_v2_msg *);
static struct pf_key_v2_node *pf_key_v2_find_ext(struct pf_key_v2_msg *,
u_int16_t);
static void pf_key_v2_notify(struct pf_key_v2_msg *);
static struct pf_key_v2_msg *pf_key_v2_read(u_int32_t);
static u_int32_t pf_key_v2_seq(void);
static u_int32_t pf_key_v2_write(struct pf_key_v2_msg *);
static int pf_key_v2_remove_conf(char *);
static int pf_key_v2_conf_refhandle(int, char *);
static int pf_key_v2_conf_refinc(int, char *);
/* The socket to use for PF_KEY interactions. */
int pf_key_v2_socket;
static struct pf_key_v2_msg *
pf_key_v2_msg_new(struct sadb_msg *msg, int flags)
{
struct pf_key_v2_node *node;
struct pf_key_v2_msg *ret;
node = malloc(sizeof *node);
if (!node)
goto cleanup;
ret = malloc(sizeof *ret);
if (!ret)
goto cleanup;
TAILQ_INIT(ret);
node->seg = msg;
node->sz = sizeof *msg;
node->type = 0;
node->cnt = 1;
node->flags = flags;
TAILQ_INSERT_HEAD(ret, node, link);
return ret;
cleanup:
free(node);
return 0;
}
/* Add a SZ sized segment SEG to the PF_KEY message MSG. */
static int
pf_key_v2_msg_add(struct pf_key_v2_msg *msg, struct sadb_ext *ext, int flags)
{
struct pf_key_v2_node *node;
node = malloc(sizeof *node);
if (!node)
return -1;
node->seg = ext;
node->sz = ext->sadb_ext_len * PF_KEY_V2_CHUNK;
node->type = ext->sadb_ext_type;
node->flags = flags;
TAILQ_FIRST(msg)->cnt++;
TAILQ_INSERT_TAIL(msg, node, link);
return 0;
}
/* Deallocate the PF_KEY message MSG. */
static void
pf_key_v2_msg_free(struct pf_key_v2_msg *msg)
{
struct pf_key_v2_node *np;
np = TAILQ_FIRST(msg);
while (np) {
TAILQ_REMOVE(msg, np, link);
if (np->flags & PF_KEY_V2_NODE_MALLOCED)
free(np->seg);
free(np);
np = TAILQ_FIRST(msg);
}
free(msg);
}
/* Just return a new sequence number. */
static u_int32_t
pf_key_v2_seq(void)
{
static u_int32_t seq = 0;
return ++seq;
}
/*
* Read a PF_KEY packet with SEQ as the sequence number, looping if necessary.
* If SEQ is zero just read the first message we see, otherwise we queue
* messages up until both the PID and the sequence number match.
*/
static struct pf_key_v2_msg *
pf_key_v2_read(u_int32_t seq)
{
ssize_t n;
u_int8_t *buf = 0;
struct pf_key_v2_msg *ret = 0;
struct sadb_msg *msg;
struct sadb_msg hdr;
struct sadb_ext *ext;
struct timespec ts;
struct pollfd pfd[1];
pfd[0].fd = pf_key_v2_socket;
pfd[0].events = POLLIN;
while (1) {
/*
* If this is a read of a reply we should actually expect the
* reply to get lost as PF_KEY is an unreliable service per
* the specs. Currently we do this by setting a short timeout,
* and if it is not readable in that time, we fail the read.
*/
if (seq) {
n = poll(pfd, 1, PF_KEY_REPLY_TIMEOUT / 1000);
if (n == -1) {
log_error("pf_key_v2_read: poll() failed");
goto cleanup;
}
if (!n) {
log_print("pf_key_v2_read: "
"no reply from PF_KEY");
goto cleanup;
}
}
n = recv(pf_key_v2_socket, &hdr, sizeof hdr, MSG_PEEK);
if (n == -1) {
log_error("pf_key_v2_read: recv (%d, ...) failed",
pf_key_v2_socket);
goto cleanup;
}
if (n != sizeof hdr) {
log_error("pf_key_v2_read: recv (%d, ...) "
"returned short packet (%lu bytes)",
pf_key_v2_socket, (unsigned long) n);
goto cleanup;
}
buf = reallocarray(NULL, hdr.sadb_msg_len, PF_KEY_V2_CHUNK);
if (!buf) {
log_error("pf_key_v2_read: reallocarray (%d, %d) failed",
hdr.sadb_msg_len, PF_KEY_V2_CHUNK);
goto cleanup;
}
n = hdr.sadb_msg_len * PF_KEY_V2_CHUNK;
n = read(pf_key_v2_socket, buf, n);
if (n == -1) {
log_error("pf_key_v2_read: read (%d, ...) failed",
pf_key_v2_socket);
goto cleanup;
}
if (n != hdr.sadb_msg_len * PF_KEY_V2_CHUNK) {
log_print("pf_key_v2_read: read (%d, ...) "
"returned short packet (%lu bytes)",
pf_key_v2_socket, (unsigned long) n);
goto cleanup;
}
LOG_DBG_BUF((LOG_SYSDEP, 80, "pf_key_v2_read: msg", buf, n));
/* We drop all messages that is not what we expect. */
msg = (struct sadb_msg *) buf;
if (msg->sadb_msg_version != PF_KEY_V2 ||
(msg->sadb_msg_pid != 0 &&
msg->sadb_msg_pid != (u_int32_t) getpid())) {
if (seq) {
free(buf);
buf = 0;
continue;
} else {
LOG_DBG((LOG_SYSDEP, 90, "pf_key_v2_read:"
"bad version (%d) or PID (%d, mine is "
"%ld), ignored", msg->sadb_msg_version,
msg->sadb_msg_pid, (long) getpid()));
goto cleanup;
}
}
/* Parse the message. */
ret = pf_key_v2_msg_new(msg, PF_KEY_V2_NODE_MALLOCED);
if (!ret)
goto cleanup;
buf = 0;
for (ext = (struct sadb_ext *) (msg + 1);
(u_int8_t *) ext - (u_int8_t *) msg <
msg->sadb_msg_len * PF_KEY_V2_CHUNK;
ext = (struct sadb_ext *) ((u_int8_t *) ext +
ext->sadb_ext_len * PF_KEY_V2_CHUNK))
pf_key_v2_msg_add(ret, ext, 0);
/*
* If the message is not the one we are waiting for, queue it
* up.
*/
if (seq && (msg->sadb_msg_pid != (u_int32_t) getpid() ||
msg->sadb_msg_seq != seq)) {
clock_gettime(CLOCK_MONOTONIC, &ts);
timer_add_event("pf_key_v2_notify",
(void (*) (void *)) pf_key_v2_notify, ret, &ts);
ret = 0;
continue;
}
return ret;
}
cleanup:
free(buf);
if (ret)
pf_key_v2_msg_free(ret);
return 0;
}
/* Write the message in PMSG to the PF_KEY socket. */
u_int32_t
pf_key_v2_write(struct pf_key_v2_msg *pmsg)
{
struct iovec *iov = 0;
ssize_t n;
size_t len;
int i, cnt = TAILQ_FIRST(pmsg)->cnt;
char header[80];
struct sadb_msg *msg = TAILQ_FIRST(pmsg)->seg;
struct pf_key_v2_node *np = TAILQ_FIRST(pmsg);
iov = calloc(cnt, sizeof *iov);
if (!iov) {
log_error("pf_key_v2_write: malloc (%lu) failed",
cnt * (unsigned long) sizeof *iov);
return 0;
}
msg->sadb_msg_version = PF_KEY_V2;
msg->sadb_msg_errno = 0;
msg->sadb_msg_reserved = 0;
msg->sadb_msg_pid = getpid();
if (!msg->sadb_msg_seq)
msg->sadb_msg_seq = pf_key_v2_seq();
/* Compute the iovec segments as well as the message length. */
len = 0;
for (i = 0; i < cnt; i++) {
iov[i].iov_base = np->seg;
len += iov[i].iov_len = np->sz;
/*
* XXX One can envision setting specific extension fields,
* like *_reserved ones here. For now we require them to be
* set by the caller.
*/
np = TAILQ_NEXT(np, link);
}
msg->sadb_msg_len = len / PF_KEY_V2_CHUNK;
for (i = 0; i < cnt; i++) {
snprintf(header, sizeof header, "pf_key_v2_write: iov[%d]", i);
LOG_DBG_BUF((LOG_SYSDEP, 80, header,
(u_int8_t *) iov[i].iov_base, iov[i].iov_len));
}
do {
n = writev(pf_key_v2_socket, iov, cnt);
} while (n == -1 && (errno == EAGAIN || errno == EINTR));
if (n == -1) {
log_error("pf_key_v2_write: writev (%d, %p, %d) failed",
pf_key_v2_socket, iov, cnt);
goto cleanup;
}
if ((size_t) n != len) {
log_error("pf_key_v2_write: "
"writev (%d, ...) returned prematurely (%lu)",
pf_key_v2_socket, (unsigned long) n);
goto cleanup;
}
free(iov);
return msg->sadb_msg_seq;
cleanup:
free(iov);
return 0;
}
/*
* Do a PF_KEY "call", i.e. write a message MSG, read the reply and return
* it to the caller.
*/
static struct pf_key_v2_msg *
pf_key_v2_call(struct pf_key_v2_msg *msg)
{
u_int32_t seq;
seq = pf_key_v2_write(msg);
if (!seq)
return 0;
return pf_key_v2_read(seq);
}
/* Find the TYPE extension in MSG. Return zero if none found. */
static struct pf_key_v2_node *
pf_key_v2_find_ext(struct pf_key_v2_msg *msg, u_int16_t type)
{
struct pf_key_v2_node *ext;
for (ext = TAILQ_NEXT(TAILQ_FIRST(msg), link); ext;
ext = TAILQ_NEXT(ext, link))
if (ext->type == type)
return ext;
return 0;
}
/*
* Open the PF_KEYv2 sockets and return the descriptor used for notifies.
* Return -1 for failure and -2 if no notifies will show up.
*/
int
pf_key_v2_open(void)
{
int fd = -1, err;
struct sadb_msg msg;
struct pf_key_v2_msg *regmsg = 0, *ret = 0;
/* Open the socket we use to speak to IPsec. */
pf_key_v2_socket = -1;
fd = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
if (fd == -1) {
log_error("pf_key_v2_open: "
"socket (PF_KEY, SOCK_RAW, PF_KEY_V2) failed");
goto cleanup;
}
pf_key_v2_socket = fd;
/* Register it to get ESP and AH acquires from the kernel. */
msg.sadb_msg_seq = 0;
msg.sadb_msg_type = SADB_REGISTER;
msg.sadb_msg_satype = SADB_SATYPE_ESP;
regmsg = pf_key_v2_msg_new(&msg, 0);
if (!regmsg)
goto cleanup;
ret = pf_key_v2_call(regmsg);
pf_key_v2_msg_free(regmsg);
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
if (err) {
log_print("pf_key_v2_open: REGISTER: %s", strerror(err));
goto cleanup;
}
/* XXX Register the accepted transforms. */
pf_key_v2_msg_free(ret);
ret = 0;
msg.sadb_msg_seq = 0;
msg.sadb_msg_type = SADB_REGISTER;
msg.sadb_msg_satype = SADB_SATYPE_AH;
regmsg = pf_key_v2_msg_new(&msg, 0);
if (!regmsg)
goto cleanup;
ret = pf_key_v2_call(regmsg);
pf_key_v2_msg_free(regmsg);
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
if (err) {
log_print("pf_key_v2_open: REGISTER: %s", strerror(err));
goto cleanup;
}
/* XXX Register the accepted transforms. */
pf_key_v2_msg_free(ret);
ret = 0;
msg.sadb_msg_seq = 0;
msg.sadb_msg_type = SADB_REGISTER;
msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
regmsg = pf_key_v2_msg_new(&msg, 0);
if (!regmsg)
goto cleanup;
ret = pf_key_v2_call(regmsg);
pf_key_v2_msg_free(regmsg);
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
if (err) {
log_print("pf_key_v2_open: REGISTER: %s", strerror(err));
goto cleanup;
}
/* XXX Register the accepted transforms. */
pf_key_v2_msg_free(ret);
return fd;
cleanup:
if (pf_key_v2_socket != -1) {
close(pf_key_v2_socket);
pf_key_v2_socket = -1;
}
if (ret)
pf_key_v2_msg_free(ret);
return -1;
}
/*
* Generate a SPI for protocol PROTO and the source/destination pair given by
* SRC, SRCLEN, DST & DSTLEN. Stash the SPI size in SZ.
*/
u_int8_t *
pf_key_v2_get_spi(size_t *sz, u_int8_t proto, struct sockaddr *src,
struct sockaddr *dst, u_int32_t seq)
{
struct sadb_msg msg;
struct sadb_sa *sa;
struct sadb_address *addr = 0;
struct sadb_spirange spirange;
struct pf_key_v2_msg *getspi = 0, *ret = 0;
struct pf_key_v2_node *ext;
u_int8_t *spi = 0;
int len, err;
msg.sadb_msg_type = SADB_GETSPI;
switch (proto) {
case IPSEC_PROTO_IPSEC_ESP:
msg.sadb_msg_satype = SADB_SATYPE_ESP;
break;
case IPSEC_PROTO_IPSEC_AH:
msg.sadb_msg_satype = SADB_SATYPE_AH;
break;
case IPSEC_PROTO_IPCOMP:
msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
break;
default:
log_print("pf_key_v2_get_spi: invalid proto %d", proto);
goto cleanup;
}
/* Set the sequence number from the ACQUIRE message. */
msg.sadb_msg_seq = seq;
getspi = pf_key_v2_msg_new(&msg, 0);
if (!getspi)
goto cleanup;
/* Setup the ADDRESS extensions. */
len =
sizeof(struct sadb_address) + PF_KEY_V2_ROUND(SA_LEN(src));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, src, SA_LEN(src));
switch (((struct sockaddr *) (addr + 1))->sa_family) {
case AF_INET:
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
break;
case AF_INET6:
((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
break;
}
if (pf_key_v2_msg_add(getspi, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
len = sizeof(struct sadb_address) + PF_KEY_V2_ROUND(SA_LEN(dst));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, dst, SA_LEN(dst));
switch (((struct sockaddr *) (addr + 1))->sa_family) {
case AF_INET:
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
break;
case AF_INET6:
((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
break;
}
if (pf_key_v2_msg_add(getspi, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
/* Setup the SPIRANGE extension. */
spirange.sadb_spirange_exttype = SADB_EXT_SPIRANGE;
spirange.sadb_spirange_len = sizeof spirange / PF_KEY_V2_CHUNK;
if (proto == IPSEC_PROTO_IPCOMP) {
spirange.sadb_spirange_min = CPI_RESERVED_MAX + 1;
spirange.sadb_spirange_max = CPI_PRIVATE_MIN - 1;
} else {
spirange.sadb_spirange_min = IPSEC_SPI_LOW;
spirange.sadb_spirange_max = 0xffffffff;
}
spirange.sadb_spirange_reserved = 0;
if (pf_key_v2_msg_add(getspi, (struct sadb_ext *)&spirange, 0) == -1)
goto cleanup;
ret = pf_key_v2_call(getspi);
pf_key_v2_msg_free(getspi);
getspi = 0;
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
if (err) {
log_print("pf_key_v2_get_spi: GETSPI: %s", strerror(err));
goto cleanup;
}
ext = pf_key_v2_find_ext(ret, SADB_EXT_SA);
if (!ext) {
log_print("pf_key_v2_get_spi: no SA extension found");
goto cleanup;
}
sa = ext->seg;
/* IPCOMP CPIs are only 16 bits long. */
*sz = (proto == IPSEC_PROTO_IPCOMP) ? sizeof(u_int16_t)
: sizeof sa->sadb_sa_spi;
spi = malloc(*sz);
if (!spi)
goto cleanup;
/* XXX This is ugly. */
if (proto == IPSEC_PROTO_IPCOMP) {
u_int32_t tspi = ntohl(sa->sadb_sa_spi);
*(u_int16_t *) spi = htons((u_int16_t) tspi);
} else
memcpy(spi, &sa->sadb_sa_spi, *sz);
pf_key_v2_msg_free(ret);
LOG_DBG_BUF((LOG_SYSDEP, 50, "pf_key_v2_get_spi: spi", spi, *sz));
return spi;
cleanup:
free(spi);
free(addr);
if (getspi)
pf_key_v2_msg_free(getspi);
if (ret)
pf_key_v2_msg_free(ret);
return 0;
}
/* Fetch SA information from the kernel. XXX OpenBSD only? */
struct sa_kinfo *
pf_key_v2_get_kernel_sa(u_int8_t *spi, size_t spi_sz, u_int8_t proto,
struct sockaddr *dst)
{
struct sadb_msg msg;
struct sadb_sa *ssa;
struct sadb_address *addr = 0;
struct sockaddr *sa;
struct sadb_lifetime *life;
struct pf_key_v2_msg *gettdb = 0, *ret = 0;
struct pf_key_v2_node *ext;
static struct sa_kinfo ksa;
struct sadb_x_udpencap *udpencap;
int len, err;
if (spi_sz != sizeof (ssa->sadb_sa_spi))
return 0;
msg.sadb_msg_type = SADB_GET;
switch (proto) {
case IPSEC_PROTO_IPSEC_ESP:
msg.sadb_msg_satype = SADB_SATYPE_ESP;
break;
case IPSEC_PROTO_IPSEC_AH:
msg.sadb_msg_satype = SADB_SATYPE_AH;
break;
case IPSEC_PROTO_IPCOMP:
msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
break;
default:
log_print("pf_key_v2_get_kernel_sa: invalid proto %d", proto);
goto cleanup;
}
gettdb = pf_key_v2_msg_new(&msg, 0);
if (!gettdb)
goto cleanup;
/* SPI */
ssa = calloc(1, sizeof *ssa);
if (!ssa) {
log_print("pf_key_v2_get_kernel_sa: calloc(1, %lu) failed",
(unsigned long)sizeof *ssa);
goto cleanup;
}
ssa->sadb_sa_exttype = SADB_EXT_SA;
ssa->sadb_sa_len = sizeof *ssa / PF_KEY_V2_CHUNK;
memcpy(&ssa->sadb_sa_spi, spi, sizeof ssa->sadb_sa_spi);
ssa->sadb_sa_state = SADB_SASTATE_MATURE;
if (pf_key_v2_msg_add(gettdb, (struct sadb_ext *)ssa,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
ssa = 0;
/* Address */
len =
sizeof(struct sadb_address) + PF_KEY_V2_ROUND(SA_LEN(dst));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, dst, SA_LEN(dst));
switch (((struct sockaddr *) (addr + 1))->sa_family) {
case AF_INET:
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
break;
case AF_INET6:
((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
break;
}
if (pf_key_v2_msg_add(gettdb, (struct sadb_ext *)addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
ret = pf_key_v2_call(gettdb);
pf_key_v2_msg_free(gettdb);
gettdb = 0;
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
if (err) {
log_print("pf_key_v2_get_kernel_sa: SADB_GET: %s",
strerror(err));
goto cleanup;
}
/* Extract the data. */
bzero(&ksa, sizeof ksa);
ext = pf_key_v2_find_ext(ret, SADB_EXT_SA);
if (!ext)
goto cleanup;
ssa = (struct sadb_sa *)ext;
ksa.spi = ssa->sadb_sa_spi;
ksa.wnd = ssa->sadb_sa_replay;
ksa.flags = ssa->sadb_sa_flags;
ext = pf_key_v2_find_ext(ret, SADB_EXT_LIFETIME_CURRENT);
if (ext) {
life = (struct sadb_lifetime *)ext->seg;
ksa.cur_allocations = life->sadb_lifetime_allocations;
ksa.cur_bytes = life->sadb_lifetime_bytes;
ksa.first_use = life->sadb_lifetime_usetime;
ksa.established = life->sadb_lifetime_addtime;
}
ext = pf_key_v2_find_ext(ret, SADB_EXT_LIFETIME_SOFT);
if (ext) {
life = (struct sadb_lifetime *)ext->seg;
ksa.soft_allocations = life->sadb_lifetime_allocations;
ksa.soft_bytes = life->sadb_lifetime_bytes;
ksa.soft_timeout = life->sadb_lifetime_addtime;
ksa.soft_first_use = life->sadb_lifetime_usetime;
}
ext = pf_key_v2_find_ext(ret, SADB_EXT_LIFETIME_HARD);
if (ext) {
life = (struct sadb_lifetime *)ext->seg;
ksa.exp_allocations = life->sadb_lifetime_allocations;
ksa.exp_bytes = life->sadb_lifetime_bytes;
ksa.exp_timeout = life->sadb_lifetime_addtime;
ksa.exp_first_use = life->sadb_lifetime_usetime;
}
ext = pf_key_v2_find_ext(ret, SADB_X_EXT_LIFETIME_LASTUSE);
if (ext) {
life = (struct sadb_lifetime *)ext->seg;
ksa.last_used = life->sadb_lifetime_usetime;
}
ext = pf_key_v2_find_ext(ret, SADB_EXT_ADDRESS_SRC);
if (ext) {
sa = (struct sockaddr *)ext->seg;
memcpy(&ksa.src, sa,
sa->sa_family == AF_INET ? sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6));
}
ext = pf_key_v2_find_ext(ret, SADB_EXT_ADDRESS_DST);
if (ext) {
sa = (struct sockaddr *)ext->seg;
memcpy(&ksa.dst, sa,
sa->sa_family == AF_INET ? sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6));
}
ext = pf_key_v2_find_ext(ret, SADB_X_EXT_UDPENCAP);
if (ext) {
udpencap = (struct sadb_x_udpencap *)ext->seg;
ksa.udpencap_port = udpencap->sadb_x_udpencap_port;
}
pf_key_v2_msg_free(ret);
LOG_DBG_BUF((LOG_SYSDEP, 50, "pf_key_v2_get_kernel_sa: spi", spi,
spi_sz));
return &ksa;
cleanup:
free(addr);
if (gettdb)
pf_key_v2_msg_free(gettdb);
if (ret)
pf_key_v2_msg_free(ret);
return 0;
}
static void
pf_key_v2_setup_sockaddr(void *res, struct sockaddr *src,
struct sockaddr *dst, in_port_t port, int ingress)
{
struct sockaddr_in *ip4_sa;
struct sockaddr_in6 *ip6_sa;
u_int8_t *p;
switch (src->sa_family) {
case AF_INET:
ip4_sa = (struct sockaddr_in *) res;
ip4_sa->sin_family = AF_INET;
ip4_sa->sin_len = sizeof *ip4_sa;
ip4_sa->sin_port = port;
if (dst)
p = (u_int8_t *) (ingress ?
&((struct sockaddr_in *)src)->sin_addr.s_addr :
&((struct sockaddr_in *)dst)->sin_addr.s_addr);
else
p = (u_int8_t *)&((struct sockaddr_in *)src)->sin_addr.s_addr;
ip4_sa->sin_addr.s_addr = *((in_addr_t *) p);
break;
case AF_INET6:
ip6_sa = (struct sockaddr_in6 *) res;
ip6_sa->sin6_family = AF_INET6;
ip6_sa->sin6_len = sizeof *ip6_sa;
ip6_sa->sin6_port = port;
if (dst)
p = (u_int8_t *) (ingress ?
&((struct sockaddr_in6 *)src)->sin6_addr.s6_addr :
&((struct sockaddr_in6 *)dst)->sin6_addr.s6_addr);
else
p = (u_int8_t *)&((struct sockaddr_in6 *)src)->sin6_addr.s6_addr;
memcpy(ip6_sa->sin6_addr.s6_addr, p, sizeof(struct in6_addr));
break;
default:
log_print("pf_key_v2_setup_sockaddr: unknown family %d\n",
src->sa_family);
break;
}
}
/*
* Store/update a PF_KEY_V2 security association with full information from the
* IKE SA and PROTO into the kernel. INCOMING is set if we are setting the
* parameters for the incoming SA, and cleared otherwise.
*/
int
pf_key_v2_set_spi(struct sa *sa, struct proto *proto, int incoming,
struct sa *isakmp_sa)
{
struct sadb_msg msg;
struct sadb_sa ssa;
struct sadb_x_tag *stag = NULL;
struct sadb_lifetime *life = 0;
struct sadb_address *addr = 0;
struct sadb_key *key = 0;
struct sadb_ident *sid = 0;
struct sockaddr *src, *dst;
struct pf_key_v2_msg *update = 0, *ret = 0;
struct ipsec_proto *iproto = proto->data;
size_t len;
int keylen, hashlen, err;
u_int8_t *pp;
int idtype;
struct ipsec_sa *isa = sa->data;
struct sadb_protocol flowtype, tprotocol;
struct sadb_x_udpencap udpencap;
char *addr_str, *s;
char iface_str[32];
msg.sadb_msg_type = incoming ? SADB_UPDATE : SADB_ADD;
switch (proto->proto) {
case IPSEC_PROTO_IPSEC_ESP:
msg.sadb_msg_satype = SADB_SATYPE_ESP;
keylen = ipsec_esp_enckeylength(proto);
hashlen = ipsec_esp_authkeylength(proto);
switch (proto->id) {
case IPSEC_ESP_3DES:
ssa.sadb_sa_encrypt = SADB_EALG_3DESCBC;
break;
case IPSEC_ESP_AES:
ssa.sadb_sa_encrypt = SADB_X_EALG_AES;
break;
case IPSEC_ESP_AES_CTR:
ssa.sadb_sa_encrypt = SADB_X_EALG_AESCTR;
break;
case IPSEC_ESP_AES_GCM_16:
ssa.sadb_sa_encrypt = SADB_X_EALG_AESGCM16;
break;
case IPSEC_ESP_AES_GMAC:
ssa.sadb_sa_encrypt = SADB_X_EALG_AESGMAC;
break;
case IPSEC_ESP_CAST:
ssa.sadb_sa_encrypt = SADB_X_EALG_CAST;
break;
case IPSEC_ESP_BLOWFISH:
ssa.sadb_sa_encrypt = SADB_X_EALG_BLF;
break;
case IPSEC_ESP_NULL:
ssa.sadb_sa_encrypt = SADB_EALG_NULL;
break;
default:
LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_set_spi: "
"unknown encryption algorithm %d", proto->id));
return -1;
}
switch (iproto->auth) {
case IPSEC_AUTH_HMAC_MD5:
ssa.sadb_sa_auth = SADB_AALG_MD5HMAC;
break;
case IPSEC_AUTH_HMAC_SHA:
ssa.sadb_sa_auth = SADB_AALG_SHA1HMAC;
break;
case IPSEC_AUTH_HMAC_RIPEMD:
ssa.sadb_sa_auth = SADB_X_AALG_RIPEMD160HMAC;
break;
case IPSEC_AUTH_HMAC_SHA2_256:
ssa.sadb_sa_auth = SADB_X_AALG_SHA2_256;
break;
case IPSEC_AUTH_HMAC_SHA2_384:
ssa.sadb_sa_auth = SADB_X_AALG_SHA2_384;
break;
case IPSEC_AUTH_HMAC_SHA2_512:
ssa.sadb_sa_auth = SADB_X_AALG_SHA2_512;
break;
case IPSEC_AUTH_DES_MAC:
case IPSEC_AUTH_KPDK:
/* XXX We should be supporting KPDK */
LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_set_spi: "
"unknown authentication algorithm %d",
iproto->auth));
return -1;
default:
ssa.sadb_sa_auth = SADB_AALG_NONE;
}
break;
case IPSEC_PROTO_IPSEC_AH:
msg.sadb_msg_satype = SADB_SATYPE_AH;
hashlen = ipsec_ah_keylength(proto);
keylen = 0;
ssa.sadb_sa_encrypt = SADB_EALG_NONE;
switch (proto->id) {
case IPSEC_AH_MD5:
ssa.sadb_sa_auth = SADB_AALG_MD5HMAC;
break;
case IPSEC_AH_SHA:
ssa.sadb_sa_auth = SADB_AALG_SHA1HMAC;
break;
case IPSEC_AH_RIPEMD:
ssa.sadb_sa_auth = SADB_X_AALG_RIPEMD160HMAC;
break;
case IPSEC_AH_SHA2_256:
ssa.sadb_sa_auth = SADB_X_AALG_SHA2_256;
break;
case IPSEC_AH_SHA2_384:
ssa.sadb_sa_auth = SADB_X_AALG_SHA2_384;
break;
case IPSEC_AH_SHA2_512:
ssa.sadb_sa_auth = SADB_X_AALG_SHA2_512;
break;
default:
LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_set_spi: "
"unknown authentication algorithm %d", proto->id));
goto cleanup;
}
break;
case IPSEC_PROTO_IPCOMP:
msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
ssa.sadb_sa_auth = SADB_AALG_NONE;
keylen = 0;
hashlen = 0;
/*
* Put compression algorithm type in the sadb_sa_encrypt
* field.
*/
switch (proto->id) {
case IPSEC_IPCOMP_OUI:
ssa.sadb_sa_encrypt = SADB_X_CALG_OUI;
break;
case IPSEC_IPCOMP_DEFLATE:
ssa.sadb_sa_encrypt = SADB_X_CALG_DEFLATE;
break;
default:
break;
}
break;
default:
log_print("pf_key_v2_set_spi: invalid proto %d", proto->proto);
goto cleanup;
}
if (incoming)
sa->transport->vtbl->get_src(sa->transport, &dst);
else
sa->transport->vtbl->get_dst(sa->transport, &dst);
msg.sadb_msg_seq = sa->seq;
update = pf_key_v2_msg_new(&msg, 0);
if (!update)
goto cleanup;
/* Setup the rest of the SA extension. */
ssa.sadb_sa_exttype = SADB_EXT_SA;
ssa.sadb_sa_len = sizeof ssa / PF_KEY_V2_CHUNK;
if (proto->spi_sz[incoming] == 2) /* IPCOMP uses 16bit CPIs. */
ssa.sadb_sa_spi = htonl(proto->spi[incoming][0] << 8 |
proto->spi[incoming][1]);
else
memcpy(&ssa.sadb_sa_spi, proto->spi[incoming],
sizeof ssa.sadb_sa_spi);
ssa.sadb_sa_replay = conf_get_str("General", "Shared-SADB") ? 0 :
iproto->replay_window;
ssa.sadb_sa_state = SADB_SASTATE_MATURE;
ssa.sadb_sa_flags = 0;
if (iproto->encap_mode == IPSEC_ENCAP_TUNNEL ||
iproto->encap_mode == IPSEC_ENCAP_UDP_ENCAP_TUNNEL ||
iproto->encap_mode == IPSEC_ENCAP_UDP_ENCAP_TUNNEL_DRAFT)
ssa.sadb_sa_flags = SADB_X_SAFLAGS_TUNNEL;
if (isakmp_sa->flags & SA_FLAG_NAT_T_ENABLE) {
bzero(&udpencap, sizeof udpencap);
ssa.sadb_sa_flags |= SADB_X_SAFLAGS_UDPENCAP;
udpencap.sadb_x_udpencap_exttype = SADB_X_EXT_UDPENCAP;
udpencap.sadb_x_udpencap_len =
sizeof udpencap / PF_KEY_V2_CHUNK;
udpencap.sadb_x_udpencap_port = sockaddr_port(dst);
if (pf_key_v2_msg_add(update, (struct sadb_ext *)&udpencap, 0)
== -1)
goto cleanup;
}
if (pf_key_v2_msg_add(update, (struct sadb_ext *)&ssa, 0) == -1)
goto cleanup;
if (sa->seconds || sa->kilobytes) {
/* Setup the hard limits. */
life = malloc(sizeof *life);
if (!life)
goto cleanup;
life->sadb_lifetime_len = sizeof *life / PF_KEY_V2_CHUNK;
life->sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD;
life->sadb_lifetime_allocations = 0;
life->sadb_lifetime_bytes = sa->kilobytes * 1024;
/*
* XXX I am not sure which one is best in security respect.
* Maybe the RFCs actually mandate what a lifetime really is.
*/
#if 0
life->sadb_lifetime_addtime = 0;
life->sadb_lifetime_usetime = sa->seconds;
#else
life->sadb_lifetime_addtime = sa->seconds;
life->sadb_lifetime_usetime = 0;
#endif
if (pf_key_v2_msg_add(update, (struct sadb_ext *) life,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
life = 0;
/*
* Setup the soft limits, we use 90 % of the hard ones.
* XXX A configurable ratio would be better.
*/
life = malloc(sizeof *life);
if (!life)
goto cleanup;
life->sadb_lifetime_len = sizeof *life / PF_KEY_V2_CHUNK;
life->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT;
life->sadb_lifetime_allocations = 0;
life->sadb_lifetime_bytes = sa->kilobytes * 1024 * 9 / 10;
/*
* XXX I am not sure which one is best in security respect.
* Maybe the RFCs actually mandate what a lifetime really is.
*/
#if 0
life->sadb_lifetime_addtime = 0;
life->sadb_lifetime_usetime = sa->seconds * 9 / 10;
#else
life->sadb_lifetime_addtime = sa->seconds * 9 / 10;
life->sadb_lifetime_usetime = 0;
#endif
if (pf_key_v2_msg_add(update, (struct sadb_ext *) life,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
life = 0;
}
/*
* Setup the ADDRESS extensions.
*/
if (incoming)
sa->transport->vtbl->get_dst(sa->transport, &src);
else
sa->transport->vtbl->get_src(sa->transport, &src);
len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(src));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, src, SA_LEN(src));
switch (((struct sockaddr *) (addr + 1))->sa_family) {
case AF_INET:
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
break;
case AF_INET6:
((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
break;
}
if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(dst));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, dst, SA_LEN(dst));
switch (((struct sockaddr *) (addr + 1))->sa_family) {
case AF_INET:
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
break;
case AF_INET6:
((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
break;
}
if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
if (proto->proto != IPSEC_PROTO_IPCOMP) {
/* Setup the KEY extensions. */
if (hashlen) {
len = sizeof *key + PF_KEY_V2_ROUND(hashlen);
key = malloc(len);
if (!key)
goto cleanup;
key->sadb_key_exttype = SADB_EXT_KEY_AUTH;
key->sadb_key_len = len / PF_KEY_V2_CHUNK;
key->sadb_key_bits = hashlen * 8;
key->sadb_key_reserved = 0;
memcpy(key + 1,
iproto->keymat[incoming] +
(proto->proto ==
IPSEC_PROTO_IPSEC_ESP ? keylen : 0),
hashlen);
if (pf_key_v2_msg_add(update, (struct sadb_ext *) key,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
key = 0;
}
if (keylen) {
len = sizeof *key + PF_KEY_V2_ROUND(keylen);
key = malloc(len);
if (!key)
goto cleanup;
key->sadb_key_exttype = SADB_EXT_KEY_ENCRYPT;
key->sadb_key_len = len / PF_KEY_V2_CHUNK;
key->sadb_key_bits = keylen * 8;
key->sadb_key_reserved = 0;
memcpy(key + 1, iproto->keymat[incoming], keylen);
if (pf_key_v2_msg_add(update, (struct sadb_ext *) key,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
key = 0;
}
}
/* Setup identity extensions. */
if (isakmp_sa->id_i) {
pp = pf_key_v2_convert_id(isakmp_sa->id_i, isakmp_sa->id_i_len,
&len, &idtype);
if (!pp)
goto nosid;
sid = calloc(PF_KEY_V2_ROUND(len + 1) + sizeof *sid,
sizeof(u_int8_t));
if (!sid) {
free(pp);
goto cleanup;
}
sid->sadb_ident_type = idtype;
sid->sadb_ident_len = ((sizeof *sid) / PF_KEY_V2_CHUNK) +
PF_KEY_V2_ROUND(len + 1) / PF_KEY_V2_CHUNK;
if ((isakmp_sa->initiator && !incoming) ||
(!isakmp_sa->initiator && incoming))
sid->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC;
else
sid->sadb_ident_exttype = SADB_EXT_IDENTITY_DST;
memcpy(sid + 1, pp, len);
free(pp);
if (pf_key_v2_msg_add(update, (struct sadb_ext *) sid,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
sid = 0;
nosid:
free(sid);
sid = 0;
}
if (isakmp_sa->id_r) {
pp = pf_key_v2_convert_id(isakmp_sa->id_r, isakmp_sa->id_r_len,
&len, &idtype);
if (!pp)
goto nodid;
sid = calloc(PF_KEY_V2_ROUND(len + 1) + sizeof *sid,
sizeof(u_int8_t));
if (!sid) {
free(pp);
goto cleanup;
}
sid->sadb_ident_type = idtype;
sid->sadb_ident_len = ((sizeof *sid) / PF_KEY_V2_CHUNK) +
PF_KEY_V2_ROUND(len + 1) / PF_KEY_V2_CHUNK;
if ((isakmp_sa->initiator && !incoming) ||
(!isakmp_sa->initiator && incoming))
sid->sadb_ident_exttype = SADB_EXT_IDENTITY_DST;
else
sid->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC;
memcpy(sid + 1, pp, len);
free(pp);
if (pf_key_v2_msg_add(update, (struct sadb_ext *) sid,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
sid = 0;
nodid:
free(sid);
sid = 0;
}
/* Setup the flow type extension. */
bzero(&flowtype, sizeof flowtype);
flowtype.sadb_protocol_exttype = SADB_X_EXT_FLOW_TYPE;
flowtype.sadb_protocol_len = sizeof flowtype / PF_KEY_V2_CHUNK;
flowtype.sadb_protocol_direction = incoming ?
IPSP_DIRECTION_IN : IPSP_DIRECTION_OUT;
if (pf_key_v2_msg_add(update, (struct sadb_ext *)&flowtype, 0) == -1)
goto cleanup;
bzero(&tprotocol, sizeof tprotocol);
tprotocol.sadb_protocol_exttype = SADB_X_EXT_PROTOCOL;
tprotocol.sadb_protocol_len = sizeof tprotocol / PF_KEY_V2_CHUNK;
tprotocol.sadb_protocol_proto = isa->tproto;
if (pf_key_v2_msg_add(update, (struct sadb_ext *)&tprotocol,
0) == -1)
goto cleanup;
len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(isa->src_net));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = incoming ?
SADB_X_EXT_DST_FLOW : SADB_X_EXT_SRC_FLOW;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, isa->src_net, 0, isa->sport, 0);
if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype =
incoming ? SADB_X_EXT_DST_MASK : SADB_X_EXT_SRC_MASK;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, isa->src_mask, 0,
isa->sport ? 0xffff : 0, 0);
if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = incoming ?
SADB_X_EXT_SRC_FLOW : SADB_X_EXT_DST_FLOW;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, isa->dst_net, 0, isa->dport, 0);
if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype =
incoming ? SADB_X_EXT_SRC_MASK : SADB_X_EXT_DST_MASK;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, isa->dst_mask, 0,
isa->dport ? 0xffff : 0, 0);
if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
/* Add a pf tag to matching packets of this SA. */
if (sa->tag != NULL) {
len = sizeof(*stag) + PF_KEY_V2_ROUND(strlen(sa->tag) + 1);
if ((stag = calloc(1, len)) == NULL)
goto cleanup;
stag->sadb_x_tag_exttype = SADB_X_EXT_TAG;
stag->sadb_x_tag_len = len / PF_KEY_V2_CHUNK;
stag->sadb_x_tag_taglen = strlen(sa->tag) + 1;
s = (char *)(stag + 1);
strlcpy(s, sa->tag, stag->sadb_x_tag_taglen);
if (pf_key_v2_msg_add(update, (struct sadb_ext *)stag,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
}
if (sa->flags & SA_FLAG_IFACE) {
struct sadb_x_iface *siface;
len = sizeof(*siface);
siface = calloc(1, len);
if (siface == NULL)
goto cleanup;
siface->sadb_x_iface_len = len / PF_KEY_V2_CHUNK;
siface->sadb_x_iface_exttype = SADB_X_EXT_IFACE;
siface->sadb_x_iface_unit = sa->iface;
siface->sadb_x_iface_direction = incoming ?
IPSP_DIRECTION_IN : IPSP_DIRECTION_OUT;
if (pf_key_v2_msg_add(update, (struct sadb_ext *)siface,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
snprintf(iface_str, sizeof(iface_str), "iface %u", sa->iface);
}
/* XXX Here can sensitivity extensions be setup. */
if (sockaddr2text(dst, &addr_str, 0))
addr_str = 0;
LOG_DBG((LOG_SYSDEP, 10, "pf_key_v2_set_spi: "
"satype %d dst %s SPI 0x%x%s%s%s", msg.sadb_msg_satype,
addr_str ? addr_str : "unknown",
ntohl(ssa.sadb_sa_spi), sa->tag ? " tag " : "",
sa->tag ? sa->tag : "", iface_str));
free(addr_str);
/*
* Although PF_KEY knows about expirations, it is unreliable per the
* specs thus we need to do them inside isakmpd as well.
*/
if (sa->seconds)
if (sa_setup_expirations(sa))
goto cleanup;
ret = pf_key_v2_call(update);
pf_key_v2_msg_free(update);
update = 0;
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
pf_key_v2_msg_free(ret);
ret = 0;
/*
* If we are doing an addition into an SADB shared with our peer,
* errors here are to be expected as the peer will already have
* created the SA, and can thus be ignored.
*/
if (err && !(msg.sadb_msg_type == SADB_ADD &&
conf_get_str("General", "Shared-SADB"))) {
log_print("pf_key_v2_set_spi: %s: %s",
msg.sadb_msg_type == SADB_ADD ? "ADD" : "UPDATE",
strerror(err));
goto cleanup;
}
LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_set_spi: done"));
return 0;
cleanup:
free(sid);
free(addr);
free(life);
free(key);
if (update)
pf_key_v2_msg_free(update);
if (ret)
pf_key_v2_msg_free(ret);
return -1;
}
static __inline__ int
pf_key_v2_mask_to_bits(u_int32_t mask)
{
u_int32_t hmask = ntohl(mask);
return (33 - ffs(~hmask + 1)) % 33;
}
static int
pf_key_v2_mask6_to_bits(u_int8_t *mask)
{
int n;
bit_ffc(mask, 128, &n);
return n == -1 ? 128 : n;
}
/*
* Enable/disable a flow.
* XXX Assumes OpenBSD {ADD,DEL}FLOW extensions.
*/
static int
pf_key_v2_flow(struct sockaddr *laddr, struct sockaddr *lmask,
struct sockaddr *raddr, struct sockaddr *rmask,
u_int8_t tproto, u_int16_t sport, u_int16_t dport,
u_int8_t *spi, u_int8_t proto, struct sockaddr *dst,
struct sockaddr *src, int delete, int ingress,
u_int8_t srcid_type, u_int8_t *srcid, int srcid_len,
u_int8_t dstid_type, u_int8_t *dstid, int dstid_len,
struct ipsec_proto *iproto)
{
char *laddr_str, *lmask_str, *raddr_str, *rmask_str;
struct sadb_msg msg;
struct sadb_protocol flowtype;
struct sadb_ident *sid = 0;
struct sadb_address *addr = 0;
struct sadb_protocol tprotocol;
struct pf_key_v2_msg *flow = 0, *ret = 0;
size_t len;
int err;
msg.sadb_msg_type = delete ? SADB_X_DELFLOW : SADB_X_ADDFLOW;
switch (proto) {
case IPSEC_PROTO_IPSEC_ESP:
msg.sadb_msg_satype = SADB_SATYPE_ESP;
break;
case IPSEC_PROTO_IPSEC_AH:
msg.sadb_msg_satype = SADB_SATYPE_AH;
break;
case IPSEC_PROTO_IPCOMP:
msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
break;
default:
log_print("pf_key_v2_flow: invalid proto %d", proto);
goto cleanup;
}
msg.sadb_msg_seq = 0;
flow = pf_key_v2_msg_new(&msg, 0);
if (!flow)
goto cleanup;
if (!delete) {
/* Setup the source ID, if provided. */
if (srcid) {
sid = calloc(
PF_KEY_V2_ROUND(srcid_len + 1) + sizeof *sid,
sizeof(u_int8_t));
if (!sid)
goto cleanup;
sid->sadb_ident_len = ((sizeof *sid) / PF_KEY_V2_CHUNK)
+ PF_KEY_V2_ROUND(srcid_len + 1) / PF_KEY_V2_CHUNK;
sid->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC;
sid->sadb_ident_type = srcid_type;
memcpy(sid + 1, srcid, srcid_len);
if (pf_key_v2_msg_add(flow, (struct sadb_ext *) sid,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
sid = 0;
}
/* Setup the destination ID, if provided. */
if (dstid) {
sid = calloc(
PF_KEY_V2_ROUND(dstid_len + 1) + sizeof *sid,
sizeof(u_int8_t));
if (!sid)
goto cleanup;
sid->sadb_ident_len = ((sizeof *sid) / PF_KEY_V2_CHUNK)
+ PF_KEY_V2_ROUND(dstid_len + 1) / PF_KEY_V2_CHUNK;
sid->sadb_ident_exttype = SADB_EXT_IDENTITY_DST;
sid->sadb_ident_type = dstid_type;
memcpy(sid + 1, dstid, dstid_len);
if (pf_key_v2_msg_add(flow, (struct sadb_ext *) sid,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
sid = 0;
}
}
/* Setup the flow type extension. */
bzero(&flowtype, sizeof flowtype);
flowtype.sadb_protocol_exttype = SADB_X_EXT_FLOW_TYPE;
flowtype.sadb_protocol_len = sizeof flowtype / PF_KEY_V2_CHUNK;
flowtype.sadb_protocol_direction =
ingress ? IPSP_DIRECTION_IN : IPSP_DIRECTION_OUT;
flowtype.sadb_protocol_proto = SADB_X_FLOW_TYPE_REQUIRE;
if (pf_key_v2_msg_add(flow, (struct sadb_ext *)&flowtype, 0) == -1)
goto cleanup;
/*
* Setup the ADDRESS extensions.
*/
len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(src));
if (!delete)
{
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, src, dst, 0, ingress);
if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
}
len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(laddr));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_X_EXT_SRC_FLOW;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, laddr, 0, sport, 0);
if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_X_EXT_SRC_MASK;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, lmask, 0, sport ? 0xffff : 0, 0);
if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_X_EXT_DST_FLOW;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, raddr, 0, dport, 0);
if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_X_EXT_DST_MASK;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
pf_key_v2_setup_sockaddr(addr + 1, rmask, 0, dport ? 0xffff : 0, 0);
if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
/* Setup the protocol extension. */
bzero(&tprotocol, sizeof tprotocol);
tprotocol.sadb_protocol_exttype = SADB_X_EXT_PROTOCOL;
tprotocol.sadb_protocol_len = sizeof tprotocol / PF_KEY_V2_CHUNK;
tprotocol.sadb_protocol_proto = tproto;
if (pf_key_v2_msg_add(flow, (struct sadb_ext *)&tprotocol, 0) == -1)
goto cleanup;
if (sockaddr2text(laddr, &laddr_str, 0))
laddr_str = 0;
if (sockaddr2text(lmask, &lmask_str, 0))
lmask_str = 0;
if (sockaddr2text(raddr, &raddr_str, 0))
raddr_str = 0;
if (sockaddr2text(rmask, &rmask_str, 0))
rmask_str = 0;
LOG_DBG((LOG_SYSDEP, 50,
"pf_key_v2_flow: src %s %s dst %s %s proto %u sport %u dport %u",
laddr_str ? laddr_str : "<??\?>", lmask_str ? lmask_str : "<??\?>",
raddr_str ? raddr_str : "<??\?>", rmask_str ? rmask_str : "<??\?>",
tproto, ntohs(sport), ntohs(dport)));
free(laddr_str);
free(lmask_str);
free(raddr_str);
free(rmask_str);
ret = pf_key_v2_call(flow);
pf_key_v2_msg_free(flow);
flow = 0;
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
if (err) {
if (err == ESRCH) /* These are common and usually
* harmless. */
LOG_DBG((LOG_SYSDEP, 10, "pf_key_v2_flow: %sFLOW: %s",
delete ? "DEL" : "ADD", strerror(err)));
else
log_print("pf_key_v2_flow: %sFLOW: %s",
delete ? "DEL" : "ADD", strerror(err));
goto cleanup;
}
pf_key_v2_msg_free(ret);
LOG_DBG((LOG_MISC, 50, "pf_key_v2_flow: %sFLOW: done",
delete ? "DEL" : "ADD"));
return 0;
cleanup:
free(sid);
free(addr);
if (flow)
pf_key_v2_msg_free(flow);
if (ret)
pf_key_v2_msg_free(ret);
return -1;
}
static u_int8_t *
pf_key_v2_convert_id(u_int8_t *id, int idlen, size_t *reslen, int *idtype)
{
u_int8_t *addr, *res = 0;
char addrbuf[ADDRESS_MAX + 5];
switch (id[0]) {
case IPSEC_ID_FQDN:
res = calloc(idlen - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ,
sizeof(u_int8_t));
if (!res)
return 0;
*reslen = idlen - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ;
memcpy(res, id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ, *reslen);
*idtype = SADB_IDENTTYPE_FQDN;
LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: FQDN %.*s",
(int) *reslen, res));
return res;
case IPSEC_ID_USER_FQDN:
res = calloc(idlen - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ,
sizeof(u_int8_t));
if (!res)
return 0;
*reslen = idlen - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ;
memcpy(res, id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ, *reslen);
*idtype = SADB_IDENTTYPE_USERFQDN;
LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: UFQDN %.*s",
(int) *reslen, res));
return res;
case IPSEC_ID_IPV4_ADDR:
if (inet_ntop(AF_INET, id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ,
addrbuf, ADDRESS_MAX) == NULL)
return 0;
*reslen = strlen(addrbuf) + 3;
strlcat(addrbuf, "/32", ADDRESS_MAX + 5);
res = (u_int8_t *) strdup(addrbuf);
if (!res)
return 0;
*idtype = SADB_IDENTTYPE_PREFIX;
LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: "
"IPv4 address %s", res));
return res;
case IPSEC_ID_IPV6_ADDR:
if (inet_ntop(AF_INET6,
id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ,
addrbuf, ADDRESS_MAX) == NULL)
return 0;
*reslen = strlen(addrbuf) + 4;
strlcat(addrbuf, "/128", ADDRESS_MAX + 5);
res = (u_int8_t *) strdup(addrbuf);
if (!res)
return 0;
LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: "
"IPv6 address %s", res));
*idtype = SADB_IDENTTYPE_PREFIX;
return res;
case IPSEC_ID_IPV4_ADDR_SUBNET: /* XXX PREFIX */
addr = id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ;
if (inet_ntop(AF_INET, addr, addrbuf, ADDRESS_MAX) == NULL)
return 0;
snprintf(addrbuf + strlen(addrbuf),
ADDRESS_MAX - strlen(addrbuf), "/%d",
pf_key_v2_mask_to_bits(*(u_int32_t *)(addr +
sizeof(struct in_addr))));
*reslen = strlen(addrbuf);
res = (u_int8_t *) strdup(addrbuf);
if (!res)
return 0;
*idtype = SADB_IDENTTYPE_PREFIX;
LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: "
"IPv4 subnet %s", res));
return res;
case IPSEC_ID_IPV6_ADDR_SUBNET: /* XXX PREFIX */
addr = id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ;
if (inet_ntop(AF_INET6, addr, addrbuf, ADDRESS_MAX) == NULL)
return 0;
snprintf(addrbuf + strlen(addrbuf),
ADDRESS_MAX - strlen(addrbuf), "/%d",
pf_key_v2_mask6_to_bits(addr +
sizeof(struct in6_addr)));
*reslen = strlen(addrbuf);
res = (u_int8_t *) strdup(addrbuf);
if (!res)
return 0;
LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: "
"IPv6 subnet %s", res));
*idtype = SADB_IDENTTYPE_PREFIX;
return res;
case IPSEC_ID_IPV4_RANGE:
case IPSEC_ID_IPV6_RANGE:
case IPSEC_ID_DER_ASN1_DN:
case IPSEC_ID_DER_ASN1_GN:
case IPSEC_ID_KEY_ID:
/* XXX Not implemented yet. */
return 0;
}
return 0;
}
/* Enable a flow given an SA. */
int
pf_key_v2_enable_sa(struct sa *sa, struct sa *isakmp_sa)
{
struct ipsec_sa *isa = sa->data;
struct sockaddr *dst, *src;
int error;
struct proto *proto = TAILQ_FIRST(&sa->protos);
int sidtype = 0, didtype = 0;
size_t sidlen = 0, didlen = 0;
u_int8_t *sid = 0, *did = 0;
if (proto == NULL) {
log_print("pf_key_v2_enable_sa: no proto");
return EINVAL;
}
sa->transport->vtbl->get_dst(sa->transport, &dst);
sa->transport->vtbl->get_src(sa->transport, &src);
if (isakmp_sa->id_i) {
if (isakmp_sa->initiator)
sid = pf_key_v2_convert_id(isakmp_sa->id_i,
isakmp_sa->id_i_len, &sidlen, &sidtype);
else
did = pf_key_v2_convert_id(isakmp_sa->id_i,
isakmp_sa->id_i_len, &didlen, &didtype);
}
if (isakmp_sa->id_r) {
if (isakmp_sa->initiator)
did = pf_key_v2_convert_id(isakmp_sa->id_r,
isakmp_sa->id_r_len, &didlen, &didtype);
else
sid = pf_key_v2_convert_id(isakmp_sa->id_r,
isakmp_sa->id_r_len, &sidlen, &sidtype);
}
error = pf_key_v2_flow(isa->src_net, isa->src_mask, isa->dst_net,
isa->dst_mask, isa->tproto, isa->sport, isa->dport, proto->spi[0],
proto->proto, dst, src, 0, 0, sidtype, sid, sidlen, didtype, did,
didlen, proto->data);
if (error)
goto cleanup;
error = pf_key_v2_flow(isa->dst_net, isa->dst_mask, isa->src_net,
isa->src_mask, isa->tproto, isa->dport, isa->sport, proto->spi[1],
proto->proto, src, dst, 0, 1, sidtype, sid, sidlen, didtype, did,
didlen, proto->data);
cleanup:
free(sid);
free(did);
return error;
}
/* Increase reference count of refcounted sections. */
static int
pf_key_v2_conf_refinc(int af, char *section)
{
char conn[22];
int num;
if (!section)
return 0;
num = conf_get_num(section, "Refcount", 0);
if (num == 0)
return 0;
snprintf(conn, sizeof conn, "%d", num + 1);
conf_set(af, section, "Refcount", conn, 1, 0);
return 0;
}
/*
* Return 0 if the section didn't exist or was removed, non-zero otherwise.
* Don't touch non-refcounted (statically defined) sections.
*/
static int
pf_key_v2_conf_refhandle(int af, char *section)
{
char conn[22];
int num;
if (!section)
return 0;
num = conf_get_num(section, "Refcount", 0);
if (num == 1) {
conf_remove_section(af, section);
num--;
} else if (num != 0) {
snprintf(conn, sizeof conn, "%d", num - 1);
conf_set(af, section, "Refcount", conn, 1, 0);
}
return num;
}
/* Remove all dynamically-established configuration entries. */
static int
pf_key_v2_remove_conf(char *section)
{
char *ikepeer, *localid, *remoteid, *configname;
struct conf_list_node *attr;
struct conf_list *attrs;
int af;
if (!section)
return 0;
if (!conf_get_str(section, "Phase"))
return 0;
/* Only remove dynamically-established entries. */
attrs = conf_get_list(section, "Flags");
if (attrs) {
for (attr = TAILQ_FIRST(&attrs->fields); attr;
attr = TAILQ_NEXT(attr, link))
if (!strcasecmp(attr->field, "__ondemand"))
goto passed;
conf_free_list(attrs);
}
return 0;
passed:
conf_free_list(attrs);
af = conf_begin();
configname = conf_get_str(section, "Configuration");
pf_key_v2_conf_refhandle(af, configname);
/* These are the Phase 2 Local/Remote IDs. */
localid = conf_get_str(section, "Local-ID");
pf_key_v2_conf_refhandle(af, localid);
remoteid = conf_get_str(section, "Remote-ID");
pf_key_v2_conf_refhandle(af, remoteid);
ikepeer = conf_get_str(section, "ISAKMP-peer");
pf_key_v2_conf_refhandle(af, section);
if (ikepeer) {
remoteid = conf_get_str(ikepeer, "Remote-ID");
localid = conf_get_str(ikepeer, "ID");
configname = conf_get_str(ikepeer, "Configuration");
pf_key_v2_conf_refhandle(af, ikepeer);
pf_key_v2_conf_refhandle(af, configname);
/* Phase 1 IDs */
pf_key_v2_conf_refhandle(af, localid);
pf_key_v2_conf_refhandle(af, remoteid);
}
conf_end(af, 1);
return 0;
}
/* Disable a flow given a SA. */
int
pf_key_v2_disable_sa(struct sa *sa, int incoming)
{
struct ipsec_sa *isa = sa->data;
struct sockaddr *dst, *src;
struct proto *proto = TAILQ_FIRST(&sa->protos);
sa->transport->vtbl->get_dst(sa->transport, &dst);
sa->transport->vtbl->get_src(sa->transport, &src);
if (!incoming)
return pf_key_v2_flow(isa->src_net, isa->src_mask,
isa->dst_net, isa->dst_mask, isa->tproto, isa->sport,
isa->dport, proto->spi[0], proto->proto, src, dst, 1, 0,
0, 0, 0, 0, 0, 0, proto->data);
else {
return pf_key_v2_flow(isa->dst_net, isa->dst_mask,
isa->src_net, isa->src_mask, isa->tproto, isa->dport,
isa->sport, proto->spi[1], proto->proto, src, dst, 1, 1,
0, 0, 0, 0, 0, 0, proto->data);
}
}
/*
* Delete the IPsec SA represented by the INCOMING direction in protocol PROTO
* of the IKE security association SA. Also delete potential flows tied to it.
*/
int
pf_key_v2_delete_spi(struct sa *sa, struct proto *proto, int incoming)
{
struct sadb_msg msg;
struct sadb_sa ssa;
struct sadb_address *addr = 0;
struct sockaddr *saddr;
int len, err;
struct pf_key_v2_msg *delete = 0, *ret = 0;
/* If it's not an established SA, don't proceed. */
if (!(sa->flags & SA_FLAG_READY))
return 0;
if (sa->name && !(sa->flags & SA_FLAG_REPLACED)) {
LOG_DBG((LOG_SYSDEP, 50,
"pf_key_v2_delete_spi: removing configuration %s",
sa->name));
pf_key_v2_remove_conf(sa->name);
}
msg.sadb_msg_type = SADB_DELETE;
switch (proto->proto) {
case IPSEC_PROTO_IPSEC_ESP:
msg.sadb_msg_satype = SADB_SATYPE_ESP;
break;
case IPSEC_PROTO_IPSEC_AH:
msg.sadb_msg_satype = SADB_SATYPE_AH;
break;
case IPSEC_PROTO_IPCOMP:
msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
break;
default:
log_print("pf_key_v2_delete_spi: invalid proto %d",
proto->proto);
goto cleanup;
}
msg.sadb_msg_seq = 0;
delete = pf_key_v2_msg_new(&msg, 0);
if (!delete)
goto cleanup;
/* Setup the SA extension. */
ssa.sadb_sa_exttype = SADB_EXT_SA;
ssa.sadb_sa_len = sizeof ssa / PF_KEY_V2_CHUNK;
memcpy(&ssa.sadb_sa_spi, proto->spi[incoming], sizeof ssa.sadb_sa_spi);
ssa.sadb_sa_replay = 0;
ssa.sadb_sa_state = 0;
ssa.sadb_sa_auth = 0;
ssa.sadb_sa_encrypt = 0;
ssa.sadb_sa_flags = 0;
if (pf_key_v2_msg_add(delete, (struct sadb_ext *)&ssa, 0) == -1)
goto cleanup;
/*
* Setup the ADDRESS extensions.
*/
if (incoming)
sa->transport->vtbl->get_dst(sa->transport, &saddr);
else
sa->transport->vtbl->get_src(sa->transport, &saddr);
len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(saddr));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, saddr, SA_LEN(saddr));
switch (saddr->sa_family) {
case AF_INET:
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
break;
case AF_INET6:
((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
break;
}
if (pf_key_v2_msg_add(delete, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
if (incoming)
sa->transport->vtbl->get_src(sa->transport, &saddr);
else
sa->transport->vtbl->get_dst(sa->transport, &saddr);
len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(saddr));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, saddr, SA_LEN(saddr));
switch (saddr->sa_family) {
case AF_INET:
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
break;
case AF_INET6:
((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
break;
}
if (pf_key_v2_msg_add(delete, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
ret = pf_key_v2_call(delete);
pf_key_v2_msg_free(delete);
delete = 0;
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
if (err) {
LOG_DBG((LOG_SYSDEP, 10, "pf_key_v2_delete_spi: DELETE: %s",
strerror(err)));
goto cleanup;
}
pf_key_v2_msg_free(ret);
LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_delete_spi: done"));
return 0;
cleanup:
free(addr);
if (delete)
pf_key_v2_msg_free(delete);
if (ret)
pf_key_v2_msg_free(ret);
return -1;
}
static void
pf_key_v2_stayalive(struct exchange *exchange, void *vconn, int fail)
{
char *conn = vconn;
struct sa *sa;
/* XXX What if it is phase 1 ? */
sa = sa_lookup_by_name(conn, 2);
if (sa)
sa->flags |= SA_FLAG_STAYALIVE;
/*
* Remove failed configuration entry -- call twice because it is
* created with a Refcount of 2.
*/
if (fail && (!exchange || exchange->name)) {
pf_key_v2_remove_conf(conn);
pf_key_v2_remove_conf(conn);
}
free(conn);
}
/* Check if a connection CONN exists, otherwise establish it. */
void
pf_key_v2_connection_check(char *conn)
{
if (!sa_lookup_by_name(conn, 2)) {
LOG_DBG((LOG_SYSDEP, 70,
"pf_key_v2_connection_check: SA for %s missing", conn));
exchange_establish(conn, pf_key_v2_stayalive, conn, 0);
} else {
LOG_DBG((LOG_SYSDEP, 70, "pf_key_v2_connection_check: "
"SA for %s exists", conn));
free(conn);
}
}
/* Handle a PF_KEY lifetime expiration message PMSG. */
static void
pf_key_v2_expire(struct pf_key_v2_msg *pmsg)
{
struct sadb_msg *msg;
struct sadb_sa *ssa;
struct sadb_address *dst;
struct sockaddr *dstaddr;
struct sadb_lifetime *life, *lifecurrent;
struct sa *sa;
struct pf_key_v2_node *lifenode, *ext;
char *dst_str;
msg = (struct sadb_msg *)TAILQ_FIRST(pmsg)->seg;
ext = pf_key_v2_find_ext(pmsg, SADB_EXT_SA);
if (!ext) {
log_print("pf_key_v2_expire: no SA extension found");
return;
}
ssa = ext->seg;
ext = pf_key_v2_find_ext(pmsg, SADB_EXT_ADDRESS_DST);
if (!ext) {
log_print("pf_key_v2_expire: "
"no destination address extension found");
return;
}
dst = ext->seg;
dstaddr = (struct sockaddr *) (dst + 1);
lifenode = pf_key_v2_find_ext(pmsg, SADB_EXT_LIFETIME_HARD);
if (!lifenode)
lifenode = pf_key_v2_find_ext(pmsg, SADB_EXT_LIFETIME_SOFT);
if (!lifenode) {
log_print("pf_key_v2_expire: no lifetime extension found");
return;
}
life = lifenode->seg;
lifenode = pf_key_v2_find_ext(pmsg, SADB_EXT_LIFETIME_CURRENT);
if (!lifenode) {
log_print("pf_key_v2_expire: "
"no current lifetime extension found");
return;
}
lifecurrent = lifenode->seg;
if (sockaddr2text(dstaddr, &dst_str, 0))
dst_str = 0;
LOG_DBG((LOG_SYSDEP, 20, "pf_key_v2_expire: "
"%s dst %s SPI %x sproto %d",
life->sadb_lifetime_exttype == SADB_EXT_LIFETIME_SOFT ? "SOFT"
: "HARD", dst_str ? dst_str : "<unknown>",
ntohl(ssa->sadb_sa_spi), msg->sadb_msg_satype));
free(dst_str);
/*
* Find the IPsec SA. The IPsec stack has two SAs for every IKE SA,
* one outgoing and one incoming, we regard expirations for any of
* them as an expiration of the full IKE SA. Likewise, in
* protection suites consisting of more than one protocol, any
* expired individual IPsec stack SA will be seen as an expiration
* of the full suite.
*/
switch (msg->sadb_msg_satype) {
case SADB_SATYPE_ESP:
sa = ipsec_sa_lookup(dstaddr, ssa->sadb_sa_spi,
IPSEC_PROTO_IPSEC_ESP);
break;
case SADB_SATYPE_AH:
sa = ipsec_sa_lookup(dstaddr, ssa->sadb_sa_spi,
IPSEC_PROTO_IPSEC_AH);
break;
case SADB_X_SATYPE_IPCOMP:
sa = ipsec_sa_lookup(dstaddr, ssa->sadb_sa_spi,
IPSEC_PROTO_IPCOMP);
break;
default:
/* XXX Log? */
sa = 0;
break;
}
/* If the SA is already gone, don't do anything. */
if (!sa)
return;
/*
* If we got a notification, try to renegotiate the SA -- unless of
* course it has already been replaced by another.
* Also, ignore SAs that were not dynamically established, or that
* did not see any use.
*/
if (!(sa->flags & SA_FLAG_REPLACED) &&
(sa->flags & SA_FLAG_ONDEMAND) &&
lifecurrent->sadb_lifetime_bytes)
exchange_establish(sa->name, 0, 0, 0);
if (life->sadb_lifetime_exttype == SADB_EXT_LIFETIME_HARD) {
/* Remove the old SA, it isn't useful anymore. */
sa_free(sa);
}
}
static int
mask4len(const struct sockaddr_in *mask)
{
int len;
u_int32_t m;
len = 0;
for (m = 0x80000000; m & ntohl(mask->sin_addr.s_addr); m >>= 1)
len++;
if (len == 32)
len = -1;
return len;
}
#ifndef s6_addr8
#define s6_addr8 __u6_addr.__u6_addr8
#endif
static int
mask6len(const struct sockaddr_in6 *mask)
{
int i, len;
u_int8_t m;
len = 0;
for (i = 0, m = 0; i < 16 && !m; i++)
for (m = 0x80; m & mask->sin6_addr.s6_addr8[i]; m >>= 1)
len++;
if (len == 128)
len = -1;
return len;
}
static int
phase2id(char *str, size_t size, const char *side, const char *sflow,
int masklen, u_int8_t proto, u_int16_t port)
{
char smasklen[10], sproto[10], sport[10];
smasklen[0] = sproto[0] = sport[0] = 0;
if (masklen != -1)
snprintf(smasklen, sizeof smasklen, "/%d", masklen);
if (proto)
snprintf(sproto, sizeof sproto, "=%u", proto);
if (port)
snprintf(sport, sizeof sport, ":%u", ntohs(port));
return snprintf(str, size, "%s-%s%s%s%s", side, sflow, smasklen,
sproto, sport);
}
/* Handle a PF_KEY SA ACQUIRE message PMSG. */
static void
pf_key_v2_acquire(struct pf_key_v2_msg *pmsg)
{
struct sadb_msg *msg, askpolicy_msg;
struct pf_key_v2_msg *askpolicy = 0, *ret = 0;
struct sadb_x_policy policy;
struct sadb_address *dst = 0, *src = 0;
struct sockaddr *dstaddr, *srcaddr = 0;
struct sadb_ident *srcident = 0, *dstident = 0;
char dstbuf[ADDRESS_MAX], srcbuf[ADDRESS_MAX], *peer = 0;
char confname[120], *conn = 0;
char *srcid = 0, *dstid = 0, *prefstring = 0;
int slen, af, afamily, masklen;
struct sockaddr *smask, *sflow, *dmask, *dflow;
struct sadb_protocol *sproto;
char ssflow[ADDRESS_MAX], sdflow[ADDRESS_MAX];
char sdmask[ADDRESS_MAX], ssmask[ADDRESS_MAX];
int dmasklen, smasklen;
char *sidtype = 0, *didtype = 0;
char lname[100], dname[100], configname[200];
int shostflag = 0, dhostflag = 0;
struct pf_key_v2_node *ext;
struct passwd *pwd = 0;
u_int16_t sport = 0, dport = 0;
u_int8_t tproto = 0;
char tmbuf[sizeof sport * 3 + 1], *xform;
int connlen;
/* This needs to be dynamically allocated. */
connlen = 22;
conn = malloc(connlen);
if (!conn) {
log_error("pf_key_v2_acquire: malloc (%d) failed", connlen);
return;
}
msg = (struct sadb_msg *)TAILQ_FIRST(pmsg)->seg;
ext = pf_key_v2_find_ext(pmsg, SADB_EXT_ADDRESS_DST);
if (!ext) {
log_print("pf_key_v2_acquire: "
"no destination address specified");
free(conn);
return;
}
dst = ext->seg;
ext = pf_key_v2_find_ext(pmsg, SADB_EXT_ADDRESS_SRC);
if (ext)
src = ext->seg;
ext = pf_key_v2_find_ext(pmsg, SADB_EXT_IDENTITY_SRC);
if (ext)
srcident = ext->seg;
ext = pf_key_v2_find_ext(pmsg, SADB_EXT_IDENTITY_DST);
if (ext)
dstident = ext->seg;
/* Ask the kernel for the matching policy. */
bzero(&askpolicy_msg, sizeof askpolicy_msg);
askpolicy_msg.sadb_msg_type = SADB_X_ASKPOLICY;
askpolicy = pf_key_v2_msg_new(&askpolicy_msg, 0);
if (!askpolicy)
goto fail;
policy.sadb_x_policy_exttype = SADB_X_EXT_POLICY;
policy.sadb_x_policy_len = sizeof policy / PF_KEY_V2_CHUNK;
policy.sadb_x_policy_seq = msg->sadb_msg_seq;
if (pf_key_v2_msg_add(askpolicy, (struct sadb_ext *)&policy, 0) == -1)
goto fail;
ret = pf_key_v2_call(askpolicy);
if (!ret)
goto fail;
/* Now we have all the information needed. */
ext = pf_key_v2_find_ext(ret, SADB_X_EXT_SRC_FLOW);
if (!ext) {
log_print("pf_key_v2_acquire: no source flow extension found");
goto fail;
}
sflow = (struct sockaddr *) (((struct sadb_address *) ext->seg) + 1);
ext = pf_key_v2_find_ext(ret, SADB_X_EXT_DST_FLOW);
if (!ext) {
log_print("pf_key_v2_acquire: "
"no destination flow extension found");
goto fail;
}
dflow = (struct sockaddr *) (((struct sadb_address *) ext->seg) + 1);
ext = pf_key_v2_find_ext(ret, SADB_X_EXT_SRC_MASK);
if (!ext) {
log_print("pf_key_v2_acquire: no source mask extension found");
goto fail;
}
smask = (struct sockaddr *) (((struct sadb_address *) ext->seg) + 1);
ext = pf_key_v2_find_ext(ret, SADB_X_EXT_DST_MASK);
if (!ext) {
log_print("pf_key_v2_acquire: "
"no destination mask extension found");
goto fail;
}
dmask = (struct sockaddr *) (((struct sadb_address *) ext->seg) + 1);
ext = pf_key_v2_find_ext(ret, SADB_X_EXT_FLOW_TYPE);
if (!ext) {
log_print("pf_key_v2_acquire: no flow type extension found");
goto fail;
}
sproto = ext->seg;
tproto = sproto->sadb_protocol_proto;
bzero(ssflow, sizeof ssflow);
bzero(sdflow, sizeof sdflow);
bzero(ssmask, sizeof ssmask);
bzero(sdmask, sizeof sdmask);
smasklen = dmasklen = -1;
sidtype = didtype = "IPV4_ADDR_SUBNET"; /* default */
switch (sflow->sa_family) {
case AF_INET:
if (inet_ntop(AF_INET,
&((struct sockaddr_in *) sflow)->sin_addr, ssflow,
ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
sport = ((struct sockaddr_in *) sflow)->sin_port;
if (inet_ntop(AF_INET,
&((struct sockaddr_in *) dflow)->sin_addr, sdflow,
ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
dport = ((struct sockaddr_in *) dflow)->sin_port;
if (inet_ntop(AF_INET,
&((struct sockaddr_in *) smask)->sin_addr, ssmask,
ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
if (inet_ntop(AF_INET,
&((struct sockaddr_in *) dmask)->sin_addr, sdmask,
ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
smasklen = mask4len((struct sockaddr_in *) smask);
dmasklen = mask4len((struct sockaddr_in *) dmask);
if (((struct sockaddr_in *) smask)->sin_addr.s_addr ==
INADDR_BROADCAST) {
shostflag = 1;
sidtype = "IPV4_ADDR";
}
if (((struct sockaddr_in *) dmask)->sin_addr.s_addr ==
INADDR_BROADCAST) {
dhostflag = 1;
didtype = "IPV4_ADDR";
}
break;
case AF_INET6:
if (inet_ntop(AF_INET6,
&((struct sockaddr_in6 *) sflow)->sin6_addr,
ssflow, ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
sport = ((struct sockaddr_in6 *) sflow)->sin6_port;
if (inet_ntop(AF_INET6,
&((struct sockaddr_in6 *) dflow)->sin6_addr,
sdflow, ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
dport = ((struct sockaddr_in6 *) dflow)->sin6_port;
if (inet_ntop(AF_INET6,
&((struct sockaddr_in6 *) smask)->sin6_addr,
ssmask, ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
if (inet_ntop(AF_INET6,
&((struct sockaddr_in6 *) dmask)->sin6_addr,
sdmask, ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
smasklen = mask6len((struct sockaddr_in6 *) smask);
dmasklen = mask6len((struct sockaddr_in6 *) dmask);
sidtype = didtype = "IPV6_ADDR_SUBNET";
if (IN6_IS_ADDR_FULL(&((struct sockaddr_in6 *)smask)->sin6_addr)) {
shostflag = 1;
sidtype = "IPV6_ADDR";
}
if (IN6_IS_ADDR_FULL(&((struct sockaddr_in6 *)dmask)->sin6_addr)) {
dhostflag = 1;
didtype = "IPV6_ADDR";
}
break;
}
dstaddr = (struct sockaddr *)(dst + 1);
bzero(dstbuf, sizeof dstbuf);
bzero(srcbuf, sizeof srcbuf);
if (dstaddr->sa_family == 0) {
/*
* Destination was not specified in the flow -- can we derive
* it?
*/
if (dhostflag == 0) {
log_print("pf_key_v2_acquire: "
"Cannot determine precise destination");
goto fail;
}
dstaddr = dflow;
}
switch (dstaddr->sa_family) {
case AF_INET:
if (inet_ntop(AF_INET,
&((struct sockaddr_in *) dstaddr)->sin_addr,
dstbuf, ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
LOG_DBG((LOG_SYSDEP, 20,
"pf_key_v2_acquire: dst=%s sproto %d", dstbuf,
msg->sadb_msg_satype));
break;
case AF_INET6:
if (inet_ntop(AF_INET6,
&((struct sockaddr_in6 *) dstaddr)->sin6_addr,
dstbuf, ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: inet_ntop failed");
goto fail;
}
LOG_DBG((LOG_SYSDEP, 20,
"pf_key_v2_acquire: dst=%s sproto %d", dstbuf,
msg->sadb_msg_satype));
break;
}
if (src) {
srcaddr = (struct sockaddr *) (src + 1);
switch (srcaddr->sa_family) {
case AF_INET:
if (inet_ntop(AF_INET,
&((struct sockaddr_in *) srcaddr)->sin_addr,
srcbuf, ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: "
"inet_ntop failed");
goto fail;
}
break;
case AF_INET6:
if (inet_ntop(AF_INET6,
&((struct sockaddr_in6 *)srcaddr)->sin6_addr,
srcbuf, ADDRESS_MAX) == NULL) {
log_print("pf_key_v2_acquire: "
"inet_ntop failed");
goto fail;
}
break;
default:
/*
* The kernel will pass an all '0' EXT_ADDRESS_SRC if
* it wasn't specified for the flow. In that case, do
* NOT specify the srcaddr in the Peer-name below
*/
srcbuf[0] = 0;
srcaddr = NULL;
break;
}
}
/* Insert source ID. */
if (srcident) {
slen = (srcident->sadb_ident_len * sizeof(u_int64_t))
- sizeof(struct sadb_ident);
if (((unsigned char *) (srcident + 1))[slen - 1] != '\0') {
log_print("pf_key_v2_acquire: "
"source identity not NUL-terminated");
goto fail;
}
/* Check for valid type. */
switch (srcident->sadb_ident_type) {
case SADB_IDENTTYPE_PREFIX:
/* Determine what the address family is. */
srcid = memchr(srcident + 1, ':', slen);
if (srcid)
afamily = AF_INET6;
else
afamily = AF_INET;
srcid = memchr(srcident + 1, '/', slen);
if (!srcid) {
log_print("pf_key_v2_acquire: "
"badly formatted PREFIX identity");
goto fail;
}
masklen = atoi(srcid + 1);
/* XXX We only support host addresses. */
if ((afamily == AF_INET6 && masklen != 128) ||
(afamily == AF_INET && masklen != 32)) {
log_print("pf_key_v2_acquire: "
"non-host address specified in source "
"identity (mask length %d), ignoring "
"request", masklen);
goto fail;
}
/*
* NUL-terminate the PREFIX string at the separator,
* then dup.
*/
*srcid = '\0';
if (asprintf(&srcid, "id-%s",
(char *) (srcident + 1)) == -1) {
log_error("pf_key_v2_acquire: asprintf() failed");
goto fail;
}
/* Set the section if it doesn't already exist. */
af = conf_begin();
if (!conf_get_str(srcid, "ID-type")) {
if (conf_set(af, srcid, "ID-type",
afamily == AF_INET ? "IPV4_ADDR" :
"IPV6_ADDR", 1, 0) ||
conf_set(af, srcid, "Refcount", "1", 1, 0) ||
conf_set(af, srcid, "Address",
(char *) (srcident + 1), 1, 0)) {
conf_end(af, 0);
goto fail;
}
} else
pf_key_v2_conf_refinc(af, srcid);
conf_end(af, 1);
break;
case SADB_IDENTTYPE_FQDN:
prefstring = "FQDN";
/*FALLTHROUGH*/
case SADB_IDENTTYPE_USERFQDN:
if (!prefstring) {
prefstring = "USER_FQDN";
/*
* Check whether there is a string following
* the header; if no, that there is a user ID
* (and acquire the login name). If there is
* both a string and a user ID, check that
* they match.
*/
if ((slen == 0) &&
(srcident->sadb_ident_id == 0)) {
log_print("pf_key_v2_acquire: "
"no user FQDN or ID provided");
goto fail;
}
if (srcident->sadb_ident_id) {
pwd =
getpwuid(srcident->sadb_ident_id);
if (!pwd) {
log_error("pf_key_v2_acquire: "
"could not acquire "
"username from provided "
"ID %llu",
srcident->sadb_ident_id);
goto fail;
}
if (slen != 0)
if (strcmp(pwd->pw_name,
(char *) (srcident + 1))
!= 0) {
log_print("pf_key_v2_acquire: "
"provided user "
"name and ID do "
"not match (%s != "
"%s)",
(char *) (srcident + 1),
pwd->pw_name);
/*
* String has
* precedence, per
* RFC 2367.
*/
}
}
}
if (asprintf(&srcid, "id-%s",
slen ? (char *) (srcident + 1) : pwd->pw_name) == -1) {
log_error("pf_key_v2_acquire: asprintf() failed");
goto fail;
}
pwd = 0;
/* Set the section if it doesn't already exist. */
af = conf_begin();
if (!conf_get_str(srcid, "ID-type")) {
if (conf_set(af, srcid, "ID-type", prefstring,
1, 0) ||
conf_set(af, srcid, "Refcount", "1", 1, 0) ||
conf_set(af, srcid, "Name",
srcid + 3, 1, 0)) {
conf_end(af, 0);
goto fail;
}
} else
pf_key_v2_conf_refinc(af, srcid);
conf_end(af, 1);
break;
default:
LOG_DBG((LOG_SYSDEP, 20,
"pf_key_v2_acquire: invalid source ID type %d",
srcident->sadb_ident_type));
goto fail;
}
LOG_DBG((LOG_SYSDEP, 50,
"pf_key_v2_acquire: constructed source ID \"%s\"", srcid));
prefstring = 0;
}
/* Insert destination ID. */
if (dstident) {
slen = (dstident->sadb_ident_len * sizeof(u_int64_t))
- sizeof(struct sadb_ident);
/* Check for valid type. */
switch (dstident->sadb_ident_type) {
case SADB_IDENTTYPE_PREFIX:
/* Determine what the address family is. */
dstid = memchr(dstident + 1, ':', slen);
if (dstid)
afamily = AF_INET6;
else
afamily = AF_INET;
dstid = memchr(dstident + 1, '/', slen);
if (!dstid) {
log_print("pf_key_v2_acquire: "
"badly formatted PREFIX identity");
goto fail;
}
masklen = atoi(dstid + 1);
/* XXX We only support host addresses. */
if ((afamily == AF_INET6 && masklen != 128) ||
(afamily == AF_INET && masklen != 32)) {
log_print("pf_key_v2_acquire: "
"non-host address specified in "
"destination identity (mask length %d), "
"ignoring request", masklen);
goto fail;
}
/*
* NUL-terminate the PREFIX string at the separator,
* then dup.
*/
*dstid = '\0';
if (asprintf(&dstid, "id-%s",
(char *) (dstident + 1)) == -1) {
log_error("pf_key_v2_acquire: asprintf() failed");
goto fail;
}
/* Set the section if it doesn't already exist. */
af = conf_begin();
if (!conf_get_str(dstid, "ID-type")) {
if (conf_set(af, dstid, "ID-type",
afamily == AF_INET ? "IPV4_ADDR" :
"IPV6_ADDR", 1, 0) ||
conf_set(af, dstid, "Refcount", "1", 1, 0) ||
conf_set(af, dstid, "Address",
(char *) (dstident + 1), 1, 0)) {
conf_end(af, 0);
goto fail;
}
} else
pf_key_v2_conf_refinc(af, dstid);
conf_end(af, 1);
break;
case SADB_IDENTTYPE_FQDN:
prefstring = "FQDN";
/*FALLTHROUGH*/
case SADB_IDENTTYPE_USERFQDN:
if (!prefstring) {
prefstring = "USER_FQDN";
/*
* Check whether there is a string following
* the header; if no, that there is a user ID
* (and acquire the login name). If there is
* both a string and a user ID, check that
* they match.
*/
if (slen == 0 &&
dstident->sadb_ident_id == 0) {
log_print("pf_key_v2_acquire: "
"no user FQDN or ID provided");
goto fail;
}
if (dstident->sadb_ident_id) {
pwd = getpwuid(dstident->sadb_ident_id);
if (!pwd) {
log_error("pf_key_v2_acquire: "
"could not acquire "
"username from provided "
"ID %llu",
dstident->sadb_ident_id);
goto fail;
}
if (slen != 0)
if (strcmp(pwd->pw_name,
(char *) (dstident + 1))
!= 0) {
log_print("pf_key_v2_acquire: "
"provided user "
"name and ID do "
"not match (%s != "
"%s)",
(char *) (dstident + 1),
pwd->pw_name);
/*
* String has
* precedence, per RF
* 2367.
*/
}
}
}
if (asprintf(&dstid, "id-%s",
slen ? (char *) (dstident + 1) : pwd->pw_name) == -1) {
log_error("pf_key_v2_acquire: asprintf() failed");
goto fail;
}
pwd = 0;
/* Set the section if it doesn't already exist. */
af = conf_begin();
if (!conf_get_str(dstid, "ID-type")) {
if (conf_set(af, dstid, "ID-type", prefstring,
1, 0) ||
conf_set(af, dstid, "Refcount", "1", 1, 0) ||
conf_set(af, dstid, "Name",
dstid + 3, 1, 0)) {
conf_end(af, 0);
goto fail;
}
} else
pf_key_v2_conf_refinc(af, dstid);
conf_end(af, 1);
break;
default:
LOG_DBG((LOG_SYSDEP, 20, "pf_key_v2_acquire: "
"invalid destination ID type %d",
dstident->sadb_ident_type));
goto fail;
}
LOG_DBG((LOG_SYSDEP, 50,
"pf_key_v2_acquire: constructed destination ID \"%s\"",
dstid));
}
/* Now we've placed the necessary IDs in the configuration space. */
/* Get a new connection sequence number. */
for (;; connection_seq++) {
snprintf(conn, connlen, "Connection-%u", connection_seq);
/* Does it exist ? */
if (!conf_get_str(conn, "Phase"))
break;
}
/*
* Set the IPsec connection entry. In particular, the following fields:
* - Phase
* - ISAKMP-peer
* - Local-ID/Remote-ID (if provided)
* - Acquire-ID (sequence number of kernel message, e.g., PF_KEYv2)
* - Configuration
*
* Also set the following section:
* [peer-dstaddr(-local-srcaddr)]
* with these fields:
* - Phase
* - ID (if provided)
* - Remote-ID (if provided)
* - Local-address (if provided)
* - Address
* - Configuration (if an entry phase1-dstaddr-srcadd)
* exists -- otherwise use the defaults)
*/
/*
* The various cases:
* - peer-dstaddr
* - peer-dstaddr-local-srcaddr
*/
if (asprintf(&peer, "peer-%s%s%s", dstbuf, srcaddr ? "-local-" : "",
srcaddr ? srcbuf : "") == -1)
goto fail;
/*
* Set the IPsec connection section. Refcount is set to 2, because
* it will be linked both to the incoming and the outgoing SA.
*/
af = conf_begin();
if (conf_set(af, conn, "Phase", "2", 0, 0) ||
conf_set(af, conn, "Flags", "__ondemand", 0, 0) ||
conf_set(af, conn, "Refcount", "2", 0, 0) ||
conf_set(af, conn, "ISAKMP-peer", peer, 0, 0)) {
conf_end(af, 0);
goto fail;
}
/* Set the sequence number. */
snprintf(lname, sizeof lname, "%u", msg->sadb_msg_seq);
if (conf_set(af, conn, "Acquire-ID", lname, 0, 0)) {
conf_end(af, 0);
goto fail;
}
/*
* Set Phase 2 IDs -- this is the Local-ID section.
* - from-address
* - from-address=proto
* - from-address=proto:port
* - from-network/masklen
* - from-network/masklen=proto
* - from-network/masklen=proto:port
*/
phase2id(lname, sizeof lname, "from", ssflow, smasklen, tproto, sport);
if (conf_set(af, conn, "Local-ID", lname, 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (!conf_get_str(lname, "ID-type")) {
if (conf_set(af, lname, "Refcount", "1", 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (shostflag) {
if (conf_set(af, lname, "ID-type", sidtype, 0, 0) ||
conf_set(af, lname, "Address", ssflow, 0, 0)) {
conf_end(af, 0);
goto fail;
}
} else {
if (conf_set(af, lname, "ID-type", sidtype, 0, 0) ||
conf_set(af, lname, "Network", ssflow, 0, 0) ||
conf_set(af, lname, "Netmask", ssmask, 0, 0)) {
conf_end(af, 0);
goto fail;
}
}
if (tproto) {
snprintf(tmbuf, sizeof sport * 3 + 1, "%u", tproto);
if (conf_set(af, lname, "Protocol", tmbuf, 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (sport) {
snprintf(tmbuf, sizeof sport * 3 + 1, "%u",
ntohs(sport));
if (conf_set(af, lname, "Port", tmbuf, 0, 0)) {
conf_end(af, 0);
goto fail;
}
}
}
} else
pf_key_v2_conf_refinc(af, lname);
/*
* Set Remote-ID section.
* to-address
* to-address=proto
* to-address=proto:port
* to-network/masklen
* to-network/masklen=proto
* to-network/masklen=proto:port
*/
phase2id(dname, sizeof dname, "to", sdflow, dmasklen, tproto, dport);
if (conf_set(af, conn, "Remote-ID", dname, 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (!conf_get_str(dname, "ID-type")) {
if (conf_set(af, dname, "Refcount", "1", 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (dhostflag) {
if (conf_set(af, dname, "ID-type", didtype, 0, 0) ||
conf_set(af, dname, "Address", sdflow, 0, 0)) {
conf_end(af, 0);
goto fail;
}
} else {
if (conf_set(af, dname, "ID-type", didtype, 0, 0) ||
conf_set(af, dname, "Network", sdflow, 0, 0) ||
conf_set(af, dname, "Netmask", sdmask, 0, 0)) {
conf_end(af, 0);
goto fail;
}
}
if (tproto) {
snprintf(tmbuf, sizeof dport * 3 + 1, "%u", tproto);
if (conf_set(af, dname, "Protocol", tmbuf, 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (dport) {
snprintf(tmbuf, sizeof dport * 3 + 1, "%u",
ntohs(dport));
if (conf_set(af, dname, "Port", tmbuf, 0, 0)) {
conf_end(af, 0);
goto fail;
}
}
}
} else
pf_key_v2_conf_refinc(af, dname);
/*
* XXX
* We should be using information from the proposal to set this up.
* At least, we should make this selectable.
*/
/*
* Phase 2 configuration.
* - phase2-from-address-to-address
* - ...
* - phase2-from-net/len=proto:port-to-net/len=proto:port
*/
snprintf(configname, sizeof configname, "phase2-%s-%s", lname, dname);
if (conf_set(af, conn, "Configuration", configname, 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (!conf_get_str(configname, "Exchange_type")) {
if (conf_set(af, configname, "Exchange_type", "Quick_mode",
0, 0) ||
conf_set(af, peer, "Refcount", "1", 0, 0) ||
conf_set(af, configname, "DOI", "IPSEC", 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (conf_get_str("General", "Default-phase-2-suites")) {
if (conf_set(af, configname, "Suites",
conf_get_str("General", "Default-phase-2-suites"),
0, 0)) {
conf_end(af, 0);
goto fail;
}
} else {
if (conf_set(af, configname, "Suites",
"QM-ESP-3DES-SHA-PFS-SUITE", 0, 0)) {
conf_end(af, 0);
goto fail;
}
}
} else
pf_key_v2_conf_refinc(af, configname);
/* Set the ISAKMP-peer section. */
if (!conf_get_str(peer, "Phase")) {
if (conf_set(af, peer, "Phase", "1", 0, 0) ||
conf_set(af, peer, "Refcount", "1", 0, 0) ||
conf_set(af, peer, "Address", dstbuf, 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (srcaddr && conf_set(af, peer, "Local-address", srcbuf, 0,
0)) {
conf_end(af, 0);
goto fail;
}
snprintf(confname, sizeof confname, "phase1-%s", peer);
if (conf_set(af, peer, "Configuration", confname, 0, 0)) {
conf_end(af, 0);
goto fail;
}
/* Phase 1 configuration. */
if (!conf_get_str(confname, "exchange_type")) {
xform = conf_get_str("Default-phase-1-configuration",
"Transforms");
if (conf_set(af, confname, "Transforms", xform ? xform :
"3DES-SHA-RSA_SIG", 0, 0)) {
conf_end(af, 0);
goto fail;
}
if (conf_set(af, confname, "Exchange_Type", "ID_PROT",
0, 0) ||
conf_set(af, confname, "DOI", "IPSEC", 0, 0) ||
conf_set(af, confname, "Refcount", "1", 0, 0)) {
conf_end(af, 0);
goto fail;
}
} else
pf_key_v2_conf_refinc(af, confname);
/* The ID we should use in Phase 1. */
if (srcid && conf_set(af, peer, "ID", srcid, 0, 0)) {
conf_end(af, 0);
goto fail;
}
/* The ID the other side should use in Phase 1. */
if (dstid && conf_set(af, peer, "Remote-ID", dstid, 0, 0)) {
conf_end(af, 0);
goto fail;
}
} else
pf_key_v2_conf_refinc(af, peer);
/* All done. */
conf_end(af, 1);
/* Let's rock 'n roll. */
connection_record_passive(conn);
pf_key_v2_connection_check(conn);
conn = 0;
/* Fall-through to cleanup. */
fail:
if (ret)
pf_key_v2_msg_free(ret);
if (askpolicy)
pf_key_v2_msg_free(askpolicy);
free(srcid);
free(dstid);
free(peer);
free(conn);
return;
}
static void
pf_key_v2_notify(struct pf_key_v2_msg *msg)
{
switch (((struct sadb_msg *)TAILQ_FIRST(msg)->seg)->sadb_msg_type) {
case SADB_EXPIRE:
pf_key_v2_expire(msg);
break;
case SADB_ACQUIRE:
if (!ui_daemon_passive)
pf_key_v2_acquire(msg);
break;
default:
log_print("pf_key_v2_notify: unexpected message type (%d)",
((struct sadb_msg *)TAILQ_FIRST(msg)->seg)->sadb_msg_type);
}
pf_key_v2_msg_free(msg);
}
void
pf_key_v2_handler(int fd)
{
struct pf_key_v2_msg *msg;
int n;
/*
* As synchronous read/writes to the socket can have taken place
* between the select(2) call of the main loop and this handler, we
* need to recheck the readability.
*/
if (ioctl(pf_key_v2_socket, FIONREAD, &n) == -1) {
log_error("pf_key_v2_handler: ioctl (%d, FIONREAD, &n) failed",
pf_key_v2_socket);
return;
}
if (!n)
return;
msg = pf_key_v2_read(0);
if (msg)
pf_key_v2_notify(msg);
}
/*
* Group 2 IPsec SAs given by the PROTO1 and PROTO2 protocols of the SA IKE
* security association in a chain.
* XXX Assumes OpenBSD GRPSPIS extension.
*/
int
pf_key_v2_group_spis(struct sa *sa, struct proto *proto1,
struct proto *proto2, int incoming)
{
struct sadb_msg msg;
struct sadb_sa sa1, sa2;
struct sadb_address *addr = 0;
struct sadb_protocol protocol;
struct pf_key_v2_msg *grpspis = 0, *ret = 0;
struct sockaddr *saddr;
int err;
size_t len;
msg.sadb_msg_type = SADB_X_GRPSPIS;
switch (proto1->proto) {
case IPSEC_PROTO_IPSEC_ESP:
msg.sadb_msg_satype = SADB_SATYPE_ESP;
break;
case IPSEC_PROTO_IPSEC_AH:
msg.sadb_msg_satype = SADB_SATYPE_AH;
break;
case IPSEC_PROTO_IPCOMP:
msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
break;
default:
log_print("pf_key_v2_group_spis: invalid proto %d",
proto1->proto);
goto cleanup;
}
msg.sadb_msg_seq = 0;
grpspis = pf_key_v2_msg_new(&msg, 0);
if (!grpspis)
goto cleanup;
/* Setup the SA extensions. */
sa1.sadb_sa_exttype = SADB_EXT_SA;
sa1.sadb_sa_len = sizeof sa1 / PF_KEY_V2_CHUNK;
memcpy(&sa1.sadb_sa_spi, proto1->spi[incoming],
sizeof sa1.sadb_sa_spi);
sa1.sadb_sa_replay = 0;
sa1.sadb_sa_state = 0;
sa1.sadb_sa_auth = 0;
sa1.sadb_sa_encrypt = 0;
sa1.sadb_sa_flags = 0;
if (pf_key_v2_msg_add(grpspis, (struct sadb_ext *)&sa1, 0) == -1)
goto cleanup;
sa2.sadb_sa_exttype = SADB_X_EXT_SA2;
sa2.sadb_sa_len = sizeof sa2 / PF_KEY_V2_CHUNK;
memcpy(&sa2.sadb_sa_spi, proto2->spi[incoming],
sizeof sa2.sadb_sa_spi);
sa2.sadb_sa_replay = 0;
sa2.sadb_sa_state = 0;
sa2.sadb_sa_auth = 0;
sa2.sadb_sa_encrypt = 0;
sa2.sadb_sa_flags = 0;
if (pf_key_v2_msg_add(grpspis, (struct sadb_ext *)&sa2, 0) == -1)
goto cleanup;
/*
* Setup the ADDRESS extensions.
*/
if (incoming)
sa->transport->vtbl->get_src(sa->transport, &saddr);
else
sa->transport->vtbl->get_dst(sa->transport, &saddr);
len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(saddr));
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, saddr, SA_LEN(saddr));
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
if (pf_key_v2_msg_add(grpspis, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
addr = calloc(1, len);
if (!addr)
goto cleanup;
addr->sadb_address_exttype = SADB_X_EXT_DST2;
addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
addr->sadb_address_reserved = 0;
memcpy(addr + 1, saddr, SA_LEN(saddr));
((struct sockaddr_in *) (addr + 1))->sin_port = 0;
if (pf_key_v2_msg_add(grpspis, (struct sadb_ext *) addr,
PF_KEY_V2_NODE_MALLOCED) == -1)
goto cleanup;
addr = 0;
/* Setup the sa type extension. */
protocol.sadb_protocol_exttype = SADB_X_EXT_SATYPE2;
protocol.sadb_protocol_len = sizeof protocol / PF_KEY_V2_CHUNK;
switch (proto2->proto) {
case IPSEC_PROTO_IPSEC_ESP:
protocol.sadb_protocol_proto = SADB_SATYPE_ESP;
break;
case IPSEC_PROTO_IPSEC_AH:
protocol.sadb_protocol_proto = SADB_SATYPE_AH;
break;
case IPSEC_PROTO_IPCOMP:
protocol.sadb_protocol_proto = SADB_X_SATYPE_IPCOMP;
break;
default:
log_print("pf_key_v2_group_spis: invalid proto %d",
proto2->proto);
goto cleanup;
}
protocol.sadb_protocol_reserved2 = 0;
if (pf_key_v2_msg_add(grpspis,
(struct sadb_ext *)&protocol, 0) == -1)
goto cleanup;
ret = pf_key_v2_call(grpspis);
pf_key_v2_msg_free(grpspis);
grpspis = 0;
if (!ret)
goto cleanup;
err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
if (err) {
log_print("pf_key_v2_group_spis: GRPSPIS: %s", strerror(err));
goto cleanup;
}
pf_key_v2_msg_free(ret);
LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_group_spis: done"));
return 0;
cleanup:
free(addr);
if (grpspis)
pf_key_v2_msg_free(grpspis);
if (ret)
pf_key_v2_msg_free(ret);
return -1;
}