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).
24
25-include("ssh.hrl").
26-include("ssh_connect.hrl").
27-include_lib("public_key/include/public_key.hrl").
28-include_lib("kernel/include/file.hrl").
29-include_lib("kernel/include/inet.hrl").
30
31-export([start/0, start/1, stop/0,
32	 connect/2, connect/3, connect/4,
33	 close/1, connection_info/2,
34         connection_info/1,
35	 channel_info/3,
36	 daemon/1, daemon/2, daemon/3,
37	 daemon_info/1, daemon_info/2,
38         set_sock_opts/2, get_sock_opts/2,
39	 default_algorithms/0,
40         chk_algos_opts/1,
41	 stop_listener/1, stop_listener/2,  stop_listener/3,
42	 stop_daemon/1, stop_daemon/2, stop_daemon/3,
43	 shell/1, shell/2, shell/3,
44         tcpip_tunnel_from_server/5, tcpip_tunnel_from_server/6,
45         tcpip_tunnel_to_server/5, tcpip_tunnel_to_server/6
46	]).
47
48%% In move from public_key
49-export([hostkey_fingerprint/1, hostkey_fingerprint/2
50        ]).
51
52
53%%% Internal export
54-export([is_host/2]).
55
56-behaviour(ssh_dbg).
57-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2, ssh_dbg_format/3]).
58
59%%% "Deprecated" types export:
60-export_type([ssh_daemon_ref/0, ssh_connection_ref/0, ssh_channel_id/0]).
61-opaque ssh_daemon_ref()     :: daemon_ref().
62-opaque ssh_connection_ref() :: connection_ref().
63-opaque ssh_channel_id()     :: channel_id().
64
65
66%%% Type exports
67-export_type([daemon_ref/0,
68              connection_ref/0,
69	      channel_id/0,
70              client_options/0, client_option/0,
71              daemon_options/0, daemon_option/0,
72              common_options/0,
73              role/0,
74              subsystem_spec/0,
75              algs_list/0,
76              double_algs/1,
77              modify_algs_list/0,
78              alg_entry/0,
79              kex_alg/0,
80              pubkey_alg/0,
81              cipher_alg/0,
82              mac_alg/0,
83              compression_alg/0,
84              host/0,
85              open_socket/0,
86              ip_port/0
87	     ]).
88
89
90-opaque daemon_ref()         :: pid() .
91-opaque channel_id()     :: non_neg_integer().
92-type connection_ref()       :: pid().  % should be -opaque, but that gives problems
93
94%%--------------------------------------------------------------------
95%% Description: Starts the ssh application. Default type
96%% is temporary. see application(3)
97%%--------------------------------------------------------------------
98-spec start() -> ok | {error, term()}.
99
100start() ->
101    start(temporary).
102
103-spec start(Type) -> ok | {error, term()} when
104      Type :: permanent | transient | temporary .
105
106start(Type) ->
107    case application:ensure_all_started(ssh, Type) of
108        {ok, _} ->
109            %% Clear cached default_algorithms (if exists) ...
110            ssh_transport:clear_default_algorithms_env(),
111            %% ... and rebuld them taking configure options in account
112            ssh_transport:default_algorithms(),
113            ok;
114        Other ->
115            Other
116    end.
117
118%%--------------------------------------------------------------------
119%% Description: Stops the ssh application.
120%%--------------------------------------------------------------------
121-spec stop() -> ok | {error, term()}.
122
123stop() ->
124    application:stop(ssh).
125
126%%--------------------------------------------------------------------
127%% Description: Starts an ssh connection.
128%%--------------------------------------------------------------------
129-spec connect(OpenTcpSocket, Options) -> {ok,connection_ref()} | {error,term()} when
130      OpenTcpSocket :: open_socket(),
131      Options :: client_options().
132
133connect(OpenTcpSocket, Options) when is_list(Options) ->
134    connect(OpenTcpSocket, Options, infinity).
135
136
137-spec connect(open_socket(), client_options(), timeout()) ->
138                     {ok,connection_ref()} | {error,term()}
139           ; (host(), inet:port_number(), client_options()) ->
140                     {ok,connection_ref()} | {error,term()}.
141
142connect(Host, Port, Options) when is_integer(Port),
143                                  Port>0,
144                                  is_list(Options) ->
145    connect(Host, Port, Options, infinity);
146
147connect(Socket, UserOptions, NegotiationTimeout) when is_list(UserOptions) ->
148    case ssh_options:handle_options(client, UserOptions) of
149	{error, Error} ->
150	    {error, Error};
151
152	Options = #{} ->
153            case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
154                ok ->
155                    continue_connect(Socket, Options, NegotiationTimeout);
156                {error,SockError} ->
157                    {error,SockError}
158            end
159    end.
160
161
162-spec connect(Host, Port, Options, NegotiationTimeout) -> {ok,connection_ref()} | {error,term()} when
163      Host :: host(),
164      Port :: inet:port_number(),
165      Options :: client_options(),
166      NegotiationTimeout :: timeout().
167
168connect(Host0, Port, UserOptions, NegotiationTimeout) when is_integer(Port),
169                                                           Port>0,
170                                                           is_list(UserOptions) ->
171    case ssh_options:handle_options(client, UserOptions) of
172	{error, Reason} ->
173            {error, Reason};
174
175        Options ->
176            SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)],
177            Host = mangle_connect_address(Host0, Options),
178            try
179                transport_connect(Host, Port, SocketOpts, Options)
180            of
181                {ok, Socket} ->
182                    continue_connect(Socket, Options, NegotiationTimeout);
183                {error, Reason} ->
184                    {error, Reason}
185            catch
186                _:badarg -> {error, {options,?GET_OPT(socket_options,Options)}};
187                _:{error,Reason} -> {error,Reason};
188                error:Error -> {error,Error};
189                Class:Error -> {error, {Class,Error}}
190            end
191    end.
192
193%%%----------------
194continue_connect(Socket, Options0, NegTimeout) ->
195    {ok, {SockHost,SockPort}} = inet:sockname(Socket),
196    Options = ?PUT_INTERNAL_OPT([{negotiation_timeout,NegTimeout}], Options0),
197    Address = #address{address = SockHost,
198                       port = SockPort,
199                       profile = ?GET_OPT(profile,Options)
200                      },
201    ssh_system_sup:start_subsystem(client, Address, Socket, Options).
202
203%%--------------------------------------------------------------------
204-spec close(ConnectionRef) -> ok | {error,term()} when
205      ConnectionRef :: connection_ref() .
206%%
207%% Description: Closes an ssh connection.
208%%--------------------------------------------------------------------
209close(ConnectionRef) ->
210    ssh_connection_handler:stop(ConnectionRef).
211
212%%--------------------------------------------------------------------
213%% Description: Retrieves information about a connection.
214%%---------------------------------------------------------------------
215-type version() :: {protocol_version(), software_version()}.
216-type protocol_version() :: {Major::pos_integer(), Minor::non_neg_integer()}.
217-type software_version() :: string().
218-type conn_info_algs() :: [{kex, kex_alg()}
219                           | {hkey, pubkey_alg()}
220                           | {encrypt, cipher_alg()}
221                           | {decrypt, cipher_alg()}
222                           | {send_mac, mac_alg()}
223                           | {recv_mac, mac_alg()}
224                           | {compress, compression_alg()}
225                           | {decompress, compression_alg()}
226                           | {send_ext_info, boolean()}
227                           | {recv_ext_info, boolean()}
228                          ].
229-type conn_info_channels() :: [proplists:proplist()].
230
231-type connection_info_tuple() ::
232        {client_version, version()}
233      | {server_version, version()}
234      | {user, string()}
235      | {peer, {inet:hostname(), ip_port()}}
236      | {sockname, ip_port()}
237      | {options, client_options()}
238      | {algorithms, conn_info_algs()}
239      | {channels, conn_info_channels()}.
240
241-spec connection_info(ConnectionRef) -> InfoTupleList when
242      ConnectionRef :: connection_ref(),
243      InfoTupleList :: [InfoTuple],
244      InfoTuple :: connection_info_tuple().
245
246connection_info(ConnectionRef) ->
247    connection_info(ConnectionRef, []).
248
249-spec connection_info(ConnectionRef, ItemList|Item) ->  InfoTupleList|InfoTuple when
250      ConnectionRef :: connection_ref(),
251      ItemList :: [Item],
252      Item :: client_version | server_version | user | peer | sockname | options | algorithms | sockname,
253      InfoTupleList :: [InfoTuple],
254      InfoTuple :: connection_info_tuple().
255
256connection_info(ConnectionRef, Key) ->
257    ssh_connection_handler:connection_info(ConnectionRef, Key).
258
259%%--------------------------------------------------------------------
260-spec channel_info(connection_ref(), channel_id(), [atom()]) -> proplists:proplist().
261%%
262%% Description: Retrieves information about a connection.
263%%--------------------------------------------------------------------
264channel_info(ConnectionRef, ChannelId, Options) ->
265    ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options).
266
267%%--------------------------------------------------------------------
268%% Description: Starts a server listening for SSH connections
269%% on the given port.
270%%--------------------------------------------------------------------
271-spec daemon(inet:port_number()) ->  {ok,daemon_ref()} | {error,term()}.
272
273daemon(Port) ->
274    daemon(Port, []).
275
276
277-spec daemon(inet:port_number()|open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}.
278
279daemon(Port, UserOptions) when 0 =< Port,Port =< 65535 ->
280    daemon(any, Port, UserOptions);
281
282daemon(Socket, UserOptions) ->
283    case ssh_options:handle_options(server, UserOptions) of
284        #{} = Options0 ->
285            case valid_socket_to_use(Socket, ?GET_OPT(transport,Options0)) of
286                ok ->
287                    try
288                        %% throws error:Error if no usable hostkey is found
289                        ssh_connection_handler:available_hkey_algorithms(server, Options0),
290                        {ok, {SockHost,SockPort}} = inet:sockname(Socket),
291                        Address = #address{address = SockHost,
292                                           port = SockPort,
293                                           profile = ?GET_OPT(profile,Options0)
294                                          },
295                        Options = ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options0),
296                        case ssh_system_sup:start_subsystem(server, Address, Socket, Options) of
297                            {ok,Pid} ->
298                                {ok,Pid};
299                            {error, {already_started, _}} ->
300                                {error, eaddrinuse};
301                            {error, Error} ->
302                                {error, Error}
303                        end
304                    catch
305                        error:{shutdown,Err} ->
306                            {error,Err};
307                        exit:{noproc, _} ->
308                            {error, ssh_not_started};
309                        C:R ->
310                            {error,{could_not_start_connection,{C,R}}}
311                    end;
312
313                {error,SockError} ->
314                    {error,SockError}
315            end;
316
317        {error,OptionError} ->
318            {error,OptionError}
319    end.
320
321
322
323-spec daemon(any | inet:ip_address(), inet:port_number(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}
324           ;(socket, open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}
325            .
326
327daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535,
328                                        Host0 == any ; Host0 == loopback ; is_tuple(Host0) ->
329    try
330        {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0),
331        #{} = Options0 = ssh_options:handle_options(server, UserOptions),
332        %% We need to open the listen socket here before start of the system supervisor. That
333        %% is because Port0 might be 0, or if an FD is provided in the Options0, in which case
334        %% the real listening port will be known only after the gen_tcp:listen call.
335        maybe_open_listen_socket(Host1, Port0, Options0)
336    of
337        {Host, Port, ListenSocket, Options1} ->
338            try
339                %% Now Host,Port is what to use for the supervisor to register its name,
340                %% and ListenSocket, if provided,  is for listening on connections. But
341                %% it is still owned by self()...
342
343                %% throws error:Error if no usable hostkey is found
344                ssh_connection_handler:available_hkey_algorithms(server, Options1),
345                ssh_system_sup:start_system(server,
346                                            #address{address = Host,
347                                                     port = Port,
348                                                     profile = ?GET_OPT(profile,Options1)},
349                                            Options1)
350            of
351                {ok,DaemonRef} when ListenSocket == undefined ->
352                    {ok,DaemonRef};
353                {ok,DaemonRef} ->
354                    receive
355                        {request_control, ListenSocket, ReqPid} ->
356                            ok = controlling_process(ListenSocket, ReqPid, Options1),
357                            ReqPid ! {its_yours,ListenSocket}
358                    end,
359                    {ok,DaemonRef};
360                {error, {already_started, _}} ->
361                    close_listen_socket(ListenSocket, Options1),
362                    {error, eaddrinuse};
363                {error, Error} ->
364                    close_listen_socket(ListenSocket, Options1),
365                    {error, Error}
366            catch
367                error:{shutdown,Err} ->
368                    close_listen_socket(ListenSocket, Options1),
369                    {error,Err};
370                exit:{noproc, _} ->
371                    close_listen_socket(ListenSocket, Options1),
372                    {error, ssh_not_started};
373                error:Error ->
374                    close_listen_socket(ListenSocket, Options1),
375                    error(Error);
376                exit:Exit ->
377                    close_listen_socket(ListenSocket, Options1),
378                    exit(Exit)
379            end
380    catch
381        throw:bad_fd ->
382            {error,bad_fd};
383        throw:bad_socket ->
384            {error,bad_socket};
385        error:{badmatch,{error,Error}} ->
386            {error,Error};
387        error:Error ->
388            {error,Error};
389        _C:_E ->
390            {error,{cannot_start_daemon,_C,_E}}
391    end;
392
393daemon(_, _, _) ->
394    {error, badarg}.
395
396%%--------------------------------------------------------------------
397-type daemon_info_tuple() ::
398        {port, inet:port_number()}
399      | {ip, inet:ip_address()}
400      | {profile, atom()}
401      | {options, daemon_options()}.
402
403-spec daemon_info(DaemonRef) -> {ok,InfoTupleList} | {error,bad_daemon_ref} when
404      DaemonRef :: daemon_ref(),
405      InfoTupleList :: [InfoTuple],
406      InfoTuple :: daemon_info_tuple().
407
408daemon_info(DaemonRef) ->
409    case ssh_system_sup:get_daemon_listen_address(DaemonRef) of
410        {ok,A} ->
411            Address =
412                case inet:parse_strict_address(A#address.address) of
413                    {ok,IP} -> A#address{address=IP};
414                    _ -> A
415                end,
416            Opts =
417                %% Pick a subset of the Options to present:
418                case ssh_system_sup:get_options(DaemonRef, Address) of
419                    {ok, OptMap} ->
420                        lists:sort(
421                          maps:to_list(
422                            ssh_options:keep_set_options(
423                              server,
424                              ssh_options:keep_user_options(server,OptMap))));
425                    _ ->
426                        []
427                end,
428
429	    {ok, [{port,    Address#address.port},
430                  {ip,      Address#address.address},
431                  {profile, Address#address.profile},
432                  {options, Opts}
433                 ]};
434
435	_ ->
436	    {error,bad_daemon_ref}
437    end.
438
439-spec daemon_info(DaemonRef, ItemList|Item) ->  InfoTupleList|InfoTuple | {error,bad_daemon_ref} when
440      DaemonRef :: daemon_ref(),
441      ItemList :: [Item],
442      Item :: ip | port | profile | options,
443      InfoTupleList :: [InfoTuple],
444      InfoTuple :: daemon_info_tuple().
445
446daemon_info(DaemonRef, Key) when is_atom(Key) ->
447    case daemon_info(DaemonRef, [Key]) of
448        [{Key,Val}] -> {Key,Val};
449        Other -> Other
450    end;
451daemon_info(DaemonRef, Keys) ->
452    case daemon_info(DaemonRef) of
453        {ok,KVs} ->
454            [{Key,proplists:get_value(Key,KVs)} || Key <- Keys,
455                                                   lists:keymember(Key,1,KVs)];
456        _ ->
457            []
458    end.
459
460%%--------------------------------------------------------------------
461%% Description: Stops the listener, but leaves
462%% existing connections started by the listener up and running.
463%%--------------------------------------------------------------------
464-spec stop_listener(daemon_ref()) -> ok.
465
466stop_listener(SysSup) ->
467    ssh_system_sup:stop_listener(SysSup).
468
469
470-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok.
471
472stop_listener(Address, Port) ->
473    stop_listener(Address, Port, ?DEFAULT_PROFILE).
474
475
476-spec stop_listener(any|inet:ip_address(), inet:port_number(), term()) -> ok.
477
478stop_listener(Address, Port, Profile) ->
479    lists:foreach(fun({Sup,_Addr}) ->
480                          stop_listener(Sup)
481                  end,
482                  ssh_system_sup:addresses(server,
483                                           #address{address=Address,
484                                                    port=Port,
485                                                    profile=Profile})).
486
487%%--------------------------------------------------------------------
488%% Description: Stops the listener and all connections started by
489%% the listener.
490%%--------------------------------------------------------------------
491-spec stop_daemon(DaemonRef::daemon_ref()) -> ok.
492
493stop_daemon(SysSup) ->
494    ssh_system_sup:stop_system(server, SysSup).
495
496
497-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok.
498
499stop_daemon(Address, Port) ->
500    stop_daemon(Address, Port, ?DEFAULT_PROFILE).
501
502
503-spec stop_daemon(any|inet:ip_address(), inet:port_number(), atom()) -> ok.
504
505stop_daemon(Address, Port, Profile) ->
506    lists:foreach(fun({Sup,_Addr}) ->
507                          stop_daemon(Sup)
508                  end,
509                  ssh_system_sup:addresses(server,
510                                           #address{address=Address,
511                                                    port=Port,
512                                                    profile=Profile})).
513
514%%--------------------------------------------------------------------
515%% Description: Starts an interactive shell to an SSH server on the
516%% given <Host>. The function waits for user input,
517%% and will not return until the remote shell is ended.(e.g. on
518%% exit from the shell)
519%%--------------------------------------------------------------------
520-spec shell(open_socket() | host() | connection_ref()) ->  _.
521
522shell(ConnectionRef) when is_pid(ConnectionRef) ->
523    case ssh_connection:session_channel(ConnectionRef, infinity) of
524	{ok,ChannelId}  ->
525	    success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
526                                                [{pty_opts, [{echo,0}]}
527                                                ]),
528            success = ssh_connection:send_environment_vars(ConnectionRef, ChannelId,
529                                                           ["LANG", "LC_ALL"]),
530	    Args = [{channel_cb, ssh_shell},
531		    {init_args,[ConnectionRef, ChannelId]},
532		    {cm, ConnectionRef}, {channel_id, ChannelId}],
533	    {ok, State} = ssh_client_channel:init([Args]),
534            try
535                ssh_client_channel:enter_loop(State)
536            catch
537                exit:normal ->
538                    ok
539            end;
540	Error ->
541	    Error
542    end;
543
544shell(Dest) ->
545    case is_host(Dest, []) of
546        true ->
547            shell(Dest, ?SSH_DEFAULT_PORT, []);
548        false ->
549            %% Maybe socket
550            shell_socket(Dest, [])
551    end.
552
553
554
555-spec shell(open_socket() | host(), client_options()) ->  _.
556
557shell(Dest, Options) ->
558    case is_host(Dest, Options) of
559        true ->
560            shell(Dest, ?SSH_DEFAULT_PORT, Options);
561        false ->
562            %% Maybe socket
563            shell_socket(Dest, Options)
564    end.
565
566shell_socket(Socket, Options) ->
567    case connect(Socket, Options) of
568        {ok,ConnectionRef} ->
569            shell(ConnectionRef),
570            close(ConnectionRef);
571        Error ->
572            Error
573    end.
574
575
576
577-spec shell(Host, Port, Options) -> _ when
578      Host :: host(),
579      Port :: inet:port_number(),
580      Options :: client_options() .
581
582shell(Host, Port, Options) ->
583    case connect(Host, Port, Options) of
584        {ok,ConnectionRef} ->
585            shell(ConnectionRef),
586            close(ConnectionRef);
587        Error ->
588            Error
589    end.
590
591%%--------------------------------------------------------------------
592-spec default_algorithms() -> algs_list() .
593%%--------------------------------------------------------------------
594default_algorithms() ->
595    ssh_transport:default_algorithms().
596
597%%--------------------------------------------------------------------
598-spec chk_algos_opts(client_options()|daemon_options()) -> internal_options() | {error,term()}.
599%%--------------------------------------------------------------------
600chk_algos_opts(Opts) ->
601    case lists:foldl(
602           fun({preferred_algorithms,_}, Acc) -> Acc;
603              ({modify_algorithms,_}, Acc) -> Acc;
604              (KV, Acc) -> [KV|Acc]
605           end, [], Opts)
606    of
607        [] ->
608            case ssh_options:handle_options(client, Opts) of
609                M when is_map(M) ->
610                    maps:get(preferred_algorithms, M);
611                Others ->
612                    Others
613            end;
614        OtherOps ->
615            {error, {non_algo_opts_found,OtherOps}}
616    end.
617
618
619%%--------------------------------------------------------------------
620-spec set_sock_opts(ConnectionRef, SocketOptions) ->
621                           ok | {error, inet:posix()}  when
622      ConnectionRef :: connection_ref(),
623      SocketOptions :: [gen_tcp:option()] .
624%%--------------------------------------------------------------------
625set_sock_opts(ConnectionRef, SocketOptions) ->
626    ssh_connection_handler:set_sock_opts(ConnectionRef, SocketOptions).
627
628%%--------------------------------------------------------------------
629-spec get_sock_opts(ConnectionRef, SocketGetOptions) ->
630                           ok | {error, inet:posix()}  when
631      ConnectionRef :: connection_ref(),
632      SocketGetOptions :: [gen_tcp:option_name()] .
633%%--------------------------------------------------------------------
634get_sock_opts(ConnectionRef, SocketGetOptions) ->
635    ssh_connection_handler:get_sock_opts(ConnectionRef, SocketGetOptions).
636
637%%--------------------------------------------------------------------
638%% Ask local client to listen to ListenHost:ListenPort.  When someone
639%% connects that address, connect to ConnectToHost:ConnectToPort from
640%% the server.
641%%--------------------------------------------------------------------
642-spec tcpip_tunnel_to_server(ConnectionRef,
643                             ListenHost, ListenPort,
644                             ConnectToHost, ConnectToPort
645                          ) ->
646                                  {ok,TrueListenPort} | {error, term()} when
647      ConnectionRef :: connection_ref(),
648      ListenHost :: host(),
649      ListenPort :: inet:port_number(),
650      ConnectToHost :: host(),
651      ConnectToPort :: inet:port_number(),
652      TrueListenPort :: inet:port_number().
653
654tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort) ->
655    tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort, infinity).
656
657
658-spec tcpip_tunnel_to_server(ConnectionRef,
659                             ListenHost, ListenPort,
660                             ConnectToHost, ConnectToPort,
661                             Timeout) ->
662                                  {ok,TrueListenPort} | {error, term()} when
663      ConnectionRef :: connection_ref(),
664      ListenHost :: host(),
665      ListenPort :: inet:port_number(),
666      ConnectToHost :: host(),
667      ConnectToPort :: inet:port_number(),
668      Timeout :: timeout(),
669      TrueListenPort :: inet:port_number().
670
671tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost0, ConnectToPort, Timeout) ->
672    SockOpts = [],
673    try
674        list_to_binary(
675          case mangle_connect_address(ConnectToHost0,SockOpts) of
676              IP when is_tuple(IP) -> inet_parse:ntoa(IP);
677              _ when is_list(ConnectToHost0) -> ConnectToHost0
678          end)
679    of
680        ConnectToHost ->
681            ssh_connection_handler:handle_direct_tcpip(ConnectionHandler,
682                                                       mangle_tunnel_address(ListenHost), ListenPort,
683                                                       ConnectToHost, ConnectToPort,
684                                                       Timeout)
685    catch
686        _:_ ->
687            {error, bad_connect_to_address}
688    end.
689
690%%--------------------------------------------------------------------
691%% Ask remote server to listen to ListenHost:ListenPort.  When someone
692%% connects that address, connect to ConnectToHost:ConnectToPort from
693%% the client.
694%%--------------------------------------------------------------------
695-spec tcpip_tunnel_from_server(ConnectionRef,
696                               ListenHost, ListenPort,
697                               ConnectToHost, ConnectToPort
698                              ) ->
699                                    {ok,TrueListenPort} | {error, term()} when
700      ConnectionRef :: connection_ref(),
701      ListenHost :: host(),
702      ListenPort :: inet:port_number(),
703      ConnectToHost :: host(),
704      ConnectToPort :: inet:port_number(),
705      TrueListenPort :: inet:port_number().
706
707tcpip_tunnel_from_server(ConnectionRef, ListenHost, ListenPort, ConnectToHost, ConnectToPort) ->
708    tcpip_tunnel_from_server(ConnectionRef, ListenHost, ListenPort, ConnectToHost, ConnectToPort, infinity).
709
710-spec tcpip_tunnel_from_server(ConnectionRef,
711                               ListenHost, ListenPort,
712                               ConnectToHost, ConnectToPort,
713                               Timeout) ->
714                                    {ok,TrueListenPort} | {error, term()} when
715      ConnectionRef :: connection_ref(),
716      ListenHost :: host(),
717      ListenPort :: inet:port_number(),
718      ConnectToHost :: host(),
719      ConnectToPort :: inet:port_number(),
720      Timeout :: timeout(),
721      TrueListenPort :: inet:port_number().
722
723tcpip_tunnel_from_server(ConnectionRef, ListenHost0, ListenPort, ConnectToHost0, ConnectToPort, Timeout) ->
724    SockOpts = [],
725    ListenHost = mangle_tunnel_address(ListenHost0),
726    ConnectToHost = mangle_connect_address(ConnectToHost0, SockOpts),
727    case ssh_connection_handler:global_request(ConnectionRef, "tcpip-forward", true,
728                                               {ListenHost,ListenPort,ConnectToHost,ConnectToPort},
729                                               Timeout) of
730        {success,<<>>} ->
731            {ok, ListenPort};
732        {success,<<TruePort:32/unsigned-integer>>} when ListenPort==0 ->
733            {ok, TruePort};
734        {success,_} = Res ->
735            {error, {bad_result,Res}};
736        {failure,<<>>} ->
737            {error,not_accepted};
738        {failure,Error} ->
739            {error,Error};
740        Other ->
741            Other
742    end.
743
744%%--------------------------------------------------------------------
745%% In move from public_key
746%%--------------------------------------------------------------------
747-spec hostkey_fingerprint(public_key:public_key()) -> string().
748
749hostkey_fingerprint(Key) ->
750    sshfp_string(md5, ssh_message:ssh2_pubkey_encode(Key) ).
751
752-spec hostkey_fingerprint(TypeOrTypes, Key) -> StringOrString
753                                                   when
754      TypeOrTypes :: public_key:digest_type() | [public_key:digest_type()],
755      Key :: public_key:public_key(),
756      StringOrString :: string() | [string()] .
757
758hostkey_fingerprint(HashAlgs, Key) when is_list(HashAlgs) ->
759    EncKey = ssh_message:ssh2_pubkey_encode(Key),
760    [sshfp_full_string(HashAlg,EncKey) || HashAlg <- HashAlgs];
761hostkey_fingerprint(HashAlg, Key) when is_atom(HashAlg) ->
762    EncKey = ssh_message:ssh2_pubkey_encode(Key),
763    sshfp_full_string(HashAlg, EncKey).
764
765
766sshfp_string(HashAlg, EncodedKey) ->
767    %% Other HashAlgs than md5 will be printed with
768    %% other formats than hextstr by
769    %%    ssh-keygen -E <alg> -lf <file>
770    fp_fmt(sshfp_fmt(HashAlg), crypto:hash(HashAlg, EncodedKey)).
771
772sshfp_full_string(HashAlg, EncKey) ->
773    lists:concat([sshfp_alg_name(HashAlg),
774		  [$: | sshfp_string(HashAlg, EncKey)]
775		 ]).
776
777sshfp_alg_name(sha) -> "SHA1";
778sshfp_alg_name(Alg) -> string:to_upper(atom_to_list(Alg)).
779
780sshfp_fmt(md5) -> hexstr;
781sshfp_fmt(_) -> b64.
782
783fp_fmt(hexstr, Bin) ->
784    lists:flatten(string:join([io_lib:format("~2.16.0b",[C1]) || <<C1>> <= Bin], ":"));
785fp_fmt(b64, Bin) ->
786    %% This function clause *seems* to be
787    %%    [C || C<-base64:encode_to_string(Bin), C =/= $=]
788    %% but I am not sure. Must be checked.
789    B64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
790    BitsInLast = 8*size(Bin) rem 6,
791    Padding = (6-BitsInLast) rem 6, % Want BitsInLast = [1:5] to map to padding [5:1] and 0 -> 0
792    [lists:nth(C+1,B64Chars) || <<C:6>> <= <<Bin/binary,0:Padding>> ].
793
794%%--------------------------------------------------------------------
795%%% Internal functions
796%%--------------------------------------------------------------------
797%% The handle_daemon_args/2 function basically only sets the ip-option in Opts
798%% so that it is correctly set when opening the listening socket.
799
800handle_daemon_args(any, Opts) ->
801    case proplists:get_value(ip, Opts) of
802        undefined -> {any, Opts};
803        IP -> {IP, Opts}
804    end;
805
806handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) ; IPaddr == loopback ->
807    case proplists:get_value(ip, Opts) of
808        undefined -> {IPaddr, [{ip,IPaddr}|Opts]};
809        IPaddr -> {IPaddr, Opts};
810        IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility
811    end.
812
813%%%----------------------------------------------------------------
814valid_socket_to_use(Socket, {tcp,_,_}) ->
815    %% Is this tcp-socket a valid socket?
816    try {is_tcp_socket(Socket),
817         {ok,[{active,false}]} == inet:getopts(Socket, [active])
818        }
819    of
820        {true,  true} -> ok;
821        {true, false} -> {error, not_passive_mode};
822        _ ->             {error, not_tcp_socket}
823    catch
824        _:_ ->           {error, bad_socket}
825    end;
826
827valid_socket_to_use(_, {L4,_,_}) ->
828    {error, {unsupported,L4}}.
829
830
831is_tcp_socket(Socket) ->
832    case inet:getopts(Socket, [delay_send]) of
833        {ok,[_]} -> true;
834        _ -> false
835    end.
836
837%%%----------------------------------------------------------------
838maybe_open_listen_socket(Host, Port, Options) ->
839    Opened =
840        case ?GET_SOCKET_OPT(fd, Options) of
841            undefined when Port == 0 ->
842                ssh_acceptor:listen(0, Options);
843            Fd when is_integer(Fd) ->
844                %% Do gen_tcp:listen with the option {fd,Fd}:
845                ssh_acceptor:listen(0, Options);
846            undefined ->
847                open_later
848        end,
849    case Opened of
850        {ok,LSock} ->
851            {ok,{LHost,LPort}} = inet:sockname(LSock),
852            {LHost, LPort, LSock, ?PUT_INTERNAL_OPT({lsocket,{LSock,self()}}, Options)};
853        open_later ->
854            {Host, Port, undefined, Options};
855        Others ->
856            Others
857    end.
858
859%%%----------------------------------------------------------------
860close_listen_socket(ListenSocket, Options) ->
861    try
862        {_, Callback, _} = ?GET_OPT(transport, Options),
863        Callback:close(ListenSocket)
864    catch
865        _C:_E -> ok
866    end.
867
868controlling_process(ListenSocket, ReqPid, Options) ->
869    {_, Callback, _} = ?GET_OPT(transport, Options),
870    Callback:controlling_process(ListenSocket, ReqPid).
871
872transport_connect(Host, Port, SocketOpts, Options) ->
873    {_, Callback, _} = ?GET_OPT(transport, Options),
874    Callback:connect(Host, Port, SocketOpts, ?GET_OPT(connect_timeout,Options)).
875
876%%%----------------------------------------------------------------
877is_host(X, Opts) ->
878    try is_host1(mangle_connect_address(X, Opts))
879    catch
880        _:_ -> false
881    end.
882
883
884is_host1(L) when is_list(L) -> true; %% "string()"
885is_host1(T) when is_tuple(T), size(T)==4 -> lists:all(fun(I) -> 0=<I andalso I=<255 end,
886                                                      tuple_to_list(T));
887is_host1(T) when is_tuple(T), size(T)==16 -> lists:all(fun(I) -> 0=<I andalso I=<65535 end,
888                                                       tuple_to_list(T));
889is_host1(loopback) -> true.
890
891%%%----------------------------------------------------------------
892mangle_connect_address(A,  #{socket_options := SockOpts}) ->
893    mangle_connect_address(A, SockOpts);
894mangle_connect_address(A, SockOpts) ->
895    mangle_connect_address1(A, proplists:get_value(inet6,SockOpts,false)).
896
897loopback(true) -> {0,0,0,0,0,0,0,1};
898loopback(false) ->      {127,0,0,1}.
899
900mangle_connect_address1( loopback,     V6flg) -> loopback(V6flg);
901mangle_connect_address1(      any,     V6flg) -> loopback(V6flg);
902mangle_connect_address1({0,0,0,0},         _) -> loopback(false);
903mangle_connect_address1({0,0,0,0,0,0,0,0}, _) -> loopback(true);
904mangle_connect_address1(       IP,     _) when is_tuple(IP) -> IP;
905mangle_connect_address1(A, _) ->
906    case catch inet:parse_address(A) of
907        {ok,         {0,0,0,0}} -> loopback(false);
908        {ok, {0,0,0,0,0,0,0,0}} -> loopback(true);
909        _ -> A
910    end.
911
912%%%----------------------------------------------------------------
913mangle_tunnel_address(any) -> <<"">>;
914mangle_tunnel_address(loopback) -> <<"localhost">>;
915mangle_tunnel_address({0,0,0,0}) -> <<"">>;
916mangle_tunnel_address({0,0,0,0,0,0,0,0}) -> <<"">>;
917mangle_tunnel_address(IP) when is_tuple(IP) -> list_to_binary(inet_parse:ntoa(IP));
918mangle_tunnel_address(A) when is_atom(A) -> mangle_tunnel_address(atom_to_list(A));
919mangle_tunnel_address(X) when is_list(X) -> case catch inet:parse_address(X) of
920                                     {ok, {0,0,0,0}} -> <<"">>;
921                                     {ok, {0,0,0,0,0,0,0,0}} -> <<"">>;
922                                     _ -> list_to_binary(X)
923                                 end.
924
925
926%%%################################################################
927%%%#
928%%%# Tracing
929%%%#
930
931ssh_dbg_trace_points() -> [tcp].
932
933ssh_dbg_flags(tcp) -> [c].
934
935ssh_dbg_on(tcp) -> dbg:tpl(?MODULE, controlling_process, 3, x),
936                   dbg:tpl(?MODULE, transport_connect, 4, x),
937                   dbg:tpl(?MODULE, close_listen_socket, 2, x).
938
939ssh_dbg_off(tcp) ->dbg:ctpl(?MODULE, controlling_process, 3),
940                   dbg:ctpl(?MODULE, transport_connect, 4),
941                   dbg:ctpl(?MODULE, close_listen_socket, 2).
942
943ssh_dbg_format(tcp, {call, {?MODULE,controlling_process, [ListenSocket, ReqPid, _Opts]}}) ->
944    ["TCP socket transferred to\n",
945     io_lib:format("Sock: ~p~n"
946                   "ToPid: ~p~n", [ListenSocket, ReqPid])
947    ];
948ssh_dbg_format(tcp, {return_from, {?MODULE,controlling_process,3}, _Result}) ->
949    skip;
950
951ssh_dbg_format(tcp, {call, {?MODULE,close_listen_socket, [ListenSocket, _Opts]}}) ->
952    ["TCP socket listening closed\n",
953     io_lib:format("Sock: ~p~n", [ListenSocket])
954    ];
955ssh_dbg_format(tcp, {return_from, {?MODULE,close_listen_socket,2}, _Result}) ->
956    skip.
957
958
959ssh_dbg_format(tcp, {call, {?MODULE,transport_connect, [Host,Port,SockOpts,_Opts]}}, Stack) ->
960    {skip, [{transport_connect,Host,Port,SockOpts}|Stack]};
961ssh_dbg_format(tcp, {return_from, {?MODULE,transport_connect,4}, {ok,Sock}},
962               [{transport_connect,Host,Port,SockOpts}|Stack]) ->
963    {["TCP connected to\n",
964      io_lib:format("Host: ~p~n"
965                    "Port: ~p~n"
966                    "SockOpts: ~p~n"
967                    "Socket: ~p~n", [Host,Port,SockOpts,Sock])
968     ],
969     Stack};
970ssh_dbg_format(tcp, {return_from, {?MODULE,transport_connect,4}, Result},
971               [{transport_connect,Host,Port,SockOpts}|Stack]) ->
972    {["TCP connected FAILED to\n",
973      io_lib:format("Host: ~p~n"
974                    "Port: ~p~n"
975                    "SockOpts: ~p~n"
976                    "Result: ~p~n", [Host,Port,SockOpts,Result])
977     ],
978     Stack}.
979