Skip to content

Commit ccd5e02

Browse files
committed
Issue #2054: ftplib now provides an FTP_TLS class to do secure FTP using
TLS or SSL. Patch by Giampaolo Rodola'.
1 parent 82864d1 commit ccd5e02

File tree

4 files changed

+450
-5
lines changed

4 files changed

+450
-5
lines changed

Doc/library/ftplib.rst

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,41 @@ The module defines the following items:
4949
.. versionchanged:: 2.6
5050
*timeout* was added.
5151

52+
.. class:: FTP_TLS([host[, user[, passwd[, acct[, keyfile[, certfile[, timeout]]]]]]])
53+
54+
A :class:`FTP` subclass which adds TLS support to FTP as described in
55+
:rfc:`4217`.
56+
Connect as usual to port 21 implicitly securing the FTP control connection
57+
before authenticating. Securing the data connection requires user to
58+
explicitly ask for it by calling :exc:`prot_p()` method.
59+
*keyfile* and *certfile* are optional - they can contain a PEM formatted
60+
private key and certificate chain file for the SSL connection.
61+
62+
.. versionadded:: 2.7 Contributed by Giampaolo Rodola'
63+
64+
65+
Here's a sample session using :class:`FTP_TLS` class:
66+
67+
>>> from ftplib import FTP_TLS
68+
>>> ftps = FTP_TLS('ftp.python.org')
69+
>>> ftps.login() # login anonimously previously securing control channel
70+
>>> ftps.prot_p() # switch to secure data connection
71+
>>> ftps.retrlines('LIST') # list directory content securely
72+
total 9
73+
drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
74+
drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
75+
drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
76+
drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
77+
d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
78+
drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
79+
drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
80+
drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
81+
-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
82+
'226 Transfer complete.'
83+
>>> ftps.quit()
84+
>>>
85+
86+
5287

5388
.. attribute:: all_errors
5489

@@ -329,3 +364,26 @@ followed by ``lines`` for the text version or ``binary`` for the binary version.
329364
:meth:`close` or :meth:`quit` you cannot reopen the connection by issuing
330365
another :meth:`login` method).
331366

367+
368+
FTP_TLS Objects
369+
---------------
370+
371+
:class:`FTP_TLS` class inherits from :class:`FTP`, defining these additional objects:
372+
373+
.. attribute:: FTP_TLS.ssl_version
374+
375+
The SSL version to use (defaults to *TLSv1*).
376+
377+
.. method:: FTP_TLS.auth()
378+
379+
Set up secure control connection by using TLS or SSL, depending on what specified in :meth:`ssl_version` attribute.
380+
381+
.. method:: FTP_TLS.prot_p()
382+
383+
Set up secure data connection.
384+
385+
.. method:: FTP_TLS.prot_c()
386+
387+
Set up clear text data connection.
388+
389+

Lib/ftplib.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
# Modified by Jack to work on the mac.
3434
# Modified by Siebren to support docstrings and PASV.
3535
# Modified by Phil Schwartz to add storbinary and storlines callbacks.
36+
# Modified by Giampaolo Rodola' to add TLS support.
3637
#
3738

3839
import os
@@ -575,6 +576,181 @@ def close(self):
575576
self.file = self.sock = None
576577

577578

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+
578754
_150_re = None
579755

580756
def parse150(resp):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy