xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 34774)
121183Sdist /*
221183Sdist  * Copyright (c) 1983 Regents of the University of California.
333822Sbostic  * All rights reserved.
433822Sbostic  *
533822Sbostic  * Redistribution and use in source and binary forms are permitted
6*34774Sbostic  * provided that the above copyright notice and this paragraph are
7*34774Sbostic  * duplicated in all such forms and that any documentation,
8*34774Sbostic  * advertising materials, and other materials related to such
9*34774Sbostic  * distribution and use acknowledge that the software was developed
10*34774Sbostic  * by the University of California, Berkeley.  The name of the
11*34774Sbostic  * University may not be used to endorse or promote products derived
12*34774Sbostic  * from this software without specific prior written permission.
13*34774Sbostic  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14*34774Sbostic  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15*34774Sbostic  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1621183Sdist  */
1721183Sdist 
1813609Ssam #ifndef lint
1921183Sdist char copyright[] =
2021183Sdist "@(#) Copyright (c) 1983 Regents of the University of California.\n\
2121183Sdist  All rights reserved.\n";
2233822Sbostic #endif /* not lint */
237772Ssam 
2421183Sdist #ifndef lint
25*34774Sbostic static char sccsid[] = "@(#)tftpd.c	5.8 (Berkeley) 06/18/88";
2633822Sbostic #endif /* not lint */
2721183Sdist 
287772Ssam /*
297772Ssam  * Trivial file transfer protocol server.
3026108Sminshall  *
3126108Sminshall  * This version includes many modifications by Jim Guyton <guyton@rand-unix>
327772Ssam  */
3326108Sminshall 
347772Ssam #include <sys/types.h>
357772Ssam #include <sys/socket.h>
369220Ssam #include <sys/ioctl.h>
3713609Ssam #include <sys/wait.h>
3813609Ssam #include <sys/stat.h>
399220Ssam 
409220Ssam #include <netinet/in.h>
419220Ssam 
4212217Ssam #include <arpa/tftp.h>
4312217Ssam 
447772Ssam #include <signal.h>
457772Ssam #include <stdio.h>
467772Ssam #include <errno.h>
477772Ssam #include <ctype.h>
488385Ssam #include <netdb.h>
4913020Ssam #include <setjmp.h>
5017188Sralph #include <syslog.h>
519220Ssam 
5213020Ssam #define	TIMEOUT		5
5313020Ssam 
547772Ssam extern	int errno;
558385Ssam struct	sockaddr_in sin = { AF_INET };
5616372Skarels int	peer;
5713020Ssam int	rexmtval = TIMEOUT;
5813020Ssam int	maxtimeout = 5*TIMEOUT;
5926108Sminshall 
6026108Sminshall #define	PKTSIZE	SEGSIZE+4
6126108Sminshall char	buf[PKTSIZE];
6226108Sminshall char	ackbuf[PKTSIZE];
6316372Skarels struct	sockaddr_in from;
6416372Skarels int	fromlen;
657772Ssam 
6616372Skarels main()
677772Ssam {
687772Ssam 	register struct tftphdr *tp;
697772Ssam 	register int n;
7028070Sminshall 	int on = 1;
717772Ssam 
7224852Seric 	openlog("tftpd", LOG_PID, LOG_DAEMON);
7328070Sminshall 	if (ioctl(0, FIONBIO, &on) < 0) {
7428070Sminshall 		syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
7528070Sminshall 		exit(1);
7628070Sminshall 	}
7716372Skarels 	fromlen = sizeof (from);
7816372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
7916372Skarels 	    (caddr_t)&from, &fromlen);
8016372Skarels 	if (n < 0) {
8128070Sminshall 		syslog(LOG_ERR, "recvfrom: %m\n");
828385Ssam 		exit(1);
838385Ssam 	}
8428070Sminshall 	/*
8528070Sminshall 	 * Now that we have read the message out of the UDP
8628070Sminshall 	 * socket, we fork and exit.  Thus, inetd will go back
8728070Sminshall 	 * to listening to the tftp port, and the next request
8828070Sminshall 	 * to come in will start up a new instance of tftpd.
8928070Sminshall 	 *
9028070Sminshall 	 * We do this so that inetd can run tftpd in "wait" mode.
9128070Sminshall 	 * The problem with tftpd running in "nowait" mode is that
9228070Sminshall 	 * inetd may get one or more successful "selects" on the
9328070Sminshall 	 * tftp port before we do our receive, so more than one
9428070Sminshall 	 * instance of tftpd may be started up.  Worse, if tftpd
9528070Sminshall 	 * break before doing the above "recvfrom", inetd would
9628070Sminshall 	 * spawn endless instances, clogging the system.
9728070Sminshall 	 */
9828070Sminshall 	{
9928070Sminshall 		int pid;
10028070Sminshall 		int i, j;
10128070Sminshall 
10228070Sminshall 		for (i = 1; i < 20; i++) {
10328070Sminshall 		    pid = fork();
10428070Sminshall 		    if (pid < 0) {
10528070Sminshall 				sleep(i);
10628070Sminshall 				/*
10728070Sminshall 				 * flush out to most recently sent request.
10828070Sminshall 				 *
10928070Sminshall 				 * This may drop some request, but those
11028070Sminshall 				 * will be resent by the clients when
11128070Sminshall 				 * they timeout.  The positive effect of
11228070Sminshall 				 * this flush is to (try to) prevent more
11328070Sminshall 				 * than one tftpd being started up to service
11428070Sminshall 				 * a single request from a single client.
11528070Sminshall 				 */
11628070Sminshall 				j = sizeof from;
11728070Sminshall 				i = recvfrom(0, buf, sizeof (buf), 0,
11828070Sminshall 				    (caddr_t)&from, &j);
11928070Sminshall 				if (i > 0) {
12028070Sminshall 					n = i;
12128070Sminshall 					fromlen = j;
12228070Sminshall 				}
12328070Sminshall 		    } else {
12428070Sminshall 				break;
12528070Sminshall 		    }
12628070Sminshall 		}
12728070Sminshall 		if (pid < 0) {
12828070Sminshall 			syslog(LOG_ERR, "fork: %m\n");
12928070Sminshall 			exit(1);
13028070Sminshall 		} else if (pid != 0) {
13128070Sminshall 			exit(0);
13228070Sminshall 		}
13328070Sminshall 	}
13416372Skarels 	from.sin_family = AF_INET;
13516372Skarels 	alarm(0);
13616372Skarels 	close(0);
13716372Skarels 	close(1);
13816372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
13916372Skarels 	if (peer < 0) {
14028070Sminshall 		syslog(LOG_ERR, "socket: %m\n");
14116372Skarels 		exit(1);
1427772Ssam 	}
14316372Skarels 	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
14428070Sminshall 		syslog(LOG_ERR, "bind: %m\n");
14516372Skarels 		exit(1);
14616372Skarels 	}
14716372Skarels 	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
14828070Sminshall 		syslog(LOG_ERR, "connect: %m\n");
14916372Skarels 		exit(1);
1507772Ssam 	}
15116372Skarels 	tp = (struct tftphdr *)buf;
15216372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
15316372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
15416372Skarels 		tftp(tp, n);
15516372Skarels 	exit(1);
1567772Ssam }
1577772Ssam 
1587772Ssam int	validate_access();
1597772Ssam int	sendfile(), recvfile();
1607772Ssam 
1617772Ssam struct formats {
1627772Ssam 	char	*f_mode;
1637772Ssam 	int	(*f_validate)();
1647772Ssam 	int	(*f_send)();
1657772Ssam 	int	(*f_recv)();
16626108Sminshall 	int	f_convert;
1677772Ssam } formats[] = {
16826108Sminshall 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
16926108Sminshall 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
1707772Ssam #ifdef notdef
17126108Sminshall 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
1727772Ssam #endif
1737772Ssam 	{ 0 }
1747772Ssam };
1757772Ssam 
1767772Ssam /*
1777772Ssam  * Handle initial connection protocol.
1787772Ssam  */
17916372Skarels tftp(tp, size)
1807772Ssam 	struct tftphdr *tp;
1817772Ssam 	int size;
1827772Ssam {
1837772Ssam 	register char *cp;
1847772Ssam 	int first = 1, ecode;
1857772Ssam 	register struct formats *pf;
1867772Ssam 	char *filename, *mode;
1877772Ssam 
1887772Ssam 	filename = cp = tp->th_stuff;
1897772Ssam again:
1907772Ssam 	while (cp < buf + size) {
1917772Ssam 		if (*cp == '\0')
1927772Ssam 			break;
1937772Ssam 		cp++;
1947772Ssam 	}
1957772Ssam 	if (*cp != '\0') {
1967772Ssam 		nak(EBADOP);
1977772Ssam 		exit(1);
1987772Ssam 	}
1997772Ssam 	if (first) {
2007772Ssam 		mode = ++cp;
2017772Ssam 		first = 0;
2027772Ssam 		goto again;
2037772Ssam 	}
2047772Ssam 	for (cp = mode; *cp; cp++)
2057772Ssam 		if (isupper(*cp))
2067772Ssam 			*cp = tolower(*cp);
2077772Ssam 	for (pf = formats; pf->f_mode; pf++)
2087772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
2097772Ssam 			break;
2107772Ssam 	if (pf->f_mode == 0) {
2117772Ssam 		nak(EBADOP);
2127772Ssam 		exit(1);
2137772Ssam 	}
21416372Skarels 	ecode = (*pf->f_validate)(filename, tp->th_opcode);
2157772Ssam 	if (ecode) {
2167772Ssam 		nak(ecode);
2177772Ssam 		exit(1);
2187772Ssam 	}
2197772Ssam 	if (tp->th_opcode == WRQ)
2207772Ssam 		(*pf->f_recv)(pf);
2217772Ssam 	else
2227772Ssam 		(*pf->f_send)(pf);
2237772Ssam 	exit(0);
2247772Ssam }
2257772Ssam 
22616372Skarels 
22726108Sminshall FILE *file;
22826108Sminshall 
2297772Ssam /*
2307772Ssam  * Validate file access.  Since we
2317772Ssam  * have no uid or gid, for now require
2327772Ssam  * file to exist and be publicly
2337772Ssam  * readable/writable.
2347772Ssam  * Note also, full path name must be
2357772Ssam  * given as we have no login directory.
2367772Ssam  */
23726108Sminshall validate_access(filename, mode)
23826108Sminshall 	char *filename;
2397772Ssam 	int mode;
2407772Ssam {
2417772Ssam 	struct stat stbuf;
24226108Sminshall 	int	fd;
2437772Ssam 
24426108Sminshall 	if (*filename != '/')
2457772Ssam 		return (EACCESS);
24626108Sminshall 	if (stat(filename, &stbuf) < 0)
2477772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
2487772Ssam 	if (mode == RRQ) {
2497772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
2507772Ssam 			return (EACCESS);
2517772Ssam 	} else {
2527772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
2537772Ssam 			return (EACCESS);
2547772Ssam 	}
25526108Sminshall 	fd = open(filename, mode == RRQ ? 0 : 1);
2567772Ssam 	if (fd < 0)
2577772Ssam 		return (errno + 100);
25826108Sminshall 	file = fdopen(fd, (mode == RRQ)? "r":"w");
25926108Sminshall 	if (file == NULL) {
26026108Sminshall 		return errno+100;
26126108Sminshall 	}
2627772Ssam 	return (0);
2637772Ssam }
2647772Ssam 
26513020Ssam int	timeout;
26613020Ssam jmp_buf	timeoutbuf;
2677772Ssam 
2687772Ssam timer()
2697772Ssam {
27013020Ssam 
27113020Ssam 	timeout += rexmtval;
27213020Ssam 	if (timeout >= maxtimeout)
2737772Ssam 		exit(1);
27413020Ssam 	longjmp(timeoutbuf, 1);
2757772Ssam }
2767772Ssam 
2777772Ssam /*
2787772Ssam  * Send the requested file.
2797772Ssam  */
2807772Ssam sendfile(pf)
28126108Sminshall 	struct formats *pf;
2827772Ssam {
28326108Sminshall 	struct tftphdr *dp, *r_init();
28426108Sminshall 	register struct tftphdr *ap;    /* ack packet */
2857772Ssam 	register int block = 1, size, n;
2867772Ssam 
28713020Ssam 	signal(SIGALRM, timer);
28826108Sminshall 	dp = r_init();
28926108Sminshall 	ap = (struct tftphdr *)ackbuf;
2907772Ssam 	do {
29126108Sminshall 		size = readit(file, &dp, pf->f_convert);
2927772Ssam 		if (size < 0) {
2937772Ssam 			nak(errno + 100);
29426108Sminshall 			goto abort;
2957772Ssam 		}
29626108Sminshall 		dp->th_opcode = htons((u_short)DATA);
29726108Sminshall 		dp->th_block = htons((u_short)block);
2987772Ssam 		timeout = 0;
29913020Ssam 		(void) setjmp(timeoutbuf);
30026108Sminshall 
30126109Sminshall send_data:
30226108Sminshall 		if (send(peer, dp, size + 4, 0) != size + 4) {
30328070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
30426108Sminshall 			goto abort;
3057772Ssam 		}
30626108Sminshall 		read_ahead(file, pf->f_convert);
30726109Sminshall 		for ( ; ; ) {
30826108Sminshall 			alarm(rexmtval);        /* read the ack */
30926108Sminshall 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
3107772Ssam 			alarm(0);
31113020Ssam 			if (n < 0) {
31228070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
31326108Sminshall 				goto abort;
31413020Ssam 			}
31526108Sminshall 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
31626108Sminshall 			ap->th_block = ntohs((u_short)ap->th_block);
31726108Sminshall 
31826108Sminshall 			if (ap->th_opcode == ERROR)
31926108Sminshall 				goto abort;
32026109Sminshall 
32126109Sminshall 			if (ap->th_opcode == ACK) {
32226109Sminshall 				if (ap->th_block == block) {
32326109Sminshall 					break;
32426109Sminshall 				}
32526115Sminshall 				/* Re-synchronize with the other side */
32626115Sminshall 				(void) synchnet(peer);
32726109Sminshall 				if (ap->th_block == (block -1)) {
32826109Sminshall 					goto send_data;
32926109Sminshall 				}
33026109Sminshall 			}
33126108Sminshall 
33226109Sminshall 		}
3337772Ssam 		block++;
3347772Ssam 	} while (size == SEGSIZE);
33526108Sminshall abort:
33626108Sminshall 	(void) fclose(file);
3377772Ssam }
3387772Ssam 
33926108Sminshall justquit()
34026108Sminshall {
34126108Sminshall 	exit(0);
34226108Sminshall }
34326108Sminshall 
34426108Sminshall 
3457772Ssam /*
3467772Ssam  * Receive a file.
3477772Ssam  */
3487772Ssam recvfile(pf)
34926108Sminshall 	struct formats *pf;
3507772Ssam {
35126108Sminshall 	struct tftphdr *dp, *w_init();
35226108Sminshall 	register struct tftphdr *ap;    /* ack buffer */
3537772Ssam 	register int block = 0, n, size;
3547772Ssam 
35513020Ssam 	signal(SIGALRM, timer);
35626108Sminshall 	dp = w_init();
35726108Sminshall 	ap = (struct tftphdr *)ackbuf;
3587772Ssam 	do {
3597772Ssam 		timeout = 0;
36026108Sminshall 		ap->th_opcode = htons((u_short)ACK);
36126108Sminshall 		ap->th_block = htons((u_short)block);
3627772Ssam 		block++;
36313020Ssam 		(void) setjmp(timeoutbuf);
36426108Sminshall send_ack:
36526108Sminshall 		if (send(peer, ackbuf, 4, 0) != 4) {
36628070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
36713020Ssam 			goto abort;
3687772Ssam 		}
36926108Sminshall 		write_behind(file, pf->f_convert);
37026108Sminshall 		for ( ; ; ) {
37113020Ssam 			alarm(rexmtval);
37226108Sminshall 			n = recv(peer, dp, PKTSIZE, 0);
3737772Ssam 			alarm(0);
37426108Sminshall 			if (n < 0) {            /* really? */
37528070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
37613020Ssam 				goto abort;
37713020Ssam 			}
37826108Sminshall 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
37926108Sminshall 			dp->th_block = ntohs((u_short)dp->th_block);
38026108Sminshall 			if (dp->th_opcode == ERROR)
38113020Ssam 				goto abort;
38226108Sminshall 			if (dp->th_opcode == DATA) {
38326108Sminshall 				if (dp->th_block == block) {
38426108Sminshall 					break;   /* normal */
38526108Sminshall 				}
38626115Sminshall 				/* Re-synchronize with the other side */
38726115Sminshall 				(void) synchnet(peer);
38826108Sminshall 				if (dp->th_block == (block-1))
38926108Sminshall 					goto send_ack;          /* rexmit */
39026108Sminshall 			}
39126108Sminshall 		}
39226108Sminshall 		/*  size = write(file, dp->th_data, n - 4); */
39326108Sminshall 		size = writeit(file, &dp, n - 4, pf->f_convert);
39426108Sminshall 		if (size != (n-4)) {                    /* ahem */
39526108Sminshall 			if (size < 0) nak(errno + 100);
39626108Sminshall 			else nak(ENOSPACE);
39713020Ssam 			goto abort;
3987772Ssam 		}
3997772Ssam 	} while (size == SEGSIZE);
40026108Sminshall 	write_behind(file, pf->f_convert);
40126108Sminshall 	(void) fclose(file);            /* close data file */
40226108Sminshall 
40326108Sminshall 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
40426108Sminshall 	ap->th_block = htons((u_short)(block));
40526108Sminshall 	(void) send(peer, ackbuf, 4, 0);
40626108Sminshall 
40726108Sminshall 	signal(SIGALRM, justquit);      /* just quit on timeout */
40826108Sminshall 	alarm(rexmtval);
40926108Sminshall 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
41026108Sminshall 	alarm(0);
41126108Sminshall 	if (n >= 4 &&                   /* if read some data */
41226108Sminshall 	    dp->th_opcode == DATA &&    /* and got a data block */
41326108Sminshall 	    block == dp->th_block) {	/* then my last ack was lost */
41426108Sminshall 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
41526108Sminshall 	}
41613020Ssam abort:
41726108Sminshall 	return;
4187772Ssam }
4197772Ssam 
4207772Ssam struct errmsg {
4217772Ssam 	int	e_code;
4227772Ssam 	char	*e_msg;
4237772Ssam } errmsgs[] = {
4247772Ssam 	{ EUNDEF,	"Undefined error code" },
4257772Ssam 	{ ENOTFOUND,	"File not found" },
4267772Ssam 	{ EACCESS,	"Access violation" },
4277772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
4287772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
4297772Ssam 	{ EBADID,	"Unknown transfer ID" },
4307772Ssam 	{ EEXISTS,	"File already exists" },
4317772Ssam 	{ ENOUSER,	"No such user" },
4327772Ssam 	{ -1,		0 }
4337772Ssam };
4347772Ssam 
4357772Ssam /*
4367772Ssam  * Send a nak packet (error message).
4377772Ssam  * Error code passed in is one of the
4387772Ssam  * standard TFTP codes, or a UNIX errno
4397772Ssam  * offset by 100.
4407772Ssam  */
4417772Ssam nak(error)
4427772Ssam 	int error;
4437772Ssam {
4447772Ssam 	register struct tftphdr *tp;
4457772Ssam 	int length;
4467772Ssam 	register struct errmsg *pe;
4477772Ssam 	extern char *sys_errlist[];
4487772Ssam 
4497772Ssam 	tp = (struct tftphdr *)buf;
4507772Ssam 	tp->th_opcode = htons((u_short)ERROR);
4517772Ssam 	tp->th_code = htons((u_short)error);
4527772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
4537772Ssam 		if (pe->e_code == error)
4547772Ssam 			break;
45526108Sminshall 	if (pe->e_code < 0) {
4567772Ssam 		pe->e_msg = sys_errlist[error - 100];
45726108Sminshall 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
45826108Sminshall 	}
4597772Ssam 	strcpy(tp->th_msg, pe->e_msg);
4607772Ssam 	length = strlen(pe->e_msg);
4617772Ssam 	tp->th_msg[length] = '\0';
4627772Ssam 	length += 5;
46316372Skarels 	if (send(peer, buf, length, 0) != length)
46428070Sminshall 		syslog(LOG_ERR, "nak: %m\n");
4657772Ssam }
466