comparison plugins/mod_s2s/mod_s2s.lua @ 10411:db2a06b9ff98

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Sat, 16 Nov 2019 16:52:31 +0100
parents 3b82e9df5a7a
children 09b54ad0fdc4
comparison
equal deleted inserted replaced
10410:659b577f280c 10411:db2a06b9ff98
25 local s2s_new_outgoing = require "core.s2smanager".new_outgoing; 25 local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
26 local s2s_destroy_session = require "core.s2smanager".destroy_session; 26 local s2s_destroy_session = require "core.s2smanager".destroy_session;
27 local uuid_gen = require "util.uuid".generate; 27 local uuid_gen = require "util.uuid".generate;
28 local fire_global_event = prosody.events.fire_event; 28 local fire_global_event = prosody.events.fire_event;
29 local runner = require "util.async".runner; 29 local runner = require "util.async".runner;
30 30 local connect = require "net.connect".connect;
31 local s2sout = module:require("s2sout"); 31 local service = require "net.resolvers.service";
32 local errors = require "util.error";
32 33
33 local connect_timeout = module:get_option_number("s2s_timeout", 90); 34 local connect_timeout = module:get_option_number("s2s_timeout", 90);
34 local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5); 35 local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5);
35 local opt_keepalives = module:get_option_boolean("s2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); 36 local opt_keepalives = module:get_option_boolean("s2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true));
36 local secure_auth = module:get_option_boolean("s2s_secure_auth", false); -- One day... 37 local secure_auth = module:get_option_boolean("s2s_secure_auth", false); -- One day...
42 local measure_ipv6 = module:measure("ipv6", "amount"); 43 local measure_ipv6 = module:measure("ipv6", "amount");
43 44
44 local sessions = module:shared("sessions"); 45 local sessions = module:shared("sessions");
45 46
46 local runner_callbacks = {}; 47 local runner_callbacks = {};
48
49 local listener = {};
47 50
48 local log = module._log; 51 local log = module._log;
49 52
50 module:hook("stats-update", function () 53 module:hook("stats-update", function ()
51 local count = 0; 54 local count = 0;
75 dummy = true; 78 dummy = true;
76 close = function () 79 close = function ()
77 (session.log or log)("error", "Attempting to close the dummy origin of s2s error replies, please report this! Traceback: %s", traceback()); 80 (session.log or log)("error", "Attempting to close the dummy origin of s2s error replies, please report this! Traceback: %s", traceback());
78 end; 81 end;
79 }; 82 };
83 -- FIXME Allow for more specific error conditions
84 -- TODO use util.error ?
85 local error_type = "cancel";
86 local condition = "remote-server-not-found";
87 local reason_text;
88 if session.had_stream then -- set when a stream is opened by the remote
89 error_type, condition = "wait", "remote-server-timeout";
90 end
91 if errors.is_err(reason) then
92 error_type, condition, reason_text = reason.type, reason.condition, reason.text;
93 elseif type(reason) == "string" then
94 reason_text = reason;
95 end
80 for i, data in ipairs(sendq) do 96 for i, data in ipairs(sendq) do
81 local reply = data[2]; 97 local reply = data[2];
82 if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then 98 if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then
83 reply.attr.type = "error"; 99 reply.attr.type = "error";
84 reply:tag("error", {type = "cancel", by = session.from_host}) 100 reply:tag("error", {type = error_type, by = session.from_host})
85 :tag("remote-server-not-found", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up(); 101 :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
86 if reason then 102 if reason_text then
87 reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}) 103 reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"})
88 :text("Server-to-server connection failed: "..reason):up(); 104 :text("Server-to-server connection failed: "..reason_text):up();
89 end 105 end
90 core_process_stanza(dummy, reply); 106 core_process_stanza(dummy, reply);
91 end 107 end
92 sendq[i] = nil; 108 sendq[i] = nil;
93 end 109 end
125 host.log("debug", "stanza [%s] queued ", stanza.name); 141 host.log("debug", "stanza [%s] queued ", stanza.name);
126 return true; 142 return true;
127 elseif host.type == "local" or host.type == "component" then 143 elseif host.type == "local" or host.type == "component" then
128 log("error", "Trying to send a stanza to ourselves??") 144 log("error", "Trying to send a stanza to ourselves??")
129 log("error", "Traceback: %s", traceback()); 145 log("error", "Traceback: %s", traceback());
130 log("error", "Stanza: %s", tostring(stanza)); 146 log("error", "Stanza: %s", stanza);
131 return false; 147 return false;
132 else 148 else
133 -- FIXME
134 if host.from_host ~= from_host then
135 log("error", "WARNING! This might, possibly, be a bug, but it might not...");
136 log("error", "We are going to send from %s instead of %s", host.from_host, from_host);
137 end
138 if host.sends2s(stanza) then 149 if host.sends2s(stanza) then
139 return true; 150 return true;
140 end 151 end
141 end 152 end
142 end 153 end
145 -- Create a new outgoing session for a stanza 156 -- Create a new outgoing session for a stanza
146 function route_to_new_session(event) 157 function route_to_new_session(event)
147 local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; 158 local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
148 log("debug", "opening a new outgoing connection for this stanza"); 159 log("debug", "opening a new outgoing connection for this stanza");
149 local host_session = s2s_new_outgoing(from_host, to_host); 160 local host_session = s2s_new_outgoing(from_host, to_host);
161 host_session.version = 1;
150 162
151 -- Store in buffer 163 -- Store in buffer
152 host_session.bounce_sendq = bounce_sendq; 164 host_session.bounce_sendq = bounce_sendq;
153 host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; 165 host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} };
154 log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name)); 166 log("debug", "stanza [%s] queued until connection complete", stanza.name);
155 s2sout.initiate_connection(host_session); 167 connect(service.new(to_host, "xmpp-server", "tcp", { default_port = 5269 }), listener, nil, { session = host_session });
156 if (not host_session.connecting) and (not host_session.conn) then
157 log("warn", "Connection to %s failed already, destroying session...", to_host);
158 s2s_destroy_session(host_session, "Connection failed");
159 return false;
160 end
161 return true; 168 return true;
162 end 169 end
163 170
164 local function keepalive(event) 171 local function keepalive(event)
165 return event.session.sends2s(' '); 172 return event.session.sends2s(' ');
182 -- so the stream is ready for stanzas. RFC 6120 Section 4.3 189 -- so the stream is ready for stanzas. RFC 6120 Section 4.3
183 mark_connected(session); 190 mark_connected(session);
184 return true; 191 return true;
185 elseif not session.dialback_verifying then 192 elseif not session.dialback_verifying then
186 session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up"); 193 session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up");
187 session:close(); 194 session:close({
195 condition = "unsupported-feature",
196 text = "No viable authentication method offered",
197 });
188 return false; 198 return false;
189 end 199 end
190 end, -1); 200 end, -1);
191 end 201 end
192 202
201 211
202 local event_data = { session = session }; 212 local event_data = { session = session };
203 if session.type == "s2sout" then 213 if session.type == "s2sout" then
204 fire_global_event("s2sout-established", event_data); 214 fire_global_event("s2sout-established", event_data);
205 hosts[from].events.fire_event("s2sout-established", event_data); 215 hosts[from].events.fire_event("s2sout-established", event_data);
216
217 if session.incoming then
218 session.send = function(stanza)
219 return hosts[from].events.fire_event("route/remote", { from_host = from, to_host = to, stanza = stanza });
220 end;
221 end
222
206 else 223 else
224 if session.outgoing and not hosts[to].s2sout[from] then
225 session.log("debug", "Setting up to handle route from %s to %s", to, from);
226 hosts[to].s2sout[from] = session; -- luacheck: ignore 122
227 end
207 local host_session = hosts[to]; 228 local host_session = hosts[to];
208 session.send = function(stanza) 229 session.send = function(stanza)
209 return host_session.events.fire_event("route/remote", { from_host = to, to_host = from, stanza = stanza }); 230 return host_session.events.fire_event("route/remote", { from_host = to, to_host = from, stanza = stanza });
210 end; 231 end;
211 232
221 send(data[1]); 242 send(data[1]);
222 sendq[i] = nil; 243 sendq[i] = nil;
223 end 244 end
224 session.sendq = nil; 245 session.sendq = nil;
225 end 246 end
226
227 if session.resolver then
228 session.resolver._resolver:closeall()
229 end
230 session.resolver = nil;
231 session.ip_hosts = nil;
232 session.srv_hosts = nil;
233 end 247 end
234 end 248 end
235 249
236 function make_authenticated(event) 250 function make_authenticated(event)
237 local session, host = event.session, event.host; 251 local session, host = event.session, event.host;
249 end 263 end
250 if session.type == "s2sout_unauthed" then 264 if session.type == "s2sout_unauthed" then
251 session.type = "s2sout"; 265 session.type = "s2sout";
252 elseif session.type == "s2sin_unauthed" then 266 elseif session.type == "s2sin_unauthed" then
253 session.type = "s2sin"; 267 session.type = "s2sin";
254 if host then 268 elseif session.type ~= "s2sin" and session.type ~= "s2sout" then
255 if not session.hosts[host] then session.hosts[host] = {}; end 269 return false;
256 session.hosts[host].authed = true; 270 end
257 end 271
258 elseif session.type == "s2sin" and host then 272 if session.incoming and host then
259 if not session.hosts[host] then session.hosts[host] = {}; end 273 if not session.hosts[host] then session.hosts[host] = {}; end
260 session.hosts[host].authed = true; 274 session.hosts[host].authed = true;
261 else
262 return false;
263 end 275 end
264 session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host); 276 session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host);
265 277
266 if (session.type == "s2sout" and session.external_auth ~= "succeeded") or session.type == "s2sin" then 278 if (session.type == "s2sout" and session.external_auth ~= "succeeded") or session.type == "s2sin" then
267 -- Stream either used dialback for authentication or is an incoming stream. 279 -- Stream either used dialback for authentication or is an incoming stream.
299 session.thread:run({ attr = attr }); 311 session.thread:run({ attr = attr });
300 end 312 end
301 313
302 function stream_callbacks._streamopened(session, attr) 314 function stream_callbacks._streamopened(session, attr)
303 session.version = tonumber(attr.version) or 0; 315 session.version = tonumber(attr.version) or 0;
316 session.had_stream = true; -- Had a stream opened at least once
304 317
305 -- TODO: Rename session.secure to session.encrypted 318 -- TODO: Rename session.secure to session.encrypted
306 if session.secure == false then 319 if session.secure == false then
307 session.secure = true; 320 session.secure = true;
308 session.encrypted = true; 321 session.encrypted = true;
312 local info = sock:info(); 325 local info = sock:info();
313 (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher); 326 (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher);
314 session.compressed = info.compression; 327 session.compressed = info.compression;
315 else 328 else
316 (session.log or log)("info", "Stream encrypted"); 329 (session.log or log)("info", "Stream encrypted");
317 session.compressed = sock.compression and sock:compression(); --COMPAT mw/luasec-hg
318 end 330 end
319 end 331 end
320 332
321 if session.direction == "incoming" then 333 if session.direction == "incoming" then
322 -- Send a reply stream header 334 -- Send a reply stream header
323 335
324 -- Validate to/from 336 -- Validate to/from
325 local to, from = nameprep(attr.to), nameprep(attr.from); 337 local to, from = attr.to, attr.from;
338 if to then to = nameprep(attr.to); end
339 if from then from = nameprep(attr.from); end
326 if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts) 340 if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts)
327 session:close({ condition = "improper-addressing", text = "Invalid 'to' address" }); 341 session:close({ condition = "improper-addressing", text = "Invalid 'to' address" });
328 return; 342 return;
329 end 343 end
330 if not from and attr.from then -- COMPAT: Some servers do not reliably set 'from' (especially on stream restarts) 344 if not from and attr.from then -- COMPAT: Some servers do not reliably set 'from' (especially on stream restarts)
468 text = condition .. (text and (" ("..text..")") or ""); 482 text = condition .. (text and (" ("..text..")") or "");
469 session.log("info", "Session closed by remote with error: %s", text); 483 session.log("info", "Session closed by remote with error: %s", text);
470 session:close(nil, text); 484 session:close(nil, text);
471 end 485 end
472 end 486 end
473
474 local listener = {};
475 487
476 --- Session methods 488 --- Session methods
477 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; 489 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
478 local function session_close(session, reason, remote_reason) 490 local function session_close(session, reason, remote_reason)
479 local log = session.log or log; 491 local log = session.log or log;
593 function session.data(data) 605 function session.data(data)
594 data = filter("bytes/in", data); 606 data = filter("bytes/in", data);
595 if data then 607 if data then
596 local ok, err = stream:feed(data); 608 local ok, err = stream:feed(data);
597 if ok then return; end 609 if ok then return; end
598 log("warn", "Received invalid XML: %s", data); 610 log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300));
599 log("warn", "Problem was: %s", err);
600 session:close("not-well-formed"); 611 session:close("not-well-formed");
601 end 612 end
602 end 613 end
603 614
604 session.close = session_close; 615 session.close = session_close;
670 681
671 function listener.ondisconnect(conn, err) 682 function listener.ondisconnect(conn, err)
672 local session = sessions[conn]; 683 local session = sessions[conn];
673 if session then 684 if session then
674 sessions[conn] = nil; 685 sessions[conn] = nil;
686 (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed");
687 s2s_destroy_session(session, err);
688 end
689 end
690
691 function listener.onfail(data, err)
692 local session = data and data.session;
693 if session then
675 if err and session.direction == "outgoing" and session.notopen then 694 if err and session.direction == "outgoing" and session.notopen then
676 (session.log or log)("debug", "s2s connection attempt failed: %s", err); 695 (session.log or log)("debug", "s2s connection attempt failed: %s", err);
677 if s2sout.attempt_connection(session, err) then
678 return; -- Session lives for now
679 end
680 end 696 end
681 (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed"); 697 (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed");
682 s2s_destroy_session(session, err); 698 s2s_destroy_session(session, err);
683 end 699 end
684 end 700 end
696 initialize_session(session); 712 initialize_session(session);
697 end 713 end
698 714
699 function listener.ondetach(conn) 715 function listener.ondetach(conn)
700 sessions[conn] = nil; 716 sessions[conn] = nil;
717 end
718
719 function listener.onattach(conn, data)
720 local session = data and data.session;
721 if session then
722 session.conn = conn;
723 sessions[conn] = session;
724 initialize_session(session);
725 end
701 end 726 end
702 727
703 function check_auth_policy(event) 728 function check_auth_policy(event)
704 local host, session = event.host, event.session; 729 local host, session = event.host, event.session;
705 local must_secure = secure_auth; 730 local must_secure = secure_auth;
721 end 746 end
722 end 747 end
723 748
724 module:hook("s2s-check-certificate", check_auth_policy, -1); 749 module:hook("s2s-check-certificate", check_auth_policy, -1);
725 750
726 s2sout.set_listener(listener);
727
728 module:hook("server-stopping", function(event) 751 module:hook("server-stopping", function(event)
729 local reason = event.reason; 752 local reason = event.reason;
730 for _, session in pairs(sessions) do 753 for _, session in pairs(sessions) do
731 session:close{ condition = "system-shutdown", text = reason }; 754 session:close{ condition = "system-shutdown", text = reason };
732 end 755 end
737 module:provides("net", { 760 module:provides("net", {
738 name = "s2s"; 761 name = "s2s";
739 listener = listener; 762 listener = listener;
740 default_port = 5269; 763 default_port = 5269;
741 encryption = "starttls"; 764 encryption = "starttls";
765 ssl_config = { -- FIXME This is not used atm, see mod_tls
766 verify = { "peer", "client_once", };
767 };
742 multiplex = { 768 multiplex = {
743 pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>"; 769 pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>";
744 }; 770 };
745 }); 771 });
746 772