xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 13020)
1*13020Ssam /*	tftpd.c	4.9	83/06/12	*/
27772Ssam 
37772Ssam /*
47772Ssam  * Trivial file transfer protocol server.
57772Ssam  */
67772Ssam #include <sys/types.h>
77772Ssam #include <sys/socket.h>
89220Ssam #include <sys/ioctl.h>
99220Ssam 
109220Ssam #include <netinet/in.h>
119220Ssam 
1212217Ssam #include <arpa/tftp.h>
1312217Ssam 
147772Ssam #include <signal.h>
157772Ssam #include <stat.h>
167772Ssam #include <stdio.h>
177772Ssam #include <wait.h>
187772Ssam #include <errno.h>
197772Ssam #include <ctype.h>
208385Ssam #include <netdb.h>
21*13020Ssam #include <setjmp.h>
229220Ssam 
23*13020Ssam #define	DEBUG	1
24*13020Ssam #define	TIMEOUT		5
25*13020Ssam 
267772Ssam extern	int errno;
278385Ssam struct	sockaddr_in sin = { AF_INET };
287772Ssam int	f;
29*13020Ssam int	rexmtval = TIMEOUT;
30*13020Ssam int	maxtimeout = 5*TIMEOUT;
317772Ssam char	buf[BUFSIZ];
32*13020Ssam int	reapchild();
337772Ssam 
347772Ssam main(argc, argv)
357772Ssam 	char *argv[];
367772Ssam {
377772Ssam 	struct sockaddr_in from;
387772Ssam 	register struct tftphdr *tp;
397772Ssam 	register int n;
408385Ssam 	struct servent *sp;
417772Ssam 
428385Ssam 	sp = getservbyname("tftp", "udp");
438385Ssam 	if (sp == 0) {
448385Ssam 		fprintf(stderr, "tftpd: udp/tftp: unknown service\n");
458385Ssam 		exit(1);
468385Ssam 	}
479971Ssam 	sin.sin_port = sp->s_port;
487772Ssam #ifndef DEBUG
497772Ssam 	if (fork())
507772Ssam 		exit(0);
517772Ssam 	for (f = 0; f < 10; f++)
527772Ssam 		(void) close(f);
537772Ssam 	(void) open("/", 0);
547772Ssam 	(void) dup2(0, 1);
557772Ssam 	(void) dup2(0, 2);
567772Ssam 	{ int t = open("/dev/tty", 2);
577772Ssam 	  if (t >= 0) {
587772Ssam 		ioctl(t, TIOCNOTTY, (char *)0);
597772Ssam 		(void) close(t);
607772Ssam 	  }
617772Ssam 	}
627772Ssam #endif
63*13020Ssam 	signal(SIGCHLD, reapchild);
647772Ssam 	for (;;) {
659220Ssam 		int fromlen;
669220Ssam 
67*13020Ssam 		f = socket(AF_INET, SOCK_DGRAM, 0);
687772Ssam 		if (f < 0) {
699220Ssam 			perror("tftpd: socket");
707772Ssam 			sleep(5);
717772Ssam 			continue;
727772Ssam 		}
73*13020Ssam 		if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0)
74*13020Ssam 			perror("tftpd: setsockopt (SO_REUSEADDR)");
75*13020Ssam 		sleep(1);			/* let child do connect */
769220Ssam 		while (bind(f, (caddr_t)&sin, sizeof (sin), 0) < 0) {
779220Ssam 			perror("tftpd: bind");
789220Ssam 			sleep(5);
799220Ssam 		}
80*13020Ssam 		do {
81*13020Ssam 			fromlen = sizeof (from);
82*13020Ssam 			n = recvfrom(f, buf, sizeof (buf), 0,
83*13020Ssam 			    (caddr_t)&from, &fromlen);
84*13020Ssam 		} while (n <= 0);
857772Ssam 		tp = (struct tftphdr *)buf;
867772Ssam 		tp->th_opcode = ntohs(tp->th_opcode);
877772Ssam 		if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
887772Ssam 			if (fork() == 0)
897772Ssam 				tftp(&from, tp, n);
907772Ssam 		(void) close(f);
917772Ssam 	}
927772Ssam }
937772Ssam 
94*13020Ssam reapchild()
95*13020Ssam {
96*13020Ssam 	union wait status;
97*13020Ssam 
98*13020Ssam 	while (wait3(&status, WNOHANG, 0) > 0)
99*13020Ssam 		;
100*13020Ssam }
101*13020Ssam 
1027772Ssam int	validate_access();
1037772Ssam int	sendfile(), recvfile();
1047772Ssam 
1057772Ssam struct formats {
1067772Ssam 	char	*f_mode;
1077772Ssam 	int	(*f_validate)();
1087772Ssam 	int	(*f_send)();
1097772Ssam 	int	(*f_recv)();
1107772Ssam } formats[] = {
1117772Ssam 	{ "netascii",	validate_access,	sendfile,	recvfile },
1127772Ssam 	{ "octet",	validate_access,	sendfile,	recvfile },
1137772Ssam #ifdef notdef
1147772Ssam 	{ "mail",	validate_user,		sendmail,	recvmail },
1157772Ssam #endif
1167772Ssam 	{ 0 }
1177772Ssam };
1187772Ssam 
1197772Ssam int	fd;			/* file being transferred */
1207772Ssam 
1217772Ssam /*
1227772Ssam  * Handle initial connection protocol.
1237772Ssam  */
1247772Ssam tftp(client, tp, size)
1257772Ssam 	struct sockaddr_in *client;
1267772Ssam 	struct tftphdr *tp;
1277772Ssam 	int size;
1287772Ssam {
1297772Ssam 	register char *cp;
1307772Ssam 	int first = 1, ecode;
1317772Ssam 	register struct formats *pf;
1327772Ssam 	char *filename, *mode;
1337772Ssam 
1349220Ssam 	if (connect(f, (caddr_t)client, sizeof (*client), 0) < 0) {
1357772Ssam 		perror("connect");
1367772Ssam 		exit(1);
1377772Ssam 	}
1387772Ssam 	filename = cp = tp->th_stuff;
1397772Ssam again:
1407772Ssam 	while (cp < buf + size) {
1417772Ssam 		if (*cp == '\0')
1427772Ssam 			break;
1437772Ssam 		cp++;
1447772Ssam 	}
1457772Ssam 	if (*cp != '\0') {
1467772Ssam 		nak(EBADOP);
1477772Ssam 		exit(1);
1487772Ssam 	}
1497772Ssam 	if (first) {
1507772Ssam 		mode = ++cp;
1517772Ssam 		first = 0;
1527772Ssam 		goto again;
1537772Ssam 	}
1547772Ssam 	for (cp = mode; *cp; cp++)
1557772Ssam 		if (isupper(*cp))
1567772Ssam 			*cp = tolower(*cp);
1577772Ssam 	for (pf = formats; pf->f_mode; pf++)
1587772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
1597772Ssam 			break;
1607772Ssam 	if (pf->f_mode == 0) {
1617772Ssam 		nak(EBADOP);
1627772Ssam 		exit(1);
1637772Ssam 	}
1647772Ssam 	ecode = (*pf->f_validate)(filename, client, tp->th_opcode);
1657772Ssam 	if (ecode) {
1667772Ssam 		nak(ecode);
1677772Ssam 		exit(1);
1687772Ssam 	}
1697772Ssam 	if (tp->th_opcode == WRQ)
1707772Ssam 		(*pf->f_recv)(pf);
1717772Ssam 	else
1727772Ssam 		(*pf->f_send)(pf);
1737772Ssam 	exit(0);
1747772Ssam }
1757772Ssam 
1767772Ssam /*
1777772Ssam  * Validate file access.  Since we
1787772Ssam  * have no uid or gid, for now require
1797772Ssam  * file to exist and be publicly
1807772Ssam  * readable/writable.
1817772Ssam  * Note also, full path name must be
1827772Ssam  * given as we have no login directory.
1837772Ssam  */
1847772Ssam validate_access(file, client, mode)
1857772Ssam 	char *file;
1867772Ssam 	struct sockaddr_in *client;
1877772Ssam 	int mode;
1887772Ssam {
1897772Ssam 	struct stat stbuf;
1907772Ssam 
1917772Ssam 	if (*file != '/')
1927772Ssam 		return (EACCESS);
1937772Ssam 	if (stat(file, &stbuf) < 0)
1947772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
1957772Ssam 	if (mode == RRQ) {
1967772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
1977772Ssam 			return (EACCESS);
1987772Ssam 	} else {
1997772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
2007772Ssam 			return (EACCESS);
2017772Ssam 	}
2027772Ssam 	fd = open(file, mode == RRQ ? 0 : 1);
2037772Ssam 	if (fd < 0)
2047772Ssam 		return (errno + 100);
2057772Ssam 	return (0);
2067772Ssam }
2077772Ssam 
208*13020Ssam int	timeout;
209*13020Ssam jmp_buf	timeoutbuf;
2107772Ssam 
2117772Ssam timer()
2127772Ssam {
213*13020Ssam 
214*13020Ssam 	timeout += rexmtval;
215*13020Ssam 	if (timeout >= maxtimeout)
2167772Ssam 		exit(1);
217*13020Ssam 	longjmp(timeoutbuf, 1);
2187772Ssam }
2197772Ssam 
2207772Ssam /*
2217772Ssam  * Send the requested file.
2227772Ssam  */
2237772Ssam sendfile(pf)
2247772Ssam 	struct format *pf;
2257772Ssam {
2267772Ssam 	register struct tftphdr *tp;
2277772Ssam 	register int block = 1, size, n;
2287772Ssam 
229*13020Ssam 	signal(SIGALRM, timer);
2307772Ssam 	tp = (struct tftphdr *)buf;
2317772Ssam 	do {
2327772Ssam 		size = read(fd, tp->th_data, SEGSIZE);
2337772Ssam 		if (size < 0) {
2347772Ssam 			nak(errno + 100);
235*13020Ssam 			goto abort;
2367772Ssam 		}
2377772Ssam 		tp->th_opcode = htons((u_short)DATA);
2387772Ssam 		tp->th_block = htons((u_short)block);
2397772Ssam 		timeout = 0;
240*13020Ssam 		(void) setjmp(timeoutbuf);
2417772Ssam 		if (write(f, buf, size + 4) != size + 4) {
242*13020Ssam 			perror("tftpd: write");
243*13020Ssam 			goto abort;
2447772Ssam 		}
245*13020Ssam 		do {
246*13020Ssam 			alarm(rexmtval);
247*13020Ssam 			n = read(f, buf, sizeof (buf));
2487772Ssam 			alarm(0);
249*13020Ssam 			if (n < 0) {
250*13020Ssam 				perror("tftpd: read");
251*13020Ssam 				goto abort;
252*13020Ssam 			}
253*13020Ssam 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
254*13020Ssam 			tp->th_block = ntohs((u_short)tp->th_block);
255*13020Ssam 			if (tp->th_opcode == ERROR)
256*13020Ssam 				goto abort;
257*13020Ssam 		} while (tp->th_opcode != ACK || tp->th_block != block);
2587772Ssam 		block++;
2597772Ssam 	} while (size == SEGSIZE);
260*13020Ssam abort:
2617772Ssam 	(void) close(fd);
2627772Ssam }
2637772Ssam 
2647772Ssam /*
2657772Ssam  * Receive a file.
2667772Ssam  */
2677772Ssam recvfile(pf)
2687772Ssam 	struct format *pf;
2697772Ssam {
2707772Ssam 	register struct tftphdr *tp;
2717772Ssam 	register int block = 0, n, size;
2727772Ssam 
273*13020Ssam 	signal(SIGALRM, timer);
2747772Ssam 	tp = (struct tftphdr *)buf;
2757772Ssam 	do {
2767772Ssam 		timeout = 0;
2777772Ssam 		tp->th_opcode = htons((u_short)ACK);
2787772Ssam 		tp->th_block = htons((u_short)block);
2797772Ssam 		block++;
280*13020Ssam 		(void) setjmp(timeoutbuf);
2817772Ssam 		if (write(f, buf, 4) != 4) {
282*13020Ssam 			perror("tftpd: write");
283*13020Ssam 			goto abort;
2847772Ssam 		}
285*13020Ssam 		do {
286*13020Ssam 			alarm(rexmtval);
287*13020Ssam 			n = read(f, buf, sizeof (buf));
2887772Ssam 			alarm(0);
289*13020Ssam 			if (n < 0) {
290*13020Ssam 				perror("tftpd: read");
291*13020Ssam 				goto abort;
292*13020Ssam 			}
293*13020Ssam 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
294*13020Ssam 			tp->th_block = ntohs((u_short)tp->th_block);
295*13020Ssam 			if (tp->th_opcode == ERROR)
296*13020Ssam 				goto abort;
297*13020Ssam 		} while (tp->th_opcode != DATA || block != tp->th_block);
2987772Ssam 		size = write(fd, tp->th_data, n - 4);
2997772Ssam 		if (size < 0) {
3007772Ssam 			nak(errno + 100);
301*13020Ssam 			goto abort;
3027772Ssam 		}
3037772Ssam 	} while (size == SEGSIZE);
304*13020Ssam abort:
3057772Ssam 	tp->th_opcode = htons((u_short)ACK);
3067772Ssam 	tp->th_block = htons((u_short)(block));
3077772Ssam 	(void) write(f, buf, 4);
3087772Ssam 	(void) close(fd);
3097772Ssam }
3107772Ssam 
3117772Ssam struct errmsg {
3127772Ssam 	int	e_code;
3137772Ssam 	char	*e_msg;
3147772Ssam } errmsgs[] = {
3157772Ssam 	{ EUNDEF,	"Undefined error code" },
3167772Ssam 	{ ENOTFOUND,	"File not found" },
3177772Ssam 	{ EACCESS,	"Access violation" },
3187772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
3197772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
3207772Ssam 	{ EBADID,	"Unknown transfer ID" },
3217772Ssam 	{ EEXISTS,	"File already exists" },
3227772Ssam 	{ ENOUSER,	"No such user" },
3237772Ssam 	{ -1,		0 }
3247772Ssam };
3257772Ssam 
3267772Ssam /*
3277772Ssam  * Send a nak packet (error message).
3287772Ssam  * Error code passed in is one of the
3297772Ssam  * standard TFTP codes, or a UNIX errno
3307772Ssam  * offset by 100.
3317772Ssam  */
3327772Ssam nak(error)
3337772Ssam 	int error;
3347772Ssam {
3357772Ssam 	register struct tftphdr *tp;
3367772Ssam 	int length;
3377772Ssam 	register struct errmsg *pe;
3387772Ssam 	extern char *sys_errlist[];
3397772Ssam 
3407772Ssam 	tp = (struct tftphdr *)buf;
3417772Ssam 	tp->th_opcode = htons((u_short)ERROR);
3427772Ssam 	tp->th_code = htons((u_short)error);
3437772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
3447772Ssam 		if (pe->e_code == error)
3457772Ssam 			break;
3467772Ssam 	if (pe->e_code < 0)
3477772Ssam 		pe->e_msg = sys_errlist[error - 100];
3487772Ssam 	strcpy(tp->th_msg, pe->e_msg);
3497772Ssam 	length = strlen(pe->e_msg);
3507772Ssam 	tp->th_msg[length] = '\0';
3517772Ssam 	length += 5;
3527772Ssam 	if (write(f, buf, length) != length)
3537772Ssam 		perror("nak");
3547772Ssam }
355