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