xref: /inferno-os/appl/cmd/ip/tftpd.b (revision 62d7827bc358c000db9ff48fe61bd28ac352a884)
1implement Tftpd;
2
3include "sys.m";
4	sys: Sys;
5	stderr: ref Sys->FD;
6
7include "draw.m";
8
9include "arg.m";
10
11include "dial.m";
12	dial: Dial;
13
14include "ip.m";
15	ip: IP;
16	IPaddr, Udphdr: import ip;
17
18Tftpd: module
19{
20	init: fn (nil: ref Draw->Context, argv: list of string);
21};
22
23dir:=  "/services/tftpd";
24net:=  "/net";
25
26Tftp_READ: con 1;
27Tftp_WRITE: con 2;
28Tftp_DATA: con 3;
29Tftp_ACK: con 4;
30Tftp_ERROR: con 5;
31
32Segsize: con 512;
33
34dbg := 0;
35restricted := 0;
36port := 69;
37
38Udphdrsize: con IP->Udphdrlen;
39
40tftpcon: ref Sys->Connection;
41tftpreq: ref Sys->FD;
42
43dokill(pid: int, scope: string)
44{
45	fd := sys->open("/prog/" + string pid + "/ctl", sys->OWRITE);
46	if(fd == nil)
47		fd = sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
48	if(fd != nil)
49		sys->fprint(fd, "kill%s", scope);
50}
51
52kill(pid: int) { dokill(pid, ""); }
53killgrp(pid: int) { dokill(pid, "grp"); }
54killme() { kill(sys->pctl(0,nil)); }
55killus() { killgrp(sys->pctl(0,nil)); }
56
57DBG(s: string)
58{
59	if(dbg)
60		sys->fprint(stderr, "tfptd: %d: %s\n", sys->pctl(0,nil), s);
61}
62
63false, true: con iota;
64
65Timer: adt {
66	KILL: con -1;
67	ALARM: con -2;
68	RETRY: con -3;
69	sig: chan of int;
70	create: fn(): ref Timer;
71	destroy: fn(t: self ref Timer);
72	set: fn(t: self ref Timer, msec, nretry: int);
73
74	ticker: fn(t: self ref Timer);
75	ticking: int;
76	wakeup: int;
77	timeout: int;
78	nretry: int;
79};
80
81Timer.create(): ref Timer
82{
83	t := ref Timer;
84	t.wakeup = 0;
85	t.ticking = false;
86	t.sig = chan of int;
87	return t;
88}
89
90Timer.destroy(t: self ref Timer)
91{
92	DBG("Timer.destroy");
93	alt {
94		t.sig <-= t.KILL =>
95			DBG("sent final msg");
96		* =>
97			DBG("couldn't send final msg");
98	}
99	DBG("Timer.destroy done");
100}
101
102Timer.ticker(t: self ref Timer)
103{
104	DBG("spawn: ticker");
105	t.ticking = true;
106	while(t.wakeup > sys->millisec()) {
107		DBG("Timer.ticker sleeping for "
108			+string (t.wakeup-sys->millisec()));
109		sys->sleep(t.wakeup-sys->millisec());
110	}
111	if(t.wakeup) {
112		DBG("Timer.ticker wakeup");
113		if(t.nretry) {
114			alt { t.sig <-= t.RETRY => ; }
115			t.ticking = false;
116			t.set(t.timeout, t.nretry-1);
117		} else
118			alt { t.sig <-= t.ALARM => ; }
119	}
120	t.ticking = false;
121	DBG("unspawn: ticker");
122}
123
124Timer.set(t: self ref Timer, msec, nretry: int)
125{
126	DBG(sys->sprint("Timer.set(%d, %d)", msec, nretry));
127	if(msec == 0) {
128		t.wakeup = 0;
129		t.timeout = 0;
130		t.nretry = 0;
131	} else {
132		t.wakeup = sys->millisec()+msec;
133		t.timeout = msec;
134		t.nretry = nretry;
135		if(!t.ticking)
136			spawn t.ticker();
137	}
138}
139
140killer(c: chan of int, pgid: int)
141{
142	DBG("spawn: killer");
143	cmd := <- c;
144	DBG(sys->sprint("killer has awakened (flag=%d)", cmd));
145	if(cmd == Timer.ALARM) {
146		killgrp(pgid);
147		DBG(sys->sprint("group %d has been killed", pgid));
148	}
149	DBG("unspawn killer");
150}
151
152init(nil: ref Draw->Context, args: list of string)
153{
154	sys = load Sys Sys->PATH;
155	sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil);
156	stderr = sys->fildes(2);
157
158	dial = load Dial Dial->PATH;
159	if(dial == nil)
160		fatal("can't load Dial");
161
162	arg := load Arg Arg->PATH;
163	if(arg == nil)
164		fatal("can't load Arg");
165
166	arg->init(args);
167	arg->setusage("tftpd [-dr] [-p port] [-h homedir] [-x network-dir]");
168	while((o := arg->opt()) != 0)
169		case o {
170		'd' =>	dbg++;
171		'h' =>	dir = arg->earg();
172		'r' =>		restricted = 1;
173		'p' =>	port = int arg->earg();
174		'x' =>	net = arg->earg();
175		* =>		arg->usage();
176		}
177	args =arg->argv();
178	if(args != nil){
179		net = hd args;
180		args = tl args;
181	}
182	if(args != nil)
183		arg->usage();
184	arg = nil;
185
186	ip = load IP IP->PATH;
187	if(ip == nil)
188		fatal(sys->sprint("can't load %s: %r", IP->PATH));
189	ip->init();
190
191	if(sys->chdir(dir) < 0)
192		fatal("can't chdir to " + dir);
193
194	spawn mainthing();
195}
196
197mainthing()
198{
199	DBG("spawn: mainthing");
200	bigbuf := array[32768] of byte;
201
202	openlisten();
203	setuser();
204	for(;;) {
205		dlen := sys->read(tftpreq, bigbuf, len bigbuf);
206		if(dlen < 0)
207			fatal("listen");
208		if(dlen < Udphdrsize)
209			continue;
210
211		hdr := Udphdr.unpack(bigbuf, Udphdrsize);
212
213		raddr := sys->sprint("%s/udp!%s!%d", net, hdr.raddr.text(), hdr.rport);
214
215		DBG(sys->sprint("raddr=%s", raddr));
216		cx := dial->dial(raddr, nil);
217		if(cx == nil)
218			fatal("dialing "+raddr);
219
220#		showbuf("bigbuf", bigbuf[0:dlen]);
221
222		op := ip->get2(bigbuf, Udphdrsize);
223		mbuf := bigbuf[Udphdrsize+2:dlen];		# get past Udphdr and op
224		dlen -= 14;
225
226		case op {
227		Tftp_READ or Tftp_WRITE =>
228			;
229		Tftp_ERROR =>
230			DBG("tftp error");
231			continue;
232		* =>
233			nak(cx.dfd, 4, "Illegal TFTP operation");
234			continue;
235		}
236
237#		showbuf("mbuf", mbuf[0:dlen]);
238
239		i := 0;
240		while(dlen > 0 && mbuf[i] != byte 0) {
241			dlen--;
242			i++;
243		}
244
245		p := i++;
246		dlen--;
247		while(dlen > 0 && mbuf[i] != byte 0) {
248			dlen--;
249			i++;
250		}
251
252		path := string mbuf[0:p];
253		mode := string mbuf[p+1:i];
254		DBG(sys->sprint("path = %s, mode = %s", path, mode));
255
256		if(dlen == 0) {
257			nak(cx.dfd, 0, "bad tftpmode");
258			continue;
259		}
260
261		if(restricted && dodgy(path)){
262			nak(cx.dfd, 4, "Permission denied");
263			continue;
264		}
265
266		if(op == Tftp_READ)
267			spawn sendfile(cx.dfd, path, mode);
268		else
269			spawn recvfile(cx.dfd, path, mode);
270	}
271}
272
273dodgy(path: string): int
274{
275	n := len path;
276	nd := len dir;
277	if(n == 0 ||
278	   path[0] == '#' ||
279	   path[0] == '/' && (n < nd+1 || path[0:nd] != dir || path[nd] != '/'))
280		return 1;
281	(nil, flds) := sys->tokenize(path, "/");
282	for(; flds != nil; flds = tl flds)
283		if(hd flds == "..")
284			return 1;
285	return 0;
286}
287
288showbuf(msg: string, b: array of byte)
289{
290	sys->fprint(stderr, "%s: size %d: ", msg, len b);
291	for(i:=0; i<len b; i++)
292		sys->fprint(stderr, "%.2ux ", int b[i]);
293	sys->fprint(stderr, "\n");
294	for(i=0; i<len b; i++)
295		if(int b[i] >= 32 && int b[i] <= 126)
296			sys->fprint(stderr, " %c", int b[i]);
297		else
298			sys->fprint(stderr, " .");
299	sys->fprint(stderr, "\n");
300}
301
302sendblock(sig: chan of int, buf: array of byte, net: ref sys->FD, ksig: chan of int)
303{
304	DBG("spawn: sendblocks");
305	nbytes := 0;
306	loop: for(;;) {
307		DBG("sendblock: waiting for cmd");
308		cmd := <- sig;
309		DBG(sys->sprint("sendblock: cmd=%d", cmd));
310		case cmd {
311		Timer.KILL =>
312			DBG("sendblock: killed");
313			return;
314		Timer.RETRY =>
315			;
316		Timer.ALARM =>
317			DBG("too many retries");
318			break loop;
319		* =>
320			nbytes = cmd;
321		}
322#		showbuf("sendblock", buf[0:nbytes]);
323		ret := sys->write(net, buf, 4+nbytes);
324		DBG(sys->sprint("ret=%d", ret));
325
326		if(ret < 0) {
327			ksig <-= Timer.ALARM;
328			fatal("tftp: network write error");
329		}
330		if(ret != 4+nbytes)
331			return;
332	}
333	DBG("sendblock: exiting");
334	alt { ksig <-= Timer.ALARM => ; }
335	DBG("unspawn: sendblocks");
336}
337
338sendfile(net: ref sys->FD, name: string, mode: string)
339{
340
341	DBG(sys->sprint("spawn: sendfile: name=%s mode=%s", name, mode));
342
343	pgrp := sys->pctl(Sys->NEWPGRP, nil);
344	ack := array[1024] of byte;
345	if(name == "") {
346		nak(net, 0, "not in our database");
347		return;
348	}
349
350	file := sys->open(name, Sys->OREAD);
351	if(file == nil) {
352		DBG(sys->sprint("open failed: %s", name));
353		errbuf := sys->sprint("%r");
354		nak(net, 0, errbuf);
355		return;
356	}
357	DBG(sys->sprint("opened %s", name));
358
359	block := 0;
360	timer := Timer.create();
361	ksig := chan of int;
362	buf := array[4+Segsize] of byte;
363
364	spawn killer(ksig, pgrp);
365	spawn sendblock(timer.sig, buf, net, ksig);
366
367	mainloop: for(;;) {
368		block++;
369		buf[0:] = array[] of {byte 0, byte Tftp_DATA,
370				byte (block>>8), byte block};
371		n := sys->read(file, buf[4:], len buf-4);
372		DBG(sys->sprint("n=%d", n));
373		if(n < 0) {
374			errbuf := sys->sprint("%r");
375			nak(net, 0, errbuf);
376			break;
377		}
378		DBG(sys->sprint("signalling write of %d to block %d", n, block));
379		timer.sig <-= n;
380		for(rxl := 0; rxl < 10; rxl++) {
381
382			timer.set(1000, 15);
383			al := sys->read(net, ack, len ack);
384			timer.set(0, 0);
385			if(al < 0) {
386				timer.sig <-= Timer.ALARM;
387				break;
388			}
389			op := (int ack[0]<<8) | int ack[1];
390			if(op == Tftp_ERROR)
391				break mainloop;
392			ackblock := (int ack[2]<<8) | int ack[3];
393			DBG(sys->sprint("got ack: block=%d ackblock=%d",
394				block, ackblock));
395			if(ackblock == block)
396				break;
397			if(ackblock == 16rffff) {
398				block--;
399				break;
400			}
401		}
402		if(n < len buf-4)
403			break;
404	}
405	timer.destroy();
406	ksig <-= Timer.KILL;
407}
408
409recvfile(fd: ref sys->FD, name: string, mode: string)
410{
411	DBG(sys->sprint("spawn: recvfile: name=%s mode=%s", name, mode));
412
413	pgrp := sys->pctl(Sys->NEWPGRP, nil);
414
415	file := sys->create(name, sys->OWRITE, 8r666);
416	if(file == nil) {
417		errbuf := sys->sprint("%r");
418		nak(fd, 0, errbuf);
419		return;
420	}
421
422	block := 0;
423	ack(fd, block);
424	block++;
425
426	buf := array[8+Segsize] of byte;
427	timer := Timer.create();
428	spawn killer(timer.sig, pgrp);
429
430	for(;;) {
431		timer.set(15000, 0);
432		DBG(sys->sprint("reading block %d", block));
433		n := sys->read(fd, buf, len buf);
434		DBG(sys->sprint("read %d bytes", n));
435		timer.set(0, 0);
436
437		if(n < 0)
438			break;
439		op := int buf[0]<<8 | int buf[1];
440		if(op == Tftp_ERROR)
441			break;
442
443#		showbuf("got", buf[0:n]);
444		n -= 4;
445		inblock := int buf[2]<<8 | int buf[3];
446#		showbuf("hdr", buf[0:4]);
447		if(op == Tftp_DATA) {
448			if(inblock == block) {
449				ret := sys->write(file, buf[4:], n);
450				if(ret < 0) {
451					errbuf := sys->sprint("%r");
452					nak(fd, 0, errbuf);
453					break;
454				}
455				block++;
456			}
457			if(inblock < block) {
458				ack(fd, inblock);
459				DBG(sys->sprint("ok: inblock=%d block=%d",
460					inblock, block));
461			} else
462				DBG(sys->sprint("FAIL: inblock=%d block=%d",
463					inblock, block));
464			ack(fd, 16rffff);
465			if(n < 512)
466				break;
467		}
468	}
469	timer.destroy();
470}
471
472ack(fd: ref Sys->FD, block: int)
473{
474	buf := array[] of {byte 0, byte Tftp_ACK, byte (block>>8), byte block};
475#	showbuf("ack", buf);
476	if(sys->write(fd, buf, 4) < 0)
477		fatal("write ack");
478}
479
480
481nak(fd: ref Sys->FD, code: int, msg: string)
482{
483sys->print("nak: %s\n", msg);
484	buf := array[128] of {byte 0, byte Tftp_ERROR, byte 0, byte code};
485	bmsg := array of byte msg;
486	buf[4:] = bmsg;
487	buf[4+len bmsg] = byte 0;
488	if(sys->write(fd, buf, 4+len bmsg+1) < 0)
489		fatal("write nak");
490}
491
492fatal(msg: string)
493{
494	sys->fprint(stderr, "tftpd: %s: %r\n", msg);
495	killus();
496	raise "fail:error";
497}
498
499openlisten()
500{
501	name := net+"/udp!*!" + string port;
502	tftpcon = dial->announce(name);
503	if(tftpcon == nil)
504		fatal("can't announce "+name);
505	if(sys->fprint(tftpcon.cfd, "headers") < 0)
506		fatal("can't set header mode");
507	tftpreq = sys->open(tftpcon.dir+"/data", sys->ORDWR);
508	if(tftpreq == nil)
509		fatal("open udp data");
510}
511
512setuser()
513{
514	f := sys->open("/dev/user", sys->OWRITE);
515	if(f != nil)
516		sys->fprint(f, "none");
517}
518
519