Software / code / prosody
Comparison
plugins/mod_c2s.lua @ 4543:db27a4c18b6a
mod_c2s, sessionmanager, xmppclient_listener: Move all c2s network and stream logic into a new module, mod_c2s
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Sun, 22 Jan 2012 23:57:13 +0000 |
| child | 4548:e6e5c76ff009 |
comparison
equal
deleted
inserted
replaced
| 4542:50aca1e0bfbd | 4543:db27a4c18b6a |
|---|---|
| 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 module:set_global(); | |
| 10 | |
| 11 local new_xmpp_stream = require "util.xmppstream".new; | |
| 12 local nameprep = require "util.encodings".stringprep.nameprep; | |
| 13 local portmanager = require "core.portmanager"; | |
| 14 local sessionmanager = require "core.sessionmanager"; | |
| 15 local st = require "util.stanza"; | |
| 16 local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; | |
| 17 local uuid_generate = require "util.uuid".generate; | |
| 18 | |
| 19 local xpcall, tostring, type = xpcall, tostring, type; | |
| 20 local format = string.format; | |
| 21 local traceback = debug.traceback; | |
| 22 | |
| 23 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; | |
| 24 | |
| 25 local log = module._log; | |
| 26 | |
| 27 local opt_keepalives = module:get_option_boolean("tcp_keepalives", false); | |
| 28 | |
| 29 local sessions = module:shared("sessions"); | |
| 30 | |
| 31 local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza }; | |
| 32 local listener = { default_port = 5222, default_mode = "*a" }; | |
| 33 | |
| 34 --- Stream events handlers | |
| 35 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; | |
| 36 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; | |
| 37 | |
| 38 function stream_callbacks.streamopened(session, attr) | |
| 39 local send = session.send; | |
| 40 session.host = attr.to; | |
| 41 if not session.host then | |
| 42 session:close{ condition = "improper-addressing", | |
| 43 text = "A 'to' attribute is required on stream headers" }; | |
| 44 return; | |
| 45 end | |
| 46 session.host = nameprep(session.host); | |
| 47 session.version = tonumber(attr.version) or 0; | |
| 48 session.streamid = uuid_generate(); | |
| 49 (session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host); | |
| 50 | |
| 51 if not hosts[session.host] then | |
| 52 -- We don't serve this host... | |
| 53 session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)}; | |
| 54 return; | |
| 55 end | |
| 56 | |
| 57 send("<?xml version='1.0'?>"); | |
| 58 send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host)); | |
| 59 | |
| 60 (session.log or log)("debug", "Sent reply <stream:stream> to client"); | |
| 61 session.notopen = nil; | |
| 62 | |
| 63 -- If session.secure is *false* (not nil) then it means we /were/ encrypting | |
| 64 -- since we now have a new stream header, session is secured | |
| 65 if session.secure == false then | |
| 66 session.secure = true; | |
| 67 end | |
| 68 | |
| 69 local features = st.stanza("stream:features"); | |
| 70 hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); | |
| 71 module:fire_event("stream-features", session, features); | |
| 72 | |
| 73 send(features); | |
| 74 end | |
| 75 | |
| 76 function stream_callbacks.streamclosed(session) | |
| 77 session.log("debug", "Received </stream:stream>"); | |
| 78 session:close(); | |
| 79 end | |
| 80 | |
| 81 function stream_callbacks.error(session, error, data) | |
| 82 if error == "no-stream" then | |
| 83 session.log("debug", "Invalid opening stream header"); | |
| 84 session:close("invalid-namespace"); | |
| 85 elseif error == "parse-error" then | |
| 86 (session.log or log)("debug", "Client XML parse error: %s", tostring(data)); | |
| 87 session:close("not-well-formed"); | |
| 88 elseif error == "stream-error" then | |
| 89 local condition, text = "undefined-condition"; | |
| 90 for child in data:children() do | |
| 91 if child.attr.xmlns == xmlns_xmpp_streams then | |
| 92 if child.name ~= "text" then | |
| 93 condition = child.name; | |
| 94 else | |
| 95 text = child:get_text(); | |
| 96 end | |
| 97 if condition ~= "undefined-condition" and text then | |
| 98 break; | |
| 99 end | |
| 100 end | |
| 101 end | |
| 102 text = condition .. (text and (" ("..text..")") or ""); | |
| 103 session.log("info", "Session closed by remote with error: %s", text); | |
| 104 session:close(nil, text); | |
| 105 end | |
| 106 end | |
| 107 | |
| 108 local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end | |
| 109 function stream_callbacks.handlestanza(session, stanza) | |
| 110 stanza = session.filter("stanzas/in", stanza); | |
| 111 if stanza then | |
| 112 return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); | |
| 113 end | |
| 114 end | |
| 115 | |
| 116 --- Session methods | |
| 117 local function session_close(session, reason) | |
| 118 local log = session.log or log; | |
| 119 if session.conn then | |
| 120 if session.notopen then | |
| 121 session.send("<?xml version='1.0'?>"); | |
| 122 session.send(st.stanza("stream:stream", default_stream_attr):top_tag()); | |
| 123 end | |
| 124 if reason then | |
| 125 if type(reason) == "string" then -- assume stream error | |
| 126 log("info", "Disconnecting client, <stream:error> is: %s", reason); | |
| 127 session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); | |
| 128 elseif type(reason) == "table" then | |
| 129 if reason.condition then | |
| 130 local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); | |
| 131 if reason.text then | |
| 132 stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); | |
| 133 end | |
| 134 if reason.extra then | |
| 135 stanza:add_child(reason.extra); | |
| 136 end | |
| 137 log("info", "Disconnecting client, <stream:error> is: %s", tostring(stanza)); | |
| 138 session.send(stanza); | |
| 139 elseif reason.name then -- a stanza | |
| 140 log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason)); | |
| 141 session.send(reason); | |
| 142 end | |
| 143 end | |
| 144 end | |
| 145 session.send("</stream:stream>"); | |
| 146 session.conn:close(); | |
| 147 listener.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed"); | |
| 148 end | |
| 149 end | |
| 150 | |
| 151 --- Port listener | |
| 152 function listener.onconnect(conn) | |
| 153 local session = sm_new_session(conn); | |
| 154 sessions[conn] = session; | |
| 155 | |
| 156 session.log("info", "Client connected"); | |
| 157 | |
| 158 -- Client is using legacy SSL (otherwise mod_tls sets this flag) | |
| 159 if conn:ssl() then | |
| 160 session.secure = true; | |
| 161 end | |
| 162 | |
| 163 if opt_keepalives then | |
| 164 conn:setoption("keepalive", opt_keepalives); | |
| 165 end | |
| 166 | |
| 167 session.close = session_close; | |
| 168 | |
| 169 local stream = new_xmpp_stream(session, stream_callbacks); | |
| 170 session.stream = stream; | |
| 171 session.notopen = true; | |
| 172 | |
| 173 function session.reset_stream() | |
| 174 session.notopen = true; | |
| 175 session.stream:reset(); | |
| 176 end | |
| 177 | |
| 178 local filter = session.filter; | |
| 179 function session.data(data) | |
| 180 data = filter("bytes/in", data); | |
| 181 if data then | |
| 182 local ok, err = stream:feed(data); | |
| 183 if ok then return; end | |
| 184 log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); | |
| 185 session:close("not-well-formed"); | |
| 186 end | |
| 187 end | |
| 188 | |
| 189 session.dispatch_stanza = stream_callbacks.handlestanza; | |
| 190 end | |
| 191 | |
| 192 function listener.onincoming(conn, data) | |
| 193 local session = sessions[conn]; | |
| 194 if session then | |
| 195 session.data(data); | |
| 196 end | |
| 197 end | |
| 198 | |
| 199 function listener.ondisconnect(conn, err) | |
| 200 local session = sessions[conn]; | |
| 201 if session then | |
| 202 (session.log or log)("info", "Client disconnected: %s", err); | |
| 203 sm_destroy_session(session, err); | |
| 204 sessions[conn] = nil; | |
| 205 session = nil; | |
| 206 end | |
| 207 end | |
| 208 | |
| 209 function listener.associate_session(conn, session) | |
| 210 sessions[conn] = session; | |
| 211 end | |
| 212 | |
| 213 portmanager.register_service("c2s", { | |
| 214 listener = listener; | |
| 215 default_port = 5222; | |
| 216 encryption = "starttls"; | |
| 217 }); | |
| 218 | |
| 219 portmanager.register_service("legacy_ssl", { | |
| 220 listener = listener; | |
| 221 encryption = "ssl"; | |
| 222 }); | |
| 223 | |
| 224 |