2 -- Copyright (C) 2009-2014 Matthew Wild
3 -- Copyright (C) 2009-2014 Waqas Hussain
4 -- Copyright (C) 2009 Thilo Cestonaro
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
10 module
:add_feature("jabber:iq:privacy");
12 local st
= require
"util.stanza";
13 local bare_sessions
, full_sessions
= prosody
.bare_sessions
, prosody
.full_sessions
;
14 local util_Jid
= require
"util.jid";
15 local jid_bare
= util_Jid
.bare
;
16 local jid_split
, jid_join
= util_Jid
.split
, util_Jid
.join
;
17 local load_roster
= require
"core.rostermanager".load_roster
;
18 local to_number
= tonumber;
20 local privacy_storage
= module
:open_store("privacy");
21 local user_sessions
= hosts
[module
.host
].sessions
;
23 local function get_lists(username
)
24 return user_sessions
[username
].privacy_lists
;
27 local function save_lists(username
)
28 local privacy_lists
= user_sessions
[username
].privacy_lists
;
29 if privacy_lists
.default
== nil and next(privacy_lists
.lists
) == nil then
32 return privacy_storage
:set(username
, privacy_lists
);
35 module
:hook("resource-bind", function (event
)
36 local username
= event
.session
.username
;
37 user_sessions
[username
].privacy_lists
= privacy_storage
:get(username
) or { lists
= {} };
40 function isListUsed(origin
, name
, privacy_lists
)
41 local user
= bare_sessions
[origin
.username
.."@"..origin
.host
];
43 for resource
, session
in pairs(user
.sessions
) do
44 if resource
~= origin
.resource
then
45 if session
.activePrivacyList
== name
then
47 elseif session
.activePrivacyList
== nil and privacy_lists
.default
== name
then
55 function isAnotherSessionUsingDefaultList(origin
)
56 local user
= bare_sessions
[origin
.username
.."@"..origin
.host
];
58 for resource
, session
in pairs(user
.sessions
) do
59 if resource
~= origin
.resource
and session
.activePrivacyList
== nil then
66 function declineList(privacy_lists
, origin
, stanza
, which
)
67 if which
== "default" then
68 if isAnotherSessionUsingDefaultList(origin
) then
69 return { "cancel", "conflict", "Another session is online and using the default list."};
71 privacy_lists
.default
= nil;
72 origin
.send(st
.reply(stanza
));
73 elseif which
== "active" then
74 origin
.activePrivacyList
= nil;
75 origin
.send(st
.reply(stanza
));
77 return {"modify", "bad-request", "Neither default nor active list specifed to decline."};
82 function activateList(privacy_lists
, origin
, stanza
, which
, name
)
83 local list
= privacy_lists
.lists
[name
];
85 if which
== "default" and list
then
86 if isAnotherSessionUsingDefaultList(origin
) then
87 return {"cancel", "conflict", "Another session is online and using the default list."};
89 privacy_lists
.default
= name
;
90 origin
.send(st
.reply(stanza
));
91 elseif which
== "active" and list
then
92 origin
.activePrivacyList
= name
;
93 origin
.send(st
.reply(stanza
));
95 return {"cancel", "item-not-found", "No such list: "..name
};
97 return {"modify", "bad-request", "No list chosen to be active or default."};
102 function deleteList(privacy_lists
, origin
, stanza
, name
)
103 local list
= privacy_lists
.lists
[name
];
106 if isListUsed(origin
, name
, privacy_lists
) then
107 return {"cancel", "conflict", "Another session is online and using the list which should be deleted."};
109 if privacy_lists
.default
== name
then
110 privacy_lists
.default
= nil;
112 if origin
.activePrivacyList
== name
then
113 origin
.activePrivacyList
= nil;
115 privacy_lists
.lists
[name
] = nil;
116 origin
.send(st
.reply(stanza
));
119 return {"modify", "bad-request", "Not existing list specifed to be deleted."};
122 function createOrReplaceList (privacy_lists
, origin
, stanza
, name
, entries
)
123 local bare_jid
= origin
.username
.."@"..origin
.host
;
125 if privacy_lists
.lists
== nil then
126 privacy_lists
.lists
= {};
130 privacy_lists
.lists
[name
] = list
;
132 local orderCheck
= {};
136 for _
,item
in ipairs(entries
) do
137 if to_number(item
.attr
.order
) == nil or to_number(item
.attr
.order
) < 0 or orderCheck
[item
.attr
.order
] ~= nil then
138 return {"modify", "bad-request", "Order attribute not valid."};
141 if item
.attr
.type ~= nil and item
.attr
.type ~= "jid" and item
.attr
.type ~= "subscription" and item
.attr
.type ~= "group" then
142 return {"modify", "bad-request", "Type attribute not valid."};
146 orderCheck
[item
.attr
.order
] = true;
148 tmp
["type"] = item
.attr
.type;
149 tmp
["value"] = item
.attr
.value
;
150 tmp
["action"] = item
.attr
.action
;
151 tmp
["order"] = to_number(item
.attr
.order
);
152 tmp
["presence-in"] = false;
153 tmp
["presence-out"] = false;
154 tmp
["message"] = false;
157 if #item
.tags
> 0 then
158 for _
,tag in ipairs(item
.tags
) do
159 tmp
[tag.name
] = true;
163 if tmp
.type == "subscription" then
164 if tmp
.value
~= "both" and
165 tmp
.value
~= "to" and
166 tmp
.value
~= "from" and
167 tmp
.value
~= "none" then
168 return {"cancel", "bad-request", "Subscription value must be both, to, from or none."};
172 if tmp
.action
~= "deny" and tmp
.action
~= "allow" then
173 return {"cancel", "bad-request", "Action must be either deny or allow."};
175 list
.items
[#list
.items
+ 1] = tmp
;
178 table.sort(list
.items
, function(a
, b
) return a
.order
< b
.order
; end);
180 origin
.send(st
.reply(stanza
));
181 if bare_sessions
[bare_jid
] ~= nil then
182 local iq
= st
.iq ( { type = "set", id
="push1" } );
183 iq
:tag ("query", { xmlns
= "jabber:iq:privacy" } );
184 iq
:tag ("list", { name
= list
.name
} ):up();
186 for resource
, session
in pairs(bare_sessions
[bare_jid
].sessions
) do
187 iq
.attr
.to
= bare_jid
.."/"..resource
191 return {"cancel", "bad-request", "internal error."};
196 function getList(privacy_lists
, origin
, stanza
, name
)
197 local reply
= st
.reply(stanza
);
198 reply
:tag("query", {xmlns
="jabber:iq:privacy"});
201 if privacy_lists
.lists
then
202 if origin
.activePrivacyList
then
203 reply
:tag("active", {name
=origin
.activePrivacyList
}):up();
205 if privacy_lists
.default
then
206 reply
:tag("default", {name
=privacy_lists
.default
}):up();
208 for name
,list
in pairs(privacy_lists
.lists
) do
209 reply
:tag("list", {name
=name
}):up();
213 local list
= privacy_lists
.lists
[name
];
215 reply
= reply
:tag("list", {name
=list
.name
});
216 for _
,item
in ipairs(list
.items
) do
217 reply
:tag("item", {type=item
.type, value
=item
.value
, action
=item
.action
, order
=("%d"):format(item
.order
)});
218 if item
["message"] then reply
:tag("message"):up(); end
219 if item
["iq"] then reply
:tag("iq"):up(); end
220 if item
["presence-in"] then reply
:tag("presence-in"):up(); end
221 if item
["presence-out"] then reply
:tag("presence-out"):up(); end
225 return {"cancel", "item-not-found", "Unknown list specified."};
233 module
:hook("iq/bare/jabber:iq:privacy:query", function(data
)
234 local origin
, stanza
= data
.origin
, data
.stanza
;
236 if stanza
.attr
.to
== nil then -- only service requests to own bare JID
237 local query
= stanza
.tags
[1]; -- the query element
239 local privacy_lists
= get_lists(origin
.username
);
241 if privacy_lists
.lists
[1] then -- Code to migrate from old privacy lists format, remove in 0.8
242 module
:log("info", "Upgrading format of stored privacy lists for %s@%s", origin
.username
, origin
.host
);
243 local lists
= privacy_lists
.lists
;
244 for idx
, list
in ipairs(lists
) do
245 lists
[list
.name
] = list
;
250 if stanza
.attr
.type == "set" then
251 if #query
.tags
== 1 then -- the <query/> element MUST NOT include more than one child element
252 for _
,tag in ipairs(query
.tags
) do
253 if tag.name
== "active" or tag.name
== "default" then
254 if tag.attr
.name
== nil then -- Client declines the use of active / default list
255 valid
= declineList(privacy_lists
, origin
, stanza
, tag.name
);
256 else -- Client requests change of active / default list
257 valid
= activateList(privacy_lists
, origin
, stanza
, tag.name
, tag.attr
.name
);
259 elseif tag.name
== "list" and tag.attr
.name
then -- Client adds / edits a privacy list
260 if #tag.tags
== 0 then -- Client removes a privacy list
261 valid
= deleteList(privacy_lists
, origin
, stanza
, tag.attr
.name
);
262 else -- Client edits a privacy list
263 valid
= createOrReplaceList(privacy_lists
, origin
, stanza
, tag.attr
.name
, tag.tags
);
268 elseif stanza
.attr
.type == "get" then
270 local listsToRetrieve
= 0;
271 if #query
.tags
>= 1 then
272 for _
,tag in ipairs(query
.tags
) do
273 if tag.name
== "list" then -- Client requests a privacy list from server
274 name
= tag.attr
.name
;
275 listsToRetrieve
= listsToRetrieve
+ 1;
279 if listsToRetrieve
== 0 or listsToRetrieve
== 1 then
280 valid
= getList(privacy_lists
, origin
, stanza
, name
);
284 if valid
~= true then
285 valid
= valid
or { "cancel", "bad-request", "Couldn't understand request" };
286 if valid
[1] == nil then
289 if valid
[2] == nil then
290 valid
[2] = "bad-request";
292 origin
.send(st
.error_reply(stanza
, valid
[1], valid
[2], valid
[3]));
294 save_lists(origin
.username
);
300 function checkIfNeedToBeBlocked(e
, session
)
301 local origin
, stanza
= e
.origin
, e
.stanza
;
302 local user
= user_sessions
[session
.username
];
303 local privacy_lists
= user
and user
.privacy_lists
;
304 local bare_jid
= session
.username
.."@"..session
.host
;
305 local to
= stanza
.attr
.to
or bare_jid
;
306 local from
= stanza
.attr
.from
;
308 local is_to_user
= bare_jid
== jid_bare(to
);
309 local is_from_user
= bare_jid
== jid_bare(from
);
311 --module:log("debug", "stanza: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from));
313 if not privacy_lists
or privacy_lists
.lists
== nil or
314 not (session
.activePrivacyList
or privacy_lists
.default
)
316 return; -- Nothing to block, default is Allow all
318 if is_from_user
and is_to_user
then
319 --module:log("debug", "Not blocking communications between user's resources");
320 return; -- from one of a user's resource to another => HANDS OFF!
323 local listname
= session
.activePrivacyList
;
324 if listname
== nil then
325 listname
= privacy_lists
.default
; -- no active list selected, use default list
327 local list
= privacy_lists
.lists
[listname
];
328 if not list
then -- should never happen
329 module
:log("warn", "given privacy list not found. name: %s for user %s", listname
, bare_jid
);
332 for _
,item
in ipairs(list
.items
) do
336 (stanza
.name
== "message" and item
.message
) or
337 (stanza
.name
== "iq" and item
.iq
) or
338 (stanza
.name
== "presence" and is_to_user
and item
["presence-in"]) or
339 (stanza
.name
== "presence" and is_from_user
and item
["presence-out"]) or
340 (item
.message
== false and item
.iq
== false and item
["presence-in"] == false and item
["presence-out"] == false)
348 --module:log("debug", "evil jid is (from): %s", from);
349 evilJid
.node
, evilJid
.host
, evilJid
.resource
= jid_split(from
);
351 --module:log("debug", "evil jid is (to): %s", to);
352 evilJid
.node
, evilJid
.host
, evilJid
.resource
= jid_split(to
);
354 if item
.type == "jid" and
355 (evilJid
.node
and evilJid
.host
and evilJid
.resource
and item
.value
== evilJid
.node
.."@"..evilJid
.host
.."/"..evilJid
.resource
) or
356 (evilJid
.node
and evilJid
.host
and item
.value
== evilJid
.node
.."@"..evilJid
.host
) or
357 (evilJid
.host
and evilJid
.resource
and item
.value
== evilJid
.host
.."/"..evilJid
.resource
) or
358 (evilJid
.host
and item
.value
== evilJid
.host
) then
360 block
= (item
.action
== "deny");
361 elseif item
.type == "group" then
362 local roster
= load_roster(session
.username
, session
.host
);
363 local roster_entry
= roster
[jid_join(evilJid
.node
, evilJid
.host
)];
365 local groups
= roster_entry
.groups
;
366 for group
in pairs(groups
) do
367 if group
== item
.value
then
369 block
= (item
.action
== "deny");
374 elseif item
.type == "subscription" then -- we need a valid bare evil jid
375 local roster
= load_roster(session
.username
, session
.host
);
376 local roster_entry
= roster
[jid_join(evilJid
.node
, evilJid
.host
)];
377 if (not(roster_entry
) and item
.value
== "none")
378 or (roster_entry
and roster_entry
.subscription
== item
.value
) then
380 block
= (item
.action
== "deny");
382 elseif item
.type == nil then
384 block
= (item
.action
== "deny");
389 -- drop and not bounce groupchat messages, otherwise users will get kicked
390 if stanza
.attr
.type == "groupchat" then
393 module
:log("debug", "stanza blocked: %s, to: %s, from: %s", tostring(stanza
.name
), tostring(to
), tostring(from
));
394 if stanza
.name
== "message" then
395 origin
.send(st
.error_reply(stanza
, "cancel", "service-unavailable"));
396 elseif stanza
.name
== "iq" and (stanza
.attr
.type == "get" or stanza
.attr
.type == "set") then
397 origin
.send(st
.error_reply(stanza
, "cancel", "service-unavailable"));
399 return true; -- stanza blocked !
401 --module:log("debug", "stanza explicitly allowed!")
408 function preCheckIncoming(e
)
410 if e
.stanza
.attr
.to
~= nil then
411 local node
, host
, resource
= jid_split(e
.stanza
.attr
.to
);
412 if node
== nil or host
== nil then
415 if resource
== nil then
417 if bare_sessions
[node
.."@"..host
] ~= nil then
418 for resource
, session_
in pairs(bare_sessions
[node
.."@"..host
].sessions
) do
419 if session_
.priority
~= nil and session_
.priority
>= prio
then
421 prio
= session_
.priority
;
426 session
= full_sessions
[node
.."@"..host
.."/"..resource
];
428 if session
~= nil then
429 return checkIfNeedToBeBlocked(e
, session
);
431 --module:log("debug", "preCheckIncoming: Couldn't get session for jid: %s@%s/%s", tostring(node), tostring(host), tostring(resource));
436 function preCheckOutgoing(e
)
437 local session
= e
.origin
;
438 if e
.stanza
.attr
.from
== nil then
439 e
.stanza
.attr
.from
= session
.username
.. "@" .. session
.host
;
440 if session
.resource
~= nil then
441 e
.stanza
.attr
.from
= e
.stanza
.attr
.from
.. "/" .. session
.resource
;
444 if session
.username
then -- FIXME do properly
445 return checkIfNeedToBeBlocked(e
, session
);
449 module
:hook("pre-message/full", preCheckOutgoing
, 500);
450 module
:hook("pre-message/bare", preCheckOutgoing
, 500);
451 module
:hook("pre-message/host", preCheckOutgoing
, 500);
452 module
:hook("pre-iq/full", preCheckOutgoing
, 500);
453 module
:hook("pre-iq/bare", preCheckOutgoing
, 500);
454 module
:hook("pre-iq/host", preCheckOutgoing
, 500);
455 module
:hook("pre-presence/full", preCheckOutgoing
, 500);
456 module
:hook("pre-presence/bare", preCheckOutgoing
, 500);
457 module
:hook("pre-presence/host", preCheckOutgoing
, 500);
459 module
:hook("message/full", preCheckIncoming
, 500);
460 module
:hook("message/bare", preCheckIncoming
, 500);
461 module
:hook("message/host", preCheckIncoming
, 500);
462 module
:hook("iq/full", preCheckIncoming
, 500);
463 module
:hook("iq/bare", preCheckIncoming
, 500);
464 module
:hook("iq/host", preCheckIncoming
, 500);
465 module
:hook("presence/full", preCheckIncoming
, 500);
466 module
:hook("presence/bare", preCheckIncoming
, 500);
467 module
:hook("presence/host", preCheckIncoming
, 500);