diff --git a/Makefile.am b/Makefile.am index 5a08de209d..245d7cf199 100644 --- a/Makefile.am +++ b/Makefile.am @@ -543,6 +543,7 @@ dist_noinst_HEADERS = \ ldap/servers/slapd/getopt_ext.h \ ldap/servers/slapd/getsocketpeer.h \ ldap/servers/slapd/http.h \ + ldap/servers/slapd/haproxy.h \ ldap/servers/slapd/intrinsics.h \ ldap/servers/slapd/log.h \ ldap/servers/slapd/openldapber.h \ @@ -1537,6 +1538,7 @@ libslapd_la_SOURCES = ldap/servers/slapd/add.c \ ldap/servers/slapd/filterentry.c \ ldap/servers/slapd/generation.c \ ldap/servers/slapd/getfilelist.c \ + ldap/servers/slapd/haproxy.c \ ldap/servers/slapd/ldaputil.c \ ldap/servers/slapd/lenstr.c \ ldap/servers/slapd/libglobs.c \ @@ -2408,6 +2410,7 @@ test_slapd_SOURCES = test/main.c \ test/libslapd/schema/filter_validate.c \ test/libslapd/operation/v3_compat.c \ test/libslapd/spal/meminfo.c \ + test/libslapd/haproxy/parse.c \ test/plugins/test.c \ test/plugins/pwdstorage/pbkdf2.c diff --git a/dirsrvtests/tests/suites/basic/haproxy_test.py b/dirsrvtests/tests/suites/basic/haproxy_test.py new file mode 100644 index 0000000000..08db0b50ea --- /dev/null +++ b/dirsrvtests/tests/suites/basic/haproxy_test.py @@ -0,0 +1,96 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2023 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +import ldap +import logging +import pytest +from lib389._constants import DEFAULT_SUFFIX, PASSWORD +from lib389.topologies import topology_st as topo +from lib389.idm.user import UserAccount, UserAccounts +from lib389.idm.account import Anonymous + +log = logging.getLogger(__name__) +DN = "uid=common,ou=people," + DEFAULT_SUFFIX +HOME_DIR = '/home/common' + +@pytest.fixture(scope="function") +def setup_test(topo, request): + """Setup test environment""" + log.info("Add nsslapd-haproxy-trusted-ip attribute") + topo.standalone.config.set('nsslapd-haproxy-trusted-ip', '192.168.0.1') + assert topo.standalone.config.present('nsslapd-haproxy-trusted-ip', '192.168.0.1') + + log.info("Add a user") + users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) + try: + users.create(properties={ + 'uid': 'common', + 'cn': 'common', + 'sn': 'common', + 'uidNumber': '3000', + 'gidNumber': '4000', + 'homeDirectory': HOME_DIR, + 'description': 'test haproxy with this user', + 'userPassword': PASSWORD + }) + except ldap.ALREADY_EXISTS: + log.info("User already exists") + pass + + +def test_haproxy_trust_ip_attribute(topo, setup_test): + """Test nsslapd-haproxy-trusted-ip attribute set and delete + + :id: 8a0789a6-3ede-40e2-966c-9a2c87eaac05 + :setup: Standalone instance with nsslapd-haproxy-trusted-ip attribute and a user + :steps: + 1. Check that nsslapd-haproxy-trusted-ip attribute is present + 2. Delete nsslapd-haproxy-trusted-ip attribute + 3. Check that nsslapd-haproxy-trusted-ip attribute is not present + :expectedresults: + 1. Success + 2. Success + 3. Success + """ + + log.info("Check that nsslapd-haproxy-trusted-ip attribute is present") + assert topo.standalone.config.present('nsslapd-haproxy-trusted-ip', '192.168.0.1') + + log.info("Delete nsslapd-haproxy-trusted-ip attribute") + topo.standalone.config.remove_all('nsslapd-haproxy-trusted-ip') + + log.info("Check that nsslapd-haproxy-trusted-ip attribute is not present") + assert not topo.standalone.config.present('nsslapd-haproxy-trusted-ip', '192.168.0.1') + + +def test_binds_with_haproxy_trust_ip_attribute(topo, setup_test): + """Test that non-proxy binds are not blocked when nsslapd-haproxy-trusted-ip attribute is set + + :id: 14273c16-fed9-497e-8ebb-09e3dabc7914 + :setup: Standalone instance with nsslapd-haproxy-trusted-ip attribute and a user + :steps: + 1. Try to bind as anonymous user + 2. Try to bind as a user + 3. Check that userPassword is correct and we can get it + :expectedresults: + 1. Success + 2. Success + 3. Success + """ + + log.info("Bind as anonymous user") + Anonymous(topo.standalone).bind() + + log.info("Bind as a user") + user_entry = UserAccount(topo.standalone, DN) + user_conn = user_entry.bind(PASSWORD) + + log.info("Check that userPassword is correct and we can get it") + user_entry = UserAccount(user_conn, DN) + home = user_entry.get_attr_val_utf8('homeDirectory') + assert home == HOME_DIR diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c index cfa8b98d92..ee0c42a95c 100644 --- a/ldap/servers/slapd/connection.c +++ b/ldap/servers/slapd/connection.c @@ -196,6 +196,7 @@ connection_cleanup(Connection *conn) slapi_ch_free((void **)&conn->cin_destaddr); slapi_ch_free((void **)&conn->cin_addr_aclip); slapi_ch_free_string(&conn->c_ipaddr); + slapi_ch_free_string(&conn->c_serveripaddr); if (conn->c_domain != NULL) { ber_bvecfree(conn->c_domain); conn->c_domain = NULL; @@ -213,6 +214,7 @@ connection_cleanup(Connection *conn) conn->c_idlesince = 0; conn->c_flags = 0; conn->c_needpw = 0; + conn->c_haproxyheader_read = 0; conn->c_prfd = NULL; /* c_ci stays as it is */ conn->c_fdi = SLAPD_INVALID_SOCKET_INDEX; @@ -420,6 +422,7 @@ connection_reset(Connection *conn, int ns, PRNetAddr *from, int fromLen __attrib conn->c_ssl_ssf = 0; conn->c_local_ssf = 0; conn->c_ipaddr = slapi_ch_strdup(str_ip); + conn->c_serveripaddr = slapi_ch_strdup(str_destip); } /* Create a pool of threads for handling the operations */ @@ -1132,6 +1135,28 @@ conn_buffered_data_avail_nolock(Connection *conn, int *conn_closed) } } +/* Function to convert a PRNetAddr to a normalized IPv4 string and keep original address string. */ +static void +normalize_IPv4(const PRNetAddr *addr, char *normalizedAddr, size_t normalizedAddrSize, char *originalAddr, size_t originalAddrSize) +{ + /* Keep the original address string */ + PR_NetAddrToString(addr, originalAddr, originalAddrSize); + + if (PR_IsNetAddrType(addr, PR_IpAddrV4Mapped)) { + /* Handle IPv4-mapped-to-IPv6 */ + PRNetAddr v4addr; + /* v4addr gets the lower 32 bits of addr6 (the IPv4 address in the IPv6 format) */ + v4addr.inet.family = PR_AF_INET; + v4addr.inet.ip = addr->ipv6.ip.pr_s6_addr32[3]; + PR_NetAddrToString(&v4addr, normalizedAddr, normalizedAddrSize); + } else { + /* If it's not an IPv4-mapped IPv6 address, just keep the original format */ + strncpy(normalizedAddr, originalAddr, normalizedAddrSize - 1); + normalizedAddr[normalizedAddrSize - 1] = '\0'; + } + originalAddr[originalAddrSize - 1] = '\0'; +} + /* Upon returning from this function, we have either: 1. Read a PDU successfully. 2. Detected some error condition with the connection which requires closing it. @@ -1146,6 +1171,7 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int * { ber_len_t len = 0; int ret = 0; + int haproxy_rc = 0; int32_t waits_done = 0; ber_int_t msgid; int new_operation = 1; /* Are we doing the first I/O read for a new operation ? */ @@ -1154,6 +1180,17 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int * PRInt32 syserr = 0; size_t buffer_data_avail; int conn_closed = 0; + PRNetAddr pr_netaddr_from = {0}; + PRNetAddr pr_netaddr_dest = {0}; + char buf_ip[INET6_ADDRSTRLEN + 1] = {0}; + char buf_haproxy_destip[INET6_ADDRSTRLEN + 1] = {0}; + char str_ip[INET6_ADDRSTRLEN + 1] = {0}; + char str_haproxy_ip[INET6_ADDRSTRLEN + 1] = {0}; + char str_haproxy_destip[INET6_ADDRSTRLEN + 1] = {0}; + PRStatus status = PR_SUCCESS; + struct berval **bvals = NULL; + int proxy_connection = 0; + int restrict_access = 0; pthread_mutex_lock(&(conn->c_mutex)); /* @@ -1179,6 +1216,7 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int * } new_operation = 0; } + /* If we still haven't seen a complete PDU, read from the network */ while (*tag == LBER_DEFAULT) { int32_t ioblocktimeout_waits = conn->c_ioblocktimeout / CONN_TURBO_TIMEOUT_INTERVAL; @@ -1186,6 +1224,68 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int * PR_ASSERT(!new_operation || !conn_buffered_data_avail_nolock(conn, &conn_closed)); /* We make a non-blocking read call */ if (CONNECTION_BUFFER_OFF != conn->c_private->use_buffer) { + /* Process HAProxy header */ + if (conn->c_haproxyheader_read == 0) { + conn->c_haproxyheader_read = 1; + /* + * We only check for HAProxy header if nsslapd-haproxy-trusted-ip is configured. + * If it is we proceed with the connection only if it's comming from trusted + * proxy server with correct and complete header. + */ + if ((bvals = g_get_haproxy_trusted_ip()) != NULL) { + /* Can we have an unknown address at that point? */ + if ((haproxy_rc = haproxy_receive(conn->c_sd, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest)) == HAPROXY_ERROR) { + slapi_log_err(SLAPI_LOG_CONNS, "connection_read_operation", "Error reading HAProxy header.\n"); + disconnect_server_nomutex(conn, conn->c_connid, -1, SLAPD_DISCONNECT_PROXY_INVALID_HEADER, EPROTO); + ret = CONN_DONE; + goto done; + } + + /* If bval is NULL and we don't have an error - we still want the proper log and update */ + if ((haproxy_rc == HAPROXY_HEADER_PARSED) && (proxy_connection)) { + /* Normalize IP addresses */ + normalize_IPv4(conn->cin_addr, buf_ip, sizeof(buf_ip), str_ip, sizeof(str_ip)); + normalize_IPv4(&pr_netaddr_dest, buf_haproxy_destip, sizeof(buf_haproxy_destip), + str_haproxy_destip, sizeof(str_haproxy_destip)); + + /* Now, reset RC and set it to 0 only if a match is found */ + haproxy_rc = -1; + + /* Allow only: + * Trusted IP == Original Client IP == HAProxy Header Destination IP */ + for (size_t i = 0; bvals[i] != NULL; ++i) { + if ((strlen(bvals[i]->bv_val) == strlen(buf_ip)) && + (strlen(bvals[i]->bv_val) == strlen(buf_haproxy_destip)) && + (strncasecmp(bvals[i]->bv_val, buf_ip, strlen(buf_ip)) == 0) && + (strncasecmp(bvals[i]->bv_val, buf_haproxy_destip, strlen(buf_haproxy_destip)) == 0)) { + haproxy_rc = 0; + break; + } + } + if (haproxy_rc == -1) { + slapi_log_err(SLAPI_LOG_CONNS, "connection_read_operation", "HAProxy header received from unknown source.\n"); + disconnect_server_nomutex(conn, conn->c_connid, -1, SLAPD_DISCONNECT_PROXY_UNKNOWN, EPROTO); + ret = CONN_DONE; + goto done; + } + /* Get the HAProxy header client IP address */ + PR_NetAddrToString(&pr_netaddr_from, str_haproxy_ip, sizeof(str_haproxy_ip)); + + /* Replace cin_addr and cin_destaddr in the Connection struct with received addresses */ + slapi_ch_free((void**)&conn->cin_addr); + slapi_ch_free((void**)&conn->cin_destaddr); + conn->cin_addr = (PRNetAddr*)malloc(sizeof(PRNetAddr)); + conn->cin_destaddr = (PRNetAddr*)malloc(sizeof(PRNetAddr)); + memcpy(conn->cin_addr, &pr_netaddr_from, sizeof(PRNetAddr)); + memcpy(conn->cin_destaddr, &pr_netaddr_dest, sizeof(PRNetAddr)); + conn->c_ipaddr = slapi_ch_strdup(str_haproxy_ip); + conn->c_serveripaddr = slapi_ch_strdup(str_haproxy_destip); + slapi_log_access(LDAP_DEBUG_STATS, + "conn=%" PRIu64 " fd=%d HAProxy new_address_from=%s to new_address_dest=%s\n", + conn->c_connid, conn->c_sd, str_haproxy_ip, str_haproxy_destip); + } + } + } ret = connection_read_ldap_data(conn, &err); } else { ret = get_next_from_buffer(NULL, 0, &len, tag, op->o_ber, conn); diff --git a/ldap/servers/slapd/disconnect_error_strings.h b/ldap/servers/slapd/disconnect_error_strings.h index 6b51b5c379..f7a31d7280 100644 --- a/ldap/servers/slapd/disconnect_error_strings.h +++ b/ldap/servers/slapd/disconnect_error_strings.h @@ -32,6 +32,8 @@ ER2(SLAPD_DISCONNECT_UNBIND, "U1") ER2(SLAPD_DISCONNECT_POLL, "P2") ER2(SLAPD_DISCONNECT_NTSSL_TIMEOUT, "T2") ER2(SLAPD_DISCONNECT_SASL_FAIL, "S1") +ER2(SLAPD_DISCONNECT_PROXY_INVALID_HEADER, "P3") +ER2(SLAPD_DISCONNECT_PROXY_UNKNOWN, "P4") #endif /* __DISCONNECT_ERROR_STRINGS_H_ */ diff --git a/ldap/servers/slapd/disconnect_errors.h b/ldap/servers/slapd/disconnect_errors.h index 1179527292..a0484f1c23 100644 --- a/ldap/servers/slapd/disconnect_errors.h +++ b/ldap/servers/slapd/disconnect_errors.h @@ -33,6 +33,8 @@ #define SLAPD_DISCONNECT_POLL SLAPD_DISCONNECT_ERROR_BASE + 10 #define SLAPD_DISCONNECT_NTSSL_TIMEOUT SLAPD_DISCONNECT_ERROR_BASE + 11 #define SLAPD_DISCONNECT_SASL_FAIL SLAPD_DISCONNECT_ERROR_BASE + 12 +#define SLAPD_DISCONNECT_PROXY_INVALID_HEADER SLAPD_DISCONNECT_ERROR_BASE + 13 +#define SLAPD_DISCONNECT_PROXY_UNKNOWN SLAPD_DISCONNECT_ERROR_BASE + 14 #endif /* __DISCONNECT_ERRORS_H_ */ diff --git a/ldap/servers/slapd/haproxy.c b/ldap/servers/slapd/haproxy.c new file mode 100644 index 0000000000..ad362b9da8 --- /dev/null +++ b/ldap/servers/slapd/haproxy.c @@ -0,0 +1,392 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2023 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + +/* haproxy.c - process connection PROXY header if present */ + +#include +#include +#include +#include +#include +#include +#include +#include "slap.h" + +/* Function to parse IPv4 addresses in version 2 */ +static int haproxy_parse_v2_addr_v4(uint32_t in_addr, unsigned in_port, PRNetAddr *pr_netaddr) +{ + char addr[INET_ADDRSTRLEN]; + + /* Check if the port is valid */ + if (in_port > 65535) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_addr_v4", "Port number exceeds maximum value.\n"); + return -1; + } + + /* Assign the input address and port to the PRNetAddr structure */ + pr_netaddr->inet.family = PR_AF_INET; + pr_netaddr->inet.port = in_port; + pr_netaddr->inet.ip = in_addr; + + /* Print the address in a human-readable format */ + if (inet_ntop(AF_INET, &(pr_netaddr->inet.ip), addr, INET_ADDRSTRLEN) == NULL) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_addr_v4", "Failed to print address.\n"); + } else { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_addr_v4", "Address: %s\n", addr); + } + return 0; +} + + +/* Function to parse IPv6 addresses in version 2 */ +static int haproxy_parse_v2_addr_v6(uint8_t *in6_addr, unsigned in6_port, PRNetAddr *pr_netaddr) +{ + struct sockaddr_in6 sin6; + char addr[INET6_ADDRSTRLEN]; + + /* Check if the port is valid */ + if (in6_port > 65535) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_addr_v6", "Port number exceeds maximum value.\n"); + return -1; + } + + /* Assign the input address and port to the PRNetAddr structure */ + memset((void *) &sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, in6_addr, 16); + memcpy(&pr_netaddr->ipv6.ip, &sin6.sin6_addr, sizeof(pr_netaddr->ipv6.ip)); + pr_netaddr->ipv6.port = in6_port; + pr_netaddr->ipv6.family = PR_AF_INET6; + + /* Print the address in a human-readable format */ + if (inet_ntop(AF_INET6, &(pr_netaddr->ipv6.ip), addr, INET6_ADDRSTRLEN) == NULL) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_addr_v6", "Failed to print address.\n"); + } else { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_addr_v6", "Address: %s\n", addr); + } + return 0; +} + + +/* Function to parse the header in version 2 */ +int haproxy_parse_v2_hdr(const char *str, size_t *str_len, int *proxy_connection, PRNetAddr *pr_netaddr_from, PRNetAddr *pr_netaddr_dest) +{ + struct proxy_hdr_v2 *hdr_v2 = (struct proxy_hdr_v2 *) str; + uint16_t hdr_v2_len = 0; + PRNetAddr parsed_addr_from = {{0}}; + PRNetAddr parsed_addr_dest = {{0}}; + int rc = HAPROXY_ERROR; + + *proxy_connection = 0; + + /* Check if we received enough bytes to contain the HAProxy v2 header */ + if (*str_len < PP2_HEADER_LEN) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Protocol header is short\n"); + rc = HAPROXY_NOT_A_HEADER; + goto done; + } + hdr_v2_len = ntohs(hdr_v2->len); + + if (memcmp(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN) != 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Protocol header is invalid\n"); + rc = HAPROXY_NOT_A_HEADER; + goto done; + } + + /* Check if the header has the correct signature */ + if ((hdr_v2->ver_cmd & 0xF0) != PP2_VERSION) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Protocol version is invalid\n"); + goto done; + } + /* Check if we received enough bytes to contain the entire HAProxy v2 header, including the address information */ + if (*str_len < PP2_HEADER_LEN + hdr_v2_len) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Protocol header v2 is short\n"); + goto done; + } + + switch (hdr_v2->ver_cmd & 0x0F) { + case PP2_VER_CMD_PROXY: + /* Process the header based on the address family */ + switch (hdr_v2->fam) { + case PP2_FAM_INET | PP2_TRANS_STREAM:{ /* TCP over IPv4 */ + if (hdr_v2_len < PP2_ADDR_LEN_INET) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Address field is short\n"); + goto done; + } + if (haproxy_parse_v2_addr_v4(hdr_v2->addr.ip4.src_addr, hdr_v2->addr.ip4.src_port, &parsed_addr_from) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Client address is invalid\n"); + goto done; + } + if (haproxy_parse_v2_addr_v4(hdr_v2->addr.ip4.dst_addr, hdr_v2->addr.ip4.dst_port, &parsed_addr_dest) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Server address is invalid\n"); + goto done; + } + break; + } + case PP2_FAM_INET6 | PP2_TRANS_STREAM:{/* TCP over IPv6 */ + if (hdr_v2_len < PP2_ADDR_LEN_INET6) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Address field is short\n"); + goto done; + } + if (haproxy_parse_v2_addr_v6(hdr_v2->addr.ip6.src_addr, hdr_v2->addr.ip6.src_port, &parsed_addr_from) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Client address is invalid\n"); + goto done; + } + if (haproxy_parse_v2_addr_v6(hdr_v2->addr.ip6.dst_addr, hdr_v2->addr.ip6.dst_port, &parsed_addr_dest) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Server address is invalid\n"); + goto done; + } + break; + } + default: + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Unsupported address family\n"); + goto done; + } + /* Update the received string length to include the address information */ + *str_len = PP2_HEADER_LEN + hdr_v2_len; + rc = HAPROXY_HEADER_PARSED; + *proxy_connection = 1; + /* Copy the parsed addresses to the output parameters */ + memcpy(pr_netaddr_from, &parsed_addr_from, sizeof(PRNetAddr)); + memcpy(pr_netaddr_dest, &parsed_addr_dest, sizeof(PRNetAddr)); + goto done; + /* If it's a LOCAL command, there's no address information to parse, so just update the received string length */ + case PP2_VER_CMD_LOCAL: + *str_len = PP2_HEADER_LEN + hdr_v2_len; + rc = HAPROXY_HEADER_PARSED; + goto done; + default: + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v2_hdr", "Invalid header command\n"); + goto done; + } +done: + return rc; +} + + +/* Function to parse the protocol in version 1 */ +static int haproxy_parse_v1_protocol(const char *str, const char *protocol) +{ + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_protocol", "HAProxy protocol - %s\n", str ? str : "(null)"); + if ((str != 0) && (strcasecmp(str, protocol) == 0)) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_protocol", "HAProxy protocol is valid\n"); + return 0; + } + return -1; +} + + +/* Function to parse the family (i.e., IPv4 or IPv6) in version 1 */ +static int haproxy_parse_v1_fam(const char *str, int *addr_family) +{ + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_fam", "Address family - %s\n", str ? str : "(null)"); + if (str == 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_fam", "Address family is missing\n"); + return -1; + } + + if (strcasecmp(str, "TCP4") == 0) { + *addr_family = AF_INET; + return 0; + } else if (strcasecmp(str, "TCP6") == 0) { + *addr_family = AF_INET6; + return 0; + } else { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_fam", "Address family %s is unsupported\n", str); + return -1; + } +} + + +/* Function to parse addresses in version 1 */ +static int haproxy_parse_v1_addr(const char *str, PRNetAddr *pr_netaddr, int addr_family) +{ + char addrbuf[256]; + + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_addr", "addr=%s proto=%d\n", str ? str : "(null)", addr_family); + if (str == 0 || strlen(str) >= sizeof(addrbuf)) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_addr", "incorrect IP address: %s\n", str); + return -1; + } + + switch (addr_family) { + case AF_INET6: + if (slapi_is_ipv6_addr(str)) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_addr", "ipv6 address: %s\n", str); + pr_netaddr->ipv6.family = PR_AF_INET6; + } + break; + case AF_INET: + if (slapi_is_ipv4_addr(str)) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_addr", "ipv4 address: %s\n", str); + pr_netaddr->inet.family = PR_AF_INET; + } + break; + default: + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_addr", "incorrect address family: %d\n", addr_family); + return -1; + } + + if (PR_StringToNetAddr(str, pr_netaddr) != PR_SUCCESS) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_addr", "Failed to set IP address: %s\n", str); + return -1; + } + + return 0; +} + + +/* Function to parse port numbers in version 1 */ +static int haproxy_parse_v1_port(const char *str, PRNetAddr *pr_netaddr) +{ + char *endptr; + long port; + + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_port", "port=%s\n", str ? str : "(null)"); + errno = 0; /* Reset errno to 0 before calling strtol */ + port = strtol(str, &endptr, 10); + + /* Check for conversion errors */ + if (errno == ERANGE || port < 0 || port > 65535) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_port", "Port is out of range: %s\n", str); + return -1; + } + if (endptr == str || *endptr != '\0') { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_port", "No digits were found: %s\n", str); + return -1; + } + + /* Successfully parsed the port number. Set it */ + PRLDAP_SET_PORT(pr_netaddr, port); + return 0; +} + + +static inline char *get_next_token(char **copied) { + return tokenize_string(copied, " \r"); +} + + +/* Function to parse the header in version 1 */ +int haproxy_parse_v1_hdr(const char *str, size_t *str_len, int *proxy_connection, PRNetAddr *pr_netaddr_from, PRNetAddr *pr_netaddr_dest) +{ + PRNetAddr parsed_addr_from = {{0}}; + PRNetAddr parsed_addr_dest = {{0}}; + char *str_saved = NULL; + char *copied = NULL; + char *after_header = NULL; + int addr_family; + int rc = HAPROXY_ERROR; + + *proxy_connection = 0; + if (strncmp(str, "PROXY ", 6) == 0) { + str_saved = slapi_ch_strdup(str); + copied = str_saved; + after_header = split_string_at_delim(str_saved, '\n'); + + /* Check if the header is valid */ + if (after_header == 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_hdr", "Missing protocol header terminator\n"); + goto done; + } + /* Parse the protocol, family, addresses, and ports */ + if (haproxy_parse_v1_protocol(get_next_token(&copied), "PROXY") < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_hdr", "Missing or bad protocol header\n"); + goto done; + } + /* Parse the family */ + if (haproxy_parse_v1_fam(get_next_token(&copied), &addr_family) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_hdr", "Missing or bad protocol type\n"); + goto done; + } + /* Parse the addresses */ + if (haproxy_parse_v1_addr(get_next_token(&copied), &parsed_addr_from, addr_family) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_hdr", "Missing or bad client address\n"); + goto done; + } + if (haproxy_parse_v1_addr(get_next_token(&copied), &parsed_addr_dest, addr_family) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_hdr", "Missing or bad server address\n"); + goto done; + } + /* Parse the ports */ + if (haproxy_parse_v1_port(get_next_token(&copied), &parsed_addr_from) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_hdr", "Missing or bad client port\n"); + goto done; + } + if (haproxy_parse_v1_port(get_next_token(&copied), &parsed_addr_dest) < 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_parse_v1_hdr", "Missing or bad server port\n"); + goto done; + } + rc = HAPROXY_HEADER_PARSED; + *proxy_connection = 1; + *str_len = after_header - str_saved; + /* Copy the parsed addresses to the output parameters */ + memcpy(pr_netaddr_from, &parsed_addr_from, sizeof(PRNetAddr)); + memcpy(pr_netaddr_dest, &parsed_addr_dest, sizeof(PRNetAddr)); + +done: + slapi_ch_free_string(&str_saved); + } else { + rc = HAPROXY_NOT_A_HEADER; + } + return rc; +} + +/** + * Function to receive and parse HAProxy headers, supporting both v1 and v2 of the protocol. + * + * @param fd: The file descriptor of the socket from which to read. + * @param proxy_connection: A pointer to an integer to store the proxy connection status (0 or 1). + * @param pr_netaddr_from: A pointer to a PRNetAddr structure to store the source address info. + * @param pr_netaddr_dest: A pointer to a PRNetAddr structure to store the destination address info. + * + * @return: Returns 0 on successful operation, -1 on error. + */ +int haproxy_receive(int fd, int *proxy_connection, PRNetAddr *pr_netaddr_from, PRNetAddr *pr_netaddr_dest) +{ + /* Buffer to store the header received from the HAProxy server */ + char hdr[HAPROXY_HEADER_MAX_LEN + 1] = {0}; + ssize_t recv_result = 0; + size_t hdr_len; + int rc = HAPROXY_ERROR; + + /* Attempt to receive the header from the HAProxy server */ + recv_result = recv(fd, hdr, sizeof(hdr) - 1, MSG_PEEK | MSG_DONTWAIT); + if (recv_result <= 0) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_receive", "EOF or error on haproxy socket: %s\n", strerror(errno)); + return rc; + } else { + hdr_len = recv_result; + } + + /* Null-terminate the header string */ + if (hdr_len < sizeof(hdr)) { + hdr[hdr_len] = 0; + } else { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_receive", "Recieved header is too long: %d\n", hdr_len); + rc = HAPROXY_NOT_A_HEADER; + return rc; + } + + rc = haproxy_parse_v1_hdr(hdr, &hdr_len, proxy_connection, pr_netaddr_from, pr_netaddr_dest); + if (rc == HAPROXY_NOT_A_HEADER) { + rc = haproxy_parse_v2_hdr(hdr, &hdr_len, proxy_connection, pr_netaddr_from, pr_netaddr_dest); + } + + if (rc == HAPROXY_HEADER_PARSED) { + slapi_log_err(SLAPI_LOG_CONNS, "haproxy_receive", "HAProxy header parsed successfully\n"); + /* Consume the data from the socket */ + recv_result = recv(fd, hdr, hdr_len, MSG_DONTWAIT); + + if (recv_result != hdr_len) { + slapi_log_err(SLAPI_LOG_ERR, "haproxy_receive", "Read error: %s: %s\n", hdr, strerror(errno)); + return HAPROXY_ERROR; + } + } + return rc; +} diff --git a/ldap/servers/slapd/haproxy.h b/ldap/servers/slapd/haproxy.h new file mode 100644 index 0000000000..0040d15b71 --- /dev/null +++ b/ldap/servers/slapd/haproxy.h @@ -0,0 +1,76 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2023 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + +#include + +#define HAPROXY_HEADER_PARSED 0 +#define HAPROXY_NOT_A_HEADER 1 +#define HAPROXY_ERROR -1 + + /* + * Begin protocol v2 definitions from haproxy/include/types/connection.h. + */ +#define PP2_SIGNATURE "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" +#define PP2_SIGNATURE_LEN 12 +#define PP2_HEADER_LEN 16 + +/* ver_cmd byte */ +#define PP2_VER_CMD_LOCAL 0x00 +#define PP2_VER_CMD_PROXY 0x01 + +#define PP2_VERSION 0x20 + +/* Family byte */ +#define PP2_TRANS_UNSPEC 0x00 +#define PP2_TRANS_STREAM 0x01 +#define PP2_FAM_UNSPEC 0x00 +#define PP2_FAM_INET 0x10 +#define PP2_FAM_INET6 0x20 + +/* len field (2 bytes) */ +#define PP2_ADDR_LEN_UNSPEC (0) +#define PP2_ADDR_LEN_INET (4 + 4 + 2 + 2) +#define PP2_ADDR_LEN_INET6 (16 + 16 + 2 + 2) +#define PP2_HDR_LEN_UNSPEC (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC) +#define PP2_HDR_LEN_INET (PP2_HEADER_LEN + PP2_ADDR_LEN_INET) +#define PP2_HDR_LEN_INET6 (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6) + +/* Both formats (v1 and v2) are designed to fit in the smallest TCP segment + * that any TCP/IP host is required to support (576 - 40 = 536 bytes). + */ +#define HAPROXY_HEADER_MAX_LEN 536 + +/* Define struct for the proxy header */ +struct proxy_hdr_v2 { + uint8_t sig[PP2_SIGNATURE_LEN]; /* PP2_SIGNATURE */ + uint8_t ver_cmd; /* protocol version | command */ + uint8_t fam; /* protocol family and transport */ + uint16_t len; /* length of remainder */ + union { + struct { /* for TCP/UDP over IPv4, len = 12 */ + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ip4; + struct { /* for TCP/UDP over IPv6, len = 36 */ + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ip6; + struct { /* for AF_UNIX sockets, len = 216 */ + uint8_t src_addr[108]; + uint8_t dst_addr[108]; + } unx; + } addr; +}; + +int haproxy_parse_v1_hdr(const char *str, size_t *str_len, int *proxy_connection, PRNetAddr *pr_netaddr_from, PRNetAddr *pr_netaddr_dest); +int haproxy_parse_v2_hdr(const char *str, size_t *str_len, int *proxy_connection, PRNetAddr *pr_netaddr_from, PRNetAddr *pr_netaddr_dest); +int haproxy_receive(int fd, int *proxy_connection, PRNetAddr *pr_netaddr_from, PRNetAddr *pr_netaddr_dest); diff --git a/ldap/servers/slapd/ldaputil.c b/ldap/servers/slapd/ldaputil.c index db3300e302..3fefd22533 100644 --- a/ldap/servers/slapd/ldaputil.c +++ b/ldap/servers/slapd/ldaputil.c @@ -2295,11 +2295,24 @@ mozldap_ldap_explode_rdn(const char *rdn, const int notypes) } int -slapi_is_ipv6_addr(const char *hostname) +slapi_is_ipv4_addr(const char *ipAddress) { PRNetAddr addr; - if (PR_StringToNetAddr(hostname, &addr) == PR_SUCCESS && + if (PR_StringToNetAddr(ipAddress, &addr) == PR_SUCCESS && + PR_IsNetAddrType(&addr, PR_IpAddrV4Mapped) && + addr.raw.family == PR_AF_INET) { + return 1; + } + return 0; +} + +int +slapi_is_ipv6_addr(const char *ipAddress) +{ + PRNetAddr addr; + + if (PR_StringToNetAddr(ipAddress, &addr) == PR_SUCCESS && !PR_IsNetAddrType(&addr, PR_IpAddrV4Mapped) && addr.raw.family == PR_AF_INET6) { return 1; @@ -2307,6 +2320,25 @@ slapi_is_ipv6_addr(const char *hostname) return 0; } +/* For debug purpose */ +void +slapi_log_prnetaddr(const PRNetAddr *addr) { + char ip_str[INET6_ADDRSTRLEN] = {0}; + uint16_t port = 0; + + if (addr->inet.family == PR_AF_INET) { + PR_NetAddrToString(addr, ip_str, sizeof(ip_str)); + port = PR_ntohs(addr->inet.port); + slapi_log_error(SLAPI_LOG_ERR, "slapi_log_prnetaddr", "IPv4: %s:%u\n", ip_str, port); + } else if (addr->raw.family == PR_AF_INET6) { + PR_NetAddrToString(addr, ip_str, sizeof(ip_str)); + port = PR_ntohs(addr->ipv6.port); + slapi_log_error(SLAPI_LOG_ERR, "slapi_log_prnetaddr", "IPv6: %s:%u\n", ip_str, port); + } else { + slapi_log_error(SLAPI_LOG_ERR, "slapi_log_prnetaddr", "Unknown address family\n"); + } +} + /* * Get the length of the ber-encoded ldap message. Note, only the length of * the LDAP operation is returned, not the length of the entire berval. diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c index a0bf0b001a..2097ab93cf 100644 --- a/ldap/servers/slapd/libglobs.c +++ b/ldap/servers/slapd/libglobs.c @@ -159,6 +159,7 @@ typedef enum { CONFIG_STRING_OR_UNKNOWN, /* use "unknown" instead of an empty string */ CONFIG_CONSTANT_INT, /* for #define values, e.g. */ CONFIG_CONSTANT_STRING, /* for #define values, e.g. */ + CONFIG_SPECIAL_TRUSTED_IP_LIST, /* this is a berval list */ CONFIG_SPECIAL_REFERRALLIST, /* this is a berval list */ CONFIG_SPECIAL_SSLCLIENTAUTH, /* maps strings to an enumeration */ CONFIG_SPECIAL_ERRORLOGLEVEL, /* requires & with LDAP_DEBUG_ANY */ @@ -818,6 +819,10 @@ static struct config_get_and_set NULL, 0, (void **)&global_slapdFrontendConfig.listenhost, CONFIG_STRING, NULL, "", NULL /* Empty value is allowed */}, + {CONFIG_HAPROXY_TRUSTED_IP, (ConfigSetFunc)config_set_haproxy_trusted_ip, + NULL, 0, + (void **)&global_slapdFrontendConfig.haproxy_trusted_ip, + CONFIG_SPECIAL_TRUSTED_IP_LIST, NULL, NULL, NULL}, {CONFIG_SNMP_INDEX_ATTRIBUTE, config_set_snmp_index, NULL, 0, (void **)&global_slapdFrontendConfig.snmp_index, @@ -2539,6 +2544,59 @@ config_set_listenhost(const char *attrname __attribute__((unused)), char *value, return retVal; } +int +config_set_haproxy_trusted_ip(const char *attrname, struct berval **value, char *errorbuf, int apply) +{ + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + int retVal = LDAP_SUCCESS; + int conn_buffer = 0; + size_t end = 0; + + if (config_value_is_null(attrname, (char *)value, errorbuf, 0)) { + return LDAP_OPERATIONS_ERROR; + } + + CFG_LOCK_READ(slapdFrontendConfig); + conn_buffer = slapdFrontendConfig->connection_buffer; + CFG_UNLOCK_READ(slapdFrontendConfig); + if (CONNECTION_BUFFER_OFF == conn_buffer) { + slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "HAProxy is not supported when nsslapd-connection-buffer is disabled (set to '0')\n"); + return LDAP_OPERATIONS_ERROR; + } + + if (value && value[0] && + PL_strncasecmp((char *)value[0]->bv_val, HAPROXY_TRUSTED_IP_REMOVE_CMD, value[0]->bv_len) != 0) { + for (size_t i = 0; value[i] != NULL; i++) { + end = strspn(value[i]->bv_val, "0123456789:ABCDEFabcdef.*"); + /* + * If no valid characters are found, or if there are characters after the valid ones, + * then print an error message and exit with LDAP_OPERATIONS_ERROR. + */ + if (!end || value[i]->bv_val[end] != '\0') { + slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "IP address contains invalid characters (%s), skipping\n", + value[i]->bv_val); + return LDAP_OPERATIONS_ERROR; + } + if (strstr(value[i]->bv_val, ":") == 0) { + /* IPv4 - make sure it's just numbers, dots, and wildcard */ + end = strspn(value[i]->bv_val, "0123456789.*"); + if (!end || value[i]->bv_val[end] != '\0') { + slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "IPv4 address contains invalid characters (%s), skipping\n", + value[i]->bv_val); + return LDAP_OPERATIONS_ERROR; + } + } + } + } + + if (apply) { + CFG_LOCK_WRITE(slapdFrontendConfig); + g_set_haproxy_trusted_ip(value); + CFG_UNLOCK_WRITE(slapdFrontendConfig); + } + return retVal; +} + int config_set_snmp_index(const char *attrname, char *value, char *errorbuf, int apply) { @@ -5687,6 +5745,38 @@ config_get_securelistenhost(void) return retVal; } +struct berval ** +config_get_haproxy_trusted_ip(void) +{ + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + struct berval **retVal = NULL; + int nTrustedIPs = 0; + + CFG_LOCK_READ(slapdFrontendConfig); + /* count the number of trusted IPs */ + for (nTrustedIPs = 0; + slapdFrontendConfig->haproxy_trusted_ip && + slapdFrontendConfig->haproxy_trusted_ip[nTrustedIPs]; + nTrustedIPs++) + ; + + retVal = (struct berval **) slapi_ch_malloc((nTrustedIPs + 1) * sizeof(struct berval *)); + + /*terminate the end, and add the trusted IPs backwards */ + retVal[nTrustedIPs--] = NULL; + + while (nTrustedIPs >= 0) { + retVal[nTrustedIPs] = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + retVal[nTrustedIPs]->bv_val = + config_copy_strval(slapdFrontendConfig->haproxy_trusted_ip[nTrustedIPs]->bv_val); + retVal[nTrustedIPs]->bv_len = slapdFrontendConfig->haproxy_trusted_ip[nTrustedIPs]->bv_len; + nTrustedIPs--; + } + CFG_UNLOCK_READ(slapdFrontendConfig); + + return retVal; +} + char * config_get_srvtab(void) { @@ -8405,6 +8495,19 @@ config_set(const char *attr, struct berval **values, char *errorbuf, int apply) retval = config_set_defaultreferral(attr, values, errorbuf, apply); } break; + case CONFIG_SPECIAL_TRUSTED_IP_LIST: + if (NULL == values) /* special token which means to remove trusted IPs */ + { + struct berval val; + struct berval *vals[2] = {0, 0}; + vals[0] = &val; + val.bv_val = HAPROXY_TRUSTED_IP_REMOVE_CMD; + val.bv_len = strlen(HAPROXY_TRUSTED_IP_REMOVE_CMD); + retval = config_set_haproxy_trusted_ip(attr, vals, errorbuf, apply); + } else { + retval = config_set_haproxy_trusted_ip(attr, values, errorbuf, apply); + } + break; default: if (values == NULL && (cgas->initvalue != NULL || cgas->geninitfunc != NULL)) { @@ -8521,6 +8624,14 @@ config_set_value( slapi_entry_attr_set_charptr(e, cgas->attr_name, ""); break; + case CONFIG_SPECIAL_TRUSTED_IP_LIST: + /* trusted IP list is already an array of berval* */ + if (value) + slapi_entry_attr_replace(e, cgas->attr_name, (struct berval **)*value); + else + slapi_entry_attr_set_charptr(e, cgas->attr_name, ""); + break; + case CONFIG_CONSTANT_STRING: PR_ASSERT(value); /* should be a constant value */ slapi_entry_attr_set_charptr(e, cgas->attr_name, (char *)value); diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c index c6b1d8abf7..8074735e28 100644 --- a/ldap/servers/slapd/log.c +++ b/ldap/servers/slapd/log.c @@ -2870,6 +2870,7 @@ log__open_accesslogfile(int logfile_state, int locked) LOG_ACCESS_UNLOCK_WRITE(); return LOG_SUCCESS; } + /****************************************************************************** * log__needrotation * diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h index 76b2e3af23..410a3c5fe4 100644 --- a/ldap/servers/slapd/proto-slap.h +++ b/ldap/servers/slapd/proto-slap.h @@ -242,6 +242,7 @@ int config_set_SSL3ciphers(const char *attrname, char *value, char *errorbuf, in int config_set_localhost(const char *attrname, char *value, char *errorbuf, int apply); int config_set_listenhost(const char *attrname, char *value, char *errorbuf, int apply); int config_set_securelistenhost(const char *attrname, char *value, char *errorbuf, int apply); +int config_set_haproxy_trusted_ip(const char *attrname, struct berval **value, char *errorbuf, int apply); int config_set_ldapi_filename(const char *attrname, char *value, char *errorbuf, int apply); int config_set_snmp_index(const char *attrname, char *value, char *errorbuf, int apply); int config_set_ldapi_switch(const char *attrname, char *value, char *errorbuf, int apply); @@ -425,6 +426,7 @@ char *config_get_SSL3ciphers(void); char *config_get_localhost(void); char *config_get_listenhost(void); char *config_get_securelistenhost(void); +struct berval **config_get_haproxy_trusted_ip(void); char *config_get_ldapi_filename(void); int config_get_ldapi_switch(void); int config_get_ldapi_bind_switch(void); @@ -882,6 +884,8 @@ int strarray2str(char **a, char *buf, size_t buflen, int include_quotes); int slapd_chown_if_not_owner(const char *filename, uid_t uid, gid_t gid); int slapd_comp_path(char *p0, char *p1); void replace_char(char *name, char c, char c2); +char *split_string_at_delim(char *str, char delim); +char *tokenize_string(char **str, const char *delim); void slapd_cert_not_found_error_help(char *cert_name); @@ -1053,6 +1057,8 @@ PRUint64 g_get_num_entries_sent(void); PRUint64 g_get_num_bytes_sent(void); void g_set_default_referral(struct berval **ldap_url); struct berval **g_get_default_referral(void); +void g_set_haproxy_trusted_ip(struct berval **ipaddress); +struct berval **g_get_haproxy_trusted_ip(void); void disconnect_server(Connection *conn, PRUint64 opconnid, int opid, PRErrorCode reason, PRInt32 error); int send_ldap_search_entry(Slapi_PBlock *pb, Slapi_Entry *e, LDAPControl **ectrls, char **attrs, int attrsonly); void send_ldap_result(Slapi_PBlock *pb, int err, char *matched, char *text, int nentries, struct berval **urls); diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c index 29249e3f66..adcef95393 100644 --- a/ldap/servers/slapd/result.c +++ b/ldap/servers/slapd/result.c @@ -162,6 +162,58 @@ g_get_default_referral() return slapdFrontendConfig->defaultreferral; } +static void +delete_haproxy_trusted_ip(struct berval **ipaddress) +{ + if (ipaddress) { + int ii = 0; + for (ii = 0; ipaddress[ii]; ++ii) + ber_bvfree(ipaddress[ii]); + slapi_ch_free((void **)&ipaddress); + } +} + +void +g_set_haproxy_trusted_ip(struct berval **ipaddress) +{ + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + struct berval **haproxy_trusted_ip = NULL; + int nTrustedIPs = 0; + + /* check to see if we want to delete all values */ + if (ipaddress && ipaddress[0] && + PL_strncasecmp((char *)ipaddress[0]->bv_val, HAPROXY_TRUSTED_IP_REMOVE_CMD, ipaddress[0]->bv_len) == 0) { + delete_haproxy_trusted_ip(slapdFrontendConfig->haproxy_trusted_ip); + slapdFrontendConfig->haproxy_trusted_ip = NULL; + return; + } + + /* count the number of ip addresses */ + for (nTrustedIPs = 0; ipaddress && ipaddress[nTrustedIPs]; nTrustedIPs++) + ; + + haproxy_trusted_ip = (struct berval **) + slapi_ch_malloc((nTrustedIPs + 1) * sizeof(struct berval *)); + + /* terminate the end, and add the trusted IPs backwards */ + haproxy_trusted_ip[nTrustedIPs--] = NULL; + + while (nTrustedIPs >= 0) { + haproxy_trusted_ip[nTrustedIPs] = ber_bvdup(ipaddress[nTrustedIPs]); + nTrustedIPs--; + } + + delete_haproxy_trusted_ip(slapdFrontendConfig->haproxy_trusted_ip); + slapdFrontendConfig->haproxy_trusted_ip = haproxy_trusted_ip; +} + +struct berval ** +g_get_haproxy_trusted_ip() +{ + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + return slapdFrontendConfig->haproxy_trusted_ip; +} + /* * routines to manage keeping track of the current number of connections * to the server. this information is used by the listener thread to diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h index 91e6c1118b..072f6f962b 100644 --- a/ldap/servers/slapd/slap.h +++ b/ldap/servers/slapd/slap.h @@ -433,6 +433,7 @@ typedef void (*VFPV)(); /* takes undefined arguments */ #define PWD_PBE_DELIM '-' #define REFERRAL_REMOVE_CMD "remove" +#define HAPROXY_TRUSTED_IP_REMOVE_CMD "remove" /* Filenames for DSE storage */ #define DSE_FILENAME "dse.ldif" @@ -489,6 +490,8 @@ struct subfilt #include "filter.h" /* mr_filter_t */ +#include "haproxy.h" + /* * represents a search filter */ @@ -1674,6 +1677,7 @@ typedef struct conn Conn_private *c_private; /* data which is not shared outside connection.c */ int c_flags; /* Misc flags used only for SSL status currently */ int c_needpw; /* need new password */ + int c_haproxyheader_read; /* 0 if HAProxy header has not been read, 1 if it has been read */ CERTCertificate *c_client_cert; /* Client's Cert */ PRFileDesc *c_prfd; /* NSPR 2.1 FileDesc */ int c_ci; /* An index into the Connection array. For printing. */ @@ -1698,6 +1702,7 @@ typedef struct conn struct connection_table *c_ct; /* connection table that this connection belongs to */ int c_ns_close_jobs; /* number of current close jobs */ char *c_ipaddr; /* ip address str - used by monitor */ + char *c_serveripaddr; /* server ip address str - used by monitor */ /* per conn static config */ ber_len_t c_maxbersize; int32_t c_ioblocktimeout; @@ -2150,6 +2155,7 @@ typedef struct _slapdEntryPoints #define CONFIG_PORT_ATTRIBUTE "nsslapd-port" #define CONFIG_WORKINGDIR_ATTRIBUTE "nsslapd-workingdir" #define CONFIG_LISTENHOST_ATTRIBUTE "nsslapd-listenhost" +#define CONFIG_HAPROXY_TRUSTED_IP "nsslapd-haproxy-trusted-ip" #define CONFIG_SNMP_INDEX_ATTRIBUTE "nsslapd-snmp-index" #define CONFIG_LDAPI_FILENAME_ATTRIBUTE "nsslapd-ldapifilepath" #define CONFIG_LDAPI_SWITCH_ATTRIBUTE "nsslapd-ldapilisten" @@ -2402,6 +2408,7 @@ typedef struct _slapdFrontendConfig char *encryptionalias; char *errorlog; char *listenhost; + struct berval **haproxy_trusted_ip; int snmp_index; char *localuser; char *localhost; diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h index 5698d4c233..b3b7155830 100644 --- a/ldap/servers/slapd/slapi-plugin.h +++ b/ldap/servers/slapd/slapi-plugin.h @@ -3171,15 +3171,32 @@ void slapi_rdn_set_rdn(Slapi_RDN *rdn, const Slapi_RDN *fromrdn); */ void slapi_rdn_free(Slapi_RDN **rdn); +/** + * Checks if the value of ipAddress is a IPv4 address + * + * \param ipAddress is a string + * \return 1 if address is an IPv4 address + * \return 0 if address is not an IPv4 address + */ +int slapi_is_ipv4_addr(const char *ipAddress); + /** * Checks if the value of ipAddress is a IPv6 address * - * \param ipAddress is a string that is either an IPv4 or IPv6 address + * \param ipAddress is a string * \return 1 if address is an IPv6 address - * \return 0 if address is an IPv4 address + * \return 0 if address is not an IPv6 address */ int slapi_is_ipv6_addr(const char *ipAddress); +/** + * Log to Error log the value of a PRNetAddr - IPv4 or IPv6 + * For debugging purposes only + * + * \param addr is a PRNetAddr + */ +void slapi_log_prnetaddr(const PRNetAddr *addr); + /** * Returns the length of a ber-encoded ldap operation * diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c index 0d76337101..ea7b1b9a80 100644 --- a/ldap/servers/slapd/util.c +++ b/ldap/servers/slapd/util.c @@ -479,6 +479,64 @@ replace_char(char *str, char c, char c2) } } + +/* +** Break a string at the delimiter +** If the delimiter is not found, the string is not modified. +** The position immediately following the delimiter is returned. +*/ +char *split_string_at_delim(char *str, char delim) { + if (str == NULL) { + return NULL; + } + + char *delim_position = strchr(str, delim); + + if (delim_position != NULL) { + *delim_position = '\0'; + delim_position++; + } + + return delim_position; +} + + +/* a simple string tokenizer */ +char *tokenize_string(char **str, const char *delim) +{ + char *cp = *str; + char *result; + + /* The string is empty or has been fully tokenized */ + if (cp == NULL) { + return NULL; + } + + /* The first loop skips any leading delimiter characters in the input string */ + while (*cp && strchr(delim, *cp)) { + cp++; + } + if (cp == NULL) { + return NULL; + } + + /* The second loop tries to find a delimiter character or reaches the end of the string */ + result = cp; + while (*cp && strchr(delim, *cp) == 0) { + cp++; + } + + /* Found! Replace the delimiter with a null character */ + if (*cp) { + *cp++ = '\0'; + } + + /* In future, search from the updated position */ + *str = cp; + return (result); +} + + /* ** This function takes a quoted attribute value of the form "abc", ** and strips off the enclosing quotes. It also deals with quoted @@ -1653,3 +1711,79 @@ mkdir_p(char *dir, unsigned int mode) return 0; } } + + +/* + * Standard openldap ldif_getline function does modify the input buffer in place + * which leads to SIGSEGV if buffer is directly comming from mdb data + * (because the database memory is write protected). So lets have a read only version. + */ +const char *ldif_getline_ro( const char **next) +{ + const char *line = NULL; + const char *pt = *next; + + do { + if ( pt == NULL || *pt == '\n' || *pt == '\0' ) { + *next = NULL; + return( line ); + } + + line = pt; + while ( (pt = strchr( pt, '\n' )) != NULL && *++pt == ' ') { + pt ++; /* skip continuation line leading space */ + } + } while( *line == '#' ); + *next = pt; + return( line ); +} + +/* + * Duplicate the contents of an ldif "line" generated by ldif_getline_ro + * removing \r or \n and leading space marking continuation on next line + * copy->bv_val may get realloc if copy->bv_len is too small) + * Note: copy->bv_len is the buffer size the line lenght is smaller + * and marked by \0 + */ +void dup_ldif_line(struct berval *copy, const char *line, const char *endline) +{ + const char *ptend = endline ? endline : line + strlen(line) + 1; + char *buf = copy->bv_val; + const char *pt = line; + int copylen = 0; + int pos = 0; + + /* Realloc the buffer if it is too small */ + if (buf == NULL || (ptend - line) > copy->bv_len) { + buf = copy->bv_val = slapi_ch_realloc(buf, ptend - line); + copy->bv_len = (ptend - line); + } + + /* endline was computed by ldif_getline_ro and is either the start of next line or the end of string */ + PR_ASSERT( endline == NULL || *endline == 0 || endline[-1] == '\n' ); + + while (pt && pt < ptend) { + line = pt; + /* Search end of line */ + while (pt < ptend && *pt != '\n' && *pt != 0) { + pt++; + } + PR_ASSERT(pt <= ptend); + copylen = pt - line; + if (copylen>0 && line[copylen-1] == '\r') { + copylen--; + } + if (copylen == 0) { + break; + } + if (*line == '#' && pos == 0) { + pt++; + continue; + } + strncpy(buf+pos, line, copylen); + pos += copylen; + pt += 2; /* Skip \n and continuation line space */ + } + buf[pos] = 0; + copy->bv_len = copylen; +} diff --git a/src/lib389/lib389/topologies.py b/src/lib389/lib389/topologies.py index f4f2dbe10d..102ea7716c 100644 --- a/src/lib389/lib389/topologies.py +++ b/src/lib389/lib389/topologies.py @@ -20,6 +20,7 @@ from lib389.cli_base import LogCapture TLS_HOSTNAME_CHECK = os.getenv('TLS_HOSTNAME_CHECK', default=True) +HAPROXY_TRUSTED_IP = os.getenv('HAPROXY_TRUSTED_IP', default='') DEBUGGING = os.getenv('DEBUGGING', default=False) if DEBUGGING: logging.getLogger(__name__).setLevel(logging.DEBUG) @@ -118,6 +119,8 @@ def _create_instances(topo_dict, suffix): instance.config.set('nsslapd-auditlog-logging-enabled','on') instance.config.set('nsslapd-auditfaillog-logging-enabled','on') instance.config.set('nsslapd-plugin-logging', 'on') + if HAPROXY_TRUSTED_IP: + instance.config.set('nsslapd-haproxy-trusted-ip', HAPROXY_TRUSTED_IP) log.info("Instance with parameters {} was created.".format(args_instance)) if "standalone1" in instances and len(instances) == 1: diff --git a/test/libslapd/haproxy/parse.c b/test/libslapd/haproxy/parse.c new file mode 100644 index 0000000000..d6a09fe7ec --- /dev/null +++ b/test/libslapd/haproxy/parse.c @@ -0,0 +1,321 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2023 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + +#include "../../test_slapd.h" +#include +#include + + +typedef struct test_input { + const char *input_str; + int expected_result; + size_t expected_len; + int expected_proxy_connection; + PRNetAddr expected_pr_netaddr_from; + PRNetAddr expected_pr_netaddr_dest; +} test_input; + +test_input test_cases[] = { + { + .input_str = "PROXY TCP4 192.168.0.1 192.168.0.2 12345 389\r\n", + .expected_result = HAPROXY_HEADER_PARSED, + .expected_len = 39, + .expected_proxy_connection = 1, + .expected_pr_netaddr_from = { .inet = { .family = PR_AF_INET, .ip = 0x0100A8C0, .port = 0x3930 }}, + .expected_pr_netaddr_dest = { .inet = { .family = PR_AF_INET, .ip = 0x0200A8C0, .port = 0x8501 }} + }, + { + .input_str = "PROXY TCP6 2001:db8::1 2001:db8::2 12345 389\r\n", + .expected_result = HAPROXY_HEADER_PARSED, + .expected_len = 46, + .expected_proxy_connection = 1, + .expected_pr_netaddr_from = { .ipv6 = { .family = PR_AF_INET6, .ip = {0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, .port = 0x3930 }}, + .expected_pr_netaddr_dest = { .ipv6 = { .family = PR_AF_INET6, .ip = {0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, .port = 0x8501 }}, + }, + { + .input_str = "PROXY TCP6 ::ffff:192.168.0.1 ::ffff:192.168.0.2 12345 389\r\n", + .expected_result = HAPROXY_HEADER_PARSED, + .expected_len = 54, + .expected_proxy_connection = 1, + .expected_pr_netaddr_from = { .ipv6 = { .family = PR_AF_INET6, .ip = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01}, .port = 0x3930 }}, + .expected_pr_netaddr_dest = { .ipv6 = { .family = PR_AF_INET6, .ip = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x02}, .port = 0x8501 }}, + }, + // Invalid IP + { + .input_str = "PROXY TCP4 256.168.0.1 192.168.0.2 12345 389\r\n", + .expected_result = HAPROXY_ERROR, + .expected_proxy_connection = 0, + }, + // Invalid port + { + .input_str = "PROXY TCP4 192.168.0.1 192.168.0.2 123456 389\r\n", + .expected_result = HAPROXY_ERROR, + .expected_proxy_connection = 0, + }, + // One port + { + .input_str = "PROXY TCP4 192.168.0.1 192.168.0.2 12345\r\n", + .expected_result = HAPROXY_ERROR, + .expected_proxy_connection = 0, + }, + // No ports + { + .input_str = "PROXY TCP4 192.168.0.1 192.168.0.2\r\n", + .expected_result = HAPROXY_ERROR, + .expected_proxy_connection = 0, + }, + // Empty string + { + .input_str = "", + .expected_result = HAPROXY_NOT_A_HEADER, + .expected_proxy_connection = 0, + }, + // Invalid protocol + { + .input_str = "PROXY TCP3 192.168.0.1 192.168.0.2 12345 389\r\n", + .expected_result = HAPROXY_ERROR, + .expected_proxy_connection = 0, + }, + // Missing protocol + { + .input_str = "PROXY 192.168.0.1 192.168.0.2 12345 389\r\n", + .expected_result = HAPROXY_ERROR, + .expected_proxy_connection = 0, + }, + +}; + +size_t num_tests = sizeof(test_cases) / sizeof(test_cases[0]); + +void test_libslapd_haproxy_v1(void **state) { + (void)state; + int result = 0; + + for (size_t i = 0; i < num_tests; i++) { + int proxy_connection = 0; + PRNetAddr pr_netaddr_from = {{0}}; + PRNetAddr pr_netaddr_dest = {{0}}; + size_t str_len = strlen(test_cases[i].input_str); + result = haproxy_parse_v1_hdr(test_cases[i].input_str, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); + + assert_int_equal(result, test_cases[i].expected_result); + assert_int_equal(proxy_connection, test_cases[i].expected_proxy_connection); + + if (test_cases[i].expected_result == 0) { + // slapi_log_error(SLAPI_LOG_ERR, "haproxy_parse_v1_hdr", "Expected pr_netaddr_from: "); + // slapi_log_prnetaddr(&test_cases[i].expected_pr_netaddr_from); + // slapi_log_error(SLAPI_LOG_ERR, "haproxy_parse_v1_hdr", "Actual pr_netaddr_from: "); + // slapi_log_prnetaddr(&pr_netaddr_from); + + // slapi_log_error(SLAPI_LOG_ERR, "haproxy_parse_v1_hdr", "Expected pr_netaddr_dest: "); + // slapi_log_prnetaddr(&test_cases[i].expected_pr_netaddr_dest); + // slapi_log_error(SLAPI_LOG_ERR, "haproxy_parse_v1_hdr", "Actual pr_netaddr_dest: "); + // slapi_log_prnetaddr(&pr_netaddr_dest); + + assert_memory_equal(&test_cases[i].expected_pr_netaddr_from, &pr_netaddr_from, sizeof(PRNetAddr)); + assert_memory_equal(&test_cases[i].expected_pr_netaddr_dest, &pr_netaddr_dest, sizeof(PRNetAddr)); + } + } +} + + +void test_libslapd_haproxy_v2_invalid(void **state) { + (void) state; // Unused + + struct { + char *desc; + char *str; + struct proxy_hdr_v2 hdr_v2; + size_t str_len; + int expected_result; + int expected_proxy_connection; + } tests[] = { + {"short header", + "short", + {}, + sizeof("short"), + HAPROXY_NOT_A_HEADER, + 0}, + {"invalid header", + "invalid_signature", + {}, + sizeof("invalid_signature"), + HAPROXY_NOT_A_HEADER, + 0}, + {"invalid signature", + NULL, + {"INVALID", PP2_VERSION | PP2_VER_CMD_PROXY, PP2_FAM_INET | PP2_TRANS_STREAM, htons(PP2_ADDR_LEN_INET)}, + PP2_HEADER_LEN + PP2_ADDR_LEN_INET, + HAPROXY_NOT_A_HEADER, + 0}, + {"unsupported family", + NULL, + {PP2_SIGNATURE, PP2_VERSION | PP2_VER_CMD_PROXY, 0x30 | PP2_TRANS_STREAM, htons(0)}, + PP2_HEADER_LEN, + HAPROXY_ERROR, + 0}, + {"unsupported protocol", + NULL, + {PP2_SIGNATURE, PP2_VERSION | PP2_VER_CMD_PROXY, PP2_FAM_INET | 0x30, htons(0)}, + PP2_HEADER_LEN, + HAPROXY_ERROR, + 0}, + {"invalid version", + NULL, + {PP2_SIGNATURE, (PP2_VERSION ^ 0xF0) | PP2_VER_CMD_PROXY, PP2_FAM_INET | PP2_TRANS_STREAM, htons(PP2_ADDR_LEN_INET)}, + PP2_HEADER_LEN + PP2_ADDR_LEN_INET, + HAPROXY_ERROR, + 0}, + {"valid header, wrong command", + NULL, + {PP2_SIGNATURE, PP2_VERSION | (PP2_VER_CMD_PROXY ^ 0xF0), PP2_FAM_INET | PP2_TRANS_STREAM, htons(PP2_ADDR_LEN_INET)}, + PP2_HEADER_LEN + PP2_ADDR_LEN_INET, + HAPROXY_ERROR, + 0}, + {"valid header, too long", + NULL, + {PP2_SIGNATURE, PP2_VERSION | PP2_VER_CMD_PROXY, PP2_FAM_INET | PP2_TRANS_STREAM, htons(PP2_ADDR_LEN_INET * 2)}, + PP2_HEADER_LEN + PP2_ADDR_LEN_INET, + HAPROXY_ERROR, + 0} + }; + + for (int i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + int proxy_connection; + PRNetAddr pr_netaddr_from; + PRNetAddr pr_netaddr_dest; + char *str_to_test = tests[i].str ? tests[i].str : (char *) &tests[i].hdr_v2; + + int result = haproxy_parse_v2_hdr(str_to_test, &tests[i].str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); + + assert_int_equal(result, tests[i].expected_result); + assert_int_equal(proxy_connection, tests[i].expected_proxy_connection); + } +} + +// Test for a valid proxy header v2 with unsupported transport protocol +void test_libslapd_haproxy_v2_unsupported_protocol(void **state) { + (void) state; // Unused + + // Create a sample string with valid proxy header v2 and unsupported transport protocol + struct proxy_hdr_v2 hdr_v2; + memcpy(hdr_v2.sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN); + hdr_v2.ver_cmd = PP2_VERSION | PP2_VER_CMD_PROXY; + hdr_v2.fam = PP2_FAM_INET | 0x30; // 0x30 is unsupported + hdr_v2.len = htons(0); + + size_t str_len = PP2_HEADER_LEN; + int proxy_connection; + PRNetAddr pr_netaddr_from; + PRNetAddr pr_netaddr_dest; + + int result = haproxy_parse_v2_hdr((const char *) &hdr_v2, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); + + assert_int_equal(result, HAPROXY_ERROR); + assert_int_equal(proxy_connection, 0); +} + + +// Test for the case when the protocol version is invalid +void test_libslapd_haproxy_v2_invalid_version(void **state) { + (void) state; // Unused + + // Create a sample string with an invalid protocol version + struct proxy_hdr_v2 hdr_v2; + memcpy(hdr_v2.sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN); + hdr_v2.ver_cmd = (PP2_VERSION ^ 0xF0) | PP2_VER_CMD_PROXY; + hdr_v2.fam = PP2_FAM_INET | PP2_TRANS_STREAM; + hdr_v2.len = htons(PP2_ADDR_LEN_INET); + + size_t str_len = PP2_HEADER_LEN + PP2_ADDR_LEN_INET; + int proxy_connection; + PRNetAddr pr_netaddr_from; + PRNetAddr pr_netaddr_dest; + + int result = haproxy_parse_v2_hdr((const char *) &hdr_v2, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); + + assert_int_equal(result, HAPROXY_ERROR); + assert_int_equal(proxy_connection, 0); +} + + +// Test for the case when the protocol command is valid - IPv4 and IPv6 +void test_libslapd_haproxy_v2_valid(void **state) { + (void) state; // Unused + + // We need only first two test cases as they are valid + size_t num_tests = 2; + + for (size_t i = 0; i < num_tests; i++) { + struct proxy_hdr_v2 hdr_v2; + memcpy(hdr_v2.sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN); + hdr_v2.ver_cmd = PP2_VERSION | PP2_VER_CMD_PROXY; + + size_t str_len; + int proxy_connection; + PRNetAddr pr_netaddr_from = {0}; + PRNetAddr pr_netaddr_dest = {0}; + + if (i == 0) { // IPv4 test case + hdr_v2.fam = PP2_FAM_INET | PP2_TRANS_STREAM; + hdr_v2.len = htons(PP2_ADDR_LEN_INET); + uint32_t src_addr = test_cases[i].expected_pr_netaddr_from.inet.ip; + uint32_t dst_addr = test_cases[i].expected_pr_netaddr_dest.inet.ip; + uint16_t src_port = test_cases[i].expected_pr_netaddr_from.inet.port; + uint16_t dst_port = test_cases[i].expected_pr_netaddr_dest.inet.port; + memcpy(&hdr_v2.addr.ip4.src_addr, &src_addr, sizeof(src_addr)); + memcpy(&hdr_v2.addr.ip4.dst_addr, &dst_addr, sizeof(dst_addr)); + memcpy(&hdr_v2.addr.ip4.src_port, &src_port, sizeof(src_port)); + memcpy(&hdr_v2.addr.ip4.dst_port, &dst_port, sizeof(dst_port)); + str_len = PP2_HEADER_LEN + PP2_ADDR_LEN_INET; + } else { // IPv6 test case + hdr_v2.fam = PP2_FAM_INET6 | PP2_TRANS_STREAM; + hdr_v2.len = htons(PP2_ADDR_LEN_INET6); + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + memcpy(src_addr, &test_cases[i].expected_pr_netaddr_from.ipv6.ip, sizeof(src_addr)); + memcpy(dst_addr, &test_cases[i].expected_pr_netaddr_dest.ipv6.ip, sizeof(dst_addr)); + uint16_t src_port = test_cases[i].expected_pr_netaddr_from.ipv6.port; + uint16_t dst_port = test_cases[i].expected_pr_netaddr_dest.ipv6.port; + memcpy(&hdr_v2.addr.ip6.src_addr, src_addr, sizeof(src_addr)); + memcpy(&hdr_v2.addr.ip6.dst_addr, dst_addr, sizeof(dst_addr)); + memcpy(&hdr_v2.addr.ip6.src_port, &src_port, sizeof(src_port)); + memcpy(&hdr_v2.addr.ip6.dst_port, &dst_port, sizeof(dst_port)); + str_len = PP2_HEADER_LEN + PP2_ADDR_LEN_INET6; + } + + int rc = haproxy_parse_v2_hdr((const char *) &hdr_v2, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); + + assert_int_equal(rc, HAPROXY_HEADER_PARSED); + assert_int_equal(proxy_connection, 1); + assert_memory_equal(&pr_netaddr_from, &test_cases[i].expected_pr_netaddr_from, sizeof(PRNetAddr)); + assert_memory_equal(&pr_netaddr_dest, &test_cases[i].expected_pr_netaddr_dest, sizeof(PRNetAddr)); + } +} + + +// Test for a valid proxy header v2 with LOCAL command +void test_libslapd_haproxy_v2_valid_local(void **state) { + (void) state; // Unused + // Create a sample string with valid proxy header v2 and LOCAL command + struct proxy_hdr_v2 hdr_v2; + memcpy(hdr_v2.sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN); + hdr_v2.ver_cmd = PP2_VERSION | PP2_VER_CMD_LOCAL; + hdr_v2.fam = PP2_FAM_INET | PP2_TRANS_STREAM; + hdr_v2.len = htons(0); + + size_t str_len = PP2_HEADER_LEN; + int proxy_connection; + PRNetAddr pr_netaddr_from; + PRNetAddr pr_netaddr_dest; + + int result = haproxy_parse_v2_hdr((const char *) &hdr_v2, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); + + assert_int_equal(result, HAPROXY_HEADER_PARSED); + assert_int_equal(proxy_connection, 0); +} \ No newline at end of file diff --git a/test/libslapd/test.c b/test/libslapd/test.c index 0a81076bfa..36ee8139f9 100644 --- a/test/libslapd/test.c +++ b/test/libslapd/test.c @@ -32,6 +32,10 @@ run_libslapd_tests(void) cmocka_unit_test(test_libslapd_counters_atomic_overflow), cmocka_unit_test(test_libslapd_pal_meminfo), cmocka_unit_test(test_libslapd_util_cachesane), + cmocka_unit_test(test_libslapd_haproxy_v1), + cmocka_unit_test(test_libslapd_haproxy_v2_valid), + cmocka_unit_test(test_libslapd_haproxy_v2_valid_local), + cmocka_unit_test(test_libslapd_haproxy_v2_invalid), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/test_slapd.h b/test/test_slapd.h index f517e9ddfb..93e9775809 100644 --- a/test/test_slapd.h +++ b/test/test_slapd.h @@ -52,6 +52,12 @@ void test_libslapd_counters_atomic_overflow(void **state); void test_libslapd_pal_meminfo(void **state); void test_libslapd_util_cachesane(void **state); +/* libslapd-haproxy */ +void test_libslapd_haproxy_v1(void **state); +void test_libslapd_haproxy_v2_valid(void **state); +void test_libslapd_haproxy_v2_valid_local(void **state); +void test_libslapd_haproxy_v2_invalid(void **state); + /* plugins */ void test_plugin_hello(void **state);