mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-26 17:38:45 +01:00
added antiflood and filter modules from TeamLeader
This commit is contained in:
parent
db2baa8f84
commit
b6dcd41225
205
src/floodcheck.erl
Normal file
205
src/floodcheck.erl
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% File : floodcheck.erl
|
||||||
|
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
||||||
|
%%% Description :
|
||||||
|
%%%
|
||||||
|
%%% Created : 11 Sep 2008 by Christophe Romain <christophe.romain@process-one.net>
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(floodcheck).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/0, stop/0]).
|
||||||
|
-export([monitor/5, demonitor/1, interval/1, check/0]).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-define(DEFAULT_INTERVAL, 300). %% check every 5mn
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
|
-record(state, {timer, interval, monitors}).
|
||||||
|
-record(monitor, {id, pid, ref, info, rule, value, handler}).
|
||||||
|
|
||||||
|
%%====================================================================
|
||||||
|
%% API
|
||||||
|
%%====================================================================
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||||
|
%% Description: Starts the server
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
start_link() ->
|
||||||
|
case whereis(?SERVER) of
|
||||||
|
undefined -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []);
|
||||||
|
Pid -> {ok, Pid}
|
||||||
|
end.
|
||||||
|
|
||||||
|
stop() ->
|
||||||
|
gen_server:call(?SERVER, stop).
|
||||||
|
|
||||||
|
monitor(Id, Pid, Info, Spec, {Mod, Fun}) ->
|
||||||
|
gen_server:cast(?SERVER, {monitor, Id, Pid, Info, Spec, {Mod, Fun}}).
|
||||||
|
|
||||||
|
demonitor(Id) ->
|
||||||
|
gen_server:cast(?SERVER, {demonitor, Id}).
|
||||||
|
|
||||||
|
interval(Value) ->
|
||||||
|
gen_server:cast(?SERVER, {interval, Value}).
|
||||||
|
|
||||||
|
check() ->
|
||||||
|
gen_server:call(?SERVER, check).
|
||||||
|
|
||||||
|
%%====================================================================
|
||||||
|
%% gen_server callbacks
|
||||||
|
%%====================================================================
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Function: init(Args) -> {ok, State} |
|
||||||
|
%% {ok, State, Timeout} |
|
||||||
|
%% ignore |
|
||||||
|
%% {stop, Reason}
|
||||||
|
%% Description: Initiates the server
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
init([]) ->
|
||||||
|
Timer = erlang:send_after(?DEFAULT_INTERVAL*1000, ?SERVER, monitor),
|
||||||
|
{ok, #state{timer=Timer, interval=?DEFAULT_INTERVAL, monitors=[]}}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||||
|
%% {reply, Reply, State, Timeout} |
|
||||||
|
%% {noreply, State} |
|
||||||
|
%% {noreply, State, Timeout} |
|
||||||
|
%% {stop, Reason, Reply, State} |
|
||||||
|
%% {stop, Reason, State}
|
||||||
|
%% Description: Handling call messages
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_call(check, _From, State) ->
|
||||||
|
Reply = lists:map(fun(#monitor{id=Id}=M) ->
|
||||||
|
{Id, check(M)}
|
||||||
|
end, State#state.monitors),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call(stop, _From, State) ->
|
||||||
|
erlang:cancel_timer(State#state.timer),
|
||||||
|
{stop, normal, ok, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||||
|
%% {noreply, State, Timeout} |
|
||||||
|
%% {stop, Reason, State}
|
||||||
|
%% Description: Handling cast messages
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_cast({monitor, Id, Pid, Info, Spec, Handler}, State) ->
|
||||||
|
Monitors = State#state.monitors,
|
||||||
|
Ref = erlang:monitor(process, Pid),
|
||||||
|
{Rule, Value} = case Spec of
|
||||||
|
{Op, V} -> {Op, V};
|
||||||
|
V -> {'>', V}
|
||||||
|
end,
|
||||||
|
Monitor = #monitor{id=Id, pid=Pid, ref=Ref, info=Info, rule=Rule, value=Value, handler=Handler},
|
||||||
|
New = case lists:keysearch(Id, #monitor.id, Monitors) of
|
||||||
|
{value, #monitor{ref=OldRef}} ->
|
||||||
|
erlang:demonitor(OldRef),
|
||||||
|
lists:keyreplace(Id, #monitor.id, Monitors, Monitor);
|
||||||
|
_ ->
|
||||||
|
[Monitor|Monitors]
|
||||||
|
end,
|
||||||
|
{noreply, State#state{monitors=New}};
|
||||||
|
handle_cast({demonitor, Id}, State) ->
|
||||||
|
Monitors = State#state.monitors,
|
||||||
|
New = case lists:keysearch(Id, #monitor.id, Monitors) of
|
||||||
|
{value, #monitor{ref=Ref}} ->
|
||||||
|
erlang:demonitor(Ref),
|
||||||
|
lists:keydelete(Id, #monitor.id, Monitors);
|
||||||
|
_ ->
|
||||||
|
Monitors
|
||||||
|
end,
|
||||||
|
{noreply, State#state{monitors=New}};
|
||||||
|
handle_cast({interval, Value}, State) ->
|
||||||
|
erlang:cancel_timer(State#state.timer),
|
||||||
|
Timer = erlang:send_after(Value*1000, ?SERVER, monitor),
|
||||||
|
{noreply, State#state{timer=Timer, interval=Value}};
|
||||||
|
handle_cast(_Msg, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||||
|
%% {noreply, State, Timeout} |
|
||||||
|
%% {stop, Reason, State}
|
||||||
|
%% Description: Handling all non call/cast messages
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_info({'DOWN', Ref, _Type, _Pid, _Info}, State) ->
|
||||||
|
Monitors = State#state.monitors,
|
||||||
|
New = lists:keydelete(Ref, #monitor.ref, Monitors),
|
||||||
|
{noreply, State#state{monitors=New}};
|
||||||
|
handle_info(monitor, State) ->
|
||||||
|
lists:foreach(fun(#monitor{id=Id, pid=Pid, info=Info, handler={Mod, Fun}}=M) ->
|
||||||
|
case check(M) of
|
||||||
|
ok -> ok;
|
||||||
|
Value -> spawn(Mod, Fun, [Id, Pid, Info, Value])
|
||||||
|
end
|
||||||
|
end, State#state.monitors),
|
||||||
|
Timer = erlang:send_after(State#state.interval*1000, ?SERVER, monitor),
|
||||||
|
{noreply, State#state{timer=Timer}};
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Function: terminate(Reason, State) -> void()
|
||||||
|
%% Description: This function is called by a gen_server when it is about to
|
||||||
|
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||||
|
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||||
|
%% The return value is ignored.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
terminate(_Reason, _State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||||
|
%% Description: Convert process state when code is changed
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%% Internal functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
check(#monitor{pid=Pid, info=Info, rule=Rule, value=Value}) ->
|
||||||
|
case catch process_info(Pid, Info) of
|
||||||
|
{Info, Actual} ->
|
||||||
|
Check = case Info of
|
||||||
|
messages -> byte_size(term_to_binary(Actual));
|
||||||
|
dictionary -> byte_size(term_to_binary(Actual));
|
||||||
|
_ -> Actual
|
||||||
|
end,
|
||||||
|
case Rule of
|
||||||
|
'>' ->
|
||||||
|
if Check > Value -> Value;
|
||||||
|
true -> ok
|
||||||
|
end;
|
||||||
|
'<' ->
|
||||||
|
if Check < Value -> Value;
|
||||||
|
true -> ok
|
||||||
|
end;
|
||||||
|
'=' ->
|
||||||
|
if Check == Value -> Value;
|
||||||
|
true -> ok
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%% Documentation
|
||||||
|
%%% authorized Info
|
||||||
|
%%% message_queue_len: number of messages
|
||||||
|
%%% messages: messages queue size in bytes
|
||||||
|
%%% dictionary: dictionary size in bytes
|
||||||
|
%%% total_heap_size: total size in words of all heap fragments
|
||||||
|
%%% heap_size: size in words of youngest heap generation
|
||||||
|
%%% stack_size: stack size in words
|
||||||
|
%%% reductions: number of reductions executed by the process
|
||||||
|
%%% memory: process size in bytes
|
186
src/mod_antiflood.erl
Normal file
186
src/mod_antiflood.erl
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% File : mod_antiflood.erl
|
||||||
|
%%% Author : Christophe Romain <cromain@process-one.net>
|
||||||
|
%%% Description :
|
||||||
|
%%% Created : 12 Sep 2008 by Christophe Romain <cromain@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%% 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
|
||||||
|
%%% 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
|
||||||
|
%%%
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(mod_antiflood).
|
||||||
|
-author('cromain@process-one.net').
|
||||||
|
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start/2, stop/1]).
|
||||||
|
-export([handler/4]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
|
||||||
|
%%====================================================================
|
||||||
|
%% API
|
||||||
|
%%====================================================================
|
||||||
|
start(Host, Opts) ->
|
||||||
|
floodcheck:start_link(),
|
||||||
|
lists:foreach(fun
|
||||||
|
({listeners, Info, Op, Val}) ->
|
||||||
|
lists:foreach(fun({Name, Pid}) ->
|
||||||
|
Id = {Name, Info},
|
||||||
|
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler})
|
||||||
|
end, supervised_processes(listeners));
|
||||||
|
({Module, Info, Op, Val}) ->
|
||||||
|
case module_pid(Host, Module) of
|
||||||
|
Pid when is_pid(Pid) ->
|
||||||
|
Id = {Host, Module, Info},
|
||||||
|
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
|
||||||
|
Error ->
|
||||||
|
?INFO_MSG("can not monitor ~s (~p)", [Module, Error])
|
||||||
|
end;
|
||||||
|
(Arg) ->
|
||||||
|
?INFO_MSG("invalid argument: ~p", [Arg])
|
||||||
|
end, Opts).
|
||||||
|
|
||||||
|
stop(Host) ->
|
||||||
|
MList = gen_mod:loaded_modules_with_opts(Host),
|
||||||
|
case lists:keysearch(?MODULE, 1, MList) of
|
||||||
|
{value, {?MODULE, Opts}} ->
|
||||||
|
lists:foreach(fun
|
||||||
|
({Type, Info, _, _}) ->
|
||||||
|
case supervised_processes(Type) of
|
||||||
|
[] ->
|
||||||
|
Id = {Host, Type, Info},
|
||||||
|
floodcheck:demonitor(Id);
|
||||||
|
Childs ->
|
||||||
|
lists:foreach(fun({Name, _}) ->
|
||||||
|
Id = {Name, Info},
|
||||||
|
floodcheck:demonitor(Id)
|
||||||
|
end, Childs)
|
||||||
|
end;
|
||||||
|
(_) ->
|
||||||
|
ok
|
||||||
|
end, Opts);
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
case floodcheck:check() of
|
||||||
|
[] -> floodcheck:stop(), ok;
|
||||||
|
_ -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
handler({Host, Module, Info}, Pid, Info, Value) ->
|
||||||
|
?WARNING_MSG("Flood alert on Process ~p (~s on ~s): ~s=~p", [Pid, Module, Host, Info, Value]),
|
||||||
|
restart_module(Host, Module),
|
||||||
|
remonitor({Host, Module, Info});
|
||||||
|
handler({Name, Info}, Pid, Info, Value) ->
|
||||||
|
?WARNING_MSG("Flood alert on Process ~p (~s): ~s=~p", [Pid, Name, Info, Value]),
|
||||||
|
kill_process(Name, Pid),
|
||||||
|
remonitor({Name, Info});
|
||||||
|
handler(Id, Pid, Info, Value) ->
|
||||||
|
?WARNING_MSG("Flood alert on Process ~p (~s): ~s=~p~nUnknown id, alert ignored", [Pid, Id, Info, Value]).
|
||||||
|
|
||||||
|
|
||||||
|
%%====================================================================
|
||||||
|
%% Internal functions
|
||||||
|
%%====================================================================
|
||||||
|
|
||||||
|
process_pid(Name) -> whereis(Name).
|
||||||
|
server_pid(Host, Name) -> process_pid(gen_mod:get_module_proc(Host, Name)).
|
||||||
|
|
||||||
|
module_pid(Host, mod_caps) -> server_pid(Host, ejabberd_mod_caps);
|
||||||
|
module_pid(Host, mod_ip_blacklist) -> server_pid(Host, mod_ip_blacklist);
|
||||||
|
module_pid(Host, mod_offline) -> server_pid(Host, ejabberd_offline);
|
||||||
|
module_pid(Host, mod_offline_odbc) -> server_pid(Host, ejabberd_offline);
|
||||||
|
module_pid(Host, mod_vcard) -> server_pid(Host, ejabberd_mod_vcard);
|
||||||
|
module_pid(Host, mod_vcard_odbc) -> server_pid(Host, ejabberd_mod_vcard);
|
||||||
|
module_pid(Host, mod_vcard_ldap) -> server_pid(Host, ejabberd_mod_vcard_ldap);
|
||||||
|
module_pid(Host, mod_irc) -> server_pid(Host, ejabberd_mod_irc);
|
||||||
|
module_pid(Host, mod_muc) -> server_pid(Host, ejabberd_mod_muc);
|
||||||
|
module_pid(Host, mod_muc_log) -> server_pid(Host, ejabberd_mod_muc_log);
|
||||||
|
module_pid(Host, mod_proxy65) -> server_pid(Host, ejabberd_mod_proxy65);
|
||||||
|
module_pid(Host, mod_proxy65_service) -> server_pid(Host, ejabberd_mod_proxy65_service);
|
||||||
|
module_pid(Host, mod_proxy65_sm) -> server_pid(Host, ejabberd_mod_proxy65_sm);
|
||||||
|
module_pid(Host, mod_pubsub) -> server_pid(Host, ejabberd_mod_pubsub);
|
||||||
|
module_pid(_, _) -> unsupported.
|
||||||
|
|
||||||
|
supervised_processes(listeners) ->
|
||||||
|
{links, Links} = process_info(whereis(ejabberd_listeners), links),
|
||||||
|
lists:map(fun(Pid) ->
|
||||||
|
{dictionary, Dict} = process_info(Pid, dictionary),
|
||||||
|
{_, _, [Port|_]} = proplists:get_value('$initial_call', Dict),
|
||||||
|
Name = list_to_atom("listener_"++integer_to_list(Port)),
|
||||||
|
{Name, Pid}
|
||||||
|
end, Links);
|
||||||
|
supervised_processes(_) -> [].
|
||||||
|
|
||||||
|
remonitor({Host, Module, Info}) ->
|
||||||
|
MList = gen_mod:loaded_modules_with_opts(Host),
|
||||||
|
case lists:keysearch(?MODULE, 1, MList) of
|
||||||
|
{value, {?MODULE, Opts}} ->
|
||||||
|
lists:foreach(fun
|
||||||
|
({M, I, Op, Val}) when M =:= Module, I =:= Info ->
|
||||||
|
case module_pid(Host, Module) of
|
||||||
|
Pid when is_pid(Pid) ->
|
||||||
|
Id = {Host, Module, Info},
|
||||||
|
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
|
||||||
|
Error ->
|
||||||
|
?INFO_MSG("can not monitor ~s (~p)", [Module, Error])
|
||||||
|
end;
|
||||||
|
(_) ->
|
||||||
|
ok
|
||||||
|
end, Opts);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
remonitor({Name, Info}) ->
|
||||||
|
[Host|_] = ejabberd_config:get_global_option(hosts),
|
||||||
|
MList = gen_mod:loaded_modules_with_opts(Host),
|
||||||
|
case lists:keysearch(?MODULE, 1, MList) of
|
||||||
|
{value, {?MODULE, Opts}} ->
|
||||||
|
lists:foreach(fun
|
||||||
|
({Type, I, Op, Val}) when I =:= Info ->
|
||||||
|
lists:foreach(fun
|
||||||
|
({N, Pid}) when N =:= Name ->
|
||||||
|
Id = {Name, Info},
|
||||||
|
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
|
||||||
|
(_) ->
|
||||||
|
ok
|
||||||
|
end, supervised_processes(Type));
|
||||||
|
(_) ->
|
||||||
|
ok
|
||||||
|
end, Opts);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
remonitor(Id) ->
|
||||||
|
?INFO_MSG("can not monitor ~s", [Id]).
|
||||||
|
|
||||||
|
restart_module(Host, Module) ->
|
||||||
|
MList = gen_mod:loaded_modules_with_opts(Host),
|
||||||
|
case lists:keysearch(Module, 1, MList) of
|
||||||
|
{value, {Module, Opts}} ->
|
||||||
|
?WARNING_MSG("restarting module ~s on ~s", [Module, Host]),
|
||||||
|
gen_mod:stop_module(Host, Module),
|
||||||
|
gen_mod:start_module(Host, Module, Opts);
|
||||||
|
_ ->
|
||||||
|
not_running
|
||||||
|
end.
|
||||||
|
|
||||||
|
kill_process(Name, Pid) ->
|
||||||
|
?WARNING_MSG("killing process ~s(~p)", [Name, Pid]),
|
||||||
|
exit(Pid, kill).
|
350
src/mod_filter.erl
Normal file
350
src/mod_filter.erl
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
%%% ====================================================================
|
||||||
|
%%% This software is copyright 2006-2009, ProcessOne.
|
||||||
|
%%%
|
||||||
|
%%% mod_filter
|
||||||
|
%%% allow message filtering using regexp on message body
|
||||||
|
%%% THIS MODULE NEEDS A PATCHED ERLANG VM AGAINST
|
||||||
|
%%% THE PCRE PATCH AND NEEDS LIBPCRE INSTALLED
|
||||||
|
%%% ejabberd MUST USE THAT PATCHED ERLANG VM
|
||||||
|
%%% BUT, if patch is not available, mod_filter uses re.beam module
|
||||||
|
%%% instead, with speed degradation.
|
||||||
|
%%%
|
||||||
|
%%% @copyright 2006-2009 ProcessOne
|
||||||
|
%%% @author Christophe Romain <christophe.romain@process-one.net>
|
||||||
|
%%% [http://www.process-one.net/]
|
||||||
|
%%% @version {@vsn}, {@date} {@time}
|
||||||
|
%%% @end
|
||||||
|
%%% ====================================================================
|
||||||
|
|
||||||
|
-module(mod_filter).
|
||||||
|
-author('christophe.romain@process-one.net').
|
||||||
|
-vsn('$Id: mod_filter.erl 767 2009-04-20 16:31:40Z cromain $').
|
||||||
|
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
|
% module functions
|
||||||
|
-export([start/2,stop/1,init/2,update/2,is_loaded/0,loop/5]).
|
||||||
|
-export([add_regexp/4,add_regexp/3,del_regexp/3,del_regexp/2]).
|
||||||
|
-export([purge_logs/0,purge_regexps/1,reload/1]).
|
||||||
|
-export([logged/0,logged/1,rules/0]).
|
||||||
|
-export([process_local_iq/3]).
|
||||||
|
|
||||||
|
% handled ejabberd hooks
|
||||||
|
-export([filter_packet/1]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("jlib.hrl").
|
||||||
|
-include("licence.hrl").
|
||||||
|
|
||||||
|
-record(filter_rule, {id, type="filter", regexp, binre}).
|
||||||
|
-record(filter_log, {date, from, to, message}).
|
||||||
|
|
||||||
|
-define(TIMEOUT, 5000). % deliver message anyway if filter does not respond after 5s
|
||||||
|
-define(PROCNAME(VH), list_to_atom(VH++"_message_filter")).
|
||||||
|
-define(NS_FILTER, "p1:iq:filter").
|
||||||
|
|
||||||
|
-define(ALLHOSTS, "all hosts"). %% must be sync with filter.erl
|
||||||
|
|
||||||
|
start(Host, Opts) ->
|
||||||
|
case ?IS_VALID of
|
||||||
|
true ->
|
||||||
|
mnesia:create_table(filter_rule, [
|
||||||
|
{disc_copies, [node()]}, {type, set},
|
||||||
|
{attributes, record_info(fields, filter_rule)} ]),
|
||||||
|
mnesia:create_table(filter_log, [
|
||||||
|
{disc_only_copies, [node()]}, {type, bag},
|
||||||
|
{attributes, record_info(fields, filter_log)} ]),
|
||||||
|
%% this force the last code to be used
|
||||||
|
case whereis(?PROCNAME(Host)) of
|
||||||
|
undefined ->
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
ejabberd_hooks:delete(filter_packet, ?MODULE, filter_packet, 10),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_FILTER),
|
||||||
|
?PROCNAME(Host) ! quit
|
||||||
|
end,
|
||||||
|
ejabberd_hooks:add(filter_packet, ?MODULE, filter_packet, 10),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_FILTER,
|
||||||
|
?MODULE, process_local_iq, one_queue),
|
||||||
|
case whereis(?PROCNAME(Host)) of
|
||||||
|
undefined -> register(?PROCNAME(Host), spawn(?MODULE, init, [Host, Opts]));
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
%% start the all_alias handler
|
||||||
|
case whereis(?PROCNAME(?ALLHOSTS)) of
|
||||||
|
undefined -> init_all_hosts_handler();
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
start;
|
||||||
|
false ->
|
||||||
|
not_started
|
||||||
|
end.
|
||||||
|
|
||||||
|
stop(Host) ->
|
||||||
|
ejabberd_hooks:delete(filter_packet, ?MODULE, filter_packet, 10),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_FILTER),
|
||||||
|
exit(whereis(?PROCNAME(Host)), kill),
|
||||||
|
{wait, ?PROCNAME(Host)}.
|
||||||
|
|
||||||
|
% this is used by team_leader to check code presence
|
||||||
|
is_loaded() ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
% this loads rules and return {Types, BinRegExps}
|
||||||
|
% RegExps are text regexp list
|
||||||
|
% Types are regexp type list
|
||||||
|
% both list share the same ordered
|
||||||
|
load_rules(Host) ->
|
||||||
|
Rules = mnesia:dirty_match_object(#filter_rule{id={'_', Host}, _ = '_'})
|
||||||
|
++ mnesia:dirty_match_object(#filter_rule{id={'_', ?ALLHOSTS}, _ = '_'}),
|
||||||
|
lists:map(fun({filter_rule, _, Type, _, BinRegExp}) -> {Type, BinRegExp} end, Rules).
|
||||||
|
%lists:unzip(Config).
|
||||||
|
|
||||||
|
% this call init or reset local rules reading database
|
||||||
|
init(Host, Opts) ->
|
||||||
|
Rules = load_rules(Host),
|
||||||
|
Scope = gen_mod:get_opt(scope, Opts, message),
|
||||||
|
Pattern = gen_mod:get_opt(pattern, Opts, ""),
|
||||||
|
?MODULE:loop(Host, Opts, Rules, Scope, Pattern).
|
||||||
|
|
||||||
|
init_all_hosts_handler() ->
|
||||||
|
register(?PROCNAME(?ALLHOSTS), spawn(?MODULE, loop, [?ALLHOSTS, [], [], none, []])).
|
||||||
|
|
||||||
|
% this call reset local rules reading database
|
||||||
|
% and tell other nodes to reset rules as well
|
||||||
|
update(?ALLHOSTS, _Opts) ->
|
||||||
|
lists:foreach(fun(Host) ->
|
||||||
|
lists:foreach(fun(Node) ->
|
||||||
|
catch rpc:call(Node,mod_filter,reload,[Host])
|
||||||
|
end, mnesia:system_info(running_db_nodes))
|
||||||
|
end, ejabberd_config:get_global_option(hosts)),
|
||||||
|
?MODULE:loop(?ALLHOSTS, [], [], none, []);
|
||||||
|
update(Host, Opts) ->
|
||||||
|
% tell other nodes to update filter
|
||||||
|
lists:foreach(fun(Node) ->
|
||||||
|
catch rpc:call(Node,mod_filter,reload,[Host])
|
||||||
|
end, mnesia:system_info(running_db_nodes)--[node()]),
|
||||||
|
% update rules
|
||||||
|
init(Host, Opts).
|
||||||
|
|
||||||
|
loop(Host, Opts, Rules, Scope, Pattern) ->
|
||||||
|
receive
|
||||||
|
{add, Id, RegExp} ->
|
||||||
|
[BinRegExp] = tlre:compile([RegExp]),
|
||||||
|
mnesia:dirty_write(#filter_rule{id={Id, Host}, regexp=RegExp, binre=BinRegExp}),
|
||||||
|
?MODULE:update(Host, Opts);
|
||||||
|
{add, Id, RegExp, Type} ->
|
||||||
|
[BinRegExp] = tlre:compile([RegExp]),
|
||||||
|
mnesia:dirty_write(#filter_rule{id={Id, Host}, regexp=RegExp, binre=BinRegExp, type=Type}),
|
||||||
|
?MODULE:update(Host, Opts);
|
||||||
|
{del, Id} ->
|
||||||
|
mnesia:dirty_delete_object(#filter_rule{id={Id, Host}, _='_'}),
|
||||||
|
?MODULE:update(Host, Opts);
|
||||||
|
{del, Id, RegExp} ->
|
||||||
|
mnesia:dirty_delete_object(#filter_rule{id={Id, Host}, regexp=RegExp, _='_'}),
|
||||||
|
?MODULE:update(Host, Opts);
|
||||||
|
{match, From, String} ->
|
||||||
|
From ! {match, string_filter(String, Rules, Scope, Pattern)},
|
||||||
|
?MODULE:loop(Host, Opts, Rules, Scope, Pattern);
|
||||||
|
reload ->
|
||||||
|
?MODULE:init(Host, Opts);
|
||||||
|
quit ->
|
||||||
|
unregister(?PROCNAME(Host)),
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
string_filter(String, Rules, Scope, Pattern) ->
|
||||||
|
lists:foldl(fun
|
||||||
|
(_, {Pass, []}) -> {Pass, []};
|
||||||
|
({Type, RegExp}, {Pass, NewString}) -> string_filter(NewString, Pass, RegExp, Type, Scope, Pattern)
|
||||||
|
end, {"pass", String}, Rules).
|
||||||
|
string_filter(String, Pass, RegExp, Type, Scope, Pattern) ->
|
||||||
|
case tlre:grep(String, [RegExp]) of
|
||||||
|
[no_match] ->
|
||||||
|
{Pass, String};
|
||||||
|
[{S1, S2, _}] ->
|
||||||
|
case Scope of
|
||||||
|
word ->
|
||||||
|
Start = string:sub_string(String, 1, S1),
|
||||||
|
StringTail = string:sub_string(String, S2+1, length(String)),
|
||||||
|
NewPass = pass_rule(Pass, Type),
|
||||||
|
{LastPass, End} = string_filter(StringTail, NewPass, RegExp, Type, Scope, Pattern),
|
||||||
|
NewString = case Type of
|
||||||
|
"log" -> lists:append([string:sub_string(String, 1, S2), End]);
|
||||||
|
_ -> lists:append([Start, Pattern, End])
|
||||||
|
end,
|
||||||
|
{LastPass, NewString};
|
||||||
|
_ ->
|
||||||
|
NewString = case Type of
|
||||||
|
"log" -> String;
|
||||||
|
_ -> []
|
||||||
|
end,
|
||||||
|
{pass_rule(Pass, Type), NewString}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
pass_rule("pass", New) -> New;
|
||||||
|
pass_rule("log", "log") -> "log";
|
||||||
|
pass_rule("log", "log and filter") -> "log and filter";
|
||||||
|
pass_rule("log", "filter") -> "log and filter";
|
||||||
|
pass_rule("filter", "log") -> "log and filter";
|
||||||
|
pass_rule("filter", "log and filter") -> "log and filter";
|
||||||
|
pass_rule("filter", "filter") -> "filter";
|
||||||
|
pass_rule("log and filter", _) -> "log and filter".
|
||||||
|
|
||||||
|
add_regexp(VH, Id, RegExp) ->
|
||||||
|
?PROCNAME(VH) ! {add, Id, RegExp},
|
||||||
|
ok.
|
||||||
|
|
||||||
|
add_regexp(VH, Id, RegExp, Type) ->
|
||||||
|
?PROCNAME(VH) ! {add, Id, RegExp, Type},
|
||||||
|
ok.
|
||||||
|
|
||||||
|
del_regexp(VH, Id) ->
|
||||||
|
?PROCNAME(VH) ! {del, Id},
|
||||||
|
ok.
|
||||||
|
|
||||||
|
del_regexp(VH, Id, RegExp) ->
|
||||||
|
?PROCNAME(VH) ! {del, Id, RegExp},
|
||||||
|
ok.
|
||||||
|
|
||||||
|
reload(VH) ->
|
||||||
|
?PROCNAME(VH) ! reload,
|
||||||
|
ok.
|
||||||
|
|
||||||
|
purge_logs() ->
|
||||||
|
mnesia:dirty_delete_object(#filter_log{_='_'}).
|
||||||
|
|
||||||
|
%purge_regexps() ->
|
||||||
|
% mnesia:dirty_delete_object(#filter_rule{_='_'}),
|
||||||
|
% reload().
|
||||||
|
|
||||||
|
purge_regexps(VH) ->
|
||||||
|
mnesia:dirty_delete_object(#filter_rule{id={'_', VH}, _='_'}),
|
||||||
|
reload(VH).
|
||||||
|
|
||||||
|
rules() ->
|
||||||
|
lists:map(fun(#filter_rule{id={Label, VH}, type=Type, regexp=Regexp}) ->
|
||||||
|
{VH, Label, Type, Regexp}
|
||||||
|
end, mnesia:dirty_match_object(#filter_rule{_='_'})).
|
||||||
|
|
||||||
|
logged() ->
|
||||||
|
lists:reverse(lists:map(fun(#filter_log{date=Date, from=From, to=To, message=Msg}) ->
|
||||||
|
{Date, jlib:jid_to_string(From), jlib:jid_to_string(To), Msg}
|
||||||
|
end, mnesia:dirty_match_object(#filter_log{_='_'}))).
|
||||||
|
|
||||||
|
logged(Limit) ->
|
||||||
|
List = mnesia:dirty_match_object(#filter_log{_='_'}),
|
||||||
|
Len = length(List),
|
||||||
|
FinalList = if
|
||||||
|
Len < Limit -> List;
|
||||||
|
true -> lists:nthtail(Len-Limit, List)
|
||||||
|
end,
|
||||||
|
lists:reverse(lists:map(fun(#filter_log{date=Date, from=From, to=To, message=Msg}) ->
|
||||||
|
{Date, jlib:jid_to_string(From), jlib:jid_to_string(To), Msg}
|
||||||
|
end, FinalList)).
|
||||||
|
|
||||||
|
%% filter_packet can receive drop if a previous filter already dropped
|
||||||
|
%% the packet
|
||||||
|
filter_packet(drop) -> drop;
|
||||||
|
filter_packet({From, To, Packet}) ->
|
||||||
|
case Packet of
|
||||||
|
{xmlelement, "message", MsgAttrs, Els} ->
|
||||||
|
case lists:keysearch("body", 2, Els) of
|
||||||
|
{value, {xmlelement, "body", BodyAttrs, Data}} ->
|
||||||
|
NewData = lists:foldl(fun
|
||||||
|
({xmlcdata, CData}, DataAcc) when is_binary(CData) ->
|
||||||
|
#jid{lserver = Host} = To,
|
||||||
|
case lists:member(Host, ejabberd_config:get_global_option(hosts)) of
|
||||||
|
true ->
|
||||||
|
Msg = binary_to_list(CData),
|
||||||
|
?PROCNAME(Host) ! {match, self(), Msg},
|
||||||
|
receive
|
||||||
|
{match, {"pass", _}} ->
|
||||||
|
[{xmlcdata, CData}|DataAcc];
|
||||||
|
{match, {"log", FinalString}} ->
|
||||||
|
mnesia:dirty_write(#filter_log{
|
||||||
|
date=erlang:localtime(),
|
||||||
|
from=From, to=To, message=Msg}),
|
||||||
|
[{xmlcdata, CData}|DataAcc];
|
||||||
|
{match, {"log and filter", FinalString}} ->
|
||||||
|
mnesia:dirty_write(#filter_log{
|
||||||
|
date=erlang:localtime(),
|
||||||
|
from=From, to=To, message=Msg}),
|
||||||
|
case FinalString of
|
||||||
|
[] -> % entire message is dropped
|
||||||
|
DataAcc;
|
||||||
|
S -> % message must be regenerated
|
||||||
|
[{xmlcdata, list_to_binary(S)}|DataAcc]
|
||||||
|
end;
|
||||||
|
{match, {"filter", FinalString}} ->
|
||||||
|
case FinalString of
|
||||||
|
[] -> % entire message is dropped
|
||||||
|
DataAcc;
|
||||||
|
S -> % message must be regenerated
|
||||||
|
[{xmlcdata, list_to_binary(S)}|DataAcc]
|
||||||
|
end
|
||||||
|
after ?TIMEOUT ->
|
||||||
|
[{xmlcdata, CData}|DataAcc]
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
[{xmlcdata, CData}|DataAcc]
|
||||||
|
end;
|
||||||
|
(Item, DataAcc) -> %% to not filter internal messages
|
||||||
|
[Item|DataAcc]
|
||||||
|
end, [], Data),
|
||||||
|
case NewData of
|
||||||
|
[] ->
|
||||||
|
drop;
|
||||||
|
D ->
|
||||||
|
NewEls = lists:keyreplace("body", 2, Els, {xmlelement, "body", BodyAttrs, lists:reverse(D)}),
|
||||||
|
{From, To, {xmlelement, "message", MsgAttrs, NewEls}}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{From, To, Packet}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{From, To, Packet}
|
||||||
|
end.
|
||||||
|
|
||||||
|
process_local_iq(From, #jid{lserver=VH} = _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
|
||||||
|
case Type of
|
||||||
|
get ->
|
||||||
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||||
|
set ->
|
||||||
|
#jid{luser = User, lserver = Server, lresource = Resource} = From,
|
||||||
|
case acl:match_rule(global, configure, {User, Server, Resource}) of
|
||||||
|
allow ->
|
||||||
|
case xml:get_subtag(SubEl, "add") of
|
||||||
|
{xmlelement, "add", AddAttrs, _} ->
|
||||||
|
AID = xml:get_attr_s("id", AddAttrs),
|
||||||
|
ARE = xml:get_attr_s("re", AddAttrs),
|
||||||
|
case xml:get_attr_s("type", AddAttrs) of
|
||||||
|
"" -> add_regexp(VH, AID, ARE);
|
||||||
|
ATP -> add_regexp(VH, AID, ARE, ATP)
|
||||||
|
end;
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
case xml:get_subtag(SubEl, "del") of
|
||||||
|
{xmlelement, "del", DelAttrs, _} ->
|
||||||
|
DID = xml:get_attr_s("id", DelAttrs),
|
||||||
|
case xml:get_attr_s("re", DelAttrs) of
|
||||||
|
"" -> del_regexp(VH, DID);
|
||||||
|
DRE -> del_regexp(VH, DID, DRE)
|
||||||
|
end;
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
case xml:get_subtag(SubEl, "dellogs") of
|
||||||
|
{xmlelement, "dellogs", _, _} -> purge_logs();
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
case xml:get_subtag(SubEl, "delrules") of
|
||||||
|
{xmlelement, "delrules", _, _} -> purge_regexps(VH);
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
IQ#iq{type = result, sub_el = []};
|
||||||
|
_ ->
|
||||||
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
261
src/mod_support.erl
Normal file
261
src/mod_support.erl
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
%%% ====================================================================
|
||||||
|
%%% This software is copyright 2006-2009, ProcessOne.
|
||||||
|
%%%
|
||||||
|
%%% mod_support
|
||||||
|
%%% allow automatic build of support archive to be sent to Process-One
|
||||||
|
%%%
|
||||||
|
%%% @copyright 2006-2009 ProcessOne
|
||||||
|
%%% @author Christophe Romain <christophe.romain@process-one.net>
|
||||||
|
%%% [http://www.process-one.net/]
|
||||||
|
%%% @version {@vsn}, {@date} {@time}
|
||||||
|
%%% @end
|
||||||
|
%%% ====================================================================
|
||||||
|
|
||||||
|
|
||||||
|
-module(mod_support).
|
||||||
|
-author('christophe.romain@process-one.net').
|
||||||
|
-vsn('$Id: mod_support.erl 856 2009-09-21 18:46:38Z jpcarlino $').
|
||||||
|
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
%-behaviour(gen_server).
|
||||||
|
|
||||||
|
% module functions
|
||||||
|
-export([start/2,stop/1,is_loaded/0,loop/1,dump/0]).
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("jlib.hrl").
|
||||||
|
-include("licence.hrl").
|
||||||
|
|
||||||
|
-include_lib("kernel/include/file.hrl").
|
||||||
|
|
||||||
|
-define(LOG_FETCH_SIZE, 1000000).
|
||||||
|
-define(RPC_TIMEOUT, 10000). % 10
|
||||||
|
-define(MAX_FILE_SIZE, 2147483648). %%2Gb
|
||||||
|
|
||||||
|
start(Host, Opts) ->
|
||||||
|
case ?IS_VALID of
|
||||||
|
true ->
|
||||||
|
case gen_mod:get_opt(dump_freq, Opts, 0) of
|
||||||
|
0 -> no_dump;
|
||||||
|
Freq -> spawn(?MODULE, loop, [Freq*60000])
|
||||||
|
end,
|
||||||
|
ok;
|
||||||
|
false ->
|
||||||
|
not_started
|
||||||
|
end.
|
||||||
|
|
||||||
|
stop(Host) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
is_loaded() ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
loop(Timeout) ->
|
||||||
|
receive
|
||||||
|
quit -> ok
|
||||||
|
after Timeout ->
|
||||||
|
Dump = dump(),
|
||||||
|
BaseName = get_base_name(),
|
||||||
|
%%{Data,EjabberdLog,SaslLog,ECrash} = Dump,
|
||||||
|
write_logs(tuple_to_list(Dump),BaseName,["_memory.bin",
|
||||||
|
"_ejabberd.log.gz",
|
||||||
|
"_sasl.log.gz",
|
||||||
|
"_erlang_crash_dump.log.gz"]),
|
||||||
|
loop(Timeout)
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_base_name() ->
|
||||||
|
{{Y,M,D},{Hr,Mn,_Sc}} = calendar:local_time(),
|
||||||
|
case os:getenv("EJABBERD_LOG_PATH") of
|
||||||
|
false ->
|
||||||
|
filename:join(filename:dirname(filename:absname("")),
|
||||||
|
lists:flatten(io_lib:format("~b~b~b~b~b",[Y,M,D,Hr,Mn])));
|
||||||
|
Path ->
|
||||||
|
filename:join(filename:dirname(Path),
|
||||||
|
lists:flatten(io_lib:format("~b~b~b~b~b",[Y,M,D,Hr,Mn])))
|
||||||
|
end.
|
||||||
|
|
||||||
|
write_logs([BinaryData|T],BaseName,[Filename|Filenames]) ->
|
||||||
|
Log = BaseName++Filename,
|
||||||
|
file:write_file(Log, BinaryData),
|
||||||
|
write_logs(T,BaseName,Filenames);
|
||||||
|
|
||||||
|
write_logs([],BaseName,_)-> ok.
|
||||||
|
|
||||||
|
dump() ->
|
||||||
|
Dump = lists:map(fun(LogFile) ->
|
||||||
|
Content = case file:open(LogFile,[read,raw]) of
|
||||||
|
{ok, IO} ->
|
||||||
|
Size = case file:read_file_info(LogFile) of
|
||||||
|
{ok, FileInfo} -> FileInfo#file_info.size;
|
||||||
|
_ -> ?LOG_FETCH_SIZE
|
||||||
|
end,
|
||||||
|
case Size>?MAX_FILE_SIZE of
|
||||||
|
true -> io_lib:format("File ~s is too big: ~p bytes.",[LogFile, Size]);
|
||||||
|
false ->
|
||||||
|
if Size>?LOG_FETCH_SIZE ->
|
||||||
|
file:position(IO, Size-?LOG_FETCH_SIZE),
|
||||||
|
case file:read(IO, ?LOG_FETCH_SIZE) of
|
||||||
|
{ok, Data1} -> Data1;
|
||||||
|
Error1 -> io_lib:format("can not read log file (~s): ~p",[LogFile, Error1])
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
case file:read(IO, Size) of
|
||||||
|
{ok, Data2} -> Data2;
|
||||||
|
Error2 -> io_lib:format("can not read log file (~s): ~p",[LogFile, Error2])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
{error, Reason} ->
|
||||||
|
io_lib:format("can not open log file (~s): ~p",[LogFile, Reason])
|
||||||
|
end,
|
||||||
|
zlib:gzip(list_to_binary(Content))
|
||||||
|
end, [ejabberd_logs(), sasl_logs(), erl_crash()]),
|
||||||
|
NodeState = get_node_state(),
|
||||||
|
list_to_tuple([NodeState|Dump]).
|
||||||
|
|
||||||
|
ejabberd_logs() ->
|
||||||
|
LogPath = case application:get_env(log_path) of
|
||||||
|
{ok, Path} ->
|
||||||
|
Path;
|
||||||
|
undefined ->
|
||||||
|
case os:getenv("EJABBERD_LOG_PATH") of
|
||||||
|
false -> ?LOG_PATH;
|
||||||
|
Path -> Path
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
sasl_logs() ->
|
||||||
|
case os:getenv("SASL_LOG_PATH") of
|
||||||
|
false -> filename:join([filename:dirname(ejabberd_logs()),"sasl.log"]);
|
||||||
|
Path -> Path
|
||||||
|
end.
|
||||||
|
|
||||||
|
erl_crash() ->
|
||||||
|
LogsDir = filename:dirname(ejabberd_logs()),
|
||||||
|
CrashDumpWildcard = filename:join([LogsDir,"erl_crash*dump"]),
|
||||||
|
FileName = case filelib:wildcard(CrashDumpWildcard) of
|
||||||
|
[Files] -> [LastFile|T] = lists:reverse([Files]),
|
||||||
|
LastFile;
|
||||||
|
_ -> case os:getenv("ERL_CRASH_DUMP") of
|
||||||
|
false -> "erl_crash.dump";
|
||||||
|
Path -> Path
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
proc_info(Pid) ->
|
||||||
|
Info = process_info(Pid),
|
||||||
|
lists:map(fun(Elem) ->
|
||||||
|
List = proplists:get_value(Elem, Info),
|
||||||
|
{Elem, size(term_to_binary(List))}
|
||||||
|
end, [messages, dictionary])
|
||||||
|
++ [X || X <- Info,
|
||||||
|
lists:member(element(1,X),
|
||||||
|
[heap_size,stack_size,reductions,links,status,initial_call,current_function])].
|
||||||
|
|
||||||
|
environment() ->
|
||||||
|
{ok, KE} = application:get_key(kernel,env),
|
||||||
|
{ok, EE} = application:get_key(ejabberd,env),
|
||||||
|
Env = [{inetrc, os:getenv("ERL_INETRC")},
|
||||||
|
{sopath, os:getenv("EJABBERD_SO_PATH")},
|
||||||
|
{maxports, os:getenv("ERL_MAX_PORTS")},
|
||||||
|
{maxtables, os:getenv("ERL_MAX_ETS_TABLES")},
|
||||||
|
{crashdump, os:getenv("ERL_CRASH_DUMP")},
|
||||||
|
{archdir, os:getenv("ARCHDIR")},
|
||||||
|
{mnesia, mnesia:system_info(all)}],
|
||||||
|
Args = [{args, init:get_arguments()}, {plain, init:get_plain_arguments()}],
|
||||||
|
KE++EE++Env++Args.
|
||||||
|
|
||||||
|
memtop(N) ->
|
||||||
|
E = lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Tab) -> {Tab, ets:info(Tab,memory)} end, ets:all()))),N),
|
||||||
|
M = lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Tab) -> {Tab, mnesia:table_info(Tab,memory)} end, mnesia:system_info(tables)))),N),
|
||||||
|
E++M.
|
||||||
|
|
||||||
|
maxmsgqueue() ->
|
||||||
|
lists:max(lists:map(fun(Pid) -> proplists:get_value(message_queue_len,process_info(Pid)) end, erlang:processes())).
|
||||||
|
|
||||||
|
msgqueue(N) ->
|
||||||
|
lists:filter(fun(L) -> proplists:get_value(message_queue_len, L) > N
|
||||||
|
end, lists:map(fun(Pid) -> process_info(Pid) end, erlang:processes())).
|
||||||
|
|
||||||
|
%lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Pid) -> {E,L} = process_info(Pid, dictionary), {E,length(L)} end, erlang:processes()))), 10)
|
||||||
|
|
||||||
|
%%Entry point to invoke mod_support via command line.
|
||||||
|
%%Example: erl -sname debug@localhost -s mod_support report ejabberd@localhost
|
||||||
|
%%See issue #TECH-286.
|
||||||
|
report(Node) ->
|
||||||
|
[NodeId|T]=Node,
|
||||||
|
UploadResult = force_load_code_into_node(NodeId, ?MODULE),
|
||||||
|
case UploadResult of
|
||||||
|
ok -> NodeState = rpc:call(NodeId,mod_support,get_node_state,[],?RPC_TIMEOUT),
|
||||||
|
Dump = rpc:call(NodeId,mod_support,dump,[],?RPC_TIMEOUT),
|
||||||
|
BaseName = get_base_name(),
|
||||||
|
%%{Data,EjabberdLog,SaslLog,ECrash} = Dump,
|
||||||
|
write_logs(tuple_to_list(Dump),BaseName,["_memory.bin",
|
||||||
|
"_ejabberd.log.gz",
|
||||||
|
"_sasl.log.gz",
|
||||||
|
"_erlang_crash_dump.log.gz"]),
|
||||||
|
error_logger:info_msg("State in node ~p was written to log~n",[NodeId]),
|
||||||
|
error_logger:info_msg("Unloading module ~s from node ~p. ",[?MODULE,NodeId]),
|
||||||
|
force_unload_code_from_node(NodeId, ?MODULE);
|
||||||
|
_ -> error_logger:info_msg("Error uploading module ~s from node ~p~n",[?MODULE,NodeId])
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%Load Module into the ejabberd Node specified.
|
||||||
|
force_load_code_into_node(Node, Module) ->
|
||||||
|
CodeFile = code:where_is_file(atom_to_list(Module)++".beam"),
|
||||||
|
case file:read_file(CodeFile) of
|
||||||
|
{ok, Code} ->
|
||||||
|
rpc:call(Node, code, purge, [Module], ?RPC_TIMEOUT),
|
||||||
|
rpc:call(Node, code, delete, [Module], ?RPC_TIMEOUT),
|
||||||
|
case rpc:call(Node, code, load_binary, [Module, CodeFile, Code], ?RPC_TIMEOUT) of
|
||||||
|
{module, _} ->
|
||||||
|
error_logger:info_msg("Loading ~s module into ~p : success ~n", [Module,Node]),
|
||||||
|
rpc:block_call(Node, Module, is_loaded, [], ?RPC_TIMEOUT);
|
||||||
|
{error, badfile} ->
|
||||||
|
error_logger:info_msg("Loading ~s module into ~p : incorrect format ~n", [Module,Node]),
|
||||||
|
{error, badfile};
|
||||||
|
{error, not_purged} ->
|
||||||
|
% this should never happen anyway..
|
||||||
|
error_logger:info_msg("Loading ~s module into ~p : old code already exists ~n", [Module,Node]),
|
||||||
|
{error, not_purged};
|
||||||
|
{badrpc, Reason} ->
|
||||||
|
error_logger:info_msg("Loading ~s module into ~p: badrpc ~p ~n", [Module,Node,Reason]),
|
||||||
|
{badrpc, Reason}
|
||||||
|
end;
|
||||||
|
Error ->
|
||||||
|
error_logger:error_msg("Cannot read module file ~s ~p : ~p ~n", [Module, CodeFile, Error]),
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%Unload erlang Module from the Node specified. Used to ensure cleanup after rpc calls.
|
||||||
|
force_unload_code_from_node(Node, Module) ->
|
||||||
|
rpc:call(Node, code, purge, [Module], ?RPC_TIMEOUT),
|
||||||
|
rpc:call(Node, code, delete, [Module], ?RPC_TIMEOUT).
|
||||||
|
|
||||||
|
%%Retrieve system state and pack it into Data
|
||||||
|
%%TODO enhance state info. See #TECH-286.
|
||||||
|
get_node_state() ->
|
||||||
|
Mem = erlang:memory(),
|
||||||
|
Ets = lists:map(fun(Tab) -> ets:info(Tab) end, ets:all()),
|
||||||
|
Mnesia = lists:map(fun(Tab) -> mnesia:table_info(Tab,all) end, mnesia:system_info(tables)),
|
||||||
|
Procs = lists:map(fun(Pid) -> proc_info(Pid) end, erlang:processes()),
|
||||||
|
Data = term_to_binary({Mem, Ets, Mnesia, Procs}).
|
||||||
|
|
||||||
|
crash_dump() ->
|
||||||
|
SystemInfo = [erlang:system_info(X) || X<-[info,loaded,procs]],
|
||||||
|
[zlib:gzip(list_to_binary(lists:flatten(SystemInfo)))].
|
||||||
|
|
||||||
|
crash_dump(Node) ->
|
||||||
|
[NodeId|T]=Node,
|
||||||
|
UploadResult = force_load_code_into_node(NodeId, ?MODULE),
|
||||||
|
case UploadResult of
|
||||||
|
ok -> Dump = rpc:call(NodeId,mod_support,crash_dump,[],?RPC_TIMEOUT),
|
||||||
|
BaseName = get_base_name(),
|
||||||
|
write_logs(Dump,BaseName,["_realtime_crash_dump.gz"]),
|
||||||
|
error_logger:info_msg("Unloading module ~s from node ~p. ",[?MODULE,NodeId]),
|
||||||
|
force_unload_code_from_node(NodeId, ?MODULE);
|
||||||
|
_ -> error_logger:info_msg("Error uploading module ~s from node ~p~n",[?MODULE,NodeId])
|
||||||
|
end.
|
Loading…
Reference in New Issue
Block a user