phpseclib: SSH2 Examples and Notes

Action:

<?php
require __DIR__ . '/vendor/autoload.php';

use phpseclib\Net\SSH2;
use phpseclib\File\ANSI;

define('NET_SSH2_LOGGING', SSH2::LOG_COMPLEX);

$ssh = new SSH2('www.domain.tld');
if (!$ssh->login('username', 'password')) {
    exit('Login Failed');
}
// save $ssh->getServerPublicHostKey() if this is your first time connecting
// check $ssh->getServerPublicHostKey() against previously saved value on subsequent connections

echo $ssh->exec('pwd');
echo $ssh->exec('ls -la');echo $ssh->exec('pwd'); // outputs /home/username
$ssh->exec('cd /');
echo $ssh->exec('pwd'); // (despite the previous command) outputs /home/username
echo $ssh->read('username@username:~$');
$ssh->write("ls -la\n"); // note the "\n"
echo $ssh->read('username@username:~$');echo $ssh->read('username@username:~$');
$ssh->write("sudo ls -la\n");
$output = $ssh->read('#[pP]assword[^:]*:|username@username:~\$#', SSH2::READ_REGEX);
echo $output;
if (preg_match('#[pP]assword[^:]*:#', $output)) {
    $ssh->write("password\n");
    echo $ssh->read('username@username:~$');
}$ssh->setTimeout(1);
echo $ssh->read();function packet_handler($str)
{
    echo $str;
}

$ssh->exec('ping 127.0.0.1', 'packet_handler');$ansi = new ANSI();

$ansi->appendString($ssh->read('username@username:~$'));
$ssh->write("top\n");
$ssh->setTimeout(5);
$ansi->appendString($ssh->read());
echo $ansi->getScreen(); // outputs HTML
$ssh->setTimeout(2);
$ssh->read();
$ssh->write("vim\n");
$ssh->read();
$ssh->write("\x1BOP"); // send F1

$ansi = new ANSI();
$ansi->appendString($ssh->read());

echo $ansi->getScreen();

echo $ssh->getLog();
?>

stderr and exit status

By default $ssh->exec() returns both stdout and stderr. To suppress stderr you can call $ssh->enableQuietMode(). To re-enable it call $ssh->disableQuietMode().

To get stderr separately from stdout you'll need to call $ssh->enableQuietMode() and then call $ssh->getStdError(). These functions work with $ssh->read() as well.

To get the exit status you can call $ssh->getExitStatus()

Successive calls to exec()

If done on an interactive shell, the output you'd receive for the first pwd would (depending on how your system is setup) be different than the output of the second pwd. The above code snippet, however, will yield two identical lines.

The reason for this is that any "state changes" you make to the one-time shell are gone once the exec() has been ran and the channel has been deleted.

You can workaround this on Linux by doing $ssh->exec('cd /; pwd')

setTimeout

After each $ssh->read() the timeout resets to what you set it to last. By default the "channel" remains open so if you do $ssh->write("ping 127.0.0.1\n") and do two successive $ssh->read() you'll get ping output in both of those calls. If you don't want it to do that you can either send Ctrl + C via $ssh->write("\x03") or you can reset the whole channel by calling $ssh->reset().

$ssh->isTimeout() will return true if the result of the last $ssh->read() or $ssh->exec() was due to a timeout. Otherwise it will return false.

$ssh->setTimeout() works with $ssh->read() and $ssh->exec().

New lines needed to send commands

On a terminal you normally don't just type the command and expect to get the output. You type the command and then hit enter. To simulate that you'll need to add "\n" to the commands you send via write()

sudo with read() / write()

By default, sudo caches passwords for 5 minutes after they've been entered. So while $ssh->read('Password:') will work the first time you try it, it won't work if you try it within a five minutes after having initially ran it.

Callbacks

This example is best run via the CLI. Run it via the webbrowser and you may need to flush the output buffer and even then YMMV.

ANSI Escape Codes

