Relocate presence broadcast to core_handle_stanza()
[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                         core_handle_stanza(origin, stanza);
41         elseif hosts[to] and hosts[to].type == "local" then
42                 core_handle_stanza(origin, stanza);
43         elseif stanza.name == "iq" and not select(3, jid_split(to)) then
44                 core_handle_stanza(origin, stanza);
45         elseif origin.type == "c2s" then
46                 core_route_stanza(origin, stanza);
47         end
48 end
49
50 function core_handle_stanza(origin, stanza)
51         -- Handlers
52         if origin.type == "c2s" or origin.type == "c2s_unauthed" then
53                 local session = origin;
54                 
55                 if stanza.name == "presence" and origin.roster then
56                         if stanza.attr.type == nil or stanza.attr.type == "available" or stanza.attr.type == "unavailable" then
57                                 for jid in pairs(origin.roster) do -- broadcast to all interested contacts
58                                         local subscription = origin.roster[jid].subscription;
59                                         if subscription == "both" or subscription == "from" then
60                                                 stanza.attr.to = jid;
61                                                 core_route_stanza(origin, stanza);
62                                         end
63                                 end
64                                 --[[local node, host = jid_split(stanza.attr.from);
65                                 for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
66                                         if res.full_jid then
67                                                 res = user.sessions[k];
68                                                 break;
69                                         end
70                                 end]]
71                                 if not origin.presence then -- presence probes on initial presence
72                                         local probe = st.presence({from = origin.full_jid, type = "probe"});
73                                         for jid in pairs(origin.roster) do
74                                                 local subscription = origin.roster[jid].subscription;
75                                                 if subscription == "both" or subscription == "to" then
76                                                         probe.attr.to = jid;
77                                                         core_route_stanza(origin, probe);
78                                                 end
79                                         end
80                                 end
81                                 origin.presence = stanza;
82                                 stanza.attr.to = nil; -- reset it
83                         else
84                                 -- TODO error, bad type
85                         end
86                 else
87                         log("debug", "Routing stanza to local");
88                         handle_stanza(session, stanza);
89                 end
90         end
91 end
92
93 function is_authorized_to_see_presence(origin, username, host)
94         local roster = datamanager.load(username, host, "roster") or {};
95         local item = roster[origin.username.."@"..origin.host];
96         return item and (item.subscription == "both" or item.subscription == "from");
97 end
98
99 function core_route_stanza(origin, stanza)
100         -- Hooks
101         --- ...later
102         
103         -- Deliver
104         local to = stanza.attr.to;
105         local node, host, resource = jid_split(to);
106
107         if stanza.name == "presence" and stanza.attr.type == "probe" then resource = nil; end
108
109         local host_session = hosts[host]
110         if host_session and host_session.type == "local" then
111                 -- Local host
112                 local user = host_session.sessions[node];
113                 if user then
114                         local res = user.sessions[resource];
115                         if not res then
116                                 -- if we get here, resource was not specified or was unavailable
117                                 if stanza.name == "presence" then
118                                         if stanza.attr.type == "probe" then
119                                                 if is_authorized_to_see_presence(origin, node, host) then
120                                                         for k in pairs(user.sessions) do -- return presence for all resources
121                                                                 if user.sessions[k].presence then
122                                                                         local pres = user.sessions[k].presence;
123                                                                         pres.attr.to = origin.full_jid;
124                                                                         pres.attr.from = user.sessions[k].full_jid;
125                                                                         send(origin, pres);
126                                                                         pres.attr.to = nil;
127                                                                         pres.attr.from = nil;
128                                                                 end
129                                                         end
130                                                 else
131                                                         send(origin, st.presence({from = user.."@"..host, to = origin.username.."@"..origin.host, type = "unsubscribed"}));
132                                                 end
133                                         else
134                                                 for k in pairs(user.sessions) do -- presence broadcast to all user resources
135                                                         if user.sessions[k].full_jid then
136                                                                 stanza.attr.to = user.sessions[k].full_jid;
137                                                                 send(user.sessions[k], stanza);
138                                                         end
139                                                 end
140                                         end
141                                 elseif stanza.name == "message" then -- select a resource to recieve message
142                                         for k in pairs(user.sessions) do
143                                                 if user.sessions[k].full_jid then
144                                                         res = user.sessions[k];
145                                                         break;
146                                                 end
147                                         end
148                                         -- TODO find resource with greatest priority
149                                         send(res, stanza);
150                                 else
151                                         -- TODO send IQ error
152                                 end
153                         else
154                                 stanza.attr.to = res.full_jid;
155                                 send(res, stanza); -- Yay \o/
156                         end
157                 else
158                         -- user not online
159                         if user_exists(node, host) then
160                                 if stanza.name == "presence" then
161                                         if stanza.attr.type == "probe" and is_authorized_to_see_presence(origin, node, host) then -- FIXME what to do for not c2s?
162                                                 -- TODO send last recieved unavailable presence
163                                         else
164                                                 -- TODO send unavailable presence
165                                         end
166                                 elseif stanza.name == "message" then
167                                         -- TODO send message error, or store offline messages
168                                 elseif stanza.name == "iq" then
169                                         -- TODO send IQ error
170                                 end
171                         else -- user does not exist
172                                 -- TODO we would get here for nodeless JIDs too. Do something fun maybe? Echo service? Let plugins use xmpp:server/resource addresses?
173                                 if stanza.name == "presence" then
174                                         if stanza.attr.type == "probe" then
175                                                 send(origin, st.presence({from = user.."@"..host, to = origin.username.."@"..origin.host, type = "unsubscribed"}));
176                                         end
177                                         -- else ignore
178                                 else
179                                         send(origin, st.error_reply(stanza, "cancel", "service-unavailable"));
180                                 end
181                         end
182                 end
183         else
184                 -- Remote host
185                 if host_session then
186                         -- Send to session
187                 else
188                         -- Need to establish the connection
189                 end
190         end
191         stanza.attr.to = to; -- reset
192 end
193
194 function handle_stanza_nodest(stanza)
195         if stanza.name == "iq" then
196                 handle_stanza_iq_no_to(session, stanza);
197         elseif stanza.name == "presence" then
198                 -- Broadcast to this user's contacts
199                 handle_stanza_presence_broadcast(session, stanza);
200                 -- also, if it is initial presence, send out presence probes
201                 if not session.last_presence then
202                         handle_stanza_presence_probe_broadcast(session, stanza);
203                 end
204                 session.last_presence = stanza;
205         elseif stanza.name == "message" then
206                 -- Treat as if message was sent to bare JID of the sender
207                 handle_stanza_to_local_user(stanza);
208         end
209 end
210
211 function handle_stanza_tolocal(stanza)
212         local node, host, resource = jid.split(stanza.attr.to);
213         if host and hosts[host] and hosts[host].type == "local" then
214                         -- Is a local host, handle internally
215                         if node then
216                                 -- Is a local user, send to their session
217                                 log("debug", "Routing stanza to %s@%s", node, host);
218                                 if not session.username then return; end --FIXME: Correct response when trying to use unauthed stream is what?
219                                 handle_stanza_to_local_user(stanza);
220                         else
221                                 -- Is sent to this server, let's handle it...
222                                 log("debug", "Routing stanza to %s", host);
223                                 handle_stanza_to_server(stanza, session);
224                         end
225         end
226 end
227
228 function handle_stanza_toremote(stanza)
229         log("error", "Stanza bound for remote host, but s2s is not implemented");
230 end
231
232
233 --[[
234 local function route_c2s_stanza(session, stanza)
235         stanza.attr.from = session.full_jid;
236         if not stanza.attr.to and session.username then
237                 -- Has no 'to' attribute, handle internally
238                 if stanza.name == "iq" then
239                         handle_stanza_iq_no_to(session, stanza);
240                 elseif stanza.name == "presence" then
241                         -- Broadcast to this user's contacts
242                         handle_stanza_presence_broadcast(session, stanza);
243                         -- also, if it is initial presence, send out presence probes
244                         if not session.last_presence then
245                                 handle_stanza_presence_probe_broadcast(session, stanza);
246                         end
247                         session.last_presence = stanza;
248                 elseif stanza.name == "message" then
249                         -- Treat as if message was sent to bare JID of the sender
250                         handle_stanza_to_local_user(stanza);
251                 end
252         end
253         local node, host, resource = jid.split(stanza.attr.to);
254         if host and hosts[host] and hosts[host].type == "local" then
255                         -- Is a local host, handle internally
256                         if node then
257                                 -- Is a local user, send to their session
258                                 if not session.username then return; end --FIXME: Correct response when trying to use unauthed stream is what?
259                                 handle_stanza_to_local_user(stanza);
260                         else
261                                 -- Is sent to this server, let's handle it...
262                                 handle_stanza_to_server(stanza, session);
263                         end
264         else
265                 -- Is not for us or a local user, route accordingly
266                 route_s2s_stanza(stanza);
267         end
268 end
269
270 function handle_stanza_no_to(session, stanza)
271         if not stanza.attr.id then log("warn", "<iq> without id attribute is invalid"); end
272         local xmlns = (stanza.tags[1].attr and stanza.tags[1].attr.xmlns);
273         if stanza.attr.type == "get" or stanza.attr.type == "set" then
274                 if iq_handlers[xmlns] then
275                         if iq_handlers[xmlns](stanza) then return; end; -- If handler returns true, it handled it
276                 end
277                 -- Oh, handler didn't handle it. Need to send service-unavailable now.
278                 log("warn", "Unhandled namespace: "..xmlns);
279                 session:send(format("<iq type='error' id='%s'><error type='cancel'><service-unavailable/></error></iq>", stanza.attr.id));
280                 return; -- All done!
281         end
282 end
283
284 function handle_stanza_to_local_user(stanza)
285         if stanza.name == "message" then
286                 handle_stanza_message_to_local_user(stanza);
287         elseif stanza.name == "presence" then
288                 handle_stanza_presence_to_local_user(stanza);
289         elseif stanza.name == "iq" then
290                 handle_stanza_iq_to_local_user(stanza);
291         end
292 end
293
294 function handle_stanza_message_to_local_user(stanza)
295         local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
296         local destuser = hosts[host].sessions[node];
297         if destuser then
298                 if resource and destuser[resource] then
299                         destuser[resource]:send(stanza);
300                 else
301                         -- Bare JID, or resource offline
302                         local best_session;
303                         for resource, session in pairs(destuser.sessions) do
304                                 if not best_session then best_session = session;
305                                 elseif session.priority >= best_session.priority and session.priority >= 0 then
306                                         best_session = session;
307                                 end
308                         end
309                         if not best_session then
310                                 offlinemessage.new(node, host, stanza);
311                         else
312                                 print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'");
313                                 destuser[best_session]:send(stanza);
314                         end
315                 end
316         else
317                 -- User is offline
318                 offlinemessage.new(node, host, stanza);
319         end
320 end
321
322 function handle_stanza_presence_to_local_user(stanza)
323         local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
324         local destuser = hosts[host].sessions[node];
325         if destuser then
326                 if resource then
327                         if destuser[resource] then
328                                 destuser[resource]:send(stanza);
329                         else
330                                 return;
331                         end
332                 else
333                         -- Broadcast to all user's resources
334                         for resource, session in pairs(destuser.sessions) do
335                                 session:send(stanza);
336                         end
337                 end
338         end
339 end
340
341 function handle_stanza_iq_to_local_user(stanza)
342
343 end
344
345 function foo()
346                 local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource;
347                 local destuser = hosts[host].sessions[node];
348                 if destuser and destuser.sessions then
349                         -- User online
350                         if resource and destuser.sessions[resource] then
351                                 stanza.to:send(stanza);
352                         else
353                                 --User is online, but specified resource isn't (or no resource specified)
354                                 local best_session;
355                                 for resource, session in pairs(destuser.sessions) do
356                                         if not best_session then best_session = session;
357                                         elseif session.priority >= best_session.priority and session.priority >= 0 then
358                                                 best_session = session;
359                                         end
360                                 end
361                                 if not best_session then
362                                         offlinemessage.new(node, host, stanza);
363                                 else
364                                         print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'");
365                                         resource = best_session.resource;
366                                 end
367                         end
368                         if destuser.sessions[resource] == session then
369                                 log("warn", "core", "Attempt to send stanza to self, dropping...");
370                         else
371                                 print("...sending...", tostring(stanza));
372                                 --destuser.sessions[resource].conn.write(tostring(data));
373                                 print("   to conn ", destuser.sessions[resource].conn);
374                                 destuser.sessions[resource].conn.write(tostring(stanza));
375                                 print("...sent")
376                         end
377                 elseif stanza.name == "message" then
378                         print("   ...will be stored offline");
379                         offlinemessage.new(node, host, stanza);
380                 elseif stanza.name == "iq" then
381                         print("   ...is an iq");
382                         stanza.from:send(st.reply(stanza)
383                                 :tag("error", { type = "cancel" })
384                                         :tag("service-unavailable", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" }));
385                 end
386 end
387
388 -- Broadcast a presence stanza to all of a user's contacts
389 function handle_stanza_presence_broadcast(session, stanza)
390         if session.roster then
391                 local initial_presence = not session.last_presence;
392                 session.last_presence = stanza;
393                 
394                 -- Broadcast presence and probes
395                 local broadcast = st.presence({ from = session.full_jid, type = stanza.attr.type });
396
397                 for child in stanza:childtags() do
398                         broadcast:add_child(child);
399                 end
400                 for contact_jid in pairs(session.roster) do
401                         broadcast.attr.to = contact_jid;
402                         send_to(contact_jid, broadcast);
403                         if initial_presence then
404                                 local node, host = jid.split(contact_jid);
405                                 if hosts[host] and hosts[host].type == "local" then
406                                         local contact = hosts[host].sessions[node]
407                                         if contact then
408                                                 local pres = st.presence { to = session.full_jid };
409                                                 for resource, contact_session in pairs(contact.sessions) do
410                                                         if contact_session.last_presence then
411                                                                 pres.tags = contact_session.last_presence.tags;
412                                                                 pres.attr.from = contact_session.full_jid;
413                                                                 send(pres);
414                                                         end
415                                                 end
416                                         end
417                                         --FIXME: Do we send unavailable if they are offline?
418                                 else
419                                         probe.attr.to = contact;
420                                         send_to(contact, probe);
421                                 end
422                         end
423                 end
424                 
425                 -- Probe for our contacts' presence
426         end
427 end
428
429 -- Broadcast presence probes to all of a user's contacts
430 function handle_stanza_presence_probe_broadcast(session, stanza)
431 end
432
433 -- 
434 function handle_stanza_to_server(stanza)
435 end
436
437 function handle_stanza_iq_no_to(session, stanza)
438 end
439 ]]