|
33 | 33 | # Modified by Jack to work on the mac.
|
34 | 34 | # Modified by Siebren to support docstrings and PASV.
|
35 | 35 | # Modified by Phil Schwartz to add storbinary and storlines callbacks.
|
| 36 | +# Modified by Giampaolo Rodola' to add TLS support. |
36 | 37 | #
|
37 | 38 |
|
38 | 39 | import os
|
@@ -575,6 +576,181 @@ def close(self):
|
575 | 576 | self.file = self.sock = None
|
576 | 577 |
|
577 | 578 |
|
| 579 | +try: |
| 580 | + import ssl |
| 581 | +except ImportError: |
| 582 | + pass |
| 583 | +else: |
| 584 | + class FTP_TLS(FTP): |
| 585 | + '''A FTP subclass which adds TLS support to FTP as described |
| 586 | + in RFC-4217. |
| 587 | +
|
| 588 | + Connect as usual to port 21 implicitly securing the FTP control |
| 589 | + connection before authenticating. |
| 590 | +
|
| 591 | + Securing the data connection requires user to explicitly ask |
| 592 | + for it by calling prot_p() method. |
| 593 | +
|
| 594 | + Usage example: |
| 595 | + >>> from ftplib import FTP_TLS |
| 596 | + >>> ftps = FTP_TLS('ftp.python.org') |
| 597 | + >>> ftps.login() # login anonimously previously securing control channel |
| 598 | + '230 Guest login ok, access restrictions apply.' |
| 599 | + >>> ftps.prot_p() # switch to secure data connection |
| 600 | + '200 Protection level set to P' |
| 601 | + >>> ftps.retrlines('LIST') # list directory content securely |
| 602 | + total 9 |
| 603 | + drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . |
| 604 | + drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. |
| 605 | + drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin |
| 606 | + drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc |
| 607 | + d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming |
| 608 | + drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib |
| 609 | + drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub |
| 610 | + drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr |
| 611 | + -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg |
| 612 | + '226 Transfer complete.' |
| 613 | + >>> ftps.quit() |
| 614 | + '221 Goodbye.' |
| 615 | + >>> |
| 616 | + ''' |
| 617 | + ssl_version = ssl.PROTOCOL_TLSv1 |
| 618 | + |
| 619 | + def __init__(self, host='', user='', passwd='', acct='', keyfile=None, |
| 620 | + certfile=None, timeout=_GLOBAL_DEFAULT_TIMEOUT): |
| 621 | + self.keyfile = keyfile |
| 622 | + self.certfile = certfile |
| 623 | + self._prot_p = False |
| 624 | + FTP.__init__(self, host, user, passwd, acct, timeout) |
| 625 | + |
| 626 | + def login(self, user='', passwd='', acct='', secure=True): |
| 627 | + if secure and not isinstance(self.sock, ssl.SSLSocket): |
| 628 | + self.auth() |
| 629 | + return FTP.login(self, user, passwd, acct) |
| 630 | + |
| 631 | + def auth(self): |
| 632 | + '''Set up secure control connection by using TLS/SSL.''' |
| 633 | + if isinstance(self.sock, ssl.SSLSocket): |
| 634 | + raise ValueError("Already using TLS") |
| 635 | + if self.ssl_version == ssl.PROTOCOL_TLSv1: |
| 636 | + resp = self.voidcmd('AUTH TLS') |
| 637 | + else: |
| 638 | + resp = self.voidcmd('AUTH SSL') |
| 639 | + self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, |
| 640 | + ssl_version=self.ssl_version) |
| 641 | + self.file = self.sock.makefile(mode='rb') |
| 642 | + return resp |
| 643 | + |
| 644 | + def prot_p(self): |
| 645 | + '''Set up secure data connection.''' |
| 646 | + # PROT defines whether or not the data channel is to be protected. |
| 647 | + # Though RFC-2228 defines four possible protection levels, |
| 648 | + # RFC-4217 only recommends two, Clear and Private. |
| 649 | + # Clear (PROT C) means that no security is to be used on the |
| 650 | + # data-channel, Private (PROT P) means that the data-channel |
| 651 | + # should be protected by TLS. |
| 652 | + # PBSZ command MUST still be issued, but must have a parameter of |
| 653 | + # '0' to indicate that no buffering is taking place and the data |
| 654 | + # connection should not be encapsulated. |
| 655 | + self.voidcmd('PBSZ 0') |
| 656 | + resp = self.voidcmd('PROT P') |
| 657 | + self._prot_p = True |
| 658 | + return resp |
| 659 | + |
| 660 | + def prot_c(self): |
| 661 | + '''Set up clear text data connection.''' |
| 662 | + resp = self.voidcmd('PROT C') |
| 663 | + self._prot_p = False |
| 664 | + return resp |
| 665 | + |
| 666 | + # --- Overridden FTP methods |
| 667 | + |
| 668 | + def ntransfercmd(self, cmd, rest=None): |
| 669 | + conn, size = FTP.ntransfercmd(self, cmd, rest) |
| 670 | + if self._prot_p: |
| 671 | + conn = ssl.wrap_socket(conn, self.keyfile, self.certfile, |
| 672 | + ssl_version=self.ssl_version) |
| 673 | + return conn, size |
| 674 | + |
| 675 | + def retrbinary(self, cmd, callback, blocksize=8192, rest=None): |
| 676 | + self.voidcmd('TYPE I') |
| 677 | + conn = self.transfercmd(cmd, rest) |
| 678 | + try: |
| 679 | + while 1: |
| 680 | + data = conn.recv(blocksize) |
| 681 | + if not data: |
| 682 | + break |
| 683 | + callback(data) |
| 684 | + # shutdown ssl layer |
| 685 | + if isinstance(conn, ssl.SSLSocket): |
| 686 | + conn.unwrap() |
| 687 | + finally: |
| 688 | + conn.close() |
| 689 | + return self.voidresp() |
| 690 | + |
| 691 | + def retrlines(self, cmd, callback = None): |
| 692 | + if callback is None: callback = print_line |
| 693 | + resp = self.sendcmd('TYPE A') |
| 694 | + conn = self.transfercmd(cmd) |
| 695 | + fp = conn.makefile('rb') |
| 696 | + try: |
| 697 | + while 1: |
| 698 | + line = fp.readline() |
| 699 | + if self.debugging > 2: print '*retr*', repr(line) |
| 700 | + if not line: |
| 701 | + break |
| 702 | + if line[-2:] == CRLF: |
| 703 | + line = line[:-2] |
| 704 | + elif line[-1:] == '\n': |
| 705 | + line = line[:-1] |
| 706 | + callback(line) |
| 707 | + # shutdown ssl layer |
| 708 | + if isinstance(conn, ssl.SSLSocket): |
| 709 | + conn.unwrap() |
| 710 | + finally: |
| 711 | + fp.close() |
| 712 | + conn.close() |
| 713 | + return self.voidresp() |
| 714 | + |
| 715 | + def storbinary(self, cmd, fp, blocksize=8192, callback=None): |
| 716 | + self.voidcmd('TYPE I') |
| 717 | + conn = self.transfercmd(cmd) |
| 718 | + try: |
| 719 | + while 1: |
| 720 | + buf = fp.read(blocksize) |
| 721 | + if not buf: break |
| 722 | + conn.sendall(buf) |
| 723 | + if callback: callback(buf) |
| 724 | + # shutdown ssl layer |
| 725 | + if isinstance(conn, ssl.SSLSocket): |
| 726 | + conn.unwrap() |
| 727 | + finally: |
| 728 | + conn.close() |
| 729 | + return self.voidresp() |
| 730 | + |
| 731 | + def storlines(self, cmd, fp, callback=None): |
| 732 | + self.voidcmd('TYPE A') |
| 733 | + conn = self.transfercmd(cmd) |
| 734 | + try: |
| 735 | + while 1: |
| 736 | + buf = fp.readline() |
| 737 | + if not buf: break |
| 738 | + if buf[-2:] != CRLF: |
| 739 | + if buf[-1] in CRLF: buf = buf[:-1] |
| 740 | + buf = buf + CRLF |
| 741 | + conn.sendall(buf) |
| 742 | + if callback: callback(buf) |
| 743 | + # shutdown ssl layer |
| 744 | + if isinstance(conn, ssl.SSLSocket): |
| 745 | + conn.unwrap() |
| 746 | + finally: |
| 747 | + conn.close() |
| 748 | + return self.voidresp() |
| 749 | + |
| 750 | + __all__.append('FTP_TLS') |
| 751 | + all_errors = (Error, IOError, EOFError, ssl.SSLError) |
| 752 | + |
| 753 | + |
578 | 754 | _150_re = None
|
579 | 755 |
|
580 | 756 | def parse150(resp):
|
|
0 commit comments