/phpseclib/Net/SSH2.php

http://github.com/phpseclib/phpseclib · PHP · 5160 lines · 2879 code · 546 blank · 1735 comment · 453 complexity · b048711bdddb15f44d6466fb86e06fe4 MD5 · raw file

  1. <?php
  2. /**
  3. * Pure-PHP implementation of SSHv2.
  4. *
  5. * PHP version 5
  6. *
  7. * Here are some examples of how to use this library:
  8. * <code>
  9. * <?php
  10. * include 'vendor/autoload.php';
  11. *
  12. * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
  13. * if (!$ssh->login('username', 'password')) {
  14. * exit('Login Failed');
  15. * }
  16. *
  17. * echo $ssh->exec('pwd');
  18. * echo $ssh->exec('ls -la');
  19. * ?>
  20. * </code>
  21. *
  22. * <code>
  23. * <?php
  24. * include 'vendor/autoload.php';
  25. *
  26. * $key = \phpseclib3\Crypt\PublicKeyLoader::load('...', '(optional) password');
  27. *
  28. * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
  29. * if (!$ssh->login('username', $key)) {
  30. * exit('Login Failed');
  31. * }
  32. *
  33. * echo $ssh->read('username@username:~$');
  34. * $ssh->write("ls -la\n");
  35. * echo $ssh->read('username@username:~$');
  36. * ?>
  37. * </code>
  38. *
  39. * @category Net
  40. * @package SSH2
  41. * @author Jim Wigginton <terrafrost@php.net>
  42. * @copyright 2007 Jim Wigginton
  43. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  44. * @link http://phpseclib.sourceforge.net
  45. */
  46. namespace phpseclib3\Net;
  47. use phpseclib3\Crypt\Blowfish;
  48. use phpseclib3\Crypt\Hash;
  49. use phpseclib3\Crypt\Random;
  50. use phpseclib3\Crypt\RC4;
  51. use phpseclib3\Crypt\Rijndael;
  52. use phpseclib3\Crypt\Common\PrivateKey;
  53. use phpseclib3\Crypt\RSA;
  54. use phpseclib3\Crypt\DSA;
  55. use phpseclib3\Crypt\EC;
  56. use phpseclib3\Crypt\DH;
  57. use phpseclib3\Crypt\TripleDES;
  58. use phpseclib3\Crypt\Twofish;
  59. use phpseclib3\Crypt\ChaCha20;
  60. use phpseclib3\Math\BigInteger; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
  61. use phpseclib3\System\SSH\Agent;
  62. use phpseclib3\System\SSH\Agent\Identity as AgentIdentity;
  63. use phpseclib3\Exception\NoSupportedAlgorithmsException;
  64. use phpseclib3\Exception\UnsupportedAlgorithmException;
  65. use phpseclib3\Exception\UnsupportedCurveException;
  66. use phpseclib3\Exception\ConnectionClosedException;
  67. use phpseclib3\Exception\UnableToConnectException;
  68. use phpseclib3\Exception\InsufficientSetupException;
  69. use phpseclib3\Common\Functions\Strings;
  70. use phpseclib3\Crypt\Common\AsymmetricKey;
  71. /**#@+
  72. * @access private
  73. */
  74. /**
  75. * No compression
  76. */
  77. define('NET_SSH2_COMPRESSION_NONE', 1);
  78. /**
  79. * zlib compression
  80. */
  81. define('NET_SSH2_COMPRESSION_ZLIB', 2);
  82. /**
  83. * zlib@openssh.com
  84. */
  85. define('NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH', 3);
  86. /**#@-*/
  87. /**
  88. * Pure-PHP implementation of SSHv2.
  89. *
  90. * @package SSH2
  91. * @author Jim Wigginton <terrafrost@php.net>
  92. * @access public
  93. */
  94. class SSH2
  95. {
  96. // Execution Bitmap Masks
  97. const MASK_CONSTRUCTOR = 0x00000001;
  98. const MASK_CONNECTED = 0x00000002;
  99. const MASK_LOGIN_REQ = 0x00000004;
  100. const MASK_LOGIN = 0x00000008;
  101. const MASK_SHELL = 0x00000010;
  102. const MASK_WINDOW_ADJUST = 0x00000020;
  103. /*
  104. * Channel constants
  105. *
  106. * RFC4254 refers not to client and server channels but rather to sender and recipient channels. we don't refer
  107. * to them in that way because RFC4254 toggles the meaning. the client sends a SSH_MSG_CHANNEL_OPEN message with
  108. * a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response, with a sender and a
  109. * recipient channel. at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel
  110. * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snippet:
  111. * The 'recipient channel' is the channel number given in the original
  112. * open request, and 'sender channel' is the channel number allocated by
  113. * the other side.
  114. *
  115. * @see \phpseclib3\Net\SSH2::send_channel_packet()
  116. * @see \phpseclib3\Net\SSH2::get_channel_packet()
  117. * @access private
  118. */
  119. const CHANNEL_EXEC = 1; // PuTTy uses 0x100
  120. const CHANNEL_SHELL = 2;
  121. const CHANNEL_SUBSYSTEM = 3;
  122. const CHANNEL_AGENT_FORWARD = 4;
  123. const CHANNEL_KEEP_ALIVE = 5;
  124. /**
  125. * Returns the message numbers
  126. *
  127. * @access public
  128. * @see \phpseclib3\Net\SSH2::getLog()
  129. */
  130. const LOG_SIMPLE = 1;
  131. /**
  132. * Returns the message content
  133. *
  134. * @access public
  135. * @see \phpseclib3\Net\SSH2::getLog()
  136. */
  137. const LOG_COMPLEX = 2;
  138. /**
  139. * Outputs the content real-time
  140. *
  141. * @access public
  142. * @see \phpseclib3\Net\SSH2::getLog()
  143. */
  144. const LOG_REALTIME = 3;
  145. /**
  146. * Dumps the content real-time to a file
  147. *
  148. * @access public
  149. * @see \phpseclib3\Net\SSH2::getLog()
  150. */
  151. const LOG_REALTIME_FILE = 4;
  152. /**
  153. * Make sure that the log never gets larger than this
  154. *
  155. * @access public
  156. * @see \phpseclib3\Net\SSH2::getLog()
  157. */
  158. const LOG_MAX_SIZE = 1048576; // 1024 * 1024
  159. /**
  160. * Returns when a string matching $expect exactly is found
  161. *
  162. * @access public
  163. * @see \phpseclib3\Net\SSH2::read()
  164. */
  165. const READ_SIMPLE = 1;
  166. /**
  167. * Returns when a string matching the regular expression $expect is found
  168. *
  169. * @access public
  170. * @see \phpseclib3\Net\SSH2::read()
  171. */
  172. const READ_REGEX = 2;
  173. /**
  174. * Returns whenever a data packet is received.
  175. *
  176. * Some data packets may only contain a single character so it may be necessary
  177. * to call read() multiple times when using this option
  178. *
  179. * @access public
  180. * @see \phpseclib3\Net\SSH2::read()
  181. */
  182. const READ_NEXT = 3;
  183. /**
  184. * The SSH identifier
  185. *
  186. * @var string
  187. * @access private
  188. */
  189. private $identifier;
  190. /**
  191. * The Socket Object
  192. *
  193. * @var object
  194. * @access private
  195. */
  196. public $fsock;
  197. /**
  198. * Execution Bitmap
  199. *
  200. * The bits that are set represent functions that have been called already. This is used to determine
  201. * if a requisite function has been successfully executed. If not, an error should be thrown.
  202. *
  203. * @var int
  204. * @access private
  205. */
  206. protected $bitmap = 0;
  207. /**
  208. * Error information
  209. *
  210. * @see self::getErrors()
  211. * @see self::getLastError()
  212. * @var array
  213. * @access private
  214. */
  215. private $errors = [];
  216. /**
  217. * Server Identifier
  218. *
  219. * @see self::getServerIdentification()
  220. * @var array|false
  221. * @access private
  222. */
  223. protected $server_identifier = false;
  224. /**
  225. * Key Exchange Algorithms
  226. *
  227. * @see self::getKexAlgorithims()
  228. * @var array|false
  229. * @access private
  230. */
  231. private $kex_algorithms = false;
  232. /**
  233. * Key Exchange Algorithm
  234. *
  235. * @see self::getMethodsNegotiated()
  236. * @var string|false
  237. * @access private
  238. */
  239. private $kex_algorithm = false;
  240. /**
  241. * Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
  242. *
  243. * @see self::_key_exchange()
  244. * @var int
  245. * @access private
  246. */
  247. private $kex_dh_group_size_min = 1536;
  248. /**
  249. * Preferred Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
  250. *
  251. * @see self::_key_exchange()
  252. * @var int
  253. * @access private
  254. */
  255. private $kex_dh_group_size_preferred = 2048;
  256. /**
  257. * Maximum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
  258. *
  259. * @see self::_key_exchange()
  260. * @var int
  261. * @access private
  262. */
  263. private $kex_dh_group_size_max = 4096;
  264. /**
  265. * Server Host Key Algorithms
  266. *
  267. * @see self::getServerHostKeyAlgorithms()
  268. * @var array|false
  269. * @access private
  270. */
  271. private $server_host_key_algorithms = false;
  272. /**
  273. * Encryption Algorithms: Client to Server
  274. *
  275. * @see self::getEncryptionAlgorithmsClient2Server()
  276. * @var array|false
  277. * @access private
  278. */
  279. private $encryption_algorithms_client_to_server = false;
  280. /**
  281. * Encryption Algorithms: Server to Client
  282. *
  283. * @see self::getEncryptionAlgorithmsServer2Client()
  284. * @var array|false
  285. * @access private
  286. */
  287. private $encryption_algorithms_server_to_client = false;
  288. /**
  289. * MAC Algorithms: Client to Server
  290. *
  291. * @see self::getMACAlgorithmsClient2Server()
  292. * @var array|false
  293. * @access private
  294. */
  295. private $mac_algorithms_client_to_server = false;
  296. /**
  297. * MAC Algorithms: Server to Client
  298. *
  299. * @see self::getMACAlgorithmsServer2Client()
  300. * @var array|false
  301. * @access private
  302. */
  303. private $mac_algorithms_server_to_client = false;
  304. /**
  305. * Compression Algorithms: Client to Server
  306. *
  307. * @see self::getCompressionAlgorithmsClient2Server()
  308. * @var array|false
  309. * @access private
  310. */
  311. private $compression_algorithms_client_to_server = false;
  312. /**
  313. * Compression Algorithms: Server to Client
  314. *
  315. * @see self::getCompressionAlgorithmsServer2Client()
  316. * @var array|false
  317. * @access private
  318. */
  319. private $compression_algorithms_server_to_client = false;
  320. /**
  321. * Languages: Server to Client
  322. *
  323. * @see self::getLanguagesServer2Client()
  324. * @var array|false
  325. * @access private
  326. */
  327. private $languages_server_to_client = false;
  328. /**
  329. * Languages: Client to Server
  330. *
  331. * @see self::getLanguagesClient2Server()
  332. * @var array|false
  333. * @access private
  334. */
  335. private $languages_client_to_server = false;
  336. /**
  337. * Preferred Algorithms
  338. *
  339. * @see self::setPreferredAlgorithms()
  340. * @var array
  341. * @access private
  342. */
  343. private $preferred = [];
  344. /**
  345. * Block Size for Server to Client Encryption
  346. *
  347. * "Note that the length of the concatenation of 'packet_length',
  348. * 'padding_length', 'payload', and 'random padding' MUST be a multiple
  349. * of the cipher block size or 8, whichever is larger. This constraint
  350. * MUST be enforced, even when using stream ciphers."
  351. *
  352. * -- http://tools.ietf.org/html/rfc4253#section-6
  353. *
  354. * @see self::__construct()
  355. * @see self::_send_binary_packet()
  356. * @var int
  357. * @access private
  358. */
  359. private $encrypt_block_size = 8;
  360. /**
  361. * Block Size for Client to Server Encryption
  362. *
  363. * @see self::__construct()
  364. * @see self::_get_binary_packet()
  365. * @var int
  366. * @access private
  367. */
  368. private $decrypt_block_size = 8;
  369. /**
  370. * Server to Client Encryption Object
  371. *
  372. * @see self::_get_binary_packet()
  373. * @var object
  374. * @access private
  375. */
  376. private $decrypt = false;
  377. /**
  378. * Server to Client Length Encryption Object
  379. *
  380. * @see self::_get_binary_packet()
  381. * @var object
  382. * @access private
  383. */
  384. private $lengthDecrypt = false;
  385. /**
  386. * Client to Server Encryption Object
  387. *
  388. * @see self::_send_binary_packet()
  389. * @var object
  390. * @access private
  391. */
  392. private $encrypt = false;
  393. /**
  394. * Client to Server Length Encryption Object
  395. *
  396. * @see self::_send_binary_packet()
  397. * @var object
  398. * @access private
  399. */
  400. private $lengthEncrypt = false;
  401. /**
  402. * Client to Server HMAC Object
  403. *
  404. * @see self::_send_binary_packet()
  405. * @var object
  406. * @access private
  407. */
  408. private $hmac_create = false;
  409. /**
  410. * Server to Client HMAC Object
  411. *
  412. * @see self::_get_binary_packet()
  413. * @var object
  414. * @access private
  415. */
  416. private $hmac_check = false;
  417. /**
  418. * Size of server to client HMAC
  419. *
  420. * We need to know how big the HMAC will be for the server to client direction so that we know how many bytes to read.
  421. * For the client to server side, the HMAC object will make the HMAC as long as it needs to be. All we need to do is
  422. * append it.
  423. *
  424. * @see self::_get_binary_packet()
  425. * @var int
  426. * @access private
  427. */
  428. private $hmac_size = false;
  429. /**
  430. * Server Public Host Key
  431. *
  432. * @see self::getServerPublicHostKey()
  433. * @var string
  434. * @access private
  435. */
  436. private $server_public_host_key;
  437. /**
  438. * Session identifier
  439. *
  440. * "The exchange hash H from the first key exchange is additionally
  441. * used as the session identifier, which is a unique identifier for
  442. * this connection."
  443. *
  444. * -- http://tools.ietf.org/html/rfc4253#section-7.2
  445. *
  446. * @see self::_key_exchange()
  447. * @var string
  448. * @access private
  449. */
  450. private $session_id = false;
  451. /**
  452. * Exchange hash
  453. *
  454. * The current exchange hash
  455. *
  456. * @see self::_key_exchange()
  457. * @var string
  458. * @access private
  459. */
  460. private $exchange_hash = false;
  461. /**
  462. * Message Numbers
  463. *
  464. * @see self::__construct()
  465. * @var array
  466. * @access private
  467. */
  468. private $message_numbers = [];
  469. /**
  470. * Disconnection Message 'reason codes' defined in RFC4253
  471. *
  472. * @see self::__construct()
  473. * @var array
  474. * @access private
  475. */
  476. private $disconnect_reasons = [];
  477. /**
  478. * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254
  479. *
  480. * @see self::__construct()
  481. * @var array
  482. * @access private
  483. */
  484. private $channel_open_failure_reasons = [];
  485. /**
  486. * Terminal Modes
  487. *
  488. * @link http://tools.ietf.org/html/rfc4254#section-8
  489. * @see self::__construct()
  490. * @var array
  491. * @access private
  492. */
  493. private $terminal_modes = [];
  494. /**
  495. * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes
  496. *
  497. * @link http://tools.ietf.org/html/rfc4254#section-5.2
  498. * @see self::__construct()
  499. * @var array
  500. * @access private
  501. */
  502. private $channel_extended_data_type_codes = [];
  503. /**
  504. * Send Sequence Number
  505. *
  506. * See 'Section 6.4. Data Integrity' of rfc4253 for more info.
  507. *
  508. * @see self::_send_binary_packet()
  509. * @var int
  510. * @access private
  511. */
  512. private $send_seq_no = 0;
  513. /**
  514. * Get Sequence Number
  515. *
  516. * See 'Section 6.4. Data Integrity' of rfc4253 for more info.
  517. *
  518. * @see self::_get_binary_packet()
  519. * @var int
  520. * @access private
  521. */
  522. private $get_seq_no = 0;
  523. /**
  524. * Server Channels
  525. *
  526. * Maps client channels to server channels
  527. *
  528. * @see self::get_channel_packet()
  529. * @see self::exec()
  530. * @var array
  531. * @access private
  532. */
  533. protected $server_channels = [];
  534. /**
  535. * Channel Buffers
  536. *
  537. * If a client requests a packet from one channel but receives two packets from another those packets should
  538. * be placed in a buffer
  539. *
  540. * @see self::get_channel_packet()
  541. * @see self::exec()
  542. * @var array
  543. * @access private
  544. */
  545. private $channel_buffers = [];
  546. /**
  547. * Channel Status
  548. *
  549. * Contains the type of the last sent message
  550. *
  551. * @see self::get_channel_packet()
  552. * @var array
  553. * @access private
  554. */
  555. protected $channel_status = [];
  556. /**
  557. * Packet Size
  558. *
  559. * Maximum packet size indexed by channel
  560. *
  561. * @see self::send_channel_packet()
  562. * @var array
  563. * @access private
  564. */
  565. private $packet_size_client_to_server = [];
  566. /**
  567. * Message Number Log
  568. *
  569. * @see self::getLog()
  570. * @var array
  571. * @access private
  572. */
  573. private $message_number_log = [];
  574. /**
  575. * Message Log
  576. *
  577. * @see self::getLog()
  578. * @var array
  579. * @access private
  580. */
  581. private $message_log = [];
  582. /**
  583. * The Window Size
  584. *
  585. * Bytes the other party can send before it must wait for the window to be adjusted (0x7FFFFFFF = 2GB)
  586. *
  587. * @var int
  588. * @see self::send_channel_packet()
  589. * @see self::exec()
  590. * @access private
  591. */
  592. protected $window_size = 0x7FFFFFFF;
  593. /**
  594. * What we resize the window to
  595. *
  596. * When PuTTY resizes the window it doesn't add an additional 0x7FFFFFFF bytes - it adds 0x40000000 bytes.
  597. * Some SFTP clients (GoAnywhere) don't support adding 0x7FFFFFFF to the window size after the fact so
  598. * we'll just do what PuTTY does
  599. *
  600. * @var int
  601. * @see self::_send_channel_packet()
  602. * @see self::exec()
  603. * @access private
  604. */
  605. private $window_resize = 0x40000000;
  606. /**
  607. * Window size, server to client
  608. *
  609. * Window size indexed by channel
  610. *
  611. * @see self::send_channel_packet()
  612. * @var array
  613. * @access private
  614. */
  615. protected $window_size_server_to_client = [];
  616. /**
  617. * Window size, client to server
  618. *
  619. * Window size indexed by channel
  620. *
  621. * @see self::get_channel_packet()
  622. * @var array
  623. * @access private
  624. */
  625. private $window_size_client_to_server = [];
  626. /**
  627. * Server signature
  628. *
  629. * Verified against $this->session_id
  630. *
  631. * @see self::getServerPublicHostKey()
  632. * @var string
  633. * @access private
  634. */
  635. private $signature = '';
  636. /**
  637. * Server signature format
  638. *
  639. * ssh-rsa or ssh-dss.
  640. *
  641. * @see self::getServerPublicHostKey()
  642. * @var string
  643. * @access private
  644. */
  645. private $signature_format = '';
  646. /**
  647. * Interactive Buffer
  648. *
  649. * @see self::read()
  650. * @var array
  651. * @access private
  652. */
  653. private $interactiveBuffer = '';
  654. /**
  655. * Current log size
  656. *
  657. * Should never exceed self::LOG_MAX_SIZE
  658. *
  659. * @see self::_send_binary_packet()
  660. * @see self::_get_binary_packet()
  661. * @var int
  662. * @access private
  663. */
  664. private $log_size;
  665. /**
  666. * Timeout
  667. *
  668. * @see self::setTimeout()
  669. * @access private
  670. */
  671. protected $timeout;
  672. /**
  673. * Current Timeout
  674. *
  675. * @see self::get_channel_packet()
  676. * @access private
  677. */
  678. protected $curTimeout;
  679. /**
  680. * Keep Alive Interval
  681. *
  682. * @see self::setKeepAlive()
  683. * @access private
  684. */
  685. private $keepAlive;
  686. /**
  687. * Real-time log file pointer
  688. *
  689. * @see self::_append_log()
  690. * @var resource
  691. * @access private
  692. */
  693. private $realtime_log_file;
  694. /**
  695. * Real-time log file size
  696. *
  697. * @see self::_append_log()
  698. * @var int
  699. * @access private
  700. */
  701. private $realtime_log_size;
  702. /**
  703. * Has the signature been validated?
  704. *
  705. * @see self::getServerPublicHostKey()
  706. * @var bool
  707. * @access private
  708. */
  709. private $signature_validated = false;
  710. /**
  711. * Real-time log file wrap boolean
  712. *
  713. * @see self::_append_log()
  714. * @access private
  715. */
  716. private $realtime_log_wrap;
  717. /**
  718. * Flag to suppress stderr from output
  719. *
  720. * @see self::enableQuietMode()
  721. * @access private
  722. */
  723. private $quiet_mode = false;
  724. /**
  725. * Time of first network activity
  726. *
  727. * @var int
  728. * @access private
  729. */
  730. private $last_packet;
  731. /**
  732. * Exit status returned from ssh if any
  733. *
  734. * @var int
  735. * @access private
  736. */
  737. private $exit_status;
  738. /**
  739. * Flag to request a PTY when using exec()
  740. *
  741. * @var bool
  742. * @see self::enablePTY()
  743. * @access private
  744. */
  745. private $request_pty = false;
  746. /**
  747. * Flag set while exec() is running when using enablePTY()
  748. *
  749. * @var bool
  750. * @access private
  751. */
  752. private $in_request_pty_exec = false;
  753. /**
  754. * Flag set after startSubsystem() is called
  755. *
  756. * @var bool
  757. * @access private
  758. */
  759. private $in_subsystem;
  760. /**
  761. * Contents of stdError
  762. *
  763. * @var string
  764. * @access private
  765. */
  766. private $stdErrorLog;
  767. /**
  768. * The Last Interactive Response
  769. *
  770. * @see self::_keyboard_interactive_process()
  771. * @var string
  772. * @access private
  773. */
  774. private $last_interactive_response = '';
  775. /**
  776. * Keyboard Interactive Request / Responses
  777. *
  778. * @see self::_keyboard_interactive_process()
  779. * @var array
  780. * @access private
  781. */
  782. private $keyboard_requests_responses = [];
  783. /**
  784. * Banner Message
  785. *
  786. * Quoting from the RFC, "in some jurisdictions, sending a warning message before
  787. * authentication may be relevant for getting legal protection."
  788. *
  789. * @see self::_filter()
  790. * @see self::getBannerMessage()
  791. * @var string
  792. * @access private
  793. */
  794. private $banner_message = '';
  795. /**
  796. * Did read() timeout or return normally?
  797. *
  798. * @see self::isTimeout()
  799. * @var bool
  800. * @access private
  801. */
  802. private $is_timeout = false;
  803. /**
  804. * Log Boundary
  805. *
  806. * @see self::_format_log()
  807. * @var string
  808. * @access private
  809. */
  810. private $log_boundary = ':';
  811. /**
  812. * Log Long Width
  813. *
  814. * @see self::_format_log()
  815. * @var int
  816. * @access private
  817. */
  818. private $log_long_width = 65;
  819. /**
  820. * Log Short Width
  821. *
  822. * @see self::_format_log()
  823. * @var int
  824. * @access private
  825. */
  826. private $log_short_width = 16;
  827. /**
  828. * Hostname
  829. *
  830. * @see self::__construct()
  831. * @see self::_connect()
  832. * @var string
  833. * @access private
  834. */
  835. private $host;
  836. /**
  837. * Port Number
  838. *
  839. * @see self::__construct()
  840. * @see self::_connect()
  841. * @var int
  842. * @access private
  843. */
  844. private $port;
  845. /**
  846. * Number of columns for terminal window size
  847. *
  848. * @see self::getWindowColumns()
  849. * @see self::setWindowColumns()
  850. * @see self::setWindowSize()
  851. * @var int
  852. * @access private
  853. */
  854. private $windowColumns = 80;
  855. /**
  856. * Number of columns for terminal window size
  857. *
  858. * @see self::getWindowRows()
  859. * @see self::setWindowRows()
  860. * @see self::setWindowSize()
  861. * @var int
  862. * @access private
  863. */
  864. private $windowRows = 24;
  865. /**
  866. * Crypto Engine
  867. *
  868. * @see self::setCryptoEngine()
  869. * @see self::_key_exchange()
  870. * @var int
  871. * @access private
  872. */
  873. private static $crypto_engine = false;
  874. /**
  875. * A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario
  876. *
  877. * @var \phpseclib3\System\Ssh\Agent
  878. * @access private
  879. */
  880. private $agent;
  881. /**
  882. * Connection storage to replicates ssh2 extension functionality:
  883. * {@link http://php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-examples}
  884. *
  885. * @var SSH2[]
  886. */
  887. private static $connections;
  888. /**
  889. * Send the identification string first?
  890. *
  891. * @var bool
  892. * @access private
  893. */
  894. private $send_id_string_first = true;
  895. /**
  896. * Send the key exchange initiation packet first?
  897. *
  898. * @var bool
  899. * @access private
  900. */
  901. private $send_kex_first = true;
  902. /**
  903. * Some versions of OpenSSH incorrectly calculate the key size
  904. *
  905. * @var bool
  906. * @access private
  907. */
  908. private $bad_key_size_fix = false;
  909. /**
  910. * Should we try to re-connect to re-establish keys?
  911. *
  912. * @var bool
  913. * @access private
  914. */
  915. private $retry_connect = false;
  916. /**
  917. * Binary Packet Buffer
  918. *
  919. * @var string|false
  920. * @access private
  921. */
  922. private $binary_packet_buffer = false;
  923. /**
  924. * Preferred Signature Format
  925. *
  926. * @var string|false
  927. * @access private
  928. */
  929. protected $preferred_signature_format = false;
  930. /**
  931. * Authentication Credentials
  932. *
  933. * @var array
  934. * @access private
  935. */
  936. protected $auth = [];
  937. /**
  938. * Terminal
  939. *
  940. * @var string
  941. * @access private
  942. */
  943. private $term = 'vt100';
  944. /**
  945. * The authentication methods that may productively continue authentication.
  946. *
  947. * @see https://tools.ietf.org/html/rfc4252#section-5.1
  948. * @var array|null
  949. * @access private
  950. */
  951. private $auth_methods_to_continue = null;
  952. /**
  953. * Compression method
  954. *
  955. * @var int
  956. * @access private
  957. */
  958. private $compress = NET_SSH2_COMPRESSION_NONE;
  959. /**
  960. * Decompression method
  961. *
  962. * @var resource|object
  963. * @access private
  964. */
  965. private $decompress = NET_SSH2_COMPRESSION_NONE;
  966. /**
  967. * Compression context
  968. *
  969. * @var int
  970. * @access private
  971. */
  972. private $compress_context;
  973. /**
  974. * Decompression context
  975. *
  976. * @var resource|object
  977. * @access private
  978. */
  979. private $decompress_context;
  980. /**
  981. * Regenerate Compression Context
  982. *
  983. * @var bool
  984. * @access private
  985. */
  986. private $regenerate_compression_context = false;
  987. /**
  988. * Regenerate Decompression Context
  989. *
  990. * @var bool
  991. * @access private
  992. */
  993. private $regenerate_decompression_context = false;
  994. /**
  995. * Smart multi-factor authentication flag
  996. *
  997. * @var bool
  998. * @access private
  999. */
  1000. private $smartMFA = true;
  1001. /**
  1002. * Default Constructor.
  1003. *
  1004. * $host can either be a string, representing the host, or a stream resource.
  1005. *
  1006. * @param mixed $host
  1007. * @param int $port
  1008. * @param int $timeout
  1009. * @see self::login()
  1010. * @return SSH2|void
  1011. * @access public
  1012. */
  1013. public function __construct($host, $port = 22, $timeout = 10)
  1014. {
  1015. $this->message_numbers = [
  1016. 1 => 'NET_SSH2_MSG_DISCONNECT',
  1017. 2 => 'NET_SSH2_MSG_IGNORE',
  1018. 3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
  1019. 4 => 'NET_SSH2_MSG_DEBUG',
  1020. 5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
  1021. 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
  1022. 20 => 'NET_SSH2_MSG_KEXINIT',
  1023. 21 => 'NET_SSH2_MSG_NEWKEYS',
  1024. 30 => 'NET_SSH2_MSG_KEXDH_INIT',
  1025. 31 => 'NET_SSH2_MSG_KEXDH_REPLY',
  1026. 50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
  1027. 51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
  1028. 52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
  1029. 53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
  1030. 80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
  1031. 81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
  1032. 82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
  1033. 90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
  1034. 91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
  1035. 92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
  1036. 93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
  1037. 94 => 'NET_SSH2_MSG_CHANNEL_DATA',
  1038. 95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
  1039. 96 => 'NET_SSH2_MSG_CHANNEL_EOF',
  1040. 97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
  1041. 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
  1042. 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
  1043. 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
  1044. ];
  1045. $this->disconnect_reasons = [
  1046. 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
  1047. 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
  1048. 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
  1049. 4 => 'NET_SSH2_DISCONNECT_RESERVED',
  1050. 5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
  1051. 6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
  1052. 7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
  1053. 8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
  1054. 9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
  1055. 10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
  1056. 11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
  1057. 12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
  1058. 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
  1059. 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
  1060. 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
  1061. ];
  1062. $this->channel_open_failure_reasons = [
  1063. 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
  1064. ];
  1065. $this->terminal_modes = [
  1066. 0 => 'NET_SSH2_TTY_OP_END'
  1067. ];
  1068. $this->channel_extended_data_type_codes = [
  1069. 1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
  1070. ];
  1071. $this->define_array(
  1072. $this->message_numbers,
  1073. $this->disconnect_reasons,
  1074. $this->channel_open_failure_reasons,
  1075. $this->terminal_modes,
  1076. $this->channel_extended_data_type_codes,
  1077. [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
  1078. [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
  1079. [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
  1080. 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
  1081. // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
  1082. [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
  1083. 31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
  1084. 32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
  1085. 33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
  1086. 34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
  1087. // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
  1088. [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
  1089. 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
  1090. );
  1091. self::$connections[$this->getResourceId()] = class_exists('WeakReference') ? \WeakReference::create($this) : $this;
  1092. if (is_resource($host)) {
  1093. $this->fsock = $host;
  1094. return;
  1095. }
  1096. if (is_string($host)) {
  1097. $this->host = $host;
  1098. $this->port = $port;
  1099. $this->timeout = $timeout;
  1100. }
  1101. }
  1102. /**
  1103. * Set Crypto Engine Mode
  1104. *
  1105. * Possible $engine values:
  1106. * OpenSSL, mcrypt, Eval, PHP
  1107. *
  1108. * @param int $engine
  1109. * @access public
  1110. */
  1111. public static function setCryptoEngine($engine)
  1112. {
  1113. self::$crypto_engine = $engine;
  1114. }
  1115. /**
  1116. * Send Identification String First
  1117. *
  1118. * https://tools.ietf.org/html/rfc4253#section-4.2 says "when the connection has been established,
  1119. * both sides MUST send an identification string". It does not say which side sends it first. In
  1120. * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
  1121. *
  1122. * @access public
  1123. */
  1124. public function sendIdentificationStringFirst()
  1125. {
  1126. $this->send_id_string_first = true;
  1127. }
  1128. /**
  1129. * Send Identification String Last
  1130. *
  1131. * https://tools.ietf.org/html/rfc4253#section-4.2 says "when the connection has been established,
  1132. * both sides MUST send an identification string". It does not say which side sends it first. In
  1133. * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
  1134. *
  1135. * @access public
  1136. */
  1137. public function sendIdentificationStringLast()
  1138. {
  1139. $this->send_id_string_first = false;
  1140. }
  1141. /**
  1142. * Send SSH_MSG_KEXINIT First
  1143. *
  1144. * https://tools.ietf.org/html/rfc4253#section-7.1 says "key exchange begins by each sending
  1145. * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory
  1146. * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
  1147. *
  1148. * @access public
  1149. */
  1150. public function sendKEXINITFirst()
  1151. {
  1152. $this->send_kex_first = true;
  1153. }
  1154. /**
  1155. * Send SSH_MSG_KEXINIT Last
  1156. *
  1157. * https://tools.ietf.org/html/rfc4253#section-7.1 says "key exchange begins by each sending
  1158. * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory
  1159. * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
  1160. *
  1161. * @access public
  1162. */
  1163. public function sendKEXINITLast()
  1164. {
  1165. $this->send_kex_first = false;
  1166. }
  1167. /**
  1168. * Connect to an SSHv2 server
  1169. *
  1170. * @throws \UnexpectedValueException on receipt of unexpected packets
  1171. * @throws \RuntimeException on other errors
  1172. * @access private
  1173. */
  1174. private function connect()
  1175. {
  1176. if ($this->bitmap & self::MASK_CONSTRUCTOR) {
  1177. return;
  1178. }
  1179. $this->bitmap |= self::MASK_CONSTRUCTOR;
  1180. $this->curTimeout = $this->timeout;
  1181. $this->last_packet = microtime(true);
  1182. if (!is_resource($this->fsock)) {
  1183. $start = microtime(true);
  1184. // with stream_select a timeout of 0 means that no timeout takes place;
  1185. // with fsockopen a timeout of 0 means that you instantly timeout
  1186. // to resolve this incompatibility a timeout of 100,000 will be used for fsockopen if timeout is 0
  1187. $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout == 0 ? 100000 : $this->curTimeout);
  1188. if (!$this->fsock) {
  1189. $host = $this->host . ':' . $this->port;
  1190. throw new UnableToConnectException(rtrim("Cannot connect to $host. Error $errno. $errstr"));
  1191. }
  1192. $elapsed = microtime(true) - $start;
  1193. if ($this->curTimeout) {
  1194. $this->curTimeout-= $elapsed;
  1195. if ($this->curTimeout < 0) {
  1196. throw new \RuntimeException('Connection timed out whilst attempting to open socket connection');
  1197. }
  1198. }
  1199. }
  1200. $this->identifier = $this->generate_identifier();
  1201. if ($this->send_id_string_first) {
  1202. fputs($this->fsock, $this->identifier . "\r\n");
  1203. }
  1204. /* According to the SSH2 specs,
  1205. "The server MAY send other lines of data before sending the version
  1206. string. Each line SHOULD be terminated by a Carriage Return and Line
  1207. Feed. Such lines MUST NOT begin with "SSH-", and SHOULD be encoded
  1208. in ISO-10646 UTF-8 [RFC3629] (language is not specified). Clients
  1209. MUST be able to process such lines." */
  1210. $data = '';
  1211. while (!feof($this->fsock) && !preg_match('#(.*)^(SSH-(\d\.\d+).*)#ms', $data, $matches)) {
  1212. $line = '';
  1213. while (true) {
  1214. if ($this->curTimeout) {
  1215. if ($this->curTimeout < 0) {
  1216. throw new \RuntimeException('Connection timed out whilst receiving server identification string');
  1217. }
  1218. $read = [$this->fsock];
  1219. $write = $except = null;
  1220. $start = microtime(true);
  1221. $sec = floor($this->curTimeout);
  1222. $usec = 1000000 * ($this->curTimeout - $sec);
  1223. if (@stream_select($read, $write, $except, $sec, $usec) === false) {
  1224. throw new \RuntimeException('Connection timed out whilst receiving server identification string');
  1225. }
  1226. $elapsed = microtime(true) - $start;
  1227. $this->curTimeout-= $elapsed;
  1228. }
  1229. $temp = stream_get_line($this->fsock, 255, "\n");
  1230. if ($temp === false) {
  1231. throw new \RuntimeException('Error reading from socket');
  1232. }
  1233. if (strlen($temp) == 255) {
  1234. continue;
  1235. }
  1236. $line.= "$temp\n";
  1237. // quoting RFC4253, "Implementers who wish to maintain
  1238. // compatibility with older, undocumented versions of this protocol may
  1239. // want to process the identification string without expecting the
  1240. // presence of the carriage return character for reasons described in
  1241. // Section 5 of this document."
  1242. //if (substr($line, -2) == "\r\n") {
  1243. // break;
  1244. //}
  1245. break;
  1246. }
  1247. $data.= $line;
  1248. }
  1249. if (feof($this->fsock)) {
  1250. $this->bitmap = 0;
  1251. throw new ConnectionClosedException('Connection closed by server');
  1252. }
  1253. $extra = $matches[1];
  1254. if (defined('NET_SSH2_LOGGING')) {
  1255. $this->append_log('<-', $matches[0]);
  1256. $this->append_log('->', $this->identifier . "\r\n");
  1257. }
  1258. $this->server_identifier = trim($temp, "\r\n");
  1259. if (strlen($extra)) {
  1260. $this->errors[] = $data;
  1261. }
  1262. if (version_compare($matches[3], '1.99', '<')) {
  1263. $this->bitmap = 0;
  1264. throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers");
  1265. }
  1266. if (!$this->send_id_string_first) {
  1267. fputs($this->fsock, $this->identifier . "\r\n");
  1268. }
  1269. if (!$this->send_kex_first) {
  1270. $response = $this->get_binary_packet();
  1271. if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) {
  1272. $this->bitmap = 0;
  1273. throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT');
  1274. }
  1275. $this->key_exchange($response);
  1276. }
  1277. if ($this->send_kex_first) {
  1278. $this->key_exchange();
  1279. }
  1280. $this->bitmap|= self::MASK_CONNECTED;
  1281. return true;
  1282. }
  1283. /**
  1284. * Generates the SSH identifier
  1285. *
  1286. * You should overwrite this method in your own class if you want to use another identifier
  1287. *
  1288. * @access protected
  1289. * @return string
  1290. */
  1291. private function generate_identifier()
  1292. {
  1293. $identifier = 'SSH-2.0-phpseclib_3.0';
  1294. $ext = [];
  1295. if (extension_loaded('sodium')) {
  1296. $ext[] = 'libsodium';
  1297. }
  1298. if (extension_loaded('openssl')) {
  1299. $ext[] = 'openssl';
  1300. } elseif (extension_loaded('mcrypt')) {
  1301. $ext[] = 'mcrypt';
  1302. }
  1303. if (extension_loaded('gmp')) {
  1304. $ext[] = 'gmp';
  1305. } elseif (extension_loaded('bcmath')) {
  1306. $ext[] = 'bcmath';
  1307. }
  1308. if (!empty($ext)) {
  1309. $identifier .= ' (' . implode(', ', $ext) . ')';
  1310. }
  1311. return $identifier;
  1312. }
  1313. /**
  1314. * Key Exchange
  1315. *
  1316. * @return bool
  1317. * @param string|bool $kexinit_payload_server optional
  1318. * @throws \UnexpectedValueException on receipt of unexpected packets
  1319. * @throws \RuntimeException on other errors
  1320. * @throws \phpseclib3\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible
  1321. * @access private
  1322. */
  1323. private function key_exchange($kexinit_payload_server = false)
  1324. {
  1325. $preferred = $this->preferred;
  1326. $send_kex = true;
  1327. $kex_algorithms = isset($preferred['kex']) ?
  1328. $preferred['kex'] :
  1329. SSH2::getSupportedKEXAlgorithms();
  1330. $server_host_key_algorithms = isset($preferred['hostkey']) ?
  1331. $preferred['hostkey'] :
  1332. SSH2::getSupportedHostKeyAlgorithms();
  1333. $s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ?
  1334. $preferred['server_to_client']['crypt'] :
  1335. SSH2::getSupportedEncryptionAlgorithms();
  1336. $c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ?
  1337. $preferred['client_to_server']['crypt'] :
  1338. SSH2::getSupportedEncryptionAlgorithms();
  1339. $s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ?
  1340. $preferred['server_to_client']['mac'] :
  1341. SSH2::getSupportedMACAlgorithms();
  1342. $c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ?
  1343. $preferred['client_to_server']['mac'] :
  1344. SSH2::getSupportedMACAlgorithms();
  1345. $s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ?
  1346. $preferred['server_to_client']['comp'] :
  1347. SSH2::getSupportedCompressionAlgorithms();
  1348. $c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ?
  1349. $preferred['client_to_server']['comp'] :
  1350. SSH2::getSupportedCompressionAlgorithms();
  1351. // some SSH servers have buggy implementations of some of the above algorithms
  1352. switch (true) {
  1353. case $this->server_identifier == 'SSH-2.0-SSHD':
  1354. case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK':
  1355. if (!isset($preferred['server_to_client']['mac'])) {
  1356. $s2c_mac_algorithms = array_values(array_diff(
  1357. $s2c_mac_algorithms,
  1358. ['hmac-sha1-96', 'hmac-md5-96']
  1359. ));
  1360. }
  1361. if (!isset($preferred['client_to_server']['mac'])) {
  1362. $c2s_mac_algorithms = array_values(array_diff(
  1363. $c2s_mac_algorithms,
  1364. ['hmac-sha1-96', 'hmac-md5-96']
  1365. ));
  1366. }
  1367. }
  1368. $client_cookie = Random::string(16);
  1369. $kexinit_payload_client = pack('Ca*', NET_SSH2_MSG_KEXINIT, $client_cookie);
  1370. $kexinit_payload_client.= Strings::packSSH2(
  1371. 'L10bN',
  1372. $kex_algorithms,
  1373. $server_host_key_algorithms,
  1374. $c2s_encryption_algorithms,
  1375. $s2c_encryption_algorithms,
  1376. $c2s_mac_algorithms,
  1377. $s2c_mac_algorithms,
  1378. $c2s_compression_algorithms,
  1379. $s2c_compression_algorithms,
  1380. [], // language, client to server
  1381. [], // language, server to client
  1382. false, // first_kex_packet_follows
  1383. 0 // reserved for future extension
  1384. );
  1385. if ($kexinit_payload_server === false) {
  1386. $this->send_binary_packet($kexinit_payload_client);
  1387. $kexinit_payload_server = $this->get_binary_packet();
  1388. if (!strlen($kexinit_payload_server) || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT) {
  1389. $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
  1390. throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT');
  1391. }
  1392. $send_kex = false;
  1393. }
  1394. $response = $kexinit_payload_server;
  1395. Strings::shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)
  1396. $server_cookie = Strings::shift($response, 16);
  1397. list(
  1398. $this->kex_algorithms,
  1399. $this->server_host_key_algorithms,
  1400. $this->encryption_algorithms_client_to_server,
  1401. $this->encryption_algorithms_server_to_client,
  1402. $this->mac_algorithms_client_to_server,
  1403. $this->mac_algorithms_server_to_client,
  1404. $this->compression_algorithms_client_to_server,
  1405. $this->compression_algorithms_server_to_client,
  1406. $this->languages_client_to_server,
  1407. $this->languages_server_to_client,
  1408. $first_kex_packet_follows
  1409. ) = Strings::unpackSSH2('L10C', $response);
  1410. if ($send_kex) {
  1411. $this->send_binary_packet($kexinit_payload_client);
  1412. }
  1413. // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
  1414. // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
  1415. // diffie-hellman key exchange as fast as possible
  1416. $decrypt = self::array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
  1417. $decryptKeyLength = $this->encryption_algorithm_to_key_size($decrypt);
  1418. if ($decryptKeyLength === null) {
  1419. $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  1420. throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found');
  1421. }
  1422. $encrypt = self::array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
  1423. $encryptKeyLength = $this->encryption_algorithm_to_key_size($encrypt);
  1424. if ($encryptKeyLength === null) {
  1425. $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  1426. throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found');
  1427. }
  1428. // through diffie-hellman key exchange a symmetric key is obtained
  1429. $this->kex_algorithm = self::array_intersect_first($kex_algorithms, $this->kex_algorithms);
  1430. if ($this->kex_algorithm === false) {
  1431. $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  1432. throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found');
  1433. }
  1434. $server_host_key_algorithm = self::array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
  1435. if ($server_host_key_algorithm === false) {
  1436. $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  1437. throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found');
  1438. }
  1439. $mac_algorithm_out = self::array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
  1440. if ($mac_algorithm_out === false) {
  1441. $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  1442. throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found');
  1443. }
  1444. $mac_algorithm_in = self::array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
  1445. if ($mac_algorithm_in === false) {
  1446. $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  1447. throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found');
  1448. }
  1449. $compression_map = [
  1450. 'none' => NET_SSH2_COMPRESSION_NONE,
  1451. 'zlib' => NET_SSH2_COMPRESSION_ZLIB,
  1452. 'zlib@openssh.com' => NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH
  1453. ];
  1454. $compression_algorithm_in = self::array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
  1455. if ($compression_algorithm_in === false) {
  1456. $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  1457. throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found');
  1458. }
  1459. $this->decompress = $compression_map[$compression_algorithm_in];
  1460. $compression_algorithm_out = self::array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
  1461. if ($compression_algorithm_out === false) {
  1462. $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  1463. throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found');
  1464. }
  1465. $this->compress = $compression_map[$compression_algorithm_out];
  1466. switch ($this->kex_algorithm) {
  1467. case 'diffie-hellman-group15-sha512':
  1468. case 'diffie-hellman-group16-sha512':
  1469. case 'diffie-hellman-group17-sha512':
  1470. case 'diffie-hellman-group18-sha512':
  1471. case 'ecdh-sha2-nistp521':
  1472. $kexHash = new Hash('sha512');
  1473. break;
  1474. case 'ecdh-sha2-nistp384':
  1475. $kexHash = new Hash('sha384');
  1476. break;
  1477. case 'diffie-hellman-group-exchange-sha256':
  1478. case 'diffie-hellman-group14-sha256':
  1479. case 'ecdh-sha2-nistp256':
  1480. case 'curve25519-sha256@libssh.org':
  1481. case 'curve25519-sha256':
  1482. $kexHash = new Hash('sha256');
  1483. break;
  1484. default:
  1485. $kexHash = new Hash('sha1');
  1486. }
  1487. // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
  1488. $exchange_hash_rfc4419 = '';
  1489. if (strpos($this->kex_algorithm, 'curve25519-sha256') === 0 || strpos($this->kex_algorithm, 'ecdh-sha2-nistp') === 0) {
  1490. $curve = strpos($this->kex_algorithm, 'curve25519-sha256') === 0 ?
  1491. 'Curve25519' :
  1492. substr($this->kex_algorithm, 10);
  1493. $ourPrivate = EC::createKey($curve);
  1494. $ourPublicBytes = $ourPrivate->getPublicKey()->getEncodedCoordinates();
  1495. $clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT';
  1496. $serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY';
  1497. } else {
  1498. if (strpos($this->kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
  1499. $dh_group_sizes_packed = pack(
  1500. 'NNN',
  1501. $this->kex_dh_group_size_min,
  1502. $this->kex_dh_group_size_preferred,
  1503. $this->kex_dh_group_size_max
  1504. );
  1505. $packet = pack(
  1506. 'Ca*',
  1507. NET_SSH2_MSG_KEXDH_GEX_REQUEST,
  1508. $dh_group_sizes_packed
  1509. );
  1510. $this->send_binary_packet($packet);
  1511. $this->updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST');
  1512. $response = $this->get_binary_packet();
  1513. list($type, $primeBytes, $gBytes) = Strings::unpackSSH2('Css', $response);
  1514. if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) {
  1515. $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
  1516. throw new \UnexpectedValueException('Expected SSH_MSG_KEX_DH_GEX_GROUP');
  1517. }
  1518. $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP');
  1519. $prime = new BigInteger($primeBytes, -256);
  1520. $g = new BigInteger($gBytes, -256);
  1521. $exchange_hash_rfc4419 = $dh_group_sizes_packed . Strings::packSSH2(
  1522. 'ss',
  1523. $primeBytes,
  1524. $gBytes
  1525. );
  1526. $params = DH::createParameters($prime, $g);
  1527. $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT';
  1528. $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY';
  1529. } else {
  1530. $params = DH::createParameters($this->kex_algorithm);
  1531. $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT';
  1532. $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY';
  1533. }
  1534. $keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength));
  1535. $ourPrivate = DH::createKey($params, 16 * $keyLength); // 2 * 8 * $keyLength
  1536. $ourPublic = $ourPrivate->getPublicKey()->toBigInteger();
  1537. $ourPublicBytes = $ourPublic->toBytes(true);
  1538. }
  1539. $data = pack('CNa*', constant($clientKexInitMessage), strlen($ourPublicBytes), $ourPublicBytes);
  1540. $this->send_binary_packet($data);
  1541. switch ($clientKexInitMessage) {
  1542. case 'NET_SSH2_MSG_KEX_ECDH_INIT':
  1543. $this->updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT');
  1544. break;
  1545. case 'NET_SSH2_MSG_KEXDH_GEX_INIT':
  1546. $this->updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT');
  1547. }
  1548. $response = $this->get_binary_packet();
  1549. list(
  1550. $type,
  1551. $server_public_host_key,
  1552. $theirPublicBytes,
  1553. $this->signature
  1554. ) = Strings::unpackSSH2('Csss', $response);
  1555. if ($type != constant($serverKexReplyMessage)) {
  1556. $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
  1557. throw new \UnexpectedValueException("Expected $serverKexReplyMessage");
  1558. }
  1559. switch ($serverKexReplyMessage) {
  1560. case 'NET_SSH2_MSG_KEX_ECDH_REPLY':
  1561. $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY');
  1562. break;
  1563. case 'NET_SSH2_MSG_KEXDH_GEX_REPLY':
  1564. $this->updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY');
  1565. }
  1566. $this->server_public_host_key = $server_public_host_key;
  1567. list($public_key_format) = Strings::unpackSSH2('s', $server_public_host_key);
  1568. if (strlen($this->signature) < 4) {
  1569. throw new \LengthException('The signature needs at least four bytes');
  1570. }
  1571. $temp = unpack('Nlength', substr($this->signature, 0, 4));
  1572. $this->signature_format = substr($this->signature, 4, $temp['length']);
  1573. $keyBytes = DH::computeSecret($ourPrivate, $theirPublicBytes);
  1574. if (($keyBytes & "\xFF\x80") === "\x00\x00") {
  1575. $keyBytes = substr($keyBytes, 1);
  1576. } elseif (($keyBytes[0] & "\x80") === "\x80") {
  1577. $keyBytes = "\0$keyBytes";
  1578. }
  1579. $this->exchange_hash = Strings::packSSH2('s5',
  1580. $this->identifier,
  1581. $this->server_identifier,
  1582. $kexinit_payload_client,
  1583. $kexinit_payload_server,
  1584. $this->server_public_host_key
  1585. );
  1586. $this->exchange_hash.= $exchange_hash_rfc4419;
  1587. $this->exchange_hash.= Strings::packSSH2('s3',
  1588. $ourPublicBytes,
  1589. $theirPublicBytes,
  1590. $keyBytes
  1591. );
  1592. $this->exchange_hash = $kexHash->hash($this->exchange_hash);
  1593. if ($this->session_id === false) {
  1594. $this->session_id = $this->exchange_hash;
  1595. }
  1596. switch ($server_host_key_algorithm) {
  1597. case 'rsa-sha2-256':
  1598. case 'rsa-sha2-512':
  1599. //case 'ssh-rsa':
  1600. $expected_key_format = 'ssh-rsa';
  1601. break;
  1602. default:
  1603. $expected_key_format = $server_host_key_algorithm;
  1604. }
  1605. if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) {
  1606. switch (true) {
  1607. case $this->signature_format == $server_host_key_algorithm:
  1608. case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512':
  1609. case $this->signature_format != 'ssh-rsa':
  1610. $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
  1611. throw new \RuntimeException('Server Host Key Algorithm Mismatch (' . $this->signature_format . ' vs ' . $server_host_key_algorithm . ')');
  1612. }
  1613. }
  1614. $packet = pack('C', NET_SSH2_MSG_NEWKEYS);
  1615. $this->send_binary_packet($packet);
  1616. $response = $this->get_binary_packet();
  1617. if ($response === false) {
  1618. $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
  1619. throw new ConnectionClosedException('Connection closed by server');
  1620. }
  1621. list($type) = Strings::unpackSSH2('C', $response);
  1622. if ($type != NET_SSH2_MSG_NEWKEYS) {
  1623. $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
  1624. throw new \UnexpectedValueException('Expected SSH_MSG_NEWKEYS');
  1625. }
  1626. $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
  1627. $this->encrypt = self::encryption_algorithm_to_crypt_instance($encrypt);
  1628. if ($this->encrypt) {
  1629. if (self::$crypto_engine) {
  1630. $this->encrypt->setPreferredEngine(self::$crypto_engine);
  1631. }
  1632. if ($this->encrypt->getBlockLengthInBytes()) {
  1633. $this->encrypt_block_size = $this->encrypt->getBlockLengthInBytes();
  1634. }
  1635. $this->encrypt->disablePadding();
  1636. if ($this->encrypt->usesIV()) {
  1637. $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
  1638. while ($this->encrypt_block_size > strlen($iv)) {
  1639. $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
  1640. }
  1641. $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
  1642. }
  1643. switch ($encrypt) {
  1644. case 'aes128-gcm@openssh.com':
  1645. case 'aes256-gcm@openssh.com':
  1646. $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
  1647. $this->encrypt->fixed = substr($nonce, 0, 4);
  1648. $this->encrypt->invocation_counter = substr($nonce, 4, 8);
  1649. case 'chacha20-poly1305@openssh.com':
  1650. break;
  1651. default:
  1652. $this->encrypt->enableContinuousBuffer();
  1653. }
  1654. $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
  1655. while ($encryptKeyLength > strlen($key)) {
  1656. $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
  1657. }
  1658. switch ($encrypt) {
  1659. case 'chacha20-poly1305@openssh.com':
  1660. $encryptKeyLength = 32;
  1661. $this->lengthEncrypt = self::encryption_algorithm_to_crypt_instance($encrypt);
  1662. $this->lengthEncrypt->setKey(substr($key, 32, 32));
  1663. }
  1664. $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
  1665. $this->encrypt->name = $encrypt;
  1666. }
  1667. $this->decrypt = self::encryption_algorithm_to_crypt_instance($decrypt);
  1668. if ($this->decrypt) {
  1669. if (self::$crypto_engine) {
  1670. $this->decrypt->setPreferredEngine(self::$crypto_engine);
  1671. }
  1672. if ($this->decrypt->getBlockLengthInBytes()) {
  1673. $this->decrypt_block_size = $this->decrypt->getBlockLengthInBytes();
  1674. }
  1675. $this->decrypt->disablePadding();
  1676. if ($this->decrypt->usesIV()) {
  1677. $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
  1678. while ($this->decrypt_block_size > strlen($iv)) {
  1679. $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
  1680. }
  1681. $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
  1682. }
  1683. switch ($decrypt) {
  1684. case 'aes128-gcm@openssh.com':
  1685. case 'aes256-gcm@openssh.com':
  1686. // see https://tools.ietf.org/html/rfc5647#section-7.1
  1687. $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
  1688. $this->decrypt->fixed = substr($nonce, 0, 4);
  1689. $this->decrypt->invocation_counter = substr($nonce, 4, 8);
  1690. case 'chacha20-poly1305@openssh.com':
  1691. break;
  1692. default:
  1693. $this->decrypt->enableContinuousBuffer();
  1694. }
  1695. $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
  1696. while ($decryptKeyLength > strlen($key)) {
  1697. $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
  1698. }
  1699. switch ($decrypt) {
  1700. case 'chacha20-poly1305@openssh.com':
  1701. $decryptKeyLength = 32;
  1702. $this->lengthDecrypt = self::encryption_algorithm_to_crypt_instance($decrypt);
  1703. $this->lengthDecrypt->setKey(substr($key, 32, 32));
  1704. }
  1705. $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
  1706. $this->decrypt->name = $decrypt;
  1707. }
  1708. /* The "arcfour128" algorithm is the RC4 cipher, as described in
  1709. [SCHNEIER], using a 128-bit key. The first 1536 bytes of keystream
  1710. generated by the cipher MUST be discarded, and the first byte of the
  1711. first encrypted packet MUST be encrypted using the 1537th byte of
  1712. keystream.
  1713. -- http://tools.ietf.org/html/rfc4345#section-4 */
  1714. if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') {
  1715. $this->encrypt->encrypt(str_repeat("\0", 1536));
  1716. }
  1717. if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') {
  1718. $this->decrypt->decrypt(str_repeat("\0", 1536));
  1719. }
  1720. if (!$this->encrypt->usesNonce()) {
  1721. list($this->hmac_create, $createKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_out);
  1722. } else {
  1723. $this->hmac_create = new \stdClass;
  1724. $this->hmac_create->name = $mac_algorithm_out;
  1725. //$mac_algorithm_out = 'none';
  1726. $createKeyLength = 0;
  1727. }
  1728. if ($this->hmac_create instanceof Hash) {
  1729. $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
  1730. while ($createKeyLength > strlen($key)) {
  1731. $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
  1732. }
  1733. $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
  1734. $this->hmac_create->name = $mac_algorithm_out;
  1735. $this->hmac_create->etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_out);
  1736. }
  1737. if (!$this->decrypt->usesNonce()) {
  1738. list($this->hmac_check, $checkKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_in);
  1739. $this->hmac_size = $this->hmac_check->getLengthInBytes();
  1740. } else {
  1741. $this->hmac_check = new \stdClass;
  1742. $this->hmac_check->name = $mac_algorithm_in;
  1743. //$mac_algorithm_in = 'none';
  1744. $checkKeyLength = 0;
  1745. $this->hmac_size = 0;
  1746. }
  1747. if ($this->hmac_check instanceof Hash) {
  1748. $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
  1749. while ($checkKeyLength > strlen($key)) {
  1750. $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
  1751. }
  1752. $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
  1753. $this->hmac_check->name = $mac_algorithm_in;
  1754. $this->hmac_check->etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_in);
  1755. }
  1756. $this->regenerate_compression_context = $this->regenerate_decompression_context = true;
  1757. return true;
  1758. }
  1759. /**
  1760. * Maps an encryption algorithm name to the number of key bytes.
  1761. *
  1762. * @param string $algorithm Name of the encryption algorithm
  1763. * @return int|null Number of bytes as an integer or null for unknown
  1764. * @access private
  1765. */
  1766. private function encryption_algorithm_to_key_size($algorithm)
  1767. {
  1768. if ($this->bad_key_size_fix && self::bad_algorithm_candidate($algorithm)) {
  1769. return 16;
  1770. }
  1771. switch ($algorithm) {
  1772. case 'none':
  1773. return 0;
  1774. case 'aes128-gcm@openssh.com':
  1775. case 'aes128-cbc':
  1776. case 'aes128-ctr':
  1777. case 'arcfour':
  1778. case 'arcfour128':
  1779. case 'blowfish-cbc':
  1780. case 'blowfish-ctr':
  1781. case 'twofish128-cbc':
  1782. case 'twofish128-ctr':
  1783. return 16;
  1784. case '3des-cbc':
  1785. case '3des-ctr':
  1786. case 'aes192-cbc':
  1787. case 'aes192-ctr':
  1788. case 'twofish192-cbc':
  1789. case 'twofish192-ctr':
  1790. return 24;
  1791. case 'aes256-gcm@openssh.com':
  1792. case 'aes256-cbc':
  1793. case 'aes256-ctr':
  1794. case 'arcfour256':
  1795. case 'twofish-cbc':
  1796. case 'twofish256-cbc':
  1797. case 'twofish256-ctr':
  1798. return 32;
  1799. case 'chacha20-poly1305@openssh.com':
  1800. return 64;
  1801. }
  1802. return null;
  1803. }
  1804. /**
  1805. * Maps an encryption algorithm name to an instance of a subclass of
  1806. * \phpseclib3\Crypt\Common\SymmetricKey.
  1807. *
  1808. * @param string $algorithm Name of the encryption algorithm
  1809. * @return mixed Instance of \phpseclib3\Crypt\Common\SymmetricKey or null for unknown
  1810. * @access private
  1811. */
  1812. private static function encryption_algorithm_to_crypt_instance($algorithm)
  1813. {
  1814. switch ($algorithm) {
  1815. case '3des-cbc':
  1816. return new TripleDES('cbc');
  1817. case '3des-ctr':
  1818. return new TripleDES('ctr');
  1819. case 'aes256-cbc':
  1820. case 'aes192-cbc':
  1821. case 'aes128-cbc':
  1822. return new Rijndael('cbc');
  1823. case 'aes256-ctr':
  1824. case 'aes192-ctr':
  1825. case 'aes128-ctr':
  1826. return new Rijndael('ctr');
  1827. case 'blowfish-cbc':
  1828. return new Blowfish('cbc');
  1829. case 'blowfish-ctr':
  1830. return new Blowfish('ctr');
  1831. case 'twofish128-cbc':
  1832. case 'twofish192-cbc':
  1833. case 'twofish256-cbc':
  1834. case 'twofish-cbc':
  1835. return new Twofish('cbc');
  1836. case 'twofish128-ctr':
  1837. case 'twofish192-ctr':
  1838. case 'twofish256-ctr':
  1839. return new Twofish('ctr');
  1840. case 'arcfour':
  1841. case 'arcfour128':
  1842. case 'arcfour256':
  1843. return new RC4();
  1844. case 'aes128-gcm@openssh.com':
  1845. case 'aes256-gcm@openssh.com':
  1846. return new Rijndael('gcm');
  1847. case 'chacha20-poly1305@openssh.com':
  1848. return new ChaCha20();
  1849. }
  1850. return null;
  1851. }
  1852. /**
  1853. * Maps an encryption algorithm name to an instance of a subclass of
  1854. * \phpseclib3\Crypt\Hash.
  1855. *
  1856. * @param string $algorithm Name of the encryption algorithm
  1857. * @return mixed Instance of \phpseclib3\Crypt\Hash or null for unknown
  1858. * @access private
  1859. */
  1860. private static function mac_algorithm_to_hash_instance($algorithm)
  1861. {
  1862. switch ($algorithm) {
  1863. case 'umac-64@openssh.com':
  1864. case 'umac-64-etm@openssh.com':
  1865. return [new Hash('umac-64'), 16];
  1866. case 'umac-128@openssh.com':
  1867. case 'umac-128-etm@openssh.com':
  1868. return [new Hash('umac-128'), 16];
  1869. case 'hmac-sha2-512':
  1870. case 'hmac-sha2-512-etm@openssh.com':
  1871. return [new Hash('sha512'), 64];
  1872. case 'hmac-sha2-256':
  1873. case 'hmac-sha2-256-etm@openssh.com':
  1874. return [new Hash('sha256'), 32];
  1875. case 'hmac-sha1':
  1876. case 'hmac-sha1-etm@openssh.com':
  1877. return [new Hash('sha1'), 20];
  1878. case 'hmac-sha1-96':
  1879. return [new Hash('sha1-96'), 20];
  1880. case 'hmac-md5':
  1881. return [new Hash('md5'), 16];
  1882. case 'hmac-md5-96':
  1883. return [new Hash('md5-96'), 16];
  1884. }
  1885. }
  1886. /*
  1887. * Tests whether or not proposed algorithm has a potential for issues
  1888. *
  1889. * @link https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/ssh2-aesctr-openssh.html
  1890. * @link https://bugzilla.mindrot.org/show_bug.cgi?id=1291
  1891. * @param string $algorithm Name of the encryption algorithm
  1892. * @return bool
  1893. * @access private
  1894. */
  1895. private static function bad_algorithm_candidate($algorithm)
  1896. {
  1897. switch ($algorithm) {
  1898. case 'arcfour256':
  1899. case 'aes192-ctr':
  1900. case 'aes256-ctr':
  1901. return true;
  1902. }
  1903. return false;
  1904. }
  1905. /**
  1906. * Login
  1907. *
  1908. * The $password parameter can be a plaintext password, a \phpseclib3\Crypt\RSA|EC|DSA object, a \phpseclib3\System\SSH\Agent object or an array
  1909. *
  1910. * @param string $username
  1911. * @param string|AsymmetricKey|array[]|Agent|null ...$args
  1912. * @return bool
  1913. * @see self::_login()
  1914. * @access public
  1915. */
  1916. public function login($username, ...$args)
  1917. {
  1918. $this->auth[] = func_get_args();
  1919. // try logging with 'none' as an authentication method first since that's what
  1920. // PuTTY does
  1921. if (substr($this->server_identifier, 0, 15) != 'SSH-2.0-CoreFTP' && $this->auth_methods_to_continue === null) {
  1922. if ($this->sublogin($username)) {
  1923. return true;
  1924. }
  1925. if (!count($args)) {
  1926. return false;
  1927. }
  1928. }
  1929. return $this->sublogin($username, ...$args);
  1930. }
  1931. /**
  1932. * Login Helper
  1933. *
  1934. * @param string $username
  1935. * @param string[] ...$args
  1936. * @return bool
  1937. * @see self::_login_helper()
  1938. * @access private
  1939. */
  1940. protected function sublogin($username, ...$args)
  1941. {
  1942. if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
  1943. $this->connect();
  1944. }
  1945. if (empty($args)) {
  1946. return $this->login_helper($username);
  1947. }
  1948. while (count($args)) {
  1949. if (!$this->auth_methods_to_continue || !$this->smartMFA) {
  1950. $newargs = $args;
  1951. $args = [];
  1952. } else {
  1953. $newargs = [];
  1954. foreach ($this->auth_methods_to_continue as $method) {
  1955. switch ($method) {
  1956. case 'publickey':
  1957. foreach ($args as $key => $arg) {
  1958. if ($arg instanceof PrivateKey || $arg instanceof Agent) {
  1959. $newargs[] = $arg;
  1960. unset($args[$key]);
  1961. break;
  1962. }
  1963. }
  1964. break;
  1965. case 'keyboard-interactive':
  1966. $hasArray = $hasString = false;
  1967. foreach ($args as $arg) {
  1968. if ($hasArray || is_array($arg)) {
  1969. $hasArray = true;
  1970. break;
  1971. }
  1972. if ($hasString || is_string($arg)) {
  1973. $hasString = true;
  1974. break;
  1975. }
  1976. }
  1977. if ($hasArray && $hasString) {
  1978. foreach ($args as $key => $arg) {
  1979. if (is_array($arg)) {
  1980. $newargs[] = $arg;
  1981. break 2;
  1982. }
  1983. }
  1984. }
  1985. case 'password':
  1986. foreach ($args as $key => $arg) {
  1987. $newargs[] = $arg;
  1988. unset($args[$key]);
  1989. break;
  1990. }
  1991. }
  1992. }
  1993. }
  1994. foreach ($newargs as $arg) {
  1995. if ($this->login_helper($username, $arg)) {
  1996. return true;
  1997. }
  1998. }
  1999. }
  2000. return false;
  2001. }
  2002. /**
  2003. * Login Helper
  2004. *
  2005. * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
  2006. * by sending dummy SSH_MSG_IGNORE messages.}
  2007. *
  2008. * @param string $username
  2009. * @param string|AsymmetricKey|array[]|Agent|null ...$args
  2010. * @return bool
  2011. * @throws \UnexpectedValueException on receipt of unexpected packets
  2012. * @throws \RuntimeException on other errors
  2013. * @access private
  2014. */
  2015. private function login_helper($username, $password = null)
  2016. {
  2017. if (!($this->bitmap & self::MASK_CONNECTED)) {
  2018. return false;
  2019. }
  2020. if (!($this->bitmap & self::MASK_LOGIN_REQ)) {
  2021. $packet = Strings::packSSH2('Cs', NET_SSH2_MSG_SERVICE_REQUEST, 'ssh-userauth');
  2022. $this->send_binary_packet($packet);
  2023. try {
  2024. $response = $this->get_binary_packet();
  2025. } catch (\Exception $e) {
  2026. if ($this->retry_connect) {
  2027. $this->retry_connect = false;
  2028. $this->connect();
  2029. return $this->login_helper($username, $password);
  2030. }
  2031. $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
  2032. throw new ConnectionClosedException('Connection closed by server');
  2033. }
  2034. list($type, $service) = Strings::unpackSSH2('Cs', $response);
  2035. if ($type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth') {
  2036. $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
  2037. throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT');
  2038. }
  2039. $this->bitmap |= self::MASK_LOGIN_REQ;
  2040. }
  2041. if (strlen($this->last_interactive_response)) {
  2042. return !is_string($password) && !is_array($password) ? false : $this->keyboard_interactive_process($password);
  2043. }
  2044. if ($password instanceof PrivateKey) {
  2045. return $this->privatekey_login($username, $password);
  2046. }
  2047. if ($password instanceof Agent) {
  2048. return $this->ssh_agent_login($username, $password);
  2049. }
  2050. if (is_array($password)) {
  2051. if ($this->keyboard_interactive_login($username, $password)) {
  2052. $this->bitmap |= self::MASK_LOGIN;
  2053. return true;
  2054. }
  2055. return false;
  2056. }
  2057. if (!isset($password)) {
  2058. $packet = Strings::packSSH2(
  2059. 'Cs3',
  2060. NET_SSH2_MSG_USERAUTH_REQUEST,
  2061. $username,
  2062. 'ssh-connection',
  2063. 'none'
  2064. );
  2065. $this->send_binary_packet($packet);
  2066. $response = $this->get_binary_packet();
  2067. list($type) = Strings::unpackSSH2('C', $response);
  2068. switch ($type) {
  2069. case NET_SSH2_MSG_USERAUTH_SUCCESS:
  2070. $this->bitmap |= self::MASK_LOGIN;
  2071. return true;
  2072. case NET_SSH2_MSG_USERAUTH_FAILURE:
  2073. list($auth_methods) = Strings::unpackSSH2('L', $response);
  2074. $this->auth_methods_to_continue = $auth_methods;
  2075. default:
  2076. return false;
  2077. }
  2078. }
  2079. if (!is_string($password)) {
  2080. throw new \UnexpectedValueException('$password needs to either be an instance of \phpseclib3\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string');
  2081. }
  2082. $packet = Strings::packSSH2(
  2083. 'Cs3bs',
  2084. NET_SSH2_MSG_USERAUTH_REQUEST,
  2085. $username,
  2086. 'ssh-connection',
  2087. 'password',
  2088. false,
  2089. $password
  2090. );
  2091. // remove the username and password from the logged packet
  2092. if (!defined('NET_SSH2_LOGGING')) {
  2093. $logged = null;
  2094. } else {
  2095. $logged = Strings::packSSH2(
  2096. 'Cs3bs',
  2097. NET_SSH2_MSG_USERAUTH_REQUEST,
  2098. $username,
  2099. 'ssh-connection',
  2100. 'password',
  2101. false,
  2102. 'password'
  2103. );
  2104. }
  2105. $this->send_binary_packet($packet, $logged);
  2106. $response = $this->get_binary_packet();
  2107. list($type) = Strings::unpackSSH2('C', $response);
  2108. switch ($type) {
  2109. case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
  2110. $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ');
  2111. list($message) = Strings::unpackSSH2('s', $response);
  2112. $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message;
  2113. return $this->disconnect_helper(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
  2114. case NET_SSH2_MSG_USERAUTH_FAILURE:
  2115. // can we use keyboard-interactive authentication? if not then either the login is bad or the server employees
  2116. // multi-factor authentication
  2117. list($auth_methods, $partial_success) = Strings::unpackSSH2('Lb', $response);
  2118. $this->auth_methods_to_continue = $auth_methods;
  2119. if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) {
  2120. if ($this->keyboard_interactive_login($username, $password)) {
  2121. $this->bitmap |= self::MASK_LOGIN;
  2122. return true;
  2123. }
  2124. return false;
  2125. }
  2126. return false;
  2127. case NET_SSH2_MSG_USERAUTH_SUCCESS:
  2128. $this->bitmap |= self::MASK_LOGIN;
  2129. return true;
  2130. }
  2131. return false;
  2132. }
  2133. /**
  2134. * Login via keyboard-interactive authentication
  2135. *
  2136. * See {@link http://tools.ietf.org/html/rfc4256 RFC4256} for details. This is not a full-featured keyboard-interactive authenticator.
  2137. *
  2138. * @param string $username
  2139. * @param string $password
  2140. * @return bool
  2141. * @access private
  2142. */
  2143. private function keyboard_interactive_login($username, $password)
  2144. {
  2145. $packet = Strings::packSSH2(
  2146. 'Cs5',
  2147. NET_SSH2_MSG_USERAUTH_REQUEST,
  2148. $username,
  2149. 'ssh-connection',
  2150. 'keyboard-interactive',
  2151. '', // language tag
  2152. '' // submethods
  2153. );
  2154. $this->send_binary_packet($packet);
  2155. return $this->keyboard_interactive_process($password);
  2156. }
  2157. /**
  2158. * Handle the keyboard-interactive requests / responses.
  2159. *
  2160. * @param mixed[] ...$responses
  2161. * @return bool
  2162. * @throws \RuntimeException on connection error
  2163. * @access private
  2164. */
  2165. private function keyboard_interactive_process(...$responses)
  2166. {
  2167. if (strlen($this->last_interactive_response)) {
  2168. $response = $this->last_interactive_response;
  2169. } else {
  2170. $orig = $response = $this->get_binary_packet();
  2171. }
  2172. list($type) = Strings::unpackSSH2('C', $response);
  2173. switch ($type) {
  2174. case NET_SSH2_MSG_USERAUTH_INFO_REQUEST:
  2175. list(
  2176. , // name; may be empty
  2177. , // instruction; may be empty
  2178. , // language tag; may be empty
  2179. $num_prompts
  2180. ) = Strings::unpackSSH2('s3N', $response);
  2181. for ($i = 0; $i < count($responses); $i++) {
  2182. if (is_array($responses[$i])) {
  2183. foreach ($responses[$i] as $key => $value) {
  2184. $this->keyboard_requests_responses[$key] = $value;
  2185. }
  2186. unset($responses[$i]);
  2187. }
  2188. }
  2189. $responses = array_values($responses);
  2190. if (isset($this->keyboard_requests_responses)) {
  2191. for ($i = 0; $i < $num_prompts; $i++) {
  2192. list(
  2193. $prompt, // prompt - ie. "Password: "; must not be empty
  2194. // echo
  2195. ) = Strings::unpackSSH2('sC', $response);
  2196. foreach ($this->keyboard_requests_responses as $key => $value) {
  2197. if (substr($prompt, 0, strlen($key)) == $key) {
  2198. $responses[] = $value;
  2199. break;
  2200. }
  2201. }
  2202. }
  2203. }
  2204. // see http://tools.ietf.org/html/rfc4256#section-3.2
  2205. if (strlen($this->last_interactive_response)) {
  2206. $this->last_interactive_response = '';
  2207. } else {
  2208. $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST');
  2209. }
  2210. if (!count($responses) && $num_prompts) {
  2211. $this->last_interactive_response = $orig;
  2212. return false;
  2213. }
  2214. /*
  2215. After obtaining the requested information from the user, the client
  2216. MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message.
  2217. */
  2218. // see http://tools.ietf.org/html/rfc4256#section-3.4
  2219. $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses));
  2220. for ($i = 0; $i < count($responses); $i++) {
  2221. $packet.= Strings::packSSH2('s', $responses[$i]);
  2222. $logged.= Strings::packSSH2('s', 'dummy-answer');
  2223. }
  2224. $this->send_binary_packet($packet, $logged);
  2225. $this->updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE');
  2226. /*
  2227. After receiving the response, the server MUST send either an
  2228. SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, or another
  2229. SSH_MSG_USERAUTH_INFO_REQUEST message.
  2230. */
  2231. // maybe phpseclib should force close the connection after x request / responses? unless something like that is done
  2232. // there could be an infinite loop of request / responses.
  2233. return $this->keyboard_interactive_process();
  2234. case NET_SSH2_MSG_USERAUTH_SUCCESS:
  2235. return true;
  2236. case NET_SSH2_MSG_USERAUTH_FAILURE:
  2237. list($auth_methods) = Strings::unpackSSH2('L', $response);
  2238. $this->auth_methods_to_continue = $auth_methods;
  2239. return false;
  2240. }
  2241. return false;
  2242. }
  2243. /**
  2244. * Login with an ssh-agent provided key
  2245. *
  2246. * @param string $username
  2247. * @param \phpseclib3\System\SSH\Agent $agent
  2248. * @return bool
  2249. * @access private
  2250. */
  2251. private function ssh_agent_login($username, Agent $agent)
  2252. {
  2253. $this->agent = $agent;
  2254. $keys = $agent->requestIdentities();
  2255. foreach ($keys as $key) {
  2256. if ($this->privatekey_login($username, $key)) {
  2257. return true;
  2258. }
  2259. }
  2260. return false;
  2261. }
  2262. /**
  2263. * Login with an RSA private key
  2264. *
  2265. * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
  2266. * by sending dummy SSH_MSG_IGNORE messages.}
  2267. *
  2268. * @param string $username
  2269. * @param \phpseclib3\Crypt\Common\PrivateKey $privatekey
  2270. * @return bool
  2271. * @throws \RuntimeException on connection error
  2272. * @access private
  2273. */
  2274. private function privatekey_login($username, PrivateKey $privatekey)
  2275. {
  2276. $publickey = $privatekey->getPublicKey();
  2277. if ($publickey instanceof RSA) {
  2278. $privatekey = $privatekey->withPadding(RSA::SIGNATURE_PKCS1);
  2279. $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa'];
  2280. if (isset($this->preferred['hostkey'])) {
  2281. $algos = array_intersect($this->preferred['hostkey'] , $algos);
  2282. }
  2283. $algo = self::array_intersect_first($algos, $this->server_host_key_algorithms);
  2284. switch ($algo) {
  2285. case 'rsa-sha2-512':
  2286. $hash = 'sha512';
  2287. $signatureType = 'rsa-sha2-512';
  2288. break;
  2289. case 'rsa-sha2-256':
  2290. $hash = 'sha256';
  2291. $signatureType = 'rsa-sha2-256';
  2292. break;
  2293. //case 'ssh-rsa':
  2294. default:
  2295. $hash = 'sha1';
  2296. $signatureType = 'ssh-rsa';
  2297. }
  2298. } else if ($publickey instanceof EC) {
  2299. $privatekey = $privatekey->withSignatureFormat('SSH2');
  2300. $curveName = $privatekey->getCurve();
  2301. switch ($curveName) {
  2302. case 'Ed25519':
  2303. $hash = 'sha512';
  2304. $signatureType = 'ssh-ed25519';
  2305. break;
  2306. case 'secp256r1': // nistp256
  2307. $hash = 'sha256';
  2308. $signatureType = 'ecdsa-sha2-nistp256';
  2309. break;
  2310. case 'secp384r1': // nistp384
  2311. $hash = 'sha384';
  2312. $signatureType = 'ecdsa-sha2-nistp384';
  2313. break;
  2314. case 'secp521r1': // nistp521
  2315. $hash = 'sha512';
  2316. $signatureType = 'ecdsa-sha2-nistp521';
  2317. break;
  2318. default:
  2319. if (is_array($curveName)) {
  2320. throw new UnsupportedCurveException('Specified Curves are not supported by SSH2');
  2321. }
  2322. throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported by phpseclib3\'s SSH2 implementation');
  2323. }
  2324. } else if ($publickey instanceof DSA) {
  2325. $privatekey = $privatekey->withSignatureFormat('SSH2');
  2326. $hash = 'sha1';
  2327. $signatureType = 'ssh-dss';
  2328. } else {
  2329. throw new UnsupportedAlgorithmException('Please use either an RSA key, an EC one or a DSA key');
  2330. }
  2331. $publickeyStr = $publickey->toString('OpenSSH', ['binary' => true]);
  2332. $part1 = Strings::packSSH2(
  2333. 'Csss',
  2334. NET_SSH2_MSG_USERAUTH_REQUEST,
  2335. $username,
  2336. 'ssh-connection',
  2337. 'publickey'
  2338. );
  2339. $part2 = Strings::packSSH2('ss', $signatureType, $publickeyStr);
  2340. $packet = $part1 . chr(0) . $part2;
  2341. $this->send_binary_packet($packet);
  2342. $response = $this->get_binary_packet();
  2343. list($type) = Strings::unpackSSH2('C', $response);
  2344. switch ($type) {
  2345. case NET_SSH2_MSG_USERAUTH_FAILURE:
  2346. list($auth_methods) = Strings::unpackSSH2('L', $response);
  2347. $this->auth_methods_to_continue = $auth_methods;
  2348. $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE';
  2349. return false;
  2350. case NET_SSH2_MSG_USERAUTH_PK_OK:
  2351. // we'll just take it on faith that the public key blob and the public key algorithm name are as
  2352. // they should be
  2353. $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK');
  2354. break;
  2355. case NET_SSH2_MSG_USERAUTH_SUCCESS:
  2356. $this->bitmap |= self::MASK_LOGIN;
  2357. return true;
  2358. default:
  2359. $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  2360. throw new ConnectionClosedException('Unexpected response to publickey authentication pt 1');
  2361. }
  2362. $packet = $part1 . chr(1) . $part2;
  2363. $privatekey = $privatekey->withHash($hash);
  2364. $signature = $privatekey->sign(Strings::packSSH2('s', $this->session_id) . $packet);
  2365. if ($publickey instanceof RSA) {
  2366. $signature = Strings::packSSH2('ss', $signatureType, $signature);
  2367. }
  2368. $packet.= Strings::packSSH2('s', $signature);
  2369. $this->send_binary_packet($packet);
  2370. $response = $this->get_binary_packet();
  2371. list($type) = Strings::unpackSSH2('C', $response);
  2372. switch ($type) {
  2373. case NET_SSH2_MSG_USERAUTH_FAILURE:
  2374. // either the login is bad or the server employs multi-factor authentication
  2375. list($auth_methods) = Strings::unpackSSH2('L', $response);
  2376. $this->auth_methods_to_continue = $auth_methods;
  2377. return false;
  2378. case NET_SSH2_MSG_USERAUTH_SUCCESS:
  2379. $this->bitmap |= self::MASK_LOGIN;
  2380. return true;
  2381. }
  2382. $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  2383. throw new ConnectionClosedException('Unexpected response to publickey authentication pt 2');
  2384. }
  2385. /**
  2386. * Set Timeout
  2387. *
  2388. * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely. setTimeout() makes it so it'll timeout.
  2389. * Setting $timeout to false or 0 will mean there is no timeout.
  2390. *
  2391. * @param mixed $timeout
  2392. * @access public
  2393. */
  2394. public function setTimeout($timeout)
  2395. {
  2396. $this->timeout = $this->curTimeout = $timeout;
  2397. }
  2398. /**
  2399. * Set Keep Alive
  2400. *
  2401. * Sends an SSH2_MSG_IGNORE message every x seconds, if x is a positive non-zero number.
  2402. *
  2403. * @param int $interval
  2404. * @access public
  2405. */
  2406. function setKeepAlive($interval)
  2407. {
  2408. $this->keepAlive = $interval;
  2409. }
  2410. /**
  2411. * Get the output from stdError
  2412. *
  2413. * @access public
  2414. */
  2415. public function getStdError()
  2416. {
  2417. return $this->stdErrorLog;
  2418. }
  2419. /**
  2420. * Execute Command
  2421. *
  2422. * If $callback is set to false then \phpseclib3\Net\SSH2::get_channel_packet(self::CHANNEL_EXEC) will need to be called manually.
  2423. * In all likelihood, this is not a feature you want to be taking advantage of.
  2424. *
  2425. * @param string $command
  2426. * @param callback $callback
  2427. * @return string
  2428. * @throws \RuntimeException on connection error
  2429. * @access public
  2430. */
  2431. public function exec($command, $callback = null)
  2432. {
  2433. $this->curTimeout = $this->timeout;
  2434. $this->is_timeout = false;
  2435. $this->stdErrorLog = '';
  2436. if (!$this->isAuthenticated()) {
  2437. return false;
  2438. }
  2439. if ($this->in_request_pty_exec) {
  2440. throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.');
  2441. }
  2442. // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
  2443. // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but,
  2444. // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
  2445. // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
  2446. $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size;
  2447. // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
  2448. // uses 0x4000, that's what will be used here, as well.
  2449. $packet_size = 0x4000;
  2450. $packet = Strings::packSSH2(
  2451. 'CsN3',
  2452. NET_SSH2_MSG_CHANNEL_OPEN,
  2453. 'session',
  2454. self::CHANNEL_EXEC,
  2455. $this->window_size_server_to_client[self::CHANNEL_EXEC],
  2456. $packet_size
  2457. );
  2458. $this->send_binary_packet($packet);
  2459. $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;
  2460. $this->get_channel_packet(self::CHANNEL_EXEC);
  2461. if ($this->request_pty === true) {
  2462. $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
  2463. $packet = Strings::packSSH2(
  2464. 'CNsCsN4s',
  2465. NET_SSH2_MSG_CHANNEL_REQUEST,
  2466. $this->server_channels[self::CHANNEL_EXEC],
  2467. 'pty-req',
  2468. 1,
  2469. $this->term,
  2470. $this->windowColumns,
  2471. $this->windowRows,
  2472. 0,
  2473. 0,
  2474. $terminal_modes
  2475. );
  2476. $this->send_binary_packet($packet);
  2477. $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
  2478. if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
  2479. $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  2480. throw new \RuntimeException('Unable to request pseudo-terminal');
  2481. }
  2482. $this->in_request_pty_exec = true;
  2483. }
  2484. // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
  2485. // down. the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &').
  2486. // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
  2487. // then immediately terminate. without such a request exec() will loop indefinitely. the ping process won't end but
  2488. // neither will your script.
  2489. // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
  2490. // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
  2491. // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates.
  2492. $packet = Strings::packSSH2(
  2493. 'CNsCs',
  2494. NET_SSH2_MSG_CHANNEL_REQUEST,
  2495. $this->server_channels[self::CHANNEL_EXEC],
  2496. 'exec',
  2497. 1,
  2498. $command
  2499. );
  2500. $this->send_binary_packet($packet);
  2501. $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
  2502. if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
  2503. return false;
  2504. }
  2505. $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
  2506. if ($callback === false || $this->in_request_pty_exec) {
  2507. return true;
  2508. }
  2509. $output = '';
  2510. while (true) {
  2511. $temp = $this->get_channel_packet(self::CHANNEL_EXEC);
  2512. switch (true) {
  2513. case $temp === true:
  2514. return is_callable($callback) ? true : $output;
  2515. case $temp === false:
  2516. return false;
  2517. default:
  2518. if (is_callable($callback)) {
  2519. if ($callback($temp) === true) {
  2520. $this->close_channel(self::CHANNEL_EXEC);
  2521. return true;
  2522. }
  2523. } else {
  2524. $output.= $temp;
  2525. }
  2526. }
  2527. }
  2528. }
  2529. /**
  2530. * Creates an interactive shell
  2531. *
  2532. * @see self::read()
  2533. * @see self::write()
  2534. * @return bool
  2535. * @throws \UnexpectedValueException on receipt of unexpected packets
  2536. * @throws \RuntimeException on other errors
  2537. * @access private
  2538. */
  2539. private function initShell()
  2540. {
  2541. if ($this->in_request_pty_exec === true) {
  2542. return true;
  2543. }
  2544. $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size;
  2545. $packet_size = 0x4000;
  2546. $packet = Strings::packSSH2(
  2547. 'CsN3',
  2548. NET_SSH2_MSG_CHANNEL_OPEN,
  2549. 'session',
  2550. self::CHANNEL_SHELL,
  2551. $this->window_size_server_to_client[self::CHANNEL_SHELL],
  2552. $packet_size
  2553. );
  2554. $this->send_binary_packet($packet);
  2555. $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;
  2556. $this->get_channel_packet(self::CHANNEL_SHELL);
  2557. $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
  2558. $packet = Strings::packSSH2(
  2559. 'CNsbsN4s',
  2560. NET_SSH2_MSG_CHANNEL_REQUEST,
  2561. $this->server_channels[self::CHANNEL_SHELL],
  2562. 'pty-req',
  2563. true, // want reply
  2564. $this->term,
  2565. $this->windowColumns,
  2566. $this->windowRows,
  2567. 0,
  2568. 0,
  2569. $terminal_modes
  2570. );
  2571. $this->send_binary_packet($packet);
  2572. $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  2573. if (!$this->get_channel_packet(self::CHANNEL_SHELL)) {
  2574. throw new \RuntimeException('Unable to request pty');
  2575. }
  2576. $packet = Strings::packSSH2(
  2577. 'CNsb',
  2578. NET_SSH2_MSG_CHANNEL_REQUEST,
  2579. $this->server_channels[self::CHANNEL_SHELL],
  2580. 'shell',
  2581. true // want reply
  2582. );
  2583. $this->send_binary_packet($packet);
  2584. $response = $this->get_channel_packet(self::CHANNEL_SHELL);
  2585. if ($response === false) {
  2586. throw new \RuntimeException('Unable to request shell');
  2587. }
  2588. $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
  2589. $this->bitmap |= self::MASK_SHELL;
  2590. return true;
  2591. }
  2592. /**
  2593. * Return the channel to be used with read() / write()
  2594. *
  2595. * @see self::read()
  2596. * @see self::write()
  2597. * @return int
  2598. * @access public
  2599. */
  2600. private function get_interactive_channel()
  2601. {
  2602. switch (true) {
  2603. case $this->in_subsystem:
  2604. return self::CHANNEL_SUBSYSTEM;
  2605. case $this->in_request_pty_exec:
  2606. return self::CHANNEL_EXEC;
  2607. default:
  2608. return self::CHANNEL_SHELL;
  2609. }
  2610. }
  2611. /**
  2612. * Return an available open channel
  2613. *
  2614. * @return int
  2615. * @access public
  2616. */
  2617. private function get_open_channel()
  2618. {
  2619. $channel = self::CHANNEL_EXEC;
  2620. do {
  2621. if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) {
  2622. return $channel;
  2623. }
  2624. } while ($channel++ < self::CHANNEL_SUBSYSTEM);
  2625. return false;
  2626. }
  2627. /**
  2628. * Request agent forwarding of remote server
  2629. *
  2630. * @return bool
  2631. * @access public
  2632. */
  2633. public function requestAgentForwarding()
  2634. {
  2635. $request_channel = $this->get_open_channel();
  2636. if ($request_channel === false) {
  2637. return false;
  2638. }
  2639. $packet = Strings::packSSH2(
  2640. 'CNsC',
  2641. NET_SSH2_MSG_CHANNEL_REQUEST,
  2642. $this->server_channels[$request_channel],
  2643. 'auth-agent-req@openssh.com',
  2644. 1
  2645. );
  2646. $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST;
  2647. $this->send_binary_packet($packet);
  2648. if (!$this->get_channel_packet($request_channel)) {
  2649. return false;
  2650. }
  2651. $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN;
  2652. return true;
  2653. }
  2654. /**
  2655. * Returns the output of an interactive shell
  2656. *
  2657. * Returns when there's a match for $expect, which can take the form of a string literal or,
  2658. * if $mode == self::READ_REGEX, a regular expression.
  2659. *
  2660. * @see self::write()
  2661. * @param string $expect
  2662. * @param int $mode
  2663. * @return string|bool|null
  2664. * @throws \RuntimeException on connection error
  2665. * @access public
  2666. */
  2667. public function read($expect = '', $mode = self::READ_SIMPLE)
  2668. {
  2669. $this->curTimeout = $this->timeout;
  2670. $this->is_timeout = false;
  2671. if (!$this->isAuthenticated()) {
  2672. throw new InsufficientSetupException('Operation disallowed prior to login()');
  2673. }
  2674. if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) {
  2675. throw new \RuntimeException('Unable to initiate an interactive shell session');
  2676. }
  2677. $channel = $this->get_interactive_channel();
  2678. if ($mode == self::READ_NEXT) {
  2679. return $this->get_channel_packet($channel);
  2680. }
  2681. $match = $expect;
  2682. while (true) {
  2683. if ($mode == self::READ_REGEX) {
  2684. preg_match($expect, substr($this->interactiveBuffer, -1024), $matches);
  2685. $match = isset($matches[0]) ? $matches[0] : '';
  2686. }
  2687. $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false;
  2688. if ($pos !== false) {
  2689. return Strings::shift($this->interactiveBuffer, $pos + strlen($match));
  2690. }
  2691. $response = $this->get_channel_packet($channel);
  2692. if ($response === true) {
  2693. $this->in_request_pty_exec = false;
  2694. return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
  2695. }
  2696. $this->interactiveBuffer.= $response;
  2697. }
  2698. }
  2699. /**
  2700. * Inputs a command into an interactive shell.
  2701. *
  2702. * @see self::read()
  2703. * @param string $cmd
  2704. * @return bool
  2705. * @throws \RuntimeException on connection error
  2706. * @access public
  2707. */
  2708. public function write($cmd)
  2709. {
  2710. if (!$this->isAuthenticated()) {
  2711. throw new InsufficientSetupException('Operation disallowed prior to login()');
  2712. }
  2713. if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) {
  2714. throw new \RuntimeException('Unable to initiate an interactive shell session');
  2715. }
  2716. return $this->send_channel_packet($this->get_interactive_channel(), $cmd);
  2717. }
  2718. /**
  2719. * Start a subsystem.
  2720. *
  2721. * Right now only one subsystem at a time is supported. To support multiple subsystem's stopSubsystem() could accept
  2722. * a string that contained the name of the subsystem, but at that point, only one subsystem of each type could be opened.
  2723. * To support multiple subsystem's of the same name maybe it'd be best if startSubsystem() generated a new channel id and
  2724. * returns that and then that that was passed into stopSubsystem() but that'll be saved for a future date and implemented
  2725. * if there's sufficient demand for such a feature.
  2726. *
  2727. * @see self::stopSubsystem()
  2728. * @param string $subsystem
  2729. * @return bool
  2730. * @access public
  2731. */
  2732. public function startSubsystem($subsystem)
  2733. {
  2734. $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size;
  2735. $packet = Strings::packSSH2(
  2736. 'CsN3',
  2737. NET_SSH2_MSG_CHANNEL_OPEN,
  2738. 'session',
  2739. self::CHANNEL_SUBSYSTEM,
  2740. $this->window_size,
  2741. 0x4000
  2742. );
  2743. $this->send_binary_packet($packet);
  2744. $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;
  2745. $this->get_channel_packet(self::CHANNEL_SUBSYSTEM);
  2746. $packet = Strings::packSSH2(
  2747. 'CNsCs',
  2748. NET_SSH2_MSG_CHANNEL_REQUEST,
  2749. $this->server_channels[self::CHANNEL_SUBSYSTEM],
  2750. 'subsystem',
  2751. 1,
  2752. $subsystem
  2753. );
  2754. $this->send_binary_packet($packet);
  2755. $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;
  2756. if (!$this->get_channel_packet(self::CHANNEL_SUBSYSTEM)) {
  2757. return false;
  2758. }
  2759. $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
  2760. $this->bitmap |= self::MASK_SHELL;
  2761. $this->in_subsystem = true;
  2762. return true;
  2763. }
  2764. /**
  2765. * Stops a subsystem.
  2766. *
  2767. * @see self::startSubsystem()
  2768. * @return bool
  2769. * @access public
  2770. */
  2771. public function stopSubsystem()
  2772. {
  2773. $this->in_subsystem = false;
  2774. $this->close_channel(self::CHANNEL_SUBSYSTEM);
  2775. return true;
  2776. }
  2777. /**
  2778. * Closes a channel
  2779. *
  2780. * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
  2781. *
  2782. * @access public
  2783. */
  2784. public function reset()
  2785. {
  2786. $this->close_channel($this->get_interactive_channel());
  2787. }
  2788. /**
  2789. * Is timeout?
  2790. *
  2791. * Did exec() or read() return because they timed out or because they encountered the end?
  2792. *
  2793. * @access public
  2794. */
  2795. public function isTimeout()
  2796. {
  2797. return $this->is_timeout;
  2798. }
  2799. /**
  2800. * Disconnect
  2801. *
  2802. * @access public
  2803. */
  2804. public function disconnect()
  2805. {
  2806. $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  2807. if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) {
  2808. fclose($this->realtime_log_file);
  2809. }
  2810. unset(self::$connections[$this->getResourceId()]);
  2811. }
  2812. /**
  2813. * Destructor.
  2814. *
  2815. * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call
  2816. * disconnect().
  2817. *
  2818. * @access public
  2819. */
  2820. public function __destruct()
  2821. {
  2822. $this->disconnect();
  2823. }
  2824. /**
  2825. * Is the connection still active?
  2826. *
  2827. * @return bool
  2828. * @access public
  2829. */
  2830. public function isConnected()
  2831. {
  2832. return (bool) ($this->bitmap & self::MASK_CONNECTED);
  2833. }
  2834. /**
  2835. * Have you successfully been logged in?
  2836. *
  2837. * @return bool
  2838. * @access public
  2839. */
  2840. public function isAuthenticated()
  2841. {
  2842. return (bool) ($this->bitmap & self::MASK_LOGIN);
  2843. }
  2844. /**
  2845. * Pings a server connection, or tries to reconnect if the connection has gone down
  2846. *
  2847. * Inspired by http://php.net/manual/en/mysqli.ping.php
  2848. *
  2849. * @return bool
  2850. */
  2851. public function ping()
  2852. {
  2853. if (!$this->isAuthenticated()) {
  2854. if (!empty($this->auth)) {
  2855. return $this->reconnect();
  2856. }
  2857. return false;
  2858. }
  2859. $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size;
  2860. $packet_size = 0x4000;
  2861. $packet = Strings::packSSH2(
  2862. 'CsN3',
  2863. NET_SSH2_MSG_CHANNEL_OPEN,
  2864. 'session',
  2865. self::CHANNEL_KEEP_ALIVE,
  2866. $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE],
  2867. $packet_size
  2868. );
  2869. try {
  2870. $this->send_binary_packet($packet);
  2871. $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN;
  2872. $response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE);
  2873. } catch (\RuntimeException $e) {
  2874. return $this->reconnect();
  2875. }
  2876. $this->close_channel(self::CHANNEL_KEEP_ALIVE);
  2877. return true;
  2878. }
  2879. /**
  2880. * In situ reconnect method
  2881. *
  2882. * @return boolean
  2883. */
  2884. private function reconnect()
  2885. {
  2886. $this->reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST);
  2887. $this->retry_connect = true;
  2888. $this->connect();
  2889. foreach ($this->auth as $auth) {
  2890. $result = $this->login(...$auth);
  2891. }
  2892. return $result;
  2893. }
  2894. /**
  2895. * Resets a connection for re-use
  2896. *
  2897. * @param int $reason
  2898. * @access private
  2899. */
  2900. protected function reset_connection($reason)
  2901. {
  2902. $this->disconnect_helper($reason);
  2903. $this->decrypt = $this->encrypt = false;
  2904. $this->decrypt_block_size = $this->encrypt_block_size = 8;
  2905. $this->hmac_check = $this->hmac_create = false;
  2906. $this->hmac_size = false;
  2907. $this->session_id = false;
  2908. $this->retry_connect = true;
  2909. $this->get_seq_no = $this->send_seq_no = 0;
  2910. }
  2911. /**
  2912. * Gets Binary Packets
  2913. *
  2914. * See '6. Binary Packet Protocol' of rfc4253 for more info.
  2915. *
  2916. * @see self::_send_binary_packet()
  2917. * @param bool $skip_channel_filter
  2918. * @return string
  2919. * @access private
  2920. */
  2921. private function get_binary_packet($skip_channel_filter = false)
  2922. {
  2923. if ($skip_channel_filter) {
  2924. $read = [$this->fsock];
  2925. $write = $except = null;
  2926. if (!$this->curTimeout) {
  2927. if ($this->keepAlive <= 0) {
  2928. @stream_select($read, $write, $except, null);
  2929. } else {
  2930. if (!@stream_select($read, $write, $except, $this->keepAlive)) {
  2931. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
  2932. return $this->get_binary_packet(true);
  2933. }
  2934. }
  2935. } else {
  2936. if ($this->curTimeout < 0) {
  2937. $this->is_timeout = true;
  2938. return true;
  2939. }
  2940. $read = [$this->fsock];
  2941. $write = $except = null;
  2942. $start = microtime(true);
  2943. if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) {
  2944. if (!@stream_select($read, $write, $except, $this->keepAlive)) {
  2945. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
  2946. $elapsed = microtime(true) - $start;
  2947. $this->curTimeout-= $elapsed;
  2948. return $this->get_binary_packet(true);
  2949. }
  2950. $elapsed = microtime(true) - $start;
  2951. $this->curTimeout-= $elapsed;
  2952. }
  2953. $sec = floor($this->curTimeout);
  2954. $usec = 1000000 * ($this->curTimeout - $sec);
  2955. // this can return a "stream_select(): unable to select [4]: Interrupted system call" error
  2956. if (!@stream_select($read, $write, $except, $sec, $usec)) {
  2957. $this->is_timeout = true;
  2958. return true;
  2959. }
  2960. $elapsed = microtime(true) - $start;
  2961. $this->curTimeout-= $elapsed;
  2962. }
  2963. }
  2964. if (!is_resource($this->fsock) || feof($this->fsock)) {
  2965. $this->bitmap = 0;
  2966. throw new ConnectionClosedException('Connection closed (by server) prematurely ' . $elapsed . 's');
  2967. }
  2968. $start = microtime(true);
  2969. $raw = stream_get_contents($this->fsock, $this->decrypt_block_size);
  2970. if (!strlen($raw)) {
  2971. return '';
  2972. }
  2973. if ($this->decrypt) {
  2974. switch ($this->decrypt->name) {
  2975. case 'aes128-gcm@openssh.com':
  2976. case 'aes256-gcm@openssh.com':
  2977. $this->decrypt->setNonce(
  2978. $this->decrypt->fixed .
  2979. $this->decrypt->invocation_counter
  2980. );
  2981. Strings::increment_str($this->decrypt->invocation_counter);
  2982. $this->decrypt->setAAD($temp = Strings::shift($raw, 4));
  2983. extract(unpack('Npacket_length', $temp));
  2984. /**
  2985. * @var integer $packet_length
  2986. */
  2987. $raw.= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4);
  2988. $stop = microtime(true);
  2989. $tag = stream_get_contents($this->fsock, $this->decrypt_block_size);
  2990. $this->decrypt->setTag($tag);
  2991. $raw = $this->decrypt->decrypt($raw);
  2992. $raw = $temp . $raw;
  2993. $remaining_length = 0;
  2994. break;
  2995. case 'chacha20-poly1305@openssh.com':
  2996. $nonce = pack('N2', 0, $this->get_seq_no);
  2997. $this->lengthDecrypt->setNonce($nonce);
  2998. $temp = $this->lengthDecrypt->decrypt($aad = Strings::shift($raw, 4));
  2999. extract(unpack('Npacket_length', $temp));
  3000. /**
  3001. * @var integer $packet_length
  3002. */
  3003. $raw.= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4);
  3004. $stop = microtime(true);
  3005. $tag = stream_get_contents($this->fsock, 16);
  3006. $this->decrypt->setNonce($nonce);
  3007. $this->decrypt->setCounter(0);
  3008. // this is the same approach that's implemented in Salsa20::createPoly1305Key()
  3009. // but we don't want to use the same AEAD construction that RFC8439 describes
  3010. // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
  3011. $this->decrypt->setPoly1305Key(
  3012. $this->decrypt->encrypt(str_repeat("\0", 32))
  3013. );
  3014. $this->decrypt->setAAD($aad);
  3015. $this->decrypt->setCounter(1);
  3016. $this->decrypt->setTag($tag);
  3017. $raw = $this->decrypt->decrypt($raw);
  3018. $raw = $temp . $raw;
  3019. $remaining_length = 0;
  3020. break;
  3021. default:
  3022. if (!$this->hmac_check instanceof Hash || !$this->hmac_check->etm) {
  3023. $raw = $this->decrypt->decrypt($raw);
  3024. break;
  3025. }
  3026. extract(unpack('Npacket_length', $temp = Strings::shift($raw, 4)));
  3027. /**
  3028. * @var integer $packet_length
  3029. */
  3030. $raw.= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4);
  3031. $stop = microtime(true);
  3032. $encrypted = $temp . $raw;
  3033. $raw = $temp . $this->decrypt->decrypt($raw);
  3034. $remaining_length = 0;
  3035. }
  3036. }
  3037. if (strlen($raw) < 5) {
  3038. $this->bitmap = 0;
  3039. throw new \RuntimeException('Plaintext is too short');
  3040. }
  3041. extract(unpack('Npacket_length/Cpadding_length', Strings::shift($raw, 5)));
  3042. /**
  3043. * @var integer $packet_length
  3044. * @var integer $padding_length
  3045. */
  3046. if (!isset($remaining_length)) {
  3047. $remaining_length = $packet_length + 4 - $this->decrypt_block_size;
  3048. }
  3049. $buffer = $this->read_remaining_bytes($remaining_length);
  3050. if (!isset($stop)) {
  3051. $stop = microtime(true);
  3052. }
  3053. if (strlen($buffer)) {
  3054. $raw.= $this->decrypt ? $this->decrypt->decrypt($buffer) : $buffer;
  3055. }
  3056. $payload = Strings::shift($raw, $packet_length - $padding_length - 1);
  3057. $padding = Strings::shift($raw, $padding_length); // should leave $raw empty
  3058. if ($this->hmac_check instanceof Hash) {
  3059. $hmac = stream_get_contents($this->fsock, $this->hmac_size);
  3060. if ($hmac === false || strlen($hmac) != $this->hmac_size) {
  3061. $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
  3062. throw new \RuntimeException('Error reading socket');
  3063. }
  3064. $reconstructed = !$this->hmac_check->etm ?
  3065. pack('NCa*', $packet_length, $padding_length, $payload . $padding) :
  3066. $encrypted;
  3067. if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
  3068. $this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no));
  3069. if ($hmac != $this->hmac_check->hash($reconstructed)) {
  3070. $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
  3071. throw new \RuntimeException('Invalid UMAC');
  3072. }
  3073. } else {
  3074. if ($hmac != $this->hmac_check->hash(pack('Na*', $this->get_seq_no, $reconstructed))) {
  3075. $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
  3076. throw new \RuntimeException('Invalid HMAC');
  3077. }
  3078. }
  3079. }
  3080. switch ($this->decompress) {
  3081. case NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH:
  3082. if (!$this->isAuthenticated()) {
  3083. break;
  3084. }
  3085. case NET_SSH2_COMPRESSION_ZLIB:
  3086. if ($this->regenerate_decompression_context) {
  3087. $this->regenerate_decompression_context = false;
  3088. $cmf = ord($payload[0]);
  3089. $cm = $cmf & 0x0F;
  3090. if ($cm != 8) { // deflate
  3091. user_error("Only CM = 8 ('deflate') is supported ($cm)");
  3092. }
  3093. $cinfo = ($cmf & 0xF0) >> 4;
  3094. if ($cinfo > 7) {
  3095. user_error("CINFO above 7 is not allowed ($cinfo)");
  3096. }
  3097. $windowSize = 1 << ($cinfo + 8);
  3098. $flg = ord($payload[1]);
  3099. //$fcheck = $flg && 0x0F;
  3100. if ((($cmf << 8) | $flg) % 31) {
  3101. user_error('fcheck failed');
  3102. }
  3103. $fdict = boolval($flg & 0x20);
  3104. $flevel = ($flg & 0xC0) >> 6;
  3105. $this->decompress_context = inflate_init(ZLIB_ENCODING_RAW, ['window' => $cinfo + 8]);
  3106. $payload = substr($payload, 2);
  3107. }
  3108. if ($this->decompress_context) {
  3109. $payload = inflate_add($this->decompress_context, $payload, ZLIB_PARTIAL_FLUSH);
  3110. }
  3111. }
  3112. $this->get_seq_no++;
  3113. if (defined('NET_SSH2_LOGGING')) {
  3114. $current = microtime(true);
  3115. $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
  3116. $message_number = '<- ' . $message_number .
  3117. ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
  3118. $this->append_log($message_number, $payload);
  3119. $this->last_packet = $current;
  3120. }
  3121. return $this->filter($payload, $skip_channel_filter);
  3122. }
  3123. /**
  3124. * Read Remaining Bytes
  3125. *
  3126. * @see self::get_binary_packet()
  3127. * @param int $remaining_length
  3128. * @return string
  3129. * @access private
  3130. */
  3131. private function read_remaining_bytes($remaining_length)
  3132. {
  3133. if (!$remaining_length) {
  3134. return '';
  3135. }
  3136. $adjustLength = false;
  3137. if ($this->decrypt) {
  3138. switch (true) {
  3139. case $this->decrypt->name == 'aes128-gcm@openssh.com':
  3140. case $this->decrypt->name == 'aes256-gcm@openssh.com':
  3141. case $this->decrypt->name == 'chacha20-poly1305@openssh.com':
  3142. case $this->hmac_check instanceof Hash && $this->hmac_check->etm:
  3143. $remaining_length+= $this->decrypt_block_size - 4;
  3144. $adjustLength = true;
  3145. }
  3146. }
  3147. // quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
  3148. // "implementations SHOULD check that the packet length is reasonable"
  3149. // PuTTY uses 0x9000 as the actual max packet size and so to shall we
  3150. // don't do this when GCM mode is used since GCM mode doesn't encrypt the length
  3151. if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
  3152. if (!$this->bad_key_size_fix && self::bad_algorithm_candidate($this->decrypt ? $this->decrypt->name : '') && !($this->bitmap & SSH2::MASK_LOGIN)) {
  3153. $this->bad_key_size_fix = true;
  3154. $this->reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
  3155. return false;
  3156. }
  3157. throw new \RuntimeException('Invalid size');
  3158. }
  3159. if ($adjustLength) {
  3160. $remaining_length-= $this->decrypt_block_size - 4;
  3161. }
  3162. $buffer = '';
  3163. while ($remaining_length > 0) {
  3164. $temp = stream_get_contents($this->fsock, $remaining_length);
  3165. if ($temp === false || feof($this->fsock)) {
  3166. $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
  3167. throw new \RuntimeException('Error reading from socket');
  3168. }
  3169. $buffer.= $temp;
  3170. $remaining_length-= strlen($temp);
  3171. }
  3172. return $buffer;
  3173. }
  3174. /**
  3175. * Filter Binary Packets
  3176. *
  3177. * Because some binary packets need to be ignored...
  3178. *
  3179. * @see self::_get_binary_packet()
  3180. * @param string $payload
  3181. * @param bool $skip_channel_filter
  3182. * @return string
  3183. * @access private
  3184. */
  3185. private function filter($payload, $skip_channel_filter)
  3186. {
  3187. switch (ord($payload[0])) {
  3188. case NET_SSH2_MSG_DISCONNECT:
  3189. Strings::shift($payload, 1);
  3190. list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
  3191. $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n$message";
  3192. $this->bitmap = 0;
  3193. return false;
  3194. case NET_SSH2_MSG_IGNORE:
  3195. $payload = $this->get_binary_packet($skip_channel_filter);
  3196. break;
  3197. case NET_SSH2_MSG_DEBUG:
  3198. Strings::shift($payload, 2); // second byte is "always_display"
  3199. list($message) = Strings::unpackSSH2('s', $payload);
  3200. $this->errors[] = "SSH_MSG_DEBUG: $message";
  3201. $payload = $this->get_binary_packet($skip_channel_filter);
  3202. break;
  3203. case NET_SSH2_MSG_UNIMPLEMENTED:
  3204. return false;
  3205. case NET_SSH2_MSG_KEXINIT:
  3206. if ($this->session_id !== false) {
  3207. if (!$this->key_exchange($payload)) {
  3208. $this->bitmap = 0;
  3209. return false;
  3210. }
  3211. $payload = $this->get_binary_packet($skip_channel_filter);
  3212. }
  3213. }
  3214. // 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
  3215. if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
  3216. Strings::shift($payload, 1);
  3217. list($this->banner_message) = Strings::unpackSSH2('s', $payload);
  3218. $payload = $this->get_binary_packet();
  3219. }
  3220. // only called when we've already logged in
  3221. if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) {
  3222. if ($payload === true) {
  3223. return true;
  3224. }
  3225. switch (ord($payload[0])) {
  3226. case NET_SSH2_MSG_CHANNEL_REQUEST:
  3227. if (strlen($payload) == 31) {
  3228. extract(unpack('cpacket_type/Nchannel/Nlength', $payload));
  3229. if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) {
  3230. if (ord(substr($payload, 9 + $length))) { // want reply
  3231. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel]));
  3232. }
  3233. $payload = $this->get_binary_packet($skip_channel_filter);
  3234. }
  3235. }
  3236. break;
  3237. case NET_SSH2_MSG_CHANNEL_DATA:
  3238. case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
  3239. case NET_SSH2_MSG_CHANNEL_CLOSE:
  3240. case NET_SSH2_MSG_CHANNEL_EOF:
  3241. if (!$skip_channel_filter && !empty($this->server_channels)) {
  3242. $this->binary_packet_buffer = $payload;
  3243. $this->get_channel_packet(true);
  3244. $payload = $this->get_binary_packet();
  3245. }
  3246. break;
  3247. case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
  3248. Strings::shift($payload, 1);
  3249. list($request_name) = Strings::unpackSSH2('s', $payload);
  3250. $this->errors[] = "SSH_MSG_GLOBAL_REQUEST: $request_name";
  3251. try {
  3252. $this->send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE));
  3253. } catch (\RuntimeException $e) {
  3254. return $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  3255. }
  3256. $payload = $this->get_binary_packet($skip_channel_filter);
  3257. break;
  3258. case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
  3259. Strings::shift($payload, 1);
  3260. list($data, $server_channel) = Strings::unpackSSH2('sN', $payload);
  3261. switch ($data) {
  3262. case 'auth-agent':
  3263. case 'auth-agent@openssh.com':
  3264. if (isset($this->agent)) {
  3265. $new_channel = self::CHANNEL_AGENT_FORWARD;
  3266. list(
  3267. $remote_window_size,
  3268. $remote_maximum_packet_size
  3269. ) = Strings::unpackSSH2('NN', $payload);
  3270. $this->packet_size_client_to_server[$new_channel] = $remote_window_size;
  3271. $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size;
  3272. $this->window_size_client_to_server[$new_channel] = $this->window_size;
  3273. $packet_size = 0x4000;
  3274. $packet = pack(
  3275. 'CN4',
  3276. NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,
  3277. $server_channel,
  3278. $new_channel,
  3279. $packet_size,
  3280. $packet_size
  3281. );
  3282. $this->server_channels[$new_channel] = $server_channel;
  3283. $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
  3284. $this->send_binary_packet($packet);
  3285. }
  3286. break;
  3287. default:
  3288. $packet = Strings::packSSH2(
  3289. 'CN2ss',
  3290. NET_SSH2_MSG_CHANNEL_OPEN_FAILURE,
  3291. $server_channel,
  3292. NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
  3293. '', // description
  3294. '' // language tag
  3295. );
  3296. try {
  3297. $this->send_binary_packet($packet);
  3298. } catch (\RuntimeException $e) {
  3299. return $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  3300. }
  3301. }
  3302. $payload = $this->get_binary_packet($skip_channel_filter);
  3303. break;
  3304. case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
  3305. Strings::shift($payload, 1);
  3306. list($channel, $window_size) = Strings::unpackSSH2('NN', $payload);
  3307. $this->window_size_client_to_server[$channel]+= $window_size;
  3308. $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->get_binary_packet($skip_channel_filter);
  3309. }
  3310. }
  3311. return $payload;
  3312. }
  3313. /**
  3314. * Enable Quiet Mode
  3315. *
  3316. * Suppress stderr from output
  3317. *
  3318. * @access public
  3319. */
  3320. public function enableQuietMode()
  3321. {
  3322. $this->quiet_mode = true;
  3323. }
  3324. /**
  3325. * Disable Quiet Mode
  3326. *
  3327. * Show stderr in output
  3328. *
  3329. * @access public
  3330. */
  3331. public function disableQuietMode()
  3332. {
  3333. $this->quiet_mode = false;
  3334. }
  3335. /**
  3336. * Returns whether Quiet Mode is enabled or not
  3337. *
  3338. * @see self::enableQuietMode()
  3339. * @see self::disableQuietMode()
  3340. * @access public
  3341. * @return bool
  3342. */
  3343. public function isQuietModeEnabled()
  3344. {
  3345. return $this->quiet_mode;
  3346. }
  3347. /**
  3348. * Enable request-pty when using exec()
  3349. *
  3350. * @access public
  3351. */
  3352. public function enablePTY()
  3353. {
  3354. $this->request_pty = true;
  3355. }
  3356. /**
  3357. * Disable request-pty when using exec()
  3358. *
  3359. * @access public
  3360. */
  3361. public function disablePTY()
  3362. {
  3363. if ($this->in_request_pty_exec) {
  3364. $this->close_channel(self::CHANNEL_EXEC);
  3365. $this->in_request_pty_exec = false;
  3366. }
  3367. $this->request_pty = false;
  3368. }
  3369. /**
  3370. * Returns whether request-pty is enabled or not
  3371. *
  3372. * @see self::enablePTY()
  3373. * @see self::disablePTY()
  3374. * @access public
  3375. * @return bool
  3376. */
  3377. public function isPTYEnabled()
  3378. {
  3379. return $this->request_pty;
  3380. }
  3381. /**
  3382. * Gets channel data
  3383. *
  3384. * Returns the data as a string. bool(true) is returned if:
  3385. *
  3386. * - the server closes the channel
  3387. * - if the connection times out
  3388. * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
  3389. * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
  3390. *
  3391. * bool(false) is returned if:
  3392. *
  3393. * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE
  3394. *
  3395. * @param int $client_channel
  3396. * @param bool $skip_extended
  3397. * @return mixed
  3398. * @throws \RuntimeException on connection error
  3399. * @access private
  3400. */
  3401. protected function get_channel_packet($client_channel, $skip_extended = false)
  3402. {
  3403. if (!empty($this->channel_buffers[$client_channel])) {
  3404. switch ($this->channel_status[$client_channel]) {
  3405. case NET_SSH2_MSG_CHANNEL_REQUEST:
  3406. foreach ($this->channel_buffers[$client_channel] as $i => $packet) {
  3407. switch (ord($packet[0])) {
  3408. case NET_SSH2_MSG_CHANNEL_SUCCESS:
  3409. case NET_SSH2_MSG_CHANNEL_FAILURE:
  3410. unset($this->channel_buffers[$client_channel][$i]);
  3411. return substr($packet, 1);
  3412. }
  3413. }
  3414. break;
  3415. default:
  3416. return substr(array_shift($this->channel_buffers[$client_channel]), 1);
  3417. }
  3418. }
  3419. while (true) {
  3420. if ($this->binary_packet_buffer !== false) {
  3421. $response = $this->binary_packet_buffer;
  3422. $this->binary_packet_buffer = false;
  3423. } else {
  3424. $response = $this->get_binary_packet(true);
  3425. if ($response === true && $this->is_timeout) {
  3426. if ($client_channel == self::CHANNEL_EXEC && !$this->request_pty) {
  3427. $this->close_channel($client_channel);
  3428. }
  3429. return true;
  3430. }
  3431. if ($response === false) {
  3432. $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
  3433. throw new ConnectionClosedException('Connection closed by server');
  3434. }
  3435. }
  3436. if ($client_channel == -1 && $response === true) {
  3437. return true;
  3438. }
  3439. list($type, $channel) = Strings::unpackSSH2('CN', $response);
  3440. // will not be setup yet on incoming channel open request
  3441. if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) {
  3442. $this->window_size_server_to_client[$channel]-= strlen($response);
  3443. // resize the window, if appropriate
  3444. if ($this->window_size_server_to_client[$channel] < 0) {
  3445. // PuTTY does something more analogous to the following:
  3446. //if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) {
  3447. $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize);
  3448. $this->send_binary_packet($packet);
  3449. $this->window_size_server_to_client[$channel]+= $this->window_resize;
  3450. }
  3451. switch ($type) {
  3452. case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
  3453. /*
  3454. if ($client_channel == self::CHANNEL_EXEC) {
  3455. $this->send_channel_packet($client_channel, chr(0));
  3456. }
  3457. */
  3458. // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
  3459. list($data_type_code, $data) = Strings::unpackSSH2('Ns', $response);
  3460. $this->stdErrorLog.= $data;
  3461. if ($skip_extended || $this->quiet_mode) {
  3462. continue 2;
  3463. }
  3464. if ($client_channel == $channel && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA) {
  3465. return $data;
  3466. }
  3467. $this->channel_buffers[$channel][] = chr($type) . $data;
  3468. continue 2;
  3469. case NET_SSH2_MSG_CHANNEL_REQUEST:
  3470. if ($this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_CLOSE) {
  3471. continue 2;
  3472. }
  3473. list($value) = Strings::unpackSSH2('s', $response);
  3474. switch ($value) {
  3475. case 'exit-signal':
  3476. list(
  3477. , // FALSE
  3478. $signal_name,
  3479. , // core dumped
  3480. $error_message
  3481. ) = Strings::unpackSSH2('bsbs', $response);
  3482. $this->errors[] = "SSH_MSG_CHANNEL_REQUEST (exit-signal): $signal_name";
  3483. if (strlen($error_message)) {
  3484. $this->errors[count($this->errors) - 1].= "\r\n$error_message";
  3485. }
  3486. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
  3487. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
  3488. $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF;
  3489. continue 3;
  3490. case 'exit-status':
  3491. list(, $this->exit_status) = Strings::unpackSSH2('CN', $response);
  3492. // "The client MAY ignore these messages."
  3493. // -- http://tools.ietf.org/html/rfc4254#section-6.10
  3494. continue 3;
  3495. default:
  3496. // "Some systems may not implement signals, in which case they SHOULD ignore this message."
  3497. // -- http://tools.ietf.org/html/rfc4254#section-6.9
  3498. continue 3;
  3499. }
  3500. }
  3501. switch ($this->channel_status[$channel]) {
  3502. case NET_SSH2_MSG_CHANNEL_OPEN:
  3503. switch ($type) {
  3504. case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
  3505. list(
  3506. $this->server_channels[$channel],
  3507. $window_size,
  3508. $this->packet_size_client_to_server[$channel]
  3509. ) = Strings::unpackSSH2('NNN', $response);
  3510. if ($window_size < 0) {
  3511. $window_size&= 0x7FFFFFFF;
  3512. $window_size+= 0x80000000;
  3513. }
  3514. $this->window_size_client_to_server[$channel] = $window_size;
  3515. $result = $client_channel == $channel ? true : $this->get_channel_packet($client_channel, $skip_extended);
  3516. $this->on_channel_open();
  3517. return $result;
  3518. case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
  3519. $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  3520. throw new \RuntimeException('Unable to open channel');
  3521. default:
  3522. if ($client_channel == $channel) {
  3523. $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  3524. throw new \RuntimeException('Unexpected response to open request');
  3525. }
  3526. return $this->get_channel_packet($client_channel, $skip_extended);
  3527. }
  3528. break;
  3529. case NET_SSH2_MSG_CHANNEL_REQUEST:
  3530. switch ($type) {
  3531. case NET_SSH2_MSG_CHANNEL_SUCCESS:
  3532. return true;
  3533. case NET_SSH2_MSG_CHANNEL_FAILURE:
  3534. return false;
  3535. case NET_SSH2_MSG_CHANNEL_DATA:
  3536. list($data) = Strings::unpackSSH2('s', $response);
  3537. $this->channel_buffers[$channel][] = chr($type) . $data;
  3538. return $this->get_channel_packet($client_channel, $skip_extended);
  3539. default:
  3540. $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  3541. throw new \RuntimeException('Unable to fulfill channel request');
  3542. }
  3543. case NET_SSH2_MSG_CHANNEL_CLOSE:
  3544. return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->get_channel_packet($client_channel, $skip_extended);
  3545. }
  3546. }
  3547. // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
  3548. switch ($type) {
  3549. case NET_SSH2_MSG_CHANNEL_DATA:
  3550. /*
  3551. if ($channel == self::CHANNEL_EXEC) {
  3552. // SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server
  3553. // this actually seems to make things twice as fast. more to the point, the message right after
  3554. // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise.
  3555. // in OpenSSH it slows things down but only by a couple thousandths of a second.
  3556. $this->send_channel_packet($channel, chr(0));
  3557. }
  3558. */
  3559. list($data) = Strings::unpackSSH2('s', $response);
  3560. if ($channel == self::CHANNEL_AGENT_FORWARD) {
  3561. $agent_response = $this->agent->forwardData($data);
  3562. if (!is_bool($agent_response)) {
  3563. $this->send_channel_packet($channel, $agent_response);
  3564. }
  3565. break;
  3566. }
  3567. if ($client_channel == $channel) {
  3568. return $data;
  3569. }
  3570. $this->channel_buffers[$channel][] = chr($type) . $data;
  3571. break;
  3572. case NET_SSH2_MSG_CHANNEL_CLOSE:
  3573. $this->curTimeout = 5;
  3574. if ($this->bitmap & self::MASK_SHELL) {
  3575. $this->bitmap&= ~self::MASK_SHELL;
  3576. }
  3577. if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
  3578. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
  3579. }
  3580. $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
  3581. if ($client_channel == $channel) {
  3582. return true;
  3583. }
  3584. case NET_SSH2_MSG_CHANNEL_EOF:
  3585. break;
  3586. default:
  3587. $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
  3588. throw new \RuntimeException("Error reading channel data ($type)");
  3589. }
  3590. }
  3591. }
  3592. /**
  3593. * Sends Binary Packets
  3594. *
  3595. * See '6. Binary Packet Protocol' of rfc4253 for more info.
  3596. *
  3597. * @param string $data
  3598. * @param string $logged
  3599. * @see self::_get_binary_packet()
  3600. * @return bool
  3601. * @access private
  3602. */
  3603. protected function send_binary_packet($data, $logged = null)
  3604. {
  3605. if (!is_resource($this->fsock) || feof($this->fsock)) {
  3606. $this->bitmap = 0;
  3607. throw new ConnectionClosedException('Connection closed prematurely');
  3608. }
  3609. if (!isset($logged)) {
  3610. $logged = $data;
  3611. }
  3612. switch ($this->compress) {
  3613. case NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH:
  3614. if (!$this->isAuthenticated()) {
  3615. break;
  3616. }
  3617. case NET_SSH2_COMPRESSION_ZLIB:
  3618. if (!$this->regenerate_compression_context) {
  3619. $header = '';
  3620. } else {
  3621. $this->regenerate_compression_context = false;
  3622. $this->compress_context = deflate_init(ZLIB_ENCODING_RAW, ['window' => 15]);
  3623. $header = "\x78\x9C";
  3624. }
  3625. if ($this->compress_context) {
  3626. $data = $header . deflate_add($this->compress_context, $data, ZLIB_PARTIAL_FLUSH);
  3627. }
  3628. }
  3629. // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
  3630. $packet_length = strlen($data) + 9;
  3631. if ($this->encrypt && $this->encrypt->usesNonce()) {
  3632. $packet_length-= 4;
  3633. }
  3634. // round up to the nearest $this->encrypt_block_size
  3635. $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
  3636. // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
  3637. $padding_length = $packet_length - strlen($data) - 5;
  3638. switch (true) {
  3639. case $this->encrypt && $this->encrypt->usesNonce():
  3640. case $this->hmac_create instanceof Hash && $this->hmac_create->etm:
  3641. $padding_length+= 4;
  3642. $packet_length+= 4;
  3643. }
  3644. $padding = Random::string($padding_length);
  3645. // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
  3646. $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);
  3647. $hmac = '';
  3648. if ($this->hmac_create instanceof Hash && !$this->hmac_create->etm) {
  3649. if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
  3650. $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
  3651. $hmac = $this->hmac_create->hash($packet);
  3652. } else {
  3653. $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
  3654. }
  3655. }
  3656. if ($this->encrypt) {
  3657. switch ($this->encrypt->name) {
  3658. case 'aes128-gcm@openssh.com':
  3659. case 'aes256-gcm@openssh.com':
  3660. $this->encrypt->setNonce(
  3661. $this->encrypt->fixed .
  3662. $this->encrypt->invocation_counter
  3663. );
  3664. Strings::increment_str($this->encrypt->invocation_counter);
  3665. $this->encrypt->setAAD($temp = ($packet & "\xFF\xFF\xFF\xFF"));
  3666. $packet = $temp . $this->encrypt->encrypt(substr($packet, 4));
  3667. break;
  3668. case 'chacha20-poly1305@openssh.com':
  3669. $nonce = pack('N2', 0, $this->send_seq_no);
  3670. $this->encrypt->setNonce($nonce);
  3671. $this->lengthEncrypt->setNonce($nonce);
  3672. $length = $this->lengthEncrypt->encrypt($packet & "\xFF\xFF\xFF\xFF");
  3673. $this->encrypt->setCounter(0);
  3674. // this is the same approach that's implemented in Salsa20::createPoly1305Key()
  3675. // but we don't want to use the same AEAD construction that RFC8439 describes
  3676. // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
  3677. $this->encrypt->setPoly1305Key(
  3678. $this->encrypt->encrypt(str_repeat("\0", 32))
  3679. );
  3680. $this->encrypt->setAAD($length);
  3681. $this->encrypt->setCounter(1);
  3682. $packet = $length . $this->encrypt->encrypt(substr($packet, 4));
  3683. break;
  3684. default:
  3685. $packet = $this->hmac_create instanceof Hash && $this->hmac_create->etm ?
  3686. ($packet & "\xFF\xFF\xFF\xFF") . $this->encrypt->encrypt(substr($packet, 4)) :
  3687. $this->encrypt->encrypt($packet);
  3688. }
  3689. }
  3690. if ($this->hmac_create instanceof Hash && $this->hmac_create->etm) {
  3691. if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
  3692. $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
  3693. $hmac = $this->hmac_create->hash($packet);
  3694. } else {
  3695. $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
  3696. }
  3697. }
  3698. $this->send_seq_no++;
  3699. $packet.= $this->encrypt && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac;
  3700. $start = microtime(true);
  3701. $sent = @fputs($this->fsock, $packet);
  3702. $stop = microtime(true);
  3703. if (defined('NET_SSH2_LOGGING')) {
  3704. $current = microtime(true);
  3705. $message_number = isset($this->message_numbers[ord($logged[0])]) ? $this->message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
  3706. $message_number = '-> ' . $message_number .
  3707. ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
  3708. $this->append_log($message_number, $logged);
  3709. $this->last_packet = $current;
  3710. }
  3711. if (strlen($packet) != $sent) {
  3712. $this->bitmap = 0;
  3713. throw new \RuntimeException("Only $sent of " . strlen($packet) . " bytes were sent");
  3714. }
  3715. }
  3716. /**
  3717. * Logs data packets
  3718. *
  3719. * Makes sure that only the last 1MB worth of packets will be logged
  3720. *
  3721. * @param string $message_number
  3722. * @param string $message
  3723. * @access private
  3724. */
  3725. private function append_log($message_number, $message)
  3726. {
  3727. // remove the byte identifying the message type from all but the first two messages (ie. the identification strings)
  3728. if (strlen($message_number) > 2) {
  3729. Strings::shift($message);
  3730. }
  3731. switch (NET_SSH2_LOGGING) {
  3732. // useful for benchmarks
  3733. case self::LOG_SIMPLE:
  3734. $this->message_number_log[] = $message_number;
  3735. break;
  3736. // the most useful log for SSH2
  3737. case self::LOG_COMPLEX:
  3738. $this->message_number_log[] = $message_number;
  3739. $this->log_size+= strlen($message);
  3740. $this->message_log[] = $message;
  3741. while ($this->log_size > self::LOG_MAX_SIZE) {
  3742. $this->log_size-= strlen(array_shift($this->message_log));
  3743. array_shift($this->message_number_log);
  3744. }
  3745. break;
  3746. // dump the output out realtime; packets may be interspersed with non packets,
  3747. // passwords won't be filtered out and select other packets may not be correctly
  3748. // identified
  3749. case self::LOG_REALTIME:
  3750. switch (PHP_SAPI) {
  3751. case 'cli':
  3752. $start = $stop = "\r\n";
  3753. break;
  3754. default:
  3755. $start = '<pre>';
  3756. $stop = '</pre>';
  3757. }
  3758. echo $start . $this->format_log([$message], [$message_number]) . $stop;
  3759. @flush();
  3760. @ob_flush();
  3761. break;
  3762. // basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME
  3763. // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
  3764. // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
  3765. // at the beginning of the file
  3766. case self::LOG_REALTIME_FILE:
  3767. if (!isset($this->realtime_log_file)) {
  3768. // PHP doesn't seem to like using constants in fopen()
  3769. $filename = NET_SSH2_LOG_REALTIME_FILENAME;
  3770. $fp = fopen($filename, 'w');
  3771. $this->realtime_log_file = $fp;
  3772. }
  3773. if (!is_resource($this->realtime_log_file)) {
  3774. break;
  3775. }
  3776. $entry = $this->format_log([$message], [$message_number]);
  3777. if ($this->realtime_log_wrap) {
  3778. $temp = "<<< START >>>\r\n";
  3779. $entry.= $temp;
  3780. fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp));
  3781. }
  3782. $this->realtime_log_size+= strlen($entry);
  3783. if ($this->realtime_log_size > self::LOG_MAX_SIZE) {
  3784. fseek($this->realtime_log_file, 0);
  3785. $this->realtime_log_size = strlen($entry);
  3786. $this->realtime_log_wrap = true;
  3787. }
  3788. fputs($this->realtime_log_file, $entry);
  3789. }
  3790. }
  3791. /**
  3792. * Sends channel data
  3793. *
  3794. * Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate
  3795. *
  3796. * @param int $client_channel
  3797. * @param string $data
  3798. * @return bool
  3799. * @access private
  3800. */
  3801. protected function send_channel_packet($client_channel, $data)
  3802. {
  3803. while (strlen($data)) {
  3804. if (!$this->window_size_client_to_server[$client_channel]) {
  3805. $this->bitmap^= self::MASK_WINDOW_ADJUST;
  3806. // using an invalid channel will let the buffers be built up for the valid channels
  3807. $this->get_channel_packet(-1);
  3808. $this->bitmap^= self::MASK_WINDOW_ADJUST;
  3809. }
  3810. /* The maximum amount of data allowed is determined by the maximum
  3811. packet size for the channel, and the current window size, whichever
  3812. is smaller.
  3813. -- http://tools.ietf.org/html/rfc4254#section-5.2 */
  3814. $max_size = min(
  3815. $this->packet_size_client_to_server[$client_channel],
  3816. $this->window_size_client_to_server[$client_channel]
  3817. );
  3818. $temp = Strings::shift($data, $max_size);
  3819. $packet = Strings::packSSH2(
  3820. 'CNs',
  3821. NET_SSH2_MSG_CHANNEL_DATA,
  3822. $this->server_channels[$client_channel],
  3823. $temp
  3824. );
  3825. $this->window_size_client_to_server[$client_channel]-= strlen($temp);
  3826. $this->send_binary_packet($packet);
  3827. }
  3828. return true;
  3829. }
  3830. /**
  3831. * Closes and flushes a channel
  3832. *
  3833. * \phpseclib3\Net\SSH2 doesn't properly close most channels. For exec() channels are normally closed by the server
  3834. * and for SFTP channels are presumably closed when the client disconnects. This functions is intended
  3835. * for SCP more than anything.
  3836. *
  3837. * @param int $client_channel
  3838. * @param bool $want_reply
  3839. * @return bool
  3840. * @access private
  3841. */
  3842. private function close_channel($client_channel, $want_reply = false)
  3843. {
  3844. // see http://tools.ietf.org/html/rfc4254#section-5.3
  3845. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
  3846. if (!$want_reply) {
  3847. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
  3848. }
  3849. $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
  3850. $this->curTimeout = 5;
  3851. while (!is_bool($this->get_channel_packet($client_channel))) {
  3852. }
  3853. if ($this->is_timeout) {
  3854. $this->disconnect();
  3855. }
  3856. if ($want_reply) {
  3857. $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
  3858. }
  3859. if ($this->bitmap & self::MASK_SHELL) {
  3860. $this->bitmap&= ~self::MASK_SHELL;
  3861. }
  3862. }
  3863. /**
  3864. * Disconnect
  3865. *
  3866. * @param int $reason
  3867. * @return bool
  3868. * @access protected
  3869. */
  3870. protected function disconnect_helper($reason)
  3871. {
  3872. if ($this->bitmap & self::MASK_CONNECTED) {
  3873. $data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', '');
  3874. try {
  3875. $this->send_binary_packet($data);
  3876. } catch (\Exception $e) {
  3877. }
  3878. }
  3879. $this->bitmap = 0;
  3880. if (is_resource($this->fsock) && get_resource_type($this->fsock) == 'stream') {
  3881. fclose($this->fsock);
  3882. }
  3883. return false;
  3884. }
  3885. /**
  3886. * Define Array
  3887. *
  3888. * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of
  3889. * named constants from it, using the value as the name of the constant and the index as the value of the constant.
  3890. * If any of the constants that would be defined already exists, none of the constants will be defined.
  3891. *
  3892. * @param mixed[] ...$args
  3893. * @access protected
  3894. */
  3895. protected function define_array(...$args)
  3896. {
  3897. foreach ($args as $arg) {
  3898. foreach ($arg as $key => $value) {
  3899. if (!defined($value)) {
  3900. define($value, $key);
  3901. } else {
  3902. break 2;
  3903. }
  3904. }
  3905. }
  3906. }
  3907. /**
  3908. * Returns a log of the packets that have been sent and received.
  3909. *
  3910. * Returns a string if NET_SSH2_LOGGING == self::LOG_COMPLEX, an array if NET_SSH2_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING')
  3911. *
  3912. * @access public
  3913. * @return array|false|string
  3914. */
  3915. public function getLog()
  3916. {
  3917. if (!defined('NET_SSH2_LOGGING')) {
  3918. return false;
  3919. }
  3920. switch (NET_SSH2_LOGGING) {
  3921. case self::LOG_SIMPLE:
  3922. return $this->message_number_log;
  3923. case self::LOG_COMPLEX:
  3924. $log = $this->format_log($this->message_log, $this->message_number_log);
  3925. return PHP_SAPI == 'cli' ? $log : '<pre>' . $log . '</pre>';
  3926. default:
  3927. return false;
  3928. }
  3929. }
  3930. /**
  3931. * Formats a log for printing
  3932. *
  3933. * @param array $message_log
  3934. * @param array $message_number_log
  3935. * @access private
  3936. * @return string
  3937. */
  3938. protected function format_log($message_log, $message_number_log)
  3939. {
  3940. $output = '';
  3941. for ($i = 0; $i < count($message_log); $i++) {
  3942. $output.= $message_number_log[$i] . "\r\n";
  3943. $current_log = $message_log[$i];
  3944. $j = 0;
  3945. do {
  3946. if (strlen($current_log)) {
  3947. $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 ';
  3948. }
  3949. $fragment = Strings::shift($current_log, $this->log_short_width);
  3950. $hex = substr(preg_replace_callback('#.#s', function ($matches) {
  3951. return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
  3952. }, $fragment), strlen($this->log_boundary));
  3953. // replace non ASCII printable characters with dots
  3954. // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
  3955. // also replace < with a . since < messes up the output on web browsers
  3956. $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
  3957. $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
  3958. $j++;
  3959. } while (strlen($current_log));
  3960. $output.= "\r\n";
  3961. }
  3962. return $output;
  3963. }
  3964. /**
  3965. * Helper function for agent->on_channel_open()
  3966. *
  3967. * Used when channels are created to inform agent
  3968. * of said channel opening. Must be called after
  3969. * channel open confirmation received
  3970. *
  3971. * @access private
  3972. */
  3973. private function on_channel_open()
  3974. {
  3975. if (isset($this->agent)) {
  3976. $this->agent->registerChannelOpen($this);
  3977. }
  3978. }
  3979. /**
  3980. * Returns the first value of the intersection of two arrays or false if
  3981. * the intersection is empty. The order is defined by the first parameter.
  3982. *
  3983. * @param array $array1
  3984. * @param array $array2
  3985. * @return mixed False if intersection is empty, else intersected value.
  3986. * @access private
  3987. */
  3988. private static function array_intersect_first($array1, $array2)
  3989. {
  3990. foreach ($array1 as $value) {
  3991. if (in_array($value, $array2)) {
  3992. return $value;
  3993. }
  3994. }
  3995. return false;
  3996. }
  3997. /**
  3998. * Returns all errors
  3999. *
  4000. * @return string[]
  4001. * @access public
  4002. */
  4003. public function getErrors()
  4004. {
  4005. return $this->errors;
  4006. }
  4007. /**
  4008. * Returns the last error
  4009. *
  4010. * @return string
  4011. * @access public
  4012. */
  4013. public function getLastError()
  4014. {
  4015. $count = count($this->errors);
  4016. if ($count > 0) {
  4017. return $this->errors[$count - 1];
  4018. }
  4019. }
  4020. /**
  4021. * Return the server identification.
  4022. *
  4023. * @return string
  4024. * @access public
  4025. */
  4026. public function getServerIdentification()
  4027. {
  4028. $this->connect();
  4029. return $this->server_identifier;
  4030. }
  4031. /**
  4032. * Returns a list of algorithms the server supports
  4033. *
  4034. * @return array
  4035. * @access public
  4036. */
  4037. public function getServerAlgorithms()
  4038. {
  4039. $this->connect();
  4040. return [
  4041. 'kex' => $this->kex_algorithms,
  4042. 'hostkey' => $this->server_host_key_algorithms,
  4043. 'client_to_server' => [
  4044. 'crypt' => $this->encryption_algorithms_client_to_server,
  4045. 'mac' => $this->mac_algorithms_client_to_server,
  4046. 'comp' => $this->compression_algorithms_client_to_server,
  4047. 'lang' => $this->languages_client_to_server
  4048. ],
  4049. 'server_to_client' => [
  4050. 'crypt' => $this->encryption_algorithms_server_to_client,
  4051. 'mac' => $this->mac_algorithms_server_to_client,
  4052. 'comp' => $this->compression_algorithms_server_to_client,
  4053. 'lang' => $this->languages_server_to_client
  4054. ]
  4055. ];
  4056. }
  4057. /**
  4058. * Returns a list of KEX algorithms that phpseclib supports
  4059. *
  4060. * @return array
  4061. * @access public
  4062. */
  4063. public static function getSupportedKEXAlgorithms()
  4064. {
  4065. $kex_algorithms = [
  4066. // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using
  4067. // Curve25519. See doc/curve25519-sha256@libssh.org.txt in the
  4068. // libssh repository for more information.
  4069. 'curve25519-sha256',
  4070. 'curve25519-sha256@libssh.org',
  4071. 'ecdh-sha2-nistp256', // RFC 5656
  4072. 'ecdh-sha2-nistp384', // RFC 5656
  4073. 'ecdh-sha2-nistp521', // RFC 5656
  4074. 'diffie-hellman-group-exchange-sha256',// RFC 4419
  4075. 'diffie-hellman-group-exchange-sha1', // RFC 4419
  4076. // Diffie-Hellman Key Agreement (DH) using integer modulo prime
  4077. // groups.
  4078. 'diffie-hellman-group14-sha256',
  4079. 'diffie-hellman-group14-sha1', // REQUIRED
  4080. 'diffie-hellman-group15-sha512',
  4081. 'diffie-hellman-group16-sha512',
  4082. 'diffie-hellman-group17-sha512',
  4083. 'diffie-hellman-group18-sha512',
  4084. 'diffie-hellman-group1-sha1', // REQUIRED
  4085. ];
  4086. return $kex_algorithms;
  4087. }
  4088. /**
  4089. * Returns a list of host key algorithms that phpseclib supports
  4090. *
  4091. * @return array
  4092. * @access public
  4093. */
  4094. public static function getSupportedHostKeyAlgorithms()
  4095. {
  4096. return [
  4097. 'ssh-ed25519', // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02
  4098. 'ecdsa-sha2-nistp256', // RFC 5656
  4099. 'ecdsa-sha2-nistp384', // RFC 5656
  4100. 'ecdsa-sha2-nistp521', // RFC 5656
  4101. 'rsa-sha2-256', // RFC 8332
  4102. 'rsa-sha2-512', // RFC 8332
  4103. 'ssh-rsa', // RECOMMENDED sign Raw RSA Key
  4104. 'ssh-dss' // REQUIRED sign Raw DSS Key
  4105. ];
  4106. }
  4107. /**
  4108. * Returns a list of symmetric key algorithms that phpseclib supports
  4109. *
  4110. * @return array
  4111. * @access public
  4112. */
  4113. public static function getSupportedEncryptionAlgorithms()
  4114. {
  4115. $algos = [
  4116. // from <https://tools.ietf.org/html/rfc5647>:
  4117. 'aes128-gcm@openssh.com',
  4118. 'aes256-gcm@openssh.com',
  4119. // from <http://tools.ietf.org/html/rfc4345#section-4>:
  4120. 'arcfour256',
  4121. 'arcfour128',
  4122. //'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
  4123. // CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
  4124. 'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key
  4125. 'aes192-ctr', // RECOMMENDED AES with 192-bit key
  4126. 'aes256-ctr', // RECOMMENDED AES with 256-bit key
  4127. // from <https://git.io/fhxOl>:
  4128. // one of the big benefits of chacha20-poly1305 is speed. the problem is...
  4129. // libsodium doesn't generate the poly1305 keys in the way ssh does and openssl's PHP bindings don't even
  4130. // seem to support poly1305 currently. so even if libsodium or openssl are being used for the chacha20
  4131. // part, pure-PHP has to be used for the poly1305 part and that's gonna cause a big slow down.
  4132. // speed-wise it winds up being faster to use AES (when openssl or mcrypt are available) and some HMAC
  4133. // (which is always gonna be super fast to compute thanks to the hash extension, which
  4134. // "is bundled and compiled into PHP by default")
  4135. 'chacha20-poly1305@openssh.com',
  4136. 'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key
  4137. 'twofish192-ctr', // OPTIONAL Twofish with 192-bit key
  4138. 'twofish256-ctr', // OPTIONAL Twofish with 256-bit key
  4139. 'aes128-cbc', // RECOMMENDED AES with a 128-bit key
  4140. 'aes192-cbc', // OPTIONAL AES with a 192-bit key
  4141. 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key
  4142. 'twofish128-cbc', // OPTIONAL Twofish with a 128-bit key
  4143. 'twofish192-cbc', // OPTIONAL Twofish with a 192-bit key
  4144. 'twofish256-cbc',
  4145. 'twofish-cbc', // OPTIONAL alias for "twofish256-cbc"
  4146. // (this is being retained for historical reasons)
  4147. 'blowfish-ctr', // OPTIONAL Blowfish in SDCTR mode
  4148. 'blowfish-cbc', // OPTIONAL Blowfish in CBC mode
  4149. '3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode
  4150. '3des-cbc', // REQUIRED three-key 3DES in CBC mode
  4151. //'none' // OPTIONAL no encryption; NOT RECOMMENDED
  4152. ];
  4153. if (self::$crypto_engine) {
  4154. $engines = [self::$crypto_engine];
  4155. } else {
  4156. $engines = [
  4157. 'libsodium',
  4158. 'OpenSSL (GCM)',
  4159. 'OpenSSL',
  4160. 'mcrypt',
  4161. 'Eval',
  4162. 'PHP'
  4163. ];
  4164. }
  4165. $ciphers = [];
  4166. foreach ($engines as $engine) {
  4167. foreach ($algos as $algo) {
  4168. $obj = self::encryption_algorithm_to_crypt_instance($algo);
  4169. if ($obj instanceof Rijndael) {
  4170. $obj->setKeyLength(preg_replace('#[^\d]#', '', $algo));
  4171. }
  4172. switch ($algo) {
  4173. case 'chacha20-poly1305@openssh.com':
  4174. case 'arcfour128':
  4175. case 'arcfour256':
  4176. if ($engine != 'Eval') {
  4177. continue 2;
  4178. }
  4179. break;
  4180. case 'aes128-gcm@openssh.com':
  4181. case 'aes256-gcm@openssh.com':
  4182. if ($engine == 'OpenSSL') {
  4183. continue 2;
  4184. }
  4185. $obj->setNonce('dummydummydu');
  4186. }
  4187. if ($obj->isValidEngine($engine)) {
  4188. $algos = array_diff($algos, [$algo]);
  4189. $ciphers[] = $algo;
  4190. }
  4191. }
  4192. }
  4193. return $ciphers;
  4194. }
  4195. /**
  4196. * Returns a list of MAC algorithms that phpseclib supports
  4197. *
  4198. * @return array
  4199. * @access public
  4200. */
  4201. public static function getSupportedMACAlgorithms()
  4202. {
  4203. return [
  4204. 'hmac-sha2-256-etm@openssh.com',
  4205. 'hmac-sha2-512-etm@openssh.com',
  4206. 'umac-64-etm@openssh.com',
  4207. 'umac-128-etm@openssh.com',
  4208. 'hmac-sha1-etm@openssh.com',
  4209. // from <http://www.ietf.org/rfc/rfc6668.txt>:
  4210. 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
  4211. 'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64)
  4212. // from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>:
  4213. 'umac-64@openssh.com',
  4214. 'umac-128@openssh.com',
  4215. 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
  4216. 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20)
  4217. 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
  4218. 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16)
  4219. //'none' // OPTIONAL no MAC; NOT RECOMMENDED
  4220. ];
  4221. }
  4222. /**
  4223. * Returns a list of compression algorithms that phpseclib supports
  4224. *
  4225. * @return array
  4226. * @access public
  4227. */
  4228. public static function getSupportedCompressionAlgorithms()
  4229. {
  4230. $algos = ['none']; // REQUIRED no compression
  4231. if (function_exists('deflate_init')) {
  4232. $algos[] = 'zlib@openssh.com'; // https://datatracker.ietf.org/doc/html/draft-miller-secsh-compression-delayed
  4233. $algos[] = 'zlib';
  4234. }
  4235. return $algos;
  4236. }
  4237. /**
  4238. * Return list of negotiated algorithms
  4239. *
  4240. * Uses the same format as https://www.php.net/ssh2-methods-negotiated
  4241. *
  4242. * @return array
  4243. * @access public
  4244. */
  4245. public function getAlgorithmsNegotiated()
  4246. {
  4247. $this->connect();
  4248. $compression_map = [
  4249. NET_SSH2_COMPRESSION_NONE => 'none',
  4250. NET_SSH2_COMPRESSION_ZLIB => 'zlib',
  4251. NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH => 'zlib@openssh.com'
  4252. ];
  4253. return [
  4254. 'kex' => $this->kex_algorithm,
  4255. 'hostkey' => $this->signature_format,
  4256. 'client_to_server' => [
  4257. 'crypt' => $this->encrypt->name,
  4258. 'mac' => $this->hmac_create->name,
  4259. 'comp' => $compression_map[$this->compress],
  4260. ],
  4261. 'server_to_client' => [
  4262. 'crypt' => $this->decrypt->name,
  4263. 'mac' => $this->hmac_check->name,
  4264. 'comp' => $compression_map[$this->decompress],
  4265. ]
  4266. ];
  4267. }
  4268. /**
  4269. * Allows you to set the terminal
  4270. *
  4271. * @param string $term
  4272. * @access public
  4273. */
  4274. public function setTerminal($term)
  4275. {
  4276. $this->term = $term;
  4277. }
  4278. /**
  4279. * Accepts an associative array with up to four parameters as described at
  4280. * <https://www.php.net/manual/en/function.ssh2-connect.php>
  4281. *
  4282. * @param array $methods
  4283. * @access public
  4284. */
  4285. public function setPreferredAlgorithms(array $methods)
  4286. {
  4287. $preferred = $methods;
  4288. if (isset($preferred['kex'])) {
  4289. $preferred['kex'] = array_intersect(
  4290. $preferred['kex'],
  4291. static::getSupportedKEXAlgorithms()
  4292. );
  4293. }
  4294. if (isset($preferred['hostkey'])) {
  4295. $preferred['hostkey'] = array_intersect(
  4296. $preferred['hostkey'],
  4297. static::getSupportedHostKeyAlgorithms()
  4298. );
  4299. }
  4300. $keys = ['client_to_server', 'server_to_client'];
  4301. foreach ($keys as $key) {
  4302. if (isset($preferred[$key])) {
  4303. $a = &$preferred[$key];
  4304. if (isset($a['crypt'])) {
  4305. $a['crypt'] = array_intersect(
  4306. $a['crypt'],
  4307. static::getSupportedEncryptionAlgorithms()
  4308. );
  4309. }
  4310. if (isset($a['comp'])) {
  4311. $a['comp'] = array_intersect(
  4312. $a['comp'],
  4313. static::getSupportedCompressionAlgorithms()
  4314. );
  4315. }
  4316. if (isset($a['mac'])) {
  4317. $a['mac'] = array_intersect(
  4318. $a['mac'],
  4319. static::getSupportedMACAlgorithms()
  4320. );
  4321. }
  4322. }
  4323. }
  4324. $keys = [
  4325. 'kex',
  4326. 'hostkey',
  4327. 'client_to_server/crypt',
  4328. 'client_to_server/comp',
  4329. 'client_to_server/mac',
  4330. 'server_to_client/crypt',
  4331. 'server_to_client/comp',
  4332. 'server_to_client/mac',
  4333. ];
  4334. foreach ($keys as $key) {
  4335. $p = $preferred;
  4336. $m = $methods;
  4337. $subkeys = explode('/', $key);
  4338. foreach ($subkeys as $subkey) {
  4339. if (!isset($p[$subkey])) {
  4340. continue 2;
  4341. }
  4342. $p = $p[$subkey];
  4343. $m = $m[$subkey];
  4344. }
  4345. if (count($p) != count($m)) {
  4346. $diff = array_diff($m, $p);
  4347. $msg = count($diff) == 1 ?
  4348. ' is not a supported algorithm' :
  4349. ' are not supported algorithms';
  4350. throw new UnsupportedAlgorithmException(implode(', ', $diff) . $msg);
  4351. }
  4352. }
  4353. $this->preferred = $preferred;
  4354. }
  4355. /**
  4356. * Returns the banner message.
  4357. *
  4358. * Quoting from the RFC, "in some jurisdictions, sending a warning message before
  4359. * authentication may be relevant for getting legal protection."
  4360. *
  4361. * @return string
  4362. * @access public
  4363. */
  4364. public function getBannerMessage()
  4365. {
  4366. return $this->banner_message;
  4367. }
  4368. /**
  4369. * Returns the server public host key.
  4370. *
  4371. * Caching this the first time you connect to a server and checking the result on subsequent connections
  4372. * is recommended. Returns false if the server signature is not signed correctly with the public host key.
  4373. *
  4374. * @return mixed
  4375. * @throws \RuntimeException on badly formatted keys
  4376. * @throws \phpseclib3\Exception\NoSupportedAlgorithmsException when the key isn't in a supported format
  4377. * @access public
  4378. */
  4379. public function getServerPublicHostKey()
  4380. {
  4381. if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
  4382. $this->connect();
  4383. }
  4384. $signature = $this->signature;
  4385. $server_public_host_key = base64_encode($this->server_public_host_key);
  4386. if ($this->signature_validated) {
  4387. return $this->bitmap ?
  4388. $this->signature_format . ' ' . $server_public_host_key :
  4389. false;
  4390. }
  4391. $this->signature_validated = true;
  4392. switch ($this->signature_format) {
  4393. case 'ssh-ed25519':
  4394. case 'ecdsa-sha2-nistp256':
  4395. case 'ecdsa-sha2-nistp384':
  4396. case 'ecdsa-sha2-nistp521':
  4397. $key = EC::loadFormat('OpenSSH', $server_public_host_key)
  4398. ->withSignatureFormat('SSH2');
  4399. switch ($this->signature_format) {
  4400. case 'ssh-ed25519':
  4401. $hash = 'sha512';
  4402. break;
  4403. case 'ecdsa-sha2-nistp256':
  4404. $hash = 'sha256';
  4405. break;
  4406. case 'ecdsa-sha2-nistp384':
  4407. $hash = 'sha384';
  4408. break;
  4409. case 'ecdsa-sha2-nistp521':
  4410. $hash = 'sha512';
  4411. }
  4412. $key = $key->withHash($hash);
  4413. break;
  4414. case 'ssh-dss':
  4415. $key = DSA::loadFormat('OpenSSH', $server_public_host_key)
  4416. ->withSignatureFormat('SSH2')
  4417. ->withHash('sha1');
  4418. break;
  4419. case 'ssh-rsa':
  4420. case 'rsa-sha2-256':
  4421. case 'rsa-sha2-512':
  4422. if (strlen($signature) < 15) {
  4423. return false;
  4424. }
  4425. Strings::shift($signature, 11);
  4426. $temp = unpack('Nlength', Strings::shift($signature, 4));
  4427. $signature = Strings::shift($signature, $temp['length']);
  4428. $key = RSA::loadFormat('OpenSSH', $server_public_host_key)
  4429. ->withPadding(RSA::SIGNATURE_PKCS1);
  4430. switch ($this->signature_format) {
  4431. case 'rsa-sha2-512':
  4432. $hash = 'sha512';
  4433. break;
  4434. case 'rsa-sha2-256':
  4435. $hash = 'sha256';
  4436. break;
  4437. //case 'ssh-rsa':
  4438. default:
  4439. $hash = 'sha1';
  4440. }
  4441. $key = $key->withHash($hash);
  4442. break;
  4443. default:
  4444. $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
  4445. throw new NoSupportedAlgorithmsException('Unsupported signature format');
  4446. }
  4447. if (!$key->verify($this->exchange_hash, $signature)) {
  4448. return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
  4449. };
  4450. return $this->signature_format . ' ' . $server_public_host_key;
  4451. }
  4452. /**
  4453. * Returns the exit status of an SSH command or false.
  4454. *
  4455. * @return false|int
  4456. * @access public
  4457. */
  4458. public function getExitStatus()
  4459. {
  4460. if (is_null($this->exit_status)) {
  4461. return false;
  4462. }
  4463. return $this->exit_status;
  4464. }
  4465. /**
  4466. * Returns the number of columns for the terminal window size.
  4467. *
  4468. * @return int
  4469. * @access public
  4470. */
  4471. public function getWindowColumns()
  4472. {
  4473. return $this->windowColumns;
  4474. }
  4475. /**
  4476. * Returns the number of rows for the terminal window size.
  4477. *
  4478. * @return int
  4479. * @access public
  4480. */
  4481. public function getWindowRows()
  4482. {
  4483. return $this->windowRows;
  4484. }
  4485. /**
  4486. * Sets the number of columns for the terminal window size.
  4487. *
  4488. * @param int $value
  4489. * @access public
  4490. */
  4491. public function setWindowColumns($value)
  4492. {
  4493. $this->windowColumns = $value;
  4494. }
  4495. /**
  4496. * Sets the number of rows for the terminal window size.
  4497. *
  4498. * @param int $value
  4499. * @access public
  4500. */
  4501. public function setWindowRows($value)
  4502. {
  4503. $this->windowRows = $value;
  4504. }
  4505. /**
  4506. * Sets the number of columns and rows for the terminal window size.
  4507. *
  4508. * @param int $columns
  4509. * @param int $rows
  4510. * @access public
  4511. */
  4512. public function setWindowSize($columns = 80, $rows = 24)
  4513. {
  4514. $this->windowColumns = $columns;
  4515. $this->windowRows = $rows;
  4516. }
  4517. /**
  4518. * To String Magic Method
  4519. *
  4520. * @return string
  4521. * @access public
  4522. */
  4523. public function __toString()
  4524. {
  4525. return $this->getResourceId();
  4526. }
  4527. /**
  4528. * Get Resource ID
  4529. *
  4530. * We use {} because that symbols should not be in URL according to
  4531. * {@link http://tools.ietf.org/html/rfc3986#section-2 RFC}.
  4532. * It will safe us from any conflicts, because otherwise regexp will
  4533. * match all alphanumeric domains.
  4534. *
  4535. * @return string
  4536. */
  4537. public function getResourceId()
  4538. {
  4539. return '{' . spl_object_hash($this) . '}';
  4540. }
  4541. /**
  4542. * Return existing connection
  4543. *
  4544. * @param string $id
  4545. *
  4546. * @return bool|SSH2 will return false if no such connection
  4547. */
  4548. public static function getConnectionByResourceId($id)
  4549. {
  4550. if (isset(self::$connections[$id])) {
  4551. return self::$connections[$id] instanceof \WeakReference ? self::$connections[$id]->get() : self::$connections[$id];
  4552. }
  4553. return false;
  4554. }
  4555. /**
  4556. * Return all excising connections
  4557. *
  4558. * @return SSH2[]
  4559. */
  4560. public static function getConnections()
  4561. {
  4562. if (!class_exists('WeakReference')) {
  4563. return self::$connections;
  4564. }
  4565. $temp = [];
  4566. foreach (self::$connections as $key=>$ref) {
  4567. $temp[$key] = $ref->get();
  4568. }
  4569. return $temp;
  4570. }
  4571. /*
  4572. * Update packet types in log history
  4573. *
  4574. * @param string $old
  4575. * @param string $new
  4576. * @access private
  4577. */
  4578. private function updateLogHistory($old, $new)
  4579. {
  4580. if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) {
  4581. $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
  4582. $old,
  4583. $new,
  4584. $this->message_number_log[count($this->message_number_log) - 1]
  4585. );
  4586. }
  4587. }
  4588. /**
  4589. * Return the list of authentication methods that may productively continue authentication.
  4590. *
  4591. * @see https://tools.ietf.org/html/rfc4252#section-5.1
  4592. * @return array|null
  4593. */
  4594. public function getAuthMethodsToContinue()
  4595. {
  4596. return $this->auth_methods_to_continue;
  4597. }
  4598. /**
  4599. * Enables "smart" multi-factor authentication (MFA)
  4600. */
  4601. public function enableSmartMFA()
  4602. {
  4603. $this->smartMFA = true;
  4604. }
  4605. /**
  4606. * Disables "smart" multi-factor authentication (MFA)
  4607. */
  4608. public function disableSmartMFA()
  4609. {
  4610. $this->smartMFA = false;
  4611. }
  4612. }