1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """
20 Server-mode SFTP support.
21 """
22
23 import os
24 import errno
25 import sys
26 from hashlib import md5, sha1
27
28 from paramiko import util
29 from paramiko.sftp import BaseSFTP, Message, SFTP_FAILURE, \
30 SFTP_PERMISSION_DENIED, SFTP_NO_SUCH_FILE
31 from paramiko.sftp_si import SFTPServerInterface
32 from paramiko.sftp_attr import SFTPAttributes
33 from paramiko.common import DEBUG
34 from paramiko.py3compat import long, string_types, bytes_types, b
35 from paramiko.server import SubsystemHandler
36
37
38
39 from paramiko.sftp import CMD_HANDLE, SFTP_DESC, CMD_STATUS, SFTP_EOF, CMD_NAME, \
40 SFTP_BAD_MESSAGE, CMD_EXTENDED_REPLY, SFTP_FLAG_READ, SFTP_FLAG_WRITE, \
41 SFTP_FLAG_APPEND, SFTP_FLAG_CREATE, SFTP_FLAG_TRUNC, SFTP_FLAG_EXCL, \
42 CMD_NAMES, CMD_OPEN, CMD_CLOSE, SFTP_OK, CMD_READ, CMD_DATA, CMD_WRITE, \
43 CMD_REMOVE, CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_OPENDIR, CMD_READDIR, \
44 CMD_STAT, CMD_ATTRS, CMD_LSTAT, CMD_FSTAT, CMD_SETSTAT, CMD_FSETSTAT, \
45 CMD_READLINK, CMD_SYMLINK, CMD_REALPATH, CMD_EXTENDED, SFTP_OP_UNSUPPORTED
46
47 _hash_class = {
48 'sha1': sha1,
49 'md5': md5,
50 }
51
52
54 """
55 Server-side SFTP subsystem support. Since this is a `.SubsystemHandler`,
56 it can be (and is meant to be) set as the handler for ``"sftp"`` requests.
57 Use `.Transport.set_subsystem_handler` to activate this class.
58 """
59
61 """
62 The constructor for SFTPServer is meant to be called from within the
63 `.Transport` as a subsystem handler. ``server`` and any additional
64 parameters or keyword parameters are passed from the original call to
65 `.Transport.set_subsystem_handler`.
66
67 :param .Channel channel: channel passed from the `.Transport`.
68 :param str name: name of the requested subsystem.
69 :param .ServerInterface server:
70 the server object associated with this channel and subsystem
71 :param class sftp_si:
72 a subclass of `.SFTPServerInterface` to use for handling individual
73 requests.
74 """
75 BaseSFTP.__init__(self)
76 SubsystemHandler.__init__(self, channel, name, server)
77 transport = channel.get_transport()
78 self.logger = util.get_logger(transport.get_log_channel() + '.sftp')
79 self.ultra_debug = transport.get_hexdump()
80 self.next_handle = 1
81
82 self.file_table = {}
83 self.folder_table = {}
84 self.server = sftp_si(server, *largs, **kwargs)
85
86 - def _log(self, level, msg):
87 if issubclass(type(msg), list):
88 for m in msg:
89 super(SFTPServer, self)._log(level, "[chan " + self.sock.get_name() + "] " + m)
90 else:
91 super(SFTPServer, self)._log(level, "[chan " + self.sock.get_name() + "] " + msg)
92
94 self.sock = channel
95 self._log(DEBUG, 'Started sftp server on channel %s' % repr(channel))
96 self._send_server_version()
97 self.server.session_started()
98 while True:
99 try:
100 t, data = self._read_packet()
101 except EOFError:
102 self._log(DEBUG, 'EOF -- end of session')
103 return
104 except Exception as e:
105 self._log(DEBUG, 'Exception on channel: ' + str(e))
106 self._log(DEBUG, util.tb_strings())
107 return
108 msg = Message(data)
109 request_number = msg.get_int()
110 try:
111 self._process(t, request_number, msg)
112 except Exception as e:
113 self._log(DEBUG, 'Exception in server processing: ' + str(e))
114 self._log(DEBUG, util.tb_strings())
115
116 try:
117 self._send_status(request_number, SFTP_FAILURE)
118 except:
119 pass
120
131
133 """
134 Convert an errno value (as from an ``OSError`` or ``IOError``) into a
135 standard SFTP result code. This is a convenience function for trapping
136 exceptions in server code and returning an appropriate result.
137
138 :param int e: an errno code, as from ``OSError.errno``.
139 :return: an `int` SFTP error code like ``SFTP_NO_SUCH_FILE``.
140 """
141 if e == errno.EACCES:
142
143 return SFTP_PERMISSION_DENIED
144 elif (e == errno.ENOENT) or (e == errno.ENOTDIR):
145
146 return SFTP_NO_SUCH_FILE
147 else:
148 return SFTP_FAILURE
149 convert_errno = staticmethod(convert_errno)
150
152 """
153 Change a file's attributes on the local filesystem. The contents of
154 ``attr`` are used to change the permissions, owner, group ownership,
155 and/or modification & access time of the file, depending on which
156 attributes are present in ``attr``.
157
158 This is meant to be a handy helper function for translating SFTP file
159 requests into local file operations.
160
161 :param str filename:
162 name of the file to alter (should usually be an absolute path).
163 :param .SFTPAttributes attr: attributes to change.
164 """
165 if sys.platform != 'win32':
166
167 if attr._flags & attr.FLAG_PERMISSIONS:
168 os.chmod(filename, attr.st_mode)
169 if attr._flags & attr.FLAG_UIDGID:
170 os.chown(filename, attr.st_uid, attr.st_gid)
171 if attr._flags & attr.FLAG_AMTIME:
172 os.utime(filename, (attr.st_atime, attr.st_mtime))
173 if attr._flags & attr.FLAG_SIZE:
174 with open(filename, 'w+') as f:
175 f.truncate(attr.st_size)
176 set_file_attr = staticmethod(set_file_attr)
177
178
179
180 - def _response(self, request_number, t, *arg):
181 msg = Message()
182 msg.add_int(request_number)
183 for item in arg:
184 if isinstance(item, long):
185 msg.add_int64(item)
186 elif isinstance(item, int):
187 msg.add_int(item)
188 elif isinstance(item, (string_types, bytes_types)):
189 msg.add_string(item)
190 elif type(item) is SFTPAttributes:
191 item._pack(msg)
192 else:
193 raise Exception('unknown type for ' + repr(item) + ' type ' + repr(type(item)))
194 self._send_packet(t, msg)
195
197 if not issubclass(type(handle), SFTPHandle):
198
199 self._send_status(request_number, handle)
200 return
201 handle._set_name(b('hx%d' % self.next_handle))
202 self.next_handle += 1
203 if folder:
204 self.folder_table[handle._get_name()] = handle
205 else:
206 self.file_table[handle._get_name()] = handle
207 self._response(request_number, CMD_HANDLE, handle._get_name())
208
210 if desc is None:
211 try:
212 desc = SFTP_DESC[code]
213 except IndexError:
214 desc = 'Unknown'
215
216 self._response(request_number, CMD_STATUS, code, desc, '')
217
219 resp = self.server.list_folder(path)
220 if issubclass(type(resp), list):
221
222 folder = SFTPHandle()
223 folder._set_files(resp)
224 self._send_handle_response(request_number, folder, True)
225 return
226
227 self._send_status(request_number, resp)
228
230 flist = folder._get_next_files()
231 if len(flist) == 0:
232 self._send_status(request_number, SFTP_EOF)
233 return
234 msg = Message()
235 msg.add_int(request_number)
236 msg.add_int(len(flist))
237 for attr in flist:
238 msg.add_string(attr.filename)
239 msg.add_string(attr)
240 attr._pack(msg)
241 self._send_packet(CMD_NAME, msg)
242
244
245
246
247
248 handle = msg.get_binary()
249 alg_list = msg.get_list()
250 start = msg.get_int64()
251 length = msg.get_int64()
252 block_size = msg.get_int()
253 if handle not in self.file_table:
254 self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
255 return
256 f = self.file_table[handle]
257 for x in alg_list:
258 if x in _hash_class:
259 algname = x
260 alg = _hash_class[x]
261 break
262 else:
263 self._send_status(request_number, SFTP_FAILURE, 'No supported hash types found')
264 return
265 if length == 0:
266 st = f.stat()
267 if not issubclass(type(st), SFTPAttributes):
268 self._send_status(request_number, st, 'Unable to stat file')
269 return
270 length = st.st_size - start
271 if block_size == 0:
272 block_size = length
273 if block_size < 256:
274 self._send_status(request_number, SFTP_FAILURE, 'Block size too small')
275 return
276
277 sum_out = bytes()
278 offset = start
279 while offset < start + length:
280 blocklen = min(block_size, start + length - offset)
281
282 chunklen = min(blocklen, 65536)
283 count = 0
284 hash_obj = alg()
285 while count < blocklen:
286 data = f.read(offset, chunklen)
287 if not isinstance(data, bytes_types):
288 self._send_status(request_number, data, 'Unable to hash file')
289 return
290 hash_obj.update(data)
291 count += len(data)
292 offset += count
293 sum_out += hash_obj.digest()
294
295 msg = Message()
296 msg.add_int(request_number)
297 msg.add_string('check-file')
298 msg.add_string(algname)
299 msg.add_bytes(sum_out)
300 self._send_packet(CMD_EXTENDED_REPLY, msg)
301
319
320 - def _process(self, t, request_number, msg):
321 self._log(DEBUG, 'Request: %s' % CMD_NAMES[t])
322 if t == CMD_OPEN:
323 path = msg.get_text()
324 flags = self._convert_pflags(msg.get_int())
325 attr = SFTPAttributes._from_msg(msg)
326 self._send_handle_response(request_number, self.server.open(path, flags, attr))
327 elif t == CMD_CLOSE:
328 handle = msg.get_binary()
329 if handle in self.folder_table:
330 del self.folder_table[handle]
331 self._send_status(request_number, SFTP_OK)
332 return
333 if handle in self.file_table:
334 self.file_table[handle].close()
335 del self.file_table[handle]
336 self._send_status(request_number, SFTP_OK)
337 return
338 self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
339 elif t == CMD_READ:
340 handle = msg.get_binary()
341 offset = msg.get_int64()
342 length = msg.get_int()
343 if handle not in self.file_table:
344 self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
345 return
346 data = self.file_table[handle].read(offset, length)
347 if isinstance(data, (bytes_types, string_types)):
348 if len(data) == 0:
349 self._send_status(request_number, SFTP_EOF)
350 else:
351 self._response(request_number, CMD_DATA, data)
352 else:
353 self._send_status(request_number, data)
354 elif t == CMD_WRITE:
355 handle = msg.get_binary()
356 offset = msg.get_int64()
357 data = msg.get_binary()
358 if handle not in self.file_table:
359 self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
360 return
361 self._send_status(request_number, self.file_table[handle].write(offset, data))
362 elif t == CMD_REMOVE:
363 path = msg.get_text()
364 self._send_status(request_number, self.server.remove(path))
365 elif t == CMD_RENAME:
366 oldpath = msg.get_text()
367 newpath = msg.get_text()
368 self._send_status(request_number, self.server.rename(oldpath, newpath))
369 elif t == CMD_MKDIR:
370 path = msg.get_text()
371 attr = SFTPAttributes._from_msg(msg)
372 self._send_status(request_number, self.server.mkdir(path, attr))
373 elif t == CMD_RMDIR:
374 path = msg.get_text()
375 self._send_status(request_number, self.server.rmdir(path))
376 elif t == CMD_OPENDIR:
377 path = msg.get_text()
378 self._open_folder(request_number, path)
379 return
380 elif t == CMD_READDIR:
381 handle = msg.get_binary()
382 if handle not in self.folder_table:
383 self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
384 return
385 folder = self.folder_table[handle]
386 self._read_folder(request_number, folder)
387 elif t == CMD_STAT:
388 path = msg.get_text()
389 resp = self.server.stat(path)
390 if issubclass(type(resp), SFTPAttributes):
391 self._response(request_number, CMD_ATTRS, resp)
392 else:
393 self._send_status(request_number, resp)
394 elif t == CMD_LSTAT:
395 path = msg.get_text()
396 resp = self.server.lstat(path)
397 if issubclass(type(resp), SFTPAttributes):
398 self._response(request_number, CMD_ATTRS, resp)
399 else:
400 self._send_status(request_number, resp)
401 elif t == CMD_FSTAT:
402 handle = msg.get_binary()
403 if handle not in self.file_table:
404 self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
405 return
406 resp = self.file_table[handle].stat()
407 if issubclass(type(resp), SFTPAttributes):
408 self._response(request_number, CMD_ATTRS, resp)
409 else:
410 self._send_status(request_number, resp)
411 elif t == CMD_SETSTAT:
412 path = msg.get_text()
413 attr = SFTPAttributes._from_msg(msg)
414 self._send_status(request_number, self.server.chattr(path, attr))
415 elif t == CMD_FSETSTAT:
416 handle = msg.get_binary()
417 attr = SFTPAttributes._from_msg(msg)
418 if handle not in self.file_table:
419 self._response(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
420 return
421 self._send_status(request_number, self.file_table[handle].chattr(attr))
422 elif t == CMD_READLINK:
423 path = msg.get_text()
424 resp = self.server.readlink(path)
425 if isinstance(resp, (bytes_types, string_types)):
426 self._response(request_number, CMD_NAME, 1, resp, '', SFTPAttributes())
427 else:
428 self._send_status(request_number, resp)
429 elif t == CMD_SYMLINK:
430
431 target_path = msg.get_text()
432 path = msg.get_text()
433 self._send_status(request_number, self.server.symlink(target_path, path))
434 elif t == CMD_REALPATH:
435 path = msg.get_text()
436 rpath = self.server.canonicalize(path)
437 self._response(request_number, CMD_NAME, 1, rpath, '', SFTPAttributes())
438 elif t == CMD_EXTENDED:
439 tag = msg.get_text()
440 if tag == 'check-file':
441 self._check_file(request_number, msg)
442 else:
443 self._send_status(request_number, SFTP_OP_UNSUPPORTED)
444 else:
445 self._send_status(request_number, SFTP_OP_UNSUPPORTED)
446
447
448 from paramiko.sftp_handle import SFTPHandle
449