diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index cccd41b07..135bc7005 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -623,9 +623,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> P, D, DGen) of {true, AuthModule} -> - ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p", - [StateData#state.socket, - jlib:jid_to_string(JID), AuthModule]), + ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p from ~s", + [StateData#state.socket, + jlib:jid_to_string(JID), AuthModule, + jlib:ip_to_list(StateData#state.ip)]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [true, U, StateData#state.server, + StateData#state.ip]), Conn = get_conn_type(StateData), Info = [{ip, StateData#state.ip}, {conn, Conn}, {auth_module, AuthModule}], @@ -660,12 +664,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> privacy_list = PrivList}, fsm_next_state(session_established, NewStateData); _ -> - IP = peerip(StateData#state.sockmod, - StateData#state.socket), - ?INFO_MSG("(~w) Failed legacy authentication for " - "~s from IP ~s", - [StateData#state.socket, - jlib:jid_to_string(JID), jlib:ip_to_list(IP)]), + ?INFO_MSG("(~w) Failed legacy authentication for ~s from ~s", + [StateData#state.socket, + jlib:jid_to_string(JID), + jlib:ip_to_list(StateData#state.ip)]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [false, U, StateData#state.server, + StateData#state.ip]), Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED), send_element(StateData, Err), fsm_next_state(wait_for_auth, StateData) @@ -680,9 +685,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> fsm_next_state(wait_for_auth, StateData); true -> ?INFO_MSG("(~w) Forbidden legacy authentication " - "for ~s", + "for ~s from ~s", [StateData#state.socket, - jlib:jid_to_string(JID)]), + jlib:jid_to_string(JID), + jlib:ip_to_list(StateData#state.ip)]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [false, U, StateData#state.server, + StateData#state.ip]), Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), send_element(StateData, Err), fsm_next_state(wait_for_auth, StateData) @@ -732,8 +741,12 @@ wait_for_feature_request({xmlstreamelement, El}, %AuthModule = xml:get_attr_s(auth_module, Props), AuthModule = proplists:get_value(auth_module, Props, undefined), ?INFO_MSG("(~w) Accepted authentication for ~s " - "by ~p", - [StateData#state.socket, U, AuthModule]), + "by ~p from ~s", + [StateData#state.socket, U, AuthModule, + jlib:ip_to_list(StateData#state.ip)]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [true, U, StateData#state.server, + StateData#state.ip]), send_element(StateData, #xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL}], @@ -754,10 +767,13 @@ wait_for_feature_request({xmlstreamelement, El}, fsm_next_state(wait_for_sasl_response, StateData#state{sasl_state = NewSASLState}); {error, Error, Username} -> - IP = peerip(StateData#state.sockmod, StateData#state.socket), - ?INFO_MSG("(~w) Failed authentication for ~s@~s from IP ~s", - [StateData#state.socket, - Username, StateData#state.server, jlib:ip_to_list(IP)]), + ?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s", + [StateData#state.socket, + Username, StateData#state.server, + jlib:ip_to_list(StateData#state.ip)]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [false, Username, StateData#state.server, + StateData#state.ip]), send_element(StateData, #xmlel{name = <<"failure">>, attrs = [{<<"xmlns">>, ?NS_SASL}], @@ -878,8 +894,12 @@ wait_for_sasl_response({xmlstreamelement, El}, % AuthModule = xml:get_attr_s(auth_module, Props), AuthModule = proplists:get_value(auth_module, Props, <<>>), ?INFO_MSG("(~w) Accepted authentication for ~s " - "by ~p", - [StateData#state.socket, U, AuthModule]), + "by ~p from ~s", + [StateData#state.socket, U, AuthModule, + jlib:ip_to_list(StateData#state.ip)]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [true, U, StateData#state.server, + StateData#state.ip]), send_element(StateData, #xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL}], @@ -897,8 +917,12 @@ wait_for_sasl_response({xmlstreamelement, El}, % AuthModule = xml:get_attr_s(auth_module, Props), AuthModule = proplists:get_value(auth_module, Props, undefined), ?INFO_MSG("(~w) Accepted authentication for ~s " - "by ~p", - [StateData#state.socket, U, AuthModule]), + "by ~p from ~s", + [StateData#state.socket, U, AuthModule, + jlib:ip_to_list(StateData#state.ip)]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [true, U, StateData#state.server, + StateData#state.ip]), send_element(StateData, #xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL}], @@ -921,10 +945,13 @@ wait_for_sasl_response({xmlstreamelement, El}, fsm_next_state(wait_for_sasl_response, StateData#state{sasl_state = NewSASLState}); {error, Error, Username} -> - IP = peerip(StateData#state.sockmod, StateData#state.socket), - ?INFO_MSG("(~w) Failed authentication for ~s@~s from IP ~s", - [StateData#state.socket, - Username, StateData#state.server, jlib:ip_to_list(IP)]), + ?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s", + [StateData#state.socket, + Username, StateData#state.server, + jlib:ip_to_list(StateData#state.ip)]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [false, Username, StateData#state.server, + StateData#state.ip]), send_element(StateData, #xmlel{name = <<"failure">>, attrs = [{<<"xmlns">>, ?NS_SASL}], diff --git a/src/mod_fail2ban.erl b/src/mod_fail2ban.erl new file mode 100644 index 000000000..ef40433d0 --- /dev/null +++ b/src/mod_fail2ban.erl @@ -0,0 +1,55 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2014, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 15 Aug 2014 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_fail2ban). + +-behaviour(gen_mod). + +%% API +-export([start/2, stop/1, c2s_auth_result/4]). + +-include("jlib.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +start(Host, _Opts) -> + ets:new(failed_auth, [bag, named_table, public]), + ejabberd_hooks:add(c2s_auth_result, Host, ?MODULE, c2s_auth_result, 100). + +stop(Host) -> + ejabberd_hooks:delete(c2s_auth_result, Host, ?MODULE, c2s_auth_result, 100). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +c2s_auth_result(true, User, Server, {Addr, _Port}) -> + case jlib:make_jid(User, Server, <<"">>) of + #jid{luser = LUser, lserver = LServer} -> + US = {LUser, LServer}, + Objs = ets:lookup(failed_auth, Addr), + case lists:filter(fun({_, US1, _}) -> US1 == US end, Objs) of + [_|_] -> + ets:match_delete(failed_auth, {'_', US, '_'}); + [] -> + true + end; + _ -> + false + end; +c2s_auth_result(false, User, Server, {Addr, _Port}) -> + case jlib:make_jid(User, Server, <<"">>) of + #jid{luser = LUser, lserver = LServer} -> + US = {LUser, LServer}, + ets:insert(failed_auth, {Addr, US, now()}), + Objs = ets:match_object(failed_auth, {'_', US, '_'}), + Timeout = round(math:exp(length(Objs))), + timer:sleep(timer:seconds(Timeout)); + _ -> + ok + end.