From 25009ff9f414bffc7ff782e7367b6312ad0a5bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20P=C3=A9dron?= Date: Mon, 19 Jan 2009 11:59:40 +0000 Subject: [PATCH] Merge from trunk (r1764 to r1787). Warning: Ejabberd may be broken until the merge is completly finished. PR: EJABP-1 SVN Revision: 1827 --- ChangeLog | 41 ++++++++++++- doc/guide.html | 12 +++- doc/guide.tex | 12 ++++ src/ejabberd_app.erl | 2 +- src/ejabberd_auth.erl | 4 +- src/ejabberd_auth_internal.erl | 4 +- src/ejabberd_auth_odbc.erl | 4 +- src/ejabberd_c2s.erl | 8 ++- src/ejabberd_ctl.erl | 9 +-- src/ejabberd_listener.erl | 8 ++- src/ejabberd_s2s_out.erl | 6 +- src/mod_configure.erl | 92 ++++++++++++++++++----------- src/mod_last.erl | 2 +- src/mod_last_odbc.erl | 2 +- src/mod_pubsub/mod_pubsub.erl | 12 ++-- src/mod_pubsub/node_default.erl | 4 +- src/mod_pubsub/node_mb.erl | 1 + src/mod_pubsub/nodetree_default.erl | 10 ++-- src/mod_pubsub/nodetree_virtual.erl | 12 ++-- src/mod_roster.erl | 25 +++++++- src/mod_roster_odbc.erl | 25 +++++++- src/mod_shared_roster.erl | 4 +- src/msgs/ru.msg | 2 +- src/msgs/ru.po | 2 +- src/tls/tls.erl | 10 +++- src/tls/tls_drv.c | 7 +++ src/web/ejabberd_web_admin.erl | 53 +++++++++++++---- src/web/ejabberd_web_admin.hrl | 14 +++++ 28 files changed, 289 insertions(+), 98 deletions(-) diff --git a/ChangeLog b/ChangeLog index 64f489264..8a3b0c38b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,6 @@ 2009-01-19 Jean-Sébastien Pédron - Merge from trunk (r1752 to r1764). + Merge from trunk (r1752 to r1787). 2009-01-19 Jean-Sébastien Pédron @@ -77,6 +77,16 @@ * src/ejabberd_c2s.erl: Fix bug in handle_info/3 when dealing with VCARD requests: convert to IQ struct before invoking gen_iq_handler. +2009-01-09 Badlop + + * src/mod_configure.erl: Fix access check for vhost configuration + +2009-01-08 Mickael Remond + + * src/ejabberd_listener.erl: Define send timeout option to avoid + blocking on socket send (EJAB-746). + * src/ejabberd_s2s_out.erl: Likewise. + 2009-01-08 Christophe Romain * src/mod_pubsub/mod_pubsub.erl: completely support subscription using @@ -143,6 +153,35 @@ ejabberd_sm, ejabberd_router, ejabberd_hooks, mod_last, mod_roster to use binary() storage. +2009-01-07 Badlop + + * src/mod_roster.erl: Show hyperlinks to local contacts when + browsing roster of account in Web Admin (EJAB-480) + * src/mod_roster_odbc.erl: Likewise + + * src/web/ejabberd_web_admin.erl: WebAdmin serves Guide and links + to related sections; the path to guide.html can be configured with + option doc_path (EJAB-837) + * src/web/ejabberd_web_admin.hrl: Likewise + * src/mod_shared_roster.erl: Likewise + * doc/guide.tex: Likewise + * doc/guide.html: Likewise + +2009-01-06 Badlop + + * src/msgs/ru.po: Fix typo (thanks to Dominges) + * src/msgs/ru.msg: Likewise + +2009-01-05 Alexey Shchepin + + * src/tls/tls_drv.c: Added a flag to avoid certificate validation + * src/tls/tls.erl: Likewise + * src/ejabberd_c2s.erl: Likewise + +2009-01-03 Badlop + + * src/*.erl: Fix EDoc comments + 2009-01-03 Christophe Romain * src/mod_pubsub/mod_pubsub.erl: deliver notification depending on diff --git a/doc/guide.html b/doc/guide.html index 6d143c335..b7466955e 100644 --- a/doc/guide.html +++ b/doc/guide.html @@ -3072,7 +3072,17 @@ web browser to https://192.168.1.1:5280/admin/: tls, {certfile, "/usr/local/etc/server.pem"}]}, ... ]}. -

+

Certain pages in the ejabberd Web Admin contain a link to a related +section in the ejabberd Installation and Operation Guide. +In order to view such links, a copy in HTML format of the Guide must +be installed in the system. +The file is searched by default in +"/share/doc/ejabberd/guide.html". +The directory of the documentation can be specified in +ejabberd.cfg with the option doc_path. +For example: +

{doc_path, "/usr/local/share/doc/ejabberd/"}.
+

4.3  Ad-hoc Commands

If you enable mod_configure and mod_adhoc, you can perform several administrative tasks in ejabberd with a Jabber client. diff --git a/doc/guide.tex b/doc/guide.tex index afc63a3ff..c3fb6db07 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -3943,6 +3943,18 @@ Examples: \end{verbatim} \end{itemize} +Certain pages in the ejabberd Web Admin contain a link to a related +section in the ejabberd Installation and Operation Guide. +In order to view such links, a copy in HTML format of the Guide must +be installed in the system. +The file is searched by default in +\term{"/share/doc/ejabberd/guide.html"}. +The directory of the documentation can be specified in +\term{ejabberd.cfg} with the option \term{doc\_path}. +For example: +\begin{verbatim} +{doc_path, "/usr/local/share/doc/ejabberd/"}. +\end{verbatim} \makesection{adhoccommands}{Ad-hoc Commands} diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index afe700a76..ea193eebb 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -163,7 +163,7 @@ connect_nodes() -> end. %% @spec () -> string() -%% Returns the full path to the ejabberd log file. +%% @doc Returns the full path to the ejabberd log file. %% It first checks for application configuration parameter 'log_path'. %% If not defined it checks the environment variable EJABBERD_LOG_PATH. %% And if that one is neither defined, returns the default value: diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 6462b6edd..8f089afe6 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -260,7 +260,7 @@ is_user_exists_in_other_modules(Module, User, Server) -> end, auth_modules(Server)--[Module]). %% @spec (User, Server) -> ok | error | {error, not_allowed} -%% Remove user. +%% @doc Remove user. %% Note: it may return ok even if there was some problem removing the user. remove_user(User, Server) -> R = lists:foreach( @@ -274,7 +274,7 @@ remove_user(User, Server) -> R. %% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error -%% Try to remove user if the provided password is correct. +%% @doc Try to remove user if the provided password is correct. %% The removal is attempted in each auth method provided: %% when one returns 'ok' the loop stops; %% if no method returns 'ok' then it returns the error message indicated by the last method attempted. diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl index ae4ef4989..15f073866 100644 --- a/src/ejabberd_auth_internal.erl +++ b/src/ejabberd_auth_internal.erl @@ -254,7 +254,7 @@ is_user_exists(User, Server) -> end. %% @spec (User, Server) -> ok -%% Remove user. +%% @doc Remove user. %% Note: it returns ok even if there was some problem removing the user. remove_user(User, Server) -> try @@ -272,7 +272,7 @@ remove_user(User, Server) -> end. %% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request -%% Remove user if the provided password is correct. +%% @doc Remove user if the provided password is correct. remove_user(User, Server, Password) -> try LUser = exmpp_stringprep:nodeprep(User), diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl index 1f0a97b0c..e2f106b88 100644 --- a/src/ejabberd_auth_odbc.erl +++ b/src/ejabberd_auth_odbc.erl @@ -227,7 +227,7 @@ is_user_exists(User, Server) -> end. %% @spec (User, Server) -> ok | error -%% Remove user. +%% @doc Remove user. %% Note: it may return ok even if there was some problem removing the user. remove_user(User, Server) -> try @@ -242,7 +242,7 @@ remove_user(User, Server) -> end. %% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed -%% Remove user if the provided password is correct. +%% @doc Remove user if the provided password is correct. remove_user(User, Server, Password) -> try LUser = exmpp_stringprep:nodeprep(User), diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 649eadf6d..c850ccf13 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -175,9 +175,11 @@ init([{SockMod, Socket}, Opts]) -> StartTLSRequired = lists:member(starttls_required, Opts), TLSEnabled = lists:member(tls, Opts), TLS = StartTLS orelse StartTLSRequired orelse TLSEnabled, - TLSOpts = lists:filter(fun({certfile, _}) -> true; - (_) -> false - end, Opts), + TLSOpts1 = + lists:filter(fun({certfile, _}) -> true; + (_) -> false + end, Opts), + TLSOpts = [verify_none | TLSOpts1], IP = peerip(SockMod, Socket), %% Check if IP is blacklisted: case is_ip_blacklisted(IP) of diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index 328974189..b7b1b9963 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -226,8 +226,7 @@ process2(Args) -> %% Command calling %%----------------------------- -%% @spec (Args::[string()]) -> -%% String::string() | Code::integer() | {String::string(), Code::integer()} +%% @spec (Args::[string()]) -> string() | integer() | {string(), integer()} try_run_ctp(Args) -> try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of false when Args /= [] -> @@ -248,8 +247,7 @@ try_run_ctp(Args) -> {io_lib:format("Error in ejabberd ctl process: '~p' ~p", [Error, Why]), ?STATUS_USAGE} end. -%% @spec (Args::[string()]) -> -%% String::string() | Code::integer() | {String::string(), Code::integer()} +%% @spec (Args::[string()]) -> string() | integer() | {string(), integer()} try_call_command(Args) -> try call_command(Args) of {error, command_unknown} -> @@ -264,8 +262,7 @@ try_call_command(Args) -> {io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR} end. -%% @spec (Args::[string()]) -> -%% String::string() | Code::integer() | {String::string(), Code::integer()} | {error, ErrorType} +%% @spec (Args::[string()]) -> string() | integer() | {string(), integer()} | {error, ErrorType} call_command([CmdString | Args]) -> {ok, CmdStringU, _} = regexp:gsub(CmdString, "-", "_"), Command = list_to_atom(CmdStringU), diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 9c46f6d4c..6992c6a53 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -16,7 +16,7 @@ %%% 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 @@ -38,6 +38,9 @@ -include("ejabberd.hrl"). +%% We do not block on send anymore. +-define(TCP_SEND_TIMEOUT, 15000). + start_link() -> supervisor:start_link({local, ejabberd_listeners}, ?MODULE, []). @@ -96,10 +99,11 @@ init(Port, Module, Opts) -> end, Opts), Res = gen_tcp:listen(Port, [binary, - {packet, 0}, + {packet, 0}, {active, false}, {reuseaddr, true}, {nodelay, true}, + {send_timeout, ?TCP_SEND_TIMEOUT}, {keepalive, true} | SockOpts]), case Res of diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index 553609a97..bdb67c095 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -16,7 +16,7 @@ %%% 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 @@ -96,6 +96,9 @@ %% -define(FSMLIMITS, [{max_queue, 2000}]). -define(FSMTIMEOUT, 30000). +%% We do not block on send anymore. +-define(TCP_SEND_TIMEOUT, 15000). + %% Maximum delay to wait before retrying to connect after a failed attempt. %% Specified in miliseconds. Default value is 5 minutes. -define(MAX_RETRY_DELAY, 300000). @@ -259,6 +262,7 @@ open_socket2(Type, Addr, Port) -> Timeout = outgoing_s2s_timeout(), case (catch ejabberd_socket:connect(Addr, Port, [binary, {packet, 0}, + {send_timeout, ?TCP_SEND_TIMEOUT}, {active, false}, Type], Timeout)) of {ok, _Socket} = R -> R; diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 00fcee006..5e91e15dc 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -319,8 +319,10 @@ adhoc_local_items(Acc, From, To, Lang) -> {result, Its} -> Its; empty -> [] end, + PermLev = get_permission_level(From), %% Recursively get all configure commands - Nodes = recursively_get_local_items(LServer, "", exmpp_jid:domain(To), Lang), + Nodes = recursively_get_local_items(PermLev, LServer, "", exmpp_jid:domain_as_list(Server), + Lang), Nodes1 = lists:filter( fun(N) -> Nd = exmpp_xml:get_attribute(N, 'node', ""), @@ -337,15 +339,15 @@ adhoc_local_items(Acc, From, To, Lang) -> Acc end. -recursively_get_local_items(_LServer, "online users", _Server, _Lang) -> +recursively_get_local_items(_PermLev, _LServer, "online users", _Server, _Lang) -> []; -recursively_get_local_items(_LServer, "all users", _Server, _Lang) -> +recursively_get_local_items(_PermLev, _LServer, "all users", _Server, _Lang) -> []; -recursively_get_local_items(LServer, Node, Server, Lang) -> +recursively_get_local_items(PermLev, LServer, Node, Server, Lang) -> LNode = tokenize(Node), - Items = case get_local_items(LServer, LNode, Server, Lang) of + Items = case get_local_items({PermLev, LServer}, LNode, Server, Lang) of {result, Res} -> Res; {error, _Error} -> @@ -360,11 +362,17 @@ recursively_get_local_items(LServer, Node, Server, Lang) -> []; true -> [N, recursively_get_local_items( - LServer, Nd, Server, Lang)] + PermLev, LServer, Nd, Server, Lang)] end end, Items)), Nodes. +get_permission_level(JID) -> + case acl:match_rule(global, configure, JID) of + allow -> global; + deny -> vhost + end. + %%%----------------------------------------------------------------------- -define(ITEMS_RESULT(Allow, LNode, Fallback), @@ -372,7 +380,8 @@ recursively_get_local_items(LServer, Node, Server, Lang) -> deny -> Fallback; allow -> - case get_local_items(LServer, LNode, + PermLev = get_permission_level(From), + case get_local_items({PermLev, LServer}, LNode, exmpp_jid:jid_to_binary(To), Lang) of {result, Res} -> {result, Res}; @@ -396,7 +405,8 @@ get_local_items(Acc, From, To, <<>>, Lang) -> deny -> {result, Items}; allow -> - case get_local_items(LServer, [], + PermLev = get_permission_level(From), + case get_local_items({PermLev, LServer}, [], exmpp_jid:jid_to_binary(To), Lang) of {result, Res} -> {result, Items ++ Res}; @@ -462,6 +472,9 @@ get_local_items(Acc, From, To, Node, Lang) -> %%%----------------------------------------------------------------------- +%% @spec ({PermissionLevel, Host}, [string()], Server::string(), Lang) +%% -> {result, [xmlelement()]} +%% PermissionLevel = global | vhost get_local_items(_Host, [], Server, Lang) -> {result, [?NODE("Configuration", <<"config">>), @@ -498,13 +511,13 @@ get_local_items(_Host, ["user"], Server, Lang) -> get_local_items(_Host, ["http:" | _], _Server, _Lang) -> {result, []}; -get_local_items(Host, ["online users"], _Server, _Lang) -> +get_local_items({_, Host}, ["online users"], _Server, _Lang) -> {result, get_online_vh_users(Host)}; -get_local_items(Host, ["all users"], _Server, _Lang) -> +get_local_items({_, Host}, ["all users"], _Server, _Lang) -> {result, get_all_vh_users(Host)}; -get_local_items(Host, ["all users", [$@ | Diap]], _Server, _Lang) -> +get_local_items({_, Host}, ["all users", [$@ | Diap]], _Server, _Lang) -> case catch ejabberd_auth:get_vh_registered_users(Host) of {'EXIT', _Reason} -> {error, 'internal-server-error'}; @@ -528,10 +541,10 @@ get_local_items(Host, ["all users", [$@ | Diap]], _Server, _Lang) -> end end; -get_local_items(Host, ["outgoing s2s"], _Server, Lang) -> +get_local_items({_, Host}, ["outgoing s2s"], _Server, Lang) -> {result, get_outgoing_s2s(Host, Lang)}; -get_local_items(Host, ["outgoing s2s", To], _Server, Lang) -> +get_local_items({_, Host}, ["outgoing s2s", To], _Server, Lang) -> {result, get_outgoing_s2s(Host, Lang, To)}; get_local_items(_Host, ["running nodes"], Server, Lang) -> @@ -540,7 +553,7 @@ get_local_items(_Host, ["running nodes"], Server, Lang) -> get_local_items(_Host, ["stopped nodes"], _Server, Lang) -> {result, get_stopped_nodes(Lang)}; -get_local_items(_Host, ["running nodes", ENode], Server, Lang) -> +get_local_items({global, _Host}, ["running nodes", ENode], Server, Lang) -> ENodeB = list_to_binary(ENode), {result, [?NODE("Database", <<"running nodes/", ENodeB/binary, "/DB">>), @@ -552,6 +565,11 @@ get_local_items(_Host, ["running nodes", ENode], Server, Lang) -> ?NODE("Shut Down Service", <<"running nodes/", ENodeB/binary, "/shutdown">>) ]}; +get_local_items({vhost, _Host}, ["running nodes", ENode], Server, Lang) -> + {result, + [?NODE("Modules", "running nodes/" ++ ENode ++ "/modules") + ]}; + get_local_items(_Host, ["running nodes", _ENode, "DB"], _Server, _Lang) -> {result, []}; @@ -721,8 +739,8 @@ get_stopped_nodes(_Lang) -> %%------------------------------------------------------------------------- --define(COMMANDS_RESULT(Allow, From, To, Request), - case Allow of +-define(COMMANDS_RESULT(LServerOrGlobal, From, To, Request), + case acl:match_rule(LServerOrGlobal, configure, From) of deny -> {error, 'forbidden'}; allow -> @@ -732,24 +750,23 @@ get_stopped_nodes(_Lang) -> adhoc_local_commands(Acc, From, To, #adhoc_request{node = Node} = Request) -> LServer = exmpp_jid:ldomain_as_list(To), LNode = tokenize(Node), - Allow = acl:match_rule(LServer, configure, From), case LNode of ["running nodes", _ENode, "DB"] -> - ?COMMANDS_RESULT(Allow, From, To, Request); + ?COMMANDS_RESULT(global, From, To, Request); ["running nodes", _ENode, "modules", _] -> - ?COMMANDS_RESULT(Allow, From, To, Request); + ?COMMANDS_RESULT(LServer, From, To, Request); ["running nodes", _ENode, "backup", _] -> - ?COMMANDS_RESULT(Allow, From, To, Request); + ?COMMANDS_RESULT(global, From, To, Request); ["running nodes", _ENode, "import", _] -> - ?COMMANDS_RESULT(Allow, From, To, Request); + ?COMMANDS_RESULT(global, From, To, Request); ["running nodes", _ENode, "restart"] -> - ?COMMANDS_RESULT(Allow, From, To, Request); + ?COMMANDS_RESULT(global, From, To, Request); ["running nodes", _ENode, "shutdown"] -> - ?COMMANDS_RESULT(Allow, From, To, Request); + ?COMMANDS_RESULT(global, From, To, Request); ["config", _] -> - ?COMMANDS_RESULT(Allow, From, To, Request); + ?COMMANDS_RESULT(LServer, From, To, Request); ?NS_ADMINL(_) -> - ?COMMANDS_RESULT(Allow, From, To, Request); + ?COMMANDS_RESULT(LServer, From, To, Request); _ -> Acc end. @@ -1545,7 +1562,7 @@ set_form(_From, Host, ["config", "access"], _Lang, XData) -> {error, 'bad-request'} end; -set_form(_From, _Host, ?NS_ADMINL("add-user"), _Lang, XData) -> +set_form(From, Host, ?NS_ADMINL("add-user"), _Lang, XData) -> AccountString = get_value("accountjid", XData), Password = get_value("password", XData), Password = get_value("password-verify", XData), @@ -1553,17 +1570,19 @@ set_form(_From, _Host, ?NS_ADMINL("add-user"), _Lang, XData) -> User = exmpp_jid:lnode_as_list(AccountJID), Server = exmpp_jid:ldomain_as_list(AccountJID), true = lists:member(Server, ?MYHOSTS), + true = (Server == Host) orelse (get_permission_level(From) == global), ejabberd_auth:try_register(User, Server, Password), {result, []}; -set_form(_From, _Host, ?NS_ADMINL("delete-user"), _Lang, XData) -> +set_form(From, Host, ?NS_ADMINL("delete-user"), _Lang, XData) -> AccountStringList = get_values("accountjids", XData), [_|_] = AccountStringList, ASL2 = lists:map( fun(AccountString) -> - JID = exmpp_jid:list_to_jid(AccountString), + JID = exmpp_jid:parse_jid(AccountString), User = [_|_] = exmpp_jid:lnode_as_list(JID), - Server = exmpp_jid:ldomain_as_list(JID), + Server = exmpp_jid:ldomain_as_list(JID), + true = (Server == Host) orelse (get_permission_level(From) == global), true = ejabberd_auth:is_user_exists(User, Server), {User, Server} end, @@ -1571,11 +1590,12 @@ set_form(_From, _Host, ?NS_ADMINL("delete-user"), _Lang, XData) -> [ejabberd_auth:remove_user(User, Server) || {User, Server} <- ASL2], {result, []}; -set_form(_From, _Host, ?NS_ADMINL("end-user-session"), _Lang, XData) -> +set_form(From, Host, ?NS_ADMINL("end-user-session"), _Lang, XData) -> AccountString = get_value("accountjid", XData), JID = exmpp_jid:list_to_jid(AccountString), LUser = [_|_] = exmpp_jid:lnode_as_list(JID), LServer = exmpp_jid:ldomain_as_list(JID), + true = (LServer == Host) orelse (get_permission_level(From) == global), %% Code copied from ejabberd_sm.erl case exmpp_jid:lresource_as_list(JID) of undefined -> @@ -1589,11 +1609,12 @@ set_form(_From, _Host, ?NS_ADMINL("end-user-session"), _Lang, XData) -> end, {result, []}; -set_form(_From, _Host, ?NS_ADMINL("get-user-password"), Lang, XData) -> +set_form(From, Host, ?NS_ADMINL("get-user-password"), Lang, XData) -> AccountString = get_value("accountjid", XData), JID = exmpp_jid:list_to_jid(AccountString), User = [_|_] = exmpp_jid:lnode_as_list(JID), Server = exmpp_jid:ldomain_as_list(JID), + true = (Server == Host) orelse (get_permission_level(From) == global), Password = ejabberd_auth:get_password(User, Server), true = is_list(Password), {result, [#xmlel{ns = ?NS_DATA_FORMS, name = 'x', children = @@ -1602,21 +1623,23 @@ set_form(_From, _Host, ?NS_ADMINL("get-user-password"), Lang, XData) -> ?XFIELD(<<"text-single">>, "Password", <<"password">>, list_to_binary(Password)) ]}]}; -set_form(_From, _Host, ?NS_ADMINL("change-user-password"), _Lang, XData) -> +set_form(From, Host, ?NS_ADMINL("change-user-password"), _Lang, XData) -> AccountString = get_value("accountjid", XData), Password = get_value("password", XData), JID = exmpp_jid:list_to_jid(AccountString), User = [_|_] = exmpp_jid:lnode_as_list(JID), Server = exmpp_jid:ldomain_as_list(JID), + true = (Server == Host) orelse (get_permission_level(From) == global), true = ejabberd_auth:is_user_exists(User, Server), ejabberd_auth:set_password(User, Server, Password), {result, []}; -set_form(_From, _Host, ?NS_ADMINL("get-user-lastlogin"), Lang, XData) -> +set_form(From, Host, ?NS_ADMINL("get-user-lastlogin"), Lang, XData) -> AccountString = get_value("accountjid", XData), JID = exmpp_jid:list_to_jid(AccountString), User = [_|_] = exmpp_jid:lnode_as_list(JID), Server = exmpp_jid:ldomain_as_list(JID), + true = (Server == Host) orelse (get_permission_level(From) == global), %% Code copied from web/ejabberd_web_admin.erl %% TODO: Update time format to XEP-0202: Entity Time @@ -1649,11 +1672,12 @@ set_form(_From, _Host, ?NS_ADMINL("get-user-lastlogin"), Lang, XData) -> ?XFIELD(<<"text-single">>, "Last login", <<"lastlogin">>, list_to_binary(FLast)) ]}]}; -set_form(_From, _Host, ?NS_ADMINL("user-stats"), Lang, XData) -> +set_form(From, Host, ?NS_ADMINL("user-stats"), Lang, XData) -> AccountString = get_value("accountjid", XData), JID = exmpp_jid:list_to_jid(AccountString), User = [_|_] = exmpp_jid:lnode_as_list(JID), Server = exmpp_jid:ldomain_as_list(JID), + true = (Server == Host) orelse (get_permission_level(From) == global), Resources = ejabberd_sm:get_user_resources(exmpp_jid:lnode(JID), exmpp_jid:ldomain(JID)), diff --git a/src/mod_last.erl b/src/mod_last.erl index 4ddb716f3..613e49317 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -168,7 +168,7 @@ store_last_info(User, Server, TimeStamp, Status) ok end. -%% @spec (LUser::string(), LServer::string() -> +%% @spec (LUser::string(), LServer::string()) -> %% {ok, Timestamp::integer(), Status::string()} | not_found get_last_info(LUser, LServer) when is_binary(LUser), is_binary(LServer) -> case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of diff --git a/src/mod_last_odbc.erl b/src/mod_last_odbc.erl index 4d8ea152d..c8533f5d8 100644 --- a/src/mod_last_odbc.erl +++ b/src/mod_last_odbc.erl @@ -168,7 +168,7 @@ store_last_info(User, Server, TimeStamp, Status) ok end. -%% @spec (LUser::string(), LServer::string() -> +%% @spec (LUser::string(), LServer::string()) -> %% {ok, Timestamp::integer(), Status::string()} | not_found get_last_info(LUser, LServer) -> Username = ejabberd_odbc:escape(LUser), diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index 045cedf8c..a78ac6bad 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -33,7 +33,7 @@ %%% This module uses version 1.12 of the specification as a base. %%% Most of the specification is implemented. %%% Functions concerning configuration should be rewritten. -%%% Code is derivated from the original pubsub v1.7, by Alexey Shchepin +%%% Code is derivated from the original pubsub v1.7, by Alexey Shchepin %%% TODO %%% plugin: generate Reply (do not use broadcast atom anymore) @@ -1749,10 +1749,11 @@ get_items(Host, Node, From) -> send_last_item(Host, Node, LJID) -> send_items(Host, Node, LJID, last). -%% @spec (Host, Node, LJID) -> any() +%% @spec (Host, Node, LJID, Number) -> any() %% Host = host() %% Node = pubsubNode() %% LJID = {U, S, []} +%% Number = last | integer() %% @doc

Resend the items of a node to the user.

%% @todo use cache-last-item feature send_items(Host, Node, {LU, LS, LR} = LJID, Number) -> @@ -2157,10 +2158,9 @@ is_to_deliver({User, Server, _}, _, true) -> end, false, Ss) end. -%% @spec (Elem, Payload) -> int() -%% Elem = atom() +%% @spec (Payload) -> int() %% Payload = term() -%% @doc

Count occurence of given element in payload.

+%% @doc

Count occurence of XML elements in payload.

payload_xmlelements(Payload) -> payload_xmlelements(Payload, 0). payload_xmlelements([], Count) -> Count; payload_xmlelements([#xmlel{}|Tail], Count) -> payload_xmlelements(Tail, Count+1); @@ -2168,7 +2168,7 @@ payload_xmlelements([_|Tail], Count) -> payload_xmlelements(Tail, Count). %% @spec (Els) -> stanza() %% Els = [xmlelement()] -%% @doc

Build pubsub event stanza +%% @doc

Build pubsub event stanza

event_stanza(Els) -> #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = Els}]}. diff --git a/src/mod_pubsub/node_default.erl b/src/mod_pubsub/node_default.erl index 8acf2449d..91d4e7054 100644 --- a/src/mod_pubsub/node_default.erl +++ b/src/mod_pubsub/node_default.erl @@ -677,7 +677,7 @@ set_state(_) -> del_state(StateId) -> mnesia:delete({pubsub_state, StateId}). -%% @spec (Host, Node) -> [Items] | [] +%% @spec (Host, Node, From) -> [Items] | [] %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% Items = mod_pubsub:pubsubItems() @@ -784,7 +784,7 @@ set_item(Item) when is_record(Item, pubsub_item) -> set_item(_) -> {error, 'internal-server-error'}. -%% @spec (ItemId) -> ok | {error, Reason::stanzaError()} +%% @spec (Host, Node, ItemId) -> ok | {error, Reason::stanzaError()} %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% ItemId = string() diff --git a/src/mod_pubsub/node_mb.erl b/src/mod_pubsub/node_mb.erl index d90f2391f..a05ea883c 100644 --- a/src/mod_pubsub/node_mb.erl +++ b/src/mod_pubsub/node_mb.erl @@ -30,6 +30,7 @@ %%% {plugins, ["default", "pep","mb"]}, %%% {pep_mapping, [{"urn:xmpp:microblog", "mb"}]} %%% ]}, +%%%

%%%

PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.

-module(node_mb). diff --git a/src/mod_pubsub/nodetree_default.erl b/src/mod_pubsub/nodetree_default.erl index 126ea7f75..6bac99434 100644 --- a/src/mod_pubsub/nodetree_default.erl +++ b/src/mod_pubsub/nodetree_default.erl @@ -97,12 +97,12 @@ set_node(Record) when is_record(Record, pubsub_node) -> set_node(_) -> {error, 'internal-server-error'}. -%% @spec (Host, Node) -> pubsubNode() | {error, Reason} -%% Host = mod_pubsub:host() -%% Node = mod_pubsub:pubsubNode() get_node(Host, Node, _From) -> get_node(Host, Node). +%% @spec (Host, Node) -> pubsubNode() | {error, Reason} +%% Host = mod_pubsub:host() +%% Node = mod_pubsub:pubsubNode() get_node(Host, Node) -> case catch mnesia:read({pubsub_node, {Host, Node}}) of [Record] when is_record(Record, pubsub_node) -> Record; @@ -110,11 +110,11 @@ get_node(Host, Node) -> Error -> Error end. -%% @spec (Key) -> [pubsubNode()] | {error, Reason} -%% Key = mod_pubsub:host() | mod_pubsub:jid() get_nodes(Key, _From) -> get_nodes(Key). +%% @spec (Key) -> [pubsubNode()] | {error, Reason} +%% Key = mod_pubsub:host() | mod_pubsub:jid() get_nodes(Key) -> mnesia:match_object(#pubsub_node{nodeid = {Key, '_'}, _ = '_'}). diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl index 2df61e70a..b76419b6c 100644 --- a/src/mod_pubsub/nodetree_virtual.erl +++ b/src/mod_pubsub/nodetree_virtual.erl @@ -84,24 +84,24 @@ options() -> set_node(_NodeRecord) -> ok. +get_node(Host, Node, _From) -> + get_node(Host, Node). + %% @spec (Host, Node) -> pubsubNode() %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% @doc

Virtual node tree does not handle a node database. Any node is considered %% as existing. Node record contains default values.

-get_node(Host, Node, _From) -> - get_node(Host, Node). - get_node(Host, Node) -> #pubsub_node{nodeid = {Host, Node}}. +get_nodes(Key, _From) -> + get_nodes(Key). + %% @spec (Key) -> [pubsubNode()] %% Host = mod_pubsub:host() | mod_pubsub:jid() %% @doc

Virtual node tree does not handle a node database. Any node is considered %% as existing. Nodes list can not be determined.

-get_nodes(Key, _From) -> - get_nodes(Key). - get_nodes(_Key) -> []. diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 05f0381ac..569c30b2d 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -883,10 +883,9 @@ user_roster(User, Server, Query, Lang) -> [?C(Group), ?BR] end, R#roster.groups), Pending = ask_to_pending(R#roster.ask), - {U, S, R} = R#roster.jid, + TDJID = build_contact_jid_td(R#roster.jid), ?XE("tr", - [?XAC("td", [{"class", "valign"}], - catch exmpp_jid:jid_to_list(U, S, R)), + [TDJID, ?XAC("td", [{"class", "valign"}], binary_to_list(R#roster.name)), ?XAC("td", [{"class", "valign"}], @@ -934,6 +933,26 @@ user_roster(User, Server, Query, Lang) -> ])] end. +build_contact_jid_td({U, S, R}) -> + %% Convert {U, S, R} into {jid, U, S, R, U, S, R}: + ContactJID = exmpp_jid:make_jid(U, S, R), + JIDURI = case {exmpp_jid:lnode(ContactJID), exmpp_jid:ldomain(ContactJID)} of + {undefined, _} -> ""; + {CUser, CServer} -> + CUser_S = binary_to_list(CUser), + CServer_S = binary_to_list(CServer), + case lists:member(CServer_S, ?MYHOSTS) of + false -> ""; + true -> "/admin/server/" ++ CServer_S ++ "/user/" ++ CUser_S ++ "/" + end + end, + case JIDURI of + [] -> + ?XAC('td', [#xmlattr{name = 'class', value = <<"valign">>}], exmpp_jid:jid_to_list(ContactJID)); + URI when is_list(URI) -> + ?XAE('td', [#xmlattr{name = 'class', value = <<"valign">>}], [?AC(JIDURI, exmpp_jid:jid_to_list(ContactJID))]) + end. + user_roster_parse_query(User, Server, Items, Query) -> case lists:keysearch("addjid", 1, Query) of {value, _} -> diff --git a/src/mod_roster_odbc.erl b/src/mod_roster_odbc.erl index 3860a09c7..d41b911ce 100644 --- a/src/mod_roster_odbc.erl +++ b/src/mod_roster_odbc.erl @@ -922,10 +922,9 @@ user_roster(User, Server, Query, Lang) -> [?C(Group), ?BR] end, R#roster.groups), Pending = ask_to_pending(R#roster.ask), - {U, S, R} = R#roster.jid, + TDJID = build_contact_jid_td(R#roster.jid), ?XE("tr", - [?XAC("td", [{"class", "valign"}], - catch exmpp_jid:jid_to_list(U, S, R)), + [TDJID, ?XAC("td", [{"class", "valign"}], binary_to_list(R#roster.name)), ?XAC("td", [{"class", "valign"}], @@ -973,6 +972,26 @@ user_roster(User, Server, Query, Lang) -> ])] end. +build_contact_jid_td({U, S, R}) -> + %% Convert {U, S, R} into {jid, U, S, R, U, S, R}: + ContactJID = exmpp_jid:make_jid(U, S, R), + JIDURI = case {exmpp_jid:lnode(ContactJID), exmpp_jid:ldomain(ContactJID)} of + {undefined, _} -> ""; + {CUser, CServer} -> + CUser_S = binary_to_list(CUser), + CServer_S = binary_to_list(CServer), + case lists:member(CServer_S, ?MYHOSTS) of + false -> ""; + true -> "/admin/server/" ++ CServer_S ++ "/user/" ++ CUser_S ++ "/" + end + end, + case JIDURI of + [] -> + ?XAC('td', [#xmlattr{name = 'class', value = <<"valign">>}], exmpp_jid:jid_to_list(ContactJID)); + URI when is_list(URI) -> + ?XAE('td', [#xmlattr{name = 'class', value = <<"valign">>}], [?AC(JIDURI, exmpp_jid:jid_to_list(ContactJID))]) + end. + user_roster_parse_query(User, Server, Items, Query) -> case lists:keysearch("addjid", 1, Query) of {value, _} -> diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index 6ed4fbb0c..d3bbd9aab 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -740,7 +740,7 @@ list_shared_roster_groups(Host, Query, Lang) -> ] )] )]), - [?XC("h1", ?T("Shared Roster Groups"))] ++ + ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++ case Res of ok -> [?CT("Submitted"), ?P]; error -> [?CT("Bad format"), ?P]; @@ -839,7 +839,7 @@ shared_roster_group(Host, Group, Query, Lang) -> ] )] )]), - [?XC("h1", ?T("Shared Roster Groups"))] ++ + ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++ [?XC("h2", ?T("Group ") ++ Group)] ++ case Res of ok -> [?CT("Submitted"), ?P]; diff --git a/src/msgs/ru.msg b/src/msgs/ru.msg index 94bc74f42..93ca02cc6 100644 --- a/src/msgs/ru.msg +++ b/src/msgs/ru.msg @@ -166,7 +166,7 @@ {"Name","Название"}. {"Never","Никогда"}. {"Nickname is already in use by another occupant","Псевдоним занят кем-то из присутствующих"}. -{"Nickname is registered by another person","Псевдоним зарегистирован кем-то другим"}. +{"Nickname is registered by another person","Псевдоним зарегистрирован кем-то другим"}. {"Nickname Registration at ","Регистрация псевдонима на "}. {"Nickname ~s does not exist in the room","Псевдоним ~s в комнате отсутствует"}. {"Nickname","Псевдоним"}. diff --git a/src/msgs/ru.po b/src/msgs/ru.po index 347eafa40..8c6aa6207 100644 --- a/src/msgs/ru.po +++ b/src/msgs/ru.po @@ -723,7 +723,7 @@ msgstr "Псевдоним занят кем-то из присутствующ #: mod_muc/mod_muc_room.erl:973 mod_muc/mod_muc_room.erl:1492 msgid "Nickname is registered by another person" -msgstr "Псевдоним зарегистирован кем-то другим" +msgstr "Псевдоним зарегистрирован кем-то другим" #: mod_muc/mod_muc_room.erl:1473 msgid "You have been banned from this room" diff --git a/src/tls/tls.erl b/src/tls/tls.erl index 72897cf08..7281fd475 100644 --- a/src/tls/tls.erl +++ b/src/tls/tls.erl @@ -59,6 +59,7 @@ -define(GET_DECRYPTED_INPUT, 6). -define(GET_PEER_CERTIFICATE, 7). -define(GET_VERIFY_RESULT, 8). +-define(VERIFY_NONE, 16#10000). -record(tlssock, {tcpsock, tlsport}). @@ -120,13 +121,20 @@ tcp_to_tls(TCPSocket, Options) -> {error, already_loaded} -> ok end, Port = open_port({spawn, tls_drv}, [binary]), + Flags = + case lists:member(verify_none, Options) of + true -> + ?VERIFY_NONE; + false -> + 0 + end, Command = case lists:member(connect, Options) of true -> ?SET_CERTIFICATE_FILE_CONNECT; false -> ?SET_CERTIFICATE_FILE_ACCEPT end, - case port_control(Port, Command, CertFile ++ [0]) of + case port_control(Port, Command bor Flags, CertFile ++ [0]) of <<0>> -> {ok, #tlssock{tcpsock = TCPSocket, tlsport = Port}}; <<1, Error/binary>> -> diff --git a/src/tls/tls_drv.c b/src/tls/tls_drv.c index b90cab87c..2f8e56150 100644 --- a/src/tls/tls_drv.c +++ b/src/tls/tls_drv.c @@ -272,6 +272,7 @@ static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) #define GET_DECRYPTED_INPUT 6 #define GET_PEER_CERTIFICATE 7 #define GET_VERIFY_RESULT 8 +#define VERIFY_NONE 0x10000 #define die_unless(cond, errstr) \ @@ -312,6 +313,9 @@ static int tls_drv_control(ErlDrvData handle, int size; ErlDrvBinary *b; X509 *cert; + unsigned int flags = command; + + command &= 0xffff; ERR_clear_error(); switch (command) @@ -354,6 +358,9 @@ static int tls_drv_control(ErlDrvData handle, d->ssl = SSL_new(ssl_ctx); die_unless(d->ssl, "SSL_new failed"); + if (flags & VERIFY_NONE) + SSL_set_verify(d->ssl, SSL_VERIFY_NONE, verify_callback); + d->bio_read = BIO_new(BIO_s_mem()); d->bio_write = BIO_new(BIO_s_mem()); diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl index a43e92cd0..fbac03762 100644 --- a/src/web/ejabberd_web_admin.erl +++ b/src/web/ejabberd_web_admin.erl @@ -40,6 +40,30 @@ -include("ejabberd_web_admin.hrl"). +process(["doc", LocalFile], _Request) -> + DocPath = case ejabberd_config:get_global_option(doc_path) of + P when is_list(P) -> P; + _ -> "/share/doc/ejabberd/" + end, + %% Code based in mod_http_fileserver + FileName = filename:join(DocPath, LocalFile), + case file:read_file(FileName) of + {ok, FileContents} -> + ?DEBUG("Delivering content.", []), + {200, + [{"Server", "ejabberd"}], + FileContents}; + {error, Error} -> + ?DEBUG("Delivering error: ~p", [Error]), + Help = " " ++ FileName ++ " - Try to specify the path to ejabberd guide.html " + "with the option doc_path. Check the ejabberd Guide for more information", + case Error of + eacces -> {403, [], "Forbidden"++Help}; + enoent -> {404, [], "Not found"++Help}; + _Else -> {404, [], atom_to_list(Error)++Help} + end + end; + process(["server", SHost | RPath], #request{auth = Auth} = Request) -> Host = exmpp_stringprep:nameprep(SHost), case lists:member(Host, ?MYHOSTS) of @@ -108,7 +132,7 @@ get_auth(Auth) -> make_xhtml(Els, Host, Lang) -> make_xhtml(Els, Host, cluster, Lang). -%% @spec (Els, Host, Node, Lang) +%% @spec (Els, Host, Node, Lang) -> {200, [html], xmlelement()} %% where Host = global | string() %% Node = cluster | atom() make_xhtml(Els, Host, Node, Lang) -> @@ -513,8 +537,13 @@ h3 { padding-top: 5px; } -*.alignright { +div.guidelink { text-align: right; + padding-right: 1em; +} + +*.alignright { + font-size: 10pt; } ". @@ -560,8 +589,8 @@ process_admin(global, lang = Lang}) -> Base = get_base_path(global, cluster), MenuItems2 = make_menu_items(global, cluster, Base, Lang), - make_xhtml([?XCT('h1', "Administration"), - ?XE('ul', + make_xhtml(?H1GL(?T("Administration"), "toc", "Contents") ++ + [?XE('ul', [?LI([?ACT("/admin/acls/", "Access Control Lists"), ?C(" "), ?ACT("/admin/acls-raw/", "(Raw)")]), ?LI([?ACT("/admin/access/", "Access Rules"), ?C(" "), @@ -635,7 +664,7 @@ process_admin(Host, "~p.", [lists:keysort( 2, ets:select(acl, [{{acl, {'$1', Host}, '$2'}, [], [{{acl, '$1', '$2'}}]}]))])), - make_xhtml([?XCT('h1', "Access Control Lists")] ++ + make_xhtml(?H1GL(?T("Access Control Lists"), "ACLDefinition", "ACL Definition") ++ case Res of ok -> [?CT("Submitted"), ?P]; error -> [?CT("Bad format"), ?P]; @@ -678,7 +707,7 @@ process_admin(Host, ACLs = lists:keysort( 2, ets:select(acl, [{{acl, {'$1', Host}, '$2'}, [], [{{acl, '$1', '$2'}}]}])), - make_xhtml([?XCT('h1', "Access Control Lists")] ++ + make_xhtml(?H1GL(?T("Access Control Lists"), "ACLDefinition", "ACL Definition") ++ case Res of ok -> [?CT("Submitted"), ?P]; error -> [?CT("Bad format"), ?P]; @@ -746,7 +775,7 @@ process_admin(Host, [{{config, {access, '$1', Host}, '$2'}, [], [{{access, '$1', '$2'}}]}])])), - make_xhtml([?XCT('h1', "Access Rules")] ++ + make_xhtml(?H1GL(?T("Access Rules"), "AccessRights", "Access Rights") ++ case Res of ok -> [?CT("Submitted"), ?P]; error -> [?CT("Bad format"), ?P]; @@ -784,7 +813,7 @@ process_admin(Host, [{{config, {access, '$1', Host}, '$2'}, [], [{{access, '$1', '$2'}}]}]), - make_xhtml([?XCT('h1', "Access Rules")] ++ + make_xhtml(?H1GL(?T("Access Rules"), "AccessRights", "Access Rights") ++ case Res of ok -> [?CT("Submitted"), ?P]; error -> [?CT("Bad format"), ?P]; @@ -841,7 +870,7 @@ process_admin(global, #request{path = ["vhosts"], lang = Lang}) -> Res = list_vhosts(Lang), - make_xhtml([?XCT('h1', "ejabberd virtual hosts")] ++ Res, global, Lang); + make_xhtml(?H1GL(?T("ejabberd virtual hosts"), "virtualhost", "Virtual Hosting") ++ Res, global, Lang); process_admin(Host, #request{path = ["users"], @@ -1791,7 +1820,8 @@ get_node(global, Node, ["ports"], Query, Lang) -> end, NewPorts = lists:sort( rpc:call(Node, ejabberd_config, get_local_option, [listen])), - [?XC('h1', ?T("Listened Ports at ") ++ atom_to_list(Node))] ++ + H1String = ?T("Listened Ports at ") ++ atom_to_list(Node), + ?H1GL(H1String, "listened", "Listening Ports") ++ case Res of ok -> [?CT("Submitted"), ?P]; error -> [?CT("Bad format"), ?P]; @@ -1814,7 +1844,8 @@ get_node(Host, Node, ["modules"], Query, Lang) when is_list(Host) -> end, NewModules = lists:sort( rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host])), - [?XC('h1', ?T("Modules at ") ++ atom_to_list(Node))] ++ + H1String = ?T("Modules at ") ++ atom_to_list(Node), + ?H1GL(H1String, "modoverview", "Modules Overview") ++ case Res of ok -> [?CT("Submitted"), ?P]; error -> [?CT("Bad format"), ?P]; diff --git a/src/web/ejabberd_web_admin.hrl b/src/web/ejabberd_web_admin.hrl index bfa7475e1..1469b83d8 100644 --- a/src/web/ejabberd_web_admin.hrl +++ b/src/web/ejabberd_web_admin.hrl @@ -52,3 +52,17 @@ #xmlattr{name = 'size', value = Size}])). -define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)). -define(ACLINPUT(Text), ?XE('td', [?INPUT("text", "value" ++ ID, Text)])). + +%% Guide Link +-define(GL(Ref, Title), + ?XAE('div', + [#xmlattr{name = 'class', value = <<"guidelink">>}], + [?XAE('a', + [#xmlattr{name = "href", value = list_to_binary("/admin/doc/guide.html#"++ Ref)}, + #xmlattr{name = "target", value = <<"_blank">>}], + [?C("[Guide: " ++ Title ++ "]")]) + ])). + + +%% h1 with a Guide Link +-define(H1GL(Name, Ref, Title), [?XC('h1', Name), ?GL(Ref, Title)]).