diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 68fc6e81f..d86e50258 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -57,7 +57,7 @@ % Roster add_rosteritem/7, delete_rosteritem/4, - process_rosteritems/5, get_roster/2, push_roster/3, + get_roster/2, push_roster/3, push_roster_all/1, push_alltoall/2, push_roster_item/5, build_roster_item/3, @@ -506,7 +506,7 @@ get_commands_spec() -> args_desc = ["User name", "Server name", "Contact user name", "Contact server name"], result = {res, rescode}}, #ejabberd_commands{name = process_rosteritems, tags = [roster], - desc = "List/delete rosteritems that match filter (only Mnesia)", + desc = "List/delete rosteritems that match filter", longdesc = "Explanation of each argument:\n" " - action: what to do with each rosteritem that " "matches all the filtering options\n" @@ -515,6 +515,8 @@ get_commands_spec() -> " - users: the JIDs of the local user\n" " - contacts: the JIDs of the contact in the roster\n" "\n" + " *** Mnesia: \n" + "\n" "Allowed values in the arguments:\n" " ACTION = list | delete\n" " SUBS = SUB[:SUB]* | any\n" @@ -532,8 +534,26 @@ get_commands_spec() -> "'example.org' and that the contact JID is either a " "bare server name (without user part) or that has a " "user part and the server part contains the word 'icq'" - ":\n list none:from:to any *@example.org *:*@*icq*", - module = ?MODULE, function = process_rosteritems, + ":\n list none:from:to any *@example.org *:*@*icq*" + "\n\n" + " *** SQL:\n" + "\n" + "Allowed values in the arguments:\n" + " ACTION = list | delete\n" + " SUBS = any | none | from | to | both\n" + " ASKS = any | none | out | in\n" + " USERS = JID\n" + " CONTACTS = JID\n" + " JID = characters valid in a JID, and can use the " + "globs: _ and %\n" + "\n" + "This example will list roster items with subscription " + "'to' that have any ask property, of " + "local users which JID is in the virtual host " + "'example.org' and that the contact JID's " + "server part contains the word 'icq'" + ":\n list to any %@example.org %@%icq%", + module = mod_roster, function = process_rosteritems, args = [{action, string}, {subs, string}, {asks, string}, {users, string}, {contacts, string}], @@ -1535,164 +1555,20 @@ stats(Name, Host) -> end. - -%%----------------------------- -%% Purge roster items -%%----------------------------- - -process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) -> - Action = case ActionS of - "list" -> list; - "delete" -> delete - end, - - Subs = lists:foldl( - fun(any, _) -> [none, from, to, both]; - (Sub, Subs) -> [Sub | Subs] - end, - [], - [list_to_atom(S) || S <- string:tokens(SubsS, ":")] - ), - - Asks = lists:foldl( - fun(any, _) -> [none, out, in]; - (Ask, Asks) -> [Ask | Asks] - end, - [], - [list_to_atom(S) || S <- string:tokens(AsksS, ":")] - ), - - Users = lists:foldl( - fun("any", _) -> ["*", "*@*"]; - (U, Us) -> [U | Us] - end, - [], - [S || S <- string:tokens(UsersS, ":")] - ), - - Contacts = lists:foldl( - fun("any", _) -> ["*", "*@*"]; - (U, Us) -> [U | Us] - end, - [], - [S || S <- string:tokens(ContactsS, ":")] - ), - - rosteritem_purge({Action, Subs, Asks, Users, Contacts}). - -%% @spec ({Action::atom(), Subs::[atom()], Asks::[atom()], User::string(), Contact::string()}) -> {atomic, ok} -rosteritem_purge(Options) -> - Num_rosteritems = mnesia:table_info(roster, size), - io:format("There are ~p roster items in total.~n", [Num_rosteritems]), - Key = mnesia:dirty_first(roster), - rip(Key, Options, {0, Num_rosteritems, 0, 0}, []). - -rip('$end_of_table', _Options, Counters, Res) -> - print_progress_line(Counters), - Res; -rip(Key, Options, {Pr, NT, NV, ND}, Res) -> - Key_next = mnesia:dirty_next(roster, Key), - {Action, _, _, _, _} = Options, - {ND2, Res2} = case decide_rip(Key, Options) of - true -> - Jids = apply_action(Action, Key), - {ND+1, [Jids | Res]}; - false -> - {ND, Res} - end, - NV2 = NV+1, - Pr2 = print_progress_line({Pr, NT, NV2, ND2}), - rip(Key_next, Options, {Pr2, NT, NV2, ND2}, Res2). - -apply_action(list, Key) -> - {User, Server, JID} = Key, - {RUser, RServer, _} = JID, - Jid1string = <>, - Jid2string = <>, - io:format("Matches: ~s ~s~n", [Jid1string, Jid2string]), - {Jid1string, Jid2string}; -apply_action(delete, Key) -> - R = apply_action(list, Key), - mnesia:dirty_delete(roster, Key), - R. - -print_progress_line({_Pr, 0, _NV, _ND}) -> - ok; -print_progress_line({Pr, NT, NV, ND}) -> - Pr2 = trunc((NV/NT)*100), - case Pr == Pr2 of - true -> - ok; - false -> - io:format("Progress ~p% - visited ~p - deleted ~p~n", [Pr2, NV, ND]) - end, - Pr2. - -decide_rip(Key, {_Action, Subs, Asks, User, Contact}) -> - case catch mnesia:dirty_read(roster, Key) of - [RI] -> - lists:member(RI#roster.subscription, Subs) - andalso lists:member(RI#roster.ask, Asks) - andalso decide_rip_jid(RI#roster.us, User) - andalso decide_rip_jid(RI#roster.jid, Contact); - _ -> - false - end. - -%% Returns true if the server of the JID is included in the servers -decide_rip_jid({UName, UServer, _UResource}, Match_list) -> - decide_rip_jid({UName, UServer}, Match_list); -decide_rip_jid({UName, UServer}, Match_list) -> - lists:any( - fun(Match_string) -> - MJID = jid:decode(list_to_binary(Match_string)), - MName = MJID#jid.luser, - MServer = MJID#jid.lserver, - Is_server = is_glob_match(UServer, MServer), - case MName of - <<>> when UName == <<>> -> - Is_server; - <<>> -> - false; - _ -> - Is_server - andalso is_glob_match(UName, MName) - end - end, - Match_list). - user_action(User, Server, Fun, OK) -> case ejabberd_auth:user_exists(User, Server) of true -> - case catch Fun() of + case catch Fun() of OK -> ok; - {error, Error} -> throw(Error); + {error, Error} -> throw(Error); Error -> ?ERROR_MSG("Command returned: ~p", [Error]), - 1 - end; - false -> - throw({not_found, "unknown_user"}) + 1 + end; + false -> + throw({not_found, "unknown_user"}) end. -%% Copied from ejabberd-2.0.0/src/acl.erl -is_regexp_match(String, RegExp) -> - case ejabberd_regexp:run(String, RegExp) of - nomatch -> - false; - match -> - true; - {error, ErrDesc} -> - io:format( - "Wrong regexp ~p in ACL: ~p", - [RegExp, ErrDesc]), - false - end. -is_glob_match(String, <<"!", Glob/binary>>) -> - not is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)); -is_glob_match(String, Glob) -> - is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)). - num_prio(Priority) when is_integer(Priority) -> Priority; num_prio(_) -> diff --git a/src/mod_roster.erl b/src/mod_roster.erl index a0e52ee7d..6d8e45512 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -50,6 +50,7 @@ webadmin_user/4, get_versioning_feature/2, roster_versioning_enabled/1, roster_version/2, mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3, + process_rosteritems/5, depends/2]). -include("logger.hrl"). @@ -892,6 +893,11 @@ is_subscribed(From, #jid{luser = LUser, lserver = LServer}) -> (Sub /= none) orelse (Ask == subscribe) orelse (Ask == out) orelse (Ask == both). +process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) -> + LServer = ejabberd_config:get_myname(), + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% webadmin_page(_, Host, diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl index c67d7d5e4..41da9a6b4 100644 --- a/src/mod_roster_mnesia.erl +++ b/src/mod_roster_mnesia.erl @@ -31,11 +31,13 @@ get_roster/2, get_roster_item/3, roster_subscribe/4, remove_user/2, update_roster/4, del_roster/3, transaction/2, read_subscription_and_groups/3, import/3, create_roster/1, + process_rosteritems/5, use_cache/2]). -export([need_transform/1, transform/1]). -include("mod_roster.hrl"). -include("logger.hrl"). +-include("xmpp.hrl"). %%%=================================================================== %%% API @@ -154,6 +156,142 @@ transform(#roster_version{us = {U, S}, version = Ver} = R) -> R#roster_version{us = {iolist_to_binary(U), iolist_to_binary(S)}, version = iolist_to_binary(Ver)}. +%%%=================================================================== + +process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) -> + Action = case ActionS of + "list" -> list; + "delete" -> delete + end, + Subs = lists:foldl( + fun(any, _) -> [none, from, to, both]; + (Sub, Subs) -> [Sub | Subs] + end, + [], + [list_to_atom(S) || S <- string:tokens(SubsS, ":")] + ), + Asks = lists:foldl( + fun(any, _) -> [none, out, in]; + (Ask, Asks) -> [Ask | Asks] + end, + [], + [list_to_atom(S) || S <- string:tokens(AsksS, ":")] + ), + Users = lists:foldl( + fun("any", _) -> ["*", "*@*"]; + (U, Us) -> [U | Us] + end, + [], + [S || S <- string:tokens(UsersS, ":")] + ), + Contacts = lists:foldl( + fun("any", _) -> ["*", "*@*"]; + (U, Us) -> [U | Us] + end, + [], + [S || S <- string:tokens(ContactsS, ":")] + ), + rosteritem_purge({Action, Subs, Asks, Users, Contacts}). + +%% @spec ({Action::atom(), Subs::[atom()], Asks::[atom()], User::string(), Contact::string()}) -> {atomic, ok} +rosteritem_purge(Options) -> + Num_rosteritems = mnesia:table_info(roster, size), + io:format("There are ~p roster items in total.~n", [Num_rosteritems]), + Key = mnesia:dirty_first(roster), + rip(Key, Options, {0, Num_rosteritems, 0, 0}, []). + +rip('$end_of_table', _Options, Counters, Res) -> + print_progress_line(Counters), + Res; +rip(Key, Options, {Pr, NT, NV, ND}, Res) -> + Key_next = mnesia:dirty_next(roster, Key), + {Action, _, _, _, _} = Options, + {ND2, Res2} = case decide_rip(Key, Options) of + true -> + Jids = apply_action(Action, Key), + {ND+1, [Jids | Res]}; + false -> + {ND, Res} + end, + NV2 = NV+1, + Pr2 = print_progress_line({Pr, NT, NV2, ND2}), + rip(Key_next, Options, {Pr2, NT, NV2, ND2}, Res2). + +apply_action(list, Key) -> + {User, Server, JID} = Key, + {RUser, RServer, _} = JID, + Jid1string = <>, + Jid2string = <>, + io:format("Matches: ~s ~s~n", [Jid1string, Jid2string]), + {Jid1string, Jid2string}; +apply_action(delete, Key) -> + R = apply_action(list, Key), + mnesia:dirty_delete(roster, Key), + R. + +print_progress_line({_Pr, 0, _NV, _ND}) -> + ok; +print_progress_line({Pr, NT, NV, ND}) -> + Pr2 = trunc((NV/NT)*100), + case Pr == Pr2 of + true -> + ok; + false -> + io:format("Progress ~p% - visited ~p - deleted ~p~n", [Pr2, NV, ND]) + end, + Pr2. + +decide_rip(Key, {_Action, Subs, Asks, User, Contact}) -> + case catch mnesia:dirty_read(roster, Key) of + [RI] -> + lists:member(RI#roster.subscription, Subs) + andalso lists:member(RI#roster.ask, Asks) + andalso decide_rip_jid(RI#roster.us, User) + andalso decide_rip_jid(RI#roster.jid, Contact); + _ -> + false + end. + +%% Returns true if the server of the JID is included in the servers +decide_rip_jid({UName, UServer, _UResource}, Match_list) -> + decide_rip_jid({UName, UServer}, Match_list); +decide_rip_jid({UName, UServer}, Match_list) -> + lists:any( + fun(Match_string) -> + MJID = jid:decode(list_to_binary(Match_string)), + MName = MJID#jid.luser, + MServer = MJID#jid.lserver, + Is_server = is_glob_match(UServer, MServer), + case MName of + <<>> when UName == <<>> -> + Is_server; + <<>> -> + false; + _ -> + Is_server + andalso is_glob_match(UName, MName) + end + end, + Match_list). + +%% Copied from ejabberd-2.0.0/src/acl.erl +is_regexp_match(String, RegExp) -> + case ejabberd_regexp:run(String, RegExp) of + nomatch -> + false; + match -> + true; + {error, ErrDesc} -> + io:format( + "Wrong regexp ~p in ACL: ~p", + [RegExp, ErrDesc]), + false + end. +is_glob_match(String, <<"!", Glob/binary>>) -> + not is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)); +is_glob_match(String, Glob) -> + is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)). + %%%=================================================================== %%% Internal functions %%%=================================================================== diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl index 91242f610..a512f1bf5 100644 --- a/src/mod_roster_sql.erl +++ b/src/mod_roster_sql.erl @@ -33,11 +33,13 @@ get_roster/2, get_roster_item/3, roster_subscribe/4, read_subscription_and_groups/3, remove_user/2, update_roster/4, del_roster/3, transaction/2, + process_rosteritems/5, import/3, export/1, raw_to_record/2]). -include("mod_roster.hrl"). -include("ejabberd_sql_pt.hrl"). -include("logger.hrl"). +-include("jid.hrl"). %%%=================================================================== %%% API @@ -375,3 +377,39 @@ format_row_error(User, Server, Why) -> {ask, Ask} -> ["Malformed 'ask' field with value '", Ask, "'"] end, " detected for ", User, "@", Server, " in table 'rosterusers'"]. + +process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) -> + process_rosteritems_sql(ActionS, list_to_atom(SubsS), list_to_atom(AsksS), + list_to_binary(UsersS), list_to_binary(ContactsS)). + +process_rosteritems_sql(ActionS, Subscription, Ask, SLocalJID, SJID) -> + [LUser, LServer] = binary:split(SLocalJID, <<"@">>), + SSubscription = case Subscription of + any -> <<"_">>; + both -> <<"B">>; + to -> <<"T">>; + from -> <<"F">>; + none -> <<"N">> + end, + SAsk = case Ask of + any -> <<"_">>; + subscribe -> <<"S">>; + unsubscribe -> <<"U">>; + both -> <<"B">>; + out -> <<"O">>; + in -> <<"I">>; + none -> <<"N">> + end, + {selected, List} = ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s, @(jid)s from rosterusers " + "where username LIKE %(LUser)s" + " and %(LServer)H" + " and jid LIKE %(SJID)s" + " and subscription LIKE %(SSubscription)s" + " and ask LIKE %(SAsk)s")), + case ActionS of + "delete" -> [mod_roster:del_roster(User, LServer, jid:decode(Contact)) || {User, Contact} <- List]; + "list" -> ok + end, + List.