xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 26109)
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*26109Sminshall static char sccsid[] = "@(#)tftpd.c	5.4 (Berkeley) 02/07/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;
607772Ssam 
6124852Seric 	openlog("tftpd", LOG_PID, LOG_DAEMON);
6216372Skarels 	alarm(10);
6316372Skarels 	fromlen = sizeof (from);
6416372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
6516372Skarels 	    (caddr_t)&from, &fromlen);
6616372Skarels 	if (n < 0) {
6716372Skarels 		perror("tftpd: recvfrom");
688385Ssam 		exit(1);
698385Ssam 	}
7016372Skarels 	from.sin_family = AF_INET;
7116372Skarels 	alarm(0);
7216372Skarels 	close(0);
7316372Skarels 	close(1);
7416372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
7516372Skarels 	if (peer < 0) {
7617188Sralph 		syslog(LOG_ERR, "socket: %m");
7716372Skarels 		exit(1);
787772Ssam 	}
7916372Skarels 	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
8017188Sralph 		syslog(LOG_ERR, "bind: %m");
8116372Skarels 		exit(1);
8216372Skarels 	}
8316372Skarels 	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
8417188Sralph 		syslog(LOG_ERR, "connect: %m");
8516372Skarels 		exit(1);
867772Ssam 	}
8716372Skarels 	tp = (struct tftphdr *)buf;
8816372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
8916372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
9016372Skarels 		tftp(tp, n);
9116372Skarels 	exit(1);
927772Ssam }
937772Ssam 
947772Ssam int	validate_access();
957772Ssam int	sendfile(), recvfile();
967772Ssam 
977772Ssam struct formats {
987772Ssam 	char	*f_mode;
997772Ssam 	int	(*f_validate)();
1007772Ssam 	int	(*f_send)();
1017772Ssam 	int	(*f_recv)();
10226108Sminshall 	int	f_convert;
1037772Ssam } formats[] = {
10426108Sminshall 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
10526108Sminshall 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
1067772Ssam #ifdef notdef
10726108Sminshall 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
1087772Ssam #endif
1097772Ssam 	{ 0 }
1107772Ssam };
1117772Ssam 
1127772Ssam /*
1137772Ssam  * Handle initial connection protocol.
1147772Ssam  */
11516372Skarels tftp(tp, size)
1167772Ssam 	struct tftphdr *tp;
1177772Ssam 	int size;
1187772Ssam {
1197772Ssam 	register char *cp;
1207772Ssam 	int first = 1, ecode;
1217772Ssam 	register struct formats *pf;
1227772Ssam 	char *filename, *mode;
1237772Ssam 
1247772Ssam 	filename = cp = tp->th_stuff;
1257772Ssam again:
1267772Ssam 	while (cp < buf + size) {
1277772Ssam 		if (*cp == '\0')
1287772Ssam 			break;
1297772Ssam 		cp++;
1307772Ssam 	}
1317772Ssam 	if (*cp != '\0') {
1327772Ssam 		nak(EBADOP);
1337772Ssam 		exit(1);
1347772Ssam 	}
1357772Ssam 	if (first) {
1367772Ssam 		mode = ++cp;
1377772Ssam 		first = 0;
1387772Ssam 		goto again;
1397772Ssam 	}
1407772Ssam 	for (cp = mode; *cp; cp++)
1417772Ssam 		if (isupper(*cp))
1427772Ssam 			*cp = tolower(*cp);
1437772Ssam 	for (pf = formats; pf->f_mode; pf++)
1447772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
1457772Ssam 			break;
1467772Ssam 	if (pf->f_mode == 0) {
1477772Ssam 		nak(EBADOP);
1487772Ssam 		exit(1);
1497772Ssam 	}
15016372Skarels 	ecode = (*pf->f_validate)(filename, tp->th_opcode);
1517772Ssam 	if (ecode) {
1527772Ssam 		nak(ecode);
1537772Ssam 		exit(1);
1547772Ssam 	}
1557772Ssam 	if (tp->th_opcode == WRQ)
1567772Ssam 		(*pf->f_recv)(pf);
1577772Ssam 	else
1587772Ssam 		(*pf->f_send)(pf);
1597772Ssam 	exit(0);
1607772Ssam }
1617772Ssam 
16216372Skarels 
16326108Sminshall FILE *file;
16426108Sminshall 
1657772Ssam /*
1667772Ssam  * Validate file access.  Since we
1677772Ssam  * have no uid or gid, for now require
1687772Ssam  * file to exist and be publicly
1697772Ssam  * readable/writable.
1707772Ssam  * Note also, full path name must be
1717772Ssam  * given as we have no login directory.
1727772Ssam  */
17326108Sminshall validate_access(filename, mode)
17426108Sminshall 	char *filename;
1757772Ssam 	int mode;
1767772Ssam {
1777772Ssam 	struct stat stbuf;
17826108Sminshall 	int	fd;
1797772Ssam 
18026108Sminshall 	if (*filename != '/')
1817772Ssam 		return (EACCESS);
18226108Sminshall 	if (stat(filename, &stbuf) < 0)
1837772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
1847772Ssam 	if (mode == RRQ) {
1857772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
1867772Ssam 			return (EACCESS);
1877772Ssam 	} else {
1887772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
1897772Ssam 			return (EACCESS);
1907772Ssam 	}
19126108Sminshall 	fd = open(filename, mode == RRQ ? 0 : 1);
1927772Ssam 	if (fd < 0)
1937772Ssam 		return (errno + 100);
19426108Sminshall 	file = fdopen(fd, (mode == RRQ)? "r":"w");
19526108Sminshall 	if (file == NULL) {
19626108Sminshall 		return errno+100;
19726108Sminshall 	}
1987772Ssam 	return (0);
1997772Ssam }
2007772Ssam 
20113020Ssam int	timeout;
20213020Ssam jmp_buf	timeoutbuf;
2037772Ssam 
2047772Ssam timer()
2057772Ssam {
20613020Ssam 
20713020Ssam 	timeout += rexmtval;
20813020Ssam 	if (timeout >= maxtimeout)
2097772Ssam 		exit(1);
21013020Ssam 	longjmp(timeoutbuf, 1);
2117772Ssam }
2127772Ssam 
2137772Ssam /*
2147772Ssam  * Send the requested file.
2157772Ssam  */
2167772Ssam sendfile(pf)
21726108Sminshall 	struct formats *pf;
2187772Ssam {
21926108Sminshall 	struct tftphdr *dp, *r_init();
22026108Sminshall 	register struct tftphdr *ap;    /* ack packet */
2217772Ssam 	register int block = 1, size, n;
2227772Ssam 
22313020Ssam 	signal(SIGALRM, timer);
22426108Sminshall 	dp = r_init();
22526108Sminshall 	ap = (struct tftphdr *)ackbuf;
2267772Ssam 	do {
22726108Sminshall 		size = readit(file, &dp, pf->f_convert);
2287772Ssam 		if (size < 0) {
2297772Ssam 			nak(errno + 100);
23026108Sminshall 			goto abort;
2317772Ssam 		}
23226108Sminshall 		dp->th_opcode = htons((u_short)DATA);
23326108Sminshall 		dp->th_block = htons((u_short)block);
2347772Ssam 		timeout = 0;
23513020Ssam 		(void) setjmp(timeoutbuf);
23626108Sminshall 
237*26109Sminshall send_data:
238*26109Sminshall 		/* Now, we flush anything pending to be read */
239*26109Sminshall 		/* This is to try to keep in synch between the two sides */
240*26109Sminshall 		while (1) {
241*26109Sminshall 			int i;
242*26109Sminshall 			char rbuf[PKTSIZE];
243*26109Sminshall 
244*26109Sminshall 			(void) ioctl(peer, FIONREAD, &i);
245*26109Sminshall 			if (i) {
246*26109Sminshall 				fromlen = sizeof from;
247*26109Sminshall 				n = recvfrom(peer, rbuf, sizeof (rbuf), 0,
248*26109Sminshall 					(caddr_t)&from, &fromlen);
249*26109Sminshall 			} else {
250*26109Sminshall 				break;
251*26109Sminshall 			}
252*26109Sminshall 		}
25326108Sminshall 		if (send(peer, dp, size + 4, 0) != size + 4) {
25426108Sminshall 			perror("tftpd: write");
25526108Sminshall 			goto abort;
2567772Ssam 		}
25726108Sminshall 		read_ahead(file, pf->f_convert);
258*26109Sminshall 		for ( ; ; ) {
25926108Sminshall 			alarm(rexmtval);        /* read the ack */
26026108Sminshall 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
2617772Ssam 			alarm(0);
26213020Ssam 			if (n < 0) {
26326108Sminshall 				perror("tftpd: read");
26426108Sminshall 				goto abort;
26513020Ssam 			}
26626108Sminshall 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
26726108Sminshall 			ap->th_block = ntohs((u_short)ap->th_block);
26826108Sminshall 
26926108Sminshall 			if (ap->th_opcode == ERROR)
27026108Sminshall 				goto abort;
271*26109Sminshall 
272*26109Sminshall 			if (ap->th_opcode == ACK) {
273*26109Sminshall 				if (ap->th_block == block) {
274*26109Sminshall 					break;
275*26109Sminshall 				}
276*26109Sminshall 				if (ap->th_block == (block -1)) {
277*26109Sminshall 					goto send_data;
278*26109Sminshall 				}
279*26109Sminshall 			}
28026108Sminshall 
281*26109Sminshall 		}
2827772Ssam 		block++;
2837772Ssam 	} while (size == SEGSIZE);
28426108Sminshall abort:
28526108Sminshall 	(void) fclose(file);
2867772Ssam }
2877772Ssam 
28826108Sminshall justquit()
28926108Sminshall {
29026108Sminshall 	exit(0);
29126108Sminshall }
29226108Sminshall 
29326108Sminshall 
2947772Ssam /*
2957772Ssam  * Receive a file.
2967772Ssam  */
2977772Ssam recvfile(pf)
29826108Sminshall 	struct formats *pf;
2997772Ssam {
30026108Sminshall 	struct tftphdr *dp, *w_init();
30126108Sminshall 	register struct tftphdr *ap;    /* ack buffer */
3027772Ssam 	register int block = 0, n, size;
3037772Ssam 
30413020Ssam 	signal(SIGALRM, timer);
30526108Sminshall 	dp = w_init();
30626108Sminshall 	ap = (struct tftphdr *)ackbuf;
3077772Ssam 	do {
3087772Ssam 		timeout = 0;
30926108Sminshall 		ap->th_opcode = htons((u_short)ACK);
31026108Sminshall 		ap->th_block = htons((u_short)block);
3117772Ssam 		block++;
31213020Ssam 		(void) setjmp(timeoutbuf);
31326108Sminshall send_ack:
314*26109Sminshall 		/* Now, we flush anything pending to be read */
315*26109Sminshall 		/* This is to try to keep in synch between the two sides */
316*26109Sminshall 		while (1) {
317*26109Sminshall 			int i;
318*26109Sminshall 			char rbuf[PKTSIZE];
319*26109Sminshall 
320*26109Sminshall 			(void) ioctl(peer, FIONREAD, &i);
321*26109Sminshall 			if (i) {
322*26109Sminshall 				fromlen = sizeof from;
323*26109Sminshall 				n = recvfrom(peer, rbuf, sizeof (rbuf), 0,
324*26109Sminshall 					(caddr_t)&from, &fromlen);
325*26109Sminshall 			} else {
326*26109Sminshall 				break;
327*26109Sminshall 			}
328*26109Sminshall 		}
32926108Sminshall 		if (send(peer, ackbuf, 4, 0) != 4) {
33026108Sminshall 			perror("tftpd: write");
33113020Ssam 			goto abort;
3327772Ssam 		}
33326108Sminshall 		write_behind(file, pf->f_convert);
33426108Sminshall 		for ( ; ; ) {
33513020Ssam 			alarm(rexmtval);
33626108Sminshall 			n = recv(peer, dp, PKTSIZE, 0);
3377772Ssam 			alarm(0);
33826108Sminshall 			if (n < 0) {            /* really? */
33926108Sminshall 				perror("tftpd: read");
34013020Ssam 				goto abort;
34113020Ssam 			}
34226108Sminshall 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
34326108Sminshall 			dp->th_block = ntohs((u_short)dp->th_block);
34426108Sminshall 			if (dp->th_opcode == ERROR)
34513020Ssam 				goto abort;
34626108Sminshall 			if (dp->th_opcode == DATA) {
34726108Sminshall 				if (dp->th_block == block) {
34826108Sminshall 					break;   /* normal */
34926108Sminshall 				}
35026108Sminshall 				if (dp->th_block == (block-1))
35126108Sminshall 					goto send_ack;          /* rexmit */
35226108Sminshall 			}
35326108Sminshall 		}
35426108Sminshall 		/*  size = write(file, dp->th_data, n - 4); */
35526108Sminshall 		size = writeit(file, &dp, n - 4, pf->f_convert);
35626108Sminshall 		if (size != (n-4)) {                    /* ahem */
35726108Sminshall 			if (size < 0) nak(errno + 100);
35826108Sminshall 			else nak(ENOSPACE);
35913020Ssam 			goto abort;
3607772Ssam 		}
3617772Ssam 	} while (size == SEGSIZE);
36226108Sminshall 	write_behind(file, pf->f_convert);
36326108Sminshall 	(void) fclose(file);            /* close data file */
36426108Sminshall 
36526108Sminshall 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
36626108Sminshall 	ap->th_block = htons((u_short)(block));
36726108Sminshall 	(void) send(peer, ackbuf, 4, 0);
36826108Sminshall 
36926108Sminshall 	signal(SIGALRM, justquit);      /* just quit on timeout */
37026108Sminshall 	alarm(rexmtval);
37126108Sminshall 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
37226108Sminshall 	alarm(0);
37326108Sminshall 	if (n >= 4 &&                   /* if read some data */
37426108Sminshall 	    dp->th_opcode == DATA &&    /* and got a data block */
37526108Sminshall 	    block == dp->th_block) {	/* then my last ack was lost */
37626108Sminshall 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
37726108Sminshall 	}
37813020Ssam abort:
37926108Sminshall 	return;
3807772Ssam }
3817772Ssam 
3827772Ssam struct errmsg {
3837772Ssam 	int	e_code;
3847772Ssam 	char	*e_msg;
3857772Ssam } errmsgs[] = {
3867772Ssam 	{ EUNDEF,	"Undefined error code" },
3877772Ssam 	{ ENOTFOUND,	"File not found" },
3887772Ssam 	{ EACCESS,	"Access violation" },
3897772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
3907772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
3917772Ssam 	{ EBADID,	"Unknown transfer ID" },
3927772Ssam 	{ EEXISTS,	"File already exists" },
3937772Ssam 	{ ENOUSER,	"No such user" },
3947772Ssam 	{ -1,		0 }
3957772Ssam };
3967772Ssam 
3977772Ssam /*
3987772Ssam  * Send a nak packet (error message).
3997772Ssam  * Error code passed in is one of the
4007772Ssam  * standard TFTP codes, or a UNIX errno
4017772Ssam  * offset by 100.
4027772Ssam  */
4037772Ssam nak(error)
4047772Ssam 	int error;
4057772Ssam {
4067772Ssam 	register struct tftphdr *tp;
4077772Ssam 	int length;
4087772Ssam 	register struct errmsg *pe;
4097772Ssam 	extern char *sys_errlist[];
4107772Ssam 
4117772Ssam 	tp = (struct tftphdr *)buf;
4127772Ssam 	tp->th_opcode = htons((u_short)ERROR);
4137772Ssam 	tp->th_code = htons((u_short)error);
4147772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
4157772Ssam 		if (pe->e_code == error)
4167772Ssam 			break;
41726108Sminshall 	if (pe->e_code < 0) {
4187772Ssam 		pe->e_msg = sys_errlist[error - 100];
41926108Sminshall 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
42026108Sminshall 	}
4217772Ssam 	strcpy(tp->th_msg, pe->e_msg);
4227772Ssam 	length = strlen(pe->e_msg);
4237772Ssam 	tp->th_msg[length] = '\0';
4247772Ssam 	length += 5;
42516372Skarels 	if (send(peer, buf, length, 0) != length)
4267772Ssam 		perror("nak");
4277772Ssam }
428