10335fa32a83544338e61dea910da037911dcd27
[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 require "util.jid"
11 local jid_split = jid.split;
12
13 function core_process_stanza(origin, stanza)
14         log("debug", "Received: "..tostring(stanza))
15         local to = stanza.attr.to;
16         
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);
21         end
22                 
23 end
24
25 function core_handle_stanza(origin, stanza)
26         -- Handlers
27         if origin.type == "c2s" or origin.type == "c2s_unauthed" then
28                 local session = origin;
29                 stanza.attr.from = session.full_jid;
30                 
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
35                 
36                 -- Stanza is to this server, or a user on this server
37                 log("debug", "Routing stanza to local");
38                 handle_stanza(session, stanza);
39         end     
40 end
41
42 function core_route_stanza(origin, stanza)
43         -- Hooks
44         --- ...later
45         
46         -- Deliver
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
50                 -- Local host
51         else
52                 -- Remote host
53                 if host_session then
54                         -- Send to session
55                 else
56                         -- Need to establish the connection
57                 end
58         end
59 end
60
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);
70                 end
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);
75         end
76 end
77
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
82                         if node then
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);
87                         else
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);
91                         end
92         end
93 end
94
95 function handle_stanza_toremote(stanza)
96         log("error", "Stanza bound for remote host, but s2s is not implemented");
97 end
98
99
100 --[[
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);
113                         end
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);
118                 end
119         end
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
123                         if node then
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);
127                         else
128                                 -- Is sent to this server, let's handle it...
129                                 handle_stanza_to_server(stanza, session);
130                         end
131         else
132                 -- Is not for us or a local user, route accordingly
133                 route_s2s_stanza(stanza);
134         end
135 end
136
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
143                 end
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));
147                 return; -- All done!
148         end
149 end
150
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);
158         end
159 end
160
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];
164         if destuser then
165                 if resource and destuser[resource] then
166                         destuser[resource]:send(stanza);
167                 else
168                         -- Bare JID, or resource offline
169                         local best_session;
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;
174                                 end
175                         end
176                         if not best_session then
177                                 offlinemessage.new(node, host, stanza);
178                         else
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);
181                         end
182                 end
183         else
184                 -- User is offline
185                 offlinemessage.new(node, host, stanza);
186         end
187 end
188
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];
192         if destuser then
193                 if resource then
194                         if destuser[resource] then
195                                 destuser[resource]:send(stanza);
196                         else
197                                 return;
198                         end
199                 else
200                         -- Broadcast to all user's resources
201                         for resource, session in pairs(destuser.sessions) do
202                                 session:send(stanza);
203                         end
204                 end
205         end
206 end
207
208 function handle_stanza_iq_to_local_user(stanza)
209
210 end
211
212 function foo()
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
216                         -- User online
217                         if resource and destuser.sessions[resource] then
218                                 stanza.to:send(stanza);
219                         else
220                                 --User is online, but specified resource isn't (or no resource specified)
221                                 local best_session;
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;
226                                         end
227                                 end
228                                 if not best_session then
229                                         offlinemessage.new(node, host, stanza);
230                                 else
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;
233                                 end
234                         end
235                         if destuser.sessions[resource] == session then
236                                 log("warn", "core", "Attempt to send stanza to self, dropping...");
237                         else
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));
242                                 print("...sent")
243                         end
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" }));
252                 end
253 end
254
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;
260                 
261                 -- Broadcast presence and probes
262                 local broadcast = st.presence({ from = session.full_jid, type = stanza.attr.type });
263
264                 for child in stanza:childtags() do
265                         broadcast:add_child(child);
266                 end
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]
274                                         if contact then
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;
280                                                                 send(pres);
281                                                         end
282                                                 end
283                                         end
284                                         --FIXME: Do we send unavailable if they are offline?
285                                 else
286                                         probe.attr.to = contact;
287                                         send_to(contact, probe);
288                                 end
289                         end
290                 end
291                 
292                 -- Probe for our contacts' presence
293         end
294 end
295
296 -- Broadcast presence probes to all of a user's contacts
297 function handle_stanza_presence_probe_broadcast(session, stanza)
298 end
299
300 -- 
301 function handle_stanza_to_server(stanza)
302 end
303
304 function handle_stanza_iq_no_to(session, stanza)
305 end
306 ]]