aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/ejabberd_commands.hrl13
-rw-r--r--sql/mysql.sql2
-rw-r--r--src/ejabberd_auth.erl75
-rw-r--r--src/ejabberd_c2s.erl11
-rw-r--r--src/ejabberd_commands.erl22
-rw-r--r--src/ejabberd_commands_doc.erl415
-rw-r--r--src/ejabberd_listener.erl6
-rw-r--r--src/ejabberd_odbc.erl2
-rw-r--r--src/gen_mod.erl6
-rw-r--r--src/mod_admin_p1.erl2
-rw-r--r--src/mod_ejabberd_log.erl287
-rw-r--r--src/mod_register.erl2
-rw-r--r--src/mod_roster.erl48
-rw-r--r--src/mod_shared_roster.erl163
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},