| 1 | -- Prosody IM |
| 2 | -- Copyright (C) 2008-2010 Matthew Wild |
| 3 | -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | -- |
| 8 | |
| 9 | |
| 10 | local tostring = tostring; |
| 11 | local type = type; |
| 12 | local xpcall = xpcall; |
| 13 | local s_format = string.format; |
| 14 | local traceback = debug.traceback; |
| 15 | |
| 16 | local logger = require "logger"; |
| 17 | local log = logger.init("xmppserver_listener"); |
| 18 | local st = require "util.stanza"; |
| 19 | local connlisteners_register = require "net.connlisteners".register; |
| 20 | local new_xmpp_stream = require "util.xmppstream".new; |
| 21 | local s2s_new_incoming = require "core.s2smanager".new_incoming; |
| 22 | local s2s_streamopened = require "core.s2smanager".streamopened; |
| 23 | local s2s_streamclosed = require "core.s2smanager".streamclosed; |
| 24 | local s2s_destroy_session = require "core.s2smanager".destroy_session; |
| 25 | local s2s_attempt_connect = require "core.s2smanager".attempt_connection; |
| 26 | local stream_callbacks = { default_ns = "jabber:server", |
| 27 | streamopened = s2s_streamopened, streamclosed = s2s_streamclosed, handlestanza = core_process_stanza }; |
| 28 | |
| 29 | local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; |
| 30 | |
| 31 | function stream_callbacks.error(session, error, data) |
| 32 | if error == "no-stream" then |
| 33 | session:close("invalid-namespace"); |
| 34 | elseif error == "parse-error" then |
| 35 | session.log("debug", "Server-to-server XML parse error: %s", tostring(error)); |
| 36 | session:close("not-well-formed"); |
| 37 | elseif error == "stream-error" then |
| 38 | local condition, text = "undefined-condition"; |
| 39 | for child in data:children() do |
| 40 | if child.attr.xmlns == xmlns_xmpp_streams then |
| 41 | if child.name ~= "text" then |
| 42 | condition = child.name; |
| 43 | else |
| 44 | text = child:get_text(); |
| 45 | end |
| 46 | if condition ~= "undefined-condition" and text then |
| 47 | break; |
| 48 | end |
| 49 | end |
| 50 | end |
| 51 | text = condition .. (text and (" ("..text..")") or ""); |
| 52 | session.log("info", "Session closed by remote with error: %s", text); |
| 53 | session:close(nil, text); |
| 54 | end |
| 55 | end |
| 56 | |
| 57 | local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end |
| 58 | function stream_callbacks.handlestanza(session, stanza) |
| 59 | if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client |
| 60 | stanza.attr.xmlns = nil; |
| 61 | end |
| 62 | stanza = session.filter("stanzas/in", stanza); |
| 63 | if stanza then |
| 64 | return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); |
| 65 | end |
| 66 | end |
| 67 | |
| 68 | local sessions = {}; |
| 69 | local xmppserver = { default_port = 5269, default_mode = "*a" }; |
| 70 | |
| 71 | -- These are session methods -- |
| 72 | |
| 73 | local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; |
| 74 | local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; |
| 75 | local function session_close(session, reason, remote_reason) |
| 76 | local log = session.log or log; |
| 77 | if session.conn then |
| 78 | if session.notopen then |
| 79 | session.sends2s("<?xml version='1.0'?>"); |
| 80 | session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag()); |
| 81 | end |
| 82 | if reason then |
| 83 | if type(reason) == "string" then -- assume stream error |
| 84 | log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, reason); |
| 85 | session.sends2s(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); |
| 86 | elseif type(reason) == "table" then |
| 87 | if reason.condition then |
| 88 | local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); |
| 89 | if reason.text then |
| 90 | stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); |
| 91 | end |
| 92 | if reason.extra then |
| 93 | stanza:add_child(reason.extra); |
| 94 | end |
| 95 | log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, tostring(stanza)); |
| 96 | session.sends2s(stanza); |
| 97 | elseif reason.name then -- a stanza |
| 98 | log("info", "Disconnecting %s->%s[%s], <stream:error> is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, tostring(reason)); |
| 99 | session.sends2s(reason); |
| 100 | end |
| 101 | end |
| 102 | end |
| 103 | session.sends2s("</stream:stream>"); |
| 104 | if session.notopen or not session.conn:close() then |
| 105 | session.conn:close(true); -- Force FIXME: timer? |
| 106 | end |
| 107 | session.conn:close(); |
| 108 | xmppserver.ondisconnect(session.conn, remote_reason or (reason and (reason.text or reason.condition)) or reason or "stream closed"); |
| 109 | end |
| 110 | end |
| 111 | |
| 112 | |
| 113 | -- End of session methods -- |
| 114 | |
| 115 | local function initialize_session(session) |
| 116 | local stream = new_xmpp_stream(session, stream_callbacks); |
| 117 | session.stream = stream; |
| 118 | |
| 119 | session.notopen = true; |
| 120 | |
| 121 | function session.reset_stream() |
| 122 | session.notopen = true; |
| 123 | session.stream:reset(); |
| 124 | end |
| 125 | |
| 126 | local filter = session.filter; |
| 127 | function session.data(data) |
| 128 | data = filter("bytes/in", data); |
| 129 | if data then |
| 130 | local ok, err = stream:feed(data); |
| 131 | if ok then return; end |
| 132 | (session.log or log)("warn", "Received invalid XML: %s", data); |
| 133 | (session.log or log)("warn", "Problem was: %s", err); |
| 134 | session:close("not-well-formed"); |
| 135 | end |
| 136 | end |
| 137 | |
| 138 | session.close = session_close; |
| 139 | local handlestanza = stream_callbacks.handlestanza; |
| 140 | function session.dispatch_stanza(session, stanza) |
| 141 | return handlestanza(session, stanza); |
| 142 | end |
| 143 | end |
| 144 | |
| 145 | function xmppserver.onconnect(conn) |
| 146 | if not sessions[conn] then -- May be an existing outgoing session |
| 147 | local session = s2s_new_incoming(conn); |
| 148 | sessions[conn] = session; |
| 149 | |
| 150 | -- Logging functions -- |
| 151 | local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$"); |
| 152 | session.log = logger.init(conn_name); |
| 153 | |
| 154 | session.log("info", "Incoming s2s connection"); |
| 155 | |
| 156 | initialize_session(session); |
| 157 | end |
| 158 | end |
| 159 | |
| 160 | function xmppserver.onincoming(conn, data) |
| 161 | local session = sessions[conn]; |
| 162 | if session then |
| 163 | session.data(data); |
| 164 | end |
| 165 | end |
| 166 | |
| 167 | function xmppserver.onstatus(conn, status) |
| 168 | if status == "ssl-handshake-complete" then |
| 169 | local session = sessions[conn]; |
| 170 | if session and session.direction == "outgoing" then |
| 171 | local to_host, from_host = session.to_host, session.from_host; |
| 172 | session.log("debug", "Sending stream header..."); |
| 173 | session.sends2s(s_format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0'>]], from_host, to_host)); |
| 174 | end |
| 175 | end |
| 176 | end |
| 177 | |
| 178 | function xmppserver.ondisconnect(conn, err) |
| 179 | local session = sessions[conn]; |
| 180 | if session then |
| 181 | if err and err ~= "closed" and session.srv_hosts then |
| 182 | (session.log or log)("debug", "s2s connection attempt failed: %s", err); |
| 183 | if s2s_attempt_connect(session, err) then |
| 184 | (session.log or log)("debug", "...so we're going to try another target"); |
| 185 | return; -- Session lives for now |
| 186 | end |
| 187 | end |
| 188 | (session.log or log)("info", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "closed")); |
| 189 | s2s_destroy_session(session, err); |
| 190 | sessions[conn] = nil; |
| 191 | session = nil; |
| 192 | end |
| 193 | end |
| 194 | |
| 195 | function xmppserver.register_outgoing(conn, session) |
| 196 | session.direction = "outgoing"; |
| 197 | sessions[conn] = session; |
| 198 | |
| 199 | initialize_session(session); |
| 200 | end |
| 201 | |
| 202 | connlisteners_register("xmppserver", xmppserver); |
| 203 | |
| 204 | |
| 205 | -- We need to perform some initialisation when a connection is created |
| 206 | -- We also need to perform that same initialisation at other points (SASL, TLS, ...) |
| 207 | |
| 208 | -- ...and we need to handle data |
| 209 | -- ...and record all sessions associated with connections |