Description
Description
The issue is strictly speaking not a bug rather than improvement.
When the SSH server receives USERAUTH_REQUEST it starts user authentication, changing server's state to userauth. If SERVICE_REQUEST comes in this state the server drops the connection with PROTOCOL_ERROR. While the behaviour for such case is not described in the RFCs 4252, 4253 (if I'm reading it right) it does not harm to accept the SERVICE_REQUEST if it would be accepted in the previous state (i.e. if it would start normal authentication procedure if the server was not in the userauth state).
The behaviour with sending one SERVICE_REQUEST per authentication method is observed with Paramiko, maybe there are more clients that can do that.
Affected versions:
OTP-23.2, OTP-26 (0113d9c) configured as
./configure --enable-hipe
--with-ssl-rpath=no
--with-ssl=/usr/local/openssl-1.1.1d
--enable-aslr
--enable-small-memory
Tested on Linux, Mac OSX.
Attached is the archive with test and printouts from Erlang.
ssh_proto_error.zip
To reproduce the issue prerequisites are:
Installed OTP with erl and erlc in PATH, Python3 installed with Paramiko (I have ver 2.11 locally)
Unpack the archive, run "make -f Makefile.protoerr build start test". The printout from Paramiko will show "Disconnect (code 2): Protocol error"
A patch that could solve the issue:
--- a/lib/ssh/src/ssh_fsm_userauth_server.erl
+++ b/lib/ssh/src/ssh_fsm_userauth_server.erl
@@ -119,6 +119,23 @@ handle_event(internal,
{stop, Shutdown, D}
end;
+handle_event(internal, Msg = #ssh_msg_service_request{name=ServiceName},
+ StateName = {userauth,server}, D0) ->
+ case ServiceName of
+ "ssh-userauth" ->
+ Ssh0 = #ssh{session_id=SessionId} = D0#data.ssh_params,
+ {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
+ D = ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {keep_state, D};
+
+ _ ->
+ {Shutdown, D} =
+ ?send_disconnect(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ io_lib:format("Unknown service: ~p",[ServiceName]),
+ StateName, D0),
+ {stop, Shutdown, D}
+ end;
+
handle_event(internal, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D0) ->
case ssh_auth:handle_userauth_info_response(Msg, D0#data.ssh_params) of
{authorized, User, {Reply, Ssh1}} ->