Initial revision
diff --git a/Lib/ftplib.py b/Lib/ftplib.py
new file mode 100644
index 0000000..b360942
--- /dev/null
+++ b/Lib/ftplib.py
@@ -0,0 +1,258 @@
+# An FTP client class.  Based on RFC 959: File Transfer Protocol
+# (FTP), by J. Postel and J. Reynolds
+
+
+import os
+import sys
+import socket
+import string
+
+
+# Default port numbers used by the FTP protocol
+FTP_PORT = 21
+FTP_DATA_PORT = 20
+
+
+# Exception raiseds when an error or invalid response is received
+error_reply = 'nntp.error_reply'	# unexpected [123]xx reply
+error_function = 'nntp.error_function'	# 4xx errors
+error_form = 'nntp.error_form'		# 5xx errors
+error_protocol = 'nntp.error_protocol'	# response does not begin with [1-5]
+
+
+# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
+CRLF = '\r\n'
+
+
+# Next port to be used by makeport(), with PORT_OFFSET added
+nextport = 0
+PORT_OFFSET = 40000
+PORT_CYCLE = 1000
+# XXX This is a nuisance: when using the program several times in a row,
+# reusing the port doesn't work and you have to edit the first port
+# assignment...
+
+
+# The class itself
+class FTP:
+
+	# Initialize an instance.  Arguments:
+	# - host: hostname to connect to
+	# - port: port to connect to (default the standard FTP port)
+	def init(self, host, *args):
+		if len(args) > 1: raise TypeError, 'too many args'
+		if args: port = args[0]
+		else: port = FTP_PORT
+		self.host = host
+		self.port = port
+		self.debugging = 0
+		self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		self.sock.connect(self.host, self.port)
+		self.file = self.sock.makefile('r')
+		self.welcome = self.getresp()
+		return self
+
+	# Get the welcome message from the server
+	# (this is read and squirreled away by init())
+	def getwelcome(self):
+		if self.debugging: print '*welcome*', `self.welcome`
+		return self.welcome
+
+	# Set the debugging level.  Argument level means:
+	# 0: no debugging output (default)
+	# 1: print commands and responses but not body text etc.
+	# 2: also print raw lines read and sent before stripping CR/LF
+	def debug(self, level):
+		self.debugging = level
+
+	# Internal: send one line to the server, appending CRLF
+	def putline(self, line):
+		line = line + CRLF
+		if self.debugging > 1: print '*put*', `line`
+		self.sock.send(line)
+
+	# Internal: send one command to the server (through putline())
+	def putcmd(self, line):
+		if self.debugging: print '*cmd*', `line`
+		self.putline(line)
+
+	# Internal: return one line from the server, stripping CRLF.
+	# Raise EOFError if the connection is closed
+	def getline(self):
+		line = self.file.readline()
+		if self.debugging > 1:
+			print '*get*', `line`
+		if not line: raise EOFError
+		if line[-2:] == CRLF: line = line[:-2]
+		elif line[-1:] in CRLF: line = line[:-1]
+		return line
+
+	# Internal: get a response from the server, which may possibly
+	# consist of multiple lines.  Return a single string with no
+	# trailing CRLF.  If the response consists of multiple lines,
+	# these are separated by '\n' characters in the string
+	def getmultiline(self):
+		line = self.getline()
+		if line[3:4] == '-':
+			code = line[:3]
+			while 1:
+				nextline = self.getline()
+				line = line + ('\n' + nextline)
+				if nextline[:3] == code and \
+					nextline[3:4] <> '-':
+					break
+		return line
+
+	# Internal: get a response from the server.
+	# Raise various errors if the response indicates an error
+	def getresp(self):
+		resp = self.getmultiline()
+		if self.debugging: print '*resp*', `resp`
+		self.lastresp = resp[:3]
+		c = resp[:1]
+		if c == '4':
+			raise error_function, resp
+		if c == '5':
+			raise error_form, resp
+		if c not in '123':
+			raise error_protocol, resp
+		return resp
+
+	# Send a command and return the response
+	def sendcmd(self, cmd):
+		self.putcmd(cmd)
+		return self.getresp()
+
+	# Send a PORT command with the current host and the given port number
+	def sendport(self, port):
+		hostname = socket.gethostname()
+		hostaddr = socket.gethostbyname(hostname)
+		hbytes = string.splitfields(hostaddr, '.')
+		pbytes = [`port/256`, `port%256`]
+		bytes = hbytes + pbytes
+		cmd = 'PORT ' + string.joinfields(bytes, ',')
+		resp = self.sendcmd(cmd)
+		if resp[:3] <> '200':
+			raise error_reply, resp
+
+	# Create a new socket and send a PORT command for it
+	def makeport(self):
+		global nextport
+		port = nextport + PORT_OFFSET
+		nextport = (nextport + 1) % PORT_CYCLE
+		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		sock.bind('', port)
+		sock.listen(0)
+		resp = self.sendport(port)
+		return sock
+
+	# Retrieve data in binary mode.  (You must set the mode first.)
+	# The argument is a RETR command.
+	# The callback function is called for each block.
+	# This creates a new port for you
+	def retrbinary(self, cmd, callback, blocksize):
+		sock = self.makeport()
+		resp = self.sendcmd(cmd)
+		if resp[0] <> '1':
+			raise error_reply, resp
+		conn, host = sock.accept()
+		sock.close()
+		while 1:
+			data = conn.recv(blocksize)
+			if not data:
+				break
+			callback(data)
+		conn.close()
+		resp = self.getresp()
+		if resp[0] <> '2':
+			raise error_reply, resp
+
+	# Retrieve data in line mode.  (You must set the mode first.)
+	# The argument is a RETR or LIST command.
+	# The callback function is called for each line, with trailing
+	# CRLF stripped.  This creates a new port for you
+	def retrlines(self, cmd, callback):
+		sock = self.makeport()
+		resp = self.sendcmd(cmd)
+		if resp[0] <> '1':
+			raise error_reply, resp
+		conn, host = sock.accept()
+		sock.close()
+		fp = conn.makefile('r')
+		while 1:
+			line = fp.readline()
+			if not line:
+				break
+			if line[-2:] == CRLF:
+				line = line[:-2]
+			elif line[:-1] == '\n':
+				line = line[:-1]
+			callback(line)
+		fp.close()
+		conn.close()
+		resp = self.getresp()
+		if resp[0] <> '2':
+			raise error_reply, resp
+
+	# Login as user anonymous with given passwd (default user@thishost)
+	def anonymouslogin(self, *args):
+		resp = self.sendcmd('USER anonymous')
+		if resp[0] == '3':
+			if args:
+				passwd = args[0]
+			else:
+				thishost = socket.gethostname()
+				if os.environ.has_key('LOGNAME'):
+					user = os.environ['LOGNAME']
+				elif os.environ.has_key('USER'):
+					user = os.environ['USER']
+				else:
+					user = 'anonymous'
+				passwd = user + '@' + thishost
+			resp = self.sendcmd('PASS ' + passwd)
+		if resp[0] <> '2':
+			raise error_reply, resp
+
+	# Quit, and close the connection
+	def quit(self):
+		resp = self.sendcmd('QUIT')
+		if resp[0] <> '2':
+			raise error_reply, resp
+		self.file.close()
+		self.sock.close()
+
+
+# Test program.
+# Usage: ftp [-d] host [-l[dir]] [-d[dir]] [file] ...
+def test():
+	import marshal
+	global nextport
+	try:
+		nextport = marshal.load(open('.@nextport', 'r'))
+	except IOError:
+		pass
+	try:
+		debugging = 0
+		while sys.argv[1] == '-d':
+			debugging = debugging+1
+			del sys.argv[1]
+		host = sys.argv[1]
+		ftp = FTP().init(host)
+		ftp.debug(debugging)
+		ftp.anonymouslogin()
+		def writeln(line): print line
+		for file in sys.argv[2:]:
+			if file[:2] == '-l':
+				cmd = 'LIST'
+				if file[2:]: cmd = cmd + ' ' + file[2:]
+				ftp.retrlines(cmd, writeln)
+			elif file[:2] == '-d':
+				cmd = 'CWD'
+				if file[2:]: cmd = cmd + ' ' + file[2:]
+				resp = ftp.sendcmd(cmd)
+			else:
+				ftp.retrbinary('RETR ' + file, \
+					       sys.stdout.write, 1024)
+		ftp.quit()
+	finally:
+		marshal.dump(nextport, open('.@nextport', 'w'))