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 21 import sys, posix, string 22 from socket import * 23 24 25 BUFSIZE = 1024 26 27 # Default port numbers used by the FTP protocol. 28 # 29 FTP_PORT = 21 30 FTP_DATA_PORT = FTP_PORT - 1 31 32 # Change the data port to something not needing root permissions. 33 # 34 FTP_DATA_PORT = FTP_DATA_PORT + 50000 35 36 37 # Main program (called at the end of this file). 38 # 39 def main(): 40 hostname = sys.argv[1] 41 control(hostname) 42 43 44 # Control process (user interface and user protocol interpreter). 45 # 46 def 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 # 75 nextport = 0 76 # 77 def 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 # 90 def 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 # 107 def 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 # 123 def 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 # 135 def 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 # 146 main() 147