1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2008-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%% Purpose: Handles an ssh connection, e.i. both the 23%% setup SSH Transport Layer Protocol (RFC 4253), Authentication 24%% Protocol (RFC 4252) and SSH connection Protocol (RFC 4255) 25%% Details of the different protocols are 26%% implemented in ssh_transport.erl, ssh_auth.erl and ssh_connection.erl 27%% ---------------------------------------------------------------------- 28 29-module(ssh_connection_handler). 30 31-behaviour(gen_statem). 32 33-include("ssh.hrl"). 34-include("ssh_transport.hrl"). 35-include("ssh_auth.hrl"). 36-include("ssh_connect.hrl"). 37 38%%==================================================================== 39%%% Exports 40%%==================================================================== 41 42%%% Start and stop 43-export([start_link/3, 44 stop/1 45 ]). 46 47%%% Internal application API 48-export([start_connection/4, 49 available_hkey_algorithms/2, 50 open_channel/6, 51 start_channel/5, 52 handle_direct_tcpip/6, 53 request/6, request/7, 54 reply_request/3, 55 global_request/5, 56 send/5, 57 send_eof/2, 58 store/3, 59 retrieve/2, 60 info/1, info/2, 61 connection_info/2, 62 channel_info/3, 63 adjust_window/3, close/2, 64 disconnect/4, 65 get_print_info/1, 66 set_sock_opts/2, get_sock_opts/2, 67 prohibited_sock_option/1 68 ]). 69 70-type connection_ref() :: ssh:connection_ref(). 71-type channel_id() :: ssh:channel_id(). 72 73%%% Behaviour callbacks 74-export([init/1, callback_mode/0, handle_event/4, terminate/3, 75 format_status/2, code_change/4]). 76 77%%% Exports not intended to be used :). They are used for spawning and tests 78-export([init_connection_handler/3, % proc_lib:spawn needs this 79 init_ssh_record/3, % Export of this internal function 80 % intended for low-level protocol test suites 81 renegotiate/1, alg/1 % Export intended for test cases 82 ]). 83 84-behaviour(ssh_dbg). 85-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]). 86 87 88-define(send_disconnect(Code, DetailedText, StateName, State), 89 send_disconnect(Code, DetailedText, ?MODULE, ?LINE, StateName, State)). 90 91-define(send_disconnect(Code, Reason, DetailedText, StateName, State), 92 send_disconnect(Code, Reason, DetailedText, ?MODULE, ?LINE, StateName, State)). 93 94-define(call_disconnectfun_and_log_cond(LogMsg, DetailedText, StateName, D), 95 call_disconnectfun_and_log_cond(LogMsg, DetailedText, ?MODULE, ?LINE, StateName, D)). 96 97%%==================================================================== 98%% Start / stop 99%%==================================================================== 100%%-------------------------------------------------------------------- 101-spec start_link(role(), 102 gen_tcp:socket(), 103 internal_options() 104 ) -> {ok, pid()}. 105%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106start_link(Role, Socket, Options) -> 107 {ok, proc_lib:spawn_opt(?MODULE, 108 init_connection_handler, 109 [Role, Socket, Options], 110 [link, {message_queue_data,off_heap}] 111 )}. 112 113 114%%-------------------------------------------------------------------- 115-spec stop(connection_ref() 116 ) -> ok | {error, term()}. 117%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118stop(ConnectionHandler)-> 119 case call(ConnectionHandler, stop) of 120 {error, closed} -> 121 ok; 122 Other -> 123 Other 124 end. 125 126%%==================================================================== 127%% Internal application API 128%%==================================================================== 129 130%%-------------------------------------------------------------------- 131-spec start_connection(role(), 132 gen_tcp:socket(), 133 internal_options(), 134 timeout() 135 ) -> {ok, connection_ref()} | {error, term()}. 136%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137start_connection(Role, Socket, Options, Timeout) -> 138 try 139 case Role of 140 client -> 141 ChildPid = start_the_connection_child(self(), Role, Socket, Options), 142 handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout); 143 server -> 144 case ?GET_OPT(parallel_login, Options) of 145 true -> 146 HandshakerPid = 147 spawn_link(fun() -> 148 receive 149 {do_handshake, Pid} -> 150 handshake(Pid, erlang:monitor(process,Pid), Timeout) 151 end 152 end), 153 ChildPid = start_the_connection_child(HandshakerPid, Role, Socket, Options), 154 HandshakerPid ! {do_handshake, ChildPid}; 155 false -> 156 ChildPid = start_the_connection_child(self(), Role, Socket, Options), 157 handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout) 158 end 159 end 160 catch 161 exit:{noproc, _} -> 162 {error, ssh_not_started}; 163 _:Error -> 164 {error, Error} 165 end. 166 167%%-------------------------------------------------------------------- 168%%% Some other module has decided to disconnect. 169 170-spec disconnect(Code::integer(), Details::iodata(), 171 Module::atom(), Line::integer()) -> no_return(). 172%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 174% Preferable called with the macro ?DISCONNECT 175 176disconnect(Code, DetailedText, Module, Line) -> 177 throw({keep_state_and_data, 178 [{next_event, internal, {send_disconnect, Code, DetailedText, Module, Line}}]}). 179 180%%-------------------------------------------------------------------- 181%%% Open a channel in the connection to the peer, that is, do the ssh 182%%% signalling with the peer. 183-spec open_channel(connection_ref(), 184 string(), 185 iodata(), 186 pos_integer(), 187 pos_integer(), 188 timeout() 189 ) -> {open, channel_id()} | {error, term()}. 190 191%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192open_channel(ConnectionHandler, 193 ChannelType, ChannelSpecificData, InitialWindowSize, MaxPacketSize, 194 Timeout) -> 195 call(ConnectionHandler, 196 {open, 197 self(), 198 ChannelType, InitialWindowSize, MaxPacketSize, ChannelSpecificData, 199 Timeout}). 200 201%%-------------------------------------------------------------------- 202%%% Start a channel handling process in the superviser tree 203-spec start_channel(connection_ref(), atom(), channel_id(), list(), term()) -> 204 {ok, pid()} | {error, term()}. 205 206%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207start_channel(ConnectionHandler, CallbackModule, ChannelId, Args, Exec) -> 208 {ok, {SubSysSup,Role,Opts}} = call(ConnectionHandler, get_misc), 209 ssh_subsystem_sup:start_channel(Role, SubSysSup, 210 ConnectionHandler, CallbackModule, ChannelId, 211 Args, Exec, Opts). 212 213%%-------------------------------------------------------------------- 214handle_direct_tcpip(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort, Timeout) -> 215 call(ConnectionHandler, {handle_direct_tcpip, ListenHost, ListenPort, ConnectToHost, ConnectToPort, Timeout}). 216 217%%-------------------------------------------------------------------- 218-spec request(connection_ref(), 219 pid(), 220 channel_id(), 221 string(), 222 boolean(), 223 iodata(), 224 timeout() 225 ) -> success | failure | ok | {error,timeout}. 226 227-spec request(connection_ref(), 228 channel_id(), 229 string(), 230 boolean(), 231 iodata(), 232 timeout() 233 ) -> success | failure | ok | {error,timeout}. 234%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235request(ConnectionHandler, ChannelPid, ChannelId, Type, true, Data, Timeout) -> 236 call(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data, Timeout}); 237request(ConnectionHandler, ChannelPid, ChannelId, Type, false, Data, _) -> 238 cast(ConnectionHandler, {request, ChannelPid, ChannelId, Type, Data}). 239 240request(ConnectionHandler, ChannelId, Type, true, Data, Timeout) -> 241 call(ConnectionHandler, {request, ChannelId, Type, Data, Timeout}); 242request(ConnectionHandler, ChannelId, Type, false, Data, _) -> 243 cast(ConnectionHandler, {request, ChannelId, Type, Data}). 244 245%%-------------------------------------------------------------------- 246-spec reply_request(connection_ref(), 247 success | failure, 248 channel_id() 249 ) -> ok. 250 251%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252reply_request(ConnectionHandler, Status, ChannelId) -> 253 cast(ConnectionHandler, {reply_request, Status, ChannelId}). 254 255%%-------------------------------------------------------------------- 256global_request(ConnectionHandler, Type, true, Data, Timeout) -> 257 call(ConnectionHandler, {global_request, Type, Data, Timeout}); 258global_request(ConnectionHandler, Type, false, Data, _) -> 259 cast(ConnectionHandler, {global_request, Type, Data}). 260 261%%-------------------------------------------------------------------- 262-spec send(connection_ref(), 263 channel_id(), 264 non_neg_integer(), 265 iodata(), 266 timeout() 267 ) -> ok | {error, timeout|closed}. 268%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269send(ConnectionHandler, ChannelId, Type, Data, Timeout) -> 270 call(ConnectionHandler, {data, ChannelId, Type, Data, Timeout}). 271 272%%-------------------------------------------------------------------- 273-spec send_eof(connection_ref(), 274 channel_id() 275 ) -> ok | {error,closed}. 276%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277send_eof(ConnectionHandler, ChannelId) -> 278 call(ConnectionHandler, {eof, ChannelId}). 279 280%%-------------------------------------------------------------------- 281-spec info(connection_ref() 282 ) -> {ok, [#channel{}]} . 283 284-spec info(connection_ref(), 285 pid() | all 286 ) -> {ok, [#channel{}]} . 287%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288info(ConnectionHandler) -> 289 info(ConnectionHandler, all). 290 291info(ConnectionHandler, ChannelProcess) -> 292 call(ConnectionHandler, {info, ChannelProcess}). 293 294%%-------------------------------------------------------------------- 295-type local_sock_info() :: {inet:ip_address(), non_neg_integer()} | string(). 296-type peer_sock_info() :: {inet:ip_address(), non_neg_integer()} | string(). 297-type state_info() :: iolist(). 298 299-spec get_print_info(connection_ref() 300 ) -> {{local_sock_info(), peer_sock_info()}, 301 state_info() 302 }. 303%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304get_print_info(ConnectionHandler) -> 305 call(ConnectionHandler, get_print_info, 1000). 306 307%%-------------------------------------------------------------------- 308connection_info(ConnectionHandler, []) -> 309 connection_info(ConnectionHandler, conn_info_keys()); 310connection_info(ConnectionHandler, Key) when is_atom(Key) -> 311 case connection_info(ConnectionHandler, [Key]) of 312 [{Key,Val}] -> {Key,Val}; 313 Other -> Other 314 end; 315connection_info(ConnectionHandler, Options) -> 316 call(ConnectionHandler, {connection_info, Options}). 317 318%%-------------------------------------------------------------------- 319-spec channel_info(connection_ref(), 320 channel_id(), 321 [atom()] 322 ) -> proplists:proplist(). 323%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324channel_info(ConnectionHandler, ChannelId, Options) -> 325 call(ConnectionHandler, {channel_info, ChannelId, Options}). 326 327%%-------------------------------------------------------------------- 328-spec adjust_window(connection_ref(), 329 channel_id(), 330 integer() 331 ) -> ok. 332%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333adjust_window(ConnectionHandler, Channel, Bytes) -> 334 cast(ConnectionHandler, {adjust_window, Channel, Bytes}). 335 336%%-------------------------------------------------------------------- 337-spec close(connection_ref(), 338 channel_id() 339 ) -> ok. 340%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341close(ConnectionHandler, ChannelId) -> 342 case call(ConnectionHandler, {close, ChannelId}) of 343 ok -> 344 ok; 345 {error, closed} -> 346 ok 347 end. 348 349 350%%-------------------------------------------------------------------- 351store(ConnectionHandler, Key, Value) -> 352 cast(ConnectionHandler, {store,Key,Value}). 353 354retrieve(#connection{options=Opts}, Key) -> 355 try ?GET_INTERNAL_OPT(Key, Opts) of 356 Value -> 357 {ok,Value} 358 catch 359 error:{badkey,Key} -> 360 undefined 361 end; 362retrieve(ConnectionHandler, Key) -> 363 call(ConnectionHandler, {retrieve,Key}). 364 365%%-------------------------------------------------------------------- 366%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367set_sock_opts(ConnectionRef, SocketOptions) -> 368 try lists:foldr(fun({Name,_Val}, Acc) -> 369 case prohibited_sock_option(Name) of 370 true -> [Name|Acc]; 371 false -> Acc 372 end 373 end, [], SocketOptions) 374 of 375 [] -> 376 call(ConnectionRef, {set_sock_opts,SocketOptions}); 377 Bad -> 378 {error, {not_allowed,Bad}} 379 catch 380 _:_ -> 381 {error, badarg} 382 end. 383 384prohibited_sock_option(active) -> true; 385prohibited_sock_option(deliver) -> true; 386prohibited_sock_option(mode) -> true; 387prohibited_sock_option(packet) -> true; 388prohibited_sock_option(_) -> false. 389 390%%-------------------------------------------------------------------- 391%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392get_sock_opts(ConnectionRef, SocketGetOptions) -> 393 call(ConnectionRef, {get_sock_opts,SocketGetOptions}). 394 395%%==================================================================== 396%% Test support 397%%==================================================================== 398%%-------------------------------------------------------------------- 399-spec renegotiate(connection_ref() 400 ) -> ok. 401%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402renegotiate(ConnectionHandler) -> 403 cast(ConnectionHandler, force_renegotiate). 404 405%%-------------------------------------------------------------------- 406alg(ConnectionHandler) -> 407 call(ConnectionHandler, get_alg). 408 409%%==================================================================== 410%% Internal process state 411%%==================================================================== 412-record(data, { 413 starter :: pid() 414 | undefined, 415 auth_user :: string() 416 | undefined, 417 connection_state :: #connection{} 418 | undefined, 419 latest_channel_id = 0 :: non_neg_integer() 420 | undefined, 421 transport_protocol :: atom() 422 | undefined, % ex: tcp 423 transport_cb :: atom() 424 | undefined, % ex: gen_tcp 425 transport_close_tag :: atom() 426 | undefined, % ex: tcp_closed 427 ssh_params :: #ssh{} 428 | undefined, 429 socket :: gen_tcp:socket() 430 | undefined, 431 decrypted_data_buffer = <<>> :: binary() 432 | undefined, 433 encrypted_data_buffer = <<>> :: binary() 434 | undefined, 435 aead_data = <<>> :: binary() 436 | undefined, 437 undecrypted_packet_length :: undefined | non_neg_integer(), 438 key_exchange_init_msg :: #ssh_msg_kexinit{} 439 | undefined, 440 last_size_rekey = 0 :: non_neg_integer(), 441 event_queue = [] :: list(), 442 inet_initial_recbuf_size :: pos_integer() 443 | undefined 444 }). 445 446%%==================================================================== 447%% Intitialisation 448%%==================================================================== 449%%-------------------------------------------------------------------- 450-spec init_connection_handler(role(), 451 gen_tcp:socket(), 452 internal_options() 453 ) -> no_return(). 454%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455init_connection_handler(Role, Socket, Opts) -> 456 case init([Role, Socket, Opts]) of 457 {ok, StartState, D} when Role == server -> 458 process_flag(trap_exit, true), 459 gen_statem:enter_loop(?MODULE, 460 [], %%[{debug,[trace,log,statistics,debug]} ], %% [] 461 StartState, 462 D); 463 464 {ok, StartState, D0=#data{connection_state=C}} when Role == client -> 465 process_flag(trap_exit, true), 466 Sups = ?GET_INTERNAL_OPT(supervisors, Opts), 467 D = D0#data{connection_state = 468 C#connection{system_supervisor = proplists:get_value(system_sup, Sups), 469 sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), 470 connection_supervisor = proplists:get_value(connection_sup, Sups) 471 }}, 472 gen_statem:enter_loop(?MODULE, 473 [], %%[{debug,[trace,log,statistics,debug]} ], %% [] 474 StartState, 475 D); 476 477 {stop, Error} -> 478 D = try 479 %% Only servers have supervisorts defined in Opts 480 Sups = ?GET_INTERNAL_OPT(supervisors, Opts), 481 #connection{system_supervisor = proplists:get_value(system_sup, Sups), 482 sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), 483 connection_supervisor = proplists:get_value(connection_sup, Sups) 484 } 485 of 486 C -> 487 #data{connection_state=C} 488 catch 489 _:_ -> 490 #data{connection_state=#connection{}} 491 end, 492 gen_statem:enter_loop(?MODULE, 493 [], 494 {init_error,Error}, 495 D#data{socket=Socket}) 496 end. 497 498 499 500init([Role,Socket,Opts]) -> 501 case inet:peername(Socket) of 502 {ok, PeerAddr} -> 503 {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), 504 C = #connection{channel_cache = ssh_client_channel:cache_create(), 505 channel_id_seed = 0, 506 requests = [], 507 options = Opts}, 508 D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), 509 connection_state = C, 510 socket = Socket, 511 transport_protocol = Protocol, 512 transport_cb = Callback, 513 transport_close_tag = CloseTag, 514 ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts) 515 }, 516 D = case Role of 517 client -> 518 D0; 519 server -> 520 Sups = ?GET_INTERNAL_OPT(supervisors, Opts), 521 D0#data{connection_state = 522 C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}), 523 exec = ?GET_OPT(exec, Opts), 524 system_supervisor = proplists:get_value(system_sup, Sups), 525 sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), 526 connection_supervisor = proplists:get_value(connection_sup, Sups) 527 }} 528 end, 529 {ok, {hello,Role}, D}; 530 531 {error,Error} -> 532 {stop, Error} 533 end. 534 535 536 537init_ssh_record(Role, Socket, Opts) -> 538 %% Export of this internal function is 539 %% intended for low-level protocol test suites 540 {ok,PeerAddr} = inet:peername(Socket), 541 init_ssh_record(Role, Socket, PeerAddr, Opts). 542 543init_ssh_record(Role, Socket, PeerAddr, Opts) -> 544 AuthMethods = ?GET_OPT(auth_methods, Opts), 545 S0 = #ssh{role = Role, 546 opts = Opts, 547 userauth_supported_methods = AuthMethods, 548 available_host_keys = available_hkey_algorithms(Role, Opts), 549 random_length_padding = ?GET_OPT(max_random_length_padding, Opts) 550 }, 551 552 {Vsn, Version} = ssh_transport:versions(Role, Opts), 553 LocalName = case inet:sockname(Socket) of 554 {ok,Local} -> Local; 555 _ -> undefined 556 end, 557 case Role of 558 client -> 559 PeerName = case ?GET_INTERNAL_OPT(host, Opts, element(1,PeerAddr)) of 560 PeerIP when is_tuple(PeerIP) -> 561 inet_parse:ntoa(PeerIP); 562 PeerName0 when is_atom(PeerName0) -> 563 atom_to_list(PeerName0); 564 PeerName0 when is_list(PeerName0) -> 565 PeerName0 566 end, 567 S1 = 568 S0#ssh{c_vsn = Vsn, 569 c_version = Version, 570 opts = ?PUT_INTERNAL_OPT({io_cb, case ?GET_OPT(user_interaction, Opts) of 571 true -> ssh_io; 572 false -> ssh_no_io 573 end}, 574 Opts), 575 userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts), 576 peer = {PeerName, PeerAddr}, 577 local = LocalName 578 }, 579 S1#ssh{userauth_pubkeys = [K || K <- ?GET_OPT(pref_public_key_algs, Opts), 580 is_usable_user_pubkey(K, S1) 581 ] 582 }; 583 584 server -> 585 S0#ssh{s_vsn = Vsn, 586 s_version = Version, 587 userauth_methods = string:tokens(AuthMethods, ","), 588 kb_tries_left = 3, 589 peer = {undefined, PeerAddr}, 590 local = LocalName 591 } 592 end. 593 594 595 596%%==================================================================== 597%% gen_statem callbacks 598%%==================================================================== 599%%-------------------------------------------------------------------- 600-type event_content() :: any(). 601 602-type renegotiate_flag() :: init | renegotiate. 603 604-type state_name() :: 605 {hello, role() } 606 | {kexinit, role(), renegotiate_flag()} 607 | {key_exchange, role(), renegotiate_flag()} 608 | {key_exchange_dh_gex_init, server, renegotiate_flag()} 609 | {key_exchange_dh_gex_reply, client, renegotiate_flag()} 610 | {new_keys, role(), renegotiate_flag()} 611 | {ext_info, role(), renegotiate_flag()} 612 | {service_request, role() } 613 | {userauth, role() } 614 | {userauth_keyboard_interactive, role() } 615 | {userauth_keyboard_interactive_extra, server } 616 | {userauth_keyboard_interactive_info_response, client } 617 | {connected, role() } 618 . 619 620%% The state names must fulfill some rules regarding 621%% where the role() and the renegotiate_flag() is placed: 622 623-spec role(state_name()) -> role(). 624role({_,Role}) -> Role; 625role({_,Role,_}) -> Role. 626 627-spec renegotiation(state_name()) -> boolean(). 628renegotiation({_,_,ReNeg}) -> ReNeg == renegotiate; 629renegotiation(_) -> false. 630 631 632-define(CONNECTED(StateName), 633 (element(1,StateName) == connected orelse 634 element(1,StateName) == ext_info ) ). 635 636-spec handle_event(gen_statem:event_type(), 637 event_content(), 638 state_name(), 639 #data{} 640 ) -> gen_statem:event_handler_result(state_name()) . 641 642-define(CONNECTION_MSG(Msg), 643 [{next_event, internal, prepare_next_packet}, 644 {next_event,internal,{conn_msg,Msg}}]). 645 646%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647 648callback_mode() -> 649 [handle_event_function, 650 state_enter]. 651 652 653handle_event(_, _Event, {init_error,Error}=StateName, D) -> 654 case Error of 655 enotconn -> 656 %% Handles the abnormal sequence: 657 %% SYN-> 658 %% <-SYNACK 659 %% ACK-> 660 %% RST-> 661 ?call_disconnectfun_and_log_cond("Protocol Error", 662 "TCP connenction to server was prematurely closed by the client", 663 StateName, D), 664 {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}}; 665 666 OtherError -> 667 {stop, {shutdown,{init,OtherError}}} 668 end; 669 670%%% ######## {hello, client|server} #### 671%% The very first event that is sent when the we are set as controlling process of Socket 672handle_event(_, socket_control, {hello,_}=StateName, #data{ssh_params = Ssh0} = D) -> 673 VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh0)), 674 send_bytes(VsnMsg, D), 675 case inet:getopts(Socket=D#data.socket, [recbuf]) of 676 {ok, [{recbuf,Size}]} -> 677 %% Set the socket to the hello text line handling mode: 678 inet:setopts(Socket, [{packet, line}, 679 {active, once}, 680 % Expecting the version string which might 681 % be max ?MAX_PROTO_VERSION bytes: 682 {recbuf, ?MAX_PROTO_VERSION}, 683 {nodelay,true}]), 684 Time = ?GET_OPT(hello_timeout, Ssh0#ssh.opts, infinity), 685 {keep_state, D#data{inet_initial_recbuf_size=Size}, [{state_timeout,Time,no_hello_received}] }; 686 687 Other -> 688 ?call_disconnectfun_and_log_cond("Option return", 689 io_lib:format("Unexpected getopts return:~n ~p",[Other]), 690 StateName, D), 691 {stop, {shutdown,{unexpected_getopts_return, Other}}} 692 end; 693 694handle_event(_, {info_line,Line}, {hello,Role}=StateName, D) -> 695 case Role of 696 client -> 697 %% The server may send info lines to the client before the version_exchange 698 %% RFC4253/4.2 699 inet:setopts(D#data.socket, [{active, once}]), 700 keep_state_and_data; 701 server -> 702 %% But the client may NOT send them to the server. Openssh answers with cleartext, 703 %% and so do we 704 send_bytes("Protocol mismatch.", D), 705 Msg = io_lib:format("Protocol mismatch in version exchange. Client sent info lines.~n~s", 706 [ssh_dbg:hex_dump(Line, 64)]), 707 ?call_disconnectfun_and_log_cond("Protocol mismatch.", Msg, StateName, D), 708 {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}} 709 end; 710 711handle_event(_, {version_exchange,Version}, {hello,Role}, D0) -> 712 {NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version), 713 case handle_version(NumVsn, StrVsn, D0#data.ssh_params) of 714 {ok, Ssh1} -> 715 %% Since the hello part is finnished correctly, we set the 716 %% socket to the packet handling mode (including recbuf size): 717 inet:setopts(D0#data.socket, [{packet,0}, 718 {mode,binary}, 719 {active, once}, 720 {recbuf, D0#data.inet_initial_recbuf_size}]), 721 {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), 722 send_bytes(SshPacket, D0), 723 {next_state, {kexinit,Role,init}, D0#data{ssh_params = Ssh, 724 key_exchange_init_msg = KeyInitMsg}}; 725 not_supported -> 726 {Shutdown, D} = 727 ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, 728 io_lib:format("Offending version is ~p",[string:chomp(Version)]), 729 {hello,Role}, 730 D0), 731 {stop, Shutdown, D} 732 end; 733 734handle_event(_, no_hello_received, {hello,_Role}=StateName, D0) -> 735 {Shutdown, D} = 736 ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, "No HELLO recieved", StateName, D0), 737 {stop, Shutdown, D}; 738 739%%% ######## {kexinit, client|server, init|renegotiate} #### 740 741handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, 742 D = #data{key_exchange_init_msg = OwnKex}) -> 743 Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload), 744 Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of 745 {ok, NextKexMsg, Ssh2} when Role==client -> 746 send_bytes(NextKexMsg, D), 747 Ssh2; 748 {ok, Ssh2} when Role==server -> 749 Ssh2 750 end, 751 {next_state, {key_exchange,Role,ReNeg}, D#data{ssh_params=Ssh}}; 752 753 754%%% ######## {key_exchange, client|server, init|renegotiate} #### 755 756%%%---- diffie-hellman 757handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> 758 {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), 759 send_bytes(KexdhReply, D), 760 {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), 761 send_bytes(NewKeys, D), 762 {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), 763 send_bytes(ExtInfo, D), 764 {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; 765 766handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> 767 {ok, NewKeys, Ssh1} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), 768 send_bytes(NewKeys, D), 769 {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), 770 send_bytes(ExtInfo, D), 771 {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; 772 773%%%---- diffie-hellman group exchange 774handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> 775 {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), 776 send_bytes(GexGroup, D), 777 Ssh = ssh_transport:parallell_gen_key(Ssh1), 778 {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; 779 780handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> 781 {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), 782 send_bytes(GexGroup, D), 783 Ssh = ssh_transport:parallell_gen_key(Ssh1), 784 {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; 785 786handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> 787 {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params), 788 send_bytes(KexGexInit, D), 789 {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}}; 790 791%%%---- elliptic curve diffie-hellman 792handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> 793 {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), 794 send_bytes(KexEcdhReply, D), 795 {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), 796 send_bytes(NewKeys, D), 797 {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), 798 send_bytes(ExtInfo, D), 799 {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; 800 801handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> 802 {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), 803 send_bytes(NewKeys, D), 804 {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), 805 send_bytes(ExtInfo, D), 806 {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; 807 808 809%%% ######## {key_exchange_dh_gex_init, server, init|renegotiate} #### 810 811handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> 812 {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), 813 send_bytes(KexGexReply, D), 814 {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1), 815 send_bytes(NewKeys, D), 816 {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2), 817 send_bytes(ExtInfo, D), 818 {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; 819 820 821%%% ######## {key_exchange_dh_gex_reply, client, init|renegotiate} #### 822 823handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> 824 {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), 825 send_bytes(NewKeys, D), 826 {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), 827 send_bytes(ExtInfo, D), 828 {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; 829 830 831%%% ######## {new_keys, client|server} #### 832 833%% First key exchange round: 834handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D0) -> 835 {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D0#data.ssh_params), 836 %% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1), 837 %% send_bytes(ExtInfo, D0), 838 {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1), 839 D = send_msg(MsgReq, D0#data{ssh_params = Ssh}), 840 {next_state, {ext_info,client,init}, D}; 841 842handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, D) -> 843 {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), 844 %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), 845 %% send_bytes(ExtInfo, D), 846 {next_state, {ext_info,server,init}, D#data{ssh_params=Ssh}}; 847 848%% Subsequent key exchange rounds (renegotiation): 849handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) -> 850 {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), 851 %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1), 852 %% send_bytes(ExtInfo, D), 853 {next_state, {ext_info,Role,renegotiate}, D#data{ssh_params=Ssh}}; 854 855 856%%% ######## {ext_info, client|server, init|renegotiate} #### 857 858handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,init}, D0) -> 859 D = handle_ssh_msg_ext_info(Msg, D0), 860 {next_state, {service_request,Role}, D}; 861 862handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,renegotiate}, D0) -> 863 D = handle_ssh_msg_ext_info(Msg, D0), 864 {next_state, {connected,Role}, D}; 865 866handle_event(_, #ssh_msg_newkeys{}=Msg, {ext_info,_Role,renegotiate}, D) -> 867 {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), 868 {keep_state, D#data{ssh_params = Ssh}}; 869 870 871handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) -> 872 %% If something else arrives, goto next state and handle the event in that one 873 {next_state, {service_request,Role}, D, [postpone]}; 874 875handle_event(internal, Msg, {ext_info,Role,_ReNegFlag}, D) when is_tuple(Msg) -> 876 %% If something else arrives, goto next state and handle the event in that one 877 {next_state, {connected,Role}, D, [postpone]}; 878 879%%% ######## {service_request, client|server} #### 880 881handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D0) -> 882 case ServiceName of 883 "ssh-userauth" -> 884 Ssh0 = #ssh{session_id=SessionId} = D0#data.ssh_params, 885 {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), 886 D = send_msg(Reply, D0#data{ssh_params = Ssh}), 887 {next_state, {userauth,server}, D}; 888 889 _ -> 890 {Shutdown, D} = 891 ?send_disconnect(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, 892 io_lib:format("Unknown service: ~p",[ServiceName]), 893 StateName, D0), 894 {stop, Shutdown, D} 895 end; 896 897handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, 898 #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = D0) -> 899 {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), 900 D = send_msg(Msg, D0#data{ssh_params = Ssh, 901 auth_user = Ssh#ssh.user 902 }), 903 {next_state, {userauth,client}, D}; 904 905 906%%% ######## {userauth, client|server} #### 907 908%%---- userauth request to server 909handle_event(_, 910 Msg = #ssh_msg_userauth_request{service = ServiceName, method = Method}, 911 StateName = {userauth,server}, 912 D0 = #data{ssh_params=Ssh0}) -> 913 914 case {ServiceName, Ssh0#ssh.service, Method} of 915 {"ssh-connection", "ssh-connection", "none"} -> 916 %% Probably the very first userauth_request but we deny unauthorized login 917 {not_authorized, _, {Reply,Ssh}} = 918 ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), 919 D = send_msg(Reply, D0#data{ssh_params = Ssh}), 920 {keep_state, D}; 921 922 {"ssh-connection", "ssh-connection", Method} -> 923 %% Userauth request with a method like "password" or so 924 case lists:member(Method, Ssh0#ssh.userauth_methods) of 925 true -> 926 %% Yepp! we support this method 927 case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of 928 {authorized, User, {Reply, Ssh1}} -> 929 D = #data{ssh_params=Ssh} = 930 send_msg(Reply, D0#data{ssh_params = Ssh1}), 931 D#data.starter ! ssh_connected, 932 connected_fun(User, Method, D), 933 {next_state, {connected,server}, 934 D#data{auth_user=User, 935 %% Note: authenticated=true MUST NOT be sent 936 %% before send_msg! 937 ssh_params = Ssh#ssh{authenticated = true}}}; 938 {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> 939 retry_fun(User, Reason, D0), 940 D = send_msg(Reply, D0#data{ssh_params = Ssh}), 941 {next_state, {userauth_keyboard_interactive,server}, D}; 942 {not_authorized, {User, Reason}, {Reply, Ssh}} -> 943 retry_fun(User, Reason, D0), 944 D = send_msg(Reply, D0#data{ssh_params = Ssh}), 945 {keep_state, D} 946 end; 947 false -> 948 %% No we do not support this method (=/= none) 949 %% At least one non-erlang client does like this. Retry as the next event 950 {keep_state_and_data, 951 [{next_event, internal, Msg#ssh_msg_userauth_request{method="none"}}] 952 } 953 end; 954 955 %% {"ssh-connection", Expected, Method} when Expected =/= ServiceName -> Do what? 956 %% {ServiceName, Expected, Method} when Expected =/= ServiceName -> Do what? 957 958 {ServiceName, _, _} when ServiceName =/= "ssh-connection" -> 959 {Shutdown, D} = 960 ?send_disconnect(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, 961 io_lib:format("Unknown service: ~p",[ServiceName]), 962 StateName, D0), 963 {stop, Shutdown, D} 964 end; 965 966%%---- userauth success to client 967handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) -> 968 %% FIXME: need new state to receive this msg! 969 D = handle_ssh_msg_ext_info(Msg, D0), 970 {keep_state, D}; 971 972handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) -> 973 ssh_auth:ssh_msg_userauth_result(success), 974 D#data.starter ! ssh_connected, 975 {next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}}; 976 977 978%%---- userauth failure response to client 979handle_event(_, #ssh_msg_userauth_failure{}, {userauth,client}=StateName, 980 #data{ssh_params = #ssh{userauth_methods = []}} = D0) -> 981 {Shutdown, D} = 982 ?send_disconnect(?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 983 io_lib:format("User auth failed for: ~p",[D0#data.auth_user]), 984 StateName, D0), 985 {stop, Shutdown, D}; 986handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client}, 987 D0 = #data{ssh_params = Ssh0}) -> 988 %% The prefered authentication method failed try next method 989 Ssh1 = case Ssh0#ssh.userauth_methods of 990 none -> 991 %% Server tells us which authentication methods that are allowed 992 Ssh0#ssh{userauth_methods = string:tokens(Methods, ",")}; 993 _ -> 994 %% We already know... 995 Ssh0 996 end, 997 case ssh_auth:userauth_request_msg(Ssh1) of 998 {send_disconnect, Code, Ssh} -> 999 {Shutdown, D} = 1000 ?send_disconnect(Code, 1001 io_lib:format("User auth failed for: ~p",[D0#data.auth_user]), 1002 StateName, D0#data{ssh_params = Ssh}), 1003 {stop, Shutdown, D}; 1004 {"keyboard-interactive", {Msg, Ssh}} -> 1005 D = send_msg(Msg, D0#data{ssh_params = Ssh}), 1006 {next_state, {userauth_keyboard_interactive,client}, D}; 1007 {_Method, {Msg, Ssh}} -> 1008 D = send_msg(Msg, D0#data{ssh_params = Ssh}), 1009 {keep_state, D} 1010 end; 1011 1012%%---- banner to client 1013handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) -> 1014 case D#data.ssh_params#ssh.userauth_quiet_mode of 1015 false -> io:format("~s", [Msg]); 1016 true -> ok 1017 end, 1018 keep_state_and_data; 1019 1020 1021%%% ######## {userauth_keyboard_interactive, client|server} 1022 1023handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client}, 1024 #data{ssh_params = Ssh0} = D0) -> 1025 case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of 1026 {ok, {Reply, Ssh}} -> 1027 D = send_msg(Reply, D0#data{ssh_params = Ssh}), 1028 {next_state, {userauth_keyboard_interactive_info_response,client}, D}; 1029 not_ok -> 1030 {next_state, {userauth,client}, D0, [postpone]} 1031 end; 1032 1033handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D0) -> 1034 case ssh_auth:handle_userauth_info_response(Msg, D0#data.ssh_params) of 1035 {authorized, User, {Reply, Ssh1}} -> 1036 D = #data{ssh_params=Ssh} = 1037 send_msg(Reply, D0#data{ssh_params = Ssh1}), 1038 D#data.starter ! ssh_connected, 1039 connected_fun(User, "keyboard-interactive", D), 1040 {next_state, {connected,server}, D#data{auth_user = User, 1041 %% Note: authenticated=true MUST NOT be sent 1042 %% before send_msg! 1043 ssh_params = Ssh#ssh{authenticated = true}}}; 1044 {not_authorized, {User, Reason}, {Reply, Ssh}} -> 1045 retry_fun(User, Reason, D0), 1046 D = send_msg(Reply, D0#data{ssh_params = Ssh}), 1047 {next_state, {userauth,server}, D}; 1048 1049 {authorized_but_one_more, _User, {Reply, Ssh}} -> 1050 D = send_msg(Reply, D0#data{ssh_params = Ssh}), 1051 {next_state, {userauth_keyboard_interactive_extra,server}, D} 1052 end; 1053 1054handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D0) -> 1055 {authorized, User, {Reply, Ssh1}} = 1056 ssh_auth:handle_userauth_info_response({extra,Msg}, D0#data.ssh_params), 1057 D = #data{ssh_params=Ssh} = 1058 send_msg(Reply, D0#data{ssh_params = Ssh1}), 1059 D#data.starter ! ssh_connected, 1060 connected_fun(User, "keyboard-interactive", D), 1061 {next_state, {connected,server}, D#data{auth_user = User, 1062 %% Note: authenticated=true MUST NOT be sent 1063 %% before send_msg! 1064 ssh_params = Ssh#ssh{authenticated = true}}}; 1065 1066handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, 1067 #data{ssh_params = Ssh0} = D0) -> 1068 Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Ssh0#ssh.userauth_preference, 1069 Method =/= "keyboard-interactive"], 1070 D = D0#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}, 1071 {next_state, {userauth,client}, D, [postpone]}; 1072 1073handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, 1074 #data{ssh_params = Ssh0} = D0) -> 1075 Opts = Ssh0#ssh.opts, 1076 D = case ?GET_OPT(password, Opts) of 1077 undefined -> 1078 D0; 1079 _ -> 1080 D0#data{ssh_params = 1081 Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} % FIXME:intermodule dependency 1082 end, 1083 {next_state, {userauth,client}, D, [postpone]}; 1084 1085handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth_keyboard_interactive_info_response, client}, D0) -> 1086 %% FIXME: need new state to receive this msg! 1087 D = handle_ssh_msg_ext_info(Msg, D0), 1088 {keep_state, D}; 1089 1090handle_event(_, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> 1091 {next_state, {userauth,client}, D, [postpone]}; 1092 1093handle_event(_, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) -> 1094 {next_state, {userauth_keyboard_interactive,client}, D, [postpone]}; 1095 1096 1097%%% ######## {connected, client|server} #### 1098 1099%% Skip ext_info messages in connected state (for example from OpenSSH >= 7.7) 1100handle_event(_, #ssh_msg_ext_info{}, {connected,_Role}, D) -> 1101 {keep_state, D}; 1102 1103handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) -> 1104 {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params), 1105 D = D0#data{ssh_params = Ssh, 1106 key_exchange_init_msg = KeyInitMsg}, 1107 send_bytes(SshPacket, D), 1108 {next_state, {kexinit,Role,renegotiate}, D, [postpone]}; 1109 1110handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) -> 1111 {disconnect, _, RepliesCon} = 1112 ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName), D0#data.ssh_params), 1113 {Actions,D} = send_replies(RepliesCon, D0), 1114 disconnect_fun("Received disconnect: "++Desc, D), 1115 {stop_and_reply, {shutdown,Desc}, Actions, D}; 1116 1117handle_event(_, #ssh_msg_ignore{}, _, _) -> 1118 keep_state_and_data; 1119 1120handle_event(_, #ssh_msg_unimplemented{}, _, _) -> 1121 keep_state_and_data; 1122 1123handle_event(_, #ssh_msg_debug{} = Msg, _, D) -> 1124 debug_fun(Msg, D), 1125 keep_state_and_data; 1126 1127handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User, 1128 connection_state = Connection0, 1129 event_queue = Qev0} = D0) -> 1130 Role = role(StateName), 1131 Rengotation = renegotiation(StateName), 1132 try ssh_connection:handle_msg(Msg, Connection0, Role, D0#data.ssh_params) of 1133 {disconnect, Reason0, RepliesConn} -> 1134 {Repls, D} = send_replies(RepliesConn, D0), 1135 case {Reason0,Role} of 1136 {{_, Reason}, client} when ((StateName =/= {connected,client}) 1137 and (not Rengotation)) -> 1138 User ! {self(), not_connected, Reason}; 1139 _ -> 1140 ok 1141 end, 1142 {stop_and_reply, {shutdown,normal}, Repls, D}; 1143 1144 {Replies, Connection} when is_list(Replies) -> 1145 {Repls, D} = 1146 case StateName of 1147 {connected,_} -> 1148 send_replies(Replies, D0#data{connection_state=Connection}); 1149 _ -> 1150 {ConnReplies, NonConnReplies} = lists:splitwith(fun not_connected_filter/1, Replies), 1151 send_replies(NonConnReplies, D0#data{event_queue = Qev0 ++ ConnReplies}) 1152 end, 1153 case {Msg, StateName} of 1154 {#ssh_msg_channel_close{}, {connected,_}} -> 1155 {keep_state, D, [cond_set_idle_timer(D)|Repls]}; 1156 {#ssh_msg_channel_success{}, _} -> 1157 update_inet_buffers(D#data.socket), 1158 {keep_state, D, Repls}; 1159 _ -> 1160 {keep_state, D, Repls} 1161 end 1162 1163 catch 1164 Class:Error -> 1165 {Repls, D1} = send_replies(ssh_connection:handle_stop(Connection0), D0), 1166 {Shutdown, D} = ?send_disconnect(?SSH_DISCONNECT_BY_APPLICATION, 1167 io_lib:format("Internal error: ~p:~p",[Class,Error]), 1168 StateName, D1), 1169 {stop_and_reply, Shutdown, Repls, D} 1170 end; 1171 1172 1173handle_event(enter, OldState, {connected,_}=NewState, D) -> 1174 %% Entering the state where re-negotiation is possible 1175 init_renegotiate_timers(OldState, NewState, D); 1176 1177handle_event(enter, OldState, {ext_info,_,renegotiate}=NewState, D) -> 1178 %% Could be hanging in exit_info state if nothing else arrives 1179 init_renegotiate_timers(OldState, NewState, D); 1180 1181handle_event(enter, {connected,_}=OldState, NewState, D) -> 1182 %% Exiting the state where re-negotiation is possible 1183 pause_renegotiate_timers(OldState, NewState, D); 1184 1185handle_event(cast, force_renegotiate, StateName, D) -> 1186 handle_event({timeout,renegotiate}, undefined, StateName, D); 1187 1188handle_event({timeout,renegotiate}, _, StateName, D0) -> 1189 case StateName of 1190 {connected,Role} -> 1191 start_rekeying(Role, D0); 1192 {ext_info,Role,renegotiate} -> 1193 start_rekeying(Role, D0); 1194 _ -> 1195 %% Wrong state for starting a renegotiation, must be in re-negotiation 1196 keep_state_and_data 1197 end; 1198 1199handle_event({timeout,check_data_size}, _, StateName, D0) -> 1200 %% Rekey due to sent data limit reached? (Can't be in {ext_info,...} if data is sent) 1201 case StateName of 1202 {connected,Role} -> 1203 check_data_rekeying(Role, D0); 1204 _ -> 1205 %% Wrong state for starting a renegotiation, must be in re-negotiation 1206 keep_state_and_data 1207 end; 1208 1209handle_event({call,From}, get_alg, _, D) -> 1210 #ssh{algorithms=Algs} = D#data.ssh_params, 1211 {keep_state_and_data, [{reply,From,Algs}]}; 1212 1213handle_event(cast, _, StateName, _) when not ?CONNECTED(StateName) -> 1214 {keep_state_and_data, [postpone]}; 1215 1216handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTED(StateName) -> 1217 case ssh_client_channel:cache_lookup(cache(D), ChannelId) of 1218 #channel{recv_window_size = WinSize, 1219 recv_window_pending = Pending, 1220 recv_packet_size = PktSize} = Channel 1221 when (WinSize-Bytes) >= 2*PktSize -> 1222 %% The peer can send at least two more *full* packet, no hurry. 1223 ssh_client_channel:cache_update(cache(D), 1224 Channel#channel{recv_window_pending = Pending + Bytes}), 1225 keep_state_and_data; 1226 1227 #channel{recv_window_size = WinSize, 1228 recv_window_pending = Pending, 1229 remote_id = Id} = Channel -> 1230 %% Now we have to update the window - we can't receive so many more pkts 1231 ssh_client_channel:cache_update(cache(D), 1232 Channel#channel{recv_window_size = 1233 WinSize + Bytes + Pending, 1234 recv_window_pending = 0}), 1235 Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes + Pending), 1236 {keep_state, send_msg(Msg,D)}; 1237 1238 undefined -> 1239 keep_state_and_data 1240 end; 1241 1242handle_event(cast, {reply_request,Resp,ChannelId}, StateName, D) when ?CONNECTED(StateName) -> 1243 case ssh_client_channel:cache_lookup(cache(D), ChannelId) of 1244 #channel{remote_id = RemoteId} when Resp== success ; Resp==failure -> 1245 Msg = 1246 case Resp of 1247 success -> ssh_connection:channel_success_msg(RemoteId); 1248 failure -> ssh_connection:channel_failure_msg(RemoteId) 1249 end, 1250 update_inet_buffers(D#data.socket), 1251 {keep_state, send_msg(Msg,D)}; 1252 1253 #channel{} -> 1254 Details = io_lib:format("Unhandled reply in state ~p:~n~p", [StateName,Resp]), 1255 ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D); 1256 1257 undefined -> 1258 keep_state_and_data 1259 end; 1260 1261handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, StateName, D) when ?CONNECTED(StateName) -> 1262 {keep_state, handle_request(ChannelPid, ChannelId, Type, Data, false, none, D)}; 1263 1264handle_event(cast, {request,ChannelId,Type,Data}, StateName, D) when ?CONNECTED(StateName) -> 1265 {keep_state, handle_request(ChannelId, Type, Data, false, none, D)}; 1266 1267handle_event(cast, {unknown,Data}, StateName, D) when ?CONNECTED(StateName) -> 1268 Msg = #ssh_msg_unimplemented{sequence = Data}, 1269 {keep_state, send_msg(Msg,D)}; 1270 1271handle_event(cast, {global_request, Type, Data}, StateName, D) when ?CONNECTED(StateName) -> 1272 {keep_state, send_msg(ssh_connection:request_global_msg(Type,false,Data), D)}; 1273 1274 1275%%% Previously handle_sync_event began here 1276handle_event({call,From}, get_print_info, StateName, D) -> 1277 Reply = 1278 try 1279 {inet:sockname(D#data.socket), 1280 inet:peername(D#data.socket) 1281 } 1282 of 1283 {{ok,Local}, {ok,Remote}} -> 1284 {{Local,Remote},io_lib:format("statename=~p",[StateName])}; 1285 _ -> 1286 {{"-",0},"-"} 1287 catch 1288 _:_ -> 1289 {{"?",0},"?"} 1290 end, 1291 {keep_state_and_data, [{reply,From,Reply}]}; 1292 1293handle_event({call,From}, {connection_info, Options}, _, D) -> 1294 Info = fold_keys(Options, fun conn_info/2, D), 1295 {keep_state_and_data, [{reply,From,Info}]}; 1296 1297handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) -> 1298 case ssh_client_channel:cache_lookup(cache(D), ChannelId) of 1299 #channel{} = Channel -> 1300 Info = fold_keys(Options, fun chann_info/2, Channel), 1301 {keep_state_and_data, [{reply,From,Info}]}; 1302 undefined -> 1303 {keep_state_and_data, [{reply,From,[]}]} 1304 end; 1305 1306 1307handle_event({call,From}, {info, all}, _, D) -> 1308 Result = ssh_client_channel:cache_foldl(fun(Channel, Acc) -> 1309 [Channel | Acc] 1310 end, 1311 [], cache(D)), 1312 {keep_state_and_data, [{reply, From, {ok,Result}}]}; 1313 1314handle_event({call,From}, {info, ChannelPid}, _, D) -> 1315 Result = ssh_client_channel:cache_foldl( 1316 fun(Channel, Acc) when Channel#channel.user == ChannelPid -> 1317 [Channel | Acc]; 1318 (_, Acc) -> 1319 Acc 1320 end, [], cache(D)), 1321 {keep_state_and_data, [{reply, From, {ok,Result}}]}; 1322 1323handle_event({call,From}, {set_sock_opts,SocketOptions}, _StateName, D) -> 1324 Result = try inet:setopts(D#data.socket, SocketOptions) 1325 catch 1326 _:_ -> {error, badarg} 1327 end, 1328 {keep_state_and_data, [{reply,From,Result}]}; 1329 1330handle_event({call,From}, {get_sock_opts,SocketGetOptions}, _StateName, D) -> 1331 Result = try inet:getopts(D#data.socket, SocketGetOptions) 1332 catch 1333 _:_ -> {error, badarg} 1334 end, 1335 {keep_state_and_data, [{reply,From,Result}]}; 1336 1337handle_event({call,From}, stop, _StateName, D0) -> 1338 {Repls,D} = send_replies(ssh_connection:handle_stop(D0#data.connection_state), D0), 1339 {stop_and_reply, normal, [{reply,From,ok}|Repls], D}; 1340 1341handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) -> 1342 {keep_state_and_data, [postpone]}; 1343 1344handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0) 1345 when ?CONNECTED(StateName) -> 1346 case handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0) of 1347 {error,Error} -> 1348 {keep_state, D0, {reply,From,{error,Error}}}; 1349 D -> 1350 %% Note reply to channel will happen later when reply is recived from peer on the socket 1351 start_channel_request_timer(ChannelId, From, Timeout), 1352 {keep_state, D, cond_set_idle_timer(D)} 1353 end; 1354 1355handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0) 1356 when ?CONNECTED(StateName) -> 1357 case handle_request(ChannelId, Type, Data, true, From, D0) of 1358 {error,Error} -> 1359 {keep_state, D0, {reply,From,{error,Error}}}; 1360 D -> 1361 %% Note reply to channel will happen later when reply is recived from peer on the socket 1362 start_channel_request_timer(ChannelId, From, Timeout), 1363 {keep_state, D, cond_set_idle_timer(D)} 1364 end; 1365 1366handle_event({call,From}, {global_request, "tcpip-forward" = Type, 1367 {ListenHost,ListenPort,ConnectToHost,ConnectToPort}, 1368 Timeout}, StateName, D0) when ?CONNECTED(StateName) -> 1369 Id = make_ref(), 1370 Data = <<?STRING(ListenHost), ?Euint32(ListenPort)>>, 1371 Fun = fun({success, <<Port:32/unsigned-integer>>}, C) -> 1372 Key = {tcpip_forward,ListenHost,Port}, 1373 Value = {ConnectToHost,ConnectToPort}, 1374 C#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C#connection.options)}; 1375 ({success, <<>>}, C) -> 1376 Key = {tcpip_forward,ListenHost,ListenPort}, 1377 Value = {ConnectToHost,ConnectToPort}, 1378 C#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C#connection.options)}; 1379 (_, C) -> 1380 C 1381 end, 1382 D = send_msg(ssh_connection:request_global_msg(Type, true, Data), 1383 add_request(Fun, Id, From, D0)), 1384 start_channel_request_timer(Id, From, Timeout), 1385 {keep_state, D, cond_set_idle_timer(D)}; 1386 1387handle_event({call,From}, {global_request, Type, Data, Timeout}, StateName, D0) when ?CONNECTED(StateName) -> 1388 Id = make_ref(), 1389 D = send_msg(ssh_connection:request_global_msg(Type, true, Data), 1390 add_request(true, Id, From, D0)), 1391 start_channel_request_timer(Id, From, Timeout), 1392 {keep_state, D, cond_set_idle_timer(D)}; 1393 1394handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) 1395 when ?CONNECTED(StateName) -> 1396 {Repls,D} = send_replies(ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), 1397 D0), 1398 start_channel_request_timer(ChannelId, From, Timeout), % FIXME: No message exchange so why? 1399 {keep_state, D, Repls}; 1400 1401handle_event({call,From}, {eof, ChannelId}, StateName, D0) 1402 when ?CONNECTED(StateName) -> 1403 case ssh_client_channel:cache_lookup(cache(D0), ChannelId) of 1404 #channel{remote_id = Id, sent_close = false} -> 1405 D = send_msg(ssh_connection:channel_eof_msg(Id), D0), 1406 {keep_state, D, [{reply,From,ok}]}; 1407 _ -> 1408 {keep_state, D0, [{reply,From,{error,closed}}]} 1409 end; 1410 1411handle_event({call,From}, get_misc, StateName, 1412 #data{connection_state = #connection{options = Opts}} = D) when ?CONNECTED(StateName) -> 1413 Sups = ?GET_INTERNAL_OPT(supervisors, Opts), 1414 SubSysSup = proplists:get_value(subsystem_sup, Sups), 1415 Reply = {ok, {SubSysSup, role(StateName), Opts}}, 1416 {keep_state, D, [{reply,From,Reply}]}; 1417 1418handle_event({call,From}, 1419 {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, 1420 StateName, 1421 D0) when ?CONNECTED(StateName) -> 1422 erlang:monitor(process, ChannelPid), 1423 {ChannelId, D1} = new_channel_id(D0), 1424 D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId, 1425 InitialWindowSize, 1426 MaxPacketSize, Data), 1427 D1), 1428 ssh_client_channel:cache_update(cache(D2), 1429 #channel{type = Type, 1430 sys = "none", 1431 user = ChannelPid, 1432 local_id = ChannelId, 1433 recv_window_size = InitialWindowSize, 1434 recv_packet_size = MaxPacketSize, 1435 send_buf = queue:new() 1436 }), 1437 D = add_request(true, ChannelId, From, D2), 1438 start_channel_request_timer(ChannelId, From, Timeout), 1439 {keep_state, D, cond_set_idle_timer(D)}; 1440 1441handle_event({call,From}, {send_window, ChannelId}, StateName, D) 1442 when ?CONNECTED(StateName) -> 1443 Reply = case ssh_client_channel:cache_lookup(cache(D), ChannelId) of 1444 #channel{send_window_size = WinSize, 1445 send_packet_size = Packsize} -> 1446 {ok, {WinSize, Packsize}}; 1447 undefined -> 1448 {error, einval} 1449 end, 1450 {keep_state_and_data, [{reply,From,Reply}]}; 1451 1452handle_event({call,From}, {recv_window, ChannelId}, StateName, D) 1453 when ?CONNECTED(StateName) -> 1454 Reply = case ssh_client_channel:cache_lookup(cache(D), ChannelId) of 1455 #channel{recv_window_size = WinSize, 1456 recv_packet_size = Packsize} -> 1457 {ok, {WinSize, Packsize}}; 1458 undefined -> 1459 {error, einval} 1460 end, 1461 {keep_state_and_data, [{reply,From,Reply}]}; 1462 1463handle_event({call,From}, {close, ChannelId}, StateName, D0) 1464 when ?CONNECTED(StateName) -> 1465 case ssh_client_channel:cache_lookup(cache(D0), ChannelId) of 1466 #channel{remote_id = Id} = Channel -> 1467 D1 = send_msg(ssh_connection:channel_close_msg(Id), D0), 1468 ssh_client_channel:cache_update(cache(D1), Channel#channel{sent_close = true}), 1469 {keep_state, D1, [cond_set_idle_timer(D1), {reply,From,ok}]}; 1470 undefined -> 1471 {keep_state_and_data, [{reply,From,ok}]} 1472 end; 1473 1474handle_event(cast, {store,Key,Value}, _StateName, #data{connection_state=C0} = D) -> 1475 C = C0#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C0#connection.options)}, 1476 {keep_state, D#data{connection_state = C}}; 1477 1478handle_event({call,From}, {retrieve,Key}, _StateName, #data{connection_state=C}) -> 1479 case retrieve(C, Key) of 1480 {ok,Value} -> 1481 {keep_state_and_data, [{reply,From,{ok,Value}}]}; 1482 _ -> 1483 {keep_state_and_data, [{reply,From,undefined}]} 1484 end; 1485 1486%%===== Reception of encrypted bytes, decryption and framing 1487handle_event(info, {Proto, Sock, Info}, {hello,_}, #data{socket = Sock, 1488 transport_protocol = Proto}) -> 1489 case Info of 1490 "SSH-" ++ _ -> 1491 {keep_state_and_data, [{next_event, internal, {version_exchange,Info}}]}; 1492 _ -> 1493 {keep_state_and_data, [{next_event, internal, {info_line,Info}}]} 1494 end; 1495 1496 1497handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, 1498 transport_protocol = Proto}) -> 1499 try ssh_transport:handle_packet_part( 1500 D0#data.decrypted_data_buffer, 1501 <<(D0#data.encrypted_data_buffer)/binary, NewData/binary>>, 1502 D0#data.aead_data, 1503 D0#data.undecrypted_packet_length, 1504 D0#data.ssh_params) 1505 of 1506 {packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} -> 1507 D1 = D0#data{ssh_params = 1508 Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)}, 1509 decrypted_data_buffer = <<>>, 1510 undecrypted_packet_length = undefined, 1511 aead_data = <<>>, 1512 encrypted_data_buffer = EncryptedDataRest}, 1513 try 1514 ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D1)) 1515 of 1516 #ssh_msg_kexinit{} = Msg -> 1517 {keep_state, D1, [{next_event, internal, prepare_next_packet}, 1518 {next_event, internal, {Msg,DecryptedBytes}} 1519 ]}; 1520 1521 #ssh_msg_global_request{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1522 #ssh_msg_request_success{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1523 #ssh_msg_request_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1524 #ssh_msg_channel_open{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1525 #ssh_msg_channel_open_confirmation{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1526 #ssh_msg_channel_open_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1527 #ssh_msg_channel_window_adjust{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1528 #ssh_msg_channel_data{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1529 #ssh_msg_channel_extended_data{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1530 #ssh_msg_channel_eof{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1531 #ssh_msg_channel_close{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1532 #ssh_msg_channel_request{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1533 #ssh_msg_channel_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1534 #ssh_msg_channel_success{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)}; 1535 1536 Msg -> 1537 {keep_state, D1, [{next_event, internal, prepare_next_packet}, 1538 {next_event, internal, Msg} 1539 ]} 1540 catch 1541 C:E:ST -> 1542 {Shutdown, D} = 1543 ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, 1544 io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p", 1545 [C,E,ST]), 1546 StateName, D1), 1547 {stop, Shutdown, D} 1548 end; 1549 1550 {get_more, DecryptedBytes, EncryptedDataRest, AeadData, RemainingSshPacketLen, Ssh1} -> 1551 %% Here we know that there are not enough bytes in 1552 %% EncryptedDataRest to use. We must wait for more. 1553 inet:setopts(Sock, [{active, once}]), 1554 {keep_state, D0#data{encrypted_data_buffer = EncryptedDataRest, 1555 decrypted_data_buffer = DecryptedBytes, 1556 undecrypted_packet_length = RemainingSshPacketLen, 1557 aead_data = AeadData, 1558 ssh_params = Ssh1}}; 1559 1560 {bad_mac, Ssh1} -> 1561 {Shutdown, D} = 1562 ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, 1563 "Bad packet: bad mac", 1564 StateName, D0#data{ssh_params=Ssh1}), 1565 {stop, Shutdown, D}; 1566 1567 {error, {exceeds_max_size,PacketLen}} -> 1568 {Shutdown, D} = 1569 ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, 1570 io_lib:format("Bad packet: Size (~p bytes) exceeds max size", 1571 [PacketLen]), 1572 StateName, D0), 1573 {stop, Shutdown, D} 1574 catch 1575 C:E:ST -> 1576 {Shutdown, D} = 1577 ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, 1578 io_lib:format("Bad packet: Couldn't decrypt~n~p:~p~n~p",[C,E,ST]), 1579 StateName, D0), 1580 {stop, Shutdown, D} 1581 end; 1582 1583 1584%%%==== 1585handle_event(internal, prepare_next_packet, _, D) -> 1586 Enough = erlang:max(8, D#data.ssh_params#ssh.decrypt_block_size), 1587 case size(D#data.encrypted_data_buffer) of 1588 Sz when Sz >= Enough -> 1589 self() ! {D#data.transport_protocol, D#data.socket, <<>>}; 1590 _ -> 1591 ok 1592 end, 1593 inet:setopts(D#data.socket, [{active, once}]), 1594 keep_state_and_data; 1595 1596handle_event(info, {CloseTag,Socket}, _StateName, 1597 D0 = #data{socket = Socket, 1598 transport_close_tag = CloseTag, 1599 connection_state = C0}) -> 1600 {Repls, D} = send_replies(ssh_connection:handle_stop(C0), D0), 1601 disconnect_fun("Received a transport close", D), 1602 {stop_and_reply, {shutdown,"Connection closed"}, Repls, D}; 1603 1604handle_event(info, {timeout, {_, From} = Request}, _, 1605 #data{connection_state = #connection{requests = Requests} = C0} = D) -> 1606 case lists:member(Request, Requests) of 1607 true -> 1608 %% A channel request is not answered in time. Answer {error,timeout} 1609 %% to the caller 1610 C = C0#connection{requests = lists:delete(Request, Requests)}, 1611 {keep_state, D#data{connection_state=C}, [{reply,From,{error,timeout}}]}; 1612 false -> 1613 %% The request is answered - just ignore the timeout 1614 keep_state_and_data 1615 end; 1616 1617%%% Handle that ssh channels user process goes down 1618handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D) -> 1619 Cache = cache(D), 1620 ssh_client_channel:cache_foldl( 1621 fun(#channel{user=U, 1622 local_id=Id}, Acc) when U == ChannelPid -> 1623 ssh_client_channel:cache_delete(Cache, Id), 1624 Acc; 1625 (_,Acc) -> 1626 Acc 1627 end, [], Cache), 1628 {keep_state, D, cond_set_idle_timer(D)}; 1629 1630handle_event({timeout,idle_time}, _Data, _StateName, D) -> 1631 case ssh_client_channel:cache_info(num_entries, cache(D)) of 1632 0 -> 1633 {stop, {shutdown, "Timeout"}}; 1634 _ -> 1635 keep_state_and_data 1636 end; 1637 1638%%% So that terminate will be run when supervisor is shutdown 1639handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) -> 1640 Role = role(StateName), 1641 if 1642 Role == client -> 1643 %% OTP-8111 tells this function clause fixes a problem in 1644 %% clients, but there were no check for that role. 1645 {stop, {shutdown, Reason}}; 1646 1647 Reason == normal -> 1648 %% An exit normal should not cause a server to crash. This has happend... 1649 keep_state_and_data; 1650 1651 true -> 1652 {stop, {shutdown, Reason}} 1653 end; 1654 1655handle_event(info, check_cache, _, D) -> 1656 {keep_state, D, cond_set_idle_timer(D)}; 1657 1658handle_event(info, {fwd_connect_received, Sock, ChId, ChanCB}, StateName, #data{connection_state = Connection}) -> 1659 #connection{options = Options, 1660 channel_cache = Cache, 1661 sub_system_supervisor = SubSysSup} = Connection, 1662 Channel = ssh_client_channel:cache_lookup(Cache, ChId), 1663 {ok,Pid} = ssh_subsystem_sup:start_channel(role(StateName), SubSysSup, self(), ChanCB, ChId, [Sock], undefined, Options), 1664 ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}), 1665 gen_tcp:controlling_process(Sock, Pid), 1666 inet:setopts(Sock, [{active,once}]), 1667 keep_state_and_data; 1668 1669handle_event({call,From}, 1670 {handle_direct_tcpip, ListenHost, ListenPort, ConnectToHost, ConnectToPort, _Timeout}, 1671 _StateName, 1672 #data{connection_state = #connection{sub_system_supervisor=SubSysSup}}) -> 1673 case ssh_tcpip_forward_acceptor:supervised_start(ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup), 1674 {ListenHost, ListenPort}, 1675 {ConnectToHost, ConnectToPort}, 1676 "direct-tcpip", ssh_tcpip_forward_client, 1677 self()) of 1678 {ok,LPort} -> 1679 {keep_state_and_data, [{reply,From,{ok,LPort}}]}; 1680 {error,Error} -> 1681 {keep_state_and_data, [{reply,From,{error,Error}}]} 1682 end; 1683 1684handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> 1685 case unexpected_fun(UnexpectedMessage, D) of 1686 report -> 1687 Msg = lists:flatten( 1688 io_lib:format( 1689 "*** SSH: " 1690 "Unexpected message '~p' received in state '~p'\n" 1691 "Role: ~p\n" 1692 "Peer: ~p\n" 1693 "Local Address: ~p\n", 1694 [UnexpectedMessage, 1695 StateName, 1696 Ssh#ssh.role, 1697 Ssh#ssh.peer, 1698 ?GET_INTERNAL_OPT(address, Ssh#ssh.opts, undefined)])), 1699 error_logger:info_report(Msg), 1700 keep_state_and_data; 1701 1702 skip -> 1703 keep_state_and_data; 1704 1705 Other -> 1706 Msg = lists:flatten( 1707 io_lib:format("*** SSH: " 1708 "Call to fun in 'unexpectedfun' failed:~n" 1709 "Return: ~p\n" 1710 "Message: ~p\n" 1711 "Role: ~p\n" 1712 "Peer: ~p\n" 1713 "Local Address: ~p\n", 1714 [Other, 1715 UnexpectedMessage, 1716 Ssh#ssh.role, 1717 Ssh#ssh.peer, 1718 ?GET_INTERNAL_OPT(address, Ssh#ssh.opts, undefined)] 1719 )), 1720 error_logger:error_report(Msg), 1721 keep_state_and_data 1722 end; 1723 1724handle_event(internal, {send_disconnect,Code,DetailedText,Module,Line}, StateName, D0) -> 1725 {Shutdown, D} = 1726 send_disconnect(Code, DetailedText, Module, Line, StateName, D0), 1727 {stop, Shutdown, D}; 1728 1729 1730handle_event(enter, _OldState, State, D) -> 1731 %% Just skip 1732 {next_state, State, D}; 1733 1734handle_event(_Type, _Msg, {ext_info,Role,_ReNegFlag}, D) -> 1735 %% If something else arrives, goto next state and handle the event in that one 1736 {next_state, {connected,Role}, D, [postpone]}; 1737 1738handle_event(Type, Ev, StateName, D0) -> 1739 Details = 1740 case catch atom_to_list(element(1,Ev)) of 1741 "ssh_msg_" ++_ when Type==internal -> 1742 lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName])); 1743 _ -> 1744 io_lib:format("Unhandled event in state ~p:~n~p", [StateName,Ev]) 1745 end, 1746 {Shutdown, D} = 1747 ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D0), 1748 {stop, Shutdown, D}. 1749 1750 1751%%-------------------------------------------------------------------- 1752-spec terminate(any(), 1753 state_name(), 1754 #data{} 1755 ) -> term(). 1756 1757%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1758 1759terminate(normal, _StateName, D) -> 1760 stop_subsystem(D), 1761 close_transport(D); 1762 1763terminate({shutdown,"Connection closed"}, _StateName, D) -> 1764 %% Normal: terminated by a sent by peer 1765 stop_subsystem(D), 1766 close_transport(D); 1767 1768terminate({shutdown,{init,Reason}}, StateName, D) -> 1769 %% Error in initiation. "This error should not occur". 1770 log(error, D, "Shutdown in init (StateName=~p): ~p~n", [StateName,Reason]), 1771 stop_subsystem(D), 1772 close_transport(D); 1773 1774terminate({shutdown,_R}, _StateName, D) -> 1775 %% Internal termination, usually already reported via ?send_disconnect resulting in a log entry 1776 stop_subsystem(D), 1777 close_transport(D); 1778 1779terminate(shutdown, _StateName, D0) -> 1780 %% Terminated by supervisor 1781 %% Use send_msg directly instead of ?send_disconnect to avoid filling the log 1782 D = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, 1783 description = "Terminated (shutdown) by supervisor"}, 1784 D0), 1785 close_transport(D); 1786 1787terminate(killed, _StateName, D) -> 1788 %% Got a killed signal 1789 stop_subsystem(D), 1790 close_transport(D); 1791 1792terminate(Reason, StateName, D0) -> 1793 %% Others, e.g undef, {badmatch,_}, ... 1794 log(error, D0, Reason), 1795 {_ShutdownReason, D} = ?send_disconnect(?SSH_DISCONNECT_BY_APPLICATION, 1796 "Internal error", 1797 io_lib:format("Reason: ~p",[Reason]), 1798 StateName, D0), 1799 stop_subsystem(D), 1800 close_transport(D). 1801 1802%%-------------------------------------------------------------------- 1803 1804%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1805 1806format_status(normal, [_, _StateName, D]) -> 1807 [{data, [{"State", D}]}]; 1808format_status(terminate, [_, _StateName, D]) -> 1809 [{data, [{"State", clean(D)}]}]. 1810 1811 1812clean(#data{}=R) -> 1813 fmt_stat_rec(record_info(fields,data), R, 1814 [decrypted_data_buffer, 1815 encrypted_data_buffer, 1816 key_exchange_init_msg, 1817 user_passwords, 1818 opts, 1819 inet_initial_recbuf_size]); 1820clean(#ssh{}=R) -> 1821 fmt_stat_rec(record_info(fields, ssh), R, 1822 [c_keyinit, 1823 s_keyinit, 1824 send_mac_key, 1825 send_mac_size, 1826 recv_mac_key, 1827 recv_mac_size, 1828 encrypt_keys, 1829 encrypt_ctx, 1830 decrypt_keys, 1831 decrypt_ctx, 1832 compress_ctx, 1833 decompress_ctx, 1834 shared_secret, 1835 exchanged_hash, 1836 session_id, 1837 keyex_key, 1838 keyex_info, 1839 available_host_keys]); 1840clean(#connection{}=R) -> 1841 fmt_stat_rec(record_info(fields, connection), R, 1842 []); 1843clean(L) when is_list(L) -> 1844 lists:map(fun clean/1, L); 1845clean(T) when is_tuple(T) -> 1846 list_to_tuple( clean(tuple_to_list(T))); 1847clean(X) -> 1848 ssh_options:no_sensitive(filter, X). 1849 1850 1851fmt_stat_rec(FieldNames, Rec, Exclude) -> 1852 Values = tl(tuple_to_list(Rec)), 1853 list_to_tuple( 1854 [element(1,Rec) | 1855 lists:map(fun({K,V}) -> 1856 case lists:member(K, Exclude) of 1857 true -> '****'; 1858 false -> clean(V) 1859 end 1860 end, lists:zip(FieldNames, Values)) 1861 ]). 1862 1863%%-------------------------------------------------------------------- 1864-spec code_change(term() | {down,term()}, 1865 state_name(), 1866 #data{}, 1867 term() 1868 ) -> {ok, state_name(), #data{}}. 1869 1870%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1871 1872code_change(_OldVsn, StateName, State, _Extra) -> 1873 {ok, StateName, State}. 1874 1875 1876%%==================================================================== 1877%% Internal functions 1878%%==================================================================== 1879 1880%%-------------------------------------------------------------------- 1881%% Starting 1882 1883start_the_connection_child(UserPid, Role, Socket, Options0) -> 1884 Sups = ?GET_INTERNAL_OPT(supervisors, Options0), 1885 ConnectionSup = proplists:get_value(connection_sup, Sups), 1886 Options = ?PUT_INTERNAL_OPT({user_pid,UserPid}, Options0), 1887 InitArgs = [Role, Socket, Options], 1888 {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, InitArgs), 1889 ok = socket_control(Socket, Pid, Options), % transfer the Socket ownership in a controlled way. 1890 Pid. 1891 1892%%-------------------------------------------------------------------- 1893%% Stopping 1894 1895stop_subsystem(#data{ssh_params = 1896 #ssh{role = Role}, 1897 connection_state = 1898 #connection{system_supervisor = SysSup, 1899 sub_system_supervisor = SubSysSup, 1900 options = Opts} 1901 }) when is_pid(SysSup) andalso is_pid(SubSysSup) -> 1902 C = self(), 1903 spawn(fun() -> 1904 wait_until_dead(C, 10000), 1905 case {Role, ?GET_INTERNAL_OPT(connected_socket,Opts,non_socket_started)} of 1906 {server, non_socket_started} -> 1907 ssh_system_sup:stop_subsystem(SysSup, SubSysSup); 1908 {client, non_socket_started} -> 1909 ssh_system_sup:stop_system(Role, SysSup); 1910 {server, _Socket} -> 1911 ssh_system_sup:stop_system(Role, SysSup); 1912 {client, _Socket} -> 1913 ssh_system_sup:stop_subsystem(SysSup, SubSysSup), 1914 wait_until_dead(SubSysSup, 1000), 1915 sshc_sup:stop_system(SysSup) 1916 end 1917 end); 1918stop_subsystem(_) -> 1919 ok. 1920 1921 1922wait_until_dead(Pid, Timeout) -> 1923 Mref = erlang:monitor(process, Pid), 1924 receive 1925 {'DOWN', Mref, process, Pid, _Info} -> ok 1926 after 1927 Timeout -> ok 1928 end. 1929 1930 1931close_transport(#data{transport_cb = Transport, 1932 socket = Socket}) -> 1933 try 1934 Transport:close(Socket) 1935 of 1936 _ -> ok 1937 catch 1938 _:_ -> ok 1939 end. 1940 1941%%-------------------------------------------------------------------- 1942%% "Invert" the Role 1943peer_role(client) -> server; 1944peer_role(server) -> client. 1945 1946%%-------------------------------------------------------------------- 1947available_hkey_algorithms(client, Options) -> 1948 case available_hkey_algos(Options) of 1949 [] -> 1950 error({shutdown, "No public key algs"}); 1951 Algs -> 1952 [atom_to_list(A) || A<-Algs] 1953 end; 1954 1955available_hkey_algorithms(server, Options) -> 1956 case [A || A <- available_hkey_algos(Options), 1957 is_usable_host_key(A, Options)] of 1958 [] -> 1959 error({shutdown, "No host key available"}); 1960 Algs -> 1961 [atom_to_list(A) || A<-Algs] 1962 end. 1963 1964 1965available_hkey_algos(Options) -> 1966 SupAlgos = ssh_transport:supported_algorithms(public_key), 1967 HKeys = proplists:get_value(public_key, 1968 ?GET_OPT(preferred_algorithms,Options) 1969 ), 1970 NonSupported = HKeys -- SupAlgos, 1971 AvailableAndSupported = HKeys -- NonSupported, 1972 AvailableAndSupported. 1973 1974 1975send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> 1976 {Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), 1977 send_bytes(Bytes, State), 1978 State#data{ssh_params=Ssh}. 1979 1980send_bytes("", _D) -> 1981 ok; 1982send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> 1983 _ = Transport:send(Socket, Bytes), 1984 ok. 1985 1986handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> 1987 Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), 1988 {ok, Ssh}; 1989handle_version(_,_,_) -> 1990 not_supported. 1991 1992string_version(#ssh{role = client, c_version = Vsn}) -> 1993 Vsn; 1994string_version(#ssh{role = server, s_version = Vsn}) -> 1995 Vsn. 1996 1997 1998cast(FsmPid, Event) -> 1999 gen_statem:cast(FsmPid, Event). 2000 2001call(FsmPid, Event) -> 2002 call(FsmPid, Event, infinity). 2003 2004call(FsmPid, Event, Timeout) -> 2005 try gen_statem:call(FsmPid, Event, Timeout) of 2006 {closed, _R} -> 2007 {error, closed}; 2008 {killed, _R} -> 2009 {error, closed}; 2010 Result -> 2011 Result 2012 catch 2013 exit:{noproc, _R} -> 2014 {error, closed}; 2015 exit:{normal, _R} -> 2016 {error, closed}; 2017 exit:{{shutdown, _R},_} -> 2018 {error, closed}; 2019 exit:{shutdown, _R} -> 2020 {error, closed} 2021 end. 2022 2023 2024set_kex_overload_prefix(Msg = <<?BYTE(Op),_/binary>>, #data{ssh_params=SshParams}) 2025 when Op == 30; 2026 Op == 31 2027 -> 2028 case catch atom_to_list(kex(SshParams)) of 2029 "ecdh-sha2-" ++ _ -> 2030 <<"ecdh",Msg/binary>>; 2031 "curve25519-" ++ _ -> 2032 <<"ecdh",Msg/binary>>; 2033 "curve448-" ++ _ -> 2034 <<"ecdh",Msg/binary>>; 2035 "diffie-hellman-group-exchange-" ++ _ -> 2036 <<"dh_gex",Msg/binary>>; 2037 "diffie-hellman-group" ++ _ -> 2038 <<"dh",Msg/binary>>; 2039 _ -> 2040 Msg 2041 end; 2042set_kex_overload_prefix(Msg, _) -> 2043 Msg. 2044 2045kex(#ssh{algorithms=#alg{kex=Kex}}) -> Kex; 2046kex(_) -> undefined. 2047 2048cache(#data{connection_state=C}) -> C#connection.channel_cache. 2049 2050 2051%%%---------------------------------------------------------------- 2052handle_ssh_msg_ext_info(#ssh_msg_ext_info{}, D=#data{ssh_params = #ssh{recv_ext_info=false}} ) -> 2053 % The peer sent this although we didn't allow it! 2054 D; 2055 2056handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) -> 2057 lists:foldl(fun ext_info/2, D0, Data). 2058 2059 2060ext_info({"server-sig-algs",SigAlgsStr}, 2061 D0 = #data{ssh_params=#ssh{role=client, 2062 userauth_pubkeys=ClientSigAlgs}=Ssh0}) -> 2063 %% ClientSigAlgs are the pub_key algortithms that: 2064 %% 1) is usable, that is, the user has such a public key and 2065 %% 2) is either the default list or set by the caller 2066 %% with the client option 'pref_public_key_algs' 2067 %% 2068 %% The list is already checked for duplicates. 2069 2070 SigAlgs = [A || Astr <- string:tokens(SigAlgsStr, ","), 2071 A <- try [list_to_existing_atom(Astr)] 2072 %% list_to_existing_atom will fail for unknown algorithms 2073 catch _:_ -> [] 2074 end], 2075 2076 CommonAlgs = [A || A <- SigAlgs, 2077 lists:member(A, ClientSigAlgs)], 2078 2079 %% Re-arrange the client supported public-key algorithms so that the server 2080 %% preferred ones are tried first. 2081 %% Trying algorithms not mentioned by the server is ok, since the server can't know 2082 %% if the client supports 'server-sig-algs' or not. 2083 2084 D0#data{ 2085 ssh_params = 2086 Ssh0#ssh{ 2087 userauth_pubkeys = 2088 CommonAlgs ++ (ClientSigAlgs -- CommonAlgs) 2089 }}; 2090 2091ext_info(_, D0) -> 2092 %% Not implemented 2093 D0. 2094 2095%%%---------------------------------------------------------------- 2096is_usable_user_pubkey(Alg, Ssh) -> 2097 try ssh_auth:get_public_key(Alg, Ssh) of 2098 {ok,_} -> true; 2099 _ -> false 2100 catch 2101 _:_ -> false 2102 end. 2103 2104%%%---------------------------------------------------------------- 2105is_usable_host_key(Alg, Opts) -> 2106 try ssh_transport:get_host_key(Alg, Opts) 2107 of 2108 _PrivHostKey -> true 2109 catch 2110 _:_ -> false 2111 end. 2112 2113%%%---------------------------------------------------------------- 2114handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) -> 2115 case ssh_client_channel:cache_lookup(cache(D), ChannelId) of 2116 #channel{remote_id = Id, 2117 sent_close = false} = Channel -> 2118 update_sys(cache(D), Channel, Type, ChannelPid), 2119 send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data), 2120 add_request(WantReply, ChannelId, From, D)); 2121 2122 _ when WantReply==true -> 2123 {error,closed}; 2124 2125 _ -> 2126 D 2127 end. 2128 2129handle_request(ChannelId, Type, Data, WantReply, From, D) -> 2130 case ssh_client_channel:cache_lookup(cache(D), ChannelId) of 2131 #channel{remote_id = Id, 2132 sent_close = false} -> 2133 send_msg(ssh_connection:channel_request_msg(Id, Type, WantReply, Data), 2134 add_request(WantReply, ChannelId, From, D)); 2135 2136 _ when WantReply==true -> 2137 {error,closed}; 2138 2139 _ -> 2140 D 2141 end. 2142 2143%%%---------------------------------------------------------------- 2144update_sys(Cache, Channel, Type, ChannelPid) -> 2145 ssh_client_channel:cache_update(Cache, 2146 Channel#channel{sys = Type, user = ChannelPid}). 2147 2148add_request(false, _ChannelId, _From, State) -> 2149 State; 2150add_request(true, ChannelId, From, #data{connection_state = 2151 #connection{requests = Requests0} = 2152 Connection} = State) -> 2153 Requests = [{ChannelId, From} | Requests0], 2154 State#data{connection_state = Connection#connection{requests = Requests}}; 2155add_request(Fun, ChannelId, From, #data{connection_state = 2156 #connection{requests = Requests0} = 2157 Connection} = State) when is_function(Fun) -> 2158 Requests = [{ChannelId, From, Fun} | Requests0], 2159 State#data{connection_state = Connection#connection{requests = Requests}}. 2160 2161new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} = 2162 Connection} 2163 = State) -> 2164 {Id, State#data{connection_state = 2165 Connection#connection{channel_id_seed = Id + 1}}}. 2166 2167 2168%%%---------------------------------------------------------------- 2169start_rekeying(Role, D0) -> 2170 {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params), 2171 send_bytes(SshPacket, D0), 2172 D = D0#data{ssh_params = Ssh, 2173 key_exchange_init_msg = KeyInitMsg}, 2174 {next_state, {kexinit,Role,renegotiate}, D}. 2175 2176 2177init_renegotiate_timers(_OldState, NewState, D) -> 2178 {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), 2179 {next_state, NewState, D, [{{timeout,renegotiate}, RekeyTimeout, none}, 2180 {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none} ]}. 2181 2182 2183pause_renegotiate_timers(_OldState, NewState, D) -> 2184 {next_state, NewState, D, [{{timeout,renegotiate}, infinity, none}, 2185 {{timeout,check_data_size}, infinity, none} ]}. 2186 2187check_data_rekeying(Role, D) -> 2188 case inet:getstat(D#data.socket, [send_oct]) of 2189 {ok, [{send_oct,SocketSentTotal}]} -> 2190 SentSinceRekey = SocketSentTotal - D#data.last_size_rekey, 2191 {_RekeyTimeout,MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), 2192 case check_data_rekeying_dbg(SentSinceRekey, MaxSent) of 2193 true -> 2194 start_rekeying(Role, D#data{last_size_rekey = SocketSentTotal}); 2195 _ -> 2196 %% Not enough data sent for a re-negotiation. Restart timer. 2197 {keep_state, D, {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none}} 2198 end; 2199 {error,_} -> 2200 %% Socket closed, but before this module has handled that. Maybe 2201 %% it is in the message queue. 2202 %% Just go on like if there was not enough data transmitted to start re-keying: 2203 {keep_state, D, {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none}} 2204 end. 2205 2206check_data_rekeying_dbg(SentSinceRekey, MaxSent) -> 2207 %% This function is for the ssh_dbg to trace on. See dbg_trace/3 at the end. 2208 SentSinceRekey >= MaxSent. 2209 2210%%%---------------------------------------------------------------- 2211%%% This server/client has decided to disconnect via the state machine: 2212%%% The unused arguments are for debugging. 2213 2214send_disconnect(Code, DetailedText, Module, Line, StateName, D) -> 2215 send_disconnect(Code, default_text(Code), DetailedText, Module, Line, StateName, D). 2216 2217send_disconnect(Code, Reason, DetailedText, Module, Line, StateName, D0) -> 2218 Msg = #ssh_msg_disconnect{code = Code, 2219 description = Reason}, 2220 D = send_msg(Msg, D0), 2221 LogMsg = io_lib:format("Disconnects with code = ~p [RFC4253 11.1]: ~s",[Code,Reason]), 2222 call_disconnectfun_and_log_cond(LogMsg, DetailedText, Module, Line, StateName, D), 2223 {{shutdown,Reason}, D}. 2224 2225call_disconnectfun_and_log_cond(LogMsg, DetailedText, Module, Line, StateName, D) -> 2226 case disconnect_fun(LogMsg, D) of 2227 void -> 2228 log(info, D, 2229 "~s~n" 2230 "State = ~p~n" 2231 "Module = ~p, Line = ~p.~n" 2232 "Details:~n ~s~n", 2233 [LogMsg, StateName, Module, Line, DetailedText]); 2234 _ -> 2235 ok 2236 end. 2237 2238 2239default_text(?SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT) -> "Host not allowed to connect"; 2240default_text(?SSH_DISCONNECT_PROTOCOL_ERROR) -> "Protocol error"; 2241default_text(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED) -> "Key exchange failed"; 2242default_text(?SSH_DISCONNECT_RESERVED) -> "Reserved"; 2243default_text(?SSH_DISCONNECT_MAC_ERROR) -> "Mac error"; 2244default_text(?SSH_DISCONNECT_COMPRESSION_ERROR) -> "Compression error"; 2245default_text(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE) -> "Service not available"; 2246default_text(?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED) -> "Protocol version not supported"; 2247default_text(?SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE) -> "Host key not verifiable"; 2248default_text(?SSH_DISCONNECT_CONNECTION_LOST) -> "Connection lost"; 2249default_text(?SSH_DISCONNECT_BY_APPLICATION) -> "By application"; 2250default_text(?SSH_DISCONNECT_TOO_MANY_CONNECTIONS) -> "Too many connections"; 2251default_text(?SSH_DISCONNECT_AUTH_CANCELLED_BY_USER) -> "Auth cancelled by user"; 2252default_text(?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) -> "Unable to connect using the available authentication methods"; 2253default_text(?SSH_DISCONNECT_ILLEGAL_USER_NAME) -> "Illegal user name". 2254 2255%%%---------------------------------------------------------------- 2256counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) -> 2257 Ssh#ssh{c_vsn = NumVsn , c_version = StrVsn}; 2258counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) -> 2259 Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}. 2260 2261%%%---------------------------------------------------------------- 2262conn_info_keys() -> 2263 [client_version, 2264 server_version, 2265 peer, 2266 user, 2267 sockname, 2268 options, 2269 algorithms, 2270 channels 2271 ]. 2272 2273conn_info(client_version, #data{ssh_params=S}) -> {S#ssh.c_vsn, S#ssh.c_version}; 2274conn_info(server_version, #data{ssh_params=S}) -> {S#ssh.s_vsn, S#ssh.s_version}; 2275conn_info(peer, #data{ssh_params=S}) -> S#ssh.peer; 2276conn_info(user, D) -> D#data.auth_user; 2277conn_info(sockname, #data{ssh_params=S}) -> S#ssh.local; 2278conn_info(options, #data{ssh_params=#ssh{opts=Opts}}) -> lists:sort( 2279 maps:to_list( 2280 ssh_options:keep_set_options( 2281 client, 2282 ssh_options:keep_user_options(client,Opts)))); 2283conn_info(algorithms, #data{ssh_params=#ssh{algorithms=A}}) -> conn_info_alg(A); 2284conn_info(channels, D) -> try conn_info_chans(ets:tab2list(cache(D))) 2285 catch _:_ -> undefined 2286 end; 2287%% dbg options ( = not documented): 2288conn_info(socket, D) -> D#data.socket; 2289conn_info(chan_ids, D) -> 2290 ssh_client_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) -> 2291 [Id | Acc] 2292 end, [], cache(D)). 2293 2294conn_info_chans(Chs) -> 2295 Fs = record_info(fields, channel), 2296 [lists:zip(Fs, tl(tuple_to_list(Ch))) || Ch=#channel{} <- Chs]. 2297 2298conn_info_alg(AlgTup) -> 2299 [alg|Vs] = tuple_to_list(AlgTup), 2300 Fs = record_info(fields, alg), 2301 [{K,V} || {K,V} <- lists:zip(Fs,Vs), 2302 lists:member(K,[kex, 2303 hkey, 2304 encrypt, 2305 decrypt, 2306 send_mac, 2307 recv_mac, 2308 compress, 2309 decompress, 2310 send_ext_info, 2311 recv_ext_info])]. 2312 2313%%%---------------------------------------------------------------- 2314chann_info(recv_window, C) -> 2315 {{win_size, C#channel.recv_window_size}, 2316 {packet_size, C#channel.recv_packet_size}}; 2317chann_info(send_window, C) -> 2318 {{win_size, C#channel.send_window_size}, 2319 {packet_size, C#channel.send_packet_size}}; 2320%% dbg options ( = not documented): 2321chann_info(pid, C) -> 2322 C#channel.user. 2323 2324%%%---------------------------------------------------------------- 2325%% Assisting meta function for the *_info functions 2326fold_keys(Keys, Fun, Extra) -> 2327 lists:foldr(fun(Key, Acc) -> 2328 try Fun(Key, Extra) of 2329 Value -> [{Key,Value}|Acc] 2330 catch 2331 _:_ -> Acc 2332 end 2333 end, [], Keys). 2334 2335%%%---------------------------------------------------------------- 2336log(Tag, D, Format, Args) -> 2337 log(Tag, D, io_lib:format(Format,Args)). 2338 2339log(Tag, D, Reason) -> 2340 case atom_to_list(Tag) of % Dialyzer-technical reasons... 2341 "error" -> do_log(error_msg, Reason, D); 2342 "warning" -> do_log(warning_msg, Reason, D); 2343 "info" -> do_log(info_msg, Reason, D) 2344 end. 2345 2346 2347do_log(F, Reason0, #data{ssh_params = S}) -> 2348 Reason = 2349 try io_lib:format("~s",[Reason0]) 2350 of _ -> Reason0 2351 catch 2352 _:_ -> io_lib:format("~p",[Reason0]) 2353 end, 2354 case S of 2355 #ssh{role = Role} when Role==server ; 2356 Role==client -> 2357 {PeerRole,PeerVersion} = 2358 case Role of 2359 server -> {"Client", S#ssh.c_version}; 2360 client -> {"Server", S#ssh.s_version} 2361 end, 2362 error_logger:F("Erlang SSH ~p ~s ~s.~n" 2363 "~s: ~p~n" 2364 "~s~n", 2365 [Role, 2366 ssh_log_version(), crypto_log_info(), 2367 PeerRole, PeerVersion, 2368 Reason]); 2369 _ -> 2370 error_logger:F("Erlang SSH ~s ~s.~n" 2371 "~s~n", 2372 [ssh_log_version(), crypto_log_info(), 2373 Reason]) 2374 end. 2375 2376crypto_log_info() -> 2377 try 2378 [{_,_,CI}] = crypto:info_lib(), 2379 case crypto:info_fips() of 2380 enabled -> 2381 <<"(",CI/binary,". FIPS enabled)">>; 2382 not_enabled -> 2383 <<"(",CI/binary,". FIPS available but not enabled)">>; 2384 _ -> 2385 <<"(",CI/binary,")">> 2386 end 2387 catch 2388 _:_ -> "" 2389 end. 2390 2391ssh_log_version() -> 2392 case application:get_key(ssh,vsn) of 2393 {ok,Vsn} -> Vsn; 2394 undefined -> "" 2395 end. 2396 2397%%%---------------------------------------------------------------- 2398not_connected_filter({connection_reply, _Data}) -> true; 2399not_connected_filter(_) -> false. 2400 2401%%%---------------------------------------------------------------- 2402 2403send_replies({Repls,C = #connection{}}, D) when is_list(Repls) -> 2404 send_replies(Repls, D#data{connection_state=C}); 2405send_replies(Repls, State) -> 2406 lists:foldl(fun get_repl/2, {[],State}, Repls). 2407 2408get_repl({connection_reply,Msg}, {CallRepls,S}) -> 2409 if is_record(Msg, ssh_msg_channel_success) -> 2410 update_inet_buffers(S#data.socket); 2411 true -> 2412 ok 2413 end, 2414 {CallRepls, send_msg(Msg,S)}; 2415get_repl({channel_data,undefined,_Data}, Acc) -> 2416 Acc; 2417get_repl({channel_data,Pid,Data}, Acc) -> 2418 Pid ! {ssh_cm, self(), Data}, 2419 Acc; 2420get_repl({channel_request_reply,From,Data}, {CallRepls,S}) -> 2421 {[{reply,From,Data}|CallRepls], S}; 2422get_repl({flow_control,Cache,Channel,From,Msg}, {CallRepls,S}) -> 2423 ssh_client_channel:cache_update(Cache, Channel#channel{flow_control = undefined}), 2424 {[{reply,From,Msg}|CallRepls], S}; 2425get_repl({flow_control,From,Msg}, {CallRepls,S}) -> 2426 {[{reply,From,Msg}|CallRepls], S}; 2427%% get_repl(noreply, Acc) -> 2428%% Acc; 2429%% get_repl([], Acc) -> 2430%% Acc; 2431get_repl(X, Acc) -> 2432 exit({get_repl,X,Acc}). 2433 2434%%%---------------------------------------------------------------- 2435-define(CALL_FUN(Key,D), catch (?GET_OPT(Key, (D#data.ssh_params)#ssh.opts)) ). 2436 2437%%disconnect_fun({disconnect,Msg}, D) -> ?CALL_FUN(disconnectfun,D)(Msg); 2438disconnect_fun(Reason, D) -> ?CALL_FUN(disconnectfun,D)(Reason). 2439 2440unexpected_fun(UnexpectedMessage, #data{ssh_params = #ssh{peer = {_,Peer} }} = D) -> 2441 ?CALL_FUN(unexpectedfun,D)(UnexpectedMessage, Peer). 2442 2443debug_fun(#ssh_msg_debug{always_display = Display, 2444 message = DbgMsg, 2445 language = Lang}, 2446 D) -> 2447 ?CALL_FUN(ssh_msg_debug_fun,D)(self(), Display, DbgMsg, Lang). 2448 2449 2450connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}} = D) -> 2451 ?CALL_FUN(connectfun,D)(User, Peer, Method). 2452 2453 2454retry_fun(_, undefined, _) -> 2455 ok; 2456retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts, 2457 peer = {_,Peer} 2458 }}) -> 2459 {Tag,Info} = 2460 case Reason of 2461 {error, Error} -> 2462 {failfun, Error}; 2463 _ -> 2464 {infofun, Reason} 2465 end, 2466 Fun = ?GET_OPT(Tag, Opts), 2467 try erlang:fun_info(Fun, arity) 2468 of 2469 {arity, 2} -> %% Backwards compatible 2470 catch Fun(User, Info); 2471 {arity, 3} -> 2472 catch Fun(User, Peer, Info); 2473 _ -> 2474 ok 2475 catch 2476 _:_ -> 2477 ok 2478 end. 2479 2480%%%---------------------------------------------------------------- 2481%%% Cache idle timer that closes the connection if there are no 2482%%% channels open for a while. 2483 2484cond_set_idle_timer(D) -> 2485 case ssh_client_channel:cache_info(num_entries, cache(D)) of 2486 0 -> {{timeout,idle_time}, ?GET_OPT(idle_time, (D#data.ssh_params)#ssh.opts), none}; 2487 _ -> {{timeout,idle_time}, infinity, none} 2488 end. 2489 2490%%%---------------------------------------------------------------- 2491start_channel_request_timer(_,_, infinity) -> 2492 ok; 2493start_channel_request_timer(Channel, From, Time) -> 2494 erlang:send_after(Time, self(), {timeout, {Channel, From}}). 2495 2496%%%---------------------------------------------------------------- 2497%%% Connection start and initalization helpers 2498 2499socket_control(Socket, Pid, Options) -> 2500 {_, Callback, _} = ?GET_OPT(transport, Options), 2501 case Callback:controlling_process(Socket, Pid) of 2502 ok -> 2503 gen_statem:cast(Pid, socket_control); 2504 {error, Reason} -> 2505 {error, Reason} 2506 end. 2507 2508handshake(Pid, Ref, Timeout) -> 2509 receive 2510 ssh_connected -> 2511 erlang:demonitor(Ref), 2512 {ok, Pid}; 2513 {Pid, not_connected, Reason} -> 2514 {error, Reason}; 2515 {Pid, user_password} -> 2516 Pass = io:get_password(), 2517 Pid ! Pass, 2518 handshake(Pid, Ref, Timeout); 2519 {Pid, question} -> 2520 Answer = io:get_line(""), 2521 Pid ! Answer, 2522 handshake(Pid, Ref, Timeout); 2523 {'DOWN', _, process, Pid, {shutdown, Reason}} -> 2524 {error, Reason}; 2525 {'DOWN', _, process, Pid, Reason} -> 2526 {error, Reason} 2527 after Timeout -> 2528 stop(Pid), 2529 {error, timeout} 2530 end. 2531 2532update_inet_buffers(Socket) -> 2533 try 2534 {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]), 2535 MinVal = 655360, 2536 [{Tag,MinVal} || {Tag,Val} <- BufSzs0, 2537 Val < MinVal] 2538 of 2539 [] -> ok; 2540 NewOpts -> inet:setopts(Socket, NewOpts) 2541 catch 2542 _:_ -> ok 2543 end. 2544 2545%%%################################################################ 2546%%%# 2547%%%# Tracing 2548%%%# 2549 2550ssh_dbg_trace_points() -> [terminate, disconnect, connections, connection_events, renegotiation]. 2551 2552ssh_dbg_flags(connections) -> [c | ssh_dbg_flags(terminate)]; 2553ssh_dbg_flags(renegotiation) -> [c]; 2554ssh_dbg_flags(connection_events) -> [c]; 2555ssh_dbg_flags(terminate) -> [c]; 2556ssh_dbg_flags(disconnect) -> [c]. 2557 2558ssh_dbg_on(connections) -> dbg:tp(?MODULE, init_connection_handler, 3, x), 2559 ssh_dbg_on(terminate); 2560ssh_dbg_on(connection_events) -> dbg:tp(?MODULE, handle_event, 4, x); 2561ssh_dbg_on(renegotiation) -> dbg:tpl(?MODULE, init_renegotiate_timers, 3, x), 2562 dbg:tpl(?MODULE, pause_renegotiate_timers, 3, x), 2563 dbg:tpl(?MODULE, check_data_rekeying_dbg, 2, x), 2564 dbg:tpl(?MODULE, start_rekeying, 2, x), 2565 dbg:tp(?MODULE, renegotiate, 1, x); 2566ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 3, x); 2567ssh_dbg_on(disconnect) -> dbg:tpl(?MODULE, send_disconnect, 7, x). 2568 2569 2570ssh_dbg_off(disconnect) -> dbg:ctpl(?MODULE, send_disconnect, 7); 2571ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 3); 2572ssh_dbg_off(renegotiation) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 3), 2573 dbg:ctpl(?MODULE, pause_renegotiate_timers, 3), 2574 dbg:ctpl(?MODULE, check_data_rekeying_dbg, 2), 2575 dbg:ctpl(?MODULE, start_rekeying, 2), 2576 dbg:ctpg(?MODULE, renegotiate, 1); 2577ssh_dbg_off(connection_events) -> dbg:ctpg(?MODULE, handle_event, 4); 2578ssh_dbg_off(connections) -> dbg:ctpg(?MODULE, init_connection_handler, 3), 2579 ssh_dbg_off(terminate). 2580 2581 2582ssh_dbg_format(connections, {call, {?MODULE,init_connection_handler, [Role, Sock, Opts]}}) -> 2583 DefaultOpts = ssh_options:handle_options(Role,[]), 2584 ExcludedKeys = [internal_options, user_options], 2585 NonDefaultOpts = 2586 maps:filter(fun(K,V) -> 2587 case lists:member(K,ExcludedKeys) of 2588 true -> 2589 false; 2590 false -> 2591 V =/= (catch maps:get(K,DefaultOpts)) 2592 end 2593 end, 2594 Opts), 2595 {ok, {IPp,Portp}} = inet:peername(Sock), 2596 {ok, {IPs,Ports}} = inet:sockname(Sock), 2597 [io_lib:format("Starting ~p connection:\n",[Role]), 2598 io_lib:format("Socket = ~p, Peer = ~s:~p, Local = ~s:~p,~n" 2599 "Non-default options:~n~p", 2600 [Sock,inet:ntoa(IPp),Portp,inet:ntoa(IPs),Ports, 2601 NonDefaultOpts]) 2602 ]; 2603ssh_dbg_format(connections, F) -> 2604 ssh_dbg_format(terminate, F); 2605 2606ssh_dbg_format(connection_events, {call, {?MODULE,handle_event, [EventType, EventContent, State, _Data]}}) -> 2607 ["Connection event\n", 2608 io_lib:format("EventType: ~p~nEventContent: ~p~nState: ~p~n", [EventType, EventContent, State]) 2609 ]; 2610ssh_dbg_format(connection_events, {return_from, {?MODULE,handle_event,4}, Ret}) -> 2611 ["Connection event result\n", 2612 io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret, #data{})]) 2613 ]; 2614 2615ssh_dbg_format(renegotiation, {call, {?MODULE,init_renegotiate_timers,[OldState,NewState,D]}}) -> 2616 ["Renegotiation: start timer (init_renegotiate_timers)\n", 2617 io_lib:format("State: ~p --> ~p~n" 2618 "rekey_limit: ~p ({ms,bytes})~n" 2619 "check_data_size: ~p (ms)~n", 2620 [OldState, NewState, 2621 ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), 2622 ?REKEY_DATA_TIMOUT]) 2623 ]; 2624ssh_dbg_format(renegotiation, {return_from, {?MODULE,init_renegotiate_timers,3}, _Ret}) -> 2625 skip; 2626 2627ssh_dbg_format(renegotiation, {call, {?MODULE,renegotiate,[ConnectionHandler]}}) -> 2628 ["Renegotiation: renegotiation forced\n", 2629 io_lib:format("~p:renegotiate(~p) called~n", 2630 [?MODULE,ConnectionHandler]) 2631 ]; 2632ssh_dbg_format(renegotiation, {return_from, {?MODULE,renegotiate,1}, _Ret}) -> 2633 skip; 2634 2635ssh_dbg_format(renegotiation, {call, {?MODULE,pause_renegotiate_timers,[OldState,NewState,_D]}}) -> 2636 ["Renegotiation: pause timers\n", 2637 io_lib:format("State: ~p --> ~p~n", 2638 [OldState, NewState]) 2639 ]; 2640ssh_dbg_format(renegotiation, {return_from, {?MODULE,pause_renegotiate_timers,3}, _Ret}) -> 2641 skip; 2642 2643ssh_dbg_format(renegotiation, {call, {?MODULE,start_rekeying,[_Role,_D]}}) -> 2644 ["Renegotiation: start rekeying\n"]; 2645ssh_dbg_format(renegotiation, {return_from, {?MODULE,start_rekeying,2}, _Ret}) -> 2646 skip; 2647 2648ssh_dbg_format(renegotiation, {call, {?MODULE,check_data_rekeying_dbg,[SentSinceRekey, MaxSent]}}) -> 2649 ["Renegotiation: check size of data sent\n", 2650 io_lib:format("TotalSentSinceRekey: ~p~nMaxBeforeRekey: ~p~nStartRekey: ~p~n", 2651 [SentSinceRekey, MaxSent, SentSinceRekey >= MaxSent]) 2652 ]; 2653ssh_dbg_format(renegotiation, {return_from, {?MODULE,check_data_rekeying_dbg,2}, _Ret}) -> 2654 skip; 2655 2656 2657ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, StateName, D]}}) -> 2658 ExtraInfo = 2659 try 2660 {conn_info(peer,D), 2661 conn_info(user,D), 2662 conn_info(sockname,D)} 2663 of 2664 {{_,{IPp,Portp}}, Usr, {IPs,Ports}} when is_tuple(IPp), is_tuple(IPs), 2665 is_integer(Portp), is_integer(Ports) -> 2666 io_lib:format("Peer=~s:~p, Local=~s:~p, User=~p", 2667 [inet:ntoa(IPp),Portp,inet:ntoa(IPs),Ports,Usr]); 2668 {Peer,Usr,Sockname} -> 2669 io_lib:format("Peer=~p, Local=~p, User=~p",[Peer,Sockname,Usr]) 2670 catch 2671 _:_ -> 2672 "" 2673 end, 2674 if 2675 Reason == normal ; 2676 Reason == shutdown ; 2677 element(1,Reason) == shutdown 2678 -> 2679 ["Connection Terminating:\n", 2680 io_lib:format("Reason: ~p, StateName: ~p~n~s", [Reason, StateName, ExtraInfo]) 2681 ]; 2682 2683 true -> 2684 ["Connection Terminating:\n", 2685 io_lib:format("Reason: ~p, StateName: ~p~n~s~nStateData = ~p", 2686 [Reason, StateName, ExtraInfo, clean(D)]) 2687 ] 2688 end; 2689ssh_dbg_format(renegotiation, {return_from, {?MODULE,terminate,3}, _Ret}) -> 2690 skip; 2691 2692ssh_dbg_format(disconnect, {call,{?MODULE,send_disconnect, 2693 [Code, Reason, DetailedText, Module, Line, StateName, _D]}}) -> 2694 ["Disconnecting:\n", 2695 io_lib:format(" Module = ~p, Line = ~p, StateName = ~p,~n" 2696 " Code = ~p, Reason = ~p,~n" 2697 " DetailedText =~n" 2698 " ~p", 2699 [Module, Line, StateName, Code, Reason, lists:flatten(DetailedText)]) 2700 ]; 2701ssh_dbg_format(renegotiation, {return_from, {?MODULE,send_disconnect,7}, _Ret}) -> 2702 skip. 2703 2704