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