From 8e069daa612e7e96140f0f7ddbf34d4a7e63b71d Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 16 May 2008 14:23:37 +0000 Subject: [PATCH] fixing 1324 commit issue on tagged versions SVN Revision: 1326 --- ChangeLog | 8 +++ src/ejabberd_c2s.erl | 127 ++++++++++++++++++++------------------- src/ejabberd_socket.erl | 28 ++++++--- src/jlib.erl | 9 ++- src/mod_ip_blacklist.erl | 113 ++++++++++++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 73 deletions(-) create mode 100644 src/mod_ip_blacklist.erl diff --git a/ChangeLog b/ChangeLog index 22fc6162f..0700d05e2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2008-05-16 Mickael Remond + + * src/ejabberd_c2s.erl: Added C2S blacklist support (EJAB-625). + * src/mod_ip_blacklist.erl: Likewise. + * src/jlib.erl: Added IP format tuple to string function. + * src/ejabberd_socket.erl: Properly handled c2s start failure (happen + for blacklisted IP). + 2008-05-16 Christophe Romain * src/ejabberd_receiver.erl: Don't activate a socket untill its diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 567cd6b6e..f2b7f7e70 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -56,7 +56,6 @@ -include("ejabberd.hrl"). -include("jlib.hrl"). --include("mod_privacy.hrl"). -define(SETS, gb_sets). -define(DICT, dict). @@ -85,9 +84,8 @@ pres_last, pres_pri, pres_timestamp, pres_invis = false, - privacy_list = #userlist{}, + privacy_list = none, conn = unknown, - auth_module = unknown, ip, lang}). @@ -176,26 +174,35 @@ init([{SockMod, Socket}, Opts]) -> (_) -> false end, Opts), IP = peerip(SockMod, Socket), - Socket1 = - if - TLSEnabled -> - SockMod:starttls(Socket, TLSOpts); - true -> - Socket - end, - SocketMonitor = SockMod:monitor(Socket1), - {ok, wait_for_stream, #state{socket = Socket1, - sockmod = SockMod, - socket_monitor = SocketMonitor, - zlib = Zlib, - tls = TLS, - tls_required = StartTLSRequired, - tls_enabled = TLSEnabled, - tls_options = TLSOpts, - streamid = new_id(), - access = Access, - shaper = Shaper, - ip = IP}, ?C2S_OPEN_TIMEOUT}. + %% Check if IP is blacklisted: + case is_ip_blacklisted(IP) of + true -> + ?INFO_MSG("Connection attempt from blacklisted IP: ~s", + [jlib:ip_to_list(IP)]), + {stop, normal}; + false -> + Socket1 = + if + TLSEnabled -> + SockMod:starttls(Socket, TLSOpts); + true -> + Socket + end, + SocketMonitor = SockMod:monitor(Socket1), + {ok, wait_for_stream, #state{socket = Socket1, + sockmod = SockMod, + socket_monitor = SocketMonitor, + zlib = Zlib, + tls = TLS, + tls_required = StartTLSRequired, + tls_enabled = TLSEnabled, + tls_options = TLSOpts, + streamid = new_id(), + access = Access, + shaper = Shaper, + ip = IP}, + ?C2S_OPEN_TIMEOUT} + end. %% Return list of all available resources of contacts, %% in form [{JID, Caps}]. @@ -239,11 +246,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> cyrsasl:server_new( "jabber", Server, "", [], fun(U) -> - ejabberd_auth:get_password_with_authmodule( + ejabberd_auth:get_password( U, Server) end, fun(U, P) -> - ejabberd_auth:check_password_with_authmodule( + ejabberd_auth:check_password( U, Server, P) end), Mechs = lists:map( @@ -344,9 +351,9 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> true -> send_text(StateData, Header), fsm_next_state(wait_for_auth, - StateData#state{ - server = Server, - lang = Lang}) + StateData#state{ + server = Server, + lang = Lang}) end end; _ -> @@ -431,18 +438,17 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> (acl:match_rule(StateData#state.server, StateData#state.access, JID) == allow) of true -> - case ejabberd_auth:check_password_with_authmodule( + case ejabberd_auth:check_password( U, StateData#state.server, P, StateData#state.streamid, D) of - {true, AuthModule} -> + true -> ?INFO_MSG( "(~w) Accepted legacy authentication for ~s", [StateData#state.socket, jlib:jid_to_string(JID)]), SID = {now(), self()}, Conn = get_conn_type(StateData), - Info = [{ip, StateData#state.ip}, {conn, Conn}, - {auth_module, AuthModule}], + Info = [{ip, StateData#state.ip}, {conn, Conn}], ejabberd_sm:open_session( SID, U, StateData#state.server, R, Info), Res1 = jlib:make_result_iq_reply(El), @@ -461,7 +467,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> PrivList = ejabberd_hooks:run_fold( privacy_get_user_list, StateData#state.server, - #userlist{}, + none, [U, StateData#state.server]), fsm_next_state(session_established, StateData#state{ @@ -470,7 +476,6 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> jid = JID, sid = SID, conn = Conn, - auth_module = AuthModule, pres_f = ?SETS:from_list(Fs1), pres_t = ?SETS:from_list(Ts1), privacy_list = PrivList}); @@ -677,14 +682,12 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) -> {xmlelement, "success", [{"xmlns", ?NS_SASL}], []}), U = xml:get_attr_s(username, Props), - AuthModule = xml:get_attr_s(auth_module, Props), ?INFO_MSG("(~w) Accepted authentication for ~s", [StateData#state.socket, U]), fsm_next_state(wait_for_stream, StateData#state{ streamid = new_id(), authenticated = true, - auth_module = AuthModule, user = U}); {continue, ServerOut, NewSASLState} -> send_element(StateData, @@ -795,8 +798,7 @@ wait_for_session({xmlstreamelement, El}, StateData) -> jlib:jid_to_string(JID)]), SID = {now(), self()}, Conn = get_conn_type(StateData), - Info = [{ip, StateData#state.ip}, {conn, Conn}, - {auth_module, StateData#state.auth_module}], + Info = [{ip, StateData#state.ip}, {conn, Conn}], ejabberd_sm:open_session( SID, U, StateData#state.server, R, Info), Res = jlib:make_result_iq_reply(El), @@ -813,7 +815,7 @@ wait_for_session({xmlstreamelement, El}, StateData) -> PrivList = ejabberd_hooks:run_fold( privacy_get_user_list, StateData#state.server, - #userlist{}, + none, [U, StateData#state.server]), fsm_next_state(session_established, StateData#state{ @@ -849,8 +851,6 @@ wait_for_session(closed, StateData) -> {stop, normal, StateData}. - - session_established({xmlstreamelement, El}, StateData) -> {xmlelement, Name, Attrs, _Els} = El, User = StateData#state.user, @@ -907,18 +907,24 @@ session_established({xmlstreamelement, El}, StateData) -> StateData) end; "iq" -> - case jlib:iq_query_info(NewEl) of - #iq{xmlns = ?NS_PRIVACY} = IQ -> - process_privacy_iq( - FromJID, ToJID, IQ, StateData); - _ -> - ejabberd_hooks:run( - user_send_packet, - Server, - [FromJID, ToJID, NewEl]), - ejabberd_router:route( - FromJID, ToJID, NewEl), - StateData + case StateData#state.privacy_list of + none -> + ejabberd_router:route(FromJID, ToJID, NewEl), + StateData; + _PrivList -> + case jlib:iq_query_info(NewEl) of + #iq{xmlns = ?NS_PRIVACY} = IQ -> + process_privacy_iq( + FromJID, ToJID, IQ, StateData); + _ -> + ejabberd_hooks:run( + user_send_packet, + Server, + [FromJID, ToJID, NewEl]), + ejabberd_router:route( + FromJID, ToJID, NewEl), + StateData + end end; "message" -> ejabberd_hooks:run(user_send_packet, @@ -1420,13 +1426,7 @@ process_presence_probe(From, To, StateData) -> allow -> Pid=element(2, StateData#state.sid), ejabberd_hooks:run(presence_probe_hook, StateData#state.server, [From, To, Pid]), - %% Don't route a presence probe to oneself - case From == To of - false -> - ejabberd_router:route(To, From, Packet); - true -> - ok - end + ejabberd_router:route(To, From, Packet) end; Cond2 -> ejabberd_router:route(To, From, @@ -1908,8 +1908,7 @@ process_unauthenticated_stanza(StateData, El) -> Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq, StateData#state.server, empty, - [StateData#state.server, IQ, - StateData#state.ip]), + [StateData#state.server, IQ]), case Res of empty -> % The only reasonable IQ's here are auth and register IQ's @@ -1952,3 +1951,7 @@ fsm_reply(Reply, session_established, StateData) -> {reply, Reply, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT}; fsm_reply(Reply, StateName, StateData) -> {reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}. + +%% Used by c2s blacklist plugins +is_ip_blacklisted({IP,_Port}) -> + ejabberd_hooks:run_fold(check_bl_c2s, false, [IP]). diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl index f94a0a0e1..389b3a106 100644 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@ -65,19 +65,27 @@ start(Module, SockMod, Socket, Opts) -> SocketData = #socket_state{sockmod = SockMod, socket = Socket, receiver = Receiver}, - {ok, Pid} = Module:start({?MODULE, SocketData}, Opts), - case SockMod:controlling_process(Socket, Receiver) of - ok -> - ok; + case Module:start({?MODULE, SocketData}, Opts) of + {ok, Pid} -> + case SockMod:controlling_process(Socket, Receiver) of + ok -> + ok; + {error, _Reason} -> + SockMod:close(Socket) + end, + ejabberd_receiver:become_controller(Receiver, Pid); {error, _Reason} -> SockMod:close(Socket) - end, - ejabberd_receiver:become_controller(Receiver, Pid); + end; raw -> - {ok, Pid} = Module:start({SockMod, Socket}, Opts), - case SockMod:controlling_process(Socket, Pid) of - ok -> - ok; + case Module:start({SockMod, Socket}, Opts) of + {ok, Pid} -> + case SockMod:controlling_process(Socket, Pid) of + ok -> + ok; + {error, _Reason} -> + SockMod:close(Socket) + end; {error, _Reason} -> SockMod:close(Socket) end diff --git a/src/jlib.erl b/src/jlib.erl index 1ee2e4ffa..4fd897599 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -59,7 +59,8 @@ now_to_local_string/1, datetime_string_to_timestamp/1, decode_base64/1, - encode_base64/1]). + encode_base64/1, + ip_to_list/1]). -include("jlib.hrl"). @@ -676,3 +677,9 @@ e(X) when X>51, X<62 -> X-4; e(62) -> $+; e(63) -> $/; e(X) -> exit({bad_encode_base64_token, X}). + +%% Convert Erlang inet IP to list +ip_to_list({IP, _Port}) -> + ip_to_list(IP); +ip_to_list({A,B,C,D}) -> + lists:flatten(io_lib:format("~w.~w.~w.~w",[A,B,C,D])). diff --git a/src/mod_ip_blacklist.erl b/src/mod_ip_blacklist.erl new file mode 100644 index 000000000..095c501a4 --- /dev/null +++ b/src/mod_ip_blacklist.erl @@ -0,0 +1,113 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_ip_blacklist.erl +%%% Author : Mickael Remond +%%% Purpose : Download blacklists from ProcessOne +%%% Created : 5 May 2008 by Mickael Remond +%%% Usage : Add the following line in modules section of ejabberd.cfg: +%%% {mod_ip_blacklist, []} +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2008 Process-one +%%% +%%% 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_ip_blacklist). +-author('mremond@process-one.net'). + +-behaviour(gen_mod). + +%% API: +-export([start/2, + init/1, + stop/1]). +-export([update_bl_c2s/0]). +%% Hooks: +-export([is_ip_in_c2s_blacklist/2]). + +-include("ejabberd.hrl"). + +-define(PROCNAME, ?MODULE). +-define(BLC2S, "http://xaai.process-one.net/bl_c2s.txt"). +-define(UPDATE_INTERVAL, 6). %% in hours + +-record(state, {timer}). +-record(bl_c2s, {ip}). + +%% Start once for all vhost +start(Host, Opts) -> + case whereis(?PROCNAME) of + undefined -> + ?DEBUG("Starting mod_ip_blacklist ~p ~p~n", [Host, Opts]), + register(?PROCNAME, + spawn(?MODULE, init, [#state{}])); + _ -> + ok + end. + +%% TODO: +stop(_Host) -> + ok. + +init(State)-> + inets:start(), + ets:new(bl_c2s, [named_table, public, {keypos, #bl_c2s.ip}]), + update_bl_c2s(), + %% Register hooks for blacklist + ejabberd_hooks:add(check_bl_c2s, ?MODULE, is_ip_in_c2s_blacklist, 50), + %% Set timer: Download the blacklist file every 6 hours + timer:apply_interval(timer:hours(?UPDATE_INTERVAL), ?MODULE, update_bl_c2s, []), + loop(State). + +%% Remove timer when stop is received. +loop(_State) -> + receive + stop -> + ok + end. + +%% Download blacklist file from ProcessOne XAAI +%% and update the table internal table +%% TODO: Support comment lines starting by % +update_bl_c2s() -> + ?INFO_MSG("Updating C2S Blacklist", []), + {ok, {{_Version, 200, _Reason}, _Headers, Body}} = http:request(?BLC2S), + IPs = string:tokens(Body,"\n"), + ets:delete_all_objects(bl_c2s), + lists:foreach( + fun(IP) -> + ets:insert(bl_c2s, #bl_c2s{ip=list_to_binary(IP)}) + end, IPs). + +%% Hook is run with: +%% ejabberd_hooks:run_fold(check_bl_c2s, false, [IP]), +%% Return: false: IP not blacklisted +%% true: IP is blacklisted +%% IPV4 IP tuple: +is_ip_in_c2s_blacklist(_Val, IP) -> + BinaryIP = list_to_binary(jlib:ip_to_list(IP)), + case ets:lookup(bl_c2s, BinaryIP) of + [] -> %% Not in blacklist + false; + [_] -> %% Blacklisted! + {stop, true} + end. + + +%% TODO: +%% - For now, we do not kick user already logged on a given IP after +%% we update the blacklist. -- 2.50.1