/* $OpenBSD: pf_key_v2.c,v 1.177 2006/11/24 13:52:14 reyk 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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]) == 0xffff) && \ (*(u_int32_t *)(void *)(&(a)->s6_addr[4]) == 0xffff) && \ (*(u_int32_t *)(void *)(&(a)->s6_addr[8]) == 0xffff) && \ (*(u_int32_t *)(void *)(&(a)->s6_addr[12]) == 0xffff)) #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 = 0; 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: if (node) 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 timeval tv; fd_set *fds; 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) { fds = calloc(howmany(pf_key_v2_socket + 1, NFDBITS), sizeof(fd_mask)); if (!fds) { log_error("pf_key_v2_read: " "calloc (%lu, %lu) failed", (unsigned long) howmany(pf_key_v2_socket + 1, NFDBITS), (unsigned long) sizeof(fd_mask)); goto cleanup; } FD_SET(pf_key_v2_socket, fds); tv.tv_sec = 0; tv.tv_usec = PF_KEY_REPLY_TIMEOUT; n = select(pf_key_v2_socket + 1, fds, 0, 0, &tv); free(fds); if (n == -1) { log_error("pf_key_v2_read: " "select (%d, fds, 0, 0, &tv) failed", pf_key_v2_socket + 1); 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; } n = hdr.sadb_msg_len * PF_KEY_V2_CHUNK; buf = malloc(n); if (!buf) { log_error("pf_key_v2_read: malloc (%lu) failed", (unsigned long) n); goto cleanup; } 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)) { gettimeofday(&tv, 0); timer_add_event("pf_key_v2_notify", (void (*) (void *)) pf_key_v2_notify, ret, &tv); ret = 0; continue; } return ret; } cleanup: if (buf) 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 = (struct iovec *) malloc(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: if (iov) 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: if (spi) free(spi); if (addr) 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 = (struct sadb_sa *)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_EXT_ADDRESS_PROXY); if (ext) { sa = (struct sockaddr *)ext->seg; memcpy(sa, &ksa.proxy, 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: if (addr) 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_x_cred *cred; struct sadb_protocol flowtype, tprotocol; struct sadb_x_udpencap udpencap; char *addr_str, *s; 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_DES: case IPSEC_ESP_DES_IV32: case IPSEC_ESP_DES_IV64: ssa.sadb_sa_encrypt = SADB_EALG_DESCBC; break; 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_128_CTR: ssa.sadb_sa_encrypt = SADB_X_EALG_AESCTR; 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; 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; case IPSEC_IPCOMP_LZS: ssa.sadb_sa_encrypt = SADB_X_CALG_LZS; 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 0 /* XXX I am not sure about what to do here just yet. */ if (iproto->encap_mode == IPSEC_ENCAP_TUNNEL) { 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_PROXY; 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 0 msg->em_odst = msg->em_dst; msg->em_osrc = msg->em_src; #endif } #endif 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: if (sid) 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: if (sid) free(sid); sid = 0; } /* * Send received credentials to the kernel. We don't bother with * our credentials, since the process either knows them (if it * specified them with setsockopt()), or has no business looking at * them (e.g., system wide certs). */ if (isakmp_sa->recv_cert) { switch (isakmp_sa->recv_certtype) { case ISAKMP_CERTENC_NONE: /* Nothing to be done here. */ break; case ISAKMP_CERTENC_KEYNOTE: len = strlen(isakmp_sa->recv_cert); cred = calloc(PF_KEY_V2_ROUND(len) + sizeof *cred, sizeof(u_int8_t)); if (!cred) goto cleanup; cred->sadb_x_cred_len = ((sizeof *cred) / PF_KEY_V2_CHUNK) + PF_KEY_V2_ROUND(len) / PF_KEY_V2_CHUNK; cred->sadb_x_cred_exttype = SADB_X_EXT_REMOTE_CREDENTIALS; cred->sadb_x_cred_type = SADB_X_CREDTYPE_KEYNOTE; memcpy(cred + 1, isakmp_sa->recv_cert, len); if (pf_key_v2_msg_add(update, (struct sadb_ext *) cred, PF_KEY_V2_NODE_MALLOCED) == -1) goto cleanup; break; case ISAKMP_CERTENC_X509_SIG: { u_int8_t *data; u_int32_t datalen; struct cert_handler *handler; /* We do it this way to avoid weird includes.*/ handler = cert_get(ISAKMP_CERTENC_X509_SIG); if (!handler) break; handler->cert_serialize(isakmp_sa->recv_cert, &data, &datalen); if (!data) break; len = datalen; cred = calloc(PF_KEY_V2_ROUND(len) + sizeof *cred, sizeof(u_int8_t)); if (!cred) { free(data); goto cleanup; } cred->sadb_x_cred_len = ((sizeof *cred) / PF_KEY_V2_CHUNK) + PF_KEY_V2_ROUND(len) / PF_KEY_V2_CHUNK; cred->sadb_x_cred_exttype = SADB_X_EXT_REMOTE_CREDENTIALS; cred->sadb_x_cred_type = SADB_X_CREDTYPE_X509; memcpy(cred + 1, data, len); free(data); if (pf_key_v2_msg_add(update, (struct sadb_ext *) cred, PF_KEY_V2_NODE_MALLOCED) == -1) goto cleanup; } break; } } /* * Tell the kernel what the peer used to authenticate, unless it was a * passphrase. */ if (isakmp_sa->recv_key) { u_int8_t *data; /* * If it's a private key, we shouldn't pass it to the kernel * for processes to see; successful authentication of Phase 1 * implies that the process already knew the passphrase. On * the other hand, we don't want to reveal to processes any * system-wide passphrases used for authentication with remote * systems. Same reason we don't send up the key (private or * passphrase) we used to authenticate with the peer. */ if (isakmp_sa->recv_keytype == ISAKMP_KEY_PASSPHRASE) goto doneauth; key_serialize(isakmp_sa->recv_keytype, ISAKMP_KEYTYPE_PUBLIC, isakmp_sa->recv_key, &data, &len); if (!data) goto cleanup; cred = calloc(PF_KEY_V2_ROUND(len) + sizeof *cred, sizeof(u_int8_t)); if (!cred) { free(data); goto cleanup; } cred->sadb_x_cred_len = ((sizeof *cred) / PF_KEY_V2_CHUNK) + PF_KEY_V2_ROUND(len) / PF_KEY_V2_CHUNK; cred->sadb_x_cred_exttype = SADB_X_EXT_REMOTE_AUTH; memcpy(cred + 1, data, len); free(data); switch (isakmp_sa->recv_keytype) { case ISAKMP_KEY_RSA: cred->sadb_x_cred_type = SADB_X_AUTHTYPE_RSA; break; default: log_print("pf_key_v2_set_spi: " "unknown received key type %d", isakmp_sa->recv_keytype); free(cred); goto cleanup; } if (pf_key_v2_msg_add(update, (struct sadb_ext *) cred, PF_KEY_V2_NODE_MALLOCED) == -1) goto cleanup; } doneauth: /* 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 = (struct sadb_x_tag *)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; } /* 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", msg.sadb_msg_satype, addr_str ? addr_str : "unknown", ntohl(ssa.sadb_sa_spi), sa->tag ? " tag " : "", sa->tag ? sa->tag : "")); if (addr_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: if (sid) free(sid); if (addr) free(addr); if (life) free(life); if (key) 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 = ingress ? SADB_X_FLOW_TYPE_USE : 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))); if (laddr_str) free(laddr_str); if (lmask_str) free(lmask_str); if (raddr_str) free(raddr_str); if (rmask_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: if (sid) free(sid); if (addr) 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: /* XXX CONNECTION ? */ 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: /* XXX CONNECTION ? */ 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; 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: if (sid) free(sid); if (did) 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"); conf_remove_section(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: if (addr) 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); } } /* 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)); } /* 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 : "", ntohl(ssa->sadb_sa_spi), msg->sadb_msg_satype)); if (dst_str) 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); } } /* 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_comb *scmb = 0; struct sadb_prop *sprp = 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]; char *sidtype = 0, *didtype = 0; char lname[100], dname[100], configname[30]; 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; struct sadb_x_cred *cred = 0, *sauth = 0; /* 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_PROPOSAL); if (ext) { sprp = ext->seg; scmb = (struct sadb_comb *) (sprp + 1); } 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; ext = pf_key_v2_find_ext(pmsg, SADB_X_EXT_LOCAL_CREDENTIALS); if (ext) cred = (struct sadb_x_cred *) ext->seg; else cred = 0; ext = pf_key_v2_find_ext(pmsg, SADB_X_EXT_LOCAL_AUTH); if (ext) sauth = (struct sadb_x_cred *) ext->seg; else sauth = 0; bzero(ssflow, sizeof ssflow); bzero(sdflow, sizeof sdflow); bzero(ssmask, sizeof ssmask); bzero(sdmask, sizeof sdmask); 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; } 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; } 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_X_IDENTTYPE_CONNECTION: /* XXX */ break; 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:Address/%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"; /* Fall through */ 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/%s", prefstring, 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 + sizeof "ID:/" - 1 + strlen(prefstring), 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_X_IDENTTYPE_CONNECTION: /* XXX */ break; 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:Address/%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"; /* Fall through */ 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/%s", prefstring, 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 + sizeof "ID:/" - 1 + strlen(prefstring), 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); snprintf(configname, sizeof configname, "Config-Phase2-%u", connection_seq); /* Does it exist ? */ if (!conf_get_str(conn, "Phase") && !conf_get_str(configname, "Suites")) 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(/srcaddr)(-srcid)(/dstid)] * with these fields: * - Phase * - ID (if provided) * - Remote-ID (if provided) * - Local-address (if provided) * - Address * - Configuration (if an entry ISAKMP-configuration-dstaddr(/srcaddr) * exists -- otherwise use the defaults) */ /* * The various cases: * - Peer-dstaddr * - Peer-dstaddr/srcaddr * - Peer-dstaddr/srcaddr-srcid * - Peer-dstaddr/srcaddr-srcid/dstid * - Peer-dstaddr/srcaddr-/dstid * - Peer-dstaddr-srcid/dstid * - Peer-dstaddr-/dstid * - Peer-dstaddr-srcid */ if (asprintf(&peer, "Peer-%s%s%s%s%s%s%s", dstbuf, srcaddr ? "/" : "", srcaddr ? srcbuf : "", srcid ? "-" : "", srcid ? srcid : "", dstid ? (srcid ? "/" : "-/") : "", dstid ? dstid : "") == -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. */ snprintf(lname, sizeof lname, "Phase2-ID:%s/%s/%u/%u", ssflow, ssmask, 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. */ snprintf(dname, sizeof dname, "Phase2-ID:%s/%s/%u/%u", sdflow, sdmask, 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. */ if (conf_set(af, conn, "Configuration", configname, 0, 0)) { conf_end(af, 0); goto fail; } if (conf_set(af, configname, "Exchange_type", "Quick_mode", 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; } } /* 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, "ISAKMP-Configuration-%s", peer); if (conf_set(af, peer, "Configuration", confname, 0, 0)) { conf_end(af, 0); goto fail; } /* Store any credentials passed to us. */ if (cred) { struct cert_handler *handler = 0; void *cert; char num[12], *certprint; /* Convert to bytes in-place. */ cred->sadb_x_cred_len *= PF_KEY_V2_CHUNK; if (cred->sadb_x_cred_len <= sizeof *cred) { log_print("pf_key_v2_acquire: " "zero-length credentials, aborting SA " "acquisition"); conf_end(af, 0); goto fail; } switch (cred->sadb_x_cred_type) { case SADB_X_CREDTYPE_X509: snprintf(num, sizeof num, "%d", ISAKMP_CERTENC_X509_SIG); handler = cert_get(ISAKMP_CERTENC_X509_SIG); break; case SADB_X_CREDTYPE_KEYNOTE: snprintf(num, sizeof num, "%d", ISAKMP_CERTENC_KEYNOTE); handler = cert_get(ISAKMP_CERTENC_KEYNOTE); break; default: log_print("pf_key_v2_acquire: " "unknown credential type %d", cred->sadb_x_cred_type); conf_end(af, 0); goto fail; } if (!handler) { log_print("pf_key_v2_acquire: " "cert_get (%s) failed", num); conf_end(af, 0); goto fail; } /* Set the credential type as a number. */ if (conf_set(af, peer, "Credential_type", num, 0, 0)) { conf_end(af, 0); goto fail; } /* Get the certificate. */ cert = handler->cert_get((u_int8_t *) (cred + 1), cred->sadb_x_cred_len - sizeof *cred); /* Now convert to printable format. */ certprint = handler->cert_printable(cert); handler->cert_free(cert); if (!certprint || conf_set(af, peer, "Credentials", certprint, 0, 0)) { if (certprint) free(certprint); conf_end(af, 0); goto fail; } free(certprint); } /* Phase 1 configuration. */ if (!conf_get_str(confname, "exchange_type")) { /* * We may have been provided with authentication * material. */ if (sauth) { char *authm; /* Convert to bytes in-place. */ sauth->sadb_x_cred_len *= PF_KEY_V2_CHUNK; switch (sauth->sadb_x_cred_type) { case SADB_X_AUTHTYPE_PASSPHRASE: if (conf_set(af, confname, "Transforms", "3DES-SHA", 0, 0)) { conf_end(af, 0); goto fail; } if (sauth->sadb_x_cred_len <= sizeof *sauth) { log_print("pf_key_v2_acquire: " "zero-length passphrase, " "aborting SA acquisition"); conf_end(af, 0); goto fail; } authm = malloc(sauth->sadb_x_cred_len - sizeof *sauth + 1); if (!authm) { log_error("pf_key_v2_acquire: " "malloc (%lu) failed", sauth->sadb_x_cred_len - (unsigned long) sizeof *sauth + 1); conf_end(af, 0); goto fail; } memcpy(authm, sauth + 1, sauth->sadb_x_cred_len - sizeof *sauth + 1); /* Set the passphrase in the peer. */ if (conf_set(af, peer, "Authentication", authm, 0, 0)) { free(authm); conf_end(af, 0); goto fail; } free(authm); break; case SADB_X_AUTHTYPE_RSA: if (conf_set(af, confname, "Transforms", "3DES-SHA-RSA_SIG", 0, 0)) { conf_end(af, 0); goto fail; } if (sauth->sadb_x_cred_len <= sizeof *sauth) { log_print("pf_key_v2_acquire: " "zero-length RSA key, " "aborting SA acquisition"); conf_end(af, 0); goto fail; } authm = key_printable(ISAKMP_KEY_RSA, ISAKMP_KEYTYPE_PRIVATE, (u_int8_t *)(sauth + 1), sauth->sadb_x_cred_len - sizeof *sauth); if (!authm) { log_print("pf_key_v2_acquire: " "failed to convert " "private key to printable " "format (size %lu)", sauth->sadb_x_cred_len - (unsigned long) sizeof *sauth); conf_end(af, 0); goto fail; } /* * Set the key in the peer. We don't * use "Authentication" to avoid * potential conflicts with file-based * configurations that use public key * authentication but still specify * an "Authentication" tag (typically * as a remnant of passphrase-based * testing). */ if (conf_set(af, peer, "PKAuthentication", authm, 0, 0)) { free(authm); conf_end(af, 0); goto fail; } free(authm); break; default: log_print("pf_key_v2_acquire: " "unknown authentication " "material type %d received from " "kernel", sauth->sadb_x_cred_type); conf_end(af, 0); goto fail; } } else /* Fall through */ { 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. */ pf_key_v2_connection_check(conn); connection_record_passive(conn); conn = 0; /* Fall-through to cleanup. */ fail: if (ret) pf_key_v2_msg_free(ret); if (askpolicy) pf_key_v2_msg_free(askpolicy); if (srcid) free(srcid); if (dstid) free(dstid); if (peer) free(peer); if (conn) 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 PROTOCOL extension. */ protocol.sadb_protocol_exttype = SADB_X_EXT_PROTOCOL; 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: if (addr) free(addr); if (grpspis) pf_key_v2_msg_free(grpspis); if (ret) pf_key_v2_msg_free(ret); return -1; }