Changeset View
Changeset View
Standalone View
Standalone View
apps/kolab_guam/src/kolab_guam_session.erl
Show All 21 Lines | |||||
%% 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 | %% state record definition | ||||
-record(state, { listen_socket, socket = undefined, super_pid, tls_config = [], client_implicit_tls = false, client_tls_active = false, server_config = [], | -record(state, { listen_socket, socket = undefined, super_pid, tls_config = [], client_implicit_tls = false, client_tls_active = false, server_config = [], | ||||
rules_active = [], rules_deciding = [], imap_session = undefined, inflator, deflator }). | 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 57 Lines • ▼ Show 20 Lines | case proplists:get_value(Module, ActiveRules) of | ||||
[], 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: ~s", [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), | ||||
{ noreply, State#state{ rules_active = CurrentlyActiveRules } }; | NewSplitCommand = update_split_command_state(ModifiedData, State), | ||||
{ 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]), | ||||
{ noreply, State }. | { noreply, State }. | ||||
terminate(_Reason, #state{ inflator = Inflator, imap_session = ImapSession, deflator = Deflator, socket = Socket, client_implicit_tls = ImplicitTLS, client_tls_active = TLS }) -> | terminate(_Reason, #state{ inflator = Inflator, imap_session = ImapSession, deflator = Deflator, socket = Socket, client_implicit_tls = ImplicitTLS, client_tls_active = TLS }) -> | ||||
close_zlib_handle(Inflator), | close_zlib_handle(Inflator), | ||||
close_zlib_handle(Deflator), | close_zlib_handle(Deflator), | ||||
close_socket(ImplicitTLS, TLS, Socket), | close_socket(ImplicitTLS, TLS, Socket), | ||||
case ImapSession of | case ImapSession of | ||||
undefined -> ok; | undefined -> ok; | ||||
_ -> exit(ImapSession, kill) | _ -> exit(ImapSession, kill) | ||||
end, | end, | ||||
ok. | ok. | ||||
code_change(_OldVsn, State, _Extra) -> | code_change(_OldVsn, State, _Extra) -> | ||||
{ ok, State }. | { ok, State }. | ||||
%% private API | %% private API | ||||
%% update_split_command_state updates the split_command being held on to when we get a server response | |||||
%% in the case of "transactional" messages (such as authentication) where the client and server enter a bidirectional conversation | |||||
%% that is goverened by rules outside the the usual IMAP call/response pattern, we need to wait for the end of the server response | |||||
%% since this is relatively expensive due to having to scan the data for the tagged server response, and is not necessary for all other | |||||
%% IMAP commands, we shortcircuit when the command does not trigger a "transactional" interaction between client and server, and instead | |||||
%% just always reset the split data state at that point | |||||
update_split_command_state(Data, #state{ command_split_reset_trigger = reset_on_server_response, current_command_split = CurrentCommandSplit }) -> | |||||
case CurrentCommandSplit of | |||||
undefined -> undefined; | |||||
{ Tag, _Command, _Data } -> | |||||
case binary:match(Data, <<Tag/binary, " ">>) of | |||||
nomatch -> CurrentCommandSplit; | |||||
{ 0, _ } -> undefined; | |||||
{ Start, _ } -> | |||||
case binary:at(Data, Start - 1) of | |||||
$\n -> undefined; | |||||
_ -> CurrentCommandSplit | |||||
end | |||||
end | |||||
end; | |||||
update_split_command_state(_Data, _State) -> | |||||
undefined. | |||||
accept_client(#state{ client_implicit_tls = true, tls_config = TLSConfig, listen_socket = ListenSocket, super_pid = SupervisorPID }) -> | accept_client(#state{ client_implicit_tls = true, tls_config = TLSConfig, listen_socket = ListenSocket, super_pid = SupervisorPID }) -> | ||||
AcceptSocket = accept_socket(ListenSocket, SupervisorPID), | AcceptSocket = accept_socket(ListenSocket, SupervisorPID), | ||||
%% prep for the next listen | %% prep for the next listen | ||||
{ ok, SSLSocket } = ssl:ssl_accept(AcceptSocket, TLSConfig), | { ok, SSLSocket } = ssl:ssl_accept(AcceptSocket, TLSConfig), | ||||
ok = ssl:setopts(SSLSocket, [{ active, once }, { mode, binary }]), | ok = ssl:setopts(SSLSocket, [{ active, once }, { mode, binary }]), | ||||
% lager:info("~p All done!", [self()]), | % lager:info("~p All done!", [self()]), | ||||
{ ok, SSLSocket, true }; | { ok, SSLSocket, true }; | ||||
accept_client(#state{ listen_socket = ListenSocket, super_pid = SupervisorPID }) -> | accept_client(#state{ listen_socket = ListenSocket, super_pid = SupervisorPID }) -> | ||||
Show All 12 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 } = State) -> | 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 | %%TODO: multipacket input from clients | ||||
% 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), | PreprocessData = preprocess_client_data(Inflator, Data, State), | ||||
%lager:info("FROM CLIENT: ~s", [PreprocessData]), | %lager:info("FROM CLIENT: ~s", [PreprocessData]), | ||||
{ TLSActive, CurrentSocket, CurrentInflator, CurrentDeflator, CurrentUndecidedRules, CurrentActiveRules } = | { 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, PreprocessData, 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 }; | { 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 }; | { TLS, Socket, NewInflator, NewDeflator, UndecidedRules, ActiveRules, <<>>, undefined, undefined }; | ||||
nochange -> | nochange -> | ||||
%%lager:debug("... now applying rules"), | %%lager:debug("... now applying rules"), | ||||
{ ModifiedData, NewUndecidedRules, NewActiveRules } = apply_ruleset_clientside(ImapSession, Socket, PreprocessData, 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 modified data is: ~s", [ModifiedData]), | ||||
%lager:info("The post-processed data is: ~s", [PostProcessed]), | %lager:info("The post-processed data is: ~s", [PostProcessed]), | ||||
BufferThisData = | |||||
case PostAction of | |||||
perform_passthrough -> | |||||
eimap:passthrough_data(ImapSession, ModifiedData), | eimap:passthrough_data(ImapSession, ModifiedData), | ||||
{ TLS, Socket, Inflator, Deflator, NewUndecidedRules, NewActiveRules} | <<>>; | ||||
buffer_data -> | |||||
Data | |||||
end, | |||||
{ TLS, Socket, Inflator, Deflator, NewUndecidedRules, NewActiveRules, BufferThisData, NewSplitCommand, NewSplitResetTrigger } | |||||
end, | end, | ||||
set_socket_active(TLSActive, CurrentSocket), | set_socket_active(TLSActive, CurrentSocket), | ||||
PrevBuffered = State#state.buffered_client_data, | |||||
{ noreply, 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 = <<PrevBuffered/binary, DataToBuffer/binary>>, | |||||
preprocess_client_data(undefined, Data) -> | current_command_split = SplitCommand, | ||||
Data; | command_split_reset_trigger = SplitResetTrigger } }. | ||||
preprocess_client_data(Z, Data) -> | |||||
joined(zlib:inflate(Z, Data), <<>>). | preprocess_client_data(undefined, Data, #state{ buffered_client_data = Buffered }) -> | ||||
<<Buffered/binary, Data/binary>>; | |||||
preprocess_client_data(Z, Data, #state{ buffered_client_data = Buffered }) -> | |||||
Inflated = joined(zlib:inflate(Z, Data), <<>>), | |||||
<<Buffered/binary, Inflated/binary>>. | |||||
postprocess_server_data(undefined, Data) -> | postprocess_server_data(undefined, Data) -> | ||||
%% we aren't compressing so there is nothing to do | %% we aren't compressing so there is nothing to do | ||||
Data; | Data; | ||||
postprocess_server_data(Z, Data) -> | postprocess_server_data(Z, Data) -> | ||||
joined(zlib:deflate(Z, Data, sync), <<>>). | joined(zlib:deflate(Z, Data, sync), <<>>). | ||||
joined([], Binary) -> Binary; | joined([], Binary) -> Binary; | ||||
Show All 22 Lines | apply_ruleset_serverside(ImapSession, ServerData, CurrentlyActiveRules) -> | ||||
apply_next_rule_serverside(ImapSession, ServerData, [], CurrentlyActiveRules). | apply_next_rule_serverside(ImapSession, ServerData, [], CurrentlyActiveRules). | ||||
apply_next_rule_serverside(_ImapSession, ServerData, ActiveRulesAcc, []) -> { ServerData, lists:reverse(ActiveRulesAcc) }; | apply_next_rule_serverside(_ImapSession, ServerData, ActiveRulesAcc, []) -> { ServerData, lists:reverse(ActiveRulesAcc) }; | ||||
apply_next_rule_serverside(ImapSession, ServerData, ActiveRulesAcc, [{ Module, RuleState } | ActiveRules]) -> | apply_next_rule_serverside(ImapSession, ServerData, ActiveRulesAcc, [{ Module, RuleState } | ActiveRules]) -> | ||||
%TODO: allow rules to remove themselves from the action during serverside processing? | %TODO: allow rules to remove themselves from the action during serverside processing? | ||||
{ ModifiedData, ModifiedRuleState } = Module:apply_to_server_message(ImapSession, ServerData, RuleState), | { ModifiedData, ModifiedRuleState } = Module:apply_to_server_message(ImapSession, ServerData, RuleState), | ||||
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, UndecidedRules, CurrentlyActiveRules) -> | apply_ruleset_clientside(_ImapSession, _Socket, ClientData, _CurrentCommandSplit, [], []) -> | ||||
{ StillUndecided, NewlyActive } = check_undecided(Socket, ClientData, UndecidedRules), | { ClientData, [], [], perform_passthrough, undefined }; | ||||
apply_ruleset_clientside(ImapSession, Socket, ClientData, CurrentCommandSplit, UndecidedRules, CurrentlyActiveRules) -> | |||||
{ PostAction, SplitCommand, SplitResetTrigger } = | |||||
case CurrentCommandSplit of | |||||
undefined -> | |||||
case eimap_utils:split_command_into_components(ClientData) of | |||||
{ _Tag, <<>>, <<>> } -> { buffer_data, undefined, reset_for_next_client_command }; | |||||
{ _Tag, Command, _Data } = Split -> { perform_passthrough, Split, when_to_reset_split(Command) } | |||||
end; | |||||
_ -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command } | |||||
end, | |||||
{ StillUndecided, NewlyActive } = check_undecided(Socket, ClientData, SplitCommand, UndecidedRules), | |||||
ActiveRules = CurrentlyActiveRules ++ NewlyActive, | ActiveRules = CurrentlyActiveRules ++ NewlyActive, | ||||
{ ModifiedData, ActiveRulesRun } = apply_next_rule_clientside(ImapSession, ClientData, [], ActiveRules), | %lager:info("Active Rules: ~p", [ActiveRules]), | ||||
{ ModifiedData, StillUndecided, ActiveRulesRun }. | { ModifiedData, ActiveRulesRun } = apply_next_rule_clientside(ImapSession, ClientData, SplitCommand, [], ActiveRules), | ||||
{ ModifiedData, SplitCommand, SplitResetTrigger, StillUndecided, ActiveRulesRun, PostAction }. | |||||
check_undecided(Socket, ClientData, Rules) -> check_next_undecided_rule(Socket, ClientData, Rules, { [], [] }). | |||||
check_next_undecided_rule(_Socket, _ClientData, [], Accs) -> Accs; | when_to_reset_split(<<"AUTHENTICATE">>) -> reset_on_server_response; | ||||
check_next_undecided_rule(Socket, ClientData, [Rule|Rules], { UndecidedAcc, NewActiveAcc }) -> | when_to_reset_split(<<"authenticate">>) -> reset_on_server_response; | ||||
when_to_reset_split(_) -> reset_for_next_client_command. | |||||
check_undecided(Socket, ClientData, undefined, Rules) -> | |||||
%% if we do not have a properly split command ... do nothing! | |||||
{ Rules, [] }; | |||||
check_undecided(Socket, ClientData, SplitCommand, Rules) -> check_next_undecided_rule(Socket, ClientData, SplitCommand, Rules, { [], [] }). | |||||
check_next_undecided_rule(_Socket, _ClientData, _SplitCommand, [], Accs) -> Accs; | |||||
check_next_undecided_rule(Socket, ClientData, SplitCommand, [Rule|Rules], { UndecidedAcc, NewActiveAcc }) -> | |||||
{ Module, RuleState } = Rule, | { Module, RuleState } = Rule, | ||||
%%lager:debug("Does ~p apply with state ~p? let's find out!", [Module, RuleState]), | %%lager:debug("Does ~p apply with state ~p? let's find out!", [Module, RuleState]), | ||||
check_next_undecided_rule(Socket, ClientData, Rules, applies(Module, Module:applies(Socket, ClientData, RuleState), UndecidedAcc, NewActiveAcc)). | Application = Module:applies(Socket, ClientData, SplitCommand, RuleState), | ||||
check_next_undecided_rule(Socket, ClientData, SplitCommand, Rules, applies(Module, Application, UndecidedAcc, NewActiveAcc)). | |||||
applies(Module, { true, RuleState }, UndecidedAcc, NewActiveAcc) -> { UndecidedAcc, [{ Module, RuleState }|NewActiveAcc] }; | applies(Module, { true, RuleState }, UndecidedAcc, NewActiveAcc) -> { UndecidedAcc, [{ Module, RuleState }|NewActiveAcc] }; | ||||
applies(_Module, { false, _RuleState }, UndecidedAcc, NewActiveAcc) -> { UndecidedAcc, NewActiveAcc }; | applies(_Module, { false, _RuleState }, UndecidedAcc, NewActiveAcc) -> { UndecidedAcc, NewActiveAcc }; | ||||
applies(Module, { notyet, RuleState }, UndecidedAcc, NewActiveAcc) -> { [{ Module, RuleState }|UndecidedAcc], NewActiveAcc }. | applies(Module, { notyet, RuleState }, UndecidedAcc, NewActiveAcc) -> { [{ Module, RuleState }|UndecidedAcc], NewActiveAcc }. | ||||
apply_next_rule_clientside(_ImapSession, ClientData, ActiveRulesAcc, []) -> { ClientData, lists:reverse(ActiveRulesAcc) }; | apply_next_rule_clientside(_ImapSession, ClientData, _SplitCommand, ActiveRulesAcc, []) -> { ClientData, lists:reverse(ActiveRulesAcc) }; | ||||
apply_next_rule_clientside(ImapSession, ClientData, ActiveRulesAcc, [{ Module, RuleState }|Rules]) -> | apply_next_rule_clientside(ImapSession, ClientData, SplitCommand, ActiveRulesAcc, [{ Module, RuleState }|Rules]) -> | ||||
{ Data, NewState } = Module:apply_to_client_message(ImapSession, ClientData, RuleState), | { Data, NewState } = Module:apply_to_client_message(ImapSession, ClientData, SplitCommand, RuleState), | ||||
apply_next_rule_clientside(ImapSession, Data, [{ Module, NewState } | ActiveRulesAcc], Rules). | apply_next_rule_clientside(ImapSession, Data, SplitCommand, [{ Module, NewState } | ActiveRulesAcc], Rules). | ||||
relay_response(Socket, Data, false) -> | relay_response(Socket, Data, false) -> | ||||
%lager:debug("Sending over non-secure socket ..."), | %lager:debug("Sending over non-secure socket ..."), | ||||
gen_tcp:send(Socket, Data); | gen_tcp:send(Socket, Data); | ||||
relay_response(Socket, Data, _TLS) -> | relay_response(Socket, Data, _TLS) -> | ||||
%lager:debug("Sending over TLS!"), | %lager:debug("Sending over TLS!"), | ||||
ssl:send(Socket, Data). | ssl:send(Socket, Data). | ||||
▲ Show 20 Lines • Show All 109 Lines • Show Last 20 Lines |