[ Index ]

PHP Cross Reference of phpSSH

/ -> ssh-pipes.php (source)

   1  <?php
   2  /**
   3   * Implementation of SExec using pipes.
   4   *
   5   * @package SExec
   6   * @author José R. Valverde <jrvalverde@acm.org>
   7   * @version 1.0
   8   * @copyright José R. Valverde <jrvalverde@es.embnet.org>
   9   *
  10   * This library is free software; you can redistribute it and/or
  11   * modify it under the terms of the GNU Lesser General Public
  12   * License as published by the Free Software Foundation; either
  13   * version 2.1 of the License, or (at your option) any later version.
  14   * 
  15   * This library is distributed in the hope that it will be useful,
  16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18   * Lesser General Public License for more details.
  19   * 
  20   * You should have received a copy of the GNU Lesser General Public
  21   * License along with this library; if not, write to the Free Software
  22   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  23   *
  24   */
  25  
  26  /**
  27   *    NOTE: this is version 1.0! It has not been updated yet to exploit
  28   * SSH shared channel facilities.
  29   */
  30  class SExec {
  31  
  32      var $version="1.0";
  33      var remote;
  34      var password;
  35  
  36      var $ssh = "/usr/bin/ssh";
  37      var $scp = "/usr/bin/scp";
  38      
  39      function SExec($this->remote="localhost", $this->password="xxyzzy")
  40      {
  41          $this->remote = $this->remote;
  42      $this->password = "$this->password";
  43      }
  44      
  45      function set_remote_end($this->remote, $this->password="xxyzzy")
  46      {
  47          $this->remote = $this->remote;
  48      $this->password = $this->password;
  49      }
  50      
  51      
  52      /**
  53       *  Execute a single command remotely using ssh and 
  54       * display its output, optionally returning its exit 
  55       * status (like passthru)
  56       *
  57       *    This function is intended to be used as a one-time
  58       * all-at-once non-interactive execution mechanism which
  59       * will run the command remotely and display its output.
  60       *
  61       *    If you try to issue an interactive command using this
  62       * function, all you will get is unneccessary trouble. So
  63       * don't!
  64       *
  65       *  This might be done as well using a pipe on /tmp and
  66       * making the command 'cat' the pipe: when ssh runs, it
  67       * runs the command 'cat' on the pipe and hangs on read.
  68       *  Then we just need a thread to open the pipe, put the
  69       * password and close the pipe.
  70       *  This other way the password is never wirtten down.
  71       * But, OTOH, the file life is so ephemeral that most
  72       * of the time it will only exist in the internal system
  73       * cache, so this approach is not that bad either.
  74       *
  75       *  @param remote   The remote end to run the command, in
  76       *                  the form 'user@host:port' (you may
  77       *                        omit the 'user@' or ':port' parts
  78       *                        if the default values [i.e. same user
  79       *                        or standard port] are OK).
  80       *  @param password The remote password. Note that if direct
  81       *                  RSA/DSA/.shosts/.rhosts login is enabled
  82       *                  then the password should be ignored as
  83       *                  SSH should not run the ASKPASS command).
  84       *  @param command  The command to execute on the remote end
  85       *                  NOTE: if you want to use redirection, the
  86       *                  entire remote command line should be 
  87       *                  enclosed in additional quotes!
  88       *  @param status   Optional, this will hold the termination
  89       *                  status of SSH after invocation, which
  90       *                  should be the exit status of the remote
  91       *                  command or 255 if an error occurred
  92       *  @return void
  93       */
  94      function ssh_passthru($command, &$status)
  95      {
  96          global $debug;
  97  
  98          // Setup environment
  99      umask(0077);
 100      $tmpfname = tempnam('/tmp', 'phpSsh-');
 101      chmod($tmpfname, 0700);
 102          if ($debug) echo $tmpfname."\n";
 103      
 104      putenv("DISPLAY=none:0.");
 105      putenv("SSH_ASKPASS=$tmpfname");
 106  
 107      // make askpass command
 108      $fp = fopen($tmpfname, "w");
 109      fputs($fp, "#!/bin/sh\necho $this->password\n");
 110      fputs($fp, "rm -f $tmpfname\n");
 111      fclose($fp);
 112      // go
 113      if (isset($status)) {
 114          if ($debug) echo "$this->ssh -x -t -t $this->remote \"$command\"\n";
 115          passthru("$this->ssh -x -t -t $this->remote \"$command\"", $status);
 116      }
 117      else {
 118          if ($debug) echo "$this->ssh -x -t -t $this->remote \"$command\"\n";
 119          passthru("$this->ssh -x -t -t $this->remote \"$command\"");
 120      }
 121      }
 122      
 123      
 124      /**
 125       *    Execute a remote command using SSH
 126       *
 127       *    This function sort of mimics rexec(3) using SSH as the transport
 128       * protocol.
 129       *
 130       *    The function returns the exit status of the remote command, and
 131       * appends the remote job output to an optional argument.
 132       *
 133       *    This function is intended to be used as a one-time
 134       * all-at-once non-interactive execution mechanism which
 135       * will run the command remotely and return its output.
 136       *
 137       *    If you try to issue an interactive command using this
 138       * function, all you will get is unneccessary trouble. So
 139       * don't!
 140       *
 141       *  @param remote   The remote end to run the command, in
 142       *                  the form 'user@host:port' (you may
 143       *                        omit the 'user@' or ':port' parts
 144       *                        if the default values [i.e. same user
 145       *                        or standard port] are OK).
 146       *  @param password The remote password. Note that if direct
 147       *                  RSA/DSA/.shosts/.rhosts login is enabled
 148       *                  then the password should be ignored as
 149       *                  SSH should not run the ASKPASS command).
 150       *  @param command  The command to execute on the remote end
 151       *                  NOTE: if you want to use redirection, the
 152       *                  entire remote command line should be 
 153       *                  enclosed in additional quotes!
 154       *  @param output   Optional, the collated (stdout+stderr) output 
 155       *                        of the remote command.
 156       *  @return status  will hold the termination
 157       *                  status of SSH after invocation, which
 158       *                  should be the exit status of the remote
 159       *                  command or 255 if an error occurred
 160       */
 161      function ssh_exec($command, &$out)
 162      {
 163          global $debug;
 164  
 165      umask(0077);
 166      $tmpfname = tempnam('/tmp', 'phpSsh');
 167      chmod($tmpfname, 0700);
 168      if ($debug) echo $tmpfname . "\n";
 169  
 170      putenv('DISPLAY=none:0.');
 171      putenv("SSH_ASKPASS=$tmpfname");
 172      $fp = fopen($tmpfname, "w");
 173      fputs($fp, "#!/bin/sh\necho $this->password\n");
 174      fputs($fp, "rm -f $tmpfname\n");
 175      fclose($fp);
 176      exec("$this->ssh -x -t -t $this->remote \"$command\"", $out, $retval);
 177      return $retval;
 178  
 179      }
 180      
 181      /**
 182       *  Copy a file or directory from one source to a destination
 183       *
 184       *  This function copies source to dest, where one of them is a
 185       * local filespec and the other a remote filespec of the form
 186       * [user@]host:path
 187       *
 188       *  If the original source is a directory, it will be copied
 189       * recursively to destination (hence easing file transfers).
 190       *
 191       *  The function returns TRUE on success or FALSE on failure.
 192       *
 193       *    @param origin    The origin path, of the form
 194       *                    [user@][host][:port]path
 195       *                    You may omit the optional sections if
 196       *                    the default values (local username, local
 197       *                    host, standard SSH port) are OK
 198       *
 199       *    @param destination    The destination path, of the form
 200       *                    [user@][host][:port:]path
 201       *                    You may omit the optional sections if
 202       *                    the default values (local username, local
 203       *                    host, standard SSH port) are OK
 204       *
 205       *    @param password    The password to use to connect to the remote
 206       *                    end of the copy (be it the origin or the
 207       *                    destination, it's all the same). If connection
 208       *                    is automatic by some means (.shosts or RSA/DSA
 209       *                    authentication) then it should be ignored and
 210       *                    any password should do.
 211       *
 212       *    @return status    TRUE if all went well, or FALSE on failure.
 213       */
 214      function ssh_copy($origin, $destination, $this->password)
 215      {
 216          global $debug;
 217  
 218      umask(0077);
 219      $tmpfname = tempnam("/tmp", "phpSsh");
 220      chmod($tmpfname, 0700);
 221      putenv("DISPLAY=none:0.");
 222      putenv("SSH_ASKPASS=$tmpfname");
 223      $fp = fopen($tmpfname, "w");
 224      fputs($fp, "#!/bin/sh\necho $this->password\n");
 225      fputs($fp, "rm $tmpfname\n");
 226      fclose($fp);
 227      exec("$this->scp -pqrC $origin $destination", $out, $status);
 228      if ($status == 0)
 229          return TRUE;
 230      else
 231          return FALSE;
 232      }
 233  
 234      /**
 235       *    Open an SSH connection to a remote site with a shell to run 
 236       * interactive commands
 237       *
 238       *    Connects to a remote host and opens an interactive shell session
 239       * with NO controlling terminal.
 240       *
 241       *    Returns a process_control array which contains the process resource
 242       * ID and an the standard file descriptors which the caller may use to
 243       * interact with the remote shell.
 244       *
 245       *  @param remote   The remote end to run the shell, in
 246       *                  the form 'user@host:port' (you may
 247       *                        omit the 'user@' or ':port' parts
 248       *                        if the default values [i.e. same user
 249       *                        or standard port] are OK).
 250       *  @param password The remote password. Note that if direct
 251       *                  RSA/DSA/.shosts/.rhosts login is enabled
 252       *                  then the password should be ignored as
 253       *                  SSH should not run the ASKPASS command).
 254       */
 255      function ssh_open_shell()
 256      {    
 257      global $debug;
 258  
 259      // Open a child process with the 'proc_open' function. 
 260      //
 261      // Some tricks: we must open the connection using '-x' to disable
 262      // X11 forwarding, and use '-t -t' to avoid SSH generating an error
 263      // because we are not connected to any terminal.
 264      //
 265      // NOTE: if the web server is trusted remotely (i.e. it's SSH public 
 266      // key is accepted in ~user@host:.ssh/authorized_keys) then any 
 267      // password will do.
 268  
 269      // Prepare I/O
 270      $descriptorspec = array(
 271              0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
 272              1 => array("pipe", "a"),  // connect child's stdout to the write end of a pipe
 273              2 => array("pipe", "a")   // stderr is a pipe to read from
 274      );
 275  
 276      // prepare password
 277      umask(0077);
 278      $tmpfname = tempnam("/tmp", "phpSsh-");
 279      chmod($tmpfname, 0700);
 280          if ($debug) echo $tmpfname . "\n";
 281  
 282      putenv("DISPLAY=none:0.");
 283      putenv("SSH_ASKPASS=$tmpfname");
 284      $fp = fopen($tmpfname, "w");
 285      fputs($fp, "#!/bin/sh\necho $this->password\n");
 286      fputs($fp, "rm $tmpfname\n");
 287      fclose($fp);
 288  
 289      if ($debug) echo "$this->ssh -x -t -t $this->remote<br />\n";
 290      $process = proc_open("$this->ssh -x -t -t $this->remote", 
 291                   $descriptorspec,
 292               $pipes);
 293      
 294      // check status
 295      if (!is_resource($process)) 
 296      {
 297          letal("SSH::connect", "cannot connect to the remote host");
 298          return;
 299      }
 300      if ($debug) echo "proc_open done<br />\n";
 301  
 302      // $pipes now looks like this:
 303      //   0 => writeable handle connected to child stdin
 304      //   1 => readable handle connected to child stdout
 305      //   2 => readable handle connected to child stderr
 306      
 307      // Should we leave this to the user?
 308      // set to non-blocking and avoid having to call fflush
 309      stream_set_blocking($pipes[0], FALSE);
 310      stream_set_blocking($pipes[1], FALSE);
 311      stream_set_blocking($pipes[2], FALSE);
 312      stream_set_write_buffer($pipes[0], 0);
 313      stream_set_write_buffer($pipes[1], 0);
 314      stream_set_write_buffer($pipes[2], 0);
 315  
 316      // We now have a connection to the remote SSH
 317      // Server which we may use to send commands/receive output
 318      $p = array('process' => $process
 319                  ,'std_in' => $pipes[0]
 320                      ,'std_out' => $pipes[1]
 321              ,'std_err' => $pipes[2] 
 322             );
 323      if ($debug)  {
 324          echo "process descriptor array is \n";
 325          print_r($p);
 326          /*
 327          fwrite($p['std_in'], "\n");
 328          fwrite($p['std_in'], "touch touche\n");
 329          fwrite($p['std_in'], "logout\n");
 330          fflush($p['std_in']);
 331          fclose($p['std_in']); fclose($p['std_out']); fclose($p['std_err']);
 332          echo "pipes closed\n";
 333          proc_close($p['process']);
 334          echo "process closed\n";
 335      } 
 336      if ($debug == "CHANGE ME") {
 337          echo "process "; print_r($process); echo "\n";
 338          echo "p->process "; print_r($p['process']); echo "\n";
 339          fwrite($pipes[0], "touch touche\n");
 340          fwrite($pipes[0], "logout\n");
 341          fflush($pipes[0]);
 342          fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]);
 343          proc_close($process);
 344          */
 345      }
 346      return $p;
 347      }
 348      
 349      /**
 350       *    Open an SSH connection to run an interactive command on a remote
 351       * site
 352       *
 353       *    Connects to a remote host and runs an interactive command
 354       * with NO controlling terminal.
 355       *
 356       *    Returns a process_control array which contains the process resource
 357       * ID and an the standard file descriptors which the caller may use to
 358       * interact with the remote shell.
 359       */
 360      function ssh_open_command($command)
 361      {    
 362      global $debug;
 363  
 364      // Open a child process with the 'proc_open' function. 
 365      //
 366      // Some tricks: we must open the connection using '-x' to disable
 367      // X11 forwarding, and use '-t -t' to avoid SSH generating an error
 368      // because we are not connected to any terminal.
 369      //
 370      // NOTE: if the web server is trusted remotely (i.e. it's SSH public 
 371      // key is accepted in ~user@host:.ssh/authorized_keys) then any 
 372      // password will do.
 373  
 374      // Prepare I/O
 375      umask(0077);
 376      $descriptorspec = array(
 377              0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
 378              1 => array("pipe", "a"),  // connect child's stdout to the write end of a pipe
 379              2 => array("pipe", "a")   // stderr is a pipe to read from
 380      );
 381  
 382      // prepare password
 383      umask(0077);
 384      $tmpfname = tempnam("/tmp", "phpSsh-");
 385      chmod($tmpfname, 0700);
 386          if ($debug) echo $tmpfname . "\n";
 387  
 388      putenv("DISPLAY=none:0.");
 389      putenv("SSH_ASKPASS=$tmpfname");
 390      $fp = fopen($tmpfname, "w");
 391      fputs($fp, "#!/bin/sh\necho $this->password\n");
 392      fputs($fp, "rm $tmpfname\n");
 393      fclose($fp);
 394  
 395      if ($debug) echo "$this->ssh -x -t -t $this->remote $command<br />\n";
 396      $process = proc_open("$this->ssh -x -t -t $this->remote \"$command\"", 
 397                   $descriptorspec,
 398               $pipes);
 399      
 400      // check status
 401      if (!is_resource($process)) 
 402      {
 403          letal("SSH::connect", "cannot connect to the remote host");
 404          return;
 405      }
 406      if ($debug) echo "proc_open done<br />\n";
 407  
 408      // $pipes now looks like this:
 409      //   0 => writeable handle connected to child stdin
 410      //   1 => readable handle connected to child stdout
 411      //   2 => readable handle connected to child stderr
 412      
 413      // Should we leave this to the user?
 414      // set to non-blocking and avoid having to call fflush
 415      stream_set_blocking($pipes[0], FALSE);
 416      stream_set_blocking($pipes[1], FALSE);
 417      stream_set_blocking($pipes[2], FALSE);
 418      stream_set_write_buffer($pipes[0], 0);
 419      stream_set_write_buffer($pipes[1], 0);
 420      stream_set_write_buffer($pipes[2], 0);
 421  
 422      // We now have a connection to the remote SSH
 423      // Server which we may use to send commands/receive output
 424      $p = array('process' => $process
 425                  ,'std_in' => $pipes[0]
 426                      ,'std_out' => $pipes[1]
 427              ,'std_err' => $pipes[2] 
 428             );
 429      if ($debug)  {
 430          echo "process descriptor array is \n";
 431          print_r($p);
 432      }
 433      return $p;
 434      }
 435  
 436      /**
 437       * Get output until we reach a given regular expression
 438       */
 439      function ssh_out_expect($p, $expr="^# ")
 440      {
 441          flush();
 442      fseek($p["std_out"], $last);
 443          do {
 444          $line = fgets($p["std_out"], 1024);
 445          #echo ">> ".$line;
 446          } while ((! feof($p["std_out"]) ) && (! ereg($expr, $line)));
 447      $last = ftell($p["std_out"]);
 448      }
 449  
 450      /**
 451       * Close an SSH interactive session
 452       */
 453      function ssh_close($p)
 454      {
 455          global $debug;
 456      
 457          fwrite($p['std_in'], "\n");
 458          fwrite($p['std_in'], "logout\n");
 459          fflush($p['std_in']);
 460          fclose($p['std_in']); fclose($p['std_out']); fclose($p['std_err']);
 461          if ($debug) echo "pipes closed\n";
 462          return proc_close($p['process']);
 463      }
 464      
 465  #    if ($php_version >= 5)
 466  #    {
 467  #    /**
 468  #     * send a signal to a running ssh_open_* process
 469  #     */
 470  #    function ssh_signal($p, $signal)
 471  #    {
 472  #            return proc_terminate($p['process'], $signal);
 473  #    }
 474  #    /**
 475  #     * get info about a running ssh_open_* process
 476  #     */
 477  #    function ssh_get_status($p)
 478  #    {
 479  #            return proc_get_status($p['process']);
 480  #    }
 481  #    }
 482      
 483      /**
 484       *    Execute a remote command and keep an unidirectional stream
 485       * contact with it.
 486       *
 487       *    This routine mimics 'popen()' but uses ssh to connect to
 488       * a remote host and run the requested command.
 489       */
 490      function ssh_popen($command, $mode)
 491      {
 492          global $debug;
 493  
 494          // Setup environment
 495      umask(0077);
 496      $tmpfname = tempnam('/tmp', 'phpSsh-');
 497      chmod($tmpfname, 0700);
 498          if ($debug) echo $tmpfname."\n";
 499      
 500      putenv("DISPLAY=none:0.");
 501      putenv("SSH_ASKPASS=$tmpfname");
 502  
 503      // make askpass command
 504      $fp = fopen($tmpfname, "w");
 505      fputs($fp, "#!/bin/sh\necho $this->password\n");
 506      fputs($fp, "rm -f $tmpfname\n");
 507      fclose($fp);
 508      // go
 509      return popen("$this->ssh -x -t -t $this->remote \"$command\"", $mode);
 510      }
 511      
 512      function ssh_pclose($f)
 513      {
 514          return pclose($f);
 515      }
 516  
 517  }
 518  
 519  ?>


Generated: Wed May 25 19:30:43 2005 Cross-referenced by PHPXref 0.4.1