Mercurial > prosody-hg
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 |
