Home | History | Annotate | Download | only in Lib
      1 """An FTP client class and some helper functions.
      2 
      3 Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds
      4 
      5 Example:
      6 
      7 >>> from ftplib import FTP
      8 >>> ftp = FTP('ftp.python.org') # connect to host, default port
      9 >>> ftp.login() # default, i.e.: user anonymous, passwd anonymous@
     10 '230 Guest login ok, access restrictions apply.'
     11 >>> ftp.retrlines('LIST') # list directory contents
     12 total 9
     13 drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 .
     14 drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 ..
     15 drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 bin
     16 drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 etc
     17 d-wxrwxr-x   2 ftp      wheel        1024 Sep  5 13:43 incoming
     18 drwxr-xr-x   2 root     wheel        1024 Nov 17  1993 lib
     19 drwxr-xr-x   6 1094     wheel        1024 Sep 13 19:07 pub
     20 drwxr-xr-x   3 root     wheel        1024 Jan  3  1994 usr
     21 -rw-r--r--   1 root     root          312 Aug  1  1994 welcome.msg
     22 '226 Transfer complete.'
     23 >>> ftp.quit()
     24 '221 Goodbye.'
     25 >>>
     26 
     27 A nice test that reveals some of the network dialogue would be:
     28 python ftplib.py -d localhost -l -p -l
     29 """
     30 
     31 #

     32 # Changes and improvements suggested by Steve Majewski.

     33 # Modified by Jack to work on the mac.

     34 # Modified by Siebren to support docstrings and PASV.

     35 # Modified by Phil Schwartz to add storbinary and storlines callbacks.

     36 # Modified by Giampaolo Rodola' to add TLS support.

     37 #

     38 
     39 import os
     40 import sys
     41 
     42 # Import SOCKS module if it exists, else standard socket module socket

     43 try:
     44     import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket

     45     from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn
     46 except ImportError:
     47     import socket
     48 from socket import _GLOBAL_DEFAULT_TIMEOUT
     49 
     50 __all__ = ["FTP","Netrc"]
     51 
     52 # Magic number from <socket.h>

     53 MSG_OOB = 0x1                           # Process data out of band

     54 
     55 
     56 # The standard FTP server control port

     57 FTP_PORT = 21
     58 
     59 
     60 # Exception raised when an error or invalid response is received

     61 class Error(Exception): pass
     62 class error_reply(Error): pass          # unexpected [123]xx reply

     63 class error_temp(Error): pass           # 4xx errors

     64 class error_perm(Error): pass           # 5xx errors

     65 class error_proto(Error): pass          # response does not begin with [1-5]

     66 
     67 
     68 # All exceptions (hopefully) that may be raised here and that aren't

     69 # (always) programming errors on our side

     70 all_errors = (Error, IOError, EOFError)
     71 
     72 
     73 # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)

     74 CRLF = '\r\n'
     75 
     76 # The class itself

     77 class FTP:
     78 
     79     '''An FTP client class.
     80 
     81     To create a connection, call the class using these arguments:
     82             host, user, passwd, acct, timeout
     83 
     84     The first four arguments are all strings, and have default value ''.
     85     timeout must be numeric and defaults to None if not passed,
     86     meaning that no timeout will be set on any ftp socket(s)
     87     If a timeout is passed, then this is now the default timeout for all ftp
     88     socket operations for this instance.
     89 
     90     Then use self.connect() with optional host and port argument.
     91 
     92     To download a file, use ftp.retrlines('RETR ' + filename),
     93     or ftp.retrbinary() with slightly different arguments.
     94     To upload a file, use ftp.storlines() or ftp.storbinary(),
     95     which have an open file as argument (see their definitions
     96     below for details).
     97     The download/upload functions first issue appropriate TYPE
     98     and PORT or PASV commands.
     99 '''
    100 
    101     debugging = 0
    102     host = ''
    103     port = FTP_PORT
    104     sock = None
    105     file = None
    106     welcome = None
    107     passiveserver = 1
    108 
    109     # Initialization method (called by class instantiation).

    110     # Initialize host to localhost, port to standard ftp port

    111     # Optional arguments are host (for connect()),

    112     # and user, passwd, acct (for login())

    113     def __init__(self, host='', user='', passwd='', acct='',
    114                  timeout=_GLOBAL_DEFAULT_TIMEOUT):
    115         self.timeout = timeout
    116         if host:
    117             self.connect(host)
    118             if user:
    119                 self.login(user, passwd, acct)
    120 
    121     def connect(self, host='', port=0, timeout=-999):
    122         '''Connect to host.  Arguments are:
    123          - host: hostname to connect to (string, default previous host)
    124          - port: port to connect to (integer, default previous port)
    125         '''
    126         if host != '':
    127             self.host = host
    128         if port > 0:
    129             self.port = port
    130         if timeout != -999:
    131             self.timeout = timeout
    132         self.sock = socket.create_connection((self.host, self.port), self.timeout)
    133         self.af = self.sock.family
    134         self.file = self.sock.makefile('rb')
    135         self.welcome = self.getresp()
    136         return self.welcome
    137 
    138     def getwelcome(self):
    139         '''Get the welcome message from the server.
    140         (this is read and squirreled away by connect())'''
    141         if self.debugging:
    142             print '*welcome*', self.sanitize(self.welcome)
    143         return self.welcome
    144 
    145     def set_debuglevel(self, level):
    146         '''Set the debugging level.
    147         The required argument level means:
    148         0: no debugging output (default)
    149         1: print commands and responses but not body text etc.
    150         2: also print raw lines read and sent before stripping CR/LF'''
    151         self.debugging = level
    152     debug = set_debuglevel
    153 
    154     def set_pasv(self, val):
    155         '''Use passive or active mode for data transfers.
    156         With a false argument, use the normal PORT mode,
    157         With a true argument, use the PASV command.'''
    158         self.passiveserver = val
    159 
    160     # Internal: "sanitize" a string for printing

    161     def sanitize(self, s):
    162         if s[:5] == 'pass ' or s[:5] == 'PASS ':
    163             i = len(s)
    164             while i > 5 and s[i-1] in '\r\n':
    165                 i = i-1
    166             s = s[:5] + '*'*(i-5) + s[i:]
    167         return repr(s)
    168 
    169     # Internal: send one line to the server, appending CRLF

    170     def putline(self, line):
    171         line = line + CRLF
    172         if self.debugging > 1: print '*put*', self.sanitize(line)
    173         self.sock.sendall(line)
    174 
    175     # Internal: send one command to the server (through putline())

    176     def putcmd(self, line):
    177         if self.debugging: print '*cmd*', self.sanitize(line)
    178         self.putline(line)
    179 
    180     # Internal: return one line from the server, stripping CRLF.

    181     # Raise EOFError if the connection is closed

    182     def getline(self):
    183         line = self.file.readline()
    184         if self.debugging > 1:
    185             print '*get*', self.sanitize(line)
    186         if not line: raise EOFError
    187         if line[-2:] == CRLF: line = line[:-2]
    188         elif line[-1:] in CRLF: line = line[:-1]
    189         return line
    190 
    191     # Internal: get a response from the server, which may possibly

    192     # consist of multiple lines.  Return a single string with no

    193     # trailing CRLF.  If the response consists of multiple lines,

    194     # these are separated by '\n' characters in the string

    195     def getmultiline(self):
    196         line = self.getline()
    197         if line[3:4] == '-':
    198             code = line[:3]
    199             while 1:
    200                 nextline = self.getline()
    201                 line = line + ('\n' + nextline)
    202                 if nextline[:3] == code and \
    203                         nextline[3:4] != '-':
    204                     break
    205         return line
    206 
    207     # Internal: get a response from the server.

    208     # Raise various errors if the response indicates an error

    209     def getresp(self):
    210         resp = self.getmultiline()
    211         if self.debugging: print '*resp*', self.sanitize(resp)
    212         self.lastresp = resp[:3]
    213         c = resp[:1]
    214         if c in ('1', '2', '3'):
    215             return resp
    216         if c == '4':
    217             raise error_temp, resp
    218         if c == '5':
    219             raise error_perm, resp
    220         raise error_proto, resp
    221 
    222     def voidresp(self):
    223         """Expect a response beginning with '2'."""
    224         resp = self.getresp()
    225         if resp[:1] != '2':
    226             raise error_reply, resp
    227         return resp
    228 
    229     def abort(self):
    230         '''Abort a file transfer.  Uses out-of-band data.
    231         This does not follow the procedure from the RFC to send Telnet
    232         IP and Synch; that doesn't seem to work with the servers I've
    233         tried.  Instead, just send the ABOR command as OOB data.'''
    234         line = 'ABOR' + CRLF
    235         if self.debugging > 1: print '*put urgent*', self.sanitize(line)
    236         self.sock.sendall(line, MSG_OOB)
    237         resp = self.getmultiline()
    238         if resp[:3] not in ('426', '225', '226'):
    239             raise error_proto, resp
    240 
    241     def sendcmd(self, cmd):
    242         '''Send a command and return the response.'''
    243         self.putcmd(cmd)
    244         return self.getresp()
    245 
    246     def voidcmd(self, cmd):
    247         """Send a command and expect a response beginning with '2'."""
    248         self.putcmd(cmd)
    249         return self.voidresp()
    250 
    251     def sendport(self, host, port):
    252         '''Send a PORT command with the current host and the given
    253         port number.
    254         '''
    255         hbytes = host.split('.')
    256         pbytes = [repr(port//256), repr(port%256)]
    257         bytes = hbytes + pbytes
    258         cmd = 'PORT ' + ','.join(bytes)
    259         return self.voidcmd(cmd)
    260 
    261     def sendeprt(self, host, port):
    262         '''Send a EPRT command with the current host and the given port number.'''
    263         af = 0
    264         if self.af == socket.AF_INET:
    265             af = 1
    266         if self.af == socket.AF_INET6:
    267             af = 2
    268         if af == 0:
    269             raise error_proto, 'unsupported address family'
    270         fields = ['', repr(af), host, repr(port), '']
    271         cmd = 'EPRT ' + '|'.join(fields)
    272         return self.voidcmd(cmd)
    273 
    274     def makeport(self):
    275         '''Create a new socket and send a PORT command for it.'''
    276         msg = "getaddrinfo returns an empty list"
    277         sock = None
    278         for res in socket.getaddrinfo(None, 0, self.af, socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
    279             af, socktype, proto, canonname, sa = res
    280             try:
    281                 sock = socket.socket(af, socktype, proto)
    282                 sock.bind(sa)
    283             except socket.error, msg:
    284                 if sock:
    285                     sock.close()
    286                 sock = None
    287                 continue
    288             break
    289         if not sock:
    290             raise socket.error, msg
    291         sock.listen(1)
    292         port = sock.getsockname()[1] # Get proper port

    293         host = self.sock.getsockname()[0] # Get proper host

    294         if self.af == socket.AF_INET:
    295             resp = self.sendport(host, port)
    296         else:
    297             resp = self.sendeprt(host, port)
    298         if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
    299             sock.settimeout(self.timeout)
    300         return sock
    301 
    302     def makepasv(self):
    303         if self.af == socket.AF_INET:
    304             host, port = parse227(self.sendcmd('PASV'))
    305         else:
    306             host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
    307         return host, port
    308 
    309     def ntransfercmd(self, cmd, rest=None):
    310         """Initiate a transfer over the data connection.
    311 
    312         If the transfer is active, send a port command and the
    313         transfer command, and accept the connection.  If the server is
    314         passive, send a pasv command, connect to it, and start the
    315         transfer command.  Either way, return the socket for the
    316         connection and the expected size of the transfer.  The
    317         expected size may be None if it could not be determined.
    318 
    319         Optional `rest' argument can be a string that is sent as the
    320         argument to a REST command.  This is essentially a server
    321         marker used to tell the server to skip over any data up to the
    322         given marker.
    323         """
    324         size = None
    325         if self.passiveserver:
    326             host, port = self.makepasv()
    327             conn = socket.create_connection((host, port), self.timeout)
    328             if rest is not None:
    329                 self.sendcmd("REST %s" % rest)
    330             resp = self.sendcmd(cmd)
    331             # Some servers apparently send a 200 reply to
    332             # a LIST or STOR command, before the 150 reply
    333             # (and way before the 226 reply). This seems to
    334             # be in violation of the protocol (which only allows
    335             # 1xx or error messages for LIST), so we just discard
    336             # this response.
    337             if resp[0] == '2':
    338                 resp = self.getresp()
    339             if resp[0] != '1':
    340                 raise error_reply, resp
    341         else:
    342             sock = self.makeport()
    343             if rest is not None:
    344                 self.sendcmd("REST %s" % rest)
    345             resp = self.sendcmd(cmd)
    346             # See above.
    347             if resp[0] == '2':
    348                 resp = self.getresp()
    349             if resp[0] != '1':
    350                 raise error_reply, resp
    351             conn, sockaddr = sock.accept()
    352             if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
    353                 conn.settimeout(self.timeout)
    354         if resp[:3] == '150':
    355             # this is conditional in case we received a 125
    356             size = parse150(resp)
    357         return conn, size
    358 
    359     def transfercmd(self, cmd, rest=None):
    360         """Like ntransfercmd() but returns only the socket."""
    361         return self.ntransfercmd(cmd, rest)[0]
    362 
    363     def login(self, user = '', passwd = '', acct = ''):
    364         '''Login, default anonymous.'''
    365         if not user: user = 'anonymous'
    366         if not passwd: passwd = ''
    367         if not acct: acct = ''
    368         if user == 'anonymous' and passwd in ('', '-'):
    369             # If there is no anonymous ftp password specified
    370             # then we'll just use anonymous@
    371             # We don't send any other thing because:

    372             # - We want to remain anonymous

    373             # - We want to stop SPAM

    374             # - We don't want to let ftp sites to discriminate by the user,

    375             #   host or country.

    376             passwd = passwd + 'anonymous@'
    377         resp = self.sendcmd('USER ' + user)
    378         if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
    379         if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
    380         if resp[0] != '2':
    381             raise error_reply, resp
    382         return resp
    383 
    384     def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
    385         """Retrieve data in binary mode.  A new port is created for you.
    386 
    387         Args:
    388           cmd: A RETR command.
    389           callback: A single parameter callable to be called on each
    390                     block of data read.
    391           blocksize: The maximum number of bytes to read from the
    392                      socket at one time.  [default: 8192]
    393           rest: Passed to transfercmd().  [default: None]
    394 
    395         Returns:
    396           The response code.
    397         """
    398         self.voidcmd('TYPE I')
    399         conn = self.transfercmd(cmd, rest)
    400         while 1:
    401             data = conn.recv(blocksize)
    402             if not data:
    403                 break
    404             callback(data)
    405         conn.close()
    406         return self.voidresp()
    407 
    408     def retrlines(self, cmd, callback = None):
    409         """Retrieve data in line mode.  A new port is created for you.
    410 
    411         Args:
    412           cmd: A RETR, LIST, NLST, or MLSD command.
    413           callback: An optional single parameter callable that is called
    414                     for each line with the trailing CRLF stripped.
    415                     [default: print_line()]
    416 
    417         Returns:
    418           The response code.
    419         """
    420         if callback is None: callback = print_line
    421         resp = self.sendcmd('TYPE A')
    422         conn = self.transfercmd(cmd)
    423         fp = conn.makefile('rb')
    424         while 1:
    425             line = fp.readline()
    426             if self.debugging > 2: print '*retr*', repr(line)
    427             if not line:
    428                 break
    429             if line[-2:] == CRLF:
    430                 line = line[:-2]
    431             elif line[-1:] == '\n':
    432                 line = line[:-1]
    433             callback(line)
    434         fp.close()
    435         conn.close()
    436         return self.voidresp()
    437 
    438     def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
    439         """Store a file in binary mode.  A new port is created for you.
    440 
    441         Args:
    442           cmd: A STOR command.
    443           fp: A file-like object with a read(num_bytes) method.
    444           blocksize: The maximum data size to read from fp and send over
    445                      the connection at once.  [default: 8192]
    446           callback: An optional single parameter callable that is called on
    447                     on each block of data after it is sent.  [default: None]
    448           rest: Passed to transfercmd().  [default: None]
    449 
    450         Returns:
    451           The response code.
    452         """
    453         self.voidcmd('TYPE I')
    454         conn = self.transfercmd(cmd, rest)
    455         while 1:
    456             buf = fp.read(blocksize)
    457             if not buf: break
    458             conn.sendall(buf)
    459             if callback: callback(buf)
    460         conn.close()
    461         return self.voidresp()
    462 
    463     def storlines(self, cmd, fp, callback=None):
    464         """Store a file in line mode.  A new port is created for you.
    465 
    466         Args:
    467           cmd: A STOR command.
    468           fp: A file-like object with a readline() method.
    469           callback: An optional single parameter callable that is called on
    470                     on each line after it is sent.  [default: None]
    471 
    472         Returns:
    473           The response code.
    474         """
    475         self.voidcmd('TYPE A')
    476         conn = self.transfercmd(cmd)
    477         while 1:
    478             buf = fp.readline()
    479             if not buf: break
    480             if buf[-2:] != CRLF:
    481                 if buf[-1] in CRLF: buf = buf[:-1]
    482                 buf = buf + CRLF
    483             conn.sendall(buf)
    484             if callback: callback(buf)
    485         conn.close()
    486         return self.voidresp()
    487 
    488     def acct(self, password):
    489         '''Send new account name.'''
    490         cmd = 'ACCT ' + password
    491         return self.voidcmd(cmd)
    492 
    493     def nlst(self, *args):
    494         '''Return a list of files in a given directory (default the current).'''
    495         cmd = 'NLST'
    496         for arg in args:
    497             cmd = cmd + (' ' + arg)
    498         files = []
    499         self.retrlines(cmd, files.append)
    500         return files
    501 
    502     def dir(self, *args):
    503         '''List a directory in long form.
    504         By default list current directory to stdout.
    505         Optional last argument is callback function; all
    506         non-empty arguments before it are concatenated to the
    507         LIST command.  (This *should* only be used for a pathname.)'''
    508         cmd = 'LIST'
    509         func = None
    510         if args[-1:] and type(args[-1]) != type(''):
    511             args, func = args[:-1], args[-1]
    512         for arg in args:
    513             if arg:
    514                 cmd = cmd + (' ' + arg)
    515         self.retrlines(cmd, func)
    516 
    517     def rename(self, fromname, toname):
    518         '''Rename a file.'''
    519         resp = self.sendcmd('RNFR ' + fromname)
    520         if resp[0] != '3':
    521             raise error_reply, resp
    522         return self.voidcmd('RNTO ' + toname)
    523 
    524     def delete(self, filename):
    525         '''Delete a file.'''
    526         resp = self.sendcmd('DELE ' + filename)
    527         if resp[:3] in ('250', '200'):
    528             return resp
    529         else:
    530             raise error_reply, resp
    531 
    532     def cwd(self, dirname):
    533         '''Change to a directory.'''
    534         if dirname == '..':
    535             try:
    536                 return self.voidcmd('CDUP')
    537             except error_perm, msg:
    538                 if msg.args[0][:3] != '500':
    539                     raise
    540         elif dirname == '':
    541             dirname = '.'  # does nothing, but could return error

    542         cmd = 'CWD ' + dirname
    543         return self.voidcmd(cmd)
    544 
    545     def size(self, filename):
    546         '''Retrieve the size of a file.'''
    547         # The SIZE command is defined in RFC-3659

    548         resp = self.sendcmd('SIZE ' + filename)
    549         if resp[:3] == '213':
    550             s = resp[3:].strip()
    551             try:
    552                 return int(s)
    553             except (OverflowError, ValueError):
    554                 return long(s)
    555 
    556     def mkd(self, dirname):
    557         '''Make a directory, return its full pathname.'''
    558         resp = self.sendcmd('MKD ' + dirname)
    559         return parse257(resp)
    560 
    561     def rmd(self, dirname):
    562         '''Remove a directory.'''
    563         return self.voidcmd('RMD ' + dirname)
    564 
    565     def pwd(self):
    566         '''Return current working directory.'''
    567         resp = self.sendcmd('PWD')
    568         return parse257(resp)
    569 
    570     def quit(self):
    571         '''Quit, and close the connection.'''
    572         resp = self.voidcmd('QUIT')
    573         self.close()
    574         return resp
    575 
    576     def close(self):
    577         '''Close the connection without assuming anything about it.'''
    578         if self.file:
    579             self.file.close()
    580             self.sock.close()
    581             self.file = self.sock = None
    582 
    583 
    584 try:
    585     import ssl
    586 except ImportError:
    587     pass
    588 else:
    589     class FTP_TLS(FTP):
    590         '''A FTP subclass which adds TLS support to FTP as described
    591         in RFC-4217.
    592 
    593         Connect as usual to port 21 implicitly securing the FTP control
    594         connection before authenticating.
    595 
    596         Securing the data connection requires user to explicitly ask
    597         for it by calling prot_p() method.
    598 
    599         Usage example:
    600         >>> from ftplib import FTP_TLS
    601         >>> ftps = FTP_TLS('ftp.python.org')
    602         >>> ftps.login()  # login anonymously previously securing control channel
    603         '230 Guest login ok, access restrictions apply.'
    604         >>> ftps.prot_p()  # switch to secure data connection
    605         '200 Protection level set to P'
    606         >>> ftps.retrlines('LIST')  # list directory content securely
    607         total 9
    608         drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 .
    609         drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 ..
    610         drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 bin
    611         drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 etc
    612         d-wxrwxr-x   2 ftp      wheel        1024 Sep  5 13:43 incoming
    613         drwxr-xr-x   2 root     wheel        1024 Nov 17  1993 lib
    614         drwxr-xr-x   6 1094     wheel        1024 Sep 13 19:07 pub
    615         drwxr-xr-x   3 root     wheel        1024 Jan  3  1994 usr
    616         -rw-r--r--   1 root     root          312 Aug  1  1994 welcome.msg
    617         '226 Transfer complete.'
    618         >>> ftps.quit()
    619         '221 Goodbye.'
    620         >>>
    621         '''
    622         ssl_version = ssl.PROTOCOL_TLSv1
    623 
    624         def __init__(self, host='', user='', passwd='', acct='', keyfile=None,
    625                      certfile=None, timeout=_GLOBAL_DEFAULT_TIMEOUT):
    626             self.keyfile = keyfile
    627             self.certfile = certfile
    628             self._prot_p = False
    629             FTP.__init__(self, host, user, passwd, acct, timeout)
    630 
    631         def login(self, user='', passwd='', acct='', secure=True):
    632             if secure and not isinstance(self.sock, ssl.SSLSocket):
    633                 self.auth()
    634             return FTP.login(self, user, passwd, acct)
    635 
    636         def auth(self):
    637             '''Set up secure control connection by using TLS/SSL.'''
    638             if isinstance(self.sock, ssl.SSLSocket):
    639                 raise ValueError("Already using TLS")
    640             if self.ssl_version == ssl.PROTOCOL_TLSv1:
    641                 resp = self.voidcmd('AUTH TLS')
    642             else:
    643                 resp = self.voidcmd('AUTH SSL')
    644             self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile,
    645                                         ssl_version=self.ssl_version)
    646             self.file = self.sock.makefile(mode='rb')
    647             return resp
    648 
    649         def prot_p(self):
    650             '''Set up secure data connection.'''
    651             # PROT defines whether or not the data channel is to be protected.

    652             # Though RFC-2228 defines four possible protection levels,

    653             # RFC-4217 only recommends two, Clear and Private.

    654             # Clear (PROT C) means that no security is to be used on the

    655             # data-channel, Private (PROT P) means that the data-channel

    656             # should be protected by TLS.

    657             # PBSZ command MUST still be issued, but must have a parameter of

    658             # '0' to indicate that no buffering is taking place and the data

    659             # connection should not be encapsulated.

    660             self.voidcmd('PBSZ 0')
    661             resp = self.voidcmd('PROT P')
    662             self._prot_p = True
    663             return resp
    664 
    665         def prot_c(self):
    666             '''Set up clear text data connection.'''
    667             resp = self.voidcmd('PROT C')
    668             self._prot_p = False
    669             return resp
    670 
    671         # --- Overridden FTP methods

    672 
    673         def ntransfercmd(self, cmd, rest=None):
    674             conn, size = FTP.ntransfercmd(self, cmd, rest)
    675             if self._prot_p:
    676                 conn = ssl.wrap_socket(conn, self.keyfile, self.certfile,
    677                                        ssl_version=self.ssl_version)
    678             return conn, size
    679 
    680         def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
    681             self.voidcmd('TYPE I')
    682             conn = self.transfercmd(cmd, rest)
    683             try:
    684                 while 1:
    685                     data = conn.recv(blocksize)
    686                     if not data:
    687                         break
    688                     callback(data)
    689                 # shutdown ssl layer

    690                 if isinstance(conn, ssl.SSLSocket):
    691                     conn.unwrap()
    692             finally:
    693                 conn.close()
    694             return self.voidresp()
    695 
    696         def retrlines(self, cmd, callback = None):
    697             if callback is None: callback = print_line
    698             resp = self.sendcmd('TYPE A')
    699             conn = self.transfercmd(cmd)
    700             fp = conn.makefile('rb')
    701             try:
    702                 while 1:
    703                     line = fp.readline()
    704                     if self.debugging > 2: print '*retr*', repr(line)
    705                     if not line:
    706                         break
    707                     if line[-2:] == CRLF:
    708                         line = line[:-2]
    709                     elif line[-1:] == '\n':
    710                         line = line[:-1]
    711                     callback(line)
    712                 # shutdown ssl layer

    713                 if isinstance(conn, ssl.SSLSocket):
    714                     conn.unwrap()
    715             finally:
    716                 fp.close()
    717                 conn.close()
    718             return self.voidresp()
    719 
    720         def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
    721             self.voidcmd('TYPE I')
    722             conn = self.transfercmd(cmd, rest)
    723             try:
    724                 while 1:
    725                     buf = fp.read(blocksize)
    726                     if not buf: break
    727                     conn.sendall(buf)
    728                     if callback: callback(buf)
    729                 # shutdown ssl layer

    730                 if isinstance(conn, ssl.SSLSocket):
    731                     conn.unwrap()
    732             finally:
    733                 conn.close()
    734             return self.voidresp()
    735 
    736         def storlines(self, cmd, fp, callback=None):
    737             self.voidcmd('TYPE A')
    738             conn = self.transfercmd(cmd)
    739             try:
    740                 while 1:
    741                     buf = fp.readline()
    742                     if not buf: break
    743                     if buf[-2:] != CRLF:
    744                         if buf[-1] in CRLF: buf = buf[:-1]
    745                         buf = buf + CRLF
    746                     conn.sendall(buf)
    747                     if callback: callback(buf)
    748                 # shutdown ssl layer

    749                 if isinstance(conn, ssl.SSLSocket):
    750                     conn.unwrap()
    751             finally:
    752                 conn.close()
    753             return self.voidresp()
    754 
    755     __all__.append('FTP_TLS')
    756     all_errors = (Error, IOError, EOFError, ssl.SSLError)
    757 
    758 
    759 _150_re = None
    760 
    761 def parse150(resp):
    762     '''Parse the '150' response for a RETR request.
    763     Returns the expected transfer size or None; size is not guaranteed to
    764     be present in the 150 message.
    765     '''
    766     if resp[:3] != '150':
    767         raise error_reply, resp
    768     global _150_re
    769     if _150_re is None:
    770         import re
    771         _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
    772     m = _150_re.match(resp)
    773     if not m:
    774         return None
    775     s = m.group(1)
    776     try:
    777         return int(s)
    778     except (OverflowError, ValueError):
    779         return long(s)
    780 
    781 
    782 _227_re = None
    783 
    784 def parse227(resp):
    785     '''Parse the '227' response for a PASV request.
    786     Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
    787     Return ('host.addr.as.numbers', port#) tuple.'''
    788 
    789     if resp[:3] != '227':
    790         raise error_reply, resp
    791     global _227_re
    792     if _227_re is None:
    793         import re
    794         _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)')
    795     m = _227_re.search(resp)
    796     if not m:
    797         raise error_proto, resp
    798     numbers = m.groups()
    799     host = '.'.join(numbers[:4])
    800     port = (int(numbers[4]) << 8) + int(numbers[5])
    801     return host, port
    802 
    803 
    804 def parse229(resp, peer):
    805     '''Parse the '229' response for a EPSV request.
    806     Raises error_proto if it does not contain '(|||port|)'
    807     Return ('host.addr.as.numbers', port#) tuple.'''
    808 
    809     if resp[:3] != '229':
    810         raise error_reply, resp
    811     left = resp.find('(')
    812     if left < 0: raise error_proto, resp
    813     right = resp.find(')', left + 1)
    814     if right < 0:
    815         raise error_proto, resp # should contain '(|||port|)'

    816     if resp[left + 1] != resp[right - 1]:
    817         raise error_proto, resp
    818     parts = resp[left + 1:right].split(resp[left+1])
    819     if len(parts) != 5:
    820         raise error_proto, resp
    821     host = peer[0]
    822     port = int(parts[3])
    823     return host, port
    824 
    825 
    826 def parse257(resp):
    827     '''Parse the '257' response for a MKD or PWD request.
    828     This is a response to a MKD or PWD request: a directory name.
    829     Returns the directoryname in the 257 reply.'''
    830 
    831     if resp[:3] != '257':
    832         raise error_reply, resp
    833     if resp[3:5] != ' "':
    834         return '' # Not compliant to RFC 959, but UNIX ftpd does this

    835     dirname = ''
    836     i = 5
    837     n = len(resp)
    838     while i < n:
    839         c = resp[i]
    840         i = i+1
    841         if c == '"':
    842             if i >= n or resp[i] != '"':
    843                 break
    844             i = i+1
    845         dirname = dirname + c
    846     return dirname
    847 
    848 
    849 def print_line(line):
    850     '''Default retrlines callback to print a line.'''
    851     print line
    852 
    853 
    854 def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
    855     '''Copy file from one FTP-instance to another.'''
    856     if not targetname: targetname = sourcename
    857     type = 'TYPE ' + type
    858     source.voidcmd(type)
    859     target.voidcmd(type)
    860     sourcehost, sourceport = parse227(source.sendcmd('PASV'))
    861     target.sendport(sourcehost, sourceport)
    862     # RFC 959: the user must "listen" [...] BEFORE sending the

    863     # transfer request.

    864     # So: STOR before RETR, because here the target is a "user".

    865     treply = target.sendcmd('STOR ' + targetname)
    866     if treply[:3] not in ('125', '150'): raise error_proto  # RFC 959

    867     sreply = source.sendcmd('RETR ' + sourcename)
    868     if sreply[:3] not in ('125', '150'): raise error_proto  # RFC 959

    869     source.voidresp()
    870     target.voidresp()
    871 
    872 
    873 class Netrc:
    874     """Class to parse & provide access to 'netrc' format files.
    875 
    876     See the netrc(4) man page for information on the file format.
    877 
    878     WARNING: This class is obsolete -- use module netrc instead.
    879 
    880     """
    881     __defuser = None
    882     __defpasswd = None
    883     __defacct = None
    884 
    885     def __init__(self, filename=None):
    886         if filename is None:
    887             if "HOME" in os.environ:
    888                 filename = os.path.join(os.environ["HOME"],
    889                                         ".netrc")
    890             else:
    891                 raise IOError, \
    892                       "specify file to load or set $HOME"
    893         self.__hosts = {}
    894         self.__macros = {}
    895         fp = open(filename, "r")
    896         in_macro = 0
    897         while 1:
    898             line = fp.readline()
    899             if not line: break
    900             if in_macro and line.strip():
    901                 macro_lines.append(line)
    902                 continue
    903             elif in_macro:
    904                 self.__macros[macro_name] = tuple(macro_lines)
    905                 in_macro = 0
    906             words = line.split()
    907             host = user = passwd = acct = None
    908             default = 0
    909             i = 0
    910             while i < len(words):
    911                 w1 = words[i]
    912                 if i+1 < len(words):
    913                     w2 = words[i + 1]
    914                 else:
    915                     w2 = None
    916                 if w1 == 'default':
    917                     default = 1
    918                 elif w1 == 'machine' and w2:
    919                     host = w2.lower()
    920                     i = i + 1
    921                 elif w1 == 'login' and w2:
    922                     user = w2
    923                     i = i + 1
    924                 elif w1 == 'password' and w2:
    925                     passwd = w2
    926                     i = i + 1
    927                 elif w1 == 'account' and w2:
    928                     acct = w2
    929                     i = i + 1
    930                 elif w1 == 'macdef' and w2:
    931                     macro_name = w2
    932                     macro_lines = []
    933                     in_macro = 1
    934                     break
    935                 i = i + 1
    936             if default:
    937                 self.__defuser = user or self.__defuser
    938                 self.__defpasswd = passwd or self.__defpasswd
    939                 self.__defacct = acct or self.__defacct
    940             if host:
    941                 if host in self.__hosts:
    942                     ouser, opasswd, oacct = \
    943                            self.__hosts[host]
    944                     user = user or ouser
    945                     passwd = passwd or opasswd
    946                     acct = acct or oacct
    947                 self.__hosts[host] = user, passwd, acct
    948         fp.close()
    949 
    950     def get_hosts(self):
    951         """Return a list of hosts mentioned in the .netrc file."""
    952         return self.__hosts.keys()
    953 
    954     def get_account(self, host):
    955         """Returns login information for the named host.
    956 
    957         The return value is a triple containing userid,
    958         password, and the accounting field.
    959 
    960         """
    961         host = host.lower()
    962         user = passwd = acct = None
    963         if host in self.__hosts:
    964             user, passwd, acct = self.__hosts[host]
    965         user = user or self.__defuser
    966         passwd = passwd or self.__defpasswd
    967         acct = acct or self.__defacct
    968         return user, passwd, acct
    969 
    970     def get_macros(self):
    971         """Return a list of all defined macro names."""
    972         return self.__macros.keys()
    973 
    974     def get_macro(self, macro):
    975         """Return a sequence of lines which define a named macro."""
    976         return self.__macros[macro]
    977 
    978 
    979 
    980 def test():
    981     '''Test program.
    982     Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...
    983 
    984     -d dir
    985     -l list
    986     -p password
    987     '''
    988 
    989     if len(sys.argv) < 2:
    990         print test.__doc__
    991         sys.exit(0)
    992 
    993     debugging = 0
    994     rcfile = None
    995     while sys.argv[1] == '-d':
    996         debugging = debugging+1
    997         del sys.argv[1]
    998     if sys.argv[1][:2] == '-r':
    999         # get name of alternate ~/.netrc file:

   1000         rcfile = sys.argv[1][2:]
   1001         del sys.argv[1]
   1002     host = sys.argv[1]
   1003     ftp = FTP(host)
   1004     ftp.set_debuglevel(debugging)
   1005     userid = passwd = acct = ''
   1006     try:
   1007         netrc = Netrc(rcfile)
   1008     except IOError:
   1009         if rcfile is not None:
   1010             sys.stderr.write("Could not open account file"
   1011                              " -- using anonymous login.")
   1012     else:
   1013         try:
   1014             userid, passwd, acct = netrc.get_account(host)
   1015         except KeyError:
   1016             # no account for host

   1017             sys.stderr.write(
   1018                     "No account -- using anonymous login.")
   1019     ftp.login(userid, passwd, acct)
   1020     for file in sys.argv[2:]:
   1021         if file[:2] == '-l':
   1022             ftp.dir(file[2:])
   1023         elif file[:2] == '-d':
   1024             cmd = 'CWD'
   1025             if file[2:]: cmd = cmd + ' ' + file[2:]
   1026             resp = ftp.sendcmd(cmd)
   1027         elif file == '-p':
   1028             ftp.set_pasv(not ftp.passiveserver)
   1029         else:
   1030             ftp.retrbinary('RETR ' + file, \
   1031                            sys.stdout.write, 1024)
   1032     ftp.quit()
   1033 
   1034 
   1035 if __name__ == '__main__':
   1036     test()
   1037