1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 from binascii import hexlify
21 import errno
22 import os
23 import stat
24 import threading
25 import time
26 import weakref
27 from paramiko import util
28 from paramiko.channel import Channel
29 from paramiko.message import Message
30 from paramiko.common import INFO, DEBUG, o777
31 from paramiko.py3compat import bytestring, b, u, long, string_types, bytes_types
32 from paramiko.sftp import BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, \
33 CMD_NAME, CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE, \
34 SFTP_FLAG_TRUNC, SFTP_FLAG_APPEND, SFTP_FLAG_EXCL, CMD_OPEN, CMD_REMOVE, \
35 CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_STAT, CMD_ATTRS, CMD_LSTAT, \
36 CMD_SYMLINK, CMD_SETSTAT, CMD_READLINK, CMD_REALPATH, CMD_STATUS, SFTP_OK, \
37 SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED
38
39 from paramiko.sftp_attr import SFTPAttributes
40 from paramiko.ssh_exception import SSHException
41 from paramiko.sftp_file import SFTPFile
42
43
45 """
46 decode a string as ascii or utf8 if possible (as required by the sftp
47 protocol). if neither works, just return a byte string because the server
48 probably doesn't know the filename's encoding.
49 """
50 try:
51 return s.encode('ascii')
52 except (UnicodeError, AttributeError):
53 try:
54 return s.decode('utf-8')
55 except UnicodeError:
56 return s
57
58 b_slash = b'/'
59
60
62 """
63 SFTP client object.
64
65 Used to open an SFTP session across an open SSH `.Transport` and perform
66 remote file operations.
67 """
69 """
70 Create an SFTP client from an existing `.Channel`. The channel
71 should already have requested the ``"sftp"`` subsystem.
72
73 An alternate way to create an SFTP client context is by using
74 `from_transport`.
75
76 :param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem
77
78 :raises SSHException: if there's an exception while negotiating
79 sftp
80 """
81 BaseSFTP.__init__(self)
82 self.sock = sock
83 self.ultra_debug = False
84 self.request_number = 1
85
86 self._lock = threading.Lock()
87 self._cwd = None
88
89 self._expecting = weakref.WeakValueDictionary()
90 if type(sock) is Channel:
91
92 transport = self.sock.get_transport()
93 self.logger = util.get_logger(transport.get_log_channel() + '.sftp')
94 self.ultra_debug = transport.get_hexdump()
95 try:
96 server_version = self._send_version()
97 except EOFError:
98 raise SSHException('EOF during negotiation')
99 self._log(INFO, 'Opened sftp connection (server version %d)' % server_version)
100
102 """
103 Create an SFTP client channel from an open `.Transport`.
104
105 :param .Transport t: an open `.Transport` which is already authenticated
106 :return:
107 a new `.SFTPClient` object, referring to an sftp session (channel)
108 across the transport
109 """
110 chan = t.open_session()
111 if chan is None:
112 return None
113 chan.invoke_subsystem('sftp')
114 return cls(chan)
115 from_transport = classmethod(from_transport)
116
117 - def _log(self, level, msg, *args):
118 if isinstance(msg, list):
119 for m in msg:
120 self._log(level, m, *args)
121 else:
122
123 msg = msg.replace('%','%%')
124 super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([self.sock.get_name()] + list(args)))
125
127 """
128 Close the SFTP session and its underlying channel.
129
130 .. versionadded:: 1.4
131 """
132 self._log(INFO, 'sftp session closed.')
133 self.sock.close()
134
136 """
137 Return the underlying `.Channel` object for this SFTP session. This
138 might be useful for doing things like setting a timeout on the channel.
139
140 .. versionadded:: 1.7.1
141 """
142 return self.sock
143
145 """
146 Return a list containing the names of the entries in the given ``path``.
147
148 The list is in arbitrary order. It does not include the special
149 entries ``'.'`` and ``'..'`` even if they are present in the folder.
150 This method is meant to mirror ``os.listdir`` as closely as possible.
151 For a list of full `.SFTPAttributes` objects, see `listdir_attr`.
152
153 :param str path: path to list (defaults to ``'.'``)
154 """
155 return [f.filename for f in self.listdir_attr(path)]
156
158 """
159 Return a list containing `.SFTPAttributes` objects corresponding to
160 files in the given ``path``. The list is in arbitrary order. It does
161 not include the special entries ``'.'`` and ``'..'`` even if they are
162 present in the folder.
163
164 The returned `.SFTPAttributes` objects will each have an additional
165 field: ``longname``, which may contain a formatted string of the file's
166 attributes, in unix format. The content of this string will probably
167 depend on the SFTP server implementation.
168
169 :param str path: path to list (defaults to ``'.'``)
170 :return: list of `.SFTPAttributes` objects
171
172 .. versionadded:: 1.2
173 """
174 path = self._adjust_cwd(path)
175 self._log(DEBUG, 'listdir(%r)' % path)
176 t, msg = self._request(CMD_OPENDIR, path)
177 if t != CMD_HANDLE:
178 raise SFTPError('Expected handle')
179 handle = msg.get_binary()
180 filelist = []
181 while True:
182 try:
183 t, msg = self._request(CMD_READDIR, handle)
184 except EOFError:
185
186 break
187 if t != CMD_NAME:
188 raise SFTPError('Expected name response')
189 count = msg.get_int()
190 for i in range(count):
191 filename = msg.get_text()
192 longname = msg.get_text()
193 attr = SFTPAttributes._from_msg(msg, filename, longname)
194 if (filename != '.') and (filename != '..'):
195 filelist.append(attr)
196 self._request(CMD_CLOSE, handle)
197 return filelist
198
199 - def open(self, filename, mode='r', bufsize=-1):
200 """
201 Open a file on the remote server. The arguments are the same as for
202 Python's built-in `python:file` (aka `python:open`). A file-like
203 object is returned, which closely mimics the behavior of a normal
204 Python file object, including the ability to be used as a context
205 manager.
206
207 The mode indicates how the file is to be opened: ``'r'`` for reading,
208 ``'w'`` for writing (truncating an existing file), ``'a'`` for
209 appending, ``'r+'`` for reading/writing, ``'w+'`` for reading/writing
210 (truncating an existing file), ``'a+'`` for reading/appending. The
211 Python ``'b'`` flag is ignored, since SSH treats all files as binary.
212 The ``'U'`` flag is supported in a compatible way.
213
214 Since 1.5.2, an ``'x'`` flag indicates that the operation should only
215 succeed if the file was created and did not previously exist. This has
216 no direct mapping to Python's file flags, but is commonly known as the
217 ``O_EXCL`` flag in posix.
218
219 The file will be buffered in standard Python style by default, but
220 can be altered with the ``bufsize`` parameter. ``0`` turns off
221 buffering, ``1`` uses line buffering, and any number greater than 1
222 (``>1``) uses that specific buffer size.
223
224 :param str filename: name of the file to open
225 :param str mode: mode (Python-style) to open in
226 :param int bufsize: desired buffering (-1 = default buffer size)
227 :return: an `.SFTPFile` object representing the open file
228
229 :raises IOError: if the file could not be opened.
230 """
231 filename = self._adjust_cwd(filename)
232 self._log(DEBUG, 'open(%r, %r)' % (filename, mode))
233 imode = 0
234 if ('r' in mode) or ('+' in mode):
235 imode |= SFTP_FLAG_READ
236 if ('w' in mode) or ('+' in mode) or ('a' in mode):
237 imode |= SFTP_FLAG_WRITE
238 if 'w' in mode:
239 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
240 if 'a' in mode:
241 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
242 if 'x' in mode:
243 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
244 attrblock = SFTPAttributes()
245 t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
246 if t != CMD_HANDLE:
247 raise SFTPError('Expected handle')
248 handle = msg.get_binary()
249 self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle)))
250 return SFTPFile(self, handle, mode, bufsize)
251
252
253 file = open
254
256 """
257 Remove the file at the given path. This only works on files; for
258 removing folders (directories), use `rmdir`.
259
260 :param str path: path (absolute or relative) of the file to remove
261
262 :raises IOError: if the path refers to a folder (directory)
263 """
264 path = self._adjust_cwd(path)
265 self._log(DEBUG, 'remove(%r)' % path)
266 self._request(CMD_REMOVE, path)
267
268 unlink = remove
269
270 - def rename(self, oldpath, newpath):
271 """
272 Rename a file or folder from ``oldpath`` to ``newpath``.
273
274 :param str oldpath: existing name of the file or folder
275 :param str newpath: new name for the file or folder
276
277 :raises IOError: if ``newpath`` is a folder, or something else goes
278 wrong
279 """
280 oldpath = self._adjust_cwd(oldpath)
281 newpath = self._adjust_cwd(newpath)
282 self._log(DEBUG, 'rename(%r, %r)' % (oldpath, newpath))
283 self._request(CMD_RENAME, oldpath, newpath)
284
286 """
287 Create a folder (directory) named ``path`` with numeric mode ``mode``.
288 The default mode is 0777 (octal). On some systems, mode is ignored.
289 Where it is used, the current umask value is first masked out.
290
291 :param str path: name of the folder to create
292 :param int mode: permissions (posix-style) for the newly-created folder
293 """
294 path = self._adjust_cwd(path)
295 self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode))
296 attr = SFTPAttributes()
297 attr.st_mode = mode
298 self._request(CMD_MKDIR, path, attr)
299
301 """
302 Remove the folder named ``path``.
303
304 :param str path: name of the folder to remove
305 """
306 path = self._adjust_cwd(path)
307 self._log(DEBUG, 'rmdir(%r)' % path)
308 self._request(CMD_RMDIR, path)
309
310 - def stat(self, path):
311 """
312 Retrieve information about a file on the remote system. The return
313 value is an object whose attributes correspond to the attributes of
314 Python's ``stat`` structure as returned by ``os.stat``, except that it
315 contains fewer fields. An SFTP server may return as much or as little
316 info as it wants, so the results may vary from server to server.
317
318 Unlike a Python `python:stat` object, the result may not be accessed as
319 a tuple. This is mostly due to the author's slack factor.
320
321 The fields supported are: ``st_mode``, ``st_size``, ``st_uid``,
322 ``st_gid``, ``st_atime``, and ``st_mtime``.
323
324 :param str path: the filename to stat
325 :return:
326 an `.SFTPAttributes` object containing attributes about the given
327 file
328 """
329 path = self._adjust_cwd(path)
330 self._log(DEBUG, 'stat(%r)' % path)
331 t, msg = self._request(CMD_STAT, path)
332 if t != CMD_ATTRS:
333 raise SFTPError('Expected attributes')
334 return SFTPAttributes._from_msg(msg)
335
337 """
338 Retrieve information about a file on the remote system, without
339 following symbolic links (shortcuts). This otherwise behaves exactly
340 the same as `stat`.
341
342 :param str path: the filename to stat
343 :return:
344 an `.SFTPAttributes` object containing attributes about the given
345 file
346 """
347 path = self._adjust_cwd(path)
348 self._log(DEBUG, 'lstat(%r)' % path)
349 t, msg = self._request(CMD_LSTAT, path)
350 if t != CMD_ATTRS:
351 raise SFTPError('Expected attributes')
352 return SFTPAttributes._from_msg(msg)
353
355 """
356 Create a symbolic link (shortcut) of the ``source`` path at
357 ``destination``.
358
359 :param str source: path of the original file
360 :param str dest: path of the newly created symlink
361 """
362 dest = self._adjust_cwd(dest)
363 self._log(DEBUG, 'symlink(%r, %r)' % (source, dest))
364 source = bytestring(source)
365 self._request(CMD_SYMLINK, source, dest)
366
367 - def chmod(self, path, mode):
368 """
369 Change the mode (permissions) of a file. The permissions are
370 unix-style and identical to those used by Python's `os.chmod`
371 function.
372
373 :param str path: path of the file to change the permissions of
374 :param int mode: new permissions
375 """
376 path = self._adjust_cwd(path)
377 self._log(DEBUG, 'chmod(%r, %r)' % (path, mode))
378 attr = SFTPAttributes()
379 attr.st_mode = mode
380 self._request(CMD_SETSTAT, path, attr)
381
382 - def chown(self, path, uid, gid):
383 """
384 Change the owner (``uid``) and group (``gid``) of a file. As with
385 Python's `os.chown` function, you must pass both arguments, so if you
386 only want to change one, use `stat` first to retrieve the current
387 owner and group.
388
389 :param str path: path of the file to change the owner and group of
390 :param int uid: new owner's uid
391 :param int gid: new group id
392 """
393 path = self._adjust_cwd(path)
394 self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid))
395 attr = SFTPAttributes()
396 attr.st_uid, attr.st_gid = uid, gid
397 self._request(CMD_SETSTAT, path, attr)
398
399 - def utime(self, path, times):
400 """
401 Set the access and modified times of the file specified by ``path``. If
402 ``times`` is ``None``, then the file's access and modified times are set
403 to the current time. Otherwise, ``times`` must be a 2-tuple of numbers,
404 of the form ``(atime, mtime)``, which is used to set the access and
405 modified times, respectively. This bizarre API is mimicked from Python
406 for the sake of consistency -- I apologize.
407
408 :param str path: path of the file to modify
409 :param tuple times:
410 ``None`` or a tuple of (access time, modified time) in standard
411 internet epoch time (seconds since 01 January 1970 GMT)
412 """
413 path = self._adjust_cwd(path)
414 if times is None:
415 times = (time.time(), time.time())
416 self._log(DEBUG, 'utime(%r, %r)' % (path, times))
417 attr = SFTPAttributes()
418 attr.st_atime, attr.st_mtime = times
419 self._request(CMD_SETSTAT, path, attr)
420
422 """
423 Change the size of the file specified by ``path``. This usually
424 extends or shrinks the size of the file, just like the `~file.truncate`
425 method on Python file objects.
426
427 :param str path: path of the file to modify
428 :param size: the new size of the file
429 :type size: int or long
430 """
431 path = self._adjust_cwd(path)
432 self._log(DEBUG, 'truncate(%r, %r)' % (path, size))
433 attr = SFTPAttributes()
434 attr.st_size = size
435 self._request(CMD_SETSTAT, path, attr)
436
438 """
439 Return the target of a symbolic link (shortcut). You can use
440 `symlink` to create these. The result may be either an absolute or
441 relative pathname.
442
443 :param str path: path of the symbolic link file
444 :return: target path, as a `str`
445 """
446 path = self._adjust_cwd(path)
447 self._log(DEBUG, 'readlink(%r)' % path)
448 t, msg = self._request(CMD_READLINK, path)
449 if t != CMD_NAME:
450 raise SFTPError('Expected name response')
451 count = msg.get_int()
452 if count == 0:
453 return None
454 if count != 1:
455 raise SFTPError('Readlink returned %d results' % count)
456 return _to_unicode(msg.get_string())
457
459 """
460 Return the normalized path (on the server) of a given path. This
461 can be used to quickly resolve symbolic links or determine what the
462 server is considering to be the "current folder" (by passing ``'.'``
463 as ``path``).
464
465 :param str path: path to be normalized
466 :return: normalized form of the given path (as a `str`)
467
468 :raises IOError: if the path can't be resolved on the server
469 """
470 path = self._adjust_cwd(path)
471 self._log(DEBUG, 'normalize(%r)' % path)
472 t, msg = self._request(CMD_REALPATH, path)
473 if t != CMD_NAME:
474 raise SFTPError('Expected name response')
475 count = msg.get_int()
476 if count != 1:
477 raise SFTPError('Realpath returned %d results' % count)
478 return msg.get_text()
479
480 - def chdir(self, path=None):
481 """
482 Change the "current directory" of this SFTP session. Since SFTP
483 doesn't really have the concept of a current working directory, this is
484 emulated by Paramiko. Once you use this method to set a working
485 directory, all operations on this `.SFTPClient` object will be relative
486 to that path. You can pass in ``None`` to stop using a current working
487 directory.
488
489 :param str path: new current working directory
490
491 :raises IOError: if the requested path doesn't exist on the server
492
493 .. versionadded:: 1.4
494 """
495 if path is None:
496 self._cwd = None
497 return
498 if not stat.S_ISDIR(self.stat(path).st_mode):
499 raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path))
500 self._cwd = b(self.normalize(path))
501
503 """
504 Return the "current working directory" for this SFTP session, as
505 emulated by Paramiko. If no directory has been set with `chdir`,
506 this method will return ``None``.
507
508 .. versionadded:: 1.4
509 """
510 return self._cwd and u(self._cwd)
511
512 - def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
513 """
514 Copy the contents of an open file object (``fl``) to the SFTP server as
515 ``remotepath``. Any exception raised by operations will be passed
516 through.
517
518 The SFTP operations use pipelining for speed.
519
520 :param file fl: opened file or file-like object to copy
521 :param str remotepath: the destination path on the SFTP server
522 :param int file_size:
523 optional size parameter passed to callback. If none is specified,
524 size defaults to 0
525 :param callable callback:
526 optional callback function (form: ``func(int, int)``) that accepts
527 the bytes transferred so far and the total bytes to be transferred
528 (since 1.7.4)
529 :param bool confirm:
530 whether to do a stat() on the file afterwards to confirm the file
531 size (since 1.7.7)
532
533 :return:
534 an `.SFTPAttributes` object containing attributes about the given
535 file.
536
537 .. versionadded:: 1.4
538 .. versionchanged:: 1.7.4
539 Began returning rich attribute objects.
540 """
541 with self.file(remotepath, 'wb') as fr:
542 fr.set_pipelined(True)
543 size = 0
544 while True:
545 data = fl.read(32768)
546 fr.write(data)
547 size += len(data)
548 if callback is not None:
549 callback(size, file_size)
550 if len(data) == 0:
551 break
552 if confirm:
553 s = self.stat(remotepath)
554 if s.st_size != size:
555 raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
556 else:
557 s = SFTPAttributes()
558 return s
559
560 - def put(self, localpath, remotepath, callback=None, confirm=True):
561 """
562 Copy a local file (``localpath``) to the SFTP server as ``remotepath``.
563 Any exception raised by operations will be passed through. This
564 method is primarily provided as a convenience.
565
566 The SFTP operations use pipelining for speed.
567
568 :param str localpath: the local file to copy
569 :param str remotepath: the destination path on the SFTP server
570 :param callable callback:
571 optional callback function (form: ``func(int, int)``) that accepts
572 the bytes transferred so far and the total bytes to be transferred
573 :param bool confirm:
574 whether to do a stat() on the file afterwards to confirm the file
575 size
576
577 :return: an `.SFTPAttributes` object containing attributes about the given file
578
579 .. versionadded:: 1.4
580 .. versionchanged:: 1.7.4
581 ``callback`` and rich attribute return value added.
582 .. versionchanged:: 1.7.7
583 ``confirm`` param added.
584 """
585 file_size = os.stat(localpath).st_size
586 with open(localpath, 'rb') as fl:
587 return self.putfo(fl, remotepath, os.stat(localpath).st_size, callback, confirm)
588
589 - def getfo(self, remotepath, fl, callback=None):
590 """
591 Copy a remote file (``remotepath``) from the SFTP server and write to
592 an open file or file-like object, ``fl``. Any exception raised by
593 operations will be passed through. This method is primarily provided
594 as a convenience.
595
596 :param object remotepath: opened file or file-like object to copy to
597 :param str fl:
598 the destination path on the local host or open file object
599 :param callable callback:
600 optional callback function (form: ``func(int, int)``) that accepts
601 the bytes transferred so far and the total bytes to be transferred
602 :return: the `number <int>` of bytes written to the opened file object
603
604 .. versionadded:: 1.4
605 .. versionchanged:: 1.7.4
606 Added the ``callable`` param.
607 """
608 with self.open(remotepath, 'rb') as fr:
609 file_size = self.stat(remotepath).st_size
610 fr.prefetch()
611 size = 0
612 while True:
613 data = fr.read(32768)
614 fl.write(data)
615 size += len(data)
616 if callback is not None:
617 callback(size, file_size)
618 if len(data) == 0:
619 break
620 return size
621
622 - def get(self, remotepath, localpath, callback=None):
623 """
624 Copy a remote file (``remotepath``) from the SFTP server to the local
625 host as ``localpath``. Any exception raised by operations will be
626 passed through. This method is primarily provided as a convenience.
627
628 :param str remotepath: the remote file to copy
629 :param str localpath: the destination path on the local host
630 :param callable callback:
631 optional callback function (form: ``func(int, int)``) that accepts
632 the bytes transferred so far and the total bytes to be transferred
633
634 .. versionadded:: 1.4
635 .. versionchanged:: 1.7.4
636 Added the ``callback`` param
637 """
638 file_size = self.stat(remotepath).st_size
639 with open(localpath, 'wb') as fl:
640 size = self.getfo(remotepath, fl, callback)
641 s = os.stat(localpath)
642 if s.st_size != size:
643 raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
644
645
646
648 num = self._async_request(type(None), t, *arg)
649 return self._read_response(num)
650
652
653 self._lock.acquire()
654 try:
655 msg = Message()
656 msg.add_int(self.request_number)
657 for item in arg:
658 if isinstance(item, long):
659 msg.add_int64(item)
660 elif isinstance(item, int):
661 msg.add_int(item)
662 elif isinstance(item, (string_types, bytes_types)):
663 msg.add_string(item)
664 elif isinstance(item, SFTPAttributes):
665 item._pack(msg)
666 else:
667 raise Exception('unknown type for %r type %r' % (item, type(item)))
668 num = self.request_number
669 self._expecting[num] = fileobj
670 self._send_packet(t, msg)
671 self.request_number += 1
672 finally:
673 self._lock.release()
674 return num
675
677 while True:
678 try:
679 t, data = self._read_packet()
680 except EOFError as e:
681 raise SSHException('Server connection dropped: %s' % str(e))
682 msg = Message(data)
683 num = msg.get_int()
684 if num not in self._expecting:
685
686 self._log(DEBUG, 'Unexpected response #%d' % (num,))
687 if waitfor is None:
688
689 break
690 continue
691 fileobj = self._expecting[num]
692 del self._expecting[num]
693 if num == waitfor:
694
695 if t == CMD_STATUS:
696 self._convert_status(msg)
697 return t, msg
698 if fileobj is not type(None):
699 fileobj._async_response(t, msg, num)
700 if waitfor is None:
701
702 break
703 return None, None
704
706 while fileobj in self._expecting.values():
707 self._read_response()
708 fileobj._check_exception()
709
727
729 """
730 Return an adjusted path if we're emulating a "current working
731 directory" for the server.
732 """
733 path = b(path)
734 if self._cwd is None:
735 return path
736 if len(path) and path[0:1] == b_slash:
737
738 return path
739 if self._cwd == b_slash:
740 return self._cwd + path
741 return self._cwd + b_slash + path
742
743
744 -class SFTP(SFTPClient):
745 """
746 An alias for `.SFTPClient` for backwards compatability.
747 """
748 pass
749