[ Index ]

PHP Cross Reference of phpSSH

/ -> ssh.php (source)

   1  <?php
   2  
   3  /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
   4  
   5  /**
   6   * ssh.php -- a class to execute remote commands over SSH
   7   *
   8   *    This file contains the files-based implementation of the SExec
   9   * class. This implementation relies on the usage of regular temporary
  10   * files to communicate with the remote end, thus avoiding several
  11   * drawbacks (mainly deadlocks) associated with pipes.
  12   *
  13   *    The SExec class provides methods to launch and control jobs and
  14   * transfer files over SSH.
  15   *
  16   * PHP versions 4 and 5
  17   *
  18   * LICENSE:
  19   * This library is free software; you can redistribute it and/or
  20   * modify it under the terms of the GNU Lesser General Public
  21   * License as published by the Free Software Foundation; either
  22   * version 2.1 of the License, or (at your option) any later version.
  23   * 
  24   * This library is distributed in the hope that it will be useful,
  25   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  26   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  27   * Lesser General Public License for more details.
  28   * 
  29   * You should have received a copy of the GNU Lesser General Public
  30   * License along with this library; if not, write to the Free Software
  31   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  32   *
  33   * @category    Net
  34   * @package    SExec
  35   * @author     José R. Valverde <jrvalverde@acm.org>
  36   * @copyright     José R. Valverde <jrvalverde@acm.org>
  37   * @license    doc/lic/lgpl.txt
  38   * @version    CVS: $Id: ssh.php,v 1.9 2005/05/30 15:32:41 netadmin Exp $
  39   * @link    http://savannah.cern.ch/projects/GridGRAMM
  40   * @see        ssh(1), scp(1)
  41   * @since    File available since Release 1.0
  42   */
  43  
  44  
  45  /** 
  46   * Allow for remote execution of commands using SSH
  47   *
  48   *    The SExec class provides a number of facilities for remote
  49   * command execution using SSH.
  50   *
  51   *    The name SExec comes after "rexec" (the remote execution library)
  52   * and the "exec" facilities available under PHP. As a matter of fact,
  53   * we try to mimic to some extent the execution facilities offered by
  54   * PHP over SSH: thus you will find ssh_popen() akin to popen(), etc.
  55   *
  56   * <b>RATIONALE</b>
  57   *
  58   *    The reason for this class is to allow executing code on a remote
  59   * back-end avoiding MITM spoofs in your communications. This allows you
  60   * to provide a web front-end (possibly redundant) and call a remote
  61   * back-end to execute the job.
  62   *
  63   *    Furthermore, you may have fallback features where if execution
  64   * on a remote back-end fails you can restart the command on a fallback
  65   * remote host, increasing reliability.
  66   *
  67   * <b>DEPENDENCIES</b>
  68   *
  69   *    The class relies on an underlying installation of SSH. It has
  70   * been tested with OpenSSH on Linux, but should work on other systems
  71   * with OpenSSH as well.
  72   *
  73   *    Further, the class in its current inception relies on OpenSSH
  74   * version being greater than 3.8. If you have an older SSH, please use
  75   * version 1.0 of this class instead.
  76   *
  77   * <b>DESIGN RATIONALE</b>
  78   *
  79   *    The reasons for the choices taken are simple: we might have
  80   * relied on an SSH library (like libSSH) and integrated it with PHP,
  81   * but then, any weakness/bug/change on said library would require a
  82   * recompilation of the library and PHP. This is a serious inconvenience.
  83   * More to that, it would require the maintenance of two simultaneous
  84   * SSH installations, viz. OpenSSH and the library, duplicating the work
  85   * of tracking security/bug issues.
  86   *
  87   *    By using the underlying SSH commands, we become independent of
  88   * them: if anything is discovered, you just have to update your system
  89   * SSH, and nothing else. Otherwise you would have a dependency on SSH
  90   * to remember, which is always forgotten. This way we avoid getting out
  91   * of sync with the system's SSH.
  92   *
  93   *    Better yet: this easies development, making this class a lot
  94   * simpler to write, understand, maintain and debug.
  95   *
  96   *    Finally, the dependency on SSH being OpenSSH 3.8 or greater is
  97   * due to efficiency reasons. Establishing an SSH connection is costly
  98   * in time. If you are going to make many, this would impose a heavy
  99   * cost on your scripts. We routinely launch several thousand remote 
 100   * jobs, and authentication delays soon proved unacceptable.
 101   *
 102   *    OpenSSH 3.8 introduced the possibility of sharing a single SSH
 103   * channel between many "connections". This means that only the first
 104   * (or master) instance (which will provide the shared channel) needs 
 105   * to authenticate, hence saving significant time.
 106   *
 107   *    The constructor then creates a master channel, leaves it idle
 108   * all the object's lifetime and closses it at the end. This channel
 109   * might be used as well, but we felt it wasn't such a big loss to keep
 110   * it idle, and furthermore, being the master, we didn't want to risk
 111   * getting into any trouble that might close it prematurely. So it stands.
 112   *
 113   *    All other routines (which actually do the work) simply hijack on
 114   * the master channel, hence avoiding the costly authentication step (and
 115   * executing significantly faster). The only exception are the "COPY" 
 116   * routines, which can not hijack the master channel and hence must do
 117   * authentication every time.
 118   *
 119   *    One more detail: some methods allow for interactive communication
 120   * with the remote end. We have simply used a terminal-less connection
 121   * for them, using regular files as the intermediate communication channels.
 122   * A pipe implementation is also possible, and works as well, but we have
 123   * found that dealing with pipes is tricky and error-prone, while using
 124   * files is simple and intuitive, so we opted for using files.
 125   *
 126   *    The difference has to do with the way you communicate with the
 127   * other end: using pipes you may block on read and/or write, and so
 128   * may the other end. Since there may occur errors in the process, that
 129   * implies that getting into a deadlock is trivial. Just picture this
 130   * scenarios:
 131   *
 132   *     You send a command -> the remote ends starts the command and
 133   * prompts for input on stdout, hangs reding on stdin -> you read the 
 134   * prompt and send the input -> the remote end wakes and processes it.
 135   *
 136   *    You send a command -> the remote end fails, logs an error on
 137   * stderr, gets back the system prompt and hangs on reading stdin -> you
 138   * notice the prompt and read stderr... since you can't predict the 
 139   * length of the error message you must empty the pipe... and when doing it
 140   * you hang after reading the last char... -> deadlock
 141   *
 142   *    You send a command -> the remote end fails, logs an error on stderr,
 143   * gets back the system prompt and hangs on reading stdin -> you don't read
 144   * stderr to avoid hanging, so submit a new command... this goes on and on
 145   * until the remote side's stderr buffer fills, then the remote side locks
 146   * waiting for you to read stderr -> you can't know it hang, so you try
 147   * to submit a new command, and hang on writing waiting for the other end
 148   * to read your command -> deadlock
 149   *
 150   *     More scenarios are possible, and since you (or the other side)
 151   * can't predict what's going to happen, it is very tricky to avoid them.
 152   *
 153   *    Now, using files, you don't have that problem: whenever you reach
 154   * the current end-of-file, you get an EOF, no need to hang waiting for
 155   * the other side to fill it in with data. The other side doesn't hang on
 156   * writing unless your disk space fills up. It's a lot simpler. 
 157   *
 158   *    Your problem with files is continuing reads after new data becomes
 159   * available: the safest way is to call flush() before reading and seeking
 160   * to the last position read to avoid having to re-read everything (which
 161   * implies that after finishing reading you must ftell() your position.
 162   *
 163   *    See the included test script for examples.
 164   *
 165   * <b>CUSTOMIZATION</b>
 166   *
 167   *    You <i>must</i> state to the class where your SSH executables (ssh and
 168   * scp) are located. This allows you to have them placed anywhere, but
 169   * also implies the responsability of using full pathnames to reduce
 170   * hacking dangers. It also allows you to use/test a new SSH implementation
 171   * installed in a non-standard place before switching to it, or even to
 172   * keep various SSH installations on the system (e.g. if the system's
 173   * SSH is not up-to-date, you may install one on your home and use it).
 174   *
 175   *    You may also indicate where to store temporary files. This must 
 176   * be a directory followed by a prefix to use when creating a temporal
 177   * directory. The parent directory must be writeable by the user who runs 
 178   * the class (usually it will be run by apache, www or some such). Most commonly 
 179   * the parent directory will be /tmp or $DocumentRoot/tmp or something similar.
 180   *
 181   *    The directory+prefix you state will be used to create a unique
 182   * temporary work directory for each object instantiated. Examples of
 183   * a valid specifications are "/tmp/phpSsh-" or "/tmp/". When the object is 
 184   * instantiated, a random string will be appended to this value to create
 185   * the actual temporary directory name.
 186   *    
 187   *    The reason for allowing specifying a prefix is so that debugging
 188   * may be easier by facilitating identification of temporaries generated
 189   * by this class.
 190   *
 191   * <b>DEBUGGING</b>
 192   *
 193   *    The class comes with extensive debugging aids. To enable them,
 194   * just set a global variable called $debug_sexec to TRUE. This will output
 195   * abundant debugging information and leave copies of communication log
 196   * files for your reference.
 197   *
 198   *    Additionally, there is a sample demo script that shows how to
 199   * use this class and may help you debug it. This script is included 
 200   * in the distribution (or should be) as 'ssh_debug.php'. See notes
 201   * and comments within it for more details.
 202   *
 203   * @category       Net
 204   * @package    SExec
 205   * @author     José R. Valverde <jrvalverde@acm.org>
 206   * @copyright     José R. Valverde <jrvalverde@es.embnet.org>
 207   * @license    doc/lic/
 208   * @version        Release: 2.1
 209   * @link    http://savannah.cern.ch/projects/GridGRAMM
 210   * @see        ssh(1), scp(1)
 211   * @since    File available since Release 1.0
 212  
 213   */
 214  class SExec {
 215  
 216      // {{{ properties
 217  
 218      /**
 219       * The current version of the class
 220       *
 221       * @var string
 222       * @access public
 223       */
 224      var $version="2.2";
 225      
 226      /**
 227       * remote endpoint ([user@]host[:port])
 228       *
 229       * @var string
 230       * @access private
 231       */
 232      var $remote;
 233      
 234      /**
 235       * remote password 
 236       *
 237       * @var string
 238       * @access private
 239       */
 240      var $password;
 241  
 242      /**
 243       * location of ssh program
 244       *
 245       * @var string
 246       * @access private
 247       */
 248      var $ssh = "/usr/bin/ssh";
 249      
 250      /**
 251       * location of scp program
 252       *
 253       * @var string
 254       * @access private
 255       */
 256      var $scp = "/usr/bin/scp";
 257      
 258      /**
 259       * tmp. dir prefix specification
 260       *
 261       * @var string
 262       * @access private
 263       */
 264      var $workdir = "/tmp/phpSsh";
 265      
 266      /**
 267       * name of multiplexing socket
 268       *
 269       * @var string
 270       * @access private
 271       */
 272      var $mplex_socket = "/tmp/ssh.mplex";
 273      
 274      /**
 275       * handle to process controlling the master channel
 276       *
 277       * @var string
 278       * @access private
 279       */
 280      var $master;
 281      
 282      /**
 283       * stdin of process controlling the master channel
 284       *
 285       * @var string
 286       * @access private
 287       */
 288      var $master_input;
 289  
 290      //}}}
 291      
 292      //{{{   instantiation
 293      
 294      /** Class constructor.
 295       *
 296       *    Generate a new instance of a remote execution environment.
 297       * The object returned allows you to invoke commands to be executed
 298       * remotely in a way similar to PHP exec commands (popen, proc_open...)
 299       * over SSH (so that your communications can be secure).
 300       *
 301       *    You may specify a remote endpoint and a password, a remote endpoint
 302       * alone, or nothing at all.
 303       *
 304       *    If you provide a remote endpoint and password they are used to drive
 305       * the communications and execute your commands.
 306       *
 307       *    If no password is provided, then a default of "xxyzzy" (the canonical
 308       * computer magic word) is used. Unless this is your password (not 
 309       * recommended), this means that the default password is useless unless
 310       * you are working in a trusted environment where it is not needed and
 311       * ignored. That may be the case if you enable trusting mechanisms with
 312       * .shosts/.rhosts or passphraseless RSA/DSA authentication. Not that
 313       * we endorse them either, but in these cases any password provided will
 314       * be ignored and it doesn't make sense to provide a real one: xxyzzy
 315       * can do as well as any other.
 316       *
 317       *    If no password and no remote end is provided, then "localhost" is
 318       * used as the remote end, assuming no password is required (as described
 319       * above). This is only useful if localhost is trusted, and you have reasons
 320       * to use SSH internally... Some people does.
 321       *
 322       *    Regarding the remote end specification, it can be any valid single-string
 323       * SSH remote end description: the basic format is
 324       *
 325       *    [username@]remote.host[:port]
 326       *
 327       *    - "username" is the remote user name to log in as. It is optional. If provided, 
 328       *       it must be separated from the remote host by an "@" sign. If it is not 
 329       *    provided, the remote username is assumed to be the same as the current local
 330       *    one.
 331       *
 332       *    - "remote.host" is a valid host specification, either a numeric IP address
 333       *    or a valid host name (which may require a full name or not depending on
 334       *    your settings).
 335       *
 336       *     - "port" is the remote port where SSH is listening and which we want to
 337       *    connect to. It is optional, and if provided, must follow the remote host
 338       *    specification separated from it by a colon ":". If not provided, the
 339       *    default port (22) is used.
 340       *
 341       *    Examples of remote host specifications are "user@host.example.net:22",
 342       * "someone@host:22", "host.example.net:22", "host:22", 
 343       * "somebody@host.example.net", "user@host", "host.example.net", "host".
 344       *
 345       * Here is an example of how to use this constructor:
 346       * <code>
 347       *  require_once 'ssh.php';
 348       *
 349       *  $remote = "jruser@example.com";
 350       *  $password = "PASSWORD";
 351       *
 352       *  $rmt = new SExec($remote, $password);
 353       *   if (! $rmt)
 354       *      echo "Couldn't connect to $remote\n";
 355       * </code>
 356       *
 357       *  @param string   The remote end to run the command, in
 358       *                  the form 'user@host:port' (you may
 359       *                        omit the 'user@' or ':port' parts
 360       *                        if the default values [i.e. same user
 361       *                        or standard port] are OK).
 362       *
 363       *  @param string The remote password. Note that if direct
 364       *                  RSA/DSA/.shosts/.rhosts login is enabled
 365       *                  then the password will be ignored as
 366       *                  SSH should not run the ASKPASS command).
 367       *
 368       *    @return SExec|false a new connection object with the remote end or
 369       *                        FALSE if the connection could not be established.
 370       *
 371       *    @access public
 372       *  @since Method available since Release 1.0
 373       */
 374      function SExec($remote="localhost", $password="xxyzzy")
 375      {
 376          global $debug_sexec;
 377      
 378          if ($debug_sexec) echo "\nSExec::SExec($remote, $password)\n";
 379      if ($debug_sexec) echo "--> Creating a new SExec\n";
 380          $this->remote = $remote;
 381      $this->password = "$password";
 382      umask(0077);
 383      /* DESIGN
 384       * In order to increase efficiency, we will create a master channel
 385       * on class instantiation. The master channel should be closed by a
 386       * corresponding class destructor!
 387       *
 388       * Creating a master channel has the advantage that subsequent SSH
 389       * connections will use it and avoid repeating the slow authentication
 390       * process: in other words, they will go much, much faster.
 391       */
 392       
 393      // first we must generate a unique UNIX socket address or we'll fail
 394      // We use a tricky trick: generate two random numbers and use them;
 395      // this is tricky since there might be a problem, but with very low
 396      // probability. BUT IT MAY STILL FAIL: there's a race condition between
 397      // the end of the while and the subsequent if.
 398      do {
 399          mt_srand((double)microtime()*1000000 ) .
 400          $this->workdir = "/tmp/phpSsh-" . mt_rand() .".". mt_rand();
 401          if ($debug_sexec) echo "\nSExec: trying $this->workdir/ ...";
 402          // CAUTION: this is potentially an endless loop (albeit with very
 403          // low probability) if every possible file did exist.
 404      }
 405      while (file_exists($this->workdir));
 406      if (mkdir($this->workdir) == FALSE) {
 407          // we can't continue. How can we cancel this?
 408          // try these and then check what happens
 409          unset($this);
 410          $this = NULL;
 411          return FALSE;
 412      }
 413      else 
 414          if ($debug_sexec) echo " OK\n";
 415      // Now we have a place to put the socket... Mmm...
 416      // Come to think of it, we have a place to put ANY temporary
 417      // for the class... 
 418      // XXX Maybe we can change everywhere else to use this?
 419      $this->mplex_socket = $this->workdir."/mplex_socket";
 420      
 421      // Finally we can call SSH -M
 422      // Create SSH_ASKPASS script to provide the password
 423      $tmpfname = tempnam($this->workdir, 'SExec-');
 424      chmod($tmpfname, 0700);
 425      putenv("DISPLAY=none:0.");
 426      putenv("SSH_ASKPASS=$tmpfname");
 427      $fp = fopen($tmpfname, "w");
 428      fputs($fp, "#!/bin/sh\necho $this->password\n");
 429      if (!$debug_sexec) 
 430          fputs($fp, "/bin/touch $tmpfname.called\n");
 431      else
 432          fputs($fp, "/bin/rm -f $tmpfname\n");
 433      fclose($fp);
 434      
 435      // OK, we are ready. Now let's open a master shell
 436      $child_stdout = tempnam($this->workdir, "open_sh-O-");
 437      $child_stderr = tempnam($this->workdir, "open_sh-E-");
 438      $descriptorspec = array(
 439          0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
 440          1 => array("file", $child_stdout, "a"),  // connect child's stdout to the write end of a pipe
 441          2 => array("file", $child_stderr, "a")   // stderr is a pipe to read from
 442      );
 443  
 444      if ($debug_sexec) echo "$this->ssh -x -t -t ".
 445               "-M -S $this->mplex_socket " .
 446               "$this->remote\n";
 447      $this->master = proc_open("$this->ssh -x -t -t ".
 448               "-M -S $this->mplex_socket " .
 449               "$this->remote",
 450               $descriptorspec,
 451               $pipes);
 452      if ((! is_resource($this->master)) || ($this->master == FALSE)) {
 453          putenv("SSH_ASKPASS=dummy");
 454          unset($this);
 455          $this = NULL;
 456          return FALSE;
 457      }
 458      // we do not need to worry about the output log files, just the
 459      // input pipe for logout
 460      $this->master_input = $pipes[0];
 461      
 462      // Before going ahead, we need to ensure the control shell 
 463      // has started: wait for the socket to become available
 464      // note: there should be a timeout here to avoid a possibly
 465      // infinite loop XXX JR XXX
 466      do {
 467          if ($debug_sexec) echo "waiting 0.1 sec\n";
 468          usleep(100000);    // wait 0.1 seconds
 469      } while (! file_exists($this->mplex_socket));
 470  
 471      // and now we must register a destructor for the class
 472      // that will close the connection.
 473      //register_shutdown_function($this->destruct());
 474      
 475      return $this;
 476      }
 477      
 478      /** Class destructor
 479       *
 480       *    Destroy all working processes and data streams and structures
 481       * used by an instance of this class.
 482       *
 483       *    This method will send a termination message to the other end
 484       * of the master channel, close the control stream of the master
 485       * channel and terminate its controlling process, finally unsetting
 486       * the object and setting the object handle to NULL.
 487       *
 488       *    If a global $debug_sexec is not set to TRUE, then it will also remove
 489       * all communication traces of this object: i.e. all log files for
 490       * interactive and master sessions, communications socket, etc...
 491       *
 492       *    If global $debug_sexec is set to TRUE, then a copy of all log files
 493       * created during the lifetime of the object will be left on a
 494       * temporary directory for your perusal and reference.
 495       *
 496       *    @return integer exit status of the master channel control process.
 497       *
 498       *    @access public
 499       *  @since Method available since Release 1.0
 500       */
 501      function destruct()
 502      {
 503          global $debug_sexec;
 504      
 505      if ($debug_sexec) echo "\nSExec::destruct()\n";
 506      if ($debug_sexec) echo "--> Destroying SExec master\n";
 507      if ($debug_sexec) print_r($this);
 508      if ($debug_sexec) echo "sending logout\n";
 509      // log out master process
 510      fputs($this->master_input, "\n\nlogout\n\n");
 511      // close master stdin
 512      fclose($this->master_input);
 513      // close master process
 514      $ret = proc_close($this->master);
 515      // remove temporaries
 516          if (! $debug_sexec) system("/bin/rm -rf $this->workdir");
 517      // utterly destroy this instance
 518      unset($this);
 519      $this = NULL;
 520      return $ret;
 521      }    
 522      
 523      //}}}
 524      
 525      //{{{ methods
 526      /**
 527       *  Copy a file or directory from one source to a destination
 528       *
 529       *  This function copies source to dest, where one of them is a
 530       * local filespec and the other a remote filespec of the form
 531       * [user@]host:path
 532       *
 533       *  If the original source is a directory, it will be copied
 534       * recursively to destination (hence easing file transfers).
 535       *
 536       *  The function returns TRUE on success or FALSE on failure.
 537       *
 538       * <b>EFFICIENCY NOTICE:</b>
 539       *
 540       *    The copy routines use 'scp' to do their actual work. Since
 541       * scp seems to be unable to hitchhike on the master channel,
 542       * we must do authentication for each copy operation (subroutine 
 543       * call). These routines are hence a lot more time-expensive 
 544       * than all the other ones.
 545       *
 546       *    You may want to consider whether you can group several
 547       * copies into one single call to reduce authentication 
 548       * overheads.
 549       *
 550       *    @note DEPRECATED (inconsistent with the class)
 551       *
 552       *    @see scp(1)
 553       *
 554       *    @param string    The origin path, of the form
 555       *                    [user@][host][:port]path
 556       *                    You may omit the optional sections if
 557       *                    the default values (local username, local
 558       *                    host, standard SSH port) are OK
 559       *
 560       *    @param string    The destination path, of the form
 561       *                    [user@][host][:port:]path
 562       *                    You may omit the optional sections if
 563       *                    the default values (local username, local
 564       *                    host, standard SSH port) are OK
 565       *
 566       *    @param string    The password to use to connect to the remote
 567       *                    end of the copy (be it the origin or the
 568       *                    destination, it's all the same). If connection
 569       *                    is automatic by some means (.shosts or RSA/DSA
 570       *                    authentication) then it should be ignored and
 571       *                    any password should do.
 572       *
 573       *    @return bool TRUE if all went well, or FALSE on failure.
 574       *
 575       *    @access public
 576       *  @since Method available since Release 1.0
 577       *    @deprecated Method deprecated as of Release 2.1
 578       */
 579      function ssh_copy($origin, $destination, $password)
 580      {
 581          global $debug_sexec;
 582  
 583          if ($debug_sexec) echo "\nSExec::ssh_copy($origin, $destination, $password)\n";
 584      umask(0077);
 585      $tmpfname = tempnam($this->workdir, "copy-");
 586      chmod($tmpfname, 0700);
 587      putenv("DISPLAY=none:0.");
 588      putenv("SSH_ASKPASS=$tmpfname");
 589      $fp = fopen($tmpfname, "w");
 590      fputs($fp, "#!/bin/sh\necho $password\n");
 591      if (! $debug_sexec)  
 592          fputs($fp, "/bin/touch $tmpfname.called\n");
 593      else
 594          fputs($fp, "/bin/rm $tmpfname\n");
 595      fclose($fp);
 596      $out="";
 597      exec("$this->scp -pqrC $origin $destination", $out, $status);
 598      if ($status == 0)
 599          return TRUE;
 600      else
 601          return FALSE;
 602      }
 603  
 604  
 605      /**
 606       *  Copy a file or directory from a local source to a remote destination
 607       *
 608       *  This function copies source to dest, where first of them is a
 609       * local filespec and then comes a remote filespec as a normal
 610       * system path.
 611       *
 612       *    Both, local and remote paths may be absolute or relative.
 613       *
 614       *  If the original source is a directory, it will be copied
 615       * recursively to destination (hence easing file transfers).
 616       *
 617       *  The function returns TRUE on success or FALSE on failure.
 618       *
 619       *    @param string    The origin local path, either absolute or
 620       *            relative to the current working directory. 
 621       *            If it denotes a directory, the copy will 
 622       *            be recursive.
 623       *
 624       *    @param string   The destination path, either
 625       *            absolute or relative to the login home.
 626       *
 627       *    @param array   An optional array of strings to be appended the 
 628       *                    copy operation's output for debugging/diagnostics.
 629       *
 630       *    @return bool TRUE if all went well, or FALSE on failure.
 631       *
 632       *    @access public
 633       *  @since Method available since Release 2.1
 634       */
 635      function ssh_copy_to($localpath, $remotepath, &$out)
 636      {
 637          global $debug_sexec;
 638  
 639          if ($debug_sexec) echo "\nSExec::ssh_copy_to($localpath, $remotepath)\n";
 640  
 641  /*    This would be great if SCP could hijack the shared connection (sic)
 642      umask(0077);
 643      $tmpfname = tempnam($this->workdir, "copy-to-");
 644      chmod($tmpfname, 0700);
 645      putenv("DISPLAY=none:0.");
 646      putenv("SSH_ASKPASS=$tmpfname");
 647      $fp = fopen($tmpfname, "w");
 648      fputs($fp, "#!/bin/sh\necho $this->password\n");
 649      if (! $debug_sexec)  
 650          fputs($fp, "/bin/touch $tmpfname.called\n");
 651      else
 652          fputs($fp, "/bin/rm $tmpfname\n");
 653      fclose($fp);
 654      if ($debug_sexec) echo "$this->scp -pqrC $localpath $this->remote:$remotepath\n";
 655      $out = "";
 656      exec("$this->scp -pqrC $localpath $this->remote:$remotepath", $out, $status);
 657      if ($status == 0)
 658          return TRUE;
 659      else {
 660          if ($debug_sexec) echo $out . "\n";
 661          return FALSE;
 662      }
 663  */
 664      // NOTE THAT WE NEED GNU TAR !!!
 665      $retval = $this->ssh_exec("test -d $remotepath 2>&1", $out);
 666      if ($retval == 0) {
 667          // destination is a directory, copy $local inside it
 668          if ($debug_sexec) echo "--> Remote is a directory\n";
 669          $fn = basename($localpath);
 670          $dn = dirname($localpath);
 671          if ($debug_sexec) echo "--> Executing\n" .
 672              "/bin/tar -C $dn -cf - $fn | " .
 673          "ssh -x -T -C -S  $this->mplex_socket $this->remote " .
 674          "\"/bin/tar -C $remotepath -xf -\"\n";
 675          exec("(/bin/tar -C $dn -cf - $fn | " .
 676               "ssh -x -T -C -S  $this->mplex_socket $this->remote " .
 677           "\"/bin/tar -C $remotepath -xf -\")2>&1", 
 678              $out, $retval);
 679      } else {
 680          // destination is not a directory, copy _to_ it
 681          if ($debug_sexec) 
 682              echo "--> remote is not a directory or does not exist\n";
 683          if ((file_exists("$localpath/.")) && (is_dir("$localpath/."))) {
 684              // if local is a dir, try to create it remotely with new name
 685              $retval = $this->ssh_exec("/bin/mkdir -p $remotepath ", $out);
 686          if ($retval != 0) {
 687              // we can't create it, either it already exists as a
 688              // regular file or we don't have permissions, anyhow,
 689              // we can't do the copy
 690                  if ($debug_sexec) print_r($out);
 691              return FALSE;
 692          }
 693          // now cd lo local and copy over to remote
 694          if ($debug_sexec) echo "--> Executing \n" .
 695              "    /bin/tar -C $localpath -cf - . | \n" .
 696              "    $this->ssh -x -T -C -S $this->mplex_socket $this->remote \n" .
 697                  "    /bin/tar -C $remotepath -xf -\n";
 698          exec("(/bin/tar -C $localpath -cf - . | " .
 699                  "$this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
 700                  "/bin/tar -C $remotepath -xf -)2>&1", 
 701              $out, $retval);
 702          }
 703          else {
 704              // non-dir: file, block-special, char-special, pipe, socket...
 705          if ($debug_sexec) echo "--> Executing \n" .
 706              "    cat $localpath | \n" .
 707              "    $this->ssh -x -T -C -S $this->mplex_socket $this->remote \n" .
 708              "    \"cat > $remotepath\"\n";
 709              exec("(cat $localpath | " .
 710                  "$this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
 711              "\"cat > $remotepath\") 2>&1", $out, $retval);
 712          }
 713      }
 714      if ($retval != 0) {
 715          if ($debug_sexec) print_r($out);
 716              return FALSE;
 717      }
 718      else
 719          return TRUE;
 720      }
 721      
 722      /**
 723       *  Copy a file or directory from a remote source to a local destination
 724       *
 725       *  This function copies source to dest, where first of them is a
 726       * remote filespec and then comes a local filespec, both specified 
 727       * as normal system paths.
 728       *
 729       *    Both, local and remote paths may be absolute or relative.
 730       *
 731       *  If the original source is a directory, it will be copied
 732       * recursively to destination (hence easing file transfers).
 733       *
 734       *  The function returns TRUE on success or FALSE on failure.
 735       *
 736       *    @param string   The origin remote path, either absolute or
 737       *            relative to the login home. If it denotes a 
 738       *            directory, the copy will be recursive.
 739       *
 740       *    @param string    The local destination path, either
 741       *            absolute or relative to the current working
 742       *            directory.
 743       *
 744       *    @param array   An optional array of strings to be appended the 
 745       *                    copy operation's output for debugging/diagnostics.
 746       *
 747       *    @return bool TRUE if all went well, or FALSE on failure.
 748       *
 749       *    @access public
 750       *  @since Method available since Release 1.0
 751       */
 752      function ssh_copy_from($remotepath, $localpath, &$out)
 753      {
 754          global $debug_sexec;
 755  
 756          if ($debug_sexec) echo "SExec::ssh_copy_from($remotepath, $localpath)\n";
 757  
 758  /*    This would be great if SCP could hijack the shared connection (sic)
 759      umask(0077);
 760      $tmpfname = tempnam($this->workdir, "copy-from-");
 761      chmod($tmpfname, 0700);
 762      putenv("DISPLAY=none:0.");
 763      putenv("SSH_ASKPASS=$tmpfname");
 764      $fp = fopen($tmpfname, "w");
 765      fputs($fp, "#!/bin/sh\necho $this->password\n");
 766      if (! $debug_sexec)  
 767          fputs($fp, "/bin/touch $tmpfname.called\n");
 768      else
 769          fputs($fp, "/bin/rm $tmpfname\n");
 770      fclose($fp);
 771      if ($debug_sexec) echo "$this->scp -pqrC $this->remote:$remotepath $localpath\n";
 772      $out = "";
 773      exec("$this->scp -pqrC $this->remote:$remotepath $localpath", $out, $status);
 774      if ($status == 0)
 775          return TRUE;
 776      else {
 777          if ($debug_sexec) echo $out . "\n";
 778          return FALSE;
 779      }
 780  */      
 781          if ((file_exists("$localpath/.")) && (is_dir("$localpath/."))) {
 782          // Local is a directory. Copy remote into it.
 783          if ($debug_sexec) echo "--> $localpath/. is a dir\n";
 784              if ($debug_sexec) echo "--> Executing\n" .
 785          "$this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
 786                  "\"/bin/tar -C " .dirname($remotepath). 
 787                  " -cf - ". basename($remotepath) ."\" | ".
 788                  "/bin/tar -C $localpath -xf -\n";
 789              exec("($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
 790                  "\"/usr/local/bin/tar -C " .dirname($remotepath). 
 791                  " -cf - ". basename($remotepath) ."\" | ".
 792                  "/bin/tar -C $localpath -xf -) 2>&1",
 793           $out, $res);
 794          }
 795      else {
 796          // either the local side does not exist or is not a directory
 797          // if remote is a directory
 798          //        make local equivalent and copy contents (make will
 799          //        fail if local exists as a non-dir)
 800          if ($debug_sexec) echo "--> $localpath is NOT a dir\n";
 801          $res = $this->ssh_exec("test -d $remotepath 2>&1", $out);
 802          if ($res == 0) {
 803              exec("/bin/mkdir -p $localpath 2>&1", $out, $res);
 804          if ($res != 0) {
 805              // can't create the dir, either it is a regular file
 806              // or we don't have privileges
 807              if ($debug_sexec) print_r($out);
 808              return FALSE;
 809          }
 810          // copy in the remote contents
 811          if ($debug_sexec) echo "-->Executing\n" .
 812              "($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
 813              "\"/bin/tar -C $remotepath -cf - .\" | " .
 814              "/bin/tar -C $localpath -xf -)2>&1\n";
 815          exec("($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
 816              "\"/bin/tar -C $remotepath -cf - .\" | " .
 817              "/bin/tar -C $localpath -xf -)2>&1",
 818              $out, $res);
 819          
 820          } else {
 821              // remote is a non-dir: cat over local
 822          if ($debug_sexec) echo "-->Executing\n" .
 823              "($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
 824              "\"cat $remotepath\" | ".
 825              " cat > $localpath) 2>&1\n";
 826              exec("($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
 827              "\"cat $remotepath\" | ".
 828              " cat > $localpath) 2>&1", $out, $res);
 829              }
 830          }
 831      if ($res == 0) 
 832          return TRUE;
 833      else {
 834          if ($debug_sexec) print_r($out);
 835          return FALSE;
 836      }
 837      }
 838  
 839      /**
 840       *    Execute a single command remotely
 841       *
 842       *  Execute a single command remotely using ssh and 
 843       * display its output, optionally returning its exit 
 844       * status (like passthru)
 845       *
 846       *    This function is intended to be used as a one-time
 847       * all-at-once non-interactive execution mechanism which
 848       * will run the command remotely and display its output.
 849       *
 850       *    If you try to issue an interactive command using this
 851       * function, all you will get is unneccessary trouble. So
 852       * don't!
 853       *
 854       *  This might be done as well using a pipe on /tmp and
 855       * making the command 'cat' the pipe: when ssh runs, it
 856       * runs the command 'cat' on the pipe and hangs on read.
 857       *  Then we just need a thread to open the pipe, put the
 858       * password and close the pipe.
 859       *
 860       *  This other way the password is never wirtten down.
 861       * But, OTOH, the file life is so ephemeral that most
 862       * of the time it will only exist in the internal system
 863       * cache, so this approach is not that bad either.
 864       *
 865       *    @see passthru()
 866       *
 867       *  @param string command  The command to execute on the remote end
 868       *                  NOTE: if you want to use redirection, the
 869       *                  entire remote command line should be 
 870       *                  enclosed in additional quotes!
 871       *  @param integer status   Optional, this will hold the termination
 872       *                  status of SSH after invocation, which
 873       *                  should be the exit status of the remote
 874       *                  command or 255 if an error occurred
 875       *  @return void
 876       *
 877       *    @access public
 878       *  @since Method available since Release 1.0
 879       */
 880      function ssh_passthru($command, &$status)
 881      {
 882          global $debug_sexec;
 883  
 884          if ($debug_sexec) echo "status = $status\n";
 885      // go
 886      if (isset($status)) {
 887          if ($debug_sexec) echo "st: $this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"\n";
 888          passthru("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $status);
 889      }
 890      else {
 891          if ($debug_sexec) echo "~st: $this->ssh -x -t -t  -S $this->mplex_socket $this->remote \"$command\"\n";
 892          passthru("$this->ssh -x -t -t  -S $this->mplex_socket $this->remote \"$command\"");
 893          }
 894      }
 895      
 896      
 897      /**
 898       *    Execute a remote command using SSH
 899       *
 900       *    This function sort of mimics rexec(3) using SSH as the transport
 901       * protocol.
 902       *
 903       *    The function returns the exit status of the remote command, and
 904       * appends the remote job output to an optional argument.
 905       *
 906       *    This function is intended to be used as a one-time
 907       * all-at-once non-interactive execution mechanism which
 908       * will run the command remotely and return its output.
 909       *
 910       *    If you try to issue an interactive command using this
 911       * function, all you will get is unneccessary trouble. So
 912       * don't!
 913       *
 914       *  @param string command  The command to execute on the remote end
 915       *                  NOTE: if you want to use redirection, the
 916       *                  entire remote command line should be 
 917       *                  enclosed in additional quotes!
 918       *    @param  array If the output argument is present, then the specified 
 919       *                        array will be filled with every line of output 
 920       *                        from the command. Line endings, such as \n, are 
 921       *                        not included in this array. Note that if the array 
 922       *                        already contains some elements, exec() will append 
 923       *                        to the end of the array. If you do not want the 
 924       *                        function to append elements, call unset() on the 
 925       *                        array before passing it to exec().
 926       *  @return integer status  will hold the termination
 927       *                  status of SSH after invocation, which
 928       *                  should be the exit status of the remote
 929       *                  command or 255 if an error occurred
 930       *
 931       *    @access public
 932       *  @since Method available since Release 1.0
 933       */
 934      function ssh_exec($command, &$out)
 935      {
 936          global $debug_sexec;
 937  
 938          if ($debug_sexec) echo "SExec::ssh_exec($command, $out)\n";
 939      umask(0077);
 940      $tmpfname = tempnam($this->workdir, 'exec');
 941      chmod($tmpfname, 0700);
 942      if ($debug_sexec) echo $tmpfname . "\n";
 943  
 944      exec("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $out, $retval);
 945      return $retval;
 946  
 947      }
 948      
 949      /**
 950       *    Open an SSH connection to a remote site with a shell to run 
 951       * interactive commands
 952       *
 953       *    Connects to a remote host and opens an interactive shell session
 954       * with NO controlling terminal.
 955       *
 956       *    This routine creates communication streams with the remote shell,
 957       * and stores all output (standard and error) of the connection into
 958       * two separate local log files (one for stdout and one for stderr).
 959       *
 960       *    Returns a process_control array which contains the process resource
 961       * ID and an the standard file descriptors which the caller may use to
 962       * interact with the remote shell.
 963       *
 964       * The process control array contains:
 965       *
 966       *    'process' -- the process resource for the newly created connection
 967       *
 968       *    'std_in' -- handle to the standard input of the new connection
 969       *
 970       *    'std_out' -- handle to standard output of the new connection
 971       *
 972       *    'std_err' -- handle to standard error of the new connection
 973       *
 974       *    'stdout_file' -- actual filename of the local log file for the
 975       *        new connection standard output
 976       *
 977       *    'stderr_file' -- actual filename of the local log file for the
 978       *        new connection standard error
 979       *
 980       *    @return mixed|false a process control associative array or FALSE
 981       *            on failure.
 982       *
 983       *    @access public
 984       *  @since Method available since Release 1.0
 985       */
 986      function ssh_open_shell()
 987      {    
 988      global $debug_sexec;
 989  
 990      // Open a child process with the 'proc_open' function. 
 991      //
 992      // Some tricks: we must open the connection using '-x' to disable
 993      // X11 forwarding, and use '-t -t' to avoid SSH generating an error
 994      // because we are not connected to any terminal.
 995      //
 996      // NOTE: if the web server is trusted remotely (i.e. it's SSH public 
 997      // key is accepted in ~user@host:.ssh/authorized_keys) then any 
 998      // password will do.
 999  
1000      // Prepare I/O
1001      umask(0077);
1002      if ($debug_sexec) {
1003          $child_stdout = tempnam($this->workdir, "open_sh-".getmypid()."-O-");
1004          $child_stderr = tempnam($this->workdir, "open_sh-".getmypid()."-E-");
1005      } else {
1006          $child_stdout = tempnam($this->workdir, "open_sh-");
1007          $child_stderr = tempnam($this->workdir, "open_sh-");
1008      }
1009      $descriptorspec = array(
1010              0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
1011              1 => array("file", $child_stdout, "a"),  // connect child's stdout to the write end of a pipe
1012              2 => array("file", $child_stderr, "a")   // stderr is a pipe to read from
1013      );
1014      if ($debug_sexec) echo "$this->ssh -x -t -t -S $this->mplex_socket $this->remote<br />\n";
1015      $process = proc_open("$this->ssh -x -t -t -S $this->mplex_socket $this->remote", 
1016                   $descriptorspec,
1017               $pipes);
1018      
1019      // check status
1020      if ((!is_resource($process)) || ($process == FALSE)) 
1021      {
1022          letal("SSH::connect", "cannot connect to the remote host");
1023          return FALSE;
1024      }
1025      if ($debug_sexec) echo "proc_open done<br />\n";
1026  
1027      // $pipes now looks like this:
1028      //   0 => writeable handle connected to child stdin
1029      
1030      // Open child's stdin and stdout
1031      $pipes[1] = fopen($child_stdout, "r");
1032      $pipes[2] = fopen($child_stderr, "r");
1033      
1034      // Should we leave this to the user?
1035      // set to non-blocking and avoid having to call fflush
1036      //stream_set_blocking($pipes[0], FALSE);
1037      //stream_set_blocking($pipes[1], FALSE);
1038      //stream_set_blocking($pipes[2], FALSE);
1039      stream_set_write_buffer($pipes[0], 0);
1040      stream_set_write_buffer($pipes[1], 0);
1041      stream_set_write_buffer($pipes[2], 0);
1042  
1043      // We now have a connection to the remote SSH
1044      // Server which we may use to send commands/receive output
1045      $p = array('process' => $process
1046                  ,'std_in' => $pipes[0]
1047                      ,'std_out' => $pipes[1]
1048              ,'std_err' => $pipes[2] 
1049              ,'stdout_file' => $child_stdout
1050              ,'stderr_file' => $child_stderr
1051             );
1052      if ($debug_sexec)  {
1053          echo "process descriptor array is \n";
1054          print_r($p);
1055      }
1056      return $p;
1057      }
1058      
1059      /**
1060       *    Open an SSH connection to run an interactive command on a remote
1061       * site
1062       *
1063       *    Connects to a remote host and runs an interactive command
1064       * with NO controlling terminal.
1065       *
1066       *    This routine creates communication streams with the remote shell,
1067       * and stores all output (standard and error) of the connection into
1068       * two separate local log files (one for stdout and one for stderr).
1069       *
1070       *    Returns a process_control array which contains the process resource
1071       * ID and an the standard file descriptors which the caller may use to
1072       * interact with the remote shell.
1073       *
1074       * The process control array contains:
1075       *
1076       *    'process' -- the process resource for the newly created connection
1077       *
1078       *    'std_in' -- handle to the standard input of the new connection
1079       *
1080       *    'std_out' -- handle to standard output of the new connection
1081       *
1082       *    'std_err' -- handle to standard error of the new connection
1083       *
1084       *    'stdout_file' -- actual filename of the local log file for the
1085       *        new connection standard output
1086       *
1087       *    'stderr_file' -- actual filename of the local log file for the
1088       *        new connection standard error
1089       *
1090       *    @param    string command to be executed interactively on the remote end
1091       *
1092       *    @return mixed|false a process control associative array or FALSE
1093       *            on failure.
1094       *
1095       *    @access public
1096       *  @since Method available since Release 1.0
1097       */
1098      function ssh_open_command($command)
1099      {    
1100      global $debug_sexec;
1101  
1102      // Open a child process with the 'proc_open' function. 
1103      //
1104      // Some tricks: we must open the connection using '-x' to disable
1105      // X11 forwarding, and use '-t -t' to avoid SSH generating an error
1106      // because we are not connected to any terminal.
1107      //
1108      // NOTE: if the web server is trusted remotely (i.e. it's SSH public 
1109      // key is accepted in ~user@host:.ssh/authorized_keys) then any 
1110      // password will do.
1111  
1112      // Prepare I/O
1113      umask(0077);
1114      if ($debug_sexec) {
1115          $child_stdout = tempnam($this->workdir, "open_cmd-".getmypid()."-1-");
1116          $child_stderr = tempnam($this->workdir, "open_cmd-".getmypid()."-2-");
1117      } else {
1118          $child_stdout = tempnam($this->workdir, "open_cmd-");
1119          $child_stderr = tempnam($this->workdir, "open_cmd-");
1120      }
1121      $descriptorspec = array(
1122              0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
1123              #1 => array("pipe", "a"),  // connect child's stdout to the write end of a pipe
1124              #2 => array("pipe", "a")   // stderr is a pipe to read from
1125          1 => array("file", $child_stdout, "a"),
1126          2 => array("file", $child_stderr, "a")
1127      );
1128  
1129      if ($debug_sexec) echo "$this->ssh -x -t -t -S $this->mplex_socket $this->remote $command<br />\n";
1130      $process = proc_open("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", 
1131                   $descriptorspec,
1132               $pipes);
1133      
1134      // check status
1135      if ((!is_resource($process)) || ($process == FALSE)) 
1136      {
1137          letal("SSH::connect", "cannot connect to the remote host");
1138          return FALSE;
1139      }
1140      if ($debug_sexec) echo "proc_open done<br />\n";
1141  
1142      // $pipes now looks like this:
1143      //   0 => writeable handle connected to child stdin
1144      
1145      // Open child's stdin and stdout
1146      $pipes[1] = fopen($child_stdout, "r");
1147      $pipes[2] = fopen($child_stderr, "r");
1148      
1149      // Should we leave this to the user?
1150      // set to non-blocking and avoid having to call fflush
1151      #stream_set_blocking($pipes[0], FALSE);
1152      #stream_set_blocking($pipes[1], FALSE);
1153      #stream_set_blocking($pipes[2], FALSE);
1154      stream_set_write_buffer($pipes[0], 0);
1155      stream_set_write_buffer($pipes[1], 0);
1156      stream_set_write_buffer($pipes[2], 0);
1157  
1158      // We now have a connection to the remote SSH
1159      // Server which we may use to send commands/receive output
1160      $p = array('process' => $process
1161                  ,'std_in' => $pipes[0]
1162                      ,'std_out' => $pipes[1]
1163              ,'std_err' => $pipes[2] 
1164              ,'stdout_file' => $child_stdout
1165              ,'stderr_file' => $child_stderr
1166             );
1167      if ($debug_sexec)  {
1168          echo "process descriptor array is \n";
1169          print_r($p);
1170      }
1171      return $p;
1172      }
1173      
1174      /**
1175       * Get output until we reach a given regular expression
1176       *
1177       *    @note EXPERIMENTAL, requires more thought and experience.
1178       */
1179      function ssh_out_expect($p, $expr="^# ")
1180      {
1181          do {
1182              flush();
1183          fseek($p["std_out"], $last);
1184              $line = fgets($p["std_out"], 1024);
1185              #echo ">> ".$line;
1186          $last = ftell($p["std_out"]);
1187          } while ((! feof($p["std_out"]) ) || (! ereg($expr, $line)));
1188      }
1189  
1190      /**
1191       * Close an SSH interactive session
1192       *
1193       *    This method terminates a previously open interactive remote 
1194       * session. It will send a termination notification to the
1195       * remote end, close the connection with control and communication
1196       * streams, and terminate the local control process.
1197       *
1198       *    Copies of the log files that contain the output and error
1199       * of the communication are left out for later reference and 
1200       * local peruse. If you don't need them any longer, you may
1201       * delete them or just leave them around until the class destructor
1202       * is called (which will remove all session traces),
1203       *
1204       *    @param mixed p an associative array with the description of the interactive
1205       *        session control process, obtained by a previous call to one
1206       *        of the interactive session creation methods ssh_open_shell()
1207       *        or ssh_open_command().
1208       *
1209       *    @return integer the exit status of the remote interactive session.
1210       *
1211       *    @access public
1212       *  @since Method available since Release 1.0
1213       */
1214      function ssh_close($p)
1215      {
1216          global $debug_sexec;
1217      
1218          fwrite($p['std_in'], "\n");
1219          fwrite($p['std_in'], "logout\n");
1220          fflush($p['std_in']);
1221          fclose($p['std_in']); fclose($p['std_out']); fclose($p['std_err']);
1222          if ($debug_sexec) echo "pipes/files closed\n";
1223          // XXX we should delete the log files here...
1224          return proc_close($p['process']);
1225      }
1226      
1227  #    if ($php_version >= 5)
1228  #    {
1229  #    /**
1230  #     * send a signal to a running ssh_open_* process
1231  #     */
1232  #    function ssh_signal($p, $signal)
1233  #    {
1234  #            return proc_terminate($p['process'], $signal);
1235  #    }
1236  #    /**
1237  #     * get info about a running ssh_open_* process
1238  #     */
1239  #    function ssh_get_status($p)
1240  #    {
1241  #            return proc_get_status($p['process']);
1242  #    }
1243  #    }
1244      
1245      /**
1246       *    Execute a remote command and keep an unidirectional stream
1247       * contact with it.
1248       *
1249       *    This routine mimics 'popen()' but uses ssh to connect to
1250       * a remote host and run the requested command: in other words,
1251       * it opens a pipe to a remotely executed command. This pipe is
1252       * unidirectional, with the communications direction controlled
1253       * by a method parameter.
1254       *
1255       *    @see popen() for more details.
1256       *
1257       *    @param string command is the command to execute on the remote end
1258       *
1259       *    @param string mode specifies the communications direction for the 
1260       *        pipe: if set to "r" (read), then we will be able to
1261       *        collect command output only; if set to "w" (write)
1262       *        then we may only send input to the remote command.
1263       *
1264       *    @return resource a handle to the unidirectional communication stream,
1265       *        similar to that returned by fopen(), or FALSE on
1266       *        failure. This handle must be closed with ssh_pclose().
1267       *
1268       *    @access public
1269       *  @since Method available since Release 1.0
1270       */
1271      function ssh_popen($command, $mode)
1272      {
1273          global $debug_sexec;
1274  
1275      // go
1276      return popen("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $mode);
1277      }
1278      
1279      /**
1280       * Close a piped remote execution command control pipe.
1281       *
1282       *    This routine accepts as input the handle for the control stream
1283       * of a remote command and closes it, terminating the command as well.
1284       * The handle must be valid and obtained through a call to ssh_popen().
1285       *
1286       *    @param resource f is the file handle associated with the pipe control stream
1287       *
1288       *    @return integer the termination status of the command that was run.
1289       *
1290       *    @access public
1291       *  @since Method available since Release 1.0
1292       */
1293      function ssh_pclose($f)
1294      {
1295          return pclose($f);
1296      }
1297  
1298      //}}}
1299  }
1300  
1301  /*
1302   * Local variables:
1303   * tab-width: 4
1304   * c-basic-offset: 4
1305   * c-hanging-comment-ender-p: nil
1306   * End:
1307   */
1308  
1309  ?>


Generated: Tue May 31 15:44:47 2005 Cross-referenced by PHPXref 0.4.1