Package paramiko :: Module sftp_server
[frames] | no frames]

Source Code for Module paramiko.sftp_server

  1  # Copyright (C) 2003-2007  Robey Pointer <robey@lag.net> 
  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 distrubuted 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   
 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  # known hash algorithms for the "check-file" extension 
 35  _hash_class = { 
 36      'sha1': SHA, 
 37      'md5': MD5, 
 38  } 
 39   
 40   
41 -class SFTPServer (BaseSFTP, SubsystemHandler):
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
48 - def __init__(self, channel, name, server, sftp_si=SFTPServerInterface, *largs, **kwargs):
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 # map of handle-string to SFTPHandle for files & folders: 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
84 - def start_subsystem(self, name, transport, channel):
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 # send some kind of failure message, at least 107 try: 108 self._send_status(request_number, SFTP_FAILURE) 109 except: 110 pass
111
112 - def finish_subsystem(self):
113 self.server.session_ended() 114 super(SFTPServer, self).finish_subsystem() 115 # close any file handles that were left open (so we can return them to the OS quickly) 116 for f in self.file_table.itervalues(): 117 f.close() 118 for f in self.folder_table.itervalues(): 119 f.close() 120 self.file_table = {} 121 self.folder_table = {}
122
123 - def convert_errno(e):
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 # permission denied 136 return SFTP_PERMISSION_DENIED 137 elif (e == errno.ENOENT) or (e == errno.ENOTDIR): 138 # no such file 139 return SFTP_NO_SUCH_FILE 140 else: 141 return SFTP_FAILURE
142 convert_errno = staticmethod(convert_errno) 143
144 - def set_file_attr(filename, attr):
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 # mode operations are meaningless on win32 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 ### internals... 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
192 - def _send_handle_response(self, request_number, handle, folder=False):
193 if not issubclass(type(handle), SFTPHandle): 194 # must be error code 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
205 - def _send_status(self, request_number, code, desc=None):
206 if desc is None: 207 try: 208 desc = SFTP_DESC[code] 209 except IndexError: 210 desc = 'Unknown' 211 # some clients expect a "langauge" tag at the end (but don't mind it being blank) 212 self._response(request_number, CMD_STATUS, code, desc, '')
213
214 - def _open_folder(self, request_number, path):
215 resp = self.server.list_folder(path) 216 if issubclass(type(resp), list): 217 # got an actual list of filenames in the folder 218 folder = SFTPHandle() 219 folder._set_files(resp) 220 self._send_handle_response(request_number, folder, True) 221 return 222 # must be an error code 223 self._send_status(request_number, resp)
224
225 - def _read_folder(self, request_number, folder):
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
239 - def _check_file(self, request_number, msg):
240 # this extension actually comes from v6 protocol, but since it's an 241 # extension, i feel like we can reasonably support it backported. 242 # it's very useful for verifying uploaded files or checking for 243 # rsync-like differences between local and remote files. 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 # don't try to read more than about 64KB at a time 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
298 - def _convert_pflags(self, pflags):
299 "convert SFTP-style open() flags to python's os.open() flags" 300 if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE): 301 flags = os.O_RDWR 302 elif pflags & SFTP_FLAG_WRITE: 303 flags = os.O_WRONLY 304 else: 305 flags = os.O_RDONLY 306 if pflags & SFTP_FLAG_APPEND: 307 flags |= os.O_APPEND 308 if pflags & SFTP_FLAG_CREATE: 309 flags |= os.O_CREAT 310 if pflags & SFTP_FLAG_TRUNC: 311 flags |= os.O_TRUNC 312 if pflags & SFTP_FLAG_EXCL: 313 flags |= os.O_EXCL 314 return flags
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 # the sftp 2 draft is incorrect here! path always follows target_path 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