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 ?>