diff options
| author | 2024-07-17 00:06:50 +0100 | |
|---|---|---|
| committer | 2024-07-17 00:06:50 +0100 | |
| commit | 889d521e05f2e922683d48c74eb00290e7a2f548 (patch) | |
| tree | b4c56bd4cdf0881601c7e4d6ff571e491af10bf4 /src/modules/m_haproxy.cpp | |
| parent | Use std::endian from C++20 in the sha1 module. (diff) | |
Shuffle the modules about a bit.
Diffstat (limited to 'src/modules/m_haproxy.cpp')
| -rw-r--r-- | src/modules/m_haproxy.cpp | 437 |
1 files changed, 0 insertions, 437 deletions
diff --git a/src/modules/m_haproxy.cpp b/src/modules/m_haproxy.cpp deleted file mode 100644 index 101204328..000000000 --- a/src/modules/m_haproxy.cpp +++ /dev/null @@ -1,437 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2021 Dominic Hamon - * Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com> - * Copyright (C) 2018-2019, 2021-2023 Sadie Powell <sadie@witchery.services> - * - * This file is part of InspIRCd. InspIRCd 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, version 2. - * - * 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, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "iohook.h" -#include "modules/ssl.h" - -enum -{ - // The TLV flag for a client being connected over TLS. - PP2_CLIENT_SSL = 0x01, - - // The family for TCP over IPv4. - PP2_FAMILY_IPV4 = 0x11, - - // The length of the PP2_FAMILY_IPV4 endpoints. - PP2_FAMILY_IPV4_LENGTH = 12, - - // The family for TCP over IPv6. - PP2_FAMILY_IPV6 = 0x21, - - // The length of the PP2_FAMILY_IPV6 endpoints. - PP2_FAMILY_IPV6_LENGTH = 36, - - // The family for UNIX sockets. - PP2_FAMILY_UNIX = 0x31, - - // The length of the PP2_FAMILY_UNIX endpoints. - PP2_FAMILY_UNIX_LENGTH = 216, - - // The bitmask we apply to extract the command. - PP2_COMMAND_MASK = 0x0F, - - // The length of the PROXY protocol header. - PP2_HEADER_LENGTH = 16, - - // The minimum length of a Type-Length-Value entry. - PP2_TLV_LENGTH = 3, - - // The identifier for a TLS TLV entry. - PP2_TYPE_SSL = 0x20, - - // The minimum length of a PP2_TYPE_SSL TLV entry. - PP2_TYPE_SSL_LENGTH = 5, - - // The length of the PROXY protocol signature. - PP2_SIGNATURE_LENGTH = 12, - - // The PROXY protocol version we support. - PP2_VERSION = 0x20, - - // The bitmask we apply to extract the protocol version. - PP2_VERSION_MASK = 0xF0 -}; - -enum HAProxyState -{ - // We are waiting for the PROXY header section. - HPS_WAITING_FOR_HEADER, - - // We are waiting for the PROXY address section. - HPS_WAITING_FOR_ADDRESS, - - // The client is fully connected. - HPS_CONNECTED -}; - -enum HAProxyCommand -{ - // LOCAL command. - HPC_LOCAL = 0x00, - - // PROXY command. - HPC_PROXY = 0x01 -}; - -struct HAProxyHeader final -{ - // The signature used to identify the HAProxy protocol. - uint8_t signature[PP2_SIGNATURE_LENGTH]; - - // The version of the PROXY protocol and command being sent. - uint8_t version_command; - - // The family for the address. - uint8_t family; - - // The length of the address section. - uint16_t length; -}; - -class HAProxyHookProvider final - : public IOHookProvider -{ -private: - UserCertificateAPI sslapi; - -public: - HAProxyHookProvider(Module* mod) - : IOHookProvider(mod, "haproxy", IOHookProvider::IOH_UNKNOWN, true) - , sslapi(mod) - { - } - - void OnAccept(StreamSocket* sock, const irc::sockets::sockaddrs& client, const irc::sockets::sockaddrs& server) override; - - void OnConnect(StreamSocket* sock) override - { - // We don't need to implement this. - } -}; - -// The signature for a HAProxy PROXY protocol header. -static constexpr char proxy_signature[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; - -class HAProxyHook final - : public IOHookMiddle -{ -private: - // The length of the address section. - uint16_t address_length = 0; - - // The endpoint the client is connecting from. - irc::sockets::sockaddrs client; - - // The command sent by the proxy server. - HAProxyCommand command; - - // The endpoint the client is connected to. - irc::sockets::sockaddrs server; - - // The API for interacting with user TLS internals. - UserCertificateAPI& sslapi; - - // The current state of the PROXY parser. - HAProxyState state = HPS_WAITING_FOR_HEADER; - - size_t ReadProxyTLV(StreamSocket* sock, size_t start_index, uint16_t buffer_length) - { - // A TLV must at least consist of a type (uint8_t) and a length (uint16_t). - if (buffer_length < PP2_TLV_LENGTH) - { - sock->SetError("Truncated HAProxy PROXY TLV type and/or length"); - return 0; - } - - // Check that the length can actually contain the TLV value. - std::string& recvq = GetRecvQ(); - uint16_t length = ntohs(recvq[start_index + 1] | (recvq[start_index + 2] << 8)); - if (buffer_length < PP2_TLV_LENGTH + length) - { - sock->SetError("Truncated HAProxy PROXY TLV value"); - return 0; - } - - // What type of TLV are we parsing? - switch (recvq[start_index]) - { - case PP2_TYPE_SSL: - if (!ReadProxyTLVSSL(sock, start_index + PP2_TLV_LENGTH, length)) - return 0; - break; - } - - return PP2_TLV_LENGTH + length; - } - - bool ReadProxyTLVSSL(StreamSocket* sock, size_t start_index, uint16_t buffer_length) - { - // A TLS TLV must at least consist of client info (uint8_t) and verification info (uint32_t). - if (buffer_length < PP2_TYPE_SSL_LENGTH) - { - sock->SetError("Truncated HAProxy PROXY TLS TLV"); - return false; - } - - // If the socket is not a user socket we don't have to do - // anything with this TLVs information. - if (sock->type != StreamSocket::SS_USER) - return true; - - // If the sslinfo module is not loaded we can't - // do anything with this TLV. - if (!sslapi) - return true; - - // If the client is not connecting via TLS the rest of this TLV is irrelevant. - std::string& recvq = GetRecvQ(); - if ((recvq[start_index] & PP2_CLIENT_SSL) == 0) - return true; - - // Create a fake ssl_cert for the user. Ideally we should use the user's - // TLS client certificate here but as of 2018-10-16 this is not forwarded - // by HAProxy. - auto* cert = new ssl_cert(); - cert->error = "HAProxy does not forward client TLS certificates"; - cert->invalid = true; - cert->revoked = true; - cert->trusted = false; - cert->unknownsigner = true; - - // Extract the user for this socket and set their certificate. - LocalUser* luser = static_cast<UserIOHandler*>(sock)->user; - sslapi->SetCertificate(luser, cert); - return true; - } - - int ReadData(std::string& destrecvq) - { - // Once connected we handle no special data. - std::string& recvq = GetRecvQ(); - destrecvq.append(recvq); - recvq.clear(); - return 1; - } - - int ReadProxyAddress(StreamSocket* sock, std::string& destrecvq) - { - // Block until we have the entire address. - std::string& recvq = GetRecvQ(); - if (recvq.length() < address_length) - return 0; - - switch (command) - { - case HPC_LOCAL: - // Skip the address completely. - recvq.erase(0, address_length); - break; - - case HPC_PROXY: - // Store the endpoint information. - size_t tlv_index = 0; - switch (client.family()) - { - case AF_INET: - memcpy(&client.in4.sin_addr.s_addr, &recvq[0], 4); - memcpy(&server.in4.sin_addr.s_addr, &recvq[4], 4); - memcpy(&client.in4.sin_port, &recvq[8], 2); - memcpy(&server.in4.sin_port, &recvq[10], 2); - tlv_index = 12; - break; - - case AF_INET6: - memcpy(client.in6.sin6_addr.s6_addr, &recvq[0], 16); - memcpy(server.in6.sin6_addr.s6_addr, &recvq[16], 16); - memcpy(&client.in6.sin6_port, &recvq[32], 2); - memcpy(&server.in6.sin6_port, &recvq[34], 2); - tlv_index = 36; - break; - - case AF_UNIX: - memcpy(client.un.sun_path, &recvq[0], 108); - memcpy(server.un.sun_path, &recvq[108], 108); - tlv_index = 216; - break; - } - - if (!sock->OnChangeLocalSocketAddress(server) || !sock->OnChangeRemoteSocketAddress(client)) - return -1; - - // Parse any available TLVs. - while (tlv_index < address_length) - { - size_t length = ReadProxyTLV(sock, tlv_index, address_length - tlv_index); - if (!length) - return -1; - - tlv_index += length; - } - - // Erase the processed proxy information from the receive queue. - recvq.erase(0, address_length); - break; - } - - // We're done! - state = HPS_CONNECTED; - return ReadData(destrecvq); - } - - int ReadProxyHeader(StreamSocket* sock, std::string& destrecvq) - { - // Block until we have a header. - std::string& recvq = GetRecvQ(); - if (recvq.length() < PP2_HEADER_LENGTH) - return 0; - - // Read the header. - HAProxyHeader header; - memcpy(&header, recvq.c_str(), PP2_HEADER_LENGTH); - recvq.erase(0, PP2_HEADER_LENGTH); - - // Check we are actually parsing a HAProxy header. - if (memcmp(&header.signature, proxy_signature, PP2_SIGNATURE_LENGTH) != 0) - { - // If we've reached this point the proxy server did not send a proxy information. - sock->SetError("Invalid HAProxy PROXY signature"); - return -1; - } - - // We only support this version of the protocol. - const uint8_t version = (header.version_command & PP2_VERSION_MASK); - if (version != PP2_VERSION) - { - sock->SetError("Unsupported HAProxy PROXY protocol version"); - return -1; - } - - // We only support the LOCAL and PROXY commands. - command = static_cast<HAProxyCommand>(header.version_command & PP2_COMMAND_MASK); - switch (command) - { - case HPC_LOCAL: - // Intentionally left blank. - break; - - case HPC_PROXY: - // Check the protocol support and initialise the sockaddrs. - uint16_t shortest_length; - switch (header.family) - { - case PP2_FAMILY_IPV4: // TCP over IPv4. - client.sa.sa_family = server.sa.sa_family = AF_INET; - shortest_length = PP2_FAMILY_IPV4_LENGTH; - break; - - case PP2_FAMILY_IPV6: // TCP over IPv6. - client.sa.sa_family = server.sa.sa_family = AF_INET6; - shortest_length = PP2_FAMILY_IPV6_LENGTH; - break; - - case PP2_FAMILY_UNIX: // UNIX stream. - client.sa.sa_family = server.sa.sa_family = AF_UNIX; - shortest_length = PP2_FAMILY_UNIX_LENGTH; - break; - - default: // Unknown protocol. - sock->SetError("Invalid HAProxy PROXY protocol type"); - return -1; - } - - // Check that the length can actually contain the addresses. - address_length = ntohs(header.length); - if (address_length < shortest_length) - { - sock->SetError("Truncated HAProxy PROXY address section"); - return -1; - } - break; - - default: - sock->SetError("Unsupported HAProxy PROXY command"); - return -1; - } - - state = HPS_WAITING_FOR_ADDRESS; - return ReadProxyAddress(sock, destrecvq); - } - -public: - HAProxyHook(const std::shared_ptr<IOHookProvider>& Prov, StreamSocket* sock, UserCertificateAPI& api) - : IOHookMiddle(Prov) - , sslapi(api) - { - sock->AddIOHook(this); - } - - bool IsHookReady() const override - { - return state == HPS_CONNECTED; - } - - ssize_t OnStreamSocketWrite(StreamSocket* sock, StreamSocket::SendQueue& uppersendq) override - { - // We don't need to implement this. - GetSendQ().moveall(uppersendq); - return 1; - } - - ssize_t OnStreamSocketRead(StreamSocket* sock, std::string& destrecvq) override - { - switch (state) - { - case HPS_WAITING_FOR_HEADER: - return ReadProxyHeader(sock, destrecvq); - - case HPS_WAITING_FOR_ADDRESS: - return ReadProxyAddress(sock, destrecvq); - - case HPS_CONNECTED: - return ReadData(destrecvq); - } - - // We should never reach this point. - return -1; - } -}; - -void HAProxyHookProvider::OnAccept(StreamSocket* sock, const irc::sockets::sockaddrs& client, const irc::sockets::sockaddrs& server) -{ - new HAProxyHook(shared_from_this(), sock, sslapi); -} - -class ModuleHAProxy final - : public Module -{ -private: - std::shared_ptr<HAProxyHookProvider> hookprov; - -public: - ModuleHAProxy() - : Module(VF_VENDOR, "Allows IRC connections to be made using reverse proxies that implement version 2 of the HAProxy PROXY protocol.") - , hookprov(std::make_shared<HAProxyHookProvider>(this)) - { - } -}; - -MODULE_INIT(ModuleHAProxy) |
