From 9dc46e8d58c9464c8a48b74342951265c3b43dc8 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 22 Jan 2016 19:28:16 +0100 Subject: ssh: Gen_statem rewrite of ssh_connection_handler Including misc fixes in surronding code as well as in test cases. --- lib/ssh/src/ssh_auth.erl | 22 +- lib/ssh/src/ssh_connection.erl | 26 +- lib/ssh/src/ssh_connection_handler.erl | 1760 +++++++++++++--------------- lib/ssh/src/ssh_message.erl | 46 +- lib/ssh/src/ssh_no_io.erl | 32 +- lib/ssh/src/ssh_transport.erl | 191 ++- lib/ssh/test/ssh_algorithms_SUITE.erl | 4 +- lib/ssh/test/ssh_benchmark_SUITE.erl | 44 +- lib/ssh/test/ssh_options_SUITE.erl | 4 +- lib/ssh/test/ssh_renegotiate_SUITE.erl | 3 +- lib/ssh/test/ssh_sftp_SUITE.erl | 1 - lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl | 1 - lib/ssh/test/ssh_test_lib.erl | 4 + 13 files changed, 1015 insertions(+), 1123 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 4b3c21ce3f..49eec8072f 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -135,9 +135,9 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> service = "ssh-connection"}); {error, no_user} -> ErrStr = "Could not determine the users name", - throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, - description = ErrStr, - language = "en"}) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME, + description = ErrStr}) end. userauth_request_msg(#ssh{userauth_preference = []} = Ssh) -> @@ -355,10 +355,10 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> - throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "Server does not support" - "keyboard-interactive", - language = "en"}). + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + description = "Server does not support keyboard-interactive" + }). %%-------------------------------------------------------------------- @@ -420,10 +420,10 @@ check_password(User, Password, Opts, Ssh) -> {false,NewState} -> {false, Ssh#ssh{pwdfun_user_state=NewState}}; disconnect -> - throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = - "Unable to connect using the available authentication methods", - language = ""}) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + description = "Unable to connect using the available authentication methods" + }) end end. diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index a34478732c..6ca6ed6d77 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -232,6 +232,8 @@ exit_status(ConnectionHandler, Channel, Status) -> ssh_connection_handler:request(ConnectionHandler, Channel, "exit-status", false, [?uint32(Status)], 0). +%% The client wants the server to make a tcp connection on behalf of +%% the client direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort, Timeout) -> direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort, @@ -256,6 +258,10 @@ direct_tcpip(ConnectionHandler, RemoteIP, RemotePort, OrigIP, OrigPort, Timeout) end. +%% The client wants the server to listen on BindIP:BindPort for tcp +%% connections. When there is a tcp connect (SYN) to that pair on the +%% server, the server sends a #ssh_msg_channel_open{"forwarded-tcpip"} +%% back to the client for each new tcp connection tcpip_forward(ConnectionHandler, BindIP, BindPort) -> case encode_ip(BindIP) of false -> @@ -331,8 +337,9 @@ channel_data(ChannelId, DataType, Data, FlowCtrlMsgs = flow_control(Replies, Channel, Cache), {{replies, Replies ++ FlowCtrlMsgs}, Connection}; _ -> - gen_fsm:reply(From, {error, closed}), - {noreply, Connection} + {{replies,[{channel_request_reply,From,{error,closed}}]}, Connection} + %% gen_fsm:reply(From, {error, closed}), + %% {noreply, Connection} end. handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, @@ -499,7 +506,8 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, handle_msg(#ssh_msg_channel_open{channel_type = "session", sender_channel = RemoteId}, - Connection, client) -> + Connection, + client) -> %% Client implementations SHOULD reject any session channel open %% requests to make it more difficult for a corrupt server to attack the %% client. See See RFC 4254 6.1. @@ -514,10 +522,10 @@ handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip" = Type, initial_window_size = RWindowSz, maximum_packet_size = RPacketSz, data = Data}, - #connection{channel_cache = Cache, - options = SSHopts} = Connection0, server) -> + #connection{channel_cache = Cache, options = SSHopts} = Connection0, + server) -> <> = Data, + ?UINT32(OLen), Orig:OLen/binary, ?UINT32(OrigPort)>> = Data, MinAcceptedPackSz = proplists:get_value(minimal_remote_max_packet_size, SSHopts, 0), @@ -786,11 +794,11 @@ handle_msg(#ssh_msg_global_request{name = _Type, handle_msg(#ssh_msg_request_failure{}, #connection{requests = [{_, From} | Rest]} = Connection, _) -> - {{replies, [{channel_requst_reply, From, {failure, <<>>}}]}, + {{replies, [{channel_request_reply, From, {failure, <<>>}}]}, Connection#connection{requests = Rest}}; handle_msg(#ssh_msg_request_success{data = Data}, #connection{requests = [{_, From} | Rest]} = Connection, _) -> - {{replies, [{channel_requst_reply, From, {success, Data}}]}, + {{replies, [{channel_request_reply, From, {success, Data}}]}, Connection#connection{requests = Rest}}; handle_msg(#ssh_msg_disconnect{code = Code, @@ -1059,7 +1067,7 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid}, Connection, Reply) -> case lists:keysearch(ChannelId, 1, Requests) of {value, {ChannelId, From}} -> - {{channel_requst_reply, From, Reply}, + {{channel_request_reply, From, Reply}, Connection#connection{requests = lists:keydelete(ChannelId, 1, Requests)}}; false when (Reply == success) or (Reply == failure) -> diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 2bef6a41cd..d26c586c54 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -28,7 +28,7 @@ -module(ssh_connection_handler). --behaviour(gen_fsm). +-behaviour(gen_statem). -include("ssh.hrl"). -include("ssh_transport.hrl"). @@ -37,45 +37,37 @@ -compile(export_all). -export([start_link/3]). +%%-define(IO_FORMAT(F,A), io:format(F,A)). +-define(IO_FORMAT(F,A), ok). + %% Internal application API -export([open_channel/6, reply_request/3, request/6, request/7, global_request/4, send/5, send_eof/2, info/1, info/2, connection_info/2, channel_info/3, adjust_window/3, close/2, stop/1, renegotiate/1, renegotiate_data/1, + disconnect/1, disconnect/2, start_connection/4, get_print_info/1]). -%% gen_fsm callbacks --export([hello/2, kexinit/2, key_exchange/2, - key_exchange_dh_gex_init/2, key_exchange_dh_gex_reply/2, - new_keys/2, - service_request/2, connected/2, - userauth/2, - userauth_keyboard_interactive/2, - userauth_keyboard_interactive_info_response/2, - error/2]). - --export([init/1, handle_event/3, - handle_sync_event/4, handle_info/3, terminate/3, format_status/2, code_change/4]). +%% gen_statem callbacks +-export([init/1, handle_event/4, terminate/3, format_status/2, code_change/4]). -record(state, { - role, client, starter, auth_user, connection_state, latest_channel_id = 0, idle_timer_ref, - transport_protocol, % ex: tcp + transport_protocol, % ex: tcp transport_cb, transport_close_tag, - ssh_params, % #ssh{} - from ssh.hrl - socket, % socket() - decoded_data_buffer, % binary() - encoded_data_buffer, % binary() + ssh_params, % #ssh{} - from ssh.hrl + socket, % socket() + decoded_data_buffer, % binary() + encoded_data_buffer, % binary() undecoded_packet_length, % integer() - key_exchange_init_msg, % #ssh_msg_kexinit{} - renegotiate = false, % boolean() + key_exchange_init_msg, % #ssh_msg_kexinit{} last_size_rekey = 0, event_queue = [], connection_queue, @@ -83,30 +75,13 @@ port, opts, recbuf - }). - --type state_name() :: hello | kexinit | key_exchange | key_exchange_dh_gex_init | - key_exchange_dh_gex_reply | new_keys | service_request | - userauth | userauth_keyboard_interactive | - userauth_keyboard_interactive_info_response | - connection. - --type gen_fsm_state_return() :: {next_state, state_name(), term()} | - {next_state, state_name(), term(), timeout()} | - {stop, term(), term()}. - --type gen_fsm_sync_return() :: {next_state, state_name(), term()} | - {next_state, state_name(), term(), timeout()} | - {reply, term(), state_name(), term()} | - {stop, term(), term(), term()}. + }). %%==================================================================== %% Internal application API %%==================================================================== %%-------------------------------------------------------------------- --spec start_connection(client| server, port(), proplists:proplist(), - timeout()) -> {ok, pid()} | {error, term()}. %%-------------------------------------------------------------------- start_connection(client = Role, Socket, Options, Timeout) -> try @@ -128,8 +103,8 @@ start_connection(server = Role, Socket, Options, Timeout) -> try case proplists:get_value(parallel_login, SSH_Opts, false) of true -> - HandshakerPid = - spawn_link(fun() -> + HandshakerPid = + spawn_link(fun() -> receive {do_handshake, Pid} -> handshake(Pid, erlang:monitor(process,Pid), Timeout) @@ -164,11 +139,10 @@ start_link(Role, Socket, Options) -> init([Role, Socket, SshOpts]) -> process_flag(trap_exit, true), {NumVsn, StrVsn} = ssh_transport:versions(Role, SshOpts), - {Protocol, Callback, CloseTag} = + {Protocol, Callback, CloseTag} = proplists:get_value(transport, SshOpts, {tcp, gen_tcp, tcp_closed}), Cache = ssh_channel:cache_create(), State0 = #state{ - role = Role, connection_state = #connection{channel_cache = Cache, channel_id_seed = 0, port_bindings = [], @@ -183,142 +157,118 @@ init([Role, Socket, SshOpts]) -> opts = SshOpts }, - State = init_role(State0), + State = init_role(Role, State0), try init_ssh(Role, NumVsn, StrVsn, SshOpts, Socket) of Ssh -> - gen_fsm:enter_loop(?MODULE, [], hello, - State#state{ssh_params = Ssh}) + gen_statem:enter_loop(?MODULE, + [], %%[{debug,[trace,log,statistics,debug]} || Role==server], + handle_event_function, + {hello,Role}, + State#state{ssh_params = Ssh}, + []) catch _:Error -> - gen_fsm:enter_loop(?MODULE, [], error, {Error, State}) + gen_statem:enter_loop(?MODULE, + [], + handle_event_function, + {init_error,Error}, + State, + []) end. -%% Temporary fix for the Nessus error. SYN-> <-SYNACK ACK-> RST-> ? -error(_Event, {Error,State=#state{}}) -> - case Error of - {badmatch,{error,enotconn}} -> - %% {error,enotconn} probably from inet:peername in - %% init_ssh(server,..)/5 called from init/1 - {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}, State}; - _ -> - {stop, {shutdown,{init,Error}}, State} - end; -error(Event, State) -> - %% State deliberately not checked beeing #state. This is a panic-clause... - {stop, {shutdown,{init,{spurious_error,Event}}}, State}. - %%-------------------------------------------------------------------- --spec open_channel(pid(), string(), iodata(), integer(), integer(), - timeout()) -> {open, channel_id()} | {error, term()}. %%-------------------------------------------------------------------- open_channel(ConnectionHandler, ChannelType, ChannelSpecificData, InitialWindowSize, MaxPacketSize, Timeout) -> - sync_send_all_state_event(ConnectionHandler, {open, self(), ChannelType, + call(ConnectionHandler, {open, self(), ChannelType, InitialWindowSize, MaxPacketSize, ChannelSpecificData, Timeout}). %%-------------------------------------------------------------------- --spec request(pid(), pid(), channel_id(), string(), boolean(), iodata(), - timeout()) -> success | failure | ok | {error, term()}. %%-------------------------------------------------------------------- request(ConnectionHandler, ChannelPid, ChannelId, Type, true, Data, Timeout) -> - sync_send_all_state_event(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data, + call(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data, Timeout}); request(ConnectionHandler, ChannelPid, ChannelId, Type, false, Data, _) -> - send_all_state_event(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data}). + cast(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data}). %%-------------------------------------------------------------------- --spec request(pid(), channel_id(), string(), boolean(), iodata(), - timeout()) -> success | failure | {error, timeout}. %%-------------------------------------------------------------------- request(ConnectionHandler, ChannelId, Type, true, Data, Timeout) -> - sync_send_all_state_event(ConnectionHandler, {request, ChannelId, Type, Data, Timeout}); + call(ConnectionHandler, {request, ChannelId, Type, Data, Timeout}); request(ConnectionHandler, ChannelId, Type, false, Data, _) -> - send_all_state_event(ConnectionHandler, {request, ChannelId, Type, Data}). + cast(ConnectionHandler, {request, ChannelId, Type, Data}). %%-------------------------------------------------------------------- --spec reply_request(pid(), success | failure, channel_id()) -> ok. %%-------------------------------------------------------------------- reply_request(ConnectionHandler, Status, ChannelId) -> - send_all_state_event(ConnectionHandler, {reply_request, Status, ChannelId}). + cast(ConnectionHandler, {reply_request, Status, ChannelId}). %%-------------------------------------------------------------------- --spec global_request(pid(), string(), boolean(), iolist()) -> ok | error. %%-------------------------------------------------------------------- global_request(ConnectionHandler, Type, true = Reply, Data) -> - case sync_send_all_state_event(ConnectionHandler, - {global_request, self(), Type, Reply, Data}) of + case call(ConnectionHandler, {global_request, self(), Type, Reply, Data}) of {ssh_cm, ConnectionHandler, {success, _}} -> ok; {ssh_cm, ConnectionHandler, {failure, _}} -> error end; global_request(ConnectionHandler, Type, false = Reply, Data) -> - send_all_state_event(ConnectionHandler, {global_request, self(), Type, Reply, Data}). + cast(ConnectionHandler, {global_request, self(), Type, Reply, Data}). %%-------------------------------------------------------------------- --spec send(pid(), channel_id(), integer(), iodata(), timeout()) -> - ok | {error, timeout} | {error, closed}. %%-------------------------------------------------------------------- send(ConnectionHandler, ChannelId, Type, Data, Timeout) -> - sync_send_all_state_event(ConnectionHandler, {data, ChannelId, Type, Data, Timeout}). + call(ConnectionHandler, {data, ChannelId, Type, Data, Timeout}). %%-------------------------------------------------------------------- --spec send_eof(pid(), channel_id()) -> ok | {error, closed}. %%-------------------------------------------------------------------- send_eof(ConnectionHandler, ChannelId) -> - sync_send_all_state_event(ConnectionHandler, {eof, ChannelId}). + call(ConnectionHandler, {eof, ChannelId}). %%-------------------------------------------------------------------- --spec connection_info(pid(), [atom()]) -> proplists:proplist(). %%-------------------------------------------------------------------- get_print_info(ConnectionHandler) -> - sync_send_all_state_event(ConnectionHandler, get_print_info, 1000). + call(ConnectionHandler, get_print_info, 1000). connection_info(ConnectionHandler, Options) -> - sync_send_all_state_event(ConnectionHandler, {connection_info, Options}). + call(ConnectionHandler, {connection_info, Options}). %%-------------------------------------------------------------------- --spec channel_info(pid(), channel_id(), [atom()]) -> proplists:proplist(). %%-------------------------------------------------------------------- channel_info(ConnectionHandler, ChannelId, Options) -> - sync_send_all_state_event(ConnectionHandler, {channel_info, ChannelId, Options}). + call(ConnectionHandler, {channel_info, ChannelId, Options}). %%-------------------------------------------------------------------- --spec adjust_window(pid(), channel_id(), integer()) -> ok. %%-------------------------------------------------------------------- adjust_window(ConnectionHandler, Channel, Bytes) -> - send_all_state_event(ConnectionHandler, {adjust_window, Channel, Bytes}). + cast(ConnectionHandler, {adjust_window, Channel, Bytes}). %%-------------------------------------------------------------------- --spec renegotiate(pid()) -> ok. %%-------------------------------------------------------------------- renegotiate(ConnectionHandler) -> - send_all_state_event(ConnectionHandler, renegotiate). + cast(ConnectionHandler, renegotiate). %%-------------------------------------------------------------------- --spec renegotiate_data(pid()) -> ok. %%-------------------------------------------------------------------- renegotiate_data(ConnectionHandler) -> - send_all_state_event(ConnectionHandler, data_size). + cast(ConnectionHandler, data_size). %%-------------------------------------------------------------------- --spec close(pid(), channel_id()) -> ok. %%-------------------------------------------------------------------- close(ConnectionHandler, ChannelId) -> - case sync_send_all_state_event(ConnectionHandler, {close, ChannelId}) of + case call(ConnectionHandler, {close, ChannelId}) of ok -> ok; - {error, closed} -> + {error, closed} -> ok - end. - + end. + %%-------------------------------------------------------------------- --spec stop(pid()) -> ok | {error, term()}. %%-------------------------------------------------------------------- stop(ConnectionHandler)-> - case sync_send_all_state_event(ConnectionHandler, stop) of + case call(ConnectionHandler, stop) of {error, closed} -> ok; Other -> @@ -329,484 +279,492 @@ info(ConnectionHandler) -> info(ConnectionHandler, {info, all}). info(ConnectionHandler, ChannelProcess) -> - sync_send_all_state_event(ConnectionHandler, {info, ChannelProcess}). - + call(ConnectionHandler, {info, ChannelProcess}). %%==================================================================== -%% gen_fsm callbacks +%% gen_statem callbacks %%==================================================================== -%%-------------------------------------------------------------------- --spec hello(socket_control | {info_line, list()} | {version_exchange, list()}, - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- +%% Temporary fix for the Nessus error. SYN-> <-SYNACK ACK-> RST-> ? +handle_event(_, _Event, {init_error,Error}, _State) -> + case Error of + {badmatch,{error,enotconn}} -> + %% {error,enotconn} probably from inet:peername in + %% init_ssh(server,..)/5 called from init/1 + {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}}; + _ -> + {stop, {shutdown,{init,Error}}} + end; + -hello(socket_control, #state{socket = Socket, ssh_params = Ssh} = State) -> +%%% ######## {hello, client|server} #### + +handle_event(_, socket_control, StateName={hello,_}, S=#state{socket=Socket, + ssh_params=Ssh}) -> VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)), - send_msg(VsnMsg, State), + send_bytes(VsnMsg, S), case getopt(recbuf, Socket) of {ok, Size} -> - inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]), - {next_state, hello, State#state{recbuf = Size}}; + inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}, {nodelay,true}]), + {next_state, StateName, S#state{recbuf=Size}}; {error, Reason} -> - {stop, {shutdown, Reason}, State} + {stop, {shutdown,Reason}} end; -hello({info_line, _Line},#state{role = client, socket = Socket} = State) -> +handle_event(_, {info_line,_Line}, StateName={hello,client}, S=#state{socket=Socket}) -> %% The server may send info lines before the version_exchange inet:setopts(Socket, [{active, once}]), - {next_state, hello, State}; + {next_state, StateName, S}; -hello({info_line, _Line},#state{role = server, - socket = Socket, - transport_cb = Transport } = State) -> +handle_event(_, {info_line,_Line}, {hello,server}, S) -> %% as openssh - Transport:send(Socket, "Protocol mismatch."), - {stop, {shutdown,"Protocol mismatch in version exchange."}, State}; + send_bytes("Protocol mismatch.", S), + {stop, {shutdown,"Protocol mismatch in version exchange."}}; -hello({version_exchange, Version}, #state{ssh_params = Ssh0, - socket = Socket, - recbuf = Size} = State) -> +handle_event(_, {version_exchange,Version}, {hello,Role}, S=#state{ssh_params = Ssh0, + socket = Socket, + recbuf = Size}) -> {NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version), case handle_version(NumVsn, StrVsn, Ssh0) of {ok, Ssh1} -> inet:setopts(Socket, [{packet,0}, {mode,binary}, {active, once}, {recbuf, Size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), - send_msg(SshPacket, State), - {next_state, kexinit, next_packet(State#state{ssh_params = Ssh, - key_exchange_init_msg = - KeyInitMsg})}; + send_bytes(SshPacket, S), + {next_state, {kexinit,Role,init}, S#state{ssh_params = Ssh, + key_exchange_init_msg = KeyInitMsg}}; not_supported -> - DisconnectMsg = - #ssh_msg_disconnect{code = - ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, - description = "Protocol version " ++ StrVsn - ++ " not supported", - language = "en"}, - handle_disconnect(DisconnectMsg, State) - end. - -%%-------------------------------------------------------------------- --spec kexinit({#ssh_msg_kexinit{}, binary()}, #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -kexinit({#ssh_msg_kexinit{} = Kex, Payload}, - #state{ssh_params = #ssh{role = Role} = Ssh0, - key_exchange_init_msg = OwnKex} = - State) -> - Ssh1 = ssh_transport:key_init(opposite_role(Role), Ssh0, Payload), - case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of - {ok, NextKexMsg, Ssh} when Role == client -> - send_msg(NextKexMsg, State), - {next_state, key_exchange, - next_packet(State#state{ssh_params = Ssh})}; - {ok, Ssh} when Role == server -> - {next_state, key_exchange, - next_packet(State#state{ssh_params = Ssh})} - end. - -%%-------------------------------------------------------------------- --spec key_exchange(#ssh_msg_kexdh_init{} | #ssh_msg_kexdh_reply{} | - #ssh_msg_kex_dh_gex_group{} | #ssh_msg_kex_dh_gex_request{} | - #ssh_msg_kex_dh_gex_request{} | #ssh_msg_kex_dh_gex_reply{}, #state{}) - -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- - -key_exchange(#ssh_msg_kexdh_init{} = Msg, - #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - case ssh_transport:handle_kexdh_init(Msg, Ssh0) of - {ok, KexdhReply, Ssh1} -> - send_msg(KexdhReply, State), - {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})} + disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, + description = ["Protocol version ",StrVsn," not supported"]}, + {next_state, {hello,Role}, S}) end; + +%%% ######## {kexinit, client|server, init|renegotiate} #### + +handle_event(_, {#ssh_msg_kexinit{} = Kex, Payload}, {kexinit,client,ReNeg}, + S = #state{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#state{ssh_params = Ssh}}; + +handle_event(_, {#ssh_msg_kexinit{} = Kex, Payload}, {kexinit,server,ReNeg}, + S = #state{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#state{ssh_params = Ssh}}; + +%%% ######## {key_exchange, client|server, init|renegotiate} #### + +handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, + S = #state{ssh_params = Ssh0}) -> + {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, Ssh0), + send_bytes(KexdhReply, S), + {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), + send_bytes(NewKeys, S), + {next_state, {new_keys,server,ReNeg}, S#state{ssh_params = Ssh}}; -key_exchange(#ssh_msg_kexdh_reply{} = Msg, - #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> +handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, + #state{ssh_params=Ssh0} = State) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, Ssh0), - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(NewKeys, State), + {next_state, {new_keys,client,ReNeg}, State#state{ssh_params = Ssh}}; -key_exchange(#ssh_msg_kex_dh_gex_request{} = Msg, - #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> +handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, + #state{ssh_params=Ssh0} = State) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), - send_msg(GexGroup, State), - {next_state, key_exchange_dh_gex_init, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(GexGroup, State), + {next_state, {key_exchange_dh_gex_init,server,ReNeg}, State#state{ssh_params = Ssh}}; -key_exchange(#ssh_msg_kex_dh_gex_request_old{} = Msg, - #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> +handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, + #state{ssh_params=Ssh0} = State) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), - send_msg(GexGroup, State), - {next_state, key_exchange_dh_gex_init, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(GexGroup, State), + {next_state, {key_exchange_dh_gex_init,server,ReNeg}, State#state{ssh_params = Ssh}}; -key_exchange(#ssh_msg_kex_dh_gex_group{} = Msg, - #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> +handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, + #state{ssh_params=Ssh0} = State) -> {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, Ssh0), - send_msg(KexGexInit, State), - {next_state, key_exchange_dh_gex_reply, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(KexGexInit, State), + {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, State#state{ssh_params = Ssh}}; -key_exchange(#ssh_msg_kex_ecdh_init{} = Msg, - #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> +handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, + #state{ssh_params=Ssh0} = State) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, Ssh0), - send_msg(KexEcdhReply, State), + send_bytes(KexEcdhReply, State), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(NewKeys, State), + {next_state, {new_keys,server,ReNeg}, State#state{ssh_params = Ssh}}; -key_exchange(#ssh_msg_kex_ecdh_reply{} = Msg, - #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> +handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, + #state{ssh_params=Ssh0} = State) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, Ssh0), - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}. + send_bytes(NewKeys, State), + {next_state, {new_keys,client,ReNeg}, State#state{ssh_params = Ssh}}; -%%-------------------------------------------------------------------- --spec key_exchange_dh_gex_init(#ssh_msg_kex_dh_gex_init{}, #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -key_exchange_dh_gex_init(#ssh_msg_kex_dh_gex_init{} = Msg, - #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> +%%% ######## {key_exchange_dh_gex_init, server, init|renegotiate} #### + +handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, + #state{ssh_params=Ssh0} = State) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, Ssh0), - send_msg(KexGexReply, State), + send_bytes(KexGexReply, State), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}. + send_bytes(NewKeys, State), + {next_state, {new_keys,server,ReNeg}, State#state{ssh_params = Ssh}}; -%%-------------------------------------------------------------------- --spec key_exchange_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{}, #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -key_exchange_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{} = Msg, - #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> - {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, Ssh0), - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh1})}. +%%% ######## {key_exchange_dh_gex_reply, client, init|renegotiate} #### -%%-------------------------------------------------------------------- --spec new_keys(#ssh_msg_newkeys{}, #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- +handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, + #state{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#state{ssh_params = Ssh1}}; + +%%% ######## {new_keys, client|server} #### + +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, + #state{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#state{ssh_params=Ssh}}; -new_keys(#ssh_msg_newkeys{} = Msg, #state{ssh_params = Ssh0} = State0) -> +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, + S = #state{ssh_params = Ssh0}) -> {ok, Ssh} = ssh_transport:handle_new_keys(Msg, Ssh0), - after_new_keys(next_packet(State0#state{ssh_params = Ssh})). + {next_state, {service_request,server}, S#state{ssh_params = Ssh}}; -%%-------------------------------------------------------------------- --spec service_request(#ssh_msg_service_request{} | #ssh_msg_service_accept{}, - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -service_request(#ssh_msg_service_request{name = "ssh-userauth"} = Msg, - #state{ssh_params = #ssh{role = server, - session_id = SessionId} = Ssh0} = State) -> +handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, S) -> + {next_state, {connected,Role}, S}; + +%%% ######## {service_request, client|server} + +handle_event(_, #ssh_msg_service_request{name = "ssh-userauth"} = Msg, {service_request,server}, + #state{ssh_params = #ssh{session_id=SessionId} = Ssh0} = State) -> {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(Reply, State), + {next_state, {userauth,server}, State#state{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); -service_request(#ssh_msg_service_accept{name = "ssh-userauth"}, - #state{ssh_params = #ssh{role = client, - service = "ssh-userauth"} = Ssh0} = - State) -> +handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, + #state{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), - send_msg(Msg, State), - {next_state, userauth, next_packet(State#state{auth_user = Ssh#ssh.user, ssh_params = Ssh})}. + send_bytes(Msg, State), + {next_state, {userauth,client}, State#state{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; -%%-------------------------------------------------------------------- --spec userauth(#ssh_msg_userauth_request{} | #ssh_msg_userauth_info_request{} | - #ssh_msg_userauth_info_response{} | #ssh_msg_userauth_success{} | - #ssh_msg_userauth_failure{} | #ssh_msg_userauth_banner{}, - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -userauth(#ssh_msg_userauth_request{service = "ssh-connection", - method = "none"} = Msg, - #state{ssh_params = #ssh{session_id = SessionId, role = server, +%%% ######## {userauth, client|server} #### + +handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", + method = "none"} = Msg, StateName={userauth,server}, + #state{ssh_params = #ssh{session_id = SessionId, service = "ssh-connection"} = Ssh0 - } = State) -> + } = State) -> +?IO_FORMAT('~p #ssh_msg_userauth_request{ssh-connection,~p}~n',[self(),Msg#ssh_msg_userauth_request.method]), {not_authorized, {_User, _Reason}, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(Reply, State), + {next_state, StateName, State#state{ssh_params = Ssh}}; -userauth(#ssh_msg_userauth_request{service = "ssh-connection", - method = Method} = Msg, - #state{ssh_params = #ssh{session_id = SessionId, role = server, +handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", + method = Method} = Msg, StateName={userauth,server}, + #state{ssh_params = #ssh{session_id = SessionId, service = "ssh-connection", peer = {_, Address}} = Ssh0, opts = Opts, starter = Pid} = State) -> +?IO_FORMAT('~p #ssh_msg_userauth_request{ssh-connection,~p}~n',[self(),Msg#ssh_msg_userauth_request.method]), 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_msg(Reply, State), + send_bytes(Reply, State), Pid ! ssh_connected, connected_fun(User, Address, Method, Opts), - {next_state, connected, - next_packet(State#state{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}})}; +?IO_FORMAT('~p CONNECTED!~n',[self()]), + {next_state, {connected,server}, + State#state{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_msg(Reply, State), - {next_state, userauth_keyboard_interactive, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(Reply, State), +?IO_FORMAT('~p not_authorized (1)~n',[self()]), + {next_state, {userauth_keyboard_interactive,server}, State#state{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Address, Reason, Opts), - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + send_bytes(Reply, State), +?IO_FORMAT('~p not_authorized (2)~n',[self()]), + {next_state, StateName, State#state{ssh_params = Ssh}} end; false -> - userauth(Msg#ssh_msg_userauth_request{method="none"}, State) - end; + %% At least one non-erlang client does like this. Retry as the next event +?IO_FORMAT('~p bug-fix~n',[self()]), + {next_state, StateName, State, + [{next_event, internal, Msg#ssh_msg_userauth_request{method="none"}}] + } + end; -userauth(#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client} = Ssh, - starter = Pid} = State) -> +handle_event(_, #ssh_msg_userauth_request{service = Service}, {userauth,server}=StateName, State) + when Service =/= "ssh-connection" -> +?IO_FORMAT('~p #ssh_msg_userauth_request{~p,...}~n',[self(),Service]), + Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + description = "Unknown service"}, + disconnect(Msg, StateName, State); + +handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, #state{ssh_params = Ssh, + starter = Pid} = State) -> Pid ! ssh_connected, - {next_state, connected, next_packet(State#state{ssh_params = - Ssh#ssh{authenticated = true}})}; -userauth(#ssh_msg_userauth_failure{}, - #state{ssh_params = #ssh{role = client, - userauth_methods = []}} - = State) -> - Msg = #ssh_msg_disconnect{code = - ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + {next_state, {connected,client}, State#state{ssh_params=Ssh#ssh{authenticated = true}}}; + +handle_event(_, #ssh_msg_userauth_failure{}, {userauth,client}=StateName, + #state{ssh_params = #ssh{userauth_methods = []}} = State) -> + Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, description = "Unable to connect using the available" - " authentication methods", - language = "en"}, - handle_disconnect(Msg, State); - -%% Server tells us which authentication methods that are allowed -userauth(#ssh_msg_userauth_failure{authentications = Methodes}, - #state{ssh_params = #ssh{role = client, - userauth_methods = none} = Ssh0} = State) -> - AuthMethods = string:tokens(Methodes, ","), - Ssh1 = Ssh0#ssh{userauth_methods = AuthMethods}, + " authentication methods"}, + disconnect(Msg, StateName, State); + + +handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client}, + #state{ssh_params = Ssh0 = #ssh{userauth_methods=AuthMthds}} = State) -> + %% The prefered authentication method failed try next method + Ssh1 = case AuthMthds of + none -> + %% Server tells us which authentication methods that are allowed + Ssh0#ssh{userauth_methods = string:tokens(Methods, ",")}; + _ -> + %% We already know... + Ssh0 + end, case ssh_auth:userauth_request_msg(Ssh1) of {disconnect, DisconnectMsg, {Msg, Ssh}} -> - send_msg(Msg, State), - handle_disconnect(DisconnectMsg, State#state{ssh_params = Ssh}); + send_bytes(Msg, State), + disconnect(DisconnectMsg, StateName, State#state{ssh_params = Ssh}); {"keyboard-interactive", {Msg, Ssh}} -> - send_msg(Msg, State), - {next_state, userauth_keyboard_interactive, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(Msg, State), + {next_state, {userauth_keyboard_interactive,client}, State#state{ssh_params = Ssh}}; {_Method, {Msg, Ssh}} -> - send_msg(Msg, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + send_bytes(Msg, State), + {next_state, StateName, State#state{ssh_params = Ssh}} end; -%% The prefered authentication method failed try next method -userauth(#ssh_msg_userauth_failure{}, - #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> - case ssh_auth:userauth_request_msg(Ssh0) of - {disconnect, DisconnectMsg,{Msg, Ssh}} -> - send_msg(Msg, State), - handle_disconnect(DisconnectMsg, State#state{ssh_params = Ssh}); - {"keyboard-interactive", {Msg, Ssh}} -> - send_msg(Msg, State), - {next_state, userauth_keyboard_interactive, next_packet(State#state{ssh_params = Ssh})}; - {_Method, {Msg, Ssh}} -> - send_msg(Msg, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} - end; +handle_event(_, #ssh_msg_userauth_banner{}, StateName={userauth,client}, + #state{ssh_params = #ssh{userauth_quiet_mode=true}} = State) -> + {next_state, StateName, State}; -userauth(#ssh_msg_userauth_banner{}, - #state{ssh_params = #ssh{userauth_quiet_mode = true, - role = client}} = State) -> - {next_state, userauth, next_packet(State)}; -userauth(#ssh_msg_userauth_banner{message = Msg}, - #state{ssh_params = - #ssh{userauth_quiet_mode = false, role = client}} = State) -> +handle_event(_, #ssh_msg_userauth_banner{message = Msg}, StateName={userauth,client}, + #state{ssh_params = #ssh{userauth_quiet_mode=false}} = State) -> io:format("~s", [Msg]), - {next_state, userauth, next_packet(State)}. - + {next_state, StateName, State}; +%%% ######## {userauth_keyboard_interactive, client|server} -userauth_keyboard_interactive(#ssh_msg_userauth_info_request{} = Msg, - #state{ssh_params = #ssh{role = client, - io_cb = IoCb} = Ssh0} = State) -> +handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client}, + #state{ssh_params = #ssh{io_cb=IoCb} = Ssh0} = State) -> {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0), - send_msg(Reply, State), - {next_state, userauth_keyboard_interactive_info_response, next_packet(State#state{ssh_params = Ssh})}; + send_bytes(Reply, State), + {next_state, {userauth_keyboard_interactive_info_response,client}, State#state{ssh_params = Ssh}}; -userauth_keyboard_interactive(#ssh_msg_userauth_info_response{} = Msg, - #state{ssh_params = #ssh{role = server, - peer = {_, Address}} = Ssh0, - opts = Opts, starter = Pid} = State) -> +handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, + #state{ssh_params = #ssh{peer = {_,Address}} = Ssh0, + opts = Opts, + starter = Pid} = State) -> case ssh_auth:handle_userauth_info_response(Msg, Ssh0) of {authorized, User, {Reply, Ssh}} -> - send_msg(Reply, State), + send_bytes(Reply, State), Pid ! ssh_connected, connected_fun(User, Address, "keyboard-interactive", Opts), - {next_state, connected, - next_packet(State#state{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}})}; + {next_state, {connected,server}, State#state{auth_user = User, + ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Address, Reason, Opts), - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + send_bytes(Reply, State), + {next_state, {userauth,server}, State#state{ssh_params = Ssh}} end; -userauth_keyboard_interactive(Msg = #ssh_msg_userauth_failure{}, - #state{ssh_params = Ssh0 = - #ssh{role = client, - userauth_preference = Prefs0}} - = State) -> +handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, + #state{ssh_params = Ssh0 = #ssh{userauth_preference=Prefs0}} = State) -> Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Prefs0, Method =/= "keyboard-interactive"], - userauth(Msg, State#state{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}). + {next_state, {userauth,client}, + State#state{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}, + [{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_success{}, {userauth_keyboard_interactive_info_response, client}, S) -> + {next_state, {userauth,client}, S, [{next_event, internal, Msg}]}; -userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_failure{}, - #state{ssh_params = #ssh{role = client}} = State) -> - userauth(Msg, State); -userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_success{}, - #state{ssh_params = #ssh{role = client}} = State) -> - userauth(Msg, State); -userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_info_request{}, - #state{ssh_params = #ssh{role = client}} = State) -> - userauth_keyboard_interactive(Msg, State). +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}]}; -%%-------------------------------------------------------------------- --spec connected({#ssh_msg_kexinit{}, binary()}, %%| %% #ssh_msg_kexdh_init{}, - #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- -connected({#ssh_msg_kexinit{}, _Payload} = Event, #state{ssh_params = Ssh0} = State0) -> +%%% ######## {connected, client|server} #### + +handle_event(_, {#ssh_msg_kexinit{},_} = Event, {connected,Role}, #state{ssh_params = Ssh0} = State0) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), State = State0#state{ssh_params = Ssh, - key_exchange_init_msg = KeyInitMsg, - renegotiate = true}, - send_msg(SshPacket, State), - kexinit(Event, State). - -%%-------------------------------------------------------------------- --spec handle_event(#ssh_msg_disconnect{} | #ssh_msg_ignore{} | #ssh_msg_debug{} | - #ssh_msg_unimplemented{} | {adjust_window, integer(), integer()} | - {reply_request, success | failure, integer()} | renegotiate | - data_size | {request, pid(), integer(), integer(), iolist()} | - {request, integer(), integer(), iolist()}, state_name(), - #state{}) -> gen_fsm_state_return(). - -%%-------------------------------------------------------------------- -handle_event(#ssh_msg_disconnect{description = Desc} = DisconnectMsg, _StateName, #state{} = State) -> - handle_disconnect(peer, DisconnectMsg, State), - {stop, {shutdown, Desc}, State}; + key_exchange_init_msg = KeyInitMsg}, + send_bytes(SshPacket, State), + {next_state, {kexinit,Role,renegotiate}, State, [{next_event, internal, Event}]}; + +handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, + State0 = #state{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#state.opts), + {stop_and_reply, {shutdown,Desc}, Repls, State}; -handle_event(#ssh_msg_ignore{}, StateName, State) -> - {next_state, StateName, next_packet(State)}; +handle_event(_, #ssh_msg_ignore{}, StateName, State) -> + {next_state, StateName, State}; -handle_event(#ssh_msg_debug{always_display = Display, message = DbgMsg, language=Lang}, - StateName, #state{opts = Opts} = State) -> - F = proplists:get_value(ssh_msg_debug_fun, Opts, +handle_event(_, #ssh_msg_debug{always_display = Display, + message = DbgMsg, + language = Lang}, StateName, #state{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, next_packet(State)}; + {next_state, StateName, State}; + +handle_event(_, #ssh_msg_unimplemented{}, StateName, State) -> + {next_state, StateName, State}; + +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, State) -> + handle_connection_msg(Msg, StateName, State); -handle_event(#ssh_msg_unimplemented{}, StateName, State) -> - {next_state, StateName, next_packet(State)}; +handle_event(internal, Msg=#ssh_msg_request_failure{}, StateName, State) -> + handle_connection_msg(Msg, StateName, State); -handle_event(renegotiate, connected, #state{ssh_params = Ssh0} - = State) -> +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, State) -> + handle_connection_msg(Msg, StateName, State); + +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, State) -> + handle_connection_msg(Msg, StateName, State); + +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, State) -> + handle_connection_msg(Msg, StateName, State); + +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, State) -> + handle_connection_msg(Msg, StateName, State); + +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, State) -> + handle_connection_msg(Msg, StateName, State); + +handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, State) -> + handle_connection_msg(Msg, StateName, State); + +handle_event(cast, renegotiate, {connected,Role}, #state{ssh_params=Ssh0} = State) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), - send_msg(SshPacket, State), - timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]), - {next_state, kexinit, - next_packet(State#state{ssh_params = Ssh, - key_exchange_init_msg = KeyInitMsg, - renegotiate = true})}; - -handle_event(renegotiate, StateName, State) -> + send_bytes(SshPacket, State), +%%% FIXME: timer + timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), + {next_state, {kexinit,Role,renegotiate}, State#state{ssh_params = Ssh, + key_exchange_init_msg = KeyInitMsg}}; + +handle_event(cast, renegotiate, StateName, State) -> %% Already in key-exchange so safe to ignore {next_state, StateName, State}; %% Rekey due to sent data limit reached? -handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) -> +handle_event(cast, data_size, {connected,Role}, #state{ssh_params=Ssh0} = State) -> {ok, [{send_oct,Sent0}]} = inet:getstat(State#state.socket, [send_oct]), Sent = Sent0 - State#state.last_size_rekey, MaxSent = proplists:get_value(rekey_limit, State#state.opts, 1024000000), - timer:apply_after(?REKEY_DATA_TIMOUT, gen_fsm, send_all_state_event, [self(), data_size]), +%%% FIXME: timer + 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_msg(SshPacket, State), - {next_state, kexinit, - next_packet(State#state{ssh_params = Ssh, - key_exchange_init_msg = KeyInitMsg, - renegotiate = true, - last_size_rekey = Sent0})}; + send_bytes(SshPacket, State), + {next_state, {kexinit,Role,renegotiate}, State#state{ssh_params = Ssh, + key_exchange_init_msg = KeyInitMsg, + last_size_rekey = Sent0}}; _ -> - {next_state, connected, next_packet(State)} + {next_state, {connected,Role}, State} end; -handle_event(data_size, StateName, State) -> +handle_event(cast, data_size, StateName, State) -> %% Already in key-exchange so safe to ignore {next_state, StateName, State}; -handle_event(Event, StateName, State) when StateName /= connected -> - Events = [{event, Event} | State#state.event_queue], - {next_state, StateName, State#state{event_queue = Events}}; +handle_event(cast, _, StateName, State) when StateName /= {connected,server}, + StateName /= {connected,client} -> + {next_state, StateName, State, [postpone]}; -handle_event({adjust_window, ChannelId, Bytes}, StateName, +handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName={connected,_Role}, #state{connection_state = #connection{channel_cache = Cache}} = State0) -> - State = - case ssh_channel:cache_lookup(Cache, 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, - Channel#channel{recv_window_pending = Pending + Bytes}), - State0; - - #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, - Channel#channel{recv_window_size = - WinSize + Bytes + Pending, - recv_window_pending = 0}), - Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes + Pending), - send_replies([{connection_reply, Msg}], State0); + case ssh_channel:cache_lookup(Cache, 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, + Channel#channel{recv_window_pending = Pending + Bytes}), + {next_state, StateName, State0}; + + #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, + 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)}; + + undefined -> + {next_state, StateName, State0} + end; - undefined -> - State0 - end, - {next_state, StateName, next_packet(State)}; - -handle_event({reply_request, success, ChannelId}, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> - State = case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = RemoteId} -> - Msg = ssh_connection:channel_success_msg(RemoteId), - send_replies([{connection_reply, Msg}], State0); - undefined -> - State0 - end, - {next_state, StateName, State}; +handle_event(cast, {reply_request,success,ChannelId}, StateName={connected,_}, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = RemoteId} -> + Msg = ssh_connection:channel_success_msg(RemoteId), + {next_state, StateName, send_msg(Msg,State0)}; + + undefined -> + {next_state, StateName, State0} + end; -handle_event({request, ChannelPid, ChannelId, Type, Data}, StateName, State0) -> - {{replies, Replies}, State1} = handle_request(ChannelPid, ChannelId, - Type, Data, - false, none, State0), - State = send_replies(Replies, State1), - {next_state, StateName, next_packet(State)}; +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({request, ChannelId, Type, Data}, StateName, State0) -> - {{replies, Replies}, State1} = handle_request(ChannelId, Type, Data, - false, none, State0), - State = send_replies(Replies, State1), - {next_state, StateName, next_packet(State)}; +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({unknown, Data}, StateName, State) -> +handle_event(cast, {unknown,Data}, StateName={connected,_}, State) -> Msg = #ssh_msg_unimplemented{sequence = Data}, - send_msg(Msg, State), - {next_state, StateName, next_packet(State)}. + {next_state, StateName, send_msg(Msg,State)}; -%%-------------------------------------------------------------------- --spec handle_sync_event({request, pid(), channel_id(), integer(), binary(), timeout()} | - {request, channel_id(), integer(), binary(), timeout()} | - {global_request, pid(), integer(), boolean(), binary()} | {eof, integer()} | - {open, pid(), integer(), channel_id(), integer(), binary(), _} | - {send_window, channel_id()} | {recv_window, channel_id()} | - {connection_info, [client_version | server_version | peer | - sockname]} | {channel_info, channel_id(), [recv_window | - send_window]} | - {close, channel_id()} | stop, term(), state_name(), #state{}) - -> gen_fsm_sync_return(). -%%-------------------------------------------------------------------- -handle_sync_event(get_print_info, _From, StateName, State) -> +%%% Previously handle_sync_event began here +handle_event({call,From}, get_print_info, StateName, State) -> Reply = try {inet:sockname(State#state.socket), @@ -818,25 +776,24 @@ handle_sync_event(get_print_info, _From, StateName, State) -> catch _:_ -> {{"?",0},"?"} end, - {reply, Reply, StateName, State}; + {next_state, StateName, State, [{reply,From,Reply}]}; -handle_sync_event({connection_info, Options}, _From, StateName, State) -> +handle_event({call,From}, {connection_info, Options}, StateName, State) -> Info = ssh_info(Options, State, []), - {reply, Info, StateName, State}; + {next_state, StateName, State, [{reply,From,Info}]}; -handle_sync_event({channel_info, ChannelId, Options}, _From, StateName, - #state{connection_state = #connection{channel_cache = Cache}} = State) -> +handle_event({call,From}, {channel_info,ChannelId,Options}, StateName, + State=#state{connection_state = #connection{channel_cache = Cache}}) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{} = Channel -> Info = ssh_channel_info(Options, Channel, []), - {reply, Info, StateName, State}; + {next_state, StateName, State, [{reply,From,Info}]}; undefined -> - {reply, [], StateName, State} + {next_state, StateName, State, [{reply,From,[]}]} end; -handle_sync_event({info, ChannelPid}, _From, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State) -> +handle_event({call,From}, {info, ChannelPid}, StateName, State = #state{connection_state = + #connection{channel_cache = Cache}}) -> Result = ssh_channel:cache_foldl( fun(Channel, Acc) when ChannelPid == all; Channel#channel.user == ChannelPid -> @@ -844,86 +801,74 @@ handle_sync_event({info, ChannelPid}, _From, StateName, (_, Acc) -> Acc end, [], Cache), - {reply, {ok, Result}, StateName, State}; + {next_state, StateName, State, [{reply, From, {ok,Result}}]}; -handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0, - role = Role} = State0) -> +handle_event({call,From}, stop, StateName, #state{connection_state = Connection0} = State0) -> {disconnect, _Reason, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "User closed down connection", - language = "en"}, Connection0, Role), - State = send_replies(Replies, State0), - {stop, normal, ok, State#state{connection_state = Connection}}; - + description = "User closed down connection"}, + Connection0, role(StateName)), + {Repls,State} = send_replies(Replies, State0), + {stop_and_reply, normal, [{reply,From,ok}|Repls], State#state{connection_state=Connection}}; -handle_sync_event(Event, From, StateName, State) when StateName /= connected -> - Events = [{sync, Event, From} | State#state.event_queue], - {next_state, StateName, State#state{event_queue = Events}}; +handle_event({call,_}, _, StateName, State) when StateName /= {connected,server}, + StateName /= {connected,client} -> + {next_state, StateName, State, [postpone]}; -handle_sync_event({request, ChannelPid, ChannelId, Type, Data, Timeout}, From, StateName, State0) -> - {{replies, Replies}, State1} = handle_request(ChannelPid, - ChannelId, Type, Data, - true, From, State0), +handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName={connected,_}, State0) -> + State = handle_request(ChannelPid, ChannelId, Type, Data, true, From, State0), %% Note reply to channel will happen later when %% reply is recived from peer on the socket - State = send_replies(Replies, State1), start_timeout(ChannelId, From, Timeout), handle_idle_timeout(State), - {next_state, StateName, next_packet(State)}; + {next_state, StateName, State}; -handle_sync_event({request, ChannelId, Type, Data, Timeout}, From, StateName, State0) -> - {{replies, Replies}, State1} = handle_request(ChannelId, Type, Data, - true, From, State0), +handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName={connected,_}, State0) -> + State = handle_request(ChannelId, Type, Data, true, From, State0), %% Note reply to channel will happen later when %% reply is recived from peer on the socket - State = send_replies(Replies, State1), start_timeout(ChannelId, From, Timeout), handle_idle_timeout(State), - {next_state, StateName, next_packet(State)}; + {next_state, StateName, State}; -handle_sync_event({global_request, Pid, _, _, _} = Request, From, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> +handle_event({call,From}, {global_request, Pid, _, _, _} = Request, StateName={connected,_}, + #state{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, next_packet(State)}; - -handle_sync_event({data, ChannelId, Type, Data, Timeout}, From, StateName, - #state{connection_state = #connection{channel_cache = _Cache} - = Connection0} = State0) -> + {next_state, StateName, State}; +handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName={connected,_}, + #state{connection_state = #connection{channel_cache=_Cache} = Connection0} = State0) -> case ssh_connection:channel_data(ChannelId, Type, Data, Connection0, From) of {{replies, Replies}, Connection} -> - State = send_replies(Replies, State0#state{connection_state = Connection}), + {Repls,State} = send_replies(Replies, State0#state{connection_state = Connection}), start_timeout(ChannelId, From, Timeout), - {next_state, StateName, next_packet(State)}; + {next_state, StateName, State, Repls}; {noreply, Connection} -> start_timeout(ChannelId, From, Timeout), - {next_state, StateName, next_packet(State0#state{connection_state = Connection})} + {next_state, StateName, State0#state{connection_state = Connection}} end; -handle_sync_event({eof, ChannelId}, _From, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> +handle_event({call,From}, {eof, ChannelId}, StateName={connected,_}, + #state{connection_state = #connection{channel_cache=Cache}} = State0) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id, sent_close = false} -> - State = send_replies([{connection_reply, - ssh_connection:channel_eof_msg(Id)}], State0), - {reply, ok, StateName, next_packet(State)}; + State = send_msg(ssh_connection:channel_eof_msg(Id), State0), + {next_state, StateName, State, [{reply,From,ok}]}; _ -> - {reply, {error,closed}, StateName, State0} + {next_state, StateName, State0, [{reply,From,{error,closed}}]} end; -handle_sync_event({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, - From, StateName, #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> +handle_event({call,From}, {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, + StateName={connected,_}, + #state{connection_state = #connection{channel_cache = Cache}} = State0) -> erlang:monitor(process, ChannelPid), {ChannelId, State1} = new_channel_id(State0), Msg = ssh_connection:channel_open_msg(Type, ChannelId, InitialWindowSize, MaxPacketSize, Data), - State2 = send_replies([{connection_reply, Msg}], State1), + State2 = send_msg(Msg, State1), Channel = #channel{type = Type, sys = "none", user = ChannelPid, @@ -935,11 +880,10 @@ handle_sync_event({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Dat ssh_channel:cache_update(Cache, Channel), State = add_request(true, ChannelId, From, State2), start_timeout(ChannelId, From, Timeout), - {next_state, StateName, next_packet(remove_timer_ref(State))}; + {next_state, StateName, remove_timer_ref(State)}; -handle_sync_event({send_window, ChannelId}, _From, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State) -> +handle_event({call,From}, {send_window, ChannelId}, StateName={connected,_}, + #state{connection_state = #connection{channel_cache = Cache}} = State) -> Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{send_window_size = WinSize, send_packet_size = Packsize} -> @@ -947,12 +891,10 @@ handle_sync_event({send_window, ChannelId}, _From, StateName, undefined -> {error, einval} end, - {reply, Reply, StateName, next_packet(State)}; - -handle_sync_event({recv_window, ChannelId}, _From, StateName, - #state{connection_state = #connection{channel_cache = Cache}} - = State) -> + {next_state, StateName, State, [{reply,From,Reply}]}; +handle_event({call,From}, {recv_window, ChannelId}, StateName={connected,_}, + #state{connection_state = #connection{channel_cache = Cache}} = State) -> Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{recv_window_size = WinSize, recv_packet_size = Packsize} -> @@ -960,127 +902,145 @@ handle_sync_event({recv_window, ChannelId}, _From, StateName, undefined -> {error, einval} end, - {reply, Reply, StateName, next_packet(State)}; - -handle_sync_event({close, ChannelId}, _, StateName, - #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> - State = - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = Id} = Channel -> - State1 = send_replies([{connection_reply, - ssh_connection:channel_close_msg(Id)}], State0), - ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}), - handle_idle_timeout(State1), - State1; - undefined -> - State0 - end, - {reply, ok, StateName, next_packet(State)}. + {next_state, StateName, State, [{reply,From,Reply}]}; -%%-------------------------------------------------------------------- --spec handle_info({atom(), port(), binary()} | {atom(), port()} | - term (), state_name(), #state{}) -> gen_fsm_state_return(). -%%-------------------------------------------------------------------- +handle_event({call,From}, {close, ChannelId}, StateName={connected,_}, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + case ssh_channel:cache_lookup(Cache, 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}]}; + undefined -> + {next_state, StateName, State0, [{reply,From,ok}]} + end; -handle_info({Protocol, Socket, "SSH-" ++ _ = Version}, hello, - #state{socket = Socket, - transport_protocol = Protocol} = State ) -> - event({version_exchange, Version}, hello, State); - -handle_info({Protocol, Socket, Info}, hello, - #state{socket = Socket, - transport_protocol = Protocol} = State) -> - event({info_line, Info}, hello, State); - -handle_info({Protocol, Socket, Data}, StateName, - #state{socket = Socket, - transport_protocol = Protocol, - ssh_params = Ssh0, - decoded_data_buffer = DecData0, - encoded_data_buffer = EncData0, - undecoded_packet_length = RemainingSshPacketLen0} = State0) -> +handle_event(info, {Protocol, Socket, "SSH-" ++ _ = Version}, StateName={hello,_}, + State=#state{socket = Socket, + transport_protocol = Protocol}) -> + {next_state, StateName, State, [{next_event, internal, {version_exchange,Version}}]}; + +handle_event(info, {Protocol, Socket, Info}, StateName={hello,_}, + State=#state{socket = Socket, + transport_protocol = Protocol}) -> + {next_state, StateName, State, [{next_event, internal, {info_line,Info}}]}; + +handle_event(info, {Protocol, Socket, Data}, StateName, State0 = + #state{socket = Socket, + transport_protocol = Protocol, + decoded_data_buffer = DecData0, + encoded_data_buffer = EncData0, + undecoded_packet_length = RemainingSshPacketLen0, + ssh_params = Ssh0}) -> +?IO_FORMAT('~p Recv tcp~n',[self()]), Encoded = <>, - try ssh_transport:handle_packet_part(DecData0, Encoded, RemainingSshPacketLen0, Ssh0) + try ssh_transport:handle_packet_part(DecData0, Encoded, RemainingSshPacketLen0, Ssh0) of + {decoded, Bytes, EncDataRest, Ssh1} -> + State = State0#state{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}, + try + ssh_message:decode(set_prefix_if_trouble(Bytes,State)) + of + Msg = #ssh_msg_kexinit{} -> + {next_state, StateName, State, [{next_event, internal, {Msg,Bytes}}, + {next_event, internal, prepare_next_packet} + ]}; + Msg -> + {next_state, StateName, State, [{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) + end; + {get_more, DecBytes, EncDataRest, RemainingSshPacketLen, Ssh1} -> - {next_state, StateName, - next_packet(State0#state{encoded_data_buffer = EncDataRest, - decoded_data_buffer = DecBytes, - undecoded_packet_length = RemainingSshPacketLen, - ssh_params = Ssh1})}; - {decoded, MsgBytes, EncDataRest, Ssh1} -> - generate_event(MsgBytes, StateName, - State0#state{ssh_params = Ssh1, - %% Important to be set for - %% next_packet -%%% FIXME: the following three seem to always be set in generate_event! - decoded_data_buffer = <<>>, - undecoded_packet_length = undefined, - encoded_data_buffer = EncDataRest}, - EncDataRest); + %% Here we know that there are not enough bytes in EncDataRest to use. Must wait. + inet:setopts(Socket, [{active, once}]), + {next_state, StateName, State0#state{encoded_data_buffer = EncDataRest, + decoded_data_buffer = DecBytes, + undecoded_packet_length = RemainingSshPacketLen, + ssh_params = Ssh1}}; + {bad_mac, Ssh1} -> - DisconnectMsg = + DisconnectMsg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad mac", - language = ""}, - handle_disconnect(DisconnectMsg, State0#state{ssh_params=Ssh1}); + description = "Bad mac"}, + disconnect(DisconnectMsg, StateName, State0#state{ssh_params=Ssh1}); {error, {exceeds_max_size,PacketLen}} -> - DisconnectMsg = + DisconnectMsg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet length " - ++ integer_to_list(PacketLen), - language = ""}, - handle_disconnect(DisconnectMsg, State0) + description = "Bad packet length " + ++ integer_to_list(PacketLen)}, + disconnect(DisconnectMsg, StateName, State0) catch - _:_ -> - DisconnectMsg = + _C:_E -> + DisconnectMsg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet", - language = ""}, - handle_disconnect(DisconnectMsg, State0) + description = "Bad packet"}, + disconnect(DisconnectMsg, StateName, State0) end; - -handle_info({CloseTag, _Socket}, _StateName, - #state{transport_close_tag = CloseTag, - ssh_params = #ssh{role = _Role, opts = _Opts}} = State) -> - DisconnectMsg = + +handle_event(internal, prepare_next_packet, StateName, State) -> + Enough = erlang:max(8, State#state.ssh_params#ssh.decrypt_block_size), + case size(State#state.encoded_data_buffer) of + Sz when Sz >= Enough -> +?IO_FORMAT('~p Send <<>> to self~n',[self()]), + self() ! {State#state.transport_protocol, State#state.socket, <<>>}; + _ -> +?IO_FORMAT('~p Set active_once~n',[self()]), + inet:setopts(State#state.socket, [{active, once}]) + end, + {next_state, StateName, State}; + +handle_event(info, {CloseTag,Socket}, StateName, + State=#state{socket = Socket, + transport_close_tag = CloseTag}) -> + DisconnectMsg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Connection closed", - language = "en"}, - handle_disconnect(DisconnectMsg, State); + description = "Connection closed"}, + disconnect(DisconnectMsg, StateName, State); -handle_info({timeout, {_, From} = Request}, Statename, +handle_event(info, {timeout, {_, From} = Request}, StateName, #state{connection_state = #connection{requests = Requests} = Connection} = State) -> case lists:member(Request, Requests) of true -> - gen_fsm:reply(From, {error, timeout}), - {next_state, Statename, + {next_state, StateName, State#state{connection_state = Connection#connection{requests = - lists:delete(Request, Requests)}}}; + lists:delete(Request, Requests)}}, + [{reply,From,{error,timeout}}]}; false -> - {next_state, Statename, State} + {next_state, StateName, State} end; %%% Handle that ssh channels user process goes down -handle_info({'DOWN', _Ref, process, ChannelPid, _Reason}, Statename, State0) -> +handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, StateName, State0) -> {{replies, Replies}, State1} = handle_channel_down(ChannelPid, State0), - State = send_replies(Replies, State1), - {next_state, Statename, next_packet(State)}; + {Repls, State} = send_replies(Replies, State1), + {next_state, StateName, State, Repls}; %%% So that terminate will be run when supervisor is shutdown -handle_info({'EXIT', _Sup, Reason}, _StateName, State) -> - {stop, {shutdown, Reason}, State}; +handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> + {stop, {shutdown, Reason}}; -handle_info({check_cache, _ , _}, - StateName, #state{connection_state = - #connection{channel_cache = Cache}} = State) -> +handle_event(info, {check_cache, _ , _}, StateName, + #state{connection_state = #connection{channel_cache=Cache}} = State) -> {next_state, StateName, check_cache(State, Cache)}; -handle_info(UnexpectedMessage, StateName, #state{opts = Opts, - ssh_params = SshParams} = State) -> +handle_event(info, UnexpectedMessage, StateName, + State = #state{opts = Opts, + ssh_params = SshParams}) -> case unexpected_fun(UnexpectedMessage, Opts, SshParams) of report -> Msg = lists:flatten( @@ -1091,10 +1051,11 @@ handle_info(UnexpectedMessage, StateName, #state{opts = Opts, "Local Address: ~p\n", [UnexpectedMessage, StateName, SshParams#ssh.role, SshParams#ssh.peer, proplists:get_value(address, SshParams#ssh.opts)])), - error_logger:info_report(Msg); + error_logger:info_report(Msg), + {next_state, StateName, State}; skip -> - ok; + {next_state, StateName, State}; Other -> Msg = lists:flatten( @@ -1103,60 +1064,78 @@ handle_info(UnexpectedMessage, StateName, #state{opts = Opts, "Message: ~p\n" "Role: ~p\n" "Peer: ~p\n" - "Local Address: ~p\n", [Other, UnexpectedMessage, - SshParams#ssh.role, + "Local Address: ~p\n", [Other, UnexpectedMessage, + SshParams#ssh.role, element(2,SshParams#ssh.peer), proplists:get_value(address, SshParams#ssh.opts)] )), + error_logger:error_report(Msg), + {next_state, StateName, State} + end; - error_logger:error_report(Msg) - end, - {next_state, StateName, State}. +handle_event(internal, {disconnect,Msg,_Reason}, StateName, State) -> + disconnect(Msg, StateName, State); + +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. %%-------------------------------------------------------------------- --spec terminate(Reason::term(), state_name(), #state{}) -> _. -%%-------------------------------------------------------------------- -terminate(normal, _, #state{transport_cb = Transport, - connection_state = Connection, - socket = Socket}) -> - terminate_subsystem(Connection), - (catch Transport:close(Socket)), - ok; +terminate(normal, StateName, State) -> + ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,normal,StateName]), + normal_termination(StateName, State); terminate({shutdown,{init,Reason}}, StateName, State) -> + ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,{shutdown,{init,Reason}},StateName]), error_logger:info_report(io_lib:format("Erlang ssh in connection handler init: ~p~n",[Reason])), - terminate(normal, StateName, State); + normal_termination(StateName, State); + +terminate(shutdown, StateName, State) -> + %% Terminated by supervisor + ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,shutdown,StateName]), + normal_termination(StateName, + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "Application shutdown"}, + State); + +%% terminate({shutdown,Msg}, StateName, State) when is_record(Msg,ssh_msg_disconnect)-> +%% ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,{shutdown,Msg},StateName]), +%% normal_termination(StateName, Msg, State); + +terminate({shutdown,_R}, StateName, State) -> + ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,{shutdown,_R},StateName]), + normal_termination(StateName, State); + +terminate(Reason, StateName, State) -> + %% Others, e.g undef, {badmatch,_} + ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,Reason,StateName]), + log_error(Reason), + normal_termination(StateName, + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "Internal error"}, + State). -%% Terminated by supervisor -terminate(shutdown, StateName, #state{ssh_params = Ssh0} = State) -> - DisconnectMsg = - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Application shutdown", - language = "en"}, - {SshPacket, Ssh} = ssh_transport:ssh_packet(DisconnectMsg, Ssh0), - send_msg(SshPacket, State), - terminate(normal, StateName, State#state{ssh_params = Ssh}); - -terminate({shutdown, #ssh_msg_disconnect{} = Msg}, StateName, - #state{ssh_params = Ssh0} = State) -> - {SshPacket, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), - send_msg(SshPacket, State), - terminate(normal, StateName, State#state{ssh_params = Ssh}); - -terminate({shutdown, _}, StateName, State) -> - terminate(normal, StateName, State); - -terminate(Reason, StateName, #state{ssh_params = Ssh0, starter = _Pid, - connection_state = Connection} = State) -> + +normal_termination(StateName, Msg, State0) -> + State = send_msg(Msg,State0), +timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead + normal_termination(StateName, State). + +normal_termination(_StateName, #state{transport_cb = Transport, + connection_state = Connection, + socket = Socket}) -> + ?IO_FORMAT('~p ~p:~p normal_termination in state ~p~n',[self(),?MODULE,?LINE,_StateName]), terminate_subsystem(Connection), - log_error(Reason), - DisconnectMsg = - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Internal error", - language = "en"}, - {SshPacket, Ssh} = ssh_transport:ssh_packet(DisconnectMsg, Ssh0), - send_msg(SshPacket, State), - terminate(normal, StateName, State#state{ssh_params = Ssh}). + (catch Transport:close(Socket)), + ok. terminate_subsystem(#connection{system_supervisor = SysSup, @@ -1165,9 +1144,10 @@ terminate_subsystem(#connection{system_supervisor = SysSup, terminate_subsystem(_) -> ok. -format_status(normal, [_, State]) -> - [{data, [{"StateData", State}]}]; -format_status(terminate, [_, State]) -> + +format_status(normal, [_, _StateName, State]) -> + [{data, [{"State", State}]}]; +format_status(terminate, [_, _StateName, State]) -> SshParams0 = (State#state.ssh_params), SshParams = SshParams0#ssh{c_keyinit = "***", s_keyinit = "***", @@ -1183,37 +1163,44 @@ format_status(terminate, [_, State]) -> decompress_ctx = "***", shared_secret = "***", exchanged_hash = "***", - session_id = "***", - keyex_key = "***", - keyex_info = "***", + session_id = "***", + keyex_key = "***", + keyex_info = "***", available_host_keys = "***"}, - [{data, [{"StateData", State#state{decoded_data_buffer = "***", - encoded_data_buffer = "***", - key_exchange_init_msg = "***", - opts = "***", - recbuf = "***", - ssh_params = SshParams - }}]}]. + [{data, [{"State", State#state{decoded_data_buffer = "***", + encoded_data_buffer = "***", + key_exchange_init_msg = "***", + opts = "***", + recbuf = "***", + ssh_params = SshParams + }}]}]. + -%%-------------------------------------------------------------------- --spec code_change(OldVsn::term(), state_name(), Oldstate::term(), Extra::term()) -> - {ok, state_name(), #state{}}. -%%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -init_role(#state{role = client, opts = Opts} = State0) -> + +%% StateName to Role +role({_,Role}) -> Role; +role({_,Role,_}) -> Role. + +renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; +renegotiation(_) -> false. + + + +init_role(client, #state{opts = Opts} = State0) -> Pid = proplists:get_value(user_pid, Opts), TimerRef = get_idle_time(Opts), - timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]), - timer:apply_after(?REKEY_DATA_TIMOUT, gen_fsm, send_all_state_event, + timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), + timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), State0#state{starter = Pid, idle_timer_ref = TimerRef}; -init_role(#state{role = server, opts = Opts, connection_state = Connection} = State) -> +init_role(server, #state{opts = Opts, connection_state = Connection} = State) -> Sups = proplists:get_value(supervisors, Opts), Pid = proplists:get_value(user_pid, Opts), SystemSup = proplists:get_value(system_sup, Sups), @@ -1240,16 +1227,16 @@ get_idle_time(SshOptions) -> init_ssh(client = Role, Vsn, Version, Options, Socket) -> IOCb = case proplists:get_value(user_interaction, Options, true) of - true -> + true -> ssh_io; - false -> + false -> ssh_no_io end, - AuthMethods = proplists:get_value(auth_methods, Options, + AuthMethods = proplists:get_value(auth_methods, Options, ?SUPPORTED_AUTH_METHODS), {ok, PeerAddr} = inet:peername(Socket), - + PeerName = proplists:get_value(host, Options), KeyCb = proplists:get_value(key_cb, Options, ssh_file), @@ -1263,13 +1250,13 @@ init_ssh(client = Role, Vsn, Version, Options, Socket) -> userauth_supported_methods = AuthMethods, peer = {PeerName, PeerAddr}, available_host_keys = supported_host_keys(Role, KeyCb, Options), - random_length_padding = proplists:get_value(max_random_length_padding, - Options, + random_length_padding = proplists:get_value(max_random_length_padding, + Options, (#ssh{})#ssh.random_length_padding) }; init_ssh(server = Role, Vsn, Version, Options, Socket) -> - AuthMethods = proplists:get_value(auth_methods, Options, + AuthMethods = proplists:get_value(auth_methods, Options, ?SUPPORTED_AUTH_METHODS), AuthMethodsAsList = string:tokens(AuthMethods, ","), {ok, PeerAddr} = inet:peername(Socket), @@ -1286,17 +1273,17 @@ init_ssh(server = Role, Vsn, Version, Options, Socket) -> kb_tries_left = 3, peer = {undefined, PeerAddr}, available_host_keys = supported_host_keys(Role, KeyCb, Options), - random_length_padding = proplists:get_value(max_random_length_padding, - Options, + random_length_padding = proplists:get_value(max_random_length_padding, + Options, (#ssh{})#ssh.random_length_padding) }. supported_host_keys(client, _, Options) -> try - case proplists:get_value(public_key, + case proplists:get_value(public_key, proplists:get_value(preferred_algorithms,Options,[]) ) of - undefined -> + undefined -> ssh_transport:default_algorithms(public_key); L -> L -- (L--ssh_transport:default_algorithms(public_key)) @@ -1311,7 +1298,7 @@ supported_host_keys(client, _, Options) -> {stop, {shutdown, Reason}} end; supported_host_keys(server, KeyCb, Options) -> - [atom_to_list(A) || A <- proplists:get_value(public_key, + [atom_to_list(A) || A <- proplists:get_value(public_key, proplists:get_value(preferred_algorithms,Options,[]), ssh_transport:default_algorithms(public_key) ), @@ -1322,10 +1309,19 @@ supported_host_keys(server, KeyCb, Options) -> available_host_key(KeyCb, Alg, Opts) -> element(1, catch KeyCb:host_key(Alg, Opts)) == ok. -send_msg(Msg, #state{socket = Socket, transport_cb = Transport}) -> - Transport:send(Socket, Msg). -handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> +send_msg(Msg, State=#state{ssh_params=Ssh0}) when is_tuple(Msg) -> + {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), + send_bytes(Bytes, State), + State#state{ssh_params=Ssh}. + +send_bytes(Bytes, #state{socket = Socket, transport_cb = Transport}) -> + R = Transport:send(Socket, Bytes), +?IO_FORMAT('~p send_bytes ~p~n',[self(),R]), + R. + + +handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), {ok, Ssh}; handle_version(_,_,_) -> @@ -1336,161 +1332,89 @@ string_version(#ssh{role = client, c_version = Vsn}) -> string_version(#ssh{role = server, s_version = Vsn}) -> Vsn. -send_event(FsmPid, Event) -> - gen_fsm:send_event(FsmPid, Event). -send_all_state_event(FsmPid, Event) -> - gen_fsm:send_all_state_event(FsmPid, Event). +cast(FsmPid, Event) -> + gen_statem:cast(FsmPid, Event). -sync_send_all_state_event(FsmPid, Event) -> - sync_send_all_state_event(FsmPid, Event, infinity). +call(FsmPid, Event) -> + call(FsmPid, Event, infinity). -sync_send_all_state_event(FsmPid, Event, Timeout) -> - try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout) of - {closed, _Channel} -> +call(FsmPid, Event, Timeout) -> + try gen_statem:call(FsmPid, Event, Timeout) of + {closed, _R} -> + {error, closed}; + {killed, _R} -> {error, closed}; Result -> Result catch - exit:{noproc, _} -> + exit:{noproc, _R} -> {error, closed}; - exit:{normal, _} -> + exit:{normal, _R} -> {error, closed}; - exit:{{shutdown, _},_} -> + exit:{{shutdown, _R},_} -> {error, closed} end. -%% simulate send_all_state_event(self(), Event) -event(#ssh_msg_disconnect{} = Event, StateName, State) -> - handle_event(Event, StateName, State); -event(#ssh_msg_ignore{} = Event, StateName, State) -> - handle_event(Event, StateName, State); -event(#ssh_msg_debug{} = Event, StateName, State) -> - handle_event(Event, StateName, State); -event(#ssh_msg_unimplemented{} = Event, StateName, State) -> - handle_event(Event, StateName, State); -%% simulate send_event(self(), Event) -event(Event, StateName, State) -> - try - ?MODULE:StateName(Event, State) + +handle_connection_msg(Msg, StateName, State0 = + #state{starter = User, + connection_state = Connection0, + event_queue = Qev0}) -> + Renegotiation = renegotiation(StateName), + Role = role(StateName), + try ssh_connection:handle_msg(Msg, Connection0, Role) of + {{replies, Replies}, Connection} -> + case StateName of + {connected,_} -> + {Repls, State} = send_replies(Replies, + State0#state{connection_state=Connection}), + {next_state, StateName, State, Repls}; + _ -> + {ConnReplies, Replies} = + lists:splitwith(fun not_connected_filter/1, Replies), + {Repls, State} = send_replies(Replies, + State0#state{event_queue = Qev0 ++ ConnReplies}), + {next_state, StateName, State, Repls} + end; + + {noreply, Connection} -> + {next_state, StateName, State0#state{connection_state = Connection}}; + + {disconnect, Reason0, {{replies, Replies}, Connection}} -> + {Repls,State} = send_replies(Replies, State0#state{connection_state = Connection}), + case {Reason0,Role} of + {{_, Reason}, client} when ((StateName =/= {connected,client}) and (not Renegotiation)) -> + User ! {self(), not_connected, Reason}; + _ -> + ok + end, + {stop, {shutdown,normal}, Repls, State#state{connection_state = Connection}} + catch - throw:#ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - throw:{ErrorToDisplay, #ssh_msg_disconnect{} = DisconnectMsg} -> - handle_disconnect(DisconnectMsg, State, ErrorToDisplay); - _C:_Error -> - handle_disconnect(#ssh_msg_disconnect{code = error_code(StateName), - description = "Invalid state", - language = "en"}, State) + _:Error -> + {disconnect, _Reason, {{replies, Replies}, Connection}} = + ssh_connection:handle_msg( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "Internal error"}, + Connection0, Role), + {Repls,State} = send_replies(Replies, State0#state{connection_state = Connection}), + {stop, {shutdown,Error}, Repls, State#state{connection_state = Connection}} end. -error_code(key_exchange) -> - ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED; -error_code(new_keys) -> - ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED; -error_code(_) -> - ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE. - -generate_event(<> = Msg, StateName, - #state{ - role = Role, - starter = User, - renegotiate = Renegotiation, - connection_state = Connection0} = State0, EncData) - when Byte == ?SSH_MSG_GLOBAL_REQUEST; - Byte == ?SSH_MSG_REQUEST_SUCCESS; - Byte == ?SSH_MSG_REQUEST_FAILURE; - Byte == ?SSH_MSG_CHANNEL_OPEN; - Byte == ?SSH_MSG_CHANNEL_OPEN_CONFIRMATION; - Byte == ?SSH_MSG_CHANNEL_OPEN_FAILURE; - Byte == ?SSH_MSG_CHANNEL_WINDOW_ADJUST; - Byte == ?SSH_MSG_CHANNEL_DATA; - Byte == ?SSH_MSG_CHANNEL_EXTENDED_DATA; - Byte == ?SSH_MSG_CHANNEL_EOF; - Byte == ?SSH_MSG_CHANNEL_CLOSE; - Byte == ?SSH_MSG_CHANNEL_REQUEST; - Byte == ?SSH_MSG_CHANNEL_SUCCESS; - Byte == ?SSH_MSG_CHANNEL_FAILURE -> - try - ssh_message:decode(Msg) - of - ConnectionMsg -> - State1 = generate_event_new_state(State0, EncData), - try ssh_connection:handle_msg(ConnectionMsg, Connection0, Role) of - {{replies, Replies0}, Connection} -> - if StateName == connected -> - Replies = Replies0, - State2 = State1; - true -> - {ConnReplies, Replies} = - lists:splitwith(fun not_connected_filter/1, Replies0), - Q = State1#state.event_queue ++ ConnReplies, - State2 = State1#state{ event_queue = Q } - end, - State = send_replies(Replies, State2#state{connection_state = Connection}), - {next_state, StateName, next_packet(State)}; - {noreply, Connection} -> - {next_state, StateName, next_packet(State1#state{connection_state = Connection})}; - {disconnect, {_, Reason}, {{replies, Replies}, Connection}} when - Role == client andalso ((StateName =/= connected) and (not Renegotiation)) -> - State = send_replies(Replies, State1#state{connection_state = Connection}), - User ! {self(), not_connected, Reason}, - {stop, {shutdown, normal}, - next_packet(State#state{connection_state = Connection})}; - {disconnect, _Reason, {{replies, Replies}, Connection}} -> - State = send_replies(Replies, State1#state{connection_state = Connection}), - {stop, {shutdown, normal}, State#state{connection_state = Connection}} - catch - _:Error -> - {disconnect, _Reason, {{replies, Replies}, Connection}} = - ssh_connection:handle_msg( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Internal error", - language = "en"}, Connection0, Role), - State = send_replies(Replies, State1#state{connection_state = Connection}), - {stop, {shutdown, Error}, State#state{connection_state = Connection}} - end - catch - _:_ -> - handle_disconnect( - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad packet received", - language = ""}, State0) - end; -generate_event(Msg, StateName, State0, EncData) -> - try - Event = ssh_message:decode(set_prefix_if_trouble(Msg,State0)), - State = generate_event_new_state(State0, EncData), - case Event of - #ssh_msg_kexinit{} -> - %% We need payload for verification later. - event({Event, Msg}, StateName, State); - _ -> - event(Event, StateName, State) - end - catch - _C:_E -> - DisconnectMsg = - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Encountered unexpected input", - language = "en"}, - handle_disconnect(DisconnectMsg, State0) - end. - - -set_prefix_if_trouble(Msg = <>, #state{ssh_params=SshParams}) +set_prefix_if_trouble(Msg = <>, #state{ssh_params=SshParams}) when Op == 30; Op == 31 -> case catch atom_to_list(kex(SshParams)) of - "ecdh-sha2-" ++ _ -> + "ecdh-sha2-" ++ _ -> <<"ecdh",Msg/binary>>; "diffie-hellman-group-exchange-" ++ _ -> <<"dh_gex",Msg/binary>>; "diffie-hellman-group" ++ _ -> <<"dh",Msg/binary>>; - _ -> + _ -> Msg end; set_prefix_if_trouble(Msg, _) -> @@ -1499,7 +1423,7 @@ set_prefix_if_trouble(Msg, _) -> kex(#ssh{algorithms=#alg{kex=Kex}}) -> Kex; kex(_) -> undefined. - +%%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, #state{connection_state = #connection{channel_cache = Cache}} = State0) -> @@ -1508,11 +1432,9 @@ handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, update_sys(Cache, Channel, Type, ChannelPid), Msg = ssh_connection:channel_request_msg(Id, Type, WantReply, Data), - Replies = [{connection_reply, Msg}], - State = add_request(WantReply, ChannelId, From, State0), - {{replies, Replies}, State}; + send_msg(Msg, add_request(WantReply, ChannelId, From, State0)); undefined -> - {{replies, []}, State0} + State0 end. handle_request(ChannelId, Type, Data, WantReply, From, @@ -1522,13 +1444,12 @@ handle_request(ChannelId, Type, Data, WantReply, From, #channel{remote_id = Id} -> Msg = ssh_connection:channel_request_msg(Id, Type, WantReply, Data), - Replies = [{connection_reply, Msg}], - State = add_request(WantReply, ChannelId, From, State0), - {{replies, Replies}, State}; + send_msg(Msg, add_request(WantReply, ChannelId, From, State0)); undefined -> - {{replies, []}, State0} + State0 end. +%%%---------------------------------------------------------------- handle_global_request({global_request, ChannelPid, "tcpip-forward" = Type, WantReply, < Connection = ssh_connection:unbind(IP, Port, Connection0), Msg = ssh_connection:global_request_msg(Type, WantReply, Data), - send_replies([{connection_reply, Msg}], State#state{connection_state = Connection}); + send_msg(Msg, State#state{connection_state = Connection}); handle_global_request({global_request, _, "cancel-tcpip-forward" = Type, WantReply, Data}, State) -> Msg = ssh_connection:global_request_msg(Type, WantReply, Data), - send_replies([{connection_reply, Msg}], State). + send_msg(Msg, State). +%%%---------------------------------------------------------------- handle_idle_timeout(#state{opts = Opts}) -> case proplists:get_value(idle_time, Opts, infinity) of infinity -> @@ -1594,21 +1516,10 @@ new_channel_id(#state{connection_state = #connection{channel_id_seed = Id} = {Id, State#state{connection_state = Connection#connection{channel_id_seed = Id + 1}}}. -generate_event_new_state(#state{ssh_params = - #ssh{recv_sequence = SeqNum0} - = Ssh} = State, EncData) -> - SeqNum = ssh_transport:next_seqnum(SeqNum0), - State#state{ssh_params = Ssh#ssh{recv_sequence = SeqNum}, - decoded_data_buffer = <<>>, - encoded_data_buffer = EncData, - undecoded_packet_length = undefined}. - -next_packet(#state{decoded_data_buffer = <<>>, - encoded_data_buffer = Buff, - ssh_params = #ssh{decrypt_block_size = BlockSize}, - socket = Socket, - transport_protocol = Protocol} = State) when Buff =/= <<>> -> - case size(Buff) >= erlang:max(8, BlockSize) of +prepare_for_next_packet(State = #state{transport_protocol = Protocol, + socket = Socket}, + Ssh, EncDataRest) -> + case size(EncDataRest) >= erlang:max(8, Ssh#ssh.decrypt_block_size) of true -> %% Enough data from the next packet has been received to %% decode the length indicator, fake a socket-recive @@ -1617,84 +1528,37 @@ next_packet(#state{decoded_data_buffer = <<>>, false -> inet:setopts(Socket, [{active, once}]) end, - State; - -next_packet(#state{socket = Socket} = State) -> - inet:setopts(Socket, [{active, once}]), - State. - -after_new_keys(#state{renegotiate = true} = State) -> - State1 = State#state{renegotiate = false, event_queue = []}, - lists:foldr(fun after_new_keys_events/2, {next_state, connected, State1}, State#state.event_queue); -after_new_keys(#state{renegotiate = false, - ssh_params = #ssh{role = client} = Ssh0} = State) -> - {Msg, Ssh} = ssh_auth:service_request_msg(Ssh0), - send_msg(Msg, State), - {next_state, service_request, State#state{ssh_params = Ssh}}; -after_new_keys(#state{renegotiate = false, - ssh_params = #ssh{role = server}} = State) -> - {next_state, service_request, State}. - -after_new_keys_events({sync, _Event, From}, {stop, _Reason, _StateData}=Terminator) -> - gen_fsm:reply(From, {error, closed}), - Terminator; -after_new_keys_events(_, {stop, _Reason, _StateData}=Terminator) -> - Terminator; -after_new_keys_events({sync, Event, From}, {next_state, StateName, StateData}) -> - case handle_sync_event(Event, From, StateName, StateData) of - {reply, Reply, NextStateName, NewStateData} -> - gen_fsm:reply(From, Reply), - {next_state, NextStateName, NewStateData}; - {next_state, NextStateName, NewStateData}-> - {next_state, NextStateName, NewStateData}; - {stop, Reason, Reply, NewStateData} -> - gen_fsm:reply(From, Reply), - {stop, Reason, NewStateData} - end; -after_new_keys_events({event, Event}, {next_state, StateName, StateData}) -> - case handle_event(Event, StateName, StateData) of - {next_state, NextStateName, NewStateData}-> - {next_state, NextStateName, NewStateData}; - {stop, Reason, NewStateData} -> - {stop, Reason, NewStateData} - end; -after_new_keys_events({connection_reply, _Data} = Reply, {StateName, State}) -> - NewState = send_replies([Reply], State), - {next_state, StateName, NewState}. + State#state{ssh_params = + Ssh#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh#ssh.recv_sequence)}, + decoded_data_buffer = <<>>, + undecoded_packet_length = undefined, + encoded_data_buffer = EncDataRest}. +%%%---------------------------------------------------------------- +%%% Some other module has decided to disconnect: +disconnect(Msg = #ssh_msg_disconnect{}) -> + throw({keep_state_and_data, + [{next_event, internal, {disconnect, Msg, Msg#ssh_msg_disconnect.description}}]}). -handle_disconnect(DisconnectMsg, State) -> - handle_disconnect(own, DisconnectMsg, State). +disconnect(Msg = #ssh_msg_disconnect{}, ExtraInfo) -> + throw({keep_state_and_data, + [{next_event, internal, {disconnect, Msg, {Msg#ssh_msg_disconnect.description,ExtraInfo}}}]}). -handle_disconnect(#ssh_msg_disconnect{} = DisconnectMsg, State, Error) -> - handle_disconnect(own, DisconnectMsg, State, Error); -handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, role = Role} = State0) -> - {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role), - State = send_replies(disconnect_replies(Type, Msg, Replies), State0), - disconnect_fun(Desc, State#state.opts), - {stop, {shutdown, Desc}, State#state{connection_state = Connection}}. -handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, - role = Role} = State0, ErrorMsg) -> - {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role), - State = send_replies(disconnect_replies(Type, Msg, Replies), State0), - disconnect_fun(Desc, State#state.opts), - {stop, {shutdown, {Desc, ErrorMsg}}, State#state{connection_state = Connection}}. - -disconnect_replies(own, Msg, Replies) -> - [{connection_reply, Msg} | Replies]; -disconnect_replies(peer, _, Replies) -> - Replies. +%% %%% This server/client has decided to disconnect via the state machine: +disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) -> + ?IO_FORMAT('~p ~p:~p disconnect ~p ~p~n',[self(),?MODULE,?LINE,Msg,_StateName]), + State = send_msg(Msg, State0), + disconnect_fun(Description, State#state.opts), +timer:sleep(400), + {stop, {shutdown,Description}, State}. +%%%---------------------------------------------------------------- counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) -> Ssh#ssh{c_vsn = NumVsn , c_version = StrVsn}; counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) -> Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}. -opposite_role(client) -> - server; -opposite_role(server) -> - client. connected_fun(User, PeerAddr, Method, Opts) -> case proplists:get_value(connectfun, Opts) of undefined -> @@ -1739,7 +1603,7 @@ ssh_info([client_version | Rest], #state{ssh_params = #ssh{c_vsn = IntVsn, ssh_info([server_version | Rest], #state{ssh_params =#ssh{s_vsn = IntVsn, s_version = StringVsn}} = State, Acc) -> ssh_info(Rest, State, [{server_version, {IntVsn, StringVsn}} | Acc]); -ssh_info([peer | Rest], #state{ssh_params = #ssh{peer = Peer}} = State, Acc) -> +ssh_info([peer | Rest], #state{ssh_params = #ssh{peer = Peer}} = State, Acc) -> ssh_info(Rest, State, [{peer, Peer} | Acc]); ssh_info([sockname | Rest], #state{socket = Socket} = State, Acc) -> {ok, SockName} = inet:sockname(Socket), @@ -1749,6 +1613,7 @@ ssh_info([user | Rest], #state{auth_user = User} = State, Acc) -> ssh_info([ _ | Rest], State, Acc) -> ssh_info(Rest, State, Acc). + ssh_channel_info([], _, Acc) -> Acc; @@ -1765,38 +1630,48 @@ ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize, ssh_channel_info([ _ | Rest], Channel, Acc) -> ssh_channel_info(Rest, Channel, Acc). + log_error(Reason) -> - Report = io_lib:format("Erlang ssh connection handler failed with reason: " - "~p ~n, Stacktrace: ~p ~n", - [Reason, erlang:get_stacktrace()]), - error_logger:error_report(Report), - "Internal error". - -not_connected_filter({connection_reply, _Data}) -> - true; -not_connected_filter(_) -> - false. - -send_replies([], State) -> - State; -send_replies([{connection_reply, Data} | Rest], #state{ssh_params = Ssh0} = State) -> - {Packet, Ssh} = ssh_transport:ssh_packet(Data, Ssh0), - send_msg(Packet, State), - send_replies(Rest, State#state{ssh_params = Ssh}); -send_replies([Msg | Rest], State) -> - catch send_reply(Msg), - send_replies(Rest, State). - -send_reply({channel_data, Pid, Data}) -> - Pid ! {ssh_cm, self(), Data}; -send_reply({channel_requst_reply, From, Data}) -> - gen_fsm:reply(From, Data); -send_reply({flow_control, Cache, Channel, From, Msg}) -> + Report = io_lib:format("Erlang ssh connection handler failed with reason:~n" + " ~p~n" + "Stacktrace:~n" + " ~p~n", + [Reason, erlang:get_stacktrace()]), + error_logger:error_report(Report). + + +%%%---------------------------------------------------------------- +not_connected_filter({connection_reply, _Data}) -> true; +not_connected_filter(_) -> false. + +%%%---------------------------------------------------------------- +send_replies(Repls, State) -> + lists:foldl(fun get_repl/2, + {[],State}, + Repls). + +get_repl({connection_reply,Msg}, {CallRepls,S}) -> + {CallRepls, send_msg(Msg,S)}; +get_repl({channel_data,undefined,Data}, Acc) -> + Acc; +get_repl({channel_data,Pid,Data}, Acc) -> + Pid ! {ssh_cm, self(), Data}, + Acc; +get_repl({channel_request_reply,From,Data}, {CallRepls,S}) -> + {[{reply,From,Data}|CallRepls], S}; +get_repl({flow_control,Cache,Channel,From,Msg}, {CallRepls,S}) -> ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), - gen_fsm:reply(From, Msg); -send_reply({flow_control, From, Msg}) -> - gen_fsm:reply(From, Msg). + {[{reply,From,Msg}|CallRepls], S}; +get_repl({flow_control,From,Msg}, {CallRepls,S}) -> + {[{reply,From,Msg}|CallRepls], S}; +get_repl(noreply, Acc) -> + Acc; +get_repl(X, Acc) -> + exit({get_repl,X,Acc}). + + +%%%---------------------------------------------------------------- disconnect_fun({disconnect,Msg}, Opts) -> disconnect_fun(Msg, Opts); disconnect_fun(_, undefined) -> @@ -1814,7 +1689,7 @@ unexpected_fun(UnexpectedMessage, Opts, #ssh{peer={_,Peer}}) -> undefined -> report; Fun -> - catch Fun(UnexpectedMessage, Peer) + catch Fun(UnexpectedMessage, Peer) end. @@ -1852,7 +1727,7 @@ remove_timer_ref(State) -> socket_control(Socket, Pid, Transport) -> case Transport:controlling_process(Socket, Pid) of ok -> - send_event(Pid, socket_control); + gen_statem:cast(Pid, socket_control); {error, Reason} -> {error, Reason} end. @@ -1893,4 +1768,3 @@ getopt(Opt, Socket) -> Other -> {error, {unexpected_getopts_return, Other}} end. - diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 65754956aa..db80d4c9e3 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -50,13 +50,7 @@ -define(Empint(X), (ssh_bits:mpint(X))/binary ). -define(Ebinary(X), ?STRING(X) ). -%% encode(Msg) -> -%% try encode1(Msg) -%% catch -%% C:E -> -%% io:format('***********************~n~p:~p ~p~n',[C,E,Msg]), -%% error(E) -%% end. +-define(unicode_list(B), unicode:characters_to_list(B)). encode(#ssh_msg_global_request{ name = Name, @@ -176,7 +170,7 @@ encode(#ssh_msg_userauth_pk_ok{ encode(#ssh_msg_userauth_passwd_changereq{prompt = Prompt, languge = Lang })-> - <>; + <>; encode(#ssh_msg_userauth_info_request{ name = Name, @@ -184,14 +178,14 @@ encode(#ssh_msg_userauth_info_request{ language_tag = Lang, num_prompts = NumPromtps, data = Data}) -> - <>; encode(#ssh_msg_userauth_info_response{ num_responses = Num, data = Data}) -> lists:foldl(fun %%("", Acc) -> Acc; % commented out since it seem wrong - (Response, Acc) -> <> + (Response, Acc) -> <> end, <>, Data); @@ -201,17 +195,17 @@ encode(#ssh_msg_disconnect{ description = Desc, language = Lang }) -> - <>; + <>; encode(#ssh_msg_service_request{ name = Service }) -> - <>; + <>; encode(#ssh_msg_service_accept{ name = Service }) -> - <>; + <>; encode(#ssh_msg_newkeys{}) -> <>; @@ -283,7 +277,7 @@ encode(#ssh_msg_kex_ecdh_reply{public_host_key = Key, q_s = Q_s, h_sig = Sign}) <>; encode(#ssh_msg_ignore{data = Data}) -> - <>; + <>; encode(#ssh_msg_unimplemented{sequence = Seq}) -> <>; @@ -291,7 +285,7 @@ encode(#ssh_msg_unimplemented{sequence = Seq}) -> encode(#ssh_msg_debug{always_display = Bool, message = Msg, language = Lang}) -> - <>. + <>. %% Connection Messages @@ -330,7 +324,7 @@ decode(<>) -> @@ -363,7 +357,7 @@ decode(<>) -> #ssh_msg_channel_request{ recipient_channel = Recipient, - request_type = unicode:characters_to_list(RequestType), + request_type = ?unicode_list(RequestType), want_reply = erl_boolean(Bool), data = Data }; @@ -381,9 +375,9 @@ decode(<>) -> #ssh_msg_userauth_request{ - user = unicode:characters_to_list(User), - service = unicode:characters_to_list(Service), - method = unicode:characters_to_list(Method), + user = ?unicode_list(User), + service = ?unicode_list(Service), + method = ?unicode_list(Method), data = Data }; @@ -391,7 +385,7 @@ decode(<>) -> #ssh_msg_userauth_failure { - authentications = unicode:characters_to_list(Auths), + authentications = ?unicode_list(Auths), partial_success = erl_boolean(Bool) }; @@ -493,18 +487,18 @@ decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_REPLY), decode(<>) -> #ssh_msg_service_request{ - name = unicode:characters_to_list(Service) + name = ?unicode_list(Service) }; decode(<>) -> #ssh_msg_service_accept{ - name = unicode:characters_to_list(Service) + name = ?unicode_list(Service) }; decode(<>) -> #ssh_msg_disconnect{ code = Code, - description = unicode:characters_to_list(Desc), + description = ?unicode_list(Desc), language = Lang }; @@ -512,7 +506,7 @@ decode(<>) -> #ssh_msg_disconnect{ code = Code, - description = unicode:characters_to_list(Desc), + description = ?unicode_list(Desc), language = <<"en">> }; @@ -554,7 +548,7 @@ decode_kex_init(<>, Acc, 0) -> X = 0, list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc])); decode_kex_init(<>, Acc, N) -> - Names = string:tokens(unicode:characters_to_list(Data), ","), + Names = string:tokens(?unicode_list(Data), ","), decode_kex_init(Rest, [Names | Acc], N -1). diff --git a/lib/ssh/src/ssh_no_io.erl b/lib/ssh/src/ssh_no_io.erl index 8144aac66e..2358560a26 100644 --- a/lib/ssh/src/ssh_no_io.erl +++ b/lib/ssh/src/ssh_no_io.erl @@ -28,26 +28,26 @@ -export([yes_no/2, read_password/2, read_line/2, format/2]). yes_no(_, _) -> - throw({{no_io_allowed, yes_no}, - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction is not allowed", - language = "en"}}). + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + description = "User interaction is not allowed"}, + {no_io_allowed, yes_no}). read_password(_, _) -> - throw({{no_io_allowed, read_password}, - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction is not allowed", - language = "en"}}). + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + description = "User interaction is not allowed"}, + {no_io_allowed, read_password}). read_line(_, _) -> - throw({{no_io_allowed, read_line}, - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction is not allowed", - language = "en"}} ). + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + description = "User interaction is not allowed"}, + {no_io_allowed, read_line}). format(_, _) -> - throw({{no_io_allowed, format}, - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = "User interaction is not allowed", - language = "en"}}). + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + description = "User interaction is not allowed"}, + {no_io_allowed, format}). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index c04bd350c7..83e75eb8c6 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -265,7 +265,8 @@ new_keys_message(Ssh0) -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), {ok, SshPacket, Ssh}. - + + handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, #ssh{role = client} = Ssh0) -> {ok, Algoritms} = select_algorithm(client, Own, CounterPart), @@ -275,10 +276,10 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, Ssh0#ssh{algorithms = Algoritms}); _ -> %% TODO: Correct code? - throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange" - " algorithm failed", - language = ""}) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Selection of key exchange algorithm failed" + }) end; handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, @@ -288,10 +289,10 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, true -> {ok, Ssh#ssh{algorithms = Algoritms}}; _ -> - throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Selection of key exchange" - " algorithm failed", - language = ""}) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Selection of key exchange algorithm failed" + }) end. @@ -371,12 +372,12 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, session_id = sid(Ssh1, H)}}; true -> - throw({{error,bad_e_from_peer}, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'e' out of bounds", - language = ""} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'e' out of bounds"}, + {error,bad_e_from_peer} + ) end. handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, @@ -396,21 +397,20 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, exchanged_hash = H, session_id = sid(Ssh, H)}}; Error -> - throw({Error, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed", - language = "en"} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed"}, + Error) end; true -> - throw({{error,bad_f_from_peer}, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'f' out of bounds", - language = ""} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'f' out of bounds"}, + bad_f_from_peer + ) end. @@ -435,10 +435,11 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0, keyex_info = {Min, Max, NBits} }}; {error,_} -> - throw(#ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "No possible diffie-hellman-group-exchange group found", - language = ""}) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "No possible diffie-hellman-group-exchange group found" + }) end; handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, @@ -469,19 +470,19 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits}, keyex_info = {-1, -1, NBits} % flag for kex_h hash calc }}; {error,_} -> - throw(#ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "No possible diffie-hellman-group-exchange group found", - language = ""}) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "No possible diffie-hellman-group-exchange group found" + }) end; handle_kex_dh_gex_request(_, _) -> - throw({{error,bad_ssh_msg_kex_dh_gex_request}, + ssh_connection_handler:disconnect( #ssh_msg_disconnect{ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, bad values in ssh_msg_kex_dh_gex_request", - language = ""} - }). + description = "Key exchange failed, bad values in ssh_msg_kex_dh_gex_request"}, + bad_ssh_msg_kex_dh_gex_request). adjust_gex_min_max(Min0, Max0, Opts) -> @@ -495,10 +496,11 @@ adjust_gex_min_max(Min0, Max0, Opts) -> Min2 =< Max2 -> {Min2, Max2}; Max2 < Min2 -> - throw(#ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "No possible diffie-hellman-group-exchange group possible", - language = ""}) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "No possible diffie-hellman-group-exchange group possible" + }) end end. @@ -535,20 +537,18 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, session_id = sid(Ssh, H) }}; true -> - throw({{error,bad_K}, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'K' out of bounds", - language = ""} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'K' out of bounds"}, + bad_K) end; true -> - throw({{error,bad_e_from_peer}, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'e' out of bounds", - language = ""} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'e' out of bounds"}, + bad_e_from_peer) end. handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostKey, @@ -572,29 +572,28 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK exchanged_hash = H, session_id = sid(Ssh, H)}}; _Error -> - throw(#ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed", - language = ""} - ) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed" + }) end; true -> - throw({{error,bad_K}, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'K' out of bounds", - language = ""} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'K' out of bounds"}, + bad_K) end; true -> - throw({{error,bad_f_from_peer}, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'f' out of bounds", - language = ""} - }) - end. + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'f' out of bounds"}, + bad_f_from_peer + ) + end. %%%---------------------------------------------------------------- %%% @@ -624,12 +623,11 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, session_id = sid(Ssh1, H)}} catch _:_ -> - throw({{error,invalid_peer_public_key}, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Peer ECDH public key is invalid", - language = ""} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Peer ECDH public key is invalid"}, + invalid_peer_public_key) end. handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, @@ -650,21 +648,19 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, exchanged_hash = H, session_id = sid(Ssh, H)}}; Error -> - throw({Error, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed", - language = ""} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed"}, + Error) end catch _:_ -> - throw({{error,invalid_peer_public_key}, - #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Peer ECDH public key is invalid", - language = ""} - }) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Peer ECDH public key is invalid"}, + invalid_peer_public_key) end. @@ -675,9 +671,10 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> {ok, Ssh} catch _C:_Error -> %% TODO: Throw earlier .... - throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Install alg failed", - language = "en"}) + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "Install alg failed" + }) end. %% select session id @@ -929,9 +926,9 @@ select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS -> lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)); select_all(CL, SL) -> Err = lists:concat(["Received too many algorithms (",length(CL),"+",length(SL)," >= ",?MAX_NUM_ALGORITHMS,")."]), - throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = Err, - language = ""}). + ssh_connection_handler:disconnect( + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = Err}). select([], []) -> diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl index bdc980e65c..440308d1b3 100644 --- a/lib/ssh/test/ssh_algorithms_SUITE.erl +++ b/lib/ssh/test/ssh_algorithms_SUITE.erl @@ -361,13 +361,15 @@ get_atoms(L) -> %%% Test case related %%% start_std_daemon(Opts, Config) -> + ct:log("starting std_daemon",[]), {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, Opts), ct:log("started ~p:~p ~p",[Host,Port,Opts]), [{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config]. start_pubkey_daemon(Opts, Config) -> + ct:log("starting pubkey_daemon",[]), {Pid, Host, Port} = ssh_test_lib:std_daemon1(Config, Opts), - ct:log("started1 ~p:~p ~p",[Host,Port,Opts]), + ct:log("started pubkey_daemon ~p:~p ~p",[Host,Port,Opts]), [{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config]. diff --git a/lib/ssh/test/ssh_benchmark_SUITE.erl b/lib/ssh/test/ssh_benchmark_SUITE.erl index 6e1d18cc95..1f11fee350 100644 --- a/lib/ssh/test/ssh_benchmark_SUITE.erl +++ b/lib/ssh/test/ssh_benchmark_SUITE.erl @@ -333,52 +333,64 @@ find_time(accept_to_hello, L) -> [T0,T1] = find([fun(C=#call{mfa = {ssh_acceptor,handle_connection,5}}) -> C#call.t_call end, - fun(C=#call{mfa = {ssh_connection_handler,hello,_}, - args = [socket_control|_]}) -> - C#call.t_return - end + ?LINE, + fun(C=#call{mfa = {ssh_connection_handler,handle_event,5}, + args = [_, {version_exchange,_}, _, {hello,_}, _]}) -> + C#call.t_call + end, + ?LINE ], L, []), {accept_to_hello, now2micro_sec(now_diff(T1,T0)), microsec}; find_time(kex, L) -> - [T0,T1] = find([fun(C=#call{mfa = {ssh_connection_handler,hello,_}, - args = [socket_control|_]}) -> + [T0,T1] = find([fun(C=#call{mfa = {ssh_connection_handler,handle_event,5}, + args = [_, {version_exchange,_}, _, {hello,_}, _]}) -> C#call.t_call end, - ?send(#ssh_msg_newkeys{}) + ?LINE, + ?send(#ssh_msg_newkeys{}), + ?LINE ], L, []), {kex, now2micro_sec(now_diff(T1,T0)), microsec}; find_time(kex_to_auth, L) -> [T0,T1] = find([?send(#ssh_msg_newkeys{}), - ?recv(#ssh_msg_userauth_request{}) + ?LINE, + ?recv(#ssh_msg_userauth_request{}), + ?LINE ], L, []), {kex_to_auth, now2micro_sec(now_diff(T1,T0)), microsec}; find_time(auth, L) -> [T0,T1] = find([?recv(#ssh_msg_userauth_request{}), - ?send(#ssh_msg_userauth_success{}) + ?LINE, + ?send(#ssh_msg_userauth_success{}), + ?LINE ], L, []), {auth, now2micro_sec(now_diff(T1,T0)), microsec}; find_time(to_prompt, L) -> [T0,T1] = find([fun(C=#call{mfa = {ssh_acceptor,handle_connection,5}}) -> C#call.t_call end, - ?recv(#ssh_msg_channel_request{request_type="env"}) + ?LINE, + ?recv(#ssh_msg_channel_request{request_type="env"}), + ?LINE ], L, []), {to_prompt, now2micro_sec(now_diff(T1,T0)), microsec}; find_time(channel_open_close, L) -> [T0,T1] = find([?recv(#ssh_msg_channel_request{request_type="subsystem"}), - ?send(#ssh_msg_channel_close{}) + ?LINE, + ?send(#ssh_msg_channel_close{}), + ?LINE ], L, []), {channel_open_close, now2micro_sec(now_diff(T1,T0)), microsec}. -find([F|Fs], [C|Cs], Acc) when is_function(F,1) -> +find([F,Id|Fs], [C|Cs], Acc) when is_function(F,1) -> try F(C) of T -> find(Fs, Cs, [T|Acc]) catch - _:_ -> find([F|Fs], Cs, Acc) + _:_ -> find([F,Id|Fs], Cs, Acc) end; find([], _, Acc) -> lists:reverse(Acc). @@ -444,7 +456,7 @@ erlang_trace() -> 0 = erlang:trace(new, true, [call,timestamp,{tracer,TracerPid}]), [init_trace(MFA, tp(MFA)) || MFA <- [{ssh_acceptor,handle_connection,5}, - {ssh_connection_handler,hello,2}, +%% {ssh_connection_handler,hello,2}, {ssh_message,encode,1}, {ssh_message,decode,1}, {ssh_transport,select_algorithm,3}, @@ -454,6 +466,10 @@ erlang_trace() -> {ssh_message,decode,1}, {public_key,dh_gex_group,4} % To find dh_gex group size ]], + init_trace({ssh_connection_handler,handle_event,5}, + [{['_', {version_exchange,'_'}, '_', {hello,'_'}, '_'], + [], + [return_trace]}]), {ok, TracerPid}. tp({_M,_F,Arity}) -> diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index 1d14a16065..4106385d2d 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -493,7 +493,7 @@ ssh_msg_debug_fun_option_client(Config) -> {user_interaction, false}, {ssh_msg_debug_fun,DbgFun}]), %% Beware, implementation knowledge: - gen_fsm:send_all_state_event(ConnectionRef,{ssh_msg_debug,false,<<"Hello">>,<<>>}), + gen_statem:cast(ConnectionRef,{ssh_msg_debug,false,<<"Hello">>,<<>>}), receive {msg_dbg,X={ConnectionRef,false,<<"Hello">>,<<>>}} -> ct:log("Got expected dbg msg ~p",[X]), @@ -606,7 +606,7 @@ ssh_msg_debug_fun_option_server(Config) -> receive {connection_pid,Server} -> %% Beware, implementation knowledge: - gen_fsm:send_all_state_event(Server,{ssh_msg_debug,false,<<"Hello">>,<<>>}), + gen_statem:cast(Server,{ssh_msg_debug,false,<<"Hello">>,<<>>}), receive {msg_dbg,X={_,false,<<"Hello">>,<<>>}} -> ct:log("Got expected dbg msg ~p",[X]), diff --git a/lib/ssh/test/ssh_renegotiate_SUITE.erl b/lib/ssh/test/ssh_renegotiate_SUITE.erl index 90132becbd..f1a909cbd0 100644 --- a/lib/ssh/test/ssh_renegotiate_SUITE.erl +++ b/lib/ssh/test/ssh_renegotiate_SUITE.erl @@ -33,7 +33,6 @@ suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{seconds,40}}]. - all() -> [{group,default_algs}, {group,aes_gcm} ]. @@ -238,7 +237,7 @@ renegotiate2(Config) -> %% get_kex_init - helper function to get key_exchange_init_msg get_kex_init(Conn) -> %% First, validate the key exchange is complete (StateName == connected) - {connected,S} = sys:get_state(Conn), + {{connected,_},S} = sys:get_state(Conn), %% Next, walk through the elements of the #state record looking %% for the #ssh_msg_kexinit record. This method is robust against %% changes to either record. The KEXINIT message contains a cookie diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index cd6c5f82b9..e06b646bba 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -38,7 +38,6 @@ suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{seconds,40}}]. - all() -> [{group, not_unicode}, {group, unicode} diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl index 09bef87148..355ce6a8f5 100644 --- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl +++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl @@ -39,7 +39,6 @@ suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{seconds,40}}]. - all() -> [close_file, quit, diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index 4db7d09ccd..69329a5c75 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -57,12 +57,16 @@ daemon(Host, Options) -> daemon(Host, inet_port(), Options). daemon(Host, Port, Options) -> + ct:log("~p:~p Calling ssh:daemon(~p, ~p, ~p)",[?MODULE,?LINE,Host,Port,Options]), case ssh:daemon(Host, Port, Options) of {ok, Pid} when Host == any -> + ct:log("ssh:daemon ok (1)",[]), {Pid, hostname(), Port}; {ok, Pid} -> + ct:log("ssh:daemon ok (2)",[]), {Pid, Host, Port}; Error -> + ct:log("ssh:daemon error ~p",[Error]), Error end. -- cgit v1.2.3 From 9ec2d2fcd17484163296b15315c18ab4509d0629 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 14 Apr 2016 19:35:39 +0200 Subject: ssh: Begin -spec for ssh_connection_handler --- lib/ssh/src/ssh.erl | 5 + lib/ssh/src/ssh.hrl | 4 +- lib/ssh/src/ssh_connect.hrl | 4 +- lib/ssh/src/ssh_connection_handler.erl | 694 ++++++++++++++++++++------------- 4 files changed, 431 insertions(+), 276 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index d0121e73ba..48ef8aad2a 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -36,6 +36,11 @@ shell/1, shell/2, shell/3 ]). +%%% Type exports +-export_type([connection_ref/0, + channel_id/0 + ]). + %%-------------------------------------------------------------------- -spec start() -> ok | {error, term()}. -spec start(permanent | transient | temporary) -> ok | {error, term()}. diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 73d6e4d2bc..868f3a9181 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -70,8 +70,6 @@ -record(ssh, { - %%state, %% what it's waiting for - role, %% client | server peer, %% string version of peer address @@ -135,8 +133,8 @@ user, service, userauth_quiet_mode, % boolean() - userauth_supported_methods, % string() eg "keyboard-interactive,password" userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"] + userauth_supported_methods, % string() eg "keyboard-interactive,password" kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive" userauth_preference, available_host_keys, diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 397d51de9d..3860bb3202 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -22,7 +22,9 @@ %%% Description : SSH connection protocol --type channel_id() :: integer(). +-type role() :: client | server . +-type connection_ref() :: pid(). +-type channel_id() :: pos_integer(). -define(DEFAULT_PACKET_SIZE, 65536). -define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE). diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index d26c586c54..1a2cdb6f87 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -34,26 +34,38 @@ -include("ssh_transport.hrl"). -include("ssh_auth.hrl"). -include("ssh_connect.hrl"). --compile(export_all). --export([start_link/3]). -%%-define(IO_FORMAT(F,A), io:format(F,A)). --define(IO_FORMAT(F,A), ok). +%%==================================================================== +%%% Exports +%%==================================================================== -%% Internal application API --export([open_channel/6, reply_request/3, request/6, request/7, - global_request/4, send/5, send_eof/2, info/1, info/2, - connection_info/2, channel_info/3, - adjust_window/3, close/2, stop/1, renegotiate/1, renegotiate_data/1, +-export([start_link/3, + stop/1 + ]). + +%%% Internal application API +-export([start_connection/4, + open_channel/6, + request/6, request/7, + reply_request/3, + global_request/4, + send/5, + send_eof/2, + info/1, info/2, + connection_info/2, + channel_info/3, + adjust_window/3, close/2, renegotiate/1, renegotiate_data/1, disconnect/1, disconnect/2, - start_connection/4, - get_print_info/1]). + get_print_info/1 + ]). -%% gen_statem callbacks +%%% gen_statem callbacks -export([init/1, handle_event/4, terminate/3, format_status/2, code_change/4]). +%%==================================================================== +%% Process state +%%==================================================================== -record(state, { - client, starter, auth_user, connection_state, @@ -78,11 +90,41 @@ }). %%==================================================================== -%% Internal application API +%% Start / stop %%==================================================================== +%%-------------------------------------------------------------------- +-spec start_link(role(), + inet:socket(), + proplists:proplist() + ) -> {ok, pid()}. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +start_link(Role, Socket, Options) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Socket, Options]])}. + %%-------------------------------------------------------------------- +-spec stop(connection_ref() + ) -> ok | {error, term()}. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +stop(ConnectionHandler)-> + case call(ConnectionHandler, stop) of + {error, closed} -> + ok; + Other -> + Other + end. + +%%==================================================================== +%% Internal application API +%%==================================================================== + %%-------------------------------------------------------------------- +-spec start_connection(role(), + inet:socket(), + proplists:proplist(), + timeout() + ) -> {ok, connection_ref()} | {error, term()}. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . start_connection(client = Role, Socket, Options, Timeout) -> try {ok, Pid} = sshc_sup:start_child([Role, Socket, Options]), @@ -123,91 +165,83 @@ start_connection(server = Role, Socket, Options, Timeout) -> {error, Error} end. -start_the_connection_child(UserPid, Role, Socket, Options) -> - Sups = proplists:get_value(supervisors, Options), - ConnectionSup = proplists:get_value(connection_sup, Sups), - Opts = [{supervisors, Sups}, {user_pid, UserPid} | proplists:get_value(ssh_opts, Options, [])], - {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Opts]), - {_, Callback, _} = proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), - socket_control(Socket, Pid, Callback), - Pid. +%%-------------------------------------------------------------------- +%%% Some other module has decided to disconnect. +-spec disconnect(#ssh_msg_disconnect{}) -> no_return(). +-spec disconnect(#ssh_msg_disconnect{}, iodata()) -> no_return(). +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +disconnect(Msg = #ssh_msg_disconnect{}) -> + throw({keep_state_and_data, + [{next_event, internal, {disconnect, Msg, Msg#ssh_msg_disconnect.description}}]}). +disconnect(Msg = #ssh_msg_disconnect{}, ExtraInfo) -> + throw({keep_state_and_data, + [{next_event, internal, {disconnect, Msg, {Msg#ssh_msg_disconnect.description,ExtraInfo}}}]}). -start_link(Role, Socket, Options) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Socket, Options]])}. - -init([Role, Socket, SshOpts]) -> - process_flag(trap_exit, true), - {NumVsn, StrVsn} = ssh_transport:versions(Role, SshOpts), - {Protocol, Callback, CloseTag} = - proplists:get_value(transport, SshOpts, {tcp, gen_tcp, tcp_closed}), - Cache = ssh_channel:cache_create(), - State0 = #state{ - connection_state = #connection{channel_cache = Cache, - channel_id_seed = 0, - port_bindings = [], - requests = [], - options = SshOpts}, - socket = Socket, - decoded_data_buffer = <<>>, - encoded_data_buffer = <<>>, - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag, - opts = SshOpts - }, - - State = init_role(Role, State0), - - try init_ssh(Role, NumVsn, StrVsn, SshOpts, Socket) of - Ssh -> - gen_statem:enter_loop(?MODULE, - [], %%[{debug,[trace,log,statistics,debug]} || Role==server], - handle_event_function, - {hello,Role}, - State#state{ssh_params = Ssh}, - []) - catch - _:Error -> - gen_statem:enter_loop(?MODULE, - [], - handle_event_function, - {init_error,Error}, - State, - []) - end. %%-------------------------------------------------------------------- +-spec open_channel(connection_ref(), + string(), + binary(), + pos_integer(), + pos_integer(), + timeout() + ) -> {ok, channel_id()} | {error, term()}. + +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +open_channel(ConnectionHandler, + ChannelType, ChannelSpecificData, InitialWindowSize, MaxPacketSize, + Timeout) -> + call(ConnectionHandler, + {open, self(), + ChannelType, InitialWindowSize, MaxPacketSize, ChannelSpecificData, + Timeout}). + %%-------------------------------------------------------------------- -open_channel(ConnectionHandler, ChannelType, ChannelSpecificData, - InitialWindowSize, - MaxPacketSize, Timeout) -> - call(ConnectionHandler, {open, self(), ChannelType, - InitialWindowSize, MaxPacketSize, - ChannelSpecificData, - Timeout}). -%%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec request(connection_ref(), + pid(), + channel_id(), + string(), + boolean(), + iodata(), + timeout() + ) -> success | failure | ok | {error,timeout}. + +-spec request(connection_ref(), + channel_id(), + string(), + boolean(), + iodata(), + timeout() + ) -> success | failure | ok | {error,timeout}. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . request(ConnectionHandler, ChannelPid, ChannelId, Type, true, Data, Timeout) -> - call(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data, - Timeout}); + call(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data, Timeout}); request(ConnectionHandler, ChannelPid, ChannelId, Type, false, Data, _) -> cast(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data}). -%%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- request(ConnectionHandler, ChannelId, Type, true, Data, Timeout) -> call(ConnectionHandler, {request, ChannelId, Type, Data, Timeout}); request(ConnectionHandler, ChannelId, Type, false, Data, _) -> cast(ConnectionHandler, {request, ChannelId, Type, Data}). %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec reply_request(connection_ref(), + success | failure, + channel_id() + ) -> ok. + +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . reply_request(ConnectionHandler, Status, ChannelId) -> cast(ConnectionHandler, {reply_request, Status, ChannelId}). %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec global_request(connection_ref(), + string(), + boolean(), + iolist() + ) -> ok | error. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . global_request(ConnectionHandler, Type, true = Reply, Data) -> case call(ConnectionHandler, {global_request, self(), Type, Reply, Data}) of {ssh_cm, ConnectionHandler, {success, _}} -> @@ -219,44 +253,96 @@ global_request(ConnectionHandler, Type, false = Reply, Data) -> cast(ConnectionHandler, {global_request, self(), Type, Reply, Data}). %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec send(connection_ref(), + channel_id(), + non_neg_integer(), + iodata(), + timeout() + ) -> ok | {error, timeout|closed}. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . send(ConnectionHandler, ChannelId, Type, Data, Timeout) -> call(ConnectionHandler, {data, ChannelId, Type, Data, Timeout}). %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec send_eof(connection_ref(), + channel_id() + ) -> ok | {error,closed}. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . send_eof(ConnectionHandler, ChannelId) -> call(ConnectionHandler, {eof, ChannelId}). %%-------------------------------------------------------------------- +-spec info(connection_ref() + ) -> [ #channel{} ]. + +-spec info(connection_ref(), + pid() + ) -> [ #channel{} ]. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +info(ConnectionHandler) -> + info(ConnectionHandler, {info, all}). + +info(ConnectionHandler, ChannelProcess) -> + call(ConnectionHandler, {info, ChannelProcess}). + %%-------------------------------------------------------------------- +-type local_sock_info() :: {inet:ip_address(), non_neg_integer()} | string(). +-type peer_sock_info() :: {inet:ip_address(), non_neg_integer()} | string(). +-type state_info() :: iolist(). + +-spec get_print_info(connection_ref() + ) -> {{local_sock_info(), peer_sock_info()}, + state_info() + }. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . get_print_info(ConnectionHandler) -> call(ConnectionHandler, get_print_info, 1000). +%%-------------------------------------------------------------------- +-spec connection_info(connection_ref(), + [atom()] + ) -> proplists:proplist(). +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . connection_info(ConnectionHandler, Options) -> call(ConnectionHandler, {connection_info, Options}). %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec channel_info(connection_ref(), + channel_id(), + [atom()] + ) -> proplists:proplist(). +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . channel_info(ConnectionHandler, ChannelId, Options) -> call(ConnectionHandler, {channel_info, ChannelId, Options}). %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec adjust_window(connection_ref(), + channel_id(), + integer() + ) -> ok. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . adjust_window(ConnectionHandler, Channel, Bytes) -> cast(ConnectionHandler, {adjust_window, Channel, Bytes}). + %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec renegotiate(connection_ref() + ) -> ok. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . renegotiate(ConnectionHandler) -> cast(ConnectionHandler, renegotiate). %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec renegotiate_data(connection_ref() + ) -> ok. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . renegotiate_data(ConnectionHandler) -> cast(ConnectionHandler, data_size). %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- +-spec close(connection_ref(), + channel_id() + ) -> ok. +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . close(ConnectionHandler, ChannelId) -> case call(ConnectionHandler, {close, ChannelId}) of ok -> @@ -265,40 +351,73 @@ close(ConnectionHandler, ChannelId) -> ok end. +%%==================================================================== +%% gen_statem callbacks +%%==================================================================== %%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- -stop(ConnectionHandler)-> - case call(ConnectionHandler, stop) of - {error, closed} -> - ok; - Other -> - Other + +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + +init([Role, Socket, SshOpts]) -> + process_flag(trap_exit, true), + {NumVsn, StrVsn} = ssh_transport:versions(Role, SshOpts), + {Protocol, Callback, CloseTag} = + proplists:get_value(transport, SshOpts, {tcp, gen_tcp, tcp_closed}), + Cache = ssh_channel:cache_create(), + State = + init_role(Role, + #state{ + connection_state = #connection{channel_cache = Cache, + channel_id_seed = 0, + port_bindings = [], + requests = [], + options = SshOpts}, + socket = Socket, + decoded_data_buffer = <<>>, + encoded_data_buffer = <<>>, + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag, + opts = SshOpts + }), + + try init_ssh_record(Role, NumVsn, StrVsn, SshOpts, Socket) of + Ssh -> + gen_statem:enter_loop(?MODULE, + [], %%[{debug,[trace,log,statistics,debug]} || Role==server], + handle_event_function, + {hello,Role}, + State#state{ssh_params = Ssh}, + []) + catch + _:Error -> + gen_statem:enter_loop(?MODULE, + [], + handle_event_function, + {init_error,Error}, + State, + []) end. -info(ConnectionHandler) -> - info(ConnectionHandler, {info, all}). +%%-------------------------------------------------------------------- -info(ConnectionHandler, ChannelProcess) -> - call(ConnectionHandler, {info, ChannelProcess}). +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -%%==================================================================== -%% gen_statem callbacks -%%==================================================================== +%%% ######## Error in the initialiasation #### -%% Temporary fix for the Nessus error. SYN-> <-SYNACK ACK-> RST-> ? -handle_event(_, _Event, {init_error,Error}, _State) -> - case Error of - {badmatch,{error,enotconn}} -> - %% {error,enotconn} probably from inet:peername in - %% init_ssh(server,..)/5 called from init/1 - {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}}; - _ -> - {stop, {shutdown,{init,Error}}} - end; +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}}}; -%%% ######## {hello, client|server} #### +%%% ######## {hello, client|server} #### handle_event(_, socket_control, StateName={hello,_}, S=#state{socket=Socket, ssh_params=Ssh}) -> VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)), @@ -338,6 +457,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, S=#state{ssh_params = description = ["Protocol version ",StrVsn," not supported"]}, {next_state, {hello,Role}, S}) end; + %%% ######## {kexinit, client|server, init|renegotiate} #### @@ -356,6 +476,7 @@ handle_event(_, {#ssh_msg_kexinit{} = Kex, Payload}, {kexinit,server,ReNeg}, {ok, Ssh} = ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1), {next_state, {key_exchange,server,ReNeg}, S#state{ssh_params = Ssh}}; + %%% ######## {key_exchange, client|server, init|renegotiate} #### handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, @@ -404,6 +525,7 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, send_bytes(NewKeys, State), {next_state, {new_keys,client,ReNeg}, State#state{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}, @@ -414,6 +536,7 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv send_bytes(NewKeys, State), {next_state, {new_keys,server,ReNeg}, State#state{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}, @@ -422,6 +545,7 @@ handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,cl send_bytes(NewKeys, State), {next_state, {new_keys,client,ReNeg}, State#state{ssh_params = Ssh1}}; + %%% ######## {new_keys, client|server} #### handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, @@ -439,6 +563,7 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, S) -> {next_state, {connected,Role}, S}; + %%% ######## {service_request, client|server} handle_event(_, #ssh_msg_service_request{name = "ssh-userauth"} = Msg, {service_request,server}, @@ -458,6 +583,7 @@ handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request send_bytes(Msg, State), {next_state, {userauth,client}, State#state{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; + %%% ######## {userauth, client|server} #### handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", @@ -465,7 +591,6 @@ handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", #state{ssh_params = #ssh{session_id = SessionId, service = "ssh-connection"} = Ssh0 } = State) -> -?IO_FORMAT('~p #ssh_msg_userauth_request{ssh-connection,~p}~n',[self(),Msg#ssh_msg_userauth_request.method]), {not_authorized, {_User, _Reason}, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), send_bytes(Reply, State), @@ -477,7 +602,6 @@ handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", service = "ssh-connection", peer = {_, Address}} = Ssh0, opts = Opts, starter = Pid} = State) -> -?IO_FORMAT('~p #ssh_msg_userauth_request{ssh-connection,~p}~n',[self(),Msg#ssh_msg_userauth_request.method]), case lists:member(Method, Ssh0#ssh.userauth_methods) of true -> case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of @@ -485,23 +609,19 @@ handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", send_bytes(Reply, State), Pid ! ssh_connected, connected_fun(User, Address, Method, Opts), -?IO_FORMAT('~p CONNECTED!~n',[self()]), {next_state, {connected,server}, State#state{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), -?IO_FORMAT('~p not_authorized (1)~n',[self()]), {next_state, {userauth_keyboard_interactive,server}, State#state{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Address, Reason, Opts), send_bytes(Reply, State), -?IO_FORMAT('~p not_authorized (2)~n',[self()]), {next_state, StateName, State#state{ssh_params = Ssh}} end; false -> %% At least one non-erlang client does like this. Retry as the next event -?IO_FORMAT('~p bug-fix~n',[self()]), {next_state, StateName, State, [{next_event, internal, Msg#ssh_msg_userauth_request{method="none"}}] } @@ -509,7 +629,6 @@ handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", handle_event(_, #ssh_msg_userauth_request{service = Service}, {userauth,server}=StateName, State) when Service =/= "ssh-connection" -> -?IO_FORMAT('~p #ssh_msg_userauth_request{~p,...}~n',[self(),Service]), Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, description = "Unknown service"}, disconnect(Msg, StateName, State); @@ -526,7 +645,6 @@ handle_event(_, #ssh_msg_userauth_failure{}, {userauth,client}=StateName, " authentication methods"}, disconnect(Msg, StateName, State); - handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client}, #state{ssh_params = Ssh0 = #ssh{userauth_methods=AuthMthds}} = State) -> %% The prefered authentication method failed try next method @@ -559,6 +677,7 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, StateName={userauth,cli io:format("~s", [Msg]), {next_state, StateName, State}; + %%% ######## {userauth_keyboard_interactive, client|server} handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client}, @@ -583,6 +702,7 @@ handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_inte send_bytes(Reply, State), {next_state, {userauth,server}, State#state{ssh_params = Ssh}} end; + handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, #state{ssh_params = Ssh0 = #ssh{userauth_preference=Prefs0}} = State) -> Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Prefs0, @@ -600,6 +720,7 @@ handle_event(_, Msg=#ssh_msg_userauth_success{}, {userauth_keyboard_interactive_ 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}]}; + %%% ######## {connected, client|server} #### handle_event(_, {#ssh_msg_kexinit{},_} = Event, {connected,Role}, #state{ssh_params = Ssh0} = State0) -> @@ -703,6 +824,7 @@ handle_event(cast, data_size, {connected,Role}, #state{ssh_params=Ssh0} = State) _ -> {next_state, {connected,Role}, State} end; + handle_event(cast, data_size, StateName, State) -> %% Already in key-exchange so safe to ignore {next_state, StateName, State}; @@ -817,16 +939,14 @@ handle_event({call,_}, _, StateName, State) when StateName /= {connected,server} handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName={connected,_}, State0) -> State = handle_request(ChannelPid, ChannelId, Type, Data, true, From, State0), - %% Note reply to channel will happen later when - %% reply is recived from peer on the socket + %% 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}, {request, ChannelId, Type, Data, Timeout}, StateName={connected,_}, State0) -> State = handle_request(ChannelId, Type, Data, true, From, State0), - %% Note reply to channel will happen later when - %% reply is recived from peer on the socket + %% 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}; @@ -860,11 +980,12 @@ handle_event({call,From}, {eof, ChannelId}, StateName={connected,_}, {next_state, StateName, State0, [{reply,From,{error,closed}}]} end; -handle_event({call,From}, {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, - StateName={connected,_}, +handle_event({call,From}, + {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, + StateName = {connected,_}, #state{connection_state = #connection{channel_cache = Cache}} = State0) -> erlang:monitor(process, ChannelPid), - {ChannelId, State1} = new_channel_id(State0), + {ChannelId, State1} = new_channel_id(State0), Msg = ssh_connection:channel_open_msg(Type, ChannelId, InitialWindowSize, MaxPacketSize, Data), @@ -934,7 +1055,6 @@ handle_event(info, {Protocol, Socket, Data}, StateName, State0 = encoded_data_buffer = EncData0, undecoded_packet_length = RemainingSshPacketLen0, ssh_params = Ssh0}) -> -?IO_FORMAT('~p Recv tcp~n',[self()]), Encoded = <>, try ssh_transport:handle_packet_part(DecData0, Encoded, RemainingSshPacketLen0, Ssh0) of @@ -995,10 +1115,8 @@ handle_event(internal, prepare_next_packet, StateName, State) -> Enough = erlang:max(8, State#state.ssh_params#ssh.decrypt_block_size), case size(State#state.encoded_data_buffer) of Sz when Sz >= Enough -> -?IO_FORMAT('~p Send <<>> to self~n',[self()]), self() ! {State#state.transport_protocol, State#state.socket, <<>>}; _ -> -?IO_FORMAT('~p Set active_once~n',[self()]), inet:setopts(State#state.socket, [{active, once}]) end, {next_state, StateName, State}; @@ -1088,62 +1206,45 @@ handle_event(Type, Ev, StateName, State) -> disconnect(Msg, StateName, State) end. + %%-------------------------------------------------------------------- + +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + terminate(normal, StateName, State) -> - ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,normal,StateName]), - normal_termination(StateName, State); + finalize_termination(StateName, State); terminate({shutdown,{init,Reason}}, StateName, State) -> - ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,{shutdown,{init,Reason}},StateName]), error_logger:info_report(io_lib:format("Erlang ssh in connection handler init: ~p~n",[Reason])), - normal_termination(StateName, State); + finalize_termination(StateName, State); -terminate(shutdown, StateName, State) -> +terminate(shutdown, StateName, State0) -> %% Terminated by supervisor - ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,shutdown,StateName]), - normal_termination(StateName, - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Application shutdown"}, - State); + State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "Application shutdown"}, + State0), +timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead + finalize_termination(StateName, State); -%% terminate({shutdown,Msg}, StateName, State) when is_record(Msg,ssh_msg_disconnect)-> -%% ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,{shutdown,Msg},StateName]), -%% normal_termination(StateName, Msg, State); +%% terminate({shutdown,Msg}, StateName, State0) when is_record(Msg,ssh_msg_disconnect)-> +%% State = send_msg(Msg, State0), +%% timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead +%% finalize_termination(StateName, Msg, State); terminate({shutdown,_R}, StateName, State) -> - ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,{shutdown,_R},StateName]), - normal_termination(StateName, State); + finalize_termination(StateName, State); -terminate(Reason, StateName, State) -> +terminate(Reason, StateName, State0) -> %% Others, e.g undef, {badmatch,_} - ?IO_FORMAT('~p ~p:~p terminate ~p ~p~n',[self(),?MODULE,?LINE,Reason,StateName]), log_error(Reason), - normal_termination(StateName, - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, description = "Internal error"}, - State). + State0), + finalize_termination(StateName, State). +%%-------------------------------------------------------------------- -normal_termination(StateName, Msg, State0) -> - State = send_msg(Msg,State0), -timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead - normal_termination(StateName, State). - -normal_termination(_StateName, #state{transport_cb = Transport, - connection_state = Connection, - socket = Socket}) -> - ?IO_FORMAT('~p ~p:~p normal_termination in state ~p~n',[self(),?MODULE,?LINE,_StateName]), - terminate_subsystem(Connection), - (catch Transport:close(Socket)), - ok. - - -terminate_subsystem(#connection{system_supervisor = SysSup, - sub_system_supervisor = SubSysSup}) when is_pid(SubSysSup) -> - ssh_system_sup:stop_subsystem(SysSup, SubSysSup); -terminate_subsystem(_) -> - ok. - +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . format_status(normal, [_, _StateName, State]) -> [{data, [{"State", State}]}]; @@ -1176,20 +1277,29 @@ format_status(terminate, [_, _StateName, State]) -> }}]}]. +%%-------------------------------------------------------------------- + +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -%% StateName to Role -role({_,Role}) -> Role; -role({_,Role,_}) -> Role. +%%==================================================================== +%% Internal functions +%%==================================================================== -renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; -renegotiation(_) -> false. +%%-------------------------------------------------------------------- +%% Starting +start_the_connection_child(UserPid, Role, Socket, Options) -> + Sups = proplists:get_value(supervisors, Options), + ConnectionSup = proplists:get_value(connection_sup, Sups), + Opts = [{supervisors, Sups}, {user_pid, UserPid} | proplists:get_value(ssh_opts, Options, [])], + {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Opts]), + {_, Callback, _} = proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), + socket_control(Socket, Pid, Callback), + Pid. init_role(client, #state{opts = Opts} = State0) -> @@ -1200,6 +1310,7 @@ init_role(client, #state{opts = Opts} = State0) -> [self(), data_size]), State0#state{starter = Pid, idle_timer_ref = TimerRef}; + init_role(server, #state{opts = Opts, connection_state = Connection} = State) -> Sups = proplists:get_value(supervisors, Opts), Pid = proplists:get_value(user_pid, Opts), @@ -1209,13 +1320,137 @@ init_role(server, #state{opts = Opts, connection_state = Connection} = State) -> Shell = proplists:get_value(shell, Opts), Exec = proplists:get_value(exec, Opts), CliSpec = proplists:get_value(ssh_cli, Opts, {ssh_cli, [Shell]}), - State#state{starter = Pid, connection_state = Connection#connection{ - cli_spec = CliSpec, - exec = Exec, - system_supervisor = SystemSup, - sub_system_supervisor = SubSystemSup, - connection_supervisor = ConnectionSup - }}. + State#state{starter = Pid, + connection_state = Connection#connection{ + cli_spec = CliSpec, + exec = Exec, + system_supervisor = SystemSup, + sub_system_supervisor = SubSystemSup, + connection_supervisor = ConnectionSup + }}. + + +%% init_ssh_record(client = Role, Vsn, Version, Options, Socket) -> +%% IOCb = case proplists:get_value(user_interaction, Options, true) of +%% true -> +%% ssh_io; +%% false -> +%% ssh_no_io +%% end, + +%% AuthMethods = proplists:get_value(auth_methods, Options, +%% ?SUPPORTED_AUTH_METHODS), +%% {ok, PeerAddr} = inet:peername(Socket), + +%% PeerName = proplists:get_value(host, Options), +%% KeyCb = proplists:get_value(key_cb, Options, ssh_file), + +%% #ssh{role = Role, +%% c_vsn = Vsn, +%% c_version = Version, +%% key_cb = KeyCb, +%% io_cb = IOCb, +%% userauth_quiet_mode = proplists:get_value(quiet_mode, Options, false), +%% opts = Options, +%% userauth_supported_methods = AuthMethods, +%% peer = {PeerName, PeerAddr}, +%% available_host_keys = supported_host_keys(Role, KeyCb, Options), +%% random_length_padding = proplists:get_value(max_random_length_padding, +%% Options, +%% (#ssh{})#ssh.random_length_padding) +%% }; + +%% init_ssh_record(server = Role, Vsn, Version, Options, Socket) -> +%% AuthMethods = proplists:get_value(auth_methods, Options, +%% ?SUPPORTED_AUTH_METHODS), +%% AuthMethodsAsList = string:tokens(AuthMethods, ","), +%% {ok, PeerAddr} = inet:peername(Socket), +%% KeyCb = proplists:get_value(key_cb, Options, ssh_file), + +%% #ssh{role = Role, +%% s_vsn = Vsn, +%% s_version = Version, +%% key_cb = KeyCb, +%% io_cb = proplists:get_value(io_cb, Options, ssh_io), +%% opts = Options, +%% userauth_supported_methods = AuthMethods, +%% userauth_methods = AuthMethodsAsList, +%% kb_tries_left = 3, +%% peer = {undefined, PeerAddr}, +%% available_host_keys = supported_host_keys(Role, KeyCb, Options), +%% random_length_padding = proplists:get_value(max_random_length_padding, +%% Options, +%% (#ssh{})#ssh.random_length_padding) +%% }. + + +init_ssh_record(Role, Vsn, Version, Options, Socket) -> + {ok, PeerAddr} = inet:peername(Socket), + KeyCb = proplists:get_value(key_cb, Options, ssh_file), + AuthMethods = proplists:get_value(auth_methods, Options, ?SUPPORTED_AUTH_METHODS), + + S0 = #ssh{role = Role, + key_cb = KeyCb, + opts = Options, + userauth_supported_methods = AuthMethods, + available_host_keys = supported_host_keys(Role, KeyCb, Options), + random_length_padding = proplists:get_value(max_random_length_padding, + Options, + (#ssh{})#ssh.random_length_padding) + }, + + case Role of + client -> + PeerName = proplists:get_value(host, Options), + S0#ssh{c_vsn = Vsn, + c_version = Version, + io_cb = case proplists:get_value(user_interaction, Options, true) of + true -> ssh_io; + false -> ssh_no_io + end, + userauth_quiet_mode = proplists:get_value(quiet_mode, Options, false), + peer = {PeerName, PeerAddr} + }; + + server -> + S0#ssh{s_vsn = Vsn, + s_version = Version, + io_cb = proplists:get_value(io_cb, Options, ssh_io), + userauth_methods = string:tokens(AuthMethods, ","), + kb_tries_left = 3, + peer = {undefined, PeerAddr} + } + end. + + + +%%-------------------------------------------------------------------- +%% Stopping + +finalize_termination(_StateName, #state{transport_cb = Transport, + connection_state = Connection, + socket = Socket}) -> + case Connection of + #connection{system_supervisor = SysSup, + sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) -> + ssh_system_sup:stop_subsystem(SysSup, SubSysSup); + _ -> + do_nothing + end, + (catch Transport:close(Socket)), + ok. + + + + +%% StateName to Role +role({_,Role}) -> Role; +role({_,Role,_}) -> Role. + + +renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation; +renegotiation(_) -> false. + get_idle_time(SshOptions) -> case proplists:get_value(idle_time, SshOptions) of @@ -1225,59 +1460,6 @@ get_idle_time(SshOptions) -> undefined end. -init_ssh(client = Role, Vsn, Version, Options, Socket) -> - IOCb = case proplists:get_value(user_interaction, Options, true) of - true -> - ssh_io; - false -> - ssh_no_io - end, - - AuthMethods = proplists:get_value(auth_methods, Options, - ?SUPPORTED_AUTH_METHODS), - {ok, PeerAddr} = inet:peername(Socket), - - PeerName = proplists:get_value(host, Options), - KeyCb = proplists:get_value(key_cb, Options, ssh_file), - - #ssh{role = Role, - c_vsn = Vsn, - c_version = Version, - key_cb = KeyCb, - io_cb = IOCb, - userauth_quiet_mode = proplists:get_value(quiet_mode, Options, false), - opts = Options, - userauth_supported_methods = AuthMethods, - peer = {PeerName, PeerAddr}, - available_host_keys = supported_host_keys(Role, KeyCb, Options), - random_length_padding = proplists:get_value(max_random_length_padding, - Options, - (#ssh{})#ssh.random_length_padding) - }; - -init_ssh(server = Role, Vsn, Version, Options, Socket) -> - AuthMethods = proplists:get_value(auth_methods, Options, - ?SUPPORTED_AUTH_METHODS), - AuthMethodsAsList = string:tokens(AuthMethods, ","), - {ok, PeerAddr} = inet:peername(Socket), - KeyCb = proplists:get_value(key_cb, Options, ssh_file), - - #ssh{role = Role, - s_vsn = Vsn, - s_version = Version, - key_cb = KeyCb, - io_cb = proplists:get_value(io_cb, Options, ssh_io), - opts = Options, - userauth_supported_methods = AuthMethods, - userauth_methods = AuthMethodsAsList, - kb_tries_left = 3, - peer = {undefined, PeerAddr}, - available_host_keys = supported_host_keys(Role, KeyCb, Options), - random_length_padding = proplists:get_value(max_random_length_padding, - Options, - (#ssh{})#ssh.random_length_padding) - }. - supported_host_keys(client, _, Options) -> try case proplists:get_value(public_key, @@ -1316,10 +1498,7 @@ send_msg(Msg, State=#state{ssh_params=Ssh0}) when is_tuple(Msg) -> State#state{ssh_params=Ssh}. send_bytes(Bytes, #state{socket = Socket, transport_cb = Transport}) -> - R = Transport:send(Socket, Bytes), -?IO_FORMAT('~p send_bytes ~p~n',[self(),R]), - R. - + Transport:send(Socket, Bytes). handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), @@ -1516,38 +1695,9 @@ new_channel_id(#state{connection_state = #connection{channel_id_seed = Id} = {Id, State#state{connection_state = Connection#connection{channel_id_seed = Id + 1}}}. -prepare_for_next_packet(State = #state{transport_protocol = Protocol, - socket = Socket}, - Ssh, EncDataRest) -> - case size(EncDataRest) >= erlang:max(8, Ssh#ssh.decrypt_block_size) of - true -> - %% Enough data from the next packet has been received to - %% decode the length indicator, fake a socket-recive - %% message so that the data will be processed - self() ! {Protocol, Socket, <<>>}; - false -> - inet:setopts(Socket, [{active, once}]) - end, - State#state{ssh_params = - Ssh#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh#ssh.recv_sequence)}, - decoded_data_buffer = <<>>, - undecoded_packet_length = undefined, - encoded_data_buffer = EncDataRest}. - %%%---------------------------------------------------------------- -%%% Some other module has decided to disconnect: -disconnect(Msg = #ssh_msg_disconnect{}) -> - throw({keep_state_and_data, - [{next_event, internal, {disconnect, Msg, Msg#ssh_msg_disconnect.description}}]}). - -disconnect(Msg = #ssh_msg_disconnect{}, ExtraInfo) -> - throw({keep_state_and_data, - [{next_event, internal, {disconnect, Msg, {Msg#ssh_msg_disconnect.description,ExtraInfo}}}]}). - - %% %%% This server/client has decided to disconnect via the state machine: disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) -> - ?IO_FORMAT('~p ~p:~p disconnect ~p ~p~n',[self(),?MODULE,?LINE,Msg,_StateName]), State = send_msg(Msg, State0), disconnect_fun(Description, State#state.opts), timer:sleep(400), @@ -1652,7 +1802,7 @@ send_replies(Repls, State) -> get_repl({connection_reply,Msg}, {CallRepls,S}) -> {CallRepls, send_msg(Msg,S)}; -get_repl({channel_data,undefined,Data}, Acc) -> +get_repl({channel_data,undefined,_Data}, Acc) -> Acc; get_repl({channel_data,Pid,Data}, Acc) -> Pid ! {ssh_cm, self(), Data}, -- cgit v1.2.3 From c37f0b1ccb54fd76311259eaa747424d77e76559 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 15 Apr 2016 10:57:23 +0200 Subject: ssh: refactor connection handler initialization --- lib/ssh/src/ssh_connection_handler.erl | 339 ++++++++++++++------------------- lib/ssh/test/ssh_trpt_test_lib.erl | 9 +- 2 files changed, 150 insertions(+), 198 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 1a2cdb6f87..6d5cd3f262 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -39,6 +39,7 @@ %%% Exports %%==================================================================== +%%% Start and stop -export([start_link/3, stop/1 ]). @@ -54,40 +55,19 @@ info/1, info/2, connection_info/2, channel_info/3, - adjust_window/3, close/2, renegotiate/1, renegotiate_data/1, + adjust_window/3, close/2, disconnect/1, disconnect/2, get_print_info/1 ]). -%%% gen_statem callbacks --export([init/1, handle_event/4, terminate/3, format_status/2, code_change/4]). +%%% Behaviour callbacks +-export([handle_event/4, terminate/3, format_status/2, code_change/4]). -%%==================================================================== -%% Process state -%%==================================================================== --record(state, { - starter, - auth_user, - connection_state, - latest_channel_id = 0, - idle_timer_ref, - transport_protocol, % ex: tcp - transport_cb, - transport_close_tag, - ssh_params, % #ssh{} - from ssh.hrl - socket, % socket() - decoded_data_buffer, % binary() - encoded_data_buffer, % binary() - undecoded_packet_length, % integer() - key_exchange_init_msg, % #ssh_msg_kexinit{} - last_size_rekey = 0, - event_queue = [], - connection_queue, - address, - port, - opts, - recbuf - }). +%%% 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 + renegotiate/1, renegotiate_data/1 % Export intended for test cases + ]). %%==================================================================== %% Start / stop @@ -99,7 +79,7 @@ ) -> {ok, pid()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . start_link(Role, Socket, Options) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Socket, Options]])}. + {ok, proc_lib:spawn_link(?MODULE, init_connection_handler, [Role, Socket, Options])}. %%-------------------------------------------------------------------- @@ -352,53 +332,149 @@ close(ConnectionHandler, ChannelId) -> end. %%==================================================================== -%% gen_statem callbacks +%% Internal process state %%==================================================================== -%%-------------------------------------------------------------------- +-record(state, { + starter :: pid(), + auth_user :: string(), + connection_state :: #connection{}, + latest_channel_id = 0 :: non_neg_integer(), + idle_timer_ref :: infinity | reference(), + transport_protocol :: atom(), % ex: tcp + transport_cb :: atom(), % ex: gen_tcp + 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(), + key_exchange_init_msg :: #ssh_msg_kexinit{}, + last_size_rekey = 0 :: non_neg_integer(), + event_queue = [] :: list(), + opts :: proplists:proplist(), + recbuf :: pos_integer() + }). +%%==================================================================== +%% Intitialisation +%%==================================================================== +%%-------------------------------------------------------------------- +-spec init_connection_handler(role(), + inet:socket(), + proplists:proplist() + ) -> no_return(). %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - -init([Role, Socket, SshOpts]) -> +init_connection_handler(Role, Socket, Opts) -> process_flag(trap_exit, true), - {NumVsn, StrVsn} = ssh_transport:versions(Role, SshOpts), - {Protocol, Callback, CloseTag} = - proplists:get_value(transport, SshOpts, {tcp, gen_tcp, tcp_closed}), - Cache = ssh_channel:cache_create(), - State = - init_role(Role, - #state{ - connection_state = #connection{channel_cache = Cache, - channel_id_seed = 0, - port_bindings = [], - requests = [], - options = SshOpts}, - socket = Socket, - decoded_data_buffer = <<>>, - encoded_data_buffer = <<>>, - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag, - opts = SshOpts - }), - - try init_ssh_record(Role, NumVsn, StrVsn, SshOpts, Socket) of - Ssh -> - gen_statem:enter_loop(?MODULE, - [], %%[{debug,[trace,log,statistics,debug]} || Role==server], - handle_event_function, - {hello,Role}, - State#state{ssh_params = Ssh}, - []) + S0 = init_process_state(Role, Socket, Opts), + try + {Protocol, Callback, CloseTag} = + proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}), + S0#state{ssh_params = init_ssh_record(Role, Socket, Opts), + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag + } + of + S -> gen_statem:enter_loop(?MODULE, + [], %%[{debug,[trace,log,statistics,debug]} || Role==server], + handle_event_function, + {hello,Role}, + S, + []) catch - _:Error -> - gen_statem:enter_loop(?MODULE, - [], - handle_event_function, - {init_error,Error}, - State, - []) + _:Error -> init_error(Error, S0) + end. + + +init_error(Error, S) -> + gen_statem:enter_loop(?MODULE, [], handle_event_function, {init_error,Error}, S, []). + + +init_process_state(Role, Socket, Opts) -> + S = #state{connection_state = + C = #connection{channel_cache = ssh_channel:cache_create(), + channel_id_seed = 0, + port_bindings = [], + requests = [], + options = Opts}, + starter = proplists:get_value(user_pid, Opts), + socket = Socket, + decoded_data_buffer = <<>>, + encoded_data_buffer = <<>>, + opts = Opts + }, + case Role of + client -> + TimerRef = get_idle_time(Opts), + timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), + timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), + S#state{idle_timer_ref = TimerRef}; + + server -> + S#state{connection_state = init_connection(Role, C, Opts)} + end. + + +init_connection(server, C = #connection{}, Opts) -> + Sups = proplists:get_value(supervisors, Opts), + SystemSup = proplists:get_value(system_sup, Sups), + SubSystemSup = proplists:get_value(subsystem_sup, Sups), + ConnectionSup = proplists:get_value(connection_sup, Sups), + Shell = proplists:get_value(shell, Opts), + Exec = proplists:get_value(exec, Opts), + CliSpec = proplists:get_value(ssh_cli, Opts, {ssh_cli, [Shell]}), + C#connection{cli_spec = CliSpec, + exec = Exec, + system_supervisor = SystemSup, + sub_system_supervisor = SubSystemSup, + connection_supervisor = ConnectionSup + }. + + +init_ssh_record(Role, Socket, Opts) -> + {ok, PeerAddr} = inet:peername(Socket), + KeyCb = proplists:get_value(key_cb, Opts, ssh_file), + AuthMethods = proplists:get_value(auth_methods, Opts, ?SUPPORTED_AUTH_METHODS), + S0 = #ssh{role = Role, + key_cb = KeyCb, + opts = Opts, + userauth_supported_methods = AuthMethods, + available_host_keys = supported_host_keys(Role, KeyCb, Opts), + random_length_padding = proplists:get_value(max_random_length_padding, + Opts, + (#ssh{})#ssh.random_length_padding) + }, + + {Vsn, Version} = ssh_transport:versions(Role, Opts), + case Role of + client -> + PeerName = proplists:get_value(host, Opts), + S0#ssh{c_vsn = Vsn, + c_version = Version, + io_cb = case proplists:get_value(user_interaction, Opts, true) of + true -> ssh_io; + false -> ssh_no_io + end, + userauth_quiet_mode = proplists:get_value(quiet_mode, Opts, false), + peer = {PeerName, PeerAddr} + }; + + server -> + S0#ssh{s_vsn = Vsn, + s_version = Version, + io_cb = proplists:get_value(io_cb, Opts, ssh_io), + userauth_methods = string:tokens(AuthMethods, ","), + kb_tries_left = 3, + peer = {undefined, PeerAddr} + } end. + + +%%==================================================================== +%% gen_statem callbacks +%%==================================================================== %%-------------------------------------------------------------------- %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1301,129 +1377,6 @@ start_the_connection_child(UserPid, Role, Socket, Options) -> socket_control(Socket, Pid, Callback), Pid. - -init_role(client, #state{opts = Opts} = State0) -> - Pid = proplists:get_value(user_pid, Opts), - TimerRef = get_idle_time(Opts), - timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), - timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, - [self(), data_size]), - State0#state{starter = Pid, - idle_timer_ref = TimerRef}; - -init_role(server, #state{opts = Opts, connection_state = Connection} = State) -> - Sups = proplists:get_value(supervisors, Opts), - Pid = proplists:get_value(user_pid, Opts), - SystemSup = proplists:get_value(system_sup, Sups), - SubSystemSup = proplists:get_value(subsystem_sup, Sups), - ConnectionSup = proplists:get_value(connection_sup, Sups), - Shell = proplists:get_value(shell, Opts), - Exec = proplists:get_value(exec, Opts), - CliSpec = proplists:get_value(ssh_cli, Opts, {ssh_cli, [Shell]}), - State#state{starter = Pid, - connection_state = Connection#connection{ - cli_spec = CliSpec, - exec = Exec, - system_supervisor = SystemSup, - sub_system_supervisor = SubSystemSup, - connection_supervisor = ConnectionSup - }}. - - -%% init_ssh_record(client = Role, Vsn, Version, Options, Socket) -> -%% IOCb = case proplists:get_value(user_interaction, Options, true) of -%% true -> -%% ssh_io; -%% false -> -%% ssh_no_io -%% end, - -%% AuthMethods = proplists:get_value(auth_methods, Options, -%% ?SUPPORTED_AUTH_METHODS), -%% {ok, PeerAddr} = inet:peername(Socket), - -%% PeerName = proplists:get_value(host, Options), -%% KeyCb = proplists:get_value(key_cb, Options, ssh_file), - -%% #ssh{role = Role, -%% c_vsn = Vsn, -%% c_version = Version, -%% key_cb = KeyCb, -%% io_cb = IOCb, -%% userauth_quiet_mode = proplists:get_value(quiet_mode, Options, false), -%% opts = Options, -%% userauth_supported_methods = AuthMethods, -%% peer = {PeerName, PeerAddr}, -%% available_host_keys = supported_host_keys(Role, KeyCb, Options), -%% random_length_padding = proplists:get_value(max_random_length_padding, -%% Options, -%% (#ssh{})#ssh.random_length_padding) -%% }; - -%% init_ssh_record(server = Role, Vsn, Version, Options, Socket) -> -%% AuthMethods = proplists:get_value(auth_methods, Options, -%% ?SUPPORTED_AUTH_METHODS), -%% AuthMethodsAsList = string:tokens(AuthMethods, ","), -%% {ok, PeerAddr} = inet:peername(Socket), -%% KeyCb = proplists:get_value(key_cb, Options, ssh_file), - -%% #ssh{role = Role, -%% s_vsn = Vsn, -%% s_version = Version, -%% key_cb = KeyCb, -%% io_cb = proplists:get_value(io_cb, Options, ssh_io), -%% opts = Options, -%% userauth_supported_methods = AuthMethods, -%% userauth_methods = AuthMethodsAsList, -%% kb_tries_left = 3, -%% peer = {undefined, PeerAddr}, -%% available_host_keys = supported_host_keys(Role, KeyCb, Options), -%% random_length_padding = proplists:get_value(max_random_length_padding, -%% Options, -%% (#ssh{})#ssh.random_length_padding) -%% }. - - -init_ssh_record(Role, Vsn, Version, Options, Socket) -> - {ok, PeerAddr} = inet:peername(Socket), - KeyCb = proplists:get_value(key_cb, Options, ssh_file), - AuthMethods = proplists:get_value(auth_methods, Options, ?SUPPORTED_AUTH_METHODS), - - S0 = #ssh{role = Role, - key_cb = KeyCb, - opts = Options, - userauth_supported_methods = AuthMethods, - available_host_keys = supported_host_keys(Role, KeyCb, Options), - random_length_padding = proplists:get_value(max_random_length_padding, - Options, - (#ssh{})#ssh.random_length_padding) - }, - - case Role of - client -> - PeerName = proplists:get_value(host, Options), - S0#ssh{c_vsn = Vsn, - c_version = Version, - io_cb = case proplists:get_value(user_interaction, Options, true) of - true -> ssh_io; - false -> ssh_no_io - end, - userauth_quiet_mode = proplists:get_value(quiet_mode, Options, false), - peer = {PeerName, PeerAddr} - }; - - server -> - S0#ssh{s_vsn = Vsn, - s_version = Version, - io_cb = proplists:get_value(io_cb, Options, ssh_io), - userauth_methods = string:tokens(AuthMethods, ","), - kb_tries_left = 3, - peer = {undefined, PeerAddr} - } - end. - - - %%-------------------------------------------------------------------- %% Stopping diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl index 4269529ae8..e34071af99 100644 --- a/lib/ssh/test/ssh_trpt_test_lib.erl +++ b/lib/ssh/test/ssh_trpt_test_lib.erl @@ -294,12 +294,11 @@ instantiate(X, _S) -> %%%================================================================ %%% init_ssh(Role, Socket, Options0) -> - Options = [{user_interaction,false} + Options = [{user_interaction, false}, + {vsn, {2,0}}, + {id_string, "ErlangTestLib"} | Options0], - ssh_connection_handler:init_ssh(Role, - {2,0}, - lists:concat(["SSH-2.0-ErlangTestLib ",Role]), - Options, Socket). + ssh_connection_handler:init_ssh_record(Role, Socket, Options). mangle_opts(Options) -> SysOpts = [{reuseaddr, true}, -- cgit v1.2.3 From e21bebd0550c46cbb5d207bdbe4624f727191fda Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 15 Apr 2016 14:47:04 +0200 Subject: ssh: change #state to #data --- lib/ssh/src/ssh_connection_handler.erl | 288 ++++++++++++++++----------------- 1 file changed, 144 insertions(+), 144 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 6d5cd3f262..2468791c20 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -334,7 +334,7 @@ close(ConnectionHandler, ChannelId) -> %%==================================================================== %% Internal process state %%==================================================================== --record(state, { +-record(data, { starter :: pid(), auth_user :: string(), connection_state :: #connection{}, @@ -370,7 +370,7 @@ init_connection_handler(Role, Socket, Opts) -> try {Protocol, Callback, CloseTag} = proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}), - S0#state{ssh_params = init_ssh_record(Role, Socket, Opts), + S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), transport_protocol = Protocol, transport_cb = Callback, transport_close_tag = CloseTag @@ -392,7 +392,7 @@ init_error(Error, S) -> init_process_state(Role, Socket, Opts) -> - S = #state{connection_state = + S = #data{connection_state = C = #connection{channel_cache = ssh_channel:cache_create(), channel_id_seed = 0, port_bindings = [], @@ -409,10 +409,10 @@ init_process_state(Role, Socket, Opts) -> TimerRef = get_idle_time(Opts), timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]), - S#state{idle_timer_ref = TimerRef}; + S#data{idle_timer_ref = TimerRef}; server -> - S#state{connection_state = init_connection(Role, C, Opts)} + S#data{connection_state = init_connection(Role, C, Opts)} end. @@ -494,19 +494,19 @@ handle_event(_, _Event, {init_error,OtherError}, _State) -> %%% ######## {hello, client|server} #### -handle_event(_, socket_control, StateName={hello,_}, S=#state{socket=Socket, +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 {ok, Size} -> inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}, {nodelay,true}]), - {next_state, StateName, S#state{recbuf=Size}}; + {next_state, StateName, S#data{recbuf=Size}}; {error, Reason} -> {stop, {shutdown,Reason}} end; -handle_event(_, {info_line,_Line}, StateName={hello,client}, S=#state{socket=Socket}) -> +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}; @@ -516,7 +516,7 @@ handle_event(_, {info_line,_Line}, {hello,server}, S) -> send_bytes("Protocol mismatch.", S), {stop, {shutdown,"Protocol mismatch in version exchange."}}; -handle_event(_, {version_exchange,Version}, {hello,Role}, S=#state{ssh_params = Ssh0, +handle_event(_, {version_exchange,Version}, {hello,Role}, S=#data{ssh_params = Ssh0, socket = Socket, recbuf = Size}) -> {NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version), @@ -525,7 +525,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, S=#state{ssh_params = inet:setopts(Socket, [{packet,0}, {mode,binary}, {active, once}, {recbuf, Size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), send_bytes(SshPacket, S), - {next_state, {kexinit,Role,init}, S#state{ssh_params = Ssh, + {next_state, {kexinit,Role,init}, S#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; not_supported -> disconnect( @@ -538,103 +538,103 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, S=#state{ssh_params = %%% ######## {kexinit, client|server, init|renegotiate} #### handle_event(_, {#ssh_msg_kexinit{} = Kex, Payload}, {kexinit,client,ReNeg}, - S = #state{ssh_params = Ssh0, + 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#state{ssh_params = Ssh}}; + {next_state, {key_exchange,client,ReNeg}, S#data{ssh_params = Ssh}}; handle_event(_, {#ssh_msg_kexinit{} = Kex, Payload}, {kexinit,server,ReNeg}, - S = #state{ssh_params = Ssh0, + 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#state{ssh_params = Ssh}}; + {next_state, {key_exchange,server,ReNeg}, S#data{ssh_params = Ssh}}; %%% ######## {key_exchange, client|server, init|renegotiate} #### handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, - S = #state{ssh_params = Ssh0}) -> + S = #data{ssh_params = Ssh0}) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, Ssh0), send_bytes(KexdhReply, S), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, S), - {next_state, {new_keys,server,ReNeg}, S#state{ssh_params = Ssh}}; + {next_state, {new_keys,server,ReNeg}, S#data{ssh_params = Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, - #state{ssh_params=Ssh0} = State) -> + #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#state{ssh_params = Ssh}}; + {next_state, {new_keys,client,ReNeg}, State#data{ssh_params = Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, - #state{ssh_params=Ssh0} = State) -> + #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#state{ssh_params = Ssh}}; + {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}, - #state{ssh_params=Ssh0} = State) -> + #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#state{ssh_params = Ssh}}; + {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}, - #state{ssh_params=Ssh0} = State) -> + #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#state{ssh_params = Ssh}}; + {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}, - #state{ssh_params=Ssh0} = State) -> + #data{ssh_params=Ssh0} = State) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, Ssh0), send_bytes(KexEcdhReply, State), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, State), - {next_state, {new_keys,server,ReNeg}, State#state{ssh_params = Ssh}}; + {next_state, {new_keys,server,ReNeg}, State#data{ssh_params = Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, - #state{ssh_params=Ssh0} = State) -> + #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#state{ssh_params = Ssh}}; + {next_state, {new_keys,client,ReNeg}, State#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}, - #state{ssh_params=Ssh0} = State) -> + #data{ssh_params=Ssh0} = State) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, Ssh0), send_bytes(KexGexReply, State), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), send_bytes(NewKeys, State), - {next_state, {new_keys,server,ReNeg}, State#state{ssh_params = Ssh}}; + {next_state, {new_keys,server,ReNeg}, State#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}, - #state{ssh_params=Ssh0} = State) -> + #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#state{ssh_params = Ssh1}}; + {next_state, {new_keys,client,ReNeg}, State#data{ssh_params = Ssh1}}; %%% ######## {new_keys, client|server} #### handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, - #state{ssh_params = Ssh0} = State) -> + #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#state{ssh_params=Ssh}}; + {next_state, {service_request,client}, State#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, - S = #state{ssh_params = Ssh0}) -> + S = #data{ssh_params = Ssh0}) -> {ok, Ssh} = ssh_transport:handle_new_keys(Msg, Ssh0), - {next_state, {service_request,server}, S#state{ssh_params = Ssh}}; + {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}; @@ -643,10 +643,10 @@ handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, S) -> %%% ######## {service_request, client|server} handle_event(_, #ssh_msg_service_request{name = "ssh-userauth"} = Msg, {service_request,server}, - #state{ssh_params = #ssh{session_id=SessionId} = Ssh0} = State) -> + #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#state{ssh_params = Ssh}}; + {next_state, {userauth,server}, State#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, @@ -654,27 +654,27 @@ handle_event(_, #ssh_msg_service_request{}, {service_request,server}=StateName, disconnect(Msg, StateName, State); handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, - #state{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> + #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), send_bytes(Msg, State), - {next_state, {userauth,client}, State#state{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; + {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}, - #state{ssh_params = #ssh{session_id = SessionId, + #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#state{ssh_params = Ssh}}; + {next_state, StateName, State#data{ssh_params = Ssh}}; handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", method = Method} = Msg, StateName={userauth,server}, - #state{ssh_params = #ssh{session_id = SessionId, + #data{ssh_params = #ssh{session_id = SessionId, service = "ssh-connection", peer = {_, Address}} = Ssh0, opts = Opts, starter = Pid} = State) -> @@ -686,15 +686,15 @@ handle_event(_, #ssh_msg_userauth_request{service = "ssh-connection", Pid ! ssh_connected, connected_fun(User, Address, Method, Opts), {next_state, {connected,server}, - State#state{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}}}; + 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#state{ssh_params = Ssh}}; + {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#state{ssh_params = Ssh}} + {next_state, StateName, State#data{ssh_params = Ssh}} end; false -> %% At least one non-erlang client does like this. Retry as the next event @@ -709,20 +709,20 @@ handle_event(_, #ssh_msg_userauth_request{service = Service}, {userauth,server}= description = "Unknown service"}, disconnect(Msg, StateName, State); -handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, #state{ssh_params = Ssh, +handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, #data{ssh_params = Ssh, starter = Pid} = State) -> Pid ! ssh_connected, - {next_state, {connected,client}, State#state{ssh_params=Ssh#ssh{authenticated = true}}}; + {next_state, {connected,client}, State#data{ssh_params=Ssh#ssh{authenticated = true}}}; handle_event(_, #ssh_msg_userauth_failure{}, {userauth,client}=StateName, - #state{ssh_params = #ssh{userauth_methods = []}} = State) -> + #data{ssh_params = #ssh{userauth_methods = []}} = State) -> 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); handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client}, - #state{ssh_params = Ssh0 = #ssh{userauth_methods=AuthMthds}} = State) -> + #data{ssh_params = Ssh0 = #ssh{userauth_methods=AuthMthds}} = State) -> %% The prefered authentication method failed try next method Ssh1 = case AuthMthds of none -> @@ -735,21 +735,21 @@ handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName= case ssh_auth:userauth_request_msg(Ssh1) of {disconnect, DisconnectMsg, {Msg, Ssh}} -> send_bytes(Msg, State), - disconnect(DisconnectMsg, StateName, State#state{ssh_params = Ssh}); + disconnect(DisconnectMsg, StateName, State#data{ssh_params = Ssh}); {"keyboard-interactive", {Msg, Ssh}} -> send_bytes(Msg, State), - {next_state, {userauth_keyboard_interactive,client}, State#state{ssh_params = Ssh}}; + {next_state, {userauth_keyboard_interactive,client}, State#data{ssh_params = Ssh}}; {_Method, {Msg, Ssh}} -> send_bytes(Msg, State), - {next_state, StateName, State#state{ssh_params = Ssh}} + {next_state, StateName, State#data{ssh_params = Ssh}} end; handle_event(_, #ssh_msg_userauth_banner{}, StateName={userauth,client}, - #state{ssh_params = #ssh{userauth_quiet_mode=true}} = State) -> + #data{ssh_params = #ssh{userauth_quiet_mode=true}} = State) -> {next_state, StateName, State}; handle_event(_, #ssh_msg_userauth_banner{message = Msg}, StateName={userauth,client}, - #state{ssh_params = #ssh{userauth_quiet_mode=false}} = State) -> + #data{ssh_params = #ssh{userauth_quiet_mode=false}} = State) -> io:format("~s", [Msg]), {next_state, StateName, State}; @@ -757,13 +757,13 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, StateName={userauth,cli %%% ######## {userauth_keyboard_interactive, client|server} handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client}, - #state{ssh_params = #ssh{io_cb=IoCb} = Ssh0} = State) -> + #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#state{ssh_params = Ssh}}; + {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}, - #state{ssh_params = #ssh{peer = {_,Address}} = Ssh0, + #data{ssh_params = #ssh{peer = {_,Address}} = Ssh0, opts = Opts, starter = Pid} = State) -> case ssh_auth:handle_userauth_info_response(Msg, Ssh0) of @@ -771,20 +771,20 @@ handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_inte send_bytes(Reply, State), Pid ! ssh_connected, connected_fun(User, Address, "keyboard-interactive", Opts), - {next_state, {connected,server}, State#state{auth_user = User, + {next_state, {connected,server}, State#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#state{ssh_params = Ssh}} + {next_state, {userauth,server}, State#data{ssh_params = Ssh}} end; handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, - #state{ssh_params = Ssh0 = #ssh{userauth_preference=Prefs0}} = State) -> + #data{ssh_params = Ssh0 = #ssh{userauth_preference=Prefs0}} = State) -> Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Prefs0, Method =/= "keyboard-interactive"], {next_state, {userauth,client}, - State#state{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}, + State#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}, [{next_event, internal, Msg}]}; handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, S) -> @@ -799,19 +799,19 @@ handle_event(_, Msg=#ssh_msg_userauth_info_request{}, {userauth_keyboard_interac %%% ######## {connected, client|server} #### -handle_event(_, {#ssh_msg_kexinit{},_} = Event, {connected,Role}, #state{ssh_params = Ssh0} = State0) -> +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#state{ssh_params = Ssh, + 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_disconnect{description=Desc} = Msg, StateName, - State0 = #state{connection_state = Connection0}) -> + 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#state.opts), + disconnect_fun(Desc, State#data.opts), {stop_and_reply, {shutdown,Desc}, Repls, State}; handle_event(_, #ssh_msg_ignore{}, StateName, State) -> @@ -819,7 +819,7 @@ handle_event(_, #ssh_msg_ignore{}, StateName, State) -> handle_event(_, #ssh_msg_debug{always_display = Display, message = DbgMsg, - language = Lang}, StateName, #state{opts = Opts} = State) -> + language = Lang}, StateName, #data{opts = Opts} = State) -> F = proplists:get_value(ssh_msg_debug_fun, Opts, fun(_ConnRef, _AlwaysDisplay, _Msg, _Language) -> ok end ), @@ -871,12 +871,12 @@ handle_event(internal, Msg=#ssh_msg_channel_success{}, StateName, Stat handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, State) -> handle_connection_msg(Msg, StateName, State); -handle_event(cast, renegotiate, {connected,Role}, #state{ssh_params=Ssh0} = 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 timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]), - {next_state, {kexinit,Role,renegotiate}, State#state{ssh_params = Ssh, + {next_state, {kexinit,Role,renegotiate}, State#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; handle_event(cast, renegotiate, StateName, State) -> @@ -884,17 +884,17 @@ handle_event(cast, renegotiate, StateName, State) -> {next_state, StateName, State}; %% Rekey due to sent data limit reached? -handle_event(cast, data_size, {connected,Role}, #state{ssh_params=Ssh0} = State) -> - {ok, [{send_oct,Sent0}]} = inet:getstat(State#state.socket, [send_oct]), - Sent = Sent0 - State#state.last_size_rekey, - MaxSent = proplists:get_value(rekey_limit, State#state.opts, 1024000000), +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 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#state{ssh_params = Ssh, + {next_state, {kexinit,Role,renegotiate}, State#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg, last_size_rekey = Sent0}}; _ -> @@ -910,7 +910,7 @@ handle_event(cast, _, StateName, State) when StateName /= {connected,server}, {next_state, StateName, State, [postpone]}; handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName={connected,_Role}, - #state{connection_state = + #data{connection_state = #connection{channel_cache = Cache}} = State0) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{recv_window_size = WinSize, @@ -938,7 +938,7 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName={connected,_Role}, end; handle_event(cast, {reply_request,success,ChannelId}, StateName={connected,_}, - #state{connection_state = + #data{connection_state = #connection{channel_cache = Cache}} = State0) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = RemoteId} -> @@ -965,8 +965,8 @@ handle_event(cast, {unknown,Data}, StateName={connected,_}, State) -> handle_event({call,From}, get_print_info, StateName, State) -> Reply = try - {inet:sockname(State#state.socket), - inet:peername(State#state.socket) + {inet:sockname(State#data.socket), + inet:peername(State#data.socket) } of {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])}; @@ -981,7 +981,7 @@ handle_event({call,From}, {connection_info, Options}, StateName, State) -> {next_state, StateName, State, [{reply,From,Info}]}; handle_event({call,From}, {channel_info,ChannelId,Options}, StateName, - State=#state{connection_state = #connection{channel_cache = Cache}}) -> + State=#data{connection_state = #connection{channel_cache = Cache}}) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{} = Channel -> Info = ssh_channel_info(Options, Channel, []), @@ -990,7 +990,7 @@ handle_event({call,From}, {channel_info,ChannelId,Options}, StateName, {next_state, StateName, State, [{reply,From,[]}]} end; -handle_event({call,From}, {info, ChannelPid}, StateName, State = #state{connection_state = +handle_event({call,From}, {info, ChannelPid}, StateName, State = #data{connection_state = #connection{channel_cache = Cache}}) -> Result = ssh_channel:cache_foldl( fun(Channel, Acc) when ChannelPid == all; @@ -1001,13 +1001,13 @@ handle_event({call,From}, {info, ChannelPid}, StateName, State = #state{connecti end, [], Cache), {next_state, StateName, State, [{reply, From, {ok,Result}}]}; -handle_event({call,From}, stop, StateName, #state{connection_state = Connection0} = State0) -> +handle_event({call,From}, stop, StateName, #data{connection_state = Connection0} = State0) -> {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#state{connection_state=Connection}}; + {stop_and_reply, normal, [{reply,From,ok}|Repls], State#data{connection_state=Connection}}; handle_event({call,_}, _, StateName, State) when StateName /= {connected,server}, StateName /= {connected,client} -> @@ -1028,26 +1028,26 @@ handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName={ {next_state, StateName, State}; handle_event({call,From}, {global_request, Pid, _, _, _} = Request, StateName={connected,_}, - #state{connection_state = #connection{channel_cache = Cache}} = State0) -> + #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,_}, - #state{connection_state = #connection{channel_cache=_Cache} = Connection0} = State0) -> + #data{connection_state = #connection{channel_cache=_Cache} = Connection0} = State0) -> case ssh_connection:channel_data(ChannelId, Type, Data, Connection0, From) of {{replies, Replies}, Connection} -> - {Repls,State} = send_replies(Replies, State0#state{connection_state = Connection}), + {Repls,State} = send_replies(Replies, State0#data{connection_state = Connection}), start_timeout(ChannelId, From, Timeout), {next_state, StateName, State, Repls}; {noreply, Connection} -> start_timeout(ChannelId, From, Timeout), - {next_state, StateName, State0#state{connection_state = Connection}} + {next_state, StateName, State0#data{connection_state = Connection}} end; handle_event({call,From}, {eof, ChannelId}, StateName={connected,_}, - #state{connection_state = #connection{channel_cache=Cache}} = State0) -> + #data{connection_state = #connection{channel_cache=Cache}} = State0) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id, sent_close = false} -> State = send_msg(ssh_connection:channel_eof_msg(Id), State0), @@ -1059,7 +1059,7 @@ handle_event({call,From}, {eof, ChannelId}, StateName={connected,_}, handle_event({call,From}, {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, StateName = {connected,_}, - #state{connection_state = #connection{channel_cache = Cache}} = State0) -> + #data{connection_state = #connection{channel_cache = Cache}} = State0) -> erlang:monitor(process, ChannelPid), {ChannelId, State1} = new_channel_id(State0), Msg = ssh_connection:channel_open_msg(Type, ChannelId, @@ -1080,7 +1080,7 @@ handle_event({call,From}, {next_state, StateName, remove_timer_ref(State)}; handle_event({call,From}, {send_window, ChannelId}, StateName={connected,_}, - #state{connection_state = #connection{channel_cache = Cache}} = State) -> + #data{connection_state = #connection{channel_cache = Cache}} = State) -> Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{send_window_size = WinSize, send_packet_size = Packsize} -> @@ -1091,7 +1091,7 @@ handle_event({call,From}, {send_window, ChannelId}, StateName={connected,_}, {next_state, StateName, State, [{reply,From,Reply}]}; handle_event({call,From}, {recv_window, ChannelId}, StateName={connected,_}, - #state{connection_state = #connection{channel_cache = Cache}} = State) -> + #data{connection_state = #connection{channel_cache = Cache}} = State) -> Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{recv_window_size = WinSize, recv_packet_size = Packsize} -> @@ -1102,7 +1102,7 @@ handle_event({call,From}, {recv_window, ChannelId}, StateName={connected,_}, {next_state, StateName, State, [{reply,From,Reply}]}; handle_event({call,From}, {close, ChannelId}, StateName={connected,_}, - #state{connection_state = + #data{connection_state = #connection{channel_cache = Cache}} = State0) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id} = Channel -> @@ -1115,17 +1115,17 @@ handle_event({call,From}, {close, ChannelId}, StateName={connected,_}, end; handle_event(info, {Protocol, Socket, "SSH-" ++ _ = Version}, StateName={hello,_}, - State=#state{socket = Socket, + 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=#state{socket = Socket, + 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 = - #state{socket = Socket, + #data{socket = Socket, transport_protocol = Protocol, decoded_data_buffer = DecData0, encoded_data_buffer = EncData0, @@ -1135,7 +1135,7 @@ handle_event(info, {Protocol, Socket, Data}, StateName, State0 = try ssh_transport:handle_packet_part(DecData0, Encoded, RemainingSshPacketLen0, Ssh0) of {decoded, Bytes, EncDataRest, Ssh1} -> - State = State0#state{ssh_params = + State = State0#data{ssh_params = Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)}, decoded_data_buffer = <<>>, undecoded_packet_length = undefined, @@ -1162,7 +1162,7 @@ handle_event(info, {Protocol, Socket, Data}, StateName, State0 = {get_more, DecBytes, EncDataRest, RemainingSshPacketLen, Ssh1} -> %% Here we know that there are not enough bytes in EncDataRest to use. Must wait. inet:setopts(Socket, [{active, once}]), - {next_state, StateName, State0#state{encoded_data_buffer = EncDataRest, + {next_state, StateName, State0#data{encoded_data_buffer = EncDataRest, decoded_data_buffer = DecBytes, undecoded_packet_length = RemainingSshPacketLen, ssh_params = Ssh1}}; @@ -1171,7 +1171,7 @@ handle_event(info, {Protocol, Socket, Data}, StateName, State0 = DisconnectMsg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, description = "Bad mac"}, - disconnect(DisconnectMsg, StateName, State0#state{ssh_params=Ssh1}); + disconnect(DisconnectMsg, StateName, State0#data{ssh_params=Ssh1}); {error, {exceeds_max_size,PacketLen}} -> DisconnectMsg = @@ -1188,17 +1188,17 @@ handle_event(info, {Protocol, Socket, Data}, StateName, State0 = end; handle_event(internal, prepare_next_packet, StateName, State) -> - Enough = erlang:max(8, State#state.ssh_params#ssh.decrypt_block_size), - case size(State#state.encoded_data_buffer) of + Enough = erlang:max(8, State#data.ssh_params#ssh.decrypt_block_size), + case size(State#data.encoded_data_buffer) of Sz when Sz >= Enough -> - self() ! {State#state.transport_protocol, State#state.socket, <<>>}; + self() ! {State#data.transport_protocol, State#data.socket, <<>>}; _ -> - inet:setopts(State#state.socket, [{active, once}]) + inet:setopts(State#data.socket, [{active, once}]) end, {next_state, StateName, State}; handle_event(info, {CloseTag,Socket}, StateName, - State=#state{socket = Socket, + State=#data{socket = Socket, transport_close_tag = CloseTag}) -> DisconnectMsg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, @@ -1206,11 +1206,11 @@ handle_event(info, {CloseTag,Socket}, StateName, disconnect(DisconnectMsg, StateName, State); handle_event(info, {timeout, {_, From} = Request}, StateName, - #state{connection_state = #connection{requests = Requests} = Connection} = State) -> + #data{connection_state = #connection{requests = Requests} = Connection} = State) -> case lists:member(Request, Requests) of true -> {next_state, StateName, - State#state{connection_state = + State#data{connection_state = Connection#connection{requests = lists:delete(Request, Requests)}}, [{reply,From,{error,timeout}}]}; @@ -1229,11 +1229,11 @@ handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> {stop, {shutdown, Reason}}; handle_event(info, {check_cache, _ , _}, StateName, - #state{connection_state = #connection{channel_cache=Cache}} = State) -> + #data{connection_state = #connection{channel_cache=Cache}} = State) -> {next_state, StateName, check_cache(State, Cache)}; handle_event(info, UnexpectedMessage, StateName, - State = #state{opts = Opts, + State = #data{opts = Opts, ssh_params = SshParams}) -> case unexpected_fun(UnexpectedMessage, Opts, SshParams) of report -> @@ -1325,7 +1325,7 @@ terminate(Reason, StateName, State0) -> format_status(normal, [_, _StateName, State]) -> [{data, [{"State", State}]}]; format_status(terminate, [_, _StateName, State]) -> - SshParams0 = (State#state.ssh_params), + SshParams0 = (State#data.ssh_params), SshParams = SshParams0#ssh{c_keyinit = "***", s_keyinit = "***", send_mac_key = "***", @@ -1344,7 +1344,7 @@ format_status(terminate, [_, _StateName, State]) -> keyex_key = "***", keyex_info = "***", available_host_keys = "***"}, - [{data, [{"State", State#state{decoded_data_buffer = "***", + [{data, [{"State", State#data{decoded_data_buffer = "***", encoded_data_buffer = "***", key_exchange_init_msg = "***", opts = "***", @@ -1380,7 +1380,7 @@ start_the_connection_child(UserPid, Role, Socket, Options) -> %%-------------------------------------------------------------------- %% Stopping -finalize_termination(_StateName, #state{transport_cb = Transport, +finalize_termination(_StateName, #data{transport_cb = Transport, connection_state = Connection, socket = Socket}) -> case Connection of @@ -1445,12 +1445,12 @@ available_host_key(KeyCb, Alg, Opts) -> element(1, catch KeyCb:host_key(Alg, Opts)) == ok. -send_msg(Msg, State=#state{ssh_params=Ssh0}) when is_tuple(Msg) -> +send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), send_bytes(Bytes, State), - State#state{ssh_params=Ssh}. + State#data{ssh_params=Ssh}. -send_bytes(Bytes, #state{socket = Socket, transport_cb = Transport}) -> +send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> Transport:send(Socket, Bytes). handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> @@ -1490,7 +1490,7 @@ call(FsmPid, Event, Timeout) -> handle_connection_msg(Msg, StateName, State0 = - #state{starter = User, + #data{starter = User, connection_state = Connection0, event_queue = Qev0}) -> Renegotiation = renegotiation(StateName), @@ -1500,28 +1500,28 @@ handle_connection_msg(Msg, StateName, State0 = case StateName of {connected,_} -> {Repls, State} = send_replies(Replies, - State0#state{connection_state=Connection}), + State0#data{connection_state=Connection}), {next_state, StateName, State, Repls}; _ -> {ConnReplies, Replies} = lists:splitwith(fun not_connected_filter/1, Replies), {Repls, State} = send_replies(Replies, - State0#state{event_queue = Qev0 ++ ConnReplies}), + State0#data{event_queue = Qev0 ++ ConnReplies}), {next_state, StateName, State, Repls} end; {noreply, Connection} -> - {next_state, StateName, State0#state{connection_state = Connection}}; + {next_state, StateName, State0#data{connection_state = Connection}}; {disconnect, Reason0, {{replies, Replies}, Connection}} -> - {Repls,State} = send_replies(Replies, State0#state{connection_state = Connection}), + {Repls,State} = send_replies(Replies, State0#data{connection_state = Connection}), case {Reason0,Role} of {{_, Reason}, client} when ((StateName =/= {connected,client}) and (not Renegotiation)) -> User ! {self(), not_connected, Reason}; _ -> ok end, - {stop, {shutdown,normal}, Repls, State#state{connection_state = Connection}} + {stop, {shutdown,normal}, Repls, State#data{connection_state = Connection}} catch _:Error -> @@ -1530,12 +1530,12 @@ handle_connection_msg(Msg, StateName, State0 = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, description = "Internal error"}, Connection0, Role), - {Repls,State} = send_replies(Replies, State0#state{connection_state = Connection}), - {stop, {shutdown,Error}, Repls, State#state{connection_state = Connection}} + {Repls,State} = send_replies(Replies, State0#data{connection_state = Connection}), + {stop, {shutdown,Error}, Repls, State#data{connection_state = Connection}} end. -set_prefix_if_trouble(Msg = <>, #state{ssh_params=SshParams}) +set_prefix_if_trouble(Msg = <>, #data{ssh_params=SshParams}) when Op == 30; Op == 31 -> @@ -1557,7 +1557,7 @@ kex(_) -> undefined. %%%---------------------------------------------------------------- handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, - #state{connection_state = + #data{connection_state = #connection{channel_cache = Cache}} = State0) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id} = Channel -> @@ -1570,7 +1570,7 @@ handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, end. handle_request(ChannelId, Type, Data, WantReply, From, - #state{connection_state = + #data{connection_state = #connection{channel_cache = Cache}} = State0) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id} -> @@ -1586,7 +1586,7 @@ handle_global_request({global_request, ChannelPid, "tcpip-forward" = Type, WantReply, <> = Data}, - #state{connection_state = + #data{connection_state = #connection{channel_cache = Cache} = Connection0} = State) -> ssh_channel:cache_update(Cache, #channel{user = ChannelPid, @@ -1594,15 +1594,15 @@ handle_global_request({global_request, ChannelPid, sys = none}), Connection = ssh_connection:bind(IP, Port, ChannelPid, Connection0), Msg = ssh_connection:global_request_msg(Type, WantReply, Data), - send_msg(Msg, State#state{connection_state = Connection}); + send_msg(Msg, State#data{connection_state = Connection}); handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type, WantReply, <> = Data}, - #state{connection_state = Connection0} = State) -> + #data{connection_state = Connection0} = State) -> Connection = ssh_connection:unbind(IP, Port, Connection0), Msg = ssh_connection:global_request_msg(Type, WantReply, Data), - send_msg(Msg, State#state{connection_state = Connection}); + send_msg(Msg, State#data{connection_state = Connection}); handle_global_request({global_request, _, "cancel-tcpip-forward" = Type, WantReply, Data}, State) -> @@ -1610,7 +1610,7 @@ handle_global_request({global_request, _, "cancel-tcpip-forward" = Type, send_msg(Msg, State). %%%---------------------------------------------------------------- -handle_idle_timeout(#state{opts = Opts}) -> +handle_idle_timeout(#data{opts = Opts}) -> case proplists:get_value(idle_time, Opts, infinity) of infinity -> ok; @@ -1618,7 +1618,7 @@ handle_idle_timeout(#state{opts = Opts}) -> erlang:send_after(IdleTime, self(), {check_cache, [], []}) end. -handle_channel_down(ChannelPid, #state{connection_state = +handle_channel_down(ChannelPid, #data{connection_state = #connection{channel_cache = Cache}} = State) -> ssh_channel:cache_foldl( @@ -1636,23 +1636,23 @@ update_sys(Cache, Channel, Type, ChannelPid) -> Channel#channel{sys = Type, user = ChannelPid}). add_request(false, _ChannelId, _From, State) -> State; -add_request(true, ChannelId, From, #state{connection_state = +add_request(true, ChannelId, From, #data{connection_state = #connection{requests = Requests0} = Connection} = State) -> Requests = [{ChannelId, From} | Requests0], - State#state{connection_state = Connection#connection{requests = Requests}}. + State#data{connection_state = Connection#connection{requests = Requests}}. -new_channel_id(#state{connection_state = #connection{channel_id_seed = Id} = +new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} = Connection} = State) -> - {Id, State#state{connection_state = + {Id, State#data{connection_state = 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#state.opts), + disconnect_fun(Description, State#data.opts), timer:sleep(400), {stop, {shutdown,Description}, State}. @@ -1699,19 +1699,19 @@ do_retry_fun(Fun, User, PeerAddr, Reason) -> ssh_info([], _State, Acc) -> Acc; -ssh_info([client_version | Rest], #state{ssh_params = #ssh{c_vsn = IntVsn, +ssh_info([client_version | Rest], #data{ssh_params = #ssh{c_vsn = IntVsn, c_version = StringVsn}} = State, Acc) -> ssh_info(Rest, State, [{client_version, {IntVsn, StringVsn}} | Acc]); -ssh_info([server_version | Rest], #state{ssh_params =#ssh{s_vsn = IntVsn, +ssh_info([server_version | Rest], #data{ssh_params =#ssh{s_vsn = IntVsn, s_version = StringVsn}} = State, Acc) -> ssh_info(Rest, State, [{server_version, {IntVsn, StringVsn}} | Acc]); -ssh_info([peer | Rest], #state{ssh_params = #ssh{peer = Peer}} = State, Acc) -> +ssh_info([peer | Rest], #data{ssh_params = #ssh{peer = Peer}} = State, Acc) -> ssh_info(Rest, State, [{peer, Peer} | Acc]); -ssh_info([sockname | Rest], #state{socket = Socket} = State, Acc) -> +ssh_info([sockname | Rest], #data{socket = Socket} = State, Acc) -> {ok, SockName} = inet:sockname(Socket), ssh_info(Rest, State, [{sockname, SockName}|Acc]); -ssh_info([user | Rest], #state{auth_user = User} = State, Acc) -> +ssh_info([user | Rest], #data{auth_user = User} = State, Acc) -> ssh_info(Rest, State, [{user, User}|Acc]); ssh_info([ _ | Rest], State, Acc) -> ssh_info(Rest, State, Acc). @@ -1796,7 +1796,7 @@ unexpected_fun(UnexpectedMessage, Opts, #ssh{peer={_,Peer}}) -> end. -check_cache(#state{opts = Opts} = State, Cache) -> +check_cache(#data{opts = Opts} = State, Cache) -> %% Check the number of entries in Cache case proplists:get_value(size, ets:info(Cache)) of 0 -> @@ -1810,21 +1810,21 @@ check_cache(#state{opts = Opts} = State, Cache) -> State end. -handle_idle_timer(Time, #state{idle_timer_ref = undefined} = State) -> +handle_idle_timer(Time, #data{idle_timer_ref = undefined} = State) -> TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), - State#state{idle_timer_ref=TimerRef}; + State#data{idle_timer_ref=TimerRef}; handle_idle_timer(_, State) -> State. remove_timer_ref(State) -> - case State#state.idle_timer_ref of + case State#data.idle_timer_ref of infinity -> %% If the timer is not activated State; undefined -> %% If we already has cancelled the timer State; TimerRef -> %% Timer is active erlang:cancel_timer(TimerRef), - State#state{idle_timer_ref = undefined} + State#data{idle_timer_ref = undefined} end. socket_control(Socket, Pid, Transport) -> -- cgit v1.2.3 From fcaf134960f30f96afd08626c2680be84094e1a9 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 15 Apr 2016 17:38:04 +0200 Subject: ssh: Partly refactor and polish ssh_connection_handler --- lib/ssh/src/ssh_connect.hrl | 2 +- lib/ssh/src/ssh_connection_handler.erl | 1378 +++++++++++++++++--------------- lib/ssh/src/ssh_transport.erl | 4 +- 3 files changed, 719 insertions(+), 665 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 3860bb3202..47a166dcfd 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -30,7 +30,7 @@ -define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE). -define(DEFAULT_TIMEOUT, 5000). --define(MAX_PROTO_VERSION, 255). +-define(MAX_PROTO_VERSION, 255). % Max length of the hello string %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 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 ]). @@ -304,6 +305,22 @@ channel_info(ConnectionHandler, ChannelId, Options) -> 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 = <>, - 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, - <> = 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), + <> = 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, < 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) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 83e75eb8c6..7cb3b75ac0 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -1039,7 +1039,7 @@ handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, {bad_mac, Ssh1}; true -> {Ssh, DecompressedPayload} = decompress(Ssh1, payload(DecryptedPacket)), - {decoded, DecompressedPayload, NextPacketBytes, Ssh} + {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh} end; aead -> PacketLenBin = DecryptedPfx, @@ -1049,7 +1049,7 @@ handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, {Ssh1, DecryptedSfx} -> DecryptedPacket = <>, {Ssh, DecompressedPayload} = decompress(Ssh1, payload(DecryptedPacket)), - {decoded, DecompressedPayload, NextPacketBytes, Ssh} + {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh} end end. -- cgit v1.2.3 From 0594459c07da22dd527d06ad694f30bbdd443c6e Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 20 Apr 2016 12:36:28 +0200 Subject: ssh: fix dialyzer warnings and errors --- lib/ssh/src/ssh_connection.erl | 15 +-- lib/ssh/src/ssh_connection_handler.erl | 218 ++++++++++++++++++--------------- lib/ssh/src/ssh_info.erl | 2 +- 3 files changed, 118 insertions(+), 117 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 6ca6ed6d77..25d552240a 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -306,22 +306,11 @@ l2b([]) -> channel_data(ChannelId, DataType, Data, Connection, From) when is_list(Data)-> - channel_data(ChannelId, DataType, -%% list_to_binary(Data), Connection, From); - l2b(Data), Connection, From); - %% try list_to_binary(Data) - %% of - %% B -> B - %% catch - %% _:_ -> io:format('BAD BINARY: ~p~n',[Data]), - %% unicode:characters_to_binary(Data) - %% end, - %% Connection, From); + channel_data(ChannelId, DataType, l2b(Data), Connection, From); channel_data(ChannelId, DataType, Data, #connection{channel_cache = Cache} = Connection, From) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id, sent_close = false} = Channel0 -> {SendList, Channel} = @@ -338,8 +327,6 @@ channel_data(ChannelId, DataType, Data, {{replies, Replies ++ FlowCtrlMsgs}, Connection}; _ -> {{replies,[{channel_request_reply,From,{error,closed}}]}, Connection} - %% gen_fsm:reply(From, {error, closed}), - %% {noreply, Connection} end. handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index b49562db9c..f2545c93df 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -163,18 +163,19 @@ disconnect(Msg = #ssh_msg_disconnect{}, ExtraInfo) -> %%-------------------------------------------------------------------- -spec open_channel(connection_ref(), string(), - binary(), + iodata(), pos_integer(), pos_integer(), timeout() - ) -> {ok, channel_id()} | {error, term()}. + ) -> {open, channel_id()} | {error, term()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . open_channel(ConnectionHandler, ChannelType, ChannelSpecificData, InitialWindowSize, MaxPacketSize, Timeout) -> call(ConnectionHandler, - {open, self(), + {open, + self(), ChannelType, InitialWindowSize, MaxPacketSize, ChannelSpecificData, Timeout}). @@ -254,14 +255,14 @@ send_eof(ConnectionHandler, ChannelId) -> %%-------------------------------------------------------------------- -spec info(connection_ref() - ) -> [ #channel{} ]. + ) -> {ok, [#channel{}]} . -spec info(connection_ref(), - pid() - ) -> [ #channel{} ]. + pid() | all + ) -> {ok, [#channel{}]} . %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . info(ConnectionHandler) -> - info(ConnectionHandler, {info, all}). + info(ConnectionHandler, all). info(ConnectionHandler, ChannelProcess) -> call(ConnectionHandler, {info, ChannelProcess}). @@ -340,24 +341,30 @@ renegotiate_data(ConnectionHandler) -> %% Internal process state %%==================================================================== -record(data, { - starter :: pid(), - auth_user :: string(), - connection_state :: #connection{}, - latest_channel_id = 0 :: non_neg_integer(), - idle_timer_ref :: infinity | reference(), - transport_protocol :: atom(), % ex: tcp - transport_cb :: atom(), % ex: gen_tcp - transport_close_tag :: atom(), % ex: tcp_closed - ssh_params :: #ssh{}, - socket :: inet:socket(), - 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_size :: pos_integer() + starter :: pid(), + auth_user :: string() + | undefined, + connection_state :: #connection{}, + latest_channel_id = 0 :: non_neg_integer(), + idle_timer_ref :: undefined + | infinity + | reference(), + transport_protocol :: atom(), % ex: tcp + transport_cb :: atom(), % ex: gen_tcp + transport_close_tag :: atom(), % ex: tcp_closed + ssh_params :: #ssh{} + | undefined, + socket :: inet:socket(), + decrypted_data_buffer = <<>> :: binary(), + encrypted_data_buffer = <<>> :: binary(), + undecrypted_packet_length :: undefined | non_neg_integer(), + key_exchange_init_msg :: #ssh_msg_kexinit{} + | undefined, + last_size_rekey = 0 :: non_neg_integer(), + event_queue = [] :: list(), + opts :: proplists:proplist(), + inet_initial_recbuf_size :: pos_integer() + | undefined }). %%==================================================================== @@ -381,21 +388,22 @@ init_connection_handler(Role, Socket, Opts) -> transport_close_tag = CloseTag } of - S -> gen_statem:enter_loop(?MODULE, - [], %%[{debug,[trace,log,statistics,debug]} || Role==server], - handle_event_function, - {hello,Role}, - S, - []) + S -> + gen_statem:enter_loop(?MODULE, + [], %%[{debug,[trace,log,statistics,debug]} || Role==server], + handle_event_function, + {hello,Role}, + S) catch - _:Error -> init_error(Error, S0) + _:Error -> + gen_statem:enter_loop(?MODULE, + [], + handle_event_function, + {init_error,Error}, + S0) end. -init_error(Error, S) -> - gen_statem:enter_loop(?MODULE, [], handle_event_function, {init_error,Error}, S, []). - - init_process_state(Role, Socket, Opts) -> S = #data{connection_state = C = #connection{channel_cache = ssh_channel:cache_create(), @@ -405,8 +413,6 @@ init_process_state(Role, Socket, Opts) -> options = Opts}, starter = proplists:get_value(user_pid, Opts), socket = Socket, - decrypted_data_buffer = <<>>, - encrypted_data_buffer = <<>>, opts = Opts }, case Role of @@ -509,7 +515,7 @@ init_ssh_record(Role, Socket, Opts) -> %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -%%% ######## Error in the initialiasation #### +%%% ######## Error in the initialisation #### handle_event(_, _Event, {init_error,Error}, _) -> case Error of @@ -540,7 +546,7 @@ handle_event(_, socket_control, {hello,_}, D) -> % be max ?MAX_PROTO_VERSION bytes: {recbuf, ?MAX_PROTO_VERSION}, {nodelay,true}]), - {keep_state, D#data{recbuf_size=Size}}; + {keep_state, D#data{inet_initial_recbuf_size=Size}}; {error, Reason} -> {stop, {shutdown,Reason}} end; @@ -567,7 +573,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D) -> inet:setopts(D#data.socket, [{packet,0}, {mode,binary}, {active, once}, - {recbuf, D#data.recbuf_size}]), + {recbuf, D#data.inet_initial_recbuf_size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), ok = send_bytes(SshPacket, D), {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh, @@ -1029,10 +1035,17 @@ handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) -> {keep_state_and_data, [{reply,From,[]}]} end; + +handle_event({call,From}, {info, all}, _, D) -> + Result = ssh_channel:cache_foldl(fun(Channel, Acc) -> + [Channel | Acc] + end, + [], cache(D)), + {keep_state_and_data, [{reply, From, {ok,Result}}]}; + handle_event({call,From}, {info, ChannelPid}, _, D) -> Result = ssh_channel:cache_foldl( - fun(Channel, Acc) when ChannelPid == all; - Channel#channel.user == ChannelPid -> + fun(Channel, Acc) when Channel#channel.user == ChannelPid -> [Channel | Acc]; (_, Acc) -> Acc @@ -1073,15 +1086,11 @@ handle_event({call,From}, {global_request, Pid, _, _, _} = Request, {connected,_ {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,D} = send_replies(Replies, D0#data{connection_state = Connection}), - start_timeout(ChannelId, From, Timeout), - {keep_state, D, Repls}; - {noreply, Connection} -> - start_timeout(ChannelId, From, Timeout), - {keep_state, D0#data{connection_state = Connection}} - end; + {{replies, Replies}, Connection} = + ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), + {Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}), + start_timeout(ChannelId, From, Timeout), + {keep_state, D, Repls}; handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) -> case ssh_channel:cache_lookup(cache(D0), ChannelId) of @@ -1148,19 +1157,17 @@ handle_event({call,From}, {close, ChannelId}, {connected,_}, D0) -> %%===== 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}) -> +handle_event(info, {Proto, Sock, Info}, {hello,_}, #data{socket = Sock, + transport_protocol = Proto}) -> + case Info of + "SSH-" ++ _ -> + {keep_state_and_data, [{next_event, internal, {version_exchange,Info}}]}; + _ -> + {keep_state_and_data, [{next_event, internal, {info_line,Info}}]} + end; + +handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, + transport_protocol = Proto}) -> try ssh_transport:handle_packet_part( D0#data.decrypted_data_buffer, <<(D0#data.encrypted_data_buffer)/binary, NewData/binary>>, @@ -1174,7 +1181,7 @@ handle_event(info, {Protocol, Socket, NewData}, StateName, undecrypted_packet_length = undefined, encrypted_data_buffer = EncryptedDataRest}, try - ssh_message:decode(set_prefix_if_trouble(DecryptedBytes,D)) + ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D)) of Msg = #ssh_msg_kexinit{} -> {keep_state, D, [{next_event, internal, {Msg,DecryptedBytes}}, @@ -1194,7 +1201,7 @@ handle_event(info, {Protocol, Socket, NewData}, StateName, {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}]), + inet:setopts(Sock, [{active, once}]), {keep_state, D0#data{encrypted_data_buffer = EncryptedDataRest, decrypted_data_buffer = DecryptedBytes, undecrypted_packet_length = RemainingSshPacketLen, @@ -1354,48 +1361,55 @@ terminate(Reason, StateName, State0) -> %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -format_status(normal, [_, _StateName, State]) -> - [{data, [{"State", State}]}]; -format_status(terminate, [_, _StateName, State]) -> - 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 - }}]}]. - +format_status(normal, [_, _StateName, D]) -> + [{data, [{"State", D}]}]; +format_status(terminate, [_, _StateName, D]) -> + DataPropList0 = fmt_stat_rec(record_info(fields, data), D, + [decrypted_data_buffer, + encrypted_data_buffer, + key_exchange_init_msg, + opts, + inet_initial_recbuf_size]), + SshPropList = fmt_stat_rec(record_info(fields, ssh), D#data.ssh_params, + [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]), + DataPropList = lists:keyreplace(ssh_params, 1, DataPropList0, + {ssh_params,SshPropList}), + [{data, [{"State", DataPropList}]}]. + + +fmt_stat_rec(FieldNames, Rec, Exclude) -> + Values = tl(tuple_to_list(Rec)), + [P || {K,_} = P <- lists:zip(FieldNames, Values), + not lists:member(K, Exclude)]. %%-------------------------------------------------------------------- --spec code_change(term(), +-spec code_change(term() | {down,term()}, state_name(), #data{}, term() - ) -> {ok, state_name(), #data{}}. + ) -> {gen_statem:callback_mode(), state_name(), #data{}}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . code_change(_OldVsn, StateName, State, _Extra) -> - {ok, StateName, State}. + {handle_event_function, StateName, State}. %%==================================================================== @@ -1577,7 +1591,7 @@ handle_connection_msg(Msg, StateName, State0 = end. -set_prefix_if_trouble(Msg = <>, #data{ssh_params=SshParams}) +set_kex_overload_prefix(Msg = <>, #data{ssh_params=SshParams}) when Op == 30; Op == 31 -> @@ -1591,7 +1605,7 @@ set_prefix_if_trouble(Msg = <>, #data{ssh_params=SshParams}) _ -> Msg end; -set_prefix_if_trouble(Msg, _) -> +set_kex_overload_prefix(Msg, _) -> Msg. kex(#ssh{algorithms=#alg{kex=Kex}}) -> Kex; diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl index 67130d5eac..0c24c09887 100644 --- a/lib/ssh/src/ssh_info.erl +++ b/lib/ssh/src/ssh_info.erl @@ -37,7 +37,7 @@ print() -> io:format("~s", [string()]). print(File) when is_list(File) -> - {ok,D} = file:open(File, write), + {ok,D} = file:open(File, [write]), print(D), file:close(D); print(D) -> -- cgit v1.2.3 From 73f90b506c5ceff51cd8a6f264dc8fe20dd9351d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 26 Apr 2016 13:02:39 +0200 Subject: ssh: remove user_passwords from dumped state --- lib/ssh/src/ssh_connection_handler.erl | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f2545c93df..46c45b6f68 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -1368,6 +1368,7 @@ format_status(terminate, [_, _StateName, D]) -> [decrypted_data_buffer, encrypted_data_buffer, key_exchange_init_msg, + user_passwords, opts, inet_initial_recbuf_size]), SshPropList = fmt_stat_rec(record_info(fields, ssh), D#data.ssh_params, -- cgit v1.2.3 From bbf8fb6e42e730a4037485c3313e63733d8c100b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 26 Apr 2016 12:45:49 +0200 Subject: ssh: Idle-timer refactoring and some cosmetics and inlineing --- lib/ssh/src/ssh.erl | 9 +-- lib/ssh/src/ssh_channel.erl | 5 +- lib/ssh/src/ssh_connection_handler.erl | 135 +++++++++++++++++---------------- 3 files changed, 73 insertions(+), 76 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 48ef8aad2a..2eae897ce2 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -86,7 +86,7 @@ connect(Host, Port, Options, Timeout) -> ConnectionTimeout = proplists:get_value(connect_timeout, Options, infinity), try Transport:connect(Host, Port, [ {active, false} | SocketOptions], ConnectionTimeout) of {ok, Socket} -> - Opts = [{user_pid, self()}, {host, Host} | fix_idle_time(SshOptions)], + Opts = [{user_pid,self()}, {host,Host} | SshOptions], ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error, Reason} -> {error, Reason} @@ -228,13 +228,6 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -fix_idle_time(SshOptions) -> - case proplists:get_value(idle_time, SshOptions) of - undefined -> - [{idle_time, infinity}|SshOptions]; - _ -> - SshOptions - end. start_daemon(Host, Port, Options, Inet) -> case handle_options(Options) of {error, _Reason} = Error -> diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index de6908bb38..a8e6ebde16 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -68,7 +68,7 @@ %% Internal application API -export([cache_create/0, cache_lookup/2, cache_update/2, cache_delete/1, cache_delete/2, cache_foldl/3, - cache_find/2, + cache_info/2, cache_find/2, get_print_info/1]). -record(state, { @@ -335,6 +335,9 @@ cache_delete(Cache) -> cache_foldl(Fun, Acc, Cache) -> ets:foldl(Fun, Acc, Cache). +cache_info(num_entries, Cache) -> + proplists:get_value(size, ets:info(Cache)). + cache_find(ChannelPid, Cache) -> case ets:match_object(Cache, #channel{user = ChannelPid}) of [] -> diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 46c45b6f68..6f9b2b3e22 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -349,6 +349,8 @@ renegotiate_data(ConnectionHandler) -> idle_timer_ref :: undefined | infinity | reference(), + idle_timer_value = infinity :: infinity + | pos_integer(), transport_protocol :: atom(), % ex: tcp transport_cb :: atom(), % ex: gen_tcp transport_close_tag :: atom(), % ex: tcp_closed @@ -405,7 +407,7 @@ init_connection_handler(Role, Socket, Opts) -> init_process_state(Role, Socket, Opts) -> - S = #data{connection_state = + D = #data{connection_state = C = #connection{channel_cache = ssh_channel:cache_create(), channel_id_seed = 0, port_bindings = [], @@ -420,10 +422,9 @@ init_process_state(Role, Socket, Opts) -> %% 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 = get_idle_time(Opts)}; - + cache_init_idle_timer(D); server -> - S#data{connection_state = init_connection(Role, C, Opts)} + D#data{connection_state = init_connection(Role, C, Opts)} end. @@ -537,8 +538,8 @@ handle_event(_, _Event, {init_error,Error}, _) -> 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} -> + case inet:getopts(Socket=D#data.socket, [recbuf]) of + {ok, [{recbuf,Size}]} -> %% Set the socket to the hello text line handling mode: inet:setopts(Socket, [{packet, line}, {active, once}, @@ -547,8 +548,9 @@ handle_event(_, socket_control, {hello,_}, D) -> {recbuf, ?MAX_PROTO_VERSION}, {nodelay,true}]), {keep_state, D#data{inet_initial_recbuf_size=Size}}; - {error, Reason} -> - {stop, {shutdown,Reason}} + + Other -> + {stop, {shutdown,{unexpected_getopts_return, Other}}} end; handle_event(_, {info_line,_Line}, {hello,Role}, D) -> @@ -1069,15 +1071,13 @@ handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, 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(D), - {keep_state, D}; + {keep_state, cache_request_idle_timer_check(D)}; 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(D), - {keep_state, D}; + {keep_state, cache_request_idle_timer_check(D)}; handle_event({call,From}, {global_request, Pid, _, _, _} = Request, {connected,_}, D0) -> D1 = handle_global_request(Request, D0), @@ -1122,7 +1122,7 @@ handle_event({call,From}, }), D = add_request(true, ChannelId, From, D2), start_timeout(ChannelId, From, Timeout), - {keep_state, remove_timer_ref(D)}; + {keep_state, cache_cancel_idle_timer(D)}; handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) -> Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of @@ -1149,8 +1149,7 @@ handle_event({call,From}, {close, ChannelId}, {connected,_}, D0) -> #channel{remote_id = Id} = Channel -> 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}]}; + {keep_state, cache_request_idle_timer_check(D1), [{reply,From,ok}]}; undefined -> {keep_state_and_data, [{reply,From,ok}]} end; @@ -1263,8 +1262,8 @@ handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D0) -> handle_event(info, {'EXIT', _Sup, Reason}, _, _) -> {stop, {shutdown, Reason}}; -handle_event(info, {check_cache, _ , _}, _, D) -> - {keep_state, check_cache(D)}; +handle_event(info, check_cache, _, D) -> + {keep_state, cache_check_set_idle_timer(D)}; handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> case unexpected_fun(UnexpectedMessage, D) of @@ -1462,14 +1461,6 @@ 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 - undefined - end. - supported_host_keys(client, _, Options) -> try case proplists:get_value(public_key, @@ -1663,14 +1654,6 @@ handle_global_request({global_request, _, "cancel-tcpip-forward" = Type, send_msg(Msg, State). %%%---------------------------------------------------------------- -handle_idle_timeout(#data{opts = Opts}) -> - case proplists:get_value(idle_time, Opts, infinity) of - infinity -> - ok; - IdleTime -> - erlang:send_after(IdleTime, self(), {check_cache, [], []}) - end. - handle_channel_down(ChannelPid, D) -> ssh_channel:cache_foldl( fun(Channel, Acc) when Channel#channel.user == ChannelPid -> @@ -1680,7 +1663,7 @@ handle_channel_down(ChannelPid, D) -> (_,Acc) -> Acc end, [], cache(D)), - {{replies, []}, check_cache(D)}. + {{replies, []}, cache_check_set_idle_timer(D)}. update_sys(Cache, Channel, Type, ChannelPid) -> @@ -1826,8 +1809,6 @@ get_repl(noreply, Acc) -> get_repl(X, Acc) -> exit({get_repl,X,Acc}). - - %%%---------------------------------------------------------------- disconnect_fun({disconnect,Msg}, D) -> disconnect_fun(Msg, D); @@ -1864,38 +1845,65 @@ debug_fun(#ssh_msg_debug{always_display = Display, end. +%%%---------------------------------------------------------------- +%%% Cache idle timer that closes the connection if there are no +%%% channels open for a while. + +cache_init_idle_timer(D) -> + case proplists:get_value(idle_time, D#data.opts, infinity) of + infinity -> + D#data{idle_timer_value = infinity, + idle_timer_ref = infinity % A flag used later... + }; + IdleTime -> + %% We dont want to set the timeout on first connect + D#data{idle_timer_value = IdleTime} + end. + -check_cache(D) -> - %% Check the number of entries in Cache - case proplists:get_value(size, ets:info(cache(D))) of +cache_check_set_idle_timer(D = #data{idle_timer_ref = undefined, + idle_timer_value = IdleTime}) -> + %% No timer set - shall we set one? + case ssh_channel:cache_info(num_entries, cache(D)) of + 0 when IdleTime == infinity -> + %% No. Meaningless to set a timer that fires in an infinite time... + D; 0 -> - case proplists:get_value(idle_time, D#data.opts, infinity) of - infinity -> - D; - Time -> - handle_idle_timer(Time, D) - end; + %% Yes, we'll set one since the cache is empty and it should not + %% be that for a specified time + D#data{idle_timer_ref = + erlang:send_after(IdleTime, self(), {'EXIT',[],"Timeout"})}; _ -> + %% No - there are entries in the cache D - end. + end; +cache_check_set_idle_timer(D) -> + %% There is already a timer set or the timeout time is infinite + D. + -handle_idle_timer(Time, #data{idle_timer_ref = undefined} = State) -> - TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), - State#data{idle_timer_ref=TimerRef}; -handle_idle_timer(_, State) -> - State. - -remove_timer_ref(State) -> - case State#data.idle_timer_ref of - infinity -> %% If the timer is not activated - State; - undefined -> %% If we already has cancelled the timer - State; - TimerRef -> %% Timer is active +cache_cancel_idle_timer(D) -> + case D#data.idle_timer_ref of + infinity -> + %% The timer is not activated + D; + undefined -> + %% The timer is already cancelled + D; + TimerRef -> + %% The timer is active erlang:cancel_timer(TimerRef), - State#data{idle_timer_ref = undefined} + D#data{idle_timer_ref = undefined} end. + +cache_request_idle_timer_check(D = #data{idle_timer_value = infinity}) -> + D; +cache_request_idle_timer_check(D = #data{idle_timer_value = IdleTime}) -> + erlang:send_after(IdleTime, self(), check_cache), + D. + +%%%---------------------------------------------------------------- socket_control(Socket, Pid, Transport) -> case Transport:controlling_process(Socket, Pid) of ok -> @@ -1933,10 +1941,3 @@ start_timeout(_,_, infinity) -> start_timeout(Channel, From, Time) -> erlang:send_after(Time, self(), {timeout, {Channel, From}}). -getopt(Opt, Socket) -> - case inet:getopts(Socket, [Opt]) of - {ok, [{Opt, Value}]} -> - {ok, Value}; - Other -> - {error, {unexpected_getopts_return, Other}} - end. -- cgit v1.2.3 From b7f81aa55ffa161be01929b8d156e40bf751de17 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 26 Apr 2016 15:52:51 +0200 Subject: ssh: Channel request timer refactoring --- lib/ssh/src/ssh_connection_handler.erl | 121 +++++++++++++++++---------------- 1 file changed, 62 insertions(+), 59 deletions(-) (limited to 'lib') diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 6f9b2b3e22..e5229eb954 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -63,7 +63,7 @@ %%% Behaviour callbacks -export([handle_event/4, terminate/3, format_status/2, code_change/4]). -%%% Exports not intended to be used :) +%%% Exports not intended to be used :). They are used for spawning and tests -export([init_connection_handler/3, % proc_lib:spawn needs this init_ssh_record/3, % Export of this internal function % intended for low-level protocol test suites @@ -99,6 +99,8 @@ stop(ConnectionHandler)-> %% Internal application API %%==================================================================== +-define(DefaultTransport, {tcp, gen_tcp, tcp_closed} ). + %%-------------------------------------------------------------------- -spec start_connection(role(), inet:socket(), @@ -109,11 +111,8 @@ stop(ConnectionHandler)-> start_connection(client = Role, Socket, Options, Timeout) -> try {ok, Pid} = sshc_sup:start_child([Role, Socket, Options]), - {_, Callback, _} = - proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), - ok = socket_control(Socket, Pid, Callback), - Ref = erlang:monitor(process, Pid), - handshake(Pid, Ref, Timeout) + ok = socket_control(Socket, Pid, Options), + handshake(Pid, erlang:monitor(process,Pid), Timeout) catch exit:{noproc, _} -> {error, ssh_not_started}; @@ -383,7 +382,7 @@ init_connection_handler(Role, Socket, Opts) -> S0 = init_process_state(Role, Socket, Opts), try {Protocol, Callback, CloseTag} = - proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}), + proplists:get_value(transport, Opts, ?DefaultTransport), S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), transport_protocol = Protocol, transport_cb = Callback, @@ -1070,13 +1069,13 @@ handle_event({call,_}, _, StateName, _) when StateName /= {connected,server}, 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), + start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; 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), + start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_request_idle_timer_check(D)}; handle_event({call,From}, {global_request, Pid, _, _, _} = Request, {connected,_}, D0) -> @@ -1089,7 +1088,7 @@ handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, {connected,_}, {{replies, Replies}, Connection} = ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), {Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}), - start_timeout(ChannelId, From, Timeout), + start_channel_request_timer(ChannelId, From, Timeout), % FIXME: No message exchange so why? {keep_state, D, Repls}; handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) -> @@ -1121,7 +1120,7 @@ handle_event({call,From}, send_buf = queue:new() }), D = add_request(true, ChannelId, From, D2), - start_timeout(ChannelId, From, Timeout), + start_channel_request_timer(ChannelId, From, Timeout), {keep_state, cache_cancel_idle_timer(D)}; handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) -> @@ -1243,12 +1242,15 @@ handle_event(info, {CloseTag,Socket}, StateName, StateName, D); handle_event(info, {timeout, {_, From} = Request}, _, - #data{connection_state = #connection{requests = Requests} = C0} = D) -> + #data{connection_state = #connection{requests = Requests} = C0} = D) -> case lists:member(Request, Requests) of true -> + %% A channel request is not answered in time. Answer {error,timeout} + %% to the caller C = C0#connection{requests = lists:delete(Request, Requests)}, {keep_state, D#data{connection_state=C}, [{reply,From,{error,timeout}}]}; false -> + %% The request is answered - just ignore the timeout keep_state_and_data end; @@ -1424,8 +1426,7 @@ start_the_connection_child(UserPid, Role, Socket, Options) -> ConnectionSup = proplists:get_value(connection_sup, Sups), Opts = [{supervisors, Sups}, {user_pid, UserPid} | proplists:get_value(ssh_opts, Options, [])], {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Opts]), - {_, Callback, _} = proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), - socket_control(Socket, Pid, Callback), + ok = socket_control(Socket, Pid, Options), Pid. %%-------------------------------------------------------------------- @@ -1698,42 +1699,6 @@ 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, Method, #data{ssh_params = #ssh{peer = {_,Peer}}, - opts = Opts}) -> - case proplists:get_value(connectfun, Opts) of - undefined -> - ok; - Fun -> - catch Fun(User, Peer, Method) - end. - -retry_fun(_, undefined, _) -> - ok; -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, Info); - {arity, 3} -> - 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, @@ -1812,8 +1777,6 @@ get_repl(X, Acc) -> %%%---------------------------------------------------------------- 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 -> @@ -1845,6 +1808,41 @@ debug_fun(#ssh_msg_debug{always_display = Display, end. +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, Peer, Method) + end. + +retry_fun(_, undefined, _) -> + ok; +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, Info); + {arity, 3} -> + catch Fun(User, Peer, Info); + _ -> + ok + catch + _:_ -> + ok + end. + %%%---------------------------------------------------------------- %%% Cache idle timer that closes the connection if there are no %%% channels open for a while. @@ -1904,8 +1902,18 @@ cache_request_idle_timer_check(D = #data{idle_timer_value = IdleTime}) -> D. %%%---------------------------------------------------------------- -socket_control(Socket, Pid, Transport) -> - case Transport:controlling_process(Socket, Pid) of +start_channel_request_timer(_,_, infinity) -> + ok; +start_channel_request_timer(Channel, From, Time) -> + erlang:send_after(Time, self(), {timeout, {Channel, From}}). + +%%%---------------------------------------------------------------- +%%% Connection start and initalization helpers + +socket_control(Socket, Pid, Options) -> + {_, TransportCallback, _} = % For example {_,gen_tcp,_} + proplists:get_value(transport, Options, ?DefaultTransport), + case TransportCallback:controlling_process(Socket, Pid) of ok -> gen_statem:cast(Pid, socket_control); {error, Reason} -> @@ -1936,8 +1944,3 @@ handshake(Pid, Ref, Timeout) -> {error, timeout} end. -start_timeout(_,_, infinity) -> - ok; -start_timeout(Channel, From, Time) -> - erlang:send_after(Time, self(), {timeout, {Channel, From}}). - -- cgit v1.2.3 From 5fa07312d27a7ff6826bf943e3b9d6575be3acf1 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 29 Apr 2016 10:29:21 +0200 Subject: ssh: remove dialyzer 'no-local-return' warnings --- lib/ssh/src/ssh_no_io.erl | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'lib') diff --git a/lib/ssh/src/ssh_no_io.erl b/lib/ssh/src/ssh_no_io.erl index 2358560a26..1da257ed99 100644 --- a/lib/ssh/src/ssh_no_io.erl +++ b/lib/ssh/src/ssh_no_io.erl @@ -27,24 +27,36 @@ -export([yes_no/2, read_password/2, read_line/2, format/2]). + +-spec yes_no(any(), any()) -> no_return(). + yes_no(_, _) -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, description = "User interaction is not allowed"}, {no_io_allowed, yes_no}). + +-spec read_password(any(), any()) -> no_return(). + read_password(_, _) -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, description = "User interaction is not allowed"}, {no_io_allowed, read_password}). + +-spec read_line(any(), any()) -> no_return(). + read_line(_, _) -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, description = "User interaction is not allowed"}, {no_io_allowed, read_line}). + +-spec format(any(), any()) -> no_return(). + format(_, _) -> ssh_connection_handler:disconnect( #ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, -- cgit v1.2.3