aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ssh/src/ssh_connection.erl14
-rw-r--r--lib/ssh/test/Makefile3
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl504
-rw-r--r--lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key15
-rw-r--r--lib/ssh/test/ssh_echo_server.erl71
5 files changed, 310 insertions, 297 deletions
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 5372d48986..240d7f70d1 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -318,22 +318,22 @@ channel_data(ChannelId, DataType, Data,
From) ->
case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = Id, sent_close = false} = Channel0 ->
+ #channel{remote_id = Id, sent_close = false} = Channel0 ->
{SendList, Channel} = update_send_window(Channel0#channel{flow_control = From}, DataType,
Data, Connection),
Replies =
lists:map(fun({SendDataType, SendData}) ->
- {connection_reply, ConnectionPid,
- channel_data_msg(Id,
- SendDataType,
- SendData)}
+ {connection_reply, ConnectionPid,
+ channel_data_msg(Id,
+ SendDataType,
+ SendData)}
end, SendList),
FlowCtrlMsgs = flow_control(Replies,
Channel,
Cache),
{{replies, Replies ++ FlowCtrlMsgs}, Connection};
- _ ->
- gen_server:reply(From, {error, closed}),
+ _ ->
+ gen_server:reply(From, {error, closed}),
{noreply, Connection}
end.
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index 4be49da435..f5db31baee 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -37,7 +37,8 @@ MODULES= \
ssh_sftp_SUITE \
ssh_sftpd_SUITE \
ssh_sftpd_erlclient_SUITE \
- ssh_connection_SUITE
+ ssh_connection_SUITE \
+ ssh_echo_server
HRL_FILES_NEEDED_IN_TEST= \
$(ERL_TOP)/lib/ssh/src/ssh.hrl \
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index 5cd892def8..43a899f974 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -21,125 +21,69 @@
-module(ssh_connection_SUITE).
-include_lib("common_test/include/ct.hrl").
--include("test_server_line.hrl").
-%% Note: This directive should only be used in test suites.
-compile(export_all).
-define(SSH_DEFAULT_PORT, 22).
-define(EXEC_TIMEOUT, 10000).
-%% Test server callback functions
%%--------------------------------------------------------------------
-%% Function: init_per_suite(Config) -> Config
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Description: Initialization before the whole suite
-%%
-%% Note: This function is free to add any key/value pairs to the Config
-%% variable, but should NOT alter/remove any existing entries.
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [
+ {group, erlang_client},
+ interrupted_send
+ ].
+groups() ->
+ [{erlang_client, [], [simple_exec,
+ small_cat,
+ big_cat,
+ send_after_exit
+ ]}].
+
%%--------------------------------------------------------------------
+
init_per_suite(Config) ->
case catch crypto:start() of
ok ->
- case gen_tcp:connect("localhost", 22, []) of
- {error,econnrefused} ->
- {skip,"No openssh deamon"};
- _ ->
- Config
- end;
+ Config;
_Else ->
- {skip,"Could not start crypto!"}
+ {skip, "Crypto could not be started!"}
end.
-%%--------------------------------------------------------------------
-%% Function: end_per_suite(Config) -> _
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Description: Cleanup after the whole suite
-%%--------------------------------------------------------------------
end_per_suite(_Config) ->
crypto:stop(),
ok.
-
%%--------------------------------------------------------------------
-%% Function: init_per_testcase(TestCase, Config) -> Config
-%% Case - atom()
-%% Name of the test case that is about to be run.
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%%
-%% Description: Initialization before each test case
-%%
-%% Note: This function is free to add any key/value pairs to the Config
-%% variable, but should NOT alter/remove any existing entries.
-%% Description: Initialization before each test case
+init_per_group(erlang_client, Config) ->
+ case gen_tcp:connect("localhost", 22, []) of
+ {error,econnrefused} ->
+ {skip,"No openssh deamon"};
+ _ ->
+ Config
+ end;
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
%%--------------------------------------------------------------------
init_per_testcase(_TestCase, Config) ->
ssh:start(),
Config.
-%%--------------------------------------------------------------------
-%% Function: end_per_testcase(TestCase, Config) -> _
-%% Case - atom()
-%% Name of the test case that is about to be run.
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Description: Cleanup after each test case
-%%--------------------------------------------------------------------
-end_per_testcase(_TestCase, _Config) ->
+end_per_testcase(_Config) ->
ssh:stop(),
ok.
+%%% TEST cases starts here.
%%--------------------------------------------------------------------
-%% Function: all(Clause) -> TestCases
-%% Clause - atom() - suite | doc
-%% TestCases - [Case]
-%% Case - atom()
-%% Name of a test case.
-%% Description: Returns a list of all test cases in this test suite
-%%--------------------------------------------------------------------
-all() ->
- case os:find_executable("ssh") of
- false ->
- {skip, "openSSH not installed on host"};
- _ ->
- [{group, erlang_client}
- ]
- end.
-
-groups() ->
- [{erlang_client, [], [
- simple_exec,
- small_cat,
- big_cat,
- send_after_exit,
- interrupted_send
- ]}].
-
-init_per_group(erlang_server, Config) ->
- DataDir = ?config(data_dir, Config),
- UserDir = ?config(priv_dir, Config),
- ssh_test_lib:setup_dsa_known_host(DataDir, UserDir),
- Config;
-init_per_group(_, Config) ->
- Config.
-
-end_per_group(erlang_server, Config) ->
- UserDir = ?config(priv_dir, Config),
- ssh_test_lib:clean_dsa(UserDir),
- Config;
-end_per_group(_, Config) ->
- Config.
-
-%% TEST cases starts here.
-%--------------------------------------------------------------------
simple_exec(doc) ->
["Simple openssh connectivity test for ssh_connection:exec"];
-simple_exec(suite) ->
- [];
-
simple_exec(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
@@ -147,34 +91,30 @@ simple_exec(Config) when is_list(Config) ->
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"echo testing", infinity),
- %% receive response to input
- receive
- {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
-
- %% receive close messages
- receive
- {ssh_cm, ConnectionRef, {eof, ChannelId0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
- receive
- {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
- receive
- {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end.
-
-
-%--------------------------------------------------------------------
+ %% receive response to input
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} ->
+ ok
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
small_cat(doc) ->
["Use 'cat' to echo small data block back to us."];
-small_cat(suite) ->
- [];
-
small_cat(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
@@ -182,37 +122,34 @@ small_cat(Config) when is_list(Config) ->
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"cat", infinity),
- Data = <<"I like spaghetti squash">>,
- ok = ssh_connection:send(ConnectionRef, ChannelId0, Data),
- ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
-
- %% receive response to input
- receive
- {ssh_cm, ConnectionRef, {data, ChannelId0, 0, Data}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
-
- %% receive close messages
- receive
- {ssh_cm, ConnectionRef, {eof, ChannelId0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
- receive
- {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
- receive
- {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end.
-
-%--------------------------------------------------------------------
+ Data = <<"I like spaghetti squash">>,
+ ok = ssh_connection:send(ConnectionRef, ChannelId0, Data),
+ ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
+
+ %% receive response to input
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, Data}} ->
+ ok
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
big_cat(doc) ->
["Use 'cat' to echo large data block back to us."];
-big_cat(suite) ->
- [];
-
big_cat(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
@@ -220,167 +157,156 @@ big_cat(Config) when is_list(Config) ->
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"cat", infinity),
- %% build 10MB binary
- Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
-
- %% pre-adjust receive window so the other end doesn't block
- ssh_connection:adjust_window(ConnectionRef, ChannelId0, size(Data)),
-
- test_server:format("sending ~p byte binary~n",[size(Data)]),
- ok = ssh_connection:send(ConnectionRef, ChannelId0, Data, 10000),
- %timer:sleep(3000),
- ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
-
- %% collect echoed data until eof
- case big_cat_rx(ConnectionRef, ChannelId0) of
- {ok, Data} -> ok;
- {ok, Other} ->
- case size(Data) =:= size(Other) of
- true ->
- test_server:format("received and sent data are same size but do not match~n",[]);
- false ->
- test_server:format("sent ~p but only received ~p~n",[size(Data), size(Other)])
- end,
- test_server:fail(receive_data_mismatch);
- Else ->
- test_server:fail(Else)
- end,
-
- %% receive close messages (eof already consumed)
- receive
- {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
- receive
- {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end.
+ %% build 10MB binary
+ Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
+
+ %% pre-adjust receive window so the other end doesn't block
+ ssh_connection:adjust_window(ConnectionRef, ChannelId0, size(Data)),
+
+ test_server:format("sending ~p byte binary~n",[size(Data)]),
+ ok = ssh_connection:send(ConnectionRef, ChannelId0, Data, 10000),
+ ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
+
+ %% collect echoed data until eof
+ case big_cat_rx(ConnectionRef, ChannelId0) of
+ {ok, Data} ->
+ ok;
+ {ok, Other} ->
+ case size(Data) =:= size(Other) of
+ true ->
+ test_server:format("received and sent data are same"
+ "size but do not match~n",[]);
+ false ->
+ test_server:format("sent ~p but only received ~p~n",
+ [size(Data), size(Other)])
+ end,
+ ct:fail(receive_data_mismatch);
+ Else ->
+ ct:fail(Else)
+ end,
+
+ %% receive close messages (eof already consumed)
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
big_cat_rx(ConnectionRef, ChannelId) ->
- big_cat_rx(ConnectionRef, ChannelId, []).
+ big_cat_rx(ConnectionRef, ChannelId, []).
big_cat_rx(ConnectionRef, ChannelId, Acc) ->
- receive
- {ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
- %% ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)), % window was pre-adjusted, don't adjust again here
- big_cat_rx(ConnectionRef, ChannelId, [Data | Acc]);
- {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
- {ok, iolist_to_binary(lists:reverse(Acc))}
- after ?EXEC_TIMEOUT -> timeout
- end.
-
-%--------------------------------------------------------------------
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
+ %% ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)),
+ %% window was pre-adjusted, don't adjust again here
+ big_cat_rx(ConnectionRef, ChannelId, [Data | Acc]);
+ {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
+ {ok, iolist_to_binary(lists:reverse(Acc))}
+ after ?EXEC_TIMEOUT ->
+ timeout
+ end.
+
+%%--------------------------------------------------------------------
send_after_exit(doc) ->
["Send channel data after the channel has been closed."];
-send_after_exit(suite) ->
- [];
-
send_after_exit(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
- %% Shell command "false" will exit immediately
- success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ %% Shell command "false" will exit immediately
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
"false", infinity),
- timer:sleep(2000), %% Allow incoming eof/close/exit_status ssh messages to be processed
-
- Data = <<"I like spaghetti squash">>,
- case ssh_connection:send(ConnectionRef, ChannelId0, Data, 2000) of
- {error, closed} -> ok;
- ok -> test_server:fail({expected,{error,closed}});
- {error, timeout} -> test_server:fail({expected,{error,closed}});
- Else -> test_server:fail(Else)
- end,
-
- %% receive close messages
- receive
- {ssh_cm, ConnectionRef, {eof, ChannelId0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
- receive
- {ssh_cm, ConnectionRef, {exit_status, ChannelId0, _}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
- receive
- {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end.
-
-%--------------------------------------------------------------------
-interrupted_send(doc) ->
- ["Use 'head' to cause a channel exit partway through a large send."];
+ timer:sleep(2000), %% Allow incoming eof/close/exit_status ssh messages to be processed
-interrupted_send(suite) ->
- [];
+ Data = <<"I like spaghetti squash">>,
+ case ssh_connection:send(ConnectionRef, ChannelId0, Data, 2000) of
+ {error, closed} -> ok;
+ ok ->
+ ct:fail({expected,{error,closed}});
+ {error, timeout} ->
+ ct:fail({expected,{error,closed}});
+ Else ->
+ ct:fail(Else)
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, _}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+%%--------------------------------------------------------------------
+interrupted_send(doc) ->
+ ["Use a subsystem that echos n char and then sends eof to cause a channel exit partway through a large send."];
interrupted_send(Config) when is_list(Config) ->
- ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false}]),
- {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
- success = ssh_connection:exec(ConnectionRef, ChannelId0,
- "head -c 4000000", infinity),
-
- %% build 10MB binary
- Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
-
- %% expect remote end to send us 4MB back
- <<ExpectedData:4000000/binary, _/binary>> = Data,
-
- %% pre-adjust receive window so the other end doesn't block
- ssh_connection:adjust_window(ConnectionRef, ChannelId0, size(Data)),
-
- test_server:format("sending ~p byte binary~n",[size(Data)]),
-
- case ssh_connection:send(ConnectionRef, ChannelId0, Data, 10000) of
- {error, closed} -> ok;
- ok -> test_server:fail({expected,{error,closed}});
- {error, timeout} -> test_server:fail({expected,{error,closed}});
- SendElse -> test_server:fail(SendElse)
- end,
-
- case ssh_connection:send_eof(ConnectionRef, ChannelId0) of
- {error, closed} -> ok;
- ok -> test_server:fail({expected,{error,closed}});
- EofElse -> test_server:fail(EofElse)
- end,
-
- %% collect echoed data until eof
- case interrupted_send_rx(ConnectionRef, ChannelId0) of
- {ok, ExpectedData} -> ok;
- {ok, Other} ->
- case size(ExpectedData) =:= size(Other) of
- true ->
- test_server:format("received expected number of bytes, but bytes do not match~n",[]);
- false ->
- test_server:format("expected ~p but only received ~p~n",[size(ExpectedData), size(Other)])
- end,
- test_server:fail(receive_data_mismatch);
- RxElse ->
- test_server:fail(RxElse)
- end,
-
- %% receive close messages (eof already consumed)
- receive
- {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end,
- receive
- {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok
- after ?EXEC_TIMEOUT -> test_server:fail()
- end.
-
-interrupted_send_rx(ConnectionRef, ChannelId) ->
- interrupted_send_rx(ConnectionRef, ChannelId, []).
-
-interrupted_send_rx(ConnectionRef, ChannelId, Acc) ->
- receive
- {ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
- %% ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)), % window was pre-adjusted, don't adjust again here
- interrupted_send_rx(ConnectionRef, ChannelId, [Data | Acc]);
- {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
- {ok, iolist_to_binary(lists:reverse(Acc))}
- after ?EXEC_TIMEOUT -> timeout
- end.
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]}]),
+
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+
+ success = ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity),
+
+ %% build 10MB binary
+ Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
+
+ %% expect remote end to send us 4MB back
+ <<ExpectedData:4000000/binary, _/binary>> = Data,
+
+ %% pre-adjust receive window so the other end doesn't block
+ ssh_connection:adjust_window(ConnectionRef, ChannelId, size(ExpectedData) + 1),
+
+ case ssh_connection:send(ConnectionRef, ChannelId, Data, 10000) of
+ {error, closed} ->
+ ok;
+ Msg ->
+ ct:fail({expected,{error,closed}, got, Msg})
+ end,
+ receive_data(ExpectedData, ConnectionRef, ChannelId),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+
+%% Internal funtions ------------------------------------------------------------------
+
+receive_data(ExpectedData, ConnectionRef, ChannelId) ->
+ ExpectedData = collect_data(ConnectionRef, ChannelId).
+
+collect_data(ConnectionRef, ChannelId) ->
+ collect_data(ConnectionRef, ChannelId, []).
+
+collect_data(ConnectionRef, ChannelId, Acc) ->
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
+ collect_data(ConnectionRef, ChannelId, [Data | Acc]);
+ {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
+ iolist_to_binary(lists:reverse(Acc))
+ after 5000 ->
+ timeout
+ end.
diff --git a/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..6ae7ee023d
--- /dev/null
+++ b/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_echo_server.erl b/lib/ssh/test/ssh_echo_server.erl
new file mode 100644
index 0000000000..739aabe6fb
--- /dev/null
+++ b/lib/ssh/test/ssh_echo_server.erl
@@ -0,0 +1,71 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2012. 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%
+%%
+
+%%
+
+%%% Description: Example ssh server
+-module(ssh_echo_server).
+-behaviour(ssh_channel).
+-record(state, {
+ n,
+ id,
+ cm
+ }).
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+init([N]) ->
+ {ok, #state{n = N}}.
+
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
+ {ok, State#state{id = ChannelId,
+ cm = ConnectionManager}}.
+
+handle_ssh_msg({ssh_cm, CM, {data, ChannelId, 0, Data}}, #state{n = N} = State) ->
+ M = N - size(Data),
+ case M > 0 of
+ true ->
+ ssh_connection:send(CM, ChannelId, Data),
+ {ok, State#state{n = M}};
+ false ->
+ <<SendData:N/binary, _/binary>> = Data,
+ ssh_connection:send(CM, ChannelId, SendData),
+ ssh_connection:send_eof(CM, ChannelId),
+ {stop, ChannelId, State}
+ end;
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, 1, Data}}, State) ->
+ error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, _Error, _}},
+ State) ->
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, _Status}}, State) ->
+ {stop, ChannelId, State}.
+
+terminate(_Reason, _State) ->
+ ok.