2013-04-15 12:03:14 +02:00
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
%%% File : mod_admin_extra.erl
|
|
|
|
%%% Author : Badlop <badlop@process-one.net>
|
|
|
|
%%% Purpose : Contributed administrative functions and commands
|
|
|
|
%%% Created : 10 Aug 2008 by Badlop <badlop@process-one.net>
|
|
|
|
%%%
|
|
|
|
%%%
|
2024-01-22 16:40:01 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2024 ProcessOne
|
2013-04-15 12:03:14 +02:00
|
|
|
%%%
|
|
|
|
%%% 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.
|
|
|
|
%%%
|
2015-02-25 15:17:31 +01:00
|
|
|
%%% 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.,
|
|
|
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2013-04-15 12:03:14 +02:00
|
|
|
%%%
|
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
|
|
|
|
-module(mod_admin_extra).
|
|
|
|
-author('badlop@process-one.net').
|
|
|
|
|
|
|
|
-behaviour(gen_mod).
|
|
|
|
|
2015-02-10 17:09:03 +01:00
|
|
|
-include("logger.hrl").
|
2020-01-08 10:24:51 +01:00
|
|
|
-include("translate.hrl").
|
2015-02-10 17:09:03 +01:00
|
|
|
|
2018-01-23 08:54:52 +01:00
|
|
|
-export([start/2, stop/1, reload/3, mod_options/1,
|
2020-01-08 10:24:51 +01:00
|
|
|
get_commands_spec/0, depends/2, mod_doc/0]).
|
2016-11-30 10:31:36 +01:00
|
|
|
|
|
|
|
% Commands API
|
|
|
|
-export([
|
|
|
|
% Adminsys
|
2017-06-09 12:02:49 +02:00
|
|
|
compile/1, get_cookie/0,
|
2016-11-30 10:31:36 +01:00
|
|
|
restart_module/2,
|
|
|
|
|
|
|
|
% Sessions
|
2018-08-08 14:08:07 +02:00
|
|
|
num_resources/2, resource_num/3,
|
2015-06-01 14:38:27 +02:00
|
|
|
kick_session/4, status_num/2, status_num/1,
|
|
|
|
status_list/2, status_list/1, connected_users_info/0,
|
|
|
|
connected_users_vhost/1, set_presence/7,
|
2017-09-25 18:43:24 +02:00
|
|
|
get_presence/2, user_sessions_info/2, get_last/2, set_last/4,
|
2016-11-30 10:31:36 +01:00
|
|
|
|
|
|
|
% Accounts
|
|
|
|
set_password/3, check_password_hash/4, delete_old_users/1,
|
2024-04-19 13:47:06 +02:00
|
|
|
delete_old_users_vhost/2, check_password/3,
|
|
|
|
ban_account/3, ban_account_v2/3, get_ban_details/2, unban_account/2,
|
2016-11-30 10:31:36 +01:00
|
|
|
|
|
|
|
% vCard
|
|
|
|
set_nickname/3, get_vcard/3,
|
2015-06-01 14:38:27 +02:00
|
|
|
get_vcard/4, get_vcard_multi/4, set_vcard/4,
|
2016-11-30 10:31:36 +01:00
|
|
|
set_vcard/5,
|
|
|
|
|
|
|
|
% Roster
|
|
|
|
add_rosteritem/7, delete_rosteritem/4,
|
2024-06-13 00:15:42 +02:00
|
|
|
get_roster/2, get_roster_count/2, push_roster/3,
|
2016-11-30 10:31:36 +01:00
|
|
|
push_roster_all/1, push_alltoall/2,
|
2018-02-16 09:52:29 +01:00
|
|
|
push_roster_item/5, build_roster_item/3,
|
2016-11-30 10:31:36 +01:00
|
|
|
|
|
|
|
% Private storage
|
|
|
|
private_get/4, private_set/3,
|
|
|
|
|
|
|
|
% Shared roster
|
2024-06-13 00:15:42 +02:00
|
|
|
srg_create/5, srg_add/2,
|
2015-06-01 14:38:27 +02:00
|
|
|
srg_delete/2, srg_list/1, srg_get_info/2,
|
2024-06-13 00:15:42 +02:00
|
|
|
srg_set_info/4,
|
|
|
|
srg_get_displayed/2, srg_add_displayed/3, srg_del_displayed/3,
|
2015-06-01 14:38:27 +02:00
|
|
|
srg_get_members/2, srg_user_add/4, srg_user_del/4,
|
2016-11-30 10:31:36 +01:00
|
|
|
|
|
|
|
% Send message
|
|
|
|
send_message/5, send_stanza/3, send_stanza_c2s/4,
|
|
|
|
|
|
|
|
% Privacy list
|
|
|
|
privacy_set/3,
|
|
|
|
|
|
|
|
% Stats
|
|
|
|
stats/1, stats/2
|
|
|
|
]).
|
2024-06-13 00:15:42 +02:00
|
|
|
-export([web_menu_main/2, web_page_main/2,
|
|
|
|
web_menu_host/3, web_page_host/3,
|
|
|
|
web_menu_hostuser/4, web_page_hostuser/4,
|
|
|
|
web_menu_hostnode/4, web_page_hostnode/4,
|
|
|
|
web_menu_node/3, web_page_node/3]).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2024-06-13 00:15:42 +02:00
|
|
|
-import(ejabberd_web_admin, [make_command/4, make_table/2]).
|
2015-05-26 10:08:46 +02:00
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
-include("ejabberd_commands.hrl").
|
2024-06-13 00:15:42 +02:00
|
|
|
-include("ejabberd_http.hrl").
|
|
|
|
-include("ejabberd_web_admin.hrl").
|
2013-04-15 12:03:14 +02:00
|
|
|
-include("mod_roster.hrl").
|
2016-10-22 12:01:45 +02:00
|
|
|
-include("mod_privacy.hrl").
|
2015-05-26 10:08:46 +02:00
|
|
|
-include("ejabberd_sm.hrl").
|
2024-04-19 13:47:06 +02:00
|
|
|
-include_lib("xmpp/include/scram.hrl").
|
2020-09-03 13:45:57 +02:00
|
|
|
-include_lib("xmpp/include/xmpp.hrl").
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% gen_mod
|
|
|
|
%%%
|
|
|
|
|
|
|
|
start(_Host, _Opts) ->
|
2024-06-13 00:15:42 +02:00
|
|
|
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
|
|
|
|
{ok, [{hook, webadmin_menu_main, web_menu_main, 50, global},
|
|
|
|
{hook, webadmin_page_main, web_page_main, 50, global},
|
|
|
|
{hook, webadmin_menu_host, web_menu_host, 50},
|
|
|
|
{hook, webadmin_page_host, web_page_host, 50},
|
|
|
|
{hook, webadmin_menu_hostuser, web_menu_hostuser, 50},
|
|
|
|
{hook, webadmin_page_hostuser, web_page_hostuser, 50},
|
|
|
|
{hook, webadmin_menu_hostnode, web_menu_hostnode, 50},
|
|
|
|
{hook, webadmin_page_hostnode, web_page_hostnode, 50},
|
|
|
|
{hook, webadmin_menu_node, web_menu_node, 50, global},
|
|
|
|
{hook, webadmin_page_node, web_page_node, 50, global}]}.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2017-11-06 16:53:49 +01:00
|
|
|
stop(Host) ->
|
|
|
|
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
|
|
|
|
false ->
|
|
|
|
ejabberd_commands:unregister_commands(get_commands_spec());
|
|
|
|
true ->
|
|
|
|
ok
|
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2017-02-22 17:46:47 +01:00
|
|
|
reload(_Host, _NewOpts, _OldOpts) ->
|
|
|
|
ok.
|
|
|
|
|
2016-07-06 13:58:48 +02:00
|
|
|
depends(_Host, _Opts) ->
|
|
|
|
[].
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Register commands
|
|
|
|
%%%
|
|
|
|
|
2016-01-26 10:00:11 +01:00
|
|
|
get_commands_spec() ->
|
2023-11-24 11:58:50 +01:00
|
|
|
Vcard1FieldsString = "Some vcard field names in `get`/`set_vcard` are:\n\n"
|
2020-05-14 20:02:47 +02:00
|
|
|
"* FN - Full Name\n"
|
|
|
|
"* NICKNAME - Nickname\n"
|
|
|
|
"* BDAY - Birthday\n"
|
|
|
|
"* TITLE - Work: Position\n"
|
|
|
|
"* ROLE - Work: Role\n",
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2023-11-24 11:58:50 +01:00
|
|
|
Vcard2FieldsString = "Some vcard field names and subnames in `get`/`set_vcard2` are:\n\n"
|
2020-05-14 20:02:47 +02:00
|
|
|
"* N FAMILY - Family name\n"
|
|
|
|
"* N GIVEN - Given name\n"
|
|
|
|
"* N MIDDLE - Middle name\n"
|
|
|
|
"* ADR CTRY - Address: Country\n"
|
|
|
|
"* ADR LOCALITY - Address: City\n"
|
|
|
|
"* TEL HOME - Telephone: Home\n"
|
|
|
|
"* TEL CELL - Telephone: Cellphone\n"
|
|
|
|
"* TEL WORK - Telephone: Work\n"
|
|
|
|
"* TEL VOICE - Telephone: Voice\n"
|
|
|
|
"* EMAIL USERID - E-Mail Address\n"
|
|
|
|
"* ORG ORGNAME - Work: Company\n"
|
|
|
|
"* ORG ORGUNIT - Work: Department\n",
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2023-11-24 11:58:50 +01:00
|
|
|
VcardXEP = "For a full list of vCard fields check [XEP-0054: vcard-temp]"
|
|
|
|
"(https://xmpp.org/extensions/xep-0054.html)",
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
[
|
|
|
|
#ejabberd_commands{name = compile, tags = [erlang],
|
|
|
|
desc = "Recompile and reload Erlang source code file",
|
|
|
|
module = ?MODULE, function = compile,
|
|
|
|
args = [{file, string}],
|
2016-02-09 13:03:33 +01:00
|
|
|
args_example = ["/home/me/srcs/ejabberd/mod_example.erl"],
|
|
|
|
args_desc = ["Filename of erlang source file to compile"],
|
|
|
|
result = {res, rescode},
|
2023-11-24 16:52:52 +01:00
|
|
|
result_example = ok},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = get_cookie, tags = [erlang],
|
|
|
|
desc = "Get the Erlang cookie of this node",
|
|
|
|
module = ?MODULE, function = get_cookie,
|
|
|
|
args = [],
|
2016-02-09 13:03:33 +01:00
|
|
|
result = {cookie, string},
|
|
|
|
result_example = "MWTAVMODFELNLSMYXPPD",
|
|
|
|
result_desc = "Erlang cookie used for authentication by ejabberd"},
|
2016-11-30 10:31:36 +01:00
|
|
|
#ejabberd_commands{name = restart_module, tags = [erlang],
|
|
|
|
desc = "Stop an ejabberd module, reload code and start",
|
|
|
|
module = ?MODULE, function = restart_module,
|
|
|
|
args = [{host, binary}, {module, binary}],
|
|
|
|
args_example = ["myserver.com","mod_admin_extra"],
|
|
|
|
args_desc = ["Server name", "Module to restart"],
|
|
|
|
result = {res, integer},
|
|
|
|
result_example = 0,
|
|
|
|
result_desc = "Returns integer code:\n"
|
2023-11-24 11:58:50 +01:00
|
|
|
" - `0`: code reloaded, module restarted\n"
|
|
|
|
" - `1`: error: module not loaded\n"
|
|
|
|
" - `2`: code not reloaded, but module restarted"},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
|
|
|
|
desc = "Delete users that didn't log in last days, or that never logged",
|
2017-02-23 19:38:17 +01:00
|
|
|
longdesc = "To protect admin accounts, configure this for example:\n"
|
2024-04-03 22:55:04 +02:00
|
|
|
"``` yaml\n"
|
2017-02-23 19:38:17 +01:00
|
|
|
"access_rules:\n"
|
2017-06-09 19:18:47 +02:00
|
|
|
" protect_old_users:\n"
|
|
|
|
" - allow: admin\n"
|
2023-08-14 13:59:43 +02:00
|
|
|
" - deny: all\n"
|
|
|
|
"```\n",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = delete_old_users,
|
|
|
|
args = [{days, integer}],
|
2016-02-09 13:03:33 +01:00
|
|
|
args_example = [30],
|
|
|
|
args_desc = ["Last login age in days of accounts that should be removed"],
|
2016-02-09 16:33:19 +01:00
|
|
|
result = {res, restuple},
|
|
|
|
result_example = {ok, <<"Deleted 2 users: [\"oldman@myserver.com\", \"test@myserver.com\"]">>},
|
|
|
|
result_desc = "Result tuple"},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = delete_old_users_vhost, tags = [accounts, purge],
|
|
|
|
desc = "Delete users that didn't log in last days in vhost, or that never logged",
|
2017-02-23 19:38:17 +01:00
|
|
|
longdesc = "To protect admin accounts, configure this for example:\n"
|
2024-04-03 22:55:04 +02:00
|
|
|
"``` yaml\n"
|
2017-02-23 19:38:17 +01:00
|
|
|
"access_rules:\n"
|
|
|
|
" delete_old_users:\n"
|
|
|
|
" - deny: admin\n"
|
2023-08-14 13:59:43 +02:00
|
|
|
" - allow: all\n"
|
|
|
|
"```\n",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = delete_old_users_vhost,
|
2014-03-18 09:01:31 +01:00
|
|
|
args = [{host, binary}, {days, integer}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"myserver.com">>, 30],
|
|
|
|
args_desc = ["Server name",
|
|
|
|
"Last login age in days of accounts that should be removed"],
|
|
|
|
result = {res, restuple},
|
|
|
|
result_example = {ok, <<"Deleted 2 users: [\"oldman@myserver.com\", \"test@myserver.com\"]">>},
|
|
|
|
result_desc = "Result tuple"},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = check_account, tags = [accounts],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "Check if an account exists or not",
|
2017-05-11 14:49:06 +02:00
|
|
|
module = ejabberd_auth, function = user_exists,
|
2014-03-18 09:01:31 +01:00
|
|
|
args = [{user, binary}, {host, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"peter">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["User name to check", "Server to check"],
|
|
|
|
result = {res, rescode},
|
2023-11-24 16:52:52 +01:00
|
|
|
result_example = ok},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = check_password, tags = [accounts],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "Check if a password is correct",
|
2016-04-10 15:37:36 +02:00
|
|
|
module = ?MODULE, function = check_password,
|
2014-03-18 09:01:31 +01:00
|
|
|
args = [{user, binary}, {host, binary}, {password, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
|
|
|
|
args_desc = ["User name to check", "Server to check", "Password to check"],
|
|
|
|
result = {res, rescode},
|
2023-11-24 16:52:52 +01:00
|
|
|
result_example = ok},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = check_password_hash, tags = [accounts],
|
|
|
|
desc = "Check if the password hash is correct",
|
2023-08-14 13:59:43 +02:00
|
|
|
longdesc = "Allows hash methods from the Erlang/OTP "
|
|
|
|
"[crypto](https://www.erlang.org/doc/man/crypto) application.",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = check_password_hash,
|
2016-11-10 11:20:57 +01:00
|
|
|
args = [{user, binary}, {host, binary}, {passwordhash, binary},
|
|
|
|
{hashmethod, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"peter">>, <<"myserver.com">>,
|
|
|
|
<<"5ebe2294ecd0e0f08eab7690d2a6ee69">>, <<"md5">>],
|
|
|
|
args_desc = ["User name to check", "Server to check",
|
|
|
|
"Password's hash value", "Name of hash method"],
|
|
|
|
result = {res, rescode},
|
2023-11-24 16:52:52 +01:00
|
|
|
result_example = ok},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = change_password, tags = [accounts],
|
|
|
|
desc = "Change the password of an account",
|
2016-04-01 12:24:00 +02:00
|
|
|
module = ?MODULE, function = set_password,
|
2014-03-16 20:49:01 +01:00
|
|
|
args = [{user, binary}, {host, binary}, {newpass, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"peter">>, <<"myserver.com">>, <<"blank">>],
|
|
|
|
args_desc = ["User name", "Server name",
|
|
|
|
"New password for user"],
|
|
|
|
result = {res, rescode},
|
2023-11-24 16:52:52 +01:00
|
|
|
result_example = ok},
|
2024-04-19 13:47:06 +02:00
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = ban_account, tags = [accounts],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "Ban an account: kick sessions and set random password",
|
2024-04-19 13:47:06 +02:00
|
|
|
longdesc = "This simply sets a random password.",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = ban_account,
|
2014-03-18 09:01:31 +01:00
|
|
|
args = [{user, binary}, {host, binary}, {reason, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"attacker">>, <<"myserver.com">>, <<"Spaming other users">>],
|
|
|
|
args_desc = ["User name to ban", "Server name",
|
|
|
|
"Reason for banning user"],
|
|
|
|
result = {res, rescode},
|
2023-11-24 16:52:52 +01:00
|
|
|
result_example = ok},
|
2024-04-19 13:47:06 +02:00
|
|
|
#ejabberd_commands{name = ban_account, tags = [accounts],
|
|
|
|
desc = "Ban an account",
|
|
|
|
longdesc = "This command kicks the account sessions, "
|
|
|
|
"sets a random password, and stores ban details in the "
|
|
|
|
"account private storage. "
|
|
|
|
"This command requires mod_private to be enabled. "
|
|
|
|
"Check also _`get_ban_details`_ API "
|
|
|
|
"and `_unban_account`_ API.",
|
|
|
|
module = ?MODULE, function = ban_account_v2,
|
|
|
|
version = 2,
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "improved in 24.06",
|
2024-04-19 13:47:06 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {reason, binary}],
|
|
|
|
args_example = [<<"attacker">>, <<"myserver.com">>, <<"Spaming other users">>],
|
|
|
|
args_desc = ["User name to ban", "Server name",
|
|
|
|
"Reason for banning user"],
|
|
|
|
result = {res, rescode},
|
|
|
|
result_example = ok},
|
|
|
|
#ejabberd_commands{name = get_ban_details, tags = [accounts],
|
|
|
|
desc = "Get ban details about an account",
|
|
|
|
longdesc = "Check _`ban_account`_ API.",
|
|
|
|
module = ?MODULE, function = get_ban_details,
|
|
|
|
version = 2,
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "added in 24.06",
|
2024-04-19 13:47:06 +02:00
|
|
|
args = [{user, binary}, {host, binary}],
|
|
|
|
args_example = [<<"attacker">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["User name to unban", "Server name"],
|
|
|
|
result = {ban_details, {list,
|
|
|
|
{detail, {tuple, [{name, string},
|
|
|
|
{value, string}
|
|
|
|
]}}
|
|
|
|
}},
|
|
|
|
result_example = [{"reason", "Spamming other users"},
|
|
|
|
{"bandate", "2024-04-22T09:16:47.975312Z"},
|
|
|
|
{"lastdate", "2024-04-22T08:39:12Z"},
|
|
|
|
{"lastreason", "Connection reset by peer"}]},
|
|
|
|
#ejabberd_commands{name = unban_account, tags = [accounts],
|
|
|
|
desc = "Revert the ban from an account: set back the old password",
|
|
|
|
longdesc = "Check _`ban_account`_ API.",
|
|
|
|
module = ?MODULE, function = unban_account,
|
|
|
|
version = 2,
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "added in 24.06",
|
2024-04-19 13:47:06 +02:00
|
|
|
args = [{user, binary}, {host, binary}],
|
|
|
|
args_example = [<<"gooduser">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["User name to unban", "Server name"],
|
|
|
|
result = {res, rescode},
|
|
|
|
result_example = ok},
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = num_resources, tags = [session],
|
|
|
|
desc = "Get the number of resources of a user",
|
|
|
|
module = ?MODULE, function = num_resources,
|
2014-03-18 09:01:31 +01:00
|
|
|
args = [{user, binary}, {host, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"peter">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["User name", "Server name"],
|
|
|
|
result = {resources, integer},
|
|
|
|
result_example = 5,
|
|
|
|
result_desc = "Number of active resources for a user"},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = resource_num, tags = [session],
|
|
|
|
desc = "Resource string of a session number",
|
|
|
|
module = ?MODULE, function = resource_num,
|
2014-03-18 09:01:31 +01:00
|
|
|
args = [{user, binary}, {host, binary}, {num, integer}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"peter">>, <<"myserver.com">>, 2],
|
|
|
|
args_desc = ["User name", "Server name", "ID of resource to return"],
|
|
|
|
result = {resource, string},
|
|
|
|
result_example = <<"Psi">>,
|
|
|
|
result_desc = "Name of user resource"},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = kick_session, tags = [session],
|
|
|
|
desc = "Kick a user session",
|
|
|
|
module = ?MODULE, function = kick_session,
|
2014-04-02 04:17:08 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {resource, binary}, {reason, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"peter">>, <<"myserver.com">>, <<"Psi">>,
|
|
|
|
<<"Stuck connection">>],
|
|
|
|
args_desc = ["User name", "Server name", "User's resource",
|
|
|
|
"Reason for closing session"],
|
|
|
|
result = {res, rescode},
|
2023-11-24 16:52:52 +01:00
|
|
|
result_example = ok},
|
2021-04-13 14:47:49 +02:00
|
|
|
#ejabberd_commands{name = status_num_host, tags = [session, statistics],
|
2013-04-15 12:03:14 +02:00
|
|
|
desc = "Number of logged users with this status in host",
|
2016-11-30 10:31:36 +01:00
|
|
|
policy = admin,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = status_num,
|
2014-04-02 04:17:08 +02:00
|
|
|
args = [{host, binary}, {status, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"myserver.com">>, <<"dnd">>],
|
|
|
|
args_desc = ["Server name", "Status type to check"],
|
|
|
|
result = {users, integer},
|
|
|
|
result_example = 23,
|
|
|
|
result_desc = "Number of connected sessions with given status type"},
|
2021-04-13 14:47:49 +02:00
|
|
|
#ejabberd_commands{name = status_num, tags = [session, statistics],
|
2013-04-15 12:03:14 +02:00
|
|
|
desc = "Number of logged users with this status",
|
2016-11-30 10:31:36 +01:00
|
|
|
policy = admin,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = status_num,
|
2014-04-02 04:17:08 +02:00
|
|
|
args = [{status, binary}],
|
2016-02-09 16:33:19 +01:00
|
|
|
args_example = [<<"dnd">>],
|
|
|
|
args_desc = ["Status type to check"],
|
|
|
|
result = {users, integer},
|
|
|
|
result_example = 23,
|
|
|
|
result_desc = "Number of connected sessions with given status type"},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = status_list_host, tags = [session],
|
|
|
|
desc = "List of users logged in host with their statuses",
|
|
|
|
module = ?MODULE, function = status_list,
|
2014-04-02 04:17:08 +02:00
|
|
|
args = [{host, binary}, {status, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"myserver.com">>, <<"dnd">>],
|
|
|
|
args_desc = ["Server name", "Status type to check"],
|
|
|
|
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {users, {list,
|
2016-04-01 12:24:00 +02:00
|
|
|
{userstatus, {tuple, [
|
|
|
|
{user, string},
|
|
|
|
{host, string},
|
|
|
|
{resource, string},
|
|
|
|
{priority, integer},
|
|
|
|
{status, string}
|
|
|
|
]}}
|
2013-04-15 12:03:14 +02:00
|
|
|
}}},
|
|
|
|
#ejabberd_commands{name = status_list, tags = [session],
|
|
|
|
desc = "List of logged users with this status",
|
|
|
|
module = ?MODULE, function = status_list,
|
2014-04-02 04:17:08 +02:00
|
|
|
args = [{status, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"dnd">>],
|
|
|
|
args_desc = ["Status type to check"],
|
|
|
|
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {users, {list,
|
|
|
|
{userstatus, {tuple, [
|
|
|
|
{user, string},
|
|
|
|
{host, string},
|
|
|
|
{resource, string},
|
|
|
|
{priority, integer},
|
|
|
|
{status, string}
|
|
|
|
]}}
|
|
|
|
}}},
|
|
|
|
#ejabberd_commands{name = connected_users_info,
|
|
|
|
tags = [session],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "List all established sessions and their information",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = connected_users_info,
|
|
|
|
args = [],
|
2018-06-15 11:28:44 +02:00
|
|
|
result_example = [{"user1@myserver.com/tka",
|
|
|
|
"c2s", "127.0.0.1", 42656,8, "ejabberd@localhost",
|
|
|
|
231, <<"dnd">>, <<"tka">>, <<>>}],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {connected_users_info,
|
|
|
|
{list,
|
2018-06-15 11:28:44 +02:00
|
|
|
{session, {tuple,
|
|
|
|
[{jid, string},
|
|
|
|
{connection, string},
|
|
|
|
{ip, string},
|
|
|
|
{port, integer},
|
|
|
|
{priority, integer},
|
|
|
|
{node, string},
|
|
|
|
{uptime, integer},
|
|
|
|
{status, string},
|
|
|
|
{resource, string},
|
|
|
|
{statustext, string}
|
|
|
|
]}}
|
2013-04-15 12:03:14 +02:00
|
|
|
}}},
|
2018-06-15 11:28:44 +02:00
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = connected_users_vhost,
|
2016-11-30 10:31:36 +01:00
|
|
|
tags = [session],
|
|
|
|
desc = "Get the list of established sessions in a vhost",
|
|
|
|
module = ?MODULE, function = connected_users_vhost,
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"myexample.com">>],
|
|
|
|
args_desc = ["Server name"],
|
|
|
|
result_example = [<<"user1@myserver.com/tka">>, <<"user2@localhost/tka">>],
|
2016-11-30 10:31:36 +01:00
|
|
|
args = [{host, binary}],
|
|
|
|
result = {connected_users_vhost, {list, {sessions, string}}}},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = user_sessions_info,
|
|
|
|
tags = [session],
|
|
|
|
desc = "Get information about all sessions of a user",
|
|
|
|
module = ?MODULE, function = user_sessions_info,
|
2015-02-14 00:15:02 +01:00
|
|
|
args = [{user, binary}, {host, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"peter">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["User name", "Server name"],
|
|
|
|
result_example = [{"c2s", "127.0.0.1", 42656,8, "ejabberd@localhost",
|
|
|
|
231, <<"dnd">>, <<"tka">>, <<>>}],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {sessions_info,
|
|
|
|
{list,
|
|
|
|
{session, {tuple,
|
|
|
|
[{connection, string},
|
|
|
|
{ip, string},
|
|
|
|
{port, integer},
|
|
|
|
{priority, integer},
|
|
|
|
{node, string},
|
|
|
|
{uptime, integer},
|
|
|
|
{status, string},
|
|
|
|
{resource, string},
|
|
|
|
{statustext, string}
|
|
|
|
]}}
|
|
|
|
}}},
|
2016-04-01 12:24:00 +02:00
|
|
|
|
2016-11-30 10:31:36 +01:00
|
|
|
#ejabberd_commands{name = get_presence, tags = [session],
|
|
|
|
desc =
|
|
|
|
"Retrieve the resource with highest priority, "
|
|
|
|
"and its presence (show and status message) "
|
|
|
|
"for a given user.",
|
|
|
|
longdesc =
|
2023-08-14 13:59:43 +02:00
|
|
|
"The `jid` value contains the user JID "
|
|
|
|
"with resource.\n\nThe `show` value contains "
|
2016-11-30 10:31:36 +01:00
|
|
|
"the user presence flag. It can take "
|
2023-08-14 13:59:43 +02:00
|
|
|
"limited values:\n\n - `available`\n - `chat` "
|
|
|
|
"(Free for chat)\n - `away`\n - `dnd` (Do "
|
|
|
|
"not disturb)\n - `xa` (Not available, "
|
|
|
|
"extended away)\n - `unavailable` (Not "
|
|
|
|
"connected)\n\n`status` is a free text "
|
2016-11-30 10:31:36 +01:00
|
|
|
"defined by the user client.",
|
|
|
|
module = ?MODULE, function = get_presence,
|
2019-06-19 09:26:28 +02:00
|
|
|
args = [{user, binary}, {host, binary}],
|
|
|
|
args_rename = [{server, host}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"peter">>, <<"myexample.com">>],
|
|
|
|
args_desc = ["User name", "Server name"],
|
|
|
|
result_example = {<<"user1@myserver.com/tka">>, <<"dnd">>, <<"Busy">>},
|
2016-11-30 10:31:36 +01:00
|
|
|
result =
|
|
|
|
{presence,
|
|
|
|
{tuple,
|
|
|
|
[{jid, string}, {show, string},
|
|
|
|
{status, string}]}}},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = set_presence,
|
|
|
|
tags = [session],
|
|
|
|
desc = "Set presence of a session",
|
|
|
|
module = ?MODULE, function = set_presence,
|
2015-03-26 20:41:16 +01:00
|
|
|
args = [{user, binary}, {host, binary},
|
|
|
|
{resource, binary}, {type, binary},
|
|
|
|
{show, binary}, {status, binary},
|
2016-04-01 12:24:00 +02:00
|
|
|
{priority, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"tka1">>,
|
|
|
|
<<"available">>,<<"away">>,<<"BB">>, <<"7">>],
|
|
|
|
args_desc = ["User name", "Server name", "Resource",
|
2023-08-14 13:59:43 +02:00
|
|
|
"Type: `available`, `error`, `probe`...",
|
|
|
|
"Show: `away`, `chat`, `dnd`, `xa`.", "Status text",
|
2017-07-14 16:43:22 +02:00
|
|
|
"Priority, provide this value as an integer"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
2023-11-24 17:07:21 +01:00
|
|
|
#ejabberd_commands{name = set_presence,
|
|
|
|
tags = [session],
|
|
|
|
desc = "Set presence of a session",
|
|
|
|
module = ?MODULE, function = set_presence,
|
|
|
|
version = 1,
|
2024-02-24 17:40:03 +01:00
|
|
|
note = "updated in 24.02",
|
2023-11-24 17:07:21 +01:00
|
|
|
args = [{user, binary}, {host, binary},
|
|
|
|
{resource, binary}, {type, binary},
|
|
|
|
{show, binary}, {status, binary},
|
|
|
|
{priority, integer}],
|
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"tka1">>,
|
|
|
|
<<"available">>,<<"away">>,<<"BB">>, 7],
|
|
|
|
args_desc = ["User name", "Server name", "Resource",
|
|
|
|
"Type: `available`, `error`, `probe`...",
|
|
|
|
"Show: `away`, `chat`, `dnd`, `xa`.", "Status text",
|
|
|
|
"Priority, provide this value as an integer"],
|
|
|
|
result = {res, rescode}},
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
#ejabberd_commands{name = set_nickname, tags = [vcard],
|
|
|
|
desc = "Set nickname in a user's vCard",
|
|
|
|
module = ?MODULE, function = set_nickname,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {nickname, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"User 1">>],
|
|
|
|
args_desc = ["User name", "Server name", "Nickname"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = get_vcard, tags = [vcard],
|
|
|
|
desc = "Get content from a vCard field",
|
2020-05-14 20:02:47 +02:00
|
|
|
longdesc = Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = get_vcard,
|
2014-03-24 19:01:43 +01:00
|
|
|
args = [{user, binary}, {host, binary}, {name, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"NICKNAME">>],
|
|
|
|
args_desc = ["User name", "Server name", "Field name"],
|
|
|
|
result_example = "User 1",
|
|
|
|
result_desc = "Field content",
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {content, string}},
|
|
|
|
#ejabberd_commands{name = get_vcard2, tags = [vcard],
|
2017-07-14 16:43:22 +02:00
|
|
|
desc = "Get content from a vCard subfield",
|
2020-05-14 20:02:47 +02:00
|
|
|
longdesc = Vcard2FieldsString ++ "\n" ++ VcardXEP,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = get_vcard,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"N">>, <<"FAMILY">>],
|
|
|
|
args_desc = ["User name", "Server name", "Field name", "Subfield name"],
|
|
|
|
result_example = "Schubert",
|
|
|
|
result_desc = "Field content",
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {content, string}},
|
|
|
|
#ejabberd_commands{name = get_vcard2_multi, tags = [vcard],
|
2015-04-01 15:44:49 +02:00
|
|
|
desc = "Get multiple contents from a vCard field",
|
2020-05-14 20:02:47 +02:00
|
|
|
longdesc = Vcard2FieldsString ++ "\n" ++ VcardXEP,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = get_vcard_multi,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
|
2015-04-01 16:32:34 +02:00
|
|
|
result = {contents, {list, {value, string}}}},
|
2016-04-01 12:24:00 +02:00
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = set_vcard, tags = [vcard],
|
|
|
|
desc = "Set content in a vCard field",
|
2020-05-14 20:02:47 +02:00
|
|
|
longdesc = Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = set_vcard,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {name, binary}, {content, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>, <<"URL">>, <<"www.example.com">>],
|
|
|
|
args_desc = ["User name", "Server name", "Field name", "Value"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = set_vcard2, tags = [vcard],
|
|
|
|
desc = "Set content in a vCard subfield",
|
2020-05-14 20:02:47 +02:00
|
|
|
longdesc = Vcard2FieldsString ++ "\n" ++ VcardXEP,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = set_vcard,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {content, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"TEL">>, <<"NUMBER">>, <<"123456">>],
|
|
|
|
args_desc = ["User name", "Server name", "Field name", "Subfield name", "Value"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = set_vcard2_multi, tags = [vcard],
|
|
|
|
desc = "Set multiple contents in a vCard subfield",
|
2020-05-14 20:02:47 +02:00
|
|
|
longdesc = Vcard2FieldsString ++ "\n" ++ VcardXEP,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = set_vcard,
|
2015-12-28 12:19:49 +01:00
|
|
|
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {contents, {list, {value, binary}}}],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
|
|
|
|
#ejabberd_commands{name = add_rosteritem, tags = [roster],
|
|
|
|
desc = "Add an item to a user's roster (supports ODBC)",
|
2023-08-14 13:59:43 +02:00
|
|
|
longdesc = "Group can be several groups separated by `;` for example: `g1;g2;g3`",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = add_rosteritem,
|
2019-06-19 09:26:28 +02:00
|
|
|
args = [{localuser, binary}, {localhost, binary},
|
|
|
|
{user, binary}, {host, binary},
|
2014-03-25 17:15:53 +01:00
|
|
|
{nick, binary}, {group, binary},
|
2014-04-02 11:23:04 +02:00
|
|
|
{subs, binary}],
|
2019-06-19 09:26:28 +02:00
|
|
|
args_rename = [{localserver, localhost}, {server, host}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>,
|
|
|
|
<<"User 2">>, <<"Friends">>, <<"both">>],
|
|
|
|
args_desc = ["User name", "Server name", "Contact user name", "Contact server name",
|
|
|
|
"Nickname", "Group", "Subscription"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
2023-11-29 19:04:57 +01:00
|
|
|
#ejabberd_commands{name = add_rosteritem, tags = [roster],
|
|
|
|
desc = "Add an item to a user's roster (supports ODBC)",
|
|
|
|
module = ?MODULE, function = add_rosteritem,
|
|
|
|
version = 1,
|
2024-02-24 17:40:03 +01:00
|
|
|
note = "updated in 24.02",
|
2023-11-29 19:04:57 +01:00
|
|
|
args = [{localuser, binary}, {localhost, binary},
|
|
|
|
{user, binary}, {host, binary},
|
|
|
|
{nick, binary}, {groups, {list, {group, binary}}},
|
|
|
|
{subs, binary}],
|
|
|
|
args_rename = [{localserver, localhost}, {server, host}],
|
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>,
|
|
|
|
<<"User 2">>, [<<"Friends">>, <<"Team 1">>], <<"both">>],
|
|
|
|
args_desc = ["User name", "Server name", "Contact user name", "Contact server name",
|
|
|
|
"Nickname", "Groups", "Subscription"],
|
|
|
|
result = {res, rescode}},
|
2013-04-15 12:03:14 +02:00
|
|
|
%%{"", "subs= none, from, to or both"},
|
|
|
|
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
|
|
|
|
%%{"", "will add mike@server.com to peter@localhost roster"},
|
|
|
|
#ejabberd_commands{name = delete_rosteritem, tags = [roster],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "Delete an item from a user's roster (supports ODBC)",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = delete_rosteritem,
|
2019-06-19 09:26:28 +02:00
|
|
|
args = [{localuser, binary}, {localhost, binary},
|
|
|
|
{user, binary}, {host, binary}],
|
|
|
|
args_rename = [{localserver, localhost}, {server, host}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["User name", "Server name", "Contact user name", "Contact server name"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = process_rosteritems, tags = [roster],
|
2019-05-15 10:57:55 +02:00
|
|
|
desc = "List/delete rosteritems that match filter",
|
2023-11-24 11:58:50 +01:00
|
|
|
longdesc = "Explanation of each argument:\n\n"
|
|
|
|
"* `action`: what to do with each rosteritem that "
|
2013-04-15 12:03:14 +02:00
|
|
|
"matches all the filtering options\n"
|
2023-11-24 11:58:50 +01:00
|
|
|
"* `subs`: subscription type\n"
|
|
|
|
"* `asks`: pending subscription\n"
|
|
|
|
"* `users`: the JIDs of the local user\n"
|
|
|
|
"* `contacts`: the JIDs of the contact in the roster\n"
|
2013-04-15 12:03:14 +02:00
|
|
|
"\n"
|
2023-11-24 11:58:50 +01:00
|
|
|
"**Mnesia backend:**\n"
|
2019-05-15 10:57:55 +02:00
|
|
|
"\n"
|
2023-11-24 11:58:50 +01:00
|
|
|
"Allowed values in the arguments:\n\n"
|
|
|
|
"* `action` = `list` | `delete`\n"
|
|
|
|
"* `subs` = `any` | SUB[:SUB]*\n"
|
|
|
|
"* `asks` = `any` | ASK[:ASK]*\n"
|
|
|
|
"* `users` = `any` | JID[:JID]*\n"
|
|
|
|
"* `contacts` = `any` | JID[:JID]*\n"
|
|
|
|
"\nwhere\n\n"
|
|
|
|
"* SUB = `none` | `from `| `to` | `both`\n"
|
|
|
|
"* ASK = `none` | `out` | `in`\n"
|
|
|
|
"* JID = characters valid in a JID, and can use the "
|
|
|
|
"globs: `*`, `?`, `!` and `[...]`\n"
|
2013-04-15 12:03:14 +02:00
|
|
|
"\n"
|
|
|
|
"This example will list roster items with subscription "
|
2023-11-24 11:58:50 +01:00
|
|
|
"`none`, `from` or `to` that have any ask property, of "
|
2013-04-15 12:03:14 +02:00
|
|
|
"local users which JID is in the virtual host "
|
2023-11-24 11:58:50 +01:00
|
|
|
"`example.org` and that the contact JID is either a "
|
2013-04-15 12:03:14 +02:00
|
|
|
"bare server name (without user part) or that has a "
|
2023-11-24 11:58:50 +01:00
|
|
|
"user part and the server part contains the word `icq`"
|
|
|
|
":\n `list none:from:to any *@example.org *:*@*icq*`"
|
2019-05-15 10:57:55 +02:00
|
|
|
"\n\n"
|
2023-11-24 11:58:50 +01:00
|
|
|
"**SQL backend:**\n"
|
2019-05-15 10:57:55 +02:00
|
|
|
"\n"
|
2023-11-24 11:58:50 +01:00
|
|
|
"Allowed values in the arguments:\n\n"
|
|
|
|
"* `action` = `list` | `delete`\n"
|
|
|
|
"* `subs` = `any` | SUB\n"
|
|
|
|
"* `asks` = `any` | ASK\n"
|
|
|
|
"* `users` = JID\n"
|
|
|
|
"* `contacts` = JID\n"
|
|
|
|
"\nwhere\n\n"
|
|
|
|
"* SUB = `none` | `from` | `to` | `both`\n"
|
|
|
|
"* ASK = `none` | `out` | `in`\n"
|
|
|
|
"* JID = characters valid in a JID, and can use the "
|
|
|
|
"globs: `_` and `%`\n"
|
2019-05-15 10:57:55 +02:00
|
|
|
"\n"
|
|
|
|
"This example will list roster items with subscription "
|
2023-11-24 11:58:50 +01:00
|
|
|
"`to` that have any ask property, of "
|
2019-05-15 10:57:55 +02:00
|
|
|
"local users which JID is in the virtual host "
|
2023-11-24 11:58:50 +01:00
|
|
|
"`example.org` and that the contact JID's "
|
|
|
|
"server part contains the word `icq`"
|
|
|
|
":\n `list to any %@example.org %@%icq%`",
|
2019-05-15 10:57:55 +02:00
|
|
|
module = mod_roster, function = process_rosteritems,
|
2013-04-15 12:03:14 +02:00
|
|
|
args = [{action, string}, {subs, string},
|
|
|
|
{asks, string}, {users, string},
|
|
|
|
{contacts, string}],
|
|
|
|
result = {response,
|
|
|
|
{list,
|
|
|
|
{pairs, {tuple,
|
|
|
|
[{user, string},
|
|
|
|
{contact, string}
|
|
|
|
]}}
|
|
|
|
}}},
|
|
|
|
#ejabberd_commands{name = get_roster, tags = [roster],
|
2023-09-21 12:45:27 +02:00
|
|
|
desc = "Get list of contacts in a local user roster",
|
|
|
|
longdesc =
|
2023-11-24 11:58:50 +01:00
|
|
|
"`subscription` can be: `none`, `from`, `to`, `both`.\n\n"
|
|
|
|
"`pending` can be: `in`, `out`, `none`.",
|
2023-10-02 10:54:24 +02:00
|
|
|
note = "improved in 23.10",
|
2015-09-28 14:25:43 +02:00
|
|
|
policy = user,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = get_roster,
|
2015-09-28 14:25:43 +02:00
|
|
|
args = [],
|
2019-06-19 09:26:28 +02:00
|
|
|
args_rename = [{server, host}],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {contacts, {list, {contact, {tuple, [
|
|
|
|
{jid, string},
|
|
|
|
{nick, string},
|
|
|
|
{subscription, string},
|
2023-09-21 12:45:27 +02:00
|
|
|
{pending, string},
|
|
|
|
{groups, {list, {group, string}}}
|
2013-04-15 12:03:14 +02:00
|
|
|
]}}}}},
|
2024-06-13 00:15:42 +02:00
|
|
|
#ejabberd_commands{name = get_roster_count, tags = [roster],
|
|
|
|
desc = "Get number of contacts in a local user roster",
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "added in 24.06",
|
2024-06-13 00:15:42 +02:00
|
|
|
policy = user,
|
|
|
|
module = ?MODULE, function = get_roster_count,
|
|
|
|
args = [],
|
|
|
|
args_rename = [{server, host}],
|
|
|
|
result_example = 5,
|
|
|
|
result_desc = "Number",
|
|
|
|
result = {value, integer}},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = push_roster, tags = [roster],
|
|
|
|
desc = "Push template roster from file to a user",
|
2017-07-14 16:43:22 +02:00
|
|
|
longdesc = "The text file must contain an erlang term: a list "
|
2023-11-24 11:58:50 +01:00
|
|
|
"of tuples with username, servername, group and nick. For example:\n"
|
|
|
|
"`[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
|
|
|
|
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].`\n\n"
|
|
|
|
"If there are problems parsing UTF8 character encoding, "
|
|
|
|
"provide the corresponding string with the `<<\"STRING\"/utf8>>` syntax, for example:\n"
|
|
|
|
"`[{\"user2\", \"localhost\", \"Workers\", <<\"User 2\"/utf8>>}]`.",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = push_roster,
|
2015-06-01 13:30:26 +02:00
|
|
|
args = [{file, binary}, {user, binary}, {host, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>],
|
|
|
|
args_desc = ["File path", "User name", "Server name"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = push_roster_all, tags = [roster],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "Push template roster from file to all those users",
|
2017-06-08 19:54:34 +02:00
|
|
|
longdesc = "The text file must contain an erlang term: a list "
|
|
|
|
"of tuples with username, servername, group and nick. Example:\n"
|
2023-11-24 11:58:50 +01:00
|
|
|
"`[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
|
|
|
|
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].`",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = push_roster_all,
|
2015-06-01 13:30:26 +02:00
|
|
|
args = [{file, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"/home/ejabberd/roster.txt">>],
|
|
|
|
args_desc = ["File path"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = push_alltoall, tags = [roster],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "Add all the users to all the users of Host in Group",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = push_alltoall,
|
2015-06-02 12:52:15 +02:00
|
|
|
args = [{host, binary}, {group, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"myserver.com">>,<<"Everybody">>],
|
|
|
|
args_desc = ["Server name", "Group name"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
|
2016-04-01 12:24:00 +02:00
|
|
|
#ejabberd_commands{name = get_last, tags = [last],
|
2017-02-23 23:27:57 +01:00
|
|
|
desc = "Get last activity information",
|
2023-11-24 11:58:50 +01:00
|
|
|
longdesc = "Timestamp is UTC and "
|
|
|
|
"[XEP-0082](https://xmpp.org/extensions/xep-0082.html)"
|
|
|
|
" format, for example: "
|
2023-08-14 13:59:43 +02:00
|
|
|
"`2017-02-23T22:25:28.063062Z ONLINE`",
|
2016-04-01 12:24:00 +02:00
|
|
|
module = ?MODULE, function = get_last,
|
|
|
|
args = [{user, binary}, {host, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>],
|
|
|
|
args_desc = ["User name", "Server name"],
|
|
|
|
result_example = {<<"2017-06-30T14:32:16.060684Z">>, "ONLINE"},
|
|
|
|
result_desc = "Last activity timestamp and status",
|
2017-02-23 23:27:57 +01:00
|
|
|
result = {last_activity,
|
|
|
|
{tuple, [{timestamp, string},
|
|
|
|
{status, string}
|
|
|
|
]}}},
|
2016-04-01 12:24:00 +02:00
|
|
|
#ejabberd_commands{name = set_last, tags = [last],
|
|
|
|
desc = "Set last activity information",
|
2017-09-25 18:43:24 +02:00
|
|
|
longdesc = "Timestamp is the seconds since "
|
2023-08-14 13:59:43 +02:00
|
|
|
"`1970-01-01 00:00:00 UTC`. For example value see `date +%s`",
|
2017-09-25 18:43:24 +02:00
|
|
|
module = ?MODULE, function = set_last,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
|
2017-07-14 17:17:26 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>],
|
|
|
|
args_desc = ["User name", "Server name", "Number of seconds since epoch", "Status message"],
|
2016-04-01 12:24:00 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = private_get, tags = [private],
|
|
|
|
desc = "Get some information from a user private storage",
|
|
|
|
module = ?MODULE, function = private_get,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {element, binary}, {ns, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"storage">>, <<"storage:rosternotes">>],
|
|
|
|
args_desc = ["User name", "Server name", "Element name", "Namespace"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, string}},
|
|
|
|
#ejabberd_commands{name = private_set, tags = [private],
|
|
|
|
desc = "Set to the user private storage",
|
|
|
|
module = ?MODULE, function = private_set,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {element, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>,<<"myserver.com">>,
|
2019-05-29 19:41:36 +02:00
|
|
|
<<"<storage xmlns='storage:rosternotes'/>">>],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_desc = ["User name", "Server name", "XML storage element"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
|
|
|
|
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
|
|
|
|
desc = "Create a Shared Roster Group",
|
|
|
|
longdesc = "If you want to specify several group "
|
|
|
|
"identifiers in the Display argument,\n"
|
2023-08-14 13:59:43 +02:00
|
|
|
"put `\\ \"` around the argument and\nseparate the "
|
|
|
|
"identifiers with `\\ \\ n`\n"
|
2013-04-15 12:03:14 +02:00
|
|
|
"For example:\n"
|
2023-08-14 13:59:43 +02:00
|
|
|
" `ejabberdctl srg_create group3 myserver.com "
|
|
|
|
"name desc \\\"group1\\\\ngroup2\\\"`",
|
2021-07-20 19:19:58 +02:00
|
|
|
note = "changed in 21.07",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = srg_create,
|
2014-03-03 12:31:17 +01:00
|
|
|
args = [{group, binary}, {host, binary},
|
2021-04-17 18:49:26 +02:00
|
|
|
{label, binary}, {description, binary}, {display, binary}],
|
|
|
|
args_rename = [{name, label}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>, <<"Group3">>,
|
|
|
|
<<"Third group">>, <<"group1\\\\ngroup2">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name", "Group name",
|
|
|
|
"Group description", "Groups to display"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
2023-11-29 19:04:57 +01:00
|
|
|
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
|
|
|
|
desc = "Create a Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_create,
|
|
|
|
version = 1,
|
2024-02-24 17:40:03 +01:00
|
|
|
note = "updated in 24.02",
|
2023-11-29 19:04:57 +01:00
|
|
|
args = [{group, binary}, {host, binary},
|
|
|
|
{label, binary}, {description, binary}, {display, {list, {group, binary}}}],
|
|
|
|
args_rename = [{name, label}],
|
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>, <<"Group3">>,
|
|
|
|
<<"Third group">>, [<<"group1">>, <<"group2">>]],
|
|
|
|
args_desc = ["Group identifier", "Group server name", "Group name",
|
|
|
|
"Group description", "List of groups to display"],
|
|
|
|
result = {res, rescode}},
|
2024-06-13 00:15:42 +02:00
|
|
|
#ejabberd_commands{name = srg_add, tags = [shared_roster_group],
|
|
|
|
desc = "Add/Create a Shared Roster Group (without details)",
|
|
|
|
module = ?MODULE, function = srg_add,
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "added in 24.06",
|
2024-06-13 00:15:42 +02:00
|
|
|
args = [{group, binary}, {host, binary}],
|
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name"],
|
|
|
|
result = {res, rescode}},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
|
|
|
|
desc = "Delete a Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_delete,
|
2014-03-03 12:31:17 +01:00
|
|
|
args = [{group, binary}, {host, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = srg_list, tags = [shared_roster_group],
|
|
|
|
desc = "List the Shared Roster Groups in Host",
|
|
|
|
module = ?MODULE, function = srg_list,
|
2014-03-03 12:31:17 +01:00
|
|
|
args = [{host, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"myserver.com">>],
|
|
|
|
args_desc = ["Server name"],
|
|
|
|
result_example = [<<"group1">>, <<"group2">>],
|
|
|
|
result_desc = "List of group identifiers",
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {groups, {list, {id, string}}}},
|
|
|
|
#ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
|
|
|
|
desc = "Get info of a Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_get_info,
|
2014-03-03 12:31:17 +01:00
|
|
|
args = [{group, binary}, {host, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name"],
|
|
|
|
result_example = [{<<"name">>, "Group 3"}, {<<"displayed_groups">>, "group1"}],
|
2022-01-24 01:01:39 +01:00
|
|
|
result_desc = "List of group information, as key and value",
|
2016-04-01 12:24:00 +02:00
|
|
|
result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
|
2024-06-13 00:15:42 +02:00
|
|
|
#ejabberd_commands{name = srg_set_info, tags = [shared_roster_group],
|
|
|
|
desc = "Set info of a Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_set_info,
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "added in 24.06",
|
2024-06-13 00:15:42 +02:00
|
|
|
args = [{group, binary}, {host, binary}, {key, binary}, {value, binary}],
|
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>, <<"label">>, <<"Family">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name",
|
|
|
|
"Information key: label, description",
|
|
|
|
"Information value"],
|
|
|
|
result = {res, rescode}},
|
|
|
|
|
|
|
|
#ejabberd_commands{name = srg_get_displayed, tags = [shared_roster_group],
|
|
|
|
desc = "Get displayed groups of a Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_get_displayed,
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "added in 24.06",
|
2024-06-13 00:15:42 +02:00
|
|
|
args = [{group, binary}, {host, binary}],
|
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name"],
|
|
|
|
result_example = [<<"group1">>, <<"group2">>],
|
|
|
|
result_desc = "List of groups to display",
|
|
|
|
result = {display, {list, {group, binary}}}},
|
|
|
|
#ejabberd_commands{name = srg_add_displayed, tags = [shared_roster_group],
|
|
|
|
desc = "Add a group to displayed_groups of a Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_add_displayed,
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "added in 24.06",
|
2024-06-13 00:15:42 +02:00
|
|
|
args = [{group, binary}, {host, binary},
|
|
|
|
{add, binary}],
|
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>, <<"group1">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name",
|
|
|
|
"Group to add to displayed_groups"],
|
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = srg_del_displayed, tags = [shared_roster_group],
|
|
|
|
desc = "Delete a group from displayed_groups of a Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_del_displayed,
|
2024-06-27 10:54:09 +02:00
|
|
|
note = "added in 24.06",
|
2024-06-13 00:15:42 +02:00
|
|
|
args = [{group, binary}, {host, binary},
|
|
|
|
{del, binary}],
|
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>, <<"group1">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name",
|
|
|
|
"Group to delete from displayed_groups"],
|
|
|
|
result = {res, rescode}},
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
|
|
|
|
desc = "Get members of a Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_get_members,
|
2014-03-03 12:31:17 +01:00
|
|
|
args = [{group, binary}, {host, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"group3">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["Group identifier", "Group server name"],
|
|
|
|
result_example = [<<"user1@localhost">>, <<"user2@localhost">>],
|
|
|
|
result_desc = "List of group identifiers",
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {members, {list, {member, string}}}},
|
|
|
|
#ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
|
|
|
|
desc = "Add the JID user@host to the Shared Roster Group",
|
|
|
|
module = ?MODULE, function = srg_user_add,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
#ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "Delete this JID user@host from the Shared Roster Group",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = srg_user_del,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
|
|
|
|
args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
|
2016-04-01 12:24:00 +02:00
|
|
|
#ejabberd_commands{name = get_offline_count,
|
|
|
|
tags = [offline],
|
|
|
|
desc = "Get the number of unread offline messages",
|
2016-11-30 10:31:36 +01:00
|
|
|
policy = user,
|
2016-04-01 12:24:00 +02:00
|
|
|
module = mod_offline, function = count_offline_messages,
|
|
|
|
args = [],
|
2019-06-19 09:26:28 +02:00
|
|
|
args_rename = [{server, host}],
|
2017-07-14 16:43:22 +02:00
|
|
|
result_example = 5,
|
|
|
|
result_desc = "Number",
|
2016-07-30 20:12:04 +02:00
|
|
|
result = {value, integer}},
|
2024-06-13 00:15:42 +02:00
|
|
|
#ejabberd_commands{name = get_offline_messages,
|
|
|
|
tags = [internal, offline],
|
|
|
|
desc = "Get the offline messages",
|
|
|
|
policy = user,
|
|
|
|
module = mod_offline, function = get_offline_messages,
|
|
|
|
args = [],
|
|
|
|
result = {queue, {list, {messages, {tuple, [{time, string},
|
|
|
|
{from, string},
|
|
|
|
{to, string},
|
|
|
|
{packet, string}
|
|
|
|
]}}}}},
|
|
|
|
|
2015-02-11 18:48:43 +01:00
|
|
|
#ejabberd_commands{name = send_message, tags = [stanza],
|
2016-04-01 12:24:00 +02:00
|
|
|
desc = "Send a message to a local or remote bare of full JID",
|
2019-08-01 11:29:42 +02:00
|
|
|
longdesc = "When sending a groupchat message to a MUC room, "
|
2023-08-14 13:59:43 +02:00
|
|
|
"`from` must be the full JID of a room occupant, "
|
2019-08-01 11:29:42 +02:00
|
|
|
"or the bare JID of a MUC service admin, "
|
|
|
|
"or the bare JID of a MUC/Sub subscribed user.",
|
2015-02-11 18:48:43 +01:00
|
|
|
module = ?MODULE, function = send_message,
|
|
|
|
args = [{type, binary}, {from, binary}, {to, binary},
|
2015-02-10 17:09:03 +01:00
|
|
|
{subject, binary}, {body, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"headline">>, <<"admin@localhost">>, <<"user1@localhost">>,
|
|
|
|
<<"Restart">>, <<"In 5 minutes">>],
|
2023-08-14 13:59:43 +02:00
|
|
|
args_desc = ["Message type: `normal`, `chat`, `headline`, `groupchat`", "Sender JID",
|
2017-07-14 17:17:26 +02:00
|
|
|
"Receiver JID", "Subject, or empty string", "Body"],
|
2015-02-10 17:09:03 +01:00
|
|
|
result = {res, rescode}},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
|
2021-03-16 12:58:48 +01:00
|
|
|
desc = "Send a stanza from an existing C2S session",
|
2023-08-14 13:59:43 +02:00
|
|
|
longdesc = "`user`@`host`/`resource` must be an existing C2S session."
|
2024-03-07 01:09:53 +01:00
|
|
|
" As an alternative, use _`send_stanza`_ API instead.",
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = send_stanza_c2s,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"admin">>, <<"myserver.com">>, <<"bot">>,
|
|
|
|
<<"<message to='user1@localhost'><ext attr='value'/></message>">>],
|
|
|
|
args_desc = ["Username", "Server name", "Resource", "Stanza"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
2015-12-30 12:53:40 +01:00
|
|
|
#ejabberd_commands{name = send_stanza, tags = [stanza],
|
|
|
|
desc = "Send a stanza; provide From JID and valid To JID",
|
|
|
|
module = ?MODULE, function = send_stanza,
|
|
|
|
args = [{from, binary}, {to, binary}, {stanza, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"admin@localhost">>, <<"user1@localhost">>,
|
|
|
|
<<"<message><ext attr='value'/></message>">>],
|
|
|
|
args_desc = ["Sender JID", "Destination JID", "Stanza"],
|
2015-12-30 12:53:40 +01:00
|
|
|
result = {res, rescode}},
|
2013-04-15 12:03:14 +02:00
|
|
|
#ejabberd_commands{name = privacy_set, tags = [stanza],
|
|
|
|
desc = "Send a IQ set privacy stanza for a local account",
|
|
|
|
module = ?MODULE, function = privacy_set,
|
2016-04-01 12:24:00 +02:00
|
|
|
args = [{user, binary}, {host, binary}, {xmlquery, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"user1">>, <<"myserver.com">>,
|
|
|
|
<<"<query xmlns='jabber:iq:privacy'>...">>],
|
|
|
|
args_desc = ["Username", "Server name", "Query XML element"],
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {res, rescode}},
|
|
|
|
|
2021-04-13 14:47:49 +02:00
|
|
|
#ejabberd_commands{name = stats, tags = [statistics],
|
2023-11-24 11:58:50 +01:00
|
|
|
desc = "Get some statistical value for the whole ejabberd server",
|
|
|
|
longdesc = "Allowed statistics `name` are: `registeredusers`, "
|
|
|
|
"`onlineusers`, `onlineusersnode`, `uptimeseconds`, `processes`.",
|
2016-11-30 10:31:36 +01:00
|
|
|
policy = admin,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = stats,
|
2014-06-10 22:37:16 +02:00
|
|
|
args = [{name, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"registeredusers">>],
|
|
|
|
args_desc = ["Statistic name"],
|
|
|
|
result_example = 6,
|
|
|
|
result_desc = "Integer statistic value",
|
2013-04-15 12:03:14 +02:00
|
|
|
result = {stat, integer}},
|
2021-04-13 14:47:49 +02:00
|
|
|
#ejabberd_commands{name = stats_host, tags = [statistics],
|
2023-11-24 11:58:50 +01:00
|
|
|
desc = "Get some statistical value for this host",
|
|
|
|
longdesc = "Allowed statistics `name` are: `registeredusers`, `onlineusers`.",
|
2016-11-30 10:31:36 +01:00
|
|
|
policy = admin,
|
2013-04-15 12:03:14 +02:00
|
|
|
module = ?MODULE, function = stats,
|
2014-06-10 22:37:16 +02:00
|
|
|
args = [{name, binary}, {host, binary}],
|
2017-07-14 16:43:22 +02:00
|
|
|
args_example = [<<"registeredusers">>, <<"example.com">>],
|
2017-07-14 17:17:26 +02:00
|
|
|
args_desc = ["Statistic name", "Server JID"],
|
2017-07-14 16:43:22 +02:00
|
|
|
result_example = 6,
|
|
|
|
result_desc = "Integer statistic value",
|
2016-04-01 12:24:00 +02:00
|
|
|
result = {stat, integer}}
|
2016-03-31 13:53:31 +02:00
|
|
|
].
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2016-04-01 12:24:00 +02:00
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
%%%
|
2016-12-06 12:01:04 +01:00
|
|
|
%%% Adminsys
|
2013-04-15 12:03:14 +02:00
|
|
|
%%%
|
|
|
|
|
|
|
|
compile(File) ->
|
2016-12-06 12:01:04 +01:00
|
|
|
Ebin = filename:join(code:lib_dir(ejabberd), "ebin"),
|
2016-12-07 09:27:21 +01:00
|
|
|
case ext_mod:compile_erlang_file(Ebin, File) of
|
|
|
|
{ok, Module} ->
|
|
|
|
code:purge(Module),
|
|
|
|
code:load_file(Module),
|
|
|
|
ok;
|
|
|
|
_ ->
|
|
|
|
error
|
2016-12-06 12:01:04 +01:00
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
get_cookie() ->
|
|
|
|
atom_to_list(erlang:get_cookie()).
|
|
|
|
|
2016-11-30 10:31:36 +01:00
|
|
|
restart_module(Host, Module) when is_binary(Module) ->
|
2017-04-11 12:13:58 +02:00
|
|
|
restart_module(Host, misc:binary_to_atom(Module));
|
2016-11-30 10:31:36 +01:00
|
|
|
restart_module(Host, Module) when is_atom(Module) ->
|
2018-03-13 16:18:53 +01:00
|
|
|
case gen_mod:is_loaded(Host, Module) of
|
|
|
|
false ->
|
2016-11-30 10:31:36 +01:00
|
|
|
% not a running module, force code reload anyway
|
|
|
|
code:purge(Module),
|
|
|
|
code:delete(Module),
|
|
|
|
code:load_file(Module),
|
|
|
|
1;
|
2018-03-13 16:18:53 +01:00
|
|
|
true ->
|
2016-11-30 10:31:36 +01:00
|
|
|
gen_mod:stop_module(Host, Module),
|
|
|
|
case code:soft_purge(Module) of
|
|
|
|
true ->
|
|
|
|
code:delete(Module),
|
|
|
|
code:load_file(Module),
|
2018-03-13 16:18:53 +01:00
|
|
|
gen_mod:start_module(Host, Module),
|
2016-11-30 10:31:36 +01:00
|
|
|
0;
|
|
|
|
false ->
|
2018-03-13 16:18:53 +01:00
|
|
|
gen_mod:start_module(Host, Module),
|
2016-11-30 10:31:36 +01:00
|
|
|
2
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2016-03-31 13:53:31 +02:00
|
|
|
%%%
|
|
|
|
%%% Accounts
|
|
|
|
%%%
|
|
|
|
|
2016-04-01 12:24:00 +02:00
|
|
|
set_password(User, Host, Password) ->
|
2016-04-01 13:05:41 +02:00
|
|
|
Fun = fun () -> ejabberd_auth:set_password(User, Host, Password) end,
|
|
|
|
user_action(User, Host, Fun, ok).
|
2016-03-31 13:53:31 +02:00
|
|
|
|
2016-04-10 15:37:36 +02:00
|
|
|
check_password(User, Host, Password) ->
|
|
|
|
ejabberd_auth:check_password(User, <<>>, Host, Password).
|
|
|
|
|
2024-04-19 13:47:06 +02:00
|
|
|
%% Copied some code from ejabberd_commands.erln
|
2013-04-15 12:03:14 +02:00
|
|
|
check_password_hash(User, Host, PasswordHash, HashMethod) ->
|
|
|
|
AccountPass = ejabberd_auth:get_password_s(User, Host),
|
2018-03-02 14:10:30 +01:00
|
|
|
Methods = lists:map(fun(A) -> atom_to_binary(A, latin1) end,
|
|
|
|
proplists:get_value(hashs, crypto:supports())),
|
|
|
|
MethodAllowed = lists:member(HashMethod, Methods),
|
|
|
|
AccountPassHash = case {AccountPass, MethodAllowed} of
|
2015-06-25 13:39:45 +02:00
|
|
|
{A, _} when is_tuple(A) -> scrammed;
|
2018-03-02 14:10:30 +01:00
|
|
|
{_, true} -> get_hash(AccountPass, HashMethod);
|
|
|
|
{_, false} ->
|
2019-06-24 19:32:34 +02:00
|
|
|
?ERROR_MSG("Check_password_hash called "
|
2018-03-02 14:10:30 +01:00
|
|
|
"with hash method: ~p", [HashMethod]),
|
2016-11-30 10:31:36 +01:00
|
|
|
undefined
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
|
|
|
case AccountPassHash of
|
2015-06-25 13:39:45 +02:00
|
|
|
scrammed ->
|
2016-04-01 13:05:41 +02:00
|
|
|
?ERROR_MSG("Passwords are scrammed, and check_password_hash cannot work.", []),
|
2015-06-25 13:39:45 +02:00
|
|
|
throw(passwords_scrammed_command_cannot_work);
|
2016-04-01 13:05:41 +02:00
|
|
|
undefined -> throw(unkown_hash_method);
|
2013-04-15 12:03:14 +02:00
|
|
|
PasswordHash -> ok;
|
2016-04-01 13:05:41 +02:00
|
|
|
_ -> false
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
2018-03-02 14:10:30 +01:00
|
|
|
|
|
|
|
get_hash(AccountPass, Method) ->
|
2016-04-01 13:05:41 +02:00
|
|
|
iolist_to_binary([io_lib:format("~2.16.0B", [X])
|
2018-03-02 14:10:30 +01:00
|
|
|
|| X <- binary_to_list(
|
|
|
|
crypto:hash(binary_to_atom(Method, latin1), AccountPass))]).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
delete_old_users(Days) ->
|
|
|
|
%% Get the list of registered users
|
2017-05-11 13:37:21 +02:00
|
|
|
Users = ejabberd_auth:get_users(),
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
{removed, N, UR} = delete_old_users(Days, Users),
|
|
|
|
{ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
|
|
|
|
|
|
|
|
delete_old_users_vhost(Host, Days) ->
|
|
|
|
%% Get the list of registered users
|
2017-05-11 13:37:21 +02:00
|
|
|
Users = ejabberd_auth:get_users(Host),
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
{removed, N, UR} = delete_old_users(Days, Users),
|
|
|
|
{ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
|
|
|
|
|
|
|
|
delete_old_users(Days, Users) ->
|
|
|
|
SecOlder = Days*24*60*60,
|
2019-02-27 09:56:20 +01:00
|
|
|
TimeStamp_now = erlang:system_time(second),
|
2017-02-23 19:38:17 +01:00
|
|
|
TimeStamp_oldest = TimeStamp_now - SecOlder,
|
2013-04-15 12:03:14 +02:00
|
|
|
F = fun({LUser, LServer}) ->
|
2017-02-23 19:38:17 +01:00
|
|
|
case catch delete_or_not(LUser, LServer, TimeStamp_oldest) of
|
|
|
|
true ->
|
|
|
|
ejabberd_auth:remove_user(LUser, LServer),
|
|
|
|
true;
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
|
|
|
Users_removed = lists:filter(F, Users),
|
|
|
|
{removed, length(Users_removed), Users_removed}.
|
|
|
|
|
2017-02-23 19:38:17 +01:00
|
|
|
delete_or_not(LUser, LServer, TimeStamp_oldest) ->
|
2017-06-09 19:18:47 +02:00
|
|
|
deny = acl:match_rule(LServer, protect_old_users, jid:make(LUser, LServer)),
|
2017-02-23 19:38:17 +01:00
|
|
|
[] = ejabberd_sm:get_user_resources(LUser, LServer),
|
|
|
|
case mod_last:get_last_info(LUser, LServer) of
|
|
|
|
{ok, TimeStamp, _Status} ->
|
|
|
|
if TimeStamp_oldest < TimeStamp ->
|
|
|
|
false;
|
|
|
|
true ->
|
|
|
|
true
|
|
|
|
end;
|
|
|
|
not_found ->
|
|
|
|
true
|
|
|
|
end.
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
%%
|
2024-04-19 13:47:06 +02:00
|
|
|
%% Ban account v0
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
ban_account(User, Host, ReasonText) ->
|
|
|
|
Reason = prepare_reason(ReasonText),
|
|
|
|
kick_sessions(User, Host, Reason),
|
|
|
|
set_random_password(User, Host, Reason),
|
|
|
|
ok.
|
|
|
|
|
|
|
|
kick_sessions(User, Server, Reason) ->
|
|
|
|
lists:map(
|
|
|
|
fun(Resource) ->
|
|
|
|
kick_this_session(User, Server, Resource, Reason)
|
|
|
|
end,
|
2016-03-21 18:30:05 +01:00
|
|
|
ejabberd_sm:get_user_resources(User, Server)).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
set_random_password(User, Server, Reason) ->
|
|
|
|
NewPass = build_random_password(Reason),
|
|
|
|
set_password_auth(User, Server, NewPass).
|
|
|
|
|
|
|
|
build_random_password(Reason) ->
|
2016-11-13 08:44:53 +01:00
|
|
|
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(),
|
|
|
|
Date = str:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B",
|
|
|
|
[Year, Month, Day, Hour, Minute, Second]),
|
2018-07-05 10:51:49 +02:00
|
|
|
RandomString = p1_rand:get_string(),
|
2015-03-26 20:41:16 +01:00
|
|
|
<<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
set_password_auth(User, Server, Password) ->
|
|
|
|
ok = ejabberd_auth:set_password(User, Server, Password).
|
|
|
|
|
|
|
|
prepare_reason([]) ->
|
2014-05-07 18:44:48 +02:00
|
|
|
<<"Kicked by administrator">>;
|
2013-04-15 12:03:14 +02:00
|
|
|
prepare_reason([Reason]) ->
|
|
|
|
Reason;
|
2014-05-07 18:44:48 +02:00
|
|
|
prepare_reason(Reason) when is_binary(Reason) ->
|
|
|
|
Reason.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2024-04-19 13:47:06 +02:00
|
|
|
%%
|
|
|
|
%% Ban account v2
|
|
|
|
|
|
|
|
ban_account_v2(User, Host, ReasonText) ->
|
2024-04-29 17:17:06 +02:00
|
|
|
case gen_mod:is_loaded(Host, mod_private) of
|
2024-04-19 13:47:06 +02:00
|
|
|
false ->
|
2024-04-29 17:17:06 +02:00
|
|
|
mod_private_is_required_but_disabled;
|
|
|
|
true ->
|
|
|
|
case is_banned(User, Host) of
|
|
|
|
true ->
|
|
|
|
account_was_already_banned;
|
|
|
|
false ->
|
|
|
|
ban_account_v2_b(User, Host, ReasonText)
|
|
|
|
end
|
2024-04-19 13:47:06 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
ban_account_v2_b(User, Host, ReasonText) ->
|
|
|
|
Reason = prepare_reason(ReasonText),
|
|
|
|
Pass = ejabberd_auth:get_password_s(User, Host),
|
|
|
|
Last = get_last(User, Host),
|
|
|
|
BanDate = xmpp_util:encode_timestamp(erlang:timestamp()),
|
2024-04-24 19:54:45 +02:00
|
|
|
Hash = get_hash_value(User, Host),
|
|
|
|
BanPrivateXml = build_ban_xmlel(Reason, Pass, Last, BanDate, Hash),
|
2024-04-19 13:47:06 +02:00
|
|
|
ok = private_set2(User, Host, BanPrivateXml),
|
|
|
|
ok = set_random_password_v2(User, Host),
|
|
|
|
kick_sessions(User, Host, Reason),
|
|
|
|
ok.
|
|
|
|
|
2024-04-24 19:54:45 +02:00
|
|
|
get_hash_value(User, Host) ->
|
|
|
|
Cookie = misc:atom_to_binary(erlang:get_cookie()),
|
|
|
|
misc:term_to_base64(crypto:hash(sha256, <<User/binary, Host/binary, Cookie/binary>>)).
|
|
|
|
|
2024-04-19 13:47:06 +02:00
|
|
|
set_random_password_v2(User, Server) ->
|
|
|
|
NewPass = p1_rand:get_string(),
|
|
|
|
ok = ejabberd_auth:set_password(User, Server, NewPass).
|
|
|
|
|
2024-04-24 19:54:45 +02:00
|
|
|
build_ban_xmlel(Reason, Pass, {LastDate, LastReason}, BanDate, Hash) ->
|
2024-04-19 13:47:06 +02:00
|
|
|
PassEls = build_pass_els(Pass),
|
|
|
|
#xmlel{name = <<"banned">>,
|
2024-04-24 20:17:02 +02:00
|
|
|
attrs = [{<<"xmlns">>, <<"jabber:ejabberd:banned">>}],
|
2024-04-19 13:47:06 +02:00
|
|
|
children = [#xmlel{name = <<"reason">>, attrs = [], children = [{xmlcdata, Reason}]},
|
|
|
|
#xmlel{name = <<"password">>, attrs = [], children = PassEls},
|
|
|
|
#xmlel{name = <<"lastdate">>, attrs = [], children = [{xmlcdata, LastDate}]},
|
|
|
|
#xmlel{name = <<"lastreason">>, attrs = [], children = [{xmlcdata, LastReason}]},
|
2024-04-24 19:54:45 +02:00
|
|
|
#xmlel{name = <<"bandate">>, attrs = [], children = [{xmlcdata, BanDate}]},
|
|
|
|
#xmlel{name = <<"hash">>, attrs = [], children = [{xmlcdata, Hash}]}
|
2024-04-19 13:47:06 +02:00
|
|
|
]}.
|
|
|
|
|
|
|
|
build_pass_els(Pass) when is_binary(Pass) ->
|
|
|
|
[{xmlcdata, Pass}];
|
|
|
|
build_pass_els(#scram{storedkey = StoredKey,
|
|
|
|
serverkey = ServerKey,
|
|
|
|
salt = Salt,
|
|
|
|
hash = Hash,
|
|
|
|
iterationcount = IterationCount}) ->
|
|
|
|
[#xmlel{name = <<"storedkey">>, attrs = [], children = [{xmlcdata, StoredKey}]},
|
|
|
|
#xmlel{name = <<"serverkey">>, attrs = [], children = [{xmlcdata, ServerKey}]},
|
|
|
|
#xmlel{name = <<"salt">>, attrs = [], children = [{xmlcdata, Salt}]},
|
|
|
|
#xmlel{name = <<"hash">>, attrs = [], children = [{xmlcdata, misc:atom_to_binary(Hash)}]},
|
|
|
|
#xmlel{name = <<"iterationcount">>, attrs = [], children = [{xmlcdata, integer_to_binary(IterationCount)}]}
|
|
|
|
].
|
|
|
|
|
|
|
|
%%
|
|
|
|
%% Get ban details
|
|
|
|
|
|
|
|
get_ban_details(User, Host) ->
|
2024-04-25 00:22:45 +02:00
|
|
|
case private_get2(User, Host, <<"banned">>, <<"jabber:ejabberd:banned">>) of
|
|
|
|
[El] ->
|
|
|
|
get_ban_details(User, Host, El);
|
|
|
|
[] ->
|
|
|
|
[]
|
|
|
|
end.
|
|
|
|
|
|
|
|
get_ban_details(User, Host, El) ->
|
2024-04-19 13:47:06 +02:00
|
|
|
Reason = fxml:get_subtag_cdata(El, <<"reason">>),
|
|
|
|
LastDate = fxml:get_subtag_cdata(El, <<"lastdate">>),
|
|
|
|
LastReason = fxml:get_subtag_cdata(El, <<"lastreason">>),
|
|
|
|
BanDate = fxml:get_subtag_cdata(El, <<"bandate">>),
|
2024-04-24 19:54:45 +02:00
|
|
|
Hash = fxml:get_subtag_cdata(El, <<"hash">>),
|
|
|
|
case Hash == get_hash_value(User, Host) of
|
|
|
|
true ->
|
|
|
|
[{"reason", Reason},
|
|
|
|
{"bandate", BanDate},
|
|
|
|
{"lastdate", LastDate},
|
|
|
|
{"lastreason", LastReason}];
|
|
|
|
false ->
|
|
|
|
[]
|
|
|
|
end.
|
2024-04-19 13:47:06 +02:00
|
|
|
|
|
|
|
is_banned(User, Host) ->
|
|
|
|
case lists:keyfind("bandate", 1, get_ban_details(User, Host)) of
|
|
|
|
{_, BanDate} when BanDate /= <<>> ->
|
|
|
|
true;
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end.
|
|
|
|
|
|
|
|
%%
|
|
|
|
%% Unban account
|
|
|
|
|
|
|
|
unban_account(User, Host) ->
|
2024-04-29 17:17:06 +02:00
|
|
|
case gen_mod:is_loaded(Host, mod_private) of
|
2024-04-19 13:47:06 +02:00
|
|
|
false ->
|
2024-04-29 17:17:06 +02:00
|
|
|
mod_private_is_required_but_disabled;
|
2024-04-19 13:47:06 +02:00
|
|
|
true ->
|
2024-04-29 17:17:06 +02:00
|
|
|
case is_banned(User, Host) of
|
|
|
|
false ->
|
|
|
|
account_was_not_banned;
|
|
|
|
true ->
|
|
|
|
unban_account2(User, Host)
|
|
|
|
end
|
2024-04-19 13:47:06 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
unban_account2(User, Host) ->
|
|
|
|
OldPass = get_oldpass(User, Host),
|
|
|
|
ok = ejabberd_auth:set_password(User, Host, OldPass),
|
|
|
|
UnBanPrivateXml = build_unban_xmlel(),
|
|
|
|
private_set2(User, Host, UnBanPrivateXml).
|
|
|
|
|
|
|
|
get_oldpass(User, Host) ->
|
2024-04-24 20:17:02 +02:00
|
|
|
[El] = private_get2(User, Host, <<"banned">>, <<"jabber:ejabberd:banned">>),
|
2024-04-19 13:47:06 +02:00
|
|
|
Pass = fxml:get_subtag(El, <<"password">>),
|
|
|
|
get_pass(Pass).
|
|
|
|
|
|
|
|
get_pass(#xmlel{children = [{xmlcdata, Pass}]}) ->
|
|
|
|
Pass;
|
|
|
|
get_pass(#xmlel{children = ScramEls} = Pass) when is_list(ScramEls) ->
|
|
|
|
StoredKey = fxml:get_subtag_cdata(Pass, <<"storedkey">>),
|
|
|
|
ServerKey = fxml:get_subtag_cdata(Pass, <<"serverkey">>),
|
|
|
|
Salt = fxml:get_subtag_cdata(Pass, <<"salt">>),
|
|
|
|
Hash = fxml:get_subtag_cdata(Pass, <<"hash">>),
|
|
|
|
IterationCount = fxml:get_subtag_cdata(Pass, <<"iterationcount">>),
|
|
|
|
#scram{storedkey = StoredKey,
|
|
|
|
serverkey = ServerKey,
|
|
|
|
salt = Salt,
|
|
|
|
hash = binary_to_existing_atom(Hash, latin1),
|
|
|
|
iterationcount = binary_to_integer(IterationCount)}.
|
|
|
|
|
|
|
|
build_unban_xmlel() ->
|
2024-04-24 20:17:02 +02:00
|
|
|
#xmlel{name = <<"banned">>, attrs = [{<<"xmlns">>, <<"jabber:ejabberd:banned">>}]}.
|
2024-04-19 13:47:06 +02:00
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
%%%
|
|
|
|
%%% Sessions
|
|
|
|
%%%
|
|
|
|
|
|
|
|
num_resources(User, Host) ->
|
|
|
|
length(ejabberd_sm:get_user_resources(User, Host)).
|
|
|
|
|
|
|
|
resource_num(User, Host, Num) ->
|
|
|
|
Resources = ejabberd_sm:get_user_resources(User, Host),
|
|
|
|
case (0<Num) and (Num=<length(Resources)) of
|
|
|
|
true ->
|
|
|
|
lists:nth(Num, Resources);
|
|
|
|
false ->
|
2016-04-01 13:05:41 +02:00
|
|
|
throw({bad_argument,
|
|
|
|
lists:flatten(io_lib:format("Wrong resource number: ~p", [Num]))})
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
kick_session(User, Server, Resource, ReasonText) ->
|
|
|
|
kick_this_session(User, Server, Resource, prepare_reason(ReasonText)),
|
|
|
|
ok.
|
|
|
|
|
|
|
|
kick_this_session(User, Server, Resource, Reason) ->
|
2016-12-11 13:03:37 +01:00
|
|
|
ejabberd_sm:route(jid:make(User, Server, Resource),
|
|
|
|
{exit, Reason}).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
status_num(Host, Status) ->
|
|
|
|
length(get_status_list(Host, Status)).
|
|
|
|
status_num(Status) ->
|
2014-04-02 04:17:08 +02:00
|
|
|
status_num(<<"all">>, Status).
|
2013-04-15 12:03:14 +02:00
|
|
|
status_list(Host, Status) ->
|
|
|
|
Res = get_status_list(Host, Status),
|
2018-07-13 09:50:38 +02:00
|
|
|
[{U, S, R, num_prio(P), St} || {U, S, R, P, St} <- Res].
|
2013-04-15 12:03:14 +02:00
|
|
|
status_list(Status) ->
|
2014-04-02 04:17:08 +02:00
|
|
|
status_list(<<"all">>, Status).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
get_status_list(Host, Status_required) ->
|
|
|
|
%% Get list of all logged users
|
|
|
|
Sessions = ejabberd_sm:dirty_get_my_sessions_list(),
|
|
|
|
%% Reformat the list
|
|
|
|
Sessions2 = [ {Session#session.usr, Session#session.sid, Session#session.priority} || Session <- Sessions],
|
|
|
|
Fhost = case Host of
|
2014-04-02 04:17:08 +02:00
|
|
|
<<"all">> ->
|
2018-01-27 17:35:38 +01:00
|
|
|
%% All hosts are requested, so don't filter at all
|
2013-04-15 12:03:14 +02:00
|
|
|
fun(_, _) -> true end;
|
|
|
|
_ ->
|
|
|
|
%% Filter the list, only Host is interesting
|
|
|
|
fun(A, B) -> A == B end
|
|
|
|
end,
|
|
|
|
Sessions3 = [ {Pid, Server, Priority} || {{_User, Server, _Resource}, {_, Pid}, Priority} <- Sessions2, apply(Fhost, [Server, Host])],
|
|
|
|
%% For each Pid, get its presence
|
2016-12-11 13:03:37 +01:00
|
|
|
Sessions4 = [ {catch get_presence(Pid), Server, Priority} || {Pid, Server, Priority} <- Sessions3],
|
2013-04-15 12:03:14 +02:00
|
|
|
%% Filter by status
|
|
|
|
Fstatus = case Status_required of
|
2014-04-02 04:17:08 +02:00
|
|
|
<<"all">> ->
|
2013-04-15 12:03:14 +02:00
|
|
|
fun(_, _) -> true end;
|
|
|
|
_ ->
|
|
|
|
fun(A, B) -> A == B end
|
|
|
|
end,
|
2018-07-13 09:50:38 +02:00
|
|
|
[{User, Server, Resource, num_prio(Priority), stringize(Status_text)}
|
2013-04-15 12:03:14 +02:00
|
|
|
|| {{User, Resource, Status, Status_text}, Server, Priority} <- Sessions4,
|
|
|
|
apply(Fstatus, [Status, Status_required])].
|
|
|
|
|
|
|
|
connected_users_info() ->
|
2019-01-15 10:04:15 +01:00
|
|
|
lists:filtermap(
|
2018-06-15 11:28:44 +02:00
|
|
|
fun({U, S, R}) ->
|
2019-01-15 10:04:15 +01:00
|
|
|
case user_session_info(U, S, R) of
|
|
|
|
offline ->
|
|
|
|
false;
|
|
|
|
Info ->
|
|
|
|
Jid = jid:encode(jid:make(U, S, R)),
|
|
|
|
{true, erlang:insert_element(1, Info, Jid)}
|
|
|
|
end
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
2018-06-15 11:28:44 +02:00
|
|
|
ejabberd_sm:dirty_get_sessions_list()).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
connected_users_vhost(Host) ->
|
|
|
|
USRs = ejabberd_sm:get_vh_session_list(Host),
|
2017-07-14 16:43:22 +02:00
|
|
|
[ jid:encode(jid:make(USR)) || USR <- USRs].
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%% Make string more print-friendly
|
|
|
|
stringize(String) ->
|
|
|
|
%% Replace newline characters with other code
|
2014-04-02 04:17:08 +02:00
|
|
|
ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2016-12-11 13:03:37 +01:00
|
|
|
get_presence(Pid) ->
|
2018-08-15 12:16:01 +02:00
|
|
|
try get_presence2(Pid) of
|
|
|
|
{_, _, _, _} = Res ->
|
|
|
|
Res
|
|
|
|
catch
|
|
|
|
_:_ -> {<<"">>, <<"">>, <<"offline">>, <<"">>}
|
|
|
|
end.
|
|
|
|
get_presence2(Pid) ->
|
2016-12-11 13:03:37 +01:00
|
|
|
Pres = #presence{from = From} = ejabberd_c2s:get_presence(Pid),
|
|
|
|
Show = case Pres of
|
|
|
|
#presence{type = unavailable} -> <<"unavailable">>;
|
|
|
|
#presence{show = undefined} -> <<"available">>;
|
|
|
|
#presence{show = S} -> atom_to_binary(S, utf8)
|
|
|
|
end,
|
|
|
|
Status = xmpp:get_text(Pres#presence.status),
|
|
|
|
{From#jid.user, From#jid.resource, Show, Status}.
|
|
|
|
|
2016-11-30 10:31:36 +01:00
|
|
|
get_presence(U, S) ->
|
|
|
|
Pids = [ejabberd_sm:get_session_pid(U, S, R)
|
|
|
|
|| R <- ejabberd_sm:get_user_resources(U, S)],
|
|
|
|
OnlinePids = [Pid || Pid <- Pids, Pid=/=none],
|
|
|
|
case OnlinePids of
|
|
|
|
[] ->
|
2017-02-26 08:07:12 +01:00
|
|
|
{jid:encode({U, S, <<>>}), <<"unavailable">>, <<"">>};
|
2016-11-30 10:31:36 +01:00
|
|
|
[SessionPid|_] ->
|
2016-12-11 13:03:37 +01:00
|
|
|
{_User, Resource, Show, Status} = get_presence(SessionPid),
|
2017-02-26 08:07:12 +01:00
|
|
|
FullJID = jid:encode({U, S, Resource}),
|
2016-11-30 10:31:36 +01:00
|
|
|
{FullJID, Show, Status}
|
|
|
|
end.
|
|
|
|
|
2023-11-24 17:07:21 +01:00
|
|
|
set_presence(User, Host, Resource, Type, Show, Status, Priority) when is_binary(Priority) ->
|
|
|
|
set_presence(User, Host, Resource, Type, Show, Status, binary_to_integer(Priority));
|
|
|
|
|
|
|
|
set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
|
2017-07-07 10:55:08 +02:00
|
|
|
Pres = #presence{
|
|
|
|
from = jid:make(User, Host, Resource),
|
|
|
|
to = jid:make(User, Host),
|
|
|
|
type = misc:binary_to_atom(Type),
|
|
|
|
status = xmpp:mk_text(Status),
|
|
|
|
show = misc:binary_to_atom(Show),
|
|
|
|
priority = Priority,
|
|
|
|
sub_els = []},
|
2024-09-02 12:01:56 +02:00
|
|
|
case ejabberd_sm:get_session_pid(User, Host, Resource) of
|
|
|
|
none -> throw({error, "User session not found"});
|
|
|
|
Ref -> ejabberd_c2s:set_presence(Ref, Pres)
|
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
user_sessions_info(User, Host) ->
|
2019-01-15 10:04:15 +01:00
|
|
|
lists:filtermap(fun(Resource) ->
|
|
|
|
case user_session_info(User, Host, Resource) of
|
|
|
|
offline -> false;
|
|
|
|
Info -> {true, Info}
|
|
|
|
end
|
|
|
|
end, ejabberd_sm:get_user_resources(User, Host)).
|
2018-06-15 11:28:44 +02:00
|
|
|
|
|
|
|
user_session_info(User, Host, Resource) ->
|
2013-04-15 12:03:14 +02:00
|
|
|
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
2019-01-15 10:04:15 +01:00
|
|
|
case ejabberd_sm:get_user_info(User, Host, Resource) of
|
|
|
|
offline ->
|
|
|
|
offline;
|
|
|
|
Info ->
|
|
|
|
Now = proplists:get_value(ts, Info),
|
|
|
|
Pid = proplists:get_value(pid, Info),
|
|
|
|
{_U, _Resource, Status, StatusText} = get_presence(Pid),
|
|
|
|
Priority = proplists:get_value(priority, Info),
|
|
|
|
Conn = proplists:get_value(conn, Info),
|
|
|
|
{Ip, Port} = proplists:get_value(ip, Info),
|
|
|
|
IPS = inet_parse:ntoa(Ip),
|
|
|
|
NodeS = atom_to_list(node(Pid)),
|
|
|
|
Uptime = CurrentSec - calendar:datetime_to_gregorian_seconds(
|
|
|
|
calendar:now_to_local_time(Now)),
|
|
|
|
{atom_to_list(Conn), IPS, Port, num_prio(Priority), NodeS, Uptime, Status, Resource, StatusText}
|
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Vcard
|
|
|
|
%%%
|
|
|
|
|
2016-04-01 12:24:00 +02:00
|
|
|
set_nickname(User, Host, Nickname) ->
|
2016-07-29 12:21:00 +02:00
|
|
|
VCard = xmpp:encode(#vcard_temp{nickname = Nickname}),
|
|
|
|
case mod_vcard:set_vcard(User, jid:nameprep(Host), VCard) of
|
|
|
|
{error, badarg} ->
|
|
|
|
error;
|
|
|
|
ok ->
|
|
|
|
ok
|
2016-04-01 12:24:00 +02:00
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
get_vcard(User, Host, Name) ->
|
|
|
|
[Res | _] = get_vcard_content(User, Host, [Name]),
|
|
|
|
Res.
|
|
|
|
|
|
|
|
get_vcard(User, Host, Name, Subname) ->
|
|
|
|
[Res | _] = get_vcard_content(User, Host, [Name, Subname]),
|
|
|
|
Res.
|
|
|
|
|
|
|
|
get_vcard_multi(User, Host, Name, Subname) ->
|
|
|
|
get_vcard_content(User, Host, [Name, Subname]).
|
|
|
|
|
|
|
|
set_vcard(User, Host, Name, SomeContent) ->
|
|
|
|
set_vcard_content(User, Host, [Name], SomeContent).
|
|
|
|
|
|
|
|
set_vcard(User, Host, Name, Subname, SomeContent) ->
|
|
|
|
set_vcard_content(User, Host, [Name, Subname], SomeContent).
|
|
|
|
|
2022-04-12 13:23:36 +02:00
|
|
|
%%
|
|
|
|
%% Room vcard
|
|
|
|
|
|
|
|
is_muc_service(Domain) ->
|
|
|
|
try mod_muc_admin:get_room_serverhost(Domain) of
|
|
|
|
Domain -> false;
|
|
|
|
Service when is_binary(Service) -> true
|
|
|
|
catch _:{unregistered_route, _} ->
|
|
|
|
throw(error_wrong_hostname)
|
|
|
|
end.
|
|
|
|
|
|
|
|
get_room_vcard(Name, Service) ->
|
|
|
|
case mod_muc_admin:get_room_options(Name, Service) of
|
|
|
|
[] ->
|
|
|
|
throw(error_no_vcard_found);
|
|
|
|
Opts ->
|
|
|
|
case lists:keyfind(<<"vcard">>, 1, Opts) of
|
|
|
|
false ->
|
|
|
|
throw(error_no_vcard_found);
|
|
|
|
{_, VCardRaw} ->
|
|
|
|
[fxml_stream:parse_element(VCardRaw)]
|
|
|
|
end
|
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%
|
|
|
|
%% Internal vcard
|
|
|
|
|
|
|
|
get_vcard_content(User, Server, Data) ->
|
2022-04-12 13:23:36 +02:00
|
|
|
case get_vcard_element(User, Server) of
|
2017-01-06 16:13:57 +01:00
|
|
|
[El|_] ->
|
|
|
|
case get_vcard(Data, El) of
|
2015-03-23 13:27:46 +01:00
|
|
|
[false] -> throw(error_no_value_found_in_vcard);
|
2016-02-03 19:03:17 +01:00
|
|
|
ElemList -> ?DEBUG("ELS ~p", [ElemList]), [fxml:get_tag_cdata(Elem) || Elem <- ElemList]
|
2013-04-15 12:03:14 +02:00
|
|
|
end;
|
|
|
|
[] ->
|
2016-07-29 12:21:00 +02:00
|
|
|
throw(error_no_vcard_found);
|
|
|
|
error ->
|
|
|
|
throw(database_failure)
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
2022-04-12 13:23:36 +02:00
|
|
|
get_vcard_element(User, Server) ->
|
|
|
|
case is_muc_service(Server) of
|
|
|
|
true ->
|
|
|
|
get_room_vcard(User, Server);
|
|
|
|
false ->
|
|
|
|
mod_vcard:get_vcard(jid:nodeprep(User), jid:nameprep(Server))
|
|
|
|
end.
|
|
|
|
|
2015-02-17 16:21:57 +01:00
|
|
|
get_vcard([<<"TEL">>, TelType], {_, _, _, OldEls}) ->
|
|
|
|
{TakenEl, _NewEls} = take_vcard_tel(TelType, OldEls, [], not_found),
|
|
|
|
[TakenEl];
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
get_vcard([Data1, Data2], A1) ->
|
|
|
|
case get_subtag(A1, Data1) of
|
2015-04-01 16:32:34 +02:00
|
|
|
[false] -> [false];
|
2015-02-17 16:21:57 +01:00
|
|
|
A2List ->
|
|
|
|
lists:flatten([get_vcard([Data2], A2) || A2 <- A2List])
|
2013-04-15 12:03:14 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
get_vcard([Data], A1) ->
|
|
|
|
get_subtag(A1, Data).
|
|
|
|
|
|
|
|
get_subtag(Xmlelement, Name) ->
|
2016-02-03 19:03:17 +01:00
|
|
|
[fxml:get_subtag(Xmlelement, Name)].
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
set_vcard_content(User, Server, Data, SomeContent) ->
|
|
|
|
ContentList = case SomeContent of
|
2014-03-24 19:01:43 +01:00
|
|
|
[Bin | _] when is_binary(Bin) -> SomeContent;
|
|
|
|
Bin when is_binary(Bin) -> [SomeContent]
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
|
|
|
%% Get old vcard
|
2016-07-29 12:21:00 +02:00
|
|
|
A4 = case mod_vcard:get_vcard(jid:nodeprep(User), jid:nameprep(Server)) of
|
2013-04-15 12:03:14 +02:00
|
|
|
[A1] ->
|
|
|
|
{_, _, _, A2} = A1,
|
|
|
|
update_vcard_els(Data, ContentList, A2);
|
|
|
|
[] ->
|
2016-07-29 12:21:00 +02:00
|
|
|
update_vcard_els(Data, ContentList, []);
|
|
|
|
error ->
|
|
|
|
throw(database_failure)
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
|
|
|
%% Build new vcard
|
2014-03-24 19:01:43 +01:00
|
|
|
SubEl = {xmlel, <<"vCard">>, [{<<"xmlns">>,<<"vcard-temp">>}], A4},
|
2018-10-30 23:07:30 +01:00
|
|
|
mod_vcard:set_vcard(User, jid:nameprep(Server), SubEl).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2015-02-17 16:21:57 +01:00
|
|
|
take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) ->
|
|
|
|
{Taken2, NewEls2} = case lists:keymember(TelType, 2, SubEls) of
|
2016-02-03 19:03:17 +01:00
|
|
|
true -> {fxml:get_subtag(OldEl, <<"NUMBER">>), NewEls};
|
2015-02-17 16:21:57 +01:00
|
|
|
false -> {Taken, [OldEl | NewEls]}
|
|
|
|
end,
|
|
|
|
take_vcard_tel(TelType, OldEls, NewEls2, Taken2);
|
|
|
|
take_vcard_tel(TelType, [OldEl | OldEls], NewEls, Taken) ->
|
|
|
|
take_vcard_tel(TelType, OldEls, [OldEl | NewEls], Taken);
|
|
|
|
take_vcard_tel(_TelType, [], NewEls, Taken) ->
|
|
|
|
{Taken, NewEls}.
|
|
|
|
|
|
|
|
update_vcard_els([<<"TEL">>, TelType], [TelValue], OldEls) ->
|
|
|
|
{_, NewEls} = take_vcard_tel(TelType, OldEls, [], not_found),
|
|
|
|
NewEl = {xmlel,<<"TEL">>,[],
|
2016-04-01 12:24:00 +02:00
|
|
|
[{xmlel,TelType,[],[]},
|
|
|
|
{xmlel,<<"NUMBER">>,[],[{xmlcdata,TelValue}]}]},
|
2015-02-17 16:21:57 +01:00
|
|
|
[NewEl | NewEls];
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
update_vcard_els(Data, ContentList, Els1) ->
|
|
|
|
Els2 = lists:keysort(2, Els1),
|
|
|
|
[Data1 | Data2] = Data,
|
|
|
|
NewEls = case Data2 of
|
|
|
|
[] ->
|
2014-03-24 19:01:43 +01:00
|
|
|
[{xmlel, Data1, [], [{xmlcdata,Content}]} || Content <- ContentList];
|
2013-04-15 12:03:14 +02:00
|
|
|
[D2] ->
|
|
|
|
OldEl = case lists:keysearch(Data1, 2, Els2) of
|
|
|
|
{value, A} -> A;
|
2014-04-02 04:17:08 +02:00
|
|
|
false -> {xmlel, Data1, [], []}
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
2014-03-24 19:01:43 +01:00
|
|
|
{xmlel, _, _, ContentOld1} = OldEl,
|
|
|
|
Content2 = [{xmlel, D2, [], [{xmlcdata,Content}]} || Content <- ContentList],
|
2013-04-15 12:03:14 +02:00
|
|
|
ContentOld2 = [A || {_, X, _, _} = A <- ContentOld1, X/=D2],
|
|
|
|
ContentOld3 = lists:keysort(2, ContentOld2),
|
|
|
|
ContentNew = lists:keymerge(2, Content2, ContentOld3),
|
2014-03-24 19:01:43 +01:00
|
|
|
[{xmlel, Data1, [], ContentNew}]
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
|
|
|
Els3 = lists:keydelete(Data1, 2, Els2),
|
|
|
|
lists:keymerge(2, NewEls, Els3).
|
|
|
|
|
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Roster
|
|
|
|
%%%
|
|
|
|
|
2023-11-29 19:04:57 +01:00
|
|
|
add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs) when is_binary(Group) ->
|
|
|
|
add_rosteritem(LocalUser, LocalServer, User, Server, Nick, [Group], Subs);
|
|
|
|
add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Groups, Subs) ->
|
2021-02-09 13:45:50 +01:00
|
|
|
case {jid:make(LocalUser, LocalServer), jid:make(User, Server)} of
|
|
|
|
{error, _} ->
|
|
|
|
throw({error, "Invalid 'localuser'/'localserver'"});
|
|
|
|
{_, error} ->
|
|
|
|
throw({error, "Invalid 'user'/'server'"});
|
|
|
|
{Jid, _Jid2} ->
|
2023-11-29 19:04:57 +01:00
|
|
|
RosterItem = build_roster_item(User, Server, {add, Nick, Subs, Groups}),
|
2021-02-09 13:45:50 +01:00
|
|
|
case mod_roster:set_item_and_notify_clients(Jid, RosterItem, true) of
|
|
|
|
ok -> ok;
|
|
|
|
_ -> error
|
|
|
|
end
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
subscribe(LU, LS, User, Server, Nick, Group, Subscription, _Xattrs) ->
|
2021-02-09 13:45:50 +01:00
|
|
|
case {jid:make(LU, LS), jid:make(User, Server)} of
|
|
|
|
{error, _} ->
|
|
|
|
throw({error, "Invalid 'localuser'/'localserver'"});
|
|
|
|
{_, error} ->
|
|
|
|
throw({error, "Invalid 'user'/'server'"});
|
|
|
|
{_Jid, _Jid2} ->
|
|
|
|
ItemEl = build_roster_item(User, Server, {add, Nick, Subscription, Group}),
|
|
|
|
mod_roster:set_items(LU, LS, #roster_query{items = [ItemEl]})
|
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
delete_rosteritem(LocalUser, LocalServer, User, Server) ->
|
2021-02-09 13:45:50 +01:00
|
|
|
case {jid:make(LocalUser, LocalServer), jid:make(User, Server)} of
|
|
|
|
{error, _} ->
|
|
|
|
throw({error, "Invalid 'localuser'/'localserver'"});
|
|
|
|
{_, error} ->
|
|
|
|
throw({error, "Invalid 'user'/'server'"});
|
|
|
|
{Jid, _Jid2} ->
|
|
|
|
RosterItem = build_roster_item(User, Server, remove),
|
|
|
|
case mod_roster:set_item_and_notify_clients(Jid, RosterItem, true) of
|
|
|
|
ok -> ok;
|
|
|
|
_ -> error
|
|
|
|
end
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
%% -----------------------------
|
|
|
|
%% Get Roster
|
|
|
|
%% -----------------------------
|
|
|
|
|
|
|
|
get_roster(User, Server) ->
|
2021-02-09 13:45:50 +01:00
|
|
|
case jid:make(User, Server) of
|
|
|
|
error ->
|
|
|
|
throw({error, "Invalid 'user'/'server'"});
|
|
|
|
#jid{luser = U, lserver = S} ->
|
|
|
|
Items = ejabberd_hooks:run_fold(roster_get, S, [], [{U, S}]),
|
|
|
|
make_roster_xmlrpc(Items)
|
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
make_roster_xmlrpc(Roster) ->
|
2023-09-21 12:45:27 +02:00
|
|
|
lists:map(
|
2023-09-26 16:50:55 +02:00
|
|
|
fun(#roster_item{jid = JID, name = Nick, subscription = Sub, ask = Ask, groups = Groups}) ->
|
2022-07-16 23:23:48 +02:00
|
|
|
JIDS = jid:encode(JID),
|
|
|
|
Subs = atom_to_list(Sub),
|
|
|
|
Asks = atom_to_list(Ask),
|
2023-09-21 12:45:27 +02:00
|
|
|
{JIDS, Nick, Subs, Asks, Groups}
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
|
|
|
Roster).
|
|
|
|
|
2024-06-13 00:15:42 +02:00
|
|
|
get_roster_count(User, Server) ->
|
|
|
|
case jid:make(User, Server) of
|
|
|
|
error ->
|
|
|
|
throw({error, "Invalid 'user'/'server'"});
|
|
|
|
#jid{luser = U, lserver = S} ->
|
|
|
|
Items = ejabberd_hooks:run_fold(roster_get, S, [], [{U, S}]),
|
|
|
|
length(Items)
|
|
|
|
end.
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
%%-----------------------------
|
|
|
|
%% Push Roster from file
|
|
|
|
%%-----------------------------
|
|
|
|
|
|
|
|
push_roster(File, User, Server) ->
|
|
|
|
{ok, [Roster]} = file:consult(File),
|
2014-04-02 04:17:08 +02:00
|
|
|
subscribe_roster({User, Server, <<>>, User}, Roster).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
push_roster_all(File) ->
|
|
|
|
{ok, [Roster]} = file:consult(File),
|
|
|
|
subscribe_all(Roster).
|
|
|
|
|
|
|
|
subscribe_all(Roster) ->
|
|
|
|
subscribe_all(Roster, Roster).
|
|
|
|
subscribe_all([], _) ->
|
|
|
|
ok;
|
|
|
|
subscribe_all([User1 | Users], Roster) ->
|
|
|
|
subscribe_roster(User1, Roster),
|
|
|
|
subscribe_all(Users, Roster).
|
|
|
|
|
|
|
|
subscribe_roster(_, []) ->
|
|
|
|
ok;
|
|
|
|
%% Do not subscribe a user to itself
|
|
|
|
subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) ->
|
|
|
|
subscribe_roster({Name, Server, Group, Nick}, Roster);
|
|
|
|
%% Subscribe Name2 to Name1
|
|
|
|
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
|
2017-06-08 19:54:34 +02:00
|
|
|
subscribe(iolist_to_binary(Name1), iolist_to_binary(Server1), iolist_to_binary(Name2), iolist_to_binary(Server2),
|
2016-08-04 09:49:23 +02:00
|
|
|
iolist_to_binary(Nick2), iolist_to_binary(Group2), <<"both">>, []),
|
2013-04-15 12:03:14 +02:00
|
|
|
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
|
|
|
|
|
|
|
|
push_alltoall(S, G) ->
|
2017-05-11 13:37:21 +02:00
|
|
|
Users = ejabberd_auth:get_users(S),
|
2013-04-15 12:03:14 +02:00
|
|
|
Users2 = build_list_users(G, Users, []),
|
|
|
|
subscribe_all(Users2),
|
|
|
|
ok.
|
|
|
|
|
|
|
|
build_list_users(_Group, [], Res) ->
|
|
|
|
Res;
|
|
|
|
build_list_users(Group, [{User, Server}|Users], Res) ->
|
|
|
|
build_list_users(Group, Users, [{User, Server, Group, User}|Res]).
|
|
|
|
|
|
|
|
%% @spec(LU, LS, U, S, Action) -> ok
|
|
|
|
%% Action = {add, Nick, Subs, Group} | remove
|
|
|
|
%% @doc Push to the roster of account LU@LS the contact U@S.
|
|
|
|
%% The specific action to perform is defined in Action.
|
|
|
|
push_roster_item(LU, LS, U, S, Action) ->
|
|
|
|
lists:foreach(fun(R) ->
|
|
|
|
push_roster_item(LU, LS, R, U, S, Action)
|
|
|
|
end, ejabberd_sm:get_user_resources(LU, LS)).
|
|
|
|
|
|
|
|
push_roster_item(LU, LS, R, U, S, Action) ->
|
2015-11-24 16:44:13 +01:00
|
|
|
LJID = jid:make(LU, LS, R),
|
2013-04-15 12:03:14 +02:00
|
|
|
BroadcastEl = build_broadcast(U, S, Action),
|
2016-12-11 13:03:37 +01:00
|
|
|
ejabberd_sm:route(LJID, BroadcastEl),
|
2013-04-15 12:03:14 +02:00
|
|
|
Item = build_roster_item(U, S, Action),
|
|
|
|
ResIQ = build_iq_roster_push(Item),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route(
|
|
|
|
xmpp:set_from_to(ResIQ, jid:remove_resource(LJID), LJID)).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2023-11-29 19:04:57 +01:00
|
|
|
build_roster_item(U, S, {add, Nick, Subs, Groups}) when is_list(Groups) ->
|
|
|
|
#roster_item{jid = jid:make(U, S),
|
|
|
|
name = Nick,
|
|
|
|
subscription = misc:binary_to_atom(Subs),
|
|
|
|
groups = Groups};
|
2013-04-15 12:03:14 +02:00
|
|
|
build_roster_item(U, S, {add, Nick, Subs, Group}) ->
|
2022-09-09 18:27:49 +02:00
|
|
|
Groups = binary:split(Group,<<";">>, [global, trim]),
|
2016-07-29 12:21:00 +02:00
|
|
|
#roster_item{jid = jid:make(U, S),
|
|
|
|
name = Nick,
|
2017-04-11 12:13:58 +02:00
|
|
|
subscription = misc:binary_to_atom(Subs),
|
2016-11-12 11:27:15 +01:00
|
|
|
groups = Groups};
|
2013-04-15 12:03:14 +02:00
|
|
|
build_roster_item(U, S, remove) ->
|
2016-07-29 12:21:00 +02:00
|
|
|
#roster_item{jid = jid:make(U, S), subscription = remove}.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
build_iq_roster_push(Item) ->
|
2016-07-29 12:21:00 +02:00
|
|
|
#iq{type = set, id = <<"push">>,
|
|
|
|
sub_els = [#roster_query{items = [Item]}]}.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
build_broadcast(U, S, {add, _Nick, Subs, _Group}) ->
|
2014-04-02 11:23:04 +02:00
|
|
|
build_broadcast(U, S, list_to_atom(binary_to_list(Subs)));
|
2013-04-15 12:03:14 +02:00
|
|
|
build_broadcast(U, S, remove) ->
|
|
|
|
build_broadcast(U, S, none);
|
2014-04-02 04:17:08 +02:00
|
|
|
%% @spec (U::binary(), S::binary(), Subs::atom()) -> any()
|
2013-04-15 12:03:14 +02:00
|
|
|
%% Subs = both | from | to | none
|
|
|
|
build_broadcast(U, S, SubsAtom) when is_atom(SubsAtom) ->
|
2016-12-11 13:03:37 +01:00
|
|
|
{item, {U, S, <<>>}, SubsAtom}.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Last Activity
|
|
|
|
%%%
|
|
|
|
|
2013-08-07 13:36:42 +02:00
|
|
|
get_last(User, Server) ->
|
2017-02-23 23:27:57 +01:00
|
|
|
{Now, Status} = case ejabberd_sm:get_user_resources(User, Server) of
|
2016-04-01 12:24:00 +02:00
|
|
|
[] ->
|
|
|
|
case mod_last:get_last_info(User, Server) of
|
|
|
|
not_found ->
|
2019-02-27 09:56:20 +01:00
|
|
|
{erlang:timestamp(), "NOT FOUND"};
|
2017-02-23 23:27:57 +01:00
|
|
|
{ok, Shift, Status1} ->
|
|
|
|
{{Shift div 1000000, Shift rem 1000000, 0}, Status1}
|
2016-04-01 12:24:00 +02:00
|
|
|
end;
|
|
|
|
_ ->
|
2019-02-27 09:56:20 +01:00
|
|
|
{erlang:timestamp(), "ONLINE"}
|
2017-02-23 23:27:57 +01:00
|
|
|
end,
|
|
|
|
{xmpp_util:encode_timestamp(Now), Status}.
|
2013-08-07 13:36:42 +02:00
|
|
|
|
2017-09-25 18:43:24 +02:00
|
|
|
set_last(User, Server, Timestamp, Status) ->
|
|
|
|
case mod_last:store_last_info(User, Server, Timestamp, Status) of
|
|
|
|
{ok, _} -> ok;
|
|
|
|
Error -> Error
|
|
|
|
end.
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
%%%
|
|
|
|
%%% Private Storage
|
|
|
|
%%%
|
|
|
|
|
|
|
|
%% Example usage:
|
|
|
|
%% $ ejabberdctl private_set badlop localhost "\<aa\ xmlns=\'bb\'\>Cluth\</aa\>"
|
|
|
|
%% $ ejabberdctl private_get badlop localhost aa bb
|
|
|
|
%% <aa xmlns='bb'>Cluth</aa>
|
|
|
|
|
|
|
|
private_get(Username, Host, Element, Ns) ->
|
2024-04-19 13:47:06 +02:00
|
|
|
Els = private_get2(Username, Host, Element, Ns),
|
2018-01-25 18:02:47 +01:00
|
|
|
binary_to_list(fxml:element_to_binary(xmpp:encode(#private{sub_els = Els}))).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2024-04-19 13:47:06 +02:00
|
|
|
private_get2(Username, Host, Element, Ns) ->
|
2024-04-25 00:22:45 +02:00
|
|
|
case gen_mod:is_loaded(Host, mod_private) of
|
|
|
|
true -> private_get3(Username, Host, Element, Ns);
|
|
|
|
false -> []
|
|
|
|
end.
|
|
|
|
|
|
|
|
private_get3(Username, Host, Element, Ns) ->
|
2024-04-19 13:47:06 +02:00
|
|
|
ElementXml = #xmlel{name = Element, attrs = [{<<"xmlns">>, Ns}]},
|
|
|
|
mod_private:get_data(jid:nodeprep(Username), jid:nameprep(Host),
|
|
|
|
[{Ns, ElementXml}]).
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
private_set(Username, Host, ElementString) ->
|
2016-02-03 19:03:17 +01:00
|
|
|
case fxml_stream:parse_element(ElementString) of
|
2013-04-15 12:03:14 +02:00
|
|
|
{error, Error} ->
|
|
|
|
io:format("Error found parsing the element:~n ~p~nError: ~p~n",
|
|
|
|
[ElementString, Error]),
|
|
|
|
error;
|
|
|
|
Xml ->
|
|
|
|
private_set2(Username, Host, Xml)
|
|
|
|
end.
|
|
|
|
|
|
|
|
private_set2(Username, Host, Xml) ->
|
2016-07-29 12:21:00 +02:00
|
|
|
NS = fxml:get_tag_attr_s(<<"xmlns">>, Xml),
|
2018-11-23 11:33:29 +01:00
|
|
|
JID = jid:make(Username, Host),
|
|
|
|
mod_private:set_data(JID, [{NS, Xml}]).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Shared Roster Groups
|
|
|
|
%%%
|
|
|
|
|
2023-11-29 19:04:57 +01:00
|
|
|
srg_create(Group, Host, Label, Description, Display) when is_binary(Display) ->
|
2013-04-15 12:03:14 +02:00
|
|
|
DisplayList = case Display of
|
2023-11-29 19:04:57 +01:00
|
|
|
<<>> -> [];
|
|
|
|
_ -> ejabberd_regexp:split(Display, <<"\\\\n">>)
|
2013-04-15 12:03:14 +02:00
|
|
|
end,
|
2023-11-29 19:04:57 +01:00
|
|
|
srg_create(Group, Host, Label, Description, DisplayList);
|
|
|
|
|
|
|
|
srg_create(Group, Host, Label, Description, DisplayList) ->
|
2024-06-13 00:15:42 +02:00
|
|
|
{_DispGroups, WrongDispGroups} = filter_groups_existence(Host, DisplayList),
|
|
|
|
case (WrongDispGroups -- [Group]) /= [] of
|
|
|
|
true ->
|
|
|
|
{wrong_displayed_groups, WrongDispGroups};
|
|
|
|
false ->
|
|
|
|
srg_create2(Group, Host, Label, Description, DisplayList)
|
|
|
|
end.
|
|
|
|
|
|
|
|
srg_create2(Group, Host, Label, Description, DisplayList) ->
|
2021-04-17 18:49:26 +02:00
|
|
|
Opts = [{label, Label},
|
2013-04-15 12:03:14 +02:00
|
|
|
{displayed_groups, DisplayList},
|
|
|
|
{description, Description}],
|
2016-08-15 15:53:35 +02:00
|
|
|
{atomic, _} = mod_shared_roster:create_group(Host, Group, Opts),
|
2013-04-15 12:03:14 +02:00
|
|
|
ok.
|
|
|
|
|
2024-06-13 00:15:42 +02:00
|
|
|
srg_add(Group, Host) ->
|
|
|
|
Opts = [{label, <<"">>},
|
|
|
|
{description, <<"">>},
|
|
|
|
{displayed_groups, []}
|
|
|
|
],
|
|
|
|
{atomic, _} = mod_shared_roster:create_group(Host, Group, Opts),
|
|
|
|
ok.
|
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
srg_delete(Group, Host) ->
|
2016-08-15 15:53:35 +02:00
|
|
|
{atomic, _} = mod_shared_roster:delete_group(Host, Group),
|
2013-04-15 12:03:14 +02:00
|
|
|
ok.
|
|
|
|
|
|
|
|
srg_list(Host) ->
|
|
|
|
lists:sort(mod_shared_roster:list_groups(Host)).
|
|
|
|
|
|
|
|
srg_get_info(Group, Host) ->
|
2014-03-05 12:12:47 +01:00
|
|
|
Opts = case mod_shared_roster:get_group_opts(Host,Group) of
|
|
|
|
Os when is_list(Os) -> Os;
|
|
|
|
error -> []
|
|
|
|
end,
|
2018-02-20 00:44:47 +01:00
|
|
|
[{misc:atom_to_binary(Title), to_list(Value)} || {Title, Value} <- Opts].
|
2015-04-03 12:16:11 +02:00
|
|
|
|
2018-02-20 00:44:47 +01:00
|
|
|
to_list([]) -> [];
|
2024-06-13 00:15:42 +02:00
|
|
|
to_list([H|_]=List) when is_binary(H) -> lists:join(", ", [to_list(E) || E <- List]);
|
2018-02-20 00:44:47 +01:00
|
|
|
to_list(E) when is_atom(E) -> atom_to_list(E);
|
2024-06-13 00:15:42 +02:00
|
|
|
to_list(E) when is_binary(E) -> binary_to_list(E).
|
|
|
|
|
|
|
|
%% @format-begin
|
|
|
|
|
|
|
|
srg_set_info(Group, Host, Key, Value) ->
|
|
|
|
Opts =
|
|
|
|
case mod_shared_roster:get_group_opts(Host, Group) of
|
|
|
|
Os when is_list(Os) ->
|
|
|
|
Os;
|
|
|
|
error ->
|
|
|
|
[]
|
|
|
|
end,
|
|
|
|
Opts2 = srg_set_info(Key, Value, Opts),
|
|
|
|
case mod_shared_roster:set_group_opts(Host, Group, Opts2) of
|
|
|
|
{atomic, ok} ->
|
|
|
|
ok;
|
|
|
|
Problem ->
|
|
|
|
?INFO_MSG("Problem: ~n ~p", [Problem]), %+++
|
|
|
|
error
|
|
|
|
end.
|
|
|
|
|
|
|
|
srg_set_info(<<"description">>, Value, Opts) ->
|
|
|
|
[{description, Value} | proplists:delete(description, Opts)];
|
|
|
|
srg_set_info(<<"label">>, Value, Opts) ->
|
|
|
|
[{label, Value} | proplists:delete(label, Opts)];
|
|
|
|
srg_set_info(<<"all_users">>, <<"true">>, Opts) ->
|
|
|
|
[{all_users, true} | proplists:delete(all_users, Opts)];
|
|
|
|
srg_set_info(<<"online_users">>, <<"true">>, Opts) ->
|
|
|
|
[{online_users, true} | proplists:delete(online_users, Opts)];
|
|
|
|
srg_set_info(<<"all_users">>, _, Opts) ->
|
|
|
|
proplists:delete(all_users, Opts);
|
|
|
|
srg_set_info(<<"online_users">>, _, Opts) ->
|
|
|
|
proplists:delete(online_users, Opts);
|
|
|
|
srg_set_info(Key, _Value, Opts) ->
|
|
|
|
?ERROR_MSG("Unknown Key in srg_set_info: ~p", [Key]),
|
|
|
|
Opts.
|
|
|
|
|
|
|
|
srg_get_displayed(Group, Host) ->
|
|
|
|
Opts =
|
|
|
|
case mod_shared_roster:get_group_opts(Host, Group) of
|
|
|
|
Os when is_list(Os) ->
|
|
|
|
Os;
|
|
|
|
error ->
|
|
|
|
[]
|
|
|
|
end,
|
2024-07-08 10:53:50 +02:00
|
|
|
proplists:get_value(displayed_groups, Opts, []).
|
2024-06-13 00:15:42 +02:00
|
|
|
|
|
|
|
srg_add_displayed(Group, Host, NewGroup) ->
|
|
|
|
Opts =
|
|
|
|
case mod_shared_roster:get_group_opts(Host, Group) of
|
|
|
|
Os when is_list(Os) ->
|
|
|
|
Os;
|
|
|
|
error ->
|
|
|
|
[]
|
|
|
|
end,
|
|
|
|
{DispGroups, WrongDispGroups} = filter_groups_existence(Host, [NewGroup]),
|
|
|
|
case WrongDispGroups /= [] of
|
|
|
|
true ->
|
|
|
|
{wrong_displayed_groups, WrongDispGroups};
|
|
|
|
false ->
|
|
|
|
DisplayedOld = proplists:get_value(displayed_groups, Opts, []),
|
|
|
|
Opts2 =
|
|
|
|
[{displayed_groups, lists:flatten(DisplayedOld, DispGroups)}
|
|
|
|
| proplists:delete(displayed_groups, Opts)],
|
|
|
|
case mod_shared_roster:set_group_opts(Host, Group, Opts2) of
|
|
|
|
{atomic, ok} ->
|
|
|
|
ok;
|
|
|
|
Problem ->
|
|
|
|
?INFO_MSG("Problem: ~n ~p", [Problem]), %+++
|
|
|
|
error
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
|
|
|
srg_del_displayed(Group, Host, OldGroup) ->
|
|
|
|
Opts =
|
|
|
|
case mod_shared_roster:get_group_opts(Host, Group) of
|
|
|
|
Os when is_list(Os) ->
|
|
|
|
Os;
|
|
|
|
error ->
|
|
|
|
[]
|
|
|
|
end,
|
|
|
|
DisplayedOld = proplists:get_value(displayed_groups, Opts, []),
|
|
|
|
{DispGroups, OldDispGroups} = lists:partition(fun(G) -> G /= OldGroup end, DisplayedOld),
|
|
|
|
case OldDispGroups == [] of
|
|
|
|
true ->
|
|
|
|
{inexistent_displayed_groups, OldGroup};
|
|
|
|
false ->
|
|
|
|
Opts2 = [{displayed_groups, DispGroups} | proplists:delete(displayed_groups, Opts)],
|
|
|
|
case mod_shared_roster:set_group_opts(Host, Group, Opts2) of
|
|
|
|
{atomic, ok} ->
|
|
|
|
ok;
|
|
|
|
Problem ->
|
|
|
|
?INFO_MSG("Problem: ~n ~p", [Problem]), %+++
|
|
|
|
error
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
|
|
|
filter_groups_existence(Host, Groups) ->
|
|
|
|
lists:partition(fun(Group) -> error /= mod_shared_roster:get_group_opts(Host, Group) end,
|
|
|
|
Groups).
|
|
|
|
%% @format-end
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
srg_get_members(Group, Host) ->
|
|
|
|
Members = mod_shared_roster:get_group_explicit_users(Host,Group),
|
2017-02-26 08:07:12 +01:00
|
|
|
[jid:encode(jid:make(MUser, MServer))
|
2013-04-15 12:03:14 +02:00
|
|
|
|| {MUser, MServer} <- Members].
|
|
|
|
|
|
|
|
srg_user_add(User, Host, Group, GroupHost) ->
|
2017-06-15 11:05:41 +02:00
|
|
|
mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group),
|
2013-04-15 12:03:14 +02:00
|
|
|
ok.
|
|
|
|
|
|
|
|
srg_user_del(User, Host, Group, GroupHost) ->
|
2017-06-15 11:05:41 +02:00
|
|
|
mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group),
|
2013-04-15 12:03:14 +02:00
|
|
|
ok.
|
|
|
|
|
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Stanza
|
|
|
|
%%%
|
|
|
|
|
2021-01-11 20:18:28 +01:00
|
|
|
%% @doc Send a message to an XMPP account.
|
2022-05-12 17:25:11 +02:00
|
|
|
-spec send_message(Type::binary(), From::binary(), To::binary(),
|
|
|
|
Subject::binary(), Body::binary()) -> ok.
|
2015-02-11 18:48:43 +01:00
|
|
|
send_message(Type, From, To, Subject, Body) ->
|
2019-11-29 09:27:57 +01:00
|
|
|
CodecOpts = ejabberd_config:codec_options(),
|
|
|
|
try xmpp:decode(
|
|
|
|
#xmlel{name = <<"message">>,
|
|
|
|
attrs = [{<<"to">>, To},
|
|
|
|
{<<"from">>, From},
|
|
|
|
{<<"type">>, Type},
|
|
|
|
{<<"id">>, p1_rand:get_string()}],
|
|
|
|
children =
|
|
|
|
[#xmlel{name = <<"subject">>,
|
|
|
|
children = [{xmlcdata, Subject}]},
|
|
|
|
#xmlel{name = <<"body">>,
|
|
|
|
children = [{xmlcdata, Body}]}]},
|
|
|
|
?NS_CLIENT, CodecOpts) of
|
2021-01-31 23:07:13 +01:00
|
|
|
#message{from = JID, subject = SubjectEl, body = BodyEl} = Msg ->
|
|
|
|
Msg2 = case {xmpp:get_text(SubjectEl), xmpp:get_text(BodyEl)} of
|
|
|
|
{Subject, <<>>} -> Msg;
|
|
|
|
{<<>>, Body} -> Msg#message{subject = []};
|
2021-01-13 20:57:03 +01:00
|
|
|
_ -> Msg
|
|
|
|
end,
|
2020-09-04 11:37:18 +02:00
|
|
|
State = #{jid => JID},
|
2021-01-13 20:57:03 +01:00
|
|
|
ejabberd_hooks:run_fold(user_send_packet, JID#jid.lserver, {Msg2, State}, []),
|
|
|
|
ejabberd_router:route(Msg2)
|
2019-11-29 09:27:57 +01:00
|
|
|
catch _:{xmpp_codec, Why} ->
|
|
|
|
{error, xmpp:format_error(Why)}
|
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2016-01-19 16:16:04 +01:00
|
|
|
send_stanza(FromString, ToString, Stanza) ->
|
2016-07-29 12:21:00 +02:00
|
|
|
try
|
|
|
|
#xmlel{} = El = fxml_stream:parse_element(Stanza),
|
2017-02-26 08:07:12 +01:00
|
|
|
From = jid:decode(FromString),
|
|
|
|
To = jid:decode(ToString),
|
2019-06-21 20:06:32 +02:00
|
|
|
CodecOpts = ejabberd_config:codec_options(),
|
2018-02-09 16:12:50 +01:00
|
|
|
Pkt = xmpp:decode(El, ?NS_CLIENT, CodecOpts),
|
2020-12-01 13:06:04 +01:00
|
|
|
Pkt2 = xmpp:set_from_to(Pkt, From, To),
|
|
|
|
State = #{jid => From},
|
|
|
|
ejabberd_hooks:run_fold(user_send_packet, From#jid.lserver,
|
|
|
|
{Pkt2, State}, []),
|
|
|
|
ejabberd_router:route(Pkt2)
|
2016-07-29 12:21:00 +02:00
|
|
|
catch _:{xmpp_codec, Why} ->
|
2019-09-23 14:17:20 +02:00
|
|
|
io:format("incorrect stanza: ~ts~n", [xmpp:format_error(Why)]),
|
2016-07-29 12:21:00 +02:00
|
|
|
{error, Why};
|
2020-03-23 12:59:30 +01:00
|
|
|
_:{badmatch, {error, {Code, Why}}} when is_integer(Code) ->
|
|
|
|
io:format("invalid xml: ~p~n", [Why]),
|
|
|
|
{error, Why};
|
2016-07-29 12:21:00 +02:00
|
|
|
_:{badmatch, {error, Why}} ->
|
|
|
|
io:format("invalid xml: ~p~n", [Why]),
|
|
|
|
{error, Why};
|
2017-02-26 08:07:12 +01:00
|
|
|
_:{bad_jid, S} ->
|
2019-09-23 14:17:20 +02:00
|
|
|
io:format("malformed JID: ~ts~n", [S]),
|
2016-07-29 12:21:00 +02:00
|
|
|
{error, "JID malformed"}
|
2016-01-19 16:16:04 +01:00
|
|
|
end.
|
2015-12-30 12:53:40 +01:00
|
|
|
|
2019-07-09 00:01:56 +02:00
|
|
|
-spec send_stanza_c2s(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
|
2013-04-15 12:03:14 +02:00
|
|
|
send_stanza_c2s(Username, Host, Resource, Stanza) ->
|
2019-07-09 00:01:56 +02:00
|
|
|
try
|
|
|
|
#xmlel{} = El = fxml_stream:parse_element(Stanza),
|
|
|
|
CodecOpts = ejabberd_config:codec_options(),
|
|
|
|
Pkt = xmpp:decode(El, ?NS_CLIENT, CodecOpts),
|
|
|
|
case ejabberd_sm:get_session_pid(Username, Host, Resource) of
|
|
|
|
Pid when is_pid(Pid) ->
|
|
|
|
ejabberd_c2s:send(Pid, Pkt);
|
|
|
|
_ ->
|
|
|
|
{error, no_session}
|
|
|
|
end
|
|
|
|
catch _:{badmatch, {error, Why} = Err} ->
|
|
|
|
io:format("invalid xml: ~p~n", [Why]),
|
|
|
|
Err;
|
|
|
|
_:{xmpp_codec, Why} ->
|
2019-09-23 14:17:20 +02:00
|
|
|
io:format("incorrect stanza: ~ts~n", [xmpp:format_error(Why)]),
|
2019-07-09 00:01:56 +02:00
|
|
|
{error, Why}
|
2016-01-19 16:16:04 +01:00
|
|
|
end.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
privacy_set(Username, Host, QueryS) ->
|
2018-01-04 14:57:26 +01:00
|
|
|
Jid = jid:make(Username, Host),
|
2016-02-03 19:03:17 +01:00
|
|
|
QueryEl = fxml_stream:parse_element(QueryS),
|
2016-07-29 12:21:00 +02:00
|
|
|
SubEl = xmpp:decode(QueryEl),
|
2016-10-22 12:01:45 +02:00
|
|
|
IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
|
2018-01-04 14:57:26 +01:00
|
|
|
from = Jid, to = Jid},
|
|
|
|
Result = mod_privacy:process_iq(IQ),
|
|
|
|
Result#iq.type == result.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Stats
|
|
|
|
%%%
|
|
|
|
|
|
|
|
stats(Name) ->
|
|
|
|
case Name of
|
2014-05-02 22:01:36 +02:00
|
|
|
<<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000);
|
2016-01-14 15:09:03 +01:00
|
|
|
<<"processes">> -> length(erlang:processes());
|
2019-06-14 11:33:26 +02:00
|
|
|
<<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:count_users(Host) + Sum end, 0, ejabberd_option:hosts());
|
2014-05-02 22:01:36 +02:00
|
|
|
<<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list());
|
|
|
|
<<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list())
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
stats(Name, Host) ->
|
|
|
|
case Name of
|
2017-05-11 13:37:21 +02:00
|
|
|
<<"registeredusers">> -> ejabberd_auth:count_users(Host);
|
2014-05-02 22:01:36 +02:00
|
|
|
<<"onlineusers">> -> length(ejabberd_sm:get_vh_session_list(Host))
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
|
2016-04-01 13:05:41 +02:00
|
|
|
user_action(User, Server, Fun, OK) ->
|
2017-05-11 14:49:06 +02:00
|
|
|
case ejabberd_auth:user_exists(User, Server) of
|
2016-04-01 13:05:41 +02:00
|
|
|
true ->
|
2019-05-15 10:57:55 +02:00
|
|
|
case catch Fun() of
|
2016-04-01 13:05:41 +02:00
|
|
|
OK -> ok;
|
2019-05-15 10:57:55 +02:00
|
|
|
{error, Error} -> throw(Error);
|
2016-04-01 13:05:41 +02:00
|
|
|
Error ->
|
|
|
|
?ERROR_MSG("Command returned: ~p", [Error]),
|
2019-05-15 10:57:55 +02:00
|
|
|
1
|
|
|
|
end;
|
|
|
|
false ->
|
|
|
|
throw({not_found, "unknown_user"})
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
2015-06-01 14:38:27 +02:00
|
|
|
|
2018-07-13 09:50:38 +02:00
|
|
|
num_prio(Priority) when is_integer(Priority) ->
|
|
|
|
Priority;
|
|
|
|
num_prio(_) ->
|
|
|
|
-1.
|
|
|
|
|
2024-06-13 00:15:42 +02:00
|
|
|
%%%
|
|
|
|
%%% Web Admin
|
|
|
|
%%%
|
|
|
|
|
|
|
|
%% @format-begin
|
|
|
|
|
|
|
|
%%% Main
|
|
|
|
|
|
|
|
web_menu_main(Acc, _Lang) ->
|
|
|
|
Acc ++ [{<<"stats">>, <<"Statistics">>}].
|
|
|
|
|
|
|
|
web_page_main(_, #request{path = [<<"stats">>]} = R) ->
|
|
|
|
Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>)
|
|
|
|
++ [make_command(stats_host, R, [], [{only, presentation}]),
|
|
|
|
make_command(incoming_s2s_number, R, [], [{only, presentation}]),
|
|
|
|
make_command(outgoing_s2s_number, R, [], [{only, presentation}]),
|
|
|
|
make_table([<<"stat name">>, {<<"stat value">>, right}],
|
|
|
|
[{?C(<<"Registered Users:">>),
|
|
|
|
make_command(stats,
|
|
|
|
R,
|
|
|
|
[{<<"name">>, <<"registeredusers">>}],
|
|
|
|
[{only, value}])},
|
|
|
|
{?C(<<"Online Users:">>),
|
|
|
|
make_command(stats,
|
|
|
|
R,
|
|
|
|
[{<<"name">>, <<"onlineusers">>}],
|
|
|
|
[{only, value}])},
|
|
|
|
{?C(<<"S2S Connections Incoming:">>),
|
|
|
|
make_command(incoming_s2s_number, R, [], [{only, value}])},
|
|
|
|
{?C(<<"S2S Connections Outgoing:">>),
|
|
|
|
make_command(outgoing_s2s_number, R, [], [{only, value}])}])],
|
|
|
|
{stop, Res};
|
|
|
|
web_page_main(Acc, _) ->
|
|
|
|
Acc.
|
|
|
|
|
|
|
|
%%% Host
|
|
|
|
|
|
|
|
web_menu_host(Acc, _Host, _Lang) ->
|
|
|
|
Acc ++ [{<<"purge">>, <<"Purge">>}, {<<"stats">>, <<"Statistics">>}].
|
|
|
|
|
|
|
|
web_page_host(_, Host, #request{path = [<<"purge">>]} = R) ->
|
|
|
|
Head = [?XC(<<"h1">>, <<"Purge">>)],
|
|
|
|
Set = [ejabberd_web_admin:make_command(delete_old_users_vhost,
|
|
|
|
R,
|
|
|
|
[{<<"host">>, Host}],
|
|
|
|
[])],
|
|
|
|
{stop, Head ++ Set};
|
|
|
|
web_page_host(_, Host, #request{path = [<<"stats">>]} = R) ->
|
|
|
|
Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>)
|
|
|
|
++ [make_command(stats_host, R, [], [{only, presentation}]),
|
|
|
|
make_table([<<"stat name">>, {<<"stat value">>, right}],
|
|
|
|
[{?C(<<"Registered Users:">>),
|
|
|
|
make_command(stats_host,
|
|
|
|
R,
|
|
|
|
[{<<"host">>, Host}, {<<"name">>, <<"registeredusers">>}],
|
|
|
|
[{only, value},
|
|
|
|
{result_links, [{stat, arg_host, 3, <<"users">>}]}])},
|
|
|
|
{?C(<<"Online Users:">>),
|
|
|
|
make_command(stats_host,
|
|
|
|
R,
|
|
|
|
[{<<"host">>, Host}, {<<"name">>, <<"onlineusers">>}],
|
|
|
|
[{only, value},
|
|
|
|
{result_links,
|
|
|
|
[{stat, arg_host, 3, <<"online-users">>}]}])}])],
|
|
|
|
{stop, Res};
|
|
|
|
web_page_host(Acc, _, _) ->
|
|
|
|
Acc.
|
|
|
|
|
|
|
|
%%% HostUser
|
|
|
|
|
|
|
|
web_menu_hostuser(Acc, _Host, _Username, _Lang) ->
|
2024-09-16 17:09:01 +02:00
|
|
|
Acc ++ [{<<"auth">>, <<"Authentication">>}, {<<"session">>, <<"Sessions">>}].
|
2024-06-13 00:15:42 +02:00
|
|
|
|
|
|
|
web_page_hostuser(_, Host, User, #request{path = [<<"auth">>]} = R) ->
|
|
|
|
Ban = make_command(ban_account,
|
|
|
|
R,
|
|
|
|
[{<<"user">>, User}, {<<"host">>, Host}],
|
|
|
|
[{style, danger}]),
|
|
|
|
Unban = make_command(unban_account, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
Res = ?H1GLraw(<<"Authentication">>,
|
|
|
|
<<"admin/configuration/authentication/">>,
|
|
|
|
<<"Authentication">>)
|
|
|
|
++ [make_command(register, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
make_command(check_account, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
?X(<<"hr">>),
|
|
|
|
make_command(check_password, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
make_command(check_password_hash, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
make_command(change_password,
|
|
|
|
R,
|
|
|
|
[{<<"user">>, User}, {<<"host">>, Host}],
|
|
|
|
[{style, danger}]),
|
|
|
|
?X(<<"hr">>),
|
|
|
|
make_command(get_ban_details, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
Ban,
|
|
|
|
Unban,
|
|
|
|
?X(<<"hr">>),
|
|
|
|
make_command(unregister,
|
|
|
|
R,
|
|
|
|
[{<<"user">>, User}, {<<"host">>, Host}],
|
|
|
|
[{style, danger}])],
|
|
|
|
{stop, Res};
|
|
|
|
web_page_hostuser(_, Host, User, #request{path = [<<"session">>]} = R) ->
|
|
|
|
Head = [?XC(<<"h1">>, <<"Sessions">>), ?BR],
|
|
|
|
Set = [make_command(resource_num, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
make_command(set_presence, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
make_command(kick_user, R, [{<<"user">>, User}, {<<"host">>, Host}], [{style, danger}]),
|
|
|
|
make_command(kick_session,
|
|
|
|
R,
|
|
|
|
[{<<"user">>, User}, {<<"host">>, Host}],
|
|
|
|
[{style, danger}])],
|
|
|
|
timer:sleep(100), % kicking sessions takes a while, let's delay the get commands
|
|
|
|
Get = [make_command(user_sessions_info,
|
|
|
|
R,
|
|
|
|
[{<<"user">>, User}, {<<"host">>, Host}],
|
|
|
|
[{result_links, [{node, node, 5, <<>>}]}]),
|
|
|
|
make_command(user_resources, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
make_command(get_presence, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
|
|
|
make_command(num_resources, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
|
|
|
|
{stop, Head ++ Get ++ Set};
|
|
|
|
web_page_hostuser(Acc, _, _, _) ->
|
|
|
|
Acc.
|
|
|
|
|
|
|
|
%%% HostNode
|
|
|
|
|
|
|
|
web_menu_hostnode(Acc, _Host, _Username, _Lang) ->
|
|
|
|
Acc ++ [{<<"modules">>, <<"Modules">>}].
|
|
|
|
|
|
|
|
web_page_hostnode(_, Host, Node, #request{path = [<<"modules">>]} = R) ->
|
|
|
|
Res = ?H1GLraw(<<"Modules">>, <<"admin/configuration/modules/">>, <<"Modules Options">>)
|
|
|
|
++ [ejabberd_cluster:call(Node,
|
|
|
|
ejabberd_web_admin,
|
|
|
|
make_command,
|
|
|
|
[restart_module, R, [{<<"host">>, Host}], []])],
|
|
|
|
{stop, Res};
|
|
|
|
web_page_hostnode(Acc, _Host, _Node, _Request) ->
|
|
|
|
Acc.
|
|
|
|
|
|
|
|
%%% Node
|
|
|
|
|
|
|
|
web_menu_node(Acc, _Node, _Lang) ->
|
|
|
|
Acc ++ [{<<"stats">>, <<"Statistics">>}].
|
|
|
|
|
|
|
|
web_page_node(_, Node, #request{path = [<<"stats">>]} = R) ->
|
|
|
|
UpSecs =
|
|
|
|
ejabberd_cluster:call(Node,
|
|
|
|
ejabberd_web_admin,
|
|
|
|
make_command,
|
|
|
|
[stats, R, [{<<"name">>, <<"uptimeseconds">>}], [{only, value}]]),
|
|
|
|
UpDaysBin = integer_to_binary(binary_to_integer(fxml:get_tag_cdata(UpSecs)) div 24000),
|
|
|
|
UpDays =
|
|
|
|
#xmlel{name = <<"code">>,
|
|
|
|
attrs = [],
|
|
|
|
children = [{xmlcdata, UpDaysBin}]},
|
|
|
|
Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>)
|
|
|
|
++ [make_command(stats, R, [], [{only, presentation}]),
|
|
|
|
make_table([<<"stat name">>, {<<"stat value">>, right}],
|
|
|
|
[{?C(<<"Online Users in this node:">>),
|
|
|
|
ejabberd_cluster:call(Node,
|
|
|
|
ejabberd_web_admin,
|
|
|
|
make_command,
|
|
|
|
[stats,
|
|
|
|
R,
|
|
|
|
[{<<"name">>, <<"onlineusersnode">>}],
|
|
|
|
[{only, value}]])},
|
|
|
|
{?C(<<"Uptime Seconds:">>), UpSecs},
|
|
|
|
{?C(<<"Uptime Seconds (rounded to days):">>), UpDays},
|
|
|
|
{?C(<<"Processes:">>),
|
|
|
|
ejabberd_cluster:call(Node,
|
|
|
|
ejabberd_web_admin,
|
|
|
|
make_command,
|
|
|
|
[stats,
|
|
|
|
R,
|
|
|
|
[{<<"name">>, <<"processes">>}],
|
|
|
|
[{only, value}]])}])],
|
|
|
|
{stop, Res};
|
|
|
|
web_page_node(Acc, _, _) ->
|
|
|
|
Acc.
|
|
|
|
%% @format-end
|
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Document
|
|
|
|
%%%
|
|
|
|
|
2018-01-23 08:54:52 +01:00
|
|
|
mod_options(_) -> [].
|
2020-01-08 10:24:51 +01:00
|
|
|
|
|
|
|
mod_doc() ->
|
|
|
|
#{desc =>
|
|
|
|
[?T("This module provides additional administrative commands."), "",
|
|
|
|
?T("Details for some commands:"), "",
|
2024-06-27 17:20:50 +02:00
|
|
|
?T("- 'ban_account':"),
|
2020-04-17 17:13:14 +02:00
|
|
|
?T("This command kicks all the connected sessions of the account "
|
|
|
|
"from the server. It also changes their password to a randomly "
|
|
|
|
"generated one, so they can't login anymore unless a server "
|
|
|
|
"administrator changes their password again. It is possible to "
|
|
|
|
"define the reason of the ban. The new password also includes "
|
2020-04-21 19:45:17 +02:00
|
|
|
"the reason and the date and time of the ban. See an example below."),
|
2020-05-12 21:21:28 +02:00
|
|
|
?T("- 'pushroster': (and 'pushroster-all')"),
|
2020-04-17 17:13:14 +02:00
|
|
|
?T("The roster file must be placed, if using Windows, on the "
|
|
|
|
"directory where you installed ejabberd: "
|
2024-03-07 01:09:53 +01:00
|
|
|
"`C:/Program Files/ejabberd` or similar. If you use other "
|
2020-04-17 17:13:14 +02:00
|
|
|
"Operating System, place the file on the same directory where "
|
|
|
|
"the .beam files are installed. See below an example roster file."),
|
2024-06-27 17:20:50 +02:00
|
|
|
?T("- 'srg_create':"),
|
|
|
|
?T("If you want to put a group Name with blank spaces, use the "
|
2020-04-17 17:13:14 +02:00
|
|
|
"characters \"\' and \'\" to define when the Name starts and "
|
2020-04-21 19:45:17 +02:00
|
|
|
"ends. See an example below.")],
|
2020-01-08 10:24:51 +01:00
|
|
|
example =>
|
2020-04-17 17:13:14 +02:00
|
|
|
[{?T("With this configuration, vCards can only be modified with "
|
|
|
|
"mod_admin_extra commands:"),
|
2020-01-08 10:24:51 +01:00
|
|
|
["acl:",
|
|
|
|
" adminextraresource:",
|
|
|
|
" - resource: \"modadminextraf8x,31ad\"",
|
|
|
|
"access_rules:",
|
|
|
|
" vcard_set:",
|
|
|
|
" - allow: adminextraresource",
|
|
|
|
"modules:",
|
2021-09-14 11:24:49 +02:00
|
|
|
" mod_admin_extra: {}",
|
2020-01-08 10:24:51 +01:00
|
|
|
" mod_vcard:",
|
|
|
|
" access_set: vcard_set"]},
|
|
|
|
{?T("Content of roster file for 'pushroster' command:"),
|
|
|
|
["[{<<\"bob\">>, <<\"example.org\">>, <<\"workers\">>, <<\"Bob\">>},",
|
|
|
|
"{<<\"mart\">>, <<\"example.org\">>, <<\"workers\">>, <<\"Mart\">>},",
|
2020-04-21 19:45:17 +02:00
|
|
|
"{<<\"Rich\">>, <<\"example.org\">>, <<\"bosses\">>, <<\"Rich\">>}]."]},
|
|
|
|
{?T("With this call, the sessions of the local account which JID is "
|
|
|
|
"boby@example.org will be kicked, and its password will be set "
|
|
|
|
"to something like "
|
|
|
|
"'BANNED_ACCOUNT--20080425T21:45:07--2176635--Spammed_rooms'"),
|
2024-06-27 17:20:50 +02:00
|
|
|
["ejabberdctl vhost example.org ban_account boby \"Spammed rooms\""]},
|
|
|
|
{?T("Call to srg_create using double-quotes and single-quotes:"),
|
|
|
|
["ejabberdctl srg_create g1 example.org \"\'Group number 1\'\" this_is_g1 g1"]}]}.
|