2 -- The code in this file should be self-explanatory, though the logic is horrible
3 -- for more info on that, see doc/stanza_routing.txt, which attempts to condense
4 -- the rules from the RFCs (mainly 3921)
6 require "core.servermanager"
8 local log = require "util.logger".init("stanzarouter")
10 local st = require "util.stanza";
11 local send = require "core.sessionmanager".send_to_session;
12 local user_exists = require "core.usermanager".user_exists;
14 local jid_split = require "util.jid".split;
17 function core_process_stanza(origin, stanza)
18 log("debug", "Received: "..tostring(stanza))
19 -- TODO verify validity of stanza (as well as JID validity)
20 if stanza.name == "iq" and not(#stanza.tags == 1 and stanza.tags[1].attr.xmlns) then
21 if stanza.attr.type == "set" or stanza.attr.type == "get" then
23 elseif #stanza.tags > 1 or not(stanza.attr.type == "error" or stanza.attr.type == "result") then
28 if origin.type == "c2s" and not origin.full_jid
29 and not(stanza.name == "iq" and stanza.tags[1].name == "bind"
30 and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then
31 error("Client MUST bind resource after auth");
34 local to = stanza.attr.to;
35 stanza.attr.from = origin.full_jid; -- quick fix to prevent impersonation (FIXME this would be incorrect when the origin is not c2s)
36 -- TODO also, stazas should be returned to their original state before the function ends
38 -- TODO presence subscriptions
40 if stanza.name == "presence" and origin.roster then
41 if stanza.attr.type == nil or stanza.attr.type == "available" or stanza.attr.type == "unavailable" then
42 --stanza.attr.from = origin.full_jid;
43 for jid in pairs(origin.roster) do -- broadcast to all interested contacts
44 local subscription = origin.roster[jid].subscription;
45 if subscription == "both" or subscription == "from" then
47 core_route_stanza(origin, stanza);
50 --[[local node, host = jid_split(stanza.attr.from);
51 for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
53 res = user.sessions[k];
57 if not origin.presence then -- presence probes on initial presence
58 local probe = st.presence({from = origin.full_jid, type = "probe"});
59 for jid in pairs(origin.roster) do
60 local subscription = origin.roster[jid].subscription;
61 if subscription == "both" or subscription == "to" then
63 core_route_stanza(origin, probe);
67 origin.presence = stanza;
68 stanza.attr.to = nil; -- reset it
70 -- TODO error, bad type
73 core_handle_stanza(origin, stanza);
75 elseif hosts[to] and hosts[to].type == "local" then
76 core_handle_stanza(origin, stanza);
77 elseif stanza.name == "iq" and not select(3, jid_split(to)) then
78 core_handle_stanza(origin, stanza);
79 elseif origin.type == "c2s" then
80 core_route_stanza(origin, stanza);
84 function core_handle_stanza(origin, stanza)
86 if origin.type == "c2s" or origin.type == "c2s_unauthed" then
87 local session = origin;
89 log("debug", "Routing stanza");
90 -- Stanza has no to attribute
91 --local to_node, to_host, to_resource = jid_split(stanza.attr.to);
92 --if not to_host then error("Invalid destination JID: "..string.format("{ %q, %q, %q } == %q", to_node or "", to_host or "", to_resource or "", stanza.attr.to or "nil")); end
94 -- Stanza is to this server, or a user on this server
95 log("debug", "Routing stanza to local");
96 handle_stanza(session, stanza);
100 function is_authorized_to_see_presence(origin, username, host)
101 local roster = datamanager.load(username, host, "roster") or {};
102 local item = roster[origin.username.."@"..origin.host];
103 return item and (item.subscription == "both" or item.subscription == "from");
106 function core_route_stanza(origin, stanza)
111 local to = stanza.attr.to;
112 local node, host, resource = jid_split(to);
114 if stanza.name == "presence" and stanza.attr.type == "probe" then resource = nil; end
116 local host_session = hosts[host]
117 if host_session and host_session.type == "local" then
119 local user = host_session.sessions[node];
121 local res = user.sessions[resource];
123 -- if we get here, resource was not specified or was unavailable
124 if stanza.name == "presence" then
125 if stanza.attr.type == "probe" then
126 if is_authorized_to_see_presence(origin, node, host) then
127 for k in pairs(user.sessions) do -- return presence for all resources
128 if user.sessions[k].presence then
129 local pres = user.sessions[k].presence;
130 pres.attr.to = origin.full_jid;
131 pres.attr.from = user.sessions[k].full_jid;
134 pres.attr.from = nil;
138 send(origin, st.presence({from = user.."@"..host, to = origin.username.."@"..origin.host, type = "unsubscribed"}));
141 for k in pairs(user.sessions) do -- presence broadcast to all user resources
142 if user.sessions[k].full_jid then
143 stanza.attr.to = user.sessions[k].full_jid;
144 send(user.sessions[k], stanza);
148 elseif stanza.name == "message" then -- select a resource to recieve message
149 for k in pairs(user.sessions) do
150 if user.sessions[k].full_jid then
151 res = user.sessions[k];
155 -- TODO find resource with greatest priority
158 -- TODO send IQ error
161 stanza.attr.to = res.full_jid;
162 send(res, stanza); -- Yay \o/
166 if user_exists(node, host) then
167 if stanza.name == "presence" then
168 if stanza.attr.type == "probe" and is_authorized_to_see_presence(origin, node, host) then -- FIXME what to do for not c2s?
169 -- TODO send last recieved unavailable presence
171 -- TODO send unavailable presence
173 elseif stanza.name == "message" then
174 -- TODO send message error, or store offline messages
175 elseif stanza.name == "iq" then
176 -- TODO send IQ error
178 else -- user does not exist
179 -- TODO we would get here for nodeless JIDs too. Do something fun maybe? Echo service? Let plugins use xmpp:server/resource addresses?
180 if stanza.name == "presence" then
181 if stanza.attr.type == "probe" then
182 send(origin, st.presence({from = user.."@"..host, to = origin.username.."@"..origin.host, type = "unsubscribed"}));
186 send(origin, st.error_reply(stanza, "cancel", "service-unavailable"));
195 -- Need to establish the connection
198 stanza.attr.to = to; -- reset
201 function handle_stanza_nodest(stanza)
202 if stanza.name == "iq" then
203 handle_stanza_iq_no_to(session, stanza);
204 elseif stanza.name == "presence" then
205 -- Broadcast to this user's contacts
206 handle_stanza_presence_broadcast(session, stanza);
207 -- also, if it is initial presence, send out presence probes
208 if not session.last_presence then
209 handle_stanza_presence_probe_broadcast(session, stanza);
211 session.last_presence = stanza;
212 elseif stanza.name == "message" then
213 -- Treat as if message was sent to bare JID of the sender
214 handle_stanza_to_local_user(stanza);
218 function handle_stanza_tolocal(stanza)
219 local node, host, resource = jid.split(stanza.attr.to);
220 if host and hosts[host] and hosts[host].type == "local" then
221 -- Is a local host, handle internally
223 -- Is a local user, send to their session
224 log("debug", "Routing stanza to %s@%s", node, host);
225 if not session.username then return; end --FIXME: Correct response when trying to use unauthed stream is what?
226 handle_stanza_to_local_user(stanza);
228 -- Is sent to this server, let's handle it...
229 log("debug", "Routing stanza to %s", host);
230 handle_stanza_to_server(stanza, session);
235 function handle_stanza_toremote(stanza)
236 log("error", "Stanza bound for remote host, but s2s is not implemented");
241 local function route_c2s_stanza(session, stanza)
242 stanza.attr.from = session.full_jid;
243 if not stanza.attr.to and session.username then
244 -- Has no 'to' attribute, handle internally
245 if stanza.name == "iq" then
246 handle_stanza_iq_no_to(session, stanza);
247 elseif stanza.name == "presence" then
248 -- Broadcast to this user's contacts
249 handle_stanza_presence_broadcast(session, stanza);
250 -- also, if it is initial presence, send out presence probes
251 if not session.last_presence then
252 handle_stanza_presence_probe_broadcast(session, stanza);
254 session.last_presence = stanza;
255 elseif stanza.name == "message" then
256 -- Treat as if message was sent to bare JID of the sender
257 handle_stanza_to_local_user(stanza);
260 local node, host, resource = jid.split(stanza.attr.to);
261 if host and hosts[host] and hosts[host].type == "local" then
262 -- Is a local host, handle internally
264 -- Is a local user, send to their session
265 if not session.username then return; end --FIXME: Correct response when trying to use unauthed stream is what?
266 handle_stanza_to_local_user(stanza);
268 -- Is sent to this server, let's handle it...
269 handle_stanza_to_server(stanza, session);
272 -- Is not for us or a local user, route accordingly
273 route_s2s_stanza(stanza);
277 function handle_stanza_no_to(session, stanza)
278 if not stanza.attr.id then log("warn", "<iq> without id attribute is invalid"); end
279 local xmlns = (stanza.tags[1].attr and stanza.tags[1].attr.xmlns);
280 if stanza.attr.type == "get" or stanza.attr.type == "set" then
281 if iq_handlers[xmlns] then
282 if iq_handlers[xmlns](stanza) then return; end; -- If handler returns true, it handled it
284 -- Oh, handler didn't handle it. Need to send service-unavailable now.
285 log("warn", "Unhandled namespace: "..xmlns);
286 session:send(format("<iq type='error' id='%s'><error type='cancel'><service-unavailable/></error></iq>", stanza.attr.id));
291 function handle_stanza_to_local_user(stanza)
292 if stanza.name == "message" then
293 handle_stanza_message_to_local_user(stanza);
294 elseif stanza.name == "presence" then
295 handle_stanza_presence_to_local_user(stanza);
296 elseif stanza.name == "iq" then
297 handle_stanza_iq_to_local_user(stanza);
301 function handle_stanza_message_to_local_user(stanza)
302 local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
303 local destuser = hosts[host].sessions[node];
305 if resource and destuser[resource] then
306 destuser[resource]:send(stanza);
308 -- Bare JID, or resource offline
310 for resource, session in pairs(destuser.sessions) do
311 if not best_session then best_session = session;
312 elseif session.priority >= best_session.priority and session.priority >= 0 then
313 best_session = session;
316 if not best_session then
317 offlinemessage.new(node, host, stanza);
319 print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'");
320 destuser[best_session]:send(stanza);
325 offlinemessage.new(node, host, stanza);
329 function handle_stanza_presence_to_local_user(stanza)
330 local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
331 local destuser = hosts[host].sessions[node];
334 if destuser[resource] then
335 destuser[resource]:send(stanza);
340 -- Broadcast to all user's resources
341 for resource, session in pairs(destuser.sessions) do
342 session:send(stanza);
348 function handle_stanza_iq_to_local_user(stanza)
353 local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
354 local destuser = hosts[host].sessions[node];
355 if destuser and destuser.sessions then
357 if resource and destuser.sessions[resource] then
358 stanza.to:send(stanza);
360 --User is online, but specified resource isn't (or no resource specified)
362 for resource, session in pairs(destuser.sessions) do
363 if not best_session then best_session = session;
364 elseif session.priority >= best_session.priority and session.priority >= 0 then
365 best_session = session;
368 if not best_session then
369 offlinemessage.new(node, host, stanza);
371 print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'");
372 resource = best_session.resource;
375 if destuser.sessions[resource] == session then
376 log("warn", "core", "Attempt to send stanza to self, dropping...");
378 print("...sending...", tostring(stanza));
379 --destuser.sessions[resource].conn.write(tostring(data));
380 print(" to conn ", destuser.sessions[resource].conn);
381 destuser.sessions[resource].conn.write(tostring(stanza));
384 elseif stanza.name == "message" then
385 print(" ...will be stored offline");
386 offlinemessage.new(node, host, stanza);
387 elseif stanza.name == "iq" then
388 print(" ...is an iq");
389 stanza.from:send(st.reply(stanza)
390 :tag("error", { type = "cancel" })
391 :tag("service-unavailable", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" }));
395 -- Broadcast a presence stanza to all of a user's contacts
396 function handle_stanza_presence_broadcast(session, stanza)
397 if session.roster then
398 local initial_presence = not session.last_presence;
399 session.last_presence = stanza;
401 -- Broadcast presence and probes
402 local broadcast = st.presence({ from = session.full_jid, type = stanza.attr.type });
404 for child in stanza:childtags() do
405 broadcast:add_child(child);
407 for contact_jid in pairs(session.roster) do
408 broadcast.attr.to = contact_jid;
409 send_to(contact_jid, broadcast);
410 if initial_presence then
411 local node, host = jid.split(contact_jid);
412 if hosts[host] and hosts[host].type == "local" then
413 local contact = hosts[host].sessions[node]
415 local pres = st.presence { to = session.full_jid };
416 for resource, contact_session in pairs(contact.sessions) do
417 if contact_session.last_presence then
418 pres.tags = contact_session.last_presence.tags;
419 pres.attr.from = contact_session.full_jid;
424 --FIXME: Do we send unavailable if they are offline?
426 probe.attr.to = contact;
427 send_to(contact, probe);
432 -- Probe for our contacts' presence
436 -- Broadcast presence probes to all of a user's contacts
437 function handle_stanza_presence_probe_broadcast(session, stanza)
441 function handle_stanza_to_server(stanza)
444 function handle_stanza_iq_no_to(session, stanza)