/* $OpenBSD: pfkey.c,v 1.20 2010/06/29 18:10:04 kjell Exp $ */ /* * Copyright (c) 2005 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 Multicom Security AB. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sasyncd.h" #include "monitor.h" #include "net.h" struct pfkey_msg { SIMPLEQ_ENTRY(pfkey_msg) next; u_int8_t *buf; u_int32_t len; }; SIMPLEQ_HEAD(, pfkey_msg) pfkey_msglist; static const char *msgtypes[] = { "RESERVED", "GETSPI", "UPDATE", "ADD", "DELETE", "GET", "ACQUIRE", "REGISTER", "EXPIRE", "FLUSH", "DUMP", "X_PROMISC", "X_ADDFLOW", "X_DELFLOW", "X_GRPSPIS", "X_ASKPOLICY", "X_SPDDUMP" }; #define CHUNK sizeof(u_int64_t) static const char *pfkey_print_type(struct sadb_msg *); static int pfkey_write(u_int8_t *buf, ssize_t len) { struct sadb_msg *msg = (struct sadb_msg *)buf; ssize_t n; if (cfgstate.pfkey_socket == -1) return 0; do { n = write(cfgstate.pfkey_socket, buf, len); } while (n == -1 && (errno == EAGAIN || errno == EINTR)); if (n == -1) { log_err("pfkey: msg %s write() failed on socket %d", pfkey_print_type(msg), cfgstate.pfkey_socket); return -1; } return 0; } int pfkey_set_promisc(void) { struct sadb_msg msg; static u_int32_t seq = 1; memset(&msg, 0, sizeof msg); msg.sadb_msg_version = PF_KEY_V2; msg.sadb_msg_seq = seq++; msg.sadb_msg_satype = 1; /* Special; 1 to enable, 0 to disable */ msg.sadb_msg_type = SADB_X_PROMISC; msg.sadb_msg_pid = getpid(); msg.sadb_msg_len = sizeof msg / CHUNK; return pfkey_write((u_int8_t *)&msg, sizeof msg); } /* Send a SADB_FLUSH PFKEY message to peer 'p' */ static void pfkey_send_flush(struct syncpeer *p) { struct sadb_msg *m = (struct sadb_msg *)calloc(1, sizeof *m); static u_int32_t seq = 1; if (m) { memset(m, 0, sizeof *m); m->sadb_msg_version = PF_KEY_V2; m->sadb_msg_seq = seq++; m->sadb_msg_type = SADB_FLUSH; m->sadb_msg_satype = SADB_SATYPE_UNSPEC; m->sadb_msg_pid = getpid(); m->sadb_msg_len = sizeof *m / CHUNK; log_msg(2, "pfkey_send_flush: sending FLUSH to peer %s", p->name); net_queue(p, MSG_PFKEYDATA, (u_int8_t *)m, sizeof *m); } } static const char * pfkey_print_type(struct sadb_msg *msg) { static char uk[20]; if (msg->sadb_msg_type < sizeof msgtypes / sizeof msgtypes[0]) return msgtypes[msg->sadb_msg_type]; else { snprintf(uk, sizeof uk, "", msg->sadb_msg_type); return uk; } } static struct sadb_ext * pfkey_find_ext(struct sadb_msg *msg, u_int16_t type) { struct sadb_ext *ext; u_int8_t *e; for (e = (u_int8_t *)msg + sizeof *msg; e < (u_int8_t *)msg + msg->sadb_msg_len * CHUNK; e += ext->sadb_ext_len * CHUNK) { ext = (struct sadb_ext *)e; if (ext->sadb_ext_len == 0) break; if (ext->sadb_ext_type != type) continue; return ext; } return NULL; } /* Return: 0 means ok to sync msg, 1 means to skip it */ static int pfkey_msg_filter(struct sadb_msg *msg) { struct sockaddr *src = 0, *dst = 0; struct syncpeer *p; struct sadb_ext *ext; u_int8_t *max; switch (msg->sadb_msg_type) { case SADB_X_PROMISC: case SADB_DUMP: case SADB_GET: case SADB_GETSPI: case SADB_ACQUIRE: case SADB_X_ASKPOLICY: case SADB_REGISTER: /* Some messages should not be synced. */ return 1; case SADB_ADD: /* No point in syncing LARVAL SAs */ if (pfkey_find_ext(msg, SADB_EXT_KEY_ENCRYPT) == 0) return 1; case SADB_DELETE: case SADB_X_ADDFLOW: case SADB_X_DELFLOW: case SADB_EXPIRE: /* Continue below */ break; case SADB_FLUSH: if ((cfgstate.flags & FM_MASK) == FM_NEVER) return 1; break; default: return 0; } if ((cfgstate.flags & SKIP_LOCAL_SAS) == 0) return 0; /* SRC or DST address of this msg must not be one of our peers. */ ext = pfkey_find_ext(msg, SADB_EXT_ADDRESS_SRC); if (ext) src = (struct sockaddr *)((struct sadb_address *)ext + 1); ext = pfkey_find_ext(msg, SADB_EXT_ADDRESS_DST); if (ext) dst = (struct sockaddr *)((struct sadb_address *)ext + 1); if (!src && !dst) return 0; max = (u_int8_t *)msg + msg->sadb_msg_len * CHUNK; if (src && ((u_int8_t *)src + src->sa_len) > max) return 1; if (dst && ((u_int8_t *)dst + dst->sa_len) > max) return 1; /* Found SRC or DST, check it against our peers */ for (p = LIST_FIRST(&cfgstate.peerlist); p; p = LIST_NEXT(p, link)) { if (p->socket < 0 || p->sa->sa_family != (src ? src->sa_family : dst->sa_family)) continue; switch (p->sa->sa_family) { case AF_INET: if (src && memcmp( &((struct sockaddr_in *)p->sa)->sin_addr.s_addr, &((struct sockaddr_in *)src)->sin_addr.s_addr, sizeof(struct in_addr)) == 0) return 1; if (dst && memcmp( &((struct sockaddr_in *)p->sa)->sin_addr.s_addr, &((struct sockaddr_in *)dst)->sin_addr.s_addr, sizeof(struct in_addr)) == 0) return 1; break; case AF_INET6: if (src && memcmp(&((struct sockaddr_in6 *)p->sa)->sin6_addr, &((struct sockaddr_in6 *)src)->sin6_addr, sizeof(struct in_addr)) == 0) return 1; if (dst && memcmp(&((struct sockaddr_in6 *)p->sa)->sin6_addr, &((struct sockaddr_in6 *)dst)->sin6_addr, sizeof(struct in_addr)) == 0) return 1; break; } } return 0; } static int pfkey_handle_message(struct sadb_msg *m) { struct sadb_msg *msg = m; /* * Report errors, but ignore for DELETE (both isakmpd and kernel will * expire the SA, if the kernel is first, DELETE returns failure). */ if (msg->sadb_msg_errno && msg->sadb_msg_type != SADB_DELETE && msg->sadb_msg_pid == (u_int32_t)getpid()) { errno = msg->sadb_msg_errno; log_msg(1, "pfkey error (%s)", pfkey_print_type(msg)); } /* We only want promiscuous messages here, skip all others. */ if (msg->sadb_msg_type != SADB_X_PROMISC || (msg->sadb_msg_len * CHUNK) < 2 * sizeof *msg) { free(m); return 0; } /* Move next msg to start of the buffer. */ msg++; /* * We should not listen to PFKEY messages when we are not running * as MASTER, or the pid is our own. */ if (cfgstate.runstate != MASTER || msg->sadb_msg_pid == (u_int32_t)getpid()) { free(m); return 0; } if (pfkey_msg_filter(msg)) { free(m); return 0; } switch (msg->sadb_msg_type) { case SADB_UPDATE: /* * Tweak -- the peers do not have a larval SA to update, so * instead we ADD it here. */ msg->sadb_msg_type = SADB_ADD; /* FALLTHROUGH */ default: /* Pass the rest along to our peers. */ memmove(m, msg, msg->sadb_msg_len * CHUNK); /* for realloc */ return net_queue(NULL, MSG_PFKEYDATA, (u_int8_t *)m, m->sadb_msg_len * CHUNK); } return 0; } static int pfkey_read(void) { struct sadb_msg hdr, *msg; u_int8_t *data; ssize_t datalen; int fd = cfgstate.pfkey_socket; if (recv(fd, &hdr, sizeof hdr, MSG_PEEK) != sizeof hdr) { log_err("pfkey_read: recv() failed"); return -1; } datalen = hdr.sadb_msg_len * CHUNK; data = (u_int8_t *)malloc(datalen); if (!data) { log_err("pfkey_read: malloc(%lu) failed", datalen); return -1; } msg = (struct sadb_msg *)data; if (read(fd, data, datalen) != datalen) { log_err("pfkey_read: read() failed, %lu bytes", datalen); free(data); return -1; } return pfkey_handle_message(msg); } int pfkey_init(int reinit) { int fd; fd = socket(PF_KEY, SOCK_RAW, PF_KEY_V2); if (fd == -1) { perror("failed to open PF_KEY socket"); return -1; } cfgstate.pfkey_socket = fd; if (cfgstate.runstate == MASTER) pfkey_set_promisc(); if (reinit) return (fd > -1 ? 0 : -1); SIMPLEQ_INIT(&pfkey_msglist); return 0; } void pfkey_set_rfd(fd_set *fds) { if (cfgstate.pfkey_socket != -1) FD_SET(cfgstate.pfkey_socket, fds); } void pfkey_set_pending_wfd(fd_set *fds) { if (cfgstate.pfkey_socket != -1 && SIMPLEQ_FIRST(&pfkey_msglist)) FD_SET(cfgstate.pfkey_socket, fds); } void pfkey_read_message(fd_set *fds) { if (cfgstate.pfkey_socket != -1) if (FD_ISSET(cfgstate.pfkey_socket, fds)) (void)pfkey_read(); } void pfkey_send_message(fd_set *fds) { struct pfkey_msg *pmsg = SIMPLEQ_FIRST(&pfkey_msglist); if (!pmsg || !FD_ISSET(cfgstate.pfkey_socket, fds)) return; if (cfgstate.pfkey_socket == -1) if (pfkey_init(1)) /* Reinit socket */ return; (void)pfkey_write(pmsg->buf, pmsg->len); SIMPLEQ_REMOVE_HEAD(&pfkey_msglist, next); free(pmsg->buf); free(pmsg); return; } int pfkey_queue_message(u_int8_t *data, u_int32_t datalen) { struct pfkey_msg *pmsg; struct sadb_msg *sadb = (struct sadb_msg *)data; static u_int32_t seq = 1; pmsg = (struct pfkey_msg *)malloc(sizeof *pmsg); if (!pmsg) { log_err("malloc()"); return -1; } memset(pmsg, 0, sizeof *pmsg); pmsg->buf = data; pmsg->len = datalen; sadb->sadb_msg_pid = getpid(); sadb->sadb_msg_seq = seq++; log_msg(2, "pfkey_queue_message: pfkey %s len %u seq %u", pfkey_print_type(sadb), sadb->sadb_msg_len * CHUNK, sadb->sadb_msg_seq); SIMPLEQ_INSERT_TAIL(&pfkey_msglist, pmsg, next); return 0; } void pfkey_shutdown(void) { struct pfkey_msg *p = SIMPLEQ_FIRST(&pfkey_msglist); while ((p = SIMPLEQ_FIRST(&pfkey_msglist))) { SIMPLEQ_REMOVE_HEAD(&pfkey_msglist, next); free(p->buf); free(p); } if (cfgstate.pfkey_socket > -1) close(cfgstate.pfkey_socket); } /* ------------------------------------------------------------------------- */ void pfkey_snapshot(void *v) { struct syncpeer *p = (struct syncpeer *)v; struct sadb_msg *m; u_int8_t *sadb, *spd, *max, *next, *sendbuf; u_int32_t sadbsz, spdsz; if (!p) return; if (monitor_get_pfkey_snap(&sadb, &sadbsz, &spd, &spdsz)) { log_msg(0, "pfkey_snapshot: failed to get pfkey snapshot"); return; } /* XXX needs moving if snapshot is called more than once per peer */ if ((cfgstate.flags & FM_MASK) == FM_STARTUP) pfkey_send_flush(p); /* Parse SADB data */ if (sadbsz && sadb) { dump_buf(2, sadb, sadbsz, "pfkey_snapshot: SADB data"); max = sadb + sadbsz; for (next = sadb; next < max; next += m->sadb_msg_len * CHUNK) { m = (struct sadb_msg *)next; if (m->sadb_msg_len == 0) break; /* Tweak and send this SA to the peer. */ m->sadb_msg_type = SADB_ADD; if (pfkey_msg_filter(m)) continue; /* Allocate msgbuffer, net_queue() will free it. */ sendbuf = (u_int8_t *)calloc(m->sadb_msg_len, CHUNK); if (sendbuf) { memcpy(sendbuf, m, m->sadb_msg_len * CHUNK); net_queue(p, MSG_PFKEYDATA, sendbuf, m->sadb_msg_len * CHUNK); log_msg(2, "pfkey_snapshot: sync SA %p len %u " "to peer %s", m, m->sadb_msg_len * CHUNK, p->name); } } memset(sadb, 0, sadbsz); free(sadb); } /* Parse SPD data */ if (spdsz && spd) { dump_buf(2, spd, spdsz, "pfkey_snapshot: SPD data"); max = spd + spdsz; for (next = spd; next < max; next += m->sadb_msg_len * CHUNK) { m = (struct sadb_msg *)next; if (m->sadb_msg_len == 0) break; /* Tweak msg type. */ m->sadb_msg_type = SADB_X_ADDFLOW; if (pfkey_msg_filter(m)) continue; /* Allocate msgbuffer, freed by net_queue(). */ sendbuf = (u_int8_t *)calloc(m->sadb_msg_len, CHUNK); if (sendbuf) { memcpy(sendbuf, m, m->sadb_msg_len * CHUNK); net_queue(p, MSG_PFKEYDATA, sendbuf, m->sadb_msg_len * CHUNK); log_msg(2, "pfkey_snapshot: sync FLOW %p len " "%u to peer %s", m, m->sadb_msg_len * CHUNK, p->name); } } /* Cleanup. */ memset(spd, 0, spdsz); free(spd); } net_ctl_send_endsnap(p); return; }