xref: /plan9-contrib/sys/src/cmd/ip/tftpd.c (revision 219b2ee8daee37f4aad58d63f21287faa8e4ffdc)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ip.h>
5 #include <ndb.h>
6 
7 void	fatal(int syserr, char *fmt, ...);
8 void	openlisten(void);
9 
10 int 	dbg;
11 int	restricted;
12 int	tftpreq;
13 int	tftpaddr;
14 int	tftpctl;
15 void	openlisten(void);
16 void	sendfile(int, char*, char*);
17 void	recvfile(int, char*, char*);
18 void	nak(int, int, char*);
19 void	ack(int, ushort);
20 void	clrcon(void);
21 void	setuser(void);
22 char*	sunkernel(char*);
23 
24 char	mbuf[32768];
25 char	raddr[32];
26 
27 char	*dir = "/lib/tftpd";
28 char	*dirsl;
29 int	dirsllen;
30 char	flog[] = "ipboot";
31 
32 enum
33 {
34 	Tftp_READ	= 1,
35 	Tftp_WRITE	= 2,
36 	Tftp_DATA	= 3,
37 	Tftp_ACK	= 4,
38 	Tftp_ERROR	= 5,
39 	Segsize		= 512,
40 };
41 
42 void
43 main(int argc, char **argv)
44 {
45 	int n, dlen, clen;
46 	char connect[64], buf[64], datadir[64];
47 	char *mode, *p;
48 	short op;
49 	int ctl, data;
50 
51 	ARGBEGIN{
52 	case 'd':
53 		dbg++;
54 		break;
55 	case 'h':
56 		dir = ARGF();
57 		break;
58 	case 'r':
59 		restricted = 1;
60 		break;
61 	default:
62 		fprint(2, "usage: tftpd [-dr] [-h homedir]\n");
63 		exits("usage");
64 	}ARGEND
65 	USED(argc); USED(argv);
66 
67 	snprint(buf, sizeof buf, "%s/", dir);
68 	dirsl = strdup(buf);
69 	dirsllen = strlen(dirsl);
70 
71 	fmtinstall('E', eipconv);
72 	fmtinstall('I', eipconv);
73 
74 	if(chdir(dir) < 0)
75 		fatal(1, "cant get to directory %s", dir);
76 
77 	switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
78 	case -1:
79 		fatal(1, "fork");
80 	case 0:
81 		break;
82 	default:
83 		exits(0);
84 	}
85 
86 	syslog(dbg, flog, "started");
87 
88 	openlisten();
89 	setuser();
90 	for(;;) {
91 		dlen = read(tftpreq, mbuf, sizeof(mbuf));
92 		if(dlen < 0)
93 			fatal(1, "listen read");
94 		seek(tftpaddr, 0, 0);
95 		clen = read(tftpaddr, raddr, sizeof(raddr));
96 		if(clen < 0)
97 			fatal(1, "request address read");
98 		raddr[clen-1] = '\0';
99 		clrcon();
100 
101 		ctl = open("/net/udp/clone", ORDWR);
102 		if(ctl < 0)
103 			fatal(1, "open udp clone");
104 		n = read(ctl, buf, sizeof(buf));
105 		if(n < 0)
106 			fatal(1, "read udp ctl");
107 		buf[n] = 0;
108 
109 		clen = sprint(connect, "connect %s", raddr);
110 		n = write(ctl, connect, clen);
111 		if(n < 0)
112 			fatal(1, "udp %s", raddr);
113 
114 		sprint(datadir, "/net/udp/%s/data", buf);
115 		data = open(datadir, ORDWR);
116 		if(data < 0)
117 			fatal(1, "open udp data");
118 
119 		close(ctl);
120 
121 		dlen -= 2;
122 		mode = mbuf+2;
123 		while(*mode != '\0' && dlen--)
124 			mode++;
125 		mode++;
126 		p = mode;
127 		while(*p && dlen--)
128 			p++;
129 		if(dlen == 0) {
130 			nak(data, 0, "bad tftpmode");
131 			close(data);
132 			syslog(dbg, flog, "bad mode %s", raddr);
133 			continue;
134 		}
135 
136 		op = mbuf[0]<<8 | mbuf[1];
137 		if(op != Tftp_READ && op != Tftp_WRITE) {
138 			nak(data, 4, "Illegal TFTP operation");
139 			close(data);
140 			syslog(dbg, flog, "bad request %d %s", op, raddr);
141 			continue;
142 		}
143 		if(restricted){
144 			if(strncmp(mbuf+2, "../", 3) || strstr(mbuf+2, "/../") ||
145 			  (mbuf[2] == '/' && strncmp(mbuf+2, dirsl, dirsllen)!=0)){
146 				nak(data, 4, "Permission denied");
147 				close(data);
148 				syslog(dbg, flog, "bad request %d %s", op, raddr);
149 				continue;
150 			}
151 		}
152 		switch(fork()) {
153 		case -1:
154 			fatal(1, "fork");
155 		case 0:
156 			if(op == Tftp_READ)
157 				sendfile(data, mbuf+2, mode);
158 			else
159 				recvfile(data, mbuf+2, mode);
160 			exits("done");
161 		default:
162 			close(data);
163 		}
164 	}
165 }
166 
167 void
168 catcher(void *junk, char *msg)
169 {
170 	USED(junk);
171 
172 	if(strncmp(msg, "exit", 4) == 0)
173 		noted(NDFLT);
174 	noted(NCONT);
175 }
176 
177 void
178 sendfile(int fd, char *name, char *mode)
179 {
180 	int file;
181 	uchar buf[Segsize+4];
182 	uchar ack[1024];
183 	char errbuf[ERRLEN];
184 	int ackblock, block, ret;
185 	int rexmit, n, al, txtry, rxl;
186 	short op;
187 
188 	syslog(dbg, flog, "send file '%s' %s to %s", name, mode, raddr);
189 	name = sunkernel(name);
190 	if(name == 0){
191 		nak(fd, 0, "not in our database");
192 		return;
193 	}
194 
195 	notify(catcher);
196 
197 	file = open(name, OREAD);
198 	if(file < 0) {
199 		errstr(errbuf);
200 		nak(fd, 0, errbuf);
201 		return;
202 	}
203 	block = 0;
204 	rexmit = 0;
205 	n = 0;
206 	for(txtry = 0; txtry < 5;) {
207 		if(rexmit == 0) {
208 			block++;
209 			buf[0] = 0;
210 			buf[1] = Tftp_DATA;
211 			buf[2] = block>>8;
212 			buf[3] = block;
213 			n = read(file, buf+4, Segsize);
214 			if(n < 0) {
215 				errstr(errbuf);
216 				nak(fd, 0, errbuf);
217 				return;
218 			}
219 			txtry = 0;
220 		}
221 		else
222 			txtry++;
223 		ret = write(fd, buf, 4+n);
224 		if(ret < 0)
225 			fatal(1, "tftp: network write error");
226 
227 		for(rxl = 0; rxl < 10; rxl++) {
228 			rexmit = 0;
229 			alarm(500);
230 			al = read(fd, ack, sizeof(ack));
231 			alarm(0);
232 			if(al < 0) {
233 				rexmit = 1;
234 				break;
235 			}
236 			op = ack[0]<<8|ack[1];
237 			if(op == Tftp_ERROR)
238 				goto error;
239 			ackblock = ack[2]<<8|ack[3];
240 			if(ackblock == block)
241 				break;
242 			if(ackblock == 0xffff) {
243 				rexmit = 1;
244 				break;
245 			}
246 		}
247 		if(ret != Segsize+4 && rexmit == 0)
248 			break;
249 	}
250 error:
251 	close(fd);
252 	close(file);
253 }
254 
255 void
256 recvfile(int fd, char *name, char *mode)
257 {
258 	ushort op, block, inblock;
259 	uchar buf[Segsize+8];
260 	char errbuf[ERRLEN];
261 	int n, ret, file;
262 
263 	syslog(dbg, flog, "receive file '%s' %s from %s", name, mode, raddr);
264 
265 	file = create(name, OWRITE, 0666);
266 	if(file < 0) {
267 		errstr(errbuf);
268 		nak(fd, 0, errbuf);
269 		return;
270 	}
271 
272 	block = 0;
273 	ack(fd, block);
274 	block++;
275 
276 	for(;;) {
277 		alarm(15000);
278 		n = read(fd, buf, sizeof(buf));
279 		alarm(0);
280 		if(n < 0)
281 			goto error;
282 		op = buf[0]<<8|buf[1];
283 		if(op == Tftp_ERROR)
284 			goto error;
285 
286 		n -= 4;
287 		inblock = buf[2]<<8|buf[3];
288 		if(op == Tftp_DATA) {
289 			if(inblock == block) {
290 				ret = write(file, buf, n);
291 				if(ret < 0) {
292 					errstr(errbuf);
293 					nak(fd, 0, errbuf);
294 					goto error;
295 				}
296 				ack(fd, block);
297 				block++;
298 			}
299 			ack(fd, 0xffff);
300 		}
301 	}
302 error:
303 	close(file);
304 }
305 
306 void
307 ack(int fd, ushort block)
308 {
309 	uchar ack[4];
310 	int n;
311 
312 	ack[0] = 0;
313 	ack[1] = Tftp_ACK;
314 	ack[2] = block>>8;
315 	ack[3] = block;
316 
317 	n = write(fd, ack, 4);
318 	if(n < 0)
319 		fatal(1, "network write");
320 }
321 
322 void
323 nak(int fd, int code, char *msg)
324 {
325 	char buf[128];
326 	int n;
327 
328 	buf[0] = 0;
329 	buf[1] = Tftp_ERROR;
330 	buf[2] = 0;
331 	buf[3] = code;
332 	strcpy(buf+4, msg);
333 	n = strlen(msg) + 4 + 1;
334 	n = write(fd, buf, n);
335 	if(n < 0)
336 		fatal(1, "write nak");
337 }
338 
339 void
340 fatal(int syserr, char *fmt, ...)
341 {
342 	char buf[ERRLEN], sysbuf[ERRLEN];
343 
344 	doprint(buf, buf+sizeof(buf), fmt, (&fmt+1));
345 	if(syserr) {
346 		errstr(sysbuf);
347 		fprint(2, "tftpd: %s: %s\n", buf, sysbuf);
348 	}
349 	else
350 		fprint(2, "tftpd: %s\n", buf);
351 	exits(buf);
352 }
353 
354 void
355 openlisten(void)
356 {
357 	char buf[128], data[128];
358 	int n;
359 
360 	tftpctl = open("/net/udp/clone", ORDWR);
361 	if(tftpctl < 0)
362 		fatal(1, "open udp clone");
363 
364 	n = read(tftpctl, buf, sizeof(buf));
365 	if(n < 0)
366 		fatal(1, "read clone");
367 	buf[n] = 0;
368 
369 	n = write(tftpctl, "announce 69", sizeof("announce 69"));
370 	if(n < 0)
371 		fatal(1, "can't announce");
372 
373 	sprint(data, "/net/udp/%s/data", buf);
374 
375 	tftpreq = open(data, ORDWR);
376 	if(tftpreq < 0)
377 		fatal(1, "open udp/data");
378 
379 	sprint(data, "/net/udp/%s/remote", buf);
380 	tftpaddr = open(data, OREAD);
381 	if(tftpaddr < 0)
382 		fatal(1, "open udp/remote");
383 
384 }
385 
386 void
387 clrcon(void)
388 {
389 	int n;
390 
391 	n = write(tftpctl, "connect 0.0.0.0!0!r", sizeof("connect 0.0.0.0!0!r"));
392 	if(n < 0)
393 		fatal(1, "clear connect");
394 }
395 
396 void
397 setuser(void)
398 {
399 	int f;
400 
401 	f = open("/dev/user", OWRITE);
402 	if(f < 0)
403 		return;
404 	write(f, "none", sizeof("none"));
405 	close(f);
406 }
407 
408 /*
409  *  for sun kernel boots, replace the requested file name with
410  *  a one from our database.  If the database doesn't specify a file,
411  *  don't answer.
412  */
413 char*
414 sunkernel(char *name)
415 {
416 	ulong addr;
417 	uchar ipaddr[4];
418 	char buf[32];
419 	static Ipinfo info;
420 	static Ndb *db;
421 
422 	if(strlen(name) != 14 || strncmp(name + 8, ".SUN", 4) != 0)
423 		return name;
424 
425 	addr = strtoul(name, 0, 16);
426 	ipaddr[0] = addr>>24;
427 	ipaddr[1] = addr>>16;
428 	ipaddr[2] = addr>>8;
429 	ipaddr[3] = addr;
430 	sprint(buf, "%I", ipaddr);
431 	if(db == 0)
432 		db = ndbopen(0);
433 	if(db == 0)
434 		return 0;
435 	if(ipinfo(db, 0, buf, 0, &info) < 0)
436 		return 0;
437 	if(info.bootf[0])
438 		return info.bootf;
439 	return 0;
440 }
441