mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-26 17:38:45 +01:00
1348 lines
43 KiB
Erlang
1348 lines
43 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% File : mod_admin_p1.erl
|
|
%%% Author : Badlop / Mickael Remond / Christophe Romain
|
|
%%% Purpose : Administrative functions and commands for ProcessOne customers
|
|
%%% Created : 21 May 2008 by Badlop <badlop@process-one.net>
|
|
%%%
|
|
%%%
|
|
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
|
%%%
|
|
%%% This program is free software; you can redistribute it and/or
|
|
%%% modify it under the terms of the GNU General Public License as
|
|
%%% published by the Free Software Foundation; either version 2 of the
|
|
%%% License, or (at your option) any later version.
|
|
%%%
|
|
%%% This program is distributed in the hope that it will be useful,
|
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
%%% General Public License for more details.
|
|
%%%
|
|
%%% You should have received a copy of the GNU General Public License
|
|
%%% along with this program; if not, write to the Free Software
|
|
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
%%% 02111-1307 USA
|
|
%%%
|
|
%%%-------------------------------------------------------------------
|
|
|
|
%%% @doc Administrative functions and commands for ProcessOne customers
|
|
%%%
|
|
%%% This ejabberd module defines and registers many ejabberd commands
|
|
%%% that can be used for performing administrative tasks in ejabberd.
|
|
%%%
|
|
%%% The documentation of all those commands can be read using ejabberdctl
|
|
%%% in the shell.
|
|
%%%
|
|
%%% The commands can be executed using any frontend to ejabberd commands.
|
|
%%% Currently ejabberd_xmlrpc and ejabberdctl. Using ejabberd_xmlrpc it is possible
|
|
%%% to call any ejabberd command. However using ejabberdctl not all commands
|
|
%%% can be called.
|
|
|
|
%%% Changelog:
|
|
%%%
|
|
%%% 0.8 - 26 September 2008 - badlop
|
|
%%% - added patch for parameter 'Push'
|
|
%%%
|
|
%%% 0.7 - 20 August 2008 - badlop
|
|
%%% - module converted to ejabberd commands
|
|
%%%
|
|
%%% 0.6 - 02 June 2008 - cromain
|
|
%%% - add user existance checking
|
|
%%% - improve parameter checking
|
|
%%% - allow orderless parameter
|
|
%%%
|
|
%%% 0.5 - 17 March 2008 - cromain
|
|
%%% - add user changing and higher level methods
|
|
%%%
|
|
%%% 0.4 - 18 February 2008 - cromain
|
|
%%% - add roster handling
|
|
%%% - add message sending
|
|
%%% - code and api clean-up
|
|
%%%
|
|
%%% 0.3 - 18 October 2007 - cromain
|
|
%%% - presence improvement
|
|
%%% - add new functionality
|
|
%%%
|
|
%%% 0.2 - 4 March 2006 - mremond
|
|
%%% - Code clean-up
|
|
%%% - Made it compatible with current ejabberd SVN version
|
|
%%%
|
|
%%% 0.1.2 - 28 December 2005
|
|
%%% - Now compatible with ejabberd 1.0.0
|
|
%%% - The XMLRPC server is started only once, not once for every virtual host
|
|
%%% - Added comments for handlers. Every available handler must be explained
|
|
%%%
|
|
|
|
-module(mod_admin_p1).
|
|
-author('ProcessOne').
|
|
|
|
-export([start/2, stop/1,
|
|
%% Erlang
|
|
restart_module/2,
|
|
%% Accounts
|
|
create_account/3,
|
|
delete_account/2,
|
|
change_password/3,
|
|
rename_account/4,
|
|
check_users_registration/1,
|
|
%% Sessions
|
|
get_presence/2,
|
|
get_resources/2,
|
|
%% Vcard
|
|
set_nickname/3,
|
|
%% Roster
|
|
add_rosteritem/6,
|
|
delete_rosteritem/3,
|
|
add_rosteritem_groups/5,
|
|
del_rosteritem_groups/5,
|
|
modify_rosteritem_groups/6,
|
|
link_contacts/6,
|
|
unlink_contacts/2,
|
|
link_contacts/7, unlink_contacts/3, % Versions with Push parameter
|
|
get_roster/2,
|
|
get_roster_with_presence/2,
|
|
add_contacts/3,
|
|
remove_contacts/3,
|
|
%% PubSub
|
|
update_status/4,
|
|
delete_status/3,
|
|
%% Transports
|
|
transport_register/5,
|
|
%% Stanza
|
|
send_chat/3,
|
|
send_message/4,
|
|
send_stanza/3
|
|
]).
|
|
|
|
-include("ejabberd.hrl").
|
|
-include("ejabberd_commands.hrl").
|
|
-include("mod_roster.hrl").
|
|
-include("jlib.hrl").
|
|
|
|
-ifdef(EJABBERD1).
|
|
-record(session, {sid, usr, us, priority}). %% ejabberd 1.1.x
|
|
-else.
|
|
-record(session, {sid, usr, us, priority, info}). %% ejabberd 2.x.x
|
|
-endif.
|
|
|
|
start(_Host, _Opts) ->
|
|
ejabberd_commands:register_commands(commands()).
|
|
|
|
stop(_Host) ->
|
|
ejabberd_commands:unregister_commands(commands()).
|
|
|
|
%%%
|
|
%%% Register commands
|
|
%%%
|
|
|
|
commands() ->
|
|
[
|
|
#ejabberd_commands{name = restart_module, tags = [erlang],
|
|
desc = "Stop an ejabberd module, reload code and start",
|
|
module = ?MODULE, function = restart_module,
|
|
args = [{module, string}, {host, string}],
|
|
result = {res, rescode}},
|
|
|
|
%% Similar to ejabberd_admin register
|
|
#ejabberd_commands{name = create_account, tags = [accounts],
|
|
desc = "Create an ejabberd user account",
|
|
longdesc = "This command is similar to 'register'.",
|
|
module = ?MODULE, function = create_account,
|
|
args = [{user, string}, {server, string},
|
|
{password, string}],
|
|
result = {res, integer}},
|
|
|
|
%% Similar to ejabberd_admin unregister
|
|
#ejabberd_commands{name = delete_account, tags = [accounts],
|
|
desc = "Remove an account from the server",
|
|
longdesc = "This command is similar to 'unregister'.",
|
|
module = ?MODULE, function = delete_account,
|
|
args = [{user, string}, {server, string}],
|
|
result = {res, integer}},
|
|
|
|
#ejabberd_commands{name = rename_account, tags = [accounts],
|
|
desc = "Change an acount name",
|
|
longdesc = "Creates a new account "
|
|
"and copies the roster from the old one, and updates the rosters of his contacts. "
|
|
"Offline messages and private storage are lost.",
|
|
module = ?MODULE, function = rename_account,
|
|
args = [{user, string}, {server, string},
|
|
{newuser, string}, {newserver, string}],
|
|
result = {res, integer}},
|
|
|
|
%% This command is also implemented in mod_admin_contrib
|
|
#ejabberd_commands{name = change_password, tags = [accounts],
|
|
desc = "Change the password on behalf of the given user",
|
|
module = ?MODULE, function = change_password,
|
|
args = [{user, string}, {server, string},
|
|
{newpass, string}],
|
|
result = {res, integer}},
|
|
|
|
%% This command is also implemented in mod_admin_contrib
|
|
#ejabberd_commands{name = set_nickname, tags = [vcard],
|
|
desc = "Define user nickname",
|
|
longdesc = "Set/updated nickname in the user Vcard. "
|
|
"Other informations are unchanged.",
|
|
module = ?MODULE, function = set_nickname,
|
|
args = [{user, string}, {server, string}, {nick,string}],
|
|
result = {res, integer}},
|
|
|
|
%% This command is also implemented in mod_admin_contrib
|
|
#ejabberd_commands{name = add_rosteritem, tags = [roster],
|
|
desc = "Add an entry in a user's roster",
|
|
longdesc = "Some arguments are:\n"
|
|
" - jid: the JabberID of the user you would "
|
|
"like to add in user roster on the server.\n"
|
|
" - subs: the state of the roster item subscription.\n\n"
|
|
"The allowed values of the 'subs' argument are: both, to, from or none.\n"
|
|
" - none: presence packets are not sent between parties.\n"
|
|
" - both: presence packets are sent in both direction.\n"
|
|
" - to: the user sees the presence of the given JID.\n"
|
|
" - from: the JID specified sees the user presence.\n\n"
|
|
"ejabberd sends to the user's connected client both the roster item and the presence."
|
|
"Don't forget that roster items should keep symmetric: "
|
|
"when adding a roster item for a user, "
|
|
"you have to do the symmetric roster item addition.\n\n",
|
|
module = ?MODULE, function = add_rosteritem,
|
|
args = [{user, string}, {server, string}, {jid, string},
|
|
{group, string}, {nick, string}, {subs, string}],
|
|
result = {res, integer}},
|
|
|
|
%% This command is also implemented in mod_admin_contrib
|
|
#ejabberd_commands{name = delete_rosteritem, tags = [roster],
|
|
desc = "Remove a roster item from the user's roster",
|
|
longdesc = "Roster items should be kept symmetric: "
|
|
"when removing a roster item for a user you have to do "
|
|
"the symmetric roster item removal. \n\n"
|
|
"ejabberd sends to the user's connected client both the roster item removel and the presence unsubscription."
|
|
"This mechanism bypass the standard roster approval "
|
|
"addition mechanism and should only be used for server "
|
|
"administration or server integration purpose.",
|
|
module = ?MODULE, function = delete_rosteritem,
|
|
args = [{user, string}, {server, string}, {jid, string}],
|
|
result = {res, integer}},
|
|
|
|
#ejabberd_commands{name = add_rosteritem_groups, tags = [roster],
|
|
desc = "Add new groups in an existing roster item",
|
|
longdesc = "The argument Groups must be a string with group names separated by the character ;",
|
|
module = ?MODULE, function = add_rosteritem_groups,
|
|
args = [{user, string}, {server, string}, {jid, string},
|
|
{groups, string}, {push, string}],
|
|
result = {res, integer}},
|
|
|
|
#ejabberd_commands{name = del_rosteritem_groups, tags = [roster],
|
|
desc = "Delete groups in an existing roster item",
|
|
longdesc = "The argument Groups must be a string with group names separated by the character ;",
|
|
module = ?MODULE, function = del_rosteritem_groups,
|
|
args = [{user, string}, {server, string}, {jid, string},
|
|
{groups, string}, {push, string}],
|
|
result = {res, integer}},
|
|
|
|
#ejabberd_commands{name = modify_rosteritem_groups, tags = [roster],
|
|
desc = "Modify the groups of an existing roster item",
|
|
longdesc = "The argument Groups must be a string with group names separated by the character ;",
|
|
module = ?MODULE, function = modify_rosteritem_groups,
|
|
args = [{user, string}, {server, string}, {jid, string},
|
|
{groups, string}, {subs, string}, {push, string}],
|
|
result = {res, integer}},
|
|
|
|
#ejabberd_commands{name = link_contacts, tags = [roster],
|
|
desc = "Add a symmetrical entry in two users roster",
|
|
longdesc = "jid1 is the JabberID of the user1 you would "
|
|
"like to add in user2 roster on the server.\n"
|
|
"nick1 is the nick of user1.\n"
|
|
"group1 is the group name when adding user1 to user2 roster.\n"
|
|
"jid2 is the JabberID of the user2 you would like to "
|
|
"add in user1 roster on the server.\n"
|
|
"nick2 is the nick of user2.\n"
|
|
"group2 is the group name when adding user2 to user1 roster.\n\n"
|
|
"This mechanism bypasses the standard roster approval "
|
|
"addition mechanism "
|
|
"and should only be userd for server administration or "
|
|
"server integration purpose.",
|
|
module = ?MODULE, function = link_contacts,
|
|
args = [{jid1, string}, {nick1, string}, {group1, string}, {jid2, string}, {nick2, string}, {group2, string}],
|
|
result = {res, integer}},
|
|
|
|
#ejabberd_commands{name = unlink_contacts, tags = [roster],
|
|
desc = "Remove a symmetrical entry in two users roster",
|
|
longdesc = "jid1 is the JabberID of the user1.\n"
|
|
"jid2 is the JabberID of the user2.\n\n"
|
|
"This mechanism bypass the standard roster approval "
|
|
"addition mechanism "
|
|
"and should only be used for server administration or "
|
|
"server integration purpose.",
|
|
module = ?MODULE, function = unlink_contacts,
|
|
args = [{jid1, string}, {jid2, string}],
|
|
result = {res, integer}},
|
|
|
|
%% TODO: test
|
|
%% This command is not supported by ejabberdctl
|
|
#ejabberd_commands{name = add_contacts, tags = [roster],
|
|
desc = "Call add_rosteritem with subscription \"both\" "
|
|
"for a given list of contacts",
|
|
module = ?MODULE, function = add_contacts,
|
|
args = [{user, string},
|
|
{server, string},
|
|
{contacts, {list,
|
|
{contact, {tuple, [
|
|
{jid, string},
|
|
{group, string},
|
|
{nick, string}
|
|
]}}
|
|
}}
|
|
],
|
|
result = {res, integer}},
|
|
%% xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, add_contacts, [{struct,
|
|
%% [{user, "badlop"},
|
|
%% {server, "localhost"},
|
|
%% {contacts, {array, [{struct, [
|
|
%% {contact, {array, [{struct, [
|
|
%% {group, "Friends"},
|
|
%% {jid, "tom@localhost"},
|
|
%% {nick, "Tom"}
|
|
%% ]}]}}
|
|
%% ]}]}}
|
|
%% ]
|
|
%% }]}).
|
|
|
|
%% TODO: test
|
|
%% This command is not supported by ejabberdctl
|
|
#ejabberd_commands{name = remove_contacts, tags = [roster],
|
|
desc = "Call del_rosteritem for a list of contacts",
|
|
module = ?MODULE, function = remove_contacts,
|
|
args = [{user, string},
|
|
{server, string},
|
|
{contacts, {list,
|
|
{jid, string}
|
|
}}
|
|
],
|
|
result = {res, integer}},
|
|
%% xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, remove_contacts, [{struct,
|
|
%% [{user, "badlop"},
|
|
%% {server, "localhost"},
|
|
%% {contacts, {array, [{struct, [
|
|
%% {jid, "tom@localhost"}
|
|
%% ]}]}}
|
|
%% ]
|
|
%% }]}).
|
|
|
|
%% TODO: test
|
|
%% This command is not supported by ejabberdctl
|
|
#ejabberd_commands{name = check_users_registration, tags = [roster],
|
|
desc = "List registration status for a list of users",
|
|
module = ?MODULE, function = check_users_registration,
|
|
args = [{users, {list,
|
|
{auser, {tuple, [
|
|
{user, string},
|
|
{server, string}
|
|
]}}
|
|
}}
|
|
],
|
|
result = {users, {list,
|
|
{auser, {tuple, [
|
|
{user, string},
|
|
{server, string},
|
|
{status, integer}
|
|
]}}
|
|
}}},
|
|
%% xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, check_users_registration, [{struct,
|
|
%% [{users, {array, [{struct, [
|
|
%% {auser, {array, [{struct, [
|
|
%% {user, "badlop"},
|
|
%% {server, "localhost"}
|
|
%% ]}]}}
|
|
%% ]}]}}]
|
|
%% }]}).
|
|
|
|
%% This command is also implemented in mod_admin_contrib
|
|
#ejabberd_commands{name = get_roster, tags = [roster],
|
|
desc = "Retrieve the roster for a given user",
|
|
longdesc = "Returns a list of the contacts in a user "
|
|
"roster.\n\n"
|
|
"Also returns the state of the contact subscription. "
|
|
"Subscription can be either "
|
|
" \"none\", \"from\", \"to\", \"both\". "
|
|
"Pending can be \"in\", \"out\" or \"none\".",
|
|
module = ?MODULE, function = get_roster,
|
|
args = [{user, string}, {server, string}],
|
|
result = {contacts, {list, {contact, {tuple, [{jid, string}, {groups, {list, {group, string}}},
|
|
{nick, string}, {subscription, string}, {pending, string}]}}}}},
|
|
|
|
#ejabberd_commands{name = get_roster_with_presence, tags = [roster],
|
|
desc = "Retrieve the roster for a given user including "
|
|
"presence information",
|
|
longdesc = "The 'show' value contains the user presence. "
|
|
"It can take limited values:\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 defined by the user client.\n\n"
|
|
"Also returns the state of the contact subscription. "
|
|
"Subscription can be either "
|
|
"\"none\", \"from\", \"to\", \"both\". "
|
|
"Pending can be \"in\", \"out\" or \"none\".\n\n"
|
|
"Note: If user is connected several times, only keep the"
|
|
" resource with the highest non-negative priority.",
|
|
module = ?MODULE, function = get_roster_with_presence,
|
|
args = [{user, string}, {server, string}],
|
|
result = {contacts, {list, {contact, {tuple, [{jid, string}, {resource, string}, {group, string}, {nick, string}, {subscription, string}, {pending, string}, {show, string}, {status, string}]}}}}},
|
|
|
|
#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 = "The 'jid' value contains the user jid with "
|
|
"resource.\n"
|
|
"The 'show' value contains the user presence flag. "
|
|
"It can take limited values:\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 defined by the user client.",
|
|
module = ?MODULE, function = get_presence,
|
|
args = [{user, string}, {server, string}],
|
|
result = {presence, {tuple, [{jid, string},
|
|
{show, string},
|
|
{status, string}]}}},
|
|
|
|
#ejabberd_commands{name = get_resources, tags = [session],
|
|
desc = "Get all available resources for a given user",
|
|
module = ?MODULE, function = get_resources,
|
|
args = [{user, string}, {server, string}],
|
|
result = {resources, {list, {resource, string}}}},
|
|
|
|
%% PubSub
|
|
#ejabberd_commands{name = update_status, tags = [pubsub],
|
|
desc = "Update the status on behalf of a user",
|
|
longdesc =
|
|
"jid: the JabberID of the user. Example: user@domain.\n\n"
|
|
"node: the reference of the node to publish on.\n"
|
|
"Example: http://process-one.net/protocol/availability\n\n"
|
|
"itemid: the reference of the item (in our case profile ID).\n\n"
|
|
"payload: the payload of the publish operation in XML.\n"
|
|
"The string has to be properly escaped to comply with XML formalism of XML RPC.",
|
|
module = ?MODULE, function = update_status,
|
|
args = [{jid, string}, {node, string}, {itemid, string}, {payload, string}],
|
|
result = {res, string}},
|
|
|
|
#ejabberd_commands{name = delete_status, tags = [pubsub],
|
|
desc = "Delete the status on behalf of a user",
|
|
longdesc =
|
|
"jid: the JabberID of the user. Example: user@domain.\n\n"
|
|
"node: the reference of the node to publish on.\n"
|
|
"Example: http://process-one.net/protocol/availability\n\n"
|
|
"itemid: the reference of the item (in our case profile ID).",
|
|
module = ?MODULE, function = delete_status,
|
|
args = [{jid, string}, {node, string}, {itemid, string}],
|
|
result = {res, string}},
|
|
|
|
#ejabberd_commands{name = transport_register, tags = [transports],
|
|
desc = "Register a user in a transport",
|
|
module = ?MODULE, function = transport_register,
|
|
args = [{host, string}, {transport, string},
|
|
{jidstring, string}, {username, string}, {password, string}],
|
|
result = {res, string}},
|
|
|
|
%% Similar to mod_admin_contrib send_message which sends a headline
|
|
#ejabberd_commands{name = send_chat, tags = [stanza],
|
|
desc = "Send chat message to a given user",
|
|
module = ?MODULE, function = send_chat,
|
|
args = [{from, string}, {to, string}, {body, string}],
|
|
result = {res, integer}},
|
|
|
|
#ejabberd_commands{name = send_message, tags = [stanza],
|
|
desc = "Send normal message to a given user",
|
|
module = ?MODULE, function = send_message,
|
|
args = [{from, string}, {to, string},
|
|
{subject, string}, {body, string}],
|
|
result = {res, integer}},
|
|
|
|
#ejabberd_commands{name = send_stanza, tags = [stanza],
|
|
desc = "Send stanza to a given user",
|
|
longdesc = "If Stanza contains a \"from\" field, "
|
|
"then it overrides the passed from argument."
|
|
"If Stanza contains a \"to\" field, then it overrides "
|
|
"the passed to argument.",
|
|
module = ?MODULE, function = send_stanza,
|
|
args = [{user, string}, {server, string},
|
|
{stanza, string}],
|
|
result = {res, integer}}
|
|
].
|
|
|
|
|
|
%%%
|
|
%%% Erlang
|
|
%%%
|
|
|
|
restart_module(ModuleString, Host) ->
|
|
Module = list_to_atom(ModuleString),
|
|
List = gen_mod:loaded_modules_with_opts(Host),
|
|
Opts = case lists:keysearch(Module,1, List) of
|
|
{value, {_, O}} -> O;
|
|
_ -> []
|
|
end,
|
|
gen_mod:stop_module(Host, Module),
|
|
code:delete(Module),
|
|
code:purge(Module),
|
|
gen_mod:start_module(Host, Module, Opts),
|
|
ok.
|
|
|
|
|
|
%%%
|
|
%%% Accounts
|
|
%%%
|
|
|
|
create_account(U, S, P) ->
|
|
case ejabberd_auth:try_register(U, S, P) of
|
|
{atomic, ok} ->
|
|
0;
|
|
{atomic, exists} ->
|
|
409;
|
|
_ ->
|
|
1
|
|
end.
|
|
|
|
delete_account(U, S) ->
|
|
Fun = fun() -> ejabberd_auth:remove_user(U, S) end,
|
|
user_action(U, S, Fun, ok).
|
|
|
|
change_password(U, S, P) ->
|
|
Fun = fun() -> ejabberd_auth:set_password(U, S, P) end,
|
|
user_action(U, S, Fun, ok).
|
|
|
|
rename_account(U, S, NU, NS) ->
|
|
case ejabberd_auth:is_user_exists(U, S) of
|
|
true ->
|
|
case ejabberd_auth:get_password(U, S) of
|
|
false ->
|
|
1;
|
|
Password ->
|
|
case ejabberd_auth:try_register(NU, NS, Password) of
|
|
{atomic, ok} ->
|
|
OldJID = jlib:jid_to_string({U, S, ""}),
|
|
NewJID = jlib:jid_to_string({NU, NS, ""}),
|
|
Roster = get_roster2(U, S),
|
|
lists:foreach(fun(#roster{jid={RU, RS, RE}, name=Nick, groups=Groups}) ->
|
|
NewGroup = extract_group(Groups),
|
|
{NewNick, Group} = case lists:filter(fun(#roster{jid={PU, PS, _}}) ->
|
|
(PU == U) and (PS == S)
|
|
end, get_roster2(RU, RS)) of
|
|
[#roster{name=OldNick, groups=OldGroups}|_] -> {OldNick, extract_group(OldGroups)};
|
|
[] -> {NU, []}
|
|
end,
|
|
JIDStr = jlib:jid_to_string({RU, RS, RE}),
|
|
link_contacts2(NewJID, NewNick, NewGroup, JIDStr, Nick, Group, true),
|
|
unlink_contacts2(OldJID, JIDStr, true)
|
|
end, Roster),
|
|
ejabberd_auth:remove_user(U, S),
|
|
0;
|
|
{atomic, exists} ->
|
|
409;
|
|
_ ->
|
|
1
|
|
end
|
|
end;
|
|
false ->
|
|
404
|
|
end.
|
|
|
|
|
|
%%%
|
|
%%% Sessions
|
|
%%%
|
|
|
|
get_presence(U, S) ->
|
|
case ejabberd_auth:is_user_exists(U, S) of
|
|
true ->
|
|
{Resource, Show, Status} = get_presence2(U, S),
|
|
FullJID = case Resource of
|
|
[] ->
|
|
lists:flatten([U,"@",S]);
|
|
_ ->
|
|
lists:flatten([U,"@",S,"/",Resource])
|
|
end,
|
|
{FullJID, Show, Status};
|
|
false ->
|
|
404
|
|
end.
|
|
|
|
get_resources(U, S) ->
|
|
case ejabberd_auth:is_user_exists(U, S) of
|
|
true ->
|
|
get_resources2(U, S);
|
|
false ->
|
|
404
|
|
end.
|
|
|
|
|
|
%%%
|
|
%%% Vcard
|
|
%%%
|
|
|
|
set_nickname(U, S, N) ->
|
|
Fun = fun() -> case mod_vcard:process_sm_iq(
|
|
{jid, U, S, "", U, S, ""},
|
|
{jid, U, S, "", U, S, ""},
|
|
{iq, "", set, "", "en",
|
|
{xmlelement, "vCard",
|
|
[{"xmlns", "vcard-temp"}], [
|
|
{xmlelement, "NICKNAME", [], [{xmlcdata, N}]}
|
|
]
|
|
}}) of
|
|
{iq, [], result, [], _, []} -> ok;
|
|
_ -> error
|
|
end
|
|
end,
|
|
user_action(U, S, Fun, ok).
|
|
|
|
|
|
%%%
|
|
%%% Roster
|
|
%%%
|
|
|
|
add_rosteritem(U, S, JID, G, N, Subs) ->
|
|
add_rosteritem(U, S, JID, G, N, Subs, true).
|
|
|
|
add_rosteritem(U, S, JID, G, N, Subs, Push) ->
|
|
Fun = fun() -> add_rosteritem2(U, S, JID, N, G, Subs, Push) end,
|
|
user_action(U, S, Fun, {atomic, ok}).
|
|
|
|
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2) ->
|
|
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2, true).
|
|
|
|
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2, Push) ->
|
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
|
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
|
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
|
|
{true, true} ->
|
|
case link_contacts2(JID1, Nick1, Group1, JID2, Nick2, Group2, Push) of
|
|
{atomic, ok} ->
|
|
0;
|
|
_ ->
|
|
1
|
|
end;
|
|
_ ->
|
|
404
|
|
end.
|
|
|
|
delete_rosteritem(U, S, JID) ->
|
|
Fun = fun() -> del_rosteritem(U, S, JID) end,
|
|
user_action(U, S, Fun, {atomic, ok}).
|
|
|
|
unlink_contacts(JID1, JID2) ->
|
|
unlink_contacts(JID1, JID2, true).
|
|
|
|
unlink_contacts(JID1, JID2, Push) ->
|
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
|
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
|
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
|
|
{true, true} ->
|
|
case unlink_contacts2(JID1, JID2, Push) of
|
|
{atomic, ok} ->
|
|
0;
|
|
_ ->
|
|
1
|
|
end;
|
|
_ ->
|
|
404
|
|
end.
|
|
|
|
get_roster(U, S) ->
|
|
case ejabberd_auth:is_user_exists(U, S) of
|
|
true ->
|
|
format_roster(get_roster2(U, S));
|
|
false ->
|
|
404
|
|
end.
|
|
|
|
get_roster_with_presence(U, S) ->
|
|
case ejabberd_auth:is_user_exists(U, S) of
|
|
true ->
|
|
format_roster_with_presence(get_roster2(U, S));
|
|
false ->
|
|
404
|
|
end.
|
|
|
|
add_contacts(U, S, Contacts) ->
|
|
case ejabberd_auth:is_user_exists(U, S) of
|
|
true ->
|
|
JID1 = jlib:jid_to_string({U, S, ""}),
|
|
lists:foldl(fun({JID2, Group, Nick}, Acc) ->
|
|
{PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
|
case ejabberd_auth:is_user_exists(PU, PS) of
|
|
true ->
|
|
case link_contacts2(JID1, "", Group, JID2, Nick, Group, true) of
|
|
{atomic, ok} -> Acc;
|
|
_ -> 1
|
|
end;
|
|
false ->
|
|
Acc
|
|
end
|
|
end, 0, Contacts);
|
|
false ->
|
|
404
|
|
end.
|
|
|
|
remove_contacts(U, S, Contacts) ->
|
|
case ejabberd_auth:is_user_exists(U, S) of
|
|
true ->
|
|
JID1 = jlib:jid_to_string({U, S, ""}),
|
|
lists:foldl(fun(JID2, Acc) ->
|
|
{PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
|
case ejabberd_auth:is_user_exists(PU, PS) of
|
|
true ->
|
|
case unlink_contacts2(JID1, JID2, true) of
|
|
{atomic, ok} -> Acc;
|
|
_ -> 1
|
|
end;
|
|
false ->
|
|
Acc
|
|
end
|
|
end, 0, Contacts);
|
|
false ->
|
|
404
|
|
end.
|
|
|
|
check_users_registration(Users) ->
|
|
lists:map(fun({U, S}) ->
|
|
Registered = case ejabberd_auth:is_user_exists(U, S) of
|
|
true -> 1;
|
|
false -> 0
|
|
end,
|
|
{U, S, Registered}
|
|
end, Users).
|
|
|
|
%%%
|
|
%%% Groups of Roster Item
|
|
%%%
|
|
|
|
add_rosteritem_groups(User, Server, JID, NewGroupsString, PushString) ->
|
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
|
NewGroups = string:tokens(NewGroupsString, ";"),
|
|
Push = list_to_atom(PushString),
|
|
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(User, Server)} of
|
|
{true, true} ->
|
|
case add_rosteritem_groups2(User, Server, JID, NewGroups, Push) of
|
|
ok ->
|
|
0;
|
|
Error ->
|
|
?INFO_MSG("Error found: ~n~p", [Error]),
|
|
1
|
|
end;
|
|
_ ->
|
|
404
|
|
end.
|
|
|
|
del_rosteritem_groups(User, Server, JID, NewGroupsString, PushString) ->
|
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
|
NewGroups = string:tokens(NewGroupsString, ";"),
|
|
Push = list_to_atom(PushString),
|
|
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(User, Server)} of
|
|
{true, true} ->
|
|
case del_rosteritem_groups2(User, Server, JID, NewGroups, Push) of
|
|
ok ->
|
|
0;
|
|
Error ->
|
|
?INFO_MSG("Error found: ~n~p", [Error]),
|
|
1
|
|
end;
|
|
_ ->
|
|
404
|
|
end.
|
|
|
|
modify_rosteritem_groups(User, Server, JID, NewGroupsString, SubsString, PushString) ->
|
|
Nick = "", %% That information will not be used, anyway
|
|
Subs = list_to_atom(SubsString),
|
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
|
NewGroups = string:tokens(NewGroupsString, ";"),
|
|
Push = list_to_atom(PushString),
|
|
case ejabberd_auth:is_user_exists(User, Server) of
|
|
true ->
|
|
case modify_rosteritem_groups2(User, Server, JID, NewGroups, Push, Nick, Subs) of
|
|
ok ->
|
|
0;
|
|
Error ->
|
|
?INFO_MSG("Error found: ~n~p", [Error]),
|
|
1
|
|
end;
|
|
_ ->
|
|
404
|
|
end.
|
|
|
|
add_rosteritem_groups2(User, Server, JID, NewGroups, Push) ->
|
|
GroupsFun =
|
|
fun(Groups) ->
|
|
lists:usort(NewGroups ++ Groups)
|
|
end,
|
|
change_rosteritem_group(User, Server, JID, GroupsFun, Push).
|
|
|
|
del_rosteritem_groups2(User, Server, JID, NewGroups, Push) ->
|
|
GroupsFun =
|
|
fun(Groups) ->
|
|
Groups -- NewGroups
|
|
end,
|
|
change_rosteritem_group(User, Server, JID, GroupsFun, Push).
|
|
|
|
modify_rosteritem_groups2(User, Server, JID2, NewGroups, Push, Nick, Subs) when NewGroups == [] ->
|
|
JID1 = jlib:jid_to_string(jlib:make_jid(User, Server, "")),
|
|
case unlink_contacts(JID1, JID2) of
|
|
{atomic, ok} ->
|
|
ok;
|
|
Error ->
|
|
Error
|
|
end;
|
|
modify_rosteritem_groups2(User, Server, JID, NewGroups, Push, Nick, Subs) ->
|
|
GroupsFun =
|
|
fun(_Groups) ->
|
|
NewGroups
|
|
end,
|
|
change_rosteritem_group(User, Server, JID, GroupsFun, Push, NewGroups, Nick, Subs).
|
|
|
|
change_rosteritem_group(User, Server, JID, GroupsFun, Push) ->
|
|
change_rosteritem_group(User, Server, JID, GroupsFun, Push, [], "", "both").
|
|
|
|
change_rosteritem_group(User, Server, JID, GroupsFun, Push, NewGroups, Nick, Subs) ->
|
|
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
|
LJID = {RU,RS,[]},
|
|
LUser = jlib:nodeprep(User),
|
|
LServer = jlib:nameprep(Server),
|
|
Result =
|
|
case roster_backend(LServer) of
|
|
mnesia ->
|
|
mnesia:transaction(
|
|
fun() ->
|
|
case mnesia:read({roster, {LUser, LServer, LJID}}) of
|
|
[#roster{} = Roster] ->
|
|
NewGroups2 = GroupsFun(Roster#roster.groups),
|
|
NewRoster = Roster#roster{groups = NewGroups2},
|
|
mnesia:write(NewRoster),
|
|
{ok, NewRoster#roster.name,
|
|
NewRoster#roster.subscription,
|
|
NewGroups2};
|
|
_ ->
|
|
not_in_roster
|
|
end
|
|
end);
|
|
odbc ->
|
|
ejabberd_odbc:sql_transaction(
|
|
LServer,
|
|
fun() ->
|
|
Username = ejabberd_odbc:escape(User),
|
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
|
case ejabberd_odbc:sql_query_t(
|
|
["select nick, subscription from rosterusers "
|
|
" where username='", Username, "' "
|
|
" and jid='", SJID, "';"]) of
|
|
{selected, ["nick", "subscription"],
|
|
[{Name, SSubscription}]} ->
|
|
Subscription =
|
|
case SSubscription of
|
|
"B" -> both;
|
|
"T" -> to;
|
|
"F" -> from;
|
|
_ -> none
|
|
end,
|
|
Groups =
|
|
case odbc_queries:get_roster_groups(
|
|
LServer, Username, SJID) of
|
|
{selected, ["grp"], JGrps}
|
|
when is_list(JGrps) ->
|
|
[JGrp || {JGrp} <- JGrps];
|
|
_ ->
|
|
[]
|
|
end,
|
|
NewGroups2 = GroupsFun(Groups),
|
|
ejabberd_odbc:sql_query_t(
|
|
["delete from rostergroups "
|
|
" where username='", Username, "' "
|
|
" and jid='", SJID, "';"]),
|
|
lists:foreach(
|
|
fun(Group) ->
|
|
ejabberd_odbc:sql_query_t(
|
|
["insert into rostergroups("
|
|
" username, jid, grp) "
|
|
" values ('", Username, "',"
|
|
"'", SJID, "',"
|
|
"'", ejabberd_odbc:escape(Group), "');"])
|
|
end,
|
|
NewGroups2),
|
|
{ok, Name, Subscription, NewGroups2};
|
|
_ ->
|
|
not_in_roster
|
|
end
|
|
end);
|
|
none ->
|
|
%% Apollo change: force roster push anyway with success
|
|
{atomic, {ok, Nick, Subs, NewGroups}}
|
|
end,
|
|
case {Result, Push} of
|
|
{{atomic, {ok, Name, Subscription, NewGroups3}}, true} ->
|
|
roster_push(User, Server, JID,
|
|
Name, atom_to_list(Subscription), NewGroups3),
|
|
ok;
|
|
{{atomic, {ok, _Name, _Subscription, _NewGroups3}}, false} -> ok;
|
|
{{atomic, not_in_roster}, _} -> not_in_roster;
|
|
Error -> {error, Error}
|
|
end.
|
|
|
|
%%%
|
|
%%% PubSub
|
|
%%%
|
|
|
|
update_status(JidString, NodeString, Itemid, PayloadString) ->
|
|
Publisher = jlib:string_to_jid(JidString),
|
|
Host = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
|
|
ServerHost = Publisher#jid.lserver,
|
|
Node = mod_pubsub_on:string_to_node(NodeString),
|
|
Payload = [xml_stream:parse_element(PayloadString)],
|
|
?DEBUG("PayloadString: ~n~p~nPayload elements: ~n~p", [PayloadString, Payload]),
|
|
case mod_pubsub_on:publish_item_nothook(Host, ServerHost, Node, Publisher, Itemid, Payload) of
|
|
{result, _} ->
|
|
"OK";
|
|
{error, {xmlelement, _, _, _} = XmlEl} ->
|
|
"ERROR: " ++ xml:element_to_string(XmlEl);
|
|
{error, ErrorAtom} when is_atom(ErrorAtom) ->
|
|
"ERROR: " ++ atom_to_list(ErrorAtom);
|
|
{error, ErrorString} when is_list(ErrorString) ->
|
|
"ERROR: " ++ ErrorString
|
|
end.
|
|
|
|
delete_status(JidString, NodeString, Itemid) ->
|
|
Publisher = jlib:string_to_jid(JidString),
|
|
Host = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
|
|
Node = mod_pubsub_on:string_to_node(NodeString),
|
|
case mod_pubsub_on:delete_item_nothook(Host, Node, Publisher, Itemid, true) of
|
|
{result, _} ->
|
|
"OK";
|
|
{error, {xmlelement, _, _, _} = XmlEl} ->
|
|
"ERROR: " ++ xml:element_to_string(XmlEl);
|
|
{error, ErrorAtom} when is_atom(ErrorAtom) ->
|
|
"ERROR: " ++ atom_to_list(ErrorAtom);
|
|
{error, ErrorString} when is_list(ErrorString) ->
|
|
"ERROR: " ++ ErrorString
|
|
end.
|
|
|
|
transport_register(Host, TransportString, JIDString, Username, Password) ->
|
|
TransportAtom = list_to_atom(TransportString),
|
|
case {lists:member(Host, ?MYHOSTS), jlib:string_to_jid(JIDString)} of
|
|
{true, JID} when is_record(JID, jid) ->
|
|
case catch gen_transport:register(Host, TransportAtom, JIDString,
|
|
Username, Password) of
|
|
ok ->
|
|
"OK";
|
|
{error, Reason} ->
|
|
"ERROR: " ++ atom_to_list(Reason);
|
|
{'EXIT', {timeout,_}} ->
|
|
"ERROR: timed_out";
|
|
{'EXIT', _} ->
|
|
"ERROR: unexpected_error"
|
|
end;
|
|
{false, _} ->
|
|
"ERROR: unknown_host";
|
|
_ ->
|
|
"ERROR: bad_jid"
|
|
end.
|
|
|
|
%%%
|
|
%%% Stanza
|
|
%%%
|
|
|
|
send_chat(FromJID, ToJID, Msg) ->
|
|
From = jlib:string_to_jid(FromJID),
|
|
To = jlib:string_to_jid(ToJID),
|
|
Stanza = {xmlelement, "message", [{"type", "chat"}],
|
|
[{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
|
|
ejabberd_router:route(From, To, Stanza),
|
|
0.
|
|
|
|
send_message(FromJID, ToJID, Sub, Msg) ->
|
|
From = jlib:string_to_jid(FromJID),
|
|
To = jlib:string_to_jid(ToJID),
|
|
Stanza = {xmlelement, "message", [{"type", "normal"}],
|
|
[{xmlelement, "subject", [], [{xmlcdata, Sub}]},
|
|
{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
|
|
ejabberd_router:route(From, To, Stanza),
|
|
0.
|
|
|
|
send_stanza(FromJID, ToJID, StanzaStr) ->
|
|
case xml_stream:parse_element(StanzaStr) of
|
|
{error, _} ->
|
|
1;
|
|
Stanza ->
|
|
{xmlelement, _, Attrs, _} = Stanza,
|
|
From = jlib:string_to_jid(proplists:get_value("from", Attrs, FromJID)),
|
|
To = jlib:string_to_jid(proplists:get_value("to", Attrs, ToJID)),
|
|
ejabberd_router:route(From, To, Stanza),
|
|
0
|
|
end.
|
|
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
%%% Internal functions
|
|
|
|
%% -----------------------------
|
|
%% Internal roster handling
|
|
%% -----------------------------
|
|
|
|
get_roster2(User, Server) ->
|
|
LUser = jlib:nodeprep(User),
|
|
LServer = jlib:nameprep(Server),
|
|
case roster_backend(LServer) of
|
|
mnesia -> mod_roster:get_user_roster([], {LUser, LServer});
|
|
odbc -> mod_roster_odbc:get_user_roster([], {LUser, LServer})
|
|
end.
|
|
|
|
add_rosteritem2(User, Server, JID, Nick, Group, Subscription, Push) ->
|
|
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
|
LJID = {RU,RS,[]},
|
|
Groups = case Group of
|
|
[] -> [];
|
|
_ -> [Group]
|
|
end,
|
|
Roster = #roster{
|
|
usj = {User,Server,LJID},
|
|
us = {User,Server},
|
|
jid = LJID,
|
|
name = Nick,
|
|
ask = none,
|
|
subscription = list_to_atom(Subscription),
|
|
groups = Groups},
|
|
Result =
|
|
case roster_backend(Server) of
|
|
mnesia ->
|
|
mnesia:transaction(fun() ->
|
|
case mnesia:read({roster,{User,Server,LJID}}) of
|
|
[#roster{subscription=both}] ->
|
|
already_added;
|
|
_ ->
|
|
mnesia:write(Roster)
|
|
end
|
|
end);
|
|
odbc ->
|
|
%% MREMOND: TODO: check if already_added
|
|
case ejabberd_odbc:sql_transaction(Server,
|
|
fun() ->
|
|
Username = ejabberd_odbc:escape(User),
|
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
|
case ejabberd_odbc:sql_query_t(
|
|
["select username from rosterusers "
|
|
" where username='", Username, "' "
|
|
" and jid='", SJID,
|
|
"' and subscription = 'B';"]) of
|
|
{selected, ["username"],[]} ->
|
|
ItemVals = record_to_string(Roster),
|
|
ItemGroups = groups_to_string(Roster),
|
|
ejabberd_odbc:sql_query_t(
|
|
odbc_queries:update_roster_sql(
|
|
Username, SJID, ItemVals, ItemGroups));
|
|
_ ->
|
|
already_added
|
|
end
|
|
end) of
|
|
{atomic, already_added} -> {atomic, already_added};
|
|
{atomic, _} -> {atomic, ok};
|
|
Error -> Error
|
|
end;
|
|
none ->
|
|
%% If no known mod_roster is enabled, still let the code to proceed
|
|
{atomic, ok}
|
|
end,
|
|
case {Result, Push} of
|
|
{{atomic, already_added}, _} -> ok; %% No need for roster push
|
|
{{atomic, ok}, true} -> roster_push(User, Server, JID, Nick, Subscription, Groups);
|
|
{{atomic, ok}, false} -> ok;
|
|
_ -> error
|
|
end,
|
|
Result.
|
|
|
|
del_rosteritem(User, Server, JID) ->
|
|
del_rosteritem(User, Server, JID, true).
|
|
|
|
del_rosteritem(User, Server, JID, Push) ->
|
|
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
|
LJID = {RU,RS,[]},
|
|
Result = case roster_backend(Server) of
|
|
mnesia ->
|
|
mnesia:transaction(fun() ->
|
|
mnesia:delete({roster, {User,Server,LJID}})
|
|
end);
|
|
odbc ->
|
|
case ejabberd_odbc:sql_transaction(Server, fun() ->
|
|
Username = ejabberd_odbc:escape(User),
|
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
|
odbc_queries:del_roster(Server, Username, SJID)
|
|
end) of
|
|
{atomic, _} -> {atomic, ok};
|
|
Error -> Error
|
|
end;
|
|
none ->
|
|
%% If no known mod_roster is enabled, still let the code to proceed
|
|
{atomic, ok}
|
|
end,
|
|
case {Result, Push} of
|
|
{{atomic, ok}, true} -> roster_push(User, Server, JID, "", "remove", []);
|
|
{{atomic, ok}, false} -> ok;
|
|
_ -> error
|
|
end,
|
|
Result.
|
|
|
|
link_contacts2(JID1, Nick1, Group1, JID2, Nick2, Group2, Push) ->
|
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
|
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
|
case add_rosteritem2(U1, S1, JID2, Nick2, Group1, "both", Push) of
|
|
{atomic, ok} -> add_rosteritem2(U2, S2, JID1, Nick1, Group2, "both", Push);
|
|
Error -> Error
|
|
end.
|
|
|
|
unlink_contacts2(JID1, JID2, Push) ->
|
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
|
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
|
case del_rosteritem(U1, S1, JID2, Push) of
|
|
{atomic, ok} -> del_rosteritem(U2, S2, JID1, Push);
|
|
Error -> Error
|
|
end.
|
|
|
|
roster_push(User, Server, JID, Nick, Subscription, Groups) ->
|
|
LJID = jlib:make_jid(User, Server, ""),
|
|
TJID = jlib:string_to_jid(JID),
|
|
{TU, TS, _} = jlib:jid_tolower(TJID),
|
|
Presence =
|
|
{xmlelement, "presence",
|
|
[{"type",
|
|
case Subscription of
|
|
"remove" -> "unsubscribed";
|
|
"none" -> "unsubscribe";
|
|
"both" -> "subscribed";
|
|
_ -> "subscribe"
|
|
end}], []},
|
|
ItemAttrs =
|
|
case Nick of
|
|
"" -> [{"jid", JID}, {"subscription", Subscription}];
|
|
_ -> [{"jid", JID}, {"name", Nick}, {"subscription", Subscription}]
|
|
end,
|
|
ItemGroups =
|
|
lists:map(fun(G) ->
|
|
{xmlelement, "group", [], [{xmlcdata, G}]}
|
|
end, Groups),
|
|
Result =
|
|
jlib:iq_to_xml(
|
|
#iq{type = set, xmlns = ?NS_ROSTER, id = "push",
|
|
lang = "langxmlrpc-en",
|
|
sub_el = [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}],
|
|
[{xmlelement, "item", ItemAttrs, ItemGroups}]}]}),
|
|
%% ejabberd_router:route(TJID, LJID, Presence),
|
|
%% ejabberd_router:route(LJID, LJID, Result),
|
|
lists:foreach(
|
|
fun(Resource) ->
|
|
UJID = jlib:make_jid(User, Server, Resource),
|
|
ejabberd_router:route(TJID, UJID, Presence),
|
|
ejabberd_router:route(UJID, UJID, Result),
|
|
case Subscription of
|
|
"remove" -> none;
|
|
_ ->
|
|
lists:foreach(
|
|
fun(TR) ->
|
|
ejabberd_router:route(
|
|
jlib:make_jid(TU, TS, TR), UJID,
|
|
{xmlelement, "presence", [], []})
|
|
end, get_resources(TU, TS))
|
|
end
|
|
end, [R || R <- get_resources(User, Server)]).
|
|
|
|
roster_backend(Server) ->
|
|
Modules = gen_mod:loaded_modules(Server),
|
|
Mnesia = lists:member(mod_roster, Modules),
|
|
Odbc = lists:member(mod_roster_odbc, Modules),
|
|
if Mnesia -> mnesia;
|
|
true ->
|
|
if Odbc -> odbc;
|
|
true -> none
|
|
end
|
|
end.
|
|
|
|
record_to_string(#roster{us = {User, _Server},
|
|
jid = JID,
|
|
name = Name,
|
|
subscription = Subscription,
|
|
ask = Ask,
|
|
askmessage = AskMessage}) ->
|
|
Username = ejabberd_odbc:escape(User),
|
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
|
|
Nick = ejabberd_odbc:escape(Name),
|
|
SSubscription = case Subscription of
|
|
both -> "B";
|
|
to -> "T";
|
|
from -> "F";
|
|
none -> "N"
|
|
end,
|
|
SAsk = case Ask of
|
|
subscribe -> "S";
|
|
unsubscribe -> "U";
|
|
both -> "B";
|
|
out -> "O";
|
|
in -> "I";
|
|
none -> "N"
|
|
end,
|
|
SAskMessage = ejabberd_odbc:escape(AskMessage),
|
|
["'", Username, "',"
|
|
"'", SJID, "',"
|
|
"'", Nick, "',"
|
|
"'", SSubscription, "',"
|
|
"'", SAsk, "',"
|
|
"'", SAskMessage, "',"
|
|
"'N', '', 'item'"].
|
|
|
|
groups_to_string(#roster{us = {User, _Server},
|
|
jid = JID,
|
|
groups = Groups}) ->
|
|
Username = ejabberd_odbc:escape(User),
|
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
|
|
%% Empty groups do not need to be converted to string to be inserted in
|
|
%% the database
|
|
lists:foldl(fun([], Acc) -> Acc;
|
|
(Group, Acc) ->
|
|
String = ["'", Username, "',"
|
|
"'", SJID, "',"
|
|
"'", ejabberd_odbc:escape(Group), "'"],
|
|
[String|Acc]
|
|
end, [], Groups).
|
|
|
|
%% Format roster items as a list of:
|
|
%% [{struct, [{jid, "test@localhost"},{group, "Friends"},{nick, "Nicktest"}]}]
|
|
format_roster([]) ->
|
|
[];
|
|
format_roster(Items) ->
|
|
format_roster(Items, []).
|
|
format_roster([], Structs) ->
|
|
Structs;
|
|
format_roster([#roster{jid=JID, name=Nick, groups=Group,
|
|
subscription=Subs, ask=Ask}|Items], Structs) ->
|
|
{User,Server,_Resource} = JID,
|
|
Struct = {lists:flatten([User,"@",Server]),
|
|
Group,
|
|
Nick,
|
|
atom_to_list(Subs),
|
|
atom_to_list(Ask)
|
|
},
|
|
format_roster(Items, [Struct|Structs]).
|
|
|
|
%% Note: If user is connected several times, only keep the resource with the
|
|
%% highest non-negative priority
|
|
format_roster_with_presence([]) ->
|
|
[];
|
|
format_roster_with_presence(Items) ->
|
|
format_roster_with_presence(Items, []).
|
|
format_roster_with_presence([], Structs) ->
|
|
Structs;
|
|
format_roster_with_presence([#roster{jid=JID, name=Nick, groups=Group,
|
|
subscription=Subs, ask=Ask}|Items], Structs) ->
|
|
{User,Server,_R} = JID,
|
|
Presence = case Subs of
|
|
both -> get_presence2(User, Server);
|
|
from -> get_presence2(User, Server);
|
|
_Other -> {"", "unavailable", ""}
|
|
end,
|
|
{Resource, Show, Status} =
|
|
case Presence of
|
|
{_R, "invisible", _S} -> {"", "unavailable", ""};
|
|
_Status -> Presence
|
|
end,
|
|
Struct = {lists:flatten([User,"@",Server]),
|
|
Resource,
|
|
extract_group(Group),
|
|
Nick,
|
|
atom_to_list(Subs),
|
|
atom_to_list(Ask),
|
|
Show,
|
|
Status
|
|
},
|
|
format_roster_with_presence(Items, [Struct|Structs]).
|
|
|
|
extract_group([]) -> [];
|
|
%extract_group([Group|_Groups]) -> Group.
|
|
extract_group(Groups) -> string:join(Groups, ";").
|
|
|
|
extract_groups([]) -> [];
|
|
%extract_groups([Group|_Groups]) -> Group.
|
|
extract_groups(Groups) -> {list, Groups}.
|
|
|
|
%% -----------------------------
|
|
%% Internal session handling
|
|
%% -----------------------------
|
|
|
|
%% This is inspired from ejabberd_sm.erl
|
|
get_presence2(User, Server) ->
|
|
case get_sessions(User, Server) of
|
|
[] ->
|
|
{"", "unavailable", ""};
|
|
Ss ->
|
|
Session = hd(Ss),
|
|
if Session#session.priority >= 0 ->
|
|
Pid = element(2, Session#session.sid),
|
|
%{_User, _Resource, Show, Status} = rpc:call(node(Pid), ejabberd_c2s, get_presence, [Pid]),
|
|
{_User, Resource, Show, Status} = ejabberd_c2s:get_presence(Pid),
|
|
{Resource, Show, Status};
|
|
true ->
|
|
{"", "unavailable", ""}
|
|
end
|
|
end.
|
|
|
|
get_resources2(User, Server) ->
|
|
lists:map(fun(S) -> element(3, S#session.usr)
|
|
end, get_sessions(User, Server)).
|
|
|
|
get_sessions(User, Server) ->
|
|
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
|
|
Node = ejabberd_cluster:get_node(US),
|
|
case catch rpc:call(Node, mnesia, dirty_index_read,
|
|
[session, US, #session.us], 5000) of
|
|
Result when is_list(Result), Result /= [] ->
|
|
lists:reverse(lists:keysort(#session.priority, clean_session_list(Result)));
|
|
_ ->
|
|
[]
|
|
end.
|
|
|
|
clean_session_list(Ss) ->
|
|
clean_session_list(lists:keysort(#session.usr, Ss), []).
|
|
|
|
clean_session_list([], Res) ->
|
|
Res;
|
|
clean_session_list([S], Res) ->
|
|
[S | Res];
|
|
clean_session_list([S1, S2 | Rest], Res) ->
|
|
if
|
|
S1#session.usr == S2#session.usr ->
|
|
if
|
|
S1#session.sid > S2#session.sid ->
|
|
clean_session_list([S1 | Rest], Res);
|
|
true ->
|
|
clean_session_list([S2 | Rest], Res)
|
|
end;
|
|
true ->
|
|
clean_session_list([S2 | Rest], [S1 | Res])
|
|
end.
|
|
|
|
|
|
%% -----------------------------
|
|
%% Internal function pattern
|
|
%% -----------------------------
|
|
|
|
user_action(User, Server, Fun, OK) ->
|
|
case ejabberd_auth:is_user_exists(User, Server) of
|
|
true ->
|
|
case catch Fun() of
|
|
OK ->
|
|
0;
|
|
_ ->
|
|
1
|
|
end;
|
|
false ->
|
|
404
|
|
end.
|