Skip to content

Commit f988cd0

Browse files
committed
Merged revisions 76309 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r76309 | antoine.pitrou | 2009-11-15 18:22:09 +0100 (dim., 15 nov. 2009) | 4 lines Issue #2054: ftplib now provides an FTP_TLS class to do secure FTP using TLS or SSL. Patch by Giampaolo Rodola'. ........
1 parent 1309adb commit f988cd0

4 files changed

Lines changed: 453 additions & 5 deletions

File tree

Doc/library/ftplib.rst

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,42 @@ The module defines the following items:
4646
connection attempt (if is not specified, the global default timeout setting
4747
will be used).
4848

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

5187
The set of all exceptions (as a tuple) that methods of :class:`FTP`
@@ -312,3 +348,26 @@ followed by ``lines`` for the text version or ``binary`` for the binary version.
312348
:meth:`close` or :meth:`quit` you cannot reopen the connection by issuing
313349
another :meth:`login` method).
314350

351+
352+
FTP_TLS Objects
353+
---------------
354+
355+
:class:`FTP_TLS` class inherits from :class:`FTP`, defining these additional objects:
356+
357+
.. attribute:: FTP_TLS.ssl_version
358+
359+
The SSL version to use (defaults to *TLSv1*).
360+
361+
.. method:: FTP_TLS.auth()
362+
363+
Set up secure control connection by using TLS or SSL, depending on what specified in :meth:`ssl_version` attribute.
364+
365+
.. method:: FTP_TLS.prot_p()
366+
367+
Set up secure data connection.
368+
369+
.. method:: FTP_TLS.prot_c()
370+
371+
Set up clear text data connection.
372+
373+

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
@@ -577,6 +578,181 @@ def close(self):
577578
self.file = self.sock = None
578579

579580

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

582758
def parse150(resp):

0 commit comments

Comments
 (0)