# Copyright (C) 2003-2007 Robey Pointer # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import select import socket import struct from paramiko import util from paramiko.common import DEBUG, byte_chr, byte_ord from paramiko.message import Message ( CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK, ) = range(1, 21) (CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS) = range(101, 106) (CMD_EXTENDED, CMD_EXTENDED_REPLY) = range(200, 202) SFTP_OK = 0 ( SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, SFTP_BAD_MESSAGE, SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED, ) = range(1, 9) SFTP_DESC = [ "Success", "End of file", "No such file", "Permission denied", "Failure", "Bad message", "No connection", "Connection lost", "Operation unsupported", ] SFTP_FLAG_READ = 0x1 SFTP_FLAG_WRITE = 0x2 SFTP_FLAG_APPEND = 0x4 SFTP_FLAG_CREATE = 0x8 SFTP_FLAG_TRUNC = 0x10 SFTP_FLAG_EXCL = 0x20 _VERSION = 3 # for debugging CMD_NAMES = { CMD_INIT: "init", CMD_VERSION: "version", CMD_OPEN: "open", CMD_CLOSE: "close", CMD_READ: "read", CMD_WRITE: "write", CMD_LSTAT: "lstat", CMD_FSTAT: "fstat", CMD_SETSTAT: "setstat", CMD_FSETSTAT: "fsetstat", CMD_OPENDIR: "opendir", CMD_READDIR: "readdir", CMD_REMOVE: "remove", CMD_MKDIR: "mkdir", CMD_RMDIR: "rmdir", CMD_REALPATH: "realpath", CMD_STAT: "stat", CMD_RENAME: "rename", CMD_READLINK: "readlink", CMD_SYMLINK: "symlink", CMD_STATUS: "status", CMD_HANDLE: "handle", CMD_DATA: "data", CMD_NAME: "name", CMD_ATTRS: "attrs", CMD_EXTENDED: "extended", CMD_EXTENDED_REPLY: "extended_reply", } # TODO: rewrite SFTP file/server modules' overly-flexible "make a request with # xyz components" so we don't need this very silly method of signaling whether # a given Python integer should be 32- or 64-bit. # NOTE: this only became an issue when dropping Python 2 support; prior to # doing so, we had to support actual-longs, which served as that signal. This # is simply recreating that structure in a more tightly scoped fashion. class int64(int): pass class SFTPError(Exception): pass class BaseSFTP: def __init__(self): self.logger = util.get_logger("paramiko.sftp") self.sock = None self.ultra_debug = False # ...internals... def _send_version(self): m = Message() m.add_int(_VERSION) self._send_packet(CMD_INIT, m) t, data = self._read_packet() if t != CMD_VERSION: raise SFTPError("Incompatible sftp protocol") version = struct.unpack(">I", data[:4])[0] # if version != _VERSION: # raise SFTPError('Incompatible sftp protocol') return version def _send_server_version(self): # winscp will freak out if the server sends version info before the # client finishes sending INIT. t, data = self._read_packet() if t != CMD_INIT: raise SFTPError("Incompatible sftp protocol") version = struct.unpack(">I", data[:4])[0] # advertise that we support "check-file" extension_pairs = ["check-file", "md5,sha1"] msg = Message() msg.add_int(_VERSION) msg.add(*extension_pairs) self._send_packet(CMD_VERSION, msg) return version def _log(self, level, msg, *args): self.logger.log(level, msg, *args) def _write_all(self, out): while len(out) > 0: n = self.sock.send(out) if n <= 0: raise EOFError() if n == len(out): return out = out[n:] return def _read_all(self, n): out = bytes() while n > 0: if isinstance(self.sock, socket.socket): # sometimes sftp is used directly over a socket instead of # through a paramiko channel. in this case, check periodically # if the socket is closed. (for some reason, recv() won't ever # return or raise an exception, but calling select on a closed # socket will.) while True: read, write, err = select.select([self.sock], [], [], 0.1) if len(read) > 0: x = self.sock.recv(n) break else: x = self.sock.recv(n) if len(x) == 0: raise EOFError() out += x n -= len(x) return out def _send_packet(self, t, packet): packet = packet.asbytes() out = struct.pack(">I", len(packet) + 1) + byte_chr(t) + packet if self.ultra_debug: self._log(DEBUG, util.format_binary(out, "OUT: ")) self._write_all(out) def _read_packet(self): x = self._read_all(4) # most sftp servers won't accept packets larger than about 32k, so # anything with the high byte set (> 16MB) is just garbage. if byte_ord(x[0]): raise SFTPError("Garbage packet received") size = struct.unpack(">I", x)[0] data = self._read_all(size) if self.ultra_debug: self._log(DEBUG, util.format_binary(data, "IN: ")) if size > 0: t = byte_ord(data[0]) return t, data[1:] return 0, bytes()