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