25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-26 16:26:24 +01:00

Merge from trunk (r1613 to 1649).

PR:		EJABP-1

SVN Revision: 1650
This commit is contained in:
Jean-Sébastien Pédron 2008-10-13 10:11:19 +00:00
parent 3190c0ed6c
commit ab2b70f189
26 changed files with 1866 additions and 1040 deletions

View File

@ -1,3 +1,7 @@
2008-10-13 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
Merge from trunk (r1613 to 1649).
2008-10-13 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
* src/extauth.erl (call_port/2): Replace jlib:nameprep/1 by
@ -8,6 +12,68 @@
* src/ejd2odbc.erl, src/jd2ejd.erl: Convert to exmpp.
2008-10-13 Badlop <badlop@process-one.net>
* src/web/ejabberd_web_admin.erl: When requesting page of
nonexistent user, show 'Not Found' page (EJAB-771)
2008-10-12 Badlop <badlop@process-one.net>
* src/web/ejabberd_web_admin.erl: Run new hook
webadmin_user_parse_query when POST in web admin user
page (thanks to Oleg Palij)(EJAB-747)
* src/mod_offline.erl: Add button "Remove All Offline Messages" in
a user page (thanks to Oleg Palij)(EJAB-747)
* src/mod_offline_odbc.erl: Likewise
* src/web/ejabberd_web_admin.erl: Improve Web Admin navigation
menu for vhosts and nodes (EJAB-734)
* doc/guide.tex: Explain the new ejabberdctl command 'help'
* doc/guide.html: Likewise
* src/mod_configure.erl: Update calls from ctl to
commands (EJAB-694)
* src/web/ejabberd_web_admin.erl: Likewise
* src/ejabberd_sm.erl: Update from ctl to commands (EJAB-694)
* src/ejabberd_s2s.erl: Likewise
* src/ejabberd_auth.erl: Update from ctl to commands (EJAB-694)
* src/ejabberd_auth_internal.erl: Likewise
* src/ejabberd_auth_ldap.erl: Likewise
* src/ejabberd_auth_odbc.erl: Likewise
* src/ejabberdctl.template: Move help print to a separate
function (EJAB-694)
* src/ejabberd_ctl.erl: Add frontend support for
commands (EJAB-694). Categorization and sorting of commands in
ejabberd_ctl help (EJAB-313). Lines in command line help of length
80, and text formatting (EJAB-473)
* src/ejabberd_app.erl: Initialize ejabberd_commands and start
ejabbed_admin (EJAB-694)
* src/ejabberd_admin.erl: Implement commands from old
ejabberd_ctl (EJAB-694)
* src/ejabberd_commands.erl: New 'ejabberd commands': separate
command definition and calling interface (EJAB-694)
* src/ejabberd_commands.hrl: Likewise
* src/mod_proxy65/mod_proxy65.erl: Update so the listener starts
correctly (EJAB-303)
* src/mod_proxy65/mod_proxy65_service.erl: Likewise
* src/ejabberd_app.erl: Start listeners explicitely at server
start after everything else (EJAB-303). Implement support in
ejabberd for 'independent listeners', which handle their
connections themselves: gen_tcp:listen, etc.
* src/ejabberd_listener.erl: Likewise
* src/ejabberd_socket.erl: Likewise
* src/ejabberd_sup.erl: Likewise
2008-10-10 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
* src/ejabberd_c2s.erl (is_auth_packet/1): Fix a bug where
@ -63,6 +129,11 @@
* src/mod_private.erl, src/mod_private_odbc.erl, src/mod_version.erl:
Convert to exmpp. Thanks to Pablo Polvorin!
2008-10-07 Christophe Romain <christophe.romain@process-one.net>
* src/mod_pubsub/mod_pubsub.erl: uncomment pubsub_publish_item hook
call (EJAB-765)
2008-10-07 Jerome Sautret <jerome.sautret@process-one.net>
* src/mod_roster_odbc.erl: fix MySQL multiple requests issue.

View File

@ -413,8 +413,8 @@ you can execute <TT>ejabberdctl</TT> with either that system account or root.</P
</P><PRE CLASS="verbatim">ejabberdctl start
ejabberdctl status
Node ejabberd@localhost is started. Status: started
ejabberd is running
The node ejabberd@localhost is started with status: started
ejabberd is running in that node
ejabberdctl stop
</PRE><P>If <TT>ejabberd</TT> doesn&#X2019;t start correctly and a crash dump is generated,
@ -2894,7 +2894,8 @@ the available parameters are:
<TT>ejabberdctl</TT> shows all the available commands in that server.
The more interesting ones are:
</P><DL CLASS="description"><DT CLASS="dt-description">
<B><TT>status</TT></B></DT><DD CLASS="dd-description"> Check the status of the <TT>ejabberd</TT> server.
<B><TT>help</TT></B></DT><DD CLASS="dd-description"> Get help about ejabberdctl or any available command. Try <TT>ejabberdctl help help</TT>.
</DD><DT CLASS="dt-description"><B><TT>status</TT></B></DT><DD CLASS="dd-description"> Check the status of the <TT>ejabberd</TT> server.
</DD><DT CLASS="dt-description"><B><TT>stop</TT></B></DT><DD CLASS="dd-description"> Stop the <TT>ejabberd</TT> server which is running in the machine.
</DD><DT CLASS="dt-description"><B><TT>reopen-log</TT></B></DT><DD CLASS="dd-description"> If you use a tool to rotate logs, you have to configure it
so that this command is executed after each rotation.

View File

@ -425,8 +425,8 @@ Usage example:
ejabberdctl start
ejabberdctl status
Node ejabberd@localhost is started. Status: started
ejabberd is running
The node ejabberd@localhost is started with status: started
ejabberd is running in that node
ejabberdctl stop
\end{verbatim}
@ -3712,6 +3712,7 @@ If there is an \ejabberd{} server running in the system,
\term{ejabberdctl} shows all the available commands in that server.
The more interesting ones are:
\begin{description}
\titem{help} Get help about ejabberdctl or any available command. Try \term{ejabberdctl help help}.
\titem{status} Check the status of the \ejabberd{} server.
\titem{stop} Stop the \ejabberd{} server which is running in the machine.
\titem{reopen-log} If you use a tool to rotate logs, you have to configure it

View File

