diff --git a/src/transport.c b/src/transport.c index 6344329..1b5844c 100644 --- a/src/transport.c +++ b/src/transport.c @@ -1,1197 +1,1210 @@ /* * Copyright (C) 2011 Jiaju Zhang * Copyright (C) 2013-2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "b_config.h" #include #include #include #include #include #include #include #include /* getnameinfo */ #include #include #include #include #include #include #include #include /* getnameinfo */ #include "attr.h" #include "auth.h" #include "booth.h" #include "config.h" #include "inline-fn.h" #include "log.h" #include "ticket.h" #include "transport.h" #define BOOTH_IPADDR_LEN (sizeof(struct in6_addr)) #define NETLINK_BUFSIZE 16384 #define SOCKET_BUFFER_SIZE 160000 #define FRAME_SIZE_MAX 10000 struct booth_site *local = NULL; /* function to be called when handling booth-group-internal messages; * it's expected to return 0 to indicate success, negative integer * to indicate silent (or possibly already complained about) error, * or positive integer to indicate sender's ID that will then be * emitted in the error log message together with the real source * address if this is available */ static int (*deliver_fn) (struct booth_config *conf, void *msg, int msglen); static void parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) { while (RTA_OK(rta, len)) { if (rta->rta_type <= max) { tb[rta->rta_type] = rta; } rta = RTA_NEXT(rta,len); } } enum match_type { NO_MATCH = 0, FUZZY_MATCH, EXACT_MATCH, }; static int find_address(struct booth_config *conf, unsigned char ipaddr[BOOTH_IPADDR_LEN], int family, int prefixlen, int fuzzy_allowed, struct booth_site **me, int *address_bits_matched) { int i; struct booth_site *node; int bytes, bits_left, mask; unsigned char node_bits, ip_bits; uint8_t *n_a; int matched; enum match_type did_match = NO_MATCH; assert(conf != NULL); bytes = prefixlen / 8; bits_left = prefixlen % 8; /* One bit left to check means ignore 7 lowest bits. */ mask = ~( (1 << (8 - bits_left)) -1); FOREACH_NODE(conf, i, node) { if (family != node->family) { continue; } n_a = node_to_addr_pointer(node); for (matched = 0; matched < node->addrlen; matched++) { if (ipaddr[matched] != n_a[matched]) { break; } } if (matched == node->addrlen) { *address_bits_matched = matched * 8; *me = node; did_match = EXACT_MATCH; break; } if (!fuzzy_allowed) { continue; } /* Check prefix, whole bytes */ if (matched < bytes) { continue; } if (matched * 8 < *address_bits_matched) { continue; } node_bits = n_a[bytes]; ip_bits = ipaddr[bytes]; if (((node_bits ^ ip_bits) & mask) == 0) { /* _At_least_ prefixlen bits matched. */ if (did_match < EXACT_MATCH) { *address_bits_matched = prefixlen; *me = node; did_match = FUZZY_MATCH; } } } return did_match; } static int _find_myself(struct booth_config *conf, int family, struct booth_site **mep, int fuzzy_allowed) { + int rc; int fd; struct sockaddr_nl nladdr; struct booth_site *me; unsigned char ipaddr[BOOTH_IPADDR_LEN]; static char rcvbuf[NETLINK_BUFSIZE]; struct { struct nlmsghdr nlh; struct rtgenmsg g; } req; int address_bits_matched; if (local) { goto found; } me = NULL; address_bits_matched = 0; if (mep) { *mep = NULL; } fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) { log_error("failed to create netlink socket"); return 0; } - setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); + rc = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); + if (rc == -1) { + log_error("setsockopt error %d %d", fd, errno); + close(fd); + return 0; + } memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = sizeof(req); req.nlh.nlmsg_type = RTM_GETADDR; req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; req.nlh.nlmsg_pid = 0; req.nlh.nlmsg_seq = 1; req.g.rtgen_family = family; if (sendto(fd, (void *) &req, sizeof(req), 0, (struct sockaddr*) &nladdr, sizeof(nladdr)) < 0) { close(fd); log_error("failed to send data to netlink socket"); return 0; } while (true) { int status; struct nlmsghdr *h; struct iovec iov = { rcvbuf, sizeof(rcvbuf) }; struct msghdr msg = { (void *) &nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0 }; status = recvmsg(fd, &msg, 0); if (!status) { close(fd); log_error("failed to recvmsg from netlink socket"); return 0; } for (h = (struct nlmsghdr *) rcvbuf; NLMSG_OK(h, status); h = NLMSG_NEXT(h, status)) { struct ifaddrmsg *ifa = NULL; struct rtattr *tb[IFA_MAX+1]; int len; if (h->nlmsg_type == NLMSG_DONE) { goto out; } if (h->nlmsg_type == NLMSG_ERROR) { close(fd); log_error("netlink socket recvmsg error"); return 0; } if (h->nlmsg_type != RTM_NEWADDR) { continue; } ifa = NLMSG_DATA(h); len = h->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)); memset(tb, 0, sizeof(tb)); parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), len); memset(ipaddr, 0, BOOTH_IPADDR_LEN); /* prefer IFA_LOCAL if it exists, for p-t-p interfaces, * otherwise use IFA_ADDRESS */ if (tb[IFA_LOCAL]) { memcpy(ipaddr, RTA_DATA(tb[IFA_LOCAL]), BOOTH_IPADDR_LEN); } else if (tb[IFA_ADDRESS]) { memcpy(ipaddr, RTA_DATA(tb[IFA_ADDRESS]), BOOTH_IPADDR_LEN); } else { log_error("failed to copy netlink addr"); close(fd); return 0; } /* Try to find the exact address or the address with subnet matching. * The function find_address will be called for each address received * from NLMSG_DATA above. * The exact match will be preferred. If no exact match is found, * the function find_address will try to return another, most similar * address (with the longest possible number of same bytes). */ if (ifa->ifa_prefixlen > address_bits_matched) { find_address(conf, ipaddr, ifa->ifa_family, ifa->ifa_prefixlen, fuzzy_allowed, &me, &address_bits_matched); if (me) { log_debug("found myself at %s (%d bits matched)", site_string(me), address_bits_matched); } } /* If the previous NLMSG_DATA calls have already allowed us * to find an address with address_bits_matched matching bits, * then no other better non-exact address can be found. * But we can still try to find an exact match, so let us * call the function find_address with disabled searching of * similar addresses (fuzzy_allowed == 0) */ else if (ifa->ifa_prefixlen == address_bits_matched) { find_address(conf, ipaddr, ifa->ifa_family, ifa->ifa_prefixlen, 0 /* fuzzy_allowed */, &me, &address_bits_matched); if (me) { log_debug("found myself at %s (exact match)", site_string(me)); } } } } out: close(fd); if (!me) { return 0; } me->local = 1; local = me; found: if (mep) { *mep = local; } return 1; } int find_myself(struct booth_config *conf, struct booth_site **mep, int fuzzy_allowed) { return _find_myself(conf, AF_INET6, mep, fuzzy_allowed) || _find_myself(conf, AF_INET, mep, fuzzy_allowed); } /* Check the header fields for validity. * For @len_incl_data < 0 the length is not checked. * Return < 0 if error, else bytes read. */ int check_boothc_header(struct boothc_header *h, int len_incl_data) { int l; if (h->magic != htonl(BOOTHC_MAGIC)) { log_error("magic error %x", ntohl(h->magic)); return -EINVAL; } if (h->version != htonl(BOOTHC_VERSION)) { log_error("version error %x", ntohl(h->version)); return -EINVAL; } l = ntohl(h->length); if (l < sizeof(*h)) { log_error("length %d out of range", l); return -EINVAL; } if (len_incl_data < 0) { return 0; } if (l != len_incl_data) { log_error("length error - got %d, wanted %d", len_incl_data, l); return -EINVAL; } return len_incl_data; } static int do_read(int fd, void *buf, size_t count) { int rv, off = 0; while (off < count) { rv = read(fd, (char *)buf + off, count - off); if (rv == 0) { return -1; } if (rv == -1 && errno == EINTR) { continue; } if (rv == -1 && errno == EWOULDBLOCK) { break; } if (rv == -1) { return -1; } off += rv; } return off; } static int do_write(int fd, void *buf, size_t count) { int rv, off = 0; retry: rv = send(fd, (char *)buf + off, count, MSG_NOSIGNAL); if (rv == -1 && errno == EINTR) { goto retry; } /* If we cannot write _any_ data, we'd be in an (potential) loop. */ if (rv <= 0) { log_error("send failed: %s (%d)", strerror(errno), errno); return rv; } if (rv != count) { count -= rv; off += rv; goto retry; } return 0; } /* Only used for client requests (tcp) */ int read_client(struct client *req_cl) { char *msg; struct boothc_header *header; int rv, fd; int len = MAX_MSG_LEN; if (!req_cl->msg) { msg = malloc(MAX_MSG_LEN); if (!msg) { log_error("out of memory for client messages"); return -1; } memset(msg, 0, MAX_MSG_LEN); req_cl->msg = (void *) msg; } else { msg = (char *) req_cl->msg; } header = (struct boothc_header *) msg; /* update len if we read enough */ if (req_cl->offset >= sizeof(*header)) { len = min(ntohl(header->length), MAX_MSG_LEN); } fd = req_cl->fd; rv = do_read(fd, msg+req_cl->offset, len-req_cl->offset); if (rv < 0) { if (errno == ECONNRESET) { log_debug("client connection reset for fd %d", fd); } return -1; } req_cl->offset += rv; /* update len if we read enough */ if (req_cl->offset >= sizeof(*header)) { len = min(ntohl(header->length), MAX_MSG_LEN); } if (req_cl->offset < len) { /* client promised to send more */ return 1; } if (check_boothc_header(header, len) < 0) { return -1; } return 0; } /* Only used for client requests (tcp) */ static void process_connection(struct booth_config *conf, int ci) { struct client *req_cl; void *msg = NULL; struct boothc_header *header; struct boothc_hdr_msg err_reply; cmd_result_t errc; void (*deadfn) (int ci); req_cl = clients + ci; switch (read_client(req_cl)) { case -1: /* error */ goto kill; case 1: /* more to read */ return; case 0: /* we can process the request now */ msg = req_cl->msg; } header = (struct boothc_header *) msg; if (check_auth(conf, NULL, msg, ntohl(header->length))) { errc = RLT_AUTH; goto send_err; } /* For CMD_GRANT and CMD_REVOKE: * Don't close connection immediately, but send result a second later? */ switch (ntohl(header->cmd)) { case CMD_LIST: ticket_answer_list(conf, req_cl->fd); goto kill; case CMD_PEERS: list_peers(conf, req_cl->fd); goto kill; case CMD_GRANT: case CMD_REVOKE: if (process_client_request(conf, req_cl, msg) == 1) { goto kill; /* request processed definitely, close connection */ } else { return; } case ATTR_LIST: case ATTR_GET: case ATTR_SET: case ATTR_DEL: if (process_attr_request(conf, req_cl, msg) == 1) { goto kill; /* request processed definitely, close connection */ } else { return; } default: log_error("connection %d cmd %x unknown", ci, ntohl(header->cmd)); errc = RLT_INVALID_ARG; goto send_err; } assert(0); return; send_err: init_header(&err_reply.header, CL_RESULT, 0, 0, errc, 0, sizeof(err_reply)); send_client_msg(conf, req_cl->fd, &err_reply); kill: deadfn = req_cl->deadfn; if (deadfn) { deadfn(ci); } } static void process_tcp_listener(struct booth_config *conf, int ci) { + int rc; int fd, i, flags, one = 1; socklen_t addrlen = sizeof(struct sockaddr); struct sockaddr addr; fd = accept(clients[ci].fd, &addr, &addrlen); if (fd < 0) { log_error("process_tcp_listener: accept error %d %d", fd, errno); return; } - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)); + + rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)); + if (rc == -1) { + log_error("process_tcp_listener: setsockopt error %d %d", fd, errno); + close(fd); + return; + } flags = fcntl(fd, F_GETFL, 0); if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { log_error("process_tcp_listener: fcntl O_NONBLOCK error %d %d", fd, errno); close(fd); return; } i = client_add(fd, clients[ci].transport, process_connection, NULL); log_debug("client connection %d fd %d", i, fd); } int setup_tcp_listener(int test_only) { int s, rv; int one = 1; s = socket(local->family, SOCK_STREAM, 0); if (s == -1) { log_error("failed to create tcp socket %s", strerror(errno)); return s; } rv = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); if (rv == -1) { close(s); log_error("failed to set the SO_REUSEADDR option"); return rv; } rv = bind(s, &local->sa6, local->saddrlen); if (test_only) { rv = (rv == -1) ? errno : 0; close(s); return rv; } if (rv == -1) { close(s); log_error("failed to bind socket %s", strerror(errno)); return rv; } rv = listen(s, 5); if (rv == -1) { close(s); log_error("failed to listen on socket %s", strerror(errno)); return rv; } return s; } static int booth_tcp_init(void * unused __attribute__((unused))) { int rv; if (get_local_id() < 0) { return -1; } rv = setup_tcp_listener(0); if (rv < 0) { return rv; } client_add(rv, booth_transport + TCP, process_tcp_listener, NULL); return 0; } static int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int sec) { int flags, n, error; socklen_t len; fd_set rset, wset; struct timeval tval; flags = fcntl(sockfd, F_GETFL, 0); if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) { log_error("fcntl: Can't set sockfd to nonblocking mode"); return -1; } error = 0; n = connect(sockfd, saptr, salen); if (n < 0 && errno != EINPROGRESS) { return -1; } if (n == 0) { goto done; /* connect completed immediately */ } FD_ZERO(&rset); FD_SET(sockfd, &rset); wset = rset; tval.tv_sec = sec; tval.tv_usec = 0; if (select(sockfd + 1, &rset, &wset, NULL, sec ? &tval : NULL) == 0) { errno = ETIMEDOUT; return -1; } if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { len = sizeof(error); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { return -1; /* Solaris pending error */ } } else { log_error("select error: sockfd not set"); return -1; } done: /* restore file status flags */ if (fcntl(sockfd, F_SETFL, flags) == -1) { log_error("fcntl: Can't restore sockfd flags"); return -1; } if (error) { errno = error; return -1; } return 0; } static int booth_tcp_open(struct booth_site *to) { int s, rv; if (to->tcp_fd >= STDERR_FILENO) { goto found; } s = socket(to->family, SOCK_STREAM, 0); if (s == -1) { log_error("cannot create socket of family %d", to->family); return -1; } rv = connect_nonb(s, (struct sockaddr *) &to->sa6, to->saddrlen, 10); if (rv == -1) { if (errno == ETIMEDOUT) { log_error("connect to %s got a timeout", site_string(to)); } else { log_error("connect to %s got an error: %s", site_string(to), strerror(errno)); } goto error; } to->tcp_fd = s; found: return 1; error: if (s >= 0) { close(s); } return -1; } /* data + (datalen-sizeof(struct hmac)) points to struct hmac * i.e. struct hmac is always tacked on the payload */ static int add_hmac(struct booth_config *conf, void *data, int len) { int rv = 0; #if HAVE_LIBGNUTLS || HAVE_LIBGCRYPT || HAVE_LIBMHASH int payload_len; struct hmac *hp; if (!is_auth_req()) { return 0; } payload_len = len - sizeof(struct hmac); hp = (struct hmac *) ((unsigned char *) data + payload_len); hp->hid = htonl(BOOTH_HASH); memset(hp->hash, 0, BOOTH_MAC_SIZE); rv = calc_hmac(data, payload_len, BOOTH_HASH, hp->hash, conf->authkey, conf->authkey_len); if (rv < 0) { log_error("internal error: cannot calculate mac"); } #endif return rv; } static int booth_tcp_send(struct booth_config *conf, struct booth_site *to, void *buf, int len) { int rv; rv = add_hmac(conf, buf, len); if (!rv) { rv = do_write(to->tcp_fd, buf, len); } return rv; } static int booth_tcp_recv(struct booth_site *from, void *buf, int len) { /* Needs timeouts! */ int got = do_read(from->tcp_fd, buf, len); if (got < 0) { log_error("read failed (%d): %s", errno, strerror(errno)); } return got; } static int booth_tcp_recv_auth(struct booth_config *conf, struct booth_site *from, void *buf, int len) { int got, total; int payload_len; /* Needs timeouts! */ payload_len = len - sizeof(struct hmac); got = booth_tcp_recv(from, buf, payload_len); if (got < 0) { return got; } total = got; if (is_auth_req()) { got = booth_tcp_recv(from, (unsigned char *) buf+payload_len, sizeof(struct hmac)); if (got != sizeof(struct hmac) || check_auth(conf, from, buf, len)) { return -1; } total += got; } return total; } static int booth_tcp_close(struct booth_site *to) { if (to) { if (to->tcp_fd > STDERR_FILENO) { close(to->tcp_fd); } to->tcp_fd = -1; } return 0; } static int booth_tcp_exit(void) { return 0; } static int setup_udp_server(void) { int rv, fd; int one = 1; unsigned int recvbuf_size; fd = socket(local->family, SOCK_DGRAM, 0); if (fd == -1) { log_error("failed to create UDP socket %s", strerror(errno)); goto ex; } rv = fcntl(fd, F_SETFL, O_NONBLOCK); if (rv == -1) { log_error("failed to set non-blocking operation on UDP socket: %s", strerror(errno)); goto ex; } rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); if (rv == -1) { log_error("failed to set the SO_REUSEADDR option"); goto ex; } rv = bind(fd, (struct sockaddr *) &local->sa6, local->saddrlen); if (rv == -1) { log_error("failed to bind UDP socket to [%s]:%d: %s", site_string(local), site_port(local), strerror(errno)); goto ex; } recvbuf_size = SOCKET_BUFFER_SIZE; rv = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &recvbuf_size, sizeof(recvbuf_size)); if (rv == -1) { log_error("failed to set recvbuf size"); goto ex; } local->udp_fd = fd; return 0; ex: if (fd >= 0) { close(fd); } return -1; } /* Receive/process callback for UDP */ static void process_recv(struct booth_config *conf, int ci) { struct sockaddr_storage sa; int rv; socklen_t sa_len; /* buffer needs to be large enough to accept a packet */ char buffer[MAX_MSG_LEN]; /* Used for unit tests */ struct boothc_ticket_msg *msg; sa_len = sizeof(sa); msg = (void*) buffer; rv = recvfrom(clients[ci].fd, buffer, sizeof(buffer), MSG_NOSIGNAL | MSG_DONTWAIT, (struct sockaddr *) &sa, &sa_len); if (rv == -1) { return; } rv = deliver_fn(conf, (void*) msg, rv); if (rv > 0) { if (getnameinfo((struct sockaddr *) &sa, sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST) == 0) { log_error("unknown sender: %08x (real: %s)", rv, buffer); } else { log_error("unknown sender: %08x", rv); } } } static int booth_udp_init(void *f) { int rv; rv = setup_udp_server(); if (rv < 0) { return rv; } deliver_fn = f; client_add(local->udp_fd, booth_transport + UDP, process_recv, NULL); return 0; } static int booth_udp_send(struct booth_config *conf, struct booth_site *to, void *buf, int len) { int rv; to->sent_cnt++; rv = sendto(local->udp_fd, buf, len, MSG_NOSIGNAL, (struct sockaddr *) &to->sa6, to->saddrlen); if (rv == len) { rv = 0; } else if (rv < 0) { to->sent_err_cnt++; log_error("Cannot send to %s: %d %s", site_string(to), errno, strerror(errno)); } else { rv = -1; to->sent_err_cnt++; log_error("Packet sent to %s got truncated", site_string(to)); } return rv; } int booth_udp_send_auth(struct booth_config *conf, struct booth_site *to, void *buf, int len) { int rv; rv = add_hmac(conf, buf, len); if (rv < 0) { return rv; } return booth_udp_send(conf, to, buf, len); } static int booth_udp_broadcast_auth(struct booth_config *conf, void *buf, int len) { int i, rv, rvs; struct booth_site *site; if (!conf || !conf->site_count) { return -1; } rv = add_hmac(conf, buf, len); if (rv < 0) { return rv; } rvs = 0; FOREACH_NODE(conf, i, site) { if (site == local) { continue; } rv = booth_udp_send(conf, site, buf, len); if (!rvs) { rvs = rv; } } return rvs; } static int booth_udp_exit(void) { return 0; } /* SCTP transport layer has not been developed yet */ static int booth_sctp_init(void *f __attribute__((unused))) { return 0; } static int booth_sctp_send(struct booth_config *conf __attribute__((unused)), struct booth_site *to __attribute__((unused)), void *buf __attribute__((unused)), int len __attribute__((unused))) { return 0; } static int booth_sctp_broadcast(void *buf __attribute__((unused)), int len __attribute__((unused))) { return 0; } static int return_0_booth_site(struct booth_site *v __attribute((unused))) { return 0; } static int return_0(void) { return 0; } const struct booth_transport booth_transport[TRANSPORT_ENTRIES] = { [TCP] = { .name = "TCP", .init = booth_tcp_init, .open = booth_tcp_open, .send = booth_tcp_send, .recv = booth_tcp_recv, .recv_auth = booth_tcp_recv_auth, .close = booth_tcp_close, .exit = booth_tcp_exit }, [UDP] = { .name = "UDP", .init = booth_udp_init, .open = return_0_booth_site, .send = booth_udp_send, .send_auth = booth_udp_send_auth, .close = return_0_booth_site, .broadcast_auth = booth_udp_broadcast_auth, .exit = booth_udp_exit }, [SCTP] = { .name = "SCTP", .init = booth_sctp_init, .open = return_0_booth_site, .send = booth_sctp_send, .broadcast = booth_sctp_broadcast, .exit = return_0, } }; #if HAVE_LIBGNUTLS || HAVE_LIBGCRYPT || HAVE_LIBMHASH /* TODO: we need some client identification for logging */ #define peer_string(p) (p ? site_string(p) : "client") /* verify the validity of timestamp from the header * the timestamp needs to be either greater than the one already * recorded for the site or, and this is checked for clients, * not to be older than conf->maxtimeskew * update the timestamp for the site, if this packet is from a * site */ static int verify_ts(struct booth_config *conf, struct booth_site *from, void *buf, int len) { struct boothc_header *h; struct timeval tv, curr_tv, now; if (len < sizeof(*h)) { log_error("%s: packet too short", peer_string(from)); return -1; } h = (struct boothc_header *) buf; tv.tv_sec = ntohl(h->secs); tv.tv_usec = ntohl(h->usecs); if (from) { curr_tv.tv_sec = from->last_secs; curr_tv.tv_usec = from->last_usecs; if (timercmp(&tv, &curr_tv, >)) { goto accept; } log_warn("%s: packet timestamp older than previous one", site_string(from)); } gettimeofday(&now, NULL); now.tv_sec -= conf->maxtimeskew; if (timercmp(&tv, &now, >)) { goto accept; } log_error("%s: packet timestamp older than %d seconds", peer_string(from), conf->maxtimeskew); return -1; accept: if (from) { from->last_secs = tv.tv_sec; from->last_usecs = tv.tv_usec; } return 0; } #endif int check_auth(struct booth_config *conf, struct booth_site *from, void *buf, int len) { int rv = 0; #if HAVE_LIBGNUTLS || HAVE_LIBGCRYPT || HAVE_LIBMHASH int payload_len; struct hmac *hp; if (!is_auth_req()) { return 0; } payload_len = len - sizeof(struct hmac); if (payload_len < 0) { log_error("%s: failed to authenticate, packet too short (size:%d)", peer_string(from), len); return -1; } hp = (struct hmac *) ((unsigned char *) buf + payload_len); rv = verify_hmac(buf, payload_len, ntohl(hp->hid), hp->hash, conf->authkey, conf->authkey_len); if (!rv) { rv = verify_ts(conf, from, buf, len); } if (rv != 0) { log_error("%s: failed to authenticate", peer_string(from)); } #endif return rv; } int send_data(struct booth_config *conf, int fd, void *data, int datalen) { int rv = 0; rv = add_hmac(conf, data, datalen); if (!rv) { rv = do_write(fd, data, datalen); } return rv; } int send_header_plus(struct booth_config *conf, int fd, struct boothc_hdr_msg *msg, void *data, int len) { int rv; rv = send_data(conf, fd, msg, sendmsglen(msg)-len); if (rv >= 0 && len) { rv = do_write(fd, data, len); } return rv; } /* UDP message receiver (see also deliver_fn declaration's comment) */ int message_recv(struct booth_config *conf, void *msg, int msglen) { uint32_t from; struct boothc_header *header = msg; struct booth_site *source; from = ntohl(header->from); if (!find_site_by_id(conf, from, &source)) { /* caller knows the actual source address, pass the (assuredly) positive number and let it report */ from = from ? from : ~from; /* avoid 0 (success) */ return from & (~0U >> 1); /* avoid negative (error code} */ } time(&source->last_recv); source->recv_cnt++; if (check_boothc_header(header, msglen) < 0) { log_error("message from %s receive error", site_string(source)); source->recv_err_cnt++; return -1; } if (check_auth(conf, source, msg, msglen)) { log_error("%s failed to authenticate", site_string(source)); source->sec_cnt++; return -1; } if (ntohl(header->opts) & BOOTH_OPT_ATTR) { /* not used, clients send/retrieve attributes directly from sites */ return attr_recv(conf, msg, source); } else { return ticket_recv(conf, msg, source); } }