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 data := conn.recv(blocksize):
438 callback(data)
439 # shutdown ssl layer
440 if _SSLSocket is not None and isinstance(conn, _SSLSocket):
441 conn.unwrap()
442 return self.voidresp()
443
444 def retrlines(self, cmd, callback = None):
445 """Retrieve data in line mode. A new port is created for you.
446
447 Args:
448 cmd: A RETR, LIST, or NLST command.
449 callback: An optional single parameter callable that is called
450 for each line with the trailing CRLF stripped.
451 [default: print_line()]
452
453 Returns:
454 The response code.
455 """
456 if callback is None:
457 callback = print_line
458 resp = self.sendcmd('TYPE A')
459 with self.transfercmd(cmd) as conn, \
460 conn.makefile('r', encoding=self.encoding) as fp:
461 while 1:
462 line = fp.readline(self.maxline + 1)
463 if len(line) > self.maxline:
464 raise Error("got more than %d bytes" % self.maxline)
465 if self.debugging > 2:
466 print('*retr*', repr(line))
467 if not line:
468 break
469 if line[-2:] == CRLF:
470 line = line[:-2]
471 elif line[-1:] == '\n':
472 line = line[:-1]
473 callback(line)
474 # shutdown ssl layer
475 if _SSLSocket is not None and isinstance(conn, _SSLSocket):
476 conn.unwrap()
477 return self.voidresp()
478
479 def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
480 """Store a file in binary mode. A new port is created for you.
481
482 Args:
483 cmd: A STOR command.
484 fp: A file-like object with a read(num_bytes) method.
485 blocksize: The maximum data size to read from fp and send over
486 the connection at once. [default: 8192]
487 callback: An optional single parameter callable that is called on
488 each block of data after it is sent. [default: None]
489 rest: Passed to transfercmd(). [default: None]
490
491 Returns:
492 The response code.
493 """
494 self.voidcmd('TYPE I')
495 with self.transfercmd(cmd, rest) as conn:
496 while buf := fp.read(blocksize):
497 conn.sendall(buf)
498 if callback:
499 callback(buf)
500 # shutdown ssl layer
501 if _SSLSocket is not None and isinstance(conn, _SSLSocket):
502 conn.unwrap()
503 return self.voidresp()
504
505 def storlines(self, cmd, fp, callback=None):
506 """Store a file in line mode. A new port is created for you.
507
508 Args:
509 cmd: A STOR command.
510 fp: A file-like object with a readline() method.
511 callback: An optional single parameter callable that is called on
512 each line after it is sent. [default: None]
513
514 Returns:
515 The response code.
516 """
517 self.voidcmd('TYPE A')
518 with self.transfercmd(cmd) as conn:
519 while 1:
520 buf = fp.readline(self.maxline + 1)
521 if len(buf) > self.maxline:
522 raise Error("got more than %d bytes" % self.maxline)
523 if not buf:
524 break
525 if buf[-2:] != B_CRLF:
526 if buf[-1] in B_CRLF: buf = buf[:-1]
527 buf = buf + B_CRLF
528 conn.sendall(buf)
529 if callback:
530 callback(buf)
531 # shutdown ssl layer
532 if _SSLSocket is not None and isinstance(conn, _SSLSocket):
533 conn.unwrap()
534 return self.voidresp()
535
536 def acct(self, password):
537 '''Send new account name.'''
538 cmd = 'ACCT ' + password
539 return self.voidcmd(cmd)
540
541 def nlst(self, *args):
542 '''Return a list of files in a given directory (default the current).'''
543 cmd = 'NLST'
544 for arg in args:
545 cmd = cmd + (' ' + arg)
546 files = []
547 self.retrlines(cmd, files.append)
548 return files
549
550 def dir(self, *args):
551 '''List a directory in long form.
552 By default list current directory to stdout.
553 Optional last argument is callback function; all
554 non-empty arguments before it are concatenated to the
555 LIST command. (This *should* only be used for a pathname.)'''
556 cmd = 'LIST'
557 func = None
558 if args[-1:] and not isinstance(args[-1], str):
559 args, func = args[:-1], args[-1]
560 for arg in args:
561 if arg:
562 cmd = cmd + (' ' + arg)
563 self.retrlines(cmd, func)
564
565 def mlsd(self, path="", facts=[]):
566 '''List a directory in a standardized format by using MLSD
567 command (RFC-3659). If path is omitted the current directory
568 is assumed. "facts" is a list of strings representing the type
569 of information desired (e.g. ["type", "size", "perm"]).
570
571 Return a generator object yielding a tuple of two elements
572 for every file found in path.
573 First element is the file name, the second one is a dictionary
574 including a variable number of "facts" depending on the server
575 and whether "facts" argument has been provided.
576 '''
577 if facts:
578 self.sendcmd("OPTS MLST " + ";".join(facts) + ";")
579 if path:
580 cmd = "MLSD %s" % path
581 else:
582 cmd = "MLSD"
583 lines = []
584 self.retrlines(cmd, lines.append)
585 for line in lines:
586 facts_found, _, name = line.rstrip(CRLF).partition(' ')
587 entry = {}
588 for fact in facts_found[:-1].split(";"):
589 key, _, value = fact.partition("=")
590 entry[key.lower()] = value
591 yield (name, entry)
592
593 def rename(self, fromname, toname):
594 '''Rename a file.'''
595 resp = self.sendcmd('RNFR ' + fromname)
596 if resp[0] != '3':
597 raise error_reply(resp)
598 return self.voidcmd('RNTO ' + toname)
599
600 def delete(self, filename):
601 '''Delete a file.'''
602 resp = self.sendcmd('DELE ' + filename)
603 if resp[:3] in {'250', '200'}:
604 return resp
605 else:
606 raise error_reply(resp)
607
608 def cwd(self, dirname):
609 '''Change to a directory.'''
610 if dirname == '..':
611 try:
612 return self.voidcmd('CDUP')
613 except error_perm as msg:
614 if msg.args[0][:3] != '500':
615 raise
616 elif dirname == '':
617 dirname = '.' # does nothing, but could return error
618 cmd = 'CWD ' + dirname
619 return self.voidcmd(cmd)
620
621 def size(self, filename):
622 '''Retrieve the size of a file.'''
623 # The SIZE command is defined in RFC-3659
624 resp = self.sendcmd('SIZE ' + filename)
625 if resp[:3] == '213':
626 s = resp[3:].strip()
627 return int(s)
628
629 def mkd(self, dirname):
630 '''Make a directory, return its full pathname.'''
631 resp = self.voidcmd('MKD ' + dirname)
632 # fix around non-compliant implementations such as IIS shipped
633 # with Windows server 2003
634 if not resp.startswith('257'):
635 return ''
636 return parse257(resp)
637
638 def rmd(self, dirname):
639 '''Remove a directory.'''
640 return self.voidcmd('RMD ' + dirname)
641
642 def pwd(self):
643 '''Return current working directory.'''
644 resp = self.voidcmd('PWD')
645 # fix around non-compliant implementations such as IIS shipped
646 # with Windows server 2003
647 if not resp.startswith('257'):
648 return ''
649 return parse257(resp)
650
651 def quit(self):
652 '''Quit, and close the connection.'''
653 resp = self.voidcmd('QUIT')
654 self.close()
655 return resp
656
657 def close(self):
658 '''Close the connection without assuming anything about it.'''
659 try:
660 file = self.file
661 self.file = None
662 if file is not None:
663 file.close()
664 finally:
665 sock = self.sock
666 self.sock = None
667 if sock is not None:
668 sock.close()
669
670 try:
671 import ssl
672 except ImportError:
673 _SSLSocket = None
674 else:
675 _SSLSocket = ssl.SSLSocket
676
677 class ESC[4;38;5;81mFTP_TLS(ESC[4;38;5;149mFTP):
678 '''A FTP subclass which adds TLS support to FTP as described
679 in RFC-4217.
680
681 Connect as usual to port 21 implicitly securing the FTP control
682 connection before authenticating.
683
684 Securing the data connection requires user to explicitly ask
685 for it by calling prot_p() method.
686
687 Usage example:
688 >>> from ftplib import FTP_TLS
689 >>> ftps = FTP_TLS('ftp.python.org')
690 >>> ftps.login() # login anonymously previously securing control channel
691 '230 Guest login ok, access restrictions apply.'
692 >>> ftps.prot_p() # switch to secure data connection
693 '200 Protection level set to P'
694 >>> ftps.retrlines('LIST') # list directory content securely
695 total 9
696 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
697 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
698 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
699 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
700 d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
701 drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
702 drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
703 drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
704 -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
705 '226 Transfer complete.'
706 >>> ftps.quit()
707 '221 Goodbye.'
708 >>>
709 '''
710
711 def __init__(self, host='', user='', passwd='', acct='',
712 *, context=None, timeout=_GLOBAL_DEFAULT_TIMEOUT,
713 source_address=None, encoding='utf-8'):
714 if context is None:
715 context = ssl._create_stdlib_context()
716 self.context = context
717 self._prot_p = False
718 super().__init__(host, user, passwd, acct,
719 timeout, source_address, encoding=encoding)
720
721 def login(self, user='', passwd='', acct='', secure=True):
722 if secure and not isinstance(self.sock, ssl.SSLSocket):
723 self.auth()
724 return super().login(user, passwd, acct)
725
726 def auth(self):
727 '''Set up secure control connection by using TLS/SSL.'''
728 if isinstance(self.sock, ssl.SSLSocket):
729 raise ValueError("Already using TLS")
730 if self.context.protocol >= ssl.PROTOCOL_TLS:
731 resp = self.voidcmd('AUTH TLS')
732 else:
733 resp = self.voidcmd('AUTH SSL')
734 self.sock = self.context.wrap_socket(self.sock, server_hostname=self.host)
735 self.file = self.sock.makefile(mode='r', encoding=self.encoding)
736 return resp
737
738 def ccc(self):
739 '''Switch back to a clear-text control connection.'''
740 if not isinstance(self.sock, ssl.SSLSocket):
741 raise ValueError("not using TLS")
742 resp = self.voidcmd('CCC')
743 self.sock = self.sock.unwrap()
744 return resp
745
746 def prot_p(self):
747 '''Set up secure data connection.'''
748 # PROT defines whether or not the data channel is to be protected.
749 # Though RFC-2228 defines four possible protection levels,
750 # RFC-4217 only recommends two, Clear and Private.
751 # Clear (PROT C) means that no security is to be used on the
752 # data-channel, Private (PROT P) means that the data-channel
753 # should be protected by TLS.
754 # PBSZ command MUST still be issued, but must have a parameter of
755 # '0' to indicate that no buffering is taking place and the data
756 # connection should not be encapsulated.
757 self.voidcmd('PBSZ 0')
758 resp = self.voidcmd('PROT P')
759 self._prot_p = True
760 return resp
761
762 def prot_c(self):
763 '''Set up clear text data connection.'''
764 resp = self.voidcmd('PROT C')
765 self._prot_p = False
766 return resp
767
768 # --- Overridden FTP methods
769
770 def ntransfercmd(self, cmd, rest=None):
771 conn, size = super().ntransfercmd(cmd, rest)
772 if self._prot_p:
773 conn = self.context.wrap_socket(conn,
774 server_hostname=self.host)
775 return conn, size
776
777 def abort(self):
778 # overridden as we can't pass MSG_OOB flag to sendall()
779 line = b'ABOR' + B_CRLF
780 self.sock.sendall(line)
781 resp = self.getmultiline()
782 if resp[:3] not in {'426', '225', '226'}:
783 raise error_proto(resp)
784 return resp
785
786 __all__.append('FTP_TLS')
787 all_errors = (Error, OSError, EOFError, ssl.SSLError)
788
789
790 _150_re = None
791
792 def parse150(resp):
793 '''Parse the '150' response for a RETR request.
794 Returns the expected transfer size or None; size is not guaranteed to
795 be present in the 150 message.
796 '''
797 if resp[:3] != '150':
798 raise error_reply(resp)
799 global _150_re
800 if _150_re is None:
801 import re
802 _150_re = re.compile(
803 r"150 .* \((\d+) bytes\)", re.IGNORECASE | re.ASCII)
804 m = _150_re.match(resp)
805 if not m:
806 return None
807 return int(m.group(1))
808
809
810 _227_re = None
811
812 def parse227(resp):
813 '''Parse the '227' response for a PASV request.
814 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
815 Return ('host.addr.as.numbers', port#) tuple.'''
816 if resp[:3] != '227':
817 raise error_reply(resp)
818 global _227_re
819 if _227_re is None:
820 import re
821 _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)', re.ASCII)
822 m = _227_re.search(resp)
823 if not m:
824 raise error_proto(resp)
825 numbers = m.groups()
826 host = '.'.join(numbers[:4])
827 port = (int(numbers[4]) << 8) + int(numbers[5])
828 return host, port
829
830
831 def parse229(resp, peer):
832 '''Parse the '229' response for an EPSV request.
833 Raises error_proto if it does not contain '(|||port|)'
834 Return ('host.addr.as.numbers', port#) tuple.'''
835 if resp[:3] != '229':
836 raise error_reply(resp)
837 left = resp.find('(')
838 if left < 0: raise error_proto(resp)
839 right = resp.find(')', left + 1)
840 if right < 0:
841 raise error_proto(resp) # should contain '(|||port|)'
842 if resp[left + 1] != resp[right - 1]:
843 raise error_proto(resp)
844 parts = resp[left + 1:right].split(resp[left+1])
845 if len(parts) != 5:
846 raise error_proto(resp)
847 host = peer[0]
848 port = int(parts[3])
849 return host, port
850
851
852 def parse257(resp):
853 '''Parse the '257' response for a MKD or PWD request.
854 This is a response to a MKD or PWD request: a directory name.
855 Returns the directoryname in the 257 reply.'''
856 if resp[:3] != '257':
857 raise error_reply(resp)
858 if resp[3:5] != ' "':
859 return '' # Not compliant to RFC 959, but UNIX ftpd does this
860 dirname = ''
861 i = 5
862 n = len(resp)
863 while i < n:
864 c = resp[i]
865 i = i+1
866 if c == '"':
867 if i >= n or resp[i] != '"':
868 break
869 i = i+1
870 dirname = dirname + c
871 return dirname
872
873
874 def print_line(line):
875 '''Default retrlines callback to print a line.'''
876 print(line)
877
878
879 def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
880 '''Copy file from one FTP-instance to another.'''
881 if not targetname:
882 targetname = sourcename
883 type = 'TYPE ' + type
884 source.voidcmd(type)
885 target.voidcmd(type)
886 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
887 target.sendport(sourcehost, sourceport)
888 # RFC 959: the user must "listen" [...] BEFORE sending the
889 # transfer request.
890 # So: STOR before RETR, because here the target is a "user".
891 treply = target.sendcmd('STOR ' + targetname)
892 if treply[:3] not in {'125', '150'}:
893 raise error_proto # RFC 959
894 sreply = source.sendcmd('RETR ' + sourcename)
895 if sreply[:3] not in {'125', '150'}:
896 raise error_proto # RFC 959
897 source.voidresp()
898 target.voidresp()
899
900
901 def test():
902 '''Test program.
903 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...
904
905 -d dir
906 -l list
907 -p password
908 '''
909
910 if len(sys.argv) < 2:
911 print(test.__doc__)
912 sys.exit(0)
913
914 import netrc
915
916 debugging = 0
917 rcfile = None
918 while sys.argv[1] == '-d':
919 debugging = debugging+1
920 del sys.argv[1]
921 if sys.argv[1][:2] == '-r':
922 # get name of alternate ~/.netrc file:
923 rcfile = sys.argv[1][2:]
924 del sys.argv[1]
925 host = sys.argv[1]
926 ftp = FTP(host)
927 ftp.set_debuglevel(debugging)
928 userid = passwd = acct = ''
929 try:
930 netrcobj = netrc.netrc(rcfile)
931 except OSError:
932 if rcfile is not None:
933 sys.stderr.write("Could not open account file"
934 " -- using anonymous login.")
935 else:
936 try:
937 userid, acct, passwd = netrcobj.authenticators(host)
938 except KeyError:
939 # no account for host
940 sys.stderr.write(
941 "No account -- using anonymous login.")
942 ftp.login(userid, passwd, acct)
943 for file in sys.argv[2:]:
944 if file[:2] == '-l':
945 ftp.dir(file[2:])
946 elif file[:2] == '-d':
947 cmd = 'CWD'
948 if file[2:]: cmd = cmd + ' ' + file[2:]
949 resp = ftp.sendcmd(cmd)
950 elif file == '-p':
951 ftp.set_pasv(not ftp.passiveserver)
952 else:
953 ftp.retrbinary('RETR ' + file, \
954 sys.stdout.write, 1024)
955 ftp.quit()
956
957
958 if __name__ == '__main__':
959 test()