Changeset View
Changeset View
Standalone View
Standalone View
apps/kolab_guam/src/kolab_guam_session.erl
Show All 19 Lines | |||||
-behaviour(gen_server). | -behaviour(gen_server). | ||||
%% API | %% API | ||||
-export([ start_link/6 ]). | -export([ start_link/6 ]). | ||||
%% gen_server callbacks | %% gen_server callbacks | ||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). | ||||
%% state record definition | |||||
-record(state, { listen_socket, socket = undefined, super_pid, tls_config = [], client_implicit_tls = false, client_tls_active = false, server_config = [], | -include("kolab_guam_session.hrl"). | ||||
rules_active = [], rules_deciding = [], imap_session = undefined, inflator, deflator, buffered_client_data = <<>>, | |||||
current_command_split = undefined, command_split_reset_trigger = reset_for_next_client_command }). | |||||
%% public API | %% public API | ||||
start_link(SupervisorPID, ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules) -> gen_server:start_link(?MODULE, [SupervisorPID, ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules], []). | start_link(SupervisorPID, ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules) -> gen_server:start_link(?MODULE, [SupervisorPID, ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules], []). | ||||
%% gen_server API | %% gen_server API | ||||
init([SupervisorPID, ListenSocket, ServerConfig, ImplicitTLS, TLSConfig, Rules]) -> | init([SupervisorPID, ListenSocket, ServerConfig, ImplicitTLS, TLSConfig, Rules]) -> | ||||
%% accepting a connection is blocking .. so do it async | %% accepting a connection is blocking .. so do it async | ||||
%% lager:debug("Starting a session handler on socket ~p for listener ~p", [ListenSocket, SupervisorPID]), | %% lager:debug("Starting a session handler on socket ~p for listener ~p", [ListenSocket, SupervisorPID]), | ||||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Lines | case proplists:get_value(Module, ActiveRules) of | ||||
_ -> [{ Rule, RuleState }|Acc] | _ -> [{ Rule, RuleState }|Acc] | ||||
end | end | ||||
end, | end, | ||||
[], ActiveRules)) | [], ActiveRules)) | ||||
end, | end, | ||||
%TODO: should we also support non-active rules doing imapy things here? | %TODO: should we also support non-active rules doing imapy things here? | ||||
{ noreply, State#state{ rules_active = NewActiveRules } }; | { noreply, State#state{ rules_active = NewActiveRules } }; | ||||
handle_info({ imap_server_response, Data }, #state{ socket = Socket, imap_session = ImapSession, client_tls_active = TLS, deflator = Deflator, rules_active = ActiveRules } = State) -> | handle_info({ imap_server_response, Data }, #state{ socket = Socket, imap_session = ImapSession, client_tls_active = TLS, deflator = Deflator, rules_active = ActiveRules } = State) -> | ||||
%lager:debug("FROM SERVER: ~s", [Data]), | lager:debug("FROM SERVER: ~p", [Data]), | ||||
{ ModifiedData, CurrentlyActiveRules } = apply_ruleset_serverside(ImapSession, Data, ActiveRules), | { ModifiedData, CurrentlyActiveRules } = apply_ruleset_serverside(ImapSession, Data, ActiveRules), | ||||
relay_response(Socket, postprocess_server_data(Deflator, ModifiedData), TLS), | relay_response(Socket, postprocess_server_data(Deflator, ModifiedData), TLS), | ||||
NewSplitCommand = update_split_command_state(ModifiedData, State), | NewSplitCommand = update_split_command_state(ModifiedData, State), | ||||
{ noreply, State#state{ rules_active = CurrentlyActiveRules, current_command_split = NewSplitCommand } }; | { noreply, State#state{ rules_active = CurrentlyActiveRules, current_command_split = NewSplitCommand } }; | ||||
handle_info({ 'EXIT', PID, _Reason }, #state { imap_session = PID } = State) -> | handle_info({ 'EXIT', PID, _Reason }, #state { imap_session = PID } = State) -> | ||||
{ stop, normal, State#state{ imap_session = undefined } }; | { stop, normal, State#state{ imap_session = undefined } }; | ||||
handle_info(Info, State) -> | handle_info(Info, State) -> | ||||
lager:debug("Received unexpected info... ~p", [Info]), | lager:debug("Received unexpected info... ~p", [Info]), | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | |||||
close_zlib_handle(undefined) -> ok; | close_zlib_handle(undefined) -> ok; | ||||
close_zlib_handle(Z) -> zlib:close(Z). | close_zlib_handle(Z) -> zlib:close(Z). | ||||
close_socket(_ImplicitTLS, _TLS, undefined) -> ok; | close_socket(_ImplicitTLS, _TLS, undefined) -> ok; | ||||
close_socket(_ImplicitTLS, true, Socket) -> ssl:close(Socket); | close_socket(_ImplicitTLS, true, Socket) -> ssl:close(Socket); | ||||
close_socket(true, _TLS, Socket) -> ssl:close(Socket); | close_socket(true, _TLS, Socket) -> ssl:close(Socket); | ||||
close_socket(_ImplicitTLS, _TLS, Socket) -> gen_tcp:close(Socket). | close_socket(_ImplicitTLS, _TLS, Socket) -> gen_tcp:close(Socket). | ||||
process_client_data(Socket, Data, #state{ rules_deciding = UndecidedRules, tls_config = TLSConfig, client_tls_active = TLS, rules_active = ActiveRules, socket = Socket, imap_session = ImapSession, inflator = Inflator, deflator = Deflator, server_config = ServerConfig, current_command_split = CurrentCommandSplit } = State) -> | |||||
%%TODO: multipacket input from clients | read_line(Data) -> | ||||
case binary:match(Data, <<"\r\n">>) of | |||||
nomatch -> | |||||
{Data, <<>>}; | |||||
{SplitPos, SplitSize} -> | |||||
{binary:part(Data, 0, SplitPos + SplitSize), binary:part(Data, SplitPos + SplitSize, size(Data)-(SplitPos + SplitSize))} | |||||
end. | |||||
process_preprocessed_client_data(Socket, PreprocessData, #state{ command_split_reset_trigger = CurrentCommandSplitResetTrigger, current_command_split = CurrentCommandSplit } = State) -> | |||||
NewSplitCommand = case CurrentCommandSplitResetTrigger of | |||||
reset_for_next_client_command -> undefined; | |||||
_ -> CurrentCommandSplit | |||||
end, | |||||
% TODO Maybe we should prepend buffered data here instead. | |||||
{Line, Remainder} = read_line(PreprocessData), | |||||
NewState = process_client_line(Line, Socket, State#state{ current_command_split = NewSplitCommand }), | |||||
case Remainder of | |||||
<<>> -> NewState; | |||||
_ -> process_preprocessed_client_data(Socket, Remainder, NewState) | |||||
end. | |||||
% Client data is processed as it comes in, but split up by lines if more than one line comes in at a time. | |||||
% The processing interanly buffers data as necessary if it encounters incomplete lines. | |||||
process_client_data(Socket, Data, #state{ inflator = Inflator } = State) -> | |||||
% TODO: refactor so starttls and compress commands can be made into rules | % TODO: refactor so starttls and compress commands can be made into rules | ||||
PreprocessData = preprocess_client_data(Inflator, Data, State), | PreprocessData = preprocess_client_data(Inflator, Data, State), | ||||
%lager:info("FROM CLIENT: ~s", [PreprocessData]), | lager:debug("FROM CLIENT: ~p", [PreprocessData]), | ||||
{ noreply, process_preprocessed_client_data(Socket, PreprocessData, State) }. | |||||
process_client_line(Data, Socket, #state{ rules_deciding = UndecidedRules, tls_config = TLSConfig, client_tls_active = TLS, rules_active = ActiveRules, socket = Socket, imap_session = ImapSession, inflator = Inflator, deflator = Deflator, server_config = ServerConfig, current_command_split = CurrentCommandSplit } = State) -> | |||||
{ TLSActive, CurrentSocket, CurrentInflator, CurrentDeflator, CurrentUndecidedRules, CurrentActiveRules, DataToBuffer, SplitCommand, SplitResetTrigger } = | { TLSActive, CurrentSocket, CurrentInflator, CurrentDeflator, CurrentUndecidedRules, CurrentActiveRules, DataToBuffer, SplitCommand, SplitResetTrigger } = | ||||
case check_for_transmission_change_commands(TLS, TLSConfig, PreprocessData, Deflator, Socket) of | case check_for_transmission_change_commands(TLS, TLSConfig, Data, Deflator, Socket) of | ||||
{ socket_upgraded, SSLSocket } -> | { socket_upgraded, SSLSocket } -> | ||||
%% if we have upgraded our socket, then do so to the backend if that hasn't happened auomatically | %% if we have upgraded our socket, then do so to the backend if that hasn't happened auomatically | ||||
case proplists:get_value(implicit_tls, ServerConfig, false) of | case proplists:get_value(implicit_tls, ServerConfig, false) of | ||||
false -> eimap:starttls(ImapSession, undefined, undefined); | false -> eimap:starttls(ImapSession, undefined, undefined); | ||||
_ -> ok | _ -> ok | ||||
end, | end, | ||||
{ true, SSLSocket, Inflator, Deflator, UndecidedRules, ActiveRules, <<>>, undefined, undefined }; | { true, SSLSocket, Inflator, Deflator, UndecidedRules, ActiveRules, <<>>, undefined, undefined }; | ||||
{ compression, NewInflator, NewDeflator } -> | { compression, NewInflator, NewDeflator } -> | ||||
eimap:compress(ImapSession), % TODO: make optional | eimap:compress(ImapSession), % TODO: make optional | ||||
{ TLS, Socket, NewInflator, NewDeflator, UndecidedRules, ActiveRules, <<>>, undefined, undefined }; | { TLS, Socket, NewInflator, NewDeflator, UndecidedRules, ActiveRules, <<>>, undefined, undefined }; | ||||
nochange -> | nochange -> | ||||
%%lager:debug("... now applying rules"), | { ModifiedData, NewSplitCommand, NewSplitResetTrigger, NewUndecidedRules, NewActiveRules, PostAction } = apply_ruleset_clientside(ImapSession, Socket, Data, CurrentCommandSplit, UndecidedRules, ActiveRules), | ||||
{ ModifiedData, NewSplitCommand, NewSplitResetTrigger, NewUndecidedRules, NewActiveRules, PostAction } = apply_ruleset_clientside(ImapSession, Socket, PreprocessData, CurrentCommandSplit, UndecidedRules, ActiveRules), | |||||
%%lager:info("The modified data is: ~s", [ModifiedData]), | |||||
%lager:info("The post-processed data is: ~s", [PostProcessed]), | |||||
BufferThisData = | BufferThisData = | ||||
case PostAction of | case PostAction of | ||||
perform_passthrough -> | perform_passthrough -> | ||||
%lager:info("sending (no buffer): ~s", [ModifiedData]), | |||||
eimap:passthrough_data(ImapSession, ModifiedData), | eimap:passthrough_data(ImapSession, ModifiedData), | ||||
<<>>; | <<>>; | ||||
buffer_data -> | buffer_data -> | ||||
% Originally Aaron uses Data here, but later on this buffer is assumed to be | Data | ||||
% already decoded, so we do have to use PreprocessData here, I think. | |||||
case binary:matches(PreprocessData, <<"\r\n">>) of | |||||
[] -> | |||||
%lager:info("buffering: ~s", [PreprocessData]), | |||||
PreprocessData; | |||||
List -> | |||||
{FoundPos, _} = lists:last(List), | |||||
% I would like to have some binary:match for the last instead of the | |||||
% first occurrence; but I'm really inexperienced in erlang so I don't | |||||
% know how to solve this efficiently, so I'm using binary:matches with | |||||
% using the last element only | |||||
SplitPos = FoundPos + 2, | |||||
eimap:passthrough_data(ImapSession, binary:part(PreprocessData, 0, SplitPos)), | |||||
%lager:info("sending first part: ~s", [binary:part(PreprocessData, 0, SplitPos)] ), | |||||
%lager:info("buffering second part: ~s", [binary:part(PreprocessData, SplitPos, size(PreprocessData)-SplitPos)]), | |||||
binary:part(PreprocessData, SplitPos, size(PreprocessData)-SplitPos) | |||||
end | |||||
end, | end, | ||||
{ TLS, Socket, Inflator, Deflator, NewUndecidedRules, NewActiveRules, BufferThisData, NewSplitCommand, NewSplitResetTrigger } | { TLS, Socket, Inflator, Deflator, NewUndecidedRules, NewActiveRules, BufferThisData, NewSplitCommand, NewSplitResetTrigger } | ||||
end, | end, | ||||
set_socket_active(TLSActive, CurrentSocket), | set_socket_active(TLSActive, CurrentSocket), | ||||
%buffered_client_data is already in DataToBuffer via preprocess_client_data | State#state{ rules_deciding = CurrentUndecidedRules, rules_active = CurrentActiveRules, | ||||
{ noreply, State#state{ rules_deciding = CurrentUndecidedRules, rules_active = CurrentActiveRules, | |||||
socket = CurrentSocket, client_tls_active = TLSActive, | socket = CurrentSocket, client_tls_active = TLSActive, | ||||
inflator = CurrentInflator, deflator = CurrentDeflator, | inflator = CurrentInflator, deflator = CurrentDeflator, | ||||
buffered_client_data = <<DataToBuffer/binary>>, | buffered_client_data = DataToBuffer, | ||||
current_command_split = SplitCommand, | current_command_split = SplitCommand, | ||||
command_split_reset_trigger = SplitResetTrigger } }. | command_split_reset_trigger = SplitResetTrigger }. | ||||
preprocess_client_data(undefined, Data, #state{ buffered_client_data = Buffered }) -> | preprocess_client_data(undefined, Data, #state{ buffered_client_data = Buffered }) -> | ||||
<<Buffered/binary, Data/binary>>; | <<Buffered/binary, Data/binary>>; | ||||
preprocess_client_data(Z, Data, #state{ buffered_client_data = Buffered }) -> | preprocess_client_data(Z, Data, #state{ buffered_client_data = Buffered }) -> | ||||
Inflated = iolist_to_binary(zlib:inflate(Z, Data)), | Inflated = iolist_to_binary(zlib:inflate(Z, Data)), | ||||
<<Buffered/binary, Inflated/binary>>. | <<Buffered/binary, Inflated/binary>>. | ||||
postprocess_server_data(undefined, Data) -> | postprocess_server_data(undefined, Data) -> | ||||
Show All 31 Lines | apply_next_rule_serverside(ImapSession, ServerData, ActiveRulesAcc, [{ Module, RuleState } | ActiveRules]) -> | ||||
apply_next_rule_serverside(ImapSession, ModifiedData, [{ Module, ModifiedRuleState } | ActiveRulesAcc], ActiveRules). | apply_next_rule_serverside(ImapSession, ModifiedData, [{ Module, ModifiedRuleState } | ActiveRulesAcc], ActiveRules). | ||||
apply_ruleset_clientside(_ImapSession, _Socket, ClientData, _CurrentCommandSplit, [], []) -> | apply_ruleset_clientside(_ImapSession, _Socket, ClientData, _CurrentCommandSplit, [], []) -> | ||||
{ ClientData, undefined, [], [], [], perform_passthrough }; | { ClientData, undefined, [], [], [], perform_passthrough }; | ||||
apply_ruleset_clientside(ImapSession, Socket, ClientData, CurrentCommandSplit, UndecidedRules, CurrentlyActiveRules) -> | apply_ruleset_clientside(ImapSession, Socket, ClientData, CurrentCommandSplit, UndecidedRules, CurrentlyActiveRules) -> | ||||
{ PostAction, SplitCommand, SplitResetTrigger } = | { PostAction, SplitCommand, SplitResetTrigger } = | ||||
case CurrentCommandSplit of | case CurrentCommandSplit of | ||||
undefined -> | undefined -> | ||||
%We first have to check whether the command is an empty line. In such a case split_command_into_components would return an empty command, | |||||
%even though the command is complete. | |||||
case ClientData of | |||||
<<"\r\n">> -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command }; | |||||
_ -> | |||||
case eimap_utils:split_command_into_components(ClientData) of | case eimap_utils:split_command_into_components(ClientData) of | ||||
% In case of an empty line ("\r\n") the line is complete (doesn't need to be buffered), but there is also no command or tag. | |||||
{ <<>>, <<>>, <<>> } -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command }; | |||||
{ _Tag, <<>>, <<>> } -> { buffer_data, undefined, reset_for_next_client_command }; | { _Tag, <<>>, <<>> } -> { buffer_data, undefined, reset_for_next_client_command }; | ||||
{ _Tag, Command, _Data } = Split -> { perform_passthrough, Split, when_to_reset_split(Command) } | { _Tag, Command, _Data } = Split -> { perform_passthrough, Split, when_to_reset_split(Command) } | ||||
end | |||||
end; | end; | ||||
_ -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command } | _ -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command } | ||||
end, | end, | ||||
{ StillUndecided, NewlyActive } = check_undecided(Socket, ClientData, SplitCommand, UndecidedRules), | { StillUndecided, NewlyActive } = check_undecided(Socket, ClientData, SplitCommand, UndecidedRules), | ||||
ActiveRules = CurrentlyActiveRules ++ NewlyActive, | ActiveRules = CurrentlyActiveRules ++ NewlyActive, | ||||
%lager:info("Active Rules: ~p", [ActiveRules]), | %lager:info("Active Rules: ~p", [ActiveRules]), | ||||
{ ModifiedData, ActiveRulesRun } = apply_next_rule_clientside(ImapSession, ClientData, SplitCommand, [], ActiveRules), | { ModifiedData, ActiveRulesRun } = apply_next_rule_clientside(ImapSession, ClientData, SplitCommand, [], ActiveRules), | ||||
{ ModifiedData, SplitCommand, SplitResetTrigger, StillUndecided, ActiveRulesRun, PostAction }. | { ModifiedData, SplitCommand, SplitResetTrigger, StillUndecided, ActiveRulesRun, PostAction }. | ||||
▲ Show 20 Lines • Show All 142 Lines • Show Last 20 Lines |