| 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 | |
| 11 | local logger = require "logger"; |
| 12 | local log = logger.init("xmppclient_listener"); |
| 13 | local new_xmpp_stream = require "util.xmppstream".new; |
| 14 | |
| 15 | local connlisteners_register = require "net.connlisteners".register; |
| 16 | |
| 17 | local sessionmanager = require "core.sessionmanager"; |
| 18 | local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; |
| 19 | local sm_streamopened = sessionmanager.streamopened; |
| 20 | local sm_streamclosed = sessionmanager.streamclosed; |
| 21 | local st = require "util.stanza"; |
| 22 | local xpcall = xpcall; |
| 23 | local tostring = tostring; |
| 24 | local type = type; |
| 25 | local traceback = debug.traceback; |
| 26 | |
| 27 | local config = require "core.configmanager"; |
| 28 | local opt_keepalives = config.get("*", "core", "tcp_keepalives"); |
| 29 | |
| 30 | local stream_callbacks = { default_ns = "jabber:client", |
| 31 | streamopened = sm_streamopened, streamclosed = sm_streamclosed, handlestanza = core_process_stanza }; |
| 32 | |
| 33 | local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; |
| 34 | |
| 35 | function stream_callbacks.error(session, error, data) |
| 36 | if error == "no-stream" then |
| 37 | session.log("debug", "Invalid opening stream header"); |
| 38 | session:close("invalid-namespace"); |
| 39 | elseif error == "parse-error" then |
| 40 | (session.log or log)("debug", "Client XML parse error: %s", tostring(data)); |
| 41 | session:close("not-well-formed"); |
| 42 | elseif error == "stream-error" then |
| 43 | local condition, text = "undefined-condition"; |
| 44 | for child in data:children() do |
| 45 | if child.attr.xmlns == xmlns_xmpp_streams then |
| 46 | if child.name ~= "text" then |
| 47 | condition = child.name; |
| 48 | else |
| 49 | text = child:get_text(); |
| 50 | end |
| 51 | if condition ~= "undefined-condition" and text then |
| 52 | break; |
| 53 | end |
| 54 | end |
| 55 | end |
| 56 | text = condition .. (text and (" ("..text..")") or ""); |
| 57 | session.log("info", "Session closed by remote with error: %s", text); |
| 58 | session:close(nil, text); |
| 59 | end |
| 60 | end |
| 61 | |
| 62 | local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end |
| 63 | function stream_callbacks.handlestanza(session, stanza) |
| 64 | stanza = session.filter("stanzas/in", stanza); |
| 65 | if stanza then |
| 66 | return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); |
| 67 | end |
| 68 | end |
| 69 | |
| 70 | local sessions = {}; |
| 71 | local xmppclient = { default_port = 5222, default_mode = "*a" }; |
| 72 | |
| 73 | -- These are session methods -- |
| 74 | |
| 75 | local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; |
| 76 | local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; |
| 77 | local function session_close(session, reason) |
| 78 | local log = session.log or log; |
| 79 | if session.conn then |
| 80 | if session.notopen then |
| 81 | session.send("<?xml version='1.0'?>"); |
| 82 | session.send(st.stanza("stream:stream", default_stream_attr):top_tag()); |
| 83 | end |
| 84 | if reason then |
| 85 | if type(reason) == "string" then -- assume stream error |
| 86 | log("info", "Disconnecting client, <stream:error> is: %s", reason); |
| 87 | session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); |
| 88 | elseif type(reason) == "table" then |
| 89 | if reason.condition then |
| 90 | local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); |
| 91 | if reason.text then |
| 92 | stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); |
| 93 | end |
| 94 | if reason.extra then |
| 95 | stanza:add_child(reason.extra); |
| 96 | end |
| 97 | log("info", "Disconnecting client, <stream:error> is: %s", tostring(stanza)); |
| 98 | session.send(stanza); |
| 99 | elseif reason.name then -- a stanza |
| 100 | log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason)); |
| 101 | session.send(reason); |
| 102 | end |
| 103 | end |
| 104 | end |
| 105 | session.send("</stream:stream>"); |
| 106 | session.conn:close(); |
| 107 | xmppclient.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed"); |
| 108 | end |
| 109 | end |
| 110 | |
| 111 | |
| 112 | -- End of session methods -- |
| 113 | |
| 114 | function xmppclient.onconnect(conn) |
| 115 | local session = sm_new_session(conn); |
| 116 | sessions[conn] = session; |
| 117 | |
| 118 | session.log("info", "Client connected"); |
| 119 | |
| 120 | -- Client is using legacy SSL (otherwise mod_tls sets this flag) |
| 121 | if conn:ssl() then |
| 122 | session.secure = true; |
| 123 | end |
| 124 | |
| 125 | if opt_keepalives ~= nil then |
| 126 | conn:setoption("keepalive", opt_keepalives); |
| 127 | end |
| 128 | |
| 129 | session.close = session_close; |
| 130 | |
| 131 | local stream = new_xmpp_stream(session, stream_callbacks); |
| 132 | session.stream = stream; |
| 133 | |
| 134 | session.notopen = true; |
| 135 | |
| 136 | function session.reset_stream() |
| 137 | session.notopen = true; |
| 138 | session.stream:reset(); |
| 139 | end |
| 140 | |
| 141 | local filter = session.filter; |
| 142 | function session.data(data) |
| 143 | data = filter("bytes/in", data); |
| 144 | if data then |
| 145 | local ok, err = stream:feed(data); |
| 146 | if ok then return; end |
| 147 | log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); |
| 148 | session:close("not-well-formed"); |
| 149 | end |
| 150 | end |
| 151 | |
| 152 | local handlestanza = stream_callbacks.handlestanza; |
| 153 | function session.dispatch_stanza(session, stanza) |
| 154 | return handlestanza(session, stanza); |
| 155 | end |
| 156 | end |
| 157 | |
| 158 | function xmppclient.onincoming(conn, data) |
| 159 | local session = sessions[conn]; |
| 160 | if session then |
| 161 | session.data(data); |
| 162 | end |
| 163 | end |
| 164 | |
| 165 | function xmppclient.ondisconnect(conn, err) |
| 166 | local session = sessions[conn]; |
| 167 | if session then |
| 168 | (session.log or log)("info", "Client disconnected: %s", err); |
| 169 | sm_destroy_session(session, err); |
| 170 | sessions[conn] = nil; |
| 171 | session = nil; |
| 172 | end |
| 173 | end |
| 174 | |
| 175 | function xmppclient.associate_session(conn, session) |
| 176 | sessions[conn] = session; |
| 177 | end |
| 178 | |
| 179 | connlisteners_register("xmppclient", xmppclient); |