1<?php 2 3/** 4 * Pure-PHP implementation of SSHv2. 5 * 6 * PHP version 5 7 * 8 * Here are some examples of how to use this library: 9 * <code> 10 * <?php 11 * include 'vendor/autoload.php'; 12 * 13 * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld'); 14 * if (!$ssh->login('username', 'password')) { 15 * exit('Login Failed'); 16 * } 17 * 18 * echo $ssh->exec('pwd'); 19 * echo $ssh->exec('ls -la'); 20 * ?> 21 * </code> 22 * 23 * <code> 24 * <?php 25 * include 'vendor/autoload.php'; 26 * 27 * $key = \phpseclib3\Crypt\PublicKeyLoader::load('...', '(optional) password'); 28 * 29 * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld'); 30 * if (!$ssh->login('username', $key)) { 31 * exit('Login Failed'); 32 * } 33 * 34 * echo $ssh->read('username@username:~$'); 35 * $ssh->write("ls -la\n"); 36 * echo $ssh->read('username@username:~$'); 37 * ?> 38 * </code> 39 * 40 * @author Jim Wigginton <terrafrost@php.net> 41 * @copyright 2007 Jim Wigginton 42 * @license http://www.opensource.org/licenses/mit-license.html MIT License 43 * @link http://phpseclib.sourceforge.net 44 */ 45 46namespace phpseclib3\Net; 47 48use phpseclib3\Common\Functions\Strings; 49use phpseclib3\Crypt\Blowfish; 50use phpseclib3\Crypt\ChaCha20; 51use phpseclib3\Crypt\Common\AsymmetricKey; 52use phpseclib3\Crypt\Common\PrivateKey; 53use phpseclib3\Crypt\Common\PublicKey; 54use phpseclib3\Crypt\Common\SymmetricKey; 55use phpseclib3\Crypt\DH; 56use phpseclib3\Crypt\DSA; 57use phpseclib3\Crypt\EC; 58use phpseclib3\Crypt\Hash; 59use phpseclib3\Crypt\Random; 60use phpseclib3\Crypt\RC4; 61use phpseclib3\Crypt\Rijndael; 62use phpseclib3\Crypt\RSA; 63use phpseclib3\Crypt\TripleDES; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification. 64use phpseclib3\Crypt\Twofish; 65use phpseclib3\Exception\ConnectionClosedException; 66use phpseclib3\Exception\InsufficientSetupException; 67use phpseclib3\Exception\NoSupportedAlgorithmsException; 68use phpseclib3\Exception\UnableToConnectException; 69use phpseclib3\Exception\UnsupportedAlgorithmException; 70use phpseclib3\Exception\UnsupportedCurveException; 71use phpseclib3\Math\BigInteger; 72use phpseclib3\System\SSH\Agent; 73 74/** 75 * Pure-PHP implementation of SSHv2. 76 * 77 * @author Jim Wigginton <terrafrost@php.net> 78 */ 79class SSH2 80{ 81 /**#@+ 82 * Compression Types 83 * 84 */ 85 /** 86 * No compression 87 */ 88 const NET_SSH2_COMPRESSION_NONE = 1; 89 /** 90 * zlib compression 91 */ 92 const NET_SSH2_COMPRESSION_ZLIB = 2; 93 /** 94 * zlib@openssh.com 95 */ 96 const NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH = 3; 97 /**#@-*/ 98 99 // Execution Bitmap Masks 100 const MASK_CONSTRUCTOR = 0x00000001; 101 const MASK_CONNECTED = 0x00000002; 102 const MASK_LOGIN_REQ = 0x00000004; 103 const MASK_LOGIN = 0x00000008; 104 const MASK_SHELL = 0x00000010; 105 const MASK_WINDOW_ADJUST = 0x00000020; 106 107 /* 108 * Channel constants 109 * 110 * RFC4254 refers not to client and server channels but rather to sender and recipient channels. we don't refer 111 * to them in that way because RFC4254 toggles the meaning. the client sends a SSH_MSG_CHANNEL_OPEN message with 112 * a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response, with a sender and a 113 * recipient channel. at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel 114 * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snippet: 115 * The 'recipient channel' is the channel number given in the original 116 * open request, and 'sender channel' is the channel number allocated by 117 * the other side. 118 * 119 * @see \phpseclib3\Net\SSH2::send_channel_packet() 120 * @see \phpseclib3\Net\SSH2::get_channel_packet() 121 */ 122 const CHANNEL_EXEC = 1; // PuTTy uses 0x100 123 const CHANNEL_SHELL = 2; 124 const CHANNEL_SUBSYSTEM = 3; 125 const CHANNEL_AGENT_FORWARD = 4; 126 const CHANNEL_KEEP_ALIVE = 5; 127 128 /** 129 * Returns the message numbers 130 * 131 * @see \phpseclib3\Net\SSH2::getLog() 132 */ 133 const LOG_SIMPLE = 1; 134 /** 135 * Returns the message content 136 * 137 * @see \phpseclib3\Net\SSH2::getLog() 138 */ 139 const LOG_COMPLEX = 2; 140 /** 141 * Outputs the content real-time 142 */ 143 const LOG_REALTIME = 3; 144 /** 145 * Dumps the content real-time to a file 146 */ 147 const LOG_REALTIME_FILE = 4; 148 /** 149 * Outputs the message numbers real-time 150 */ 151 const LOG_SIMPLE_REALTIME = 5; 152 /** 153 * Make sure that the log never gets larger than this 154 * 155 * @see \phpseclib3\Net\SSH2::getLog() 156 */ 157 const LOG_MAX_SIZE = 1048576; // 1024 * 1024 158 159 /** 160 * Returns when a string matching $expect exactly is found 161 * 162 * @see \phpseclib3\Net\SSH2::read() 163 */ 164 const READ_SIMPLE = 1; 165 /** 166 * Returns when a string matching the regular expression $expect is found 167 * 168 * @see \phpseclib3\Net\SSH2::read() 169 */ 170 const READ_REGEX = 2; 171 /** 172 * Returns whenever a data packet is received. 173 * 174 * Some data packets may only contain a single character so it may be necessary 175 * to call read() multiple times when using this option 176 * 177 * @see \phpseclib3\Net\SSH2::read() 178 */ 179 const READ_NEXT = 3; 180 181 /** 182 * The SSH identifier 183 * 184 * @var string 185 */ 186 private $identifier; 187 188 /** 189 * The Socket Object 190 * 191 * @var resource|closed-resource|null 192 */ 193 public $fsock; 194 195 /** 196 * Execution Bitmap 197 * 198 * The bits that are set represent functions that have been called already. This is used to determine 199 * if a requisite function has been successfully executed. If not, an error should be thrown. 200 * 201 * @var int 202 */ 203 protected $bitmap = 0; 204 205 /** 206 * Error information 207 * 208 * @see self::getErrors() 209 * @see self::getLastError() 210 * @var array 211 */ 212 private $errors = []; 213 214 /** 215 * Server Identifier 216 * 217 * @see self::getServerIdentification() 218 * @var string|false 219 */ 220 protected $server_identifier = false; 221 222 /** 223 * Key Exchange Algorithms 224 * 225 * @see self::getKexAlgorithims() 226 * @var array|false 227 */ 228 private $kex_algorithms = false; 229 230 /** 231 * Key Exchange Algorithm 232 * 233 * @see self::getMethodsNegotiated() 234 * @var string|false 235 */ 236 private $kex_algorithm = false; 237 238 /** 239 * Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods 240 * 241 * @see self::_key_exchange() 242 * @var int 243 */ 244 private $kex_dh_group_size_min = 1536; 245 246 /** 247 * Preferred Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods 248 * 249 * @see self::_key_exchange() 250 * @var int 251 */ 252 private $kex_dh_group_size_preferred = 2048; 253 254 /** 255 * Maximum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods 256 * 257 * @see self::_key_exchange() 258 * @var int 259 */ 260 private $kex_dh_group_size_max = 4096; 261 262 /** 263 * Server Host Key Algorithms 264 * 265 * @see self::getServerHostKeyAlgorithms() 266 * @var array|false 267 */ 268 private $server_host_key_algorithms = false; 269 270 /** 271 * Supported Private Key Algorithms 272 * 273 * In theory this should be the same as the Server Host Key Algorithms but, in practice, 274 * some servers (eg. Azure) will support rsa-sha2-512 as a server host key algorithm but 275 * not a private key algorithm 276 * 277 * @see self::privatekey_login() 278 * @var array|false 279 */ 280 private $supported_private_key_algorithms = false; 281 282 /** 283 * Encryption Algorithms: Client to Server 284 * 285 * @see self::getEncryptionAlgorithmsClient2Server() 286 * @var array|false 287 */ 288 private $encryption_algorithms_client_to_server = false; 289 290 /** 291 * Encryption Algorithms: Server to Client 292 * 293 * @see self::getEncryptionAlgorithmsServer2Client() 294 * @var array|false 295 */ 296 private $encryption_algorithms_server_to_client = false; 297 298 /** 299 * MAC Algorithms: Client to Server 300 * 301 * @see self::getMACAlgorithmsClient2Server() 302 * @var array|false 303 */ 304 private $mac_algorithms_client_to_server = false; 305 306 /** 307 * MAC Algorithms: Server to Client 308 * 309 * @see self::getMACAlgorithmsServer2Client() 310 * @var array|false 311 */ 312 private $mac_algorithms_server_to_client = false; 313 314 /** 315 * Compression Algorithms: Client to Server 316 * 317 * @see self::getCompressionAlgorithmsClient2Server() 318 * @var array|false 319 */ 320 private $compression_algorithms_client_to_server = false; 321 322 /** 323 * Compression Algorithms: Server to Client 324 * 325 * @see self::getCompressionAlgorithmsServer2Client() 326 * @var array|false 327 */ 328 private $compression_algorithms_server_to_client = false; 329 330 /** 331 * Languages: Server to Client 332 * 333 * @see self::getLanguagesServer2Client() 334 * @var array|false 335 */ 336 private $languages_server_to_client = false; 337 338 /** 339 * Languages: Client to Server 340 * 341 * @see self::getLanguagesClient2Server() 342 * @var array|false 343 */ 344 private $languages_client_to_server = false; 345 346 /** 347 * Preferred Algorithms 348 * 349 * @see self::setPreferredAlgorithms() 350 * @var array 351 */ 352 private $preferred = []; 353 354 /** 355 * Block Size for Server to Client Encryption 356 * 357 * "Note that the length of the concatenation of 'packet_length', 358 * 'padding_length', 'payload', and 'random padding' MUST be a multiple 359 * of the cipher block size or 8, whichever is larger. This constraint 360 * MUST be enforced, even when using stream ciphers." 361 * 362 * -- http://tools.ietf.org/html/rfc4253#section-6 363 * 364 * @see self::__construct() 365 * @see self::_send_binary_packet() 366 * @var int 367 */ 368 private $encrypt_block_size = 8; 369 370 /** 371 * Block Size for Client to Server Encryption 372 * 373 * @see self::__construct() 374 * @see self::_get_binary_packet() 375 * @var int 376 */ 377 private $decrypt_block_size = 8; 378 379 /** 380 * Server to Client Encryption Object 381 * 382 * @see self::_get_binary_packet() 383 * @var SymmetricKey|false 384 */ 385 private $decrypt = false; 386 387 /** 388 * Decryption Algorithm Name 389 * 390 * @var string|null 391 */ 392 private $decryptName; 393 394 /** 395 * Decryption Invocation Counter 396 * 397 * Used by GCM 398 * 399 * @var string|null 400 */ 401 private $decryptInvocationCounter; 402 403 /** 404 * Fixed Part of Nonce 405 * 406 * Used by GCM 407 * 408 * @var string|null 409 */ 410 private $decryptFixedPart; 411 412 /** 413 * Server to Client Length Encryption Object 414 * 415 * @see self::_get_binary_packet() 416 * @var object 417 */ 418 private $lengthDecrypt = false; 419 420 /** 421 * Client to Server Encryption Object 422 * 423 * @see self::_send_binary_packet() 424 * @var SymmetricKey|false 425 */ 426 private $encrypt = false; 427 428 /** 429 * Encryption Algorithm Name 430 * 431 * @var string|null 432 */ 433 private $encryptName; 434 435 /** 436 * Encryption Invocation Counter 437 * 438 * Used by GCM 439 * 440 * @var string|null 441 */ 442 private $encryptInvocationCounter; 443 444 /** 445 * Fixed Part of Nonce 446 * 447 * Used by GCM 448 * 449 * @var string|null 450 */ 451 private $encryptFixedPart; 452 453 /** 454 * Client to Server Length Encryption Object 455 * 456 * @see self::_send_binary_packet() 457 * @var object 458 */ 459 private $lengthEncrypt = false; 460 461 /** 462 * Client to Server HMAC Object 463 * 464 * @see self::_send_binary_packet() 465 * @var object 466 */ 467 private $hmac_create = false; 468 469 /** 470 * Client to Server HMAC Name 471 * 472 * @var string|false 473 */ 474 private $hmac_create_name; 475 476 /** 477 * Client to Server ETM 478 * 479 * @var int|false 480 */ 481 private $hmac_create_etm; 482 483 /** 484 * Server to Client HMAC Object 485 * 486 * @see self::_get_binary_packet() 487 * @var object 488 */ 489 private $hmac_check = false; 490 491 /** 492 * Server to Client HMAC Name 493 * 494 * @var string|false 495 */ 496 private $hmac_check_name; 497 498 /** 499 * Server to Client ETM 500 * 501 * @var int|false 502 */ 503 private $hmac_check_etm; 504 505 /** 506 * Size of server to client HMAC 507 * 508 * We need to know how big the HMAC will be for the server to client direction so that we know how many bytes to read. 509 * For the client to server side, the HMAC object will make the HMAC as long as it needs to be. All we need to do is 510 * append it. 511 * 512 * @see self::_get_binary_packet() 513 * @var int 514 */ 515 private $hmac_size = false; 516 517 /** 518 * Server Public Host Key 519 * 520 * @see self::getServerPublicHostKey() 521 * @var string 522 */ 523 private $server_public_host_key; 524 525 /** 526 * Session identifier 527 * 528 * "The exchange hash H from the first key exchange is additionally 529 * used as the session identifier, which is a unique identifier for 530 * this connection." 531 * 532 * -- http://tools.ietf.org/html/rfc4253#section-7.2 533 * 534 * @see self::_key_exchange() 535 * @var string 536 */ 537 private $session_id = false; 538 539 /** 540 * Exchange hash 541 * 542 * The current exchange hash 543 * 544 * @see self::_key_exchange() 545 * @var string 546 */ 547 private $exchange_hash = false; 548 549 /** 550 * Message Numbers 551 * 552 * @see self::__construct() 553 * @var array 554 * @access private 555 */ 556 private static $message_numbers = []; 557 558 /** 559 * Disconnection Message 'reason codes' defined in RFC4253 560 * 561 * @see self::__construct() 562 * @var array 563 * @access private 564 */ 565 private static $disconnect_reasons = []; 566 567 /** 568 * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254 569 * 570 * @see self::__construct() 571 * @var array 572 * @access private 573 */ 574 private static $channel_open_failure_reasons = []; 575 576 /** 577 * Terminal Modes 578 * 579 * @link http://tools.ietf.org/html/rfc4254#section-8 580 * @see self::__construct() 581 * @var array 582 * @access private 583 */ 584 private static $terminal_modes = []; 585 586 /** 587 * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes 588 * 589 * @link http://tools.ietf.org/html/rfc4254#section-5.2 590 * @see self::__construct() 591 * @var array 592 * @access private 593 */ 594 private static $channel_extended_data_type_codes = []; 595 596 /** 597 * Send Sequence Number 598 * 599 * See 'Section 6.4. Data Integrity' of rfc4253 for more info. 600 * 601 * @see self::_send_binary_packet() 602 * @var int 603 */ 604 private $send_seq_no = 0; 605 606 /** 607 * Get Sequence Number 608 * 609 * See 'Section 6.4. Data Integrity' of rfc4253 for more info. 610 * 611 * @see self::_get_binary_packet() 612 * @var int 613 */ 614 private $get_seq_no = 0; 615 616 /** 617 * Server Channels 618 * 619 * Maps client channels to server channels 620 * 621 * @see self::get_channel_packet() 622 * @see self::exec() 623 * @var array 624 */ 625 protected $server_channels = []; 626 627 /** 628 * Channel Buffers 629 * 630 * If a client requests a packet from one channel but receives two packets from another those packets should 631 * be placed in a buffer 632 * 633 * @see self::get_channel_packet() 634 * @see self::exec() 635 * @var array 636 */ 637 private $channel_buffers = []; 638 639 /** 640 * Channel Status 641 * 642 * Contains the type of the last sent message 643 * 644 * @see self::get_channel_packet() 645 * @var array 646 */ 647 protected $channel_status = []; 648 649 /** 650 * The identifier of the interactive channel which was opened most recently 651 * 652 * @see self::getInteractiveChannelId() 653 * @var int 654 */ 655 private $channel_id_last_interactive = 0; 656 657 /** 658 * Packet Size 659 * 660 * Maximum packet size indexed by channel 661 * 662 * @see self::send_channel_packet() 663 * @var array 664 */ 665 private $packet_size_client_to_server = []; 666 667 /** 668 * Message Number Log 669 * 670 * @see self::getLog() 671 * @var array 672 */ 673 private $message_number_log = []; 674 675 /** 676 * Message Log 677 * 678 * @see self::getLog() 679 * @var array 680 */ 681 private $message_log = []; 682 683 /** 684 * The Window Size 685 * 686 * Bytes the other party can send before it must wait for the window to be adjusted (0x7FFFFFFF = 2GB) 687 * 688 * @var int 689 * @see self::send_channel_packet() 690 * @see self::exec() 691 */ 692 protected $window_size = 0x7FFFFFFF; 693 694 /** 695 * What we resize the window to 696 * 697 * When PuTTY resizes the window it doesn't add an additional 0x7FFFFFFF bytes - it adds 0x40000000 bytes. 698 * Some SFTP clients (GoAnywhere) don't support adding 0x7FFFFFFF to the window size after the fact so 699 * we'll just do what PuTTY does 700 * 701 * @var int 702 * @see self::_send_channel_packet() 703 * @see self::exec() 704 */ 705 private $window_resize = 0x40000000; 706 707 /** 708 * Window size, server to client 709 * 710 * Window size indexed by channel 711 * 712 * @see self::send_channel_packet() 713 * @var array 714 */ 715 protected $window_size_server_to_client = []; 716 717 /** 718 * Window size, client to server 719 * 720 * Window size indexed by channel 721 * 722 * @see self::get_channel_packet() 723 * @var array 724 */ 725 private $window_size_client_to_server = []; 726 727 /** 728 * Server signature 729 * 730 * Verified against $this->session_id 731 * 732 * @see self::getServerPublicHostKey() 733 * @var string 734 */ 735 private $signature = ''; 736 737 /** 738 * Server signature format 739 * 740 * ssh-rsa or ssh-dss. 741 * 742 * @see self::getServerPublicHostKey() 743 * @var string 744 */ 745 private $signature_format = ''; 746 747 /** 748 * Interactive Buffer 749 * 750 * @see self::read() 751 * @var string 752 */ 753 private $interactiveBuffer = ''; 754 755 /** 756 * Current log size 757 * 758 * Should never exceed self::LOG_MAX_SIZE 759 * 760 * @see self::_send_binary_packet() 761 * @see self::_get_binary_packet() 762 * @var int 763 */ 764 private $log_size; 765 766 /** 767 * Timeout 768 * 769 * @see self::setTimeout() 770 */ 771 protected $timeout; 772 773 /** 774 * Current Timeout 775 * 776 * @see self::get_channel_packet() 777 */ 778 protected $curTimeout; 779 780 /** 781 * Keep Alive Interval 782 * 783 * @see self::setKeepAlive() 784 */ 785 private $keepAlive; 786 787 /** 788 * Real-time log file pointer 789 * 790 * @see self::_append_log() 791 * @var resource|closed-resource 792 */ 793 private $realtime_log_file; 794 795 /** 796 * Real-time log file size 797 * 798 * @see self::_append_log() 799 * @var int 800 */ 801 private $realtime_log_size; 802 803 /** 804 * Has the signature been validated? 805 * 806 * @see self::getServerPublicHostKey() 807 * @var bool 808 */ 809 private $signature_validated = false; 810 811 /** 812 * Real-time log file wrap boolean 813 * 814 * @see self::_append_log() 815 * @var bool 816 */ 817 private $realtime_log_wrap; 818 819 /** 820 * Flag to suppress stderr from output 821 * 822 * @see self::enableQuietMode() 823 */ 824 private $quiet_mode = false; 825 826 /** 827 * Time of first network activity 828 * 829 * @var float 830 */ 831 private $last_packet; 832 833 /** 834 * Exit status returned from ssh if any 835 * 836 * @var int 837 */ 838 private $exit_status; 839 840 /** 841 * Flag to request a PTY when using exec() 842 * 843 * @var bool 844 * @see self::enablePTY() 845 */ 846 private $request_pty = false; 847 848 /** 849 * Contents of stdError 850 * 851 * @var string 852 */ 853 private $stdErrorLog; 854 855 /** 856 * The Last Interactive Response 857 * 858 * @see self::_keyboard_interactive_process() 859 * @var string 860 */ 861 private $last_interactive_response = ''; 862 863 /** 864 * Keyboard Interactive Request / Responses 865 * 866 * @see self::_keyboard_interactive_process() 867 * @var array 868 */ 869 private $keyboard_requests_responses = []; 870 871 /** 872 * Banner Message 873 * 874 * Quoting from the RFC, "in some jurisdictions, sending a warning message before 875 * authentication may be relevant for getting legal protection." 876 * 877 * @see self::_filter() 878 * @see self::getBannerMessage() 879 * @var string 880 */ 881 private $banner_message = ''; 882 883 /** 884 * Did read() timeout or return normally? 885 * 886 * @see self::isTimeout() 887 * @var bool 888 */ 889 private $is_timeout = false; 890 891 /** 892 * Log Boundary 893 * 894 * @see self::_format_log() 895 * @var string 896 */ 897 private $log_boundary = ':'; 898 899 /** 900 * Log Long Width 901 * 902 * @see self::_format_log() 903 * @var int 904 */ 905 private $log_long_width = 65; 906 907 /** 908 * Log Short Width 909 * 910 * @see self::_format_log() 911 * @var int 912 */ 913 private $log_short_width = 16; 914 915 /** 916 * Hostname 917 * 918 * @see self::__construct() 919 * @see self::_connect() 920 * @var string 921 */ 922 private $host; 923 924 /** 925 * Port Number 926 * 927 * @see self::__construct() 928 * @see self::_connect() 929 * @var int 930 */ 931 private $port; 932 933 /** 934 * Number of columns for terminal window size 935 * 936 * @see self::getWindowColumns() 937 * @see self::setWindowColumns() 938 * @see self::setWindowSize() 939 * @var int 940 */ 941 private $windowColumns = 80; 942 943 /** 944 * Number of columns for terminal window size 945 * 946 * @see self::getWindowRows() 947 * @see self::setWindowRows() 948 * @see self::setWindowSize() 949 * @var int 950 */ 951 private $windowRows = 24; 952 953 /** 954 * Crypto Engine 955 * 956 * @see self::setCryptoEngine() 957 * @see self::_key_exchange() 958 * @var int 959 */ 960 private static $crypto_engine = false; 961 962 /** 963 * A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario 964 * 965 * @var Agent 966 */ 967 private $agent; 968 969 /** 970 * Connection storage to replicates ssh2 extension functionality: 971 * {@link http://php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-examples} 972 * 973 * @var array<string, SSH2|\WeakReference<SSH2>> 974 */ 975 private static $connections; 976 977 /** 978 * Send the identification string first? 979 * 980 * @var bool 981 */ 982 private $send_id_string_first = true; 983 984 /** 985 * Send the key exchange initiation packet first? 986 * 987 * @var bool 988 */ 989 private $send_kex_first = true; 990 991 /** 992 * Some versions of OpenSSH incorrectly calculate the key size 993 * 994 * @var bool 995 */ 996 private $bad_key_size_fix = false; 997 998 /** 999 * Should we try to re-connect to re-establish keys? 1000 * 1001 * @var bool 1002 */ 1003 private $retry_connect = false; 1004 1005 /** 1006 * Binary Packet Buffer 1007 * 1008 * @var string|false 1009 */ 1010 private $binary_packet_buffer = false; 1011 1012 /** 1013 * Preferred Signature Format 1014 * 1015 * @var string|false 1016 */ 1017 protected $preferred_signature_format = false; 1018 1019 /** 1020 * Authentication Credentials 1021 * 1022 * @var array 1023 */ 1024 protected $auth = []; 1025 1026 /** 1027 * Terminal 1028 * 1029 * @var string 1030 */ 1031 private $term = 'vt100'; 1032 1033 /** 1034 * The authentication methods that may productively continue authentication. 1035 * 1036 * @see https://tools.ietf.org/html/rfc4252#section-5.1 1037 * @var array|null 1038 */ 1039 private $auth_methods_to_continue = null; 1040 1041 /** 1042 * Compression method 1043 * 1044 * @var int 1045 */ 1046 private $compress = self::NET_SSH2_COMPRESSION_NONE; 1047 1048 /** 1049 * Decompression method 1050 * 1051 * @var int 1052 */ 1053 private $decompress = self::NET_SSH2_COMPRESSION_NONE; 1054 1055 /** 1056 * Compression context 1057 * 1058 * @var resource|false|null 1059 */ 1060 private $compress_context; 1061 1062 /** 1063 * Decompression context 1064 * 1065 * @var resource|object 1066 */ 1067 private $decompress_context; 1068 1069 /** 1070 * Regenerate Compression Context 1071 * 1072 * @var bool 1073 */ 1074 private $regenerate_compression_context = false; 1075 1076 /** 1077 * Regenerate Decompression Context 1078 * 1079 * @var bool 1080 */ 1081 private $regenerate_decompression_context = false; 1082 1083 /** 1084 * Smart multi-factor authentication flag 1085 * 1086 * @var bool 1087 */ 1088 private $smartMFA = true; 1089 1090 /** 1091 * How many channels are currently opened 1092 * 1093 * @var int 1094 */ 1095 private $channelCount = 0; 1096 1097 /** 1098 * Does the server support multiple channels? If not then error out 1099 * when multiple channels are attempted to be opened 1100 * 1101 * @var bool 1102 */ 1103 private $errorOnMultipleChannels; 1104 1105 /** 1106 * Terrapin Countermeasure 1107 * 1108 * "During initial KEX, terminate the connection if any unexpected or out-of-sequence packet is received" 1109 * -- https://github.com/openssh/openssh-portable/commit/1edb00c58f8a6875fad6a497aa2bacf37f9e6cd5 1110 * 1111 * @var int 1112 */ 1113 private $extra_packets; 1114 1115 /** 1116 * Default Constructor. 1117 * 1118 * $host can either be a string, representing the host, or a stream resource. 1119 * If $host is a stream resource then $port doesn't do anything, altho $timeout 1120 * still will be used 1121 * 1122 * @param mixed $host 1123 * @param int $port 1124 * @param int $timeout 1125 * @see self::login() 1126 */ 1127 public function __construct($host, $port = 22, $timeout = 10) 1128 { 1129 if (empty(self::$message_numbers)) { 1130 self::$message_numbers = [ 1131 1 => 'NET_SSH2_MSG_DISCONNECT', 1132 2 => 'NET_SSH2_MSG_IGNORE', 1133 3 => 'NET_SSH2_MSG_UNIMPLEMENTED', 1134 4 => 'NET_SSH2_MSG_DEBUG', 1135 5 => 'NET_SSH2_MSG_SERVICE_REQUEST', 1136 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT', 1137 7 => 'NET_SSH2_MSG_EXT_INFO', // RFC 8308 1138 20 => 'NET_SSH2_MSG_KEXINIT', 1139 21 => 'NET_SSH2_MSG_NEWKEYS', 1140 30 => 'NET_SSH2_MSG_KEXDH_INIT', 1141 31 => 'NET_SSH2_MSG_KEXDH_REPLY', 1142 50 => 'NET_SSH2_MSG_USERAUTH_REQUEST', 1143 51 => 'NET_SSH2_MSG_USERAUTH_FAILURE', 1144 52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS', 1145 53 => 'NET_SSH2_MSG_USERAUTH_BANNER', 1146 1147 80 => 'NET_SSH2_MSG_GLOBAL_REQUEST', 1148 81 => 'NET_SSH2_MSG_REQUEST_SUCCESS', 1149 82 => 'NET_SSH2_MSG_REQUEST_FAILURE', 1150 90 => 'NET_SSH2_MSG_CHANNEL_OPEN', 1151 91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION', 1152 92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE', 1153 93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST', 1154 94 => 'NET_SSH2_MSG_CHANNEL_DATA', 1155 95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA', 1156 96 => 'NET_SSH2_MSG_CHANNEL_EOF', 1157 97 => 'NET_SSH2_MSG_CHANNEL_CLOSE', 1158 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST', 1159 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS', 1160 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE' 1161 ]; 1162 self::$disconnect_reasons = [ 1163 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT', 1164 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR', 1165 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED', 1166 4 => 'NET_SSH2_DISCONNECT_RESERVED', 1167 5 => 'NET_SSH2_DISCONNECT_MAC_ERROR', 1168 6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR', 1169 7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE', 1170 8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED', 1171 9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE', 1172 10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST', 1173 11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION', 1174 12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS', 1175 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER', 1176 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE', 1177 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME' 1178 ]; 1179 self::$channel_open_failure_reasons = [ 1180 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED' 1181 ]; 1182 self::$terminal_modes = [ 1183 0 => 'NET_SSH2_TTY_OP_END' 1184 ]; 1185 self::$channel_extended_data_type_codes = [ 1186 1 => 'NET_SSH2_EXTENDED_DATA_STDERR' 1187 ]; 1188 1189 self::define_array( 1190 self::$message_numbers, 1191 self::$disconnect_reasons, 1192 self::$channel_open_failure_reasons, 1193 self::$terminal_modes, 1194 self::$channel_extended_data_type_codes, 1195 [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'], 1196 [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'], 1197 [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST', 1198 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'], 1199 // RFC 4419 - diffie-hellman-group-exchange-sha{1,256} 1200 [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD', 1201 31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP', 1202 32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT', 1203 33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY', 1204 34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'], 1205 // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org) 1206 [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT', 1207 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY'] 1208 ); 1209 } 1210 1211 /** 1212 * Typehint is required due to a bug in Psalm: https://github.com/vimeo/psalm/issues/7508 1213 * @var \WeakReference<SSH2>|SSH2 1214 */ 1215 self::$connections[$this->getResourceId()] = class_exists('WeakReference') 1216 ? \WeakReference::create($this) 1217 : $this; 1218 1219 $this->timeout = $timeout; 1220 1221 if (is_resource($host)) { 1222 $this->fsock = $host; 1223 return; 1224 } 1225 1226 if (Strings::is_stringable($host)) { 1227 $this->host = $host; 1228 $this->port = $port; 1229 } 1230 } 1231 1232 /** 1233 * Set Crypto Engine Mode 1234 * 1235 * Possible $engine values: 1236 * OpenSSL, mcrypt, Eval, PHP 1237 * 1238 * @param int $engine 1239 */ 1240 public static function setCryptoEngine($engine) 1241 { 1242 self::$crypto_engine = $engine; 1243 } 1244 1245 /** 1246 * Send Identification String First 1247 * 1248 * https://tools.ietf.org/html/rfc4253#section-4.2 says "when the connection has been established, 1249 * both sides MUST send an identification string". It does not say which side sends it first. In 1250 * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy 1251 * 1252 */ 1253 public function sendIdentificationStringFirst() 1254 { 1255 $this->send_id_string_first = true; 1256 } 1257 1258 /** 1259 * Send Identification String Last 1260 * 1261 * https://tools.ietf.org/html/rfc4253#section-4.2 says "when the connection has been established, 1262 * both sides MUST send an identification string". It does not say which side sends it first. In 1263 * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy 1264 * 1265 */ 1266 public function sendIdentificationStringLast() 1267 { 1268 $this->send_id_string_first = false; 1269 } 1270 1271 /** 1272 * Send SSH_MSG_KEXINIT First 1273 * 1274 * https://tools.ietf.org/html/rfc4253#section-7.1 says "key exchange begins by each sending 1275 * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory 1276 * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy 1277 * 1278 */ 1279 public function sendKEXINITFirst() 1280 { 1281 $this->send_kex_first = true; 1282 } 1283 1284 /** 1285 * Send SSH_MSG_KEXINIT Last 1286 * 1287 * https://tools.ietf.org/html/rfc4253#section-7.1 says "key exchange begins by each sending 1288 * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory 1289 * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy 1290 * 1291 */ 1292 public function sendKEXINITLast() 1293 { 1294 $this->send_kex_first = false; 1295 } 1296 1297 /** 1298 * stream_select wrapper 1299 * 1300 * Quoting https://stackoverflow.com/a/14262151/569976, 1301 * "The general approach to `EINTR` is to simply handle the error and retry the operation again" 1302 * 1303 * This wrapper does that loop 1304 */ 1305 private static function stream_select(&$read, &$write, &$except, $seconds, $microseconds = null) 1306 { 1307 $remaining = $seconds + $microseconds / 1000000; 1308 $start = microtime(true); 1309 while (true) { 1310 $result = @stream_select($read, $write, $except, $seconds, $microseconds); 1311 if ($result !== false) { 1312 return $result; 1313 } 1314 $elapsed = microtime(true) - $start; 1315 $seconds = (int) ($remaining - floor($elapsed)); 1316 $microseconds = (int) (1000000 * ($remaining - $seconds)); 1317 if ($elapsed >= $remaining) { 1318 return false; 1319 } 1320 } 1321 } 1322 1323 /** 1324 * Connect to an SSHv2 server 1325 * 1326 * @throws \UnexpectedValueException on receipt of unexpected packets 1327 * @throws \RuntimeException on other errors 1328 */ 1329 private function connect() 1330 { 1331 if ($this->bitmap & self::MASK_CONSTRUCTOR) { 1332 return; 1333 } 1334 1335 $this->bitmap |= self::MASK_CONSTRUCTOR; 1336 1337 $this->curTimeout = $this->timeout; 1338 1339 $this->last_packet = microtime(true); 1340 1341 if (!is_resource($this->fsock)) { 1342 $start = microtime(true); 1343 // with stream_select a timeout of 0 means that no timeout takes place; 1344 // with fsockopen a timeout of 0 means that you instantly timeout 1345 // to resolve this incompatibility a timeout of 100,000 will be used for fsockopen if timeout is 0 1346 $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout == 0 ? 100000 : $this->curTimeout); 1347 if (!$this->fsock) { 1348 $host = $this->host . ':' . $this->port; 1349 throw new UnableToConnectException(rtrim("Cannot connect to $host. Error $errno. $errstr")); 1350 } 1351 $elapsed = microtime(true) - $start; 1352 1353 if ($this->curTimeout) { 1354 $this->curTimeout -= $elapsed; 1355 if ($this->curTimeout < 0) { 1356 throw new \RuntimeException('Connection timed out whilst attempting to open socket connection'); 1357 } 1358 } 1359 } 1360 1361 $this->identifier = $this->generate_identifier(); 1362 1363 if ($this->send_id_string_first) { 1364 fputs($this->fsock, $this->identifier . "\r\n"); 1365 } 1366 1367 /* According to the SSH2 specs, 1368 1369 "The server MAY send other lines of data before sending the version 1370 string. Each line SHOULD be terminated by a Carriage Return and Line 1371 Feed. Such lines MUST NOT begin with "SSH-", and SHOULD be encoded 1372 in ISO-10646 UTF-8 [RFC3629] (language is not specified). Clients 1373 MUST be able to process such lines." */ 1374 $data = ''; 1375 while (!feof($this->fsock) && !preg_match('#(.*)^(SSH-(\d\.\d+).*)#ms', $data, $matches)) { 1376 $line = ''; 1377 while (true) { 1378 if ($this->curTimeout) { 1379 if ($this->curTimeout < 0) { 1380 throw new \RuntimeException('Connection timed out whilst receiving server identification string'); 1381 } 1382 $read = [$this->fsock]; 1383 $write = $except = null; 1384 $start = microtime(true); 1385 $sec = (int) floor($this->curTimeout); 1386 $usec = (int) (1000000 * ($this->curTimeout - $sec)); 1387 if (static::stream_select($read, $write, $except, $sec, $usec) === false) { 1388 throw new \RuntimeException('Connection timed out whilst receiving server identification string'); 1389 } 1390 $elapsed = microtime(true) - $start; 1391 $this->curTimeout -= $elapsed; 1392 } 1393 1394 $temp = stream_get_line($this->fsock, 255, "\n"); 1395 if ($temp === false) { 1396 throw new \RuntimeException('Error reading from socket'); 1397 } 1398 if (strlen($temp) == 255) { 1399 continue; 1400 } 1401 1402 $line .= "$temp\n"; 1403 1404 // quoting RFC4253, "Implementers who wish to maintain 1405 // compatibility with older, undocumented versions of this protocol may 1406 // want to process the identification string without expecting the 1407 // presence of the carriage return character for reasons described in 1408 // Section 5 of this document." 1409 1410 //if (substr($line, -2) == "\r\n") { 1411 // break; 1412 //} 1413 1414 break; 1415 } 1416 1417 $data .= $line; 1418 } 1419 1420 if (feof($this->fsock)) { 1421 $this->bitmap = 0; 1422 throw new ConnectionClosedException('Connection closed by server'); 1423 } 1424 1425 $extra = $matches[1]; 1426 1427 if (defined('NET_SSH2_LOGGING')) { 1428 $this->append_log('<-', $matches[0]); 1429 $this->append_log('->', $this->identifier . "\r\n"); 1430 } 1431 1432 $this->server_identifier = trim($temp, "\r\n"); 1433 if (strlen($extra)) { 1434 $this->errors[] = $data; 1435 } 1436 1437 if (version_compare($matches[3], '1.99', '<')) { 1438 $this->bitmap = 0; 1439 throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers"); 1440 } 1441 1442 // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see 1443 // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info. 1444 // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses 1445 // when consolekit was incorporated. 1446 // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the 1447 // issues with how Ubuntu incorporated consolekit 1448 $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#'; 1449 $match = preg_match($pattern, $this->server_identifier, $matches); 1450 $match = $match && version_compare('5.8', $matches[1], '<='); 1451 $match = $match && version_compare('6.9', $matches[1], '>='); 1452 $this->errorOnMultipleChannels = $match; 1453 1454 if (!$this->send_id_string_first) { 1455 fputs($this->fsock, $this->identifier . "\r\n"); 1456 } 1457 1458 if (!$this->send_kex_first) { 1459 $response = $this->get_binary_packet(); 1460 1461 if (is_bool($response) || !strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { 1462 $this->bitmap = 0; 1463 throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); 1464 } 1465 1466 $this->key_exchange($response); 1467 } 1468 1469 if ($this->send_kex_first) { 1470 $this->key_exchange(); 1471 } 1472 1473 $this->bitmap |= self::MASK_CONNECTED; 1474 1475 return true; 1476 } 1477 1478 /** 1479 * Generates the SSH identifier 1480 * 1481 * You should overwrite this method in your own class if you want to use another identifier 1482 * 1483 * @return string 1484 */ 1485 private function generate_identifier() 1486 { 1487 $identifier = 'SSH-2.0-phpseclib_3.0'; 1488 1489 $ext = []; 1490 if (extension_loaded('sodium')) { 1491 $ext[] = 'libsodium'; 1492 } 1493 1494 if (extension_loaded('openssl')) { 1495 $ext[] = 'openssl'; 1496 } elseif (extension_loaded('mcrypt')) { 1497 $ext[] = 'mcrypt'; 1498 } 1499 1500 if (extension_loaded('gmp')) { 1501 $ext[] = 'gmp'; 1502 } elseif (extension_loaded('bcmath')) { 1503 $ext[] = 'bcmath'; 1504 } 1505 1506 if (!empty($ext)) { 1507 $identifier .= ' (' . implode(', ', $ext) . ')'; 1508 } 1509 1510 return $identifier; 1511 } 1512 1513 /** 1514 * Key Exchange 1515 * 1516 * @return bool 1517 * @param string|bool $kexinit_payload_server optional 1518 * @throws \UnexpectedValueException on receipt of unexpected packets 1519 * @throws \RuntimeException on other errors 1520 * @throws \phpseclib3\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible 1521 */ 1522 private function key_exchange($kexinit_payload_server = false) 1523 { 1524 $preferred = $this->preferred; 1525 $send_kex = true; 1526 1527 $kex_algorithms = isset($preferred['kex']) ? 1528 $preferred['kex'] : 1529 SSH2::getSupportedKEXAlgorithms(); 1530 $server_host_key_algorithms = isset($preferred['hostkey']) ? 1531 $preferred['hostkey'] : 1532 SSH2::getSupportedHostKeyAlgorithms(); 1533 $s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ? 1534 $preferred['server_to_client']['crypt'] : 1535 SSH2::getSupportedEncryptionAlgorithms(); 1536 $c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ? 1537 $preferred['client_to_server']['crypt'] : 1538 SSH2::getSupportedEncryptionAlgorithms(); 1539 $s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ? 1540 $preferred['server_to_client']['mac'] : 1541 SSH2::getSupportedMACAlgorithms(); 1542 $c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ? 1543 $preferred['client_to_server']['mac'] : 1544 SSH2::getSupportedMACAlgorithms(); 1545 $s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ? 1546 $preferred['server_to_client']['comp'] : 1547 SSH2::getSupportedCompressionAlgorithms(); 1548 $c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ? 1549 $preferred['client_to_server']['comp'] : 1550 SSH2::getSupportedCompressionAlgorithms(); 1551 1552 $kex_algorithms = array_merge($kex_algorithms, ['ext-info-c', 'kex-strict-c-v00@openssh.com']); 1553 1554 // some SSH servers have buggy implementations of some of the above algorithms 1555 switch (true) { 1556 case $this->server_identifier == 'SSH-2.0-SSHD': 1557 case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK': 1558 if (!isset($preferred['server_to_client']['mac'])) { 1559 $s2c_mac_algorithms = array_values(array_diff( 1560 $s2c_mac_algorithms, 1561 ['hmac-sha1-96', 'hmac-md5-96'] 1562 )); 1563 } 1564 if (!isset($preferred['client_to_server']['mac'])) { 1565 $c2s_mac_algorithms = array_values(array_diff( 1566 $c2s_mac_algorithms, 1567 ['hmac-sha1-96', 'hmac-md5-96'] 1568 )); 1569 } 1570 break; 1571 case substr($this->server_identifier, 0, 24) == 'SSH-2.0-TurboFTP_SERVER_': 1572 if (!isset($preferred['server_to_client']['crypt'])) { 1573 $s2c_encryption_algorithms = array_values(array_diff( 1574 $s2c_encryption_algorithms, 1575 ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'] 1576 )); 1577 } 1578 if (!isset($preferred['client_to_server']['crypt'])) { 1579 $c2s_encryption_algorithms = array_values(array_diff( 1580 $c2s_encryption_algorithms, 1581 ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'] 1582 )); 1583 } 1584 } 1585 1586 $client_cookie = Random::string(16); 1587 1588 $kexinit_payload_client = pack('Ca*', NET_SSH2_MSG_KEXINIT, $client_cookie); 1589 $kexinit_payload_client .= Strings::packSSH2( 1590 'L10bN', 1591 $kex_algorithms, 1592 $server_host_key_algorithms, 1593 $c2s_encryption_algorithms, 1594 $s2c_encryption_algorithms, 1595 $c2s_mac_algorithms, 1596 $s2c_mac_algorithms, 1597 $c2s_compression_algorithms, 1598 $s2c_compression_algorithms, 1599 [], // language, client to server 1600 [], // language, server to client 1601 false, // first_kex_packet_follows 1602 0 // reserved for future extension 1603 ); 1604 1605 if ($kexinit_payload_server === false) { 1606 $this->send_binary_packet($kexinit_payload_client); 1607 1608 $this->extra_packets = 0; 1609 $kexinit_payload_server = $this->get_binary_packet(); 1610 1611 if ( 1612 is_bool($kexinit_payload_server) 1613 || !strlen($kexinit_payload_server) 1614 || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT 1615 ) { 1616 $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); 1617 throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); 1618 } 1619 1620 $send_kex = false; 1621 } 1622 1623 $response = $kexinit_payload_server; 1624 Strings::shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) 1625 $server_cookie = Strings::shift($response, 16); 1626 1627 list( 1628 $this->kex_algorithms, 1629 $this->server_host_key_algorithms, 1630 $this->encryption_algorithms_client_to_server, 1631 $this->encryption_algorithms_server_to_client, 1632 $this->mac_algorithms_client_to_server, 1633 $this->mac_algorithms_server_to_client, 1634 $this->compression_algorithms_client_to_server, 1635 $this->compression_algorithms_server_to_client, 1636 $this->languages_client_to_server, 1637 $this->languages_server_to_client, 1638 $first_kex_packet_follows 1639 ) = Strings::unpackSSH2('L10C', $response); 1640 if (in_array('kex-strict-s-v00@openssh.com', $this->kex_algorithms)) { 1641 if ($this->session_id === false && $this->extra_packets) { 1642 throw new \UnexpectedValueException('Possible Terrapin Attack detected'); 1643 } 1644 } 1645 1646 $this->supported_private_key_algorithms = $this->server_host_key_algorithms; 1647 1648 if ($send_kex) { 1649 $this->send_binary_packet($kexinit_payload_client); 1650 } 1651 1652 // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange 1653 1654 // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the 1655 // diffie-hellman key exchange as fast as possible 1656 $decrypt = self::array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client); 1657 $decryptKeyLength = $this->encryption_algorithm_to_key_size($decrypt); 1658 if ($decryptKeyLength === null) { 1659 $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1660 throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found'); 1661 } 1662 1663 $encrypt = self::array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server); 1664 $encryptKeyLength = $this->encryption_algorithm_to_key_size($encrypt); 1665 if ($encryptKeyLength === null) { 1666 $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1667 throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found'); 1668 } 1669 1670 // through diffie-hellman key exchange a symmetric key is obtained 1671 $this->kex_algorithm = self::array_intersect_first($kex_algorithms, $this->kex_algorithms); 1672 if ($this->kex_algorithm === false) { 1673 $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1674 throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found'); 1675 } 1676 1677 $server_host_key_algorithm = self::array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms); 1678 if ($server_host_key_algorithm === false) { 1679 $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1680 throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found'); 1681 } 1682 1683 $mac_algorithm_out = self::array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server); 1684 if ($mac_algorithm_out === false) { 1685 $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1686 throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found'); 1687 } 1688 1689 $mac_algorithm_in = self::array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client); 1690 if ($mac_algorithm_in === false) { 1691 $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1692 throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found'); 1693 } 1694 1695 $compression_map = [ 1696 'none' => self::NET_SSH2_COMPRESSION_NONE, 1697 'zlib' => self::NET_SSH2_COMPRESSION_ZLIB, 1698 'zlib@openssh.com' => self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH 1699 ]; 1700 1701 $compression_algorithm_in = self::array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client); 1702 if ($compression_algorithm_in === false) { 1703 $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1704 throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found'); 1705 } 1706 $this->decompress = $compression_map[$compression_algorithm_in]; 1707 1708 $compression_algorithm_out = self::array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server); 1709 if ($compression_algorithm_out === false) { 1710 $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1711 throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found'); 1712 } 1713 $this->compress = $compression_map[$compression_algorithm_out]; 1714 1715 switch ($this->kex_algorithm) { 1716 case 'diffie-hellman-group15-sha512': 1717 case 'diffie-hellman-group16-sha512': 1718 case 'diffie-hellman-group17-sha512': 1719 case 'diffie-hellman-group18-sha512': 1720 case 'ecdh-sha2-nistp521': 1721 $kexHash = new Hash('sha512'); 1722 break; 1723 case 'ecdh-sha2-nistp384': 1724 $kexHash = new Hash('sha384'); 1725 break; 1726 case 'diffie-hellman-group-exchange-sha256': 1727 case 'diffie-hellman-group14-sha256': 1728 case 'ecdh-sha2-nistp256': 1729 case 'curve25519-sha256@libssh.org': 1730 case 'curve25519-sha256': 1731 $kexHash = new Hash('sha256'); 1732 break; 1733 default: 1734 $kexHash = new Hash('sha1'); 1735 } 1736 1737 // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty. 1738 1739 $exchange_hash_rfc4419 = ''; 1740 1741 if (strpos($this->kex_algorithm, 'curve25519-sha256') === 0 || strpos($this->kex_algorithm, 'ecdh-sha2-nistp') === 0) { 1742 $curve = strpos($this->kex_algorithm, 'curve25519-sha256') === 0 ? 1743 'Curve25519' : 1744 substr($this->kex_algorithm, 10); 1745 $ourPrivate = EC::createKey($curve); 1746 $ourPublicBytes = $ourPrivate->getPublicKey()->getEncodedCoordinates(); 1747 $clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT'; 1748 $serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY'; 1749 } else { 1750 if (strpos($this->kex_algorithm, 'diffie-hellman-group-exchange') === 0) { 1751 $dh_group_sizes_packed = pack( 1752 'NNN', 1753 $this->kex_dh_group_size_min, 1754 $this->kex_dh_group_size_preferred, 1755 $this->kex_dh_group_size_max 1756 ); 1757 $packet = pack( 1758 'Ca*', 1759 NET_SSH2_MSG_KEXDH_GEX_REQUEST, 1760 $dh_group_sizes_packed 1761 ); 1762 $this->send_binary_packet($packet); 1763 $this->updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'); 1764 1765 $response = $this->get_binary_packet(); 1766 1767 list($type, $primeBytes, $gBytes) = Strings::unpackSSH2('Css', $response); 1768 if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) { 1769 $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); 1770 throw new \UnexpectedValueException('Expected SSH_MSG_KEX_DH_GEX_GROUP'); 1771 } 1772 $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP'); 1773 $prime = new BigInteger($primeBytes, -256); 1774 $g = new BigInteger($gBytes, -256); 1775 1776 $exchange_hash_rfc4419 = $dh_group_sizes_packed . Strings::packSSH2( 1777 'ss', 1778 $primeBytes, 1779 $gBytes 1780 ); 1781 1782 $params = DH::createParameters($prime, $g); 1783 $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT'; 1784 $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY'; 1785 } else { 1786 $params = DH::createParameters($this->kex_algorithm); 1787 $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT'; 1788 $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY'; 1789 } 1790 1791 $keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength)); 1792 1793 $ourPrivate = DH::createKey($params, 16 * $keyLength); // 2 * 8 * $keyLength 1794 $ourPublic = $ourPrivate->getPublicKey()->toBigInteger(); 1795 $ourPublicBytes = $ourPublic->toBytes(true); 1796 } 1797 1798 $data = pack('CNa*', constant($clientKexInitMessage), strlen($ourPublicBytes), $ourPublicBytes); 1799 1800 $this->send_binary_packet($data); 1801 1802 switch ($clientKexInitMessage) { 1803 case 'NET_SSH2_MSG_KEX_ECDH_INIT': 1804 $this->updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT'); 1805 break; 1806 case 'NET_SSH2_MSG_KEXDH_GEX_INIT': 1807 $this->updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT'); 1808 } 1809 1810 $response = $this->get_binary_packet(); 1811 1812 list( 1813 $type, 1814 $server_public_host_key, 1815 $theirPublicBytes, 1816 $this->signature 1817 ) = Strings::unpackSSH2('Csss', $response); 1818 1819 if ($type != constant($serverKexReplyMessage)) { 1820 $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); 1821 throw new \UnexpectedValueException("Expected $serverKexReplyMessage"); 1822 } 1823 switch ($serverKexReplyMessage) { 1824 case 'NET_SSH2_MSG_KEX_ECDH_REPLY': 1825 $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY'); 1826 break; 1827 case 'NET_SSH2_MSG_KEXDH_GEX_REPLY': 1828 $this->updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY'); 1829 } 1830 1831 $this->server_public_host_key = $server_public_host_key; 1832 list($public_key_format) = Strings::unpackSSH2('s', $server_public_host_key); 1833 if (strlen($this->signature) < 4) { 1834 throw new \LengthException('The signature needs at least four bytes'); 1835 } 1836 $temp = unpack('Nlength', substr($this->signature, 0, 4)); 1837 $this->signature_format = substr($this->signature, 4, $temp['length']); 1838 1839 $keyBytes = DH::computeSecret($ourPrivate, $theirPublicBytes); 1840 if (($keyBytes & "\xFF\x80") === "\x00\x00") { 1841 $keyBytes = substr($keyBytes, 1); 1842 } elseif (($keyBytes[0] & "\x80") === "\x80") { 1843 $keyBytes = "\0$keyBytes"; 1844 } 1845 1846 $this->exchange_hash = Strings::packSSH2( 1847 's5', 1848 $this->identifier, 1849 $this->server_identifier, 1850 $kexinit_payload_client, 1851 $kexinit_payload_server, 1852 $this->server_public_host_key 1853 ); 1854 $this->exchange_hash .= $exchange_hash_rfc4419; 1855 $this->exchange_hash .= Strings::packSSH2( 1856 's3', 1857 $ourPublicBytes, 1858 $theirPublicBytes, 1859 $keyBytes 1860 ); 1861 1862 $this->exchange_hash = $kexHash->hash($this->exchange_hash); 1863 1864 if ($this->session_id === false) { 1865 $this->session_id = $this->exchange_hash; 1866 } 1867 1868 switch ($server_host_key_algorithm) { 1869 case 'rsa-sha2-256': 1870 case 'rsa-sha2-512': 1871 //case 'ssh-rsa': 1872 $expected_key_format = 'ssh-rsa'; 1873 break; 1874 default: 1875 $expected_key_format = $server_host_key_algorithm; 1876 } 1877 if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) { 1878 switch (true) { 1879 case $this->signature_format == $server_host_key_algorithm: 1880 case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512': 1881 case $this->signature_format != 'ssh-rsa': 1882 $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); 1883 throw new \RuntimeException('Server Host Key Algorithm Mismatch (' . $this->signature_format . ' vs ' . $server_host_key_algorithm . ')'); 1884 } 1885 } 1886 1887 $packet = pack('C', NET_SSH2_MSG_NEWKEYS); 1888 $this->send_binary_packet($packet); 1889 1890 $response = $this->get_binary_packet(); 1891 1892 if ($response === false) { 1893 $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); 1894 throw new ConnectionClosedException('Connection closed by server'); 1895 } 1896 1897 list($type) = Strings::unpackSSH2('C', $response); 1898 if ($type != NET_SSH2_MSG_NEWKEYS) { 1899 $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); 1900 throw new \UnexpectedValueException('Expected SSH_MSG_NEWKEYS'); 1901 } 1902 1903 if (in_array('kex-strict-s-v00@openssh.com', $this->kex_algorithms)) { 1904 $this->get_seq_no = $this->send_seq_no = 0; 1905 } 1906 1907 $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); 1908 1909 $this->encrypt = self::encryption_algorithm_to_crypt_instance($encrypt); 1910 if ($this->encrypt) { 1911 if (self::$crypto_engine) { 1912 $this->encrypt->setPreferredEngine(self::$crypto_engine); 1913 } 1914 if ($this->encrypt->getBlockLengthInBytes()) { 1915 $this->encrypt_block_size = $this->encrypt->getBlockLengthInBytes(); 1916 } 1917 $this->encrypt->disablePadding(); 1918 1919 if ($this->encrypt->usesIV()) { 1920 $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id); 1921 while ($this->encrypt_block_size > strlen($iv)) { 1922 $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); 1923 } 1924 $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size)); 1925 } 1926 1927 switch ($encrypt) { 1928 case 'aes128-gcm@openssh.com': 1929 case 'aes256-gcm@openssh.com': 1930 $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id); 1931 $this->encryptFixedPart = substr($nonce, 0, 4); 1932 $this->encryptInvocationCounter = substr($nonce, 4, 8); 1933 // fall-through 1934 case 'chacha20-poly1305@openssh.com': 1935 break; 1936 default: 1937 $this->encrypt->enableContinuousBuffer(); 1938 } 1939 1940 $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id); 1941 while ($encryptKeyLength > strlen($key)) { 1942 $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key); 1943 } 1944 switch ($encrypt) { 1945 case 'chacha20-poly1305@openssh.com': 1946 $encryptKeyLength = 32; 1947 $this->lengthEncrypt = self::encryption_algorithm_to_crypt_instance($encrypt); 1948 $this->lengthEncrypt->setKey(substr($key, 32, 32)); 1949 } 1950 $this->encrypt->setKey(substr($key, 0, $encryptKeyLength)); 1951 $this->encryptName = $encrypt; 1952 } 1953 1954 $this->decrypt = self::encryption_algorithm_to_crypt_instance($decrypt); 1955 if ($this->decrypt) { 1956 if (self::$crypto_engine) { 1957 $this->decrypt->setPreferredEngine(self::$crypto_engine); 1958 } 1959 if ($this->decrypt->getBlockLengthInBytes()) { 1960 $this->decrypt_block_size = $this->decrypt->getBlockLengthInBytes(); 1961 } 1962 $this->decrypt->disablePadding(); 1963 1964 if ($this->decrypt->usesIV()) { 1965 $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id); 1966 while ($this->decrypt_block_size > strlen($iv)) { 1967 $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); 1968 } 1969 $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size)); 1970 } 1971 1972 switch ($decrypt) { 1973 case 'aes128-gcm@openssh.com': 1974 case 'aes256-gcm@openssh.com': 1975 // see https://tools.ietf.org/html/rfc5647#section-7.1 1976 $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id); 1977 $this->decryptFixedPart = substr($nonce, 0, 4); 1978 $this->decryptInvocationCounter = substr($nonce, 4, 8); 1979 // fall-through 1980 case 'chacha20-poly1305@openssh.com': 1981 break; 1982 default: 1983 $this->decrypt->enableContinuousBuffer(); 1984 } 1985 1986 $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id); 1987 while ($decryptKeyLength > strlen($key)) { 1988 $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key); 1989 } 1990 switch ($decrypt) { 1991 case 'chacha20-poly1305@openssh.com': 1992 $decryptKeyLength = 32; 1993 $this->lengthDecrypt = self::encryption_algorithm_to_crypt_instance($decrypt); 1994 $this->lengthDecrypt->setKey(substr($key, 32, 32)); 1995 } 1996 $this->decrypt->setKey(substr($key, 0, $decryptKeyLength)); 1997 $this->decryptName = $decrypt; 1998 } 1999 2000 /* The "arcfour128" algorithm is the RC4 cipher, as described in 2001 [SCHNEIER], using a 128-bit key. The first 1536 bytes of keystream 2002 generated by the cipher MUST be discarded, and the first byte of the 2003 first encrypted packet MUST be encrypted using the 1537th byte of 2004 keystream. 2005 2006 -- http://tools.ietf.org/html/rfc4345#section-4 */ 2007 if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') { 2008 $this->encrypt->encrypt(str_repeat("\0", 1536)); 2009 } 2010 if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') { 2011 $this->decrypt->decrypt(str_repeat("\0", 1536)); 2012 } 2013 2014 if (!$this->encrypt->usesNonce()) { 2015 list($this->hmac_create, $createKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_out); 2016 } else { 2017 $this->hmac_create = new \stdClass(); 2018 $this->hmac_create_name = $mac_algorithm_out; 2019 //$mac_algorithm_out = 'none'; 2020 $createKeyLength = 0; 2021 } 2022 2023 if ($this->hmac_create instanceof Hash) { 2024 $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id); 2025 while ($createKeyLength > strlen($key)) { 2026 $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key); 2027 } 2028 $this->hmac_create->setKey(substr($key, 0, $createKeyLength)); 2029 $this->hmac_create_name = $mac_algorithm_out; 2030 $this->hmac_create_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_out); 2031 } 2032 2033 if (!$this->decrypt->usesNonce()) { 2034 list($this->hmac_check, $checkKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_in); 2035 $this->hmac_size = $this->hmac_check->getLengthInBytes(); 2036 } else { 2037 $this->hmac_check = new \stdClass(); 2038 $this->hmac_check_name = $mac_algorithm_in; 2039 //$mac_algorithm_in = 'none'; 2040 $checkKeyLength = 0; 2041 $this->hmac_size = 0; 2042 } 2043 2044 if ($this->hmac_check instanceof Hash) { 2045 $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id); 2046 while ($checkKeyLength > strlen($key)) { 2047 $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key); 2048 } 2049 $this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); 2050 $this->hmac_check_name = $mac_algorithm_in; 2051 $this->hmac_check_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_in); 2052 } 2053 2054 $this->regenerate_compression_context = $this->regenerate_decompression_context = true; 2055 2056 return true; 2057 } 2058 2059 /** 2060 * Maps an encryption algorithm name to the number of key bytes. 2061 * 2062 * @param string $algorithm Name of the encryption algorithm 2063 * @return int|null Number of bytes as an integer or null for unknown 2064 */ 2065 private function encryption_algorithm_to_key_size($algorithm) 2066 { 2067 if ($this->bad_key_size_fix && self::bad_algorithm_candidate($algorithm)) { 2068 return 16; 2069 } 2070 2071 switch ($algorithm) { 2072 case 'none': 2073 return 0; 2074 case 'aes128-gcm@openssh.com': 2075 case 'aes128-cbc': 2076 case 'aes128-ctr': 2077 case 'arcfour': 2078 case 'arcfour128': 2079 case 'blowfish-cbc': 2080 case 'blowfish-ctr': 2081 case 'twofish128-cbc': 2082 case 'twofish128-ctr': 2083 return 16; 2084 case '3des-cbc': 2085 case '3des-ctr': 2086 case 'aes192-cbc': 2087 case 'aes192-ctr': 2088 case 'twofish192-cbc': 2089 case 'twofish192-ctr': 2090 return 24; 2091 case 'aes256-gcm@openssh.com': 2092 case 'aes256-cbc': 2093 case 'aes256-ctr': 2094 case 'arcfour256': 2095 case 'twofish-cbc': 2096 case 'twofish256-cbc': 2097 case 'twofish256-ctr': 2098 return 32; 2099 case 'chacha20-poly1305@openssh.com': 2100 return 64; 2101 } 2102 return null; 2103 } 2104 2105 /** 2106 * Maps an encryption algorithm name to an instance of a subclass of 2107 * \phpseclib3\Crypt\Common\SymmetricKey. 2108 * 2109 * @param string $algorithm Name of the encryption algorithm 2110 * @return SymmetricKey|null 2111 */ 2112 private static function encryption_algorithm_to_crypt_instance($algorithm) 2113 { 2114 switch ($algorithm) { 2115 case '3des-cbc': 2116 return new TripleDES('cbc'); 2117 case '3des-ctr': 2118 return new TripleDES('ctr'); 2119 case 'aes256-cbc': 2120 case 'aes192-cbc': 2121 case 'aes128-cbc': 2122 return new Rijndael('cbc'); 2123 case 'aes256-ctr': 2124 case 'aes192-ctr': 2125 case 'aes128-ctr': 2126 return new Rijndael('ctr'); 2127 case 'blowfish-cbc': 2128 return new Blowfish('cbc'); 2129 case 'blowfish-ctr': 2130 return new Blowfish('ctr'); 2131 case 'twofish128-cbc': 2132 case 'twofish192-cbc': 2133 case 'twofish256-cbc': 2134 case 'twofish-cbc': 2135 return new Twofish('cbc'); 2136 case 'twofish128-ctr': 2137 case 'twofish192-ctr': 2138 case 'twofish256-ctr': 2139 return new Twofish('ctr'); 2140 case 'arcfour': 2141 case 'arcfour128': 2142 case 'arcfour256': 2143 return new RC4(); 2144 case 'aes128-gcm@openssh.com': 2145 case 'aes256-gcm@openssh.com': 2146 return new Rijndael('gcm'); 2147 case 'chacha20-poly1305@openssh.com': 2148 return new ChaCha20(); 2149 } 2150 return null; 2151 } 2152 2153 /** 2154 * Maps an encryption algorithm name to an instance of a subclass of 2155 * \phpseclib3\Crypt\Hash. 2156 * 2157 * @param string $algorithm Name of the encryption algorithm 2158 * @return array{Hash, int}|null 2159 */ 2160 private static function mac_algorithm_to_hash_instance($algorithm) 2161 { 2162 switch ($algorithm) { 2163 case 'umac-64@openssh.com': 2164 case 'umac-64-etm@openssh.com': 2165 return [new Hash('umac-64'), 16]; 2166 case 'umac-128@openssh.com': 2167 case 'umac-128-etm@openssh.com': 2168 return [new Hash('umac-128'), 16]; 2169 case 'hmac-sha2-512': 2170 case 'hmac-sha2-512-etm@openssh.com': 2171 return [new Hash('sha512'), 64]; 2172 case 'hmac-sha2-256': 2173 case 'hmac-sha2-256-etm@openssh.com': 2174 return [new Hash('sha256'), 32]; 2175 case 'hmac-sha1': 2176 case 'hmac-sha1-etm@openssh.com': 2177 return [new Hash('sha1'), 20]; 2178 case 'hmac-sha1-96': 2179 return [new Hash('sha1-96'), 20]; 2180 case 'hmac-md5': 2181 return [new Hash('md5'), 16]; 2182 case 'hmac-md5-96': 2183 return [new Hash('md5-96'), 16]; 2184 } 2185 } 2186 2187 /* 2188 * Tests whether or not proposed algorithm has a potential for issues 2189 * 2190 * @link https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/ssh2-aesctr-openssh.html 2191 * @link https://bugzilla.mindrot.org/show_bug.cgi?id=1291 2192 * @param string $algorithm Name of the encryption algorithm 2193 * @return bool 2194 */ 2195 private static function bad_algorithm_candidate($algorithm) 2196 { 2197 switch ($algorithm) { 2198 case 'arcfour256': 2199 case 'aes192-ctr': 2200 case 'aes256-ctr': 2201 return true; 2202 } 2203 2204 return false; 2205 } 2206 2207 /** 2208 * Login 2209 * 2210 * The $password parameter can be a plaintext password, a \phpseclib3\Crypt\RSA|EC|DSA object, a \phpseclib3\System\SSH\Agent object or an array 2211 * 2212 * @param string $username 2213 * @param string|PrivateKey|array[]|Agent|null ...$args 2214 * @return bool 2215 * @see self::_login() 2216 */ 2217 public function login($username, ...$args) 2218 { 2219 if (!$this->retry_connect) { 2220 $this->auth[] = func_get_args(); 2221 } 2222 2223 // try logging with 'none' as an authentication method first since that's what 2224 // PuTTY does 2225 if (substr($this->server_identifier, 0, 15) != 'SSH-2.0-CoreFTP' && $this->auth_methods_to_continue === null) { 2226 if ($this->sublogin($username)) { 2227 return true; 2228 } 2229 if (!count($args)) { 2230 return false; 2231 } 2232 } 2233 return $this->sublogin($username, ...$args); 2234 } 2235 2236 /** 2237 * Login Helper 2238 * 2239 * @param string $username 2240 * @param string|PrivateKey|array[]|Agent|null ...$args 2241 * @return bool 2242 * @see self::_login_helper() 2243 */ 2244 protected function sublogin($username, ...$args) 2245 { 2246 if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { 2247 $this->connect(); 2248 } 2249 2250 if (empty($args)) { 2251 return $this->login_helper($username); 2252 } 2253 2254 foreach ($args as $arg) { 2255 switch (true) { 2256 case $arg instanceof PublicKey: 2257 throw new \UnexpectedValueException('A PublicKey object was passed to the login method instead of a PrivateKey object'); 2258 case $arg instanceof PrivateKey: 2259 case $arg instanceof Agent: 2260 case is_array($arg): 2261 case Strings::is_stringable($arg): 2262 break; 2263 default: 2264 throw new \UnexpectedValueException('$password needs to either be an instance of \phpseclib3\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string'); 2265 } 2266 } 2267 2268 while (count($args)) { 2269 if (!$this->auth_methods_to_continue || !$this->smartMFA) { 2270 $newargs = $args; 2271 $args = []; 2272 } else { 2273 $newargs = []; 2274 foreach ($this->auth_methods_to_continue as $method) { 2275 switch ($method) { 2276 case 'publickey': 2277 foreach ($args as $key => $arg) { 2278 if ($arg instanceof PrivateKey || $arg instanceof Agent) { 2279 $newargs[] = $arg; 2280 unset($args[$key]); 2281 break; 2282 } 2283 } 2284 break; 2285 case 'keyboard-interactive': 2286 $hasArray = $hasString = false; 2287 foreach ($args as $arg) { 2288 if ($hasArray || is_array($arg)) { 2289 $hasArray = true; 2290 break; 2291 } 2292 if ($hasString || Strings::is_stringable($arg)) { 2293 $hasString = true; 2294 break; 2295 } 2296 } 2297 if ($hasArray && $hasString) { 2298 foreach ($args as $key => $arg) { 2299 if (is_array($arg)) { 2300 $newargs[] = $arg; 2301 break 2; 2302 } 2303 } 2304 } 2305 // fall-through 2306 case 'password': 2307 foreach ($args as $key => $arg) { 2308 $newargs[] = $arg; 2309 unset($args[$key]); 2310 break; 2311 } 2312 } 2313 } 2314 } 2315 2316 if (!count($newargs)) { 2317 return false; 2318 } 2319 2320 foreach ($newargs as $arg) { 2321 if ($this->login_helper($username, $arg)) { 2322 return true; 2323 } 2324 } 2325 } 2326 return false; 2327 } 2328 2329 /** 2330 * Login Helper 2331 * 2332 * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} 2333 * by sending dummy SSH_MSG_IGNORE messages.} 2334 * 2335 * @param string $username 2336 * @param string|AsymmetricKey|array[]|Agent|null ...$args 2337 * @return bool 2338 * @throws \UnexpectedValueException on receipt of unexpected packets 2339 * @throws \RuntimeException on other errors 2340 */ 2341 private function login_helper($username, $password = null) 2342 { 2343 if (!($this->bitmap & self::MASK_CONNECTED)) { 2344 return false; 2345 } 2346 2347 if (!($this->bitmap & self::MASK_LOGIN_REQ)) { 2348 $packet = Strings::packSSH2('Cs', NET_SSH2_MSG_SERVICE_REQUEST, 'ssh-userauth'); 2349 $this->send_binary_packet($packet); 2350 2351 try { 2352 $response = $this->get_binary_packet(); 2353 } catch (\Exception $e) { 2354 if ($this->retry_connect) { 2355 $this->retry_connect = false; 2356 $this->connect(); 2357 return $this->login_helper($username, $password); 2358 } 2359 $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); 2360 throw $e; 2361 } 2362 2363 list($type) = Strings::unpackSSH2('C', $response); 2364 2365 if ($type == NET_SSH2_MSG_EXT_INFO) { 2366 list($nr_extensions) = Strings::unpackSSH2('N', $response); 2367 for ($i = 0; $i < $nr_extensions; $i++) { 2368 list($extension_name, $extension_value) = Strings::unpackSSH2('ss', $response); 2369 if ($extension_name == 'server-sig-algs') { 2370 $this->supported_private_key_algorithms = explode(',', $extension_value); 2371 } 2372 } 2373 2374 $response = $this->get_binary_packet(); 2375 list($type) = Strings::unpackSSH2('C', $response); 2376 } 2377 2378 list($service) = Strings::unpackSSH2('s', $response); 2379 2380 if ($type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth') { 2381 $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); 2382 throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT'); 2383 } 2384 $this->bitmap |= self::MASK_LOGIN_REQ; 2385 } 2386 2387 if (strlen($this->last_interactive_response)) { 2388 return !Strings::is_stringable($password) && !is_array($password) ? false : $this->keyboard_interactive_process($password); 2389 } 2390 2391 if ($password instanceof PrivateKey) { 2392 return $this->privatekey_login($username, $password); 2393 } 2394 2395 if ($password instanceof Agent) { 2396 return $this->ssh_agent_login($username, $password); 2397 } 2398 2399 if (is_array($password)) { 2400 if ($this->keyboard_interactive_login($username, $password)) { 2401 $this->bitmap |= self::MASK_LOGIN; 2402 return true; 2403 } 2404 return false; 2405 } 2406 2407 if (!isset($password)) { 2408 $packet = Strings::packSSH2( 2409 'Cs3', 2410 NET_SSH2_MSG_USERAUTH_REQUEST, 2411 $username, 2412 'ssh-connection', 2413 'none' 2414 ); 2415 2416 $this->send_binary_packet($packet); 2417 2418 $response = $this->get_binary_packet(); 2419 2420 list($type) = Strings::unpackSSH2('C', $response); 2421 switch ($type) { 2422 case NET_SSH2_MSG_USERAUTH_SUCCESS: 2423 $this->bitmap |= self::MASK_LOGIN; 2424 return true; 2425 case NET_SSH2_MSG_USERAUTH_FAILURE: 2426 list($auth_methods) = Strings::unpackSSH2('L', $response); 2427 $this->auth_methods_to_continue = $auth_methods; 2428 // fall-through 2429 default: 2430 return false; 2431 } 2432 } 2433 2434 $packet = Strings::packSSH2( 2435 'Cs3bs', 2436 NET_SSH2_MSG_USERAUTH_REQUEST, 2437 $username, 2438 'ssh-connection', 2439 'password', 2440 false, 2441 $password 2442 ); 2443 2444 // remove the username and password from the logged packet 2445 if (!defined('NET_SSH2_LOGGING')) { 2446 $logged = null; 2447 } else { 2448 $logged = Strings::packSSH2( 2449 'Cs3bs', 2450 NET_SSH2_MSG_USERAUTH_REQUEST, 2451 $username, 2452 'ssh-connection', 2453 'password', 2454 false, 2455 'password' 2456 ); 2457 } 2458 2459 $this->send_binary_packet($packet, $logged); 2460 2461 $response = $this->get_binary_packet(); 2462 if ($response === false) { 2463 return false; 2464 } 2465 list($type) = Strings::unpackSSH2('C', $response); 2466 switch ($type) { 2467 case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed 2468 $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'); 2469 2470 list($message) = Strings::unpackSSH2('s', $response); 2471 $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message; 2472 2473 return $this->disconnect_helper(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); 2474 case NET_SSH2_MSG_USERAUTH_FAILURE: 2475 // can we use keyboard-interactive authentication? if not then either the login is bad or the server employees 2476 // multi-factor authentication 2477 list($auth_methods, $partial_success) = Strings::unpackSSH2('Lb', $response); 2478 $this->auth_methods_to_continue = $auth_methods; 2479 if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) { 2480 if ($this->keyboard_interactive_login($username, $password)) { 2481 $this->bitmap |= self::MASK_LOGIN; 2482 return true; 2483 } 2484 return false; 2485 } 2486 return false; 2487 case NET_SSH2_MSG_USERAUTH_SUCCESS: 2488 $this->bitmap |= self::MASK_LOGIN; 2489 return true; 2490 } 2491 2492 return false; 2493 } 2494 2495 /** 2496 * Login via keyboard-interactive authentication 2497 * 2498 * See {@link http://tools.ietf.org/html/rfc4256 RFC4256} for details. This is not a full-featured keyboard-interactive authenticator. 2499 * 2500 * @param string $username 2501 * @param string|array $password 2502 * @return bool 2503 */ 2504 private function keyboard_interactive_login($username, $password) 2505 { 2506 $packet = Strings::packSSH2( 2507 'Cs5', 2508 NET_SSH2_MSG_USERAUTH_REQUEST, 2509 $username, 2510 'ssh-connection', 2511 'keyboard-interactive', 2512 '', // language tag 2513 '' // submethods 2514 ); 2515 $this->send_binary_packet($packet); 2516 2517 return $this->keyboard_interactive_process($password); 2518 } 2519 2520 /** 2521 * Handle the keyboard-interactive requests / responses. 2522 * 2523 * @param string|array ...$responses 2524 * @return bool 2525 * @throws \RuntimeException on connection error 2526 */ 2527 private function keyboard_interactive_process(...$responses) 2528 { 2529 if (strlen($this->last_interactive_response)) { 2530 $response = $this->last_interactive_response; 2531 } else { 2532 $orig = $response = $this->get_binary_packet(); 2533 } 2534 2535 list($type) = Strings::unpackSSH2('C', $response); 2536 switch ($type) { 2537 case NET_SSH2_MSG_USERAUTH_INFO_REQUEST: 2538 list( 2539 , // name; may be empty 2540 , // instruction; may be empty 2541 , // language tag; may be empty 2542 $num_prompts 2543 ) = Strings::unpackSSH2('s3N', $response); 2544 2545 for ($i = 0; $i < count($responses); $i++) { 2546 if (is_array($responses[$i])) { 2547 foreach ($responses[$i] as $key => $value) { 2548 $this->keyboard_requests_responses[$key] = $value; 2549 } 2550 unset($responses[$i]); 2551 } 2552 } 2553 $responses = array_values($responses); 2554 2555 if (isset($this->keyboard_requests_responses)) { 2556 for ($i = 0; $i < $num_prompts; $i++) { 2557 list( 2558 $prompt, // prompt - ie. "Password: "; must not be empty 2559 // echo 2560 ) = Strings::unpackSSH2('sC', $response); 2561 foreach ($this->keyboard_requests_responses as $key => $value) { 2562 if (substr($prompt, 0, strlen($key)) == $key) { 2563 $responses[] = $value; 2564 break; 2565 } 2566 } 2567 } 2568 } 2569 2570 // see http://tools.ietf.org/html/rfc4256#section-3.2 2571 if (strlen($this->last_interactive_response)) { 2572 $this->last_interactive_response = ''; 2573 } else { 2574 $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST'); 2575 } 2576 2577 if (!count($responses) && $num_prompts) { 2578 $this->last_interactive_response = $orig; 2579 return false; 2580 } 2581 2582 /* 2583 After obtaining the requested information from the user, the client 2584 MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message. 2585 */ 2586 // see http://tools.ietf.org/html/rfc4256#section-3.4 2587 $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses)); 2588 for ($i = 0; $i < count($responses); $i++) { 2589 $packet .= Strings::packSSH2('s', $responses[$i]); 2590 $logged .= Strings::packSSH2('s', 'dummy-answer'); 2591 } 2592 2593 $this->send_binary_packet($packet, $logged); 2594 2595 $this->updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'); 2596 2597 /* 2598 After receiving the response, the server MUST send either an 2599 SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, or another 2600 SSH_MSG_USERAUTH_INFO_REQUEST message. 2601 */ 2602 // maybe phpseclib should force close the connection after x request / responses? unless something like that is done 2603 // there could be an infinite loop of request / responses. 2604 return $this->keyboard_interactive_process(); 2605 case NET_SSH2_MSG_USERAUTH_SUCCESS: 2606 return true; 2607 case NET_SSH2_MSG_USERAUTH_FAILURE: 2608 list($auth_methods) = Strings::unpackSSH2('L', $response); 2609 $this->auth_methods_to_continue = $auth_methods; 2610 return false; 2611 } 2612 2613 return false; 2614 } 2615 2616 /** 2617 * Login with an ssh-agent provided key 2618 * 2619 * @param string $username 2620 * @param \phpseclib3\System\SSH\Agent $agent 2621 * @return bool 2622 */ 2623 private function ssh_agent_login($username, Agent $agent) 2624 { 2625 $this->agent = $agent; 2626 $keys = $agent->requestIdentities(); 2627 foreach ($keys as $key) { 2628 if ($this->privatekey_login($username, $key)) { 2629 return true; 2630 } 2631 } 2632 2633 return false; 2634 } 2635 2636 /** 2637 * Login with an RSA private key 2638 * 2639 * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} 2640 * by sending dummy SSH_MSG_IGNORE messages.} 2641 * 2642 * @param string $username 2643 * @param \phpseclib3\Crypt\Common\PrivateKey $privatekey 2644 * @return bool 2645 * @throws \RuntimeException on connection error 2646 */ 2647 private function privatekey_login($username, PrivateKey $privatekey) 2648 { 2649 $publickey = $privatekey->getPublicKey(); 2650 2651 if ($publickey instanceof RSA) { 2652 $privatekey = $privatekey->withPadding(RSA::SIGNATURE_PKCS1); 2653 $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa']; 2654 if (isset($this->preferred['hostkey'])) { 2655 $algos = array_intersect($algos, $this->preferred['hostkey']); 2656 } 2657 $algo = self::array_intersect_first($algos, $this->supported_private_key_algorithms); 2658 switch ($algo) { 2659 case 'rsa-sha2-512': 2660 $hash = 'sha512'; 2661 $signatureType = 'rsa-sha2-512'; 2662 break; 2663 case 'rsa-sha2-256': 2664 $hash = 'sha256'; 2665 $signatureType = 'rsa-sha2-256'; 2666 break; 2667 //case 'ssh-rsa': 2668 default: 2669 $hash = 'sha1'; 2670 $signatureType = 'ssh-rsa'; 2671 } 2672 } elseif ($publickey instanceof EC) { 2673 $privatekey = $privatekey->withSignatureFormat('SSH2'); 2674 $curveName = $privatekey->getCurve(); 2675 switch ($curveName) { 2676 case 'Ed25519': 2677 $hash = 'sha512'; 2678 $signatureType = 'ssh-ed25519'; 2679 break; 2680 case 'secp256r1': // nistp256 2681 $hash = 'sha256'; 2682 $signatureType = 'ecdsa-sha2-nistp256'; 2683 break; 2684 case 'secp384r1': // nistp384 2685 $hash = 'sha384'; 2686 $signatureType = 'ecdsa-sha2-nistp384'; 2687 break; 2688 case 'secp521r1': // nistp521 2689 $hash = 'sha512'; 2690 $signatureType = 'ecdsa-sha2-nistp521'; 2691 break; 2692 default: 2693 if (is_array($curveName)) { 2694 throw new UnsupportedCurveException('Specified Curves are not supported by SSH2'); 2695 } 2696 throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported by phpseclib3\'s SSH2 implementation'); 2697 } 2698 } elseif ($publickey instanceof DSA) { 2699 $privatekey = $privatekey->withSignatureFormat('SSH2'); 2700 $hash = 'sha1'; 2701 $signatureType = 'ssh-dss'; 2702 } else { 2703 throw new UnsupportedAlgorithmException('Please use either an RSA key, an EC one or a DSA key'); 2704 } 2705 2706 $publickeyStr = $publickey->toString('OpenSSH', ['binary' => true]); 2707 2708 $part1 = Strings::packSSH2( 2709 'Csss', 2710 NET_SSH2_MSG_USERAUTH_REQUEST, 2711 $username, 2712 'ssh-connection', 2713 'publickey' 2714 ); 2715 $part2 = Strings::packSSH2('ss', $signatureType, $publickeyStr); 2716 2717 $packet = $part1 . chr(0) . $part2; 2718 $this->send_binary_packet($packet); 2719 2720 $response = $this->get_binary_packet(); 2721 2722 list($type) = Strings::unpackSSH2('C', $response); 2723 switch ($type) { 2724 case NET_SSH2_MSG_USERAUTH_FAILURE: 2725 list($auth_methods) = Strings::unpackSSH2('L', $response); 2726 if (in_array('publickey', $auth_methods) && substr($signatureType, 0, 9) == 'rsa-sha2-') { 2727 $this->supported_private_key_algorithms = array_diff($this->supported_private_key_algorithms, ['rsa-sha2-256', 'rsa-sha2-512']); 2728 return $this->privatekey_login($username, $privatekey); 2729 } 2730 $this->auth_methods_to_continue = $auth_methods; 2731 $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE'; 2732 return false; 2733 case NET_SSH2_MSG_USERAUTH_PK_OK: 2734 // we'll just take it on faith that the public key blob and the public key algorithm name are as 2735 // they should be 2736 $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK'); 2737 break; 2738 case NET_SSH2_MSG_USERAUTH_SUCCESS: 2739 $this->bitmap |= self::MASK_LOGIN; 2740 return true; 2741 default: 2742 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 2743 throw new ConnectionClosedException('Unexpected response to publickey authentication pt 1'); 2744 } 2745 2746 $packet = $part1 . chr(1) . $part2; 2747 $privatekey = $privatekey->withHash($hash); 2748 $signature = $privatekey->sign(Strings::packSSH2('s', $this->session_id) . $packet); 2749 if ($publickey instanceof RSA) { 2750 $signature = Strings::packSSH2('ss', $signatureType, $signature); 2751 } 2752 $packet .= Strings::packSSH2('s', $signature); 2753 2754 $this->send_binary_packet($packet); 2755 2756 $response = $this->get_binary_packet(); 2757 2758 list($type) = Strings::unpackSSH2('C', $response); 2759 switch ($type) { 2760 case NET_SSH2_MSG_USERAUTH_FAILURE: 2761 // either the login is bad or the server employs multi-factor authentication 2762 list($auth_methods) = Strings::unpackSSH2('L', $response); 2763 $this->auth_methods_to_continue = $auth_methods; 2764 return false; 2765 case NET_SSH2_MSG_USERAUTH_SUCCESS: 2766 $this->bitmap |= self::MASK_LOGIN; 2767 return true; 2768 } 2769 2770 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 2771 throw new ConnectionClosedException('Unexpected response to publickey authentication pt 2'); 2772 } 2773 2774 /** 2775 * Return the currently configured timeout 2776 * 2777 * @return int 2778 */ 2779 public function getTimeout() 2780 { 2781 return $this->timeout; 2782 } 2783 2784 /** 2785 * Set Timeout 2786 * 2787 * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely. setTimeout() makes it so it'll timeout. 2788 * Setting $timeout to false or 0 will mean there is no timeout. 2789 * 2790 * @param mixed $timeout 2791 */ 2792 public function setTimeout($timeout) 2793 { 2794 $this->timeout = $this->curTimeout = $timeout; 2795 } 2796 2797 /** 2798 * Set Keep Alive 2799 * 2800 * Sends an SSH2_MSG_IGNORE message every x seconds, if x is a positive non-zero number. 2801 * 2802 * @param int $interval 2803 */ 2804 public function setKeepAlive($interval) 2805 { 2806 $this->keepAlive = $interval; 2807 } 2808 2809 /** 2810 * Get the output from stdError 2811 * 2812 */ 2813 public function getStdError() 2814 { 2815 return $this->stdErrorLog; 2816 } 2817 2818 /** 2819 * Execute Command 2820 * 2821 * If $callback is set to false then \phpseclib3\Net\SSH2::get_channel_packet(self::CHANNEL_EXEC) will need to be called manually. 2822 * In all likelihood, this is not a feature you want to be taking advantage of. 2823 * 2824 * @param string $command 2825 * @return string|bool 2826 * @psalm-return ($callback is callable ? bool : string|bool) 2827 * @throws \RuntimeException on connection error 2828 */ 2829 public function exec($command, callable $callback = null) 2830 { 2831 $this->curTimeout = $this->timeout; 2832 $this->is_timeout = false; 2833 $this->stdErrorLog = ''; 2834 2835 if (!$this->isAuthenticated()) { 2836 return false; 2837 } 2838 2839 //if ($this->isPTYOpen()) { 2840 // throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); 2841 //} 2842 2843 $this->open_channel(self::CHANNEL_EXEC); 2844 2845 if ($this->request_pty === true) { 2846 $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); 2847 $packet = Strings::packSSH2( 2848 'CNsCsN4s', 2849 NET_SSH2_MSG_CHANNEL_REQUEST, 2850 $this->server_channels[self::CHANNEL_EXEC], 2851 'pty-req', 2852 1, 2853 $this->term, 2854 $this->windowColumns, 2855 $this->windowRows, 2856 0, 2857 0, 2858 $terminal_modes 2859 ); 2860 2861 $this->send_binary_packet($packet); 2862 2863 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST; 2864 if (!$this->get_channel_packet(self::CHANNEL_EXEC)) { 2865 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 2866 throw new \RuntimeException('Unable to request pseudo-terminal'); 2867 } 2868 } 2869 2870 // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things 2871 // down. the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &'). 2872 // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then 2873 // then immediately terminate. without such a request exec() will loop indefinitely. the ping process won't end but 2874 // neither will your script. 2875 2876 // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by 2877 // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the 2878 // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates. 2879 $packet = Strings::packSSH2( 2880 'CNsCs', 2881 NET_SSH2_MSG_CHANNEL_REQUEST, 2882 $this->server_channels[self::CHANNEL_EXEC], 2883 'exec', 2884 1, 2885 $command 2886 ); 2887 $this->send_binary_packet($packet); 2888 2889 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST; 2890 2891 if (!$this->get_channel_packet(self::CHANNEL_EXEC)) { 2892 return false; 2893 } 2894 2895 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA; 2896 2897 if ($this->request_pty === true) { 2898 $this->channel_id_last_interactive = self::CHANNEL_EXEC; 2899 return true; 2900 } 2901 2902 $output = ''; 2903 while (true) { 2904 $temp = $this->get_channel_packet(self::CHANNEL_EXEC); 2905 switch (true) { 2906 case $temp === true: 2907 return is_callable($callback) ? true : $output; 2908 case $temp === false: 2909 return false; 2910 default: 2911 if (is_callable($callback)) { 2912 if ($callback($temp) === true) { 2913 $this->close_channel(self::CHANNEL_EXEC); 2914 return true; 2915 } 2916 } else { 2917 $output .= $temp; 2918 } 2919 } 2920 } 2921 } 2922 2923 /** 2924 * How many channels are currently open? 2925 * 2926 * @return int 2927 */ 2928 public function getOpenChannelCount() 2929 { 2930 return $this->channelCount; 2931 } 2932 2933 /** 2934 * Opens a channel 2935 * 2936 * @param string $channel 2937 * @param bool $skip_extended 2938 * @return bool 2939 */ 2940 protected function open_channel($channel, $skip_extended = false) 2941 { 2942 if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) { 2943 throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again'); 2944 } 2945 2946 $this->channelCount++; 2947 2948 if ($this->channelCount > 1 && $this->errorOnMultipleChannels) { 2949 throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels"); 2950 } 2951 2952 // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to 2953 // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, 2954 // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. 2955 // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info 2956 $this->window_size_server_to_client[$channel] = $this->window_size; 2957 // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy 2958 // uses 0x4000, that's what will be used here, as well. 2959 $packet_size = 0x4000; 2960 2961 $packet = Strings::packSSH2( 2962 'CsN3', 2963 NET_SSH2_MSG_CHANNEL_OPEN, 2964 'session', 2965 $channel, 2966 $this->window_size_server_to_client[$channel], 2967 $packet_size 2968 ); 2969 2970 $this->send_binary_packet($packet); 2971 2972 $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN; 2973 2974 return $this->get_channel_packet($channel, $skip_extended); 2975 } 2976 2977 /** 2978 * Creates an interactive shell 2979 * 2980 * Returns bool(true) if the shell was opened. 2981 * Returns bool(false) if the shell was already open. 2982 * 2983 * @see self::isShellOpen() 2984 * @see self::read() 2985 * @see self::write() 2986 * @return bool 2987 * @throws InsufficientSetupException if not authenticated 2988 * @throws \UnexpectedValueException on receipt of unexpected packets 2989 * @throws \RuntimeException on other errors 2990 */ 2991 public function openShell() 2992 { 2993 if (!$this->isAuthenticated()) { 2994 throw new InsufficientSetupException('Operation disallowed prior to login()'); 2995 } 2996 2997 $this->open_channel(self::CHANNEL_SHELL); 2998 2999 $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); 3000 $packet = Strings::packSSH2( 3001 'CNsbsN4s', 3002 NET_SSH2_MSG_CHANNEL_REQUEST, 3003 $this->server_channels[self::CHANNEL_SHELL], 3004 'pty-req', 3005 true, // want reply 3006 $this->term, 3007 $this->windowColumns, 3008 $this->windowRows, 3009 0, 3010 0, 3011 $terminal_modes 3012 ); 3013 3014 $this->send_binary_packet($packet); 3015 3016 $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST; 3017 3018 if (!$this->get_channel_packet(self::CHANNEL_SHELL)) { 3019 throw new \RuntimeException('Unable to request pty'); 3020 } 3021 3022 $packet = Strings::packSSH2( 3023 'CNsb', 3024 NET_SSH2_MSG_CHANNEL_REQUEST, 3025 $this->server_channels[self::CHANNEL_SHELL], 3026 'shell', 3027 true // want reply 3028 ); 3029 $this->send_binary_packet($packet); 3030 3031 $response = $this->get_channel_packet(self::CHANNEL_SHELL); 3032 if ($response === false) { 3033 throw new \RuntimeException('Unable to request shell'); 3034 } 3035 3036 $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA; 3037 3038 $this->channel_id_last_interactive = self::CHANNEL_SHELL; 3039 3040 $this->bitmap |= self::MASK_SHELL; 3041 3042 return true; 3043 } 3044 3045 /** 3046 * Return the channel to be used with read(), write(), and reset(), if none were specified 3047 * @deprecated for lack of transparency in intended channel target, to be potentially replaced 3048 * with method which guarantees open-ness of all yielded channels and throws 3049 * error for multiple open channels 3050 * @see self::read() 3051 * @see self::write() 3052 * @return int 3053 */ 3054 private function get_interactive_channel() 3055 { 3056 switch (true) { 3057 case $this->is_channel_status_data(self::CHANNEL_SUBSYSTEM): 3058 return self::CHANNEL_SUBSYSTEM; 3059 case $this->is_channel_status_data(self::CHANNEL_EXEC): 3060 return self::CHANNEL_EXEC; 3061 default: 3062 return self::CHANNEL_SHELL; 3063 } 3064 } 3065 3066 /** 3067 * Indicates the DATA status on the given channel 3068 * 3069 * @param int $channel The channel number to evaluate 3070 * @return bool 3071 */ 3072 private function is_channel_status_data($channel) 3073 { 3074 return isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA; 3075 } 3076 3077 /** 3078 * Return an available open channel 3079 * 3080 * @return int 3081 */ 3082 private function get_open_channel() 3083 { 3084 $channel = self::CHANNEL_EXEC; 3085 do { 3086 if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) { 3087 return $channel; 3088 } 3089 } while ($channel++ < self::CHANNEL_SUBSYSTEM); 3090 3091 return false; 3092 } 3093 3094 /** 3095 * Request agent forwarding of remote server 3096 * 3097 * @return bool 3098 */ 3099 public function requestAgentForwarding() 3100 { 3101 $request_channel = $this->get_open_channel(); 3102 if ($request_channel === false) { 3103 return false; 3104 } 3105 3106 $packet = Strings::packSSH2( 3107 'CNsC', 3108 NET_SSH2_MSG_CHANNEL_REQUEST, 3109 $this->server_channels[$request_channel], 3110 'auth-agent-req@openssh.com', 3111 1 3112 ); 3113 3114 $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST; 3115 3116 $this->send_binary_packet($packet); 3117 3118 if (!$this->get_channel_packet($request_channel)) { 3119 return false; 3120 } 3121 3122 $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN; 3123 3124 return true; 3125 } 3126 3127 /** 3128 * Returns the output of an interactive shell 3129 * 3130 * Returns when there's a match for $expect, which can take the form of a string literal or, 3131 * if $mode == self::READ_REGEX, a regular expression. 3132 * 3133 * If not specifying a channel, an open interactive channel will be selected, or, if there are 3134 * no open channels, an interactive shell will be created. If there are multiple open 3135 * interactive channels, a legacy behavior will apply in which channel selection prioritizes 3136 * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive 3137 * channels, callers are discouraged from relying on this legacy behavior and should specify 3138 * the intended channel. 3139 * 3140 * @see self::write() 3141 * @param string $expect 3142 * @param int $mode One of the self::READ_* constants 3143 * @param int|null $channel Channel id returned by self::getInteractiveChannelId() 3144 * @return string|bool|null 3145 * @throws \RuntimeException on connection error 3146 * @throws InsufficientSetupException on unexpected channel status, possibly due to closure 3147 */ 3148 public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null) 3149 { 3150 if (!$this->isAuthenticated()) { 3151 throw new InsufficientSetupException('Operation disallowed prior to login()'); 3152 } 3153 3154 $this->curTimeout = $this->timeout; 3155 $this->is_timeout = false; 3156 3157 if ($channel === null) { 3158 $channel = $this->get_interactive_channel(); 3159 } 3160 3161 if (!$this->is_channel_status_data($channel) && empty($this->channel_buffers[$channel])) { 3162 if ($channel != self::CHANNEL_SHELL) { 3163 throw new InsufficientSetupException('Data is not available on channel'); 3164 } elseif (!$this->openShell()) { 3165 throw new \RuntimeException('Unable to initiate an interactive shell session'); 3166 } 3167 } 3168 3169 if ($mode == self::READ_NEXT) { 3170 return $this->get_channel_packet($channel); 3171 } 3172 3173 $match = $expect; 3174 while (true) { 3175 if ($mode == self::READ_REGEX) { 3176 preg_match($expect, substr($this->interactiveBuffer, -1024), $matches); 3177 $match = isset($matches[0]) ? $matches[0] : ''; 3178 } 3179 $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false; 3180 if ($pos !== false) { 3181 return Strings::shift($this->interactiveBuffer, $pos + strlen($match)); 3182 } 3183 $response = $this->get_channel_packet($channel); 3184 if ($response === true) { 3185 return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer)); 3186 } 3187 3188 $this->interactiveBuffer .= $response; 3189 } 3190 } 3191 3192 /** 3193 * Inputs a command into an interactive shell. 3194 * 3195 * If not specifying a channel, an open interactive channel will be selected, or, if there are 3196 * no open channels, an interactive shell will be created. If there are multiple open 3197 * interactive channels, a legacy behavior will apply in which channel selection prioritizes 3198 * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive 3199 * channels, callers are discouraged from relying on this legacy behavior and should specify 3200 * the intended channel. 3201 * 3202 * @see SSH2::read() 3203 * @param string $cmd 3204 * @param int|null $channel Channel id returned by self::getInteractiveChannelId() 3205 * @return void 3206 * @throws \RuntimeException on connection error 3207 * @throws InsufficientSetupException on unexpected channel status, possibly due to closure 3208 */ 3209 public function write($cmd, $channel = null) 3210 { 3211 if (!$this->isAuthenticated()) { 3212 throw new InsufficientSetupException('Operation disallowed prior to login()'); 3213 } 3214 3215 if ($channel === null) { 3216 $channel = $this->get_interactive_channel(); 3217 } 3218 3219 if (!$this->is_channel_status_data($channel)) { 3220 if ($channel != self::CHANNEL_SHELL) { 3221 throw new InsufficientSetupException('Data is not available on channel'); 3222 } elseif (!$this->openShell()) { 3223 throw new \RuntimeException('Unable to initiate an interactive shell session'); 3224 } 3225 } 3226 3227 $this->send_channel_packet($channel, $cmd); 3228 } 3229 3230 /** 3231 * Start a subsystem. 3232 * 3233 * Right now only one subsystem at a time is supported. To support multiple subsystem's stopSubsystem() could accept 3234 * a string that contained the name of the subsystem, but at that point, only one subsystem of each type could be opened. 3235 * To support multiple subsystem's of the same name maybe it'd be best if startSubsystem() generated a new channel id and 3236 * returns that and then that that was passed into stopSubsystem() but that'll be saved for a future date and implemented 3237 * if there's sufficient demand for such a feature. 3238 * 3239 * @see self::stopSubsystem() 3240 * @param string $subsystem 3241 * @return bool 3242 */ 3243 public function startSubsystem($subsystem) 3244 { 3245 $this->open_channel(self::CHANNEL_SUBSYSTEM); 3246 3247 $packet = Strings::packSSH2( 3248 'CNsCs', 3249 NET_SSH2_MSG_CHANNEL_REQUEST, 3250 $this->server_channels[self::CHANNEL_SUBSYSTEM], 3251 'subsystem', 3252 1, 3253 $subsystem 3254 ); 3255 $this->send_binary_packet($packet); 3256 3257 $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST; 3258 3259 if (!$this->get_channel_packet(self::CHANNEL_SUBSYSTEM)) { 3260 return false; 3261 } 3262 3263 $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA; 3264 3265 $this->channel_id_last_interactive = self::CHANNEL_SUBSYSTEM; 3266 3267 return true; 3268 } 3269 3270 /** 3271 * Stops a subsystem. 3272 * 3273 * @see self::startSubsystem() 3274 * @return bool 3275 */ 3276 public function stopSubsystem() 3277 { 3278 if ($this->isInteractiveChannelOpen(self::CHANNEL_SUBSYSTEM)) { 3279 $this->close_channel(self::CHANNEL_SUBSYSTEM); 3280 } 3281 return true; 3282 } 3283 3284 /** 3285 * Closes a channel 3286 * 3287 * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call 3288 * 3289 * If not specifying a channel, an open interactive channel will be selected. If there are 3290 * multiple open interactive channels, a legacy behavior will apply in which channel selection 3291 * prioritizes an active subsystem, the exec pty, and, lastly, the shell. If using multiple 3292 * interactive channels, callers are discouraged from relying on this legacy behavior and 3293 * should specify the intended channel. 3294 * 3295 * @param int|null $channel Channel id returned by self::getInteractiveChannelId() 3296 * @return void 3297 */ 3298 public function reset($channel = null) 3299 { 3300 if ($channel === null) { 3301 $channel = $this->get_interactive_channel(); 3302 } 3303 if ($this->isInteractiveChannelOpen($channel)) { 3304 $this->close_channel($channel); 3305 } 3306 } 3307 3308 /** 3309 * Is timeout? 3310 * 3311 * Did exec() or read() return because they timed out or because they encountered the end? 3312 * 3313 */ 3314 public function isTimeout() 3315 { 3316 return $this->is_timeout; 3317 } 3318 3319 /** 3320 * Disconnect 3321 * 3322 */ 3323 public function disconnect() 3324 { 3325 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 3326 if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) { 3327 fclose($this->realtime_log_file); 3328 } 3329 unset(self::$connections[$this->getResourceId()]); 3330 } 3331 3332 /** 3333 * Destructor. 3334 * 3335 * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call 3336 * disconnect(). 3337 * 3338 */ 3339 public function __destruct() 3340 { 3341 $this->disconnect(); 3342 } 3343 3344 /** 3345 * Is the connection still active? 3346 * 3347 * $level has 3x possible values: 3348 * 0 (default): phpseclib takes a passive approach to see if the connection is still active by calling feof() 3349 * on the socket 3350 * 1: phpseclib takes an active approach to see if the connection is still active by sending an SSH_MSG_IGNORE 3351 * packet that doesn't require a response 3352 * 2: phpseclib takes an active approach to see if the connection is still active by sending an SSH_MSG_CHANNEL_OPEN 3353 * packet and imediately trying to close that channel. some routers, in particular, however, will only let you 3354 * open one channel, so this approach could yield false positives 3355 * 3356 * @param int $level 3357 * @return bool 3358 */ 3359 public function isConnected($level = 0) 3360 { 3361 if (!is_int($level) || $level < 0 || $level > 2) { 3362 throw new \InvalidArgumentException('$level must be 0, 1 or 2'); 3363 } 3364 3365 if ($level == 0) { 3366 return ($this->bitmap & self::MASK_CONNECTED) && is_resource($this->fsock) && !feof($this->fsock); 3367 } 3368 try { 3369 if ($level == 1) { 3370 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); 3371 } else { 3372 $this->open_channel(self::CHANNEL_KEEP_ALIVE); 3373 $this->close_channel(self::CHANNEL_KEEP_ALIVE); 3374 } 3375 return true; 3376 } catch (\Exception $e) { 3377 return false; 3378 } 3379 } 3380 3381 /** 3382 * Have you successfully been logged in? 3383 * 3384 * @return bool 3385 */ 3386 public function isAuthenticated() 3387 { 3388 return (bool) ($this->bitmap & self::MASK_LOGIN); 3389 } 3390 3391 /** 3392 * Is the interactive shell active? 3393 * 3394 * @return bool 3395 */ 3396 public function isShellOpen() 3397 { 3398 return $this->isInteractiveChannelOpen(self::CHANNEL_SHELL); 3399 } 3400 3401 /** 3402 * Is the exec pty active? 3403 * 3404 * @return bool 3405 */ 3406 public function isPTYOpen() 3407 { 3408 return $this->isInteractiveChannelOpen(self::CHANNEL_EXEC); 3409 } 3410 3411 /** 3412 * Is the given interactive channel active? 3413 * 3414 * @param int $channel Channel id returned by self::getInteractiveChannelId() 3415 * @return bool 3416 */ 3417 public function isInteractiveChannelOpen($channel) 3418 { 3419 return $this->isAuthenticated() && $this->is_channel_status_data($channel); 3420 } 3421 3422 /** 3423 * Returns a channel identifier, presently of the last interactive channel opened, regardless of current status. 3424 * Returns 0 if no interactive channel has been opened. 3425 * 3426 * @see self::isInteractiveChannelOpen() 3427 * @return int 3428 */ 3429 public function getInteractiveChannelId() 3430 { 3431 return $this->channel_id_last_interactive; 3432 } 3433 3434 /** 3435 * Pings a server connection, or tries to reconnect if the connection has gone down 3436 * 3437 * Inspired by http://php.net/manual/en/mysqli.ping.php 3438 * 3439 * @return bool 3440 */ 3441 public function ping() 3442 { 3443 if (!$this->isAuthenticated()) { 3444 if (!empty($this->auth)) { 3445 return $this->reconnect(); 3446 } 3447 return false; 3448 } 3449 3450 try { 3451 $this->open_channel(self::CHANNEL_KEEP_ALIVE); 3452 } catch (\RuntimeException $e) { 3453 return $this->reconnect(); 3454 } 3455 3456 $this->close_channel(self::CHANNEL_KEEP_ALIVE); 3457 return true; 3458 } 3459 3460 /** 3461 * In situ reconnect method 3462 * 3463 * @return boolean 3464 */ 3465 private function reconnect() 3466 { 3467 $this->reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST); 3468 $this->retry_connect = true; 3469 $this->connect(); 3470 foreach ($this->auth as $auth) { 3471 $result = $this->login(...$auth); 3472 } 3473 return $result; 3474 } 3475 3476 /** 3477 * Resets a connection for re-use 3478 * 3479 * @param int $reason 3480 */ 3481 protected function reset_connection($reason) 3482 { 3483 $this->disconnect_helper($reason); 3484 $this->decrypt = $this->encrypt = false; 3485 $this->decrypt_block_size = $this->encrypt_block_size = 8; 3486 $this->hmac_check = $this->hmac_create = false; 3487 $this->hmac_size = false; 3488 $this->session_id = false; 3489 $this->retry_connect = true; 3490 $this->get_seq_no = $this->send_seq_no = 0; 3491 $this->channel_status = []; 3492 $this->channel_id_last_interactive = 0; 3493 } 3494 3495 /** 3496 * Gets Binary Packets 3497 * 3498 * See '6. Binary Packet Protocol' of rfc4253 for more info. 3499 * 3500 * @see self::_send_binary_packet() 3501 * @param bool $skip_channel_filter 3502 * @return bool|string 3503 */ 3504 private function get_binary_packet($skip_channel_filter = false) 3505 { 3506 if ($skip_channel_filter) { 3507 if (!is_resource($this->fsock)) { 3508 throw new \InvalidArgumentException('fsock is not a resource.'); 3509 } 3510 $read = [$this->fsock]; 3511 $write = $except = null; 3512 3513 if (!$this->curTimeout) { 3514 if ($this->keepAlive <= 0) { 3515 static::stream_select($read, $write, $except, null); 3516 } else { 3517 if (!static::stream_select($read, $write, $except, $this->keepAlive)) { 3518 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); 3519 return $this->get_binary_packet(true); 3520 } 3521 } 3522 } else { 3523 if ($this->curTimeout < 0) { 3524 $this->is_timeout = true; 3525 return true; 3526 } 3527 3528 $start = microtime(true); 3529 3530 if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) { 3531 if (!static::stream_select($read, $write, $except, $this->keepAlive)) { 3532 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); 3533 $elapsed = microtime(true) - $start; 3534 $this->curTimeout -= $elapsed; 3535 return $this->get_binary_packet(true); 3536 } 3537 $elapsed = microtime(true) - $start; 3538 $this->curTimeout -= $elapsed; 3539 } 3540 3541 $sec = (int) floor($this->curTimeout); 3542 $usec = (int) (1000000 * ($this->curTimeout - $sec)); 3543 3544 // this can return a "stream_select(): unable to select [4]: Interrupted system call" error 3545 if (!static::stream_select($read, $write, $except, $sec, $usec)) { 3546 $this->is_timeout = true; 3547 return true; 3548 } 3549 $elapsed = microtime(true) - $start; 3550 $this->curTimeout -= $elapsed; 3551 } 3552 } 3553 3554 if (!is_resource($this->fsock) || feof($this->fsock)) { 3555 $this->bitmap = 0; 3556 $str = 'Connection closed (by server) prematurely'; 3557 if (isset($elapsed)) { 3558 $str .= ' ' . $elapsed . 's'; 3559 } 3560 throw new ConnectionClosedException($str); 3561 } 3562 3563 $start = microtime(true); 3564 if ($this->curTimeout) { 3565 $sec = (int) floor($this->curTimeout); 3566 $usec = (int) (1000000 * ($this->curTimeout - $sec)); 3567 stream_set_timeout($this->fsock, $sec, $usec); 3568 } 3569 $raw = stream_get_contents($this->fsock, $this->decrypt_block_size); 3570 3571 if (!strlen($raw)) { 3572 $this->bitmap = 0; 3573 throw new ConnectionClosedException('No data received from server'); 3574 } 3575 3576 if ($this->decrypt) { 3577 switch ($this->decryptName) { 3578 case 'aes128-gcm@openssh.com': 3579 case 'aes256-gcm@openssh.com': 3580 $this->decrypt->setNonce( 3581 $this->decryptFixedPart . 3582 $this->decryptInvocationCounter 3583 ); 3584 Strings::increment_str($this->decryptInvocationCounter); 3585 $this->decrypt->setAAD($temp = Strings::shift($raw, 4)); 3586 extract(unpack('Npacket_length', $temp)); 3587 /** 3588 * @var integer $packet_length 3589 */ 3590 3591 $raw .= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4); 3592 $stop = microtime(true); 3593 $tag = stream_get_contents($this->fsock, $this->decrypt_block_size); 3594 $this->decrypt->setTag($tag); 3595 $raw = $this->decrypt->decrypt($raw); 3596 $raw = $temp . $raw; 3597 $remaining_length = 0; 3598 break; 3599 case 'chacha20-poly1305@openssh.com': 3600 // This should be impossible, but we are checking anyway to narrow the type for Psalm. 3601 if (!($this->decrypt instanceof ChaCha20)) { 3602 throw new \LogicException('$this->decrypt is not a ' . ChaCha20::class); 3603 } 3604 3605 $nonce = pack('N2', 0, $this->get_seq_no); 3606 3607 $this->lengthDecrypt->setNonce($nonce); 3608 $temp = $this->lengthDecrypt->decrypt($aad = Strings::shift($raw, 4)); 3609 extract(unpack('Npacket_length', $temp)); 3610 /** 3611 * @var integer $packet_length 3612 */ 3613 3614 $raw .= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4); 3615 $stop = microtime(true); 3616 $tag = stream_get_contents($this->fsock, 16); 3617 3618 $this->decrypt->setNonce($nonce); 3619 $this->decrypt->setCounter(0); 3620 // this is the same approach that's implemented in Salsa20::createPoly1305Key() 3621 // but we don't want to use the same AEAD construction that RFC8439 describes 3622 // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305()) 3623 $this->decrypt->setPoly1305Key( 3624 $this->decrypt->encrypt(str_repeat("\0", 32)) 3625 ); 3626 $this->decrypt->setAAD($aad); 3627 $this->decrypt->setCounter(1); 3628 $this->decrypt->setTag($tag); 3629 $raw = $this->decrypt->decrypt($raw); 3630 $raw = $temp . $raw; 3631 $remaining_length = 0; 3632 break; 3633 default: 3634 if (!$this->hmac_check instanceof Hash || !$this->hmac_check_etm) { 3635 $raw = $this->decrypt->decrypt($raw); 3636 break; 3637 } 3638 extract(unpack('Npacket_length', $temp = Strings::shift($raw, 4))); 3639 /** 3640 * @var integer $packet_length 3641 */ 3642 $raw .= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4); 3643 $stop = microtime(true); 3644 $encrypted = $temp . $raw; 3645 $raw = $temp . $this->decrypt->decrypt($raw); 3646 $remaining_length = 0; 3647 } 3648 } 3649 3650 if (strlen($raw) < 5) { 3651 $this->bitmap = 0; 3652 throw new \RuntimeException('Plaintext is too short'); 3653 } 3654 extract(unpack('Npacket_length/Cpadding_length', Strings::shift($raw, 5))); 3655 /** 3656 * @var integer $packet_length 3657 * @var integer $padding_length 3658 */ 3659 3660 if (!isset($remaining_length)) { 3661 $remaining_length = $packet_length + 4 - $this->decrypt_block_size; 3662 } 3663 3664 $buffer = $this->read_remaining_bytes($remaining_length); 3665 3666 if (!isset($stop)) { 3667 $stop = microtime(true); 3668 } 3669 if (strlen($buffer)) { 3670 $raw .= $this->decrypt ? $this->decrypt->decrypt($buffer) : $buffer; 3671 } 3672 3673 $payload = Strings::shift($raw, $packet_length - $padding_length - 1); 3674 $padding = Strings::shift($raw, $padding_length); // should leave $raw empty 3675 3676 if ($this->hmac_check instanceof Hash) { 3677 $hmac = stream_get_contents($this->fsock, $this->hmac_size); 3678 if ($hmac === false || strlen($hmac) != $this->hmac_size) { 3679 $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR); 3680 throw new \RuntimeException('Error reading socket'); 3681 } 3682 3683 $reconstructed = !$this->hmac_check_etm ? 3684 pack('NCa*', $packet_length, $padding_length, $payload . $padding) : 3685 $encrypted; 3686 if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') { 3687 $this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no)); 3688 if ($hmac != $this->hmac_check->hash($reconstructed)) { 3689 $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR); 3690 throw new \RuntimeException('Invalid UMAC'); 3691 } 3692 } else { 3693 if ($hmac != $this->hmac_check->hash(pack('Na*', $this->get_seq_no, $reconstructed))) { 3694 $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR); 3695 throw new \RuntimeException('Invalid HMAC'); 3696 } 3697 } 3698 } 3699 3700 switch ($this->decompress) { 3701 case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH: 3702 if (!$this->isAuthenticated()) { 3703 break; 3704 } 3705 // fall-through 3706 case self::NET_SSH2_COMPRESSION_ZLIB: 3707 if ($this->regenerate_decompression_context) { 3708 $this->regenerate_decompression_context = false; 3709 3710 $cmf = ord($payload[0]); 3711 $cm = $cmf & 0x0F; 3712 if ($cm != 8) { // deflate 3713 user_error("Only CM = 8 ('deflate') is supported ($cm)"); 3714 } 3715 $cinfo = ($cmf & 0xF0) >> 4; 3716 if ($cinfo > 7) { 3717 user_error("CINFO above 7 is not allowed ($cinfo)"); 3718 } 3719 $windowSize = 1 << ($cinfo + 8); 3720 3721 $flg = ord($payload[1]); 3722 //$fcheck = $flg && 0x0F; 3723 if ((($cmf << 8) | $flg) % 31) { 3724 user_error('fcheck failed'); 3725 } 3726 $fdict = boolval($flg & 0x20); 3727 $flevel = ($flg & 0xC0) >> 6; 3728 3729 $this->decompress_context = inflate_init(ZLIB_ENCODING_RAW, ['window' => $cinfo + 8]); 3730 $payload = substr($payload, 2); 3731 } 3732 if ($this->decompress_context) { 3733 $payload = inflate_add($this->decompress_context, $payload, ZLIB_PARTIAL_FLUSH); 3734 } 3735 } 3736 3737 $this->get_seq_no++; 3738 3739 if (defined('NET_SSH2_LOGGING')) { 3740 $current = microtime(true); 3741 $message_number = isset(self::$message_numbers[ord($payload[0])]) ? self::$message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')'; 3742 $message_number = '<- ' . $message_number . 3743 ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)'; 3744 $this->append_log($message_number, $payload); 3745 $this->last_packet = $current; 3746 } 3747 3748 return $this->filter($payload, $skip_channel_filter); 3749 } 3750 3751 /** 3752 * Read Remaining Bytes 3753 * 3754 * @see self::get_binary_packet() 3755 * @param int $remaining_length 3756 * @return string 3757 */ 3758 private function read_remaining_bytes($remaining_length) 3759 { 3760 if (!$remaining_length) { 3761 return ''; 3762 } 3763 3764 $adjustLength = false; 3765 if ($this->decrypt) { 3766 switch (true) { 3767 case $this->decryptName == 'aes128-gcm@openssh.com': 3768 case $this->decryptName == 'aes256-gcm@openssh.com': 3769 case $this->decryptName == 'chacha20-poly1305@openssh.com': 3770 case $this->hmac_check instanceof Hash && $this->hmac_check_etm: 3771 $remaining_length += $this->decrypt_block_size - 4; 3772 $adjustLength = true; 3773 } 3774 } 3775 3776 // quoting <http://tools.ietf.org/html/rfc4253#section-6.1>, 3777 // "implementations SHOULD check that the packet length is reasonable" 3778 // PuTTY uses 0x9000 as the actual max packet size and so to shall we 3779 // don't do this when GCM mode is used since GCM mode doesn't encrypt the length 3780 if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) { 3781 if (!$this->bad_key_size_fix && self::bad_algorithm_candidate($this->decrypt ? $this->decryptName : '') && !($this->bitmap & SSH2::MASK_LOGIN)) { 3782 $this->bad_key_size_fix = true; 3783 $this->reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 3784 return false; 3785 } 3786 throw new \RuntimeException('Invalid size'); 3787 } 3788 3789 if ($adjustLength) { 3790 $remaining_length -= $this->decrypt_block_size - 4; 3791 } 3792 3793 $buffer = ''; 3794 while ($remaining_length > 0) { 3795 $temp = stream_get_contents($this->fsock, $remaining_length); 3796 if ($temp === false || feof($this->fsock)) { 3797 $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); 3798 throw new \RuntimeException('Error reading from socket'); 3799 } 3800 $buffer .= $temp; 3801 $remaining_length -= strlen($temp); 3802 } 3803 3804 return $buffer; 3805 } 3806 3807 /** 3808 * Filter Binary Packets 3809 * 3810 * Because some binary packets need to be ignored... 3811 * 3812 * @see self::_get_binary_packet() 3813 * @param string $payload 3814 * @param bool $skip_channel_filter 3815 * @return string|bool 3816 */ 3817 private function filter($payload, $skip_channel_filter) 3818 { 3819 switch (ord($payload[0])) { 3820 case NET_SSH2_MSG_DISCONNECT: 3821 Strings::shift($payload, 1); 3822 list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload); 3823 $this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message"; 3824 $this->bitmap = 0; 3825 return false; 3826 case NET_SSH2_MSG_IGNORE: 3827 $this->extra_packets++; 3828 $payload = $this->get_binary_packet($skip_channel_filter); 3829 break; 3830 case NET_SSH2_MSG_DEBUG: 3831 $this->extra_packets++; 3832 Strings::shift($payload, 2); // second byte is "always_display" 3833 list($message) = Strings::unpackSSH2('s', $payload); 3834 $this->errors[] = "SSH_MSG_DEBUG: $message"; 3835 $payload = $this->get_binary_packet($skip_channel_filter); 3836 break; 3837 case NET_SSH2_MSG_UNIMPLEMENTED: 3838 return false; 3839 case NET_SSH2_MSG_KEXINIT: 3840 // this is here for key re-exchanges after the initial key exchange 3841 if ($this->session_id !== false) { 3842 if (!$this->key_exchange($payload)) { 3843 $this->bitmap = 0; 3844 return false; 3845 } 3846 $payload = $this->get_binary_packet($skip_channel_filter); 3847 } 3848 } 3849 3850 // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in 3851 if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && !is_bool($payload) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { 3852 Strings::shift($payload, 1); 3853 list($this->banner_message) = Strings::unpackSSH2('s', $payload); 3854 $payload = $this->get_binary_packet(); 3855 } 3856 3857 // only called when we've already logged in 3858 if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) { 3859 if (is_bool($payload)) { 3860 return $payload; 3861 } 3862 3863 switch (ord($payload[0])) { 3864 case NET_SSH2_MSG_CHANNEL_REQUEST: 3865 if (strlen($payload) == 31) { 3866 extract(unpack('cpacket_type/Nchannel/Nlength', $payload)); 3867 if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) { 3868 if (ord(substr($payload, 9 + $length))) { // want reply 3869 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel])); 3870 } 3871 $payload = $this->get_binary_packet($skip_channel_filter); 3872 } 3873 } 3874 break; 3875 case NET_SSH2_MSG_CHANNEL_DATA: 3876 case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: 3877 case NET_SSH2_MSG_CHANNEL_CLOSE: 3878 case NET_SSH2_MSG_CHANNEL_EOF: 3879 if (!$skip_channel_filter && !empty($this->server_channels)) { 3880 $this->binary_packet_buffer = $payload; 3881 $this->get_channel_packet(true); 3882 $payload = $this->get_binary_packet(); 3883 } 3884 break; 3885 case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4 3886 Strings::shift($payload, 1); 3887 list($request_name) = Strings::unpackSSH2('s', $payload); 3888 $this->errors[] = "SSH_MSG_GLOBAL_REQUEST: $request_name"; 3889 3890 try { 3891 $this->send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE)); 3892 } catch (\RuntimeException $e) { 3893 return $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 3894 } 3895 3896 $payload = $this->get_binary_packet($skip_channel_filter); 3897 break; 3898 case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 3899 Strings::shift($payload, 1); 3900 list($data, $server_channel) = Strings::unpackSSH2('sN', $payload); 3901 switch ($data) { 3902 case 'auth-agent': 3903 case 'auth-agent@openssh.com': 3904 if (isset($this->agent)) { 3905 $new_channel = self::CHANNEL_AGENT_FORWARD; 3906 3907 list( 3908 $remote_window_size, 3909 $remote_maximum_packet_size 3910 ) = Strings::unpackSSH2('NN', $payload); 3911 3912 $this->packet_size_client_to_server[$new_channel] = $remote_window_size; 3913 $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size; 3914 $this->window_size_client_to_server[$new_channel] = $this->window_size; 3915 3916 $packet_size = 0x4000; 3917 3918 $packet = pack( 3919 'CN4', 3920 NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, 3921 $server_channel, 3922 $new_channel, 3923 $packet_size, 3924 $packet_size 3925 ); 3926 3927 $this->server_channels[$new_channel] = $server_channel; 3928 $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION; 3929 $this->send_binary_packet($packet); 3930 } 3931 break; 3932 default: 3933 $packet = Strings::packSSH2( 3934 'CN2ss', 3935 NET_SSH2_MSG_CHANNEL_OPEN_FAILURE, 3936 $server_channel, 3937 NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 3938 '', // description 3939 '' // language tag 3940 ); 3941 3942 try { 3943 $this->send_binary_packet($packet); 3944 } catch (\RuntimeException $e) { 3945 return $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 3946 } 3947 } 3948 3949 $payload = $this->get_binary_packet($skip_channel_filter); 3950 break; 3951 case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: 3952 Strings::shift($payload, 1); 3953 list($channel, $window_size) = Strings::unpackSSH2('NN', $payload); 3954 3955 $this->window_size_client_to_server[$channel] += $window_size; 3956 3957 $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->get_binary_packet($skip_channel_filter); 3958 } 3959 } 3960 3961 return $payload; 3962 } 3963 3964 /** 3965 * Enable Quiet Mode 3966 * 3967 * Suppress stderr from output 3968 * 3969 */ 3970 public function enableQuietMode() 3971 { 3972 $this->quiet_mode = true; 3973 } 3974 3975 /** 3976 * Disable Quiet Mode 3977 * 3978 * Show stderr in output 3979 * 3980 */ 3981 public function disableQuietMode() 3982 { 3983 $this->quiet_mode = false; 3984 } 3985 3986 /** 3987 * Returns whether Quiet Mode is enabled or not 3988 * 3989 * @see self::enableQuietMode() 3990 * @see self::disableQuietMode() 3991 * @return bool 3992 */ 3993 public function isQuietModeEnabled() 3994 { 3995 return $this->quiet_mode; 3996 } 3997 3998 /** 3999 * Enable request-pty when using exec() 4000 * 4001 */ 4002 public function enablePTY() 4003 { 4004 $this->request_pty = true; 4005 } 4006 4007 /** 4008 * Disable request-pty when using exec() 4009 * 4010 */ 4011 public function disablePTY() 4012 { 4013 if ($this->isPTYOpen()) { 4014 $this->close_channel(self::CHANNEL_EXEC); 4015 } 4016 $this->request_pty = false; 4017 } 4018 4019 /** 4020 * Returns whether request-pty is enabled or not 4021 * 4022 * @see self::enablePTY() 4023 * @see self::disablePTY() 4024 * @return bool 4025 */ 4026 public function isPTYEnabled() 4027 { 4028 return $this->request_pty; 4029 } 4030 4031 /** 4032 * Gets channel data 4033 * 4034 * Returns the data as a string. bool(true) is returned if: 4035 * 4036 * - the server closes the channel 4037 * - if the connection times out 4038 * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION 4039 * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS 4040 * - if the channel status is CHANNEL_CLOSE and the response was CHANNEL_CLOSE 4041 * 4042 * bool(false) is returned if: 4043 * 4044 * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE 4045 * 4046 * @param int $client_channel 4047 * @param bool $skip_extended 4048 * @return mixed 4049 * @throws \RuntimeException on connection error 4050 */ 4051 protected function get_channel_packet($client_channel, $skip_extended = false) 4052 { 4053 if (!empty($this->channel_buffers[$client_channel])) { 4054 switch ($this->channel_status[$client_channel]) { 4055 case NET_SSH2_MSG_CHANNEL_REQUEST: 4056 foreach ($this->channel_buffers[$client_channel] as $i => $packet) { 4057 switch (ord($packet[0])) { 4058 case NET_SSH2_MSG_CHANNEL_SUCCESS: 4059 case NET_SSH2_MSG_CHANNEL_FAILURE: 4060 unset($this->channel_buffers[$client_channel][$i]); 4061 return substr($packet, 1); 4062 } 4063 } 4064 break; 4065 default: 4066 return substr(array_shift($this->channel_buffers[$client_channel]), 1); 4067 } 4068 } 4069 4070 while (true) { 4071 if ($this->binary_packet_buffer !== false) { 4072 $response = $this->binary_packet_buffer; 4073 $this->binary_packet_buffer = false; 4074 } else { 4075 $response = $this->get_binary_packet(true); 4076 if ($response === true && $this->is_timeout) { 4077 if ($client_channel == self::CHANNEL_EXEC && !$this->request_pty) { 4078 $this->close_channel($client_channel); 4079 } 4080 return true; 4081 } 4082 if ($response === false) { 4083 $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); 4084 throw new ConnectionClosedException('Connection closed by server'); 4085 } 4086 } 4087 4088 if ($client_channel == -1 && $response === true) { 4089 return true; 4090 } 4091 list($type, $channel) = Strings::unpackSSH2('CN', $response); 4092 4093 // will not be setup yet on incoming channel open request 4094 if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) { 4095 $this->window_size_server_to_client[$channel] -= strlen($response); 4096 4097 // resize the window, if appropriate 4098 if ($this->window_size_server_to_client[$channel] < 0) { 4099 // PuTTY does something more analogous to the following: 4100 //if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) { 4101 $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize); 4102 $this->send_binary_packet($packet); 4103 $this->window_size_server_to_client[$channel] += $this->window_resize; 4104 } 4105 4106 switch ($type) { 4107 case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: 4108 /* 4109 if ($client_channel == self::CHANNEL_EXEC) { 4110 $this->send_channel_packet($client_channel, chr(0)); 4111 } 4112 */ 4113 // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR 4114 list($data_type_code, $data) = Strings::unpackSSH2('Ns', $response); 4115 $this->stdErrorLog .= $data; 4116 if ($skip_extended || $this->quiet_mode) { 4117 continue 2; 4118 } 4119 if ($client_channel == $channel && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA) { 4120 return $data; 4121 } 4122 $this->channel_buffers[$channel][] = chr($type) . $data; 4123 4124 continue 2; 4125 case NET_SSH2_MSG_CHANNEL_REQUEST: 4126 if ($this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_CLOSE) { 4127 continue 2; 4128 } 4129 list($value) = Strings::unpackSSH2('s', $response); 4130 switch ($value) { 4131 case 'exit-signal': 4132 list( 4133 , // FALSE 4134 $signal_name, 4135 , // core dumped 4136 $error_message 4137 ) = Strings::unpackSSH2('bsbs', $response); 4138 4139 $this->errors[] = "SSH_MSG_CHANNEL_REQUEST (exit-signal): $signal_name"; 4140 if (strlen($error_message)) { 4141 $this->errors[count($this->errors) - 1] .= "\r\n$error_message"; 4142 } 4143 4144 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); 4145 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); 4146 4147 $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF; 4148 4149 continue 3; 4150 case 'exit-status': 4151 list(, $this->exit_status) = Strings::unpackSSH2('CN', $response); 4152 4153 // "The client MAY ignore these messages." 4154 // -- http://tools.ietf.org/html/rfc4254#section-6.10 4155 4156 continue 3; 4157 default: 4158 // "Some systems may not implement signals, in which case they SHOULD ignore this message." 4159 // -- http://tools.ietf.org/html/rfc4254#section-6.9 4160 continue 3; 4161 } 4162 } 4163 4164 switch ($this->channel_status[$channel]) { 4165 case NET_SSH2_MSG_CHANNEL_OPEN: 4166 switch ($type) { 4167 case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: 4168 list( 4169 $this->server_channels[$channel], 4170 $window_size, 4171 $this->packet_size_client_to_server[$channel] 4172 ) = Strings::unpackSSH2('NNN', $response); 4173 4174 if ($window_size < 0) { 4175 $window_size &= 0x7FFFFFFF; 4176 $window_size += 0x80000000; 4177 } 4178 $this->window_size_client_to_server[$channel] = $window_size; 4179 $result = $client_channel == $channel ? true : $this->get_channel_packet($client_channel, $skip_extended); 4180 $this->on_channel_open(); 4181 return $result; 4182 case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: 4183 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 4184 throw new \RuntimeException('Unable to open channel'); 4185 default: 4186 if ($client_channel == $channel) { 4187 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 4188 throw new \RuntimeException('Unexpected response to open request'); 4189 } 4190 return $this->get_channel_packet($client_channel, $skip_extended); 4191 } 4192 break; 4193 case NET_SSH2_MSG_CHANNEL_REQUEST: 4194 switch ($type) { 4195 case NET_SSH2_MSG_CHANNEL_SUCCESS: 4196 return true; 4197 case NET_SSH2_MSG_CHANNEL_FAILURE: 4198 return false; 4199 case NET_SSH2_MSG_CHANNEL_DATA: 4200 list($data) = Strings::unpackSSH2('s', $response); 4201 $this->channel_buffers[$channel][] = chr($type) . $data; 4202 return $this->get_channel_packet($client_channel, $skip_extended); 4203 default: 4204 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 4205 throw new \RuntimeException('Unable to fulfill channel request'); 4206 } 4207 case NET_SSH2_MSG_CHANNEL_CLOSE: 4208 if ($client_channel == $channel && $type == NET_SSH2_MSG_CHANNEL_CLOSE) { 4209 return true; 4210 } 4211 return $this->get_channel_packet($client_channel, $skip_extended); 4212 } 4213 } 4214 4215 // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA 4216 4217 switch ($type) { 4218 case NET_SSH2_MSG_CHANNEL_DATA: 4219 /* 4220 if ($channel == self::CHANNEL_EXEC) { 4221 // SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server 4222 // this actually seems to make things twice as fast. more to the point, the message right after 4223 // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise. 4224 // in OpenSSH it slows things down but only by a couple thousandths of a second. 4225 $this->send_channel_packet($channel, chr(0)); 4226 } 4227 */ 4228 list($data) = Strings::unpackSSH2('s', $response); 4229 4230 if ($channel == self::CHANNEL_AGENT_FORWARD) { 4231 $agent_response = $this->agent->forwardData($data); 4232 if (!is_bool($agent_response)) { 4233 $this->send_channel_packet($channel, $agent_response); 4234 } 4235 break; 4236 } 4237 4238 if ($client_channel == $channel) { 4239 return $data; 4240 } 4241 $this->channel_buffers[$channel][] = chr($type) . $data; 4242 break; 4243 case NET_SSH2_MSG_CHANNEL_CLOSE: 4244 $this->curTimeout = 5; 4245 4246 $this->close_channel_bitmap($channel); 4247 4248 if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) { 4249 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); 4250 } 4251 4252 $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE; 4253 $this->channelCount--; 4254 4255 if ($client_channel == $channel) { 4256 return true; 4257 } 4258 // fall-through 4259 case NET_SSH2_MSG_CHANNEL_EOF: 4260 break; 4261 default: 4262 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); 4263 throw new \RuntimeException("Error reading channel data ($type)"); 4264 } 4265 } 4266 } 4267 4268 /** 4269 * Sends Binary Packets 4270 * 4271 * See '6. Binary Packet Protocol' of rfc4253 for more info. 4272 * 4273 * @param string $data 4274 * @param string $logged 4275 * @see self::_get_binary_packet() 4276 * @return void 4277 */ 4278 protected function send_binary_packet($data, $logged = null) 4279 { 4280 if (!is_resource($this->fsock) || feof($this->fsock)) { 4281 $this->bitmap = 0; 4282 throw new ConnectionClosedException('Connection closed prematurely'); 4283 } 4284 4285 if (!isset($logged)) { 4286 $logged = $data; 4287 } 4288 4289 switch ($this->compress) { 4290 case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH: 4291 if (!$this->isAuthenticated()) { 4292 break; 4293 } 4294 // fall-through 4295 case self::NET_SSH2_COMPRESSION_ZLIB: 4296 if (!$this->regenerate_compression_context) { 4297 $header = ''; 4298 } else { 4299 $this->regenerate_compression_context = false; 4300 $this->compress_context = deflate_init(ZLIB_ENCODING_RAW, ['window' => 15]); 4301 $header = "\x78\x9C"; 4302 } 4303 if ($this->compress_context) { 4304 $data = $header . deflate_add($this->compress_context, $data, ZLIB_PARTIAL_FLUSH); 4305 } 4306 } 4307 4308 // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9 4309 $packet_length = strlen($data) + 9; 4310 if ($this->encrypt && $this->encrypt->usesNonce()) { 4311 $packet_length -= 4; 4312 } 4313 // round up to the nearest $this->encrypt_block_size 4314 $packet_length += (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size; 4315 // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length 4316 $padding_length = $packet_length - strlen($data) - 5; 4317 switch (true) { 4318 case $this->encrypt && $this->encrypt->usesNonce(): 4319 case $this->hmac_create instanceof Hash && $this->hmac_create_etm: 4320 $padding_length += 4; 4321 $packet_length += 4; 4322 } 4323 4324 $padding = Random::string($padding_length); 4325 4326 // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself 4327 $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding); 4328 4329 $hmac = ''; 4330 if ($this->hmac_create instanceof Hash && !$this->hmac_create_etm) { 4331 if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') { 4332 $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no)); 4333 $hmac = $this->hmac_create->hash($packet); 4334 } else { 4335 $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)); 4336 } 4337 } 4338 4339 if ($this->encrypt) { 4340 switch ($this->encryptName) { 4341 case 'aes128-gcm@openssh.com': 4342 case 'aes256-gcm@openssh.com': 4343 $this->encrypt->setNonce( 4344 $this->encryptFixedPart . 4345 $this->encryptInvocationCounter 4346 ); 4347 Strings::increment_str($this->encryptInvocationCounter); 4348 $this->encrypt->setAAD($temp = ($packet & "\xFF\xFF\xFF\xFF")); 4349 $packet = $temp . $this->encrypt->encrypt(substr($packet, 4)); 4350 break; 4351 case 'chacha20-poly1305@openssh.com': 4352 // This should be impossible, but we are checking anyway to narrow the type for Psalm. 4353 if (!($this->encrypt instanceof ChaCha20)) { 4354 throw new \LogicException('$this->encrypt is not a ' . ChaCha20::class); 4355 } 4356 4357 $nonce = pack('N2', 0, $this->send_seq_no); 4358 4359 $this->encrypt->setNonce($nonce); 4360 $this->lengthEncrypt->setNonce($nonce); 4361 4362 $length = $this->lengthEncrypt->encrypt($packet & "\xFF\xFF\xFF\xFF"); 4363 4364 $this->encrypt->setCounter(0); 4365 // this is the same approach that's implemented in Salsa20::createPoly1305Key() 4366 // but we don't want to use the same AEAD construction that RFC8439 describes 4367 // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305()) 4368 $this->encrypt->setPoly1305Key( 4369 $this->encrypt->encrypt(str_repeat("\0", 32)) 4370 ); 4371 $this->encrypt->setAAD($length); 4372 $this->encrypt->setCounter(1); 4373 $packet = $length . $this->encrypt->encrypt(substr($packet, 4)); 4374 break; 4375 default: 4376 $packet = $this->hmac_create instanceof Hash && $this->hmac_create_etm ? 4377 ($packet & "\xFF\xFF\xFF\xFF") . $this->encrypt->encrypt(substr($packet, 4)) : 4378 $this->encrypt->encrypt($packet); 4379 } 4380 } 4381 4382 if ($this->hmac_create instanceof Hash && $this->hmac_create_etm) { 4383 if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') { 4384 $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no)); 4385 $hmac = $this->hmac_create->hash($packet); 4386 } else { 4387 $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)); 4388 } 4389 } 4390 4391 $this->send_seq_no++; 4392 4393 $packet .= $this->encrypt && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac; 4394 4395 $start = microtime(true); 4396 $sent = @fputs($this->fsock, $packet); 4397 $stop = microtime(true); 4398 4399 if (defined('NET_SSH2_LOGGING')) { 4400 $current = microtime(true); 4401 $message_number = isset(self::$message_numbers[ord($logged[0])]) ? self::$message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')'; 4402 $message_number = '-> ' . $message_number . 4403 ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)'; 4404 $this->append_log($message_number, $logged); 4405 $this->last_packet = $current; 4406 } 4407 4408 if (strlen($packet) != $sent) { 4409 $this->bitmap = 0; 4410 $message = $sent === false ? 4411 'Unable to write ' . strlen($packet) . ' bytes' : 4412 "Only $sent of " . strlen($packet) . " bytes were sent"; 4413 throw new \RuntimeException($message); 4414 } 4415 } 4416 4417 /** 4418 * Logs data packets 4419 * 4420 * Makes sure that only the last 1MB worth of packets will be logged 4421 * 4422 * @param string $message_number 4423 * @param string $message 4424 */ 4425 private function append_log($message_number, $message) 4426 { 4427 $this->append_log_helper( 4428 NET_SSH2_LOGGING, 4429 $message_number, 4430 $message, 4431 $this->message_number_log, 4432 $this->message_log, 4433 $this->log_size, 4434 $this->realtime_log_file, 4435 $this->realtime_log_wrap, 4436 $this->realtime_log_size 4437 ); 4438 } 4439 4440 /** 4441 * Logs data packet helper 4442 * 4443 * @param int $constant 4444 * @param string $message_number 4445 * @param string $message 4446 * @param array &$message_number_log 4447 * @param array &$message_log 4448 * @param int &$log_size 4449 * @param resource &$realtime_log_file 4450 * @param bool &$realtime_log_wrap 4451 * @param int &$realtime_log_size 4452 */ 4453 protected function append_log_helper($constant, $message_number, $message, array &$message_number_log, array &$message_log, &$log_size, &$realtime_log_file, &$realtime_log_wrap, &$realtime_log_size) 4454 { 4455 // remove the byte identifying the message type from all but the first two messages (ie. the identification strings) 4456 if (strlen($message_number) > 2) { 4457 Strings::shift($message); 4458 } 4459 4460 switch ($constant) { 4461 // useful for benchmarks 4462 case self::LOG_SIMPLE: 4463 $message_number_log[] = $message_number; 4464 break; 4465 case self::LOG_SIMPLE_REALTIME: 4466 echo $message_number; 4467 echo PHP_SAPI == 'cli' ? "\r\n" : '<br>'; 4468 @flush(); 4469 @ob_flush(); 4470 break; 4471 // the most useful log for SSH2 4472 case self::LOG_COMPLEX: 4473 $message_number_log[] = $message_number; 4474 $log_size += strlen($message); 4475 $message_log[] = $message; 4476 while ($log_size > self::LOG_MAX_SIZE) { 4477 $log_size -= strlen(array_shift($message_log)); 4478 array_shift($message_number_log); 4479 } 4480 break; 4481 // dump the output out realtime; packets may be interspersed with non packets, 4482 // passwords won't be filtered out and select other packets may not be correctly 4483 // identified 4484 case self::LOG_REALTIME: 4485 switch (PHP_SAPI) { 4486 case 'cli': 4487 $start = $stop = "\r\n"; 4488 break; 4489 default: 4490 $start = '<pre>'; 4491 $stop = '</pre>'; 4492 } 4493 echo $start . $this->format_log([$message], [$message_number]) . $stop; 4494 @flush(); 4495 @ob_flush(); 4496 break; 4497 // basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME 4498 // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE. 4499 // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily 4500 // at the beginning of the file 4501 case self::LOG_REALTIME_FILE: 4502 if (!isset($realtime_log_file)) { 4503 // PHP doesn't seem to like using constants in fopen() 4504 $filename = NET_SSH2_LOG_REALTIME_FILENAME; 4505 $fp = fopen($filename, 'w'); 4506 $realtime_log_file = $fp; 4507 } 4508 if (!is_resource($realtime_log_file)) { 4509 break; 4510 } 4511 $entry = $this->format_log([$message], [$message_number]); 4512 if ($realtime_log_wrap) { 4513 $temp = "<<< START >>>\r\n"; 4514 $entry .= $temp; 4515 fseek($realtime_log_file, ftell($realtime_log_file) - strlen($temp)); 4516 } 4517 $realtime_log_size += strlen($entry); 4518 if ($realtime_log_size > self::LOG_MAX_SIZE) { 4519 fseek($realtime_log_file, 0); 4520 $realtime_log_size = strlen($entry); 4521 $realtime_log_wrap = true; 4522 } 4523 fputs($realtime_log_file, $entry); 4524 } 4525 } 4526 4527 /** 4528 * Sends channel data 4529 * 4530 * Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate 4531 * 4532 * @param int $client_channel 4533 * @param string $data 4534 * @return void 4535 */ 4536 protected function send_channel_packet($client_channel, $data) 4537 { 4538 while (strlen($data)) { 4539 if (!$this->window_size_client_to_server[$client_channel]) { 4540 $this->bitmap ^= self::MASK_WINDOW_ADJUST; 4541 // using an invalid channel will let the buffers be built up for the valid channels 4542 $this->get_channel_packet(-1); 4543 $this->bitmap ^= self::MASK_WINDOW_ADJUST; 4544 } 4545 4546 /* The maximum amount of data allowed is determined by the maximum 4547 packet size for the channel, and the current window size, whichever 4548 is smaller. 4549 -- http://tools.ietf.org/html/rfc4254#section-5.2 */ 4550 $max_size = min( 4551 $this->packet_size_client_to_server[$client_channel], 4552 $this->window_size_client_to_server[$client_channel] 4553 ); 4554 4555 $temp = Strings::shift($data, $max_size); 4556 $packet = Strings::packSSH2( 4557 'CNs', 4558 NET_SSH2_MSG_CHANNEL_DATA, 4559 $this->server_channels[$client_channel], 4560 $temp 4561 ); 4562 $this->window_size_client_to_server[$client_channel] -= strlen($temp); 4563 $this->send_binary_packet($packet); 4564 } 4565 } 4566 4567 /** 4568 * Closes and flushes a channel 4569 * 4570 * \phpseclib3\Net\SSH2 doesn't properly close most channels. For exec() channels are normally closed by the server 4571 * and for SFTP channels are presumably closed when the client disconnects. This functions is intended 4572 * for SCP more than anything. 4573 * 4574 * @param int $client_channel 4575 * @param bool $want_reply 4576 * @return void 4577 */ 4578 private function close_channel($client_channel, $want_reply = false) 4579 { 4580 // see http://tools.ietf.org/html/rfc4254#section-5.3 4581 4582 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); 4583 4584 if (!$want_reply) { 4585 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); 4586 } 4587 4588 $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE; 4589 $this->channelCount--; 4590 4591 $this->curTimeout = 5; 4592 4593 while (!is_bool($this->get_channel_packet($client_channel))) { 4594 } 4595 4596 if ($want_reply) { 4597 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); 4598 } 4599 4600 $this->close_channel_bitmap($client_channel); 4601 } 4602 4603 /** 4604 * Maintains execution state bitmap in response to channel closure 4605 * 4606 * @param int $client_channel The channel number to maintain closure status of 4607 * @return void 4608 */ 4609 private function close_channel_bitmap($client_channel) 4610 { 4611 switch ($client_channel) { 4612 case self::CHANNEL_SHELL: 4613 // Shell status has been maintained in the bitmap for backwards 4614 // compatibility sake, but can be removed going forward 4615 if ($this->bitmap & self::MASK_SHELL) { 4616 $this->bitmap &= ~self::MASK_SHELL; 4617 } 4618 break; 4619 } 4620 } 4621 4622 /** 4623 * Disconnect 4624 * 4625 * @param int $reason 4626 * @return false 4627 */ 4628 protected function disconnect_helper($reason) 4629 { 4630 if ($this->bitmap & self::MASK_CONNECTED) { 4631 $data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', ''); 4632 try { 4633 $this->send_binary_packet($data); 4634 } catch (\Exception $e) { 4635 } 4636 } 4637 4638 $this->bitmap = 0; 4639 if (is_resource($this->fsock) && get_resource_type($this->fsock) === 'stream') { 4640 fclose($this->fsock); 4641 } 4642 4643 return false; 4644 } 4645 4646 /** 4647 * Define Array 4648 * 4649 * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of 4650 * named constants from it, using the value as the name of the constant and the index as the value of the constant. 4651 * If any of the constants that would be defined already exists, none of the constants will be defined. 4652 * 4653 * @param mixed[] ...$args 4654 * @access protected 4655 */ 4656 protected static function define_array(...$args) 4657 { 4658 foreach ($args as $arg) { 4659 foreach ($arg as $key => $value) { 4660 if (!defined($value)) { 4661 define($value, $key); 4662 } else { 4663 break 2; 4664 } 4665 } 4666 } 4667 } 4668 4669 /** 4670 * Returns a log of the packets that have been sent and received. 4671 * 4672 * Returns a string if NET_SSH2_LOGGING == self::LOG_COMPLEX, an array if NET_SSH2_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING') 4673 * 4674 * @return array|false|string 4675 */ 4676 public function getLog() 4677 { 4678 if (!defined('NET_SSH2_LOGGING')) { 4679 return false; 4680 } 4681 4682 switch (NET_SSH2_LOGGING) { 4683 case self::LOG_SIMPLE: 4684 return $this->message_number_log; 4685 case self::LOG_COMPLEX: 4686 $log = $this->format_log($this->message_log, $this->message_number_log); 4687 return PHP_SAPI == 'cli' ? $log : '<pre>' . $log . '</pre>'; 4688 default: 4689 return false; 4690 } 4691 } 4692 4693 /** 4694 * Formats a log for printing 4695 * 4696 * @param array $message_log 4697 * @param array $message_number_log 4698 * @return string 4699 */ 4700 protected function format_log(array $message_log, array $message_number_log) 4701 { 4702 $output = ''; 4703 for ($i = 0; $i < count($message_log); $i++) { 4704 $output .= $message_number_log[$i] . "\r\n"; 4705 $current_log = $message_log[$i]; 4706 $j = 0; 4707 do { 4708 if (strlen($current_log)) { 4709 $output .= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 '; 4710 } 4711 $fragment = Strings::shift($current_log, $this->log_short_width); 4712 $hex = substr(preg_replace_callback('#.#s', function ($matches) { 4713 return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT); 4714 }, $fragment), strlen($this->log_boundary)); 4715 // replace non ASCII printable characters with dots 4716 // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters 4717 // also replace < with a . since < messes up the output on web browsers 4718 $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment); 4719 $output .= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n"; 4720 $j++; 4721 } while (strlen($current_log)); 4722 $output .= "\r\n"; 4723 } 4724 4725 return $output; 4726 } 4727 4728 /** 4729 * Helper function for agent->on_channel_open() 4730 * 4731 * Used when channels are created to inform agent 4732 * of said channel opening. Must be called after 4733 * channel open confirmation received 4734 * 4735 */ 4736 private function on_channel_open() 4737 { 4738 if (isset($this->agent)) { 4739 $this->agent->registerChannelOpen($this); 4740 } 4741 } 4742 4743 /** 4744 * Returns the first value of the intersection of two arrays or false if 4745 * the intersection is empty. The order is defined by the first parameter. 4746 * 4747 * @param array $array1 4748 * @param array $array2 4749 * @return mixed False if intersection is empty, else intersected value. 4750 */ 4751 private static function array_intersect_first(array $array1, array $array2) 4752 { 4753 foreach ($array1 as $value) { 4754 if (in_array($value, $array2)) { 4755 return $value; 4756 } 4757 } 4758 return false; 4759 } 4760 4761 /** 4762 * Returns all errors / debug messages on the SSH layer 4763 * 4764 * If you are looking for messages from the SFTP layer, please see SFTP::getSFTPErrors() 4765 * 4766 * @return string[] 4767 */ 4768 public function getErrors() 4769 { 4770 return $this->errors; 4771 } 4772 4773 /** 4774 * Returns the last error received on the SSH layer 4775 * 4776 * If you are looking for messages from the SFTP layer, please see SFTP::getLastSFTPError() 4777 * 4778 * @return string 4779 */ 4780 public function getLastError() 4781 { 4782 $count = count($this->errors); 4783 4784 if ($count > 0) { 4785 return $this->errors[$count - 1]; 4786 } 4787 } 4788 4789 /** 4790 * Return the server identification. 4791 * 4792 * @return string|false 4793 */ 4794 public function getServerIdentification() 4795 { 4796 $this->connect(); 4797 4798 return $this->server_identifier; 4799 } 4800 4801 /** 4802 * Returns a list of algorithms the server supports 4803 * 4804 * @return array 4805 */ 4806 public function getServerAlgorithms() 4807 { 4808 $this->connect(); 4809 4810 return [ 4811 'kex' => $this->kex_algorithms, 4812 'hostkey' => $this->server_host_key_algorithms, 4813 'client_to_server' => [ 4814 'crypt' => $this->encryption_algorithms_client_to_server, 4815 'mac' => $this->mac_algorithms_client_to_server, 4816 'comp' => $this->compression_algorithms_client_to_server, 4817 'lang' => $this->languages_client_to_server 4818 ], 4819 'server_to_client' => [ 4820 'crypt' => $this->encryption_algorithms_server_to_client, 4821 'mac' => $this->mac_algorithms_server_to_client, 4822 'comp' => $this->compression_algorithms_server_to_client, 4823 'lang' => $this->languages_server_to_client 4824 ] 4825 ]; 4826 } 4827 4828 /** 4829 * Returns a list of KEX algorithms that phpseclib supports 4830 * 4831 * @return array 4832 */ 4833 public static function getSupportedKEXAlgorithms() 4834 { 4835 $kex_algorithms = [ 4836 // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using 4837 // Curve25519. See doc/curve25519-sha256@libssh.org.txt in the 4838 // libssh repository for more information. 4839 'curve25519-sha256', 4840 'curve25519-sha256@libssh.org', 4841 4842 'ecdh-sha2-nistp256', // RFC 5656 4843 'ecdh-sha2-nistp384', // RFC 5656 4844 'ecdh-sha2-nistp521', // RFC 5656 4845 4846 'diffie-hellman-group-exchange-sha256',// RFC 4419 4847 'diffie-hellman-group-exchange-sha1', // RFC 4419 4848 4849 // Diffie-Hellman Key Agreement (DH) using integer modulo prime 4850 // groups. 4851 'diffie-hellman-group14-sha256', 4852 'diffie-hellman-group14-sha1', // REQUIRED 4853 'diffie-hellman-group15-sha512', 4854 'diffie-hellman-group16-sha512', 4855 'diffie-hellman-group17-sha512', 4856 'diffie-hellman-group18-sha512', 4857 4858 'diffie-hellman-group1-sha1', // REQUIRED 4859 ]; 4860 4861 return $kex_algorithms; 4862 } 4863 4864 /** 4865 * Returns a list of host key algorithms that phpseclib supports 4866 * 4867 * @return array 4868 */ 4869 public static function getSupportedHostKeyAlgorithms() 4870 { 4871 return [ 4872 'ssh-ed25519', // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02 4873 'ecdsa-sha2-nistp256', // RFC 5656 4874 'ecdsa-sha2-nistp384', // RFC 5656 4875 'ecdsa-sha2-nistp521', // RFC 5656 4876 'rsa-sha2-256', // RFC 8332 4877 'rsa-sha2-512', // RFC 8332 4878 'ssh-rsa', // RECOMMENDED sign Raw RSA Key 4879 'ssh-dss' // REQUIRED sign Raw DSS Key 4880 ]; 4881 } 4882 4883 /** 4884 * Returns a list of symmetric key algorithms that phpseclib supports 4885 * 4886 * @return array 4887 */ 4888 public static function getSupportedEncryptionAlgorithms() 4889 { 4890 $algos = [ 4891 // from <https://tools.ietf.org/html/rfc5647>: 4892 'aes128-gcm@openssh.com', 4893 'aes256-gcm@openssh.com', 4894 4895 // from <http://tools.ietf.org/html/rfc4345#section-4>: 4896 'arcfour256', 4897 'arcfour128', 4898 4899 //'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key 4900 4901 // CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>: 4902 'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key 4903 'aes192-ctr', // RECOMMENDED AES with 192-bit key 4904 'aes256-ctr', // RECOMMENDED AES with 256-bit key 4905 4906 // from <https://github.com/openssh/openssh-portable/blob/001aa55/PROTOCOL.chacha20poly1305>: 4907 // one of the big benefits of chacha20-poly1305 is speed. the problem is... 4908 // libsodium doesn't generate the poly1305 keys in the way ssh does and openssl's PHP bindings don't even 4909 // seem to support poly1305 currently. so even if libsodium or openssl are being used for the chacha20 4910 // part, pure-PHP has to be used for the poly1305 part and that's gonna cause a big slow down. 4911 // speed-wise it winds up being faster to use AES (when openssl or mcrypt are available) and some HMAC 4912 // (which is always gonna be super fast to compute thanks to the hash extension, which 4913 // "is bundled and compiled into PHP by default") 4914 'chacha20-poly1305@openssh.com', 4915 4916 'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key 4917 'twofish192-ctr', // OPTIONAL Twofish with 192-bit key 4918 'twofish256-ctr', // OPTIONAL Twofish with 256-bit key 4919 4920 'aes128-cbc', // RECOMMENDED AES with a 128-bit key 4921 'aes192-cbc', // OPTIONAL AES with a 192-bit key 4922 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key 4923 4924 'twofish128-cbc', // OPTIONAL Twofish with a 128-bit key 4925 'twofish192-cbc', // OPTIONAL Twofish with a 192-bit key 4926 'twofish256-cbc', 4927 'twofish-cbc', // OPTIONAL alias for "twofish256-cbc" 4928 // (this is being retained for historical reasons) 4929 4930 'blowfish-ctr', // OPTIONAL Blowfish in SDCTR mode 4931 4932 'blowfish-cbc', // OPTIONAL Blowfish in CBC mode 4933 4934 '3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode 4935 4936 '3des-cbc', // REQUIRED three-key 3DES in CBC mode 4937 4938 //'none' // OPTIONAL no encryption; NOT RECOMMENDED 4939 ]; 4940 4941 if (self::$crypto_engine) { 4942 $engines = [self::$crypto_engine]; 4943 } else { 4944 $engines = [ 4945 'libsodium', 4946 'OpenSSL (GCM)', 4947 'OpenSSL', 4948 'mcrypt', 4949 'Eval', 4950 'PHP' 4951 ]; 4952 } 4953 4954 $ciphers = []; 4955 4956 foreach ($engines as $engine) { 4957 foreach ($algos as $algo) { 4958 $obj = self::encryption_algorithm_to_crypt_instance($algo); 4959 if ($obj instanceof Rijndael) { 4960 $obj->setKeyLength(preg_replace('#[^\d]#', '', $algo)); 4961 } 4962 switch ($algo) { 4963 case 'chacha20-poly1305@openssh.com': 4964 case 'arcfour128': 4965 case 'arcfour256': 4966 if ($engine != 'Eval') { 4967 continue 2; 4968 } 4969 break; 4970 case 'aes128-gcm@openssh.com': 4971 case 'aes256-gcm@openssh.com': 4972 if ($engine == 'OpenSSL') { 4973 continue 2; 4974 } 4975 $obj->setNonce('dummydummydu'); 4976 } 4977 if ($obj->isValidEngine($engine)) { 4978 $algos = array_diff($algos, [$algo]); 4979 $ciphers[] = $algo; 4980 } 4981 } 4982 } 4983 4984 return $ciphers; 4985 } 4986 4987 /** 4988 * Returns a list of MAC algorithms that phpseclib supports 4989 * 4990 * @return array 4991 */ 4992 public static function getSupportedMACAlgorithms() 4993 { 4994 return [ 4995 'hmac-sha2-256-etm@openssh.com', 4996 'hmac-sha2-512-etm@openssh.com', 4997 'umac-64-etm@openssh.com', 4998 'umac-128-etm@openssh.com', 4999 'hmac-sha1-etm@openssh.com', 5000 5001 // from <http://www.ietf.org/rfc/rfc6668.txt>: 5002 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32) 5003 'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64) 5004 5005 // from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>: 5006 'umac-64@openssh.com', 5007 'umac-128@openssh.com', 5008 5009 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20) 5010 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20) 5011 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16) 5012 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16) 5013 //'none' // OPTIONAL no MAC; NOT RECOMMENDED 5014 ]; 5015 } 5016 5017 /** 5018 * Returns a list of compression algorithms that phpseclib supports 5019 * 5020 * @return array 5021 */ 5022 public static function getSupportedCompressionAlgorithms() 5023 { 5024 $algos = ['none']; // REQUIRED no compression 5025 if (function_exists('deflate_init')) { 5026 $algos[] = 'zlib@openssh.com'; // https://datatracker.ietf.org/doc/html/draft-miller-secsh-compression-delayed 5027 $algos[] = 'zlib'; 5028 } 5029 return $algos; 5030 } 5031 5032 /** 5033 * Return list of negotiated algorithms 5034 * 5035 * Uses the same format as https://www.php.net/ssh2-methods-negotiated 5036 * 5037 * @return array 5038 */ 5039 public function getAlgorithmsNegotiated() 5040 { 5041 $this->connect(); 5042 5043 $compression_map = [ 5044 self::NET_SSH2_COMPRESSION_NONE => 'none', 5045 self::NET_SSH2_COMPRESSION_ZLIB => 'zlib', 5046 self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH => 'zlib@openssh.com' 5047 ]; 5048 5049 return [ 5050 'kex' => $this->kex_algorithm, 5051 'hostkey' => $this->signature_format, 5052 'client_to_server' => [ 5053 'crypt' => $this->encryptName, 5054 'mac' => $this->hmac_create_name, 5055 'comp' => $compression_map[$this->compress], 5056 ], 5057 'server_to_client' => [ 5058 'crypt' => $this->decryptName, 5059 'mac' => $this->hmac_check_name, 5060 'comp' => $compression_map[$this->decompress], 5061 ] 5062 ]; 5063 } 5064 5065 /** 5066 * Force multiple channels (even if phpseclib has decided to disable them) 5067 */ 5068 public function forceMultipleChannels() 5069 { 5070 $this->errorOnMultipleChannels = false; 5071 } 5072 5073 /** 5074 * Allows you to set the terminal 5075 * 5076 * @param string $term 5077 */ 5078 public function setTerminal($term) 5079 { 5080 $this->term = $term; 5081 } 5082 5083 /** 5084 * Accepts an associative array with up to four parameters as described at 5085 * <https://www.php.net/manual/en/function.ssh2-connect.php> 5086 * 5087 * @param array $methods 5088 */ 5089 public function setPreferredAlgorithms(array $methods) 5090 { 5091 $preferred = $methods; 5092 5093 if (isset($preferred['kex'])) { 5094 $preferred['kex'] = array_intersect( 5095 $preferred['kex'], 5096 static::getSupportedKEXAlgorithms() 5097 ); 5098 } 5099 5100 if (isset($preferred['hostkey'])) { 5101 $preferred['hostkey'] = array_intersect( 5102 $preferred['hostkey'], 5103 static::getSupportedHostKeyAlgorithms() 5104 ); 5105 } 5106 5107 $keys = ['client_to_server', 'server_to_client']; 5108 foreach ($keys as $key) { 5109 if (isset($preferred[$key])) { 5110 $a = &$preferred[$key]; 5111 if (isset($a['crypt'])) { 5112 $a['crypt'] = array_intersect( 5113 $a['crypt'], 5114 static::getSupportedEncryptionAlgorithms() 5115 ); 5116 } 5117 if (isset($a['comp'])) { 5118 $a['comp'] = array_intersect( 5119 $a['comp'], 5120 static::getSupportedCompressionAlgorithms() 5121 ); 5122 } 5123 if (isset($a['mac'])) { 5124 $a['mac'] = array_intersect( 5125 $a['mac'], 5126 static::getSupportedMACAlgorithms() 5127 ); 5128 } 5129 } 5130 } 5131 5132 $keys = [ 5133 'kex', 5134 'hostkey', 5135 'client_to_server/crypt', 5136 'client_to_server/comp', 5137 'client_to_server/mac', 5138 'server_to_client/crypt', 5139 'server_to_client/comp', 5140 'server_to_client/mac', 5141 ]; 5142 foreach ($keys as $key) { 5143 $p = $preferred; 5144 $m = $methods; 5145 5146 $subkeys = explode('/', $key); 5147 foreach ($subkeys as $subkey) { 5148 if (!isset($p[$subkey])) { 5149 continue 2; 5150 } 5151 $p = $p[$subkey]; 5152 $m = $m[$subkey]; 5153 } 5154 5155 if (count($p) != count($m)) { 5156 $diff = array_diff($m, $p); 5157 $msg = count($diff) == 1 ? 5158 ' is not a supported algorithm' : 5159 ' are not supported algorithms'; 5160 throw new UnsupportedAlgorithmException(implode(', ', $diff) . $msg); 5161 } 5162 } 5163 5164 $this->preferred = $preferred; 5165 } 5166 5167 /** 5168 * Returns the banner message. 5169 * 5170 * Quoting from the RFC, "in some jurisdictions, sending a warning message before 5171 * authentication may be relevant for getting legal protection." 5172 * 5173 * @return string 5174 */ 5175 public function getBannerMessage() 5176 { 5177 return $this->banner_message; 5178 } 5179 5180 /** 5181 * Returns the server public host key. 5182 * 5183 * Caching this the first time you connect to a server and checking the result on subsequent connections 5184 * is recommended. Returns false if the server signature is not signed correctly with the public host key. 5185 * 5186 * @return string|false 5187 * @throws \RuntimeException on badly formatted keys 5188 * @throws \phpseclib3\Exception\NoSupportedAlgorithmsException when the key isn't in a supported format 5189 */ 5190 public function getServerPublicHostKey() 5191 { 5192 if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { 5193 $this->connect(); 5194 } 5195 5196 $signature = $this->signature; 5197 $server_public_host_key = base64_encode($this->server_public_host_key); 5198 5199 if ($this->signature_validated) { 5200 return $this->bitmap ? 5201 $this->signature_format . ' ' . $server_public_host_key : 5202 false; 5203 } 5204 5205 $this->signature_validated = true; 5206 5207 switch ($this->signature_format) { 5208 case 'ssh-ed25519': 5209 case 'ecdsa-sha2-nistp256': 5210 case 'ecdsa-sha2-nistp384': 5211 case 'ecdsa-sha2-nistp521': 5212 $key = EC::loadFormat('OpenSSH', $server_public_host_key) 5213 ->withSignatureFormat('SSH2'); 5214 switch ($this->signature_format) { 5215 case 'ssh-ed25519': 5216 $hash = 'sha512'; 5217 break; 5218 case 'ecdsa-sha2-nistp256': 5219 $hash = 'sha256'; 5220 break; 5221 case 'ecdsa-sha2-nistp384': 5222 $hash = 'sha384'; 5223 break; 5224 case 'ecdsa-sha2-nistp521': 5225 $hash = 'sha512'; 5226 } 5227 $key = $key->withHash($hash); 5228 break; 5229 case 'ssh-dss': 5230 $key = DSA::loadFormat('OpenSSH', $server_public_host_key) 5231 ->withSignatureFormat('SSH2') 5232 ->withHash('sha1'); 5233 break; 5234 case 'ssh-rsa': 5235 case 'rsa-sha2-256': 5236 case 'rsa-sha2-512': 5237 // could be ssh-rsa, rsa-sha2-256, rsa-sha2-512 5238 // we don't check here because we already checked in key_exchange 5239 // some signatures have the type embedded within the message and some don't 5240 list(, $signature) = Strings::unpackSSH2('ss', $signature); 5241 5242 $key = RSA::loadFormat('OpenSSH', $server_public_host_key) 5243 ->withPadding(RSA::SIGNATURE_PKCS1); 5244 switch ($this->signature_format) { 5245 case 'rsa-sha2-512': 5246 $hash = 'sha512'; 5247 break; 5248 case 'rsa-sha2-256': 5249 $hash = 'sha256'; 5250 break; 5251 //case 'ssh-rsa': 5252 default: 5253 $hash = 'sha1'; 5254 } 5255 $key = $key->withHash($hash); 5256 break; 5257 default: 5258 $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); 5259 throw new NoSupportedAlgorithmsException('Unsupported signature format'); 5260 } 5261 5262 if (!$key->verify($this->exchange_hash, $signature)) { 5263 return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); 5264 }; 5265 5266 return $this->signature_format . ' ' . $server_public_host_key; 5267 } 5268 5269 /** 5270 * Returns the exit status of an SSH command or false. 5271 * 5272 * @return false|int 5273 */ 5274 public function getExitStatus() 5275 { 5276 if (is_null($this->exit_status)) { 5277 return false; 5278 } 5279 return $this->exit_status; 5280 } 5281 5282 /** 5283 * Returns the number of columns for the terminal window size. 5284 * 5285 * @return int 5286 */ 5287 public function getWindowColumns() 5288 { 5289 return $this->windowColumns; 5290 } 5291 5292 /** 5293 * Returns the number of rows for the terminal window size. 5294 * 5295 * @return int 5296 */ 5297 public function getWindowRows() 5298 { 5299 return $this->windowRows; 5300 } 5301 5302 /** 5303 * Sets the number of columns for the terminal window size. 5304 * 5305 * @param int $value 5306 */ 5307 public function setWindowColumns($value) 5308 { 5309 $this->windowColumns = $value; 5310 } 5311 5312 /** 5313 * Sets the number of rows for the terminal window size. 5314 * 5315 * @param int $value 5316 */ 5317 public function setWindowRows($value) 5318 { 5319 $this->windowRows = $value; 5320 } 5321 5322 /** 5323 * Sets the number of columns and rows for the terminal window size. 5324 * 5325 * @param int $columns 5326 * @param int $rows 5327 */ 5328 public function setWindowSize($columns = 80, $rows = 24) 5329 { 5330 $this->windowColumns = $columns; 5331 $this->windowRows = $rows; 5332 } 5333 5334 /** 5335 * To String Magic Method 5336 * 5337 * @return string 5338 */ 5339 #[\ReturnTypeWillChange] 5340 public function __toString() 5341 { 5342 return $this->getResourceId(); 5343 } 5344 5345 /** 5346 * Get Resource ID 5347 * 5348 * We use {} because that symbols should not be in URL according to 5349 * {@link http://tools.ietf.org/html/rfc3986#section-2 RFC}. 5350 * It will safe us from any conflicts, because otherwise regexp will 5351 * match all alphanumeric domains. 5352 * 5353 * @return string 5354 */ 5355 public function getResourceId() 5356 { 5357 return '{' . spl_object_hash($this) . '}'; 5358 } 5359 5360 /** 5361 * Return existing connection 5362 * 5363 * @param string $id 5364 * 5365 * @return bool|SSH2 will return false if no such connection 5366 */ 5367 public static function getConnectionByResourceId($id) 5368 { 5369 if (isset(self::$connections[$id])) { 5370 return self::$connections[$id] instanceof \WeakReference ? self::$connections[$id]->get() : self::$connections[$id]; 5371 } 5372 return false; 5373 } 5374 5375 /** 5376 * Return all excising connections 5377 * 5378 * @return array<string, SSH2> 5379 */ 5380 public static function getConnections() 5381 { 5382 if (!class_exists('WeakReference')) { 5383 /** @var array<string, SSH2> */ 5384 return self::$connections; 5385 } 5386 $temp = []; 5387 foreach (self::$connections as $key => $ref) { 5388 $temp[$key] = $ref->get(); 5389 } 5390 return $temp; 5391 } 5392 5393 /* 5394 * Update packet types in log history 5395 * 5396 * @param string $old 5397 * @param string $new 5398 */ 5399 private function updateLogHistory($old, $new) 5400 { 5401 if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) { 5402 $this->message_number_log[count($this->message_number_log) - 1] = str_replace( 5403 $old, 5404 $new, 5405 $this->message_number_log[count($this->message_number_log) - 1] 5406 ); 5407 } 5408 } 5409 5410 /** 5411 * Return the list of authentication methods that may productively continue authentication. 5412 * 5413 * @see https://tools.ietf.org/html/rfc4252#section-5.1 5414 * @return array|null 5415 */ 5416 public function getAuthMethodsToContinue() 5417 { 5418 return $this->auth_methods_to_continue; 5419 } 5420 5421 /** 5422 * Enables "smart" multi-factor authentication (MFA) 5423 */ 5424 public function enableSmartMFA() 5425 { 5426 $this->smartMFA = true; 5427 } 5428 5429 /** 5430 * Disables "smart" multi-factor authentication (MFA) 5431 */ 5432 public function disableSmartMFA() 5433 { 5434 $this->smartMFA = false; 5435 } 5436} 5437