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