@ -1,11 +1,7 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_admin.erl
%%% Author : Mickael Remond <mremond@process-one.net>
%%% Description : This module gathers admin functions used by different
%%% access method:
%%% - ejabberdctl command-line tool
%%% - web admin interface
%%% - adhoc mode
%%% Purpose : Administrative functions and commands
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
@ -31,9 +27,237 @@
-module(ejabberd_admin).
-author('mickael.remond@process-one.net').
-export([restore/1]).
-export([start/0, stop/0,
%% Server
status/0, reopen_log/0,
%% Accounts
register/3, unregister/2,
registered_users/1,
%% Migration
import_file/1, import_dir/1,
%% Purge DB
delete_expired_messages/0, delete_old_messages/1,
%% Mnesia
backup_mnesia/1, restore_mnesia/1,
dump_mnesia/1, load_mnesia/1,
install_fallback_mnesia/1,
dump_to_textfile/1,
restore/1 % Still used by some modules
]).
-include("ejabberd.hrl").
-include("ejabberd_commands.hrl").
start() ->
ejabberd_commands:register_commands(commands()).
stop() ->
ejabberd_commands:unregister_commands(commands()).
%%%
%%% ejabberd commands
%%%
commands() ->
[
%% The commands status, stop and restart are implemented also in ejabberd_ctl
%% They are defined here so that other interfaces can use them too
#ejabberd_commands{name = status, tags = [server],
desc = "Get status of the ejabberd server",
module = ?MODULE, function = status,
args = [], result = {res, restuple}},
#ejabberd_commands{name = stop, tags = [server],
desc = "Stop ejabberd gracefully",
module = init, function = stop,
args = [], result = {res, rescode}},
#ejabberd_commands{name = restart, tags = [server],
desc = "Restart ejabberd gracefully",
module = init, function = restart,
args = [], result = {res, rescode}},
#ejabberd_commands{name = reopen_log, tags = [logs, server],
desc = "Reopen the log files",
module = ?MODULE, function = reopen_log,
args = [], result = {res, rescode}},
#ejabberd_commands{name = register, tags = [accounts],
desc = "Register a user",
module = ?MODULE, function = register,
args = [{user, string}, {host, string}, {password, string}],
result = {res, restuple}},
#ejabberd_commands{name = unregister, tags = [accounts],
desc = "Unregister a user",
module = ?MODULE, function = unregister,
args = [{user, string}, {host, string}],
result = {res, restuple}},
#ejabberd_commands{name = registered_users, tags = [accounts],
desc = "List all registered users in HOST",
module = ?MODULE, function = registered_users,
args = [{host, string}],
result = {users, {list, {username, string}}}},
#ejabberd_commands{name = import_file, tags = [mnesia],
desc = "Import user data from jabberd-1.4 spool file",
module = ?MODULE, function = import_file,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = import_dir, tags = [mnesia],
desc = "Import user data from jabberd-1.4 spool dir",
module = ?MODULE, function = import_dir,
args = [{file, string}],
result = {res, restuple}},
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
desc = "Delete expired offline messages from database",
module = ?MODULE, function = delete_expired_messages,
args = [], result = {res, rescode}},
#ejabberd_commands{name = delete_old_messages, tags = [purge],
desc = "Delete offline messages older than DAYS",
module = ?MODULE, function = delete_old_messages,
args = [{days, integer}], result = {res, rescode}},
#ejabberd_commands{name = backup, tags = [mnesia],
desc = "Store the database to backup file",
module = ?MODULE, function = backup_mnesia,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = restore, tags = [mnesia],
desc = "Restore the database from backup file",
module = ?MODULE, function = restore_mnesia,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump, tags = [mnesia],
desc = "Dump the database to text file",
module = ?MODULE, function = dump_mnesia,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = load, tags = [mnesia],
desc = "Restore the database from text file",
module = ?MODULE, function = load_mnesia,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = install_fallback, tags = [mnesia],
desc = "Install the database from a fallback file",
module = ?MODULE, function = install_fallback_mnesia,
args = [{file, string}], result = {res, restuple}}
].
%%%
%%% Server management
%%%
status() ->
{InternalStatus, ProvidedStatus} = init:get_status(),
String1 = io_lib:format("The node ~p is ~p. Status: ~p",
[node(), InternalStatus, ProvidedStatus]),
{Is_running, String2} =
case lists:keysearch(ejabberd, 1, application:which_applications()) of
false ->
{ejabberd_not_running, "ejabberd is not running in that node."};
{value, {_, _, Version}} ->
{ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
end,
{Is_running, String1 ++ String2}.
reopen_log() ->
ejabberd_hooks:run(reopen_log_hook, []),
%% TODO: Use the Reopen log API for logger_h ?
ejabberd_logger_h:reopen_log(),
ok.
%%%
%%% Account management
%%%
register(User, Host, Password) ->
case ejabberd_auth:try_register(User, Host, Password) of
{atomic, ok} ->
{ok, io_lib:format("User ~s@~s succesfully registered", [User, Host])};
{atomic, exists} ->
String = io_lib:format("User ~s@~s already registered at node ~p",
[User, Host, node()]),
{exists, String};
{error, Reason} ->
String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
[User, Host, node(), Reason]),
{cannot_register, String}
end.
unregister(User, Host) ->
ejabberd_auth:remove_user(User, Host),
{ok, ""}.
registered_users(Host) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort(Users),
lists:map(fun({U, _S}) -> U end, SUsers).
%%%
%%% Migration management
%%%
import_file(Path) ->
case jd2ejd:import_file(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't import jabberd 1.4 spool file ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_import_file, String}
end.
import_dir(Path) ->
case jd2ejd:import_dir(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't import jabberd 1.4 spool dir ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_import_dir, String}
end.
%%%
%%% Purge DB
%%%
delete_expired_messages() ->
{atomic, ok} = mod_offline:remove_expired_messages(),
ok.
delete_old_messages(Days) ->
{atomic, _} = mod_offline:remove_old_messages(Days),
ok.
%%%
%%% Mnesia management
%%%
backup_mnesia(Path) ->
case mnesia:backup(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_backup, String}
end.
restore_mnesia(Path) ->
case ejabberd_admin:restore(Path) of
{atomic, _} ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't restore backup from ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_restore, String};
{aborted,{no_exists,Table}} ->
String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
[filename:absname(Path), node(), Table]),
{table_not_exists, String};
{aborted,enoent} ->
String = io_lib:format("Can't restore backup from ~p at node ~p: File not found.",
[filename:absname(Path), node()]),
{file_not_found, String}
end.
%% Mnesia database restore
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
@ -71,3 +295,67 @@ module_tables(mod_roster) -> [roster];
module_tables(mod_shared_roster) -> [sr_group, sr_user];
module_tables(mod_vcard) -> [vcard, vcard_search];
module_tables(_Other) -> [].
dump_mnesia(Path) ->
case dump_to_textfile(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_dump, String}
end.
dump_to_textfile(File) ->
dump_to_textfile(mnesia:system_info(is_running), file:open(File, [write])).
dump_to_textfile(yes, {ok, F}) ->
Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
Tabs = lists:filter(
fun(T) ->
case mnesia:table_info(T, storage_type) of
disc_copies -> true;
disc_only_copies -> true;
_ -> false
end
end, Tabs1),
Defs = lists:map(
fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
{attributes, mnesia:table_info(T, attributes)}]}
end,
Tabs),
io:format(F, "~p.~n", [{tables, Defs}]),
lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
file:close(F);
dump_to_textfile(_, {ok, F}) ->
file:close(F),
{error, mnesia_not_running};
dump_to_textfile(_, {error, Reason}) ->
{error, Reason}.
dump_tab(F, T) ->
W = mnesia:table_info(T, wild_pattern),
{atomic,All} = mnesia:transaction(
fun() -> mnesia:match_object(T, W, read) end),
lists:foreach(
fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
load_mnesia(Path) ->
case mnesia:load_textfile(Path) of
{atomic, ok} ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_load, String}
end.
install_fallback_mnesia(Path) ->
case mnesia:install_fallback(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_fallback, String}
end.

View File

@ -29,7 +29,7 @@
-behaviour(application).
-export([start/2, prep_stop/1, stop/1, init/0]).
-export([start_modules/0,start/2, prep_stop/1, stop/1, init/0]).
-include("ejabberd.hrl").
@ -47,6 +47,8 @@ start(normal, _Args) ->
translate:start(),
acl:start(),
ejabberd_ctl:init(),
ejabberd_commands:init(),
ejabberd_admin:start(),
gen_mod:start(),
ejabberd_config:start(),
ejabberd_check:config(),
@ -61,6 +63,7 @@ start(normal, _Args) ->
%eprof:profile([self()]),
%fprof:trace(start, "/tmp/fprof"),
start_modules(),
ejabberd_listener:start_listeners(),
Sup;
start(_, _) ->
{error, badarg}.
@ -70,6 +73,7 @@ start(_, _) ->
%% before shutting down the processes of the application.
prep_stop(State) ->
stop_modules(),
ejabberd_admin:stop(),
State.
%% All the processes were killed when this function is called

View File

@ -49,14 +49,12 @@
is_user_exists_in_other_modules/3,
remove_user/2,
remove_user/3,
plain_password_required/1,
ctl_process_get_registered/3
plain_password_required/1
]).
-export([auth_modules/1]).
-include("ejabberd.hrl").
-include("ejabberd_ctl.hrl").
%%%----------------------------------------------------------------------
%%% API
@ -265,15 +263,6 @@ remove_user(User, Server, Password) ->
M:remove_user(User, Server, Password)
end, auth_modules(Server)).
ctl_process_get_registered(_Val, Host, ["registered-users"]) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
NewLine = io_lib:format("~n", []),
SUsers = lists:sort(Users),
FUsers = lists:map(fun({U, _S}) -> [U, NewLine] end, SUsers),
?PRINT("~s", [FUsers]),
{stop, ?STATUS_SUCCESS};
ctl_process_get_registered(Val, _Host, _Args) ->
Val.
%%%----------------------------------------------------------------------
%%% Internal functions

View File

