xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 42413)
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
634774Sbostic  * provided that the above copyright notice and this paragraph are
734774Sbostic  * duplicated in all such forms and that any documentation,
834774Sbostic  * advertising materials, and other materials related to such
934774Sbostic  * distribution and use acknowledge that the software was developed
1034774Sbostic  * by the University of California, Berkeley.  The name of the
1134774Sbostic  * University may not be used to endorse or promote products derived
1234774Sbostic  * from this software without specific prior written permission.
1334774Sbostic  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
1434774Sbostic  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
1534774Sbostic  * 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*42413Sbostic static char sccsid[] = "@(#)tftpd.c	5.11 (Berkeley) 05/28/90";
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>
39*42413Sbostic #include <sys/signal.h>
409220Ssam 
419220Ssam #include <netinet/in.h>
429220Ssam 
4312217Ssam #include <arpa/tftp.h>
4412217Ssam 
45*42413Sbostic #include <netdb.h>
46*42413Sbostic #include <setjmp.h>
477772Ssam #include <stdio.h>
487772Ssam #include <errno.h>
497772Ssam #include <ctype.h>
5017188Sralph #include <syslog.h>
51*42413Sbostic #include <string.h>
529220Ssam 
5313020Ssam #define	TIMEOUT		5
5413020Ssam 
557772Ssam extern	int errno;
568385Ssam struct	sockaddr_in sin = { AF_INET };
5716372Skarels int	peer;
5813020Ssam int	rexmtval = TIMEOUT;
5913020Ssam int	maxtimeout = 5*TIMEOUT;
6026108Sminshall 
6126108Sminshall #define	PKTSIZE	SEGSIZE+4
6226108Sminshall char	buf[PKTSIZE];
6326108Sminshall char	ackbuf[PKTSIZE];
6416372Skarels struct	sockaddr_in from;
6516372Skarels int	fromlen;
667772Ssam 
6735783Stef #define MAXARG	4
6835783Stef char	*dirs[MAXARG+1];
6935783Stef 
7035783Stef main(ac, av)
7135783Stef 	char **av;
727772Ssam {
737772Ssam 	register struct tftphdr *tp;
7435783Stef 	register int n = 0;
7528070Sminshall 	int on = 1;
767772Ssam 
7735783Stef 	ac--; av++;
7835783Stef 	while (ac-- > 0 && n < MAXARG)
7935783Stef 		dirs[n++] = *av++;
8024852Seric 	openlog("tftpd", LOG_PID, LOG_DAEMON);
8128070Sminshall 	if (ioctl(0, FIONBIO, &on) < 0) {
8228070Sminshall 		syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
8328070Sminshall 		exit(1);
8428070Sminshall 	}
8516372Skarels 	fromlen = sizeof (from);
8616372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
8716372Skarels 	    (caddr_t)&from, &fromlen);
8816372Skarels 	if (n < 0) {
8928070Sminshall 		syslog(LOG_ERR, "recvfrom: %m\n");
908385Ssam 		exit(1);
918385Ssam 	}
9228070Sminshall 	/*
9328070Sminshall 	 * Now that we have read the message out of the UDP
9428070Sminshall 	 * socket, we fork and exit.  Thus, inetd will go back
9528070Sminshall 	 * to listening to the tftp port, and the next request
9628070Sminshall 	 * to come in will start up a new instance of tftpd.
9728070Sminshall 	 *
9828070Sminshall 	 * We do this so that inetd can run tftpd in "wait" mode.
9928070Sminshall 	 * The problem with tftpd running in "nowait" mode is that
10028070Sminshall 	 * inetd may get one or more successful "selects" on the
10128070Sminshall 	 * tftp port before we do our receive, so more than one
10228070Sminshall 	 * instance of tftpd may be started up.  Worse, if tftpd
10328070Sminshall 	 * break before doing the above "recvfrom", inetd would
10428070Sminshall 	 * spawn endless instances, clogging the system.
10528070Sminshall 	 */
10628070Sminshall 	{
10728070Sminshall 		int pid;
10828070Sminshall 		int i, j;
10928070Sminshall 
11028070Sminshall 		for (i = 1; i < 20; i++) {
11128070Sminshall 		    pid = fork();
11228070Sminshall 		    if (pid < 0) {
11328070Sminshall 				sleep(i);
11428070Sminshall 				/*
11528070Sminshall 				 * flush out to most recently sent request.
11628070Sminshall 				 *
11728070Sminshall 				 * This may drop some request, but those
11828070Sminshall 				 * will be resent by the clients when
11928070Sminshall 				 * they timeout.  The positive effect of
12028070Sminshall 				 * this flush is to (try to) prevent more
12128070Sminshall 				 * than one tftpd being started up to service
12228070Sminshall 				 * a single request from a single client.
12328070Sminshall 				 */
12428070Sminshall 				j = sizeof from;
12528070Sminshall 				i = recvfrom(0, buf, sizeof (buf), 0,
12628070Sminshall 				    (caddr_t)&from, &j);
12728070Sminshall 				if (i > 0) {
12828070Sminshall 					n = i;
12928070Sminshall 					fromlen = j;
13028070Sminshall 				}
13128070Sminshall 		    } else {
13228070Sminshall 				break;
13328070Sminshall 		    }
13428070Sminshall 		}
13528070Sminshall 		if (pid < 0) {
13628070Sminshall 			syslog(LOG_ERR, "fork: %m\n");
13728070Sminshall 			exit(1);
13828070Sminshall 		} else if (pid != 0) {
13928070Sminshall 			exit(0);
14028070Sminshall 		}
14128070Sminshall 	}
14216372Skarels 	from.sin_family = AF_INET;
14316372Skarels 	alarm(0);
14416372Skarels 	close(0);
14516372Skarels 	close(1);
14616372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
14716372Skarels 	if (peer < 0) {
14828070Sminshall 		syslog(LOG_ERR, "socket: %m\n");
14916372Skarels 		exit(1);
1507772Ssam 	}
15116372Skarels 	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
15228070Sminshall 		syslog(LOG_ERR, "bind: %m\n");
15316372Skarels 		exit(1);
15416372Skarels 	}
15516372Skarels 	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
15628070Sminshall 		syslog(LOG_ERR, "connect: %m\n");
15716372Skarels 		exit(1);
1587772Ssam 	}
15916372Skarels 	tp = (struct tftphdr *)buf;
16016372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
16116372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
16216372Skarels 		tftp(tp, n);
16316372Skarels 	exit(1);
1647772Ssam }
1657772Ssam 
1667772Ssam int	validate_access();
1677772Ssam int	sendfile(), recvfile();
1687772Ssam 
1697772Ssam struct formats {
1707772Ssam 	char	*f_mode;
1717772Ssam 	int	(*f_validate)();
1727772Ssam 	int	(*f_send)();
1737772Ssam 	int	(*f_recv)();
17426108Sminshall 	int	f_convert;
1757772Ssam } formats[] = {
17626108Sminshall 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
17726108Sminshall 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
1787772Ssam #ifdef notdef
17926108Sminshall 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
1807772Ssam #endif
1817772Ssam 	{ 0 }
1827772Ssam };
1837772Ssam 
1847772Ssam /*
1857772Ssam  * Handle initial connection protocol.
1867772Ssam  */
18716372Skarels tftp(tp, size)
1887772Ssam 	struct tftphdr *tp;
1897772Ssam 	int size;
1907772Ssam {
1917772Ssam 	register char *cp;
1927772Ssam 	int first = 1, ecode;
1937772Ssam 	register struct formats *pf;
1947772Ssam 	char *filename, *mode;
1957772Ssam 
1967772Ssam 	filename = cp = tp->th_stuff;
1977772Ssam again:
1987772Ssam 	while (cp < buf + size) {
1997772Ssam 		if (*cp == '\0')
2007772Ssam 			break;
2017772Ssam 		cp++;
2027772Ssam 	}
2037772Ssam 	if (*cp != '\0') {
2047772Ssam 		nak(EBADOP);
2057772Ssam 		exit(1);
2067772Ssam 	}
2077772Ssam 	if (first) {
2087772Ssam 		mode = ++cp;
2097772Ssam 		first = 0;
2107772Ssam 		goto again;
2117772Ssam 	}
2127772Ssam 	for (cp = mode; *cp; cp++)
2137772Ssam 		if (isupper(*cp))
2147772Ssam 			*cp = tolower(*cp);
2157772Ssam 	for (pf = formats; pf->f_mode; pf++)
2167772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
2177772Ssam 			break;
2187772Ssam 	if (pf->f_mode == 0) {
2197772Ssam 		nak(EBADOP);
2207772Ssam 		exit(1);
2217772Ssam 	}
22216372Skarels 	ecode = (*pf->f_validate)(filename, tp->th_opcode);
2237772Ssam 	if (ecode) {
2247772Ssam 		nak(ecode);
2257772Ssam 		exit(1);
2267772Ssam 	}
2277772Ssam 	if (tp->th_opcode == WRQ)
2287772Ssam 		(*pf->f_recv)(pf);
2297772Ssam 	else
2307772Ssam 		(*pf->f_send)(pf);
2317772Ssam 	exit(0);
2327772Ssam }
2337772Ssam 
23416372Skarels 
23526108Sminshall FILE *file;
23626108Sminshall 
2377772Ssam /*
2387772Ssam  * Validate file access.  Since we
2397772Ssam  * have no uid or gid, for now require
2407772Ssam  * file to exist and be publicly
2417772Ssam  * readable/writable.
24235783Stef  * If we were invoked with arguments
24335783Stef  * from inetd then the file must also be
24435783Stef  * in one of the given directory prefixes.
2457772Ssam  * Note also, full path name must be
2467772Ssam  * given as we have no login directory.
2477772Ssam  */
24826108Sminshall validate_access(filename, mode)
24926108Sminshall 	char *filename;
2507772Ssam 	int mode;
2517772Ssam {
2527772Ssam 	struct stat stbuf;
25326108Sminshall 	int	fd;
25441146Stef 	char *cp, **dirp;
2557772Ssam 
25626108Sminshall 	if (*filename != '/')
2577772Ssam 		return (EACCESS);
25841146Stef 	/*
25941146Stef 	 * prevent tricksters from getting around the directory restrictions
26041146Stef 	 */
26141146Stef 	for (cp = filename + 1; *cp; cp++)
26241146Stef 		if(*cp == '.' && strncmp(cp-1, "/../", 4) == 0)
26341146Stef 			return(EACCESS);
26441146Stef 	for (dirp = dirs; *dirp; dirp++)
26535783Stef 		if (strncmp(filename, *dirp, strlen(*dirp)) == 0)
26635783Stef 			break;
26735783Stef 	if (*dirp==0 && dirp!=dirs)
26835783Stef 		return (EACCESS);
26926108Sminshall 	if (stat(filename, &stbuf) < 0)
2707772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
2717772Ssam 	if (mode == RRQ) {
2727772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
2737772Ssam 			return (EACCESS);
2747772Ssam 	} else {
2757772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
2767772Ssam 			return (EACCESS);
2777772Ssam 	}
27826108Sminshall 	fd = open(filename, mode == RRQ ? 0 : 1);
2797772Ssam 	if (fd < 0)
2807772Ssam 		return (errno + 100);
28126108Sminshall 	file = fdopen(fd, (mode == RRQ)? "r":"w");
28226108Sminshall 	if (file == NULL) {
28326108Sminshall 		return errno+100;
28426108Sminshall 	}
2857772Ssam 	return (0);
2867772Ssam }
2877772Ssam 
28813020Ssam int	timeout;
28913020Ssam jmp_buf	timeoutbuf;
2907772Ssam 
2917772Ssam timer()
2927772Ssam {
29313020Ssam 
29413020Ssam 	timeout += rexmtval;
29513020Ssam 	if (timeout >= maxtimeout)
2967772Ssam 		exit(1);
29713020Ssam 	longjmp(timeoutbuf, 1);
2987772Ssam }
2997772Ssam 
3007772Ssam /*
3017772Ssam  * Send the requested file.
3027772Ssam  */
3037772Ssam sendfile(pf)
30426108Sminshall 	struct formats *pf;
3057772Ssam {
30626108Sminshall 	struct tftphdr *dp, *r_init();
30726108Sminshall 	register struct tftphdr *ap;    /* ack packet */
3087772Ssam 	register int block = 1, size, n;
3097772Ssam 
31013020Ssam 	signal(SIGALRM, timer);
31126108Sminshall 	dp = r_init();
31226108Sminshall 	ap = (struct tftphdr *)ackbuf;
3137772Ssam 	do {
31426108Sminshall 		size = readit(file, &dp, pf->f_convert);
3157772Ssam 		if (size < 0) {
3167772Ssam 			nak(errno + 100);
31726108Sminshall 			goto abort;
3187772Ssam 		}
31926108Sminshall 		dp->th_opcode = htons((u_short)DATA);
32026108Sminshall 		dp->th_block = htons((u_short)block);
3217772Ssam 		timeout = 0;
32213020Ssam 		(void) setjmp(timeoutbuf);
32326108Sminshall 
32426109Sminshall send_data:
32526108Sminshall 		if (send(peer, dp, size + 4, 0) != size + 4) {
32628070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
32726108Sminshall 			goto abort;
3287772Ssam 		}
32926108Sminshall 		read_ahead(file, pf->f_convert);
33026109Sminshall 		for ( ; ; ) {
33126108Sminshall 			alarm(rexmtval);        /* read the ack */
33226108Sminshall 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
3337772Ssam 			alarm(0);
33413020Ssam 			if (n < 0) {
33528070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
33626108Sminshall 				goto abort;
33713020Ssam 			}
33826108Sminshall 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
33926108Sminshall 			ap->th_block = ntohs((u_short)ap->th_block);
34026108Sminshall 
34126108Sminshall 			if (ap->th_opcode == ERROR)
34226108Sminshall 				goto abort;
34326109Sminshall 
34426109Sminshall 			if (ap->th_opcode == ACK) {
34526109Sminshall 				if (ap->th_block == block) {
34626109Sminshall 					break;
34726109Sminshall 				}
34826115Sminshall 				/* Re-synchronize with the other side */
34926115Sminshall 				(void) synchnet(peer);
35026109Sminshall 				if (ap->th_block == (block -1)) {
35126109Sminshall 					goto send_data;
35226109Sminshall 				}
35326109Sminshall 			}
35426108Sminshall 
35526109Sminshall 		}
3567772Ssam 		block++;
3577772Ssam 	} while (size == SEGSIZE);
35826108Sminshall abort:
35926108Sminshall 	(void) fclose(file);
3607772Ssam }
3617772Ssam 
36226108Sminshall justquit()
36326108Sminshall {
36426108Sminshall 	exit(0);
36526108Sminshall }
36626108Sminshall 
36726108Sminshall 
3687772Ssam /*
3697772Ssam  * Receive a file.
3707772Ssam  */
3717772Ssam recvfile(pf)
37226108Sminshall 	struct formats *pf;
3737772Ssam {
37426108Sminshall 	struct tftphdr *dp, *w_init();
37526108Sminshall 	register struct tftphdr *ap;    /* ack buffer */
3767772Ssam 	register int block = 0, n, size;
3777772Ssam 
37813020Ssam 	signal(SIGALRM, timer);
37926108Sminshall 	dp = w_init();
38026108Sminshall 	ap = (struct tftphdr *)ackbuf;
3817772Ssam 	do {
3827772Ssam 		timeout = 0;
38326108Sminshall 		ap->th_opcode = htons((u_short)ACK);
38426108Sminshall 		ap->th_block = htons((u_short)block);
3857772Ssam 		block++;
38613020Ssam 		(void) setjmp(timeoutbuf);
38726108Sminshall send_ack:
38826108Sminshall 		if (send(peer, ackbuf, 4, 0) != 4) {
38928070Sminshall 			syslog(LOG_ERR, "tftpd: write: %m\n");
39013020Ssam 			goto abort;
3917772Ssam 		}
39226108Sminshall 		write_behind(file, pf->f_convert);
39326108Sminshall 		for ( ; ; ) {
39413020Ssam 			alarm(rexmtval);
39526108Sminshall 			n = recv(peer, dp, PKTSIZE, 0);
3967772Ssam 			alarm(0);
39726108Sminshall 			if (n < 0) {            /* really? */
39828070Sminshall 				syslog(LOG_ERR, "tftpd: read: %m\n");
39913020Ssam 				goto abort;
40013020Ssam 			}
40126108Sminshall 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
40226108Sminshall 			dp->th_block = ntohs((u_short)dp->th_block);
40326108Sminshall 			if (dp->th_opcode == ERROR)
40413020Ssam 				goto abort;
40526108Sminshall 			if (dp->th_opcode == DATA) {
40626108Sminshall 				if (dp->th_block == block) {
40726108Sminshall 					break;   /* normal */
40826108Sminshall 				}
40926115Sminshall 				/* Re-synchronize with the other side */
41026115Sminshall 				(void) synchnet(peer);
41126108Sminshall 				if (dp->th_block == (block-1))
41226108Sminshall 					goto send_ack;          /* rexmit */
41326108Sminshall 			}
41426108Sminshall 		}
41526108Sminshall 		/*  size = write(file, dp->th_data, n - 4); */
41626108Sminshall 		size = writeit(file, &dp, n - 4, pf->f_convert);
41726108Sminshall 		if (size != (n-4)) {                    /* ahem */
41826108Sminshall 			if (size < 0) nak(errno + 100);
41926108Sminshall 			else nak(ENOSPACE);
42013020Ssam 			goto abort;
4217772Ssam 		}
4227772Ssam 	} while (size == SEGSIZE);
42326108Sminshall 	write_behind(file, pf->f_convert);
42426108Sminshall 	(void) fclose(file);            /* close data file */
42526108Sminshall 
42626108Sminshall 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
42726108Sminshall 	ap->th_block = htons((u_short)(block));
42826108Sminshall 	(void) send(peer, ackbuf, 4, 0);
42926108Sminshall 
43026108Sminshall 	signal(SIGALRM, justquit);      /* just quit on timeout */
43126108Sminshall 	alarm(rexmtval);
43226108Sminshall 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
43326108Sminshall 	alarm(0);
43426108Sminshall 	if (n >= 4 &&                   /* if read some data */
43526108Sminshall 	    dp->th_opcode == DATA &&    /* and got a data block */
43626108Sminshall 	    block == dp->th_block) {	/* then my last ack was lost */
43726108Sminshall 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
43826108Sminshall 	}
43913020Ssam abort:
44026108Sminshall 	return;
4417772Ssam }
4427772Ssam 
4437772Ssam struct errmsg {
4447772Ssam 	int	e_code;
4457772Ssam 	char	*e_msg;
4467772Ssam } errmsgs[] = {
4477772Ssam 	{ EUNDEF,	"Undefined error code" },
4487772Ssam 	{ ENOTFOUND,	"File not found" },
4497772Ssam 	{ EACCESS,	"Access violation" },
4507772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
4517772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
4527772Ssam 	{ EBADID,	"Unknown transfer ID" },
4537772Ssam 	{ EEXISTS,	"File already exists" },
4547772Ssam 	{ ENOUSER,	"No such user" },
4557772Ssam 	{ -1,		0 }
4567772Ssam };
4577772Ssam 
4587772Ssam /*
4597772Ssam  * Send a nak packet (error message).
4607772Ssam  * Error code passed in is one of the
4617772Ssam  * standard TFTP codes, or a UNIX errno
4627772Ssam  * offset by 100.
4637772Ssam  */
4647772Ssam nak(error)
4657772Ssam 	int error;
4667772Ssam {
4677772Ssam 	register struct tftphdr *tp;
4687772Ssam 	int length;
4697772Ssam 	register struct errmsg *pe;
4707772Ssam 
4717772Ssam 	tp = (struct tftphdr *)buf;
4727772Ssam 	tp->th_opcode = htons((u_short)ERROR);
4737772Ssam 	tp->th_code = htons((u_short)error);
4747772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
4757772Ssam 		if (pe->e_code == error)
4767772Ssam 			break;
47726108Sminshall 	if (pe->e_code < 0) {
478*42413Sbostic 		pe->e_msg = strerror(error - 100);
47926108Sminshall 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
48026108Sminshall 	}
4817772Ssam 	strcpy(tp->th_msg, pe->e_msg);
4827772Ssam 	length = strlen(pe->e_msg);
4837772Ssam 	tp->th_msg[length] = '\0';
4847772Ssam 	length += 5;
48516372Skarels 	if (send(peer, buf, length, 0) != length)
48628070Sminshall 		syslog(LOG_ERR, "nak: %m\n");
4877772Ssam }
488