diff options
Diffstat (limited to 'lib/ssh/test')
-rw-r--r-- | lib/ssh/test/Makefile | 3 | ||||
-rw-r--r-- | lib/ssh/test/ssh_connection_SUITE.erl | 504 | ||||
-rw-r--r-- | lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key | 15 | ||||
-rw-r--r-- | lib/ssh/test/ssh_echo_server.erl | 71 |
4 files changed, 303 insertions, 290 deletions
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. |