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%%% "Deprecated" types export:
49-export_type([ssh_daemon_ref/0, ssh_connection_ref/0, ssh_channel_id/0]).
50-opaque ssh_daemon_ref()     :: daemon_ref().
51-opaque ssh_connection_ref() :: connection_ref().
52-opaque ssh_channel_id()     :: channel_id().
53
54
55%%% Type exports
56-export_type([daemon_ref/0,
57              connection_ref/0,
58	      channel_id/0,
59              client_options/0, client_option/0,
60              daemon_options/0, daemon_option/0,
61              common_options/0,
62              role/0,
63              subsystem_spec/0,
64              algs_list/0,
65              double_algs/1,
66              modify_algs_list/0,
67              alg_entry/0,
68              kex_alg/0,
69              pubkey_alg/0,
70              cipher_alg/0,
71              mac_alg/0,
72              compression_alg/0,
73              host/0,
74              open_socket/0,
75              ip_port/0
76	     ]).
77
78
79-opaque daemon_ref()         :: pid() .
80-opaque channel_id()     :: non_neg_integer().
81-type connection_ref()       :: pid().  % should be -opaque, but that gives problems
82
83%%--------------------------------------------------------------------
84%% Description: Starts the ssh application. Default type
85%% is temporary. see application(3)
86%%--------------------------------------------------------------------
87-spec start() -> ok | {error, term()}.
88
89start() ->
90    start(temporary).
91
92-spec start(Type) -> ok | {error, term()} when
93      Type :: permanent | transient | temporary .
94
95start(Type) ->
96    case application:ensure_all_started(ssh, Type) of
97        {ok, _} ->
98            %% Clear cached default_algorithms (if exists) ...
99            ssh_transport:clear_default_algorithms_env(),
100            %% ... and rebuld them taking configure options in account
101            ssh_transport:default_algorithms(),
102            ok;
103        Other ->
104            Other
105    end.
106
107%%--------------------------------------------------------------------
108%% Description: Stops the ssh application.
109%%--------------------------------------------------------------------
110-spec stop() -> ok | {error, term()}.
111
112stop() ->
113    application:stop(ssh).
114
115%%--------------------------------------------------------------------
116%% Description: Starts an ssh connection.
117%%--------------------------------------------------------------------
118-spec connect(OpenTcpSocket, Options) -> {ok,connection_ref()} | {error,term()} when
119      OpenTcpSocket :: open_socket(),
120      Options :: client_options().
121
122connect(OpenTcpSocket, Options) when is_port(OpenTcpSocket),
123                                     is_list(Options) ->
124    connect(OpenTcpSocket, Options, infinity).
125
126
127-spec connect(open_socket(), client_options(), timeout()) ->
128                     {ok,connection_ref()} | {error,term()}
129           ; (host(), inet:port_number(), client_options()) ->
130                     {ok,connection_ref()} | {error,term()}.
131
132connect(Socket, UserOptions, NegotiationTimeout) when is_port(Socket),
133                                                      is_list(UserOptions) ->
134    case ssh_options:handle_options(client, UserOptions) of
135	{error, Error} ->
136	    {error, Error};
137	Options ->
138           case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
139               ok ->
140                   connect_socket(Socket,
141                                  ?PUT_INTERNAL_OPT({connected_socket,Socket}, Options),
142                                  NegotiationTimeout);
143               {error,SockError} ->
144                   {error,SockError}
145           end
146        end;
147
148connect(Host, Port, Options) when is_integer(Port),
149                                  Port>0,
150                                  is_list(Options) ->
151    Timeout = proplists:get_value(connect_timeout, Options, infinity),
152    connect(Host, Port, Options, Timeout).
153
154
155-spec connect(Host, Port, Options, NegotiationTimeout) -> {ok,connection_ref()} | {error,term()} when
156      Host :: host(),
157      Port :: inet:port_number(),
158      Options :: client_options(),
159      NegotiationTimeout :: timeout().
160
161connect(Host0, Port, UserOptions, NegotiationTimeout) when is_integer(Port),
162                                                           Port>0,
163                                                           is_list(UserOptions) ->
164    case ssh_options:handle_options(client, UserOptions) of
165	{error, _Reason} = Error ->
166	    Error;
167        Options ->
168	    {_, Transport, _} = TransportOpts = ?GET_OPT(transport, Options),
169	    ConnectionTimeout = ?GET_OPT(connect_timeout, Options),
170            SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)],
171            Host = mangle_connect_address(Host0, SocketOpts),
172	    try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of
173		{ok, Socket} ->
174                    connect_socket(Socket,
175                                   ?PUT_INTERNAL_OPT({host,Host}, Options),
176                                   NegotiationTimeout);
177		{error, Reason} ->
178		    {error, Reason}
179	    catch
180		exit:{function_clause, _F} ->
181		    {error, {options, {transport, TransportOpts}}};
182		exit:badarg ->
183		    {error, {options, {socket_options, SocketOpts}}}
184	    end
185    end.
186
187
188connect_socket(Socket, Options0, NegotiationTimeout) ->
189    {ok, {Host,Port}} = inet:sockname(Socket),
190    Profile = ?GET_OPT(profile, Options0),
191
192    {ok, {SystemSup, SubSysSup}} = sshc_sup:start_system_subsystem(Host, Port, Profile, Options0),
193
194    ConnectionSup = ssh_system_sup:connection_supervisor(SystemSup),
195    Opts = ?PUT_INTERNAL_OPT([{user_pid,self()},
196                              {supervisors, [{system_sup, SystemSup},
197                                             {subsystem_sup, SubSysSup},
198                                             {connection_sup, ConnectionSup}]}
199                             ], Options0),
200    ssh_connection_handler:start_connection(client, Socket, Opts, NegotiationTimeout).
201
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(Socket, UserOptions) when is_port(Socket) ->
280    try
281        #{} = Options = ssh_options:handle_options(server, UserOptions),
282
283        case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
284            ok ->
285                {ok, {IP,Port}} = inet:sockname(Socket),
286                finalize_start(IP, Port, ?GET_OPT(profile, Options),
287                               ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options),
288                               fun(Opts, DefaultResult) ->
289                                       try ssh_acceptor:handle_established_connection(
290                                             IP, Port, Opts, Socket)
291                                       of
292                                           {error,Error} ->
293                                               {error,Error};
294                                           _ ->
295                                               DefaultResult
296                                       catch
297                                           C:R ->
298                                               {error,{could_not_start_connection,{C,R}}}
299                                       end
300                               end);
301            {error,SockError} ->
302                {error,SockError}
303            end
304    catch
305        throw:bad_fd ->
306            {error,bad_fd};
307        throw:bad_socket ->
308            {error,bad_socket};
309        error:{badmatch,{error,Error}} ->
310            {error,Error};
311        error:Error ->
312            {error,Error};
313        _C:_E ->
314            {error,{cannot_start_daemon,_C,_E}}
315    end;
316
317daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 ->
318    daemon(any, Port, UserOptions).
319
320
321-spec daemon(any | inet:ip_address(), inet:port_number(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}
322           ;(socket, open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}
323            .
324
325daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535,
326                                        Host0 == any ; Host0 == loopback ; is_tuple(Host0) ->
327    try
328        {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0),
329        #{} = Options0 = ssh_options:handle_options(server, UserOptions),
330        {open_listen_socket(Host1, Port0, Options0), Options0}
331    of
332        {{{Host,Port}, ListenSocket}, Options1} ->
333            try
334                %% Now Host,Port is what to use for the supervisor to register its name,
335                %% and ListenSocket is for listening on connections. But it is still owned
336                %% by self()...
337                finalize_start(Host, Port, ?GET_OPT(profile, Options1),
338                               ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options1),
339                               fun(Opts, Result) ->
340                                       {_, Callback, _} = ?GET_OPT(transport, Opts),
341                                       receive
342                                           {request_control, ListenSocket, ReqPid} ->
343                                               ok = Callback:controlling_process(ListenSocket, ReqPid),
344                                               ReqPid ! {its_yours,ListenSocket},
345                                               Result
346                                       end
347                               end)
348            of
349                {error,Err} ->
350                    close_listen_socket(ListenSocket, Options1),
351                    {error,Err};
352                OK ->
353                    OK
354            catch
355                error:Error ->
356                    close_listen_socket(ListenSocket, Options1),
357                    error(Error);
358                exit:Exit ->
359                    close_listen_socket(ListenSocket, Options1),
360                    exit(Exit)
361            end
362    catch
363        throw:bad_fd ->
364            {error,bad_fd};
365        throw:bad_socket ->
366            {error,bad_socket};
367        error:{badmatch,{error,Error}} ->
368            {error,Error};
369        error:Error ->
370            {error,Error};
371        _C:_E ->
372            {error,{cannot_start_daemon,_C,_E}}
373    end;
374
375daemon(_, _, _) ->
376    {error, badarg}.
377
378%%--------------------------------------------------------------------
379-type daemon_info_tuple() ::
380        {port, inet:port_number()}
381      | {ip, inet:ip_address()}
382      | {profile, atom()}
383      | {options, daemon_options()}.
384
385-spec daemon_info(DaemonRef) -> {ok,InfoTupleList} | {error,bad_daemon_ref} when
386      DaemonRef :: daemon_ref(),
387      InfoTupleList :: [InfoTuple],
388      InfoTuple :: daemon_info_tuple().
389
390daemon_info(DaemonRef) ->
391    case catch ssh_system_sup:acceptor_supervisor(DaemonRef) of
392	AsupPid when is_pid(AsupPid) ->
393	    [{Host,Port,Profile}] =
394		[{Hst,Prt,Prf}
395                 || {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]}
396                        <- supervisor:which_children(AsupPid)],
397            IP =
398                case inet:parse_strict_address(Host) of
399                    {ok,IP0} -> IP0;
400                    _ -> Host
401                end,
402
403            Opts =
404                case ssh_system_sup:get_options(DaemonRef, Host, Port, Profile) of
405                    {ok, OptMap} ->
406                        lists:sort(
407                          maps:to_list(
408                            ssh_options:keep_set_options(
409                              server,
410                              ssh_options:keep_user_options(server,OptMap))));
411                    _ ->
412                        []
413                end,
414
415	    {ok, [{port,Port},
416                  {ip,IP},
417                  {profile,Profile},
418                  {options,Opts}
419                 ]};
420	_ ->
421	    {error,bad_daemon_ref}
422    end.
423
424-spec daemon_info(DaemonRef, ItemList|Item) ->  InfoTupleList|InfoTuple | {error,bad_daemon_ref} when
425      DaemonRef :: daemon_ref(),
426      ItemList :: [Item],
427      Item :: ip | port | profile | options,
428      InfoTupleList :: [InfoTuple],
429      InfoTuple :: daemon_info_tuple().
430
431daemon_info(DaemonRef, Key) when is_atom(Key) ->
432    case daemon_info(DaemonRef, [Key]) of
433        [{Key,Val}] -> {Key,Val};
434        Other -> Other
435    end;
436daemon_info(DaemonRef, Keys) ->
437    case daemon_info(DaemonRef) of
438        {ok,KVs} ->
439            [{Key,proplists:get_value(Key,KVs)} || Key <- Keys,
440                                                   lists:keymember(Key,1,KVs)];
441        _ ->
442            []
443    end.
444
445%%--------------------------------------------------------------------
446%% Description: Stops the listener, but leaves
447%% existing connections started by the listener up and running.
448%%--------------------------------------------------------------------
449-spec stop_listener(daemon_ref()) -> ok.
450
451stop_listener(SysSup) ->
452    ssh_system_sup:stop_listener(SysSup).
453
454
455-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok.
456
457stop_listener(Address, Port) ->
458    stop_listener(Address, Port, ?DEFAULT_PROFILE).
459
460
461-spec stop_listener(any|inet:ip_address(), inet:port_number(), term()) -> ok.
462
463stop_listener(any, Port, Profile) ->
464    map_ip(fun(IP) ->
465                   ssh_system_sup:stop_listener(IP, Port, Profile)
466           end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]);
467stop_listener(Address, Port, Profile) ->
468    map_ip(fun(IP) ->
469                   ssh_system_sup:stop_listener(IP, Port, Profile)
470           end, {address,Address}).
471
472%%--------------------------------------------------------------------
473%% Description: Stops the listener and all connections started by
474%% the listener.
475%%--------------------------------------------------------------------
476-spec stop_daemon(DaemonRef::daemon_ref()) -> ok.
477
478stop_daemon(SysSup) ->
479    ssh_system_sup:stop_system(server, SysSup).
480
481
482-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok.
483
484stop_daemon(Address, Port) ->
485    stop_daemon(Address, Port, ?DEFAULT_PROFILE).
486
487
488-spec stop_daemon(any|inet:ip_address(), inet:port_number(), atom()) -> ok.
489
490stop_daemon(any, Port, Profile) ->
491    map_ip(fun(IP) ->
492                   ssh_system_sup:stop_system(server, IP, Port, Profile)
493           end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]);
494stop_daemon(Address, Port, Profile) ->
495    map_ip(fun(IP) ->
496                   ssh_system_sup:stop_system(server, IP, Port, Profile)
497           end, {address,Address}).
498
499%%--------------------------------------------------------------------
500%% Description: Starts an interactive shell to an SSH server on the
501%% given <Host>. The function waits for user input,
502%% and will not return until the remote shell is ended.(e.g. on
503%% exit from the shell)
504%%--------------------------------------------------------------------
505-spec shell(open_socket() | host() | connection_ref()) ->  _.
506
507shell(Socket) when is_port(Socket) ->
508    shell(Socket, []);
509
510shell(ConnectionRef) when is_pid(ConnectionRef) ->
511    case ssh_connection:session_channel(ConnectionRef, infinity) of
512	{ok,ChannelId}  ->
513	    success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
514                                                [{pty_opts, [{echo,0}]}
515                                                ]),
516            success = ssh_connection:send_environment_vars(ConnectionRef, ChannelId,
517                                                           ["LANG", "LC_ALL"]),
518	    Args = [{channel_cb, ssh_shell},
519		    {init_args,[ConnectionRef, ChannelId]},
520		    {cm, ConnectionRef}, {channel_id, ChannelId}],
521	    {ok, State} = ssh_client_channel:init([Args]),
522            try
523                ssh_client_channel:enter_loop(State)
524            catch
525                exit:normal ->
526                    ok
527            end;
528	Error ->
529	    Error
530    end;
531
532shell(Host) ->
533    shell(Host, ?SSH_DEFAULT_PORT, []).
534
535
536-spec shell(open_socket() | host(), client_options()) ->  _.
537
538shell(Socket, Options) when is_port(Socket) ->
539    case connect(Socket, Options) of
540        {ok,ConnectionRef} ->
541            shell(ConnectionRef),
542            close(ConnectionRef);
543        Error ->
544            Error
545    end;
546
547shell(Host, Options) ->
548    shell(Host, ?SSH_DEFAULT_PORT, Options).
549
550
551-spec shell(Host, Port, Options) -> _ when
552      Host :: host(),
553      Port :: inet:port_number(),
554      Options :: client_options() .
555
556shell(Host, Port, Options) ->
557    case connect(Host, Port, Options) of
558        {ok,ConnectionRef} ->
559            shell(ConnectionRef),
560            close(ConnectionRef);
561        Error ->
562            Error
563    end.
564
565%%--------------------------------------------------------------------
566-spec default_algorithms() -> algs_list() .
567%%--------------------------------------------------------------------
568default_algorithms() ->
569    ssh_transport:default_algorithms().
570
571%%--------------------------------------------------------------------
572-spec chk_algos_opts(client_options()|daemon_options()) -> internal_options() | {error,term()}.
573%%--------------------------------------------------------------------
574chk_algos_opts(Opts) ->
575    case lists:foldl(
576           fun({preferred_algorithms,_}, Acc) -> Acc;
577              ({modify_algorithms,_}, Acc) -> Acc;
578              (KV, Acc) -> [KV|Acc]
579           end, [], Opts)
580    of
581        [] ->
582            case ssh_options:handle_options(client, Opts) of
583                M when is_map(M) ->
584                    maps:get(preferred_algorithms, M);
585                Others ->
586                    Others
587            end;
588        OtherOps ->
589            {error, {non_algo_opts_found,OtherOps}}
590    end.
591
592
593%%--------------------------------------------------------------------
594-spec set_sock_opts(ConnectionRef, SocketOptions) ->
595                           ok | {error, inet:posix()}  when
596      ConnectionRef :: connection_ref(),
597      SocketOptions :: [gen_tcp:option()] .
598%%--------------------------------------------------------------------
599set_sock_opts(ConnectionRef, SocketOptions) ->
600    ssh_connection_handler:set_sock_opts(ConnectionRef, SocketOptions).
601
602%%--------------------------------------------------------------------
603-spec get_sock_opts(ConnectionRef, SocketGetOptions) ->
604                           ok | {error, inet:posix()}  when
605      ConnectionRef :: connection_ref(),
606      SocketGetOptions :: [gen_tcp:option_name()] .
607%%--------------------------------------------------------------------
608get_sock_opts(ConnectionRef, SocketGetOptions) ->
609    ssh_connection_handler:get_sock_opts(ConnectionRef, SocketGetOptions).
610
611%%--------------------------------------------------------------------
612%% Ask local client to listen to ListenHost:ListenPort.  When someone
613%% connects that address, connect to ConnectToHost:ConnectToPort from
614%% the server.
615%%--------------------------------------------------------------------
616-spec tcpip_tunnel_to_server(ConnectionRef,
617                             ListenHost, ListenPort,
618                             ConnectToHost, ConnectToPort
619                          ) ->
620                                  {ok,TrueListenPort} | {error, term()} when
621      ConnectionRef :: connection_ref(),
622      ListenHost :: host(),
623      ListenPort :: inet:port_number(),
624      ConnectToHost :: host(),
625      ConnectToPort :: inet:port_number(),
626      TrueListenPort :: inet:port_number().
627
628tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort) ->
629    tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort, infinity).
630
631
632-spec tcpip_tunnel_to_server(ConnectionRef,
633                             ListenHost, ListenPort,
634                             ConnectToHost, ConnectToPort,
635                             Timeout) ->
636                                  {ok,TrueListenPort} | {error, term()} when
637      ConnectionRef :: connection_ref(),
638      ListenHost :: host(),
639      ListenPort :: inet:port_number(),
640      ConnectToHost :: host(),
641      ConnectToPort :: inet:port_number(),
642      Timeout :: timeout(),
643      TrueListenPort :: inet:port_number().
644
645tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost0, ConnectToPort, Timeout) ->
646    SockOpts = [],
647    try
648        list_to_binary(
649          case mangle_connect_address(ConnectToHost0,SockOpts) of
650              IP when is_tuple(IP) -> inet_parse:ntoa(IP);
651              _ when is_list(ConnectToHost0) -> ConnectToHost0
652          end)
653    of
654        ConnectToHost ->
655            ssh_connection_handler:handle_direct_tcpip(ConnectionHandler,
656                                                       mangle_tunnel_address(ListenHost), ListenPort,
657                                                       ConnectToHost, ConnectToPort,
658                                                       Timeout)
659    catch
660        _:_ ->
661            {error, bad_connect_to_address}
662    end.
663
664%%--------------------------------------------------------------------
665%% Ask remote server to listen to ListenHost:ListenPort.  When someone
666%% connects that address, connect to ConnectToHost:ConnectToPort from
667%% the client.
668%%--------------------------------------------------------------------
669-spec tcpip_tunnel_from_server(ConnectionRef,
670                               ListenHost, ListenPort,
671                               ConnectToHost, ConnectToPort
672                              ) ->
673                                    {ok,TrueListenPort} | {error, term()} when
674      ConnectionRef :: connection_ref(),
675      ListenHost :: host(),
676      ListenPort :: inet:port_number(),
677      ConnectToHost :: host(),
678      ConnectToPort :: inet:port_number(),
679      TrueListenPort :: inet:port_number().
680
681tcpip_tunnel_from_server(ConnectionRef, ListenHost, ListenPort, ConnectToHost, ConnectToPort) ->
682    tcpip_tunnel_from_server(ConnectionRef, ListenHost, ListenPort, ConnectToHost, ConnectToPort, infinity).
683
684-spec tcpip_tunnel_from_server(ConnectionRef,
685                               ListenHost, ListenPort,
686                               ConnectToHost, ConnectToPort,
687                               Timeout) ->
688                                    {ok,TrueListenPort} | {error, term()} when
689      ConnectionRef :: connection_ref(),
690      ListenHost :: host(),
691      ListenPort :: inet:port_number(),
692      ConnectToHost :: host(),
693      ConnectToPort :: inet:port_number(),
694      Timeout :: timeout(),
695      TrueListenPort :: inet:port_number().
696
697tcpip_tunnel_from_server(ConnectionRef, ListenHost0, ListenPort, ConnectToHost0, ConnectToPort, Timeout) ->
698    SockOpts = [],
699    ListenHost = mangle_tunnel_address(ListenHost0),
700    ConnectToHost = mangle_connect_address(ConnectToHost0, SockOpts),
701    case ssh_connection_handler:global_request(ConnectionRef, "tcpip-forward", true,
702                                               {ListenHost,ListenPort,ConnectToHost,ConnectToPort},
703                                               Timeout) of
704        {success,<<>>} ->
705            {ok, ListenPort};
706        {success,<<TruePort:32/unsigned-integer>>} when ListenPort==0 ->
707            {ok, TruePort};
708        {success,_} = Res ->
709            {error, {bad_result,Res}};
710        {failure,<<>>} ->
711            {error,not_accepted};
712        {failure,Error} ->
713            {error,Error};
714        Other ->
715            Other
716    end.
717
718%%--------------------------------------------------------------------
719%%% Internal functions
720%%--------------------------------------------------------------------
721%% The handle_daemon_args/2 function basically only sets the ip-option in Opts
722%% so that it is correctly set when opening the listening socket.
723
724handle_daemon_args(any, Opts) ->
725    case proplists:get_value(ip, Opts) of
726        undefined -> {any, Opts};
727        IP -> {IP, Opts}
728    end;
729
730handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) ; IPaddr == loopback ->
731    case proplists:get_value(ip, Opts) of
732        undefined -> {IPaddr, [{ip,IPaddr}|Opts]};
733        IPaddr -> {IPaddr, Opts};
734        IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility
735    end.
736
737%%%----------------------------------------------------------------
738valid_socket_to_use(Socket, {tcp,_,_}) ->
739    %% Is this tcp-socket a valid socket?
740    try {is_tcp_socket(Socket),
741         {ok,[{active,false}]} == inet:getopts(Socket, [active])
742        }
743    of
744        {true,  true} -> ok;
745        {true, false} -> {error, not_passive_mode};
746        _ ->             {error, not_tcp_socket}
747    catch
748        _:_ ->           {error, bad_socket}
749    end;
750
751valid_socket_to_use(_, {L4,_,_}) ->
752    {error, {unsupported,L4}}.
753
754
755is_tcp_socket(Socket) ->
756    case inet:getopts(Socket, [delay_send]) of
757        {ok,[_]} -> true;
758        _ -> false
759    end.
760
761%%%----------------------------------------------------------------
762open_listen_socket(_Host0, Port0, Options0) ->
763    {ok,LSock} =
764        case ?GET_SOCKET_OPT(fd, Options0) of
765            undefined ->
766                ssh_acceptor:listen(Port0, Options0);
767            Fd when is_integer(Fd) ->
768                %% Do gen_tcp:listen with the option {fd,Fd}:
769                ssh_acceptor:listen(0, Options0)
770        end,
771    {ok,{LHost,LPort}} = inet:sockname(LSock),
772    {{LHost,LPort}, LSock}.
773
774%%%----------------------------------------------------------------
775close_listen_socket(ListenSocket, Options) ->
776    try
777        {_, Callback, _} = ?GET_OPT(transport, Options),
778        Callback:close(ListenSocket)
779    catch
780        _C:_E -> ok
781    end.
782
783%%%----------------------------------------------------------------
784finalize_start(Host, Port, Profile, Options0, F) ->
785    try
786        %% throws error:Error if no usable hostkey is found
787        ssh_connection_handler:available_hkey_algorithms(server, Options0),
788
789        sshd_sup:start_child(Host, Port, Profile, Options0)
790    of
791        {error, {already_started, _}} ->
792            {error, eaddrinuse};
793        {error, Error} ->
794            {error, Error};
795        Result = {ok,_} ->
796            F(Options0, Result)
797    catch
798        error:{shutdown,Err} ->
799            {error,Err};
800        exit:{noproc, _} ->
801            {error, ssh_not_started}
802    end.
803
804%%%----------------------------------------------------------------
805map_ip(Fun, {address,IP}) when is_tuple(IP) ->
806    Fun(IP);
807map_ip(Fun, {address,Address}) ->
808    IPs = try {ok,#hostent{h_addr_list=IP0s}} = inet:gethostbyname(Address),
809               IP0s
810          catch
811              _:_ -> []
812          end,
813    map_ip(Fun, IPs);
814map_ip(Fun, IPs) ->
815    lists:map(Fun, IPs).
816
817%%%----------------------------------------------------------------
818mangle_connect_address(A, SockOpts) ->
819    mangle_connect_address1(A, proplists:get_value(inet6,SockOpts,false)).
820
821loopback(true) -> {0,0,0,0,0,0,0,1};
822loopback(false) ->      {127,0,0,1}.
823
824mangle_connect_address1( loopback,     V6flg) -> loopback(V6flg);
825mangle_connect_address1(      any,     V6flg) -> loopback(V6flg);
826mangle_connect_address1({0,0,0,0},         _) -> loopback(false);
827mangle_connect_address1({0,0,0,0,0,0,0,0}, _) -> loopback(true);
828mangle_connect_address1(       IP,     _) when is_tuple(IP) -> IP;
829mangle_connect_address1(A, _) ->
830    case catch inet:parse_address(A) of
831        {ok,         {0,0,0,0}} -> loopback(false);
832        {ok, {0,0,0,0,0,0,0,0}} -> loopback(true);
833        _ -> A
834    end.
835
836%%%----------------------------------------------------------------
837mangle_tunnel_address(any) -> <<"">>;
838mangle_tunnel_address(loopback) -> <<"localhost">>;
839mangle_tunnel_address({0,0,0,0}) -> <<"">>;
840mangle_tunnel_address({0,0,0,0,0,0,0,0}) -> <<"">>;
841mangle_tunnel_address(IP) when is_tuple(IP) -> list_to_binary(inet_parse:ntoa(IP));
842mangle_tunnel_address(A) when is_atom(A) -> mangle_tunnel_address(atom_to_list(A));
843mangle_tunnel_address(X) when is_list(X) -> case catch inet:parse_address(X) of
844                                     {ok, {0,0,0,0}} -> <<"">>;
845                                     {ok, {0,0,0,0,0,0,0,0}} -> <<"">>;
846                                     _ -> list_to_binary(X)
847                                 end.
848