mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-08 15:42:29 +01:00
1167 lines
37 KiB
Erlang
1167 lines
37 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-2008 ProcessOne
|
|
%%%
|
|
%%% This program is free software; you can redistribute it and/or
|
|
%%% modify it under the terms of the GNU General Public License as
|
|
%%% published by the Free Software Foundation; either version 2 of the
|
|
%%% License, or (at your option) any later version.
|
|
%%%
|
|
%%% This program is distributed in the hope that it will be useful,
|
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
%%% General Public License for more details.
|
|
%%%
|
|
%%% You should have received a copy of the GNU General Public License
|
|
%%% along with this program; if not, write to the Free Software
|
|
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
%%% 02111-1307 USA
|
|
%%%
|
|
%%%-------------------------------------------------------------------
|
|
|
|
%%% @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,
|
|
link_contacts/4,
|
|
unlink_contacts/2,
|
|
link_contacts/5, 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_notification/6, %% ON
|
|
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. "
|
|
"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"
|
|
"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 an entry for a user 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"
|
|
"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 = 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"
|
|
"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\n"
|
|
"This mechanism bypass 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}, {jid2, string}, {nick2, 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 userd 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}, {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_notification, tags = [stanza],
|
|
desc = "Send ON notification to XMPP client sessions",
|
|
module = ?MODULE, function = send_notification,
|
|
args = [{send_from, string}, {send_to, string}, {host, string},
|
|
{unread_items, string}, {message, string},
|
|
{type, 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, JID2, Nick2) ->
|
|
link_contacts(JID1, Nick1, JID2, Nick2, true).
|
|
|
|
link_contacts(JID1, Nick1, JID2, Nick2, 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, JID2, Nick2, 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).
|
|
|
|
|
|
%%%
|
|
%%% 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_notification(SendFromUsername, SendToUsername, Host, UnreadItemsInteger, MessageBody, Type) ->
|
|
case get_resources(SendToUsername, Host) of
|
|
404 ->
|
|
-1;
|
|
[] ->
|
|
-2;
|
|
[A|_] when is_list(A) ->
|
|
send_notification_really(SendFromUsername, SendToUsername, Host, UnreadItemsInteger, MessageBody, Type),
|
|
0
|
|
end.
|
|
|
|
send_notification_really(SendFromUsername, SendToUsername, Host, UnreadItemsInteger, MessageBody, Type) ->
|
|
FromString = Host ++ "/voicemail-notifier",
|
|
ToString = SendToUsername ++ "@" ++ Host,
|
|
|
|
XAttrs = [{"type", Type},
|
|
{"send_from", SendFromUsername},
|
|
{"unread_items", UnreadItemsInteger}],
|
|
XChildren = [{xmlelement, "text", [], [{xmlcdata, MessageBody}]}],
|
|
XEl = {xmlelement, "x", XAttrs, XChildren},
|
|
|
|
Attrs = [{"from", FromString}, {"to", ToString}, {"type", "chat"}],
|
|
Children = [XEl],
|
|
Stanza = {xmlelement, "message", Attrs, Children},
|
|
|
|
From = jlib:string_to_jid(FromString),
|
|
To = jlib:string_to_jid(ToString),
|
|
ejabberd_router:route(From, To, Stanza).
|
|
|
|
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),
|
|
odbc_queries:update_roster(Server, Username,
|
|
SJID, ItemVals,
|
|
ItemGroups);
|
|
_ ->
|
|
already_added
|
|
end
|
|
end) of
|
|
{atomic, already_added} -> {atomic, already_added};
|
|
{atomic, _} -> {atomic, ok};
|
|
Error -> Error
|
|
end
|
|
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
|
|
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, JID2, Nick2, Push) ->
|
|
link_contacts2(JID1, Nick1, [], JID2, Nick2, [], Push).
|
|
|
|
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),
|
|
|
|
%% TODO: Problem: We assume that both user are local. More test
|
|
%% are needed to check if the JID is remote or not:
|
|
|
|
%% TODO: We need to probe user2 especially, if it is not local.
|
|
%% As a quick fix, I do not go for the probe solution however, because all users
|
|
%% are local
|
|
case Subscription of
|
|
"to" -> %% Probe second user to route his presence to modified user
|
|
%% TODO: For now we assume both user are local so we do not, but we need to move to probe.
|
|
set_roster(User, Server, TJID, Nick, Subscription, Groups);
|
|
"from" ->
|
|
%% Send roster updates
|
|
set_roster(User, Server, TJID, Nick, Subscription, Groups);
|
|
"both" ->
|
|
%% Update both presence
|
|
set_roster(User, Server, TJID, Nick, Subscription, Groups),
|
|
UJID = jlib:make_jid(User, Server, ""),
|
|
set_roster(TU, TS, UJID, Nick, Subscription, Groups);
|
|
_ ->
|
|
%% Remove subscription
|
|
set_roster(User, Server, TJID, Nick, "none", Groups)
|
|
end.
|
|
|
|
|
|
set_roster(User, Server, TJID, Nick, Subscription, Groups) ->
|
|
GroupsXML = [{xmlelement, "group", [], [{xmlcdata, GroupString}]} || GroupString <- Groups],
|
|
Item = case Nick of
|
|
"" -> [{"jid", jlib:jid_to_string(TJID)}, {"subscription", Subscription}];
|
|
_ -> [{"jid", jlib:jid_to_string(TJID)}, {"name", Nick}, {"subscription", Subscription}]
|
|
end,
|
|
Result = jlib:iq_to_xml(#iq{type = set, xmlns = ?NS_ROSTER, id = "push",
|
|
sub_el = [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}],
|
|
[{xmlelement, "item", Item, GroupsXML}]}]}),
|
|
lists:foreach(fun(Session) ->
|
|
JID = jlib:make_jid(Session#session.usr),
|
|
ejabberd_router:route(JID, JID, Result),
|
|
PID = element(2, Session#session.sid),
|
|
ejabberd_c2s:add_rosteritem(PID, TJID, list_to_atom(Subscription)) %% TODO: Better error management
|
|
end, get_sessions(User, Server)).
|
|
|
|
|
|
roster_backend(Server) ->
|
|
case lists:member(mod_roster, gen_mod:loaded_modules(Server)) of
|
|
true -> mnesia;
|
|
_ -> odbc % we assume that
|
|
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]),
|
|
extract_group(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.
|
|
|
|
%% -----------------------------
|
|
%% 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) ->
|
|
LUser = jlib:nodeprep(User),
|
|
LServer = jlib:nameprep(Server),
|
|
case catch mnesia:dirty_index_read(session, {LUser, LServer}, #session.us) of
|
|
{'EXIT', _Reason} -> [];
|
|
[] -> [];
|
|
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.
|