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',
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' 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' 931 $this->channel_open_failure_reasons = array(
932 1 =>
'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED' 934 $this->terminal_modes = array(
935 0 =>
'NET_SSH2_TTY_OP_END' 937 $this->channel_extended_data_type_codes = array(
938 1 =>
'NET_SSH2_EXTENDED_DATA_STDERR' 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'),
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'),
958 array(30 =>
'NET_SSH2_MSG_KEX_ECDH_INIT',
959 31 =>
'NET_SSH2_MSG_KEX_ECDH_REPLY')
962 if (is_resource($host)) {
963 $this->fsock =
$host;
967 if (is_string($host)) {
985 $this->crypto_engine =
$engine;
996 if ($this->bitmap & self::MASK_CONSTRUCTOR) {
1000 $this->bitmap |= self::MASK_CONSTRUCTOR;
1004 $this->last_packet = microtime(
true);
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) {
1011 user_error(rtrim(
"Cannot connect to $host. Error $errno. $errstr"));
1014 $elapsed = microtime(
true) -
$start;
1016 $this->curTimeout-= $elapsed;
1018 if ($this->curTimeout <= 0) {
1019 $this->is_timeout =
true;
1033 while (!feof($this->fsock) && !preg_match(
'#^SSH-(\d\.\d+)#', $temp, $matches)) {
1034 if (substr($temp, -2) ==
"\r\n") {
1039 if ($this->curTimeout) {
1040 if ($this->curTimeout < 0) {
1041 $this->is_timeout =
true;
1044 $read = array($this->fsock);
1045 $write = $except = null;
1046 $start = microtime(
true);
1047 $sec = floor($this->curTimeout);
1048 $usec = 1000000 * ($this->curTimeout - $sec);
1051 if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
1052 $this->is_timeout =
true;
1055 $elapsed = microtime(
true) -
$start;
1056 $this->curTimeout-= $elapsed;
1059 $temp.= fgets($this->fsock, 255);
1062 if (feof($this->fsock)) {
1063 user_error(
'Connection closed by server');
1069 if (defined(
'NET_SSH2_LOGGING')) {
1071 $this->
_append_log(
'->', $this->identifier .
"\r\n");
1074 $this->server_identifier = trim($temp,
"\r\n");
1075 if (strlen($extra)) {
1076 $this->errors[] = utf8_decode($extra);
1079 if ($matches[1] !=
'1.99' && $matches[1] !=
'2.0') {
1080 user_error(
"Cannot connect to SSH $matches[1] servers");
1084 fputs($this->fsock, $this->identifier .
"\r\n");
1088 user_error(
'Connection closed by server');
1092 if (ord(
$response[0]) != NET_SSH2_MSG_KEXINIT) {
1093 user_error(
'Expected SSH_MSG_KEXINIT');
1101 $this->bitmap|= self::MASK_CONNECTED;
1116 $identifier =
'SSH-2.0-phpseclib_2.0';
1119 if (extension_loaded(
'libsodium')) {
1120 $ext[] =
'libsodium';
1123 if (extension_loaded(
'openssl')) {
1125 } elseif (extension_loaded(
'mcrypt')) {
1129 if (extension_loaded(
'gmp')) {
1131 } elseif (extension_loaded(
'bcmath')) {
1136 $identifier .=
' (' . implode(
', ', $ext) .
')';
1150 $kex_algorithms = array(
1154 'curve25519-sha256@libssh.org',
1158 'diffie-hellman-group1-sha1',
1159 'diffie-hellman-group14-sha1',
1160 'diffie-hellman-group-exchange-sha1',
1161 'diffie-hellman-group-exchange-sha256',
1163 if (!class_exists(
'\Sodium')) {
1164 $kex_algorithms = array_diff(
1166 array(
'curve25519-sha256@libssh.org')
1170 $server_host_key_algorithms = array(
1175 $encryption_algorithms = array(
1211 if (extension_loaded(
'openssl') && !extension_loaded(
'mcrypt')) {
1214 $encryption_algorithms = array_diff(
1215 $encryption_algorithms,
1216 array(
'arcfour256',
'arcfour128',
'arcfour')
1220 if (class_exists(
'\phpseclib\Crypt\RC4') ===
false) {
1221 $encryption_algorithms = array_diff(
1222 $encryption_algorithms,
1223 array(
'arcfour256',
'arcfour128',
'arcfour')
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')
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')
1238 if (class_exists(
'\phpseclib\Crypt\Blowfish') ===
false) {
1239 $encryption_algorithms = array_diff(
1240 $encryption_algorithms,
1241 array(
'blowfish-ctr',
'blowfish-cbc')
1244 if (class_exists(
'\phpseclib\Crypt\TripleDES') ===
false) {
1245 $encryption_algorithms = array_diff(
1246 $encryption_algorithms,
1247 array(
'3des-ctr',
'3des-cbc')
1250 $encryption_algorithms = array_values($encryption_algorithms);
1252 $mac_algorithms = array(
1263 $compression_algorithms = array(
1269 switch ($this->server_identifier) {
1270 case 'SSH-2.0-SSHD':
1271 $mac_algorithms = array_values(array_diff(
1273 array(
'hmac-sha1-96',
'hmac-md5-96')
1277 $str_kex_algorithms = implode(
',', $kex_algorithms);
1278 $str_server_host_key_algorithms = implode(
',', $server_host_key_algorithms);
1279 $encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(
',', $encryption_algorithms);
1280 $mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(
',', $mac_algorithms);
1281 $compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(
',', $compression_algorithms);
1296 $this->encryption_algorithms_client_to_server = explode(
',', $this->
_string_shift(
$response, $temp[
'length']));
1299 $this->encryption_algorithms_server_to_client = explode(
',', $this->
_string_shift(
$response, $temp[
'length']));
1308 $this->compression_algorithms_client_to_server = explode(
',', $this->
_string_shift(
$response, $temp[
'length']));
1311 $this->compression_algorithms_server_to_client = explode(
',', $this->
_string_shift(
$response, $temp[
'length']));
1320 $first_kex_packet_follows = $first_kex_packet_follows != 0;
1323 $kexinit_payload_client = pack(
1324 'Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN',
1325 NET_SSH2_MSG_KEXINIT,
1327 strlen($str_kex_algorithms),
1328 $str_kex_algorithms,
1329 strlen($str_server_host_key_algorithms),
1330 $str_server_host_key_algorithms,
1331 strlen($encryption_algorithms_client_to_server),
1332 $encryption_algorithms_client_to_server,
1333 strlen($encryption_algorithms_server_to_client),
1334 $encryption_algorithms_server_to_client,
1335 strlen($mac_algorithms_client_to_server),
1336 $mac_algorithms_client_to_server,
1337 strlen($mac_algorithms_server_to_client),
1338 $mac_algorithms_server_to_client,
1339 strlen($compression_algorithms_client_to_server),
1340 $compression_algorithms_client_to_server,
1341 strlen($compression_algorithms_server_to_client),
1342 $compression_algorithms_server_to_client,
1359 $decrypt = $this->
_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_server_to_client);
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);
1366 $encrypt = $this->
_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_client_to_server);
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);
1375 if ($kex_algorithm ===
false) {
1376 user_error(
'No compatible key exchange algorithms found');
1377 return $this->
_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1381 $exchange_hash_rfc4419 =
'';
1383 if ($kex_algorithm ===
'curve25519-sha256@libssh.org') {
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');
1390 if (strpos($kex_algorithm,
'diffie-hellman-group-exchange') === 0) {
1391 $dh_group_sizes_packed = pack(
1393 $this->kex_dh_group_size_min,
1394 $this->kex_dh_group_size_preferred,
1395 $this->kex_dh_group_size_max
1399 NET_SSH2_MSG_KEXDH_GEX_REQUEST,
1400 $dh_group_sizes_packed
1408 user_error(
'Connection closed by server');
1412 if (
$type != NET_SSH2_MSG_KEXDH_GEX_GROUP) {
1413 user_error(
'Expected SSH_MSG_KEX_DH_GEX_GROUP');
1425 $exchange_hash_rfc4419 = pack(
1427 $dh_group_sizes_packed,
1434 $clientKexInitMessage = NET_SSH2_MSG_KEXDH_GEX_INIT;
1435 $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_GEX_REPLY;
1437 switch ($kex_algorithm) {
1440 case 'diffie-hellman-group1-sha1':
1441 $prime =
'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
1442 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
1443 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
1444 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
1447 case 'diffie-hellman-group14-sha1':
1448 $prime =
'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
1449 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
1450 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
1451 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
1452 '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
1453 '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
1454 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
1455 '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
1462 $clientKexInitMessage = NET_SSH2_MSG_KEXDH_INIT;
1463 $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_REPLY;
1466 switch ($kex_algorithm) {
1467 case 'diffie-hellman-group-exchange-sha256':
1468 $kexHash =
new Hash(
'sha256');
1471 $kexHash =
new Hash(
'sha1');
1482 $keyLength = min($kexHash->getLength(), max($encryptKeyLength, $decryptKeyLength));
1483 $max = $one->bitwise_leftShift(16 * $keyLength);
1484 $max = $max->subtract($one);
1486 $x = $one->random($one, $max);
1487 $e = $g->modPow(
$x, $prime);
1489 $eBytes = $e->toBytes(
true);
1491 $data = pack(
'CNa*', $clientKexInitMessage, strlen($eBytes), $eBytes);
1494 user_error(
'Connection closed by server');
1500 user_error(
'Connection closed by server');
1505 if (
$type != $serverKexReplyMessage) {
1506 user_error(
'Expected SSH_MSG_KEXDH_REPLY');
1511 $this->server_public_host_key = $server_public_host_key = $this->
_string_shift(
$response, $temp[
'length']);
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']);
1522 $temp = unpack(
'Nlength', $this->
_string_shift($this->signature, 4));
1523 $this->signature_format = $this->
_string_shift($this->signature, $temp[
'length']);
1525 if ($kex_algorithm ===
'curve25519-sha256@libssh.org') {
1526 if (strlen($fBytes) !== 32) {
1527 user_error(
'Received curve25519 public key of invalid length.');
1531 \Sodium::sodium_memzero(
$x);
1536 $keyBytes =
$key->toBytes(
true);
1538 $this->exchange_hash = pack(
1539 'Na*Na*Na*Na*Na*a*Na*Na*Na*',
1540 strlen($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,
1559 $this->exchange_hash = $kexHash->hash($this->exchange_hash);
1561 if ($this->session_id ===
false) {
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);
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);
1578 NET_SSH2_MSG_NEWKEYS
1588 user_error(
'Connection closed by server');
1594 if (
$type != NET_SSH2_MSG_NEWKEYS) {
1595 user_error(
'Expected SSH_MSG_NEWKEYS');
1599 $keyBytes = pack(
'Na*', strlen($keyBytes), $keyBytes);
1602 if ($this->encrypt) {
1603 if ($this->crypto_engine) {
1604 $this->encrypt->setEngine($this->crypto_engine);
1606 if ($this->encrypt->block_size) {
1607 $this->encrypt_block_size = $this->encrypt->block_size;
1609 $this->encrypt->enableContinuousBuffer();
1610 $this->encrypt->disablePadding();
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);
1616 $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
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);
1622 $this->encrypt->setKey(substr(
$key, 0, $encryptKeyLength));
1626 if ($this->decrypt) {
1627 if ($this->crypto_engine) {
1628 $this->decrypt->setEngine($this->crypto_engine);
1630 if ($this->decrypt->block_size) {
1631 $this->decrypt_block_size = $this->decrypt->block_size;
1633 $this->decrypt->enableContinuousBuffer();
1634 $this->decrypt->disablePadding();
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);
1640 $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
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);
1646 $this->decrypt->setKey(substr(
$key, 0, $decryptKeyLength));
1656 if ($encrypt ==
'arcfour128' || $encrypt ==
'arcfour256') {
1657 $this->encrypt->encrypt(str_repeat(
"\0", 1536));
1659 if ($decrypt ==
'arcfour128' || $decrypt ==
'arcfour256') {
1660 $this->decrypt->decrypt(str_repeat(
"\0", 1536));
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);
1669 $createKeyLength = 0;
1670 switch ($mac_algorithm) {
1671 case 'hmac-sha2-256':
1672 $this->hmac_create =
new Hash(
'sha256');
1673 $createKeyLength = 32;
1676 $this->hmac_create =
new Hash(
'sha1');
1677 $createKeyLength = 20;
1679 case 'hmac-sha1-96':
1680 $this->hmac_create =
new Hash(
'sha1-96');
1681 $createKeyLength = 20;
1684 $this->hmac_create =
new Hash(
'md5');
1685 $createKeyLength = 16;
1688 $this->hmac_create =
new Hash(
'md5-96');
1689 $createKeyLength = 16;
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);
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;
1707 $this->hmac_check =
new Hash(
'sha1');
1708 $checkKeyLength = 20;
1709 $this->hmac_size = 20;
1711 case 'hmac-sha1-96':
1712 $this->hmac_check =
new Hash(
'sha1-96');
1713 $checkKeyLength = 20;
1714 $this->hmac_size = 12;
1717 $this->hmac_check =
new Hash(
'md5');
1718 $checkKeyLength = 16;
1719 $this->hmac_size = 16;
1722 $this->hmac_check =
new Hash(
'md5-96');
1723 $checkKeyLength = 16;
1724 $this->hmac_size = 12;
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);
1731 $this->hmac_create->setKey(substr(
$key, 0, $createKeyLength));
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);
1737 $this->hmac_check->setKey(substr(
$key, 0, $checkKeyLength));
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);
1744 $this->decompress = $compression_algorithm ==
'zlib';
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);
1751 $this->compress = $compression_algorithm ==
'zlib';
1765 switch ($algorithm) {
1772 case 'blowfish-cbc':
1773 case 'blowfish-ctr':
1774 case 'twofish128-cbc':
1775 case 'twofish128-ctr':
1781 case 'twofish192-cbc':
1782 case 'twofish192-ctr':
1788 case 'twofish256-cbc':
1789 case 'twofish256-ctr':
1805 switch ($algorithm) {
1818 case 'blowfish-cbc':
1820 case 'blowfish-ctr':
1822 case 'twofish128-cbc':
1823 case 'twofish192-cbc':
1824 case 'twofish256-cbc':
1827 case 'twofish128-ctr':
1828 case 'twofish192-ctr':
1829 case 'twofish256-ctr':
1853 $args = func_get_args();
1854 return call_user_func_array(array(&$this,
'_login'), $args);
1869 if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
1875 $args = array_slice(func_get_args(), 1);
1880 foreach ($args as $arg) {
1900 if (!($this->bitmap & self::MASK_CONNECTED)) {
1904 if (!($this->bitmap & self::MASK_LOGIN_REQ)) {
1907 NET_SSH2_MSG_SERVICE_REQUEST,
1908 strlen(
'ssh-userauth'),
1918 user_error(
'Connection closed by server');
1924 if (
$type != NET_SSH2_MSG_SERVICE_ACCEPT) {
1925 user_error(
'Expected SSH_MSG_SERVICE_ACCEPT');
1928 $this->bitmap |= self::MASK_LOGIN_REQ;
1931 if (strlen($this->last_interactive_response)) {
1943 $this->bitmap |= self::MASK_LOGIN;
1952 NET_SSH2_MSG_USERAUTH_REQUEST,
1955 strlen(
'ssh-connection'),
1967 user_error(
'Connection closed by server');
1974 case NET_SSH2_MSG_USERAUTH_SUCCESS:
1975 $this->bitmap |= self::MASK_LOGIN;
1985 NET_SSH2_MSG_USERAUTH_REQUEST,
1988 strlen(
'ssh-connection'),
1998 if (!defined(
'NET_SSH2_LOGGING')) {
2003 NET_SSH2_MSG_USERAUTH_REQUEST,
2006 strlen(
'ssh-connection'),
2022 user_error(
'Connection closed by server');
2029 case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ:
2030 if (defined(
'NET_SSH2_LOGGING')) {
2031 $this->message_number_log[count($this->message_number_log) - 1] =
'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ';
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:
2042 $partial_success = $partial_success != 0;
2044 if (!$partial_success && in_array(
'keyboard-interactive', $auth_methods)) {
2046 $this->bitmap |= self::MASK_LOGIN;
2052 case NET_SSH2_MSG_USERAUTH_SUCCESS:
2053 $this->bitmap |= self::MASK_LOGIN;
2074 NET_SSH2_MSG_USERAUTH_REQUEST,
2077 strlen(
'ssh-connection'),
2079 strlen(
'keyboard-interactive'),
2080 'keyboard-interactive',
2103 $responses = func_get_args();
2105 if (strlen($this->last_interactive_response)) {
2110 user_error(
'Connection closed by server');
2118 case NET_SSH2_MSG_USERAUTH_INFO_REQUEST:
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;
2132 unset($responses[$i]);
2135 $responses = array_values($responses);
2137 if (isset($this->keyboard_requests_responses)) {
2138 for (
$i = 0;
$i < $num_prompts;
$i++) {
2143 foreach ($this->keyboard_requests_responses as
$key => $value) {
2144 if (substr($prompt, 0, strlen(
$key)) ==
$key) {
2145 $responses[] = $value;
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(
2158 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
2159 $this->message_number_log[count($this->message_number_log) - 1]
2163 if (!count($responses) && $num_prompts) {
2164 $this->last_interactive_response = $orig;
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');
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(
2186 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE',
2187 $this->message_number_log[count($this->message_number_log) - 1]
2199 case NET_SSH2_MSG_USERAUTH_SUCCESS:
2201 case NET_SSH2_MSG_USERAUTH_FAILURE:
2219 $keys = $agent->requestIdentities();
2243 if ($publickey ===
false) {
2248 'e' => $publickey[
'e']->toBytes(
true),
2249 'n' => $publickey[
'n']->toBytes(
true)
2255 strlen($publickey[
'e']),
2257 strlen($publickey[
'n']),
2263 NET_SSH2_MSG_USERAUTH_REQUEST,
2266 strlen(
'ssh-connection'),
2268 strlen(
'publickey'),
2271 $part2 = pack(
'Na*Na*', strlen(
'ssh-rsa'),
'ssh-rsa', strlen($publickey), $publickey);
2273 $packet = $part1 . chr(0) . $part2;
2280 user_error(
'Connection closed by server');
2287 case NET_SSH2_MSG_USERAUTH_FAILURE:
2291 case NET_SSH2_MSG_USERAUTH_PK_OK:
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(
2297 'NET_SSH2_MSG_USERAUTH_PK_OK',
2298 $this->message_number_log[count($this->message_number_log) - 1]
2303 $packet = $part1 . chr(1) . $part2;
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);
2315 user_error(
'Connection closed by server');
2322 case NET_SSH2_MSG_USERAUTH_FAILURE:
2325 case NET_SSH2_MSG_USERAUTH_SUCCESS:
2326 $this->bitmap |= self::MASK_LOGIN;
2344 $this->timeout = $this->curTimeout =
$timeout;
2368 function exec($command, $callback = null)
2371 $this->is_timeout =
false;
2372 $this->stdErrorLog =
'';
2374 if (!($this->bitmap & self::MASK_LOGIN)) {
2385 $packet_size = 0x4000;
2389 NET_SSH2_MSG_CHANNEL_OPEN,
2393 $this->window_size_server_to_client[self::CHANNEL_EXEC],
2401 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;
2408 if ($this->request_pty ===
true) {
2409 $terminal_modes = pack(
'C', NET_SSH2_TTY_OP_END);
2412 NET_SSH2_MSG_CHANNEL_REQUEST,
2413 $this->server_channels[self::CHANNEL_EXEC],
2419 $this->windowColumns,
2423 strlen($terminal_modes),
2433 user_error(
'Connection closed by server');
2440 case NET_SSH2_MSG_CHANNEL_SUCCESS:
2442 case NET_SSH2_MSG_CHANNEL_FAILURE:
2444 user_error(
'Unable to request pseudo-terminal');
2445 return $this->
_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
2447 $this->in_request_pty_exec =
true;
2461 NET_SSH2_MSG_CHANNEL_REQUEST,
2462 $this->server_channels[self::CHANNEL_EXEC],
2473 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
2480 $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
2482 if ($callback ===
false || $this->in_request_pty_exec) {
2490 case $temp ===
true:
2491 return is_callable($callback) ? true :
$output;
2492 case $temp ===
false:
2495 if (is_callable($callback)) {
2496 if (call_user_func($callback, $temp) ===
true) {
2517 if ($this->in_request_pty_exec ===
true) {
2522 $packet_size = 0x4000;
2526 NET_SSH2_MSG_CHANNEL_OPEN,
2529 self::CHANNEL_SHELL,
2530 $this->window_size_server_to_client[self::CHANNEL_SHELL],
2538 $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;
2545 $terminal_modes = pack(
'C', NET_SSH2_TTY_OP_END);
2548 NET_SSH2_MSG_CHANNEL_REQUEST,
2549 $this->server_channels[self::CHANNEL_SHELL],
2555 $this->windowColumns,
2559 strlen($terminal_modes),
2569 user_error(
'Connection closed by server');
2576 case NET_SSH2_MSG_CHANNEL_SUCCESS:
2578 case NET_SSH2_MSG_CHANNEL_FAILURE:
2581 user_error(
'Unable to request pseudo-terminal');
2582 return $this->
_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
2587 NET_SSH2_MSG_CHANNEL_REQUEST,
2588 $this->server_channels[self::CHANNEL_SHELL],
2597 $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST;
2604 $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
2606 $this->bitmap |= self::MASK_SHELL;
2622 case $this->in_subsystem:
2623 return self::CHANNEL_SUBSYSTEM;
2624 case $this->in_request_pty_exec:
2625 return self::CHANNEL_EXEC;
2627 return self::CHANNEL_SHELL;
2639 $channel = self::CHANNEL_EXEC;
2641 if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) {
2644 }
while ($channel++ < self::CHANNEL_SUBSYSTEM);
2661 function read($expect =
'', $mode = self::READ_SIMPLE)
2664 $this->is_timeout =
false;
2666 if (!($this->bitmap & self::MASK_LOGIN)) {
2667 user_error(
'Operation disallowed prior to login()');
2671 if (!($this->bitmap & self::MASK_SHELL) && !$this->
_initShell()) {
2672 user_error(
'Unable to initiate an interactive shell session');
2680 if ($mode == self::READ_REGEX) {
2681 preg_match($expect, substr($this->interactiveBuffer, -1024), $matches);
2682 $match = isset($matches[0]) ? $matches[0] :
'';
2684 $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) :
false;
2685 if ($pos !==
false) {
2686 return $this->
_string_shift($this->interactiveBuffer, $pos + strlen($match));
2690 $this->in_request_pty_exec =
false;
2691 return $response ? $this->
_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)) :
false;
2708 if (!($this->bitmap & self::MASK_LOGIN)) {
2709 user_error(
'Operation disallowed prior to login()');
2713 if (!($this->bitmap & self::MASK_SHELL) && !$this->
_initShell()) {
2714 user_error(
'Unable to initiate an interactive shell session');
2741 NET_SSH2_MSG_CHANNEL_OPEN,
2744 self::CHANNEL_SUBSYSTEM,
2753 $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;
2762 NET_SSH2_MSG_CHANNEL_REQUEST,
2763 $this->server_channels[self::CHANNEL_SUBSYSTEM],
2764 strlen(
'subsystem'),
2774 $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;
2782 $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
2784 $this->bitmap |= self::MASK_SHELL;
2785 $this->in_subsystem =
true;
2799 $this->in_subsystem =
false;
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);
2862 return (
bool) ($this->bitmap & self::MASK_CONNECTED);
2873 return (
bool) ($this->bitmap & self::MASK_LOGIN);
2887 if (!is_resource($this->fsock) || feof($this->fsock)) {
2888 user_error(
'Connection closed prematurely');
2893 $start = microtime(
true);
2894 $raw = fread($this->fsock, $this->decrypt_block_size);
2896 if (!strlen($raw)) {
2900 if ($this->decrypt !==
false) {
2901 $raw = $this->decrypt->decrypt($raw);
2903 if ($raw ===
false) {
2904 user_error(
'Unable to decrypt content');
2908 extract(unpack(
'Npacket_length/Cpadding_length', $this->
_string_shift($raw, 5)));
2915 if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
2916 user_error(
'Invalid size');
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');
2929 $remaining_length-= strlen($temp);
2931 $stop = microtime(
true);
2932 if (strlen($buffer)) {
2933 $raw.= $this->decrypt !==
false ? $this->decrypt->decrypt($buffer) : $buffer;
2936 $payload = $this->
_string_shift($raw, $packet_length - $padding_length - 1);
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');
2945 } elseif ($hmac != $this->hmac_check->hash(pack(
'NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) {
2946 user_error(
'Invalid HMAC');
2955 $this->get_seq_no++;
2957 if (defined(
'NET_SSH2_LOGGING')) {
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)';
2966 return $this->
_filter($payload);
2980 switch (ord($payload[0])) {
2981 case NET_SSH2_MSG_DISCONNECT:
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));
2987 case NET_SSH2_MSG_IGNORE:
2990 case NET_SSH2_MSG_DEBUG:
2992 extract(unpack(
'Nlength', $this->
_string_shift($payload, 4)));
2993 $this->errors[] =
'SSH_MSG_DEBUG: ' . utf8_decode($this->
_string_shift($payload, $length));
2996 case NET_SSH2_MSG_UNIMPLEMENTED:
2998 case NET_SSH2_MSG_KEXINIT:
2999 if ($this->session_id !==
false) {
3009 if (($this->bitmap & self::MASK_CONNECTED) && !($this->bitmap & self::MASK_LOGIN) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
3011 extract(unpack(
'Nlength', $this->
_string_shift($payload, 4)));
3012 $this->banner_message = utf8_decode($this->
_string_shift($payload, $length));
3017 if (($this->bitmap & self::MASK_CONNECTED) && ($this->bitmap & self::MASK_LOGIN)) {
3018 switch (ord($payload[0])) {
3019 case NET_SSH2_MSG_GLOBAL_REQUEST:
3020 extract(unpack(
'Nlength', $this->
_string_shift($payload, 4)));
3021 $this->errors[] =
'SSH_MSG_GLOBAL_REQUEST: ' . $this->
_string_shift($payload, $length);
3024 return $this->
_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3029 case NET_SSH2_MSG_CHANNEL_OPEN:
3031 extract(unpack(
'Nlength', $this->
_string_shift($payload, 4)));
3033 extract(unpack(
'Nserver_channel', $this->
_string_shift($payload, 4)));
3036 case 'auth-agent@openssh.com':
3037 if (isset($this->agent)) {
3038 $new_channel = self::CHANNEL_AGENT_FORWARD;
3040 extract(unpack(
'Nremote_window_size', $this->
_string_shift($payload, 4)));
3041 extract(unpack(
'Nremote_maximum_packet_size', $this->
_string_shift($payload, 4)));
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;
3047 $packet_size = 0x4000;
3051 NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,
3058 $this->server_channels[$new_channel] = $server_channel;
3059 $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
3068 NET_SSH2_MSG_REQUEST_FAILURE,
3070 NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
3078 return $this->
_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3083 case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
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;
3089 $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ?
true : $this->
_get_binary_packet();
3105 $this->quiet_mode =
true;
3117 $this->quiet_mode =
false;
3140 $this->request_pty =
true;
3150 $this->request_pty =
false;
3177 if (!empty($this->channel_buffers[$client_channel])) {
3178 return array_shift($this->channel_buffers[$client_channel]);
3182 if ($this->curTimeout) {
3183 if ($this->curTimeout < 0) {
3184 $this->is_timeout =
true;
3188 $read = array($this->fsock);
3189 $write = $except = null;
3191 $start = microtime(
true);
3192 $sec = floor($this->curTimeout);
3193 $usec = 1000000 * ($this->curTimeout - $sec);
3195 if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
3196 $this->is_timeout =
true;
3199 $elapsed = microtime(
true) -
$start;
3200 $this->curTimeout-= $elapsed;
3205 user_error(
'Connection closed by server');
3208 if ($client_channel == -1 &&
$response ===
true) {
3217 if (
$type == NET_SSH2_MSG_CHANNEL_OPEN) {
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);
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);
3236 switch ($this->channel_status[$channel]) {
3237 case NET_SSH2_MSG_CHANNEL_OPEN:
3239 case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
3241 $this->server_channels[$channel] = $server_channel;
3243 if ($window_size < 0) {
3244 $window_size&= 0x7FFFFFFF;
3245 $window_size+= 0x80000000;
3247 $this->window_size_client_to_server[$channel] =
$window_size;
3249 $this->packet_size_client_to_server[$channel] = $temp[
'packet_size_client_to_server'];
3255 user_error(
'Unable to open channel');
3256 return $this->
_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3259 case NET_SSH2_MSG_CHANNEL_REQUEST:
3261 case NET_SSH2_MSG_CHANNEL_SUCCESS:
3263 case NET_SSH2_MSG_CHANNEL_FAILURE:
3266 user_error(
'Unable to fulfill channel request');
3267 return $this->
_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3269 case NET_SSH2_MSG_CHANNEL_CLOSE:
3277 case NET_SSH2_MSG_CHANNEL_DATA:
3290 if ($channel == self::CHANNEL_AGENT_FORWARD) {
3291 $agent_response = $this->agent->_forward_data(
$data);
3292 if (!is_bool($agent_response)) {
3298 if ($client_channel == $channel) {
3301 if (!isset($this->channel_buffers[$channel])) {
3302 $this->channel_buffers[$channel] = array();
3304 $this->channel_buffers[$channel][] =
$data;
3306 case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
3315 $this->stdErrorLog.=
$data;
3316 if ($skip_extended || $this->quiet_mode) {
3319 if ($client_channel == $channel) {
3322 if (!isset($this->channel_buffers[$channel])) {
3323 $this->channel_buffers[$channel] = array();
3325 $this->channel_buffers[$channel][] =
$data;
3327 case NET_SSH2_MSG_CHANNEL_REQUEST:
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]));
3344 $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF;
3361 case NET_SSH2_MSG_CHANNEL_CLOSE:
3362 $this->curTimeout = 0;
3364 if ($this->bitmap & self::MASK_SHELL) {
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]));
3371 $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
3373 case NET_SSH2_MSG_CHANNEL_EOF:
3376 user_error(
'Error reading channel data');
3377 return $this->
_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
3395 if (!is_resource($this->fsock) || feof($this->fsock)) {
3396 user_error(
'Connection closed prematurely');
3408 $packet_length = strlen(
$data) + 9;
3412 $padding_length = $packet_length - strlen(
$data) - 5;
3416 $packet = pack(
'NCa*', $packet_length - 4, $padding_length,
$data . $padding);
3418 $hmac = $this->hmac_create !==
false ? $this->hmac_create->hash(pack(
'Na*', $this->send_seq_no, $packet)) :
'';
3419 $this->send_seq_no++;
3421 if ($this->encrypt !==
false) {
3422 $packet = $this->encrypt->encrypt($packet);
3427 $start = microtime(
true);
3428 $result = strlen($packet) == fputs($this->fsock, $packet);
3429 $stop = microtime(
true);
3431 if (defined(
'NET_SSH2_LOGGING')) {
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)';
3454 if (strlen($message_number) > 2) {
3458 switch (NET_SSH2_LOGGING) {
3460 case self::LOG_SIMPLE:
3461 $this->message_number_log[] = $message_number;
3464 case self::LOG_COMPLEX:
3465 $this->message_number_log[] = $message_number;
3466 $this->log_size+= strlen(
$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);
3476 case self::LOG_REALTIME:
3493 case self::LOG_REALTIME_FILE:
3494 if (!isset($this->realtime_log_file)) {
3496 $filename = self::LOG_REALTIME_FILENAME;
3498 $this->realtime_log_file = $fp;
3500 if (!is_resource($this->realtime_log_file)) {
3504 if ($this->realtime_log_wrap) {
3505 $temp =
"<<< START >>>\r\n";
3507 fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp));
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;
3515 fputs($this->realtime_log_file, $entry);
3531 while (strlen(
$data)) {
3532 if (!$this->window_size_client_to_server[$client_channel]) {
3533 $this->bitmap^= self::MASK_WINDOW_ADJUST;
3536 $this->bitmap^= self::MASK_WINDOW_ADJUST;
3544 $this->packet_size_client_to_server[$client_channel],
3545 $this->window_size_client_to_server[$client_channel]
3551 NET_SSH2_MSG_CHANNEL_DATA,
3552 $this->server_channels[$client_channel],
3556 $this->window_size_client_to_server[$client_channel]-= strlen($temp);
3581 $this->
_send_binary_packet(pack(
'CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
3584 $this->
_send_binary_packet(pack(
'CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
3587 $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
3589 $this->curTimeout = 0;
3595 $this->
_send_binary_packet(pack(
'CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
3598 if ($this->bitmap & self::MASK_SHELL) {
3612 if ($this->bitmap & self::MASK_CONNECTED) {
3613 $data = pack(
'CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0,
'', 0,
'');
3616 fclose($this->fsock);
3633 $substr = substr($string, 0,
$index);
3634 $string = substr($string,
$index);
3650 $args = func_get_args();
3651 foreach ($args as $arg) {
3652 foreach ($arg as
$key => $value) {
3653 if (!defined($value)) {
3654 define($value,
$key);
3672 if (!defined(
'NET_SSH2_LOGGING')) {
3676 switch (NET_SSH2_LOGGING) {
3677 case self::LOG_SIMPLE:
3680 case self::LOG_COMPLEX:
3681 return $this->
_format_log($this->message_log, $this->message_number_log);
3699 for (
$i = 0;
$i < count($message_log);
$i++) {
3700 $output.= $message_number_log[
$i] .
"\r\n";
3701 $current_log = $message_log[
$i];
3704 if (strlen($current_log)) {
3705 $output.= str_pad(dechex($j), 7,
'0', STR_PAD_LEFT) .
'0 ';
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));
3712 $raw = preg_replace(
'#[^\x20-\x7E]|<#',
'.', $fragment);
3713 $output.= str_pad($hex, $this->log_long_width - $this->log_short_width,
' ') . $raw .
"\r\n";
3715 }
while (strlen($current_log));
3733 return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2,
'0', STR_PAD_LEFT);
3747 if (isset($this->agent)) {
3748 $this->agent->_on_channel_open($this);
3763 foreach ($array1 as $value) {
3764 if (in_array($value, $array2)) {
3790 $count = count($this->errors);
3793 return $this->errors[$count - 1];
3965 if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
3974 extract(unpack(
'Nlength', $this->
_string_shift($server_public_host_key, 4)));
3977 if ($this->signature_validated) {
3978 return $this->bitmap ?
3979 $this->signature_format .
' ' . base64_encode($this->server_public_host_key) :
3983 $this->signature_validated =
true;
3985 switch ($this->signature_format) {
3989 $temp = unpack(
'Nlength', $this->
_string_shift($server_public_host_key, 4));
3992 $temp = unpack(
'Nlength', $this->
_string_shift($server_public_host_key, 4));
3995 $temp = unpack(
'Nlength', $this->
_string_shift($server_public_host_key, 4));
3998 $temp = unpack(
'Nlength', $this->
_string_shift($server_public_host_key, 4));
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);
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);
4022 $w =
$s->modInverse($q);
4024 $u1 =
$w->multiply(
new BigInteger(sha1($this->exchange_hash), 16));
4025 list(, $u1) = $u1->divide($q);
4027 $u2 =
$w->multiply(
$r);
4028 list(, $u2) = $u2->divide($q);
4030 $g = $g->modPow($u1, $p);
4031 $y =
$y->modPow($u2, $p);
4033 $v = $g->multiply(
$y);
4034 list(, $v) = $v->divide($p);
4035 list(, $v) = $v->divide($q);
4037 if (!$v->equals(
$r)) {
4038 user_error(
'Bad server signature');
4039 return $this->
_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
4044 $temp = unpack(
'Nlength', $this->
_string_shift($server_public_host_key, 4));
4047 $temp = unpack(
'Nlength', $this->
_string_shift($server_public_host_key, 4));
4048 $rawN = $this->
_string_shift($server_public_host_key, $temp[
'length']);
4050 $nLength = strlen(ltrim($rawN,
"\0"));
4065 $temp = unpack(
'Nlength', $this->
_string_shift($signature, 4));
4075 user_error(
'Invalid signature');
4076 return $this->
_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
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;
4086 user_error(
'Bad server signature');
4087 return $this->
_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
4091 user_error(
'Unsupported signature format');
4092 return $this->
_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
4095 return $this->signature_format .
' ' . base64_encode($this->server_public_host_key);
4106 if (is_null($this->exit_status)) {
4142 $this->windowColumns = $value;
4153 $this->windowRows = $value;
4166 $this->windowRows =
$rows;
setTimeout($timeout)
Set Timeout.
$languages_server_to_client
getBannerMessage()
Returns the banner message.
_generate_identifier()
Generates the SSH identifier.
getLastError()
Returns the last error.
_get_interactive_channel()
Return the channel to be used with read() / write()
$kex_dh_group_size_preferred
enableQuietMode()
Enable Quiet Mode.
$keyboard_requests_responses
const LOG_MAX_SIZE
Make sure that the log never gets larger than this.
_define_array()
Define Array.
Pure-PHP implementation of Twofish.
$channel_extended_data_type_codes
Pure-PHP implementation of Blowfish.
const PUBLIC_FORMAT_RAW
#-
$packet_size_client_to_server
_login($username)
Login Helper.
_format_log($message_log, $message_number_log)
Formats a log for printing.
stopSubsystem()
Stops a subsystem.
_close_channel($client_channel, $want_reply=false)
Closes and flushes a channel.
getCompressionAlgorithmsServer2Client()
Return a list of the compression algorithms the server supports, when sending stuff to the client...
$last_interactive_response
$mac_algorithms_server_to_client
setCryptoEngine($engine)
Set Crypto Engine Mode.
const LOG_REALTIME_FILE
Dumps the content real-time to a file.
_get_binary_packet()
Gets Binary Packets.
const MASK_CONSTRUCTOR
#+ Execution Bitmap Masks
$window_size_client_to_server
getLog()
Returns a log of the packets that have been sent and received.
_on_channel_open()
Helper function for agent->_on_channel_open()
$server_host_key_algorithms
setWindowSize($columns=80, $rows=24)
Sets the number of columns and rows for the terminal window size.
_encryption_algorithm_to_key_size($algorithm)
Maps an encryption algorithm name to the number of key bytes.
setWindowColumns($value)
Sets the number of columns for the terminal window size.
getExitStatus()
Returns the exit status of an SSH command or false.
getMACAlgorithmsClient2Server()
Return a list of the MAC algorithms the server supports, when receiving stuff from the client...
__construct($host, $port=22, $timeout=10)
Default Constructor.
$channel_open_failure_reasons
isConnected()
Is the connection still active?
enablePTY()
Enable request-pty when using exec()
_array_intersect_first($array1, $array2)
Returns the first value of the intersection of two arrays or false if the intersection is empty...
getStdError()
Get the output from stdError.
write($cmd)
Inputs a command into an interactive shell.
_key_exchange($kexinit_payload_server)
Key Exchange.
read($expect='', $mode=self::READ_SIMPLE)
Returns the output of an interactive shell.
Pure-PHP implementation of SSHv2.
Pure-PHP implementation of Rijndael.
$window_size_server_to_client
catch(Exception $e) $message
disablePTY()
Disable request-pty when using exec()
getWindowRows()
Returns the number of rows for the terminal window size.
isAuthenticated()
Have you successfully been logged in?
getErrors()
Returns all errors.
_privatekey_login($username, $privatekey)
Login with an RSA private key.
$realtime_log_wrap
Real-time log file wrap boolean.
Pure-PHP PKCS#1 compliant implementation of RSA.
getEncryptionAlgorithmsClient2Server()
Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff ...
_initShell()
Creates an interactive shell.
_disconnect($reason)
Disconnect.
$curTimeout
Current Timeout.
$compression_algorithms_server_to_client
getKexAlgorithms()
Return a list of the key exchange algorithms the server supports.
Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic...
Pure-PHP implementation of Triple DES.
getWindowColumns()
Returns the number of columns for the terminal window size.
getLanguagesClient2Server()
Return a list of the languages the server supports, when receiving stuff from the client...
isPTYEnabled()
Returns whether request-pty is enabled or not.
getLanguagesServer2Client()
Return a list of the languages the server supports, when sending stuff to the client.
_filter($payload)
Filter Binary Packets.
getServerPublicHostKey()
Returns the server public host key.
_append_log($message_number, $message)
Logs data packets.
const READ_REGEX
Returns when a string matching the regular expression $expect is found.
_format_log_helper($matches)
Helper function for _format_log.
_string_shift(&$string, $index=1)
String Shift.
_send_binary_packet($data, $logged=null)
Sends Binary Packets.
$compression_algorithms_client_to_server
const SIGNATURE_PKCS1
Use the PKCS#1 scheme by default.
getServerHostKeyAlgorithms()
Return a list of the host key (public key) algorithms the server supports.
_ssh_agent_login($username, $agent)
Login with an ssh-agent provided key.
getCompressionAlgorithmsClient2Server()
Return a list of the compression algorithms the server supports, when receiving stuff from the client...
disableQuietMode()
Disable Quiet Mode.
isQuietModeEnabled()
Returns whether Quiet Mode is enabled or not.
$encryption_algorithms_client_to_server
Pure-PHP arbitrary precision integer arithmetic library.
Pure-PHP implementation of RC4.
_keyboard_interactive_login($username, $password)
Login via keyboard-interactive authentication.
_get_open_channel()
Return an available open channel.
$languages_client_to_server
_get_channel_packet($client_channel, $skip_extended=false)
Gets channel data.
_keyboard_interactive_process()
Handle the keyboard-interactive requests / responses.
getMACAlgorithmsServer2Client()
Return a list of the MAC algorithms the server supports, when sending stuff to the client...
$encryption_algorithms_server_to_client
getServerIdentification()
Return the server identification.
static string($length)
Generate a random string.
exec($command, $callback=null)
Execute Command.
_send_channel_packet($client_channel, $data)
Sends channel data.
getEncryptionAlgorithmsServer2Client()
Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to...
$mac_algorithms_client_to_server
const LOG_REALTIME
Outputs the content real-time.
setWindowRows($value)
Sets the number of rows for the terminal window size.
_encryption_algorithm_to_crypt_instance($algorithm)
Maps an encryption algorithm name to an instance of a subclass of .
const CHANNEL_AGENT_FORWARD
_login_helper($username, $password=null)
Login Helper.
const LOG_COMPLEX
Returns the message content.
_connect()
Connect to an SSHv2 server.
$quiet_mode
Flag to suppress stderr from output.
startSubsystem($subsystem)
Start a subsystem.