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