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