diff options
author | Ingela Anderton Andin <[email protected]> | 2013-10-15 18:24:50 +0200 |
---|---|---|
committer | Ingela Anderton Andin <[email protected]> | 2013-11-13 10:58:19 +0100 |
commit | 68263a48bfbdac4dc219a91f06af3d535d881850 (patch) | |
tree | f36d0cbb6c097faa7a17e8e6ee677f8262e8c924 /lib | |
parent | d53cea682010766c82ba7088f40efcfafe196621 (diff) | |
download | otp-68263a48bfbdac4dc219a91f06af3d535d881850.tar.gz otp-68263a48bfbdac4dc219a91f06af3d535d881850.tar.bz2 otp-68263a48bfbdac4dc219a91f06af3d535d881850.zip |
ssh: Merge connection_manager and connection_handler processes
Also start adding dialyzer specs and removing dead code
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ssh/src/Makefile | 1 | ||||
-rw-r--r-- | lib/ssh/src/ssh.app.src | 1 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 134 | ||||
-rw-r--r-- | lib/ssh/src/ssh.hrl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_acceptor.erl | 19 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.erl | 95 | ||||
-rw-r--r-- | lib/ssh/src/ssh_bits.erl | 244 | ||||
-rw-r--r-- | lib/ssh/src/ssh_channel.erl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_channel_sup.erl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_cli.erl | 40 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connect.hrl | 5 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection.erl | 584 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_controler.erl | 137 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 1245 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_manager.erl | 914 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_sup.erl | 87 | ||||
-rw-r--r-- | lib/ssh/src/ssh_message.erl | 12 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftpd.erl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_subsystem_sup.erl | 16 | ||||
-rw-r--r-- | lib/ssh/src/ssh_system_sup.erl | 6 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 47 | ||||
-rw-r--r-- | lib/ssh/src/ssh_xfer.erl | 4 | ||||
-rw-r--r-- | lib/ssh/src/sshc_sup.erl | 6 | ||||
-rw-r--r-- | lib/ssh/test/ssh_basic_SUITE.erl | 14 |
24 files changed, 1286 insertions, 2337 deletions
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index a31e4a8525..caca355955 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -53,7 +53,6 @@ MODULES= \ ssh_connection_sup \ ssh_connection \ ssh_connection_handler \ - ssh_connection_manager \ ssh_shell \ ssh_system_sup \ ssh_subsystem_sup \ diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index bae8f82d01..9740b67dca 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -16,7 +16,6 @@ ssh_channel_sup, ssh_connection, ssh_connection_handler, - ssh_connection_manager, ssh_connection_sup, ssh_daemon_channel, ssh_shell, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 80d20abbbd..9cf10e7a3a 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -34,9 +34,8 @@ shell/1, shell/2, shell/3]). %%-------------------------------------------------------------------- -%% Function: start([, Type]) -> ok -%% -%% Type = permanent | transient | temporary +-spec start() -> ok. +-spec start(permanent | transient | temporary) -> ok. %% %% Description: Starts the ssh application. Default type %% is temporary. see application(3) @@ -54,7 +53,7 @@ start(Type) -> application:start(ssh, Type). %%-------------------------------------------------------------------- -%% Function: stop() -> ok +-spec stop() -> ok. %% %% Description: Stops the ssh application. %%-------------------------------------------------------------------- @@ -62,13 +61,8 @@ stop() -> application:stop(ssh). %%-------------------------------------------------------------------- -%% Function: connect(Host, Port, Options) -> -%% connect(Host, Port, Options, Timeout -> ConnectionRef | {error, Reason} -%% -%% Host - string() -%% Port - integer() -%% Options - [{Option, Value}] -%% Timeout - infinity | integer(). +-spec connect(string(), integer(), proplists:proplists()) -> {ok, pid()} | {error, term()}. +-spec connect(string(), integer(), proplists:proplists(), timeout()) -> {ok, pid()} | {error, term()}. %% %% Description: Starts an ssh connection. %%-------------------------------------------------------------------- @@ -80,82 +74,52 @@ connect(Host, Port, Options, Timeout) -> Error; {SocketOptions, SshOptions} -> DisableIpv6 = proplists:get_value(ipv6_disabled, SshOptions, false), + {_, Transport, _} = TransportOpts = + proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), Inet = inetopt(DisableIpv6), - do_connect(Host, Port, [Inet | SocketOptions], - [{user_pid, self()}, {host, Host} | fix_idle_time(SshOptions)], Timeout, DisableIpv6) + try Transport:connect(Host, Port, [ {active, false}, Inet | SocketOptions], Timeout) of + {ok, Socket} -> + Opts = [{user_pid, self()}, {host, Host} | fix_idle_time(SshOptions)], + ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); + {error, Reason} -> + {error, Reason} + catch + exit:{function_clause, _} -> + {error, {options, {transport, TransportOpts}}}; + exit:badarg -> + {error, {options, {socket_options, SocketOptions}}} + end end. -do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) -> - try sshc_sup:start_child([[{address, Host}, {port, Port}, - {role, client}, - {channel_pid, self()}, - {socket_opts, SocketOptions}, - {ssh_opts, SshOptions}]]) of - {ok, ConnectionSup} -> - {ok, Manager} = - ssh_connection_sup:connection_manager(ConnectionSup), - msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) - catch - exit:{noproc, _} -> - {error, ssh_not_started} - end. -msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) -> - receive - {Manager, is_connected} -> - {ok, Manager}; - %% When the connection fails - %% ssh_connection_sup:connection_manager - %% might return undefined as the connection manager - %% could allready have terminated, so we will not - %% match the Manager in this case - {_, not_connected, {error, econnrefused}} when DisableIpv6 == false -> - do_connect(Host, Port, proplists:delete(inet6, SocketOptions), - SshOptions, Timeout, true); - {_, not_connected, {error, Reason}} -> - {error, Reason}; - {_, not_connected, Other} -> - {error, Other}; - {From, user_password} -> - Pass = io:get_password(), - From ! Pass, - msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout); - {From, question} -> - Answer = io:get_line(""), - From ! Answer, - msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) - after Timeout -> - ssh_connection_manager:stop(Manager), - {error, timeout} - end. %%-------------------------------------------------------------------- -%% Function: close(ConnectionRef) -> ok +-spec close(pid()) -> ok. %% %% Description: Closes an ssh connection. %%-------------------------------------------------------------------- close(ConnectionRef) -> - ssh_connection_manager:stop(ConnectionRef). + ssh_connection_handler:stop(ConnectionRef). %%-------------------------------------------------------------------- -%% Function: connection_info(ConnectionRef) -> [{Option, Value}] +-spec connection_info(pid(), [atom()]) -> [{atom(), term()}]. %% %% Description: Retrieves information about a connection. %%-------------------------------------------------------------------- connection_info(ConnectionRef, Options) -> - ssh_connection_manager:connection_info(ConnectionRef, Options). + ssh_connection_handler:connection_info(ConnectionRef, Options). %%-------------------------------------------------------------------- -%% Function: channel_info(ConnectionRef) -> [{Option, Value}] +-spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}]. %% %% Description: Retrieves information about a connection. %%-------------------------------------------------------------------- channel_info(ConnectionRef, ChannelId, Options) -> - ssh_connection_manager:channel_info(ConnectionRef, ChannelId, Options). + ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- -%% Function: daemon(Port) -> -%% daemon(Port, Options) -> -%% daemon(Address, Port, Options) -> SshSystemRef -%% +-spec daemon(integer()) -> {ok, pid()}. +-spec daemon(integer(), proplists:proplist()) -> {ok, pid()}. +-spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()}. + %% Description: Starts a server listening for SSH connections %% on the given port. %%-------------------------------------------------------------------- @@ -187,9 +151,8 @@ daemon(HostAddr, Port, Options0) -> start_daemon(Host, Port, Options, Inet). %%-------------------------------------------------------------------- -%% Function: stop_listener(SysRef) -> ok -%% stop_listener(Address, Port) -> ok -%% +-spec stop_listener(pid()) -> ok. +-spec stop_listener(inet:ip_address(), integer()) -> ok. %% %% Description: Stops the listener, but leaves %% existing connections started by the listener up and running. @@ -200,9 +163,8 @@ stop_listener(Address, Port) -> ssh_system_sup:stop_listener(Address, Port). %%-------------------------------------------------------------------- -%% Function: stop_daemon(SysRef) -> ok -%%% stop_daemon(Address, Port) -> ok -%% +-spec stop_daemon(pid()) -> ok. +-spec stop_daemon(inet:ip_address(), integer()) -> ok. %% %% Description: Stops the listener and all connections started by %% the listener. @@ -213,9 +175,10 @@ stop_daemon(Address, Port) -> ssh_system_sup:stop_system(Address, Port). %%-------------------------------------------------------------------- -%% Function: shell(Host [,Port,Options]) -> {ok, ConnectionRef} | -%% {error, Reason} -%% +-spec shell(string()) -> _. +-spec shell(string(), proplists:proplist()) -> _. +-spec shell(string(), integer(), proplists:proplist()) -> _. + %% Host = string() %% Port = integer() %% Options = [{Option, Value}] @@ -247,25 +210,23 @@ shell(Host, Port, Options) -> end. %%-------------------------------------------------------------------- -%% Function: peername(ConnectionRef) -> {ok, {Host,Port}} -%% | {error,Error} +-spec peername(pid()) -> {ok, {inet:ip_address(), integer()}} | {error, term()}. %% %% Description: Returns the peer address of the connection %%-------------------------------------------------------------------- peername(ConnectionRef) -> [{peer, {_Name,{IP,Port}}}] = - ssh_connection_manager:connection_info(ConnectionRef, [peer]), + ssh_connection_handler:connection_info(ConnectionRef, [peer]), {ok, {IP,Port}}. %%-------------------------------------------------------------------- -%% Function: sockname(ConnectionRef) -> {ok, {Host,Port}} -%% | {error,Error} +-spec sockname(pid()) -> {ok, {inet:ip_address(), integer()}} | {error, term()}. %% %% Description: Returns the local address of the connection %%-------------------------------------------------------------------- sockname(ConnectionRef) -> [{sockname, Result}] = - ssh_connection_manager:connection_info(ConnectionRef, [sockname]), + ssh_connection_handler:connection_info(ConnectionRef, [sockname]), Result. %%-------------------------------------------------------------------- @@ -403,9 +364,9 @@ handle_ssh_option({user_dir, Value} = Opt) when is_list(Value) -> Opt; handle_ssh_option({user_dir_fun, Value} = Opt) when is_function(Value) -> Opt; -handle_ssh_option({silently_accept_hosts, Value} = Opt) when Value == true; Value == false -> +handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) -> Opt; -handle_ssh_option({user_interaction, Value} = Opt) when Value == true; Value == false -> +handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({public_key_alg, ssh_dsa}) -> {public_key_alg, 'ssh-dss'}; @@ -453,8 +414,7 @@ handle_ssh_option({disconnectfun , Value} = Opt) when is_function(Value) -> handle_ssh_option({failfun, Value} = Opt) when is_function(Value) -> Opt; -handle_ssh_option({ipv6_disabled, Value} = Opt) when Value == true; - Value == false -> +handle_ssh_option({ipv6_disabled, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({transport, {Protocol, Cb, ClosTag}} = Opt) when is_atom(Protocol), is_atom(Cb), @@ -469,8 +429,7 @@ handle_ssh_option({shell, {Module, Function, _}} = Opt) when is_atom(Module), Opt; handle_ssh_option({shell, Value} = Opt) when is_function(Value) -> Opt; -handle_ssh_option({quiet_mode, Value} = Opt) when Value == true; - Value == false -> +handle_ssh_option({quiet_mode, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> Opt; @@ -521,6 +480,3 @@ inetopt(false) -> inet end. -%%% -%% Deprecated -%%% diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 4fd347ba8f..94ced9da6f 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2012. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -29,6 +29,8 @@ -define(SSH_DEFAULT_PORT, 22). -define(SSH_MAX_PACKET_SIZE, (256*1024)). -define(SSH_LENGHT_INDICATOR_SIZE, 4). +-define(REKEY_TIMOUT, 3600000). +-define(REKEY_DATA_TIMOUT, 60000). -define(FALSE, 0). -define(TRUE, 1). diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index d023656c32..91905b2eaf 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -25,7 +25,6 @@ -export([start_link/5]). %% spawn export -%% TODO: system messages -export([acceptor_init/6, acceptor_loop/6]). -define(SLEEP_TIME, 200). @@ -81,17 +80,15 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) -> ListenSocket, AcceptTimeout) end. -handle_connection(Callback, Address, Port, Options, Socket) -> +handle_connection(_Callback, Address, Port, Options, Socket) -> SystemSup = ssh_system_sup:system_supervisor(Address, Port), {ok, SubSysSup} = ssh_system_sup:start_subsystem(SystemSup, Options), - ConnectionSup = ssh_system_sup:connection_supervisor(SystemSup), - {ok, Pid} = - ssh_connection_sup:start_manager_child(ConnectionSup, - [server, Socket, Options]), - Callback:controlling_process(Socket, Pid), - SshOpts = proplists:get_value(ssh_opts, Options), - Pid ! {start_connection, server, [Address, Port, Socket, SshOpts, SubSysSup]}. - + ConnectionSup = ssh_subsystem_sup:connection_supervisor(SubSysSup), + ssh_connection_handler:start_connection(server, Socket, + [{supervisors, [{system_sup, SystemSup}, + {subsystem_sup, SubSysSup}, + {connection_sup, ConnectionSup}]} + | Options], infinity). handle_error(timeout) -> ok; diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index f3d1a711ae..1fa3df847f 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -31,7 +31,6 @@ service_request_msg/1, init_userauth_request_msg/1, userauth_request_msg/1, handle_userauth_request/3, handle_userauth_info_request/3, handle_userauth_info_response/2 - %%userauth_messages/0 ]). %%-------------------------------------------------------------------- @@ -43,7 +42,6 @@ publickey_msg([Alg, #ssh{user = User, opts = Opts} = Ssh]) -> Hash = sha, %% Maybe option?! - %%ssh_bits:install_messages(userauth_pk_messages()), KeyCb = proplists:get_value(key_cb, Opts, ssh_file), case KeyCb:user_key(Alg, Opts) of @@ -69,7 +67,6 @@ publickey_msg([Alg, #ssh{user = User, password_msg([#ssh{opts = Opts, io_cb = IoCb, user = User, service = Service} = Ssh]) -> - %%ssh_bits:install_messages(userauth_passwd_messages()), Password = case proplists:get_value(password, Opts) of undefined -> user_interaction(IoCb, Ssh); @@ -99,7 +96,6 @@ user_interaction(IoCb, Ssh) -> %% See RFC 4256 for info on keyboard-interactive keyboard_interactive_msg([#ssh{user = User, service = Service} = Ssh]) -> - %%ssh_bits:install_messages(userauth_keyboard_interactive_messages()), ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, service = Service, @@ -239,7 +235,6 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, partial_success = false}, Ssh)} end; ?FALSE -> - %%ssh_bits:install_messages(userauth_pk_messages()), {not_authorized, {User, undefined}, ssh_transport:ssh_packet( #ssh_msg_userauth_pk_ok{algorithm_name = Alg, @@ -275,26 +270,10 @@ handle_userauth_info_request( 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", + description = "Server does not support" + "keyboard-interactive", language = "en"}). -%% userauth_messages() -> -%% [ {ssh_msg_userauth_request, ?SSH_MSG_USERAUTH_REQUEST, -%% [string, -%% string, -%% string, -%% '...']}, - -%% {ssh_msg_userauth_failure, ?SSH_MSG_USERAUTH_FAILURE, -%% [string, -%% boolean]}, - -%% {ssh_msg_userauth_success, ?SSH_MSG_USERAUTH_SUCCESS, -%% []}, - -%% {ssh_msg_userauth_banner, ?SSH_MSG_USERAUTH_BANNER, -%% [string, -%% string]}]. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -386,13 +365,8 @@ algorithm_string('ssh-rsa') -> algorithm_string('ssh-dss') -> "ssh-dss". -decode_keyboard_interactive_prompts(NumPrompts, Data) -> - Types = lists:append(lists:duplicate(NumPrompts, [string, boolean])), - pairwise_tuplify(ssh_bits:decode(Data, Types)). - -pairwise_tuplify([E1, E2 | Rest]) -> [{E1, E2} | pairwise_tuplify(Rest)]; -pairwise_tuplify([]) -> []. - +decode_keyboard_interactive_prompts(_NumPrompts, Data) -> + ssh_message:decode_keyboard_interactive_prompts(Data, []). keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> NumPrompts = length(PromptInfos), @@ -431,50 +405,29 @@ keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> end, Prompts). -%% userauth_passwd_messages() -> -%% [ -%% {ssh_msg_userauth_passwd_changereq, ?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, -%% [string, -%% string]} -%% ]. - -%% userauth_keyboard_interactive_messages() -> -%% [ {ssh_msg_userauth_info_request, ?SSH_MSG_USERAUTH_INFO_REQUEST, -%% [string, -%% string, -%% string, -%% uint32, -%% '...']}, - - %% {ssh_msg_userauth_info_response, ?SSH_MSG_USERAUTH_INFO_RESPONSE, - %% [uint32, - %% '...']} - %% ]. - -%% userauth_pk_messages() -> -%% [ {ssh_msg_userauth_pk_ok, ?SSH_MSG_USERAUTH_PK_OK, -%% [string, % algorithm name -%% binary]} % key blob -%% ]. - other_alg('ssh-rsa') -> 'ssh-dss'; other_alg('ssh-dss') -> 'ssh-rsa'. -decode_public_key_v2(K_S, "ssh-rsa") -> - case ssh_bits:decode(K_S,[string,mpint,mpint]) of - ["ssh-rsa", E, N] -> - {ok, #'RSAPublicKey'{publicExponent = E, modulus = N}}; - _ -> - {error, bad_format} - end; -decode_public_key_v2(K_S, "ssh-dss") -> - case ssh_bits:decode(K_S,[string,mpint,mpint,mpint,mpint]) of - ["ssh-dss",P,Q,G,Y] -> - {ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}}; - _ -> - {error, bad_format} - end; +decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, + ?UINT32(Len1), BinE:Len1/binary, + ?UINT32(Len2), BinN:Len2/binary>> + ,"ssh-rsa") -> + E = ssh_bits:erlint(Len1, BinE), + N = ssh_bits:erlint(Len2, BinN), + {ok, #'RSAPublicKey'{publicExponent = E, modulus = N}}; +decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, + ?UINT32(Len1), BinP:Len1/binary, + ?UINT32(Len2), BinQ:Len2/binary, + ?UINT32(Len3), BinG:Len3/binary, + ?UINT32(Len4), BinY:Len4/binary>> + , "ssh-dss") -> + P = ssh_bits:erlint(Len1, BinP), + Q = ssh_bits:erlint(Len2, BinQ), + G = ssh_bits:erlint(Len3, BinG), + Y = ssh_bits:erlint(Len4, BinY), + {ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}}; + decode_public_key_v2(_, _) -> {error, bad_format}. diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl index 1570e5ed4d..2b0241cb83 100644 --- a/lib/ssh/src/ssh_bits.erl +++ b/lib/ssh/src/ssh_bits.erl @@ -25,19 +25,9 @@ -include("ssh.hrl"). --export([encode/2, decode/2]). -%%-export([decode/1, decode/2, decode/3]). --export([mpint/1, bignum/1, string/1, name_list/1]). -%%-export([b64_encode/1, b64_decode/1]). -%%-export([install_messages/1, uninstall_messages/1]). - -%% integer utils --export([isize/1]). +-export([encode/2]). +-export([mpint/1, erlint/2, string/1, name_list/1]). -export([random/1]). -%%-export([xor_bits/2, fill_bits/2]). --export([i2bin/2, bin2i/1]). - -%%-import(lists, [foreach/2, reverse/1]). -define(name_list(X), (fun(B) -> ?binary(B) end)(list_to_binary(name_concat(X)))). @@ -95,38 +85,6 @@ mpint_pos(X,I,Ds) -> mpint_pos(X bsr 8,I+1,[(X band 255)|Ds]). -%% BIGNUM representation SSH1 -bignum(X) -> - XSz = isize(X), - Pad = (8 - (XSz rem 8)) rem 8, - <<?UINT16(XSz),0:Pad/unsigned-integer,X:XSz/big-unsigned-integer>>. - - -%% install_messages(Codes) -> -%% foreach(fun({Name, Code, Ts}) -> -%% put({msg_name,Code}, {Name,Ts}), -%% put({msg_code,Name}, {Code,Ts}) -%% end, Codes). - -%% uninstall_messages(Codes) -> -%% foreach(fun({Name, Code, _Ts}) -> -%% erase({msg_name,Code}), -%% erase({msg_code,Name}) -%% end, Codes). - -%% -%% Encode a record, the type spec is expected to be -%% in process dictionary under the key {msg_code, RecodeName} -%% -%% encode(Record) -> -%% case get({msg_code, element(1, Record)}) of -%% undefined -> -%% {error, unimplemented}; -%% {Code, Ts} -> -%% Data = enc(tl(tuple_to_list(Record)), Ts), -%% list_to_binary([Code, Data]) -%% end. - encode(List, Types) -> list_to_binary(enc(List, Types)). @@ -154,9 +112,6 @@ enc(Xs, [uint64|Ts], Offset) -> enc(Xs, [mpint|Ts], Offset) -> Y = mpint(hd(Xs)), [Y | enc(tl(Xs), Ts,Offset+size(Y))]; -enc(Xs, [bignum|Ts], Offset) -> - Y = bignum(hd(Xs)), - [Y | enc(tl(Xs),Ts,Offset+size(Y))]; enc(Xs, [string|Ts], Offset) -> X0 = hd(Xs), Y = ?string(X0), @@ -183,179 +138,14 @@ enc(Xs, ['...'| []], _Offset) -> X==undefined -> [] end; - enc([], [],_) -> []. -%% -%% Decode a SSH record the type is encoded as the first byte -%% and the type spec MUST be installed in {msg_name, ID} -%% - -%% decode(Binary = <<?BYTE(ID), _/binary>>) -> -%% case get({msg_name, ID}) of -%% undefined -> -%% {unknown, Binary}; -%% {Name, Ts} -> -%% {_, Elems} = decode(Binary,1,Ts), -%% list_to_tuple([Name | Elems]) -%% end. - -%% -%% Decode a binary form offset 0 -%% - -decode(Binary, Types) when is_binary(Binary) andalso is_list(Types) -> - {_,Elems} = decode(Binary, 0, Types), - Elems. - - -%% %% -%% %% Decode a binary from byte offset Offset -%% %% return {UpdatedOffset, DecodedElements} -%% %% -decode(Binary, Offset, Types) -> - decode(Binary, Offset, Types, []). - -decode(Binary, Offset, [Type|Ts], Acc) -> - case Type of - boolean -> - <<_:Offset/binary, ?BOOLEAN(X0), _/binary>> = Binary, - X = if X0 == 0 -> false; true -> true end, - decode(Binary, Offset+1, Ts, [X | Acc]); - - byte -> - <<_:Offset/binary, ?BYTE(X), _/binary>> = Binary, - decode(Binary, Offset+1, Ts, [X | Acc]); - - uint16 -> - <<_:Offset/binary, ?UINT16(X), _/binary>> = Binary, - decode(Binary, Offset+2, Ts, [X | Acc]); - - uint32 -> - <<_:Offset/binary, ?UINT32(X), _/binary>> = Binary, - decode(Binary, Offset+4, Ts, [X | Acc]); - - uint64 -> - <<_:Offset/binary, ?UINT64(X), _/binary>> = Binary, - decode(Binary, Offset+8, Ts, [X | Acc]); - - mpint -> - <<_:Offset/binary, ?UINT32(L), X0:L/binary,_/binary>> = Binary, - Sz = L*8, - <<X:Sz/big-signed-integer>> = X0, - decode(Binary, Offset+4+L, Ts, [X | Acc]); - - bignum -> - <<_:Offset/binary, ?UINT16(Bits),_/binary>> = Binary, - L = (Bits+7) div 8, - Pad = (8 - (Bits rem 8)) rem 8, - <<_:Offset/binary, _:16, _:Pad, X:Bits/big-unsigned-integer, - _/binary>> = Binary, - decode(Binary, Offset+2+L, Ts, [X | Acc]); - - string -> - Size = size(Binary), - if Size < Offset + 4 -> - %% empty string at end - {Size, lists:reverse(["" | Acc])}; - true -> - <<_:Offset/binary,?UINT32(L), X:L/binary,_/binary>> = - Binary, - decode(Binary, Offset+4+L, Ts, [binary_to_list(X) | - Acc]) - end; - - binary -> - <<_:Offset/binary,?UINT32(L), X:L/binary,_/binary>> = Binary, - decode(Binary, Offset+4+L, Ts, [X | Acc]); - - name_list -> - <<_:Offset/binary,?UINT32(L), X:L/binary,_/binary>> = Binary, - List = string:tokens(binary_to_list(X), ","), - decode(Binary, Offset+4+L, Ts, [List | Acc]); - - cookie -> - <<_:Offset/binary, X:16/binary, _/binary>> = Binary, - decode(Binary, Offset+16, Ts, [X | Acc]); - - {pad,N} -> %% pad offset to a multiple of N - K = (N - (Offset rem N)) rem N, - decode(Binary, Offset+K, Ts, Acc); - +erlint(Len, BinInt) -> + Sz = Len*8, + <<Int:Sz/big-signed-integer>> = BinInt, + Int. - '...' when Ts==[] -> - <<_:Offset/binary, X/binary>> = Binary, - {Offset+size(X), lists:reverse([X | Acc])} - end; - decode(_Binary, Offset, [], Acc) -> - {Offset, lists:reverse(Acc)}. - - - -%% HACK WARNING :-) --define(VERSION_MAGIC, 131). --define(SMALL_INTEGER_EXT, $a). --define(INTEGER_EXT, $b). --define(SMALL_BIG_EXT, $n). --define(LARGE_BIG_EXT, $o). - -isize(N) when N > 0 -> - case term_to_binary(N) of - <<?VERSION_MAGIC, ?SMALL_INTEGER_EXT, X>> -> - isize_byte(X); - <<?VERSION_MAGIC, ?INTEGER_EXT, X3,X2,X1,X0>> -> - isize_bytes([X3,X2,X1,X0]); - <<?VERSION_MAGIC, ?SMALL_BIG_EXT, S:8/big-unsigned-integer, 0, - Ds:S/binary>> -> - K = S - 1, - <<_:K/binary, Top>> = Ds, - isize_byte(Top)+K*8; - <<?VERSION_MAGIC, ?LARGE_BIG_EXT, S:32/big-unsigned-integer, 0, - Ds:S/binary>> -> - K = S - 1, - <<_:K/binary, Top>> = Ds, - isize_byte(Top)+K*8 - end; -isize(0) -> 0. - -%% big endian byte list -isize_bytes([0|L]) -> - isize_bytes(L); -isize_bytes([Top|L]) -> - isize_byte(Top) + length(L)*8. - -%% Well could be improved -isize_byte(X) -> - if X >= 2#10000000 -> 8; - X >= 2#1000000 -> 7; - X >= 2#100000 -> 6; - X >= 2#10000 -> 5; - X >= 2#1000 -> 4; - X >= 2#100 -> 3; - X >= 2#10 -> 2; - X >= 2#1 -> 1; - true -> 0 - end. - -%% Convert integer into binary -%% When XLen is the wanted size in octets of the output -i2bin(X, XLen) -> - XSz = isize(X), - Sz = XLen*8, - if Sz < XSz -> - exit(integer_to_large); - true -> - (<<X:Sz/big-unsigned-integer>>) - end. - -%% Convert a binary into an integer -%% -bin2i(X) -> - Sz = size(X)*8, - <<Y:Sz/big-unsigned-integer>> = X, - Y. - %% %% Create a binary with constant bytes %% @@ -373,15 +163,6 @@ fill(N,C) -> [C,Cs,Cs] end. -%% xor 2 binaries -%% xor_bits(XBits, YBits) -> -%% XSz = size(XBits)*8, -%% YSz = size(YBits)*8, -%% Sz = if XSz < YSz -> XSz; true -> YSz end, %% min -%% <<X:Sz, _/binary>> = XBits, -%% <<Y:Sz, _/binary>> = YBits, -%% <<(X bxor Y):Sz>>. - %% random/1 %% Generate N random bytes @@ -389,18 +170,5 @@ fill(N,C) -> random(N) -> crypto:strong_rand_bytes(N). -%% %% -%% %% Base 64 encode/decode -%% %% - -%% b64_encode(Bs) when is_list(Bs) -> -%% base64:encode(Bs); -%% b64_encode(Bin) when is_binary(Bin) -> -%% base64:encode(Bin). - -%% b64_decode(Bin) when is_binary(Bin) -> -%% base64:mime_decode(Bin); -%% b64_decode(Cs) when is_list(Cs) -> -%% base64:mime_decode(Cs). diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index 062ed764ca..508ae637cf 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -284,7 +284,7 @@ handle_info(Msg, #state{cm = ConnectionManager, channel_cb = Module, terminate(Reason, #state{cm = ConnectionManager, channel_id = ChannelId, close_sent = false} = State) -> - ssh_connection:close(ConnectionManager, ChannelId), + catch ssh_connection:close(ConnectionManager, ChannelId), terminate(Reason, State#state{close_sent = true}); terminate(_, #state{channel_cb = Cb, channel_state = ChannelState}) -> catch Cb:terminate(Cb, ChannelState), diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl index 0093bce9c2..ee37ed35f8 100644 --- a/lib/ssh/src/ssh_channel_sup.erl +++ b/lib/ssh/src/ssh_channel_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -31,7 +31,7 @@ -export([init/1]). %%%========================================================================= -%%% API +%%% Internal API %%%========================================================================= start_link(Args) -> supervisor:start_link(?MODULE, [Args]). diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 54911e757c..69a4d0b247 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -32,9 +32,6 @@ %% ssh_channel callbacks -export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). -%% backwards compatibility --export([listen/1, listen/2, listen/3, listen/4, stop/1]). - %% state -record(state, { cm, @@ -444,8 +441,9 @@ start_shell(ConnectionManager, State) -> {arity, 1} -> fun() -> Shell(User) end; {arity, 2} -> - {ok, PeerAddr} = - ssh_connection_manager:peer_addr(ConnectionManager), + [{ok, PeerAddr}] = + ssh_connection_handler:info(ConnectionManager, + [peer]), fun() -> Shell(User, PeerAddr) end; _ -> Shell @@ -470,8 +468,8 @@ start_shell(ConnectionManager, Cmd, #state{exec=Shell} = State) when is_function {arity, 2} -> fun() -> Shell(Cmd, User) end; {arity, 3} -> - {ok, PeerAddr} = - ssh_connection_manager:peer_addr(ConnectionManager), + [{ok, PeerAddr}] = + ssh_connection_handler:connection_info(ConnectionManager, [peer]), fun() -> Shell(Cmd, User, PeerAddr) end; _ -> Shell @@ -505,31 +503,3 @@ not_zero(0, B) -> not_zero(A, _) -> A. -%%% Backwards compatibility - -%%-------------------------------------------------------------------- -%% Function: listen(...) -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts a listening server -%% Note that the pid returned is NOT the pid of this gen_server; -%% this server is started when an SSH connection is made on the -%% listening port -%%-------------------------------------------------------------------- -listen(Shell) -> - listen(Shell, 22). - -listen(Shell, Port) -> - listen(Shell, Port, []). - -listen(Shell, Port, Opts) -> - listen(Shell, any, Port, Opts). - -listen(Shell, HostAddr, Port, Opts) -> - ssh:daemon(HostAddr, Port, [{shell, Shell} | Opts]). - - -%%-------------------------------------------------------------------- -%% Function: stop(Pid) -> ok -%% Description: Stops the listener -%%-------------------------------------------------------------------- -stop(Pid) -> - ssh:stop_listener(Pid). diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 932b0642f1..8421b07167 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2012. All Rights Reserved. +%% Copyright Ericsson AB 2005-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -21,6 +21,8 @@ %%% Description : SSH connection protocol +-type channel_id() :: integer(). + -define(DEFAULT_PACKET_SIZE, 32768). -define(DEFAULT_WINDOW_SIZE, 2*?DEFAULT_PACKET_SIZE). -define(DEFAULT_TIMEOUT, 5000). @@ -260,6 +262,7 @@ port, options, exec, + system_supervisor, sub_system_supervisor, connection_supervisor }). diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index ec3022e7c1..d3760f8295 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -29,232 +29,205 @@ -include("ssh_connect.hrl"). -include("ssh_transport.hrl"). +%% API -export([session_channel/2, session_channel/4, exec/4, shell/2, subsystem/4, send/3, send/4, send/5, - send_eof/2, adjust_window/3, open_pty/3, open_pty/7, - open_pty/9, setenv/5, window_change/4, window_change/6, + send_eof/2, adjust_window/3, setenv/5, close/2, reply_request/4]). + +%% Potential API currently unsupported and not tested +-export([open_pty/3, open_pty/7, + open_pty/9, window_change/4, window_change/6, direct_tcpip/6, direct_tcpip/8, tcpip_forward/3, - cancel_tcpip_forward/3, signal/3, exit_status/3, encode_ip/1, close/2, - reply_request/4]). + cancel_tcpip_forward/3, signal/3, exit_status/3]). --export([channel_data/6, handle_msg/4, channel_eof_msg/1, +%% Internal application API +-export([channel_data/5, handle_msg/3, channel_eof_msg/1, channel_close_msg/1, channel_success_msg/1, channel_failure_msg/1, + channel_status_msg/1, channel_adjust_window_msg/2, channel_data_msg/3, channel_open_msg/5, channel_open_confirmation_msg/4, channel_open_failure_msg/4, channel_request_msg/4, global_request_msg/3, request_failure_msg/0, request_success_msg/1, bind/4, unbind/3, unbind_channel/2, - bound_channel/3]). + bound_channel/3, encode_ip/1]). %%-------------------------------------------------------------------- -%%% Internal application API +%%% API %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- -%% Function: session_channel(ConnectionManager -%% [, InitialWindowSize, MaxPacketSize], -%% Timeout) -> {ok, } -%% ConnectionManager = pid() -%% InitialWindowSize = integer() -%% MaxPacketSize = integer() -%% +-spec session_channel(pid(), timeout()) -> {ok, channel_id()} | {error, term()}. +-spec session_channel(pid(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, term()}. + %% Description: Opens a channel for a ssh session. A session is a %% remote execution of a program. The program may be a shell, an %% application, a system command, or some built-in subsystem. %% -------------------------------------------------------------------- -session_channel(ConnectionManager, Timeout) -> - session_channel(ConnectionManager, + +session_channel(ConnectionHandler, Timeout) -> + session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). -session_channel(ConnectionManager, InitialWindowSize, + +session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> - ssh_connection_manager:open_channel(ConnectionManager, "session", <<>>, + case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>, InitialWindowSize, - MaxPacketSize, Timeout). + MaxPacketSize, Timeout) of + {open, Channel} -> + {ok, Channel}; + Error -> + Error + end. + %%-------------------------------------------------------------------- -%% Function: exec(ConnectionManager, ChannelId, Command, Timeout) -> -%% -%% ConnectionManager = pid() -%% ChannelId = integer() -%% Cmd = string() -%% Timeout = integer() -%% +-spec exec(pid(), channel_id(), string(), timeout()) -> success | failure. + %% Description: Will request that the server start the %% execution of the given command. %%-------------------------------------------------------------------- -exec(ConnectionManager, ChannelId, Command, TimeOut) -> - ssh_connection_manager:request(ConnectionManager, self(), ChannelId, "exec", - true, [?string(Command)], TimeOut). +exec(ConnectionHandler, ChannelId, Command, TimeOut) -> + ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec", + true, [?string(Command)], TimeOut). + %%-------------------------------------------------------------------- -%% Function: shell(ConnectionManager, ChannelId) -> -%% -%% ConnectionManager = pid() -%% ChannelId = integer() -%% +-spec shell(pid(), channel_id()) -> _. + %% Description: Will request that the user's default shell (typically %% defined in /etc/passwd in UNIX systems) be started at the other %% end. %%-------------------------------------------------------------------- -shell(ConnectionManager, ChannelId) -> - ssh_connection_manager:request(ConnectionManager, self(), ChannelId, +shell(ConnectionHandler, ChannelId) -> + ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "shell", false, <<>>, 0). %%-------------------------------------------------------------------- -%% Function: subsystem(ConnectionManager, ChannelId, SubSystem, TimeOut) -> -%% -%% ConnectionManager = pid() -%% ChannelId = integer() -%% SubSystem = string() -%% TimeOut = integer() -%% +-spec subsystem(pid(), channel_id(), string(), timeout()) -> + success | failure | {error, timeout}. %% %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- -subsystem(ConnectionManager, ChannelId, SubSystem, TimeOut) -> - ssh_connection_manager:request(ConnectionManager, self(), +subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> + ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "subsystem", true, [?string(SubSystem)], TimeOut). %%-------------------------------------------------------------------- -%% Function: send(ConnectionManager, ChannelId, Type, Data, [TimeOut]) -> +-spec send(pid(), channel_id(), iodata()) -> + ok | {error, closed}. +-spec send(pid(), channel_id(), integer()| iodata(), timeout() | iodata()) -> + ok | {error, timeout} | {error, closed}. +-spec send(pid(), channel_id(), integer(), iodata(), timeout()) -> + ok | {error, timeout} | {error, closed}. %% %% %% Description: Sends channel data. %%-------------------------------------------------------------------- -send(ConnectionManager, ChannelId, Data) -> - send(ConnectionManager, ChannelId, 0, Data, infinity). -send(ConnectionManager, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> - send(ConnectionManager, ChannelId, 0, Data, TimeOut); -send(ConnectionManager, ChannelId, Data, infinity) -> - send(ConnectionManager, ChannelId, 0, Data, infinity); -send(ConnectionManager, ChannelId, Type, Data) -> - send(ConnectionManager, ChannelId, Type, Data, infinity). -send(ConnectionManager, ChannelId, Type, Data, TimeOut) -> - ssh_connection_manager:send(ConnectionManager, ChannelId, +send(ConnectionHandler, ChannelId, Data) -> + send(ConnectionHandler, ChannelId, 0, Data, infinity). +send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> + send(ConnectionHandler, ChannelId, 0, Data, TimeOut); +send(ConnectionHandler, ChannelId, Data, infinity) -> + send(ConnectionHandler, ChannelId, 0, Data, infinity); +send(ConnectionHandler, ChannelId, Type, Data) -> + send(ConnectionHandler, ChannelId, Type, Data, infinity). +send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> + ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). %%-------------------------------------------------------------------- -%% Function: send_eof(ConnectionManager, ChannelId) -> +-spec send_eof(pid(), channel_id()) -> ok | {error, closed}. %% %% %% Description: Sends eof on the channel <ChannelId>. %%-------------------------------------------------------------------- -send_eof(ConnectionManager, Channel) -> - ssh_connection_manager:send_eof(ConnectionManager, Channel). +send_eof(ConnectionHandler, Channel) -> + ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- -%% Function: adjust_window(ConnectionManager, Channel, Bytes) -> +-spec adjust_window(pid(), channel_id(), integer()) -> ok. %% %% %% Description: Adjusts the ssh flowcontrol window. %%-------------------------------------------------------------------- -adjust_window(ConnectionManager, Channel, Bytes) -> - ssh_connection_manager:adjust_window(ConnectionManager, Channel, Bytes). +adjust_window(ConnectionHandler, Channel, Bytes) -> + ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes). %%-------------------------------------------------------------------- -%% Function: setenv(ConnectionManager, ChannelId, Var, Value, TimeOut) -> +-spec setenv(pid(), channel_id(), string(), string(), timeout()) -> success | failure. %% %% %% Description: Environment variables may be passed to the shell/command to be %% started later. %%-------------------------------------------------------------------- -setenv(ConnectionManager, ChannelId, Var, Value, TimeOut) -> - ssh_connection_manager:request(ConnectionManager, ChannelId, +setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) -> + ssh_connection_handler:request(ConnectionHandler, ChannelId, "env", true, [?string(Var), ?string(Value)], TimeOut). %%-------------------------------------------------------------------- -%% Function: close(ConnectionManager, ChannelId) -> +-spec close(pid(), channel_id()) -> ok. %% %% %% Description: Sends a close message on the channel <ChannelId>. %%-------------------------------------------------------------------- -close(ConnectionManager, ChannelId) -> - ssh_connection_manager:close(ConnectionManager, ChannelId). - +close(ConnectionHandler, ChannelId) -> + ssh_connection_handler:close(ConnectionHandler, ChannelId). %%-------------------------------------------------------------------- -%% Function: reply_request(ConnectionManager, WantReply, Status, CannelId) ->_ +-spec reply_request(pid(), boolean(), success | failure, channel_id()) -> ok. %% %% %% Description: Send status replies to requests that want such replies. %%-------------------------------------------------------------------- -reply_request(ConnectionManager, true, Status, ChannelId) -> - ssh_connection_manager:reply_request(ConnectionManager, Status, ChannelId), - ok; +reply_request(ConnectionHandler, true, Status, ChannelId) -> + ssh_connection_handler:reply_request(ConnectionHandler, Status, ChannelId); reply_request(_,false, _, _) -> ok. - %%-------------------------------------------------------------------- -%% Function: window_change(ConnectionManager, Channel, Width, Height) -> -%% -%% -%% Description: Not yet officialy supported. +%% Not yet officialy supported! The following functions are part of the +%% initial contributed ssh application. They are untested. Do we want them? +%% Should they be documented and tested? %%-------------------------------------------------------------------- -window_change(ConnectionManager, Channel, Width, Height) -> - window_change(ConnectionManager, Channel, Width, Height, 0, 0). -window_change(ConnectionManager, Channel, Width, Height, +window_change(ConnectionHandler, Channel, Width, Height) -> + window_change(ConnectionHandler, Channel, Width, Height, 0, 0). +window_change(ConnectionHandler, Channel, Width, Height, PixWidth, PixHeight) -> - ssh_connection_manager:request(ConnectionManager, Channel, + ssh_connection_handler:request(ConnectionHandler, Channel, "window-change", false, [?uint32(Width), ?uint32(Height), ?uint32(PixWidth), ?uint32(PixHeight)], 0). -%%-------------------------------------------------------------------- -%% Function: signal(ConnectionManager, Channel, Sig) -> -%% -%% -%% Description: Not yet officialy supported. -%%-------------------------------------------------------------------- -signal(ConnectionManager, Channel, Sig) -> - ssh_connection_manager:request(ConnectionManager, Channel, + +signal(ConnectionHandler, Channel, Sig) -> + ssh_connection_handler:request(ConnectionHandler, Channel, "signal", false, [?string(Sig)], 0). -%%-------------------------------------------------------------------- -%% Function: signal(ConnectionManager, Channel, Status) -> -%% -%% -%% Description: Not yet officialy supported. -%%-------------------------------------------------------------------- -exit_status(ConnectionManager, Channel, Status) -> - ssh_connection_manager:request(ConnectionManager, Channel, - "exit-status", false, [?uint32(Status)], 0). +exit_status(ConnectionHandler, Channel, Status) -> + ssh_connection_handler:request(ConnectionHandler, Channel, + "exit-status", false, [?uint32(Status)], 0). -%%-------------------------------------------------------------------- -%% Function: open_pty(ConnectionManager, Channel, TimeOut) -> -%% -%% -%% Description: Not yet officialy supported. -%%-------------------------------------------------------------------- -open_pty(ConnectionManager, Channel, TimeOut) -> - open_pty(ConnectionManager, Channel, +open_pty(ConnectionHandler, Channel, TimeOut) -> + open_pty(ConnectionHandler, Channel, os:getenv("TERM"), 80, 24, [], TimeOut). -open_pty(ConnectionManager, Channel, Term, Width, Height, PtyOpts, TimeOut) -> - open_pty(ConnectionManager, Channel, Term, Width, +open_pty(ConnectionHandler, Channel, Term, Width, Height, PtyOpts, TimeOut) -> + open_pty(ConnectionHandler, Channel, Term, Width, Height, 0, 0, PtyOpts, TimeOut). -open_pty(ConnectionManager, Channel, Term, Width, Height, +open_pty(ConnectionHandler, Channel, Term, Width, Height, PixWidth, PixHeight, PtyOpts, TimeOut) -> - ssh_connection_manager:request(ConnectionManager, + ssh_connection_handler:request(ConnectionHandler, Channel, "pty-req", true, [?string(Term), ?uint32(Width), ?uint32(Height), ?uint32(PixWidth),?uint32(PixHeight), encode_pty_opts(PtyOpts)], TimeOut). - -%%-------------------------------------------------------------------- -%% Function: direct_tcpip(ConnectionManager, RemoteHost, -%% RemotePort, OrigIP, OrigPort, Timeout) -> -%% -%% -%% Description: Not yet officialy supported. -%%-------------------------------------------------------------------- -direct_tcpip(ConnectionManager, RemoteHost, +direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort, Timeout) -> - direct_tcpip(ConnectionManager, RemoteHost, RemotePort, OrigIP, OrigPort, + direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). -direct_tcpip(ConnectionManager, RemoteIP, RemotePort, OrigIP, OrigPort, +direct_tcpip(ConnectionHandler, RemoteIP, RemotePort, OrigIP, OrigPort, InitialWindowSize, MaxPacketSize, Timeout) -> case {encode_ip(RemoteIP), encode_ip(OrigIP)} of {false, _} -> @@ -262,7 +235,7 @@ direct_tcpip(ConnectionManager, RemoteIP, RemotePort, OrigIP, OrigPort, {_, false} -> {error, einval}; {RIP, OIP} -> - ssh_connection_manager:open_channel(ConnectionManager, + ssh_connection_handler:open_channel(ConnectionHandler, "direct-tcpip", [?string(RIP), ?uint32(RemotePort), @@ -272,34 +245,24 @@ direct_tcpip(ConnectionManager, RemoteIP, RemotePort, OrigIP, OrigPort, MaxPacketSize, Timeout) end. -%%-------------------------------------------------------------------- -%% Function: tcpip_forward(ConnectionManager, BindIP, BindPort) -> -%% -%% -%% Description: Not yet officialy supported. -%%-------------------------------------------------------------------- -tcpip_forward(ConnectionManager, BindIP, BindPort) -> + +tcpip_forward(ConnectionHandler, BindIP, BindPort) -> case encode_ip(BindIP) of false -> {error, einval}; IPStr -> - ssh_connection_manager:global_request(ConnectionManager, + ssh_connection_handler:global_request(ConnectionHandler, "tcpip-forward", true, [?string(IPStr), ?uint32(BindPort)]) end. -%%-------------------------------------------------------------------- -%% Function: cancel_tcpip_forward(ConnectionManager, BindIP, Port) -> -%% -%% -%% Description: Not yet officialy supported. -%%-------------------------------------------------------------------- -cancel_tcpip_forward(ConnectionManager, BindIP, Port) -> + +cancel_tcpip_forward(ConnectionHandler, BindIP, Port) -> case encode_ip(BindIP) of false -> {error, einval}; IPStr -> - ssh_connection_manager:global_request(ConnectionManager, + ssh_connection_handler:global_request(ConnectionHandler, "cancel-tcpip-forward", true, [?string(IPStr), ?uint32(Port)]) @@ -308,22 +271,23 @@ cancel_tcpip_forward(ConnectionManager, BindIP, Port) -> %%-------------------------------------------------------------------- %%% Internal API %%-------------------------------------------------------------------- -channel_data(ChannelId, DataType, Data, Connection, ConnectionPid, From) +channel_data(ChannelId, DataType, Data, Connection, From) when is_list(Data)-> channel_data(ChannelId, DataType, - list_to_binary(Data), Connection, ConnectionPid, From); + list_to_binary(Data), Connection, From); channel_data(ChannelId, DataType, Data, - #connection{channel_cache = Cache} = Connection, ConnectionPid, + #connection{channel_cache = Cache} = Connection, From) -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = Id, sent_close = false} = Channel0 -> - {SendList, Channel} = update_send_window(Channel0#channel{flow_control = From}, DataType, - Data, Connection), + {SendList, Channel} = + update_send_window(Channel0#channel{flow_control = From}, DataType, + Data, Connection), Replies = lists:map(fun({SendDataType, SendData}) -> - {connection_reply, ConnectionPid, + {connection_reply, channel_data_msg(Id, SendDataType, SendData)} @@ -333,7 +297,7 @@ channel_data(ChannelId, DataType, Data, Cache), {{replies, Replies ++ FlowCtrlMsgs}, Connection}; _ -> - gen_server:reply(From, {error, closed}), + gen_fsm:reply(From, {error, closed}), {noreply, Connection} end. @@ -341,7 +305,7 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId, sender_channel = RemoteId, initial_window_size = WindowSz, maximum_packet_size = PacketSz}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> #channel{remote_id = undefined} = Channel = ssh_channel:cache_lookup(Cache, ChannelId), @@ -357,7 +321,7 @@ handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId, reason = Reason, description = Descr, lang = Lang}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> Channel = ssh_channel:cache_lookup(Cache, ChannelId), ssh_channel:cache_delete(Cache, ChannelId), {Reply, Connection} = @@ -365,51 +329,59 @@ handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId, {{replies, [Reply]}, Connection}; handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> Channel = ssh_channel:cache_lookup(Cache, ChannelId), - {Reply, Connection} = reply_msg(Channel, Connection0, success), - {{replies, [Reply]}, Connection}; + case reply_msg(Channel, Connection0, success) of + {[], Connection} -> + {noreply, Connection}; + {Reply, Connection} -> + {{replies, [Reply]}, Connection} + end; handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> Channel = ssh_channel:cache_lookup(Cache, ChannelId), - {Reply, Connection} = reply_msg(Channel, Connection0, failure), - {{replies, [Reply]}, Connection}; + case reply_msg(Channel, Connection0, failure) of + {[], Connection} -> + {noreply, Connection}; + {Reply, Connection} -> + {{replies, [Reply]}, Connection} + end; + handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> Channel = ssh_channel:cache_lookup(Cache, ChannelId), {Reply, Connection} = reply_msg(Channel, Connection0, {eof, ChannelId}), {{replies, [Reply]}, Connection}; handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, - #connection{channel_cache = Cache} = Connection0, - ConnectionPid, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{sent_close = Closed, remote_id = RemoteId, flow_control = FlowControl} = Channel -> + #channel{sent_close = Closed, remote_id = RemoteId, + flow_control = FlowControl} = Channel -> ssh_channel:cache_delete(Cache, ChannelId), {CloseMsg, Connection} = reply_msg(Channel, Connection0, {closed, ChannelId}), - - ConnReplyMsgs = - case Closed of - true -> []; - false -> - RemoteCloseMsg = channel_close_msg(RemoteId), - [{connection_reply, ConnectionPid, RemoteCloseMsg}] - end, - - %% if there was a send() in progress, make it fail - SendReplyMsgs = - case FlowControl of - undefined -> []; - From -> - [{flow_control, From, {error, closed}}] - end, - - Replies = ConnReplyMsgs ++ [CloseMsg] ++ SendReplyMsgs, - {{replies, Replies}, Connection}; + ConnReplyMsgs = + case Closed of + true -> []; + false -> + RemoteCloseMsg = channel_close_msg(RemoteId), + [{connection_reply, RemoteCloseMsg}] + end, + + %% if there was a send() in progress, make it fail + SendReplyMsgs = + case FlowControl of + undefined -> []; + From -> + [{flow_control, From, {error, closed}}] + end, + + Replies = ConnReplyMsgs ++ [CloseMsg] ++ SendReplyMsgs, + {{replies, Replies}, Connection}; undefined -> {{replies, []}, Connection0} @@ -417,21 +389,24 @@ handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId}, handle_msg(#ssh_msg_channel_data{recipient_channel = ChannelId, data = Data}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> - #channel{recv_window_size = Size} = Channel = - ssh_channel:cache_lookup(Cache, ChannelId), - WantedSize = Size - size(Data), - ssh_channel:cache_update(Cache, Channel#channel{ - recv_window_size = WantedSize}), - {Replies, Connection} = - channel_data_reply(Cache, Channel, Connection0, 0, Data), - {{replies, Replies}, Connection}; + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{recv_window_size = Size} = Channel -> + WantedSize = Size - size(Data), + ssh_channel:cache_update(Cache, Channel#channel{ + recv_window_size = WantedSize}), + {Replies, Connection} = + channel_data_reply(Cache, Channel, Connection0, 0, Data), + {{replies, Replies}, Connection}; + undefined -> + {noreply, Connection0} + end; handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId, data_type_code = DataType, data = Data}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> #channel{recv_window_size = Size} = Channel = ssh_channel:cache_lookup(Cache, ChannelId), @@ -444,9 +419,7 @@ handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId, handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, bytes_to_add = Add}, - #connection{channel_cache = Cache} = Connection, - ConnectionPid, _) -> - + #connection{channel_cache = Cache} = Connection, _) -> #channel{send_window_size = Size, remote_id = RemoteId} = Channel0 = ssh_channel:cache_lookup(Cache, ChannelId), @@ -455,8 +428,7 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, 0, undefined, Connection), Replies = lists:map(fun({Type, Data}) -> - {connection_reply, ConnectionPid, - channel_data_msg(RemoteId, Type, Data)} + {connection_reply, channel_data_msg(RemoteId, Type, Data)} end, SendList), FlowCtrlMsgs = flow_control(Channel, Cache), {{replies, Replies ++ FlowCtrlMsgs}, Connection}; @@ -464,10 +436,9 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, sender_channel = RemoteId, initial_window_size = WindowSz, - maximum_packet_size = PacketSz}, Connection0, - ConnectionPid, server) -> + maximum_packet_size = PacketSz}, Connection0, server) -> - try setup_session(Connection0, ConnectionPid, RemoteId, + try setup_session(Connection0, RemoteId, Type, WindowSz, PacketSz) of Result -> Result @@ -475,20 +446,20 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, "Connection refused", "en"), - {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, + {{replies, [{connection_reply, FailMsg}]}, Connection0} end; handle_msg(#ssh_msg_channel_open{channel_type = "session", sender_channel = RemoteId}, - Connection, ConnectionPid, 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. FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, "Connection refused", "en"), - {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, + {{replies, [{connection_reply, FailMsg}]}, Connection}; handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip" = Type, @@ -496,8 +467,7 @@ 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} = Connection0, - ConnectionPid, server) -> + #connection{channel_cache = Cache} = Connection0, server) -> <<?UINT32(ALen), Address:ALen/binary, ?UINT32(Port), ?UINT32(OLen), Orig:OLen/binary, ?UINT32(OrigPort)>> = Data, @@ -507,7 +477,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip" = Type, ?SSH_OPEN_CONNECT_FAILED, "Connection refused", "en"), {{replies, - [{connection_reply, ConnectionPid, FailMsg}]}, Connection0}; + [{connection_reply, FailMsg}]}, Connection0}; ChannelPid -> {ChannelId, Connection1} = new_channel_id(Connection0), LWindowSz = ?DEFAULT_WINDOW_SIZE, @@ -528,32 +498,31 @@ handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip" = Type, {open, Channel, {forwarded_tcpip, decode_ip(Address), Port, decode_ip(Orig), OrigPort}}), - {{replies, [{connection_reply, ConnectionPid, OpenConfMsg}, + {{replies, [{connection_reply, OpenConfMsg}, OpenMsg]}, Connection} end; handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip", sender_channel = RemoteId}, - Connection, ConnectionPid, client) -> + Connection, client) -> %% Client implementations SHOULD reject direct TCP/IP open requests for %% security reasons. See RFC 4254 7.2. FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_CONNECT_FAILED, "Connection refused", "en"), - {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, Connection}; + {{replies, [{connection_reply, FailMsg}]}, Connection}; -handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, - ConnectionPid, _) -> +handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _) -> FailMsg = channel_open_failure_msg(RemoteId, ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "Not allowed", "en"), - {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, Connection}; + {{replies, [{connection_reply, FailMsg}]}, Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exit-status", data = Data}, - #connection{channel_cache = Cache} = Connection, _, _) -> + #connection{channel_cache = Cache} = Connection, _) -> <<?UINT32(Status)>> = Data, Channel = ssh_channel:cache_lookup(Cache, ChannelId), {Reply, Connection} = @@ -564,8 +533,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exit-signal", want_reply = false, data = Data}, - #connection{channel_cache = Cache} = Connection0, - ConnectionPid, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> <<?UINT32(SigLen), SigName:SigLen/binary, ?BOOLEAN(_Core), ?UINT32(ErrLen), Err:ErrLen/binary, @@ -578,14 +546,14 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, binary_to_list(Err), binary_to_list(Lang)}), CloseMsg = channel_close_msg(RemoteId), - {{replies, [{connection_reply, ConnectionPid, CloseMsg}, Reply]}, + {{replies, [{connection_reply, CloseMsg}, Reply]}, Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "xon-xoff", want_reply = false, data = Data}, - #connection{channel_cache = Cache} = Connection, _, _) -> + #connection{channel_cache = Cache} = Connection, _) -> <<?BOOLEAN(CDo)>> = Data, Channel = ssh_channel:cache_lookup(Cache, ChannelId), {Reply, Connection} = @@ -596,7 +564,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "window-change", want_reply = false, data = Data}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> <<?UINT32(Width),?UINT32(Height), ?UINT32(PixWidth), ?UINT32(PixHeight)>> = Data, Channel = ssh_channel:cache_lookup(Cache, ChannelId), @@ -609,7 +577,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "signal", data = Data}, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> <<?UINT32(SigLen), SigName:SigLen/binary>> = Data, Channel = ssh_channel:cache_lookup(Cache, ChannelId), @@ -622,8 +590,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "subsystem", want_reply = WantReply, data = Data}, - #connection{channel_cache = Cache} = Connection, - ConnectionPid, server) -> + #connection{channel_cache = Cache} = Connection, server) -> <<?UINT32(SsLen), SsName:SsLen/binary>> = Data, #channel{remote_id = RemoteId} = Channel0 = @@ -631,22 +598,23 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, ReplyMsg = {subsystem, ChannelId, WantReply, binary_to_list(SsName)}, - try start_subsytem(SsName, Connection, Channel0, ReplyMsg) of - {ok, Pid} -> - erlang:monitor(process, Pid), - Channel = Channel0#channel{user = Pid}, - ssh_channel:cache_update(Cache, Channel), - Reply = {connection_reply, ConnectionPid, - channel_success_msg(RemoteId)}, - {{replies, [Reply]}, Connection} - catch _:_ -> - Reply = {connection_reply, ConnectionPid, - channel_failure_msg(RemoteId)}, - {{replies, [Reply]}, Connection} + try + {ok, Pid} = start_subsytem(SsName, Connection, Channel0, ReplyMsg), + erlang:monitor(process, Pid), + Channel = Channel0#channel{user = Pid}, + ssh_channel:cache_update(Cache, Channel), + Reply = {connection_reply, + channel_success_msg(RemoteId)}, + {{replies, [Reply]}, Connection} + catch + _:_ -> + ErrorReply = {connection_reply, + channel_failure_msg(RemoteId)}, + {{replies, [ErrorReply]}, Connection} end; handle_msg(#ssh_msg_channel_request{request_type = "subsystem"}, - Connection, _, client) -> + Connection, client) -> %% The client SHOULD ignore subsystem requests. See RFC 4254 6.5. {{replies, []}, Connection}; @@ -654,8 +622,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "pty-req", want_reply = WantReply, data = Data}, - #connection{channel_cache = Cache} = Connection, - ConnectionPid, server) -> + #connection{channel_cache = Cache} = Connection, server) -> <<?UINT32(TermLen), BTermName:TermLen/binary, ?UINT32(Width),?UINT32(Height), ?UINT32(PixWidth), ?UINT32(PixHeight), @@ -667,27 +634,26 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, Channel = ssh_channel:cache_lookup(Cache, ChannelId), - handle_cli_msg(Connection, ConnectionPid, Channel, + handle_cli_msg(Connection, Channel, {pty, ChannelId, WantReply, PtyRequest}); handle_msg(#ssh_msg_channel_request{request_type = "pty-req"}, - Connection, _, client) -> + Connection, client) -> %% The client SHOULD ignore pty requests. See RFC 4254 6.2. {{replies, []}, Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "shell", want_reply = WantReply}, - #connection{channel_cache = Cache} = Connection, - ConnectionPid, server) -> + #connection{channel_cache = Cache} = Connection, server) -> Channel = ssh_channel:cache_lookup(Cache, ChannelId), - handle_cli_msg(Connection, ConnectionPid, Channel, + handle_cli_msg(Connection, Channel, {shell, ChannelId, WantReply}); handle_msg(#ssh_msg_channel_request{request_type = "shell"}, - Connection, _, client) -> + Connection, client) -> %% The client SHOULD ignore shell requests. See RFC 4254 6.5. {{replies, []}, Connection}; @@ -695,17 +661,16 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "exec", want_reply = WantReply, data = Data}, - #connection{channel_cache = Cache} = Connection, - ConnectionPid, server) -> + #connection{channel_cache = Cache} = Connection, server) -> <<?UINT32(Len), Command:Len/binary>> = Data, Channel = ssh_channel:cache_lookup(Cache, ChannelId), - handle_cli_msg(Connection, ConnectionPid, Channel, + handle_cli_msg(Connection, Channel, {exec, ChannelId, WantReply, binary_to_list(Command)}); handle_msg(#ssh_msg_channel_request{request_type = "exec"}, - Connection, _, client) -> + Connection, client) -> %% The client SHOULD ignore exec requests. See RFC 4254 6.5. {{replies, []}, Connection}; @@ -713,31 +678,30 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = "env", want_reply = WantReply, data = Data}, - #connection{channel_cache = Cache} = Connection, - ConnectionPid, server) -> + #connection{channel_cache = Cache} = Connection, server) -> <<?UINT32(VarLen), Var:VarLen/binary, ?UINT32(ValueLen), Value:ValueLen/binary>> = Data, Channel = ssh_channel:cache_lookup(Cache, ChannelId), - handle_cli_msg(Connection, ConnectionPid, Channel, + handle_cli_msg(Connection, Channel, {env, ChannelId, WantReply, Var, Value}); handle_msg(#ssh_msg_channel_request{request_type = "env"}, - Connection, _, client) -> + Connection, client) -> %% The client SHOULD ignore env requests. {{replies, []}, Connection}; handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, request_type = _Other, - want_reply = WantReply}, #connection{channel_cache = Cache} = Connection, - ConnectionPid, _) -> + want_reply = WantReply}, + #connection{channel_cache = Cache} = Connection, _) -> if WantReply == true -> case ssh_channel:cache_lookup(Cache, ChannelId) of #channel{remote_id = RemoteId} -> FailMsg = channel_failure_msg(RemoteId), - {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, + {{replies, [{connection_reply, FailMsg}]}, Connection}; undefined -> %% Chanel has been closed {noreply, Connection} @@ -748,61 +712,75 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, handle_msg(#ssh_msg_global_request{name = _Type, want_reply = WantReply, - data = _Data}, Connection, - ConnectionPid, _) -> + data = _Data}, Connection, _) -> if WantReply == true -> FailMsg = request_failure_msg(), - {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, + {{replies, [{connection_reply, FailMsg}]}, Connection}; true -> {noreply, Connection} end; +handle_msg(#ssh_msg_request_failure{}, + #connection{requests = [{_, From} | Rest]} = Connection, _) -> + {{replies, [{channel_requst_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}}]}, + Connection#connection{requests = Rest}}; + %%% This transport message will also be handled at the connection level handle_msg(#ssh_msg_disconnect{code = Code, description = Description, language = _Lang }, - #connection{channel_cache = Cache} = Connection0, _, _) -> + #connection{channel_cache = Cache} = Connection0, _) -> {Connection, Replies} = ssh_channel:cache_foldl(fun(Channel, {Connection1, Acc}) -> {Reply, Connection2} = reply_msg(Channel, - Connection1, {closed, Channel#channel.local_id}), + Connection1, + {closed, Channel#channel.local_id}), {Connection2, [Reply | Acc]} end, {Connection0, []}, Cache), ssh_channel:cache_delete(Cache), {disconnect, {Code, Description}, {{replies, Replies}, Connection}}. -handle_cli_msg(#connection{channel_cache = Cache} = Connection0, - ConnectionPid, +handle_cli_msg(#connection{channel_cache = Cache} = Connection, #channel{user = undefined, + remote_id = RemoteId, local_id = ChannelId} = Channel0, Reply0) -> - case (catch start_cli(Connection0, ChannelId)) of + case (catch start_cli(Connection, ChannelId)) of {ok, Pid} -> erlang:monitor(process, Pid), Channel = Channel0#channel{user = Pid}, ssh_channel:cache_update(Cache, Channel), - {Reply, Connection} = reply_msg(Channel, Connection0, Reply0), - {{replies, [Reply]}, Connection}; - _ -> - Reply = {connection_reply, ConnectionPid, - request_failure_msg()}, - {{replies, [Reply]}, Connection0} + Reply = {connection_reply, + channel_success_msg(RemoteId)}, + {{replies, [{channel_data, Pid, Reply0}, Reply]}, Connection}; + _Other -> + Reply = {connection_reply, + channel_failure_msg(RemoteId)}, + {{replies, [Reply]}, Connection} end; -handle_cli_msg(Connection0, _, Channel, Reply0) -> +handle_cli_msg(Connection0, Channel, Reply0) -> {Reply, Connection} = reply_msg(Channel, Connection0, Reply0), {{replies, [Reply]}, Connection}. - channel_eof_msg(ChannelId) -> #ssh_msg_channel_eof{recipient_channel = ChannelId}. channel_close_msg(ChannelId) -> #ssh_msg_channel_close {recipient_channel = ChannelId}. +channel_status_msg({success, ChannelId}) -> + channel_success_msg(ChannelId); +channel_status_msg({failure, ChannelId}) -> + channel_failure_msg(ChannelId). + channel_success_msg(ChannelId) -> #ssh_msg_channel_success{recipient_channel = ChannelId}. @@ -901,14 +879,14 @@ start_channel(Cb, Id, Args, SubSysSup) -> start_channel(Cb, Id, Args, SubSysSup, Exec) -> ChildSpec = child_spec(Cb, Id, Args, Exec), - ChannelSup =ssh_subsystem_sup:channel_supervisor(SubSysSup), + ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), ssh_channel_sup:start_child(ChannelSup, ChildSpec). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- setup_session(#connection{channel_cache = Cache} = Connection0, - ConnectionPid, RemoteId, + RemoteId, Type, WindowSize, PacketSize) -> {ChannelId, Connection} = new_channel_id(Connection0), @@ -926,7 +904,7 @@ setup_session(#connection{channel_cache = Cache} = Connection0, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE), - {{replies, [{connection_reply, ConnectionPid, OpenConfMsg}]}, Connection}. + {{replies, [{connection_reply, OpenConfMsg}]}, Connection}. check_subsystem("sftp"= SsName, Options) -> @@ -955,35 +933,19 @@ child_spec(Callback, Id, Args, Exec) -> Type = worker, {Name, StartFunc, Restart, Shutdown, Type, [ssh_channel]}. -%% Backwards compatibility -start_cli(#connection{address = Address, port = Port, cli_spec = {Fun, [Shell]}, - options = Options}, - _ChannelId) when is_function(Fun) -> - case Fun(Shell, Address, Port, Options) of - NewFun when is_function(NewFun) -> - {ok, NewFun()}; - Pid when is_pid(Pid) -> - {ok, Pid} - end; - start_cli(#connection{cli_spec = {CbModule, Args}, exec = Exec, sub_system_supervisor = SubSysSup}, ChannelId) -> start_channel(CbModule, ChannelId, Args, SubSysSup, Exec). -start_subsytem(BinName, #connection{address = Address, port = Port, - options = Options, +start_subsytem(BinName, #connection{options = Options, sub_system_supervisor = SubSysSup}, - #channel{local_id = ChannelId, remote_id = RemoteChannelId}, - ReplyMsg) -> + #channel{local_id = ChannelId}, _ReplyMsg) -> Name = binary_to_list(BinName), case check_subsystem(Name, Options) of {Callback, Opts} when is_atom(Callback), Callback =/= none -> start_channel(Callback, ChannelId, Opts, SubSysSup); {Other, _} when Other =/= none -> - handle_backwards_compatibility(Other, self(), - ChannelId, RemoteChannelId, - Options, Address, Port, - {ssh_cm, self(), ReplyMsg}) + {error, legacy_option_not_supported} end. channel_data_reply(_, #channel{local_id = ChannelId} = Channel, @@ -1006,9 +968,12 @@ reply_msg(Channel, Connection, failure = Reply) -> request_reply_or_data(Channel, Connection, Reply); reply_msg(Channel, Connection, {closed, _} = Reply) -> request_reply_or_data(Channel, Connection, Reply); +reply_msg(undefined, Connection, _Reply) -> + {noreply, Connection}; reply_msg(#channel{user = ChannelPid}, Connection, Reply) -> {{channel_data, ChannelPid, Reply}, Connection}. + request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid}, #connection{requests = Requests} = Connection, Reply) -> @@ -1016,10 +981,13 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid}, {value, {ChannelId, From}} -> {{channel_requst_reply, From, Reply}, Connection#connection{requests = - lists:keydelete(ChannelId, 1, Requests)}}; + lists:keydelete(ChannelId, 1, Requests)}}; + false when (Reply == success) or (Reply == failure) -> + {[], Connection}; false -> {{channel_data, ChannelPid, Reply}, Connection} end. + update_send_window(Channel, _, undefined, #connection{channel_cache = Cache}) -> do_update_send_window(Channel, Channel#channel.send_buf, Cache); @@ -1075,7 +1043,7 @@ flow_control([], Channel, Cache) -> []; flow_control([_|_], #channel{flow_control = From, - send_buf = []} = Channel, Cache) when From =/= undefined -> + send_buf = []} = Channel, Cache) when From =/= undefined -> [{flow_control, Cache, Channel, From, ok}]; flow_control(_,_,_) -> []. @@ -1277,43 +1245,3 @@ decode_ip(Addr) when is_binary(Addr) -> {ok,A} -> A end. -%% This is really awful and that is why it is beeing phased out. -handle_backwards_compatibility({_,_,_,_,_,_} = ChildSpec, _, _, _, _, - Address, Port, _) -> - SystemSup = ssh_system_sup:system_supervisor(Address, Port), - ChannelSup = ssh_system_sup:channel_supervisor(SystemSup), - ssh_channel_sup:start_child(ChannelSup, ChildSpec); - -handle_backwards_compatibility(Module, ConnectionManager, ChannelId, - RemoteChannelId, Opts, - _, _, Msg) when is_atom(Module) -> - {ok, SubSystemPid} = gen_server:start_link(Module, [Opts], []), - SubSystemPid ! - {ssh_cm, ConnectionManager, - {open, ChannelId, RemoteChannelId, {session}}}, - SubSystemPid ! Msg, - {ok, SubSystemPid}; - -handle_backwards_compatibility(Fun, ConnectionManager, ChannelId, - RemoteChannelId, - _, _, _, Msg) when is_function(Fun) -> - SubSystemPid = Fun(), - SubSystemPid ! - {ssh_cm, ConnectionManager, - {open, ChannelId, RemoteChannelId, {session}}}, - SubSystemPid ! Msg, - {ok, SubSystemPid}; - -handle_backwards_compatibility(ChildSpec, - ConnectionManager, - ChannelId, RemoteChannelId, _, - Address, Port, Msg) -> - SystemSup = ssh_system_sup:system_supervisor(Address, Port), - ChannelSup = ssh_system_sup:channel_supervisor(SystemSup), - {ok, SubSystemPid} - = ssh_channel_sup:start_child(ChannelSup, ChildSpec), - SubSystemPid ! - {ssh_cm, ConnectionManager, - {open, ChannelId, RemoteChannelId, {session}}}, - SubSystemPid ! Msg, - {ok, SubSystemPid}. diff --git a/lib/ssh/src/ssh_connection_controler.erl b/lib/ssh/src/ssh_connection_controler.erl deleted file mode 100644 index ca3e62dc83..0000000000 --- a/lib/ssh/src/ssh_connection_controler.erl +++ /dev/null @@ -1,137 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2009-2010. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% -%%-------------------------------------------------------------------- -%% File : ssh_connection_controler.erl -%% Description : -%% -%%-------------------------------------------------------------------- - --module(ssh_connection_controler). - --behaviour(gen_server). - -%%----------------------------------------------------------------- -%% External exports -%%----------------------------------------------------------------- --export([start_link/1, start_handler_child/2, start_manager_child/2, - connection_manager/1]). - -%%----------------------------------------------------------------- -%% Internal exports -%%----------------------------------------------------------------- --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - code_change/3, terminate/2, stop/1]). - --record(state, {role, manager, handler, timeout}). - -%%----------------------------------------------------------------- -%% External interface functions -%%----------------------------------------------------------------- -%%----------------------------------------------------------------- -%% Func: start/0 -%%----------------------------------------------------------------- -start_link(Args) -> - gen_server:start_link(?MODULE, [Args], []). - -%% Will be called from the manager child process -start_handler_child(ServerRef, Args) -> - gen_server:call(ServerRef, {handler, self(), Args}, infinity). - -%% Will be called from the acceptor process -start_manager_child(ServerRef, Args) -> - gen_server:call(ServerRef, {manager, Args}, infinity). - -connection_manager(ServerRef) -> - {ok, gen_server:call(ServerRef, manager, infinity)}. - -%%----------------------------------------------------------------- -%% Internal interface functions -%%----------------------------------------------------------------- -%%----------------------------------------------------------------- -%% Func: stop/1 -%%----------------------------------------------------------------- -stop(Pid) -> - gen_server:cast(Pid, stop). - -%%----------------------------------------------------------------- -%% Server functions -%%----------------------------------------------------------------- -%%----------------------------------------------------------------- -%% Func: init/1 -%%----------------------------------------------------------------- -init([Opts]) -> - process_flag(trap_exit, true), - case proplists:get_value(role, Opts) of - client -> - {ok, Manager} = ssh_connection_manager:start_link([client, Opts]), - {ok, #state{role = client, manager = Manager}}; - _server -> - %% Children started by acceptor process - {ok, #state{role = server}} - end. - - -%%----------------------------------------------------------------- -%% Func: terminate/2 -%%----------------------------------------------------------------- -terminate(_Reason, #state{}) -> - ok. - -%%----------------------------------------------------------------- -%% Func: handle_call/3 -%%----------------------------------------------------------------- -handle_call({handler, Pid, [Role, Socket, Opts]}, _From, State) -> - {ok, Handler} = ssh_connection_handler:start_link(Role, Pid, Socket, Opts), - {reply, {ok, Handler}, State#state{handler = Handler}}; -handle_call({manager, [server = Role, Socket, Opts, SubSysSup]}, _From, State) -> - {ok, Manager} = ssh_connection_manager:start_link([Role, Socket, Opts, SubSysSup]), - {reply, {ok, Manager}, State#state{manager = Manager}}; -handle_call({manager, [client = Role | Opts]}, _From, State) -> - {ok, Manager} = ssh_connection_manager:start_link([Role, Opts]), - {reply, {ok, Manager}, State#state{manager = Manager}}; -handle_call(manager, _From, State) -> - {reply, State#state.manager, State}; -handle_call(stop, _From, State) -> - {stop, normal, ok, State}; -handle_call(_, _, State) -> - {noreply, State, State#state.timeout}. - -%%----------------------------------------------------------------- -%% Func: handle_cast/2 -%%----------------------------------------------------------------- -handle_cast(stop, State) -> - {stop, normal, State}; -handle_cast(_, State) -> - {noreply, State, State#state.timeout}. - -%%----------------------------------------------------------------- -%% Func: handle_info/2 -%%----------------------------------------------------------------- -%% handle_info(ssh_connected, State) -> -%% {stop, normal, State}; -%% Servant termination. -handle_info({'EXIT', _Pid, Reason}, State) -> - {stop, Reason, State}. - -%%----------------------------------------------------------------- -%% Func: code_change/3 -%%----------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index c10a60bfcf..6bff27b860 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -18,10 +18,11 @@ %% %% %%---------------------------------------------------------------------- -%% Purpose: Handles the setup of an ssh connection, e.i. both the -%% setup SSH Transport Layer Protocol (RFC 4253) and Authentication -%% Protocol (RFC 4252). Details of the different protocols are -%% implemented in ssh_transport.erl, ssh_auth.erl +%% Purpose: Handles an ssh connection, e.i. both the +%% setup SSH Transport Layer Protocol (RFC 4253), Authentication +%% Protocol (RFC 4252) and SSH connection Protocol (RFC 4255) +%% Details of the different protocols are +%% implemented in ssh_transport.erl, ssh_auth.erl and ssh_connection.erl %% ---------------------------------------------------------------------- -module(ssh_connection_handler). @@ -33,10 +34,14 @@ -include("ssh_auth.hrl"). -include("ssh_connect.hrl"). --export([start_link/4, send/2, renegotiate/1, send_event/2, - connection_info/3, - peer_address/1, - renegotiate_data/1]). +-export([start_link/3]). + +%% 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, + start_connection/4]). %% gen_fsm callbacks -export([hello/2, kexinit/2, key_exchange/2, new_keys/2, @@ -45,10 +50,13 @@ -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). -%% spawn export --export([ssh_info_handler/4]). - -record(state, { + role, + client, + starter, + connection_state, + latest_channel_id = 0, + idle_timer_ref, transport_protocol, % ex: tcp transport_cb, transport_close_tag, @@ -59,104 +67,234 @@ undecoded_packet_length, % integer() key_exchange_init_msg, % #ssh_msg_kexinit{} renegotiate = false, % boolean() - manager, % pid() connection_queue, address, port, opts }). --define(DBG_MESSAGE, true). +-type state_name() :: hello | kexinit | key_exchange | new_keys | userauth | connection. +-type gen_fsm_state_return() :: {next_state, state_name(), term()} | + {next_state, state_name(), term(), timeout()} | + {stop, term(), term()}. %%==================================================================== %% Internal application API %%==================================================================== + %%-------------------------------------------------------------------- -%% Function: start_link() -> ok,Pid} | ignore | {error,Error} -%% Description:Creates a gen_fsm process which calls Module:init/1 to -%% initialize. To ensure a synchronized start-up procedure, this function -%% does not return until Module:init/1 has returned. +-spec start_connection(client| server, port(), proplists:proplist(), + timeout()) -> {ok, pid()} | {error, term()}. %%-------------------------------------------------------------------- -start_link(Role, Manager, Socket, Options) -> - gen_fsm:start_link(?MODULE, [Role, Manager, Socket, Options], []). - -send(ConnectionHandler, Data) -> - send_all_state_event(ConnectionHandler, {send, Data}). +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) + catch + exit:{noproc, _} -> + {error, ssh_not_started}; + _:Error -> + {error, Error} + end; -renegotiate(ConnectionHandler) -> - send_all_state_event(ConnectionHandler, renegotiate). - -renegotiate_data(ConnectionHandler) -> - send_all_state_event(ConnectionHandler, data_size). -connection_info(ConnectionHandler, From, Options) -> - send_all_state_event(ConnectionHandler, {info, From, Options}). +start_connection(server = Role, Socket, Options, Timeout) -> + try + Sups = proplists:get_value(supervisors, Options), + ConnectionSup = proplists:get_value(connection_sup, Sups), + Opts = [{supervisors, Sups}, {user_pid, self()} | 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), + Ref = erlang:monitor(process, Pid), + handshake(Pid, Ref, Timeout) + catch + exit:{noproc, _} -> + {error, ssh_not_started}; + _:Error -> + {error, Error} + end. -%% Replaced with option to connection_info/3. For now keep -%% for backwards compatibility -peer_address(ConnectionHandler) -> - sync_send_all_state_event(ConnectionHandler, peer_address). +start_link(Role, Socket, Options) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Socket, Options]])}. -%%==================================================================== -%% gen_fsm callbacks -%%==================================================================== -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, StateName, State} | -%% {ok, StateName, State, Timeout} | -%% ignore | -%% {stop, StopReason} -%% Description:Whenever a gen_fsm is started using gen_fsm:start/[3,4] or -%% gen_fsm:start_link/3,4, this function is called by the new process to -%% initialize. -%%-------------------------------------------------------------------- -init([Role, Manager, Socket, SshOpts]) -> +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{ + role = Role, + 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(State0), + try init_ssh(Role, NumVsn, StrVsn, SshOpts, Socket) of Ssh -> - {ok, hello, #state{ssh_params = - Ssh#ssh{send_sequence = 0, recv_sequence = 0}, - socket = Socket, - decoded_data_buffer = <<>>, - encoded_data_buffer = <<>>, - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag, - manager = Manager, - opts = SshOpts - }} + gen_fsm:enter_loop(?MODULE, [], hello, + State#state{ssh_params = Ssh}) catch - exit:Reason -> - {stop, {shutdown, Reason}} + _:Error -> + gen_fsm:enter_loop(?MODULE, [], error, {Error, State0}) end. + +%%-------------------------------------------------------------------- +-spec open_channel(pid(), string(), iodata(), integer(), integer(), + timeout()) -> {open, channel_id()} | {open_error, term(), string(), string()}. +%%-------------------------------------------------------------------- +open_channel(ConnectionHandler, ChannelType, ChannelSpecificData, + InitialWindowSize, + MaxPacketSize, Timeout) -> + sync_send_all_state_event(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, + Timeout}); +request(ConnectionHandler, ChannelPid, ChannelId, Type, false, Data, _) -> + send_all_state_event(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data}). + +%%-------------------------------------------------------------------- +-spec request(pid(), channel_id(), string(), boolean(), iodata(), + timeout()) -> success | failure | {error, timeout}. %%-------------------------------------------------------------------- -%% Function: -%% state_name(Event, State) -> {next_state, NextStateName, NextState}| -%% {next_state, NextStateName, -%% NextState, Timeout} | -%% {stop, Reason, NewState} -%% Description:There should be one instance of this function for each possible -%% state name. Whenever a gen_fsm receives an event sent using -%% gen_fsm:send_event/2, the instance of this function with the same name as -%% the current state name StateName is called to handle the event. It is also -%% called if a timeout occurs. +request(ConnectionHandler, ChannelId, Type, true, Data, Timeout) -> + sync_send_all_state_event(ConnectionHandler, {request, ChannelId, Type, Data, Timeout}); +request(ConnectionHandler, ChannelId, Type, false, Data, _) -> + send_all_state_event(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}). + +%%-------------------------------------------------------------------- +-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 + {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}). + +%%-------------------------------------------------------------------- +-spec send(pid(), channel_id(), integer(), iolist(), timeout()) -> + ok | {error, timeout} | {error, closed}. +%%-------------------------------------------------------------------- +send(ConnectionHandler, ChannelId, Type, Data, Timeout) -> + sync_send_all_state_event(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}). + +%%-------------------------------------------------------------------- +-spec connection_info(pid(), [atom()]) -> proplists:proplist(). +%%-------------------------------------------------------------------- +connection_info(ConnectionHandler, Options) -> + sync_send_all_state_event(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}). + +%%-------------------------------------------------------------------- +-spec adjust_window(pid(), channel_id(), integer()) -> ok. +%%-------------------------------------------------------------------- +adjust_window(ConnectionHandler, Channel, Bytes) -> + send_all_state_event(ConnectionHandler, {adjust_window, Channel, Bytes}). +%%-------------------------------------------------------------------- +-spec renegotiate(pid()) -> ok. +%%-------------------------------------------------------------------- +renegotiate(ConnectionHandler) -> + send_all_state_event(ConnectionHandler, renegotiate). + +%%-------------------------------------------------------------------- +-spec renegotiate_data(pid()) -> ok. +%%-------------------------------------------------------------------- +renegotiate_data(ConnectionHandler) -> + send_all_state_event(ConnectionHandler, data_size). + +%%-------------------------------------------------------------------- +-spec close(pid(), channel_id()) -> ok. +%%-------------------------------------------------------------------- +close(ConnectionHandler, ChannelId) -> + sync_send_all_state_event(ConnectionHandler, {close, ChannelId}). + +%%-------------------------------------------------------------------- +-spec stop(pid()) -> ok | {error, term()}. +%%-------------------------------------------------------------------- +stop(ConnectionHandler)-> + case sync_send_all_state_event(ConnectionHandler, stop) of + {error, closed} -> + ok; + Other -> + Other + end. + +info(ConnectionHandler) -> + info(ConnectionHandler, {info, all}). + +info(ConnectionHandler, ChannelProcess) -> + sync_send_all_state_event(ConnectionHandler, {info, ChannelProcess}). + + +%%==================================================================== +%% gen_fsm callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec hello(socket_control | {info_line, list()} | {version_exchange, list()}, + #state{}) -> gen_fsm_state_return(). +%%-------------------------------------------------------------------- + hello(socket_control, #state{socket = Socket, ssh_params = Ssh} = State) -> VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)), send_msg(VsnMsg, State), - inet:setopts(Socket, [{packet, line}]), - {next_state, hello, next_packet(State)}; + inet:setopts(Socket, [{packet, line}, {active, once}]), + {next_state, hello, State}; -hello({info_line, _Line}, State) -> - {next_state, hello, next_packet(State)}; +hello({info_line, _Line},#state{socket = Socket} = State) -> + inet:setopts(Socket, [{active, once}]), + {next_state, hello, State}; hello({version_exchange, Version}, #state{ssh_params = Ssh0, socket = Socket} = State) -> {NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version), case handle_version(NumVsn, StrVsn, Ssh0) of {ok, Ssh1} -> - inet:setopts(Socket, [{packet,0}, {mode,binary}]), + inet:setopts(Socket, [{packet,0}, {mode,binary}, {active, once}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), send_msg(SshPacket, State), {next_state, kexinit, next_packet(State#state{ssh_params = Ssh, @@ -172,12 +310,15 @@ hello({version_exchange, Version}, #state{ssh_params = Ssh0, 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) -> + key_exchange_init_msg = OwnKex} = + State) -> Ssh1 = ssh_transport:key_init(opposite_role(Role), Ssh0, Payload), - try ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of + case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of {ok, NextKexMsg, Ssh} when Role == client -> send_msg(NextKexMsg, State), {next_state, key_exchange, @@ -185,156 +326,75 @@ kexinit({#ssh_msg_kexinit{} = Kex, Payload}, {ok, Ssh} when Role == server -> {next_state, key_exchange, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = Desc, - language = "en"}, State) 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) -> - try ssh_transport:handle_kexdh_init(Msg, Ssh0) of + #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})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = Desc, - language = "en"}, State) end; -key_exchange({#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), - try 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})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = Desc, - language = "en"}, State) - end; - key_exchange(#ssh_msg_kexdh_reply{} = Msg, #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> - try ssh_transport:handle_kexdh_reply(Msg, Ssh0) of - {ok, NewKeys, Ssh} -> - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - {ErrorToDisplay, #ssh_msg_disconnect{} = DisconnectMsg} -> - handle_disconnect(DisconnectMsg, State, ErrorToDisplay); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = Desc, - language = "en"}, State) - end; + {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})}; key_exchange(#ssh_msg_kex_dh_gex_group{} = Msg, #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - try ssh_transport:handle_kex_dh_gex_group(Msg, Ssh0) of - {ok, NextKexMsg, Ssh1} -> - send_msg(NextKexMsg, 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})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = Desc, - language = "en"}, State) - end; + {ok, NextKexMsg, Ssh1} = ssh_transport:handle_kex_dh_gex_group(Msg, Ssh0), + send_msg(NextKexMsg, 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})}; key_exchange(#ssh_msg_kex_dh_gex_request{} = Msg, #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> - try ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0) of - {ok, NextKexMsg, Ssh} -> - send_msg(NextKexMsg, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = Desc, - language = "en"}, State) - end; + {ok, NextKexMsg, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), + send_msg(NextKexMsg, State), + {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}; + key_exchange(#ssh_msg_kex_dh_gex_reply{} = Msg, #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> - try ssh_transport:handle_kex_dh_gex_reply(Msg, Ssh0) of - {ok, NewKeys, Ssh} -> - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = Desc, - language = "en"}, State) - end. + {ok, NewKeys, Ssh} = ssh_transport:handle_kex_dh_gex_reply(Msg, Ssh0), + send_msg(NewKeys, State), + {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}. + +%%-------------------------------------------------------------------- +-spec new_keys(#ssh_msg_newkeys{}, #state{}) -> gen_fsm_state_return(). +%%-------------------------------------------------------------------- new_keys(#ssh_msg_newkeys{} = Msg, #state{ssh_params = Ssh0} = State0) -> - try ssh_transport:handle_new_keys(Msg, Ssh0) of - {ok, Ssh} -> - {NextStateName, State} = - after_new_keys(State0#state{ssh_params = Ssh}), - {next_state, NextStateName, next_packet(State)} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State0); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = Desc, - language = "en"}, State0) - end. + {ok, Ssh} = ssh_transport:handle_new_keys(Msg, Ssh0), + {NextStateName, State} = + after_new_keys(State0#state{ssh_params = Ssh}), + {next_state, NextStateName, next_packet(State)}. + +%%-------------------------------------------------------------------- +-spec userauth(#ssh_msg_service_request{} | #ssh_msg_service_accept{} | + #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_service_request{name = "ssh-userauth"} = Msg, #state{ssh_params = #ssh{role = server, session_id = SessionId} = Ssh0} = State) -> - try ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of - {ok, {Reply, Ssh}} -> - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = Desc, - language = "en"}, State) - end; + {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})}; userauth(#ssh_msg_service_accept{name = "ssh-userauth"}, #state{ssh_params = #ssh{role = client, @@ -349,27 +409,18 @@ userauth(#ssh_msg_userauth_request{service = "ssh-connection", #state{ssh_params = #ssh{session_id = SessionId, role = server, service = "ssh-connection"} = Ssh0 } = State) -> - try ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of - {not_authorized, {_User, _Reason}, {Reply, Ssh}} -> - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = Desc, - language = "en"}, State) - end; + {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})}; userauth(#ssh_msg_userauth_request{service = "ssh-connection", method = Method} = Msg, #state{ssh_params = #ssh{session_id = SessionId, role = server, service = "ssh-connection", peer = {_, Address}} = Ssh0, - opts = Opts, manager = Pid} = State) -> - try ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of + opts = Opts, starter = Pid} = State) -> + case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of {authorized, User, {Reply, Ssh}} -> send_msg(Reply, State), ssh_userreg:register_user(User, Pid), @@ -381,54 +432,26 @@ userauth(#ssh_msg_userauth_request{service = "ssh-connection", retry_fun(User, Reason, Opts), send_msg(Reply, State), {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = Desc, - language = "en"}, State) end; userauth(#ssh_msg_userauth_info_request{} = Msg, #state{ssh_params = #ssh{role = client, io_cb = IoCb} = Ssh0} = State) -> - try ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0) of - {ok, {Reply, Ssh}} -> - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = Desc, - language = "en"}, State) - end; + {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0), + send_msg(Reply, State), + {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}; userauth(#ssh_msg_userauth_info_response{} = Msg, #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - try ssh_auth:handle_userauth_info_response(Msg, Ssh0) of - {ok, {Reply, Ssh}} -> - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} - catch - #ssh_msg_disconnect{} = DisconnectMsg -> - handle_disconnect(DisconnectMsg, State); - _:Error -> - Desc = log_error(Error), - handle_disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, - description = Desc, - language = "en"}, State) - end; + {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response(Msg, Ssh0), + send_msg(Reply, State), + {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}; userauth(#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client} = Ssh, - manager = Pid} = State) -> + starter = Pid} = State) -> Pid ! ssh_connected, - {next_state, connected, next_packet(State#state{ssh_params = Ssh#ssh{authenticated = true}})}; - + {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 = []}} @@ -477,31 +500,27 @@ userauth(#ssh_msg_userauth_banner{message = Msg}, io:format("~s", [Msg]), {next_state, userauth, next_packet(State)}. +%%-------------------------------------------------------------------- +-spec connected({#ssh_msg_kexinit{}, binary()}, %%| %% #ssh_msg_kexdh_init{}, + #state{}) -> gen_fsm_state_return(). +%%-------------------------------------------------------------------- connected({#ssh_msg_kexinit{}, _Payload} = Event, State) -> - kexinit(Event, State#state{renegotiate = true}); -connected({#ssh_msg_kexdh_init{}, _Payload} = Event, State) -> - key_exchange(Event, State#state{renegotiate = true}). + kexinit(Event, State#state{renegotiate = true}). +%% ; +%% connected(#ssh_msg_kexdh_init{} = Event, State) -> +%% key_exchange(Event, State#state{renegotiate = true}). %%-------------------------------------------------------------------- -%% Function: -%% handle_event(Event, StateName, State) -> {next_state, NextStateName, -%% NextState} | -%% {next_state, NextStateName, -%% NextState, Timeout} | -%% {stop, Reason, NewState} -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:send_all_state_event/2, this function is called to handle -%% the event. -%%-------------------------------------------------------------------- -handle_event({send, Data}, StateName, #state{ssh_params = Ssh0} = State) -> - {Packet, Ssh} = ssh_transport:pack(Data, Ssh0), - send_msg(Packet, State), - {next_state, StateName, next_packet(State#state{ssh_params = Ssh})}; +-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{} = Msg, _StateName, - #state{manager = Pid} = State) -> - (catch ssh_connection_manager:event(Pid, Msg)), - {stop, normal, State}; +%%-------------------------------------------------------------------- +handle_event(#ssh_msg_disconnect{description = Desc}, _StateName, #state{} = State) -> + {stop, {shutdown, Desc}, State}; handle_event(#ssh_msg_ignore{}, StateName, State) -> {next_state, StateName, next_packet(State)}; @@ -517,30 +536,58 @@ handle_event(#ssh_msg_debug{}, StateName, State) -> handle_event(#ssh_msg_unimplemented{}, StateName, State) -> {next_state, StateName, next_packet(State)}; +handle_event({adjust_window, ChannelId, Bytes}, StateName, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + State = + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{recv_window_size = WinSize, remote_id = Id} = Channel -> + ssh_channel:cache_update(Cache, Channel#channel{recv_window_size = + WinSize + Bytes}), + Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes), + send_replies([{connection_reply, Msg}], State0); + 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(renegotiate, connected, #state{ssh_params = Ssh0} = State) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), send_msg(SshPacket, State), - {next_state, connected, + 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) -> + timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiatie]), %% Allready in keyexcahange so ignore {next_state, StateName, State}; -handle_event({info, From, Options}, StateName, #state{ssh_params = Ssh} = State) -> - spawn(?MODULE, ssh_info_handler, [Options, Ssh, State, From]), - {next_state, StateName, State}; +%% Rekey due to sent data limit reached? handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) -> {ok, [{send_oct,Sent}]} = inet:getstat(State#state.socket, [send_oct]), 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]), case Sent >= MaxSent of true -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), send_msg(SshPacket, State), - {next_state, connected, + {next_state, kexinit, next_packet(State#state{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg, renegotiate = true})}; @@ -549,42 +596,196 @@ handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) -> end; handle_event(data_size, StateName, State) -> {next_state, StateName, State}; + +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({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({unknown, Data}, StateName, State) -> Msg = #ssh_msg_unimplemented{sequence = Data}, send_msg(Msg, State), {next_state, StateName, next_packet(State)}. + %%-------------------------------------------------------------------- -%% Function: -%% handle_sync_event(Event, From, StateName, -%% State) -> {next_state, NextStateName, NextState} | -%% {next_state, NextStateName, NextState, -%% Timeout} | -%% {reply, Reply, NextStateName, NextState}| -%% {reply, Reply, NextStateName, NextState, -%% Timeout} | -%% {stop, Reason, NewState} | -%% {stop, Reason, Reply, NewState} -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle -%% the event. +-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_state_return(). %%-------------------------------------------------------------------- +handle_sync_event({request, ChannelPid, ChannelId, Type, Data, Timeout}, From, StateName, State0) -> + {{replies, Replies}, State1} = 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)}; + +handle_sync_event({request, ChannelId, Type, Data, Timeout}, From, StateName, State0) -> + {{replies, Replies}, State1} = 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)}; + +handle_sync_event({global_request, Pid, _, _, _} = Request, From, StateName, + #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)}; -%% Replaced with option to connection_info/3. For now keep -%% for backwards compatibility -handle_sync_event(peer_address, _From, StateName, - #state{ssh_params = #ssh{peer = {_, Address}}} = State) -> - {reply, {ok, Address}, StateName, State}. +handle_sync_event({data, ChannelId, Type, Data, Timeout}, From, StateName, + #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}), + start_timeout(ChannelId, From, Timeout), + {next_state, StateName, next_packet(State)}; + {noreply, Connection} -> + start_timeout(ChannelId, From, Timeout), + {next_state, StateName, next_packet(State0#state{connection_state = Connection})} + end; + +handle_sync_event({eof, ChannelId}, _From, StateName, + #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)}; + _ -> + {reply, {error,closed}, StateName, State0} + end; + +handle_sync_event({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, + From, StateName, #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), + Channel = #channel{type = Type, + sys = "none", + user = ChannelPid, + local_id = ChannelId, + recv_window_size = InitialWindowSize, + recv_packet_size = MaxPacketSize}, + 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))}; + +handle_sync_event({send_window, ChannelId}, _From, StateName, + #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} -> + {ok, {WinSize, Packsize}}; + 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) -> + + Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{recv_window_size = WinSize, + recv_packet_size = Packsize} -> + {ok, {WinSize, Packsize}}; + undefined -> + {error, einval} + end, + {reply, Reply, StateName, next_packet(State)}; + +handle_sync_event({connection_info, Options}, _From, StateName, State) -> + Info = ssh_info(Options, State, []), + {reply, Info, StateName, State}; + +handle_sync_event({channel_info, ChannelId, Options}, _From, StateName, + #state{connection_state = #connection{channel_cache = Cache}} = State) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{} = Channel -> + Info = ssh_channel_info(Options, Channel, []), + {reply, Info, StateName, State}; + undefined -> + {reply, [], StateName, State} + end; + +handle_sync_event({info, ChannelPid}, _From, StateName, + #state{connection_state = + #connection{channel_cache = Cache}} = State) -> + Result = ssh_channel:cache_foldl( + fun(Channel, Acc) when ChannelPid == all; + Channel#channel.user == ChannelPid -> + [Channel | Acc]; + (_, Acc) -> + Acc + end, [], Cache), + {reply, {ok, Result}, StateName, 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)}; + +handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0, + role = Role, + opts = Opts} = 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), + SSHOpts = proplists:get_value(ssh_opts, Opts), + disconnect_fun(Reason, SSHOpts), + {stop, normal, ok, State#state{connection_state = Connection}}. %%-------------------------------------------------------------------- -%% Function: -%% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}| -%% {next_state, NextStateName, NextState, -%% Timeout} | -%% {stop, Reason, NewState} -%% Description: This function is called by a gen_fsm when it receives any -%% other message than a synchronous or asynchronous event -%% (or a system message). +-spec handle_info({atom(), port(), binary()} | {atom(), port()} | + term (), state_name(), #state{}) -> gen_fsm_state_return(). %%-------------------------------------------------------------------- + handle_info({Protocol, Socket, "SSH-" ++ _ = Version}, hello, #state{socket = Socket, transport_protocol = Protocol} = State ) -> @@ -649,15 +850,35 @@ handle_info({Protocol, Socket, Data}, Statename, handle_info({CloseTag, _Socket}, _StateName, #state{transport_close_tag = CloseTag, ssh_params = #ssh{role = _Role, opts = _Opts}} = State) -> - DisconnectMsg = - #ssh_msg_disconnect{code = ?SSH_DISCONNECT_CONNECTION_LOST, - description = "Connection Lost", - language = "en"}, - {stop, {shutdown, DisconnectMsg}, State}; + {stop, {shutdown, "Connection Lost"}, State}; + +handle_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, + State#state{connection_state = + Connection#connection{requests = + lists:delete(Request, Requests)}}}; + false -> + {next_state, Statename, State} + end; + +%%% Handle that ssh channels user process goes down +handle_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)}; %%% So that terminate will be run when supervisor is shutdown handle_info({'EXIT', _Sup, Reason}, _StateName, State) -> - {stop, Reason, State}; + {stop, {shutdown, Reason}, State}; + +handle_info({check_cache, _ , _}, + StateName, #state{connection_state = + #connection{channel_cache = Cache}} = State) -> + {next_state, StateName, check_cache(State, Cache)}; handle_info(UnexpectedMessage, StateName, #state{ssh_params = SshParams} = State) -> Msg = lists:flatten(io_lib:format( @@ -671,20 +892,17 @@ handle_info(UnexpectedMessage, StateName, #state{ssh_params = SshParams} = State {next_state, StateName, State}. %%-------------------------------------------------------------------- -%% Function: terminate(Reason, StateName, State) -> void() -%% Description:This function is called by a gen_fsm when it is about -%% to terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_fsm terminates with -%% Reason. The return value is ignored. +-spec terminate(Reason::term(), state_name(), #state{}) -> _. %%-------------------------------------------------------------------- terminate(normal, _, #state{transport_cb = Transport, - socket = Socket, - manager = Pid}) -> - (catch ssh_userreg:delete_user(Pid)), + connection_state = Connection, + socket = Socket}) -> + terminate_subsytem(Connection), + (catch ssh_userreg:delete_user(self())), (catch Transport:close(Socket)), ok; -%% Terminated as manager terminated +%% Terminated by supervisor terminate(shutdown, StateName, #state{ssh_params = Ssh0} = State) -> DisconnectMsg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, @@ -694,31 +912,34 @@ terminate(shutdown, StateName, #state{ssh_params = Ssh0} = State) -> send_msg(SshPacket, State), terminate(normal, StateName, State#state{ssh_params = Ssh}); -terminate({shutdown, #ssh_msg_disconnect{} = Msg}, StateName, #state{ssh_params = Ssh0, manager = Pid} = State) -> - {SshPacket, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), +terminate({shutdown, #ssh_msg_disconnect{} = Msg}, StateName, + #state{ssh_params = Ssh0} = State) -> + {SshPacket, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), send_msg(SshPacket, State), - ssh_connection_manager:event(Pid, Msg), - terminate(normal, StateName, State#state{ssh_params = Ssh}); -terminate({shutdown, {#ssh_msg_disconnect{} = Msg, ErrorMsg}}, StateName, #state{ssh_params = Ssh0, manager = Pid} = State) -> - {SshPacket, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), - send_msg(SshPacket, State), - ssh_connection_manager:event(Pid, Msg, ErrorMsg), - terminate(normal, StateName, State#state{ssh_params = Ssh}); -terminate(Reason, StateName, #state{ssh_params = Ssh0, manager = Pid} = 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) -> + terminate_subsytem(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), - ssh_connection_manager:event(Pid, DisconnectMsg), send_msg(SshPacket, State), terminate(normal, StateName, State#state{ssh_params = Ssh}). +terminate_subsytem(#connection{system_supervisor = SysSup, + sub_system_supervisor = SubSysSup}) when is_pid(SubSysSup) -> + ssh_system_sup:stop_subsystem(SysSup, SubSysSup); +terminate_subsytem(_) -> + ok. + %%-------------------------------------------------------------------- -%% Function: -%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} -%% Description: Convert process state when code is changed +-spec code_change(OldVsn::term(), state_name(), Oldstate::term(), Extra::term()) -> + {ok, state_name(), #state{}}. %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. @@ -726,6 +947,39 @@ code_change(_OldVsn, StateName, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +init_role(#state{role = client, 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, + [self(), data_size]), + State0#state{starter = Pid, + idle_timer_ref = TimerRef}; +init_role(#state{role = server, 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 + }}. + +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. + init_ssh(client = Role, Vsn, Version, Options, Socket) -> IOCb = case proplists:get_value(user_interaction, Options, true) of true -> @@ -843,7 +1097,15 @@ send_all_state_event(FsmPid, Event) -> gen_fsm:send_all_state_event(FsmPid, Event). sync_send_all_state_event(FsmPid, Event) -> - gen_fsm:sync_send_all_state_event(FsmPid, Event). + try gen_fsm:sync_send_all_state_event(FsmPid, Event, infinity) + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{{shutdown, _},_} -> + {error, closed} + end. %% simulate send_all_state_event(self(), Event) event(#ssh_msg_disconnect{} = Event, StateName, State) -> @@ -856,10 +1118,33 @@ event(#ssh_msg_unimplemented{} = Event, StateName, State) -> handle_event(Event, StateName, State); %% simulate send_event(self(), Event) event(Event, StateName, State) -> - ?MODULE:StateName(Event, State). + try + ?MODULE:StateName(Event, State) + catch + throw:#ssh_msg_disconnect{} = DisconnectMsg -> + handle_disconnect(DisconnectMsg, State); + throw:{ErrorToDisplay, #ssh_msg_disconnect{} = DisconnectMsg} -> + handle_disconnect(DisconnectMsg, State, ErrorToDisplay); + _:Error -> + log_error(Error), + handle_disconnect(#ssh_msg_disconnect{code = error_code(StateName), + description = "Internal error", + language = "en"}, State) + 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(<<?BYTE(Byte), _/binary>> = Msg, StateName, - #state{manager = Pid} = State0, EncData) + #state{ + role = Role, + starter = User, + opts = Opts, + renegotiate = Renegotiation, + connection_state = Connection0} = State0, EncData) when Byte == ?SSH_MSG_GLOBAL_REQUEST; Byte == ?SSH_MSG_REQUEST_SUCCESS; Byte == ?SSH_MSG_REQUEST_FAILURE; @@ -874,16 +1159,38 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName, Byte == ?SSH_MSG_CHANNEL_REQUEST; Byte == ?SSH_MSG_CHANNEL_SUCCESS; Byte == ?SSH_MSG_CHANNEL_FAILURE -> - - try - ssh_connection_manager:event(Pid, Msg), - State = generate_event_new_state(State0, EncData), - next_packet(State), - {next_state, StateName, State} + ConnectionMsg = ssh_message:decode(Msg), + State1 = generate_event_new_state(State0, EncData), + try ssh_connection:handle_msg(ConnectionMsg, Connection0, Role) of + {{replies, Replies}, Connection} -> + State = send_replies(Replies, State1#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}), + SSHOpts = proplists:get_value(ssh_opts, Opts), + disconnect_fun(Reason, SSHOpts), + {stop, {shutdown, normal}, State#state{connection_state = Connection}} catch - exit:{noproc, Reason} -> - {stop, {shutdown, Reason}, State0} + _: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}), + SSHOpts = proplists:get_value(ssh_opts, Opts), + disconnect_fun(Reason, SSHOpts), + {stop, {shutdown, Error}, State#state{connection_state = Connection}} end; + generate_event(Msg, StateName, State0, EncData) -> Event = ssh_message:decode(Msg), State = generate_event_new_state(State0, EncData), @@ -895,6 +1202,100 @@ generate_event(Msg, StateName, State0, EncData) -> event(Event, StateName, State) end. + +handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, + #state{connection_state = + #connection{channel_cache = Cache}} = State0) -> + case ssh_channel:cache_lookup(Cache, ChannelId) of + #channel{remote_id = Id} = Channel -> + 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}; + undefined -> + {{replies, []}, State0} + end. + +handle_request(ChannelId, Type, Data, WantReply, From, + #state{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), + Replies = [{connection_reply, Msg}], + State = add_request(WantReply, ChannelId, From, State0), + {{replies, Replies}, State}; + undefined -> + {{replies, []}, State0} + end. + +handle_global_request({global_request, ChannelPid, + "tcpip-forward" = Type, WantReply, + <<?UINT32(IPLen), + IP:IPLen/binary, ?UINT32(Port)>> = Data}, + #state{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), + Msg = ssh_connection:global_request_msg(Type, WantReply, Data), + send_replies([{connection_reply, Msg}], State#state{connection_state = Connection}); + +handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type, + WantReply, <<?UINT32(IPLen), + IP:IPLen/binary, ?UINT32(Port)>> = Data}, + #state{connection_state = Connection0} = State) -> + 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}); + +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). + +handle_idle_timeout(#state{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, #state{connection_state = + #connection{channel_cache = Cache}} = + State) -> + ssh_channel:cache_foldl( + fun(Channel, Acc) when Channel#channel.user == ChannelPid -> + ssh_channel:cache_delete(Cache, + Channel#channel.local_id), + Acc; + (_,Acc) -> + Acc + end, [], Cache), + {{replies, []}, check_cache(State, Cache)}. + +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, #state{connection_state = + #connection{requests = Requests0} = + Connection} = State) -> + Requests = [{ChannelId, From} | Requests0], + State#state{connection_state = Connection#connection{requests = Requests}}. + +new_channel_id(#state{connection_state = #connection{channel_id_seed = Id} = + Connection} + = State) -> + {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) -> @@ -904,7 +1305,6 @@ generate_event_new_state(#state{ssh_params = 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}, @@ -978,10 +1378,10 @@ handle_ssh_packet(Length, StateName, #state{decoded_data_buffer = DecData0, handle_disconnect(DisconnectMsg, State0) end. -handle_disconnect(#ssh_msg_disconnect{} = Msg, State) -> - {stop, {shutdown, Msg}, State}. -handle_disconnect(#ssh_msg_disconnect{} = Msg, State, ErrorMsg) -> - {stop, {shutdown, {Msg, ErrorMsg}}, State}. +handle_disconnect(#ssh_msg_disconnect{description = Desc}, State) -> + {stop, {shutdown, Desc}, State}. +handle_disconnect(#ssh_msg_disconnect{description = Desc}, State, ErrorMsg) -> + {stop, {shutdown, {Desc, ErrorMsg}}, State}. counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) -> Ssh#ssh{c_vsn = NumVsn , c_version = StrVsn}; @@ -1019,29 +1419,40 @@ retry_fun(User, Reason, Opts) -> catch Fun(User, Reason) end. -ssh_info_handler(Options, Ssh, State, From) -> - Info = ssh_info(Options, Ssh, State, []), - ssh_connection_manager:send_msg({channel_requst_reply, From, Info}). - -ssh_info([], _, _, Acc) -> +ssh_info([], _State, Acc) -> Acc; +ssh_info([client_version | Rest], #state{ssh_params = #ssh{c_vsn = IntVsn, + c_version = StringVsn}} = State, Acc) -> + ssh_info(Rest, State, [{client_version, {IntVsn, StringVsn}} | Acc]); -ssh_info([client_version | Rest], #ssh{c_vsn = IntVsn, - c_version = StringVsn} = SshParams, State, Acc) -> - ssh_info(Rest, SshParams, State, [{client_version, {IntVsn, StringVsn}} | Acc]); +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([server_version | Rest], #ssh{s_vsn = IntVsn, - s_version = StringVsn} = SshParams, State, Acc) -> - ssh_info(Rest, SshParams, State, [{server_version, {IntVsn, StringVsn}} | Acc]); +ssh_info([peer | Rest], #state{ssh_params = #ssh{peer = Peer}} = State, Acc) -> + ssh_info(Rest, State, [{peer, Peer} | Acc]); -ssh_info([peer | Rest], #ssh{peer = Peer} = SshParams, State, Acc) -> - ssh_info(Rest, SshParams, State, [{peer, Peer} | Acc]); +ssh_info([sockname | Rest], #state{socket = Socket} = State, Acc) -> + ssh_info(Rest, State, [{sockname,inet:sockname(Socket)}|Acc]); -ssh_info([sockname | Rest], SshParams, #state{socket=Socket}=State, Acc) -> - ssh_info(Rest, SshParams, State, [{sockname,inet:sockname(Socket)}|Acc]); +ssh_info([ _ | Rest], State, Acc) -> + ssh_info(Rest, State, Acc). -ssh_info([ _ | Rest], SshParams, State, Acc) -> - ssh_info(Rest, SshParams, State, Acc). +ssh_channel_info([], _, Acc) -> + Acc; + +ssh_channel_info([recv_window | Rest], #channel{recv_window_size = WinSize, + recv_packet_size = Packsize + } = Channel, Acc) -> + ssh_channel_info(Rest, Channel, [{recv_window, {{win_size, WinSize}, + {packet_size, Packsize}}} | Acc]); +ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize, + send_packet_size = Packsize + } = Channel, Acc) -> + ssh_channel_info(Rest, Channel, [{send_window, {{win_size, WinSize}, + {packet_size, Packsize}}} | Acc]); +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: " @@ -1050,3 +1461,101 @@ log_error(Reason) -> [Reason, erlang:get_stacktrace()]), error_logger:error_report(Report), "Internal error". + +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}) -> + 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). + +disconnect_fun(_, undefined) -> + ok; +disconnect_fun(Reason, Opts) -> + case proplists:get_value(disconnectfun, Opts) of + undefined -> + ok; + Fun -> + catch Fun(Reason) + end. + +check_cache(#state{opts = Opts} = State, Cache) -> + %% Check the number of entries in Cache + case proplists:get_value(size, ets:info(Cache)) of + 0 -> + case proplists:get_value(idle_time, Opts, infinity) of + infinity -> + State; + Time -> + handle_idle_timer(Time, State) + end; + _ -> + State + end. + +handle_idle_timer(Time, #state{idle_timer_ref = undefined} = State) -> + TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), + State#state{idle_timer_ref=TimerRef}; +handle_idle_timer(_, State) -> + State. + +remove_timer_ref(State) -> + case State#state.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} + end. + +socket_control(Socket, Pid, Transport) -> + case Transport:controlling_process(Socket, Pid) of + ok -> + send_event(Pid, socket_control); + {error, Reason} -> + {error, Reason} + end. + +handshake(Pid, Ref, Timeout) -> + receive + ssh_connected -> + erlang:demonitor(Ref), + {ok, Pid}; + {Pid, not_connected, Reason} -> + {error, Reason}; + {Pid, user_password} -> + Pass = io:get_password(), + Pid ! Pass, + handshake(Pid, Ref, Timeout); + {Pid, question} -> + Answer = io:get_line(""), + Pid ! Answer, + handshake(Pid, Ref, Timeout); + {'DOWN', _, process, Pid, {shutdown, Reason}} -> + {error, Reason}; + {'DOWN', _, process, Pid, Reason} -> + {error, Reason} + after Timeout -> + stop(Pid), + {error, Timeout} + end. + +start_timeout(_,_, infinity) -> + ok; +start_timeout(Channel, From, Time) -> + erlang:send_after(Time, self(), {timeout, {Channel, From}}). diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl deleted file mode 100644 index fb57a790fe..0000000000 --- a/lib/ssh/src/ssh_connection_manager.erl +++ /dev/null @@ -1,914 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% -%% -%%---------------------------------------------------------------------- -%% Purpose: Handles multiplexing to ssh channels and global connection -%% requests e.i. the SSH Connection Protocol (RFC 4254), that provides -%% interactive login sessions, remote execution of commands, forwarded -%% TCP/IP connections, and forwarded X11 connections. Details of the -%% protocol is implemented in ssh_connection.erl -%% ---------------------------------------------------------------------- --module(ssh_connection_manager). - --behaviour(gen_server). - --include("ssh.hrl"). --include("ssh_connect.hrl"). --include("ssh_transport.hrl"). - --export([start_link/1]). - --export([info/1, info/2, - renegotiate/1, connection_info/2, channel_info/3, - peer_addr/1, send_window/3, recv_window/3, adjust_window/3, - close/2, stop/1, send/5, - send_eof/2]). - --export([open_channel/6, reply_request/3, request/6, request/7, global_request/4, event/2, event/3, cast/2]). - -%% Internal application API and spawn --export([send_msg/1, ssh_channel_info_handler/3]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --define(DBG_MESSAGE, true). - --record(state, - { - role, - client, - starter, - connection, % pid() - connection_state, % #connection{} - latest_channel_id = 0, - opts, - channel_args, - idle_timer_ref, % timerref - connected - }). - -%%==================================================================== -%% Internal application API -%%==================================================================== - -start_link(Opts) -> - gen_server:start_link(?MODULE, Opts, []). - -open_channel(ConnectionManager, ChannelType, ChannelSpecificData, - InitialWindowSize, MaxPacketSize, Timeout) -> - case (catch call(ConnectionManager, {open, self(), ChannelType, - InitialWindowSize, - MaxPacketSize, ChannelSpecificData}, - Timeout)) of - {open, Channel} -> - {ok, Channel}; - Error -> - %% TODO: Best way? - Error - end. - -request(ConnectionManager, ChannelPid, ChannelId, Type, true, Data, Timeout) -> - call(ConnectionManager, {request, ChannelPid, ChannelId, Type, Data}, Timeout); -request(ConnectionManager, ChannelPid, ChannelId, Type, false, Data, _) -> - cast(ConnectionManager, {request, ChannelPid, ChannelId, Type, Data}). - -request(ConnectionManager, ChannelId, Type, true, Data, Timeout) -> - call(ConnectionManager, {request, ChannelId, Type, Data}, Timeout); -request(ConnectionManager, ChannelId, Type, false, Data, _) -> - cast(ConnectionManager, {request, ChannelId, Type, Data}). - -reply_request(ConnectionManager, Status, ChannelId) -> - cast(ConnectionManager, {reply_request, Status, ChannelId}). - -global_request(ConnectionManager, Type, true = Reply, Data) -> - case call(ConnectionManager, - {global_request, self(), Type, Reply, Data}) of - {ssh_cm, ConnectionManager, {success, _}} -> - ok; - {ssh_cm, ConnectionManager, {failure, _}} -> - error - end; - -global_request(ConnectionManager, Type, false = Reply, Data) -> - cast(ConnectionManager, {global_request, self(), Type, Reply, Data}). - -event(ConnectionManager, BinMsg, ErrorMsg) -> - call(ConnectionManager, {ssh_msg, self(), BinMsg, ErrorMsg}). -event(ConnectionManager, BinMsg) -> - call(ConnectionManager, {ssh_msg, self(), BinMsg}). -info(ConnectionManager) -> - info(ConnectionManager, {info, all}). - -info(ConnectionManager, ChannelProcess) -> - call(ConnectionManager, {info, ChannelProcess}). - -%% TODO: Do we really want this function? Should not -%% renegotiation be triggered by configurable timer -%% or amount of data sent counter! -renegotiate(ConnectionManager) -> - cast(ConnectionManager, renegotiate). -renegotiate_data(ConnectionManager) -> - cast(ConnectionManager, renegotiate_data). -connection_info(ConnectionManager, Options) -> - call(ConnectionManager, {connection_info, Options}). - -channel_info(ConnectionManager, ChannelId, Options) -> - call(ConnectionManager, {channel_info, ChannelId, Options}). - -%% Replaced by option peer to connection_info/2 keep for now -%% for Backwards compatibility! -peer_addr(ConnectionManager) -> - call(ConnectionManager, {peer_addr, self()}). - -%% Backwards compatibility! -send_window(ConnectionManager, Channel, TimeOut) -> - call(ConnectionManager, {send_window, Channel}, TimeOut). -%% Backwards compatibility! -recv_window(ConnectionManager, Channel, TimeOut) -> - call(ConnectionManager, {recv_window, Channel}, TimeOut). - -adjust_window(ConnectionManager, Channel, Bytes) -> - cast(ConnectionManager, {adjust_window, Channel, Bytes}). - -close(ConnectionManager, ChannelId) -> - case call(ConnectionManager, {close, ChannelId}) of - ok -> - ok; - {error, channel_closed} -> - ok - end. - -stop(ConnectionManager) -> - case call(ConnectionManager, stop) of - ok -> - ok; - {error, channel_closed} -> - ok - end. - -send(ConnectionManager, ChannelId, Type, Data, Timeout) -> - call(ConnectionManager, {data, ChannelId, Type, Data}, Timeout). - -send_eof(ConnectionManager, ChannelId) -> - call(ConnectionManager, {eof, ChannelId}). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([server, _Socket, Opts]) -> - process_flag(trap_exit, true), - Cache = ssh_channel:cache_create(), - {ok, #state{role = server, - connection_state = #connection{channel_cache = Cache, - channel_id_seed = 0, - port_bindings = [], - requests = []}, - opts = Opts, - connected = false}}; - -init([client, Opts]) -> - process_flag(trap_exit, true), - {links, [Parent]} = process_info(self(), links), - Cache = ssh_channel:cache_create(), - Address = proplists:get_value(address, Opts), - Port = proplists:get_value(port, Opts), - SocketOpts = proplists:get_value(socket_opts, Opts), - Options = proplists:get_value(ssh_opts, Opts), - ChannelPid = proplists:get_value(channel_pid, Opts), - self() ! - {start_connection, client, [Parent, Address, Port, SocketOpts, Options]}, - TimerRef = get_idle_time(Options), - - {ok, #state{role = client, - client = ChannelPid, - connection_state = #connection{channel_cache = Cache, - channel_id_seed = 0, - port_bindings = [], - connection_supervisor = Parent, - requests = []}, - opts = Opts, - idle_timer_ref = TimerRef, - connected = false}}. - -%%-------------------------------------------------------------------- -%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%%-------------------------------------------------------------------- -handle_call({request, ChannelPid, ChannelId, Type, Data}, From, State0) -> - {{replies, Replies}, State} = handle_request(ChannelPid, - ChannelId, Type, Data, - true, From, State0), - %% Sends message to the connection handler process, reply to - %% channel is sent later when reply arrives from the connection - %% handler. - lists:foreach(fun send_msg/1, Replies), - SshOpts = proplists:get_value(ssh_opts, State0#state.opts), - case proplists:get_value(idle_time, SshOpts) of - infinity -> - ok; - _IdleTime -> - erlang:send_after(5000, self(), {check_cache, [], []}) - end, - {noreply, State}; - -handle_call({request, ChannelId, Type, Data}, From, State0) -> - {{replies, Replies}, State} = handle_request(ChannelId, Type, Data, - true, From, State0), - %% Sends message to the connection handler process, reply to - %% channel is sent later when reply arrives from the connection - %% handler. - lists:foreach(fun send_msg/1, Replies), - {noreply, State}; - -%% Message from ssh_connection_handler -handle_call({ssh_msg, Pid, Msg}, From, - #state{connection_state = Connection0, - role = Role, opts = Opts, connected = IsConnected, - client = ClientPid} - = State) -> - - %% To avoid that not all data sent by the other side is processes before - %% possible crash in ssh_connection_handler takes down the connection. - gen_server:reply(From, ok), - ConnectionMsg = decode_ssh_msg(Msg), - try ssh_connection:handle_msg(ConnectionMsg, Connection0, Pid, Role) of - {{replies, Replies}, Connection} -> - lists:foreach(fun send_msg/1, Replies), - {noreply, State#state{connection_state = Connection}}; - {noreply, Connection} -> - {noreply, State#state{connection_state = Connection}}; - {disconnect, {_, Reason}, {{replies, Replies}, Connection}} - when Role == client andalso (not IsConnected) -> - lists:foreach(fun send_msg/1, Replies), - ClientPid ! {self(), not_connected, Reason}, - {stop, {shutdown, normal}, State#state{connection = Connection}}; - {disconnect, Reason, {{replies, Replies}, Connection}} -> - lists:foreach(fun send_msg/1, Replies), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), - {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, undefined, - Role), - lists:foreach(fun send_msg/1, Replies), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), - {stop, {shutdown, Error}, State#state{connection_state = Connection}} - end; -handle_call({ssh_msg, Pid, Msg, ErrorMsg}, From, - #state{connection_state = Connection0, - role = Role, opts = Opts, connected = IsConnected, - client = ClientPid} - = State) -> - - %% To avoid that not all data sent by the other side is processes before - %% possible crash in ssh_connection_handler takes down the connection. - gen_server:reply(From, ok), - ConnectionMsg = decode_ssh_msg(Msg), - try ssh_connection:handle_msg(ConnectionMsg, Connection0, Pid, Role) of - {{replies, Replies}, Connection} -> - lists:foreach(fun send_msg/1, Replies), - {noreply, State#state{connection_state = Connection}}; - {noreply, Connection} -> - {noreply, State#state{connection_state = Connection}}; - {disconnect, {_, Reason}, {{replies, Replies}, Connection}} - when Role == client andalso (not IsConnected) -> - lists:foreach(fun send_msg/1, Replies), - ClientPid ! {self(), not_connected, {Reason, ErrorMsg}}, - {stop, {shutdown, normal}, State#state{connection = Connection}}; - {disconnect, Reason, {{replies, Replies}, Connection}} -> - lists:foreach(fun send_msg/1, Replies), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), - {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, undefined, - Role), - lists:foreach(fun send_msg/1, Replies), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), - {stop, {shutdown, Error}, State#state{connection_state = Connection}} - end; -handle_call({global_request, Pid, _, _, _} = Request, From, - #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), - {noreply, State}; - -handle_call({data, ChannelId, Type, Data}, From, - #state{connection_state = #connection{channel_cache = _Cache} - = Connection0, - connection = ConnectionPid} = State) -> - channel_data(ChannelId, Type, Data, Connection0, ConnectionPid, From, - State); - -handle_call({eof, ChannelId}, _From, - #state{connection = Pid, connection_state = - #connection{channel_cache = Cache}} = State) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = Id, sent_close = false} -> - send_msg({connection_reply, Pid, - ssh_connection:channel_eof_msg(Id)}), - {reply, ok, State}; - _ -> - {reply, {error,closed}, State} - end; - -handle_call({connection_info, Options}, From, - #state{connection = Connection} = State) -> - ssh_connection_handler:connection_info(Connection, From, Options), - %% Reply will be sent by the connection handler by calling - %% ssh_connection_handler:send_msg/1. - {noreply, State}; - -handle_call({channel_info, ChannelId, Options}, From, - #state{connection_state = #connection{channel_cache = Cache}} = State) -> - - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{} = Channel -> - spawn(?MODULE, ssh_channel_info_handler, [Options, Channel, From]), - {noreply, State}; - undefined -> - {reply, []} - end; - -handle_call({info, ChannelPid}, _From, - #state{connection_state = - #connection{channel_cache = Cache}} = State) -> - Result = ssh_channel:cache_foldl( - fun(Channel, Acc) when ChannelPid == all; - Channel#channel.user == ChannelPid -> - [Channel | Acc]; - (_, Acc) -> - Acc - end, [], Cache), - {reply, {ok, Result}, State}; - -handle_call({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data}, - From, #state{connection = Pid, - 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), - send_msg({connection_reply, Pid, Msg}), - Channel = #channel{type = Type, - sys = "none", - user = ChannelPid, - local_id = ChannelId, - recv_window_size = InitialWindowSize, - recv_packet_size = MaxPacketSize}, - ssh_channel:cache_update(Cache, Channel), - State = add_request(true, ChannelId, From, State1), - {noreply, remove_timer_ref(State)}; - -handle_call({send_window, ChannelId}, _From, - #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} -> - {ok, {WinSize, Packsize}}; - undefined -> - {error, einval} - end, - {reply, Reply, State}; - -handle_call({recv_window, ChannelId}, _From, - #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} -> - {ok, {WinSize, Packsize}}; - undefined -> - {error, einval} - end, - {reply, Reply, State}; - -%% Replaced by option peer to connection_info/2 keep for now -%% for Backwards compatibility! -handle_call({peer_addr, _ChannelId}, _From, - #state{connection = Pid} = State) -> - Reply = ssh_connection_handler:peer_address(Pid), - {reply, Reply, State}; - -handle_call(opts, _, #state{opts = Opts} = State) -> - {reply, Opts, State}; - -handle_call({close, ChannelId}, _, - #state{connection = Pid, connection_state = - #connection{channel_cache = Cache}} = State) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = Id} = Channel -> - send_msg({connection_reply, Pid, - ssh_connection:channel_close_msg(Id)}), - ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}), - SshOpts = proplists:get_value(ssh_opts, State#state.opts), - case proplists:get_value(idle_time, SshOpts) of - infinity -> - ok; - _IdleTime -> - erlang:send_after(5000, self(), {check_cache, [], []}) - end, - {reply, ok, State}; - undefined -> - {reply, ok, State} - end; - -handle_call(stop, _, #state{connection_state = Connection0, - role = Role, - opts = Opts} = State) -> - {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, undefined, - Role), - lists:foreach(fun send_msg/1, Replies), - SSHOpts = proplists:get_value(ssh_opts, Opts), - disconnect_fun(Reason, SSHOpts), - {stop, normal, ok, State#state{connection_state = Connection}}; - -%% API violation make it the violaters problem -%% by ignoring it. The violating process will get -%% a timeout or hang. -handle_call(_, _, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast({request, ChannelPid, ChannelId, Type, Data}, State0) -> - {{replies, Replies}, State} = handle_request(ChannelPid, ChannelId, - Type, Data, - false, none, State0), - lists:foreach(fun send_msg/1, Replies), - {noreply, State}; - -handle_cast({request, ChannelId, Type, Data}, State0) -> - {{replies, Replies}, State} = handle_request(ChannelId, Type, Data, - false, none, State0), - lists:foreach(fun send_msg/1, Replies), - {noreply, State}; - -handle_cast({reply_request, Status, ChannelId}, #state{connection_state = - #connection{channel_cache = Cache}} = State0) -> - State = case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = RemoteId} -> - cm_message({Status, RemoteId}, State0); - undefined -> - State0 - end, - {noreply, State}; - -handle_cast({global_request, _, _, _, _} = Request, State0) -> - State = handle_global_request(Request, State0), - {noreply, State}; - -handle_cast(renegotiate, #state{connection = Pid} = State) -> - ssh_connection_handler:renegotiate(Pid), - {noreply, State}; -handle_cast(renegotiate_data, #state{connection = Pid} = State) -> - ssh_connection_handler:renegotiate_data(Pid), - {noreply, State}; -handle_cast({adjust_window, ChannelId, Bytes}, - #state{connection = Pid, connection_state = - #connection{channel_cache = Cache}} = State) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{recv_window_size = WinSize, remote_id = Id} = Channel -> - ssh_channel:cache_update(Cache, Channel#channel{recv_window_size = - WinSize + Bytes}), - Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes), - send_msg({connection_reply, Pid, Msg}); - undefined -> - ignore - end, - {noreply, State}; - -handle_cast({success, ChannelId}, #state{connection = Pid} = State) -> - Msg = ssh_connection:channel_success_msg(ChannelId), - send_msg({connection_reply, Pid, Msg}), - {noreply, State}; - -handle_cast({failure, ChannelId}, #state{connection = Pid} = State) -> - Msg = ssh_connection:channel_failure_msg(ChannelId), - send_msg({connection_reply, Pid, Msg}), - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info({start_connection, server, - [Address, Port, Socket, Options, SubSysSup]}, - #state{connection_state = CState} = State) -> - {ok, Connection} = ssh_transport:accept(Address, Port, Socket, Options), - Shell = proplists:get_value(shell, Options), - Exec = proplists:get_value(exec, Options), - CliSpec = proplists:get_value(ssh_cli, Options, {ssh_cli, [Shell]}), - ssh_connection_handler:send_event(Connection, socket_control), - erlang:send_after(60000, self(), rekey_data), - {noreply, State#state{connection = Connection, - connection_state = - CState#connection{address = Address, - port = Port, - cli_spec = CliSpec, - options = Options, - exec = Exec, - sub_system_supervisor = SubSysSup - }}}; - -handle_info({start_connection, client, - [Parent, Address, Port, SocketOpts, Options]}, - #state{client = Pid} = State) -> - case (catch ssh_transport:connect(Parent, Address, - Port, SocketOpts, Options)) of - {ok, Connection} -> - erlang:send_after(60000, self(), rekey_data), - erlang:send_after(3600000, self(), rekey), - {noreply, State#state{connection = Connection}}; - Reason -> - Pid ! {self(), not_connected, Reason}, - {stop, {shutdown, normal}, State} - end; -handle_info({check_cache, _ , _}, - #state{connection_state = - #connection{channel_cache = Cache}} = State) -> - {noreply, check_cache(State, Cache)}; -handle_info({ssh_cm, _Sender, Msg}, State0) -> - %% Backwards compatibility! - State = cm_message(Msg, State0), - {noreply, State}; - -%% Nop backwards compatibility -handle_info({same_user, _}, State) -> - {noreply, State}; - -handle_info(ssh_connected, #state{role = client, client = Pid} - = State) -> - Pid ! {self(), is_connected}, - {noreply, State#state{connected = true, opts = handle_password(State#state.opts)}}; - -handle_info(ssh_connected, #state{role = server} = State) -> - {noreply, State#state{connected = true}}; - -%%% Handle that ssh channels user process goes down -handle_info({'DOWN', _Ref, process, ChannelPid, _Reason}, State) -> - handle_down(handle_channel_down(ChannelPid, State)); - -%%% So that terminate will be run when supervisor is shutdown -handle_info({'EXIT', _Sup, Reason}, State) -> - {stop, Reason, State}; -handle_info(rekey, State) -> - renegotiate(self()), - erlang:send_after(3600000, self(), rekey), - {noreply, State}; -handle_info(rekey_data, State) -> - renegotiate_data(self()), - erlang:send_after(60000, self(), rekey_data), - {noreply, State}. -handle_password(Opts) -> - handle_rsa_password(handle_dsa_password(handle_normal_password(Opts))). -handle_normal_password(Opts) -> - case proplists:get_value(ssh_opts, Opts, false) of - false -> - Opts; - SshOpts -> - case proplists:get_value(password, SshOpts, false) of - false -> - Opts; - _Password -> - NewOpts = [{password, undefined}|lists:keydelete(password, 1, SshOpts)], - [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)] - end - end. -handle_dsa_password(Opts) -> - case proplists:get_value(ssh_opts, Opts, false) of - false -> - Opts; - SshOpts -> - case proplists:get_value(dsa_pass_phrase, SshOpts, false) of - false -> - Opts; - _Password -> - NewOpts = [{dsa_pass_phrase, undefined}|lists:keydelete(dsa_pass_phrase, 1, SshOpts)], - [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)] - end - end. -handle_rsa_password(Opts) -> - case proplists:get_value(ssh_opts, Opts, false) of - false -> - Opts; - SshOpts -> - case proplists:get_value(rsa_pass_phrase, SshOpts, false) of - false -> - Opts; - _Password -> - NewOpts = [{rsa_pass_phrase, undefined}|lists:keydelete(rsa_pass_phrase, 1, SshOpts)], - [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)] - end - end. -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -terminate(_Reason, #state{role = client, - connection_state = - #connection{connection_supervisor = Supervisor}}) -> - sshc_sup:stop_child(Supervisor); - -terminate(_Reason, #state{role = server, - connection_state = - #connection{sub_system_supervisor = SubSysSup}, - opts = Opts}) -> - Address = proplists:get_value(address, Opts), - Port = proplists:get_value(port, Opts), - SystemSup = ssh_system_sup:system_supervisor(Address, Port), - ssh_system_sup:stop_subsystem(SystemSup, SubSysSup). - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -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. -check_cache(State, Cache) -> - %% Check the number of entries in Cache - case proplists:get_value(size, ets:info(Cache)) of - 0 -> - Opts = proplists:get_value(ssh_opts, State#state.opts), - case proplists:get_value(idle_time, Opts) of - infinity -> - State; - undefined -> - State; - Time -> - case State#state.idle_timer_ref of - undefined -> - TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), - State#state{idle_timer_ref=TimerRef}; - _ -> - State - end - end; - _ -> - State - end. -remove_timer_ref(State) -> - case State#state.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} - end. -channel_data(Id, Type, Data, Connection0, ConnectionPid, From, State) -> - case ssh_connection:channel_data(Id, Type, Data, Connection0, - ConnectionPid, From) of - {{replies, Replies}, Connection} -> - lists:foreach(fun send_msg/1, Replies), - {noreply, State#state{connection_state = Connection}}; - {noreply, Connection} -> - {noreply, State#state{connection_state = Connection}} - end. - -call(Pid, Msg) -> - call(Pid, Msg, infinity). -call(Pid, Msg, Timeout) -> - try gen_server:call(Pid, Msg, Timeout) of - Result -> - Result - catch - exit:{timeout, _} -> - {error, timeout}; - exit:{normal, _} -> - {error, channel_closed}; - exit:{{shutdown, _}, _} -> - {error, channel_closed}; - exit:{noproc,_} -> - {error, channel_closed} - end. - -cast(Pid, Msg) -> - gen_server:cast(Pid, Msg). - -decode_ssh_msg(BinMsg) when is_binary(BinMsg)-> - ssh_message:decode(BinMsg); -decode_ssh_msg(Msg) -> - Msg. - - -send_msg(Msg) -> - catch do_send_msg(Msg). -do_send_msg({channel_data, Pid, Data}) -> - Pid ! {ssh_cm, self(), Data}; -do_send_msg({channel_requst_reply, From, Data}) -> - gen_server:reply(From, Data); -do_send_msg({connection_reply, Pid, Data}) -> - Msg = ssh_message:encode(Data), - ssh_connection_handler:send(Pid, Msg); -do_send_msg({flow_control, Cache, Channel, From, Msg}) -> - ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), - gen_server:reply(From, Msg); -do_send_msg({flow_control, From, Msg}) -> - gen_server:reply(From, Msg). - -handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, - #state{connection = Pid, - connection_state = - #connection{channel_cache = Cache}} = State0) -> - case ssh_channel:cache_lookup(Cache, ChannelId) of - #channel{remote_id = Id} = Channel -> - update_sys(Cache, Channel, Type, ChannelPid), - Msg = ssh_connection:channel_request_msg(Id, Type, - WantReply, Data), - Replies = [{connection_reply, Pid, Msg}], - State = add_request(WantReply, ChannelId, From, State0), - {{replies, Replies}, State}; - undefined -> - {{replies, []}, State0} - end. - -handle_request(ChannelId, Type, Data, WantReply, From, - #state{connection = Pid, - 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), - Replies = [{connection_reply, Pid, Msg}], - State = add_request(WantReply, ChannelId, From, State0), - {{replies, Replies}, State}; - undefined -> - {{replies, []}, State0} - end. - -handle_down({{replies, Replies}, State}) -> - lists:foreach(fun send_msg/1, Replies), - {noreply, State}. - -handle_channel_down(ChannelPid, #state{connection_state = - #connection{channel_cache = Cache}} = - State) -> - ssh_channel:cache_foldl( - fun(Channel, Acc) when Channel#channel.user == ChannelPid -> - ssh_channel:cache_delete(Cache, - Channel#channel.local_id), - Acc; - (_,Acc) -> - Acc - end, [], Cache), - {{replies, []}, check_cache(State, Cache)}. - -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, #state{connection_state = - #connection{requests = Requests0} = - Connection} = State) -> - Requests = [{ChannelId, From} | Requests0], - State#state{connection_state = Connection#connection{requests = Requests}}. - -new_channel_id(#state{connection_state = #connection{channel_id_seed = Id} = - Connection} - = State) -> - {Id, State#state{connection_state = - Connection#connection{channel_id_seed = Id + 1}}}. - -handle_global_request({global_request, ChannelPid, - "tcpip-forward" = Type, WantReply, - <<?UINT32(IPLen), - IP:IPLen/binary, ?UINT32(Port)>> = Data}, - #state{connection = ConnectionPid, - 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), - Msg = ssh_connection:global_request_msg(Type, WantReply, Data), - send_msg({connection_reply, ConnectionPid, Msg}), - State#state{connection_state = Connection}; - -handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type, - WantReply, <<?UINT32(IPLen), - IP:IPLen/binary, ?UINT32(Port)>> = Data}, - #state{connection = Pid, - connection_state = Connection0} = State) -> - Connection = ssh_connection:unbind(IP, Port, Connection0), - Msg = ssh_connection:global_request_msg(Type, WantReply, Data), - send_msg({connection_reply, Pid, Msg}), - State#state{connection_state = Connection}; - -handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type, - WantReply, Data}, #state{connection = Pid} = State) -> - Msg = ssh_connection:global_request_msg(Type, WantReply, Data), - send_msg({connection_reply, Pid, Msg}), - State. - -cm_message(Msg, State) -> - {noreply, NewState} = handle_cast(Msg, State), - NewState. - -disconnect_fun(Reason, Opts) -> - case proplists:get_value(disconnectfun, Opts) of - undefined -> - ok; - Fun -> - catch Fun(Reason) - end. - -ssh_channel_info_handler(Options, Channel, From) -> - Info = ssh_channel_info(Options, Channel, []), - send_msg({channel_requst_reply, From, Info}). - -ssh_channel_info([], _, Acc) -> - Acc; - -ssh_channel_info([recv_window | Rest], #channel{recv_window_size = WinSize, - recv_packet_size = Packsize - } = Channel, Acc) -> - ssh_channel_info(Rest, Channel, [{recv_window, {{win_size, WinSize}, - {packet_size, Packsize}}} | Acc]); -ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize, - send_packet_size = Packsize - } = Channel, Acc) -> - ssh_channel_info(Rest, Channel, [{send_window, {{win_size, WinSize}, - {packet_size, Packsize}}} | Acc]); -ssh_channel_info([ _ | Rest], Channel, Acc) -> - ssh_channel_info(Rest, Channel, Acc). - - - diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl index b620056310..c5abc8f23b 100644 --- a/lib/ssh/src/ssh_connection_sup.erl +++ b/lib/ssh/src/ssh_connection_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -25,8 +25,9 @@ -behaviour(supervisor). --export([start_link/1, start_handler_child/2, start_manager_child/2, - connection_manager/1]). +%% API +-export([start_link/1]). +-export([start_child/2]). %% Supervisor callback -export([init/1]). @@ -37,83 +38,23 @@ start_link(Args) -> supervisor:start_link(?MODULE, [Args]). -%% Will be called from the manager child process -start_handler_child(Sup, Args) -> - [Spec] = child_specs(handler, Args), - supervisor:start_child(Sup, Spec). - -%% Will be called from the acceptor process -start_manager_child(Sup, Args) -> - [Spec] = child_specs(manager, Args), - supervisor:start_child(Sup, Spec). - -connection_manager(SupPid) -> - try supervisor:which_children(SupPid) of - Children -> - {ok, ssh_connection_manager(Children)} - catch exit:{noproc,_} -> - {ok, undefined} - end. +start_child(Sup, Args) -> + supervisor:start_child(Sup, Args). %%%========================================================================= %%% Supervisor callback %%%========================================================================= -init([Args]) -> - RestartStrategy = one_for_all, +init(_) -> + RestartStrategy = simple_one_for_one, MaxR = 0, MaxT = 3600, - Children = child_specs(Args), - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. - -%%%========================================================================= -%%% Internal functions -%%%========================================================================= -child_specs(Opts) -> - case proplists:get_value(role, Opts) of - client -> - child_specs(manager, [client | Opts]); - server -> - %% Children started by acceptor process - [] - end. - -% The manager process starts the handler process -child_specs(manager, Opts) -> - [manager_spec(Opts)]; -child_specs(handler, Opts) -> - [handler_spec(Opts)]. - -manager_spec([server = Role, Socket, Opts]) -> - Name = make_ref(), - StartFunc = {ssh_connection_manager, start_link, [[Role, Socket, Opts]]}, - Restart = temporary, - Shutdown = 3600, - Modules = [ssh_connection_manager], - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}; - -manager_spec([client = Role | Opts]) -> - Name = make_ref(), - StartFunc = {ssh_connection_manager, start_link, [[Role, Opts]]}, - Restart = temporary, - Shutdown = 3600, - Modules = [ssh_connection_manager], - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. -handler_spec([Role, Socket, Opts]) -> - Name = make_ref(), - StartFunc = {ssh_connection_handler, - start_link, [Role, self(), Socket, Opts]}, - Restart = temporary, - Shutdown = 3600, + Name = undefined, % As simple_one_for_one is used. + StartFunc = {ssh_connection_handler, start_link, []}, + Restart = temporary, % E.g. should not be restarted + Shutdown = 4000, Modules = [ssh_connection_handler], Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. -ssh_connection_manager([]) -> - undefined; -ssh_connection_manager([{_, Child, _, [ssh_connection_manager]} | _]) -> - Child; -ssh_connection_manager([_ | Rest]) -> - ssh_connection_manager(Rest). + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index dc4943ace4..7bd0375521 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -29,7 +29,7 @@ -include("ssh_auth.hrl"). -include("ssh_transport.hrl"). --export([encode/1, decode/1, encode_host_key/1]). +-export([encode/1, decode/1, encode_host_key/1, decode_keyboard_interactive_prompts/2]). encode(#ssh_msg_global_request{ name = Name, @@ -238,6 +238,9 @@ encode(#ssh_msg_kex_dh_gex_group{p = Prime, g = Generator}) -> ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_GROUP, Prime, Generator], [byte, mpint, mpint]); +encode(#ssh_msg_kex_dh_gex_init{e = Public}) -> + ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_INIT, Public], [byte, mpint]); + encode(#ssh_msg_kex_dh_gex_reply{ %% Will be private key encode_host_key extracts only the public part! public_host_key = Key, @@ -468,6 +471,13 @@ decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?UINT32(Len0), Msg:Len0/binary, #ssh_msg_debug{always_display = erl_boolean(Bool), message = Msg, language = Lang}. + +decode_keyboard_interactive_prompts(<<>>, Acc) -> + lists:reverse(Acc); +decode_keyboard_interactive_prompts(<<?UINT32(Len), Prompt:Len/binary, ?BYTE(Bool), Bin/binary>>, + Acc) -> + decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]). + erl_boolean(0) -> false; erl_boolean(1) -> diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 3d469d3c6e..174ca0126b 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -76,7 +76,7 @@ listen(Addr, Port, Options) -> %% Description: Stops the listener %%-------------------------------------------------------------------- stop(Pid) -> - ssh_cli:stop(Pid). + ssh:stop_listener(Pid). %%% DEPRECATED END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index cd6defd535..e8855b09ac 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -25,7 +25,9 @@ -behaviour(supervisor). --export([start_link/1, connection_supervisor/1, channel_supervisor/1 +-export([start_link/1, + connection_supervisor/1, + channel_supervisor/1 ]). %% Supervisor callback @@ -61,9 +63,9 @@ init([Opts]) -> child_specs(Opts) -> case proplists:get_value(role, Opts) of client -> - [ssh_connectinon_child_spec(Opts)]; + []; server -> - [ssh_connectinon_child_spec(Opts), ssh_channel_child_spec(Opts)] + [ssh_channel_child_spec(Opts), ssh_connectinon_child_spec(Opts)] end. ssh_connectinon_child_spec(Opts) -> @@ -72,9 +74,9 @@ ssh_connectinon_child_spec(Opts) -> Role = proplists:get_value(role, Opts), Name = id(Role, ssh_connection_sup, Address, Port), StartFunc = {ssh_connection_sup, start_link, [Opts]}, - Restart = transient, + Restart = temporary, Shutdown = 5000, - Modules = [ssh_connection_sup], + Modules = [ssh_connection_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. @@ -84,7 +86,7 @@ ssh_channel_child_spec(Opts) -> Role = proplists:get_value(role, Opts), Name = id(Role, ssh_channel_sup, Address, Port), StartFunc = {ssh_channel_sup, start_link, [Opts]}, - Restart = transient, + Restart = temporary, Shutdown = infinity, Modules = [ssh_channel_sup], Type = supervisor, diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 36daf3b1ac..158a829eb0 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -40,7 +40,7 @@ -export([init/1]). %%%========================================================================= -%%% API +%%% Internal API %%%========================================================================= start_link(ServerOpts) -> Address = proplists:get_value(address, ServerOpts), @@ -146,7 +146,7 @@ ssh_acceptor_child_spec(ServerOpts) -> ssh_subsystem_child_spec(ServerOpts) -> Name = make_ref(), StartFunc = {ssh_subsystem_sup, start_link, [ServerOpts]}, - Restart = transient, + Restart = temporary, Shutdown = infinity, Modules = [ssh_subsystem_sup], Type = supervisor, diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 45292ca6f0..27723dc870 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -29,7 +29,6 @@ -include("ssh_transport.hrl"). -include("ssh.hrl"). --export([connect/5, accept/4]). -export([versions/2, hello_version_msg/1]). -export([next_seqnum/1, decrypt_first_block/2, decrypt_blocks/3, is_valid_mac/3, @@ -78,52 +77,6 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm, yes_no(Ssh, Prompt) -> (Ssh#ssh.io_cb):yes_no(Prompt, Ssh). -connect(ConnectionSup, Address, Port, SocketOpts, Opts) -> - Timeout = proplists:get_value(connect_timeout, Opts, infinity), - {_, Callback, _} = - proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}), - case do_connect(Callback, Address, Port, SocketOpts, Timeout) of - {ok, Socket} -> - {ok, Pid} = - ssh_connection_sup:start_handler_child(ConnectionSup, - [client, Socket, - [{address, Address}, - {port, Port} | - Opts]]), - Callback:controlling_process(Socket, Pid), - ssh_connection_handler:send_event(Pid, socket_control), - {ok, Pid}; - {error, Reason} -> - {error, Reason} - end. - -do_connect(Callback, Address, Port, SocketOpts, Timeout) -> - Opts = [{active, false} | SocketOpts], - case Callback:connect(Address, Port, Opts, Timeout) of - {error, nxdomain} -> - Callback:connect(Address, Port, lists:delete(inet6, Opts), Timeout); - {error, eafnosupport} -> - Callback:connect(Address, Port, lists:delete(inet6, Opts), Timeout); - {error, enetunreach} -> - Callback:connect(Address, Port, lists:delete(inet6, Opts), Timeout); - Other -> - Other - end. - -accept(Address, Port, Socket, Options) -> - {_, Callback, _} = - proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}), - ConnectionSup = - ssh_system_sup:connection_supervisor( - ssh_system_sup:system_supervisor(Address, Port)), - {ok, Pid} = - ssh_connection_sup:start_handler_child(ConnectionSup, - [server, Socket, - [{address, Address}, - {port, Port} | Options]]), - Callback:controlling_process(Socket, Pid), - {ok, Pid}. - format_version({Major,Minor}) -> "SSH-" ++ integer_to_list(Major) ++ "." ++ integer_to_list(Minor) ++ "-Erlang". diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index b299868d41..e18e18a9a9 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -267,7 +267,7 @@ xf_request(XF, Op, Arg) -> list_to_binary(Arg) end, Size = 1+size(Data), - ssh_connection:send(CM, Channel, <<?UINT32(Size), Op, Data/binary>>). + ssh_connection:send(CM, Channel, [<<?UINT32(Size), Op, Data/binary>>]). xf_send_reply(#ssh_xfer{cm = CM, channel = Channel}, Op, Arg) -> Data = if @@ -277,7 +277,7 @@ xf_send_reply(#ssh_xfer{cm = CM, channel = Channel}, Op, Arg) -> list_to_binary(Arg) end, Size = 1 + size(Data), - ssh_connection:send(CM, Channel, <<?UINT32(Size), Op, Data/binary>>). + ssh_connection:send(CM, Channel, [<<?UINT32(Size), Op, Data/binary>>]). xf_send_name(XF, ReqId, Name, Attr) -> xf_send_names(XF, ReqId, [{Name, Attr}]). diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 1d2779de23..e6b4b681a4 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -61,9 +61,9 @@ init(Args) -> %%%========================================================================= child_spec(_) -> Name = undefined, % As simple_one_for_one is used. - StartFunc = {ssh_connection_sup, start_link, []}, + StartFunc = {ssh_connection_handler, start_link, []}, Restart = temporary, Shutdown = infinity, - Modules = [ssh_connection_sup], + Modules = [ssh_connection_handler], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index e8f1d5213c..813031eab2 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -255,7 +255,7 @@ idle_time(Config) -> ssh_connection:close(ConnectionRef, Id), receive after 10000 -> - {error,channel_closed} = ssh_connection:session_channel(ConnectionRef, 1000) + {error, closed} = ssh_connection:session_channel(ConnectionRef, 1000) end, ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- @@ -448,10 +448,11 @@ internal_error(Config) when is_list(Config) -> {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {user_dir, UserDir}, {failfun, fun ssh_test_lib:failfun/2}]), - {error,"Internal error"} = + {error,Error} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, {user_dir, UserDir}, {user_interaction, false}]), + check_error(Error), ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- @@ -564,6 +565,15 @@ openssh_zlib_basic_test(Config) -> %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- +%% Due to timing the error message may or may not be delivered to +%% the "tcp-application" before the socket closed message is recived +check_error("Internal error") -> + ok; +check_error("Connection Lost") -> + ok; +check_error(Error) -> + ct:fail(Error). + basic_test(Config) -> ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), |