diff options
Diffstat (limited to 'src/mod_admin_extra.erl')
-rw-r--r-- | src/mod_admin_extra.erl | 1216 |
1 files changed, 588 insertions, 628 deletions
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 2bb436f31..79b5c8d67 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -5,7 +5,7 @@ %%% Created : 10 Aug 2008 by Badlop <badlop@process-one.net> %%% %%% -%%% ejabberd, Copyright (C) 2002-2008 ProcessOne +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -30,31 +30,61 @@ -include("logger.hrl"). --export([start/2, stop/1, compile/1, get_cookie/0, - remove_node/1, set_password/3, check_password/3, - check_password_hash/4, delete_old_users/1, - delete_old_users_vhost/2, ban_account/3, - num_active_users/2, num_resources/2, resource_num/3, +-export([start/2, stop/1, reload/3, mod_options/1, + get_commands_spec/0, depends/2]). + +% Commands API +-export([ + % Adminsys + compile/1, get_cookie/0, + restart_module/2, + + % Sessions + num_resources/2, resource_num/3, 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, - user_sessions_info/2, set_nickname/3, get_vcard/3, + get_presence/2, user_sessions_info/2, get_last/2, set_last/4, + + % Accounts + set_password/3, check_password_hash/4, delete_old_users/1, + delete_old_users_vhost/2, ban_account/3, check_password/3, + + % vCard + set_nickname/3, get_vcard/3, get_vcard/4, get_vcard_multi/4, set_vcard/4, - set_vcard/5, add_rosteritem/7, delete_rosteritem/4, - process_rosteritems/5, get_roster/2, push_roster/3, - push_roster_all/1, push_alltoall/2, get_last/2, - private_get/4, private_set/3, srg_create/5, + set_vcard/5, + + % Roster + add_rosteritem/7, delete_rosteritem/4, + get_roster/2, push_roster/3, + push_roster_all/1, push_alltoall/2, + push_roster_item/5, build_roster_item/3, + + % Private storage + private_get/4, private_set/3, + + % Shared roster + srg_create/5, srg_delete/2, srg_list/1, srg_get_info/2, srg_get_members/2, srg_user_add/4, srg_user_del/4, - send_message/5, send_stanza/3, send_stanza_c2s/4, privacy_set/3, - stats/1, stats/2, mod_opt_type/1, get_commands_spec/0, depends/2]). + + % Send message + send_message/5, send_stanza/3, send_stanza_c2s/4, + + % Privacy list + privacy_set/3, + + % Stats + stats/1, stats/2 + ]). --include("ejabberd.hrl"). -include("ejabberd_commands.hrl"). -include("mod_roster.hrl"). +-include("mod_privacy.hrl"). -include("ejabberd_sm.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). %%% %%% gen_mod @@ -63,8 +93,16 @@ start(_Host, _Opts) -> ejabberd_commands:register_commands(get_commands_spec()). -stop(_Host) -> - ejabberd_commands:unregister_commands(get_commands_spec()). +stop(Host) -> + case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of + false -> + ejabberd_commands:unregister_commands(get_commands_spec()); + true -> + ok + end. + +reload(_Host, _NewOpts, _OldOpts) -> + ok. depends(_Host, _Opts) -> []. @@ -115,27 +153,25 @@ get_commands_spec() -> result = {cookie, string}, result_example = "MWTAVMODFELNLSMYXPPD", result_desc = "Erlang cookie used for authentication by ejabberd"}, - #ejabberd_commands{name = remove_node, tags = [erlang], - desc = "Remove an ejabberd node from Mnesia clustering config", - module = ?MODULE, function = remove_node, - args = [{node, string}], - args_example = ["ejabberd@server2"], - args_desc = ["Name of erlang node to remove"], - result = {res, rescode}, - result_example = ok, - result_desc = "Status code: 0 on success, 1 otherwise"}, - #ejabberd_commands{name = num_active_users, tags = [accounts, stats], - desc = "Get number of users active in the last days", - policy = admin, - module = ?MODULE, function = num_active_users, - args = [{host, binary}, {days, integer}], - args_example = [<<"myserver.com">>, 3], - args_desc = ["Name of host to check", "Number of days to calculate sum"], - result = {users, integer}, - result_example = 123, - result_desc = "Number of users active on given server in last n days"}, + #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" + " - 0: code reloaded, module restarted\n" + " - 1: error: module not loaded\n" + " - 2: code not reloaded, but module restarted"}, #ejabberd_commands{name = delete_old_users, tags = [accounts, purge], desc = "Delete users that didn't log in last days, or that never logged", + longdesc = "To protect admin accounts, configure this for example:\n" + "access_rules:\n" + " protect_old_users:\n" + " - allow: admin\n" + " - deny: all\n", module = ?MODULE, function = delete_old_users, args = [{days, integer}], args_example = [30], @@ -145,6 +181,11 @@ get_commands_spec() -> result_desc = "Result tuple"}, #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", + longdesc = "To protect admin accounts, configure this for example:\n" + "access_rules:\n" + " delete_old_users:\n" + " - deny: admin\n" + " - allow: all\n", module = ?MODULE, function = delete_old_users_vhost, args = [{host, binary}, {days, integer}], args_example = [<<"myserver.com">>, 30], @@ -155,7 +196,7 @@ get_commands_spec() -> result_desc = "Result tuple"}, #ejabberd_commands{name = check_account, tags = [accounts], desc = "Check if an account exists or not", - module = ejabberd_auth, function = is_user_exists, + module = ejabberd_auth, function = user_exists, args = [{user, binary}, {host, binary}], args_example = [<<"peter">>, <<"myserver.com">>], args_desc = ["User name to check", "Server to check"], @@ -173,10 +214,10 @@ get_commands_spec() -> result_desc = "Status code: 0 on success, 1 otherwise"}, #ejabberd_commands{name = check_password_hash, tags = [accounts], desc = "Check if the password hash is correct", - longdesc = "Allowed hash methods: md5, sha.", + longdesc = "Allows hash methods from crypto application", module = ?MODULE, function = check_password_hash, - args = [{user, binary}, {host, binary}, {passwordhash, string}, - {hashmethod, string}], + args = [{user, binary}, {host, binary}, {passwordhash, binary}, + {hashmethod, binary}], args_example = [<<"peter">>, <<"myserver.com">>, <<"5ebe2294ecd0e0f08eab7690d2a6ee69">>, <<"md5">>], args_desc = ["User name to check", "Server to check", @@ -235,7 +276,7 @@ get_commands_spec() -> result_desc = "Status code: 0 on success, 1 otherwise"}, #ejabberd_commands{name = status_num_host, tags = [session, stats], desc = "Number of logged users with this status in host", - policy = admin, + policy = admin, module = ?MODULE, function = status_num, args = [{host, binary}, {status, binary}], args_example = [<<"myserver.com">>, <<"dnd">>], @@ -245,7 +286,7 @@ get_commands_spec() -> result_desc = "Number of connected sessions with given status type"}, #ejabberd_commands{name = status_num, tags = [session, stats], desc = "Number of logged users with this status", - policy = admin, + policy = admin, module = ?MODULE, function = status_num, args = [{status, binary}], args_example = [<<"dnd">>], @@ -257,6 +298,9 @@ get_commands_spec() -> desc = "List of users logged in host with their statuses", module = ?MODULE, function = status_list, args = [{host, binary}, {status, binary}], + args_example = [<<"myserver.com">>, <<"dnd">>], + args_desc = ["Server name", "Status type to check"], + result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}], result = {users, {list, {userstatus, {tuple, [ {user, string}, @@ -270,6 +314,9 @@ get_commands_spec() -> desc = "List of logged users with this status", module = ?MODULE, function = status_list, args = [{status, binary}], + args_example = [<<"dnd">>], + args_desc = ["Status type to check"], + result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}], result = {users, {list, {userstatus, {tuple, [ {user, string}, @@ -284,29 +331,43 @@ get_commands_spec() -> desc = "List all established sessions and their information", module = ?MODULE, function = connected_users_info, args = [], + result_example = [{"user1@myserver.com/tka", + "c2s", "127.0.0.1", 42656,8, "ejabberd@localhost", + 231, <<"dnd">>, <<"tka">>, <<>>}], result = {connected_users_info, {list, - {sessions, {tuple, - [{jid, string}, - {connection, string}, - {ip, string}, - {port, integer}, - {priority, integer}, - {node, string}, - {uptime, integer} - ]}} + {session, {tuple, + [{jid, string}, + {connection, string}, + {ip, string}, + {port, integer}, + {priority, integer}, + {node, string}, + {uptime, integer}, + {status, string}, + {resource, string}, + {statustext, string} + ]}} }}}, + #ejabberd_commands{name = connected_users_vhost, - tags = [session], - desc = "Get the list of established sessions in a vhost", - module = ?MODULE, function = connected_users_vhost, - args = [{host, binary}], - result = {connected_users_vhost, {list, {sessions, string}}}}, + tags = [session], + desc = "Get the list of established sessions in a vhost", + module = ?MODULE, function = connected_users_vhost, + args_example = [<<"myexample.com">>], + args_desc = ["Server name"], + result_example = [<<"user1@myserver.com/tka">>, <<"user2@localhost/tka">>], + args = [{host, binary}], + result = {connected_users_vhost, {list, {sessions, string}}}}, #ejabberd_commands{name = user_sessions_info, tags = [session], desc = "Get information about all sessions of a user", module = ?MODULE, function = user_sessions_info, args = [{user, binary}, {host, binary}], + 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">>, <<>>}], result = {sessions_info, {list, {session, {tuple, @@ -322,6 +383,32 @@ get_commands_spec() -> ]}} }}}, + #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.\nThe '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, binary}, {host, binary}], + args_rename = [{server, host}], + args_example = [<<"peter">>, <<"myexample.com">>], + args_desc = ["User name", "Server name"], + result_example = {<<"user1@myserver.com/tka">>, <<"dnd">>, <<"Busy">>}, + result = + {presence, + {tuple, + [{jid, string}, {show, string}, + {status, string}]}}}, #ejabberd_commands{name = set_presence, tags = [session], desc = "Set presence of a session", @@ -330,24 +417,40 @@ get_commands_spec() -> {resource, binary}, {type, binary}, {show, binary}, {status, binary}, {priority, binary}], + 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}}, #ejabberd_commands{name = set_nickname, tags = [vcard], desc = "Set nickname in a user's vCard", module = ?MODULE, function = set_nickname, args = [{user, binary}, {host, binary}, {nickname, binary}], + args_example = [<<"user1">>,<<"myserver.com">>,<<"User 1">>], + args_desc = ["User name", "Server name", "Nickname"], result = {res, rescode}}, #ejabberd_commands{name = get_vcard, tags = [vcard], desc = "Get content from a vCard field", longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP, module = ?MODULE, function = get_vcard, args = [{user, binary}, {host, binary}, {name, binary}], + args_example = [<<"user1">>,<<"myserver.com">>,<<"NICKNAME">>], + args_desc = ["User name", "Server name", "Field name"], + result_example = "User 1", + result_desc = "Field content", result = {content, string}}, #ejabberd_commands{name = get_vcard2, tags = [vcard], - desc = "Get content from a vCard field", + desc = "Get content from a vCard subfield", longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP, module = ?MODULE, function = get_vcard, args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}], + args_example = [<<"user1">>,<<"myserver.com">>,<<"N">>, <<"FAMILY">>], + args_desc = ["User name", "Server name", "Field name", "Subfield name"], + result_example = "Schubert", + result_desc = "Field content", result = {content, string}}, #ejabberd_commands{name = get_vcard2_multi, tags = [vcard], desc = "Get multiple contents from a vCard field", @@ -361,12 +464,16 @@ get_commands_spec() -> longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP, module = ?MODULE, function = set_vcard, args = [{user, binary}, {host, binary}, {name, binary}, {content, binary}], + args_example = [<<"user1">>,<<"myserver.com">>, <<"URL">>, <<"www.example.com">>], + args_desc = ["User name", "Server name", "Field name", "Value"], result = {res, rescode}}, #ejabberd_commands{name = set_vcard2, tags = [vcard], desc = "Set content in a vCard subfield", longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP, module = ?MODULE, function = set_vcard, args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {content, binary}], + args_example = [<<"user1">>,<<"myserver.com">>,<<"TEL">>, <<"NUMBER">>, <<"123456">>], + args_desc = ["User name", "Server name", "Field name", "Subfield name", "Value"], result = {res, rescode}}, #ejabberd_commands{name = set_vcard2_multi, tags = [vcard], desc = "Set multiple contents in a vCard subfield", @@ -377,11 +484,17 @@ get_commands_spec() -> #ejabberd_commands{name = add_rosteritem, tags = [roster], desc = "Add an item to a user's roster (supports ODBC)", + longdesc = "Group can be several groups separated by ; for example: \"g1;g2;g3\"", module = ?MODULE, function = add_rosteritem, - args = [{localuser, binary}, {localserver, binary}, - {user, binary}, {server, binary}, + args = [{localuser, binary}, {localhost, binary}, + {user, binary}, {host, binary}, {nick, binary}, {group, binary}, {subs, binary}], + args_rename = [{localserver, localhost}, {server, host}], + 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"], result = {res, rescode}}, %%{"", "subs= none, from, to or both"}, %%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"}, @@ -389,11 +502,14 @@ get_commands_spec() -> #ejabberd_commands{name = delete_rosteritem, tags = [roster], desc = "Delete an item from a user's roster (supports ODBC)", module = ?MODULE, function = delete_rosteritem, - args = [{localuser, binary}, {localserver, binary}, - {user, binary}, {server, binary}], + args = [{localuser, binary}, {localhost, binary}, + {user, binary}, {host, binary}], + args_rename = [{localserver, localhost}, {server, host}], + args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>], + args_desc = ["User name", "Server name", "Contact user name", "Contact server name"], result = {res, rescode}}, #ejabberd_commands{name = process_rosteritems, tags = [roster], - desc = "List or delete rosteritems that match filtering options", + desc = "List/delete rosteritems that match filter", longdesc = "Explanation of each argument:\n" " - action: what to do with each rosteritem that " "matches all the filtering options\n" @@ -402,6 +518,8 @@ get_commands_spec() -> " - users: the JIDs of the local user\n" " - contacts: the JIDs of the contact in the roster\n" "\n" + " *** Mnesia: \n" + "\n" "Allowed values in the arguments:\n" " ACTION = list | delete\n" " SUBS = SUB[:SUB]* | any\n" @@ -419,8 +537,26 @@ get_commands_spec() -> "'example.org' and that the contact JID is either a " "bare server name (without user part) or that has a " "user part and the server part contains the word 'icq'" - ":\n list none:from:to any *@example.org *:*@*icq*", - module = ?MODULE, function = process_rosteritems, + ":\n list none:from:to any *@example.org *:*@*icq*" + "\n\n" + " *** SQL:\n" + "\n" + "Allowed values in the arguments:\n" + " ACTION = list | delete\n" + " SUBS = any | none | from | to | both\n" + " ASKS = any | none | out | in\n" + " USERS = JID\n" + " CONTACTS = JID\n" + " JID = characters valid in a JID, and can use the " + "globs: _ and %\n" + "\n" + "This example will list roster items with subscription " + "'to' that have any ask property, of " + "local users which JID is in the virtual host " + "'example.org' and that the contact JID's " + "server part contains the word 'icq'" + ":\n list to any %@example.org %@%icq%", + module = mod_roster, function = process_rosteritems, args = [{action, string}, {subs, string}, {asks, string}, {users, string}, {contacts, string}], @@ -436,6 +572,7 @@ get_commands_spec() -> policy = user, module = ?MODULE, function = get_roster, args = [], + args_rename = [{server, host}], result = {contacts, {list, {contact, {tuple, [ {jid, string}, {nick, string}, @@ -445,44 +582,74 @@ get_commands_spec() -> ]}}}}}, #ejabberd_commands{name = push_roster, tags = [roster], desc = "Push template roster from file to a user", + longdesc = "The text file must contain an erlang term: a list " + "of tuples with username, servername, group and nick. Example:\n" + "[{<<\"user1\">>, <<\"localhost\">>, <<\"Workers\">>, <<\"User 1\">>},\n" + " {<<\"user2\">>, <<\"localhost\">>, <<\"Workers\">>, <<\"User 2\">>}].\n" + "When using UTF8 character encoding add /utf8 to certain string. Example:\n" + "[{<<\"user2\">>, <<\"localhost\">>, <<\"Workers\"/utf8>>, <<\"User 2\"/utf8>>}].", module = ?MODULE, function = push_roster, args = [{file, binary}, {user, binary}, {host, binary}], + args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>], + args_desc = ["File path", "User name", "Server name"], result = {res, rescode}}, #ejabberd_commands{name = push_roster_all, tags = [roster], desc = "Push template roster from file to all those users", + longdesc = "The text file must contain an erlang term: a list " + "of tuples with username, servername, group and nick. Example:\n" + "[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n" + " {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].", module = ?MODULE, function = push_roster_all, args = [{file, binary}], + args_example = [<<"/home/ejabberd/roster.txt">>], + args_desc = ["File path"], result = {res, rescode}}, #ejabberd_commands{name = push_alltoall, tags = [roster], desc = "Add all the users to all the users of Host in Group", module = ?MODULE, function = push_alltoall, args = [{host, binary}, {group, binary}], + args_example = [<<"myserver.com">>,<<"Everybody">>], + args_desc = ["Server name", "Group name"], result = {res, rescode}}, #ejabberd_commands{name = get_last, tags = [last], - desc = "Get last activity information (timestamp and status)", - longdesc = "Timestamp is the seconds since" - "1970-01-01 00:00:00 UTC, for example: date +%s", + desc = "Get last activity information", + longdesc = "Timestamp is UTC and XEP-0082 format, for example: " + "2017-02-23T22:25:28.063062Z ONLINE", module = ?MODULE, function = get_last, args = [{user, binary}, {host, binary}], - result = {last_activity, string}}, + 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", + result = {last_activity, + {tuple, [{timestamp, string}, + {status, string} + ]}}}, #ejabberd_commands{name = set_last, tags = [last], desc = "Set last activity information", - longdesc = "Timestamp is the seconds since" + longdesc = "Timestamp is the seconds since " "1970-01-01 00:00:00 UTC, for example: date +%s", - module = mod_last, function = store_last_info, + module = ?MODULE, function = set_last, args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}], + args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>], + args_desc = ["User name", "Server name", "Number of seconds since epoch", "Status message"], result = {res, rescode}}, #ejabberd_commands{name = private_get, tags = [private], desc = "Get some information from a user private storage", module = ?MODULE, function = private_get, args = [{user, binary}, {host, binary}, {element, binary}, {ns, binary}], + args_example = [<<"user1">>,<<"myserver.com">>,<<"storage">>, <<"storage:rosternotes">>], + args_desc = ["User name", "Server name", "Element name", "Namespace"], result = {res, string}}, #ejabberd_commands{name = private_set, tags = [private], desc = "Set to the user private storage", module = ?MODULE, function = private_set, args = [{user, binary}, {host, binary}, {element, binary}], + args_example = [<<"user1">>,<<"myserver.com">>, + <<"<storage xmlns='storage:rosternotes'/>">>], + args_desc = ["User name", "Server name", "XML storage element"], result = {res, rescode}}, #ejabberd_commands{name = srg_create, tags = [shared_roster_group], @@ -492,100 +659,178 @@ get_commands_spec() -> "put \\ \" around the argument and\nseparate the " "identifiers with \\ \\ n\n" "For example:\n" - " ejabberdctl srg_create group3 localhost " + " ejabberdctl srg_create group3 myserver.com " "name desc \\\"group1\\\\ngroup2\\\"", module = ?MODULE, function = srg_create, args = [{group, binary}, {host, binary}, {name, binary}, {description, binary}, {display, binary}], + args_example = [<<"group3">>, <<"myserver.com">>, <<"Group3">>, + <<"Third group">>, <<"group1\\\\ngroup2">>], + args_desc = ["Group identifier", "Group server name", "Group name", + "Group description", "Groups to display"], result = {res, rescode}}, #ejabberd_commands{name = srg_delete, tags = [shared_roster_group], desc = "Delete a Shared Roster Group", module = ?MODULE, function = srg_delete, args = [{group, binary}, {host, binary}], + args_example = [<<"group3">>, <<"myserver.com">>], + args_desc = ["Group identifier", "Group server name"], 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, args = [{host, binary}], + args_example = [<<"myserver.com">>], + args_desc = ["Server name"], + result_example = [<<"group1">>, <<"group2">>], + result_desc = "List of group identifiers", 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, args = [{group, binary}, {host, binary}], + args_example = [<<"group3">>, <<"myserver.com">>], + args_desc = ["Group identifier", "Group server name"], + result_example = [{<<"name">>, "Group 3"}, {<<"displayed_groups">>, "group1"}], + result_desc = "List of group informations, as key and value", result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}}, #ejabberd_commands{name = srg_get_members, tags = [shared_roster_group], desc = "Get members of a Shared Roster Group", module = ?MODULE, function = srg_get_members, args = [{group, binary}, {host, binary}], + args_example = [<<"group3">>, <<"myserver.com">>], + args_desc = ["Group identifier", "Group server name"], + result_example = [<<"user1@localhost">>, <<"user2@localhost">>], + result_desc = "List of group identifiers", 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, args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}], + args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>], + args_desc = ["Username", "User server name", "Group identifier", "Group server name"], result = {res, rescode}}, #ejabberd_commands{name = srg_user_del, tags = [shared_roster_group], desc = "Delete this JID user@host from the Shared Roster Group", module = ?MODULE, function = srg_user_del, args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}], + args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>], + args_desc = ["Username", "User server name", "Group identifier", "Group server name"], result = {res, rescode}}, #ejabberd_commands{name = get_offline_count, tags = [offline], desc = "Get the number of unread offline messages", - policy = user, + policy = user, module = mod_offline, function = count_offline_messages, args = [], + args_rename = [{server, host}], + result_example = 5, + result_desc = "Number", result = {value, integer}}, #ejabberd_commands{name = send_message, tags = [stanza], desc = "Send a message to a local or remote bare of full JID", + longdesc = "When sending a groupchat message to a MUC room, " + "FROM must be the full JID of a room occupant, " + "or the bare JID of a MUC service admin, " + "or the bare JID of a MUC/Sub subscribed user.", module = ?MODULE, function = send_message, args = [{type, binary}, {from, binary}, {to, binary}, {subject, binary}, {body, binary}], + args_example = [<<"headline">>, <<"admin@localhost">>, <<"user1@localhost">>, + <<"Restart">>, <<"In 5 minutes">>], + args_desc = ["Message type: normal, chat, headline, groupchat", "Sender JID", + "Receiver JID", "Subject, or empty string", "Body"], result = {res, rescode}}, #ejabberd_commands{name = send_stanza_c2s, tags = [stanza], desc = "Send a stanza as if sent from a c2s session", module = ?MODULE, function = send_stanza_c2s, args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}], + args_example = [<<"admin">>, <<"myserver.com">>, <<"bot">>, + <<"<message to='user1@localhost'><ext attr='value'/></message>">>], + args_desc = ["Username", "Server name", "Resource", "Stanza"], result = {res, rescode}}, #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}], + args_example = [<<"admin@localhost">>, <<"user1@localhost">>, + <<"<message><ext attr='value'/></message>">>], + args_desc = ["Sender JID", "Destination JID", "Stanza"], result = {res, rescode}}, #ejabberd_commands{name = privacy_set, tags = [stanza], desc = "Send a IQ set privacy stanza for a local account", module = ?MODULE, function = privacy_set, args = [{user, binary}, {host, binary}, {xmlquery, binary}], + args_example = [<<"user1">>, <<"myserver.com">>, + <<"<query xmlns='jabber:iq:privacy'>...">>], + args_desc = ["Username", "Server name", "Query XML element"], result = {res, rescode}}, #ejabberd_commands{name = stats, tags = [stats], desc = "Get statistical value: registeredusers onlineusers onlineusersnode uptimeseconds processes", - policy = admin, + policy = admin, module = ?MODULE, function = stats, args = [{name, binary}], + args_example = [<<"registeredusers">>], + args_desc = ["Statistic name"], + result_example = 6, + result_desc = "Integer statistic value", result = {stat, integer}}, #ejabberd_commands{name = stats_host, tags = [stats], desc = "Get statistical value for this host: registeredusers onlineusers", - policy = admin, + policy = admin, module = ?MODULE, function = stats, args = [{name, binary}, {host, binary}], + args_example = [<<"registeredusers">>, <<"example.com">>], + args_desc = ["Statistic name", "Server JID"], + result_example = 6, + result_desc = "Integer statistic value", result = {stat, integer}} ]. %%% -%%% Node +%%% Adminsys %%% compile(File) -> - compile:file(File). + Ebin = filename:join(code:lib_dir(ejabberd), "ebin"), + case ext_mod:compile_erlang_file(Ebin, File) of + {ok, Module} -> + code:purge(Module), + code:load_file(Module), + ok; + _ -> + error + end. get_cookie() -> atom_to_list(erlang:get_cookie()). -remove_node(Node) -> - mnesia:del_table_copy(schema, list_to_atom(Node)), - ok. +restart_module(Host, Module) when is_binary(Module) -> + restart_module(Host, misc:binary_to_atom(Module)); +restart_module(Host, Module) when is_atom(Module) -> + case gen_mod:is_loaded(Host, Module) of + false -> + % not a running module, force code reload anyway + code:purge(Module), + code:delete(Module), + code:load_file(Module), + 1; + true -> + gen_mod:stop_module(Host, Module), + case code:soft_purge(Module) of + true -> + code:delete(Module), + code:load_file(Module), + gen_mod:start_module(Host, Module), + 0; + false -> + gen_mod:start_module(Host, Module), + 2 + end + end. %%% %%% Accounts @@ -601,14 +846,16 @@ check_password(User, Host, Password) -> %% Copied some code from ejabberd_commands.erl check_password_hash(User, Host, PasswordHash, HashMethod) -> AccountPass = ejabberd_auth:get_password_s(User, Host), - AccountPassHash = case {AccountPass, HashMethod} of + 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 {A, _} when is_tuple(A) -> scrammed; - {_, <<"md5">>} -> get_md5(AccountPass); - {_, <<"sha">>} -> get_sha(AccountPass); - {_, Method} -> - ?ERROR_MSG("check_password_hash called " - "with hash method: ~p", [Method]), - undefined + {_, true} -> get_hash(AccountPass, HashMethod); + {_, false} -> + ?ERROR_MSG("Check_password_hash called " + "with hash method: ~p", [HashMethod]), + undefined end, case AccountPassHash of scrammed -> @@ -618,126 +865,56 @@ check_password_hash(User, Host, PasswordHash, HashMethod) -> PasswordHash -> ok; _ -> false end. -get_md5(AccountPass) -> - iolist_to_binary([io_lib:format("~2.16.0B", [X]) - || X <- binary_to_list(erlang:md5(AccountPass))]). -get_sha(AccountPass) -> - iolist_to_binary([io_lib:format("~2.16.0B", [X]) - || X <- binary_to_list(p1_sha:sha1(AccountPass))]). - -num_active_users(Host, Days) -> - list_last_activity(Host, true, Days). - -%% Code based on ejabberd/src/web/ejabberd_web_admin.erl -list_last_activity(Host, Integral, Days) -> - TimeStamp = p1_time_compat:system_time(seconds), - TS = TimeStamp - Days * 86400, - case catch mnesia:dirty_select( - last_activity, [{{last_activity, {'_', Host}, '$1', '_'}, - [{'>', '$1', TS}], - [{'trunc', {'/', - {'-', TimeStamp, '$1'}, - 86400}}]}]) of - {'EXIT', _Reason} -> - []; - Vals -> - Hist = histogram(Vals, Integral), - if - Hist == [] -> - 0; - true -> - Left = Days - length(Hist), - Tail = if - Integral -> - lists:duplicate(Left, lists:last(Hist)); - true -> - lists:duplicate(Left, 0) - end, - lists:nth(Days, Hist ++ Tail) - end - end. -histogram(Values, Integral) -> - histogram(lists:sort(Values), Integral, 0, 0, []). -histogram([H | T], Integral, Current, Count, Hist) when Current == H -> - histogram(T, Integral, Current, Count + 1, Hist); -histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H -> - if - Integral -> - histogram(Values, Integral, Current + 1, Count, [Count | Hist]); - true -> - histogram(Values, Integral, Current + 1, 0, [Count | Hist]) - end; -histogram([], _Integral, _Current, Count, Hist) -> - if - Count > 0 -> - lists:reverse([Count | Hist]); - true -> - lists:reverse(Hist) - end. +get_hash(AccountPass, Method) -> + iolist_to_binary([io_lib:format("~2.16.0B", [X]) + || X <- binary_to_list( + crypto:hash(binary_to_atom(Method, latin1), AccountPass))]). delete_old_users(Days) -> %% Get the list of registered users - Users = ejabberd_auth:dirty_get_registered_users(), + Users = ejabberd_auth:get_users(), {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 - Users = ejabberd_auth:get_vh_registered_users(Host), + Users = ejabberd_auth:get_users(Host), {removed, N, UR} = delete_old_users(Days, Users), {ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}. delete_old_users(Days, Users) -> - %% Convert older time SecOlder = Days*24*60*60, - - %% Get current time - TimeStamp_now = p1_time_compat:system_time(seconds), - - %% For a user, remove if required and answer true + TimeStamp_now = erlang:system_time(second), + TimeStamp_oldest = TimeStamp_now - SecOlder, F = fun({LUser, LServer}) -> - %% Check if the user is logged - case ejabberd_sm:get_user_resources(LUser, LServer) of - %% If it isnt - [] -> - %% Look for his last_activity - case mod_last:get_last_info(LUser, LServer) of - %% If it is - %% existent: - {ok, TimeStamp, _Status} -> - %% get his age - Sec = TimeStamp_now - TimeStamp, - %% If he is - if - %% younger than SecOlder: - Sec < SecOlder -> - %% do nothing - false; - %% older: - true -> - %% remove the user - ejabberd_auth:remove_user(LUser, LServer), - true - end; - %% nonexistent: - not_found -> - %% remove the user - ejabberd_auth:remove_user(LUser, LServer), - true - end; - %% Else - _ -> - %% do nothing - false - end + case catch delete_or_not(LUser, LServer, TimeStamp_oldest) of + true -> + ejabberd_auth:remove_user(LUser, LServer), + true; + _ -> + false + end end, - %% Apply the function to every user in the list Users_removed = lists:filter(F, Users), {removed, length(Users_removed), Users_removed}. +delete_or_not(LUser, LServer, TimeStamp_oldest) -> + deny = acl:match_rule(LServer, protect_old_users, jid:make(LUser, LServer)), + [] = 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. + %% %% Ban account @@ -759,8 +936,10 @@ set_random_password(User, Server, Reason) -> set_password_auth(User, Server, NewPass). build_random_password(Reason) -> - Date = jlib:timestamp_to_legacy(calendar:universal_time()), - RandomString = randoms:get_string(), + {{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]), + RandomString = p1_rand:get_string(), <<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>. set_password_auth(User, Server, Password) -> @@ -795,9 +974,8 @@ kick_session(User, Server, Resource, ReasonText) -> ok. kick_this_session(User, Server, Resource, Reason) -> - ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>), - jid:make(User, Server, Resource), - {broadcast, {exit, Reason}}). + ejabberd_sm:route(jid:make(User, Server, Resource), + {exit, Reason}). status_num(Host, Status) -> length(get_status_list(Host, Status)). @@ -805,7 +983,7 @@ status_num(Status) -> status_num(<<"all">>, Status). status_list(Host, Status) -> Res = get_status_list(Host, Status), - [{U, S, R, P, St} || {U, S, R, P, St} <- Res]. + [{U, S, R, num_prio(P), St} || {U, S, R, P, St} <- Res]. status_list(Status) -> status_list(<<"all">>, Status). @@ -817,7 +995,7 @@ get_status_list(Host, Status_required) -> Sessions2 = [ {Session#session.usr, Session#session.sid, Session#session.priority} || Session <- Sessions], Fhost = case Host of <<"all">> -> - %% All hosts are requested, so dont filter at all + %% All hosts are requested, so don't filter at all fun(_, _) -> true end; _ -> %% Filter the list, only Host is interesting @@ -825,7 +1003,7 @@ get_status_list(Host, Status_required) -> end, Sessions3 = [ {Pid, Server, Priority} || {{_User, Server, _Resource}, {_, Pid}, Priority} <- Sessions2, apply(Fhost, [Server, Host])], %% For each Pid, get its presence - Sessions4 = [ {catch ejabberd_c2s:get_presence(Pid), Server, Priority} || {Pid, Server, Priority} <- Sessions3], + Sessions4 = [ {catch get_presence(Pid), Server, Priority} || {Pid, Server, Priority} <- Sessions3], %% Filter by status Fstatus = case Status_required of <<"all">> -> @@ -833,99 +1011,107 @@ get_status_list(Host, Status_required) -> _ -> fun(A, B) -> A == B end end, - [{User, Server, Resource, Priority, stringize(Status_text)} + [{User, Server, Resource, num_prio(Priority), stringize(Status_text)} || {{User, Resource, Status, Status_text}, Server, Priority} <- Sessions4, apply(Fstatus, [Status, Status_required])]. connected_users_info() -> - USRIs = dirty_get_sessions_list2(), - CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}), - lists:map( - fun([{U, S, R}, {Now, Pid}, 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)), - PriorityI = case Priority of - PI when is_integer(PI) -> PI; - _ -> nil - end, - {binary_to_list(<<U/binary, $@, S/binary, $/, R/binary>>), - atom_to_list(Conn), IPS, Port, PriorityI, NodeS, Uptime} + lists:filtermap( + fun({U, S, R}) -> + 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 end, - USRIs). + ejabberd_sm:dirty_get_sessions_list()). connected_users_vhost(Host) -> USRs = ejabberd_sm:get_vh_session_list(Host), - [ [U, $@, S, $/, R] || {U, S, R} <- USRs]. - -%% Code copied from ejabberd_sm.erl and customized -dirty_get_sessions_list2() -> - Ss = mnesia:dirty_select( - session, - [{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4', - _ = '_'}, - [], - [['$1', '$2', '$3', '$4']]}]), - lists:filter(fun([_USR, _SID, _Priority, Info]) -> - not proplists:get_bool(offline, Info) - end, Ss). + [ jid:encode(jid:make(USR)) || USR <- USRs]. %% Make string more print-friendly stringize(String) -> %% Replace newline characters with other code ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>). +get_presence(Pid) -> + try get_presence2(Pid) of + {_, _, _, _} = Res -> + Res + catch + _:_ -> {<<"">>, <<"">>, <<"offline">>, <<"">>} + end. +get_presence2(Pid) -> + 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}. + +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 + [] -> + {jid:encode({U, S, <<>>}), <<"unavailable">>, <<"">>}; + [SessionPid|_] -> + {_User, Resource, Show, Status} = get_presence(SessionPid), + FullJID = jid:encode({U, S, Resource}), + {FullJID, Show, Status} + end. + set_presence(User, Host, Resource, Type, Show, Status, Priority) when is_integer(Priority) -> BPriority = integer_to_binary(Priority), set_presence(User, Host, Resource, Type, Show, Status, BPriority); -set_presence(User, Host, Resource, Type, Show, Status, Priority) -> - case ejabberd_sm:get_session_pid(User, Host, Resource) of - none -> - error; - Pid -> - USR = jid:to_string(jid:make(User, Host, Resource)), - US = jid:to_string(jid:make(User, Host, <<>>)), - Message = {route_xmlstreamelement, - {xmlel, <<"presence">>, - [{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}], - [{xmlel, <<"show">>, [], [{xmlcdata, Show}]}, - {xmlel, <<"status">>, [], [{xmlcdata, Status}]}, - {xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}}, - Pid ! Message, - ok - end. +set_presence(User, Host, Resource, Type, Show, Status, Priority0) -> + Priority = if is_integer(Priority0) -> Priority0; + true -> binary_to_integer(Priority0) + end, + 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 = []}, + Ref = ejabberd_sm:get_session_pid(User, Host, Resource), + ejabberd_c2s:set_presence(Ref, Pres). user_sessions_info(User, Host) -> + 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)). + +user_session_info(User, Host, Resource) -> CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}), - US = {User, Host}, - Sessions = case catch mnesia:dirty_index_read(session, US, #session.us) of - {'EXIT', _Reason} -> - []; - Ss -> - lists:filter(fun(#session{info = Info}) -> - not proplists:get_bool(offline, Info) - end, Ss) - end, - lists:map( - fun(Session) -> - {_U, _S, Resource} = Session#session.usr, - {Now, Pid} = Session#session.sid, - {_U, _Resource, Status, StatusText} = ejabberd_c2s:get_presence(Pid), - Info = Session#session.info, - Priority = Session#session.priority, - 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, Priority, NodeS, Uptime, Status, Resource, StatusText} - end, - Sessions). + 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. %%% @@ -933,20 +1119,12 @@ user_sessions_info(User, Host) -> %%% set_nickname(User, Host, Nickname) -> - R = mod_vcard:process_sm_iq( - {jid, User, Host, <<>>, User, Host, <<>>}, - {jid, User, Host, <<>>, User, Host, <<>>}, - {iq, <<>>, set, <<>>, <<"en">>, - {xmlel, <<"vCard">>, [ - {<<"xmlns">>, <<"vcard-temp">>}], [ - {xmlel, <<"NICKNAME">>, [], [{xmlcdata, Nickname}]} - ] - }}), - case R of - {iq, <<>>, result, <<>>, _L, []} -> - ok; - _ -> - error + VCard = xmpp:encode(#vcard_temp{nickname = Nickname}), + case mod_vcard:set_vcard(User, jid:nameprep(Host), VCard) of + {error, badarg} -> + error; + ok -> + ok end. get_vcard(User, Host, Name) -> @@ -970,26 +1148,17 @@ set_vcard(User, Host, Name, Subname, SomeContent) -> %% %% Internal vcard -get_module_resource(Server) -> - case gen_mod:get_module_opt(Server, ?MODULE, module_resource, fun(A) -> A end, none) of - none -> list_to_binary(atom_to_list(?MODULE)); - R when is_binary(R) -> R - end. - get_vcard_content(User, Server, Data) -> - [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}), - JID = jid:make(User, Server, get_module_resource(Server)), - IQ = #iq{type = get, xmlns = ?NS_VCARD}, - IQr = Module:Function(JID, JID, IQ), - [A1] = IQr#iq.sub_el, - case A1#xmlel.children of - [_|_] -> - case get_vcard(Data, A1) of + case mod_vcard:get_vcard(jid:nodeprep(User), jid:nameprep(Server)) of + [El|_] -> + case get_vcard(Data, El) of [false] -> throw(error_no_value_found_in_vcard); ElemList -> ?DEBUG("ELS ~p", [ElemList]), [fxml:get_tag_cdata(Elem) || Elem <- ElemList] end; [] -> - throw(error_no_vcard_found) + throw(error_no_vcard_found); + error -> + throw(database_failure) end. get_vcard([<<"TEL">>, TelType], {_, _, _, OldEls}) -> @@ -1014,26 +1183,19 @@ set_vcard_content(User, Server, Data, SomeContent) -> [Bin | _] when is_binary(Bin) -> SomeContent; Bin when is_binary(Bin) -> [SomeContent] end, - [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}), - JID = jid:make(User, Server, get_module_resource(Server)), - IQ = #iq{type = get, xmlns = ?NS_VCARD}, - IQr = Module:Function(JID, JID, IQ), - %% Get old vcard - A4 = case IQr#iq.sub_el of + A4 = case mod_vcard:get_vcard(jid:nodeprep(User), jid:nameprep(Server)) of [A1] -> {_, _, _, A2} = A1, update_vcard_els(Data, ContentList, A2); [] -> - update_vcard_els(Data, ContentList, []) + update_vcard_els(Data, ContentList, []); + error -> + throw(database_failure) end, - %% Build new vcard SubEl = {xmlel, <<"vCard">>, [{<<"xmlns">>,<<"vcard-temp">>}], A4}, - IQ2 = #iq{type=set, sub_el = SubEl}, - - Module:Function(JID, JID, IQ2), - ok. + mod_vcard:set_vcard(User, jid:nameprep(Server), SubEl). take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) -> {Taken2, NewEls2} = case lists:keymember(TelType, 2, SubEls) of @@ -1080,42 +1242,25 @@ update_vcard_els(Data, ContentList, Els1) -> %%% add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs) -> - case add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs, []) of - {atomic, ok} -> - push_roster_item(LocalUser, LocalServer, User, Server, {add, Nick, Subs, Group}), - ok; - _ -> - error + Jid = jid:make(LocalUser, LocalServer), + RosterItem = build_roster_item(User, Server, {add, Nick, Subs, Group}), + case mod_roster:set_item_and_notify_clients(Jid, RosterItem, true) of + ok -> ok; + _ -> error end. -add_rosteritem(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) -> - subscribe(LU, LS, User, Server, Nick, Group, Subscription, Xattrs). - subscribe(LU, LS, User, Server, Nick, Group, Subscription, _Xattrs) -> ItemEl = build_roster_item(User, Server, {add, Nick, Subscription, Group}), - mod_roster:set_items( - LU, LS, - {xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_ROSTER}], - [ItemEl]}). + mod_roster:set_items(LU, LS, #roster_query{items = [ItemEl]}). delete_rosteritem(LocalUser, LocalServer, User, Server) -> - case unsubscribe(LocalUser, LocalServer, User, Server) of - {atomic, ok} -> - push_roster_item(LocalUser, LocalServer, User, Server, remove), - ok; - _ -> - error + Jid = jid:make(LocalUser, LocalServer), + RosterItem = build_roster_item(User, Server, remove), + case mod_roster:set_item_and_notify_clients(Jid, RosterItem, true) of + ok -> ok; + _ -> error end. -unsubscribe(LU, LS, User, Server) -> - ItemEl = build_roster_item(User, Server, remove), - mod_roster:set_items( - LU, LS, - {xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_ROSTER}], - [ItemEl]}). - %% ----------------------------- %% Get Roster %% ----------------------------- @@ -1129,7 +1274,7 @@ get_roster(User, Server) -> make_roster_xmlrpc(Roster) -> lists:foldl( fun(Item, Res) -> - JIDS = jid:to_string(Item#roster.jid), + JIDS = jid:encode(Item#roster.jid), Nick = Item#roster.name, Subs = atom_to_list(Item#roster.subscription), Ask = atom_to_list(Item#roster.ask), @@ -1171,12 +1316,12 @@ 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]) -> - subscribe(Name1, Server1, iolist_to_binary(Name2), iolist_to_binary(Server2), + subscribe(iolist_to_binary(Name1), iolist_to_binary(Server1), iolist_to_binary(Name2), iolist_to_binary(Server2), iolist_to_binary(Nick2), iolist_to_binary(Group2), <<"both">>, []), subscribe_roster({Name1, Server1, Group1, Nick1}, Roster). push_alltoall(S, G) -> - Users = ejabberd_auth:get_vh_registered_users(S), + Users = ejabberd_auth:get_users(S), Users2 = build_list_users(G, Users, []), subscribe_all(Users2), ok. @@ -1198,34 +1343,24 @@ push_roster_item(LU, LS, U, S, Action) -> push_roster_item(LU, LS, R, U, S, Action) -> LJID = jid:make(LU, LS, R), BroadcastEl = build_broadcast(U, S, Action), - ejabberd_sm:route(LJID, LJID, BroadcastEl), + ejabberd_sm:route(LJID, BroadcastEl), Item = build_roster_item(U, S, Action), ResIQ = build_iq_roster_push(Item), - ejabberd_router:route(jid:remove_resource(LJID), LJID, ResIQ). + ejabberd_router:route( + xmpp:set_from_to(ResIQ, jid:remove_resource(LJID), LJID)). build_roster_item(U, S, {add, Nick, Subs, Group}) -> - {xmlel, <<"item">>, - [{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))}, - {<<"name">>, Nick}, - {<<"subscription">>, Subs}], - [{xmlel, <<"group">>, [], [{xmlcdata, Group}]}] - }; + Groups = binary:split(Group,<<";">>, [global]), + #roster_item{jid = jid:make(U, S), + name = Nick, + subscription = misc:binary_to_atom(Subs), + groups = Groups}; build_roster_item(U, S, remove) -> - {xmlel, <<"item">>, - [{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))}, - {<<"subscription">>, <<"remove">>}], - [] - }. + #roster_item{jid = jid:make(U, S), subscription = remove}. build_iq_roster_push(Item) -> - {xmlel, <<"iq">>, - [{<<"type">>, <<"set">>}, {<<"id">>, <<"push">>}], - [{xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_ROSTER}], - [Item] - } - ] - }. + #iq{type = set, id = <<"push">>, + sub_els = [#roster_query{items = [Item]}]}. build_broadcast(U, S, {add, _Nick, Subs, _Group}) -> build_broadcast(U, S, list_to_atom(binary_to_list(Subs))); @@ -1234,31 +1369,30 @@ build_broadcast(U, S, remove) -> %% @spec (U::binary(), S::binary(), Subs::atom()) -> any() %% Subs = both | from | to | none build_broadcast(U, S, SubsAtom) when is_atom(SubsAtom) -> - {broadcast, {item, {U, S, <<>>}, SubsAtom}}. + {item, {U, S, <<>>}, SubsAtom}. %%% %%% Last Activity %%% get_last(User, Server) -> - case ejabberd_sm:get_user_resources(User, Server) of + {Now, Status} = case ejabberd_sm:get_user_resources(User, Server) of [] -> case mod_last:get_last_info(User, Server) of not_found -> - "Never"; - {ok, Shift, Status} -> - TimeStamp = {Shift div 1000000, - Shift rem 1000000, - 0}, - {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:now_to_local_time(TimeStamp), - lists:flatten( - io_lib:format( - "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w ~s", - [Year, Month, Day, Hour, Minute, Second, Status])) + {erlang:timestamp(), "NOT FOUND"}; + {ok, Shift, Status1} -> + {{Shift div 1000000, Shift rem 1000000, 0}, Status1} end; _ -> - "Online" + {erlang:timestamp(), "ONLINE"} + end, + {xmpp_util:encode_timestamp(Now), Status}. + +set_last(User, Server, Timestamp, Status) -> + case mod_last:store_last_info(User, Server, Timestamp, Status) of + {ok, _} -> ok; + Error -> Error end. %%% @@ -1271,17 +1405,10 @@ get_last(User, Server) -> %% <aa xmlns='bb'>Cluth</aa> private_get(Username, Host, Element, Ns) -> - From = jid:make(Username, Host, <<>>), - To = jid:make(Username, Host, <<>>), - IQ = {iq, <<>>, get, ?NS_PRIVATE, <<>>, - {xmlel, <<"query">>, - [{<<"xmlns">>,?NS_PRIVATE}], - [{xmlel, Element, [{<<"xmlns">>, Ns}], []}]}}, - ResIq = mod_private:process_sm_iq(From, To, IQ), - [{xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_PRIVATE}], - [SubEl]}] = ResIq#iq.sub_el, - binary_to_list(fxml:element_to_binary(SubEl)). + ElementXml = #xmlel{name = Element, attrs = [{<<"xmlns">>, Ns}]}, + Els = mod_private:get_data(jid:nodeprep(Username), jid:nameprep(Host), + [{Ns, ElementXml}]), + binary_to_list(fxml:element_to_binary(xmpp:encode(#private{sub_els = Els}))). private_set(Username, Host, ElementString) -> case fxml_stream:parse_element(ElementString) of @@ -1294,14 +1421,9 @@ private_set(Username, Host, ElementString) -> end. private_set2(Username, Host, Xml) -> - From = jid:make(Username, Host, <<>>), - To = jid:make(Username, Host, <<>>), - IQ = {iq, <<>>, set, ?NS_PRIVATE, <<>>, - {xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_PRIVATE}], - [Xml]}}, - mod_private:process_sm_iq(From, To, IQ), - ok. + NS = fxml:get_tag_attr_s(<<"xmlns">>, Xml), + JID = jid:make(Username, Host), + mod_private:set_data(JID, [{NS, Xml}]). %%% %%% Shared Roster Groups @@ -1330,23 +1452,24 @@ srg_get_info(Group, Host) -> Os when is_list(Os) -> Os; error -> [] end, - [{jlib:atom_to_binary(Title), btl(Value)} || {Title, Value} <- Opts]. + [{misc:atom_to_binary(Title), to_list(Value)} || {Title, Value} <- Opts]. -btl([]) -> []; -btl([B|L]) -> [btl(B)|btl(L)]; -btl(B) -> binary_to_list(B). +to_list([]) -> []; +to_list([H|T]) -> [to_list(H)|to_list(T)]; +to_list(E) when is_atom(E) -> atom_to_list(E); +to_list(E) -> binary_to_list(E). srg_get_members(Group, Host) -> Members = mod_shared_roster:get_group_explicit_users(Host,Group), - [jid:to_string(jid:make(MUser, MServer, <<>>)) + [jid:encode(jid:make(MUser, MServer)) || {MUser, MServer} <- Members]. srg_user_add(User, Host, Group, GroupHost) -> - {atomic, _} = mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group), + mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group), ok. srg_user_del(User, Host, Group, GroupHost) -> - {atomic, _} = mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group), + mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group), ok. @@ -1357,91 +1480,68 @@ srg_user_del(User, Host, Group, GroupHost) -> %% @doc Send a message to a Jabber account. %% @spec (Type::binary(), From::binary(), To::binary(), Subject::binary(), Body::binary()) -> ok send_message(Type, From, To, Subject, Body) -> - Packet = build_packet(Type, Subject, Body), - send_packet_all_resources(From, To, Packet). - -%% @doc Send a packet to a Jabber account. -%% If a resource was specified in the JID, -%% the packet is sent only to that specific resource. -%% If no resource was specified in the JID, -%% and the user is remote or local but offline, -%% the packet is sent to the bare JID. -%% If the user is local and is online in several resources, -%% the packet is sent to all its resources. -send_packet_all_resources(FromJIDString, ToJIDString, Packet) -> - FromJID = jid:from_string(FromJIDString), - ToJID = jid:from_string(ToJIDString), - ToUser = ToJID#jid.user, - ToServer = ToJID#jid.server, - case ToJID#jid.resource of - <<>> -> - send_packet_all_resources(FromJID, ToUser, ToServer, Packet); - Res -> - send_packet_all_resources(FromJID, ToUser, ToServer, Res, Packet) - end. - -send_packet_all_resources(FromJID, ToUser, ToServer, Packet) -> - case ejabberd_sm:get_user_resources(ToUser, ToServer) of - [] -> - send_packet_all_resources(FromJID, ToUser, ToServer, <<>>, Packet); - ToResources -> - lists:foreach( - fun(ToResource) -> - send_packet_all_resources(FromJID, ToUser, ToServer, - ToResource, Packet) - end, - ToResources) - end. - -send_packet_all_resources(FromJID, ToU, ToS, ToR, Packet) -> - ToJID = jid:make(ToU, ToS, ToR), - ejabberd_router:route(FromJID, ToJID, Packet). - -build_packet(Type, Subject, Body) -> - Tail = if Subject == <<"">>; Type == <<"chat">> -> []; - true -> [{xmlel, <<"subject">>, [], [{xmlcdata, Subject}]}] - end, - {xmlel, <<"message">>, - [{<<"type">>, Type}, {<<"id">>, randoms:get_string()}], - [{xmlel, <<"body">>, [], [{xmlcdata, Body}]} | Tail] - }. + FromJID = jid:decode(From), + ToJID = jid:decode(To), + Packet = build_packet(Type, Subject, Body, FromJID, ToJID), + State1 = #{jid => FromJID}, + ejabberd_hooks:run_fold(user_send_packet, FromJID#jid.lserver, {Packet, State1}, []), + ejabberd_router:route(xmpp:set_from_to(Packet, FromJID, ToJID)). + +build_packet(Type, Subject, Body, FromJID, ToJID) -> + #message{type = misc:binary_to_atom(Type), + body = xmpp:mk_text(Body), + from = FromJID, + to = ToJID, + id = p1_rand:get_string(), + subject = xmpp:mk_text(Subject)}. send_stanza(FromString, ToString, Stanza) -> - case fxml_stream:parse_element(Stanza) of - {error, Error} -> - {error, Error}; - XmlEl -> - #xmlel{attrs = Attrs} = XmlEl, - From = jid:from_string(proplists:get_value(<<"from">>, Attrs, FromString)), - To = jid:from_string(proplists:get_value(<<"to">>, Attrs, ToString)), - ejabberd_router:route(From, To, XmlEl) + try + #xmlel{} = El = fxml_stream:parse_element(Stanza), + From = jid:decode(FromString), + To = jid:decode(ToString), + CodecOpts = ejabberd_config:codec_options(), + Pkt = xmpp:decode(El, ?NS_CLIENT, CodecOpts), + ejabberd_router:route(xmpp:set_from_to(Pkt, From, To)) + catch _:{xmpp_codec, Why} -> + io:format("incorrect stanza: ~ts~n", [xmpp:format_error(Why)]), + {error, Why}; + _:{badmatch, {error, Why}} -> + io:format("invalid xml: ~p~n", [Why]), + {error, Why}; + _:{bad_jid, S} -> + io:format("malformed JID: ~ts~n", [S]), + {error, "JID malformed"} end. +-spec send_stanza_c2s(binary(), binary(), binary(), binary()) -> ok | {error, any()}. send_stanza_c2s(Username, Host, Resource, Stanza) -> - case {fxml_stream:parse_element(Stanza), - ejabberd_sm:get_session_pid(Username, Host, Resource)} - of - {{error, Error}, _} -> - {error, Error}; - {_, none} -> - {error, no_session}; - {XmlEl, C2sPid} -> - p1_fsm:send_event(C2sPid, {xmlstreamelement, XmlEl}) + 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} -> + io:format("incorrect stanza: ~ts~n", [xmpp:format_error(Why)]), + {error, Why} end. privacy_set(Username, Host, QueryS) -> - From = jid:make(Username, Host, <<"">>), - To = jid:make(<<"">>, Host, <<"">>), + Jid = jid:make(Username, Host), QueryEl = fxml_stream:parse_element(QueryS), - StanzaEl = {xmlel, <<"iq">>, [{<<"type">>, <<"set">>}], [QueryEl]}, - IQ = jlib:iq_query_info(StanzaEl), - ejabberd_hooks:run_fold( - privacy_iq_set, - Host, - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}, - [From, To, IQ] - ), - ok. + SubEl = xmpp:decode(QueryEl), + IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl], + from = Jid, to = Jid}, + Result = mod_privacy:process_iq(IQ), + Result#iq.type == result. %%% %%% Stats @@ -1451,175 +1551,35 @@ stats(Name) -> case Name of <<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000); <<"processes">> -> length(erlang:processes()); - <<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:get_vh_registered_users_number(Host) + Sum end, 0, ?MYHOSTS); + <<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:count_users(Host) + Sum end, 0, ejabberd_option:hosts()); <<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list()); <<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list()) end. stats(Name, Host) -> case Name of - <<"registeredusers">> -> ejabberd_auth:get_vh_registered_users_number(Host); + <<"registeredusers">> -> ejabberd_auth:count_users(Host); <<"onlineusers">> -> length(ejabberd_sm:get_vh_session_list(Host)) end. - -%%----------------------------- -%% Purge roster items -%%----------------------------- - -process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) -> - Action = case ActionS of - "list" -> list; - "delete" -> delete - end, - - Subs = lists:foldl( - fun(any, _) -> [none, from, to, both]; - (Sub, Subs) -> [Sub | Subs] - end, - [], - [list_to_atom(S) || S <- string:tokens(SubsS, ":")] - ), - - Asks = lists:foldl( - fun(any, _) -> [none, out, in]; - (Ask, Asks) -> [Ask | Asks] - end, - [], - [list_to_atom(S) || S <- string:tokens(AsksS, ":")] - ), - - Users = lists:foldl( - fun("any", _) -> ["*", "*@*"]; - (U, Us) -> [U | Us] - end, - [], - [S || S <- string:tokens(UsersS, ":")] - ), - - Contacts = lists:foldl( - fun("any", _) -> ["*", "*@*"]; - (U, Us) -> [U | Us] - end, - [], - [S || S <- string:tokens(ContactsS, ":")] - ), - - rosteritem_purge({Action, Subs, Asks, Users, Contacts}). - -%% @spec ({Action::atom(), Subs::[atom()], Asks::[atom()], User::string(), Contact::string()}) -> {atomic, ok} -rosteritem_purge(Options) -> - Num_rosteritems = mnesia:table_info(roster, size), - io:format("There are ~p roster items in total.~n", [Num_rosteritems]), - Key = mnesia:dirty_first(roster), - rip(Key, Options, {0, Num_rosteritems, 0, 0}, []). - -rip('$end_of_table', _Options, Counters, Res) -> - print_progress_line(Counters), - Res; -rip(Key, Options, {Pr, NT, NV, ND}, Res) -> - Key_next = mnesia:dirty_next(roster, Key), - {Action, _, _, _, _} = Options, - {ND2, Res2} = case decide_rip(Key, Options) of - true -> - Jids = apply_action(Action, Key), - {ND+1, [Jids | Res]}; - false -> - {ND, Res} - end, - NV2 = NV+1, - Pr2 = print_progress_line({Pr, NT, NV2, ND2}), - rip(Key_next, Options, {Pr2, NT, NV2, ND2}, Res2). - -apply_action(list, Key) -> - {User, Server, JID} = Key, - {RUser, RServer, _} = JID, - Jid1string = <<User/binary, "@", Server/binary>>, - Jid2string = <<RUser/binary, "@", RServer/binary>>, - io:format("Matches: ~s ~s~n", [Jid1string, Jid2string]), - {Jid1string, Jid2string}; -apply_action(delete, Key) -> - R = apply_action(list, Key), - mnesia:dirty_delete(roster, Key), - R. - -print_progress_line({_Pr, 0, _NV, _ND}) -> - ok; -print_progress_line({Pr, NT, NV, ND}) -> - Pr2 = trunc((NV/NT)*100), - case Pr == Pr2 of - true -> - ok; - false -> - io:format("Progress ~p% - visited ~p - deleted ~p~n", [Pr2, NV, ND]) - end, - Pr2. - -decide_rip(Key, {_Action, Subs, Asks, User, Contact}) -> - case catch mnesia:dirty_read(roster, Key) of - [RI] -> - lists:member(RI#roster.subscription, Subs) - andalso lists:member(RI#roster.ask, Asks) - andalso decide_rip_jid(RI#roster.us, User) - andalso decide_rip_jid(RI#roster.jid, Contact); - _ -> - false - end. - -%% Returns true if the server of the JID is included in the servers -decide_rip_jid({UName, UServer, _UResource}, Match_list) -> - decide_rip_jid({UName, UServer}, Match_list); -decide_rip_jid({UName, UServer}, Match_list) -> - lists:any( - fun(Match_string) -> - MJID = jid:from_string(list_to_binary(Match_string)), - MName = MJID#jid.luser, - MServer = MJID#jid.lserver, - Is_server = is_glob_match(UServer, MServer), - case MName of - <<>> when UName == <<>> -> - Is_server; - <<>> -> - false; - _ -> - Is_server - andalso is_glob_match(UName, MName) - end - end, - Match_list). - user_action(User, Server, Fun, OK) -> - case ejabberd_auth:is_user_exists(User, Server) of + case ejabberd_auth:user_exists(User, Server) of true -> - case catch Fun() of + case catch Fun() of OK -> ok; - {error, Error} -> throw(Error); + {error, Error} -> throw(Error); Error -> ?ERROR_MSG("Command returned: ~p", [Error]), - 1 - end; - false -> - throw({not_found, "unknown_user"}) + 1 + end; + false -> + throw({not_found, "unknown_user"}) end. -%% Copied from ejabberd-2.0.0/src/acl.erl -is_regexp_match(String, RegExp) -> - case ejabberd_regexp:run(String, RegExp) of - nomatch -> - false; - match -> - true; - {error, ErrDesc} -> - io:format( - "Wrong regexp ~p in ACL: ~p", - [RegExp, ErrDesc]), - false - end. -is_glob_match(String, <<"!", Glob/binary>>) -> - not is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)); -is_glob_match(String, Glob) -> - is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)). +num_prio(Priority) when is_integer(Priority) -> + Priority; +num_prio(_) -> + -1. -mod_opt_type(module_resource) -> fun (A) -> A end; -mod_opt_type(_) -> [module_resource]. +mod_options(_) -> []. |