diff --git a/src/gen_smtp_client.erl b/src/gen_smtp_client.erl index a3030657..56008d7f 100644 --- a/src/gen_smtp_client.erl +++ b/src/gen_smtp_client.erl @@ -394,11 +394,31 @@ do_AUTH_each(Socket, Username, Password, [_Type | Tail]) -> -spec try_EHLO(Socket :: socket:socket(), Options :: list()) -> {ok, list()}. try_EHLO(Socket, Options) -> - socket:send(Socket, ["EHLO ", proplists:get_value(hostname, Options, smtp_util:guess_FQDN()), "\r\n"]), - %% TODO handle fallback to HELO! - {ok, Reply} = read_possible_multiline_reply(Socket), - Extensions = parse_extensions(Reply), - {ok, Extensions}. + ok = socket:send(Socket, ["EHLO ", proplists:get_value(hostname, Options, smtp_util:guess_FQDN()), "\r\n"]), + case read_possible_multiline_reply(Socket) of + {ok, <<"500", _Rest/binary>>} -> + % Unrecognized command, fall back to HELO + try_HELO(Socket, Options); + {ok, <<"4", _Rest/binary>> = Msg} -> + quit(Socket), + throw({temporary_failure, Msg}); + {ok, Reply} -> + {ok, parse_extensions(Reply)} + end. + +-spec try_HELO(Socket :: socket:socket(), Options :: list()) -> {ok, list()}. +try_HELO(Socket, Options) -> + ok = socket:send(Socket, ["HELO ", proplists:get_value(hostname, Options, smtp_util:guess_FQDN()), "\r\n"]), + case read_possible_multiline_reply(Socket) of + {ok, <<"250", _Rest/binary>>} -> + {ok, []}; + {ok, <<"4", _Rest/binary>> = Msg} -> + quit(Socket), + throw({temporary_failure, Msg}); + {ok, Msg} -> + quit(Socket), + throw({permanent_failure, Msg}) + end. % check if we should try to do TLS -spec try_STARTTLS(Socket :: socket:socket(), Options :: list(), Extensions :: list()) -> {socket:socket(), list()}. @@ -686,6 +706,31 @@ session_start_test_() -> end } end, + fun({ListenSock}) -> + {"retry with HELO when EHLO not accepted", + fun() -> + Options = [{relay, "localhost"}, {port, 9876}, {hostname, "testing"}], + {ok, _Pid} = send({"test@foo.com", ["foo@bar.com"], "hello world"}, Options), + {ok, X} = socket:accept(ListenSock, 1000), + socket:send(X, "220 \r\n"), + ?assertMatch({ok, "EHLO testing\r\n"}, socket:recv(X, 0, 1000)), + socket:send(X, "500 5.3.3 Unrecognized command\r\n"), + ?assertMatch({ok, "HELO testing\r\n"}, socket:recv(X, 0, 1000)), + socket:send(X, "250 Some banner\r\n"), + ?assertMatch({ok, "MAIL FROM: \r\n"}, socket:recv(X, 0, 1000)), + socket:send(X, "250 ok\r\n"), + ?assertMatch({ok, "RCPT TO: \r\n"}, socket:recv(X, 0, 1000)), + socket:send(X, "250 ok\r\n"), + ?assertMatch({ok, "DATA\r\n"}, socket:recv(X, 0, 1000)), + socket:send(X, "354 ok\r\n"), + ?assertMatch({ok, "hello world\r\n"}, socket:recv(X, 0, 1000)), + ?assertMatch({ok, ".\r\n"}, socket:recv(X, 0, 1000)), + socket:send(X, "250 ok\r\n"), + ?assertMatch({ok, "QUIT\r\n"}, socket:recv(X, 0, 1000)), + ok + end + } + end, fun({ListenSock}) -> {"a valid complete transaction without TLS advertised should succeed", fun() ->