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