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