Some commands issued to a terminal may yield ANSI escape codes. eg. ^[[H. These provide the terminal with information on the formating of the characters and their positioning.

Since Net_SSH2 uses vt100 as the "TERM environment variable value" a VT100 terminal emulator is needed to properly handle the ANSI escape codes. File_ANSI aims to be such an emulator. The default screen size is 80x24.

$ansi->getScreen() returns what'd be seen on the current screen. In the case of top this is desirable as it'll produce output like this:

top - 23:39:24 up 77 days,  1:13,  1 user,  load average: 0.00, 0.00, 0.00
Tasks:  45 total,   2 running,  43 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1740956k total,  1079288k used,   661668k free,   221240k buffers
Swap:        0k total,        0k used,        0k free,   399940k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND            
    1 root      16   0  2128  696  600 S  0.0  0.0   0:01.10 init               
    2 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 migration/0        
    3 root      34  19     0    0    0 S  0.0  0.0   0:00.05 ksoftirqd/0        
    4 root      RT   0     0    0    0 S  0.0  0.0   0:00.01 watchdog/0         
    5 root      10  -5     0    0    0 S  0.0  0.0   0:00.25 events/0           
    6 root      11  -5     0    0    0 S  0.0  0.0   0:00.63 khelper            
    7 root      10  -5     0    0    0 S  0.0  0.0   0:00.00 kthread            
    8 root      14  -5     0    0    0 S  0.0  0.0   0:00.00 xenwatch           
    9 root      10  -5     0    0    0 S  0.0  0.0   0:00.00 xenbus             
   17 root      10  -5     0    0    0 S  0.0  0.0   0:00.00 kblockd/0          
   46 root      20  -5     0    0    0 S  0.0  0.0   0:00.00 aio/0              
   45 root      15   0     0    0    0 S  0.0  0.0   0:00.64 kswapd0            
  562 root      20  -5     0    0    0 S  0.0  0.0   0:00.00 kseriod            
  657 root      15   0     0    0    0 S  0.0  0.0   0:04.23 kjournald          
  718 root      13  -4  2360  656  424 S  0.0  0.0   0:00.18 udevd              
 1592 root      14  -2  2396  848  560 S  0.0  0.0   0:00.03 dhclient           
 1647 root      15   0     0    0    0 S  0.0  0.0   0:00.16 kjournald

In the case of ls, however, it is less desirable. For commands like ls it may be preferable to do $ansi->getHistory(). For top, that'd return the following:

         __|  __|_  )  Fedora 8
         _|  (     /    32-bit
        ___|\___|___|

 Welcome to an EC2 Public Image
                       :-)

    Base

 --[ see /etc/ec2/release-notes ]--

[username@username:~$]$top

top - 23:51:56 up 77 days,  1:25,  1 user,  load average: 0.00, 0.00, 0.00
Tasks:  45 total,   2 running,  43 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1740956k total,  1079256k used,   661700k free,   221240k buffers
Swap:        0k total,        0k used,        0k free,   399940k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND            
    1 root      16   0  2128  696  600 S  0.0  0.0   0:01.10 init               
    2 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 migration/0        
    3 root      34  19     0    0    0 S  0.0  0.0   0:00.05 ksoftirqd/0        
    4 root      RT   0     0    0    0 S  0.0  0.0   0:00.01 watchdog/0         
    5 root      10  -5     0    0    0 S  0.0  0.0   0:00.25 events/0           
    6 root      11  -5     0    0    0 S  0.0  0.0   0:00.63 khelper            
    7 root      10  -5     0    0    0 S  0.0  0.0   0:00.00 kthread            
    8 root      14  -5     0    0    0 S  0.0  0.0   0:00.00 xenwatch           
    9 root      10  -5     0    0    0 S  0.0  0.0   0:00.00 xenbus             
   17 root      10  -5     0    0    0 S  0.0  0.0   0:00.00 kblockd/0          
   46 root      20  -5     0    0    0 S  0.0  0.0   0:00.00 aio/0              
   45 root      15   0     0    0    0 S  0.0  0.0   0:00.64 kswapd0            
  562 root      20  -5     0    0    0 S  0.0  0.0   0:00.00 kseriod            
  657 root      15   0     0    0    0 S  0.0  0.0   0:04.23 kjournald          
  718 root      13  -4  2360  656  424 S  0.0  0.0   0:00.18 udevd              
 1592 root      14  -2  2396  848  560 S  0.0  0.0   0:00.03 dhclient           
 1647 root      15   0     0    0    0 S  0.0  0.0   0:00.16 kjournald

The history, by default, stores 200 lines (not including the current screen). Both functions return HTML with the various formatting properties specified by HTML. If you don't want HTML and want just the raw text of the terminal without any formatting do htmlspecialchars_decode(strip_tags($ansi->getScreen())).

Sending Special Characters

A table of special characters and the keys they correspond to can be found at SSH2 Special Characters. The output of the above program is as follows:

*help.txt*      For Vim version 7.3.  Last change: 2010 Jul 20

  VIM - main help file
  k
      Move around:  Use the cursor keys, or "h" to go left, h   l
  "j" to go down, "k" to go up, "l" to go right.       j
Close this window:  Use ":q<Enter>".
   Get out of Vim:  Use ":qa!<Enter>" (careful, all changes are lost!).

Jump to a subject:  Position the cursor on a tag (e.g. |bars|) and hit CTRL-].
   With the mouse:  ":set mouse=a" to enable the mouse (in xterm or GUI).
  Double-click the left mouse button on a tag, e.g. |bars|.
        Jump back:  Type CTRL-T or CTRL-O (repeat to go further back).

Get specific help:  It is possible to go directly to whatever you want help
     on, by giving an argument to the |:help| command.
     It is possible to further specify the context:
                                         *help-context*
  WHAT                  PREPEND    EXAMPLE      ~
  Normal mode command      (nothing)   :help x
help.txt [Help][RO]                                           1,1            Top

[No Name]                                                     0,0-1          All
"help.txt" [readonly] 221L, 8239C

The output may look different than vim when ran through, for example, PuTTY, because PuTTY uses xterm as it's shell whereas phpseclib uses vt100.