|
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() |