@ -53,14 +53,10 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
start(_Host) ->
mnesia:create_table(passwd, [{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
update_table(),
ejabberd_ctl:register_commands(
Host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered),
ok.
plain_password_required() ->

View File

@ -112,11 +112,8 @@ start_link(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
terminate(_Reason, State) ->
ejabberd_ctl:unregister_commands(
State#state.host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered).
terminate(_Reason, _State) ->
ok.
init(Host) ->
State = parse_options(Host),
@ -132,10 +129,6 @@ init(Host) ->
State#state.port,
State#state.dn,
State#state.password),
ejabberd_ctl:register_commands(
Host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered),
{ok, State}.
plain_password_required() ->

View File

@ -51,11 +51,7 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
ejabberd_ctl:register_commands(
Host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered),
start(_Host) ->
ok.
plain_password_required() ->

328
src/ejabberd_commands.erl Normal file
View 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
View 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.

File diff suppressed because it is too large Load Diff

View File

@ -29,10 +29,11 @@
-export([start_link/0, init/1, start/3,
init/3,
start_listeners/0,
start_listener/3,
stop_listener/1,
stop_listener/2,
add_listener/3,
delete_listener/1
delete_listener/2
]).
-include("ejabberd.hrl").
@ -42,24 +43,27 @@ start_link() ->
init(_) ->
{ok, {{one_for_one, 10, 1}, []}}.
start_listeners() ->
case ejabberd_config:get_local_option(listen) of
undefined ->
ignore;
Ls ->
{ok, {{one_for_one, 10, 1},
lists:map(
fun({Port, Module, Opts}) ->
{Port,
{?MODULE, start, [Port, Module, Opts]},
transient,
brutal_kill,
worker,
[?MODULE]}
end, Ls)}}
lists:map(
fun({Port, Module, Opts}) ->
start_listener(Port, Module, Opts)
end, Ls)
end.
start(Port, Module, Opts) ->
%% Check if the module is an ejabberd listener or an independent listener
case Module:socket_type() of
independent -> Module:start_listener(Port, Opts);
_ -> start_dependent(Port, Module, Opts)
end.
start_dependent(Port, Module, Opts) ->
case includes_deprecated_ssl_option(Opts) of
false ->
{ok, proc_lib:spawn_link(?MODULE, init,
@ -130,6 +134,21 @@ accept(ListenSocket, Module, Opts) ->
end.
start_listener(Port, Module, Opts) ->
start_module_sup(Port, Module),
start_listener_sup(Port, Module, Opts).
start_module_sup(_Port, Module) ->
Proc1 = gen_mod:get_module_proc("sup", Module),
ChildSpec1 =
{Proc1,
{ejabberd_tmp_sup, start_link, [Proc1, Module]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
catch supervisor:start_child(ejabberd_sup, ChildSpec1).
start_listener_sup(Port, Module, Opts) ->
ChildSpec = {Port,
{?MODULE, start, [Port, Module, Opts]},
transient,
@ -138,9 +157,13 @@ start_listener(Port, Module, Opts) ->
[?MODULE]},
supervisor:start_child(ejabberd_listeners, ChildSpec).
stop_listener(Port) ->
stop_listener(Port, Module) ->
supervisor:terminate_child(ejabberd_listeners, Port),
supervisor:delete_child(ejabberd_listeners, Port).
supervisor:delete_child(ejabberd_listeners, Port),
Proc1 = gen_mod:get_module_proc("sup", Module),
supervisor:terminate_child(ejabberd_sup, Proc1),
supervisor:delete_child(ejabberd_sup, Proc1).
add_listener(Port, Module, Opts) ->
Ports = case ejabberd_config:get_local_option(listen) of
@ -154,7 +177,7 @@ add_listener(Port, Module, Opts) ->
ejabberd_config:add_local_option(listen, Ports2),
start_listener(Port, Module, Opts).
delete_listener(Port) ->
delete_listener(Port, Module) ->
Ports = case ejabberd_config:get_local_option(listen) of
undefined ->
[];
@ -163,5 +186,5 @@ delete_listener(Port) ->
end,
Ports1 = lists:keydelete(Port, 1, Ports),
ejabberd_config:add_local_option(listen, Ports1),
stop_listener(Port).
stop_listener(Port, Module).

View File

@ -39,7 +39,8 @@
remove_connection/3,
dirty_get_connections/0,
allow_host/2,
ctl_process/2
incoming_s2s_number/0,
outgoing_s2s_number/0
]).
%% gen_server callbacks
@ -49,7 +50,7 @@
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("ejabberd_ctl.hrl").
-include("ejabberd_commands.hrl").
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
@ -182,10 +183,7 @@ init([]) ->
{attributes, record_info(fields, s2s)}]),
mnesia:add_table_copy(s2s, node(), ram_copies),
mnesia:subscribe(system),
ejabberd_ctl:register_commands(
[{"incoming-s2s-number", "print number of incoming s2s connections on the node"},
{"outgoing-s2s-number", "print number of outgoing s2s connections on the node"}],
?MODULE, ctl_process),
ejabberd_commands:register_commands(commands()),
{ok, #state{}}.
%%--------------------------------------------------------------------
@ -249,6 +247,7 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ejabberd_commands:unregister_commands(commands()),
ok.
%%--------------------------------------------------------------------
@ -453,16 +452,35 @@ is_subdomain(Domain1, Domain2) ->
send_element(Pid, El) ->
Pid ! {send_element, El}.
ctl_process(_Val, ["incoming-s2s-number"]) ->
N = length(supervisor:which_children(ejabberd_s2s_in_sup)),
?PRINT("~p~n", [N]),
{stop, ?STATUS_SUCCESS};
ctl_process(_Val, ["outgoing-s2s-number"]) ->
N = length(supervisor:which_children(ejabberd_s2s_out_sup)),
?PRINT("~p~n", [N]),
{stop, ?STATUS_SUCCESS};
ctl_process(Val, _Args) ->
Val.
%%%----------------------------------------------------------------------
%%% ejabberd commands
commands() ->
[
#ejabberd_commands{name = incoming_s2s_number,
tags = [stats, s2s],
desc = "Number of incoming s2s connections on the node",
module = ?MODULE, function = incoming_s2s_number,
args = [],
result = {s2s_incoming, integer}},
#ejabberd_commands{name = outgoing_s2s_number,
tags = [stats, s2s],
desc = "Number of outgoing s2s connections on the node",
module = ?MODULE, function = outgoing_s2s_number,
args = [],
result = {s2s_outgoing, integer}}
].
incoming_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_in_sup)).
outgoing_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_out_sup)).
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
update_tables() ->
case catch mnesia:table_info(s2s, type) of

View File

@ -46,7 +46,9 @@
register_iq_handler/4,
register_iq_handler/5,
unregister_iq_handler/2,
ctl_process/2,
connected_users/0,
connected_users_number/0,
user_resources/2,
get_session_pid/3,
get_user_info/3,
get_user_ip/3
@ -59,7 +61,7 @@
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("ejabberd_ctl.hrl").
-include("ejabberd_commands.hrl").
-record(session, {sid, usr, us, priority, info}).
-record(state, {}).
@ -269,11 +271,7 @@ init([]) ->
ejabberd_hooks:add(remove_user, Host,
ejabberd_sm, disconnect_removed_user, 100)
end, ?MYHOSTS),
ejabberd_ctl:register_commands(
[{"connected-users", "list all established sessions"},
{"connected-users-number", "print a number of established sessions"},
{"user-resources user server", "print user's connected resources"}],
?MODULE, ctl_process),
ejabberd_commands:register_commands(commands()),
{ok, #state{}}.
@ -353,6 +351,7 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ejabberd_commands:unregister_commands(commands()),
ok.
%%--------------------------------------------------------------------
@ -670,27 +669,46 @@ process_iq(From, To, Packet) ->
end.
ctl_process(_Val, ["connected-users"]) ->
USRs = dirty_get_sessions_list(),
NewLine = io_lib:format("~n", []),
SUSRs = lists:sort(USRs),
FUSRs = lists:map(fun({U, S, R}) -> [U, $@, S, $/, R, NewLine] end, SUSRs),
?PRINT("~s", [FUSRs]),
{stop, ?STATUS_SUCCESS};
ctl_process(_Val, ["connected-users-number"]) ->
N = length(dirty_get_sessions_list()),
?PRINT("~p~n", [N]),
{stop, ?STATUS_SUCCESS};
ctl_process(_Val, ["user-resources", User, Server]) ->
Resources = get_user_resources(User, Server),
NewLine = io_lib:format("~n", []),
SResources = lists:sort(Resources),
FResources = lists:map(fun(R) -> [R, NewLine] end, SResources),
?PRINT("~s", [FResources]),
{stop, ?STATUS_SUCCESS};
ctl_process(Val, _Args) ->
Val.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ejabberd commands
commands() ->
[
#ejabberd_commands{name = connected_users,
tags = [session],
desc = "List all established sessions",
module = ?MODULE, function = connected_users,
args = [],
result = {connected_users, {list, {sessions, string}}}},
#ejabberd_commands{name = connected_users_number,
tags = [session, stats],
desc = "Get the number of established sessions",
module = ?MODULE, function = connected_users_number,
args = [],
result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources,
tags = [session],
desc = "List user's connected resources",
module = ?MODULE, function = user_resources,
args = [{user, string}, {host, string}],
result = {resources, {list, {resource, string}}}}
].
connected_users() ->
USRs = dirty_get_sessions_list(),
SUSRs = lists:sort(USRs),
lists:map(fun({U, S, R}) -> [U, $@, S, $/, R] end, SUSRs).
connected_users_number() ->
length(dirty_get_sessions_list()).
user_resources(User, Server) ->
Resources = get_user_resources(User, Server),
lists:sort(Resources).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Update Mnesia tables
update_tables() ->
case catch mnesia:table_info(session, attributes) of

View File

@ -77,6 +77,8 @@ start(Module, SockMod, Socket, Opts) ->
{error, _Reason} ->
SockMod:close(Socket)
end;
independent ->
ok;
raw ->
case Module:start({SockMod, Socket}, Opts) of
{ok, Pid} ->

