2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
9 local log = module
._log
;
11 local require
= require
;
13 local s_find
= string.find
;
14 local tonumber = tonumber;
16 local core_post_stanza
= prosody
.core_post_stanza
;
17 local st
= require
"util.stanza";
18 local jid_split
= require
"util.jid".split
;
19 local jid_bare
= require
"util.jid".bare
;
20 local datetime
= require
"util.datetime";
21 local hosts
= prosody
.hosts
;
22 local bare_sessions
= prosody
.bare_sessions
;
23 local full_sessions
= prosody
.full_sessions
;
26 local rostermanager
= require
"core.rostermanager";
27 local sessionmanager
= require
"core.sessionmanager";
29 local recalc_resource_map
= require
"util.presence".recalc_resource_map
;
31 local ignore_presence_priority
= module
:get_option_boolean("ignore_presence_priority", false);
33 function handle_normal_presence(origin
, stanza
)
34 if ignore_presence_priority
then
35 local priority
= stanza
:get_child("priority");
36 if priority
and priority
[1] ~= "0" then
37 for i
=#priority
.tags
,1,-1 do priority
.tags
[i
] = nil; end
38 for i
=#priority
,2,-1 do priority
[i
] = nil; end
42 local priority
= stanza
:get_child_text("priority");
43 if priority
and s_find(priority
, "^[+-]?[0-9]+$") then
44 priority
= tonumber(priority
);
45 if priority
< -128 then priority
= -128 end
46 if priority
> 127 then priority
= 127 end
47 else priority
= 0; end
48 if full_sessions
[origin
.full_jid
] then -- if user is still connected
49 origin
.send(stanza
); -- reflect their presence back to them
51 local roster
= origin
.roster
;
52 local node
, host
= origin
.username
, origin
.host
;
53 local user
= bare_sessions
[node
.."@"..host
];
54 for _
, res
in pairs(user
and user
.sessions
or NULL
) do -- broadcast to all resources
55 if res
~= origin
and res
.presence
then -- to resource
56 stanza
.attr
.to
= res
.full_jid
;
57 core_post_stanza(origin
, stanza
, true);
60 for jid
, item
in pairs(roster
) do -- broadcast to all interested contacts
61 if item
.subscription
== "both" or item
.subscription
== "from" then
63 core_post_stanza(origin
, stanza
, true);
67 if stanza
.attr
.type == nil and not origin
.presence
then -- initial presence
68 module
:fire_event("presence/initial", { origin
= origin
, stanza
= stanza
} );
69 origin
.presence
= stanza
; -- FIXME repeated later
70 local probe
= st
.presence({from
= origin
.full_jid
, type = "probe"});
71 for jid
, item
in pairs(roster
) do -- probe all contacts we are subscribed to
72 if item
.subscription
== "both" or item
.subscription
== "to" then
74 core_post_stanza(origin
, probe
, true);
77 for _
, res
in pairs(user
and user
.sessions
or NULL
) do -- broadcast from all available resources
78 if res
~= origin
and res
.presence
then
79 res
.presence
.attr
.to
= origin
.full_jid
;
80 core_post_stanza(res
, res
.presence
, true);
81 res
.presence
.attr
.to
= nil;
84 for jid
, pending_request
in pairs(roster
[false].pending
) do -- resend incoming subscription requests
85 if type(pending_request
) == "table" then
86 local subscribe
= st
.deserialize(pending_request
);
87 subscribe
.attr
.type, subscribe
.attr
.from
= "subscribe", jid
;
88 origin
.send(subscribe
);
90 origin
.send(st
.presence({type="subscribe", from
=jid
}));
93 local request
= st
.presence({type="subscribe", from
=origin
.username
.."@"..origin
.host
});
94 for jid
, item
in pairs(roster
) do -- resend outgoing subscription requests
96 request
.attr
.to
= jid
;
97 core_post_stanza(origin
, request
, true);
101 if priority
>= 0 then
102 local event
= { origin
= origin
}
103 module
:fire_event('message/offline/broadcast', event
);
106 if stanza
.attr
.type == "unavailable" then
107 origin
.presence
= nil;
108 if origin
.priority
then
109 origin
.priority
= nil;
110 recalc_resource_map(user
);
112 if origin
.directed
then
113 for jid
in pairs(origin
.directed
) do
114 stanza
.attr
.to
= jid
;
115 core_post_stanza(origin
, stanza
, true);
117 origin
.directed
= nil;
120 origin
.presence
= stanza
;
121 stanza
:tag("delay", { xmlns
= "urn:xmpp:delay", from
= host
, stamp
= datetime
.datetime() }):up();
122 if origin
.priority
~= priority
then
123 origin
.priority
= priority
;
124 recalc_resource_map(user
);
127 stanza
.attr
.to
= nil; -- reset it
130 -- luacheck: ignore 212/recipient_session
131 -- TODO This argument is used in 3rd party modules
132 function send_presence_of_available_resources(user
, host
, jid
, recipient_session
, stanza
)
133 local h
= hosts
[host
];
135 if h
and h
.type == "local" then
136 local u
= h
.sessions
[user
];
138 for _
, session
in pairs(u
.sessions
) do
139 local pres
= session
.presence
;
141 if stanza
then pres
= stanza
; pres
.attr
.from
= session
.full_jid
; end
143 core_post_stanza(session
, pres
, true);
150 log("debug", "broadcasted presence of %d resources from %s@%s to %s", count
, user
, host
, jid
);
154 function handle_outbound_presence_subscriptions_and_probes(origin
, stanza
, from_bare
, to_bare
)
155 local node
, host
= jid_split(from_bare
);
156 if to_bare
== from_bare
then return; end -- No self contacts
157 local st_from
, st_to
= stanza
.attr
.from
, stanza
.attr
.to
;
158 stanza
.attr
.from
, stanza
.attr
.to
= from_bare
, to_bare
;
159 log("debug", "outbound presence %s from %s for %s", stanza
.attr
.type, from_bare
, to_bare
);
160 if stanza
.attr
.type == "probe" then
161 stanza
.attr
.from
, stanza
.attr
.to
= st_from
, st_to
;
163 elseif stanza
.attr
.type == "subscribe" then
165 -- 2. roster push (subscription = none, ask = subscribe)
166 if rostermanager
.set_contact_pending_out(node
, host
, to_bare
) then
167 rostermanager
.roster_push(node
, host
, to_bare
);
168 end -- else file error
169 core_post_stanza(origin
, stanza
);
170 elseif stanza
.attr
.type == "unsubscribe" then
172 -- 2. roster push (subscription = none or from)
173 if rostermanager
.unsubscribe(node
, host
, to_bare
) then
174 rostermanager
.roster_push(node
, host
, to_bare
); -- FIXME do roster push when roster has in fact not changed?
175 end -- else file error
176 core_post_stanza(origin
, stanza
);
177 elseif stanza
.attr
.type == "subscribed" then
180 -- 3. send_presence_of_available_resources
181 if rostermanager
.subscribed(node
, host
, to_bare
) then
182 rostermanager
.roster_push(node
, host
, to_bare
);
184 core_post_stanza(origin
, stanza
);
185 send_presence_of_available_resources(node
, host
, to_bare
, origin
);
186 if rostermanager
.is_user_subscribed(node
, host
, to_bare
) then
187 core_post_stanza(origin
, st
.presence({ type = "probe", from
= from_bare
, to
= to_bare
}));
189 elseif stanza
.attr
.type == "unsubscribed" then
190 -- 1. send unavailable
192 -- 3. roster push (subscription = from or both)
193 local success
, pending_in
, subscribed
= rostermanager
.unsubscribed(node
, host
, to_bare
);
196 rostermanager
.roster_push(node
, host
, to_bare
);
198 core_post_stanza(origin
, stanza
);
200 send_presence_of_available_resources(node
, host
, to_bare
, origin
, st
.presence({ type = "unavailable" }));
204 origin
.send(st
.error_reply(stanza
, "modify", "bad-request", "Invalid presence type"));
206 stanza
.attr
.from
, stanza
.attr
.to
= st_from
, st_to
;
210 function handle_inbound_presence_subscriptions_and_probes(origin
, stanza
, from_bare
, to_bare
)
211 local node
, host
= jid_split(to_bare
);
212 local st_from
, st_to
= stanza
.attr
.from
, stanza
.attr
.to
;
213 stanza
.attr
.from
, stanza
.attr
.to
= from_bare
, to_bare
;
214 log("debug", "inbound presence %s from %s for %s", stanza
.attr
.type, from_bare
, to_bare
);
216 if stanza
.attr
.type == "probe" then
217 local result
, err
= rostermanager
.is_contact_subscribed(node
, host
, from_bare
);
219 if 0 == send_presence_of_available_resources(node
, host
, st_from
, origin
) then
220 core_post_stanza(hosts
[host
], st
.presence({from
=to_bare
, to
=st_from
, type="unavailable"}), true); -- TODO send last activity
223 core_post_stanza(hosts
[host
], st
.presence({from
=to_bare
, to
=from_bare
, type="unsubscribed"}), true);
225 elseif stanza
.attr
.type == "subscribe" then
226 if rostermanager
.is_contact_subscribed(node
, host
, from_bare
) then
227 core_post_stanza(hosts
[host
], st
.presence({from
=to_bare
, to
=from_bare
, type="subscribed"}), true); -- already subscribed
228 -- Sending presence is not clearly stated in the RFC, but it seems appropriate
229 if 0 == send_presence_of_available_resources(node
, host
, from_bare
, origin
) then
230 core_post_stanza(hosts
[host
], st
.presence({from
=to_bare
, to
=from_bare
, type="unavailable"}), true); -- TODO send last activity
233 core_post_stanza(hosts
[host
], st
.presence({from
=to_bare
, to
=from_bare
, type="unavailable"}), true); -- acknowledging receipt
234 if not rostermanager
.is_contact_pending_in(node
, host
, from_bare
) then
235 if rostermanager
.set_contact_pending_in(node
, host
, from_bare
, stanza
) then
236 sessionmanager
.send_to_available_resources(node
, host
, stanza
);
237 end -- TODO else return error, unable to save
240 elseif stanza
.attr
.type == "unsubscribe" then
241 if rostermanager
.process_inbound_unsubscribe(node
, host
, from_bare
) then
242 sessionmanager
.send_to_interested_resources(node
, host
, stanza
);
243 rostermanager
.roster_push(node
, host
, from_bare
);
245 elseif stanza
.attr
.type == "subscribed" then
246 if rostermanager
.process_inbound_subscription_approval(node
, host
, from_bare
) then
247 sessionmanager
.send_to_interested_resources(node
, host
, stanza
);
248 rostermanager
.roster_push(node
, host
, from_bare
);
250 elseif stanza
.attr
.type == "unsubscribed" then
251 if rostermanager
.process_inbound_subscription_cancellation(node
, host
, from_bare
) then
252 sessionmanager
.send_to_interested_resources(node
, host
, stanza
);
253 rostermanager
.roster_push(node
, host
, from_bare
);
256 origin
.send(st
.error_reply(stanza
, "modify", "bad-request", "Invalid presence type"));
258 stanza
.attr
.from
, stanza
.attr
.to
= st_from
, st_to
;
262 local outbound_presence_handler
= function(data
)
263 -- outbound presence received
264 local origin
, stanza
= data
.origin
, data
.stanza
;
266 local to
= stanza
.attr
.to
;
268 local t
= stanza
.attr
.type;
269 if t
~= nil and t
~= "unavailable" and t
~= "error" then -- check for subscriptions and probes
270 return handle_outbound_presence_subscriptions_and_probes(origin
, stanza
, jid_bare(stanza
.attr
.from
), jid_bare(stanza
.attr
.to
));
273 local to_bare
= jid_bare(to
);
274 local roster
= origin
.roster
;
275 if roster
and not(roster
[to_bare
] and (roster
[to_bare
].subscription
== "both" or roster
[to_bare
].subscription
== "from")) then -- directed presence
276 origin
.directed
= origin
.directed
or {};
277 if t
then -- removing from directed presence list on sending an error or unavailable
278 origin
.directed
[to
] = nil; -- FIXME does it make more sense to add to_bare rather than to?
280 origin
.directed
[to
] = true; -- FIXME does it make more sense to add to_bare rather than to?
283 end -- TODO maybe handle normal presence here, instead of letting it pass to incoming handlers?
286 module
:hook("pre-presence/full", outbound_presence_handler
);
287 module
:hook("pre-presence/bare", outbound_presence_handler
);
288 module
:hook("pre-presence/host", outbound_presence_handler
);
290 module
:hook("presence/bare", function(data
)
291 -- inbound presence to bare JID received
292 local origin
, stanza
= data
.origin
, data
.stanza
;
294 local to
= stanza
.attr
.to
;
295 local t
= stanza
.attr
.type;
297 if t
~= nil and t
~= "unavailable" and t
~= "error" then -- check for subscriptions and probes sent to bare JID
298 return handle_inbound_presence_subscriptions_and_probes(origin
, stanza
, jid_bare(stanza
.attr
.from
), jid_bare(stanza
.attr
.to
));
301 local user
= bare_sessions
[to
];
303 for _
, session
in pairs(user
.sessions
) do
304 if session
.presence
then -- only send to available resources
305 session
.send(stanza
);
308 end -- no resources not online, discard
309 elseif not t
or t
== "unavailable" then
310 handle_normal_presence(origin
, stanza
);
312 origin
.send(st
.error_reply(stanza
, "modify", "bad-request", "Invalid presence type"));
316 module
:hook("presence/full", function(data
)
317 -- inbound presence to full JID received
318 local origin
, stanza
= data
.origin
, data
.stanza
;
320 local t
= stanza
.attr
.type;
321 if t
~= nil and t
~= "unavailable" and t
~= "error" then -- check for subscriptions and probes sent to full JID
322 return handle_inbound_presence_subscriptions_and_probes(origin
, stanza
, jid_bare(stanza
.attr
.from
), jid_bare(stanza
.attr
.to
));
325 local session
= full_sessions
[stanza
.attr
.to
];
327 -- TODO fire post processing event
328 session
.send(stanza
);
329 end -- resource not online, discard
332 module
:hook("presence/host", function(data
)
333 -- inbound presence to the host
334 local stanza
= data
.stanza
;
336 local from_bare
= jid_bare(stanza
.attr
.from
);
337 local t
= stanza
.attr
.type;
339 core_post_stanza(hosts
[module
.host
], st
.presence({ from
= module
.host
, to
= from_bare
, id
= stanza
.attr
.id
}));
340 elseif t
== "subscribe" then
341 core_post_stanza(hosts
[module
.host
], st
.presence({ from
= module
.host
, to
= from_bare
, id
= stanza
.attr
.id
, type = "subscribed" }));
342 core_post_stanza(hosts
[module
.host
], st
.presence({ from
= module
.host
, to
= from_bare
, id
= stanza
.attr
.id
}));
347 module
:hook("resource-unbind", function(event
)
348 local session
, err
= event
.session
, event
.error;
349 -- Send unavailable presence
350 if session
.presence
then
351 local pres
= st
.presence
{ type = "unavailable" };
353 pres
:tag("status"):text("Disconnected: "..err
):up();
355 session
:dispatch_stanza(pres
);
356 elseif session
.directed
then
357 local pres
= st
.presence
{ type = "unavailable", from
= session
.full_jid
};
359 pres
:tag("status"):text("Disconnected: "..err
):up();
361 for jid
in pairs(session
.directed
) do
363 core_post_stanza(session
, pres
, true);
365 session
.directed
= nil;
369 module
:hook("roster-item-removed", function (event
)
370 local username
= event
.username
;
371 local session
= event
.origin
;
372 local roster
= event
.roster
or session
and session
.roster
;
373 local jid
= event
.jid
;
374 local item
= event
.item
;
375 local from_jid
= session
.full_jid
or (username
.. "@" .. module
.host
);
377 local subscription
= item
and item
.subscription
or "none";
378 local ask
= item
and item
.ask
;
379 local pending
= roster
and roster
[false].pending
[jid
];
381 if subscription
== "both" or subscription
== "from" or pending
then
382 core_post_stanza(session
, st
.presence({type="unsubscribed", from
=from_jid
, to
=jid
}));
385 if subscription
== "both" or subscription
== "to" or ask
then
386 send_presence_of_available_resources(username
, module
.host
, jid
, session
, st
.presence({type="unavailable"}));
387 core_post_stanza(session
, st
.presence({type="unsubscribe", from
=from_jid
, to
=jid
}));