1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : mod_privacy.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : jabber:iq:privacy support |
5 |
|
%%% Created : 21 Jul 2003 by Alexey Shchepin <alexey@process-one.net> |
6 |
|
%%% |
7 |
|
%%% |
8 |
|
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne |
9 |
|
%%% |
10 |
|
%%% This program is free software; you can redistribute it and/or |
11 |
|
%%% modify it under the terms of the GNU General Public License as |
12 |
|
%%% published by the Free Software Foundation; either version 2 of the |
13 |
|
%%% License, or (at your option) any later version. |
14 |
|
%%% |
15 |
|
%%% This program is distributed in the hope that it will be useful, |
16 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 |
|
%%% General Public License for more details. |
19 |
|
%%% |
20 |
|
%%% You should have received a copy of the GNU General Public License |
21 |
|
%%% along with this program; if not, write to the Free Software |
22 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
|
%%% |
24 |
|
%%%---------------------------------------------------------------------- |
25 |
|
|
26 |
|
-module(mod_privacy). |
27 |
|
-author('alexey@process-one.net'). |
28 |
|
-xep([{xep, 16}, {version, "1.6"}]). |
29 |
|
-xep([{xep, 126}, {version, "1.1"}]). |
30 |
|
-behaviour(gen_mod). |
31 |
|
-behaviour(mongoose_module_metrics). |
32 |
|
|
33 |
|
%% gen_mod |
34 |
|
-export([start/2]). |
35 |
|
-export([stop/1]). |
36 |
|
-export([supported_features/0]). |
37 |
|
-export([config_spec/0]). |
38 |
|
|
39 |
|
-export([process_iq_set/4, |
40 |
|
process_iq_get/5, |
41 |
|
get_user_list/3, |
42 |
|
check_packet/5, |
43 |
|
remove_user/3, |
44 |
|
remove_domain/3, |
45 |
|
updated_list/3, |
46 |
|
disco_local_features/1, |
47 |
|
remove_unused_backend_opts/1 |
48 |
|
]). |
49 |
|
|
50 |
|
-export([config_metrics/1]). |
51 |
|
|
52 |
|
-ignore_xref([ |
53 |
|
behaviour_info/1, check_packet/5, get_user_list/3, process_iq_get/5, |
54 |
|
process_iq_set/4, remove_user/3, updated_list/3, |
55 |
|
remove_user/3, remove_domain/3, disco_local_features/1]). |
56 |
|
|
57 |
|
-include("jlib.hrl"). |
58 |
|
-include("mod_privacy.hrl"). |
59 |
|
-include("mongoose_config_spec.hrl"). |
60 |
|
|
61 |
|
-export_type([list_name/0]). |
62 |
|
-export_type([list_item/0]). |
63 |
|
-export_type([privacy_item_type/0]). |
64 |
|
|
65 |
|
-type list_name() :: binary() | none. |
66 |
|
-type list_item() :: #listitem{}. |
67 |
|
-type privacy_item_type() :: none | jid | group | subscription. |
68 |
|
|
69 |
|
%% ------------------------------------------------------------------ |
70 |
|
%% gen_mod callbacks |
71 |
|
%% ------------------------------------------------------------------ |
72 |
|
|
73 |
|
-spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok. |
74 |
|
start(HostType, Opts) when is_map(Opts) -> |
75 |
4 |
mod_privacy_backend:init(HostType, Opts), |
76 |
4 |
ejabberd_hooks:add(hooks(HostType)). |
77 |
|
|
78 |
|
-spec stop(mongooseim:host_type()) -> ok. |
79 |
|
stop(HostType) -> |
80 |
4 |
ejabberd_hooks:delete(hooks(HostType)). |
81 |
|
|
82 |
|
config_spec() -> |
83 |
328 |
#section{ |
84 |
|
items = #{<<"backend">> => #option{type = atom, |
85 |
|
validate = {module, mod_privacy}}, |
86 |
|
<<"riak">> => riak_config_spec()}, |
87 |
|
defaults = #{<<"backend">> => mnesia}, |
88 |
|
format_items = map, |
89 |
|
process = fun ?MODULE:remove_unused_backend_opts/1 |
90 |
|
}. |
91 |
|
|
92 |
|
riak_config_spec() -> |
93 |
328 |
#section{ |
94 |
|
items = #{<<"defaults_bucket_type">> => #option{type = binary, |
95 |
|
validate = non_empty}, |
96 |
|
<<"names_bucket_type">> => #option{type = binary, |
97 |
|
validate = non_empty}, |
98 |
|
<<"bucket_type">> => #option{type = binary, |
99 |
|
validate = non_empty} |
100 |
|
}, |
101 |
|
defaults = #{<<"defaults_bucket_type">> => <<"privacy_defaults">>, |
102 |
|
<<"names_bucket_type">> => <<"privacy_lists_names">>, |
103 |
|
<<"bucket_type">> => <<"privacy_lists">>}, |
104 |
|
format_items = map, |
105 |
|
include = always |
106 |
|
}. |
107 |
|
|
108 |
:-( |
remove_unused_backend_opts(Opts = #{backend := riak}) -> Opts; |
109 |
:-( |
remove_unused_backend_opts(Opts) -> maps:remove(riak, Opts). |
110 |
|
|
111 |
|
-spec supported_features() -> [atom()]. |
112 |
|
supported_features() -> |
113 |
:-( |
[dynamic_domains]. |
114 |
|
|
115 |
|
hooks(HostType) -> |
116 |
8 |
[ |
117 |
|
{disco_local_features, HostType, ?MODULE, disco_local_features, 98}, |
118 |
|
{privacy_iq_get, HostType, ?MODULE, process_iq_get, 50}, |
119 |
|
{privacy_iq_set, HostType, ?MODULE, process_iq_set, 50}, |
120 |
|
{privacy_get_user_list, HostType, ?MODULE, get_user_list, 50}, |
121 |
|
{privacy_check_packet, HostType, ?MODULE, check_packet, 50}, |
122 |
|
{privacy_updated_list, HostType, ?MODULE, updated_list, 50}, |
123 |
|
{remove_user, HostType, ?MODULE, remove_user, 50}, |
124 |
|
{remove_domain, HostType, ?MODULE, remove_domain, 50}, |
125 |
|
{anonymous_purge_hook, HostType, ?MODULE, remove_user, 50} |
126 |
|
]. |
127 |
|
|
128 |
|
%% ------------------------------------------------------------------ |
129 |
|
%% Handlers |
130 |
|
%% ------------------------------------------------------------------ |
131 |
|
|
132 |
|
-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). |
133 |
|
disco_local_features(Acc = #{node := <<>>}) -> |
134 |
3 |
mongoose_disco:add_features([?NS_PRIVACY], Acc); |
135 |
|
disco_local_features(Acc) -> |
136 |
1 |
Acc. |
137 |
|
|
138 |
|
process_iq_get(Acc, |
139 |
|
_From = #jid{luser = LUser, lserver = LServer}, |
140 |
|
_To, |
141 |
|
#iq{xmlns = ?NS_PRIVACY, sub_el = #xmlel{children = Els}}, |
142 |
|
#userlist{name = Active}) -> |
143 |
54 |
HostType = mongoose_acc:host_type(Acc), |
144 |
54 |
Res = case xml:remove_cdata(Els) of |
145 |
|
[] -> |
146 |
3 |
process_lists_get(HostType, LUser, LServer, Active); |
147 |
|
[#xmlel{name = Name, attrs = Attrs}] -> |
148 |
50 |
case Name of |
149 |
|
<<"list">> -> |
150 |
50 |
ListName = xml:get_attr(<<"name">>, Attrs), |
151 |
50 |
process_list_get(HostType, LUser, LServer, ListName); |
152 |
|
_ -> |
153 |
:-( |
{error, mongoose_xmpp_errors:bad_request()} |
154 |
|
end; |
155 |
|
_ -> |
156 |
1 |
{error, mongoose_xmpp_errors:bad_request()} |
157 |
|
end, |
158 |
54 |
mongoose_acc:set(hook, result, Res, Acc); |
159 |
|
process_iq_get(Val, _, _, _, _) -> |
160 |
8 |
Val. |
161 |
|
|
162 |
|
process_lists_get(HostType, LUser, LServer, Active) -> |
163 |
3 |
case mod_privacy_backend:get_list_names(HostType, LUser, LServer) of |
164 |
|
{ok, {Default, ListNames}} -> |
165 |
2 |
{result, [list_names_query(Active, Default, ListNames)]}; |
166 |
|
{error, not_found} -> |
167 |
1 |
{result, [empty_list_names_query()]}; |
168 |
|
{error, _Reason} -> |
169 |
:-( |
{error, mongoose_xmpp_errors:internal_server_error()} |
170 |
|
end. |
171 |
|
|
172 |
|
process_list_get(HostType, LUser, LServer, {value, Name}) -> |
173 |
50 |
case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, Name) of |
174 |
|
{ok, List} -> |
175 |
48 |
LItems = lists:map(fun item_to_xml/1, List), |
176 |
48 |
{result, [list_query_result(Name, LItems)]}; |
177 |
|
{error, not_found} -> |
178 |
2 |
{error, mongoose_xmpp_errors:item_not_found()}; |
179 |
|
{error, _Reason} -> |
180 |
:-( |
{error, mongoose_xmpp_errors:internal_server_error()} |
181 |
|
end; |
182 |
|
process_list_get(_HostType, _LUser, _LServer, false) -> |
183 |
:-( |
{error, mongoose_xmpp_errors:bad_request()}. |
184 |
|
|
185 |
|
process_iq_set(Acc, From, _To, #iq{xmlns = ?NS_PRIVACY, sub_el = SubEl}) -> |
186 |
111 |
#xmlel{children = Els} = SubEl, |
187 |
111 |
HostType = mongoose_acc:host_type(Acc), |
188 |
111 |
Res = case xml:remove_cdata(Els) of |
189 |
|
[#xmlel{name = Name, attrs = Attrs, children = SubEls}] -> |
190 |
111 |
ListName = xml:get_attr(<<"name">>, Attrs), |
191 |
111 |
case Name of |
192 |
|
<<"list">> -> |
193 |
51 |
process_list_set(HostType, From, ListName, |
194 |
|
xml:remove_cdata(SubEls)); |
195 |
|
<<"active">> -> |
196 |
44 |
process_active_set(HostType, From, ListName); |
197 |
|
<<"default">> -> |
198 |
16 |
process_default_set(HostType, From, ListName); |
199 |
|
_ -> |
200 |
:-( |
{error, mongoose_xmpp_errors:bad_request()} |
201 |
|
end; |
202 |
|
_ -> |
203 |
:-( |
{error, mongoose_xmpp_errors:bad_request()} |
204 |
|
end, |
205 |
111 |
mongoose_acc:set(hook, result, Res, Acc); |
206 |
|
process_iq_set(Val, _, _, _) -> |
207 |
28 |
Val. |
208 |
|
|
209 |
|
process_default_set(HostType, #jid{luser = LUser, lserver = LServer}, {value, Name}) -> |
210 |
15 |
case mod_privacy_backend:set_default_list(HostType, LUser, LServer, Name) of |
211 |
|
ok -> |
212 |
14 |
{result, []}; |
213 |
|
{error, not_found} -> |
214 |
1 |
{error, mongoose_xmpp_errors:item_not_found()}; |
215 |
|
{error, _Reason} -> |
216 |
:-( |
{error, mongoose_xmpp_errors:internal_server_error()} |
217 |
|
end; |
218 |
|
process_default_set(HostType, #jid{luser = LUser, lserver = LServer}, false) -> |
219 |
1 |
case mod_privacy_backend:forget_default_list(HostType, LUser, LServer) of |
220 |
|
ok -> |
221 |
1 |
{result, []}; |
222 |
|
{error, _Reason} -> |
223 |
:-( |
{error, mongoose_xmpp_errors:internal_server_error()} |
224 |
|
end. |
225 |
|
|
226 |
|
process_active_set(HostType, #jid{luser = LUser, lserver = LServer}, {value, Name}) -> |
227 |
43 |
case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, Name) of |
228 |
|
{ok, List} -> |
229 |
42 |
NeedDb = is_list_needdb(List), |
230 |
42 |
{result, [], #userlist{name = Name, list = List, needdb = NeedDb}}; |
231 |
|
{error, not_found} -> |
232 |
1 |
{error, mongoose_xmpp_errors:item_not_found()}; |
233 |
|
{error, _Reason} -> |
234 |
:-( |
{error, mongoose_xmpp_errors:internal_server_error()} |
235 |
|
end; |
236 |
|
process_active_set(_HostType, _UserJID, false) -> |
237 |
1 |
{result, [], #userlist{}}. |
238 |
|
|
239 |
|
process_list_set(HostType, UserJID, {value, Name}, Els) -> |
240 |
51 |
case parse_items(Els) of |
241 |
|
false -> |
242 |
:-( |
{error, mongoose_xmpp_errors:bad_request()}; |
243 |
|
remove -> |
244 |
1 |
remove_privacy_list(HostType, UserJID, Name); |
245 |
|
List -> |
246 |
50 |
replace_privacy_list(HostType, UserJID, Name, List) |
247 |
|
end; |
248 |
|
process_list_set(_HostType, _UserJID, false, _Els) -> |
249 |
:-( |
{error, mongoose_xmpp_errors:bad_request()}. |
250 |
|
|
251 |
|
remove_privacy_list(HostType, #jid{luser = LUser, lserver = LServer} = UserJID, Name) -> |
252 |
1 |
case mod_privacy_backend:remove_privacy_list(HostType, LUser, LServer, Name) of |
253 |
|
ok -> |
254 |
1 |
UserList = #userlist{name = Name, list = []}, |
255 |
1 |
broadcast_privacy_list(UserJID, Name, UserList), |
256 |
1 |
{result, []}; |
257 |
|
%% TODO if Name == Active -> conflict |
258 |
|
{error, conflict} -> |
259 |
:-( |
{error, mongoose_xmpp_errors:conflict()}; |
260 |
|
{error, _Reason} -> |
261 |
:-( |
{error, mongoose_xmpp_errors:internal_server_error()} |
262 |
|
end. |
263 |
|
|
264 |
|
replace_privacy_list(HostType, #jid{luser = LUser, lserver = LServer} = UserJID, Name, List) -> |
265 |
50 |
case mod_privacy_backend:replace_privacy_list(HostType, LUser, LServer, Name, List) of |
266 |
|
ok -> |
267 |
50 |
NeedDb = is_list_needdb(List), |
268 |
50 |
UserList = #userlist{name = Name, list = List, needdb = NeedDb}, |
269 |
50 |
broadcast_privacy_list(UserJID, Name, UserList), |
270 |
50 |
{result, []}; |
271 |
|
{error, _Reason} -> |
272 |
:-( |
{error, mongoose_xmpp_errors:internal_server_error()} |
273 |
|
end. |
274 |
|
|
275 |
|
is_list_needdb(Items) -> |
276 |
113 |
lists:any(fun is_item_needdb/1, Items). |
277 |
|
|
278 |
12 |
is_item_needdb(#listitem{type = subscription}) -> true; |
279 |
2 |
is_item_needdb(#listitem{type = group}) -> true; |
280 |
101 |
is_item_needdb(_) -> false. |
281 |
|
|
282 |
|
get_user_list(_, HostType, #jid{luser = LUser, lserver = LServer}) -> |
283 |
502 |
case mod_privacy_backend:get_default_list(HostType, LUser, LServer) of |
284 |
|
{ok, {Default, List}} -> |
285 |
21 |
NeedDb = is_list_needdb(List), |
286 |
21 |
#userlist{name = Default, list = List, needdb = NeedDb}; |
287 |
|
{error, _} -> |
288 |
481 |
#userlist{} |
289 |
|
end. |
290 |
|
|
291 |
|
%% From is the sender, To is the destination. |
292 |
|
%% If Dir = out, User@Server is the sender account (From). |
293 |
|
%% If Dir = in, User@Server is the destination account (To). |
294 |
|
check_packet(Acc, _JID, #userlist{list = []}, _, _Dir) -> |
295 |
2439 |
mongoose_acc:set(hook, result, allow, Acc); |
296 |
|
check_packet(Acc, JID, |
297 |
|
#userlist{list = List, needdb = NeedDb}, |
298 |
|
{From, To, Name, Type}, Dir) -> |
299 |
302 |
HostType = mongoose_acc:host_type(Acc), |
300 |
302 |
PType = packet_directed_type(Dir, packet_type(Name, Type)), |
301 |
302 |
LJID = case Dir of |
302 |
194 |
in -> jid:to_lower(From); |
303 |
108 |
out -> jid:to_lower(To) |
304 |
|
end, |
305 |
302 |
{Subscription, Groups} = |
306 |
|
case NeedDb of |
307 |
|
true -> |
308 |
61 |
roster_get_jid_info(HostType, JID, LJID); |
309 |
|
false -> |
310 |
241 |
{[], []} |
311 |
|
end, |
312 |
302 |
CheckResult = check_packet_aux(List, PType, Type, LJID, Subscription, Groups), |
313 |
302 |
mongoose_acc:set(hook, result, CheckResult, Acc). |
314 |
|
|
315 |
|
%% allow error messages |
316 |
|
check_packet_aux(_, message, <<"error">>, _JID, _Subscription, _Groups) -> |
317 |
8 |
allow; |
318 |
|
%% if we run of of list items then it is allowed |
319 |
|
check_packet_aux([], _PType, _MType, _JID, _Subscription, _Groups) -> |
320 |
224 |
allow; |
321 |
|
%% check packet against next privacy list item |
322 |
|
check_packet_aux([Item | List], PType, MType, JID, Subscription, Groups) -> |
323 |
382 |
#listitem{type = Type, value = Value, action = Action} = Item, |
324 |
382 |
do_check_packet_aux(Type, Action, PType, Value, JID, MType, Subscription, Groups, Item, List). |
325 |
|
|
326 |
|
%% list set by blocking commands (XEP-0191) block all communication, both in and out, |
327 |
|
%% for a given JID |
328 |
|
do_check_packet_aux(jid, block, message, JID, JID, _, _, _, _, _) -> |
329 |
:-( |
block; |
330 |
|
do_check_packet_aux(jid, block, message_out, JID, JID, _, _, _, _, _) -> |
331 |
3 |
block; |
332 |
|
%% then we do more complicated checking |
333 |
|
do_check_packet_aux(Type, Action, PType, Value, JID, MType, Subscription, Groups, Item, List) -> |
334 |
379 |
#listitem{type = Type, value = Value, action = Action} = Item, |
335 |
379 |
case {is_ptype_match(Item, PType), Type} of |
336 |
|
{true, none} -> |
337 |
29 |
Action; |
338 |
|
{true, _} -> |
339 |
185 |
case is_type_match(Type, Value, JID, Subscription, Groups) of |
340 |
|
true -> |
341 |
38 |
Action; |
342 |
|
false -> |
343 |
147 |
check_packet_aux(List, PType, MType, JID, Subscription, Groups) |
344 |
|
end; |
345 |
|
{false, _} -> |
346 |
165 |
check_packet_aux(List, PType, MType, JID, Subscription, Groups) |
347 |
|
end. |
348 |
|
|
349 |
|
is_ptype_match(#listitem{match_all = true}, _PType) -> |
350 |
155 |
true; |
351 |
|
is_ptype_match(Item, message) -> |
352 |
41 |
Item#listitem.match_message; |
353 |
|
is_ptype_match(_Item, message_out) -> |
354 |
17 |
false; % according to xep-0016, privacy lists do not stop outgoing messages (so they say) |
355 |
|
is_ptype_match(Item, iq) -> |
356 |
75 |
Item#listitem.match_iq; |
357 |
|
is_ptype_match(Item, presence_in) -> |
358 |
12 |
Item#listitem.match_presence_in; |
359 |
|
is_ptype_match(Item, presence_out) -> |
360 |
63 |
Item#listitem.match_presence_out; |
361 |
|
is_ptype_match(_Item, other) -> |
362 |
16 |
false. |
363 |
|
|
364 |
|
is_type_match(jid, Value, JID, _Subscription, _Groups) -> |
365 |
172 |
case Value of |
366 |
|
{<<>>, Server, <<>>} -> |
367 |
7 |
case JID of |
368 |
|
{_, Server, _} -> |
369 |
7 |
true; |
370 |
|
_ -> |
371 |
:-( |
false |
372 |
|
end; |
373 |
|
{User, Server, <<>>} -> |
374 |
165 |
case JID of |
375 |
|
{User, Server, _} -> |
376 |
24 |
true; |
377 |
|
_ -> |
378 |
141 |
false |
379 |
|
end; |
380 |
|
_ -> |
381 |
:-( |
Value == JID |
382 |
|
end; |
383 |
|
|
384 |
|
is_type_match(subscription, Value, _JID, Subscription, _Groups) -> |
385 |
12 |
Value == Subscription; |
386 |
|
is_type_match(group, Value, _JID, _Subscription, Groups) -> |
387 |
1 |
lists:member(Value, Groups). |
388 |
|
|
389 |
|
remove_user(Acc, User, Server) -> |
390 |
114 |
LUser = jid:nodeprep(User), |
391 |
114 |
LServer = jid:nameprep(Server), |
392 |
114 |
HostType = mongoose_acc:host_type(Acc), |
393 |
114 |
R = mod_privacy_backend:remove_user(HostType, LUser, LServer), |
394 |
114 |
mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, User, Server}), |
395 |
114 |
Acc. |
396 |
|
|
397 |
|
-spec remove_domain(mongoose_hooks:simple_acc(), |
398 |
|
mongooseim:host_type(), jid:lserver()) -> |
399 |
|
mongoose_hooks:simple_acc(). |
400 |
|
remove_domain(Acc, HostType, Domain) -> |
401 |
:-( |
mod_privacy_backend:remove_domain(HostType, Domain), |
402 |
:-( |
Acc. |
403 |
|
|
404 |
1 |
updated_list(_, #userlist{name = SameName}, #userlist{name = SameName} = New) -> New; |
405 |
52 |
updated_list(_, Old, _) -> Old. |
406 |
|
|
407 |
|
%% ------------------------------------------------------------------ |
408 |
|
%% Deserialization |
409 |
|
%% ------------------------------------------------------------------ |
410 |
|
|
411 |
16 |
packet_directed_type(out, message) -> message_out; |
412 |
58 |
packet_directed_type(in, message) -> message; |
413 |
116 |
packet_directed_type(in, iq) -> iq; |
414 |
16 |
packet_directed_type(in, presence) -> presence_in; |
415 |
88 |
packet_directed_type(out, presence) -> presence_out; |
416 |
8 |
packet_directed_type(_Dir, _Type) -> other. |
417 |
|
|
418 |
74 |
packet_type(<<"message">>, _Type) -> message; |
419 |
116 |
packet_type(<<"iq">>, _Type) -> iq; |
420 |
|
packet_type(<<"presence">>, Type) -> |
421 |
112 |
case Type of |
422 |
|
%% notification |
423 |
28 |
undefined -> presence; |
424 |
76 |
<<"unavailable">> -> presence; |
425 |
|
%% subscribe, subscribed, unsubscribe, |
426 |
|
%% unsubscribed, error, probe, or other |
427 |
8 |
_ -> other |
428 |
|
end. |
429 |
|
|
430 |
|
parse_items([]) -> |
431 |
1 |
remove; |
432 |
|
parse_items(Els) -> |
433 |
50 |
parse_items(Els, []). |
434 |
|
|
435 |
|
parse_items([], Res) -> |
436 |
|
%% Sort the items by their 'order' attribute |
437 |
50 |
lists:keysort(#listitem.order, Res); |
438 |
|
parse_items([#xmlel{name = <<"item">>, attrs = Attrs, |
439 |
|
children = SubEls} | Els], Res) -> |
440 |
57 |
Type = xml:get_attr_s(<<"type">>, Attrs), |
441 |
57 |
Value = xml:get_attr_s(<<"value">>, Attrs), |
442 |
57 |
SAction = xml:get_attr_s(<<"action">>, Attrs), |
443 |
57 |
SOrder = xml:get_attr_s(<<"order">>, Attrs), |
444 |
57 |
Action = parse_action(SAction), |
445 |
57 |
Order = parse_order(SOrder), |
446 |
57 |
I1 = set_action_and_order(Action, Order), |
447 |
57 |
I2 = set_type_and_value(Type, Value, I1), |
448 |
57 |
I3 = set_matches(SubEls, I2), |
449 |
57 |
parse_items(Els, add_item(I3, Res)); |
450 |
|
parse_items(_, _Res) -> |
451 |
:-( |
false. |
452 |
|
|
453 |
|
parse_action(<<>>) -> |
454 |
:-( |
false; |
455 |
|
parse_action(Action) -> |
456 |
57 |
binary_to_action_s(Action). |
457 |
|
|
458 |
|
parse_order(<<>>) -> |
459 |
:-( |
false; |
460 |
|
parse_order(Order) -> |
461 |
57 |
validate_order(binary_to_order_s(Order)). |
462 |
|
|
463 |
|
validate_order(Order) when Order >= 0 -> |
464 |
57 |
Order; |
465 |
|
validate_order(_) -> |
466 |
:-( |
false. |
467 |
|
|
468 |
|
set_action_and_order(false, _) -> |
469 |
:-( |
false; |
470 |
|
set_action_and_order(_, false) -> |
471 |
:-( |
false; |
472 |
|
set_action_and_order(Action, Order) when is_atom(Action), is_integer(Order) -> |
473 |
57 |
#listitem{action = Action, order = Order}. |
474 |
|
|
475 |
|
set_type_and_value(_Type, _Value, false) -> |
476 |
:-( |
false; |
477 |
|
set_type_and_value(<<>>, _Value, Item) -> |
478 |
29 |
Item; |
479 |
|
set_type_and_value(_Type, <<>>, _Item) -> |
480 |
:-( |
false; |
481 |
|
set_type_and_value(<<"jid">>, Value, Item) -> |
482 |
19 |
case jid:from_binary(Value) of |
483 |
|
error -> |
484 |
:-( |
false; |
485 |
|
JID -> |
486 |
19 |
Item#listitem{type = jid, value = jid:to_lower(JID)} |
487 |
|
end; |
488 |
|
set_type_and_value(<<"group">>, Value, Item) -> |
489 |
1 |
Item#listitem{type = group, value = Value}; |
490 |
|
set_type_and_value(<<"subscription">>, Value, Item) -> |
491 |
8 |
case Value of |
492 |
|
<<"none">> -> |
493 |
2 |
Item#listitem{type = subscription, value = none}; |
494 |
|
<<"both">> -> |
495 |
2 |
Item#listitem{type = subscription, value = both}; |
496 |
|
<<"from">> -> |
497 |
2 |
Item#listitem{type = subscription, value = from}; |
498 |
|
<<"to">> -> |
499 |
2 |
Item#listitem{type = subscription, value = to}; |
500 |
|
_ -> |
501 |
:-( |
false |
502 |
|
end. |
503 |
|
|
504 |
|
set_matches(_SubEls, false) -> |
505 |
:-( |
false; |
506 |
|
set_matches(SubEls, Item) -> |
507 |
57 |
parse_matches(Item, xml:remove_cdata(SubEls)). |
508 |
|
|
509 |
|
parse_matches(Item, []) -> |
510 |
12 |
Item#listitem{match_all = true}; |
511 |
|
parse_matches(Item, Els) -> |
512 |
45 |
parse_matches1(Item, Els). |
513 |
|
|
514 |
|
parse_matches1(Item, []) -> |
515 |
45 |
Item; |
516 |
|
parse_matches1(Item, [#xmlel{name = <<"message">>} | Els]) -> |
517 |
37 |
parse_matches1(Item#listitem{match_message = true}, Els); |
518 |
|
parse_matches1(Item, [#xmlel{name = <<"iq">>} | Els]) -> |
519 |
1 |
parse_matches1(Item#listitem{match_iq = true}, Els); |
520 |
|
parse_matches1(Item, [#xmlel{name = <<"presence-in">>} | Els]) -> |
521 |
1 |
parse_matches1(Item#listitem{match_presence_in = true}, Els); |
522 |
|
parse_matches1(Item, [#xmlel{name = <<"presence-out">>} | Els]) -> |
523 |
6 |
parse_matches1(Item#listitem{match_presence_out = true}, Els); |
524 |
|
parse_matches1(_Item, [#xmlel{} | _Els]) -> |
525 |
:-( |
false. |
526 |
|
|
527 |
|
add_item(false, Items) -> |
528 |
:-( |
Items; |
529 |
|
add_item(Item, Items) -> |
530 |
57 |
[Item | Items]. |
531 |
|
|
532 |
|
%% ------------------------------------------------------------------ |
533 |
|
%% Serialization |
534 |
|
%% ------------------------------------------------------------------ |
535 |
|
|
536 |
|
empty_list_names_query() -> |
537 |
1 |
#xmlel{ |
538 |
|
name = <<"query">>, |
539 |
|
attrs = [{<<"xmlns">>, ?NS_PRIVACY}]}. |
540 |
|
|
541 |
|
list_names_query(Active, Default, ListNames) -> |
542 |
2 |
#xmlel{ |
543 |
|
name = <<"query">>, |
544 |
|
attrs = [{<<"xmlns">>, ?NS_PRIVACY}], |
545 |
|
children = list_names(Active, Default, ListNames)}. |
546 |
|
|
547 |
|
list_names(Active, Default, ListNames) -> |
548 |
2 |
[list_name(<<"active">>, Active) || Active =/= none] ++ |
549 |
1 |
[list_name(<<"default">>, Default) || Default =/= none] ++ |
550 |
3 |
[list_name(<<"list">>, ListName) || ListName <- ListNames]. |
551 |
|
|
552 |
|
list_name(Type, Name) -> |
553 |
5 |
#xmlel{name = Type, attrs = [{<<"name">>, Name}]}. |
554 |
|
|
555 |
|
list_query_result(Name, LItems) -> |
556 |
48 |
#xmlel{ |
557 |
|
name = <<"query">>, |
558 |
|
attrs = [{<<"xmlns">>, ?NS_PRIVACY}], |
559 |
|
children = [ |
560 |
|
#xmlel{ |
561 |
|
name = <<"list">>, |
562 |
|
attrs = [{<<"name">>, Name}], |
563 |
|
children = LItems}]}. |
564 |
|
|
565 |
|
item_to_xml(Item) -> |
566 |
55 |
#xmlel{ |
567 |
|
name = <<"item">>, |
568 |
|
attrs = item_to_xml_attrs(Item), |
569 |
|
children = item_to_xml_children(Item)}. |
570 |
|
|
571 |
|
item_to_xml_attrs(Item=#listitem{type=none}) -> |
572 |
29 |
item_to_xml_attrs1(Item); |
573 |
|
item_to_xml_attrs(Item=#listitem{type=Type, value=Value}) -> |
574 |
26 |
[{<<"type">>, type_to_binary(Type)}, |
575 |
|
{<<"value">>, value_to_binary(Type, Value)} |
576 |
|
| item_to_xml_attrs1(Item)]. |
577 |
|
|
578 |
|
item_to_xml_attrs1(#listitem{action=Action, order=Order}) -> |
579 |
55 |
[{<<"action">>, action_to_binary(Action)}, |
580 |
|
{<<"order">>, order_to_binary(Order)}]. |
581 |
|
|
582 |
|
item_to_xml_children(#listitem{match_all=true}) -> |
583 |
10 |
[]; |
584 |
|
item_to_xml_children(#listitem{match_all=false, |
585 |
|
match_iq=MatchIQ, |
586 |
|
match_message=MatchMessage, |
587 |
|
match_presence_in=MatchPresenceIn, |
588 |
|
match_presence_out=MatchPresenceOut}) -> |
589 |
37 |
[#xmlel{name = <<"message">>} || MatchMessage] |
590 |
45 |
++ [#xmlel{name = <<"presence-in">>} || MatchPresenceIn] |
591 |
6 |
++ [#xmlel{name = <<"presence-out">>} || MatchPresenceOut] |
592 |
1 |
++ [#xmlel{name = <<"iq">>} || MatchIQ]. |
593 |
|
|
594 |
|
action_to_binary(Action) -> |
595 |
55 |
case Action of |
596 |
5 |
allow -> <<"allow">>; |
597 |
50 |
deny -> <<"deny">> |
598 |
|
end. |
599 |
|
|
600 |
|
order_to_binary(Order) -> |
601 |
55 |
integer_to_binary(Order). |
602 |
|
|
603 |
|
binary_to_order(Binary) -> |
604 |
57 |
binary_to_integer(Binary). |
605 |
|
|
606 |
|
type_to_binary(Type) -> |
607 |
26 |
case Type of |
608 |
17 |
jid -> <<"jid">>; |
609 |
1 |
group -> <<"group">>; |
610 |
8 |
subscription -> <<"subscription">> |
611 |
|
end. |
612 |
|
|
613 |
|
value_to_binary(Type, Val) -> |
614 |
26 |
case Type of |
615 |
17 |
jid -> jid:to_binary(Val); |
616 |
1 |
group -> Val; |
617 |
|
subscription -> |
618 |
8 |
case Val of |
619 |
2 |
both -> <<"both">>; |
620 |
2 |
to -> <<"to">>; |
621 |
2 |
from -> <<"from">>; |
622 |
2 |
none -> <<"none">> |
623 |
|
end |
624 |
|
end. |
625 |
|
|
626 |
|
binary_to_action(S) -> |
627 |
57 |
case S of |
628 |
5 |
<<"allow">> -> allow; |
629 |
52 |
<<"deny">> -> deny |
630 |
|
end. |
631 |
|
|
632 |
|
binary_to_action_s(Action) -> |
633 |
57 |
try |
634 |
57 |
binary_to_action(Action) |
635 |
|
catch error:_ -> |
636 |
:-( |
false |
637 |
|
end. |
638 |
|
|
639 |
|
binary_to_order_s(Order) -> |
640 |
57 |
try |
641 |
57 |
binary_to_order(Order) |
642 |
|
catch error:_ -> |
643 |
:-( |
false |
644 |
|
end. |
645 |
|
|
646 |
|
%% ------------------------------------------------------------------ |
647 |
|
%% Ejabberd |
648 |
|
%% ------------------------------------------------------------------ |
649 |
|
|
650 |
|
broadcast_privacy_list(UserJID, Name, UserList) -> |
651 |
51 |
JID = jid:to_bare(UserJID), |
652 |
51 |
ejabberd_sm:route(JID, JID, broadcast_privacy_list_packet(Name, UserList)). |
653 |
|
|
654 |
|
%% TODO this is dirty |
655 |
|
broadcast_privacy_list_packet(Name, UserList) -> |
656 |
51 |
{broadcast, {privacy_list, UserList, Name}}. |
657 |
|
|
658 |
|
roster_get_jid_info(HostType, ToJID, LJID) -> |
659 |
61 |
mongoose_hooks:roster_get_jid_info(HostType, ToJID, LJID). |
660 |
|
|
661 |
|
config_metrics(HostType) -> |
662 |
:-( |
OptsToReport = [{backend, mnesia}], %list of tuples {option, defualt_value} |
663 |
:-( |
mongoose_module_metrics:opts_for_module(HostType, ?MODULE, OptsToReport). |