aboutsummaryrefslogtreecommitdiff
path: root/src/mod_privacy.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_privacy.erl')
-rw-r--r--src/mod_privacy.erl1177
1 files changed, 723 insertions, 454 deletions
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index 18ff78371..850fd6aa5 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -5,7 +5,7 @@
%%% Created : 21 Jul 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@@ -31,461 +31,570 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process_iq/3, export/1, import/1,
- process_iq_set/4, process_iq_get/5, get_user_list/3,
- check_packet/6, remove_user/2,
- is_list_needdb/1, updated_list/3,
- item_to_xml/1, get_user_lists/2, import/3,
- set_privacy_list/1, mod_opt_type/1, depends/2]).
+-export([start/2, stop/1, reload/3, process_iq/1, export/1,
+ c2s_copy_session/2, push_list_update/2, disco_features/5,
+ check_packet/4, remove_user/2, encode_list_item/1,
+ get_user_lists/2, get_user_list/3,
+ set_list/1, set_list/4, set_default_list/3,
+ user_send_packet/1,
+ import_start/2, import_stop/2, import/5, import_info/0,
+ mod_opt_type/1, mod_options/1, depends/2]).
--include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
-
+-include("xmpp.hrl").
-include("mod_privacy.hrl").
+-include("translate.hrl").
+-define(PRIVACY_CACHE, privacy_cache).
+-define(PRIVACY_LIST_CACHE, privacy_list_cache).
+
+-type c2s_state() :: ejabberd_c2s:state().
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #privacy{}) -> ok | pass.
--callback process_lists_get(binary(), binary()) -> {none | binary(), [xmlel()]} | error.
--callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found.
--callback process_default_set(binary(), binary(), {value, binary()} | false) -> {atomic, any()}.
--callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error.
--callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}.
--callback set_privacy_list(#privacy{}) -> any().
--callback set_privacy_list(binary(), binary(), binary(), [listitem()]) -> {atomic, any()}.
--callback get_user_list(binary(), binary()) -> {none | binary(), [listitem()]}.
--callback get_user_lists(binary(), binary()) -> {ok, #privacy{}} | error.
--callback remove_user(binary(), binary()) -> {atomic, any()}.
+-callback import(#privacy{}) -> ok.
+-callback set_default(binary(), binary(), binary()) ->
+ ok | {error, notfound | any()}.
+-callback unset_default(binary(), binary()) -> ok | {error, any()}.
+-callback remove_list(binary(), binary(), binary()) ->
+ ok | {error, notfound | conflict | any()}.
+-callback remove_lists(binary(), binary()) -> ok | {error, any()}.
+-callback set_lists(#privacy{}) -> ok | {error, any()}.
+-callback set_list(binary(), binary(), binary(), [listitem()]) ->
+ ok | {error, any()}.
+-callback get_list(binary(), binary(), binary() | default) ->
+ {ok, {binary(), [listitem()]}} | error | {error, any()}.
+-callback get_lists(binary(), binary()) ->
+ {ok, #privacy{}} | error | {error, any()}.
+-callback use_cache(binary()) -> boolean().
+-callback cache_nodes(binary()) -> [node()].
+
+-optional_callbacks([use_cache/1, cache_nodes/1]).
start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
- one_queue),
- Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod = gen_mod:db_mod(Opts, ?MODULE),
Mod:init(Host, Opts),
- mod_disco:register_feature(Host, ?NS_PRIVACY),
- ejabberd_hooks:add(privacy_iq_get, Host, ?MODULE,
- process_iq_get, 50),
- ejabberd_hooks:add(privacy_iq_set, Host, ?MODULE,
- process_iq_set, 50),
- ejabberd_hooks:add(privacy_get_user_list, Host, ?MODULE,
- get_user_list, 50),
+ init_cache(Mod, Host, Opts),
+ ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
+ disco_features, 50),
+ ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
+ c2s_copy_session, 50),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
+ user_send_packet, 50),
ejabberd_hooks:add(privacy_check_packet, Host, ?MODULE,
check_packet, 50),
- ejabberd_hooks:add(privacy_updated_list, Host, ?MODULE,
- updated_list, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_PRIVACY, ?MODULE, process_iq, IQDisc).
+ ?NS_PRIVACY, ?MODULE, process_iq).
stop(Host) ->
- mod_disco:unregister_feature(Host, ?NS_PRIVACY),
- ejabberd_hooks:delete(privacy_iq_get, Host, ?MODULE,
- process_iq_get, 50),
- ejabberd_hooks:delete(privacy_iq_set, Host, ?MODULE,
- process_iq_set, 50),
- ejabberd_hooks:delete(privacy_get_user_list, Host,
- ?MODULE, get_user_list, 50),
+ ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
+ disco_features, 50),
+ ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
+ c2s_copy_session, 50),
+ ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
+ user_send_packet, 50),
ejabberd_hooks:delete(privacy_check_packet, Host,
?MODULE, check_packet, 50),
- ejabberd_hooks:delete(privacy_updated_list, Host,
- ?MODULE, updated_list, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PRIVACY).
-process_iq(_From, _To, IQ) ->
- SubEl = IQ#iq.sub_el,
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
-
-process_iq_get(_, From, _To, #iq{lang = Lang, sub_el = SubEl},
- #userlist{name = Active}) ->
- #jid{luser = LUser, lserver = LServer} = From,
- #xmlel{children = Els} = SubEl,
- case fxml:remove_cdata(Els) of
- [] -> process_lists_get(LUser, LServer, Active, Lang);
- [#xmlel{name = Name, attrs = Attrs}] ->
- case Name of
- <<"list">> ->
- ListName = fxml:get_attr(<<"name">>, Attrs),
- process_list_get(LUser, LServer, ListName, Lang);
- _ ->
- Txt = <<"Unsupported tag name">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
- _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Too many elements">>)}
+reload(Host, NewOpts, OldOpts) ->
+ NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
+ OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
+ if NewMod /= OldMod ->
+ NewMod:init(Host, NewOpts);
+ true ->
+ ok
+ end,
+ init_cache(NewMod, Host, NewOpts).
+
+-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]}.
+disco_features({error, Err}, _From, _To, _Node, _Lang) ->
+ {error, Err};
+disco_features(empty, _From, _To, <<"">>, _Lang) ->
+ {result, [?NS_PRIVACY]};
+disco_features({result, Feats}, _From, _To, <<"">>, _Lang) ->
+ {result, [?NS_PRIVACY|Feats]};
+disco_features(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+-spec process_iq(iq()) -> iq().
+process_iq(#iq{type = Type,
+ from = #jid{luser = U, lserver = S},
+ to = #jid{luser = U, lserver = S}} = IQ) ->
+ case Type of
+ get -> process_iq_get(IQ);
+ set -> process_iq_set(IQ)
+ end;
+process_iq(#iq{lang = Lang} = IQ) ->
+ Txt = ?T("Query to another users is forbidden"),
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)).
+
+-spec process_iq_get(iq()) -> iq().
+process_iq_get(#iq{lang = Lang,
+ sub_els = [#privacy_query{default = Default,
+ active = Active}]} = IQ)
+ when Default /= undefined; Active /= undefined ->
+ Txt = ?T("Only <list/> element is allowed in this query"),
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+process_iq_get(#iq{lang = Lang,
+ sub_els = [#privacy_query{lists = Lists}]} = IQ) ->
+ case Lists of
+ [] ->
+ process_lists_get(IQ);
+ [#privacy_list{name = ListName}] ->
+ process_list_get(IQ, ListName);
+ _ ->
+ Txt = ?T("Too many <list/> elements"),
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
+ end;
+process_iq_get(#iq{lang = Lang} = IQ) ->
+ Txt = ?T("No module is handling this query"),
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec process_lists_get(iq()) -> iq().
+process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ) ->
+ case get_user_lists(LUser, LServer) of
+ {ok, #privacy{default = Default, lists = Lists}} ->
+ Active = xmpp:get_meta(IQ, privacy_active_list, none),
+ xmpp:make_iq_result(
+ IQ, #privacy_query{active = Active,
+ default = Default,
+ lists = [#privacy_list{name = Name}
+ || {Name, _} <- Lists]});
+ error ->
+ xmpp:make_iq_result(
+ IQ, #privacy_query{active = none, default = none});
+ {error, _} ->
+ Txt = ?T("Database failure"),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
-process_lists_get(LUser, LServer, Active, Lang) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- case Mod:process_lists_get(LUser, LServer) of
- error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
- {_Default, []} ->
- {result,
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVACY}], children = []}]};
- {Default, LItems} ->
- DItems = case Default of
- none -> LItems;
- _ ->
- [#xmlel{name = <<"default">>,
- attrs = [{<<"name">>, Default}], children = []}
- | LItems]
- end,
- ADItems = case Active of
- none -> DItems;
- _ ->
- [#xmlel{name = <<"active">>,
- attrs = [{<<"name">>, Active}], children = []}
- | DItems]
- end,
- {result,
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
- children = ADItems}]}
+-spec process_list_get(iq(), binary()) -> iq().
+process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ, Name) ->
+ case get_user_list(LUser, LServer, Name) of
+ {ok, {_, List}} ->
+ Items = lists:map(fun encode_list_item/1, List),
+ xmpp:make_iq_result(
+ IQ,
+ #privacy_query{
+ lists = [#privacy_list{name = Name, items = Items}]});
+ error ->
+ Txt = ?T("No privacy list with this name found"),
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
+ {error, _} ->
+ Txt = ?T("Database failure"),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
-process_list_get(LUser, LServer, {value, Name}, Lang) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- case Mod:process_list_get(LUser, LServer, Name) of
- error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
- not_found -> {error, ?ERR_ITEM_NOT_FOUND};
- Items ->
- LItems = lists:map(fun item_to_xml/1, Items),
- {result,
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
- children =
- [#xmlel{name = <<"list">>, attrs = [{<<"name">>, Name}],
- children = LItems}]}]}
- end;
-process_list_get(_LUser, _LServer, false, _Lang) ->
- {error, ?ERR_BAD_REQUEST}.
-
-item_to_xml(Item) ->
- Attrs1 = [{<<"action">>,
- action_to_list(Item#listitem.action)},
- {<<"order">>, order_to_list(Item#listitem.order)}],
- Attrs2 = case Item#listitem.type of
- none -> Attrs1;
- Type ->
- [{<<"type">>, type_to_list(Item#listitem.type)},
- {<<"value">>, value_to_list(Type, Item#listitem.value)}
- | Attrs1]
- end,
- SubEls = case Item#listitem.match_all of
- true -> [];
- false ->
- SE1 = case Item#listitem.match_iq of
- true ->
- [#xmlel{name = <<"iq">>, attrs = [],
- children = []}];
- false -> []
- end,
- SE2 = case Item#listitem.match_message of
- true ->
- [#xmlel{name = <<"message">>, attrs = [],
- children = []}
- | SE1];
- false -> SE1
- end,
- SE3 = case Item#listitem.match_presence_in of
- true ->
- [#xmlel{name = <<"presence-in">>, attrs = [],
- children = []}
- | SE2];
- false -> SE2
- end,
- SE4 = case Item#listitem.match_presence_out of
- true ->
- [#xmlel{name = <<"presence-out">>, attrs = [],
- children = []}
- | SE3];
- false -> SE3
- end,
- SE4
- end,
- #xmlel{name = <<"item">>, attrs = Attrs2,
- children = SubEls}.
-
-action_to_list(Action) ->
- case Action of
- allow -> <<"allow">>;
- deny -> <<"deny">>
+-spec encode_list_item(listitem()) -> privacy_item().
+encode_list_item(#listitem{action = Action,
+ order = Order,
+ type = Type,
+ match_all = MatchAll,
+ match_iq = MatchIQ,
+ match_message = MatchMessage,
+ match_presence_in = MatchPresenceIn,
+ match_presence_out = MatchPresenceOut,
+ value = Value}) ->
+ Item = #privacy_item{action = Action,
+ order = Order,
+ type = case Type of
+ none -> undefined;
+ Type -> Type
+ end,
+ value = encode_value(Type, Value)},
+ case MatchAll of
+ true ->
+ Item;
+ false ->
+ Item#privacy_item{message = MatchMessage,
+ iq = MatchIQ,
+ presence_in = MatchPresenceIn,
+ presence_out = MatchPresenceOut}
end.
-order_to_list(Order) ->
- iolist_to_binary(integer_to_list(Order)).
-
-type_to_list(Type) ->
+-spec encode_value(listitem_type(), listitem_value()) -> binary().
+encode_value(Type, Val) ->
case Type of
- jid -> <<"jid">>;
- group -> <<"group">>;
- subscription -> <<"subscription">>
+ jid -> jid:encode(Val);
+ group -> Val;
+ subscription ->
+ case Val of
+ both -> <<"both">>;
+ to -> <<"to">>;
+ from -> <<"from">>;
+ none -> <<"none">>
+ end;
+ none -> <<"">>
end.
-value_to_list(Type, Val) ->
+-spec decode_value(jid | subscription | group | undefined, binary()) ->
+ listitem_value().
+decode_value(Type, Value) ->
case Type of
- jid -> jid:to_string(Val);
- group -> Val;
- subscription ->
- case Val of
- both -> <<"both">>;
- to -> <<"to">>;
- from -> <<"from">>;
- none -> <<"none">>
- end
+ jid -> jid:tolower(jid:decode(Value));
+ subscription ->
+ case Value of
+ <<"from">> -> from;
+ <<"to">> -> to;
+ <<"both">> -> both;
+ <<"none">> -> none
+ end;
+ group when Value /= <<"">> -> Value;
+ undefined -> none
end.
-list_to_action(S) ->
- case S of
- <<"allow">> -> allow;
- <<"deny">> -> deny
+-spec process_iq_set(iq()) -> iq().
+process_iq_set(#iq{lang = Lang,
+ sub_els = [#privacy_query{default = Default,
+ active = Active,
+ lists = Lists}]} = IQ) ->
+ case Lists of
+ [#privacy_list{items = Items, name = ListName}]
+ when Default == undefined, Active == undefined ->
+ process_lists_set(IQ, ListName, Items);
+ [] when Default == undefined, Active /= undefined ->
+ process_active_set(IQ, Active);
+ [] when Active == undefined, Default /= undefined ->
+ process_default_set(IQ, Default);
+ _ ->
+ Txt = ?T("The stanza MUST contain only one <active/> element, "
+ "one <default/> element, or one <list/> element"),
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
+ end;
+process_iq_set(#iq{lang = Lang} = IQ) ->
+ Txt = ?T("No module is handling this query"),
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec process_default_set(iq(), none | binary()) -> iq().
+process_default_set(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ, Value) ->
+ case set_default_list(LUser, LServer, Value) of
+ ok ->
+ xmpp:make_iq_result(IQ);
+ {error, notfound} ->
+ Txt = ?T("No privacy list with this name found"),
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
+ {error, _} ->
+ Txt = ?T("Database failure"),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
-process_iq_set(_, From, _To, #iq{lang = Lang, sub_el = SubEl}) ->
- #jid{luser = LUser, lserver = LServer} = From,
- #xmlel{children = Els} = SubEl,
- case fxml:remove_cdata(Els) of
- [#xmlel{name = Name, attrs = Attrs,
- children = SubEls}] ->
- ListName = fxml:get_attr(<<"name">>, Attrs),
- case Name of
- <<"list">> ->
- process_list_set(LUser, LServer, ListName,
- fxml:remove_cdata(SubEls), Lang);
- <<"active">> ->
- process_active_set(LUser, LServer, ListName);
- <<"default">> ->
- process_default_set(LUser, LServer, ListName, Lang);
- _ ->
- Txt = <<"Unsupported tag name">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
- _ -> {error, ?ERR_BAD_REQUEST}
+-spec process_active_set(IQ, none | binary()) -> IQ.
+process_active_set(IQ, none) ->
+ xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_active_list, none));
+process_active_set(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ, Name) ->
+ case get_user_list(LUser, LServer, Name) of
+ {ok, _} ->
+ xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_active_list, Name));
+ error ->
+ Txt = ?T("No privacy list with this name found"),
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
+ {error, _} ->
+ Txt = ?T("Database failure"),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
-process_default_set(LUser, LServer, Value, Lang) ->
+-spec set_list(privacy()) -> ok | {error, any()}.
+set_list(#privacy{us = {LUser, LServer}, lists = Lists} = Privacy) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- case Mod:process_default_set(LUser, LServer, Value) of
- {atomic, error} ->
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
- {atomic, not_found} -> {error, ?ERR_ITEM_NOT_FOUND};
- {atomic, ok} -> {result, []};
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ case Mod:set_lists(Privacy) of
+ ok ->
+ Names = [Name || {Name, _} <- Lists],
+ delete_cache(Mod, LUser, LServer, Names);
+ {error, _} = Err ->
+ Err
end.
-process_active_set(LUser, LServer, {value, Name}) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- case Mod:process_active_set(LUser, LServer, Name) of
- error -> {error, ?ERR_ITEM_NOT_FOUND};
- Items ->
- NeedDb = is_list_needdb(Items),
- {result, [],
- #userlist{name = Name, list = Items, needdb = NeedDb}}
+-spec process_lists_set(iq(), binary(), [privacy_item()]) -> iq().
+process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ, Name, []) ->
+ case xmpp:get_meta(IQ, privacy_active_list, none) of
+ Name ->
+ Txt = ?T("Cannot remove active list"),
+ xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
+ _ ->
+ case remove_list(LUser, LServer, Name) of
+ ok ->
+ xmpp:make_iq_result(IQ);
+ {error, conflict} ->
+ Txt = ?T("Cannot remove default list"),
+ xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
+ {error, notfound} ->
+ Txt = ?T("No privacy list with this name found"),
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
+ {error, _} ->
+ Txt = ?T("Database failure"),
+ Err = xmpp:err_internal_server_error(Txt, Lang),
+ xmpp:make_error(IQ, Err)
+ end
end;
-process_active_set(_LUser, _LServer, false) ->
- {result, [], #userlist{}}.
+process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From,
+ lang = Lang} = IQ, Name, Items) ->
+ case catch lists:map(fun decode_item/1, Items) of
+ {error, Why} ->
+ Txt = xmpp:io_format_error(Why),
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+ List ->
+ case set_list(LUser, LServer, Name, List) of
+ ok ->
+ push_list_update(From, Name),
+ xmpp:make_iq_result(IQ);
+ {error, _} ->
+ Txt = ?T("Database failure"),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
+ end
+ end.
-set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:set_privacy_list(Privacy).
-
-process_list_set(LUser, LServer, {value, Name}, Els, Lang) ->
- case parse_items(Els) of
- false -> {error, ?ERR_BAD_REQUEST};
- remove ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- case Mod:remove_privacy_list(LUser, LServer, Name) of
- {atomic, conflict} ->
- Txt = <<"Cannot remove default list">>,
- {error, ?ERRT_CONFLICT(Lang, Txt)};
- {atomic, ok} ->
- ejabberd_sm:route(jid:make(LUser, LServer,
- <<"">>),
- jid:make(LUser, LServer, <<"">>),
- {broadcast, {privacy_list,
- #userlist{name = Name,
- list = []},
- Name}}),
- {result, []};
- _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
- end;
- List ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- case Mod:set_privacy_list(LUser, LServer, Name, List) of
- {atomic, ok} ->
- NeedDb = is_list_needdb(List),
- ejabberd_sm:route(jid:make(LUser, LServer,
- <<"">>),
- jid:make(LUser, LServer, <<"">>),
- {broadcast, {privacy_list,
- #userlist{name = Name,
- list = List,
- needdb = NeedDb},
- Name}}),
- {result, []};
- _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
- end
- end;
-process_list_set(_LUser, _LServer, false, _Els, _Lang) ->
- {error, ?ERR_BAD_REQUEST}.
-
-parse_items([]) -> remove;
-parse_items(Els) -> parse_items(Els, []).
-
-parse_items([], Res) ->
- lists:keysort(#listitem.order, Res);
-parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
- children = SubEls}
- | Els],
- Res) ->
- Type = fxml:get_attr(<<"type">>, Attrs),
- Value = fxml:get_attr(<<"value">>, Attrs),
- SAction = fxml:get_attr(<<"action">>, Attrs),
- SOrder = fxml:get_attr(<<"order">>, Attrs),
- Action = case catch list_to_action(element(2, SAction))
- of
- {'EXIT', _} -> false;
- Val -> Val
- end,
- Order = case catch jlib:binary_to_integer(element(2,
- SOrder))
- of
- {'EXIT', _} -> false;
- IntVal ->
- if IntVal >= 0 -> IntVal;
- true -> false
- end
+-spec push_list_update(jid(), binary()) -> ok.
+push_list_update(From, Name) ->
+ BareFrom = jid:remove_resource(From),
+ lists:foreach(
+ fun(R) ->
+ To = jid:replace_resource(From, R),
+ IQ = #iq{type = set, from = BareFrom, to = To,
+ id = <<"push", (p1_rand:get_string())/binary>>,
+ sub_els = [#privacy_query{
+ lists = [#privacy_list{name = Name}]}]},
+ ejabberd_router:route(IQ)
+ end, ejabberd_sm:get_user_resources(From#jid.luser, From#jid.lserver)).
+
+-spec decode_item(privacy_item()) -> listitem().
+decode_item(#privacy_item{order = Order,
+ action = Action,
+ type = T,
+ value = V,
+ message = MatchMessage,
+ iq = MatchIQ,
+ presence_in = MatchPresenceIn,
+ presence_out = MatchPresenceOut}) ->
+ Value = try decode_value(T, V)
+ catch _:_ ->
+ throw({error, {bad_attr_value, <<"value">>,
+ <<"item">>, ?NS_PRIVACY}})
end,
- if (Action /= false) and (Order /= false) ->
- I1 = #listitem{action = Action, order = Order},
- I2 = case {Type, Value} of
- {{value, T}, {value, V}} ->
- case T of
- <<"jid">> ->
- case jid:from_string(V) of
- error -> false;
- JID ->
- I1#listitem{type = jid,
- value = jid:tolower(JID)}
- end;
- <<"group">> -> I1#listitem{type = group, value = V};
- <<"subscription">> ->
- case V of
- <<"none">> ->
- I1#listitem{type = subscription,
- value = none};
- <<"both">> ->
- I1#listitem{type = subscription,
- value = both};
- <<"from">> ->
- I1#listitem{type = subscription,
- value = from};
- <<"to">> ->
- I1#listitem{type = subscription, value = to};
- _ -> false
- end
- end;
- {{value, _}, false} -> false;
- _ -> I1
- end,
- case I2 of
- false -> false;
- _ ->
- case parse_matches(I2, fxml:remove_cdata(SubEls)) of
- false -> false;
- I3 -> parse_items(Els, [I3 | Res])
- end
- end;
- true -> false
+ Type = case T of
+ undefined -> none;
+ _ -> T
+ end,
+ ListItem = #listitem{order = Order,
+ action = Action,
+ type = Type,
+ value = Value},
+ if not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) ->
+ ListItem#listitem{match_all = true};
+ true ->
+ ListItem#listitem{match_iq = MatchIQ,
+ match_message = MatchMessage,
+ match_presence_in = MatchPresenceIn,
+ match_presence_out = MatchPresenceOut}
+ end.
+
+-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
+c2s_copy_session(State, #{privacy_active_list := List}) ->
+ State#{privacy_active_list => List};
+c2s_copy_session(State, _) ->
+ State.
+
+%% Adjust the client's state, so next packets (which can be already queued)
+%% will take the active list into account.
+-spec update_c2s_state_with_privacy_list(stanza(), c2s_state()) -> c2s_state().
+update_c2s_state_with_privacy_list(#iq{type = set,
+ to = #jid{luser = U, lserver = S,
+ lresource = <<"">>} = To} = IQ,
+ State) ->
+ %% Match a IQ set containing a new active privacy list
+ case xmpp:get_subtag(IQ, #privacy_query{}) of
+ #privacy_query{default = undefined, active = Active} ->
+ case Active of
+ none ->
+ ?DEBUG("Removing active privacy list for user: ~ts",
+ [jid:encode(To)]),
+ State#{privacy_active_list => none};
+ undefined ->
+ State;
+ _ ->
+ case get_user_list(U, S, Active) of
+ {ok, _} ->
+ ?DEBUG("Setting active privacy list '~ts' for user: ~ts",
+ [Active, jid:encode(To)]),
+ State#{privacy_active_list => Active};
+ _ ->
+ %% unknown privacy list name
+ State
+ end
+ end;
+ _ ->
+ State
end;
-parse_items(_, _Res) -> false.
-
-parse_matches(Item, []) ->
- Item#listitem{match_all = true};
-parse_matches(Item, Els) -> parse_matches1(Item, Els).
-
-parse_matches1(Item, []) -> Item;
-parse_matches1(Item,
- [#xmlel{name = <<"message">>} | Els]) ->
- parse_matches1(Item#listitem{match_message = true},
- Els);
-parse_matches1(Item, [#xmlel{name = <<"iq">>} | Els]) ->
- parse_matches1(Item#listitem{match_iq = true}, Els);
-parse_matches1(Item,
- [#xmlel{name = <<"presence-in">>} | Els]) ->
- parse_matches1(Item#listitem{match_presence_in = true},
- Els);
-parse_matches1(Item,
- [#xmlel{name = <<"presence-out">>} | Els]) ->
- parse_matches1(Item#listitem{match_presence_out = true},
- Els);
-parse_matches1(_Item, [#xmlel{} | _Els]) -> false.
-
-is_list_needdb(Items) ->
- lists:any(fun (X) ->
- case X#listitem.type of
- subscription -> true;
- group -> true;
- _ -> false
- end
- end,
- Items).
+update_c2s_state_with_privacy_list(_Packet, State) ->
+ State.
+
+%% Add the active privacy list to packet metadata
+-spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
+user_send_packet({#iq{type = Type,
+ to = #jid{luser = U, lserver = S, lresource = <<"">>},
+ from = #jid{luser = U, lserver = S},
+ sub_els = [_]} = IQ,
+ #{privacy_active_list := Name} = State})
+ when Type == get; Type == set ->
+ NewIQ = case xmpp:has_subtag(IQ, #privacy_query{}) of
+ true -> xmpp:put_meta(IQ, privacy_active_list, Name);
+ false -> IQ
+ end,
+ {NewIQ, update_c2s_state_with_privacy_list(IQ, State)};
+%% For client with no active privacy list, see if there is
+%% one about to be activated in this packet and update client state
+user_send_packet({Packet, State}) ->
+ {Packet, update_c2s_state_with_privacy_list(Packet, State)}.
+
+-spec set_list(binary(), binary(), binary(), [listitem()]) -> ok | {error, any()}.
+set_list(LUser, LServer, Name, List) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:set_list(LUser, LServer, Name, List) of
+ ok ->
+ delete_cache(Mod, LUser, LServer, [Name]);
+ {error, _} = Err ->
+ Err
+ end.
-get_user_list(_Acc, User, Server) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+-spec remove_list(binary(), binary(), binary()) ->
+ ok | {error, conflict | notfound | any()}.
+remove_list(LUser, LServer, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- {Default, Items} = Mod:get_user_list(LUser, LServer),
- NeedDb = is_list_needdb(Items),
- #userlist{name = Default, list = Items,
- needdb = NeedDb}.
+ case Mod:remove_list(LUser, LServer, Name) of
+ ok ->
+ delete_cache(Mod, LUser, LServer, [Name]);
+ Err ->
+ Err
+ end.
+-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error | {error, any()}.
get_user_lists(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:get_user_lists(LUser, LServer).
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:lookup(
+ ?PRIVACY_CACHE, {LUser, LServer},
+ fun() -> Mod:get_lists(LUser, LServer) end);
+ false ->
+ Mod:get_lists(LUser, LServer)
+ end.
+
+-spec get_user_list(binary(), binary(), binary() | default) ->
+ {ok, {binary(), [listitem()]}} | error | {error, any()}.
+get_user_list(LUser, LServer, Name) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:lookup(
+ ?PRIVACY_LIST_CACHE, {LUser, LServer, Name},
+ fun() ->
+ case ets_cache:lookup(
+ ?PRIVACY_CACHE, {LUser, LServer}) of
+ {ok, Privacy} ->
+ get_list_by_name(Privacy, Name);
+ error ->
+ Mod:get_list(LUser, LServer, Name)
+ end
+ end);
+ false ->
+ Mod:get_list(LUser, LServer, Name)
+ end.
+
+-spec get_list_by_name(#privacy{}, binary() | default) ->
+ {ok, {binary(), [listitem()]}} | error.
+get_list_by_name(#privacy{default = Default} = Privacy, default) ->
+ get_list_by_name(Privacy, Default);
+get_list_by_name(#privacy{lists = Lists}, Name) ->
+ case lists:keyfind(Name, 1, Lists) of
+ {_, List} -> {ok, {Name, List}};
+ false -> error
+ end.
+
+-spec set_default_list(binary(), binary(), binary() | none) ->
+ ok | {error, notfound | any()}.
+set_default_list(LUser, LServer, Name) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Res = case Name of
+ none -> Mod:unset_default(LUser, LServer);
+ _ -> Mod:set_default(LUser, LServer, Name)
+ end,
+ case Res of
+ ok ->
+ delete_cache(Mod, LUser, LServer, []);
+ Err ->
+ Err
+ end.
+
+-spec check_packet(allow | deny, c2s_state() | jid(), stanza(), in | out) -> allow | deny.
+check_packet(Acc, #{jid := JID} = State, Packet, Dir) ->
+ case maps:get(privacy_active_list, State, none) of
+ none ->
+ check_packet(Acc, JID, Packet, Dir);
+ ListName ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ case get_user_list(LUser, LServer, ListName) of
+ {ok, {_, List}} ->
+ do_check_packet(JID, List, Packet, Dir);
+ _ ->
+ ?DEBUG("Non-existing active list '~ts' is set "
+ "for user '~ts'", [ListName, jid:encode(JID)]),
+ check_packet(Acc, JID, Packet, Dir)
+ end
+ end;
+check_packet(_, JID, Packet, Dir) ->
+ #jid{luser = LUser, lserver = LServer} = JID,
+ case get_user_list(LUser, LServer, default) of
+ {ok, {_, List}} ->
+ do_check_packet(JID, List, Packet, Dir);
+ _ ->
+ allow
+ end.
%% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From).
%% If Dir = in, User@Server is the destination account (To).
-check_packet(_, _User, _Server, _UserList,
- {#jid{luser = <<"">>, lserver = Server} = _From,
- #jid{lserver = Server} = _To, _},
- in) ->
+-spec do_check_packet(jid(), [listitem()], stanza(), in | out) -> allow | deny.
+do_check_packet(_, [], _, _) ->
allow;
-check_packet(_, _User, _Server, _UserList,
- {#jid{lserver = Server} = _From,
- #jid{luser = <<"">>, lserver = Server} = _To, _},
- out) ->
+do_check_packet(#jid{luser = LUser, lserver = LServer}, List, Packet, Dir) ->
+ From = xmpp:get_from(Packet),
+ To = xmpp:get_to(Packet),
+ case {From, To} of
+ {#jid{luser = <<"">>, lserver = LServer},
+ #jid{lserver = LServer}} when Dir == in ->
+ %% Allow any packets from local server
+ allow;
+ {#jid{lserver = LServer},
+ #jid{luser = <<"">>, lserver = LServer}} when Dir == out ->
+ %% Allow any packets to local server
allow;
-check_packet(_, _User, _Server, _UserList,
- {#jid{luser = User, lserver = Server} = _From,
- #jid{luser = User, lserver = Server} = _To, _},
- _Dir) ->
+ {#jid{luser = LUser, lserver = LServer, lresource = <<"">>},
+ #jid{luser = LUser, lserver = LServer}} when Dir == in ->
+ %% Allow incoming packets from user's bare jid to his full jid
allow;
-check_packet(_, User, Server,
- #userlist{list = List, needdb = NeedDb},
- {From, To, #xmlel{name = PName, attrs = Attrs}}, Dir) ->
- case List of
- [] -> allow;
+ {#jid{luser = LUser, lserver = LServer},
+ #jid{luser = LUser, lserver = LServer, lresource = <<"">>}} when Dir == out ->
+ %% Allow outgoing packets from user's full jid to his bare JID
+ allow;
_ ->
- PType = case PName of
- <<"message">> -> message;
- <<"iq">> -> iq;
- <<"presence">> ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- %% notification
- <<"">> -> presence;
- <<"unavailable">> -> presence;
- %% subscribe, subscribed, unsubscribe,
- %% unsubscribed, error, probe, or other
- _ -> other
- end
+ PType = case Packet of
+ #message{} -> message;
+ #iq{} -> iq;
+ #presence{type = available} -> presence;
+ #presence{type = unavailable} -> presence;
+ _ -> other
end,
PType2 = case {PType, Dir} of
{message, in} -> message;
@@ -498,20 +607,18 @@ check_packet(_, User, Server,
in -> jid:tolower(From);
out -> jid:tolower(To)
end,
- {Subscription, Groups} = case NeedDb of
- true ->
- ejabberd_hooks:run_fold(roster_get_jid_info,
- jid:nameprep(Server),
- {none, []},
- [User, Server,
- LJID]);
- false -> {[], []}
- end,
- check_packet_aux(List, PType2, LJID, Subscription,
- Groups)
+ {Subscription, _Ask, Groups} = ejabberd_hooks:run_fold(
+ roster_get_jid_info, LServer,
+ {none, none, []},
+ [LUser, LServer, LJID]),
+ check_packet_aux(List, PType2, LJID, Subscription, Groups)
end.
-%% Ptype = mesage | iq | presence_in | presence_out | other
+-spec check_packet_aux([listitem()],
+ message | iq | presence_in | presence_out | other,
+ ljid(), none | both | from | to, [binary()]) ->
+ allow | deny.
+%% Ptype = message | iq | presence_in | presence_out | other
check_packet_aux([], _PType, _JID, _Subscription,
_Groups) ->
allow;
@@ -521,21 +628,18 @@ check_packet_aux([Item | List], PType, JID,
Item,
case is_ptype_match(Item, PType) of
true ->
- case Type of
- none -> Action;
- _ ->
- case is_type_match(Type, Value, JID, Subscription,
- Groups)
- of
- true -> Action;
- false ->
- check_packet_aux(List, PType, JID, Subscription, Groups)
- end
- end;
+ case is_type_match(Type, Value, JID, Subscription, Groups) of
+ true -> Action;
+ false ->
+ check_packet_aux(List, PType, JID, Subscription, Groups)
+ end;
false ->
check_packet_aux(List, PType, JID, Subscription, Groups)
end.
+-spec is_ptype_match(listitem(),
+ message | iq | presence_in | presence_out | other) ->
+ boolean().
is_ptype_match(Item, PType) ->
case Item#listitem.match_all of
true -> true;
@@ -549,53 +653,218 @@ is_ptype_match(Item, PType) ->
end
end.
-is_type_match(Type, Value, JID, Subscription, Groups) ->
- case Type of
- jid ->
- case Value of
- {<<"">>, Server, <<"">>} ->
- case JID of
- {_, Server, _} -> true;
- _ -> false
- end;
- {User, Server, <<"">>} ->
- case JID of
- {User, Server, _} -> true;
- _ -> false
- end;
- _ -> Value == JID
- end;
- subscription -> Value == Subscription;
- group -> lists:member(Value, Groups)
- end.
+-spec is_type_match(none | jid | subscription | group, listitem_value(),
+ ljid(), none | both | from | to, [binary()]) -> boolean().
+is_type_match(none, _Value, _JID, _Subscription, _Groups) ->
+ true;
+is_type_match(jid, Value, JID, _Subscription, _Groups) ->
+ case Value of
+ {<<"">>, Server, <<"">>} ->
+ case JID of
+ {_, Server, _} -> true;
+ _ -> false
+ end;
+ {User, Server, <<"">>} ->
+ case JID of
+ {User, Server, _} -> true;
+ _ -> false
+ end;
+ {<<"">>, Server, Resource} ->
+ case JID of
+ {_, Server, Resource} -> true;
+ _ -> false
+ end;
+ _ -> Value == JID
+ end;
+is_type_match(subscription, Value, _JID, Subscription, _Groups) ->
+ Value == Subscription;
+is_type_match(group, Group, _JID, _Subscription, Groups) ->
+ lists:member(Group, Groups).
+-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
+ Privacy = get_user_lists(LUser, LServer),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_user(LUser, LServer).
+ Mod:remove_lists(LUser, LServer),
+ case Privacy of
+ {ok, #privacy{lists = Lists}} ->
+ Names = [Name || {Name, _} <- Lists],
+ delete_cache(Mod, LUser, LServer, Names);
+ _ ->
+ ok
+ end.
-updated_list(_, #userlist{name = OldName} = Old,
- #userlist{name = NewName} = New) ->
- if OldName == NewName -> New;
- true -> Old
+-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
+init_cache(Mod, Host, Opts) ->
+ case use_cache(Mod, Host) of
+ true ->
+ CacheOpts = cache_opts(Opts),
+ ets_cache:new(?PRIVACY_CACHE, CacheOpts),
+ ets_cache:new(?PRIVACY_LIST_CACHE, CacheOpts);
+ false ->
+ ets_cache:delete(?PRIVACY_CACHE),
+ ets_cache:delete(?PRIVACY_LIST_CACHE)
end.
-export(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:export(LServer).
+-spec cache_opts(gen_mod:opts()) -> [proplists:property()].
+cache_opts(Opts) ->
+ MaxSize = mod_privacy_opt:cache_size(Opts),
+ CacheMissed = mod_privacy_opt:cache_missed(Opts),
+ LifeTime = mod_privacy_opt:cache_life_time(Opts),
+ [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
+
+-spec use_cache(module(), binary()) -> boolean().
+use_cache(Mod, Host) ->
+ case erlang:function_exported(Mod, use_cache, 1) of
+ true -> Mod:use_cache(Host);
+ false -> mod_privacy_opt:use_cache(Host)
+ end.
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+-spec cache_nodes(module(), binary()) -> [node()].
+cache_nodes(Mod, Host) ->
+ case erlang:function_exported(Mod, cache_nodes, 1) of
+ true -> Mod:cache_nodes(Host);
+ false -> ejabberd_cluster:get_nodes()
+ end.
+
+-spec delete_cache(module(), binary(), binary(), [binary()]) -> ok.
+delete_cache(Mod, LUser, LServer, Names) ->
+ case use_cache(Mod, LServer) of
+ true ->
+ Nodes = cache_nodes(Mod, LServer),
+ ets_cache:delete(?PRIVACY_CACHE, {LUser, LServer}, Nodes),
+ lists:foreach(
+ fun(Name) ->
+ ets_cache:delete(
+ ?PRIVACY_LIST_CACHE,
+ {LUser, LServer, Name},
+ Nodes)
+ end, [default|Names]);
+ false ->
+ ok
+ end.
-import(LServer, DBType, Data) ->
+numeric_to_binary(<<0, 0, _/binary>>) ->
+ <<"0">>;
+numeric_to_binary(<<0, _, _:6/binary, T/binary>>) ->
+ Res = lists:foldl(
+ fun(X, Sum) ->
+ Sum*10000 + X
+ end, 0, [X || <<X:16>> <= T]),
+ integer_to_binary(Res).
+
+bool_to_binary(<<0>>) -> <<"0">>;
+bool_to_binary(<<1>>) -> <<"1">>.
+
+prepare_list_data(mysql, [ID|Row]) ->
+ [binary_to_integer(ID)|Row];
+prepare_list_data(pgsql, [<<ID:64>>,
+ SType, SValue, SAction, SOrder, SMatchAll,
+ SMatchIQ, SMatchMessage, SMatchPresenceIn,
+ SMatchPresenceOut]) ->
+ [ID, SType, SValue, SAction,
+ numeric_to_binary(SOrder),
+ bool_to_binary(SMatchAll),
+ bool_to_binary(SMatchIQ),
+ bool_to_binary(SMatchMessage),
+ bool_to_binary(SMatchPresenceIn),
+ bool_to_binary(SMatchPresenceOut)].
+
+prepare_id(mysql, ID) ->
+ binary_to_integer(ID);
+prepare_id(pgsql, <<ID:32>>) ->
+ ID.
+
+import_info() ->
+ [{<<"privacy_default_list">>, 2},
+ {<<"privacy_list_data">>, 10},
+ {<<"privacy_list">>, 4}].
+
+import_start(LServer, DBType) ->
+ ets:new(privacy_default_list_tmp, [private, named_table]),
+ ets:new(privacy_list_data_tmp, [private, named_table, bag]),
+ ets:new(privacy_list_tmp, [private, named_table, bag,
+ {keypos, #privacy.us}]),
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, Data).
+ Mod:init(LServer, []).
+
+import(LServer, {sql, _}, _DBType, <<"privacy_default_list">>, [LUser, Name]) ->
+ US = {LUser, LServer},
+ ets:insert(privacy_default_list_tmp, {US, Name}),
+ ok;
+import(LServer, {sql, SQLType}, _DBType, <<"privacy_list_data">>, Row1) ->
+ [ID|Row] = prepare_list_data(SQLType, Row1),
+ case mod_privacy_sql:raw_to_item(Row) of
+ [Item] ->
+ IS = {ID, LServer},
+ ets:insert(privacy_list_data_tmp, {IS, Item}),
+ ok;
+ [] ->
+ ok
+ end;
+import(LServer, {sql, SQLType}, _DBType, <<"privacy_list">>,
+ [LUser, Name, ID, _TimeStamp]) ->
+ US = {LUser, LServer},
+ IS = {prepare_id(SQLType, ID), LServer},
+ Default = case ets:lookup(privacy_default_list_tmp, US) of
+ [{_, Name}] -> Name;
+ _ -> none
+ end,
+ case [Item || {_, Item} <- ets:lookup(privacy_list_data_tmp, IS)] of
+ [_|_] = Items ->
+ Privacy = #privacy{us = {LUser, LServer},
+ default = Default,
+ lists = [{Name, Items}]},
+ ets:insert(privacy_list_tmp, Privacy),
+ ets:delete(privacy_list_data_tmp, IS),
+ ok;
+ _ ->
+ ok
+ end.
+
+import_stop(_LServer, DBType) ->
+ import_next(DBType, ets:first(privacy_list_tmp)),
+ ets:delete(privacy_default_list_tmp),
+ ets:delete(privacy_list_data_tmp),
+ ets:delete(privacy_list_tmp),
+ ok.
+
+import_next(_DBType, '$end_of_table') ->
+ ok;
+import_next(DBType, US) ->
+ [P|_] = Ps = ets:lookup(privacy_list_tmp, US),
+ Lists = lists:flatmap(
+ fun(#privacy{lists = Lists}) ->
+ Lists
+ end, Ps),
+ Privacy = P#privacy{lists = Lists},
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(Privacy),
+ import_next(DBType, ets:next(privacy_list_tmp, US)).
+
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
depends(_Host, _Opts) ->
[].
-mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
-mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
-mod_opt_type(_) -> [db_type, iqdisc].
+mod_opt_type(db_type) ->
+ econf:db_type(?MODULE);
+mod_opt_type(use_cache) ->
+ econf:bool();
+mod_opt_type(cache_size) ->
+ econf:pos_int(infinity);
+mod_opt_type(cache_missed) ->
+ econf:bool();
+mod_opt_type(cache_life_time) ->
+ econf:timeout(second, infinity).
+
+mod_options(Host) ->
+ [{db_type, ejabberd_config:default_db(Host, ?MODULE)},
+ {use_cache, ejabberd_option:use_cache(Host)},
+ {cache_size, ejabberd_option:cache_size(Host)},
+ {cache_missed, ejabberd_option:cache_missed(Host)},
+ {cache_life_time, ejabberd_option:cache_life_time(Host)}].