[ Index ]

PHP Cross Reference of phpSSH

/ -> ssh-files.php (summary)

ssh.php -- a class to execute remote commands over SSH This file contains the files-based implementation of the SExec class. This implementation relies on the usage of regular temporary files to communicate with the remote end, thus avoiding several drawbacks (mainly deadlocks) associated with pipes.

Author: José R. Valverde
File Size: 1190 lines (43 kb)
Included or required: 0 times
Referenced: 0 times
Includes or requires: 1 file
 ssh.php

Defines 1 class

sexec:: (13 methods):
  sexec()
  destruct()
  ssh_copy()
  ssh_copy_to()
  ssh_copy_from()
  ssh_passthru()
  ssh_exec()
  ssh_open_shell()
  ssh_open_command()
  ssh_out_expect()
  ssh_close()
  ssh_popen()
  ssh_pclose()


Class: sexec  - X-Ref

Allow for remote execution of commands using SSH

The SExec class provides a number of facilities for remote
command execution using SSH.

The name SExec comes after "rexec" (the remote execution library)
and the "exec" facilities available under PHP. As a matter of fact,
we try to mimic to some extent the execution facilities offered by
PHP over SSH: thus you will find ssh_popen() akin to popen(), etc.

<b>RATIONALE</b>

The reason for this class is to allow executing code on a remote
back-end avoiding MITM spoofs in your communications. This allows you
to provide a web front-end (possibly redundant) and call a remote
back-end to execute the job.

Furthermore, you may have fallback features where if execution
on a remote back-end fails you can restart the command on a fallback
remote host, increasing reliability.

<b>DEPENDENCIES</b>

The class relies on an underlying installation of SSH. It has
been tested with OpenSSH on Linux, but should work on other systems
with OpenSSH as well.

Further, the class in its current inception relies on OpenSSH
version being greater than 3.8. If you have an older SSH, please use
version 1.0 of this class instead.

<b>DESIGN RATIONALE</b>

The reasons for the choices taken are simple: we might have
relied on an SSH library (like libSSH) and integrated it with PHP,
but then, any weakness/bug/change on said library would require a
recompilation of the library and PHP. This is a serious inconvenience.
More to that, it would require the maintenance of two simultaneous
SSH installations, viz. OpenSSH and the library, duplicating the work
of tracking security/bug issues.

By using the underlying SSH commands, we become independent of
them: if anything is discovered, you just have to update your system
SSH, and nothing else. Otherwise you would have a dependency on SSH
to remember, which is always forgotten. This way we avoid getting out
of sync with the system's SSH.

Better yet: this easies development, making this class a lot
simpler to write, understand, maintain and debug.

Finally, the dependency on SSH being OpenSSH 3.8 or greater is
due to efficiency reasons. Establishing an SSH connection is costly
in time. If you are going to make many, this would impose a heavy
cost on your scripts. We routinely launch several thousand remote
jobs, and authentication delays soon proved unacceptable.

OpenSSH 3.8 introduced the possibility of sharing a single SSH
channel between many "connections". This means that only the first
(or master) instance (which will provide the shared channel) needs
to authenticate, hence saving significant time.

The constructor then creates a master channel, leaves it idle
all the object's lifetime and closses it at the end. This channel
might be used as well, but we felt it wasn't such a big loss to keep
it idle, and furthermore, being the master, we didn't want to risk
getting into any trouble that might close it prematurely. So it stands.

All other routines (which actually do the work) simply hijack on
the master channel, hence avoiding the costly authentication step (and
executing significantly faster). The only exception are the "COPY"
routines, which can not hijack the master channel and hence must do
authentication every time.

One more detail: some methods allow for interactive communication
with the remote end. We have simply used a terminal-less connection
for them, using regular files as the intermediate communication channels.
A pipe implementation is also possible, and works as well, but we have
found that dealing with pipes is tricky and error-prone, while using
files is simple and intuitive, so we opted for using files.

The difference has to do with the way you communicate with the
other end: using pipes you may block on read and/or write, and so
may the other end. Since there may occur errors in the process, that
implies that getting into a deadlock is trivial. Just picture this
scenarios:

You send a command -> the remote ends starts the command and
prompts for input on stdout, hangs reding on stdin -> you read the
prompt and send the input -> the remote end wakes and processes it.

