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