1"""An FTP client class and some helper functions. 2 3Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds 4 5Example: 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 12total 9 13drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . 14drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. 15drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin 16drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc 17d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming 18drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib 19drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub 20drwxr-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 27A nice test that reveals some of the network dialogue would be: 28python 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 39import sys 40import socket 41from 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> 47MSG_OOB = 0x1 # Process data out of band 48 49 50# The standard FTP server control port 51FTP_PORT = 21 52# The sizehint parameter passed to readline() calls 53MAXLINE = 8192 54 55 56# Exception raised when an error or invalid response is received 57class Error(Exception): pass 58class error_reply(Error): pass # unexpected [123]xx reply 59class error_temp(Error): pass # 4xx errors 60class error_perm(Error): pass # 5xx errors 61class error_proto(Error): 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 66all_errors = (Error, OSError, EOFError) 67 68 69# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) 70CRLF = '\r\n' 71B_CRLF = b'\r\n' 72 73# The class itself 74class FTP: 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 676try: 677 import ssl 678except ImportError: 679 _SSLSocket = None 680else: 681 _SSLSocket = ssl.SSLSocket 682 683 class FTP_TLS(FTP): 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 814def 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 834def 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 853def 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 874def 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 896def print_line(line): 897 '''Default retrlines callback to print a line.''' 898 print(line) 899 900 901def 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 923def 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 980if __name__ == '__main__': 981 test() 982