Main Page | Namespace List | File List | File Members

ssh.php

Go to the documentation of this file.
00001 <?php
00002 
00003 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
00004 
00005 /**
00006  * ssh.php -- a class to execute remote commands over SSH
00007  *
00008  *      This file contains the files-based implementation of the SExec
00009  * class. This implementation relies on the usage of regular temporary
00010  * files to communicate with the remote end, thus avoiding several
00011  * drawbacks (mainly deadlocks) associated with pipes.
00012  *
00013  *      The SExec class provides methods to launch and control jobs and
00014  * transfer files over SSH.
00015  *
00016  * PHP versions 4 and 5
00017  *
00018  * LICENSE:
00019  * This library is free software; you can redistribute it and/or
00020  * modify it under the terms of the GNU Lesser General Public
00021  * License as published by the Free Software Foundation; either
00022  * version 2.1 of the License, or (at your option) any later version.
00023  * 
00024  * This library is distributed in the hope that it will be useful,
00025  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00026  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00027  * Lesser General Public License for more details.
00028  * 
00029  * You should have received a copy of the GNU Lesser General Public
00030  * License along with this library; if not, write to the Free Software
00031  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00032  *
00033  * @category    Net
00034  * @package     SExec
00035  * @author      José R. Valverde <jrvalverde@acm.org>
00036  * @copyright   José R. Valverde <jrvalverde@acm.org>
00037  * @license     doc/lic/lgpl.txt
00038  * @version     CVS: $Id: ssh.php,v 1.11 2005/06/16 07:21:40 netadmin Exp $
00039  * @link        http://savannah.cern.ch/projects/GridGRAMM
00040  * @see         ssh(1), scp(1)
00041  * @since       File available since Release 1.0
00042  */
00043 
00044 
00045 /** 
00046  * Allow for remote execution of commands using SSH
00047  *
00048  *      The SExec class provides a number of facilities for remote
00049  * command execution using SSH.
00050  *
00051  *      The name SExec comes after "rexec" (the remote execution library)
00052  * and the "exec" facilities available under PHP. As a matter of fact,
00053  * we try to mimic to some extent the execution facilities offered by
00054  * PHP over SSH: thus you will find ssh_popen() akin to popen(), etc.
00055  *
00056  * <b>RATIONALE</b>
00057  *
00058  *      The reason for this class is to allow executing code on a remote
00059  * back-end avoiding MITM spoofs in your communications. This allows you
00060  * to provide a web front-end (possibly redundant) and call a remote
00061  * back-end to execute the job.
00062  *
00063  *      Furthermore, you may have fallback features where if execution
00064  * on a remote back-end fails you can restart the command on a fallback
00065  * remote host, increasing reliability.
00066  *
00067  * <b>DEPENDENCIES</b>
00068  *
00069  *      The class relies on an underlying installation of SSH. It has
00070  * been tested with OpenSSH on Linux, but should work on other systems
00071  * with OpenSSH as well.
00072  *
00073  *      Further, the class in its current inception relies on OpenSSH
00074  * version being greater than 3.8. If you have an older SSH, please use
00075  * version 1.0 of this class instead.
00076  *
00077  * <b>DESIGN RATIONALE</b>
00078  *
00079  *      The reasons for the choices taken are simple: we might have
00080  * relied on an SSH library (like libSSH) and integrated it with PHP,
00081  * but then, any weakness/bug/change on said library would require a
00082  * recompilation of the library and PHP. This is a serious inconvenience.
00083  * More to that, it would require the maintenance of two simultaneous
00084  * SSH installations, viz. OpenSSH and the library, duplicating the work
00085  * of tracking security/bug issues.
00086  *
00087  *      By using the underlying SSH commands, we become independent of
00088  * them: if anything is discovered, you just have to update your system
00089  * SSH, and nothing else. Otherwise you would have a dependency on SSH
00090  * to remember, which is always forgotten. This way we avoid getting out
00091  * of sync with the system's SSH.
00092  *
00093  *      Better yet: this easies development, making this class a lot
00094  * simpler to write, understand, maintain and debug.
00095  *
00096  *      Finally, the dependency on SSH being OpenSSH 3.8 or greater is
00097  * due to efficiency reasons. Establishing an SSH connection is costly
00098  * in time. If you are going to make many, this would impose a heavy
00099  * cost on your scripts. We routinely launch several thousand remote 
00100  * jobs, and authentication delays soon proved unacceptable.
00101  *
00102  *      OpenSSH 3.8 introduced the possibility of sharing a single SSH
00103  * channel between many "connections". This means that only the first
00104  * (or master) instance (which will provide the shared channel) needs 
00105  * to authenticate, hence saving significant time.
00106  *
00107  *      The constructor then creates a master channel, leaves it idle
00108  * all the object's lifetime and closses it at the end. This channel
00109  * might be used as well, but we felt it wasn't such a big loss to keep
00110  * it idle, and furthermore, being the master, we didn't want to risk
00111  * getting into any trouble that might close it prematurely. So it stands.
00112  *
00113  *      All other routines (which actually do the work) simply hijack on
00114  * the master channel, hence avoiding the costly authentication step (and
00115  * executing significantly faster). The only exception are the "COPY" 
00116  * routines, which can not hijack the master channel and hence must do
00117  * authentication every time.
00118  *
00119  *      One more detail: some methods allow for interactive communication
00120  * with the remote end. We have simply used a terminal-less connection
00121  * for them, using regular files as the intermediate communication channels.
00122  * A pipe implementation is also possible, and works as well, but we have
00123  * found that dealing with pipes is tricky and error-prone, while using
00124  * files is simple and intuitive, so we opted for using files.
00125  *
00126  *      The difference has to do with the way you communicate with the
00127  * other end: using pipes you may block on read and/or write, and so
00128  * may the other end. Since there may occur errors in the process, that
00129  * implies that getting into a deadlock is trivial. Just picture this
00130  * scenarios:
00131  *
00132  *      You send a command -> the remote ends starts the command and
00133  * prompts for input on stdout, hangs reding on stdin -> you read the 
00134  * prompt and send the input -> the remote end wakes and processes it.
00135  *
00136  *      You send a command -> the remote end fails, logs an error on
00137  * stderr, gets back the system prompt and hangs on reading stdin -> you
00138  * notice the prompt and read stderr... since you can't predict the 
00139  * length of the error message you must empty the pipe... and when doing it
00140  * you hang after reading the last char... -> deadlock
00141  *
00142  *      You send a command -> the remote end fails, logs an error on stderr,
00143  * gets back the system prompt and hangs on reading stdin -> you don't read
00144  * stderr to avoid hanging, so submit a new command... this goes on and on
00145  * until the remote side's stderr buffer fills, then the remote side locks
00146  * waiting for you to read stderr -> you can't know it hang, so you try
00147  * to submit a new command, and hang on writing waiting for the other end
00148  * to read your command -> deadlock
00149  *
00150  *      More scenarios are possible, and since you (or the other side)
00151  * can't predict what's going to happen, it is very tricky to avoid them.
00152  *
00153  *      Now, using files, you don't have that problem: whenever you reach
00154  * the current end-of-file, you get an EOF, no need to hang waiting for
00155  * the other side to fill it in with data. The other side doesn't hang on
00156  * writing unless your disk space fills up. It's a lot simpler. 
00157  *
00158  *      Your problem with files is continuing reads after new data becomes
00159  * available: the safest way is to call flush() before reading and seeking
00160  * to the last position read to avoid having to re-read everything (which
00161  * implies that after finishing reading you must ftell() your position.
00162  *
00163  *      See the included test script for examples.
00164  *
00165  * <b>CUSTOMIZATION</b>
00166  *
00167  *      You <i>must</i> state to the class where your SSH executables (ssh and
00168  * scp) are located. This allows you to have them placed anywhere, but
00169  * also implies the responsability of using full pathnames to reduce
00170  * hacking dangers. It also allows you to use/test a new SSH implementation
00171  * installed in a non-standard place before switching to it, or even to
00172  * keep various SSH installations on the system (e.g. if the system's
00173  * SSH is not up-to-date, you may install one on your home and use it).
00174  *
00175  *      You may also indicate where to store temporary files. This must 
00176  * be a directory followed by a prefix to use when creating a temporal
00177  * directory. The parent directory must be writeable by the user who runs 
00178  * the class (usually it will be run by apache, www or some such). Most commonly 
00179  * the parent directory will be /tmp or $DocumentRoot/tmp or something similar.
00180  *
00181  *      The directory+prefix you state will be used to create a unique
00182  * temporary work directory for each object instantiated. Examples of
00183  * a valid specifications are "/tmp/phpSsh-" or "/tmp/". When the object is 
00184  * instantiated, a random string will be appended to this value to create
00185  * the actual temporary directory name.
00186  *      
00187  *      The reason for allowing specifying a prefix is so that debugging
00188  * may be easier by facilitating identification of temporaries generated
00189  * by this class.
00190  *
00191  * <b>DEBUGGING</b>
00192  *
00193  *      The class comes with extensive debugging aids. To enable them,
00194  * just set a global variable called $debug_sexec to TRUE. This will output
00195  * abundant debugging information and leave copies of communication log
00196  * files for your reference.
00197  *
00198  *      Additionally, there is a sample demo script that shows how to
00199  * use this class and may help you debug it. This script is included 
00200  * in the distribution (or should be) as 'ssh_debug.php'. See notes
00201  * and comments within it for more details.
00202  *
00203  * @category    Net
00204  * @package     SExec
00205  * @author      José R. Valverde <jrvalverde@acm.org>
00206  * @copyright   José R. Valverde <jrvalverde@es.embnet.org>
00207  * @license     doc/lic/
00208  * @version     Release: 2.1
00209  * @link        http://savannah.cern.ch/projects/GridGRAMM
00210  * @see         ssh(1), scp(1)
00211  * @since       File available since Release 1.0
00212 
00213  */
00214 class SExec {
00215 
00216     // {{{ properties
00217 
00218     /**
00219      * The current version of the class
00220      *
00221      * @var string
00222      * @access public
00223      */
00224     var $version="2.2";
00225     
00226     /**
00227      * remote endpoint ([user@]host[:port])
00228      *
00229      * @var string
00230      * @access private
00231      */
00232     var $remote;
00233     
00234     /**
00235      * remote password 
00236      *
00237      * @var string
00238      * @access private
00239      */
00240     var $password;
00241 
00242     /**
00243      * location of ssh program
00244      *
00245      * @var string
00246      * @access private
00247      */
00248     var $ssh = "/usr/bin/ssh";
00249     
00250     /**
00251      * location of scp program
00252      *
00253      * @var string
00254      * @access private
00255      */
00256     var $scp = "/usr/bin/scp";
00257     
00258     /**
00259      * tmp. dir prefix specification
00260      *
00261      * @var string
00262      * @access private
00263      */
00264     var $workdir = "/tmp/phpSsh";
00265     
00266     /**
00267      * name of multiplexing socket
00268      *
00269      * @var string
00270      * @access private
00271      */
00272     var $mplex_socket = "/tmp/ssh.mplex";
00273     
00274     /**
00275      * handle to process controlling the master channel
00276      *
00277      * @var string
00278      * @access private
00279      */
00280     var $master;
00281     
00282     /**
00283      * stdin of process controlling the master channel
00284      *
00285      * @var string
00286      * @access private
00287      */
00288     var $master_input;
00289 
00290     //}}}
00291     
00292     //{{{   instantiation
00293     
00294     /** Class constructor.
00295      *
00296      *  Generate a new instance of a remote execution environment.
00297      * The object returned allows you to invoke commands to be executed
00298      * remotely in a way similar to PHP exec commands (popen, proc_open...)
00299      * over SSH (so that your communications can be secure).
00300      *
00301      *  You may specify a remote endpoint and a password, a remote endpoint
00302      * alone, or nothing at all.
00303      *
00304      *  If you provide a remote endpoint and password they are used to drive
00305      * the communications and execute your commands.
00306      *
00307      *  If no password is provided, then a default of "xxyzzy" (the canonical
00308      * computer magic word) is used. Unless this is your password (not 
00309      * recommended), this means that the default password is useless unless
00310      * you are working in a trusted environment where it is not needed and
00311      * ignored. That may be the case if you enable trusting mechanisms with
00312      * .shosts/.rhosts or passphraseless RSA/DSA authentication. Not that
00313      * we endorse them either, but in these cases any password provided will
00314      * be ignored and it doesn't make sense to provide a real one: xxyzzy
00315      * can do as well as any other.
00316      *
00317      *  If no password and no remote end is provided, then "localhost" is
00318      * used as the remote end, assuming no password is required (as described
00319      * above). This is only useful if localhost is trusted, and you have reasons
00320      * to use SSH internally... Some people does.
00321      *
00322      *  Regarding the remote end specification, it can be any valid single-string
00323      * SSH remote end description: the basic format is
00324      *
00325      *  [username@]remote.host[:port]
00326      *
00327      *  - "username" is the remote user name to log in as. It is optional. If provided, 
00328      *    it must be separated from the remote host by an "@" sign. If it is not 
00329      *    provided, the remote username is assumed to be the same as the current local
00330      *    one.
00331      *
00332      *  - "remote.host" is a valid host specification, either a numeric IP address
00333      *    or a valid host name (which may require a full name or not depending on
00334      *    your settings).
00335      *
00336      *  - "port" is the remote port where SSH is listening and which we want to
00337      *    connect to. It is optional, and if provided, must follow the remote host
00338      *    specification separated from it by a colon ":". If not provided, the
00339      *    default port (22) is used.
00340      *
00341      *  Examples of remote host specifications are "user@host.example.net:22",
00342      * "someone@host:22", "host.example.net:22", "host:22", 
00343      * "somebody@host.example.net", "user@host", "host.example.net", "host".
00344      *
00345      * Here is an example of how to use this constructor:
00346      * <code>
00347      *  require_once 'ssh.php';
00348      *
00349      *  $remote = "jruser@example.com";
00350      *  $password = "PASSWORD";
00351      *
00352      *  $rmt = new SExec($remote, $password);
00353      *   if (! $rmt)
00354      *          echo "Couldn't connect to $remote\n";
00355      * </code>
00356      *
00357      *  @param string   The remote end to run the command, in
00358      *                      the form 'user@host:port' (you may
00359      *                      omit the 'user@' or ':port' parts
00360      *                      if the default values [i.e. same user
00361      *                      or standard port] are OK).
00362      *
00363      *  @param string The remote password. Note that if direct
00364      *                      RSA/DSA/.shosts/.rhosts login is enabled
00365      *                      then the password will be ignored as
00366      *                      SSH should not run the ASKPASS command).
00367      *
00368      *  @return SExec|false a new connection object with the remote end or
00369      *                      FALSE if the connection could not be established.
00370      *
00371      *  @access public
00372      *  @since Method available since Release 1.0
00373      */
00374     function SExec($remote, $password)
00375     {
00376         global $debug_sexec;
00377         
00378         if ($debug_sexec) echo "\nSExec::SExec($remote, $password)\n";
00379         if ($debug_sexec) echo "--> Creating a new SExec\n";
00380         $this->remote = $remote;
00381         $this->password = "$password";
00382         umask(0077);
00383         /* DESIGN
00384          * In order to increase efficiency, we will create a master channel
00385          * on class instantiation. The master channel should be closed by a
00386          * corresponding class destructor!
00387          *
00388          * Creating a master channel has the advantage that subsequent SSH
00389          * connections will use it and avoid repeating the slow authentication
00390          * process: in other words, they will go much, much faster.
00391          */
00392          
00393         // first we must generate a unique UNIX socket address or we'll fail
00394         // We use a tricky trick: generate two random numbers and use them;
00395         // this is tricky since there might be a problem, but with very low
00396         // probability. BUT IT MAY STILL FAIL: there's a race condition between
00397         // the end of the while and the subsequent if.
00398         do {
00399             mt_srand((double)microtime()*1000000 ) .
00400             $this->workdir = "/tmp/phpSsh-" . mt_rand() .".". mt_rand();
00401             if ($debug_sexec) echo "\nSExec: trying $this->workdir/ ...";
00402             // CAUTION: this is potentially an endless loop (albeit with very
00403             // low probability) if every possible file did exist.
00404         }
00405         while (file_exists($this->workdir));
00406         if (mkdir($this->workdir) == FALSE) {
00407             // we can't continue. How can we cancel this?
00408             // try these and then check what happens
00409             unset($this);
00410             // $this = NULL;    No longer needed as we now return FALSE
00411             return FALSE;
00412         }
00413         else 
00414             if ($debug_sexec) echo " OK\n";
00415         // Now we have a place to put the socket... Mmm...
00416         // Come to think of it, we have a place to put ANY temporary
00417         // for the class... 
00418         // XXX Maybe we can change everywhere else to use this?
00419         $this->mplex_socket = $this->workdir."/mplex_socket";
00420         
00421         // Finally we can call SSH -M
00422         // Create SSH_ASKPASS script to provide the password
00423         $tmpfname = tempnam($this->workdir, 'SExec-');
00424         chmod($tmpfname, 0700);
00425         putenv("DISPLAY=none:0.");
00426         putenv("SSH_ASKPASS=$tmpfname");
00427         $fp = fopen($tmpfname, "w");
00428         fputs($fp, "#!/bin/sh\necho $this->password\n");
00429         if (!$debug_sexec) 
00430             fputs($fp, "/bin/touch $tmpfname.called\n");
00431         else
00432             fputs($fp, "/bin/rm -f $tmpfname\n");
00433         fclose($fp);
00434         
00435         // OK, we are ready. Now let's open a master shell
00436         $child_stdout = tempnam($this->workdir, "open_sh-O-");
00437         $child_stderr = tempnam($this->workdir, "open_sh-E-");
00438         $descriptorspec = array(
00439             0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
00440             1 => array("file", $child_stdout, "a"),  // connect child's stdout to the write end of a pipe
00441             2 => array("file", $child_stderr, "a")   // stderr is a pipe to read from
00442         );
00443 
00444         if ($debug_sexec) echo "$this->ssh -x -t -t ".
00445                      "-M -S $this->mplex_socket " .
00446                      "$this->remote\n";
00447         $this->master = proc_open("$this->ssh -x -t -t ".
00448                      "-M -S $this->mplex_socket " .
00449                      "$this->remote",
00450                      $descriptorspec,
00451                      $pipes);
00452         if ((! is_resource($this->master)) || ($this->master == FALSE)) {
00453                 putenv("SSH_ASKPASS=dummy");
00454                 unset($this);
00455                 $this = NULL;
00456                 return FALSE;
00457         }
00458         // we do not need to worry about the output log files, just the
00459         // input pipe for logout
00460         $this->master_input = $pipes[0];
00461         
00462         // Before going ahead, we need to ensure the control shell 
00463         // has started: wait for the socket to become available
00464         // note: there should be a timeout here to avoid a possibly
00465         // infinite loop XXX JR XXX
00466         do {
00467             if ($debug_sexec) echo "waiting 0.1 sec\n";
00468             usleep(100000);     // wait 0.1 seconds
00469         } while (! file_exists($this->mplex_socket));
00470 
00471         // and now we must register a destructor for the class
00472         // that will close the connection.
00473         //register_shutdown_function($this->destruct());
00474         
00475         return $this;
00476     }
00477     
00478     /** Class destructor
00479      *
00480      *  Destroy all working processes and data streams and structures
00481      * used by an instance of this class.
00482      *
00483      *  This method will send a termination message to the other end
00484      * of the master channel, close the control stream of the master
00485      * channel and terminate its controlling process, finally unsetting
00486      * the object and setting the object handle to NULL.
00487      *
00488      *  If a global $debug_sexec is not set to TRUE, then it will also remove
00489      * all communication traces of this object: i.e. all log files for
00490      * interactive and master sessions, communications socket, etc...
00491      *
00492      *  If global $debug_sexec is set to TRUE, then a copy of all log files
00493      * created during the lifetime of the object will be left on a
00494      * temporary directory for your perusal and reference.
00495      *
00496      *  @return integer exit status of the master channel control process.
00497      *
00498      *  @access public
00499      *  @since Method available since Release 1.0
00500      */
00501     function destruct()
00502     {
00503         global $debug_sexec;
00504         
00505         if ($debug_sexec) echo "\nSExec::destruct()\n";
00506         if ($debug_sexec) echo "--> Destroying SExec master\n";
00507         if ($debug_sexec) print_r($this);
00508         if ($debug_sexec) echo "sending logout\n";
00509         // log out master process
00510         fputs($this->master_input, "\n\nlogout\n\n");
00511         // close master stdin
00512         fclose($this->master_input);
00513         // close master process
00514         $ret = proc_close($this->master);
00515         // remove temporaries
00516         if (! $debug_sexec) system("/bin/rm -rf $this->workdir");
00517         // utterly destroy this instance
00518         unset($this);
00519         $this = NULL;
00520         return $ret;
00521     }    
00522     
00523     //}}}
00524     
00525     //{{{ methods
00526     /**
00527      *  Copy a file or directory from one source to a destination
00528      *
00529      *  This function copies source to dest, where one of them is a
00530      * local filespec and the other a remote filespec of the form
00531      * [user@]host:path
00532      *
00533      *  If the original source is a directory, it will be copied
00534      * recursively to destination (hence easing file transfers).
00535      *
00536      *  The function returns TRUE on success or FALSE on failure.
00537      *
00538      * <b>EFFICIENCY NOTICE:</b>
00539      *
00540      *  The copy routines use 'scp' to do their actual work. Since
00541      * scp seems to be unable to hitchhike on the master channel,
00542      * we must do authentication for each copy operation (subroutine 
00543      * call). These routines are hence a lot more time-expensive 
00544      * than all the other ones.
00545      *
00546      *  You may want to consider whether you can group several
00547      * copies into one single call to reduce authentication 
00548      * overheads.
00549      *
00550      *  @note DEPRECATED (inconsistent with the class)
00551      *
00552      *  @see scp(1)
00553      *
00554      *  @param string   The origin path, of the form
00555      *                  [user@][host][:port]path
00556      *                  You may omit the optional sections if
00557      *                  the default values (local username, local
00558      *                  host, standard SSH port) are OK
00559      *
00560      *  @param string   The destination path, of the form
00561      *                  [user@][host][:port:]path
00562      *                  You may omit the optional sections if
00563      *                  the default values (local username, local
00564      *                  host, standard SSH port) are OK
00565      *
00566      *  @param string   The password to use to connect to the remote
00567      *                  end of the copy (be it the origin or the
00568      *                  destination, it's all the same). If connection
00569      *                  is automatic by some means (.shosts or RSA/DSA
00570      *                  authentication) then it should be ignored and
00571      *                  any password should do.
00572      *
00573      *  @return bool TRUE if all went well, or FALSE on failure.
00574      *
00575      *  @access public
00576      *  @since Method available since Release 1.0
00577      *  @deprecated Method deprecated as of Release 2.1
00578      */
00579     function ssh_copy($origin, $destination, $password)
00580     {
00581         global $debug_sexec;
00582 
00583         if ($debug_sexec) echo "\nSExec::ssh_copy($origin, $destination, $password)\n";
00584         umask(0077);
00585         $tmpfname = tempnam($this->workdir, "copy-");
00586         chmod($tmpfname, 0700);
00587         putenv("DISPLAY=none:0.");
00588         putenv("SSH_ASKPASS=$tmpfname");
00589         $fp = fopen($tmpfname, "w");
00590         fputs($fp, "#!/bin/sh\necho $password\n");
00591         if (! $debug_sexec)  
00592             fputs($fp, "/bin/touch $tmpfname.called\n");
00593         else
00594             fputs($fp, "/bin/rm $tmpfname\n");
00595         fclose($fp);
00596         $out="";
00597         exec("$this->scp -pqrC $origin $destination", $out, $status);
00598         if ($status == 0)
00599             return TRUE;
00600         else
00601             return FALSE;
00602     }
00603 
00604 
00605     /**
00606      *  Copy a file or directory from a local source to a remote destination
00607      *
00608      *  This function copies source to dest, where first of them is a
00609      * local filespec and then comes a remote filespec as a normal
00610      * system path.
00611      *
00612      *  Both, local and remote paths may be absolute or relative.
00613      *
00614      *  If the original source is a directory, it will be copied
00615      * recursively to destination (hence easing file transfers).
00616      *
00617      *  The function returns TRUE on success or FALSE on failure.
00618      *
00619      *  @param string    The origin local path, either absolute or
00620      *                  relative to the current working directory. 
00621      *                  If it denotes a directory, the copy will 
00622      *                  be recursive.
00623      *
00624      *  @param string   The destination path, either
00625      *                  absolute or relative to the login home.
00626      *
00627      *  @param array   An optional array of strings to be appended the 
00628      *                  copy operation's output for debugging/diagnostics.
00629      *
00630      *  @return bool TRUE if all went well, or FALSE on failure.
00631      *
00632      *  @access public
00633      *  @since Method available since Release 2.1
00634      */
00635     function ssh_copy_to($localpath, $remotepath, &$out)
00636     {
00637         global $debug_sexec;
00638         $debug_sexec = TRUE;
00639         if ($debug_sexec) echo "\nSExec::ssh_copy_to($localpath, $remotepath)\n";
00640 
00641 /*      This would be great if SCP could hijack the shared connection (sic)
00642         umask(0077);
00643         $tmpfname = tempnam($this->workdir, "copy-to-");
00644         chmod($tmpfname, 0700);
00645         putenv("DISPLAY=none:0.");
00646         putenv("SSH_ASKPASS=$tmpfname");
00647         $fp = fopen($tmpfname, "w");
00648         fputs($fp, "#!/bin/sh\necho $this->password\n");
00649         if (! $debug_sexec)  
00650             fputs($fp, "/bin/touch $tmpfname.called\n");
00651         else
00652             fputs($fp, "/bin/rm $tmpfname\n");
00653         fclose($fp);
00654         if ($debug_sexec) echo "$this->scp -pqrC $localpath $this->remote:$remotepath\n";
00655         $out = "";
00656         exec("$this->scp -pqrC $localpath $this->remote:$remotepath", $out, $status);
00657         if ($status == 0)
00658             return TRUE;
00659         else {
00660             if ($debug_sexec) echo $out . "\n";
00661             return FALSE;
00662         }
00663 */
00664         // NOTE THAT WE NEED GNU TAR !!!
00665         $retval = $this->ssh_exec("test -d $remotepath 2>&1", $out);
00666         if ($retval == 0) {
00667             // destination is a directory, copy $local inside it
00668             if ($debug_sexec) echo "--> Remote is a directory\n";
00669             $fn = basename($localpath);
00670             $dn = dirname($localpath);
00671             if ($debug_sexec) echo "--> Executing\n" .
00672                 "/bin/tar -C $dn -cf - $fn | " .
00673                 "ssh -x -T -C -S  $this->mplex_socket $this->remote " .
00674                 "\"/bin/tar -C $remotepath -xf -\"\n";
00675             exec("(/bin/tar -C $dn -cf - $fn | " .
00676                  "ssh -x -T -C -S  $this->mplex_socket $this->remote " .
00677                  "\"/bin/tar -C $remotepath -xf -\")2>&1", 
00678                 $out, $retval);
00679         } else {
00680             // destination is not a directory, copy _to_ it
00681             if ($debug_sexec) 
00682                 echo "--> remote is not a directory or does not exist\n";
00683             if ((file_exists("$localpath/.")) && (is_dir("$localpath/."))) {
00684                 // if local is a dir, try to create it remotely with new name
00685                 $retval = $this->ssh_exec("/bin/mkdir -p $remotepath ", $out);
00686                 if ($retval != 0) {
00687                     // we can't create it, either it already exists as a
00688                     // regular file or we don't have permissions, anyhow,
00689                     // we can't do the copy
00690                     if ($debug_sexec) print_r($out);
00691                     return FALSE;
00692                 }
00693                 // now cd lo local and copy over to remote
00694                 if ($debug_sexec) echo "--> Executing \n" .
00695                         "    /bin/tar -C $localpath -cf - . | \n" .
00696                         "    $this->ssh -x -T -C -S $this->mplex_socket $this->remote \n" .
00697                         "    /bin/tar -C $remotepath -xf -\n";
00698                 exec("(/bin/tar -C $localpath -cf - . | " .
00699                     "$this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
00700                     "/bin/tar -C $remotepath -xf -)2>&1", 
00701                     $out, $retval);
00702             }
00703             else {
00704                 // non-dir: file, block-special, char-special, pipe, socket...
00705                 if ($debug_sexec) echo "--> Executing \n" .
00706                     "    cat $localpath | \n" .
00707                     "    $this->ssh -x -T -C -S $this->mplex_socket $this->remote \n" .
00708                     "    \"cat > $remotepath\"\n";
00709                 exec("(cat $localpath | " .
00710                     "$this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
00711                     "\"cat > $remotepath\") 2>&1", $out, $retval);
00712             }
00713         }
00714         if ($retval != 0) {
00715             if ($debug_sexec) print_r($out);
00716             return FALSE;
00717         }
00718         else
00719             return TRUE;
00720     }
00721     
00722     /**
00723      *  Copy a file or directory from a remote source to a local destination
00724      *
00725      *  This function copies source to dest, where first of them is a
00726      * remote filespec and then comes a local filespec, both specified 
00727      * as normal system paths.
00728      *
00729      *  Both, local and remote paths may be absolute or relative.
00730      *
00731      *  If the original source is a directory, it will be copied
00732      * recursively to destination (hence easing file transfers).
00733      *
00734      *  The function returns TRUE on success or FALSE on failure.
00735      *
00736      *  @param string   The origin remote path, either absolute or
00737      *                  relative to the login home. If it denotes a 
00738      *                  directory, the copy will be recursive.
00739      *
00740      *  @param string    The local destination path, either
00741      *                  absolute or relative to the current working
00742      *                  directory.
00743      *
00744      *  @param array   An optional array of strings to be appended the 
00745      *                  copy operation's output for debugging/diagnostics.
00746      *
00747      *  @return bool TRUE if all went well, or FALSE on failure.
00748      *
00749      *  @access public
00750      *  @since Method available since Release 1.0
00751      */
00752     function ssh_copy_from($remotepath, $localpath, &$out)
00753     {
00754         global $debug_sexec;
00755 
00756         if ($debug_sexec) echo "SExec::ssh_copy_from($remotepath, $localpath)\n";
00757 
00758 /*      This would be great if SCP could hijack the shared connection (sic)
00759         umask(0077);
00760         $tmpfname = tempnam($this->workdir, "copy-from-");
00761         chmod($tmpfname, 0700);
00762         putenv("DISPLAY=none:0.");
00763         putenv("SSH_ASKPASS=$tmpfname");
00764         $fp = fopen($tmpfname, "w");
00765         fputs($fp, "#!/bin/sh\necho $this->password\n");
00766         if (! $debug_sexec)  
00767             fputs($fp, "/bin/touch $tmpfname.called\n");
00768         else
00769             fputs($fp, "/bin/rm $tmpfname\n");
00770         fclose($fp);
00771         if ($debug_sexec) echo "$this->scp -pqrC $this->remote:$remotepath $localpath\n";
00772         $out = "";
00773         exec("$this->scp -pqrC $this->remote:$remotepath $localpath", $out, $status);
00774         if ($status == 0)
00775             return TRUE;
00776         else {
00777             if ($debug_sexec) echo $out . "\n";
00778             return FALSE;
00779         }
00780 */      
00781         if ((file_exists("$localpath/.")) && (is_dir("$localpath/."))) {
00782             // Local is a directory. Copy remote into it.
00783             if ($debug_sexec) echo "--> $localpath/. is a dir\n";
00784             if ($debug_sexec) echo "--> Executing\n" .
00785             "$this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
00786                     "\"/bin/tar -C " .dirname($remotepath). 
00787                         " -cf - ". basename($remotepath) ."\" | ".
00788                         "/bin/tar -C $localpath -xf -\n";
00789             exec("($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
00790                     "\"/usr/local/bin/tar -C " .dirname($remotepath). 
00791                         " -cf - ". basename($remotepath) ."\" | ".
00792                         "/bin/tar -C $localpath -xf -) 2>&1",
00793                  $out, $res);
00794         }
00795         else {
00796             // either the local side does not exist or is not a directory
00797             // if remote is a directory
00798             //      make local equivalent and copy contents (make will
00799             //      fail if local exists as a non-dir)
00800             if ($debug_sexec) echo "--> $localpath is NOT a dir\n";
00801             $res = $this->ssh_exec("test -d $remotepath 2>&1", $out);
00802             if ($res == 0) {
00803                 exec("/bin/mkdir -p $localpath 2>&1", $out, $res);
00804                 if ($res != 0) {
00805                     // can't create the dir, either it is a regular file
00806                     // or we don't have privileges
00807                     if ($debug_sexec) print_r($out);
00808                     return FALSE;
00809                 }
00810                 // copy in the remote contents
00811                 if ($debug_sexec) echo "-->Executing\n" .
00812                     "($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
00813                     "\"/bin/tar -C $remotepath -cf - .\" | " .
00814                     "/bin/tar -C $localpath -xf -)2>&1\n";
00815                 exec("($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
00816                     "\"/bin/tar -C $remotepath -cf - .\" | " .
00817                     "/bin/tar -C $localpath -xf -)2>&1",
00818                     $out, $res);
00819                 
00820             } else {
00821                 // remote is a non-dir: cat over local
00822                 if ($debug_sexec) echo "-->Executing\n" .
00823                     "($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
00824                     "\"cat $remotepath\" | ".
00825                     " cat > $localpath) 2>&1\n";
00826                 exec("($this->ssh -x -T -C -S $this->mplex_socket $this->remote " .
00827                     "\"cat $remotepath\" | ".
00828                     " cat > $localpath) 2>&1", $out, $res);
00829             }
00830         }
00831         if ($res == 0) 
00832             return TRUE;
00833         else {
00834             if ($debug_sexec) print_r($out);
00835             return FALSE;
00836         }
00837     }
00838 
00839     /**
00840      *  Execute a single command remotely
00841      *
00842      *  Execute a single command remotely using ssh and 
00843      * display its output, optionally returning its exit 
00844      * status (like passthru)
00845      *
00846      *  This function is intended to be used as a one-time
00847      * all-at-once non-interactive execution mechanism which
00848      * will run the command remotely and display its output.
00849      *
00850      *  If you try to issue an interactive command using this
00851      * function, all you will get is unneccessary trouble. So
00852      * don't!
00853      *
00854      *  This might be done as well using a pipe on /tmp and
00855      * making the command 'cat' the pipe: when ssh runs, it
00856      * runs the command 'cat' on the pipe and hangs on read.
00857      *  Then we just need a thread to open the pipe, put the
00858      * password and close the pipe.
00859      *
00860      *  This other way the password is never wirtten down.
00861      * But, OTOH, the file life is so ephemeral that most
00862      * of the time it will only exist in the internal system
00863      * cache, so this approach is not that bad either.
00864      *
00865      *  @see passthru()
00866      *
00867      *  @param string command  The command to execute on the remote end
00868      *                      NOTE: if you want to use redirection, the
00869      *                      entire remote command line should be 
00870      *                      enclosed in additional quotes!
00871      *  @param integer status   Optional, this will hold the termination
00872      *                      status of SSH after invocation, which
00873      *                      should be the exit status of the remote
00874      *                      command or 255 if an error occurred
00875      *  @return void
00876      *
00877      *  @access public
00878      *  @since Method available since Release 1.0
00879      */
00880     function ssh_passthru($command, &$status)
00881     {
00882         global $debug_sexec;
00883 
00884         if ($debug_sexec) echo "status = $status\n";
00885         // go
00886         if (isset($status)) {
00887             if ($debug_sexec) echo "st: $this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"\n";
00888             passthru("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $status);
00889         }
00890         else {
00891             if ($debug_sexec) echo "~st: $this->ssh -x -t -t  -S $this->mplex_socket $this->remote \"$command\"\n";
00892             passthru("$this->ssh -x -t -t  -S $this->mplex_socket $this->remote \"$command\"");
00893         }
00894     }
00895     
00896     
00897     /**
00898      *  Execute a remote command using SSH
00899      *
00900      *  This function sort of mimics rexec(3) using SSH as the transport
00901      * protocol.
00902      *
00903      *  The function returns the exit status of the remote command, and
00904      * appends the remote job output to an optional argument.
00905      *
00906      *  This function is intended to be used as a one-time
00907      * all-at-once non-interactive execution mechanism which
00908      * will run the command remotely and return its output.
00909      *
00910      *  If you try to issue an interactive command using this
00911      * function, all you will get is unneccessary trouble. So
00912      * don't!
00913      *
00914      *  @param string command  The command to execute on the remote end
00915      *                      NOTE: if you want to use redirection, the
00916      *                      entire remote command line should be 
00917      *                      enclosed in additional quotes!
00918      *  @param  array If the output argument is present, then the specified 
00919      *                      array will be filled with every line of output 
00920      *                      from the command. Line endings, such as \n, are 
00921      *                      not included in this array. Note that if the array 
00922      *                      already contains some elements, exec() will append 
00923      *                      to the end of the array. If you do not want the 
00924      *                      function to append elements, call unset() on the 
00925      *                      array before passing it to exec().
00926      *  @return integer status  will hold the termination
00927      *                      status of SSH after invocation, which
00928      *                      should be the exit status of the remote
00929      *                      command or 255 if an error occurred
00930      *
00931      *  @access public
00932      *  @since Method available since Release 1.0
00933      */
00934     function ssh_exec($command, &$out)
00935     {
00936         global $debug_sexec;
00937 
00938         if ($debug_sexec) echo "SExec::ssh_exec($command, $out)\n";
00939         umask(0077);
00940         $tmpfname = tempnam($this->workdir, 'exec');
00941         chmod($tmpfname, 0700);
00942         if ($debug_sexec) echo $tmpfname . "\n";
00943 
00944         exec("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $out, $retval);
00945         return $retval;
00946 
00947     }
00948     
00949     /**
00950      *  Open an SSH connection to a remote site with a shell to run 
00951      * interactive commands
00952      *
00953      *  Connects to a remote host and opens an interactive shell session
00954      * with NO controlling terminal.
00955      *
00956      *  This routine creates communication streams with the remote shell,
00957      * and stores all output (standard and error) of the connection into
00958      * two separate local log files (one for stdout and one for stderr).
00959      *
00960      *  Returns a process_control array which contains the process resource
00961      * ID and an the standard file descriptors which the caller may use to
00962      * interact with the remote shell.
00963      *
00964      * The process control array contains:
00965      *
00966      *  'process' -- the process resource for the newly created connection
00967      *
00968      *  'std_in' -- handle to the standard input of the new connection
00969      *
00970      *  'std_out' -- handle to standard output of the new connection
00971      *
00972      *  'std_err' -- handle to standard error of the new connection
00973      *
00974      *  'stdout_file' -- actual filename of the local log file for the
00975      *          new connection standard output
00976      *
00977      *  'stderr_file' -- actual filename of the local log file for the
00978      *          new connection standard error
00979      *
00980      *  @return mixed|false a process control associative array or FALSE
00981      *          on failure.
00982      *
00983      *  @access public
00984      *  @since Method available since Release 1.0
00985      */
00986     function ssh_open_shell()
00987     {   
00988         global $debug_sexec;
00989         $debug_sexec = TRUE;
00990         // Open a child process with the 'proc_open' function. 
00991         //
00992         // Some tricks: we must open the connection using '-x' to disable
00993         // X11 forwarding, and use '-t -t' to avoid SSH generating an error
00994         // because we are not connected to any terminal.
00995         //
00996         // NOTE: if the web server is trusted remotely (i.e. it's SSH public 
00997         // key is accepted in ~user@host:.ssh/authorized_keys) then any 
00998         // password will do.
00999 
01000         // Prepare I/O
01001         umask(0077);
01002         if ($debug_sexec) {
01003             $child_stdout = tempnam($this->workdir, "open_sh-".getmypid()."-O-");
01004             $child_stderr = tempnam($this->workdir, "open_sh-".getmypid()."-E-");
01005         } else {
01006             $child_stdout = tempnam($this->workdir, "open_sh-");
01007             $child_stderr = tempnam($this->workdir, "open_sh-");
01008         }
01009         $descriptorspec = array(
01010             0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
01011             1 => array("file", $child_stdout, "a"),  // connect child's stdout to the write end of a pipe
01012             2 => array("file", $child_stderr, "a")   // stderr is a pipe to read from
01013         );
01014         if ($debug_sexec) echo "$this->ssh -x -t -t -S $this->mplex_socket $this->remote<br />\n";
01015         $process = proc_open("$this->ssh -x -t -t -S $this->mplex_socket $this->remote", 
01016                          $descriptorspec,
01017                          $pipes);
01018         
01019         // check status
01020         if ((!is_resource($process)) || ($process == FALSE)) 
01021         {
01022             letal("SSH::connect", "cannot connect to the remote host");
01023             return FALSE;
01024         }
01025         if ($debug_sexec) echo "proc_open done<br />\n";
01026 
01027         // $pipes now looks like this:
01028         //   0 => writeable handle connected to child stdin
01029         
01030         // Open child's stdin and stdout
01031         $pipes[1] = fopen($child_stdout, "r");
01032         $pipes[2] = fopen($child_stderr, "r");
01033         
01034         // Should we leave this to the user?
01035         // set to non-blocking and avoid having to call fflush
01036         //stream_set_blocking($pipes[0], FALSE);
01037         //stream_set_blocking($pipes[1], FALSE);
01038         //stream_set_blocking($pipes[2], FALSE);
01039         stream_set_write_buffer($pipes[0], 0);
01040         stream_set_write_buffer($pipes[1], 0);
01041         stream_set_write_buffer($pipes[2], 0);
01042 
01043         // We now have a connection to the remote SSH
01044         // Server which we may use to send commands/receive output
01045         $p = array('process' => $process
01046                     ,'std_in' => $pipes[0]
01047                     ,'std_out' => $pipes[1]
01048                     ,'std_err' => $pipes[2] 
01049                     ,'stdout_file' => $child_stdout
01050                     ,'stderr_file' => $child_stderr
01051                    );
01052         if ($debug_sexec)  {
01053             echo "process descriptor array is \n";
01054             print_r($p);
01055         }
01056         return $p;
01057     }
01058     
01059     /**
01060      *  Open an SSH connection to run an interactive command on a remote
01061      * site
01062      *
01063      *  Connects to a remote host and runs an interactive command
01064      * with NO controlling terminal.
01065      *
01066      *  This routine creates communication streams with the remote shell,
01067      * and stores all output (standard and error) of the connection into
01068      * two separate local log files (one for stdout and one for stderr).
01069      *
01070      *  Returns a process_control array which contains the process resource
01071      * ID and an the standard file descriptors which the caller may use to
01072      * interact with the remote shell.
01073      *
01074      * The process control array contains:
01075      *
01076      *  'process' -- the process resource for the newly created connection
01077      *
01078      *  'std_in' -- handle to the standard input of the new connection
01079      *
01080      *  'std_out' -- handle to standard output of the new connection
01081      *
01082      *  'std_err' -- handle to standard error of the new connection
01083      *
01084      *  'stdout_file' -- actual filename of the local log file for the
01085      *          new connection standard output
01086      *
01087      *  'stderr_file' -- actual filename of the local log file for the
01088      *          new connection standard error
01089      *
01090      *  @param  string command to be executed interactively on the remote end
01091      *
01092      *  @return mixed|false a process control associative array or FALSE
01093      *          on failure.
01094      *
01095      *  @access public
01096      *  @since Method available since Release 1.0
01097      */
01098     function ssh_open_command($command)
01099     {   
01100         global $debug_sexec;
01101         $debug_sexec = TRUE;
01102         // Open a child process with the 'proc_open' function. 
01103         //
01104         // Some tricks: we must open the connection using '-x' to disable
01105         // X11 forwarding, and use '-t -t' to avoid SSH generating an error
01106         // because we are not connected to any terminal.
01107         //
01108         // NOTE: if the web server is trusted remotely (i.e. it's SSH public 
01109         // key is accepted in ~user@host:.ssh/authorized_keys) then any 
01110         // password will do.
01111 
01112         // Prepare I/O
01113         umask(0077);
01114         if ($debug_sexec) {
01115             $child_stdout = tempnam($this->workdir, "open_cmd-".getmypid()."-1-");
01116             $child_stderr = tempnam($this->workdir, "open_cmd-".getmypid()."-2-");
01117         } else {
01118             $child_stdout = tempnam($this->workdir, "open_cmd-");
01119             $child_stderr = tempnam($this->workdir, "open_cmd-");
01120         }
01121         $descriptorspec = array(
01122             0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
01123             1 => array("file", $child_stdout, "a"),
01124             2 => array("file", $child_stderr, "a")
01125         );
01126 
01127         if ($debug_sexec) echo "$this->ssh -x -t -t -S $this->mplex_socket $this->remote $command<br />\n";
01128         $process = proc_open("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", 
01129                          $descriptorspec,
01130                          $pipes);
01131         
01132         // check status
01133         if ((!is_resource($process)) || ($process == FALSE)) 
01134         {
01135             letal("SSH::connect", "cannot connect to the remote host");
01136             return FALSE;
01137         }
01138         if ($debug_sexec) echo "proc_open done<br />\n";
01139 
01140         // $pipes now looks like this:
01141         //   0 => writeable handle connected to child stdin
01142         
01143         // Open child's stdin and stdout
01144         $pipes[1] = fopen($child_stdout, "r");
01145         $pipes[2] = fopen($child_stderr, "r");
01146         
01147         // Should we leave this to the user?
01148         // set to non-blocking and avoid having to call fflush
01149         stream_set_blocking($pipes[0], FALSE);
01150         #stream_set_blocking($pipes[1], FALSE);
01151         #stream_set_blocking($pipes[2], FALSE);
01152         stream_set_write_buffer($pipes[0], 0);
01153         stream_set_write_buffer($pipes[1], 0);
01154         stream_set_write_buffer($pipes[2], 0);
01155 
01156         // We now have a connection to the remote SSH
01157         // Server which we may use to send commands/receive output
01158         $p = array('process' => $process
01159                     ,'std_in' => $pipes[0]
01160                     ,'std_out' => $pipes[1]
01161                     ,'std_err' => $pipes[2] 
01162                     ,'stdout_file' => $child_stdout
01163                     ,'stderr_file' => $child_stderr
01164                    );
01165         if ($debug_sexec)  {
01166             echo "process descriptor array is \n";
01167             print_r($p);
01168         }
01169         return $p;
01170     }
01171     
01172     /**
01173      * Get output until we reach a given regular expression
01174      *
01175      *  @note EXPERIMENTAL, requires more thought and experience.
01176      */
01177     function ssh_out_expect($p, $expr="^# ")
01178     {
01179         do {
01180                 flush();
01181                 fseek($p["std_out"], $last);
01182                 $line = fgets($p["std_out"], 1024);
01183                  #echo ">> ".$line;
01184                 $last = ftell($p["std_out"]);
01185         } while ((! feof($p["std_out"]) ) || (! ereg($expr, $line)));
01186     }
01187 
01188     /**
01189      * Close an SSH interactive session
01190      *
01191      *  This method terminates a previously open interactive remote 
01192      * session. It will send a termination notification to the
01193      * remote end, close the connection with control and communication
01194      * streams, and terminate the local control process.
01195      *
01196      *  Copies of the log files that contain the output and error
01197      * of the communication are left out for later reference and 
01198      * local peruse. If you don't need them any longer, you may
01199      * delete them or just leave them around until the class destructor
01200      * is called (which will remove all session traces),
01201      *
01202      *  @param mixed p an associative array with the description of the interactive
01203      *          session control process, obtained by a previous call to one
01204      *          of the interactive session creation methods ssh_open_shell()
01205      *          or ssh_open_command().
01206      *
01207      *  @return integer the exit status of the remote interactive session.
01208      *
01209      *  @access public
01210      *  @since Method available since Release 1.0
01211      */
01212     function ssh_close($p)
01213     {
01214         global $debug_sexec;
01215         
01216             fwrite($p['std_in'], "\n");
01217             fwrite($p['std_in'], "logout\n");
01218             fflush($p['std_in']);
01219             fclose($p['std_in']); 
01220             fclose($p['std_out']); 
01221             fclose($p['std_err']);
01222             if ($debug_sexec) echo "pipes/files closed\n";
01223             // XXX we should delete the log files here...
01224             return proc_close($p['process']);
01225     }
01226     
01227 #    if ($php_version >= 5)
01228 #    {
01229 #       /**
01230 #        * send a signal to a running ssh_open_* process
01231 #        */
01232 #       function ssh_signal($p, $signal)
01233 #       {
01234 #           return proc_terminate($p['process'], $signal);
01235 #       }
01236 #       /**
01237 #        * get info about a running ssh_open_* process
01238 #        */
01239 #       function ssh_get_status($p)
01240 #       {
01241 #           return proc_get_status($p['process']);
01242 #       }
01243 #    }
01244     
01245     /**
01246      *  Execute a remote command and keep an unidirectional stream
01247      * contact with it.
01248      *
01249      *  This routine mimics 'popen()' but uses ssh to connect to
01250      * a remote host and run the requested command: in other words,
01251      * it opens a pipe to a remotely executed command. This pipe is
01252      * unidirectional, with the communications direction controlled
01253      * by a method parameter.
01254      *
01255      *  @see popen() for more details.
01256      *
01257      *  @param string command is the command to execute on the remote end
01258      *
01259      *  @param string mode specifies the communications direction for the 
01260      *          pipe: if set to "r" (read), then we will be able to
01261      *          collect command output only; if set to "w" (write)
01262      *          then we may only send input to the remote command.
01263      *
01264      *  @return resource a handle to the unidirectional communication stream,
01265      *          similar to that returned by fopen(), or FALSE on
01266      *          failure. This handle must be closed with ssh_pclose().
01267      *
01268      *  @access public
01269      *  @since Method available since Release 1.0
01270      */
01271     function ssh_popen($command, $mode)
01272     {
01273         global $debug_sexec;
01274 
01275         // go
01276         return popen("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $mode);
01277     }
01278     
01279     /**
01280      * Close a piped remote execution command control pipe.
01281      *
01282      *  This routine accepts as input the handle for the control stream
01283      * of a remote command and closes it, terminating the command as well.
01284      * The handle must be valid and obtained through a call to ssh_popen().
01285      *
01286      *  @param resource f is the file handle associated with the pipe control stream
01287      *
01288      *  @return integer the termination status of the command that was run.
01289      *
01290      *  @access public
01291      *  @since Method available since Release 1.0
01292      */
01293     function ssh_pclose($f)
01294     {
01295         return pclose($f);
01296     }
01297 
01298     //}}}
01299 }
01300 
01301 /*
01302  * Local variables:
01303  * tab-width: 4
01304  * c-basic-offset: 4
01305  * c-hanging-comment-ender-p: nil
01306  * End:
01307  */
01308 
01309 ?>

Generated on Thu Jun 16 11:03:56 2005 for SExec by doxygen 1.3.6