/* $OpenBSD: net.c,v 1.23 2015/12/12 20:04:23 mmcc 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 #include #include "sasyncd.h" #include "net.h" struct msg { u_int8_t *buf; u_int32_t len; int refcnt; }; struct qmsg { SIMPLEQ_ENTRY(qmsg) next; struct msg *msg; }; int *listeners; AES_KEY aes_key[2]; #define AES_IV_LEN AES_BLOCK_SIZE /* We never send (or expect to receive) messages smaller/larger than this. */ #define MSG_MINLEN 12 #define MSG_MAXLEN 4096 /* Local prototypes. */ static u_int8_t *net_read(struct syncpeer *, u_int32_t *, u_int32_t *); static int net_set_sa(struct sockaddr *, char *, in_port_t); static void net_check_peers(void *); /* Pretty-print a buffer. */ void dump_buf(int lvl, u_int8_t *b, u_int32_t len, char *title) { u_int32_t i, off, blen; u_int8_t *buf; const char def[] = "Buffer:"; if (cfgstate.verboselevel < lvl) return; blen = 2 * (len + len / 36) + 3 + (title ? strlen(title) : sizeof def); if (!(buf = calloc(1, blen))) return; snprintf(buf, blen, "%s\n ", title ? title : def); off = strlen(buf); for (i = 0; i < len; i++, off+=2) { snprintf(buf + off, blen - off, "%02x", b[i]); if ((i+1) % 36 == 0) { off += 2; snprintf(buf + off, blen - off, "\n "); } } log_msg(lvl, "%s", buf); free(buf); } /* Add a listening socket. */ static int net_add_listener(struct sockaddr *sa) { char host[NI_MAXHOST], port[NI_MAXSERV]; int r, s; s = socket(sa->sa_family, SOCK_STREAM, 0); if (s < 0) { perror("net_add_listener: socket()"); close(s); return -1; } r = 1; if (setsockopt(s, SOL_SOCKET, cfgstate.listen_on ? SO_REUSEADDR : SO_REUSEPORT, (void *)&r, sizeof r)) { perror("net_add_listener: setsockopt()"); close(s); return -1; } if (bind(s, sa, sa->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof (struct sockaddr_in6))) { perror("net_add_listener: bind()"); close(s); return -1; } if (listen(s, 3)) { perror("net_add_listener: listen()"); close(s); return -1; } if (getnameinfo(sa, sa->sa_len, host, sizeof host, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV)) log_msg(2, "listening on port %u fd %d", cfgstate.listen_port, s); else log_msg(2, "listening on %s port %s fd %d", host, port, s); return s; } /* Allocate and fill in listeners array. */ static int net_setup_listeners(void) { struct sockaddr_storage sa_storage; struct sockaddr *sa = (struct sockaddr *)&sa_storage; struct sockaddr_in *sin = (struct sockaddr_in *)sa; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; struct ifaddrs *ifap = 0, *ifa; int i, count; /* Setup listening sockets. */ memset(&sa_storage, 0, sizeof sa_storage); if (net_set_sa(sa, cfgstate.listen_on, cfgstate.listen_port) == 0) { listeners = calloc(2, sizeof(int)); if (!listeners) { perror("net_setup_listeners: calloc()"); goto errout; } listeners[1] = -1; listeners[0] = net_add_listener(sa); if (listeners[0] == -1) { log_msg(0, "net_setup_listeners: could not find " "listen address (%s)", cfgstate.listen_on); goto errout; } return 0; } /* * If net_set_sa() failed, cfgstate.listen_on is probably an * interface name, so we should listen on all it's addresses. */ if (getifaddrs(&ifap) != 0) { perror("net_setup_listeners: getifaddrs()"); goto errout; } /* How many addresses matches? */ for (count = 0, ifa = ifap; ifa; ifa = ifa->ifa_next) { if (!ifa->ifa_name || !ifa->ifa_addr || (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6)) continue; if (cfgstate.listen_family && cfgstate.listen_family != ifa->ifa_addr->sa_family) continue; if (strcmp(ifa->ifa_name, cfgstate.listen_on) != 0) continue; count++; } if (!count) { log_msg(0, "net_setup_listeners: no listeners found for %s", cfgstate.listen_on); goto errout; } /* Allocate one extra slot and set to -1, marking end of array. */ listeners = calloc(count + 1, sizeof(int)); if (!listeners) { perror("net_setup_listeners: calloc()"); goto errout; } for (i = 0; i <= count; i++) listeners[i] = -1; /* Create listening sockets */ for (count = 0, ifa = ifap; ifa; ifa = ifa->ifa_next) { if (!ifa->ifa_name || !ifa->ifa_addr || (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6)) continue; if (cfgstate.listen_family && cfgstate.listen_family != ifa->ifa_addr->sa_family) continue; if (strcmp(ifa->ifa_name, cfgstate.listen_on) != 0) continue; memset(&sa_storage, 0, sizeof sa_storage); sa->sa_family = ifa->ifa_addr->sa_family; switch (sa->sa_family) { case AF_INET: sin->sin_port = htons(cfgstate.listen_port); sin->sin_len = sizeof *sin; memcpy(&sin->sin_addr, &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, sizeof sin->sin_addr); break; case AF_INET6: sin6->sin6_port = htons(cfgstate.listen_port); sin6->sin6_len = sizeof *sin6; memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, sizeof sin6->sin6_addr); break; } listeners[count] = net_add_listener(sa); if (listeners[count] == -1) { log_msg(2, "net_setup_listeners(setup): failed to " "add listener, count = %d", count); goto errout; } count++; } freeifaddrs(ifap); return 0; errout: if (ifap) freeifaddrs(ifap); if (listeners) { for (i = 0; listeners[i] != -1; i++) close(listeners[i]); free(listeners); } return -1; } int net_init(void) { struct syncpeer *p; if (AES_set_encrypt_key(cfgstate.sharedkey, cfgstate.sharedkey_len, &aes_key[0]) || AES_set_decrypt_key(cfgstate.sharedkey, cfgstate.sharedkey_len, &aes_key[1])) { fprintf(stderr, "Bad AES shared key\n"); return -1; } if (net_setup_listeners()) return -1; for (p = LIST_FIRST(&cfgstate.peerlist); p; p = LIST_NEXT(p, link)) { p->socket = -1; SIMPLEQ_INIT(&p->msgs); } net_check_peers(0); return 0; } static void net_enqueue(struct syncpeer *p, struct msg *m) { struct qmsg *qm; if (p->socket < 0) return; qm = calloc(1, sizeof *qm); if (!qm) { log_err("net_enqueue: calloc()"); return; } qm->msg = m; m->refcnt++; SIMPLEQ_INSERT_TAIL(&p->msgs, qm, next); return; } /* * Queue a message for transmission to a particular peer, * or to all peers if no peer is specified. */ int net_queue(struct syncpeer *p0, u_int32_t msgtype, u_int8_t *buf, u_int32_t len) { struct syncpeer *p = p0; struct msg *m; SHA_CTX ctx; u_int8_t hash[SHA_DIGEST_LENGTH]; u_int8_t iv[AES_IV_LEN], tmp_iv[AES_IV_LEN]; u_int32_t v, padlen = 0; int i, offset; m = calloc(1, sizeof *m); if (!m) { log_err("net_queue: calloc()"); free(buf); return -1; } /* Generate hash */ SHA1_Init(&ctx); SHA1_Update(&ctx, buf, len); SHA1_Final(hash, &ctx); dump_buf(2, hash, sizeof hash, "net_queue: computed hash"); /* Padding required? */ i = len % AES_IV_LEN; if (i) { u_int8_t *pbuf; i = AES_IV_LEN - i; pbuf = realloc(buf, len + i); if (!pbuf) { log_err("net_queue: realloc()"); free(buf); free(m); return -1; } padlen = i; while (i > 0) pbuf[len++] = (u_int8_t)i--; buf = pbuf; } /* Get random IV */ for (i = 0; (size_t)i <= sizeof iv - sizeof v; i += sizeof v) { v = arc4random(); memcpy(&iv[i], &v, sizeof v); } dump_buf(2, iv, sizeof iv, "net_queue: IV"); memcpy(tmp_iv, iv, sizeof tmp_iv); /* Encrypt */ dump_buf(2, buf, len, "net_queue: pre encrypt"); AES_cbc_encrypt(buf, buf, len, &aes_key[0], tmp_iv, AES_ENCRYPT); dump_buf(2, buf, len, "net_queue: post encrypt"); /* Allocate send buffer */ m->len = len + sizeof iv + sizeof hash + 3 * sizeof(u_int32_t); m->buf = malloc(m->len); if (!m->buf) { free(m); free(buf); log_err("net_queue: calloc()"); return -1; } offset = 0; /* Fill it (order must match parsing code in net_read()) */ v = htonl(m->len - sizeof(u_int32_t)); memcpy(m->buf + offset, &v, sizeof v); offset += sizeof v; v = htonl(msgtype); memcpy(m->buf + offset, &v, sizeof v); offset += sizeof v; v = htonl(padlen); memcpy(m->buf + offset, &v, sizeof v); offset += sizeof v; memcpy(m->buf + offset, hash, sizeof hash); offset += sizeof hash; memcpy(m->buf + offset, iv, sizeof iv); offset += sizeof iv; memcpy(m->buf + offset, buf, len); free(buf); if (p) net_enqueue(p, m); else for (p = LIST_FIRST(&cfgstate.peerlist); p; p = LIST_NEXT(p, link)) net_enqueue(p, m); if (!m->refcnt) { free(m->buf); free(m); } return 0; } /* Set all write pending filedescriptors. */ int net_set_pending_wfds(fd_set *fds) { struct syncpeer *p; int max_fd = -1; for (p = LIST_FIRST(&cfgstate.peerlist); p; p = LIST_NEXT(p, link)) if (p->socket > -1 && SIMPLEQ_FIRST(&p->msgs)) { FD_SET(p->socket, fds); if (p->socket > max_fd) max_fd = p->socket; } return max_fd + 1; } /* * Set readable filedescriptors. They are basically the same as for write, * plus the listening socket. */ int net_set_rfds(fd_set *fds) { struct syncpeer *p; int i, max_fd = -1; for (p = LIST_FIRST(&cfgstate.peerlist); p; p = LIST_NEXT(p, link)) { if (p->socket > -1) FD_SET(p->socket, fds); if (p->socket > max_fd) max_fd = p->socket; } for (i = 0; listeners[i] != -1; i++) { FD_SET(listeners[i], fds); if (listeners[i] > max_fd) max_fd = listeners[i]; } return max_fd + 1; } static void net_accept(int accept_socket) { struct sockaddr_storage sa_storage, sa_storage2; struct sockaddr *sa = (struct sockaddr *)&sa_storage; struct sockaddr *sa2 = (struct sockaddr *)&sa_storage2; struct sockaddr_in *sin, *sin2; struct sockaddr_in6 *sin6, *sin62; struct syncpeer *p; socklen_t socklen; int s, found; /* Accept a new incoming connection */ socklen = sizeof sa_storage; memset(&sa_storage, 0, socklen); memset(&sa_storage2, 0, socklen); s = accept(accept_socket, sa, &socklen); if (s > -1) { /* Setup the syncpeer structure */ found = 0; for (p = LIST_FIRST(&cfgstate.peerlist); p && !found; p = LIST_NEXT(p, link)) { /* Match? */ if (net_set_sa(sa2, p->name, 0)) continue; if (sa->sa_family != sa2->sa_family) continue; if (sa->sa_family == AF_INET) { sin = (struct sockaddr_in *)sa; sin2 = (struct sockaddr_in *)sa2; if (memcmp(&sin->sin_addr, &sin2->sin_addr, sizeof(struct in_addr))) continue; } else { sin6 = (struct sockaddr_in6 *)sa; sin62 = (struct sockaddr_in6 *)sa2; if (memcmp(&sin6->sin6_addr, &sin62->sin6_addr, sizeof(struct in6_addr))) continue; } /* Match! */ found++; p->socket = s; log_msg(1, "net: peer \"%s\" connected", p->name); if (cfgstate.runstate == MASTER) timer_add("pfkey_snap", 2, pfkey_snapshot, p); } if (!found) { log_msg(1, "net: found no matching peer for accepted " "socket, closing."); close(s); } } else if (errno != EWOULDBLOCK && errno != EINTR && errno != ECONNABORTED) log_err("net: accept()"); } void net_handle_messages(fd_set *fds) { struct syncpeer *p; u_int8_t *msg; u_int32_t msgtype, msglen; int i; for (i = 0; listeners[i] != -1; i++) if (FD_ISSET(listeners[i], fds)) net_accept(listeners[i]); for (p = LIST_FIRST(&cfgstate.peerlist); p; p = LIST_NEXT(p, link)) { if (p->socket < 0 || !FD_ISSET(p->socket, fds)) continue; msg = net_read(p, &msgtype, &msglen); if (!msg) continue; log_msg(2, "net_handle_messages: got msg type %u len %u from " "peer %s", msgtype, msglen, p->name); switch (msgtype) { case MSG_SYNCCTL: net_ctl_handle_msg(p, msg, msglen); free(msg); break; case MSG_PFKEYDATA: if (p->runstate != MASTER || cfgstate.runstate == MASTER) { log_msg(1, "net: got PFKEY message from " "non-MASTER peer"); free(msg); if (cfgstate.runstate == MASTER) net_ctl_send_state(p); else net_ctl_send_error(p, 0); } else if (pfkey_queue_message(msg, msglen)) free(msg); break; default: log_msg(0, "net: got unknown message type %u len %u " "from peer %s", msgtype, msglen, p->name); free(msg); net_ctl_send_error(p, 0); } } } void net_send_messages(fd_set *fds) { struct syncpeer *p; struct qmsg *qm; struct msg *m; ssize_t r; for (p = LIST_FIRST(&cfgstate.peerlist); p; p = LIST_NEXT(p, link)) { if (p->socket < 0 || !FD_ISSET(p->socket, fds)) continue; qm = SIMPLEQ_FIRST(&p->msgs); if (!qm) { /* XXX Log */ continue; } m = qm->msg; log_msg(2, "net_send_messages: msg %p len %u ref %d " "to peer %s", m, m->len, m->refcnt, p->name); /* write message */ r = write(p->socket, m->buf, m->len); if (r == -1) { net_disconnect_peer(p); log_msg(0, "net_send_messages: write() failed, " "peer disconnected"); } else if (r < (ssize_t)m->len) { /* retransmit later */ continue; } /* cleanup */ SIMPLEQ_REMOVE_HEAD(&p->msgs, next); free(qm); if (--m->refcnt < 1) { log_msg(2, "net_send_messages: freeing msg %p", m); free(m->buf); free(m); } } return; } void net_disconnect_peer(struct syncpeer *p) { if (p->socket > -1) { log_msg(1, "net_disconnect_peer: peer \"%s\" removed", p->name); close(p->socket); } p->socket = -1; } void net_shutdown(void) { struct syncpeer *p; struct qmsg *qm; struct msg *m; int i; while ((p = LIST_FIRST(&cfgstate.peerlist))) { while ((qm = SIMPLEQ_FIRST(&p->msgs))) { SIMPLEQ_REMOVE_HEAD(&p->msgs, next); m = qm->msg; if (--m->refcnt < 1) { free(m->buf); free(m); } free(qm); } net_disconnect_peer(p); free(p->sa); free(p->name); LIST_REMOVE(p, link); cfgstate.peercnt--; free(p); } if (listeners) { for (i = 0; listeners[i] != -1; i++) close(listeners[i]); free(listeners); listeners = 0; } } /* * Helper functions (local) below here. */ static u_int8_t * net_read(struct syncpeer *p, u_int32_t *msgtype, u_int32_t *msglen) { u_int8_t *msg, *blob, *rhash, *iv, hash[SHA_DIGEST_LENGTH]; u_int32_t v, blob_len, pos = 0; int padlen = 0, offset = 0; ssize_t r; SHA_CTX ctx; /* Read blob length */ r = read(p->socket, &v, sizeof v); if (r != (ssize_t)sizeof v) { if (r < 1) net_disconnect_peer(p); return NULL; } blob_len = ntohl(v); if (blob_len < sizeof hash + AES_IV_LEN + 2 * sizeof(u_int32_t)) return NULL; *msglen = blob_len - sizeof hash - AES_IV_LEN - 2 * sizeof(u_int32_t); if (*msglen < MSG_MINLEN || *msglen > MSG_MAXLEN) return NULL; /* Read message blob */ blob = malloc(blob_len); if (!blob) { log_err("net_read: malloc()"); return NULL; } while (blob_len > pos) { switch (r = read(p->socket, blob + pos, blob_len - pos)) { case -1: if (errno == EINTR || errno == EAGAIN) continue; /* FALLTHROUGH */ case 0: net_disconnect_peer(p); free(blob); return NULL; /* NOTREACHED */ default: pos += r; } } offset = 0; memcpy(&v, blob + offset, sizeof v); *msgtype = ntohl(v); offset += sizeof v; if (*msgtype > MSG_MAXTYPE) { free(blob); return NULL; } memcpy(&v, blob + offset, sizeof v); padlen = ntohl(v); offset += sizeof v; rhash = blob + offset; iv = rhash + sizeof hash; msg = malloc(*msglen); if (!msg) { free(blob); return NULL; } memcpy(msg, iv + AES_IV_LEN, *msglen); dump_buf(2, rhash, sizeof hash, "net_read: got hash"); dump_buf(2, iv, AES_IV_LEN, "net_read: got IV"); dump_buf(2, msg, *msglen, "net_read: pre decrypt"); AES_cbc_encrypt(msg, msg, *msglen, &aes_key[1], iv, AES_DECRYPT); dump_buf(2, msg, *msglen, "net_read: post decrypt"); *msglen -= padlen; SHA1_Init(&ctx); SHA1_Update(&ctx, msg, *msglen); SHA1_Final(hash, &ctx); dump_buf(2, hash, sizeof hash, "net_read: computed hash"); if (memcmp(hash, rhash, sizeof hash) != 0) { free(blob); free(msg); log_msg(0, "net_read: got bad message (typo in shared key?)"); return NULL; } free(blob); return msg; } static int net_set_sa(struct sockaddr *sa, char *name, in_port_t port) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; if (!name) { /* XXX Assume IPv4 */ sa->sa_family = AF_INET; sin->sin_port = htons(port); sin->sin_len = sizeof *sin; return 0; } if (inet_pton(AF_INET, name, &sin->sin_addr) == 1) { sa->sa_family = AF_INET; sin->sin_port = htons(port); sin->sin_len = sizeof *sin; return 0; } if (inet_pton(AF_INET6, name, &sin6->sin6_addr) == 1) { sa->sa_family = AF_INET6; sin6->sin6_port = htons(port); sin6->sin6_len = sizeof *sin6; return 0; } return -1; } static void got_sigalrm(int s) { return; } void net_connect(void) { struct itimerval iv; struct syncpeer *p; signal(SIGALRM, got_sigalrm); memset(&iv, 0, sizeof iv); iv.it_value.tv_sec = 5; iv.it_interval.tv_sec = 5; setitimer(ITIMER_REAL, &iv, NULL); for (p = LIST_FIRST(&cfgstate.peerlist); p; p = LIST_NEXT(p, link)) { if (p->socket > -1) continue; if (!p->sa) { p->sa = calloc(1, sizeof(struct sockaddr_storage)); if (!p->sa) return; if (net_set_sa(p->sa, p->name, cfgstate.listen_port)) continue; } p->socket = socket(p->sa->sa_family, SOCK_STREAM, 0); if (p->socket < 0) { log_err("peer \"%s\": socket()", p->name); continue; } if (connect(p->socket, p->sa, p->sa->sa_len)) { log_msg(1, "net_connect: peer \"%s\" not ready yet", p->name); net_disconnect_peer(p); continue; } if (net_ctl_send_state(p)) { log_msg(0, "net_connect: peer \"%s\" failed", p->name); net_disconnect_peer(p); continue; } log_msg(1, "net_connect: peer \"%s\" connected, fd %d", p->name, p->socket); /* Schedule a pfkey sync to the newly connected peer. */ if (cfgstate.runstate == MASTER) timer_add("pfkey_snapshot", 2, pfkey_snapshot, p); } timerclear(&iv.it_value); timerclear(&iv.it_interval); setitimer(ITIMER_REAL, &iv, NULL); signal(SIGALRM, SIG_IGN); return; } static void net_check_peers(void *arg) { net_connect(); (void)timer_add("peer recheck", 600, net_check_peers, 0); }