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