PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.6.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.6.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / phpseclib / phpseclib / phpseclib / Net / SSH2.php

SSH2.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.6.0, at vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php

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