diff options
Diffstat (limited to 'core/portmanager.lua')
-rw-r--r-- | core/portmanager.lua | 104 |
1 files changed, 88 insertions, 16 deletions
diff --git a/core/portmanager.lua b/core/portmanager.lua index bed5eca5..95d42b77 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 @@ -222,15 +234,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; |