aboutsummaryrefslogtreecommitdiff
path: root/src/ejabberd_admin.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/ejabberd_admin.erl')
-rw-r--r--src/ejabberd_admin.erl418
1 files changed, 410 insertions, 8 deletions
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index 44b7213ea..b02d83be3 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -1,15 +1,11 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_admin.erl
%%% Author : Mickael Remond <mremond@process-one.net>
-%%% Description : This module gathers admin functions used by different
-%%% access method:
-%%% - ejabberdctl command-line tool
-%%% - web admin interface
-%%% - adhoc mode
+%%% Purpose : Administrative functions and commands
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2008 Process-one
+%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@@ -20,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
-%%%
+%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@@ -31,9 +27,291 @@
-module(ejabberd_admin).
-author('mickael.remond@process-one.net').
--export([restore/1]).
+-export([start/0, stop/0,
+ %% Server
+ status/0, reopen_log/0,
+ %% Accounts
+ register/3, unregister/2,
+ registered_users/1,
+ %% Migration jabberd1.4
+ import_file/1, import_dir/1,
+ %% Purge DB
+ delete_expired_messages/0, delete_old_messages/1,
+ %% Mnesia
+ backup_mnesia/1, restore_mnesia/1,
+ dump_mnesia/1, dump_table/2, load_mnesia/1,
+ install_fallback_mnesia/1,
+ dump_to_textfile/1, dump_to_textfile/2,
+ mnesia_change_nodename/4,
+ restore/1 % Still used by some modules
+ ]).
-include("ejabberd.hrl").
+-include("ejabberd_commands.hrl").
+
+start() ->
+ ejabberd_commands:register_commands(commands()).
+
+stop() ->
+ ejabberd_commands:unregister_commands(commands()).
+
+%%%
+%%% ejabberd commands
+%%%
+
+commands() ->
+ [
+ %% The commands status, stop and restart are implemented also in ejabberd_ctl
+ %% They are defined here so that other interfaces can use them too
+ #ejabberd_commands{name = status, tags = [server],
+ desc = "Get status of the ejabberd server",
+ module = ?MODULE, function = status,
+ args = [], result = {res, restuple}},
+ #ejabberd_commands{name = stop, tags = [server],
+ desc = "Stop ejabberd gracefully",
+ module = init, function = stop,
+ args = [], result = {res, rescode}},
+ #ejabberd_commands{name = restart, tags = [server],
+ desc = "Restart ejabberd gracefully",
+ module = init, function = restart,
+ args = [], result = {res, rescode}},
+ #ejabberd_commands{name = reopen_log, tags = [logs, server],
+ desc = "Reopen the log files",
+ module = ?MODULE, function = reopen_log,
+ args = [], result = {res, rescode}},
+ #ejabberd_commands{name = get_loglevel, tags = [logs, server],
+ desc = "Get the current loglevel",
+ module = ejabberd_loglevel, function = get,
+ args = [],
+ result = {leveltuple, {tuple, [{levelnumber, integer},
+ {levelatom, atom},
+ {leveldesc, string}
+ ]}}},
+
+ #ejabberd_commands{name = register, tags = [accounts],
+ desc = "Register a user",
+ module = ?MODULE, function = register,
+ args = [{user, string}, {host, string}, {password, string}],
+ result = {res, restuple}},
+ #ejabberd_commands{name = unregister, tags = [accounts],
+ desc = "Unregister a user",
+ module = ?MODULE, function = unregister,
+ args = [{user, string}, {host, string}],
+ result = {res, restuple}},
+ #ejabberd_commands{name = registered_users, tags = [accounts],
+ desc = "List all registered users in HOST",
+ module = ?MODULE, function = registered_users,
+ args = [{host, string}],
+ result = {users, {list, {username, string}}}},
+
+ #ejabberd_commands{name = import_file, tags = [mnesia],
+ desc = "Import user data from jabberd14 spool file",
+ module = ?MODULE, function = import_file,
+ args = [{file, string}], result = {res, restuple}},
+ #ejabberd_commands{name = import_dir, tags = [mnesia],
+ desc = "Import users data from jabberd14 spool dir",
+ module = ?MODULE, function = import_dir,
+ args = [{file, string}],
+ result = {res, restuple}},
+
+ #ejabberd_commands{name = import_piefxis, tags = [mnesia],
+ desc = "Import users data from a PIEFXIS file (XEP-0227)",
+ module = ejabberd_piefxis, function = import_file,
+ args = [{file, string}], result = {res, rescode}},
+ #ejabberd_commands{name = export_piefxis, tags = [mnesia],
+ desc = "Export data of all users in the server to PIEFXIS files (XEP-0227)",
+ module = ejabberd_piefxis, function = export_server,
+ args = [{dir, string}], result = {res, rescode}},
+ #ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
+ desc = "Export data of users in a host to PIEFXIS files (XEP-0227)",
+ module = ejabberd_piefxis, function = export_host,
+ args = [{dir, string}, {host, string}], result = {res, rescode}},
+
+ #ejabberd_commands{name = delete_expired_messages, tags = [purge],
+ desc = "Delete expired offline messages from database",
+ module = ?MODULE, function = delete_expired_messages,
+ args = [], result = {res, rescode}},
+ #ejabberd_commands{name = delete_old_messages, tags = [purge],
+ desc = "Delete offline messages older than DAYS",
+ module = ?MODULE, function = delete_old_messages,
+ args = [{days, integer}], result = {res, rescode}},
+
+ #ejabberd_commands{name = rename_default_nodeplugin, tags = [mnesia],
+ desc = "Update PubSub table from ejabberd trunk SVN to 2.1.0",
+ module = mod_pubsub, function = rename_default_nodeplugin,
+ args = [], result = {res, rescode}},
+
+ #ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
+ desc = "Change the erlang node name in a backup file",
+ module = ?MODULE, function = mnesia_change_nodename,
+ args = [{oldnodename, string}, {newnodename, string},
+ {oldbackup, string}, {newbackup, string}],
+ result = {res, restuple}},
+ #ejabberd_commands{name = backup, tags = [mnesia],
+ desc = "Store the database to backup file",
+ module = ?MODULE, function = backup_mnesia,
+ args = [{file, string}], result = {res, restuple}},
+ #ejabberd_commands{name = restore, tags = [mnesia],
+ desc = "Restore the database from backup file",
+ module = ?MODULE, function = restore_mnesia,
+ args = [{file, string}], result = {res, restuple}},
+ #ejabberd_commands{name = dump, tags = [mnesia],
+ desc = "Dump the database to text file",
+ module = ?MODULE, function = dump_mnesia,
+ args = [{file, string}], result = {res, restuple}},
+ #ejabberd_commands{name = dump_table, tags = [mnesia],
+ desc = "Dump a table to text file",
+ module = ?MODULE, function = dump_table,
+ args = [{file, string}, {table, string}], result = {res, restuple}},
+ #ejabberd_commands{name = load, tags = [mnesia],
+ desc = "Restore the database from text file",
+ module = ?MODULE, function = load_mnesia,
+ args = [{file, string}], result = {res, restuple}},
+ #ejabberd_commands{name = install_fallback, tags = [mnesia],
+ desc = "Install the database from a fallback file",
+ module = ?MODULE, function = install_fallback_mnesia,
+ args = [{file, string}], result = {res, restuple}}
+ ].
+
+
+%%%
+%%% Server management
+%%%
+
+status() ->
+ {InternalStatus, ProvidedStatus} = init:get_status(),
+ String1 = io_lib:format("The node ~p is ~p. Status: ~p",
+ [node(), InternalStatus, ProvidedStatus]),
+ {Is_running, String2} =
+ case lists:keysearch(ejabberd, 1, application:which_applications()) of
+ false ->
+ {ejabberd_not_running, "ejabberd is not running in that node."};
+ {value, {_, _, Version}} ->
+ {ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
+ end,
+ {Is_running, String1 ++ String2}.
+
+reopen_log() ->
+ ejabberd_hooks:run(reopen_log_hook, []),
+ %% TODO: Use the Reopen log API for logger_h ?
+ ejabberd_logger_h:reopen_log(),
+ case application:get_env(sasl,sasl_error_logger) of
+ {ok, {file, SASLfile}} ->
+ error_logger:delete_report_handler(sasl_report_file_h),
+ ejabberd_logger_h:rotate_log(SASLfile),
+ error_logger:add_report_handler(sasl_report_file_h,
+ {SASLfile, get_sasl_error_logger_type()});
+ _ -> false
+ end,
+ ok.
+
+%% Function copied from Erlang/OTP lib/sasl/src/sasl.erl which doesn't export it
+get_sasl_error_logger_type () ->
+ case application:get_env (sasl, errlog_type) of
+ {ok, error} -> error;
+ {ok, progress} -> progress;
+ {ok, all} -> all;
+ {ok, Bad} -> exit ({bad_config, {sasl, {errlog_type, Bad}}});
+ _ -> all
+ end.
+
+%%%
+%%% Account management
+%%%
+
+register(User, Host, Password) ->
+ case ejabberd_auth:try_register(User, Host, Password) of
+ {atomic, ok} ->
+ {ok, io_lib:format("User ~s@~s succesfully registered", [User, Host])};
+ {atomic, exists} ->
+ String = io_lib:format("User ~s@~s already registered at node ~p",
+ [User, Host, node()]),
+ {exists, String};
+ {error, Reason} ->
+ String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
+ [User, Host, node(), Reason]),
+ {cannot_register, String}
+ end.
+
+unregister(User, Host) ->
+ ejabberd_auth:remove_user(User, Host),
+ {ok, ""}.
+
+registered_users(Host) ->
+ Users = ejabberd_auth:get_vh_registered_users(Host),
+ SUsers = lists:sort(Users),
+ lists:map(fun({U, _S}) -> U end, SUsers).
+
+
+%%%
+%%% Migration management
+%%%
+
+import_file(Path) ->
+ case jd2ejd:import_file(Path) of
+ ok ->
+ {ok, ""};
+ {error, Reason} ->
+ String = io_lib:format("Can't import jabberd14 spool file ~p at node ~p: ~p",
+ [filename:absname(Path), node(), Reason]),
+ {cannot_import_file, String}
+ end.
+
+import_dir(Path) ->
+ case jd2ejd:import_dir(Path) of
+ ok ->
+ {ok, ""};
+ {error, Reason} ->
+ String = io_lib:format("Can't import jabberd14 spool dir ~p at node ~p: ~p",
+ [filename:absname(Path), node(), Reason]),
+ {cannot_import_dir, String}
+ end.
+
+
+%%%
+%%% Purge DB
+%%%
+
+delete_expired_messages() ->
+ {atomic, ok} = mod_offline:remove_expired_messages(),
+ ok.
+
+delete_old_messages(Days) ->
+ {atomic, _} = mod_offline:remove_old_messages(Days),
+ ok.
+
+
+%%%
+%%% Mnesia management
+%%%
+
+backup_mnesia(Path) ->
+ case mnesia:backup(Path) of
+ ok ->
+ {ok, ""};
+ {error, Reason} ->
+ String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
+ [filename:absname(Path), node(), Reason]),
+ {cannot_backup, String}
+ end.
+
+restore_mnesia(Path) ->
+ case ejabberd_admin:restore(Path) of
+ {atomic, _} ->
+ {ok, ""};
+ {error, Reason} ->
+ String = io_lib:format("Can't restore backup from ~p at node ~p: ~p",
+ [filename:absname(Path), node(), Reason]),
+ {cannot_restore, String};
+ {aborted,{no_exists,Table}} ->
+ String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
+ [filename:absname(Path), node(), Table]),
+ {table_not_exists, String};
+ {aborted,enoent} ->
+ String = io_lib:format("Can't restore backup from ~p at node ~p: File not found.",
+ [filename:absname(Path), node()]),
+ {file_not_found, String}
+ end.
%% Mnesia database restore
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
@@ -71,3 +349,127 @@ module_tables(mod_roster) -> [roster];
module_tables(mod_shared_roster) -> [sr_group, sr_user];
module_tables(mod_vcard) -> [vcard, vcard_search];
module_tables(_Other) -> [].
+
+get_local_tables() ->
+ Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
+ Tabs = lists:filter(
+ fun(T) ->
+ case mnesia:table_info(T, storage_type) of
+ disc_copies -> true;
+ disc_only_copies -> true;
+ _ -> false
+ end
+ end, Tabs1),
+ Tabs.
+
+dump_mnesia(Path) ->
+ Tabs = get_local_tables(),
+ dump_tables(Path, Tabs).
+
+dump_table(Path, STable) ->
+ Table = list_to_atom(STable),
+ dump_tables(Path, [Table]).
+
+dump_tables(Path, Tables) ->
+ case dump_to_textfile(Path, Tables) of
+ ok ->
+ {ok, ""};
+ {error, Reason} ->
+ String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
+ [filename:absname(Path), node(), Reason]),
+ {cannot_dump, String}
+ end.
+
+dump_to_textfile(File) ->
+ Tabs = get_local_tables(),
+ dump_to_textfile(File, Tabs).
+
+dump_to_textfile(File, Tabs) ->
+ dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])).
+dump_to_textfile(yes, Tabs, {ok, F}) ->
+ Defs = lists:map(
+ fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
+ {attributes, mnesia:table_info(T, attributes)}]}
+ end,
+ Tabs),
+ io:format(F, "~p.~n", [{tables, Defs}]),
+ lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
+ file:close(F);
+dump_to_textfile(_, _, {ok, F}) ->
+ file:close(F),
+ {error, mnesia_not_running};
+dump_to_textfile(_, _, {error, Reason}) ->
+ {error, Reason}.
+
+dump_tab(F, T) ->
+ W = mnesia:table_info(T, wild_pattern),
+ {atomic,All} = mnesia:transaction(
+ fun() -> mnesia:match_object(T, W, read) end),
+ lists:foreach(
+ fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
+
+load_mnesia(Path) ->
+ case mnesia:load_textfile(Path) of
+ {atomic, ok} ->
+ {ok, ""};
+ {error, Reason} ->
+ String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
+ [filename:absname(Path), node(), Reason]),
+ {cannot_load, String}
+ end.
+
+install_fallback_mnesia(Path) ->
+ case mnesia:install_fallback(Path) of
+ ok ->
+ {ok, ""};
+ {error, Reason} ->
+ String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
+ [filename:absname(Path), node(), Reason]),
+ {cannot_fallback, String}
+ end.
+
+mnesia_change_nodename(FromString, ToString, Source, Target) ->
+ From = list_to_atom(FromString),
+ To = list_to_atom(ToString),
+ Switch =
+ fun
+ (Node) when Node == From ->
+ io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]),
+ To;
+ (Node) when Node == To ->
+ %% throw({error, already_exists});
+ io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]),
+ Node;
+ (Node) ->
+ io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]),
+ Node
+ end,
+ Convert =
+ fun
+ ({schema, db_nodes, Nodes}, Acc) ->
+ io:format(" +++ db_nodes ~p~n", [Nodes]),
+ {[{schema, db_nodes, lists:map(Switch,Nodes)}], Acc};
+ ({schema, version, Version}, Acc) ->
+ io:format(" +++ version: ~p~n", [Version]),
+ {[{schema, version, Version}], Acc};
+ ({schema, cookie, Cookie}, Acc) ->
+ io:format(" +++ cookie: ~p~n", [Cookie]),
+ {[{schema, cookie, Cookie}], Acc};
+ ({schema, Tab, CreateList}, Acc) ->
+ io:format("~n * Checking table: '~p'~n", [Tab]),
+ Keys = [ram_copies, disc_copies, disc_only_copies],
+ OptSwitch =
+ fun({Key, Val}) ->
+ case lists:member(Key, Keys) of
+ true ->
+ io:format(" + Checking key: '~p'~n", [Key]),
+ {Key, lists:map(Switch, Val)};
+ false-> {Key, Val}
+ end
+ end,
+ Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc},
+ Res;
+ (Other, Acc) ->
+ {[Other], Acc}
+ end,
+ mnesia:traverse_backup(Source, Target, Convert, switched).