aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ejabberd_app.erl4
-rw-r--r--src/ejabberd_auth.erl10
-rw-r--r--src/ejabberd_auth_odbc.erl425
-rw-r--r--src/ejabberd_c2s.erl49
-rw-r--r--src/ejabberd_commands.erl8
-rw-r--r--src/ejabberd_config.erl38
-rw-r--r--src/ejabberd_http.erl18
-rw-r--r--src/ejabberd_http_ws.erl20
-rw-r--r--src/ejabberd_listener.erl12
-rw-r--r--src/ejabberd_logger.erl4
-rw-r--r--src/ejabberd_odbc.erl1
-rw-r--r--src/ejabberd_receiver.erl8
-rw-r--r--src/ejabberd_riak_sup.erl2
-rw-r--r--src/ejabberd_sm.erl36
-rw-r--r--src/ejabberd_sm_redis.erl3
-rw-r--r--src/ejabberd_update.erl7
-rw-r--r--src/ejabberd_websocket.erl5
-rw-r--r--src/ejabberd_xmlrpc.erl2
-rw-r--r--src/gen_mod.erl34
-rw-r--r--src/mod_admin_extra.erl1583
-rw-r--r--src/mod_announce.erl2
-rw-r--r--src/mod_caps.erl2
-rw-r--r--src/mod_irc.erl2
-rw-r--r--src/mod_last.erl2
-rw-r--r--src/mod_muc.erl2
-rw-r--r--src/mod_muc_admin.erl888
-rw-r--r--src/mod_offline.erl26
-rw-r--r--src/mod_privacy.erl2
-rw-r--r--src/mod_private.erl2
-rw-r--r--src/mod_pubsub.erl34
-rw-r--r--src/mod_pubsub_odbc.erl34
-rw-r--r--src/mod_roster.erl2
-rw-r--r--src/mod_shared_roster.erl2
-rw-r--r--src/mod_vcard.erl2
-rw-r--r--src/mod_vcard_xupdate.erl2
-rw-r--r--src/odbc_queries.erl39
-rw-r--r--src/scram.erl19
-rw-r--r--src/translate.erl2
38 files changed, 3048 insertions, 285 deletions
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index e892128b1..fabe2d3e0 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -65,8 +65,8 @@ start(normal, _Args) ->
%ejabberd_debug:eprof_start(),
%ejabberd_debug:fprof_start(),
maybe_add_nameservers(),
- start_modules(),
ext_mod:start(),
+ start_modules(),
ejabberd_listener:start_listeners(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
Sup;
@@ -110,6 +110,7 @@ loop() ->
end.
db_init() ->
+ ejabberd_config:env_binary_to_list(mnesia, dir),
MyNode = node(),
DbNodes = mnesia:system_info(db_nodes),
case lists:member(MyNode, DbNodes) of
@@ -238,6 +239,7 @@ set_loglevel_from_config() ->
ejabberd_logger:set(Level).
start_apps() ->
+ crypto:start(),
ejabberd:start_app(sasl),
ejabberd:start_app(ssl),
ejabberd:start_app(p1_yaml),
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 34d4a52b2..9985dd3de 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -71,7 +71,7 @@
-callback get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}].
-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(binary(), binary()) -> false | binary() | {binary(), binary(), binary(), integer()}.
-callback get_password_s(binary(), binary()) -> binary().
start() ->
@@ -267,7 +267,7 @@ get_vh_registered_users_number(Server, Opts) ->
end,
auth_modules(Server))).
--spec get_password(binary(), binary()) -> false | binary().
+-spec get_password(binary(), binary()) -> false | binary() | {binary(), binary(), binary(), integer()}.
get_password(User, Server) ->
lists:foldl(fun (M, false) ->
@@ -425,6 +425,10 @@ auth_modules() ->
%% Return the list of authenticated modules for a given host
auth_modules(Server) ->
LServer = jlib:nameprep(Server),
+ Default = case gen_mod:default_db(LServer) of
+ mnesia -> internal;
+ DBType -> DBType
+ end,
Methods = ejabberd_config:get_option(
{auth_method, LServer},
fun(V) when is_list(V) ->
@@ -432,7 +436,7 @@ auth_modules(Server) ->
V;
(V) when is_atom(V) ->
[V]
- end, []),
+ end, [Default]),
[jlib:binary_to_atom(<<"ejabberd_auth_",
(jlib:atom_to_binary(M))/binary>>)
|| M <- Methods].
diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl
index aea039c1b..881b86cca 100644
--- a/src/ejabberd_auth_odbc.erl
+++ b/src/ejabberd_auth_odbc.erl
@@ -38,101 +38,186 @@
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0,
- plain_password_required/0]).
+ plain_password_required/0,
+ convert_to_scram/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
+-define(SALT_LENGTH, 16).
+
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(_Host) -> ok.
-plain_password_required() -> false.
+plain_password_required() ->
+ case is_scrammed() of
+ false -> false;
+ true -> true
+ end.
-store_type() -> plain.
+store_type() ->
+ case is_scrammed() of
+ false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
+ true -> scram %% allows: PLAIN SCRAM
+ end.
%% @spec (User, Server, Password) -> true | false | {error, Error}
check_password(User, Server, Password) ->
- case jlib:nodeprep(User) of
- error -> false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- try odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[Password]]} ->
- Password /= <<"">>;
- {selected, [<<"password">>], [[_Password2]]} ->
- false; %% Password is not correct
- {selected, [<<"password">>], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end
+ LServer = jlib:nameprep(Server),
+ LUser = jlib:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ Username = ejabberd_odbc:escape(LUser),
+ case is_scrammed() of
+ true ->
+ try odbc_queries:get_password_scram(LServer, Username) of
+ {selected, [<<"password">>, <<"serverkey">>,
+ <<"salt">>, <<"iterationcount">>],
+ [[StoredKey, ServerKey, Salt, IterationCount]]} ->
+ Scram =
+ #scram{storedkey = StoredKey,
+ serverkey = ServerKey,
+ salt = Salt,
+ iterationcount = jlib:binary_to_integer(
+ IterationCount)},
+ is_password_scram_valid(Password, Scram);
+ {selected, [<<"password">>, <<"serverkey">>,
+ <<"salt">>, <<"iterationcount">>], []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end;
+ false ->
+ try odbc_queries:get_password(LServer, Username) of
+ {selected, [<<"password">>], [[Password]]} ->
+ Password /= <<"">>;
+ {selected, [<<"password">>], [[_Password2]]} ->
+ false; %% Password is not correct
+ {selected, [<<"password">>], []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end
+ end
end.
%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
check_password(User, Server, Password, Digest,
DigestGen) ->
- case jlib:nodeprep(User) of
- error -> false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- try odbc_queries:get_password(LServer, Username) of
- %% Account exists, check if password is valid
- {selected, [<<"password">>], [[Passwd]]} ->
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- {selected, [<<"password">>], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end
+ LServer = jlib:nameprep(Server),
+ LUser = jlib:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ case is_scrammed() of
+ false ->
+ Username = ejabberd_odbc:escape(LUser),
+ try odbc_queries:get_password(LServer, Username) of
+ %% Account exists, check if password is valid
+ {selected, [<<"password">>], [[Passwd]]} ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ {selected, [<<"password">>], []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end;
+ true ->
+ false
+ end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
- case jlib:nodeprep(User) of
- error -> {error, invalid_jid};
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:set_password_t(LServer,
- Username, Pass)
- of
- {atomic, ok} -> ok;
- Other -> {error, Other}
- end
+ LServer = jlib:nameprep(Server),
+ LUser = jlib:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ {error, invalid_jid};
+ (LUser == <<>>) or (LServer == <<>>) ->
+ {error, invalid_jid};
+ true ->
+ Username = ejabberd_odbc:escape(LUser),
+ case is_scrammed() of
+ true ->
+ Scram = password_to_scram(Password),
+ case catch odbc_queries:set_password_scram_t(
+ LServer,
+ Username,
+ ejabberd_odbc:escape(Scram#scram.storedkey),
+ ejabberd_odbc:escape(Scram#scram.serverkey),
+ ejabberd_odbc:escape(Scram#scram.salt),
+ jlib:integer_to_binary(Scram#scram.iterationcount)
+ )
+ of
+ {atomic, ok} -> ok;
+ Other -> {error, Other}
+ end;
+ false ->
+ Pass = ejabberd_odbc:escape(Password),
+ case catch odbc_queries:set_password_t(LServer,
+ Username, Pass)
+ of
+ {atomic, ok} -> ok;
+ Other -> {error, Other}
+ end
+ end
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) ->
- case jlib:nodeprep(User) of
- error -> {error, invalid_jid};
- LUser ->
+ LServer = jlib:nameprep(Server),
+ LUser = jlib:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ {error, invalid_jid};
+ (LUser == <<>>) or (LServer == <<>>) ->
+ {error, invalid_jid};
+ true ->
Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:add_user(LServer, Username,
- Pass)
- of
- {updated, 1} -> {atomic, ok};
- _ -> {atomic, exists}
- end
+ case is_scrammed() of
+ true ->
+ Scram = password_to_scram(Password),
+ case catch odbc_queries:add_user_scram(
+ LServer,
+ Username,
+ ejabberd_odbc:escape(Scram#scram.storedkey),
+ ejabberd_odbc:escape(Scram#scram.serverkey),
+ ejabberd_odbc:escape(Scram#scram.salt),
+ jlib:integer_to_binary(Scram#scram.iterationcount)
+ ) of
+ {updated, 1} -> {atomic, ok};
+ _ -> {atomic, exists}
+ end;
+ false ->
+ Pass = ejabberd_odbc:escape(Password),
+ case catch odbc_queries:add_user(LServer, Username,
+ Pass)
+ of
+ {updated, 1} -> {atomic, ok};
+ _ -> {atomic, exists}
+ end
+ end
end.
dirty_get_registered_users() ->
@@ -175,29 +260,53 @@ get_vh_registered_users_number(Server, Opts) ->
end.
get_password(User, Server) ->
- case jlib:nodeprep(User) of
- error -> false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:get_password(LServer, Username)
- of
- {selected, [<<"password">>], [[Password]]} -> Password;
- _ -> false
- end
+ LServer = jlib:nameprep(Server),
+ LUser = jlib:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ Username = ejabberd_odbc:escape(LUser),
+ case is_scrammed() of
+ true ->
+ case catch odbc_queries:get_password_scram(
+ LServer, Username) of
+ {selected, [<<"password">>, <<"serverkey">>,
+ <<"salt">>, <<"iterationcount">>],
+ [[StoredKey, ServerKey, Salt, IterationCount]]} ->
+ {jlib:decode_base64(StoredKey),
+ jlib:decode_base64(ServerKey),
+ jlib:decode_base64(Salt),
+ jlib:binary_to_integer(IterationCount)};
+ _ -> false
+ end;
+ false ->
+ case catch odbc_queries:get_password(LServer, Username)
+ of
+ {selected, [<<"password">>], [[Password]]} -> Password;
+ _ -> false
+ end
+ end
end.
get_password_s(User, Server) ->
- case jlib:nodeprep(User) of
- error -> <<"">>;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:get_password(LServer, Username)
- of
- {selected, [<<"password">>], [[Password]]} -> Password;
- _ -> <<"">>
- end
+ LServer = jlib:nameprep(Server),
+ LUser = jlib:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ <<"">>;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ <<"">>;
+ true ->
+ case is_scrammed() of
+ false ->
+ Username = ejabberd_odbc:escape(LUser),
+ case catch odbc_queries:get_password(LServer, Username) of
+ {selected, [<<"password">>], [[Password]]} -> Password;
+ _ -> <<"">>
+ end;
+ true -> <<"">>
+ end
end.
%% @spec (User, Server) -> true | false | {error, Error}
@@ -234,23 +343,127 @@ remove_user(User, Server) ->
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
- case jlib:nodeprep(User) of
- error -> error;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
- LServer = jlib:nameprep(Server),
- F = fun () ->
- Result = odbc_queries:del_user_return_password(LServer,
- Username,
- Pass),
- case Result of
- {selected, [<<"password">>], [[Password]]} -> ok;
- {selected, [<<"password">>], []} -> not_exists;
- _ -> not_allowed
- end
- end,
- {atomic, Result} = odbc_queries:sql_transaction(LServer,
- F),
- Result
+ LServer = jlib:nameprep(Server),
+ LUser = jlib:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ error;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ error;
+ true ->
+ case is_scrammed() of
+ true ->
+ case check_password(User, Server, Password) of
+ true ->
+ remove_user(User, Server),
+ ok;
+ false -> not_allowed
+ end;
+ false ->
+ Username = ejabberd_odbc:escape(LUser),
+ Pass = ejabberd_odbc:escape(Password),
+ F = fun () ->
+ Result = odbc_queries:del_user_return_password(
+ LServer, Username, Pass),
+ case Result of
+ {selected, [<<"password">>],
+ [[Password]]} -> ok;
+ {selected, [<<"password">>],
+ []} -> not_exists;
+ _ -> not_allowed
+ end
+ end,
+ {atomic, Result} = odbc_queries:sql_transaction(
+ LServer, F),
+ Result
+ end
+ end.
+
+%%%
+%%% SCRAM
+%%%
+
+is_scrammed() ->
+ scram ==
+ ejabberd_config:get_option({auth_password_format, ?MYNAME},
+ fun(V) -> V end).
+
+password_to_scram(Password) ->
+ password_to_scram(Password,
+ ?SCRAM_DEFAULT_ITERATION_COUNT).
+
+password_to_scram(Password, IterationCount) ->
+ Salt = crypto:rand_bytes(?SALT_LENGTH),
+ SaltedPassword = scram:salted_password(Password, Salt,
+ IterationCount),
+ StoredKey =
+ scram:stored_key(scram:client_key(SaltedPassword)),
+ ServerKey = scram:server_key(SaltedPassword),
+ #scram{storedkey = jlib:encode_base64(StoredKey),
+ serverkey = jlib:encode_base64(ServerKey),
+ salt = jlib:encode_base64(Salt),
+ iterationcount = IterationCount}.
+
+is_password_scram_valid(Password, Scram) ->
+ IterationCount = Scram#scram.iterationcount,
+ Salt = jlib:decode_base64(Scram#scram.salt),
+ SaltedPassword = scram:salted_password(Password, Salt,
+ IterationCount),
+ StoredKey =
+ scram:stored_key(scram:client_key(SaltedPassword)),
+ jlib:decode_base64(Scram#scram.storedkey) == StoredKey.
+
+-define(BATCH_SIZE, 1000).
+
+set_password_scram_t(Username,
+ StoredKey, ServerKey, Salt, IterationCount) ->
+ odbc_queries:update_t(<<"users">>,
+ [<<"username">>,
+ <<"password">>,
+ <<"serverkey">>,
+ <<"salt">>,
+ <<"iterationcount">>],
+ [Username, StoredKey,
+ ServerKey, Salt,
+ IterationCount],
+ [<<"username='">>, Username,
+ <<"'">>]).
+
+convert_to_scram(Server) ->
+ LServer = jlib:nameprep(Server),
+ if
+ LServer == error;
+ LServer == <<>> ->
+ {error, {incorrect_server_name, Server}};
+ true ->
+ F = fun () ->
+ case ejabberd_odbc:sql_query_t(
+ [<<"select username, password from users where "
+ "iterationcount=0 limit ">>,
+ jlib:integer_to_binary(?BATCH_SIZE),
+ <<";">>]) of
+ {selected, [<<"username">>, <<"password">>], []} ->
+ ok;
+ {selected, [<<"username">>, <<"password">>], Rs} ->
+ lists:foreach(
+ fun([LUser, Password]) ->
+ Username = ejabberd_odbc:escape(LUser),
+ Scram = password_to_scram(Password),
+ set_password_scram_t(
+ Username,
+ ejabberd_odbc:escape(Scram#scram.storedkey),
+ ejabberd_odbc:escape(Scram#scram.serverkey),
+ ejabberd_odbc:escape(Scram#scram.salt),
+ jlib:integer_to_binary(Scram#scram.iterationcount)
+ )
+ end, Rs),
+ continue;
+ Err -> {bad_reply, Err}
+ end
+ end,
+ case odbc_queries:sql_transaction(LServer, F) of
+ {atomic, ok} -> ok;
+ {atomic, continue} -> convert_to_scram(Server);
+ {atomic, Error} -> {error, Error};
+ Error -> Error
+ end
end.
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 7632cb121..2ac28dbb6 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -1679,38 +1679,27 @@ handle_info({route, From, To,
Packet, in)
of
allow -> {true, Attrs, StateData};
- deny -> {false, Attrs, StateData}
+ deny ->
+ Err =
+ jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From,
+ Err),
+ {false, Attrs, StateData}
end;
_ -> {true, Attrs, StateData}
end,
- if Pass == exit ->
- %% When Pass==exit, NewState contains a string instead of a #state{}
- Lang = StateData#state.lang,
- send_element(StateData, ?SERRT_CONFLICT(Lang, NewState)),
- send_trailer(StateData),
- {stop, normal, StateData};
- Pass ->
+ if Pass ->
Attrs2 =
jlib:replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To), NewAttrs),
FixedPacket = #xmlel{name = Name, attrs = Attrs2, children = Els},
- FinalState =
- case ejabberd_hooks:run_fold(c2s_filter_packet_in,
- NewState#state.server, FixedPacket,
- [NewState#state.jid, From, To])
- of
- drop ->
- NewState;
- FinalPacket = #xmlel{} ->
- SentState = send_packet(NewState, FinalPacket),
- ejabberd_hooks:run(user_receive_packet,
- SentState#state.server,
- [SentState#state.jid, From, To,
- FinalPacket]),
- SentState
- end,
+ SentStateData = send_packet(NewState, FixedPacket),
+ ejabberd_hooks:run(user_receive_packet,
+ SentStateData#state.server,
+ [SentStateData#state.jid, From, To, FixedPacket]),
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
- fsm_next_state(StateName, FinalState);
+ fsm_next_state(StateName, SentStateData);
true ->
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
fsm_next_state(StateName, NewState)
@@ -1737,6 +1726,10 @@ handle_info(system_shutdown, StateName, StateData) ->
ok
end,
{stop, normal, StateData};
+handle_info({route_xmlstreamelement, El}, _StateName, StateData) ->
+ {next_state, NStateName, NStateData, _Timeout} =
+ session_established({xmlstreamelement, El}, StateData),
+ fsm_next_state(NStateName, NStateData);
handle_info({force_update_presence, LUser}, StateName,
#state{user = LUser, server = LServer} = StateData) ->
NewStateData = case StateData#state.pres_last of
@@ -2848,8 +2841,12 @@ send_stanza_and_ack_req(StateData, Stanza) ->
AckReq = #xmlel{name = <<"r">>,
attrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns}],
children = []},
- send_element(StateData, Stanza),
- send_element(StateData, AckReq).
+ case send_element(StateData, Stanza) of
+ ok ->
+ send_element(StateData, AckReq);
+ error ->
+ error
+ end.
mgmt_queue_add(StateData, El) ->
NewNum = case StateData#state.mgmt_stanzas_out of
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index c279f2d0f..a4f38e836 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -317,7 +317,13 @@ execute_command2(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
Function = Command#ejabberd_commands.function,
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
- apply(Module, Function, Arguments).
+ try apply(Module, Function, Arguments) of
+ Response ->
+ Response
+ catch
+ Problem ->
+ {error, Problem}
+ end.
-spec get_tags_commands() -> [{string(), [string()]}].
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 6fc6c9e34..5d1df5056 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -35,7 +35,8 @@
get_version/0, get_myhosts/0, get_mylang/0,
prepare_opt_val/4, convert_table_to_binary/5,
transform_options/1, collect_options/1,
- convert_to_yaml/1, convert_to_yaml/2]).
+ convert_to_yaml/1, convert_to_yaml/2,
+ env_binary_to_list/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -84,7 +85,7 @@ start() ->
%% If not specified, the default value 'ejabberd.yml' is assumed.
%% @spec () -> string()
get_ejabberd_config_path() ->
- case application:get_env(config) of
+ case get_env_config() of
{ok, Path} -> Path;
undefined ->
case os:getenv("EJABBERD_CONFIG_PATH") of
@@ -95,6 +96,18 @@ get_ejabberd_config_path() ->
end
end.
+-spec get_env_config() -> {ok, string()} | undefined.
+get_env_config() ->
+ %% First case: the filename can be specified with: erl -config "/path/to/ejabberd.yml".
+ case application:get_env(config) of
+ R = {ok, _Path} -> R;
+ undefined ->
+ %% Second case for embbeding ejabberd in another app, for example for Elixir:
+ %% config :ejabberd,
+ %% file: "config/ejabberd.yml"
+ application:get_env(ejabberd, file)
+ end.
+
%% @doc Read the ejabberd configuration file.
%% It also includes additional configuration files and replaces macros.
%% This function will crash if finds some error in the configuration file.
@@ -155,6 +168,22 @@ convert_to_yaml(File, Output) ->
file:write_file(FileName, Data)
end.
+%% Some Erlang apps expects env parameters to be list and not binary.
+%% For example, Mnesia is not able to start if mnesia dir is passed as a binary.
+%% However, binary is most common on Elixir, so it is easy to make a setup mistake.
+-spec env_binary_to_list(atom(), atom()) -> {ok, any()}|undefined.
+env_binary_to_list(Application, Parameter) ->
+ %% Application need to be loaded to allow setting parameters
+ application:load(Application),
+ case application:get_env(Application, Parameter) of
+ {ok, Val} when is_binary(Val) ->
+ BVal = binary_to_list(Val),
+ application:set_env(Application, Parameter, BVal),
+ {ok, BVal};
+ Other ->
+ Other
+ end.
+
%% @doc Read an ejabberd configuration file and return the terms.
%% Input is an absolute or relative path to an ejabberd config file.
%% Returns a list of plain terms,
@@ -679,7 +708,10 @@ is_file_readable(Path) ->
end.
get_version() ->
- list_to_binary(element(2, application:get_key(ejabberd, vsn))).
+ case application:get_key(ejabberd, vsn) of
+ undefined -> "";
+ {ok, Vsn} -> list_to_binary(Vsn)
+ end.
-spec get_myhosts() -> [binary()].
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index c1ab5c6cd..a06d3f99b 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -94,14 +94,24 @@ start_link(SockData, Opts) ->
init({SockMod, Socket}, Opts) ->
TLSEnabled = proplists:get_bool(tls, Opts),
TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
+ ({ciphers, _}) -> true;
(_) -> false
end,
Opts),
- TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
- false -> [compression_none | TLSOpts1];
- true -> TLSOpts1
+ TLSOpts2 = case lists:keysearch(protocol_options, 1, Opts) of
+ {value, {_, O}} ->
+ [_|ProtocolOptions] = lists:foldl(
+ fun(X, Acc) -> X ++ Acc end, [],
+ [["|" | binary_to_list(Opt)] || Opt <- O, is_binary(Opt)]
+ ),
+ [{protocol_options, iolist_to_binary(ProtocolOptions)} | TLSOpts1];
+ _ -> TLSOpts1
end,
- TLSOpts = [verify_none | TLSOpts2],
+ TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
+ false -> [compression_none | TLSOpts2];
+ true -> TLSOpts2
+ end,
+ TLSOpts = [verify_none | TLSOpts3],
{SockMod1, Socket1} = if TLSEnabled ->
inet:setopts(Socket, [{recbuf, 8192}]),
{ok, TLSSocket} = p1_tls:tcp_to_tls(Socket,
diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl
index 74fa2df5f..0808ffd97 100644
--- a/src/ejabberd_http_ws.erl
+++ b/src/ejabberd_http_ws.erl
@@ -138,7 +138,9 @@ handle_event({activate, From}, StateName, StateData) ->
StateData#state{waiting_input = From}};
Input ->
Receiver = From,
- Receiver ! {tcp, StateData#state.socket, Input},
+ lists:foreach(fun(I) ->
+ Receiver ! {tcp, StateData#state.socket, I}
+ end, Input),
{next_state, StateName,
StateData#state{input = [], waiting_input = false,
last_receiver = Receiver}}
@@ -187,7 +189,19 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
skip ->
ok
end,
- {reply, ok, StateName, StateData};
+ SN2 = case Packet2 of
+ {xmlstreamelement, #xmlel{name = <<"close">>}} ->
+ stream_end_sent;
+ _ ->
+ StateName
+ end,
+ {reply, ok, SN2, StateData};
+handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}, rfc_compilant = true} = StateData)
+ when StateName /= stream_end_sent ->
+ Close = #xmlel{name = <<"close">>,
+ attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]},
+ WsPid ! {send, xml:element_to_binary(Close)},
+ {stop, normal, StateData};
handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}.
@@ -197,7 +211,7 @@ handle_info({received, Packet}, StateName, StateDataI) ->
{StateData, Parsed} = parse(StateDataI, Packet),
SD = case StateData#state.waiting_input of
false ->
- Input = StateData#state.input ++ Parsed,
+ Input = StateData#state.input ++ if is_binary(Parsed) -> [Parsed]; true -> Parsed end,
StateData#state{input = Input};
Receiver ->
Receiver ! {tcp, StateData#state.socket, Parsed},
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 7db5ab826..a4ccbe84e 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -195,20 +195,14 @@ listen_tcp(PortIP, Module, SockOpts, Port, IPS) ->
ets:delete(listen_sockets, Port),
ListenSocket;
_ ->
- SockOpts2 = try erlang:system_info(otp_release) >= "R13B" of
- true -> [{send_timeout_close, true} | SockOpts];
- false -> SockOpts
- catch
- _:_ -> []
- end,
Res = gen_tcp:listen(Port, [binary,
{packet, 0},
{active, false},
{reuseaddr, true},
{nodelay, true},
{send_timeout, ?TCP_SEND_TIMEOUT},
- {keepalive, true} |
- SockOpts2]),
+ {send_timeout_close, true},
+ {keepalive, true}]),
case Res of
{ok, ListenSocket} ->
ListenSocket;
@@ -546,7 +540,7 @@ normalize_proto(UnknownProto) ->
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) ->
ReasonT = case Reason of
eaddrnotavail ->
- "IP address not available: " ++ IPS;
+ "IP address not available: " ++ binary_to_list(IPS);
eaddrinuse ->
"IP address and port number already used: "
++binary_to_list(IPS)++" "++integer_to_list(Port);
diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl
index 59beca16d..a00ac9942 100644
--- a/src/ejabberd_logger.erl
+++ b/src/ejabberd_logger.erl
@@ -46,8 +46,10 @@
%% If not defined it checks the environment variable EJABBERD_LOG_PATH.
%% And if that one is neither defined, returns the default value:
%% "ejabberd.log" in current directory.
+%% Note: If the directory where to place the ejabberd log file to not exist,
+%% it is not created and no log file will be generated.
get_log_path() ->
- case application:get_env(ejabberd, log_path) of
+ case ejabberd_config:env_binary_to_list(ejabberd, log_path) of
{ok, Path} ->
Path;
undefined ->
diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_odbc.erl
index 278bb760f..1c163dcbb 100644
--- a/src/ejabberd_odbc.erl
+++ b/src/ejabberd_odbc.erl
@@ -534,6 +534,7 @@ pgsql_connect(Server, Port, DB, Username, Password) ->
{ok, Ref} ->
pgsql:squery(Ref, [<<"alter database ">>, DB, <<" set ">>,
<<"standard_conforming_strings='off';">>]),
+ pgsql:squery(Ref, [<<"set standard_conforming_strings to 'off';">>]),
{ok, Ref};
Err ->
Err
diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl
index 819e6d898..f63ae1ccb 100644
--- a/src/ejabberd_receiver.erl
+++ b/src/ejabberd_receiver.erl
@@ -243,7 +243,13 @@ handle_info({Tag, _TCPSocket, Data},
{ok, TLSData} ->
{noreply, process_data(TLSData, State),
?HIBERNATE_TIMEOUT};
- {error, _Reason} -> {stop, normal, State}
+ {error, Reason} ->
+ if is_binary(Reason) ->
+ ?ERROR_MSG("TLS error = ~s", [Reason]);
+ true ->
+ ok
+ end,
+ {stop, normal, State}
end;
ezlib ->
case ezlib:recv_data(Socket, Data) of
diff --git a/src/ejabberd_riak_sup.erl b/src/ejabberd_riak_sup.erl
index 871af5a06..bb36eb44d 100644
--- a/src/ejabberd_riak_sup.erl
+++ b/src/ejabberd_riak_sup.erl
@@ -75,7 +75,7 @@ is_riak_configured(Host) ->
fun(L) when is_list(L) -> L end, []),
ModuleWithRiakDBConfigured = lists:any(
fun({_Module, Opts}) ->
- gen_mod:db_type(Opts) == riak
+ gen_mod:db_type(Host, Opts) == riak
end, Modules),
ServerConfigured or PortConfigured
or AuthConfigured or ModuleWithRiakDBConfigured.
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index abe15d9ff..678452951 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -516,7 +516,18 @@ do_route(From, To, #xmlel{} = Packet) ->
PResources);
true -> ok
end;
- <<"message">> -> route_message(From, To, Packet);
+ <<"message">> ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"chat">> -> route_message(From, To, Packet, chat);
+ <<"headline">> -> route_message(From, To, Packet, headline);
+ <<"error">> -> ok;
+ <<"groupchat">> ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err);
+ _ ->
+ route_message(From, To, Packet, normal)
+ end;
<<"iq">> -> process_iq(From, To, Packet);
_ -> ok
end;
@@ -525,7 +536,15 @@ do_route(From, To, #xmlel{} = Packet) ->
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
case Name of
- <<"message">> -> route_message(From, To, Packet);
+ <<"message">> ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"chat">> -> route_message(From, To, Packet, chat);
+ <<"error">> -> ok;
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err)
+ end;
<<"iq">> ->
case xml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
@@ -568,14 +587,15 @@ is_privacy_allow(From, To, Packet, PrivacyList) ->
[User, Server, PrivacyList, {From, To, Packet},
in]).
-route_message(From, To, Packet) ->
+route_message(From, To, Packet, Type) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
PrioRes = get_user_present_resources(LUser, LServer),
case catch lists:max(PrioRes) of
{Priority, _R}
when is_integer(Priority), Priority >= 0 ->
- lists:foreach(fun ({P, R}) when P == Priority ->
+ lists:foreach(fun ({P, R}) when P == Priority;
+ (P >= 0) and (Type == headline) ->
LResource = jlib:resourceprep(R),
Mod = get_sm_backend(),
case Mod:get_sessions(LUser, LServer,
@@ -593,12 +613,8 @@ route_message(From, To, Packet) ->
end,
PrioRes);
_ ->
- case xml:get_tag_attr_s(<<"type">>, Packet) of
- <<"error">> -> ok;
- <<"groupchat">> ->
- bounce_offline_message(From, To, Packet);
- <<"headline">> ->
- bounce_offline_message(From, To, Packet);
+ case Type of
+ headline -> ok;
_ ->
case ejabberd_auth:is_user_exists(LUser, LServer) of
true ->
diff --git a/src/ejabberd_sm_redis.erl b/src/ejabberd_sm_redis.erl
index 7abab1847..0283f9c3e 100644
--- a/src/ejabberd_sm_redis.erl
+++ b/src/ejabberd_sm_redis.erl
@@ -82,7 +82,8 @@ set_session(Session) ->
?ERROR_MSG("failed to set session for redis: ~p", [Err])
end.
--spec delete_session(binary(), binary(), binary(), sid()) -> ok.
+-spec delete_session(binary(), binary(), binary(), sid()) ->
+ {ok, #session{}} | {error, notfound}.
delete_session(LUser, LServer, _LResource, SID) ->
USKey = us_to_key({LUser, LServer}),
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
diff --git a/src/ejabberd_update.erl b/src/ejabberd_update.erl
index 5b6795c4b..afcb62225 100644
--- a/src/ejabberd_update.erl
+++ b/src/ejabberd_update.erl
@@ -138,13 +138,6 @@ build_script(Dir, UpdatedBeams) ->
LowLevelScript,
[{ejabberd, "", filename:join(Dir, "..")}]),
Check1 = case Check of
- ok ->
- %% This clause is for OTP R14B03 and older.
- %% Newer Dialyzer reports a never match pattern; don't worry.
- ?DEBUG("script: ~p~n", [Script]),
- ?DEBUG("low level script: ~p~n", [LowLevelScript]),
- ?DEBUG("check: ~p~n", [Check]),
- ok;
{ok, []} ->
?DEBUG("script: ~p~n", [Script]),
?DEBUG("low level script: ~p~n", [LowLevelScript]),
diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl
index 8cd1b2289..9d5f32c33 100644
--- a/src/ejabberd_websocket.erl
+++ b/src/ejabberd_websocket.erl
@@ -73,9 +73,10 @@ check(_Path, Headers) ->
{_, HVal} ->
case Val of
ignore -> false; % ignore value -> ok, remove from list
- HVal -> false; % expected val -> ok, remove from list
_ ->
- true % val is different, keep in list
+ % expected value -> ok, remove from list (false)
+ % value is different, keep in list (true)
+ str:to_lower(HVal) /= Val
end
end
end,
diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index b1bd164a6..904604fc9 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -444,6 +444,8 @@ format_arg(Arg, binary) when is_list(Arg) -> list_to_binary(Arg);
format_arg(Arg, binary) when is_binary(Arg) -> Arg;
format_arg(Arg, string) when is_list(Arg) -> Arg;
format_arg(Arg, string) when is_binary(Arg) -> binary_to_list(Arg);
+format_arg(undefined, binary) -> <<>>;
+format_arg(undefined, string) -> "";
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
throw({error_formatting_argument, Arg, Format}).
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index 645e3142d..e628b06ab 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -33,7 +33,7 @@
get_opt_host/3, db_type/1, db_type/2, get_module_opt/5,
get_module_opt_host/3, loaded_modules/1,
loaded_modules_with_opts/1, get_hosts/2,
- get_module_proc/2, is_loaded/2]).
+ get_module_proc/2, is_loaded/2, default_db/1]).
%%-export([behaviour_info/1]).
@@ -212,24 +212,36 @@ get_opt_host(Host, Opts, Default) ->
-spec db_type(opts()) -> odbc | mnesia | riak.
db_type(Opts) ->
- get_opt(db_type, Opts,
- fun(odbc) -> odbc;
- (internal) -> mnesia;
- (mnesia) -> mnesia;
- (riak) -> riak
- end,
- mnesia).
+ db_type(global, Opts).
--spec db_type(binary(), atom()) -> odbc | mnesia | riak.
+-spec db_type(binary() | global, atom() | opts()) -> odbc | mnesia | riak.
-db_type(Host, Module) ->
+db_type(Host, Module) when is_atom(Module) ->
get_module_opt(Host, Module, db_type,
fun(odbc) -> odbc;
(internal) -> mnesia;
(mnesia) -> mnesia;
(riak) -> riak
end,
- mnesia).
+ default_db(Host));
+db_type(Host, Opts) when is_list(Opts) ->
+ get_opt(db_type, Opts,
+ fun(odbc) -> odbc;
+ (internal) -> mnesia;
+ (mnesia) -> mnesia;
+ (riak) -> riak
+ end,
+ default_db(Host)).
+
+-spec default_db(binary() | global) -> odbc | mnesia | riak.
+
+default_db(Host) ->
+ ejabberd_config:get_option({default_db, Host},
+ fun(odbc) -> odbc;
+ (mnesia) -> mnesia;
+ (riak) -> riak;
+ (internal) -> mnesia
+ end, mnesia).
-spec loaded_modules(binary()) -> [atom()].
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
new file mode 100644
index 000000000..0162a40c3
--- /dev/null
+++ b/src/mod_admin_extra.erl
@@ -0,0 +1,1583 @@
+%%%-------------------------------------------------------------------
+%%% File : mod_admin_extra.erl
+%%% Author : Badlop <badlop@process-one.net>
+%%% Purpose : Contributed administrative functions and commands
+%%% Created : 10 Aug 2008 by Badlop <badlop@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2008 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.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%-------------------------------------------------------------------
+
+-module(mod_admin_extra).
+-author('badlop@process-one.net').
+
+-behaviour(gen_mod).
+
+-include("logger.hrl").
+
+-export([start/2, stop/1,
+ %% Node
+ compile/1,
+ load_config/1,
+ get_cookie/0,
+ remove_node/1,
+ export2odbc/2,
+ %% Accounts
+ set_password/3,
+ check_password_hash/4,
+ delete_old_users/1,
+ delete_old_users_vhost/2,
+ ban_account/3,
+ num_active_users/2,
+ %% Sessions
+ num_resources/2,
+ resource_num/3,
+ kick_session/4,
+ status_num/2, status_num/1,
+ status_list/2, status_list/1,
+ connected_users_info/0,
+ connected_users_vhost/1,
+ set_presence/7,
+ user_sessions_info/2,
+ %% Vcard
+ set_nickname/3,
+ get_vcard/3,
+ get_vcard/4,
+ get_vcard_multi/4,
+ set_vcard/4,
+ set_vcard/5,
+ %% Roster
+ add_rosteritem/7,
+ delete_rosteritem/4,
+ process_rosteritems/5,
+ get_roster/2,
+ push_roster/3,
+ push_roster_all/1,
+ push_alltoall/2,
+ %% mod_last
+ get_last/2,
+ set_last/4,
+ %% mod_private
+ private_get/4,
+ private_set/3,
+ %% mod_shared_roster
+ srg_create/5,
+ srg_delete/2,
+ srg_list/1,
+ srg_get_info/2,
+ srg_get_members/2,
+ srg_user_add/4,
+ srg_user_del/4,
+ %% Stanza
+ send_message/5,
+ send_stanza_c2s/4,
+ privacy_set/3,
+ %% Stats
+ stats/1, stats/2
+ ]).
+
+-include("ejabberd.hrl").
+-include("ejabberd_commands.hrl").
+-include("mod_roster.hrl").
+-include("jlib.hrl").
+
+%% Copied from ejabberd_sm.erl
+-record(session, {sid, usr, us, priority, info}).
+
+
+%%%
+%%% gen_mod
+%%%
+
+start(_Host, _Opts) ->
+ ejabberd_commands:register_commands(commands()).
+
+stop(_Host) ->
+ ejabberd_commands:unregister_commands(commands()).
+
+
+%%%
+%%% Register commands
+%%%
+
+commands() ->
+ Vcard1FieldsString = "Some vcard field names in get/set_vcard are:\n"
+ " FN - Full Name\n"
+ " NICKNAME - Nickname\n"
+ " BDAY - Birthday\n"
+ " TITLE - Work: Position\n"
+ " ROLE - Work: Role",
+
+ Vcard2FieldsString = "Some vcard field names and subnames in get/set_vcard2 are:\n"
+ " N FAMILY - Family name\n"
+ " N GIVEN - Given name\n"
+ " N MIDDLE - Middle name\n"
+ " ADR CTRY - Address: Country\n"
+ " ADR LOCALITY - Address: City\n"
+ " TEL HOME - Telephone: Home\n"
+ " TEL CELL - Telephone: Cellphone\n"
+ " TEL WORK - Telephone: Work\n"
+ " TEL VOICE - Telephone: Voice\n"
+ " EMAIL USERID - E-Mail Address\n"
+ " ORG ORGNAME - Work: Company\n"
+ " ORG ORGUNIT - Work: Department",
+
+ VcardXEP = "For a full list of vCard fields check XEP-0054: vcard-temp at "
+ "http://www.xmpp.org/extensions/xep-0054.html",
+
+ [
+ #ejabberd_commands{name = compile, tags = [erlang],
+ desc = "Recompile and reload Erlang source code file",
+ module = ?MODULE, function = compile,
+ args = [{file, string}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = load_config, tags = [server],
+ desc = "Load ejabberd configuration file",
+ module = ?MODULE, function = load_config,
+ args = [{file, string}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = get_cookie, tags = [erlang],
+ desc = "Get the Erlang cookie of this node",
+ module = ?MODULE, function = get_cookie,
+ args = [],
+ result = {cookie, string}},
+ #ejabberd_commands{name = remove_node, tags = [erlang],
+ desc = "Remove an ejabberd node from Mnesia clustering config",
+ module = ?MODULE, function = remove_node,
+ args = [{node, string}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = export2odbc, tags = [mnesia], %% Copied to ejabberd 2.1.x after 11
+ desc = "Export Mnesia tables to files in directory",
+ module = ?MODULE, function = export2odbc,
+ args = [{host, string}, {path, string}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = num_active_users, tags = [accounts, stats],
+ desc = "Get number of users active in the last days",
+ module = ?MODULE, function = num_active_users,
+ args = [{host, binary}, {days, integer}],
+ result = {users, integer}},
+ #ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
+ desc = "Delete users that didn't log in last days, or that never logged",
+ module = ?MODULE, function = delete_old_users,
+ args = [{days, integer}],
+ result = {res, restuple}},
+ #ejabberd_commands{name = delete_old_users_vhost, tags = [accounts, purge],
+ desc = "Delete users that didn't log in last days in vhost, or that never logged",
+ module = ?MODULE, function = delete_old_users_vhost,
+ args = [{host, binary}, {days, integer}],
+ result = {res, restuple}},
+
+ #ejabberd_commands{name = check_account, tags = [accounts],
+ desc = "Check if an account exists or not",
+ module = ejabberd_auth, function = is_user_exists,
+ args = [{user, binary}, {host, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = check_password, tags = [accounts],
+ desc = "Check if a password is correct",
+ module = ejabberd_auth, function = check_password,
+ args = [{user, binary}, {host, binary}, {password, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = check_password_hash, tags = [accounts],
+ desc = "Check if the password hash is correct",
+ longdesc = "Allowed hash methods: md5, sha.",
+ module = ?MODULE, function = check_password_hash,
+ args = [{user, binary}, {host, binary}, {passwordhash, binary}, {hashmethod, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = change_password, tags = [accounts],
+ desc = "Change the password of an account",
+ module = ?MODULE, function = set_password,
+ args = [{user, binary}, {host, binary}, {newpass, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = ban_account, tags = [accounts],
+ desc = "Ban an account: kick sessions and set random password",
+ module = ?MODULE, function = ban_account,
+ args = [{user, binary}, {host, binary}, {reason, binary}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = num_resources, tags = [session],
+ desc = "Get the number of resources of a user",
+ module = ?MODULE, function = num_resources,
+ args = [{user, binary}, {host, binary}],
+ result = {resources, integer}},
+ #ejabberd_commands{name = resource_num, tags = [session],
+ desc = "Resource string of a session number",
+ module = ?MODULE, function = resource_num,
+ args = [{user, binary}, {host, binary}, {num, integer}],
+ result = {resource, string}},
+ #ejabberd_commands{name = kick_session, tags = [session],
+ desc = "Kick a user session",
+ module = ?MODULE, function = kick_session,
+ args = [{user, binary}, {host, binary}, {resource, binary}, {reason, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = status_num_host, tags = [session, stats],
+ desc = "Number of logged users with this status in host",
+ module = ?MODULE, function = status_num,
+ args = [{host, binary}, {status, binary}],
+ result = {users, integer}},
+ #ejabberd_commands{name = status_num, tags = [session, stats],
+ desc = "Number of logged users with this status",
+ module = ?MODULE, function = status_num,
+ args = [{status, binary}],
+ result = {users, integer}},
+ #ejabberd_commands{name = status_list_host, tags = [session],
+ desc = "List of users logged in host with their statuses",
+ module = ?MODULE, function = status_list,
+ args = [{host, binary}, {status, binary}],
+ result = {users, {list,
+ {userstatus, {tuple, [
+ {user, string},
+ {host, string},
+ {resource, string},
+ {priority, integer},
+ {status, string}
+ ]}}
+ }}},
+ #ejabberd_commands{name = status_list, tags = [session],
+ desc = "List of logged users with this status",
+ module = ?MODULE, function = status_list,
+ args = [{status, binary}],
+ result = {users, {list,
+ {userstatus, {tuple, [
+ {user, string},
+ {host, string},
+ {resource, string},
+ {priority, integer},
+ {status, string}
+ ]}}
+ }}},
+ #ejabberd_commands{name = connected_users_info,
+ tags = [session],
+ desc = "List all established sessions and their information",
+ module = ?MODULE, function = connected_users_info,
+ args = [],
+ result = {connected_users_info,
+ {list,
+ {sessions, {tuple,
+ [{jid, string},
+ {connection, string},
+ {ip, string},
+ {port, integer},
+ {priority, integer},
+ {node, string},
+ {uptime, integer}
+ ]}}
+ }}},
+ #ejabberd_commands{name = connected_users_vhost,
+ tags = [session],
+ desc = "Get the list of established sessions in a vhost",
+ module = ?MODULE, function = connected_users_vhost,
+ args = [{host, string}],
+ result = {connected_users_vhost, {list, {sessions, string}}}},
+ #ejabberd_commands{name = user_sessions_info,
+ tags = [session],
+ desc = "Get information about all sessions of a user",
+ module = ?MODULE, function = user_sessions_info,
+ args = [{user, binary}, {host, binary}],
+ result = {sessions_info,
+ {list,
+ {session, {tuple,
+ [{connection, string},
+ {ip, string},
+ {port, integer},
+ {priority, integer},
+ {node, string},
+ {uptime, integer},
+ {status, string},
+ {resource, string},
+ {statustext, string}
+ ]}}
+ }}},
+
+ #ejabberd_commands{name = set_presence,
+ tags = [session],
+ desc = "Set presence of a session",
+ module = ?MODULE, function = set_presence,
+ args = [{user, binary}, {host, binary},
+ {resource, binary}, {type, binary},
+ {show, binary}, {status, binary},
+ {priority, binary}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = set_nickname, tags = [vcard],
+ desc = "Set nickname in a user's vCard",
+ module = ?MODULE, function = set_nickname,
+ args = [{user, binary}, {host, binary}, {nickname, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = get_vcard, tags = [vcard],
+ desc = "Get content from a vCard field",
+ longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
+ module = ?MODULE, function = get_vcard,
+ args = [{user, binary}, {host, binary}, {name, binary}],
+ result = {content, string}},
+ #ejabberd_commands{name = get_vcard2, tags = [vcard],
+ desc = "Get content from a vCard field",
+ longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
+ module = ?MODULE, function = get_vcard,
+ args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
+ result = {content, string}},
+ #ejabberd_commands{name = get_vcard2_multi, tags = [vcard],
+ desc = "Get multiple contents from a vCard field",
+ longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
+ module = ?MODULE, function = get_vcard_multi,
+ args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
+ result = {contents, {list, {value, string}}}},
+
+ #ejabberd_commands{name = set_vcard, tags = [vcard],
+ desc = "Set content in a vCard field",
+ longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
+ module = ?MODULE, function = set_vcard,
+ args = [{user, binary}, {host, binary}, {name, binary}, {content, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = set_vcard2, tags = [vcard],
+ desc = "Set content in a vCard subfield",
+ longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
+ module = ?MODULE, function = set_vcard,
+ args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {content, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = set_vcard2_multi, tags = [vcard],
+ desc = "Set multiple contents in a vCard subfield",
+ longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
+ module = ?MODULE, function = set_vcard,
+ args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {contents, {list, binary}}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = add_rosteritem, tags = [roster],
+ desc = "Add an item to a user's roster (supports ODBC)",
+ module = ?MODULE, function = add_rosteritem,
+ args = [{localuser, binary}, {localserver, binary},
+ {user, binary}, {server, binary},
+ {nick, binary}, {group, binary},
+ {subs, binary}],
+ result = {res, rescode}},
+ %%{"", "subs= none, from, to or both"},
+ %%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
+ %%{"", "will add mike@server.com to peter@localhost roster"},
+ #ejabberd_commands{name = delete_rosteritem, tags = [roster],
+ desc = "Delete an item from a user's roster (supports ODBC)",
+ module = ?MODULE, function = delete_rosteritem,
+ args = [{localuser, binary}, {localserver, binary},
+ {user, binary}, {server, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = process_rosteritems, tags = [roster],
+ desc = "List or delete rosteritems that match filtering options",
+ longdesc = "Explanation of each argument:\n"
+ " - action: what to do with each rosteritem that "
+ "matches all the filtering options\n"
+ " - subs: subscription type\n"
+ " - asks: pending subscription\n"
+ " - users: the JIDs of the local user\n"
+ " - contacts: the JIDs of the contact in the roster\n"
+ "\n"
+ "Allowed values in the arguments:\n"
+ " ACTION = list | delete\n"
+ " SUBS = SUB[:SUB]* | any\n"
+ " SUB = none | from | to | both\n"
+ " ASKS = ASK[:ASK]* | any\n"
+ " ASK = none | out | in\n"
+ " USERS = JID[:JID]* | any\n"
+ " CONTACTS = JID[:JID]* | any\n"
+ " JID = characters valid in a JID, and can use the "
+ "globs: *, ?, ! and [...]\n"
+ "\n"
+ "This example will list roster items with subscription "
+ "'none', 'from' or 'to' that have any ask property, of "
+ "local users which JID is in the virtual host "
+ "'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,
+ args = [{action, string}, {subs, string},
+ {asks, string}, {users, string},
+ {contacts, string}],
+ result = {response,
+ {list,
+ {pairs, {tuple,
+ [{user, string},
+ {contact, string}
+ ]}}
+ }}},
+ #ejabberd_commands{name = get_roster, tags = [roster],
+ desc = "Get roster of a local user",
+ module = ?MODULE, function = get_roster,
+ args = [{user, binary}, {host, binary}],
+ result = {contacts, {list, {contact, {tuple, [
+ {jid, string},
+ {nick, string},
+ {subscription, string},
+ {ask, string},
+ {group, string}
+ ]}}}}},
+ #ejabberd_commands{name = push_roster, tags = [roster],
+ desc = "Push template roster from file to a user",
+ module = ?MODULE, function = push_roster,
+ args = [{file, string}, {user, string}, {host, string}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = push_roster_all, tags = [roster],
+ desc = "Push template roster from file to all those users",
+ module = ?MODULE, function = push_roster_all,
+ args = [{file, string}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = push_alltoall, tags = [roster],
+ desc = "Add all the users to all the users of Host in Group",
+ module = ?MODULE, function = push_alltoall,
+ args = [{host, string}, {group, string}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = get_last, tags = [last],
+ desc = "Get last activity information",
+ longdesc = "Timestamp is the seconds since"
+ "1970-01-01 00:00:00 UTC, for example: date +%s",
+ module = ?MODULE, function = get_last,
+ args = [{user, binary}, {host, binary}],
+ result = {last_activity, string}},
+ #ejabberd_commands{name = set_last, tags = [last],
+ desc = "Set last activity information",
+ longdesc = "Timestamp is the seconds since"
+ "1970-01-01 00:00:00 UTC, for example: date +%s",
+ module = ?MODULE, function = set_last,
+ args = [{user, string}, {host, string}, {timestamp, integer}, {status, string}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = private_get, tags = [private],
+ desc = "Get some information from a user private storage",
+ module = ?MODULE, function = private_get,
+ args = [{user, binary}, {host, binary}, {element, binary}, {ns, binary}],
+ result = {res, string}},
+ #ejabberd_commands{name = private_set, tags = [private],
+ desc = "Set to the user private storage",
+ module = ?MODULE, function = private_set,
+ args = [{user, binary}, {host, binary}, {element, binary}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = srg_create, tags = [shared_roster_group],
+ desc = "Create a Shared Roster Group",
+ longdesc = "If you want to specify several group "
+ "identifiers in the Display argument,\n"
+ "put \\ \" around the argument and\nseparate the "
+ "identifiers with \\ \\ n\n"
+ "For example:\n"
+ " ejabberdctl srg_create group3 localhost "
+ "name desc \\\"group1\\\\ngroup2\\\"",
+ module = ?MODULE, function = srg_create,
+ args = [{group, binary}, {host, binary},
+ {name, binary}, {description, binary}, {display, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
+ desc = "Delete a Shared Roster Group",
+ module = ?MODULE, function = srg_delete,
+ args = [{group, binary}, {host, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = srg_list, tags = [shared_roster_group],
+ desc = "List the Shared Roster Groups in Host",
+ module = ?MODULE, function = srg_list,
+ args = [{host, binary}],
+ result = {groups, {list, {id, string}}}},
+ #ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
+ desc = "Get info of a Shared Roster Group",
+ module = ?MODULE, function = srg_get_info,
+ args = [{group, binary}, {host, binary}],
+ result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
+ #ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
+ desc = "Get members of a Shared Roster Group",
+ module = ?MODULE, function = srg_get_members,
+ args = [{group, binary}, {host, binary}],
+ result = {members, {list, {member, string}}}},
+ #ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
+ desc = "Add the JID user@host to the Shared Roster Group",
+ module = ?MODULE, function = srg_user_add,
+ args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
+ desc = "Delete this JID user@host from the Shared Roster Group",
+ module = ?MODULE, function = srg_user_del,
+ args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = send_message, tags = [stanza],
+ desc = "Send a message to a local or remote bare of full JID",
+ module = ?MODULE, function = send_message,
+ args = [{type, binary}, {from, binary}, {to, binary},
+ {subject, binary}, {body, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
+ desc = "Send a stanza as if sent from a c2s session",
+ module = ?MODULE, function = send_stanza_c2s,
+ args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = privacy_set, tags = [stanza],
+ desc = "Send a IQ set privacy stanza for a local account",
+ module = ?MODULE, function = privacy_set,
+ args = [{user, binary}, {host, binary}, {xmlquery, binary}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = stats, tags = [stats],
+ desc = "Get statistical value: registeredusers onlineusers onlineusersnode uptimeseconds",
+ module = ?MODULE, function = stats,
+ args = [{name, binary}],
+ result = {stat, integer}},
+ #ejabberd_commands{name = stats_host, tags = [stats],
+ desc = "Get statistical value for this host: registeredusers onlineusers",
+ module = ?MODULE, function = stats,
+ args = [{name, binary}, {host, binary}],
+ result = {stat, integer}}
+ ].
+
+
+%%%
+%%% Node
+%%%
+
+compile(File) ->
+ compile:file(File).
+
+load_config(Path) ->
+ ok = ejabberd_config:load_file(Path).
+
+get_cookie() ->
+ atom_to_list(erlang:get_cookie()).
+
+remove_node(Node) ->
+ mnesia:del_table_copy(schema, list_to_atom(Node)),
+ ok.
+
+export2odbc(Host, Directory) ->
+ Tables = [
+ {export_last, last},
+ {export_offline, offline},
+ {export_passwd, passwd},
+ {export_private_storage, private_storage},
+ {export_roster, roster},
+ {export_vcard, vcard},
+ {export_vcard_search, vcard_search}],
+ Export = fun({TableFun, Table}) ->
+ Filename = filename:join([Directory, atom_to_list(Table)++".txt"]),
+ io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]),
+ Res = (catch ejd2odbc:TableFun(Host, Filename)),
+ io:format(" Result: ~p~n", [Res])
+ end,
+ lists:foreach(Export, Tables),
+ ok.
+
+
+%%%
+%%% Accounts
+%%%
+
+set_password(User, Host, Password) ->
+ case ejabberd_auth:set_password(User, Host, Password) of
+ ok ->
+ ok;
+ _ ->
+ error
+ end.
+
+%% Copied some code from ejabberd_commands.erl
+check_password_hash(User, Host, PasswordHash, HashMethod) ->
+ AccountPass = ejabberd_auth:get_password_s(User, Host),
+ AccountPassHash = case HashMethod of
+ "md5" -> get_md5(AccountPass);
+ "sha" -> get_sha(AccountPass);
+ _ -> undefined
+ end,
+ case AccountPassHash of
+ undefined -> error;
+ PasswordHash -> ok;
+ _ -> error
+ end.
+get_md5(AccountPass) ->
+ lists:flatten([io_lib:format("~.16B", [X])
+ || X <- binary_to_list(erlang:md5(AccountPass))]).
+get_sha(AccountPass) ->
+ lists:flatten([io_lib:format("~.16B", [X])
+ || X <- binary_to_list(p1_sha:sha1(AccountPass))]).
+
+num_active_users(Host, Days) ->
+ list_last_activity(Host, true, Days).
+
+%% Code based on ejabberd/src/web/ejabberd_web_admin.erl
+list_last_activity(Host, Integral, Days) ->
+ {MegaSecs, Secs, _MicroSecs} = now(),
+ TimeStamp = MegaSecs * 1000000 + Secs,
+ TS = TimeStamp - Days * 86400,
+ case catch mnesia:dirty_select(
+ last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
+ [{'>', '$1', TS}],
+ [{'trunc', {'/',
+ {'-', TimeStamp, '$1'},
+ 86400}}]}]) of
+ {'EXIT', _Reason} ->
+ [];
+ Vals ->
+ Hist = histogram(Vals, Integral),
+ if
+ Hist == [] ->
+ 0;
+ true ->
+ Left = Days - length(Hist),
+ Tail = if
+ Integral ->
+ lists:duplicate(Left, lists:last(Hist));
+ true ->
+ lists:duplicate(Left, 0)
+ end,
+ lists:nth(Days, Hist ++ Tail)
+ end
+ end.
+histogram(Values, Integral) ->
+ histogram(lists:sort(Values), Integral, 0, 0, []).
+histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
+ histogram(T, Integral, Current, Count + 1, Hist);
+histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H ->
+ if
+ Integral ->
+ histogram(Values, Integral, Current + 1, Count, [Count | Hist]);
+ true ->
+ histogram(Values, Integral, Current + 1, 0, [Count | Hist])
+ end;
+histogram([], _Integral, _Current, Count, Hist) ->
+ if
+ Count > 0 ->
+ lists:reverse([Count | Hist]);
+ true ->
+ lists:reverse(Hist)
+ end.
+
+
+delete_old_users(Days) ->
+ %% Get the list of registered users
+ Users = ejabberd_auth:dirty_get_registered_users(),
+
+ {removed, N, UR} = delete_old_users(Days, Users),
+ {ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
+
+delete_old_users_vhost(Host, Days) ->
+ %% Get the list of registered users
+ Users = ejabberd_auth:get_vh_registered_users(Host),
+
+ {removed, N, UR} = delete_old_users(Days, Users),
+ {ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
+
+delete_old_users(Days, Users) ->
+ %% Convert older time
+ SecOlder = Days*24*60*60,
+
+ %% Get current time
+ {MegaSecs, Secs, _MicroSecs} = now(),
+ TimeStamp_now = MegaSecs * 1000000 + Secs,
+
+ %% For a user, remove if required and answer true
+ F = fun({LUser, LServer}) ->
+ %% Check if the user is logged
+ case ejabberd_sm:get_user_resources(LUser, LServer) of
+ %% If it isnt
+ [] ->
+ %% Look for his last_activity
+ case (get_lastactivity_module(LServer)):get_last_info(LUser, LServer) of
+ %% If it is
+ %% existent:
+ {ok, TimeStamp, _Status} ->
+ %% get his age
+ Sec = TimeStamp_now - TimeStamp,
+ %% If he is
+ if
+ %% younger than SecOlder:
+ Sec < SecOlder ->
+ %% do nothing
+ false;
+ %% older:
+ true ->
+ %% remove the user
+ ejabberd_auth:remove_user(LUser, LServer),
+ true
+ end;
+ %% nonexistent:
+ not_found ->
+ %% remove the user
+ ejabberd_auth:remove_user(LUser, LServer),
+ true
+ end;
+ %% Else
+ _ ->
+ %% do nothing
+ false
+ end
+ end,
+ %% Apply the function to every user in the list
+ Users_removed = lists:filter(F, Users),
+ {removed, length(Users_removed), Users_removed}.
+
+get_lastactivity_module(Server) ->
+ case lists:member(mod_last, gen_mod:loaded_modules(Server)) of
+ true -> mod_last;
+ _ -> mod_last_odbc
+ end.
+
+
+%%
+%% Ban account
+
+ban_account(User, Host, ReasonText) ->
+ Reason = prepare_reason(ReasonText),
+ kick_sessions(User, Host, Reason),
+ set_random_password(User, Host, Reason),
+ ok.
+
+kick_sessions(User, Server, Reason) ->
+ lists:map(
+ fun(Resource) ->
+ kick_this_session(User, Server, Resource, Reason)
+ end,
+ get_resources(User, Server)).
+
+get_resources(User, Server) ->
+ lists:map(
+ fun(Session) ->
+ element(3, Session#session.usr)
+ end,
+ get_sessions(User, Server)).
+
+get_sessions(User, Server) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ Sessions = mnesia:dirty_index_read(session, {LUser, LServer}, #session.us),
+ true = is_list(Sessions),
+ Sessions.
+
+set_random_password(User, Server, Reason) ->
+ NewPass = build_random_password(Reason),
+ set_password_auth(User, Server, NewPass).
+
+build_random_password(Reason) ->
+ Date = jlib:timestamp_to_iso(calendar:universal_time()),
+ RandomString = randoms:get_string(),
+ <<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>.
+
+set_password_auth(User, Server, Password) ->
+ ok = ejabberd_auth:set_password(User, Server, Password).
+
+prepare_reason([]) ->
+ <<"Kicked by administrator">>;
+prepare_reason([Reason]) ->
+ Reason;
+prepare_reason(Reason) when is_binary(Reason) ->
+ Reason.
+
+%%%
+%%% Sessions
+%%%
+
+num_resources(User, Host) ->
+ length(ejabberd_sm:get_user_resources(User, Host)).
+
+resource_num(User, Host, Num) ->
+ Resources = ejabberd_sm:get_user_resources(User, Host),
+ case (0<Num) and (Num=<length(Resources)) of
+ true ->
+ lists:nth(Num, Resources);
+ false ->
+ lists:flatten(io_lib:format("Error: Wrong resource number: ~p", [Num]))
+ end.
+
+kick_session(User, Server, Resource, ReasonText) ->
+ kick_this_session(User, Server, Resource, prepare_reason(ReasonText)),
+ ok.
+
+kick_this_session(User, Server, Resource, Reason) ->
+ ejabberd_sm:route(jlib:make_jid(<<"">>, <<"">>, <<"">>),
+ jlib:make_jid(User, Server, Resource),
+ {broadcast, {exit, Reason}}).
+
+status_num(Host, Status) ->
+ length(get_status_list(Host, Status)).
+status_num(Status) ->
+ status_num(<<"all">>, Status).
+status_list(Host, Status) ->
+ Res = get_status_list(Host, Status),
+ [{U, S, R, P, St} || {U, S, R, P, St} <- Res].
+status_list(Status) ->
+ status_list(<<"all">>, Status).
+
+
+get_status_list(Host, Status_required) ->
+ %% Get list of all logged users
+ Sessions = ejabberd_sm:dirty_get_my_sessions_list(),
+ %% Reformat the list
+ Sessions2 = [ {Session#session.usr, Session#session.sid, Session#session.priority} || Session <- Sessions],
+ Fhost = case Host of
+ <<"all">> ->
+ %% All hosts are requested, so dont filter at all
+ fun(_, _) -> true end;
+ _ ->
+ %% Filter the list, only Host is interesting
+ fun(A, B) -> A == B end
+ end,
+ Sessions3 = [ {Pid, Server, Priority} || {{_User, Server, _Resource}, {_, Pid}, Priority} <- Sessions2, apply(Fhost, [Server, Host])],
+ %% For each Pid, get its presence
+ Sessions4 = [ {catch ejabberd_c2s:get_presence(Pid), Server, Priority} || {Pid, Server, Priority} <- Sessions3],
+ %% Filter by status
+ Fstatus = case Status_required of
+ <<"all">> ->
+ fun(_, _) -> true end;
+ _ ->
+ fun(A, B) -> A == B end
+ end,
+ [{User, Server, Resource, Priority, stringize(Status_text)}
+ || {{User, Resource, Status, Status_text}, Server, Priority} <- Sessions4,
+ apply(Fstatus, [Status, Status_required])].
+
+connected_users_info() ->
+ USRIs = dirty_get_sessions_list2(),
+ CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
+ lists:map(
+ fun([{U, S, R}, {Now, Pid}, Priority, Info]) ->
+ Conn = proplists:get_value(conn, Info),
+ {Ip, Port} = proplists:get_value(ip, Info),
+ IPS = inet_parse:ntoa(Ip),
+ NodeS = atom_to_list(node(Pid)),
+ Uptime = CurrentSec - calendar:datetime_to_gregorian_seconds(
+ calendar:now_to_local_time(Now)),
+ PriorityI = case Priority of
+ PI when is_integer(PI) -> PI;
+ _ -> nil
+ end,
+ {[U, $@, S, $/, R], atom_to_list(Conn), IPS, Port, PriorityI, NodeS, Uptime}
+ end,
+ USRIs).
+
+connected_users_vhost(Host) ->
+ USRs = ejabberd_sm:get_vh_session_list(Host),
+ [ [U, $@, S, $/, R] || {U, S, R} <- USRs].
+
+%% Code copied from ejabberd_sm.erl and customized
+dirty_get_sessions_list2() ->
+ mnesia:dirty_select(
+ session,
+ [{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4', _ = '_'},
+ [],
+ [['$1', '$2', '$3', '$4']]}]).
+
+%% Make string more print-friendly
+stringize(String) ->
+ %% Replace newline characters with other code
+ ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>).
+
+set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
+ Pid = ejabberd_sm:get_session_pid(User, Host, Resource),
+ USR = jlib:jid_to_string(jlib:make_jid(User, Host, Resource)),
+ US = jlib:jid_to_string(jlib:make_jid(User, Host, <<>>)),
+ Message = {route_xmlstreamelement,
+ {xmlel, <<"presence">>,
+ [{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}],
+ [{xmlel, <<"show">>, [], [{xmlcdata, Show}]},
+ {xmlel, <<"status">>, [], [{xmlcdata, Status}]},
+ {xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}},
+ Pid ! Message.
+
+user_sessions_info(User, Host) ->
+ CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
+ US = {User, Host},
+ Sessions = case catch mnesia:dirty_index_read(session, US, #session.us) of
+ {'EXIT', _Reason} ->
+ [];
+ Ss ->
+ Ss
+ end,
+ lists:map(
+ fun(Session) ->
+ {_U, _S, Resource} = Session#session.usr,
+ {Now, Pid} = Session#session.sid,
+ {_U, _Resource, Status, StatusText} = ejabberd_c2s:get_presence(Pid),
+ Info = Session#session.info,
+ Priority = Session#session.priority,
+ Conn = proplists:get_value(conn, Info),
+ {Ip, Port} = proplists:get_value(ip, Info),
+ IPS = inet_parse:ntoa(Ip),
+ NodeS = atom_to_list(node(Pid)),
+ Uptime = CurrentSec - calendar:datetime_to_gregorian_seconds(
+ calendar:now_to_local_time(Now)),
+ {atom_to_list(Conn), IPS, Port, Priority, NodeS, Uptime, Status, Resource, StatusText}
+ end,
+ Sessions).
+
+
+%%%
+%%% Vcard
+%%%
+
+set_nickname(User, Host, Nickname) ->
+ R = mod_vcard:process_sm_iq(
+ {jid, User, Host, <<>>, User, Host, <<>>},
+ {jid, User, Host, <<>>, User, Host, <<>>},
+ {iq, <<>>, set, <<>>, <<"en">>,
+ {xmlel, <<"vCard">>, [
+ {<<"xmlns">>, <<"vcard-temp">>}], [
+ {xmlel, <<"NICKNAME">>, [], [{xmlcdata, Nickname}]}
+ ]
+ }}),
+ case R of
+ {iq, <<>>, result, <<>>, _L, []} ->
+ ok;
+ _ ->
+ error
+ end.
+
+get_vcard(User, Host, Name) ->
+ [Res | _] = get_vcard_content(User, Host, [Name]),
+ Res.
+
+get_vcard(User, Host, Name, Subname) ->
+ [Res | _] = get_vcard_content(User, Host, [Name, Subname]),
+ Res.
+
+get_vcard_multi(User, Host, Name, Subname) ->
+ get_vcard_content(User, Host, [Name, Subname]).
+
+set_vcard(User, Host, Name, SomeContent) ->
+ set_vcard_content(User, Host, [Name], SomeContent).
+
+set_vcard(User, Host, Name, Subname, SomeContent) ->
+ set_vcard_content(User, Host, [Name, Subname], SomeContent).
+
+
+%%
+%% Internal vcard
+
+get_module_resource(Server) ->
+ case gen_mod:get_module_opt(Server, ?MODULE, module_resource, fun(A) -> A end, none) of
+ none -> list_to_binary(atom_to_list(?MODULE));
+ R when is_binary(R) -> R
+ end.
+
+get_vcard_content(User, Server, Data) ->
+ [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
+ JID = jlib:make_jid(User, Server, get_module_resource(Server)),
+ IQ = #iq{type = get, xmlns = ?NS_VCARD},
+ IQr = Module:Function(JID, JID, IQ),
+ [A1] = IQr#iq.sub_el,
+ case A1#xmlel.children of
+ [_|_] ->
+ case get_vcard(Data, A1) of
+ [false] -> throw(error_no_value_found_in_vcard);
+ ElemList -> ?DEBUG("ELS ~p", [ElemList]), [xml:get_tag_cdata(Elem) || Elem <- ElemList]
+ end;
+ [] ->
+ throw(error_no_vcard_found)
+ end.
+
+get_vcard([<<"TEL">>, TelType], {_, _, _, OldEls}) ->
+ {TakenEl, _NewEls} = take_vcard_tel(TelType, OldEls, [], not_found),
+ [TakenEl];
+
+get_vcard([Data1, Data2], A1) ->
+ case get_subtag(A1, Data1) of
+ [false] -> [false];
+ A2List ->
+ lists:flatten([get_vcard([Data2], A2) || A2 <- A2List])
+ end;
+
+get_vcard([Data], A1) ->
+ get_subtag(A1, Data).
+
+get_subtag(Xmlelement, Name) ->
+ [xml:get_subtag(Xmlelement, Name)].
+
+set_vcard_content(User, Server, Data, SomeContent) ->
+ ContentList = case SomeContent of
+ [Bin | _] when is_binary(Bin) -> SomeContent;
+ Bin when is_binary(Bin) -> [SomeContent]
+ end,
+ [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
+ JID = jlib:make_jid(User, Server, get_module_resource(Server)),
+ IQ = #iq{type = get, xmlns = ?NS_VCARD},
+ IQr = Module:Function(JID, JID, IQ),
+
+ %% Get old vcard
+ A4 = case IQr#iq.sub_el of
+ [A1] ->
+ {_, _, _, A2} = A1,
+ update_vcard_els(Data, ContentList, A2);
+ [] ->
+ update_vcard_els(Data, ContentList, [])
+ end,
+
+ %% Build new vcard
+ SubEl = {xmlel, <<"vCard">>, [{<<"xmlns">>,<<"vcard-temp">>}], A4},
+ IQ2 = #iq{type=set, sub_el = SubEl},
+
+ Module:Function(JID, JID, IQ2),
+ ok.
+
+take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) ->
+ {Taken2, NewEls2} = case lists:keymember(TelType, 2, SubEls) of
+ true -> {xml:get_subtag(OldEl, <<"NUMBER">>), NewEls};
+ false -> {Taken, [OldEl | NewEls]}
+ end,
+ take_vcard_tel(TelType, OldEls, NewEls2, Taken2);
+take_vcard_tel(TelType, [OldEl | OldEls], NewEls, Taken) ->
+ take_vcard_tel(TelType, OldEls, [OldEl | NewEls], Taken);
+take_vcard_tel(_TelType, [], NewEls, Taken) ->
+ {Taken, NewEls}.
+
+update_vcard_els([<<"TEL">>, TelType], [TelValue], OldEls) ->
+ {_, NewEls} = take_vcard_tel(TelType, OldEls, [], not_found),
+ NewEl = {xmlel,<<"TEL">>,[],
+ [{xmlel,TelType,[],[]},
+ {xmlel,<<"NUMBER">>,[],[{xmlcdata,TelValue}]}]},
+ [NewEl | NewEls];
+
+update_vcard_els(Data, ContentList, Els1) ->
+ Els2 = lists:keysort(2, Els1),
+ [Data1 | Data2] = Data,
+ NewEls = case Data2 of
+ [] ->
+ [{xmlel, Data1, [], [{xmlcdata,Content}]} || Content <- ContentList];
+ [D2] ->
+ OldEl = case lists:keysearch(Data1, 2, Els2) of
+ {value, A} -> A;
+ false -> {xmlel, Data1, [], []}
+ end,
+ {xmlel, _, _, ContentOld1} = OldEl,
+ Content2 = [{xmlel, D2, [], [{xmlcdata,Content}]} || Content <- ContentList],
+ ContentOld2 = [A || {_, X, _, _} = A <- ContentOld1, X/=D2],
+ ContentOld3 = lists:keysort(2, ContentOld2),
+ ContentNew = lists:keymerge(2, Content2, ContentOld3),
+ [{xmlel, Data1, [], ContentNew}]
+ end,
+ Els3 = lists:keydelete(Data1, 2, Els2),
+ lists:keymerge(2, NewEls, Els3).
+
+
+%%%
+%%% Roster
+%%%
+
+add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs) ->
+ case add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs, []) of
+ {atomic, ok} ->
+ push_roster_item(LocalUser, LocalServer, User, Server, {add, Nick, Subs, Group}),
+ ok;
+ _ ->
+ error
+ end.
+
+add_rosteritem(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) ->
+ subscribe(LU, LS, User, Server, Nick, Group, Subscription, Xattrs).
+
+subscribe(LU, LS, User, Server, Nick, Group, Subscription, _Xattrs) ->
+ ItemEl = build_roster_item(User, Server, {add, Nick, Subscription, Group}),
+ mod_roster:set_items(
+ LU, LS,
+ {xmlel, <<"query">>,
+ [{<<"xmlns">>, <<"jabber:iq:roster">>}],
+ [ItemEl]}).
+
+delete_rosteritem(LocalUser, LocalServer, User, Server) ->
+ case unsubscribe(LocalUser, LocalServer, User, Server) of
+ {atomic, ok} ->
+ push_roster_item(LocalUser, LocalServer, User, Server, remove),
+ ok;
+ _ ->
+ error
+ end.
+
+unsubscribe(LU, LS, User, Server) ->
+ ItemEl = build_roster_item(User, Server, remove),
+ mod_roster:set_items(
+ LU, LS,
+ {xmlel, <<"query">>,
+ [{<<"xmlns">>, <<"jabber:iq:roster">>}],
+ [ItemEl]}).
+
+%% -----------------------------
+%% Get Roster
+%% -----------------------------
+
+get_roster(User, Server) ->
+ Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
+ make_roster_xmlrpc(Items).
+
+%% Note: if a contact is in several groups, the contact is returned
+%% several times, each one in a different group.
+make_roster_xmlrpc(Roster) ->
+ lists:foldl(
+ fun(Item, Res) ->
+ JIDS = jlib:jid_to_string(Item#roster.jid),
+ Nick = Item#roster.name,
+ Subs = atom_to_list(Item#roster.subscription),
+ Ask = atom_to_list(Item#roster.ask),
+ Groups = case Item#roster.groups of
+ [] -> [<<>>];
+ Gs -> Gs
+ end,
+ ItemsX = [{JIDS, Nick, Subs, Ask, Group} || Group <- Groups],
+ ItemsX ++ Res
+ end,
+ [],
+ Roster).
+
+
+%%-----------------------------
+%% Push Roster from file
+%%-----------------------------
+
+push_roster(File, User, Server) ->
+ {ok, [Roster]} = file:consult(File),
+ subscribe_roster({User, Server, <<>>, User}, Roster).
+
+push_roster_all(File) ->
+ {ok, [Roster]} = file:consult(File),
+ subscribe_all(Roster).
+
+subscribe_all(Roster) ->
+ subscribe_all(Roster, Roster).
+subscribe_all([], _) ->
+ ok;
+subscribe_all([User1 | Users], Roster) ->
+ subscribe_roster(User1, Roster),
+ subscribe_all(Users, Roster).
+
+subscribe_roster(_, []) ->
+ ok;
+%% Do not subscribe a user to itself
+subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) ->
+ subscribe_roster({Name, Server, Group, Nick}, Roster);
+%% Subscribe Name2 to Name1
+subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
+ subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, <<"both">>, []),
+ subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
+
+push_alltoall(S, G) ->
+ Users = ejabberd_auth:get_vh_registered_users(S),
+ Users2 = build_list_users(G, Users, []),
+ subscribe_all(Users2),
+ ok.
+
+build_list_users(_Group, [], Res) ->
+ Res;
+build_list_users(Group, [{User, Server}|Users], Res) ->
+ build_list_users(Group, Users, [{User, Server, Group, User}|Res]).
+
+%% @spec(LU, LS, U, S, Action) -> ok
+%% Action = {add, Nick, Subs, Group} | remove
+%% @doc Push to the roster of account LU@LS the contact U@S.
+%% The specific action to perform is defined in Action.
+push_roster_item(LU, LS, U, S, Action) ->
+ lists:foreach(fun(R) ->
+ push_roster_item(LU, LS, R, U, S, Action)
+ end, ejabberd_sm:get_user_resources(LU, LS)).
+
+push_roster_item(LU, LS, R, U, S, Action) ->
+ LJID = jlib:make_jid(LU, LS, R),
+ BroadcastEl = build_broadcast(U, S, Action),
+ ejabberd_sm:route(LJID, LJID, BroadcastEl),
+ Item = build_roster_item(U, S, Action),
+ ResIQ = build_iq_roster_push(Item),
+ ejabberd_router:route(LJID, LJID, ResIQ).
+
+build_roster_item(U, S, {add, Nick, Subs, Group}) ->
+ {xmlel, <<"item">>,
+ [{<<"jid">>, jlib:jid_to_string(jlib:make_jid(U, S, <<>>))},
+ {<<"name">>, Nick},
+ {<<"subscription">>, Subs}],
+ [{xmlel, <<"group">>, [], [{xmlcdata, Group}]}]
+ };
+build_roster_item(U, S, remove) ->
+ {xmlel, <<"item">>,
+ [{<<"jid">>, jlib:jid_to_string(jlib:make_jid(U, S, <<>>))},
+ {<<"subscription">>, <<"remove">>}],
+ []
+ }.
+
+build_iq_roster_push(Item) ->
+ {xmlel, <<"iq">>,
+ [{<<"type">>, <<"set">>}, {<<"id">>, <<"push">>}],
+ [{xmlel, <<"query">>,
+ [{<<"xmlns">>, ?NS_ROSTER}],
+ [Item]
+ }
+ ]
+ }.
+
+build_broadcast(U, S, {add, _Nick, Subs, _Group}) ->
+ build_broadcast(U, S, list_to_atom(binary_to_list(Subs)));
+build_broadcast(U, S, remove) ->
+ build_broadcast(U, S, none);
+%% @spec (U::binary(), S::binary(), Subs::atom()) -> any()
+%% Subs = both | from | to | none
+build_broadcast(U, S, SubsAtom) when is_atom(SubsAtom) ->
+ {broadcast, {item, {U, S, <<>>}, SubsAtom}}.
+
+%%%
+%%% Last Activity
+%%%
+
+get_last(User, Server) ->
+ Mod = get_lastactivity_module(Server),
+ case ejabberd_sm:get_user_resources(User, Server) of
+ [] ->
+ case Mod:get_last_info(User, Server) of
+ not_found ->
+ "Never";
+ {ok, Shift, _Status} ->
+ TimeStamp = {Shift div 1000000,
+ Shift rem 1000000,
+ 0},
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:now_to_local_time(TimeStamp),
+ lists:flatten(
+ io_lib:format(
+ "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day, Hour, Minute, Second]))
+ end;
+ _ ->
+ "Online"
+ end.
+
+set_last(User, Server, Timestamp, Status) ->
+ Mod = get_lastactivity_module(Server),
+ Mod:store_last_info(User, Server, Timestamp, Status).
+
+%%%
+%%% Private Storage
+%%%
+
+%% Example usage:
+%% $ ejabberdctl private_set badlop localhost "\<aa\ xmlns=\'bb\'\>Cluth\</aa\>"
+%% $ ejabberdctl private_get badlop localhost aa bb
+%% <aa xmlns='bb'>Cluth</aa>
+
+private_get(Username, Host, Element, Ns) ->
+ From = jlib:make_jid(Username, Host, <<>>),
+ To = jlib:make_jid(Username, Host, <<>>),
+ IQ = {iq, <<>>, get, ?NS_PRIVATE, <<>>,
+ {xmlel, <<"query">>,
+ [{<<"xmlns">>,?NS_PRIVATE}],
+ [{xmlel, Element, [{<<"xmlns">>, Ns}], []}]}},
+ ResIq = mod_private:process_sm_iq(From, To, IQ),
+ [{xmlel, <<"query">>,
+ [{<<"xmlns">>, <<"jabber:iq:private">>}],
+ [SubEl]}] = ResIq#iq.sub_el,
+ binary_to_list(xml:element_to_binary(SubEl)).
+
+private_set(Username, Host, ElementString) ->
+ case xml_stream:parse_element(ElementString) of
+ {error, Error} ->
+ io:format("Error found parsing the element:~n ~p~nError: ~p~n",
+ [ElementString, Error]),
+ error;
+ Xml ->
+ private_set2(Username, Host, Xml)
+ end.
+
+private_set2(Username, Host, Xml) ->
+ From = jlib:make_jid(Username, Host, <<>>),
+ To = jlib:make_jid(Username, Host, <<>>),
+ IQ = {iq, <<>>, set, ?NS_PRIVATE, <<>>,
+ {xmlel, <<"query">>,
+ [{<<"xmlns">>, ?NS_PRIVATE}],
+ [Xml]}},
+ mod_private:process_sm_iq(From, To, IQ),
+ ok.
+
+%%%
+%%% Shared Roster Groups
+%%%
+
+srg_create(Group, Host, Name, Description, Display) ->
+ DisplayList = case Display of
+ <<>> -> [];
+ _ -> ejabberd_regexp:split(Display, <<"\\\\n">>)
+ end,
+ Opts = [{name, Name},
+ {displayed_groups, DisplayList},
+ {description, Description}],
+ {atomic, ok} = mod_shared_roster:create_group(Host, Group, Opts),
+ ok.
+
+srg_delete(Group, Host) ->
+ {atomic, ok} = mod_shared_roster:delete_group(Host, Group),
+ ok.
+
+srg_list(Host) ->
+ lists:sort(mod_shared_roster:list_groups(Host)).
+
+srg_get_info(Group, Host) ->
+ Opts = case mod_shared_roster:get_group_opts(Host,Group) of
+ Os when is_list(Os) -> Os;
+ error -> []
+ end,
+ [{jlib:atom_to_binary(Title),
+ io_lib:format("~p", [btl(Value)])} || {Title, Value} <- Opts].
+
+btl([]) -> [];
+btl([B|L]) -> [btl(B)|btl(L)];
+btl(B) -> binary_to_list(B).
+
+srg_get_members(Group, Host) ->
+ Members = mod_shared_roster:get_group_explicit_users(Host,Group),
+ [jlib:jid_to_string(jlib:make_jid(MUser, MServer, <<>>))
+ || {MUser, MServer} <- Members].
+
+srg_user_add(User, Host, Group, GroupHost) ->
+ {atomic, ok} = mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group),
+ ok.
+
+srg_user_del(User, Host, Group, GroupHost) ->
+ {atomic, ok} = mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group),
+ ok.
+
+
+%%%
+%%% Stanza
+%%%
+
+%% @doc Send a message to a Jabber account.
+%% @spec (Type::binary(), From::binary(), To::binary(), Subject::binary(), Body::binary()) -> ok
+send_message(Type, From, To, Subject, Body) ->
+ Packet = build_packet(Type, Subject, Body),
+ send_packet_all_resources(From, To, Packet).
+
+%% @doc Send a packet to a Jabber account.
+%% If a resource was specified in the JID,
+%% the packet is sent only to that specific resource.
+%% If no resource was specified in the JID,
+%% and the user is remote or local but offline,
+%% the packet is sent to the bare JID.
+%% If the user is local and is online in several resources,
+%% the packet is sent to all its resources.
+send_packet_all_resources(FromJIDString, ToJIDString, Packet) ->
+ FromJID = jlib:string_to_jid(FromJIDString),
+ ToJID = jlib:string_to_jid(ToJIDString),
+ ToUser = ToJID#jid.user,
+ ToServer = ToJID#jid.server,
+ case ToJID#jid.resource of
+ <<>> ->
+ send_packet_all_resources(FromJID, ToUser, ToServer, Packet);
+ Res ->
+ send_packet_all_resources(FromJID, ToUser, ToServer, Res, Packet)
+ end.
+
+send_packet_all_resources(FromJID, ToUser, ToServer, Packet) ->
+ case ejabberd_sm:get_user_resources(ToUser, ToServer) of
+ [] ->
+ send_packet_all_resources(FromJID, ToUser, ToServer, <<>>, Packet);
+ ToResources ->
+ lists:foreach(
+ fun(ToResource) ->
+ send_packet_all_resources(FromJID, ToUser, ToServer,
+ ToResource, Packet)
+ end,
+ ToResources)
+ end.
+
+send_packet_all_resources(FromJID, ToU, ToS, ToR, Packet) ->
+ ToJID = jlib:make_jid(ToU, ToS, ToR),
+ ejabberd_router:route(FromJID, ToJID, Packet).
+
+build_packet(Type, Subject, Body) ->
+ Tail = if Subject == <<"">>; Type == <<"chat">> -> [];
+ true -> [{xmlel, <<"subject">>, [], [{xmlcdata, Subject}]}]
+ end,
+ {xmlel, <<"message">>,
+ [{<<"type">>, Type}, {<<"id">>, randoms:get_string()}],
+ [{xmlel, <<"body">>, [], [{xmlcdata, Body}]} | Tail]
+ }.
+
+send_stanza_c2s(Username, Host, Resource, Stanza) ->
+ C2sPid = ejabberd_sm:get_session_pid(Username, Host, Resource),
+ XmlEl = xml_stream:parse_element(Stanza),
+ p1_fsm:send_event(C2sPid, {xmlstreamelement, XmlEl}).
+
+privacy_set(Username, Host, QueryS) ->
+ From = jlib:make_jid(Username, Host, <<"">>),
+ To = jlib:make_jid(<<"">>, Host, <<"">>),
+ QueryEl = xml_stream:parse_element(QueryS),
+ StanzaEl = {xmlel, <<"iq">>, [{<<"type">>, <<"set">>}], [QueryEl]},
+ IQ = jlib:iq_query_info(StanzaEl),
+ ejabberd_hooks:run_fold(
+ privacy_iq_set,
+ Host,
+ {error, ?ERR_FEATURE_NOT_IMPLEMENTED},
+ [From, To, IQ]
+ ),
+ ok.
+
+%%%
+%%% Stats
+%%%
+
+stats(Name) ->
+ case Name of
+ <<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000);
+ <<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:get_vh_registered_users_number(Host) + Sum end, 0, ?MYHOSTS);
+ <<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list());
+ <<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list())
+ end.
+
+stats(Name, Host) ->
+ case Name of
+ <<"registeredusers">> -> ejabberd_auth:get_vh_registered_users_number(Host);
+ <<"onlineusers">> -> length(ejabberd_sm:get_vh_session_list(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 = <<User/binary, "@", Server/binary>>,
+ Jid2string = <<RUser/binary, "@", RServer/binary>>,
+ 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 = jlib:string_to_jid(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)).
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index 0e7c9fa32..542417ff3 100644
--- a/src/mod_announce.erl
+++ b/src/mod_announce.erl
@@ -64,7 +64,7 @@
tokenize(Node) -> str:tokens(Node, <<"/#">>).
start(Host, Opts) ->
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(motd,
[{disc_copies, [node()]},
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index a96379e6d..36c8c0eed 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -321,7 +321,7 @@ init_db(_, _) ->
ok.
init([Host, Opts]) ->
- init_db(gen_mod:db_type(Opts), Host),
+ init_db(gen_mod:db_type(Host, Opts), Host),
MaxSize = gen_mod:get_opt(cache_size, Opts,
fun(I) when is_integer(I), I>0 -> I end,
1000),
diff --git a/src/mod_irc.erl b/src/mod_irc.erl
index 2cc57786c..e96fc4dc1 100644
--- a/src/mod_irc.erl
+++ b/src/mod_irc.erl
@@ -115,7 +115,7 @@ init([Host, Opts]) ->
ejabberd:start_app(p1_iconv),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"irc.@HOST@">>),
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(irc_custom,
[{disc_copies, [node()]},
diff --git a/src/mod_last.erl b/src/mod_last.erl
index 038378c7b..e079a2d38 100644
--- a/src/mod_last.erl
+++ b/src/mod_last.erl
@@ -48,7 +48,7 @@
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(last_activity,
[{disc_copies, [node()]},
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index a0c6c34e6..a3a8a9331 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -283,7 +283,7 @@ can_use_nick(LServer, Host, JID, Nick, odbc) ->
init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>),
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(muc_room,
[{disc_copies, [node()]},
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
new file mode 100644
index 000000000..3e659d2dc
--- /dev/null
+++ b/src/mod_muc_admin.erl
@@ -0,0 +1,888 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_muc_admin.erl
+%%% Author : Badlop <badlop@ono.com>
+%%% Purpose : Tools for additional MUC administration
+%%% Created : 8 Sep 2007 by Badlop <badlop@ono.com>
+%%% Id : $Id: mod_muc_admin.erl 1133 2012-10-17 22:13:06Z badlop $
+%%%----------------------------------------------------------------------
+
+-module(mod_muc_admin).
+-author('badlop@ono.com').
+
+-behaviour(gen_mod).
+
+-export([
+ start/2, stop/1, % gen_mod API
+ muc_online_rooms/1,
+ muc_unregister_nick/1,
+ create_room/3, destroy_room/3,
+ create_rooms_file/1, destroy_rooms_file/1,
+ rooms_unused_list/2, rooms_unused_destroy/2,
+ get_room_occupants/2,
+ get_room_occupants_number/2,
+ send_direct_invitation/4,
+ change_room_option/4,
+ set_room_affiliation/4,
+ get_room_affiliations/2,
+ web_menu_main/2, web_page_main/2, % Web Admin API
+ web_menu_host/3, web_page_host/3
+ ]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("jlib.hrl").
+-include("mod_muc_room.hrl").
+-include("ejabberd_http.hrl").
+-include("ejabberd_web_admin.hrl").
+-include("ejabberd_commands.hrl").
+
+%% Copied from mod_muc/mod_muc.erl
+-record(muc_online_room, {name_host, pid}).
+
+%%----------------------------
+%% gen_mod
+%%----------------------------
+
+start(Host, _Opts) ->
+ ejabberd_commands:register_commands(commands()),
+ ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
+ ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
+ ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
+ ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
+
+stop(Host) ->
+ ejabberd_commands:unregister_commands(commands()),
+ ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
+ ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
+ ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
+ ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
+
+%%%
+%%% Register commands
+%%%
+
+commands() ->
+ [
+ #ejabberd_commands{name = muc_online_rooms, tags = [muc],
+ desc = "List existing rooms ('global' to get all vhosts)",
+ module = ?MODULE, function = muc_online_rooms,
+ args = [{host, binary}],
+ result = {rooms, {list, {room, string}}}},
+ #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
+ desc = "Unregister the nick in the MUC service",
+ module = ?MODULE, function = muc_unregister_nick,
+ args = [{nick, binary}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = create_room, tags = [muc_room],
+ desc = "Create a MUC room name@service in host",
+ module = ?MODULE, function = create_room,
+ args = [{name, binary}, {service, binary},
+ {host, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = destroy_room, tags = [muc_room],
+ desc = "Destroy a MUC room",
+ module = ?MODULE, function = destroy_room,
+ args = [{name, binary}, {service, binary},
+ {host, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = create_rooms_file, tags = [muc],
+ desc = "Create the rooms indicated in file",
+ module = ?MODULE, function = create_rooms_file,
+ args = [{file, string}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = destroy_rooms_file, tags = [muc],
+ desc = "Destroy the rooms indicated in file",
+ module = ?MODULE, function = destroy_rooms_file,
+ args = [{file, string}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = rooms_unused_list, tags = [muc],
+ desc = "List the rooms that are unused for many days in host",
+ module = ?MODULE, function = rooms_unused_list,
+ args = [{host, binary}, {days, integer}],
+ result = {rooms, {list, {room, string}}}},
+ #ejabberd_commands{name = rooms_unused_destroy, tags = [muc],
+ desc = "Destroy the rooms that are unused for many days in host",
+ module = ?MODULE, function = rooms_unused_destroy,
+ args = [{host, binary}, {days, integer}],
+ result = {rooms, {list, {room, string}}}},
+
+ #ejabberd_commands{name = get_room_occupants, tags = [muc_room],
+ desc = "Get the list of occupants of a MUC room",
+ module = ?MODULE, function = get_room_occupants,
+ args = [{name, binary}, {service, binary}],
+ result = {occupants, {list,
+ {occupant, {tuple,
+ [{jid, string},
+ {nick, string},
+ {role, string}
+ ]}}
+ }}},
+
+ #ejabberd_commands{name = get_room_occupants_number, tags = [muc_room],
+ desc = "Get the number of occupants of a MUC room",
+ module = ?MODULE, function = get_room_occupants_number,
+ args = [{name, binary}, {service, binary}],
+ result = {occupants, integer}},
+
+ #ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
+ desc = "Send a direct invitation to several destinations",
+ longdesc = "Password and Message can also be: none. Users JIDs are separated with : ",
+ module = ?MODULE, function = send_direct_invitation,
+ args = [{room, binary}, {password, binary}, {reason, binary}, {users, binary}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = change_room_option, tags = [muc_room],
+ desc = "Change an option in a MUC room",
+ module = ?MODULE, function = change_room_option,
+ args = [{name, binary}, {service, binary},
+ {option, binary}, {value, binary}],
+ result = {res, rescode}},
+
+ #ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
+ desc = "Change an affiliation in a MUC room",
+ module = ?MODULE, function = set_room_affiliation,
+ args = [{name, binary}, {service, binary},
+ {jid, binary}, {affiliation, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
+ desc = "Get the list of affiliations of a MUC room",
+ module = ?MODULE, function = get_room_affiliations,
+ args = [{name, binary}, {service, binary}],
+ result = {affiliations, {list,
+ {affiliation, {tuple,
+ [{username, string},
+ {domain, string},
+ {affiliation, atom},
+ {reason, string}
+ ]}}
+ }}}
+ ].
+
+
+%%%
+%%% ejabberd commands
+%%%
+
+muc_online_rooms(ServerHost) ->
+ MUCHost = find_host(ServerHost),
+ Rooms = ets:tab2list(muc_online_room),
+ lists:foldl(
+ fun({_, {Roomname, Host}, _}, Results) ->
+ case MUCHost of
+ global ->
+ [<<Roomname/binary, "@", Host/binary>> | Results];
+ Host ->
+ [<<Roomname/binary, "@", Host/binary>> | Results];
+ _ ->
+ Results
+ end
+ end,
+ [],
+ Rooms).
+
+muc_unregister_nick(Nick) ->
+ F2 = fun(N) ->
+ [{_,Key,_}] = mnesia:index_read(muc_registered, N, 3),
+ mnesia:delete({muc_registered, Key})
+ end,
+ case mnesia:transaction(F2, [Nick], 1) of
+ {atomic, ok} ->
+ ok;
+ {aborted, _Error} ->
+ error
+ end.
+
+
+%%----------------------------
+%% Ad-hoc commands
+%%----------------------------
+
+
+%%----------------------------
+%% Web Admin
+%%----------------------------
+
+%%---------------
+%% Web Admin Menu
+
+web_menu_main(Acc, Lang) ->
+ Acc ++ [{<<"muc">>, ?T(<<"Multi-User Chat">>)}].
+
+web_menu_host(Acc, _Host, Lang) ->
+ Acc ++ [{<<"muc">>, ?T(<<"Multi-User Chat">>)}].
+
+
+%%---------------
+%% Web Admin Page
+
+-define(TDTD(L, N),
+ ?XE(<<"tr">>, [?XCT(<<"td">>, L),
+ ?XC(<<"td">>, jlib:integer_to_binary(N))
+ ])).
+
+web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
+ Res = [?XC(<<"h1">>, <<"Multi-User Chat">>),
+ ?XC(<<"h3">>, <<"Statistics">>),
+ ?XAE(<<"table">>, [],
+ [?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, ets:info(muc_online_room, size)),
+ ?TDTD(<<"Permanent rooms">>, mnesia:table_info(muc_room, size)),
+ ?TDTD(<<"Registered nicknames">>, mnesia:table_info(muc_registered, size))
+ ])
+ ]),
+ ?XE(<<"ul">>, [?LI([?ACT(<<"rooms">>, <<"List of rooms">>)])])
+ ],
+ {stop, Res};
+
+web_page_main(_, #request{path=[<<"muc">>, <<"rooms">>], q = Q, lang = Lang} = _Request) ->
+ Sort_query = get_sort_query(Q),
+ Res = make_rooms_page(global, Lang, Sort_query),
+ {stop, Res};
+
+web_page_main(Acc, _) -> Acc.
+
+web_page_host(_, Host,
+ #request{path = [<<"muc">>],
+ q = Q,
+ lang = Lang} = _Request) ->
+ Sort_query = get_sort_query(Q),
+ Res = make_rooms_page(find_host(Host), Lang, Sort_query),
+ {stop, Res};
+web_page_host(Acc, _, _) -> Acc.
+
+
+%% Returns: {normal | reverse, Integer}
+get_sort_query(Q) ->
+ case catch get_sort_query2(Q) of
+ {ok, Res} -> Res;
+ _ -> {normal, 1}
+ end.
+
+get_sort_query2(Q) ->
+ {value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
+ Integer = list_to_integer(String),
+ case Integer >= 0 of
+ true -> {ok, {normal, Integer}};
+ false -> {ok, {reverse, abs(Integer)}}
+ end.
+
+make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
+ Rooms_names = get_rooms(Host),
+ Rooms_infos = build_info_rooms(Rooms_names),
+ Rooms_sorted = sort_rooms(Sort_direction, Sort_column, Rooms_infos),
+ Rooms_prepared = prepare_rooms_infos(Rooms_sorted),
+ TList = lists:map(
+ fun(Room) ->
+ ?XE(<<"tr">>, [?XC(<<"td">>, E) || E <- Room])
+ end, Rooms_prepared),
+ Titles = [<<"Jabber ID">>,
+ <<"# participants">>,
+ <<"Last message">>,
+ <<"Public">>,
+ <<"Persistent">>,
+ <<"Logging">>,
+ <<"Just created">>,
+ <<"Title">>],
+ {Titles_TR, _} =
+ lists:mapfoldl(
+ fun(Title, Num_column) ->
+ NCS = jlib:integer_to_binary(Num_column),
+ TD = ?XE(<<"td">>, [?CT(Title),
+ ?C(<<" ">>),
+ ?ACT(<<"?sort=", NCS/binary>>, <<"<">>),
+ ?C(<<" ">>),
+ ?ACT(<<"?sort=-", NCS/binary>>, <<">">>)]),
+ {TD, Num_column+1}
+ end,
+ 1,
+ Titles),
+ [?XC(<<"h1">>, <<"Multi-User Chat">>),
+ ?XC(<<"h2">>, <<"Rooms">>),
+ ?XE(<<"table">>,
+ [?XE(<<"thead">>,
+ [?XE(<<"tr">>, Titles_TR)]
+ ),
+ ?XE(<<"tbody">>, TList)
+ ]
+ )
+ ].
+
+sort_rooms(Direction, Column, Rooms) ->
+ Rooms2 = lists:keysort(Column, Rooms),
+ case Direction of
+ normal -> Rooms2;
+ reverse -> lists:reverse(Rooms2)
+ end.
+
+build_info_rooms(Rooms) ->
+ [build_info_room(Room) || Room <- Rooms].
+
+build_info_room({Name, Host, Pid}) ->
+ C = get_room_config(Pid),
+ Title = C#config.title,
+ Public = C#config.public,
+ Persistent = C#config.persistent,
+ Logging = C#config.logging,
+
+ S = get_room_state(Pid),
+ Just_created = S#state.just_created,
+ Num_participants = length(dict:fetch_keys(S#state.users)),
+
+ History = (S#state.history)#lqueue.queue,
+ Ts_last_message =
+ case queue:is_empty(History) of
+ true ->
+ <<"A long time ago">>;
+ false ->
+ Last_message1 = queue:last(History),
+ {_, _, _, Ts_last, _} = Last_message1,
+ jlib:timestamp_to_iso(Ts_last)
+ end,
+
+ {<<Name/binary, "@", Host/binary>>,
+ Num_participants,
+ Ts_last_message,
+ Public,
+ Persistent,
+ Logging,
+ Just_created,
+ Title}.
+
+prepare_rooms_infos(Rooms) ->
+ [prepare_room_info(Room) || Room <- Rooms].
+prepare_room_info(Room_info) ->
+ {NameHost,
+ Num_participants,
+ Ts_last_message,
+ Public,
+ Persistent,
+ Logging,
+ Just_created,
+ Title} = Room_info,
+ [NameHost,
+ jlib:integer_to_binary(Num_participants),
+ Ts_last_message,
+ jlib:atom_to_binary(Public),
+ jlib:atom_to_binary(Persistent),
+ jlib:atom_to_binary(Logging),
+ jlib:atom_to_binary(Just_created),
+ Title].
+
+
+%%----------------------------
+%% Create/Delete Room
+%%----------------------------
+
+%% @spec (Name::binary(), Host::binary(), ServerHost::binary()) ->
+%% ok | error
+%% @doc Create a room immediately with the default options.
+create_room(Name, Host, ServerHost) ->
+
+ %% Get the default room options from the muc configuration
+ DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
+ default_room_options, fun(X) -> X end, []),
+
+ %% Store the room on the server, it is not started yet though at this point
+ mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts),
+
+ %% Get all remaining mod_muc parameters that might be utilized
+ Access = gen_mod:get_module_opt(ServerHost, mod_muc, access, fun(X) -> X end, all),
+ AcCreate = gen_mod:get_module_opt(ServerHost, mod_muc, access_create, fun(X) -> X end, all),
+ AcAdmin = gen_mod:get_module_opt(ServerHost, mod_muc, access_admin, fun(X) -> X end, none),
+ AcPer = gen_mod:get_module_opt(ServerHost, mod_muc, access_persistent, fun(X) -> X end, all),
+ _PersistHistory = gen_mod:get_module_opt(ServerHost, mod_muc, persist_history, fun(X) -> X end, false),
+ HistorySize = gen_mod:get_module_opt(ServerHost, mod_muc, history_size, fun(X) -> X end, 20),
+ RoomShaper = gen_mod:get_module_opt(ServerHost, mod_muc, room_shaper, fun(X) -> X end, none),
+
+ %% If the room does not exist yet in the muc_online_room
+ case mnesia:dirty_read(muc_online_room, {Name, Host}) of
+ [] ->
+ %% Start the room
+ {ok, Pid} = mod_muc_room:start(
+ Host,
+ ServerHost,
+ {Access, AcCreate, AcAdmin, AcPer},
+ Name,
+ HistorySize,
+ RoomShaper,
+ DefRoomOpts),
+ {atomic, ok} = register_room(Host, Name, Pid),
+ ok;
+ _ ->
+ error
+ end.
+
+register_room(Host, Name, Pid) ->
+ F = fun() ->
+ mnesia:write(#muc_online_room{name_host = {Name, Host},
+ pid = Pid})
+ end,
+ mnesia:transaction(F).
+
+%% Create the room only in the database.
+%% It is required to restart the MUC service for the room to appear.
+muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
+ io:format("Creating room ~s@~s~n", [Name, Host]),
+ mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts).
+
+%% @spec (Name::binary(), Host::binary(), ServerHost::binary()) ->
+%% ok | {error, room_not_exists}
+%% @doc Destroy the room immediately.
+%% If the room has participants, they are not notified that the room was destroyed;
+%% they will notice when they try to chat and receive an error that the room doesn't exist.
+destroy_room(Name, Service, _Server) ->
+ case mnesia:dirty_read(muc_online_room, {Name, Service}) of
+ [R] ->
+ Pid = R#muc_online_room.pid,
+ gen_fsm:send_all_state_event(Pid, destroy),
+ ok;
+ [] ->
+ error
+ end.
+
+destroy_room({N, H, SH}) ->
+ io:format("Destroying room: ~s@~s - vhost: ~s~n", [N, H, SH]),
+ destroy_room(N, H, SH).
+
+
+%%----------------------------
+%% Destroy Rooms in File
+%%----------------------------
+
+%% The format of the file is: one chatroom JID per line
+%% The file encoding must be UTF-8
+
+destroy_rooms_file(Filename) ->
+ {ok, F} = file:open(Filename, [read]),
+ RJID = read_room(F),
+ Rooms = read_rooms(F, RJID, []),
+ file:close(F),
+ [destroy_room(A) || A <- Rooms],
+ ok.
+
+read_rooms(_F, eof, L) ->
+ L;
+
+read_rooms(F, RJID, L) ->
+ RJID2 = read_room(F),
+ read_rooms(F, RJID2, [RJID | L]).
+
+read_room(F) ->
+ case io:get_line(F, "") of
+ eof -> eof;
+ String ->
+ case io_lib:fread("~s", String) of
+ {ok, [RoomJID], _} -> split_roomjid(RoomJID);
+ {error, What} ->
+ io:format("Parse error: what: ~p~non the line: ~p~n~n", [What, String])
+ end
+ end.
+
+%% This function is quite rudimentary
+%% and may not be accurate
+split_roomjid(RoomJID) ->
+ [Name, Host] = string:tokens(RoomJID, "@"),
+ [_MUC_service_name | ServerHostList] = string:tokens(Host, "."),
+ ServerHost = join(ServerHostList, "."),
+ {Name, Host, ServerHost}.
+
+%% This function is copied from string:join/2 in Erlang/OTP R12B-1
+%% Note that string:join/2 is not implemented in Erlang/OTP R11B
+join([H|T], Sep) ->
+ H ++ lists:concat([Sep ++ X || X <- T]).
+
+
+%%----------------------------
+%% Create Rooms in File
+%%----------------------------
+
+create_rooms_file(Filename) ->
+ {ok, F} = file:open(Filename, [read]),
+ RJID = read_room(F),
+ Rooms = read_rooms(F, RJID, []),
+ file:close(F),
+ %% Read the default room options defined for the first virtual host
+ DefRoomOpts = gen_mod:get_module_opt(?MYNAME, mod_muc,
+ default_room_options,
+ fun(L) when is_list(L) -> L end, []),
+ [muc_create_room(?MYNAME, A, DefRoomOpts) || A <- Rooms],
+ ok.
+
+
+%%----------------------------
+%% List/Delete Unused Rooms
+%%----------------------------
+
+%%---------------
+%% Control
+
+rooms_unused_list(Host, Days) ->
+ rooms_unused_report(list, Host, Days).
+rooms_unused_destroy(Host, Days) ->
+ rooms_unused_report(destroy, Host, Days).
+
+rooms_unused_report(Action, Host, Days) ->
+ {NA, NP, RP} = muc_unused(Action, Host, Days),
+ io:format("Unused rooms: ~p out of ~p~n", [NP, NA]),
+ [[R, <<"@">>, H] || {R, H, _P} <- RP].
+
+muc_unused(Action, ServerHost, Days) ->
+ Host = find_host(ServerHost),
+ muc_unused2(Action, ServerHost, Host, Days).
+
+muc_unused2(Action, ServerHost, Host, Last_allowed) ->
+ %% Get all required info about all existing rooms
+ Rooms_all = get_rooms(Host),
+
+ %% Decide which ones pass the requirements
+ Rooms_pass = decide_rooms(Rooms_all, Last_allowed),
+
+ Num_rooms_all = length(Rooms_all),
+ Num_rooms_pass = length(Rooms_pass),
+
+ %% Perform the desired action for matching rooms
+ act_on_rooms(Action, Rooms_pass, ServerHost),
+
+ {Num_rooms_all, Num_rooms_pass, Rooms_pass}.
+
+%%---------------
+%% Get info
+
+get_rooms(Host) ->
+ Get_room_names = fun(Room_reg, Names) ->
+ Pid = Room_reg#muc_online_room.pid,
+ case {Host, Room_reg#muc_online_room.name_host} of
+ {Host, {Name1, Host}} ->
+ [{Name1, Host, Pid} | Names];
+ {global, {Name1, Host1}} ->
+ [{Name1, Host1, Pid} | Names];
+ _ ->
+ Names
+ end
+ end,
+ ets:foldr(Get_room_names, [], muc_online_room).
+
+get_room_config(Room_pid) ->
+ {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config),
+ R.
+
+get_room_state(Room_pid) ->
+ {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
+ R.
+
+%%---------------
+%% Decide
+
+decide_rooms(Rooms, Last_allowed) ->
+ Decide = fun(R) -> decide_room(R, Last_allowed) end,
+ lists:filter(Decide, Rooms).
+
+decide_room({_Room_name, _Host, Room_pid}, Last_allowed) ->
+ C = get_room_config(Room_pid),
+ Persistent = C#config.persistent,
+
+ S = get_room_state(Room_pid),
+ Just_created = S#state.just_created,
+
+ Room_users = S#state.users,
+ Num_users = length(?DICT:to_list(Room_users)),
+
+ History = (S#state.history)#lqueue.queue,
+ Ts_now = calendar:now_to_universal_time(now()),
+ Ts_uptime = uptime_seconds(),
+ {Has_hist, Last} = case queue:is_empty(History) of
+ true ->
+ {false, Ts_uptime};
+ false ->
+ Last_message = queue:last(History),
+ {_, _, _, Ts_last, _} = Last_message,
+ Ts_diff =
+ calendar:datetime_to_gregorian_seconds(Ts_now)
+ - calendar:datetime_to_gregorian_seconds(Ts_last),
+ {true, Ts_diff}
+ end,
+
+ case {Persistent, Just_created, Num_users, Has_hist, seconds_to_days(Last)} of
+ {_true, false, 0, _, Last_days}
+ when Last_days >= Last_allowed ->
+ true;
+ _ ->
+ false
+ end.
+
+seconds_to_days(S) ->
+ S div (60*60*24).
+
+%%---------------
+%% Act
+
+act_on_rooms(Action, Rooms, ServerHost) ->
+ ServerHosts = [ {A, find_host(A)} || A <- ?MYHOSTS ],
+ Delete = fun({_N, H, _Pid} = Room) ->
+ SH = case ServerHost of
+ global -> find_serverhost(H, ServerHosts);
+ O -> O
+ end,
+
+ act_on_room(Action, Room, SH)
+ end,
+ lists:foreach(Delete, Rooms).
+
+find_serverhost(Host, ServerHosts) ->
+ {value, {ServerHost, Host}} = lists:keysearch(Host, 2, ServerHosts),
+ ServerHost.
+
+act_on_room(destroy, {N, H, Pid}, SH) ->
+ gen_fsm:send_all_state_event(
+ Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
+ mod_muc:room_destroyed(H, N, Pid, SH),
+ mod_muc:forget_room(SH, H, N);
+
+act_on_room(list, _, _) ->
+ ok.
+
+
+%%----------------------------
+%% Change Room Option
+%%----------------------------
+
+get_room_occupants(Room, Host) ->
+ case get_room_pid(Room, Host) of
+ room_not_found -> throw({error, room_not_found});
+ Pid -> get_room_occupants(Pid)
+ end.
+
+get_room_occupants(Pid) ->
+ S = get_room_state(Pid),
+ lists:map(
+ fun({_LJID, Info}) ->
+ {jlib:jid_to_string(Info#user.jid),
+ Info#user.nick,
+ atom_to_list(Info#user.role)}
+ end,
+ dict:to_list(S#state.users)).
+
+get_room_occupants_number(Room, Host) ->
+ length(get_room_occupants(Room, Host)).
+
+%%----------------------------
+%% Send Direct Invitation
+%%----------------------------
+%% http://xmpp.org/extensions/xep-0249.html
+
+send_direct_invitation(RoomString, Password, Reason, UsersString) ->
+ RoomJid = jlib:string_to_jid(RoomString),
+ XmlEl = build_invitation(Password, Reason, RoomString),
+ UsersStrings = get_users_to_invite(RoomJid, binary_to_list(UsersString)),
+ [send_direct_invitation(RoomJid, jlib:string_to_jid(list_to_binary(UserStrings)), XmlEl)
+ || UserStrings <- UsersStrings],
+ timer:sleep(1000),
+ ok.
+
+get_users_to_invite(RoomJid, UsersString) ->
+ UsersStrings = string:tokens(UsersString, ":"),
+ OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
+ RoomJid#jid.lserver),
+ OccupantsJids = [jlib:string_to_jid(JidString)
+ || {JidString, _Nick, _} <- OccupantsTuples],
+ lists:filter(
+ fun(UserString) ->
+ UserJid = jlib:string_to_jid(list_to_binary(UserString)),
+ %% [{"badlop@localhost/work","badlop","moderator"}]
+ lists:all(fun(OccupantJid) ->
+ UserJid#jid.luser /= OccupantJid#jid.luser
+ orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
+ end,
+ OccupantsJids)
+ end,
+ UsersStrings).
+
+build_invitation(Password, Reason, RoomString) ->
+ PasswordAttrList = case Password of
+ <<"none">> -> [];
+ _ -> [{<<"password">>, Password}]
+ end,
+ ReasonAttrList = case Reason of
+ <<"none">> -> [];
+ _ -> [{<<"reason">>, Reason}]
+ end,
+ XAttrs = [{<<"xmlns">>, ?NS_XCONFERENCE},
+ {<<"jid">>, RoomString}]
+ ++ PasswordAttrList
+ ++ ReasonAttrList,
+ XEl = {xmlel, <<"x">>, XAttrs, []},
+ {xmlel, <<"message">>, [], [XEl]}.
+
+send_direct_invitation(FromJid, UserJid, XmlEl) ->
+ ejabberd_router:route(FromJid, UserJid, XmlEl).
+
+%%----------------------------
+%% Change Room Option
+%%----------------------------
+
+%% @spec(Name::string(), Service::string(), Option::string(), Value) -> ok
+%% Value = atom() | integer() | string()
+%% @doc Change an option in an existing room.
+%% Requires the name of the room, the MUC service where it exists,
+%% the option to change (for example title or max_users),
+%% and the value to assign to the new option.
+%% For example:
+%% change_room_option("testroom", "conference.localhost", "title", "Test Room")
+change_room_option(Name, Service, Option, Value) when is_atom(Option) ->
+ Pid = get_room_pid(Name, Service),
+ {ok, _} = change_room_option(Pid, Option, Value),
+ ok;
+change_room_option(Name, Service, OptionString, ValueString) ->
+ Option = jlib:binary_to_atom(OptionString),
+ Value = case Option of
+ title -> ValueString;
+ description -> ValueString;
+ password -> ValueString;
+ subject ->ValueString;
+ subject_author ->ValueString;
+ max_users -> jlib:binary_to_integer(ValueString);
+ _ -> jlib:binary_to_atom(ValueString)
+ end,
+ change_room_option(Name, Service, Option, Value).
+
+change_room_option(Pid, Option, Value) ->
+ Config = get_room_config(Pid),
+ Config2 = change_option(Option, Value, Config),
+ gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}).
+
+%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
+get_room_pid(Name, Service) ->
+ case mnesia:dirty_read(muc_online_room, {Name, Service}) of
+ [] ->
+ room_not_found;
+ [Room] ->
+ Room#muc_online_room.pid
+ end.
+
+%% It is required to put explicitely all the options because
+%% the record elements are replaced at compile time.
+%% So, this can't be parametrized.
+change_option(Option, Value, Config) ->
+ case Option of
+ allow_change_subj -> Config#config{allow_change_subj = Value};
+ allow_private_messages -> Config#config{allow_private_messages = Value};
+ allow_query_users -> Config#config{allow_query_users = Value};
+ allow_user_invites -> Config#config{allow_user_invites = Value};
+ anonymous -> Config#config{anonymous = Value};
+ logging -> Config#config{logging = Value};
+ max_users -> Config#config{max_users = Value};
+ members_by_default -> Config#config{members_by_default = Value};
+ members_only -> Config#config{members_only = Value};
+ moderated -> Config#config{moderated = Value};
+ password -> Config#config{password = Value};
+ password_protected -> Config#config{password_protected = Value};
+ persistent -> Config#config{persistent = Value};
+ public -> Config#config{public = Value};
+ public_list -> Config#config{public_list = Value};
+ title -> Config#config{title = Value}
+ end.
+
+
+%%----------------------------
+%% Get Room Affiliations
+%%----------------------------
+
+%% @spec(Name::binary(), Service::binary()) ->
+%% [{JID::string(), Domain::string(), Role::string(), Reason::string()}]
+%% @doc Get the affiliations of the room Name@Service.
+get_room_affiliations(Name, Service) ->
+ case mnesia:dirty_read(muc_online_room, {Name, Service}) of
+ [R] ->
+ %% Get the PID of the online room, then request its state
+ Pid = R#muc_online_room.pid,
+ {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
+ Affiliations = ?DICT:to_list(StateData#state.affiliations),
+ lists:map(
+ fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
+ {Uname, Domain, Aff, Reason};
+ ({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
+ {Uname, Domain, Aff, <<>>}
+ end, Affiliations);
+ [] ->
+ throw({error, "The room does not exist."})
+ end.
+
+%%----------------------------
+%% Change Room Affiliation
+%%----------------------------
+
+%% @spec(Name, Service, JID, AffiliationString) -> ok | {error, Error}
+%% Name = binary()
+%% Service = binary()
+%% JID = binary()
+%% AffiliationString = "outcast" | "none" | "member" | "admin" | "owner"
+%% @doc Set the affiliation of JID in the room Name@Service.
+%% If the affiliation is 'none', the action is to remove,
+%% In any other case the action will be to create the affiliation.
+set_room_affiliation(Name, Service, JID, AffiliationString) ->
+ Affiliation = jlib:binary_to_atom(AffiliationString),
+ case mnesia:dirty_read(muc_online_room, {Name, Service}) of
+ [R] ->
+ %% Get the PID for the online room so we can get the state of the room
+ Pid = R#muc_online_room.pid,
+ {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
+ SJID = jlib:string_to_jid(JID),
+ LJID = jlib:jid_remove_resource(jlib:jid_tolower(SJID)),
+ Affiliations = change_affiliation(Affiliation, LJID, StateData#state.affiliations),
+ Res = StateData#state{affiliations = Affiliations},
+ {ok, _State} = gen_fsm:sync_send_all_state_event(Pid, {change_state, Res}),
+ mod_muc:store_room(Res#state.server_host, Res#state.host, Res#state.room, make_opts(Res)),
+ ok;
+ [] ->
+ error
+ end.
+
+change_affiliation(none, LJID, Affiliations) ->
+ ?DICT:erase(LJID, Affiliations);
+change_affiliation(Affiliation, LJID, Affiliations) ->
+ ?DICT:store(LJID, Affiliation, Affiliations).
+
+-define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
+
+make_opts(StateData) ->
+ Config = StateData#state.config,
+ [
+ ?MAKE_CONFIG_OPT(title),
+ ?MAKE_CONFIG_OPT(allow_change_subj),
+ ?MAKE_CONFIG_OPT(allow_query_users),
+ ?MAKE_CONFIG_OPT(allow_private_messages),
+ ?MAKE_CONFIG_OPT(public),
+ ?MAKE_CONFIG_OPT(public_list),
+ ?MAKE_CONFIG_OPT(persistent),
+ ?MAKE_CONFIG_OPT(moderated),
+ ?MAKE_CONFIG_OPT(members_by_default),
+ ?MAKE_CONFIG_OPT(members_only),
+ ?MAKE_CONFIG_OPT(allow_user_invites),
+ ?MAKE_CONFIG_OPT(password_protected),
+ ?MAKE_CONFIG_OPT(password),
+ ?MAKE_CONFIG_OPT(anonymous),
+ ?MAKE_CONFIG_OPT(logging),
+ ?MAKE_CONFIG_OPT(max_users),
+ {affiliations, ?DICT:to_list(StateData#state.affiliations)},
+ {subject, StateData#state.subject},
+ {subject_author, StateData#state.subject_author}
+ ].
+
+
+%%----------------------------
+%% Utils
+%%----------------------------
+
+uptime_seconds() ->
+ trunc(element(1, erlang:statistics(wall_clock))/1000).
+
+find_host(global) ->
+ global;
+find_host("global") ->
+ global;
+find_host(<<"global">>) ->
+ global;
+find_host(ServerHost) when is_list(ServerHost) ->
+ find_host(list_to_binary(ServerHost));
+find_host(ServerHost) ->
+ gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>).
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 3a992172b..7f9a81a0d 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -97,7 +97,8 @@ start(Host, Opts) ->
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ?GEN_SERVER:call(Proc, stop),
+ catch ?GEN_SERVER:call(Proc, stop),
+ supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc),
ok.
@@ -107,7 +108,7 @@ stop(Host) ->
%%====================================================================
init([Host, Opts]) ->
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(offline_msg,
[{disc_only_copies, [node()]}, {type, bag},
@@ -235,7 +236,7 @@ store_offline_msg(Host, {User, _}, Msgs, Len, MaxOfflineMsgs,
Len + count_offline_messages(User, Host);
true -> 0
end,
- if
+ if
Count > MaxOfflineMsgs ->
discard_warn_sender(Msgs);
true ->
@@ -560,7 +561,19 @@ remove_old_messages(Days, _LServer, mnesia) ->
ok, offline_msg)
end,
mnesia:transaction(F);
-remove_old_messages(_Days, _LServer, odbc) ->
+
+remove_old_messages(Days, LServer, odbc) ->
+ case catch ejabberd_odbc:sql_query(
+ LServer,
+ [<<"DELETE FROM spool"
+ " WHERE created_at < "
+ "DATE_SUB(CURDATE(), INTERVAL ">>,
+ integer_to_list(Days), <<" DAY);">>]) of
+ {updated, N} ->
+ ?INFO_MSG("~p message(s) deleted from offline spool", [N]);
+ _Error ->
+ ?ERROR_MSG("Cannot delete message in offline spool: ~p", [_Error])
+ end,
{atomic, ok};
remove_old_messages(_Days, _LServer, riak) ->
{atomic, ok}.
@@ -1044,10 +1057,7 @@ count_offline_messages(LUser, LServer, riak) ->
Res;
_ ->
0
- end;
-count_offline_messages(_Acc, User, Server) ->
- N = count_offline_messages(User, Server),
- {stop, N}.
+ end.
%% Return the number of records matching a given match expression.
%% This function is intended to be used inside a Mnesia transaction.
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index 971be9524..fd3f60247 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -58,7 +58,7 @@ privacy_schema() ->
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(privacy,
[{disc_copies, [node()]},
diff --git a/src/mod_private.erl b/src/mod_private.erl
index cedcb2787..f09c6100e 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -49,7 +49,7 @@
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(private_storage,
[{disc_only_copies, [node()]},
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 08e351462..579c47757 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -74,8 +74,7 @@
on_user_offline/3, remove_user/2,
disco_local_identity/5, disco_local_features/5,
disco_local_items/5, disco_sm_identity/5,
- disco_sm_features/5, disco_sm_items/5,
- drop_pep_error/4]).
+ disco_sm_features/5, disco_sm_items/5]).
%% exported iq handlers
-export([iq_sm/3]).
@@ -345,8 +344,6 @@ init([ServerHost, Opts]) ->
?MODULE, disco_sm_features, 75),
ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE,
disco_sm_items, 75),
- ejabberd_hooks:add(c2s_filter_packet_in, ServerHost, ?MODULE,
- drop_pep_error, 75),
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
@@ -1332,33 +1329,6 @@ unsubscribe_user(Entity, Owner) ->
end).
%% -------
-%% packet receive hook handling function
-%%
-
-drop_pep_error(#xmlel{name = <<"message">>, attrs = Attrs} = Packet, _JID, From,
- #jid{lresource = <<"">>} = To) ->
- case xml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> ->
- case xml:get_subtag(Packet, <<"event">>) of
- #xmlel{attrs = EventAttrs} ->
- case xml:get_attr_s(<<"xmlns">>, EventAttrs) of
- ?NS_PUBSUB_EVENT ->
- ?DEBUG("Dropping PEP error message from ~s to ~s",
- [jlib:jid_to_string(From),
- jlib:jid_to_string(To)]),
- drop;
- _ ->
- Packet
- end;
- false ->
- Packet
- end;
- _ ->
- Packet
- end;
-drop_pep_error(Acc, _JID, _From, _To) -> Acc.
-
-%% -------
%% user remove hook handling function
%%
@@ -1498,8 +1468,6 @@ terminate(_Reason,
?MODULE, disco_sm_features, 75),
ejabberd_hooks:delete(disco_sm_items, ServerHost,
?MODULE, disco_sm_items, 75),
- ejabberd_hooks:delete(c2s_filter_packet_in, ServerHost,
- ?MODULE, drop_pep_error, 75),
gen_iq_handler:remove_iq_handler(ejabberd_sm,
ServerHost, ?NS_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_sm,
diff --git a/src/mod_pubsub_odbc.erl b/src/mod_pubsub_odbc.erl
index 4b9787821..8b32b83e2 100644
--- a/src/mod_pubsub_odbc.erl
+++ b/src/mod_pubsub_odbc.erl
@@ -74,8 +74,7 @@
on_user_offline/3, remove_user/2,
disco_local_identity/5, disco_local_features/5,
disco_local_items/5, disco_sm_identity/5,
- disco_sm_features/5, disco_sm_items/5,
- drop_pep_error/4]).
+ disco_sm_features/5, disco_sm_items/5]).
%% exported iq handlers
-export([iq_sm/3]).
@@ -345,8 +344,6 @@ init([ServerHost, Opts]) ->
?MODULE, disco_sm_features, 75),
ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE,
disco_sm_items, 75),
- ejabberd_hooks:add(c2s_filter_packet_in, ServerHost, ?MODULE,
- drop_pep_error, 75),
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
@@ -942,33 +939,6 @@ unsubscribe_user(Entity, Owner) ->
end).
%% -------
-%% packet receive hook handling function
-%%
-
-drop_pep_error(#xmlel{name = <<"message">>, attrs = Attrs} = Packet, _JID, From,
- #jid{lresource = <<"">>} = To) ->
- case xml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> ->
- case xml:get_subtag(Packet, <<"event">>) of
- #xmlel{attrs = EventAttrs} ->
- case xml:get_attr_s(<<"xmlns">>, EventAttrs) of
- ?NS_PUBSUB_EVENT ->
- ?DEBUG("Dropping PEP error message from ~s to ~s",
- [jlib:jid_to_string(From),
- jlib:jid_to_string(To)]),
- drop;
- _ ->
- Packet
- end;
- false ->
- Packet
- end;
- _ ->
- Packet
- end;
-drop_pep_error(Acc, _JID, _From, _To) -> Acc.
-
-%% -------
%% user remove hook handling function
%%
@@ -1108,8 +1078,6 @@ terminate(_Reason,
?MODULE, disco_sm_features, 75),
ejabberd_hooks:delete(disco_sm_items, ServerHost,
?MODULE, disco_sm_items, 75),
- ejabberd_hooks:delete(c2s_filter_packet_in, ServerHost,
- ?MODULE, drop_pep_error, 75),
gen_iq_handler:remove_iq_handler(ejabberd_sm,
ServerHost, ?NS_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_sm,
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index e60337cda..605e8e367 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -64,7 +64,7 @@
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(roster,
[{disc_copies, [node()]},
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index 16800ded4..4c3f177ef 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -58,7 +58,7 @@
group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
start(Host, Opts) ->
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(sr_group,
[{disc_copies, [node()]},
diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl
index 7d2860ee6..ba23d0688 100644
--- a/src/mod_vcard.erl
+++ b/src/mod_vcard.erl
@@ -52,7 +52,7 @@
-define(PROCNAME, ejabberd_mod_vcard).
start(Host, Opts) ->
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(vcard,
[{disc_only_copies, [node()]},
diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl
index 97d9abbb4..41a07bbc3 100644
--- a/src/mod_vcard_xupdate.erl
+++ b/src/mod_vcard_xupdate.erl
@@ -28,7 +28,7 @@
%%====================================================================
start(Host, Opts) ->
- case gen_mod:db_type(Opts) of
+ case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(vcard_xupdate,
[{disc_copies, [node()]},
diff --git a/src/odbc_queries.erl b/src/odbc_queries.erl
index 86c3af6d0..7dee1a047 100644
--- a/src/odbc_queries.erl
+++ b/src/odbc_queries.erl
@@ -28,8 +28,10 @@
-author("mremond@process-one.net").
-export([get_db_type/0, update/5, update_t/4, sql_transaction/2,
- get_last/2, set_last_t/4, del_last/2, get_password/2,
- set_password_t/3, add_user/3, del_user/2,
+ get_last/2, set_last_t/4, del_last/2,
+ get_password/2, get_password_scram/2,
+ set_password_t/3, set_password_scram_t/6,
+ add_user/3, add_user_scram/6, del_user/2,
del_user_return_password/3, list_users/1, list_users/2,
users_number/1, users_number/2, add_spool_sql/2,
add_spool/2, get_and_del_spool_msg_t/2, del_spool_msg/2,
@@ -157,6 +159,12 @@ get_password(LServer, Username) ->
[<<"select password from users where username='">>,
Username, <<"';">>]).
+get_password_scram(LServer, Username) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ [<<"select password, serverkey, salt, iterationcount from users where "
+ "username='">>, Username, <<"';">>]).
+
set_password_t(LServer, Username, Pass) ->
ejabberd_odbc:sql_transaction(LServer,
fun () ->
@@ -168,12 +176,39 @@ set_password_t(LServer, Username, Pass) ->
<<"'">>])
end).
+set_password_scram_t(LServer, Username,
+ StoredKey, ServerKey, Salt, IterationCount) ->
+ ejabberd_odbc:sql_transaction(LServer,
+ fun () ->
+ update_t(<<"users">>,
+ [<<"username">>,
+ <<"password">>,
+ <<"serverkey">>,
+ <<"salt">>,
+ <<"iterationcount">>],
+ [Username, StoredKey,
+ ServerKey, Salt,
+ IterationCount],
+ [<<"username='">>, Username,
+ <<"'">>])
+ end).
+
add_user(LServer, Username, Pass) ->
ejabberd_odbc:sql_query(LServer,
[<<"insert into users(username, password) "
"values ('">>,
Username, <<"', '">>, Pass, <<"');">>]).
+add_user_scram(LServer, Username,
+ StoredKey, ServerKey, Salt, IterationCount) ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"insert into users(username, password, serverkey, salt, iterationcount) "
+ "values ('">>,
+ Username, <<"', '">>, StoredKey, <<"', '">>,
+ ServerKey, <<"', '">>,
+ Salt, <<"', '">>,
+ IterationCount, <<"');">>]).
+
del_user(LServer, Username) ->
ejabberd_odbc:sql_query(LServer,
[<<"delete from users where username='">>, Username,
diff --git a/src/scram.erl b/src/scram.erl
index c8c85e8d5..8c7935210 100644
--- a/src/scram.erl
+++ b/src/scram.erl
@@ -41,7 +41,7 @@ salted_password(Password, Salt, IterationCount) ->
-spec client_key(binary()) -> binary().
client_key(SaltedPassword) ->
- crypto:sha_mac(SaltedPassword, <<"Client Key">>).
+ sha_mac(SaltedPassword, <<"Client Key">>).
-spec stored_key(binary()) -> binary().
@@ -50,12 +50,12 @@ stored_key(ClientKey) -> p1_sha:sha1(ClientKey).
-spec server_key(binary()) -> binary().
server_key(SaltedPassword) ->
- crypto:sha_mac(SaltedPassword, <<"Server Key">>).
+ sha_mac(SaltedPassword, <<"Server Key">>).
-spec client_signature(binary(), binary()) -> binary().
client_signature(StoredKey, AuthMessage) ->
- crypto:sha_mac(StoredKey, AuthMessage).
+ sha_mac(StoredKey, AuthMessage).
-spec client_key(binary(), binary()) -> binary().
@@ -67,20 +67,25 @@ client_key(ClientProof, ClientSignature) ->
-spec server_signature(binary(), binary()) -> binary().
server_signature(ServerKey, AuthMessage) ->
- crypto:sha_mac(ServerKey, AuthMessage).
+ sha_mac(ServerKey, AuthMessage).
hi(Password, Salt, IterationCount) ->
- U1 = crypto:sha_mac(Password, <<Salt/binary, 0, 0, 0, 1>>),
+ U1 = sha_mac(Password, <<Salt/binary, 0, 0, 0, 1>>),
list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
binary_to_list(U1),
binary_to_list(hi_round(Password, U1,
IterationCount - 1)))).
hi_round(Password, UPrev, 1) ->
- crypto:sha_mac(Password, UPrev);
+ sha_mac(Password, UPrev);
hi_round(Password, UPrev, IterationCount) ->
- U = crypto:sha_mac(Password, UPrev),
+ U = sha_mac(Password, UPrev),
list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
binary_to_list(U),
binary_to_list(hi_round(Password, U,
IterationCount - 1)))).
+
+sha_mac(Key, Data) ->
+ Context1 = crypto:hmac_init(sha, Key),
+ Context2 = crypto:hmac_update(Context1, Data),
+ crypto:hmac_final(Context2).
diff --git a/src/translate.erl b/src/translate.erl
index 9e48e0b7a..277dfa445 100644
--- a/src/translate.erl
+++ b/src/translate.erl
@@ -81,7 +81,7 @@ load_file(Lang, File) ->
io:setopts(Fd, [{encoding,latin1}]),
load_file_loop(Fd, 1, File, Lang),
file:close(Fd);
- Error ->
+ {error, Error} ->
ExitText = iolist_to_binary([File, ": ",
file:format_error(Error)]),
?ERROR_MSG("Problem loading translation file ~n~s",