xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 60039)
121183Sdist /*
257963Sandrew  * 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[] =
1057963Sandrew "@(#) 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*60039Storek static char sccsid[] = "@(#)tftpd.c	5.15 (Berkeley) 05/16/93";
1633822Sbostic #endif /* not lint */
1721183Sdist 
187772Ssam /*
197772Ssam  * Trivial file transfer protocol server.
2026108Sminshall  *
2157963Sandrew  * This version includes many modifications by Jim Guyton
2257963Sandrew  * <guyton@rand-unix>.
237772Ssam  */
2426108Sminshall 
2557963Sandrew #include <sys/param.h>
269220Ssam #include <sys/ioctl.h>
2713609Ssam #include <sys/stat.h>
2857963Sandrew #include <sys/socket.h>
299220Ssam 
309220Ssam #include <netinet/in.h>
3112217Ssam #include <arpa/tftp.h>
3257963Sandrew #include <arpa/inet.h>
3357963Sandrew 
3457963Sandrew #include <ctype.h>
3557963Sandrew #include <errno.h>
3657963Sandrew #include <fcntl.h>
3746687Sbostic #include <netdb.h>
3842413Sbostic #include <setjmp.h>
3957963Sandrew #include <signal.h>
407772Ssam #include <stdio.h>
4157963Sandrew #include <stdlib.h>
4242413Sbostic #include <string.h>
4357963Sandrew #include <syslog.h>
44*60039Storek #include <unistd.h>
459220Ssam 
46*60039Storek #include "tftpsubs.h"
4757963Sandrew #include "pathnames.h"
4857963Sandrew 
4913020Ssam #define	TIMEOUT		5
5013020Ssam 
5116372Skarels int	peer;
5213020Ssam int	rexmtval = TIMEOUT;
5313020Ssam int	maxtimeout = 5*TIMEOUT;
5426108Sminshall 
5526108Sminshall #define	PKTSIZE	SEGSIZE+4
5626108Sminshall char	buf[PKTSIZE];
5726108Sminshall char	ackbuf[PKTSIZE];
5816372Skarels struct	sockaddr_in from;
5916372Skarels int	fromlen;
607772Ssam 
61*60039Storek void	tftp __P((struct tftphdr *, int));
62*60039Storek 
6357963Sandrew /*
64*60039Storek  * Null-terminated directory prefix list for absolute pathname requests and
6557963Sandrew  * search list for relative pathname requests.
6657963Sandrew  *
6757963Sandrew  * MAXDIRS should be at least as large as the number of arguments that
6857963Sandrew  * inetd allows (currently 20).
6957963Sandrew  */
7057963Sandrew #define MAXDIRS	20
7157963Sandrew static struct dirlist {
7257963Sandrew 	char	*name;
7357963Sandrew 	int	len;
7457963Sandrew } dirs[MAXDIRS+1];
7557963Sandrew static int	suppress_naks;
7657963Sandrew static int	logging;
7735783Stef 
78*60039Storek static char *errtomsg __P((int));
79*60039Storek static void  nak __P((int));
80*60039Storek static char *verifyhost __P((struct sockaddr_in *));
8157963Sandrew 
82*60039Storek int
8357963Sandrew main(argc, argv)
8457963Sandrew 	int argc;
8557963Sandrew 	char *argv[];
867772Ssam {
877772Ssam 	register struct tftphdr *tp;
8857963Sandrew 	register int n;
8957963Sandrew 	int ch, on;
90*60039Storek 	struct sockaddr_in sin;
917772Ssam 
9257963Sandrew 	openlog("tftpd", LOG_PID, LOG_FTP);
9357963Sandrew 	while ((ch = getopt(argc, argv, "ln")) != EOF) {
9457963Sandrew 		switch (ch) {
9557963Sandrew 		case 'l':
9657963Sandrew 			logging = 1;
9757963Sandrew 			break;
9857963Sandrew 		case 'n':
9957963Sandrew 			suppress_naks = 1;
10057963Sandrew 			break;
10157963Sandrew 		default:
10257963Sandrew 			syslog(LOG_WARNING, "ignoring unknown option -%c", ch);
10357963Sandrew 		}
10457963Sandrew 	}
10557963Sandrew 	if (optind < argc) {
10657963Sandrew 		struct dirlist *dirp;
10757963Sandrew 
10857963Sandrew 		/* Get list of directory prefixes. Skip relative pathnames. */
10957963Sandrew 		for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS];
11057963Sandrew 		     optind++) {
11157963Sandrew 			if (argv[optind][0] == '/') {
11257963Sandrew 				dirp->name = argv[optind];
11357963Sandrew 				dirp->len  = strlen(dirp->name);
11457963Sandrew 				dirp++;
11557963Sandrew 			}
11657963Sandrew 		}
11757963Sandrew 	}
11857963Sandrew 
11957963Sandrew 	on = 1;
12028070Sminshall 	if (ioctl(0, FIONBIO, &on) < 0) {
12128070Sminshall 		syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
12228070Sminshall 		exit(1);
12328070Sminshall 	}
12416372Skarels 	fromlen = sizeof (from);
12516372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
12646687Sbostic 	    (struct sockaddr *)&from, &fromlen);
12716372Skarels 	if (n < 0) {
12828070Sminshall 		syslog(LOG_ERR, "recvfrom: %m\n");
1298385Ssam 		exit(1);
1308385Ssam 	}
13128070Sminshall 	/*
13228070Sminshall 	 * Now that we have read the message out of the UDP
13328070Sminshall 	 * socket, we fork and exit.  Thus, inetd will go back
13428070Sminshall 	 * to listening to the tftp port, and the next request
13528070Sminshall 	 * to come in will start up a new instance of tftpd.
13628070Sminshall 	 *
13728070Sminshall 	 * We do this so that inetd can run tftpd in "wait" mode.
13828070Sminshall 	 * The problem with tftpd running in "nowait" mode is that
13928070Sminshall 	 * inetd may get one or more successful "selects" on the
14028070Sminshall 	 * tftp port before we do our receive, so more than one
14128070Sminshall 	 * instance of tftpd may be started up.  Worse, if tftpd
14228070Sminshall 	 * break before doing the above "recvfrom", inetd would
14328070Sminshall 	 * spawn endless instances, clogging the system.
14428070Sminshall 	 */
14528070Sminshall 	{
14628070Sminshall 		int pid;
14728070Sminshall 		int i, j;
14828070Sminshall 
14928070Sminshall 		for (i = 1; i < 20; i++) {
15028070Sminshall 		    pid = fork();
15128070Sminshall 		    if (pid < 0) {
15228070Sminshall 				sleep(i);
15328070Sminshall 				/*
15428070Sminshall 				 * flush out to most recently sent request.
15528070Sminshall 				 *
15628070Sminshall 				 * This may drop some request, but those
15728070Sminshall 				 * will be resent by the clients when
15828070Sminshall 				 * they timeout.  The positive effect of
15928070Sminshall 				 * this flush is to (try to) prevent more
16028070Sminshall 				 * than one tftpd being started up to service
16128070Sminshall 				 * a single request from a single client.
16228070Sminshall 				 */
16328070Sminshall 				j = sizeof from;
16428070Sminshall 				i = recvfrom(0, buf, sizeof (buf), 0,
16546687Sbostic 				    (struct sockaddr *)&from, &j);
16628070Sminshall 				if (i > 0) {
16728070Sminshall 					n = i;
16828070Sminshall 					fromlen = j;
16928070Sminshall 				}
17028070Sminshall 		    } else {
17128070Sminshall 				break;
17228070Sminshall 		    }
17328070Sminshall 		}
17428070Sminshall 		if (pid < 0) {
17528070Sminshall 			syslog(LOG_ERR, "fork: %m\n");
17628070Sminshall 			exit(1);
17728070Sminshall 		} else if (pid != 0) {
17828070Sminshall 			exit(0);
17928070Sminshall 		}
18028070Sminshall 	}
18116372Skarels 	from.sin_family = AF_INET;
18216372Skarels 	alarm(0);
18316372Skarels 	close(0);
18416372Skarels 	close(1);
18516372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
18616372Skarels 	if (peer < 0) {
18728070Sminshall 		syslog(LOG_ERR, "socket: %m\n");
18816372Skarels 		exit(1);
1897772Ssam 	}
190*60039Storek 	memset(&sin, 0, sizeof(sin));
191*60039Storek 	sin.sin_family = AF_INET;
19246687Sbostic 	if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
19328070Sminshall 		syslog(LOG_ERR, "bind: %m\n");
19416372Skarels 		exit(1);
19516372Skarels 	}
19646687Sbostic 	if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) {
19728070Sminshall 		syslog(LOG_ERR, "connect: %m\n");
19816372Skarels 		exit(1);
1997772Ssam 	}
20016372Skarels 	tp = (struct tftphdr *)buf;
20116372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
20216372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
20316372Skarels 		tftp(tp, n);
20416372Skarels 	exit(1);
2057772Ssam }
2067772Ssam 
207*60039Storek struct formats;
208*60039Storek int	validate_access __P((char **, int));
209*60039Storek void	sendfile __P((struct formats *));
210*60039Storek void	recvfile __P((struct formats *));
2117772Ssam 
2127772Ssam struct formats {
2137772Ssam 	char	*f_mode;
214*60039Storek 	int	(*f_validate) __P((char **, int));
215*60039Storek 	void	(*f_send) __P((struct formats *));
216*60039Storek 	void	(*f_recv) __P((struct formats *));
21726108Sminshall 	int	f_convert;
2187772Ssam } formats[] = {
21926108Sminshall 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
22026108Sminshall 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
2217772Ssam #ifdef notdef
22226108Sminshall 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
2237772Ssam #endif
2247772Ssam 	{ 0 }
2257772Ssam };
2267772Ssam 
2277772Ssam /*
2287772Ssam  * Handle initial connection protocol.
2297772Ssam  */
230*60039Storek void
23116372Skarels tftp(tp, size)
2327772Ssam 	struct tftphdr *tp;
2337772Ssam 	int size;
2347772Ssam {
2357772Ssam 	register char *cp;
2367772Ssam 	int first = 1, ecode;
2377772Ssam 	register struct formats *pf;
2387772Ssam 	char *filename, *mode;
2397772Ssam 
2407772Ssam 	filename = cp = tp->th_stuff;
2417772Ssam again:
2427772Ssam 	while (cp < buf + size) {
2437772Ssam 		if (*cp == '\0')
2447772Ssam 			break;
2457772Ssam 		cp++;
2467772Ssam 	}
2477772Ssam 	if (*cp != '\0') {
2487772Ssam 		nak(EBADOP);
2497772Ssam 		exit(1);
2507772Ssam 	}
2517772Ssam 	if (first) {
2527772Ssam 		mode = ++cp;
2537772Ssam 		first = 0;
2547772Ssam 		goto again;
2557772Ssam 	}
2567772Ssam 	for (cp = mode; *cp; cp++)
2577772Ssam 		if (isupper(*cp))
2587772Ssam 			*cp = tolower(*cp);
2597772Ssam 	for (pf = formats; pf->f_mode; pf++)
2607772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
2617772Ssam 			break;
2627772Ssam 	if (pf->f_mode == 0) {
2637772Ssam 		nak(EBADOP);
2647772Ssam 		exit(1);
2657772Ssam 	}
26657963Sandrew 	ecode = (*pf->f_validate)(&filename, tp->th_opcode);
26757963Sandrew 	if (logging) {
26857963Sandrew 		syslog(LOG_INFO, "%s: %s request for %s: %s",
26957963Sandrew 			verifyhost(&from),
27057963Sandrew 			tp->th_opcode == WRQ ? "write" : "read",
27157963Sandrew 			filename, errtomsg(ecode));
27257963Sandrew 	}
2737772Ssam 	if (ecode) {
27457963Sandrew 		/*
27557963Sandrew 		 * Avoid storms of naks to a RRQ broadcast for a relative
27657963Sandrew 		 * bootfile pathname from a diskless Sun.
27757963Sandrew 		 */
27857963Sandrew 		if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
27957963Sandrew 			exit(0);
2807772Ssam 		nak(ecode);
2817772Ssam 		exit(1);
2827772Ssam 	}
2837772Ssam 	if (tp->th_opcode == WRQ)
2847772Ssam 		(*pf->f_recv)(pf);
2857772Ssam 	else
2867772Ssam 		(*pf->f_send)(pf);
2877772Ssam 	exit(0);
2887772Ssam }
2897772Ssam 
29016372Skarels 
29126108Sminshall FILE *file;
29226108Sminshall 
2937772Ssam /*
2947772Ssam  * Validate file access.  Since we
2957772Ssam  * have no uid or gid, for now require
2967772Ssam  * file to exist and be publicly
2977772Ssam  * readable/writable.
29835783Stef  * If we were invoked with arguments
29935783Stef  * from inetd then the file must also be
30035783Stef  * in one of the given directory prefixes.
3017772Ssam  * Note also, full path name must be
3027772Ssam  * given as we have no login directory.
3037772Ssam  */
304*60039Storek int
30557963Sandrew validate_access(filep, mode)
30657963Sandrew 	char **filep;
3077772Ssam 	int mode;
3087772Ssam {
3097772Ssam 	struct stat stbuf;
31026108Sminshall 	int	fd;
31157963Sandrew 	struct dirlist *dirp;
31257963Sandrew 	static char pathname[MAXPATHLEN];
31357963Sandrew 	char *filename = *filep;
3147772Ssam 
31541146Stef 	/*
31657963Sandrew 	 * Prevent tricksters from getting around the directory restrictions
31741146Stef 	 */
31857963Sandrew 	if (strstr(filename, "/../"))
31935783Stef 		return (EACCESS);
32057963Sandrew 
32157963Sandrew 	if (*filename == '/') {
32257963Sandrew 		/*
32357963Sandrew 		 * Allow the request if it's in one of the approved locations.
324*60039Storek 		 * Special case: check the null prefix ("/") by looking
32557963Sandrew 		 * for length = 1 and relying on the arg. processing that
32657963Sandrew 		 * it's a /.
32757963Sandrew 		 */
32857963Sandrew 		for (dirp = dirs; dirp->name != NULL; dirp++) {
32957963Sandrew 			if (dirp->len == 1 ||
33057963Sandrew 			    (!strncmp(filename, dirp->name, dirp->len) &&
33157963Sandrew 			     filename[dirp->len] == '/'))
33257963Sandrew 				    break;
33357963Sandrew 		}
33457963Sandrew 		/* If directory list is empty, allow access to any file */
33557963Sandrew 		if (dirp->name == NULL && dirp != dirs)
3367772Ssam 			return (EACCESS);
33757963Sandrew 		if (stat(filename, &stbuf) < 0)
33857963Sandrew 			return (errno == ENOENT ? ENOTFOUND : EACCESS);
33957963Sandrew 		if ((stbuf.st_mode & S_IFMT) != S_IFREG)
34057963Sandrew 			return (ENOTFOUND);
34157963Sandrew 		if (mode == RRQ) {
34257963Sandrew 			if ((stbuf.st_mode & S_IROTH) == 0)
34357963Sandrew 				return (EACCESS);
34457963Sandrew 		} else {
34557963Sandrew 			if ((stbuf.st_mode & S_IWOTH) == 0)
34657963Sandrew 				return (EACCESS);
34757963Sandrew 		}
3487772Ssam 	} else {
34957963Sandrew 		int err;
35057963Sandrew 
351*60039Storek 		/*
35257963Sandrew 		 * Relative file name: search the approved locations for it.
35357963Sandrew 		 * Don't allow write requests or ones that avoid directory
35457963Sandrew 		 * restrictions.
35557963Sandrew 		 */
35657963Sandrew 
35757963Sandrew 		if (mode != RRQ || !strncmp(filename, "../", 3))
3587772Ssam 			return (EACCESS);
35957963Sandrew 
36057963Sandrew 		/*
36157963Sandrew 		 * If the file exists in one of the directories and isn't
362*60039Storek 		 * readable, continue looking. However, change the error code
36357963Sandrew 		 * to give an indication that the file exists.
36457963Sandrew 		 */
36557963Sandrew 		err = ENOTFOUND;
36657963Sandrew 		for (dirp = dirs; dirp->name != NULL; dirp++) {
36757963Sandrew 			sprintf(pathname, "%s/%s", dirp->name, filename);
36857963Sandrew 			if (stat(pathname, &stbuf) == 0 &&
36957963Sandrew 			    (stbuf.st_mode & S_IFMT) == S_IFREG) {
37057963Sandrew 				if ((stbuf.st_mode & S_IROTH) != 0) {
37157963Sandrew 					break;
37257963Sandrew 				}
37357963Sandrew 				err = EACCESS;
37457963Sandrew 			}
37557963Sandrew 		}
37657963Sandrew 		if (dirp->name == NULL)
37757963Sandrew 			return (err);
37857963Sandrew 		*filep = filename = pathname;
3797772Ssam 	}
38026108Sminshall 	fd = open(filename, mode == RRQ ? 0 : 1);
3817772Ssam 	if (fd < 0)
3827772Ssam 		return (errno + 100);
38326108Sminshall 	file = fdopen(fd, (mode == RRQ)? "r":"w");
38426108Sminshall 	if (file == NULL) {
38526108Sminshall 		return errno+100;
38626108Sminshall 	}
3877772Ssam 	return (0);
3887772Ssam }
3897772Ssam 
39013020Ssam int	timeout;
39113020Ssam jmp_buf	timeoutbuf;
3927772Ssam 
39346687Sbostic void
3947772Ssam timer()
3957772Ssam {
39613020Ssam 
39713020Ssam 	timeout += rexmtval;
39813020Ssam 	if (timeout >= maxtimeout)
3997772Ssam 		exit(1);
40013020Ssam 	longjmp(timeoutbuf, 1);
4017772Ssam }
4027772Ssam 
4037772Ssam /*
4047772Ssam  * Send the requested file.
4057772Ssam  */
406*60039Storek void
4077772Ssam sendfile(pf)
40826108Sminshall 	struct formats *pf;
4097772Ssam {
41026108Sminshall 	struct tftphdr *dp, *r_init();
41126108Sminshall 	register struct tftphdr *ap;    /* ack packet */
412*60039Storek 	register int size, n;
413*60039Storek 	volatile int block;
4147772Ssam 
41513020Ssam 	signal(SIGALRM, timer);
41626108Sminshall 	dp = r_init();
41726108Sminshall 	ap = (struct tftphdr *)ackbuf;
418*60039Storek 	block = 1;
4197772Ssam 	do {
42026108Sminshall 		size = readit(file, &dp, pf->f_convert);
4217772Ssam 		if (size < 0) {
4227772Ssam 			nak(errno + 100);
42326108Sminshall 			goto abort;
4247772Ssam 		}
42526108Sminshall 		dp->th_opcode = htons((u_short)DATA);
42626108Sminshall 		dp->th_block = htons((u_short)block);
4277772Ssam 		timeout = 0;
428*60039Storek 		(void)setjmp(timeoutbuf);
42926108Sminshall 
43026109Sminshall send_data:
43126108Sminshall 		if (send(peer, dp, size + 4, 0) != size + 4) {
43228070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
43326108Sminshall 			goto abort;
4347772Ssam 		}
43526108Sminshall 		read_ahead(file, pf->f_convert);
43626109Sminshall 		for ( ; ; ) {
43726108Sminshall 			alarm(rexmtval);        /* read the ack */
43826108Sminshall 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
4397772Ssam 			alarm(0);
44013020Ssam 			if (n < 0) {
44128070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
44226108Sminshall 				goto abort;
44313020Ssam 			}
44426108Sminshall 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
44526108Sminshall 			ap->th_block = ntohs((u_short)ap->th_block);
44626108Sminshall 
44726108Sminshall 			if (ap->th_opcode == ERROR)
44826108Sminshall 				goto abort;
449*60039Storek 
45026109Sminshall 			if (ap->th_opcode == ACK) {
451*60039Storek 				if (ap->th_block == block)
45226109Sminshall 					break;
45326115Sminshall 				/* Re-synchronize with the other side */
45426115Sminshall 				(void) synchnet(peer);
455*60039Storek 				if (ap->th_block == (block -1))
45626109Sminshall 					goto send_data;
45726109Sminshall 			}
45826108Sminshall 
45926109Sminshall 		}
4607772Ssam 		block++;
4617772Ssam 	} while (size == SEGSIZE);
46226108Sminshall abort:
46326108Sminshall 	(void) fclose(file);
4647772Ssam }
4657772Ssam 
46646687Sbostic void
46726108Sminshall justquit()
46826108Sminshall {
46926108Sminshall 	exit(0);
47026108Sminshall }
47126108Sminshall 
47226108Sminshall 
4737772Ssam /*
4747772Ssam  * Receive a file.
4757772Ssam  */
476*60039Storek void
4777772Ssam recvfile(pf)
47826108Sminshall 	struct formats *pf;
4797772Ssam {
48026108Sminshall 	struct tftphdr *dp, *w_init();
48126108Sminshall 	register struct tftphdr *ap;    /* ack buffer */
482*60039Storek 	register int n, size;
483*60039Storek 	volatile int block;
4847772Ssam 
48513020Ssam 	signal(SIGALRM, timer);
48626108Sminshall 	dp = w_init();
48726108Sminshall 	ap = (struct tftphdr *)ackbuf;
488*60039Storek 	block = 0;
4897772Ssam 	do {
4907772Ssam 		timeout = 0;
49126108Sminshall 		ap->th_opcode = htons((u_short)ACK);
49226108Sminshall 		ap->th_block = htons((u_short)block);
4937772Ssam 		block++;
49413020Ssam 		(void) setjmp(timeoutbuf);
49526108Sminshall send_ack:
49626108Sminshall 		if (send(peer, ackbuf, 4, 0) != 4) {
49728070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
49813020Ssam 			goto abort;
4997772Ssam 		}
50026108Sminshall 		write_behind(file, pf->f_convert);
50126108Sminshall 		for ( ; ; ) {
50213020Ssam 			alarm(rexmtval);
50326108Sminshall 			n = recv(peer, dp, PKTSIZE, 0);
5047772Ssam 			alarm(0);
50526108Sminshall 			if (n < 0) {            /* really? */
50628070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
50713020Ssam 				goto abort;
50813020Ssam 			}
50926108Sminshall 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
51026108Sminshall 			dp->th_block = ntohs((u_short)dp->th_block);
51126108Sminshall 			if (dp->th_opcode == ERROR)
51213020Ssam 				goto abort;
51326108Sminshall 			if (dp->th_opcode == DATA) {
51426108Sminshall 				if (dp->th_block == block) {
51526108Sminshall 					break;   /* normal */
51626108Sminshall 				}
51726115Sminshall 				/* Re-synchronize with the other side */
51826115Sminshall 				(void) synchnet(peer);
51926108Sminshall 				if (dp->th_block == (block-1))
52026108Sminshall 					goto send_ack;          /* rexmit */
52126108Sminshall 			}
52226108Sminshall 		}
52326108Sminshall 		/*  size = write(file, dp->th_data, n - 4); */
52426108Sminshall 		size = writeit(file, &dp, n - 4, pf->f_convert);
52526108Sminshall 		if (size != (n-4)) {                    /* ahem */
52626108Sminshall 			if (size < 0) nak(errno + 100);
52726108Sminshall 			else nak(ENOSPACE);
52813020Ssam 			goto abort;
5297772Ssam 		}
5307772Ssam 	} while (size == SEGSIZE);
53126108Sminshall 	write_behind(file, pf->f_convert);
53226108Sminshall 	(void) fclose(file);            /* close data file */
53326108Sminshall 
53426108Sminshall 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
53526108Sminshall 	ap->th_block = htons((u_short)(block));
53626108Sminshall 	(void) send(peer, ackbuf, 4, 0);
53726108Sminshall 
53826108Sminshall 	signal(SIGALRM, justquit);      /* just quit on timeout */
53926108Sminshall 	alarm(rexmtval);
54026108Sminshall 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
54126108Sminshall 	alarm(0);
54226108Sminshall 	if (n >= 4 &&                   /* if read some data */
54326108Sminshall 	    dp->th_opcode == DATA &&    /* and got a data block */
54426108Sminshall 	    block == dp->th_block) {	/* then my last ack was lost */
54526108Sminshall 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
54626108Sminshall 	}
54713020Ssam abort:
54826108Sminshall 	return;
5497772Ssam }
5507772Ssam 
5517772Ssam struct errmsg {
5527772Ssam 	int	e_code;
5537772Ssam 	char	*e_msg;
5547772Ssam } errmsgs[] = {
5557772Ssam 	{ EUNDEF,	"Undefined error code" },
5567772Ssam 	{ ENOTFOUND,	"File not found" },
5577772Ssam 	{ EACCESS,	"Access violation" },
5587772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
5597772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
5607772Ssam 	{ EBADID,	"Unknown transfer ID" },
5617772Ssam 	{ EEXISTS,	"File already exists" },
5627772Ssam 	{ ENOUSER,	"No such user" },
5637772Ssam 	{ -1,		0 }
5647772Ssam };
5657772Ssam 
56657963Sandrew static char *
56757963Sandrew errtomsg(error)
56857963Sandrew 	int error;
56957963Sandrew {
57057963Sandrew 	static char buf[20];
57157963Sandrew 	register struct errmsg *pe;
57257963Sandrew 	if (error == 0)
57357963Sandrew 		return "success";
57457963Sandrew 	for (pe = errmsgs; pe->e_code >= 0; pe++)
57557963Sandrew 		if (pe->e_code == error)
57657963Sandrew 			return pe->e_msg;
57757963Sandrew 	sprintf(buf, "error %d", error);
57857963Sandrew 	return buf;
57957963Sandrew }
58057963Sandrew 
5817772Ssam /*
5827772Ssam  * Send a nak packet (error message).
5837772Ssam  * Error code passed in is one of the
5847772Ssam  * standard TFTP codes, or a UNIX errno
5857772Ssam  * offset by 100.
5867772Ssam  */
587*60039Storek static void
5887772Ssam nak(error)
5897772Ssam 	int error;
5907772Ssam {
5917772Ssam 	register struct tftphdr *tp;
5927772Ssam 	int length;
5937772Ssam 	register struct errmsg *pe;
5947772Ssam 
5957772Ssam 	tp = (struct tftphdr *)buf;
5967772Ssam 	tp->th_opcode = htons((u_short)ERROR);
5977772Ssam 	tp->th_code = htons((u_short)error);
5987772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
5997772Ssam 		if (pe->e_code == error)
6007772Ssam 			break;
60126108Sminshall 	if (pe->e_code < 0) {
60242413Sbostic 		pe->e_msg = strerror(error - 100);
60326108Sminshall 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
60426108Sminshall 	}
6057772Ssam 	strcpy(tp->th_msg, pe->e_msg);
6067772Ssam 	length = strlen(pe->e_msg);
6077772Ssam 	tp->th_msg[length] = '\0';
6087772Ssam 	length += 5;
60916372Skarels 	if (send(peer, buf, length, 0) != length)
61028070Sminshall 		syslog(LOG_ERR, "nak: %m\n");
6117772Ssam }
61257963Sandrew 
61357963Sandrew static char *
61457963Sandrew verifyhost(fromp)
61557963Sandrew 	struct sockaddr_in *fromp;
61657963Sandrew {
61757963Sandrew 	struct hostent *hp;
61857963Sandrew 
61957963Sandrew 	hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (fromp->sin_addr),
62057963Sandrew 			    fromp->sin_family);
62157963Sandrew 	if (hp)
62257963Sandrew 		return hp->h_name;
62357963Sandrew 	else
62457963Sandrew 		return inet_ntoa(fromp->sin_addr);
62557963Sandrew }
626