xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 61458)
121183Sdist /*
2*61458Sbostic  * Copyright (c) 1983, 1993
3*61458Sbostic  *	The Regents of the University of California.  All rights reserved.
433822Sbostic  *
542673Sbostic  * %sccs.include.redist.c%
621183Sdist  */
721183Sdist 
813609Ssam #ifndef lint
9*61458Sbostic static char copyright[] =
10*61458Sbostic "@(#) Copyright (c) 1983, 1993\n\
11*61458Sbostic 	The Regents of the University of California.  All rights reserved.\n";
1233822Sbostic #endif /* not lint */
137772Ssam 
1421183Sdist #ifndef lint
15*61458Sbostic static char sccsid[] = "@(#)tftpd.c	8.1 (Berkeley) 06/04/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>
4460039Storek #include <unistd.h>
459220Ssam 
4660039Storek #include "tftpsubs.h"
4757963Sandrew 
4813020Ssam #define	TIMEOUT		5
4913020Ssam 
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 
6060039Storek void	tftp __P((struct tftphdr *, int));
6160039Storek 
6257963Sandrew /*
6360039Storek  * Null-terminated directory prefix list for absolute pathname requests and
6457963Sandrew  * search list for relative pathname requests.
6557963Sandrew  *
6657963Sandrew  * MAXDIRS should be at least as large as the number of arguments that
6757963Sandrew  * inetd allows (currently 20).
6857963Sandrew  */
6957963Sandrew #define MAXDIRS	20
7057963Sandrew static struct dirlist {
7157963Sandrew 	char	*name;
7257963Sandrew 	int	len;
7357963Sandrew } dirs[MAXDIRS+1];
7457963Sandrew static int	suppress_naks;
7557963Sandrew static int	logging;
7635783Stef 
7760039Storek static char *errtomsg __P((int));
7860039Storek static void  nak __P((int));
7960039Storek static char *verifyhost __P((struct sockaddr_in *));
8057963Sandrew 
8160039Storek int
main(argc,argv)8257963Sandrew main(argc, argv)
8357963Sandrew 	int argc;
8457963Sandrew 	char *argv[];
857772Ssam {
867772Ssam 	register struct tftphdr *tp;
8757963Sandrew 	register int n;
8857963Sandrew 	int ch, on;
8960039Storek 	struct sockaddr_in sin;
907772Ssam 
9157963Sandrew 	openlog("tftpd", LOG_PID, LOG_FTP);
9257963Sandrew 	while ((ch = getopt(argc, argv, "ln")) != EOF) {
9357963Sandrew 		switch (ch) {
9457963Sandrew 		case 'l':
9557963Sandrew 			logging = 1;
9657963Sandrew 			break;
9757963Sandrew 		case 'n':
9857963Sandrew 			suppress_naks = 1;
9957963Sandrew 			break;
10057963Sandrew 		default:
10157963Sandrew 			syslog(LOG_WARNING, "ignoring unknown option -%c", ch);
10257963Sandrew 		}
10357963Sandrew 	}
10457963Sandrew 	if (optind < argc) {
10557963Sandrew 		struct dirlist *dirp;
10657963Sandrew 
10757963Sandrew 		/* Get list of directory prefixes. Skip relative pathnames. */
10857963Sandrew 		for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS];
10957963Sandrew 		     optind++) {
11057963Sandrew 			if (argv[optind][0] == '/') {
11157963Sandrew 				dirp->name = argv[optind];
11257963Sandrew 				dirp->len  = strlen(dirp->name);
11357963Sandrew 				dirp++;
11457963Sandrew 			}
11557963Sandrew 		}
11657963Sandrew 	}
11757963Sandrew 
11857963Sandrew 	on = 1;
11928070Sminshall 	if (ioctl(0, FIONBIO, &on) < 0) {
12028070Sminshall 		syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
12128070Sminshall 		exit(1);
12228070Sminshall 	}
12316372Skarels 	fromlen = sizeof (from);
12416372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
12546687Sbostic 	    (struct sockaddr *)&from, &fromlen);
12616372Skarels 	if (n < 0) {
12728070Sminshall 		syslog(LOG_ERR, "recvfrom: %m\n");
1288385Ssam 		exit(1);
1298385Ssam 	}
13028070Sminshall 	/*
13128070Sminshall 	 * Now that we have read the message out of the UDP
13228070Sminshall 	 * socket, we fork and exit.  Thus, inetd will go back
13328070Sminshall 	 * to listening to the tftp port, and the next request
13428070Sminshall 	 * to come in will start up a new instance of tftpd.
13528070Sminshall 	 *
13628070Sminshall 	 * We do this so that inetd can run tftpd in "wait" mode.
13728070Sminshall 	 * The problem with tftpd running in "nowait" mode is that
13828070Sminshall 	 * inetd may get one or more successful "selects" on the
13928070Sminshall 	 * tftp port before we do our receive, so more than one
14028070Sminshall 	 * instance of tftpd may be started up.  Worse, if tftpd
14128070Sminshall 	 * break before doing the above "recvfrom", inetd would
14228070Sminshall 	 * spawn endless instances, clogging the system.
14328070Sminshall 	 */
14428070Sminshall 	{
14528070Sminshall 		int pid;
14628070Sminshall 		int i, j;
14728070Sminshall 
14828070Sminshall 		for (i = 1; i < 20; i++) {
14928070Sminshall 		    pid = fork();
15028070Sminshall 		    if (pid < 0) {
15128070Sminshall 				sleep(i);
15228070Sminshall 				/*
15328070Sminshall 				 * flush out to most recently sent request.
15428070Sminshall 				 *
15528070Sminshall 				 * This may drop some request, but those
15628070Sminshall 				 * will be resent by the clients when
15728070Sminshall 				 * they timeout.  The positive effect of
15828070Sminshall 				 * this flush is to (try to) prevent more
15928070Sminshall 				 * than one tftpd being started up to service
16028070Sminshall 				 * a single request from a single client.
16128070Sminshall 				 */
16228070Sminshall 				j = sizeof from;
16328070Sminshall 				i = recvfrom(0, buf, sizeof (buf), 0,
16446687Sbostic 				    (struct sockaddr *)&from, &j);
16528070Sminshall 				if (i > 0) {
16628070Sminshall 					n = i;
16728070Sminshall 					fromlen = j;
16828070Sminshall 				}
16928070Sminshall 		    } else {
17028070Sminshall 				break;
17128070Sminshall 		    }
17228070Sminshall 		}
17328070Sminshall 		if (pid < 0) {
17428070Sminshall 			syslog(LOG_ERR, "fork: %m\n");
17528070Sminshall 			exit(1);
17628070Sminshall 		} else if (pid != 0) {
17728070Sminshall 			exit(0);
17828070Sminshall 		}
17928070Sminshall 	}
18016372Skarels 	from.sin_family = AF_INET;
18116372Skarels 	alarm(0);
18216372Skarels 	close(0);
18316372Skarels 	close(1);
18416372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
18516372Skarels 	if (peer < 0) {
18628070Sminshall 		syslog(LOG_ERR, "socket: %m\n");
18716372Skarels 		exit(1);
1887772Ssam 	}
18960039Storek 	memset(&sin, 0, sizeof(sin));
19060039Storek 	sin.sin_family = AF_INET;
19146687Sbostic 	if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
19228070Sminshall 		syslog(LOG_ERR, "bind: %m\n");
19316372Skarels 		exit(1);
19416372Skarels 	}
19546687Sbostic 	if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) {
19628070Sminshall 		syslog(LOG_ERR, "connect: %m\n");
19716372Skarels 		exit(1);
1987772Ssam 	}
19916372Skarels 	tp = (struct tftphdr *)buf;
20016372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
20116372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
20216372Skarels 		tftp(tp, n);
20316372Skarels 	exit(1);
2047772Ssam }
2057772Ssam 
20660039Storek struct formats;
20760039Storek int	validate_access __P((char **, int));
20860039Storek void	sendfile __P((struct formats *));
20960039Storek void	recvfile __P((struct formats *));
2107772Ssam 
2117772Ssam struct formats {
2127772Ssam 	char	*f_mode;
21360039Storek 	int	(*f_validate) __P((char **, int));
21460039Storek 	void	(*f_send) __P((struct formats *));
21560039Storek 	void	(*f_recv) __P((struct formats *));
21626108Sminshall 	int	f_convert;
2177772Ssam } formats[] = {
21826108Sminshall 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
21926108Sminshall 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
2207772Ssam #ifdef notdef
22126108Sminshall 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
2227772Ssam #endif
2237772Ssam 	{ 0 }
2247772Ssam };
2257772Ssam 
2267772Ssam /*
2277772Ssam  * Handle initial connection protocol.
2287772Ssam  */
22960039Storek void
tftp(tp,size)23016372Skarels tftp(tp, size)
2317772Ssam 	struct tftphdr *tp;
2327772Ssam 	int size;
2337772Ssam {
2347772Ssam 	register char *cp;
2357772Ssam 	int first = 1, ecode;
2367772Ssam 	register struct formats *pf;
2377772Ssam 	char *filename, *mode;
2387772Ssam 
2397772Ssam 	filename = cp = tp->th_stuff;
2407772Ssam again:
2417772Ssam 	while (cp < buf + size) {
2427772Ssam 		if (*cp == '\0')
2437772Ssam 			break;
2447772Ssam 		cp++;
2457772Ssam 	}
2467772Ssam 	if (*cp != '\0') {
2477772Ssam 		nak(EBADOP);
2487772Ssam 		exit(1);
2497772Ssam 	}
2507772Ssam 	if (first) {
2517772Ssam 		mode = ++cp;
2527772Ssam 		first = 0;
2537772Ssam 		goto again;
2547772Ssam 	}
2557772Ssam 	for (cp = mode; *cp; cp++)
2567772Ssam 		if (isupper(*cp))
2577772Ssam 			*cp = tolower(*cp);
2587772Ssam 	for (pf = formats; pf->f_mode; pf++)
2597772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
2607772Ssam 			break;
2617772Ssam 	if (pf->f_mode == 0) {
2627772Ssam 		nak(EBADOP);
2637772Ssam 		exit(1);
2647772Ssam 	}
26557963Sandrew 	ecode = (*pf->f_validate)(&filename, tp->th_opcode);
26657963Sandrew 	if (logging) {
26757963Sandrew 		syslog(LOG_INFO, "%s: %s request for %s: %s",
26857963Sandrew 			verifyhost(&from),
26957963Sandrew 			tp->th_opcode == WRQ ? "write" : "read",
27057963Sandrew 			filename, errtomsg(ecode));
27157963Sandrew 	}
2727772Ssam 	if (ecode) {
27357963Sandrew 		/*
27457963Sandrew 		 * Avoid storms of naks to a RRQ broadcast for a relative
27557963Sandrew 		 * bootfile pathname from a diskless Sun.
27657963Sandrew 		 */
27757963Sandrew 		if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
27857963Sandrew 			exit(0);
2797772Ssam 		nak(ecode);
2807772Ssam 		exit(1);
2817772Ssam 	}
2827772Ssam 	if (tp->th_opcode == WRQ)
2837772Ssam 		(*pf->f_recv)(pf);
2847772Ssam 	else
2857772Ssam 		(*pf->f_send)(pf);
2867772Ssam 	exit(0);
2877772Ssam }
2887772Ssam 
28916372Skarels 
29026108Sminshall FILE *file;
29126108Sminshall 
2927772Ssam /*
2937772Ssam  * Validate file access.  Since we
2947772Ssam  * have no uid or gid, for now require
2957772Ssam  * file to exist and be publicly
2967772Ssam  * readable/writable.
29735783Stef  * If we were invoked with arguments
29835783Stef  * from inetd then the file must also be
29935783Stef  * in one of the given directory prefixes.
3007772Ssam  * Note also, full path name must be
3017772Ssam  * given as we have no login directory.
3027772Ssam  */
30360039Storek int
validate_access(filep,mode)30457963Sandrew validate_access(filep, mode)
30557963Sandrew 	char **filep;
3067772Ssam 	int mode;
3077772Ssam {
3087772Ssam 	struct stat stbuf;
30926108Sminshall 	int	fd;
31057963Sandrew 	struct dirlist *dirp;
31157963Sandrew 	static char pathname[MAXPATHLEN];
31257963Sandrew 	char *filename = *filep;
3137772Ssam 
31441146Stef 	/*
31557963Sandrew 	 * Prevent tricksters from getting around the directory restrictions
31641146Stef 	 */
31757963Sandrew 	if (strstr(filename, "/../"))
31835783Stef 		return (EACCESS);
31957963Sandrew 
32057963Sandrew 	if (*filename == '/') {
32157963Sandrew 		/*
32257963Sandrew 		 * Allow the request if it's in one of the approved locations.
32360039Storek 		 * Special case: check the null prefix ("/") by looking
32457963Sandrew 		 * for length = 1 and relying on the arg. processing that
32557963Sandrew 		 * it's a /.
32657963Sandrew 		 */
32757963Sandrew 		for (dirp = dirs; dirp->name != NULL; dirp++) {
32857963Sandrew 			if (dirp->len == 1 ||
32957963Sandrew 			    (!strncmp(filename, dirp->name, dirp->len) &&
33057963Sandrew 			     filename[dirp->len] == '/'))
33157963Sandrew 				    break;
33257963Sandrew 		}
33357963Sandrew 		/* If directory list is empty, allow access to any file */
33457963Sandrew 		if (dirp->name == NULL && dirp != dirs)
3357772Ssam 			return (EACCESS);
33657963Sandrew 		if (stat(filename, &stbuf) < 0)
33757963Sandrew 			return (errno == ENOENT ? ENOTFOUND : EACCESS);
33857963Sandrew 		if ((stbuf.st_mode & S_IFMT) != S_IFREG)
33957963Sandrew 			return (ENOTFOUND);
34057963Sandrew 		if (mode == RRQ) {
34157963Sandrew 			if ((stbuf.st_mode & S_IROTH) == 0)
34257963Sandrew 				return (EACCESS);
34357963Sandrew 		} else {
34457963Sandrew 			if ((stbuf.st_mode & S_IWOTH) == 0)
34557963Sandrew 				return (EACCESS);
34657963Sandrew 		}
3477772Ssam 	} else {
34857963Sandrew 		int err;
34957963Sandrew 
35060039Storek 		/*
35157963Sandrew 		 * Relative file name: search the approved locations for it.
35257963Sandrew 		 * Don't allow write requests or ones that avoid directory
35357963Sandrew 		 * restrictions.
35457963Sandrew 		 */
35557963Sandrew 
35657963Sandrew 		if (mode != RRQ || !strncmp(filename, "../", 3))
3577772Ssam 			return (EACCESS);
35857963Sandrew 
35957963Sandrew 		/*
36057963Sandrew 		 * If the file exists in one of the directories and isn't
36160039Storek 		 * readable, continue looking. However, change the error code
36257963Sandrew 		 * to give an indication that the file exists.
36357963Sandrew 		 */
36457963Sandrew 		err = ENOTFOUND;
36557963Sandrew 		for (dirp = dirs; dirp->name != NULL; dirp++) {
36657963Sandrew 			sprintf(pathname, "%s/%s", dirp->name, filename);
36757963Sandrew 			if (stat(pathname, &stbuf) == 0 &&
36857963Sandrew 			    (stbuf.st_mode & S_IFMT) == S_IFREG) {
36957963Sandrew 				if ((stbuf.st_mode & S_IROTH) != 0) {
37057963Sandrew 					break;
37157963Sandrew 				}
37257963Sandrew 				err = EACCESS;
37357963Sandrew 			}
37457963Sandrew 		}
37557963Sandrew 		if (dirp->name == NULL)
37657963Sandrew 			return (err);
37757963Sandrew 		*filep = filename = pathname;
3787772Ssam 	}
37926108Sminshall 	fd = open(filename, mode == RRQ ? 0 : 1);
3807772Ssam 	if (fd < 0)
3817772Ssam 		return (errno + 100);
38226108Sminshall 	file = fdopen(fd, (mode == RRQ)? "r":"w");
38326108Sminshall 	if (file == NULL) {
38426108Sminshall 		return errno+100;
38526108Sminshall 	}
3867772Ssam 	return (0);
3877772Ssam }
3887772Ssam 
38913020Ssam int	timeout;
39013020Ssam jmp_buf	timeoutbuf;
3917772Ssam 
39246687Sbostic void
timer()3937772Ssam timer()
3947772Ssam {
39513020Ssam 
39613020Ssam 	timeout += rexmtval;
39713020Ssam 	if (timeout >= maxtimeout)
3987772Ssam 		exit(1);
39913020Ssam 	longjmp(timeoutbuf, 1);
4007772Ssam }
4017772Ssam 
4027772Ssam /*
4037772Ssam  * Send the requested file.
4047772Ssam  */
40560039Storek void
sendfile(pf)4067772Ssam sendfile(pf)
40726108Sminshall 	struct formats *pf;
4087772Ssam {
40926108Sminshall 	struct tftphdr *dp, *r_init();
41026108Sminshall 	register struct tftphdr *ap;    /* ack packet */
41160039Storek 	register int size, n;
41260039Storek 	volatile int block;
4137772Ssam 
41413020Ssam 	signal(SIGALRM, timer);
41526108Sminshall 	dp = r_init();
41626108Sminshall 	ap = (struct tftphdr *)ackbuf;
41760039Storek 	block = 1;
4187772Ssam 	do {
41926108Sminshall 		size = readit(file, &dp, pf->f_convert);
4207772Ssam 		if (size < 0) {
4217772Ssam 			nak(errno + 100);
42226108Sminshall 			goto abort;
4237772Ssam 		}
42426108Sminshall 		dp->th_opcode = htons((u_short)DATA);
42526108Sminshall 		dp->th_block = htons((u_short)block);
4267772Ssam 		timeout = 0;
42760039Storek 		(void)setjmp(timeoutbuf);
42826108Sminshall 
42926109Sminshall send_data:
43026108Sminshall 		if (send(peer, dp, size + 4, 0) != size + 4) {
43128070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
43226108Sminshall 			goto abort;
4337772Ssam 		}
43426108Sminshall 		read_ahead(file, pf->f_convert);
43526109Sminshall 		for ( ; ; ) {
43626108Sminshall 			alarm(rexmtval);        /* read the ack */
43726108Sminshall 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
4387772Ssam 			alarm(0);
43913020Ssam 			if (n < 0) {
44028070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
44126108Sminshall 				goto abort;
44213020Ssam 			}
44326108Sminshall 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
44426108Sminshall 			ap->th_block = ntohs((u_short)ap->th_block);
44526108Sminshall 
44626108Sminshall 			if (ap->th_opcode == ERROR)
44726108Sminshall 				goto abort;
44860039Storek 
44926109Sminshall 			if (ap->th_opcode == ACK) {
45060039Storek 				if (ap->th_block == block)
45126109Sminshall 					break;
45226115Sminshall 				/* Re-synchronize with the other side */
45326115Sminshall 				(void) synchnet(peer);
45460039Storek 				if (ap->th_block == (block -1))
45526109Sminshall 					goto send_data;
45626109Sminshall 			}
45726108Sminshall 
45826109Sminshall 		}
4597772Ssam 		block++;
4607772Ssam 	} while (size == SEGSIZE);
46126108Sminshall abort:
46226108Sminshall 	(void) fclose(file);
4637772Ssam }
4647772Ssam 
46546687Sbostic void
justquit()46626108Sminshall justquit()
46726108Sminshall {
46826108Sminshall 	exit(0);
46926108Sminshall }
47026108Sminshall 
47126108Sminshall 
4727772Ssam /*
4737772Ssam  * Receive a file.
4747772Ssam  */
47560039Storek void
recvfile(pf)4767772Ssam recvfile(pf)
47726108Sminshall 	struct formats *pf;
4787772Ssam {
47926108Sminshall 	struct tftphdr *dp, *w_init();
48026108Sminshall 	register struct tftphdr *ap;    /* ack buffer */
48160039Storek 	register int n, size;
48260039Storek 	volatile int block;
4837772Ssam 
48413020Ssam 	signal(SIGALRM, timer);
48526108Sminshall 	dp = w_init();
48626108Sminshall 	ap = (struct tftphdr *)ackbuf;
48760039Storek 	block = 0;
4887772Ssam 	do {
4897772Ssam 		timeout = 0;
49026108Sminshall 		ap->th_opcode = htons((u_short)ACK);
49126108Sminshall 		ap->th_block = htons((u_short)block);
4927772Ssam 		block++;
49313020Ssam 		(void) setjmp(timeoutbuf);
49426108Sminshall send_ack:
49526108Sminshall 		if (send(peer, ackbuf, 4, 0) != 4) {
49628070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
49713020Ssam 			goto abort;
4987772Ssam 		}
49926108Sminshall 		write_behind(file, pf->f_convert);
50026108Sminshall 		for ( ; ; ) {
50113020Ssam 			alarm(rexmtval);
50226108Sminshall 			n = recv(peer, dp, PKTSIZE, 0);
5037772Ssam 			alarm(0);
50426108Sminshall 			if (n < 0) {            /* really? */
50528070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
50613020Ssam 				goto abort;
50713020Ssam 			}
50826108Sminshall 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
50926108Sminshall 			dp->th_block = ntohs((u_short)dp->th_block);
51026108Sminshall 			if (dp->th_opcode == ERROR)
51113020Ssam 				goto abort;
51226108Sminshall 			if (dp->th_opcode == DATA) {
51326108Sminshall 				if (dp->th_block == block) {
51426108Sminshall 					break;   /* normal */
51526108Sminshall 				}
51626115Sminshall 				/* Re-synchronize with the other side */
51726115Sminshall 				(void) synchnet(peer);
51826108Sminshall 				if (dp->th_block == (block-1))
51926108Sminshall 					goto send_ack;          /* rexmit */
52026108Sminshall 			}
52126108Sminshall 		}
52226108Sminshall 		/*  size = write(file, dp->th_data, n - 4); */
52326108Sminshall 		size = writeit(file, &dp, n - 4, pf->f_convert);
52426108Sminshall 		if (size != (n-4)) {                    /* ahem */
52526108Sminshall 			if (size < 0) nak(errno + 100);
52626108Sminshall 			else nak(ENOSPACE);
52713020Ssam 			goto abort;
5287772Ssam 		}
5297772Ssam 	} while (size == SEGSIZE);
53026108Sminshall 	write_behind(file, pf->f_convert);
53126108Sminshall 	(void) fclose(file);            /* close data file */
53226108Sminshall 
53326108Sminshall 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
53426108Sminshall 	ap->th_block = htons((u_short)(block));
53526108Sminshall 	(void) send(peer, ackbuf, 4, 0);
53626108Sminshall 
53726108Sminshall 	signal(SIGALRM, justquit);      /* just quit on timeout */
53826108Sminshall 	alarm(rexmtval);
53926108Sminshall 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
54026108Sminshall 	alarm(0);
54126108Sminshall 	if (n >= 4 &&                   /* if read some data */
54226108Sminshall 	    dp->th_opcode == DATA &&    /* and got a data block */
54326108Sminshall 	    block == dp->th_block) {	/* then my last ack was lost */
54426108Sminshall 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
54526108Sminshall 	}
54613020Ssam abort:
54726108Sminshall 	return;
5487772Ssam }
5497772Ssam 
5507772Ssam struct errmsg {
5517772Ssam 	int	e_code;
5527772Ssam 	char	*e_msg;
5537772Ssam } errmsgs[] = {
5547772Ssam 	{ EUNDEF,	"Undefined error code" },
5557772Ssam 	{ ENOTFOUND,	"File not found" },
5567772Ssam 	{ EACCESS,	"Access violation" },
5577772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
5587772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
5597772Ssam 	{ EBADID,	"Unknown transfer ID" },
5607772Ssam 	{ EEXISTS,	"File already exists" },
5617772Ssam 	{ ENOUSER,	"No such user" },
5627772Ssam 	{ -1,		0 }
5637772Ssam };
5647772Ssam 
56557963Sandrew static char *
errtomsg(error)56657963Sandrew errtomsg(error)
56757963Sandrew 	int error;
56857963Sandrew {
56957963Sandrew 	static char buf[20];
57057963Sandrew 	register struct errmsg *pe;
57157963Sandrew 	if (error == 0)
57257963Sandrew 		return "success";
57357963Sandrew 	for (pe = errmsgs; pe->e_code >= 0; pe++)
57457963Sandrew 		if (pe->e_code == error)
57557963Sandrew 			return pe->e_msg;
57657963Sandrew 	sprintf(buf, "error %d", error);
57757963Sandrew 	return buf;
57857963Sandrew }
57957963Sandrew 
5807772Ssam /*
5817772Ssam  * Send a nak packet (error message).
5827772Ssam  * Error code passed in is one of the
5837772Ssam  * standard TFTP codes, or a UNIX errno
5847772Ssam  * offset by 100.
5857772Ssam  */
58660039Storek static void
nak(error)5877772Ssam nak(error)
5887772Ssam 	int error;
5897772Ssam {
5907772Ssam 	register struct tftphdr *tp;
5917772Ssam 	int length;
5927772Ssam 	register struct errmsg *pe;
5937772Ssam 
5947772Ssam 	tp = (struct tftphdr *)buf;
5957772Ssam 	tp->th_opcode = htons((u_short)ERROR);
5967772Ssam 	tp->th_code = htons((u_short)error);
5977772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
5987772Ssam 		if (pe->e_code == error)
5997772Ssam 			break;
60026108Sminshall 	if (pe->e_code < 0) {
60142413Sbostic 		pe->e_msg = strerror(error - 100);
60226108Sminshall 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
60326108Sminshall 	}
6047772Ssam 	strcpy(tp->th_msg, pe->e_msg);
6057772Ssam 	length = strlen(pe->e_msg);
6067772Ssam 	tp->th_msg[length] = '\0';
6077772Ssam 	length += 5;
60816372Skarels 	if (send(peer, buf, length, 0) != length)
60928070Sminshall 		syslog(LOG_ERR, "nak: %m\n");
6107772Ssam }
61157963Sandrew 
61257963Sandrew static char *
verifyhost(fromp)61357963Sandrew verifyhost(fromp)
61457963Sandrew 	struct sockaddr_in *fromp;
61557963Sandrew {
61657963Sandrew 	struct hostent *hp;
61757963Sandrew 
61857963Sandrew 	hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (fromp->sin_addr),
61957963Sandrew 			    fromp->sin_family);
62057963Sandrew 	if (hp)
62157963Sandrew 		return hp->h_name;
62257963Sandrew 	else
62357963Sandrew 		return inet_ntoa(fromp->sin_addr);
62457963Sandrew }
625