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