xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 57963)
121183Sdist /*
2*57963Sandrew  * Copyright (c) 1983, 1993 Regents of the University of California.
333822Sbostic  * All rights reserved.
433822Sbostic  *
542673Sbostic  * %sccs.include.redist.c%
621183Sdist  */
721183Sdist 
813609Ssam #ifndef lint
921183Sdist char copyright[] =
10*57963Sandrew "@(#) Copyright (c) 1983, 1993 Regents of the University of California.\n\
1121183Sdist  All rights reserved.\n";
1233822Sbostic #endif /* not lint */
137772Ssam 
1421183Sdist #ifndef lint
15*57963Sandrew static char sccsid[] = "@(#)tftpd.c	5.14 (Berkeley) 02/12/93";
1633822Sbostic #endif /* not lint */
1721183Sdist 
187772Ssam /*
197772Ssam  * Trivial file transfer protocol server.
2026108Sminshall  *
21*57963Sandrew  * This version includes many modifications by Jim Guyton
22*57963Sandrew  * <guyton@rand-unix>.
237772Ssam  */
2426108Sminshall 
25*57963Sandrew #include <sys/param.h>
269220Ssam #include <sys/ioctl.h>
2713609Ssam #include <sys/stat.h>
28*57963Sandrew #include <sys/socket.h>
299220Ssam 
309220Ssam #include <netinet/in.h>
3112217Ssam #include <arpa/tftp.h>
32*57963Sandrew #include <arpa/inet.h>
33*57963Sandrew 
34*57963Sandrew #include <ctype.h>
35*57963Sandrew #include <errno.h>
36*57963Sandrew #include <fcntl.h>
3746687Sbostic #include <netdb.h>
3842413Sbostic #include <setjmp.h>
39*57963Sandrew #include <signal.h>
407772Ssam #include <stdio.h>
41*57963Sandrew #include <stdlib.h>
4242413Sbostic #include <string.h>
43*57963Sandrew #include <syslog.h>
449220Ssam 
45*57963Sandrew #include "pathnames.h"
46*57963Sandrew 
4713020Ssam #define	TIMEOUT		5
4813020Ssam 
498385Ssam struct	sockaddr_in sin = { AF_INET };
5016372Skarels int	peer;
5113020Ssam int	rexmtval = TIMEOUT;
5213020Ssam int	maxtimeout = 5*TIMEOUT;
5326108Sminshall 
5426108Sminshall #define	PKTSIZE	SEGSIZE+4
5526108Sminshall char	buf[PKTSIZE];
5626108Sminshall char	ackbuf[PKTSIZE];
5716372Skarels struct	sockaddr_in from;
5816372Skarels int	fromlen;
597772Ssam 
60*57963Sandrew /*
61*57963Sandrew  * Null-terminated directory prefix list for absolute pathname requests and
62*57963Sandrew  * search list for relative pathname requests.
63*57963Sandrew  *
64*57963Sandrew  * MAXDIRS should be at least as large as the number of arguments that
65*57963Sandrew  * inetd allows (currently 20).
66*57963Sandrew  */
67*57963Sandrew #define MAXDIRS	20
68*57963Sandrew static struct dirlist {
69*57963Sandrew 	char	*name;
70*57963Sandrew 	int	len;
71*57963Sandrew } dirs[MAXDIRS+1];
72*57963Sandrew static int	suppress_naks;
73*57963Sandrew static int	logging;
7435783Stef 
75*57963Sandrew static char *errtomsg();
76*57963Sandrew static char *verifyhost();
77*57963Sandrew 
78*57963Sandrew main(argc, argv)
79*57963Sandrew 	int argc;
80*57963Sandrew 	char *argv[];
817772Ssam {
827772Ssam 	register struct tftphdr *tp;
83*57963Sandrew 	register int n;
84*57963Sandrew 	int ch, on;
857772Ssam 
86*57963Sandrew 	openlog("tftpd", LOG_PID, LOG_FTP);
87*57963Sandrew 	while ((ch = getopt(argc, argv, "ln")) != EOF) {
88*57963Sandrew 		switch (ch) {
89*57963Sandrew 		case 'l':
90*57963Sandrew 			logging = 1;
91*57963Sandrew 			break;
92*57963Sandrew 		case 'n':
93*57963Sandrew 			suppress_naks = 1;
94*57963Sandrew 			break;
95*57963Sandrew 		default:
96*57963Sandrew 			syslog(LOG_WARNING, "ignoring unknown option -%c", ch);
97*57963Sandrew 		}
98*57963Sandrew 	}
99*57963Sandrew 	if (optind < argc) {
100*57963Sandrew 		struct dirlist *dirp;
101*57963Sandrew 
102*57963Sandrew 		/* Get list of directory prefixes. Skip relative pathnames. */
103*57963Sandrew 		for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS];
104*57963Sandrew 		     optind++) {
105*57963Sandrew 			if (argv[optind][0] == '/') {
106*57963Sandrew 				dirp->name = argv[optind];
107*57963Sandrew 				dirp->len  = strlen(dirp->name);
108*57963Sandrew 				dirp++;
109*57963Sandrew 			}
110*57963Sandrew 		}
111*57963Sandrew 	}
112*57963Sandrew 
113*57963Sandrew 	on = 1;
11428070Sminshall 	if (ioctl(0, FIONBIO, &on) < 0) {
11528070Sminshall 		syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
11628070Sminshall 		exit(1);
11728070Sminshall 	}
11816372Skarels 	fromlen = sizeof (from);
11916372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
12046687Sbostic 	    (struct sockaddr *)&from, &fromlen);
12116372Skarels 	if (n < 0) {
12228070Sminshall 		syslog(LOG_ERR, "recvfrom: %m\n");
1238385Ssam 		exit(1);
1248385Ssam 	}
12528070Sminshall 	/*
12628070Sminshall 	 * Now that we have read the message out of the UDP
12728070Sminshall 	 * socket, we fork and exit.  Thus, inetd will go back
12828070Sminshall 	 * to listening to the tftp port, and the next request
12928070Sminshall 	 * to come in will start up a new instance of tftpd.
13028070Sminshall 	 *
13128070Sminshall 	 * We do this so that inetd can run tftpd in "wait" mode.
13228070Sminshall 	 * The problem with tftpd running in "nowait" mode is that
13328070Sminshall 	 * inetd may get one or more successful "selects" on the
13428070Sminshall 	 * tftp port before we do our receive, so more than one
13528070Sminshall 	 * instance of tftpd may be started up.  Worse, if tftpd
13628070Sminshall 	 * break before doing the above "recvfrom", inetd would
13728070Sminshall 	 * spawn endless instances, clogging the system.
13828070Sminshall 	 */
13928070Sminshall 	{
14028070Sminshall 		int pid;
14128070Sminshall 		int i, j;
14228070Sminshall 
14328070Sminshall 		for (i = 1; i < 20; i++) {
14428070Sminshall 		    pid = fork();
14528070Sminshall 		    if (pid < 0) {
14628070Sminshall 				sleep(i);
14728070Sminshall 				/*
14828070Sminshall 				 * flush out to most recently sent request.
14928070Sminshall 				 *
15028070Sminshall 				 * This may drop some request, but those
15128070Sminshall 				 * will be resent by the clients when
15228070Sminshall 				 * they timeout.  The positive effect of
15328070Sminshall 				 * this flush is to (try to) prevent more
15428070Sminshall 				 * than one tftpd being started up to service
15528070Sminshall 				 * a single request from a single client.
15628070Sminshall 				 */
15728070Sminshall 				j = sizeof from;
15828070Sminshall 				i = recvfrom(0, buf, sizeof (buf), 0,
15946687Sbostic 				    (struct sockaddr *)&from, &j);
16028070Sminshall 				if (i > 0) {
16128070Sminshall 					n = i;
16228070Sminshall 					fromlen = j;
16328070Sminshall 				}
16428070Sminshall 		    } else {
16528070Sminshall 				break;
16628070Sminshall 		    }
16728070Sminshall 		}
16828070Sminshall 		if (pid < 0) {
16928070Sminshall 			syslog(LOG_ERR, "fork: %m\n");
17028070Sminshall 			exit(1);
17128070Sminshall 		} else if (pid != 0) {
17228070Sminshall 			exit(0);
17328070Sminshall 		}
17428070Sminshall 	}
17516372Skarels 	from.sin_family = AF_INET;
17616372Skarels 	alarm(0);
17716372Skarels 	close(0);
17816372Skarels 	close(1);
17916372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
18016372Skarels 	if (peer < 0) {
18128070Sminshall 		syslog(LOG_ERR, "socket: %m\n");
18216372Skarels 		exit(1);
1837772Ssam 	}
18446687Sbostic 	if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
18528070Sminshall 		syslog(LOG_ERR, "bind: %m\n");
18616372Skarels 		exit(1);
18716372Skarels 	}
18846687Sbostic 	if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) {
18928070Sminshall 		syslog(LOG_ERR, "connect: %m\n");
19016372Skarels 		exit(1);
1917772Ssam 	}
19216372Skarels 	tp = (struct tftphdr *)buf;
19316372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
19416372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
19516372Skarels 		tftp(tp, n);
19616372Skarels 	exit(1);
1977772Ssam }
1987772Ssam 
1997772Ssam int	validate_access();
2007772Ssam int	sendfile(), recvfile();
2017772Ssam 
2027772Ssam struct formats {
2037772Ssam 	char	*f_mode;
2047772Ssam 	int	(*f_validate)();
2057772Ssam 	int	(*f_send)();
2067772Ssam 	int	(*f_recv)();
20726108Sminshall 	int	f_convert;
2087772Ssam } formats[] = {
20926108Sminshall 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
21026108Sminshall 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
2117772Ssam #ifdef notdef
21226108Sminshall 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
2137772Ssam #endif
2147772Ssam 	{ 0 }
2157772Ssam };
2167772Ssam 
2177772Ssam /*
2187772Ssam  * Handle initial connection protocol.
2197772Ssam  */
22016372Skarels tftp(tp, size)
2217772Ssam 	struct tftphdr *tp;
2227772Ssam 	int size;
2237772Ssam {
2247772Ssam 	register char *cp;
2257772Ssam 	int first = 1, ecode;
2267772Ssam 	register struct formats *pf;
2277772Ssam 	char *filename, *mode;
2287772Ssam 
2297772Ssam 	filename = cp = tp->th_stuff;
2307772Ssam again:
2317772Ssam 	while (cp < buf + size) {
2327772Ssam 		if (*cp == '\0')
2337772Ssam 			break;
2347772Ssam 		cp++;
2357772Ssam 	}
2367772Ssam 	if (*cp != '\0') {
2377772Ssam 		nak(EBADOP);
2387772Ssam 		exit(1);
2397772Ssam 	}
2407772Ssam 	if (first) {
2417772Ssam 		mode = ++cp;
2427772Ssam 		first = 0;
2437772Ssam 		goto again;
2447772Ssam 	}
2457772Ssam 	for (cp = mode; *cp; cp++)
2467772Ssam 		if (isupper(*cp))
2477772Ssam 			*cp = tolower(*cp);
2487772Ssam 	for (pf = formats; pf->f_mode; pf++)
2497772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
2507772Ssam 			break;
2517772Ssam 	if (pf->f_mode == 0) {
2527772Ssam 		nak(EBADOP);
2537772Ssam 		exit(1);
2547772Ssam 	}
255*57963Sandrew 	ecode = (*pf->f_validate)(&filename, tp->th_opcode);
256*57963Sandrew 	if (logging) {
257*57963Sandrew 		syslog(LOG_INFO, "%s: %s request for %s: %s",
258*57963Sandrew 			verifyhost(&from),
259*57963Sandrew 			tp->th_opcode == WRQ ? "write" : "read",
260*57963Sandrew 			filename, errtomsg(ecode));
261*57963Sandrew 	}
2627772Ssam 	if (ecode) {
263*57963Sandrew 		/*
264*57963Sandrew 		 * Avoid storms of naks to a RRQ broadcast for a relative
265*57963Sandrew 		 * bootfile pathname from a diskless Sun.
266*57963Sandrew 		 */
267*57963Sandrew 		if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
268*57963Sandrew 			exit(0);
2697772Ssam 		nak(ecode);
2707772Ssam 		exit(1);
2717772Ssam 	}
2727772Ssam 	if (tp->th_opcode == WRQ)
2737772Ssam 		(*pf->f_recv)(pf);
2747772Ssam 	else
2757772Ssam 		(*pf->f_send)(pf);
2767772Ssam 	exit(0);
2777772Ssam }
2787772Ssam 
27916372Skarels 
28026108Sminshall FILE *file;
28126108Sminshall 
2827772Ssam /*
2837772Ssam  * Validate file access.  Since we
2847772Ssam  * have no uid or gid, for now require
2857772Ssam  * file to exist and be publicly
2867772Ssam  * readable/writable.
28735783Stef  * If we were invoked with arguments
28835783Stef  * from inetd then the file must also be
28935783Stef  * in one of the given directory prefixes.
2907772Ssam  * Note also, full path name must be
2917772Ssam  * given as we have no login directory.
2927772Ssam  */
293*57963Sandrew validate_access(filep, mode)
294*57963Sandrew 	char **filep;
2957772Ssam 	int mode;
2967772Ssam {
2977772Ssam 	struct stat stbuf;
29826108Sminshall 	int	fd;
299*57963Sandrew 	struct dirlist *dirp;
300*57963Sandrew 	static char pathname[MAXPATHLEN];
301*57963Sandrew 	char *filename = *filep;
3027772Ssam 
30341146Stef 	/*
304*57963Sandrew 	 * Prevent tricksters from getting around the directory restrictions
30541146Stef 	 */
306*57963Sandrew 	if (strstr(filename, "/../"))
30735783Stef 		return (EACCESS);
308*57963Sandrew 
309*57963Sandrew 	if (*filename == '/') {
310*57963Sandrew 		/*
311*57963Sandrew 		 * Allow the request if it's in one of the approved locations.
312*57963Sandrew 		 * Special case: check the null prefix ("/") by looking
313*57963Sandrew 		 * for length = 1 and relying on the arg. processing that
314*57963Sandrew 		 * it's a /.
315*57963Sandrew 		 */
316*57963Sandrew 		for (dirp = dirs; dirp->name != NULL; dirp++) {
317*57963Sandrew 			if (dirp->len == 1 ||
318*57963Sandrew 			    (!strncmp(filename, dirp->name, dirp->len) &&
319*57963Sandrew 			     filename[dirp->len] == '/'))
320*57963Sandrew 				    break;
321*57963Sandrew 		}
322*57963Sandrew 		/* If directory list is empty, allow access to any file */
323*57963Sandrew 		if (dirp->name == NULL && dirp != dirs)
3247772Ssam 			return (EACCESS);
325*57963Sandrew 		if (stat(filename, &stbuf) < 0)
326*57963Sandrew 			return (errno == ENOENT ? ENOTFOUND : EACCESS);
327*57963Sandrew 		if ((stbuf.st_mode & S_IFMT) != S_IFREG)
328*57963Sandrew 			return (ENOTFOUND);
329*57963Sandrew 		if (mode == RRQ) {
330*57963Sandrew 			if ((stbuf.st_mode & S_IROTH) == 0)
331*57963Sandrew 				return (EACCESS);
332*57963Sandrew 		} else {
333*57963Sandrew 			if ((stbuf.st_mode & S_IWOTH) == 0)
334*57963Sandrew 				return (EACCESS);
335*57963Sandrew 		}
3367772Ssam 	} else {
337*57963Sandrew 		int err;
338*57963Sandrew 
339*57963Sandrew 		/*
340*57963Sandrew 		 * Relative file name: search the approved locations for it.
341*57963Sandrew 		 * Don't allow write requests or ones that avoid directory
342*57963Sandrew 		 * restrictions.
343*57963Sandrew 		 */
344*57963Sandrew 
345*57963Sandrew 		if (mode != RRQ || !strncmp(filename, "../", 3))
3467772Ssam 			return (EACCESS);
347*57963Sandrew 
348*57963Sandrew 		/*
349*57963Sandrew 		 * If the file exists in one of the directories and isn't
350*57963Sandrew 		 * readable, continue looking. However, change the error code
351*57963Sandrew 		 * to give an indication that the file exists.
352*57963Sandrew 		 */
353*57963Sandrew 		err = ENOTFOUND;
354*57963Sandrew 		for (dirp = dirs; dirp->name != NULL; dirp++) {
355*57963Sandrew 			sprintf(pathname, "%s/%s", dirp->name, filename);
356*57963Sandrew 			if (stat(pathname, &stbuf) == 0 &&
357*57963Sandrew 			    (stbuf.st_mode & S_IFMT) == S_IFREG) {
358*57963Sandrew 				if ((stbuf.st_mode & S_IROTH) != 0) {
359*57963Sandrew 					break;
360*57963Sandrew 				}
361*57963Sandrew 				err = EACCESS;
362*57963Sandrew 			}
363*57963Sandrew 		}
364*57963Sandrew 		if (dirp->name == NULL)
365*57963Sandrew 			return (err);
366*57963Sandrew 		*filep = filename = pathname;
3677772Ssam 	}
36826108Sminshall 	fd = open(filename, mode == RRQ ? 0 : 1);
3697772Ssam 	if (fd < 0)
3707772Ssam 		return (errno + 100);
37126108Sminshall 	file = fdopen(fd, (mode == RRQ)? "r":"w");
37226108Sminshall 	if (file == NULL) {
37326108Sminshall 		return errno+100;
37426108Sminshall 	}
3757772Ssam 	return (0);
3767772Ssam }
3777772Ssam 
37813020Ssam int	timeout;
37913020Ssam jmp_buf	timeoutbuf;
3807772Ssam 
38146687Sbostic void
3827772Ssam timer()
3837772Ssam {
38413020Ssam 
38513020Ssam 	timeout += rexmtval;
38613020Ssam 	if (timeout >= maxtimeout)
3877772Ssam 		exit(1);
38813020Ssam 	longjmp(timeoutbuf, 1);
3897772Ssam }
3907772Ssam 
3917772Ssam /*
3927772Ssam  * Send the requested file.
3937772Ssam  */
3947772Ssam sendfile(pf)
39526108Sminshall 	struct formats *pf;
3967772Ssam {
39726108Sminshall 	struct tftphdr *dp, *r_init();
39826108Sminshall 	register struct tftphdr *ap;    /* ack packet */
3997772Ssam 	register int block = 1, size, n;
4007772Ssam 
40113020Ssam 	signal(SIGALRM, timer);
40226108Sminshall 	dp = r_init();
40326108Sminshall 	ap = (struct tftphdr *)ackbuf;
4047772Ssam 	do {
40526108Sminshall 		size = readit(file, &dp, pf->f_convert);
4067772Ssam 		if (size < 0) {
4077772Ssam 			nak(errno + 100);
40826108Sminshall 			goto abort;
4097772Ssam 		}
41026108Sminshall 		dp->th_opcode = htons((u_short)DATA);
41126108Sminshall 		dp->th_block = htons((u_short)block);
4127772Ssam 		timeout = 0;
41313020Ssam 		(void) setjmp(timeoutbuf);
41426108Sminshall 
41526109Sminshall send_data:
41626108Sminshall 		if (send(peer, dp, size + 4, 0) != size + 4) {
41728070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
41826108Sminshall 			goto abort;
4197772Ssam 		}
42026108Sminshall 		read_ahead(file, pf->f_convert);
42126109Sminshall 		for ( ; ; ) {
42226108Sminshall 			alarm(rexmtval);        /* read the ack */
42326108Sminshall 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
4247772Ssam 			alarm(0);
42513020Ssam 			if (n < 0) {
42628070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
42726108Sminshall 				goto abort;
42813020Ssam 			}
42926108Sminshall 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
43026108Sminshall 			ap->th_block = ntohs((u_short)ap->th_block);
43126108Sminshall 
43226108Sminshall 			if (ap->th_opcode == ERROR)
43326108Sminshall 				goto abort;
43426109Sminshall 
43526109Sminshall 			if (ap->th_opcode == ACK) {
43626109Sminshall 				if (ap->th_block == block) {
43726109Sminshall 					break;
43826109Sminshall 				}
43926115Sminshall 				/* Re-synchronize with the other side */
44026115Sminshall 				(void) synchnet(peer);
44126109Sminshall 				if (ap->th_block == (block -1)) {
44226109Sminshall 					goto send_data;
44326109Sminshall 				}
44426109Sminshall 			}
44526108Sminshall 
44626109Sminshall 		}
4477772Ssam 		block++;
4487772Ssam 	} while (size == SEGSIZE);
44926108Sminshall abort:
45026108Sminshall 	(void) fclose(file);
4517772Ssam }
4527772Ssam 
45346687Sbostic void
45426108Sminshall justquit()
45526108Sminshall {
45626108Sminshall 	exit(0);
45726108Sminshall }
45826108Sminshall 
45926108Sminshall 
4607772Ssam /*
4617772Ssam  * Receive a file.
4627772Ssam  */
4637772Ssam recvfile(pf)
46426108Sminshall 	struct formats *pf;
4657772Ssam {
46626108Sminshall 	struct tftphdr *dp, *w_init();
46726108Sminshall 	register struct tftphdr *ap;    /* ack buffer */
4687772Ssam 	register int block = 0, n, size;
4697772Ssam 
47013020Ssam 	signal(SIGALRM, timer);
47126108Sminshall 	dp = w_init();
47226108Sminshall 	ap = (struct tftphdr *)ackbuf;
4737772Ssam 	do {
4747772Ssam 		timeout = 0;
47526108Sminshall 		ap->th_opcode = htons((u_short)ACK);
47626108Sminshall 		ap->th_block = htons((u_short)block);
4777772Ssam 		block++;
47813020Ssam 		(void) setjmp(timeoutbuf);
47926108Sminshall send_ack:
48026108Sminshall 		if (send(peer, ackbuf, 4, 0) != 4) {
48128070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
48213020Ssam 			goto abort;
4837772Ssam 		}
48426108Sminshall 		write_behind(file, pf->f_convert);
48526108Sminshall 		for ( ; ; ) {
48613020Ssam 			alarm(rexmtval);
48726108Sminshall 			n = recv(peer, dp, PKTSIZE, 0);
4887772Ssam 			alarm(0);
48926108Sminshall 			if (n < 0) {            /* really? */
49028070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
49113020Ssam 				goto abort;
49213020Ssam 			}
49326108Sminshall 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
49426108Sminshall 			dp->th_block = ntohs((u_short)dp->th_block);
49526108Sminshall 			if (dp->th_opcode == ERROR)
49613020Ssam 				goto abort;
49726108Sminshall 			if (dp->th_opcode == DATA) {
49826108Sminshall 				if (dp->th_block == block) {
49926108Sminshall 					break;   /* normal */
50026108Sminshall 				}
50126115Sminshall 				/* Re-synchronize with the other side */
50226115Sminshall 				(void) synchnet(peer);
50326108Sminshall 				if (dp->th_block == (block-1))
50426108Sminshall 					goto send_ack;          /* rexmit */
50526108Sminshall 			}
50626108Sminshall 		}
50726108Sminshall 		/*  size = write(file, dp->th_data, n - 4); */
50826108Sminshall 		size = writeit(file, &dp, n - 4, pf->f_convert);
50926108Sminshall 		if (size != (n-4)) {                    /* ahem */
51026108Sminshall 			if (size < 0) nak(errno + 100);
51126108Sminshall 			else nak(ENOSPACE);
51213020Ssam 			goto abort;
5137772Ssam 		}
5147772Ssam 	} while (size == SEGSIZE);
51526108Sminshall 	write_behind(file, pf->f_convert);
51626108Sminshall 	(void) fclose(file);            /* close data file */
51726108Sminshall 
51826108Sminshall 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
51926108Sminshall 	ap->th_block = htons((u_short)(block));
52026108Sminshall 	(void) send(peer, ackbuf, 4, 0);
52126108Sminshall 
52226108Sminshall 	signal(SIGALRM, justquit);      /* just quit on timeout */
52326108Sminshall 	alarm(rexmtval);
52426108Sminshall 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
52526108Sminshall 	alarm(0);
52626108Sminshall 	if (n >= 4 &&                   /* if read some data */
52726108Sminshall 	    dp->th_opcode == DATA &&    /* and got a data block */
52826108Sminshall 	    block == dp->th_block) {	/* then my last ack was lost */
52926108Sminshall 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
53026108Sminshall 	}
53113020Ssam abort:
53226108Sminshall 	return;
5337772Ssam }
5347772Ssam 
5357772Ssam struct errmsg {
5367772Ssam 	int	e_code;
5377772Ssam 	char	*e_msg;
5387772Ssam } errmsgs[] = {
5397772Ssam 	{ EUNDEF,	"Undefined error code" },
5407772Ssam 	{ ENOTFOUND,	"File not found" },
5417772Ssam 	{ EACCESS,	"Access violation" },
5427772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
5437772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
5447772Ssam 	{ EBADID,	"Unknown transfer ID" },
5457772Ssam 	{ EEXISTS,	"File already exists" },
5467772Ssam 	{ ENOUSER,	"No such user" },
5477772Ssam 	{ -1,		0 }
5487772Ssam };
5497772Ssam 
550*57963Sandrew static char *
551*57963Sandrew errtomsg(error)
552*57963Sandrew 	int error;
553*57963Sandrew {
554*57963Sandrew 	static char buf[20];
555*57963Sandrew 	register struct errmsg *pe;
556*57963Sandrew 	if (error == 0)
557*57963Sandrew 		return "success";
558*57963Sandrew 	for (pe = errmsgs; pe->e_code >= 0; pe++)
559*57963Sandrew 		if (pe->e_code == error)
560*57963Sandrew 			return pe->e_msg;
561*57963Sandrew 	sprintf(buf, "error %d", error);
562*57963Sandrew 	return buf;
563*57963Sandrew }
564*57963Sandrew 
5657772Ssam /*
5667772Ssam  * Send a nak packet (error message).
5677772Ssam  * Error code passed in is one of the
5687772Ssam  * standard TFTP codes, or a UNIX errno
5697772Ssam  * offset by 100.
5707772Ssam  */
5717772Ssam nak(error)
5727772Ssam 	int error;
5737772Ssam {
5747772Ssam 	register struct tftphdr *tp;
5757772Ssam 	int length;
5767772Ssam 	register struct errmsg *pe;
5777772Ssam 
5787772Ssam 	tp = (struct tftphdr *)buf;
5797772Ssam 	tp->th_opcode = htons((u_short)ERROR);
5807772Ssam 	tp->th_code = htons((u_short)error);
5817772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
5827772Ssam 		if (pe->e_code == error)
5837772Ssam 			break;
58426108Sminshall 	if (pe->e_code < 0) {
58542413Sbostic 		pe->e_msg = strerror(error - 100);
58626108Sminshall 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
58726108Sminshall 	}
5887772Ssam 	strcpy(tp->th_msg, pe->e_msg);
5897772Ssam 	length = strlen(pe->e_msg);
5907772Ssam 	tp->th_msg[length] = '\0';
5917772Ssam 	length += 5;
59216372Skarels 	if (send(peer, buf, length, 0) != length)
59328070Sminshall 		syslog(LOG_ERR, "nak: %m\n");
5947772Ssam }
595*57963Sandrew 
596*57963Sandrew static char *
597*57963Sandrew verifyhost(fromp)
598*57963Sandrew 	struct sockaddr_in *fromp;
599*57963Sandrew {
600*57963Sandrew 	struct hostent *hp;
601*57963Sandrew 
602*57963Sandrew 	hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (fromp->sin_addr),
603*57963Sandrew 			    fromp->sin_family);
604*57963Sandrew 	if (hp)
605*57963Sandrew 		return hp->h_name;
606*57963Sandrew 	else
607*57963Sandrew 		return inet_ntoa(fromp->sin_addr);
608*57963Sandrew }
609