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 sys
40 import socket
41 from socket import _GLOBAL_DEFAULT_TIMEOUT
42
43 __all__ = ["FTP", "error_reply", "error_temp", "error_perm", "error_proto",
44 "all_errors"]
45
46 # Magic number from <socket.h>
47 MSG_OOB = 0x1 # Process data out of band
48
49
50 # The standard FTP server control port
51 FTP_PORT = 21
52 # The sizehint parameter passed to readline() calls
53 MAXLINE = 8192
54
55
56 # Exception raised when an error or invalid response is received
57 class ESC[4;38;5;81mError(ESC[4;38;5;149mException): pass
58 class ESC[4;38;5;81merror_reply(ESC[4;38;5;149mError): pass # unexpected [123]xx reply
59 class ESC[4;38;5;81merror_temp(ESC[4;38;5;149mError): pass # 4xx errors
60 class ESC[4;38;5;81merror_perm(ESC[4;38;5;149mError): pass # 5xx errors
61 class ESC[4;38;5;81merror_proto(ESC[4;38;5;149mError): pass # response does not begin with [1-5]
62
63
64 # All exceptions (hopefully) that may be raised here and that aren't
65 # (always) programming errors on our side
66 all_errors = (Error, OSError, EOFError)
67
68
69 # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
70 CRLF = '\r\n'
71 B_CRLF = b'\r\n'
72
73 # The class itself
74 class ESC[4;38;5;81mFTP:
75 '''An FTP client class.
76
77 To create a connection, call the class using these arguments:
78 host, user, passwd, acct, timeout, source_address, encoding
79
80 The first four arguments are all strings, and have default value ''.
81 The parameter ´timeout´ must be numeric and defaults to None if not
82 passed, meaning that no timeout will be set on any ftp socket(s).
83 If a timeout is passed, then this is now the default timeout for all ftp
84 socket operations for this instance.
85 The last parameter is the encoding of filenames, which defaults to utf-8.
86
87 Then use self.connect() with optional host and port argument.
88
89 To download a file, use ftp.retrlines('RETR ' + filename),
90 or ftp.retrbinary() with slightly different arguments.
91 To upload a file, use ftp.storlines() or ftp.storbinary(),
92 which have an open file as argument (see their definitions
93 below for details).
94 The download/upload functions first issue appropriate TYPE
95 and PORT or PASV commands.
96 '''
97
98 debugging = 0
99 host = ''
100 port = FTP_PORT
101 maxline = MAXLINE
102 sock = None
103 file = None
104 welcome = None
105 passiveserver = True
106 # Disables https://bugs.python.org/issue43285 security if set to True.
107 trust_server_pasv_ipv4_address = False
108
109 def __init__(self, host='', user='', passwd='', acct='',
110 timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *,
111 encoding='utf-8'):
112 """Initialization method (called by class instantiation).
113 Initialize host to localhost, port to standard ftp port.
114 Optional arguments are host (for connect()),
115 and user, passwd, acct (for login()).
116 """
117 self.encoding = encoding
118 self.source_address = source_address
119 self.timeout = timeout
120 if host:
121 self.connect(host)
122 if user:
123 self.login(user, passwd, acct)
124
125 def __enter__(self):
126 return self
127
128 # Context management protocol: try to quit() if active
129 def __exit__(self, *args):
130 if self.sock is not None:
131 try:
132 self.quit()
133 except (OSError, EOFError):
134 pass
135 finally:
136 if self.sock is not None:
137 self.close()
138
139 def connect(self, host='', port=0, timeout=-999, source_address=None):
140 '''Connect to host. Arguments are:
141 - host: hostname to connect to (string, default previous host)
142 - port: port to connect to (integer, default previous port)
143 - timeout: the timeout to set against the ftp socket(s)
144 - source_address: a 2-tuple (host, port) for the socket to bind
145 to as its source address before connecting.
146 '''
147 if host != '':
148 self.host = host
149 if port > 0:
150 self.port = port
151 if timeout != -999:
152 self.timeout = timeout
153 if self.timeout is not None and not self.timeout:
154 raise ValueError('Non-blocking socket (timeout=0) is not supported')
155 if source_address is not None:
156 self.source_address = source_address
157 sys.audit("ftplib.connect", self, self.host, self.port)
158 self.sock = socket.create_connection((self.host, self.port), self.timeout,
159 source_address=self.source_address)
160 self.af = self.sock.family
161 self.file = self.sock.makefile('r', encoding=self.encoding)
162 self.welcome = self.getresp()
163 return self.welcome
164
165 def getwelcome(self):
166 '''Get the welcome message from the server.
167 (this is read and squirreled away by connect())'''
168 if self.debugging:
169 print('*welcome*', self.sanitize(self.welcome))
170 return self.welcome
171
172 def set_debuglevel(self, level):
173 '''Set the debugging level.
174 The required argument level means:
175 0: no debugging output (default)
176 1: print commands and responses but not body text etc.
177 2: also print raw lines read and sent before stripping CR/LF'''
178 self.debugging = level
179 debug = set_debuglevel
180
181 def set_pasv(self, val):
182 '''Use passive or active mode for data transfers.
183 With a false argument, use the normal PORT mode,
184 With a true argument, use the PASV command.'''
185 self.passiveserver = val
186
187 # Internal: "sanitize" a string for printing
188 def sanitize(self, s):
189 if s[:5] in {'pass ', 'PASS '}:
190 i = len(s.rstrip('\r\n'))
191 s = s[:5] + '*'*(i-5) + s[i:]
192 return repr(s)
193
194 # Internal: send one line to the server, appending CRLF
195 def putline(self, line):
196 if '\r' in line or '\n' in line:
197 raise ValueError('an illegal newline character should not be contained')
198 sys.audit("ftplib.sendcmd", self, line)
199 line = line + CRLF
200 if self.debugging > 1:
201 print('*put*', self.sanitize(line))
202 self.sock.sendall(line.encode(self.encoding))
203
204 # Internal: send one command to the server (through putline())
205 def putcmd(self, line):
206 if self.debugging: print('*cmd*', self.sanitize(line))
207 self.putline(line)
208
209 # Internal: return one line from the server, stripping CRLF.
210 # Raise EOFError if the connection is closed
211 def getline(self):
212 line = self.file.readline(self.maxline + 1)
213 if len(line) > self.maxline:
214 raise Error("got more than %d bytes" % self.maxline)
215 if self.debugging > 1:
216 print('*get*', self.sanitize(line))
217 if not line:
218 raise EOFError
219 if line[-2:] == CRLF:
220 line = line[:-2]
221 elif line[-1:] in CRLF:
222 line = line[:-1]
223 return line
224
225 # Internal: get a response from the server, which may possibly
226 # consist of multiple lines. Return a single string with no
227 # trailing CRLF. If the response consists of multiple lines,
228 # these are separated by '\n' characters in the string
229 def getmultiline(self):
230 line = self.getline()
231 if line[3:4] == '-':
232 code = line[:3]
233 while 1:
234 nextline = self.getline()
235 line = line + ('\n' + nextline)
236 if nextline[:3] == code and \
237 nextline[3:4] != '-':
238 break
239 return line
240
241 # Internal: get a response from the server.
242 # Raise various errors if the response indicates an error
243 def getresp(self):
244 resp = self.getmultiline()
245 if self.debugging:
246 print('*resp*', self.sanitize(resp))
247 self.lastresp = resp[:3]
248 c = resp[:1]
249 if c in {'1', '2', '3'}:
250 return resp
251 if c == '4':
252 raise error_temp(resp)
253 if c == '5':
254 raise error_perm(resp)
255 raise error_proto(resp)
256
257 def voidresp(self):
258 """Expect a response beginning with '2'."""
259 resp = self.getresp()
260 if resp[:1] != '2':
261 raise error_reply(resp)
262 return resp
263
264 def abort(self):
265 '''Abort a file transfer. Uses out-of-band data.
266 This does not follow the procedure from the RFC to send Telnet
267 IP and Synch; that doesn't seem to work with the servers I've
268 tried. Instead, just send the ABOR command as OOB data.'''
269 line = b'ABOR' + B_CRLF
270 if self.debugging > 1:
271 print('*put urgent*', self.sanitize(line))
272 self.sock.sendall(line, MSG_OOB)
273 resp = self.getmultiline()
274 if resp[:3] not in {'426', '225', '226'}:
275 raise error_proto(resp)
276 return resp
277
278 def sendcmd(self, cmd):
279 '''Send a command and return the response.'''
280 self.putcmd(cmd)
281 return self.getresp()
282
283 def voidcmd(self, cmd):
284 """Send a command and expect a response beginning with '2'."""
285 self.putcmd(cmd)
286 return self.voidresp()
287
288 def sendport(self, host, port):
289 '''Send a PORT command with the current host and the given
290 port number.
291 '''
292 hbytes = host.split('.')
293 pbytes = [repr(port//256), repr(port%256)]
294 bytes = hbytes + pbytes
295 cmd = 'PORT ' + ','.join(bytes)
296 return self.voidcmd(cmd)
297
298 def sendeprt(self, host, port):
299 '''Send an EPRT command with the current host and the given port number.'''
300 af = 0
301 if self.af == socket.AF_INET:
302 af = 1
303 if self.af == socket.AF_INET6:
304 af = 2
305 if af == 0:
306 raise error_proto('unsupported address family')
307 fields = ['', repr(af), host, repr(port), '']
308 cmd = 'EPRT ' + '|'.join(fields)
309 return self.voidcmd(cmd)
310
311 def makeport(self):
312 '''Create a new socket and send a PORT command for it.'''
313 sock = socket.create_server(("", 0), family=self.af, backlog=1)
314 port = sock.getsockname()[1] # Get proper port
315 host = self.sock.getsockname()[0] # Get proper host
316 if self.af == socket.AF_INET:
317 resp = self.sendport(host, port)
318 else:
319 resp = self.sendeprt(host, port)
320 if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
321 sock.settimeout(self.timeout)
322 return sock
323
324 def makepasv(self):
325 """Internal: Does the PASV or EPSV handshake -> (address, port)"""
326 if self.af == socket.AF_INET:
327 untrusted_host, port = parse227(self.sendcmd('PASV'))
328 if self.trust_server_pasv_ipv4_address:
329 host = untrusted_host
330 else:
331 host = self.sock.getpeername()[0]
332 else:
333 host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
334 return host, port
335
336 def ntransfercmd(self, cmd, rest=None):
337 """Initiate a transfer over the data connection.
338
339 If the transfer is active, send a port command and the
340 transfer command, and accept the connection. If the server is
341 passive, send a pasv command, connect to it, and start the
342 transfer command. Either way, return the socket for the
343 connection and the expected size of the transfer. The
344 expected size may be None if it could not be determined.
345
346 Optional `rest' argument can be a string that is sent as the
347 argument to a REST command. This is essentially a server
348 marker used to tell the server to skip over any data up to the
349 given marker.
350 """
351 size = None
352 if self.passiveserver:
353 host, port = self.makepasv()
354 conn = socket.create_connection((host, port), self.timeout,
355 source_address=self.source_address)
356 try:
357 if rest is not None:
358 self.sendcmd("REST %s" % rest)
359 resp = self.sendcmd(cmd)
360 # Some servers apparently send a 200 reply to
361 # a LIST or STOR command, before the 150 reply
362 # (and way before the 226 reply). This seems to
363 # be in violation of the protocol (which only allows
364 # 1xx or error messages for LIST), so we just discard
365 # this response.
366 if resp[0] == '2':
367 resp = self.getresp()
368 if resp[0] != '1':
369 raise error_reply(resp)
370 except:
371 conn.close()
372 raise
373 else:
374 with self.makeport() as sock:
375 if rest is not None:
376 self.sendcmd("REST %s" % rest)
377 resp = self.sendcmd(cmd)
378 # See above.
379 if resp[0] == '2':
380 resp = self.getresp()
381 if resp[0] != '1':
382 raise error_reply(resp)
383 conn, sockaddr = sock.accept()
384 if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
385 conn.settimeout(self.timeout)
386 if resp[:3] == '150':
387 # this is conditional in case we received a 125
388 size = parse150(resp)
389 return conn, size
390
391 def transfercmd(self, cmd, rest=None):
392 """Like ntransfercmd() but returns only the socket."""
393 return self.ntransfercmd(cmd, rest)[0]
394
395 def login(self, user = '', passwd = '', acct = ''):
396 '''Login, default anonymous.'''
397 if not user:
398 user = 'anonymous'
399 if not passwd:
400 passwd = ''
401 if not acct:
402 acct = ''
403 if user == 'anonymous' and passwd in {'', '-'}:
404 # If there is no anonymous ftp password specified
405 # then we'll just use anonymous@
406 # We don't send any other thing because:
407 # - We want to remain anonymous
408 # - We want to stop SPAM
409 # - We don't want to let ftp sites to discriminate by the user,
410 # host or country.
411 passwd = passwd + 'anonymous@'
412 resp = self.sendcmd('USER ' + user)
413 if resp[0] == '3':
414 resp = self.sendcmd('PASS ' + passwd)
415 if resp[0] == '3':
416 resp = self.sendcmd('ACCT ' + acct)
417 if resp[0] != '2':
418 raise error_reply(resp)
419 return resp
420
421 def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
422 """Retrieve data in binary mode. A new port is created for you.
423
424 Args:
425 cmd: A RETR command.
426 callback: A single parameter callable to be called on each
427 block of data read.
428 blocksize: The maximum number of bytes to read from the
429 socket at one time. [default: 8192]
430 rest: Passed to transfercmd(). [default: None]
431
432 Returns:
433 The response code.
434 """
435 self.voidcmd('TYPE I')
436 with self.transfercmd(cmd, rest) as conn:
437 while 1:
438 data = conn.recv(blocksize)
439 if not data:
440 break
441 callback(data)
442 # shutdown ssl layer
443 if _SSLSocket is not None and isinstance(conn, _SSLSocket):
444 conn.unwrap()
445 return self.voidresp()
446
447 def retrlines(self, cmd, callback = None):
448 """Retrieve data in line mode. A new port is created for you.
449
450 Args:
451 cmd: A RETR, LIST, or NLST command.
452 callback: An optional single parameter callable that is called
453 for each line with the trailing CRLF stripped.
454 [default: print_line()]
455
456 Returns:
457 The response code.
458 """
459 if callback is None:
460 callback = print_line
461 resp = self.sendcmd('TYPE A')
462 with self.transfercmd(cmd) as conn, \
463 conn.makefile('r', encoding=self.encoding) as fp:
464 while 1:
465 line = fp.readline(self.maxline + 1)
466 if len(line) > self.maxline:
467 raise Error("got more than %d bytes" % self.maxline)
468 if self.debugging > 2:
469 print('*retr*', repr(line))
470 if not line:
471 break
472 if line[-2:] == CRLF:
473 line = line[:-2]
474 elif line[-1:] == '\n':
475 line = line[:-1]
476 callback(line)
477 # shutdown ssl layer
478 if _SSLSocket is not None and isinstance(conn, _SSLSocket):
479 conn.unwrap()
480 return self.voidresp()
481
482 def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
483 """Store a file in binary mode. A new port is created for you.
484
485 Args:
486 cmd: A STOR command.
487 fp: A file-like object with a read(num_bytes) method.
488 blocksize: The maximum data size to read from fp and send over
489 the connection at once. [default: 8192]
490 callback: An optional single parameter callable that is called on
491 each block of data after it is sent. [default: None]
492 rest: Passed to transfercmd(). [default: None]
493
494 Returns:
495 The response code.
496 """
497 self.voidcmd('TYPE I')
498 with self.transfercmd(cmd, rest) as conn:
499 while 1:
500 buf = fp.read(blocksize)
501 if not buf:
502 break
503 conn.sendall(buf)
504 if callback:
505 callback(buf)
506 # shutdown ssl layer
507 if _SSLSocket is not None and isinstance(conn, _SSLSocket):
508 conn.unwrap()
509 return self.voidresp()
510
511 def storlines(self, cmd, fp, callback=None):
512 """Store a file in line mode. A new port is created for you.
513
514 Args:
515 cmd: A STOR command.
516 fp: A file-like object with a readline() method.
517 callback: An optional single parameter callable that is called on
518 each line after it is sent. [default: None]
519
520 Returns:
521 The response code.
522 """
523 self.voidcmd('TYPE A')
524 with self.transfercmd(cmd) as conn:
525 while 1:
526 buf = fp.readline(self.maxline + 1)
527 if len(buf) > self.maxline:
528 raise Error("got more than %d bytes" % self.maxline)
529 if not buf:
530 break
531 if buf[-2:] != B_CRLF:
532 if buf[-1] in B_CRLF: buf = buf[:-1]
533 buf = buf + B_CRLF
534 conn.sendall(buf)
535 if callback:
536 callback(buf)
537 # shutdown ssl layer
538 if _SSLSocket is not None and isinstance(conn, _SSLSocket):
539 conn.unwrap()
540 return self.voidresp()
541
542 def acct(self, password):
543 '''Send new account name.'''
544 cmd = 'ACCT ' + password
545 return self.voidcmd(cmd)
546
547 def nlst(self, *args):
548 '''Return a list of files in a given directory (default the current).'''
549 cmd = 'NLST'
550 for arg in args:
551 cmd = cmd + (' ' + arg)
552 files = []
553 self.retrlines(cmd, files.append)
554 return files
555
556 def dir(self, *args):
557 '''List a directory in long form.
558 By default list current directory to stdout.
559 Optional last argument is callback function; all
560 non-empty arguments before it are concatenated to the
561 LIST command. (This *should* only be used for a pathname.)'''
562 cmd = 'LIST'
563 func = None
564 if args[-1:] and type(args[-1]) != type(''):
565 args, func = args[:-1], args[-1]
566 for arg in args:
567 if arg:
568 cmd = cmd + (' ' + arg)
569 self.retrlines(cmd, func)
570
571 def mlsd(self, path="", facts=[]):
572 '''List a directory in a standardized format by using MLSD
573 command (RFC-3659). If path is omitted the current directory
574 is assumed. "facts" is a list of strings representing the type
575 of information desired (e.g. ["type", "size", "perm"]).
576
577 Return a generator object yielding a tuple of two elements
578 for every file found in path.
579 First element is the file name, the second one is a dictionary
580 including a variable number of "facts" depending on the server
581 and whether "facts" argument has been provided.
582 '''
583 if facts:
584 self.sendcmd("OPTS MLST " + ";".join(facts) + ";")
585 if path:
586 cmd = "MLSD %s" % path
587 else:
588 cmd = "MLSD"
589 lines = []
590 self.retrlines(cmd, lines.append)
591 for line in lines:
592 facts_found, _, name = line.rstrip(CRLF).partition(' ')
593 entry = {}
594 for fact in facts_found[:-1].split(";"):
595 key, _, value = fact.partition("=")
596 entry[key.lower()] = value
597 yield (name, entry)
598
599 def rename(self, fromname, toname):
600 '''Rename a file.'''
601 resp = self.sendcmd('RNFR ' + fromname)
602 if resp[0] != '3':
603 raise error_reply(resp)
604 return self.voidcmd('RNTO ' + toname)
605
606 def delete(self, filename):
607 '''Delete a file.'''
608 resp = self.sendcmd('DELE ' + filename)
609 if resp[:3] in {'250', '200'}:
610 return resp
611 else:
612 raise error_reply(resp)
613
614 def cwd(self, dirname):
615 '''Change to a directory.'''
616 if dirname == '..':
617 try:
618 return self.voidcmd('CDUP')
619 except error_perm as msg:
620 if msg.args[0][:3] != '500':
621 raise
622 elif dirname == '':
623 dirname = '.' # does nothing, but could return error
624 cmd = 'CWD ' + dirname
625 return self.voidcmd(cmd)
626
627 def size(self, filename):
628 '''Retrieve the size of a file.'''
629 # The SIZE command is defined in RFC-3659
630 resp = self.sendcmd('SIZE ' + filename)
631 if resp[:3] == '213':
632 s = resp[3:].strip()
633 return int(s)
634
635 def mkd(self, dirname):
636 '''Make a directory, return its full pathname.'''
637 resp = self.voidcmd('MKD ' + dirname)
638 # fix around non-compliant implementations such as IIS shipped
639 # with Windows server 2003
640 if not resp.startswith('257'):
641 return ''
642 return parse257(resp)
643
644 def rmd(self, dirname):
645 '''Remove a directory.'''
646 return self.voidcmd('RMD ' + dirname)
647
648 def pwd(self):
649 '''Return current working directory.'''
650 resp = self.voidcmd('PWD')
651 # fix around non-compliant implementations such as IIS shipped
652 # with Windows server 2003
653 if not resp.startswith('257'):
654 return ''
655 return parse257(resp)
656
657 def quit(self):
658 '''Quit, and close the connection.'''
659 resp = self.voidcmd('QUIT')
660 self.close()
661 return resp
662
663 def close(self):
664 '''Close the connection without assuming anything about it.'''
665 try:
666 file = self.file
667 self.file = None
668 if file is not None:
669 file.close()
670 finally:
671 sock = self.sock
672 self.sock = None
673 if sock is not None:
674 sock.close()
675
676 try:
677 import ssl
678 except ImportError:
679 _SSLSocket = None
680 else:
681 _SSLSocket = ssl.SSLSocket
682
683 class ESC[4;38;5;81mFTP_TLS(ESC[4;38;5;149mFTP):
684 '''A FTP subclass which adds TLS support to FTP as described
685 in RFC-4217.
686
687 Connect as usual to port 21 implicitly securing the FTP control
688 connection before authenticating.
689
690 Securing the data connection requires user to explicitly ask
691 for it by calling prot_p() method.
692
693 Usage example:
694 >>> from ftplib import FTP_TLS
695 >>> ftps = FTP_TLS('ftp.python.org')
696 >>> ftps.login() # login anonymously previously securing control channel
697 '230 Guest login ok, access restrictions apply.'
698 >>> ftps.prot_p() # switch to secure data connection
699 '200 Protection level set to P'
700 >>> ftps.retrlines('LIST') # list directory content securely
701 total 9
702 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
703 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
704 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
705 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
706 d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
707 drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
708 drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
709 drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
710 -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
711 '226 Transfer complete.'
712 >>> ftps.quit()
713 '221 Goodbye.'
714 >>>
715 '''
716 ssl_version = ssl.PROTOCOL_TLS_CLIENT
717
718 def __init__(self, host='', user='', passwd='', acct='',
719 keyfile=None, certfile=None, context=None,
720 timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *,
721 encoding='utf-8'):
722 if context is not None and keyfile is not None:
723 raise ValueError("context and keyfile arguments are mutually "
724 "exclusive")
725 if context is not None and certfile is not None:
726 raise ValueError("context and certfile arguments are mutually "
727 "exclusive")
728 if keyfile is not None or certfile is not None:
729 import warnings
730 warnings.warn("keyfile and certfile are deprecated, use a "
731 "custom context instead", DeprecationWarning, 2)
732 self.keyfile = keyfile
733 self.certfile = certfile
734 if context is None:
735 context = ssl._create_stdlib_context(self.ssl_version,
736 certfile=certfile,
737 keyfile=keyfile)
738 self.context = context
739 self._prot_p = False
740 super().__init__(host, user, passwd, acct,
741 timeout, source_address, encoding=encoding)
742
743 def login(self, user='', passwd='', acct='', secure=True):
744 if secure and not isinstance(self.sock, ssl.SSLSocket):
745 self.auth()
746 return super().login(user, passwd, acct)
747
748 def auth(self):
749 '''Set up secure control connection by using TLS/SSL.'''
750 if isinstance(self.sock, ssl.SSLSocket):
751 raise ValueError("Already using TLS")
752 if self.ssl_version >= ssl.PROTOCOL_TLS:
753 resp = self.voidcmd('AUTH TLS')
754 else:
755 resp = self.voidcmd('AUTH SSL')
756 self.sock = self.context.wrap_socket(self.sock, server_hostname=self.host)
757 self.file = self.sock.makefile(mode='r', encoding=self.encoding)
758 return resp
759
760 def ccc(self):
761 '''Switch back to a clear-text control connection.'''
762 if not isinstance(self.sock, ssl.SSLSocket):
763 raise ValueError("not using TLS")
764 resp = self.voidcmd('CCC')
765 self.sock = self.sock.unwrap()
766 return resp
767
768 def prot_p(self):
769 '''Set up secure data connection.'''
770 # PROT defines whether or not the data channel is to be protected.
771 # Though RFC-2228 defines four possible protection levels,
772 # RFC-4217 only recommends two, Clear and Private.
773 # Clear (PROT C) means that no security is to be used on the
774 # data-channel, Private (PROT P) means that the data-channel
775 # should be protected by TLS.
776 # PBSZ command MUST still be issued, but must have a parameter of
777 # '0' to indicate that no buffering is taking place and the data
778 # connection should not be encapsulated.
779 self.voidcmd('PBSZ 0')
780 resp = self.voidcmd('PROT P')
781 self._prot_p = True
782 return resp
783
784 def prot_c(self):
785 '''Set up clear text data connection.'''
786 resp = self.voidcmd('PROT C')
787 self._prot_p = False
788 return resp
789
790 # --- Overridden FTP methods
791
792 def ntransfercmd(self, cmd, rest=None):
793 conn, size = super().ntransfercmd(cmd, rest)
794 if self._prot_p:
795 conn = self.context.wrap_socket(conn,
796 server_hostname=self.host)
797 return conn, size
798
799 def abort(self):
800 # overridden as we can't pass MSG_OOB flag to sendall()
801 line = b'ABOR' + B_CRLF
802 self.sock.sendall(line)
803 resp = self.getmultiline()
804 if resp[:3] not in {'426', '225', '226'}:
805 raise error_proto(resp)
806 return resp
807
808 __all__.append('FTP_TLS')
809 all_errors = (Error, OSError, EOFError, ssl.SSLError)
810
811
812 _150_re = None
813
814 def parse150(resp):
815 '''Parse the '150' response for a RETR request.
816 Returns the expected transfer size or None; size is not guaranteed to
817 be present in the 150 message.
818 '''
819 if resp[:3] != '150':
820 raise error_reply(resp)
821 global _150_re
822 if _150_re is None:
823 import re
824 _150_re = re.compile(
825 r"150 .* \((\d+) bytes\)", re.IGNORECASE | re.ASCII)
826 m = _150_re.match(resp)
827 if not m:
828 return None
829 return int(m.group(1))
830
831
832 _227_re = None
833
834 def parse227(resp):
835 '''Parse the '227' response for a PASV request.
836 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
837 Return ('host.addr.as.numbers', port#) tuple.'''
838 if resp[:3] != '227':
839 raise error_reply(resp)
840 global _227_re
841 if _227_re is None:
842 import re
843 _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)', re.ASCII)
844 m = _227_re.search(resp)
845 if not m:
846 raise error_proto(resp)
847 numbers = m.groups()
848 host = '.'.join(numbers[:4])
849 port = (int(numbers[4]) << 8) + int(numbers[5])
850 return host, port
851
852
853 def parse229(resp, peer):
854 '''Parse the '229' response for an EPSV request.
855 Raises error_proto if it does not contain '(|||port|)'
856 Return ('host.addr.as.numbers', port#) tuple.'''
857 if resp[:3] != '229':
858 raise error_reply(resp)
859 left = resp.find('(')
860 if left < 0: raise error_proto(resp)
861 right = resp.find(')', left + 1)
862 if right < 0:
863 raise error_proto(resp) # should contain '(|||port|)'
864 if resp[left + 1] != resp[right - 1]:
865 raise error_proto(resp)
866 parts = resp[left + 1:right].split(resp[left+1])
867 if len(parts) != 5:
868 raise error_proto(resp)
869 host = peer[0]
870 port = int(parts[3])
871 return host, port
872
873
874 def parse257(resp):
875 '''Parse the '257' response for a MKD or PWD request.
876 This is a response to a MKD or PWD request: a directory name.
877 Returns the directoryname in the 257 reply.'''
878 if resp[:3] != '257':
879 raise error_reply(resp)
880 if resp[3:5] != ' "':
881 return '' # Not compliant to RFC 959, but UNIX ftpd does this
882 dirname = ''
883 i = 5
884 n = len(resp)
885 while i < n:
886 c = resp[i]
887 i = i+1
888 if c == '"':
889 if i >= n or resp[i] != '"':
890 break
891 i = i+1
892 dirname = dirname + c
893 return dirname
894
895
896 def print_line(line):
897 '''Default retrlines callback to print a line.'''
898 print(line)
899
900
901 def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
902 '''Copy file from one FTP-instance to another.'''
903 if not targetname:
904 targetname = sourcename
905 type = 'TYPE ' + type
906 source.voidcmd(type)
907 target.voidcmd(type)
908 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
909 target.sendport(sourcehost, sourceport)
910 # RFC 959: the user must "listen" [...] BEFORE sending the
911 # transfer request.
912 # So: STOR before RETR, because here the target is a "user".
913 treply = target.sendcmd('STOR ' + targetname)
914 if treply[:3] not in {'125', '150'}:
915 raise error_proto # RFC 959
916 sreply = source.sendcmd('RETR ' + sourcename)
917 if sreply[:3] not in {'125', '150'}:
918 raise error_proto # RFC 959
919 source.voidresp()
920 target.voidresp()
921
922
923 def test():
924 '''Test program.
925 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...
926
927 -d dir
928 -l list
929 -p password
930 '''
931
932 if len(sys.argv) < 2:
933 print(test.__doc__)
934 sys.exit(0)
935
936 import netrc
937
938 debugging = 0
939 rcfile = None
940 while sys.argv[1] == '-d':
941 debugging = debugging+1
942 del sys.argv[1]
943 if sys.argv[1][:2] == '-r':
944 # get name of alternate ~/.netrc file:
945 rcfile = sys.argv[1][2:]
946 del sys.argv[1]
947 host = sys.argv[1]
948 ftp = FTP(host)
949 ftp.set_debuglevel(debugging)
950 userid = passwd = acct = ''
951 try:
952 netrcobj = netrc.netrc(rcfile)
953 except OSError:
954 if rcfile is not None:
955 sys.stderr.write("Could not open account file"
956 " -- using anonymous login.")
957 else:
958 try:
959 userid, acct, passwd = netrcobj.authenticators(host)
960 except KeyError:
961 # no account for host
962 sys.stderr.write(
963 "No account -- using anonymous login.")
964 ftp.login(userid, passwd, acct)
965 for file in sys.argv[2:]:
966 if file[:2] == '-l':
967 ftp.dir(file[2:])
968 elif file[:2] == '-d':
969 cmd = 'CWD'
970 if file[2:]: cmd = cmd + ' ' + file[2:]
971 resp = ftp.sendcmd(cmd)
972 elif file == '-p':
973 ftp.set_pasv(not ftp.passiveserver)
974 else:
975 ftp.retrbinary('RETR ' + file, \
976 sys.stdout.write, 1024)
977 ftp.quit()
978
979
980 if __name__ == '__main__':
981 test()