--- /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.
--- /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);
--- /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`.
--- /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)
--- /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`.
--- /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);
--- 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
--- 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(
--- 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
--- 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;
--- 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
--- 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;
--- 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.
--- 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 `<message
+to="anything@hello.example"/>`
+
+```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
--- 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();
--- 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();
--- 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
--- 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
--- 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"
},
--- 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
--- 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);
-