diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index b25b0df83..afd3dd6ad 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -63,42 +63,7 @@ -include("ejabberd.hrl"). -include("jlib.hrl"). -include("mod_privacy.hrl"). - --define(SETS, gb_sets). --define(DICT, dict). - -%% pres_a contains all the presence available send (either through roster mechanism or directed). -%% Directed presence unavailable remove user from pres_a. --record(state, {socket, - sockmod, - socket_monitor, - xml_socket, - streamid, - sasl_state, - access, - shaper, - zlib = false, - tls = false, - tls_required = false, - tls_enabled = false, - tls_options = [], - authenticated = false, - jid, - user = "", server = ?MYNAME, resource = "", - sid, - pres_t = ?SETS:new(), - pres_f = ?SETS:new(), - pres_a = ?SETS:new(), - pres_i = ?SETS:new(), - pres_last, pres_pri, - pres_timestamp, - pres_invis = false, - privacy_list = #userlist{}, - conn = unknown, - auth_module = unknown, - ip, - fsm_limit_opts, - lang}). +-include("ejabberd_c2s.hrl"). %-define(DBGFSM, true). @@ -224,21 +189,21 @@ init([{SockMod, Socket}, Opts, FSMLimitOpts]) -> Socket end, SocketMonitor = SockMod:monitor(Socket1), - {ok, wait_for_stream, #state{socket = Socket1, - sockmod = SockMod, - socket_monitor = SocketMonitor, - xml_socket = XMLSocket, - zlib = Zlib, - tls = TLS, - tls_required = StartTLSRequired, - tls_enabled = TLSEnabled, - tls_options = TLSOpts, - streamid = new_id(), - access = Access, - shaper = Shaper, - ip = IP, - fsm_limit_opts = FSMLimitOpts}, - ?C2S_OPEN_TIMEOUT} + StateData = #state{socket = Socket1, + sockmod = SockMod, + socket_monitor = SocketMonitor, + xml_socket = XMLSocket, + zlib = Zlib, + tls = TLS, + tls_required = StartTLSRequired, + tls_enabled = TLSEnabled, + tls_options = TLSOpts, + streamid = new_id(), + access = Access, + shaper = Shaper, + ip = IP, + fsm_limit_opts = FSMLimitOpts}, + {ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT} end; init([StateName, StateData, _FSMLimitOpts]) -> MRef = (StateData#state.sockmod):monitor(StateData#state.socket), @@ -534,8 +499,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> privacy_get_user_list, StateData#state.server, #userlist{}, [U, StateData#state.server]), - NewStateData = - StateData#state{ + NewStateData = StateData#state{ user = U, resource = R, jid = JID, @@ -545,7 +509,11 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> pres_f = ?SETS:from_list(Fs1), pres_t = ?SETS:from_list(Ts1), privacy_list = PrivList}, - maybe_migrate(session_established, NewStateData); + DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook, + NewStateData#state.server, + false, + [self(), NewStateData]), + maybe_migrate(session_established, NewStateData#state{debug=DebugFlag}); _ -> ?INFO_MSG( "(~w) Failed legacy authentication for ~s", @@ -910,7 +878,11 @@ wait_for_session({xmlstreamelement, El}, StateData) -> pres_f = ?SETS:from_list(Fs1), pres_t = ?SETS:from_list(Ts1), privacy_list = PrivList}, - maybe_migrate(session_established, NewStateData); + DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook, + NewStateData#state.server, + false, + [self(), NewStateData]), + maybe_migrate(session_established, NewStateData#state{debug=DebugFlag}); _ -> ejabberd_hooks:run(forbidden_session_hook, StateData#state.server, [JID]), @@ -1026,7 +998,7 @@ session_established2(El, StateData) -> ejabberd_hooks:run( user_send_packet, Server, - [FromJID, ToJID, PresenceEl]), + [StateData#state.debug, FromJID, ToJID, PresenceEl]), case ToJID of #jid{user = User, server = Server, @@ -1042,13 +1014,17 @@ session_established2(El, StateData) -> "iq" -> case jlib:iq_query_info(NewEl) of #iq{xmlns = ?NS_PRIVACY} = IQ -> + ejabberd_hooks:run( + user_send_packet, + Server, + [StateData#state.debug, FromJID, ToJID, NewEl]), process_privacy_iq( FromJID, ToJID, IQ, StateData); _ -> ejabberd_hooks:run( user_send_packet, Server, - [FromJID, ToJID, NewEl]), + [StateData#state.debug, FromJID, ToJID, NewEl]), ejabberd_router:route( FromJID, ToJID, NewEl), StateData @@ -1056,7 +1032,7 @@ session_established2(El, StateData) -> "message" -> ejabberd_hooks:run(user_send_packet, Server, - [FromJID, ToJID, NewEl]), + [StateData#state.debug, FromJID, ToJID, NewEl]), check_privacy_route(FromJID, StateData, FromJID, ToJID, NewEl), StateData; @@ -1361,7 +1337,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) -> send_element(StateData, FixedPacket), ejabberd_hooks:run(user_receive_packet, StateData#state.server, - [StateData#state.jid, From, To, FixedPacket]), + [StateData#state.debug, StateData#state.jid, From, To, FixedPacket]), ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]), fsm_next_state(StateName, NewState); true -> @@ -1419,13 +1395,16 @@ print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) -> pres_a = {pres_a, ?SETS:size(A)}, pres_i = {pres_i, ?SETS:size(I)} }. - + %%---------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm %% Returns: any %%---------------------------------------------------------------------- terminate({migrated, ClonePid}, StateName, StateData) -> + ejabberd_hooks:run(c2s_debug_stop_hook, + StateData#state.server, + [self(), StateData]), if StateName == session_established -> ?INFO_MSG("(~w) Migrating ~s to ~p on node ~p", [StateData#state.socket, @@ -2149,7 +2128,7 @@ resend_offline_messages(#state{user = User, send_element(StateData, FixedPacket), ejabberd_hooks:run(user_receive_packet, StateData#state.server, - [StateData#state.jid, + [StateData#state.debug, StateData#state.jid, From, To, FixedPacket]); true -> ok diff --git a/src/ejabberd_c2s.hrl b/src/ejabberd_c2s.hrl new file mode 100644 index 000000000..485a6e400 --- /dev/null +++ b/src/ejabberd_c2s.hrl @@ -0,0 +1,62 @@ +%%%---------------------------------------------------------------------- +%%% +%%% ejabberd, Copyright (C) 2002-2010 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 +%%% +%%%---------------------------------------------------------------------- + + +-ifndef(mod_privacy_hrl). +-include("mod_privacy.hrl"). +-endif. + +-define(SETS, gb_sets). +-define(DICT, dict). + +%% pres_a contains all the presence available send (either through roster mechanism or directed). +%% Directed presence unavailable remove user from pres_a. +-record(state, {socket, + sockmod, + socket_monitor, + xml_socket, + streamid, + sasl_state, + access, + shaper, + zlib = false, + tls = false, + tls_required = false, + tls_enabled = false, + tls_options = [], + authenticated = false, + jid, + user = "", server = ?MYNAME, resource = "", + sid, + pres_t = ?SETS:new(), + pres_f = ?SETS:new(), + pres_a = ?SETS:new(), + pres_i = ?SETS:new(), + pres_last, pres_pri, + pres_timestamp, + pres_invis = false, + privacy_list = #userlist{}, + conn = unknown, + auth_module = unknown, + ip, + fsm_limit_opts, + lang, + debug=false}). diff --git a/src/mod_c2s_debug.erl b/src/mod_c2s_debug.erl new file mode 100644 index 000000000..6bd2e6e76 --- /dev/null +++ b/src/mod_c2s_debug.erl @@ -0,0 +1,175 @@ +%% Usage: +%% In config file: +%% {mod_c2s_debug, [{logdir, "/tmp/xmpplogs"}]}, +%% From Erlang shell: +%% mod_c2s_debug:start("localhost", []). +%% mod_c2s_debug:stop("localhost"). +%% +%% Warning: Only one module for the debug handler can be defined. +-module(mod_c2s_debug). +-author('mremond@process-one.net'). + +-behaviour(gen_mod). +-behavior(gen_server). + +-export([start/2, start_link/2, stop/1, + debug_start/3, debug_stop/2, log_packet/4, log_packet/5]). +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("ejabberd_c2s.hrl"). + +-record(modstate, {host, logdir, pid, iodevice}). +-record(clientinfo, {pid, jid, auth_module, ip}). + +-define(SUPERVISOR, ejabberd_sup). +-define(PROCNAME, c2s_debug). + +%%==================================================================== +%% gen_mod callbacks +%%==================================================================== +start(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + Spec = {Proc, {?MODULE, start_link, [Host, Opts]}, + transient, 2000, worker, [?MODULE]}, + supervisor:start_child(?SUPERVISOR, Spec). + +stop(Host) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:call(Proc, stop), + supervisor:delete_child(?SUPERVISOR, Proc). + +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). + +%%==================================================================== +%% Hooks +%%==================================================================== + +%% Debug handled by another module... Do nothing: +debug_start(_Status, Pid, C2SState) -> + Host = C2SState#state.server, + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + + JID = jlib:jid_to_string(C2SState#state.jid), + AuthModule = C2SState#state.auth_module, + IP = C2SState#state.ip, + ClientInfo = #clientinfo{pid = Pid, jid = JID, auth_module = AuthModule, ip = IP}, + + gen_server:call(Proc, {debug_start, ClientInfo}). + +debug_stop(Pid, C2SState) -> + Host = C2SState#state.server, + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:cast(Proc, {debug_stop, Pid}). + +log_packet(false, _FromJID, _ToJID, _Packet) -> + ok; +log_packet(true, FromJID, ToJID, Packet) -> + Host = FromJID#jid.lserver, + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:cast(Proc, {addlog, {"Send", FromJID, ToJID, Packet}}). +log_packet(false, _JID, _FromJID, _ToJID, _Packet) -> + ok; +log_packet(true, JID, FromJID, ToJID, Packet) -> + Host = JID#jid.lserver, + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:cast(Proc, {addlog, {"Receive", FromJID, ToJID, Packet}}). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== +init([Host, Opts]) -> + ?INFO_MSG("Starting c2s debug module for: ~p", [Host]), + MyHost = gen_mod:get_opt_host(Host, Opts, "c2s_debug.@HOST@"), + ejabberd_hooks:add(c2s_debug_start_hook, Host, + ?MODULE, debug_start, 50), + ejabberd_hooks:add(c2s_debug_stop_hook, Host, + ?MODULE, debug_stop, 50), + ejabberd_hooks:add(user_send_packet, Host, ?MODULE, log_packet, 50), + ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, log_packet, 50), + + Logdir = gen_mod:get_opt(logdir, Opts, "/tmp/xmpplogs/"), + make_dir_rec(Logdir), + {ok, #modstate{host = MyHost, logdir = Logdir}}. + +terminate(_Reason, #modstate{host = Host}) -> + ?INFO_MSG("Stopping c2s debug module for: ~s", [Host]), + ejabberd_hooks:delete(c2s_debug_start_hook, Host, + ?MODULE, debug_start, 50), + ejabberd_hooks:delete(c2s_debug_stop_hook, Host, + ?MODULE, debug_stop, 50), + ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, log_packet, 50). + +handle_call({debug_start, ClientInfo}, _From, #modstate{pid=undefined} = State) -> + Pid = ClientInfo#clientinfo.pid, + ?INFO_MSG("Debug started for PID:~p", [Pid]), + + JID = ClientInfo#clientinfo.jid, + AuthModule = ClientInfo#clientinfo.auth_module, + IP = ClientInfo#clientinfo.ip, + + {ok, IOD} = file:open(filename(State#modstate.logdir), [append]), + Line = io_lib:format("~s - Session open~nJID: ~s~nAuthModule: ~p~nIP: ~p~n", + [timestamp(), JID, AuthModule, IP]), + file:write(IOD, Line), + + {reply, true, State#modstate{pid = Pid, iodevice = IOD}}; +handle_call({debug_start, _ClientInfo}, _From, State) -> + {reply, false, State}; +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; +handle_call(_Req, _From, State) -> + {reply, {error, badarg}, State}. + +handle_cast({addlog, _}, #modstate{iodevice=undefined} = State) -> + {noreply, State}; +handle_cast({addlog, {Direction, FromJID, ToJID, Packet}}, #modstate{iodevice=IOD} = State) -> + LogEntry = io_lib:format("=====~n~s - ~s~nFrom: ~s~nTo: ~s~n~s~n", [timestamp(), Direction, + jlib:jid_to_string(FromJID), + jlib:jid_to_string(ToJID), + xml:element_to_string(Packet)]), + file:write(IOD, LogEntry), + {noreply, State}; +handle_cast({debug_stop, Pid}, #modstate{pid=Pid, iodevice=IOD} = State) -> + Line = io_lib:format("=====~n~s - Session closed~n", + [timestamp()]), + file:write(IOD, Line), + + file:close(IOD), + {noreply, State#modstate{pid = undefined, iodevice=undefined}}; +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% Generate filename +filename(LogDir) -> + Filename = lists:flatten(timestamp()) ++ "-c2s.log", + filename:join([LogDir, Filename]). + +%% Generate timestamp +timestamp() -> + {Y,Mo,D} = erlang:date(), + {H,Mi,S} = erlang:time(), + io_lib:format("~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w", [Y,Mo,D,H,Mi,S]). + +%% Create dir recusively +make_dir_rec(Dir) -> + case file:read_file_info(Dir) of + {ok, _} -> + ok; + {error, enoent} -> + DirS = filename:split(Dir), + DirR = lists:sublist(DirS, length(DirS)-1), + make_dir_rec(filename:join(DirR)), + file:make_dir(Dir) + end. diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 7fb011ef9..dcd4145fe 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -53,7 +53,7 @@ ]). %% hook handlers --export([user_send_packet/3]). +-export([user_send_packet/4]). -include("ejabberd.hrl"). -include("jlib.hrl"). @@ -141,7 +141,8 @@ read_caps([], Result) -> %%==================================================================== %% Hooks %%==================================================================== -user_send_packet(#jid{luser = User, lserver = Server} = From, +user_send_packet(_DebugFlag, + #jid{luser = User, lserver = Server} = From, #jid{luser = User, lserver = Server, lresource = ""}, {xmlelement, "presence", Attrs, Els}) -> Type = xml:get_attr_s("type", Attrs), @@ -155,7 +156,7 @@ user_send_packet(#jid{luser = User, lserver = Server} = From, true -> ok end; -user_send_packet(_From, _To, _Packet) -> +user_send_packet(_DebugFlag, _From, _To, _Packet) -> ok. caps_stream_features(Acc, MyHost) -> diff --git a/src/mod_ping.erl b/src/mod_ping.erl index 80b6dac72..37b06dc98 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -51,7 +51,7 @@ handle_info/2, code_change/3]). %% Hook callbacks --export([iq_ping/3, user_online/3, user_offline/3, user_send/3]). +-export([iq_ping/3, user_online/3, user_offline/3, user_send/4]). -record(state, {host = "", send_pings = ?DEFAULT_SEND_PINGS, @@ -193,7 +193,7 @@ user_online(_SID, JID, _Info) -> user_offline(_SID, JID, _Info) -> stop_ping(JID#jid.lserver, JID). -user_send(JID, _From, _Packet) -> +user_send(_DebugFlag, JID, _From, _Packet) -> start_ping(JID#jid.lserver, JID). %%==================================================================== diff --git a/src/mod_privacy.hrl b/src/mod_privacy.hrl index 0e0b02b21..3f4489b10 100644 --- a/src/mod_privacy.hrl +++ b/src/mod_privacy.hrl @@ -19,6 +19,8 @@ %%% %%%---------------------------------------------------------------------- +-define(mod_privacy_hrl, true). + -record(privacy, {us, default = none, lists = []}). diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl index 8d33ac4d8..44fcfd25c 100644 --- a/src/mod_service_log.erl +++ b/src/mod_service_log.erl @@ -31,8 +31,8 @@ -export([start/2, stop/1, - log_user_send/3, - log_user_receive/4]). + log_user_send/4, + log_user_receive/5]). -include("ejabberd.hrl"). -include("jlib.hrl"). @@ -51,10 +51,10 @@ stop(Host) -> ?MODULE, log_user_receive, 50), ok. -log_user_send(From, To, Packet) -> +log_user_send(_DebugFlag, From, To, Packet) -> log_packet(From, To, Packet, From#jid.lserver). -log_user_receive(_JID, From, To, Packet) -> +log_user_receive(_DebugFlag, _JID, From, To, Packet) -> log_packet(From, To, Packet, To#jid.lserver). @@ -74,4 +74,3 @@ log_packet(From, To, {xmlelement, Name, Attrs, Els}, Host) -> luser = "", lserver = Logger, lresource = ""}, {xmlelement, "route", [], [FixedPacket]}) end, Loggers). -