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