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>
|
2008-10-13 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
|
||||||
|
|
||||||
* src/extauth.erl (call_port/2): Replace jlib:nameprep/1 by
|
* src/extauth.erl (call_port/2): Replace jlib:nameprep/1 by
|
||||||
@ -8,6 +12,68 @@
|
|||||||
|
|
||||||
* src/ejd2odbc.erl, src/jd2ejd.erl: Convert to exmpp.
|
* 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>
|
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
|
* 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:
|
* src/mod_private.erl, src/mod_private_odbc.erl, src/mod_version.erl:
|
||||||
Convert to exmpp. Thanks to Pablo Polvorin!
|
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>
|
2008-10-07 Jerome Sautret <jerome.sautret@process-one.net>
|
||||||
|
|
||||||
* src/mod_roster_odbc.erl: fix MySQL multiple requests issue.
|
* 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
|
</P><PRE CLASS="verbatim">ejabberdctl start
|
||||||
|
|
||||||
ejabberdctl status
|
ejabberdctl status
|
||||||
Node ejabberd@localhost is started. Status: started
|
The node ejabberd@localhost is started with status: started
|
||||||
ejabberd is running
|
ejabberd is running in that node
|
||||||
|
|
||||||
ejabberdctl stop
|
ejabberdctl stop
|
||||||
</PRE><P>If <TT>ejabberd</TT> doesn’t start correctly and a crash dump is generated,
|
</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.
|
<TT>ejabberdctl</TT> shows all the available commands in that server.
|
||||||
The more interesting ones are:
|
The more interesting ones are:
|
||||||
</P><DL CLASS="description"><DT CLASS="dt-description">
|
</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>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
|
</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.
|
so that this command is executed after each rotation.
|
||||||
|
@ -425,8 +425,8 @@ Usage example:
|
|||||||
ejabberdctl start
|
ejabberdctl start
|
||||||
|
|
||||||
ejabberdctl status
|
ejabberdctl status
|
||||||
Node ejabberd@localhost is started. Status: started
|
The node ejabberd@localhost is started with status: started
|
||||||
ejabberd is running
|
ejabberd is running in that node
|
||||||
|
|
||||||
ejabberdctl stop
|
ejabberdctl stop
|
||||||
\end{verbatim}
|
\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.
|
\term{ejabberdctl} shows all the available commands in that server.
|
||||||
The more interesting ones are:
|
The more interesting ones are:
|
||||||
\begin{description}
|
\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{status} Check the status of the \ejabberd{} server.
|
||||||
\titem{stop} Stop the \ejabberd{} server which is running in the machine.
|
\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
|
\titem{reopen-log} If you use a tool to rotate logs, you have to configure it
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
%%% File : ejabberd_admin.erl
|
%%% File : ejabberd_admin.erl
|
||||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||||
%%% Description : This module gathers admin functions used by different
|
%%% Purpose : Administrative functions and commands
|
||||||
%%% access method:
|
|
||||||
%%% - ejabberdctl command-line tool
|
|
||||||
%%% - web admin interface
|
|
||||||
%%% - adhoc mode
|
|
||||||
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
||||||
%%%
|
%%%
|
||||||
%%%
|
%%%
|
||||||
@ -31,9 +27,237 @@
|
|||||||
-module(ejabberd_admin).
|
-module(ejabberd_admin).
|
||||||
-author('mickael.remond@process-one.net').
|
-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.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
|
%% Mnesia database restore
|
||||||
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
|
%% 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_shared_roster) -> [sr_group, sr_user];
|
||||||
module_tables(mod_vcard) -> [vcard, vcard_search];
|
module_tables(mod_vcard) -> [vcard, vcard_search];
|
||||||
module_tables(_Other) -> [].
|
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).
|
-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").
|
-include("ejabberd.hrl").
|
||||||
|
|
||||||
@ -47,6 +47,8 @@ start(normal, _Args) ->
|
|||||||
translate:start(),
|
translate:start(),
|
||||||
acl:start(),
|
acl:start(),
|
||||||
ejabberd_ctl:init(),
|
ejabberd_ctl:init(),
|
||||||
|
ejabberd_commands:init(),
|
||||||
|
ejabberd_admin:start(),
|
||||||
gen_mod:start(),
|
gen_mod:start(),
|
||||||
ejabberd_config:start(),
|
ejabberd_config:start(),
|
||||||
ejabberd_check:config(),
|
ejabberd_check:config(),
|
||||||
@ -61,6 +63,7 @@ start(normal, _Args) ->
|
|||||||
%eprof:profile([self()]),
|
%eprof:profile([self()]),
|
||||||
%fprof:trace(start, "/tmp/fprof"),
|
%fprof:trace(start, "/tmp/fprof"),
|
||||||
start_modules(),
|
start_modules(),
|
||||||
|
ejabberd_listener:start_listeners(),
|
||||||
Sup;
|
Sup;
|
||||||
start(_, _) ->
|
start(_, _) ->
|
||||||
{error, badarg}.
|
{error, badarg}.
|
||||||
@ -70,6 +73,7 @@ start(_, _) ->
|
|||||||
%% before shutting down the processes of the application.
|
%% before shutting down the processes of the application.
|
||||||
prep_stop(State) ->
|
prep_stop(State) ->
|
||||||
stop_modules(),
|
stop_modules(),
|
||||||
|
ejabberd_admin:stop(),
|
||||||
State.
|
State.
|
||||||
|
|
||||||
%% All the processes were killed when this function is called
|
%% All the processes were killed when this function is called
|
||||||
|
@ -49,14 +49,12 @@
|
|||||||
is_user_exists_in_other_modules/3,
|
is_user_exists_in_other_modules/3,
|
||||||
remove_user/2,
|
remove_user/2,
|
||||||
remove_user/3,
|
remove_user/3,
|
||||||
plain_password_required/1,
|
plain_password_required/1
|
||||||
ctl_process_get_registered/3
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([auth_modules/1]).
|
-export([auth_modules/1]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("ejabberd_ctl.hrl").
|
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% API
|
%%% API
|
||||||
@ -265,15 +263,6 @@ remove_user(User, Server, Password) ->
|
|||||||
M:remove_user(User, Server, Password)
|
M:remove_user(User, Server, Password)
|
||||||
end, auth_modules(Server)).
|
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
|
%%% Internal functions
|
||||||
|
@ -53,14 +53,10 @@
|
|||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% API
|
%%% API
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
start(Host) ->
|
start(_Host) ->
|
||||||
mnesia:create_table(passwd, [{disc_copies, [node()]},
|
mnesia:create_table(passwd, [{disc_copies, [node()]},
|
||||||
{attributes, record_info(fields, passwd)}]),
|
{attributes, record_info(fields, passwd)}]),
|
||||||
update_table(),
|
update_table(),
|
||||||
ejabberd_ctl:register_commands(
|
|
||||||
Host,
|
|
||||||
[{"registered-users", "list all registered users"}],
|
|
||||||
ejabberd_auth, ctl_process_get_registered),
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
plain_password_required() ->
|
plain_password_required() ->
|
||||||
|
@ -112,11 +112,8 @@ start_link(Host) ->
|
|||||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||||
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
||||||
|
|
||||||
terminate(_Reason, State) ->
|
terminate(_Reason, _State) ->
|
||||||
ejabberd_ctl:unregister_commands(
|
ok.
|
||||||
State#state.host,
|
|
||||||
[{"registered-users", "list all registered users"}],
|
|
||||||
ejabberd_auth, ctl_process_get_registered).
|
|
||||||
|
|
||||||
init(Host) ->
|
init(Host) ->
|
||||||
State = parse_options(Host),
|
State = parse_options(Host),
|
||||||
@ -132,10 +129,6 @@ init(Host) ->
|
|||||||
State#state.port,
|
State#state.port,
|
||||||
State#state.dn,
|
State#state.dn,
|
||||||
State#state.password),
|
State#state.password),
|
||||||
ejabberd_ctl:register_commands(
|
|
||||||
Host,
|
|
||||||
[{"registered-users", "list all registered users"}],
|
|
||||||
ejabberd_auth, ctl_process_get_registered),
|
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
plain_password_required() ->
|
plain_password_required() ->
|
||||||
|
@ -51,11 +51,7 @@
|
|||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% API
|
%%% API
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
start(Host) ->
|
start(_Host) ->
|
||||||
ejabberd_ctl:register_commands(
|
|
||||||
Host,
|
|
||||||
[{"registered-users", "list all registered users"}],
|
|
||||||
ejabberd_auth, ctl_process_get_registered),
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
plain_password_required() ->
|
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
|
%%% File : ejabberd_ctl.erl
|
||||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
%%% 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>
|
%%% 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).
|
-module(ejabberd_ctl).
|
||||||
-author('alexey@process-one.net').
|
-author('alexey@process-one.net').
|
||||||
|
|
||||||
-export([start/0,
|
-export([start/0,
|
||||||
init/0,
|
init/0,
|
||||||
process/1,
|
process/1,
|
||||||
dump_to_textfile/1,
|
process2/1,
|
||||||
register_commands/3,
|
register_commands/3,
|
||||||
register_commands/4,
|
unregister_commands/3]).
|
||||||
unregister_commands/3,
|
|
||||||
unregister_commands/4]).
|
|
||||||
|
|
||||||
-include("ejabberd_ctl.hrl").
|
-include("ejabberd_ctl.hrl").
|
||||||
|
-include("ejabberd_commands.hrl").
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
%%-----------------------------
|
||||||
|
%% Module
|
||||||
|
%%-----------------------------
|
||||||
|
|
||||||
start() ->
|
start() ->
|
||||||
case init:get_plain_arguments() of
|
case init:get_plain_arguments() of
|
||||||
[SNode | Args] ->
|
[SNode | Args] ->
|
||||||
@ -59,8 +83,9 @@ start() ->
|
|||||||
Node = list_to_atom(SNode1),
|
Node = list_to_atom(SNode1),
|
||||||
Status = case rpc:call(Node, ?MODULE, process, [Args]) of
|
Status = case rpc:call(Node, ?MODULE, process, [Args]) of
|
||||||
{badrpc, Reason} ->
|
{badrpc, Reason} ->
|
||||||
?PRINT("RPC failed on the node ~p: ~p~n",
|
?PRINT("Failed RPC connection to the node ~p: ~p~n",
|
||||||
[Node, Reason]),
|
[Node, Reason]),
|
||||||
|
%% TODO: show minimal start help
|
||||||
?STATUS_BADRPC;
|
?STATUS_BADRPC;
|
||||||
S ->
|
S ->
|
||||||
S
|
S
|
||||||
@ -76,16 +101,41 @@ init() ->
|
|||||||
ets:new(ejabberd_ctl_host_cmds, [named_table, set, public]).
|
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"]) ->
|
process(["status"]) ->
|
||||||
{InternalStatus, ProvidedStatus} = init:get_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]),
|
[node(), InternalStatus, ProvidedStatus]),
|
||||||
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
||||||
false ->
|
false ->
|
||||||
?PRINT("ejabberd is not running~n", []),
|
?PRINT("ejabberd is not running in that node~n", []),
|
||||||
?STATUS_ERROR;
|
?STATUS_ERROR;
|
||||||
{value,_Version} ->
|
{value, {_, _, Version}} ->
|
||||||
?PRINT("ejabberd is running~n", []),
|
?PRINT("ejabberd ~s is running in that node~n", [Version]),
|
||||||
?STATUS_SUCCESS
|
?STATUS_SUCCESS
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -97,121 +147,6 @@ process(["restart"]) ->
|
|||||||
init:restart(),
|
init:restart(),
|
||||||
?STATUS_SUCCESS;
|
?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"]) ->
|
process(["mnesia"]) ->
|
||||||
?PRINT("~p~n", [mnesia:system_info(all)]),
|
?PRINT("~p~n", [mnesia:system_info(all)]),
|
||||||
?STATUS_SUCCESS;
|
?STATUS_SUCCESS;
|
||||||
@ -227,183 +162,589 @@ process(["mnesia", Arg]) when is_list(Arg) ->
|
|||||||
end,
|
end,
|
||||||
?STATUS_SUCCESS;
|
?STATUS_SUCCESS;
|
||||||
|
|
||||||
process(["delete-old-messages", Days]) ->
|
%% The arguments --long and --dual are not documented because they are
|
||||||
case catch list_to_integer(Days) of
|
%% automatically selected depending in the number of columns of the shell
|
||||||
{'EXIT',{Reason, _Stack}} ->
|
process(["help" | Mode]) ->
|
||||||
?PRINT("Can't delete old messages (~p). Please pass an integer as parameter.~n",
|
{MaxC, ShCode} = get_shell_info(),
|
||||||
[Reason]),
|
case Mode of
|
||||||
?STATUS_ERROR;
|
[] ->
|
||||||
Integer when Integer >= 0 ->
|
print_usage(dual, MaxC, ShCode),
|
||||||
{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),
|
|
||||||
?STATUS_USAGE;
|
?STATUS_USAGE;
|
||||||
Status ->
|
["--dual"] ->
|
||||||
Status
|
print_usage(dual, MaxC, ShCode),
|
||||||
end
|
?STATUS_USAGE;
|
||||||
catch
|
["--long"] ->
|
||||||
_ ->
|
print_usage(long, MaxC, ShCode),
|
||||||
?PRINT("Bad hostname: ~p~n", [H]),
|
?STATUS_USAGE;
|
||||||
?STATUS_ERROR
|
["--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;
|
end;
|
||||||
|
|
||||||
process(Args) ->
|
process(Args) ->
|
||||||
case ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
|
{String, Code} = process2(Args),
|
||||||
false ->
|
io:format(String),
|
||||||
print_usage(),
|
io:format("\n"),
|
||||||
?STATUS_USAGE;
|
Code.
|
||||||
Status ->
|
|
||||||
Status
|
%% @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.
|
end.
|
||||||
|
|
||||||
|
|
||||||
print_usage() ->
|
%%-----------------------------
|
||||||
CmdDescs =
|
%% Command calling
|
||||||
[{"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",
|
|
||||||
[]).
|
|
||||||
|
|
||||||
print_vhost_usage(Host) ->
|
%% @spec (Args::[string()]) ->
|
||||||
CmdDescs =
|
%% String::string() | Code::integer() | {String::string(), Code::integer()}
|
||||||
ets:select(ejabberd_ctl_host_cmds,
|
try_run_ctp(Args) ->
|
||||||
[{{{Host, '$1'}, '$2'}, [], [{{'$1', '$2'}}]}]),
|
try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
|
||||||
MaxCmdLen =
|
false when Args /= [] ->
|
||||||
if
|
try_call_command(Args);
|
||||||
CmdDescs == [] ->
|
false ->
|
||||||
0;
|
print_usage(),
|
||||||
true ->
|
{"", ?STATUS_USAGE};
|
||||||
lists:max(lists:map(
|
Status ->
|
||||||
fun({Cmd, _Desc}) ->
|
{"", Status}
|
||||||
length(Cmd)
|
catch
|
||||||
end, CmdDescs))
|
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,
|
end,
|
||||||
NewLine = io_lib:format("~n", []),
|
{io_lib:format("Error: the command ~p requires ~p ~s.",
|
||||||
FmtCmdDescs =
|
[CmdString, NumCompa, TextCompa]),
|
||||||
lists:map(
|
wrong_command_arguments}
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end, Tabs1),
|
end.
|
||||||
Defs = lists:map(
|
|
||||||
fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
|
|
||||||
{attributes, mnesia:table_info(T, attributes)}]}
|
%%-----------------------------
|
||||||
|
%% Format arguments
|
||||||
|
%%-----------------------------
|
||||||
|
|
||||||
|
format_args(Args, ArgsFormat) ->
|
||||||
|
lists:foldl(
|
||||||
|
fun({{_ArgName, ArgFormat}, Arg}, Res) ->
|
||||||
|
Formatted = format_arg(Arg, ArgFormat),
|
||||||
|
Res ++ [Formatted]
|
||||||
end,
|
end,
|
||||||
Tabs),
|
[],
|
||||||
io:format(F, "~p.~n", [{tables, Defs}]),
|
lists:zip(ArgsFormat, Args)).
|
||||||
lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
|
|
||||||
file:close(F);
|
format_arg(Arg, Format) ->
|
||||||
dump_to_textfile(_, {ok, F}) ->
|
Parse = case Format of
|
||||||
file:close(F),
|
integer ->
|
||||||
{error, mnesia_not_running};
|
"~d";
|
||||||
dump_to_textfile(_, {error, Reason}) ->
|
string ->
|
||||||
{error, Reason}.
|
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),
|
%% Format result
|
||||||
{atomic,All} = mnesia:transaction(
|
%%-----------------------------
|
||||||
fun() -> mnesia:match_object(T, W, read) end),
|
|
||||||
|
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(
|
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,
|
-export([start_link/0, init/1, start/3,
|
||||||
init/3,
|
init/3,
|
||||||
|
start_listeners/0,
|
||||||
start_listener/3,
|
start_listener/3,
|
||||||
stop_listener/1,
|
stop_listener/2,
|
||||||
add_listener/3,
|
add_listener/3,
|
||||||
delete_listener/1
|
delete_listener/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
@ -42,24 +43,27 @@ start_link() ->
|
|||||||
|
|
||||||
|
|
||||||
init(_) ->
|
init(_) ->
|
||||||
|
{ok, {{one_for_one, 10, 1}, []}}.
|
||||||
|
|
||||||
|
start_listeners() ->
|
||||||
case ejabberd_config:get_local_option(listen) of
|
case ejabberd_config:get_local_option(listen) of
|
||||||
undefined ->
|
undefined ->
|
||||||
ignore;
|
ignore;
|
||||||
Ls ->
|
Ls ->
|
||||||
{ok, {{one_for_one, 10, 1},
|
|
||||||
lists:map(
|
lists:map(
|
||||||
fun({Port, Module, Opts}) ->
|
fun({Port, Module, Opts}) ->
|
||||||
{Port,
|
start_listener(Port, Module, Opts)
|
||||||
{?MODULE, start, [Port, Module, Opts]},
|
end, Ls)
|
||||||
transient,
|
|
||||||
brutal_kill,
|
|
||||||
worker,
|
|
||||||
[?MODULE]}
|
|
||||||
end, Ls)}}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
start(Port, Module, Opts) ->
|
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
|
case includes_deprecated_ssl_option(Opts) of
|
||||||
false ->
|
false ->
|
||||||
{ok, proc_lib:spawn_link(?MODULE, init,
|
{ok, proc_lib:spawn_link(?MODULE, init,
|
||||||
@ -130,6 +134,21 @@ accept(ListenSocket, Module, Opts) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
start_listener(Port, Module, Opts) ->
|
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,
|
ChildSpec = {Port,
|
||||||
{?MODULE, start, [Port, Module, Opts]},
|
{?MODULE, start, [Port, Module, Opts]},
|
||||||
transient,
|
transient,
|
||||||
@ -138,9 +157,13 @@ start_listener(Port, Module, Opts) ->
|
|||||||
[?MODULE]},
|
[?MODULE]},
|
||||||
supervisor:start_child(ejabberd_listeners, ChildSpec).
|
supervisor:start_child(ejabberd_listeners, ChildSpec).
|
||||||
|
|
||||||
stop_listener(Port) ->
|
stop_listener(Port, Module) ->
|
||||||
supervisor:terminate_child(ejabberd_listeners, Port),
|
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) ->
|
add_listener(Port, Module, Opts) ->
|
||||||
Ports = case ejabberd_config:get_local_option(listen) of
|
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),
|
ejabberd_config:add_local_option(listen, Ports2),
|
||||||
start_listener(Port, Module, Opts).
|
start_listener(Port, Module, Opts).
|
||||||
|
|
||||||
delete_listener(Port) ->
|
delete_listener(Port, Module) ->
|
||||||
Ports = case ejabberd_config:get_local_option(listen) of
|
Ports = case ejabberd_config:get_local_option(listen) of
|
||||||
undefined ->
|
undefined ->
|
||||||
[];
|
[];
|
||||||
@ -163,5 +186,5 @@ delete_listener(Port) ->
|
|||||||
end,
|
end,
|
||||||
Ports1 = lists:keydelete(Port, 1, Ports),
|
Ports1 = lists:keydelete(Port, 1, Ports),
|
||||||
ejabberd_config:add_local_option(listen, Ports1),
|
ejabberd_config:add_local_option(listen, Ports1),
|
||||||
stop_listener(Port).
|
stop_listener(Port, Module).
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@
|
|||||||
remove_connection/3,
|
remove_connection/3,
|
||||||
dirty_get_connections/0,
|
dirty_get_connections/0,
|
||||||
allow_host/2,
|
allow_host/2,
|
||||||
ctl_process/2
|
incoming_s2s_number/0,
|
||||||
|
outgoing_s2s_number/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
@ -49,7 +50,7 @@
|
|||||||
-include_lib("exmpp/include/exmpp.hrl").
|
-include_lib("exmpp/include/exmpp.hrl").
|
||||||
|
|
||||||
-include("ejabberd.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, 1).
|
||||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
|
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
|
||||||
@ -182,10 +183,7 @@ init([]) ->
|
|||||||
{attributes, record_info(fields, s2s)}]),
|
{attributes, record_info(fields, s2s)}]),
|
||||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||||
mnesia:subscribe(system),
|
mnesia:subscribe(system),
|
||||||
ejabberd_ctl:register_commands(
|
ejabberd_commands:register_commands(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),
|
|
||||||
{ok, #state{}}.
|
{ok, #state{}}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
@ -249,6 +247,7 @@ handle_info(_Info, State) ->
|
|||||||
%% The return value is ignored.
|
%% The return value is ignored.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _State) ->
|
||||||
|
ejabberd_commands:unregister_commands(commands()),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
@ -453,16 +452,35 @@ is_subdomain(Domain1, Domain2) ->
|
|||||||
send_element(Pid, El) ->
|
send_element(Pid, El) ->
|
||||||
Pid ! {send_element, El}.
|
Pid ! {send_element, El}.
|
||||||
|
|
||||||
ctl_process(_Val, ["incoming-s2s-number"]) ->
|
|
||||||
N = length(supervisor:which_children(ejabberd_s2s_in_sup)),
|
%%%----------------------------------------------------------------------
|
||||||
?PRINT("~p~n", [N]),
|
%%% ejabberd commands
|
||||||
{stop, ?STATUS_SUCCESS};
|
|
||||||
ctl_process(_Val, ["outgoing-s2s-number"]) ->
|
commands() ->
|
||||||
N = length(supervisor:which_children(ejabberd_s2s_out_sup)),
|
[
|
||||||
?PRINT("~p~n", [N]),
|
#ejabberd_commands{name = incoming_s2s_number,
|
||||||
{stop, ?STATUS_SUCCESS};
|
tags = [stats, s2s],
|
||||||
ctl_process(Val, _Args) ->
|
desc = "Number of incoming s2s connections on the node",
|
||||||
Val.
|
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() ->
|
update_tables() ->
|
||||||
case catch mnesia:table_info(s2s, type) of
|
case catch mnesia:table_info(s2s, type) of
|
||||||
|
@ -46,7 +46,9 @@
|
|||||||
register_iq_handler/4,
|
register_iq_handler/4,
|
||||||
register_iq_handler/5,
|
register_iq_handler/5,
|
||||||
unregister_iq_handler/2,
|
unregister_iq_handler/2,
|
||||||
ctl_process/2,
|
connected_users/0,
|
||||||
|
connected_users_number/0,
|
||||||
|
user_resources/2,
|
||||||
get_session_pid/3,
|
get_session_pid/3,
|
||||||
get_user_info/3,
|
get_user_info/3,
|
||||||
get_user_ip/3
|
get_user_ip/3
|
||||||
@ -59,7 +61,7 @@
|
|||||||
-include_lib("exmpp/include/exmpp.hrl").
|
-include_lib("exmpp/include/exmpp.hrl").
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("ejabberd_ctl.hrl").
|
-include("ejabberd_commands.hrl").
|
||||||
|
|
||||||
-record(session, {sid, usr, us, priority, info}).
|
-record(session, {sid, usr, us, priority, info}).
|
||||||
-record(state, {}).
|
-record(state, {}).
|
||||||
@ -269,11 +271,7 @@ init([]) ->
|
|||||||
ejabberd_hooks:add(remove_user, Host,
|
ejabberd_hooks:add(remove_user, Host,
|
||||||
ejabberd_sm, disconnect_removed_user, 100)
|
ejabberd_sm, disconnect_removed_user, 100)
|
||||||
end, ?MYHOSTS),
|
end, ?MYHOSTS),
|
||||||
ejabberd_ctl:register_commands(
|
ejabberd_commands:register_commands(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),
|
|
||||||
|
|
||||||
{ok, #state{}}.
|
{ok, #state{}}.
|
||||||
|
|
||||||
@ -353,6 +351,7 @@ handle_info(_Info, State) ->
|
|||||||
%% The return value is ignored.
|
%% The return value is ignored.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _State) ->
|
||||||
|
ejabberd_commands:unregister_commands(commands()),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
@ -670,27 +669,46 @@ process_iq(From, To, Packet) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
ctl_process(_Val, ["connected-users"]) ->
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
USRs = dirty_get_sessions_list(),
|
%%% ejabberd commands
|
||||||
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.
|
|
||||||
|
|
||||||
|
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() ->
|
update_tables() ->
|
||||||
case catch mnesia:table_info(session, attributes) of
|
case catch mnesia:table_info(session, attributes) of
|
||||||
|
@ -77,6 +77,8 @@ start(Module, SockMod, Socket, Opts) ->
|
|||||||
{error, _Reason} ->
|
{error, _Reason} ->
|
||||||
SockMod:close(Socket)
|
SockMod:close(Socket)
|
||||||
end;
|
end;
|
||||||
|
independent ->
|
||||||
|
ok;
|
||||||
raw ->
|
raw ->
|
||||||
case Module:start({SockMod, Socket}, Opts) of
|
case Module:start({SockMod, Socket}, Opts) of
|
||||||
{ok, Pid} ->
|
{ok, Pid} ->
|
||||||
|
@ -99,21 +99,6 @@ init([]) ->
|
|||||||
infinity,
|
infinity,
|
||||||
supervisor,
|
supervisor,
|
||||||
[ejabberd_tmp_sup]},
|
[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 =
|
S2SOutSupervisor =
|
||||||
{ejabberd_s2s_out_sup,
|
{ejabberd_s2s_out_sup,
|
||||||
{ejabberd_tmp_sup, start_link,
|
{ejabberd_tmp_sup, start_link,
|
||||||
@ -130,14 +115,6 @@ init([]) ->
|
|||||||
infinity,
|
infinity,
|
||||||
supervisor,
|
supervisor,
|
||||||
[ejabberd_tmp_sup]},
|
[ejabberd_tmp_sup]},
|
||||||
HTTPSupervisor =
|
|
||||||
{ejabberd_http_sup,
|
|
||||||
{ejabberd_tmp_sup, start_link,
|
|
||||||
[ejabberd_http_sup, ejabberd_http]},
|
|
||||||
permanent,
|
|
||||||
infinity,
|
|
||||||
supervisor,
|
|
||||||
[ejabberd_tmp_sup]},
|
|
||||||
HTTPPollSupervisor =
|
HTTPPollSupervisor =
|
||||||
{ejabberd_http_poll_sup,
|
{ejabberd_http_poll_sup,
|
||||||
{ejabberd_tmp_sup, start_link,
|
{ejabberd_tmp_sup, start_link,
|
||||||
@ -171,11 +148,8 @@ init([]) ->
|
|||||||
S2S,
|
S2S,
|
||||||
Local,
|
Local,
|
||||||
ReceiverSupervisor,
|
ReceiverSupervisor,
|
||||||
C2SSupervisor,
|
|
||||||
S2SInSupervisor,
|
|
||||||
S2SOutSupervisor,
|
S2SOutSupervisor,
|
||||||
ServiceSupervisor,
|
ServiceSupervisor,
|
||||||
HTTPSupervisor,
|
|
||||||
HTTPPollSupervisor,
|
HTTPPollSupervisor,
|
||||||
IQSupervisor,
|
IQSupervisor,
|
||||||
FrontendSocketSupervisor,
|
FrontendSocketSupervisor,
|
||||||
|
@ -163,6 +163,23 @@ live ()
|
|||||||
$ERLANG_OPTS $ARGS \"$@\""
|
$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
|
# common control function
|
||||||
ctl ()
|
ctl ()
|
||||||
{
|
{
|
||||||
@ -175,20 +192,9 @@ ctl ()
|
|||||||
result=$?
|
result=$?
|
||||||
case $result in
|
case $result in
|
||||||
0) :;;
|
0) :;;
|
||||||
*)
|
1) :;;
|
||||||
echo ""
|
2) help;;
|
||||||
echo "Commands to start an ejabberd node:"
|
3) help;;
|
||||||
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 "";;
|
|
||||||
esac
|
esac
|
||||||
return $result
|
return $result
|
||||||
}
|
}
|
||||||
|
474
src/jlib.erl
474
src/jlib.erl
@ -27,32 +27,7 @@
|
|||||||
-module(jlib).
|
-module(jlib).
|
||||||
-author('alexey@process-one.net').
|
-author('alexey@process-one.net').
|
||||||
|
|
||||||
-export([make_result_iq_reply/1,
|
-export([parse_xdata_submit/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,
|
|
||||||
timestamp_to_iso/1,
|
timestamp_to_iso/1,
|
||||||
timestamp_to_xml/1,
|
timestamp_to_xml/1,
|
||||||
now_to_utc_string/1,
|
now_to_utc_string/1,
|
||||||
@ -68,410 +43,21 @@
|
|||||||
short_prepd_jid/1,
|
short_prepd_jid/1,
|
||||||
short_prepd_bare_jid/1]).
|
short_prepd_bare_jid/1]).
|
||||||
|
|
||||||
-include_lib("exmpp/include/exmpp_xml.hrl").
|
-include_lib("exmpp/include/exmpp.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.
|
|
||||||
|
|
||||||
|
|
||||||
replace_from_to_attrs(From, To, Attrs) ->
|
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
|
||||||
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}) ->
|
|
||||||
case exmpp_xml:get_attribute_from_list(Attrs, 'type', "") of
|
case exmpp_xml:get_attribute_from_list(Attrs, 'type', "") of
|
||||||
"submit" ->
|
"submit" ->
|
||||||
lists:reverse(parse_xdata_fields(Els, []));
|
lists:reverse(parse_xdata_fields(Els, []));
|
||||||
_ ->
|
_ ->
|
||||||
invalid
|
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.
|
end.
|
||||||
|
|
||||||
parse_xdata_fields([], Res) ->
|
parse_xdata_fields([], Res) ->
|
||||||
Res;
|
Res;
|
||||||
parse_xdata_fields([{xmlel, _, _, 'field', Attrs, SubEls} | Els],
|
parse_xdata_fields([#xmlel{name = 'field', attrs = Attrs, children = SubEls} |
|
||||||
Res) ->
|
Els], Res) ->
|
||||||
case exmpp_xml:get_attribute_from_list(Attrs, 'var', "") of
|
case exmpp_xml:get_attribute_from_list(Attrs, 'var', "") of
|
||||||
"" ->
|
"" ->
|
||||||
parse_xdata_fields(Els, Res);
|
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, []))},
|
Field = {Var, lists:reverse(parse_xdata_values(SubEls, []))},
|
||||||
parse_xdata_fields(Els, [Field | Res])
|
parse_xdata_fields(Els, [Field | Res])
|
||||||
end;
|
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_fields(Els, Res).
|
parse_xdata_fields(Els, Res).
|
||||||
|
|
||||||
parse_xdata_values([], Res) ->
|
parse_xdata_values([], Res) ->
|
||||||
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),
|
Val = exmpp_xml:get_cdata_from_list_as_list(SubEls),
|
||||||
parse_xdata_values(Els, [Val | Res]);
|
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) ->
|
||||||
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(
|
Timestamp = lists:flatten(
|
||||||
io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
|
io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
|
||||||
[Year, Month, Day, Hour, Minute, Second])),
|
[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).
|
'stamp', Timestamp).
|
||||||
|
|
||||||
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
|
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.
|
%% Empty fields are set to `undefined', not the empty string.
|
||||||
|
|
||||||
from_old_jid(#jid{user = Node, resource = Resource,
|
from_old_jid(#jid{node = Node, resource = Resource,
|
||||||
luser = LNode, lresource = LResource} = JID) ->
|
lnode = LNode, lresource = LResource} = JID) ->
|
||||||
{Node1, LNode1} = case Node of
|
{Node1, LNode1} = case Node of
|
||||||
"" -> {undefined, undefined};
|
"" -> {undefined, undefined};
|
||||||
_ -> {Node, LNode}
|
_ -> {Node, LNode}
|
||||||
@ -741,8 +305,8 @@ from_old_jid(#jid{user = Node, resource = Resource,
|
|||||||
"" -> {undefined, undefined};
|
"" -> {undefined, undefined};
|
||||||
_ -> {Resource, LResource}
|
_ -> {Resource, LResource}
|
||||||
end,
|
end,
|
||||||
JID#jid{user = Node1, resource = Resource1,
|
JID#jid{node = Node1, resource = Resource1,
|
||||||
luser = LNode1, lresource = LResource1}.
|
lnode = LNode1, lresource = LResource1}.
|
||||||
|
|
||||||
%% @spec (JID) -> New_JID
|
%% @spec (JID) -> New_JID
|
||||||
%% JID = 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'.
|
%% Empty fields are set to the empty string, not `undefined'.
|
||||||
|
|
||||||
to_old_jid(#jid{user = Node, resource = Resource,
|
to_old_jid(#jid{node = Node, resource = Resource,
|
||||||
luser = LNode, lresource = LResource} = JID) ->
|
lnode = LNode, lresource = LResource} = JID) ->
|
||||||
{Node1, LNode1} = case Node of
|
{Node1, LNode1} = case Node of
|
||||||
undefined -> {"", ""};
|
undefined -> {"", ""};
|
||||||
_ -> {Node, LNode}
|
_ -> {Node, LNode}
|
||||||
@ -761,19 +325,19 @@ to_old_jid(#jid{user = Node, resource = Resource,
|
|||||||
undefined -> {"", ""};
|
undefined -> {"", ""};
|
||||||
_ -> {Resource, LResource}
|
_ -> {Resource, LResource}
|
||||||
end,
|
end,
|
||||||
JID#jid{user = Node1, resource = Resource1,
|
JID#jid{node = Node1, resource = Resource1,
|
||||||
luser = LNode1, lresource = LResource1}.
|
lnode = LNode1, lresource = LResource1}.
|
||||||
|
|
||||||
short_jid(JID) ->
|
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) ->
|
short_bare_jid(JID) ->
|
||||||
Bare_JID = exmpp_jid:jid_to_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) ->
|
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) ->
|
short_prepd_bare_jid(JID) ->
|
||||||
Bare_JID = exmpp_jid:jid_to_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 ->
|
false ->
|
||||||
{error, 'bad-request'};
|
{error, 'bad-request'};
|
||||||
{value, {_, [String]}} ->
|
{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} ->
|
{badrpc, _Reason} ->
|
||||||
{error, 'internal-server-error'};
|
{error, 'internal-server-error'};
|
||||||
{error, _Reason} ->
|
{error, _Reason} ->
|
||||||
|
@ -39,7 +39,8 @@
|
|||||||
remove_old_messages/1,
|
remove_old_messages/1,
|
||||||
remove_user/2,
|
remove_user/2,
|
||||||
webadmin_page/3,
|
webadmin_page/3,
|
||||||
webadmin_user/4]).
|
webadmin_user/4,
|
||||||
|
webadmin_user_parse_query/5]).
|
||||||
|
|
||||||
-include_lib("exmpp/include/exmpp.hrl").
|
-include_lib("exmpp/include/exmpp.hrl").
|
||||||
|
|
||||||
@ -75,6 +76,8 @@ start(Host, Opts) ->
|
|||||||
?MODULE, webadmin_page, 50),
|
?MODULE, webadmin_page, 50),
|
||||||
ejabberd_hooks:add(webadmin_user, Host,
|
ejabberd_hooks:add(webadmin_user, Host,
|
||||||
?MODULE, webadmin_user, 50),
|
?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),
|
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
|
||||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
spawn(?MODULE, init, [MaxOfflineMsgs])).
|
spawn(?MODULE, init, [MaxOfflineMsgs])).
|
||||||
@ -143,6 +146,8 @@ stop(Host) ->
|
|||||||
?MODULE, webadmin_page, 50),
|
?MODULE, webadmin_page, 50),
|
||||||
ejabberd_hooks:delete(webadmin_user, Host,
|
ejabberd_hooks:delete(webadmin_user, Host,
|
||||||
?MODULE, webadmin_user, 50),
|
?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),
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
exit(whereis(Proc), stop),
|
exit(whereis(Proc), stop),
|
||||||
{wait, Proc}.
|
{wait, Proc}.
|
||||||
@ -642,4 +647,24 @@ webadmin_user(Acc, User, Server, Lang) ->
|
|||||||
_ ->
|
_ ->
|
||||||
[?C("?")]
|
[?C("?")]
|
||||||
end,
|
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,
|
pop_offline_messages/3,
|
||||||
remove_user/2,
|
remove_user/2,
|
||||||
webadmin_page/3,
|
webadmin_page/3,
|
||||||
webadmin_user/4]).
|
webadmin_user/4,
|
||||||
|
webadmin_user_parse_query/5]).
|
||||||
|
|
||||||
-include_lib("exmpp/include/exmpp.hrl").
|
-include_lib("exmpp/include/exmpp.hrl").
|
||||||
|
|
||||||
@ -69,6 +70,8 @@ start(Host, Opts) ->
|
|||||||
?MODULE, webadmin_page, 50),
|
?MODULE, webadmin_page, 50),
|
||||||
ejabberd_hooks:add(webadmin_user, Host,
|
ejabberd_hooks:add(webadmin_user, Host,
|
||||||
?MODULE, webadmin_user, 50),
|
?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),
|
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
|
||||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
spawn(?MODULE, init, [Host, MaxOfflineMsgs])).
|
spawn(?MODULE, init, [Host, MaxOfflineMsgs])).
|
||||||
@ -151,6 +154,8 @@ stop(Host) ->
|
|||||||
?MODULE, webadmin_page, 50),
|
?MODULE, webadmin_page, 50),
|
||||||
ejabberd_hooks:delete(webadmin_user, Host,
|
ejabberd_hooks:delete(webadmin_user, Host,
|
||||||
?MODULE, webadmin_user, 50),
|
?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),
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
exit(whereis(Proc), stop),
|
exit(whereis(Proc), stop),
|
||||||
ok.
|
ok.
|
||||||
@ -446,7 +451,22 @@ webadmin_user(Acc, User, Server, Lang) ->
|
|||||||
_ ->
|
_ ->
|
||||||
[?C("?")]
|
[?C("?")]
|
||||||
end,
|
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
|
%% mod_offline: number of messages quota management
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
-define(PROCNAME, ejabberd_mod_proxy65).
|
-define(PROCNAME, ejabberd_mod_proxy65).
|
||||||
|
|
||||||
start(Host, Opts) ->
|
start(Host, Opts) ->
|
||||||
|
mod_proxy65_service:add_listener(Host, Opts),
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
ChildSpec = {
|
ChildSpec = {
|
||||||
Proc, {?MODULE, start_link, [Host, Opts]},
|
Proc, {?MODULE, start_link, [Host, Opts]},
|
||||||
@ -50,6 +51,7 @@ start(Host, Opts) ->
|
|||||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||||
|
|
||||||
stop(Host) ->
|
stop(Host) ->
|
||||||
|
mod_proxy65_service:delete_listener(Host),
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||||
supervisor:delete_child(ejabberd_sup, Proc).
|
supervisor:delete_child(ejabberd_sup, Proc).
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
]).
|
]).
|
||||||
|
|
||||||
%% API.
|
%% API.
|
||||||
-export([start_link/2]).
|
-export([start_link/2, add_listener/2, delete_listener/1]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("jlib.hrl").
|
-include("jlib.hrl").
|
||||||
@ -55,28 +55,21 @@
|
|||||||
acl
|
acl
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%% Unused callbacks.
|
|
||||||
handle_cast(_Request, State) ->
|
%%%------------------------
|
||||||
{noreply, State}.
|
%%% gen_server callbacks
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
%%%------------------------
|
||||||
{ok, State}.
|
|
||||||
handle_call(_Request, _From, State) ->
|
|
||||||
{reply, ok, State}.
|
|
||||||
%%----------------
|
|
||||||
|
|
||||||
start_link(Host, Opts) ->
|
start_link(Host, Opts) ->
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||||
|
|
||||||
init([Host, Opts]) ->
|
init([Host, Opts]) ->
|
||||||
{IP, State} = parse_options(Host, Opts),
|
{_IP, State} = parse_options(Host, Opts),
|
||||||
NewOpts = [Host, {ip, IP} | Opts],
|
|
||||||
ejabberd_listener:add_listener(State#state.port, mod_proxy65_stream, NewOpts),
|
|
||||||
ejabberd_router:register_route(State#state.myhost),
|
ejabberd_router:register_route(State#state.myhost),
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
terminate(_Reason, #state{myhost=MyHost, port=Port}) ->
|
terminate(_Reason, #state{myhost=MyHost}) ->
|
||||||
catch ejabberd_listener:delete_listener(Port),
|
|
||||||
ejabberd_router:unregister_route(MyHost),
|
ejabberd_router:unregister_route(MyHost),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@ -93,10 +86,34 @@ handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) ->
|
|||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
{noreply, 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
|
%%% 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])
|
node_call(Type, publish_item, [Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload])
|
||||||
end
|
end
|
||||||
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 = [],
|
Reply = [],
|
||||||
case transaction(Host, Node, Action, sync_dirty) of
|
case transaction(Host, Node, Action, sync_dirty) of
|
||||||
{error, ?ERR_ITEM_NOT_FOUND} ->
|
{error, ?ERR_ITEM_NOT_FOUND} ->
|
||||||
|
@ -104,60 +104,15 @@ get_auth(Auth) ->
|
|||||||
unauthorized
|
unauthorized
|
||||||
end.
|
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) ->
|
make_xhtml(Els, Host, Lang) ->
|
||||||
Base = "/admin/server/" ++ Host ++ "/",
|
make_xhtml(Els, Host, cluster, Lang).
|
||||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]),
|
|
||||||
MenuItems2 = [?LI([?AC(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
%% @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],
|
{200, [html],
|
||||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
||||||
{"xml:lang", Lang},
|
{"xml:lang", Lang},
|
||||||
@ -166,7 +121,7 @@ make_xhtml(Els, Host, Lang) ->
|
|||||||
[?XCT("title", "ejabberd Web Admin"),
|
[?XCT("title", "ejabberd Web Admin"),
|
||||||
{xmlelement, "meta", [{"http-equiv", "Content-Type"},
|
{xmlelement, "meta", [{"http-equiv", "Content-Type"},
|
||||||
{"content", "text/html; charset=utf-8"}], []},
|
{"content", "text/html; charset=utf-8"}], []},
|
||||||
{xmlelement, "link", [{"href", "/admin/favicon.ico"},
|
{xmlelement, "link", [{"href", Base ++ "favicon.ico"},
|
||||||
{"type", "image/x-icon"},
|
{"type", "image/x-icon"},
|
||||||
{"rel", "shortcut icon"}], []},
|
{"rel", "shortcut icon"}], []},
|
||||||
{xmlelement, "link", [{"href", Base ++ "style.css"},
|
{xmlelement, "link", [{"href", Base ++ "style.css"},
|
||||||
@ -183,18 +138,7 @@ make_xhtml(Els, Host, Lang) ->
|
|||||||
?XAE("div",
|
?XAE("div",
|
||||||
[{"id", "navigation"}],
|
[{"id", "navigation"}],
|
||||||
[?XE("ul",
|
[?XE("ul",
|
||||||
[?LI([?XAE("div",
|
MenuItems
|
||||||
[{"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
|
|
||||||
)]),
|
)]),
|
||||||
?XAE("div",
|
?XAE("div",
|
||||||
[{"id", "content"}],
|
[{"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) ->
|
css(Host) ->
|
||||||
Base = case Host of
|
Base = get_base_path(Host, cluster),
|
||||||
global ->
|
|
||||||
"/admin/";
|
|
||||||
_ ->
|
|
||||||
"/admin/server/" ++ Host ++ "/"
|
|
||||||
end,
|
|
||||||
"
|
"
|
||||||
html,body {
|
html,body {
|
||||||
background: white;
|
background: white;
|
||||||
@ -298,7 +242,7 @@ html>body #container {
|
|||||||
|
|
||||||
#navigation ul {
|
#navigation ul {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 54px;
|
top: 65px;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 0 1px 1px 1px;
|
padding: 0 1px 1px 1px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -306,7 +250,7 @@ html>body #container {
|
|||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background: #d47911;
|
background: #d47911;
|
||||||
width: 13em;
|
width: 17em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navigation ul li {
|
#navigation ul li {
|
||||||
@ -340,9 +284,20 @@ html>body #container {
|
|||||||
background: #332;
|
background: #332;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navheadhost {
|
ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {
|
||||||
text-align: left;
|
text-align: center;
|
||||||
border-bottom: 2px solid #d47911;
|
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 {
|
#lastactivity li {
|
||||||
@ -550,7 +505,7 @@ h3 {
|
|||||||
#content {
|
#content {
|
||||||
font-family: Verdana, Arial, Helvetica, sans-serif;
|
font-family: Verdana, Arial, Helvetica, sans-serif;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding-left: 13em;
|
padding-left: 17em;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,11 +550,12 @@ logo_fill() ->
|
|||||||
"1c5dvhSU2BpKqBXl6R0ljYGS50R5zVC+tVD+vfE6YyUexE9x7g4AAAAASUVO"
|
"1c5dvhSU2BpKqBXl6R0ljYGS50R5zVC+tVD+vfE6YyUexE9x7g4AAAAASUVO"
|
||||||
"RK5CYII=").
|
"RK5CYII=").
|
||||||
|
|
||||||
|
|
||||||
process_admin(global,
|
process_admin(global,
|
||||||
#request{path = [],
|
#request{path = [],
|
||||||
lang = Lang}) ->
|
lang = Lang}) ->
|
||||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]),
|
Base = get_base_path(global, cluster),
|
||||||
MenuItems2 = [?LI([?AC("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
MenuItems2 = make_menu_items(global, cluster, Base, Lang),
|
||||||
make_xhtml([?XCT("h1", "Administration"),
|
make_xhtml([?XCT("h1", "Administration"),
|
||||||
?XE("ul",
|
?XE("ul",
|
||||||
[?LI([?ACT("/admin/acls/", "Access Control Lists"), ?C(" "),
|
[?LI([?ACT("/admin/acls/", "Access Control Lists"), ?C(" "),
|
||||||
@ -616,9 +572,8 @@ process_admin(global,
|
|||||||
process_admin(Host,
|
process_admin(Host,
|
||||||
#request{path = [],
|
#request{path = [],
|
||||||
lang = Lang}) ->
|
lang = Lang}) ->
|
||||||
Base = "/admin/server/" ++ Host ++ "/",
|
Base = get_base_path(Host, cluster),
|
||||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]),
|
MenuItems2 = make_menu_items(Host, cluster, Base, Lang),
|
||||||
MenuItems2 = [?LI([?AC(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
|
||||||
make_xhtml([?XCT("h1", "Administration"),
|
make_xhtml([?XCT("h1", "Administration"),
|
||||||
?XE("ul",
|
?XE("ul",
|
||||||
[?LI([?ACT(Base ++ "acls/", "Access Control Lists"), ?C(" "),
|
[?LI([?ACT(Base ++ "acls/", "Access Control Lists"), ?C(" "),
|
||||||
@ -953,8 +908,13 @@ process_admin(Host,
|
|||||||
#request{path = ["user", U],
|
#request{path = ["user", U],
|
||||||
q = Query,
|
q = Query,
|
||||||
lang = Lang}) ->
|
lang = Lang}) ->
|
||||||
|
case ejabberd_auth:is_user_exists(U, Host) of
|
||||||
|
true ->
|
||||||
Res = user_info(U, Host, Query, Lang),
|
Res = user_info(U, Host, Query, Lang),
|
||||||
make_xhtml(Res, Host, Lang);
|
make_xhtml(Res, Host, Lang);
|
||||||
|
false ->
|
||||||
|
make_xhtml([?XCT("h1", "Not Found")], Host, Lang)
|
||||||
|
end;
|
||||||
|
|
||||||
process_admin(Host,
|
process_admin(Host,
|
||||||
#request{path = ["nodes"],
|
#request{path = ["nodes"],
|
||||||
@ -971,7 +931,7 @@ process_admin(Host,
|
|||||||
make_xhtml([?XCT("h1", "Node not found")], Host, Lang);
|
make_xhtml([?XCT("h1", "Node not found")], Host, Lang);
|
||||||
Node ->
|
Node ->
|
||||||
Res = get_node(Host, Node, NPath, Query, Lang),
|
Res = get_node(Host, Node, NPath, Query, Lang),
|
||||||
make_xhtml(Res, Host, Lang)
|
make_xhtml(Res, Host, Node, Lang)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
process_admin(Host, #request{lang = Lang} = Request) ->
|
process_admin(Host, #request{lang = Lang} = Request) ->
|
||||||
@ -1515,8 +1475,15 @@ user_info(User, Server, Query, Lang) ->
|
|||||||
|
|
||||||
|
|
||||||
user_parse_query(User, Server, Query) ->
|
user_parse_query(User, Server, Query) ->
|
||||||
case lists:keysearch("chpassword", 1, Query) of
|
lists:foldl(fun({Action, _Value}, Acc) when Acc == nothing ->
|
||||||
{value, _} ->
|
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
|
case lists:keysearch("password", 1, Query) of
|
||||||
{value, {_, undefined}} ->
|
{value, {_, undefined}} ->
|
||||||
error;
|
error;
|
||||||
@ -1526,14 +1493,13 @@ user_parse_query(User, Server, Query) ->
|
|||||||
_ ->
|
_ ->
|
||||||
error
|
error
|
||||||
end;
|
end;
|
||||||
_ ->
|
user_parse_query1("removeuser", User, Server, _Query) ->
|
||||||
case lists:keysearch("removeuser", 1, Query) of
|
|
||||||
{value, _} ->
|
|
||||||
ejabberd_auth:remove_user(User, Server),
|
ejabberd_auth:remove_user(User, Server),
|
||||||
ok;
|
ok;
|
||||||
false ->
|
user_parse_query1(Action, User, Server, Query) ->
|
||||||
nothing
|
case ejabberd_hooks:run_fold(webadmin_user_parse_query, Server, [], [Action, User, Server, Query]) of
|
||||||
end
|
[] -> nothing;
|
||||||
|
Res -> Res
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -1661,8 +1627,8 @@ search_running_node(SNode, [Node | Nodes]) ->
|
|||||||
|
|
||||||
get_node(global, Node, [], Query, Lang) ->
|
get_node(global, Node, [], Query, Lang) ->
|
||||||
Res = node_parse_query(Node, Query),
|
Res = node_parse_query(Node, Query),
|
||||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_node, [], [Node, Lang]),
|
Base = get_base_path(global, Node),
|
||||||
MenuItems2 = [?LI([?AC(MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
MenuItems2 = make_menu_items(global, Node, Base, Lang),
|
||||||
[?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++
|
[?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++
|
||||||
case Res of
|
case Res of
|
||||||
ok -> [?CT("Submitted"), ?P];
|
ok -> [?CT("Submitted"), ?P];
|
||||||
@ -1670,11 +1636,11 @@ get_node(global, Node, [], Query, Lang) ->
|
|||||||
nothing -> []
|
nothing -> []
|
||||||
end ++
|
end ++
|
||||||
[?XE("ul",
|
[?XE("ul",
|
||||||
[?LI([?ACT("db/", "Database")]),
|
[?LI([?ACT(Base ++ "db/", "Database")]),
|
||||||
?LI([?ACT("backup/", "Backup")]),
|
?LI([?ACT(Base ++ "backup/", "Backup")]),
|
||||||
?LI([?ACT("ports/", "Listened Ports")]),
|
?LI([?ACT(Base ++ "ports/", "Listened Ports")]),
|
||||||
?LI([?ACT("stats/", "Statistics")]),
|
?LI([?ACT(Base ++ "stats/", "Statistics")]),
|
||||||
?LI([?ACT("update/", "Update")])
|
?LI([?ACT(Base ++ "update/", "Update")])
|
||||||
] ++ MenuItems2),
|
] ++ MenuItems2),
|
||||||
?XAE("form", [{"action", ""}, {"method", "post"}],
|
?XAE("form", [{"action", ""}, {"method", "post"}],
|
||||||
[?INPUTT("submit", "restart", "Restart"),
|
[?INPUTT("submit", "restart", "Restart"),
|
||||||
@ -1683,11 +1649,11 @@ get_node(global, Node, [], Query, Lang) ->
|
|||||||
];
|
];
|
||||||
|
|
||||||
get_node(Host, Node, [], _Query, Lang) ->
|
get_node(Host, Node, [], _Query, Lang) ->
|
||||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Node, Lang]),
|
Base = get_base_path(Host, Node),
|
||||||
MenuItems2 = [?LI([?AC(MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
MenuItems2 = make_menu_items(global, Node, Base, Lang),
|
||||||
[?XC("h1", ?T("Node ") ++ atom_to_list(Node)),
|
[?XC("h1", ?T("Node ") ++ atom_to_list(Node)),
|
||||||
?XE("ul",
|
?XE("ul",
|
||||||
[?LI([?ACT("modules/", "Modules")])] ++ MenuItems2)
|
[?LI([?ACT(Base ++ "modules/", "Modules")])] ++ MenuItems2)
|
||||||
];
|
];
|
||||||
|
|
||||||
get_node(global, Node, ["db"], Query, Lang) ->
|
get_node(global, Node, ["db"], Query, Lang) ->
|
||||||
@ -2022,7 +1988,7 @@ node_backup_parse_query(Node, Query) ->
|
|||||||
rpc:call(Node, mnesia,
|
rpc:call(Node, mnesia,
|
||||||
install_fallback, [Path]);
|
install_fallback, [Path]);
|
||||||
"dump" ->
|
"dump" ->
|
||||||
rpc:call(Node, ejabberd_ctl,
|
rpc:call(Node, ejabberd_admin,
|
||||||
dump_to_textfile, [Path]);
|
dump_to_textfile, [Path]);
|
||||||
"load" ->
|
"load" ->
|
||||||
rpc:call(Node, mnesia,
|
rpc:call(Node, mnesia,
|
||||||
@ -2087,7 +2053,7 @@ node_ports_to_xhtml(Ports, Lang) ->
|
|||||||
|
|
||||||
node_ports_parse_query(Node, Ports, Query) ->
|
node_ports_parse_query(Node, Ports, Query) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun({Port, _Module1, _Opts1}) ->
|
fun({Port, Module1, _Opts1}) ->
|
||||||
SPort = integer_to_list(Port),
|
SPort = integer_to_list(Port),
|
||||||
case lists:keysearch("add" ++ SPort, 1, Query) of
|
case lists:keysearch("add" ++ SPort, 1, Query) of
|
||||||
{value, _} ->
|
{value, _} ->
|
||||||
@ -2097,13 +2063,13 @@ node_ports_parse_query(Node, Ports, Query) ->
|
|||||||
Module = list_to_atom(SModule),
|
Module = list_to_atom(SModule),
|
||||||
{ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
|
{ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
|
||||||
{ok, Opts} = erl_parse:parse_term(Tokens),
|
{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]),
|
rpc:call(Node, ejabberd_listener, add_listener, [Port, Module, Opts]),
|
||||||
throw(submitted);
|
throw(submitted);
|
||||||
_ ->
|
_ ->
|
||||||
case lists:keysearch("delete" ++ SPort, 1, Query) of
|
case lists:keysearch("delete" ++ SPort, 1, Query) of
|
||||||
{value, _} ->
|
{value, _} ->
|
||||||
rpc:call(Node, ejabberd_listener, delete_listener, [Port]),
|
rpc:call(Node, ejabberd_listener, delete_listener, [Port, Module1]),
|
||||||
throw(submitted);
|
throw(submitted);
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
@ -2276,3 +2242,134 @@ last_modified() ->
|
|||||||
{"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}.
|
{"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}.
|
||||||
cache_control_public() ->
|
cache_control_public() ->
|
||||||
{"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