Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117753919
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
27 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a328559..bd8ae46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,75 +1,79 @@
# Changelog
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
+## [0.9.4] - 2018-02-19
+### Fixed
+- Support empty lines from the client
+
## [0.9.3] - 2017-05-03
### Fixed
- With a properly crafted folder list, clients could receive empty lines
in filtered folder listings
## [0.9.2] - 2017-03-21
### Fixed
- Fix client message processing when there are no active rules
## [0.9.1] - 2016-02-20
### Fixed
- Improve SSL connection accepts (prevent timing errors on the socket)
- Support fragmentary messages from clients (e.g. tag in one packet, cmd in another)
## [0.9.0] - 2016-07-29
### Added
- bind to a network interface (rather than an IP/host) with net_iface
### Changed
- handle the implicit ssl upgrade a bit more manually, allowing faster
replenish of the listener pool and simplifying socket setup code
- upgraded build to rebar3
- Upgraded eimap to 0.3.0
### Fixed
- fix CAPABILITY response (was CAPABILITIES)
## [0.8.3] - 2016-08-08
### Fixed
- always close ssl sockets as an ssl socket
- keep listen_socket separate from socket
- not required to make the socket active to accept connections
- ensure eimap processes are always terminated in all cases
## [0.8.2] - 2016-07-08
### Added
- listener_pool_size configuration option for listeners
### Changed
- Default size of listener pool drops to 10 from 20
- Rate limit (by introducing a short wait) connection accept()s
### Fixed
- Prevent starvation of the session pool due to clients dropping connections
pre-accept()
## [0.8.1] - 2016-07-06
### Added
- ipv6 connections
### Changed
- update to eimap 0.2.5
### Fixed
- Ignore non-listing LIST commands (e.g. requests for the root/separator)
- Tidy up the server greetings
## [0.8.0] - 2016-06-08
### Added
- systemd service module
- sysv init script
### Changed
- Upgraded eimap to 0.2.4
### Fixed
- Support more variations of the LIST command args in the filter_groupware rule
diff --git a/apps/kolab_guam/src/kolab_guam.app.src b/apps/kolab_guam/src/kolab_guam.app.src
index e879d29..eb352d0 100644
--- a/apps/kolab_guam/src/kolab_guam.app.src
+++ b/apps/kolab_guam/src/kolab_guam.app.src
@@ -1,20 +1,20 @@
%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
{application, kolab_guam,
[
{description, "IMAP session proxy"},
- {vsn, "0.9.3"},
+ {vsn, "0.9.4"},
{registered, []},
{applications, [
kernel,
stdlib,
compiler,
syntax_tools,
goldrush,
lager,
crypto,
ssl
]},
{mod, { kolab_guam, []}},
{env, [
]}
]}.
diff --git a/apps/kolab_guam/src/kolab_guam_session.erl b/apps/kolab_guam/src/kolab_guam_session.erl
index 4d95797..ca4e633 100644
--- a/apps/kolab_guam/src/kolab_guam_session.erl
+++ b/apps/kolab_guam/src/kolab_guam_session.erl
@@ -1,421 +1,427 @@
%% Copyright 2015 Kolab Systems AG (http://www.kolabsys.com)
%%
%% Aaron Seigo (Kolab Systems) <seigo a kolabsys.com>
%%
%% 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 3 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, see <http://www.gnu.org/licenses/>.
-module(kolab_guam_session).
-behaviour(gen_server).
%% API
-export([ start_link/6 ]).
%% gen_server callbacks
-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 = [],
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
start_link(SupervisorPID, ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules) -> gen_server:start_link(?MODULE, [SupervisorPID, ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules], []).
%% gen_server API
init([SupervisorPID, ListenSocket, ServerConfig, ImplicitTLS, TLSConfig, Rules]) ->
%% accepting a connection is blocking .. so do it async
%% lager:debug("Starting a session handler on socket ~p for listener ~p", [ListenSocket, SupervisorPID]),
process_flag(trap_exit, true),
ActiveRules = init_rules(Rules),
gen_server:cast(self(), accept),
%% lager:debug("Rules are ~p from ~p", [ActiveRules, Rules]),
{ ok, #state{ listen_socket = ListenSocket, super_pid = SupervisorPID, client_implicit_tls = ImplicitTLS, tls_config = TLSConfig, server_config = ServerConfig, rules_deciding = ActiveRules } }.
handle_call(_Request, _From, State) ->
{ reply, ok, State }.
handle_cast(accept, State = #state{ server_config = ServerConfig } = State) ->
%% try to rate limit our responses a bit here so that hammering the socket with connections is survivable
timer:sleep(3),
{ ok, AcceptSocket, TLSActive } = accept_client(State),
{ ok, ImapSession } = eimap:start_link(ServerConfig),
eimap:connect(ImapSession, self(), server_hello),
{ noreply, State#state{ listen_socket = undefined, socket = AcceptSocket, imap_session = ImapSession, client_tls_active = TLSActive } };
handle_cast(_Msg, State) ->
{ noreply, State }.
handle_info({ tcp_closed, _Socket }, State) ->
%lager:debug("Client closed socket"),
{ stop, normal, State };
handle_info({ tcp_error, _Socket, _Error }, State) ->
%lager:debug("Socket error"),
{ stop, normal, State };
handle_info({ ssl_closed, _Socket }, State) ->
%lager:debug("Client closed socket"),
{ stop, normal, State };
handle_info({ ssl_error, _Socket, _Error }, State) ->
%lager:debug("Socket error"),
{ stop, normal, State };
handle_info({ tcp, Socket, Data }, #state{ client_tls_active = false } = State) ->
%lager:debug("Data coming in from client over TCP ~s", [Data]),
process_client_data(Socket, Data, State);
handle_info({ ssl, Socket, Data }, State) ->
%lager:debug("Data coming in from client over SSL, ~p", [Data]),
process_client_data(Socket, Data, State);
handle_info({ server_hello, ServerHello }, #state{ imap_session = ImapSession, tls_config = TLSConfig, socket = Socket, client_implicit_tls = ImplicitTLS, client_tls_active = TLSActive, deflator = Deflator } = State) ->
CorrectedHello = correct_hello(TLSActive, ImplicitTLS, TLSConfig, ServerHello),
ServerIdent = proplists:get_value(server_id, ServerHello, <<>>),
FullGreeting = <<"* OK [CAPABILITY ", CorrectedHello/binary, "] ", ServerIdent/binary, "\r\n">>,
eimap:start_passthrough(ImapSession, self()),
relay_response(Socket, postprocess_server_data(Deflator, FullGreeting), TLSActive),
{ noreply, State };
handle_info({ { rule_data, Module, ResponseToken }, Data }, #state{ rules_active = ActiveRules } = State) ->
%lager:debug("Got back data requested by rule ~p: ~p", [Module, Data]),
NewActiveRules =
case proplists:get_value(Module, ActiveRules) of
undefined -> ActiveRules;
ModuleState ->
NewModuleState = Module:imap_data(ResponseToken, Data, ModuleState),
lists:reverse(lists:foldl(fun({ Rule, RuleState }, Acc) ->
case Rule =:= Module of
true -> [{ Rule, NewModuleState }|Acc];
_ -> [{ Rule, RuleState }|Acc]
end
end,
[], ActiveRules))
end,
%TODO: should we also support non-active rules doing imapy things here?
{ 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) ->
%lager:debug("FROM SERVER: ~s", [Data]),
{ ModifiedData, CurrentlyActiveRules } = apply_ruleset_serverside(ImapSession, Data, ActiveRules),
relay_response(Socket, postprocess_server_data(Deflator, ModifiedData), TLS),
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) ->
{ stop, normal, State#state{ imap_session = undefined } };
handle_info(Info, State) ->
lager:debug("Received unexpected info... ~p", [Info]),
{ noreply, State }.
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(Deflator),
close_socket(ImplicitTLS, TLS, Socket),
case ImapSession of
undefined -> ok;
_ -> exit(ImapSession, kill)
end,
ok.
code_change(_OldVsn, State, _Extra) ->
{ ok, State }.
%% 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 }) ->
AcceptSocket = accept_socket(ListenSocket, SupervisorPID),
%% prep for the next listen
{ ok, SSLSocket } = ssl:ssl_accept(AcceptSocket, TLSConfig),
ok = ssl:setopts(SSLSocket, [{ active, once }, { mode, binary }]),
% lager:info("~p All done!", [self()]),
{ ok, SSLSocket, true };
accept_client(#state{ listen_socket = ListenSocket, super_pid = SupervisorPID }) ->
AcceptSocket = accept_socket(ListenSocket, SupervisorPID),
ok = inet:setopts(AcceptSocket, [{ active, once }, { mode, binary }]),
{ ok, AcceptSocket, false }.
accept_socket(ListenSocket, SupervisorPID) ->
AcceptResult = gen_tcp:accept(ListenSocket),
%% start a new accepting process to replace this one, which is now in use
supervisor:start_child(SupervisorPID, []),
%% assert that the accept worked
{ ok, AcceptSocket } = AcceptResult,
AcceptSocket.
close_zlib_handle(undefined) -> ok;
close_zlib_handle(Z) -> zlib:close(Z).
close_socket(_ImplicitTLS, _TLS, undefined) -> ok;
close_socket(_ImplicitTLS, true, Socket) -> ssl:close(Socket);
close_socket(true, _TLS, Socket) -> ssl: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
% TODO: refactor so starttls and compress commands can be made into rules
PreprocessData = preprocess_client_data(Inflator, Data, State),
%lager:info("FROM CLIENT: ~s", [PreprocessData]),
{ TLSActive, CurrentSocket, CurrentInflator, CurrentDeflator, CurrentUndecidedRules, CurrentActiveRules, DataToBuffer, SplitCommand, SplitResetTrigger } =
case check_for_transmission_change_commands(TLS, TLSConfig, PreprocessData, Deflator, Socket) of
{ socket_upgraded, SSLSocket } ->
%% 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
false -> eimap:starttls(ImapSession, undefined, undefined);
_ -> ok
end,
{ true, SSLSocket, Inflator, Deflator, UndecidedRules, ActiveRules, <<>>, undefined, undefined };
{ compression, NewInflator, NewDeflator } ->
eimap:compress(ImapSession), % TODO: make optional
{ TLS, Socket, NewInflator, NewDeflator, UndecidedRules, ActiveRules, <<>>, undefined, undefined };
nochange ->
%%lager:debug("... now applying rules"),
{ 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 =
case PostAction of
perform_passthrough ->
eimap:passthrough_data(ImapSession, ModifiedData),
<<>>;
buffer_data ->
Data
end,
{ TLS, Socket, Inflator, Deflator, NewUndecidedRules, NewActiveRules, BufferThisData, NewSplitCommand, NewSplitResetTrigger }
end,
set_socket_active(TLSActive, CurrentSocket),
PrevBuffered = State#state.buffered_client_data,
{ noreply, State#state{ rules_deciding = CurrentUndecidedRules, rules_active = CurrentActiveRules,
socket = CurrentSocket, client_tls_active = TLSActive,
inflator = CurrentInflator, deflator = CurrentDeflator,
buffered_client_data = <<PrevBuffered/binary, DataToBuffer/binary>>,
current_command_split = SplitCommand,
command_split_reset_trigger = SplitResetTrigger } }.
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) ->
%% we aren't compressing so there is nothing to do
Data;
postprocess_server_data(Z, Data) ->
joined(zlib:deflate(Z, Data, sync), <<>>).
joined([], Binary) -> Binary;
joined([H|Rest], Binary) -> joined(Rest, <<Binary/binary, H/binary>>).
init_rules(RuleConfig) -> init_rule(RuleConfig, []).
init_rule([], Acc) -> Acc;
init_rule([{ RuleName, Config }|RuleConfig], Acc) ->
Module = full_rule_name(RuleName),
%% we try to new the module, but if something goes wrong, e.g. it does not exist,
%% then we skip this config block because it is BROKEN
try Module:new(Config) of
ModuleState -> init_rule(RuleConfig, [{ Module, ModuleState }|Acc])
catch
Type:Error ->
lager:warning("Could not create rule for ~p due to failure: ~p ~p", [RuleName, Type, Error]),
init_rule(RuleConfig, Acc)
end;
init_rule([_|RuleConfig], Acc) ->
init_rule(RuleConfig, Acc).
full_rule_name(Module) when is_atom(Module) -> list_to_atom("kolab_guam_rule_" ++ atom_to_list(Module)).
apply_ruleset_serverside(ImapSession, ServerData, CurrentlyActiveRules) ->
%TODO: allow undecided rules to opt-in here as well
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, [{ Module, RuleState } | ActiveRules]) ->
%TODO: allow rules to remove themselves from the action during serverside processing?
{ ModifiedData, ModifiedRuleState } = Module:apply_to_server_message(ImapSession, ServerData, RuleState),
apply_next_rule_serverside(ImapSession, ModifiedData, [{ Module, ModifiedRuleState } | ActiveRulesAcc], ActiveRules).
apply_ruleset_clientside(_ImapSession, _Socket, ClientData, _CurrentCommandSplit, [], []) ->
{ ClientData, [], [], [], [], perform_passthrough };
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) }
+ %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
+ { _Tag, <<>>, <<>> } -> { buffer_data, undefined, reset_for_next_client_command };
+ { _Tag, Command, _Data } = Split -> { perform_passthrough, Split, when_to_reset_split(Command) }
+ end
end;
_ -> { perform_passthrough, CurrentCommandSplit, reset_for_next_client_command }
end,
{ StillUndecided, NewlyActive } = check_undecided(Socket, ClientData, SplitCommand, UndecidedRules),
ActiveRules = CurrentlyActiveRules ++ NewlyActive,
%lager:info("Active Rules: ~p", [ActiveRules]),
{ ModifiedData, ActiveRulesRun } = apply_next_rule_clientside(ImapSession, ClientData, SplitCommand, [], ActiveRules),
{ ModifiedData, SplitCommand, SplitResetTrigger, StillUndecided, ActiveRulesRun, PostAction }.
when_to_reset_split(<<"AUTHENTICATE">>) -> reset_on_server_response;
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,
%%lager:debug("Does ~p apply with state ~p? let's find out!", [Module, RuleState]),
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, { false, _RuleState }, UndecidedAcc, NewActiveAcc) -> { UndecidedAcc, NewActiveAcc };
applies(Module, { notyet, RuleState }, UndecidedAcc, NewActiveAcc) -> { [{ Module, RuleState }|UndecidedAcc], NewActiveAcc }.
apply_next_rule_clientside(_ImapSession, ClientData, _SplitCommand, ActiveRulesAcc, []) -> { ClientData, lists:reverse(ActiveRulesAcc) };
apply_next_rule_clientside(ImapSession, ClientData, SplitCommand, ActiveRulesAcc, [{ Module, RuleState }|Rules]) ->
{ Data, NewState } = Module:apply_to_client_message(ImapSession, ClientData, SplitCommand, RuleState),
apply_next_rule_clientside(ImapSession, Data, SplitCommand, [{ Module, NewState } | ActiveRulesAcc], Rules).
relay_response(Socket, Data, false) ->
%lager:debug("Sending over non-secure socket ..."),
gen_tcp:send(Socket, Data);
relay_response(Socket, Data, _TLS) ->
%lager:debug("Sending over TLS!"),
ssl:send(Socket, Data).
check_for_transmission_change_commands(TLS, TLSConfig, Buffer, Deflator, Socket) ->
{Tag, Command, _Data } = eimap_utils:split_command_into_components(Buffer),
case check_tls_state(TLS, TLSConfig, Command, Deflator, Socket, Tag) of
nochange -> check_compress_request(Deflator, Command, Socket, TLS, Tag);
Response -> Response
end.
check_tls_state(false, TLSConfig, <<"STARTTLS">>, Deflator, Socket, Tag) -> start_client_tls(TLSConfig, Deflator, Socket, Tag);
check_tls_state(false, TLSConfig, <<"starttls">>, Deflator, Socket, Tag) -> start_client_tls(TLSConfig, Deflator, Socket, Tag);
check_tls_state(_TLS, _TLSConfig, _Buffer, _Deflator, _Socket, _Tag) -> nochange.
start_client_tls(TLSConfig, Deflator, Socket, Tag) ->
Response = <<Tag/binary, " OK Begin TLS negotiation now\r\n">>,
relay_response(Socket, postprocess_server_data(Deflator, Response), false),
inet:setopts(Socket, [{ active, false }]), %% must be set to active false, otherwise can fail depending on timing
{ ok, SSLSocket } = ssl:ssl_accept(Socket, TLSConfig),
{ socket_upgraded, SSLSocket }.
check_compress_request(undefined, <<"COMPRESS">>, Socket, TLS, Tag) -> start_client_compression(Socket, TLS, Tag);
check_compress_request(undefined, <<"compress">>, Socket, TLS, Tag) -> start_client_compression(Socket, TLS, Tag);
check_compress_request(_Deflator, _Command, _Socket, _TLS, _Tag) -> nochange.
start_client_compression(Socket, TLS, Tag) ->
Response = <<Tag/binary, " OK DEFLATE active\r\n">>,
relay_response(Socket, postprocess_server_data(undefined, Response), TLS),
%% create an inflate/deflate pair for use with the client
Inflator = zlib:open(),
ok = zlib:inflateInit(Inflator, -15),
Deflator = zlib:open(),
ok = zlib:deflateInit(Deflator, 1, deflated, -15, 8, default),
{ compression, Inflator, Deflator }.
set_socket_active(true, Socket) -> ssl:setopts(Socket, [{ active, once }]);
set_socket_active(_, Socket) -> inet:setopts(Socket, [{ active, once }]).
-spec correct_hello(TLSActive :: true | false, ImplicitTLS :: true | false, TlSConfig :: [] | list(), ServerHello :: binary()) -> CorrectedHello :: binary().
correct_hello(true, true, _TLSConfig, ServerResponse) ->
% the connection is already secured, so don't advertise starttls to the client
ensure_hello_does_not_have_starttls(ServerResponse);
correct_hello(true, _ImplicitTLS, _TLSConfig, ServerResponse) ->
% the connection is already secured, so don't advertise starttls to the client
ensure_hello_does_not_have_starttls(ServerResponse);
correct_hello(_TLSActive, _ImplicitTLS, [], ServerResponse) ->
% guam does not have a TLS config and so can not provide TLS to the client
ensure_hello_does_not_have_starttls(ServerResponse);
correct_hello(_TLSActive, _ImplicitTLS, _TLSConfig, ServerResponse) ->
% guam has a TLS config, and it is not currently active, so make sure to include
% STARTTLS in our response regardless of what the backend says
ensure_hello_has_starttls(ServerResponse).
ensure_hello_has_starttls(ServerResponse) ->
ServerHello = proplists:get_value(capabilities, ServerResponse, <<>>),
case binary:match(ServerHello, <<"STARTTLS">>) of
nomatch -> add_starttls_to_capabilities(ServerHello);
_ -> ServerHello
end.
add_starttls_to_capabilities(ServerHello) ->
case binary:match(ServerHello, <<"CAPABILITY ">>) of
nomatch -> add_starttls_after_imap4_atom(ServerHello);
{ Start, End } ->
Prefix = binary:part(ServerHello, 0, Start + End),
Suffix = binary:part(ServerHello, Start + End, size(ServerHello) - Start - End),
CorrectHello = <<Prefix/binary, "STARTTLS ", Suffix/binary>>,
remove_auth_offers(CorrectHello)
end.
add_starttls_after_imap4_atom(ServerHello) ->
case binary:match(ServerHello, <<"IMAP4rev1 ">>) of
nomatch -> <<"STARTTLS ", ServerHello/binary>>;
{ Start, End } ->
Prefix = binary:part(ServerHello, 0, Start + End),
Suffix = binary:part(ServerHello, Start + End, size(ServerHello) - Start - End),
CorrectHello = <<Prefix/binary, "STARTTLS ", Suffix/binary>>,
remove_auth_offers(CorrectHello)
end.
ensure_hello_does_not_have_starttls(ServerResponse) ->
ServerHello = proplists:get_value(capabilities, ServerResponse, <<>>),
case binary:match(ServerHello, <<"STARTTLS">>) of
nomatch -> ServerHello;
{ Start, End } ->
Prefix = binary:part(ServerHello, 0, Start),
Suffix = binary:part(ServerHello, Start + End, size(ServerHello) - Start - End),
<<Prefix/binary, Suffix/binary>>
end.
remove_auth_offers(ServerHello) ->
case binary:match(ServerHello, <<"AUTH=">>) of
nomatch -> ensure_advertise_login_disabled(ServerHello);
{ Start, _End } ->
Prefix = binary:part(ServerHello, 0, Start),
Suffix =
case binary:match(ServerHello, <<" ">>, [{ scope, { Start, size(ServerHello) - Start } }]) of
nomatch ->
%% end of the line, so no suffix
<<>>;
{ SpaceStart, SpaceEnd } ->
binary:part(ServerHello, SpaceStart + SpaceEnd, size(ServerHello) - SpaceStart - SpaceEnd)
end,
remove_auth_offers(<<Prefix/binary, Suffix/binary>>)
end.
ensure_advertise_login_disabled(ServerHello) ->
case binary:match(ServerHello, <<"LOGINDISABLED">>) of
nomatch -> <<ServerHello/binary, " LOGINDISABLED">>;
_ -> ServerHello
end.
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Apr 4, 6:21 AM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822831
Default Alt Text
(27 KB)
Attached To
Mode
rG guam
Attached
Detach File
Event Timeline