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 super(SFTPServer, self)._log(level, "[chan " + self.sock.get_name() + "] " + msg)
79
80 - def start_subsystem(self, name, transport, channel):
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 # send some kind of failure message, at least 103 try: 104 self._send_status(request_number, SFTP_FAILURE) 105 except: 106 pass
107
108 - def finish_subsystem(self):
109 self.server.session_ended() 110 super(SFTPServer, self).finish_subsystem() 111 # close any file handles that were left open (so we can return them to the OS quickly) 112 for f in self.file_table.itervalues(): 113 f.close() 114 for f in self.folder_table.itervalues(): 115 f.close() 116 self.file_table = {} 117 self.folder_table = {}
118
119 - def convert_errno(e):
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 # permission denied 132 return SFTP_PERMISSION_DENIED 133 elif (e == errno.ENOENT) or (e == errno.ENOTDIR): 134 # no such file 135 return SFTP_NO_SUCH_FILE 136 else: 137 return SFTP_FAILURE
138 convert_errno = staticmethod(convert_errno) 139
140 - def set_file_attr(filename, attr):
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 # mode operations are meaningless on win32 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 ### internals... 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
188 - def _send_handle_response(self, request_number, handle, folder=False):
189 if not issubclass(type(handle), SFTPHandle): 190 # must be error code 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
201 - def _send_status(self, request_number, code, desc=None):
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
209 - def _open_folder(self, request_number, path):
210 resp = self.server.list_folder(path) 211 if issubclass(type(resp), list): 212 # got an actual list of filenames in the folder 213 folder = SFTPHandle() 214 folder._set_files(resp) 215 self._send_handle_response(request_number, folder, True) 216 return 217 # must be an error code 218 self._send_status(request_number, resp)
219
220 - def _read_folder(self, request_number, folder):
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
234 - def _check_file(self, request_number, msg):
235 # this extension actually comes from v6 protocol, but since it's an 236 # extension, i feel like we can reasonably support it backported. 237 # it's very useful for verifying uploaded files or checking for 238 # rsync-like differences between local and remote files. 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 # don't try to read more than about 64KB at a time 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
293 - def _convert_pflags(self, pflags):
294 "convert SFTP-style open() flags to python's os.open() flags" 295 if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE): 296 flags = os.O_RDWR 297 elif pflags & SFTP_FLAG_WRITE: 298 flags = os.O_WRONLY 299 else: 300 flags = os.O_RDONLY 301 if pflags & SFTP_FLAG_APPEND: 302 flags |= os.O_APPEND 303 if pflags & SFTP_FLAG_CREATE: 304 flags |= os.O_CREAT 305 if pflags & SFTP_FLAG_TRUNC: 306 flags |= os.O_TRUNC 307 if pflags & SFTP_FLAG_EXCL: 308 flags |= os.O_EXCL 309 return flags
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 # the sftp 2 draft is incorrect here! path always follows target_path 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