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