Main Page | Namespace List | File List | File Members

ssh-pipes.php

Go to the documentation of this file.
00001 <?php
00002 /**
00003  * Implementation of SExec using pipes.
00004  *
00005  * @package SExec
00006  * @author José R. Valverde <jrvalverde@acm.org>
00007  * @version 1.0
00008  * @copyright José R. Valverde <jrvalverde@es.embnet.org>
00009  *
00010  * This library is free software; you can redistribute it and/or
00011  * modify it under the terms of the GNU Lesser General Public
00012  * License as published by the Free Software Foundation; either
00013  * version 2.1 of the License, or (at your option) any later version.
00014  * 
00015  * This library is distributed in the hope that it will be useful,
00016  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018  * Lesser General Public License for more details.
00019  * 
00020  * You should have received a copy of the GNU Lesser General Public
00021  * License along with this library; if not, write to the Free Software
00022  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00023  *
00024  */
00025 
00026 /**
00027  *      NOTE: this is version 1.0! It has not been updated yet to exploit
00028  * SSH shared channel facilities.
00029  */
00030 class SExec {
00031 
00032     var $version="1.0";
00033     var remote;
00034     var password;
00035 
00036     var $ssh = "/usr/bin/ssh";
00037     var $scp = "/usr/bin/scp";
00038     
00039     function SExec($this->remote="localhost", $this->password="xxyzzy")
00040     {
00041         $this->remote = $this->remote;
00042         $this->password = "$this->password";
00043     }
00044     
00045     function set_remote_end($this->remote, $this->password="xxyzzy")
00046     {
00047         $this->remote = $this->remote;
00048         $this->password = $this->password;
00049     }
00050     
00051     
00052     /**
00053      *  Execute a single command remotely using ssh and 
00054      * display its output, optionally returning its exit 
00055      * status (like passthru)
00056      *
00057      *  This function is intended to be used as a one-time
00058      * all-at-once non-interactive execution mechanism which
00059      * will run the command remotely and display its output.
00060      *
00061      *  If you try to issue an interactive command using this
00062      * function, all you will get is unneccessary trouble. So
00063      * don't!
00064      *
00065      *  This might be done as well using a pipe on /tmp and
00066      * making the command 'cat' the pipe: when ssh runs, it
00067      * runs the command 'cat' on the pipe and hangs on read.
00068      *  Then we just need a thread to open the pipe, put the
00069      * password and close the pipe.
00070      *  This other way the password is never wirtten down.
00071      * But, OTOH, the file life is so ephemeral that most
00072      * of the time it will only exist in the internal system
00073      * cache, so this approach is not that bad either.
00074      *
00075      *  @param remote   The remote end to run the command, in
00076      *                      the form 'user@host:port' (you may
00077      *                      omit the 'user@' or ':port' parts
00078      *                      if the default values [i.e. same user
00079      *                      or standard port] are OK).
00080      *  @param password The remote password. Note that if direct
00081      *                      RSA/DSA/.shosts/.rhosts login is enabled
00082      *                      then the password should be ignored as
00083      *                      SSH should not run the ASKPASS command).
00084      *  @param command  The command to execute on the remote end
00085      *                      NOTE: if you want to use redirection, the
00086      *                      entire remote command line should be 
00087      *                      enclosed in additional quotes!
00088      *  @param status   Optional, this will hold the termination
00089      *                      status of SSH after invocation, which
00090      *                      should be the exit status of the remote
00091      *                      command or 255 if an error occurred
00092      *  @return void
00093      */
00094     function ssh_passthru($command, &$status)
00095     {
00096         global $debug;
00097 
00098         // Setup environment
00099         umask(0077);
00100         $tmpfname = tempnam('/tmp', 'phpSsh-');
00101         chmod($tmpfname, 0700);
00102         if ($debug) echo $tmpfname."\n";
00103         
00104         putenv("DISPLAY=none:0.");
00105         putenv("SSH_ASKPASS=$tmpfname");
00106 
00107         // make askpass command
00108         $fp = fopen($tmpfname, "w");
00109         fputs($fp, "#!/bin/sh\necho $this->password\n");
00110         fputs($fp, "rm -f $tmpfname\n");
00111         fclose($fp);
00112         // go
00113         if (isset($status)) {
00114             if ($debug) echo "$this->ssh -x -t -t $this->remote \"$command\"\n";
00115             passthru("$this->ssh -x -t -t $this->remote \"$command\"", $status);
00116         }
00117         else {
00118             if ($debug) echo "$this->ssh -x -t -t $this->remote \"$command\"\n";
00119             passthru("$this->ssh -x -t -t $this->remote \"$command\"");
00120         }
00121     }
00122     
00123     
00124     /**
00125      *  Execute a remote command using SSH
00126      *
00127      *  This function sort of mimics rexec(3) using SSH as the transport
00128      * protocol.
00129      *
00130      *  The function returns the exit status of the remote command, and
00131      * appends the remote job output to an optional argument.
00132      *
00133      *  This function is intended to be used as a one-time
00134      * all-at-once non-interactive execution mechanism which
00135      * will run the command remotely and return its output.
00136      *
00137      *  If you try to issue an interactive command using this
00138      * function, all you will get is unneccessary trouble. So
00139      * don't!
00140      *
00141      *  @param remote   The remote end to run the command, in
00142      *                      the form 'user@host:port' (you may
00143      *                      omit the 'user@' or ':port' parts
00144      *                      if the default values [i.e. same user
00145      *                      or standard port] are OK).
00146      *  @param password The remote password. Note that if direct
00147      *                      RSA/DSA/.shosts/.rhosts login is enabled
00148      *                      then the password should be ignored as
00149      *                      SSH should not run the ASKPASS command).
00150      *  @param command  The command to execute on the remote end
00151      *                      NOTE: if you want to use redirection, the
00152      *                      entire remote command line should be 
00153      *                      enclosed in additional quotes!
00154      *  @param output   Optional, the collated (stdout+stderr) output 
00155      *                      of the remote command.
00156      *  @return status  will hold the termination
00157      *                      status of SSH after invocation, which
00158      *                      should be the exit status of the remote
00159      *                      command or 255 if an error occurred
00160      */
00161     function ssh_exec($command, &$out)
00162     {
00163         global $debug;
00164 
00165         umask(0077);
00166         $tmpfname = tempnam('/tmp', 'phpSsh');
00167         chmod($tmpfname, 0700);
00168         if ($debug) echo $tmpfname . "\n";
00169 
00170         putenv('DISPLAY=none:0.');
00171         putenv("SSH_ASKPASS=$tmpfname");
00172         $fp = fopen($tmpfname, "w");
00173         fputs($fp, "#!/bin/sh\necho $this->password\n");
00174         fputs($fp, "rm -f $tmpfname\n");
00175         fclose($fp);
00176         exec("$this->ssh -x -t -t $this->remote \"$command\"", $out, $retval);
00177         return $retval;
00178 
00179     }
00180     
00181     /**
00182      *  Copy a file or directory from one source to a destination
00183      *
00184      *  This function copies source to dest, where one of them is a
00185      * local filespec and the other a remote filespec of the form
00186      * [user@]host:path
00187      *
00188      *  If the original source is a directory, it will be copied
00189      * recursively to destination (hence easing file transfers).
00190      *
00191      *  The function returns TRUE on success or FALSE on failure.
00192      *
00193      *  @param origin   The origin path, of the form
00194      *                  [user@][host][:port]path
00195      *                  You may omit the optional sections if
00196      *                  the default values (local username, local
00197      *                  host, standard SSH port) are OK
00198      *
00199      *  @param destination      The destination path, of the form
00200      *                  [user@][host][:port:]path
00201      *                  You may omit the optional sections if
00202      *                  the default values (local username, local
00203      *                  host, standard SSH port) are OK
00204      *
00205      *  @param password The password to use to connect to the remote
00206      *                  end of the copy (be it the origin or the
00207      *                  destination, it's all the same). If connection
00208      *                  is automatic by some means (.shosts or RSA/DSA
00209      *                  authentication) then it should be ignored and
00210      *                  any password should do.
00211      *
00212      *  @return status  TRUE if all went well, or FALSE on failure.
00213      */
00214     function ssh_copy($origin, $destination, $this->password)
00215     {
00216         global $debug;
00217 
00218         umask(0077);
00219         $tmpfname = tempnam("/tmp", "phpSsh");
00220         chmod($tmpfname, 0700);
00221         putenv("DISPLAY=none:0.");
00222         putenv("SSH_ASKPASS=$tmpfname");
00223         $fp = fopen($tmpfname, "w");
00224         fputs($fp, "#!/bin/sh\necho $this->password\n");
00225         fputs($fp, "rm $tmpfname\n");
00226         fclose($fp);
00227         exec("$this->scp -pqrC $origin $destination", $out, $status);
00228         if ($status == 0)
00229             return TRUE;
00230         else
00231             return FALSE;
00232     }
00233 
00234     /**
00235      *  Open an SSH connection to a remote site with a shell to run 
00236      * interactive commands
00237      *
00238      *  Connects to a remote host and opens an interactive shell session
00239      * with NO controlling terminal.
00240      *
00241      *  Returns a process_control array which contains the process resource
00242      * ID and an the standard file descriptors which the caller may use to
00243      * interact with the remote shell.
00244      *
00245      *  @param remote   The remote end to run the shell, in
00246      *                      the form 'user@host:port' (you may
00247      *                      omit the 'user@' or ':port' parts
00248      *                      if the default values [i.e. same user
00249      *                      or standard port] are OK).
00250      *  @param password The remote password. Note that if direct
00251      *                      RSA/DSA/.shosts/.rhosts login is enabled
00252      *                      then the password should be ignored as
00253      *                      SSH should not run the ASKPASS command).
00254      */
00255     function ssh_open_shell()
00256     {   
00257         global $debug;
00258 
00259         // Open a child process with the 'proc_open' function. 
00260         //
00261         // Some tricks: we must open the connection using '-x' to disable
00262         // X11 forwarding, and use '-t -t' to avoid SSH generating an error
00263         // because we are not connected to any terminal.
00264         //
00265         // NOTE: if the web server is trusted remotely (i.e. it's SSH public 
00266         // key is accepted in ~user@host:.ssh/authorized_keys) then any 
00267         // password will do.
00268 
00269         // Prepare I/O
00270         $descriptorspec = array(
00271             0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
00272             1 => array("pipe", "a"),  // connect child's stdout to the write end of a pipe
00273             2 => array("pipe", "a")   // stderr is a pipe to read from
00274         );
00275 
00276         // prepare password
00277         umask(0077);
00278         $tmpfname = tempnam("/tmp", "phpSsh-");
00279         chmod($tmpfname, 0700);
00280         if ($debug) echo $tmpfname . "\n";
00281 
00282         putenv("DISPLAY=none:0.");
00283         putenv("SSH_ASKPASS=$tmpfname");
00284         $fp = fopen($tmpfname, "w");
00285         fputs($fp, "#!/bin/sh\necho $this->password\n");
00286         fputs($fp, "rm $tmpfname\n");
00287         fclose($fp);
00288 
00289         if ($debug) echo "$this->ssh -x -t -t $this->remote<br />\n";
00290         $process = proc_open("$this->ssh -x -t -t $this->remote", 
00291                          $descriptorspec,
00292                          $pipes);
00293         
00294         // check status
00295         if (!is_resource($process)) 
00296         {
00297             letal("SSH::connect", "cannot connect to the remote host");
00298             return;
00299         }
00300         if ($debug) echo "proc_open done<br />\n";
00301 
00302         // $pipes now looks like this:
00303         //   0 => writeable handle connected to child stdin
00304         //   1 => readable handle connected to child stdout
00305         //   2 => readable handle connected to child stderr
00306         
00307         // Should we leave this to the user?
00308         // set to non-blocking and avoid having to call fflush
00309         stream_set_blocking($pipes[0], FALSE);
00310         stream_set_blocking($pipes[1], FALSE);
00311         stream_set_blocking($pipes[2], FALSE);
00312         stream_set_write_buffer($pipes[0], 0);
00313         stream_set_write_buffer($pipes[1], 0);
00314         stream_set_write_buffer($pipes[2], 0);
00315 
00316         // We now have a connection to the remote SSH
00317         // Server which we may use to send commands/receive output
00318         $p = array('process' => $process
00319                     ,'std_in' => $pipes[0]
00320                     ,'std_out' => $pipes[1]
00321                     ,'std_err' => $pipes[2] 
00322                    );
00323         if ($debug)  {
00324             echo "process descriptor array is \n";
00325             print_r($p);
00326             /*
00327             fwrite($p['std_in'], "\n");
00328             fwrite($p['std_in'], "touch touche\n");
00329             fwrite($p['std_in'], "logout\n");
00330             fflush($p['std_in']);
00331             fclose($p['std_in']); fclose($p['std_out']); fclose($p['std_err']);
00332             echo "pipes closed\n";
00333             proc_close($p['process']);
00334             echo "process closed\n";
00335         } 
00336         if ($debug == "CHANGE ME") {
00337             echo "process "; print_r($process); echo "\n";
00338             echo "p->process "; print_r($p['process']); echo "\n";
00339             fwrite($pipes[0], "touch touche\n");
00340             fwrite($pipes[0], "logout\n");
00341             fflush($pipes[0]);
00342             fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]);
00343             proc_close($process);
00344             */
00345         }
00346         return $p;
00347     }
00348     
00349     /**
00350      *  Open an SSH connection to run an interactive command on a remote
00351      * site
00352      *
00353      *  Connects to a remote host and runs an interactive command
00354      * with NO controlling terminal.
00355      *
00356      *  Returns a process_control array which contains the process resource
00357      * ID and an the standard file descriptors which the caller may use to
00358      * interact with the remote shell.
00359      */
00360     function ssh_open_command($command)
00361     {   
00362         global $debug;
00363 
00364         // Open a child process with the 'proc_open' function. 
00365         //
00366         // Some tricks: we must open the connection using '-x' to disable
00367         // X11 forwarding, and use '-t -t' to avoid SSH generating an error
00368         // because we are not connected to any terminal.
00369         //
00370         // NOTE: if the web server is trusted remotely (i.e. it's SSH public 
00371         // key is accepted in ~user@host:.ssh/authorized_keys) then any 
00372         // password will do.
00373 
00374         // Prepare I/O
00375         umask(0077);
00376         $descriptorspec = array(
00377             0 => array("pipe", "r"),  // connect child's stdin to the read end of a pipe
00378             1 => array("pipe", "a"),  // connect child's stdout to the write end of a pipe
00379             2 => array("pipe", "a")   // stderr is a pipe to read from
00380         );
00381 
00382         // prepare password
00383         umask(0077);
00384         $tmpfname = tempnam("/tmp", "phpSsh-");
00385         chmod($tmpfname, 0700);
00386         if ($debug) echo $tmpfname . "\n";
00387 
00388         putenv("DISPLAY=none:0.");
00389         putenv("SSH_ASKPASS=$tmpfname");
00390         $fp = fopen($tmpfname, "w");
00391         fputs($fp, "#!/bin/sh\necho $this->password\n");
00392         fputs($fp, "rm $tmpfname\n");
00393         fclose($fp);
00394 
00395         if ($debug) echo "$this->ssh -x -t -t $this->remote $command<br />\n";
00396         $process = proc_open("$this->ssh -x -t -t $this->remote \"$command\"", 
00397                          $descriptorspec,
00398                          $pipes);
00399         
00400         // check status
00401         if (!is_resource($process)) 
00402         {
00403             letal("SSH::connect", "cannot connect to the remote host");
00404             return;
00405         }
00406         if ($debug) echo "proc_open done<br />\n";
00407 
00408         // $pipes now looks like this:
00409         //   0 => writeable handle connected to child stdin
00410         //   1 => readable handle connected to child stdout
00411         //   2 => readable handle connected to child stderr
00412         
00413         // Should we leave this to the user?
00414         // set to non-blocking and avoid having to call fflush
00415         stream_set_blocking($pipes[0], FALSE);
00416         stream_set_blocking($pipes[1], FALSE);
00417         stream_set_blocking($pipes[2], FALSE);
00418         stream_set_write_buffer($pipes[0], 0);
00419         stream_set_write_buffer($pipes[1], 0);
00420         stream_set_write_buffer($pipes[2], 0);
00421 
00422         // We now have a connection to the remote SSH
00423         // Server which we may use to send commands/receive output
00424         $p = array('process' => $process
00425                     ,'std_in' => $pipes[0]
00426                     ,'std_out' => $pipes[1]
00427                     ,'std_err' => $pipes[2] 
00428                    );
00429         if ($debug)  {
00430             echo "process descriptor array is \n";
00431             print_r($p);
00432         }
00433         return $p;
00434     }
00435 
00436     /**
00437      * Get output until we reach a given regular expression
00438      */
00439     function ssh_out_expect($p, $expr="^# ")
00440     {
00441         flush();
00442         fseek($p["std_out"], $last);
00443         do {
00444             $line = fgets($p["std_out"], 1024);
00445             #echo ">> ".$line;
00446         } while ((! feof($p["std_out"]) ) && (! ereg($expr, $line)));
00447         $last = ftell($p["std_out"]);
00448     }
00449 
00450     /**
00451      * Close an SSH interactive session
00452      */
00453     function ssh_close($p)
00454     {
00455         global $debug;
00456         
00457             fwrite($p['std_in'], "\n");
00458             fwrite($p['std_in'], "logout\n");
00459             fflush($p['std_in']);
00460             fclose($p['std_in']); fclose($p['std_out']); fclose($p['std_err']);
00461             if ($debug) echo "pipes closed\n";
00462             return proc_close($p['process']);
00463     }
00464     
00465 #    if ($php_version >= 5)
00466 #    {
00467 #       /**
00468 #        * send a signal to a running ssh_open_* process
00469 #        */
00470 #       function ssh_signal($p, $signal)
00471 #       {
00472 #           return proc_terminate($p['process'], $signal);
00473 #       }
00474 #       /**
00475 #        * get info about a running ssh_open_* process
00476 #        */
00477 #       function ssh_get_status($p)
00478 #       {
00479 #           return proc_get_status($p['process']);
00480 #       }
00481 #    }
00482     
00483     /**
00484      *  Execute a remote command and keep an unidirectional stream
00485      * contact with it.
00486      *
00487      *  This routine mimics 'popen()' but uses ssh to connect to
00488      * a remote host and run the requested command.
00489      */
00490     function ssh_popen($command, $mode)
00491     {
00492         global $debug;
00493 
00494         // Setup environment
00495         umask(0077);
00496         $tmpfname = tempnam('/tmp', 'phpSsh-');
00497         chmod($tmpfname, 0700);
00498         if ($debug) echo $tmpfname."\n";
00499         
00500         putenv("DISPLAY=none:0.");
00501         putenv("SSH_ASKPASS=$tmpfname");
00502 
00503         // make askpass command
00504         $fp = fopen($tmpfname, "w");
00505         fputs($fp, "#!/bin/sh\necho $this->password\n");
00506         fputs($fp, "rm -f $tmpfname\n");
00507         fclose($fp);
00508         // go
00509         return popen("$this->ssh -x -t -t $this->remote \"$command\"", $mode);
00510     }
00511     
00512     function ssh_pclose($f)
00513     {
00514         return pclose($f);
00515     }
00516 
00517 }
00518 
00519 ?>

Generated on Wed May 25 19:14:05 2005 for php::ssh by doxygen 1.3.6