xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 24852)
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*24852Seric static char sccsid[] = "@(#)tftpd.c	5.2 (Berkeley) 09/17/85";
1521183Sdist #endif not lint
1621183Sdist 
177772Ssam /*
187772Ssam  * Trivial file transfer protocol server.
197772Ssam  */
207772Ssam #include <sys/types.h>
217772Ssam #include <sys/socket.h>
229220Ssam #include <sys/ioctl.h>
2313609Ssam #include <sys/wait.h>
2413609Ssam #include <sys/stat.h>
259220Ssam 
269220Ssam #include <netinet/in.h>
279220Ssam 
2812217Ssam #include <arpa/tftp.h>
2912217Ssam 
307772Ssam #include <signal.h>
317772Ssam #include <stdio.h>
327772Ssam #include <errno.h>
337772Ssam #include <ctype.h>
348385Ssam #include <netdb.h>
3513020Ssam #include <setjmp.h>
3617188Sralph #include <syslog.h>
379220Ssam 
3813020Ssam #define	TIMEOUT		5
3913020Ssam 
407772Ssam extern	int errno;
418385Ssam struct	sockaddr_in sin = { AF_INET };
4216372Skarels int	peer;
4313020Ssam int	rexmtval = TIMEOUT;
4413020Ssam int	maxtimeout = 5*TIMEOUT;
457772Ssam char	buf[BUFSIZ];
4616372Skarels struct	sockaddr_in from;
4716372Skarels int	fromlen;
487772Ssam 
4916372Skarels main()
507772Ssam {
517772Ssam 	register struct tftphdr *tp;
527772Ssam 	register int n;
537772Ssam 
54*24852Seric 	openlog("tftpd", LOG_PID, LOG_DAEMON);
5516372Skarels 	alarm(10);
5616372Skarels 	fromlen = sizeof (from);
5716372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
5816372Skarels 	    (caddr_t)&from, &fromlen);
5916372Skarels 	if (n < 0) {
6016372Skarels 		perror("tftpd: recvfrom");
618385Ssam 		exit(1);
628385Ssam 	}
6316372Skarels 	from.sin_family = AF_INET;
6416372Skarels 	alarm(0);
6516372Skarels 	close(0);
6616372Skarels 	close(1);
6716372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
6816372Skarels 	if (peer < 0) {
6917188Sralph 		syslog(LOG_ERR, "socket: %m");
7016372Skarels 		exit(1);
717772Ssam 	}
7216372Skarels 	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
7317188Sralph 		syslog(LOG_ERR, "bind: %m");
7416372Skarels 		exit(1);
7516372Skarels 	}
7616372Skarels 	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
7717188Sralph 		syslog(LOG_ERR, "connect: %m");
7816372Skarels 		exit(1);
797772Ssam 	}
8016372Skarels 	tp = (struct tftphdr *)buf;
8116372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
8216372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
8316372Skarels 		tftp(tp, n);
8416372Skarels 	exit(1);
857772Ssam }
867772Ssam 
877772Ssam int	validate_access();
887772Ssam int	sendfile(), recvfile();
897772Ssam 
907772Ssam struct formats {
917772Ssam 	char	*f_mode;
927772Ssam 	int	(*f_validate)();
937772Ssam 	int	(*f_send)();
947772Ssam 	int	(*f_recv)();
957772Ssam } formats[] = {
967772Ssam 	{ "netascii",	validate_access,	sendfile,	recvfile },
977772Ssam 	{ "octet",	validate_access,	sendfile,	recvfile },
987772Ssam #ifdef notdef
997772Ssam 	{ "mail",	validate_user,		sendmail,	recvmail },
1007772Ssam #endif
1017772Ssam 	{ 0 }
1027772Ssam };
1037772Ssam 
1047772Ssam /*
1057772Ssam  * Handle initial connection protocol.
1067772Ssam  */
10716372Skarels tftp(tp, size)
1087772Ssam 	struct tftphdr *tp;
1097772Ssam 	int size;
1107772Ssam {
1117772Ssam 	register char *cp;
1127772Ssam 	int first = 1, ecode;
1137772Ssam 	register struct formats *pf;
1147772Ssam 	char *filename, *mode;
1157772Ssam 
1167772Ssam 	filename = cp = tp->th_stuff;
1177772Ssam again:
1187772Ssam 	while (cp < buf + size) {
1197772Ssam 		if (*cp == '\0')
1207772Ssam 			break;
1217772Ssam 		cp++;
1227772Ssam 	}
1237772Ssam 	if (*cp != '\0') {
1247772Ssam 		nak(EBADOP);
1257772Ssam 		exit(1);
1267772Ssam 	}
1277772Ssam 	if (first) {
1287772Ssam 		mode = ++cp;
1297772Ssam 		first = 0;
1307772Ssam 		goto again;
1317772Ssam 	}
1327772Ssam 	for (cp = mode; *cp; cp++)
1337772Ssam 		if (isupper(*cp))
1347772Ssam 			*cp = tolower(*cp);
1357772Ssam 	for (pf = formats; pf->f_mode; pf++)
1367772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
1377772Ssam 			break;
1387772Ssam 	if (pf->f_mode == 0) {
1397772Ssam 		nak(EBADOP);
1407772Ssam 		exit(1);
1417772Ssam 	}
14216372Skarels 	ecode = (*pf->f_validate)(filename, tp->th_opcode);
1437772Ssam 	if (ecode) {
1447772Ssam 		nak(ecode);
1457772Ssam 		exit(1);
1467772Ssam 	}
1477772Ssam 	if (tp->th_opcode == WRQ)
1487772Ssam 		(*pf->f_recv)(pf);
1497772Ssam 	else
1507772Ssam 		(*pf->f_send)(pf);
1517772Ssam 	exit(0);
1527772Ssam }
1537772Ssam 
15416372Skarels int	fd;
15516372Skarels 
1567772Ssam /*
1577772Ssam  * Validate file access.  Since we
1587772Ssam  * have no uid or gid, for now require
1597772Ssam  * file to exist and be publicly
1607772Ssam  * readable/writable.
1617772Ssam  * Note also, full path name must be
1627772Ssam  * given as we have no login directory.
1637772Ssam  */
16416372Skarels validate_access(file, mode)
1657772Ssam 	char *file;
1667772Ssam 	int mode;
1677772Ssam {
1687772Ssam 	struct stat stbuf;
1697772Ssam 
1707772Ssam 	if (*file != '/')
1717772Ssam 		return (EACCESS);
1727772Ssam 	if (stat(file, &stbuf) < 0)
1737772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
1747772Ssam 	if (mode == RRQ) {
1757772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
1767772Ssam 			return (EACCESS);
1777772Ssam 	} else {
1787772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
1797772Ssam 			return (EACCESS);
1807772Ssam 	}
1817772Ssam 	fd = open(file, mode == RRQ ? 0 : 1);
1827772Ssam 	if (fd < 0)
1837772Ssam 		return (errno + 100);
1847772Ssam 	return (0);
1857772Ssam }
1867772Ssam 
18713020Ssam int	timeout;
18813020Ssam jmp_buf	timeoutbuf;
1897772Ssam 
1907772Ssam timer()
1917772Ssam {
19213020Ssam 
19313020Ssam 	timeout += rexmtval;
19413020Ssam 	if (timeout >= maxtimeout)
1957772Ssam 		exit(1);
19613020Ssam 	longjmp(timeoutbuf, 1);
1977772Ssam }
1987772Ssam 
1997772Ssam /*
2007772Ssam  * Send the requested file.
2017772Ssam  */
2027772Ssam sendfile(pf)
2037772Ssam 	struct format *pf;
2047772Ssam {
2057772Ssam 	register struct tftphdr *tp;
2067772Ssam 	register int block = 1, size, n;
2077772Ssam 
20813020Ssam 	signal(SIGALRM, timer);
2097772Ssam 	tp = (struct tftphdr *)buf;
2107772Ssam 	do {
2117772Ssam 		size = read(fd, tp->th_data, SEGSIZE);
2127772Ssam 		if (size < 0) {
2137772Ssam 			nak(errno + 100);
21416372Skarels 			return;
2157772Ssam 		}
2167772Ssam 		tp->th_opcode = htons((u_short)DATA);
2177772Ssam 		tp->th_block = htons((u_short)block);
2187772Ssam 		timeout = 0;
21913020Ssam 		(void) setjmp(timeoutbuf);
22016372Skarels 		if (send(peer, buf, size + 4, 0) != size + 4) {
22116372Skarels 			perror("tftpd: send");
22216372Skarels 			return;
2237772Ssam 		}
22413020Ssam 		do {
22513020Ssam 			alarm(rexmtval);
22616372Skarels 			n = recv(peer, buf, sizeof (buf), 0);
2277772Ssam 			alarm(0);
22813020Ssam 			if (n < 0) {
22916372Skarels 				perror("tftpd: recv");
23016372Skarels 				return;
23113020Ssam 			}
23213020Ssam 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
23313020Ssam 			tp->th_block = ntohs((u_short)tp->th_block);
23413020Ssam 			if (tp->th_opcode == ERROR)
23516372Skarels 				return;
23613020Ssam 		} while (tp->th_opcode != ACK || tp->th_block != block);
2377772Ssam 		block++;
2387772Ssam 	} while (size == SEGSIZE);
2397772Ssam }
2407772Ssam 
2417772Ssam /*
2427772Ssam  * Receive a file.
2437772Ssam  */
2447772Ssam recvfile(pf)
2457772Ssam 	struct format *pf;
2467772Ssam {
2477772Ssam 	register struct tftphdr *tp;
2487772Ssam 	register int block = 0, n, size;
2497772Ssam 
25013020Ssam 	signal(SIGALRM, timer);
2517772Ssam 	tp = (struct tftphdr *)buf;
2527772Ssam 	do {
2537772Ssam 		timeout = 0;
2547772Ssam 		tp->th_opcode = htons((u_short)ACK);
2557772Ssam 		tp->th_block = htons((u_short)block);
2567772Ssam 		block++;
25713020Ssam 		(void) setjmp(timeoutbuf);
25816372Skarels 		if (send(peer, buf, 4, 0) != 4) {
25916372Skarels 			perror("tftpd: send");
26013020Ssam 			goto abort;
2617772Ssam 		}
26213020Ssam 		do {
26313020Ssam 			alarm(rexmtval);
26416372Skarels 			n = recv(peer, buf, sizeof (buf), 0);
2657772Ssam 			alarm(0);
26613020Ssam 			if (n < 0) {
26716372Skarels 				perror("tftpd: recv");
26813020Ssam 				goto abort;
26913020Ssam 			}
27013020Ssam 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
27113020Ssam 			tp->th_block = ntohs((u_short)tp->th_block);
27213020Ssam 			if (tp->th_opcode == ERROR)
27313020Ssam 				goto abort;
27413020Ssam 		} while (tp->th_opcode != DATA || block != tp->th_block);
2757772Ssam 		size = write(fd, tp->th_data, n - 4);
2767772Ssam 		if (size < 0) {
2777772Ssam 			nak(errno + 100);
27813020Ssam 			goto abort;
2797772Ssam 		}
2807772Ssam 	} while (size == SEGSIZE);
28113020Ssam abort:
2827772Ssam 	tp->th_opcode = htons((u_short)ACK);
2837772Ssam 	tp->th_block = htons((u_short)(block));
28416372Skarels 	(void) send(peer, buf, 4, 0);
2857772Ssam }
2867772Ssam 
2877772Ssam struct errmsg {
2887772Ssam 	int	e_code;
2897772Ssam 	char	*e_msg;
2907772Ssam } errmsgs[] = {
2917772Ssam 	{ EUNDEF,	"Undefined error code" },
2927772Ssam 	{ ENOTFOUND,	"File not found" },
2937772Ssam 	{ EACCESS,	"Access violation" },
2947772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
2957772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
2967772Ssam 	{ EBADID,	"Unknown transfer ID" },
2977772Ssam 	{ EEXISTS,	"File already exists" },
2987772Ssam 	{ ENOUSER,	"No such user" },
2997772Ssam 	{ -1,		0 }
3007772Ssam };
3017772Ssam 
3027772Ssam /*
3037772Ssam  * Send a nak packet (error message).
3047772Ssam  * Error code passed in is one of the
3057772Ssam  * standard TFTP codes, or a UNIX errno
3067772Ssam  * offset by 100.
3077772Ssam  */
3087772Ssam nak(error)
3097772Ssam 	int error;
3107772Ssam {
3117772Ssam 	register struct tftphdr *tp;
3127772Ssam 	int length;
3137772Ssam 	register struct errmsg *pe;
3147772Ssam 	extern char *sys_errlist[];
3157772Ssam 
3167772Ssam 	tp = (struct tftphdr *)buf;
3177772Ssam 	tp->th_opcode = htons((u_short)ERROR);
3187772Ssam 	tp->th_code = htons((u_short)error);
3197772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
3207772Ssam 		if (pe->e_code == error)
3217772Ssam 			break;
3227772Ssam 	if (pe->e_code < 0)
3237772Ssam 		pe->e_msg = sys_errlist[error - 100];
3247772Ssam 	strcpy(tp->th_msg, pe->e_msg);
3257772Ssam 	length = strlen(pe->e_msg);
3267772Ssam 	tp->th_msg[length] = '\0';
3277772Ssam 	length += 5;
32816372Skarels 	if (send(peer, buf, length, 0) != length)
3297772Ssam 		perror("nak");
33016372Skarels 	exit(1);
3317772Ssam }
332