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