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