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