100239c698befec18ce245e0c878a96af0dd4d8c
[prosody.git] / core / stanza_router.lua
1
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)
5
6 require "core.servermanager"
7
8 local log = require "util.logger".init("stanzarouter")
9
10 local st = require "util.stanza";
11 local send = require "core.sessionmanager".send_to_session;
12 local user_exists = require "core.usermanager".user_exists;
13
14 local jid_split = require "util.jid".split;
15 local print = print;
16
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
22                         error("Invalid IQ");
23                 elseif #stanza.tags > 1 or not(stanza.attr.type == "error" or stanza.attr.type == "result") then
24                         error("Invalid IQ");
25                 end
26         end
27
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");
32         end
33
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
37         
38         -- TODO presence subscriptions
39         if not to then
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
46                                                 stanza.attr.to = jid;
47                                                 core_route_stanza(origin, stanza);
48                                         end
49                                 end
50                                 --[[local node, host = jid_split(stanza.attr.from);
51                                 for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
52                                         if res.full_jid then
53                                                 res = user.sessions[k];
54                                                 break;
55                                         end
56                                 end]]
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
62                                                         probe.attr.to = jid;
63                                                         core_route_stanza(origin, probe);
64                                                 end
65                                         end
66                                 end
67                                 origin.presence = stanza;
68                                 stanza.attr.to = nil; -- reset it
69                         else
70                                 -- TODO error, bad type
71                         end
72                 else
73                         core_handle_stanza(origin, stanza);
74                 end
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);
81         end
82 end
83
84 function core_handle_stanza(origin, stanza)
85         -- Handlers
86         if origin.type == "c2s" or origin.type == "c2s_unauthed" then
87                 local session = origin;
88                 
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
93                 
94                 -- Stanza is to this server, or a user on this server
95                 log("debug", "Routing stanza to local");
96                 handle_stanza(session, stanza);
97         end
98 end
99
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");
104 end
105
106 function core_route_stanza(origin, stanza)
107         -- Hooks
108         --- ...later
109         
110         -- Deliver
111         local to = stanza.attr.to;
112         local node, host, resource = jid_split(to);
113
114         if stanza.name == "presence" and stanza.attr.type == "probe" then resource = nil; end
115
116         local host_session = hosts[host]
117         if host_session and host_session.type == "local" then
118                 -- Local host
119                 local user = host_session.sessions[node];
120                 if user then
121                         local res = user.sessions[resource];
122                         if not res then
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;
132                                                                         send(origin, pres);
133                                                                         pres.attr.to = nil;
134                                                                         pres.attr.from = nil;
135                                                                 end
136                                                         end
137                                                 else
138                                                         send(origin, st.presence({from = user.."@"..host, to = origin.username.."@"..origin.host, type = "unsubscribed"}));
139                                                 end
140                                         else
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);
145                                                         end
146                                                 end
147                                         end
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];
152                                                         break;
153                                                 end
154                                         end
155                                         -- TODO find resource with greatest priority
156                                         send(res, stanza);
157                                 else
158                                         -- TODO send IQ error
159                                 end
160                         else
161                                 stanza.attr.to = res.full_jid;
162                                 send(res, stanza); -- Yay \o/
163                         end
164                 else
165                         -- user not online
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
170                                         else
171                                                 -- TODO send unavailable presence
172                                         end
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
177                                 end
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"}));
183                                         end
184                                         -- else ignore
185                                 else
186                                         send(origin, st.error_reply(stanza, "cancel", "service-unavailable"));
187                                 end
188                         end
189                 end
190         else
191                 -- Remote host
192                 if host_session then
193                         -- Send to session
194                 else
195                         -- Need to establish the connection
196                 end
197         end
198         stanza.attr.to = to; -- reset
199 end
200
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);
210                 end
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);
215         end
216 end
217
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
222                         if node then
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);
227                         else
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);
231                         end
232         end
233 end
234
235 function handle_stanza_toremote(stanza)
236         log("error", "Stanza bound for remote host, but s2s is not implemented");
237 end
238
239
240 --[[
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);
253                         end
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);
258                 end
259         end
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
263                         if node then
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);
267                         else
268                                 -- Is sent to this server, let's handle it...
269                                 handle_stanza_to_server(stanza, session);
270                         end
271         else
272                 -- Is not for us or a local user, route accordingly
273                 route_s2s_stanza(stanza);
274         end
275 end
276
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
283                 end
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));
287                 return; -- All done!
288         end
289 end
290
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);
298         end
299 end
300
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];
304         if destuser then
305                 if resource and destuser[resource] then
306                         destuser[resource]:send(stanza);
307                 else
308                         -- Bare JID, or resource offline
309                         local best_session;
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;
314                                 end
315                         end
316                         if not best_session then
317                                 offlinemessage.new(node, host, stanza);
318                         else
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);
321                         end
322                 end
323         else
324                 -- User is offline
325                 offlinemessage.new(node, host, stanza);
326         end
327 end
328
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];
332         if destuser then
333                 if resource then
334                         if destuser[resource] then
335                                 destuser[resource]:send(stanza);
336                         else
337                                 return;
338                         end
339                 else
340                         -- Broadcast to all user's resources
341                         for resource, session in pairs(destuser.sessions) do
342                                 session:send(stanza);
343                         end
344                 end
345         end
346 end
347
348 function handle_stanza_iq_to_local_user(stanza)
349
350 end
351
352 function foo()
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
356                         -- User online
357                         if resource and destuser.sessions[resource] then
358                                 stanza.to:send(stanza);
359                         else
360                                 --User is online, but specified resource isn't (or no resource specified)
361                                 local best_session;
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;
366                                         end
367                                 end
368                                 if not best_session then
369                                         offlinemessage.new(node, host, stanza);
370                                 else
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;
373                                 end
374                         end
375                         if destuser.sessions[resource] == session then
376                                 log("warn", "core", "Attempt to send stanza to self, dropping...");
377                         else
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));
382                                 print("...sent")
383                         end
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" }));
392                 end
393 end
394
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;
400                 
401                 -- Broadcast presence and probes
402                 local broadcast = st.presence({ from = session.full_jid, type = stanza.attr.type });
403
404                 for child in stanza:childtags() do
405                         broadcast:add_child(child);
406                 end
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]
414                                         if contact then
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;
420                                                                 send(pres);
421                                                         end
422                                                 end
423                                         end
424                                         --FIXME: Do we send unavailable if they are offline?
425                                 else
426                                         probe.attr.to = contact;
427                                         send_to(contact, probe);
428                                 end
429                         end
430                 end
431                 
432                 -- Probe for our contacts' presence
433         end
434 end
435
436 -- Broadcast presence probes to all of a user's contacts
437 function handle_stanza_presence_probe_broadcast(session, stanza)
438 end
439
440 -- 
441 function handle_stanza_to_server(stanza)
442 end
443
444 function handle_stanza_iq_no_to(session, stanza)
445 end
446 ]]