xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 13609)
1*13609Ssam #ifndef lint
2*13609Ssam static char sccsid[] = "@(#)tftpd.c	4.11 (Berkeley) 07/02/83";
3*13609Ssam #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>
11*13609Ssam #include <sys/wait.h>
12*13609Ssam #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>
249220Ssam 
2513020Ssam #define	TIMEOUT		5
2613020Ssam 
277772Ssam extern	int errno;
288385Ssam struct	sockaddr_in sin = { AF_INET };
297772Ssam int	f;
3013020Ssam int	rexmtval = TIMEOUT;
3113020Ssam int	maxtimeout = 5*TIMEOUT;
327772Ssam char	buf[BUFSIZ];
3313020Ssam int	reapchild();
347772Ssam 
357772Ssam main(argc, argv)
367772Ssam 	char *argv[];
377772Ssam {
387772Ssam 	struct sockaddr_in from;
397772Ssam 	register struct tftphdr *tp;
407772Ssam 	register int n;
418385Ssam 	struct servent *sp;
427772Ssam 
438385Ssam 	sp = getservbyname("tftp", "udp");
448385Ssam 	if (sp == 0) {
458385Ssam 		fprintf(stderr, "tftpd: udp/tftp: unknown service\n");
468385Ssam 		exit(1);
478385Ssam 	}
489971Ssam 	sin.sin_port = sp->s_port;
497772Ssam #ifndef DEBUG
507772Ssam 	if (fork())
517772Ssam 		exit(0);
527772Ssam 	for (f = 0; f < 10; f++)
537772Ssam 		(void) close(f);
547772Ssam 	(void) open("/", 0);
557772Ssam 	(void) dup2(0, 1);
567772Ssam 	(void) dup2(0, 2);
577772Ssam 	{ int t = open("/dev/tty", 2);
587772Ssam 	  if (t >= 0) {
597772Ssam 		ioctl(t, TIOCNOTTY, (char *)0);
607772Ssam 		(void) close(t);
617772Ssam 	  }
627772Ssam 	}
637772Ssam #endif
6413020Ssam 	signal(SIGCHLD, reapchild);
657772Ssam 	for (;;) {
669220Ssam 		int fromlen;
679220Ssam 
6813020Ssam 		f = socket(AF_INET, SOCK_DGRAM, 0);
697772Ssam 		if (f < 0) {
709220Ssam 			perror("tftpd: socket");
717772Ssam 			sleep(5);
727772Ssam 			continue;
737772Ssam 		}
7413020Ssam 		if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0)
7513020Ssam 			perror("tftpd: setsockopt (SO_REUSEADDR)");
7613020Ssam 		sleep(1);			/* let child do connect */
779220Ssam 		while (bind(f, (caddr_t)&sin, sizeof (sin), 0) < 0) {
789220Ssam 			perror("tftpd: bind");
799220Ssam 			sleep(5);
809220Ssam 		}
8113020Ssam 		do {
8213020Ssam 			fromlen = sizeof (from);
8313020Ssam 			n = recvfrom(f, buf, sizeof (buf), 0,
8413020Ssam 			    (caddr_t)&from, &fromlen);
8513020Ssam 		} while (n <= 0);
867772Ssam 		tp = (struct tftphdr *)buf;
877772Ssam 		tp->th_opcode = ntohs(tp->th_opcode);
887772Ssam 		if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
897772Ssam 			if (fork() == 0)
907772Ssam 				tftp(&from, tp, n);
917772Ssam 		(void) close(f);
927772Ssam 	}
937772Ssam }
947772Ssam 
9513020Ssam reapchild()
9613020Ssam {
9713020Ssam 	union wait status;
9813020Ssam 
9913020Ssam 	while (wait3(&status, WNOHANG, 0) > 0)
10013020Ssam 		;
10113020Ssam }
10213020Ssam 
1037772Ssam int	validate_access();
1047772Ssam int	sendfile(), recvfile();
1057772Ssam 
1067772Ssam struct formats {
1077772Ssam 	char	*f_mode;
1087772Ssam 	int	(*f_validate)();
1097772Ssam 	int	(*f_send)();
1107772Ssam 	int	(*f_recv)();
1117772Ssam } formats[] = {
1127772Ssam 	{ "netascii",	validate_access,	sendfile,	recvfile },
1137772Ssam 	{ "octet",	validate_access,	sendfile,	recvfile },
1147772Ssam #ifdef notdef
1157772Ssam 	{ "mail",	validate_user,		sendmail,	recvmail },
1167772Ssam #endif
1177772Ssam 	{ 0 }
1187772Ssam };
1197772Ssam 
1207772Ssam int	fd;			/* file being transferred */
1217772Ssam 
1227772Ssam /*
1237772Ssam  * Handle initial connection protocol.
1247772Ssam  */
1257772Ssam tftp(client, tp, size)
1267772Ssam 	struct sockaddr_in *client;
1277772Ssam 	struct tftphdr *tp;
1287772Ssam 	int size;
1297772Ssam {
1307772Ssam 	register char *cp;
1317772Ssam 	int first = 1, ecode;
1327772Ssam 	register struct formats *pf;
1337772Ssam 	char *filename, *mode;
1347772Ssam 
1359220Ssam 	if (connect(f, (caddr_t)client, sizeof (*client), 0) < 0) {
1367772Ssam 		perror("connect");
1377772Ssam 		exit(1);
1387772Ssam 	}
1397772Ssam 	filename = cp = tp->th_stuff;
1407772Ssam again:
1417772Ssam 	while (cp < buf + size) {
1427772Ssam 		if (*cp == '\0')
1437772Ssam 			break;
1447772Ssam 		cp++;
1457772Ssam 	}
1467772Ssam 	if (*cp != '\0') {
1477772Ssam 		nak(EBADOP);
1487772Ssam 		exit(1);
1497772Ssam 	}
1507772Ssam 	if (first) {
1517772Ssam 		mode = ++cp;
1527772Ssam 		first = 0;
1537772Ssam 		goto again;
1547772Ssam 	}
1557772Ssam 	for (cp = mode; *cp; cp++)
1567772Ssam 		if (isupper(*cp))
1577772Ssam 			*cp = tolower(*cp);
1587772Ssam 	for (pf = formats; pf->f_mode; pf++)
1597772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
1607772Ssam 			break;
1617772Ssam 	if (pf->f_mode == 0) {
1627772Ssam 		nak(EBADOP);
1637772Ssam 		exit(1);
1647772Ssam 	}
1657772Ssam 	ecode = (*pf->f_validate)(filename, client, tp->th_opcode);
1667772Ssam 	if (ecode) {
1677772Ssam 		nak(ecode);
1687772Ssam 		exit(1);
1697772Ssam 	}
1707772Ssam 	if (tp->th_opcode == WRQ)
1717772Ssam 		(*pf->f_recv)(pf);
1727772Ssam 	else
1737772Ssam 		(*pf->f_send)(pf);
1747772Ssam 	exit(0);
1757772Ssam }
1767772Ssam 
1777772Ssam /*
1787772Ssam  * Validate file access.  Since we
1797772Ssam  * have no uid or gid, for now require
1807772Ssam  * file to exist and be publicly
1817772Ssam  * readable/writable.
1827772Ssam  * Note also, full path name must be
1837772Ssam  * given as we have no login directory.
1847772Ssam  */
1857772Ssam validate_access(file, client, mode)
1867772Ssam 	char *file;
1877772Ssam 	struct sockaddr_in *client;
1887772Ssam 	int mode;
1897772Ssam {
1907772Ssam 	struct stat stbuf;
1917772Ssam 
1927772Ssam 	if (*file != '/')
1937772Ssam 		return (EACCESS);
1947772Ssam 	if (stat(file, &stbuf) < 0)
1957772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
1967772Ssam 	if (mode == RRQ) {
1977772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
1987772Ssam 			return (EACCESS);
1997772Ssam 	} else {
2007772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
2017772Ssam 			return (EACCESS);
2027772Ssam 	}
2037772Ssam 	fd = open(file, mode == RRQ ? 0 : 1);
2047772Ssam 	if (fd < 0)
2057772Ssam 		return (errno + 100);
2067772Ssam 	return (0);
2077772Ssam }
2087772Ssam 
20913020Ssam int	timeout;
21013020Ssam jmp_buf	timeoutbuf;
2117772Ssam 
2127772Ssam timer()
2137772Ssam {
21413020Ssam 
21513020Ssam 	timeout += rexmtval;
21613020Ssam 	if (timeout >= maxtimeout)
2177772Ssam 		exit(1);
21813020Ssam 	longjmp(timeoutbuf, 1);
2197772Ssam }
2207772Ssam 
2217772Ssam /*
2227772Ssam  * Send the requested file.
2237772Ssam  */
2247772Ssam sendfile(pf)
2257772Ssam 	struct format *pf;
2267772Ssam {
2277772Ssam 	register struct tftphdr *tp;
2287772Ssam 	register int block = 1, size, n;
2297772Ssam 
23013020Ssam 	signal(SIGALRM, timer);
2317772Ssam 	tp = (struct tftphdr *)buf;
2327772Ssam 	do {
2337772Ssam 		size = read(fd, tp->th_data, SEGSIZE);
2347772Ssam 		if (size < 0) {
2357772Ssam 			nak(errno + 100);
23613020Ssam 			goto abort;
2377772Ssam 		}
2387772Ssam 		tp->th_opcode = htons((u_short)DATA);
2397772Ssam 		tp->th_block = htons((u_short)block);
2407772Ssam 		timeout = 0;
24113020Ssam 		(void) setjmp(timeoutbuf);
2427772Ssam 		if (write(f, buf, size + 4) != size + 4) {
24313020Ssam 			perror("tftpd: write");
24413020Ssam 			goto abort;
2457772Ssam 		}
24613020Ssam 		do {
24713020Ssam 			alarm(rexmtval);
24813020Ssam 			n = read(f, buf, sizeof (buf));
2497772Ssam 			alarm(0);
25013020Ssam 			if (n < 0) {
25113020Ssam 				perror("tftpd: read");
25213020Ssam 				goto abort;
25313020Ssam 			}
25413020Ssam 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
25513020Ssam 			tp->th_block = ntohs((u_short)tp->th_block);
25613020Ssam 			if (tp->th_opcode == ERROR)
25713020Ssam 				goto abort;
25813020Ssam 		} while (tp->th_opcode != ACK || tp->th_block != block);
2597772Ssam 		block++;
2607772Ssam 	} while (size == SEGSIZE);
26113020Ssam abort:
2627772Ssam 	(void) close(fd);
2637772Ssam }
2647772Ssam 
2657772Ssam /*
2667772Ssam  * Receive a file.
2677772Ssam  */
2687772Ssam recvfile(pf)
2697772Ssam 	struct format *pf;
2707772Ssam {
2717772Ssam 	register struct tftphdr *tp;
2727772Ssam 	register int block = 0, n, size;
2737772Ssam 
27413020Ssam 	signal(SIGALRM, timer);
2757772Ssam 	tp = (struct tftphdr *)buf;
2767772Ssam 	do {
2777772Ssam 		timeout = 0;
2787772Ssam 		tp->th_opcode = htons((u_short)ACK);
2797772Ssam 		tp->th_block = htons((u_short)block);
2807772Ssam 		block++;
28113020Ssam 		(void) setjmp(timeoutbuf);
2827772Ssam 		if (write(f, buf, 4) != 4) {
28313020Ssam 			perror("tftpd: write");
28413020Ssam 			goto abort;
2857772Ssam 		}
28613020Ssam 		do {
28713020Ssam 			alarm(rexmtval);
28813020Ssam 			n = read(f, buf, sizeof (buf));
2897772Ssam 			alarm(0);
29013020Ssam 			if (n < 0) {
29113020Ssam 				perror("tftpd: read");
29213020Ssam 				goto abort;
29313020Ssam 			}
29413020Ssam 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
29513020Ssam 			tp->th_block = ntohs((u_short)tp->th_block);
29613020Ssam 			if (tp->th_opcode == ERROR)
29713020Ssam 				goto abort;
29813020Ssam 		} while (tp->th_opcode != DATA || block != tp->th_block);
2997772Ssam 		size = write(fd, tp->th_data, n - 4);
3007772Ssam 		if (size < 0) {
3017772Ssam 			nak(errno + 100);
30213020Ssam 			goto abort;
3037772Ssam 		}
3047772Ssam 	} while (size == SEGSIZE);
30513020Ssam abort:
3067772Ssam 	tp->th_opcode = htons((u_short)ACK);
3077772Ssam 	tp->th_block = htons((u_short)(block));
3087772Ssam 	(void) write(f, buf, 4);
3097772Ssam 	(void) close(fd);
3107772Ssam }
3117772Ssam 
3127772Ssam struct errmsg {
3137772Ssam 	int	e_code;
3147772Ssam 	char	*e_msg;
3157772Ssam } errmsgs[] = {
3167772Ssam 	{ EUNDEF,	"Undefined error code" },
3177772Ssam 	{ ENOTFOUND,	"File not found" },
3187772Ssam 	{ EACCESS,	"Access violation" },
3197772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
3207772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
3217772Ssam 	{ EBADID,	"Unknown transfer ID" },
3227772Ssam 	{ EEXISTS,	"File already exists" },
3237772Ssam 	{ ENOUSER,	"No such user" },
3247772Ssam 	{ -1,		0 }
3257772Ssam };
3267772Ssam 
3277772Ssam /*
3287772Ssam  * Send a nak packet (error message).
3297772Ssam  * Error code passed in is one of the
3307772Ssam  * standard TFTP codes, or a UNIX errno
3317772Ssam  * offset by 100.
3327772Ssam  */
3337772Ssam nak(error)
3347772Ssam 	int error;
3357772Ssam {
3367772Ssam 	register struct tftphdr *tp;
3377772Ssam 	int length;
3387772Ssam 	register struct errmsg *pe;
3397772Ssam 	extern char *sys_errlist[];
3407772Ssam 
3417772Ssam 	tp = (struct tftphdr *)buf;
3427772Ssam 	tp->th_opcode = htons((u_short)ERROR);
3437772Ssam 	tp->th_code = htons((u_short)error);
3447772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
3457772Ssam 		if (pe->e_code == error)
3467772Ssam 			break;
3477772Ssam 	if (pe->e_code < 0)
3487772Ssam 		pe->e_msg = sys_errlist[error - 100];
3497772Ssam 	strcpy(tp->th_msg, pe->e_msg);
3507772Ssam 	length = strlen(pe->e_msg);
3517772Ssam 	tp->th_msg[length] = '\0';
3527772Ssam 	length += 5;
3537772Ssam 	if (write(f, buf, length) != length)
3547772Ssam 		perror("nak");
3557772Ssam }
356