diff options
-rw-r--r-- | include/ejabberd_commands.hrl | 13 | ||||
-rw-r--r-- | sql/mysql.sql | 2 | ||||
-rw-r--r-- | src/ejabberd_auth.erl | 75 | ||||
-rw-r--r-- | src/ejabberd_c2s.erl | 11 | ||||
-rw-r--r-- | src/ejabberd_commands.erl | 22 | ||||
-rw-r--r-- | src/ejabberd_commands_doc.erl | 415 | ||||
-rw-r--r-- | src/ejabberd_listener.erl | 6 | ||||
-rw-r--r-- | src/ejabberd_odbc.erl | 2 | ||||
-rw-r--r-- | src/gen_mod.erl | 6 | ||||
-rw-r--r-- | src/mod_admin_p1.erl | 2 | ||||
-rw-r--r-- | src/mod_ejabberd_log.erl | 287 | ||||
-rw-r--r-- | src/mod_register.erl | 2 | ||||
-rw-r--r-- | src/mod_roster.erl | 48 | ||||
-rw-r--r-- | src/mod_shared_roster.erl | 163 |
14 files changed, 957 insertions, 97 deletions
diff --git a/include/ejabberd_commands.hrl b/include/ejabberd_commands.hrl index 116bb7357..05120ccbe 100644 --- a/include/ejabberd_commands.hrl +++ b/include/ejabberd_commands.hrl @@ -35,7 +35,11 @@ module :: atom(), function :: atom(), args = [] :: [aterm()] | '_' | '$1' | '$2', - result = {res, rescode} :: rterm() | '_' | '$2'}). + result = {res, rescode} :: rterm() | '_' | '$2', + args_desc = none :: none | [string()], + result_desc = none :: none | string(), + args_example = none :: [any()], + result_example = none :: any()}). -type ejabberd_commands() :: #ejabberd_commands{name :: atom(), tags :: [atom()], @@ -44,7 +48,11 @@ module :: atom(), function :: atom(), args :: [aterm()], - result :: rterm()}. + result :: rterm(), + args_desc :: none | [string()], + result_desc :: none | string(), + args_example :: [any()], + result_example :: any()}. %% @type ejabberd_commands() = #ejabberd_commands{ %% name = atom(), @@ -72,4 +80,3 @@ %% @type rterm() = {Name::atom(), Type::rtype()}. %% A result term is a tuple with the term name and the term type. - diff --git a/sql/mysql.sql b/sql/mysql.sql index afdf4f3d9..72f58dcd0 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -60,7 +60,7 @@ CREATE TABLE rostergroups ( CREATE INDEX pk_rosterg_user_jid ON rostergroups(username(75), jid(75)); CREATE TABLE sr_group ( - name varchar(250) NOT NULL, + name varchar(250) NOT NULL PRIMARY KEY, opts text NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ) CHARACTER SET utf8; diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index ce0eb4875..f8ac2a3eb 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -45,6 +45,12 @@ -export([auth_modules/1]). +%% For benchmarking +-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', + password = <<"">> :: binary() | scram() | '_'}). + +-export([create_users/5]). + -include("ejabberd.hrl"). -include("logger.hrl"). @@ -70,7 +76,7 @@ -callback get_vh_registered_users_number(binary()) -> number(). -callback get_vh_registered_users_number(binary(), opts()) -> number(). -callback get_password(binary(), binary()) -> false | binary(). --callback get_password_s(binary(), binary()) -> binary(). +-callback get_password_s(binary(), binary()) -> binary(). start() -> lists:foreach(fun (Host) -> @@ -106,7 +112,7 @@ check_password(User, Server, Password) -> -spec check_password(binary(), binary(), binary(), binary(), fun((binary()) -> binary())) -> boolean(). - + check_password(User, Server, Password, Digest, DigestGen) -> case check_password_with_authmodule(User, Server, @@ -161,20 +167,34 @@ try_register(User, Server, Password) -> case is_user_exists(User, Server) of true -> {atomic, exists}; false -> - case lists:member(jlib:nameprep(Server), ?MYHOSTS) of + LServer = jlib:nameprep(Server), + case lists:member(LServer, ?MYHOSTS) of true -> - Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res; - (M, _) -> - M:try_register(User, Server, Password) - end, - {error, not_allowed}, auth_modules(Server)), - case Res of - {atomic, ok} -> - ejabberd_hooks:run(register_user, Server, - [User, Server]), - {atomic, ok}; - _ -> Res - end; + MaxUsers = ejabberd_config:get_local_option({max_users, LServer}, + fun(X) when is_integer(X) -> X end, no_limit), + RegAllowed = case MaxUsers of + no_limit -> + true; + Num -> + get_vh_registered_users_number(LServer) < Num + end, + case RegAllowed of + true -> + Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res; + (M, _) -> + M:try_register(User, Server, Password) + end, + {error, not_allowed}, auth_modules(Server)), + case Res of + {atomic, ok} -> + ejabberd_hooks:run(register_user, Server, + [User, Server]), + {atomic, ok}; + _ -> Res + end; + _ -> + {error, too_many_users} + end; false -> {error, not_allowed} end end. @@ -385,3 +405,28 @@ auth_modules(Server) -> export(Server) -> ejabberd_auth_internal:export(Server). + +-spec create_users(binary(), binary(), binary(), + pos_integer(), gen_mod:db_type()) -> any(). + +create_users(UserPattern, PassPattern, Server, Total, DBType) -> + lists:foreach( + fun(I) -> + LUser = jlib:nodeprep( + iolist_to_binary([UserPattern, integer_to_list(I)])), + Pass = iolist_to_binary([PassPattern, integer_to_list(I)]), + LServer = jlib:nameprep(Server), + US = {LUser, LServer}, + case DBType of + mnesia -> + mnesia:dirty_write(#passwd{us = US, + password = Pass}); + riak -> + ejabberd_riak:put( + #passwd{us = US, + password = Pass}, + [{'2i', [{<<"host">>, LServer}]}]); + odbc -> + erlang:error(odbc_not_supported) + end + end, lists:seq(1, Total)). diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 710c0a6d5..71194538b 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -200,17 +200,17 @@ get_subscription(LFrom, StateData) -> broadcast(FsmRef, Type, From, Packet) -> FsmRef ! {broadcast, Type, From, Packet}. -%% Used by mod_ack and mod_ping. +%% Used by mod_ack and mod_ping. %% If the client is not oor capable, we must stop the session, %% and be sure to not return until the c2s process has really stopped. This -%% is to avoid race conditions when resending messages in mod_ack (EJABS-1677). +%% is to avoid race conditions when resending messages in mod_ack (EJABS-1677). %% In the other side, if the client is oor capable, then this just %% switch reception to false, and returns inmediately. stop_or_detach(FsmRef) -> case ?GEN_FSM:sync_send_event(FsmRef, stop_or_detach) of stopped -> MRef = erlang:monitor(process, FsmRef), - receive + receive {'DOWN', MRef, process, FsmRef, _Reason}-> ok after 5 -> @@ -2403,8 +2403,9 @@ presence_broadcast_first(From, StateData, Packet) -> JIDs2Probe = format_and_check_privacy(From, StateData, Packet, JIDsProbe, out), Server = StateData#state.server, send_multiple(From, Server, JIDs2Probe, PacketProbe), - As = StateData#state.pres_f, %%Reuse existing structure, don't create a new one - JIDs = ?SETS:to_list(StateData#state.pres_f), + As = ?SETS:foldl(fun ?SETS:add_element/2, StateData#state.pres_f, + StateData#state.pres_a), + JIDs = ?SETS:to_list(As), JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out), Server = StateData#state.server, send_multiple(From, Server, JIDs2, Packet), diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index 380743674..5e70b3b5c 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -227,7 +227,19 @@ init() -> ets:new(ejabberd_commands, [named_table, set, public, - {keypos, #ejabberd_commands.name}]). + {keypos, #ejabberd_commands.name}]), + register_commands([ + #ejabberd_commands{name = gen_xmlrpc_docs, tags = [documentation], + desc = "Generates html documentation for ejabberd_commands", + module = ejabberd_commands_doc, function = generate_output, + args = [{file, binary}, {regexp, binary}], + result = {res, rescode}, + args_desc = [<<"Path to file where generated documentation should be stored">>, + <<"Commands which name or module is matched by it will be included in output">>], + result_desc = <<"0 if command failed, 1 when succedded">>, + args_example = [<<"/home/me/docs/api.html">>, <<"mod_admin">>], + result_example = ok}]). + -spec register_commands([ejabberd_commands()]) -> ok. @@ -297,7 +309,7 @@ execute_command(Name, Arguments) -> execute_command([], noauth, Name, Arguments). %% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error} -%% where +%% where %% AccessCommands = [{Access, CommandNames, Arguments}] %% Auth = {User::string(), Server::string(), Password::string()} | noauth %% Method = atom() @@ -356,7 +368,7 @@ get_tags_commands() -> %% ----------------------------- %% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok -%% where +%% where %% AccessCommands = [ {Access, CommandNames, Arguments} ] %% Auth = {User::string(), Server::string(), Password::string()} | noauth %% Method = atom() @@ -433,8 +445,8 @@ check_access_arguments(Command, ArgumentRestrictions, Arguments) -> tag_arguments(ArgsDefs, Args) -> lists:zipwith( - fun({ArgName, _ArgType}, ArgValue) -> + fun({ArgName, _ArgType}, ArgValue) -> {ArgName, ArgValue} - end, + end, ArgsDefs, Args). diff --git a/src/ejabberd_commands_doc.erl b/src/ejabberd_commands_doc.erl new file mode 100644 index 000000000..1084977b1 --- /dev/null +++ b/src/ejabberd_commands_doc.erl @@ -0,0 +1,415 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_commands_doc.erl +%%% Author : Badlop <badlop@process-one.net> +%%% Purpose : Management of ejabberd commands +%%% Created : 20 May 2008 by Badlop <badlop@process-one.net> +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2013 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%---------------------------------------------------------------------- + +-module(ejabberd_commands_doc). +-author('pawel@process-one.net'). + +-export([generate_output/2]). + +-include("ejabberd_commands.hrl"). +-include("ejabberd.hrl"). + +-define(RAW(V), xml:crypt(iolist_to_binary(V))). +-define(TAG(N), <<"<", ??N, "/>">>). +-define(TAG(N, V), <<"<", ??N, ">">>, V, <<"</", ??N, ">">>). +-define(TAG(N, C, V), <<"<", ??N, " class='", C, "'>">>, V, <<"</", ??N, ">">>). +-define(TAG_R(N, V), ?TAG(N, ?RAW(V))). +-define(TAG_R(N, C, V), ?TAG(N, C, ?RAW(V))). +-define(SPAN(N, V), ?TAG_R(span, ??N, V)). + +-define(STR(A), ?SPAN(str,[<<"\"">>, A, <<"\"">>])). +-define(NUM(A), ?SPAN(num,jlib:integer_to_binary(A))). +-define(FIELD(A), ?SPAN(field,A)). +-define(ID(A), ?SPAN(id,A)). +-define(OP(A), ?SPAN(op,A)). +-define(ARG(A), ?FIELD(atom_to_list(A))). +-define(KW(A), ?SPAN(kw,A)). +-define(BR, <<"\n">>). + +-define(RAW_L(A), ?RAW(<<A>>)). +-define(STR_L(A), ?STR(<<A>>)). +-define(FIELD_L(A), ?FIELD(<<A>>)). +-define(ID_L(A), ?ID(<<A>>)). +-define(OP_L(A), ?OP(<<A>>)). +-define(KW_L(A), ?KW(<<A>>)). + +-define(STR_A(A), ?STR(atom_to_list(A))). +-define(ID_A(A), ?ID(atom_to_list(A))). + +list_join_with([], _M) -> + []; +list_join_with([El|Tail], M) -> + lists:reverse(lists:foldl(fun(E, Acc) -> + [E, M | Acc] + end, [El], Tail)). + +rescode_to_int(ok) -> + 0; +rescode_to_int(true) -> + 0; +rescode_to_int(_) -> + 1. + +perl_gen({Name, integer}, Int, _Indent) -> + [?ARG(Name), ?OP_L(" => "), ?NUM(Int)]; +perl_gen({Name, string}, Str, _Indent) -> + [?ARG(Name), ?OP_L(" => "), ?STR(Str)]; +perl_gen({Name, binary}, Str, _Indent) -> + [?ARG(Name), ?OP_L(" => "), ?STR(Str)]; +perl_gen({Name, atom}, Atom, _Indent) -> + [?ARG(Name), ?OP_L(" => "), ?STR(atom_to_list(Atom))]; +perl_gen({Name, {tuple, Fields}}, Tuple, Indent) -> + Res = lists:map(fun({A,B})->perl_gen(A, B, Indent) end, lists:zip(Fields, tuple_to_list(Tuple))), + [?ARG(Name), ?OP_L(" => {"), list_join_with(Res, [?OP_L(", ")]), ?OP_L("}")]; +perl_gen({Name, {list, ElDesc}}, List, Indent) -> + Res = lists:map(fun(E) -> [?OP_L("{"), perl_gen(ElDesc, E, Indent), ?OP_L("}")] end, List), + [?ARG(Name), ?OP_L(" => ["), list_join_with(Res, [?OP_L(", ")]), ?OP_L("]")]. + +perl_call(Name, ArgsDesc, Values) -> + [?ID_L("XMLRPC::Lite"), ?OP_L("->"), ?ID_L("proxy"), ?OP_L("("), ?ID_L("$url"), ?OP_L(")->"), + ?ID_L("call"), ?OP_L("("), ?STR_A(Name), ?OP_L(", {"), ?BR, <<" ">>, + list_join_with(lists:map(fun({A,B})->perl_gen(A, B, <<" ">>) end, lists:zip(ArgsDesc, Values)), [?OP_L(","), ?BR, <<" ">>]), + ?BR, ?OP_L("})->"), ?ID_L("results"), ?OP_L("()")]. + +java_gen_map(Vals, Indent) -> + {Split, NL} = case Indent of + none -> {<<" ">>, <<" ">>}; + _ -> {[?BR, <<" ", Indent/binary>>], [?BR, Indent]} + end, + [?KW_L("new "), ?ID_L("HashMap"), ?OP_L("<"), ?ID_L("String"), ?OP_L(", "), ?ID_L("Object"), + ?OP_L(">() {{"), Split, list_join_with(Vals, Split), NL, ?OP_L("}}")]. + +java_gen({Name, integer}, Int, _Indent) -> + [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Integer"), ?OP_L("("), ?NUM(Int), ?OP_L("));")]; +java_gen({Name, string}, Str, _Indent) -> + [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(Str), ?OP_L(");")]; +java_gen({Name, binary}, Str, _Indent) -> + [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(Str), ?OP_L(");")]; +java_gen({Name, atom}, Atom, _Indent) -> + [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(atom_to_list(Atom)), ?OP_L(");")]; +java_gen({Name, {tuple, Fields}}, Tuple, Indent) -> + NewIndent = <<" ", Indent/binary>>, + Res = lists:map(fun({A, B}) -> [java_gen(A, B, NewIndent)] end, lists:zip(Fields, tuple_to_list(Tuple))), + [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), java_gen_map(Res, Indent), ?OP_L(")")]; +java_gen({Name, {list, ElDesc}}, List, Indent) -> + {NI, NI2, I} = case List of + [_] -> {" ", " ", Indent}; + _ -> {[?BR, <<" ", Indent/binary>>], + [?BR, <<" ", Indent/binary>>], + <<" ", Indent/binary>>} + end, + Res = lists:map(fun(E) -> java_gen_map([java_gen(ElDesc, E, I)], none) end, List), + [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Object"), ?OP_L("[] {"), NI, + list_join_with(Res, [?OP_L(","), NI]), NI2, ?OP_L("});")]. + +java_call(Name, ArgsDesc, Values) -> + [?ID_L("XmlRpcClientConfigImpl config"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClientConfigImpl"), ?OP_L("();"), ?BR, + ?ID_L("config"), ?OP_L("."), ?ID_L("setServerURL"), ?OP_L("("), ?ID_L("url"), ?OP_L(");"), ?BR, ?BR, + ?ID_L("XmlRpcClient client"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClient"), ?OP_L("();"), ?BR, + ?ID_L("client"), ?OP_L("."), ?ID_L("setConfig"), ?OP_L("("), ?ID_L("config"), ?OP_L(");"), ?BR, ?BR, + ?ID_L("client"), ?OP_L("."), ?ID_L("execute"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), + java_gen_map(lists:map(fun({A,B})->java_gen(A, B, <<"">>) end, lists:zip(ArgsDesc, Values)), <<"">>), ?OP_L(");"), ?BR]. + +-define(XML_S(N, V), ?OP_L("<"), ?FIELD_L(??N), ?OP_L(">"), V). +-define(XML_E(N), ?OP_L("</"), ?FIELD_L(??N), ?OP_L(">")). +-define(XML(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?BR, Indent, ?XML_E(N)). +-define(XML(N, Indent, D, V), ?XML(N, [Indent, lists:duplicate(D, <<" ">>)], V)). +-define(XML_L(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?XML_E(N)). +-define(XML_L(N, Indent, D, V), ?XML_L(N, [Indent, lists:duplicate(D, <<" ">>)], V)). + +xml_gen({Name, integer}, Int, Indent) -> + [?XML(member, Indent, + [?XML_L(name, Indent, 1, ?ID_A(Name)), + ?XML(value, Indent, 1, + [?XML_L(integer, Indent, 2, ?ID(jlib:integer_to_binary(Int)))])])]; +xml_gen({Name, string}, Str, Indent) -> + [?XML(member, Indent, + [?XML_L(name, Indent, 1, ?ID_A(Name)), + ?XML(value, Indent, 1, + [?XML_L(string, Indent, 2, ?ID(Str))])])]; +xml_gen({Name, binary}, Str, Indent) -> + [?XML(member, Indent, + [?XML_L(name, Indent, 1, ?ID_A(Name)), + ?XML(value, Indent, 1, + [?XML_L(string, Indent, 2, ?ID(Str))])])]; +xml_gen({Name, atom}, Atom, Indent) -> + [?XML(member, Indent, + [?XML_L(name, Indent, 1, ?ID_A(Name)), + ?XML(value, Indent, 1, + [?XML_L(string, Indent, 2, ?ID(atom_to_list(Atom)))])])]; +xml_gen({Name, {tuple, Fields}}, Tuple, Indent) -> + NewIndent = <<" ", Indent/binary>>, + Res = lists:map(fun({A, B}) -> xml_gen(A, B, NewIndent) end, lists:zip(Fields, tuple_to_list(Tuple))), + [?XML(member, Indent, + [?XML_L(name, Indent, 1, ?ID_A(Name)), + ?XML(value, Indent, 1, [?XML(struct, NewIndent, Res)])])]; +xml_gen({Name, {list, ElDesc}}, List, Indent) -> + Ind1 = <<" ", Indent/binary>>, + Ind2 = <<" ", Ind1/binary>>, + Res = lists:map(fun(E) -> [?XML(value, Ind1, [?XML(struct, Ind1, 1, xml_gen(ElDesc, E, Ind2))])] end, List), + [?XML(member, Indent, + [?XML_L(name, Indent, 1, ?ID_A(Name)), + ?XML(value, Indent, 1, [?XML(array, Indent, 2, [?XML(data, Indent, 3, Res)])])])]. + +xml_call(Name, ArgsDesc, Values) -> + Ind = <<"">>, + Res = lists:map(fun({A, B}) -> xml_gen(A, B, <<" ">>) end, lists:zip(ArgsDesc, Values)), + [?XML(methodCall, Ind, + [?XML_L(methodName, Ind, 1, ?ID_A(Name)), + ?XML(params, Ind, 1, + [?XML(param, Ind, 2, + [?XML(value, Ind, 3, + [?XML(struct, Ind, 4, Res)])])])])]. + +generate_example_input({_Name, integer}, {LastStr, LastNum}) -> + {LastNum+1, {LastStr, LastNum+1}}; +generate_example_input({_Name, string}, {LastStr, LastNum}) -> + {string:chars(LastStr+1, 5), {LastStr+1, LastNum}}; +generate_example_input({_Name, binary}, {LastStr, LastNum}) -> + {iolist_to_binary(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}}; +generate_example_input({_Name, atom}, {LastStr, LastNum}) -> + {list_to_atom(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}}; +generate_example_input({_Name, {tuple, Fields}}, Data) -> + {R, D} = lists:foldl(fun(Field, {Res2, Data2}) -> + {Res3, Data3} = generate_example_input(Field, Data2), + {[Res3 | Res2], Data3} + end, {[], Data}, Fields), + {list_to_tuple(lists:reverse(R)), D}; +generate_example_input({_Name, {list, Desc}}, Data) -> + {R1, D1} = generate_example_input(Desc, Data), + {R2, D2} = generate_example_input(Desc, D1), + {[R1, R2], D2}. + + +gen_calls(#ejabberd_commands{args_example=none, args=ArgsDesc} = C) -> + {R, D} = lists:foldl(fun(Arg, {Res2, Data2}) -> + {Res3, Data3} = generate_example_input(Arg, Data2), + {[Res3 | Res2], Data3} + end, {[], {$a-1, 0}}, ArgsDesc), + gen_calls(C#ejabberd_commands{args_example=lists:reverse(R)}); +gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc, name=Name}) -> + Perl = perl_call(Name, ArgsDesc, Values), + Java = java_call(Name, ArgsDesc, Values), + XML = xml_call(Name, ArgsDesc, Values), + [?TAG(ul, "code-samples-names", + [?TAG(li, <<"Java">>), + ?TAG(li, <<"Perl">>), + ?TAG(li, <<"XML">>)]), + ?TAG(ul, "code-samples", + [?TAG(li, ?TAG(pre, Java)), + ?TAG(li, ?TAG(pre, Perl)), + ?TAG(li, ?TAG(pre, XML))])]. + +gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc, + args=Args, args_desc=ArgsDesc, + result=Result, result_desc=ResultDesc}=Cmd) -> + LDesc = case LongDesc of + "" -> Desc; + _ -> LongDesc + end, + ArgsText = case ArgsDesc of + none -> + [?TAG(ul, "args-list", lists:map(fun({AName, Type}) -> + [?TAG(li, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>, + ?RAW(io_lib:format("~p", [Type]))])] + end, Args))]; + _ -> + [?TAG(dl, "args-list", lists:map(fun({{AName, Type}, ADesc}) -> + [?TAG(dt, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>, + ?RAW(io_lib:format("~p", [Type]))]), + ?TAG(dd, ?RAW(ADesc))] + end, lists:zip(Args, ArgsDesc)))] + end, + ResultText = case ResultDesc of + none -> + [?RAW(io_lib:format("~p", [Result]))]; + _ -> + [?RAW(io_lib:format("~p", [Result])), + ?TAG_R(p, ResultDesc)] + end, + + [?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]), + ?TAG(p, ?RAW(LDesc)), + ?TAG(h2, <<"Arguments:">>), + ArgsText, + ?TAG(h2, <<"Result:">>), + ResultText, + ?TAG(h2, <<"Examples:">>), + gen_calls(Cmd)]. + +generate_output(File, RegExp) -> + Cmds = lists:map(fun({N, _, _}) -> + ejabberd_commands:get_command_definition(N) + end, ejabberd_commands:list_commands()), + {ok, RE} = re:compile(RegExp), + Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) -> + re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse + re:run(atom_to_list(Module), RE, [{capture, none}]) == match + end, Cmds), + Cmds3 = lists:sort(fun(#ejabberd_commands{name=N1}, #ejabberd_commands{name=N2}) -> + N1 =< N2 + end, Cmds2), + Out = lists:map(fun(C) -> gen_doc(C) end, Cmds3), + {ok, Fh} = file:open(File, [write]), + io:format(Fh, "~s", [[html_pre(), Out, html_post()]]), + file:close(Fh), + ok. + +html_pre() -> + "<!DOCTYPE> +<html> + <head> + <meta http-equiv='content-type' content='text/html; charset=utf-8' /> + <style> + body { + margin: 0 auto; + font-family: Georgia, Palatino, serif; + color: #000; + line-height: 1; + max-width: 80%; + padding: 10px; + } + h1, h2, h3, h4 { + color: #111111; + font-weight: 400; + } + h1, h2, h3, h4, h5, p { + margin-bottom: 24px; + padding: 0; + } + h1 { + margin-top: 80px; + font-size: 36px; + } + h2 { + font-size: 24px; + margin: 24px 0 6px; + } + ul, ol { + padding: 0; + margin: 0; + } + li { + line-height: 24px; + } + li ul, li ul { + margin-left: 24px; + } + p, ul, ol { + font-size: 16px; + line-height: 24px; + max-width: 80%; + } + .id {color: #bbb} + .lit {color: #aaa} + .op {color: #9f9} + .str {color: #f00} + .num {color: white} + .field {color: #faa} + .kw {font-weight: bold; color: #ff6} + .code-samples li { + font-family: Consolas, Monaco, Andale Mono, monospace; + line-height: 1.5; + font-size: 13px; + background: #333; + overflow: auto; + margin: 0; + padding: 0; + } + .code-samples pre { + margin: 0; + padding: 0.5em 0.5em; + } + .code-samples { + position: relative; + } + .code-samples-names li { + display: block; + } + .code-samples-names li { + color: white; + background: #9c1; + float: left; + margin: 0 1px -4px 0; + position: relative; + z-index: 1; + border: 4px solid #9c1; + border-bottom: 0; + border-radius: 9px 9px 0 0; + padding: 0.2em 1em 4px 1em; + cursor: pointer; + } + .code-samples-names li.selected { + background: #333; + } + .code-samples { + clear: both; + } + .code-samples li { + display: block; + border: 4px solid #9c1; + border-radius: 9px; + border-top-left-radius: 0; + width: 100%; + } + .args-list li { + display: block; + } + </style> +</head> + <body> + <script> + function changeTab2(tab, addClickHandlers) { + var els = tab.parentNode.childNodes; + var els2 = tab.parentNode.nextSibling.childNodes; + for (var i = 0; i < els.length; i++) { + if (addClickHandlers) + els[i].addEventListener('click', changeTab, false); + + if (els[i] == tab) { + els[i].setAttribute('class', 'selected'); + els2[i].style.display = 'block'; + } else { + els[i].removeAttribute('class'); + els2[i].style.display = 'none'; + } + } + } + function changeTab(event) { + changeTab2(event.target); + } + </script>". + +html_post() -> +"<script> + var ul = document.getElementsByTagName('ul'); + for (var i = 0; i < ul.length; i++) { + if (ul[i].className == 'code-samples-names') + changeTab2(ul[i].firstChild, true); + } + </script> + </body> +</html>". diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index fefb5591c..23f2935c6 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -177,7 +177,11 @@ listen_tcp(PortIP, Module, SockOpts, Port, IPS) -> catch _:_ -> [] end, - Res = gen_tcp:listen(Port, [binary, + DeliverAs = case Module of + ejabberd_xmlrpc -> list; + _ -> binary + end, + Res = gen_tcp:listen(Port, [DeliverAs, {packet, 0}, {active, false}, {reuseaddr, true}, diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_odbc.erl index 1308e8665..261d46fda 100644 --- a/src/ejabberd_odbc.erl +++ b/src/ejabberd_odbc.erl @@ -510,7 +510,7 @@ mysql_connect(Server, Port, DB, Username, Password) -> binary_to_list(DB), fun log/3) of {ok, Ref} -> - mysql_conn:fetch(Ref, [<<"set names 'utf8';">>], + mysql_conn:fetch(Ref, [<<"set names 'utf8' collate 'utf8_bin';">>], self()), mysql_conn:fetch(Ref, [<<"SET SESSION query_cache_type=1;">>], self()), diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 5352533cd..7498529e6 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -45,11 +45,13 @@ opts = [] :: opts() | '_' | '$2'}). -type opts() :: [{atom(), any()}]. +-type db_type() :: odbc | mnesia | riak. -callback start(binary(), opts()) -> any(). -callback stop(binary()) -> any(). -export_type([opts/0]). +-export_type([db_type/0]). %%behaviour_info(callbacks) -> [{start, 2}, {stop, 1}]; %%behaviour_info(_Other) -> undefined. @@ -192,7 +194,7 @@ get_opt_host(Host, Opts, Default) -> Val = get_opt(host, Opts, fun iolist_to_binary/1, Default), ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host). --spec db_type(opts()) -> odbc | mnesia | riak. +-spec db_type(opts()) -> db_type(). db_type(Opts) -> get_opt(db_type, Opts, @@ -203,7 +205,7 @@ db_type(Opts) -> end, mnesia). --spec db_type(binary(), atom()) -> odbc | mnesia | riak. +-spec db_type(binary(), atom()) -> db_type(). db_type(Host, Module) -> get_module_opt(Host, Module, db_type, diff --git a/src/mod_admin_p1.erl b/src/mod_admin_p1.erl index 69019a878..cc79ca841 100644 --- a/src/mod_admin_p1.erl +++ b/src/mod_admin_p1.erl @@ -1083,7 +1083,7 @@ send_stanza(FromJID, ToJID, StanzaStr) -> get_roster2(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), - mod_roster:get_user_roster([], {LUser, LServer}). + ejabberd_hooks:run_fold(roster_get, LServer, [], [{LUser, LServer}]). add_rosteritem2(User, Server, JID, Nick, Group, Subscription, Push) -> diff --git a/src/mod_ejabberd_log.erl b/src/mod_ejabberd_log.erl new file mode 100644 index 000000000..618b19725 --- /dev/null +++ b/src/mod_ejabberd_log.erl @@ -0,0 +1,287 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_ejabberd_log.erl +%%% Author : Mickael Remond <mremond@process-one.net> +%%% Purpose : Generate generic log file for analysis +%%% Created : 10 Nov 2012 by Mickael Remond <mremond@process-one.net> +%%% +%%% ejabberd, Copyright (C) 2002-2012 Process-one +%%% +%%% Starting module manually: +%%% mod_admin_p1:restart_module("mod_ejabberd_log", "localhost"). +%%% or +%%% mod_admin_p1:restart_module("mod_ejabberd_log", "push.bbc.co.uk"). +%%% +%%% Options: {dir, "/tmp/"} +%%% +%%% example module config: +%%% {mod_ejabberd_log, [{dir, "/home/webadmin/xmpp-logs"}]}, +%%%---------------------------------------------------------------------- + +-module(mod_ejabberd_log). +-author('mremond@process-one.net'). + +-behaviour(gen_server). +-behaviour(gen_mod). + +%% API +-export([start_link/2, + start/2, + stop/1, + on_connect/3, + on_disconnect/3, + reopen_log/1, + packet/4]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). + +-record(state, {host, + filename, + dir, + fd, + day, + disabled}). + +-define(PROCNAME, ejabberd_mod_ejabberd_log). + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). + +start(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + ChildSpec = + {Proc, + {?MODULE, start_link, [Host, Opts]}, + temporary, + 1000, + worker, + [?MODULE]}, + supervisor:start_child(ejabberd_sup, ChildSpec). + +stop(Host) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:call(Proc, stop), + supervisor:delete_child(ejabberd_sup, Proc). + +%% SID is of form {Now, Pid} +%% Now being {A,B,C} +%% connect event is of the form: +%% 2012-11-10 15:51:18|DOMAIN|ERL_NODE|JID|CONN|User connect|IP +on_connect(_SID, JID, Info) -> + IP = proplists:get_value(ip, Info), + log(JID#jid.lserver, JID, conn, [formated_ip(IP)]). + +%% SID is of form {Now, Pid} +%% Now being {A,B,C} +%% disconnect event is of the form: +%% 2012-11-10 15:52:18|DOMAIN|ERL_NODE|JID|DISC|User disconnect|IP|SESSION_DURATION +on_disconnect(_SID = {Now, _Pid}, JID, Info) -> + IP = proplists:get_value(ip, Info), + + Timestamp1 = calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(Now)), + Timestamp2 = calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(os:timestamp())), + DurationInSecond = Timestamp2 - Timestamp1, + + log(JID#jid.lserver, JID, disc, [formated_ip(IP), integer_to_list(DurationInSecond)]). + +reopen_log(Host) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:cast(Proc, reopen). + +packet(_DebugFlag, JID, To, #xmlel{name = <<"message">>, attrs = Attrs}) -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"normal">> -> log(JID#jid.lserver, JID, chat, [jlib:jid_to_string(To)]); + <<"chat">> -> log(JID#jid.lserver, JID, chat, [jlib:jid_to_string(To)]); + <<"groupchat">> -> log(JID#jid.lserver, JID, groupchat, [jlib:jid_to_string(To)]); + _ -> ok + end; +packet(_, _, _, _) -> ok. + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([Host, Opts]) -> + Dir = gen_mod:get_opt(dir, Opts, fun(A) when is_list(A) -> A end, "/tmp/"), + Day = day(), + Filename = filename(Dir, Day), + Disabled = gen_mod:get_opt(disabled, Opts, fun(A) when is_list(A) -> A end, []), + {ok, FD} = file:open(Filename, [append, raw]), + ejabberd_hooks:add(sm_register_connection_hook, Host, + ?MODULE, on_connect, 50), + ejabberd_hooks:add(sm_remove_connection_hook, Host, + ?MODULE, on_disconnect, 50), + ejabberd_hooks:add(reopen_log_hook, Host, + ?MODULE, reopen_log, 55), + ejabberd_hooks:add(user_send_packet, Host, + ?MODULE, packet, 75), + {ok, #state{host = Host, + dir = Dir, + filename = Filename, + fd = FD, + day = Day, + disabled = Disabled}}. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call(stop, _From, State) -> + {stop, normal, ok, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast({log, JID, Event, Specific}, State) -> + case day() of + %% Same day + Day when Day == State#state.day -> + do_log(State, JID, Event, Specific), + {noreply, State}; + %% Another day, change log file + NewDay -> + Filename = filename(State#state.dir, NewDay), + file:close(State#state.fd), + {ok, FD} = file:open(Filename, [append, raw]), + NewState = State#state{filename = Filename, fd = FD, day = NewDay}, + do_log(NewState, JID, Event, Specific), + {noreply, NewState} + end; +handle_cast(reopen, State) -> + file:close(State#state.fd), + {ok, FD} = file:open(State#state.filename, [append, raw]), + {noreply, State#state{fd = FD}}; +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(_Reason, State) -> + Host = State#state.host, + ejabberd_hooks:delete(sm_register_connection_hook, Host, + ?MODULE, on_connect, 50), + ejabberd_hooks:delete(sm_remove_connection_hook, Host, + ?MODULE, on_disconnect, 50), + ejabberd_hooks:delete(reopen_log_hook, Host, + ?MODULE, reopen_log, 55), + ejabberd_hooks:delete(user_send_packet, Host, + ?MODULE, packet, 75), + file:close(State#state.fd), + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +log(Host, JID, Event, Specific) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:cast(Proc, {log, JID, Event, Specific}). + +do_log(State, JID, Event, Specific) -> + case lists:member(Event, State#state.disabled) of + true -> + ok; + false -> + TimeStamp = timestamp(), + Host = State#state.host, + User = jlib:jid_to_string(jlib:jid_remove_resource( + jlib:jid_tolower(JID))), + {EventID, EventName} = event(Event), + Node = atom_to_list(erlang:node()), + Mandatory = join(lists:map( + fun escape/1, + [TimeStamp, Host, Node, User, EventID, EventName]), + "|"), + Optional = join(lists:map(fun escape/1, Specific), "|"), + Log = [Mandatory, $|, Optional, $\n], + file:write(State#state.fd, Log) + end. + +event(chat) -> {"MSG", "User send message"}; +event(groupchat) -> {"MUC", "User send groupchat message"}; +event(conn) -> {"CONN", "User connect"}; +event(disc) -> {"DISC", "User disconnect"}. + +day() -> + {{Year,Month,Day},_} = calendar:now_to_datetime(os:timestamp()), + lists:flatten(io_lib:format("~4w~.2.0w~.2.0w", [Year, Month, Day])). + +filename(Dir, Day) -> + filename:join(Dir, Day ++ "-xmpp.log"). + +escape(S) when is_binary(S) -> + escape(binary_to_list(S)); +escape(S) when is_list(S) -> + [case C of + $\\ -> "\\\\"; + $| -> "\\|"; %%% " + $\n -> "\\n"; + _ -> C + end || C <- S]. + +join(List, Sep) -> + lists:foldr(fun(A, "") -> A; + (A, Acc) -> A ++ Sep ++ Acc + end, "", List). + +timestamp() -> + {{Year, Month, Day}, {Hour, Minute, Second}} = + calendar:now_to_local_time(now()), + lists:flatten( + io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", + [Year, Month, Day, Hour, Minute, Second])). + +formated_ip(IP) -> + case IP of + {{I1,I2,I3,I4}, _} -> io_lib:format("~p.~p.~p.~p", [I1,I2,I3,I4]); + {{0,0,0,0,0,0,I7,I8}, _} -> io_lib:format("::~p.~p.~p.~p", [I7 bsr 8, I7 band 255, I8 bsr 8, I8 band 255]); + {{I1,I2,I3,I4,I5,I6,I7,I8}, _} -> io_lib:format("~.16b:~.16b:~.16b:~.16b:~.16b:~.16b:~.16b:~.16b", [I1,I2,I3,I4,I5,I6,I7,I8]); + _ -> "unknown" + end. diff --git a/src/mod_register.erl b/src/mod_register.erl index ec5ebfa36..66209bed3 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -397,6 +397,8 @@ try_register(User, Server, Password, SourceRaw, Lang) -> {error, ?ERR_JID_MALFORMED}; {error, not_allowed} -> {error, ?ERR_NOT_ALLOWED}; + {error, too_many_users} -> + {error, ?ERR_NOT_ALLOWED}; {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR} end diff --git a/src/mod_roster.erl b/src/mod_roster.erl index f72112009..eb22194f6 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -49,6 +49,9 @@ roster_versioning_enabled/1, roster_version/2, record_to_string/1, groups_to_string/1]). +%% For benchmarking +-export([create_rosters/4]). + -include("ejabberd.hrl"). -include("logger.hrl"). @@ -1609,3 +1612,48 @@ export(_Server) -> (_Host, _R) -> [] end}]. + +%% For benchmarks +make_roster_range(I, Total) -> + lists:foldl( + fun(K, Range) -> + Next = if I+K > Total -> I+K-Total; + true -> I+K + end, + Prev = if I-K =< 0 -> Total+I-K; + true -> I-K + end, + [Next, Prev | Range] + end, [], lists:seq(1, 9)). + +-spec create_rosters(binary(), binary(), pos_integer(), gen_mod:db_type()) -> any(). + +create_rosters(UserPattern, Server, Total, DBType) -> + lists:foreach( + fun(I) -> + LUser = jlib:nodeprep( + iolist_to_binary([UserPattern, integer_to_list(I)])), + LServer = jlib:nameprep(Server), + Range = make_roster_range(I, Total), + lists:foreach( + fun(R) -> + Contact = jlib:nodeprep( + iolist_to_binary( + [UserPattern, integer_to_list(R)])), + LJID = {Contact, LServer, <<"">>}, + RItem = #roster{subscription = both, + us = {LUser, LServer}, + usj = {LUser, LServer, LJID}, + jid = LJID}, + case DBType of + riak -> + ejabberd_riak:put( + RItem, + [{'2i', [{<<"us">>, {LUser, LServer}}]}]); + mnesia -> + mnesia:dirty_write(RItem); + odbc -> + erlang:error(odbc_not_supported) + end + end, Range) + end, lists:seq(1, Total)). diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index 021ad31d3..a7dd7f1ff 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -581,17 +581,16 @@ is_group_enabled(Host1, Group1) -> {Host, Group} = split_grouphost(Host1, Group1), case get_group_opts(Host, Group) of error -> false; - Opts -> not lists:member(disabled, Opts) + Opts -> is_group_enabled(Opts) end. -get_group_opt(Host, Group, Opt, Default) -> - case get_group_opts(Host, Group) of - error -> Default; - Opts -> - case lists:keysearch(Opt, 1, Opts) of - {value, {_, Val}} -> Val; - false -> Default - end +is_group_enabled(Opts) -> + not lists:member(disabled, Opts). + +get_group_opt(Opt, Opts, Default) -> + case lists:keysearch(Opt, 1, Opts) of + {value, {_, Val}} -> Val; + false -> Default end. get_online_users(Host) -> @@ -600,12 +599,13 @@ get_online_users(Host) -> get_group_users(Host1, Group1) -> {Host, Group} = split_grouphost(Host1, Group1), - case get_group_opt(Host, Group, all_users, false) of + Opts = get_group_opts(Host, Group), + case get_group_opt(all_users, Opts, false) of true -> ejabberd_auth:get_vh_registered_users(Host); false -> [] end ++ - case get_group_opt(Host, Group, online_users, false) of + case get_group_opt(online_users, Opts, false) of true -> get_online_users(Host); false -> [] end @@ -661,20 +661,21 @@ get_group_explicit_users(Host, Group, odbc) -> get_group_name(Host1, Group1) -> {Host, Group} = split_grouphost(Host1, Group1), - get_group_opt(Host, Group, name, Group). + Opts = get_group_opts(Host, Group), + get_group_opt(name, Opts, Group). get_special_users_groups(Host) -> - lists:filter(fun (Group) -> - get_group_opt(Host, Group, all_users, false) orelse - get_group_opt(Host, Group, online_users, false) + lists:filter(fun ({_Group, Opts}) -> + get_group_opt(all_users, Opts, false) orelse + get_group_opt(online_users, Opts, false) end, - list_groups(Host)). + groups_with_opts(Host)). get_special_users_groups_online(Host) -> - lists:filter(fun (Group) -> - get_group_opt(Host, Group, online_users, false) + lists:filter(fun ({_Group, Opts}) -> + get_group_opt(online_users, Opts, false) end, - list_groups(Host)). + groups_with_opts(Host)). displayed_groups(GroupsOpts, SelectedGroupsOpts) -> DisplayedGroups = lists:usort(lists:flatmap(fun @@ -743,24 +744,20 @@ get_user_displayed_groups(LUser, LServer, GroupsOpts, get_user_displayed_groups(US) -> Host = element(2, US), - DisplayedGroups1 = lists:usort(lists:flatmap(fun - (Group) -> - case - is_group_enabled(Host, - Group) - of - true -> - get_group_opt(Host, - Group, - displayed_groups, - []); - false -> [] - end - end, - get_user_groups(US))), - [Group - || Group <- DisplayedGroups1, - is_group_enabled(Host, Group)]. + DisplayedGroups1 = + lists:usort( + lists:flatmap( + fun(Group) -> + Opts = get_group_opts(Host, Group), + case is_group_enabled(Opts) of + true -> + get_group_opt(displayed_groups, Opts, []); + false -> + [] + end + end, + get_user_groups(US))), + [Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)]. is_user_in_group(US, Group, Host) -> is_user_in_group(US, Group, Host, @@ -803,7 +800,7 @@ is_user_in_group(US, Group, Host, odbc) -> add_user_to_group(Host, US, Group) -> {LUser, LServer} = US, - case ejabberd_regexp:run(LUser, <<"^@.+@$">>) of + case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of match -> GroupOpts = (?MODULE):get_group_opts(Host, Group), MoreGroupOpts = case LUser of @@ -813,13 +810,14 @@ add_user_to_group(Host, US, Group) -> end, (?MODULE):set_group_opts(Host, Group, GroupOpts ++ MoreGroupOpts); - nomatch -> - push_user_to_displayed(LUser, LServer, Group, Host, - both), - push_displayed_to_user(LUser, LServer, Group, Host, - both), - add_user_to_group(Host, US, Group, - gen_mod:db_type(Host, ?MODULE)) + nomatch -> + Res = add_user_to_group(Host, US, Group, + gen_mod:db_type(Host, ?MODULE)), + push_user_to_displayed(LUser, LServer, Group, Host, + both), + push_displayed_to_user(LUser, LServer, Group, Host, + both), + Res end. add_user_to_group(Host, US, Group, mnesia) -> @@ -855,7 +853,7 @@ push_displayed_to_user(LUser, LServer, Group, Host, remove_user_from_group(Host, US, Group) -> {LUser, LServer} = US, - case ejabberd_regexp:run(LUser, <<"^@.+@$">>) of + case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of match -> GroupOpts = (?MODULE):get_group_opts(Host, Group), NewGroupOpts = case LUser of @@ -948,22 +946,48 @@ push_user_to_displayed(LUser, LServer, Group, Host, GroupsOpts = groups_with_opts(Host), GroupOpts = proplists:get_value(Group, GroupsOpts, []), GroupName = proplists:get_value(name, GroupOpts, Group), - DisplayedToGroupsOpts = displayed_to_groups(Group, - Host), - [push_user_to_group(LUser, LServer, GroupD, Host, - GroupName, Subscription) + DisplayedToGroupsOpts = displayed_to_groups(Group, Host), + Users = + lists:usort( + lists:append([get_group_users(Host, GroupD) + || {GroupD, _Opts} <- DisplayedToGroupsOpts])), + UserGroups = get_user_groups({LUser, LServer}), + [push_user_to_users(LUser, LServer, GroupD, Host, + GroupName, Subscription, UserGroups, Users) || {GroupD, _Opts} <- DisplayedToGroupsOpts]. -push_user_to_group(LUser, LServer, Group, Host, - GroupName, Subscription) -> - lists:foreach(fun ({U, S}) - when (U == LUser) and (S == LServer) -> - ok; - ({U, S}) -> - push_roster_item(U, S, LUser, LServer, GroupName, - Subscription) - end, - get_group_users(Host, Group)). +push_user_to_users(LUser, LServer, Group, Host, + GroupName, Subscription, UserGroups, Users) -> + lists:foreach( + fun({U, S}) + when (U == LUser) and (S == LServer) -> + ok; + ({U, S} = US) -> + case {Subscription, UserGroups} of + {remove, []} -> + push_roster_item(U, S, LUser, LServer, GroupName, + Subscription); + {both, [Group]} -> + push_roster_item(U, S, LUser, LServer, GroupName, + Subscription); + _ -> + UGroups = get_user_displayed_groups(US), + Groups = + lists:filter( + fun(G) -> + lists:member(G, UGroups) + end, UserGroups), + Subscription1 = + case Groups of + [] -> remove; + _ -> both + end, + push_roster_item_with_groups( + U, S, LUser, LServer, Groups, + Subscription1) + end + end, + Users). displayed_to_groups(GroupName, LServer) -> GroupsOpts = groups_with_opts(LServer), @@ -1002,6 +1026,16 @@ push_roster_item(User, Server, ContactU, ContactS, push_item(User, Server, jlib:make_jid(<<"">>, Server, <<"">>), Item). +push_roster_item_with_groups(User, Server, ContactU, ContactS, + Groups, Subscription) -> + Item = #roster{usj = + {User, Server, {ContactU, ContactS, <<"">>}}, + us = {User, Server}, jid = {ContactU, ContactS, <<"">>}, + name = <<"">>, subscription = Subscription, ask = none, + groups = Groups}, + push_item(User, Server, + jlib:make_jid(<<"">>, Server, <<"">>), Item). + item_to_xml(Item) -> Attrs1 = [{<<"jid">>, jlib:jid_to_string(Item#roster.jid)}], @@ -1461,12 +1495,15 @@ commands() -> {groups, {list, {group, string}}}]}}} ]. -code_to_restuple({atomic, ok}) -> +code_to_restuple({atomic, _}) -> {ok, ""}; code_to_restuple({_, Res}) when is_binary(Res) -> - {error, Res}; + {false, binary_to_list(Res)}; code_to_restuple({_, Res}) -> - {error, list_to_binary(io_lib:format("~p", [Res]))}. + {false, lists:flatten(io_lib:format("~p", [Res]))}; +code_to_restuple(_) -> + {false, ""}. + command_group_create(Host, Id, Name, Description, DisplayedGroups) -> Opts = [{name, Name}, |