Automated merge with http://waqas.ath.cx:8000/
[prosody.git] / plugins / mod_presence.lua
1 -- Prosody IM v0.4\r
2 -- Copyright (C) 2008-2009 Matthew Wild\r
3 -- Copyright (C) 2008-2009 Waqas Hussain\r
4 -- \r
5 -- This project is MIT/X11 licensed. Please see the\r
6 -- COPYING file in the source package for more information.\r
7 --\r
8 \r
9 \r
10 \r
11 local log = require "util.logger".init("mod_presence")\r
12 \r
13 local require = require;\r
14 local pairs, ipairs = pairs, ipairs;\r
15 local t_concat = table.concat;\r
16 local s_find = string.find;\r
17 local tonumber = tonumber;\r
18 \r
19 local st = require "util.stanza";\r
20 local jid_split = require "util.jid".split;\r
21 local jid_bare = require "util.jid".bare;\r
22 local hosts = hosts;\r
23 \r
24 local rostermanager = require "core.rostermanager";\r
25 local sessionmanager = require "core.sessionmanager";\r
26 local offlinemanager = require "core.offlinemanager";\r
27 \r
28 function handle_presence(origin, stanza, from_bare, to_bare, core_route_stanza, inbound)\r
29         local type = stanza.attr.type;\r
30         if type and type ~= "unavailable" and type ~= "error" then\r
31                 if inbound then\r
32                         handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);\r
33                 else\r
34                         handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);\r
35                 end\r
36         elseif not inbound and not stanza.attr.to then\r
37                 handle_normal_presence(origin, stanza, core_route_stanza);\r
38         else\r
39                 core_route_stanza(origin, stanza);\r
40         end\r
41 end\r
42 \r
43 function handle_normal_presence(origin, stanza, core_route_stanza)\r
44         if origin.roster then\r
45                 for jid in pairs(origin.roster) do -- broadcast to all interested contacts\r
46                         local subscription = origin.roster[jid].subscription;\r
47                         if subscription == "both" or subscription == "from" then\r
48                                 stanza.attr.to = jid;\r
49                                 core_route_stanza(origin, stanza);\r
50                         end\r
51                 end\r
52                 local node, host = jid_split(stanza.attr.from);\r
53                 for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources\r
54                         if res ~= origin and res.full_jid then -- to resource. FIXME is res.full_jid the correct check? Maybe it should be res.presence\r
55                                 stanza.attr.to = res.full_jid;\r
56                                 core_route_stanza(origin, stanza);\r
57                         end\r
58                 end\r
59                 if stanza.attr.type == nil and not origin.presence then -- initial presence\r
60                         local probe = st.presence({from = origin.full_jid, type = "probe"});\r
61                         for jid in pairs(origin.roster) do -- probe all contacts we are subscribed to\r
62                                 local subscription = origin.roster[jid].subscription;\r
63                                 if subscription == "both" or subscription == "to" then\r
64                                         probe.attr.to = jid;\r
65                                         core_route_stanza(origin, probe);\r
66                                 end\r
67                         end\r
68                         for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast from all available resources\r
69                                 if res ~= origin and res.presence then\r
70                                         res.presence.attr.to = origin.full_jid;\r
71                                         core_route_stanza(res, res.presence);\r
72                                         res.presence.attr.to = nil;\r
73                                 end\r
74                         end\r
75                         if origin.roster.pending then -- resend incoming subscription requests\r
76                                 for jid in pairs(origin.roster.pending) do\r
77                                         origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original?\r
78                                 end\r
79                         end\r
80                         local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host});\r
81                         for jid, item in pairs(origin.roster) do -- resend outgoing subscription requests\r
82                                 if item.ask then\r
83                                         request.attr.to = jid;\r
84                                         core_route_stanza(origin, request);\r
85                                 end\r
86                         end\r
87                         local offline = offlinemanager.load(node, host);\r
88                         if offline then\r
89                                 for _, msg in ipairs(offline) do\r
90                                         origin.send(msg); -- FIXME do we need to modify to/from in any way?\r
91                                 end\r
92                                 offlinemanager.deleteAll(node, host);\r
93                         end\r
94                 end\r
95                 origin.priority = 0;\r
96                 if stanza.attr.type == "unavailable" then\r
97                         origin.presence = nil;\r
98                         if origin.directed then\r
99                                 local old_from = stanza.attr.from;\r
100                                 stanza.attr.from = origin.full_jid;\r
101                                 for jid in pairs(origin.directed) do\r
102                                         stanza.attr.to = jid;\r
103                                         core_route_stanza(origin, stanza);\r
104                                 end\r
105                                 stanza.attr.from = old_from;\r
106                                 origin.directed = nil;\r
107                         end\r
108                 else\r
109                         origin.presence = stanza;\r
110                         local priority = stanza:child_with_name("priority");\r
111                         if priority and #priority > 0 then\r
112                                 priority = t_concat(priority);\r
113                                 if s_find(priority, "^[+-]?[0-9]+$") then\r
114                                         priority = tonumber(priority);\r
115                                         if priority < -128 then priority = -128 end\r
116                                         if priority > 127 then priority = 127 end\r
117                                         origin.priority = priority;\r
118                                 end\r
119                         end\r
120                 end\r
121                 stanza.attr.to = nil; -- reset it\r
122         else\r
123                 log("error", "presence recieved from client with no roster");\r
124         end\r
125 end\r
126 \r
127 function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza)\r
128         local h = hosts[host];\r
129         local count = 0;\r
130         if h and h.type == "local" then\r
131                 local u = h.sessions[user];\r
132                 if u then\r
133                         for k, session in pairs(u.sessions) do\r
134                                 local pres = session.presence;\r
135                                 if pres then\r
136                                         pres.attr.to = jid;\r
137                                         pres.attr.from = session.full_jid;\r
138                                         core_route_stanza(session, pres);\r
139                                         pres.attr.to = nil;\r
140                                         pres.attr.from = nil;\r
141                                         count = count + 1;\r
142                                 end\r
143                         end\r
144                 end\r
145         end\r
146         log("info", "broadcasted presence of "..count.." resources from "..user.."@"..host.." to "..jid);\r
147         return count;\r
148 end\r
149 \r
150 function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)\r
151         local node, host = jid_split(from_bare);\r
152         local st_from, st_to = stanza.attr.from, stanza.attr.to;\r
153         stanza.attr.from, stanza.attr.to = from_bare, to_bare;\r
154         log("debug", "outbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);\r
155         if stanza.attr.type == "subscribe" then\r
156                 -- 1. route stanza\r
157                 -- 2. roster push (subscription = none, ask = subscribe)\r
158                 if rostermanager.set_contact_pending_out(node, host, to_bare) then\r
159                         rostermanager.roster_push(node, host, to_bare);\r
160                 end -- else file error\r
161                 core_route_stanza(origin, stanza);\r
162         elseif stanza.attr.type == "unsubscribe" then\r
163                 -- 1. route stanza\r
164                 -- 2. roster push (subscription = none or from)\r
165                 if rostermanager.unsubscribe(node, host, to_bare) then\r
166                         rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed?\r
167                 end -- else file error\r
168                 core_route_stanza(origin, stanza);\r
169         elseif stanza.attr.type == "subscribed" then\r
170                 -- 1. route stanza\r
171                 -- 2. roster_push ()\r
172                 -- 3. send_presence_of_available_resources\r
173                 if rostermanager.subscribed(node, host, to_bare) then\r
174                         rostermanager.roster_push(node, host, to_bare);\r
175                 end\r
176                 core_route_stanza(origin, stanza);\r
177                 send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza);\r
178         elseif stanza.attr.type == "unsubscribed" then\r
179                 -- 1. route stanza\r
180                 -- 2. roster push (subscription = none or to)\r
181                 if rostermanager.unsubscribed(node, host, to_bare) then\r
182                         rostermanager.roster_push(node, host, to_bare);\r
183                 end\r
184                 core_route_stanza(origin, stanza);\r
185         end\r
186         stanza.attr.from, stanza.attr.to = st_from, st_to;\r
187 end\r
188 \r
189 function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)\r
190         local node, host = jid_split(to_bare);\r
191         local st_from, st_to = stanza.attr.from, stanza.attr.to;\r
192         stanza.attr.from, stanza.attr.to = from_bare, to_bare;\r
193         log("debug", "inbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);\r
194         if stanza.attr.type == "probe" then\r
195                 if rostermanager.is_contact_subscribed(node, host, from_bare) then\r
196                         if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then\r
197                                 -- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)\r
198                         end\r
199                 else\r
200                         core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));\r
201                 end\r
202         elseif stanza.attr.type == "subscribe" then\r
203                 if rostermanager.is_contact_subscribed(node, host, from_bare) then\r
204                         core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed\r
205                         -- Sending presence is not clearly stated in the RFC, but it seems appropriate\r
206                         if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then\r
207                                 -- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)\r
208                         end\r
209                 else\r
210                         if not rostermanager.is_contact_pending_in(node, host, from_bare) then\r
211                                 if rostermanager.set_contact_pending_in(node, host, from_bare) then\r
212                                         sessionmanager.send_to_available_resources(node, host, stanza);\r
213                                 end -- TODO else return error, unable to save\r
214                         end\r
215                 end\r
216         elseif stanza.attr.type == "unsubscribe" then\r
217                 if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then\r
218                         rostermanager.roster_push(node, host, from_bare);\r
219                 end\r
220         elseif stanza.attr.type == "subscribed" then\r
221                 if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then\r
222                         rostermanager.roster_push(node, host, from_bare);\r
223                 end\r
224         elseif stanza.attr.type == "unsubscribed" then\r
225                 if rostermanager.process_inbound_subscription_cancellation(node, host, from_bare) then\r
226                         rostermanager.roster_push(node, host, from_bare);\r
227                 end\r
228         end -- discard any other type\r
229         stanza.attr.from, stanza.attr.to = st_from, st_to;\r
230 end\r
231 \r
232 local function presence_handler(data)\r
233         local origin, stanza = data.origin, data.stanza;\r
234         local to = stanza.attr.to;\r
235         local node, host = jid_split(to);\r
236         local to_bare = jid_bare(to);\r
237         local from_bare = jid_bare(stanza.attr.from);\r
238         if origin.type == "c2s" then\r
239                 if to ~= nil and not(origin.roster[to_bare] and (origin.roster[to_bare].subscription == "both" or origin.roster[to_bare].subscription == "from")) then -- directed presence\r
240                         origin.directed = origin.directed or {};\r
241                         origin.directed[to] = true;\r
242                 end\r
243                 if to == nil and stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then\r
244                         handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);\r
245                 elseif not to then\r
246                         handle_normal_presence(origin, stanza);\r
247                 else\r
248                         core_route_stanza(origin, stanza);\r
249                 end\r
250         elseif (origin.type == "s2sin" or origin.type == "component") and hosts[host] then\r
251                 if to == nil and stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then\r
252                         handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);\r
253                 else\r
254                         core_route_stanza(origin, stanza);\r
255                 end\r
256         end\r
257 end\r
258 \r
259 local add_handler = require "core.eventmanager2".add_handler;\r
260 local remove_handler = require "core.eventmanager2".remove_handler;\r
261 \r
262 add_handler(module:get_host().."/presence", presence_handler);\r
263 module.unload = function()\r
264         remove_handler(module:get_host().."/presence", presence_handler);\r
265 end\r
266 \r
267 return _M;\r