%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-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%
%%
%%
%%----------------------------------------------------------------------
-module(ssh_test_lib).
%% Note: This directive should only be used in test suites.
-compile(export_all).
-include_lib("public_key/include/public_key.hrl").
-include("test_server.hrl").
-include("test_server_line.hrl").
-define(TIMEOUT, 50000).
connect(Options) ->
connect(hostname(), inet_port(), Options).
connect(Port, Options) when is_integer(Port) ->
connect(hostname(), Port, Options);
connect(any, Options) ->
connect(hostname(), inet_port(), Options);
connect(Host, Options) ->
connect(Host, inet_port(), Options).
connect(any, Port, Options) ->
connect(hostname(), Port, Options);
connect(Host, Port, Options) ->
case ssh:connect(Host, Port, Options) of
{ok, ConnectionRef} ->
ConnectionRef;
Error ->
Error
end.
daemon(Options) ->
daemon(any, inet_port(), Options).
daemon(Port, Options) when is_integer(Port) ->
daemon(any, Port, Options);
daemon(Host, Options) ->
daemon(Host, inet_port(), Options).
daemon(Host, Port, Options) ->
case ssh:daemon(Host, Port, Options) of
{ok, Pid} when Host == any ->
{Pid, hostname(), Port};
{ok, Pid} ->
{Pid, Host, Port};
Error ->
Error
end.
start_shell(Port, IOServer, UserDir) ->
spawn_link(?MODULE, init_shell, [Port, IOServer, [{user_dir, UserDir}]]).
init_shell(Port, IOServer, UserDir) ->
Host = hostname(),
Options = [{user_interaction, false}, {silently_accept_hosts,
true}] ++ UserDir,
group_leader(IOServer, self()),
loop_shell(Host, Port, Options).
loop_shell(Host, Port, Options) ->
ssh:shell(Host, Port, Options).
start_io_server() ->
spawn_link(?MODULE, init_io_server, [self()]).
init_io_server(TestCase) ->
process_flag(trap_exit, true),
loop_io_server(TestCase, []).
loop_io_server(TestCase, Buff0) ->
receive
{input, TestCase, Line} ->
%io:format("~p~n",[{input, TestCase, Line}]),
loop_io_server(TestCase, Buff0 ++ [Line]);
{io_request, From, ReplyAs, Request} ->
%io:format("request -> ~p~n",[Request]),
{ok, Reply, Buff} = io_request(Request, TestCase, From,
ReplyAs, Buff0),
%io:format("reply -> ~p~n",[Reply]),
io_reply(From, ReplyAs, Reply),
loop_io_server(TestCase, Buff);
{'EXIT',_, _} ->
erlang:display('EXIT'),
ok
end.
io_request({put_chars, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, Chars),
{ok, ok, Buff};
io_request({put_chars, Enc, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, unicode:characters_to_binary(Chars,Enc,latin1)),
{ok, ok, Buff};
io_request({get_line, _} = Request, _, From, ReplyAs, [] = Buff) ->
erlang:send_after(1000, self(), {io_request, From, ReplyAs, Request}),
{ok, [], Buff};
io_request({get_line, _Enc, _Prompt} = Request, _, From, ReplyAs, [] = Buff) ->
erlang:send_after(1000, self(), {io_request, From, ReplyAs, Request}),
{ok, [], Buff};
io_request({get_line, _Enc,_}, _, _, _, [Line | Buff]) ->
{ok, Line, Buff}.
io_reply(_, _, []) ->
ok;
io_reply(From, ReplyAs, Reply) ->
From ! {io_reply, ReplyAs, Reply}.
reply(_, []) ->
ok;
reply(TestCase, Result) ->
TestCase ! Result.
receive_exec_result(Msg) ->
test_server:format("Expect data! ~p", [Msg]),
receive
{ssh_cm,_,{data,_,1, Data}} ->
test_server:format("StdErr: ~p~n", [Data]),
receive_exec_result(Msg);
Msg ->
test_server:format("1: Collected data ~p", [Msg]),
expected;
Other ->
test_server:format("Other ~p", [Other]),
{unexpected_msg, Other}
end.
receive_exec_end(ConnectionRef, ChannelId) ->
Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
ExitStatus = {ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}},
Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}},
case receive_exec_result(ExitStatus) of
{unexpected_msg, Eof} -> %% Open ssh seems to not allways send these messages
%% in the same order!
test_server:format("2: Collected data ~p", [Eof]),
case receive_exec_result(ExitStatus) of
expected ->
expected = receive_exec_result(Closed);
{unexpected_msg, Closed} ->
test_server:format("3: Collected data ~p", [Closed])
end;
expected ->
test_server:format("4: Collected data ~p", [ExitStatus]),
expected = receive_exec_result(Eof),
expected = receive_exec_result(Closed);
Other ->
test_server:fail({unexpected_msg, Other})
end.
receive_exec_result(Data, ConnectionRef, ChannelId) ->
Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}},
expected = receive_exec_result(Data),
expected = receive_exec_result(Eof),
expected = receive_exec_result(Closed).
inet_port()->
{ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
{ok, Port} = inet:port(Socket),
gen_tcp:close(Socket),
Port.
%% copy private keys to given dir from ~/.ssh
get_id_keys(DstDir) ->
SrcDir = filename:join(os:getenv("HOME"), ".ssh"),
RsaOk = copyfile(SrcDir, DstDir, "id_rsa"),
DsaOk = copyfile(SrcDir, DstDir, "id_dsa"),
case {RsaOk, DsaOk} of
{{ok, _}, {ok, _}} -> {ok, both};
{{ok, _}, _} -> {ok, rsa};
{_, {ok, _}} -> {ok, dsa};
{Error, _} -> Error
end.
remove_id_keys(Dir) ->
file:delete(filename:join(Dir, "id_rsa")),
file:delete(filename:join(Dir, "id_dsa")).
copyfile(SrcDir, DstDir, FileName) ->
Dest = filename:join(DstDir, FileName),
Result = file:copy(filename:join(SrcDir, FileName), Dest),
{ok, Pem} = file:read_file(Dest),
case public_key:pem_decode(Pem) of
[{_,_, not_encrypted}] ->
Result;
_ ->
{error, "Has pass phrase can not be used by automated test case"}
end.
failfun(_User, {authmethod,none}) ->
ok;
failfun(User, Reason) ->
error_logger:format("~p failed XXX to login: ~p~n", [User, Reason]).
hostname() ->
{ok,Host} = inet:gethostname(),
Host.
known_hosts(BR) ->
KnownHosts = ssh_file:file_name(user, "known_hosts", []),
B = KnownHosts ++ "xxx",
case BR of
backup ->
file:rename(KnownHosts, B);
restore ->
file:delete(KnownHosts),
file:rename(B, KnownHosts)
end.
setup_dsa(DataDir, UserDir) ->
ssh_test_lib:copyfile(DataDir, UserDir, "ssh_host_dsa_key"),
ssh_test_lib:copyfile(DataDir, UserDir, "ssh_host_dsa_key.pub"),
{ok, Pem} = file:read_file(filename:join(UserDir, "ssh_host_dsa_key")),
DSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))),
PKey = DSA#'DSAPrivateKey'.y,
P = DSA#'DSAPrivateKey'.p,
Q = DSA#'DSAPrivateKey'.q,
G = DSA#'DSAPrivateKey'.g,
Dss = #'Dss-Parms'{p=P, q=Q, g=G},
{ok, Hostname} = inet:gethostname(),
{ok, {A, B, C, D}} = inet:getaddr(Hostname, inet),
IP = lists:concat([A, ".", B, ".", C, ".", D]),
HostNames = [{hostnames,[IP, IP]}],
KnownHosts = [{{PKey, Dss}, HostNames}],
KnownHostsEnc = public_key:ssh_encode(KnownHosts, known_hosts),
KHFile = filename:join(UserDir, "known_hosts"),
file:write_file(KHFile, KnownHostsEnc).
clean_dsa(UserDir) ->
file:delete(filename:join(UserDir, "ssh_host_dsa_key")),
file:delete(filename:join(UserDir, "ssh_host_dsa_key.pub")),
file:delete(filename:join(UserDir, "known_hosts")).