1# Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>
2#
3# This file is part of paramiko.
4#
5# Paramiko is free software; you can redistribute it and/or modify it under the
6# terms of the GNU Lesser General Public License as published by the Free
7# Software Foundation; either version 2.1 of the License, or (at your option)
8# any later version.
9#
10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
13# details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
17# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
18
19import select
20import socket
21import struct
22
23from paramiko import util
24from paramiko.common import asbytes, DEBUG
25from paramiko.message import Message
26from paramiko.py3compat import byte_chr, byte_ord
27
28
29(
30    CMD_INIT,
31    CMD_VERSION,
32    CMD_OPEN,
33    CMD_CLOSE,
34    CMD_READ,
35    CMD_WRITE,
36    CMD_LSTAT,
37    CMD_FSTAT,
38    CMD_SETSTAT,
39    CMD_FSETSTAT,
40    CMD_OPENDIR,
41    CMD_READDIR,
42    CMD_REMOVE,
43    CMD_MKDIR,
44    CMD_RMDIR,
45    CMD_REALPATH,
46    CMD_STAT,
47    CMD_RENAME,
48    CMD_READLINK,
49    CMD_SYMLINK,
50) = range(1, 21)
51(CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS) = range(101, 106)
52(CMD_EXTENDED, CMD_EXTENDED_REPLY) = range(200, 202)
53
54SFTP_OK = 0
55(
56    SFTP_EOF,
57    SFTP_NO_SUCH_FILE,
58    SFTP_PERMISSION_DENIED,
59    SFTP_FAILURE,
60    SFTP_BAD_MESSAGE,
61    SFTP_NO_CONNECTION,
62    SFTP_CONNECTION_LOST,
63    SFTP_OP_UNSUPPORTED,
64) = range(1, 9)
65
66SFTP_DESC = [
67    "Success",
68    "End of file",
69    "No such file",
70    "Permission denied",
71    "Failure",
72    "Bad message",
73    "No connection",
74    "Connection lost",
75    "Operation unsupported",
76]
77
78SFTP_FLAG_READ = 0x1
79SFTP_FLAG_WRITE = 0x2
80SFTP_FLAG_APPEND = 0x4
81SFTP_FLAG_CREATE = 0x8
82SFTP_FLAG_TRUNC = 0x10
83SFTP_FLAG_EXCL = 0x20
84
85_VERSION = 3
86
87
88# for debugging
89CMD_NAMES = {
90    CMD_INIT: "init",
91    CMD_VERSION: "version",
92    CMD_OPEN: "open",
93    CMD_CLOSE: "close",
94    CMD_READ: "read",
95    CMD_WRITE: "write",
96    CMD_LSTAT: "lstat",
97    CMD_FSTAT: "fstat",
98    CMD_SETSTAT: "setstat",
99    CMD_FSETSTAT: "fsetstat",
100    CMD_OPENDIR: "opendir",
101    CMD_READDIR: "readdir",
102    CMD_REMOVE: "remove",
103    CMD_MKDIR: "mkdir",
104    CMD_RMDIR: "rmdir",
105    CMD_REALPATH: "realpath",
106    CMD_STAT: "stat",
107    CMD_RENAME: "rename",
108    CMD_READLINK: "readlink",
109    CMD_SYMLINK: "symlink",
110    CMD_STATUS: "status",
111    CMD_HANDLE: "handle",
112    CMD_DATA: "data",
113    CMD_NAME: "name",
114    CMD_ATTRS: "attrs",
115    CMD_EXTENDED: "extended",
116    CMD_EXTENDED_REPLY: "extended_reply",
117}
118
119
120class SFTPError(Exception):
121    pass
122
123
124class BaseSFTP(object):
125    def __init__(self):
126        self.logger = util.get_logger("paramiko.sftp")
127        self.sock = None
128        self.ultra_debug = False
129
130    # ...internals...
131
132    def _send_version(self):
133        self._send_packet(CMD_INIT, struct.pack(">I", _VERSION))
134        t, data = self._read_packet()
135        if t != CMD_VERSION:
136            raise SFTPError("Incompatible sftp protocol")
137        version = struct.unpack(">I", data[:4])[0]
138        #        if version != _VERSION:
139        #            raise SFTPError('Incompatible sftp protocol')
140        return version
141
142    def _send_server_version(self):
143        # winscp will freak out if the server sends version info before the
144        # client finishes sending INIT.
145        t, data = self._read_packet()
146        if t != CMD_INIT:
147            raise SFTPError("Incompatible sftp protocol")
148        version = struct.unpack(">I", data[:4])[0]
149        # advertise that we support "check-file"
150        extension_pairs = ["check-file", "md5,sha1"]
151        msg = Message()
152        msg.add_int(_VERSION)
153        msg.add(*extension_pairs)
154        self._send_packet(CMD_VERSION, msg)
155        return version
156
157    def _log(self, level, msg, *args):
158        self.logger.log(level, msg, *args)
159
160    def _write_all(self, out):
161        while len(out) > 0:
162            n = self.sock.send(out)
163            if n <= 0:
164                raise EOFError()
165            if n == len(out):
166                return
167            out = out[n:]
168        return
169
170    def _read_all(self, n):
171        out = bytes()
172        while n > 0:
173            if isinstance(self.sock, socket.socket):
174                # sometimes sftp is used directly over a socket instead of
175                # through a paramiko channel.  in this case, check periodically
176                # if the socket is closed.  (for some reason, recv() won't ever
177                # return or raise an exception, but calling select on a closed
178                # socket will.)
179                while True:
180                    read, write, err = select.select([self.sock], [], [], 0.1)
181                    if len(read) > 0:
182                        x = self.sock.recv(n)
183                        break
184            else:
185                x = self.sock.recv(n)
186
187            if len(x) == 0:
188                raise EOFError()
189            out += x
190            n -= len(x)
191        return out
192
193    def _send_packet(self, t, packet):
194        packet = asbytes(packet)
195        out = struct.pack(">I", len(packet) + 1) + byte_chr(t) + packet
196        if self.ultra_debug:
197            self._log(DEBUG, util.format_binary(out, "OUT: "))
198        self._write_all(out)
199
200    def _read_packet(self):
201        x = self._read_all(4)
202        # most sftp servers won't accept packets larger than about 32k, so
203        # anything with the high byte set (> 16MB) is just garbage.
204        if byte_ord(x[0]):
205            raise SFTPError("Garbage packet received")
206        size = struct.unpack(">I", x)[0]
207        data = self._read_all(size)
208        if self.ultra_debug:
209            self._log(DEBUG, util.format_binary(data, "IN: "))
210        if size > 0:
211            t = byte_ord(data[0])
212            return t, data[1:]
213        return 0, bytes()
214