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")
11 local jid_split = jid.split;
13 function core_process_stanza(origin, stanza)
14 log("debug", "Received: "..tostring(stanza))
15 local to = stanza.attr.to;
17 if not to or (hosts[to] and hosts[to].type == "local") then
18 core_handle_stanza(origin, stanza);
19 elseif origin.type == "c2s" then
20 core_route_stanza(origin, stanza);
25 function core_handle_stanza(origin, stanza)
27 if origin.type == "c2s" or origin.type == "c2s_unauthed" then
28 local session = origin;
29 stanza.attr.from = session.full_jid;
31 log("debug", "Routing stanza");
32 -- Stanza has no to attribute
33 --local to_node, to_host, to_resource = jid_split(stanza.attr.to);
34 --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
36 -- Stanza is to this server, or a user on this server
37 log("debug", "Routing stanza to local");
38 handle_stanza(session, stanza);
42 function core_route_stanza(origin, stanza)
47 local node, host, resource = jid_split(stanza.attr.to);
48 local host_session = hosts[host]
49 if host_session and host_session.type == "local" then
56 -- Need to establish the connection
61 function handle_stanza_nodest(stanza)
62 if stanza.name == "iq" then
63 handle_stanza_iq_no_to(session, stanza);
64 elseif stanza.name == "presence" then
65 -- Broadcast to this user's contacts
66 handle_stanza_presence_broadcast(session, stanza);
67 -- also, if it is initial presence, send out presence probes
68 if not session.last_presence then
69 handle_stanza_presence_probe_broadcast(session, stanza);
71 session.last_presence = stanza;
72 elseif stanza.name == "message" then
73 -- Treat as if message was sent to bare JID of the sender
74 handle_stanza_to_local_user(stanza);
78 function handle_stanza_tolocal(stanza)
79 local node, host, resource = jid.split(stanza.attr.to);
80 if host and hosts[host] and hosts[host].type == "local" then
81 -- Is a local host, handle internally
83 -- Is a local user, send to their session
84 log("debug", "Routing stanza to %s@%s", node, host);
85 if not session.username then return; end --FIXME: Correct response when trying to use unauthed stream is what?
86 handle_stanza_to_local_user(stanza);
88 -- Is sent to this server, let's handle it...
89 log("debug", "Routing stanza to %s", host);
90 handle_stanza_to_server(stanza, session);
95 function handle_stanza_toremote(stanza)
96 log("error", "Stanza bound for remote host, but s2s is not implemented");
101 local function route_c2s_stanza(session, stanza)
102 stanza.attr.from = session.full_jid;
103 if not stanza.attr.to and session.username then
104 -- Has no 'to' attribute, handle internally
105 if stanza.name == "iq" then
106 handle_stanza_iq_no_to(session, stanza);
107 elseif stanza.name == "presence" then
108 -- Broadcast to this user's contacts
109 handle_stanza_presence_broadcast(session, stanza);
110 -- also, if it is initial presence, send out presence probes
111 if not session.last_presence then
112 handle_stanza_presence_probe_broadcast(session, stanza);
114 session.last_presence = stanza;
115 elseif stanza.name == "message" then
116 -- Treat as if message was sent to bare JID of the sender
117 handle_stanza_to_local_user(stanza);
120 local node, host, resource = jid.split(stanza.attr.to);
121 if host and hosts[host] and hosts[host].type == "local" then
122 -- Is a local host, handle internally
124 -- Is a local user, send to their session
125 if not session.username then return; end --FIXME: Correct response when trying to use unauthed stream is what?
126 handle_stanza_to_local_user(stanza);
128 -- Is sent to this server, let's handle it...
129 handle_stanza_to_server(stanza, session);
132 -- Is not for us or a local user, route accordingly
133 route_s2s_stanza(stanza);
137 function handle_stanza_no_to(session, stanza)
138 if not stanza.attr.id then log("warn", "<iq> without id attribute is invalid"); end
139 local xmlns = (stanza.tags[1].attr and stanza.tags[1].attr.xmlns);
140 if stanza.attr.type == "get" or stanza.attr.type == "set" then
141 if iq_handlers[xmlns] then
142 if iq_handlers[xmlns](stanza) then return; end; -- If handler returns true, it handled it
144 -- Oh, handler didn't handle it. Need to send service-unavailable now.
145 log("warn", "Unhandled namespace: "..xmlns);
146 session:send(format("<iq type='error' id='%s'><error type='cancel'><service-unavailable/></error></iq>", stanza.attr.id));
151 function handle_stanza_to_local_user(stanza)
152 if stanza.name == "message" then
153 handle_stanza_message_to_local_user(stanza);
154 elseif stanza.name == "presence" then
155 handle_stanza_presence_to_local_user(stanza);
156 elseif stanza.name == "iq" then
157 handle_stanza_iq_to_local_user(stanza);
161 function handle_stanza_message_to_local_user(stanza)
162 local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
163 local destuser = hosts[host].sessions[node];
165 if resource and destuser[resource] then
166 destuser[resource]:send(stanza);
168 -- Bare JID, or resource offline
170 for resource, session in pairs(destuser.sessions) do
171 if not best_session then best_session = session;
172 elseif session.priority >= best_session.priority and session.priority >= 0 then
173 best_session = session;
176 if not best_session then
177 offlinemessage.new(node, host, stanza);
179 print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'");
180 destuser[best_session]:send(stanza);
185 offlinemessage.new(node, host, stanza);
189 function handle_stanza_presence_to_local_user(stanza)
190 local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
191 local destuser = hosts[host].sessions[node];
194 if destuser[resource] then
195 destuser[resource]:send(stanza);
200 -- Broadcast to all user's resources
201 for resource, session in pairs(destuser.sessions) do
202 session:send(stanza);
208 function handle_stanza_iq_to_local_user(stanza)
213 local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
214 local destuser = hosts[host].sessions[node];
215 if destuser and destuser.sessions then
217 if resource and destuser.sessions[resource] then
218 stanza.to:send(stanza);
220 --User is online, but specified resource isn't (or no resource specified)
222 for resource, session in pairs(destuser.sessions) do
223 if not best_session then best_session = session;
224 elseif session.priority >= best_session.priority and session.priority >= 0 then
225 best_session = session;
228 if not best_session then
229 offlinemessage.new(node, host, stanza);
231 print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'");
232 resource = best_session.resource;
235 if destuser.sessions[resource] == session then
236 log("warn", "core", "Attempt to send stanza to self, dropping...");
238 print("...sending...", tostring(stanza));
239 --destuser.sessions[resource].conn.write(tostring(data));
240 print(" to conn ", destuser.sessions[resource].conn);
241 destuser.sessions[resource].conn.write(tostring(stanza));
244 elseif stanza.name == "message" then
245 print(" ...will be stored offline");
246 offlinemessage.new(node, host, stanza);
247 elseif stanza.name == "iq" then
248 print(" ...is an iq");
249 stanza.from:send(st.reply(stanza)
250 :tag("error", { type = "cancel" })
251 :tag("service-unavailable", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" }));
255 -- Broadcast a presence stanza to all of a user's contacts
256 function handle_stanza_presence_broadcast(session, stanza)
257 if session.roster then
258 local initial_presence = not session.last_presence;
259 session.last_presence = stanza;
261 -- Broadcast presence and probes
262 local broadcast = st.presence({ from = session.full_jid, type = stanza.attr.type });
264 for child in stanza:childtags() do
265 broadcast:add_child(child);
267 for contact_jid in pairs(session.roster) do
268 broadcast.attr.to = contact_jid;
269 send_to(contact_jid, broadcast);
270 if initial_presence then
271 local node, host = jid.split(contact_jid);
272 if hosts[host] and hosts[host].type == "local" then
273 local contact = hosts[host].sessions[node]
275 local pres = st.presence { to = session.full_jid };
276 for resource, contact_session in pairs(contact.sessions) do
277 if contact_session.last_presence then
278 pres.tags = contact_session.last_presence.tags;
279 pres.attr.from = contact_session.full_jid;
284 --FIXME: Do we send unavailable if they are offline?
286 probe.attr.to = contact;
287 send_to(contact, probe);
292 -- Probe for our contacts' presence
296 -- Broadcast presence probes to all of a user's contacts
297 function handle_stanza_presence_probe_broadcast(session, stanza)
301 function handle_stanza_to_server(stanza)
304 function handle_stanza_iq_no_to(session, stanza)