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@es.embnet.org> 00037 * @license doc/lic/ 00038 * @version CVS: $Id: ssh-files.php,v 1.6 2005/05/25 16:02: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 tricy 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 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: @package_version@ 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.0"; 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 remote 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 password 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="localhost", $password="xxyzzy") 00375 { 00376 global $debug; 00377 00378 if ($debug) echo "\nCreating a new SExec\n"; 00379 $this->remote = $remote; 00380 $this->password = "$password"; 00381 umask(0077); 00382 /* DESIGN 00383 * In order to increase efficiency, we will create a master channel 00384 * on class instantiation. The master channel should be closed by a 00385 * corresponding class destructor! 00386 * 00387 * Creating a master channel has the advantage that subsequent SSH 00388 * connections will use it and avoid repeating the slow authentication 00389 * process: in other words, they will go much, much faster. 00390 */ 00391 00392 // first we must generate a unique UNIX socket address or we'll fail 00393 // We use a tricky trick: generate two random numbers and use them; 00394 // this is tricky since there might be a problem, but with very low 00395 // probability. BUT IT MAY STILL FAIL: there's a race condition between 00396 // the end of the while and the subsequent if. 00397 do { 00398 mt_srand((double)microtime()*1000000 ) . 00399 $this->workdir = "/tmp/phpSsh-" . mt_rand() .".". mt_rand(); 00400 if ($debug) echo "\nSExec: trying $this->workdir/ ..."; 00401 // CAUTION: this is potentially an endless loop (albeit with very 00402 // low probability) if every possible file did exist. 00403 } 00404 while (file_exists($this->workdir)); 00405 if (mkdir($this->workdir) == FALSE) { 00406 // we can't continue. How can we cancel this? 00407 // try these and then check what happens 00408 unset($this); 00409 $this = NULL; 00410 return FALSE; 00411 } 00412 else 00413 if ($debug) echo " OK\n"; 00414 // Now we have a place to put the socket... Mmm... 00415 // Come to think of it, we have a place to put ANY temporary 00416 // for the class... 00417 // XXX Maybe we can change everywhere else to use this? 00418 $this->mplex_socket = $this->workdir."/mplex_socket"; 00419 00420 // Finally we can call SSH -M 00421 // Create SSH_ASKPASS script to provide the password 00422 $tmpfname = tempnam($this->workdir, 'SExec-'); 00423 chmod($tmpfname, 0700); 00424 putenv("DISPLAY=none:0."); 00425 putenv("SSH_ASKPASS=$tmpfname"); 00426 $fp = fopen($tmpfname, "w"); 00427 fputs($fp, "#!/bin/sh\necho $this->password\n"); 00428 if (!$debug) 00429 fputs($fp, "/bin/touch $tmpfname.called\n"); 00430 else 00431 fputs($fp, "/bin/rm -f $tmpfname\n"); 00432 fclose($fp); 00433 00434 // OK, we are ready. Now let's open a master shell 00435 $child_stdout = tempnam($this->workdir, "open_sh-O-"); 00436 $child_stderr = tempnam($this->workdir, "open_sh-E-"); 00437 $descriptorspec = array( 00438 0 => array("pipe", "r"), // connect child's stdin to the read end of a pipe 00439 1 => array("file", $child_stdout, "a"), // connect child's stdout to the write end of a pipe 00440 2 => array("file", $child_stderr, "a") // stderr is a pipe to read from 00441 ); 00442 00443 if ($debug) echo "$this->ssh -x -t -t ". 00444 "-M -S $this->mplex_socket " . 00445 "$this->remote\n"; 00446 $this->master = proc_open("$this->ssh -x -t -t ". 00447 "-M -S $this->mplex_socket " . 00448 "$this->remote", 00449 $descriptorspec, 00450 $pipes); 00451 if ((! is_resource($this->master)) || ($this->master == FALSE)) { 00452 putenv("SSH_ASKPASS=dummy"); 00453 unset($this); 00454 $this = NULL; 00455 return FALSE; 00456 } 00457 // we do not need to worry about the output log files, just the 00458 // input pipe for logout 00459 $this->master_input = $pipes[0]; 00460 00461 // Before going ahead, we need to ensure the control shell 00462 // has started: wait for the socket to become available 00463 // note: there should be a timeout here to avoid a possibly 00464 // infinite loop XXX 00465 do { 00466 if ($debug) echo "waiting 0.1 sec\n"; 00467 usleep(100000); // wait 0.1 seconds 00468 } while (! file_exists($this->mplex_socket)); 00469 00470 // and now we must register a destructor for the class 00471 // that will close the connection. 00472 //register_shutdown_function($this->destruct()); 00473 00474 return $this; 00475 } 00476 00477 /** Class destructor 00478 * 00479 * Destroy all working processes and data streams and structures 00480 * used by an instance of this class. 00481 * 00482 * This method will send a termination message to the other end 00483 * of the master channel, close the control stream of the master 00484 * channel and terminate its controlling process, finally unsetting 00485 * the object and setting the object handle to NULL. 00486 * 00487 * If a global $debug is not set to TRUE, then it will also remove 00488 * all communication traces of this object: i.e. all log files for 00489 * interactive and master sessions, communications socket, etc... 00490 * 00491 * If global $debug is set to TRUE, then a copy of all log files 00492 * created during the lifetime of the object will be left on a 00493 * temporary directory for your perusal and reference. 00494 * 00495 * @return integer exit status of the master channel control process. 00496 * 00497 * @access public 00498 * @since Method available since Release 1.0 00499 */ 00500 function destruct() 00501 { 00502 global $debug; 00503 00504 if ($debug) echo "\nDestroying SExec master\n"; 00505 if ($debug) print_r($this); 00506 if ($debug) echo "sending logout\n"; 00507 // log out master process 00508 fputs($this->master_input, "\n\nlogout\n\n"); 00509 // close master stdin 00510 fclose($this->master_input); 00511 // close master process 00512 $ret = proc_close($this->master); 00513 // remove temporaries 00514 if (! $debug) system("/bin/rm -rf $this->workdir"); 00515 // utterly destroy this instance 00516 unset($this); 00517 $this = NULL; 00518 return $ret; 00519 } 00520 00521 //}}} 00522 00523 //{{{ methods 00524 /** 00525 * Copy a file or directory from one source to a destination 00526 * 00527 * This function copies source to dest, where one of them is a 00528 * local filespec and the other a remote filespec of the form 00529 * [user@]host:path 00530 * 00531 * If the original source is a directory, it will be copied 00532 * recursively to destination (hence easing file transfers). 00533 * 00534 * The function returns TRUE on success or FALSE on failure. 00535 * 00536 * <b>EFFICIENCY NOTICE:</b> 00537 * 00538 * The copy routines use 'scp' to do their actual work. Since 00539 * scp seems to be unable to hitchhike on the master channel, 00540 * we must do authentication for each copy operation (subroutine 00541 * call). These routines are hence a lot more time-expensive 00542 * than all the other ones. 00543 * 00544 * You may want to consider whether you can group several 00545 * copies into one single call to reduce authentication 00546 * overheads. 00547 * 00548 * @note DEPRECATED (inconsistent with the class) 00549 * 00550 * @see scp(1) 00551 * 00552 * @param string origin The origin path, of the form 00553 * [user@][host][:port]path 00554 * You may omit the optional sections if 00555 * the default values (local username, local 00556 * host, standard SSH port) are OK 00557 * 00558 * @param string destination The destination path, of the form 00559 * [user@][host][:port:]path 00560 * You may omit the optional sections if 00561 * the default values (local username, local 00562 * host, standard SSH port) are OK 00563 * 00564 * @param string password The password to use to connect to the remote 00565 * end of the copy (be it the origin or the 00566 * destination, it's all the same). If connection 00567 * is automatic by some means (.shosts or RSA/DSA 00568 * authentication) then it should be ignored and 00569 * any password should do. 00570 * 00571 * @return bool TRUE if all went well, or FALSE on failure. 00572 * 00573 * @access public 00574 * @since Method available since Release 1.0 00575 * @deprecated Method deprecated as of Release 2.1 00576 */ 00577 function ssh_copy($origin, $destination, $password) 00578 { 00579 global $debug; 00580 00581 umask(0077); 00582 $tmpfname = tempnam($this->workdir, "copy-"); 00583 chmod($tmpfname, 0700); 00584 putenv("DISPLAY=none:0."); 00585 putenv("SSH_ASKPASS=$tmpfname"); 00586 $fp = fopen($tmpfname, "w"); 00587 fputs($fp, "#!/bin/sh\necho $password\n"); 00588 if (! $debug) 00589 fputs($fp, "/bin/touch $tmpfname.called\n"); 00590 else 00591 fputs($fp, "/bin/rm $tmpfname\n"); 00592 fclose($fp); 00593 exec("$this->scp -pqrC $origin $destination", $out, $status); 00594 if ($status == 0) 00595 return TRUE; 00596 else 00597 return FALSE; 00598 } 00599 00600 00601 /** 00602 * Copy a file or directory from a local source to a remote destination 00603 * 00604 * This function copies source to dest, where first of them is a 00605 * local filespec and then comes a remote filespec as a normal 00606 * system path. 00607 * 00608 * Both, local and remote paths may be absolute or relative. 00609 * 00610 * If the original source is a directory, it will be copied 00611 * recursively to destination (hence easing file transfers). 00612 * 00613 * The function returns TRUE on success or FALSE on failure. 00614 * 00615 * <b>EFFICIENCY NOTICE:</b> 00616 * 00617 * The copy routines use 'scp' to do their actual work. Since 00618 * scp seems to be unable to hitchhike on the master channel, 00619 * we must do authetication for each copy operation (subroutine 00620 * call). These routines are hence a lot more time-expensive 00621 * than all the other ones. 00622 * 00623 * You may want to consider whether you can group several 00624 * copies into one single call to reduce authentication 00625 * overheads. 00626 * 00627 * @see scp(1) 00628 * 00629 * @param string localpath The origin local path, either absolute or 00630 * relative to the current working directory. 00631 * If it denotes a directory, the copy will 00632 * be recursive. 00633 * 00634 * @param string remotepath The destination path, either 00635 * absolute or relative to the login home. 00636 * 00637 * @return bool TRUE if all went well, or FALSE on failure. 00638 * 00639 * @access public 00640 * @since Method available since Release 2.1 00641 */ 00642 function ssh_copy_to($localpath, $remotepath) 00643 { 00644 global $debug; 00645 00646 umask(0077); 00647 $tmpfname = tempnam($this->workdir, "copy-to-"); 00648 chmod($tmpfname, 0700); 00649 putenv("DISPLAY=none:0."); 00650 putenv("SSH_ASKPASS=$tmpfname"); 00651 $fp = fopen($tmpfname, "w"); 00652 fputs($fp, "#!/bin/sh\necho $this->password\n"); 00653 if (! $debug) 00654 fputs($fp, "/bin/touch $tmpfname.called\n"); 00655 else 00656 fputs($fp, "/bin/rm $tmpfname\n"); 00657 fclose($fp); 00658 exec("$this->scp -pqrC $localpath $this->remote:$remotepath", $out, $status); 00659 if ($status == 0) 00660 return TRUE; 00661 else 00662 return FALSE; 00663 } 00664 00665 /** 00666 * Copy a file or directory from a remote source to a local destination 00667 * 00668 * This function copies source to dest, where first of them is a 00669 * remote filespec and then comes a local filespec, both specified 00670 * as normal system paths. 00671 * 00672 * Both, local and remote paths may be absolute or relative. 00673 * 00674 * If the original source is a directory, it will be copied 00675 * recursively to destination (hence easing file transfers). 00676 * 00677 * The function returns TRUE on success or FALSE on failure. 00678 * 00679 * EFFICIENCY NOTICE: 00680 * 00681 * The copy routines use 'scp' to do their actual work. Since 00682 * scp seems to be unable to hitchhike on the master channel, 00683 * we must do authetication for each copy operation (subroutine 00684 * call). These routines are hence a lot more time-expensive 00685 * than all the other ones. 00686 * 00687 * You may want to consider whether you can group several 00688 * copies into one single call to reduce authentication 00689 * overheads. 00690 * 00691 * @see scp(1) 00692 * 00693 * @param string remotepath The origin remote path, either absolute or 00694 * relative to the login home. If it denotes a 00695 * directory, the copy will be recursive. 00696 * 00697 * @param string localpath The local destination path, either 00698 * absolute or relative to the current working 00699 * directory. 00700 * 00701 * @return bool TRUE if all went well, or FALSE on failure. 00702 * 00703 * @access public 00704 * @since Method available since Release 1.0 00705 */ 00706 function ssh_copy_from($remotepath, $localpath) 00707 { 00708 global $debug; 00709 00710 umask(0077); 00711 $tmpfname = tempnam($this->workdir, "copy-from-"); 00712 chmod($tmpfname, 0700); 00713 putenv("DISPLAY=none:0."); 00714 putenv("SSH_ASKPASS=$tmpfname"); 00715 $fp = fopen($tmpfname, "w"); 00716 fputs($fp, "#!/bin/sh\necho $this->password\n"); 00717 if (! $debug) 00718 fputs($fp, "/bin/touch $tmpfname.called\n"); 00719 else 00720 fputs($fp, "/bin/rm $tmpfname\n"); 00721 fclose($fp); 00722 exec("$this->scp -pqrC $this->remote:$remotepath $localpath", $out, $status); 00723 if ($status == 0) 00724 return TRUE; 00725 else 00726 return FALSE; 00727 } 00728 00729 /** 00730 * Execute a single command remotely 00731 * 00732 * Execute a single command remotely using ssh and 00733 * display its output, optionally returning its exit 00734 * status (like passthru) 00735 * 00736 * This function is intended to be used as a one-time 00737 * all-at-once non-interactive execution mechanism which 00738 * will run the command remotely and display its output. 00739 * 00740 * If you try to issue an interactive command using this 00741 * function, all you will get is unneccessary trouble. So 00742 * don't! 00743 * 00744 * This might be done as well using a pipe on /tmp and 00745 * making the command 'cat' the pipe: when ssh runs, it 00746 * runs the command 'cat' on the pipe and hangs on read. 00747 * Then we just need a thread to open the pipe, put the 00748 * password and close the pipe. 00749 * 00750 * This other way the password is never wirtten down. 00751 * But, OTOH, the file life is so ephemeral that most 00752 * of the time it will only exist in the internal system 00753 * cache, so this approach is not that bad either. 00754 * 00755 * @see passthru() 00756 * 00757 * @param string command The command to execute on the remote end 00758 * NOTE: if you want to use redirection, the 00759 * entire remote command line should be 00760 * enclosed in additional quotes! 00761 * @param integer status Optional, this will hold the termination 00762 * status of SSH after invocation, which 00763 * should be the exit status of the remote 00764 * command or 255 if an error occurred 00765 * @return void 00766 * 00767 * @access public 00768 * @since Method available since Release 1.0 00769 */ 00770 function ssh_passthru($command, &$status) 00771 { 00772 global $debug; 00773 00774 if ($debug) echo "status = $status\n"; 00775 // go 00776 if (isset($status)) { 00777 if ($debug) echo "st: $this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"\n"; 00778 passthru("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $status); 00779 } 00780 else { 00781 if ($debug) echo "~st: $this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"\n"; 00782 passthru("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\""); 00783 } 00784 } 00785 00786 00787 /** 00788 * Execute a remote command using SSH 00789 * 00790 * This function sort of mimics rexec(3) using SSH as the transport 00791 * protocol. 00792 * 00793 * The function returns the exit status of the remote command, and 00794 * appends the remote job output to an optional argument. 00795 * 00796 * This function is intended to be used as a one-time 00797 * all-at-once non-interactive execution mechanism which 00798 * will run the command remotely and return its output. 00799 * 00800 * If you try to issue an interactive command using this 00801 * function, all you will get is unneccessary trouble. So 00802 * don't! 00803 * 00804 * @param string command The command to execute on the remote end 00805 * NOTE: if you want to use redirection, the 00806 * entire remote command line should be 00807 * enclosed in additional quotes! 00808 * @param string output Optional, the collated (stdout+stderr) output 00809 * of the remote command. 00810 * @return integer status will hold the termination 00811 * status of SSH after invocation, which 00812 * should be the exit status of the remote 00813 * command or 255 if an error occurred 00814 * 00815 * @access public 00816 * @since Method available since Release 1.0 00817 */ 00818 function ssh_exec($command, &$out) 00819 { 00820 global $debug; 00821 00822 umask(0077); 00823 $tmpfname = tempnam($this->workdir, 'exec'); 00824 chmod($tmpfname, 0700); 00825 if ($debug) echo $tmpfname . "\n"; 00826 00827 exec("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $out, $retval); 00828 return $retval; 00829 00830 } 00831 00832 /** 00833 * Open an SSH connection to a remote site with a shell to run 00834 * interactive commands 00835 * 00836 * Connects to a remote host and opens an interactive shell session 00837 * with NO controlling terminal. 00838 * 00839 * This routine creates communication streams with the remote shell, 00840 * and stores all output (standard and error) of the connection into 00841 * two separate local log files (one for stdout and one for stderr). 00842 * 00843 * Returns a process_control array which contains the process resource 00844 * ID and an the standard file descriptors which the caller may use to 00845 * interact with the remote shell. 00846 * 00847 * The process control array contains: 00848 * 00849 * 'process' -- the process resource for the newly created connection 00850 * 00851 * 'std_in' -- handle to the standard input of the new connection 00852 * 00853 * 'std_out' -- handle to standard output of the new connection 00854 * 00855 * 'std_err' -- handle to standard error of the new connection 00856 * 00857 * 'stdout_file' -- actual filename of the local log file for the 00858 * new connection standard output 00859 * 00860 * 'stderr_file' -- actual filename of the local log file for the 00861 * new connection standard error 00862 * 00863 * @return mixed a process control associative array. 00864 * 00865 * @access public 00866 * @since Method available since Release 1.0 00867 */ 00868 function ssh_open_shell() 00869 { 00870 global $debug; 00871 00872 // Open a child process with the 'proc_open' function. 00873 // 00874 // Some tricks: we must open the connection using '-x' to disable 00875 // X11 forwarding, and use '-t -t' to avoid SSH generating an error 00876 // because we are not connected to any terminal. 00877 // 00878 // NOTE: if the web server is trusted remotely (i.e. it's SSH public 00879 // key is accepted in ~user@host:.ssh/authorized_keys) then any 00880 // password will do. 00881 00882 // Prepare I/O 00883 umask(0077); 00884 if ($debug) { 00885 $child_stdout = tempnam($this->workdir, "open_sh-".getmypid()."-O-"); 00886 $child_stderr = tempnam($this->workdir, "open_sh-".getmypid()."-E-"); 00887 } else { 00888 $child_stdout = tempnam($this->workdir, "open_sh-"); 00889 $child_stderr = tempnam($this->workdir, "open_sh-"); 00890 } 00891 $descriptorspec = array( 00892 0 => array("pipe", "r"), // connect child's stdin to the read end of a pipe 00893 1 => array("file", $child_stdout, "a"), // connect child's stdout to the write end of a pipe 00894 2 => array("file", $child_stderr, "a") // stderr is a pipe to read from 00895 ); 00896 if ($debug) echo "$this->ssh -x -t -t -S $this->mplex_socket $this->remote<br />\n"; 00897 $process = proc_open("$this->ssh -x -t -t -S $this->mplex_socket $this->remote", 00898 $descriptorspec, 00899 $pipes); 00900 00901 // check status 00902 if (!is_resource($process)) 00903 { 00904 letal("SSH::connect", "cannot connect to the remote host"); 00905 return; 00906 } 00907 if ($debug) echo "proc_open done<br />\n"; 00908 00909 // $pipes now looks like this: 00910 // 0 => writeable handle connected to child stdin 00911 00912 // Open child's stdin and stdout 00913 $pipes[1] = fopen($child_stdout, "r"); 00914 $pipes[2] = fopen($child_stderr, "r"); 00915 00916 // Should we leave this to the user? 00917 // set to non-blocking and avoid having to call fflush 00918 //stream_set_blocking($pipes[0], FALSE); 00919 //stream_set_blocking($pipes[1], FALSE); 00920 //stream_set_blocking($pipes[2], FALSE); 00921 stream_set_write_buffer($pipes[0], 0); 00922 stream_set_write_buffer($pipes[1], 0); 00923 stream_set_write_buffer($pipes[2], 0); 00924 00925 // We now have a connection to the remote SSH 00926 // Server which we may use to send commands/receive output 00927 $p = array('process' => $process 00928 ,'std_in' => $pipes[0] 00929 ,'std_out' => $pipes[1] 00930 ,'std_err' => $pipes[2] 00931 ,'stdout_file' => $child_stdout 00932 ,'stderr_file' => $child_stderr 00933 ); 00934 if ($debug) { 00935 echo "process descriptor array is \n"; 00936 print_r($p); 00937 } 00938 return $p; 00939 } 00940 00941 /** 00942 * Open an SSH connection to run an interactive command on a remote 00943 * site 00944 * 00945 * Connects to a remote host and runs an interactive command 00946 * with NO controlling terminal. 00947 * 00948 * This routine creates communication streams with the remote shell, 00949 * and stores all output (standard and error) of the connection into 00950 * two separate local log files (one for stdout and one for stderr). 00951 * 00952 * Returns a process_control array which contains the process resource 00953 * ID and an the standard file descriptors which the caller may use to 00954 * interact with the remote shell. 00955 * 00956 * The process control array contains: 00957 * 00958 * 'process' -- the process resource for the newly created connection 00959 * 00960 * 'std_in' -- handle to the standard input of the new connection 00961 * 00962 * 'std_out' -- handle to standard output of the new connection 00963 * 00964 * 'std_err' -- handle to standard error of the new connection 00965 * 00966 * 'stdout_file' -- actual filename of the local log file for the 00967 * new connection standard output 00968 * 00969 * 'stderr_file' -- actual filename of the local log file for the 00970 * new connection standard error 00971 * 00972 * @param string command to be executed interactively on the remote end 00973 * 00974 * @return mixed a process control associative array. 00975 * 00976 * @access public 00977 * @since Method available since Release 1.0 00978 */ 00979 function ssh_open_command($command) 00980 { 00981 global $debug; 00982 00983 // Open a child process with the 'proc_open' function. 00984 // 00985 // Some tricks: we must open the connection using '-x' to disable 00986 // X11 forwarding, and use '-t -t' to avoid SSH generating an error 00987 // because we are not connected to any terminal. 00988 // 00989 // NOTE: if the web server is trusted remotely (i.e. it's SSH public 00990 // key is accepted in ~user@host:.ssh/authorized_keys) then any 00991 // password will do. 00992 00993 // Prepare I/O 00994 umask(0077); 00995 if ($debug) { 00996 $child_stdout = tempnam($this->workdir, "open_cmd-".getmypid()."-1-"); 00997 $child_stderr = tempnam($this->workdir, "open_cmd-".getmypid()."-2-"); 00998 } else { 00999 $child_stdout = tempnam($this->workdir, "open_cmd-"); 01000 $child_stderr = tempnam($this->workdir, "open_cmd-"); 01001 } 01002 $descriptorspec = array( 01003 0 => array("pipe", "r"), // connect child's stdin to the read end of a pipe 01004 #1 => array("pipe", "a"), // connect child's stdout to the write end of a pipe 01005 #2 => array("pipe", "a") // stderr is a pipe to read from 01006 1 => array("file", $child_stdout, "a"), 01007 2 => array("file", $child_stderr, "a") 01008 ); 01009 01010 if ($debug) echo "$this->ssh -x -t -t -S $this->mplex_socket $this->remote $command<br />\n"; 01011 $process = proc_open("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", 01012 $descriptorspec, 01013 $pipes); 01014 01015 // check status 01016 if (!is_resource($process)) 01017 { 01018 letal("SSH::connect", "cannot connect to the remote host"); 01019 return; 01020 } 01021 if ($debug) echo "proc_open done<br />\n"; 01022 01023 // $pipes now looks like this: 01024 // 0 => writeable handle connected to child stdin 01025 01026 // Open child's stdin and stdout 01027 $pipes[1] = fopen($child_stdout, "r"); 01028 $pipes[2] = fopen($child_stderr, "r"); 01029 01030 // Should we leave this to the user? 01031 // set to non-blocking and avoid having to call fflush 01032 #stream_set_blocking($pipes[0], FALSE); 01033 #stream_set_blocking($pipes[1], FALSE); 01034 #stream_set_blocking($pipes[2], FALSE); 01035 stream_set_write_buffer($pipes[0], 0); 01036 stream_set_write_buffer($pipes[1], 0); 01037 stream_set_write_buffer($pipes[2], 0); 01038 01039 // We now have a connection to the remote SSH 01040 // Server which we may use to send commands/receive output 01041 $p = array('process' => $process 01042 ,'std_in' => $pipes[0] 01043 ,'std_out' => $pipes[1] 01044 ,'std_err' => $pipes[2] 01045 ,'stdout_file' => $child_stdout 01046 ,'stderr_file' => $child_stderr 01047 ); 01048 if ($debug) { 01049 echo "process descriptor array is \n"; 01050 print_r($p); 01051 } 01052 return $p; 01053 } 01054 01055 /** 01056 * Get output until we reach a given regular expression 01057 * 01058 * @note EXPERIMENTAL, requires more thought and experience. 01059 */ 01060 function ssh_out_expect($p, $expr="^# ") 01061 { 01062 do { 01063 flush(); 01064 fseek($p["std_out"], $last); 01065 $line = fgets($p["std_out"], 1024); 01066 #echo ">> ".$line; 01067 $last = ftell($p["std_out"]); 01068 } while ((! feof($p["std_out"]) ) || (! ereg($expr, $line))); 01069 } 01070 01071 /** 01072 * Close an SSH interactive session 01073 * 01074 * This method terminates a previously open interactive remote 01075 * session. It will send a termination notification to the 01076 * remote end, close the connection with control and communication 01077 * streams, and terminate the local control process. 01078 * 01079 * Copies of the log files that contain the output and error 01080 * of the communication are left out for later reference and 01081 * local peruse. If you don't need them any longer, you may 01082 * delete them or just leave them around until the class destructor 01083 * is called (which will remove all session traces), 01084 * 01085 * @param mixed p an associative array with the description of the interactive 01086 * session control process, obtained by a previous call to one 01087 * of the interactive session creation methods ssh_open_shell() 01088 * or ssh_open_command(). 01089 * 01090 * @return integer the exit status of the remote interactive session. 01091 * 01092 * @access public 01093 * @since Method available since Release 1.0 01094 */ 01095 function ssh_close($p) 01096 { 01097 global $debug; 01098 01099 fwrite($p['std_in'], "\n"); 01100 fwrite($p['std_in'], "logout\n"); 01101 fflush($p['std_in']); 01102 fclose($p['std_in']); fclose($p['std_out']); fclose($p['std_err']); 01103 if ($debug) echo "pipes/files closed\n"; 01104 // XXX we should delete the log files here... 01105 return proc_close($p['process']); 01106 } 01107 01108 # if ($php_version >= 5) 01109 # { 01110 # /** 01111 # * send a signal to a running ssh_open_* process 01112 # */ 01113 # function ssh_signal($p, $signal) 01114 # { 01115 # return proc_terminate($p['process'], $signal); 01116 # } 01117 # /** 01118 # * get info about a running ssh_open_* process 01119 # */ 01120 # function ssh_get_status($p) 01121 # { 01122 # return proc_get_status($p['process']); 01123 # } 01124 # } 01125 01126 /** 01127 * Execute a remote command and keep an unidirectional stream 01128 * contact with it. 01129 * 01130 * This routine mimics 'popen()' but uses ssh to connect to 01131 * a remote host and run the requested command: in other words, 01132 * it opens a pipe to a remotely executed command. This pipe is 01133 * unidirectional, with the communications direction controlled 01134 * by a method parameter. 01135 * 01136 * @see popen() for more details. 01137 * 01138 * @param string command is the command to execute on the remote end 01139 * 01140 * @param string mode specifies the communications direction for the 01141 * pipe: if set to "r" (read), then we will be able to 01142 * collect command output only; if set to "w" (write) 01143 * then we may only send input to the remote command. 01144 * 01145 * @return resource a handle to the unidirectional communication stream, 01146 * similar to that returned by fopen(), or FALSE on 01147 * failure. This handle must be closed with ssh_pclose(). 01148 * 01149 * @access public 01150 * @since Method available since Release 1.0 01151 */ 01152 function ssh_popen($command, $mode) 01153 { 01154 global $debug; 01155 01156 // go 01157 return popen("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $mode); 01158 } 01159 01160 /** 01161 * Close a piped remote execution command control pipe. 01162 * 01163 * This routine accepts as input the handle for the control stream 01164 * of a remote command and closes it, terminating the command as well. 01165 * The handle must be valid and obtained through a call to ssh_popen(). 01166 * 01167 * @param resource f is the file handle associated with the pipe control stream 01168 * 01169 * @return integer the termination status of the command that was run. 01170 * 01171 * @access public 01172 * @since Method available since Release 1.0 01173 */ 01174 function ssh_pclose($f) 01175 { 01176 return pclose($f); 01177 } 01178 01179 //}}} 01180 } 01181 01182 /* 01183 * Local variables: 01184 * tab-width: 4 01185 * c-basic-offset: 4 01186 * c-hanging-comment-ender-p: nil 01187 * End: 01188 */ 01189 01190 ?>