xref: /netbsd-src/libexec/tftpd/tftpd.c (revision ef0b7ea3b6e46a6be9bff83bf463a6b11ab939de)
1*ef0b7ea3Sshm /*	$NetBSD: tftpd.c,v 1.45 2016/07/20 20:18:21 shm Exp $	*/
260cef501Smrg 
361f28255Scgd /*
460cef501Smrg  * Copyright (c) 1983, 1993
560cef501Smrg  *	The Regents of the University of California.  All rights reserved.
661f28255Scgd  *
761f28255Scgd  * Redistribution and use in source and binary forms, with or without
861f28255Scgd  * modification, are permitted provided that the following conditions
961f28255Scgd  * are met:
1061f28255Scgd  * 1. Redistributions of source code must retain the above copyright
1161f28255Scgd  *    notice, this list of conditions and the following disclaimer.
1261f28255Scgd  * 2. Redistributions in binary form must reproduce the above copyright
1361f28255Scgd  *    notice, this list of conditions and the following disclaimer in the
1461f28255Scgd  *    documentation and/or other materials provided with the distribution.
158e6ab883Sagc  * 3. Neither the name of the University nor the names of its contributors
1661f28255Scgd  *    may be used to endorse or promote products derived from this software
1761f28255Scgd  *    without specific prior written permission.
1861f28255Scgd  *
1961f28255Scgd  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2061f28255Scgd  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2161f28255Scgd  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2261f28255Scgd  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2361f28255Scgd  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2461f28255Scgd  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2561f28255Scgd  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2661f28255Scgd  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2761f28255Scgd  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2861f28255Scgd  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2961f28255Scgd  * SUCH DAMAGE.
3061f28255Scgd  */
3161f28255Scgd 
3260cef501Smrg #include <sys/cdefs.h>
3361f28255Scgd #ifndef lint
34f0bccc0fSlukem __COPYRIGHT("@(#) Copyright (c) 1983, 1993\
35f0bccc0fSlukem  The Regents of the University of California.  All rights reserved.");
3660cef501Smrg #if 0
3760cef501Smrg static char sccsid[] = "@(#)tftpd.c	8.1 (Berkeley) 6/4/93";
3860cef501Smrg #else
39*ef0b7ea3Sshm __RCSID("$NetBSD: tftpd.c,v 1.45 2016/07/20 20:18:21 shm Exp $");
4060cef501Smrg #endif
4161f28255Scgd #endif /* not lint */
4261f28255Scgd 
4361f28255Scgd /*
4461f28255Scgd  * Trivial file transfer protocol server.
4561f28255Scgd  *
4660cef501Smrg  * This version includes many modifications by Jim Guyton
4760cef501Smrg  * <guyton@rand-unix>.
4861f28255Scgd  */
4961f28255Scgd 
5060cef501Smrg #include <sys/param.h>
5161f28255Scgd #include <sys/ioctl.h>
5261f28255Scgd #include <sys/stat.h>
5360cef501Smrg #include <sys/socket.h>
5460cef501Smrg 
5561f28255Scgd #include <netinet/in.h>
5661f28255Scgd #include <arpa/tftp.h>
5760cef501Smrg #include <arpa/inet.h>
5861f28255Scgd 
5961f28255Scgd #include <ctype.h>
6060cef501Smrg #include <errno.h>
6160cef501Smrg #include <fcntl.h>
6215b3c2bfSlukem #include <grp.h>
6360cef501Smrg #include <netdb.h>
6415b3c2bfSlukem #include <pwd.h>
6560cef501Smrg #include <setjmp.h>
6660cef501Smrg #include <signal.h>
6760cef501Smrg #include <stdio.h>
6861f28255Scgd #include <stdlib.h>
6960cef501Smrg #include <string.h>
7060cef501Smrg #include <syslog.h>
71a87145f3Skleink #include <time.h>
7260cef501Smrg #include <unistd.h>
7360cef501Smrg 
7460cef501Smrg #include "tftpsubs.h"
7561f28255Scgd 
7615b3c2bfSlukem #define	DEFAULTUSER	"nobody"
77ad6a2b10Sjtc 
7861f28255Scgd #define	TIMEOUT		5
7961f28255Scgd 
8073a2253aSchristos static int	peer;
8173a2253aSchristos static int	rexmtval = TIMEOUT;
8273a2253aSchristos static int	maxtimeout = 5*TIMEOUT;
8361f28255Scgd 
8473a2253aSchristos static char	buf[MAXPKTSIZE];
8573a2253aSchristos static char	ackbuf[PKTSIZE];
8673a2253aSchristos static char	oackbuf[PKTSIZE];
8773a2253aSchristos static struct	sockaddr_storage from;
8873a2253aSchristos static socklen_t	fromlen;
8973a2253aSchristos static int	debug;
9061f28255Scgd 
9173a2253aSchristos static int	tftp_opt_tsize = 0;
9273a2253aSchristos static int	tftp_blksize = SEGSIZE;
9373a2253aSchristos static int	tftp_tsize = 0;
9444411286Sbriggs 
9560cef501Smrg /*
9660cef501Smrg  * Null-terminated directory prefix list for absolute pathname requests and
9760cef501Smrg  * search list for relative pathname requests.
9860cef501Smrg  *
9960cef501Smrg  * MAXDIRS should be at least as large as the number of arguments that
10060cef501Smrg  * inetd allows (currently 20).
10160cef501Smrg  */
10260cef501Smrg #define MAXDIRS	20
10360cef501Smrg static struct dirlist {
10460cef501Smrg 	char	*name;
10560cef501Smrg 	int	len;
10660cef501Smrg } dirs[MAXDIRS+1];
10760cef501Smrg static int	suppress_naks;
10860cef501Smrg static int	logging;
10960cef501Smrg static int	secure;
11073a2253aSchristos static char	pathsep = '\0';
11160cef501Smrg static char	*securedir;
112cb578cacShubertf static int	unrestricted_writes;    /* uploaded files don't have to exist */
1133a2e9669Sbuhrow static int	broadcast_client = 0; /* Some clients ack to the broadcast address */
11461f28255Scgd 
11560cef501Smrg struct formats;
11660cef501Smrg 
117edc4b408Slukem static const char *errtomsg(int);
118edc4b408Slukem static void	nak(int);
1199eba1e42Sjoerg __dead static void	tftp(struct tftphdr *, int);
1209eba1e42Sjoerg __dead static void	usage(void);
121edc4b408Slukem static char	*verifyhost(struct sockaddr *);
1229eba1e42Sjoerg __dead static void	justquit(int);
12373a2253aSchristos static void	recvfile(struct formats *, int, int);
12473a2253aSchristos static void	sendfile(struct formats *, int, int);
1259eba1e42Sjoerg __dead static void	timer(int);
126d8c9b346Schristos static const char *opcode(int);
12773a2253aSchristos static int	validate_access(char **, int);
12860cef501Smrg 
12973a2253aSchristos static struct formats {
130edc4b408Slukem 	const char	*f_mode;
131edc4b408Slukem 	int		(*f_validate)(char **, int);
13244411286Sbriggs 	void		(*f_send)(struct formats *, int, int);
13344411286Sbriggs 	void		(*f_recv)(struct formats *, int, int);
13460cef501Smrg 	int		f_convert;
13560cef501Smrg } formats[] = {
13660cef501Smrg 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
13760cef501Smrg 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
13873a2253aSchristos 	{ .f_mode = NULL }
13960cef501Smrg };
14045fe1558Smycroft 
1419fd1fef0Scgd static void
usage(void)142edc4b408Slukem usage(void)
1439fd1fef0Scgd {
144edc4b408Slukem 
14515b3c2bfSlukem 	syslog(LOG_ERR,
1463a2e9669Sbuhrow     "Usage: %s [-bcdln] [-g group] [-p pathsep] [-s directory] [-u user] [directory ...]",
14765a10264Scgd 		    getprogname());
1489fd1fef0Scgd 	exit(1);
1499fd1fef0Scgd }
1509fd1fef0Scgd 
15160cef501Smrg int
main(int argc,char * argv[])152edc4b408Slukem main(int argc, char *argv[])
15361f28255Scgd {
154edc4b408Slukem 	struct sockaddr_storage me;
15515b3c2bfSlukem 	struct passwd	*pwent;
15615b3c2bfSlukem 	struct group	*grent;
15715b3c2bfSlukem 	struct tftphdr	*tp;
15873a2253aSchristos 	const char	*tgtuser, *tgtgroup;
15973a2253aSchristos 	char *ep;
160edc4b408Slukem 	int	n, ch, on, fd;
1610c37c63eSmrg 	int	soopt;
1620c37c63eSmrg 	socklen_t len;
16315b3c2bfSlukem 	uid_t	curuid, tgtuid;
16415b3c2bfSlukem 	gid_t	curgid, tgtgid;
16515b3c2bfSlukem 	long	nid;
16661f28255Scgd 
167edc4b408Slukem 	n = 0;
168edc4b408Slukem 	fd = 0;
169a87145f3Skleink 	tzset();
17052e679a1Slukem 	openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
17115b3c2bfSlukem 	tgtuser = DEFAULTUSER;
17215b3c2bfSlukem 	tgtgroup = NULL;
17315b3c2bfSlukem 	curuid = getuid();
17415b3c2bfSlukem 	curgid = getgid();
1759fd1fef0Scgd 
1763a2e9669Sbuhrow 	while ((ch = getopt(argc, argv, "bcdg:lnp:s:u:")) != -1)
17760cef501Smrg 		switch (ch) {
1783a2e9669Sbuhrow 		case 'b':
1793a2e9669Sbuhrow 			/*
1803a2e9669Sbuhrow 			 * Some clients, notably older Cisco boot loaders,
1813a2e9669Sbuhrow 			 * send their acknowledgements to the broadcast address
1823a2e9669Sbuhrow 			 * rather than the unicast address of the server.
1833a2e9669Sbuhrow 			 * Allow those clients to inter-operate with us.
1843a2e9669Sbuhrow 			 * It's worth noting that this interaction doesn't cause the
1853a2e9669Sbuhrow 			 * server to change where it sends the responses, meaning
1863a2e9669Sbuhrow 			 * servers that have this flag enabled are no more
1873a2e9669Sbuhrow 			 * susceptible to magnifcation DOS attacks than those
1883a2e9669Sbuhrow 			 * servers that don't use this flag.  This flag merely
1893a2e9669Sbuhrow 			 * permits the reception of acknowledgement traffic to the
1903a2e9669Sbuhrow 			 * broadcast address/specific port number that's being used for
1913a2e9669Sbuhrow 			 * this session as well as the unicast address/specific port
1923a2e9669Sbuhrow 			 * number for this session.  For example, if the session is
1933a2e9669Sbuhrow 			 * expecting acks on 192.168.1.40:50201, then this flag
1943a2e9669Sbuhrow 			 * would also allow acks to be returned to
1953a2e9669Sbuhrow 			 * 192.168.1.255:50201, assuming that 192.168.1.255 is the
1963a2e9669Sbuhrow 			 * broadcast address for the subnet containing 192.168.1.40.
1973a2e9669Sbuhrow 			 */
1983a2e9669Sbuhrow 			broadcast_client = 1;
1993a2e9669Sbuhrow 			break;
2004862012fSjnemeth 		case 'c':
20181fae527Shubertf 			unrestricted_writes = 1;
20281fae527Shubertf 			break;
20381fae527Shubertf 
204d8c9b346Schristos 		case 'd':
205d8c9b346Schristos 			debug++;
206d8c9b346Schristos 			break;
20715b3c2bfSlukem 
20815b3c2bfSlukem 		case 'g':
20915b3c2bfSlukem 			tgtgroup = optarg;
21015b3c2bfSlukem 			break;
21115b3c2bfSlukem 
21260cef501Smrg 		case 'l':
21360cef501Smrg 			logging = 1;
21460cef501Smrg 			break;
21560cef501Smrg 
21660cef501Smrg 		case 'n':
21760cef501Smrg 			suppress_naks = 1;
21860cef501Smrg 			break;
21960cef501Smrg 
22073a2253aSchristos 		case 'p':
22173a2253aSchristos 			if (optarg[0] == '\0' || optarg[1] != '\0')
22273a2253aSchristos 				usage();
22373a2253aSchristos 			pathsep = optarg[0];
22473a2253aSchristos 			break;
22573a2253aSchristos 
2269fd1fef0Scgd 		case 's':
2279fd1fef0Scgd 			secure = 1;
22860cef501Smrg 			securedir = optarg;
2299fd1fef0Scgd 			break;
2309fd1fef0Scgd 
23115b3c2bfSlukem 		case 'u':
23215b3c2bfSlukem 			tgtuser = optarg;
23315b3c2bfSlukem 			break;
23415b3c2bfSlukem 
2359fd1fef0Scgd 		default:
2369fd1fef0Scgd 			usage();
2379fd1fef0Scgd 			break;
2389fd1fef0Scgd 		}
2399fd1fef0Scgd 
24060cef501Smrg 	if (optind < argc) {
24160cef501Smrg 		struct dirlist *dirp;
24260cef501Smrg 
24360cef501Smrg 		/* Get list of directory prefixes. Skip relative pathnames. */
24460cef501Smrg 		for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS];
24560cef501Smrg 		     optind++) {
24660cef501Smrg 			if (argv[optind][0] == '/') {
24760cef501Smrg 				dirp->name = argv[optind];
24860cef501Smrg 				dirp->len  = strlen(dirp->name);
24960cef501Smrg 				dirp++;
25045fe1558Smycroft 			}
25145fe1558Smycroft 		}
25245fe1558Smycroft 	}
2539fd1fef0Scgd 
25415b3c2bfSlukem 	if (*tgtuser == '\0' || (tgtgroup != NULL && *tgtgroup == '\0'))
25515b3c2bfSlukem 		usage();
25615b3c2bfSlukem 
25715b3c2bfSlukem 	nid = (strtol(tgtuser, &ep, 10));
25815b3c2bfSlukem 	if (*ep == '\0') {
259b877ced6Smbalmer 		if ((uid_t)nid > UID_MAX) {
26015b3c2bfSlukem 			syslog(LOG_ERR, "uid %ld is too large", nid);
26115b3c2bfSlukem 			exit(1);
26215b3c2bfSlukem 		}
26315b3c2bfSlukem 		pwent = getpwuid((uid_t)nid);
26415b3c2bfSlukem 	} else
26515b3c2bfSlukem 		pwent = getpwnam(tgtuser);
26615b3c2bfSlukem 	if (pwent == NULL) {
26715b3c2bfSlukem 		syslog(LOG_ERR, "unknown user `%s'", tgtuser);
26815b3c2bfSlukem 		exit(1);
26915b3c2bfSlukem 	}
27015b3c2bfSlukem 	tgtuid = pwent->pw_uid;
27115b3c2bfSlukem 	tgtgid = pwent->pw_gid;
27215b3c2bfSlukem 
27315b3c2bfSlukem 	if (tgtgroup != NULL) {
27415b3c2bfSlukem 		nid = (strtol(tgtgroup, &ep, 10));
27515b3c2bfSlukem 		if (*ep == '\0') {
276b877ced6Smbalmer 			if ((uid_t)nid > GID_MAX) {
27715b3c2bfSlukem 				syslog(LOG_ERR, "gid %ld is too large", nid);
27815b3c2bfSlukem 				exit(1);
27915b3c2bfSlukem 			}
28015b3c2bfSlukem 			grent = getgrgid((gid_t)nid);
28115b3c2bfSlukem 		} else
28215b3c2bfSlukem 			grent = getgrnam(tgtgroup);
28315b3c2bfSlukem 		if (grent != NULL)
28415b3c2bfSlukem 			tgtgid = grent->gr_gid;
28515b3c2bfSlukem 		else {
28615b3c2bfSlukem 			syslog(LOG_ERR, "unknown group `%s'", tgtgroup);
28715b3c2bfSlukem 			exit(1);
28815b3c2bfSlukem 		}
28915b3c2bfSlukem 	}
29015b3c2bfSlukem 
29160cef501Smrg 	if (secure) {
29260cef501Smrg 		if (chdir(securedir) < 0) {
29360cef501Smrg 			syslog(LOG_ERR, "chdir %s: %m", securedir);
29460cef501Smrg 			exit(1);
29560cef501Smrg 		}
29660cef501Smrg 		if (chroot(".")) {
29715b3c2bfSlukem 			syslog(LOG_ERR, "chroot: %m");
29845fe1558Smycroft 			exit(1);
29945fe1558Smycroft 		}
30060cef501Smrg 	}
3019fd1fef0Scgd 
302edc4b408Slukem 	if (logging)
30315b3c2bfSlukem 		syslog(LOG_DEBUG, "running as user `%s' (%d), group `%s' (%d)",
304edc4b408Slukem 		    tgtuser, tgtuid, tgtgroup ? tgtgroup : "(unspecified)",
305edc4b408Slukem 		    tgtgid);
30615b3c2bfSlukem 	if (curgid != tgtgid) {
30715b3c2bfSlukem 		if (setgid(tgtgid)) {
30815b3c2bfSlukem 			syslog(LOG_ERR, "setgid to %d: %m", (int)tgtgid);
309ad6a2b10Sjtc 			exit(1);
310ad6a2b10Sjtc 		}
3118a6057d6Smrg 		if (setgroups(0, NULL)) {
3128a6057d6Smrg 			syslog(LOG_ERR, "setgroups: %m");
3138a6057d6Smrg 			exit(1);
3148a6057d6Smrg 		}
31515b3c2bfSlukem 	}
3168a6057d6Smrg 
31715b3c2bfSlukem 	if (curuid != tgtuid) {
31815b3c2bfSlukem 		if (setuid(tgtuid)) {
31915b3c2bfSlukem 			syslog(LOG_ERR, "setuid to %d: %m", (int)tgtuid);
320ad6a2b10Sjtc 			exit(1);
321ad6a2b10Sjtc 		}
32215b3c2bfSlukem 	}
323ad6a2b10Sjtc 
32460cef501Smrg 	on = 1;
3259fd1fef0Scgd 	if (ioctl(fd, FIONBIO, &on) < 0) {
32615b3c2bfSlukem 		syslog(LOG_ERR, "ioctl(FIONBIO): %m");
32761f28255Scgd 		exit(1);
32861f28255Scgd 	}
32961f28255Scgd 	fromlen = sizeof (from);
3309fd1fef0Scgd 	n = recvfrom(fd, buf, sizeof (buf), 0,
33161f28255Scgd 	    (struct sockaddr *)&from, &fromlen);
33261f28255Scgd 	if (n < 0) {
33315b3c2bfSlukem 		syslog(LOG_ERR, "recvfrom: %m");
33461f28255Scgd 		exit(1);
33561f28255Scgd 	}
33661f28255Scgd 	/*
33761f28255Scgd 	 * Now that we have read the message out of the UDP
33861f28255Scgd 	 * socket, we fork and exit.  Thus, inetd will go back
33961f28255Scgd 	 * to listening to the tftp port, and the next request
34061f28255Scgd 	 * to come in will start up a new instance of tftpd.
34161f28255Scgd 	 *
34261f28255Scgd 	 * We do this so that inetd can run tftpd in "wait" mode.
34361f28255Scgd 	 * The problem with tftpd running in "nowait" mode is that
34461f28255Scgd 	 * inetd may get one or more successful "selects" on the
34561f28255Scgd 	 * tftp port before we do our receive, so more than one
34661f28255Scgd 	 * instance of tftpd may be started up.  Worse, if tftpd
34761f28255Scgd 	 * break before doing the above "recvfrom", inetd would
34861f28255Scgd 	 * spawn endless instances, clogging the system.
34961f28255Scgd 	 */
35061f28255Scgd 	{
35161f28255Scgd 		int pid;
3520c37c63eSmrg 		int i;
3530c37c63eSmrg 		socklen_t j;
35461f28255Scgd 
35561f28255Scgd 		for (i = 1; i < 20; i++) {
35661f28255Scgd 		    pid = fork();
35761f28255Scgd 		    if (pid < 0) {
35861f28255Scgd 				sleep(i);
35961f28255Scgd 				/*
36061f28255Scgd 				 * flush out to most recently sent request.
36161f28255Scgd 				 *
36261f28255Scgd 				 * This may drop some request, but those
36361f28255Scgd 				 * will be resent by the clients when
36461f28255Scgd 				 * they timeout.  The positive effect of
36561f28255Scgd 				 * this flush is to (try to) prevent more
36661f28255Scgd 				 * than one tftpd being started up to service
36761f28255Scgd 				 * a single request from a single client.
36861f28255Scgd 				 */
36961f28255Scgd 				j = sizeof from;
3709fd1fef0Scgd 				i = recvfrom(fd, buf, sizeof (buf), 0,
37161f28255Scgd 				    (struct sockaddr *)&from, &j);
37261f28255Scgd 				if (i > 0) {
37361f28255Scgd 					n = i;
37461f28255Scgd 					fromlen = j;
37561f28255Scgd 				}
37661f28255Scgd 		    } else {
37761f28255Scgd 				break;
37861f28255Scgd 		    }
37961f28255Scgd 		}
38061f28255Scgd 		if (pid < 0) {
38115b3c2bfSlukem 			syslog(LOG_ERR, "fork: %m");
38261f28255Scgd 			exit(1);
38361f28255Scgd 		} else if (pid != 0) {
38461f28255Scgd 			exit(0);
38561f28255Scgd 		}
38661f28255Scgd 	}
387ed33c873Sexplorer 
388ed33c873Sexplorer 	/*
389ed33c873Sexplorer 	 * remember what address this was sent to, so we can respond on the
390ed33c873Sexplorer 	 * same interface
391ed33c873Sexplorer 	 */
39247b0e5ffSitojun 	len = sizeof(me);
39347b0e5ffSitojun 	if (getsockname(fd, (struct sockaddr *)&me, &len) == 0) {
39447b0e5ffSitojun 		switch (me.ss_family) {
39547b0e5ffSitojun 		case AF_INET:
39647b0e5ffSitojun 			((struct sockaddr_in *)&me)->sin_port = 0;
39747b0e5ffSitojun 			break;
39847b0e5ffSitojun 		case AF_INET6:
39947b0e5ffSitojun 			((struct sockaddr_in6 *)&me)->sin6_port = 0;
40047b0e5ffSitojun 			break;
40147b0e5ffSitojun 		default:
40247b0e5ffSitojun 			/* unsupported */
40347b0e5ffSitojun 			break;
40447b0e5ffSitojun 		}
40547b0e5ffSitojun 	} else {
40647b0e5ffSitojun 		memset(&me, 0, sizeof(me));
40747b0e5ffSitojun 		me.ss_family = from.ss_family;
40847b0e5ffSitojun 		me.ss_len = from.ss_len;
409ed33c873Sexplorer 	}
410ed33c873Sexplorer 
41161f28255Scgd 	alarm(0);
4129fd1fef0Scgd 	close(fd);
41361f28255Scgd 	close(1);
41447b0e5ffSitojun 	peer = socket(from.ss_family, SOCK_DGRAM, 0);
41561f28255Scgd 	if (peer < 0) {
41615b3c2bfSlukem 		syslog(LOG_ERR, "socket: %m");
41761f28255Scgd 		exit(1);
41861f28255Scgd 	}
4193a2e9669Sbuhrow 	if (broadcast_client) {
4203a2e9669Sbuhrow 		soopt = 1;
4213a2e9669Sbuhrow 		if (setsockopt(peer, SOL_SOCKET, SO_BROADCAST, (void *) &soopt, sizeof(soopt)) < 0) {
4223a2e9669Sbuhrow 			syslog(LOG_ERR, "set SO_BROADCAST: %m");
42361f28255Scgd 			exit(1);
42461f28255Scgd 		}
4253a2e9669Sbuhrow 	}
4263a2e9669Sbuhrow 	if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) {
4273a2e9669Sbuhrow 		syslog(LOG_ERR, "bind: %m");
42861f28255Scgd 		exit(1);
42961f28255Scgd 	}
43044411286Sbriggs 	soopt = 65536;	/* larger than we'll ever need */
43144411286Sbriggs 	if (setsockopt(peer, SOL_SOCKET, SO_SNDBUF, (void *) &soopt, sizeof(soopt)) < 0) {
43244411286Sbriggs 		syslog(LOG_ERR, "set SNDBUF: %m");
43344411286Sbriggs 		exit(1);
43444411286Sbriggs 	}
43544411286Sbriggs 	if (setsockopt(peer, SOL_SOCKET, SO_RCVBUF, (void *) &soopt, sizeof(soopt)) < 0) {
43644411286Sbriggs 		syslog(LOG_ERR, "set RCVBUF: %m");
43744411286Sbriggs 		exit(1);
43844411286Sbriggs 	}
43944411286Sbriggs 
44061f28255Scgd 	tp = (struct tftphdr *)buf;
44161f28255Scgd 	tp->th_opcode = ntohs(tp->th_opcode);
44261f28255Scgd 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
44361f28255Scgd 		tftp(tp, n);
44461f28255Scgd 	exit(1);
44561f28255Scgd }
44661f28255Scgd 
44744411286Sbriggs static int
blk_handler(struct tftphdr * tp,const char * val,char * ack,size_t asize,size_t * ackl,int * ecode)4487807c800Schristos blk_handler(struct tftphdr *tp, const char *val, char *ack, size_t asize,
4491c129848Schristos     size_t *ackl, int *ecode)
45044411286Sbriggs {
45144411286Sbriggs 	unsigned long bsize;
45244411286Sbriggs 	char *endp;
45344411286Sbriggs 	int l;
45444411286Sbriggs 
45544411286Sbriggs 	/*
45644411286Sbriggs 	 * On these failures, we could just ignore the blocksize option.
45744411286Sbriggs 	 * Perhaps that should be a command-line option.
45844411286Sbriggs 	 */
45944411286Sbriggs 	errno = 0;
46044411286Sbriggs 	bsize = strtoul(val, &endp, 10);
46144411286Sbriggs 	if ((bsize == ULONG_MAX && errno == ERANGE) || *endp) {
46244411286Sbriggs 		syslog(LOG_NOTICE, "%s: %s request for %s: "
46344411286Sbriggs 			"illegal value %s for blksize option",
46444411286Sbriggs 			verifyhost((struct sockaddr *)&from),
46544411286Sbriggs 			tp->th_opcode == WRQ ? "write" : "read",
46644411286Sbriggs 			tp->th_stuff, val);
4671c129848Schristos 		*ecode = EBADOP;
4681c129848Schristos 		return -1;
46944411286Sbriggs 	}
47044411286Sbriggs 	if (bsize < 8 || bsize > 65464) {
47144411286Sbriggs 		syslog(LOG_NOTICE, "%s: %s request for %s: "
47244411286Sbriggs 			"out of range value %s for blksize option",
47344411286Sbriggs 			verifyhost((struct sockaddr *)&from),
47444411286Sbriggs 			tp->th_opcode == WRQ ? "write" : "read",
47544411286Sbriggs 			tp->th_stuff, val);
4761c129848Schristos 		*ecode = EBADOP;
4771c129848Schristos 		return -1;
47844411286Sbriggs 	}
47944411286Sbriggs 
48044411286Sbriggs 	tftp_blksize = bsize;
4817807c800Schristos 	if (asize > *ackl && (l = snprintf(ack + *ackl, asize - *ackl,
4821c129848Schristos 	    "blksize%c%lu%c", 0, bsize, 0)) > 0) {
4837807c800Schristos 		*ackl += l;
4841c129848Schristos 	} else {
4851c129848Schristos 		*ecode = EBADOP;
4867807c800Schristos 		return -1;
4871c129848Schristos 	}
48844411286Sbriggs 
48944411286Sbriggs 	return 0;
49044411286Sbriggs }
49144411286Sbriggs 
49244411286Sbriggs static int
timeout_handler(struct tftphdr * tp,const char * val,char * ack,size_t asize,size_t * ackl,int * ecode)4937807c800Schristos timeout_handler(struct tftphdr *tp, const char *val, char *ack, size_t asize,
4941c129848Schristos 		size_t *ackl, int *ecode)
49544411286Sbriggs {
49644411286Sbriggs 	unsigned long tout;
49744411286Sbriggs 	char *endp;
49844411286Sbriggs 	int l;
49944411286Sbriggs 
50044411286Sbriggs 	errno = 0;
50144411286Sbriggs 	tout = strtoul(val, &endp, 10);
50244411286Sbriggs 	if ((tout == ULONG_MAX && errno == ERANGE) || *endp) {
50344411286Sbriggs 		syslog(LOG_NOTICE, "%s: %s request for %s: "
50444411286Sbriggs 			"illegal value %s for timeout option",
50544411286Sbriggs 			verifyhost((struct sockaddr *)&from),
50644411286Sbriggs 			tp->th_opcode == WRQ ? "write" : "read",
50744411286Sbriggs 			tp->th_stuff, val);
5081c129848Schristos 		*ecode = EBADOP;
5091c129848Schristos 		return -1;
51044411286Sbriggs 	}
51144411286Sbriggs 	if (tout < 1 || tout > 255) {
51244411286Sbriggs 		syslog(LOG_NOTICE, "%s: %s request for %s: "
51344411286Sbriggs 			"out of range value %s for timeout option",
51444411286Sbriggs 			verifyhost((struct sockaddr *)&from),
51544411286Sbriggs 			tp->th_opcode == WRQ ? "write" : "read",
51644411286Sbriggs 			tp->th_stuff, val);
51744411286Sbriggs 		return 0;
51844411286Sbriggs 	}
51944411286Sbriggs 
52044411286Sbriggs 	rexmtval = tout;
5217807c800Schristos 	if (asize > *ackl && (l = snprintf(ack + *ackl, asize - *ackl,
52259b4948aSchristos 	    "timeout%c%lu%c", 0, tout, 0)) > 0)
5237807c800Schristos 		*ackl += l;
5247807c800Schristos 	else
5257807c800Schristos 		return -1;
52644411286Sbriggs 	/*
52744411286Sbriggs 	 * Arbitrarily pick a maximum timeout on a request to 3
52844411286Sbriggs 	 * retransmissions if the interval timeout is more than
52944411286Sbriggs 	 * one minute.  Longest possible timeout is therefore
53044411286Sbriggs 	 * 3 * 255 - 1, or 764 seconds.
53144411286Sbriggs 	 */
53244411286Sbriggs 	if (rexmtval > 60) {
53344411286Sbriggs 		maxtimeout = rexmtval * 3;
53444411286Sbriggs 	} else {
53544411286Sbriggs 		maxtimeout = rexmtval * 5;
53644411286Sbriggs 	}
53744411286Sbriggs 
53844411286Sbriggs 	return 0;
53944411286Sbriggs }
54044411286Sbriggs 
54144411286Sbriggs static int
tsize_handler(struct tftphdr * tp,const char * val,char * ack,size_t asize,size_t * ackl,int * ecode)5427807c800Schristos tsize_handler(struct tftphdr *tp, const char *val, char *ack, size_t asize,
5431c129848Schristos     size_t *ackl, int *ecode)
54444411286Sbriggs {
54544411286Sbriggs 	unsigned long fsize;
54644411286Sbriggs 	char *endp;
54744411286Sbriggs 
54844411286Sbriggs 	/*
54944411286Sbriggs 	 * Maximum file even with extended tftp is 65535 blocks of
55044411286Sbriggs 	 * length 65464, or 4290183240 octets (4784056 less than 2^32).
55144411286Sbriggs 	 * unsigned long is at least 32 bits on all NetBSD archs.
55244411286Sbriggs 	 */
55344411286Sbriggs 
55444411286Sbriggs 	errno = 0;
55544411286Sbriggs 	fsize = strtoul(val, &endp, 10);
55644411286Sbriggs 	if ((fsize == ULONG_MAX && errno == ERANGE) || *endp) {
55744411286Sbriggs 		syslog(LOG_NOTICE, "%s: %s request for %s: "
55844411286Sbriggs 			"illegal value %s for tsize option",
55944411286Sbriggs 			verifyhost((struct sockaddr *)&from),
56044411286Sbriggs 			tp->th_opcode == WRQ ? "write" : "read",
56144411286Sbriggs 			tp->th_stuff, val);
5621c129848Schristos 		*ecode = EBADOP;
5631c129848Schristos 		return -1;
56444411286Sbriggs 	}
56544411286Sbriggs 	if (fsize > (unsigned long) 65535 * 65464) {
56644411286Sbriggs 		syslog(LOG_NOTICE, "%s: %s request for %s: "
56744411286Sbriggs 			"out of range value %s for tsize option",
56844411286Sbriggs 			verifyhost((struct sockaddr *)&from),
56944411286Sbriggs 			tp->th_opcode == WRQ ? "write" : "read",
57044411286Sbriggs 			tp->th_stuff, val);
5711c129848Schristos 		*ecode = EBADOP;
5721c129848Schristos 		return -1;
57344411286Sbriggs 	}
57444411286Sbriggs 
57544411286Sbriggs 	tftp_opt_tsize = 1;
57644411286Sbriggs 	tftp_tsize = fsize;
57744411286Sbriggs 	/*
57844411286Sbriggs 	 * We will report this later -- either replying with the fsize (WRQ)
57944411286Sbriggs 	 * or replying with the actual filesize (RRQ).
58044411286Sbriggs 	 */
58144411286Sbriggs 
58244411286Sbriggs 	return 0;
58344411286Sbriggs }
58444411286Sbriggs 
58573a2253aSchristos static const struct tftp_options {
58673a2253aSchristos 	const char *o_name;
5877807c800Schristos 	int (*o_handler)(struct tftphdr *, const char *, char *, size_t,
5887807c800Schristos 			 size_t *, int *);
58944411286Sbriggs } options[] = {
59044411286Sbriggs 	{ "blksize", blk_handler },
59144411286Sbriggs 	{ "timeout", timeout_handler },
59244411286Sbriggs 	{ "tsize", tsize_handler },
59373a2253aSchristos 	{ .o_name = NULL }
59444411286Sbriggs };
59544411286Sbriggs 
59644411286Sbriggs /*
59744411286Sbriggs  * Get options for an extended tftp session.  Stuff the ones we
59844411286Sbriggs  * recognize in oackbuf.
59944411286Sbriggs  */
60044411286Sbriggs static int
get_options(struct tftphdr * tp,char * cp,int size,char * ackb,size_t asize,size_t * alen,int * ecode)6017807c800Schristos get_options(struct tftphdr *tp, char *cp, int size, char *ackb, size_t asize,
6021c129848Schristos     size_t *alen, int *ecode)
60344411286Sbriggs {
60473a2253aSchristos 	const struct tftp_options *op;
60544411286Sbriggs 	char *option, *value, *endp;
6061c129848Schristos 	int r, rv=0;
60744411286Sbriggs 
60844411286Sbriggs 	endp = cp + size;
60944411286Sbriggs 	while (cp < endp) {
61044411286Sbriggs 		option = cp;
61144411286Sbriggs 		while (*cp && cp < endp) {
6121869f0e1Sdsl 			*cp = tolower((unsigned char)*cp);
61344411286Sbriggs 			cp++;
61444411286Sbriggs 		}
61544411286Sbriggs 		if (*cp) {
61644411286Sbriggs 			/* if we have garbage at the end, just ignore it */
61744411286Sbriggs 			break;
61844411286Sbriggs 		}
61944411286Sbriggs 		cp++;	/* skip over NUL */
62044411286Sbriggs 		value = cp;
62144411286Sbriggs 		while (*cp && cp < endp) {
62244411286Sbriggs 			cp++;
62344411286Sbriggs 		}
62444411286Sbriggs 		if (*cp) {
62544411286Sbriggs 			/* if we have garbage at the end, just ignore it */
62644411286Sbriggs 			break;
62744411286Sbriggs 		}
62844411286Sbriggs 		cp++;
62944411286Sbriggs 		for (op = options; op->o_name; op++) {
63044411286Sbriggs 			if (strcmp(op->o_name, option) == 0)
63144411286Sbriggs 				break;
63244411286Sbriggs 		}
63344411286Sbriggs 		if (op->o_name) {
6341c129848Schristos 			r = op->o_handler(tp, value, ackb, asize, alen, ecode);
63544411286Sbriggs 			if (r < 0) {
63644411286Sbriggs 				rv = -1;
63744411286Sbriggs 				break;
63844411286Sbriggs 			}
63944411286Sbriggs 			rv++;
64044411286Sbriggs 		} /* else ignore unknown options */
64144411286Sbriggs 	}
64244411286Sbriggs 
64344411286Sbriggs 	return rv;
64444411286Sbriggs }
64544411286Sbriggs 
64661f28255Scgd /*
64761f28255Scgd  * Handle initial connection protocol.
64861f28255Scgd  */
64960cef501Smrg static void
tftp(struct tftphdr * tp,int size)650edc4b408Slukem tftp(struct tftphdr *tp, int size)
65161f28255Scgd {
65215b3c2bfSlukem 	struct formats *pf;
653edc4b408Slukem 	char	*cp;
654edc4b408Slukem 	char	*filename, *mode;
6557807c800Schristos 	int	 first, ecode, etftp = 0, r;
6567807c800Schristos 	size_t alen;
657edc4b408Slukem 
6580c37c63eSmrg 	ecode = 0;	/* XXX gcc */
659edc4b408Slukem 	first = 1;
660edc4b408Slukem 	mode = NULL;
66161f28255Scgd 
66261f28255Scgd 	filename = cp = tp->th_stuff;
66361f28255Scgd again:
66461f28255Scgd 	while (cp < buf + size) {
66561f28255Scgd 		if (*cp == '\0')
66661f28255Scgd 			break;
66761f28255Scgd 		cp++;
66861f28255Scgd 	}
66961f28255Scgd 	if (*cp != '\0') {
67061f28255Scgd 		nak(EBADOP);
67161f28255Scgd 		exit(1);
67261f28255Scgd 	}
67361f28255Scgd 	if (first) {
67461f28255Scgd 		mode = ++cp;
67561f28255Scgd 		first = 0;
67661f28255Scgd 		goto again;
67761f28255Scgd 	}
67861f28255Scgd 	for (cp = mode; *cp; cp++)
6791869f0e1Sdsl 		*cp = tolower((unsigned char)*cp);
68061f28255Scgd 	for (pf = formats; pf->f_mode; pf++)
68161f28255Scgd 		if (strcmp(pf->f_mode, mode) == 0)
68261f28255Scgd 			break;
68361f28255Scgd 	if (pf->f_mode == 0) {
68461f28255Scgd 		nak(EBADOP);
68561f28255Scgd 		exit(1);
68661f28255Scgd 	}
68744411286Sbriggs 	/*
68844411286Sbriggs 	 * cp currently points to the NUL byte following the mode.
68944411286Sbriggs 	 *
69044411286Sbriggs 	 * If we have some valid options, then let's assume that we're
69144411286Sbriggs 	 * now dealing with an extended tftp session.  Note that if we
69244411286Sbriggs 	 * don't get any options, then we *must* assume that we do not
69344411286Sbriggs 	 * have an extended tftp session.  If we get options, we fill
69444411286Sbriggs 	 * in the ack buf to acknowledge them.  If we skip that, then
69544411286Sbriggs 	 * the client *must* assume that we are not using an extended
69644411286Sbriggs 	 * session.
69744411286Sbriggs 	 */
69844411286Sbriggs 	size -= (++cp - (char *) tp);
69944411286Sbriggs 	if (size > 0 && *cp) {
70044411286Sbriggs 		alen = 2; /* Skip over opcode */
7017807c800Schristos 		r = get_options(tp, cp, size, oackbuf, sizeof(oackbuf),
7027807c800Schristos 		    &alen, &ecode);
70344411286Sbriggs 		if (r > 0) {
70444411286Sbriggs 			etftp = 1;
70544411286Sbriggs 		} else if (r < 0) {
70644411286Sbriggs 			nak(ecode);
70744411286Sbriggs 			exit(1);
70844411286Sbriggs 		}
70944411286Sbriggs 	}
71073a2253aSchristos 	/*
71173a2253aSchristos 	 * Globally replace the path separator given in the -p option
71273a2253aSchristos 	 * with / to cope with clients expecting a non-unix path separator.
71373a2253aSchristos 	 */
71473a2253aSchristos 	if (pathsep != '\0') {
71573a2253aSchristos 		for (cp = filename; *cp != '\0'; ++cp) {
71673a2253aSchristos 			if (*cp == pathsep)
71773a2253aSchristos 				*cp = '/';
71873a2253aSchristos 		}
71973a2253aSchristos 	}
72060cef501Smrg 	ecode = (*pf->f_validate)(&filename, tp->th_opcode);
72160cef501Smrg 	if (logging) {
72260cef501Smrg 		syslog(LOG_INFO, "%s: %s request for %s: %s",
72347b0e5ffSitojun 			verifyhost((struct sockaddr *)&from),
72460cef501Smrg 			tp->th_opcode == WRQ ? "write" : "read",
72560cef501Smrg 			filename, errtomsg(ecode));
72660cef501Smrg 	}
72761f28255Scgd 	if (ecode) {
72860cef501Smrg 		/*
72960cef501Smrg 		 * Avoid storms of naks to a RRQ broadcast for a relative
73060cef501Smrg 		 * bootfile pathname from a diskless Sun.
73160cef501Smrg 		 */
73260cef501Smrg 		if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
73360cef501Smrg 			exit(0);
73461f28255Scgd 		nak(ecode);
73561f28255Scgd 		exit(1);
73661f28255Scgd 	}
73744411286Sbriggs 
73844411286Sbriggs 	if (etftp) {
73944411286Sbriggs 		struct tftphdr *oack_h;
74044411286Sbriggs 
74144411286Sbriggs 		if (tftp_opt_tsize) {
74244411286Sbriggs 			int l;
74344411286Sbriggs 
7447807c800Schristos 			if (sizeof(oackbuf) > alen &&
7457807c800Schristos 			    (l = snprintf(oackbuf + alen,
7467807c800Schristos 			    sizeof(oackbuf) - alen, "tsize%c%u%c", 0,
74759b4948aSchristos 			    tftp_tsize, 0)) > 0)
7487807c800Schristos 				alen += l;
74944411286Sbriggs 		}
75044411286Sbriggs 		oack_h = (struct tftphdr *) oackbuf;
75144411286Sbriggs 		oack_h->th_opcode = htons(OACK);
75244411286Sbriggs 	}
75344411286Sbriggs 
75461f28255Scgd 	if (tp->th_opcode == WRQ)
75544411286Sbriggs 		(*pf->f_recv)(pf, etftp, alen);
75661f28255Scgd 	else
75744411286Sbriggs 		(*pf->f_send)(pf, etftp, alen);
75861f28255Scgd 	exit(0);
75961f28255Scgd }
76061f28255Scgd 
76161f28255Scgd 
76261f28255Scgd FILE *file;
76361f28255Scgd 
76461f28255Scgd /*
76561f28255Scgd  * Validate file access.  Since we
76661f28255Scgd  * have no uid or gid, for now require
76761f28255Scgd  * file to exist and be publicly
76861f28255Scgd  * readable/writable.
76961f28255Scgd  * If we were invoked with arguments
77061f28255Scgd  * from inetd then the file must also be
77161f28255Scgd  * in one of the given directory prefixes.
77261f28255Scgd  */
77360cef501Smrg int
validate_access(char ** filep,int mode)774edc4b408Slukem validate_access(char **filep, int mode)
77561f28255Scgd {
77661f28255Scgd 	struct stat	 stbuf;
77760cef501Smrg 	struct dirlist	*dirp;
77860cef501Smrg 	static char	 pathname[MAXPATHLEN];
779edc4b408Slukem 	char		*filename;
780edc4b408Slukem 	int		 fd;
781cb578cacShubertf 	int		 create = 0;
782cb578cacShubertf 	int		 trunc = 0;
783edc4b408Slukem 
784edc4b408Slukem 	filename = *filep;
78561f28255Scgd 
78661f28255Scgd 	/*
78760cef501Smrg 	 * Prevent tricksters from getting around the directory restrictions
78861f28255Scgd 	 */
78960cef501Smrg 	if (strstr(filename, "/../"))
79061f28255Scgd 		return (EACCESS);
79160cef501Smrg 
79260cef501Smrg 	if (*filename == '/') {
79360cef501Smrg 		/*
79460cef501Smrg 		 * Allow the request if it's in one of the approved locations.
79560cef501Smrg 		 * Special case: check the null prefix ("/") by looking
79660cef501Smrg 		 * for length = 1 and relying on the arg. processing that
79760cef501Smrg 		 * it's a /.
79860cef501Smrg 		 */
79960cef501Smrg 		for (dirp = dirs; dirp->name != NULL; dirp++) {
80060cef501Smrg 			if (dirp->len == 1 ||
80160cef501Smrg 			    (!strncmp(filename, dirp->name, dirp->len) &&
80260cef501Smrg 			     filename[dirp->len] == '/'))
80361f28255Scgd 				    break;
80445fe1558Smycroft 		}
80560cef501Smrg 		/* If directory list is empty, allow access to any file */
80660cef501Smrg 		if (dirp->name == NULL && dirp != dirs)
80760cef501Smrg 			return (EACCESS);
80861f28255Scgd 		if (stat(filename, &stbuf) < 0)
80961f28255Scgd 			return (errno == ENOENT ? ENOTFOUND : EACCESS);
810b247da14Smycroft 		if (!S_ISREG(stbuf.st_mode))
81160cef501Smrg 			return (ENOTFOUND);
81261f28255Scgd 		if (mode == RRQ) {
81360cef501Smrg 			if ((stbuf.st_mode & S_IROTH) == 0)
81461f28255Scgd 				return (EACCESS);
81561f28255Scgd 		} else {
81660cef501Smrg 			if ((stbuf.st_mode & S_IWOTH) == 0)
81761f28255Scgd 				return (EACCESS);
81861f28255Scgd 		}
81960cef501Smrg 	} else {
82060cef501Smrg 		/*
82160cef501Smrg 		 * Relative file name: search the approved locations for it.
82260cef501Smrg 		 */
82360cef501Smrg 
8249935ad76Saidan 		if (!strncmp(filename, "../", 3))
82560cef501Smrg 			return (EACCESS);
82660cef501Smrg 
82760cef501Smrg 		/*
8289935ad76Saidan 		 * Find the first file that exists in any of the directories,
8299935ad76Saidan 		 * check access on it.
83060cef501Smrg 		 */
83160cef501Smrg 		if (dirs[0].name != NULL) {
83260cef501Smrg 			for (dirp = dirs; dirp->name != NULL; dirp++) {
83360cef501Smrg 				snprintf(pathname, sizeof pathname, "%s/%s",
83460cef501Smrg 				    dirp->name, filename);
83560cef501Smrg 				if (stat(pathname, &stbuf) == 0 &&
83660cef501Smrg 				    (stbuf.st_mode & S_IFMT) == S_IFREG) {
83760cef501Smrg 					break;
83860cef501Smrg 				}
83960cef501Smrg 			}
84060cef501Smrg 			if (dirp->name == NULL)
8419935ad76Saidan 				return (ENOTFOUND);
8429935ad76Saidan 			if (mode == RRQ && !(stbuf.st_mode & S_IROTH))
8439935ad76Saidan 				return (EACCESS);
8449935ad76Saidan 			if (mode == WRQ && !(stbuf.st_mode & S_IWOTH))
8459935ad76Saidan 				return (EACCESS);
84660cef501Smrg 			*filep = filename = pathname;
8479935ad76Saidan 		} else {
848cb578cacShubertf 			int stat_rc;
849cb578cacShubertf 
8509935ad76Saidan 			/*
8519935ad76Saidan 			 * If there's no directory list, take our cue from the
8529935ad76Saidan 			 * absolute file request check above (*filename == '/'),
8539935ad76Saidan 			 * and allow access to anything.
8549935ad76Saidan 			 */
855cb578cacShubertf 			stat_rc = stat(filename, &stbuf);
856cb578cacShubertf 			if (mode == RRQ) {
857cb578cacShubertf 				/* Read request */
858cb578cacShubertf 				if (stat_rc < 0)
8599935ad76Saidan 				       return (errno == ENOENT ? ENOTFOUND : EACCESS);
8609935ad76Saidan 				if (!S_ISREG(stbuf.st_mode))
8619935ad76Saidan 				       return (ENOTFOUND);
8629935ad76Saidan 				if ((stbuf.st_mode & S_IROTH) == 0)
8639935ad76Saidan 					return (EACCESS);
8649935ad76Saidan 			} else {
865cb578cacShubertf 				if (stat_rc < 0) {
866cb578cacShubertf 				       /* Can't stat */
867cb578cacShubertf 				       if (errno == EACCES) {
868cb578cacShubertf 					       /* Permission denied */
869cb578cacShubertf 					       return EACCESS;
870cb578cacShubertf 				       } else {
871cb578cacShubertf 					       /* Not there */
872cb578cacShubertf 					       if (unrestricted_writes) {
873cb578cacShubertf 						       /* need to creat new file! */
874cb578cacShubertf 						       create = O_CREAT;
875cb578cacShubertf 					       } else {
876cb578cacShubertf 						       /* Permission denied */
877cb578cacShubertf 						       return EACCESS;
878cb578cacShubertf 					       }
879cb578cacShubertf 				       }
880cb578cacShubertf 				} else {
881cb578cacShubertf 				       /* Can stat */
882cb578cacShubertf 				       if ((stbuf.st_mode & S_IWOTH) == 0) {
8839935ad76Saidan 					       return (EACCESS);
8849935ad76Saidan 				       }
885cb578cacShubertf 				       trunc = O_TRUNC;
886cb578cacShubertf 				}
887cb578cacShubertf 			}
88860cef501Smrg 			*filep = filename;
88960cef501Smrg 		}
8909935ad76Saidan 	}
89144411286Sbriggs 
89244411286Sbriggs 	if (tftp_opt_tsize && mode == RRQ)
89344411286Sbriggs 		tftp_tsize = (unsigned long) stbuf.st_size;
89444411286Sbriggs 
895cb578cacShubertf 	fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY | trunc | create,
896cb578cacShubertf 			0644); /* debatable */
89761f28255Scgd 	if (fd < 0)
89861f28255Scgd 		return (errno + 100);
89961f28255Scgd 	file = fdopen(fd, (mode == RRQ)? "r":"w");
90061f28255Scgd 	if (file == NULL) {
9019935ad76Saidan 		close(fd);
902edc4b408Slukem 		return (errno + 100);
90361f28255Scgd 	}
90461f28255Scgd 	return (0);
90561f28255Scgd }
90661f28255Scgd 
90773a2253aSchristos static int	timeout;
90873a2253aSchristos static jmp_buf	timeoutbuf;
90961f28255Scgd 
91073a2253aSchristos static void
timer(int dummy)911edc4b408Slukem timer(int dummy)
91261f28255Scgd {
91361f28255Scgd 
91461f28255Scgd 	timeout += rexmtval;
91561f28255Scgd 	if (timeout >= maxtimeout)
91661f28255Scgd 		exit(1);
91761f28255Scgd 	longjmp(timeoutbuf, 1);
91861f28255Scgd }
91961f28255Scgd 
920d8c9b346Schristos static const char *
opcode(int code)921d8c9b346Schristos opcode(int code)
922d8c9b346Schristos {
923c12ec901Slukem 	static char obuf[64];
924d8c9b346Schristos 
925d8c9b346Schristos 	switch (code) {
926d8c9b346Schristos 	case RRQ:
927d8c9b346Schristos 		return "RRQ";
928d8c9b346Schristos 	case WRQ:
929d8c9b346Schristos 		return "WRQ";
930d8c9b346Schristos 	case DATA:
931d8c9b346Schristos 		return "DATA";
932d8c9b346Schristos 	case ACK:
933d8c9b346Schristos 		return "ACK";
934d8c9b346Schristos 	case ERROR:
935d8c9b346Schristos 		return "ERROR";
93644411286Sbriggs 	case OACK:
93744411286Sbriggs 		return "OACK";
938d8c9b346Schristos 	default:
93973a2253aSchristos 		(void)snprintf(obuf, sizeof(obuf), "*code 0x%x*", code);
940c12ec901Slukem 		return obuf;
941d8c9b346Schristos 	}
942d8c9b346Schristos }
943d8c9b346Schristos 
94461f28255Scgd /*
94561f28255Scgd  * Send the requested file.
94661f28255Scgd  */
94773a2253aSchristos static void
sendfile(struct formats * pf,volatile int etftp,int acklength)94873a2253aSchristos sendfile(struct formats *pf, volatile int etftp, int acklength)
94961f28255Scgd {
950edc4b408Slukem 	volatile unsigned int block;
95160cef501Smrg 	struct tftphdr	*dp;
95215b3c2bfSlukem 	struct tftphdr	*ap;    /* ack packet */
95373a2253aSchristos 	volatile int	 size;
95473a2253aSchristos 	int n;
95561f28255Scgd 
95661f28255Scgd 	signal(SIGALRM, timer);
95761f28255Scgd 	ap = (struct tftphdr *)ackbuf;
95844411286Sbriggs 	if (etftp) {
95944411286Sbriggs 		dp = (struct tftphdr *)oackbuf;
96044411286Sbriggs 		size = acklength - 4;
96144411286Sbriggs 		block = 0;
96244411286Sbriggs 	} else {
96344411286Sbriggs 		dp = r_init();
96444411286Sbriggs 		size = 0;
96560cef501Smrg 		block = 1;
96644411286Sbriggs 	}
96744411286Sbriggs 
96861f28255Scgd 	do {
96944411286Sbriggs 		if (block > 0) {
97044411286Sbriggs 			size = readit(file, &dp, tftp_blksize, pf->f_convert);
97161f28255Scgd 			if (size < 0) {
97261f28255Scgd 				nak(errno + 100);
97361f28255Scgd 				goto abort;
97461f28255Scgd 			}
97561f28255Scgd 			dp->th_opcode = htons((u_short)DATA);
97661f28255Scgd 			dp->th_block = htons((u_short)block);
97744411286Sbriggs 		}
97861f28255Scgd 		timeout = 0;
97961f28255Scgd 		(void)setjmp(timeoutbuf);
98061f28255Scgd 
98161f28255Scgd send_data:
98244411286Sbriggs 		if (!etftp && debug)
983d8c9b346Schristos 			syslog(LOG_DEBUG, "Send DATA %u", block);
9843a2e9669Sbuhrow 		if ((n = sendto(peer, dp, size + 4, 0, (struct sockaddr *)&from, fromlen)) != size + 4) {
98515b3c2bfSlukem 			syslog(LOG_ERR, "tftpd: write: %m");
98661f28255Scgd 			goto abort;
98761f28255Scgd 		}
98844411286Sbriggs 		if (block)
98944411286Sbriggs 			read_ahead(file, tftp_blksize, pf->f_convert);
99061f28255Scgd 		for ( ; ; ) {
99161f28255Scgd 			alarm(rexmtval);        /* read the ack */
9923a2e9669Sbuhrow 			n = recvfrom(peer, ackbuf, tftp_blksize, 0,(struct sockaddr
9933a2e9669Sbuhrow 			*)&from, &fromlen );
99461f28255Scgd 			alarm(0);
99561f28255Scgd 			if (n < 0) {
99615b3c2bfSlukem 				syslog(LOG_ERR, "tftpd: read: %m");
99761f28255Scgd 				goto abort;
99861f28255Scgd 			}
99961f28255Scgd 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
100061f28255Scgd 			ap->th_block = ntohs((u_short)ap->th_block);
1001d8c9b346Schristos 			switch (ap->th_opcode) {
1002d8c9b346Schristos 			case ERROR:
100361f28255Scgd 				goto abort;
100461f28255Scgd 
1005d8c9b346Schristos 			case ACK:
100673a2253aSchristos 				if (etftp && ap->th_block == 0) {
100744411286Sbriggs 					etftp = 0;
100844411286Sbriggs 					acklength = 0;
100944411286Sbriggs 					dp = r_init();
101044411286Sbriggs 					goto done;
101144411286Sbriggs 				}
101273a2253aSchristos 				if (ap->th_block == (u_short)block)
1013d8c9b346Schristos 					goto done;
1014d8c9b346Schristos 				if (debug)
1015d8c9b346Schristos 					syslog(LOG_DEBUG, "Resync ACK %u != %u",
1016d8c9b346Schristos 					    (unsigned int)ap->th_block, block);
101761f28255Scgd 				/* Re-synchronize with the other side */
101844411286Sbriggs 				(void) synchnet(peer, tftp_blksize);
101973a2253aSchristos 				if (ap->th_block == (u_short)(block - 1))
102061f28255Scgd 					goto send_data;
1021*ef0b7ea3Sshm 				/* FALLTHROUGH */
1022d8c9b346Schristos 			default:
1023d8c9b346Schristos 				syslog(LOG_INFO, "Received %s in sendfile\n",
1024d8c9b346Schristos 				    opcode(dp->th_opcode));
102561f28255Scgd 			}
102661f28255Scgd 
102761f28255Scgd 		}
1028d8c9b346Schristos done:
1029d8c9b346Schristos 		if (debug)
1030d8c9b346Schristos 			syslog(LOG_DEBUG, "Received ACK for block %u", block);
103173a2253aSchristos 		if (block == UINT16_MAX && size == tftp_blksize)
103273a2253aSchristos 			syslog(LOG_WARNING,
103373a2253aSchristos 			    "Block number wrapped (hint: increase block size)");
103461f28255Scgd 		block++;
103544411286Sbriggs 	} while (size == tftp_blksize || block == 1);
103661f28255Scgd abort:
103761f28255Scgd 	(void) fclose(file);
103861f28255Scgd }
103961f28255Scgd 
104073a2253aSchristos static void
justquit(int dummy)1041edc4b408Slukem justquit(int dummy)
104261f28255Scgd {
1043edc4b408Slukem 
104461f28255Scgd 	exit(0);
104561f28255Scgd }
104661f28255Scgd 
104761f28255Scgd /*
104861f28255Scgd  * Receive a file.
104961f28255Scgd  */
105073a2253aSchristos static void
recvfile(struct formats * pf,volatile int etftp,volatile int acklength)105173a2253aSchristos recvfile(struct formats *pf, volatile int etftp, volatile int acklength)
105261f28255Scgd {
1053edc4b408Slukem 	volatile unsigned int block;
105460cef501Smrg 	struct tftphdr	*dp;
105515b3c2bfSlukem 	struct tftphdr	*ap;    /* ack buffer */
105673a2253aSchristos 	volatile int size;
105773a2253aSchristos 	int n;
105861f28255Scgd 
105961f28255Scgd 	signal(SIGALRM, timer);
106061f28255Scgd 	dp = w_init();
106144411286Sbriggs 	ap = (struct tftphdr *)oackbuf;
106260cef501Smrg 	block = 0;
106361f28255Scgd 	do {
106461f28255Scgd 		timeout = 0;
106544411286Sbriggs 		if (etftp == 0) {
106644411286Sbriggs 			ap = (struct tftphdr *)ackbuf;
106761f28255Scgd 			ap->th_opcode = htons((u_short)ACK);
106861f28255Scgd 			ap->th_block = htons((u_short)block);
106944411286Sbriggs 			acklength = 4;
107044411286Sbriggs 		}
1071d8c9b346Schristos 		if (debug)
1072d8c9b346Schristos 			syslog(LOG_DEBUG, "Sending ACK for block %u\n", block);
107373a2253aSchristos 		if (block == UINT16_MAX)
107473a2253aSchristos 			syslog(LOG_WARNING,
107573a2253aSchristos 			    "Block number wrapped (hint: increase block size)");
107661f28255Scgd 		block++;
107761f28255Scgd 		(void) setjmp(timeoutbuf);
107861f28255Scgd send_ack:
107973a2253aSchristos 		ap = (struct tftphdr *) (etftp ? oackbuf : ackbuf);
10803a2e9669Sbuhrow 		if (sendto(peer, ap, acklength, 0, (struct sockaddr *)&from, fromlen) != acklength) {
108115b3c2bfSlukem 			syslog(LOG_ERR, "tftpd: write: %m");
108261f28255Scgd 			goto abort;
108361f28255Scgd 		}
108461f28255Scgd 		write_behind(file, pf->f_convert);
108561f28255Scgd 		for ( ; ; ) {
108661f28255Scgd 			alarm(rexmtval);
10873a2e9669Sbuhrow 			n = recvfrom(peer, dp, tftp_blksize + 4, 0, (struct sockaddr
10883a2e9669Sbuhrow 			*)&from, &fromlen);
108961f28255Scgd 			alarm(0);
109061f28255Scgd 			if (n < 0) {            /* really? */
109115b3c2bfSlukem 				syslog(LOG_ERR, "tftpd: read: %m");
109261f28255Scgd 				goto abort;
109361f28255Scgd 			}
109444411286Sbriggs 			etftp = 0;
109561f28255Scgd 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
109661f28255Scgd 			dp->th_block = ntohs((u_short)dp->th_block);
1097d8c9b346Schristos 			if (debug)
1098d8c9b346Schristos 				syslog(LOG_DEBUG, "Received %s for block %u",
1099d8c9b346Schristos 				    opcode(dp->th_opcode),
1100d8c9b346Schristos 				    (unsigned int)dp->th_block);
1101d8c9b346Schristos 
1102d8c9b346Schristos 			switch (dp->th_opcode) {
1103d8c9b346Schristos 			case ERROR:
110461f28255Scgd 				goto abort;
1105d8c9b346Schristos 			case DATA:
1106d8c9b346Schristos 				if (dp->th_block == block)
1107d8c9b346Schristos 					goto done;   /* normal */
1108d8c9b346Schristos 				if (debug)
1109d8c9b346Schristos 					syslog(LOG_DEBUG, "Resync %u != %u",
1110d8c9b346Schristos 					    (unsigned int)dp->th_block, block);
111161f28255Scgd 				/* Re-synchronize with the other side */
111244411286Sbriggs 				(void) synchnet(peer, tftp_blksize);
111361f28255Scgd 				if (dp->th_block == (block-1))
111461f28255Scgd 					goto send_ack;          /* rexmit */
1115d8c9b346Schristos 				break;
1116d8c9b346Schristos 			default:
1117d8c9b346Schristos 				syslog(LOG_INFO, "Received %s in recvfile\n",
1118d8c9b346Schristos 				    opcode(dp->th_opcode));
1119d8c9b346Schristos 				break;
112061f28255Scgd 			}
112161f28255Scgd 		}
1122d8c9b346Schristos done:
1123d8c9b346Schristos 		if (debug)
1124d8c9b346Schristos 			syslog(LOG_DEBUG, "Got block %u", block);
112561f28255Scgd 		/*  size = write(file, dp->th_data, n - 4); */
112661f28255Scgd 		size = writeit(file, &dp, n - 4, pf->f_convert);
112761f28255Scgd 		if (size != (n-4)) {                    /* ahem */
112861f28255Scgd 			if (size < 0) nak(errno + 100);
112961f28255Scgd 			else nak(ENOSPACE);
113061f28255Scgd 			goto abort;
113161f28255Scgd 		}
113244411286Sbriggs 	} while (size == tftp_blksize);
113361f28255Scgd 	write_behind(file, pf->f_convert);
113461f28255Scgd 	(void) fclose(file);            /* close data file */
113561f28255Scgd 
113661f28255Scgd 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
113761f28255Scgd 	ap->th_block = htons((u_short)(block));
1138d8c9b346Schristos 	if (debug)
1139d8c9b346Schristos 		syslog(LOG_DEBUG, "Send final ACK %u", block);
11403a2e9669Sbuhrow 	(void) sendto(peer, ackbuf, 4, 0, (struct sockaddr *)&from, fromlen);
114161f28255Scgd 
114261f28255Scgd 	signal(SIGALRM, justquit);      /* just quit on timeout */
114361f28255Scgd 	alarm(rexmtval);
11443a2e9669Sbuhrow 	n = recvfrom(peer, buf, sizeof (buf), 0, (struct sockaddr *)&from, &fromlen); /* normally times out and quits */
114561f28255Scgd 	alarm(0);
114661f28255Scgd 	if (n >= 4 &&                   /* if read some data */
114761f28255Scgd 	    dp->th_opcode == DATA &&    /* and got a data block */
114861f28255Scgd 	    block == dp->th_block) {	/* then my last ack was lost */
11493a2e9669Sbuhrow 		(void) sendto(peer, ackbuf, 4, 0, (struct sockaddr *)&from, fromlen);     /* resend final ack */
115061f28255Scgd 	}
115161f28255Scgd abort:
115261f28255Scgd 	return;
115361f28255Scgd }
115461f28255Scgd 
11550db548a9Smycroft const struct errmsg {
115661f28255Scgd 	int		 e_code;
11570db548a9Smycroft 	const char	*e_msg;
115861f28255Scgd } errmsgs[] = {
115961f28255Scgd 	{ EUNDEF,	"Undefined error code" },
116061f28255Scgd 	{ ENOTFOUND,	"File not found" },
116161f28255Scgd 	{ EACCESS,	"Access violation" },
116261f28255Scgd 	{ ENOSPACE,	"Disk full or allocation exceeded" },
116361f28255Scgd 	{ EBADOP,	"Illegal TFTP operation" },
116461f28255Scgd 	{ EBADID,	"Unknown transfer ID" },
116561f28255Scgd 	{ EEXISTS,	"File already exists" },
116661f28255Scgd 	{ ENOUSER,	"No such user" },
116744411286Sbriggs 	{ EOPTNEG,	"Option negotiation failed" },
116861f28255Scgd 	{ -1,		0 }
116961f28255Scgd };
117061f28255Scgd 
11710db548a9Smycroft static const char *
errtomsg(int error)1172edc4b408Slukem errtomsg(int error)
117360cef501Smrg {
1174edc4b408Slukem 	static char ebuf[20];
117515b3c2bfSlukem 	const struct errmsg *pe;
117660cef501Smrg 
117760cef501Smrg 	if (error == 0)
1178edc4b408Slukem 		return ("success");
117960cef501Smrg 	for (pe = errmsgs; pe->e_code >= 0; pe++)
118060cef501Smrg 		if (pe->e_code == error)
1181edc4b408Slukem 			return (pe->e_msg);
1182edc4b408Slukem 	snprintf(ebuf, sizeof(ebuf), "error %d", error);
1183edc4b408Slukem 	return (ebuf);
118460cef501Smrg }
118560cef501Smrg 
118661f28255Scgd /*
118761f28255Scgd  * Send a nak packet (error message).
118861f28255Scgd  * Error code passed in is one of the
118961f28255Scgd  * standard TFTP codes, or a UNIX errno
119061f28255Scgd  * offset by 100.
119161f28255Scgd  */
119260cef501Smrg static void
nak(int error)1193edc4b408Slukem nak(int error)
119461f28255Scgd {
1195edc4b408Slukem 	const struct errmsg *pe;
119615b3c2bfSlukem 	struct tftphdr *tp;
119761f28255Scgd 	int	length;
1198b7b60560Sitojun 	size_t	msglen;
119961f28255Scgd 
120061f28255Scgd 	tp = (struct tftphdr *)buf;
120161f28255Scgd 	tp->th_opcode = htons((u_short)ERROR);
1202b7b60560Sitojun 	msglen = sizeof(buf) - (&tp->th_msg[0] - buf);
120361f28255Scgd 	for (pe = errmsgs; pe->e_code >= 0; pe++)
120461f28255Scgd 		if (pe->e_code == error)
120561f28255Scgd 			break;
120661f28255Scgd 	if (pe->e_code < 0) {
120761f28255Scgd 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
1208b7b60560Sitojun 		strlcpy(tp->th_msg, strerror(error - 100), msglen);
12090db548a9Smycroft 	} else {
12100db548a9Smycroft 		tp->th_code = htons((u_short)error);
1211b7b60560Sitojun 		strlcpy(tp->th_msg, pe->e_msg, msglen);
12120db548a9Smycroft 	}
1213d8c9b346Schristos 	if (debug)
1214d8c9b346Schristos 		syslog(LOG_DEBUG, "Send NACK %s", tp->th_msg);
1215b7b60560Sitojun 	length = strlen(tp->th_msg);
1216b7b60560Sitojun 	msglen = &tp->th_msg[length + 1] - buf;
12173a2e9669Sbuhrow 	if (sendto(peer, buf, msglen, 0, (struct sockaddr *)&from, fromlen) != (ssize_t)msglen)
121815b3c2bfSlukem 		syslog(LOG_ERR, "nak: %m");
121961f28255Scgd }
122060cef501Smrg 
122160cef501Smrg static char *
verifyhost(struct sockaddr * fromp)1222edc4b408Slukem verifyhost(struct sockaddr *fromp)
122360cef501Smrg {
122447b0e5ffSitojun 	static char hbuf[MAXHOSTNAMELEN];
122560cef501Smrg 
12269b1ccfd1Sitojun 	if (getnameinfo(fromp, fromp->sa_len, hbuf, sizeof(hbuf), NULL, 0, 0))
12279b1ccfd1Sitojun 		strlcpy(hbuf, "?", sizeof(hbuf));
1228edc4b408Slukem 	return (hbuf);
122960cef501Smrg }
1230