xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 17188)
113609Ssam #ifndef lint
2*17188Sralph static	char sccsid[] = "@(#)tftpd.c	4.14 (Berkeley) 09/13/84";
313609Ssam #endif
47772Ssam 
57772Ssam /*
67772Ssam  * Trivial file transfer protocol server.
77772Ssam  */
87772Ssam #include <sys/types.h>
97772Ssam #include <sys/socket.h>
109220Ssam #include <sys/ioctl.h>
1113609Ssam #include <sys/wait.h>
1213609Ssam #include <sys/stat.h>
139220Ssam 
149220Ssam #include <netinet/in.h>
159220Ssam 
1612217Ssam #include <arpa/tftp.h>
1712217Ssam 
187772Ssam #include <signal.h>
197772Ssam #include <stdio.h>
207772Ssam #include <errno.h>
217772Ssam #include <ctype.h>
228385Ssam #include <netdb.h>
2313020Ssam #include <setjmp.h>
24*17188Sralph #include <syslog.h>
259220Ssam 
2613020Ssam #define	TIMEOUT		5
2713020Ssam 
287772Ssam extern	int errno;
298385Ssam struct	sockaddr_in sin = { AF_INET };
3016372Skarels int	peer;
3113020Ssam int	rexmtval = TIMEOUT;
3213020Ssam int	maxtimeout = 5*TIMEOUT;
337772Ssam char	buf[BUFSIZ];
3416372Skarels struct	sockaddr_in from;
3516372Skarels int	fromlen;
367772Ssam 
3716372Skarels main()
387772Ssam {
397772Ssam 	register struct tftphdr *tp;
407772Ssam 	register int n;
417772Ssam 
4216372Skarels 	alarm(10);
4316372Skarels 	fromlen = sizeof (from);
4416372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
4516372Skarels 	    (caddr_t)&from, &fromlen);
4616372Skarels 	if (n < 0) {
4716372Skarels 		perror("tftpd: recvfrom");
488385Ssam 		exit(1);
498385Ssam 	}
5016372Skarels 	from.sin_family = AF_INET;
5116372Skarels 	alarm(0);
5216372Skarels 	close(0);
5316372Skarels 	close(1);
5416372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
5516372Skarels 	if (peer < 0) {
56*17188Sralph 		openlog("tftpd", LOG_PID, 0);
57*17188Sralph 		syslog(LOG_ERR, "socket: %m");
5816372Skarels 		exit(1);
597772Ssam 	}
6016372Skarels 	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
61*17188Sralph 		openlog("tftpd", LOG_PID, 0);
62*17188Sralph 		syslog(LOG_ERR, "bind: %m");
6316372Skarels 		exit(1);
6416372Skarels 	}
6516372Skarels 	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
66*17188Sralph 		openlog("tftpd", LOG_PID, 0);
67*17188Sralph 		syslog(LOG_ERR, "connect: %m");
6816372Skarels 		exit(1);
697772Ssam 	}
7016372Skarels 	tp = (struct tftphdr *)buf;
7116372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
7216372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
7316372Skarels 		tftp(tp, n);
7416372Skarels 	exit(1);
757772Ssam }
767772Ssam 
777772Ssam int	validate_access();
787772Ssam int	sendfile(), recvfile();
797772Ssam 
807772Ssam struct formats {
817772Ssam 	char	*f_mode;
827772Ssam 	int	(*f_validate)();
837772Ssam 	int	(*f_send)();
847772Ssam 	int	(*f_recv)();
857772Ssam } formats[] = {
867772Ssam 	{ "netascii",	validate_access,	sendfile,	recvfile },
877772Ssam 	{ "octet",	validate_access,	sendfile,	recvfile },
887772Ssam #ifdef notdef
897772Ssam 	{ "mail",	validate_user,		sendmail,	recvmail },
907772Ssam #endif
917772Ssam 	{ 0 }
927772Ssam };
937772Ssam 
947772Ssam /*
957772Ssam  * Handle initial connection protocol.
967772Ssam  */
9716372Skarels tftp(tp, size)
987772Ssam 	struct tftphdr *tp;
997772Ssam 	int size;
1007772Ssam {
1017772Ssam 	register char *cp;
1027772Ssam 	int first = 1, ecode;
1037772Ssam 	register struct formats *pf;
1047772Ssam 	char *filename, *mode;
1057772Ssam 
1067772Ssam 	filename = cp = tp->th_stuff;
1077772Ssam again:
1087772Ssam 	while (cp < buf + size) {
1097772Ssam 		if (*cp == '\0')
1107772Ssam 			break;
1117772Ssam 		cp++;
1127772Ssam 	}
1137772Ssam 	if (*cp != '\0') {
1147772Ssam 		nak(EBADOP);
1157772Ssam 		exit(1);
1167772Ssam 	}
1177772Ssam 	if (first) {
1187772Ssam 		mode = ++cp;
1197772Ssam 		first = 0;
1207772Ssam 		goto again;
1217772Ssam 	}
1227772Ssam 	for (cp = mode; *cp; cp++)
1237772Ssam 		if (isupper(*cp))
1247772Ssam 			*cp = tolower(*cp);
1257772Ssam 	for (pf = formats; pf->f_mode; pf++)
1267772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
1277772Ssam 			break;
1287772Ssam 	if (pf->f_mode == 0) {
1297772Ssam 		nak(EBADOP);
1307772Ssam 		exit(1);
1317772Ssam 	}
13216372Skarels 	ecode = (*pf->f_validate)(filename, tp->th_opcode);
1337772Ssam 	if (ecode) {
1347772Ssam 		nak(ecode);
1357772Ssam 		exit(1);
1367772Ssam 	}
1377772Ssam 	if (tp->th_opcode == WRQ)
1387772Ssam 		(*pf->f_recv)(pf);
1397772Ssam 	else
1407772Ssam 		(*pf->f_send)(pf);
1417772Ssam 	exit(0);
1427772Ssam }
1437772Ssam 
14416372Skarels int	fd;
14516372Skarels 
1467772Ssam /*
1477772Ssam  * Validate file access.  Since we
1487772Ssam  * have no uid or gid, for now require
1497772Ssam  * file to exist and be publicly
1507772Ssam  * readable/writable.
1517772Ssam  * Note also, full path name must be
1527772Ssam  * given as we have no login directory.
1537772Ssam  */
15416372Skarels validate_access(file, mode)
1557772Ssam 	char *file;
1567772Ssam 	int mode;
1577772Ssam {
1587772Ssam 	struct stat stbuf;
1597772Ssam 
1607772Ssam 	if (*file != '/')
1617772Ssam 		return (EACCESS);
1627772Ssam 	if (stat(file, &stbuf) < 0)
1637772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
1647772Ssam 	if (mode == RRQ) {
1657772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
1667772Ssam 			return (EACCESS);
1677772Ssam 	} else {
1687772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
1697772Ssam 			return (EACCESS);
1707772Ssam 	}
1717772Ssam 	fd = open(file, mode == RRQ ? 0 : 1);
1727772Ssam 	if (fd < 0)
1737772Ssam 		return (errno + 100);
1747772Ssam 	return (0);
1757772Ssam }
1767772Ssam 
17713020Ssam int	timeout;
17813020Ssam jmp_buf	timeoutbuf;
1797772Ssam 
1807772Ssam timer()
1817772Ssam {
18213020Ssam 
18313020Ssam 	timeout += rexmtval;
18413020Ssam 	if (timeout >= maxtimeout)
1857772Ssam 		exit(1);
18613020Ssam 	longjmp(timeoutbuf, 1);
1877772Ssam }
1887772Ssam 
1897772Ssam /*
1907772Ssam  * Send the requested file.
1917772Ssam  */
1927772Ssam sendfile(pf)
1937772Ssam 	struct format *pf;
1947772Ssam {
1957772Ssam 	register struct tftphdr *tp;
1967772Ssam 	register int block = 1, size, n;
1977772Ssam 
19813020Ssam 	signal(SIGALRM, timer);
1997772Ssam 	tp = (struct tftphdr *)buf;
2007772Ssam 	do {
2017772Ssam 		size = read(fd, tp->th_data, SEGSIZE);
2027772Ssam 		if (size < 0) {
2037772Ssam 			nak(errno + 100);
20416372Skarels 			return;
2057772Ssam 		}
2067772Ssam 		tp->th_opcode = htons((u_short)DATA);
2077772Ssam 		tp->th_block = htons((u_short)block);
2087772Ssam 		timeout = 0;
20913020Ssam 		(void) setjmp(timeoutbuf);
21016372Skarels 		if (send(peer, buf, size + 4, 0) != size + 4) {
21116372Skarels 			perror("tftpd: send");
21216372Skarels 			return;
2137772Ssam 		}
21413020Ssam 		do {
21513020Ssam 			alarm(rexmtval);
21616372Skarels 			n = recv(peer, buf, sizeof (buf), 0);
2177772Ssam 			alarm(0);
21813020Ssam 			if (n < 0) {
21916372Skarels 				perror("tftpd: recv");
22016372Skarels 				return;
22113020Ssam 			}
22213020Ssam 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
22313020Ssam 			tp->th_block = ntohs((u_short)tp->th_block);
22413020Ssam 			if (tp->th_opcode == ERROR)
22516372Skarels 				return;
22613020Ssam 		} while (tp->th_opcode != ACK || tp->th_block != block);
2277772Ssam 		block++;
2287772Ssam 	} while (size == SEGSIZE);
2297772Ssam }
2307772Ssam 
2317772Ssam /*
2327772Ssam  * Receive a file.
2337772Ssam  */
2347772Ssam recvfile(pf)
2357772Ssam 	struct format *pf;
2367772Ssam {
2377772Ssam 	register struct tftphdr *tp;
2387772Ssam 	register int block = 0, n, size;
2397772Ssam 
24013020Ssam 	signal(SIGALRM, timer);
2417772Ssam 	tp = (struct tftphdr *)buf;
2427772Ssam 	do {
2437772Ssam 		timeout = 0;
2447772Ssam 		tp->th_opcode = htons((u_short)ACK);
2457772Ssam 		tp->th_block = htons((u_short)block);
2467772Ssam 		block++;
24713020Ssam 		(void) setjmp(timeoutbuf);
24816372Skarels 		if (send(peer, buf, 4, 0) != 4) {
24916372Skarels 			perror("tftpd: send");
25013020Ssam 			goto abort;
2517772Ssam 		}
25213020Ssam 		do {
25313020Ssam 			alarm(rexmtval);
25416372Skarels 			n = recv(peer, buf, sizeof (buf), 0);
2557772Ssam 			alarm(0);
25613020Ssam 			if (n < 0) {
25716372Skarels 				perror("tftpd: recv");
25813020Ssam 				goto abort;
25913020Ssam 			}
26013020Ssam 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
26113020Ssam 			tp->th_block = ntohs((u_short)tp->th_block);
26213020Ssam 			if (tp->th_opcode == ERROR)
26313020Ssam 				goto abort;
26413020Ssam 		} while (tp->th_opcode != DATA || block != tp->th_block);
2657772Ssam 		size = write(fd, tp->th_data, n - 4);
2667772Ssam 		if (size < 0) {
2677772Ssam 			nak(errno + 100);
26813020Ssam 			goto abort;
2697772Ssam 		}
2707772Ssam 	} while (size == SEGSIZE);
27113020Ssam abort:
2727772Ssam 	tp->th_opcode = htons((u_short)ACK);
2737772Ssam 	tp->th_block = htons((u_short)(block));
27416372Skarels 	(void) send(peer, buf, 4, 0);
2757772Ssam }
2767772Ssam 
2777772Ssam struct errmsg {
2787772Ssam 	int	e_code;
2797772Ssam 	char	*e_msg;
2807772Ssam } errmsgs[] = {
2817772Ssam 	{ EUNDEF,	"Undefined error code" },
2827772Ssam 	{ ENOTFOUND,	"File not found" },
2837772Ssam 	{ EACCESS,	"Access violation" },
2847772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
2857772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
2867772Ssam 	{ EBADID,	"Unknown transfer ID" },
2877772Ssam 	{ EEXISTS,	"File already exists" },
2887772Ssam 	{ ENOUSER,	"No such user" },
2897772Ssam 	{ -1,		0 }
2907772Ssam };
2917772Ssam 
2927772Ssam /*
2937772Ssam  * Send a nak packet (error message).
2947772Ssam  * Error code passed in is one of the
2957772Ssam  * standard TFTP codes, or a UNIX errno
2967772Ssam  * offset by 100.
2977772Ssam  */
2987772Ssam nak(error)
2997772Ssam 	int error;
3007772Ssam {
3017772Ssam 	register struct tftphdr *tp;
3027772Ssam 	int length;
3037772Ssam 	register struct errmsg *pe;
3047772Ssam 	extern char *sys_errlist[];
3057772Ssam 
3067772Ssam 	tp = (struct tftphdr *)buf;
3077772Ssam 	tp->th_opcode = htons((u_short)ERROR);
3087772Ssam 	tp->th_code = htons((u_short)error);
3097772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
3107772Ssam 		if (pe->e_code == error)
3117772Ssam 			break;
3127772Ssam 	if (pe->e_code < 0)
3137772Ssam 		pe->e_msg = sys_errlist[error - 100];
3147772Ssam 	strcpy(tp->th_msg, pe->e_msg);
3157772Ssam 	length = strlen(pe->e_msg);
3167772Ssam 	tp->th_msg[length] = '\0';
3177772Ssam 	length += 5;
31816372Skarels 	if (send(peer, buf, length, 0) != length)
3197772Ssam 		perror("nak");
32016372Skarels 	exit(1);
3217772Ssam }
322