1# A simple FTP client. 2# 3# The information to write this program was gathered from RFC 959, 4# but this is not a complete implementation! Yet it shows how a simple 5# FTP client can be built, and you are welcome to extend it to suit 6# it to your needs... 7# 8# How it works (assuming you've read the RFC): 9# 10# User commands are passed uninterpreted to the server. However, the 11# user never needs to send a PORT command. Rather, the client opens a 12# port right away and sends the appropriate PORT command to the server. 13# When a response code 150 is received, this port is used to receive 14# the data (which is written to stdout in this version), and when the 15# data is exhausted, a new port is opened and a corresponding PORT 16# command sent. In order to avoid errors when reusing ports quickly 17# (and because there is no s.getsockname() method in Python yet) we 18# cycle through a number of ports in the 50000 range. 19 20 21import sys, posix, string 22from socket import * 23 24 25BUFSIZE = 1024 26 27# Default port numbers used by the FTP protocol. 28# 29FTP_PORT = 21 30FTP_DATA_PORT = FTP_PORT - 1 31 32# Change the data port to something not needing root permissions. 33# 34FTP_DATA_PORT = FTP_DATA_PORT + 50000 35 36 37# Main program (called at the end of this file). 38# 39def main(): 40 hostname = sys.argv[1] 41 control(hostname) 42 43 44# Control process (user interface and user protocol interpreter). 45# 46def control(hostname): 47 # 48 # Create control connection 49 # 50 s = socket(AF_INET, SOCK_STREAM) 51 s.connect((hostname, FTP_PORT)) 52 f = s.makefile('r') # Reading the replies is easier from a file... 53 # 54 # Control loop 55 # 56 r = None 57 while 1: 58 code = getreply(f) 59 if code in ('221', 'EOF'): break 60 if code == '150': 61 getdata(r) 62 code = getreply(f) 63 r = None 64 if not r: 65 r = newdataport(s, f) 66 cmd = getcommand() 67 if not cmd: break 68 s.send(cmd + '\r\n') 69 70 71# Create a new data port and send a PORT command to the server for it. 72# (Cycle through a number of ports to avoid problems with reusing 73# a port within a short time.) 74# 75nextport = 0 76# 77def newdataport(s, f): 78 global nextport 79 port = nextport + FTP_DATA_PORT 80 nextport = (nextport+1) % 16 81 r = socket(AF_INET, SOCK_STREAM) 82 r.bind((gethostbyname(gethostname()), port)) 83 r.listen(1) 84 sendportcmd(s, f, port) 85 return r 86 87 88# Send an appropriate port command. 89# 90def sendportcmd(s, f, port): 91 hostname = gethostname() 92 hostaddr = gethostbyname(hostname) 93 hbytes = string.splitfields(hostaddr, '.') 94 pbytes = [repr(port//256), repr(port%256)] 95 bytes = hbytes + pbytes 96 cmd = 'PORT ' + string.joinfields(bytes, ',') 97 s.send(cmd + '\r\n') 98 code = getreply(f) 99 100 101# Process an ftp reply and return the 3-digit reply code (as a string). 102# The reply should be a line of text starting with a 3-digit number. 103# If the 4th char is '-', it is a multi-line reply and is 104# terminate by a line starting with the same 3-digit number. 105# Any text while receiving the reply is echoed to the file. 106# 107def getreply(f): 108 line = f.readline() 109 if not line: return 'EOF' 110 print line, 111 code = line[:3] 112 if line[3:4] == '-': 113 while 1: 114 line = f.readline() 115 if not line: break # Really an error 116 print line, 117 if line[:3] == code and line[3:4] != '-': break 118 return code 119 120 121# Get the data from the data connection. 122# 123def getdata(r): 124 print '(accepting data connection)' 125 conn, host = r.accept() 126 print '(data connection accepted)' 127 while 1: 128 data = conn.recv(BUFSIZE) 129 if not data: break 130 sys.stdout.write(data) 131 print '(end of data connection)' 132 133# Get a command from the user. 134# 135def getcommand(): 136 try: 137 while 1: 138 line = raw_input('ftp.py> ') 139 if line: return line 140 except EOFError: 141 return '' 142 143 144# Call the main program. 145# 146main() 147