You send a command -> the remote end fails, logs an error on
stderr, gets back the system prompt and hangs on reading stdin -> you
notice the prompt and read stderr... since you can't predict the
length of the error message you must empty the pipe... and when doing it
you hang after reading the last char... -> deadlock

You send a command -> the remote end fails, logs an error on stderr,
gets back the system prompt and hangs on reading stdin -> you don't read
stderr to avoid hanging, so submit a new command... this goes on and on
until the remote side's stderr buffer fills, then the remote side locks
waiting for you to read stderr -> you can't know it hang, so you try
to submit a new command, and hang on writing waiting for the other end
to read your command -> deadlock

More scenarios are possible, and since you (or the other side)
can't predict what's going to happen, it is very tricy to avoid them.

Now, using files, you don't have that problem: whenever you reach
the current end-of-file, you get an EOF, no need to hang waiting for
the other side to fill it in with data. The other side doesn't hang on
writing unless your disk space fills up. It's a lot simpler.

Your problem with files is continuing reads after new data becomes
available: the safest way is to call flush() before reading and seeking
to the last position read to avoid having to re-read everything (which
implies that after finishing reading you must ftell() your position.

See the included test script for examples.

<b>CUSTOMIZATION</b>

You <i>must</i> state to the class where your SSH executables (ssh and
scp) are located. This allows you to have them placed anywhere, but
also implies the responsability of using full pathnames to reduce
hacking dangers. It also allows you to use/test a new SSH implementation
installed in a non-standard place before switching to it, or even to
keep various SSH installations on the system (e.g. if the system's
SSH is not up-to-date, you may install one on your home and use it).

You may also indicate where to store temporary files. This must
be a directory followed by a prefix to use when creating a temporal
directory. The parent directory must be writeable by the user who runs
the class (usually it will be run by apache, www or some such). Most commonly
the parent directory will be /tmp or $DocumentRoot/tmp or something similar.

The directory+prefix you state will be used to create a unique
temporary work directory for each object instantiated. Examples of
a valid specifications are "/tmp/phpSsh-" or "/tmp/". When the object is
instantiated, a random string will be appended to this value to create
the actual temporary directory name.

The reason for allowing specifying a prefix is so that debugging
may be easier by facilitating identification of temporaries generated
by this class.

<b>DEBUGGING</b>

The class comes with extensive debugging aids. To enable them,
just set a global variable called $debug to TRUE. This will output
abundant debugging information and leave copies of communication log
files for your reference.

Additionally, there is a sample demo script that shows how to
use this class and may help you debug it. This script is included
in the distribution (or should be) as 'ssh_debug.php'. See notes
and comments within it for more details.

sexec($remote="localhost", $password="xxyzzy")   X-Ref
No description

destruct()   X-Ref
No description

ssh_copy($origin, $destination, $password)   X-Ref
Copy a file or directory from one source to a destination

This function copies source to dest, where one of them is a
local filespec and the other a remote filespec of the form
[user@]host:path

If the original source is a directory, it will be copied
recursively to destination (hence easing file transfers).

The function returns TRUE on success or FALSE on failure.

<b>EFFICIENCY NOTICE:</b>

The copy routines use 'scp' to do their actual work. Since
scp seems to be unable to hitchhike on the master channel,
we must do authentication for each copy operation (subroutine
call). These routines are hence a lot more time-expensive
than all the other ones.

You may want to consider whether you can group several
copies into one single call to reduce authentication
overheads.


ssh_copy_to($localpath, $remotepath)   X-Ref
Copy a file or directory from a local source to a remote destination

This function copies source to dest, where first of them is a
local filespec and then comes a remote filespec as a normal
system path.

Both, local and remote paths may be absolute or relative.

If the original source is a directory, it will be copied
recursively to destination (hence easing file transfers).

The function returns TRUE on success or FALSE on failure.

<b>EFFICIENCY NOTICE:</b>

The copy routines use 'scp' to do their actual work. Since
scp seems to be unable to hitchhike on the master channel,
we must do authetication for each copy operation (subroutine
call). These routines are hence a lot more time-expensive
than all the other ones.

You may want to consider whether you can group several
copies into one single call to reduce authentication
overheads.


ssh_copy_from($remotepath, $localpath)   X-Ref
Copy a file or directory from a remote source to a local destination

This function copies source to dest, where first of them is a
remote filespec and then comes a local filespec, both specified
as normal system paths.

Both, local and remote paths may be absolute or relative.

If the original source is a directory, it will be copied
recursively to destination (hence easing file transfers).

The function returns TRUE on success or FALSE on failure.

EFFICIENCY NOTICE:

The copy routines use 'scp' to do their actual work. Since
scp seems to be unable to hitchhike on the master channel,
we must do authetication for each copy operation (subroutine
call). These routines are hence a lot more time-expensive
than all the other ones.

You may want to consider whether you can group several
copies into one single call to reduce authentication
overheads.


ssh_passthru($command, &$status)   X-Ref
Execute a single command remotely

Execute a single command remotely using ssh and
display its output, optionally returning its exit
status (like passthru)

This function is intended to be used as a one-time
all-at-once non-interactive execution mechanism which
will run the command remotely and display its output.

If you try to issue an interactive command using this
function, all you will get is unneccessary trouble. So
don't!

This might be done as well using a pipe on /tmp and
making the command 'cat' the pipe: when ssh runs, it
runs the command 'cat' on the pipe and hangs on read.
Then we just need a thread to open the pipe, put the
password and close the pipe.

This other way the password is never wirtten down.
But, OTOH, the file life is so ephemeral that most
of the time it will only exist in the internal system
cache, so this approach is not that bad either.


ssh_exec($command, &$out)   X-Ref
Execute a remote command using SSH

This function sort of mimics rexec(3) using SSH as the transport
protocol.

The function returns the exit status of the remote command, and
appends the remote job output to an optional argument.

This function is intended to be used as a one-time
all-at-once non-interactive execution mechanism which
will run the command remotely and return its output.

If you try to issue an interactive command using this
function, all you will get is unneccessary trouble. So
don't!


ssh_open_shell()   X-Ref
Open an SSH connection to a remote site with a shell to run
interactive commands

Connects to a remote host and opens an interactive shell session
with NO controlling terminal.

This routine creates communication streams with the remote shell,
and stores all output (standard and error) of the connection into
two separate local log files (one for stdout and one for stderr).

Returns a process_control array which contains the process resource
ID and an the standard file descriptors which the caller may use to
interact with the remote shell.

The process control array contains:

'process' -- the process resource for the newly created connection

'std_in' -- handle to the standard input of the new connection

'std_out' -- handle to standard output of the new connection

'std_err' -- handle to standard error of the new connection

'stdout_file' -- actual filename of the local log file for the
new connection standard output

'stderr_file' -- actual filename of the local log file for the
new connection standard error


ssh_open_command($command)   X-Ref
Open an SSH connection to run an interactive command on a remote
site

Connects to a remote host and runs an interactive command
with NO controlling terminal.

This routine creates communication streams with the remote shell,
and stores all output (standard and error) of the connection into
two separate local log files (one for stdout and one for stderr).

Returns a process_control array which contains the process resource
ID and an the standard file descriptors which the caller may use to
interact with the remote shell.

The process control array contains:

'process' -- the process resource for the newly created connection

'std_in' -- handle to the standard input of the new connection

'std_out' -- handle to standard output of the new connection

'std_err' -- handle to standard error of the new connection

'stdout_file' -- actual filename of the local log file for the
new connection standard output

'stderr_file' -- actual filename of the local log file for the
new connection standard error


ssh_out_expect($p, $expr="^# ")   X-Ref
Get output until we reach a given regular expression


ssh_close($p)   X-Ref
Close an SSH interactive session

This method terminates a previously open interactive remote
session. It will send a termination notification to the
remote end, close the connection with control and communication
streams, and terminate the local control process.

Copies of the log files that contain the output and error
of the communication are left out for later reference and
local peruse. If you don't need them any longer, you may
delete them or just leave them around until the class destructor
is called (which will remove all session traces),


ssh_popen($command, $mode)   X-Ref
Execute a remote command and keep an unidirectional stream
contact with it.

This routine mimics 'popen()' but uses ssh to connect to
a remote host and run the requested command: in other words,
it opens a pipe to a remotely executed command. This pipe is
unidirectional, with the communications direction controlled
by a method parameter.


ssh_pclose($f)   X-Ref
Close a piped remote execution command control pipe.

This routine accepts as input the handle for the control stream
of a remote command and closes it, terminating the command as well.
The handle must be valid and obtained through a call to ssh_popen().




Generated: Wed May 25 19:30:43 2005 Cross-referenced by PHPXref 0.4.1