aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/src/ssh_connection_handler.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh/src/ssh_connection_handler.erl')
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl1378
1 files changed, 716 insertions, 662 deletions
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 2468791c20..b49562db9c 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -65,7 +65,8 @@
%%% Exports not intended to be used :)
-export([init_connection_handler/3, % proc_lib:spawn needs this
- init_ssh_record/3, % Export intended for low level protocol test suites
+ init_ssh_record/3, % Export of this internal function
+ % intended for low-level protocol test suites
renegotiate/1, renegotiate_data/1 % Export intended for test cases
]).
@@ -305,6 +306,22 @@ adjust_window(ConnectionHandler, Channel, Bytes) ->
cast(ConnectionHandler, {adjust_window, Channel, Bytes}).
%%--------------------------------------------------------------------
+-spec close(connection_ref(),
+ channel_id()
+ ) -> ok.
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+close(ConnectionHandler, ChannelId) ->
+ case call(ConnectionHandler, {close, ChannelId}) of
+ ok ->
+ ok;
+ {error, closed} ->
+ ok
+ end.
+
+%%====================================================================
+%% Test support
+%%====================================================================
+%%--------------------------------------------------------------------
-spec renegotiate(connection_ref()
) -> ok.
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -318,18 +335,6 @@ renegotiate(ConnectionHandler) ->
renegotiate_data(ConnectionHandler) ->
cast(ConnectionHandler, data_size).
-%%--------------------------------------------------------------------
--spec close(connection_ref(),
- channel_id()
- ) -> ok.
-%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-close(ConnectionHandler, ChannelId) ->
- case call(ConnectionHandler, {close, ChannelId}) of
- ok ->
- ok;
- {error, closed} ->
- ok
- end.
%%====================================================================
%% Internal process state
@@ -345,14 +350,14 @@ close(ConnectionHandler, ChannelId) ->
transport_close_tag :: atom(), % ex: tcp_closed
ssh_params :: #ssh{},
socket :: inet:socket(),
- decoded_data_buffer :: binary(),
- encoded_data_buffer :: binary(),
- undecoded_packet_length :: non_neg_integer(),
+ decrypted_data_buffer :: binary(),
+ encrypted_data_buffer :: binary(),
+ undecrypted_packet_length :: non_neg_integer(),
key_exchange_init_msg :: #ssh_msg_kexinit{},
last_size_rekey = 0 :: non_neg_integer(),
event_queue = [] :: list(),
opts :: proplists:proplist(),
- recbuf :: pos_integer()
+ recbuf_size :: pos_integer()
}).
%%====================================================================
@@ -400,16 +405,16 @@ init_process_state(Role, Socket, Opts) ->
options = Opts},
starter = proplists:get_value(user_pid, Opts),
socket = Socket,
- decoded_data_buffer = <<>>,
- encoded_data_buffer = <<>>,
+ decrypted_data_buffer = <<>>,
+ encrypted_data_buffer = <<>>,
opts = Opts
},
case Role of
client ->
- TimerRef = get_idle_time(Opts),
- timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]),
+ %% Start the renegotiation timers
+ timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]),
timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]),
- S#data{idle_timer_ref = TimerRef};
+ S#data{idle_timer_ref = get_idle_time(Opts)};
server ->
S#data{connection_state = init_connection(Role, C, Opts)}
@@ -476,255 +481,298 @@ init_ssh_record(Role, Socket, Opts) ->
%% gen_statem callbacks
%%====================================================================
%%--------------------------------------------------------------------
-
+-type event_content() :: any().
+
+-type renegotiate_flag() :: init | renegotiate.
+
+-type state_name() ::
+ {init_error,any()}
+ | {hello, role()}
+ | {kexinit, role(), renegotiate_flag()}
+ | {key_exchange, role(), renegotiate_flag()}
+ | {key_exchange_dh_gex_init, server, renegotiate_flag()}
+ | {key_exchange_dh_gex_reply, client, renegotiate_flag()}
+ | {new_keys, role()}
+ | {service_request, role()}
+ | {userauth, role()}
+ | {userauth_keyboard_interactive, role()}
+ | {connected, role()}
+ .
+
+-type handle_event_result() :: gen_statem:handle_event_result().
+
+-spec handle_event(gen_statem:event_type(),
+ event_content(),
+ state_name(),
+ #data{}
+ ) -> handle_event_result().
+
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
%%% ######## Error in the initialiasation ####
-handle_event(_, _Event, {init_error,{badmatch,{error,enotconn}}}, _State) ->
- %% Handles the abnormal sequence:
- %% SYN->
- %% <-SYNACK
- %% ACK->
- %% RST->
- {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}};
-
-handle_event(_, _Event, {init_error,OtherError}, _State) ->
- {stop, {shutdown,{init,OtherError}}};
+handle_event(_, _Event, {init_error,Error}, _) ->
+ case Error of
+ {badmatch,{error,enotconn}} ->
+ %% Handles the abnormal sequence:
+ %% SYN->
+ %% <-SYNACK
+ %% ACK->
+ %% RST->
+ {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}};
+
+ OtherError ->
+ {stop, {shutdown,{init,OtherError}}}
+ end;
%%% ######## {hello, client|server} ####
-handle_event(_, socket_control, StateName={hello,_}, S=#data{socket=Socket,
- ssh_params=Ssh}) ->
- VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)),
- send_bytes(VsnMsg, S),
- case getopt(recbuf, Socket) of
+%% The very first event that is sent when the we are set as controlling process of Socket
+handle_event(_, socket_control, {hello,_}, D) ->
+ VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)),
+ ok = send_bytes(VsnMsg, D),
+ case getopt(recbuf, Socket=D#data.socket) of
{ok, Size} ->
- inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}, {nodelay,true}]),
- {next_state, StateName, S#data{recbuf=Size}};
+ %% Set the socket to the hello text line handling mode:
+ inet:setopts(Socket, [{packet, line},
+ {active, once},
+ % Expecting the version string which might
+ % be max ?MAX_PROTO_VERSION bytes:
+ {recbuf, ?MAX_PROTO_VERSION},
+ {nodelay,true}]),
+ {keep_state, D#data{recbuf_size=Size}};
{error, Reason} ->
{stop, {shutdown,Reason}}
end;
-handle_event(_, {info_line,_Line}, StateName={hello,client}, S=#data{socket=Socket}) ->
- %% The server may send info lines before the version_exchange
- inet:setopts(Socket, [{active, once}]),
- {next_state, StateName, S};
-
-handle_event(_, {info_line,_Line}, {hello,server}, S) ->
- %% as openssh
- send_bytes("Protocol mismatch.", S),
- {stop, {shutdown,"Protocol mismatch in version exchange."}};
+handle_event(_, {info_line,_Line}, {hello,Role}, D) ->
+ case Role of
+ client ->
+ %% The server may send info lines to the client before the version_exchange
+ inet:setopts(D#data.socket, [{active, once}]),
+ keep_state_and_data;
+ server ->
+ %% But the client may NOT send them to the server. Openssh answers with cleartext,
+ %% and so do we
+ ok = send_bytes("Protocol mismatch.", D),
+ {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}}
+ end;
-handle_event(_, {version_exchange,Version}, {hello,Role}, S=#data{ssh_params = Ssh0,
- socket = Socket,
- recbuf = Size}) ->
+handle_event(_, {version_exchange,Version}, {hello,Role}, D) ->
{NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version),
- case handle_version(NumVsn, StrVsn, Ssh0) of
+ case handle_version(NumVsn, StrVsn, D#data.ssh_params) of
{ok, Ssh1} ->
- inet:setopts(Socket, [{packet,0}, {mode,binary}, {active, once}, {recbuf, Size}]),
+ %% Since the hello part is finnished correctly, we set the
+ %% socket to the packet handling mode (including recbuf size):
+ inet:setopts(D#data.socket, [{packet,0},
+ {mode,binary},
+ {active, once},
+ {recbuf, D#data.recbuf_size}]),
{KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1),
- send_bytes(SshPacket, S),
- {next_state, {kexinit,Role,init}, S#data{ssh_params = Ssh,
- key_exchange_init_msg = KeyInitMsg}};
+ ok = send_bytes(SshPacket, D),
+ {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg}};
not_supported ->
disconnect(
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
description = ["Protocol version ",StrVsn," not supported"]},
- {next_state, {hello,Role}, S})
+ {next_state, {hello,Role}, D})
end;
%%% ######## {kexinit, client|server, init|renegotiate} ####
-handle_event(_, {#ssh_msg_kexinit{} = Kex, Payload}, {kexinit,client,ReNeg},
- S = #data{ssh_params = Ssh0,
- key_exchange_init_msg = OwnKex}) ->
- Ssh1 = ssh_transport:key_init(server, Ssh0, Payload), % Yes, *server*
- {ok, NextKexMsg, Ssh} = ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1),
- send_bytes(NextKexMsg, S),
- {next_state, {key_exchange,client,ReNeg}, S#data{ssh_params = Ssh}};
-
-handle_event(_, {#ssh_msg_kexinit{} = Kex, Payload}, {kexinit,server,ReNeg},
- S = #data{ssh_params = Ssh0,
- key_exchange_init_msg = OwnKex}) ->
- Ssh1 = ssh_transport:key_init(client, Ssh0, Payload), % Yes, *client*
- {ok, Ssh} = ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1),
- {next_state, {key_exchange,server,ReNeg}, S#data{ssh_params = Ssh}};
+handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
+ D = #data{key_exchange_init_msg = OwnKex}) ->
+ Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload),
+ Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of
+ {ok, NextKexMsg, Ssh2} when Role==client ->
+ ok = send_bytes(NextKexMsg, D),
+ Ssh2;
+ {ok, Ssh2} when Role==server ->
+ Ssh2
+ end,
+ {next_state, {key_exchange,Role,ReNeg}, D#data{ssh_params=Ssh}};
%%% ######## {key_exchange, client|server, init|renegotiate} ####
-handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg},
- S = #data{ssh_params = Ssh0}) ->
- {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, Ssh0),
- send_bytes(KexdhReply, S),
+%%%---- diffie-hellman
+handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
+ {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params),
+ ok = send_bytes(KexdhReply, D),
{ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- send_bytes(NewKeys, S),
- {next_state, {new_keys,server,ReNeg}, S#data{ssh_params = Ssh}};
-
-handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg},
- #data{ssh_params=Ssh0} = State) ->
- {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, Ssh0),
- send_bytes(NewKeys, State),
- {next_state, {new_keys,client,ReNeg}, State#data{ssh_params = Ssh}};
-
-handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg},
- #data{ssh_params=Ssh0} = State) ->
- {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0),
- send_bytes(GexGroup, State),
- {next_state, {key_exchange_dh_gex_init,server,ReNeg}, State#data{ssh_params = Ssh}};
-
-handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg},
- #data{ssh_params=Ssh0} = State) ->
- {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0),
- send_bytes(GexGroup, State),
- {next_state, {key_exchange_dh_gex_init,server,ReNeg}, State#data{ssh_params = Ssh}};
-
-handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg},
- #data{ssh_params=Ssh0} = State) ->
- {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, Ssh0),
- send_bytes(KexGexInit, State),
- {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, State#data{ssh_params = Ssh}};
-
-handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg},
- #data{ssh_params=Ssh0} = State) ->
- {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, Ssh0),
- send_bytes(KexEcdhReply, State),
+ ok = send_bytes(NewKeys, D),
+ {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
+
+handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
+ {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params),
+ ok = send_bytes(NewKeys, D),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
+
+%%%---- diffie-hellman group exchange
+handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) ->
+ {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
+ ok = send_bytes(GexGroup, D),
+ {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}};
+
+handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) ->
+ {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
+ ok = send_bytes(GexGroup, D),
+ {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}};
+
+handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) ->
+ {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params),
+ ok = send_bytes(KexGexInit, D),
+ {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}};
+
+%%%---- elliptic curve diffie-hellman
+handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
+ {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params),
+ ok = send_bytes(KexEcdhReply, D),
{ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- send_bytes(NewKeys, State),
- {next_state, {new_keys,server,ReNeg}, State#data{ssh_params = Ssh}};
+ ok = send_bytes(NewKeys, D),
+ {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
-handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg},
- #data{ssh_params=Ssh0} = State) ->
- {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, Ssh0),
- send_bytes(NewKeys, State),
- {next_state, {new_keys,client,ReNeg}, State#data{ssh_params = Ssh}};
+handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
+ {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params),
+ ok = send_bytes(NewKeys, D),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
%%% ######## {key_exchange_dh_gex_init, server, init|renegotiate} ####
-handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg},
- #data{ssh_params=Ssh0} = State) ->
- {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, Ssh0),
- send_bytes(KexGexReply, State),
+handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) ->
+ {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params),
+ ok = send_bytes(KexGexReply, D),
{ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- send_bytes(NewKeys, State),
- {next_state, {new_keys,server,ReNeg}, State#data{ssh_params = Ssh}};
+ ok = send_bytes(NewKeys, D),
+ {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
%%% ######## {key_exchange_dh_gex_reply, client, init|renegotiate} ####
-handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg},
- #data{ssh_params=Ssh0} = State) ->
- {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, Ssh0),
- send_bytes(NewKeys, State),
- {next_state, {new_keys,client,ReNeg}, State#data{ssh_params = Ssh1}};
+handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) ->
+ {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params),
+ ok = send_bytes(NewKeys, D),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}};
%%% ######## {new_keys, client|server} ####
-handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init},
- #data{ssh_params = Ssh0} = State) ->
- {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, Ssh0),
- {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1),
- send_bytes(MsgReq, State),
- {next_state, {service_request,client}, State#data{ssh_params=Ssh}};
-
-handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init},
- S = #data{ssh_params = Ssh0}) ->
- {ok, Ssh} = ssh_transport:handle_new_keys(Msg, Ssh0),
- {next_state, {service_request,server}, S#data{ssh_params = Ssh}};
-
-handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, S) ->
- {next_state, {connected,Role}, S};
-
+%% First key exchange round:
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) ->
+ {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+ Ssh = case Role of
+ client ->
+ {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1),
+ ok = send_bytes(MsgReq, D),
+ Ssh2;
+ server ->
+ Ssh1
+ end,
+ {next_state, {service_request,Role}, D#data{ssh_params=Ssh}};
+
+%% Subsequent key exchange rounds (renegotiation):
+handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, D) ->
+ {next_state, {connected,Role}, D};
%%% ######## {service_request, client|server}
-handle_event(_, #ssh_msg_service_request{name = "ssh-userauth"} = Msg, {service_request,server},
- #data{ssh_params = #ssh{session_id=SessionId} = Ssh0} = State) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
- send_bytes(Reply, State),
- {next_state, {userauth,server}, State#data{ssh_params = Ssh}};
+handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D) ->
+ case ServiceName of
+ "ssh-userauth" ->
+ Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params,
+ {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
+ ok = send_bytes(Reply, D),
+ {next_state, {userauth,server}, D#data{ssh_params = Ssh}};
-handle_event(_, #ssh_msg_service_request{}, {service_request,server}=StateName, State) ->
- Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
- description = "Unknown service"},
- disconnect(Msg, StateName, State);
+ _ ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "Unknown service"},
+ StateName, D)
+ end;
handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client},
#data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) ->
{Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0),
- send_bytes(Msg, State),
+ ok = send_bytes(Msg, State),
{next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}};
%%% ######## {userauth, client|server} ####
-handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection",
- method = "none"} = Msg, StateName={userauth,server},
- #data{ssh_params = #ssh{session_id = SessionId,
- service = "ssh-connection"} = Ssh0
- } = State) ->
- {not_authorized, {_User, _Reason}, {Reply, Ssh}} =
- ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
- send_bytes(Reply, State),
- {next_state, StateName, State#data{ssh_params = Ssh}};
-
-handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection",
- method = Method} = Msg, StateName={userauth,server},
- #data{ssh_params = #ssh{session_id = SessionId,
- service = "ssh-connection",
- peer = {_, Address}} = Ssh0,
- opts = Opts, starter = Pid} = State) ->
- case lists:member(Method, Ssh0#ssh.userauth_methods) of
- true ->
- case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of
- {authorized, User, {Reply, Ssh}} ->
- send_bytes(Reply, State),
- Pid ! ssh_connected,
- connected_fun(User, Address, Method, Opts),
- {next_state, {connected,server},
- State#data{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}}};
- {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" ->
- retry_fun(User, Address, Reason, Opts),
- send_bytes(Reply, State),
- {next_state, {userauth_keyboard_interactive,server}, State#data{ssh_params = Ssh}};
- {not_authorized, {User, Reason}, {Reply, Ssh}} ->
- retry_fun(User, Address, Reason, Opts),
- send_bytes(Reply, State),
- {next_state, StateName, State#data{ssh_params = Ssh}}
+%%---- userauth request to server
+handle_event(_,
+ Msg = #ssh_msg_userauth_request{service = ServiceName, method = Method},
+ StateName = {userauth,server},
+ D = #data{ssh_params=Ssh0}) ->
+
+ case {ServiceName, Ssh0#ssh.service, Method} of
+ {"ssh-connection", "ssh-connection", "none"} ->
+ %% Probably the very first userauth_request but we deny unauthorized login
+ {not_authorized, _, {Reply,Ssh}} =
+ ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0),
+ ok = send_bytes(Reply, D),
+ {keep_state, D#data{ssh_params = Ssh}};
+
+ {"ssh-connection", "ssh-connection", Method} ->
+ %% Userauth request with a method like "password" or so
+ case lists:member(Method, Ssh0#ssh.userauth_methods) of
+ true ->
+ %% Yepp! we support this method
+ case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of
+ {authorized, User, {Reply, Ssh}} ->
+ ok = send_bytes(Reply, D),
+ D#data.starter ! ssh_connected,
+ connected_fun(User, Method, D),
+ {next_state, {connected,server},
+ D#data{auth_user = User,
+ ssh_params = Ssh#ssh{authenticated = true}}};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" ->
+ retry_fun(User, Reason, D),
+ ok = send_bytes(Reply, D),
+ {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} ->
+ retry_fun(User, Reason, D),
+ ok = send_bytes(Reply, D),
+ {keep_state, D#data{ssh_params = Ssh}}
+ end;
+ false ->
+ %% No we do not support this method (=/= none)
+ %% At least one non-erlang client does like this. Retry as the next event
+ {keep_state_and_data,
+ [{next_event, internal, Msg#ssh_msg_userauth_request{method="none"}}]
+ }
end;
- false ->
- %% At least one non-erlang client does like this. Retry as the next event
- {next_state, StateName, State,
- [{next_event, internal, Msg#ssh_msg_userauth_request{method="none"}}]
- }
- end;
-handle_event(_, #ssh_msg_userauth_request{service = Service}, {userauth,server}=StateName, State)
- when Service =/= "ssh-connection" ->
- Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
- description = "Unknown service"},
- disconnect(Msg, StateName, State);
+ %% {"ssh-connection", Expected, Method} when Expected =/= ServiceName -> Do what?
+ %% {ServiceName, Expected, Method} when Expected =/= ServiceName -> Do what?
+
+ {ServiceName, _, _} when ServiceName =/= "ssh-connection" ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "Unknown service"},
+ StateName, D)
+ end;
+
+%%---- userauth success to client
+handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) ->
+ D#data.starter ! ssh_connected,
+ {next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}};
-handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, #data{ssh_params = Ssh,
- starter = Pid} = State) ->
- Pid ! ssh_connected,
- {next_state, {connected,client}, State#data{ssh_params=Ssh#ssh{authenticated = true}}};
+%%---- userauth failure response to client
handle_event(_, #ssh_msg_userauth_failure{}, {userauth,client}=StateName,
- #data{ssh_params = #ssh{userauth_methods = []}} = State) ->
+ D = #data{ssh_params = #ssh{userauth_methods = []}}) ->
Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
description = "Unable to connect using the available"
" authentication methods"},
- disconnect(Msg, StateName, State);
+ disconnect(Msg, StateName, D);
handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client},
- #data{ssh_params = Ssh0 = #ssh{userauth_methods=AuthMthds}} = State) ->
+ D = #data{ssh_params = Ssh0}) ->
%% The prefered authentication method failed try next method
- Ssh1 = case AuthMthds of
+ Ssh1 = case Ssh0#ssh.userauth_methods of
none ->
%% Server tells us which authentication methods that are allowed
Ssh0#ssh{userauth_methods = string:tokens(Methods, ",")};
@@ -734,522 +782,501 @@ handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName=
end,
case ssh_auth:userauth_request_msg(Ssh1) of
{disconnect, DisconnectMsg, {Msg, Ssh}} ->
- send_bytes(Msg, State),
- disconnect(DisconnectMsg, StateName, State#data{ssh_params = Ssh});
+ send_bytes(Msg, D),
+ disconnect(DisconnectMsg, StateName, D#data{ssh_params = Ssh});
{"keyboard-interactive", {Msg, Ssh}} ->
- send_bytes(Msg, State),
- {next_state, {userauth_keyboard_interactive,client}, State#data{ssh_params = Ssh}};
+ send_bytes(Msg, D),
+ {next_state, {userauth_keyboard_interactive,client}, D#data{ssh_params = Ssh}};
{_Method, {Msg, Ssh}} ->
- send_bytes(Msg, State),
- {next_state, StateName, State#data{ssh_params = Ssh}}
+ send_bytes(Msg, D),
+ {keep_state, D#data{ssh_params = Ssh}}
end;
-handle_event(_, #ssh_msg_userauth_banner{}, StateName={userauth,client},
- #data{ssh_params = #ssh{userauth_quiet_mode=true}} = State) ->
- {next_state, StateName, State};
-
-handle_event(_, #ssh_msg_userauth_banner{message = Msg}, StateName={userauth,client},
- #data{ssh_params = #ssh{userauth_quiet_mode=false}} = State) ->
- io:format("~s", [Msg]),
- {next_state, StateName, State};
+%%---- banner to client
+handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) ->
+ case D#data.ssh_params#ssh.userauth_quiet_mode of
+ false -> io:format("~s", [Msg]);
+ true -> ok
+ end,
+ keep_state_and_data;
%%% ######## {userauth_keyboard_interactive, client|server}
handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client},
- #data{ssh_params = #ssh{io_cb=IoCb} = Ssh0} = State) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0),
- send_bytes(Reply, State),
- {next_state, {userauth_keyboard_interactive_info_response,client}, State#data{ssh_params = Ssh}};
-
-handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server},
- #data{ssh_params = #ssh{peer = {_,Address}} = Ssh0,
- opts = Opts,
- starter = Pid} = State) ->
- case ssh_auth:handle_userauth_info_response(Msg, Ssh0) of
+ #data{ssh_params = Ssh0} = D) ->
+ {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, Ssh0#ssh.io_cb, Ssh0),
+ send_bytes(Reply, D),
+ {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}};
+
+handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) ->
+ case ssh_auth:handle_userauth_info_response(Msg, D#data.ssh_params) of
{authorized, User, {Reply, Ssh}} ->
- send_bytes(Reply, State),
- Pid ! ssh_connected,
- connected_fun(User, Address, "keyboard-interactive", Opts),
- {next_state, {connected,server}, State#data{auth_user = User,
- ssh_params = Ssh#ssh{authenticated = true}}};
+ send_bytes(Reply, D),
+ D#data.starter ! ssh_connected,
+ connected_fun(User, "keyboard-interactive", D),
+ {next_state, {connected,server}, D#data{auth_user = User,
+ ssh_params = Ssh#ssh{authenticated = true}}};
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
- retry_fun(User, Address, Reason, Opts),
- send_bytes(Reply, State),
- {next_state, {userauth,server}, State#data{ssh_params = Ssh}}
+ retry_fun(User, Reason, D),
+ send_bytes(Reply, D),
+ {next_state, {userauth,server}, D#data{ssh_params = Ssh}}
end;
handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client},
- #data{ssh_params = Ssh0 = #ssh{userauth_preference=Prefs0}} = State) ->
- Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Prefs0,
+ #data{ssh_params = Ssh0} = D0) ->
+ Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Ssh0#ssh.userauth_preference,
Method =/= "keyboard-interactive"],
- {next_state, {userauth,client},
- State#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}},
- [{next_event, internal, Msg}]};
+ D = D0#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}},
+ {next_state, {userauth,client}, D, [{next_event, internal, Msg}]};
-handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, S) ->
- {next_state, {userauth,client}, S, [{next_event, internal, Msg}]};
+handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth,client}, D, [{next_event, internal, Msg}]};
-handle_event(_, Msg=#ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, S) ->
- {next_state, {userauth,client}, S, [{next_event, internal, Msg}]};
+handle_event(_, Msg=#ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth,client}, D, [{next_event, internal, Msg}]};
-handle_event(_, Msg=#ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, S) ->
- {next_state, {userauth_keyboard_interactive,client}, S, [{next_event, internal, Msg}]};
+handle_event(_, Msg=#ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth_keyboard_interactive,client}, D, [{next_event, internal, Msg}]};
%%% ######## {connected, client|server} ####
-handle_event(_, {#ssh_msg_kexinit{},_} = Event, {connected,Role}, #data{ssh_params = Ssh0} = State0) ->
- {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0),
- State = State0#data{ssh_params = Ssh,
- key_exchange_init_msg = KeyInitMsg},
- send_bytes(SshPacket, State),
- {next_state, {kexinit,Role,renegotiate}, State, [{next_event, internal, Event}]};
+handle_event(_, {#ssh_msg_kexinit{},_} = Event, {connected,Role}, D0) ->
+ {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params),
+ D = D0#data{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg},
+ send_bytes(SshPacket, D),
+ {next_state, {kexinit,Role,renegotiate}, D, [{next_event, internal, Event}]};
+
+handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) ->
+ {disconnect, _, {{replies,Replies}, _}} =
+ ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName)),
+ {Actions,D} = send_replies(Replies, D0),
+ disconnect_fun(Desc, D),
+ {stop_and_reply, {shutdown,Desc}, Actions, D};
-handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName,
- State0 = #data{connection_state = Connection0}) ->
- {disconnect, _, {{replies, Replies}, _Connection}} =
- ssh_connection:handle_msg(Msg, Connection0, role(StateName)),
- {Repls,State} = send_replies(Replies, State0),
- disconnect_fun(Desc, State#data.opts),
- {stop_and_reply, {shutdown,Desc}, Repls, State};
+handle_event(_, #ssh_msg_ignore{}, _, _) ->
+ keep_state_and_data;
-handle_event(_, #ssh_msg_ignore{}, StateName, State) ->
- {next_state, StateName, State};
+handle_event(_, #ssh_msg_unimplemented{}, _, _) ->
+ keep_state_and_data;
-handle_event(_, #ssh_msg_debug{always_display = Display,
- message = DbgMsg,
- language = Lang}, StateName, #data{opts = Opts} = State) ->
- F = proplists:get_value(ssh_msg_debug_fun, Opts,
- fun(_ConnRef, _AlwaysDisplay, _Msg, _Language) -> ok end
- ),
- catch F(self(), Display, DbgMsg, Lang),
- {next_state, StateName, State};
+handle_event(_, #ssh_msg_debug{} = Msg, _, D) ->
+ debug_fun(Msg, D),
+ keep_state_and_data;
-handle_event(_, #ssh_msg_unimplemented{}, StateName, State) ->
- {next_state, StateName, State};
+handle_event(internal, Msg=#ssh_msg_global_request{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_global_request{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_request_success{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_request_success{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_request_failure{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_request_failure{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_open{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_open{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_open_confirmation{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_open_confirmation{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_open_failure{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_open_failure{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_window_adjust{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_window_adjust{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_data{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_data{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_extended_data{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_extended_data{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_eof{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_eof{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_close{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_close{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_request{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_request{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_success{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_success{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
+handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, D) ->
+ handle_connection_msg(Msg, StateName, D);
-handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, State) ->
- handle_connection_msg(Msg, StateName, State);
-handle_event(cast, renegotiate, {connected,Role}, #data{ssh_params=Ssh0} = State) ->
- {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0),
- send_bytes(SshPacket, State),
-%%% FIXME: timer
+handle_event(cast, renegotiate, {connected,Role}, D) ->
+ {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D#data.ssh_params),
+ send_bytes(SshPacket, D),
timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]),
- {next_state, {kexinit,Role,renegotiate}, State#data{ssh_params = Ssh,
- key_exchange_init_msg = KeyInitMsg}};
+ {next_state, {kexinit,Role,renegotiate}, D#data{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg}};
-handle_event(cast, renegotiate, StateName, State) ->
+handle_event(cast, renegotiate, _, _) ->
%% Already in key-exchange so safe to ignore
- {next_state, StateName, State};
+ timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), % FIXME: not here in original
+ keep_state_and_data;
+
%% Rekey due to sent data limit reached?
-handle_event(cast, data_size, {connected,Role}, #data{ssh_params=Ssh0} = State) ->
- {ok, [{send_oct,Sent0}]} = inet:getstat(State#data.socket, [send_oct]),
- Sent = Sent0 - State#data.last_size_rekey,
- MaxSent = proplists:get_value(rekey_limit, State#data.opts, 1024000000),
-%%% FIXME: timer
+handle_event(cast, data_size, {connected,Role}, D) ->
+ {ok, [{send_oct,Sent0}]} = inet:getstat(D#data.socket, [send_oct]),
+ Sent = Sent0 - D#data.last_size_rekey,
+ MaxSent = proplists:get_value(rekey_limit, D#data.opts, 1024000000),
timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]),
case Sent >= MaxSent of
true ->
- {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0),
- send_bytes(SshPacket, State),
- {next_state, {kexinit,Role,renegotiate}, State#data{ssh_params = Ssh,
- key_exchange_init_msg = KeyInitMsg,
- last_size_rekey = Sent0}};
+ {KeyInitMsg, SshPacket, Ssh} =
+ ssh_transport:key_exchange_init_msg(D#data.ssh_params),
+ send_bytes(SshPacket, D),
+ {next_state, {kexinit,Role,renegotiate}, D#data{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg,
+ last_size_rekey = Sent0}};
_ ->
- {next_state, {connected,Role}, State}
+ keep_state_and_data
end;
-handle_event(cast, data_size, StateName, State) ->
+handle_event(cast, data_size, _, _) ->
%% Already in key-exchange so safe to ignore
- {next_state, StateName, State};
+ timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), % FIXME: not here in original
+ keep_state_and_data;
+
+
-handle_event(cast, _, StateName, State) when StateName /= {connected,server},
- StateName /= {connected,client} ->
- {next_state, StateName, State, [postpone]};
+handle_event(cast, _, StateName, _) when StateName /= {connected,server},
+ StateName /= {connected,client} ->
+ {keep_state_and_data, [postpone]};
-handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName={connected,_Role},
- #data{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
+
+handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
recv_window_pending = Pending,
recv_packet_size = PktSize} = Channel
when (WinSize-Bytes) >= 2*PktSize ->
%% The peer can send at least two more *full* packet, no hurry.
- ssh_channel:cache_update(Cache,
+ ssh_channel:cache_update(cache(D),
Channel#channel{recv_window_pending = Pending + Bytes}),
- {next_state, StateName, State0};
-
+ keep_state_and_data;
+
#channel{recv_window_size = WinSize,
recv_window_pending = Pending,
remote_id = Id} = Channel ->
%% Now we have to update the window - we can't receive so many more pkts
- ssh_channel:cache_update(Cache,
+ ssh_channel:cache_update(cache(D),
Channel#channel{recv_window_size =
WinSize + Bytes + Pending,
recv_window_pending = 0}),
Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes + Pending),
- {next_state, StateName, send_msg(Msg,State0)};
-
+ {keep_state, send_msg(Msg,D)};
+
undefined ->
- {next_state, StateName, State0}
+ keep_state_and_data
end;
-handle_event(cast, {reply_request,success,ChannelId}, StateName={connected,_},
- #data{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
+handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{remote_id = RemoteId} ->
Msg = ssh_connection:channel_success_msg(RemoteId),
- {next_state, StateName, send_msg(Msg,State0)};
-
+ {keep_state, send_msg(Msg,D)};
+
undefined ->
- {next_state, StateName, State0}
+ keep_state_and_data
end;
-handle_event(cast, {request,ChannelPid,ChannelId,Type,Data}, StateName={connected,_}, State0) ->
- State = handle_request(ChannelPid, ChannelId, Type, Data, false, none, State0),
- {next_state, StateName, State};
+handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, {connected,_}, D) ->
+ {keep_state, handle_request(ChannelPid, ChannelId, Type, Data, false, none, D)};
-handle_event(cast, {request,ChannelId,Type,Data}, StateName={connected,_}, State0) ->
- State = handle_request(ChannelId, Type, Data, false, none, State0),
- {next_state, StateName, State};
+handle_event(cast, {request,ChannelId,Type,Data}, {connected,_}, D) ->
+ {keep_state, handle_request(ChannelId, Type, Data, false, none, D)};
-handle_event(cast, {unknown,Data}, StateName={connected,_}, State) ->
+handle_event(cast, {unknown,Data}, {connected,_}, D) ->
Msg = #ssh_msg_unimplemented{sequence = Data},
- {next_state, StateName, send_msg(Msg,State)};
+ {keep_state, send_msg(Msg,D)};
%%% Previously handle_sync_event began here
-handle_event({call,From}, get_print_info, StateName, State) ->
+handle_event({call,From}, get_print_info, StateName, D) ->
Reply =
try
- {inet:sockname(State#data.socket),
- inet:peername(State#data.socket)
+ {inet:sockname(D#data.socket),
+ inet:peername(D#data.socket)
}
of
- {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])};
- _ -> {{"-",0},"-"}
+ {{ok,Local}, {ok,Remote}} ->
+ {{Local,Remote},io_lib:format("statename=~p",[StateName])};
+ _ ->
+ {{"-",0},"-"}
catch
- _:_ -> {{"?",0},"?"}
+ _:_ ->
+ {{"?",0},"?"}
end,
- {next_state, StateName, State, [{reply,From,Reply}]};
+ {keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {connection_info, Options}, StateName, State) ->
- Info = ssh_info(Options, State, []),
- {next_state, StateName, State, [{reply,From,Info}]};
+handle_event({call,From}, {connection_info, Options}, _, D) ->
+ Info = ssh_info(Options, D, []),
+ {keep_state_and_data, [{reply,From,Info}]};
-handle_event({call,From}, {channel_info,ChannelId,Options}, StateName,
- State=#data{connection_state = #connection{channel_cache = Cache}}) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{} = Channel ->
+handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
+ #channel{} = Channel ->
Info = ssh_channel_info(Options, Channel, []),
- {next_state, StateName, State, [{reply,From,Info}]};
+ {keep_state_and_data, [{reply,From,Info}]};
undefined ->
- {next_state, StateName, State, [{reply,From,[]}]}
+ {keep_state_and_data, [{reply,From,[]}]}
end;
-handle_event({call,From}, {info, ChannelPid}, StateName, State = #data{connection_state =
- #connection{channel_cache = Cache}}) ->
+handle_event({call,From}, {info, ChannelPid}, _, D) ->
Result = ssh_channel:cache_foldl(
fun(Channel, Acc) when ChannelPid == all;
Channel#channel.user == ChannelPid ->
[Channel | Acc];
(_, Acc) ->
Acc
- end, [], Cache),
- {next_state, StateName, State, [{reply, From, {ok,Result}}]};
+ end, [], cache(D)),
+ {keep_state_and_data, [{reply, From, {ok,Result}}]};
-handle_event({call,From}, stop, StateName, #data{connection_state = Connection0} = State0) ->
+handle_event({call,From}, stop, StateName, D0) ->
{disconnect, _Reason, {{replies, Replies}, Connection}} =
ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
description = "User closed down connection"},
- Connection0, role(StateName)),
- {Repls,State} = send_replies(Replies, State0),
- {stop_and_reply, normal, [{reply,From,ok}|Repls], State#data{connection_state=Connection}};
+ D0#data.connection_state,
+ role(StateName)),
+ {Repls,D} = send_replies(Replies, D0),
+ {stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}};
-handle_event({call,_}, _, StateName, State) when StateName /= {connected,server},
- StateName /= {connected,client} ->
- {next_state, StateName, State, [postpone]};
+handle_event({call,_}, _, StateName, _) when StateName /= {connected,server},
+ StateName /= {connected,client} ->
+ {keep_state_and_data, [postpone]};
-handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName={connected,_}, State0) ->
- State = handle_request(ChannelPid, ChannelId, Type, Data, true, From, State0),
+handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+ D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0),
%% Note reply to channel will happen later when reply is recived from peer on the socket
start_timeout(ChannelId, From, Timeout),
- handle_idle_timeout(State),
- {next_state, StateName, State};
+ handle_idle_timeout(D),
+ {keep_state, D};
-handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName={connected,_}, State0) ->
- State = handle_request(ChannelId, Type, Data, true, From, State0),
+handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+ D = handle_request(ChannelId, Type, Data, true, From, D0),
%% Note reply to channel will happen later when reply is recived from peer on the socket
start_timeout(ChannelId, From, Timeout),
- handle_idle_timeout(State),
- {next_state, StateName, State};
-
-handle_event({call,From}, {global_request, Pid, _, _, _} = Request, StateName={connected,_},
- #data{connection_state = #connection{channel_cache = Cache}} = State0) ->
- State1 = handle_global_request(Request, State0),
- Channel = ssh_channel:cache_find(Pid, Cache),
- State = add_request(true, Channel#channel.local_id, From, State1),
- {next_state, StateName, State};
-
-handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName={connected,_},
- #data{connection_state = #connection{channel_cache=_Cache} = Connection0} = State0) ->
- case ssh_connection:channel_data(ChannelId, Type, Data, Connection0, From) of
+ handle_idle_timeout(D),
+ {keep_state, D};
+
+handle_event({call,From}, {global_request, Pid, _, _, _} = Request, {connected,_}, D0) ->
+ D1 = handle_global_request(Request, D0),
+ Channel = ssh_channel:cache_find(Pid, cache(D1)),
+ D = add_request(true, Channel#channel.local_id, From, D1),
+ {keep_state, D};
+
+handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+ case ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From) of
{{replies, Replies}, Connection} ->
- {Repls,State} = send_replies(Replies, State0#data{connection_state = Connection}),
+ {Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}),
start_timeout(ChannelId, From, Timeout),
- {next_state, StateName, State, Repls};
+ {keep_state, D, Repls};
{noreply, Connection} ->
start_timeout(ChannelId, From, Timeout),
- {next_state, StateName, State0#data{connection_state = Connection}}
+ {keep_state, D0#data{connection_state = Connection}}
end;
-handle_event({call,From}, {eof, ChannelId}, StateName={connected,_},
- #data{connection_state = #connection{channel_cache=Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
+handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) ->
+ case ssh_channel:cache_lookup(cache(D0), ChannelId) of
#channel{remote_id = Id, sent_close = false} ->
- State = send_msg(ssh_connection:channel_eof_msg(Id), State0),
- {next_state, StateName, State, [{reply,From,ok}]};
+ D = send_msg(ssh_connection:channel_eof_msg(Id), D0),
+ {keep_state, D, [{reply,From,ok}]};
_ ->
- {next_state, StateName, State0, [{reply,From,{error,closed}}]}
+ {keep_state, D0, [{reply,From,{error,closed}}]}
end;
handle_event({call,From},
{open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout},
- StateName = {connected,_},
- #data{connection_state = #connection{channel_cache = Cache}} = State0) ->
+ {connected,_},
+ D0) ->
erlang:monitor(process, ChannelPid),
- {ChannelId, State1} = new_channel_id(State0),
- Msg = ssh_connection:channel_open_msg(Type, ChannelId,
- InitialWindowSize,
- MaxPacketSize, Data),
- State2 = send_msg(Msg, State1),
- Channel = #channel{type = Type,
- sys = "none",
- user = ChannelPid,
- local_id = ChannelId,
- recv_window_size = InitialWindowSize,
- recv_packet_size = MaxPacketSize,
- send_buf = queue:new()
- },
- ssh_channel:cache_update(Cache, Channel),
- State = add_request(true, ChannelId, From, State2),
+ {ChannelId, D1} = new_channel_id(D0),
+ D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId,
+ InitialWindowSize,
+ MaxPacketSize, Data),
+ D1),
+ ssh_channel:cache_update(cache(D2),
+ #channel{type = Type,
+ sys = "none",
+ user = ChannelPid,
+ local_id = ChannelId,
+ recv_window_size = InitialWindowSize,
+ recv_packet_size = MaxPacketSize,
+ send_buf = queue:new()
+ }),
+ D = add_request(true, ChannelId, From, D2),
start_timeout(ChannelId, From, Timeout),
- {next_state, StateName, remove_timer_ref(State)};
+ {keep_state, remove_timer_ref(D)};
-handle_event({call,From}, {send_window, ChannelId}, StateName={connected,_},
- #data{connection_state = #connection{channel_cache = Cache}} = State) ->
- Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of
+handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) ->
+ Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{send_window_size = WinSize,
send_packet_size = Packsize} ->
{ok, {WinSize, Packsize}};
undefined ->
{error, einval}
end,
- {next_state, StateName, State, [{reply,From,Reply}]};
+ {keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {recv_window, ChannelId}, StateName={connected,_},
- #data{connection_state = #connection{channel_cache = Cache}} = State) ->
- Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of
+handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) ->
+ Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
recv_packet_size = Packsize} ->
{ok, {WinSize, Packsize}};
undefined ->
{error, einval}
end,
- {next_state, StateName, State, [{reply,From,Reply}]};
+ {keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {close, ChannelId}, StateName={connected,_},
- #data{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
+handle_event({call,From}, {close, ChannelId}, {connected,_}, D0) ->
+ case ssh_channel:cache_lookup(cache(D0), ChannelId) of
#channel{remote_id = Id} = Channel ->
- State1 = send_msg(ssh_connection:channel_close_msg(Id), State0),
- ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}),
- handle_idle_timeout(State1),
- {next_state, StateName, State1, [{reply,From,ok}]};
+ D1 = send_msg(ssh_connection:channel_close_msg(Id), D0),
+ ssh_channel:cache_update(cache(D1), Channel#channel{sent_close = true}),
+ handle_idle_timeout(D1),
+ {keep_state, D1, [{reply,From,ok}]};
undefined ->
- {next_state, StateName, State0, [{reply,From,ok}]}
+ {keep_state_and_data, [{reply,From,ok}]}
end;
-handle_event(info, {Protocol, Socket, "SSH-" ++ _ = Version}, StateName={hello,_},
- State=#data{socket = Socket,
- transport_protocol = Protocol}) ->
- {next_state, StateName, State, [{next_event, internal, {version_exchange,Version}}]};
-
-handle_event(info, {Protocol, Socket, Info}, StateName={hello,_},
- State=#data{socket = Socket,
- transport_protocol = Protocol}) ->
- {next_state, StateName, State, [{next_event, internal, {info_line,Info}}]};
-
-handle_event(info, {Protocol, Socket, Data}, StateName, State0 =
- #data{socket = Socket,
- transport_protocol = Protocol,
- decoded_data_buffer = DecData0,
- encoded_data_buffer = EncData0,
- undecoded_packet_length = RemainingSshPacketLen0,
- ssh_params = Ssh0}) ->
- Encoded = <<EncData0/binary, Data/binary>>,
- try ssh_transport:handle_packet_part(DecData0, Encoded, RemainingSshPacketLen0, Ssh0)
+
+%%===== Reception of encrypted bytes, decryption and framing
+handle_event(info, {Protocol, Socket, "SSH-" ++ _ = Version}, {hello,_},
+ #data{socket = Socket,
+ transport_protocol = Protocol}) ->
+ {keep_state_and_data, [{next_event, internal, {version_exchange,Version}}]};
+
+handle_event(info, {Protocol, Socket, Info}, {hello,_},
+ #data{socket = Socket,
+ transport_protocol = Protocol}) ->
+ {keep_state_and_data, [{next_event, internal, {info_line,Info}}]};
+
+handle_event(info, {Protocol, Socket, NewData}, StateName,
+ D0 = #data{socket = Socket,
+ transport_protocol = Protocol}) ->
+ try ssh_transport:handle_packet_part(
+ D0#data.decrypted_data_buffer,
+ <<(D0#data.encrypted_data_buffer)/binary, NewData/binary>>,
+ D0#data.undecrypted_packet_length,
+ D0#data.ssh_params)
of
- {decoded, Bytes, EncDataRest, Ssh1} ->
- State = State0#data{ssh_params =
- Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)},
- decoded_data_buffer = <<>>,
- undecoded_packet_length = undefined,
- encoded_data_buffer = EncDataRest},
+ {packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} ->
+ D = D0#data{ssh_params =
+ Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)},
+ decrypted_data_buffer = <<>>,
+ undecrypted_packet_length = undefined,
+ encrypted_data_buffer = EncryptedDataRest},
try
- ssh_message:decode(set_prefix_if_trouble(Bytes,State))
+ ssh_message:decode(set_prefix_if_trouble(DecryptedBytes,D))
of
Msg = #ssh_msg_kexinit{} ->
- {next_state, StateName, State, [{next_event, internal, {Msg,Bytes}},
- {next_event, internal, prepare_next_packet}
- ]};
+ {keep_state, D, [{next_event, internal, {Msg,DecryptedBytes}},
+ {next_event, internal, prepare_next_packet}
+ ]};
Msg ->
- {next_state, StateName, State, [{next_event, internal, Msg},
- {next_event, internal, prepare_next_packet}
- ]}
+ {keep_state, D, [{next_event, internal, Msg},
+ {next_event, internal, prepare_next_packet}
+ ]}
catch
_C:_E ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Encountered unexpected input"},
- disconnect(DisconnectMsg, StateName, State)
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Encountered unexpected input"},
+ StateName, D)
end;
- {get_more, DecBytes, EncDataRest, RemainingSshPacketLen, Ssh1} ->
- %% Here we know that there are not enough bytes in EncDataRest to use. Must wait.
+ {get_more, DecryptedBytes, EncryptedDataRest, RemainingSshPacketLen, Ssh1} ->
+ %% Here we know that there are not enough bytes in
+ %% EncryptedDataRest to use. We must wait for more.
inet:setopts(Socket, [{active, once}]),
- {next_state, StateName, State0#data{encoded_data_buffer = EncDataRest,
- decoded_data_buffer = DecBytes,
- undecoded_packet_length = RemainingSshPacketLen,
- ssh_params = Ssh1}};
+ {keep_state, D0#data{encrypted_data_buffer = EncryptedDataRest,
+ decrypted_data_buffer = DecryptedBytes,
+ undecrypted_packet_length = RemainingSshPacketLen,
+ ssh_params = Ssh1}};
{bad_mac, Ssh1} ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad mac"},
- disconnect(DisconnectMsg, StateName, State0#data{ssh_params=Ssh1});
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad mac"},
+ StateName, D0#data{ssh_params=Ssh1});
{error, {exceeds_max_size,PacketLen}} ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad packet length "
- ++ integer_to_list(PacketLen)},
- disconnect(DisconnectMsg, StateName, State0)
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad packet length "
+ ++ integer_to_list(PacketLen)},
+ StateName, D0)
catch
_C:_E ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad packet"},
- disconnect(DisconnectMsg, StateName, State0)
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad packet"},
+ StateName, D0)
end;
-handle_event(internal, prepare_next_packet, StateName, State) ->
- Enough = erlang:max(8, State#data.ssh_params#ssh.decrypt_block_size),
- case size(State#data.encoded_data_buffer) of
+
+%%%====
+handle_event(internal, prepare_next_packet, _, D) ->
+ Enough = erlang:max(8, D#data.ssh_params#ssh.decrypt_block_size),
+ case size(D#data.encrypted_data_buffer) of
Sz when Sz >= Enough ->
- self() ! {State#data.transport_protocol, State#data.socket, <<>>};
+ self() ! {D#data.transport_protocol, D#data.socket, <<>>};
_ ->
- inet:setopts(State#data.socket, [{active, once}])
+ inet:setopts(D#data.socket, [{active, once}])
end,
- {next_state, StateName, State};
+ keep_state_and_data;
handle_event(info, {CloseTag,Socket}, StateName,
- State=#data{socket = Socket,
- transport_close_tag = CloseTag}) ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "Connection closed"},
- disconnect(DisconnectMsg, StateName, State);
-
-handle_event(info, {timeout, {_, From} = Request}, StateName,
- #data{connection_state = #connection{requests = Requests} = Connection} = State) ->
+ D = #data{socket = Socket,
+ transport_close_tag = CloseTag}) ->
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "Connection closed"},
+ StateName, D);
+
+handle_event(info, {timeout, {_, From} = Request}, _,
+ #data{connection_state = #connection{requests = Requests} = C0} = D) ->
case lists:member(Request, Requests) of
true ->
- {next_state, StateName,
- State#data{connection_state =
- Connection#connection{requests =
- lists:delete(Request, Requests)}},
- [{reply,From,{error,timeout}}]};
+ C = C0#connection{requests = lists:delete(Request, Requests)},
+ {keep_state, D#data{connection_state=C}, [{reply,From,{error,timeout}}]};
false ->
- {next_state, StateName, State}
+ keep_state_and_data
end;
%%% Handle that ssh channels user process goes down
-handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, StateName, State0) ->
- {{replies, Replies}, State1} = handle_channel_down(ChannelPid, State0),
- {Repls, State} = send_replies(Replies, State1),
- {next_state, StateName, State, Repls};
+handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D0) ->
+ {{replies, Replies}, D1} = handle_channel_down(ChannelPid, D0),
+ {Repls, D} = send_replies(Replies, D1),
+ {keep_state, D, Repls};
%%% So that terminate will be run when supervisor is shutdown
handle_event(info, {'EXIT', _Sup, Reason}, _, _) ->
{stop, {shutdown, Reason}};
-handle_event(info, {check_cache, _ , _}, StateName,
- #data{connection_state = #connection{channel_cache=Cache}} = State) ->
- {next_state, StateName, check_cache(State, Cache)};
+handle_event(info, {check_cache, _ , _}, _, D) ->
+ {keep_state, check_cache(D)};
-handle_event(info, UnexpectedMessage, StateName,
- State = #data{opts = Opts,
- ssh_params = SshParams}) ->
- case unexpected_fun(UnexpectedMessage, Opts, SshParams) of
+handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) ->
+ case unexpected_fun(UnexpectedMessage, D) of
report ->
Msg = lists:flatten(
io_lib:format(
"Unexpected message '~p' received in state '~p'\n"
"Role: ~p\n"
"Peer: ~p\n"
- "Local Address: ~p\n", [UnexpectedMessage, StateName,
- SshParams#ssh.role, SshParams#ssh.peer,
- proplists:get_value(address, SshParams#ssh.opts)])),
+ "Local Address: ~p\n", [UnexpectedMessage,
+ StateName,
+ Ssh#ssh.role,
+ Ssh#ssh.peer,
+ proplists:get_value(address, Ssh#ssh.opts)])),
error_logger:info_report(Msg),
- {next_state, StateName, State};
+ keep_state_and_data;
skip ->
- {next_state, StateName, State};
+ keep_state_and_data;
Other ->
Msg = lists:flatten(
@@ -1258,33 +1285,38 @@ handle_event(info, UnexpectedMessage, StateName,
"Message: ~p\n"
"Role: ~p\n"
"Peer: ~p\n"
- "Local Address: ~p\n", [Other, UnexpectedMessage,
- SshParams#ssh.role,
- element(2,SshParams#ssh.peer),
- proplists:get_value(address, SshParams#ssh.opts)]
+ "Local Address: ~p\n", [Other,
+ UnexpectedMessage,
+ Ssh#ssh.role,
+ element(2,Ssh#ssh.peer),
+ proplists:get_value(address, Ssh#ssh.opts)]
)),
error_logger:error_report(Msg),
- {next_state, StateName, State}
+ keep_state_and_data
end;
-handle_event(internal, {disconnect,Msg,_Reason}, StateName, State) ->
- disconnect(Msg, StateName, State);
+handle_event(internal, {disconnect,Msg,_Reason}, StateName, D) ->
+ disconnect(Msg, StateName, D);
-handle_event(Type, Ev, StateName, State) ->
- case catch atom_to_list(element(1,Ev)) of
- "ssh_msg_" ++_ when Type==internal ->
- Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Message in wrong state"},
- disconnect(Msg, StateName, State);
- _ ->
- Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Internal error"},
- disconnect(Msg, StateName, State)
- end.
+handle_event(Type, Ev, StateName, D) ->
+ Descr =
+ case catch atom_to_list(element(1,Ev)) of
+ "ssh_msg_" ++_ when Type==internal ->
+ "Message in wrong state";
+ _ ->
+ "Internal error"
+ end,
+ disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = Descr},
+ StateName, D).
%%--------------------------------------------------------------------
-
+-spec terminate(any(),
+ state_name(),
+ #data{}
+ ) -> finalize_termination_result() .
+
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
terminate(normal, StateName, State) ->
@@ -1325,35 +1357,40 @@ terminate(Reason, StateName, State0) ->
format_status(normal, [_, _StateName, State]) ->
[{data, [{"State", State}]}];
format_status(terminate, [_, _StateName, State]) ->
- SshParams0 = (State#data.ssh_params),
- SshParams = SshParams0#ssh{c_keyinit = "***",
- s_keyinit = "***",
- send_mac_key = "***",
- send_mac_size = "***",
- recv_mac_key = "***",
- recv_mac_size = "***",
- encrypt_keys = "***",
- encrypt_ctx = "***",
- decrypt_keys = "***",
- decrypt_ctx = "***",
- compress_ctx = "***",
- decompress_ctx = "***",
- shared_secret = "***",
- exchanged_hash = "***",
- session_id = "***",
- keyex_key = "***",
- keyex_info = "***",
- available_host_keys = "***"},
- [{data, [{"State", State#data{decoded_data_buffer = "***",
- encoded_data_buffer = "***",
- key_exchange_init_msg = "***",
- opts = "***",
- recbuf = "***",
- ssh_params = SshParams
- }}]}].
+ Ssh0 = (State#data.ssh_params),
+ Ssh = Ssh0#ssh{c_keyinit = "***",
+ s_keyinit = "***",
+ send_mac_key = "***",
+ send_mac_size = "***",
+ recv_mac_key = "***",
+ recv_mac_size = "***",
+ encrypt_keys = "***",
+ encrypt_ctx = "***",
+ decrypt_keys = "***",
+ decrypt_ctx = "***",
+ compress_ctx = "***",
+ decompress_ctx = "***",
+ shared_secret = "***",
+ exchanged_hash = "***",
+ session_id = "***",
+ keyex_key = "***",
+ keyex_info = "***",
+ available_host_keys = "***"},
+ [{data, [{"State", State#data{decrypted_data_buffer = "***",
+ encrypted_data_buffer = "***",
+ key_exchange_init_msg = "***",
+ opts = "***",
+ recbuf_size = "***",
+ ssh_params = Ssh
+ }}]}].
%%--------------------------------------------------------------------
+-spec code_change(term(),
+ state_name(),
+ #data{},
+ term()
+ ) -> {ok, state_name(), #data{}}.
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -1379,10 +1416,11 @@ start_the_connection_child(UserPid, Role, Socket, Options) ->
%%--------------------------------------------------------------------
%% Stopping
+-type finalize_termination_result() :: ok .
finalize_termination(_StateName, #data{transport_cb = Transport,
- connection_state = Connection,
- socket = Socket}) ->
+ connection_state = Connection,
+ socket = Socket}) ->
case Connection of
#connection{system_supervisor = SysSup,
sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) ->
@@ -1393,23 +1431,27 @@ finalize_termination(_StateName, #data{transport_cb = Transport,
(catch Transport:close(Socket)),
ok.
+%%--------------------------------------------------------------------
+%% "Invert" the Role
+peer_role(client) -> server;
+peer_role(server) -> client.
-
-
+%%--------------------------------------------------------------------
%% StateName to Role
role({_,Role}) -> Role;
role({_,Role,_}) -> Role.
-
+%%--------------------------------------------------------------------
+%% Check the StateName to see if we are in the renegotiation phase
renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation;
renegotiation(_) -> false.
-
+%%--------------------------------------------------------------------
get_idle_time(SshOptions) ->
case proplists:get_value(idle_time, SshOptions) of
infinity ->
infinity;
- _IdleTime -> %% We dont want to set the timeout on first connect
+ _IdleTime -> %% We dont want to set the timeout on first connect
undefined
end.
@@ -1491,8 +1533,8 @@ call(FsmPid, Event, Timeout) ->
handle_connection_msg(Msg, StateName, State0 =
#data{starter = User,
- connection_state = Connection0,
- event_queue = Qev0}) ->
+ connection_state = Connection0,
+ event_queue = Qev0}) ->
Renegotiation = renegotiation(StateName),
Role = role(StateName),
try ssh_connection:handle_msg(Msg, Connection0, Role) of
@@ -1501,17 +1543,17 @@ handle_connection_msg(Msg, StateName, State0 =
{connected,_} ->
{Repls, State} = send_replies(Replies,
State0#data{connection_state=Connection}),
- {next_state, StateName, State, Repls};
+ {keep_state, State, Repls};
_ ->
{ConnReplies, Replies} =
lists:splitwith(fun not_connected_filter/1, Replies),
{Repls, State} = send_replies(Replies,
State0#data{event_queue = Qev0 ++ ConnReplies}),
- {next_state, StateName, State, Repls}
+ {keep_state, State, Repls}
end;
{noreply, Connection} ->
- {next_state, StateName, State0#data{connection_state = Connection}};
+ {keep_state, State0#data{connection_state = Connection}};
{disconnect, Reason0, {{replies, Replies}, Connection}} ->
{Repls,State} = send_replies(Replies, State0#data{connection_state = Connection}),
@@ -1555,46 +1597,42 @@ set_prefix_if_trouble(Msg, _) ->
kex(#ssh{algorithms=#alg{kex=Kex}}) -> Kex;
kex(_) -> undefined.
+cache(#data{connection_state=C}) -> C#connection.channel_cache.
+
+
%%%----------------------------------------------------------------
-handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From,
- #data{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
+handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{remote_id = Id} = Channel ->
- update_sys(Cache, Channel, Type, ChannelPid),
- Msg = ssh_connection:channel_request_msg(Id, Type,
- WantReply, Data),
- send_msg(Msg, add_request(WantReply, ChannelId, From, State0));
+ update_sys(cache(D), Channel, Type, ChannelPid),
+ send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data),
+ add_request(WantReply, ChannelId, From, D));
undefined ->
- State0
+ D
end.
-handle_request(ChannelId, Type, Data, WantReply, From,
- #data{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = Id} ->
- Msg = ssh_connection:channel_request_msg(Id, Type,
- WantReply, Data),
- send_msg(Msg, add_request(WantReply, ChannelId, From, State0));
+handle_request(ChannelId, Type, Data, WantReply, From, D) ->
+ case ssh_channel:cache_lookup(cache(D), ChannelId) of
+ #channel{remote_id = Id} ->
+ send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data),
+ add_request(WantReply, ChannelId, From, D));
undefined ->
- State0
+ D
end.
%%%----------------------------------------------------------------
handle_global_request({global_request, ChannelPid,
"tcpip-forward" = Type, WantReply,
- <<?UINT32(IPLen),
- IP:IPLen/binary, ?UINT32(Port)>> = Data},
- #data{connection_state =
- #connection{channel_cache = Cache}
- = Connection0} = State) ->
- ssh_channel:cache_update(Cache, #channel{user = ChannelPid,
- type = "forwarded-tcpip",
- sys = none}),
- Connection = ssh_connection:bind(IP, Port, ChannelPid, Connection0),
+ <<?UINT32(IPLen),IP:IPLen/binary, ?UINT32(Port)>> = Data
+ },
+ D) ->
+ ssh_channel:cache_update(cache(D),
+ #channel{user = ChannelPid,
+ type = "forwarded-tcpip",
+ sys = none}),
+ Connection = ssh_connection:bind(IP, Port, ChannelPid, D#data.connection_state),
Msg = ssh_connection:global_request_msg(Type, WantReply, Data),
- send_msg(Msg, State#data{connection_state = Connection});
+ send_msg(Msg, D#data{connection_state = Connection});
handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type,
WantReply, <<?UINT32(IPLen),
@@ -1618,41 +1656,41 @@ handle_idle_timeout(#data{opts = Opts}) ->
erlang:send_after(IdleTime, self(), {check_cache, [], []})
end.
-handle_channel_down(ChannelPid, #data{connection_state =
- #connection{channel_cache = Cache}} =
- State) ->
+handle_channel_down(ChannelPid, D) ->
ssh_channel:cache_foldl(
fun(Channel, Acc) when Channel#channel.user == ChannelPid ->
- ssh_channel:cache_delete(Cache,
+ ssh_channel:cache_delete(cache(D),
Channel#channel.local_id),
Acc;
(_,Acc) ->
Acc
- end, [], Cache),
- {{replies, []}, check_cache(State, Cache)}.
+ end, [], cache(D)),
+ {{replies, []}, check_cache(D)}.
+
update_sys(Cache, Channel, Type, ChannelPid) ->
ssh_channel:cache_update(Cache,
Channel#channel{sys = Type, user = ChannelPid}).
+
add_request(false, _ChannelId, _From, State) ->
State;
add_request(true, ChannelId, From, #data{connection_state =
- #connection{requests = Requests0} =
- Connection} = State) ->
+ #connection{requests = Requests0} =
+ Connection} = State) ->
Requests = [{ChannelId, From} | Requests0],
State#data{connection_state = Connection#connection{requests = Requests}}.
new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} =
- Connection}
+ Connection}
= State) ->
{Id, State#data{connection_state =
- Connection#connection{channel_id_seed = Id + 1}}}.
+ Connection#connection{channel_id_seed = Id + 1}}}.
%%%----------------------------------------------------------------
%% %%% This server/client has decided to disconnect via the state machine:
disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) ->
State = send_msg(Msg, State0),
- disconnect_fun(Description, State#data.opts),
+ disconnect_fun(Description, State),
timer:sleep(400),
{stop, {shutdown,Description}, State}.
@@ -1662,41 +1700,42 @@ counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) ->
counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) ->
Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}.
-connected_fun(User, PeerAddr, Method, Opts) ->
+connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}},
+ opts = Opts}) ->
case proplists:get_value(connectfun, Opts) of
undefined ->
ok;
Fun ->
- catch Fun(User, PeerAddr, Method)
+ catch Fun(User, Peer, Method)
end.
-retry_fun(_, _, undefined, _) ->
+retry_fun(_, undefined, _) ->
ok;
-
-retry_fun(User, PeerAddr, {error, Reason}, Opts) ->
- case proplists:get_value(failfun, Opts) of
- undefined ->
- ok;
- Fun ->
- do_retry_fun(Fun, User, PeerAddr, Reason)
- end;
-
-retry_fun(User, PeerAddr, Reason, Opts) ->
- case proplists:get_value(infofun, Opts) of
- undefined ->
- ok;
- Fun ->
- do_retry_fun(Fun, User, PeerAddr, Reason)
- end.
-
-do_retry_fun(Fun, User, PeerAddr, Reason) ->
- case erlang:fun_info(Fun, arity) of
+retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts,
+ peer = {_,Peer}
+ }}) ->
+ {Tag,Info} =
+ case Reason of
+ {error, Error} ->
+ {failfun, Error};
+ _ ->
+ {infofun, Reason}
+ end,
+ Fun = proplists:get_value(Tag, Opts, fun(_,_)-> ok end),
+ try erlang:fun_info(Fun, arity)
+ of
{arity, 2} -> %% Backwards compatible
- catch Fun(User, Reason);
+ catch Fun(User, Info);
{arity, 3} ->
- catch Fun(User, PeerAddr, Reason)
+ catch Fun(User, Peer, Info);
+ _ ->
+ ok
+ catch
+ _:_ ->
+ ok
end.
+
ssh_info([], _State, Acc) ->
Acc;
ssh_info([client_version | Rest], #data{ssh_params = #ssh{c_vsn = IntVsn,
@@ -1775,11 +1814,11 @@ get_repl(X, Acc) ->
%%%----------------------------------------------------------------
-disconnect_fun({disconnect,Msg}, Opts) ->
- disconnect_fun(Msg, Opts);
-disconnect_fun(_, undefined) ->
- ok;
-disconnect_fun(Reason, Opts) ->
+disconnect_fun({disconnect,Msg}, D) ->
+ disconnect_fun(Msg, D);
+%% disconnect_fun(_, undefined) ->
+%% ok;
+disconnect_fun(Reason, #data{opts=Opts}) ->
case proplists:get_value(disconnectfun, Opts) of
undefined ->
ok;
@@ -1787,7 +1826,9 @@ disconnect_fun(Reason, Opts) ->
catch Fun(Reason)
end.
-unexpected_fun(UnexpectedMessage, Opts, #ssh{peer={_,Peer}}) ->
+unexpected_fun(UnexpectedMessage, #data{opts = Opts,
+ ssh_params = #ssh{peer = {_,Peer} }
+ } ) ->
case proplists:get_value(unexpectedfun, Opts) of
undefined ->
report;
@@ -1796,18 +1837,31 @@ unexpected_fun(UnexpectedMessage, Opts, #ssh{peer={_,Peer}}) ->
end.
-check_cache(#data{opts = Opts} = State, Cache) ->
+debug_fun(#ssh_msg_debug{always_display = Display,
+ message = DbgMsg,
+ language = Lang},
+ #data{opts = Opts}) ->
+ case proplists:get_value(ssh_msg_debug_fun, Opts) of
+ undefined ->
+ ok;
+ Fun ->
+ catch Fun(self(), Display, DbgMsg, Lang)
+ end.
+
+
+
+check_cache(D) ->
%% Check the number of entries in Cache
- case proplists:get_value(size, ets:info(Cache)) of
+ case proplists:get_value(size, ets:info(cache(D))) of
0 ->
- case proplists:get_value(idle_time, Opts, infinity) of
+ case proplists:get_value(idle_time, D#data.opts, infinity) of
infinity ->
- State;
+ D;
Time ->
- handle_idle_timer(Time, State)
+ handle_idle_timer(Time, D)
end;
_ ->
- State
+ D
end.
handle_idle_timer(Time, #data{idle_timer_ref = undefined} = State) ->