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