]> git.mxchange.org Git - friendica.git/blob - library/phpsec/Net/SSH2.php
Merge branch 'master' of https://github.com/friendica/friendica
[friendica.git] / library / phpsec / Net / SSH2.php
1 <?php\r
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */\r
3 \r
4 /**\r
5  * Pure-PHP implementation of SSHv2.\r
6  *\r
7  * PHP versions 4 and 5\r
8  *\r
9  * Here are some examples of how to use this library:\r
10  * <code>\r
11  * <?php\r
12  *    include('Net/SSH2.php');\r
13  *\r
14  *    $ssh = new Net_SSH2('www.domain.tld');\r
15  *    if (!$ssh->login('username', 'password')) {\r
16  *        exit('Login Failed');\r
17  *    }\r
18  *\r
19  *    echo $ssh->exec('pwd');\r
20  *    echo $ssh->exec('ls -la');\r
21  * ?>\r
22  * </code>\r
23  *\r
24  * <code>\r
25  * <?php\r
26  *    include('Crypt/RSA.php');\r
27  *    include('Net/SSH2.php');\r
28  *\r
29  *    $key = new Crypt_RSA();\r
30  *    //$key->setPassword('whatever');\r
31  *    $key->loadKey(file_get_contents('privatekey'));\r
32  *\r
33  *    $ssh = new Net_SSH2('www.domain.tld');\r
34  *    if (!$ssh->login('username', $key)) {\r
35  *        exit('Login Failed');\r
36  *    }\r
37  *\r
38  *    echo $ssh->exec('pwd');\r
39  *    echo $ssh->exec('ls -la');\r
40  * ?>\r
41  * </code>\r
42  *\r
43  * LICENSE: This library is free software; you can redistribute it and/or\r
44  * modify it under the terms of the GNU Lesser General Public\r
45  * License as published by the Free Software Foundation; either\r
46  * version 2.1 of the License, or (at your option) any later version.\r
47  *\r
48  * This library is distributed in the hope that it will be useful,\r
49  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
50  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
51  * Lesser General Public License for more details.\r
52  *\r
53  * You should have received a copy of the GNU Lesser General Public\r
54  * License along with this library; if not, write to the Free Software\r
55  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,\r
56  * MA  02111-1307  USA\r
57  *\r
58  * @category   Net\r
59  * @package    Net_SSH2\r
60  * @author     Jim Wigginton <terrafrost@php.net>\r
61  * @copyright  MMVII Jim Wigginton\r
62  * @license    http://www.gnu.org/licenses/lgpl.txt\r
63  * @version    $Id: SSH2.php,v 1.46 2010/04/27 21:29:36 terrafrost Exp $\r
64  * @link       http://phpseclib.sourceforge.net\r
65  */\r
66 \r
67 /**\r
68  * Include Math_BigInteger\r
69  *\r
70  * Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.\r
71  */\r
72 require_once('Math/BigInteger.php');\r
73 \r
74 /**\r
75  * Include Crypt_Random\r
76  */\r
77 require_once('Crypt/Random.php');\r
78 \r
79 /**\r
80  * Include Crypt_Hash\r
81  */\r
82 require_once('Crypt/Hash.php');\r
83 \r
84 /**\r
85  * Include Crypt_TripleDES\r
86  */\r
87 require_once('Crypt/TripleDES.php');\r
88 \r
89 /**\r
90  * Include Crypt_RC4\r
91  */\r
92 require_once('Crypt/RC4.php');\r
93 \r
94 /**\r
95  * Include Crypt_AES\r
96  */\r
97 require_once('Crypt/AES.php');\r
98 \r
99 /**#@+\r
100  * Execution Bitmap Masks\r
101  *\r
102  * @see Net_SSH2::bitmap\r
103  * @access private\r
104  */\r
105 define('NET_SSH2_MASK_CONSTRUCTOR', 0x00000001);\r
106 define('NET_SSH2_MASK_LOGIN',       0x00000002);\r
107 /**#@-*/\r
108 \r
109 /**#@+\r
110  * Channel constants\r
111  *\r
112  * RFC4254 refers not to client and server channels but rather to sender and recipient channels.  we don't refer\r
113  * to them in that way because RFC4254 toggles the meaning. the client sends a SSH_MSG_CHANNEL_OPEN message with\r
114  * a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response, with a sender and a\r
115  * recepient channel.  at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel\r
116  * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snipet:\r
117  *     The 'recipient channel' is the channel number given in the original\r
118  *     open request, and 'sender channel' is the channel number allocated by\r
119  *     the other side.\r
120  *\r
121  * @see Net_SSH2::_send_channel_packet()\r
122  * @see Net_SSH2::_get_channel_packet()\r
123  * @access private\r
124  */\r
125 define('NET_SSH2_CHANNEL_EXEC', 0); // PuTTy uses 0x100\r
126 /**#@-*/\r
127 \r
128 /**#@+\r
129  * @access public\r
130  * @see Net_SSH2::getLog()\r
131  */\r
132 /**\r
133  * Returns the message numbers\r
134  */\r
135 define('NET_SSH2_LOG_SIMPLE',  1);\r
136 /**\r
137  * Returns the message content\r
138  */\r
139 define('NET_SSH2_LOG_COMPLEX', 2);\r
140 /**#@-*/\r
141 \r
142 /**\r
143  * Pure-PHP implementation of SSHv2.\r
144  *\r
145  * @author  Jim Wigginton <terrafrost@php.net>\r
146  * @version 0.1.0\r
147  * @access  public\r
148  * @package Net_SSH2\r
149  */\r
150 class Net_SSH2 {\r
151     /**\r
152      * The SSH identifier\r
153      *\r
154      * @var String\r
155      * @access private\r
156      */\r
157     var $identifier = 'SSH-2.0-phpseclib_0.2';\r
158 \r
159     /**\r
160      * The Socket Object\r
161      *\r
162      * @var Object\r
163      * @access private\r
164      */\r
165     var $fsock;\r
166 \r
167     /**\r
168      * Execution Bitmap\r
169      *\r
170      * The bits that are set reprsent functions that have been called already.  This is used to determine\r
171      * if a requisite function has been successfully executed.  If not, an error should be thrown.\r
172      *\r
173      * @var Integer\r
174      * @access private\r
175      */\r
176     var $bitmap = 0;\r
177 \r
178     /**\r
179      * Error information\r
180      *\r
181      * @see Net_SSH2::getErrors()\r
182      * @see Net_SSH2::getLastError()\r
183      * @var String\r
184      * @access private\r
185      */\r
186     var $errors = array();\r
187 \r
188     /**\r
189      * Server Identifier\r
190      *\r
191      * @see Net_SSH2::getServerIdentification()\r
192      * @var String\r
193      * @access private\r
194      */\r
195     var $server_identifier = '';\r
196 \r
197     /**\r
198      * Key Exchange Algorithms\r
199      *\r
200      * @see Net_SSH2::getKexAlgorithims()\r
201      * @var Array\r
202      * @access private\r
203      */\r
204     var $kex_algorithms;\r
205 \r
206     /**\r
207      * Server Host Key Algorithms\r
208      *\r
209      * @see Net_SSH2::getServerHostKeyAlgorithms()\r
210      * @var Array\r
211      * @access private\r
212      */\r
213     var $server_host_key_algorithms;\r
214 \r
215     /**\r
216      * Encryption Algorithms: Client to Server\r
217      *\r
218      * @see Net_SSH2::getEncryptionAlgorithmsClient2Server()\r
219      * @var Array\r
220      * @access private\r
221      */\r
222     var $encryption_algorithms_client_to_server;\r
223 \r
224     /**\r
225      * Encryption Algorithms: Server to Client\r
226      *\r
227      * @see Net_SSH2::getEncryptionAlgorithmsServer2Client()\r
228      * @var Array\r
229      * @access private\r
230      */\r
231     var $encryption_algorithms_server_to_client;\r
232 \r
233     /**\r
234      * MAC Algorithms: Client to Server\r
235      *\r
236      * @see Net_SSH2::getMACAlgorithmsClient2Server()\r
237      * @var Array\r
238      * @access private\r
239      */\r
240     var $mac_algorithms_client_to_server;\r
241 \r
242     /**\r
243      * MAC Algorithms: Server to Client\r
244      *\r
245      * @see Net_SSH2::getMACAlgorithmsServer2Client()\r
246      * @var Array\r
247      * @access private\r
248      */\r
249     var $mac_algorithms_server_to_client;\r
250 \r
251     /**\r
252      * Compression Algorithms: Client to Server\r
253      *\r
254      * @see Net_SSH2::getCompressionAlgorithmsClient2Server()\r
255      * @var Array\r
256      * @access private\r
257      */\r
258     var $compression_algorithms_client_to_server;\r
259 \r
260     /**\r
261      * Compression Algorithms: Server to Client\r
262      *\r
263      * @see Net_SSH2::getCompressionAlgorithmsServer2Client()\r
264      * @var Array\r
265      * @access private\r
266      */\r
267     var $compression_algorithms_server_to_client;\r
268 \r
269     /**\r
270      * Languages: Server to Client\r
271      *\r
272      * @see Net_SSH2::getLanguagesServer2Client()\r
273      * @var Array\r
274      * @access private\r
275      */\r
276     var $languages_server_to_client;\r
277 \r
278     /**\r
279      * Languages: Client to Server\r
280      *\r
281      * @see Net_SSH2::getLanguagesClient2Server()\r
282      * @var Array\r
283      * @access private\r
284      */\r
285     var $languages_client_to_server;\r
286 \r
287     /**\r
288      * Block Size for Server to Client Encryption\r
289      *\r
290      * "Note that the length of the concatenation of 'packet_length',\r
291      *  'padding_length', 'payload', and 'random padding' MUST be a multiple\r
292      *  of the cipher block size or 8, whichever is larger.  This constraint\r
293      *  MUST be enforced, even when using stream ciphers."\r
294      *\r
295      *  -- http://tools.ietf.org/html/rfc4253#section-6\r
296      *\r
297      * @see Net_SSH2::Net_SSH2()\r
298      * @see Net_SSH2::_send_binary_packet()\r
299      * @var Integer\r
300      * @access private\r
301      */\r
302     var $encrypt_block_size = 8;\r
303 \r
304     /**\r
305      * Block Size for Client to Server Encryption\r
306      *\r
307      * @see Net_SSH2::Net_SSH2()\r
308      * @see Net_SSH2::_get_binary_packet()\r
309      * @var Integer\r
310      * @access private\r
311      */\r
312     var $decrypt_block_size = 8;\r
313 \r
314     /**\r
315      * Server to Client Encryption Object\r
316      *\r
317      * @see Net_SSH2::_get_binary_packet()\r
318      * @var Object\r
319      * @access private\r
320      */\r
321     var $decrypt = false;\r
322 \r
323     /**\r
324      * Client to Server Encryption Object\r
325      *\r
326      * @see Net_SSH2::_send_binary_packet()\r
327      * @var Object\r
328      * @access private\r
329      */\r
330     var $encrypt = false;\r
331 \r
332     /**\r
333      * Client to Server HMAC Object\r
334      *\r
335      * @see Net_SSH2::_send_binary_packet()\r
336      * @var Object\r
337      * @access private\r
338      */\r
339     var $hmac_create = false;\r
340 \r
341     /**\r
342      * Server to Client HMAC Object\r
343      *\r
344      * @see Net_SSH2::_get_binary_packet()\r
345      * @var Object\r
346      * @access private\r
347      */\r
348     var $hmac_check = false;\r
349 \r
350     /**\r
351      * Size of server to client HMAC\r
352      *\r
353      * 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.\r
354      * 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\r
355      * append it.\r
356      *\r
357      * @see Net_SSH2::_get_binary_packet()\r
358      * @var Integer\r
359      * @access private\r
360      */\r
361     var $hmac_size = false;\r
362 \r
363     /**\r
364      * Server Public Host Key\r
365      *\r
366      * @see Net_SSH2::getServerPublicHostKey()\r
367      * @var String\r
368      * @access private\r
369      */\r
370     var $server_public_host_key;\r
371 \r
372     /**\r
373      * Session identifer\r
374      *\r
375      * "The exchange hash H from the first key exchange is additionally\r
376      *  used as the session identifier, which is a unique identifier for\r
377      *  this connection."\r
378      *\r
379      *  -- http://tools.ietf.org/html/rfc4253#section-7.2\r
380      *\r
381      * @see Net_SSH2::_key_exchange()\r
382      * @var String\r
383      * @access private\r
384      */\r
385     var $session_id = false;\r
386 \r
387     /**\r
388      * Message Numbers\r
389      *\r
390      * @see Net_SSH2::Net_SSH2()\r
391      * @var Array\r
392      * @access private\r
393      */\r
394     var $message_numbers = array();\r
395 \r
396     /**\r
397      * Disconnection Message 'reason codes' defined in RFC4253\r
398      *\r
399      * @see Net_SSH2::Net_SSH2()\r
400      * @var Array\r
401      * @access private\r
402      */\r
403     var $disconnect_reasons = array();\r
404 \r
405     /**\r
406      * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254\r
407      *\r
408      * @see Net_SSH2::Net_SSH2()\r
409      * @var Array\r
410      * @access private\r
411      */\r
412     var $channel_open_failure_reasons = array();\r
413 \r
414     /**\r
415      * Terminal Modes\r
416      *\r
417      * @link http://tools.ietf.org/html/rfc4254#section-8\r
418      * @see Net_SSH2::Net_SSH2()\r
419      * @var Array\r
420      * @access private\r
421      */\r
422     var $terminal_modes = array();\r
423 \r
424     /**\r
425      * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes\r
426      *\r
427      * @link http://tools.ietf.org/html/rfc4254#section-5.2\r
428      * @see Net_SSH2::Net_SSH2()\r
429      * @var Array\r
430      * @access private\r
431      */\r
432     var $channel_extended_data_type_codes = array();\r
433 \r
434     /**\r
435      * Send Sequence Number\r
436      *\r
437      * See 'Section 6.4.  Data Integrity' of rfc4253 for more info.\r
438      *\r
439      * @see Net_SSH2::_send_binary_packet()\r
440      * @var Integer\r
441      * @access private\r
442      */\r
443     var $send_seq_no = 0;\r
444 \r
445     /**\r
446      * Get Sequence Number\r
447      *\r
448      * See 'Section 6.4.  Data Integrity' of rfc4253 for more info.\r
449      *\r
450      * @see Net_SSH2::_get_binary_packet()\r
451      * @var Integer\r
452      * @access private\r
453      */\r
454     var $get_seq_no = 0;\r
455 \r
456     /**\r
457      * Server Channels\r
458      *\r
459      * Maps client channels to server channels\r
460      *\r
461      * @see Net_SSH2::_get_channel_packet()\r
462      * @see Net_SSH2::exec()\r
463      * @var Array\r
464      * @access private\r
465      */\r
466     var $server_channels = array();\r
467 \r
468     /**\r
469      * Channel Buffers\r
470      *\r
471      * If a client requests a packet from one channel but receives two packets from another those packets should\r
472      * be placed in a buffer\r
473      *\r
474      * @see Net_SSH2::_get_channel_packet()\r
475      * @see Net_SSH2::exec()\r
476      * @var Array\r
477      * @access private\r
478      */\r
479     var $channel_buffers = array();\r
480 \r
481     /**\r
482      * Channel Status\r
483      *\r
484      * Contains the type of the last sent message\r
485      *\r
486      * @see Net_SSH2::_get_channel_packet()\r
487      * @var Array\r
488      * @access private\r
489      */\r
490     var $channel_status = array();\r
491 \r
492     /**\r
493      * Packet Size\r
494      *\r
495      * Maximum packet size indexed by channel\r
496      *\r
497      * @see Net_SSH2::_send_channel_packet()\r
498      * @var Array\r
499      * @access private\r
500      */\r
501     var $packet_size_client_to_server = array();\r
502 \r
503     /**\r
504      * Message Number Log\r
505      *\r
506      * @see Net_SSH2::getLog()\r
507      * @var Array\r
508      * @access private\r
509      */\r
510     var $message_number_log = array();\r
511 \r
512     /**\r
513      * Message Log\r
514      *\r
515      * @see Net_SSH2::getLog()\r
516      * @var Array\r
517      * @access private\r
518      */\r
519     var $message_log = array();\r
520 \r
521     /**\r
522      * The Window Size\r
523      *\r
524      * Bytes the other party can send before it must wait for the window to be adjusted (0x7FFFFFFF = 4GB)\r
525      *\r
526      * @var Integer\r
527      * @see Net_SSH2::_send_channel_packet()\r
528      * @see Net_SSH2::exec()\r
529      * @access private\r
530      */\r
531     var $window_size = 0x7FFFFFFF;\r
532 \r
533     /**\r
534      * Window size\r
535      *\r
536      * Window size indexed by channel\r
537      *\r
538      * @see Net_SSH2::_send_channel_packet()\r
539      * @var Array\r
540      * @access private\r
541      */\r
542     var $window_size_client_to_server = array();\r
543 \r
544     /**\r
545      * Server signature\r
546      *\r
547      * Verified against $this->session_id\r
548      *\r
549      * @see Net_SSH2::getServerPublicHostKey()\r
550      * @var String\r
551      * @access private\r
552      */\r
553     var $signature = '';\r
554 \r
555     /**\r
556      * Server signature format\r
557      *\r
558      * ssh-rsa or ssh-dss.\r
559      *\r
560      * @see Net_SSH2::getServerPublicHostKey()\r
561      * @var String\r
562      * @access private\r
563      */\r
564     var $signature_format = '';\r
565 \r
566     /**\r
567      * Default Constructor.\r
568      *\r
569      * Connects to an SSHv2 server\r
570      *\r
571      * @param String $host\r
572      * @param optional Integer $port\r
573      * @param optional Integer $timeout\r
574      * @return Net_SSH2\r
575      * @access public\r
576      */\r
577     function Net_SSH2($host, $port = 22, $timeout = 10)\r
578     {\r
579         $this->message_numbers = array(\r
580             1 => 'NET_SSH2_MSG_DISCONNECT',\r
581             2 => 'NET_SSH2_MSG_IGNORE',\r
582             3 => 'NET_SSH2_MSG_UNIMPLEMENTED',\r
583             4 => 'NET_SSH2_MSG_DEBUG',\r
584             5 => 'NET_SSH2_MSG_SERVICE_REQUEST',\r
585             6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',\r
586             20 => 'NET_SSH2_MSG_KEXINIT',\r
587             21 => 'NET_SSH2_MSG_NEWKEYS',\r
588             30 => 'NET_SSH2_MSG_KEXDH_INIT',\r
589             31 => 'NET_SSH2_MSG_KEXDH_REPLY',\r
590             50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',\r
591             51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',\r
592             52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',\r
593             53 => 'NET_SSH2_MSG_USERAUTH_BANNER',\r
594 \r
595             80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',\r
596             81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',\r
597             82 => 'NET_SSH2_MSG_REQUEST_FAILURE',\r
598             90 => 'NET_SSH2_MSG_CHANNEL_OPEN',\r
599             91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',\r
600             92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',\r
601             93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',\r
602             94 => 'NET_SSH2_MSG_CHANNEL_DATA',\r
603             95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',\r
604             96 => 'NET_SSH2_MSG_CHANNEL_EOF',\r
605             97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',\r
606             98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',\r
607             99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',\r
608             100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'\r
609         );\r
610         $this->disconnect_reasons = array(\r
611             1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',\r
612             2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',\r
613             3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',\r
614             4 => 'NET_SSH2_DISCONNECT_RESERVED',\r
615             5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',\r
616             6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',\r
617             7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',\r
618             8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',\r
619             9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',\r
620             10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',\r
621             11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',\r
622             12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',\r
623             13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',\r
624             14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',\r
625             15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'\r
626         );\r
627         $this->channel_open_failure_reasons = array(\r
628             1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'\r
629         );\r
630         $this->terminal_modes = array(\r
631             0 => 'NET_SSH2_TTY_OP_END'\r
632         );\r
633         $this->channel_extended_data_type_codes = array(\r
634             1 => 'NET_SSH2_EXTENDED_DATA_STDERR'\r
635         );\r
636 \r
637         $this->_define_array(\r
638             $this->message_numbers,\r
639             $this->disconnect_reasons,\r
640             $this->channel_open_failure_reasons,\r
641             $this->terminal_modes,\r
642             $this->channel_extended_data_type_codes,\r
643             array(60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'),\r
644             array(60 => 'NET_SSH2_MSG_USERAUTH_PK_OK')\r
645         );\r
646 \r
647         $this->fsock = @fsockopen($host, $port, $errno, $errstr, $timeout);\r
648         if (!$this->fsock) {\r
649             user_error(rtrim("Cannot connect to $host. Error $errno. $errstr"), E_USER_NOTICE);\r
650             return;\r
651         }\r
652 \r
653         /* According to the SSH2 specs,\r
654 \r
655           "The server MAY send other lines of data before sending the version\r
656            string.  Each line SHOULD be terminated by a Carriage Return and Line\r
657            Feed.  Such lines MUST NOT begin with "SSH-", and SHOULD be encoded\r
658            in ISO-10646 UTF-8 [RFC3629] (language is not specified).  Clients\r
659            MUST be able to process such lines." */\r
660         $temp = '';\r
661         $extra = '';\r
662         while (!feof($this->fsock) && !preg_match('#^SSH-(\d\.\d+)#', $temp, $matches)) {\r
663             if (substr($temp, -2) == "\r\n") {\r
664                 $extra.= $temp;\r
665                 $temp = '';\r
666             }\r
667             $temp.= fgets($this->fsock, 255);\r
668         }\r
669 \r
670         $ext = array();\r
671         if (extension_loaded('mcrypt')) {\r
672             $ext[] = 'mcrypt';\r
673         }\r
674         if (extension_loaded('gmp')) {\r
675             $ext[] = 'gmp';\r
676         } else if (extension_loaded('bcmath')) {\r
677             $ext[] = 'bcmath';\r
678         }\r
679 \r
680         if (!empty($ext)) {\r
681             $this->identifier.= ' (' . implode(', ', $ext) . ')';\r
682         }\r
683 \r
684         if (defined('NET_SSH2_LOGGING')) {\r
685             $this->message_number_log[] = '<-';\r
686             $this->message_number_log[] = '->';\r
687 \r
688             if (NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) {\r
689                 $this->message_log[] = $temp;\r
690                 $this->message_log[] = $this->identifier . "\r\n";\r
691             }\r
692         }\r
693 \r
694         $this->server_identifier = trim($temp, "\r\n");\r
695         if (!empty($extra)) {\r
696             $this->errors[] = utf8_decode($extra);\r
697         }\r
698 \r
699         if ($matches[1] != '1.99' && $matches[1] != '2.0') {\r
700             user_error("Cannot connect to SSH $matches[1] servers", E_USER_NOTICE);\r
701             return;\r
702         }\r
703 \r
704         fputs($this->fsock, $this->identifier . "\r\n");\r
705 \r
706         $response = $this->_get_binary_packet();\r
707         if ($response === false) {\r
708             user_error('Connection closed by server', E_USER_NOTICE);\r
709             return;\r
710         }\r
711 \r
712         if (ord($response[0]) != NET_SSH2_MSG_KEXINIT) {\r
713             user_error('Expected SSH_MSG_KEXINIT', E_USER_NOTICE);\r
714             return;\r
715         }\r
716 \r
717         if (!$this->_key_exchange($response)) {\r
718             return;\r
719         }\r
720 \r
721         $this->bitmap = NET_SSH2_MASK_CONSTRUCTOR;\r
722     }\r
723 \r
724     /**\r
725      * Key Exchange\r
726      *\r
727      * @param String $kexinit_payload_server\r
728      * @access private\r
729      */\r
730     function _key_exchange($kexinit_payload_server)\r
731     {\r
732         static $kex_algorithms = array(\r
733             'diffie-hellman-group1-sha1', // REQUIRED\r
734             'diffie-hellman-group14-sha1' // REQUIRED\r
735         );\r
736 \r
737         static $server_host_key_algorithms = array(\r
738             'ssh-rsa', // RECOMMENDED  sign   Raw RSA Key\r
739             'ssh-dss'  // REQUIRED     sign   Raw DSS Key\r
740         );\r
741 \r
742         static $encryption_algorithms = array(\r
743             // from <http://tools.ietf.org/html/rfc4345#section-4>:\r
744             'arcfour256',\r
745             'arcfour128',\r
746 \r
747             'arcfour',    // OPTIONAL          the ARCFOUR stream cipher with a 128-bit key\r
748 \r
749             'aes128-cbc', // RECOMMENDED       AES with a 128-bit key\r
750             'aes192-cbc', // OPTIONAL          AES with a 192-bit key\r
751             'aes256-cbc', // OPTIONAL          AES in CBC mode, with a 256-bit key\r
752 \r
753             // from <http://tools.ietf.org/html/rfc4344#section-4>:\r
754             'aes128-ctr', // RECOMMENDED       AES (Rijndael) in SDCTR mode, with 128-bit key\r
755             'aes192-ctr', // RECOMMENDED       AES with 192-bit key\r
756             'aes256-ctr', // RECOMMENDED       AES with 256-bit key\r
757             '3des-ctr',   // RECOMMENDED       Three-key 3DES in SDCTR mode\r
758 \r
759             '3des-cbc',   // REQUIRED          three-key 3DES in CBC mode\r
760             'none'        // OPTIONAL          no encryption; NOT RECOMMENDED\r
761         );\r
762 \r
763         static $mac_algorithms = array(\r
764             'hmac-sha1-96', // RECOMMENDED     first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)\r
765             'hmac-sha1',    // REQUIRED        HMAC-SHA1 (digest length = key length = 20)\r
766             'hmac-md5-96',  // OPTIONAL        first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)\r
767             'hmac-md5',     // OPTIONAL        HMAC-MD5 (digest length = key length = 16)\r
768             'none'          // OPTIONAL        no MAC; NOT RECOMMENDED\r
769         );\r
770 \r
771         static $compression_algorithms = array(\r
772             'none'   // REQUIRED        no compression\r
773             //'zlib' // OPTIONAL        ZLIB (LZ77) compression\r
774         );\r
775 \r
776         static $str_kex_algorithms, $str_server_host_key_algorithms,\r
777                $encryption_algorithms_server_to_client, $mac_algorithms_server_to_client, $compression_algorithms_server_to_client,\r
778                $encryption_algorithms_client_to_server, $mac_algorithms_client_to_server, $compression_algorithms_client_to_server;\r
779 \r
780         if (empty($str_kex_algorithms)) {\r
781             $str_kex_algorithms = implode(',', $kex_algorithms);\r
782             $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms);\r
783             $encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(',', $encryption_algorithms);\r
784             $mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(',', $mac_algorithms);\r
785             $compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms);\r
786         }\r
787 \r
788         $client_cookie = '';\r
789         for ($i = 0; $i < 16; $i++) {\r
790             $client_cookie.= chr(crypt_random(0, 255));\r
791         }\r
792 \r
793         $response = $kexinit_payload_server;\r
794         $this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)\r
795         $server_cookie = $this->_string_shift($response, 16);\r
796 \r
797         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
798         $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length']));\r
799 \r
800         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
801         $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length']));\r
802 \r
803         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
804         $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));\r
805 \r
806         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
807         $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));\r
808 \r
809         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
810         $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));\r
811 \r
812         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
813         $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));\r
814 \r
815         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
816         $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));\r
817 \r
818         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
819         $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));\r
820 \r
821         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
822         $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));\r
823 \r
824         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
825         $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));\r
826 \r
827         extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1)));\r
828         $first_kex_packet_follows = $first_kex_packet_follows != 0;\r
829 \r
830         // the sending of SSH2_MSG_KEXINIT could go in one of two places.  this is the second place.\r
831         $kexinit_payload_client = pack('Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN',\r
832             NET_SSH2_MSG_KEXINIT, $client_cookie, strlen($str_kex_algorithms), $str_kex_algorithms,\r
833             strlen($str_server_host_key_algorithms), $str_server_host_key_algorithms, strlen($encryption_algorithms_client_to_server),\r
834             $encryption_algorithms_client_to_server, strlen($encryption_algorithms_server_to_client), $encryption_algorithms_server_to_client,\r
835             strlen($mac_algorithms_client_to_server), $mac_algorithms_client_to_server, strlen($mac_algorithms_server_to_client),\r
836             $mac_algorithms_server_to_client, strlen($compression_algorithms_client_to_server), $compression_algorithms_client_to_server,\r
837             strlen($compression_algorithms_server_to_client), $compression_algorithms_server_to_client, 0, '', 0, '',\r
838             0, 0\r
839         );\r
840 \r
841         if (!$this->_send_binary_packet($kexinit_payload_client)) {\r
842             return false;\r
843         }\r
844         // here ends the second place.\r
845 \r
846         // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange\r
847         for ($i = 0; $i < count($encryption_algorithms) && !in_array($encryption_algorithms[$i], $this->encryption_algorithms_server_to_client); $i++);\r
848         if ($i == count($encryption_algorithms)) {\r
849             user_error('No compatible server to client encryption algorithms found', E_USER_NOTICE);\r
850             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
851         }\r
852 \r
853         // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the\r
854         // diffie-hellman key exchange as fast as possible\r
855         $decrypt = $encryption_algorithms[$i];\r
856         switch ($decrypt) {\r
857             case '3des-cbc':\r
858             case '3des-ctr':\r
859                 $decryptKeyLength = 24; // eg. 192 / 8\r
860                 break;\r
861             case 'aes256-cbc':\r
862             case 'aes256-ctr':\r
863                 $decryptKeyLength = 32; // eg. 256 / 8\r
864                 break;\r
865             case 'aes192-cbc':\r
866             case 'aes192-ctr':\r
867                 $decryptKeyLength = 24; // eg. 192 / 8\r
868                 break;\r
869             case 'aes128-cbc':\r
870             case 'aes128-ctr':\r
871                 $decryptKeyLength = 16; // eg. 128 / 8\r
872                 break;\r
873             case 'arcfour':\r
874             case 'arcfour128':\r
875                 $decryptKeyLength = 16; // eg. 128 / 8\r
876                 break;\r
877             case 'arcfour256':\r
878                 $decryptKeyLength = 32; // eg. 128 / 8\r
879                 break;\r
880             case 'none';\r
881                 $decryptKeyLength = 0;\r
882         }\r
883 \r
884         for ($i = 0; $i < count($encryption_algorithms) && !in_array($encryption_algorithms[$i], $this->encryption_algorithms_client_to_server); $i++);\r
885         if ($i == count($encryption_algorithms)) {\r
886             user_error('No compatible client to server encryption algorithms found', E_USER_NOTICE);\r
887             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
888         }\r
889 \r
890         $encrypt = $encryption_algorithms[$i];\r
891         switch ($encrypt) {\r
892             case '3des-cbc':\r
893             case '3des-ctr':\r
894                 $encryptKeyLength = 24;\r
895                 break;\r
896             case 'aes256-cbc':\r
897             case 'aes256-ctr':\r
898                 $encryptKeyLength = 32;\r
899                 break;\r
900             case 'aes192-cbc':\r
901             case 'aes192-ctr':\r
902                 $encryptKeyLength = 24;\r
903                 break;\r
904             case 'aes128-cbc':\r
905             case 'aes128-ctr':\r
906                 $encryptKeyLength = 16;\r
907                 break;\r
908             case 'arcfour':\r
909             case 'arcfour128':\r
910                 $encryptKeyLength = 16;\r
911                 break;\r
912             case 'arcfour256':\r
913                 $encryptKeyLength = 32;\r
914                 break;\r
915             case 'none';\r
916                 $encryptKeyLength = 0;\r
917         }\r
918 \r
919         $keyLength = $decryptKeyLength > $encryptKeyLength ? $decryptKeyLength : $encryptKeyLength;\r
920 \r
921         // through diffie-hellman key exchange a symmetric key is obtained\r
922         for ($i = 0; $i < count($kex_algorithms) && !in_array($kex_algorithms[$i], $this->kex_algorithms); $i++);\r
923         if ($i == count($kex_algorithms)) {\r
924             user_error('No compatible key exchange algorithms found', E_USER_NOTICE);\r
925             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
926         }\r
927 \r
928         switch ($kex_algorithms[$i]) {\r
929             // see http://tools.ietf.org/html/rfc2409#section-6.2 and \r
930             // http://tools.ietf.org/html/rfc2412, appendex E\r
931             case 'diffie-hellman-group1-sha1':\r
932                 $p = pack('H256', 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . \r
933                                   '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . \r
934                                   '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . \r
935                                   'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF');\r
936                 $keyLength = $keyLength < 160 ? $keyLength : 160;\r
937                 $hash = 'sha1';\r
938                 break;\r
939             // see http://tools.ietf.org/html/rfc3526#section-3\r
940             case 'diffie-hellman-group14-sha1':\r
941                 $p = pack('H512', 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . \r
942                                   '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . \r
943                                   '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . \r
944                                   'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . \r
945                                   '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . \r
946                                   '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . \r
947                                   'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . \r
948                                   '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF');\r
949                 $keyLength = $keyLength < 160 ? $keyLength : 160;\r
950                 $hash = 'sha1';\r
951         }\r
952 \r
953         $p = new Math_BigInteger($p, 256);\r
954         //$q = $p->bitwise_rightShift(1);\r
955 \r
956         /* To increase the speed of the key exchange, both client and server may\r
957            reduce the size of their private exponents.  It should be at least\r
958            twice as long as the key material that is generated from the shared\r
959            secret.  For more details, see the paper by van Oorschot and Wiener\r
960            [VAN-OORSCHOT].\r
961 \r
962            -- http://tools.ietf.org/html/rfc4419#section-6.2 */\r
963         $q = new Math_BigInteger(1);\r
964         $q = $q->bitwise_leftShift(2 * $keyLength);\r
965         $q = $q->subtract(new Math_BigInteger(1));\r
966 \r
967         $g = new Math_BigInteger(2);\r
968         $x = new Math_BigInteger();\r
969         $x->setRandomGenerator('crypt_random');\r
970         $x = $x->random(new Math_BigInteger(1), $q);\r
971         $e = $g->modPow($x, $p);\r
972 \r
973         $eBytes = $e->toBytes(true);\r
974         $data = pack('CNa*', NET_SSH2_MSG_KEXDH_INIT, strlen($eBytes), $eBytes);\r
975 \r
976         if (!$this->_send_binary_packet($data)) {\r
977             user_error('Connection closed by server', E_USER_NOTICE);\r
978             return false;\r
979         }\r
980 \r
981         $response = $this->_get_binary_packet();\r
982         if ($response === false) {\r
983             user_error('Connection closed by server', E_USER_NOTICE);\r
984             return false;\r
985         }\r
986         extract(unpack('Ctype', $this->_string_shift($response, 1)));\r
987 \r
988         if ($type != NET_SSH2_MSG_KEXDH_REPLY) {\r
989             user_error('Expected SSH_MSG_KEXDH_REPLY', E_USER_NOTICE);\r
990             return false;\r
991         }\r
992 \r
993         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
994         $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']);\r
995 \r
996         $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));\r
997         $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']);\r
998 \r
999         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
1000         $fBytes = $this->_string_shift($response, $temp['length']);\r
1001         $f = new Math_BigInteger($fBytes, -256);\r
1002 \r
1003         $temp = unpack('Nlength', $this->_string_shift($response, 4));\r
1004         $this->signature = $this->_string_shift($response, $temp['length']);\r
1005 \r
1006         $temp = unpack('Nlength', $this->_string_shift($this->signature, 4));\r
1007         $this->signature_format = $this->_string_shift($this->signature, $temp['length']);\r
1008 \r
1009         $key = $f->modPow($x, $p);\r
1010         $keyBytes = $key->toBytes(true);\r
1011 \r
1012         if ($this->session_id === false) {\r
1013             $source = pack('Na*Na*Na*Na*Na*Na*Na*Na*',\r
1014                 strlen($this->identifier), $this->identifier, strlen($this->server_identifier), $this->server_identifier,\r
1015                 strlen($kexinit_payload_client), $kexinit_payload_client, strlen($kexinit_payload_server),\r
1016                 $kexinit_payload_server, strlen($this->server_public_host_key), $this->server_public_host_key, strlen($eBytes),\r
1017                 $eBytes, strlen($fBytes), $fBytes, strlen($keyBytes), $keyBytes\r
1018             );\r
1019 \r
1020             $source = pack('H*', $hash($source));\r
1021 \r
1022             $this->session_id = $source;\r
1023         }\r
1024 \r
1025         for ($i = 0; $i < count($server_host_key_algorithms) && !in_array($server_host_key_algorithms[$i], $this->server_host_key_algorithms); $i++);\r
1026         if ($i == count($server_host_key_algorithms)) {\r
1027             user_error('No compatible server host key algorithms found', E_USER_NOTICE);\r
1028             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
1029         }\r
1030 \r
1031         if ($public_key_format != $server_host_key_algorithms[$i] || $this->signature_format != $server_host_key_algorithms[$i]) {\r
1032             user_error('Sever Host Key Algorithm Mismatch', E_USER_NOTICE);\r
1033             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
1034         }\r
1035 \r
1036         $packet = pack('C',\r
1037             NET_SSH2_MSG_NEWKEYS\r
1038         );\r
1039 \r
1040         if (!$this->_send_binary_packet($packet)) {\r
1041             return false;\r
1042         }\r
1043 \r
1044         $response = $this->_get_binary_packet();\r
1045 \r
1046         if ($response === false) {\r
1047             user_error('Connection closed by server', E_USER_NOTICE);\r
1048             return false;\r
1049         }\r
1050 \r
1051         extract(unpack('Ctype', $this->_string_shift($response, 1)));\r
1052 \r
1053         if ($type != NET_SSH2_MSG_NEWKEYS) {\r
1054             user_error('Expected SSH_MSG_NEWKEYS', E_USER_NOTICE);\r
1055             return false;\r
1056         }\r
1057 \r
1058         switch ($encrypt) {\r
1059             case '3des-cbc':\r
1060                 $this->encrypt = new Crypt_TripleDES();\r
1061                 // $this->encrypt_block_size = 64 / 8 == the default\r
1062                 break;\r
1063             case '3des-ctr':\r
1064                 $this->encrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR);\r
1065                 // $this->encrypt_block_size = 64 / 8 == the default\r
1066                 break;\r
1067             case 'aes256-cbc':\r
1068             case 'aes192-cbc':\r
1069             case 'aes128-cbc':\r
1070                 $this->encrypt = new Crypt_AES();\r
1071                 $this->encrypt_block_size = 16; // eg. 128 / 8\r
1072                 break;\r
1073             case 'aes256-ctr':\r
1074             case 'aes192-ctr':\r
1075             case 'aes128-ctr':\r
1076                 $this->encrypt = new Crypt_AES(CRYPT_AES_MODE_CTR);\r
1077                 $this->encrypt_block_size = 16; // eg. 128 / 8\r
1078                 break;\r
1079             case 'arcfour':\r
1080             case 'arcfour128':\r
1081             case 'arcfour256':\r
1082                 $this->encrypt = new Crypt_RC4();\r
1083                 break;\r
1084             case 'none';\r
1085                 //$this->encrypt = new Crypt_Null();\r
1086         }\r
1087 \r
1088         switch ($decrypt) {\r
1089             case '3des-cbc':\r
1090                 $this->decrypt = new Crypt_TripleDES();\r
1091                 break;\r
1092             case '3des-ctr':\r
1093                 $this->decrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR);\r
1094                 break;\r
1095             case 'aes256-cbc':\r
1096             case 'aes192-cbc':\r
1097             case 'aes128-cbc':\r
1098                 $this->decrypt = new Crypt_AES();\r
1099                 $this->decrypt_block_size = 16;\r
1100                 break;\r
1101             case 'aes256-ctr':\r
1102             case 'aes192-ctr':\r
1103             case 'aes128-ctr':\r
1104                 $this->decrypt = new Crypt_AES(CRYPT_AES_MODE_CTR);\r
1105                 $this->decrypt_block_size = 16;\r
1106                 break;\r
1107             case 'arcfour':\r
1108             case 'arcfour128':\r
1109             case 'arcfour256':\r
1110                 $this->decrypt = new Crypt_RC4();\r
1111                 break;\r
1112             case 'none';\r
1113                 //$this->decrypt = new Crypt_Null();\r
1114         }\r
1115 \r
1116         $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);\r
1117 \r
1118         if ($this->encrypt) {\r
1119             $this->encrypt->enableContinuousBuffer();\r
1120             $this->encrypt->disablePadding();\r
1121 \r
1122             $iv = pack('H*', $hash($keyBytes . $this->session_id . 'A' . $this->session_id));\r
1123             while ($this->encrypt_block_size > strlen($iv)) {\r
1124                 $iv.= pack('H*', $hash($keyBytes . $this->session_id . $iv));\r
1125             }\r
1126             $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));\r
1127 \r
1128             $key = pack('H*', $hash($keyBytes . $this->session_id . 'C' . $this->session_id));\r
1129             while ($encryptKeyLength > strlen($key)) {\r
1130                 $key.= pack('H*', $hash($keyBytes . $this->session_id . $key));\r
1131             }\r
1132             $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));\r
1133         }\r
1134 \r
1135         if ($this->decrypt) {\r
1136             $this->decrypt->enableContinuousBuffer();\r
1137             $this->decrypt->disablePadding();\r
1138 \r
1139             $iv = pack('H*', $hash($keyBytes . $this->session_id . 'B' . $this->session_id));\r
1140             while ($this->decrypt_block_size > strlen($iv)) {\r
1141                 $iv.= pack('H*', $hash($keyBytes . $this->session_id . $iv));\r
1142             }\r
1143             $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));\r
1144 \r
1145             $key = pack('H*', $hash($keyBytes . $this->session_id . 'D' . $this->session_id));\r
1146             while ($decryptKeyLength > strlen($key)) {\r
1147                 $key.= pack('H*', $hash($keyBytes . $this->session_id . $key));\r
1148             }\r
1149             $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));\r
1150         }\r
1151 \r
1152         /* The "arcfour128" algorithm is the RC4 cipher, as described in\r
1153            [SCHNEIER], using a 128-bit key.  The first 1536 bytes of keystream\r
1154            generated by the cipher MUST be discarded, and the first byte of the\r
1155            first encrypted packet MUST be encrypted using the 1537th byte of\r
1156            keystream.\r
1157 \r
1158            -- http://tools.ietf.org/html/rfc4345#section-4 */\r
1159         if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') {\r
1160             $this->encrypt->encrypt(str_repeat("\0", 1536));\r
1161         }\r
1162         if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') {\r
1163             $this->decrypt->decrypt(str_repeat("\0", 1536));\r
1164         }\r
1165 \r
1166         for ($i = 0; $i < count($mac_algorithms) && !in_array($mac_algorithms[$i], $this->mac_algorithms_client_to_server); $i++);\r
1167         if ($i == count($mac_algorithms)) {\r
1168             user_error('No compatible client to server message authentication algorithms found', E_USER_NOTICE);\r
1169             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
1170         }\r
1171 \r
1172         $createKeyLength = 0; // ie. $mac_algorithms[$i] == 'none'\r
1173         switch ($mac_algorithms[$i]) {\r
1174             case 'hmac-sha1':\r
1175                 $this->hmac_create = new Crypt_Hash('sha1');\r
1176                 $createKeyLength = 20;\r
1177                 break;\r
1178             case 'hmac-sha1-96':\r
1179                 $this->hmac_create = new Crypt_Hash('sha1-96');\r
1180                 $createKeyLength = 20;\r
1181                 break;\r
1182             case 'hmac-md5':\r
1183                 $this->hmac_create = new Crypt_Hash('md5');\r
1184                 $createKeyLength = 16;\r
1185                 break;\r
1186             case 'hmac-md5-96':\r
1187                 $this->hmac_create = new Crypt_Hash('md5-96');\r
1188                 $createKeyLength = 16;\r
1189         }\r
1190 \r
1191         for ($i = 0; $i < count($mac_algorithms) && !in_array($mac_algorithms[$i], $this->mac_algorithms_server_to_client); $i++);\r
1192         if ($i == count($mac_algorithms)) {\r
1193             user_error('No compatible server to client message authentication algorithms found', E_USER_NOTICE);\r
1194             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
1195         }\r
1196 \r
1197         $checkKeyLength = 0;\r
1198         $this->hmac_size = 0;\r
1199         switch ($mac_algorithms[$i]) {\r
1200             case 'hmac-sha1':\r
1201                 $this->hmac_check = new Crypt_Hash('sha1');\r
1202                 $checkKeyLength = 20;\r
1203                 $this->hmac_size = 20;\r
1204                 break;\r
1205             case 'hmac-sha1-96':\r
1206                 $this->hmac_check = new Crypt_Hash('sha1-96');\r
1207                 $checkKeyLength = 20;\r
1208                 $this->hmac_size = 12;\r
1209                 break;\r
1210             case 'hmac-md5':\r
1211                 $this->hmac_check = new Crypt_Hash('md5');\r
1212                 $checkKeyLength = 16;\r
1213                 $this->hmac_size = 16;\r
1214                 break;\r
1215             case 'hmac-md5-96':\r
1216                 $this->hmac_check = new Crypt_Hash('md5-96');\r
1217                 $checkKeyLength = 16;\r
1218                 $this->hmac_size = 12;\r
1219         }\r
1220 \r
1221         $key = pack('H*', $hash($keyBytes . $this->session_id . 'E' . $this->session_id));\r
1222         while ($createKeyLength > strlen($key)) {\r
1223             $key.= pack('H*', $hash($keyBytes . $this->session_id . $key));\r
1224         }\r
1225         $this->hmac_create->setKey(substr($key, 0, $createKeyLength));\r
1226 \r
1227         $key = pack('H*', $hash($keyBytes . $this->session_id . 'F' . $this->session_id));\r
1228         while ($checkKeyLength > strlen($key)) {\r
1229             $key.= pack('H*', $hash($keyBytes . $this->session_id . $key));\r
1230         }\r
1231         $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));\r
1232 \r
1233         for ($i = 0; $i < count($compression_algorithms) && !in_array($compression_algorithms[$i], $this->compression_algorithms_server_to_client); $i++);\r
1234         if ($i == count($compression_algorithms)) {\r
1235             user_error('No compatible server to client compression algorithms found', E_USER_NOTICE);\r
1236             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
1237         }\r
1238         $this->decompress = $compression_algorithms[$i] == 'zlib';\r
1239 \r
1240         for ($i = 0; $i < count($compression_algorithms) && !in_array($compression_algorithms[$i], $this->compression_algorithms_client_to_server); $i++);\r
1241         if ($i == count($compression_algorithms)) {\r
1242             user_error('No compatible client to server compression algorithms found', E_USER_NOTICE);\r
1243             return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
1244         }\r
1245         $this->compress = $compression_algorithms[$i] == 'zlib';\r
1246 \r
1247         return true;\r
1248     }\r
1249 \r
1250     /**\r
1251      * Login\r
1252      *\r
1253      * @param String $username\r
1254      * @param optional String $password\r
1255      * @return Boolean\r
1256      * @access public\r
1257      * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}\r
1258      *           by sending dummy SSH_MSG_IGNORE messages.\r
1259      */\r
1260     function login($username, $password = '')\r
1261     {\r
1262         if (!($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR)) {\r
1263             return false;\r
1264         }\r
1265 \r
1266         $packet = pack('CNa*',\r
1267             NET_SSH2_MSG_SERVICE_REQUEST, strlen('ssh-userauth'), 'ssh-userauth'\r
1268         );\r
1269 \r
1270         if (!$this->_send_binary_packet($packet)) {\r
1271             return false;\r
1272         }\r
1273 \r
1274         $response = $this->_get_binary_packet();\r
1275         if ($response === false) {\r
1276             user_error('Connection closed by server', E_USER_NOTICE);\r
1277             return false;\r
1278         }\r
1279 \r
1280         extract(unpack('Ctype', $this->_string_shift($response, 1)));\r
1281 \r
1282         if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) {\r
1283             user_error('Expected SSH_MSG_SERVICE_ACCEPT', E_USER_NOTICE);\r
1284             return false;\r
1285         }\r
1286 \r
1287         // although PHP5's get_class() preserves the case, PHP4's does not\r
1288         if (is_object($password) && strtolower(get_class($password)) == 'crypt_rsa')  {\r
1289             return $this->_privatekey_login($username, $password);\r
1290         }\r
1291 \r
1292         $utf8_password = utf8_encode($password);\r
1293         $packet = pack('CNa*Na*Na*CNa*',\r
1294             NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection',\r
1295             strlen('password'), 'password', 0, strlen($utf8_password), $utf8_password\r
1296         );\r
1297 \r
1298         if (!$this->_send_binary_packet($packet)) {\r
1299             return false;\r
1300         }\r
1301 \r
1302         // remove the username and password from the last logged packet\r
1303         if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) {\r
1304             $packet = pack('CNa*Na*Na*CNa*',\r
1305                 NET_SSH2_MSG_USERAUTH_REQUEST, strlen('username'), 'username', strlen('ssh-connection'), 'ssh-connection',\r
1306                 strlen('password'), 'password', 0, strlen('password'), 'password'\r
1307             );\r
1308             $this->message_log[count($this->message_log) - 1] = $packet;\r
1309         }\r
1310 \r
1311         $response = $this->_get_binary_packet();\r
1312         if ($response === false) {\r
1313             user_error('Connection closed by server', E_USER_NOTICE);\r
1314             return false;\r
1315         }\r
1316 \r
1317         extract(unpack('Ctype', $this->_string_shift($response, 1)));\r
1318 \r
1319         switch ($type) {\r
1320             case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed\r
1321                 if (defined('NET_SSH2_LOGGING')) {\r
1322                     $this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ';\r
1323                 }\r
1324                 extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1325                 $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . utf8_decode($this->_string_shift($response, $length));\r
1326                 return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);\r
1327             case NET_SSH2_MSG_USERAUTH_FAILURE:\r
1328                 // either the login is bad or the server employees multi-factor authentication\r
1329                 return false;\r
1330             case NET_SSH2_MSG_USERAUTH_SUCCESS:\r
1331                 $this->bitmap |= NET_SSH2_MASK_LOGIN;\r
1332                 return true;\r
1333         }\r
1334 \r
1335         return false;\r
1336     }\r
1337 \r
1338     /**\r
1339      * Login with an RSA private key\r
1340      *\r
1341      * @param String $username\r
1342      * @param Crypt_RSA $password\r
1343      * @return Boolean\r
1344      * @access private\r
1345      * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}\r
1346      *           by sending dummy SSH_MSG_IGNORE messages.\r
1347      */\r
1348     function _privatekey_login($username, $privatekey)\r
1349     {\r
1350         // see http://tools.ietf.org/html/rfc4253#page-15\r
1351         $publickey = $privatekey->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_RAW);\r
1352         if ($publickey === false) {\r
1353             return false;\r
1354         }\r
1355 \r
1356         $publickey = array(\r
1357             'e' => $publickey['e']->toBytes(true),\r
1358             'n' => $publickey['n']->toBytes(true)\r
1359         );\r
1360         $publickey = pack('Na*Na*Na*',\r
1361             strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey['e']), $publickey['e'], strlen($publickey['n']), $publickey['n']\r
1362         );\r
1363 \r
1364         $part1 = pack('CNa*Na*Na*',\r
1365             NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection',\r
1366             strlen('publickey'), 'publickey'\r
1367         );\r
1368         $part2 = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey), $publickey);\r
1369 \r
1370         $packet = $part1 . chr(0) . $part2;\r
1371 \r
1372         if (!$this->_send_binary_packet($packet)) {\r
1373             return false;\r
1374         }\r
1375 \r
1376         $response = $this->_get_binary_packet();\r
1377         if ($response === false) {\r
1378             user_error('Connection closed by server', E_USER_NOTICE);\r
1379             return false;\r
1380         }\r
1381 \r
1382         extract(unpack('Ctype', $this->_string_shift($response, 1)));\r
1383 \r
1384         switch ($type) {\r
1385             case NET_SSH2_MSG_USERAUTH_FAILURE:\r
1386                 extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1387                 $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE: ' . $this->_string_shift($response, $length);\r
1388                 return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);\r
1389             case NET_SSH2_MSG_USERAUTH_PK_OK:\r
1390                 // we'll just take it on faith that the public key blob and the public key algorithm name are as\r
1391                 // they should be\r
1392                 if (defined('NET_SSH2_LOGGING')) {\r
1393                     $this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PK_OK';\r
1394                 }\r
1395         }\r
1396 \r
1397         $packet = $part1 . chr(1) . $part2;\r
1398         $privatekey->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);\r
1399         $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet));\r
1400         $signature = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($signature), $signature);\r
1401         $packet.= pack('Na*', strlen($signature), $signature);\r
1402 \r
1403         if (!$this->_send_binary_packet($packet)) {\r
1404             return false;\r
1405         }\r
1406 \r
1407         $response = $this->_get_binary_packet();\r
1408         if ($response === false) {\r
1409             user_error('Connection closed by server', E_USER_NOTICE);\r
1410             return false;\r
1411         }\r
1412 \r
1413         extract(unpack('Ctype', $this->_string_shift($response, 1)));\r
1414 \r
1415         switch ($type) {\r
1416             case NET_SSH2_MSG_USERAUTH_FAILURE:\r
1417                 // either the login is bad or the server employees multi-factor authentication\r
1418                 return false;\r
1419             case NET_SSH2_MSG_USERAUTH_SUCCESS:\r
1420                 $this->bitmap |= NET_SSH2_MASK_LOGIN;\r
1421                 return true;\r
1422         }\r
1423 \r
1424         return false;\r
1425     }\r
1426 \r
1427     /**\r
1428      * Execute Command\r
1429      *\r
1430      * @param String $command\r
1431      * @return String\r
1432      * @access public\r
1433      */\r
1434     function exec($command)\r
1435     {\r
1436         if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
1437             return false;\r
1438         }\r
1439 \r
1440         // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to\r
1441         // be adjusted".  0x7FFFFFFF is, at 4GB, the max size.  technically, it should probably be decremented, but, \r
1442         // honestly, if you're transfering more than 4GB, you probably shouldn't be using phpseclib, anyway.\r
1443         // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info\r
1444         $this->window_size_client_to_server[NET_SSH2_CHANNEL_EXEC] = 0x7FFFFFFF;\r
1445         // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy\r
1446         // uses 0x4000, that's what will be used here, as well.\r
1447         $packet_size = 0x4000;\r
1448 \r
1449         $packet = pack('CNa*N3',\r
1450             NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SSH2_CHANNEL_EXEC, $this->window_size_client_to_server[NET_SSH2_CHANNEL_EXEC], $packet_size);\r
1451 \r
1452         if (!$this->_send_binary_packet($packet)) {\r
1453             return false;\r
1454         }\r
1455 \r
1456         $this->channel_status[NET_SSH2_CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;\r
1457 \r
1458         $response = $this->_get_channel_packet(NET_SSH2_CHANNEL_EXEC);\r
1459         if ($response === false) {\r
1460             return false;\r
1461         }\r
1462 \r
1463         // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things\r
1464         // down.  the one place where it might be desirable is if you're doing something like Net_SSH2::exec('ping localhost &').\r
1465         // with a pty-req SSH_MSG_cHANNEL_REQUEST, exec() will return immediately and the ping process will then\r
1466         // then immediately terminate.  without such a request exec() will loop indefinitely.  the ping process won't end but\r
1467         // neither will your script.\r
1468 \r
1469         // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by\r
1470         // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the \r
1471         // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA.  RFC4254#section-5.2 corroborates.\r
1472         $packet = pack('CNNa*CNa*',\r
1473             NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SSH2_CHANNEL_EXEC], strlen('exec'), 'exec', 1, strlen($command), $command);\r
1474         if (!$this->_send_binary_packet($packet)) {\r
1475             return false;\r
1476         }\r
1477 \r
1478         $this->channel_status[NET_SSH2_CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;\r
1479 \r
1480         $response = $this->_get_channel_packet(NET_SSH2_CHANNEL_EXEC);\r
1481         if ($response === false) {\r
1482             return false;\r
1483         }\r
1484 \r
1485         $this->channel_status[NET_SSH2_CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;\r
1486 \r
1487         $output = '';\r
1488         while (true) {\r
1489             $temp = $this->_get_channel_packet(NET_SSH2_CHANNEL_EXEC);\r
1490             switch (true) {\r
1491                 case $temp === true:\r
1492                     return $output;\r
1493                 case $temp === false:\r
1494                     return false;\r
1495                 default:\r
1496                     $output.= $temp;\r
1497             }\r
1498         }\r
1499     }\r
1500 \r
1501     /**\r
1502      * Disconnect\r
1503      *\r
1504      * @access public\r
1505      */\r
1506     function disconnect()\r
1507     {\r
1508         $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);\r
1509     }\r
1510 \r
1511     /**\r
1512      * Destructor.\r
1513      *\r
1514      * Will be called, automatically, if you're supporting just PHP5.  If you're supporting PHP4, you'll need to call\r
1515      * disconnect().\r
1516      *\r
1517      * @access public\r
1518      */\r
1519     function __destruct()\r
1520     {\r
1521         $this->disconnect();\r
1522     }\r
1523 \r
1524     /**\r
1525      * Gets Binary Packets\r
1526      *\r
1527      * See '6. Binary Packet Protocol' of rfc4253 for more info.\r
1528      *\r
1529      * @see Net_SSH2::_send_binary_packet()\r
1530      * @return String\r
1531      * @access private\r
1532      */\r
1533     function _get_binary_packet()\r
1534     {\r
1535         if (feof($this->fsock)) {\r
1536             user_error('Connection closed prematurely', E_USER_NOTICE);\r
1537             return false;\r
1538         }\r
1539 \r
1540         $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838\r
1541         $raw = fread($this->fsock, $this->decrypt_block_size);\r
1542         $stop = strtok(microtime(), ' ') + strtok('');\r
1543 \r
1544         if ($this->decrypt !== false) {\r
1545             $raw = $this->decrypt->decrypt($raw);\r
1546         }\r
1547 \r
1548         extract(unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5)));\r
1549 \r
1550         $remaining_length = $packet_length + 4 - $this->decrypt_block_size;\r
1551         $buffer = '';\r
1552         while ($remaining_length > 0) {\r
1553             $temp = fread($this->fsock, $remaining_length);\r
1554             $buffer.= $temp;\r
1555             $remaining_length-= strlen($temp);\r
1556         }\r
1557         if (!empty($buffer)) {\r
1558             $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer;\r
1559             $buffer = $temp = '';\r
1560         }\r
1561 \r
1562         $payload = $this->_string_shift($raw, $packet_length - $padding_length - 1);\r
1563         $padding = $this->_string_shift($raw, $padding_length); // should leave $raw empty\r
1564 \r
1565         if ($this->hmac_check !== false) {\r
1566             $hmac = fread($this->fsock, $this->hmac_size);\r
1567             if ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) {\r
1568                 user_error('Invalid HMAC', E_USER_NOTICE);\r
1569                 return false;\r
1570             }\r
1571         }\r
1572 \r
1573         //if ($this->decompress) {\r
1574         //    $payload = gzinflate(substr($payload, 2));\r
1575         //}\r
1576 \r
1577         $this->get_seq_no++;\r
1578 \r
1579         if (defined('NET_SSH2_LOGGING')) {\r
1580             $temp = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN';\r
1581             $this->message_number_log[] = '<- ' . $temp .\r
1582                                           ' (' . round($stop - $start, 4) . 's)';\r
1583             if (NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) {\r
1584                 $this->message_log[] = substr($payload, 1);\r
1585             }\r
1586         }\r
1587 \r
1588         return $this->_filter($payload);\r
1589     }\r
1590 \r
1591     /**\r
1592      * Filter Binary Packets\r
1593      *\r
1594      * Because some binary packets need to be ignored...\r
1595      *\r
1596      * @see Net_SSH2::_get_binary_packet()\r
1597      * @return String\r
1598      * @access private\r
1599      */\r
1600     function _filter($payload)\r
1601     {\r
1602         switch (ord($payload[0])) {\r
1603             case NET_SSH2_MSG_DISCONNECT:\r
1604                 $this->_string_shift($payload, 1);\r
1605                 extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8)));\r
1606                 $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode($this->_string_shift($payload, $length));\r
1607                 $this->bitmask = 0;\r
1608                 return false;\r
1609             case NET_SSH2_MSG_IGNORE:\r
1610                 $payload = $this->_get_binary_packet();\r
1611                 break;\r
1612             case NET_SSH2_MSG_DEBUG:\r
1613                 $this->_string_shift($payload, 2);\r
1614                 extract(unpack('Nlength', $this->_string_shift($payload, 4)));\r
1615                 $this->errors[] = 'SSH_MSG_DEBUG: ' . utf8_decode($this->_string_shift($payload, $length));\r
1616                 $payload = $this->_get_binary_packet();\r
1617                 break;\r
1618             case NET_SSH2_MSG_UNIMPLEMENTED:\r
1619                 return false;\r
1620             case NET_SSH2_MSG_KEXINIT:\r
1621                 if ($this->session_id !== false) {\r
1622                     if (!$this->_key_exchange($payload)) {\r
1623                         $this->bitmask = 0;\r
1624                         return false;\r
1625                     }\r
1626                     $payload = $this->_get_binary_packet();\r
1627                 }\r
1628         }\r
1629 \r
1630         // 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\r
1631         if (($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR) && !($this->bitmap & NET_SSH2_MASK_LOGIN) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {\r
1632             $this->_string_shift($payload, 1);\r
1633             extract(unpack('Nlength', $this->_string_shift($payload, 4)));\r
1634             $this->errors[] = 'SSH_MSG_USERAUTH_BANNER: ' . utf8_decode($this->_string_shift($payload, $length));\r
1635             $payload = $this->_get_binary_packet();\r
1636         }\r
1637 \r
1638         // only called when we've already logged in\r
1639         if (($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR) && ($this->bitmap & NET_SSH2_MASK_LOGIN)) {\r
1640             switch (ord($payload[0])) {\r
1641                 case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4\r
1642                     $this->_string_shift($payload, 1);\r
1643                     extract(unpack('Nlength', $this->_string_shift($payload)));\r
1644                     $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . utf8_decode($this->_string_shift($payload, $length));\r
1645 \r
1646                     if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) {\r
1647                         return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);\r
1648                     }\r
1649 \r
1650                     $payload = $this->_get_binary_packet();\r
1651                     break;\r
1652                 case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1\r
1653                     $this->_string_shift($payload, 1);\r
1654                     extract(unpack('N', $this->_string_shift($payload, 4)));\r
1655                     $this->errors[] = 'SSH_MSG_CHANNEL_OPEN: ' . utf8_decode($this->_string_shift($payload, $length));\r
1656 \r
1657                     $this->_string_shift($payload, 4); // skip over client channel\r
1658                     extract(unpack('Nserver_channel', $this->_string_shift($payload, 4)));\r
1659 \r
1660                     $packet = pack('CN3a*Na*',\r
1661                         NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, '');\r
1662 \r
1663                     if (!$this->_send_binary_packet($packet)) {\r
1664                         return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);\r
1665                     }\r
1666 \r
1667                     $payload = $this->_get_binary_packet();\r
1668                     break;\r
1669                 case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:\r
1670                     $payload = $this->_get_binary_packet();\r
1671             }\r
1672         }\r
1673 \r
1674         return $payload;\r
1675     }\r
1676 \r
1677     /**\r
1678      * Gets channel data\r
1679      *\r
1680      * Returns the data as a string if it's available and false if not.\r
1681      *\r
1682      * @param $client_channel\r
1683      * @return Mixed\r
1684      * @access private\r
1685      */\r
1686     function _get_channel_packet($client_channel)\r
1687     {\r
1688         if (!empty($this->channel_buffers[$client_channel])) {\r
1689             return array_shift($this->channel_buffers[$client_channel]);\r
1690         }\r
1691 \r
1692         while (true) {\r
1693             $response = $this->_get_binary_packet();\r
1694             if ($response === false) {\r
1695                 user_error('Connection closed by server', E_USER_NOTICE);\r
1696                 return false;\r
1697             }\r
1698 \r
1699             extract(unpack('Ctype/Nchannel', $this->_string_shift($response, 5)));\r
1700 \r
1701             switch ($this->channel_status[$channel]) {\r
1702                 case NET_SSH2_MSG_CHANNEL_OPEN:\r
1703                     switch ($type) {\r
1704                         case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:\r
1705                             extract(unpack('Nserver_channel', $this->_string_shift($response, 4)));\r
1706                             $this->server_channels[$client_channel] = $server_channel;\r
1707                             $this->_string_shift($response, 4); // skip over (server) window size\r
1708                             $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4));\r
1709                             $this->packet_size_client_to_server[$client_channel] = $temp['packet_size_client_to_server'];\r
1710                             return true;\r
1711                         //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:\r
1712                         default:\r
1713                             user_error('Unable to open channel', E_USER_NOTICE);\r
1714                             return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);\r
1715                     }\r
1716                     break;\r
1717                 case NET_SSH2_MSG_CHANNEL_REQUEST:\r
1718                     switch ($type) {\r
1719                         case NET_SSH2_MSG_CHANNEL_SUCCESS:\r
1720                             return true;\r
1721                         //case NET_SSH2_MSG_CHANNEL_FAILURE:\r
1722                         default:\r
1723                             user_error('Unable to request pseudo-terminal', E_USER_NOTICE);\r
1724                             return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);\r
1725                     }\r
1726 \r
1727             }\r
1728 \r
1729             switch ($type) {\r
1730                 case NET_SSH2_MSG_CHANNEL_DATA:\r
1731                     if ($client_channel == NET_SSH2_CHANNEL_EXEC) {\r
1732                         // SCP requires null packets, such as this, be sent.  further, in the case of the ssh.com SSH server\r
1733                         // this actually seems to make things twice as fast.  more to the point, the message right after \r
1734                         // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise.\r
1735                         // in OpenSSH it slows things down but only by a couple thousandths of a second.\r
1736                         $this->_send_channel_packet($client_channel, chr(0));\r
1737                     }\r
1738                     extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1739                     $data = $this->_string_shift($response, $length);\r
1740                     if ($client_channel == $channel) {\r
1741                         return $data;\r
1742                     }\r
1743                     if (!isset($this->channel_buffers[$client_channel])) {\r
1744                         $this->channel_buffers[$client_channel] = array();\r
1745                     }\r
1746                     $this->channel_buffers[$client_channel][] = $data;\r
1747                     break;\r
1748                 case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:\r
1749                     if ($client_channel == NET_SSH2_CHANNEL_EXEC) {\r
1750                         $this->_send_channel_packet($client_channel, chr(0));\r
1751                     }\r
1752                     // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR\r
1753                     extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8)));\r
1754                     $data = $this->_string_shift($response, $length);\r
1755                     if ($client_channel == $channel) {\r
1756                         return $data;\r
1757                     }\r
1758                     if (!isset($this->channel_buffers[$client_channel])) {\r
1759                         $this->channel_buffers[$client_channel] = array();\r
1760                     }\r
1761                     $this->channel_buffers[$client_channel][] = $data;\r
1762                     break;\r
1763                 case NET_SSH2_MSG_CHANNEL_REQUEST:\r
1764                     extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1765                     $value = $this->_string_shift($response, $length);\r
1766                     switch ($value) {\r
1767                         case 'exit-signal':\r
1768                             $this->_string_shift($response, 1);\r
1769                             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1770                             $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length);\r
1771                             $this->_string_shift($response, 1);\r
1772                             extract(unpack('Nlength', $this->_string_shift($response, 4)));\r
1773                             if ($length) {\r
1774                                 $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length);\r
1775                             }\r
1776                         //case 'exit-status':\r
1777                         default:\r
1778                             // "Some systems may not implement signals, in which case they SHOULD ignore this message."\r
1779                             //  -- http://tools.ietf.org/html/rfc4254#section-6.9\r
1780                             break;\r
1781                     }\r
1782                     break;\r
1783                 case NET_SSH2_MSG_CHANNEL_CLOSE:\r
1784                     $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));\r
1785                     return true;\r
1786                 case NET_SSH2_MSG_CHANNEL_EOF:\r
1787                     break;\r
1788                 default:\r
1789                     user_error('Error reading channel data', E_USER_NOTICE);\r
1790                     return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);\r
1791             }\r
1792         }\r
1793     }\r
1794 \r
1795     /**\r
1796      * Sends Binary Packets\r
1797      *\r
1798      * See '6. Binary Packet Protocol' of rfc4253 for more info.\r
1799      *\r
1800      * @param String $data\r
1801      * @see Net_SSH2::_get_binary_packet()\r
1802      * @return Boolean\r
1803      * @access private\r
1804      */\r
1805     function _send_binary_packet($data)\r
1806     {\r
1807         if (feof($this->fsock)) {\r
1808             user_error('Connection closed prematurely', E_USER_NOTICE);\r
1809             return false;\r
1810         }\r
1811 \r
1812         //if ($this->compress) {\r
1813         //    // the -4 removes the checksum:\r
1814         //    // http://php.net/function.gzcompress#57710\r
1815         //    $data = substr(gzcompress($data), 0, -4);\r
1816         //}\r
1817 \r
1818         // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9\r
1819         $packet_length = strlen($data) + 9;\r
1820         // round up to the nearest $this->encrypt_block_size\r
1821         $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;\r
1822         // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length\r
1823         $padding_length = $packet_length - strlen($data) - 5;\r
1824 \r
1825         $padding = '';\r
1826         for ($i = 0; $i < $padding_length; $i++) {\r
1827             $padding.= chr(crypt_random(0, 255));\r
1828         }\r
1829 \r
1830         // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself\r
1831         $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);\r
1832 \r
1833         $hmac = $this->hmac_create !== false ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : '';\r
1834         $this->send_seq_no++;\r
1835 \r
1836         if ($this->encrypt !== false) {\r
1837             $packet = $this->encrypt->encrypt($packet);\r
1838         }\r
1839 \r
1840         $packet.= $hmac;\r
1841 \r
1842         $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838\r
1843         $result = strlen($packet) == fputs($this->fsock, $packet);\r
1844         $stop = strtok(microtime(), ' ') + strtok('');\r
1845 \r
1846         if (defined('NET_SSH2_LOGGING')) {\r
1847             $temp = isset($this->message_numbers[ord($data[0])]) ? $this->message_numbers[ord($data[0])] : 'UNKNOWN';\r
1848             $this->message_number_log[] = '-> ' . $temp .\r
1849                                           ' (' . round($stop - $start, 4) . 's)';\r
1850             if (NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) {\r
1851                 $this->message_log[] = substr($data, 1);\r
1852             }\r
1853         }\r
1854 \r
1855         return $result;\r
1856     }\r
1857 \r
1858     /**\r
1859      * Sends channel data\r
1860      *\r
1861      * Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate\r
1862      *\r
1863      * @param Integer $client_channel\r
1864      * @param String $data\r
1865      * @return Boolean\r
1866      * @access private\r
1867      */\r
1868     function _send_channel_packet($client_channel, $data)\r
1869     {\r
1870         while (strlen($data) > $this->packet_size_client_to_server[$client_channel]) {\r
1871             // resize the window, if appropriate\r
1872             $this->window_size_client_to_server[$client_channel]-= $this->packet_size_client_to_server[$client_channel];\r
1873             if ($this->window_size_client_to_server[$client_channel] < 0) {\r
1874                 $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$client_channel], $this->window_size);\r
1875                 if (!$this->_send_binary_packet($packet)) {\r
1876                     return false;\r
1877                 }\r
1878                 $this->window_size_client_to_server[$client_channel]+= $this->window_size;\r
1879             }\r
1880 \r
1881             $packet = pack('CN2a*',\r
1882                 NET_SSH2_MSG_CHANNEL_DATA,\r
1883                 $this->server_channels[$client_channel],\r
1884                 $this->packet_size_client_to_server[$client_channel],\r
1885                 $this->_string_shift($data, $this->packet_size_client_to_server[$client_channel])\r
1886             );\r
1887 \r
1888             if (!$this->_send_binary_packet($packet)) {\r
1889                 return false;\r
1890             }\r
1891         }\r
1892 \r
1893         // resize the window, if appropriate\r
1894         $this->window_size_client_to_server[$client_channel]-= strlen($data);\r
1895         if ($this->window_size_client_to_server[$client_channel] < 0) {\r
1896             $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$client_channel], $this->window_size);\r
1897             if (!$this->_send_binary_packet($packet)) {\r
1898                 return false;\r
1899             }\r
1900             $this->window_size_client_to_server[$client_channel]+= $this->window_size;\r
1901         }\r
1902 \r
1903         return $this->_send_binary_packet(pack('CN2a*',\r
1904             NET_SSH2_MSG_CHANNEL_DATA,\r
1905             $this->server_channels[$client_channel],\r
1906             strlen($data),\r
1907             $data));\r
1908     }\r
1909 \r
1910     /**\r
1911      * Disconnect\r
1912      *\r
1913      * @param Integer $reason\r
1914      * @return Boolean\r
1915      * @access private\r
1916      */\r
1917     function _disconnect($reason)\r
1918     {\r
1919         if ($this->bitmap) {\r
1920             $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, '');\r
1921             $this->_send_binary_packet($data);\r
1922             $this->bitmap = 0;\r
1923             fclose($this->fsock);\r
1924             return false;\r
1925         }\r
1926     }\r
1927 \r
1928     /**\r
1929      * String Shift\r
1930      *\r
1931      * Inspired by array_shift\r
1932      *\r
1933      * @param String $string\r
1934      * @param optional Integer $index\r
1935      * @return String\r
1936      * @access private\r
1937      */\r
1938     function _string_shift(&$string, $index = 1)\r
1939     {\r
1940         $substr = substr($string, 0, $index);\r
1941         $string = substr($string, $index);\r
1942         return $substr;\r
1943     }\r
1944 \r
1945     /**\r
1946      * Define Array\r
1947      *\r
1948      * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of\r
1949      * named constants from it, using the value as the name of the constant and the index as the value of the constant.\r
1950      * If any of the constants that would be defined already exists, none of the constants will be defined.\r
1951      *\r
1952      * @param Array $array\r
1953      * @access private\r
1954      */\r
1955     function _define_array()\r
1956     {\r
1957         $args = func_get_args();\r
1958         foreach ($args as $arg) {\r
1959             foreach ($arg as $key=>$value) {\r
1960                 if (!defined($value)) {\r
1961                     define($value, $key);\r
1962                 } else {\r
1963                     break 2;\r
1964                 }\r
1965             }\r
1966         }\r
1967     }\r
1968 \r
1969     /**\r
1970      * Returns a log of the packets that have been sent and received.\r
1971      *\r
1972      * 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')\r
1973      *\r
1974      * @access public\r
1975      * @return String or Array\r
1976      */\r
1977     function getLog()\r
1978     {\r
1979         if (!defined('NET_SSH2_LOGGING')) {\r
1980             return false;\r
1981         }\r
1982 \r
1983         switch (NET_SSH2_LOGGING) {\r
1984             case NET_SSH2_LOG_SIMPLE:\r
1985                 return $this->message_number_log;\r
1986                 break;\r
1987             case NET_SSH2_LOG_COMPLEX:\r
1988                 return $this->_format_log($this->message_log, $this->message_number_log);\r
1989                 break;\r
1990             default:\r
1991                 return false;\r
1992         }\r
1993     }\r
1994 \r
1995     /**\r
1996      * Formats a log for printing\r
1997      *\r
1998      * @param Array $message_log\r
1999      * @param Array $message_number_log\r
2000      * @access private\r
2001      * @return String\r
2002      */\r
2003     function _format_log($message_log, $message_number_log)\r
2004     {\r
2005         static $boundary = ':', $long_width = 65, $short_width = 16;\r
2006 \r
2007         $output = '';\r
2008         for ($i = 0; $i < count($message_log); $i++) {\r
2009             $output.= $message_number_log[$i] . "\r\n";\r
2010             $current_log = $message_log[$i];\r
2011             $j = 0;\r
2012             do {\r
2013                 if (!empty($current_log)) {\r
2014                     $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0  ';\r
2015                 }\r
2016                 $fragment = $this->_string_shift($current_log, $short_width);\r
2017                 $hex = substr(\r
2018                            preg_replace(\r
2019                                '#(.)#es',\r
2020                                '"' . $boundary . '" . str_pad(dechex(ord(substr("\\1", -1))), 2, "0", STR_PAD_LEFT)',\r
2021                                $fragment),\r
2022                            strlen($boundary)\r
2023                        );\r
2024                 // replace non ASCII printable characters with dots\r
2025                 // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters\r
2026                 // also replace < with a . since < messes up the output on web browsers\r
2027                 $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);\r
2028                 $output.= str_pad($hex, $long_width - $short_width, ' ') . $raw . "\r\n";\r
2029                 $j++;\r
2030             } while (!empty($current_log));\r
2031             $output.= "\r\n";\r
2032         }\r
2033 \r
2034         return $output;\r
2035     }\r
2036 \r
2037     /**\r
2038      * Returns all errors\r
2039      *\r
2040      * @return String\r
2041      * @access public\r
2042      */\r
2043     function getErrors()\r
2044     {\r
2045         return $this->errors;\r
2046     }\r
2047 \r
2048     /**\r
2049      * Returns the last error\r
2050      *\r
2051      * @return String\r
2052      * @access public\r
2053      */\r
2054     function getLastError()\r
2055     {\r
2056         return $this->errors[count($this->errors) - 1];\r
2057     }\r
2058 \r
2059     /**\r
2060      * Return the server identification.\r
2061      *\r
2062      * @return String\r
2063      * @access public\r
2064      */\r
2065     function getServerIdentification()\r
2066     {\r
2067         return $this->server_identifier;\r
2068     }\r
2069 \r
2070     /**\r
2071      * Return a list of the key exchange algorithms the server supports.\r
2072      *\r
2073      * @return Array\r
2074      * @access public\r
2075      */\r
2076     function getKexAlgorithms()\r
2077     {\r
2078         return $this->kex_algorithms;\r
2079     }\r
2080 \r
2081     /**\r
2082      * Return a list of the host key (public key) algorithms the server supports.\r
2083      *\r
2084      * @return Array\r
2085      * @access public\r
2086      */\r
2087     function getServerHostKeyAlgorithms()\r
2088     {\r
2089         return $this->server_host_key_algorithms;\r
2090     }\r
2091 \r
2092     /**\r
2093      * Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff from the client.\r
2094      *\r
2095      * @return Array\r
2096      * @access public\r
2097      */\r
2098     function getEncryptionAlgorithmsClient2Server()\r
2099     {\r
2100         return $this->encryption_algorithms_client_to_server;\r
2101     }\r
2102 \r
2103     /**\r
2104      * Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to the client.\r
2105      *\r
2106      * @return Array\r
2107      * @access public\r
2108      */\r
2109     function getEncryptionAlgorithmsServer2Client()\r
2110     {\r
2111         return $this->encryption_algorithms_server_to_client;\r
2112     }\r
2113 \r
2114     /**\r
2115      * Return a list of the MAC algorithms the server supports, when receiving stuff from the client.\r
2116      *\r
2117      * @return Array\r
2118      * @access public\r
2119      */\r
2120     function getMACAlgorithmsClient2Server()\r
2121     {\r
2122         return $this->mac_algorithms_client_to_server;\r
2123     }\r
2124 \r
2125     /**\r
2126      * Return a list of the MAC algorithms the server supports, when sending stuff to the client.\r
2127      *\r
2128      * @return Array\r
2129      * @access public\r
2130      */\r
2131     function getMACAlgorithmsServer2Client()\r
2132     {\r
2133         return $this->mac_algorithms_server_to_client;\r
2134     }\r
2135 \r
2136     /**\r
2137      * Return a list of the compression algorithms the server supports, when receiving stuff from the client.\r
2138      *\r
2139      * @return Array\r
2140      * @access public\r
2141      */\r
2142     function getCompressionAlgorithmsClient2Server()\r
2143     {\r
2144         return $this->compression_algorithms_client_to_server;\r
2145     }\r
2146 \r
2147     /**\r
2148      * Return a list of the compression algorithms the server supports, when sending stuff to the client.\r
2149      *\r
2150      * @return Array\r
2151      * @access public\r
2152      */\r
2153     function getCompressionAlgorithmsServer2Client()\r
2154     {\r
2155         return $this->compression_algorithms_server_to_client;\r
2156     }\r
2157 \r
2158     /**\r
2159      * Return a list of the languages the server supports, when sending stuff to the client.\r
2160      *\r
2161      * @return Array\r
2162      * @access public\r
2163      */\r
2164     function getLanguagesServer2Client()\r
2165     {\r
2166         return $this->languages_server_to_client;\r
2167     }\r
2168 \r
2169     /**\r
2170      * Return a list of the languages the server supports, when receiving stuff from the client.\r
2171      *\r
2172      * @return Array\r
2173      * @access public\r
2174      */\r
2175     function getLanguagesClient2Server()\r
2176     {\r
2177         return $this->languages_client_to_server;\r
2178     }\r
2179 \r
2180     /**\r
2181      * Returns the server public host key.\r
2182      *\r
2183      * Caching this the first time you connect to a server and checking the result on subsequent connections\r
2184      * is recommended.  Returns false if the server signature is not signed correctly with the public host key.\r
2185      *\r
2186      * @return Mixed\r
2187      * @access public\r
2188      */\r
2189     function getServerPublicHostKey()\r
2190     {\r
2191         $signature = $this->signature;\r
2192         $server_public_host_key = $this->server_public_host_key;\r
2193 \r
2194         extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4)));\r
2195         $this->_string_shift($server_public_host_key, $length);\r
2196 \r
2197         switch ($this->signature_format) {\r
2198             case 'ssh-dss':\r
2199                 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));\r
2200                 $p = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);\r
2201 \r
2202                 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));\r
2203                 $q = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);\r
2204 \r
2205                 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));\r
2206                 $g = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);\r
2207 \r
2208                 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));\r
2209                 $y = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);\r
2210 \r
2211                 /* The value for 'dss_signature_blob' is encoded as a string containing\r
2212                    r, followed by s (which are 160-bit integers, without lengths or\r
2213                    padding, unsigned, and in network byte order). */\r
2214                 $temp = unpack('Nlength', $this->_string_shift($signature, 4));\r
2215                 if ($temp['length'] != 40) {\r
2216                     user_error('Invalid signature', E_USER_NOTICE);\r
2217                     return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
2218                 }\r
2219 \r
2220                 $r = new Math_BigInteger($this->_string_shift($signature, 20), 256);\r
2221                 $s = new Math_BigInteger($this->_string_shift($signature, 20), 256);\r
2222 \r
2223                 if ($r->compare($q) >= 0 || $s->compare($q) >= 0) {\r
2224                     user_error('Invalid signature', E_USER_NOTICE);\r
2225                     return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
2226                 }\r
2227 \r
2228                 $w = $s->modInverse($q);\r
2229 \r
2230                 $u1 = $w->multiply(new Math_BigInteger(sha1($this->session_id), 16));\r
2231                 list(, $u1) = $u1->divide($q);\r
2232 \r
2233                 $u2 = $w->multiply($r);\r
2234                 list(, $u2) = $u2->divide($q);\r
2235 \r
2236                 $g = $g->modPow($u1, $p);\r
2237                 $y = $y->modPow($u2, $p);\r
2238 \r
2239                 $v = $g->multiply($y);\r
2240                 list(, $v) = $v->divide($p);\r
2241                 list(, $v) = $v->divide($q);\r
2242 \r
2243                 if (!$v->equals($r)) {\r
2244                     user_error('Bad server signature', E_USER_NOTICE);\r
2245                     return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);\r
2246                 }\r
2247 \r
2248                 break;\r
2249             case 'ssh-rsa':\r
2250                 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));\r
2251                 $e = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);\r
2252 \r
2253                 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));\r
2254                 $n = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);\r
2255                 $nLength = $temp['length'];\r
2256 \r
2257                 /*\r
2258                 $temp = unpack('Nlength', $this->_string_shift($signature, 4));\r
2259                 $signature = $this->_string_shift($signature, $temp['length']);\r
2260 \r
2261                 if (!class_exists('Crypt_RSA')) {\r
2262                     require_once('Crypt/RSA.php');\r
2263                 }\r
2264 \r
2265                 $rsa = new Crypt_RSA();\r
2266                 $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);\r
2267                 $rsa->loadKey(array('e' => $e, 'n' => $n), CRYPT_RSA_PUBLIC_FORMAT_RAW);\r
2268                 if (!$rsa->verify($this->session_id, $signature)) {\r
2269                     user_error('Bad server signature', E_USER_NOTICE);\r
2270                     return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);\r
2271                 }\r
2272                 */\r
2273 \r
2274                 $temp = unpack('Nlength', $this->_string_shift($signature, 4));\r
2275                 $s = new Math_BigInteger($this->_string_shift($signature, $temp['length']), 256);\r
2276 \r
2277                 // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the\r
2278                 // following URL:\r
2279                 // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf\r
2280 \r
2281                 // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source.\r
2282 \r
2283                 if ($s->compare(new Math_BigInteger()) < 0 || $s->compare($n->subtract(new Math_BigInteger(1))) > 0) {\r
2284                     user_error('Invalid signature', E_USER_NOTICE);\r
2285                     return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);\r
2286                 }\r
2287 \r
2288                 $s = $s->modPow($e, $n);\r
2289                 $s = $s->toBytes();\r
2290 \r
2291                 $h = pack('N4H*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, sha1($this->session_id));\r
2292                 $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 3 - strlen($h)) . $h;\r
2293 \r
2294                 if ($s != $h) {\r
2295                     user_error('Bad server signature', E_USER_NOTICE);\r
2296                     return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);\r
2297                 }\r
2298         }\r
2299 \r
2300         return $this->server_public_host_key;\r
2301     }\r
2302 }