mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-30 16:36:29 +01:00
Merge from trunk (r1613 to 1649).
PR: EJABP-1 SVN Revision: 1650
This commit is contained in:
parent
3190c0ed6c
commit
ab2b70f189
71
ChangeLog
71
ChangeLog
@ -1,3 +1,7 @@
|
||||
2008-10-13 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
|
||||
|
||||
Merge from trunk (r1613 to 1649).
|
||||
|
||||
2008-10-13 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
|
||||
|
||||
* src/extauth.erl (call_port/2): Replace jlib:nameprep/1 by
|
||||
@ -8,6 +12,68 @@
|
||||
|
||||
* src/ejd2odbc.erl, src/jd2ejd.erl: Convert to exmpp.
|
||||
|
||||
2008-10-13 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/web/ejabberd_web_admin.erl: When requesting page of
|
||||
nonexistent user, show 'Not Found' page (EJAB-771)
|
||||
|
||||
2008-10-12 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/web/ejabberd_web_admin.erl: Run new hook
|
||||
webadmin_user_parse_query when POST in web admin user
|
||||
page (thanks to Oleg Palij)(EJAB-747)
|
||||
* src/mod_offline.erl: Add button "Remove All Offline Messages" in
|
||||
a user page (thanks to Oleg Palij)(EJAB-747)
|
||||
* src/mod_offline_odbc.erl: Likewise
|
||||
|
||||
* src/web/ejabberd_web_admin.erl: Improve Web Admin navigation
|
||||
menu for vhosts and nodes (EJAB-734)
|
||||
|
||||
* doc/guide.tex: Explain the new ejabberdctl command 'help'
|
||||
* doc/guide.html: Likewise
|
||||
|
||||
* src/mod_configure.erl: Update calls from ctl to
|
||||
commands (EJAB-694)
|
||||
* src/web/ejabberd_web_admin.erl: Likewise
|
||||
|
||||
* src/ejabberd_sm.erl: Update from ctl to commands (EJAB-694)
|
||||
* src/ejabberd_s2s.erl: Likewise
|
||||
|
||||
* src/ejabberd_auth.erl: Update from ctl to commands (EJAB-694)
|
||||
* src/ejabberd_auth_internal.erl: Likewise
|
||||
* src/ejabberd_auth_ldap.erl: Likewise
|
||||
* src/ejabberd_auth_odbc.erl: Likewise
|
||||
|
||||
* src/ejabberdctl.template: Move help print to a separate
|
||||
function (EJAB-694)
|
||||
|
||||
* src/ejabberd_ctl.erl: Add frontend support for
|
||||
commands (EJAB-694). Categorization and sorting of commands in
|
||||
ejabberd_ctl help (EJAB-313). Lines in command line help of length
|
||||
80, and text formatting (EJAB-473)
|
||||
|
||||
* src/ejabberd_app.erl: Initialize ejabberd_commands and start
|
||||
ejabbed_admin (EJAB-694)
|
||||
|
||||
* src/ejabberd_admin.erl: Implement commands from old
|
||||
ejabberd_ctl (EJAB-694)
|
||||
|
||||
* src/ejabberd_commands.erl: New 'ejabberd commands': separate
|
||||
command definition and calling interface (EJAB-694)
|
||||
* src/ejabberd_commands.hrl: Likewise
|
||||
|
||||
* src/mod_proxy65/mod_proxy65.erl: Update so the listener starts
|
||||
correctly (EJAB-303)
|
||||
* src/mod_proxy65/mod_proxy65_service.erl: Likewise
|
||||
|
||||
* src/ejabberd_app.erl: Start listeners explicitely at server
|
||||
start after everything else (EJAB-303). Implement support in
|
||||
ejabberd for 'independent listeners', which handle their
|
||||
connections themselves: gen_tcp:listen, etc.
|
||||
* src/ejabberd_listener.erl: Likewise
|
||||
* src/ejabberd_socket.erl: Likewise
|
||||
* src/ejabberd_sup.erl: Likewise
|
||||
|
||||
2008-10-10 Jean-Sébastien Pédron <js.pedron@meetic-corp.com>
|
||||
|
||||
* src/ejabberd_c2s.erl (is_auth_packet/1): Fix a bug where
|
||||
@ -63,6 +129,11 @@
|
||||
* src/mod_private.erl, src/mod_private_odbc.erl, src/mod_version.erl:
|
||||
Convert to exmpp. Thanks to Pablo Polvorin!
|
||||
|
||||
2008-10-07 Christophe Romain <christophe.romain@process-one.net>
|
||||
|
||||
* src/mod_pubsub/mod_pubsub.erl: uncomment pubsub_publish_item hook
|
||||
call (EJAB-765)
|
||||
|
||||
2008-10-07 Jerome Sautret <jerome.sautret@process-one.net>
|
||||
|
||||
* src/mod_roster_odbc.erl: fix MySQL multiple requests issue.
|
||||
|
@ -413,8 +413,8 @@ you can execute <TT>ejabberdctl</TT> with either that system account or root.</P
|
||||
</P><PRE CLASS="verbatim">ejabberdctl start
|
||||
|
||||
ejabberdctl status
|
||||
Node ejabberd@localhost is started. Status: started
|
||||
ejabberd is running
|
||||
The node ejabberd@localhost is started with status: started
|
||||
ejabberd is running in that node
|
||||
|
||||
ejabberdctl stop
|
||||
</PRE><P>If <TT>ejabberd</TT> doesn’t start correctly and a crash dump is generated,
|
||||
@ -2894,7 +2894,8 @@ the available parameters are:
|
||||
<TT>ejabberdctl</TT> shows all the available commands in that server.
|
||||
The more interesting ones are:
|
||||
</P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
<B><TT>status</TT></B></DT><DD CLASS="dd-description"> Check the status of the <TT>ejabberd</TT> server.
|
||||
<B><TT>help</TT></B></DT><DD CLASS="dd-description"> Get help about ejabberdctl or any available command. Try <TT>ejabberdctl help help</TT>.
|
||||
</DD><DT CLASS="dt-description"><B><TT>status</TT></B></DT><DD CLASS="dd-description"> Check the status of the <TT>ejabberd</TT> server.
|
||||
</DD><DT CLASS="dt-description"><B><TT>stop</TT></B></DT><DD CLASS="dd-description"> Stop the <TT>ejabberd</TT> server which is running in the machine.
|
||||
</DD><DT CLASS="dt-description"><B><TT>reopen-log</TT></B></DT><DD CLASS="dd-description"> If you use a tool to rotate logs, you have to configure it
|
||||
so that this command is executed after each rotation.
|
||||
|
@ -425,8 +425,8 @@ Usage example:
|
||||
ejabberdctl start
|
||||
|
||||
ejabberdctl status
|
||||
Node ejabberd@localhost is started. Status: started
|
||||
ejabberd is running
|
||||
The node ejabberd@localhost is started with status: started
|
||||
ejabberd is running in that node
|
||||
|
||||
ejabberdctl stop
|
||||
\end{verbatim}
|
||||
@ -3712,6 +3712,7 @@ If there is an \ejabberd{} server running in the system,
|
||||
\term{ejabberdctl} shows all the available commands in that server.
|
||||
The more interesting ones are:
|
||||
\begin{description}
|
||||
\titem{help} Get help about ejabberdctl or any available command. Try \term{ejabberdctl help help}.
|
||||
\titem{status} Check the status of the \ejabberd{} server.
|
||||
\titem{stop} Stop the \ejabberd{} server which is running in the machine.
|
||||
\titem{reopen-log} If you use a tool to rotate logs, you have to configure it
|
||||
|
@ -1,11 +1,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_admin.erl
|
||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||
%%% Description : This module gathers admin functions used by different
|
||||
%%% access method:
|
||||
%%% - ejabberdctl command-line tool
|
||||
%%% - web admin interface
|
||||
%%% - adhoc mode
|
||||
%%% Purpose : Administrative functions and commands
|
||||
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@ -31,9 +27,237 @@
|
||||
-module(ejabberd_admin).
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-export([restore/1]).
|
||||
-export([start/0, stop/0,
|
||||
%% Server
|
||||
status/0, reopen_log/0,
|
||||
%% Accounts
|
||||
register/3, unregister/2,
|
||||
registered_users/1,
|
||||
%% Migration
|
||||
import_file/1, import_dir/1,
|
||||
%% Purge DB
|
||||
delete_expired_messages/0, delete_old_messages/1,
|
||||
%% Mnesia
|
||||
backup_mnesia/1, restore_mnesia/1,
|
||||
dump_mnesia/1, load_mnesia/1,
|
||||
install_fallback_mnesia/1,
|
||||
dump_to_textfile/1,
|
||||
restore/1 % Still used by some modules
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
start() ->
|
||||
ejabberd_commands:register_commands(commands()).
|
||||
|
||||
stop() ->
|
||||
ejabberd_commands:unregister_commands(commands()).
|
||||
|
||||
%%%
|
||||
%%% ejabberd commands
|
||||
%%%
|
||||
|
||||
commands() ->
|
||||
[
|
||||
%% The commands status, stop and restart are implemented also in ejabberd_ctl
|
||||
%% They are defined here so that other interfaces can use them too
|
||||
#ejabberd_commands{name = status, tags = [server],
|
||||
desc = "Get status of the ejabberd server",
|
||||
module = ?MODULE, function = status,
|
||||
args = [], result = {res, restuple}},
|
||||
#ejabberd_commands{name = stop, tags = [server],
|
||||
desc = "Stop ejabberd gracefully",
|
||||
module = init, function = stop,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = restart, tags = [server],
|
||||
desc = "Restart ejabberd gracefully",
|
||||
module = init, function = restart,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = reopen_log, tags = [logs, server],
|
||||
desc = "Reopen the log files",
|
||||
module = ?MODULE, function = reopen_log,
|
||||
args = [], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = register, tags = [accounts],
|
||||
desc = "Register a user",
|
||||
module = ?MODULE, function = register,
|
||||
args = [{user, string}, {host, string}, {password, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = unregister, tags = [accounts],
|
||||
desc = "Unregister a user",
|
||||
module = ?MODULE, function = unregister,
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = registered_users, tags = [accounts],
|
||||
desc = "List all registered users in HOST",
|
||||
module = ?MODULE, function = registered_users,
|
||||
args = [{host, string}],
|
||||
result = {users, {list, {username, string}}}},
|
||||
|
||||
#ejabberd_commands{name = import_file, tags = [mnesia],
|
||||
desc = "Import user data from jabberd-1.4 spool file",
|
||||
module = ?MODULE, function = import_file,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = import_dir, tags = [mnesia],
|
||||
desc = "Import user data from jabberd-1.4 spool dir",
|
||||
module = ?MODULE, function = import_dir,
|
||||
args = [{file, string}],
|
||||
result = {res, restuple}},
|
||||
|
||||
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
|
||||
desc = "Delete expired offline messages from database",
|
||||
module = ?MODULE, function = delete_expired_messages,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_old_messages, tags = [purge],
|
||||
desc = "Delete offline messages older than DAYS",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = backup, tags = [mnesia],
|
||||
desc = "Store the database to backup file",
|
||||
module = ?MODULE, function = backup_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = restore, tags = [mnesia],
|
||||
desc = "Restore the database from backup file",
|
||||
module = ?MODULE, function = restore_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = dump, tags = [mnesia],
|
||||
desc = "Dump the database to text file",
|
||||
module = ?MODULE, function = dump_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = load, tags = [mnesia],
|
||||
desc = "Restore the database from text file",
|
||||
module = ?MODULE, function = load_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = install_fallback, tags = [mnesia],
|
||||
desc = "Install the database from a fallback file",
|
||||
module = ?MODULE, function = install_fallback_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}}
|
||||
].
|
||||
|
||||
|
||||
%%%
|
||||
%%% Server management
|
||||
%%%
|
||||
|
||||
status() ->
|
||||
{InternalStatus, ProvidedStatus} = init:get_status(),
|
||||
String1 = io_lib:format("The node ~p is ~p. Status: ~p",
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
{Is_running, String2} =
|
||||
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
||||
false ->
|
||||
{ejabberd_not_running, "ejabberd is not running in that node."};
|
||||
{value, {_, _, Version}} ->
|
||||
{ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
|
||||
end,
|
||||
{Is_running, String1 ++ String2}.
|
||||
|
||||
reopen_log() ->
|
||||
ejabberd_hooks:run(reopen_log_hook, []),
|
||||
%% TODO: Use the Reopen log API for logger_h ?
|
||||
ejabberd_logger_h:reopen_log(),
|
||||
ok.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Account management
|
||||
%%%
|
||||
|
||||
register(User, Host, Password) ->
|
||||
case ejabberd_auth:try_register(User, Host, Password) of
|
||||
{atomic, ok} ->
|
||||
{ok, io_lib:format("User ~s@~s succesfully registered", [User, Host])};
|
||||
{atomic, exists} ->
|
||||
String = io_lib:format("User ~s@~s already registered at node ~p",
|
||||
[User, Host, node()]),
|
||||
{exists, String};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
|
||||
[User, Host, node(), Reason]),
|
||||
{cannot_register, String}
|
||||
end.
|
||||
|
||||
unregister(User, Host) ->
|
||||
ejabberd_auth:remove_user(User, Host),
|
||||
{ok, ""}.
|
||||
|
||||
registered_users(Host) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
SUsers = lists:sort(Users),
|
||||
lists:map(fun({U, _S}) -> U end, SUsers).
|
||||
|
||||
|
||||
%%%
|
||||
%%% Migration management
|
||||
%%%
|
||||
|
||||
import_file(Path) ->
|
||||
case jd2ejd:import_file(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't import jabberd 1.4 spool file ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_import_file, String}
|
||||
end.
|
||||
|
||||
import_dir(Path) ->
|
||||
case jd2ejd:import_dir(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't import jabberd 1.4 spool dir ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_import_dir, String}
|
||||
end.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Purge DB
|
||||
%%%
|
||||
|
||||
delete_expired_messages() ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(),
|
||||
ok.
|
||||
|
||||
delete_old_messages(Days) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days),
|
||||
ok.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Mnesia management
|
||||
%%%
|
||||
|
||||
backup_mnesia(Path) ->
|
||||
case mnesia:backup(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_backup, String}
|
||||
end.
|
||||
|
||||
restore_mnesia(Path) ->
|
||||
case ejabberd_admin:restore(Path) of
|
||||
{atomic, _} ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_restore, String};
|
||||
{aborted,{no_exists,Table}} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
|
||||
[filename:absname(Path), node(), Table]),
|
||||
{table_not_exists, String};
|
||||
{aborted,enoent} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: File not found.",
|
||||
[filename:absname(Path), node()]),
|
||||
{file_not_found, String}
|
||||
end.
|
||||
|
||||
%% Mnesia database restore
|
||||
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
|
||||
@ -71,3 +295,67 @@ module_tables(mod_roster) -> [roster];
|
||||
module_tables(mod_shared_roster) -> [sr_group, sr_user];
|
||||
module_tables(mod_vcard) -> [vcard, vcard_search];
|
||||
module_tables(_Other) -> [].
|
||||
|
||||
dump_mnesia(Path) ->
|
||||
case dump_to_textfile(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_dump, String}
|
||||
end.
|
||||
|
||||
dump_to_textfile(File) ->
|
||||
dump_to_textfile(mnesia:system_info(is_running), file:open(File, [write])).
|
||||
dump_to_textfile(yes, {ok, F}) ->
|
||||
Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
|
||||
Tabs = lists:filter(
|
||||
fun(T) ->
|
||||
case mnesia:table_info(T, storage_type) of
|
||||
disc_copies -> true;
|
||||
disc_only_copies -> true;
|
||||
_ -> false
|
||||
end
|
||||
end, Tabs1),
|
||||
Defs = lists:map(
|
||||
fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
|
||||
{attributes, mnesia:table_info(T, attributes)}]}
|
||||
end,
|
||||
Tabs),
|
||||
io:format(F, "~p.~n", [{tables, Defs}]),
|
||||
lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
|
||||
file:close(F);
|
||||
dump_to_textfile(_, {ok, F}) ->
|
||||
file:close(F),
|
||||
{error, mnesia_not_running};
|
||||
dump_to_textfile(_, {error, Reason}) ->
|
||||
{error, Reason}.
|
||||
|
||||
dump_tab(F, T) ->
|
||||
W = mnesia:table_info(T, wild_pattern),
|
||||
{atomic,All} = mnesia:transaction(
|
||||
fun() -> mnesia:match_object(T, W, read) end),
|
||||
lists:foreach(
|
||||
fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
|
||||
|
||||
load_mnesia(Path) ->
|
||||
case mnesia:load_textfile(Path) of
|
||||
{atomic, ok} ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_load, String}
|
||||
end.
|
||||
|
||||
install_fallback_mnesia(Path) ->
|
||||
case mnesia:install_fallback(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_fallback, String}
|
||||
end.
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, prep_stop/1, stop/1, init/0]).
|
||||
-export([start_modules/0,start/2, prep_stop/1, stop/1, init/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
@ -47,6 +47,8 @@ start(normal, _Args) ->
|
||||
translate:start(),
|
||||
acl:start(),
|
||||
ejabberd_ctl:init(),
|
||||
ejabberd_commands:init(),
|
||||
ejabberd_admin:start(),
|
||||
gen_mod:start(),
|
||||
ejabberd_config:start(),
|
||||
ejabberd_check:config(),
|
||||
@ -61,6 +63,7 @@ start(normal, _Args) ->
|
||||
%eprof:profile([self()]),
|
||||
%fprof:trace(start, "/tmp/fprof"),
|
||||
start_modules(),
|
||||
ejabberd_listener:start_listeners(),
|
||||
Sup;
|
||||
start(_, _) ->
|
||||
{error, badarg}.
|
||||
@ -70,6 +73,7 @@ start(_, _) ->
|
||||
%% before shutting down the processes of the application.
|
||||
prep_stop(State) ->
|
||||
stop_modules(),
|
||||
ejabberd_admin:stop(),
|
||||
State.
|
||||
|
||||
%% All the processes were killed when this function is called
|
||||
|
@ -49,14 +49,12 @@
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/1,
|
||||
ctl_process_get_registered/3
|
||||
plain_password_required/1
|
||||
]).
|
||||
|
||||
-export([auth_modules/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@ -265,15 +263,6 @@ remove_user(User, Server, Password) ->
|
||||
M:remove_user(User, Server, Password)
|
||||
end, auth_modules(Server)).
|
||||
|
||||
ctl_process_get_registered(_Val, Host, ["registered-users"]) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUsers = lists:sort(Users),
|
||||
FUsers = lists:map(fun({U, _S}) -> [U, NewLine] end, SUsers),
|
||||
?PRINT("~s", [FUsers]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process_get_registered(Val, _Host, _Args) ->
|
||||
Val.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
|
@ -53,14 +53,10 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
start(_Host) ->
|
||||
mnesia:create_table(passwd, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
update_table(),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
|
@ -112,11 +112,8 @@ start_link(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_ctl:unregister_commands(
|
||||
State#state.host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered).
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
init(Host) ->
|
||||
State = parse_options(Host),
|
||||
@ -132,10 +129,6 @@ init(Host) ->
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
{ok, State}.
|
||||
|
||||
plain_password_required() ->
|
||||
|
@ -51,11 +51,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
|
328
src/ejabberd_commands.erl
Normal file
328
src/ejabberd_commands.erl
Normal file
@ -0,0 +1,328 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_commands.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Management of ejabberd commands
|
||||
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2008 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% @headerfile "ejabberd_commands.hrl"
|
||||
|
||||
%%% @doc Management of ejabberd commands.
|
||||
%%%
|
||||
%%% An ejabberd command is an abstract function identified by a name,
|
||||
%%% with a defined number and type of calling arguments and type of
|
||||
%%% result, that can be defined in any Erlang module and executed
|
||||
%%% using any valid frontend.
|
||||
%%%
|
||||
%%%
|
||||
%%% == Define a new ejabberd command ==
|
||||
%%%
|
||||
%%% ejabberd commands can be defined and registered in
|
||||
%%% any Erlang module.
|
||||
%%%
|
||||
%%% Some commands are procedures; and their purpose is to perform an
|
||||
%%% action in the server, so the command result is only some result
|
||||
%%% code or result tuple. Other commands are inspectors, and their
|
||||
%%% purpose is to gather some information about the server and return
|
||||
%%% a detailed response: it can be integer, string, atom, tuple, list
|
||||
%%% or a mix of those ones.
|
||||
%%%
|
||||
%%% The arguments and result of an ejabberd command are strictly
|
||||
%%% defined. The number and format of the arguments provided when
|
||||
%%% calling an ejabberd command must match the definition of that
|
||||
%%% command. The format of the result provided by an ejabberd command
|
||||
%%% must be exactly its definition. For example, if a command is said
|
||||
%%% to return an integer, it must always return an integer (except in
|
||||
%%% case of a crash).
|
||||
%%%
|
||||
%%% If you are developing an Erlang module that will run inside
|
||||
%%% ejabberd and you want to provide a new ejabberd command to
|
||||
%%% administer some task related to your module, you only need to:
|
||||
%%% implement a function, define the command, and register it.
|
||||
%%%
|
||||
%%%
|
||||
%%% === Define a new ejabberd command ===
|
||||
%%%
|
||||
%%% An ejabberd command is defined using the Erlang record
|
||||
%%% 'ejabberd_commands'. This record has several elements that you
|
||||
%%% must define. Note that 'tags', 'desc' and 'longdesc' are optional.
|
||||
%%%
|
||||
%%% For example let's define an ejabberd command 'pow' that gets the
|
||||
%%% integers 'base' and 'exponent'. Its result will be an integer
|
||||
%%% 'power':
|
||||
%%%
|
||||
%%% <pre>#ejabberd_commands{name = pow, tags = [test],
|
||||
%%% desc = "Return the power of base for exponent",
|
||||
%%% longdesc = "This is an example command. The formula is:\n"
|
||||
%%% " power = base ^ exponent",
|
||||
%%% module = ?MODULE, function = pow,
|
||||
%%% args = [{base, integer}, {exponent, integer}],
|
||||
%%% result = {power, integer}}</pre>
|
||||
%%%
|
||||
%%%
|
||||
%%% === Implement the function associated to the command ===
|
||||
%%%
|
||||
%%% Now implement a function in your module that matches the arguments
|
||||
%%% and result of the ejabberd command.
|
||||
%%%
|
||||
%%% For example the function calc_power gets two integers Base and
|
||||
%%% Exponent. It calculates the power and rounds to an integer:
|
||||
%%%
|
||||
%%% <pre>calc_power(Base, Exponent) ->
|
||||
%%% PowFloat = math:pow(Base, Exponent),
|
||||
%%% round(PowFloat).</pre>
|
||||
%%%
|
||||
%%% Since this function will be called by ejabberd_commands, it must be exported.
|
||||
%%% Add to your module:
|
||||
%%% <pre>-export([calc_power/2]).</pre>
|
||||
%%%
|
||||
%%% Only some types of result formats are allowed.
|
||||
%%% If the format is defined as 'rescode', then your function must return:
|
||||
%%% ok | true | atom()
|
||||
%%% where the atoms ok and true as considered positive answers,
|
||||
%%% and any other response atom is considered negative.
|
||||
%%%
|
||||
%%% If the format is defined as 'restuple', then the command must return:
|
||||
%%% {rescode(), string()}
|
||||
%%%
|
||||
%%% If the format is defined as '{list, something()}', then the command
|
||||
%%% must return a list of something().
|
||||
%%%
|
||||
%%%
|
||||
%%% === Register the command ===
|
||||
%%%
|
||||
%%% Define this function and put inside the #ejabberd_command you
|
||||
%%% defined in the beginning:
|
||||
%%%
|
||||
%%% <pre>commands() ->
|
||||
%%% [
|
||||
%%%
|
||||
%%% ].</pre>
|
||||
%%%
|
||||
%%% You need to include this header file in order to use the record:
|
||||
%%%
|
||||
%%% <pre>-include("ejabberd_commands.hrl").</pre>
|
||||
%%%
|
||||
%%% When your module is initialized or started, register your commands:
|
||||
%%%
|
||||
%%% <pre>ejabberd_commands:register_commands(commands()),</pre>
|
||||
%%%
|
||||
%%% And when your module is stopped, unregister your commands:
|
||||
%%%
|
||||
%%% <pre>ejabberd_commands:unregister_commands(commands()),</pre>
|
||||
%%%
|
||||
%%% That's all! Now when your module is started, the command will be
|
||||
%%% registered and any frontend can access it. For example:
|
||||
%%%
|
||||
%%% <pre>$ ejabberdctl help pow
|
||||
%%%
|
||||
%%% Command Name: pow
|
||||
%%%
|
||||
%%% Arguments: base::integer
|
||||
%%% exponent::integer
|
||||
%%%
|
||||
%%% Returns: power::integer
|
||||
%%%
|
||||
%%% Tags: test
|
||||
%%%
|
||||
%%% Description: Return the power of base for exponent
|
||||
%%%
|
||||
%%% This is an example command. The formula is:
|
||||
%%% power = base ^ exponent
|
||||
%%%
|
||||
%%% $ ejabberdctl pow 3 4
|
||||
%%% 81
|
||||
%%% </pre>
|
||||
%%%
|
||||
%%%
|
||||
%%% == Execute an ejabberd command ==
|
||||
%%%
|
||||
%%% ejabberd commands are mean to be executed using any valid
|
||||
%%% frontend. An ejabberd commands is implemented in a regular Erlang
|
||||
%%% function, so it is also possible to execute this function in any
|
||||
%%% Erlang module, without dealing with the associated ejabberd
|
||||
%%% commands.
|
||||
%%%
|
||||
%%%
|
||||
%%% == Frontend to ejabberd commands ==
|
||||
%%%
|
||||
%%% Currently there are two frontends to ejabberd commands: the shell
|
||||
%%% script {@link ejabberd_ctl. ejabberdctl}, and the XML-RPC server
|
||||
%%% ejabberd_xmlrpc.
|
||||
%%%
|
||||
%%%
|
||||
%%% === ejabberdctl as a frontend to ejabberd commands ===
|
||||
%%%
|
||||
%%% It is possible to use ejabberdctl to get documentation of any
|
||||
%%% command. But ejabberdctl does not support all the argument types
|
||||
%%% allowed in ejabberd commands, so there are some ejabberd commands
|
||||
%%% that cannot be executed using ejabberdctl.
|
||||
%%%
|
||||
%%% Also note that the ejabberdctl shell administration script also
|
||||
%%% manages ejabberdctl commands, which are unrelated to ejabberd
|
||||
%%% commands and can only be executed using ejabberdctl.
|
||||
%%%
|
||||
%%%
|
||||
%%% === ejabberd_xmlrpc as a frontend to ejabberd commands ===
|
||||
%%%
|
||||
%%% ejabberd_xmlrpc provides an XML-RPC server to execute ejabberd commands.
|
||||
%%% ejabberd_xmlrpc is a contributed module published in ejabberd-modules SVN.
|
||||
%%%
|
||||
%%% Since ejabberd_xmlrpc does not provide any method to get documentation
|
||||
%%% of the ejabberd commands, please use ejabberdctl to know which
|
||||
%%% commands are available, and their usage.
|
||||
%%%
|
||||
%%% The number and format of the arguments provided when calling an
|
||||
%%% ejabberd command must match the definition of that command. Please
|
||||
%%% make sure the XML-RPC call provides the required arguments, with
|
||||
%%% the specified format. The order of the arguments in an XML-RPC
|
||||
%%% call is not important, because all the data is tagged and will be
|
||||
%%% correctly prepared by ejabberd_xmlrpc before executing the ejabberd
|
||||
%%% command.
|
||||
|
||||
%%% TODO: consider this feature:
|
||||
%%% All commands are catched. If an error happens, return the restuple:
|
||||
%%% {error, flattened error string}
|
||||
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
|
||||
%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
|
||||
|
||||
|
||||
-module(ejabberd_commands).
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-export([init/0,
|
||||
list_commands/0,
|
||||
get_command_format/1,
|
||||
get_command_definition/1,
|
||||
get_tags_commands/0,
|
||||
register_commands/1,
|
||||
unregister_commands/1,
|
||||
execute_command/2
|
||||
]).
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
|
||||
init() ->
|
||||
ets:new(ejabberd_commands, [named_table, set, public,
|
||||
{keypos, #ejabberd_commands.name}]).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
%% @doc Register ejabberd commands.
|
||||
%% If a command is already registered, a warning is printed and the old command is preserved.
|
||||
register_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
case ets:insert_new(ejabberd_commands, Command) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
?WARNING_MSG("This command is already defined:~n~p", [Command])
|
||||
end
|
||||
end,
|
||||
Commands).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
%% @doc Unregister ejabberd commands.
|
||||
unregister_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
ets:delete_object(ejabberd_commands, Command)
|
||||
end,
|
||||
Commands).
|
||||
|
||||
%% @spec () -> [{Name::atom(), Args::[aterm()], Desc::string()}]
|
||||
%% @doc Get a list of all the available commands, arguments and description.
|
||||
list_commands() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = '$1',
|
||||
args = '$2',
|
||||
desc = '$3',
|
||||
_ = '_'}),
|
||||
[{A, B, C} || [A, B, C] <- Commands].
|
||||
|
||||
%% @spec (Name::atom()) -> {Args::[aterm()], Result::rterm()} | {error, command_unknown}
|
||||
%% @doc Get the format of arguments and result of a command.
|
||||
get_command_format(Name) ->
|
||||
Matched = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = Name,
|
||||
args = '$1',
|
||||
result = '$2',
|
||||
_ = '_'}),
|
||||
case Matched of
|
||||
[] ->
|
||||
{error, command_unknown};
|
||||
[[Args, Result]] ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom()) -> ejabberd_commands() | command_not_found
|
||||
%% @doc Get the definition record of a command.
|
||||
get_command_definition(Name) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[E] -> E;
|
||||
[] -> command_not_found
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
|
||||
%% @doc Execute a command.
|
||||
execute_command(Name, Arguments) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[Command] ->
|
||||
execute_command2(Command, Arguments);
|
||||
[] ->
|
||||
{error, command_unknown}
|
||||
end.
|
||||
|
||||
execute_command2(Command, Arguments) ->
|
||||
Module = Command#ejabberd_commands.module,
|
||||
Function = Command#ejabberd_commands.function,
|
||||
apply(Module, Function, Arguments).
|
||||
|
||||
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands.
|
||||
get_tags_commands() ->
|
||||
CommandTags = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{
|
||||
name = '$1',
|
||||
tags = '$2',
|
||||
_ = '_'}),
|
||||
Dict = lists:foldl(
|
||||
fun([CommandNameAtom, CTags], D) ->
|
||||
CommandName = atom_to_list(CommandNameAtom),
|
||||
case CTags of
|
||||
[] ->
|
||||
orddict:append("untagged", CommandName, D);
|
||||
_ ->
|
||||
lists:foldl(
|
||||
fun(TagAtom, DD) ->
|
||||
Tag = atom_to_list(TagAtom),
|
||||
orddict:append(Tag, CommandName, DD)
|
||||
end,
|
||||
D,
|
||||
CTags)
|
||||
end
|
||||
end,
|
||||
orddict:new(),
|
||||
CommandTags),
|
||||
orddict:to_list(Dict).
|
52
src/ejabberd_commands.hrl
Normal file
52
src/ejabberd_commands.hrl
Normal file
@ -0,0 +1,52 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2008 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(ejabberd_commands, {name, tags = [],
|
||||
desc = "", longdesc = "",
|
||||
module, function,
|
||||
args = [], result = rescode}).
|
||||
|
||||
%% @type ejabberd_commands() = #ejabberd_commands{
|
||||
%% name = atom(),
|
||||
%% tags = [atom()],
|
||||
%% desc = string(),
|
||||
%% longdesc = string(),
|
||||
%% module = atom(),
|
||||
%% function = atom(),
|
||||
%% args = [aterm()],
|
||||
%% result = rterm()
|
||||
%% }.
|
||||
%% desc: Description of the command
|
||||
%% args: Describe the accepted arguments.
|
||||
%% This way the function that calls the command can format the
|
||||
%% arguments before calling.
|
||||
|
||||
%% @type atype() = integer | string | {tuple, [aterm()]} | {list, aterm()}.
|
||||
%% Allowed types for arguments are integer, string, tuple and list.
|
||||
|
||||
%% @type rtype() = integer | string | atom | {tuple, [rterm()]} | {list, rterm()} | rescode | restuple.
|
||||
%% A rtype is either an atom or a tuple with two elements.
|
||||
|
||||
%% @type aterm() = {Name::atom(), Type::atype()}.
|
||||
%% An argument term is a tuple with the term name and the term type.
|
||||
|
||||
%% @type rterm() = {Name::atom(), Type::rtype()}.
|
||||
%% A result term is a tuple with the term name and the term type.
|
File diff suppressed because it is too large
Load Diff
@ -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).
|
||||
|
||||
|
@ -39,7 +39,8 @@
|
||||
remove_connection/3,
|
||||
dirty_get_connections/0,
|
||||
allow_host/2,
|
||||
ctl_process/2
|
||||
incoming_s2s_number/0,
|
||||
outgoing_s2s_number/0
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@ -49,7 +50,7 @@
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
|
||||
@ -182,10 +183,7 @@ init([]) ->
|
||||
{attributes, record_info(fields, s2s)}]),
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ejabberd_ctl:register_commands(
|
||||
[{"incoming-s2s-number", "print number of incoming s2s connections on the node"},
|
||||
{"outgoing-s2s-number", "print number of outgoing s2s connections on the node"}],
|
||||
?MODULE, ctl_process),
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -249,6 +247,7 @@ handle_info(_Info, State) ->
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -453,16 +452,35 @@ is_subdomain(Domain1, Domain2) ->
|
||||
send_element(Pid, El) ->
|
||||
Pid ! {send_element, El}.
|
||||
|
||||
ctl_process(_Val, ["incoming-s2s-number"]) ->
|
||||
N = length(supervisor:which_children(ejabberd_s2s_in_sup)),
|
||||
?PRINT("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["outgoing-s2s-number"]) ->
|
||||
N = length(supervisor:which_children(ejabberd_s2s_out_sup)),
|
||||
?PRINT("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(Val, _Args) ->
|
||||
Val.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% ejabberd commands
|
||||
|
||||
commands() ->
|
||||
[
|
||||
#ejabberd_commands{name = incoming_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of incoming s2s connections on the node",
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [],
|
||||
result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{name = outgoing_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of outgoing s2s connections on the node",
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [],
|
||||
result = {s2s_outgoing, integer}}
|
||||
].
|
||||
|
||||
incoming_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_in_sup)).
|
||||
|
||||
outgoing_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_out_sup)).
|
||||
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Update Mnesia tables
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(s2s, type) of
|
||||
|
@ -46,7 +46,9 @@
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
ctl_process/2,
|
||||
connected_users/0,
|
||||
connected_users_number/0,
|
||||
user_resources/2,
|
||||
get_session_pid/3,
|
||||
get_user_info/3,
|
||||
get_user_ip/3
|
||||
@ -59,7 +61,7 @@
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
-record(session, {sid, usr, us, priority, info}).
|
||||
-record(state, {}).
|
||||
@ -269,11 +271,7 @@ init([]) ->
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
ejabberd_sm, disconnect_removed_user, 100)
|
||||
end, ?MYHOSTS),
|
||||
ejabberd_ctl:register_commands(
|
||||
[{"connected-users", "list all established sessions"},
|
||||
{"connected-users-number", "print a number of established sessions"},
|
||||
{"user-resources user server", "print user's connected resources"}],
|
||||
?MODULE, ctl_process),
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
|
||||
{ok, #state{}}.
|
||||
|
||||
@ -353,6 +351,7 @@ handle_info(_Info, State) ->
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -670,27 +669,46 @@ process_iq(From, To, Packet) ->
|
||||
end.
|
||||
|
||||
|
||||
ctl_process(_Val, ["connected-users"]) ->
|
||||
USRs = dirty_get_sessions_list(),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUSRs = lists:sort(USRs),
|
||||
FUSRs = lists:map(fun({U, S, R}) -> [U, $@, S, $/, R, NewLine] end, SUSRs),
|
||||
?PRINT("~s", [FUSRs]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["connected-users-number"]) ->
|
||||
N = length(dirty_get_sessions_list()),
|
||||
?PRINT("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["user-resources", User, Server]) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SResources = lists:sort(Resources),
|
||||
FResources = lists:map(fun(R) -> [R, NewLine] end, SResources),
|
||||
?PRINT("~s", [FResources]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(Val, _Args) ->
|
||||
Val.
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% ejabberd commands
|
||||
|
||||
commands() ->
|
||||
[
|
||||
#ejabberd_commands{name = connected_users,
|
||||
tags = [session],
|
||||
desc = "List all established sessions",
|
||||
module = ?MODULE, function = connected_users,
|
||||
args = [],
|
||||
result = {connected_users, {list, {sessions, string}}}},
|
||||
#ejabberd_commands{name = connected_users_number,
|
||||
tags = [session, stats],
|
||||
desc = "Get the number of established sessions",
|
||||
module = ?MODULE, function = connected_users_number,
|
||||
args = [],
|
||||
result = {num_sessions, integer}},
|
||||
#ejabberd_commands{name = user_resources,
|
||||
tags = [session],
|
||||
desc = "List user's connected resources",
|
||||
module = ?MODULE, function = user_resources,
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {resources, {list, {resource, string}}}}
|
||||
].
|
||||
|
||||
connected_users() ->
|
||||
USRs = dirty_get_sessions_list(),
|
||||
SUSRs = lists:sort(USRs),
|
||||
lists:map(fun({U, S, R}) -> [U, $@, S, $/, R] end, SUSRs).
|
||||
|
||||
connected_users_number() ->
|
||||
length(dirty_get_sessions_list()).
|
||||
|
||||
user_resources(User, Server) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
lists:sort(Resources).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Update Mnesia tables
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(session, attributes) of
|
||||
|
@ -77,6 +77,8 @@ start(Module, SockMod, Socket, Opts) ->
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
end;
|
||||
independent ->
|
||||
ok;
|
||||
raw ->
|
||||
case Module:start({SockMod, Socket}, Opts) of
|
||||
{ok, Pid} ->
|
||||
|
@ -99,21 +99,6 @@ init([]) ->
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
C2SSupervisor =
|
||||
{ejabberd_c2s_sup,
|
||||
{ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
S2SInSupervisor =
|
||||
{ejabberd_s2s_in_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
[ejabberd_s2s_in_sup, ejabberd_s2s_in]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
S2SOutSupervisor =
|
||||
{ejabberd_s2s_out_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
@ -130,14 +115,6 @@ init([]) ->
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
HTTPSupervisor =
|
||||
{ejabberd_http_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
[ejabberd_http_sup, ejabberd_http]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
HTTPPollSupervisor =
|
||||
{ejabberd_http_poll_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
@ -171,11 +148,8 @@ init([]) ->
|
||||
S2S,
|
||||
Local,
|
||||
ReceiverSupervisor,
|
||||
C2SSupervisor,
|
||||
S2SInSupervisor,
|
||||
S2SOutSupervisor,
|
||||
ServiceSupervisor,
|
||||
HTTPSupervisor,
|
||||
HTTPPollSupervisor,
|
||||
IQSupervisor,
|
||||
FrontendSocketSupervisor,
|
||||
|
@ -163,6 +163,23 @@ live ()
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
}
|
||||
|
||||
help ()
|
||||
{
|
||||
echo ""
|
||||
echo "Commands to start an ejabberd node:"
|
||||
echo " start Start an ejabberd node in server mode"
|
||||
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
|
||||
echo " live Start an ejabberd node in live (interactive) mode"
|
||||
echo ""
|
||||
echo "Optional parameters when starting an ejabberd node:"
|
||||
echo " --config file Config ejabberd: $EJABBERD_CONFIG_PATH"
|
||||
echo " --ctl-config file Config ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
|
||||
echo " --logs dir Directory for logs: $LOGS_DIR"
|
||||
echo " --spool dir Database spool dir: $SPOOLDIR"
|
||||
echo " --node nodename ejabberd node name: $ERLANG_NODE"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# common control function
|
||||
ctl ()
|
||||
{
|
||||
@ -175,20 +192,9 @@ ctl ()
|
||||
result=$?
|
||||
case $result in
|
||||
0) :;;
|
||||
*)
|
||||
echo ""
|
||||
echo "Commands to start an ejabberd node:"
|
||||
echo " start Start an ejabberd node in server mode"
|
||||
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
|
||||
echo " live Start an ejabberd node in live (interactive) mode"
|
||||
echo ""
|
||||
echo "Optional parameters when starting an ejabberd node:"
|
||||
echo " --config file Config file of ejabberd: $EJABBERD_CONFIG_PATH"
|
||||
echo " --ctl-config file Config file of ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
|
||||
echo " --logs dir Directory for logs: $LOGS_DIR"
|
||||
echo " --spool dir Database spool dir: $SPOOLDIR"
|
||||
echo " --node nodename ejabberd node name: $ERLANG_NODE"
|
||||
echo "";;
|
||||
1) :;;
|
||||
2) help;;
|
||||
3) help;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
474
src/jlib.erl
474
src/jlib.erl
@ -27,32 +27,7 @@
|
||||
-module(jlib).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([make_result_iq_reply/1,
|
||||
make_error_reply/3,
|
||||
make_error_reply/2,
|
||||
make_error_element/2,
|
||||
make_correct_from_to_attrs/3,
|
||||
replace_from_to_attrs/3,
|
||||
replace_from_to/3,
|
||||
remove_attr/2,
|
||||
make_jid/3,
|
||||
make_jid/1,
|
||||
string_to_jid/1,
|
||||
jid_to_string/1,
|
||||
is_nodename/1,
|
||||
tolower/1,
|
||||
nodeprep/1,
|
||||
nameprep/1,
|
||||
resourceprep/1,
|
||||
jid_tolower/1,
|
||||
jid_remove_resource/1,
|
||||
jid_replace_resource/2,
|
||||
get_iq_namespace/1,
|
||||
iq_query_info/1,
|
||||
iq_query_or_response_info/1,
|
||||
is_iq_request_type/1,
|
||||
iq_to_xml/1,
|
||||
parse_xdata_submit/1,
|
||||
-export([parse_xdata_submit/1,
|
||||
timestamp_to_iso/1,
|
||||
timestamp_to_xml/1,
|
||||
now_to_utc_string/1,
|
||||
@ -68,410 +43,21 @@
|
||||
short_prepd_jid/1,
|
||||
short_prepd_bare_jid/1]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp_xml.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
%send_iq(From, To, ID, SubTags) ->
|
||||
% ok.
|
||||
|
||||
make_result_iq_reply({xmlelement, Name, Attrs, SubTags}) ->
|
||||
NewAttrs = make_result_iq_reply_attrs(Attrs),
|
||||
{xmlelement, Name, NewAttrs, SubTags}.
|
||||
|
||||
make_result_iq_reply_attrs(Attrs) ->
|
||||
To = xml:get_attr("to", Attrs),
|
||||
From = xml:get_attr("from", Attrs),
|
||||
Attrs1 = lists:keydelete("to", 1, Attrs),
|
||||
Attrs2 = lists:keydelete("from", 1, Attrs1),
|
||||
Attrs3 = case To of
|
||||
{value, ToVal} ->
|
||||
[{"from", ToVal} | Attrs2];
|
||||
_ ->
|
||||
Attrs2
|
||||
end,
|
||||
Attrs4 = case From of
|
||||
{value, FromVal} ->
|
||||
[{"to", FromVal} | Attrs3];
|
||||
_ ->
|
||||
Attrs3
|
||||
end,
|
||||
Attrs5 = lists:keydelete("type", 1, Attrs4),
|
||||
Attrs6 = [{"type", "result"} | Attrs5],
|
||||
Attrs6.
|
||||
|
||||
make_error_reply({xmlelement, Name, Attrs, SubTags}, Code, Desc) ->
|
||||
NewAttrs = make_error_reply_attrs(Attrs),
|
||||
{xmlelement, Name, NewAttrs, SubTags ++ [{xmlelement, "error",
|
||||
[{"code", Code}],
|
||||
[{xmlcdata, Desc}]}]}.
|
||||
|
||||
make_error_reply({xmlelement, Name, Attrs, SubTags}, Error) ->
|
||||
NewAttrs = make_error_reply_attrs(Attrs),
|
||||
{xmlelement, Name, NewAttrs, SubTags ++ [Error]}.
|
||||
|
||||
make_error_reply_attrs(Attrs) ->
|
||||
To = xml:get_attr("to", Attrs),
|
||||
From = xml:get_attr("from", Attrs),
|
||||
Attrs1 = lists:keydelete("to", 1, Attrs),
|
||||
Attrs2 = lists:keydelete("from", 1, Attrs1),
|
||||
Attrs3 = case To of
|
||||
{value, ToVal} ->
|
||||
[{"from", ToVal} | Attrs2];
|
||||
_ ->
|
||||
Attrs2
|
||||
end,
|
||||
Attrs4 = case From of
|
||||
{value, FromVal} ->
|
||||
[{"to", FromVal} | Attrs3];
|
||||
_ ->
|
||||
Attrs3
|
||||
end,
|
||||
Attrs5 = lists:keydelete("type", 1, Attrs4),
|
||||
Attrs6 = [{"type", "error"} | Attrs5],
|
||||
Attrs6.
|
||||
|
||||
make_error_element(Code, Desc) ->
|
||||
{xmlelement, "error",
|
||||
[{"code", Code}],
|
||||
[{xmlcdata, Desc}]}.
|
||||
|
||||
make_correct_from_to_attrs(From, To, Attrs) ->
|
||||
Attrs1 = lists:keydelete("from", 1, Attrs),
|
||||
Attrs2 = case xml:get_attr("to", Attrs) of
|
||||
{value, _} ->
|
||||
Attrs1;
|
||||
_ ->
|
||||
[{"to", To} | Attrs1]
|
||||
end,
|
||||
Attrs3 = [{"from", From} | Attrs2],
|
||||
Attrs3.
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
|
||||
replace_from_to_attrs(From, To, Attrs) ->
|
||||
Attrs1 = lists:keydelete("to", 1, Attrs),
|
||||
Attrs2 = lists:keydelete("from", 1, Attrs1),
|
||||
Attrs3 = [{"to", To} | Attrs2],
|
||||
Attrs4 = [{"from", From} | Attrs3],
|
||||
Attrs4.
|
||||
|
||||
replace_from_to(From, To, {xmlelement, Name, Attrs, Els}) ->
|
||||
NewAttrs = replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
{xmlelement, Name, NewAttrs, Els}.
|
||||
|
||||
|
||||
remove_attr(Attr, {xmlelement, Name, Attrs, Els}) ->
|
||||
NewAttrs = lists:keydelete(Attr, 1, Attrs),
|
||||
{xmlelement, Name, NewAttrs, Els}.
|
||||
|
||||
|
||||
make_jid(User, Server, Resource) ->
|
||||
case nodeprep(User) of
|
||||
error -> error;
|
||||
LUser ->
|
||||
case nameprep(Server) of
|
||||
error -> error;
|
||||
LServer ->
|
||||
case resourceprep(Resource) of
|
||||
error -> error;
|
||||
LResource ->
|
||||
#jid{user = User,
|
||||
server = Server,
|
||||
resource = Resource,
|
||||
luser = LUser,
|
||||
lserver = LServer,
|
||||
lresource = LResource}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
make_jid({User, Server, Resource}) ->
|
||||
make_jid(User, Server, Resource).
|
||||
|
||||
string_to_jid(J) ->
|
||||
string_to_jid1(J, "").
|
||||
|
||||
string_to_jid1([$@ | _J], "") ->
|
||||
error;
|
||||
string_to_jid1([$@ | J], N) ->
|
||||
string_to_jid2(J, lists:reverse(N), "");
|
||||
string_to_jid1([$/ | _J], "") ->
|
||||
error;
|
||||
string_to_jid1([$/ | J], N) ->
|
||||
string_to_jid3(J, "", lists:reverse(N), "");
|
||||
string_to_jid1([C | J], N) ->
|
||||
string_to_jid1(J, [C | N]);
|
||||
string_to_jid1([], "") ->
|
||||
error;
|
||||
string_to_jid1([], N) ->
|
||||
make_jid("", lists:reverse(N), "").
|
||||
|
||||
%% Only one "@" is admitted per JID
|
||||
string_to_jid2([$@ | _J], _N, _S) ->
|
||||
error;
|
||||
string_to_jid2([$/ | _J], _N, "") ->
|
||||
error;
|
||||
string_to_jid2([$/ | J], N, S) ->
|
||||
string_to_jid3(J, N, lists:reverse(S), "");
|
||||
string_to_jid2([C | J], N, S) ->
|
||||
string_to_jid2(J, N, [C | S]);
|
||||
string_to_jid2([], _N, "") ->
|
||||
error;
|
||||
string_to_jid2([], N, S) ->
|
||||
make_jid(N, lists:reverse(S), "").
|
||||
|
||||
string_to_jid3([C | J], N, S, R) ->
|
||||
string_to_jid3(J, N, S, [C | R]);
|
||||
string_to_jid3([], N, S, R) ->
|
||||
make_jid(N, S, lists:reverse(R)).
|
||||
|
||||
jid_to_string(#jid{user = User, server = Server, resource = Resource}) ->
|
||||
jid_to_string({User, Server, Resource});
|
||||
jid_to_string({Node, Server, Resource}) ->
|
||||
S1 = case Node of
|
||||
"" ->
|
||||
"";
|
||||
_ ->
|
||||
Node ++ "@"
|
||||
end,
|
||||
S2 = S1 ++ Server,
|
||||
S3 = case Resource of
|
||||
"" ->
|
||||
S2;
|
||||
_ ->
|
||||
S2 ++ "/" ++ Resource
|
||||
end,
|
||||
S3.
|
||||
|
||||
|
||||
is_nodename([]) ->
|
||||
false;
|
||||
is_nodename(J) ->
|
||||
nodeprep(J) /= error.
|
||||
|
||||
|
||||
%tolower_c(C) when C >= $A, C =< $Z ->
|
||||
% C + 32;
|
||||
%tolower_c(C) ->
|
||||
% C.
|
||||
|
||||
-define(LOWER(Char),
|
||||
if
|
||||
Char >= $A, Char =< $Z ->
|
||||
Char + 32;
|
||||
true ->
|
||||
Char
|
||||
end).
|
||||
|
||||
%tolower(S) ->
|
||||
% lists:map(fun tolower_c/1, S).
|
||||
|
||||
%tolower(S) ->
|
||||
% [?LOWER(Char) || Char <- S].
|
||||
|
||||
% Not tail-recursive but it seems works faster than variants above
|
||||
tolower([C | Cs]) ->
|
||||
if
|
||||
C >= $A, C =< $Z ->
|
||||
[C + 32 | tolower(Cs)];
|
||||
true ->
|
||||
[C | tolower(Cs)]
|
||||
end;
|
||||
tolower([]) ->
|
||||
[].
|
||||
|
||||
%tolower([C | Cs]) when C >= $A, C =< $Z ->
|
||||
% [C + 32 | tolower(Cs)];
|
||||
%tolower([C | Cs]) ->
|
||||
% [C | tolower(Cs)];
|
||||
%tolower([]) ->
|
||||
% [].
|
||||
|
||||
|
||||
nodeprep(S) when length(S) < 1024 ->
|
||||
R = stringprep:nodeprep(S),
|
||||
if
|
||||
length(R) < 1024 -> R;
|
||||
true -> error
|
||||
end;
|
||||
nodeprep(_) ->
|
||||
error.
|
||||
|
||||
nameprep(S) when length(S) < 1024 ->
|
||||
R = stringprep:nameprep(S),
|
||||
if
|
||||
length(R) < 1024 -> R;
|
||||
true -> error
|
||||
end;
|
||||
nameprep(_) ->
|
||||
error.
|
||||
|
||||
resourceprep(S) when length(S) < 1024 ->
|
||||
R = stringprep:resourceprep(S),
|
||||
if
|
||||
length(R) < 1024 -> R;
|
||||
true -> error
|
||||
end;
|
||||
resourceprep(_) ->
|
||||
error.
|
||||
|
||||
|
||||
jid_tolower(#jid{luser = U, lserver = S, lresource = R}) ->
|
||||
{U, S, R};
|
||||
jid_tolower({U, S, R}) ->
|
||||
case nodeprep(U) of
|
||||
error -> error;
|
||||
LUser ->
|
||||
case nameprep(S) of
|
||||
error -> error;
|
||||
LServer ->
|
||||
case resourceprep(R) of
|
||||
error -> error;
|
||||
LResource ->
|
||||
{LUser, LServer, LResource}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
jid_remove_resource(#jid{} = JID) ->
|
||||
JID#jid{resource = "", lresource = ""};
|
||||
jid_remove_resource({U, S, _R}) ->
|
||||
{U, S, ""}.
|
||||
|
||||
jid_replace_resource(JID, Resource) ->
|
||||
case resourceprep(Resource) of
|
||||
error -> error;
|
||||
LResource ->
|
||||
JID#jid{resource = Resource, lresource = LResource}
|
||||
end.
|
||||
|
||||
|
||||
get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
|
||||
case xml:remove_cdata(Els) of
|
||||
[{xmlelement, _Name2, Attrs2, _Els2}] ->
|
||||
xml:get_attr_s("xmlns", Attrs2);
|
||||
_ ->
|
||||
""
|
||||
end;
|
||||
get_iq_namespace(_) ->
|
||||
"".
|
||||
|
||||
iq_query_info(El) ->
|
||||
iq_info_internal(El, request).
|
||||
|
||||
iq_query_or_response_info(El) ->
|
||||
iq_info_internal(El, any).
|
||||
|
||||
iq_info_internal({xmlel, NS, _, _, _, _} = El, Filter) ->
|
||||
ElOld = exmpp_xml:xmlel_to_xmlelement(El, [NS],
|
||||
[{'http://etherx.jabber.org/streams', "stream"}]),
|
||||
iq_info_internal2(ElOld, Filter);
|
||||
iq_info_internal(El, Filter) ->
|
||||
catch throw(for_stacktrace),
|
||||
io:format("~nJLIB: old #xmlelement:~n~p~n~p~n~n",
|
||||
[El, erlang:get_stacktrace()]),
|
||||
iq_info_internal2(El, Filter).
|
||||
|
||||
iq_info_internal2({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
|
||||
%% Filter is either request or any. If it is request, any replies
|
||||
%% are converted to the atom reply.
|
||||
ID = xml:get_attr_s("id", Attrs),
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
{Type1, Class} = case Type of
|
||||
"set" -> {set, request};
|
||||
"get" -> {get, request};
|
||||
"result" -> {result, reply};
|
||||
"error" -> {error, reply};
|
||||
_ -> {invalid, invalid}
|
||||
end,
|
||||
if
|
||||
Type1 == invalid ->
|
||||
invalid;
|
||||
Class == request; Filter == any ->
|
||||
%% The iq record is a bit strange. The sub_el field is an
|
||||
%% XML tuple for requests, but a list of XML tuples for
|
||||
%% responses.
|
||||
FilteredEls = xml:remove_cdata(Els),
|
||||
{XMLNS, SubEl} =
|
||||
case {Class, FilteredEls} of
|
||||
{request, [{xmlelement, _Name2, Attrs2, _Els2}]} ->
|
||||
{xml:get_attr_s("xmlns", Attrs2),
|
||||
hd(FilteredEls)};
|
||||
{reply, _} ->
|
||||
%% Find the namespace of the first non-error
|
||||
%% element, if there is one.
|
||||
NonErrorEls = [El ||
|
||||
{xmlelement, SubName, _, _} = El
|
||||
<- FilteredEls,
|
||||
SubName /= "error"],
|
||||
{case NonErrorEls of
|
||||
[NonErrorEl] -> xml:get_tag_attr_s("xmlns", NonErrorEl);
|
||||
_ -> invalid
|
||||
end,
|
||||
FilteredEls};
|
||||
_ ->
|
||||
{invalid, invalid}
|
||||
end,
|
||||
if XMLNS == "", Class == request ->
|
||||
invalid;
|
||||
true ->
|
||||
#iq{id = ID,
|
||||
type = Type1,
|
||||
xmlns = XMLNS,
|
||||
lang = Lang,
|
||||
sub_el = SubEl}
|
||||
end;
|
||||
Class == reply, Filter /= any ->
|
||||
reply
|
||||
end;
|
||||
iq_info_internal2(_, _) ->
|
||||
not_iq.
|
||||
|
||||
is_iq_request_type(set) -> true;
|
||||
is_iq_request_type(get) -> true;
|
||||
is_iq_request_type(_) -> false.
|
||||
|
||||
iq_type_to_string(set) -> "set";
|
||||
iq_type_to_string(get) -> "get";
|
||||
iq_type_to_string(result) -> "result";
|
||||
iq_type_to_string(error) -> "error";
|
||||
iq_type_to_string(_) -> invalid.
|
||||
|
||||
|
||||
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
|
||||
if
|
||||
ID /= "" ->
|
||||
{xmlelement, "iq",
|
||||
[{"id", ID}, {"type", iq_type_to_string(Type)}], SubEl};
|
||||
true ->
|
||||
{xmlelement, "iq",
|
||||
[{"type", iq_type_to_string(Type)}], SubEl}
|
||||
end.
|
||||
|
||||
|
||||
parse_xdata_submit({xmlel, _, _, _, Attrs, Els}) ->
|
||||
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
|
||||
case exmpp_xml:get_attribute_from_list(Attrs, 'type', "") of
|
||||
"submit" ->
|
||||
lists:reverse(parse_xdata_fields(Els, []));
|
||||
_ ->
|
||||
invalid
|
||||
end;
|
||||
parse_xdata_submit(El) ->
|
||||
{xmlelement, _Name, Attrs, Els} = El,
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"submit" ->
|
||||
lists:reverse(parse_xdata_fields(Els, []));
|
||||
_ ->
|
||||
invalid
|
||||
end.
|
||||
|
||||
parse_xdata_fields([], Res) ->
|
||||
Res;
|
||||
parse_xdata_fields([{xmlel, _, _, 'field', Attrs, SubEls} | Els],
|
||||
Res) ->
|
||||
parse_xdata_fields([#xmlel{name = 'field', attrs = Attrs, children = SubEls} |
|
||||
Els], Res) ->
|
||||
case exmpp_xml:get_attribute_from_list(Attrs, 'var', "") of
|
||||
"" ->
|
||||
parse_xdata_fields(Els, Res);
|
||||
@ -479,36 +65,14 @@ parse_xdata_fields([{xmlel, _, _, 'field', Attrs, SubEls} | Els],
|
||||
Field = {Var, lists:reverse(parse_xdata_values(SubEls, []))},
|
||||
parse_xdata_fields(Els, [Field | Res])
|
||||
end;
|
||||
parse_xdata_fields([{xmlelement, Name, Attrs, SubEls} | Els], Res) ->
|
||||
case Name of
|
||||
"field" ->
|
||||
case xml:get_attr_s("var", Attrs) of
|
||||
"" ->
|
||||
parse_xdata_fields(Els, Res);
|
||||
Var ->
|
||||
Field =
|
||||
{Var, lists:reverse(parse_xdata_values(SubEls, []))},
|
||||
parse_xdata_fields(Els, [Field | Res])
|
||||
end;
|
||||
_ ->
|
||||
parse_xdata_fields(Els, Res)
|
||||
end;
|
||||
parse_xdata_fields([_ | Els], Res) ->
|
||||
parse_xdata_fields(Els, Res).
|
||||
|
||||
parse_xdata_values([], Res) ->
|
||||
Res;
|
||||
parse_xdata_values([{xmlel, _, _, 'value', _, SubEls} | Els], Res) ->
|
||||
parse_xdata_values([#xmlel{name = 'value', children = SubEls} | Els], Res) ->
|
||||
Val = exmpp_xml:get_cdata_from_list_as_list(SubEls),
|
||||
parse_xdata_values(Els, [Val | Res]);
|
||||
parse_xdata_values([{xmlelement, Name, _Attrs, SubEls} | Els], Res) ->
|
||||
case Name of
|
||||
"value" ->
|
||||
Val = xml:get_cdata(SubEls),
|
||||
parse_xdata_values(Els, [Val | Res]);
|
||||
_ ->
|
||||
parse_xdata_values(Els, Res)
|
||||
end;
|
||||
parse_xdata_values([_ | Els], Res) ->
|
||||
parse_xdata_values(Els, Res).
|
||||
|
||||
@ -522,7 +86,7 @@ timestamp_to_xml({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||
Timestamp = lists:flatten(
|
||||
io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
|
||||
[Year, Month, Day, Hour, Minute, Second])),
|
||||
exmpp_xml:set_attribute(#xmlel{ns = ?NS_DELAY, name = 'x'},
|
||||
exmpp_xml:set_attribute(#xmlel{ns = ?NS_DELAY_OLD, name = 'x'},
|
||||
'stamp', Timestamp).
|
||||
|
||||
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
|
||||
@ -731,8 +295,8 @@ ip_to_list({A,B,C,D}) ->
|
||||
%%
|
||||
%% Empty fields are set to `undefined', not the empty string.
|
||||
|
||||
from_old_jid(#jid{user = Node, resource = Resource,
|
||||
luser = LNode, lresource = LResource} = JID) ->
|
||||
from_old_jid(#jid{node = Node, resource = Resource,
|
||||
lnode = LNode, lresource = LResource} = JID) ->
|
||||
{Node1, LNode1} = case Node of
|
||||
"" -> {undefined, undefined};
|
||||
_ -> {Node, LNode}
|
||||
@ -741,8 +305,8 @@ from_old_jid(#jid{user = Node, resource = Resource,
|
||||
"" -> {undefined, undefined};
|
||||
_ -> {Resource, LResource}
|
||||
end,
|
||||
JID#jid{user = Node1, resource = Resource1,
|
||||
luser = LNode1, lresource = LResource1}.
|
||||
JID#jid{node = Node1, resource = Resource1,
|
||||
lnode = LNode1, lresource = LResource1}.
|
||||
|
||||
%% @spec (JID) -> New_JID
|
||||
%% JID = jid()
|
||||
@ -751,8 +315,8 @@ from_old_jid(#jid{user = Node, resource = Resource,
|
||||
%%
|
||||
%% Empty fields are set to the empty string, not `undefined'.
|
||||
|
||||
to_old_jid(#jid{user = Node, resource = Resource,
|
||||
luser = LNode, lresource = LResource} = JID) ->
|
||||
to_old_jid(#jid{node = Node, resource = Resource,
|
||||
lnode = LNode, lresource = LResource} = JID) ->
|
||||
{Node1, LNode1} = case Node of
|
||||
undefined -> {"", ""};
|
||||
_ -> {Node, LNode}
|
||||
@ -761,19 +325,19 @@ to_old_jid(#jid{user = Node, resource = Resource,
|
||||
undefined -> {"", ""};
|
||||
_ -> {Resource, LResource}
|
||||
end,
|
||||
JID#jid{user = Node1, resource = Resource1,
|
||||
luser = LNode1, lresource = LResource1}.
|
||||
JID#jid{node = Node1, resource = Resource1,
|
||||
lnode = LNode1, lresource = LResource1}.
|
||||
|
||||
short_jid(JID) ->
|
||||
{JID#jid.user, JID#jid.server, JID#jid.resource}.
|
||||
{JID#jid.node, JID#jid.domain, JID#jid.resource}.
|
||||
|
||||
short_bare_jid(JID) ->
|
||||
Bare_JID = exmpp_jid:jid_to_bare_jid(JID),
|
||||
{Bare_JID#jid.user, Bare_JID#jid.server, Bare_JID#jid.resource}.
|
||||
{Bare_JID#jid.node, Bare_JID#jid.domain, Bare_JID#jid.resource}.
|
||||
|
||||
short_prepd_jid(JID) ->
|
||||
{JID#jid.luser, JID#jid.lserver, JID#jid.lresource}.
|
||||
{JID#jid.lnode, JID#jid.ldomain, JID#jid.lresource}.
|
||||
|
||||
short_prepd_bare_jid(JID) ->
|
||||
Bare_JID = exmpp_jid:jid_to_bare_jid(JID),
|
||||
{Bare_JID#jid.luser, Bare_JID#jid.lserver, Bare_JID#jid.lresource}.
|
||||
{Bare_JID#jid.lnode, Bare_JID#jid.ldomain, Bare_JID#jid.lresource}.
|
||||
|
@ -1406,7 +1406,7 @@ set_form(_From, _Host, ["running nodes", ENode, "backup", "textfile"], _Lang, XD
|
||||
false ->
|
||||
{error, 'bad-request'};
|
||||
{value, {_, [String]}} ->
|
||||
case rpc:call(Node, ejabberd_ctl, dump_to_textfile, [String]) of
|
||||
case rpc:call(Node, ejabberd_admin, dump_to_textfile, [String]) of
|
||||
{badrpc, _Reason} ->
|
||||
{error, 'internal-server-error'};
|
||||
{error, _Reason} ->
|
||||
|
@ -39,7 +39,8 @@
|
||||
remove_old_messages/1,
|
||||
remove_user/2,
|
||||
webadmin_page/3,
|
||||
webadmin_user/4]).
|
||||
webadmin_user/4,
|
||||
webadmin_user_parse_query/5]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
@ -75,6 +76,8 @@ start(Host, Opts) ->
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:add(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:add(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MaxOfflineMsgs])).
|
||||
@ -143,6 +146,8 @@ stop(Host) ->
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:delete(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
exit(whereis(Proc), stop),
|
||||
{wait, Proc}.
|
||||
@ -642,4 +647,24 @@ webadmin_user(Acc, User, Server, Lang) ->
|
||||
_ ->
|
||||
[?C("?")]
|
||||
end,
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen.
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||
|
||||
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
|
||||
US = {User, Server},
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(offline_msg),
|
||||
lists:foreach(
|
||||
fun(Msg) ->
|
||||
mnesia:delete_object(Msg)
|
||||
end, mnesia:dirty_read({offline_msg, US}))
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
{atomic, ok} ->
|
||||
?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]),
|
||||
{stop, ok}
|
||||
end;
|
||||
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
|
||||
Acc.
|
||||
|
@ -38,7 +38,8 @@
|
||||
pop_offline_messages/3,
|
||||
remove_user/2,
|
||||
webadmin_page/3,
|
||||
webadmin_user/4]).
|
||||
webadmin_user/4,
|
||||
webadmin_user_parse_query/5]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
@ -69,6 +70,8 @@ start(Host, Opts) ->
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:add(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:add(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [Host, MaxOfflineMsgs])).
|
||||
@ -151,6 +154,8 @@ stop(Host) ->
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:delete(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
exit(whereis(Proc), stop),
|
||||
ok.
|
||||
@ -446,7 +451,22 @@ webadmin_user(Acc, User, Server, Lang) ->
|
||||
_ ->
|
||||
[?C("?")]
|
||||
end,
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen.
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||
|
||||
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
|
||||
case catch odbc_queries:del_spool_msg(Server, User) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]),
|
||||
{stop, error};
|
||||
_ ->
|
||||
?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]),
|
||||
{stop, ok}
|
||||
end;
|
||||
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
|
||||
Acc.
|
||||
|
||||
%% ------------------------------------------------
|
||||
%% mod_offline: number of messages quota management
|
||||
|
@ -42,6 +42,7 @@
|
||||
-define(PROCNAME, ejabberd_mod_proxy65).
|
||||
|
||||
start(Host, Opts) ->
|
||||
mod_proxy65_service:add_listener(Host, Opts),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {
|
||||
Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
@ -50,6 +51,7 @@ start(Host, Opts) ->
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
mod_proxy65_service:delete_listener(Host),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
@ -39,7 +39,7 @@
|
||||
]).
|
||||
|
||||
%% API.
|
||||
-export([start_link/2]).
|
||||
-export([start_link/2, add_listener/2, delete_listener/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@ -55,28 +55,21 @@
|
||||
acl
|
||||
}).
|
||||
|
||||
%% Unused callbacks.
|
||||
handle_cast(_Request, State) ->
|
||||
{noreply, State}.
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
%%----------------
|
||||
|
||||
%%%------------------------
|
||||
%%% gen_server callbacks
|
||||
%%%------------------------
|
||||
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
init([Host, Opts]) ->
|
||||
{IP, State} = parse_options(Host, Opts),
|
||||
NewOpts = [Host, {ip, IP} | Opts],
|
||||
ejabberd_listener:add_listener(State#state.port, mod_proxy65_stream, NewOpts),
|
||||
{_IP, State} = parse_options(Host, Opts),
|
||||
ejabberd_router:register_route(State#state.myhost),
|
||||
{ok, State}.
|
||||
|
||||
terminate(_Reason, #state{myhost=MyHost, port=Port}) ->
|
||||
catch ejabberd_listener:delete_listener(Port),
|
||||
terminate(_Reason, #state{myhost=MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
ok.
|
||||
|
||||
@ -93,10 +86,34 @@ handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_call(get_port, _From, State) ->
|
||||
{reply, {port, State#state.port}, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast(_Request, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%------------------------
|
||||
%%% Listener management
|
||||
%%%------------------------
|
||||
|
||||
add_listener(Host, Opts) ->
|
||||
{IP, State} = parse_options(Host, Opts),
|
||||
NewOpts = [Host, {ip, IP} | Opts],
|
||||
ejabberd_listener:add_listener(State#state.port,mod_proxy65_stream,NewOpts).
|
||||
|
||||
delete_listener(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
{port, Port} = gen_server:call(Proc, get_port),
|
||||
catch ejabberd_listener:delete_listener(Port, mod_proxy65_stream).
|
||||
|
||||
%%%------------------------
|
||||
%%% IQ Processing
|
||||
%%%------------------------
|
||||
|
@ -1486,7 +1486,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
node_call(Type, publish_item, [Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload])
|
||||
end
|
||||
end,
|
||||
%%ejabberd_hooks:run(pubsub_publish_item, Host, [Host, Node, JID, service_jid(Host), ItemId, Payload]),
|
||||
ejabberd_hooks:run(pubsub_publish_item, Host, [Host, Node, Publisher, service_jid(Host), ItemId, Payload]),
|
||||
Reply = [],
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{error, ?ERR_ITEM_NOT_FOUND} ->
|
||||
|
@ -104,60 +104,15 @@ get_auth(Auth) ->
|
||||
unauthorized
|
||||
end.
|
||||
|
||||
make_xhtml(Els, global, Lang) ->
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]),
|
||||
MenuItems2 = [?LI([?AC("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
{200, [html],
|
||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
||||
{"xml:lang", Lang},
|
||||
{"lang", Lang}],
|
||||
[{xmlelement, "head", [],
|
||||
[?XCT("title", "ejabberd Web Admin"),
|
||||
{xmlelement, "meta", [{"http-equiv", "Content-Type"},
|
||||
{"content", "text/html; charset=utf-8"}], []},
|
||||
{xmlelement, "link", [{"href", "/admin/favicon.ico"},
|
||||
{"type", "image/x-icon"},
|
||||
{"rel", "shortcut icon"}], []},
|
||||
{xmlelement, "link", [{"href", "/admin/style.css"},
|
||||
{"type", "text/css"},
|
||||
{"rel", "stylesheet"}], []}]},
|
||||
?XE("body",
|
||||
[?XAE("div",
|
||||
[{"id", "container"}],
|
||||
[?XAE("div",
|
||||
[{"id", "header"}],
|
||||
[?XE("h1",
|
||||
[?ACT("/admin/", "Administration")]
|
||||
)]),
|
||||
?XAE("div",
|
||||
[{"id", "navigation"}],
|
||||
[?XE("ul",
|
||||
[?LI([?ACT("/admin/acls/", "Access Control Lists")]),
|
||||
?LI([?ACT("/admin/access/", "Access Rules")]),
|
||||
?LI([?ACT("/admin/vhosts/", "Virtual Hosts")]),
|
||||
?LI([?ACT("/admin/nodes/", "Nodes")]),
|
||||
?LI([?ACT("/admin/stats/", "Statistics")])
|
||||
] ++ MenuItems2
|
||||
)]),
|
||||
?XAE("div",
|
||||
[{"id", "content"}],
|
||||
Els),
|
||||
?XAE("div",
|
||||
[{"id", "clearcopyright"}],
|
||||
[{xmlcdata, ""}])]),
|
||||
?XAE("div",
|
||||
[{"id", "copyrightouter"}],
|
||||
[?XAE("div",
|
||||
[{"id", "copyright"}],
|
||||
[?XC("p",
|
||||
"ejabberd (c) 2002-2008 ProcessOne")
|
||||
])])])
|
||||
]}};
|
||||
|
||||
make_xhtml(Els, Host, Lang) ->
|
||||
Base = "/admin/server/" ++ Host ++ "/",
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]),
|
||||
MenuItems2 = [?LI([?AC(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
make_xhtml(Els, Host, cluster, Lang).
|
||||
|
||||
%% @spec (Els, Host, Node, Lang)
|
||||
%% where Host = global | string()
|
||||
%% Node = cluster | atom()
|
||||
make_xhtml(Els, Host, Node, Lang) ->
|
||||
Base = get_base_path(Host, cluster), %% Enforcing 'cluster' on purpose here
|
||||
MenuItems = make_navigation(Host, Node, Lang),
|
||||
{200, [html],
|
||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
||||
{"xml:lang", Lang},
|
||||
@ -166,7 +121,7 @@ make_xhtml(Els, Host, Lang) ->
|
||||
[?XCT("title", "ejabberd Web Admin"),
|
||||
{xmlelement, "meta", [{"http-equiv", "Content-Type"},
|
||||
{"content", "text/html; charset=utf-8"}], []},
|
||||
{xmlelement, "link", [{"href", "/admin/favicon.ico"},
|
||||
{xmlelement, "link", [{"href", Base ++ "favicon.ico"},
|
||||
{"type", "image/x-icon"},
|
||||
{"rel", "shortcut icon"}], []},
|
||||
{xmlelement, "link", [{"href", Base ++ "style.css"},
|
||||
@ -183,18 +138,7 @@ make_xhtml(Els, Host, Lang) ->
|
||||
?XAE("div",
|
||||
[{"id", "navigation"}],
|
||||
[?XE("ul",
|
||||
[?LI([?XAE("div",
|
||||
[{"id", "navheadhost"}],
|
||||
[?AC(Base, Host)]
|
||||
)]),
|
||||
?LI([?ACT(Base ++ "acls/", "Access Control Lists")]),
|
||||
?LI([?ACT(Base ++ "access/", "Access Rules")]),
|
||||
?LI([?ACT(Base ++ "users/", "Users")]),
|
||||
?LI([?ACT(Base ++ "online-users/", "Online Users")]),
|
||||
?LI([?ACT(Base ++ "last-activity/", "Last Activity")]),
|
||||
?LI([?ACT(Base ++ "nodes/", "Nodes")]),
|
||||
?LI([?ACT(Base ++ "stats/", "Statistics")])
|
||||
] ++ MenuItems2
|
||||
MenuItems
|
||||
)]),
|
||||
?XAE("div",
|
||||
[{"id", "content"}],
|
||||
@ -211,13 +155,13 @@ make_xhtml(Els, Host, Lang) ->
|
||||
])])])
|
||||
]}}.
|
||||
|
||||
get_base_path(global, cluster) -> "/admin/";
|
||||
get_base_path(Host, cluster) -> "/admin/server/" ++ Host ++ "/";
|
||||
get_base_path(global, Node) -> "/admin/node/" ++ atom_to_list(Node) ++ "/";
|
||||
get_base_path(Host, Node) -> "/admin/server/" ++ Host ++ "/node/" ++ atom_to_list(Node) ++ "/".
|
||||
|
||||
css(Host) ->
|
||||
Base = case Host of
|
||||
global ->
|
||||
"/admin/";
|
||||
_ ->
|
||||
"/admin/server/" ++ Host ++ "/"
|
||||
end,
|
||||
Base = get_base_path(Host, cluster),
|
||||
"
|
||||
html,body {
|
||||
background: white;
|
||||
@ -298,7 +242,7 @@ html>body #container {
|
||||
|
||||
#navigation ul {
|
||||
position: absolute;
|
||||
top: 54px;
|
||||
top: 65px;
|
||||
left: 0;
|
||||
padding: 0 1px 1px 1px;
|
||||
margin: 0;
|
||||
@ -306,7 +250,7 @@ html>body #container {
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
background: #d47911;
|
||||
width: 13em;
|
||||
width: 17em;
|
||||
}
|
||||
|
||||
#navigation ul li {
|
||||
@ -340,9 +284,20 @@ html>body #container {
|
||||
background: #332;
|
||||
}
|
||||
|
||||
#navheadhost {
|
||||
text-align: left;
|
||||
border-bottom: 2px solid #d47911;
|
||||
ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {
|
||||
text-align: center;
|
||||
border-top: 2px solid #d47911;
|
||||
border-bottom: 1px solid #d47911;
|
||||
}
|
||||
|
||||
#navheadsub, #navitemsub {
|
||||
border-left: 7px solid white;
|
||||
margin-left: 2px solid #d47911;
|
||||
}
|
||||
|
||||
#navheadsubsub, #navitemsubsub {
|
||||
border-left: 14px solid white;
|
||||
margin-left: 4px solid #d47911;
|
||||
}
|
||||
|
||||
#lastactivity li {
|
||||
@ -550,7 +505,7 @@ h3 {
|
||||
#content {
|
||||
font-family: Verdana, Arial, Helvetica, sans-serif;
|
||||
font-size: 10pt;
|
||||
padding-left: 13em;
|
||||
padding-left: 17em;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
@ -595,11 +550,12 @@ logo_fill() ->
|
||||
"1c5dvhSU2BpKqBXl6R0ljYGS50R5zVC+tVD+vfE6YyUexE9x7g4AAAAASUVO"
|
||||
"RK5CYII=").
|
||||
|
||||
|
||||
process_admin(global,
|
||||
#request{path = [],
|
||||
lang = Lang}) ->
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]),
|
||||
MenuItems2 = [?LI([?AC("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
Base = get_base_path(global, cluster),
|
||||
MenuItems2 = make_menu_items(global, cluster, Base, Lang),
|
||||
make_xhtml([?XCT("h1", "Administration"),
|
||||
?XE("ul",
|
||||
[?LI([?ACT("/admin/acls/", "Access Control Lists"), ?C(" "),
|
||||
@ -616,9 +572,8 @@ process_admin(global,
|
||||
process_admin(Host,
|
||||
#request{path = [],
|
||||
lang = Lang}) ->
|
||||
Base = "/admin/server/" ++ Host ++ "/",
|
||||
MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]),
|
||||
MenuItems2 = [?LI([?AC(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
|
||||
Base = get_base_path(Host, cluster),
|
||||
MenuItems2 = make_menu_items(Host, cluster, Base, Lang),
|
||||
make_xhtml([?XCT("h1", "Administration"),
|
||||
?XE("ul",
|
||||
[?LI([?ACT(Base ++ "acls/", "Access Control Lists"), ?C(" "),
|
||||
@ -953,8 +908,13 @@ process_admin(Host,
|
||||
#request{path = ["user", U],
|
||||
q = Query,
|
||||
lang = Lang}) ->
|
||||
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)] )]).
|
||||
|
Loading…
Reference in New Issue
Block a user