xref: /inferno-os/appl/svc/webget/ftp.b (revision fbc1184c08d18d5ac0f8763a058e015e95353341)
1implement Transport;
2
3include "sys.m";
4	sys: Sys;
5
6include "draw.m";
7
8include "string.m";
9	S: String;
10
11include "bufio.m";
12	B : Bufio;
13	Iobuf: import Bufio;
14
15include "message.m";
16	M: Message;
17	Msg, Nameval: import M;
18
19include "url.m";
20	U: Url;
21	ParsedUrl: import U;
22
23include "webget.m";
24
25include "dial.m";
26	DI: Dial;
27
28include "wgutils.m";
29	W: WebgetUtils;
30	Fid, Req: import WebgetUtils;
31
32include "transport.m";
33
34FTPPORT: con "21";
35DEBUG: con 1;
36
37# Return codes
38Extra, Success, Incomplete, TempFail, PermFail : con (1+iota);
39
40init(w: WebgetUtils)
41{
42	sys = load Sys Sys->PATH;
43	W = w;
44	M = W->M;
45	S = W->S;
46	B = W->B;
47	U = W->U;
48	DI = W->DI;
49}
50
51connect(c: ref Fid, r: ref Req, donec: chan of ref Fid)
52{
53	mrep: ref Msg = nil;
54	io, dio: ref Iobuf = nil;
55	err := "";
56	u := r.url;
57	port := u.port;
58	if(port == "")
59		port = FTPPORT;
60	addr := DI->netmkaddr(u.host, "tcp", port);
61
62dummyloop:	# just for breaking out of on error
63	for(;;) {
64		W->log(c, sys->sprint("ftp: dialing %s", addr));
65		net := DI->dial(addr, nil);
66		if(net == nil) {
67			err = sys->sprint("dial error: %r");
68			break dummyloop;
69		}
70		io = B->fopen(net.dfd, sys->ORDWR);
71		if(io == nil) {
72			err = "cannot open network via bufio";
73			break dummyloop;
74		}
75
76		# look for Hello
77		(code, msg) := getreply(c, io);
78		if(code != Success) {
79			err = "instead of hello: " + msg;
80			break dummyloop;
81		}
82		# logon
83		err = sendrequest(c, io, "USER anonymous");
84		if(err != "")
85			break dummyloop;
86		(code, msg) = getreply(c, io);
87		if(code != Success) {
88			if(code == Incomplete) {
89				# need password
90				err = sendrequest(c, io, "PASS webget@webget.com");
91				(code, msg) = getreply(c, io);
92				if(code != Success) {
93					err = "login failed: " + msg;
94					break dummyloop;
95				}
96			}
97			else {
98				err = "login failed: " + msg;
99				break dummyloop;
100			}
101		}
102		# image type
103		err = sendrequest(c, io, "TYPE I");
104		(code, msg) = getreply(c, io);
105		if(code != Success) {
106			err = "can't set type I: " + msg;
107			break dummyloop;
108		}
109		# passive mode
110		err = sendrequest(c, io, "PASV");
111		(code, msg) = getreply(c, io);
112		if(code != Success) {
113			err = "can't use passive mode: " + msg;
114			break dummyloop;
115		}
116		(paddr, pport) := passvap(msg);
117		if(paddr == "") {
118			err = "passive mode protocol botch: " + msg;
119			break dummyloop;
120		}
121		# dial data port
122		daddr := "tcp!" + paddr + "!" + pport;
123		W->log(c, sys->sprint("ftp: dialing data %s", daddr));
124		(ok2, dnet) := sys->dial(daddr, nil);
125		if(ok2 < 0) {
126			err = sys->sprint("data dial error: %r");
127			break dummyloop;
128		}
129		dio = B->fopen(dnet.dfd, sys->ORDWR);
130		if(dio == nil) {
131			err = "cannot open network via bufio";
132			break dummyloop;
133		}
134		# tell remote to send file
135		err = sendrequest(c, io, "RETR " + u.path);
136		(code, msg) = getreply(c, io);
137		if(code != Extra) {
138			err = "passive mode retrieve failed: " + msg;
139			break dummyloop;
140		}
141
142		mrep = Msg.newmsg();
143W->log(c, "reading from dio now");
144		err = W->getdata(dio, mrep, W->fixaccept(r.types), u);
145W->log(c, "done reading from dio now, err=" + err);
146		B->dio.close();
147		if(err == "")
148			W->okprefix(r, mrep);
149		break dummyloop;
150	}
151	if(io != nil)
152		B->io.close();
153	if(dio != nil)
154		B->dio.close();
155	if(err != "")
156		mrep = W->usererr(r, err);
157	if(mrep != nil) {
158		W->log(c, "ftp: reply ready for " + r.reqid + ": " + mrep.prefixline);
159		r.reply = mrep;
160		donec <-= c;
161	}
162}
163
164getreply(c: ref Fid, io: ref Iobuf) : (int, string)
165{
166	for(;;) {
167		line := B->io.gets('\n');
168		n := len line;
169		if(n == 0)
170			break;
171		if(DEBUG)
172			W->log(c, "ftp: got reply: " + line);
173		if(line[n-1] == '\n') {
174			if(n > 2 && line[n-2] == '\r')
175				line = line[0:n-2];
176			else
177				line = line[0:n-1];
178		}
179		rv := int line;
180		if(rv >= 100 && rv < 600) {
181			# if line is like '123-stuff'
182			# then there will be more lines until
183			# '123 stuff'
184			if(len line<4 || line[3]==' ')
185				return (rv/100, line);
186		}
187	}
188	return (-1, "");
189}
190
191sendrequest(c: ref Fid, io: ref Iobuf, cmd: string) : string
192{
193	if(DEBUG)
194		W->log(c, "ftp: send request: " + cmd);
195	cmd = cmd + "\r\n";
196	buf := array of byte cmd;
197	n := len buf;
198	if(B->io.write(buf, n) != n)
199		return sys->sprint("write error: %r");
200	return "";
201}
202
203passvap(s: string) : (string, string)
204{
205	# Parse reply to PASSV to find address and port numbers.
206	# This is AI
207	addr := "";
208	port := "";
209	(nil, v) := S->splitl(s, "(");
210	if(v != "")
211		s = v[1:];
212	else
213		(nil, s) = S->splitl(s, "0123456789");
214	if(s != "") {
215		(n, l) := sys->tokenize(s, ",");
216		if(n >= 6) {
217			addr = hd l + ".";
218			l = tl l;
219			addr += hd l + ".";
220			l = tl l;
221			addr += hd l + ".";
222			l = tl l;
223			addr += hd l;
224			l = tl l;
225			p1 := int hd l;
226			p2 := int hd tl l;
227			port = string (((p1&255)<<8)|(p2&255));
228		}
229	}
230	return (addr, port);
231}
232