View File

@ -99,21 +99,6 @@ init([]) ->
infinity,
supervisor,
[ejabberd_tmp_sup]},
C2SSupervisor =
{ejabberd_c2s_sup,
{ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
S2SInSupervisor =
{ejabberd_s2s_in_sup,
{ejabberd_tmp_sup, start_link,
[ejabberd_s2s_in_sup, ejabberd_s2s_in]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
S2SOutSupervisor =
{ejabberd_s2s_out_sup,
{ejabberd_tmp_sup, start_link,
@ -130,14 +115,6 @@ init([]) ->
infinity,
supervisor,
[ejabberd_tmp_sup]},
HTTPSupervisor =
{ejabberd_http_sup,
{ejabberd_tmp_sup, start_link,
[ejabberd_http_sup, ejabberd_http]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
HTTPPollSupervisor =
{ejabberd_http_poll_sup,
{ejabberd_tmp_sup, start_link,
@ -171,11 +148,8 @@ init([]) ->
S2S,
Local,
ReceiverSupervisor,
C2SSupervisor,
S2SInSupervisor,
S2SOutSupervisor,
ServiceSupervisor,
HTTPSupervisor,
HTTPPollSupervisor,
IQSupervisor,
FrontendSocketSupervisor,

View File

@ -163,6 +163,23 @@ live ()
$ERLANG_OPTS $ARGS \"$@\""
}
help ()
{
echo ""
echo "Commands to start an ejabberd node:"
echo " start Start an ejabberd node in server mode"
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
echo " live Start an ejabberd node in live (interactive) mode"
echo ""
echo "Optional parameters when starting an ejabberd node:"
echo " --config file Config ejabberd: $EJABBERD_CONFIG_PATH"
echo " --ctl-config file Config ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
echo " --logs dir Directory for logs: $LOGS_DIR"
echo " --spool dir Database spool dir: $SPOOLDIR"
echo " --node nodename ejabberd node name: $ERLANG_NODE"
echo ""
}
# common control function
ctl ()
{
@ -175,20 +192,9 @@ ctl ()
result=$?
case $result in
0) :;;
*)
echo ""
echo "Commands to start an ejabberd node:"
echo " start Start an ejabberd node in server mode"
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
echo " live Start an ejabberd node in live (interactive) mode"
echo ""
echo "Optional parameters when starting an ejabberd node:"
echo " --config file Config file of ejabberd: $EJABBERD_CONFIG_PATH"
echo " --ctl-config file Config file of ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
echo " --logs dir Directory for logs: $LOGS_DIR"
echo " --spool dir Database spool dir: $SPOOLDIR"
echo " --node nodename ejabberd node name: $ERLANG_NODE"
echo "";;
1) :;;
2) help;;
3) help;;
esac
return $result
}

View File

@ -27,32 +27,7 @@
-module(jlib).
-author('alexey@process-one.net').
-export([make_result_iq_reply/1,
make_error_reply/3,
make_error_reply/2,
make_error_element/2,
make_correct_from_to_attrs/3,
replace_from_to_attrs/3,
replace_from_to/3,
remove_attr/2,
make_jid/3,
make_jid/1,
string_to_jid/1,
jid_to_string/1,
is_nodename/1,
tolower/1,
nodeprep/1,
nameprep/1,
resourceprep/1,
jid_tolower/1,
jid_remove_resource/1,
jid_replace_resource/2,
get_iq_namespace/1,
iq_query_info/1,
iq_query_or_response_info/1,
is_iq_request_type/1,
iq_to_xml/1,
parse_xdata_submit/1,
-export([parse_xdata_submit/1,
timestamp_to_iso/1,
timestamp_to_xml/1,
now_to_utc_string/1,
@ -68,410 +43,21 @@
short_prepd_jid/1,
short_prepd_bare_jid/1]).
-include_lib("exmpp/include/exmpp_xml.hrl").
-include("jlib.hrl").
%send_iq(From, To, ID, SubTags) ->
% ok.
make_result_iq_reply({xmlelement, Name, Attrs, SubTags}) ->
NewAttrs = make_result_iq_reply_attrs(Attrs),
{xmlelement, Name, NewAttrs, SubTags}.
make_result_iq_reply_attrs(Attrs) ->
To = xml:get_attr("to", Attrs),
From = xml:get_attr("from", Attrs),
Attrs1 = lists:keydelete("to", 1, Attrs),
Attrs2 = lists:keydelete("from", 1, Attrs1),
Attrs3 = case To of
{value, ToVal} ->
[{"from", ToVal} | Attrs2];
_ ->
Attrs2
end,
Attrs4 = case From of
{value, FromVal} ->
[{"to", FromVal} | Attrs3];
_ ->
Attrs3
end,
Attrs5 = lists:keydelete("type", 1, Attrs4),
Attrs6 = [{"type", "result"} | Attrs5],
Attrs6.
make_error_reply({xmlelement, Name, Attrs, SubTags}, Code, Desc) ->
NewAttrs = make_error_reply_attrs(Attrs),
{xmlelement, Name, NewAttrs, SubTags ++ [{xmlelement, "error",
[{"code", Code}],
[{xmlcdata, Desc}]}]}.
make_error_reply({xmlelement, Name, Attrs, SubTags}, Error) ->
NewAttrs = make_error_reply_attrs(Attrs),
{xmlelement, Name, NewAttrs, SubTags ++ [Error]}.
make_error_reply_attrs(Attrs) ->
To = xml:get_attr("to", Attrs),
From = xml:get_attr("from", Attrs),
Attrs1 = lists:keydelete("to", 1, Attrs),
Attrs2 = lists:keydelete("from", 1, Attrs1),
Attrs3 = case To of
{value, ToVal} ->
[{"from", ToVal} | Attrs2];
_ ->
Attrs2
end,
Attrs4 = case From of
{value, FromVal} ->
[{"to", FromVal} | Attrs3];
_ ->
Attrs3
end,
Attrs5 = lists:keydelete("type", 1, Attrs4),
Attrs6 = [{"type", "error"} | Attrs5],
Attrs6.
make_error_element(Code, Desc) ->
{xmlelement, "error",
[{"code", Code}],
[{xmlcdata, Desc}]}.
make_correct_from_to_attrs(From, To, Attrs) ->
Attrs1 = lists:keydelete("from", 1, Attrs),
Attrs2 = case xml:get_attr("to", Attrs) of
{value, _} ->
Attrs1;
_ ->
[{"to", To} | Attrs1]
end,
Attrs3 = [{"from", From} | Attrs2],
Attrs3.
-include_lib("exmpp/include/exmpp.hrl").
replace_from_to_attrs(From, To, Attrs) ->
Attrs1 = lists:keydelete("to", 1, Attrs),
Attrs2 = lists:keydelete("from", 1, Attrs1),
Attrs3 = [{"to", To} | Attrs2],
Attrs4 = [{"from", From} | Attrs3],
Attrs4.
replace_from_to(From, To, {xmlelement, Name, Attrs, Els}) ->
NewAttrs = replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
{xmlelement, Name, NewAttrs, Els}.
remove_attr(Attr, {xmlelement, Name, Attrs, Els}) ->
NewAttrs = lists:keydelete(Attr, 1, Attrs),
{xmlelement, Name, NewAttrs, Els}.
make_jid(User, Server, Resource) ->
case nodeprep(User) of
error -> error;
LUser ->
case nameprep(Server) of
error -> error;
LServer ->
case resourceprep(Resource) of
error -> error;
LResource ->
#jid{user = User,
server = Server,
resource = Resource,
luser = LUser,
lserver = LServer,
lresource = LResource}
end
end
end.
make_jid({User, Server, Resource}) ->
make_jid(User, Server, Resource).
string_to_jid(J) ->
string_to_jid1(J, "").
string_to_jid1([$@ | _J], "") ->
error;
string_to_jid1([$@ | J], N) ->
string_to_jid2(J, lists:reverse(N), "");
string_to_jid1([$/ | _J], "") ->
error;
string_to_jid1([$/ | J], N) ->
string_to_jid3(J, "", lists:reverse(N), "");
string_to_jid1([C | J], N) ->
string_to_jid1(J, [C | N]);
string_to_jid1([], "") ->
error;
string_to_jid1([], N) ->
make_jid("", lists:reverse(N), "").
%% Only one "@" is admitted per JID
string_to_jid2([$@ | _J], _N, _S) ->
error;
string_to_jid2([$/ | _J], _N, "") ->
error;
string_to_jid2([$/ | J], N, S) ->
string_to_jid3(J, N, lists:reverse(S), "");
string_to_jid2([C | J], N, S) ->
string_to_jid2(J, N, [C | S]);
string_to_jid2([], _N, "") ->
error;
string_to_jid2([], N, S) ->
make_jid(N, lists:reverse(S), "").
string_to_jid3([C | J], N, S, R) ->
string_to_jid3(J, N, S, [C | R]);
string_to_jid3([], N, S, R) ->
make_jid(N, S, lists:reverse(R)).
jid_to_string(#jid{user = User, server = Server, resource = Resource}) ->
jid_to_string({User, Server, Resource});
jid_to_string({Node, Server, Resource}) ->
S1 = case Node of
"" ->
"";
_ ->
Node ++ "@"
end,
S2 = S1 ++ Server,
S3 = case Resource of
"" ->
S2;
_ ->
S2 ++ "/" ++ Resource
end,
S3.
is_nodename([]) ->
false;
is_nodename(J) ->
nodeprep(J) /= error.
%tolower_c(C) when C >= $A, C =< $Z ->
% C + 32;
%tolower_c(C) ->
% C.
-define(LOWER(Char),
if
Char >= $A, Char =< $Z ->
Char + 32;
true ->
Char
end).
%tolower(S) ->
% lists:map(fun tolower_c/1, S).
%tolower(S) ->
% [?LOWER(Char) || Char <- S].
% Not tail-recursive but it seems works faster than variants above
tolower([C | Cs]) ->
if
C >= $A, C =< $Z ->
[C + 32 | tolower(Cs)];
true ->
[C | tolower(Cs)]
end;
tolower([]) ->
[].
%tolower([C | Cs]) when C >= $A, C =< $Z ->
% [C + 32 | tolower(Cs)];
%tolower([C | Cs]) ->
% [C | tolower(Cs)];
%tolower([]) ->
% [].
nodeprep(S) when length(S) < 1024 ->
R = stringprep:nodeprep(S),
if
length(R) < 1024 -> R;
true -> error
end;
nodeprep(_) ->
error.
nameprep(S) when length(S) < 1024 ->
R = stringprep:nameprep(S),
if
length(R) < 1024 -> R;
true -> error
end;
nameprep(_) ->
error.
resourceprep(S) when length(S) < 1024 ->
R = stringprep:resourceprep(S),
if
length(R) < 1024 -> R;
true -> error
end;
resourceprep(_) ->
error.
jid_tolower(#jid{luser = U, lserver = S, lresource = R}) ->
{U, S, R};
jid_tolower({U, S, R}) ->
case nodeprep(U) of
error -> error;
LUser ->
case nameprep(S) of
error -> error;
LServer ->
case resourceprep(R) of
error -> error;
LResource ->
{LUser, LServer, LResource}
end
end
end.
jid_remove_resource(#jid{} = JID) ->
JID#jid{resource = "", lresource = ""};
jid_remove_resource({U, S, _R}) ->
{U, S, ""}.
jid_replace_resource(JID, Resource) ->
case resourceprep(Resource) of
error -> error;
LResource ->
JID#jid{resource = Resource, lresource = LResource}
end.
get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
case xml:remove_cdata(Els) of
[{xmlelement, _Name2, Attrs2, _Els2}] ->
xml:get_attr_s("xmlns", Attrs2);
_ ->
""
end;
get_iq_namespace(_) ->
"".
iq_query_info(El) ->
iq_info_internal(El, request).
iq_query_or_response_info(El) ->
iq_info_internal(El, any).
iq_info_internal({xmlel, NS, _, _, _, _} = El, Filter) ->
ElOld = exmpp_xml:xmlel_to_xmlelement(El, [NS],
[{'http://etherx.jabber.org/streams', "stream"}]),
iq_info_internal2(ElOld, Filter);
iq_info_internal(El, Filter) ->
catch throw(for_stacktrace),
io:format("~nJLIB: old #xmlelement:~n~p~n~p~n~n",
[El, erlang:get_stacktrace()]),
iq_info_internal2(El, Filter).
iq_info_internal2({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
%% Filter is either request or any. If it is request, any replies
%% are converted to the atom reply.
ID = xml:get_attr_s("id", Attrs),
Type = xml:get_attr_s("type", Attrs),
Lang = xml:get_attr_s("xml:lang", Attrs),
{Type1, Class} = case Type of
"set" -> {set, request};
"get" -> {get, request};
"result" -> {result, reply};
"error" -> {error, reply};
_ -> {invalid, invalid}
end,
if
Type1 == invalid ->
invalid;
Class == request; Filter == any ->
%% The iq record is a bit strange. The sub_el field is an
%% XML tuple for requests, but a list of XML tuples for
%% responses.
FilteredEls = xml:remove_cdata(Els),
{XMLNS, SubEl} =
case {Class, FilteredEls} of
{request, [{xmlelement, _Name2, Attrs2, _Els2}]} ->
{xml:get_attr_s("xmlns", Attrs2),
hd(FilteredEls)};
{reply, _} ->
%% Find the namespace of the first non-error
%% element, if there is one.
NonErrorEls = [El ||
{xmlelement, SubName, _, _} = El
<- FilteredEls,
SubName /= "error"],
{case NonErrorEls of
[NonErrorEl] -> xml:get_tag_attr_s("xmlns", NonErrorEl);
_ -> invalid
end,
FilteredEls};
_ ->
{invalid, invalid}
end,
if XMLNS == "", Class == request ->
invalid;
true ->
#iq{id = ID,
type = Type1,
xmlns = XMLNS,
lang = Lang,
sub_el = SubEl}
end;
Class == reply, Filter /= any ->
reply
end;
iq_info_internal2(_, _) ->
not_iq.
is_iq_request_type(set) -> true;
is_iq_request_type(get) -> true;
is_iq_request_type(_) -> false.
iq_type_to_string(set) -> "set";
iq_type_to_string(get) -> "get";
iq_type_to_string(result) -> "result";
iq_type_to_string(error) -> "error";
iq_type_to_string(_) -> invalid.
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
if
ID /= "" ->
{xmlelement, "iq",
[{"id", ID}, {"type", iq_type_to_string(Type)}], SubEl};
true ->
{xmlelement, "iq",
[{"type", iq_type_to_string(Type)}], SubEl}
end.
parse_xdata_submit({xmlel, _, _, _, Attrs, Els}) ->
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
case exmpp_xml:get_attribute_from_list(Attrs, 'type', "") of
"submit" ->
lists:reverse(parse_xdata_fields(Els, []));
_ ->
invalid
end;
parse_xdata_submit(El) ->
{xmlelement, _Name, Attrs, Els} = El,
case xml:get_attr_s("type", Attrs) of
"submit" ->
lists:reverse(parse_xdata_fields(Els, []));
_ ->
invalid
end.
parse_xdata_fields([], Res) ->
Res;
parse_xdata_fields([{xmlel, _, _, 'field', Attrs, SubEls} | Els],
Res) ->
parse_xdata_fields([#xmlel{name = 'field', attrs = Attrs, children = SubEls} |
Els], Res) ->
case exmpp_xml:get_attribute_from_list(Attrs, 'var', "") of
"" ->
parse_xdata_fields(Els, Res);
@ -479,36 +65,14 @@ parse_xdata_fields([{xmlel, _, _, 'field', Attrs, SubEls} | Els],
Field = {Var, lists:reverse(parse_xdata_values(SubEls, []))},
parse_xdata_fields(Els, [Field | Res])
end;
parse_xdata_fields([{xmlelement, Name, Attrs, SubEls} | Els], Res) ->
case Name of
"field" ->
case xml:get_attr_s("var", Attrs) of
"" ->
parse_xdata_fields(Els, Res);
Var ->
Field =
{Var, lists:reverse(parse_xdata_values(SubEls, []))},
parse_xdata_fields(Els, [Field | Res])
end;
_ ->
parse_xdata_fields(Els, Res)
end;
parse_xdata_fields([_ | Els], Res) ->
parse_xdata_fields(Els, Res).
parse_xdata_values([], Res) ->
Res;
parse_xdata_values([{xmlel, _, _, 'value', _, SubEls} | Els], Res) ->
parse_xdata_values([#xmlel{name = 'value', children = SubEls} | Els], Res) ->
Val = exmpp_xml:get_cdata_from_list_as_list(SubEls),
parse_xdata_values(Els, [Val | Res]);
parse_xdata_values([{xmlelement, Name, _Attrs, SubEls} | Els], Res) ->
case Name of
"value" ->
Val = xml:get_cdata(SubEls),
parse_xdata_values(Els, [Val | Res]);
_ ->
parse_xdata_values(Els, Res)
end;
parse_xdata_values([_ | Els], Res) ->
parse_xdata_values(Els, Res).
@ -522,7 +86,7 @@ timestamp_to_xml({{Year, Month, Day}, {Hour, Minute, Second}}) ->
Timestamp = lists:flatten(
io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
[Year, Month, Day, Hour, Minute, Second])),
exmpp_xml:set_attribute(#xmlel{ns = ?NS_DELAY, name = 'x'},
exmpp_xml:set_attribute(#xmlel{ns = ?NS_DELAY_OLD, name = 'x'},
'stamp', Timestamp).
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
@ -731,8 +295,8 @@ ip_to_list({A,B,C,D}) ->
%%
%% Empty fields are set to `undefined', not the empty string.
from_old_jid(#jid{user = Node, resource = Resource,
luser = LNode, lresource = LResource} = JID) ->
from_old_jid(#jid{node = Node, resource = Resource,
lnode = LNode, lresource = LResource} = JID) ->
{Node1, LNode1} = case Node of
"" -> {undefined, undefined};
_ -> {Node, LNode}
@ -741,8 +305,8 @@ from_old_jid(#jid{user = Node, resource = Resource,
"" -> {undefined, undefined};
_ -> {Resource, LResource}
end,
JID#jid{user = Node1, resource = Resource1,
luser = LNode1, lresource = LResource1}.
JID#jid{node = Node1, resource = Resource1,
lnode = LNode1, lresource = LResource1}.
%% @spec (JID) -> New_JID
%% JID = jid()
@ -751,8 +315,8 @@ from_old_jid(#jid{user = Node, resource = Resource,
%%
%% Empty fields are set to the empty string, not `undefined'.
to_old_jid(#jid{user = Node, resource = Resource,
luser = LNode, lresource = LResource} = JID) ->
to_old_jid(#jid{node = Node, resource = Resource,
lnode = LNode, lresource = LResource} = JID) ->
{Node1, LNode1} = case Node of
undefined -> {"", ""};
_ -> {Node, LNode}
@ -761,19 +325,19 @@ to_old_jid(#jid{user = Node, resource = Resource,
undefined -> {"", ""};
_ -> {Resource, LResource}
end,
JID#jid{user = Node1, resource = Resource1,
luser = LNode1, lresource = LResource1}.
JID#jid{node = Node1, resource = Resource1,
lnode = LNode1, lresource = LResource1}.
short_jid(JID) ->
{JID#jid.user, JID#jid.server, JID#jid.resource}.
{JID#jid.node, JID#jid.domain, JID#jid.resource}.
short_bare_jid(JID) ->
Bare_JID = exmpp_jid:jid_to_bare_jid(JID),
{Bare_JID#jid.user, Bare_JID#jid.server, Bare_JID#jid.resource}.
{Bare_JID#jid.node, Bare_JID#jid.domain, Bare_JID#jid.resource}.
short_prepd_jid(JID) ->
{JID#jid.luser, JID#jid.lserver, JID#jid.lresource}.
{JID#jid.lnode, JID#jid.ldomain, JID#jid.lresource}.
short_prepd_bare_jid(JID) ->
Bare_JID = exmpp_jid:jid_to_bare_jid(JID),
{Bare_JID#jid.luser, Bare_JID#jid.lserver, Bare_JID#jid.lresource}.
{Bare_JID#jid.lnode, Bare_JID#jid.ldomain, Bare_JID#jid.lresource}.

View File

@ -1406,7 +1406,7 @@ set_form(_From, _Host, ["running nodes", ENode, "backup", "textfile"], _Lang, XD
false ->
{error, 'bad-request'};
{value, {_, [String]}} ->
case rpc:call(Node, ejabberd_ctl, dump_to_textfile, [String]) of
case rpc:call(Node, ejabberd_admin, dump_to_textfile, [String]) of
{badrpc, _Reason} ->
{error, 'internal-server-error'};
{error, _Reason} ->

View File

@ -39,7 +39,8 @@
remove_old_messages/1,
remove_user/2,
webadmin_page/3,
webadmin_user/4]).
webadmin_user/4,
webadmin_user_parse_query/5]).
-include_lib("exmpp/include/exmpp.hrl").
@ -75,6 +76,8 @@ start(Host, Opts) ->
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MaxOfflineMsgs])).
@ -143,6 +146,8 @@ stop(Host) ->
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
exit(whereis(Proc), stop),
{wait, Proc}.
@ -642,4 +647,24 @@ webadmin_user(Acc, User, Server, Lang) ->
_ ->
[?C("?")]
end,
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen.
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
US = {User, Server},
F = fun() ->
mnesia:write_lock_table(offline_msg),
lists:foreach(
fun(Msg) ->
mnesia:delete_object(Msg)
end, mnesia:dirty_read({offline_msg, US}))
end,
case mnesia:transaction(F) of
{aborted, Reason} ->
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
{stop, error};
{atomic, ok} ->
?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]),
{stop, ok}
end;
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
Acc.

View File

@ -38,7 +38,8 @@
pop_offline_messages/3,
remove_user/2,
webadmin_page/3,
webadmin_user/4]).
webadmin_user/4,
webadmin_user_parse_query/5]).
-include_lib("exmpp/include/exmpp.hrl").
@ -69,6 +70,8 @@ start(Host, Opts) ->
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [Host, MaxOfflineMsgs])).
@ -151,6 +154,8 @@ stop(Host) ->
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
exit(whereis(Proc), stop),
ok.
@ -446,7 +451,22 @@ webadmin_user(Acc, User, Server, Lang) ->
_ ->
[?C("?")]
end,
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen.
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
case catch odbc_queries:del_spool_msg(Server, User) of
{'EXIT', Reason} ->
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
{stop, error};
{error, Reason} ->
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
{stop, error};
_ ->
?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]),
{stop, ok}
end;
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
Acc.
%% ------------------------------------------------
%% mod_offline: number of messages quota management

