[ Index ] |
PHP Cross Reference of phpSSH |
[Summary view] [Print]
1 <?php 2 3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 4 5 /** 6 * ssh.php -- a class to execute remote commands over SSH 7 * 8 * This file contains the files-based implementation of the SExec 9 * class. This implementation relies on the usage of regular temporary 10 * files to communicate with the remote end, thus avoiding several 11 * drawbacks (mainly deadlocks) associated with pipes. 12 * 13 * The SExec class provides methods to launch and control jobs and 14 * transfer files over SSH. 15 * 16 * PHP versions 4 and 5 17 * 18 * LICENSE: 19 * This library is free software; you can redistribute it and/or 20 * modify it under the terms of the GNU Lesser General Public 21 * License as published by the Free Software Foundation; either 22 * version 2.1 of the License, or (at your option) any later version. 23 * 24 * This library is distributed in the hope that it will be useful, 25 * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 * Lesser General Public License for more details. 28 * 29 * You should have received a copy of the GNU Lesser General Public 30 * License along with this library; if not, write to the Free Software 31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 32 * 33 * @category Net 34 * @package SExec 35 * @author José R. Valverde <jrvalverde@acm.org> 36 * @copyright José R. Valverde <jrvalverde@es.embnet.org> 37 * @license doc/lic/ 38 * @version CVS: $Id: ssh-files.php,v 1.6 2005/05/25 16:02:40 netadmin Exp $ 39 * @link http://savannah.cern.ch/projects/GridGRAMM 40 * @see ssh(1), scp(1) 41 * @since File available since Release 1.0 42 */ 43 44 45 /** 46 * Allow for remote execution of commands using SSH 47 * 48 * The SExec class provides a number of facilities for remote 49 * command execution using SSH. 50 * 51 * The name SExec comes after "rexec" (the remote execution library) 52 * and the "exec" facilities available under PHP. As a matter of fact, 53 * we try to mimic to some extent the execution facilities offered by 54 * PHP over SSH: thus you will find ssh_popen() akin to popen(), etc. 55 * 56 * <b>RATIONALE</b> 57 * 58 * The reason for this class is to allow executing code on a remote 59 * back-end avoiding MITM spoofs in your communications. This allows you 60 * to provide a web front-end (possibly redundant) and call a remote 61 * back-end to execute the job. 62 * 63 * Furthermore, you may have fallback features where if execution 64 * on a remote back-end fails you can restart the command on a fallback 65 * remote host, increasing reliability. 66 * 67 * <b>DEPENDENCIES</b> 68 * 69 * The class relies on an underlying installation of SSH. It has 70 * been tested with OpenSSH on Linux, but should work on other systems 71 * with OpenSSH as well. 72 * 73 * Further, the class in its current inception relies on OpenSSH 74 * version being greater than 3.8. If you have an older SSH, please use 75 * version 1.0 of this class instead. 76 * 77 * <b>DESIGN RATIONALE</b> 78 * 79 * The reasons for the choices taken are simple: we might have 80 * relied on an SSH library (like libSSH) and integrated it with PHP, 81 * but then, any weakness/bug/change on said library would require a 82 * recompilation of the library and PHP. This is a serious inconvenience. 83 * More to that, it would require the maintenance of two simultaneous 84 * SSH installations, viz. OpenSSH and the library, duplicating the work 85 * of tracking security/bug issues. 86 * 87 * By using the underlying SSH commands, we become independent of 88 * them: if anything is discovered, you just have to update your system 89 * SSH, and nothing else. Otherwise you would have a dependency on SSH 90 * to remember, which is always forgotten. This way we avoid getting out 91 * of sync with the system's SSH. 92 * 93 * Better yet: this easies development, making this class a lot 94 * simpler to write, understand, maintain and debug. 95 * 96 * Finally, the dependency on SSH being OpenSSH 3.8 or greater is 97 * due to efficiency reasons. Establishing an SSH connection is costly 98 * in time. If you are going to make many, this would impose a heavy 99 * cost on your scripts. We routinely launch several thousand remote 100 * jobs, and authentication delays soon proved unacceptable. 101 * 102 * OpenSSH 3.8 introduced the possibility of sharing a single SSH 103 * channel between many "connections". This means that only the first 104 * (or master) instance (which will provide the shared channel) needs 105 * to authenticate, hence saving significant time. 106 * 107 * The constructor then creates a master channel, leaves it idle 108 * all the object's lifetime and closses it at the end. This channel 109 * might be used as well, but we felt it wasn't such a big loss to keep 110 * it idle, and furthermore, being the master, we didn't want to risk 111 * getting into any trouble that might close it prematurely. So it stands. 112 * 113 * All other routines (which actually do the work) simply hijack on 114 * the master channel, hence avoiding the costly authentication step (and 115 * executing significantly faster). The only exception are the "COPY" 116 * routines, which can not hijack the master channel and hence must do 117 * authentication every time. 118 * 119 * One more detail: some methods allow for interactive communication 120 * with the remote end. We have simply used a terminal-less connection 121 * for them, using regular files as the intermediate communication channels. 122 * A pipe implementation is also possible, and works as well, but we have 123 * found that dealing with pipes is tricky and error-prone, while using 124 * files is simple and intuitive, so we opted for using files. 125 * 126 * The difference has to do with the way you communicate with the 127 * other end: using pipes you may block on read and/or write, and so 128 * may the other end. Since there may occur errors in the process, that 129 * implies that getting into a deadlock is trivial. Just picture this 130 * scenarios: 131 * 132 * You send a command -> the remote ends starts the command and 133 * prompts for input on stdout, hangs reding on stdin -> you read the 134 * prompt and send the input -> the remote end wakes and processes it. 135 * 136 * You send a command -> the remote end fails, logs an error on 137 * stderr, gets back the system prompt and hangs on reading stdin -> you 138 * notice the prompt and read stderr... since you can't predict the 139 * length of the error message you must empty the pipe... and when doing it 140 * you hang after reading the last char... -> deadlock 141 * 142 * You send a command -> the remote end fails, logs an error on stderr, 143 * gets back the system prompt and hangs on reading stdin -> you don't read 144 * stderr to avoid hanging, so submit a new command... this goes on and on 145 * until the remote side's stderr buffer fills, then the remote side locks 146 * waiting for you to read stderr -> you can't know it hang, so you try 147 * to submit a new command, and hang on writing waiting for the other end 148 * to read your command -> deadlock 149 * 150 * More scenarios are possible, and since you (or the other side) 151 * can't predict what's going to happen, it is very tricy to avoid them. 152 * 153 * Now, using files, you don't have that problem: whenever you reach 154 * the current end-of-file, you get an EOF, no need to hang waiting for 155 * the other side to fill it in with data. The other side doesn't hang on 156 * writing unless your disk space fills up. It's a lot simpler. 157 * 158 * Your problem with files is continuing reads after new data becomes 159 * available: the safest way is to call flush() before reading and seeking 160 * to the last position read to avoid having to re-read everything (which 161 * implies that after finishing reading you must ftell() your position. 162 * 163 * See the included test script for examples. 164 * 165 * <b>CUSTOMIZATION</b> 166 * 167 * You <i>must</i> state to the class where your SSH executables (ssh and 168 * scp) are located. This allows you to have them placed anywhere, but 169 * also implies the responsability of using full pathnames to reduce 170 * hacking dangers. It also allows you to use/test a new SSH implementation 171 * installed in a non-standard place before switching to it, or even to 172 * keep various SSH installations on the system (e.g. if the system's 173 * SSH is not up-to-date, you may install one on your home and use it). 174 * 175 * You may also indicate where to store temporary files. This must 176 * be a directory followed by a prefix to use when creating a temporal 177 * directory. The parent directory must be writeable by the user who runs 178 * the class (usually it will be run by apache, www or some such). Most commonly 179 * the parent directory will be /tmp or $DocumentRoot/tmp or something similar. 180 * 181 * The directory+prefix you state will be used to create a unique 182 * temporary work directory for each object instantiated. Examples of 183 * a valid specifications are "/tmp/phpSsh-" or "/tmp/". When the object is 184 * instantiated, a random string will be appended to this value to create 185 * the actual temporary directory name. 186 * 187 * The reason for allowing specifying a prefix is so that debugging 188 * may be easier by facilitating identification of temporaries generated 189 * by this class. 190 * 191 * <b>DEBUGGING</b> 192 * 193 * The class comes with extensive debugging aids. To enable them, 194 * just set a global variable called $debug to TRUE. This will output 195 * abundant debugging information and leave copies of communication log 196 * files for your reference. 197 * 198 * Additionally, there is a sample demo script that shows how to 199 * use this class and may help you debug it. This script is included 200 * in the distribution (or should be) as 'ssh_debug.php'. See notes 201 * and comments within it for more details. 202 * 203 * @category Net 204 * @package SExec 205 * @author José R. Valverde <jrvalverde@acm.org> 206 * @copyright José R. Valverde <jrvalverde@es.embnet.org> 207 * @license doc/lic/ 208 * @version Release: @package_version@ 209 * @link http://savannah.cern.ch/projects/GridGRAMM 210 * @see ssh(1), scp(1) 211 * @since File available since Release 1.0 212 213 */ 214 class SExec { 215 216 // {{{ properties 217 218 /** 219 * The current version of the class 220 * 221 * @var string 222 * @access public 223 */ 224 var $version="2.0"; 225 226 /** 227 * remote endpoint ([user@]host[:port]) 228 * 229 * @var string 230 * @access private 231 */ 232 var $remote; 233 234 /** 235 * remote password 236 * 237 * @var string 238 * @access private 239 */ 240 var $password; 241 242 /** 243 * location of ssh program 244 * 245 * @var string 246 * @access private 247 */ 248 var $ssh = "/usr/bin/ssh"; 249 250 /** 251 * location of scp program 252 * 253 * @var string 254 * @access private 255 */ 256 var $scp = "/usr/bin/scp"; 257 258 /** 259 * tmp. dir prefix specification 260 * 261 * @var string 262 * @access private 263 */ 264 var $workdir = "/tmp/phpSsh"; 265 266 /** 267 * name of multiplexing socket 268 * 269 * @var string 270 * @access private 271 */ 272 var $mplex_socket = "/tmp/ssh.mplex"; 273 274 /** 275 * handle to process controlling the master channel 276 * 277 * @var string 278 * @access private 279 */ 280 var $master; 281 282 /** 283 * stdin of process controlling the master channel 284 * 285 * @var string 286 * @access private 287 */ 288 var $master_input; 289 290 //}}} 291 292 //{{{ instantiation 293 294 /** Class constructor. 295 * 296 * Generate a new instance of a remote execution environment. 297 * The object returned allows you to invoke commands to be executed 298 * remotely in a way similar to PHP exec commands (popen, proc_open...) 299 * over SSH (so that your communications can be secure). 300 * 301 * You may specify a remote endpoint and a password, a remote endpoint 302 * alone, or nothing at all. 303 * 304 * If you provide a remote endpoint and password they are used to drive 305 * the communications and execute your commands. 306 * 307 * If no password is provided, then a default of "xxyzzy" (the canonical 308 * computer magic word) is used. Unless this is your password (not 309 * recommended), this means that the default password is useless unless 310 * you are working in a trusted environment where it is not needed and 311 * ignored. That may be the case if you enable trusting mechanisms with 312 * .shosts/.rhosts or passphraseless RSA/DSA authentication. Not that 313 * we endorse them either, but in these cases any password provided will 314 * be ignored and it doesn't make sense to provide a real one: xxyzzy 315 * can do as well as any other. 316 * 317 * If no password and no remote end is provided, then "localhost" is 318 * used as the remote end, assuming no password is required (as described 319 * above). This is only useful if localhost is trusted, and you have reasons 320 * to use SSH internally... Some people does. 321 * 322 * Regarding the remote end specification, it can be any valid single-string 323 * SSH remote end description: the basic format is 324 * 325 * [username@]remote.host[:port] 326 * 327 * - "username" is the remote user name to log in as. It is optional. If provided, 328 * it must be separated from the remote host by an "@" sign. If it is not 329 * provided, the remote username is assumed to be the same as the current local 330 * one. 331 * 332 * - "remote.host" is a valid host specification, either a numeric IP address 333 * or a valid host name (which may require a full name or not depending on 334 * your settings). 335 * 336 * - "port" is the remote port where SSH is listening and which we want to 337 * connect to. It is optional, and if provided, must follow the remote host 338 * specification separated from it by a colon ":". If not provided, the 339 * default port (22) is used. 340 * 341 * Examples of remote host specifications are "user@host.example.net:22", 342 * "someone@host:22", "host.example.net:22", "host:22", 343 * "somebody@host.example.net", "user@host", "host.example.net", "host". 344 * 345 * Here is an example of how to use this constructor: 346 * <code> 347 * require_once 'ssh.php'; 348 * 349 * $remote = "jruser@example.com"; 350 * $password = "PASSWORD"; 351 * 352 * $rmt = new SExec($remote, $password); 353 * if (! $rmt) 354 * echo "Couldn't connect to $remote\n"; 355 * </code> 356 * 357 * @param string remote The remote end to run the command, in 358 * the form 'user@host:port' (you may 359 * omit the 'user@' or ':port' parts 360 * if the default values [i.e. same user 361 * or standard port] are OK). 362 * 363 * @param string password The remote password. Note that if direct 364 * RSA/DSA/.shosts/.rhosts login is enabled 365 * then the password will be ignored as 366 * SSH should not run the ASKPASS command). 367 * 368 * @return SExec|false a new connection object with the remote end or 369 * FALSE if the connection could not be established. 370 * 371 * @access public 372 * @since Method available since Release 1.0 373 */ 374 function SExec($remote="localhost", $password="xxyzzy") 375 { 376 global $debug; 377 378 if ($debug) echo "\nCreating a new SExec\n"; 379 $this->remote = $remote; 380 $this->password = "$password"; 381 umask(0077); 382 /* DESIGN 383 * In order to increase efficiency, we will create a master channel 384 * on class instantiation. The master channel should be closed by a 385 * corresponding class destructor! 386 * 387 * Creating a master channel has the advantage that subsequent SSH 388 * connections will use it and avoid repeating the slow authentication 389 * process: in other words, they will go much, much faster. 390 */ 391 392 // first we must generate a unique UNIX socket address or we'll fail 393 // We use a tricky trick: generate two random numbers and use them; 394 // this is tricky since there might be a problem, but with very low 395 // probability. BUT IT MAY STILL FAIL: there's a race condition between 396 // the end of the while and the subsequent if. 397 do { 398 mt_srand((double)microtime()*1000000 ) . 399 $this->workdir = "/tmp/phpSsh-" . mt_rand() .".". mt_rand(); 400 if ($debug) echo "\nSExec: trying $this->workdir/ ..."; 401 // CAUTION: this is potentially an endless loop (albeit with very 402 // low probability) if every possible file did exist. 403 } 404 while (file_exists($this->workdir)); 405 if (mkdir($this->workdir) == FALSE) { 406 // we can't continue. How can we cancel this? 407 // try these and then check what happens 408 unset($this); 409 $this = NULL; 410 return FALSE; 411 } 412 else 413 if ($debug) echo " OK\n"; 414 // Now we have a place to put the socket... Mmm... 415 // Come to think of it, we have a place to put ANY temporary 416 // for the class... 417 // XXX Maybe we can change everywhere else to use this? 418 $this->mplex_socket = $this->workdir."/mplex_socket"; 419 420 // Finally we can call SSH -M 421 // Create SSH_ASKPASS script to provide the password 422 $tmpfname = tempnam($this->workdir, 'SExec-'); 423 chmod($tmpfname, 0700); 424 putenv("DISPLAY=none:0."); 425 putenv("SSH_ASKPASS=$tmpfname"); 426 $fp = fopen($tmpfname, "w"); 427 fputs($fp, "#!/bin/sh\necho $this->password\n"); 428 if (!$debug) 429 fputs($fp, "/bin/touch $tmpfname.called\n"); 430 else 431 fputs($fp, "/bin/rm -f $tmpfname\n"); 432 fclose($fp); 433 434 // OK, we are ready. Now let's open a master shell 435 $child_stdout = tempnam($this->workdir, "open_sh-O-"); 436 $child_stderr = tempnam($this->workdir, "open_sh-E-"); 437 $descriptorspec = array( 438 0 => array("pipe", "r"), // connect child's stdin to the read end of a pipe 439 1 => array("file", $child_stdout, "a"), // connect child's stdout to the write end of a pipe 440 2 => array("file", $child_stderr, "a") // stderr is a pipe to read from 441 ); 442 443 if ($debug) echo "$this->ssh -x -t -t ". 444 "-M -S $this->mplex_socket " . 445 "$this->remote\n"; 446 $this->master = proc_open("$this->ssh -x -t -t ". 447 "-M -S $this->mplex_socket " . 448 "$this->remote", 449 $descriptorspec, 450 $pipes); 451 if ((! is_resource($this->master)) || ($this->master == FALSE)) { 452 putenv("SSH_ASKPASS=dummy"); 453 unset($this); 454 $this = NULL; 455 return FALSE; 456 } 457 // we do not need to worry about the output log files, just the 458 // input pipe for logout 459 $this->master_input = $pipes[0]; 460 461 // Before going ahead, we need to ensure the control shell 462 // has started: wait for the socket to become available 463 // note: there should be a timeout here to avoid a possibly 464 // infinite loop XXX 465 do { 466 if ($debug) echo "waiting 0.1 sec\n"; 467 usleep(100000); // wait 0.1 seconds 468 } while (! file_exists($this->mplex_socket)); 469 470 // and now we must register a destructor for the class 471 // that will close the connection. 472 //register_shutdown_function($this->destruct()); 473 474 return $this; 475 } 476 477 /** Class destructor 478 * 479 * Destroy all working processes and data streams and structures 480 * used by an instance of this class. 481 * 482 * This method will send a termination message to the other end 483 * of the master channel, close the control stream of the master 484 * channel and terminate its controlling process, finally unsetting 485 * the object and setting the object handle to NULL. 486 * 487 * If a global $debug is not set to TRUE, then it will also remove 488 * all communication traces of this object: i.e. all log files for 489 * interactive and master sessions, communications socket, etc... 490 * 491 * If global $debug is set to TRUE, then a copy of all log files 492 * created during the lifetime of the object will be left on a 493 * temporary directory for your perusal and reference. 494 * 495 * @return integer exit status of the master channel control process. 496 * 497 * @access public 498 * @since Method available since Release 1.0 499 */ 500 function destruct() 501 { 502 global $debug; 503 504 if ($debug) echo "\nDestroying SExec master\n"; 505 if ($debug) print_r($this); 506 if ($debug) echo "sending logout\n"; 507 // log out master process 508 fputs($this->master_input, "\n\nlogout\n\n"); 509 // close master stdin 510 fclose($this->master_input); 511 // close master process 512 $ret = proc_close($this->master); 513 // remove temporaries 514 if (! $debug) system("/bin/rm -rf $this->workdir"); 515 // utterly destroy this instance 516 unset($this); 517 $this = NULL; 518 return $ret; 519 } 520 521 //}}} 522 523 //{{{ methods 524 /** 525 * Copy a file or directory from one source to a destination 526 * 527 * This function copies source to dest, where one of them is a 528 * local filespec and the other a remote filespec of the form 529 * [user@]host:path 530 * 531 * If the original source is a directory, it will be copied 532 * recursively to destination (hence easing file transfers). 533 * 534 * The function returns TRUE on success or FALSE on failure. 535 * 536 * <b>EFFICIENCY NOTICE:</b> 537 * 538 * The copy routines use 'scp' to do their actual work. Since 539 * scp seems to be unable to hitchhike on the master channel, 540 * we must do authentication for each copy operation (subroutine 541 * call). These routines are hence a lot more time-expensive 542 * than all the other ones. 543 * 544 * You may want to consider whether you can group several 545 * copies into one single call to reduce authentication 546 * overheads. 547 * 548 * @note DEPRECATED (inconsistent with the class) 549 * 550 * @see scp(1) 551 * 552 * @param string origin The origin path, of the form 553 * [user@][host][:port]path 554 * You may omit the optional sections if 555 * the default values (local username, local 556 * host, standard SSH port) are OK 557 * 558 * @param string destination The destination path, of the form 559 * [user@][host][:port:]path 560 * You may omit the optional sections if 561 * the default values (local username, local 562 * host, standard SSH port) are OK 563 * 564 * @param string password The password to use to connect to the remote 565 * end of the copy (be it the origin or the 566 * destination, it's all the same). If connection 567 * is automatic by some means (.shosts or RSA/DSA 568 * authentication) then it should be ignored and 569 * any password should do. 570 * 571 * @return bool TRUE if all went well, or FALSE on failure. 572 * 573 * @access public 574 * @since Method available since Release 1.0 575 * @deprecated Method deprecated as of Release 2.1 576 */ 577 function ssh_copy($origin, $destination, $password) 578 { 579 global $debug; 580 581 umask(0077); 582 $tmpfname = tempnam($this->workdir, "copy-"); 583 chmod($tmpfname, 0700); 584 putenv("DISPLAY=none:0."); 585 putenv("SSH_ASKPASS=$tmpfname"); 586 $fp = fopen($tmpfname, "w"); 587 fputs($fp, "#!/bin/sh\necho $password\n"); 588 if (! $debug) 589 fputs($fp, "/bin/touch $tmpfname.called\n"); 590 else 591 fputs($fp, "/bin/rm $tmpfname\n"); 592 fclose($fp); 593 exec("$this->scp -pqrC $origin $destination", $out, $status); 594 if ($status == 0) 595 return TRUE; 596 else 597 return FALSE; 598 } 599 600 601 /** 602 * Copy a file or directory from a local source to a remote destination 603 * 604 * This function copies source to dest, where first of them is a 605 * local filespec and then comes a remote filespec as a normal 606 * system path. 607 * 608 * Both, local and remote paths may be absolute or relative. 609 * 610 * If the original source is a directory, it will be copied 611 * recursively to destination (hence easing file transfers). 612 * 613 * The function returns TRUE on success or FALSE on failure. 614 * 615 * <b>EFFICIENCY NOTICE:</b> 616 * 617 * The copy routines use 'scp' to do their actual work. Since 618 * scp seems to be unable to hitchhike on the master channel, 619 * we must do authetication for each copy operation (subroutine 620 * call). These routines are hence a lot more time-expensive 621 * than all the other ones. 622 * 623 * You may want to consider whether you can group several 624 * copies into one single call to reduce authentication 625 * overheads. 626 * 627 * @see scp(1) 628 * 629 * @param string localpath The origin local path, either absolute or 630 * relative to the current working directory. 631 * If it denotes a directory, the copy will 632 * be recursive. 633 * 634 * @param string remotepath The destination path, either 635 * absolute or relative to the login home. 636 * 637 * @return bool TRUE if all went well, or FALSE on failure. 638 * 639 * @access public 640 * @since Method available since Release 2.1 641 */ 642 function ssh_copy_to($localpath, $remotepath) 643 { 644 global $debug; 645 646 umask(0077); 647 $tmpfname = tempnam($this->workdir, "copy-to-"); 648 chmod($tmpfname, 0700); 649 putenv("DISPLAY=none:0."); 650 putenv("SSH_ASKPASS=$tmpfname"); 651 $fp = fopen($tmpfname, "w"); 652 fputs($fp, "#!/bin/sh\necho $this->password\n"); 653 if (! $debug) 654 fputs($fp, "/bin/touch $tmpfname.called\n"); 655 else 656 fputs($fp, "/bin/rm $tmpfname\n"); 657 fclose($fp); 658 exec("$this->scp -pqrC $localpath $this->remote:$remotepath", $out, $status); 659 if ($status == 0) 660 return TRUE; 661 else 662 return FALSE; 663 } 664 665 /** 666 * Copy a file or directory from a remote source to a local destination 667 * 668 * This function copies source to dest, where first of them is a 669 * remote filespec and then comes a local filespec, both specified 670 * as normal system paths. 671 * 672 * Both, local and remote paths may be absolute or relative. 673 * 674 * If the original source is a directory, it will be copied 675 * recursively to destination (hence easing file transfers). 676 * 677 * The function returns TRUE on success or FALSE on failure. 678 * 679 * EFFICIENCY NOTICE: 680 * 681 * The copy routines use 'scp' to do their actual work. Since 682 * scp seems to be unable to hitchhike on the master channel, 683 * we must do authetication for each copy operation (subroutine 684 * call). These routines are hence a lot more time-expensive 685 * than all the other ones. 686 * 687 * You may want to consider whether you can group several 688 * copies into one single call to reduce authentication 689 * overheads. 690 * 691 * @see scp(1) 692 * 693 * @param string remotepath The origin remote path, either absolute or 694 * relative to the login home. If it denotes a 695 * directory, the copy will be recursive. 696 * 697 * @param string localpath The local destination path, either 698 * absolute or relative to the current working 699 * directory. 700 * 701 * @return bool TRUE if all went well, or FALSE on failure. 702 * 703 * @access public 704 * @since Method available since Release 1.0 705 */ 706 function ssh_copy_from($remotepath, $localpath) 707 { 708 global $debug; 709 710 umask(0077); 711 $tmpfname = tempnam($this->workdir, "copy-from-"); 712 chmod($tmpfname, 0700); 713 putenv("DISPLAY=none:0."); 714 putenv("SSH_ASKPASS=$tmpfname"); 715 $fp = fopen($tmpfname, "w"); 716 fputs($fp, "#!/bin/sh\necho $this->password\n"); 717 if (! $debug) 718 fputs($fp, "/bin/touch $tmpfname.called\n"); 719 else 720 fputs($fp, "/bin/rm $tmpfname\n"); 721 fclose($fp); 722 exec("$this->scp -pqrC $this->remote:$remotepath $localpath", $out, $status); 723 if ($status == 0) 724 return TRUE; 725 else 726 return FALSE; 727 } 728 729 /** 730 * Execute a single command remotely 731 * 732 * Execute a single command remotely using ssh and 733 * display its output, optionally returning its exit 734 * status (like passthru) 735 * 736 * This function is intended to be used as a one-time 737 * all-at-once non-interactive execution mechanism which 738 * will run the command remotely and display its output. 739 * 740 * If you try to issue an interactive command using this 741 * function, all you will get is unneccessary trouble. So 742 * don't! 743 * 744 * This might be done as well using a pipe on /tmp and 745 * making the command 'cat' the pipe: when ssh runs, it 746 * runs the command 'cat' on the pipe and hangs on read. 747 * Then we just need a thread to open the pipe, put the 748 * password and close the pipe. 749 * 750 * This other way the password is never wirtten down. 751 * But, OTOH, the file life is so ephemeral that most 752 * of the time it will only exist in the internal system 753 * cache, so this approach is not that bad either. 754 * 755 * @see passthru() 756 * 757 * @param string command The command to execute on the remote end 758 * NOTE: if you want to use redirection, the 759 * entire remote command line should be 760 * enclosed in additional quotes! 761 * @param integer status Optional, this will hold the termination 762 * status of SSH after invocation, which 763 * should be the exit status of the remote 764 * command or 255 if an error occurred 765 * @return void 766 * 767 * @access public 768 * @since Method available since Release 1.0 769 */ 770 function ssh_passthru($command, &$status) 771 { 772 global $debug; 773 774 if ($debug) echo "status = $status\n"; 775 // go 776 if (isset($status)) { 777 if ($debug) echo "st: $this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"\n"; 778 passthru("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $status); 779 } 780 else { 781 if ($debug) echo "~st: $this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"\n"; 782 passthru("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\""); 783 } 784 } 785 786 787 /** 788 * Execute a remote command using SSH 789 * 790 * This function sort of mimics rexec(3) using SSH as the transport 791 * protocol. 792 * 793 * The function returns the exit status of the remote command, and 794 * appends the remote job output to an optional argument. 795 * 796 * This function is intended to be used as a one-time 797 * all-at-once non-interactive execution mechanism which 798 * will run the command remotely and return its output. 799 * 800 * If you try to issue an interactive command using this 801 * function, all you will get is unneccessary trouble. So 802 * don't! 803 * 804 * @param string command The command to execute on the remote end 805 * NOTE: if you want to use redirection, the 806 * entire remote command line should be 807 * enclosed in additional quotes! 808 * @param string output Optional, the collated (stdout+stderr) output 809 * of the remote command. 810 * @return integer status will hold the termination 811 * status of SSH after invocation, which 812 * should be the exit status of the remote 813 * command or 255 if an error occurred 814 * 815 * @access public 816 * @since Method available since Release 1.0 817 */ 818 function ssh_exec($command, &$out) 819 { 820 global $debug; 821 822 umask(0077); 823 $tmpfname = tempnam($this->workdir, 'exec'); 824 chmod($tmpfname, 0700); 825 if ($debug) echo $tmpfname . "\n"; 826 827 exec("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $out, $retval); 828 return $retval; 829 830 } 831 832 /** 833 * Open an SSH connection to a remote site with a shell to run 834 * interactive commands 835 * 836 * Connects to a remote host and opens an interactive shell session 837 * with NO controlling terminal. 838 * 839 * This routine creates communication streams with the remote shell, 840 * and stores all output (standard and error) of the connection into 841 * two separate local log files (one for stdout and one for stderr). 842 * 843 * Returns a process_control array which contains the process resource 844 * ID and an the standard file descriptors which the caller may use to 845 * interact with the remote shell. 846 * 847 * The process control array contains: 848 * 849 * 'process' -- the process resource for the newly created connection 850 * 851 * 'std_in' -- handle to the standard input of the new connection 852 * 853 * 'std_out' -- handle to standard output of the new connection 854 * 855 * 'std_err' -- handle to standard error of the new connection 856 * 857 * 'stdout_file' -- actual filename of the local log file for the 858 * new connection standard output 859 * 860 * 'stderr_file' -- actual filename of the local log file for the 861 * new connection standard error 862 * 863 * @return mixed a process control associative array. 864 * 865 * @access public 866 * @since Method available since Release 1.0 867 */ 868 function ssh_open_shell() 869 { 870 global $debug; 871 872 // Open a child process with the 'proc_open' function. 873 // 874 // Some tricks: we must open the connection using '-x' to disable 875 // X11 forwarding, and use '-t -t' to avoid SSH generating an error 876 // because we are not connected to any terminal. 877 // 878 // NOTE: if the web server is trusted remotely (i.e. it's SSH public 879 // key is accepted in ~user@host:.ssh/authorized_keys) then any 880 // password will do. 881 882 // Prepare I/O 883 umask(0077); 884 if ($debug) { 885 $child_stdout = tempnam($this->workdir, "open_sh-".getmypid()."-O-"); 886 $child_stderr = tempnam($this->workdir, "open_sh-".getmypid()."-E-"); 887 } else { 888 $child_stdout = tempnam($this->workdir, "open_sh-"); 889 $child_stderr = tempnam($this->workdir, "open_sh-"); 890 } 891 $descriptorspec = array( 892 0 => array("pipe", "r"), // connect child's stdin to the read end of a pipe 893 1 => array("file", $child_stdout, "a"), // connect child's stdout to the write end of a pipe 894 2 => array("file", $child_stderr, "a") // stderr is a pipe to read from 895 ); 896 if ($debug) echo "$this->ssh -x -t -t -S $this->mplex_socket $this->remote<br />\n"; 897 $process = proc_open("$this->ssh -x -t -t -S $this->mplex_socket $this->remote", 898 $descriptorspec, 899 $pipes); 900 901 // check status 902 if (!is_resource($process)) 903 { 904 letal("SSH::connect", "cannot connect to the remote host"); 905 return; 906 } 907 if ($debug) echo "proc_open done<br />\n"; 908 909 // $pipes now looks like this: 910 // 0 => writeable handle connected to child stdin 911 912 // Open child's stdin and stdout 913 $pipes[1] = fopen($child_stdout, "r"); 914 $pipes[2] = fopen($child_stderr, "r"); 915 916 // Should we leave this to the user? 917 // set to non-blocking and avoid having to call fflush 918 //stream_set_blocking($pipes[0], FALSE); 919 //stream_set_blocking($pipes[1], FALSE); 920 //stream_set_blocking($pipes[2], FALSE); 921 stream_set_write_buffer($pipes[0], 0); 922 stream_set_write_buffer($pipes[1], 0); 923 stream_set_write_buffer($pipes[2], 0); 924 925 // We now have a connection to the remote SSH 926 // Server which we may use to send commands/receive output 927 $p = array('process' => $process 928 ,'std_in' => $pipes[0] 929 ,'std_out' => $pipes[1] 930 ,'std_err' => $pipes[2] 931 ,'stdout_file' => $child_stdout 932 ,'stderr_file' => $child_stderr 933 ); 934 if ($debug) { 935 echo "process descriptor array is \n"; 936 print_r($p); 937 } 938 return $p; 939 } 940 941 /** 942 * Open an SSH connection to run an interactive command on a remote 943 * site 944 * 945 * Connects to a remote host and runs an interactive command 946 * with NO controlling terminal. 947 * 948 * This routine creates communication streams with the remote shell, 949 * and stores all output (standard and error) of the connection into 950 * two separate local log files (one for stdout and one for stderr). 951 * 952 * Returns a process_control array which contains the process resource 953 * ID and an the standard file descriptors which the caller may use to 954 * interact with the remote shell. 955 * 956 * The process control array contains: 957 * 958 * 'process' -- the process resource for the newly created connection 959 * 960 * 'std_in' -- handle to the standard input of the new connection 961 * 962 * 'std_out' -- handle to standard output of the new connection 963 * 964 * 'std_err' -- handle to standard error of the new connection 965 * 966 * 'stdout_file' -- actual filename of the local log file for the 967 * new connection standard output 968 * 969 * 'stderr_file' -- actual filename of the local log file for the 970 * new connection standard error 971 * 972 * @param string command to be executed interactively on the remote end 973 * 974 * @return mixed a process control associative array. 975 * 976 * @access public 977 * @since Method available since Release 1.0 978 */ 979 function ssh_open_command($command) 980 { 981 global $debug; 982 983 // Open a child process with the 'proc_open' function. 984 // 985 // Some tricks: we must open the connection using '-x' to disable 986 // X11 forwarding, and use '-t -t' to avoid SSH generating an error 987 // because we are not connected to any terminal. 988 // 989 // NOTE: if the web server is trusted remotely (i.e. it's SSH public 990 // key is accepted in ~user@host:.ssh/authorized_keys) then any 991 // password will do. 992 993 // Prepare I/O 994 umask(0077); 995 if ($debug) { 996 $child_stdout = tempnam($this->workdir, "open_cmd-".getmypid()."-1-"); 997 $child_stderr = tempnam($this->workdir, "open_cmd-".getmypid()."-2-"); 998 } else { 999 $child_stdout = tempnam($this->workdir, "open_cmd-"); 1000 $child_stderr = tempnam($this->workdir, "open_cmd-"); 1001 } 1002 $descriptorspec = array( 1003 0 => array("pipe", "r"), // connect child's stdin to the read end of a pipe 1004 #1 => array("pipe", "a"), // connect child's stdout to the write end of a pipe 1005 #2 => array("pipe", "a") // stderr is a pipe to read from 1006 1 => array("file", $child_stdout, "a"), 1007 2 => array("file", $child_stderr, "a") 1008 ); 1009 1010 if ($debug) echo "$this->ssh -x -t -t -S $this->mplex_socket $this->remote $command<br />\n"; 1011 $process = proc_open("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", 1012 $descriptorspec, 1013 $pipes); 1014 1015 // check status 1016 if (!is_resource($process)) 1017 { 1018 letal("SSH::connect", "cannot connect to the remote host"); 1019 return; 1020 } 1021 if ($debug) echo "proc_open done<br />\n"; 1022 1023 // $pipes now looks like this: 1024 // 0 => writeable handle connected to child stdin 1025 1026 // Open child's stdin and stdout 1027 $pipes[1] = fopen($child_stdout, "r"); 1028 $pipes[2] = fopen($child_stderr, "r"); 1029 1030 // Should we leave this to the user? 1031 // set to non-blocking and avoid having to call fflush 1032 #stream_set_blocking($pipes[0], FALSE); 1033 #stream_set_blocking($pipes[1], FALSE); 1034 #stream_set_blocking($pipes[2], FALSE); 1035 stream_set_write_buffer($pipes[0], 0); 1036 stream_set_write_buffer($pipes[1], 0); 1037 stream_set_write_buffer($pipes[2], 0); 1038 1039 // We now have a connection to the remote SSH 1040 // Server which we may use to send commands/receive output 1041 $p = array('process' => $process 1042 ,'std_in' => $pipes[0] 1043 ,'std_out' => $pipes[1] 1044 ,'std_err' => $pipes[2] 1045 ,'stdout_file' => $child_stdout 1046 ,'stderr_file' => $child_stderr 1047 ); 1048 if ($debug) { 1049 echo "process descriptor array is \n"; 1050 print_r($p); 1051 } 1052 return $p; 1053 } 1054 1055 /** 1056 * Get output until we reach a given regular expression 1057 * 1058 * @note EXPERIMENTAL, requires more thought and experience. 1059 */ 1060 function ssh_out_expect($p, $expr="^# ") 1061 { 1062 do { 1063 flush(); 1064 fseek($p["std_out"], $last); 1065 $line = fgets($p["std_out"], 1024); 1066 #echo ">> ".$line; 1067 $last = ftell($p["std_out"]); 1068 } while ((! feof($p["std_out"]) ) || (! ereg($expr, $line))); 1069 } 1070 1071 /** 1072 * Close an SSH interactive session 1073 * 1074 * This method terminates a previously open interactive remote 1075 * session. It will send a termination notification to the 1076 * remote end, close the connection with control and communication 1077 * streams, and terminate the local control process. 1078 * 1079 * Copies of the log files that contain the output and error 1080 * of the communication are left out for later reference and 1081 * local peruse. If you don't need them any longer, you may 1082 * delete them or just leave them around until the class destructor 1083 * is called (which will remove all session traces), 1084 * 1085 * @param mixed p an associative array with the description of the interactive 1086 * session control process, obtained by a previous call to one 1087 * of the interactive session creation methods ssh_open_shell() 1088 * or ssh_open_command(). 1089 * 1090 * @return integer the exit status of the remote interactive session. 1091 * 1092 * @access public 1093 * @since Method available since Release 1.0 1094 */ 1095 function ssh_close($p) 1096 { 1097 global $debug; 1098 1099 fwrite($p['std_in'], "\n"); 1100 fwrite($p['std_in'], "logout\n"); 1101 fflush($p['std_in']); 1102 fclose($p['std_in']); fclose($p['std_out']); fclose($p['std_err']); 1103 if ($debug) echo "pipes/files closed\n"; 1104 // XXX we should delete the log files here... 1105 return proc_close($p['process']); 1106 } 1107 1108 # if ($php_version >= 5) 1109 # { 1110 # /** 1111 # * send a signal to a running ssh_open_* process 1112 # */ 1113 # function ssh_signal($p, $signal) 1114 # { 1115 # return proc_terminate($p['process'], $signal); 1116 # } 1117 # /** 1118 # * get info about a running ssh_open_* process 1119 # */ 1120 # function ssh_get_status($p) 1121 # { 1122 # return proc_get_status($p['process']); 1123 # } 1124 # } 1125 1126 /** 1127 * Execute a remote command and keep an unidirectional stream 1128 * contact with it. 1129 * 1130 * This routine mimics 'popen()' but uses ssh to connect to 1131 * a remote host and run the requested command: in other words, 1132 * it opens a pipe to a remotely executed command. This pipe is 1133 * unidirectional, with the communications direction controlled 1134 * by a method parameter. 1135 * 1136 * @see popen() for more details. 1137 * 1138 * @param string command is the command to execute on the remote end 1139 * 1140 * @param string mode specifies the communications direction for the 1141 * pipe: if set to "r" (read), then we will be able to 1142 * collect command output only; if set to "w" (write) 1143 * then we may only send input to the remote command. 1144 * 1145 * @return resource a handle to the unidirectional communication stream, 1146 * similar to that returned by fopen(), or FALSE on 1147 * failure. This handle must be closed with ssh_pclose(). 1148 * 1149 * @access public 1150 * @since Method available since Release 1.0 1151 */ 1152 function ssh_popen($command, $mode) 1153 { 1154 global $debug; 1155 1156 // go 1157 return popen("$this->ssh -x -t -t -S $this->mplex_socket $this->remote \"$command\"", $mode); 1158 } 1159 1160 /** 1161 * Close a piped remote execution command control pipe. 1162 * 1163 * This routine accepts as input the handle for the control stream 1164 * of a remote command and closes it, terminating the command as well. 1165 * The handle must be valid and obtained through a call to ssh_popen(). 1166 * 1167 * @param resource f is the file handle associated with the pipe control stream 1168 * 1169 * @return integer the termination status of the command that was run. 1170 * 1171 * @access public 1172 * @since Method available since Release 1.0 1173 */ 1174 function ssh_pclose($f) 1175 { 1176 return pclose($f); 1177 } 1178 1179 //}}} 1180 } 1181 1182 /* 1183 * Local variables: 1184 * tab-width: 4 1185 * c-basic-offset: 4 1186 * c-hanging-comment-ender-p: nil 1187 * End: 1188 */ 1189 1190 ?>
title
Description
Body
title
Description
Body
title
Body
Generated: Wed May 25 19:30:43 2005 | Cross-referenced by PHPXref 0.4.1 |