mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-30 16:36:29 +01:00
Merge from trunk (r1613 to 1649).
PR: EJABP-1 SVN Revision: 1650
This commit is contained in:
parent
3190c0ed6c
commit
ab2b70f189
71
ChangeLog
71
ChangeLog
@ -1,3 +1,7 @@
|
||||
2008-10-13 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
|
||||
|
||||
Merge from trunk (r1613 to 1649).
|
||||
|
||||
2008-10-13 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
|
||||
|
||||
* src/extauth.erl (call_port/2): Replace jlib:nameprep/1 by
|
||||
@ -8,6 +12,68 @@
|
||||
|
||||
* src/ejd2odbc.erl, src/jd2ejd.erl: Convert to exmpp.
|
||||
|
||||
2008-10-13 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/web/ejabberd_web_admin.erl: When requesting page of
|
||||
nonexistent user, show 'Not Found' page (EJAB-771)
|
||||
|
||||
2008-10-12 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/web/ejabberd_web_admin.erl: Run new hook
|
||||
webadmin_user_parse_query when POST in web admin user
|
||||
page (thanks to Oleg Palij)(EJAB-747)
|
||||
* src/mod_offline.erl: Add button "Remove All Offline Messages" in
|
||||
a user page (thanks to Oleg Palij)(EJAB-747)
|
||||
* src/mod_offline_odbc.erl: Likewise
|
||||
|
||||
* src/web/ejabberd_web_admin.erl: Improve Web Admin navigation
|
||||
menu for vhosts and nodes (EJAB-734)
|
||||
|
||||
* doc/guide.tex: Explain the new ejabberdctl command 'help'
|
||||
* doc/guide.html: Likewise
|
||||
|
||||
* src/mod_configure.erl: Update calls from ctl to
|
||||
commands (EJAB-694)
|
||||
* src/web/ejabberd_web_admin.erl: Likewise
|
||||
|
||||
* src/ejabberd_sm.erl: Update from ctl to commands (EJAB-694)
|
||||
* src/ejabberd_s2s.erl: Likewise
|
||||
|
||||
* src/ejabberd_auth.erl: Update from ctl to commands (EJAB-694)
|
||||
* src/ejabberd_auth_internal.erl: Likewise
|
||||
* src/ejabberd_auth_ldap.erl: Likewise
|
||||
* src/ejabberd_auth_odbc.erl: Likewise
|
||||
|
||||
* src/ejabberdctl.template: Move help print to a separate
|
||||
function (EJAB-694)
|
||||
|
||||
* src/ejabberd_ctl.erl: Add frontend support for
|
||||
commands (EJAB-694). Categorization and sorting of commands in
|
||||
ejabberd_ctl help (EJAB-313). Lines in command line help of length
|
||||
80, and text formatting (EJAB-473)
|
||||
|
||||
* src/ejabberd_app.erl: Initialize ejabberd_commands and start
|
||||
ejabbed_admin (EJAB-694)
|
||||
|
||||
* src/ejabberd_admin.erl: Implement commands from old
|
||||
ejabberd_ctl (EJAB-694)
|
||||
|
||||
* src/ejabberd_commands.erl: New 'ejabberd commands': separate
|
||||
command definition and calling interface (EJAB-694)
|
||||
* src/ejabberd_commands.hrl: Likewise
|
||||
|
||||
* src/mod_proxy65/mod_proxy65.erl: Update so the listener starts
|
||||
correctly (EJAB-303)
|
||||
* src/mod_proxy65/mod_proxy65_service.erl: Likewise
|
||||
|
||||
* src/ejabberd_app.erl: Start listeners explicitely at server
|
||||
start after everything else (EJAB-303). Implement support in
|
||||
ejabberd for 'independent listeners', which handle their
|
||||
connections themselves: gen_tcp:listen, etc.
|
||||
* src/ejabberd_listener.erl: Likewise
|
||||
* src/ejabberd_socket.erl: Likewise
|
||||
* src/ejabberd_sup.erl: Likewise
|
||||
|
||||
2008-10-10 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
|
||||
|
||||
* src/ejabberd_c2s.erl (is_auth_packet/1): Fix a bug where
|
||||
@ -63,6 +129,11 @@
|
||||
* src/mod_private.erl, src/mod_private_odbc.erl, src/mod_version.erl:
|
||||
Convert to exmpp. Thanks to Pablo Polvorin!
|
||||
|
||||
2008-10-07 Christophe Romain <christophe.romain@process-one.net>
|
||||
|
||||
* src/mod_pubsub/mod_pubsub.erl: uncomment pubsub_publish_item hook
|
||||
call (EJAB-765)
|
||||
|
||||
2008-10-07 Jerome Sautret <jerome.sautret@process-one.net>
|
||||
|
||||
* src/mod_roster_odbc.erl: fix MySQL multiple requests issue.
|
||||
|
@ -413,8 +413,8 @@ you can execute <TT>ejabberdctl</TT> with either that system account or root.</P
|
||||
</P><PRE CLASS="verbatim">ejabberdctl start
|
||||
|
||||
ejabberdctl status
|
||||
Node ejabberd@localhost is started. Status: started
|
||||
ejabberd is running
|
||||
The node ejabberd@localhost is started with status: started
|
||||
ejabberd is running in that node
|
||||
|
||||
ejabberdctl stop
|
||||
</PRE><P>If <TT>ejabberd</TT> doesn’t start correctly and a crash dump is generated,
|
||||
@ -2894,7 +2894,8 @@ the available parameters are:
|
||||
<TT>ejabberdctl</TT> shows all the available commands in that server.
|
||||
The more interesting ones are:
|
||||
</P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
<B><TT>status</TT></B></DT><DD CLASS="dd-description"> Check the status of the <TT>ejabberd</TT> server.
|
||||
<B><TT>help</TT></B></DT><DD CLASS="dd-description"> Get help about ejabberdctl or any available command. Try <TT>ejabberdctl help help</TT>.
|
||||
</DD><DT CLASS="dt-description"><B><TT>status</TT></B></DT><DD CLASS="dd-description"> Check the status of the <TT>ejabberd</TT> server.
|
||||
</DD><DT CLASS="dt-description"><B><TT>stop</TT></B></DT><DD CLASS="dd-description"> Stop the <TT>ejabberd</TT> server which is running in the machine.
|
||||
</DD><DT CLASS="dt-description"><B><TT>reopen-log</TT></B></DT><DD CLASS="dd-description"> If you use a tool to rotate logs, you have to configure it
|
||||
so that this command is executed after each rotation.
|
||||
|
@ -425,8 +425,8 @@ Usage example:
|
||||
ejabberdctl start
|
||||
|
||||
ejabberdctl status
|
||||
Node ejabberd@localhost is started. Status: started
|
||||
ejabberd is running
|
||||
The node ejabberd@localhost is started with status: started
|
||||
ejabberd is running in that node
|
||||
|
||||
ejabberdctl stop
|
||||
\end{verbatim}
|
||||
@ -3712,6 +3712,7 @@ If there is an \ejabberd{} server running in the system,
|
||||
\term{ejabberdctl} shows all the available commands in that server.
|
||||
The more interesting ones are:
|
||||
\begin{description}
|
||||
\titem{help} Get help about ejabberdctl or any available command. Try \term{ejabberdctl help help}.
|
||||
\titem{status} Check the status of the \ejabberd{} server.
|
||||
\titem{stop} Stop the \ejabberd{} server which is running in the machine.
|
||||
\titem{reopen-log} If you use a tool to rotate logs, you have to configure it
|
||||
|
@ -1,11 +1,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_admin.erl
|
||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||
%%% Description : This module gathers admin functions used by different
|
||||
%%% access method:
|
||||
%%% - ejabberdctl command-line tool
|
||||
%%% - web admin interface
|
||||
%%% - adhoc mode
|
||||
%%% Purpose : Administrative functions and commands
|
||||
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@ -31,9 +27,237 @@
|
||||
-module(ejabberd_admin).
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-export([restore/1]).
|
||||
-export([start/0, stop/0,
|
||||
%% Server
|
||||
status/0, reopen_log/0,
|
||||
%% Accounts
|
||||
register/3, unregister/2,
|
||||
registered_users/1,
|
||||
%% Migration
|
||||
import_file/1, import_dir/1,
|
||||
%% Purge DB
|
||||
delete_expired_messages/0, delete_old_messages/1,
|
||||
%% Mnesia
|
||||
backup_mnesia/1, restore_mnesia/1,
|
||||
dump_mnesia/1, load_mnesia/1,
|
||||
install_fallback_mnesia/1,
|
||||
dump_to_textfile/1,
|
||||
restore/1 % Still used by some modules
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
start() ->
|
||||
ejabberd_commands:register_commands(commands()).
|
||||
|
||||
stop() ->
|
||||
ejabberd_commands:unregister_commands(commands()).
|
||||
|
||||
%%%
|
||||
%%% ejabberd commands
|
||||
%%%
|
||||
|
||||
commands() ->
|
||||
[
|
||||
%% The commands status, stop and restart are implemented also in ejabberd_ctl
|
||||
%% They are defined here so that other interfaces can use them too
|
||||
#ejabberd_commands{name = status, tags = [server],
|
||||
desc = "Get status of the ejabberd server",
|
||||
module = ?MODULE, function = status,
|
||||
args = [], result = {res, restuple}},
|
||||
#ejabberd_commands{name = stop, tags = [server],
|
||||
desc = "Stop ejabberd gracefully",
|
||||
module = init, function = stop,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = restart, tags = [server],
|
||||
desc = "Restart ejabberd gracefully",
|
||||
module = init, function = restart,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = reopen_log, tags = [logs, server],
|
||||
desc = "Reopen the log files",
|
||||
module = ?MODULE, function = reopen_log,
|
||||
args = [], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = register, tags = [accounts],
|
||||
desc = "Register a user",
|
||||
module = ?MODULE, function = register,
|
||||
args = [{user, string}, {host, string}, {password, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = unregister, tags = [accounts],
|
||||
desc = "Unregister a user",
|
||||
module = ?MODULE, function = unregister,
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = registered_users, tags = [accounts],
|
||||
desc = "List all registered users in HOST",
|
||||
module = ?MODULE, function = registered_users,
|
||||
args = [{host, string}],
|
||||
result = {users, {list, {username, string}}}},
|
||||
|
||||
#ejabberd_commands{name = import_file, tags = [mnesia],
|
||||
desc = "Import user data from jabberd-1.4 spool file",
|
||||
module = ?MODULE, function = import_file,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = import_dir, tags = [mnesia],
|
||||
desc = "Import user data from jabberd-1.4 spool dir",
|
||||
module = ?MODULE, function = import_dir,
|
||||
args = [{file, string}],
|
||||
result = {res, restuple}},
|
||||
|
||||
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
|
||||
desc = "Delete expired offline messages from database",
|
||||
module = ?MODULE, function = delete_expired_messages,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_old_messages, tags = [purge],
|
||||
desc = "Delete offline messages older than DAYS",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = backup, tags = [mnesia],
|
||||
desc = "Store the database to backup file",
|
||||
module = ?MODULE, function = backup_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = restore, tags = [mnesia],
|
||||
desc = "Restore the database from backup file",
|
||||
module = ?MODULE, function = restore_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = dump, tags = [mnesia],
|
||||
desc = "Dump the database to text file",
|
||||
module = ?MODULE, function = dump_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = load, tags = [mnesia],
|
||||
desc = "Restore the database from text file",
|
||||
module = ?MODULE, function = load_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = install_fallback, tags = [mnesia],
|
||||
desc = "Install the database from a fallback file",
|
||||
module = ?MODULE, function = install_fallback_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}}
|
||||
].
|
||||
|
||||
|
||||
%%%
|
||||
%%% Server management
|
||||
%%%
|
||||
|
||||
status() ->
|
||||
{InternalStatus, ProvidedStatus} = init:get_status(),
|
||||
String1 = io_lib:format("The node ~p is ~p. Status: ~p",
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
{Is_running, String2} =
|
||||
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
||||
false ->
|
||||
{ejabberd_not_running, "ejabberd is not running in that node."};
|
||||
{value, {_, _, Version}} ->
|
||||
{ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
|
||||
end,
|
||||
{Is_running, String1 ++ String2}.
|
||||
|
||||
reopen_log() ->
|
||||
ejabberd_hooks:run(reopen_log_hook, []),
|
||||
%% TODO: Use the Reopen log API for logger_h ?
|
||||
ejabberd_logger_h:reopen_log(),
|
||||
ok.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Account management
|
||||
%%%
|
||||
|
||||
register(User, Host, Password) ->
|
||||
case ejabberd_auth:try_register(User, Host, Password) of
|
||||
{atomic, ok} ->
|
||||
{ok, io_lib:format("User ~s@~s succesfully registered", [User, Host])};
|
||||
{atomic, exists} ->
|
||||
String = io_lib:format("User ~s@~s already registered at node ~p",
|
||||
[User, Host, node()]),
|
||||
{exists, String};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
|
||||
[User, Host, node(), Reason]),
|
||||
{cannot_register, String}
|
||||
end.
|
||||
|
||||
unregister(User, Host) ->
|
||||
ejabberd_auth:remove_user(User, Host),
|
||||
{ok, ""}.
|
||||
|
||||
registered_users(Host) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
SUsers = lists:sort(Users),
|
||||
lists:map(fun({U, _S}) -> U end, SUsers).
|
||||
|
||||
|
||||
%%%
|
||||
%%% Migration management
|
||||
%%%
|
||||
|
||||
import_file(Path) ->
|
||||
case jd2ejd:import_file(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't import jabberd 1.4 spool file ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_import_file, String}
|
||||
end.
|
||||
|
||||
import_dir(Path) ->
|
||||
case jd2ejd:import_dir(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't import jabberd 1.4 spool dir ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_import_dir, String}
|
||||
end.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Purge DB
|
||||
%%%
|
||||
|
||||
delete_expired_messages() ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(),
|
||||
ok.
|
||||
|
||||
delete_old_messages(Days) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days),
|
||||
ok.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Mnesia management
|
||||
%%%
|
||||
|
||||
backup_mnesia(Path) ->
|
||||
case mnesia:backup(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_backup, String}
|
||||
end.
|
||||
|
||||
restore_mnesia(Path) ->
|
||||
case ejabberd_admin:restore(Path) of
|
||||
{atomic, _} ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_restore, String};
|
||||
{aborted,{no_exists,Table}} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
|
||||
[filename:absname(Path), node(), Table]),
|
||||
{table_not_exists, String};
|
||||
{aborted,enoent} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: File not found.",
|
||||
[filename:absname(Path), node()]),
|
||||
{file_not_found, String}
|
||||
end.
|
||||
|
||||
%% Mnesia database restore
|
||||
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
|
||||
@ -71,3 +295,67 @@ module_tables(mod_roster) -> [roster];
|
||||
module_tables(mod_shared_roster) -> [sr_group, sr_user];
|
||||
module_tables(mod_vcard) -> [vcard, vcard_search];
|
||||
module_tables(_Other) -> [].
|
||||
|
||||
dump_mnesia(Path) ->
|
||||
case dump_to_textfile(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_dump, String}
|
||||
end.
|
||||
|
||||
dump_to_textfile(File) ->
|
||||
dump_to_textfile(mnesia:system_info(is_running), file:open(File, [write])).
|
||||
dump_to_textfile(yes, {ok, F}) ->
|
||||
Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
|
||||
Tabs = lists:filter(
|
||||
fun(T) ->
|
||||
case mnesia:table_info(T, storage_type) of
|
||||
disc_copies -> true;
|
||||
disc_only_copies -> true;
|
||||
_ -> false
|
||||
end
|
||||
end, Tabs1),
|
||||
Defs = lists:map(
|
||||
fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
|
||||
{attributes, mnesia:table_info(T, attributes)}]}
|
||||
end,
|
||||
Tabs),
|
||||
io:format(F, "~p.~n", [{tables, Defs}]),
|
||||
lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
|
||||
file:close(F);
|
||||
dump_to_textfile(_, {ok, F}) ->
|
||||
file:close(F),
|
||||
{error, mnesia_not_running};
|
||||
dump_to_textfile(_, {error, Reason}) ->
|
||||
{error, Reason}.
|
||||
|
||||
dump_tab(F, T) ->
|
||||
W = mnesia:table_info(T, wild_pattern),
|
||||
{atomic,All} = mnesia:transaction(
|
||||
fun() -> mnesia:match_object(T, W, read) end),
|
||||
lists:foreach(
|
||||
fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
|
||||
|
||||
load_mnesia(Path) ->
|
||||
case mnesia:load_textfile(Path) of
|
||||
{atomic, ok} ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_load, String}
|
||||
end.
|
||||
|
||||
install_fallback_mnesia(Path) ->
|
||||
case mnesia:install_fallback(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_fallback, String}
|
||||
end.
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, prep_stop/1, stop/1, init/0]).
|
||||
-export([start_modules/0,start/2, prep_stop/1, stop/1, init/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
@ -47,6 +47,8 @@ start(normal, _Args) ->
|
||||
translate:start(),
|
||||
acl:start(),
|
||||
ejabberd_ctl:init(),
|
||||
ejabberd_commands:init(),
|
||||
ejabberd_admin:start(),
|
||||
gen_mod:start(),
|
||||
ejabberd_config:start(),
|
||||
ejabberd_check:config(),
|
||||
@ -61,6 +63,7 @@ start(normal, _Args) ->
|
||||
%eprof:profile([self()]),
|
||||
%fprof:trace(start, "/tmp/fprof"),
|
||||
start_modules(),
|
||||
ejabberd_listener:start_listeners(),
|
||||
Sup;
|
||||
start(_, _) ->
|
||||
{error, badarg}.
|
||||
@ -70,6 +73,7 @@ start(_, _) ->
|
||||
%% before shutting down the processes of the application.
|
||||
prep_stop(State) ->
|
||||
stop_modules(),
|
||||
ejabberd_admin:stop(),
|
||||
State.
|
||||
|
||||
%% All the processes were killed when this function is called
|
||||
|
@ -49,14 +49,12 @@
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/1,
|
||||
ctl_process_get_registered/3
|
||||
plain_password_required/1
|
||||
]).
|
||||
|
||||
-export([auth_modules/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@ -265,15 +263,6 @@ remove_user(User, Server, Password) ->
|
||||
M:remove_user(User, Server, Password)
|
||||
end, auth_modules(Server)).
|
||||
|
||||
ctl_process_get_registered(_Val, Host, ["registered-users"]) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUsers = lists:sort(Users),
|
||||
FUsers = lists:map(fun({U, _S}) -> [U, NewLine] end, SUsers),
|
||||
?PRINT("~s", [FUsers]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process_get_registered(Val, _Host, _Args) ->
|
||||
Val.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
|
@ -53,14 +53,10 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
start(_Host) ->
|
||||
mnesia:create_table(passwd, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
update_table(),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
|
@ -112,11 +112,8 @@ start_link(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_ctl:unregister_commands(
|
||||
State#state.host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered).
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
init(Host) ->
|
||||
State = parse_options(Host),
|
||||
@ -132,10 +129,6 @@ init(Host) ->
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
{ok, State}.
|
||||
|
||||
plain_password_required() ->
|
||||
|
@ -51,11 +51,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
|
328
src/ejabberd_commands.erl
Normal file
328
src/ejabberd_commands.erl
Normal file
@ -0,0 +1,328 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_commands.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Management of ejabberd commands
|
||||
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% @headerfile "ejabberd_commands.hrl"
|
||||
|
||||
%%% @doc Management of ejabberd commands.
|
||||
%%%
|
||||
%%% An ejabberd command is an abstract function identified by a name,
|
||||
%%% with a defined number and type of calling arguments and type of
|
||||
%%% result, that can be defined in any Erlang module and executed
|
||||
%%% using any valid frontend.
|
||||
%%%
|
||||
%%%
|
||||
%%% == Define a new ejabberd command ==
|
||||
%%%
|
||||
%%% ejabberd commands can be defined and registered in
|
||||
%%% any Erlang module.
|
||||
%%%
|
||||
%%% Some commands are procedures; and their purpose is to perform an
|
||||
%%% action in the server, so the command result is only some result
|
||||
%%% code or result tuple. Other commands are inspectors, and their
|
||||
%%% purpose is to gather some information about the server and return
|
||||
%%% a detailed response: it can be integer, string, atom, tuple, list
|
||||
%%% or a mix of those ones.
|
||||
%%%
|
||||
%%% The arguments and result of an ejabberd command are strictly
|
||||
%%% defined. The number and format of the arguments provided when
|
||||
%%% calling an ejabberd command must match the definition of that
|
||||
%%% command. The format of the result provided by an ejabberd command
|
||||
%%% must be exactly its definition. For example, if a command is said
|
||||
%%% to return an integer, it must always return an integer (except in
|
||||
%%% case of a crash).
|
||||
%%%
|
||||
%%% If you are developing an Erlang module that will run inside
|
||||
%%% ejabberd and you want to provide a new ejabberd command to
|
||||
%%% administer some task related to your module, you only need to:
|
||||
%%% implement a function, define the command, and register it.
|
||||
%%%
|
||||
%%%
|
||||
%%% === Define a new ejabberd command ===
|
||||
%%%
|
||||
%%% An ejabberd command is defined using the Erlang record
|
||||
%%% 'ejabberd_commands'. This record has several elements that you
|
||||
%%% must define. Note that 'tags', 'desc' and 'longdesc' are optional.
|
||||
%%%
|
||||
%%% For example let's define an ejabberd command 'pow' that gets the
|
||||
%%% integers 'base' and 'exponent'. Its result will be an integer
|
||||
%%% 'power':
|
||||
%%%
|
||||
%%% <pre>#ejabberd_commands{name = pow, tags = [test],
|
||||
%%% desc = "Return the power of base for exponent",
|
||||
%%% longdesc = "This is an example command. The formula is:\n"
|
||||
%%% " power = base ^ exponent",
|
||||
%%% module = ?MODULE, function = pow,
|
||||
%%% args = [{base, integer}, {exponent, integer}],
|
||||
%%% result = {power, integer}}</pre>
|
||||
%%%
|
||||
%%%
|
||||
%%% === Implement the function associated to the command ===
|
||||
%%%
|
||||
%%% Now implement a function in your module that matches the arguments
|
||||
%%% and result of the ejabberd command.
|
||||
%%%
|
||||
%%% For example the function calc_power gets two integers Base and
|
||||
%%% Exponent. It calculates the power and rounds to an integer:
|
||||
%%%
|
||||
%%% <pre>calc_power(Base, Exponent) ->
|
||||
%%% PowFloat = math:pow(Base, Exponent),
|
||||
%%% round(PowFloat).</pre>
|
||||
%%%
|
||||
%%% Since this function will be called by ejabberd_commands, it must be exported.
|
||||
%%% Add to your module:
|
||||
%%% <pre>-export([calc_power/2]).</pre>
|
||||
%%%
|
||||
%%% Only some types of result formats are allowed.
|
||||
%%% If the format is defined as 'rescode', then your function must return:
|
||||
%%% ok | true | atom()
|
||||
%%% where the atoms ok and true as considered positive answers,
|
||||
%%% and any other response atom is considered negative.
|
||||
%%%
|
||||
%%% If the format is defined as 'restuple', then the command must return:
|
||||
%%% {rescode(), string()}
|
||||
%%%
|
||||
%%% If the format is defined as '{list, something()}', then the command
|
||||
%%% must return a list of something().
|
||||
%%%
|
||||
%%%
|
||||
%%% === Register the command ===
|
||||
%%%
|
||||
%%% Define this function and put inside the #ejabberd_command you
|
||||
%%% defined in the beginning:
|
||||
%%%
|
||||
%%% <pre>commands() ->
|
||||
%%% [
|
||||
%%%
|
||||
%%% ].</pre>
|
||||
%%%
|
||||
%%% You need to include this header file in order to use the record:
|
||||
%%%
|
||||
%%% <pre>-include("ejabberd_commands.hrl").</pre>
|
||||
%%%
|
||||
%%% When your module is initialized or started, register your commands:
|
||||
%%%
|
||||
%%% <pre>ejabberd_commands:register_commands(commands()),</pre>
|
||||
%%%
|
||||
%%% And when your module is stopped, unregister your commands:
|
||||
%%%
|
||||
%%% <pre>ejabberd_commands:unregister_commands(commands()),</pre>
|
||||
%%%
|
||||
%%% That's all! Now when your module is started, the command will be
|
||||
%%% registered and any frontend can access it. For example:
|
||||
%%%
|
||||
%%% <pre>$ ejabberdctl help pow
|
||||
%%%
|
||||
%%% Command Name: pow
|
||||
%%%
|
||||
%%% Arguments: base::integer
|
||||
%%% exponent::integer
|
||||
%%%
|
||||
%%% Returns: power::integer
|
||||
%%%
|
||||
%%% Tags: test
|
||||
%%%
|
||||
%%% Description: Return the power of base for exponent
|
||||
%%%
|
||||
%%% This is an example command. The formula is:
|
||||
%%% power = base ^ exponent
|
||||
%%%
|
||||
%%% $ ejabberdctl pow 3 4
|
||||
%%% 81
|
||||
%%% </pre>
|
||||
%%%
|
||||
%%%
|
||||
%%% == Execute an ejabberd command ==
|
||||
%%%
|
||||
%%% ejabberd commands are mean to be executed using any valid
|
||||
%%% frontend. An ejabberd commands is implemented in a regular Erlang
|
||||
%%% function, so it is also possible to execute this function in any
|
||||
%%% Erlang module, without dealing with the associated ejabberd
|
||||
%%% commands.
|
||||
%%%
|
||||
%%%
|
||||
%%% == Frontend to ejabberd commands ==
|
||||
%%%
|
||||
%%% Currently there are two frontends to ejabberd commands: the shell
|
||||
%%% script {@link ejabberd_ctl. ejabberdctl}, and the XML-RPC server
|
||||
%%% ejabberd_xmlrpc.
|
||||
%%%
|
||||
%%%
|
||||
%%% === ejabberdctl as a frontend to ejabberd commands ===
|
||||
%%%
|
||||
%%% It is possible to use ejabberdctl to get documentation of any
|
||||
%%% command. But ejabberdctl does not support all the argument types
|
||||
%%% allowed in ejabberd commands, so there are some ejabberd commands
|
||||
%%% that cannot be executed using ejabberdctl.
|
||||
%%%
|
||||
%%% Also note that the ejabberdctl shell administration script also
|
||||
%%% manages ejabberdctl commands, which are unrelated to ejabberd
|
||||
%%% commands and can only be executed using ejabberdctl.
|
||||
%%%
|
||||
%%%
|
||||
%%% === ejabberd_xmlrpc as a frontend to ejabberd commands ===
|
||||
%%%
|
||||
%%% ejabberd_xmlrpc provides an XML-RPC server to execute ejabberd commands.
|
||||
%%% ejabberd_xmlrpc is a contributed module published in ejabberd-modules SVN.
|
||||
%%%
|
||||
%%% Since ejabberd_xmlrpc does not provide any method to get documentation
|
||||
%%% of the ejabberd commands, please use ejabberdctl to know which
|
||||
%%% commands are available, and their usage.
|
||||
%%%
|
||||
%%% The number and format of the arguments provided when calling an
|
||||
%%% ejabberd command must match the definition of that command. Please
|
||||
%%% make sure the XML-RPC call provides the required arguments, with
|
||||
%%% the specified format. The order of the arguments in an XML-RPC
|
||||
%%% call is not important, because all the data is tagged and will be
|
||||
%%% correctly prepared by ejabberd_xmlrpc before executing the ejabberd
|
||||
%%% command.
|
||||
|
||||
%%% TODO: consider this feature:
|
||||
%%% All commands are catched. If an error happens, return the restuple:
|
||||
%%% {error, flattened error string}
|
||||
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
|
||||
%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
|
||||
|
||||
|
||||
-module(ejabberd_commands).
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-export([init/0,
|
||||
list_commands/0,
|
||||
get_command_format/1,
|
||||
get_command_definition/1,
|
||||
get_tags_commands/0,
|
||||
register_commands/1,
|
||||
unregister_commands/1,
|
||||
execute_command/2
|
||||
]).
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
|
||||
init() ->
|
||||
ets:new(ejabberd_commands, [named_table, set, public,
|
||||
{keypos, #ejabberd_commands.name}]).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
%% @doc Register ejabberd commands.
|
||||
%% If a command is already registered, a warning is printed and the old command is preserved.
|
||||
register_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
case ets:insert_new(ejabberd_commands, Command) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
?WARNING_MSG("This command is already defined:~n~p", [Command])
|
||||
end
|
||||
end,
|
||||
Commands).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
%% @doc Unregister ejabberd commands.
|
||||
unregister_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
ets:delete_object(ejabberd_commands, Command)
|
||||
end,
|
||||
Commands).
|
||||
|
||||
%% @spec () -> [{Name::atom(), Args::[aterm()], Desc::string()}]
|
||||
%% @doc Get a list of all the available commands, arguments and description.
|
||||
list_commands() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = '$1',
|
||||
args = '$2',
|
||||
desc = '$3',
|
||||
_ = '_'}),
|
||||
[{A, B, C} || [A, B, C] <- Commands].
|
||||
|
||||
%% @spec (Name::atom()) -> {Args::[aterm()], Result::rterm()} | {error, command_unknown}
|
||||
%% @doc Get the format of arguments and result of a command.
|
||||
get_command_format(Name) ->
|
||||
Matched = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = Name,
|
||||
args = '$1',
|
||||
result = '$2',
|
||||
_ = '_'}),
|
||||
case Matched of
|
||||
[] ->
|
||||
{error, command_unknown};
|
||||
[[Args, Result]] ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom()) -> ejabberd_commands() | command_not_found
|
||||
%% @doc Get the definition record of a command.
|
||||
get_command_definition(Name) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[E] -> E;
|
||||
[] -> command_not_found
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
|
||||
%% @doc Execute a command.
|
||||
execute_command(Name, Arguments) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[Command] ->
|
||||
execute_command2(Command, Arguments);
|
||||
[] ->
|
||||
{error, command_unknown}
|
||||
end.
|
||||
|
||||
execute_command2(Command, Arguments) ->
|
||||
Module = Command#ejabberd_commands.module,
|
||||
Function = Command#ejabberd_commands.function,
|
||||
apply(Module, Function, Arguments).
|
||||
|
||||
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands.
|
||||
get_tags_commands() ->
|
||||
CommandTags = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{
|
||||
name = '$1',
|
||||
tags = '$2',
|
||||
_ = '_'}),
|
||||
Dict = lists:foldl(
|
||||
fun([CommandNameAtom, CTags], D) ->
|
||||
CommandName = atom_to_list(CommandNameAtom),
|
||||
case CTags of
|
||||
[] ->
|
||||
orddict:append("untagged", CommandName, D);
|
||||
_ ->
|
||||
lists:foldl(
|
||||
fun(TagAtom, DD) ->
|
||||
Tag = atom_to_list(TagAtom),
|
||||
orddict:append(Tag, CommandName, DD)
|
||||
end,
|
||||
D,
|
||||
CTags)
|
||||
end
|
||||
end,
|
||||
orddict:new(),
|
||||
CommandTags),
|
||||
orddict:to_list(Dict).
|
52
src/ejabberd_commands.hrl
Normal file
52
src/ejabberd_commands.hrl
Normal file
@ -0,0 +1,52 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% 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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(ejabberd_commands, {name, tags = [],
|
||||
desc = "", longdesc = "",
|
||||
module, function,
|
||||
args = [], result = rescode}).
|
||||
|
||||
%% @type ejabberd_commands() = #ejabberd_commands{
|
||||
%% name = atom(),
|
||||
%% tags = [atom()],
|
||||
%% desc = string(),
|
||||
%% longdesc = string(),
|
||||
%% module = atom(),
|
||||
%% function = atom(),
|
||||
%% args = [aterm()],
|
||||
%% result = rterm()
|
||||
%% }.
|
||||
%% desc: Description of the command
|
||||
%% args: Describe the accepted arguments.
|
||||
%% This way the function that calls the command can format the
|
||||
%% arguments before calling.
|
||||
|
||||
%% @type atype() = integer | string | {tuple, [aterm()]} | {list, aterm()}.
|
||||
%% Allowed types for arguments are integer, string, tuple and list.
|
||||
|
||||
%% @type rtype() = integer | string | atom | {tuple, [rterm()]} | {list, rterm()} | rescode | restuple.
|
||||
%% A rtype is either an atom or a tuple with two elements.
|
||||
|
||||
%% @type aterm() = {Name::atom(), Type::atype()}.
|
||||
%% An argument term is a tuple with the term name and the term type.
|
||||
|
||||
%% @type rterm() = {Name::atom(), Type::rtype()}.
|
||||
%% A result term is a tuple with the term name and the term type.
|
@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_ctl.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Ejabberd admin tool
|
||||
%%% Purpose : ejabberd command line admin tool
|
||||
%%% Created : 11 Jan 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@ -24,21 +24,45 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% @headerfile "ejabberd_ctl.hrl"
|
||||
|
||||
%%% @doc Management of ejabberdctl commands and frontend to ejabberd commands.
|
||||
%%%
|
||||
%%% An ejabberdctl command is an abstract function identified by a
|
||||
%%% name, with a defined number of calling arguments, that can be
|
||||
%%% defined in any Erlang module and executed using ejabberdctl
|
||||
%%% administration script.
|
||||
%%%
|
||||
%%% Note: strings cannot have blankspaces
|
||||
%%%
|
||||
%%% Does not support commands that have arguments with ctypes: list, tuple
|
||||
%%%
|
||||
%%% TODO: Update the guide
|
||||
%%% TODO: Mention this in the release notes
|
||||
%%% Note: the commands with several words use now the underline: _
|
||||
%%% It is still possible to call the commands with dash: -
|
||||
%%% but this is deprecated, and may be removed in a future version.
|
||||
|
||||
|
||||
-module(ejabberd_ctl).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0,
|
||||
init/0,
|
||||
process/1,
|
||||
dump_to_textfile/1,
|
||||
process2/1,
|
||||
register_commands/3,
|
||||
register_commands/4,
|
||||
unregister_commands/3,
|
||||
unregister_commands/4]).
|
||||
unregister_commands/3]).
|
||||
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Module
|
||||
%%-----------------------------
|
||||
|
||||
start() ->
|
||||
case init:get_plain_arguments() of
|
||||
[SNode | Args] ->
|
||||
@ -59,8 +83,9 @@ start() ->
|
||||
Node = list_to_atom(SNode1),
|
||||
Status = case rpc:call(Node, ?MODULE, process, [Args]) of
|
||||
{badrpc, Reason} ->
|
||||
?PRINT("RPC failed on the node ~p: ~p~n",
|
||||
?PRINT("Failed RPC connection to the node ~p: ~p~n",
|
||||
[Node, Reason]),
|
||||
%% TODO: show minimal start help
|
||||
?STATUS_BADRPC;
|
||||
S ->
|
||||
S
|
||||
@ -76,16 +101,41 @@ init() ->
|
||||
ets:new(ejabberd_ctl_host_cmds, [named_table, set, public]).
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% ejabberdctl Command managment
|
||||
%%-----------------------------
|
||||
|
||||
register_commands(CmdDescs, Module, Function) ->
|
||||
ets:insert(ejabberd_ctl_cmds, CmdDescs),
|
||||
ejabberd_hooks:add(ejabberd_ctl_process,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
unregister_commands(CmdDescs, Module, Function) ->
|
||||
lists:foreach(fun(CmdDesc) ->
|
||||
ets:delete_object(ejabberd_ctl_cmds, CmdDesc)
|
||||
end, CmdDescs),
|
||||
ejabberd_hooks:delete(ejabberd_ctl_process,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Process
|
||||
%%-----------------------------
|
||||
|
||||
%% The commands status, stop and restart are defined here to ensure
|
||||
%% they are usable even if ejabberd is completely stopped.
|
||||
process(["status"]) ->
|
||||
{InternalStatus, ProvidedStatus} = init:get_status(),
|
||||
?PRINT("Node ~p is ~p. Status: ~p~n",
|
||||
?PRINT("The node ~p is ~p with status: ~p~n",
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
||||
false ->
|
||||
?PRINT("ejabberd is not running~n", []),
|
||||
?PRINT("ejabberd is not running in that node~n", []),
|
||||
?STATUS_ERROR;
|
||||
{value,_Version} ->
|
||||
?PRINT("ejabberd is running~n", []),
|
||||
{value, {_, _, Version}} ->
|
||||
?PRINT("ejabberd ~s is running in that node~n", [Version]),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
@ -97,121 +147,6 @@ process(["restart"]) ->
|
||||
init:restart(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["reopen-log"]) ->
|
||||
ejabberd_hooks:run(reopen_log_hook, []),
|
||||
lists:foreach(fun(Host) ->
|
||||
ejabberd_hooks:run(reopen_log_hook, Host, [Host])
|
||||
end, ?MYHOSTS),
|
||||
%% TODO: Use the Reopen log API for logger_h ?
|
||||
ejabberd_logger_h:reopen_log(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["register", User, Server, Password]) ->
|
||||
case ejabberd_auth:try_register(User, Server, Password) of
|
||||
{atomic, ok} ->
|
||||
?STATUS_SUCCESS;
|
||||
{atomic, exists} ->
|
||||
?PRINT("User ~p already registered at node ~p~n",
|
||||
[User ++ "@" ++ Server, node()]),
|
||||
?STATUS_ERROR;
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't register user ~p at node ~p: ~p~n",
|
||||
[User ++ "@" ++ Server, node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["unregister", User, Server]) ->
|
||||
case ejabberd_auth:remove_user(User, Server) of
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't unregister user ~p at node ~p: ~p~n",
|
||||
[User ++ "@" ++ Server, node(), Reason]),
|
||||
?STATUS_ERROR;
|
||||
_ ->
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
process(["backup", Path]) ->
|
||||
case mnesia:backup(Path) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't store backup in ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["dump", Path]) ->
|
||||
case dump_to_textfile(Path) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't store dump in ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["load", Path]) ->
|
||||
case mnesia:load_textfile(Path) of
|
||||
{atomic, ok} ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't load dump in ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["restore", Path]) ->
|
||||
case ejabberd_admin:restore(Path) of
|
||||
{atomic, _} ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't restore backup from ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR;
|
||||
{aborted,{no_exists,Table}} ->
|
||||
?PRINT("Can't restore backup from ~p at node ~p: Table ~p does not exist.~n",
|
||||
[filename:absname(Path), node(), Table]),
|
||||
?STATUS_ERROR;
|
||||
{aborted,enoent} ->
|
||||
?PRINT("Can't restore backup from ~p at node ~p: File not found.~n",
|
||||
[filename:absname(Path), node()]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["install-fallback", Path]) ->
|
||||
case mnesia:install_fallback(Path) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't install fallback from ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["import-file", Path]) ->
|
||||
case jd2ejd:import_file(Path) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't import jabberd 1.4 spool file ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["import-dir", Path]) ->
|
||||
case jd2ejd:import_dir(Path) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
?PRINT("Can't import jabberd 1.4 spool dir ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["delete-expired-messages"]) ->
|
||||
mod_offline:remove_expired_messages(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia"]) ->
|
||||
?PRINT("~p~n", [mnesia:system_info(all)]),
|
||||
?STATUS_SUCCESS;
|
||||
@ -227,183 +162,589 @@ process(["mnesia", Arg]) when is_list(Arg) ->
|
||||
end,
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["delete-old-messages", Days]) ->
|
||||
case catch list_to_integer(Days) of
|
||||
{'EXIT',{Reason, _Stack}} ->
|
||||
?PRINT("Can't delete old messages (~p). Please pass an integer as parameter.~n",
|
||||
[Reason]),
|
||||
?STATUS_ERROR;
|
||||
Integer when Integer >= 0 ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Integer),
|
||||
?PRINT("Removed messages older than ~s days~n", [Days]),
|
||||
?STATUS_SUCCESS;
|
||||
_Integer ->
|
||||
?PRINT("Can't delete old messages. Please pass a positive integer as parameter.~n", []),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["vhost", H | Args]) ->
|
||||
try
|
||||
Host = exmpp_stringprep:nameprep(H),
|
||||
case ejabberd_hooks:run_fold(
|
||||
ejabberd_ctl_process, Host, false, [Host, Args]) of
|
||||
false ->
|
||||
print_vhost_usage(Host),
|
||||
%% The arguments --long and --dual are not documented because they are
|
||||
%% automatically selected depending in the number of columns of the shell
|
||||
process(["help" | Mode]) ->
|
||||
{MaxC, ShCode} = get_shell_info(),
|
||||
case Mode of
|
||||
[] ->
|
||||
print_usage(dual, MaxC, ShCode),
|
||||
?STATUS_USAGE;
|
||||
Status ->
|
||||
Status
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
?PRINT("Bad hostname: ~p~n", [H]),
|
||||
?STATUS_ERROR
|
||||
["--dual"] ->
|
||||
print_usage(dual, MaxC, ShCode),
|
||||
?STATUS_USAGE;
|
||||
["--long"] ->
|
||||
print_usage(long, MaxC, ShCode),
|
||||
?STATUS_USAGE;
|
||||
["--tags"] ->
|
||||
print_usage_tags(MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
["--tags", Tag] ->
|
||||
print_usage_tags(Tag, MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
["help"] ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
[CommandString | _] ->
|
||||
print_usage_commands(CommandString, MaxC, ShCode),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
process(Args) ->
|
||||
case ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
|
||||
false ->
|
||||
print_usage(),
|
||||
?STATUS_USAGE;
|
||||
Status ->
|
||||
Status
|
||||
{String, Code} = process2(Args),
|
||||
io:format(String),
|
||||
io:format("\n"),
|
||||
Code.
|
||||
|
||||
%% @spec (Args::[string()]) -> {String::string(), Code::integer()}
|
||||
process2(Args) ->
|
||||
case try_run_ctp(Args) of
|
||||
{String, wrong_command_arguments}
|
||||
when is_list(String) ->
|
||||
io:format(lists:flatten(["\n" | String]++["\n"])),
|
||||
[CommandString | _] = Args,
|
||||
process(["help" | [CommandString]]),
|
||||
{lists:flatten(String), ?STATUS_ERROR};
|
||||
{String, Code}
|
||||
when is_list(String) and is_integer(Code) ->
|
||||
{lists:flatten(String), Code};
|
||||
String
|
||||
when is_list(String) ->
|
||||
{lists:flatten(String), ?STATUS_SUCCESS};
|
||||
Code
|
||||
when is_integer(Code) ->
|
||||
{"", Code};
|
||||
Other ->
|
||||
{"Erroneous result: " ++ io_lib:format("~p", [Other]), ?STATUS_ERROR}
|
||||
end.
|
||||
|
||||
|
||||
print_usage() ->
|
||||
CmdDescs =
|
||||
[{"status", "get ejabberd status"},
|
||||
{"stop", "stop ejabberd"},
|
||||
{"restart", "restart ejabberd"},
|
||||
{"reopen-log", "reopen log file"},
|
||||
{"register user server password", "register a user"},
|
||||
{"unregister user server", "unregister a user"},
|
||||
{"backup file", "store a database backup to file"},
|
||||
{"restore file", "restore a database backup from file"},
|
||||
{"install-fallback file", "install a database fallback from file"},
|
||||
{"dump file", "dump a database to a text file"},
|
||||
{"load file", "restore a database from a text file"},
|
||||
{"import-file file", "import user data from jabberd 1.4 spool file"},
|
||||
{"import-dir dir", "import user data from jabberd 1.4 spool directory"},
|
||||
{"delete-expired-messages", "delete expired offline messages from database"},
|
||||
{"delete-old-messages n", "delete offline messages older than n days from database"},
|
||||
{"mnesia [info]", "show information of Mnesia system"},
|
||||
{"vhost host ...", "execute host-specific commands"}] ++
|
||||
ets:tab2list(ejabberd_ctl_cmds),
|
||||
MaxCmdLen =
|
||||
lists:max(lists:map(
|
||||
fun({Cmd, _Desc}) ->
|
||||
length(Cmd)
|
||||
end, CmdDescs)),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
FmtCmdDescs =
|
||||
lists:map(
|
||||
fun({Cmd, Desc}) ->
|
||||
[" ", Cmd, string:chars($\s, MaxCmdLen - length(Cmd) + 2),
|
||||
Desc, NewLine]
|
||||
end, CmdDescs),
|
||||
?PRINT(
|
||||
"Usage: ejabberdctl [--node nodename] command [options]~n"
|
||||
"~n"
|
||||
"Available commands in this ejabberd node:~n"
|
||||
++ FmtCmdDescs ++
|
||||
"~n"
|
||||
"Examples:~n"
|
||||
" ejabberdctl restart~n"
|
||||
" ejabberdctl --node ejabberd@host restart~n"
|
||||
" ejabberdctl vhost jabber.example.org ...~n",
|
||||
[]).
|
||||
%%-----------------------------
|
||||
%% Command calling
|
||||
%%-----------------------------
|
||||
|
||||
print_vhost_usage(Host) ->
|
||||
CmdDescs =
|
||||
ets:select(ejabberd_ctl_host_cmds,
|
||||
[{{{Host, '$1'}, '$2'}, [], [{{'$1', '$2'}}]}]),
|
||||
MaxCmdLen =
|
||||
if
|
||||
CmdDescs == [] ->
|
||||
0;
|
||||
true ->
|
||||
lists:max(lists:map(
|
||||
fun({Cmd, _Desc}) ->
|
||||
length(Cmd)
|
||||
end, CmdDescs))
|
||||
%% @spec (Args::[string()]) ->
|
||||
%% String::string() | Code::integer() | {String::string(), Code::integer()}
|
||||
try_run_ctp(Args) ->
|
||||
try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
|
||||
false when Args /= [] ->
|
||||
try_call_command(Args);
|
||||
false ->
|
||||
print_usage(),
|
||||
{"", ?STATUS_USAGE};
|
||||
Status ->
|
||||
{"", Status}
|
||||
catch
|
||||
exit:Why ->
|
||||
print_usage(),
|
||||
{io_lib:format("Error in ejabberd ctl process: ~p", [Why]), ?STATUS_USAGE}
|
||||
end.
|
||||
|
||||
%% @spec (Args::[string()]) ->
|
||||
%% String::string() | Code::integer() | {String::string(), Code::integer()}
|
||||
try_call_command(Args) ->
|
||||
try call_command(Args) of
|
||||
{error, command_unknown} ->
|
||||
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
|
||||
{error, wrong_number_parameters} ->
|
||||
{"Error: wrong number of parameters", ?STATUS_ERROR};
|
||||
Res ->
|
||||
Res
|
||||
catch
|
||||
A:Why ->
|
||||
Stack = erlang:get_stacktrace(),
|
||||
{io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR}
|
||||
end.
|
||||
|
||||
%% @spec (Args::[string()]) ->
|
||||
%% String::string() | Code::integer() | {String::string(), Code::integer()} | {error, ErrorType}
|
||||
call_command([CmdString | Args]) ->
|
||||
{ok, CmdStringU, _} = regexp:gsub(CmdString, "-", "_"),
|
||||
Command = list_to_atom(CmdStringU),
|
||||
case ejabberd_commands:get_command_format(Command) of
|
||||
{error, command_unknown} ->
|
||||
{error, command_unknown};
|
||||
{ArgsFormat, ResultFormat} ->
|
||||
case (catch format_args(Args, ArgsFormat)) of
|
||||
ArgsFormatted when is_list(ArgsFormatted) ->
|
||||
Result = ejabberd_commands:execute_command(Command,
|
||||
ArgsFormatted),
|
||||
format_result(Result, ResultFormat);
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2]} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
case {length(A1), length(A2)} of
|
||||
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
|
||||
{L1, L2} when L1 > L2 -> {L1-L2, "more argument"}
|
||||
end,
|
||||
NewLine = io_lib:format("~n", []),
|
||||
FmtCmdDescs =
|
||||
lists:map(
|
||||
fun({Cmd, Desc}) ->
|
||||
[" ", Cmd, string:chars($\s, MaxCmdLen - length(Cmd) + 2),
|
||||
Desc, NewLine]
|
||||
end, CmdDescs),
|
||||
?PRINT(
|
||||
"Usage: ejabberdctl [--node nodename] vhost hostname command [options]~n"
|
||||
"~n"
|
||||
"Available commands in this ejabberd node and this vhost:~n"
|
||||
++ FmtCmdDescs ++
|
||||
"~n"
|
||||
"Examples:~n"
|
||||
" ejabberdctl vhost "++Host++" registered-users~n",
|
||||
[]).
|
||||
|
||||
register_commands(CmdDescs, Module, Function) ->
|
||||
ets:insert(ejabberd_ctl_cmds, CmdDescs),
|
||||
ejabberd_hooks:add(ejabberd_ctl_process,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
register_commands(Host, CmdDescs, Module, Function) ->
|
||||
ets:insert(ejabberd_ctl_host_cmds,
|
||||
[{{Host, Cmd}, Desc} || {Cmd, Desc} <- CmdDescs]),
|
||||
ejabberd_hooks:add(ejabberd_ctl_process, Host,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
unregister_commands(CmdDescs, Module, Function) ->
|
||||
lists:foreach(fun(CmdDesc) ->
|
||||
ets:delete_object(ejabberd_ctl_cmds, CmdDesc)
|
||||
end, CmdDescs),
|
||||
ejabberd_hooks:delete(ejabberd_ctl_process,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
unregister_commands(Host, CmdDescs, Module, Function) ->
|
||||
lists:foreach(fun({Cmd, Desc}) ->
|
||||
ets:delete_object(ejabberd_ctl_host_cmds,
|
||||
{{Host, Cmd}, Desc})
|
||||
end, CmdDescs),
|
||||
ejabberd_hooks:delete(ejabberd_ctl_process,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
dump_to_textfile(File) ->
|
||||
dump_to_textfile(mnesia:system_info(is_running), file:open(File, write)).
|
||||
dump_to_textfile(yes, {ok, F}) ->
|
||||
Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
|
||||
Tabs = lists:filter(
|
||||
fun(T) ->
|
||||
case mnesia:table_info(T, storage_type) of
|
||||
disc_copies -> true;
|
||||
disc_only_copies -> true;
|
||||
_ -> false
|
||||
{io_lib:format("Error: the command ~p requires ~p ~s.",
|
||||
[CmdString, NumCompa, TextCompa]),
|
||||
wrong_command_arguments}
|
||||
end
|
||||
end, Tabs1),
|
||||
Defs = lists:map(
|
||||
fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
|
||||
{attributes, mnesia:table_info(T, attributes)}]}
|
||||
end.
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Format arguments
|
||||
%%-----------------------------
|
||||
|
||||
format_args(Args, ArgsFormat) ->
|
||||
lists:foldl(
|
||||
fun({{_ArgName, ArgFormat}, Arg}, Res) ->
|
||||
Formatted = format_arg(Arg, ArgFormat),
|
||||
Res ++ [Formatted]
|
||||
end,
|
||||
Tabs),
|
||||
io:format(F, "~p.~n", [{tables, Defs}]),
|
||||
lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
|
||||
file:close(F);
|
||||
dump_to_textfile(_, {ok, F}) ->
|
||||
file:close(F),
|
||||
{error, mnesia_not_running};
|
||||
dump_to_textfile(_, {error, Reason}) ->
|
||||
{error, Reason}.
|
||||
[],
|
||||
lists:zip(ArgsFormat, Args)).
|
||||
|
||||
format_arg(Arg, Format) ->
|
||||
Parse = case Format of
|
||||
integer ->
|
||||
"~d";
|
||||
string ->
|
||||
NumChars = integer_to_list(string:len(Arg)),
|
||||
"~" ++ NumChars ++ "c"
|
||||
end,
|
||||
{ok, [Arg2], _RemainingArguments} = io_lib:fread(Parse, Arg),
|
||||
Arg2.
|
||||
|
||||
|
||||
dump_tab(F, T) ->
|
||||
W = mnesia:table_info(T, wild_pattern),
|
||||
{atomic,All} = mnesia:transaction(
|
||||
fun() -> mnesia:match_object(T, W, read) end),
|
||||
%%-----------------------------
|
||||
%% Format result
|
||||
%%-----------------------------
|
||||
|
||||
format_result(Atom, {_Name, atom}) ->
|
||||
io_lib:format("~p", [Atom]);
|
||||
|
||||
format_result(Int, {_Name, integer}) ->
|
||||
io_lib:format("~p", [Int]);
|
||||
|
||||
format_result(String, {_Name, string}) ->
|
||||
io_lib:format("~s", [String]);
|
||||
|
||||
format_result(Code, {_Name, rescode}) ->
|
||||
make_status(Code);
|
||||
|
||||
format_result({Code, Text}, {_Name, restuple}) ->
|
||||
{io_lib:format("~s", [Text]), make_status(Code)};
|
||||
|
||||
%% The result is a list of something: [something()]
|
||||
format_result([], {_Name, {list, _ElementsDef}}) ->
|
||||
"";
|
||||
format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}) ->
|
||||
%% Start formatting the first element
|
||||
[format_result(FirstElement, ElementsDef) |
|
||||
%% If there are more elements, put always first a newline character
|
||||
lists:map(
|
||||
fun(Element) ->
|
||||
["\n" | format_result(Element, ElementsDef)]
|
||||
end,
|
||||
Elements)];
|
||||
|
||||
%% The result is a tuple with several elements: {something1(), something2(),...}
|
||||
%% NOTE: the elements in the tuple are separated with tabular characters,
|
||||
%% if a string is empty, it will be difficult to notice in the shell,
|
||||
%% maybe a different separation character should be used, like ;;?
|
||||
format_result(ElementsTuple, {_Name, {tuple, ElementsDef}}) ->
|
||||
ElementsList = tuple_to_list(ElementsTuple),
|
||||
[{FirstE, FirstD} | ElementsAndDef] = lists:zip(ElementsList, ElementsDef),
|
||||
[format_result(FirstE, FirstD) |
|
||||
lists:map(
|
||||
fun({Element, ElementDef}) ->
|
||||
["\t" | format_result(Element, ElementDef)]
|
||||
end,
|
||||
ElementsAndDef)].
|
||||
|
||||
make_status(ok) -> ?STATUS_SUCCESS;
|
||||
make_status(true) -> ?STATUS_SUCCESS;
|
||||
make_status(_Error) -> ?STATUS_ERROR.
|
||||
|
||||
get_list_commands() ->
|
||||
try ejabberd_commands:list_commands() of
|
||||
Commands ->
|
||||
[tuple_command_help(Command)
|
||||
|| {N,_,_}=Command <- Commands,
|
||||
%% Don't show again those commands, because they are already
|
||||
%% announced by ejabberd_ctl itself
|
||||
N /= status, N /= stop, N /= restart]
|
||||
catch
|
||||
exit:_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% Return: {string(), [string()], string()}
|
||||
tuple_command_help({Name, Args, Desc}) ->
|
||||
Arguments = [atom_to_list(ArgN) || {ArgN, _ArgF} <- Args],
|
||||
Prepend = case is_supported_args(Args) of
|
||||
true -> "";
|
||||
false -> "*"
|
||||
end,
|
||||
CallString = atom_to_list(Name),
|
||||
{CallString, Arguments, Prepend ++ Desc}.
|
||||
|
||||
is_supported_args(Args) ->
|
||||
lists:all(
|
||||
fun({_Name, Format}) ->
|
||||
(Format == integer)
|
||||
or (Format == string)
|
||||
end,
|
||||
Args).
|
||||
|
||||
get_list_ctls() ->
|
||||
case catch ets:tab2list(ejabberd_ctl_cmds) of
|
||||
{'EXIT', _} -> [];
|
||||
Cs -> [{NameArgs, [], Desc} || {NameArgs, Desc} <- Cs]
|
||||
end.
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Print help
|
||||
%%-----------------------------
|
||||
|
||||
%% Bold
|
||||
-define(B1, "\e[1m").
|
||||
-define(B2, "\e[22m").
|
||||
-define(B(S), case ShCode of true -> [?B1, S, ?B2]; false -> S end).
|
||||
|
||||
%% Underline
|
||||
-define(U1, "\e[4m").
|
||||
-define(U2, "\e[24m").
|
||||
-define(U(S), case ShCode of true -> [?U1, S, ?U2]; false -> S end).
|
||||
|
||||
print_usage() ->
|
||||
{MaxC, ShCode} = get_shell_info(),
|
||||
print_usage(dual, MaxC, ShCode).
|
||||
print_usage(HelpMode, MaxC, ShCode) ->
|
||||
AllCommands =
|
||||
[
|
||||
{"status", [], "Get ejabberd status"},
|
||||
{"stop", [], "Stop ejabberd"},
|
||||
{"restart", [], "Restart ejabberd"},
|
||||
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},
|
||||
{"mnesia", ["[info]"], "show information of Mnesia system"}] ++
|
||||
get_list_commands() ++
|
||||
get_list_ctls(),
|
||||
|
||||
?PRINT(
|
||||
["Usage: ", ?B("ejabberdctl"), " [--node ", ?U("nodename"), "] ", ?U("command"), " [options]\n"
|
||||
"\n"
|
||||
"Available commands in this ejabberd node:\n"], []),
|
||||
print_usage_commands(HelpMode, MaxC, ShCode, AllCommands),
|
||||
?PRINT(
|
||||
["\n"
|
||||
"Examples:\n"
|
||||
" ejabberdctl restart\n"
|
||||
" ejabberdctl --node ejabberd@host restart\n"],
|
||||
[]).
|
||||
|
||||
print_usage_commands(HelpMode, MaxC, ShCode, Commands) ->
|
||||
CmdDescsSorted = lists:keysort(1, Commands),
|
||||
|
||||
%% What is the length of the largest command?
|
||||
{CmdArgsLenDescsSorted, Lens} =
|
||||
lists:mapfoldl(
|
||||
fun({Cmd, Args, Desc}, Lengths) ->
|
||||
Len =
|
||||
length(Cmd) +
|
||||
lists:foldl(fun(Arg, R) ->
|
||||
R + 1 + length(Arg)
|
||||
end,
|
||||
0,
|
||||
Args),
|
||||
{{Cmd, Args, Len, Desc}, [Len | Lengths]}
|
||||
end,
|
||||
[],
|
||||
CmdDescsSorted),
|
||||
MaxCmdLen = case Lens of
|
||||
[] -> 80;
|
||||
_ -> lists:max(Lens)
|
||||
end,
|
||||
|
||||
%% For each command in the list of commands
|
||||
%% Convert its definition to a line
|
||||
FmtCmdDescs = format_command_lines(CmdArgsLenDescsSorted, MaxCmdLen, MaxC, ShCode, HelpMode),
|
||||
|
||||
?PRINT([FmtCmdDescs], []).
|
||||
|
||||
|
||||
%% Get some info about the shell:
|
||||
%% how many columns of width
|
||||
%% and guess if it supports text formatting codes.
|
||||
get_shell_info() ->
|
||||
%% This function was introduced in OTP R12B-0
|
||||
try io:columns() of
|
||||
{ok, C} -> {C-2, true};
|
||||
{error, enotsup} -> {78, false}
|
||||
catch
|
||||
_:_ -> {78, false}
|
||||
end.
|
||||
|
||||
%% Split this command description in several lines of proper length
|
||||
prepare_description(DescInit, MaxC, Desc) ->
|
||||
Words = string:tokens(Desc, " "),
|
||||
prepare_long_line(DescInit, MaxC, Words).
|
||||
|
||||
prepare_long_line(DescInit, MaxC, Words) ->
|
||||
MaxSegmentLen = MaxC - DescInit,
|
||||
MarginString = lists:duplicate(DescInit, $\s), % Put spaces
|
||||
[FirstSegment | MoreSegments] = split_desc_segments(MaxSegmentLen, Words),
|
||||
MoreSegmentsMixed = mix_desc_segments(MarginString, MoreSegments),
|
||||
[FirstSegment | MoreSegmentsMixed].
|
||||
|
||||
mix_desc_segments(MarginString, Segments) ->
|
||||
[["\n", MarginString, Segment] || Segment <- Segments].
|
||||
|
||||
split_desc_segments(MaxL, Words) ->
|
||||
join(MaxL, Words).
|
||||
|
||||
%% Join words in a segment,
|
||||
%% but stop adding to a segment if adding this word would pass L
|
||||
join(L, Words) ->
|
||||
join(L, Words, 0, [], []).
|
||||
|
||||
join(_L, [], _LenLastSeg, LastSeg, ResSeg) ->
|
||||
ResSeg2 = [lists:reverse(LastSeg) | ResSeg],
|
||||
lists:reverse(ResSeg2);
|
||||
join(L, [Word | Words], LenLastSeg, LastSeg, ResSeg) ->
|
||||
LWord = length(Word),
|
||||
case LWord + LenLastSeg < L of
|
||||
true ->
|
||||
%% This word fits in the last segment
|
||||
%% If this word ends with "\n", reset column counter
|
||||
case string:str(Word, "\n") of
|
||||
0 ->
|
||||
join(L, Words, LenLastSeg+LWord+1, [" ", Word | LastSeg], ResSeg);
|
||||
_ ->
|
||||
join(L, Words, LWord+1, [" ", Word | LastSeg], ResSeg)
|
||||
end;
|
||||
false ->
|
||||
join(L, Words, LWord, [" ", Word], [lists:reverse(LastSeg) | ResSeg])
|
||||
end.
|
||||
|
||||
format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual)
|
||||
when MaxC - MaxCmdLen < 40 ->
|
||||
%% If the space available for descriptions is too narrow, enforce long help mode
|
||||
format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, long);
|
||||
|
||||
format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual) ->
|
||||
lists:map(
|
||||
fun({Cmd, Args, CmdArgsL, Desc}) ->
|
||||
DescFmt = prepare_description(MaxCmdLen+4, MaxC, Desc),
|
||||
[" ", ?B(Cmd), " ", [[?U(Arg), " "] || Arg <- Args], string:chars($\s, MaxCmdLen - CmdArgsL + 1),
|
||||
DescFmt, "\n"]
|
||||
end, CALD);
|
||||
|
||||
format_command_lines(CALD, _MaxCmdLen, MaxC, ShCode, long) ->
|
||||
lists:map(
|
||||
fun({Cmd, Args, _CmdArgsL, Desc}) ->
|
||||
DescFmt = prepare_description(8, MaxC, Desc),
|
||||
["\n ", ?B(Cmd), " ", [[?U(Arg), " "] || Arg <- Args], "\n", " ",
|
||||
DescFmt, "\n"]
|
||||
end, CALD).
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Print Tags
|
||||
%%-----------------------------
|
||||
|
||||
print_usage_tags(MaxC, ShCode) ->
|
||||
?PRINT("Available tags and commands:", []),
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(),
|
||||
lists:foreach(
|
||||
fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
|
||||
fun({Tag, Commands} = _TagCommands) ->
|
||||
?PRINT(["\n\n ", ?B(Tag), "\n "], []),
|
||||
Words = lists:sort(Commands),
|
||||
Desc = prepare_long_line(5, MaxC, Words),
|
||||
?PRINT(Desc, [])
|
||||
end,
|
||||
TagsCommands),
|
||||
?PRINT("\n\n", []).
|
||||
|
||||
print_usage_tags(Tag, MaxC, ShCode) ->
|
||||
?PRINT(["Available commands with tag ", ?B(Tag), ":", "\n"], []),
|
||||
HelpMode = long,
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(),
|
||||
CommandsNames = case lists:keysearch(Tag, 1, TagsCommands) of
|
||||
{value, {Tag, CNs}} -> CNs;
|
||||
false -> []
|
||||
end,
|
||||
CommandsList = lists:map(
|
||||
fun(NameString) ->
|
||||
C = ejabberd_commands:get_command_definition(list_to_atom(NameString)),
|
||||
#ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc} = C,
|
||||
tuple_command_help({Name, Args, Desc})
|
||||
end,
|
||||
CommandsNames),
|
||||
print_usage_commands(HelpMode, MaxC, ShCode, CommandsList),
|
||||
?PRINT("\n", []).
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Print usage of 'help' command
|
||||
%%-----------------------------
|
||||
|
||||
print_usage_help(MaxC, ShCode) ->
|
||||
LongDesc =
|
||||
["The special 'help' ejabberdctl command provides help of ejabberd commands.\n\n"
|
||||
"The format is:\n ", ?B("ejabberdctl"), " ", ?B("help"), " [", ?B("--tags"), " ", ?U("[tag]"), " | ", ?U("com?*"), "]\n\n"
|
||||
"The optional arguments:\n"
|
||||
" ",?B("--tags")," Show all tags and the names of commands in each tag\n"
|
||||
" ",?B("--tags"), " ", ?U("tag")," Show description of commands in this tag\n"
|
||||
" ",?U("command")," Show detailed description of the command\n"
|
||||
" ",?U("com?*")," Show detailed description of commands that match this glob.\n"
|
||||
" You can use ? to match a simple character,\n"
|
||||
" and * to match several characters.\n"
|
||||
"\n",
|
||||
"Some example usages:\n",
|
||||
" ejabberdctl help\n",
|
||||
" ejabberdctl help --tags\n",
|
||||
" ejabberdctl help --tags accounts\n",
|
||||
" ejabberdctl help register\n",
|
||||
" ejabberdctl help regist*\n",
|
||||
"\n",
|
||||
"Please note that 'ejabberdctl help' shows all ejabberd commands,\n",
|
||||
"even those that cannot be used in the shell with ejabberdctl.\n",
|
||||
"Those commands can be identified because the description starts with: *"],
|
||||
ArgsDef = [],
|
||||
C = #ejabberd_commands{
|
||||
desc = "Show help of ejabberd commands",
|
||||
longdesc = LongDesc,
|
||||
args = ArgsDef,
|
||||
result = {help, string}},
|
||||
print_usage_command("help", C, MaxC, ShCode).
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Print usage command
|
||||
%%-----------------------------
|
||||
|
||||
%% @spec (CmdSubString::string(), MaxC::integer(), ShCode::boolean()) -> ok
|
||||
print_usage_commands(CmdSubString, MaxC, ShCode) ->
|
||||
%% Get which command names match this substring
|
||||
AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands()],
|
||||
Cmds = filter_commands(AllCommandsNames, CmdSubString),
|
||||
case Cmds of
|
||||
[] -> io:format("Error: not command found that match: ~p~n", [CmdSubString]);
|
||||
_ -> print_usage_commands2(lists:sort(Cmds), MaxC, ShCode)
|
||||
end.
|
||||
|
||||
print_usage_commands2(Cmds, MaxC, ShCode) ->
|
||||
%% Then for each one print it
|
||||
lists:mapfoldl(
|
||||
fun(Cmd, Remaining) ->
|
||||
print_usage_command(Cmd, MaxC, ShCode),
|
||||
case Remaining > 1 of
|
||||
true -> ?PRINT([" ", lists:duplicate(MaxC, 126), " \n"], []);
|
||||
false -> ok
|
||||
end,
|
||||
{ok, Remaining-1}
|
||||
end,
|
||||
length(Cmds),
|
||||
Cmds).
|
||||
|
||||
filter_commands(All, SubString) ->
|
||||
case lists:member(SubString, All) of
|
||||
true -> [SubString];
|
||||
false -> filter_commands_regexp(All, SubString)
|
||||
end.
|
||||
|
||||
filter_commands_regexp(All, Glob) ->
|
||||
RegExp = regexp:sh_to_awk(Glob),
|
||||
lists:filter(
|
||||
fun(Command) ->
|
||||
case regexp:first_match(Command, RegExp) of
|
||||
{match, _, _} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
All).
|
||||
|
||||
%% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean()) -> ok
|
||||
print_usage_command(Cmd, MaxC, ShCode) ->
|
||||
Name = list_to_atom(Cmd),
|
||||
case ejabberd_commands:get_command_definition(Name) of
|
||||
command_not_found ->
|
||||
io:format("Error: command ~p not known.~n", [Cmd]);
|
||||
C ->
|
||||
print_usage_command(Cmd, C, MaxC, ShCode)
|
||||
end.
|
||||
|
||||
print_usage_command(Cmd, C, MaxC, ShCode) ->
|
||||
#ejabberd_commands{
|
||||
tags = TagsAtoms,
|
||||
desc = Desc,
|
||||
longdesc = LongDesc,
|
||||
args = ArgsDef,
|
||||
result = ResultDef} = C,
|
||||
|
||||
NameFmt = [" ", ?B("Command Name"), ": ", Cmd, "\n"],
|
||||
|
||||
%% Initial indentation of result is 13 = length(" Arguments: ")
|
||||
Args = [format_usage_ctype(ArgDef, 13) || ArgDef <- ArgsDef],
|
||||
ArgsMargin = lists:duplicate(13, $\s),
|
||||
ArgsListFmt = case Args of
|
||||
[] -> "\n";
|
||||
_ -> [ [Arg, "\n", ArgsMargin] || Arg <- Args]
|
||||
end,
|
||||
ArgsFmt = [" ", ?B("Arguments"), ": ", ArgsListFmt],
|
||||
|
||||
%% Initial indentation of result is 11 = length(" Returns: ")
|
||||
ResultFmt = format_usage_ctype(ResultDef, 11),
|
||||
ReturnsFmt = [" ",?B("Returns"),": ", ResultFmt],
|
||||
|
||||
XmlrpcFmt = "", %%+++ [" ",?B("XML-RPC"),": ", format_usage_xmlrpc(ArgsDef, ResultDef), "\n\n"],
|
||||
|
||||
TagsFmt = [" ",?B("Tags"),": ", prepare_long_line(8, MaxC, [atom_to_list(TagA) || TagA <- TagsAtoms])],
|
||||
|
||||
DescFmt = [" ",?B("Description"),": ", prepare_description(15, MaxC, Desc)],
|
||||
|
||||
LongDescFmt = case LongDesc of
|
||||
"" -> "";
|
||||
_ -> ["", prepare_description(0, MaxC, LongDesc), "\n\n"]
|
||||
end,
|
||||
|
||||
NoteEjabberdctl = case is_supported_args(ArgsDef) of
|
||||
true -> "";
|
||||
false -> [" ", ?B("Note:"), " This command cannot be executed using ejabberdctl. Try ejabberd_xmlrpc.\n\n"]
|
||||
end,
|
||||
|
||||
?PRINT(["\n", NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt, "\n\n", XmlrpcFmt, TagsFmt, "\n\n", DescFmt, "\n\n", LongDescFmt, NoteEjabberdctl], []).
|
||||
|
||||
format_usage_ctype({Name, Type}, _Indentation)
|
||||
when (Type==atom) or (Type==integer) or (Type==string) or (Type==rescode) or (Type==restuple)->
|
||||
io_lib:format("~p::~p", [Name, Type]);
|
||||
|
||||
format_usage_ctype({Name, {list, ElementDef}}, Indentation) ->
|
||||
NameFmt = atom_to_list(Name),
|
||||
Indentation2 = Indentation + length(NameFmt) + 4,
|
||||
ElementFmt = format_usage_ctype(ElementDef, Indentation2),
|
||||
[NameFmt, "::[ ", ElementFmt, " ]"];
|
||||
|
||||
format_usage_ctype({Name, {tuple, ElementsDef}}, Indentation) ->
|
||||
NameFmt = atom_to_list(Name),
|
||||
Indentation2 = Indentation + length(NameFmt) + 4,
|
||||
ElementsFmt = format_usage_tuple(ElementsDef, Indentation2),
|
||||
[NameFmt, "::{ " | ElementsFmt].
|
||||
|
||||
format_usage_tuple([], _Indentation) ->
|
||||
[];
|
||||
format_usage_tuple([ElementDef], Indentation) ->
|
||||
[format_usage_ctype(ElementDef, Indentation) , " }"];
|
||||
format_usage_tuple([ElementDef | ElementsDef], Indentation) ->
|
||||
ElementFmt = format_usage_ctype(ElementDef, Indentation),
|
||||
MarginString = lists:duplicate(Indentation, $\s), % Put spaces
|
||||
[ElementFmt, ",\n", MarginString, format_usage_tuple(ElementsDef, Indentation)].
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Command managment
|
||||
%%-----------------------------
|
||||
|
||||
%%+++
|
||||
%% Struct(Integer res) create_account(Struct(String user, String server, String password))
|
||||
%%format_usage_xmlrpc(ArgsDef, ResultDef) ->
|
||||
%% ["aaaa bbb ccc"].
|
||||
|
||||
|
@ -29,10 +29,11 @@
|
||||
|
||||
-export([start_link/0, init/1, start/3,
|
||||
init/3,
|
||||
start_listeners/0,
|
||||
start_listener/3,
|
||||
stop_listener/1,
|
||||
stop_listener/2,
|
||||
add_listener/3,
|
||||
delete_listener/1
|
||||
delete_listener/2
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@ -42,24 +43,27 @@ start_link() ->
|
||||
|
||||
|
||||
init(_) ->
|
||||
{ok, {{one_for_one, 10, 1}, []}}.
|
||||
|
||||
start_listeners() ->
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
ignore;
|
||||
Ls ->
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
lists:map(
|
||||
fun({Port, Module, Opts}) ->
|
||||
{Port,
|
||||
{?MODULE, start, [Port, Module, Opts]},
|
||||
transient,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[?MODULE]}
|
||||
end, Ls)}}
|
||||
start_listener(Port, Module, Opts)
|
||||
end, Ls)
|
||||
end.
|
||||
|
||||
|
||||
start(Port, Module, Opts) ->
|
||||
%% Check if the module is an ejabberd listener or an independent listener
|
||||
case Module:socket_type() of
|
||||
independent -> Module:start_listener(Port, Opts);
|
||||
_ -> start_dependent(Port, Module, Opts)
|
||||
end.
|
||||
|
||||
start_dependent(Port, Module, Opts) ->
|
||||
case includes_deprecated_ssl_option(Opts) of
|
||||
false ->
|
||||
{ok, proc_lib:spawn_link(?MODULE, init,
|
||||
@ -130,6 +134,21 @@ accept(ListenSocket, Module, Opts) ->
|
||||
end.
|
||||
|
||||
start_listener(Port, Module, Opts) ->
|
||||
start_module_sup(Port, Module),
|
||||
start_listener_sup(Port, Module, Opts).
|
||||
|
||||
start_module_sup(_Port, Module) ->
|
||||
Proc1 = gen_mod:get_module_proc("sup", Module),
|
||||
ChildSpec1 =
|
||||
{Proc1,
|
||||
{ejabberd_tmp_sup, start_link, [Proc1, Module]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
catch supervisor:start_child(ejabberd_sup, ChildSpec1).
|
||||
|
||||
start_listener_sup(Port, Module, Opts) ->
|
||||
ChildSpec = {Port,
|
||||
{?MODULE, start, [Port, Module, Opts]},
|
||||
transient,
|
||||
@ -138,9 +157,13 @@ start_listener(Port, Module, Opts) ->
|
||||
[?MODULE]},
|
||||
supervisor:start_child(ejabberd_listeners, ChildSpec).
|
||||
|
||||
stop_listener(Port) ->
|
||||
stop_listener(Port, Module) ->
|
||||
supervisor:terminate_child(ejabberd_listeners, Port),
|
||||
supervisor:delete_child(ejabberd_listeners, Port).
|
||||
supervisor:delete_child(ejabberd_listeners, Port),
|
||||
|
||||
Proc1 = gen_mod:get_module_proc("sup", Module),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc1),
|
||||
supervisor:delete_child(ejabberd_sup, Proc1).
|
||||
|
||||
add_listener(Port, Module, Opts) ->
|
||||
Ports = case ejabberd_config:get_local_option(listen) of
|
||||
@ -154,7 +177,7 @@ add_listener(Port, Module, Opts) ->
|
||||
ejabberd_config:add_local_option(listen, Ports2),
|
||||
start_listener(Port, Module, Opts).
|
||||
|
||||
delete_listener(Port) ->
|
||||
delete_listener(Port, Module) ->
|
||||
Ports = case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
[];
|
||||
@ -163,5 +186,5 @@ delete_listener(Port) ->
|
||||
end,
|
||||
Ports1 = lists:keydelete(Port, 1, Ports),
|
||||
ejabberd_config:add_local_option(listen, Ports1),
|
||||
stop_listener(Port).
|
||||
stop_listener(Port, Module).
|
||||
|
||||
|
@ -39,7 +39,8 @@
|
||||
remove_connection/3,
|
||||
dirty_get_connections/0,
|
||||
allow_host/2,
|
||||
ctl_process/2
|
||||
incoming_s2s_number/0,
|
||||
outgoing_s2s_number/0
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@ -49,7 +50,7 @@
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
|
||||
@ -182,10 +183,7 @@ init([]) ->
|
||||
{attributes, record_info(fields, s2s)}]),
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ejabberd_ctl:register_commands(
|
||||
[{"incoming-s2s-number", "print number of incoming s2s connections on the node"},
|
||||
{"outgoing-s2s-number", "print number of outgoing s2s connections on the node"}],
|
||||
?MODULE, ctl_process),
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -249,6 +247,7 @@ handle_info(_Info, State) ->
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -453,16 +452,35 @@ is_subdomain(Domain1, Domain2) ->
|
||||
send_element(Pid, El) ->
|
||||
Pid ! {send_element, El}.
|
||||
|
||||
ctl_process(_Val, ["incoming-s2s-number"]) ->
|
||||
N = length(supervisor:which_children(ejabberd_s2s_in_sup)),
|
||||
?PRINT("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["outgoing-s2s-number"]) ->
|
||||
N = length(supervisor:which_children(ejabberd_s2s_out_sup)),
|
||||
?PRINT("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(Val, _Args) ->
|
||||
Val.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% ejabberd commands
|
||||
|
||||
commands() ->
|
||||
[
|
||||
#ejabberd_commands{name = incoming_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of incoming s2s connections on the node",
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [],
|
||||
result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{name = outgoing_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of outgoing s2s connections on the node",
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [],
|
||||
result = {s2s_outgoing, integer}}
|
||||
].
|
||||
|
||||
incoming_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_in_sup)).
|
||||
|
||||
outgoing_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_out_sup)).
|
||||
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Update Mnesia tables
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(s2s, type) of
|
||||
|
@ -46,7 +46,9 @@
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
ctl_process/2,
|
||||
connected_users/0,
|
||||
connected_users_number/0,
|
||||
user_resources/2,
|
||||
get_session_pid/3,
|
||||
get_user_info/3,
|
||||
get_user_ip/3
|
||||
@ -59,7 +61,7 @@
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
-record(session, {sid, usr, us, priority, info}).
|
||||
-record(state, {}).
|
||||
@ -269,11 +271,7 @@ init([]) ->
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
ejabberd_sm, disconnect_removed_user, 100)
|
||||
end, ?MYHOSTS),
|
||||
ejabberd_ctl:register_commands(
|
||||
[{"connected-users", "list all established sessions"},
|
||||
{"connected-users-number", "print a number of established sessions"},
|
||||
{"user-resources user server", "print user's connected resources"}],
|
||||
?MODULE, ctl_process),
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
|
||||
{ok, #state{}}.
|
||||
|
||||
@ -353,6 +351,7 @@ handle_info(_Info, State) ->
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -670,27 +669,46 @@ process_iq(From, To, Packet) ->
|
||||
end.
|
||||
|
||||
|
||||
ctl_process(_Val, ["connected-users"]) ->
|
||||
USRs = dirty_get_sessions_list(),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUSRs = lists:sort(USRs),
|
||||
FUSRs = lists:map(fun({U, S, R}) -> [U, $@, S, $/, R, NewLine] end, SUSRs),
|
||||
?PRINT("~s", [FUSRs]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["connected-users-number"]) ->
|
||||
N = length(dirty_get_sessions_list()),
|
||||
?PRINT("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["user-resources", User, Server]) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SResources = lists:sort(Resources),
|
||||
FResources = lists:map(fun(R) -> [R, NewLine] end, SResources),
|
||||
?PRINT("~s", [FResources]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(Val, _Args) ->
|
||||
Val.
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% ejabberd commands
|
||||
|
||||
commands() ->
|
||||
[
|
||||
#ejabberd_commands{name = connected_users,
|
||||
tags = [session],
|
||||
desc = "List all established sessions",
|
||||
module = ?MODULE, function = connected_users,
|
||||
args = [],
|
||||
result = {connected_users, {list, {sessions, string}}}},
|
||||
#ejabberd_commands{name = connected_users_number,
|
||||
tags = [session, stats],
|
||||
desc = "Get the number of established sessions",
|
||||
module = ?MODULE, function = connected_users_number,
|
||||
args = [],
|
||||
result = {num_sessions, integer}},
|
||||
#ejabberd_commands{name = user_resources,
|
||||
tags = [session],
|
||||
desc = "List user's connected resources",
|
||||
module = ?MODULE, function = user_resources,
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {resources, {list, {resource, string}}}}
|
||||
].
|
||||
|
||||
connected_users() ->
|
||||
USRs = dirty_get_sessions_list(),
|
||||
SUSRs = lists:sort(USRs),
|
||||
lists:map(fun({U, S, R}) -> [U, $@, S, $/, R] end, SUSRs).
|
||||
|
||||
connected_users_number() ->
|
||||
length(dirty_get_sessions_list()).
|
||||
|
||||
user_resources(User, Server) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
lists:sort(Resources).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Update Mnesia tables
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(session, attributes) of
|
||||
|
@ -77,6 +77,8 @@ start(Module, SockMod, Socket, Opts) ->
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
end;
|
||||
independent ->
|
||||
ok;
|
||||
raw ->
|
||||
case Module:start({SockMod, Socket}, Opts) of
|
||||
{ok, Pid} ->
|
||||
|
@ -99,21 +99,6 @@ init([]) ->
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
C2SSupervisor =
|
||||
{ejabberd_c2s_sup,
|
||||
{ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
S2SInSupervisor =
|
||||
{ejabberd_s2s_in_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
[ejabberd_s2s_in_sup, ejabberd_s2s_in]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
S2SOutSupervisor =
|
||||
{ejabberd_s2s_out_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
@ -130,14 +115,6 @@ init([]) ->
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
HTTPSupervisor =
|
||||
{ejabberd_http_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
[ejabberd_http_sup, ejabberd_http]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
HTTPPollSupervisor =
|
||||
{ejabberd_http_poll_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
@ -171,11 +148,8 @@ init([]) ->
|
||||
S2S,
|
||||
Local,
|
||||
ReceiverSupervisor,
|
||||
C2SSupervisor,
|
||||
S2SInSupervisor,
|
||||
S2SOutSupervisor,
|
||||
ServiceSupervisor,
|
||||
HTTPSupervisor,
|
||||
HTTPPollSupervisor,
|
||||
IQSupervisor,
|
||||
FrontendSocketSupervisor,
|
||||
|
@ -163,6 +163,23 @@ live ()
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
}
|
||||
|
||||
help ()
|
||||
{
|
||||
echo ""
|
||||
echo "Commands to start an ejabberd node:"
|
||||
echo " start Start an ejabberd node in server mode"
|
||||
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
|
||||
echo " live Start an ejabberd node in live (interactive) mode"
|
||||
echo ""
|
||||
echo "Optional parameters when starting an ejabberd node:"
|
||||
echo " --config file Config ejabberd: $EJABBERD_CONFIG_PATH"
|
||||
echo " --ctl-config file Config ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
|
||||
echo " --logs dir Directory for logs: $LOGS_DIR"
|
||||
echo " --spool dir Database spool dir: $SPOOLDIR"
|
||||
echo " --node nodename ejabberd node name: $ERLANG_NODE"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# common control function
|
||||
ctl ()
|
||||
{
|
||||
@ -175,20 +192,9 @@ ctl ()
|
||||
result=$?
|
||||
case $result in
|
||||
0) :;;
|
||||
*)
|
||||
echo ""
|
||||
echo "Commands to start an ejabberd node:"
|
||||
echo " start Start an ejabberd node in server mode"
|
||||
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
|
||||
echo " live Start an ejabberd node in live (interactive) mode"
|
||||
echo ""
|
||||
echo "Optional parameters when starting an ejabberd node:"
|
||||
echo " --config file Config file of ejabberd: $EJABBERD_CONFIG_PATH"
|
||||
echo " --ctl-config file Config file of ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
|
||||
echo " --logs dir Directory for logs: $LOGS_DIR"
|
||||
echo " --spool dir Database spool dir: $SPOOLDIR"
|
||||
echo " --node nodename ejabberd node name: $ERLANG_NODE"
|
||||
echo "";;
|
||||
1) :;;
|
||||
2) help;;
|
||||
3) help;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
474
src/jlib.erl
474
src/jlib.erl
@ -27,32 +27,7 @@
|
||||
-module(jlib).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([make_result_iq_reply/1,
|
||||
make_error_reply/3,
|
||||
make_error_reply/2,
|
||||
make_error_element/2,
|
||||
make_correct_from_to_attrs/3,
|
||||
replace_from_to_attrs/3,
|
||||
replace_from_to/3,
|
||||
remove_attr/2,
|
||||
make_jid/3,
|
||||
make_jid/1,
|
||||
string_to_jid/1,
|
||||
jid_to_string/1,
|
||||
is_nodename/1,
|
||||
tolower/1,
|
||||
nodeprep/1,
|
||||
nameprep/1,
|
||||
resourceprep/1,
|
||||
jid_tolower/1,
|
||||
jid_remove_resource/1,
|
||||
jid_replace_resource/2,
|
||||
get_iq_namespace/1,
|
||||
iq_query_info/1,
|
||||
iq_query_or_response_info/1,
|
||||
is_iq_request_type/1,
|
||||
iq_to_xml/1,
|
||||
parse_xdata_submit/1,
|
||||
-export([parse_xdata_submit/1,
|
||||
timestamp_to_iso/1,
|
||||
timestamp_to_xml/1,
|
||||
now_to_utc_string/1,
|
||||
@ -68,410 +43,21 @@
|
||||
short_prepd_jid/1,
|
||||
short_prepd_bare_jid/1]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp_xml.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
%send_iq(From, To, ID, SubTags) ->
|
||||
% ok.
|
||||
|
||||
make_result_iq_reply({xmlelement, Name, Attrs, SubTags}) ->
|
||||
NewAttrs = make_result_iq_reply_attrs(Attrs),
|
||||
{xmlelement, Name, NewAttrs, SubTags}.
|
||||
|
||||
make_result_iq_reply_attrs(Attrs) ->
|
||||
To = xml:get_attr("to", Attrs),
|
||||
From = xml:get_attr("from", Attrs),
|
||||
Attrs1 = lists:keydelete("to", 1, Attrs),
|
||||
Attrs2 = lists:keydelete("from", 1, Attrs1),
|
||||
Attrs3 = case To of
|
||||
{value, ToVal} ->
|
||||
[{"from", ToVal} | Attrs2];
|
||||
_ ->
|
||||
Attrs2
|
||||
end,
|
||||
Attrs4 = case From of
|
||||
{value, FromVal} ->
|
||||
[{"to", FromVal} | Attrs3];
|
||||
_ ->
|
||||
Attrs3
|
||||
end,
|
||||
Attrs5 = lists:keydelete("type", 1, Attrs4),
|
||||
Attrs6 = [{"type", "result"} | Attrs5],
|
||||
Attrs6.
|
||||
|
||||
make_error_reply({xmlelement, Name, Attrs, SubTags}, Code, Desc) ->
|
||||
NewAttrs = make_error_reply_attrs(Attrs),
|
||||
{xmlelement, Name, NewAttrs, SubTags ++ [{xmlelement, "error",
|
||||
[{"code", Code}],
|
||||
[{xmlcdata, Desc}]}]}.
|
||||
|
||||
make_error_reply({xmlelement, Name, Attrs, SubTags}, Error) ->
|
||||
NewAttrs = make_error_reply_attrs(Attrs),
|
||||
{xmlelement, Name, NewAttrs, SubTags ++ [Error]}.
|
||||
|
||||
make_error_reply_attrs(Attrs) ->
|
||||
To = xml:get_attr("to", Attrs),
|
||||
From = xml:get_attr("from", Attrs),
|
||||
Attrs1 = lists:keydelete("to", 1, Attrs),
|
||||
Attrs2 = lists:keydelete("from", 1, Attrs1),
|
||||
Attrs3 = case To of
|
||||
{value, ToVal} ->
|
||||
[{"from", ToVal} | Attrs2];
|
||||
_ ->
|
||||
Attrs2
|
||||
end,
|
||||
Attrs4 = case From of
|
||||
{value, FromVal} ->
|
||||
[{"to", FromVal} | Attrs3];
|
||||
_ ->
|
||||
Attrs3
|
||||
end,
|
||||
Attrs5 = lists:keydelete("type", 1, Attrs4),
|
||||
Attrs6 = [{"type", "error"} | Attrs5],
|
||||
Attrs6.
|
||||
|
||||
make_error_element(Code, Desc) ->
|
||||
{xmlelement, "error",
|
||||
[{"code", Code}],
|
||||
[{xmlcdata, Desc}]}.
|
||||
|
||||
make_correct_from_to_attrs(From, To, Attrs) ->
|
||||
Attrs1 = lists:keydelete("from", 1, Attrs),
|
||||
Attrs2 = case xml:get_attr("to", Attrs) of
|
||||
{value, _} ->
|
||||
Attrs1;
|
||||
_ ->
|
||||
[{"to", To} | Attrs1]
|
||||
end,
|
||||
Attrs3 = [{"from", From} | Attrs2],
|
||||
Attrs3.
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
|
||||
replace_from_to_attrs(From, To, Attrs) ->
|
||||
Attrs1 = lists:keydelete("to", 1, Attrs),
|
||||
Attrs2 = lists:keydelete("from", 1, Attrs1),
|
||||
Attrs3 = [{"to", To} | Attrs2],
|
||||
Attrs4 = [{"from", From} | Attrs3],
|
||||
Attrs4.
|
||||
|
||||
replace_from_to(From, To, {xmlelement, Name, Attrs, Els}) ->
|
||||
NewAttrs = replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
{xmlelement, Name, NewAttrs, Els}.
|
||||
|
||||
|
||||
remove_attr(Attr, {xmlelement, Name, Attrs, Els}) ->
|
||||
NewAttrs = lists:keydelete(Attr, 1, Attrs),
|
||||
{xmlelement, Name, NewAttrs, Els}.
|
||||
|
||||
|
||||
make_jid(User, Server, Resource) ->
|
||||
case nodeprep(User) of
|
||||
error -> error;
|
||||
LUser ->
|
||||
case nameprep(Server) of
|
||||
error -> error;
|
||||
LServer ->
|
||||
case resourceprep(Resource) of
|
||||
error -> error;
|
||||
LResource ->
|
||||
#jid{user = User,
|
||||
server = Server,
|
||||
resource = Resource,
|
||||
luser = LUser,
|
||||
lserver = LServer,
|
||||
lresource = LResource}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
make_jid({User, Server, Resource}) ->
|
||||
make_jid(User, Server, Resource).
|
||||
|
||||
string_to_jid(J) ->
|
||||
string_to_jid1(J, "").
|
||||
|
||||
string_to_jid1([$@ | _J], "") ->
|
||||
error;
|
||||
string_to_jid1([$@ | J], N) ->
|
||||
string_to_jid2(J, lists:reverse(N), "");
|
||||
string_to_jid1([$/ | _J], "") ->
|
||||
error;
|
||||
string_to_jid1([$/ | J], N) ->
|
||||
string_to_jid3(J, "", lists:reverse(N), "");
|
||||
string_to_jid1([C | J], N) ->
|
||||
string_to_jid1(J, [C | N]);
|
||||
string_to_jid1([], "") ->
|
||||
error;
|
||||
string_to_jid1([], N) ->
|
||||
make_jid("", lists:reverse(N), "").
|
||||
|
||||
%% Only one "@" is admitted per JID
|
||||
string_to_jid2([$@ | _J], _N, _S) ->
|
||||
error;
|
||||
string_to_jid2([$/ | _J], _N, "") ->
|
||||
error;
|
||||
string_to_jid2([$/ | J], N, S) ->
|
||||
string_to_jid3(J, N, lists:reverse(S), "");
|
||||
string_to_jid2([C | J], N, S) ->
|
||||
string_to_jid2(J, N, [C | S]);
|
||||
string_to_jid2([], _N, "") ->
|
||||
error;
|
||||
string_to_jid2([], N, S) ->
|
||||
make_jid(N, lists:reverse(S), "").
|
||||
|
||||
string_to_jid3([C | J], N, S, R) ->
|
||||
string_to_jid3(J, N, S, [C | R]);
|
||||
string_to_jid3([], N, S, R) ->
|
||||
make_jid(N, S, lists:reverse(R)).
|
||||
|
||||
jid_to_string(#jid{user = User, server = Server, resource = Resource}) ->
|
||||
jid_to_string({User, Server, Resource});
|
||||
jid_to_string({Node, Server, Resource}) ->
|
||||
S1 = case Node of
|
||||
"" ->
|
||||
"";
|
||||
_ ->
|
||||
Node ++ "@"
|
||||
end,
|
||||
S2 = S1 ++ Server,
|
||||
S3 = case Resource of
|
||||
"" ->
|
||||
S2;
|
||||
_ ->
|
||||
S2 ++ "/" ++ Resource
|
||||
end,
|
||||
S3.
|
||||
|
||||
|
||||
is_nodename([]) ->
|
||||
false;
|
||||
is_nodename(J) ->
|
||||
nodeprep(J) /= error.
|
||||
|
||||
|
||||
%tolower_c(C) when C >= $A, C =< $Z ->
|
||||
% C + 32;
|
||||
%tolower_c(C) ->
|
||||
% C.
|
||||
|
||||
-define(LOWER(Char),
|
||||
if
|
||||
Char >= $A, Char =< $Z ->
|
||||
Char + 32;
|
||||
true ->
|
||||
Char
|
||||
end).
|
||||
|
||||
%tolower(S) ->
|
||||
% lists:map(fun tolower_c/1, S).
|
||||
|
||||
%tolower(S) ->
|
||||
% [?LOWER(Char) || Char <- S].
|
||||
|
||||
% Not tail-recursive but it seems works faster than variants above
|
||||
tolower([C | Cs]) ->
|
||||
if
|
||||
C >= $A, C =< $Z ->
|
||||
[C + 32 | tolower(Cs)];
|
||||
true ->
|
||||
[C | tolower(Cs)]
|
||||
end;
|
||||
tolower([]) ->
|
||||
[].
|
||||
|
||||
%tolower([C | Cs]) when C >= $A, C =< $Z ->
|
||||
% [C + 32 | tolower(Cs)];
|
||||
%tolower([C | Cs]) ->
|
||||
% [C | tolower(Cs)];
|
||||
%tolower([]) ->
|
||||
% [].
|
||||
|
||||
|
||||
nodeprep(S) when length(S) < 1024 ->
|
||||
R = stringprep:nodeprep(S),
|
||||
if
|
||||
length(R) < 1024 -> R;
|
||||
true -> error
|
||||
end;
|
||||
nodeprep(_) ->
|
||||
error.
|
||||
|
||||
nameprep(S) when length(S) < 1024 ->
|
||||
R = stringprep:nameprep(S),
|
||||
if
|
||||
length(R) < 1024 -> R;
|
||||
true -> error
|
||||
end;
|
||||
nameprep(_) ->
|
||||
error.
|
||||
|
||||
resourceprep(S) when length(S) < 1024 ->
|
||||
R = stringprep:resourceprep(S),
|
||||
if
|
||||
length(R) < 1024 -> R;
|
||||
true -> error
|
||||
end;
|
||||
resourceprep(_) ->
|
||||
error.
|
||||
|
||||
|
||||
jid_tolower(#jid{luser = U, lserver = S, lresource = R}) ->
|
||||
{U, S, R};
|
||||
jid_tolower({U, S, R}) ->
|
||||
case nodeprep(U) of
|
||||
error -> error;
|
||||
LUser ->
|
||||
case nameprep(S) of
|
||||
error -> error;
|
||||
LServer ->
|
||||
case resourceprep(R) of
|
||||
error -> error;
|
||||
LResource ->
|
||||
{LUser, LServer, LResource}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
jid_remove_resource(#jid{} = JID) ->
|
||||
JID#jid{resource = "", lresource = ""};
|
||||
jid_remove_resource({U, S, _R}) ->
|
||||
{U, S, ""}.
|
||||
|
||||
jid_replace_resource(JID, Resource) ->
|
||||
case resourceprep(Resource) of
|
||||
error -> error;
|
||||
LResource ->
|
||||
JID#jid{resource = Resource, lresource = LResource}
|
||||
end.
|
||||
|
||||
|
||||
get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
|
||||
case xml:remove_cdata(Els) of
|
||||
[{xmlelement, _Name2, Attrs2, _Els2}] ->
|
||||
xml:get_attr_s("xmlns", Attrs2);
|
||||
_ ->
|
||||
""
|
||||
end;
|
||||
get_iq_namespace(_) ->
|
||||
"".
|
||||
|
||||
iq_query_info(El) ->
|
||||
iq_info_internal(El, request).
|
||||
|
||||
iq_query_or_response_info(El) ->
|
||||
iq_info_internal(El, any).
|
||||
|
||||
iq_info_internal({xmlel, NS, _, _, _, _} = El, Filter) ->
|
||||
ElOld = exmpp_xml:xmlel_to_xmlelement(El, [NS],
|
||||
[{'http://etherx.jabber.org/streams', "stream"}]),
|
||||
iq_info_internal2(ElOld, Filter);
|
||||
iq_info_internal(El, Filter) ->
|
||||
catch throw(for_stacktrace),
|
||||
io:format("~nJLIB: old #xmlelement:~n~p~n~p~n~n",
|
||||
[El, erlang:get_stacktrace()]),
|
||||
iq_info_internal2(El, Filter).
|
||||
|
||||
iq_info_internal2({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
|
||||
%% Filter is either request or any. If it is request, any replies
|
||||
%% are converted to the atom reply.
|
||||
ID = xml:get_attr_s("id", Attrs),
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
{Type1, Class} = case Type of
|
||||
"set" -> {set, request};
|
||||
"get" -> {get, request};
|
||||
"result" -> {result, reply};
|
||||
"error" -> {error, reply};
|
||||
_ -> {invalid, invalid}
|
||||
end,
|
||||
if
|
||||
Type1 == invalid ->
|
||||
invalid;
|
||||
Class == request; Filter == any ->
|
||||
%% The iq record is a bit strange. The sub_el field is an
|
||||
%% XML tuple for requests, but a list of XML tuples for
|
||||
%% responses.
|
||||
FilteredEls = xml:remove_cdata(Els),
|
||||
{XMLNS, SubEl} =
|
||||
case {Class, FilteredEls} of
|
||||
{request, [{xmlelement, _Name2, Attrs2, _Els2}]} ->
|
||||
{xml:get_attr_s("xmlns", Attrs2),
|
||||
hd(FilteredEls)};
|
||||
{reply, _} ->
|
||||
%% Find the namespace of the first non-error
|
||||
%% element, if there is one.
|
||||
NonErrorEls = [El ||
|
||||
{xmlelement, SubName, _, _} = El
|
||||
<- FilteredEls,
|
||||
SubName /= "error"],
|
||||
{case NonErrorEls of
|
||||
[NonErrorEl] -> xml:get_tag_attr_s("xmlns", NonErrorEl);
|
||||
_ -> invalid
|
||||
end,
|
||||
FilteredEls};
|
||||
_ ->
|
||||
{invalid, invalid}
|
||||
end,
|
||||
if XMLNS == "", Class == request ->
|
||||
invalid;
|
||||
true ->
|
||||
#iq{id = ID,
|
||||
type = Type1,
|
||||
xmlns = XMLNS,
|
||||
lang = Lang,
|
||||
sub_el = SubEl}
|
||||
end;
|
||||
Class == reply, Filter /= any ->
|
||||
reply
|
||||
end;
|
||||
iq_info_internal2(_, _) ->
|
||||
not_iq.
|
||||
|
||||
is_iq_request_type(set) -> true;
|
||||
is_iq_request_type(get) -> true;
|
||||
is_iq_request_type(_) -> false.
|
||||
|
||||
iq_type_to_string(set) -> "set";
|
||||
iq_type_to_string(get) -> "get";
|
||||
iq_type_to_string(result) -> "result";
|
||||
iq_type_to_string(error) -> "error";
|
||||
iq_type_to_string(_) -> invalid.
|
||||
|
||||
|
||||
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
|
||||
if
|
||||
ID /= "" ->
|
||||
{xmlelement, "iq",
|
||||
[{"id", ID}, {"type", iq_type_to_string(Type)}], SubEl};
|
||||
true ->
|
||||
{xmlelement, "iq",
|
||||
[{"type", iq_type_to_string(Type)}], SubEl}
|
||||
end.
|
||||
|
||||
|
||||
parse_xdata_submit({xmlel, _, _, _, Attrs, Els}) ->
|
||||
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
|
||||
case exmpp_xml:get_attribute_from_list(Attrs, 'type', "") of
|
||||
"submit" ->
|
||||
lists:reverse(parse_xdata_fields(Els, []));
|
||||
_ ->
|
||||
invalid
|
||||
end;
|
||||
parse_xdata_submit(El) ->
|
||||
{xmlelement, _Name, Attrs, Els} = El,
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"submit" ->
|
||||
lists:reverse(parse_xdata_fields(Els, []));
|
||||
_ ->
|
||||
invalid
|
||||
end.
|
||||
|
||||
parse_xdata_fields([], Res) ->
|
||||
Res;
|
||||
parse_xdata_fields([{xmlel, _, _, 'field', Attrs, SubEls} | Els],
|
||||
Res) ->
|
||||
parse_xdata_fields([#xmlel{name = 'field', attrs = Attrs, children = SubEls} |
|
||||
Els], Res) ->
|
||||
case exmpp_xml:get_attribute_from_list(Attrs, 'var', "") of
|
||||
"" ->
|
||||
parse_xdata_fields(Els, Res);
|
||||
@ -479,36 +65,14 @@ parse_xdata_fields([{xmlel, _, _, 'field', Attrs, SubEls} | Els],
|
||||
Field = {Var, lists:reverse(parse_xdata_values(SubEls, []))},
|
||||
parse_xdata_fields(Els, [Field | Res])
|
||||
end;
|
||||
parse_xdata_fields([{xmlelement, Name, Attrs, SubEls} | Els], Res) ->
|
||||
case Name of
|
||||
"field" ->
|
||||
case xml:get_attr_s("var", Attrs) of
|
||||
"" ->
|
||||
parse_xdata_fields(Els, Res);
|
||||
Var ->
|
||||
Field =
|
||||
{Var, lists:reverse(parse_xdata_values(SubEls, []))},
|
||||
parse_xdata_fields(Els, [Field | Res])
|
||||
end;
|
||||
_ ->
|
||||
parse_xdata_fields(Els, Res)
|
||||
end;
|
||||
parse_xdata_fields([_ | Els], Res) ->
|
||||
parse_xdata_fields(Els, Res).
|
||||
|
||||
parse_xdata_values([], Res) ->
|
||||
Res;
|
||||
parse_xdata_values([{xmlel, _, _, 'value', _, SubEls} | Els], Res) ->
|
||||
parse_xdata_values([#xmlel{name = 'value', children = SubEls} | Els], Res) ->
|
||||
Val = exmpp_xml:get_cdata_from_list_as_list(SubEls),
|
||||
parse_xdata_values(Els, [Val | Res]);
|
||||
parse_xdata_values([{xmlelement, Name, _Attrs, SubEls} | Els], Res) ->
|
||||
case Name of
|
||||
"value" ->
|
||||
Val = xml:get_cdata(SubEls),
|
||||
parse_xdata_values(Els, [Val | Res]);
|
||||
_ ->
|
||||
parse_xdata_values(Els, Res)
|
||||
end;
|
||||
parse_xdata_values([_ | Els], Res) ->
|
||||
parse_xdata_values(Els, Res).
|
||||
|
||||
@ -522,7 +86,7 @@ timestamp_to_xml({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||
Timestamp = lists:flatten(
|
||||
io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
|
||||
[Year, Month, Day, Hour, Minute, Second])),
|
||||
exmpp_xml:set_attribute(#xmlel{ns = ?NS_DELAY, name = 'x'},
|
||||
exmpp_xml:set_attribute(#xmlel{ns = ?NS_DELAY_OLD, name = 'x'},
|
||||
'stamp', Timestamp).
|
||||
|
||||
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
|
||||
@ -731,8 +295,8 @@ ip_to_list({A,B,C,D}) ->
|
||||
%%
|
||||
%% Empty fields are set to `undefined', not the empty string.
|
||||
|
||||
from_old_jid(#jid{user = Node, resource = Resource,
|
||||
luser = LNode, lresource = LResource} = JID) ->
|
||||
from_old_jid(#jid{node = Node, resource = Resource,
|
||||
lnode = LNode, lresource = LResource} = JID) ->
|
||||
{Node1, LNode1} = case Node of
|
||||
"" -> {undefined, undefined};
|
||||
_ -> {Node, LNode}
|
||||
@ -741,8 +305,8 @@ from_old_jid(#jid{user = Node, resource = Resource,
|
||||
"" -> {undefined, undefined};
|
||||
_ -> {Resource, LResource}
|
||||
end,
|
||||
JID#jid{user = Node1, resource = Resource1,
|
||||
luser = LNode1, lresource = LResource1}.
|
||||
JID#jid{node = Node1, resource = Resource1,
|
||||
lnode = LNode1, lresource = LResource1}.
|
||||
|
||||
%% @spec (JID) -> New_JID
|
||||
%% JID = jid()
|
||||
@ -751,8 +315,8 @@ from_old_jid(#jid{user = Node, resource = Resource,
|
||||
%%
|
||||
%% Empty fields are set to the empty string, not `undefined'.
|
||||
|
||||
to_old_jid(#jid{user = Node, resource = Resource,
|
||||
luser = LNode, lresource = LResource} = JID) ->
|
||||
to_old_jid(#jid{node = Node, resource = Resource,
|
||||
lnode = LNode, lresource = LResource} = JID) ->
|
||||
{Node1, LNode1} = case Node of
|
||||
undefined -> {"", ""};
|
||||
_ -> {Node, LNode}
|
||||
@ -761,19 +325,19 @@ to_old_jid(#jid{user = Node, resource = Resource,
|
||||
undefined -> {"", ""};
|
||||
_ -> {Resource, LResource}
|
||||
end,
|
||||
JID#jid{user = Node1, resource = Resource1,
|
||||
luser = LNode1, lresource = LResource1}.
|
||||
JID#jid{node = Node1, resource = Resource1,
|
||||
lnode = LNode1, lresource = LResource1}.
|
||||
|
||||
short_jid(JID) ->
|
||||
{JID#jid.user, JID#jid.server, JID#jid.resource}.
|
||||
{JID#jid.node, JID#jid.domain, JID#jid.resource}.
|
||||
|
||||
short_bare_jid(JID) ->
|
||||
Bare_JID = exmpp_jid:jid_to_bare_jid(JID),
|
||||
{Bare_JID#jid.user, Bare_JID#jid.server, Bare_JID#jid.resource}.
|
||||
{Bare_JID#jid.node, Bare_JID#jid.domain, Bare_JID#jid.resource}.
|
||||
|
||||
short_prepd_jid(JID) ->
|
||||
{JID#jid.luser, JID#jid.lserver, JID#jid.lresource}.
|
||||
{JID#jid.lnode, JID#jid.ldomain, JID#jid.lresource}.
|
||||
|
||||
short_prepd_bare_jid(JID) ->
|
||||
Bare_JID = exmpp_jid:jid_to_bare_jid(JID),
|
||||
{Bare_JID#jid.luser, Bare_JID#jid.lserver, Bare_JID#jid.lresource}.
|
||||
{Bare_JID#jid.lnode, Bare_JID#jid.ldomain, Bare_JID#jid.lresource}.
|
||||
|
@ -1406,7 +1406,7 @@ set_form(_From, _Host, ["running nodes", ENode, "backup", "textfile"], _Lang, XD
|
||||
false ->
|
||||
{error, 'bad-request'};
|
||||
{value, {_, [String]}} ->
|
||||
case rpc:call(Node, ejabberd_ctl, dump_to_textfile, [String]) of
|
||||
case rpc:call(Node, ejabberd_admin, dump_to_textfile, [String]) of
|
||||
{badrpc, _Reason} ->
|
||||
{error, 'internal-server-error'};
|
||||
{error, _Reason} ->
|
||||
|
@ -39,7 +39,8 @@
|
||||
remove_old_messages/1,
|
||||
remove_user/2,
|
||||
webadmin_page/3,
|
||||
webadmin_user/4]).
|
||||
webadmin_user/4,
|
||||
webadmin_user_parse_query/5]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
@ -75,6 +76,8 @@ start(Host, Opts) ->
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:add(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:add(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MaxOfflineMsgs])).
|
||||
@ -143,6 +146,8 @@ stop(Host) ->
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:delete(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
exit(whereis(Proc), stop),
|
||||
{wait, Proc}.
|
||||
@ -642,4 +647,24 @@ webadmin_user(Acc, User, Server, Lang) ->
|
||||
_ ->
|
||||
[?C("?")]
|
||||
end,
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen.
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||
|
||||
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
|
||||
US = {User, Server},
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(offline_msg),
|
||||
lists:foreach(
|
||||
fun(Msg) ->
|
||||
mnesia:delete_object(Msg)
|
||||
end, mnesia:dirty_read({offline_msg, US}))
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
{atomic, ok} ->
|
||||
?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]),
|
||||
{stop, ok}
|
||||
end;
|
||||
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
|
||||
Acc.
|
||||
|
@ -38,7 +38,8 @@
|
||||
pop_offline_messages/3,
|
||||
remove_user/2,
|
||||
webadmin_page/3,
|
||||
webadmin_user/4]).
|
||||
webadmin_user/4,
|
||||
webadmin_user_parse_query/5]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
@ -69,6 +70,8 @@ start(Host, Opts) ->
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:add(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:add(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [Host, MaxOfflineMsgs])).
|
||||
@ -151,6 +154,8 @@ stop(Host) ->
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:delete(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
exit(whereis(Proc), stop),
|
||||
ok.
|
||||
@ -446,7 +451,22 @@ webadmin_user(Acc, User, Server, Lang) ->
|
||||
_ ->
|
||||
[?C("?")]
|
||||
end,
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen.
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||
|
||||
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
|
||||
case catch odbc_queries:del_spool_msg(Server, User) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
_ ->
|
||||
?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]),
|
||||
{stop, ok}
|
||||
end;
|
||||
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
|
||||
Acc.
|
||||
|
||||
%% ------------------------------------------------
|
||||
%% mod_offline: number of messages quota management
|
||||
|
@ -42,6 +42,7 @@
|
||||
-define(PROCNAME, ejabberd_mod_proxy65).
|
||||
|
||||
start(Host, Opts) ->
|
||||
mod_proxy65_service:add_listener(Host, Opts),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {
|
||||
Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
@ -50,6 +51,7 @@ start(Host, Opts) ->
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
mod_proxy65_service:delete_listener(Host),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
@ -39,7 +39,7 @@
|
||||
]).
|
||||
|
||||
%% API.
|
||||
-export([start_link/2]).
|
||||
-export([start_link/2, add_listener/2, delete_listener/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@ -55,28 +55,21 @@
|
||||
acl
|
||||
}).
|
||||
|
||||
%% Unused callbacks.
|
||||
handle_cast(_Request, State) ->
|
||||
{noreply, State}.
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
%%----------------
|
||||
|
||||
%%%------------------------
|
||||
%%% gen_server callbacks
|
||||
%%%------------------------
|
||||
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
init([Host, Opts]) ->
|
||||
{IP, State} = parse_options(Host, Opts),
|
||||
NewOpts = [Host, {ip, IP} | Opts],
|
||||
ejabberd_listener:add_listener(State#state.port, mod_proxy65_stream, NewOpts),
|
||||
{_IP, State} = parse_options(Host, Opts),
|
||||
ejabberd_router:register_route(State#state.myhost),
|
||||
{ok, State}.
|
||||
|
||||
terminate(_Reason, #state{myhost=MyHost, port=Port}) ->
|
||||
catch ejabberd_listener:delete_listener(Port),
|
||||
terminate(_Reason, #state{myhost=MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
ok.
|
||||
|
||||
@ -93,10 +86,34 @@ handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_call(get_port, _From, State) ->
|
||||
{reply, {port, State#state.port}, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast(_Request, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%------------------------
|
||||
%%% Listener management
|
||||
%%%------------------------
|
||||
|
||||
add_listener(Host, Opts) ->
|
||||
{IP, State} = parse_options(Host, Opts),
|
||||
NewOpts = [Host, {ip, IP} | Opts],
|
||||
ejabberd_listener:add_listener(State#state.port,mod_proxy65_stream,NewOpts).
|
||||
|
||||
delete_listener(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
{port, Port} = gen_server:call(Proc, get_port),
|
||||
catch ejabberd_listener:delete_listener(Port, mod_proxy65_stream).
|
||||
|
||||
%%%------------------------
|
||||
%%% IQ Processing
|
||||
%%%------------------------
|
||||
|
@ -1486,7 +1486,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
node_call(Type, publish_item, [Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload])
|
||||
end
|
||||
end,
|
||||
%%ejabberd_hooks:run(pubsub_publish_item, Host, [Host, Node, JID, service_jid(Host), ItemId, Payload]),
|
||||
ejabberd_hooks:run(pubsub_publish_item, Host, [Host, Node, Publisher, service_jid(Host), ItemId, Payload]),
|
||||
Reply = [],
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{error, ?ERR_ITEM_NOT_FOUND} ->
|
||||
|
@ -104,60 +104,15 @@ get_auth(Auth) ->
|
||||
unauthorized
|
||||
end.
|
||||
|
||||
make_xhtml(Els, global, Lang) ->
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]),
|
||||
MenuItems2 = [?LI([?AC("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
{200, [html],
|
||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
||||
{"xml:lang", Lang},
|
||||
{"lang", Lang}],
|
||||
[{xmlelement, "head", [],
|
||||
[?XCT("title", "ejabberd Web Admin"),
|
||||
{xmlelement, "meta", [{"http-equiv", "Content-Type"},
|
||||
{"content", "text/html; charset=utf-8"}], []},
|
||||
{xmlelement, "link", [{"href", "/admin/favicon.ico"},
|
||||
{"type", "image/x-icon"},
|
||||
{"rel", "shortcut icon"}], []},
|
||||
{xmlelement, "link", [{"href", "/admin/style.css"},
|
||||
{"type", "text/css"},
|
||||
{"rel", "stylesheet"}], []}]},
|
||||
?XE("body",
|
||||
[?XAE("div",
|
||||
[{"id", "container"}],
|
||||
[?XAE("div",
|
||||
[{"id", "header"}],
|
||||
[?XE("h1",
|
||||
[?ACT("/admin/", "Administration")]
|
||||
)]),
|
||||
?XAE("div",
|
||||
[{"id", "navigation"}],
|
||||
[?XE("ul",
|
||||
[?LI([?ACT("/admin/acls/", "Access Control Lists")]),
|
||||
?LI([?ACT("/admin/access/", "Access Rules")]),
|
||||
?LI([?ACT("/admin/vhosts/", "Virtual Hosts")]),
|
||||
?LI([?ACT("/admin/nodes/", "Nodes")]),
|
||||
?LI([?ACT("/admin/stats/", "Statistics")])
|
||||
] ++ MenuItems2
|
||||
)]),
|
||||
?XAE("div",
|
||||
[{"id", "content"}],
|
||||
Els),
|
||||
?XAE("div",
|
||||
[{"id", "clearcopyright"}],
|
||||
[{xmlcdata, ""}])]),
|
||||
?XAE("div",
|
||||
[{"id", "copyrightouter"}],
|
||||
[?XAE("div",
|
||||
[{"id", "copyright"}],
|
||||
[?XC("p",
|
||||
"ejabberd (c) 2002-2008 ProcessOne")
|
||||
])])])
|
||||
]}};
|
||||
|
||||
make_xhtml(Els, Host, Lang) ->
|
||||
Base = "/admin/server/" ++ Host ++ "/",
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]),
|
||||
MenuItems2 = [?LI([?AC(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
make_xhtml(Els, Host, cluster, Lang).
|
||||
|
||||
%% @spec (Els, Host, Node, Lang)
|
||||
%% where Host = global | string()
|
||||
%% Node = cluster | atom()
|
||||
make_xhtml(Els, Host, Node, Lang) ->
|
||||
Base = get_base_path(Host, cluster), %% Enforcing 'cluster' on purpose here
|
||||
MenuItems = make_navigation(Host, Node, Lang),
|
||||
{200, [html],
|
||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
||||
{"xml:lang", Lang},
|
||||
@ -166,7 +121,7 @@ make_xhtml(Els, Host, Lang) ->
|
||||
[?XCT("title", "ejabberd Web Admin"),
|
||||
{xmlelement, "meta", [{"http-equiv", "Content-Type"},
|
||||
{"content", "text/html; charset=utf-8"}], []},
|
||||
{xmlelement, "link", [{"href", "/admin/favicon.ico"},
|
||||
{xmlelement, "link", [{"href", Base ++ "favicon.ico"},
|
||||
{"type", "image/x-icon"},
|
||||
{"rel", "shortcut icon"}], []},
|
||||
{xmlelement, "link", [{"href", Base ++ "style.css"},
|
||||
@ -183,18 +138,7 @@ make_xhtml(Els, Host, Lang) ->
|
||||
?XAE("div",
|
||||
[{"id", "navigation"}],
|
||||
[?XE("ul",
|
||||
[?LI([?XAE("div",
|
||||
[{"id", "navheadhost"}],
|
||||
[?AC(Base, Host)]
|
||||
)]),
|
||||
?LI([?ACT(Base ++ "acls/", "Access Control Lists")]),
|
||||
?LI([?ACT(Base ++ "access/", "Access Rules")]),
|
||||
?LI([?ACT(Base ++ "users/", "Users")]),
|
||||
?LI([?ACT(Base ++ "online-users/", "Online Users")]),
|
||||
?LI([?ACT(Base ++ "last-activity/", "Last Activity")]),
|
||||
?LI([?ACT(Base ++ "nodes/", "Nodes")]),
|
||||
?LI([?ACT(Base ++ "stats/", "Statistics")])
|
||||
] ++ MenuItems2
|
||||
MenuItems
|
||||
)]),
|
||||
?XAE("div",
|
||||
[{"id", "content"}],
|
||||
@ -211,13 +155,13 @@ make_xhtml(Els, Host, Lang) ->
|
||||
])])])
|
||||
]}}.
|
||||
|
||||
get_base_path(global, cluster) -> "/admin/";
|
||||
get_base_path(Host, cluster) -> "/admin/server/" ++ Host ++ "/";
|
||||
get_base_path(global, Node) -> "/admin/node/" ++ atom_to_list(Node) ++ "/";
|
||||
get_base_path(Host, Node) -> "/admin/server/" ++ Host ++ "/node/" ++ atom_to_list(Node) ++ "/".
|
||||
|
||||
css(Host) ->
|
||||
Base = case Host of
|
||||
global ->
|
||||
"/admin/";
|
||||
_ ->
|
||||
"/admin/server/" ++ Host ++ "/"
|
||||
end,
|
||||
Base = get_base_path(Host, cluster),
|
||||
"
|
||||
html,body {
|
||||
background: white;
|
||||
@ -298,7 +242,7 @@ html>body #container {
|
||||
|
||||
#navigation ul {
|
||||
position: absolute;
|
||||
top: 54px;
|
||||
top: 65px;
|
||||
left: 0;
|
||||
padding: 0 1px 1px 1px;
|
||||
margin: 0;
|
||||
@ -306,7 +250,7 @@ html>body #container {
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
background: #d47911;
|
||||
width: 13em;
|
||||
width: 17em;
|
||||
}
|
||||
|
||||
#navigation ul li {
|
||||
@ -340,9 +284,20 @@ html>body #container {
|
||||
background: #332;
|
||||
}
|
||||
|
||||
#navheadhost {
|
||||
text-align: left;
|
||||
border-bottom: 2px solid #d47911;
|
||||
ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {
|
||||
text-align: center;
|
||||
border-top: 2px solid #d47911;
|
||||
border-bottom: 1px solid #d47911;
|
||||
}
|
||||
|
||||
#navheadsub, #navitemsub {
|
||||
border-left: 7px solid white;
|
||||
margin-left: 2px solid #d47911;
|
||||
}
|
||||
|
||||
#navheadsubsub, #navitemsubsub {
|
||||
border-left: 14px solid white;
|
||||
margin-left: 4px solid #d47911;
|
||||
}
|
||||
|
||||
#lastactivity li {
|
||||
@ -550,7 +505,7 @@ h3 {
|
||||
#content {
|
||||
font-family: Verdana, Arial, Helvetica, sans-serif;
|
||||
font-size: 10pt;
|
||||
padding-left: 13em;
|
||||
padding-left: 17em;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
@ -595,11 +550,12 @@ logo_fill() ->
|
||||
"1c5dvhSU2BpKqBXl6R0ljYGS50R5zVC+tVD+vfE6YyUexE9x7g4AAAAASUVO"
|
||||
"RK5CYII=").
|
||||
|
||||
|
||||
process_admin(global,
|
||||
#request{path = [],
|
||||
lang = Lang}) ->
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]),
|
||||
MenuItems2 = [?LI([?AC("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
Base = get_base_path(global, cluster),
|
||||
MenuItems2 = make_menu_items(global, cluster, Base, Lang),
|
||||
make_xhtml([?XCT("h1", "Administration"),
|
||||
?XE("ul",
|
||||
[?LI([?ACT("/admin/acls/", "Access Control Lists"), ?C(" "),
|
||||
@ -616,9 +572,8 @@ process_admin(global,
|
||||
process_admin(Host,
|
||||
#request{path = [],
|
||||
lang = Lang}) ->
|
||||
Base = "/admin/server/" ++ Host ++ "/",
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]),
|
||||
MenuItems2 = [?LI([?AC(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
Base = get_base_path(Host, cluster),
|
||||
MenuItems2 = make_menu_items(Host, cluster, Base, Lang),
|
||||
make_xhtml([?XCT("h1", "Administration"),
|
||||
?XE("ul",
|
||||
[?LI([?ACT(Base ++ "acls/", "Access Control Lists"), ?C(" "),
|
||||
@ -953,8 +908,13 @@ process_admin(Host,
|
||||
#request{path = ["user", U],
|
||||
q = Query,
|
||||
lang = Lang}) ->
|
||||
case ejabberd_auth:is_user_exists(U, Host) of
|
||||
true ->
|
||||
Res = user_info(U, Host, Query, Lang),
|
||||
make_xhtml(Res, Host, Lang);
|
||||
false ->
|
||||
make_xhtml([?XCT("h1", "Not Found")], Host, Lang)
|
||||
end;
|
||||
|
||||
process_admin(Host,
|
||||
#request{path = ["nodes"],
|
||||
@ -971,7 +931,7 @@ process_admin(Host,
|
||||
make_xhtml([?XCT("h1", "Node not found")], Host, Lang);
|
||||
Node ->
|
||||
Res = get_node(Host, Node, NPath, Query, Lang),
|
||||
make_xhtml(Res, Host, Lang)
|
||||
make_xhtml(Res, Host, Node, Lang)
|
||||
end;
|
||||
|
||||
process_admin(Host, #request{lang = Lang} = Request) ->
|
||||
@ -1515,8 +1475,15 @@ user_info(User, Server, Query, Lang) ->
|
||||
|
||||
|
||||
user_parse_query(User, Server, Query) ->
|
||||
case lists:keysearch("chpassword", 1, Query) of
|
||||
{value, _} ->
|
||||
lists:foldl(fun({Action, _Value}, Acc) when Acc == nothing ->
|
||||
user_parse_query1(Action, User, Server, Query);
|
||||
({_Action, _Value}, Acc) ->
|
||||
Acc
|
||||
end, nothing, Query).
|
||||
|
||||
user_parse_query1("password", _User, _Server, _Query) ->
|
||||
nothing;
|
||||
user_parse_query1("chpassword", User, Server, Query) ->
|
||||
case lists:keysearch("password", 1, Query) of
|
||||
{value, {_, undefined}} ->
|
||||
error;
|
||||
@ -1526,14 +1493,13 @@ user_parse_query(User, Server, Query) ->
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
case lists:keysearch("removeuser", 1, Query) of
|
||||
{value, _} ->
|
||||
user_parse_query1("removeuser", User, Server, _Query) ->
|
||||
ejabberd_auth:remove_user(User, Server),
|
||||
ok;
|
||||
false ->
|
||||
nothing
|
||||
end
|
||||
user_parse_query1(Action, User, Server, Query) ->
|
||||
case ejabberd_hooks:run_fold(webadmin_user_parse_query, Server, [], [Action, User, Server, Query]) of
|
||||
[] -> nothing;
|
||||
Res -> Res
|
||||
end.
|
||||
|
||||
|
||||
@ -1661,8 +1627,8 @@ search_running_node(SNode, [Node | Nodes]) ->
|
||||
|
||||
get_node(global, Node, [], Query, Lang) ->
|
||||
Res = node_parse_query(Node, Query),
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_node, [], [Node, Lang]),
|
||||
MenuItems2 = [?LI([?AC(MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
Base = get_base_path(global, Node),
|
||||
MenuItems2 = make_menu_items(global, Node, Base, Lang),
|
||||
[?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++
|
||||
case Res of
|
||||
ok -> [?CT("Submitted"), ?P];
|
||||
@ -1670,11 +1636,11 @@ get_node(global, Node, [], Query, Lang) ->
|
||||
nothing -> []
|
||||
end ++
|
||||
[?XE("ul",
|
||||
[?LI([?ACT("db/", "Database")]),
|
||||
?LI([?ACT("backup/", "Backup")]),
|
||||
?LI([?ACT("ports/", "Listened Ports")]),
|
||||
?LI([?ACT("stats/", "Statistics")]),
|
||||
?LI([?ACT("update/", "Update")])
|
||||
[?LI([?ACT(Base ++ "db/", "Database")]),
|
||||
?LI([?ACT(Base ++ "backup/", "Backup")]),
|
||||
?LI([?ACT(Base ++ "ports/", "Listened Ports")]),
|
||||
?LI([?ACT(Base ++ "stats/", "Statistics")]),
|
||||
?LI([?ACT(Base ++ "update/", "Update")])
|
||||
] ++ MenuItems2),
|
||||
?XAE("form", [{"action", ""}, {"method", "post"}],
|
||||
[?INPUTT("submit", "restart", "Restart"),
|
||||
@ -1683,11 +1649,11 @@ get_node(global, Node, [], Query, Lang) ->
|
||||
];
|
||||
|
||||
get_node(Host, Node, [], _Query, Lang) ->
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Node, Lang]),
|
||||
MenuItems2 = [?LI([?AC(MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
Base = get_base_path(Host, Node),
|
||||
MenuItems2 = make_menu_items(global, Node, Base, Lang),
|
||||
[?XC("h1", ?T("Node ") ++ atom_to_list(Node)),
|
||||
?XE("ul",
|
||||
[?LI([?ACT("modules/", "Modules")])] ++ MenuItems2)
|
||||
[?LI([?ACT(Base ++ "modules/", "Modules")])] ++ MenuItems2)
|
||||
];
|
||||
|
||||
get_node(global, Node, ["db"], Query, Lang) ->
|
||||
@ -2022,7 +1988,7 @@ node_backup_parse_query(Node, Query) ->
|
||||
rpc:call(Node, mnesia,
|
||||
install_fallback, [Path]);
|
||||
"dump" ->
|
||||
rpc:call(Node, ejabberd_ctl,
|
||||
rpc:call(Node, ejabberd_admin,
|
||||
dump_to_textfile, [Path]);
|
||||
"load" ->
|
||||
rpc:call(Node, mnesia,
|
||||
@ -2087,7 +2053,7 @@ node_ports_to_xhtml(Ports, Lang) ->
|
||||
|
||||
node_ports_parse_query(Node, Ports, Query) ->
|
||||
lists:foreach(
|
||||
fun({Port, _Module1, _Opts1}) ->
|
||||
fun({Port, Module1, _Opts1}) ->
|
||||
SPort = integer_to_list(Port),
|
||||
case lists:keysearch("add" ++ SPort, 1, Query) of
|
||||
{value, _} ->
|
||||
@ -2097,13 +2063,13 @@ node_ports_parse_query(Node, Ports, Query) ->
|
||||
Module = list_to_atom(SModule),
|
||||
{ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
|
||||
{ok, Opts} = erl_parse:parse_term(Tokens),
|
||||
rpc:call(Node, ejabberd_listener, delete_listener, [Port]),
|
||||
rpc:call(Node, ejabberd_listener, delete_listener, [Port, Module]),
|
||||
rpc:call(Node, ejabberd_listener, add_listener, [Port, Module, Opts]),
|
||||
throw(submitted);
|
||||
_ ->
|
||||
case lists:keysearch("delete" ++ SPort, 1, Query) of
|
||||
{value, _} ->
|
||||
rpc:call(Node, ejabberd_listener, delete_listener, [Port]),
|
||||
rpc:call(Node, ejabberd_listener, delete_listener, [Port, Module1]),
|
||||
throw(submitted);
|
||||
_ ->
|
||||
ok
|
||||
@ -2276,3 +2242,134 @@ last_modified() ->
|
||||
{"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}.
|
||||
cache_control_public() ->
|
||||
{"Cache-Control", "public"}.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Navigation Menu
|
||||
%%%
|
||||
|
||||
%% @spec (Host, Node, Lang) -> [LI]
|
||||
make_navigation(Host, Node, Lang) ->
|
||||
HostNodeMenu = make_host_node_menu(Host, Node, Lang),
|
||||
HostMenu = make_host_menu(Host, HostNodeMenu, Lang),
|
||||
NodeMenu = make_node_menu(Host, Node, Lang),
|
||||
Menu = make_server_menu(HostMenu, NodeMenu, Lang),
|
||||
make_menu_items(Lang, Menu).
|
||||
|
||||
%% @spec (Host, Node, Base, Lang) -> [LI]
|
||||
make_menu_items(global, cluster, Base, Lang) ->
|
||||
HookItems = get_menu_items_hook(server, Lang),
|
||||
make_menu_items(Lang, {Base, "", HookItems});
|
||||
|
||||
make_menu_items(global, _Node, Base, Lang) ->
|
||||
HookItems = get_menu_items_hook(node, Lang),
|
||||
make_menu_items(Lang, {Base, "", HookItems});
|
||||
|
||||
make_menu_items(Host, cluster, Base, Lang) ->
|
||||
HookItems = get_menu_items_hook({host, Host}, Lang),
|
||||
make_menu_items(Lang, {Base, "", HookItems});
|
||||
|
||||
make_menu_items(Host, _Node, Base, Lang) ->
|
||||
HookItems = get_menu_items_hook({hostnode, Host}, Lang),
|
||||
make_menu_items(Lang, {Base, "", HookItems}).
|
||||
|
||||
|
||||
make_host_node_menu(global, _, _Lang) ->
|
||||
{"", "", []};
|
||||
make_host_node_menu(_, cluster, _Lang) ->
|
||||
{"", "", []};
|
||||
make_host_node_menu(Host, Node, Lang) ->
|
||||
HostNodeBase = get_base_path(Host, Node),
|
||||
HostNodeFixed = [{"modules/", "Modules"}],
|
||||
HostNodeHook = get_menu_items_hook({hostnode, Host}, Lang),
|
||||
{HostNodeBase, atom_to_list(Node), HostNodeFixed ++ HostNodeHook}.
|
||||
|
||||
make_host_menu(global, _HostNodeMenu, _Lang) ->
|
||||
{"", "", []};
|
||||
make_host_menu(Host, HostNodeMenu, Lang) ->
|
||||
HostBase = get_base_path(Host, cluster),
|
||||
HostFixed = [{"acls", "Access Control Lists"},
|
||||
{"access", "Access Rules"},
|
||||
{"users", "Users"},
|
||||
{"online-users", "Online Users"},
|
||||
{"last-activity", "Last Activity"},
|
||||
{"nodes", "Nodes", HostNodeMenu},
|
||||
{"stats", "Statistics"}],
|
||||
HostHook = get_menu_items_hook({host, Host}, Lang),
|
||||
{HostBase, Host, HostFixed ++ HostHook}.
|
||||
|
||||
make_node_menu(_Host, cluster, _Lang) ->
|
||||
{"", "", []};
|
||||
make_node_menu(global, Node, Lang) ->
|
||||
NodeBase = get_base_path(global, Node),
|
||||
NodeFixed = [{"db/", "Database"},
|
||||
{"backup/", "Backup"},
|
||||
{"ports/", "Listened Ports"},
|
||||
{"stats/", "Statistics"},
|
||||
{"update/", "Update"}],
|
||||
NodeHook = get_menu_items_hook(node, Lang),
|
||||
{NodeBase, atom_to_list(Node), NodeFixed ++ NodeHook};
|
||||
make_node_menu(_Host, _Node, _Lang) ->
|
||||
{"", "", []}.
|
||||
|
||||
make_server_menu(HostMenu, NodeMenu, Lang) ->
|
||||
Base = get_base_path(global, cluster),
|
||||
Fixed = [{"acls", "Access Control Lists"},
|
||||
{"access", "Access Rules"},
|
||||
{"vhosts", "Virtual Hosts", HostMenu},
|
||||
{"nodes", "Nodes", NodeMenu},
|
||||
{"stats", "Statistics"}],
|
||||
Hook = get_menu_items_hook(server, Lang),
|
||||
{Base, "ejabberd", Fixed ++ Hook}.
|
||||
|
||||
|
||||
get_menu_items_hook({hostnode, Host}, Lang) ->
|
||||
ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Lang]);
|
||||
get_menu_items_hook({host, Host}, Lang) ->
|
||||
ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]);
|
||||
get_menu_items_hook(node, Lang) ->
|
||||
ejabberd_hooks:run_fold(webadmin_menu_node, [], [Lang]);
|
||||
get_menu_items_hook(server, Lang) ->
|
||||
ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]).
|
||||
|
||||
|
||||
%% @spec (Lang::string(), Menu) -> [LI]
|
||||
%% where Menu = {MURI::string(), MName::string(), Items::[Item]}
|
||||
%% Item = {IURI::string(), IName::string()} | {IURI::string(), IName::string(), Menu}
|
||||
make_menu_items(Lang, Menu) ->
|
||||
lists:reverse(make_menu_items2(Lang, 1, Menu)).
|
||||
|
||||
make_menu_items2(Lang, Deep, {MURI, MName, _} = Menu) ->
|
||||
Res = case MName of
|
||||
"" -> [];
|
||||
_ -> [make_menu_item(header, Deep, MURI, MName, Lang) ]
|
||||
end,
|
||||
make_menu_items2(Lang, Deep, Menu, Res).
|
||||
|
||||
make_menu_items2(_, _Deep, {_, _, []}, Res) ->
|
||||
Res;
|
||||
|
||||
make_menu_items2(Lang, Deep, {MURI, MName, [Item | Items]}, Res) ->
|
||||
Res2 = case Item of
|
||||
{IURI, IName} ->
|
||||
[make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res];
|
||||
{IURI, IName, SubMenu} ->
|
||||
%%ResTemp = [?LI([?ACT(MURI ++ IURI ++ "/", IName)]) | Res],
|
||||
ResTemp = [make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res],
|
||||
ResSubMenu = make_menu_items2(Lang, Deep+1, SubMenu),
|
||||
ResSubMenu ++ ResTemp
|
||||
end,
|
||||
make_menu_items2(Lang, Deep, {MURI, MName, Items}, Res2).
|
||||
|
||||
make_menu_item(header, 1, URI, Name, _Lang) ->
|
||||
?LI([?XAE("div", [{"id", "navhead"}], [?AC(URI, "~ "++Name++" ~")] )]);
|
||||
make_menu_item(header, 2, URI, Name, _Lang) ->
|
||||
?LI([?XAE("div", [{"id", "navheadsub"}], [?AC(URI, "~ "++Name++" ~")] )]);
|
||||
make_menu_item(header, 3, URI, Name, _Lang) ->
|
||||
?LI([?XAE("div", [{"id", "navheadsubsub"}], [?AC(URI, "~ "++Name++" ~")] )]);
|
||||
make_menu_item(item, 1, URI, Name, Lang) ->
|
||||
?LI([?XAE("div", [{"id", "navitem"}], [?ACT(URI, Name)] )]);
|
||||
make_menu_item(item, 2, URI, Name, Lang) ->
|
||||
?LI([?XAE("div", [{"id", "navitemsub"}], [?ACT(URI, Name)] )]);
|
||||
make_menu_item(item, 3, URI, Name, Lang) ->
|
||||
?LI([?XAE("div", [{"id", "navitemsubsub"}], [?ACT(URI, Name)] )]).
|
||||
|
Loading…
Reference in New Issue
Block a user