# HG changeset patch # User Goffi # Date 1652265872 -7200 # Node ID bc8832c6696be07ee466f001f0ba3750281d20ef # Parent 3ddab718f71707d9b58b656e7a12668f803dc5a6# Parent d63657a85fb403902c125543fd276dd78ac213be upstream merge diff -r 3ddab718f717 -r bc8832c6696b mod_audit/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_audit/README.md Wed May 11 12:44:32 2022 +0200 @@ -0,0 +1,27 @@ +--- +summary: Audit Logging +rockspec: {} +... + +This module provides infrastructure for audit logging inside Prosody. + +## What is audit logging? + +Audit logs will contain security sensitive events, both for server-wide +incidents as well as user-specific. + +This module, however, only provides the infrastructure for audit logging. It +does not, by itself, generate such logs. For that, other modules, such as +`mod_audit_auth` or `mod_audit_register` need to be loaded. + +## A note on privacy + +Audit logging is intended to ensure the security of a system. As such, its +contents are often at the same time highly sensitive (containing user names +and IP addresses, for instance) and allowed to be stored under common privacy +regulations. + +Before using these modules, you may want to ensure that you are legally +allowed to store the data for the amount of time these modules will store it. +Note that it is currently not possible to store different event types with +different expiration times. diff -r 3ddab718f717 -r bc8832c6696b mod_audit/mod_audit.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_audit/mod_audit.lua Wed May 11 12:44:32 2022 +0200 @@ -0,0 +1,85 @@ +module:set_global(); + +local time_now = os.time; +local st = require "util.stanza"; +local moduleapi = require "core.moduleapi"; + +local host_wide_user = "@"; + +local stores = {}; + +local function get_store(self, host) + local store = rawget(self, host); + if store then + return store + end + store = module:context(host):open_store("audit", "archive"); + rawset(self, host, store); + return store; +end + +setmetatable(stores, { __index = get_store }); + + +local function session_extra(session) + local attr = { + xmlns = "xmpp:prosody.im/audit", + }; + if session.id then + attr.id = session.id; + end + if session.type then + attr.type = session.type; + end + local stanza = st.stanza("session", attr); + if session.ip then + stanza:text_tag("remote-ip", session.ip); + end + return stanza +end + +local function audit(host, user, source, event_type, extra) + if not host or host == "*" then + error("cannot log audit events for global"); + end + local user_key = user or host_wide_user; + + local attr = { + ["source"] = source, + ["type"] = event_type, + }; + if user_key ~= host_wide_user then + attr.user = user_key; + end + local stanza = st.stanza("audit-event", attr); + if extra ~= nil then + if extra.session then + local child = session_extra(extra.session); + if child then + stanza:add_child(child); + end + end + if extra.custom then + for _, child in extra.custom do + if not st.is_stanza(child) then + error("all extra.custom items must be stanzas") + end + stanza:add_child(child); + end + end + end + + local id, err = stores[host]:append(nil, nil, stanza, time_now(), user_key); + if err then + module:log("error", "failed to persist audit event: %s", err); + return + else + module:log("debug", "persisted audit event %s as %s", stanza:top_tag(), id); + end +end + +function moduleapi.audit(module, user, event_type, extra) + audit(module.host, user, "mod_" .. module:get_name(), event_type, extra); +end + +module:hook("audit", audit, 0); diff -r 3ddab718f717 -r bc8832c6696b mod_audit_auth/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_audit_auth/README.md Wed May 11 12:44:32 2022 +0200 @@ -0,0 +1,9 @@ +--- +summary: Store authentication events in the audit log +rockspec: + dependencies: + - mod_audit +... + +This module stores authentication failures and authentication successes in the +audit log provided by `mod_audit`. diff -r 3ddab718f717 -r bc8832c6696b mod_audit_auth/mod_audit_auth.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_audit_auth/mod_audit_auth.lua Wed May 11 12:44:32 2022 +0200 @@ -0,0 +1,16 @@ +module:depends("audit"); +-- luacheck: read globals module.audit + +module:hook("authentication-failure", function(event) + local session = event.session; + module:audit(session.sasl_handler.username, "authentication-failure", { + session = session, + }); +end) + +module:hook("authentication-success", function(event) + local session = event.session; + module:audit(session.sasl_handler.username, "authentication-success", { + session = session, + }); +end) diff -r 3ddab718f717 -r bc8832c6696b mod_audit_register/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_audit_register/README.md Wed May 11 12:44:32 2022 +0200 @@ -0,0 +1,9 @@ +--- +summary: Store registration events in the audit log +rockspec: + dependencies: + - mod_audit +... + +This module stores successful user registrations in the audit log provided by +`mod_audit`. diff -r 3ddab718f717 -r bc8832c6696b mod_audit_register/mod_audit_register.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_audit_register/mod_audit_register.lua Wed May 11 12:44:32 2022 +0200 @@ -0,0 +1,23 @@ +module:depends("audit"); +-- luacheck: read globals module.audit + +local st = require "util.stanza"; + +module:hook("user-registered", function(event) + local session = event.session; + local custom = {}; + local invite = event.validated_invite or (event.session and event.session.validated_invite); + if invite then + table.insert(custom, st.stanza( + "invite-used", + { + xmlns = "xmpp:prosody.im/audit", + token = invite.token, + } + )) + end + module:audit(event.username, "user-registered", { + session = session, + custom = custom, + }); +end); diff -r 3ddab718f717 -r bc8832c6696b mod_auth_cyrus/README.md --- a/mod_auth_cyrus/README.md Wed May 11 12:43:26 2022 +0200 +++ b/mod_auth_cyrus/README.md Wed May 11 12:44:32 2022 +0200 @@ -3,7 +3,7 @@ rockspec: build: modules: - util.sasl_cyrus: sasl_cyrus.lua + mod_auth_cyrus.sasl_cyrus: sasl_cyrus.lua --- # Introduction diff -r 3ddab718f717 -r bc8832c6696b mod_auth_cyrus/mod_auth_cyrus.lua --- a/mod_auth_cyrus/mod_auth_cyrus.lua Wed May 11 12:43:26 2022 +0200 +++ b/mod_auth_cyrus/mod_auth_cyrus.lua Wed May 11 12:44:32 2022 +0200 @@ -19,7 +19,7 @@ prosody.unlock_globals(); --FIXME: Figure out why this is needed and -- why cyrussasl isn't caught by the sandbox -local cyrus_new = require "util.sasl_cyrus".new; +local cyrus_new = module:require "sasl_cyrus".new; prosody.lock_globals(); local new_sasl = function(realm) return cyrus_new( diff -r 3ddab718f717 -r bc8832c6696b mod_conversejs/README.markdown --- a/mod_conversejs/README.markdown Wed May 11 12:43:26 2022 +0200 +++ b/mod_conversejs/README.markdown Wed May 11 12:44:32 2022 +0200 @@ -10,8 +10,6 @@ build: copy_directories: - templates - dependencies: - - mod_bookmarks --- Introduction diff -r 3ddab718f717 -r bc8832c6696b mod_conversejs/mod_conversejs.lua --- a/mod_conversejs/mod_conversejs.lua Wed May 11 12:43:26 2022 +0200 +++ b/mod_conversejs/mod_conversejs.lua Wed May 11 12:44:32 2022 +0200 @@ -1,5 +1,5 @@ -- mod_conversejs --- Copyright (C) 2017 Kim Alvefur +-- Copyright (C) 2017-2022 Kim Alvefur local json_encode = require"util.json".encode; local xml_escape = require "util.stanza".xml_escape; diff -r 3ddab718f717 -r bc8832c6696b mod_http_admin_api/mod_http_admin_api.lua --- a/mod_http_admin_api/mod_http_admin_api.lua Wed May 11 12:43:26 2022 +0200 +++ b/mod_http_admin_api/mod_http_admin_api.lua Wed May 11 12:44:32 2022 +0200 @@ -330,7 +330,8 @@ service = push_info.jid; node = push_info.node; error_count = push_errors[identifier] or 0; - client = push_info.client; + client_id = push_info.client_id; + encryption = not not push_info.encryption; }; end end diff -r 3ddab718f717 -r bc8832c6696b mod_http_muc_log/mod_http_muc_log.lua --- a/mod_http_muc_log/mod_http_muc_log.lua Wed May 11 12:43:26 2022 +0200 +++ b/mod_http_muc_log/mod_http_muc_log.lua Wed May 11 12:44:32 2022 +0200 @@ -275,6 +275,10 @@ local request, response = event.request, event.response; local room, date = path:match("^([^/]+)/([^/]*)/?$"); + if not room then + response.headers.location = url.build({ path = path .. "/" }); + return 303; + end room = nodeprep(room); if not room then return 400; diff -r 3ddab718f717 -r bc8832c6696b mod_http_oauth2/README.markdown --- a/mod_http_oauth2/README.markdown Wed May 11 12:43:26 2022 +0200 +++ b/mod_http_oauth2/README.markdown Wed May 11 12:44:32 2022 +0200 @@ -17,4 +17,4 @@ Compatibility ============= -Requires Prosody trunk. +Requires Prosody 0.12+ or trunk. diff -r 3ddab718f717 -r bc8832c6696b mod_rest/README.markdown --- a/mod_rest/README.markdown Wed May 11 12:43:26 2022 +0200 +++ b/mod_rest/README.markdown Wed May 11 12:44:32 2022 +0200 @@ -130,7 +130,12 @@ rest_callback_url = "http://my-api.example:9999/stanzas" ``` -To enable JSON payloads set +The callback URL supports a few variables from the stanza being sent, +namely `{kind}` (e.g. message, presence, iq or meta) and ones +corresponding to stanza attributes: `{type}`, `{to}` and `{from}`. + +The preferred format can be indicated via the Accept header in response +to an OPTIONS probe that mod_rest does on startup, or by configuring: ``` {.lua} rest_callback_content_type = "application/json" @@ -164,6 +169,41 @@ } ``` +### Which stanzas + +The set of stanzas routed to the callback is determined by these two +settings: + +`rest_callback_stanzas` +: The stanza kinds to handle, defaults to `{ "message", "presence", "iq" }` + +`rest_callback_events` +: For the selected stanza kinds, which events to handle. When loaded +on a Component, this defaults to `{ "bare", "full", "host" }`, while on +a VirtualHost the default is `{ "host" }`. + +Events correspond to which form of address was used in the `to` +attribute of the stanza. + +bare +: `localpart@hostpart` + +full +: `localpart@hostpart/resourcepart` + +host +: `hostpart` + +The following example would handle only stanzas like `` + +```lua +Component "hello.example" "rest" +rest_callback_url = "http://hello.internal.example:9003/api" +rest_callback_stanzas = { "message" } +rest_callback_events = { "bare" } +``` + ### Replying To accept the stanza without returning a reply, respond with HTTP status @@ -236,9 +276,6 @@ `status` : Human-readable status message. -`join` -: Boolean. Join a group chat. - #### Info-Queries Only one type of payload can be included in an `iq`. @@ -395,8 +432,8 @@ ### Presence -`join` -: Boolean, used to join group chats. +`muc` +: Object with [MUC][XEP-0045] related properties. ### IQ diff -r 3ddab718f717 -r bc8832c6696b mod_rest/apidemo.lib.lua --- a/mod_rest/apidemo.lib.lua Wed May 11 12:43:26 2022 +0200 +++ b/mod_rest/apidemo.lib.lua Wed May 11 12:44:32 2022 +0200 @@ -11,7 +11,12 @@ }); local index do - local f = assert(io.open(api_demo.."/index.html"), "'rest_demo_resources' should point to the 'dist' directory"); + local f, err = io.open(api_demo.."/index.html"); + if not f then + module:log("error", "Could not open resource: %s", err); + module:log("error", "'rest_demo_resources' should point to the 'dist' directory"); + return _M + end index = f:read("*a"); f:close(); diff -r 3ddab718f717 -r bc8832c6696b mod_rest/jsonmap.lib.lua --- a/mod_rest/jsonmap.lib.lua Wed May 11 12:43:26 2022 +0200 +++ b/mod_rest/jsonmap.lib.lua Wed May 11 12:44:32 2022 +0200 @@ -50,6 +50,8 @@ return s.attr.node or true; end local identities, features, extensions = array(), array(), {}; + + -- features and identities could be done with util.datamapper for tag in s:childtags() do if tag.name == "identity" and tag.attr.category and tag.attr.type then identities:push({ category = tag.attr.category, type = tag.attr.type, name = tag.attr.name }); @@ -57,6 +59,8 @@ features:push(tag.attr.var); end end + + -- Especially this would be hard to do with util.datamapper for form in s:childtags("x", "jabber:x:data") do local jform = field_mappings.formdata.st2json(form); local form_type = jform["FORM_TYPE"]; @@ -65,6 +69,7 @@ extensions[form_type] = jform; end end + if next(extensions) == nil then extensions = nil; end return { node = s.attr.node, identities = identities, features = features, extensions = extensions }; end; @@ -211,26 +216,6 @@ end; }; - -- XEP-0432: Simple JSON Messaging - payload = { type = "func", xmlns = "urn:xmpp:json-msg:0", tagname = "payload", - st2json = function (s) - local rawjson = s:get_child_text("json", "urn:xmpp:json:0"); - if not rawjson then return nil, "missing-json-payload"; end - local parsed, err = json.decode(rawjson); - if not parsed then return nil, err; end - return { - datatype = s.attr.datatype; - data = parsed; - }; - end; - json2st = function (s) - if type(s) == "table" then - return st.stanza("payload", { xmlns = "urn:xmpp:json-msg:0", datatype = s.datatype }) - :tag("json", { xmlns = "urn:xmpp:json:0" }):text(json.encode(s.data)); - end; - end - }; - -- XEP-0004: Data Forms dataform = { -- Generic and complete dataforms mapping @@ -450,6 +435,19 @@ return t; end + if type(t.payload) == "table" then + if type(t.payload.data) == "string" then + local data, err = json.decode(t.payload.data); + if err then + return nil, err; + else + t.payload.data = data; + end + else + return nil, "invalid payload.data"; + end + end + for _, tag in ipairs(s.tags) do local prefix = "{" .. (tag.attr.xmlns or "jabber:client") .. "}"; local mapping = byxmlname[prefix .. tag.name]; @@ -536,6 +534,15 @@ end end + if type(t.payload) == "table" then + t.payload.data = json.encode(t.payload.data); + end + + if kind == "presence" and t.join == true and t.muc == nil then + -- COMPAT Older boolean 'join' property used with XEP-0045 + t.muc = {}; + end + local s = map.unparse(schema, { [kind or "message"] = t }).tags[1]; s.attr.type = t_type; @@ -552,8 +559,8 @@ for k, v in pairs(t) do local mapping = field_mappings[k]; if mapping and mapping.type == "func" and mapping.json2st then - s:add_child(mapping.json2st(v)):up(); - end + s:add_child(mapping.json2st(v)):up(); + end end s:reset(); diff -r 3ddab718f717 -r bc8832c6696b mod_rest/mod_rest.lua --- a/mod_rest/mod_rest.lua Wed May 11 12:43:26 2022 +0200 +++ b/mod_rest/mod_rest.lua Wed May 11 12:44:32 2022 +0200 @@ -1,6 +1,6 @@ -- RESTful API -- --- Copyright (c) 2019-2020 Kim Alvefur +-- Copyright (c) 2019-2022 Kim Alvefur -- -- This file is MIT/X11 licensed. @@ -230,16 +230,21 @@ end local function encode(type, s) + if type == "text/plain" then + return s:get_child_text("body") or ""; + elseif type == "application/xmpp+xml" then + return tostring(s); + end + local mapped, err = jsonmap.st2json(s); + if not mapped then return mapped, err; end if type == "application/json" then - return json.encode(jsonmap.st2json(s)); + return json.encode(mapped); elseif type == "application/x-www-form-urlencoded" then - return http.formencode(flatten(jsonmap.st2json(s))); + return http.formencode(flatten(mapped)); elseif type == "application/cbor" then - return cbor.encode(jsonmap.st2json(s)); - elseif type == "text/plain" then - return s:get_child_text("body") or ""; + return cbor.encode(mapped); end - return tostring(s); + error "unsupported encoding"; end local post_errors = errors.init("mod_rest", { @@ -332,8 +337,10 @@ local send_type = decide_type((request.headers.accept or "") ..",".. (request.headers.content_type or ""), supported_outputs) if echo then + local ret, err = errors.coerce(encode(send_type, payload)); + if not ret then return err; end response.headers.content_type = send_type; - return encode(send_type, payload); + return ret; end if payload.name == "iq" then @@ -409,23 +416,35 @@ -- Forward stanzas from XMPP to HTTP and return any reply local rest_url = module:get_option_string("rest_callback_url", nil); if rest_url then + local function get_url() return rest_url; end + if rest_url:find("%b{}") then + local httputil = require "util.http"; + local render_url = require"util.interpolation".new("%b{}", httputil.urlencode); + function get_url(stanza) + local at = stanza.attr; + return render_url(rest_url, { kind = stanza.name, type = at.type, to = at.to, from = at.from }); + end + end local send_type = module:get_option_string("rest_callback_content_type", "application/xmpp+xml"); if send_type == "json" then send_type = "application/json"; end module:set_status("info", "Not yet connected"); - http.request(rest_url, { + http.request(get_url(st.stanza("meta", { type = "info", to = module.host, from = module.host })), { method = "OPTIONS", }, function (body, code, response) if code == 0 then - return module:log_status("error", "Could not connect to callback URL %q: %s", rest_url, body); - else + module:log_status("error", "Could not connect to callback URL %q: %s", rest_url, body); + elseif code == 200 then module:set_status("info", "Connected"); - end - if code == 200 and response.headers.accept then - send_type = decide_type(response.headers.accept, supported_outputs); - module:log("debug", "Set 'rest_callback_content_type' = %q based on Accept header", send_type); + if response.headers.accept then + send_type = decide_type(response.headers.accept, supported_outputs); + module:log("debug", "Set 'rest_callback_content_type' = %q based on Accept header", send_type); + end + else + module:log_status("warn", "Unexpected response code %d from OPTIONS probe", code); + module:log("warn", "Endpoint said: %s", body); end end); @@ -448,7 +467,7 @@ stanza = st.clone(stanza, true); module:log("debug", "Sending[rest]: %s", stanza:top_tag()); - http.request(rest_url, { + http.request(get_url(stanza), { body = request_body, headers = { ["Content-Type"] = send_type, @@ -534,21 +553,19 @@ return true; end - if module:get_host_type() == "component" then - module:hook("iq/bare", handle_stanza, -1); - module:hook("message/bare", handle_stanza, -1); - module:hook("presence/bare", handle_stanza, -1); - module:hook("iq/full", handle_stanza, -1); - module:hook("message/full", handle_stanza, -1); - module:hook("presence/full", handle_stanza, -1); - module:hook("iq/host", handle_stanza, -1); - module:hook("message/host", handle_stanza, -1); - module:hook("presence/host", handle_stanza, -1); - else - -- Don't override everything on normal VirtualHosts - module:hook("iq/host", handle_stanza, -1); - module:hook("message/host", handle_stanza, -1); - module:hook("presence/host", handle_stanza, -1); + local send_kinds = module:get_option_set("rest_callback_stanzas", { "message", "presence", "iq" }); + + local event_presets = { + -- Don't override everything on normal VirtualHosts by default + ["local"] = { "host" }, + -- Comonents get to handle all kinds of stanzas + ["component"] = { "bare", "full", "host" }, + }; + local hook_events = module:get_option_set("rest_callback_events", event_presets[module:get_host_type()]); + for kind in send_kinds do + for event in hook_events do + module:hook(kind.."/"..event, handle_stanza, -1); + end end end diff -r 3ddab718f717 -r bc8832c6696b mod_rest/res/openapi.yaml --- a/mod_rest/res/openapi.yaml Wed May 11 12:43:26 2022 +0200 +++ b/mod_rest/res/openapi.yaml Wed May 11 12:44:32 2022 +0200 @@ -136,7 +136,7 @@ get: tags: - query - summary: Query a message archive + summary: Query for external services (usually STUN and TURN) security: - basic: [] - token: [] @@ -283,8 +283,8 @@ idle_since: $ref: '#/components/schemas/idle_since' - join: - $ref: '#/components/schemas/join' + muc: + $ref: '#/components/schemas/muc' error: $ref: '#/components/schemas/error' @@ -510,11 +510,37 @@ namespace: urn:xmpp:message-correct:0 x_single_attribute: id - join: - description: For joining Multi-User-Chats - type: boolean - enum: - - true + muc: + description: Multi-User-Chat related + type: object + xml: + name: x + namespace: http://jabber.org/protocol/muc + properties: + history: + type: object + properties: + maxchars: + type: integer + minimum: 0 + xml: + attribute: true + maxstanzas: + type: integer + minimum: 0 + xml: + attribute: true + seconds: + type: integer + minimum: 0 + xml: + attribute: true + since: + type: string + format: date-time + xml: + attribute: true + invite: type: object diff -r 3ddab718f717 -r bc8832c6696b mod_rest/res/schema-xmpp.json --- a/mod_rest/res/schema-xmpp.json Wed May 11 12:43:26 2022 +0200 +++ b/mod_rest/res/schema-xmpp.json Wed May 11 12:44:32 2022 +0200 @@ -149,6 +149,28 @@ "namespace" : "http://jabber.org/protocol/nick" } }, + "payload" : { + "properties" : { + "data" : { + "format" : "json", + "type" : "string", + "xml" : { + "text" : true + } + }, + "datatype" : { + "type" : "string", + "xml" : { + "attribute" : true + } + } + }, + "title" : "XEP-0432: Simple JSON Messaging", + "type" : "object", + "xml" : { + "namespace" : "urn:xmpp:json-msg:0" + } + }, "rsm" : { "properties" : { "after" : { @@ -233,7 +255,7 @@ "items" : { "properties" : { "expires" : { - "format" : "datetime", + "format" : "date-time", "type" : "string", "xml" : { "attribute" : true @@ -1056,6 +1078,48 @@ "lang" : { "$ref" : "#/_common/lang" }, + "muc" : { + "properties" : { + "history" : { + "properties" : { + "maxchars" : { + "minimum" : 0, + "type" : "integer", + "xml" : { + "attribute" : true + } + }, + "maxstanzas" : { + "minimum" : 0, + "type" : "integer", + "xml" : { + "attribute" : true + } + }, + "seconds" : { + "minimum" : 0, + "type" : "integer", + "xml" : { + "attribute" : true + } + }, + "since" : { + "format" : "date-time", + "type" : "string", + "xml" : { + "attribute" : true + } + } + }, + "type" : "object" + } + }, + "type" : "object", + "xml" : { + "name" : "x", + "namespace" : "http://jabber.org/protocol/muc" + } + }, "nick" : { "$ref" : "#/_common/nick" }, diff -r 3ddab718f717 -r bc8832c6696b mod_s2soutinjection/README.markdown --- a/mod_s2soutinjection/README.markdown Wed May 11 12:43:26 2022 +0200 +++ b/mod_s2soutinjection/README.markdown Wed May 11 12:44:32 2022 +0200 @@ -16,15 +16,16 @@ "s2soutinjection"; } +-- targets must be IPs, not hostnames s2s_connect_overrides = { -- This one will use the default port, 5269 - ["example.com"] = "xmpp.server.local"; + ["example.com"] = "1.2.3.4"; -- To set a different port: - ["another.example"] = { "non-standard-port.example", 9999 }; + ["another.example"] = { "127.0.0.1", 9999 }; } ``` # Compatibility -Requires 0.9.x or later. +Requires 0.9.x or later. Tested on 0.12.0 diff -r 3ddab718f717 -r bc8832c6696b mod_s2soutinjection/mod_s2soutinjection.lua --- a/mod_s2soutinjection/mod_s2soutinjection.lua Wed May 11 12:43:26 2022 +0200 +++ b/mod_s2soutinjection/mod_s2soutinjection.lua Wed May 11 12:44:32 2022 +0200 @@ -2,12 +2,69 @@ local new_ip = require"util.ip".new_ip; local new_outgoing = require"core.s2smanager".new_outgoing; local bounce_sendq = module:depends"s2s".route_to_new_session.bounce_sendq; -local s2sout = module:depends"s2s".route_to_new_session.s2sout; +local initialize_filters = require "util.filters".initialize; +local st = require "util.stanza"; + +local portmanager = require "core.portmanager"; + +local addclient = require "net.server".addclient; + +module:depends("s2s"); + +local sessions = module:shared("sessions"); local injected = module:get_option("s2s_connect_overrides"); -local function isip(addr) - return not not (addr and addr:match("^%d+%.%d+%.%d+%.%d+$") or addr:match("^[%x:]*:[%x:]-:[%x:]*$")); +-- The proxy_listener handles connection while still connecting to the proxy, +-- then it hands them over to the normal listener (in mod_s2s) +local proxy_listener = { default_port = port, default_mode = "*a", default_interface = "*" }; + +function proxy_listener.onconnect(conn) + local session = sessions[conn]; + + -- Now the real s2s listener can take over the connection. + local listener = portmanager.get_service("s2s").listener; + + local w, log = conn.send, session.log; + + local filter = initialize_filters(session); + + session.version = 1; + + session.sends2s = function (t) + log("debug", "sending (s2s over proxy): %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); + if t.name then + t = filter("stanzas/out", t); + end + if t then + t = filter("bytes/out", tostring(t)); + if t then + return conn:write(tostring(t)); + end + end + end + + session.open_stream = function () + session.sends2s(st.stanza("stream:stream", { + xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', + ["xmlns:stream"]='http://etherx.jabber.org/streams', + from=session.from_host, to=session.to_host, version='1.0', ["xml:lang"]='en'}):top_tag()); + end + + conn.setlistener(conn, listener); + + listener.register_outgoing(conn, session); + + listener.onconnect(conn); +end + +function proxy_listener.register_outgoing(conn, session) + session.direction = "outgoing"; + sessions[conn] = session; +end + +function proxy_listener.ondisconnect(conn, err) + sessions[conn] = nil; end module:hook("route/remote", function(event) @@ -16,34 +73,18 @@ if not inject then return end log("debug", "opening a new outgoing connection for this stanza"); local host_session = new_outgoing(from_host, to_host); - host_session.version = 1; -- Store in buffer host_session.bounce_sendq = bounce_sendq; host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name)); - local ip_hosts, srv_hosts = {}, {}; - host_session.srv_hosts = srv_hosts; - host_session.srv_choice = 0; + local host, port = inject[1] or inject, tonumber(inject[2]) or 5269; - if type(inject) == "string" then inject = { inject } end + local conn = addclient(host, port, proxy_listener, "*a"); - for _, item in ipairs(inject) do - local host, port = item[1] or item, tonumber(item[2]) or 5269; - if isip(host) then - ip_hosts[#ip_hosts+1] = { ip = new_ip(host), port = port } - else - srv_hosts[#srv_hosts+1] = { target = host, port = port } - end - end - if #ip_hosts > 0 then - host_session.ip_hosts = ip_hosts; - host_session.ip_choice = 0; -- Incremented by try_next_ip - s2sout.try_next_ip(host_session); - return true; - end + proxy_listener.register_outgoing(conn, host_session); - return s2sout.try_connect(host_session, host_session.srv_hosts[1].target, host_session.srv_hosts[1].port); + host_session.conn = conn; + return true; end, -2); -