diff options
Diffstat (limited to 'ldpd/pfkey.c')
-rw-r--r-- | ldpd/pfkey.c | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/ldpd/pfkey.c b/ldpd/pfkey.c new file mode 100644 index 0000000..ae771ca --- /dev/null +++ b/ldpd/pfkey.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2003, 2004 Markus Friedl <markus@openbsd.org> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __OpenBSD__ +#include <sys/types.h> +#include <sys/socket.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" + +static int pfkey_send(int, uint8_t, uint8_t, uint8_t, + int, union ldpd_addr *, union ldpd_addr *, + uint32_t, uint8_t, int, char *, uint8_t, int, char *, + uint16_t, uint16_t); +static int pfkey_reply(int, uint32_t *); +static int pfkey_sa_add(int, union ldpd_addr *, union ldpd_addr *, + uint8_t, char *, uint32_t *); +static int pfkey_sa_remove(int, union ldpd_addr *, union ldpd_addr *, + uint32_t *); +static int pfkey_md5sig_establish(struct nbr *, struct nbr_params *nbrp); +static int pfkey_md5sig_remove(struct nbr *); + +#define PFKEY2_CHUNK sizeof(uint64_t) +#define ROUNDUP(x) (((x) + (PFKEY2_CHUNK - 1)) & ~(PFKEY2_CHUNK - 1)) +#define IOV_CNT 20 + +static uint32_t sadb_msg_seq; +static uint32_t pid; /* should pid_t but pfkey needs uint32_t */ +static int fd; + +static int +pfkey_send(int sd, uint8_t satype, uint8_t mtype, uint8_t dir, + int af, union ldpd_addr *src, union ldpd_addr *dst, uint32_t spi, + uint8_t aalg, int alen, char *akey, uint8_t ealg, int elen, char *ekey, + uint16_t sport, uint16_t dport) +{ + struct sadb_msg smsg; + struct sadb_sa sa; + struct sadb_address sa_src, sa_dst; + struct sadb_key sa_akey, sa_ekey; + struct sadb_spirange sa_spirange; + struct iovec iov[IOV_CNT]; + ssize_t n; + int len = 0; + int iov_cnt; + struct sockaddr_storage smask, dmask; + union sockunion su_src, su_dst; + + if (!pid) + pid = getpid(); + + /* we need clean sockaddr... no ports set */ + memset(&smask, 0, sizeof(smask)); + + addr2sa(af, src, 0, &su_src); + + switch (af) { + case AF_INET: + memset(&((struct sockaddr_in *)&smask)->sin_addr, 0xff, 32/8); + break; + case AF_INET6: + memset(&((struct sockaddr_in6 *)&smask)->sin6_addr, 0xff, + 128/8); + break; + default: + return (-1); + } + smask.ss_family = su_src.sa.sa_family; + smask.ss_len = sockaddr_len(&su_src.sa); + + memset(&dmask, 0, sizeof(dmask)); + + addr2sa(af, dst, 0, &su_dst); + + switch (af) { + case AF_INET: + memset(&((struct sockaddr_in *)&dmask)->sin_addr, 0xff, 32/8); + break; + case AF_INET6: + memset(&((struct sockaddr_in6 *)&dmask)->sin6_addr, 0xff, + 128/8); + break; + default: + return (-1); + } + dmask.ss_family = su_dst.sa.sa_family; + dmask.ss_len = sockaddr_len(&su_dst.sa); + + memset(&smsg, 0, sizeof(smsg)); + smsg.sadb_msg_version = PF_KEY_V2; + smsg.sadb_msg_seq = ++sadb_msg_seq; + smsg.sadb_msg_pid = pid; + smsg.sadb_msg_len = sizeof(smsg) / 8; + smsg.sadb_msg_type = mtype; + smsg.sadb_msg_satype = satype; + + switch (mtype) { + case SADB_GETSPI: + memset(&sa_spirange, 0, sizeof(sa_spirange)); + sa_spirange.sadb_spirange_exttype = SADB_EXT_SPIRANGE; + sa_spirange.sadb_spirange_len = sizeof(sa_spirange) / 8; + sa_spirange.sadb_spirange_min = 0x100; + sa_spirange.sadb_spirange_max = 0xffffffff; + sa_spirange.sadb_spirange_reserved = 0; + break; + case SADB_ADD: + case SADB_UPDATE: + case SADB_DELETE: + memset(&sa, 0, sizeof(sa)); + sa.sadb_sa_exttype = SADB_EXT_SA; + sa.sadb_sa_len = sizeof(sa) / 8; + sa.sadb_sa_replay = 0; + sa.sadb_sa_spi = htonl(spi); + sa.sadb_sa_state = SADB_SASTATE_MATURE; + break; + } + + memset(&sa_src, 0, sizeof(sa_src)); + sa_src.sadb_address_exttype = SADB_EXT_ADDRESS_SRC; + sa_src.sadb_address_len = + (sizeof(sa_src) + ROUNDUP(sockaddr_len(&su_src.sa))) / 8; + + memset(&sa_dst, 0, sizeof(sa_dst)); + sa_dst.sadb_address_exttype = SADB_EXT_ADDRESS_DST; + sa_dst.sadb_address_len = + (sizeof(sa_dst) + ROUNDUP(sockaddr_len(&su_dst.sa))) / 8; + + sa.sadb_sa_auth = aalg; + sa.sadb_sa_encrypt = SADB_X_EALG_AES; /* XXX */ + + switch (mtype) { + case SADB_ADD: + case SADB_UPDATE: + memset(&sa_akey, 0, sizeof(sa_akey)); + sa_akey.sadb_key_exttype = SADB_EXT_KEY_AUTH; + sa_akey.sadb_key_len = (sizeof(sa_akey) + + ((alen + 7) / 8) * 8) / 8; + sa_akey.sadb_key_bits = 8 * alen; + + memset(&sa_ekey, 0, sizeof(sa_ekey)); + sa_ekey.sadb_key_exttype = SADB_EXT_KEY_ENCRYPT; + sa_ekey.sadb_key_len = (sizeof(sa_ekey) + + ((elen + 7) / 8) * 8) / 8; + sa_ekey.sadb_key_bits = 8 * elen; + + break; + } + + iov_cnt = 0; + + /* msghdr */ + iov[iov_cnt].iov_base = &smsg; + iov[iov_cnt].iov_len = sizeof(smsg); + iov_cnt++; + + switch (mtype) { + case SADB_ADD: + case SADB_UPDATE: + case SADB_DELETE: + /* SA hdr */ + iov[iov_cnt].iov_base = &sa; + iov[iov_cnt].iov_len = sizeof(sa); + smsg.sadb_msg_len += sa.sadb_sa_len; + iov_cnt++; + break; + case SADB_GETSPI: + /* SPI range */ + iov[iov_cnt].iov_base = &sa_spirange; + iov[iov_cnt].iov_len = sizeof(sa_spirange); + smsg.sadb_msg_len += sa_spirange.sadb_spirange_len; + iov_cnt++; + break; + } + + /* dest addr */ + iov[iov_cnt].iov_base = &sa_dst; + iov[iov_cnt].iov_len = sizeof(sa_dst); + iov_cnt++; + iov[iov_cnt].iov_base = &su_dst; + iov[iov_cnt].iov_len = ROUNDUP(sockaddr_len(&su_dst.sa)); + smsg.sadb_msg_len += sa_dst.sadb_address_len; + iov_cnt++; + + /* src addr */ + iov[iov_cnt].iov_base = &sa_src; + iov[iov_cnt].iov_len = sizeof(sa_src); + iov_cnt++; + iov[iov_cnt].iov_base = &su_src; + iov[iov_cnt].iov_len = ROUNDUP(sockaddr_len(&su_src.sa)); + smsg.sadb_msg_len += sa_src.sadb_address_len; + iov_cnt++; + + switch (mtype) { + case SADB_ADD: + case SADB_UPDATE: + if (alen) { + /* auth key */ + iov[iov_cnt].iov_base = &sa_akey; + iov[iov_cnt].iov_len = sizeof(sa_akey); + iov_cnt++; + iov[iov_cnt].iov_base = akey; + iov[iov_cnt].iov_len = ((alen + 7) / 8) * 8; + smsg.sadb_msg_len += sa_akey.sadb_key_len; + iov_cnt++; + } + if (elen) { + /* encryption key */ + iov[iov_cnt].iov_base = &sa_ekey; + iov[iov_cnt].iov_len = sizeof(sa_ekey); + iov_cnt++; + iov[iov_cnt].iov_base = ekey; + iov[iov_cnt].iov_len = ((elen + 7) / 8) * 8; + smsg.sadb_msg_len += sa_ekey.sadb_key_len; + iov_cnt++; + } + break; + } + + len = smsg.sadb_msg_len * 8; + do { + n = writev(sd, iov, iov_cnt); + } while (n == -1 && (errno == EAGAIN || errno == EINTR)); + + if (n == -1) { + log_warn("writev (%d/%d)", iov_cnt, len); + return (-1); + } + + return (0); +} + +int +pfkey_read(int sd, struct sadb_msg *h) +{ + struct sadb_msg hdr; + + if (recv(sd, &hdr, sizeof(hdr), MSG_PEEK) != sizeof(hdr)) { + if (errno == EAGAIN || errno == EINTR) + return (0); + log_warn("pfkey peek"); + return (-1); + } + + /* XXX: Only one message can be outstanding. */ + if (hdr.sadb_msg_seq == sadb_msg_seq && hdr.sadb_msg_pid == pid) { + if (h) + *h = hdr; + return (0); + } + + /* not ours, discard */ + if (read(sd, &hdr, sizeof(hdr)) == -1) { + if (errno == EAGAIN || errno == EINTR) + return (0); + log_warn("pfkey read"); + return (-1); + } + + return (1); +} + +static int +pfkey_reply(int sd, uint32_t *spi) +{ + struct sadb_msg hdr, *msg; + struct sadb_ext *ext; + struct sadb_sa *sa; + uint8_t *data; + ssize_t len; + int rv; + + do { + rv = pfkey_read(sd, &hdr); + if (rv == -1) + return (-1); + } while (rv); + + if (hdr.sadb_msg_errno != 0) { + errno = hdr.sadb_msg_errno; + if (errno == ESRCH) + return (0); + else { + log_warn("pfkey"); + return (-1); + } + } + if ((data = reallocarray(NULL, hdr.sadb_msg_len, PFKEY2_CHUNK)) == NULL) { + log_warn("pfkey malloc"); + return (-1); + } + len = hdr.sadb_msg_len * PFKEY2_CHUNK; + if (read(sd, data, len) != len) { + log_warn("pfkey read"); + explicit_bzero(data, len); + free(data); + return (-1); + } + + if (hdr.sadb_msg_type == SADB_GETSPI) { + if (spi == NULL) { + explicit_bzero(data, len); + free(data); + return (0); + } + + msg = (struct sadb_msg *)data; + for (ext = (struct sadb_ext *)(msg + 1); + (size_t)((uint8_t *)ext - (uint8_t *)msg) < + msg->sadb_msg_len * PFKEY2_CHUNK; + ext = (struct sadb_ext *)((uint8_t *)ext + + ext->sadb_ext_len * PFKEY2_CHUNK)) { + if (ext->sadb_ext_type == SADB_EXT_SA) { + sa = (struct sadb_sa *) ext; + *spi = ntohl(sa->sadb_sa_spi); + break; + } + } + } + explicit_bzero(data, len); + free(data); + return (0); +} + +static int +pfkey_sa_add(int af, union ldpd_addr *src, union ldpd_addr *dst, uint8_t keylen, + char *key, uint32_t *spi) +{ + if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_GETSPI, 0, + af, src, dst, 0, 0, 0, NULL, 0, 0, NULL, 0, 0) < 0) + return (-1); + if (pfkey_reply(fd, spi) < 0) + return (-1); + if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_UPDATE, 0, + af, src, dst, *spi, 0, keylen, key, 0, 0, NULL, 0, 0) < 0) + return (-1); + if (pfkey_reply(fd, NULL) < 0) + return (-1); + return (0); +} + +static int +pfkey_sa_remove(int af, union ldpd_addr *src, union ldpd_addr *dst, + uint32_t *spi) +{ + if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_DELETE, 0, + af, src, dst, *spi, 0, 0, NULL, 0, 0, NULL, 0, 0) < 0) + return (-1); + if (pfkey_reply(fd, NULL) < 0) + return (-1); + *spi = 0; + return (0); +} + +static int +pfkey_md5sig_establish(struct nbr *nbr, struct nbr_params *nbrp) +{ + sleep(1); + + if (!nbr->auth.spi_out) + if (pfkey_sa_add(nbr->af, &nbr->laddr, &nbr->raddr, + nbrp->auth.md5key_len, nbrp->auth.md5key, + &nbr->auth.spi_out) == -1) + return (-1); + if (!nbr->auth.spi_in) + if (pfkey_sa_add(nbr->af, &nbr->raddr, &nbr->laddr, + nbrp->auth.md5key_len, nbrp->auth.md5key, + &nbr->auth.spi_in) == -1) + return (-1); + + nbr->auth.established = 1; + return (0); +} + +static int +pfkey_md5sig_remove(struct nbr *nbr) +{ + if (nbr->auth.spi_out) + if (pfkey_sa_remove(nbr->af, &nbr->laddr, &nbr->raddr, + &nbr->auth.spi_out) == -1) + return (-1); + if (nbr->auth.spi_in) + if (pfkey_sa_remove(nbr->af, &nbr->raddr, &nbr->laddr, + &nbr->auth.spi_in) == -1) + return (-1); + + nbr->auth.established = 0; + nbr->auth.spi_in = 0; + nbr->auth.spi_out = 0; + nbr->auth.method = AUTH_NONE; + memset(nbr->auth.md5key, 0, sizeof(nbr->auth.md5key)); + + return (0); +} + +int +pfkey_establish(struct nbr *nbr, struct nbr_params *nbrp) +{ + switch (nbr->auth.method) { + case AUTH_MD5SIG: + strlcpy(nbr->auth.md5key, nbrp->auth.md5key, sizeof(nbr->auth.md5key)); + return pfkey_md5sig_establish(nbr, nbrp); + case AUTH_NONE: + return 0; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +int +pfkey_remove(struct nbr *nbr) +{ + if (!nbr->auth.established) + return 0; + + switch (nbr->auth.method) { + case AUTH_MD5SIG: + return pfkey_md5sig_remove(nbr); + case AUTH_NONE: + return 0; + break; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +int +pfkey_init(void) +{ + if ((fd = socket(PF_KEY, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_KEY_V2)) == -1) { + if (errno == EPROTONOSUPPORT) { + log_warnx("PF_KEY not available"); + sysdep.no_pfkey = 1; + return (-1); + } else + fatal("pfkey setup failed"); + } + return (fd); +} +#endif /* __OpenBSD__ */ |