ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
SSH2.php
Go to the documentation of this file.
1<?php
2
50namespace phpseclib\Net;
51
61use phpseclib\Math\BigInteger; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
63
71class SSH2
72{
79 const MASK_CONSTRUCTOR = 0x00000001;
80 const MASK_CONNECTED = 0x00000002;
81 const MASK_LOGIN_REQ = 0x00000004;
82 const MASK_LOGIN = 0x00000008;
83 const MASK_SHELL = 0x00000010;
84 const MASK_WINDOW_ADJUST = 0x00000020;
103 const CHANNEL_EXEC = 0; // PuTTy uses 0x100
104 const CHANNEL_SHELL = 1;
116 const LOG_SIMPLE = 1;
120 const LOG_COMPLEX = 2;
124 const LOG_REALTIME = 3;
138 const READ_SIMPLE = 1;
142 const READ_REGEX = 2;
146 const LOG_MAX_SIZE = 1048576; // 1024 * 1024
156
164
174 var $bitmap = 0;
175
184 var $errors = array();
185
194
202 var $kex_algorithms = false;
203
212
221
230
239
248
257
266
275
284
293
302
311
328
338
346 var $decrypt = false;
347
355 var $encrypt = false;
356
364 var $hmac_create = false;
365
373 var $hmac_check = false;
374
386 var $hmac_size = false;
387
396
410 var $session_id = false;
411
421 var $exchange_hash = false;
422
430 var $message_numbers = array();
431
439 var $disconnect_reasons = array();
440
449
458 var $terminal_modes = array();
459
469
480
490 var $get_seq_no = 0;
491
502 var $server_channels = array();
503
515 var $channel_buffers = array();
516
526 var $channel_status = array();
527
538
546 var $message_number_log = array();
547
555 var $message_log = array();
556
567 var $window_size = 0x7FFFFFFF;
568
579
590
600 var $signature = '';
601
612
621
633
641
649
658
667
676
684
691 var $quiet_mode = false;
692
700
708
716 var $request_pty = false;
717
725
733
741
750
759
772
780 var $is_timeout = false;
781
789 var $log_boundary = ':';
790
799
808
817 var $host;
818
827 var $port;
828
839
849 var $windowRows = 24;
850
859 var $crypto_engine = false;
860
868
881 function __construct($host, $port = 22, $timeout = 10)
882 {
883 $this->message_numbers = array(
884 1 => 'NET_SSH2_MSG_DISCONNECT',
885 2 => 'NET_SSH2_MSG_IGNORE',
886 3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
887 4 => 'NET_SSH2_MSG_DEBUG',
888 5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
889 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
890 20 => 'NET_SSH2_MSG_KEXINIT',
891 21 => 'NET_SSH2_MSG_NEWKEYS',
892 30 => 'NET_SSH2_MSG_KEXDH_INIT',
893 31 => 'NET_SSH2_MSG_KEXDH_REPLY',
894 50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
895 51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
896 52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
897 53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
898
899 80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
900 81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
901 82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
902 90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
903 91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
904 92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
905 93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
906 94 => 'NET_SSH2_MSG_CHANNEL_DATA',
907 95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
908 96 => 'NET_SSH2_MSG_CHANNEL_EOF',
909 97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
910 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
911 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
912 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
913 );
914 $this->disconnect_reasons = array(
915 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
916 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
917 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
918 4 => 'NET_SSH2_DISCONNECT_RESERVED',
919 5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
920 6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
921 7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
922 8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
923 9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
924 10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
925 11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
926 12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
927 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
928 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
929 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
930 );
931 $this->channel_open_failure_reasons = array(
932 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
933 );
934 $this->terminal_modes = array(
935 0 => 'NET_SSH2_TTY_OP_END'
936 );
937 $this->channel_extended_data_type_codes = array(
938 1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
939 );
940
941 $this->_define_array(
942 $this->message_numbers,
943 $this->disconnect_reasons,
944 $this->channel_open_failure_reasons,
945 $this->terminal_modes,
946 $this->channel_extended_data_type_codes,
947 array(60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'),
948 array(60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'),
949 array(60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
950 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'),
951 // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
952 array(30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
953 31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
954 32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
955 33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
956 34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'),
957 // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
958 array(30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
959 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY')
960 );
961
962 if (is_resource($host)) {
963 $this->fsock = $host;
964 return;
965 }
966
967 if (is_string($host)) {
968 $this->host = $host;
969 $this->port = $port;
970 $this->timeout = $timeout;
971 }
972 }
973
984 {
985 $this->crypto_engine = $engine;
986 }
987
994 function _connect()
995 {
996 if ($this->bitmap & self::MASK_CONSTRUCTOR) {
997 return false;
998 }
999
1000 $this->bitmap |= self::MASK_CONSTRUCTOR;
1001
1002 $this->curTimeout = $this->timeout;
1003
1004 $this->last_packet = microtime(true);
1005
1006 if (!is_resource($this->fsock)) {
1007 $start = microtime(true);
1008 $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout);
1009 if (!$this->fsock) {
1010 $host = $this->host . ':' . $this->port;
1011 user_error(rtrim("Cannot connect to $host. Error $errno. $errstr"));
1012 return false;
1013 }
1014 $elapsed = microtime(true) - $start;
1015
1016 $this->curTimeout-= $elapsed;
1017
1018 if ($this->curTimeout <= 0) {
1019 $this->is_timeout = true;
1020 return false;
1021 }
1022 }
1023
1024 /* According to the SSH2 specs,
1025
1026 "The server MAY send other lines of data before sending the version
1027 string. Each line SHOULD be terminated by a Carriage Return and Line
1028 Feed. Such lines MUST NOT begin with "SSH-", and SHOULD be encoded
1029 in ISO-10646 UTF-8 [RFC3629] (language is not specified). Clients
1030 MUST be able to process such lines." */
1031 $temp = '';
1032 $extra = '';
1033 while (!feof($this->fsock) && !preg_match('#^SSH-(\d\.\d+)#', $temp, $matches)) {
1034 if (substr($temp, -2) == "\r\n") {
1035 $extra.= $temp;
1036 $temp = '';
1037 }
1038
1039 if ($this->curTimeout) {
1040 if ($this->curTimeout < 0) {
1041 $this->is_timeout = true;
1042 return false;
1043 }
1044 $read = array($this->fsock);
1045 $write = $except = null;
1046 $start = microtime(true);
1047 $sec = floor($this->curTimeout);
1048 $usec = 1000000 * ($this->curTimeout - $sec);
1049 // on windows this returns a "Warning: Invalid CRT parameters detected" error
1050 // the !count() is done as a workaround for <https://bugs.php.net/42682>
1051 if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
1052 $this->is_timeout = true;
1053 return false;
1054 }
1055 $elapsed = microtime(true) - $start;
1056 $this->curTimeout-= $elapsed;
1057 }
1058
1059 $temp.= fgets($this->fsock, 255);
1060 }
1061
1062 if (feof($this->fsock)) {
1063 user_error('Connection closed by server');
1064 return false;
1065 }
1066
1067 $this->identifier = $this->_generate_identifier();
1068
1069 if (defined('NET_SSH2_LOGGING')) {
1070 $this->_append_log('<-', $extra . $temp);
1071 $this->_append_log('->', $this->identifier . "\r\n");
1072 }
1073
1074 $this->server_identifier = trim($temp, "\r\n");
1075 if (strlen($extra)) {
1076 $this->errors[] = utf8_decode($extra);
1077 }
1078
1079 if ($matches[1] != '1.99' && $matches[1] != '2.0') {
1080 user_error("Cannot connect to SSH $matches[1] servers");
1081 return false;
1082 }
1083
1084 fputs($this->fsock, $this->identifier . "\r\n");
1085
1086 $response = $this->_get_binary_packet();
1087 if ($response === false) {
1088 user_error('Connection closed by server');
1089 return false;
1090 }
1091
1092 if (ord($response[0]) != NET_SSH2_MSG_KEXINIT) {
1093 user_error('Expected SSH_MSG_KEXINIT');
1094 return false;
1095 }
1096
1097 if (!$this->_key_exchange($response)) {
1098 return false;
1099 }
1100
1101 $this->bitmap|= self::MASK_CONNECTED;
1102
1103 return true;
1104 }
1105
1115 {
1116 $identifier = 'SSH-2.0-phpseclib_2.0';
1117
1118 $ext = array();
1119 if (extension_loaded('libsodium')) {
1120 $ext[] = 'libsodium';
1121 }
1122
1123 if (extension_loaded('openssl')) {
1124 $ext[] = 'openssl';
1125 } elseif (extension_loaded('mcrypt')) {
1126 $ext[] = 'mcrypt';
1127 }
1128
1129 if (extension_loaded('gmp')) {
1130 $ext[] = 'gmp';
1131 } elseif (extension_loaded('bcmath')) {
1132 $ext[] = 'bcmath';
1133 }
1134
1135 if (!empty($ext)) {
1136 $identifier .= ' (' . implode(', ', $ext) . ')';
1137 }
1138
1139 return $identifier;
1140 }
1141
1148 function _key_exchange($kexinit_payload_server)
1149 {
1150 $kex_algorithms = array(
1151 // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using
1152 // Curve25519. See doc/curve25519-sha256@libssh.org.txt in the
1153 // libssh repository for more information.
1154 'curve25519-sha256@libssh.org',
1155
1156 // Diffie-Hellman Key Agreement (DH) using integer modulo prime
1157 // groups.
1158 'diffie-hellman-group1-sha1', // REQUIRED
1159 'diffie-hellman-group14-sha1', // REQUIRED
1160 'diffie-hellman-group-exchange-sha1', // RFC 4419
1161 'diffie-hellman-group-exchange-sha256', // RFC 4419
1162 );
1163 if (!class_exists('\Sodium')) {
1164 $kex_algorithms = array_diff(
1166 array('curve25519-sha256@libssh.org')
1167 );
1168 }
1169
1171 'ssh-rsa', // RECOMMENDED sign Raw RSA Key
1172 'ssh-dss' // REQUIRED sign Raw DSS Key
1173 );
1174
1175 $encryption_algorithms = array(
1176 // from <http://tools.ietf.org/html/rfc4345#section-4>:
1177 'arcfour256',
1178 'arcfour128',
1179
1180 //'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
1181
1182 // CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
1183 'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key
1184 'aes192-ctr', // RECOMMENDED AES with 192-bit key
1185 'aes256-ctr', // RECOMMENDED AES with 256-bit key
1186
1187 'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key
1188 'twofish192-ctr', // OPTIONAL Twofish with 192-bit key
1189 'twofish256-ctr', // OPTIONAL Twofish with 256-bit key
1190
1191 'aes128-cbc', // RECOMMENDED AES with a 128-bit key
1192 'aes192-cbc', // OPTIONAL AES with a 192-bit key
1193 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key
1194
1195 'twofish128-cbc', // OPTIONAL Twofish with a 128-bit key
1196 'twofish192-cbc', // OPTIONAL Twofish with a 192-bit key
1197 'twofish256-cbc',
1198 'twofish-cbc', // OPTIONAL alias for "twofish256-cbc"
1199 // (this is being retained for historical reasons)
1200
1201 'blowfish-ctr', // OPTIONAL Blowfish in SDCTR mode
1202
1203 'blowfish-cbc', // OPTIONAL Blowfish in CBC mode
1204
1205 '3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode
1206
1207 '3des-cbc', // REQUIRED three-key 3DES in CBC mode
1208 //'none' // OPTIONAL no encryption; NOT RECOMMENDED
1209 );
1210
1211 if (extension_loaded('openssl') && !extension_loaded('mcrypt')) {
1212 // OpenSSL does not support arcfour256 in any capacity and arcfour128 / arcfour support is limited to
1213 // instances that do not use continuous buffers
1214 $encryption_algorithms = array_diff(
1215 $encryption_algorithms,
1216 array('arcfour256', 'arcfour128', 'arcfour')
1217 );
1218 }
1219
1220 if (class_exists('\phpseclib\Crypt\RC4') === false) {
1221 $encryption_algorithms = array_diff(
1222 $encryption_algorithms,
1223 array('arcfour256', 'arcfour128', 'arcfour')
1224 );
1225 }
1226 if (class_exists('\phpseclib\Crypt\Rijndael') === false) {
1227 $encryption_algorithms = array_diff(
1228 $encryption_algorithms,
1229 array('aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc')
1230 );
1231 }
1232 if (class_exists('\phpseclib\Crypt\Twofish') === false) {
1233 $encryption_algorithms = array_diff(
1234 $encryption_algorithms,
1235 array('twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc')
1236 );
1237 }
1238 if (class_exists('\phpseclib\Crypt\Blowfish') === false) {
1239 $encryption_algorithms = array_diff(
1240 $encryption_algorithms,
1241 array('blowfish-ctr', 'blowfish-cbc')
1242 );
1243 }
1244 if (class_exists('\phpseclib\Crypt\TripleDES') === false) {
1245 $encryption_algorithms = array_diff(
1246 $encryption_algorithms,
1247 array('3des-ctr', '3des-cbc')
1248 );
1249 }
1250 $encryption_algorithms = array_values($encryption_algorithms);
1251
1252 $mac_algorithms = array(
1253 // from <http://www.ietf.org/rfc/rfc6668.txt>:
1254 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
1255
1256 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
1257 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20)
1258 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
1259 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16)
1260 //'none' // OPTIONAL no MAC; NOT RECOMMENDED
1261 );
1262
1263 $compression_algorithms = array(
1264 'none' // REQUIRED no compression
1265 //'zlib' // OPTIONAL ZLIB (LZ77) compression
1266 );
1267
1268 // some SSH servers have buggy implementations of some of the above algorithms
1269 switch ($this->server_identifier) {
1270 case 'SSH-2.0-SSHD':
1271 $mac_algorithms = array_values(array_diff(
1272 $mac_algorithms,
1273 array('hmac-sha1-96', 'hmac-md5-96')
1274 ));
1275 }
1276
1277 $str_kex_algorithms = implode(',', $kex_algorithms);
1278 $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms);
1282
1283 $client_cookie = Random::string(16);
1284
1285 $response = $kexinit_payload_server;
1286 $this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)
1287 $server_cookie = $this->_string_shift($response, 16);
1288
1289 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1290 $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length']));
1291
1292 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1293 $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length']));
1294
1295 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1296 $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
1297
1298 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1299 $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
1300
1301 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1302 $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
1303
1304 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1305 $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
1306
1307 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1308 $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
1309
1310 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1311 $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
1312
1313 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1314 $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
1315
1316 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1317 $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
1318
1319 extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1)));
1320 $first_kex_packet_follows = $first_kex_packet_follows != 0;
1321
1322 // the sending of SSH2_MSG_KEXINIT could go in one of two places. this is the second place.
1323 $kexinit_payload_client = pack(
1324 'Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN',
1325 NET_SSH2_MSG_KEXINIT,
1326 $client_cookie,
1327 strlen($str_kex_algorithms),
1328 $str_kex_algorithms,
1329 strlen($str_server_host_key_algorithms),
1330 $str_server_host_key_algorithms,
1343 0,
1344 '',
1345 0,
1346 '',
1347 0,
1348 0
1349 );
1350
1351 if (!$this->_send_binary_packet($kexinit_payload_client)) {
1352 return false;
1353 }
1354 // here ends the second place.
1355
1356 // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
1357 // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
1358 // diffie-hellman key exchange as fast as possible
1359 $decrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_server_to_client);
1360 $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt);
1361 if ($decryptKeyLength === null) {
1362 user_error('No compatible server to client encryption algorithms found');
1363 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1364 }
1365
1366 $encrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_client_to_server);
1367 $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt);
1368 if ($encryptKeyLength === null) {
1369 user_error('No compatible client to server encryption algorithms found');
1370 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1371 }
1372
1373 // through diffie-hellman key exchange a symmetric key is obtained
1374 $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms);
1375 if ($kex_algorithm === false) {
1376 user_error('No compatible key exchange algorithms found');
1377 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1378 }
1379
1380 // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
1381 $exchange_hash_rfc4419 = '';
1382
1383 if ($kex_algorithm === 'curve25519-sha256@libssh.org') {
1384 $x = Random::string(32);
1385 $eBytes = \Sodium::crypto_box_publickey_from_secretkey($x);
1386 $clientKexInitMessage = NET_SSH2_MSG_KEX_ECDH_INIT;
1387 $serverKexReplyMessage = NET_SSH2_MSG_KEX_ECDH_REPLY;
1388 $kexHash = new Hash('sha256');
1389 } else {
1390 if (strpos($kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
1391 $dh_group_sizes_packed = pack(
1392 'NNN',
1393 $this->kex_dh_group_size_min,
1394 $this->kex_dh_group_size_preferred,
1395 $this->kex_dh_group_size_max
1396 );
1397 $packet = pack(
1398 'Ca*',
1399 NET_SSH2_MSG_KEXDH_GEX_REQUEST,
1400 $dh_group_sizes_packed
1401 );
1402 if (!$this->_send_binary_packet($packet)) {
1403 return false;
1404 }
1405
1406 $response = $this->_get_binary_packet();
1407 if ($response === false) {
1408 user_error('Connection closed by server');
1409 return false;
1410 }
1411 extract(unpack('Ctype', $this->_string_shift($response, 1)));
1412 if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) {
1413 user_error('Expected SSH_MSG_KEX_DH_GEX_GROUP');
1414 return false;
1415 }
1416
1417 extract(unpack('NprimeLength', $this->_string_shift($response, 4)));
1418 $primeBytes = $this->_string_shift($response, $primeLength);
1419 $prime = new BigInteger($primeBytes, -256);
1420
1421 extract(unpack('NgLength', $this->_string_shift($response, 4)));
1422 $gBytes = $this->_string_shift($response, $gLength);
1423 $g = new BigInteger($gBytes, -256);
1424
1425 $exchange_hash_rfc4419 = pack(
1426 'a*Na*Na*',
1427 $dh_group_sizes_packed,
1428 $primeLength,
1429 $primeBytes,
1430 $gLength,
1431 $gBytes
1432 );
1433
1434 $clientKexInitMessage = NET_SSH2_MSG_KEXDH_GEX_INIT;
1435 $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_GEX_REPLY;
1436 } else {
1437 switch ($kex_algorithm) {
1438 // see http://tools.ietf.org/html/rfc2409#section-6.2 and
1439 // http://tools.ietf.org/html/rfc2412, appendex E
1440 case 'diffie-hellman-group1-sha1':
1441 $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
1442 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
1443 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
1444 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
1445 break;
1446 // see http://tools.ietf.org/html/rfc3526#section-3
1447 case 'diffie-hellman-group14-sha1':
1448 $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
1449 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
1450 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
1451 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
1452 '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
1453 '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
1454 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
1455 '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
1456 break;
1457 }
1458 // For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1
1459 // the generator field element is 2 (decimal) and the hash function is sha1.
1460 $g = new BigInteger(2);
1461 $prime = new BigInteger($prime, 16);
1462 $clientKexInitMessage = NET_SSH2_MSG_KEXDH_INIT;
1463 $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_REPLY;
1464 }
1465
1466 switch ($kex_algorithm) {
1467 case 'diffie-hellman-group-exchange-sha256':
1468 $kexHash = new Hash('sha256');
1469 break;
1470 default:
1471 $kexHash = new Hash('sha1');
1472 }
1473
1474 /* To increase the speed of the key exchange, both client and server may
1475 reduce the size of their private exponents. It should be at least
1476 twice as long as the key material that is generated from the shared
1477 secret. For more details, see the paper by van Oorschot and Wiener
1478 [VAN-OORSCHOT].
1479
1480 -- http://tools.ietf.org/html/rfc4419#section-6.2 */
1481 $one = new BigInteger(1);
1482 $keyLength = min($kexHash->getLength(), max($encryptKeyLength, $decryptKeyLength));
1483 $max = $one->bitwise_leftShift(16 * $keyLength); // 2 * 8 * $keyLength
1484 $max = $max->subtract($one);
1485
1486 $x = $one->random($one, $max);
1487 $e = $g->modPow($x, $prime);
1488
1489 $eBytes = $e->toBytes(true);
1490 }
1491 $data = pack('CNa*', $clientKexInitMessage, strlen($eBytes), $eBytes);
1492
1493 if (!$this->_send_binary_packet($data)) {
1494 user_error('Connection closed by server');
1495 return false;
1496 }
1497
1498 $response = $this->_get_binary_packet();
1499 if ($response === false) {
1500 user_error('Connection closed by server');
1501 return false;
1502 }
1503 extract(unpack('Ctype', $this->_string_shift($response, 1)));
1504
1505 if ($type != $serverKexReplyMessage) {
1506 user_error('Expected SSH_MSG_KEXDH_REPLY');
1507 return false;
1508 }
1509
1510 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1511 $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']);
1512
1513 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
1514 $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']);
1515
1516 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1517 $fBytes = $this->_string_shift($response, $temp['length']);
1518
1519 $temp = unpack('Nlength', $this->_string_shift($response, 4));
1520 $this->signature = $this->_string_shift($response, $temp['length']);
1521
1522 $temp = unpack('Nlength', $this->_string_shift($this->signature, 4));
1523 $this->signature_format = $this->_string_shift($this->signature, $temp['length']);
1524
1525 if ($kex_algorithm === 'curve25519-sha256@libssh.org') {
1526 if (strlen($fBytes) !== 32) {
1527 user_error('Received curve25519 public key of invalid length.');
1528 return false;
1529 }
1530 $key = new BigInteger(\Sodium::crypto_scalarmult($x, $fBytes), 256);
1531 \Sodium::sodium_memzero($x);
1532 } else {
1533 $f = new BigInteger($fBytes, -256);
1534 $key = $f->modPow($x, $prime);
1535 }
1536 $keyBytes = $key->toBytes(true);
1537
1538 $this->exchange_hash = pack(
1539 'Na*Na*Na*Na*Na*a*Na*Na*Na*',
1540 strlen($this->identifier),
1541 $this->identifier,
1542 strlen($this->server_identifier),
1543 $this->server_identifier,
1544 strlen($kexinit_payload_client),
1545 $kexinit_payload_client,
1546 strlen($kexinit_payload_server),
1547 $kexinit_payload_server,
1548 strlen($this->server_public_host_key),
1549 $this->server_public_host_key,
1550 $exchange_hash_rfc4419,
1551 strlen($eBytes),
1552 $eBytes,
1553 strlen($fBytes),
1554 $fBytes,
1555 strlen($keyBytes),
1556 $keyBytes
1557 );
1558
1559 $this->exchange_hash = $kexHash->hash($this->exchange_hash);
1560
1561 if ($this->session_id === false) {
1562 $this->session_id = $this->exchange_hash;
1563 }
1564
1565 $server_host_key_algorithm = $this->_array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
1566 if ($server_host_key_algorithm === false) {
1567 user_error('No compatible server host key algorithms found');
1568 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1569 }
1570
1571 if ($public_key_format != $server_host_key_algorithm || $this->signature_format != $server_host_key_algorithm) {
1572 user_error('Server Host Key Algorithm Mismatch');
1573 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1574 }
1575
1576 $packet = pack(
1577 'C',
1578 NET_SSH2_MSG_NEWKEYS
1579 );
1580
1581 if (!$this->_send_binary_packet($packet)) {
1582 return false;
1583 }
1584
1585 $response = $this->_get_binary_packet();
1586
1587 if ($response === false) {
1588 user_error('Connection closed by server');
1589 return false;
1590 }
1591
1592 extract(unpack('Ctype', $this->_string_shift($response, 1)));
1593
1594 if ($type != NET_SSH2_MSG_NEWKEYS) {
1595 user_error('Expected SSH_MSG_NEWKEYS');
1596 return false;
1597 }
1598
1599 $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
1600
1601 $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt);
1602 if ($this->encrypt) {
1603 if ($this->crypto_engine) {
1604 $this->encrypt->setEngine($this->crypto_engine);
1605 }
1606 if ($this->encrypt->block_size) {
1607 $this->encrypt_block_size = $this->encrypt->block_size;
1608 }
1609 $this->encrypt->enableContinuousBuffer();
1610 $this->encrypt->disablePadding();
1611
1612 $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
1613 while ($this->encrypt_block_size > strlen($iv)) {
1614 $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
1615 }
1616 $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
1617
1618 $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
1619 while ($encryptKeyLength > strlen($key)) {
1620 $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
1621 }
1622 $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
1623 }
1624
1625 $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt);
1626 if ($this->decrypt) {
1627 if ($this->crypto_engine) {
1628 $this->decrypt->setEngine($this->crypto_engine);
1629 }
1630 if ($this->decrypt->block_size) {
1631 $this->decrypt_block_size = $this->decrypt->block_size;
1632 }
1633 $this->decrypt->enableContinuousBuffer();
1634 $this->decrypt->disablePadding();
1635
1636 $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
1637 while ($this->decrypt_block_size > strlen($iv)) {
1638 $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
1639 }
1640 $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
1641
1642 $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
1643 while ($decryptKeyLength > strlen($key)) {
1644 $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
1645 }
1646 $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
1647 }
1648
1649 /* The "arcfour128" algorithm is the RC4 cipher, as described in
1650 [SCHNEIER], using a 128-bit key. The first 1536 bytes of keystream
1651 generated by the cipher MUST be discarded, and the first byte of the
1652 first encrypted packet MUST be encrypted using the 1537th byte of
1653 keystream.
1654
1655 -- http://tools.ietf.org/html/rfc4345#section-4 */
1656 if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') {
1657 $this->encrypt->encrypt(str_repeat("\0", 1536));
1658 }
1659 if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') {
1660 $this->decrypt->decrypt(str_repeat("\0", 1536));
1661 }
1662
1663 $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_client_to_server);
1664 if ($mac_algorithm === false) {
1665 user_error('No compatible client to server message authentication algorithms found');
1666 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1667 }
1668
1669 $createKeyLength = 0; // ie. $mac_algorithm == 'none'
1670 switch ($mac_algorithm) {
1671 case 'hmac-sha2-256':
1672 $this->hmac_create = new Hash('sha256');
1673 $createKeyLength = 32;
1674 break;
1675 case 'hmac-sha1':
1676 $this->hmac_create = new Hash('sha1');
1677 $createKeyLength = 20;
1678 break;
1679 case 'hmac-sha1-96':
1680 $this->hmac_create = new Hash('sha1-96');
1681 $createKeyLength = 20;
1682 break;
1683 case 'hmac-md5':
1684 $this->hmac_create = new Hash('md5');
1685 $createKeyLength = 16;
1686 break;
1687 case 'hmac-md5-96':
1688 $this->hmac_create = new Hash('md5-96');
1689 $createKeyLength = 16;
1690 }
1691
1692 $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_server_to_client);
1693 if ($mac_algorithm === false) {
1694 user_error('No compatible server to client message authentication algorithms found');
1695 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1696 }
1697
1698 $checkKeyLength = 0;
1699 $this->hmac_size = 0;
1700 switch ($mac_algorithm) {
1701 case 'hmac-sha2-256':
1702 $this->hmac_check = new Hash('sha256');
1703 $checkKeyLength = 32;
1704 $this->hmac_size = 32;
1705 break;
1706 case 'hmac-sha1':
1707 $this->hmac_check = new Hash('sha1');
1708 $checkKeyLength = 20;
1709 $this->hmac_size = 20;
1710 break;
1711 case 'hmac-sha1-96':
1712 $this->hmac_check = new Hash('sha1-96');
1713 $checkKeyLength = 20;
1714 $this->hmac_size = 12;
1715 break;
1716 case 'hmac-md5':
1717 $this->hmac_check = new Hash('md5');
1718 $checkKeyLength = 16;
1719 $this->hmac_size = 16;
1720 break;
1721 case 'hmac-md5-96':
1722 $this->hmac_check = new Hash('md5-96');
1723 $checkKeyLength = 16;
1724 $this->hmac_size = 12;
1725 }
1726
1727 $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
1728 while ($createKeyLength > strlen($key)) {
1729 $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
1730 }
1731 $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
1732
1733 $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
1734 while ($checkKeyLength > strlen($key)) {
1735 $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
1736 }
1737 $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
1738
1739 $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_server_to_client);
1740 if ($compression_algorithm === false) {
1741 user_error('No compatible server to client compression algorithms found');
1742 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1743 }
1744 $this->decompress = $compression_algorithm == 'zlib';
1745
1746 $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_client_to_server);
1747 if ($compression_algorithm === false) {
1748 user_error('No compatible client to server compression algorithms found');
1749 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1750 }
1751 $this->compress = $compression_algorithm == 'zlib';
1752
1753 return true;
1754 }
1755
1764 {
1765 switch ($algorithm) {
1766 case 'none':
1767 return 0;
1768 case 'aes128-cbc':
1769 case 'aes128-ctr':
1770 case 'arcfour':
1771 case 'arcfour128':
1772 case 'blowfish-cbc':
1773 case 'blowfish-ctr':
1774 case 'twofish128-cbc':
1775 case 'twofish128-ctr':
1776 return 16;
1777 case '3des-cbc':
1778 case '3des-ctr':
1779 case 'aes192-cbc':
1780 case 'aes192-ctr':
1781 case 'twofish192-cbc':
1782 case 'twofish192-ctr':
1783 return 24;
1784 case 'aes256-cbc':
1785 case 'aes256-ctr':
1786 case 'arcfour256':
1787 case 'twofish-cbc':
1788 case 'twofish256-cbc':
1789 case 'twofish256-ctr':
1790 return 32;
1791 }
1792 return null;
1793 }
1794
1804 {
1805 switch ($algorithm) {
1806 case '3des-cbc':
1807 return new TripleDES();
1808 case '3des-ctr':
1809 return new TripleDES(Base::MODE_CTR);
1810 case 'aes256-cbc':
1811 case 'aes192-cbc':
1812 case 'aes128-cbc':
1813 return new Rijndael();
1814 case 'aes256-ctr':
1815 case 'aes192-ctr':
1816 case 'aes128-ctr':
1817 return new Rijndael(Base::MODE_CTR);
1818 case 'blowfish-cbc':
1819 return new Blowfish();
1820 case 'blowfish-ctr':
1821 return new Blowfish(Base::MODE_CTR);
1822 case 'twofish128-cbc':
1823 case 'twofish192-cbc':
1824 case 'twofish256-cbc':
1825 case 'twofish-cbc':
1826 return new Twofish();
1827 case 'twofish128-ctr':
1828 case 'twofish192-ctr':
1829 case 'twofish256-ctr':
1830 return new Twofish(Base::MODE_CTR);
1831 case 'arcfour':
1832 case 'arcfour128':
1833 case 'arcfour256':
1834 return new RC4();
1835 }
1836 return null;
1837 }
1838
1851 function login($username)
1852 {
1853 $args = func_get_args();
1854 return call_user_func_array(array(&$this, '_login'), $args);
1855 }
1856
1867 function _login($username)
1868 {
1869 if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
1870 if (!$this->_connect()) {
1871 return false;
1872 }
1873 }
1874
1875 $args = array_slice(func_get_args(), 1);
1876 if (empty($args)) {
1877 return $this->_login_helper($username);
1878 }
1879
1880 foreach ($args as $arg) {
1881 if ($this->_login_helper($username, $arg)) {
1882 return true;
1883 }
1884 }
1885 return false;
1886 }
1887
1898 function _login_helper($username, $password = null)
1899 {
1900 if (!($this->bitmap & self::MASK_CONNECTED)) {
1901 return false;
1902 }
1903
1904 if (!($this->bitmap & self::MASK_LOGIN_REQ)) {
1905 $packet = pack(
1906 'CNa*',
1907 NET_SSH2_MSG_SERVICE_REQUEST,
1908 strlen('ssh-userauth'),
1909 'ssh-userauth'
1910 );
1911
1912 if (!$this->_send_binary_packet($packet)) {
1913 return false;
1914 }
1915
1916 $response = $this->_get_binary_packet();
1917 if ($response === false) {
1918 user_error('Connection closed by server');
1919 return false;
1920 }
1921
1922 extract(unpack('Ctype', $this->_string_shift($response, 1)));
1923
1924 if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) {
1925 user_error('Expected SSH_MSG_SERVICE_ACCEPT');
1926 return false;
1927 }
1928 $this->bitmap |= self::MASK_LOGIN_REQ;
1929 }
1930
1931 if (strlen($this->last_interactive_response)) {
1932 return !is_string($password) && !is_array($password) ? false : $this->_keyboard_interactive_process($password);
1933 }
1934
1935 if ($password instanceof RSA) {
1936 return $this->_privatekey_login($username, $password);
1937 } elseif ($password instanceof Agent) {
1938 return $this->_ssh_agent_login($username, $password);
1939 }
1940
1941 if (is_array($password)) {
1942 if ($this->_keyboard_interactive_login($username, $password)) {
1943 $this->bitmap |= self::MASK_LOGIN;
1944 return true;
1945 }
1946 return false;
1947 }
1948
1949 if (!isset($password)) {
1950 $packet = pack(
1951 'CNa*Na*Na*',
1952 NET_SSH2_MSG_USERAUTH_REQUEST,
1953 strlen($username),
1954 $username,
1955 strlen('ssh-connection'),
1956 'ssh-connection',
1957 strlen('none'),
1958 'none'
1959 );
1960
1961 if (!$this->_send_binary_packet($packet)) {
1962 return false;
1963 }
1964
1965 $response = $this->_get_binary_packet();
1966 if ($response === false) {
1967 user_error('Connection closed by server');
1968 return false;
1969 }
1970
1971 extract(unpack('Ctype', $this->_string_shift($response, 1)));
1972
1973 switch ($type) {
1974 case NET_SSH2_MSG_USERAUTH_SUCCESS:
1975 $this->bitmap |= self::MASK_LOGIN;
1976 return true;
1977 //case NET_SSH2_MSG_USERAUTH_FAILURE:
1978 default:
1979 return false;
1980 }
1981 }
1982
1983 $packet = pack(
1984 'CNa*Na*Na*CNa*',
1985 NET_SSH2_MSG_USERAUTH_REQUEST,
1986 strlen($username),
1987 $username,
1988 strlen('ssh-connection'),
1989 'ssh-connection',
1990 strlen('password'),
1991 'password',
1992 0,
1993 strlen($password),
1994 $password
1995 );
1996
1997 // remove the username and password from the logged packet
1998 if (!defined('NET_SSH2_LOGGING')) {
1999 $logged = null;
2000 } else {
2001 $logged = pack(
2002 'CNa*Na*Na*CNa*',
2003 NET_SSH2_MSG_USERAUTH_REQUEST,
2004 strlen('username'),
2005 'username',
2006 strlen('ssh-connection'),
2007 'ssh-connection',
2008 strlen('password'),
2009 'password',
2010 0,
2011 strlen('password'),
2012 'password'
2013 );
2014 }
2015
2016 if (!$this->_send_binary_packet($packet, $logged)) {
2017 return false;
2018 }
2019
2020 $response = $this->_get_binary_packet();
2021 if ($response === false) {
2022 user_error('Connection closed by server');
2023 return false;
2024 }
2025
2026 extract(unpack('Ctype', $this->_string_shift($response, 1)));
2027
2028 switch ($type) {
2029 case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
2030 if (defined('NET_SSH2_LOGGING')) {
2031 $this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ';
2032 }
2033 extract(unpack('Nlength', $this->_string_shift($response, 4)));
2034 $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . utf8_decode($this->_string_shift($response, $length));
2035 return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
2036 case NET_SSH2_MSG_USERAUTH_FAILURE:
2037 // can we use keyboard-interactive authentication? if not then either the login is bad or the server employees
2038 // multi-factor authentication
2039 extract(unpack('Nlength', $this->_string_shift($response, 4)));
2040 $auth_methods = explode(',', $this->_string_shift($response, $length));
2041 extract(unpack('Cpartial_success', $this->_string_shift($response, 1)));
2042 $partial_success = $partial_success != 0;
2043
2044 if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) {
2045 if ($this->_keyboard_interactive_login($username, $password)) {
2046 $this->bitmap |= self::MASK_LOGIN;
2047 return true;
2048 }
2049 return false;
2050 }
2051 return false;
2052 case NET_SSH2_MSG_USERAUTH_SUCCESS:
2053 $this->bitmap |= self::MASK_LOGIN;
2054 return true;
2055 }
2056
2057 return false;
2058 }
2059
2071 {
2072 $packet = pack(
2073 'CNa*Na*Na*Na*Na*',
2074 NET_SSH2_MSG_USERAUTH_REQUEST,
2075 strlen($username),
2076 $username,
2077 strlen('ssh-connection'),
2078 'ssh-connection',
2079 strlen('keyboard-interactive'),
2080 'keyboard-interactive',
2081 0,
2082 '',
2083 0,
2084 ''
2085 );
2086
2087 if (!$this->_send_binary_packet($packet)) {
2088 return false;
2089 }
2090
2092 }
2093
2102 {
2103 $responses = func_get_args();
2104
2105 if (strlen($this->last_interactive_response)) {
2107 } else {
2108 $orig = $response = $this->_get_binary_packet();
2109 if ($response === false) {
2110 user_error('Connection closed by server');
2111 return false;
2112 }
2113 }
2114
2115 extract(unpack('Ctype', $this->_string_shift($response, 1)));
2116
2117 switch ($type) {
2118 case NET_SSH2_MSG_USERAUTH_INFO_REQUEST:
2119 extract(unpack('Nlength', $this->_string_shift($response, 4)));
2120 $this->_string_shift($response, $length); // name; may be empty
2121 extract(unpack('Nlength', $this->_string_shift($response, 4)));
2122 $this->_string_shift($response, $length); // instruction; may be empty
2123 extract(unpack('Nlength', $this->_string_shift($response, 4)));
2124 $this->_string_shift($response, $length); // language tag; may be empty
2125 extract(unpack('Nnum_prompts', $this->_string_shift($response, 4)));
2126
2127 for ($i = 0; $i < count($responses); $i++) {
2128 if (is_array($responses[$i])) {
2129 foreach ($responses[$i] as $key => $value) {
2130 $this->keyboard_requests_responses[$key] = $value;
2131 }
2132 unset($responses[$i]);
2133 }
2134 }
2135 $responses = array_values($responses);
2136
2137 if (isset($this->keyboard_requests_responses)) {
2138 for ($i = 0; $i < $num_prompts; $i++) {
2139 extract(unpack('Nlength', $this->_string_shift($response, 4)));
2140 // prompt - ie. "Password: "; must not be empty
2141 $prompt = $this->_string_shift($response, $length);
2142 //$echo = $this->_string_shift($response) != chr(0);
2143 foreach ($this->keyboard_requests_responses as $key => $value) {
2144 if (substr($prompt, 0, strlen($key)) == $key) {
2145 $responses[] = $value;
2146 break;
2147 }
2148 }
2149 }
2150 }
2151
2152 // see http://tools.ietf.org/html/rfc4256#section-3.2
2153 if (strlen($this->last_interactive_response)) {
2154 $this->last_interactive_response = '';
2155 } elseif (defined('NET_SSH2_LOGGING')) {
2156 $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
2157 'UNKNOWN',
2158 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
2159 $this->message_number_log[count($this->message_number_log) - 1]
2160 );
2161 }
2162
2163 if (!count($responses) && $num_prompts) {
2164 $this->last_interactive_response = $orig;
2165 return false;
2166 }
2167
2168 /*
2169 After obtaining the requested information from the user, the client
2170 MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message.
2171 */
2172 // see http://tools.ietf.org/html/rfc4256#section-3.4
2173 $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses));
2174 for ($i = 0; $i < count($responses); $i++) {
2175 $packet.= pack('Na*', strlen($responses[$i]), $responses[$i]);
2176 $logged.= pack('Na*', strlen('dummy-answer'), 'dummy-answer');
2177 }
2178
2179 if (!$this->_send_binary_packet($packet, $logged)) {
2180 return false;
2181 }
2182
2183 if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) {
2184 $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
2185 'UNKNOWN',
2186 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE',
2187 $this->message_number_log[count($this->message_number_log) - 1]
2188 );
2189 }
2190
2191 /*
2192 After receiving the response, the server MUST send either an
2193 SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, or another
2194 SSH_MSG_USERAUTH_INFO_REQUEST message.
2195 */
2196 // maybe phpseclib should force close the connection after x request / responses? unless something like that is done
2197 // there could be an infinite loop of request / responses.
2198 return $this->_keyboard_interactive_process();
2199 case NET_SSH2_MSG_USERAUTH_SUCCESS:
2200 return true;
2201 case NET_SSH2_MSG_USERAUTH_FAILURE:
2202 return false;
2203 }
2204
2205 return false;
2206 }
2207
2216 function _ssh_agent_login($username, $agent)
2217 {
2218 $this->agent = $agent;
2219 $keys = $agent->requestIdentities();
2220 foreach ($keys as $key) {
2221 if ($this->_privatekey_login($username, $key)) {
2222 return true;
2223 }
2224 }
2225
2226 return false;
2227 }
2228
2239 function _privatekey_login($username, $privatekey)
2240 {
2241 // see http://tools.ietf.org/html/rfc4253#page-15
2242 $publickey = $privatekey->getPublicKey(RSA::PUBLIC_FORMAT_RAW);
2243 if ($publickey === false) {
2244 return false;
2245 }
2246
2247 $publickey = array(
2248 'e' => $publickey['e']->toBytes(true),
2249 'n' => $publickey['n']->toBytes(true)
2250 );
2251 $publickey = pack(
2252 'Na*Na*Na*',
2253 strlen('ssh-rsa'),
2254 'ssh-rsa',
2255 strlen($publickey['e']),
2256 $publickey['e'],
2257 strlen($publickey['n']),
2258 $publickey['n']
2259 );
2260
2261 $part1 = pack(
2262 'CNa*Na*Na*',
2263 NET_SSH2_MSG_USERAUTH_REQUEST,
2264 strlen($username),
2265 $username,
2266 strlen('ssh-connection'),
2267 'ssh-connection',
2268 strlen('publickey'),
2269 'publickey'
2270 );
2271 $part2 = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey), $publickey);
2272
2273 $packet = $part1 . chr(0) . $part2;
2274 if (!$this->_send_binary_packet($packet)) {
2275 return false;
2276 }
2277
2278 $response = $this->_get_binary_packet();
2279 if ($response === false) {
2280 user_error('Connection closed by server');
2281 return false;
2282 }
2283
2284 extract(unpack('Ctype', $this->_string_shift($response, 1)));
2285
2286 switch ($type) {
2287 case NET_SSH2_MSG_USERAUTH_FAILURE:
2288 extract(unpack('Nlength', $this->_string_shift($response, 4)));
2289 $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE: ' . $this->_string_shift($response, $length);
2290 return false;
2291 case NET_SSH2_MSG_USERAUTH_PK_OK:
2292 // we'll just take it on faith that the public key blob and the public key algorithm name are as
2293 // they should be
2294 if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) {
2295 $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
2296 'UNKNOWN',
2297 'NET_SSH2_MSG_USERAUTH_PK_OK',
2298 $this->message_number_log[count($this->message_number_log) - 1]
2299 );
2300 }
2301 }
2302
2303 $packet = $part1 . chr(1) . $part2;
2304 $privatekey->setSignatureMode(RSA::SIGNATURE_PKCS1);
2305 $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet));
2306 $signature = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($signature), $signature);
2307 $packet.= pack('Na*', strlen($signature), $signature);
2308
2309 if (!$this->_send_binary_packet($packet)) {
2310 return false;
2311 }
2312
2313 $response = $this->_get_binary_packet();
2314 if ($response === false) {
2315 user_error('Connection closed by server');
2316 return false;
2317 }
2318
2319 extract(unpack('Ctype', $this->_string_shift($response, 1)));
2320
2321 switch ($type) {
2322 case NET_SSH2_MSG_USERAUTH_FAILURE:
2323 // either the login is bad or the server employs multi-factor authentication
2324 return false;
2325 case NET_SSH2_MSG_USERAUTH_SUCCESS:
2326 $this->bitmap |= self::MASK_LOGIN;
2327 return true;
2328 }
2329
2330 return false;
2331 }
2332
2343 {
2344 $this->timeout = $this->curTimeout = $timeout;
2345 }
2346
2352 function getStdError()
2353 {
2354 return $this->stdErrorLog;
2355 }
2356
2368 function exec($command, $callback = null)
2369 {
2370 $this->curTimeout = $this->timeout;
2371 $this->is_timeout = false;
2372 $this->stdErrorLog = '';
2373
2374 if (!($this->bitmap & self::MASK_LOGIN)) {
2375 return false;
2376 }
2377
2378 // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
2379 // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but,
2380 // honestly, if you're transfering more than 2GB, you probably shouldn't be using phpseclib, anyway.
2381 // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
2382 $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size;
2383 // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
2384 // uses 0x4000, that's what will be used here, as well.
2385 $packet_size = 0x4000;
2386
2387 $packet = pack(
2388 'CNa*N3',
2389 NET_SSH2_MSG_CHANNEL_OPEN,
2390 strlen('session'),
2391 'session',
2392 self::CHANNEL_EXEC,
2393 $this->window_size_server_to_client[self::CHANNEL_EXEC],
2394 $packet_size
2395 );
2396
2397 if (!$this->_send_binary_packet($packet)) {
2398 return false;
2399 }
2400
2401 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;
2402
2403 $response = $this->_get_channel_packet(self::CHANNEL_EXEC);
2404 if ($response === false) {
2405 return false;
2406 }
2407
2408 if ($this->request_pty === true) {
2409 $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
2410 $packet = pack(
2411 'CNNa*CNa*N5a*',
2412 NET_SSH2_MSG_CHANNEL_REQUEST,
2413 $this->server_channels[self::CHANNEL_EXEC],
2414 strlen('pty-req'),
2415 'pty-req',
2416 1,
2417 strlen('vt100'),
2418 'vt100',
2419 $this->windowColumns,
2420 $this->windowRows,
2421 0,
2422 0,
2423 strlen($terminal_modes),
2425 );
2426
2427 if (!$this->_send_binary_packet($packet)) {
2428 return false;
2429 }
2430
2431 $response = $this->_get_binary_packet();
2432 if ($response === false) {
2433 user_error('Connection closed by server');
2434 return false;
2435 }
2436
2437 list(, $type) = unpack('C', $this->_string_shift($response, 1));
2438
2439 switch ($type) {
2440 case NET_SSH2_MSG_CHANNEL_SUCCESS:
2441 break;
2442 case NET_SSH2_MSG_CHANNEL_FAILURE:
2443 default:
2444 user_error('Unable to request pseudo-terminal');
2445 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
2446 }
2447 $this->in_request_pty_exec = true;
2448 }
2449
2450 // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
2451 // down. the one place where it might be desirable is if you're doing something like \phpseclib\Net\SSH2::exec('ping localhost &').
2452 // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
2453 // then immediately terminate. without such a request exec() will loop indefinitely. the ping process won't end but
2454 // neither will your script.
2455
2456 // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
2457 // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
2458 // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates.
2459 $packet = pack(
2460 'CNNa*CNa*',
2461 NET_SSH2_MSG_CHANNEL_REQUEST,
2462 $this->server_channels[self::CHANNEL_EXEC],
2463 strlen('exec'),
2464 'exec',
2465 1,
2466 strlen($command),
2467 $command
2468 );
2469 if (!$this->_send_binary_packet($packet)) {
2470 return false;
2471 }
2472
2473 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
2474
2475 $response = $this->_get_channel_packet(self::CHANNEL_EXEC);
2476 if ($response === false) {
2477 return false;
2478 }
2479
2480 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
2481
2482 if ($callback === false || $this->in_request_pty_exec) {
2483 return true;
2484 }
2485
2486 $output = '';
2487 while (true) {
2488 $temp = $this->_get_channel_packet(self::CHANNEL_EXEC);
2489 switch (true) {
2490 case $temp === true:
2491 return is_callable($callback) ? true : $output;
2492 case $temp === false:
2493 return false;
2494 default:
2495 if (is_callable($callback)) {
2496 if (call_user_func($callback, $temp) === true) {
2497 $this->_close_channel(self::CHANNEL_EXEC);
2498 return true;
2499 }
2500 } else {
2501 $output.= $temp;
2502 }
2503 }
2504 }
2505 }
2506
2515 function _initShell()
2516 {
2517 if ($this->in_request_pty_exec === true) {
2518 return true;
2519 }
2520
2521 $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size;
2522 $packet_size = 0x4000;
2523
2524 $packet = pack(
2525 'CNa*N3',
2526 NET_SSH2_MSG_CHANNEL_OPEN,
2527 strlen('session'),
2528 'session',
2529 self::CHANNEL_SHELL,
2530 $this->window_size_server_to_client[self::CHANNEL_SHELL],
2531 $packet_size
2532 );
2533
2534 if (!$this->_send_binary_packet($packet)) {
2535 return false;
2536 }
2537
2538 $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;
2539
2540 $response = $this->_get_channel_packet(self::CHANNEL_SHELL);
2541 if ($response === false) {
2542 return false;
2543 }
2544
2545 $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
2546 $packet = pack(
2547 'CNNa*CNa*N5a*',
2548 NET_SSH2_MSG_CHANNEL_REQUEST,
2549 $this->server_channels[self::CHANNEL_SHELL],
2550 strlen('pty-req'),
2551 'pty-req',
2552 1,
2553 strlen('vt100'),
2554 'vt100',
2555 $this->windowColumns,
2556 $this->windowRows,
2557 0,
2558 0,
2559 strlen($terminal_modes),
2561 );
2562
2563 if (!$this->_send_binary_packet($packet)) {
2564 return false;
2565 }
2566
2567 $response = $this->_get_binary_packet();
2568 if ($response === false) {
2569 user_error('Connection closed by server');
2570 return false;
2571 }
2572
2573 list(, $type) = unpack('C', $this->_string_shift($response, 1));
2574
2575 switch ($type) {
2576 case NET_SSH2_MSG_CHANNEL_SUCCESS:
2577 // if a pty can't be opened maybe commands can still be executed
2578 case NET_SSH2_MSG_CHANNEL_FAILURE:
2579 break;
2580 default:
2581 user_error('Unable to request pseudo-terminal');
2582 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
2583 }
2584
2585 $packet = pack(
2586 'CNNa*C',
2587 NET_SSH2_MSG_CHANNEL_REQUEST,
2588 $this->server_channels[self::CHANNEL_SHELL],
2589 strlen('shell'),
2590 'shell',
2591 1
2592 );
2593 if (!$this->_send_binary_packet($packet)) {
2594 return false;
2595 }
2596
2597 $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST;
2598
2599 $response = $this->_get_channel_packet(self::CHANNEL_SHELL);
2600 if ($response === false) {
2601 return false;
2602 }
2603
2604 $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
2605
2606 $this->bitmap |= self::MASK_SHELL;
2607
2608 return true;
2609 }
2610
2620 {
2621 switch (true) {
2622 case $this->in_subsystem:
2624 case $this->in_request_pty_exec:
2625 return self::CHANNEL_EXEC;
2626 default:
2627 return self::CHANNEL_SHELL;
2628 }
2629 }
2630
2638 {
2639 $channel = self::CHANNEL_EXEC;
2640 do {
2641 if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) {
2642 return $channel;
2643 }
2644 } while ($channel++ < self::CHANNEL_SUBSYSTEM);
2645
2646 return false;
2647 }
2648
2661 function read($expect = '', $mode = self::READ_SIMPLE)
2662 {
2663 $this->curTimeout = $this->timeout;
2664 $this->is_timeout = false;
2665
2666 if (!($this->bitmap & self::MASK_LOGIN)) {
2667 user_error('Operation disallowed prior to login()');
2668 return false;
2669 }
2670
2671 if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
2672 user_error('Unable to initiate an interactive shell session');
2673 return false;
2674 }
2675
2676 $channel = $this->_get_interactive_channel();
2677
2678 $match = $expect;
2679 while (true) {
2680 if ($mode == self::READ_REGEX) {
2681 preg_match($expect, substr($this->interactiveBuffer, -1024), $matches);
2682 $match = isset($matches[0]) ? $matches[0] : '';
2683 }
2684 $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false;
2685 if ($pos !== false) {
2686 return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match));
2687 }
2688 $response = $this->_get_channel_packet($channel);
2689 if (is_bool($response)) {
2690 $this->in_request_pty_exec = false;
2691 return $response ? $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)) : false;
2692 }
2693
2694 $this->interactiveBuffer.= $response;
2695 }
2696 }
2697
2706 function write($cmd)
2707 {
2708 if (!($this->bitmap & self::MASK_LOGIN)) {
2709 user_error('Operation disallowed prior to login()');
2710 return false;
2711 }
2712
2713 if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
2714 user_error('Unable to initiate an interactive shell session');
2715 return false;
2716 }
2717
2718 return $this->_send_channel_packet($this->_get_interactive_channel(), $cmd);
2719 }
2720
2735 function startSubsystem($subsystem)
2736 {
2737 $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size;
2738
2739 $packet = pack(
2740 'CNa*N3',
2741 NET_SSH2_MSG_CHANNEL_OPEN,
2742 strlen('session'),
2743 'session',
2744 self::CHANNEL_SUBSYSTEM,
2745 $this->window_size,
2746 0x4000
2747 );
2748
2749 if (!$this->_send_binary_packet($packet)) {
2750 return false;
2751 }
2752
2753 $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;
2754
2755 $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM);
2756 if ($response === false) {
2757 return false;
2758 }
2759
2760 $packet = pack(
2761 'CNNa*CNa*',
2762 NET_SSH2_MSG_CHANNEL_REQUEST,
2763 $this->server_channels[self::CHANNEL_SUBSYSTEM],
2764 strlen('subsystem'),
2765 'subsystem',
2766 1,
2767 strlen($subsystem),
2768 $subsystem
2769 );
2770 if (!$this->_send_binary_packet($packet)) {
2771 return false;
2772 }
2773
2774 $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;
2775
2776 $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM);
2777
2778 if ($response === false) {
2779 return false;
2780 }
2781
2782 $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
2783
2784 $this->bitmap |= self::MASK_SHELL;
2785 $this->in_subsystem = true;
2786
2787 return true;
2788 }
2789
2797 function stopSubsystem()
2798 {
2799 $this->in_subsystem = false;
2800 $this->_close_channel(self::CHANNEL_SUBSYSTEM);
2801 return true;
2802 }
2803
2811 function reset()
2812 {
2814 }
2815
2823 function isTimeout()
2824 {
2825 return $this->is_timeout;
2826 }
2827
2833 function disconnect()
2834 {
2835 $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
2836 if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) {
2837 fclose($this->realtime_log_file);
2838 }
2839 }
2840
2849 function __destruct()
2850 {
2851 $this->disconnect();
2852 }
2853
2860 function isConnected()
2861 {
2862 return (bool) ($this->bitmap & self::MASK_CONNECTED);
2863 }
2864
2872 {
2873 return (bool) ($this->bitmap & self::MASK_LOGIN);
2874 }
2875
2886 {
2887 if (!is_resource($this->fsock) || feof($this->fsock)) {
2888 user_error('Connection closed prematurely');
2889 $this->bitmap = 0;
2890 return false;
2891 }
2892
2893 $start = microtime(true);
2894 $raw = fread($this->fsock, $this->decrypt_block_size);
2895
2896 if (!strlen($raw)) {
2897 return '';
2898 }
2899
2900 if ($this->decrypt !== false) {
2901 $raw = $this->decrypt->decrypt($raw);
2902 }
2903 if ($raw === false) {
2904 user_error('Unable to decrypt content');
2905 return false;
2906 }
2907
2908 extract(unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5)));
2909
2910 $remaining_length = $packet_length + 4 - $this->decrypt_block_size;
2911
2912 // quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
2913 // "implementations SHOULD check that the packet length is reasonable"
2914 // PuTTY uses 0x9000 as the actual max packet size and so to shall we
2915 if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
2916 user_error('Invalid size');
2917 return false;
2918 }
2919
2920 $buffer = '';
2921 while ($remaining_length > 0) {
2922 $temp = fread($this->fsock, $remaining_length);
2923 if ($temp === false || feof($this->fsock)) {
2924 user_error('Error reading from socket');
2925 $this->bitmap = 0;
2926 return false;
2927 }
2928 $buffer.= $temp;
2929 $remaining_length-= strlen($temp);
2930 }
2931 $stop = microtime(true);
2932 if (strlen($buffer)) {
2933 $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer;
2934 }
2935
2936 $payload = $this->_string_shift($raw, $packet_length - $padding_length - 1);
2937 $padding = $this->_string_shift($raw, $padding_length); // should leave $raw empty
2938
2939 if ($this->hmac_check !== false) {
2940 $hmac = fread($this->fsock, $this->hmac_size);
2941 if ($hmac === false || strlen($hmac) != $this->hmac_size) {
2942 user_error('Error reading socket');
2943 $this->bitmap = 0;
2944 return false;
2945 } elseif ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) {
2946 user_error('Invalid HMAC');
2947 return false;
2948 }
2949 }
2950
2951 //if ($this->decompress) {
2952 // $payload = gzinflate(substr($payload, 2));
2953 //}
2954
2955 $this->get_seq_no++;
2956
2957 if (defined('NET_SSH2_LOGGING')) {
2958 $current = microtime(true);
2959 $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
2960 $message_number = '<- ' . $message_number .
2961 ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
2962 $this->_append_log($message_number, $payload);
2963 $this->last_packet = $current;
2964 }
2965
2966 return $this->_filter($payload);
2967 }
2968
2978 function _filter($payload)
2979 {
2980 switch (ord($payload[0])) {
2981 case NET_SSH2_MSG_DISCONNECT:
2982 $this->_string_shift($payload, 1);
2983 extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8)));
2984 $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode($this->_string_shift($payload, $length));
2985 $this->bitmap = 0;
2986 return false;
2987 case NET_SSH2_MSG_IGNORE:
2988 $payload = $this->_get_binary_packet();
2989 break;
2990 case NET_SSH2_MSG_DEBUG:
2991 $this->_string_shift($payload, 2);
2992 extract(unpack('Nlength', $this->_string_shift($payload, 4)));
2993 $this->errors[] = 'SSH_MSG_DEBUG: ' . utf8_decode($this->_string_shift($payload, $length));
2994 $payload = $this->_get_binary_packet();
2995 break;
2996 case NET_SSH2_MSG_UNIMPLEMENTED:
2997 return false;
2998 case NET_SSH2_MSG_KEXINIT:
2999 if ($this->session_id !== false) {
3000 if (!$this->_key_exchange($payload)) {
3001 $this->bitmap = 0;
3002 return false;
3003 }
3004 $payload = $this->_get_binary_packet();
3005 }
3006 }
3007
3008 // 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
3009 if (($this->bitmap & self::MASK_CONNECTED) && !($this->bitmap & self::MASK_LOGIN) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
3010 $this->_string_shift($payload, 1);
3011 extract(unpack('Nlength', $this->_string_shift($payload, 4)));
3012 $this->banner_message = utf8_decode($this->_string_shift($payload, $length));
3013 $payload = $this->_get_binary_packet();
3014 }
3015
3016 // only called when we've already logged in
3017 if (($this->bitmap & self::MASK_CONNECTED) && ($this->bitmap & self::MASK_LOGIN)) {
3018 switch (ord($payload[0])) {
3019 case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
3020 extract(unpack('Nlength', $this->_string_shift($payload, 4)));
3021 $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . $this->_string_shift($payload, $length);
3022
3023 if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) {
3024 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3025 }
3026
3027 $payload = $this->_get_binary_packet();
3028 break;
3029 case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
3030 $this->_string_shift($payload, 1);
3031 extract(unpack('Nlength', $this->_string_shift($payload, 4)));
3032 $data = $this->_string_shift($payload, $length);
3033 extract(unpack('Nserver_channel', $this->_string_shift($payload, 4)));
3034 switch ($data) {
3035 case 'auth-agent':
3036 case 'auth-agent@openssh.com':
3037 if (isset($this->agent)) {
3038 $new_channel = self::CHANNEL_AGENT_FORWARD;
3039
3040 extract(unpack('Nremote_window_size', $this->_string_shift($payload, 4)));
3041 extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($payload, 4)));
3042
3043 $this->packet_size_client_to_server[$new_channel] = $remote_window_size;
3044 $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size;
3045 $this->window_size_client_to_server[$new_channel] = $this->window_size;
3046
3047 $packet_size = 0x4000;
3048
3049 $packet = pack(
3050 'CN4',
3051 NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,
3052 $server_channel,
3053 $new_channel,
3054 $packet_size,
3055 $packet_size
3056 );
3057
3058 $this->server_channels[$new_channel] = $server_channel;
3059 $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
3060 if (!$this->_send_binary_packet($packet)) {
3061 return false;
3062 }
3063 }
3064 break;
3065 default:
3066 $packet = pack(
3067 'CN3a*Na*',
3068 NET_SSH2_MSG_REQUEST_FAILURE,
3069 $server_channel,
3070 NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
3071 0,
3072 '',
3073 0,
3074 ''
3075 );
3076
3077 if (!$this->_send_binary_packet($packet)) {
3078 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3079 }
3080 }
3081 $payload = $this->_get_binary_packet();
3082 break;
3083 case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
3084 $this->_string_shift($payload, 1);
3085 extract(unpack('Nchannel', $this->_string_shift($payload, 4)));
3086 extract(unpack('Nwindow_size', $this->_string_shift($payload, 4)));
3087 $this->window_size_client_to_server[$channel]+= $window_size;
3088
3089 $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->_get_binary_packet();
3090 }
3091 }
3092
3093 return $payload;
3094 }
3095
3104 {
3105 $this->quiet_mode = true;
3106 }
3107
3116 {
3117 $this->quiet_mode = false;
3118 }
3119
3129 {
3130 return $this->quiet_mode;
3131 }
3132
3138 function enablePTY()
3139 {
3140 $this->request_pty = true;
3141 }
3142
3148 function disablePTY()
3149 {
3150 $this->request_pty = false;
3151 }
3152
3161 function isPTYEnabled()
3162 {
3163 return $this->request_pty;
3164 }
3165
3175 function _get_channel_packet($client_channel, $skip_extended = false)
3176 {
3177 if (!empty($this->channel_buffers[$client_channel])) {
3178 return array_shift($this->channel_buffers[$client_channel]);
3179 }
3180
3181 while (true) {
3182 if ($this->curTimeout) {
3183 if ($this->curTimeout < 0) {
3184 $this->is_timeout = true;
3185 return true;
3186 }
3187
3188 $read = array($this->fsock);
3189 $write = $except = null;
3190
3191 $start = microtime(true);
3192 $sec = floor($this->curTimeout);
3193 $usec = 1000000 * ($this->curTimeout - $sec);
3194 // on windows this returns a "Warning: Invalid CRT parameters detected" error
3195 if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
3196 $this->is_timeout = true;
3197 return true;
3198 }
3199 $elapsed = microtime(true) - $start;
3200 $this->curTimeout-= $elapsed;
3201 }
3202
3203 $response = $this->_get_binary_packet();
3204 if ($response === false) {
3205 user_error('Connection closed by server');
3206 return false;
3207 }
3208 if ($client_channel == -1 && $response === true) {
3209 return true;
3210 }
3211 if (!strlen($response)) {
3212 return '';
3213 }
3214
3215 extract(unpack('Ctype', $this->_string_shift($response, 1)));
3216
3217 if ($type == NET_SSH2_MSG_CHANNEL_OPEN) {
3218 extract(unpack('Nlength', $this->_string_shift($response, 4)));
3219 } else {
3220 extract(unpack('Nchannel', $this->_string_shift($response, 4)));
3221 }
3222
3223 // will not be setup yet on incoming channel open request
3224 if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) {
3225 $this->window_size_server_to_client[$channel]-= strlen($response);
3226
3227 // resize the window, if appropriate
3228 if ($this->window_size_server_to_client[$channel] < 0) {
3229 $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_size);
3230 if (!$this->_send_binary_packet($packet)) {
3231 return false;
3232 }
3233 $this->window_size_server_to_client[$channel]+= $this->window_size;
3234 }
3235
3236 switch ($this->channel_status[$channel]) {
3237 case NET_SSH2_MSG_CHANNEL_OPEN:
3238 switch ($type) {
3239 case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
3240 extract(unpack('Nserver_channel', $this->_string_shift($response, 4)));
3241 $this->server_channels[$channel] = $server_channel;
3242 extract(unpack('Nwindow_size', $this->_string_shift($response, 4)));
3243 if ($window_size < 0) {
3244 $window_size&= 0x7FFFFFFF;
3245 $window_size+= 0x80000000;
3246 }
3247 $this->window_size_client_to_server[$channel] = $window_size;
3248 $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4));
3249 $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server'];
3250 $result = $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended);
3251 $this->_on_channel_open();
3252 return $result;
3253 //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
3254 default:
3255 user_error('Unable to open channel');
3256 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3257 }
3258 break;
3259 case NET_SSH2_MSG_CHANNEL_REQUEST:
3260 switch ($type) {
3261 case NET_SSH2_MSG_CHANNEL_SUCCESS:
3262 return true;
3263 case NET_SSH2_MSG_CHANNEL_FAILURE:
3264 return false;
3265 default:
3266 user_error('Unable to fulfill channel request');
3267 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3268 }
3269 case NET_SSH2_MSG_CHANNEL_CLOSE:
3270 return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended);
3271 }
3272 }
3273
3274 // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
3275
3276 switch ($type) {
3277 case NET_SSH2_MSG_CHANNEL_DATA:
3278 /*
3279 if ($channel == self::CHANNEL_EXEC) {
3280 // SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server
3281 // this actually seems to make things twice as fast. more to the point, the message right after
3282 // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise.
3283 // in OpenSSH it slows things down but only by a couple thousandths of a second.
3284 $this->_send_channel_packet($channel, chr(0));
3285 }
3286 */
3287 extract(unpack('Nlength', $this->_string_shift($response, 4)));
3288 $data = $this->_string_shift($response, $length);
3289
3290 if ($channel == self::CHANNEL_AGENT_FORWARD) {
3291 $agent_response = $this->agent->_forward_data($data);
3292 if (!is_bool($agent_response)) {
3293 $this->_send_channel_packet($channel, $agent_response);
3294 }
3295 break;
3296 }
3297
3298 if ($client_channel == $channel) {
3299 return $data;
3300 }
3301 if (!isset($this->channel_buffers[$channel])) {
3302 $this->channel_buffers[$channel] = array();
3303 }
3304 $this->channel_buffers[$channel][] = $data;
3305 break;
3306 case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
3307 /*
3308 if ($client_channel == self::CHANNEL_EXEC) {
3309 $this->_send_channel_packet($client_channel, chr(0));
3310 }
3311 */
3312 // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
3313 extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8)));
3314 $data = $this->_string_shift($response, $length);
3315 $this->stdErrorLog.= $data;
3316 if ($skip_extended || $this->quiet_mode) {
3317 break;
3318 }
3319 if ($client_channel == $channel) {
3320 return $data;
3321 }
3322 if (!isset($this->channel_buffers[$channel])) {
3323 $this->channel_buffers[$channel] = array();
3324 }
3325 $this->channel_buffers[$channel][] = $data;
3326 break;
3327 case NET_SSH2_MSG_CHANNEL_REQUEST:
3328 extract(unpack('Nlength', $this->_string_shift($response, 4)));
3329 $value = $this->_string_shift($response, $length);
3330 switch ($value) {
3331 case 'exit-signal':
3332 $this->_string_shift($response, 1);
3333 extract(unpack('Nlength', $this->_string_shift($response, 4)));
3334 $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length);
3335 $this->_string_shift($response, 1);
3336 extract(unpack('Nlength', $this->_string_shift($response, 4)));
3337 if ($length) {
3338 $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length);
3339 }
3340
3341 $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
3342 $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
3343
3344 $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF;
3345
3346 break;
3347 case 'exit-status':
3348 extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5)));
3349 $this->exit_status = $exit_status;
3350
3351 // "The client MAY ignore these messages."
3352 // -- http://tools.ietf.org/html/rfc4254#section-6.10
3353
3354 break;
3355 default:
3356 // "Some systems may not implement signals, in which case they SHOULD ignore this message."
3357 // -- http://tools.ietf.org/html/rfc4254#section-6.9
3358 break;
3359 }
3360 break;
3361 case NET_SSH2_MSG_CHANNEL_CLOSE:
3362 $this->curTimeout = 0;
3363
3364 if ($this->bitmap & self::MASK_SHELL) {
3365 $this->bitmap&= ~self::MASK_SHELL;
3366 }
3367 if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
3368 $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
3369 }
3370
3371 $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
3372 return true;
3373 case NET_SSH2_MSG_CHANNEL_EOF:
3374 break;
3375 default:
3376 user_error('Error reading channel data');
3377 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3378 }
3379 }
3380 }
3381
3393 function _send_binary_packet($data, $logged = null)
3394 {
3395 if (!is_resource($this->fsock) || feof($this->fsock)) {
3396 user_error('Connection closed prematurely');
3397 $this->bitmap = 0;
3398 return false;
3399 }
3400
3401 //if ($this->compress) {
3402 // // the -4 removes the checksum:
3403 // // http://php.net/function.gzcompress#57710
3404 // $data = substr(gzcompress($data), 0, -4);
3405 //}
3406
3407 // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
3408 $packet_length = strlen($data) + 9;
3409 // round up to the nearest $this->encrypt_block_size
3410 $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
3411 // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
3412 $padding_length = $packet_length - strlen($data) - 5;
3413 $padding = Random::string($padding_length);
3414
3415 // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
3416 $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);
3417
3418 $hmac = $this->hmac_create !== false ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : '';
3419 $this->send_seq_no++;
3420
3421 if ($this->encrypt !== false) {
3422 $packet = $this->encrypt->encrypt($packet);
3423 }
3424
3425 $packet.= $hmac;
3426
3427 $start = microtime(true);
3428 $result = strlen($packet) == fputs($this->fsock, $packet);
3429 $stop = microtime(true);
3430
3431 if (defined('NET_SSH2_LOGGING')) {
3432 $current = microtime(true);
3433 $message_number = isset($this->message_numbers[ord($data[0])]) ? $this->message_numbers[ord($data[0])] : 'UNKNOWN (' . ord($data[0]) . ')';
3434 $message_number = '-> ' . $message_number .
3435 ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
3436 $this->_append_log($message_number, isset($logged) ? $logged : $data);
3437 $this->last_packet = $current;
3438 }
3439
3440 return $result;
3441 }
3442
3451 function _append_log($message_number, $message)
3452 {
3453 // remove the byte identifying the message type from all but the first two messages (ie. the identification strings)
3454 if (strlen($message_number) > 2) {
3455 $this->_string_shift($message);
3456 }
3457
3458 switch (NET_SSH2_LOGGING) {
3459 // useful for benchmarks
3460 case self::LOG_SIMPLE:
3461 $this->message_number_log[] = $message_number;
3462 break;
3463 // the most useful log for SSH2
3464 case self::LOG_COMPLEX:
3465 $this->message_number_log[] = $message_number;
3466 $this->log_size+= strlen($message);
3467 $this->message_log[] = $message;
3468 while ($this->log_size > self::LOG_MAX_SIZE) {
3469 $this->log_size-= strlen(array_shift($this->message_log));
3470 array_shift($this->message_number_log);
3471 }
3472 break;
3473 // dump the output out realtime; packets may be interspersed with non packets,
3474 // passwords won't be filtered out and select other packets may not be correctly
3475 // identified
3476 case self::LOG_REALTIME:
3477 switch (PHP_SAPI) {
3478 case 'cli':
3479 $start = $stop = "\r\n";
3480 break;
3481 default:
3482 $start = '<pre>';
3483 $stop = '</pre>';
3484 }
3485 echo $start . $this->_format_log(array($message), array($message_number)) . $stop;
3486 @flush();
3487 @ob_flush();
3488 break;
3489 // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE
3490 // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
3491 // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
3492 // at the beginning of the file
3494 if (!isset($this->realtime_log_file)) {
3495 // PHP doesn't seem to like using constants in fopen()
3496 $filename = self::LOG_REALTIME_FILENAME;
3497 $fp = fopen($filename, 'w');
3498 $this->realtime_log_file = $fp;
3499 }
3500 if (!is_resource($this->realtime_log_file)) {
3501 break;
3502 }
3503 $entry = $this->_format_log(array($message), array($message_number));
3504 if ($this->realtime_log_wrap) {
3505 $temp = "<<< START >>>\r\n";
3506 $entry.= $temp;
3507 fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp));
3508 }
3509 $this->realtime_log_size+= strlen($entry);
3510 if ($this->realtime_log_size > self::LOG_MAX_SIZE) {
3511 fseek($this->realtime_log_file, 0);
3512 $this->realtime_log_size = strlen($entry);
3513 $this->realtime_log_wrap = true;
3514 }
3515 fputs($this->realtime_log_file, $entry);
3516 }
3517 }
3518
3529 function _send_channel_packet($client_channel, $data)
3530 {
3531 while (strlen($data)) {
3532 if (!$this->window_size_client_to_server[$client_channel]) {
3533 $this->bitmap^= self::MASK_WINDOW_ADJUST;
3534 // using an invalid channel will let the buffers be built up for the valid channels
3535 $this->_get_channel_packet(-1);
3536 $this->bitmap^= self::MASK_WINDOW_ADJUST;
3537 }
3538
3539 /* The maximum amount of data allowed is determined by the maximum
3540 packet size for the channel, and the current window size, whichever
3541 is smaller.
3542 -- http://tools.ietf.org/html/rfc4254#section-5.2 */
3543 $max_size = min(
3544 $this->packet_size_client_to_server[$client_channel],
3545 $this->window_size_client_to_server[$client_channel]
3546 );
3547
3548 $temp = $this->_string_shift($data, $max_size);
3549 $packet = pack(
3550 'CN2a*',
3551 NET_SSH2_MSG_CHANNEL_DATA,
3552 $this->server_channels[$client_channel],
3553 strlen($temp),
3554 $temp
3555 );
3556 $this->window_size_client_to_server[$client_channel]-= strlen($temp);
3557 if (!$this->_send_binary_packet($packet)) {
3558 return false;
3559 }
3560 }
3561
3562 return true;
3563 }
3564
3577 function _close_channel($client_channel, $want_reply = false)
3578 {
3579 // see http://tools.ietf.org/html/rfc4254#section-5.3
3580
3581 $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
3582
3583 if (!$want_reply) {
3584 $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
3585 }
3586
3587 $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
3588
3589 $this->curTimeout = 0;
3590
3591 while (!is_bool($this->_get_channel_packet($client_channel))) {
3592 }
3593
3594 if ($want_reply) {
3595 $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
3596 }
3597
3598 if ($this->bitmap & self::MASK_SHELL) {
3599 $this->bitmap&= ~self::MASK_SHELL;
3600 }
3601 }
3602
3610 function _disconnect($reason)
3611 {
3612 if ($this->bitmap & self::MASK_CONNECTED) {
3613 $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, '');
3614 $this->_send_binary_packet($data);
3615 $this->bitmap = 0;
3616 fclose($this->fsock);
3617 return false;
3618 }
3619 }
3620
3631 function _string_shift(&$string, $index = 1)
3632 {
3633 $substr = substr($string, 0, $index);
3634 $string = substr($string, $index);
3635 return $substr;
3636 }
3637
3648 function _define_array()
3649 {
3650 $args = func_get_args();
3651 foreach ($args as $arg) {
3652 foreach ($arg as $key => $value) {
3653 if (!defined($value)) {
3654 define($value, $key);
3655 } else {
3656 break 2;
3657 }
3658 }
3659 }
3660 }
3661
3670 function getLog()
3671 {
3672 if (!defined('NET_SSH2_LOGGING')) {
3673 return false;
3674 }
3675
3676 switch (NET_SSH2_LOGGING) {
3677 case self::LOG_SIMPLE:
3679 break;
3680 case self::LOG_COMPLEX:
3681 return $this->_format_log($this->message_log, $this->message_number_log);
3682 break;
3683 default:
3684 return false;
3685 }
3686 }
3687
3697 {
3698 $output = '';
3699 for ($i = 0; $i < count($message_log); $i++) {
3700 $output.= $message_number_log[$i] . "\r\n";
3701 $current_log = $message_log[$i];
3702 $j = 0;
3703 do {
3704 if (strlen($current_log)) {
3705 $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 ';
3706 }
3707 $fragment = $this->_string_shift($current_log, $this->log_short_width);
3708 $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary));
3709 // replace non ASCII printable characters with dots
3710 // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
3711 // also replace < with a . since < messes up the output on web browsers
3712 $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
3713 $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
3714 $j++;
3715 } while (strlen($current_log));
3716 $output.= "\r\n";
3717 }
3718
3719 return $output;
3720 }
3721
3731 function _format_log_helper($matches)
3732 {
3733 return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
3734 }
3735
3746 {
3747 if (isset($this->agent)) {
3748 $this->agent->_on_channel_open($this);
3749 }
3750 }
3751
3761 function _array_intersect_first($array1, $array2)
3762 {
3763 foreach ($array1 as $value) {
3764 if (in_array($value, $array2)) {
3765 return $value;
3766 }
3767 }
3768 return false;
3769 }
3770
3777 function getErrors()
3778 {
3779 return $this->errors;
3780 }
3781
3788 function getLastError()
3789 {
3790 $count = count($this->errors);
3791
3792 if ($count > 0) {
3793 return $this->errors[$count - 1];
3794 }
3795 }
3796
3804 {
3805 $this->_connect();
3806
3808 }
3809
3817 {
3818 $this->_connect();
3819
3820 return $this->kex_algorithms;
3821 }
3822
3830 {
3831 $this->_connect();
3832
3834 }
3835
3843 {
3844 $this->_connect();
3845
3847 }
3848
3856 {
3857 $this->_connect();
3858
3860 }
3861
3869 {
3870 $this->_connect();
3871
3873 }
3874
3882 {
3883 $this->_connect();
3884
3886 }
3887
3895 {
3896 $this->_connect();
3897
3899 }
3900
3908 {
3909 $this->_connect();
3910
3912 }
3913
3921 {
3922 $this->_connect();
3923
3925 }
3926
3934 {
3935 $this->_connect();
3936
3938 }
3939
3950 {
3951 return $this->banner_message;
3952 }
3953
3964 {
3965 if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
3966 if (!$this->_connect()) {
3967 return false;
3968 }
3969 }
3970
3971 $signature = $this->signature;
3973
3974 extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4)));
3975 $this->_string_shift($server_public_host_key, $length);
3976
3977 if ($this->signature_validated) {
3978 return $this->bitmap ?
3979 $this->signature_format . ' ' . base64_encode($this->server_public_host_key) :
3980 false;
3981 }
3982
3983 $this->signature_validated = true;
3984
3985 switch ($this->signature_format) {
3986 case 'ssh-dss':
3987 $zero = new BigInteger();
3988
3989 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
3990 $p = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
3991
3992 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
3993 $q = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
3994
3995 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
3996 $g = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
3997
3998 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
3999 $y = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
4000
4001 /* The value for 'dss_signature_blob' is encoded as a string containing
4002 r, followed by s (which are 160-bit integers, without lengths or
4003 padding, unsigned, and in network byte order). */
4004 $temp = unpack('Nlength', $this->_string_shift($signature, 4));
4005 if ($temp['length'] != 40) {
4006 user_error('Invalid signature');
4007 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
4008 }
4009
4010 $r = new BigInteger($this->_string_shift($signature, 20), 256);
4011 $s = new BigInteger($this->_string_shift($signature, 20), 256);
4012
4013 switch (true) {
4014 case $r->equals($zero):
4015 case $r->compare($q) >= 0:
4016 case $s->equals($zero):
4017 case $s->compare($q) >= 0:
4018 user_error('Invalid signature');
4019 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
4020 }
4021
4022 $w = $s->modInverse($q);
4023
4024 $u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 16));
4025 list(, $u1) = $u1->divide($q);
4026
4027 $u2 = $w->multiply($r);
4028 list(, $u2) = $u2->divide($q);
4029
4030 $g = $g->modPow($u1, $p);
4031 $y = $y->modPow($u2, $p);
4032
4033 $v = $g->multiply($y);
4034 list(, $v) = $v->divide($p);
4035 list(, $v) = $v->divide($q);
4036
4037 if (!$v->equals($r)) {
4038 user_error('Bad server signature');
4039 return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
4040 }
4041
4042 break;
4043 case 'ssh-rsa':
4044 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
4045 $e = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
4046
4047 $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
4048 $rawN = $this->_string_shift($server_public_host_key, $temp['length']);
4049 $n = new BigInteger($rawN, -256);
4050 $nLength = strlen(ltrim($rawN, "\0"));
4051
4052 /*
4053 $temp = unpack('Nlength', $this->_string_shift($signature, 4));
4054 $signature = $this->_string_shift($signature, $temp['length']);
4055
4056 $rsa = new RSA();
4057 $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
4058 $rsa->loadKey(array('e' => $e, 'n' => $n), RSA::PUBLIC_FORMAT_RAW);
4059 if (!$rsa->verify($this->exchange_hash, $signature)) {
4060 user_error('Bad server signature');
4061 return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
4062 }
4063 */
4064
4065 $temp = unpack('Nlength', $this->_string_shift($signature, 4));
4066 $s = new BigInteger($this->_string_shift($signature, $temp['length']), 256);
4067
4068 // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the
4069 // following URL:
4070 // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf
4071
4072 // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source.
4073
4074 if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) {
4075 user_error('Invalid signature');
4076 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
4077 }
4078
4079 $s = $s->modPow($e, $n);
4080 $s = $s->toBytes();
4081
4082 $h = pack('N4H*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, sha1($this->exchange_hash));
4083 $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h;
4084
4085 if ($s != $h) {
4086 user_error('Bad server signature');
4087 return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
4088 }
4089 break;
4090 default:
4091 user_error('Unsupported signature format');
4092 return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
4093 }
4094
4095 return $this->signature_format . ' ' . base64_encode($this->server_public_host_key);
4096 }
4097
4104 function getExitStatus()
4105 {
4106 if (is_null($this->exit_status)) {
4107 return false;
4108 }
4109 return $this->exit_status;
4110 }
4111
4119 {
4120 return $this->windowColumns;
4121 }
4122
4129 function getWindowRows()
4130 {
4131 return $this->windowRows;
4132 }
4133
4140 function setWindowColumns($value)
4141 {
4142 $this->windowColumns = $value;
4143 }
4144
4151 function setWindowRows($value)
4152 {
4153 $this->windowRows = $value;
4154 }
4155
4163 function setWindowSize($columns = 80, $rows = 24)
4164 {
4165 $this->windowColumns = $columns;
4166 $this->windowRows = $rows;
4167 }
4168}
$result
$n
Definition: RandomTest.php:85
if(! $in) $columns
Definition: Utf8Test.php:45
$filename
Definition: buildRTE.php:89
An exception for terminatinating execution or to throw for unit testing.
const MODE_CTR
#+ @access public
Definition: Base.php:62
const PUBLIC_FORMAT_RAW
#-
Definition: RSA.php:212
const SIGNATURE_PKCS1
Use the PKCS#1 scheme by default.
Definition: RSA.php:124
static string($length)
Generate a random string.
Definition: Random.php:54
$quiet_mode
Flag to suppress stderr from output.
Definition: SSH2.php:691
_login($username)
Login Helper.
Definition: SSH2.php:1867
_encryption_algorithm_to_crypt_instance($algorithm)
Maps an encryption algorithm name to an instance of a subclass of \phpseclib\Crypt\Base.
Definition: SSH2.php:1803
read($expect='', $mode=self::READ_SIMPLE)
Returns the output of an interactive shell.
Definition: SSH2.php:2661
getServerPublicHostKey()
Returns the server public host key.
Definition: SSH2.php:3963
_get_channel_packet($client_channel, $skip_extended=false)
Gets channel data.
Definition: SSH2.php:3175
$channel_extended_data_type_codes
Definition: SSH2.php:468
_connect()
Connect to an SSHv2 server.
Definition: SSH2.php:994
_format_log_helper($matches)
Helper function for _format_log.
Definition: SSH2.php:3731
getLog()
Returns a log of the packets that have been sent and received.
Definition: SSH2.php:3670
setWindowColumns($value)
Sets the number of columns for the terminal window size.
Definition: SSH2.php:4140
const CHANNEL_SUBSYSTEM
Definition: SSH2.php:105
isQuietModeEnabled()
Returns whether Quiet Mode is enabled or not.
Definition: SSH2.php:3128
const MASK_LOGIN
Definition: SSH2.php:82
_send_channel_packet($client_channel, $data)
Sends channel data.
Definition: SSH2.php:3529
disablePTY()
Disable request-pty when using exec()
Definition: SSH2.php:3148
$mac_algorithms_server_to_client
Definition: SSH2.php:274
const READ_SIMPLE
#-
Definition: SSH2.php:138
isConnected()
Is the connection still active?
Definition: SSH2.php:2860
$languages_client_to_server
Definition: SSH2.php:310
setWindowRows($value)
Sets the number of rows for the terminal window size.
Definition: SSH2.php:4151
const CHANNEL_AGENT_FORWARD
Definition: SSH2.php:106
isPTYEnabled()
Returns whether request-pty is enabled or not.
Definition: SSH2.php:3161
write($cmd)
Inputs a command into an interactive shell.
Definition: SSH2.php:2706
_string_shift(&$string, $index=1)
String Shift.
Definition: SSH2.php:3631
$server_host_key_algorithms
Definition: SSH2.php:238
getErrors()
Returns all errors.
Definition: SSH2.php:3777
setCryptoEngine($engine)
Set Crypto Engine Mode.
Definition: SSH2.php:983
isTimeout()
Is timeout?
Definition: SSH2.php:2823
getExitStatus()
Returns the exit status of an SSH command or false.
Definition: SSH2.php:4104
$window_size_client_to_server
Definition: SSH2.php:589
__construct($host, $port=22, $timeout=10)
Default Constructor.
Definition: SSH2.php:881
$languages_server_to_client
Definition: SSH2.php:301
getStdError()
Get the output from stdError.
Definition: SSH2.php:2352
getWindowColumns()
Returns the number of columns for the terminal window size.
Definition: SSH2.php:4118
const CHANNEL_EXEC
#-
Definition: SSH2.php:103
getWindowRows()
Returns the number of rows for the terminal window size.
Definition: SSH2.php:4129
_close_channel($client_channel, $want_reply=false)
Closes and flushes a channel.
Definition: SSH2.php:3577
enableQuietMode()
Enable Quiet Mode.
Definition: SSH2.php:3103
getKexAlgorithms()
Return a list of the key exchange algorithms the server supports.
Definition: SSH2.php:3816
$last_interactive_response
Definition: SSH2.php:749
getEncryptionAlgorithmsClient2Server()
Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff ...
Definition: SSH2.php:3842
const LOG_COMPLEX
Returns the message content.
Definition: SSH2.php:120
getEncryptionAlgorithmsServer2Client()
Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to...
Definition: SSH2.php:3855
const MASK_WINDOW_ADJUST
Definition: SSH2.php:84
$compression_algorithms_server_to_client
Definition: SSH2.php:292
enablePTY()
Enable request-pty when using exec()
Definition: SSH2.php:3138
setWindowSize($columns=80, $rows=24)
Sets the number of columns and rows for the terminal window size.
Definition: SSH2.php:4163
getServerIdentification()
Return the server identification.
Definition: SSH2.php:3803
$encryption_algorithms_client_to_server
Definition: SSH2.php:247
$keyboard_requests_responses
Definition: SSH2.php:758
getMACAlgorithmsServer2Client()
Return a list of the MAC algorithms the server supports, when sending stuff to the client.
Definition: SSH2.php:3881
getCompressionAlgorithmsServer2Client()
Return a list of the compression algorithms the server supports, when sending stuff to the client.
Definition: SSH2.php:3907
const LOG_MAX_SIZE
Make sure that the log never gets larger than this.
Definition: SSH2.php:146
$window_size_server_to_client
Definition: SSH2.php:578
__destruct()
Destructor.
Definition: SSH2.php:2849
_disconnect($reason)
Disconnect.
Definition: SSH2.php:3610
getMACAlgorithmsClient2Server()
Return a list of the MAC algorithms the server supports, when receiving stuff from the client.
Definition: SSH2.php:3868
_get_open_channel()
Return an available open channel.
Definition: SSH2.php:2637
_define_array()
Define Array.
Definition: SSH2.php:3648
_ssh_agent_login($username, $agent)
Login with an ssh-agent provided key.
Definition: SSH2.php:2216
_initShell()
Creates an interactive shell.
Definition: SSH2.php:2515
_keyboard_interactive_process()
Handle the keyboard-interactive requests / responses.
Definition: SSH2.php:2101
isAuthenticated()
Have you successfully been logged in?
Definition: SSH2.php:2871
getServerHostKeyAlgorithms()
Return a list of the host key (public key) algorithms the server supports.
Definition: SSH2.php:3829
getCompressionAlgorithmsClient2Server()
Return a list of the compression algorithms the server supports, when receiving stuff from the client...
Definition: SSH2.php:3894
setTimeout($timeout)
Set Timeout.
Definition: SSH2.php:2342
$compression_algorithms_client_to_server
Definition: SSH2.php:283
getLanguagesClient2Server()
Return a list of the languages the server supports, when receiving stuff from the client.
Definition: SSH2.php:3933
$packet_size_client_to_server
Definition: SSH2.php:537
_keyboard_interactive_login($username, $password)
Login via keyboard-interactive authentication.
Definition: SSH2.php:2070
_encryption_algorithm_to_key_size($algorithm)
Maps an encryption algorithm name to the number of key bytes.
Definition: SSH2.php:1763
startSubsystem($subsystem)
Start a subsystem.
Definition: SSH2.php:2735
$mac_algorithms_client_to_server
Definition: SSH2.php:265
_get_binary_packet()
Gets Binary Packets.
Definition: SSH2.php:2885
stopSubsystem()
Stops a subsystem.
Definition: SSH2.php:2797
_login_helper($username, $password=null)
Login Helper.
Definition: SSH2.php:1898
const MASK_CONNECTED
Definition: SSH2.php:80
getBannerMessage()
Returns the banner message.
Definition: SSH2.php:3949
reset()
Closes a channel.
Definition: SSH2.php:2811
_filter($payload)
Filter Binary Packets.
Definition: SSH2.php:2978
disconnect()
Disconnect.
Definition: SSH2.php:2833
const LOG_SIMPLE
#-
Definition: SSH2.php:116
$curTimeout
Current Timeout.
Definition: SSH2.php:648
getLanguagesServer2Client()
Return a list of the languages the server supports, when sending stuff to the client.
Definition: SSH2.php:3920
_on_channel_open()
Helper function for agent->_on_channel_open()
Definition: SSH2.php:3745
const MASK_CONSTRUCTOR
#+ Execution Bitmap Masks
Definition: SSH2.php:79
const CHANNEL_SHELL
Definition: SSH2.php:104
_privatekey_login($username, $privatekey)
Login with an RSA private key.
Definition: SSH2.php:2239
exec($command, $callback=null)
Execute Command.
Definition: SSH2.php:2368
const MASK_LOGIN_REQ
Definition: SSH2.php:81
getLastError()
Returns the last error.
Definition: SSH2.php:3788
$channel_open_failure_reasons
Definition: SSH2.php:448
_key_exchange($kexinit_payload_server)
Key Exchange.
Definition: SSH2.php:1148
_get_interactive_channel()
Return the channel to be used with read() / write()
Definition: SSH2.php:2619
const MASK_SHELL
Definition: SSH2.php:83
$kex_dh_group_size_preferred
Definition: SSH2.php:220
_array_intersect_first($array1, $array2)
Returns the first value of the intersection of two arrays or false if the intersection is empty.
Definition: SSH2.php:3761
_append_log($message_number, $message)
Logs data packets.
Definition: SSH2.php:3451
_generate_identifier()
Generates the SSH identifier.
Definition: SSH2.php:1114
_send_binary_packet($data, $logged=null)
Sends Binary Packets.
Definition: SSH2.php:3393
$timeout
Timeout.
Definition: SSH2.php:640
$encryption_algorithms_server_to_client
Definition: SSH2.php:256
disableQuietMode()
Disable Quiet Mode.
Definition: SSH2.php:3115
const READ_REGEX
Returns when a string matching the regular expression $expect is found.
Definition: SSH2.php:142
const LOG_REALTIME
Outputs the content real-time.
Definition: SSH2.php:124
$realtime_log_wrap
Real-time log file wrap boolean.
Definition: SSH2.php:683
login($username)
Login.
Definition: SSH2.php:1851
_format_log($message_log, $message_number_log)
Formats a log for printing.
Definition: SSH2.php:3696
const LOG_REALTIME_FILE
Dumps the content real-time to a file.
Definition: SSH2.php:128
$x
Definition: complexTest.php:9
$password
Definition: cron.php:14
$key
Definition: croninfo.php:18
$i
Definition: disco.tpl.php:19
$y
Definition: example_007.php:83
$h
$w
$r
Definition: example_031.php:79
$index
Definition: metadata.php:60
catch(Exception $e) $message
$keys
Pure-PHP arbitrary precision integer arithmetic library.
Pure-PHP implementation of Blowfish.
Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic...
Pure-PHP implementation of RC4.
Pure-PHP PKCS#1 compliant implementation of RSA.
Pure-PHP implementation of Rijndael.
Pure-PHP implementation of SSHv2.
Pure-PHP implementation of Triple DES.
Pure-PHP implementation of Twofish.
$type
$response
$s
Definition: pwgen.php:45
$start
Definition: bench.php:8
$data
Definition: bench.php:6
$engine
Definition: workflow.php:89
$rows
Definition: xhr_table.php:10