diff options
-rw-r--r-- | lib/ssh/src/ssh.erl | 5 | ||||
-rw-r--r-- | lib/ssh/src/ssh.hrl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connect.hrl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 694 |
4 files changed, 431 insertions, 276 deletions
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 = <<EncData0/binary, Data/binary>>, 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}, |