1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22-module(ssh_to_openssh_SUITE).
23
24-include_lib("common_test/include/ct.hrl").
25-include("ssh_test_lib.hrl").
26
27%% Note: This directive should only be used in test suites.
28-compile(export_all).
29
30-define(SSH_DEFAULT_PORT, 22).
31-define(REKEY_DATA_TMO, 65000).
32
33%%--------------------------------------------------------------------
34%% Common Test interface functions -----------------------------------
35%%--------------------------------------------------------------------
36
37suite() ->
38    [{timetrap,{seconds,60}}].
39
40all() ->
41    case os:find_executable("ssh") of
42	false ->
43	    {skip, "openSSH not installed on host"};
44	_ ->
45	    [{group, erlang_client},
46	     {group, erlang_server}
47	     ]
48    end.
49
50groups() ->
51    [{erlang_client, [], [erlang_shell_client_openssh_server
52			 ]},
53     {erlang_server, [], [erlang_server_openssh_client_renegotiate
54			 ]}
55    ].
56
57init_per_suite(Config) ->
58    ?CHECK_CRYPTO(
59       case gen_tcp:connect("localhost", 22, []) of
60	   {error,econnrefused} ->
61	       {skip,"No openssh deamon (econnrefused)"};
62	   _ ->
63	       ssh_test_lib:openssh_sanity_check(Config)
64       end
65      ).
66
67end_per_suite(_Config) ->
68    ok.
69
70init_per_group(erlang_server, Config) ->
71    DataDir = proplists:get_value(data_dir, Config),
72    UserDir = proplists:get_value(priv_dir, Config),
73    ssh_test_lib:setup_dsa_known_host(DataDir, UserDir),
74    ssh_test_lib:setup_rsa_known_host(DataDir, UserDir),
75    Config;
76init_per_group(erlang_client, Config) ->
77    CommonAlgs = ssh_test_lib:algo_intersection(
78		   ssh:default_algorithms(),
79		   ssh_test_lib:default_algorithms(sshd)),
80    [{common_algs,CommonAlgs} | Config];
81init_per_group(_, Config) ->
82    Config.
83
84end_per_group(erlang_server, Config) ->
85    UserDir = proplists:get_value(priv_dir, Config),
86    ssh_test_lib:clean_dsa(UserDir),
87    ssh_test_lib:clean_rsa(UserDir),
88    Config;
89end_per_group(_, Config) ->
90    Config.
91
92
93init_per_testcase(erlang_server_openssh_client_renegotiate, Config) ->
94    case os:type() of
95	{unix,_} -> ssh:start(), Config;
96	Type -> {skip, io_lib:format("Unsupported test on ~p",[Type])}
97    end;
98init_per_testcase(_TestCase, Config) ->
99    ssh:start(),
100    Config.
101
102end_per_testcase(_TestCase, _Config) ->
103    ssh:stop(),
104    ok.
105
106%%--------------------------------------------------------------------
107%% Test Cases --------------------------------------------------------
108%%--------------------------------------------------------------------
109
110erlang_shell_client_openssh_server() ->
111    [{doc, "Test that ssh:shell/2 works"}].
112
113erlang_shell_client_openssh_server(Config) when is_list(Config) ->
114    process_flag(trap_exit, true),
115    IO = ssh_test_lib:start_io_server(),
116    Shell = ssh_test_lib:start_shell(?SSH_DEFAULT_PORT, IO),
117    IO ! {input, self(), "echo Hej\n"},
118    receive_data("Hej", undefined),
119    IO ! {input, self(), "exit\n"},
120    receive_logout(),
121    receive_normal_exit(Shell).
122
123%%--------------------------------------------------------------------
124%% Test that the Erlang/OTP server can renegotiate with openSSH
125erlang_server_openssh_client_renegotiate(Config) ->
126    _PubKeyAlg = ssh_rsa,
127    SystemDir = proplists:get_value(data_dir, Config),
128    PrivDir = proplists:get_value(priv_dir, Config),
129    KnownHosts = filename:join(PrivDir, "known_hosts"),
130
131    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
132                                             {failfun, fun ssh_test_lib:failfun/2}]),
133    ct:sleep(500),
134
135    RenegLimitK = 3,
136    DataFile = filename:join(PrivDir, "renegotiate_openssh_client.data"),
137    Data =  lists:duplicate(trunc(1.1*RenegLimitK*1024), $a),
138    ok = file:write_file(DataFile, Data),
139
140    Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
141                                     [" -o UserKnownHostsFile=", KnownHosts,
142                                      " -o StrictHostKeyChecking=no",
143                                      " -o RekeyLimit=",integer_to_list(RenegLimitK),"K"]),
144
145
146    OpenSsh = ssh_test_lib:open_port({spawn, Cmd++" < "++DataFile}),
147
148    Expect = fun({data,R}) ->
149		     try
150			 NonAlphaChars = [C || C<-lists:seq(1,255),
151					       not lists:member(C,lists:seq($a,$z)),
152					       not lists:member(C,lists:seq($A,$Z))
153					 ],
154			 Lines = string:tokens(binary_to_list(R), NonAlphaChars),
155			 lists:any(fun(L) -> length(L)>1 andalso lists:prefix(L, Data) end,
156				   Lines)
157		     catch
158			 _:_ -> false
159		     end;
160
161		({exit_status,E}) when E=/=0 ->
162		     ct:log("exit_status ~p",[E]),
163		     throw({skip,"exit status"});
164
165		(_) ->
166		     false
167	     end,
168
169    try
170	ssh_test_lib:rcv_expected(Expect, OpenSsh, ?TIMEOUT)
171    of
172	_ ->
173	    %% Unfortunately we can't check that there has been a renegotiation, just trust OpenSSH.
174	    ssh:stop_daemon(Pid)
175    catch
176	throw:{skip,R} -> {skip,R}
177    end.
178
179%%--------------------------------------------------------------------
180%%% Internal functions -----------------------------------------------
181%%--------------------------------------------------------------------
182receive_data(Data, Conn) ->
183    receive
184	Info when is_binary(Info) ->
185	    Lines = string:tokens(binary_to_list(Info), "\r\n "),
186	    case lists:member(Data, Lines) of
187		true ->
188		    ct:log("Expected result ~p found in lines: ~p~n", [Data,Lines]),
189		    ok;
190		false ->
191		    ct:log("Extra info: ~p~n", [Info]),
192		    receive_data(Data, Conn)
193	    end;
194	Other ->
195	    ct:log("Unexpected: ~p",[Other]),
196	    receive_data(Data, Conn)
197    after
198	30000 ->
199             {State, _} = case Conn of
200                              undefined -> {'??','??'};
201                              _ -> sys:get_state(Conn)
202                          end,
203            ct:log("timeout ~p:~p~nExpect ~p~nState = ~p",[?MODULE,?LINE,Data,State]),
204            ct:fail("timeout ~p:~p",[?MODULE,?LINE])
205    end.
206
207receive_logout() ->
208    receive
209	<<"logout">> ->
210	    extra_logout(),
211	    receive
212		<<"Connection closed">> ->
213		    ok
214	    after
215		30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
216	    end;
217	Info ->
218	    ct:log("Extra info when logging out: ~p~n", [Info]),
219	    receive_logout()
220    after
221	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
222    end.
223
224receive_normal_exit(Shell) ->
225    receive
226	{'EXIT', Shell, normal} ->
227	    ok;
228	<<"\r\n">> ->
229	    receive_normal_exit(Shell);
230	Other ->
231	    ct:fail({unexpected_msg, Other})
232    after
233	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
234    end.
235
236extra_logout() ->
237    receive
238	<<"logout">> ->
239	    ok
240    after 500 ->
241	    ok
242    end.
243
244%%--------------------------------------------------------------------
245%% Check if we have a "newer" ssh client that supports these test cases
246check_ssh_client_support(Config) ->
247    case ssh_test_lib:ssh_client_supports_Q() of
248	true ->
249	    ssh:start(),
250	    Config;
251	_ ->
252	    {skip, "test case not supported by ssh client"}
253    end.
254
255comment(AtomList) ->
256    ct:comment(
257      string:join(lists:map(fun erlang:atom_to_list/1, AtomList),
258		", ")).
259