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 |