* Pure-PHP implementation of SFTP.
* Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
* implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
* to an SFTPv4/5/6 server.
* The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
* Here's a short example of how to use this library:
* include 'Net/SFTP.php';
* $sftp = new Net_SFTP('www.domain.tld');
* if (!$sftp->login('username', 'password')) {
* echo $sftp->pwd() . "\r\n";
* $sftp->put('filename.ext', 'hello, world!');
* print_r($sftp->nlist());
* LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMIX Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
if (!class_exists('Net_SSH2')) {
require_once dirname(__FILE__).'/SSH2.php';
* @see Net_SFTP::getLog()
* Returns the message numbers
define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
* Returns the message content
define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
* Outputs the message content in real-time.
define('NET_SFTP_LOG_REALTIME', 3);
* Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
* @see Net_SSH2::_send_channel_packet()
* @see Net_SSH2::_get_channel_packet()
define('NET_SFTP_CHANNEL', 0x100);
* Reads data from a local file.
define('NET_SFTP_LOCAL_FILE', 1);
* Reads data from a string.
// this value isn't really used anymore but i'm keeping it reserved for historical reasons
define('NET_SFTP_STRING', 2);
define('NET_SFTP_RESUME', 4);
* Append a local file to an already existing remote file
define('NET_SFTP_RESUME_START', 8);
* Pure-PHP implementations of SFTP.
* @author Jim Wigginton <terrafrost@php.net>
class Net_SFTP extends Net_SSH2
* @see Net_SFTP::Net_SFTP()
public $packet_types = array();
* @see Net_SFTP::Net_SFTP()
public $status_codes = array();
* The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
* concurrent actions, so it's somewhat academic, here.
* @see Net_SFTP::_send_sftp_packet()
public $request_id = false;
* The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
* concurrent actions, so it's somewhat academic, here.
* @see Net_SFTP::_get_sftp_packet()
public $packet_type = -1;
* @see Net_SFTP::_get_sftp_packet()
public $packet_buffer = '';
* Extensions supported by the server
* @see Net_SFTP::_initChannel()
public $extensions = array();
* @see Net_SFTP::_initChannel()
* Current working directory
* @see Net_SFTP::_realpath()
* @see Net_SFTP::getLog()
public $packet_type_log = array();
* @see Net_SFTP::getLog()
public $packet_log = array();
* @see Net_SFTP::getSFTPErrors()
* @see Net_SFTP::getLastSFTPError()
public $sftp_errors = array();
* Rather than always having to open a directory and close it immediately there after to see if a file is a directory
* we'll cache the results.
* @see Net_SFTP::_update_stat_cache()
* @see Net_SFTP::_remove_from_stat_cache()
* @see Net_SFTP::_query_stat_cache()
public $stat_cache = array();
* @see Net_SFTP::Net_SFTP()
* @see Net_SFTP::disableStatCache()
* @see Net_SFTP::enableStatCache()
public $use_stat_cache = true;
* @see Net_SFTP::_comparator()
* @see Net_SFTP::setListOrder()
public $sortOptions = array();
* Connects to an SFTP server
* @param optional Integer $port
* @param optional Integer $timeout
public function __construct($host, $port = 22, $timeout = 10)
parent::__construct($host, $port, $timeout);
$this->max_sftp_packet = 1 << 15;
$this->packet_types = array(
/* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
11 => 'NET_SFTP_OPENDIR',
12 => 'NET_SFTP_READDIR',
16 => 'NET_SFTP_REALPATH',
/* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
19 => 'NET_SFTP_READLINK',
20 => 'NET_SFTP_SYMLINK',
101 => 'NET_SFTP_STATUS',
102 => 'NET_SFTP_HANDLE',
/* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
200 => 'NET_SFTP_EXTENDED',
$this->status_codes = array(
0 => 'NET_SFTP_STATUS_OK',
1 => 'NET_SFTP_STATUS_EOF',
2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
4 => 'NET_SFTP_STATUS_FAILURE',
5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
6 => 'NET_SFTP_STATUS_NO_CONNECTION',
7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
13 => 'NET_SFTP_STATUS_NO_MEDIA',
14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
21 => 'NET_SFTP_STATUS_LINK_LOOP',
22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
27 => 'NET_SFTP_STATUS_DELETE_PENDING',
28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
29 => 'NET_SFTP_STATUS_OWNER_INVALID',
30 => 'NET_SFTP_STATUS_GROUP_INVALID',
31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK',
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
// the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
$this->attributes = array(
0x00000001 => 'NET_SFTP_ATTR_SIZE',
0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
// 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
// yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
// two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
// that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
-1 << 31 => 'NET_SFTP_ATTR_EXTENDED',
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
// the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
// the array for that $this->open5_flags and similarily alter the constant names.
$this->open_flags = array(
0x00000001 => 'NET_SFTP_OPEN_READ',
0x00000002 => 'NET_SFTP_OPEN_WRITE',
0x00000004 => 'NET_SFTP_OPEN_APPEND',
0x00000008 => 'NET_SFTP_OPEN_CREATE',
0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
0x00000020 => 'NET_SFTP_OPEN_EXCL',
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
// see Net_SFTP::_parseLongname() for an explanation
$this->file_types = array(
1 => 'NET_SFTP_TYPE_REGULAR',
2 => 'NET_SFTP_TYPE_DIRECTORY',
3 => 'NET_SFTP_TYPE_SYMLINK',
4 => 'NET_SFTP_TYPE_SPECIAL',
5 => 'NET_SFTP_TYPE_UNKNOWN',
// the followin types were first defined for use in SFTPv5+
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
6 => 'NET_SFTP_TYPE_SOCKET',
7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
9 => 'NET_SFTP_TYPE_FIFO',
if (!defined('NET_SFTP_QUEUE_SIZE')) {
define('NET_SFTP_QUEUE_SIZE', 50);
* @param String $username
* @param optional String $password
public function login($username)
if (!call_user_func_array(array(&$this, '_login'), $args)) {
$this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size;
NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000);
if (!$this->_send_binary_packet($packet)) {
$this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
$response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
if ($response === false) {
$packet = pack('CNNa*CNa*',
NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');
if (!$this->_send_binary_packet($packet)) {
$this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
$response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
if ($response === false) {
// from PuTTY's psftp.exe
$command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n".
"test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n".
// we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
$packet = pack('CNNa*CNa*',
NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('exec'), 'exec', 1, strlen($command), $command);
if (!$this->_send_binary_packet($packet)) {
$this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
$response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
if ($response === false) {
$this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
$response = $this->_get_sftp_packet();
if ($this->packet_type != NET_SFTP_VERSION) {
user_error('Expected SSH_FXP_VERSION');
extract(unpack('Nversion', $this->_string_shift($response, 4)));
$this->version = $version;
while (!empty($response)) {
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$key = $this->_string_shift($response, $length);
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$value = $this->_string_shift($response, $length);
$this->extensions[$key] = $value;