aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh
diff options
context:
space:
mode:
authorIngela Anderton Andin <[email protected]>2013-10-15 18:24:50 +0200
committerIngela Anderton Andin <[email protected]>2013-11-13 10:58:19 +0100
commit68263a48bfbdac4dc219a91f06af3d535d881850 (patch)
treef36d0cbb6c097faa7a17e8e6ee677f8262e8c924 /lib/ssh
parentd53cea682010766c82ba7088f40efcfafe196621 (diff)
downloadotp-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/ssh')
-rw-r--r--lib/ssh/src/Makefile1
-rw-r--r--lib/ssh/src/ssh.app.src1
-rw-r--r--lib/ssh/src/ssh.erl134
-rw-r--r--lib/ssh/src/ssh.hrl4
-rw-r--r--lib/ssh/src/ssh_acceptor.erl19
-rw-r--r--lib/ssh/src/ssh_auth.erl95
-rw-r--r--lib/ssh/src/ssh_bits.erl244
-rw-r--r--lib/ssh/src/ssh_channel.erl2
-rw-r--r--lib/ssh/src/ssh_channel_sup.erl4
-rw-r--r--lib/ssh/src/ssh_cli.erl40
-rw-r--r--lib/ssh/src/ssh_connect.hrl5
-rw-r--r--lib/ssh/src/ssh_connection.erl584
-rw-r--r--lib/ssh/src/ssh_connection_controler.erl137
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl1245
-rw-r--r--lib/ssh/src/ssh_connection_manager.erl914
-rw-r--r--lib/ssh/src/ssh_connection_sup.erl87
-rw-r--r--lib/ssh/src/ssh_message.erl12
-rw-r--r--lib/ssh/src/ssh_sftpd.erl2
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl16
-rw-r--r--lib/ssh/src/ssh_system_sup.erl6
-rw-r--r--lib/ssh/src/ssh_transport.erl47
-rw-r--r--lib/ssh/src/ssh_xfer.erl4
-rw-r--r--lib/ssh/src/sshc_sup.erl6
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl14
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),