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