Some refactoring allowing better use as a library
This commit is contained in:
parent
e8b218e316
commit
5ae25e8aba
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -52,9 +52,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.56"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
|
||||
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2007,6 +2007,7 @@ name = "xmpp-proxy"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"data-encoding",
|
||||
"die",
|
||||
"env_logger",
|
||||
|
@ -35,6 +35,7 @@ anyhow = "1.0"
|
||||
tokio = { version = "1.9", features = ["net", "rt", "rt-multi-thread", "macros", "io-util", "signal"] }
|
||||
ring = "0.16"
|
||||
data-encoding = "2.3"
|
||||
async-trait = "0.1.64"
|
||||
|
||||
|
||||
# logging deps
|
||||
|
467
contrib/prosody-modules/mod_net_proxy.lua
Normal file
467
contrib/prosody-modules/mod_net_proxy.lua
Normal file
@ -0,0 +1,467 @@
|
||||
-- mod_net_proxy.lua
|
||||
-- Copyright (C) 2018 Pascal Mathis <mail@pascalmathis.com>
|
||||
--
|
||||
-- Implementation of PROXY protocol versions 1 and 2
|
||||
-- Specifications: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
||||
|
||||
module:set_global();
|
||||
|
||||
-- Imports
|
||||
local softreq = require "util.dependencies".softreq;
|
||||
local bit = assert(softreq "bit" or softreq "bit32" or softreq "util.bitcompat", "No bit module found. See https://prosody.im/doc/depends#bitop");
|
||||
local hex = require "util.hex";
|
||||
local ip = require "util.ip";
|
||||
local net = require "util.net";
|
||||
local set = require "util.set";
|
||||
local portmanager = require "core.portmanager";
|
||||
|
||||
-- Backwards Compatibility
|
||||
local function net_ntop_bc(input)
|
||||
if input:len() == 4 then
|
||||
return string.format("%d.%d.%d.%d", input:byte(1, 4));
|
||||
elseif input:len() == 16 then
|
||||
local octets = { nil, nil, nil, nil, nil, nil, nil, nil };
|
||||
|
||||
-- Convert received bytes into IPv6 address and skip leading zeroes for each group
|
||||
for index = 1, 8 do
|
||||
local high, low = input:byte(index * 2 - 1, index * 2);
|
||||
octets[index] = string.format("%x", high * 256 + low);
|
||||
end
|
||||
local address = table.concat(octets, ":", 1, 8);
|
||||
|
||||
-- Search for the longest sequence of zeroes
|
||||
local token;
|
||||
local length = (address:match("^0:[0:]+()") or 1) - 1;
|
||||
for s in address:gmatch(":0:[0:]+") do
|
||||
if length < #s then
|
||||
length, token = #s, s;
|
||||
end
|
||||
end
|
||||
|
||||
-- Return the shortened IPv6 address
|
||||
return address:gsub(token or "^0:[0:]+", "::", 1);
|
||||
end
|
||||
end
|
||||
|
||||
local net_ntop = net.ntop or net_ntop_bc
|
||||
|
||||
-- Utility Functions
|
||||
local function _table_invert(input)
|
||||
local output = {};
|
||||
for key, value in pairs(input) do
|
||||
output[value] = key;
|
||||
end
|
||||
return output;
|
||||
end
|
||||
|
||||
-- Constants
|
||||
local ADDR_FAMILY = { UNSPEC = 0x0, INET = 0x1, INET6 = 0x2, UNIX = 0x3 };
|
||||
local ADDR_FAMILY_STR = _table_invert(ADDR_FAMILY);
|
||||
local TRANSPORT = { UNSPEC = 0x0, STREAM = 0x1, DGRAM = 0x2 };
|
||||
local TRANSPORT_STR = _table_invert(TRANSPORT);
|
||||
|
||||
local PROTO_MAX_HEADER_LENGTH = 256;
|
||||
local PROTO_HANDLERS = {
|
||||
PROXYv1 = { signature = hex.from("50524F5859"), callback = nil },
|
||||
PROXYv2 = { signature = hex.from("0D0A0D0A000D0A515549540A"), callback = nil }
|
||||
};
|
||||
local PROTO_HANDLER_STATUS = { SUCCESS = 0, POSTPONE = 1, FAILURE = 2 };
|
||||
|
||||
-- Configuration Variables
|
||||
local config_mappings = module:get_option("proxy_port_mappings", {});
|
||||
local config_ports = module:get_option_set("proxy_ports", {});
|
||||
local config_trusted_proxies = module:get_option_set("proxy_trusted_proxies", {"127.0.0.1", "::1"});
|
||||
|
||||
-- Persistent In-Memory Storage
|
||||
local sessions = {};
|
||||
local mappings = {};
|
||||
local trusted_networks = set.new();
|
||||
|
||||
-- Proxy Data Methods
|
||||
local proxy_data_mt = {}; proxy_data_mt.__index = proxy_data_mt;
|
||||
|
||||
function proxy_data_mt:describe()
|
||||
return string.format("proto=%s/%s src=%s:%d dst=%s:%d",
|
||||
self:addr_family_str(), self:transport_str(), self:src_addr(), self:src_port(), self:dst_addr(), self:dst_port());
|
||||
end
|
||||
|
||||
function proxy_data_mt:addr_family_str()
|
||||
return ADDR_FAMILY_STR[self._addr_family] or ADDR_FAMILY_STR[ADDR_FAMILY.UNSPEC];
|
||||
end
|
||||
|
||||
function proxy_data_mt:transport_str()
|
||||
return TRANSPORT_STR[self._transport] or TRANSPORT_STR[TRANSPORT.UNSPEC];
|
||||
end
|
||||
|
||||
function proxy_data_mt:version()
|
||||
return self._version;
|
||||
end
|
||||
|
||||
function proxy_data_mt:addr_family()
|
||||
return self._addr_family;
|
||||
end
|
||||
|
||||
function proxy_data_mt:transport()
|
||||
return self._transport;
|
||||
end
|
||||
|
||||
function proxy_data_mt:src_addr()
|
||||
return self._src_addr;
|
||||
end
|
||||
|
||||
function proxy_data_mt:src_port()
|
||||
return self._src_port;
|
||||
end
|
||||
|
||||
function proxy_data_mt:dst_addr()
|
||||
return self._dst_addr;
|
||||
end
|
||||
|
||||
function proxy_data_mt:dst_port()
|
||||
return self._dst_port;
|
||||
end
|
||||
|
||||
-- Protocol Handler Functions
|
||||
PROTO_HANDLERS["PROXYv1"].callback = function(conn, session)
|
||||
local addr_family_mappings = { TCP4 = ADDR_FAMILY.INET, TCP6 = ADDR_FAMILY.INET6 };
|
||||
|
||||
-- Postpone processing if CRLF (PROXYv1 header terminator) does not exist within buffer
|
||||
if session.buffer:find("\r\n") == nil then
|
||||
return PROTO_HANDLER_STATUS.POSTPONE, nil;
|
||||
end
|
||||
|
||||
-- Declare header pattern and match current buffer against pattern
|
||||
local header_pattern = "^PROXY (%S+) (%S+) (%S+) (%d+) (%d+)\r\n";
|
||||
local addr_family, src_addr, dst_addr, src_port, dst_port = session.buffer:match(header_pattern);
|
||||
src_port, dst_port = tonumber(src_port), tonumber(dst_port);
|
||||
|
||||
-- Ensure that header was successfully parsed and contains a valid address family
|
||||
if addr_family == nil or src_addr == nil or dst_addr == nil or src_port == nil or dst_port == nil then
|
||||
module:log("warn", "Received unparseable PROXYv1 header from %s", conn:ip());
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
if addr_family_mappings[addr_family] == nil then
|
||||
module:log("warn", "Received invalid PROXYv1 address family from %s: %s", conn:ip(), addr_family);
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
|
||||
-- Ensure that received source and destination ports are within 1 and 65535 (0xFFFF)
|
||||
if src_port <= 0 or src_port >= 0xFFFF then
|
||||
module:log("warn", "Received invalid PROXYv1 source port from %s: %d", conn:ip(), src_port);
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
if dst_port <= 0 or dst_port >= 0xFFFF then
|
||||
module:log("warn", "Received invalid PROXYv1 destination port from %s: %d", conn:ip(), dst_port);
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
|
||||
-- Ensure that received source and destination address can be parsed
|
||||
local _, err = ip.new_ip(src_addr);
|
||||
if err ~= nil then
|
||||
module:log("warn", "Received unparseable PROXYv1 source address from %s: %s", conn:ip(), src_addr);
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
_, err = ip.new_ip(dst_addr);
|
||||
if err ~= nil then
|
||||
module:log("warn", "Received unparseable PROXYv1 destination address from %s: %s", conn:ip(), dst_addr);
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
|
||||
-- Strip parsed header from session buffer and build proxy data
|
||||
session.buffer = session.buffer:gsub(header_pattern, "");
|
||||
|
||||
local proxy_data = {
|
||||
_version = 1,
|
||||
_addr_family = addr_family, _transport = TRANSPORT.STREAM,
|
||||
_src_addr = src_addr, _src_port = src_port,
|
||||
_dst_addr = dst_addr, _dst_port = dst_port
|
||||
};
|
||||
setmetatable(proxy_data, proxy_data_mt);
|
||||
|
||||
-- Return successful response with gathered proxy data
|
||||
return PROTO_HANDLER_STATUS.SUCCESS, proxy_data;
|
||||
end
|
||||
|
||||
PROTO_HANDLERS["PROXYv2"].callback = function(conn, session)
|
||||
-- Postpone processing if less than 16 bytes are available
|
||||
if #session.buffer < 16 then
|
||||
return PROTO_HANDLER_STATUS.POSTPONE, nil;
|
||||
end
|
||||
|
||||
-- Parse first 16 bytes of protocol header
|
||||
local version = bit.rshift(bit.band(session.buffer:byte(13), 0xF0), 4);
|
||||
local command = bit.band(session.buffer:byte(13), 0x0F);
|
||||
local addr_family = bit.rshift(bit.band(session.buffer:byte(14), 0xF0), 4);
|
||||
local transport = bit.band(session.buffer:byte(14), 0x0F);
|
||||
local length = bit.bor(session.buffer:byte(16), bit.lshift(session.buffer:byte(15), 8));
|
||||
|
||||
-- Postpone processing if less than 16+<length> bytes are available
|
||||
if #session.buffer < 16 + length then
|
||||
return PROTO_HANDLER_STATUS.POSTPONE, nil;
|
||||
end
|
||||
|
||||
-- Ensure that version number is correct
|
||||
if version ~= 0x2 then
|
||||
module:log("warn", "Received unsupported PROXYv2 version from %s: %d", conn:ip(), version);
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
|
||||
local payload = session.buffer:sub(17);
|
||||
if command == 0x0 then
|
||||
-- Gather source/destination addresses and ports from local socket
|
||||
local src_addr, src_port = conn:socket():getpeername();
|
||||
local dst_addr, dst_port = conn:socket():getsockname();
|
||||
|
||||
-- Build proxy data based on real connection information
|
||||
local proxy_data = {
|
||||
_version = version,
|
||||
_addr_family = addr_family, _transport = transport,
|
||||
_src_addr = src_addr, _src_port = src_port,
|
||||
_dst_addr = dst_addr, _dst_port = dst_port
|
||||
};
|
||||
setmetatable(proxy_data, proxy_data_mt);
|
||||
|
||||
-- Return successful response with gathered proxy data
|
||||
return PROTO_HANDLER_STATUS.SUCCESS, proxy_data;
|
||||
elseif command == 0x1 then
|
||||
local offset = 1;
|
||||
local src_addr, src_port, dst_addr, dst_port;
|
||||
|
||||
-- Verify transport protocol is either STREAM or DGRAM
|
||||
if transport ~= TRANSPORT.STREAM and transport ~= TRANSPORT.DGRAM then
|
||||
module:log("warn", "Received unsupported PROXYv2 transport from %s: 0x%02X", conn:ip(), transport);
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
|
||||
-- Parse source and destination addresses
|
||||
if addr_family == ADDR_FAMILY.INET then
|
||||
src_addr = net_ntop(payload:sub(offset, offset + 3)); offset = offset + 4;
|
||||
dst_addr = net_ntop(payload:sub(offset, offset + 3)); offset = offset + 4;
|
||||
elseif addr_family == ADDR_FAMILY.INET6 then
|
||||
src_addr = net_ntop(payload:sub(offset, offset + 15)); offset = offset + 16;
|
||||
dst_addr = net_ntop(payload:sub(offset, offset + 15)); offset = offset + 16;
|
||||
elseif addr_family == ADDR_FAMILY.UNIX then
|
||||
src_addr = payload:sub(offset, offset + 107); offset = offset + 108;
|
||||
dst_addr = payload:sub(offset, offset + 107); offset = offset + 108;
|
||||
end
|
||||
|
||||
-- Parse source and destination ports
|
||||
if addr_family == ADDR_FAMILY.INET or addr_family == ADDR_FAMILY.INET6 then
|
||||
src_port = bit.bor(payload:byte(offset + 1), bit.lshift(payload:byte(offset), 8)); offset = offset + 2;
|
||||
-- luacheck: ignore 311
|
||||
dst_port = bit.bor(payload:byte(offset + 1), bit.lshift(payload:byte(offset), 8)); offset = offset + 2;
|
||||
end
|
||||
|
||||
-- Strip parsed header from session buffer and build proxy data
|
||||
session.buffer = session.buffer:sub(17 + length);
|
||||
|
||||
local proxy_data = {
|
||||
_version = version,
|
||||
_addr_family = addr_family, _transport = transport,
|
||||
_src_addr = src_addr, _src_port = src_port,
|
||||
_dst_addr = dst_addr, _dst_port = dst_port
|
||||
};
|
||||
setmetatable(proxy_data, proxy_data_mt);
|
||||
|
||||
-- Return successful response with gathered proxy data
|
||||
return PROTO_HANDLER_STATUS.SUCCESS, proxy_data;
|
||||
else
|
||||
module:log("warn", "Received unsupported PROXYv2 command from %s: 0x%02X", conn:ip(), command);
|
||||
return PROTO_HANDLER_STATUS.FAILURE, nil;
|
||||
end
|
||||
end
|
||||
|
||||
-- Wrap an existing connection with the provided proxy data. This will override several methods of the 'conn' object to
|
||||
-- return the proxied source instead of the source which initiated the TCP connection. Afterwards, the listener of the
|
||||
-- connection gets set according to the globally defined port<>service mappings and the methods 'onconnect' and
|
||||
-- 'onincoming' are being called manually with the current session buffer.
|
||||
local function wrap_proxy_connection(conn, session, proxy_data)
|
||||
-- Override and add functions of 'conn' object when source information has been collected
|
||||
conn.proxyip, conn.proxyport = conn.ip, conn.port;
|
||||
if proxy_data:src_addr() ~= nil and proxy_data:src_port() ~= nil then
|
||||
conn.ip = function()
|
||||
return proxy_data:src_addr();
|
||||
end
|
||||
conn.port = function()
|
||||
return proxy_data:src_port();
|
||||
end
|
||||
conn.clientport = conn.port;
|
||||
end
|
||||
|
||||
-- Attempt to find service by processing port<>service mappings
|
||||
local mapping = mappings[tonumber(conn:serverport())];
|
||||
if mapping == nil then
|
||||
conn:close();
|
||||
module:log("warn", "Connection %s@%s terminated: Could not find mapping for port %d",
|
||||
conn:ip(), conn:proxyip(), conn:serverport());
|
||||
return;
|
||||
end
|
||||
|
||||
if mapping.service == nil then
|
||||
local service = portmanager.get_service(mapping.service_name);
|
||||
|
||||
if service ~= nil then
|
||||
mapping.service = service;
|
||||
else
|
||||
conn:close();
|
||||
module:log("warn", "Connection %s@%s terminated: Could not process mapping for unknown service %s",
|
||||
conn:ip(), conn:proxyip(), mapping.service_name);
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
-- Pass connection to actual service listener and simulate onconnect/onincoming callbacks
|
||||
local service_listener = mapping.service.listener;
|
||||
|
||||
module:log("info", "Passing proxied connection %s:%d to service %s", conn:ip(), conn:port(), mapping.service_name);
|
||||
conn:setlistener(service_listener);
|
||||
if service_listener.onconnect then
|
||||
service_listener.onconnect(conn);
|
||||
end
|
||||
return service_listener.onincoming(conn, session.buffer);
|
||||
end
|
||||
|
||||
local function is_trusted_proxy(conn)
|
||||
-- If no trusted proxies were configured, trust any incoming connection
|
||||
-- While this may seem insecure, the module defaults to only trusting 127.0.0.1 and ::1
|
||||
if trusted_networks:empty() then
|
||||
return true;
|
||||
end
|
||||
|
||||
-- Iterate through all trusted proxies and check for match against connected IP address
|
||||
local conn_ip = ip.new_ip(conn:ip());
|
||||
for trusted_network in trusted_networks:items() do
|
||||
if ip.match(trusted_network.ip, conn_ip, trusted_network.cidr) then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
|
||||
-- Connection does not match any trusted proxy
|
||||
return false;
|
||||
end
|
||||
|
||||
-- Network Listener Methods
|
||||
local listener = {};
|
||||
|
||||
function listener.onconnect(conn)
|
||||
-- Silently drop connections with an IP address of <nil>, which can happen when the socket was closed before the
|
||||
-- responsible net.server backend was able to grab the IP address of the connecting client.
|
||||
if conn:ip() == nil then
|
||||
conn:close();
|
||||
return;
|
||||
end
|
||||
|
||||
-- Check if connection is coming from a trusted proxy
|
||||
if not is_trusted_proxy(conn) then
|
||||
conn:close();
|
||||
module:log("warn", "Dropped connection from untrusted proxy: %s", conn:ip());
|
||||
return;
|
||||
end
|
||||
|
||||
-- Initialize session variables
|
||||
sessions[conn] = {
|
||||
handler = nil;
|
||||
buffer = nil;
|
||||
};
|
||||
end
|
||||
|
||||
function listener.onincoming(conn, data)
|
||||
-- Abort processing if no data has been received
|
||||
if not data then
|
||||
return;
|
||||
end
|
||||
|
||||
-- Lookup session for connection and append received data to buffer
|
||||
local session = sessions[conn];
|
||||
session.buffer = session.buffer and session.buffer .. data or data;
|
||||
|
||||
-- Attempt to determine protocol handler if not done previously
|
||||
if session.handler == nil then
|
||||
-- Match current session buffer against all known protocol signatures to determine protocol handler
|
||||
for handler_name, handler in pairs(PROTO_HANDLERS) do
|
||||
if session.buffer:find("^" .. handler.signature) ~= nil then
|
||||
session.handler = handler.callback;
|
||||
module:log("debug", "Detected %s connection from %s:%d", handler_name, conn:ip(), conn:port());
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
-- Decide between waiting for a complete header signature or terminating the connection when no handler has been found
|
||||
if session.handler == nil then
|
||||
-- Terminate connection if buffer size has exceeded tolerable maximum size
|
||||
if #session.buffer > PROTO_MAX_HEADER_LENGTH then
|
||||
conn:close();
|
||||
module:log("warn", "Connection %s:%d terminated: No valid PROXY header within %d bytes",
|
||||
conn:ip(), conn:port(), PROTO_MAX_HEADER_LENGTH);
|
||||
end
|
||||
|
||||
-- Skip further processing without a valid protocol handler
|
||||
module:log("debug", "No valid header signature detected from %s:%d, waiting for more data...",
|
||||
conn:ip(), conn:port());
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
-- Execute proxy protocol handler and process response
|
||||
local response, proxy_data = session.handler(conn, session);
|
||||
if response == PROTO_HANDLER_STATUS.SUCCESS then
|
||||
module:log("info", "Received PROXY header from %s: %s", conn:ip(), proxy_data:describe());
|
||||
return wrap_proxy_connection(conn, session, proxy_data);
|
||||
elseif response == PROTO_HANDLER_STATUS.POSTPONE then
|
||||
module:log("debug", "Postponed parsing of incomplete PROXY header received from %s", conn:ip());
|
||||
return;
|
||||
elseif response == PROTO_HANDLER_STATUS.FAILURE then
|
||||
conn:close();
|
||||
module:log("warn", "Connection %s terminated: Could not process PROXY header from client, " +
|
||||
"see previous log messages.", conn:ip());
|
||||
return;
|
||||
else
|
||||
-- This code should be never reached, but is included for completeness
|
||||
conn:close();
|
||||
module:log("warn", "Connection terminated: Received invalid protocol handler response with code %d", response);
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
function listener.ondisconnect(conn)
|
||||
sessions[conn] = nil;
|
||||
end
|
||||
|
||||
listener.ondetach = listener.ondisconnect;
|
||||
|
||||
-- Parse trusted proxies which can either contain single hosts or networks
|
||||
if not config_trusted_proxies:empty() then
|
||||
for trusted_proxy in config_trusted_proxies:items() do
|
||||
local network = {};
|
||||
network.ip, network.cidr = ip.parse_cidr(trusted_proxy);
|
||||
trusted_networks:add(network);
|
||||
end
|
||||
else
|
||||
module:log("warn", "No trusted proxies configured, all connections will be accepted - this might be dangerous");
|
||||
end
|
||||
|
||||
-- Process all configured port mappings and generate a list of mapped ports
|
||||
local mapped_ports = {};
|
||||
for port, mapping in pairs(config_mappings) do
|
||||
port = tonumber(port);
|
||||
table.insert(mapped_ports, port);
|
||||
mappings[port] = {
|
||||
service_name = mapping,
|
||||
service = nil,
|
||||
};
|
||||
end
|
||||
|
||||
-- Log error message when user manually specifies ports without configuring the necessary port mappings
|
||||
if not config_ports:empty() then
|
||||
local missing_ports = config_ports - set.new(mapped_ports);
|
||||
if not missing_ports:empty() then
|
||||
module:log("error", "Missing port<>service mappings for these ports: %s", tostring(missing_ports));
|
||||
end
|
||||
end
|
||||
|
||||
-- Register the previously declared network listener
|
||||
module:provides("net", {
|
||||
name = "proxy";
|
||||
listener = listener;
|
||||
default_ports = mapped_ports;
|
||||
});
|
@ -20,8 +20,20 @@ end
|
||||
|
||||
module:hook("stream-features", function (event)
|
||||
mark_secure(event, "c2s_unauthed");
|
||||
end, 2500);
|
||||
end, 25000);
|
||||
|
||||
module:hook("s2s-stream-features", function (event)
|
||||
mark_secure(event, "s2sin_unauthed");
|
||||
end, 2500);
|
||||
end, 25000);
|
||||
|
||||
-- todo: is this the best place to do this hook?
|
||||
-- this hook marks incoming s2s as secure so we offer SASL EXTERNAL on it
|
||||
module:hook("s2s-stream-features", function(event)
|
||||
local session, features = event.origin, event.features;
|
||||
if session.type == "s2sin_unauthed" then
|
||||
module:log("debug", "marking hook session.type '%s' secure with validated cert!", session.type);
|
||||
session.secure = true;
|
||||
session.cert_chain_status = "valid";
|
||||
session.cert_identity_status = "valid";
|
||||
end
|
||||
end, 3000);
|
||||
|
@ -0,0 +1,23 @@
|
||||
$TTL 300
|
||||
; example.org
|
||||
@ IN SOA ns1.example.org. postmaster.example.org. (
|
||||
2018111111 ; Serial
|
||||
28800 ; Refresh
|
||||
1800 ; Retry
|
||||
604800 ; Expire - 1 week
|
||||
86400 ) ; Negative Cache TTL
|
||||
IN NS ns1
|
||||
ns1 IN A 192.5.0.10
|
||||
server1 IN A 192.5.0.20
|
||||
server2 IN A 192.5.0.30
|
||||
xp1 IN A 192.5.0.40
|
||||
xp2 IN A 192.5.0.50
|
||||
xp3 IN A 192.5.0.60
|
||||
|
||||
one IN CNAME xp1
|
||||
two IN CNAME xp2
|
||||
_xmpp-server._tcp.one IN SRV 5 1 52269 xp1
|
||||
_xmpp-server._tcp.two IN SRV 5 1 52269 xp2
|
||||
|
||||
scansion.one IN CNAME xp1
|
||||
scansion.two IN CNAME xp2
|
@ -0,0 +1,248 @@
|
||||
--Important for systemd
|
||||
-- daemonize is important for systemd. if you set this to false the systemd startup will freeze.
|
||||
daemonize = false
|
||||
run_as_root = true
|
||||
|
||||
pidfile = "/run/prosody/prosody.pid"
|
||||
|
||||
plugin_paths = { "/opt/xmpp-proxy/prosody-modules", "/opt/prosody-modules" }
|
||||
|
||||
-- Prosody Example Configuration File
|
||||
--
|
||||
-- Information on configuring Prosody can be found on our
|
||||
-- website at https://prosody.im/doc/configure
|
||||
--
|
||||
-- Tip: You can check that the syntax of this file is correct
|
||||
-- when you have finished by running this command:
|
||||
-- prosodyctl check config
|
||||
-- If there are any errors, it will let you know what and where
|
||||
-- they are, otherwise it will keep quiet.
|
||||
--
|
||||
-- The only thing left to do is rename this file to remove the .dist ending, and fill in the
|
||||
-- blanks. Good luck, and happy Jabbering!
|
||||
|
||||
|
||||
---------- Server-wide settings ----------
|
||||
-- Settings in this section apply to the whole server and are the default settings
|
||||
-- for any virtual hosts
|
||||
|
||||
-- This is a (by default, empty) list of accounts that are admins
|
||||
-- for the server. Note that you must create the accounts separately
|
||||
-- (see https://prosody.im/doc/creating_accounts for info)
|
||||
-- Example: admins = { "user1@example.com", "user2@example.net" }
|
||||
admins = { }
|
||||
|
||||
-- Enable use of libevent for better performance under high load
|
||||
-- For more information see: https://prosody.im/doc/libevent
|
||||
--use_libevent = true
|
||||
|
||||
-- Prosody will always look in its source directory for modules, but
|
||||
-- this option allows you to specify additional locations where Prosody
|
||||
-- will look for modules first. For community modules, see https://modules.prosody.im/
|
||||
--plugin_paths = {}
|
||||
|
||||
-- This is the list of modules Prosody will load on startup.
|
||||
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
|
||||
-- Documentation for bundled modules can be found at: https://prosody.im/doc/modules
|
||||
modules_enabled = {
|
||||
|
||||
-- Generally required
|
||||
"roster"; -- Allow users to have a roster. Recommended ;)
|
||||
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
|
||||
"tls"; -- Add support for secure TLS on c2s/s2s connections
|
||||
--"dialback"; -- s2s dialback support
|
||||
"disco"; -- Service discovery
|
||||
|
||||
-- Not essential, but recommended
|
||||
"carbons"; -- Keep multiple clients in sync
|
||||
"pep"; -- Enables users to publish their avatar, mood, activity, playing music and more
|
||||
"private"; -- Private XML storage (for room bookmarks, etc.)
|
||||
"blocklist"; -- Allow users to block communications with other users
|
||||
"vcard4"; -- User profiles (stored in PEP)
|
||||
"vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard
|
||||
"limits"; -- Enable bandwidth limiting for XMPP connections
|
||||
|
||||
-- Nice to have
|
||||
"version"; -- Replies to server version requests
|
||||
"uptime"; -- Report how long server has been running
|
||||
"time"; -- Let others know the time here on this server
|
||||
"ping"; -- Replies to XMPP pings with pongs
|
||||
"register"; -- Allow users to register on this server using a client and change passwords
|
||||
--"mam"; -- Store messages in an archive and allow users to access it
|
||||
--"csi_simple"; -- Simple Mobile optimizations
|
||||
|
||||
-- Admin interfaces
|
||||
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
|
||||
--"admin_telnet"; -- Opens telnet console interface on localhost port 5582
|
||||
|
||||
-- HTTP modules
|
||||
--"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
|
||||
--"websocket"; -- XMPP over WebSockets
|
||||
--"http_files"; -- Serve static files from a directory over HTTP
|
||||
|
||||
-- Other specific functionality
|
||||
--"groups"; -- Shared roster support
|
||||
--"server_contact_info"; -- Publish contact information for this service
|
||||
--"announce"; -- Send announcement to all online users
|
||||
--"welcome"; -- Welcome users who register accounts
|
||||
--"watchregistrations"; -- Alert admins of registrations
|
||||
--"motd"; -- Send a message to users when they log in
|
||||
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
|
||||
--"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use
|
||||
"net_proxy";
|
||||
--"s2s_outgoing_proxy";
|
||||
"secure_interfaces";
|
||||
}
|
||||
|
||||
-- These modules are auto-loaded, but should you want
|
||||
-- to disable them then uncomment them here:
|
||||
modules_disabled = {
|
||||
-- "offline"; -- Store offline messages
|
||||
-- "c2s"; -- Handle client connections
|
||||
-- "s2s"; -- Handle server-to-server connections
|
||||
-- "posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
|
||||
}
|
||||
|
||||
-- Disable account creation by default, for security
|
||||
-- For more information see https://prosody.im/doc/creating_accounts
|
||||
allow_registration = false
|
||||
|
||||
c2s_require_encryption = false
|
||||
allow_unencrypted_plain_auth = true
|
||||
|
||||
s2s_require_encryption = true
|
||||
s2s_secure_auth = true
|
||||
|
||||
-- handle PROXY protocol on these ports
|
||||
proxy_port_mappings = {
|
||||
[15222] = "c2s",
|
||||
[15269] = "s2s"
|
||||
}
|
||||
|
||||
--[[
|
||||
Specifies a list of trusted hosts or networks which may use the PROXY protocol
|
||||
If not specified, it will default to: 127.0.0.1, ::1 (local connections only)
|
||||
An empty table ({}) can be configured to allow connections from any source.
|
||||
Please read the module documentation about potential security impact.
|
||||
]]--
|
||||
proxy_trusted_proxies = {
|
||||
"192.5.0.40"
|
||||
}
|
||||
|
||||
secure_interfaces = {
|
||||
"192.5.0.40"
|
||||
}
|
||||
|
||||
-- don't listen on any normal c2s/s2s ports (xmpp-proxy listens on these now)
|
||||
-- you might need to comment these out further down in your config file if you set them
|
||||
c2s_ports = {}
|
||||
legacy_ssl_ports = {}
|
||||
-- you MUST have at least one s2s_ports defined if you want outgoing S2S to work, don't ask..
|
||||
s2s_ports = {15268}
|
||||
|
||||
|
||||
-- Some servers have invalid or self-signed certificates. You can list
|
||||
-- remote domains here that will not be required to authenticate using
|
||||
-- certificates. They will be authenticated using DNS instead, even
|
||||
-- when s2s_secure_auth is enabled.
|
||||
|
||||
--s2s_insecure_domains = { "insecure.example" }
|
||||
|
||||
-- Even if you disable s2s_secure_auth, you can still require valid
|
||||
-- certificates for some domains by specifying a list here.
|
||||
|
||||
--s2s_secure_domains = { "jabber.org" }
|
||||
|
||||
-- Enable rate limits for incoming client and server connections
|
||||
|
||||
limits = {
|
||||
c2s = {
|
||||
rate = "10kb/s";
|
||||
};
|
||||
s2sin = {
|
||||
rate = "30kb/s";
|
||||
};
|
||||
}
|
||||
|
||||
-- Select the authentication backend to use. The 'internal' providers
|
||||
-- use Prosody's configured data storage to store the authentication data.
|
||||
|
||||
authentication = "internal_hashed"
|
||||
|
||||
-- Select the storage backend to use. By default Prosody uses flat files
|
||||
-- in its configured data directory, but it also supports more backends
|
||||
-- through modules. An "sql" backend is included by default, but requires
|
||||
-- additional dependencies. See https://prosody.im/doc/storage for more info.
|
||||
|
||||
--storage = "sql" -- Default is "internal"
|
||||
|
||||
-- For the "sql" backend, you can uncomment *one* of the below to configure:
|
||||
--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
|
||||
--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
|
||||
--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
|
||||
|
||||
|
||||
-- Archiving configuration
|
||||
-- If mod_mam is enabled, Prosody will store a copy of every message. This
|
||||
-- is used to synchronize conversations between multiple clients, even if
|
||||
-- they are offline. This setting controls how long Prosody will keep
|
||||
-- messages in the archive before removing them.
|
||||
|
||||
archive_expires_after = "1w" -- Remove archived messages after 1 week
|
||||
|
||||
-- You can also configure messages to be stored in-memory only. For more
|
||||
-- archiving options, see https://prosody.im/doc/modules/mod_mam
|
||||
|
||||
-- Logging configuration
|
||||
-- For advanced logging see https://prosody.im/doc/logging
|
||||
log = {
|
||||
-- info = "prosody.log"; -- Change 'info' to 'debug' for verbose logging
|
||||
-- error = "prosody.err";
|
||||
--info = "*syslog"; -- Uncomment this for logging to syslog
|
||||
debug = "*console"; -- Log to the console, useful for debugging with daemonize=false
|
||||
}
|
||||
|
||||
-- Uncomment to enable statistics
|
||||
-- For more info see https://prosody.im/doc/statistics
|
||||
-- statistics = "internal"
|
||||
|
||||
-- Certificates
|
||||
-- Every virtual host and component needs a certificate so that clients and
|
||||
-- servers can securely verify its identity. Prosody will automatically load
|
||||
-- certificates/keys from the directory specified here.
|
||||
-- For more information, including how to use 'prosodyctl' to auto-import certificates
|
||||
-- (from e.g. Let's Encrypt) see https://prosody.im/doc/certificates
|
||||
|
||||
-- Location of directory to find certificates in (relative to main config file):
|
||||
certificates = "certs"
|
||||
|
||||
-- HTTPS currently only supports a single certificate, specify it here:
|
||||
--https_certificate = "/etc/prosody/certs/localhost.crt"
|
||||
|
||||
----------- Virtual hosts -----------
|
||||
-- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
|
||||
-- Settings under each VirtualHost entry apply *only* to that host.
|
||||
|
||||
VirtualHost "one.example.org"
|
||||
|
||||
--VirtualHost "example.com"
|
||||
-- certificate = "/path/to/example.crt"
|
||||
|
||||
------ Components ------
|
||||
-- You can specify components to add hosts that provide special services,
|
||||
-- like multi-user conferences, and transports.
|
||||
-- For more information on components, see https://prosody.im/doc/components
|
||||
|
||||
---Set up a MUC (multi-user chat) room server on conference.example.com:
|
||||
--Component "conference.example.com" "muc"
|
||||
--- Store MUC messages in an archive and allow users to access it
|
||||
--modules_enabled = { "muc_mam" }
|
||||
|
||||
---Set up an external component (default component port is 5347)
|
||||
--
|
||||
-- External components allow adding various services, such as gateways/
|
||||
-- transports to other networks like ICQ, MSN and Yahoo. For more info
|
||||
-- see: https://prosody.im/doc/components#adding_an_external_component
|
||||
--
|
||||
--Component "gateway.example.com"
|
||||
-- component_secret = "password"
|
@ -0,0 +1,247 @@
|
||||
--Important for systemd
|
||||
-- daemonize is important for systemd. if you set this to false the systemd startup will freeze.
|
||||
daemonize = false
|
||||
run_as_root = true
|
||||
|
||||
pidfile = "/run/prosody/prosody.pid"
|
||||
|
||||
plugin_paths = { "/opt/xmpp-proxy/prosody-modules", "/opt/prosody-modules" }
|
||||
|
||||
-- Prosody Example Configuration File
|
||||
--
|
||||
-- Information on configuring Prosody can be found on our
|
||||
-- website at https://prosody.im/doc/configure
|
||||
--
|
||||
-- Tip: You can check that the syntax of this file is correct
|
||||
-- when you have finished by running this command:
|
||||
-- prosodyctl check config
|
||||
-- If there are any errors, it will let you know what and where
|
||||
-- they are, otherwise it will keep quiet.
|
||||
--
|
||||
-- The only thing left to do is rename this file to remove the .dist ending, and fill in the
|
||||
-- blanks. Good luck, and happy Jabbering!
|
||||
|
||||
|
||||
---------- Server-wide settings ----------
|
||||
-- Settings in this section apply to the whole server and are the default settings
|
||||
-- for any virtual hosts
|
||||
|
||||
-- This is a (by default, empty) list of accounts that are admins
|
||||
-- for the server. Note that you must create the accounts separately
|
||||
-- (see https://prosody.im/doc/creating_accounts for info)
|
||||
-- Example: admins = { "user1@example.com", "user2@example.net" }
|
||||
admins = { }
|
||||
|
||||
-- Enable use of libevent for better performance under high load
|
||||
-- For more information see: https://prosody.im/doc/libevent
|
||||
--use_libevent = true
|
||||
|
||||
-- Prosody will always look in its source directory for modules, but
|
||||
-- this option allows you to specify additional locations where Prosody
|
||||
-- will look for modules first. For community modules, see https://modules.prosody.im/
|
||||
--plugin_paths = {}
|
||||
|
||||
-- This is the list of modules Prosody will load on startup.
|
||||
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
|
||||
-- Documentation for bundled modules can be found at: https://prosody.im/doc/modules
|
||||
modules_enabled = {
|
||||
|
||||
-- Generally required
|
||||
"roster"; -- Allow users to have a roster. Recommended ;)
|
||||
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
|
||||
"tls"; -- Add support for secure TLS on c2s/s2s connections
|
||||
--"dialback"; -- s2s dialback support
|
||||
"disco"; -- Service discovery
|
||||
|
||||
-- Not essential, but recommended
|
||||
"carbons"; -- Keep multiple clients in sync
|
||||
"pep"; -- Enables users to publish their avatar, mood, activity, playing music and more
|
||||
"private"; -- Private XML storage (for room bookmarks, etc.)
|
||||
"blocklist"; -- Allow users to block communications with other users
|
||||
"vcard4"; -- User profiles (stored in PEP)
|
||||
"vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard
|
||||
"limits"; -- Enable bandwidth limiting for XMPP connections
|
||||
|
||||
-- Nice to have
|
||||
"version"; -- Replies to server version requests
|
||||
"uptime"; -- Report how long server has been running
|
||||
"time"; -- Let others know the time here on this server
|
||||
"ping"; -- Replies to XMPP pings with pongs
|
||||
"register"; -- Allow users to register on this server using a client and change passwords
|
||||
--"mam"; -- Store messages in an archive and allow users to access it
|
||||
--"csi_simple"; -- Simple Mobile optimizations
|
||||
|
||||
-- Admin interfaces
|
||||
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
|
||||
--"admin_telnet"; -- Opens telnet console interface on localhost port 5582
|
||||
|
||||
-- HTTP modules
|
||||
--"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
|
||||
--"websocket"; -- XMPP over WebSockets
|
||||
--"http_files"; -- Serve static files from a directory over HTTP
|
||||
|
||||
-- Other specific functionality
|
||||
--"groups"; -- Shared roster support
|
||||
--"server_contact_info"; -- Publish contact information for this service
|
||||
--"announce"; -- Send announcement to all online users
|
||||
--"welcome"; -- Welcome users who register accounts
|
||||
--"watchregistrations"; -- Alert admins of registrations
|
||||
--"motd"; -- Send a message to users when they log in
|
||||
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
|
||||
--"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use
|
||||
"net_proxy";
|
||||
--"s2s_outgoing_proxy";
|
||||
"secure_interfaces";
|
||||
}
|
||||
|
||||
-- These modules are auto-loaded, but should you want
|
||||
-- to disable them then uncomment them here:
|
||||
modules_disabled = {
|
||||
-- "offline"; -- Store offline messages
|
||||
-- "c2s"; -- Handle client connections
|
||||
-- "s2s"; -- Handle server-to-server connections
|
||||
-- "posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
|
||||
}
|
||||
|
||||
-- Disable account creation by default, for security
|
||||
-- For more information see https://prosody.im/doc/creating_accounts
|
||||
allow_registration = false
|
||||
|
||||
c2s_require_encryption = false
|
||||
allow_unencrypted_plain_auth = true
|
||||
|
||||
s2s_require_encryption = true
|
||||
s2s_secure_auth = true
|
||||
|
||||
-- handle PROXY protocol on these ports
|
||||
proxy_port_mappings = {
|
||||
[15222] = "c2s",
|
||||
[15269] = "s2s"
|
||||
}
|
||||
|
||||
--[[
|
||||
Specifies a list of trusted hosts or networks which may use the PROXY protocol
|
||||
If not specified, it will default to: 127.0.0.1, ::1 (local connections only)
|
||||
An empty table ({}) can be configured to allow connections from any source.
|
||||
Please read the module documentation about potential security impact.
|
||||
]]--
|
||||
proxy_trusted_proxies = {
|
||||
"192.5.0.50"
|
||||
}
|
||||
|
||||
secure_interfaces = {
|
||||
"192.5.0.50"
|
||||
}
|
||||
|
||||
-- don't listen on any normal c2s/s2s ports (xmpp-proxy listens on these now)
|
||||
-- you might need to comment these out further down in your config file if you set them
|
||||
c2s_ports = {}
|
||||
legacy_ssl_ports = {}
|
||||
-- you MUST have at least one s2s_ports defined if you want outgoing S2S to work, don't ask..
|
||||
s2s_ports = {15268}
|
||||
|
||||
-- Some servers have invalid or self-signed certificates. You can list
|
||||
-- remote domains here that will not be required to authenticate using
|
||||
-- certificates. They will be authenticated using DNS instead, even
|
||||
-- when s2s_secure_auth is enabled.
|
||||
|
||||
--s2s_insecure_domains = { "insecure.example" }
|
||||
|
||||
-- Even if you disable s2s_secure_auth, you can still require valid
|
||||
-- certificates for some domains by specifying a list here.
|
||||
|
||||
--s2s_secure_domains = { "jabber.org" }
|
||||
|
||||
-- Enable rate limits for incoming client and server connections
|
||||
|
||||
limits = {
|
||||
c2s = {
|
||||
rate = "10kb/s";
|
||||
};
|
||||
s2sin = {
|
||||
rate = "30kb/s";
|
||||
};
|
||||
}
|
||||
|
||||
-- Select the authentication backend to use. The 'internal' providers
|
||||
-- use Prosody's configured data storage to store the authentication data.
|
||||
|
||||
authentication = "internal_hashed"
|
||||
|
||||
-- Select the storage backend to use. By default Prosody uses flat files
|
||||
-- in its configured data directory, but it also supports more backends
|
||||
-- through modules. An "sql" backend is included by default, but requires
|
||||
-- additional dependencies. See https://prosody.im/doc/storage for more info.
|
||||
|
||||
--storage = "sql" -- Default is "internal"
|
||||
|
||||
-- For the "sql" backend, you can uncomment *one* of the below to configure:
|
||||
--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
|
||||
--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
|
||||
--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
|
||||
|
||||
|
||||
-- Archiving configuration
|
||||
-- If mod_mam is enabled, Prosody will store a copy of every message. This
|
||||
-- is used to synchronize conversations between multiple clients, even if
|
||||
-- they are offline. This setting controls how long Prosody will keep
|
||||
-- messages in the archive before removing them.
|
||||
|
||||
archive_expires_after = "1w" -- Remove archived messages after 1 week
|
||||
|
||||
-- You can also configure messages to be stored in-memory only. For more
|
||||
-- archiving options, see https://prosody.im/doc/modules/mod_mam
|
||||
|
||||
-- Logging configuration
|
||||
-- For advanced logging see https://prosody.im/doc/logging
|
||||
log = {
|
||||
-- info = "prosody.log"; -- Change 'info' to 'debug' for verbose logging
|
||||
-- error = "prosody.err";
|
||||
--info = "*syslog"; -- Uncomment this for logging to syslog
|
||||
debug = "*console"; -- Log to the console, useful for debugging with daemonize=false
|
||||
}
|
||||
|
||||
-- Uncomment to enable statistics
|
||||
-- For more info see https://prosody.im/doc/statistics
|
||||
-- statistics = "internal"
|
||||
|
||||
-- Certificates
|
||||
-- Every virtual host and component needs a certificate so that clients and
|
||||
-- servers can securely verify its identity. Prosody will automatically load
|
||||
-- certificates/keys from the directory specified here.
|
||||
-- For more information, including how to use 'prosodyctl' to auto-import certificates
|
||||
-- (from e.g. Let's Encrypt) see https://prosody.im/doc/certificates
|
||||
|
||||
-- Location of directory to find certificates in (relative to main config file):
|
||||
certificates = "certs"
|
||||
|
||||
-- HTTPS currently only supports a single certificate, specify it here:
|
||||
--https_certificate = "/etc/prosody/certs/localhost.crt"
|
||||
|
||||
----------- Virtual hosts -----------
|
||||
-- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
|
||||
-- Settings under each VirtualHost entry apply *only* to that host.
|
||||
|
||||
VirtualHost "two.example.org"
|
||||
|
||||
--VirtualHost "example.com"
|
||||
-- certificate = "/path/to/example.crt"
|
||||
|
||||
------ Components ------
|
||||
-- You can specify components to add hosts that provide special services,
|
||||
-- like multi-user conferences, and transports.
|
||||
-- For more information on components, see https://prosody.im/doc/components
|
||||
|
||||
---Set up a MUC (multi-user chat) room server on conference.example.com:
|
||||
--Component "conference.example.com" "muc"
|
||||
--- Store MUC messages in an archive and allow users to access it
|
||||
--modules_enabled = { "muc_mam" }
|
||||
|
||||
---Set up an external component (default component port is 5347)
|
||||
--
|
||||
-- External components allow adding various services, such as gateways/
|
||||
-- transports to other networks like ICQ, MSN and Yahoo. For more info
|
||||
-- see: https://prosody.im/doc/components#adding_an_external_component
|
||||
--
|
||||
--Component "gateway.example.com"
|
||||
-- component_secret = "password"
|
@ -0,0 +1,44 @@
|
||||
|
||||
# interfaces to listen for reverse proxy STARTTLS/Direct TLS XMPP connections on, should be open to the internet
|
||||
incoming_listen = [ "0.0.0.0:5222", "0.0.0.0:52269" ]
|
||||
# interfaces to listen for reverse proxy QUIC XMPP connections on, should be open to the internet
|
||||
quic_listen = [ ]
|
||||
# interfaces to listen for reverse proxy TLS WebSocket (wss) XMPP connections on, should be open to the internet
|
||||
websocket_listen = [ ]
|
||||
# interfaces to listen for outgoing proxy TCP XMPP connections on, should be localhost
|
||||
outgoing_listen = [ ]
|
||||
|
||||
# these ports shouldn't do any TLS, but should assume any connection from xmpp-proxy is secure
|
||||
# prosody module: https://modules.prosody.im/mod_secure_interfaces.html
|
||||
|
||||
# c2s port backend XMPP server listens on
|
||||
c2s_target = "192.5.0.20:15222"
|
||||
|
||||
# s2s port backend XMPP server listens on
|
||||
s2s_target = "192.5.0.20:15269"
|
||||
|
||||
# send PROXYv1 header to backend XMPP server
|
||||
# https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
||||
# prosody module: https://modules.prosody.im/mod_net_proxy.html
|
||||
# ejabberd config: https://docs.ejabberd.im/admin/configuration/listen-options/#use-proxy-protocol
|
||||
proxy = true
|
||||
|
||||
# limit incoming stanzas to this many bytes, default to ejabberd's default
|
||||
# https://github.com/processone/ejabberd/blob/master/ejabberd.yml.example#L32
|
||||
# xmpp-proxy will use this many bytes + 16k per connection
|
||||
max_stanza_size_bytes = 262_144
|
||||
|
||||
# TLS key/certificate valid for all your XMPP domains, PEM format
|
||||
# included systemd unit can only read files from /etc/xmpp-proxy/ so put them in there
|
||||
tls_key = "/etc/prosody/certs/one.example.org.key"
|
||||
tls_cert = "/etc/prosody/certs/one.example.org.crt"
|
||||
|
||||
# configure logging, defaults are commented
|
||||
# can also set env variables XMPP_PROXY_LOG_LEVEL and/or XMPP_PROXY_LOG_STYLE, but values in this file override them
|
||||
# many options, trace is XML-console-level, refer to: https://docs.rs/env_logger/0.8.3/env_logger/#enabling-logging
|
||||
#log_level = "info"
|
||||
# for development/debugging:
|
||||
log_level = "info,xmpp_proxy=trace"
|
||||
|
||||
# one of auto, always, never, refer to: https://docs.rs/env_logger/0.8.3/env_logger/#disabling-colors
|
||||
#log_style = "never"
|
@ -0,0 +1,44 @@
|
||||
|
||||
# interfaces to listen for reverse proxy STARTTLS/Direct TLS XMPP connections on, should be open to the internet
|
||||
incoming_listen = [ "0.0.0.0:5222", "0.0.0.0:52269" ]
|
||||
# interfaces to listen for reverse proxy QUIC XMPP connections on, should be open to the internet
|
||||
quic_listen = [ ]
|
||||
# interfaces to listen for reverse proxy TLS WebSocket (wss) XMPP connections on, should be open to the internet
|
||||
websocket_listen = [ ]
|
||||
# interfaces to listen for outgoing proxy TCP XMPP connections on, should be localhost
|
||||
outgoing_listen = [ ]
|
||||
|
||||
# these ports shouldn't do any TLS, but should assume any connection from xmpp-proxy is secure
|
||||
# prosody module: https://modules.prosody.im/mod_secure_interfaces.html
|
||||
|
||||
# c2s port backend XMPP server listens on
|
||||
c2s_target = "192.5.0.30:15222"
|
||||
|
||||
# s2s port backend XMPP server listens on
|
||||
s2s_target = "192.5.0.30:15269"
|
||||
|
||||
# send PROXYv1 header to backend XMPP server
|
||||
# https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
||||
# prosody module: https://modules.prosody.im/mod_net_proxy.html
|
||||
# ejabberd config: https://docs.ejabberd.im/admin/configuration/listen-options/#use-proxy-protocol
|
||||
proxy = true
|
||||
|
||||
# limit incoming stanzas to this many bytes, default to ejabberd's default
|
||||
# https://github.com/processone/ejabberd/blob/master/ejabberd.yml.example#L32
|
||||
# xmpp-proxy will use this many bytes + 16k per connection
|
||||
max_stanza_size_bytes = 262_144
|
||||
|
||||
# TLS key/certificate valid for all your XMPP domains, PEM format
|
||||
# included systemd unit can only read files from /etc/xmpp-proxy/ so put them in there
|
||||
tls_key = "/etc/prosody/certs/two.example.org.key"
|
||||
tls_cert = "/etc/prosody/certs/two.example.org.crt"
|
||||
|
||||
# configure logging, defaults are commented
|
||||
# can also set env variables XMPP_PROXY_LOG_LEVEL and/or XMPP_PROXY_LOG_STYLE, but values in this file override them
|
||||
# many options, trace is XML-console-level, refer to: https://docs.rs/env_logger/0.8.3/env_logger/#enabling-logging
|
||||
#log_level = "info"
|
||||
# for development/debugging:
|
||||
log_level = "info,xmpp_proxy=trace"
|
||||
|
||||
# one of auto, always, never, refer to: https://docs.rs/env_logger/0.8.3/env_logger/#disabling-colors
|
||||
#log_style = "never"
|
@ -18,9 +18,9 @@ RUN pacman -S --noconfirm --disable-download-timeout --needed rust cargo git mer
|
||||
hg clone 'https://hg.prosody.im/prosody-modules/' /build/prosody-modules && rm -rf /build/prosody-modules/.hg && \
|
||||
git clone https://aur.archlinux.org/scansion-hg.git /build/scansion-hg && \
|
||||
git clone https://aur.archlinux.org/lua52-cjson.git /build/lua52-cjson && \
|
||||
chown -R git: /build/ && ls -lah /build/ && \
|
||||
cd /build/lua52-cjson && su -m -s /bin/bash git makepkg && pacman -U --noconfirm --needed lua52-cjson-*.pkg.tar* && \
|
||||
cd /build/scansion-hg && su -m -s /bin/bash git makepkg
|
||||
chown -R nobody: /build/ && ls -lah /build/ && \
|
||||
cd /build/lua52-cjson && su -m -s /bin/bash nobody makepkg && pacman -U --noconfirm --needed lua52-cjson-*.pkg.tar* && \
|
||||
cd /build/scansion-hg && su -m -s /bin/bash nobody makepkg
|
||||
|
||||
COPY ./Cargo.* /build/
|
||||
COPY ./src/ /build/src/
|
||||
@ -66,6 +66,8 @@ COPY ./integration/00-no-tls/prosody1.cfg.lua /etc/prosody/prosody.cfg.lua
|
||||
COPY ./contrib/prosody-modules /opt/xmpp-proxy/prosody-modules
|
||||
COPY ./integration/*.scs /scansion/
|
||||
|
||||
RUN mkdir /run/prosody/
|
||||
|
||||
ARG ECDSA=0
|
||||
|
||||
RUN if [ $ECDSA -ne 0 ]; then rm -rf /etc/prosody/certs && ln -sf /etc/certs/ecdsa /etc/prosody/certs; fi
|
||||
|
@ -117,7 +117,7 @@ run_test() {
|
||||
sed -i "s/192\.5\.0\./$ipv4.$num./g" *
|
||||
|
||||
# start the dns server
|
||||
run_container "$network_name" $num -d -v ./example.org.zone:/var/named/example.org.zone:ro 10 dns named -g -u named -d 99
|
||||
run_container "$network_name" $num -d -v ./example.org.zone:/var/named/example.org.zone:ro 10 dns named -g -d 99
|
||||
|
||||
# start the prosody servers if required
|
||||
[ -f ./prosody1.cfg.lua ] && run_container "$network_name" $num -d -v ./prosody1.cfg.lua:/etc/prosody/prosody.cfg.lua:ro 20 server1 prosody && podman exec $num-server1 prosodyctl register romeo one.example.org pass && podman exec $num-server1 prosodyctl register juliet two.example.org pass
|
||||
|
@ -9,7 +9,7 @@ lazy_static::lazy_static! {
|
||||
pub static ref TLS_SERVER_ROOTS: TlsServerTrustAnchors<'static> = {
|
||||
// we need these to stick around for 'static, this is only called once so no problem
|
||||
let certs = Box::leak(Box::new(rustls_native_certs::load_native_certs().expect("could not load platform certs")));
|
||||
let root_cert_store = Box::leak(Box::new(Vec::new()));
|
||||
let root_cert_store: &mut Box<Vec<_>> = Box::leak(Box::default());
|
||||
for cert in certs {
|
||||
// some system CAs are invalid, ignore those
|
||||
if let Ok(ta) = TrustAnchor::try_from_cert_der(&cert.0) {
|
||||
|
@ -152,7 +152,7 @@ async fn open_incoming(
|
||||
is_c2s: bool,
|
||||
in_filter: &mut StanzaFilter,
|
||||
) -> Result<(ReadHalf<tokio::net::TcpStream>, WriteHalf<tokio::net::TcpStream>)> {
|
||||
let target = if is_c2s {
|
||||
let target: Option<SocketAddr> = if is_c2s {
|
||||
#[cfg(not(feature = "c2s-incoming"))]
|
||||
bail!("incoming c2s connection but lacking compile-time support");
|
||||
#[cfg(feature = "c2s-incoming")]
|
||||
@ -162,8 +162,8 @@ async fn open_incoming(
|
||||
bail!("incoming s2s connection but lacking compile-time support");
|
||||
#[cfg(feature = "s2s-incoming")]
|
||||
config.s2s_target
|
||||
}
|
||||
.ok_or_else(|| anyhow!("incoming connection but `{}_target` not defined", c2s(is_c2s)))?;
|
||||
};
|
||||
let target = target.ok_or_else(|| anyhow!("incoming connection but `{}_target` not defined", c2s(is_c2s)))?;
|
||||
client_addr.set_to_addr(target);
|
||||
|
||||
let out_stream = tokio::net::TcpStream::connect(target).await?;
|
||||
|
@ -5,13 +5,18 @@ use crate::{
|
||||
stanzafilter::StanzaFilter,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use async_trait::async_trait;
|
||||
use log::{info, trace};
|
||||
#[cfg(feature = "rustls")]
|
||||
use rustls::{
|
||||
sign::{RsaSigningKey, SigningKey},
|
||||
Certificate, PrivateKey,
|
||||
};
|
||||
use std::{fs::File, io, io::BufReader, sync::Arc};
|
||||
use std::{fs::File, io, sync::Arc};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite, BufReader, BufStream},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
#[cfg(feature = "incoming")]
|
||||
pub mod incoming;
|
||||
@ -41,7 +46,57 @@ pub fn c2s(is_c2s: bool) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn peek_bytes<'a>(stream: &tokio::net::TcpStream, p: &'a mut [u8]) -> anyhow::Result<&'a [u8]> {
|
||||
pub trait Split: Sized {
|
||||
type ReadHalf: AsyncRead + Unpin;
|
||||
type WriteHalf: AsyncWrite + Unpin;
|
||||
|
||||
fn combine(read_half: Self::ReadHalf, write_half: Self::WriteHalf) -> Result<Self>;
|
||||
|
||||
fn split(self) -> (Self::ReadHalf, Self::WriteHalf);
|
||||
}
|
||||
|
||||
impl Split for TcpStream {
|
||||
type ReadHalf = tokio::net::tcp::OwnedReadHalf;
|
||||
type WriteHalf = tokio::net::tcp::OwnedWriteHalf;
|
||||
|
||||
fn combine(read_half: Self::ReadHalf, write_half: Self::WriteHalf) -> Result<Self> {
|
||||
Ok(read_half.reunite(write_half)?)
|
||||
}
|
||||
|
||||
fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
|
||||
self.into_split()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + AsyncWrite + Unpin + Send> Split for BufStream<T> {
|
||||
type ReadHalf = tokio::io::ReadHalf<BufStream<T>>;
|
||||
type WriteHalf = tokio::io::WriteHalf<BufStream<T>>;
|
||||
|
||||
fn combine(read_half: Self::ReadHalf, write_half: Self::WriteHalf) -> Result<Self> {
|
||||
if read_half.is_pair_of(&write_half) {
|
||||
Ok(read_half.unsplit(write_half))
|
||||
} else {
|
||||
bail!("non-matching read/write half")
|
||||
}
|
||||
}
|
||||
|
||||
fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
|
||||
tokio::io::split(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Peek {
|
||||
async fn peek_bytes<'a>(&mut self, p: &'a mut [u8]) -> anyhow::Result<&'a [u8]>;
|
||||
|
||||
async fn first_bytes_match<'a>(&mut self, p: &'a mut [u8], matcher: fn(&'a [u8]) -> bool) -> anyhow::Result<bool> {
|
||||
Ok(matcher(self.peek_bytes(p).await?))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Peek for TcpStream {
|
||||
async fn peek_bytes<'a>(&mut self, p: &'a mut [u8]) -> anyhow::Result<&'a [u8]> {
|
||||
// sooo... I don't think peek here can be used for > 1 byte without this timer craziness... can it?
|
||||
let len = p.len();
|
||||
// wait up to 10 seconds until len bytes have been read
|
||||
@ -49,9 +104,9 @@ pub async fn peek_bytes<'a>(stream: &tokio::net::TcpStream, p: &'a mut [u8]) ->
|
||||
let duration = Duration::from_secs(10);
|
||||
let now = Instant::now();
|
||||
loop {
|
||||
let n = stream.peek(p).await?;
|
||||
let n = self.peek(p).await?;
|
||||
if n == len {
|
||||
break; // success
|
||||
return Ok(p); // success
|
||||
}
|
||||
if n == 0 {
|
||||
bail!("not enough bytes");
|
||||
@ -60,12 +115,59 @@ pub async fn peek_bytes<'a>(stream: &tokio::net::TcpStream, p: &'a mut [u8]) ->
|
||||
bail!("less than {} bytes in 10 seconds, closed connection?", len);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(p)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn first_bytes_match(stream: &tokio::net::TcpStream, p: &mut [u8], matcher: fn(&[u8]) -> bool) -> anyhow::Result<bool> {
|
||||
Ok(matcher(peek_bytes(stream, p).await?))
|
||||
#[async_trait]
|
||||
impl<T: AsyncRead + AsyncWrite + Unpin + Send> Peek for BufStream<T> {
|
||||
async fn peek_bytes<'a>(&mut self, p: &'a mut [u8]) -> anyhow::Result<&'a [u8]> {
|
||||
// sooo... I don't think peek here can be used for > 1 byte without this timer craziness... can it?
|
||||
let len = p.len();
|
||||
// wait up to 10 seconds until len bytes have been read
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
let duration = Duration::from_secs(10);
|
||||
let now = Instant::now();
|
||||
loop {
|
||||
let buf = self.fill_buf().await?;
|
||||
if buf.len() >= len {
|
||||
p.copy_from_slice(&buf[0..len]);
|
||||
return Ok(p); // success
|
||||
}
|
||||
if buf.is_empty() {
|
||||
bail!("not enough bytes");
|
||||
}
|
||||
if Instant::now() - now > duration {
|
||||
bail!("less than {} bytes in 10 seconds, closed connection?", len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: AsyncRead + Unpin + Send> Peek for BufReader<T> {
|
||||
async fn peek_bytes<'a>(&mut self, p: &'a mut [u8]) -> anyhow::Result<&'a [u8]> {
|
||||
// sooo... I don't think peek here can be used for > 1 byte without this timer craziness... can it?
|
||||
let len = p.len();
|
||||
// wait up to 10 seconds until len bytes have been read
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
let duration = Duration::from_secs(10);
|
||||
let now = Instant::now();
|
||||
loop {
|
||||
let buf = self.fill_buf().await?;
|
||||
if buf.len() >= len {
|
||||
p.copy_from_slice(&buf[0..len]);
|
||||
return Ok(p); // success
|
||||
}
|
||||
if buf.is_empty() {
|
||||
bail!("not enough bytes");
|
||||
}
|
||||
if Instant::now() - now > duration {
|
||||
bail!("less than {} bytes in 10 seconds, closed connection?", len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stream_preamble(in_rd: &mut StanzaRead, in_wr: &mut StanzaWrite, client_addr: &'_ str, in_filter: &mut StanzaFilter) -> Result<(Vec<u8>, bool)> {
|
||||
@ -130,7 +232,7 @@ pub async fn shuffle_rd_wr_filter_only(
|
||||
pub fn read_certified_key(tls_key: &str, tls_cert: &str) -> Result<rustls::sign::CertifiedKey> {
|
||||
use rustls_pemfile::{certs, read_all, Item};
|
||||
|
||||
let tls_key = read_all(&mut BufReader::new(File::open(tls_key)?))
|
||||
let tls_key = read_all(&mut io::BufReader::new(File::open(tls_key)?))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key"))?
|
||||
.into_iter()
|
||||
.flat_map(|item| match item {
|
||||
@ -142,7 +244,7 @@ pub fn read_certified_key(tls_key: &str, tls_cert: &str) -> Result<rustls::sign:
|
||||
.next()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid key"))?;
|
||||
|
||||
let tls_certs = certs(&mut BufReader::new(File::open(tls_cert)?))
|
||||
let tls_certs = certs(&mut io::BufReader::new(File::open(tls_cert)?))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))
|
||||
.map(|mut certs| certs.drain(..).map(Certificate).collect())?;
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
use crate::{
|
||||
common::{certs_key::CertsKey, ALPN_XMPP_CLIENT, ALPN_XMPP_SERVER},
|
||||
verify::XmppServerCertVerifier,
|
||||
};
|
||||
use rustls::ClientConfig;
|
||||
use crate::common::{certs_key::CertsKey, ALPN_XMPP_CLIENT, ALPN_XMPP_SERVER};
|
||||
use rustls::{client::ServerCertVerifier, ClientConfig};
|
||||
use std::sync::Arc;
|
||||
use tokio_rustls::TlsConnector;
|
||||
|
||||
@ -13,16 +10,13 @@ pub struct OutgoingConfig {
|
||||
}
|
||||
|
||||
impl OutgoingConfig {
|
||||
pub fn with_custom_certificate_verifier(&self, is_c2s: bool, cert_verifier: XmppServerCertVerifier) -> OutgoingVerifierConfig {
|
||||
pub fn with_custom_certificate_verifier(&self, is_c2s: bool, cert_verifier: Arc<dyn ServerCertVerifier>) -> OutgoingVerifierConfig {
|
||||
let config = match is_c2s {
|
||||
false => ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_custom_certificate_verifier(Arc::new(cert_verifier))
|
||||
.with_custom_certificate_verifier(cert_verifier)
|
||||
.with_client_cert_resolver(self.certs_key.clone()),
|
||||
_ => ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_custom_certificate_verifier(Arc::new(cert_verifier))
|
||||
.with_no_client_auth(),
|
||||
_ => ClientConfig::builder().with_safe_defaults().with_custom_certificate_verifier(cert_verifier).with_no_client_auth(),
|
||||
};
|
||||
|
||||
let mut config_alpn = config.clone();
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
common::{first_bytes_match, outgoing::OutgoingConfig, shuffle_rd_wr_filter_only, stream_preamble},
|
||||
common::{outgoing::OutgoingConfig, shuffle_rd_wr_filter_only, stream_preamble, Peek},
|
||||
context::Context,
|
||||
in_out::{StanzaRead, StanzaWrite},
|
||||
slicesubsequence::SliceSubsequence,
|
||||
@ -10,13 +10,13 @@ use anyhow::Result;
|
||||
use log::{error, info};
|
||||
use tokio::{net::TcpListener, task::JoinHandle};
|
||||
|
||||
async fn handle_outgoing_connection(stream: tokio::net::TcpStream, client_addr: &mut Context<'_>, config: OutgoingConfig) -> Result<()> {
|
||||
async fn handle_outgoing_connection(mut stream: tokio::net::TcpStream, client_addr: &mut Context<'_>, config: OutgoingConfig) -> Result<()> {
|
||||
info!("{} connected", client_addr.log_from());
|
||||
|
||||
let mut in_filter = StanzaFilter::new(config.max_stanza_size_bytes);
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
let (mut in_rd, mut in_wr) = if first_bytes_match(&stream, &mut in_filter.buf[0..3], |p| p == b"GET").await? {
|
||||
let (mut in_rd, mut in_wr) = if stream.first_bytes_match(&mut in_filter.buf[0..3], |p| p == b"GET").await? {
|
||||
crate::websocket::incoming_websocket_connection(Box::new(stream), config.max_stanza_size_bytes).await?
|
||||
} else {
|
||||
let (in_rd, in_wr) = tokio::io::split(stream);
|
||||
|
@ -1,5 +1,57 @@
|
||||
use crate::common::Split;
|
||||
use anyhow::bail;
|
||||
use quinn::{RecvStream, SendStream};
|
||||
use std::{
|
||||
io::Error,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
#[cfg(feature = "incoming")]
|
||||
pub mod incoming;
|
||||
|
||||
#[cfg(feature = "outgoing")]
|
||||
pub mod outgoing;
|
||||
|
||||
pub struct QuicStream {
|
||||
pub send: SendStream,
|
||||
pub recv: RecvStream,
|
||||
}
|
||||
|
||||
impl AsyncRead for QuicStream {
|
||||
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<std::io::Result<()>> {
|
||||
Pin::new(&mut self.recv).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for QuicStream {
|
||||
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize, Error>> {
|
||||
Pin::new(&mut self.send).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
Pin::new(&mut self.send).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
Pin::new(&mut self.send).poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Split for QuicStream {
|
||||
type ReadHalf = RecvStream;
|
||||
type WriteHalf = SendStream;
|
||||
|
||||
fn combine(recv: Self::ReadHalf, send: Self::WriteHalf) -> anyhow::Result<Self> {
|
||||
if recv.id() != send.id() {
|
||||
bail!("ids do not match")
|
||||
} else {
|
||||
Ok(Self { recv, send })
|
||||
}
|
||||
}
|
||||
|
||||
fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
|
||||
(self.recv, self.send)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ use std::{
|
||||
cmp::Ordering,
|
||||
convert::TryFrom,
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio_rustls::webpki::{DnsName, DnsNameRef};
|
||||
#[cfg(feature = "websocket")]
|
||||
@ -463,7 +464,7 @@ pub async fn srv_connect(
|
||||
bail!("outgoing s2s connection but s2s-outgoing disabled at compile-time");
|
||||
}
|
||||
let (srvs, cert_verifier) = get_xmpp_connections(domain, is_c2s).await?;
|
||||
let config = config.with_custom_certificate_verifier(is_c2s, cert_verifier);
|
||||
let config = config.with_custom_certificate_verifier(is_c2s, Arc::new(cert_verifier));
|
||||
for srv in srvs {
|
||||
let connect = srv.connect(domain, stream_open, in_filter, client_addr, config.clone()).await;
|
||||
if connect.is_err() {
|
||||
|
@ -1,8 +1,7 @@
|
||||
use crate::{
|
||||
common::{
|
||||
first_bytes_match,
|
||||
incoming::{shuffle_rd_wr_filter, CloneableConfig, ServerCerts},
|
||||
to_str, IN_BUFFER_SIZE,
|
||||
to_str, Peek, Split, IN_BUFFER_SIZE,
|
||||
},
|
||||
context::Context,
|
||||
in_out::{StanzaRead, StanzaWrite},
|
||||
@ -14,7 +13,7 @@ use log::{error, info, trace};
|
||||
use rustls::{ServerConfig, ServerConnection};
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufStream},
|
||||
io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufStream},
|
||||
net::TcpListener,
|
||||
task::JoinHandle,
|
||||
};
|
||||
@ -41,7 +40,13 @@ pub fn spawn_tls_listener(listener: TcpListener, config: CloneableConfig, accept
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_tls_connection(mut stream: tokio::net::TcpStream, client_addr: &mut Context<'_>, local_addr: SocketAddr, config: CloneableConfig, acceptor: TlsAcceptor) -> Result<()> {
|
||||
pub async fn handle_tls_connection<S: AsyncRead + AsyncWrite + Unpin + Send + Sync + Peek + Split + 'static>(
|
||||
mut stream: S,
|
||||
client_addr: &mut Context<'_>,
|
||||
local_addr: SocketAddr,
|
||||
config: CloneableConfig,
|
||||
acceptor: TlsAcceptor,
|
||||
) -> Result<()> {
|
||||
info!("{} connected", client_addr.log_from());
|
||||
|
||||
let mut in_filter = StanzaFilter::new(config.max_stanza_size_bytes);
|
||||
@ -52,16 +57,19 @@ async fn handle_tls_connection(mut stream: tokio::net::TcpStream, client_addr: &
|
||||
*
|
||||
* could just check the leading 0x16 is TLS, it would *probably* be ok ?
|
||||
*/
|
||||
let direct_tls = first_bytes_match(&stream, &mut in_filter.buf[0..3], |p| p[0] == 0x16 && p[1] == 0x03 && p[2] <= 0x03).await?;
|
||||
let direct_tls = stream.first_bytes_match(&mut in_filter.buf[0..3], |p| p[0] == 0x16 && p[1] == 0x03 && p[2] <= 0x03).await?;
|
||||
|
||||
client_addr.set_proto(if direct_tls { "directtls-in" } else { "starttls-in" });
|
||||
info!("{} direct_tls sniffed", client_addr.log_from());
|
||||
|
||||
// starttls
|
||||
if !direct_tls {
|
||||
let stream = if !direct_tls {
|
||||
let mut proceed_sent = false;
|
||||
|
||||
let (in_rd, mut in_wr) = stream.split();
|
||||
// todo: more efficient version for TCP:
|
||||
//let (in_rd, mut in_wr) = stream.split();
|
||||
|
||||
// we naively read 1 byte at a time, which buffering significantly speeds up
|
||||
let in_rd = tokio::io::BufReader::with_capacity(IN_BUFFER_SIZE, in_rd);
|
||||
let mut in_rd = StanzaReader(in_rd);
|
||||
@ -109,7 +117,10 @@ async fn handle_tls_connection(mut stream: tokio::net::TcpStream, client_addr: &
|
||||
if !proceed_sent {
|
||||
bail!("stream ended before open");
|
||||
}
|
||||
}
|
||||
<S as Split>::combine(in_rd.0.into_inner(), in_wr)?
|
||||
} else {
|
||||
stream
|
||||
};
|
||||
|
||||
let stream = acceptor.accept(stream).await?;
|
||||
let (_, server_connection) = stream.get_ref();
|
||||
@ -138,30 +149,9 @@ async fn handle_tls_connection(mut stream: tokio::net::TcpStream, client_addr: &
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
{
|
||||
let stream: tokio_rustls::TlsStream<tokio::net::TcpStream> = stream.into();
|
||||
|
||||
let mut stream = BufStream::with_capacity(IN_BUFFER_SIZE, 0, stream);
|
||||
let websocket = {
|
||||
// wait up to 10 seconds until 3 bytes have been read
|
||||
use std::time::{Duration, Instant};
|
||||
let duration = Duration::from_secs(10);
|
||||
let now = Instant::now();
|
||||
let mut buf = stream.fill_buf().await?;
|
||||
loop {
|
||||
if buf.len() >= 3 {
|
||||
break; // success
|
||||
}
|
||||
if buf.is_empty() {
|
||||
bail!("not enough bytes");
|
||||
}
|
||||
if Instant::now() - now > duration {
|
||||
bail!("less than 3 bytes in 10 seconds, closed connection?");
|
||||
}
|
||||
buf = stream.fill_buf().await?;
|
||||
}
|
||||
|
||||
buf[..3] == b"GET"[..]
|
||||
};
|
||||
let websocket = stream.first_bytes_match(&mut in_filter.buf[0..3], |b| b == b"GET").await?;
|
||||
|
||||
if websocket {
|
||||
crate::websocket::incoming::handle_websocket_connection(Box::new(stream), config, server_certs, local_addr, client_addr, in_filter).await
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
common::ca_roots::TLS_SERVER_ROOTS,
|
||||
srv::{digest, Posh},
|
||||
};
|
||||
use log::debug;
|
||||
use log::{debug, trace};
|
||||
use ring::digest::SHA256;
|
||||
use rustls::{
|
||||
client::{ServerCertVerified, ServerCertVerifier},
|
||||
@ -41,6 +41,16 @@ pub fn pki_error(error: webpki::Error) -> Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_is_valid_tls_server_cert<'a>(end_entity: &'a Certificate, intermediates: &'a [Certificate], now: SystemTime) -> Result<webpki::EndEntityCert<'a>, Error> {
|
||||
// from WebPkiVerifier, validates CA trusted cert
|
||||
let (cert, chain) = prepare(end_entity, intermediates)?;
|
||||
let webpki_now = webpki::Time::try_from(now).map_err(|_| Error::FailedToGetCurrentTime)?;
|
||||
|
||||
cert.verify_is_valid_tls_server_cert(SUPPORTED_SIG_ALGS, &TLS_SERVER_ROOTS, &chain, webpki_now).map_err(pki_error)?;
|
||||
|
||||
Ok(cert)
|
||||
}
|
||||
|
||||
pub struct AllowAnonymousOrAnyCert;
|
||||
|
||||
impl ClientCertVerifier for AllowAnonymousOrAnyCert {
|
||||
@ -62,9 +72,9 @@ impl ClientCertVerifier for AllowAnonymousOrAnyCert {
|
||||
}
|
||||
}
|
||||
|
||||
type CertChainAndRoots<'a, 'b> = (webpki::EndEntityCert<'a>, Vec<&'a [u8]>);
|
||||
type CertChainAndRoots<'a> = (webpki::EndEntityCert<'a>, Vec<&'a [u8]>);
|
||||
|
||||
fn prepare<'a, 'b>(end_entity: &'a Certificate, intermediates: &'a [Certificate]) -> Result<CertChainAndRoots<'a, 'b>, Error> {
|
||||
fn prepare<'a>(end_entity: &'a Certificate, intermediates: &'a [Certificate]) -> Result<CertChainAndRoots<'a>, Error> {
|
||||
// EE cert must appear first.
|
||||
let cert = webpki::EndEntityCert::try_from(end_entity.0.as_ref()).map_err(pki_error)?;
|
||||
|
||||
@ -88,8 +98,8 @@ impl XmppServerCertVerifier {
|
||||
pub fn verify_cert(&self, end_entity: &Certificate, intermediates: &[Certificate], now: SystemTime) -> Result<ServerCertVerified, Error> {
|
||||
if !self.sha256_pinnedpubkeys.is_empty() {
|
||||
let cert = webpki::TrustAnchor::try_from_cert_der(end_entity.0.as_ref()).map_err(pki_error)?;
|
||||
println!("spki.len(): {}", cert.spki.len());
|
||||
println!("spki: {:?}", cert.spki);
|
||||
trace!("spki.len(): {}", cert.spki.len());
|
||||
trace!("spki: {:?}", cert.spki);
|
||||
// todo: what is wrong with webpki? it returns *almost* the right answer but missing these leading bytes:
|
||||
// guess I'll open an issue... (I assume this is some type of algorithm identifying header or something)
|
||||
let mut pubkey: Vec<u8> = vec![48, 130, 1, 34];
|
||||
@ -111,11 +121,8 @@ impl XmppServerCertVerifier {
|
||||
debug!("posh failed for {:?}", self.names.first());
|
||||
}
|
||||
}
|
||||
// from WebPkiVerifier, validates CA trusted cert
|
||||
let (cert, chain) = prepare(end_entity, intermediates)?;
|
||||
let webpki_now = webpki::Time::try_from(now).map_err(|_| Error::FailedToGetCurrentTime)?;
|
||||
|
||||
cert.verify_is_valid_tls_server_cert(SUPPORTED_SIG_ALGS, &TLS_SERVER_ROOTS, &chain, webpki_now).map_err(pki_error)?;
|
||||
// validates CA trusted cert
|
||||
let cert = verify_is_valid_tls_server_cert(end_entity, intermediates, now)?;
|
||||
|
||||
for name in &self.names {
|
||||
if cert.verify_is_valid_for_dns_name(name.as_ref()).is_ok() {
|
||||
|
Loading…
Reference in New Issue
Block a user