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