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