* login('username', $agent)) { * exit('Login Failed'); * } * * echo $ssh->exec('pwd'); * echo $ssh->exec('ls -la'); * ?> * * * @category System * @package SSH\Agent * @author Jim Wigginton * @copyright 2014 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net * @internal See http://api.libssh.org/rfc/PROTOCOL.agent */ namespace phpseclib\System\SSH; use ParagonIE\ConstantTime\Base64; use phpseclib\Crypt\RSA; use phpseclib\Exception\BadConfigurationException; use phpseclib\System\SSH\Agent\Identity; /** * Pure-PHP ssh-agent client identity factory * * requestIdentities() method pumps out \phpseclib\System\SSH\Agent\Identity objects * * @package SSH\Agent * @author Jim Wigginton * @access internal */ class Agent { /**#@+ * Message numbers * * @access private */ // to request SSH1 keys you have to use SSH_AGENTC_REQUEST_RSA_IDENTITIES (1) const SSH_AGENTC_REQUEST_IDENTITIES = 11; // this is the SSH2 response; the SSH1 response is SSH_AGENT_RSA_IDENTITIES_ANSWER (2). const SSH_AGENT_IDENTITIES_ANSWER = 12; // the SSH1 request is SSH_AGENTC_RSA_CHALLENGE (3) const SSH_AGENTC_SIGN_REQUEST = 13; // the SSH1 response is SSH_AGENT_RSA_RESPONSE (4) const SSH_AGENT_SIGN_RESPONSE = 14; /**#@-*/ /**@+ * Agent forwarding status * * @access private */ // no forwarding requested and not active const FORWARD_NONE = 0; // request agent forwarding when opportune const FORWARD_REQUEST = 1; // forwarding has been request and is active const FORWARD_ACTIVE = 2; /**#@-*/ /** * Unused */ const SSH_AGENT_FAILURE = 5; /** * Socket Resource * * @var resource * @access private */ var $fsock; /** * Agent forwarding status * * @access private */ var $forward_status = self::FORWARD_NONE; /** * Buffer for accumulating forwarded authentication * agent data arriving on SSH data channel destined * for agent unix socket * * @access private */ var $socket_buffer = ''; /** * Tracking the number of bytes we are expecting * to arrive for the agent socket on the SSH data * channel */ var $expected_bytes = 0; /** * Default Constructor * * @return \phpseclib\System\SSH\Agent * @throws \phpseclib\Exception\BadConfigurationException if SSH_AUTH_SOCK cannot be found * @throws \RuntimeException on connection errors * @access public */ function __construct() { switch (true) { case isset($_SERVER['SSH_AUTH_SOCK']): $address = $_SERVER['SSH_AUTH_SOCK']; break; case isset($_ENV['SSH_AUTH_SOCK']): $address = $_ENV['SSH_AUTH_SOCK']; break; default: throw new BadConfigurationException('SSH_AUTH_SOCK not found'); } $this->fsock = fsockopen('unix://' . $address, 0, $errno, $errstr); if (!$this->fsock) { throw new \RuntimeException("Unable to connect to ssh-agent (Error $errno: $errstr)"); } } /** * Request Identities * * See "2.5.2 Requesting a list of protocol 2 keys" * Returns an array containing zero or more \phpseclib\System\SSH\Agent\Identity objects * * @return array * @throws \RuntimeException on receipt of unexpected packets * @access public */ function requestIdentities() { if (!$this->fsock) { return []; } $packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES); if (strlen($packet) != fputs($this->fsock, $packet)) { throw new \RuntimeException('Connection closed while requesting identities'); } $length = current(unpack('N', fread($this->fsock, 4))); $type = ord(fread($this->fsock, 1)); if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) { throw new \RuntimeException('Unable to request identities'); } $identities = []; $keyCount = current(unpack('N', fread($this->fsock, 4))); for ($i = 0; $i < $keyCount; $i++) { $length = current(unpack('N', fread($this->fsock, 4))); $key_blob = fread($this->fsock, $length); $key_str = 'ssh-rsa ' . Base64::encode($key_blob); $length = current(unpack('N', fread($this->fsock, 4))); if ($length) { $key_str.= ' ' . fread($this->fsock, $length); } $length = current(unpack('N', substr($key_blob, 0, 4))); $key_type = substr($key_blob, 4, $length); switch ($key_type) { case 'ssh-rsa': $key = new RSA(); $key->load($key_str); break; case 'ssh-dss': // not currently supported break; } // resources are passed by reference by default if (isset($key)) { $identity = new Identity($this->fsock); $identity->setPublicKey($key); $identity->setPublicKeyBlob($key_blob); $identities[] = $identity; unset($key); } } return $identities; } /** * Signal that agent forwarding should * be requested when a channel is opened * * @param Net_SSH2 $ssh * @return bool * @access public */ function startSSHForwarding($ssh) { if ($this->forward_status == self::FORWARD_NONE) { $this->forward_status = self::FORWARD_REQUEST; } } /** * Request agent forwarding of remote server * * @param Net_SSH2 $ssh * @return bool * @access private */ function _request_forwarding($ssh) { $request_channel = $ssh->_get_open_channel(); if ($request_channel === false) { return false; } $packet = pack( 'CNNa*C', NET_SSH2_MSG_CHANNEL_REQUEST, $ssh->server_channels[$request_channel], strlen('auth-agent-req@openssh.com'), 'auth-agent-req@openssh.com', 1 ); $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST; if (!$ssh->_send_binary_packet($packet)) { return false; } $response = $ssh->_get_channel_packet($request_channel); if ($response === false) { return false; } $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN; $this->forward_status = self::FORWARD_ACTIVE; return true; } /** * On successful channel open * * This method is called upon successful channel * open to give the SSH Agent an opportunity * to take further action. i.e. request agent forwarding * * @param Net_SSH2 $ssh * @access private */ function _on_channel_open($ssh) { if ($this->forward_status == self::FORWARD_REQUEST) { $this->_request_forwarding($ssh); } } /** * Forward data to SSH Agent and return data reply * * @param string $data * @return data from SSH Agent * @throws \RuntimeException on connection errors * @access private */ function _forward_data($data) { if ($this->expected_bytes > 0) { $this->socket_buffer.= $data; $this->expected_bytes -= strlen($data); } else { $agent_data_bytes = current(unpack('N', $data)); $current_data_bytes = strlen($data); $this->socket_buffer = $data; if ($current_data_bytes != $agent_data_bytes + 4) { $this->expected_bytes = ($agent_data_bytes + 4) - $current_data_bytes; return false; } } if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) { throw new \RuntimeException('Connection closed attempting to forward data to SSH agent'); } $this->socket_buffer = ''; $this->expected_bytes = 0; $agent_reply_bytes = current(unpack('N', fread($this->fsock, 4))); $agent_reply_data = fread($this->fsock, $agent_reply_bytes); $agent_reply_data = current(unpack('a*', $agent_reply_data)); return pack('Na*', $agent_reply_bytes, $agent_reply_data); } }