1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2004-2020. 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%%----------------------------------------------------------------------
23-module(ssh_test_lib).
24
25-export([
26connect/2,
27connect/3,
28daemon/1,
29daemon/2,
30daemon/3,
31daemon_port/1,
32daemon_port/2,
33gen_tcp_connect/3,
34open_sshc/3,
35open_sshc/4,
36open_sshc_cmd/3,
37open_sshc_cmd/4,
38std_daemon/2,
39std_daemon1/2,
40std_connect/4,
41std_simple_sftp/3,
42std_simple_sftp/4,
43std_simple_exec/3,
44std_simple_exec/4,
45start_shell/2,
46start_shell/3,
47start_io_server/0,
48init_io_server/1,
49loop_io_server/2,
50io_request/5,
51io_reply/3,
52reply/2,
53rcv_expected/3,
54rcv_lingering/1,
55receive_exec_result/1,
56receive_exec_result_or_fail/1,
57receive_exec_end/2,
58receive_exec_end/3,
59receive_exec_result/3,
60failfun/2,
61hostname/0,
62del_dirs/1,
63del_dir_contents/1,
64do_del_files/2,
65openssh_sanity_check/1,
66default_algorithms/1,
67default_algorithms/3,
68default_algorithms/2,
69run_fake_ssh/1,
70extract_algos/1,
71get_atoms/1,
72intersection/2,
73intersect/2,
74intersect_bi_dir/1,
75some_empty/1,
76sort_spec/1,
77sshc/1,
78ssh_type/0,
79ssh_type1/0,
80installed_ssh_version/1,
81algo_intersection/2,
82to_atoms/1,
83ssh_supports/2,
84has_inet6_address/0,
85open_port/1,
86open_port/2,
87sleep_millisec/1,
88sleep_microsec/1,
89busy_wait/2,
90get_kex_init/1,
91get_kex_init/3,
92expected_state/1,
93random_chars/1,
94create_random_dir/1,
95match_ip/2,
96match_ip0/2,
97match_ip1/2,
98mangle_connect_address/1,
99mangle_connect_address/2,
100loopback/1,
101mangle_connect_address1/2,
102ntoa/1,
103try_enable_fips_mode/0,
104is_cryptolib_fips_capable/0,
105report/2,
106lc_name_in/1,
107ptty_supported/0,
108has_WSL/0,
109winpath_to_linuxpath/1,
110copy_recursive/2,
111mk_dir_path/1,
112setup_all_user_host_keys/1,
113setup_all_user_host_keys/2,
114setup_all_user_host_keys/3,
115setup_all_host_keys/1,
116setup_all_host_keys/2,
117setup_all_user_keys/2,
118setup_user_key/3,
119setup_host_key_create_dir/3,
120setup_host_key/3,
121setup_known_host/3,
122get_addr_str/0,
123file_base_name/2
124        ]).
125
126-include_lib("common_test/include/ct.hrl").
127-include("ssh_transport.hrl").
128-include_lib("kernel/include/file.hrl").
129-include("ssh_test_lib.hrl").
130
131%%%----------------------------------------------------------------
132connect(Port, Options) when is_integer(Port) ->
133    connect(hostname(), Port, Options).
134
135connect(any, Port, Options) ->
136    connect(hostname(), Port, Options);
137connect(Host, Port, Options) ->
138    R = ssh:connect(Host, Port, Options),
139    ct:log("~p:~p ssh:connect(~p, ~p, ~p)~n -> ~p",[?MODULE,?LINE,Host, Port, Options, R]),
140    {ok, ConnectionRef} = R,
141    ConnectionRef.
142
143%%%----------------------------------------------------------------
144daemon(Options) ->
145    daemon(any, 0, Options).
146
147daemon(Port, Options) when is_integer(Port) ->
148    daemon(any, Port, Options);
149daemon(Host, Options) ->
150    daemon(Host, 0, Options).
151
152
153daemon(Host, Port, Options) ->
154    ct:log("~p:~p Calling ssh:daemon(~p, ~p, ~p)",[?MODULE,?LINE,Host,Port,Options]),
155    case ssh:daemon(Host, Port, Options) of
156	{ok, Pid} ->
157            R = ssh:daemon_info(Pid),
158            ct:log("~p:~p ssh:daemon_info(~p) ->~n ~p",[?MODULE,?LINE,Pid,R]),
159            {ok,L} = R,
160            ListenPort = proplists:get_value(port, L),
161            ListenIP = proplists:get_value(ip, L),
162	    {Pid, ListenIP, ListenPort};
163	Error ->
164	    ct:log("ssh:daemon error ~p",[Error]),
165	    Error
166    end.
167
168%%%----------------------------------------------------------------
169daemon_port(Pid) -> daemon_port(0, Pid).
170
171
172daemon_port(0, Pid) -> {ok,Dinf} = ssh:daemon_info(Pid),
173		       proplists:get_value(port, Dinf);
174daemon_port(Port, _) -> Port.
175
176%%%----------------------------------------------------------------
177gen_tcp_connect(Host0, Port, Options) ->
178    Host = ssh_test_lib:ntoa(ssh_test_lib:mangle_connect_address(Host0)),
179    ct:log("~p:~p gen_tcp:connect(~p, ~p, ~p)~nHost0 = ~p",
180           [?MODULE,?LINE, Host, Port, Options, Host0]),
181    Result = gen_tcp:connect(Host, Port, Options),
182    ct:log("~p:~p Result = ~p", [?MODULE,?LINE, Result]),
183    Result.
184
185%%%----------------------------------------------------------------
186open_sshc(Host0, Port, OptStr) ->
187    open_sshc(Host0, Port, OptStr, "").
188
189open_sshc(Host0, Port, OptStr, ExecStr) ->
190    Cmd = open_sshc_cmd(Host0, Port, OptStr, ExecStr),
191    Result = os:cmd(Cmd),
192    ct:log("~p:~p Result = ~p", [?MODULE,?LINE, Result]),
193    Result.
194
195
196open_sshc_cmd(Host, Port, OptStr) ->
197    open_sshc_cmd(Host, Port, OptStr, "").
198
199open_sshc_cmd(Host0, Port, OptStr, ExecStr) ->
200    Host = ssh_test_lib:ntoa(ssh_test_lib:mangle_connect_address(Host0)),
201    Cmd = lists:flatten(["ssh -p ", integer_to_list(Port),
202                         " ", OptStr,
203                         " ", Host,
204                         " ", ExecStr]),
205    ct:log("~p:~p OpenSSH Cmd = ~p", [?MODULE,?LINE, Cmd]),
206    Cmd.
207
208%%%----------------------------------------------------------------
209std_daemon(Config, ExtraOpts) ->
210    PrivDir = proplists:get_value(priv_dir, Config),
211    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
212    file:make_dir(UserDir),
213    std_daemon1(Config,
214		ExtraOpts ++
215		    [{user_dir, UserDir},
216		     {user_passwords, [{"usr1","pwd1"}]}]).
217
218std_daemon1(Config, ExtraOpts) ->
219    SystemDir = proplists:get_value(data_dir, Config),
220    {_Server, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
221						   {failfun, fun ssh_test_lib:failfun/2}
222						   | ExtraOpts]).
223
224%%%----------------------------------------------------------------
225std_connect(Config, Host, Port, ExtraOpts) ->
226    UserDir = proplists:get_value(priv_dir, Config),
227    _ConnectionRef =
228	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
229					  {user_dir, UserDir},
230					  {user, "usr1"},
231					  {password, "pwd1"},
232					  {user_interaction, false}
233					  | ExtraOpts]).
234
235%%%----------------------------------------------------------------
236std_simple_sftp(Host, Port, Config) ->
237    std_simple_sftp(Host, Port, Config, []).
238
239std_simple_sftp(Host, Port, Config, Opts) ->
240    UserDir = proplists:get_value(priv_dir, Config),
241    DataFile = filename:join(UserDir, "test.data"),
242    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts),
243    {ok, ChannelRef} = ssh_sftp:start_channel(ConnectionRef),
244    Data = crypto:strong_rand_bytes(proplists:get_value(std_simple_sftp_size,Config,10)),
245    ok = ssh_sftp:write_file(ChannelRef, DataFile, Data),
246    {ok,ReadData} = file:read_file(DataFile),
247    ok = ssh:close(ConnectionRef),
248    Data == ReadData.
249
250%%%----------------------------------------------------------------
251std_simple_exec(Host, Port, Config) ->
252    std_simple_exec(Host, Port, Config, []).
253
254std_simple_exec(Host, Port, Config, Opts) ->
255    ct:log("~p:~p std_simple_exec",[?MODULE,?LINE]),
256    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts),
257    ct:log("~p:~p connected! ~p",[?MODULE,?LINE,ConnectionRef]),
258    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
259    ct:log("~p:~p session_channel ok ~p",[?MODULE,?LINE,ChannelId]),
260    ExecResult = ssh_connection:exec(ConnectionRef, ChannelId, "23+21-2.", infinity),
261    ct:log("~p:~p exec ~p",[?MODULE,?LINE,ExecResult]),
262    case ExecResult of
263	success ->
264	    Expected = {ssh_cm, ConnectionRef, {data,ChannelId,0,<<"42">>}},
265	    case receive_exec_result(Expected) of
266		expected ->
267		    ok;
268		Other ->
269		    ct:fail(Other)
270	    end,
271	    receive_exec_end(ConnectionRef, ChannelId),
272	    ssh:close(ConnectionRef);
273	_ ->
274	    ct:fail(ExecResult)
275    end.
276
277%%%----------------------------------------------------------------
278start_shell(Port, IOServer) ->
279    start_shell(Port, IOServer, []).
280
281start_shell(Port, IOServer, ExtraOptions) ->
282    spawn_link(
283      fun() ->
284              ct:log("~p:~p:~p ssh_test_lib:start_shell(~p, ~p, ~p)",
285                     [?MODULE,?LINE,self(), Port, IOServer, ExtraOptions]),
286	      Options = [{user_interaction, false},
287			 {silently_accept_hosts,true} | ExtraOptions],
288              try
289                  group_leader(IOServer, self()),
290                  case Port of
291                      22 ->
292                          Host = hostname(),
293                          ct:log("Port==22 Call ssh:shell(~p, ~p)",
294                                 [Host, Options]),
295                          ssh:shell(Host, Options);
296                      _ when is_integer(Port) ->
297                          Host = hostname(),
298                          ct:log("is_integer(Port) Call ssh:shell(~p, ~p, ~p)",
299                                 [Host, Port, Options]),
300                          ssh:shell(Host, Port, Options);
301                      Socket when is_port(Socket) ->
302                          receive
303                              start -> ok
304                          end,
305                          ct:log("is_port(Socket) Call ssh:shell(~p, ~p)",
306                                 [Socket, Options]),
307                          ssh:shell(Socket, Options);
308                      ConnRef when is_pid(ConnRef) ->
309                          ct:log("is_pid(ConnRef) Call ssh:shell(~p)",
310                                 [ConnRef]),
311                          ssh:shell(ConnRef) % Options were given in ssh:connect
312                  end
313              of
314                  R ->
315                      ct:log("~p:~p ssh_test_lib:start_shell(~p, ~p, ~p) -> ~p",
316                             [?MODULE,?LINE,Port, IOServer, ExtraOptions, R])
317              catch
318                  C:E:S ->
319                      ct:log("Exception ~p:~p~n~p", [C,E,S]),
320                      ct:fail("Exception",[])
321              end
322      end).
323
324
325%%%----------------------------------------------------------------
326start_io_server() ->
327    spawn_link(?MODULE, init_io_server, [self()]).
328
329init_io_server(TestCase) ->
330    process_flag(trap_exit, true),
331    loop_io_server(TestCase, []).
332
333loop_io_server(TestCase, Buff0) ->
334     receive
335	 {input, TestCase, Line} = _INP ->
336             %%ct:log("io_server ~p:~p ~p got ~p",[?MODULE,?LINE,self(),_INP]),
337	     loop_io_server(TestCase, Buff0 ++ [Line]);
338	 {io_request, From, ReplyAs, Request} = _REQ->
339             %%ct:log("io_server ~p:~p ~p got ~p",[?MODULE,?LINE,self(),_REQ]),
340	     {ok, Reply, Buff} = io_request(Request, TestCase, From,
341					    ReplyAs, Buff0),
342             %%ct:log("io_server ~p:~p ~p going to reply ~p",[?MODULE,?LINE,self(),Reply]),
343	     io_reply(From, ReplyAs, Reply),
344	     loop_io_server(TestCase, Buff);
345	 {'EXIT',_, _} = _Exit ->
346	     ct:log("ssh_test_lib:loop_io_server/2 got ~p",[_Exit]),
347	     ok
348    after
349	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
350    end.
351
352io_request(getopts,_TestCase, _, _, Buff) ->
353    {ok, [], Buff};
354io_request({get_geometry,columns},_TestCase, _, _, Buff) ->
355    {ok, 80, Buff};
356io_request({get_geometry,rows},_TestCase, _, _, Buff) ->
357    {ok, 24, Buff};
358io_request({put_chars, Chars}, TestCase, _, _, Buff) ->
359    reply(TestCase, Chars),
360    {ok, ok, Buff};
361io_request({put_chars, unicode, Chars}, TestCase, _, _, Buff) when is_binary(Chars) ->
362    reply(TestCase, Chars),
363    {ok, ok, Buff};
364io_request({put_chars, unicode, io_lib, format, [Fmt,Args]}, TestCase, _, _, Buff) ->
365    reply(TestCase,  unicode:characters_to_binary(io_lib:format(Fmt,Args))),
366    {ok, ok, Buff};
367io_request({put_chars, Enc, Chars}, TestCase, _, _, Buff) ->
368    reply(TestCase, unicode:characters_to_binary(Chars,Enc,latin1)),
369    {ok, ok, Buff};
370
371io_request({get_line, _} = Request, _, From, ReplyAs, [] = Buff) ->
372    erlang:send_after(1000, self(), {io_request, From, ReplyAs, Request}),
373    {ok, [], Buff};
374io_request({get_line, _Enc, _Prompt} = Request, _, From, ReplyAs, [] = Buff) ->
375    erlang:send_after(1000, self(), {io_request, From, ReplyAs, Request}),
376    {ok, [], Buff};
377
378io_request({get_line, _Enc,_}, _, _, _, [Line | Buff]) ->
379    {ok, Line, Buff}.
380
381
382io_reply(_, _, []) ->
383    ok;
384io_reply(From, ReplyAs, Reply) ->
385%%ct:log("io_reply ~p sending ~p ! ~p",[self(),From, {io_reply, ReplyAs, Reply}]),
386    From ! {io_reply, ReplyAs, Reply}.
387
388reply(_, []) ->
389    ok;
390reply(TestCase, Result) ->
391%%ct:log("reply ~p sending ~p ! ~p",[self(), TestCase, Result]),
392    TestCase ! Result.
393
394%%%----------------------------------------------------------------
395rcv_expected(Expect, SshPort, Timeout) ->
396    receive
397	{SshPort, Recvd} when is_function(Expect) ->
398	    case Expect(Recvd) of
399		true ->
400		    ct:log("Got expected ~p from ~p",[Recvd,SshPort]),
401		    catch port_close(SshPort),
402		    rcv_lingering(50);
403		false ->
404		    ct:log("Got UNEXPECTED ~p~n",[Recvd]),
405		    rcv_expected(Expect, SshPort, Timeout)
406	    end;
407	{SshPort, Expect} ->
408	    ct:log("Got expected ~p from ~p",[Expect,SshPort]),
409	    catch port_close(SshPort),
410	    rcv_lingering(50);
411	Other ->
412	    ct:log("Got UNEXPECTED ~p~nExpect ~p",[Other, {SshPort,Expect}]),
413	    rcv_expected(Expect, SshPort, Timeout)
414
415    after Timeout ->
416	    catch port_close(SshPort),
417	    ct:fail("Did not receive answer")
418    end.
419
420rcv_lingering(Timeout) ->
421    receive
422	Msg ->
423	    ct:log("Got LINGERING ~p",[Msg]),
424	    rcv_lingering(Timeout)
425
426    after Timeout ->
427	    ct:log("No more lingering messages",[]),
428	    ok
429    end.
430
431
432receive_exec_result([]) ->
433    expected;
434receive_exec_result(Msgs) when is_list(Msgs) ->
435    ct:log("~p:~p Expect data! ~p", [?MODULE,?FUNCTION_NAME,Msgs]),
436    receive
437        Msg ->
438            case lists:member(Msg, Msgs)
439                orelse lists:member({optional,Msg}, Msgs)
440            of
441                true ->
442                    ct:log("~p:~p Collected data ~p", [?MODULE,?FUNCTION_NAME,Msg]),
443                    receive_exec_result(Msgs--[Msg,{optional,Msg}]);
444                false ->
445                    case Msg of
446                        {ssh_cm,_,{data,_,1, Data}} ->
447                            ct:log("~p:~p unexpected StdErr: ~p~n~p~n", [?MODULE,?FUNCTION_NAME,Data,Msg]),
448                            receive_exec_result(Msgs);
449                        Other ->
450                            ct:log("~p:~p unexpected Other ~p", [?MODULE,?FUNCTION_NAME,Other]),
451                            {unexpected_msg, Other}
452                    end
453            end
454    after
455	30000 ->
456            case lists:all(fun(M) ->
457                                   is_tuple(M) andalso (element(1,M) == optional)
458                           end, Msgs)
459            of
460                false ->
461                    ct:fail("timeout ~p:~p",[?MODULE,?FUNCTION_NAME]);
462                true ->
463                    ct:log("~p:~p Only optional messages expected!~n ~p", [?MODULE,?FUNCTION_NAME,Msgs]),
464                    expected
465            end
466    end;
467receive_exec_result(Msg) ->
468    receive_exec_result([Msg]).
469
470
471receive_exec_result_or_fail(Msg) ->
472    case receive_exec_result(Msg) of
473        expected -> expected;
474        Other -> ct:fail(Other)
475    end.
476
477receive_exec_end(ConnectionRef, ChannelId) ->
478    receive_exec_end(ConnectionRef, ChannelId, 0).
479
480receive_exec_end(ConnectionRef, ChannelId, ExitStatus) ->
481    receive_exec_result(
482      [{ssh_cm, ConnectionRef, {eof, ChannelId}},
483       {optional, {ssh_cm, ConnectionRef, {exit_status, ChannelId, ExitStatus}}},
484       {ssh_cm, ConnectionRef, {closed, ChannelId}}
485      ]).
486
487receive_exec_result(Data, ConnectionRef, ChannelId) ->
488    Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
489    Closed =  {ssh_cm, ConnectionRef,{closed, ChannelId}},
490    expected = receive_exec_result(Data),
491    expected = receive_exec_result(Eof),
492    expected = receive_exec_result(Closed).
493
494
495failfun(_User, {authmethod,none}) ->
496    ok;
497failfun(User, Reason) ->
498    error_logger:format("~p failed XXX to login: ~p~n", [User, Reason]).
499
500hostname() ->
501    {ok,Host} = inet:gethostname(),
502    Host.
503
504del_dirs(Dir) ->
505    del_dir_contents(Dir),
506    file:del_dir(Dir),
507    ok.
508
509
510del_dir_contents(Dir) ->
511    case file:list_dir(Dir) of
512        {ok, Files} ->
513            do_del_files(Dir, Files);
514        _ ->
515            ok
516    end.
517
518do_del_files(Dir, Files) ->
519    lists:foreach(fun(File) ->
520                          FullPath = filename:join(Dir,File),
521                          case filelib:is_dir(FullPath) of
522                              true ->
523                                  del_dirs(FullPath);
524                              false ->
525                                  file:delete(FullPath)
526                          end
527                  end, Files).
528
529
530openssh_sanity_check(Config) ->
531    ssh:start(),
532    case ssh:connect("localhost", 22, [{password,""},
533                                       {save_accepted_host, false},
534                                       {silently_accept_hosts, true} ]) of
535	{ok, Pid} ->
536	    ssh:close(Pid),
537	    ssh:stop(),
538	    Config;
539	Err ->
540	    Str = lists:append(io_lib:format("~p", [Err])),
541	    ssh:stop(),
542	    {skip, Str}
543    end.
544
545%%%--------------------------------------------------------------------
546%%% Probe a server or a client about algorithm support
547
548default_algorithms(sshd) ->
549    default_algorithms(sshd, "localhost", 22);
550
551default_algorithms(sshc) ->
552    default_algorithms(sshc, []).
553
554default_algorithms(sshd, Host, Port) ->
555    try run_fake_ssh(
556	  ssh_trpt_test_lib:exec(
557	    [{connect,Host,Port, [{silently_accept_hosts, true},
558				  {user_interaction, false}]}]))
559    catch
560	_C:_E ->
561	    ct:log("***~p:~p: ~p:~p",[?MODULE,?LINE,_C,_E]),
562	    []
563    end.
564
565default_algorithms(sshc, DaemonOptions) ->
566    Parent = self(),
567    %% Start a process handling one connection on the server side:
568    Srvr =
569	spawn_link(
570	  fun() ->
571		  Parent !
572		      {result, self(),
573		       try
574			   {ok,InitialState} = ssh_trpt_test_lib:exec(listen),
575			   Parent ! {hostport,self(),ssh_trpt_test_lib:server_host_port(InitialState)},
576			   run_fake_ssh(
577			     ssh_trpt_test_lib:exec([{accept, DaemonOptions}],
578						    InitialState))
579		       catch
580			   _C:_E ->
581			       ct:log("***~p:~p: ~p:~p",[?MODULE,?LINE,_C,_E]),
582			       []
583		       end}
584	  end),
585
586    receive
587	{hostport,Srvr,{_Host,Port}} ->
588	    spawn(fun()-> os:cmd(lists:concat(["ssh -o \"StrictHostKeyChecking no\" -p ",Port," localhost"])) end)
589    after ?TIMEOUT ->
590	    ct:fail("No server respons (timeout) 1")
591    end,
592
593    receive
594	{result,Srvr,L} ->
595	    L
596    after ?TIMEOUT ->
597	    ct:fail("No server respons (timeout) 2")
598    end.
599
600run_fake_ssh({ok,InitialState}) ->
601    KexInitPattern =
602	#ssh_msg_kexinit{
603	   kex_algorithms = '$kex_algorithms',
604	   server_host_key_algorithms = '$server_host_key_algorithms',
605	   encryption_algorithms_client_to_server = '$encryption_algorithms_client_to_server',
606	   encryption_algorithms_server_to_client = '$encryption_algorithms_server_to_client',
607	   mac_algorithms_client_to_server = '$mac_algorithms_client_to_server',
608	   mac_algorithms_server_to_client = '$mac_algorithms_server_to_client',
609	   compression_algorithms_client_to_server = '$compression_algorithms_client_to_server',
610	   compression_algorithms_server_to_client = '$compression_algorithms_server_to_client',
611	   _ = '_'
612	  },
613    {ok,E} = ssh_trpt_test_lib:exec([{set_options,[silent]},
614				     {send, hello},
615				     receive_hello,
616				     {send, ssh_msg_kexinit},
617				     {match, KexInitPattern, receive_msg},
618				     close_socket
619				    ],
620				    InitialState),
621     [Kex, PubKey, EncC2S, EncS2C, MacC2S, MacS2C, CompC2S, CompS2C] =
622	ssh_trpt_test_lib:instantiate(['$kex_algorithms',
623				       '$server_host_key_algorithms',
624				       '$encryption_algorithms_client_to_server',
625				       '$encryption_algorithms_server_to_client',
626				       '$mac_algorithms_client_to_server',
627				       '$mac_algorithms_server_to_client',
628				       '$compression_algorithms_client_to_server',
629				       '$compression_algorithms_server_to_client'
630				      ], E),
631    [{kex, to_atoms(Kex)},
632     {public_key, to_atoms(PubKey)},
633     {cipher, [{client2server, to_atoms(EncC2S)},
634	       {server2client, to_atoms(EncS2C)}]},
635     {mac, [{client2server, to_atoms(MacC2S)},
636	    {server2client, to_atoms(MacS2C)}]},
637     {compression, [{client2server, to_atoms(CompC2S)},
638		    {server2client, to_atoms(CompS2C)}]}].
639
640
641%%%----------------------------------------------------------------
642extract_algos(Spec) ->
643    [{Tag,get_atoms(List)} || {Tag,List} <- Spec].
644
645get_atoms(L) ->
646    lists:usort(
647      [ A || X <- L,
648	     A <- case X of
649		      {_,L1} when is_list(L1) -> L1;
650		      Y when is_atom(Y) -> [Y]
651		  end]).
652
653
654intersection(AlgoSpec1, AlgoSpec2) -> intersect(sort_spec(AlgoSpec1), sort_spec(AlgoSpec2)).
655
656intersect([{Tag,S1}|Ss1], [{Tag,S2}|Ss2]) ->
657    [{Tag,intersect(S1,S2)} | intersect(Ss1,Ss2)];
658intersect(L1=[A1|_], L2=[A2|_]) when is_atom(A1),is_atom(A2) ->
659    Diff = L1 -- L2,
660    L1 -- Diff;
661intersect(_, _) ->
662    [].
663
664intersect_bi_dir([{Tag,[{client2server,L1},{server2client,L2}]}|T]) ->
665    [{Tag,intersect(L1,L2)} | intersect_bi_dir(T)];
666intersect_bi_dir([H={_,[A|_]}|T]) when is_atom(A) ->
667    [H | intersect_bi_dir(T)];
668intersect_bi_dir([]) ->
669    [].
670
671some_empty([]) ->
672    false;
673some_empty([{_,[]}|_]) ->
674    true;
675some_empty([{_,L}|T]) when is_atom(hd(L)) ->
676    some_empty(T);
677some_empty([{_,L}|T]) when is_tuple(hd(L)) ->
678    some_empty(L) orelse some_empty(T).
679
680
681sort_spec(L = [{_,_}|_] ) ->  [{Tag,sort_spec(Es)} || {Tag,Es} <- L];
682sort_spec(L) -> lists:usort(L).
683
684%%--------------------------------------------------------------------
685sshc(Tag) ->
686    to_atoms(
687      string:tokens(os:cmd(lists:concat(["ssh -Q ",Tag])), "\n")
688     ).
689
690ssh_type() ->
691    Parent = self(),
692    Pid = spawn(fun() ->
693			Parent ! {ssh_type,self(),ssh_type1()}
694		end),
695    MonitorRef = monitor(process, Pid),
696    receive
697	{ssh_type, Pid, Result} ->
698	    demonitor(MonitorRef),
699	    Result;
700	{'DOWN', MonitorRef, process, Pid, _Info} ->
701	    ct:log("~p:~p Process DOWN",[?MODULE,?LINE]),
702	    not_found
703    after
704	10000 ->
705	    ct:log("~p:~p Timeout",[?MODULE,?LINE]),
706	    demonitor(MonitorRef),
707	    not_found
708    end.
709
710
711ssh_type1() ->
712    try
713        ct:log("~p:~p os:find_executable(\"ssh\")",[?MODULE,?LINE]),
714	case os:find_executable("ssh") of
715	    false ->
716		ct:log("~p:~p Executable \"ssh\" not found",[?MODULE,?LINE]),
717		not_found;
718	    Path ->
719		ct:log("~p:~p Found \"ssh\" at ~p",[?MODULE,?LINE,Path]),
720                case installed_ssh_version(timeout) of
721		    Version = "OpenSSH" ++ _ ->
722                        ct:log("~p:~p Found OpenSSH  ~p",[?MODULE,?LINE,Version]),
723			openSSH;
724                    Other ->
725			ct:log("ssh client ~p is unknown",[Other]),
726			unknown
727		end
728	end
729    catch
730	Class:Exception ->
731	    ct:log("~p:~p Exception ~p:~p",[?MODULE,?LINE,Class,Exception]),
732	    not_found
733    end.
734
735installed_ssh_version(TimeoutReturn) ->
736    Parent = self(),
737    Pid = spawn(fun() ->
738                        Parent ! {open_ssh_version, os:cmd("ssh -V")}
739                end),
740    receive
741        {open_ssh_version, V} ->
742            V
743    after ?TIMEOUT ->
744            exit(Pid, kill),
745            TimeoutReturn
746    end.
747
748
749
750
751algo_intersection([], _) -> [];
752algo_intersection(_, []) -> [];
753algo_intersection(L1=[A1|_], L2=[A2|_]) when is_atom(A1), is_atom(A2) ->
754    true = lists:all(fun erlang:is_atom/1, L1++L2),
755    lists:foldr(fun(A,Acc) ->
756			case lists:member(A,L2) of
757			    true -> [A|Acc];
758			    false -> Acc
759			end
760		end, [], L1);
761algo_intersection([{K,V1}|T1], L2) ->
762    case lists:keysearch(K,1,L2) of
763	{value, {K,V2}} ->
764	    [{K,algo_intersection(V1,V2)} | algo_intersection(T1,L2)];
765	false ->
766	    algo_intersection(T1,L2)
767    end;
768algo_intersection(_, _) ->
769    [].
770
771
772to_atoms(L) -> lists:map(fun erlang:list_to_atom/1, L).
773
774%%%----------------------------------------------------------------
775ssh_supports(Alg, SshDefaultAlg_tag) ->
776    SupAlgs =
777	case proplists:get_value(SshDefaultAlg_tag,
778				 ssh:default_algorithms()) of
779	    [{_K1,L1}, {_K2,L2}] ->
780		lists:usort(L1++L2);
781	    L ->
782		L
783	end,
784    if
785	is_atom(Alg) ->
786	    lists:member(Alg, SupAlgs);
787	is_list(Alg) ->
788	    case Alg--SupAlgs of
789		[] ->
790		    true;
791		UnSup ->
792		    {false,UnSup}
793	    end
794    end.
795
796%%%----------------------------------------------------------------
797has_inet6_address() ->
798    try
799	[throw(6) || {ok,L} <- [inet:getifaddrs()],
800		     {_,L1} <- L,
801		     {addr,{_,_,_,_,_,_,_,_}} <- L1]
802    of
803	[] -> false
804    catch
805	throw:6 -> true
806    end.
807
808%%%----------------------------------------------------------------
809open_port(Arg1) ->
810    ?MODULE:open_port(Arg1, []).
811
812open_port(Arg1, ExtraOpts) ->
813    erlang:open_port(Arg1,
814		     [binary,
815		      stderr_to_stdout,
816		      exit_status,
817		      use_stdio,
818		      overlapped_io, hide %only affects windows
819		      | ExtraOpts]).
820
821%%%----------------------------------------------------------------
822%%% Sleeping
823
824%%% Milli sec
825sleep_millisec(Nms) -> receive after Nms -> ok end.
826
827%%% Micro sec
828sleep_microsec(Nus) ->
829   busy_wait(Nus, erlang:system_time(microsecond)).
830
831busy_wait(Nus, T0) ->
832    T = erlang:system_time(microsecond) - T0,
833    Tleft = Nus - T,
834    if
835	Tleft > 2000 ->
836	    sleep_millisec((Tleft-1500) div 1000), % μs -> ms
837	    busy_wait(Nus,T0);
838	Tleft > 1 ->
839	    busy_wait(Nus, T0);
840	true ->
841	    T
842    end.
843
844%%%----------------------------------------------------------------
845%% get_kex_init - helper function to get key_exchange_init_msg
846
847get_kex_init(Conn) ->
848    Ref = make_ref(),
849    {ok,TRef} = timer:send_after(15000, {reneg_timeout,Ref}),
850    get_kex_init(Conn, Ref, TRef).
851
852get_kex_init(Conn, Ref, TRef) ->
853    %% First, validate the key exchange is complete (StateName == connected)
854    {State, S} = sys:get_state(Conn),
855    case expected_state(State) of
856	true ->
857	    timer:cancel(TRef),
858	    %% Next, walk through the elements of the #state record looking
859	    %% for the #ssh_msg_kexinit record. This method is robust against
860	    %% changes to either record. The KEXINIT message contains a cookie
861	    %% unique to each invocation of the key exchange procedure (RFC4253)
862	    SL = tuple_to_list(S),
863	    case lists:keyfind(ssh_msg_kexinit, 1, SL) of
864		false ->
865		    throw(not_found);
866		KexInit ->
867		    KexInit
868	    end;
869
870	false ->
871	    receive
872		{reneg_timeout,Ref} ->
873                    ct:log("~p:~p Not in 'connected' state: ~p but reneg_timeout received. Fail.",
874                           [?MODULE,?LINE,State]),
875		    ct:log("S = ~p", [S]),
876		    ct:fail(reneg_timeout)
877	    after 0 ->
878                    ct:log("~p:~p Not in 'connected' state: ~p, Will try again after 100ms",[?MODULE,?LINE,State]),
879		    timer:sleep(100), % If renegotiation is complete we do not
880				      % want to exit on the reneg_timeout
881		    get_kex_init(Conn, Ref, TRef)
882	    end
883    end.
884
885expected_state({ext_info,_,_}) -> true;
886expected_state({connected,_}) -> true;
887expected_state(_) -> false.
888
889%%%----------------------------------------------------------------
890%%% Return a string with N random characters
891%%%
892random_chars(N) -> [($a-1)+rand:uniform($z-$a) || _<-lists:duplicate(N,x)].
893
894
895create_random_dir(Config) ->
896    PrivDir = proplists:get_value(priv_dir, Config),
897    Name = filename:join(PrivDir, random_chars(15)),
898    case file:make_dir(Name) of
899	ok ->
900	    Name;
901	{error,eexist} ->
902	    %% The Name already denotes an existing file system object, try again.
903	    %% The likelyhood of always generating an existing file name is low
904	    create_random_dir(Config)
905    end.
906
907%%%----------------------------------------------------------------
908match_ip(A, B) ->
909    R = match_ip0(A,B) orelse match_ip0(B,A),
910    ct:log("match_ip(~p, ~p) -> ~p",[A, B, R]),
911    R.
912
913match_ip0(A, A) ->
914    true;
915match_ip0(any, _) ->
916    true;
917match_ip0(A, B) ->
918    case match_ip1(A, B) of
919        true ->
920            true;
921        false when is_list(A) ->
922            case inet:parse_address(A) of
923                {ok,IPa} -> match_ip0(IPa, B);
924                _ -> false
925            end;
926        false when is_list(B) ->
927            case inet:parse_address(B) of
928                {ok,IPb} -> match_ip0(A, IPb);
929                _ -> false
930            end;
931        false ->
932            false
933    end.
934
935match_ip1(any, _) -> true;
936match_ip1(loopback,  {127,_,_,_}) ->  true;
937match_ip1({0,0,0,0}, {127,_,_,_}) ->  true;
938match_ip1(loopback,          {0,0,0,0,0,0,0,1}) ->  true;
939match_ip1({0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,1}) ->  true;
940match_ip1(_, _) -> false.
941
942%%%----------------------------------------------------------------
943mangle_connect_address(A) ->
944    mangle_connect_address(A, []).
945
946mangle_connect_address(A, SockOpts) ->
947    mangle_connect_address1(A, proplists:get_value(inet6,SockOpts,false)).
948
949loopback(true) -> {0,0,0,0,0,0,0,1};
950loopback(false) ->      {127,0,0,1}.
951
952mangle_connect_address1( loopback,     V6flg) -> loopback(V6flg);
953mangle_connect_address1(      any,     V6flg) -> loopback(V6flg);
954mangle_connect_address1({0,0,0,0},         _) -> loopback(false);
955mangle_connect_address1({0,0,0,0,0,0,0,0}, _) -> loopback(true);
956mangle_connect_address1(       IP,     _) when is_tuple(IP) -> IP;
957mangle_connect_address1(A, _) ->
958    case catch inet:parse_address(A) of
959        {ok,         {0,0,0,0}} -> loopback(false);
960        {ok, {0,0,0,0,0,0,0,0}} -> loopback(true);
961        _ -> A
962    end.
963
964%%%----------------------------------------------------------------
965ntoa(A) ->
966    try inet:ntoa(A)
967    of
968        {error,_} when is_atom(A) -> atom_to_list(A);
969        {error,_} when is_list(A) -> A;
970        S when is_list(S) -> S
971    catch
972        _:_ when is_atom(A) -> atom_to_list(A);
973        _:_ when is_list(A) -> A
974    end.
975
976%%%----------------------------------------------------------------
977try_enable_fips_mode() ->
978    case crypto:info_fips() of
979        enabled ->
980            report("FIPS mode already enabled", ?LINE),
981            ok;
982        not_enabled ->
983            %% Erlang/crypto configured with --enable-fips
984            case crypto:enable_fips_mode(true) of
985		true ->
986                    %% and also the cryptolib is fips enabled
987                    report("FIPS mode enabled", ?LINE),
988		    enabled = crypto:info_fips(),
989		    ok;
990		false ->
991                    case is_cryptolib_fips_capable() of
992                        false ->
993                            report("No FIPS mode in cryptolib", ?LINE),
994                            {skip, "FIPS mode not supported in cryptolib"};
995                        true ->
996                            ct:fail("Failed to enable FIPS mode", [])
997                    end
998	    end;
999        not_supported ->
1000            report("FIPS mode not supported by Erlang/OTP", ?LINE),
1001            {skip, "FIPS mode not supported"}
1002    end.
1003
1004is_cryptolib_fips_capable() ->
1005    [{_,_,Inf}] = crypto:info_lib(),
1006    nomatch =/= re:run(Inf, "(F|f)(I|i)(P|p)(S|s)").
1007
1008report(Comment, Line) ->
1009    ct:comment(Comment),
1010    ct:log("~p:~p  try_enable_fips_mode~n"
1011           "crypto:info_lib() = ~p~n"
1012           "crypto:info_fips() = ~p~n"
1013           "crypto:supports() =~n~p~n",
1014           [?MODULE, Line,
1015            crypto:info_lib(),
1016            crypto:info_fips(),
1017            crypto:supports()]).
1018
1019%%%----------------------------------------------------------------
1020lc_name_in(Names) ->
1021    case inet:gethostname() of
1022        {ok,Name} ->
1023            lists:member(string:to_lower(Name), Names);
1024        Other ->
1025            ct:log("~p:~p  inet:gethostname() returned ~p", [?MODULE,?LINE,Other]),
1026            false
1027    end.
1028
1029ptty_supported() -> not lc_name_in([]). %%["fobi"]).
1030
1031%%%----------------------------------------------------------------
1032has_WSL() ->
1033    os:getenv("WSLENV") =/= false. % " =/= false" =/= "== true" :)
1034
1035winpath_to_linuxpath(Path) ->
1036    case {has_WSL(), Path} of
1037        {true, [_,$:|WithoutWinInit]} ->
1038            "/mnt/c" ++ WithoutWinInit;
1039        _ ->
1040            Path
1041    end.
1042
1043%%%----------------------------------------------------------------
1044copy_recursive(Src, Dst) ->
1045    {ok,S} = file:read_file_info(Src),
1046    case S#file_info.type of
1047        directory ->
1048            %%ct:log("~p:~p copy dir  ~ts -> ~ts", [?MODULE,?LINE,Src,Dst]),
1049            {ok,Names} = file:list_dir(Src),
1050            mk_dir_path(Dst),
1051            %%ct:log("~p:~p Names = ~p", [?MODULE,?LINE,Names]),
1052            lists:foreach(fun(Name) ->
1053                                  copy_recursive(filename:join(Src, Name),
1054                                                 filename:join(Dst, Name))
1055                          end, Names);
1056        _ ->
1057            %%ct:log("~p:~p copy file ~ts -> ~ts", [?MODULE,?LINE,Src,Dst]),
1058            {ok,_NumBytesCopied} = file:copy(Src, Dst)
1059    end.
1060
1061%%%----------------------------------------------------------------
1062%% Make a directory even if parts of the path does not exist
1063
1064mk_dir_path(DirPath) ->
1065    case file:make_dir(DirPath) of
1066        {error,eexist} ->
1067            %%ct:log("~p:~p dir exists ~ts", [?MODULE,?LINE,DirPath]),
1068            ok;
1069        {error,enoent} ->
1070            %%ct:log("~p:~p try make dirname of ~ts", [?MODULE,?LINE,DirPath]),
1071            case mk_dir_path( filename:dirname(DirPath) ) of
1072                ok ->
1073                    %%ct:log("~p:~p redo ~ts", [?MODULE,?LINE,DirPath]),
1074                    file:make_dir(DirPath);
1075                Error ->
1076                    %%ct:log("~p:~p return Error ~p ~ts", [?MODULE,?LINE,Error,DirPath]),
1077                    Error
1078            end;
1079        Other ->
1080            %%ct:log("~p:~p return Other ~p ~ts", [?MODULE,?LINE,Other,DirPath]),
1081            Other
1082    end.
1083
1084%%%----------------------------------------------------------------
1085%%% New
1086
1087setup_all_user_host_keys(Config) ->
1088    DataDir = proplists:get_value(data_dir, Config),
1089    PrivDir = proplists:get_value(priv_dir, Config),
1090    setup_all_user_host_keys(DataDir, PrivDir).
1091
1092setup_all_user_host_keys(DataDir, PrivDir) ->
1093    setup_all_user_host_keys(DataDir, PrivDir, filename:join(PrivDir,"system")).
1094
1095setup_all_user_host_keys(DataDir, UserDir, SysDir) ->
1096    lists:foldl(fun(Alg, OkAlgs) ->
1097                        try
1098                            ok = ssh_test_lib:setup_user_key(Alg, DataDir, UserDir),
1099                            ok = ssh_test_lib:setup_host_key(Alg, DataDir, SysDir)
1100                        of
1101                            ok -> [Alg|OkAlgs]
1102                        catch
1103                            error:{badmatch, {error,enoent}} ->
1104                                OkAlgs;
1105                            C:E:S ->
1106                                ct:log("Exception in ~p:~p for alg ~p:  ~p:~p~n~p",
1107                                       [?MODULE,?FUNCTION_NAME,Alg,C,E,S]),
1108                                OkAlgs
1109                        end
1110                end, [], ssh_transport:supported_algorithms(public_key)).
1111
1112
1113setup_all_host_keys(Config) ->
1114    DataDir = proplists:get_value(data_dir, Config),
1115    PrivDir = proplists:get_value(priv_dir, Config),
1116    setup_all_host_keys(DataDir, filename:join(PrivDir,"system")).
1117
1118setup_all_host_keys(DataDir, SysDir) ->
1119    lists:foldl(fun(Alg, OkAlgs) ->
1120                        try
1121                            ok = ssh_test_lib:setup_host_key(Alg, DataDir, SysDir)
1122                        of
1123                            ok -> [Alg|OkAlgs]
1124                        catch
1125                            error:{badmatch, {error,enoent}} ->
1126                                OkAlgs;
1127                            C:E:S ->
1128                                ct:log("Exception in ~p:~p for alg ~p:  ~p:~p~n~p",
1129                                       [?MODULE,?FUNCTION_NAME,Alg,C,E,S]),
1130                                OkAlgs
1131                        end
1132                end, [], ssh_transport:supported_algorithms(public_key)).
1133
1134
1135setup_all_user_keys(DataDir, UserDir) ->
1136    lists:foldl(fun(Alg, OkAlgs) ->
1137                        try
1138                            ok = ssh_test_lib:setup_user_key(Alg, DataDir, UserDir)
1139                        of
1140                            ok -> [Alg|OkAlgs]
1141                        catch
1142                            error:{badmatch, {error,enoent}} ->
1143                                OkAlgs;
1144                            C:E:S ->
1145                                ct:log("Exception in ~p:~p for alg ~p:  ~p:~p~n~p",
1146                                       [?MODULE,?FUNCTION_NAME,Alg,C,E,S]),
1147                                OkAlgs
1148                        end
1149                end, [], ssh_transport:supported_algorithms(public_key)).
1150
1151
1152setup_user_key(SshAlg, DataDir, UserDir) ->
1153    file:make_dir(UserDir),
1154    %% Copy private user key to user's dir
1155    {ok,_} = file:copy(filename:join(DataDir, file_base_name(user_src,SshAlg)),
1156                       filename:join(UserDir, file_base_name(user,SshAlg))),
1157    %% Setup authorized_keys in user's dir
1158    {ok,Pub} = file:read_file(filename:join(DataDir, file_base_name(user_src,SshAlg)++".pub")),
1159    ok = file:write_file(filename:join(UserDir, "authorized_keys"),
1160                         io_lib:format("~n~s~n",[Pub]),
1161                         [append]),
1162    ?ct_log_show_file( filename:join(DataDir, file_base_name(user_src,SshAlg)++".pub") ),
1163    ?ct_log_show_file( filename:join(UserDir, "authorized_keys") ),
1164    ok.
1165
1166setup_host_key_create_dir(SshAlg, DataDir, BaseDir) ->
1167    SysDir = filename:join(BaseDir,"system"),
1168    ct:log("~p:~p  SshAlg=~p~nDataDir = ~p~nBaseDir = ~p~nSysDir = ~p",[?MODULE,?LINE,SshAlg, DataDir, BaseDir,SysDir]),
1169    file:make_dir(SysDir),
1170    setup_host_key(SshAlg, DataDir, SysDir),
1171    SysDir.
1172
1173setup_host_key(SshAlg, DataDir, SysDir) ->
1174    mk_dir_path(SysDir),
1175    %% Copy private host key to system's dir
1176    {ok,_} = file:copy(filename:join(DataDir, file_base_name(system_src,SshAlg)),
1177                       filename:join(SysDir,  file_base_name(system,SshAlg))),
1178    ?ct_log_show_file( filename:join(SysDir,  file_base_name(system,SshAlg)) ),
1179    ok.
1180
1181setup_known_host(SshAlg, DataDir, UserDir) ->
1182    {ok,Pub} = file:read_file(filename:join(DataDir, file_base_name(system_src,SshAlg)++".pub")),
1183    S = lists:join(" ", lists:reverse(tl(lists:reverse(string:tokens(binary_to_list(Pub), " "))))),
1184    ok = file:write_file(filename:join(UserDir, "known_hosts"),
1185                         io_lib:format("~p~n",[S])),
1186    ?ct_log_show_file( filename:join(UserDir, "known_hosts") ),
1187    ok.
1188
1189
1190get_addr_str() ->
1191    {ok, Hostname} = inet:gethostname(),
1192    {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet),
1193    IP = lists:concat([A, ".", B, ".", C, ".", D]),
1194    lists:concat([Hostname,",",IP]).
1195
1196
1197file_base_name(user,   'ecdsa-sha2-nistp256') -> "id_ecdsa";
1198file_base_name(user,   'ecdsa-sha2-nistp384') -> "id_ecdsa";
1199file_base_name(user,   'ecdsa-sha2-nistp521') -> "id_ecdsa";
1200file_base_name(user,   'rsa-sha2-256'       ) -> "id_rsa";
1201file_base_name(user,   'rsa-sha2-384'       ) -> "id_rsa";
1202file_base_name(user,   'rsa-sha2-512'       ) -> "id_rsa";
1203file_base_name(user,   'ssh-dss'            ) -> "id_dsa";
1204file_base_name(user,   'ssh-ed25519'        ) -> "id_ed25519";
1205file_base_name(user,   'ssh-ed448'          ) -> "id_ed448";
1206file_base_name(user,   'ssh-rsa'            ) -> "id_rsa";
1207
1208file_base_name(user_src, 'ecdsa-sha2-nistp256') -> "id_ecdsa256";
1209file_base_name(user_src, 'ecdsa-sha2-nistp384') -> "id_ecdsa384";
1210file_base_name(user_src, 'ecdsa-sha2-nistp521') -> "id_ecdsa521";
1211file_base_name(user_src, Alg) -> file_base_name(user, Alg);
1212
1213file_base_name(system, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key";
1214file_base_name(system, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key";
1215file_base_name(system, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key";
1216file_base_name(system, 'rsa-sha2-256'       ) -> "ssh_host_rsa_key";
1217file_base_name(system, 'rsa-sha2-384'       ) -> "ssh_host_rsa_key";
1218file_base_name(system, 'rsa-sha2-512'       ) -> "ssh_host_rsa_key";
1219file_base_name(system, 'ssh-dss'            ) -> "ssh_host_dsa_key";
1220file_base_name(system, 'ssh-ed25519'        ) -> "ssh_host_ed25519_key";
1221file_base_name(system, 'ssh-ed448'          ) -> "ssh_host_ed448_key";
1222file_base_name(system, 'ssh-rsa'            ) -> "ssh_host_rsa_key";
1223
1224file_base_name(system_src, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key256";
1225file_base_name(system_src, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key384";
1226file_base_name(system_src, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key521";
1227file_base_name(system_src, Alg) -> file_base_name(system, Alg).
1228
1229%%%----------------------------------------------------------------
1230