xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 28070)
121183Sdist /*
221183Sdist  * Copyright (c) 1983 Regents of the University of California.
321183Sdist  * All rights reserved.  The Berkeley software License Agreement
421183Sdist  * specifies the terms and conditions for redistribution.
521183Sdist  */
621183Sdist 
713609Ssam #ifndef lint
821183Sdist char copyright[] =
921183Sdist "@(#) Copyright (c) 1983 Regents of the University of California.\n\
1021183Sdist  All rights reserved.\n";
1121183Sdist #endif not lint
127772Ssam 
1321183Sdist #ifndef lint
14*28070Sminshall static char sccsid[] = "@(#)tftpd.c	5.6 (Berkeley) 05/13/86";
1521183Sdist #endif not lint
1621183Sdist 
1726108Sminshall 
187772Ssam /*
197772Ssam  * Trivial file transfer protocol server.
2026108Sminshall  *
2126108Sminshall  * This version includes many modifications by Jim Guyton <guyton@rand-unix>
227772Ssam  */
2326108Sminshall 
247772Ssam #include <sys/types.h>
257772Ssam #include <sys/socket.h>
269220Ssam #include <sys/ioctl.h>
2713609Ssam #include <sys/wait.h>
2813609Ssam #include <sys/stat.h>
299220Ssam 
309220Ssam #include <netinet/in.h>
319220Ssam 
3212217Ssam #include <arpa/tftp.h>
3312217Ssam 
347772Ssam #include <signal.h>
357772Ssam #include <stdio.h>
367772Ssam #include <errno.h>
377772Ssam #include <ctype.h>
388385Ssam #include <netdb.h>
3913020Ssam #include <setjmp.h>
4017188Sralph #include <syslog.h>
419220Ssam 
4213020Ssam #define	TIMEOUT		5
4313020Ssam 
447772Ssam extern	int errno;
458385Ssam struct	sockaddr_in sin = { AF_INET };
4616372Skarels int	peer;
4713020Ssam int	rexmtval = TIMEOUT;
4813020Ssam int	maxtimeout = 5*TIMEOUT;
4926108Sminshall 
5026108Sminshall #define	PKTSIZE	SEGSIZE+4
5126108Sminshall char	buf[PKTSIZE];
5226108Sminshall char	ackbuf[PKTSIZE];
5316372Skarels struct	sockaddr_in from;
5416372Skarels int	fromlen;
557772Ssam 
5616372Skarels main()
577772Ssam {
587772Ssam 	register struct tftphdr *tp;
597772Ssam 	register int n;
60*28070Sminshall 	int on = 1;
617772Ssam 
6224852Seric 	openlog("tftpd", LOG_PID, LOG_DAEMON);
63*28070Sminshall 	if (ioctl(0, FIONBIO, &on) < 0) {
64*28070Sminshall 		syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
65*28070Sminshall 		exit(1);
66*28070Sminshall 	}
6716372Skarels 	fromlen = sizeof (from);
6816372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
6916372Skarels 	    (caddr_t)&from, &fromlen);
7016372Skarels 	if (n < 0) {
71*28070Sminshall 		syslog(LOG_ERR, "recvfrom: %m\n");
728385Ssam 		exit(1);
738385Ssam 	}
74*28070Sminshall 	/*
75*28070Sminshall 	 * Now that we have read the message out of the UDP
76*28070Sminshall 	 * socket, we fork and exit.  Thus, inetd will go back
77*28070Sminshall 	 * to listening to the tftp port, and the next request
78*28070Sminshall 	 * to come in will start up a new instance of tftpd.
79*28070Sminshall 	 *
80*28070Sminshall 	 * We do this so that inetd can run tftpd in "wait" mode.
81*28070Sminshall 	 * The problem with tftpd running in "nowait" mode is that
82*28070Sminshall 	 * inetd may get one or more successful "selects" on the
83*28070Sminshall 	 * tftp port before we do our receive, so more than one
84*28070Sminshall 	 * instance of tftpd may be started up.  Worse, if tftpd
85*28070Sminshall 	 * break before doing the above "recvfrom", inetd would
86*28070Sminshall 	 * spawn endless instances, clogging the system.
87*28070Sminshall 	 */
88*28070Sminshall 	{
89*28070Sminshall 		int pid;
90*28070Sminshall 		int i, j;
91*28070Sminshall 
92*28070Sminshall 		for (i = 1; i < 20; i++) {
93*28070Sminshall 		    pid = fork();
94*28070Sminshall 		    if (pid < 0) {
95*28070Sminshall 				sleep(i);
96*28070Sminshall 				/*
97*28070Sminshall 				 * flush out to most recently sent request.
98*28070Sminshall 				 *
99*28070Sminshall 				 * This may drop some request, but those
100*28070Sminshall 				 * will be resent by the clients when
101*28070Sminshall 				 * they timeout.  The positive effect of
102*28070Sminshall 				 * this flush is to (try to) prevent more
103*28070Sminshall 				 * than one tftpd being started up to service
104*28070Sminshall 				 * a single request from a single client.
105*28070Sminshall 				 */
106*28070Sminshall 				j = sizeof from;
107*28070Sminshall 				i = recvfrom(0, buf, sizeof (buf), 0,
108*28070Sminshall 				    (caddr_t)&from, &j);
109*28070Sminshall 				if (i > 0) {
110*28070Sminshall 					n = i;
111*28070Sminshall 					fromlen = j;
112*28070Sminshall 				}
113*28070Sminshall 		    } else {
114*28070Sminshall 				break;
115*28070Sminshall 		    }
116*28070Sminshall 		}
117*28070Sminshall 		if (pid < 0) {
118*28070Sminshall 			syslog(LOG_ERR, "fork: %m\n");
119*28070Sminshall 			exit(1);
120*28070Sminshall 		} else if (pid != 0) {
121*28070Sminshall 			exit(0);
122*28070Sminshall 		}
123*28070Sminshall 	}
12416372Skarels 	from.sin_family = AF_INET;
12516372Skarels 	alarm(0);
12616372Skarels 	close(0);
12716372Skarels 	close(1);
12816372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
12916372Skarels 	if (peer < 0) {
130*28070Sminshall 		syslog(LOG_ERR, "socket: %m\n");
13116372Skarels 		exit(1);
1327772Ssam 	}
13316372Skarels 	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
134*28070Sminshall 		syslog(LOG_ERR, "bind: %m\n");
13516372Skarels 		exit(1);
13616372Skarels 	}
13716372Skarels 	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
138*28070Sminshall 		syslog(LOG_ERR, "connect: %m\n");
13916372Skarels 		exit(1);
1407772Ssam 	}
14116372Skarels 	tp = (struct tftphdr *)buf;
14216372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
14316372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
14416372Skarels 		tftp(tp, n);
14516372Skarels 	exit(1);
1467772Ssam }
1477772Ssam 
1487772Ssam int	validate_access();
1497772Ssam int	sendfile(), recvfile();
1507772Ssam 
1517772Ssam struct formats {
1527772Ssam 	char	*f_mode;
1537772Ssam 	int	(*f_validate)();
1547772Ssam 	int	(*f_send)();
1557772Ssam 	int	(*f_recv)();
15626108Sminshall 	int	f_convert;
1577772Ssam } formats[] = {
15826108Sminshall 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
15926108Sminshall 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
1607772Ssam #ifdef notdef
16126108Sminshall 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
1627772Ssam #endif
1637772Ssam 	{ 0 }
1647772Ssam };
1657772Ssam 
1667772Ssam /*
1677772Ssam  * Handle initial connection protocol.
1687772Ssam  */
16916372Skarels tftp(tp, size)
1707772Ssam 	struct tftphdr *tp;
1717772Ssam 	int size;
1727772Ssam {
1737772Ssam 	register char *cp;
1747772Ssam 	int first = 1, ecode;
1757772Ssam 	register struct formats *pf;
1767772Ssam 	char *filename, *mode;
1777772Ssam 
1787772Ssam 	filename = cp = tp->th_stuff;
1797772Ssam again:
1807772Ssam 	while (cp < buf + size) {
1817772Ssam 		if (*cp == '\0')
1827772Ssam 			break;
1837772Ssam 		cp++;
1847772Ssam 	}
1857772Ssam 	if (*cp != '\0') {
1867772Ssam 		nak(EBADOP);
1877772Ssam 		exit(1);
1887772Ssam 	}
1897772Ssam 	if (first) {
1907772Ssam 		mode = ++cp;
1917772Ssam 		first = 0;
1927772Ssam 		goto again;
1937772Ssam 	}
1947772Ssam 	for (cp = mode; *cp; cp++)
1957772Ssam 		if (isupper(*cp))
1967772Ssam 			*cp = tolower(*cp);
1977772Ssam 	for (pf = formats; pf->f_mode; pf++)
1987772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
1997772Ssam 			break;
2007772Ssam 	if (pf->f_mode == 0) {
2017772Ssam 		nak(EBADOP);
2027772Ssam 		exit(1);
2037772Ssam 	}
20416372Skarels 	ecode = (*pf->f_validate)(filename, tp->th_opcode);
2057772Ssam 	if (ecode) {
2067772Ssam 		nak(ecode);
2077772Ssam 		exit(1);
2087772Ssam 	}
2097772Ssam 	if (tp->th_opcode == WRQ)
2107772Ssam 		(*pf->f_recv)(pf);
2117772Ssam 	else
2127772Ssam 		(*pf->f_send)(pf);
2137772Ssam 	exit(0);
2147772Ssam }
2157772Ssam 
21616372Skarels 
21726108Sminshall FILE *file;
21826108Sminshall 
2197772Ssam /*
2207772Ssam  * Validate file access.  Since we
2217772Ssam  * have no uid or gid, for now require
2227772Ssam  * file to exist and be publicly
2237772Ssam  * readable/writable.
2247772Ssam  * Note also, full path name must be
2257772Ssam  * given as we have no login directory.
2267772Ssam  */
22726108Sminshall validate_access(filename, mode)
22826108Sminshall 	char *filename;
2297772Ssam 	int mode;
2307772Ssam {
2317772Ssam 	struct stat stbuf;
23226108Sminshall 	int	fd;
2337772Ssam 
23426108Sminshall 	if (*filename != '/')
2357772Ssam 		return (EACCESS);
23626108Sminshall 	if (stat(filename, &stbuf) < 0)
2377772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
2387772Ssam 	if (mode == RRQ) {
2397772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
2407772Ssam 			return (EACCESS);
2417772Ssam 	} else {
2427772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
2437772Ssam 			return (EACCESS);
2447772Ssam 	}
24526108Sminshall 	fd = open(filename, mode == RRQ ? 0 : 1);
2467772Ssam 	if (fd < 0)
2477772Ssam 		return (errno + 100);
24826108Sminshall 	file = fdopen(fd, (mode == RRQ)? "r":"w");
24926108Sminshall 	if (file == NULL) {
25026108Sminshall 		return errno+100;
25126108Sminshall 	}
2527772Ssam 	return (0);
2537772Ssam }
2547772Ssam 
25513020Ssam int	timeout;
25613020Ssam jmp_buf	timeoutbuf;
2577772Ssam 
2587772Ssam timer()
2597772Ssam {
26013020Ssam 
26113020Ssam 	timeout += rexmtval;
26213020Ssam 	if (timeout >= maxtimeout)
2637772Ssam 		exit(1);
26413020Ssam 	longjmp(timeoutbuf, 1);
2657772Ssam }
2667772Ssam 
2677772Ssam /*
2687772Ssam  * Send the requested file.
2697772Ssam  */
2707772Ssam sendfile(pf)
27126108Sminshall 	struct formats *pf;
2727772Ssam {
27326108Sminshall 	struct tftphdr *dp, *r_init();
27426108Sminshall 	register struct tftphdr *ap;    /* ack packet */
2757772Ssam 	register int block = 1, size, n;
2767772Ssam 
27713020Ssam 	signal(SIGALRM, timer);
27826108Sminshall 	dp = r_init();
27926108Sminshall 	ap = (struct tftphdr *)ackbuf;
2807772Ssam 	do {
28126108Sminshall 		size = readit(file, &dp, pf->f_convert);
2827772Ssam 		if (size < 0) {
2837772Ssam 			nak(errno + 100);
28426108Sminshall 			goto abort;
2857772Ssam 		}
28626108Sminshall 		dp->th_opcode = htons((u_short)DATA);
28726108Sminshall 		dp->th_block = htons((u_short)block);
2887772Ssam 		timeout = 0;
28913020Ssam 		(void) setjmp(timeoutbuf);
29026108Sminshall 
29126109Sminshall send_data:
29226108Sminshall 		if (send(peer, dp, size + 4, 0) != size + 4) {
293*28070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
29426108Sminshall 			goto abort;
2957772Ssam 		}
29626108Sminshall 		read_ahead(file, pf->f_convert);
29726109Sminshall 		for ( ; ; ) {
29826108Sminshall 			alarm(rexmtval);        /* read the ack */
29926108Sminshall 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
3007772Ssam 			alarm(0);
30113020Ssam 			if (n < 0) {
302*28070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
30326108Sminshall 				goto abort;
30413020Ssam 			}
30526108Sminshall 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
30626108Sminshall 			ap->th_block = ntohs((u_short)ap->th_block);
30726108Sminshall 
30826108Sminshall 			if (ap->th_opcode == ERROR)
30926108Sminshall 				goto abort;
31026109Sminshall 
31126109Sminshall 			if (ap->th_opcode == ACK) {
31226109Sminshall 				if (ap->th_block == block) {
31326109Sminshall 					break;
31426109Sminshall 				}
31526115Sminshall 				/* Re-synchronize with the other side */
31626115Sminshall 				(void) synchnet(peer);
31726109Sminshall 				if (ap->th_block == (block -1)) {
31826109Sminshall 					goto send_data;
31926109Sminshall 				}
32026109Sminshall 			}
32126108Sminshall 
32226109Sminshall 		}
3237772Ssam 		block++;
3247772Ssam 	} while (size == SEGSIZE);
32526108Sminshall abort:
32626108Sminshall 	(void) fclose(file);
3277772Ssam }
3287772Ssam 
32926108Sminshall justquit()
33026108Sminshall {
33126108Sminshall 	exit(0);
33226108Sminshall }
33326108Sminshall 
33426108Sminshall 
3357772Ssam /*
3367772Ssam  * Receive a file.
3377772Ssam  */
3387772Ssam recvfile(pf)
33926108Sminshall 	struct formats *pf;
3407772Ssam {
34126108Sminshall 	struct tftphdr *dp, *w_init();
34226108Sminshall 	register struct tftphdr *ap;    /* ack buffer */
3437772Ssam 	register int block = 0, n, size;
3447772Ssam 
34513020Ssam 	signal(SIGALRM, timer);
34626108Sminshall 	dp = w_init();
34726108Sminshall 	ap = (struct tftphdr *)ackbuf;
3487772Ssam 	do {
3497772Ssam 		timeout = 0;
35026108Sminshall 		ap->th_opcode = htons((u_short)ACK);
35126108Sminshall 		ap->th_block = htons((u_short)block);
3527772Ssam 		block++;
35313020Ssam 		(void) setjmp(timeoutbuf);
35426108Sminshall send_ack:
35526108Sminshall 		if (send(peer, ackbuf, 4, 0) != 4) {
356*28070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
35713020Ssam 			goto abort;
3587772Ssam 		}
35926108Sminshall 		write_behind(file, pf->f_convert);
36026108Sminshall 		for ( ; ; ) {
36113020Ssam 			alarm(rexmtval);
36226108Sminshall 			n = recv(peer, dp, PKTSIZE, 0);
3637772Ssam 			alarm(0);
36426108Sminshall 			if (n < 0) {            /* really? */
365*28070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
36613020Ssam 				goto abort;
36713020Ssam 			}
36826108Sminshall 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
36926108Sminshall 			dp->th_block = ntohs((u_short)dp->th_block);
37026108Sminshall 			if (dp->th_opcode == ERROR)
37113020Ssam 				goto abort;
37226108Sminshall 			if (dp->th_opcode == DATA) {
37326108Sminshall 				if (dp->th_block == block) {
37426108Sminshall 					break;   /* normal */
37526108Sminshall 				}
37626115Sminshall 				/* Re-synchronize with the other side */
37726115Sminshall 				(void) synchnet(peer);
37826108Sminshall 				if (dp->th_block == (block-1))
37926108Sminshall 					goto send_ack;          /* rexmit */
38026108Sminshall 			}
38126108Sminshall 		}
38226108Sminshall 		/*  size = write(file, dp->th_data, n - 4); */
38326108Sminshall 		size = writeit(file, &dp, n - 4, pf->f_convert);
38426108Sminshall 		if (size != (n-4)) {                    /* ahem */
38526108Sminshall 			if (size < 0) nak(errno + 100);
38626108Sminshall 			else nak(ENOSPACE);
38713020Ssam 			goto abort;
3887772Ssam 		}
3897772Ssam 	} while (size == SEGSIZE);
39026108Sminshall 	write_behind(file, pf->f_convert);
39126108Sminshall 	(void) fclose(file);            /* close data file */
39226108Sminshall 
39326108Sminshall 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
39426108Sminshall 	ap->th_block = htons((u_short)(block));
39526108Sminshall 	(void) send(peer, ackbuf, 4, 0);
39626108Sminshall 
39726108Sminshall 	signal(SIGALRM, justquit);      /* just quit on timeout */
39826108Sminshall 	alarm(rexmtval);
39926108Sminshall 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
40026108Sminshall 	alarm(0);
40126108Sminshall 	if (n >= 4 &&                   /* if read some data */
40226108Sminshall 	    dp->th_opcode == DATA &&    /* and got a data block */
40326108Sminshall 	    block == dp->th_block) {	/* then my last ack was lost */
40426108Sminshall 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
40526108Sminshall 	}
40613020Ssam abort:
40726108Sminshall 	return;
4087772Ssam }
4097772Ssam 
4107772Ssam struct errmsg {
4117772Ssam 	int	e_code;
4127772Ssam 	char	*e_msg;
4137772Ssam } errmsgs[] = {
4147772Ssam 	{ EUNDEF,	"Undefined error code" },
4157772Ssam 	{ ENOTFOUND,	"File not found" },
4167772Ssam 	{ EACCESS,	"Access violation" },
4177772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
4187772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
4197772Ssam 	{ EBADID,	"Unknown transfer ID" },
4207772Ssam 	{ EEXISTS,	"File already exists" },
4217772Ssam 	{ ENOUSER,	"No such user" },
4227772Ssam 	{ -1,		0 }
4237772Ssam };
4247772Ssam 
4257772Ssam /*
4267772Ssam  * Send a nak packet (error message).
4277772Ssam  * Error code passed in is one of the
4287772Ssam  * standard TFTP codes, or a UNIX errno
4297772Ssam  * offset by 100.
4307772Ssam  */
4317772Ssam nak(error)
4327772Ssam 	int error;
4337772Ssam {
4347772Ssam 	register struct tftphdr *tp;
4357772Ssam 	int length;
4367772Ssam 	register struct errmsg *pe;
4377772Ssam 	extern char *sys_errlist[];
4387772Ssam 
4397772Ssam 	tp = (struct tftphdr *)buf;
4407772Ssam 	tp->th_opcode = htons((u_short)ERROR);
4417772Ssam 	tp->th_code = htons((u_short)error);
4427772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
4437772Ssam 		if (pe->e_code == error)
4447772Ssam 			break;
44526108Sminshall 	if (pe->e_code < 0) {
4467772Ssam 		pe->e_msg = sys_errlist[error - 100];
44726108Sminshall 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
44826108Sminshall 	}
4497772Ssam 	strcpy(tp->th_msg, pe->e_msg);
4507772Ssam 	length = strlen(pe->e_msg);
4517772Ssam 	tp->th_msg[length] = '\0';
4527772Ssam 	length += 5;
45316372Skarels 	if (send(peer, buf, length, 0) != length)
454*28070Sminshall 		syslog(LOG_ERR, "nak: %m\n");
4557772Ssam }
456