7 |
7 |
8 local log = require "util.logger".init("stanzarouter") |
8 local log = require "util.logger".init("stanzarouter") |
9 |
9 |
10 local st = require "util.stanza"; |
10 local st = require "util.stanza"; |
11 local send = require "core.sessionmanager".send_to_session; |
11 local send = require "core.sessionmanager".send_to_session; |
12 |
12 local user_exists = require "core.usermanager".user_exists; |
13 require "util.jid" |
13 |
14 local jid_split = jid.split; |
14 local jid_split = require "util.jid".split; |
|
15 local print = print; |
15 |
16 |
16 function core_process_stanza(origin, stanza) |
17 function core_process_stanza(origin, stanza) |
17 log("debug", "Received: "..tostring(stanza)) |
18 log("debug", "Received: "..tostring(stanza)) |
18 -- TODO verify validity of stanza (as well as JID validity) |
19 -- TODO verify validity of stanza (as well as JID validity) |
19 if stanza.name == "iq" and not(#stanza.tags == 1 and stanza.tags[1].attr.xmlns) then |
20 if stanza.name == "iq" and not(#stanza.tags == 1 and stanza.tags[1].attr.xmlns) then |
20 error("Invalid IQ"); |
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 |
21 end |
26 end |
22 |
27 |
23 if origin.type == "c2s" and not origin.full_jid |
28 if origin.type == "c2s" and not origin.full_jid |
24 and not(stanza.name == "iq" and stanza.tags[1].name == "bind" |
29 and not(stanza.name == "iq" and stanza.tags[1].name == "bind" |
25 and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then |
30 and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then |
26 error("Client MUST bind resource after auth"); |
31 error("Client MUST bind resource after auth"); |
27 end |
32 end |
28 |
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 |
29 |
37 |
30 local to = stanza.attr.to; |
38 -- TODO presence subscriptions |
31 stanza.attr.from = origin.full_jid -- quick fix to prevent impersonation |
39 if not to then |
32 |
40 if stanza.name == "presence" and origin.roster then |
33 if not to or (hosts[to] and hosts[to].type == "local") 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 |
34 core_handle_stanza(origin, stanza); |
76 core_handle_stanza(origin, stanza); |
35 elseif to and stanza.name == "iq" and not select(3, jid_split(to)) then |
77 elseif stanza.name == "iq" and not select(3, jid_split(to)) then |
36 core_handle_stanza(origin, stanza); |
78 core_handle_stanza(origin, stanza); |
37 elseif origin.type == "c2s" then |
79 elseif origin.type == "c2s" then |
38 core_route_stanza(origin, stanza); |
80 core_route_stanza(origin, stanza); |
39 end |
81 end |
40 end |
82 end |
41 |
83 |
42 function core_handle_stanza(origin, stanza) |
84 function core_handle_stanza(origin, stanza) |
43 -- Handlers |
85 -- Handlers |
44 if origin.type == "c2s" or origin.type == "c2s_unauthed" then |
86 if origin.type == "c2s" or origin.type == "c2s_unauthed" then |
45 local session = origin; |
87 local session = origin; |
46 stanza.attr.from = session.full_jid; |
|
47 |
88 |
48 log("debug", "Routing stanza"); |
89 log("debug", "Routing stanza"); |
49 -- Stanza has no to attribute |
90 -- Stanza has no to attribute |
50 --local to_node, to_host, to_resource = jid_split(stanza.attr.to); |
91 --local to_node, to_host, to_resource = jid_split(stanza.attr.to); |
51 --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 |
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 |
54 log("debug", "Routing stanza to local"); |
95 log("debug", "Routing stanza to local"); |
55 handle_stanza(session, stanza); |
96 handle_stanza(session, stanza); |
56 end |
97 end |
57 end |
98 end |
58 |
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 |
59 function core_route_stanza(origin, stanza) |
106 function core_route_stanza(origin, stanza) |
60 -- Hooks |
107 -- Hooks |
61 --- ...later |
108 --- ...later |
62 |
109 |
63 -- Deliver |
110 -- Deliver |
64 local node, host, resource = jid_split(stanza.attr.to); |
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 |
65 local host_session = hosts[host] |
116 local host_session = hosts[host] |
66 if host_session and host_session.type == "local" then |
117 if host_session and host_session.type == "local" then |
67 -- Local host |
118 -- Local host |
68 local user = host_session.sessions[node]; |
119 local user = host_session.sessions[node]; |
69 if user then |
120 if user then |
70 local res = user.sessions[resource]; |
121 local res = user.sessions[resource]; |
71 -- TODO do something about presence broadcast |
|
72 if not res then |
122 if not res then |
73 -- if we get here, resource was not specified or was unavailable |
123 -- if we get here, resource was not specified or was unavailable |
74 for k in pairs(user.sessions) do |
124 if stanza.name == "presence" then |
75 res = user.sessions[k]; |
125 if stanza.attr.type == "probe" then |
76 break; |
126 if is_authorized_to_see_presence(origin, node, host) then |
77 end |
127 for k in pairs(user.sessions) do -- return presence for all resources |
78 -- TODO find resource with greatest priority |
128 if user.sessions[k].presence then |
79 end |
129 local pres = user.sessions[k].presence; |
80 stanza.attr.to = res.full_jid; |
130 pres.attr.to = origin.full_jid; |
81 send(res, stanza); -- Yay \o/ |
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 |
82 else |
164 else |
83 -- user not found |
165 -- user not online |
84 send(origin, st.error_reply(stanza, "cancel", "service-unavailable")); |
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 |
85 end |
189 end |
86 else |
190 else |
87 -- Remote host |
191 -- Remote host |
88 if host_session then |
192 if host_session then |
89 -- Send to session |
193 -- Send to session |
90 else |
194 else |
91 -- Need to establish the connection |
195 -- Need to establish the connection |
92 end |
196 end |
93 end |
197 end |
|
198 stanza.attr.to = to; -- reset |
94 end |
199 end |
95 |
200 |
96 function handle_stanza_nodest(stanza) |
201 function handle_stanza_nodest(stanza) |
97 if stanza.name == "iq" then |
202 if stanza.name == "iq" then |
98 handle_stanza_iq_no_to(session, stanza); |
203 handle_stanza_iq_no_to(session, stanza); |