/* * ProFTPD - mod_proxy_protocol * Copyright (c) 2013-2020 TJ Saunders * * 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 program 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, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "conf.h" #include "privs.h" #ifdef HAVE_SYS_UIO_H # include #endif /* HAVE_SYS_UIO_H */ #define MOD_PROXY_PROTOCOL_VERSION "mod_proxy_protocol/0.2" /* Make sure the version of proftpd is as necessary. */ #if PROFTPD_VERSION_NUMBER < 0x0001030504 # error "ProFTPD 1.3.5rc4 or later required" #endif module proxy_protocol_module; #define PROXY_PROTOCOL_BUFSZ 128 #define PROXY_PROTOCOL_TIMEOUT_DEFAULT 3 static int proxy_protocol_timeout = PROXY_PROTOCOL_TIMEOUT_DEFAULT; #define PROXY_PROTOCOL_VERSION_HAPROXY_V1 1 #define PROXY_PROTOCOL_VERSION_HAPROXY_V2 2 static unsigned int proxy_protocol_version = PROXY_PROTOCOL_VERSION_HAPROXY_V1; static const char *trace_channel = "proxy_protocol"; static int poll_sock(int sockfd) { fd_set rfds; int res = 0; struct timeval tv; memset(&tv, 0, sizeof(tv)); tv.tv_sec = proxy_protocol_timeout; tv.tv_usec = 0; pr_trace_msg(trace_channel, 19, "waiting for max of %lu secs while polling socket %d using select(2)", (unsigned long) tv.tv_sec, sockfd); while (TRUE) { pr_signals_handle(); FD_ZERO(&rfds); FD_SET(sockfd, &rfds); res = select(sockfd + 1, &rfds, NULL, NULL, &tv); if (res < 0) { int xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); continue; } pr_trace_msg(trace_channel, 18, "error calling select(2) on fd %d: %s", sockfd, strerror(xerrno)); errno = xerrno; return -1; } else if (res == 0) { memset(&tv, 0, sizeof(tv)); tv.tv_sec = proxy_protocol_timeout; tv.tv_usec = 0; pr_trace_msg(trace_channel, 18, "polling on socket %d timed out after %lu sec, trying again", sockfd, (unsigned long) tv.tv_sec); continue; } break; } return 0; } static int read_sock(int sockfd, void *buf, size_t reqlen) { void *ptr = NULL; size_t remainlen = 0; if (reqlen == 0) { return 0; } errno = 0; ptr = buf; remainlen = reqlen; while (remainlen > 0) { int res, xerrno = 0; if (poll_sock(sockfd) < 0) { return -1; } res = read(sockfd, ptr, remainlen); xerrno = errno; while (res <= 0) { if (res < 0) { if (xerrno == EINTR) { pr_signals_handle(); continue; } pr_trace_msg(trace_channel, 16, "error reading from client (fd %d): %s", sockfd, strerror(xerrno)); pr_log_debug(DEBUG5, MOD_PROXY_PROTOCOL_VERSION ": error reading from client (fd %d): %s", sockfd, strerror(xerrno)); /* We explicitly disconnect the client here because the errors below * all indicate a problem with the TCP connection. */ if (xerrno == ECONNRESET || xerrno == ECONNABORTED || #ifdef ETIMEDOUT xerrno == ETIMEDOUT || #endif /* ETIMEDOUT */ #ifdef ENOTCONN xerrno == ENOTCONN || #endif /* ENOTCONN */ #ifdef ESHUTDOWN xerrno == ESHUTDOWN || #endif /* ESHUTDOWNN */ xerrno == EPIPE) { errno = xerrno; pr_trace_msg(trace_channel, 16, "disconnecting client (%s)", strerror(xerrno)); pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": disconnecting client (%s)", strerror(xerrno)); pr_session_disconnect(&proxy_protocol_module, PR_SESS_DISCONNECT_CLIENT_EOF, strerror(xerrno)); } return -1; } else { /* If we read zero bytes here, treat it as an EOF and hang up on * the uncommunicative client. */ pr_trace_msg(trace_channel, 16, "%s", "disconnecting client (received EOF)"); pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": disconnecting client (received EOF)"); pr_session_disconnect(&proxy_protocol_module, PR_SESS_DISCONNECT_CLIENT_EOF, NULL); } } /* Generate an event for any interested listeners. */ pr_event_generate("core.ctrl-read", buf); session.total_raw_in += reqlen; if (res == remainlen) { break; } pr_trace_msg(trace_channel, 20, "read %lu bytes, expected %lu bytes; " "reading more", (unsigned long) res, (unsigned long) remainlen); ptr = ((char *) ptr + res); remainlen -= res; } return reqlen; } static int readv_sock(int sockfd, const struct iovec *iov, int count) { int res, xerrno = 0; if (poll_sock(sockfd) < 0) { return -1; } res = readv(sockfd, iov, count); xerrno = errno; while (res <= 0) { if (res < 0) { if (xerrno == EINTR) { pr_signals_handle(); if (poll_sock(sockfd) < 0) { return -1; } res = readv(sockfd, iov, count); xerrno = errno; continue; } pr_trace_msg(trace_channel, 16, "error reading from client (fd %d): %s", sockfd, strerror(xerrno)); pr_log_debug(DEBUG5, MOD_PROXY_PROTOCOL_VERSION ": error reading from client (fd %d): %s", sockfd, strerror(xerrno)); /* We explicitly disconnect the client here because the errors below * all indicate a problem with the TCP connection. */ if (xerrno == ECONNRESET || xerrno == ECONNABORTED || #ifdef ETIMEDOUT xerrno == ETIMEDOUT || #endif /* ETIMEDOUT */ #ifdef ENOTCONN xerrno == ENOTCONN || #endif /* ENOTCONN */ #ifdef ESHUTDOWN xerrno == ESHUTDOWN || #endif /* ESHUTDOWNN */ xerrno == EPIPE) { pr_trace_msg(trace_channel, 16, "disconnecting client (%s)", strerror(xerrno)); pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": disconnecting client (%s)", strerror(xerrno)); pr_session_disconnect(&proxy_protocol_module, PR_SESS_DISCONNECT_CLIENT_EOF, strerror(xerrno)); return -1; } /* If we read zero bytes here, treat it as an EOF and hang up on * the uncommunicative client. */ pr_trace_msg(trace_channel, 16, "%s", "disconnecting client (received EOF)"); pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": disconnecting client (received EOF)"); pr_session_disconnect(&proxy_protocol_module, PR_SESS_DISCONNECT_CLIENT_EOF, NULL); errno = ENOENT; return -1; } } session.total_raw_in += res; return res; } static unsigned int strtou(const char **str, const char *last) { const char *ptr = *str; unsigned int i = 0, j, k; while (ptr < last) { pr_signals_handle(); j = *ptr - '0'; k = i * 10; if (j > 9) { break; } i = k + j; ptr++; } *str = ptr; return i; } /* This function waits a PROXY protocol header at the beginning of the * raw data stream. The header looks like this : * * "PROXY" PROTO SRC3 DST3 SRC4 "\r\n" * * There must be exactly one space between each field. Fields are : * * - PROTO: layer 4 protocol, which must be "TCP4" or "TCP6". * - SRC3: layer 3 (e.g. IP) source address in standard text form * - DST3: layer 3 (e.g. IP) destination address in standard text form * - SRC4: layer 4 (e.g. TCP port) source address in standard text form * - DST4: layer 4 (e.g. TCP port) destination address in standard text form */ static int read_haproxy_v1(pool *p, conn_t *conn, const pr_netaddr_t **proxied_addr, unsigned int *proxied_port) { register unsigned int i; char buf[PROXY_PROTOCOL_BUFSZ], *last = NULL, *ptr = NULL; int have_cr = FALSE, have_nl = FALSE, have_tcp4 = FALSE, have_tcp6 = FALSE; size_t buflen = 0; /* Read until we find the expected PROXY string. */ memset(buf, '\0', sizeof(buf)); ptr = buf; for (i = 0; i < sizeof(buf)-1; i++) { int res, xerrno; pr_signals_handle(); res = read_sock(conn->rfd, &buf[i], 1); xerrno = errno; while (res <= 0) { if (xerrno == EINTR) { pr_signals_handle(); res = read_sock(conn->rfd, &buf[i], 1); xerrno = errno; continue; } if (res < 0) { pr_log_debug(DEBUG5, MOD_PROXY_PROTOCOL_VERSION ": error reading from client socket: %s", strerror(xerrno)); errno = xerrno; return -1; } } /* Decode a possible PROXY request as early as we can, and fail * early if it does not match. */ if (i == 6) { if (strncmp(ptr, "PROXY ", 6) != 0) { goto bad_proto; } ptr += 6; } /* We continue reading until the client has sent the terminating * CRLF sequence. */ if (buf[i] == '\r') { have_cr = TRUE; buf[i] = '\0'; continue; } if (have_cr == TRUE && buf[i] == '\n') { have_nl = TRUE; buf[i] = '\0'; break; } buflen++; } buf[sizeof(buf)-1] = '\0'; pr_trace_msg(trace_channel, 7, "read %lu bytes of proxy data (minus CRLF): '%.100s'", (unsigned long) buflen, buf); if (have_nl == FALSE) { pr_log_debug(DEBUG5, MOD_PROXY_PROTOCOL_VERSION ": missing expected CRLF termination"); goto bad_proto; } if (buflen == 0) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": missing expected PROXY protocol data"); goto bad_proto; } last = buf + buflen; /* Check the PROTO field: "TCP4" or "TCP6" are supported. */ if (strncmp(ptr, "TCP4 ", 5) == 0) { have_tcp4 = TRUE; #ifdef PR_USE_IPV6 } else if (strncmp(ptr, "TCP6 ", 5) == 0) { if (pr_netaddr_use_ipv6()) { have_tcp6 = TRUE; } #endif /* PR_USE_IPV6 */ } if (have_tcp4 || have_tcp6) { const pr_netaddr_t *src_addr = NULL, *dst_addr = NULL; char *ptr2 = NULL; unsigned int src_port, dst_port; int flags = PR_NETADDR_GET_ADDR_FL_EXCL_DNS; ptr += 5; ptr2 = strchr(ptr, ' '); if (ptr2 == NULL) { goto bad_proto; } *ptr2 = '\0'; pr_trace_msg(trace_channel, 9, "resolving source address field '%s'", ptr); src_addr = pr_netaddr_get_addr2(p, ptr, NULL, flags); if (src_addr == NULL) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": unable to resolve source address '%s': %s", ptr, strerror(errno)); *ptr2 = ' '; goto bad_proto; } else { *ptr2 = ' '; pr_trace_msg(trace_channel, 9, "resolve source address '%s': %s", ptr, pr_netaddr_get_ipstr(src_addr)); } ptr = ptr2 + 1; ptr2 = strchr(ptr, ' '); if (ptr2 == NULL) { goto bad_proto; } *ptr2 = '\0'; pr_trace_msg(trace_channel, 9, "resolving destination address field '%s'", ptr); dst_addr = pr_netaddr_get_addr2(p, ptr, NULL, flags); if (dst_addr == NULL) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": unable to resolve destination address '%s': %s", ptr, strerror(errno)); *ptr2 = ' '; goto bad_proto; } else { *ptr2 = ' '; pr_trace_msg(trace_channel, 9, "resolve destination address '%s': %s", ptr, pr_netaddr_get_ipstr(dst_addr)); } /* Check the address family against what the PROTO field says it should * be. This is to pedantically guard against IPv6 addresses in a * "TCP4" PROXY line, or IPv4 addresses in a "TCP6" line. */ /* TODO: Technically, it's possible that the remote client sent us DNS * names, rather than IP addresses, and we resolved them. To pedantically * check for this, scan the given address fields for illegal (e.g. * alphabetic) characters, keeping in mind that IPv6 addresses can use * hex. */ if (have_tcp4) { if (pr_netaddr_get_family(src_addr) != AF_INET) { pr_log_debug(DEBUG8, MOD_PROXY_PROTOCOL_VERSION ": expected IPv4 source address for '%s', got IPv6", pr_netaddr_get_ipstr(src_addr)); errno = EINVAL; return -1; } if (pr_netaddr_get_family(dst_addr) != AF_INET) { pr_log_debug(DEBUG8, MOD_PROXY_PROTOCOL_VERSION ": expected IPv4 destination address for '%s', got IPv6", pr_netaddr_get_ipstr(dst_addr)); errno = EINVAL; return -1; } #ifdef PR_USE_IPV6 } else { if (pr_netaddr_get_family(src_addr) != AF_INET6) { pr_log_debug(DEBUG8, MOD_PROXY_PROTOCOL_VERSION ": expected IPv6 source address for '%s', got IPv4", pr_netaddr_get_ipstr(src_addr)); errno = EINVAL; return -1; } if (pr_netaddr_get_family(dst_addr) != AF_INET6) { pr_log_debug(DEBUG8, MOD_PROXY_PROTOCOL_VERSION ": expected IPv6 destination address for '%s', got IPv4", pr_netaddr_get_ipstr(dst_addr)); errno = EINVAL; return -1; } /* Handle IPv4-mapped IPv6 addresses as IPv4 addresses. */ if (pr_netaddr_is_v4mappedv6(src_addr) == TRUE) { src_addr = pr_netaddr_v6tov4(p, src_addr); } if (pr_netaddr_is_v4mappedv6(dst_addr) == TRUE) { dst_addr = pr_netaddr_v6tov4(p, dst_addr); } #endif /* PR_USE_IPV6 */ } ptr = ptr2 + 1; ptr2 = strchr(ptr, ' '); if (ptr2 == NULL) { goto bad_proto; } *ptr2 = '\0'; pr_trace_msg(trace_channel, 9, "resolving source port field '%s'", ptr); src_port = strtou((const char **) &ptr, last); if (src_port == 0) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": invalid source port '%s' provided", ptr); *ptr2 = ' '; goto bad_proto; } else { *ptr2 = ' '; pr_trace_msg(trace_channel, 9, "resolved source port: %u", src_port); } if (src_port > 65535) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": out-of-range source port provided: %u", src_port); goto bad_proto; } ptr = ptr2 + 1; pr_trace_msg(trace_channel, 9, "resolving destination port field '%s'", ptr); dst_port = strtou((const char **) &ptr, last); if (dst_port == 0) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": invalid destination port '%s' provided", ptr); *ptr2 = ' '; goto bad_proto; } else { *ptr2 = ' '; pr_trace_msg(trace_channel, 9, "resolved destination port: %u", dst_port); } if (dst_port > 65535) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": out-of-range destination port provided: %u", dst_port); goto bad_proto; } if (ptr > last) { goto bad_proto; } /* Paranoidly check the given source address/port against the * destination address/port. If the two tuples match, then the remote * client is lying to us (it's not possible to have a TCP connection * FROM an address/port tuple which is identical to the destination * address/port tuple). */ if (pr_netaddr_cmp(src_addr, dst_addr) == 0 && src_port == dst_port) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": source/destination address/port are IDENTICAL: %s#%u", pr_netaddr_get_ipstr(src_addr), src_port); goto bad_proto; } /* Set the source port for the source address. */ pr_netaddr_set_port((pr_netaddr_t *) src_addr, htons(src_port)); *proxied_addr = src_addr; *proxied_port = src_port; } else if (strncmp(ptr, "UNKNOWN", 7) == 0) { pr_log_debug(DEBUG5, MOD_PROXY_PROTOCOL_VERSION ": client cannot provide proxied address: '%.100s'", buf); errno = ENOENT; return 0; } else { pr_log_debug(DEBUG5, MOD_PROXY_PROTOCOL_VERSION ": unknown/unsupported PROTO field"); goto bad_proto; } return 1; bad_proto: pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": Bad/unsupported proxy protocol data '%.100s' from %s", buf, pr_netaddr_get_ipstr(conn->remote_addr)); errno = EINVAL; return -1; } static const char haproxy_v2_sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; static int read_haproxy_v2(pool *p, conn_t *conn, const pr_netaddr_t **proxied_addr, unsigned int *proxied_port) { int res = 0; uint8_t v2_sig[12], ver_cmd, trans_fam; uint16_t v2_len; struct iovec v2_hdr[4]; const pr_netaddr_t *src_addr = NULL, *dst_addr = NULL; v2_hdr[0].iov_base = (void *) v2_sig; v2_hdr[0].iov_len = sizeof(v2_sig); v2_hdr[1].iov_base = (void *) &ver_cmd; v2_hdr[1].iov_len = sizeof(ver_cmd); v2_hdr[2].iov_base = (void *) &trans_fam; v2_hdr[2].iov_len = sizeof(trans_fam); v2_hdr[3].iov_base = (void *) &v2_len; v2_hdr[3].iov_len = sizeof(v2_len); res = readv_sock(conn->rfd, v2_hdr, 4); if (res < 0) { return -1; } if (res != 16) { pr_trace_msg(trace_channel, 20, "read %lu V2 bytes, expected %lu bytes", (unsigned long) res, (unsigned long) 16); errno = EPERM; return -1; } /* Validate the obtained data. */ if (memcmp(v2_sig, haproxy_v2_sig, sizeof(haproxy_v2_sig)) != 0) { pr_trace_msg(trace_channel, 3, "invalid proxy protocol V2 signature, rejecting"); errno = EINVAL; return -1; } if ((ver_cmd & 0xF0) != 0x20) { errno = EINVAL; return -1; } switch (ver_cmd & 0xF) { /* PROXY command */ case 0x01: switch (trans_fam) { /* TCP, IPv4 */ case 0x11: { uint32_t src_ipv4, dst_ipv4; uint16_t src_port, dst_port; struct iovec ipv4[4]; struct sockaddr_in *saddr; pr_trace_msg(trace_channel, 17, "received proxy protocol V2 TCP/IPv4 transport family (%lu bytes)", (unsigned long) ntohs(v2_len)); if (ntohs(v2_len) != 12) { pr_trace_msg(trace_channel, 3, "proxy protocol V2 TCP/IPv4 transport family sent %lu bytes, " "expected %lu bytes", (unsigned long) ntohs(v2_len), (unsigned long) 12); errno = EINVAL; return -1; } ipv4[0].iov_base = (void *) &src_ipv4; ipv4[0].iov_len = sizeof(src_ipv4); ipv4[1].iov_base = (void *) &dst_ipv4; ipv4[1].iov_len = sizeof(dst_ipv4); ipv4[2].iov_base = (void *) &src_port; ipv4[2].iov_len = sizeof(src_port); ipv4[3].iov_base = (void *) &dst_port; ipv4[3].iov_len = sizeof(dst_port); res = readv_sock(conn->rfd, ipv4, 4); if (res < 0) { return -1; } src_addr = pr_netaddr_alloc(p); pr_netaddr_set_family((pr_netaddr_t *) src_addr, AF_INET); saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(src_addr); saddr->sin_family = AF_INET; saddr->sin_addr.s_addr = src_ipv4; saddr->sin_port = src_port; pr_netaddr_set_port((pr_netaddr_t *) src_addr, src_port); dst_addr = pr_netaddr_alloc(p); pr_netaddr_set_family((pr_netaddr_t *) dst_addr, AF_INET); saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(dst_addr); saddr->sin_family = AF_INET; saddr->sin_addr.s_addr = dst_ipv4; saddr->sin_port = dst_port; pr_netaddr_set_port((pr_netaddr_t *) dst_addr, dst_port); pr_trace_msg(trace_channel, 17, "received proxy protocol V2 TCP/IPv4 transport family: " "source address %s#%d, destination address %s#%d", pr_netaddr_get_ipstr(src_addr), ntohs(pr_netaddr_get_port(src_addr)), pr_netaddr_get_ipstr(dst_addr), ntohs(pr_netaddr_get_port(dst_addr))); break; } /* TCP, IPv6 */ case 0x21: { uint8_t src_ipv6[16], dst_ipv6[16]; uint16_t src_port, dst_port; struct iovec ipv6[4]; struct sockaddr_in6 *saddr; pr_trace_msg(trace_channel, 17, "received proxy protocol V2 TCP/IPv6 transport family (%lu bytes)", (unsigned long) ntohs(v2_len)); if (ntohs(v2_len) != 36) { pr_trace_msg(trace_channel, 3, "proxy protocol V2 TCP/IPv6 transport family sent %lu bytes, " "expected %lu bytes", (unsigned long) ntohs(v2_len), (unsigned long) 36); errno = EINVAL; return -1; } #ifdef PR_USE_IPV6 ipv6[0].iov_base = (void *) &src_ipv6; ipv6[0].iov_len = sizeof(src_ipv6); ipv6[1].iov_base = (void *) &dst_ipv6; ipv6[1].iov_len = sizeof(dst_ipv6); ipv6[2].iov_base = (void *) &src_port; ipv6[2].iov_len = sizeof(src_port); ipv6[3].iov_base = (void *) &dst_port; ipv6[3].iov_len = sizeof(dst_port); res = readv_sock(conn->rfd, ipv6, 4); if (res < 0) { return -1; } src_addr = pr_netaddr_alloc(p); pr_netaddr_set_family((pr_netaddr_t *) src_addr, AF_INET6); saddr = (struct sockaddr_in6 *) pr_netaddr_get_sockaddr(src_addr); saddr->sin6_family = AF_INET6; memcpy(&(saddr->sin6_addr), src_ipv6, sizeof(src_ipv6)); saddr->sin6_port = src_port; pr_netaddr_set_port((pr_netaddr_t *) src_addr, src_port); dst_addr = pr_netaddr_alloc(p); pr_netaddr_set_family((pr_netaddr_t *) dst_addr, AF_INET6); saddr = (struct sockaddr_in6 *) pr_netaddr_get_sockaddr(dst_addr); saddr->sin6_family = AF_INET6; memcpy(&(saddr->sin6_addr), dst_ipv6, sizeof(dst_ipv6)); saddr->sin6_port = dst_port; pr_netaddr_set_port((pr_netaddr_t *) dst_addr, dst_port); /* Handle IPv4-mapped IPv6 addresses as IPv4 addresses. */ if (pr_netaddr_is_v4mappedv6(src_addr) == TRUE) { src_addr = pr_netaddr_v6tov4(p, src_addr); } if (pr_netaddr_is_v4mappedv6(dst_addr) == TRUE) { dst_addr = pr_netaddr_v6tov4(p, dst_addr); } pr_trace_msg(trace_channel, 17, "received proxy protocol V2 TCP/IPv6 transport family: " "source address %s#%d, destination address %s#%d", pr_netaddr_get_ipstr(src_addr), ntohs(pr_netaddr_get_port(src_addr)), pr_netaddr_get_ipstr(dst_addr), ntohs(pr_netaddr_get_port(dst_addr))); #else /* Avoid compiler warnings about unused variables. */ (void) src_ipv6; (void) dst_ipv6; (void) src_port; (void) dst_port; (void) ipv6; (void) saddr; pr_trace_msg(trace_channel, 3, "IPv6 support disabled, ignoring proxy protocol V2 data"); #endif /* PR_USE_IPV6 */ break; } /* Unix */ case 0x31: { unsigned char src_path[108]; unsigned char dst_path[108]; pr_trace_msg(trace_channel, 17, "received proxy protocol V2 Unix transport family " "(%lu bytes), ignoring", (unsigned long) ntohs(v2_len)); if (ntohs(v2_len) != 216) { pr_trace_msg(trace_channel, 3, "proxy protocol V2 Unix transport family sent %lu bytes, " "expected %lu bytes", (unsigned long) ntohs(v2_len), (unsigned long) 216); errno = EINVAL; return -1; } res = read_sock(conn->rfd, src_path, sizeof(src_path)); if (res > 0) { pr_trace_msg(trace_channel, 15, "received proxy protocol V2 Unix source path: '%s'", src_path); } res = read_sock(conn->rfd, dst_path, sizeof(dst_path)); if (res > 0) { pr_trace_msg(trace_channel, 15, "received proxy protocol V2 Unix destination path: '%s'", dst_path); } break; } /* Unspecified */ case 0x00: { pool *tmp_pool; unsigned char *buf; size_t buflen; buflen = ntohs(v2_len); pr_trace_msg(trace_channel, 17, "received proxy protocol V2 unspecified transport family " "(%lu bytes), ignoring", (unsigned long) buflen); tmp_pool = make_sub_pool(p); buf = palloc(tmp_pool, buflen); (void) read_sock(conn->rfd, buf, buflen); destroy_pool(tmp_pool); break; } default: pr_trace_msg(trace_channel, 3, "unsupported proxy protocol V2 transport family: %u", trans_fam); errno = EINVAL; return -1; } break; /* LOCAL command */ case 0x00: /* Keep local connection address for LOCAL commands. */ pr_trace_msg(trace_channel, 17, "received proxy protocol V2 LOCAL command, ignoring"); return 0; default: pr_trace_msg(trace_channel, 3, "unsupported proxy protocol V2 command: %u", ver_cmd); errno = EINVAL; return -1; } if (src_addr == NULL && dst_addr == NULL) { return 0; } if (ntohs(pr_netaddr_get_port(dst_addr)) > 65535) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": out-of-range destination port provided: %u", ntohs(pr_netaddr_get_port(dst_addr))); errno = EINVAL; return -1; } /* Paranoidly check the given source address/port against the * destination address/port. If the two tuples match, then the remote * client is lying to us (it's not possible to have a TCP connection * FROM an address/port tuple which is identical to the destination * address/port tuple). */ if (pr_netaddr_cmp(src_addr, dst_addr) == 0 && pr_netaddr_get_port(src_addr) == pr_netaddr_get_port(dst_addr)) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": source/destination address/port are IDENTICAL: %s#%u", pr_netaddr_get_ipstr(src_addr), ntohs(pr_netaddr_get_port(src_addr))); errno = EPERM; return -1; } *proxied_addr = src_addr; *proxied_port = ntohs(pr_netaddr_get_port(src_addr)); return 0; } static int read_proxied_addr(pool *p, conn_t *conn, const pr_netaddr_t **proxied_addr, unsigned int *proxied_port) { int res; /* Note that in theory, we could auto-detect the protocol version. */ switch (proxy_protocol_version) { case PROXY_PROTOCOL_VERSION_HAPROXY_V1: res = read_haproxy_v1(p, conn, proxied_addr, proxied_port); break; case PROXY_PROTOCOL_VERSION_HAPROXY_V2: res = read_haproxy_v2(p, conn, proxied_addr, proxied_port); break; default: errno = ENOSYS; res = -1; } return res; } static int proxy_protocol_timeout_cb(CALLBACK_FRAME) { pr_event_generate("proxy_protocol.timeout", NULL); pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": proxy protocol timeout (%d %s) reached, disconnecting client", proxy_protocol_timeout, proxy_protocol_timeout != 1 ? "secs" : "sec"); pr_session_disconnect(&proxy_protocol_module, PR_SESS_DISCONNECT_TIMEOUT, "ProxyProtocolTimeout"); return 0; } /* Configuration handlers */ /* usage: ProxyProtocolEngine on|off */ MODRET set_proxyprotocolengine(cmd_rec *cmd) { int engine = 0; config_rec *c; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); engine = get_boolean(cmd, 1); if (engine == -1) { CONF_ERROR(cmd, "expected Boolean parameter"); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = engine; return PR_HANDLED(cmd); } /* usage: ProxyProtocolTimeout nsecs */ MODRET set_proxyprotocoltimeout(cmd_rec *cmd) { int timeout = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (pr_str_get_duration(cmd->argv[1], &timeout) < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '", cmd->argv[1], "': ", strerror(errno), NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = timeout; return PR_HANDLED(cmd); } /* usage: ProxyProtocolVersion protocol */ MODRET set_proxyprotocolversion(cmd_rec *cmd) { int proto_version = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (strcasecmp(cmd->argv[1], "haproxyV1") == 0) { proto_version = PROXY_PROTOCOL_VERSION_HAPROXY_V1; } else if (strcasecmp(cmd->argv[1], "haproxyV2") == 0) { proto_version = PROXY_PROTOCOL_VERSION_HAPROXY_V2; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown protocol/version: ", cmd->argv[1], NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = proto_version; return PR_HANDLED(cmd); } /* Initialization routines */ static int proxy_protocol_sess_init(void) { config_rec *c; int engine = 0, res = 0, timerno = -1, xerrno; const pr_netaddr_t *proxied_addr = NULL; unsigned int proxied_port = 0; const char *remote_ip = NULL, *remote_name = NULL; pr_netio_t *tls_netio = NULL; c = find_config(main_server->conf, CONF_PARAM, "ProxyProtocolEngine", FALSE); if (c != NULL) { engine = *((int *) c->argv[0]); } if (engine == FALSE) { return 0; } c = find_config(main_server->conf, CONF_PARAM, "ProxyProtocolTimeout", FALSE); if (c != NULL) { proxy_protocol_timeout = *((int *) c->argv[0]); } c = find_config(main_server->conf, CONF_PARAM, "ProxyProtocolVersion", FALSE); if (c != NULL) { proxy_protocol_version = *((int *) c->argv[0]); } if (proxy_protocol_timeout > 0) { timerno = pr_timer_add(proxy_protocol_timeout, -1, &proxy_protocol_module, proxy_protocol_timeout_cb, "ProxyProtocolTimeout"); } /* If the mod_tls module is in effect, then we need to work around its * use of the NetIO API. Otherwise, trying to read the proxied address * on the control connection will cause problems, e.g. for FTPS clients * using implicit TLS. */ tls_netio = pr_get_netio(PR_NETIO_STRM_CTRL); if (tls_netio == NULL || tls_netio->owner_name == NULL || strncmp(tls_netio->owner_name, "tls", 4) != 0) { /* Not a mod_tls netio; ignore it. */ tls_netio = NULL; } else { /* Unregister it; we'll put it back after reading the proxied address. */ pr_unregister_netio(PR_NETIO_STRM_CTRL); } res = read_proxied_addr(session.pool, session.c, &proxied_addr, &proxied_port); xerrno = errno; if (tls_netio != NULL) { if (pr_register_netio(tls_netio, PR_NETIO_STRM_CTRL) < 0) { pr_log_debug(DEBUG1, MOD_PROXY_PROTOCOL_VERSION ": unable to re-register TLS control NetIO: %s", strerror(errno)); } } if (proxy_protocol_timeout > 0) { pr_timer_remove(timerno, &proxy_protocol_module); } if (res < 0) { pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": error reading proxy info: %s", strerror(xerrno)); errno = EPERM; return -1; } if (proxied_addr != NULL) { remote_ip = pstrdup(session.pool, pr_netaddr_get_ipstr(pr_netaddr_get_sess_remote_addr())); remote_name = pstrdup(session.pool, pr_netaddr_get_sess_remote_name()); pr_log_debug(DEBUG9, MOD_PROXY_PROTOCOL_VERSION ": using proxied source address: %s", pr_netaddr_get_ipstr(proxied_addr)); session.c->remote_addr = proxied_addr; session.c->remote_port = proxied_port; /* Now perform reverse DNS lookups. */ if (ServerUseReverseDNS) { int reverse_dns; reverse_dns = pr_netaddr_set_reverse_dns(ServerUseReverseDNS); session.c->remote_name = pr_netaddr_get_dnsstr(session.c->remote_addr); pr_netaddr_set_reverse_dns(reverse_dns); } else { session.c->remote_name = pr_netaddr_get_ipstr(session.c->remote_addr); } pr_netaddr_set_sess_addrs(); pr_log_debug(DEBUG0, MOD_PROXY_PROTOCOL_VERSION ": UPDATED client remote address/name: %s/%s (WAS %s/%s)", pr_netaddr_get_ipstr(pr_netaddr_get_sess_remote_addr()), pr_netaddr_get_sess_remote_name(), remote_ip, remote_name); /* Find the new class for this session. */ session.conn_class = pr_class_match_addr(session.c->remote_addr); if (session.conn_class != NULL) { pr_log_debug(DEBUG2, MOD_PROXY_PROTOCOL_VERSION ": session requested from proxied client in '%s' class", session.conn_class->cls_name); } else { pr_log_debug(DEBUG5, MOD_PROXY_PROTOCOL_VERSION ": session requested from proxied client in unknown class"); } } return 0; } /* Module API tables */ static conftable proxy_protocol_conftab[] = { { "ProxyProtocolEngine", set_proxyprotocolengine, NULL }, { "ProxyProtocolTimeout", set_proxyprotocoltimeout, NULL }, { "ProxyProtocolVersion", set_proxyprotocolversion, NULL }, { NULL } }; module proxy_protocol_module = { /* Always NULL */ NULL, NULL, /* Module API version */ 0x20, /* Module name */ "proxy_protocol", /* Module configuration handler table */ proxy_protocol_conftab, /* Module command handler table */ NULL, /* Module authentication handler table */ NULL, /* Module initialization */ NULL, /* Session initialization */ proxy_protocol_sess_init, /* Module version */ MOD_PROXY_PROTOCOL_VERSION };