aboutsummaryrefslogtreecommitdiffstats
path: root/core/portmanager.lua
diff options
context:
space:
mode:
Diffstat (limited to 'core/portmanager.lua')
-rw-r--r--core/portmanager.lua108
1 files changed, 91 insertions, 17 deletions
diff --git a/core/portmanager.lua b/core/portmanager.lua
index bed5eca5..38c74b66 100644
--- a/core/portmanager.lua
+++ b/core/portmanager.lua
@@ -9,7 +9,8 @@ local set = require "util.set";
local table = table;
local setmetatable, rawset, rawget = setmetatable, rawset, rawget;
-local type, tonumber, tostring, ipairs = type, tonumber, tostring, ipairs;
+local type, tonumber, ipairs = type, tonumber, ipairs;
+local pairs = pairs;
local prosody = prosody;
local fire_event = prosody.events.fire_event;
@@ -64,6 +65,20 @@ local function error_to_friendly_message(service_name, port, err) --luacheck: ig
return friendly_message;
end
+local function get_port_ssl_ctx(port, interface, config_prefix, service_info)
+ local global_ssl_config = config.get("*", "ssl") or {};
+ local prefix_ssl_config = config.get("*", config_prefix.."ssl") or global_ssl_config;
+ log("debug", "Creating context for direct TLS service %s on port %d", service_info.name, port);
+ local ssl, err, cfg = certmanager.create_context(service_info.name.." port "..port, "server",
+ prefix_ssl_config[interface],
+ prefix_ssl_config[port],
+ prefix_ssl_config,
+ service_info.ssl_config or {},
+ global_ssl_config[interface],
+ global_ssl_config[port]);
+ return ssl, cfg, err;
+end
+
--- Public API
local function activate(service_name)
@@ -95,31 +110,22 @@ local function activate(service_name)
}
bind_ports = set.new(type(bind_ports) ~= "table" and { bind_ports } or bind_ports );
- local mode, ssl = listener.default_mode or default_mode;
+ local mode = listener.default_mode or default_mode;
local hooked_ports = {};
for interface in bind_interfaces do
for port in bind_ports do
local port_number = tonumber(port);
if not port_number then
- log("error", "Invalid port number specified for service '%s': %s", service_info.name, tostring(port));
+ log("error", "Invalid port number specified for service '%s': %s", service_info.name, port);
elseif #active_services:search(nil, interface, port_number) > 0 then
log("error", "Multiple services configured to listen on the same port ([%s]:%d): %s, %s", interface, port,
active_services:search(nil, interface, port)[1][1].service.name or "<unnamed>", service_name or "<unnamed>");
else
- local err;
+ local ssl, cfg, err;
-- Create SSL context for this service/port
if service_info.encryption == "ssl" then
- local global_ssl_config = config.get("*", "ssl") or {};
- local prefix_ssl_config = config.get("*", config_prefix.."ssl") or global_ssl_config;
- log("debug", "Creating context for direct TLS service %s on port %d", service_info.name, port);
- ssl, err = certmanager.create_context(service_info.name.." port "..port, "server",
- prefix_ssl_config[interface],
- prefix_ssl_config[port],
- prefix_ssl_config,
- service_info.ssl_config or {},
- global_ssl_config[interface],
- global_ssl_config[port]);
+ ssl, cfg, err = get_port_ssl_ctx(port, interface, config_prefix, service_info);
if not ssl then
log("error", "Error binding encrypted port for %s: %s", service_info.name,
error_to_friendly_message(service_name, port_number, err) or "unknown error");
@@ -127,7 +133,12 @@ local function activate(service_name)
end
if not err then
-- Start listening on interface+port
- local handler, err = server.addserver(interface, port_number, listener, mode, ssl);
+ local handler, err = server.listen(interface, port_number, listener, {
+ read_size = mode,
+ tls_ctx = ssl,
+ tls_direct = service_info.encryption == "ssl";
+ sni_hosts = {},
+ });
if not handler then
log("error", "Failed to open server port %d on %s, %s", port_number, interface,
error_to_friendly_message(service_name, port_number, err));
@@ -137,6 +148,7 @@ local function activate(service_name)
active_services:add(service_name, interface, port_number, {
server = handler;
service = service_info;
+ tls_cfg = cfg;
});
end
end
@@ -163,7 +175,7 @@ end
local function register_service(service_name, service_info)
table.insert(services[service_name], service_info);
- if not active_services:get(service_name) then
+ if not active_services:get(service_name) and prosody.process_type == "prosody" then
log("debug", "No active service for %s, activating...", service_name);
local ok, err = activate(service_name);
if not ok then
@@ -204,7 +216,9 @@ function close(interface, port)
end
function get_service_at(interface, port)
- local data = active_services:search(nil, interface, port)[1][1];
+ local data = active_services:search(nil, interface, port);
+ if not data or not data[1] or not data[1][1] then return nil, "not-found"; end
+ data = data[1][1];
return data.service, data.server;
end
@@ -222,15 +236,75 @@ end
-- Event handlers
+local function add_sni_host(host, service)
+ log("debug", "Gathering certificates for SNI for host %s, %s service", host, service or "default");
+ for name, interface, port, n, active_service --luacheck: ignore 213
+ in active_services:iter(service, nil, nil, nil) do
+ if active_service.server.hosts and active_service.tls_cfg then
+ local config_prefix = (active_service.config_prefix or name).."_";
+ if config_prefix == "_" then config_prefix = ""; end
+ local prefix_ssl_config = config.get(host, config_prefix.."ssl");
+ local alternate_host = name and config.get(host, name.."_host");
+ if not alternate_host and name == "https" then
+ -- TODO should this be some generic thing? e.g. in the service definition
+ alternate_host = config.get(host, "http_host");
+ end
+ local autocert = certmanager.find_host_cert(alternate_host or host);
+ -- luacheck: ignore 211/cfg
+ local ssl, err, cfg = certmanager.create_context(host, "server", prefix_ssl_config, autocert, active_service.tls_cfg);
+ if ssl then
+ active_service.server.hosts[alternate_host or host] = ssl;
+ else
+ log("error", "Error creating TLS context for SNI host %s: %s", host, err);
+ end
+ end
+ end
+end
prosody.events.add_handler("item-added/net-provider", function (event)
local item = event.item;
register_service(item.name, item);
+ for host in pairs(prosody.hosts) do
+ add_sni_host(host, item.name);
+ end
end);
prosody.events.add_handler("item-removed/net-provider", function (event)
local item = event.item;
unregister_service(item.name, item);
end);
+prosody.events.add_handler("host-activated", add_sni_host);
+prosody.events.add_handler("host-deactivated", function (host)
+ for name, interface, port, n, active_service --luacheck: ignore 213
+ in active_services:iter(nil, nil, nil, nil) do
+ if active_service.tls_cfg then
+ active_service.server.hosts[host] = nil;
+ end
+ end
+end);
+
+prosody.events.add_handler("config-reloaded", function ()
+ for service_name, interface, port, _, active_service in active_services:iter(nil, nil, nil, nil) do
+ if active_service.tls_cfg then
+ local service_info = active_service.service;
+ local config_prefix = (service_info.config_prefix or service_name).."_";
+ if config_prefix == "_" then
+ config_prefix = "";
+ end
+ local ssl, cfg, err = get_port_ssl_ctx(port, interface, config_prefix, service_info);
+ if ssl then
+ active_service.server:set_sslctx(ssl);
+ active_service.tls_cfg = cfg;
+ else
+ log("error", "Error reloading certificate for encrypted port for %s: %s", service_info.name,
+ error_to_friendly_message(service_name, port, err) or "unknown error");
+ end
+ end
+ end
+ for host in pairs(prosody.hosts) do
+ add_sni_host(host, nil);
+ end
+end, -1);
+
return {
activate = activate;
deactivate = deactivate;