View File

@ -42,6 +42,7 @@
-define(PROCNAME, ejabberd_mod_proxy65).
start(Host, Opts) ->
mod_proxy65_service:add_listener(Host, Opts),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {
Proc, {?MODULE, start_link, [Host, Opts]},
@ -50,6 +51,7 @@ start(Host, Opts) ->
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
mod_proxy65_service:delete_listener(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).

View File

@ -39,7 +39,7 @@
]).
%% API.
-export([start_link/2]).
-export([start_link/2, add_listener/2, delete_listener/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@ -55,28 +55,21 @@
acl
}).
%% Unused callbacks.
handle_cast(_Request, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
%%----------------
%%%------------------------
%%% gen_server callbacks
%%%------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
init([Host, Opts]) ->
{IP, State} = parse_options(Host, Opts),
NewOpts = [Host, {ip, IP} | Opts],
ejabberd_listener:add_listener(State#state.port, mod_proxy65_stream, NewOpts),
{_IP, State} = parse_options(Host, Opts),
ejabberd_router:register_route(State#state.myhost),
{ok, State}.
terminate(_Reason, #state{myhost=MyHost, port=Port}) ->
catch ejabberd_listener:delete_listener(Port),
terminate(_Reason, #state{myhost=MyHost}) ->
ejabberd_router:unregister_route(MyHost),
ok.
@ -93,10 +86,34 @@ handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) ->
ok
end,
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
handle_call(get_port, _From, State) ->
{reply, {port, State#state.port}, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Request, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%------------------------
%%% Listener management
%%%------------------------
add_listener(Host, Opts) ->
{IP, State} = parse_options(Host, Opts),
NewOpts = [Host, {ip, IP} | Opts],
ejabberd_listener:add_listener(State#state.port,mod_proxy65_stream,NewOpts).
delete_listener(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
{port, Port} = gen_server:call(Proc, get_port),
catch ejabberd_listener:delete_listener(Port, mod_proxy65_stream).
%%%------------------------
%%% IQ Processing
%%%------------------------

View File

@ -1486,7 +1486,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
node_call(Type, publish_item, [Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload])
end
end,
%%ejabberd_hooks:run(pubsub_publish_item, Host, [Host, Node, JID, service_jid(Host), ItemId, Payload]),
ejabberd_hooks:run(pubsub_publish_item, Host, [Host, Node, Publisher, service_jid(Host), ItemId, Payload]),
Reply = [],
case transaction(Host, Node, Action, sync_dirty) of
{error, ?ERR_ITEM_NOT_FOUND} ->

View File

@ -104,60 +104,15 @@ get_auth(Auth) ->
unauthorized
end.
make_xhtml(Els, global, Lang) ->
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]),
MenuItems2 = [?LI([?AC("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
{200, [html],
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", Lang},
{"lang", Lang}],
[{xmlelement, "head", [],
[?XCT("title", "ejabberd Web Admin"),
{xmlelement, "meta", [{"http-equiv", "Content-Type"},
{"content", "text/html; charset=utf-8"}], []},
{xmlelement, "link", [{"href", "/admin/favicon.ico"},
{"type", "image/x-icon"},
{"rel", "shortcut icon"}], []},
{xmlelement, "link", [{"href", "/admin/style.css"},
{"type", "text/css"},
{"rel", "stylesheet"}], []}]},
?XE("body",
[?XAE("div",
[{"id", "container"}],
[?XAE("div",
[{"id", "header"}],
[?XE("h1",
[?ACT("/admin/", "Administration")]
)]),
?XAE("div",
[{"id", "navigation"}],
[?XE("ul",
[?LI([?ACT("/admin/acls/", "Access Control Lists")]),
?LI([?ACT("/admin/access/", "Access Rules")]),
?LI([?ACT("/admin/vhosts/", "Virtual Hosts")]),
?LI([?ACT("/admin/nodes/", "Nodes")]),
?LI([?ACT("/admin/stats/", "Statistics")])
] ++ MenuItems2
)]),
?XAE("div",
[{"id", "content"}],
Els),
?XAE("div",
[{"id", "clearcopyright"}],
[{xmlcdata, ""}])]),
?XAE("div",
[{"id", "copyrightouter"}],
[?XAE("div",
[{"id", "copyright"}],
[?XC("p",
"ejabberd (c) 2002-2008 ProcessOne")
])])])
]}};
make_xhtml(Els, Host, Lang) ->
Base = "/admin/server/" ++ Host ++ "/",
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]),
MenuItems2 = [?LI([?AC(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
make_xhtml(Els, Host, cluster, Lang).
%% @spec (Els, Host, Node, Lang)
%% where Host = global | string()
%% Node = cluster | atom()
make_xhtml(Els, Host, Node, Lang) ->
Base = get_base_path(Host, cluster), %% Enforcing 'cluster' on purpose here
MenuItems = make_navigation(Host, Node, Lang),
{200, [html],
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", Lang},
@ -166,7 +121,7 @@ make_xhtml(Els, Host, Lang) ->
[?XCT("title", "ejabberd Web Admin"),
{xmlelement, "meta", [{"http-equiv", "Content-Type"},
{"content", "text/html; charset=utf-8"}], []},
{xmlelement, "link", [{"href", "/admin/favicon.ico"},
{xmlelement, "link", [{"href", Base ++ "favicon.ico"},
{"type", "image/x-icon"},
{"rel", "shortcut icon"}], []},
{xmlelement, "link", [{"href", Base ++ "style.css"},
@ -183,18 +138,7 @@ make_xhtml(Els, Host, Lang) ->
?XAE("div",
[{"id", "navigation"}],
[?XE("ul",
[?LI([?XAE("div",
[{"id", "navheadhost"}],
[?AC(Base, Host)]
)]),
?LI([?ACT(Base ++ "acls/", "Access Control Lists")]),
?LI([?ACT(Base ++ "access/", "Access Rules")]),
?LI([?ACT(Base ++ "users/", "Users")]),
?LI([?ACT(Base ++ "online-users/", "Online Users")]),
?LI([?ACT(Base ++ "last-activity/", "Last Activity")]),
?LI([?ACT(Base ++ "nodes/", "Nodes")]),
?LI([?ACT(Base ++ "stats/", "Statistics")])
] ++ MenuItems2
MenuItems
)]),
?XAE("div",
[{"id", "content"}],
@ -211,13 +155,13 @@ make_xhtml(Els, Host, Lang) ->
])])])
]}}.
get_base_path(global, cluster) -> "/admin/";
get_base_path(Host, cluster) -> "/admin/server/" ++ Host ++ "/";
get_base_path(global, Node) -> "/admin/node/" ++ atom_to_list(Node) ++ "/";
get_base_path(Host, Node) -> "/admin/server/" ++ Host ++ "/node/" ++ atom_to_list(Node) ++ "/".
css(Host) ->
Base = case Host of
global ->
"/admin/";
_ ->
"/admin/server/" ++ Host ++ "/"
end,
Base = get_base_path(Host, cluster),
"
html,body {
background: white;
@ -298,7 +242,7 @@ html>body #container {
#navigation ul {
position: absolute;
top: 54px;
top: 65px;
left: 0;
padding: 0 1px 1px 1px;
margin: 0;
@ -306,7 +250,7 @@ html>body #container {
font-size: 8pt;
font-weight: bold;
background: #d47911;
width: 13em;
width: 17em;
}
#navigation ul li {
@ -340,9 +284,20 @@ html>body #container {
background: #332;
}
#navheadhost {
text-align: left;
border-bottom: 2px solid #d47911;
ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {
text-align: center;
border-top: 2px solid #d47911;
border-bottom: 1px solid #d47911;
}
#navheadsub, #navitemsub {
border-left: 7px solid white;
margin-left: 2px solid #d47911;
}
#navheadsubsub, #navitemsubsub {
border-left: 14px solid white;
margin-left: 4px solid #d47911;
}
#lastactivity li {
@ -550,7 +505,7 @@ h3 {
#content {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 10pt;
padding-left: 13em;
padding-left: 17em;
padding-top: 5px;
}
@ -595,11 +550,12 @@ logo_fill() ->
"1c5dvhSU2BpKqBXl6R0ljYGS50R5zVC+tVD+vfE6YyUexE9x7g4AAAAASUVO"
"RK5CYII=").
process_admin(global,
#request{path = [],
lang = Lang}) ->
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]),
MenuItems2 = [?LI([?AC("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
Base = get_base_path(global, cluster),
MenuItems2 = make_menu_items(global, cluster, Base, Lang),
make_xhtml([?XCT("h1", "Administration"),
?XE("ul",
[?LI([?ACT("/admin/acls/", "Access Control Lists"), ?C(" "),
@ -616,9 +572,8 @@ process_admin(global,
process_admin(Host,
#request{path = [],
lang = Lang}) ->
Base = "/admin/server/" ++ Host ++ "/",
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]),
MenuItems2 = [?LI([?AC(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
Base = get_base_path(Host, cluster),
MenuItems2 = make_menu_items(Host, cluster, Base, Lang),
make_xhtml([?XCT("h1", "Administration"),
?XE("ul",
[?LI([?ACT(Base ++ "acls/", "Access Control Lists"), ?C(" "),
@ -953,8 +908,13 @@ process_admin(Host,
#request{path = ["user", U],
q = Query,
lang = Lang}) ->
Res = user_info(U, Host, Query, Lang),
make_xhtml(Res, Host, Lang);
case ejabberd_auth:is_user_exists(U, Host) of
true ->
Res = user_info(U, Host, Query, Lang),
make_xhtml(Res, Host, Lang);
false ->
make_xhtml([?XCT("h1", "Not Found")], Host, Lang)
end;
process_admin(Host,
#request{path = ["nodes"],
@ -971,7 +931,7 @@ process_admin(Host,
make_xhtml([?XCT("h1", "Node not found")], Host, Lang);
Node ->
Res = get_node(Host, Node, NPath, Query, Lang),
make_xhtml(Res, Host, Lang)
make_xhtml(Res, Host, Node, Lang)
end;
process_admin(Host, #request{lang = Lang} = Request) ->
@ -1515,25 +1475,31 @@ user_info(User, Server, Query, Lang) ->
user_parse_query(User, Server, Query) ->
case lists:keysearch("chpassword", 1, Query) of
{value, _} ->
case lists:keysearch("password", 1, Query) of
{value, {_, undefined}} ->
error;
{value, {_, Password}} ->
ejabberd_auth:set_password(User, Server, Password),
ok;
_ ->
error
end;
_ ->
case lists:keysearch("removeuser", 1, Query) of
{value, _} ->
ejabberd_auth:remove_user(User, Server),
ok;
false ->
nothing
end
lists:foldl(fun({Action, _Value}, Acc) when Acc == nothing ->
user_parse_query1(Action, User, Server, Query);
({_Action, _Value}, Acc) ->
Acc
end, nothing, Query).
user_parse_query1("password", _User, _Server, _Query) ->
nothing;
user_parse_query1("chpassword", User, Server, Query) ->
case lists:keysearch("password", 1, Query) of
{value, {_, undefined}} ->
error;
{value, {_, Password}} ->
ejabberd_auth:set_password(User, Server, Password),
ok;
_ ->
error
end;
user_parse_query1("removeuser", User, Server, _Query) ->
ejabberd_auth:remove_user(User, Server),
ok;
user_parse_query1(Action, User, Server, Query) ->
case ejabberd_hooks:run_fold(webadmin_user_parse_query, Server, [], [Action, User, Server, Query]) of
[] -> nothing;
Res -> Res
end.
@ -1661,8 +1627,8 @@ search_running_node(SNode, [Node | Nodes]) ->
get_node(global, Node, [], Query, Lang) ->
Res = node_parse_query(Node, Query),
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_node, [], [Node, Lang]),
MenuItems2 = [?LI([?AC(MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
Base = get_base_path(global, Node),
MenuItems2 = make_menu_items(global, Node, Base, Lang),
[?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++
case Res of
ok -> [?CT("Submitted"), ?P];
@ -1670,11 +1636,11 @@ get_node(global, Node, [], Query, Lang) ->
nothing -> []
end ++
[?XE("ul",
[?LI([?ACT("db/", "Database")]),
?LI([?ACT("backup/", "Backup")]),
?LI([?ACT("ports/", "Listened Ports")]),
?LI([?ACT("stats/", "Statistics")]),
?LI([?ACT("update/", "Update")])
[?LI([?ACT(Base ++ "db/", "Database")]),
?LI([?ACT(Base ++ "backup/", "Backup")]),
?LI([?ACT(Base ++ "ports/", "Listened Ports")]),
?LI([?ACT(Base ++ "stats/", "Statistics")]),
?LI([?ACT(Base ++ "update/", "Update")])
] ++ MenuItems2),
?XAE("form", [{"action", ""}, {"method", "post"}],
[?INPUTT("submit", "restart", "Restart"),
@ -1683,11 +1649,11 @@ get_node(global, Node, [], Query, Lang) ->
];
get_node(Host, Node, [], _Query, Lang) ->
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Node, Lang]),
MenuItems2 = [?LI([?AC(MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
Base = get_base_path(Host, Node),
MenuItems2 = make_menu_items(global, Node, Base, Lang),
[?XC("h1", ?T("Node ") ++ atom_to_list(Node)),
?XE("ul",
[?LI([?ACT("modules/", "Modules")])] ++ MenuItems2)
[?LI([?ACT(Base ++ "modules/", "Modules")])] ++ MenuItems2)
];
get_node(global, Node, ["db"], Query, Lang) ->
@ -2022,7 +1988,7 @@ node_backup_parse_query(Node, Query) ->
rpc:call(Node, mnesia,
install_fallback, [Path]);
"dump" ->
rpc:call(Node, ejabberd_ctl,
rpc:call(Node, ejabberd_admin,
dump_to_textfile, [Path]);
"load" ->
rpc:call(Node, mnesia,
@ -2087,7 +2053,7 @@ node_ports_to_xhtml(Ports, Lang) ->
node_ports_parse_query(Node, Ports, Query) ->
lists:foreach(
fun({Port, _Module1, _Opts1}) ->
fun({Port, Module1, _Opts1}) ->
SPort = integer_to_list(Port),
case lists:keysearch("add" ++ SPort, 1, Query) of
{value, _} ->
@ -2097,13 +2063,13 @@ node_ports_parse_query(Node, Ports, Query) ->
Module = list_to_atom(SModule),
{ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
{ok, Opts} = erl_parse:parse_term(Tokens),
rpc:call(Node, ejabberd_listener, delete_listener, [Port]),
rpc:call(Node, ejabberd_listener, delete_listener, [Port, Module]),
rpc:call(Node, ejabberd_listener, add_listener, [Port, Module, Opts]),
throw(submitted);
_ ->
case lists:keysearch("delete" ++ SPort, 1, Query) of
{value, _} ->
rpc:call(Node, ejabberd_listener, delete_listener, [Port]),
rpc:call(Node, ejabberd_listener, delete_listener, [Port, Module1]),
throw(submitted);
_ ->
ok
@ -2276,3 +2242,134 @@ last_modified() ->
{"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}.
cache_control_public() ->
{"Cache-Control", "public"}.
%%%
%%% Navigation Menu
%%%
%% @spec (Host, Node, Lang) -> [LI]
make_navigation(Host, Node, Lang) ->
HostNodeMenu = make_host_node_menu(Host, Node, Lang),
HostMenu = make_host_menu(Host, HostNodeMenu, Lang),
NodeMenu = make_node_menu(Host, Node, Lang),
Menu = make_server_menu(HostMenu, NodeMenu, Lang),
make_menu_items(Lang, Menu).
%% @spec (Host, Node, Base, Lang) -> [LI]
make_menu_items(global, cluster, Base, Lang) ->
HookItems = get_menu_items_hook(server, Lang),
make_menu_items(Lang, {Base, "", HookItems});
make_menu_items(global, _Node, Base, Lang) ->
HookItems = get_menu_items_hook(node, Lang),
make_menu_items(Lang, {Base, "", HookItems});
make_menu_items(Host, cluster, Base, Lang) ->
HookItems = get_menu_items_hook({host, Host}, Lang),
make_menu_items(Lang, {Base, "", HookItems});
make_menu_items(Host, _Node, Base, Lang) ->
HookItems = get_menu_items_hook({hostnode, Host}, Lang),
make_menu_items(Lang, {Base, "", HookItems}).
make_host_node_menu(global, _, _Lang) ->
{"", "", []};
make_host_node_menu(_, cluster, _Lang) ->
{"", "", []};
make_host_node_menu(Host, Node, Lang) ->
HostNodeBase = get_base_path(Host, Node),
HostNodeFixed = [{"modules/", "Modules"}],
HostNodeHook = get_menu_items_hook({hostnode, Host}, Lang),
{HostNodeBase, atom_to_list(Node), HostNodeFixed ++ HostNodeHook}.
make_host_menu(global, _HostNodeMenu, _Lang) ->
{"", "", []};
make_host_menu(Host, HostNodeMenu, Lang) ->
HostBase = get_base_path(Host, cluster),
HostFixed = [{"acls", "Access Control Lists"},
{"access", "Access Rules"},
{"users", "Users"},
{"online-users", "Online Users"},
{"last-activity", "Last Activity"},
{"nodes", "Nodes", HostNodeMenu},
{"stats", "Statistics"}],
HostHook = get_menu_items_hook({host, Host}, Lang),
{HostBase, Host, HostFixed ++ HostHook}.
make_node_menu(_Host, cluster, _Lang) ->
{"", "", []};
make_node_menu(global, Node, Lang) ->
NodeBase = get_base_path(global, Node),
NodeFixed = [{"db/", "Database"},
{"backup/", "Backup"},
{"ports/", "Listened Ports"},
{"stats/", "Statistics"},
{"update/", "Update"}],
NodeHook = get_menu_items_hook(node, Lang),
{NodeBase, atom_to_list(Node), NodeFixed ++ NodeHook};
make_node_menu(_Host, _Node, _Lang) ->
{"", "", []}.
make_server_menu(HostMenu, NodeMenu, Lang) ->
Base = get_base_path(global, cluster),
Fixed = [{"acls", "Access Control Lists"},
{"access", "Access Rules"},
{"vhosts", "Virtual Hosts", HostMenu},
{"nodes", "Nodes", NodeMenu},
{"stats", "Statistics"}],
Hook = get_menu_items_hook(server, Lang),
{Base, "ejabberd", Fixed ++ Hook}.
get_menu_items_hook({hostnode, Host}, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Lang]);
get_menu_items_hook({host, Host}, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]);
get_menu_items_hook(node, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_node, [], [Lang]);
get_menu_items_hook(server, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]).
%% @spec (Lang::string(), Menu) -> [LI]
%% where Menu = {MURI::string(), MName::string(), Items::[Item]}
%% Item = {IURI::string(), IName::string()} | {IURI::string(), IName::string(), Menu}
make_menu_items(Lang, Menu) ->
lists:reverse(make_menu_items2(Lang, 1, Menu)).
make_menu_items2(Lang, Deep, {MURI, MName, _} = Menu) ->
Res = case MName of
"" -> [];
_ -> [make_menu_item(header, Deep, MURI, MName, Lang) ]
end,
make_menu_items2(Lang, Deep, Menu, Res).
make_menu_items2(_, _Deep, {_, _, []}, Res) ->
Res;
make_menu_items2(Lang, Deep, {MURI, MName, [Item | Items]}, Res) ->
Res2 = case Item of
{IURI, IName} ->
[make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res];
{IURI, IName, SubMenu} ->
%%ResTemp = [?LI([?ACT(MURI ++ IURI ++ "/", IName)]) | Res],
ResTemp = [make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res],
ResSubMenu = make_menu_items2(Lang, Deep+1, SubMenu),
ResSubMenu ++ ResTemp
end,
make_menu_items2(Lang, Deep, {MURI, MName, Items}, Res2).
make_menu_item(header, 1, URI, Name, _Lang) ->
?LI([?XAE("div", [{"id", "navhead"}], [?AC(URI, "~ "++Name++" ~")] )]);
make_menu_item(header, 2, URI, Name, _Lang) ->
?LI([?XAE("div", [{"id", "navheadsub"}], [?AC(URI, "~ "++Name++" ~")] )]);
make_menu_item(header, 3, URI, Name, _Lang) ->
?LI([?XAE("div", [{"id", "navheadsubsub"}], [?AC(URI, "~ "++Name++" ~")] )]);
make_menu_item(item, 1, URI, Name, Lang) ->
?LI([?XAE("div", [{"id", "navitem"}], [?ACT(URI, Name)] )]);
make_menu_item(item, 2, URI, Name, Lang) ->
?LI([?XAE("div", [{"id", "navitemsub"}], [?ACT(URI, Name)] )]);
make_menu_item(item, 3, URI, Name, Lang) ->
?LI([?XAE("div", [{"id", "navitemsubsub"}], [?ACT(URI, Name)] )]).