[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/System/SSH/ -> Agent.php (source)

   1  <?php
   2  
   3  /**
   4   * Pure-PHP ssh-agent client.
   5   *
   6   * {@internal See http://api.libssh.org/rfc/PROTOCOL.agent}
   7   *
   8   * PHP version 5
   9   *
  10   * Here are some examples of how to use this library:
  11   * <code>
  12   * <?php
  13   *    include 'vendor/autoload.php';
  14   *
  15   *    $agent = new \phpseclib3\System\SSH\Agent();
  16   *
  17   *    $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
  18   *    if (!$ssh->login('username', $agent)) {
  19   *        exit('Login Failed');
  20   *    }
  21   *
  22   *    echo $ssh->exec('pwd');
  23   *    echo $ssh->exec('ls -la');
  24   * ?>
  25   * </code>
  26   *
  27   * @author    Jim Wigginton <terrafrost@php.net>
  28   * @copyright 2014 Jim Wigginton
  29   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  30   * @link      http://phpseclib.sourceforge.net
  31   */
  32  
  33  namespace phpseclib3\System\SSH;
  34  
  35  use phpseclib3\Common\Functions\Strings;
  36  use phpseclib3\Crypt\PublicKeyLoader;
  37  use phpseclib3\Crypt\RSA;
  38  use phpseclib3\Exception\BadConfigurationException;
  39  use phpseclib3\Net\SSH2;
  40  use phpseclib3\System\SSH\Agent\Identity;
  41  
  42  /**
  43   * Pure-PHP ssh-agent client identity factory
  44   *
  45   * requestIdentities() method pumps out \phpseclib3\System\SSH\Agent\Identity objects
  46   *
  47   * @author  Jim Wigginton <terrafrost@php.net>
  48   */
  49  class Agent
  50  {
  51      use Common\Traits\ReadBytes;
  52  
  53      // Message numbers
  54  
  55      // to request SSH1 keys you have to use SSH_AGENTC_REQUEST_RSA_IDENTITIES (1)
  56      const SSH_AGENTC_REQUEST_IDENTITIES = 11;
  57      // this is the SSH2 response; the SSH1 response is SSH_AGENT_RSA_IDENTITIES_ANSWER (2).
  58      const SSH_AGENT_IDENTITIES_ANSWER = 12;
  59      // the SSH1 request is SSH_AGENTC_RSA_CHALLENGE (3)
  60      const SSH_AGENTC_SIGN_REQUEST = 13;
  61      // the SSH1 response is SSH_AGENT_RSA_RESPONSE (4)
  62      const SSH_AGENT_SIGN_RESPONSE = 14;
  63  
  64      // Agent forwarding status
  65  
  66      // no forwarding requested and not active
  67      const FORWARD_NONE = 0;
  68      // request agent forwarding when opportune
  69      const FORWARD_REQUEST = 1;
  70      // forwarding has been request and is active
  71      const FORWARD_ACTIVE = 2;
  72  
  73      /**
  74       * Unused
  75       */
  76      const SSH_AGENT_FAILURE = 5;
  77  
  78      /**
  79       * Socket Resource
  80       *
  81       * @var resource
  82       */
  83      private $fsock;
  84  
  85      /**
  86       * Agent forwarding status
  87       *
  88       * @var int
  89       */
  90      private $forward_status = self::FORWARD_NONE;
  91  
  92      /**
  93       * Buffer for accumulating forwarded authentication
  94       * agent data arriving on SSH data channel destined
  95       * for agent unix socket
  96       *
  97       * @var string
  98       */
  99      private $socket_buffer = '';
 100  
 101      /**
 102       * Tracking the number of bytes we are expecting
 103       * to arrive for the agent socket on the SSH data
 104       * channel
 105       *
 106       * @var int
 107       */
 108      private $expected_bytes = 0;
 109  
 110      /**
 111       * Default Constructor
 112       *
 113       * @return \phpseclib3\System\SSH\Agent
 114       * @throws \phpseclib3\Exception\BadConfigurationException if SSH_AUTH_SOCK cannot be found
 115       * @throws \RuntimeException on connection errors
 116       */
 117      public function __construct($address = null)
 118      {
 119          if (!$address) {
 120              switch (true) {
 121                  case isset($_SERVER['SSH_AUTH_SOCK']):
 122                      $address = $_SERVER['SSH_AUTH_SOCK'];
 123                      break;
 124                  case isset($_ENV['SSH_AUTH_SOCK']):
 125                      $address = $_ENV['SSH_AUTH_SOCK'];
 126                      break;
 127                  default:
 128                      throw new BadConfigurationException('SSH_AUTH_SOCK not found');
 129              }
 130          }
 131  
 132          if (in_array('unix', stream_get_transports())) {
 133              $this->fsock = fsockopen('unix://' . $address, 0, $errno, $errstr);
 134              if (!$this->fsock) {
 135                  throw new \RuntimeException("Unable to connect to ssh-agent (Error $errno: $errstr)");
 136              }
 137          } else {
 138              if (substr($address, 0, 9) != '\\\\.\\pipe\\' || strpos(substr($address, 9), '\\') !== false) {
 139                  throw new \RuntimeException('Address is not formatted as a named pipe should be');
 140              }
 141  
 142              $this->fsock = fopen($address, 'r+b');
 143              if (!$this->fsock) {
 144                  throw new \RuntimeException('Unable to open address');
 145              }
 146          }
 147      }
 148  
 149      /**
 150       * Request Identities
 151       *
 152       * See "2.5.2 Requesting a list of protocol 2 keys"
 153       * Returns an array containing zero or more \phpseclib3\System\SSH\Agent\Identity objects
 154       *
 155       * @return array
 156       * @throws \RuntimeException on receipt of unexpected packets
 157       */
 158      public function requestIdentities()
 159      {
 160          if (!$this->fsock) {
 161              return [];
 162          }
 163  
 164          $packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES);
 165          if (strlen($packet) != fputs($this->fsock, $packet)) {
 166              throw new \RuntimeException('Connection closed while requesting identities');
 167          }
 168  
 169          $length = current(unpack('N', $this->readBytes(4)));
 170          $packet = $this->readBytes($length);
 171  
 172          list($type, $keyCount) = Strings::unpackSSH2('CN', $packet);
 173          if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) {
 174              throw new \RuntimeException('Unable to request identities');
 175          }
 176  
 177          $identities = [];
 178          for ($i = 0; $i < $keyCount; $i++) {
 179              list($key_blob, $comment) = Strings::unpackSSH2('ss', $packet);
 180              $temp = $key_blob;
 181              list($key_type) = Strings::unpackSSH2('s', $temp);
 182              switch ($key_type) {
 183                  case 'ssh-rsa':
 184                  case 'ssh-dss':
 185                  case 'ssh-ed25519':
 186                  case 'ecdsa-sha2-nistp256':
 187                  case 'ecdsa-sha2-nistp384':
 188                  case 'ecdsa-sha2-nistp521':
 189                      $key = PublicKeyLoader::load($key_type . ' ' . base64_encode($key_blob));
 190              }
 191              // resources are passed by reference by default
 192              if (isset($key)) {
 193                  $identity = (new Identity($this->fsock))
 194                      ->withPublicKey($key)
 195                      ->withPublicKeyBlob($key_blob);
 196                  $identities[] = $identity;
 197                  unset($key);
 198              }
 199          }
 200  
 201          return $identities;
 202      }
 203  
 204      /**
 205       * Signal that agent forwarding should
 206       * be requested when a channel is opened
 207       *
 208       * @return void
 209       */
 210      public function startSSHForwarding()
 211      {
 212          if ($this->forward_status == self::FORWARD_NONE) {
 213              $this->forward_status = self::FORWARD_REQUEST;
 214          }
 215      }
 216  
 217      /**
 218       * Request agent forwarding of remote server
 219       *
 220       * @param \phpseclib3\Net\SSH2 $ssh
 221       * @return bool
 222       */
 223      private function request_forwarding(SSH2 $ssh)
 224      {
 225          if (!$ssh->requestAgentForwarding()) {
 226              return false;
 227          }
 228  
 229          $this->forward_status = self::FORWARD_ACTIVE;
 230  
 231          return true;
 232      }
 233  
 234      /**
 235       * On successful channel open
 236       *
 237       * This method is called upon successful channel
 238       * open to give the SSH Agent an opportunity
 239       * to take further action. i.e. request agent forwarding
 240       *
 241       * @param \phpseclib3\Net\SSH2 $ssh
 242       */
 243      public function registerChannelOpen(SSH2 $ssh)
 244      {
 245          if ($this->forward_status == self::FORWARD_REQUEST) {
 246              $this->request_forwarding($ssh);
 247          }
 248      }
 249  
 250      /**
 251       * Forward data to SSH Agent and return data reply
 252       *
 253       * @param string $data
 254       * @return string Data from SSH Agent
 255       * @throws \RuntimeException on connection errors
 256       */
 257      public function forwardData($data)
 258      {
 259          if ($this->expected_bytes > 0) {
 260              $this->socket_buffer .= $data;
 261              $this->expected_bytes -= strlen($data);
 262          } else {
 263              $agent_data_bytes = current(unpack('N', $data));
 264              $current_data_bytes = strlen($data);
 265              $this->socket_buffer = $data;
 266              if ($current_data_bytes != $agent_data_bytes + 4) {
 267                  $this->expected_bytes = ($agent_data_bytes + 4) - $current_data_bytes;
 268                  return false;
 269              }
 270          }
 271  
 272          if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) {
 273              throw new \RuntimeException('Connection closed attempting to forward data to SSH agent');
 274          }
 275  
 276          $this->socket_buffer = '';
 277          $this->expected_bytes = 0;
 278  
 279          $agent_reply_bytes = current(unpack('N', $this->readBytes(4)));
 280  
 281          $agent_reply_data = $this->readBytes($agent_reply_bytes);
 282          $agent_reply_data = current(unpack('a*', $agent_reply_data));
 283  
 284          return pack('Na*', $agent_reply_bytes, $agent_reply_data);
 285      }
 286  }