01a4696185c038117e86a39c8dd63295a28d2e27
[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_s2s = require "core.s2smanager".send_to_host;
12 local user_exists = require "core.usermanager".user_exists;
13
14 local rostermanager = require "core.rostermanager";
15 local sessionmanager = require "core.sessionmanager";
16
17 local s2s_verify_dialback = require "core.s2smanager".verify_dialback;
18 local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
19
20 local modules_handle_stanza = require "core.modulemanager".handle_stanza;
21
22 local format = string.format;
23 local tostring = tostring;
24
25 local jid_split = require "util.jid".split;
26 local print = print;
27
28 function core_process_stanza(origin, stanza)
29         log("debug", "Received["..origin.type.."]: "..tostring(stanza))
30         -- TODO verify validity of stanza (as well as JID validity)
31         if stanza.name == "iq" and not(#stanza.tags == 1 and stanza.tags[1].attr.xmlns) then
32                 if stanza.attr.type == "set" or stanza.attr.type == "get" then
33                         error("Invalid IQ");
34                 elseif #stanza.tags > 1 and not(stanza.attr.type == "error" or stanza.attr.type == "result") then
35                         error("Invalid IQ");
36                 end
37         end
38
39         if origin.type == "c2s" and not origin.full_jid
40                 and not(stanza.name == "iq" and stanza.tags[1].name == "bind"
41                                 and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then
42                 error("Client MUST bind resource after auth");
43         end
44
45         local to = stanza.attr.to;
46         -- TODO also, stazas should be returned to their original state before the function ends
47         if origin.type == "c2s" then
48                 stanza.attr.from = origin.full_jid; -- quick fix to prevent impersonation (FIXME this would be incorrect when the origin is not c2s)
49         end
50         
51         if not to then
52                 core_handle_stanza(origin, stanza);
53         elseif origin.type == "c2s" and stanza.name == "presence" and stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" then
54                 local node, host = jid_split(stanza.attr.to);
55                 local to_bare = node and (node.."@"..host) or host; -- bare JID
56                 local from_node, from_host = jid_split(stanza.attr.from);
57                 local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID
58                 handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare);
59         elseif hosts[to] and hosts[to].type == "local" then
60                 core_handle_stanza(origin, stanza);
61         elseif stanza.name == "iq" and not select(3, jid_split(to)) then
62                 core_handle_stanza(origin, stanza);
63         elseif stanza.attr.xmlns and stanza.attr.xmlns ~= "jabber:client" and stanza.attr.xmlns ~= "jabber:server" then
64                 modules_handle_stanza(origin, stanza);
65         elseif origin.type == "c2s" or origin.type == "s2sin" then
66                 core_route_stanza(origin, stanza);
67         end
68 end
69
70 -- This function handles stanzas which are not routed any further,
71 -- that is, they are handled by this server
72 function core_handle_stanza(origin, stanza)
73         -- Handlers
74         if modules_handle_stanza(origin, stanza) then return; end
75         if origin.type == "c2s" or origin.type == "c2s_unauthed" then
76                 local session = origin;
77                 
78                 if stanza.name == "presence" and origin.roster then
79                         if stanza.attr.type == nil or stanza.attr.type == "unavailable" then
80                                 for jid in pairs(origin.roster) do -- broadcast to all interested contacts
81                                         local subscription = origin.roster[jid].subscription;
82                                         if subscription == "both" or subscription == "from" then
83                                                 stanza.attr.to = jid;
84                                                 core_route_stanza(origin, stanza);
85                                         end
86                                 end
87                                 local node, host = jid_split(stanza.attr.from);
88                                 for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
89                                         if res ~= origin and res.full_jid then -- to resource. FIXME is res.full_jid the correct check? Maybe it should be res.presence
90                                                 stanza.attr.to = res.full_jid;
91                                                 core_route_stanza(origin, stanza);
92                                         end
93                                 end
94                                 if not origin.presence then -- presence probes on initial presence -- FIXME does unavailable qualify as initial presence?
95                                         local probe = st.presence({from = origin.full_jid, type = "probe"});
96                                         for jid in pairs(origin.roster) do -- probe all contacts we are subscribed to
97                                                 local subscription = origin.roster[jid].subscription;
98                                                 if subscription == "both" or subscription == "to" then
99                                                         probe.attr.to = jid;
100                                                         core_route_stanza(origin, probe);
101                                                 end
102                                         end
103                                         for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast from all resources
104                                                 if res ~= origin and stanza.attr.type ~= "unavailable" and res.presence then -- FIXME does unavailable qualify as initial presence?
105                                                         res.presence.attr.to = origin.full_jid;
106                                                         core_route_stanza(res, res.presence);
107                                                         res.presence.attr.to = nil;
108                                                 end
109                                         end
110                                         -- TODO resend subscription requests
111                                 end
112                                 origin.presence = stanza;
113                                 stanza.attr.to = nil; -- reset it
114                         else
115                                 -- TODO error, bad type
116                         end
117                 end
118         else
119                 log("warn", "Unhandled origin: %s", origin.type);
120         end
121 end
122
123 function send_presence_of_available_resources(user, host, jid, recipient_session)
124         local h = hosts[host];
125         local count = 0;
126         if h and h.type == "local" then
127                 local u = h.sessions[user];
128                 if u then
129                         for k, session in pairs(u.sessions) do
130                                 local pres = session.presence;
131                                 if pres then
132                                         pres.attr.to = jid;
133                                         pres.attr.from = session.full_jid;
134                                         recipient_session.send(pres);
135                                         pres.attr.to = nil;
136                                         pres.attr.from = nil;
137                                         count = count + 1;
138                                 end
139                         end
140                 end
141         end
142         return count;
143 end
144
145 function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare)
146         local node, host = jid_split(from_bare);
147         local st_from, st_to = stanza.attr.from, stanza.attr.to;
148         stanza.attr.from, stanza.attr.to = from_bare, to_bare;
149         if stanza.attr.type == "subscribe" then
150                 log("debug", "outbound subscribe from "..from_bare.." for "..to_bare);
151                 -- 1. route stanza
152                 -- 2. roster push (subscription = none, ask = subscribe)
153                 if rostermanager.set_contact_pending_out(node, host, to_bare) then
154                         rostermanager.roster_push(node, host, to_bare);
155                 end -- else file error
156                 core_route_stanza(origin, stanza);
157         elseif stanza.attr.type == "unsubscribe" then
158                 log("debug", "outbound unsubscribe from "..from_bare.." for "..to_bare);
159                 -- 1. route stanza
160                 -- 2. roster push (subscription = none or from)
161                 if rostermanager.unsubscribe(node, host, to_bare) then
162                         rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed?
163                 end -- else file error
164                 core_route_stanza(origin, stanza);
165         elseif stanza.attr.type == "subscribed" then
166                 log("debug", "outbound subscribed from "..from_bare.." for "..to_bare);
167                 -- 1. route stanza
168                 -- 2. roster_push ()
169                 -- 3. send_presence_of_available_resources
170                 if rostermanager.subscribed(node, host, to_bare) then
171                         rostermanager.roster_push(node, host, to_bare);
172                         core_route_stanza(origin, stanza);
173                         send_presence_of_available_resources(node, host, to_bare, origin);
174                 end
175         elseif stanza.attr.type == "unsubscribed" then
176                 log("debug", "outbound unsubscribed from "..from_bare.." for "..to_bare);
177                 -- 1. route stanza
178                 -- 2. roster push (subscription = none or to)
179                 if rostermanager.unsubscribed(node, host, to_bare) then
180                         rostermanager.roster_push(node, host, to_bare);
181                         core_route_stanza(origin, stanza);
182                 end
183         end
184         stanza.attr.from, stanza.attr.to = st_from, st_to;
185 end
186
187 function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare)
188         local node, host = jid_split(to_bare);
189         local st_from, st_to = stanza.attr.from, stanza.attr.to;
190         stanza.attr.from, stanza.attr.to = from_bare, to_bare;
191         if stanza.attr.type == "probe" then
192                 if rostermanager.is_contact_subscribed(node, host, from_bare) then
193                         if 0 == send_presence_of_available_resources(node, host, from_bare, origin) then
194                                 -- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
195                         end
196                 else
197                         core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
198                 end
199         elseif stanza.attr.type == "subscribe" then
200                 log("debug", "inbound subscribe from "..from_bare.." for "..to_bare);
201                 if rostermanager.is_contact_subscribed(node, host, from_bare) then
202                         core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
203                 else
204                         if not rostermanager.is_contact_pending_in(node, host, from_bare) then
205                                 if rostermanager.set_contact_pending_in(node, host, from_bare) then
206                                         sessionmanager.send_to_available_resources(node, host, stanza);
207                                 end -- TODO else return error, unable to save
208                         end
209                 end
210         elseif stanza.attr.type == "unsubscribe" then
211                 log("debug", "inbound unsubscribe from "..from_bare.." for "..to_bare);
212                 if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then
213                         rostermanager.roster_push(node, host, from_bare);
214                 end
215         elseif stanza.attr.type == "subscribed" then
216                 log("debug", "inbound subscribed from "..from_bare.." for "..to_bare);
217                 if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then
218                         rostermanager.roster_push(node, host, from_bare);
219                 end
220         elseif stanza.attr.type == "unsubscribed" then
221                 log("debug", "inbound unsubscribed from "..from_bare.." for "..to_bare);
222                 if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then
223                         rostermanager.roster_push(node, host, from_bare);
224                 end
225         end -- discard any other type
226         stanza.attr.from, stanza.attr.to = st_from, st_to;
227 end
228
229 function core_route_stanza(origin, stanza)
230         -- Hooks
231         --- ...later
232         
233         -- Deliver
234         local to = stanza.attr.to;
235         local node, host, resource = jid_split(to);
236         local to_bare = node and (node.."@"..host) or host; -- bare JID
237         local from = stanza.attr.from;
238         local from_node, from_host, from_resource = jid_split(from);
239         local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID
240
241         if stanza.name == "presence" and (stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable") then resource = nil; end
242
243         local host_session = hosts[host]
244         if host_session and host_session.type == "local" then
245                 -- Local host
246                 local user = host_session.sessions[node];
247                 if user then
248                         local res = user.sessions[resource];
249                         if not res then
250                                 -- if we get here, resource was not specified or was unavailable
251                                 if stanza.name == "presence" then
252                                         if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" then
253                                                 handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare);
254                                         else -- sender is available or unavailable
255                                                 for k in pairs(user.sessions) do -- presence broadcast to all user resources. FIXME should this be just for available resources? Do we need to check subscription?
256                                                         if user.sessions[k].full_jid then
257                                                                 stanza.attr.to = user.sessions[k].full_jid; -- reset at the end of function
258                                                                 user.sessions[k].send(stanza);
259                                                         end
260                                                 end
261                                         end
262                                 elseif stanza.name == "message" then -- select a resource to recieve message
263                                         for k in pairs(user.sessions) do
264                                                 if user.sessions[k].full_jid then
265                                                         res = user.sessions[k];
266                                                         break;
267                                                 end
268                                         end
269                                         -- TODO find resource with greatest priority
270                                         res.send(stanza);
271                                 else
272                                         -- TODO send IQ error
273                                 end
274                         else
275                                 -- User + resource is online...
276                                 stanza.attr.to = res.full_jid; -- reset at the end of function
277                                 res.send(stanza); -- Yay \o/
278                         end
279                 else
280                         -- user not online
281                         if user_exists(node, host) then
282                                 if stanza.name == "presence" then
283                                         if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" then
284                                                 handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare);
285                                         else
286                                                 -- TODO send unavailable presence or unsubscribed
287                                         end
288                                 elseif stanza.name == "message" then
289                                         -- TODO send message error, or store offline messages
290                                 elseif stanza.name == "iq" then
291                                         -- TODO send IQ error
292                                 end
293                         else -- user does not exist
294                                 -- TODO we would get here for nodeless JIDs too. Do something fun maybe? Echo service? Let plugins use xmpp:server/resource addresses?
295                                 if stanza.name == "presence" then
296                                         if stanza.attr.type == "probe" then
297                                                 origin.send(st.presence({from = to_bare, to = from_bare, type = "unsubscribed"}));
298                                         end
299                                         -- else ignore
300                                 else
301                                         origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
302                                 end
303                         end
304                 end
305         elseif origin.type == "c2s" then
306                 -- Remote host
307                 local xmlns = stanza.attr.xmlns;
308                 --stanza.attr.xmlns = "jabber:server";
309                 stanza.attr.xmlns = nil;
310                 log("debug", "sending s2s stanza: %s", tostring(stanza));
311                 send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors
312                 stanza.attr.xmlns = xmlns; -- reset
313         else
314                 log("warn", "received stanza from unhandled connection type: %s", origin.type);
315         end
316         stanza.attr.to = to; -- reset
317 end
318
319 function handle_stanza_toremote(stanza)
320         log("error", "Stanza bound for remote host, but s2s is not implemented");
321 end