xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 34774)
1 /*
2  * Copyright (c) 1983 Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that the above copyright notice and this paragraph are
7  * duplicated in all such forms and that any documentation,
8  * advertising materials, and other materials related to such
9  * distribution and use acknowledge that the software was developed
10  * by the University of California, Berkeley.  The name of the
11  * University may not be used to endorse or promote products derived
12  * from this software without specific prior written permission.
13  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16  */
17 
18 #ifndef lint
19 char copyright[] =
20 "@(#) Copyright (c) 1983 Regents of the University of California.\n\
21  All rights reserved.\n";
22 #endif /* not lint */
23 
24 #ifndef lint
25 static char sccsid[] = "@(#)tftpd.c	5.8 (Berkeley) 06/18/88";
26 #endif /* not lint */
27 
28 /*
29  * Trivial file transfer protocol server.
30  *
31  * This version includes many modifications by Jim Guyton <guyton@rand-unix>
32  */
33 
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <sys/ioctl.h>
37 #include <sys/wait.h>
38 #include <sys/stat.h>
39 
40 #include <netinet/in.h>
41 
42 #include <arpa/tftp.h>
43 
44 #include <signal.h>
45 #include <stdio.h>
46 #include <errno.h>
47 #include <ctype.h>
48 #include <netdb.h>
49 #include <setjmp.h>
50 #include <syslog.h>
51 
52 #define	TIMEOUT		5
53 
54 extern	int errno;
55 struct	sockaddr_in sin = { AF_INET };
56 int	peer;
57 int	rexmtval = TIMEOUT;
58 int	maxtimeout = 5*TIMEOUT;
59 
60 #define	PKTSIZE	SEGSIZE+4
61 char	buf[PKTSIZE];
62 char	ackbuf[PKTSIZE];
63 struct	sockaddr_in from;
64 int	fromlen;
65 
66 main()
67 {
68 	register struct tftphdr *tp;
69 	register int n;
70 	int on = 1;
71 
72 	openlog("tftpd", LOG_PID, LOG_DAEMON);
73 	if (ioctl(0, FIONBIO, &on) < 0) {
74 		syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
75 		exit(1);
76 	}
77 	fromlen = sizeof (from);
78 	n = recvfrom(0, buf, sizeof (buf), 0,
79 	    (caddr_t)&from, &fromlen);
80 	if (n < 0) {
81 		syslog(LOG_ERR, "recvfrom: %m\n");
82 		exit(1);
83 	}
84 	/*
85 	 * Now that we have read the message out of the UDP
86 	 * socket, we fork and exit.  Thus, inetd will go back
87 	 * to listening to the tftp port, and the next request
88 	 * to come in will start up a new instance of tftpd.
89 	 *
90 	 * We do this so that inetd can run tftpd in "wait" mode.
91 	 * The problem with tftpd running in "nowait" mode is that
92 	 * inetd may get one or more successful "selects" on the
93 	 * tftp port before we do our receive, so more than one
94 	 * instance of tftpd may be started up.  Worse, if tftpd
95 	 * break before doing the above "recvfrom", inetd would
96 	 * spawn endless instances, clogging the system.
97 	 */
98 	{
99 		int pid;
100 		int i, j;
101 
102 		for (i = 1; i < 20; i++) {
103 		    pid = fork();
104 		    if (pid < 0) {
105 				sleep(i);
106 				/*
107 				 * flush out to most recently sent request.
108 				 *
109 				 * This may drop some request, but those
110 				 * will be resent by the clients when
111 				 * they timeout.  The positive effect of
112 				 * this flush is to (try to) prevent more
113 				 * than one tftpd being started up to service
114 				 * a single request from a single client.
115 				 */
116 				j = sizeof from;
117 				i = recvfrom(0, buf, sizeof (buf), 0,
118 				    (caddr_t)&from, &j);
119 				if (i > 0) {
120 					n = i;
121 					fromlen = j;
122 				}
123 		    } else {
124 				break;
125 		    }
126 		}
127 		if (pid < 0) {
128 			syslog(LOG_ERR, "fork: %m\n");
129 			exit(1);
130 		} else if (pid != 0) {
131 			exit(0);
132 		}
133 	}
134 	from.sin_family = AF_INET;
135 	alarm(0);
136 	close(0);
137 	close(1);
138 	peer = socket(AF_INET, SOCK_DGRAM, 0);
139 	if (peer < 0) {
140 		syslog(LOG_ERR, "socket: %m\n");
141 		exit(1);
142 	}
143 	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
144 		syslog(LOG_ERR, "bind: %m\n");
145 		exit(1);
146 	}
147 	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
148 		syslog(LOG_ERR, "connect: %m\n");
149 		exit(1);
150 	}
151 	tp = (struct tftphdr *)buf;
152 	tp->th_opcode = ntohs(tp->th_opcode);
153 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
154 		tftp(tp, n);
155 	exit(1);
156 }
157 
158 int	validate_access();
159 int	sendfile(), recvfile();
160 
161 struct formats {
162 	char	*f_mode;
163 	int	(*f_validate)();
164 	int	(*f_send)();
165 	int	(*f_recv)();
166 	int	f_convert;
167 } formats[] = {
168 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
169 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
170 #ifdef notdef
171 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
172 #endif
173 	{ 0 }
174 };
175 
176 /*
177  * Handle initial connection protocol.
178  */
179 tftp(tp, size)
180 	struct tftphdr *tp;
181 	int size;
182 {
183 	register char *cp;
184 	int first = 1, ecode;
185 	register struct formats *pf;
186 	char *filename, *mode;
187 
188 	filename = cp = tp->th_stuff;
189 again:
190 	while (cp < buf + size) {
191 		if (*cp == '\0')
192 			break;
193 		cp++;
194 	}
195 	if (*cp != '\0') {
196 		nak(EBADOP);
197 		exit(1);
198 	}
199 	if (first) {
200 		mode = ++cp;
201 		first = 0;
202 		goto again;
203 	}
204 	for (cp = mode; *cp; cp++)
205 		if (isupper(*cp))
206 			*cp = tolower(*cp);
207 	for (pf = formats; pf->f_mode; pf++)
208 		if (strcmp(pf->f_mode, mode) == 0)
209 			break;
210 	if (pf->f_mode == 0) {
211 		nak(EBADOP);
212 		exit(1);
213 	}
214 	ecode = (*pf->f_validate)(filename, tp->th_opcode);
215 	if (ecode) {
216 		nak(ecode);
217 		exit(1);
218 	}
219 	if (tp->th_opcode == WRQ)
220 		(*pf->f_recv)(pf);
221 	else
222 		(*pf->f_send)(pf);
223 	exit(0);
224 }
225 
226 
227 FILE *file;
228 
229 /*
230  * Validate file access.  Since we
231  * have no uid or gid, for now require
232  * file to exist and be publicly
233  * readable/writable.
234  * Note also, full path name must be
235  * given as we have no login directory.
236  */
237 validate_access(filename, mode)
238 	char *filename;
239 	int mode;
240 {
241 	struct stat stbuf;
242 	int	fd;
243 
244 	if (*filename != '/')
245 		return (EACCESS);
246 	if (stat(filename, &stbuf) < 0)
247 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
248 	if (mode == RRQ) {
249 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
250 			return (EACCESS);
251 	} else {
252 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
253 			return (EACCESS);
254 	}
255 	fd = open(filename, mode == RRQ ? 0 : 1);
256 	if (fd < 0)
257 		return (errno + 100);
258 	file = fdopen(fd, (mode == RRQ)? "r":"w");
259 	if (file == NULL) {
260 		return errno+100;
261 	}
262 	return (0);
263 }
264 
265 int	timeout;
266 jmp_buf	timeoutbuf;
267 
268 timer()
269 {
270 
271 	timeout += rexmtval;
272 	if (timeout >= maxtimeout)
273 		exit(1);
274 	longjmp(timeoutbuf, 1);
275 }
276 
277 /*
278  * Send the requested file.
279  */
280 sendfile(pf)
281 	struct formats *pf;
282 {
283 	struct tftphdr *dp, *r_init();
284 	register struct tftphdr *ap;    /* ack packet */
285 	register int block = 1, size, n;
286 
287 	signal(SIGALRM, timer);
288 	dp = r_init();
289 	ap = (struct tftphdr *)ackbuf;
290 	do {
291 		size = readit(file, &dp, pf->f_convert);
292 		if (size < 0) {
293 			nak(errno + 100);
294 			goto abort;
295 		}
296 		dp->th_opcode = htons((u_short)DATA);
297 		dp->th_block = htons((u_short)block);
298 		timeout = 0;
299 		(void) setjmp(timeoutbuf);
300 
301 send_data:
302 		if (send(peer, dp, size + 4, 0) != size + 4) {
303 			syslog(LOG_ERR, "tftpd: write: %m\n");
304 			goto abort;
305 		}
306 		read_ahead(file, pf->f_convert);
307 		for ( ; ; ) {
308 			alarm(rexmtval);        /* read the ack */
309 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
310 			alarm(0);
311 			if (n < 0) {
312 				syslog(LOG_ERR, "tftpd: read: %m\n");
313 				goto abort;
314 			}
315 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
316 			ap->th_block = ntohs((u_short)ap->th_block);
317 
318 			if (ap->th_opcode == ERROR)
319 				goto abort;
320 
321 			if (ap->th_opcode == ACK) {
322 				if (ap->th_block == block) {
323 					break;
324 				}
325 				/* Re-synchronize with the other side */
326 				(void) synchnet(peer);
327 				if (ap->th_block == (block -1)) {
328 					goto send_data;
329 				}
330 			}
331 
332 		}
333 		block++;
334 	} while (size == SEGSIZE);
335 abort:
336 	(void) fclose(file);
337 }
338 
339 justquit()
340 {
341 	exit(0);
342 }
343 
344 
345 /*
346  * Receive a file.
347  */
348 recvfile(pf)
349 	struct formats *pf;
350 {
351 	struct tftphdr *dp, *w_init();
352 	register struct tftphdr *ap;    /* ack buffer */
353 	register int block = 0, n, size;
354 
355 	signal(SIGALRM, timer);
356 	dp = w_init();
357 	ap = (struct tftphdr *)ackbuf;
358 	do {
359 		timeout = 0;
360 		ap->th_opcode = htons((u_short)ACK);
361 		ap->th_block = htons((u_short)block);
362 		block++;
363 		(void) setjmp(timeoutbuf);
364 send_ack:
365 		if (send(peer, ackbuf, 4, 0) != 4) {
366 			syslog(LOG_ERR, "tftpd: write: %m\n");
367 			goto abort;
368 		}
369 		write_behind(file, pf->f_convert);
370 		for ( ; ; ) {
371 			alarm(rexmtval);
372 			n = recv(peer, dp, PKTSIZE, 0);
373 			alarm(0);
374 			if (n < 0) {            /* really? */
375 				syslog(LOG_ERR, "tftpd: read: %m\n");
376 				goto abort;
377 			}
378 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
379 			dp->th_block = ntohs((u_short)dp->th_block);
380 			if (dp->th_opcode == ERROR)
381 				goto abort;
382 			if (dp->th_opcode == DATA) {
383 				if (dp->th_block == block) {
384 					break;   /* normal */
385 				}
386 				/* Re-synchronize with the other side */
387 				(void) synchnet(peer);
388 				if (dp->th_block == (block-1))
389 					goto send_ack;          /* rexmit */
390 			}
391 		}
392 		/*  size = write(file, dp->th_data, n - 4); */
393 		size = writeit(file, &dp, n - 4, pf->f_convert);
394 		if (size != (n-4)) {                    /* ahem */
395 			if (size < 0) nak(errno + 100);
396 			else nak(ENOSPACE);
397 			goto abort;
398 		}
399 	} while (size == SEGSIZE);
400 	write_behind(file, pf->f_convert);
401 	(void) fclose(file);            /* close data file */
402 
403 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
404 	ap->th_block = htons((u_short)(block));
405 	(void) send(peer, ackbuf, 4, 0);
406 
407 	signal(SIGALRM, justquit);      /* just quit on timeout */
408 	alarm(rexmtval);
409 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
410 	alarm(0);
411 	if (n >= 4 &&                   /* if read some data */
412 	    dp->th_opcode == DATA &&    /* and got a data block */
413 	    block == dp->th_block) {	/* then my last ack was lost */
414 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
415 	}
416 abort:
417 	return;
418 }
419 
420 struct errmsg {
421 	int	e_code;
422 	char	*e_msg;
423 } errmsgs[] = {
424 	{ EUNDEF,	"Undefined error code" },
425 	{ ENOTFOUND,	"File not found" },
426 	{ EACCESS,	"Access violation" },
427 	{ ENOSPACE,	"Disk full or allocation exceeded" },
428 	{ EBADOP,	"Illegal TFTP operation" },
429 	{ EBADID,	"Unknown transfer ID" },
430 	{ EEXISTS,	"File already exists" },
431 	{ ENOUSER,	"No such user" },
432 	{ -1,		0 }
433 };
434 
435 /*
436  * Send a nak packet (error message).
437  * Error code passed in is one of the
438  * standard TFTP codes, or a UNIX errno
439  * offset by 100.
440  */
441 nak(error)
442 	int error;
443 {
444 	register struct tftphdr *tp;
445 	int length;
446 	register struct errmsg *pe;
447 	extern char *sys_errlist[];
448 
449 	tp = (struct tftphdr *)buf;
450 	tp->th_opcode = htons((u_short)ERROR);
451 	tp->th_code = htons((u_short)error);
452 	for (pe = errmsgs; pe->e_code >= 0; pe++)
453 		if (pe->e_code == error)
454 			break;
455 	if (pe->e_code < 0) {
456 		pe->e_msg = sys_errlist[error - 100];
457 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
458 	}
459 	strcpy(tp->th_msg, pe->e_msg);
460 	length = strlen(pe->e_msg);
461 	tp->th_msg[length] = '\0';
462 	length += 5;
463 	if (send(peer, buf, length, 0) != length)
464 		syslog(LOG_ERR, "nak: %m\n");
465 }
466