Chapter 5. Networking

Table of Contents

5.1. Net_SSH
5.1.1. Dependencies
5.1.2. Net_SSH1 Examples
5.1.3. Net_SSH2 Examples
5.1.4. Host Key Verification
5.1.5. interactiveRead() / interactiveWrite() vs. exec()
5.1.6. SSH-1's exec() vs. SSH-2's exec()
5.1.7. Successive calls to SSH-2's exec()
5.1.8. Debugging SSH-2
5.2. Net_SFTP
5.2.1. Introduction
5.2.2. Dependencies
5.2.3. Net_SFTP Example
5.2.4. put($remote_file, $data [, $mode])
5.2.5. get($remote_file [, $local_file])
5.2.6. pwd(), chdir(), mkdir() and rmdir()
5.2.7. chmod() and size()
5.2.8. nlist() and rawlist()
5.2.9. delete() and rename()
5.2.10. Debugging SFTP

5.1. Net_SSH

The Net_SSH1 and Net_SSH2 libraries have, for the most part, an identical API. Some functions, however, do behave differently.

5.1.1. Dependencies

Net_SSH1/2 require, minimally, Math/BigInteger.php, Crypt/*.php, and PHP/Compat/Function/*.php. Net_SSH1 requires PHP 4.0.0 unless you're using the interactive functions, which require PHP 4.3.0. Net_SSH2 requires PHP 4.3.0 due to it's use of sha1().

5.1.2. Net_SSH1 Examples

<?php
    include('Net/SSH1.php');

    $ssh = new Net_SSH1('www.domain.tld');
    if (!$ssh->login('username', 'password')) {
        exit('Login Failed');
    }

    while (true) {
        echo $ssh->interactiveRead();

        $read = array(STDIN);
        $write = $except = NULL;
        if (stream_select($read, $write, $except, 0)) {
            $ssh->interactiveWrite(fread(STDIN, 1));
        }
    }
?>
<?php
    include('Net/SSH1.php');

    $ssh = new Net_SSH1('www.domain.tld');
    if (!$ssh->login('username', 'password')) {
        exit('Login Failed');
    }

    echo $ssh->exec('ls -la');
?>

5.1.3. Net_SSH2 Examples

<?php
    include('Net/SSH2.php');

    $ssh = new Net_SSH2('www.domain.tld');
    if (!$ssh->login('username', 'password')) {
        exit('Login Failed');
    }

    echo $ssh->exec('pwd');
    echo $ssh->exec('ls -la');
?>
<?php
    include('Crypt/RSA.php');
    include('Net/SSH2.php');

    $key = new Crypt_RSA();
    //$key->setPassword('whatever');
    $key->loadKey(file_get_contents('privatekey'));

    $ssh = new Net_SSH2('www.domain.tld');
    if (!$ssh->login('username', $key)) {
        exit('Login Failed');
    }

    echo $ssh->exec('pwd');
    echo $ssh->exec('ls -la');
?<

5.1.4. Host Key Verification

SSH protects itself against active eavesdroppers by providing a host key. The first time you connect the host key is supposed to be cached in some manner. On subsequent connections, the host key being used for this connection should be checked against the cached host key. If they match, it's the same server. If not, it's a different one.

In SSH-1, getHostKeyPublicModulus() and getHostKeyPublicExponent() will provide you with the host key. In SSH-2, getServerPublicHostKey() gets you the key.

The Net_SSH1 and Net_SSH2 examples omit the key verification stage for brevity. Also, depending on the context in which this library is used, it may even be unnecessary. For example, if you're connecting to www.example.com:22 from www.example.com:80, eavesdroppers are not something you need to worry about.

5.1.5. interactiveRead() / interactiveWrite() vs. exec()

Say you wanted to use SSH to get the contents of a directory. If you used interactiveWrite('ls') to do this, and then interactiveRead() to get the output, you, in all likelihood, wouldn't get the whole output. You'd have to call interactiveRead() multiple times - you'd have to call it as many times as it took for you to get the complete output, which, in turn, begs the question... how do you know when you have the complete output? You could assume that whenever the prompt (eg. root@desktop:/root#) showed up, that that'd mean you had the complete output, but that's not fool proof. And what about messages of the day? The first interactiveRead() you do is liable to include a part of that rather than the directory listing. So, not only do you have to make some sort of guestimate as to when the output ends - you often may have to guestimate as to when it begins.

To top it all off, you may also get ANSI escape codes interspersed amongst the output, which would need to be removed.

Using exec('ls') resolves all of these issues. If you're implementing an interactive client, the interactive functions are the ones you'll want to use. Otherwise, exec() is likely what you'll want to use.

interactiveRead() / interactiveWrite() are not implemented in Net_SSH2. The SSH-2 protocol supports them, however, phpseclib does not. The reasons are discussed in the SSH-1's exec() vs. SSH-2's exec() section.

5.1.6. SSH-1's exec() vs. SSH-2's exec()

exec() works by creating a channel, issuing a command, and then subsequently destroying that channel. Since SSH-1 only allows one channel, exec() can only be called once. SSH-2, in contrast, allows an unlimited number of channels, and as such, you can perform as many exec()'s as you see fit.

As a consequence of this difference, Net_SSH2 does not implement interactiveRead() / interactiveWrite(), even though the SSH-2 specifications provide for those functions. Simply put, in SSH-1, those functions are necessary to do multiple commands. In SSH-2, they're not, and so they're not implemented.

5.1.7. Successive calls to SSH-2's exec()

Successive calls to SSH-2's exec() may not work as expected. Consider the following:

<?php
    include('Net/SSH2.php');

    $ssh = new Net_SSH2('www.domain.tld');
    if (!$ssh->login('username', 'password')) {
        exit('Login Failed');
    }

    echo $ssh->exec('pwd');
    echo $ssh->exec('cd /');
    echo $ssh->exec('pwd');
?>

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. As such, if you want to support cd in your program, it'd be best to just handle that internally and rewrite all commands, before they're passed to exec() such that the relative paths are expanded to the absolute paths. Alternatively, one could always run a shell script, however, that may not always be an option.

5.1.8. Debugging SSH-2

To log output, the NET_SSH2_LOGGING constant will need to be defined. If you want full logs, you'll need to do define('NET_SSH2_LOGGING', NET_SSH2_LOG_COMPLEX). $ssh->getLog() will then return a string containing the unencrypted packets in hex and ASCII. If you want to just record the packet types that are being sent to and fro, you'll need to do define('NET_SSH2_LOGGING', NET_SSH2_LOG_SIMPLE). $ssh->getLog() will then return an array. Both log types include the amount of time it took to send the packet in question. The former is useful for general diagnostics and the latter is more useful for profiling. An example follows:

<?php
    include('Net/SSH2.php');
    define('NET_SSH2_LOGGING', NET_SSH2_LOG_COMPLEX);

    $ssh = new Net_SSH2('www.domain.tld');
    if (!$ssh->login('username', 'password')) {
        exit('Login Failed');
    }

    echo $ssh->exec('pwd');
    echo $ssh->getLog();
?>

Depending on the problem, it may be more effective to just look at the output of $ssh->getLastError() (which returns a string) and $ssh->getErrors() (which returns an array) than to sift through the logs.

5.2. Net_SFTP

5.2.1. Introduction

Net_SFTP currently only supports SFTPv3, which, according to wikipedia.org, "is the most widely used version, implemented by the popular OpenSSH SFTP server".

5.2.2. Dependencies

Net_SFTP requires, minimally, PHP 4.3.0 and Net/SSH2.php, Math/BigInteger.php, Crypt/*.php, and PHP/Compat/Function/*.php.

5.2.3. Net_SFTP Example

<?php
include('Net/SFTP.php');

$sftp = new Net_SFTP('www.domain.tld');
if (!$sftp->login('username', 'password')) {
    exit('Login Failed');
}

echo $sftp->pwd() . "\r\n";
$sftp->put('filename.ext', 'hello, world!');
print_r($sftp->nlist());
?>

5.2.4. put($remote_file, $data [, $mode])

By default, put() does not read from the local filesystem. $data is dumped directly into $remote_file. So, for example, if you set $data to 'filename.ext' and then do get(), you will get a file, twelve bytes long, containing 'filename.ext' as its contents.

Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how large $remote_file will be, as well.

Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take care of that, yourself.

5.2.5. get($remote_file [, $local_file])

Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the operation

5.2.6. pwd(), chdir(), mkdir() and rmdir()

pwd() returns the current directory, chdir() changes directories, mkdir() creates direcotires, and rmdir() removes directories. In the event of failure, they all return false. chdir(), mkdir(), and rmdir() return true on successful completion of the operation.

5.2.7. chmod() and size()

chmod() sets the permissions on a file and returns the new file permissions on success or false on error. Permissions are expected to be in octal so to set a file to 777 do $sftp->chmod(0777, $filename)

size() returns the size, in bytes, of an arbitrary file.

5.2.8. nlist() and rawlist()

nlist($dir = '.') returns the contents of the current directory as a numerically indexed array and rawlist() returns an associate array where the filenames are the array keys and the array values are, themselves, arrays containing the file attributes. The directory can be changed with the first parameter.

5.2.9. delete() and rename()

The purpose of both functions should be easy enough to glean - delete() deletes files or directories and rename() renames them. Both return true on success and false on failure.

5.2.10. Debugging SFTP

Debbuging SFTP connections in phpseclib works in a manner similar to debugging SSH-2 Instead of the constant being NET_SSH2_LOGGING, however, it's NET_SFTP_LOGGING. And instead of NET_SSH2_LOG_COMPLEX or NET_SSH2_LOG_SIMPLE it's NET_SFTP_LOG_COMPLEX or NET_SFTP_LOG_SIMPLE respectively. And instead of calling $sftp->getLog() you call $sftp->getSFTPLog() or $sftp->getLastSFTPError() or whatever.