xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 8385)
1*8385Ssam /*	tftpd.c	4.3	82/10/08	*/
27772Ssam 
37772Ssam /*
47772Ssam  * Trivial file transfer protocol server.
57772Ssam  */
67772Ssam #include <sys/types.h>
77772Ssam #include <net/in.h>
87772Ssam #include <sys/socket.h>
97772Ssam #include <signal.h>
107772Ssam #include <sys/ioctl.h>
117772Ssam #include <stat.h>
127772Ssam #include <stdio.h>
137772Ssam #include <wait.h>
147772Ssam #include <errno.h>
157772Ssam #include <ctype.h>
16*8385Ssam #include <netdb.h>
177772Ssam #include "tftp.h"
187772Ssam 
197772Ssam extern	int errno;
20*8385Ssam struct	sockaddr_in sin = { AF_INET };
217772Ssam int	f;
227772Ssam int	options;
237772Ssam char	buf[BUFSIZ];
247772Ssam 
257772Ssam main(argc, argv)
267772Ssam 	char *argv[];
277772Ssam {
287772Ssam 	union wait status;
297772Ssam 	struct sockaddr_in from;
307772Ssam 	register struct tftphdr *tp;
317772Ssam 	register int n;
32*8385Ssam 	struct servent *sp;
337772Ssam 
34*8385Ssam 	sp = getservbyname("tftp", "udp");
35*8385Ssam 	if (sp == 0) {
36*8385Ssam 		fprintf(stderr, "tftpd: udp/tftp: unknown service\n");
37*8385Ssam 		exit(1);
38*8385Ssam 	}
39*8385Ssam 	sin.sin_port = htons(sp->s_port);
407772Ssam #ifndef DEBUG
417772Ssam 	if (fork())
427772Ssam 		exit(0);
437772Ssam 	for (f = 0; f < 10; f++)
447772Ssam 		(void) close(f);
457772Ssam 	(void) open("/", 0);
467772Ssam 	(void) dup2(0, 1);
477772Ssam 	(void) dup2(0, 2);
487772Ssam 	{ int t = open("/dev/tty", 2);
497772Ssam 	  if (t >= 0) {
507772Ssam 		ioctl(t, TIOCNOTTY, (char *)0);
517772Ssam 		(void) close(t);
527772Ssam 	  }
537772Ssam 	}
547772Ssam #endif
557772Ssam 	argc--, argv++;
567772Ssam 	if (argc > 0 && !strcmp(argv[0], "-d"))
577772Ssam 		options |= SO_DEBUG;
587772Ssam 	for (;;) {
597772Ssam 		errno = 0;
607772Ssam 		f = socket(SOCK_DGRAM, 0, &sin, options);
617772Ssam 		if (f < 0) {
627772Ssam 			perror("socket");
637772Ssam 			sleep(5);
647772Ssam 			continue;
657772Ssam 		}
667772Ssam again:
677772Ssam 		n = receive(f, &from, buf, sizeof (buf));
687772Ssam 		if (n <= 0) {
697772Ssam 			if (n < 0)
707772Ssam 				perror("receive");
717772Ssam 			goto again;
727772Ssam 		}
737772Ssam 		tp = (struct tftphdr *)buf;
747772Ssam #if vax || pdp11
757772Ssam 		tp->th_opcode = ntohs(tp->th_opcode);
767772Ssam #endif
777772Ssam 		if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
787772Ssam 			if (fork() == 0)
797772Ssam 				tftp(&from, tp, n);
807772Ssam 		(void) close(f);
817772Ssam #ifdef notdef
827772Ssam 		while (wait3(status, WNOHANG, 0) > 0)
837772Ssam #else
847772Ssam 		while (wait3(status, 0, 0) > 0)
857772Ssam 			continue;
867772Ssam 	}
877772Ssam }
887772Ssam 
897772Ssam int	validate_access();
907772Ssam int	sendfile(), recvfile();
917772Ssam 
927772Ssam struct formats {
937772Ssam 	char	*f_mode;
947772Ssam 	int	(*f_validate)();
957772Ssam 	int	(*f_send)();
967772Ssam 	int	(*f_recv)();
977772Ssam } formats[] = {
987772Ssam 	{ "netascii",	validate_access,	sendfile,	recvfile },
997772Ssam 	{ "octet",	validate_access,	sendfile,	recvfile },
1007772Ssam #ifdef notdef
1017772Ssam 	{ "mail",	validate_user,		sendmail,	recvmail },
1027772Ssam #endif
1037772Ssam 	{ 0 }
1047772Ssam };
1057772Ssam 
1067772Ssam int	fd;			/* file being transferred */
1077772Ssam 
1087772Ssam /*
1097772Ssam  * Handle initial connection protocol.
1107772Ssam  */
1117772Ssam tftp(client, tp, size)
1127772Ssam 	struct sockaddr_in *client;
1137772Ssam 	struct tftphdr *tp;
1147772Ssam 	int size;
1157772Ssam {
1167772Ssam 	register char *cp;
1177772Ssam 	int first = 1, ecode;
1187772Ssam 	register struct formats *pf;
1197772Ssam 	char *filename, *mode;
1207772Ssam 
1217772Ssam 	if (connect(f, client) < 0) {
1227772Ssam 		perror("connect");
1237772Ssam 		exit(1);
1247772Ssam 	}
1257772Ssam 	filename = cp = tp->th_stuff;
1267772Ssam again:
1277772Ssam 	while (cp < buf + size) {
1287772Ssam 		if (*cp == '\0')
1297772Ssam 			break;
1307772Ssam 		cp++;
1317772Ssam 	}
1327772Ssam 	if (*cp != '\0') {
1337772Ssam 		nak(EBADOP);
1347772Ssam 		exit(1);
1357772Ssam 	}
1367772Ssam 	if (first) {
1377772Ssam 		mode = ++cp;
1387772Ssam 		first = 0;
1397772Ssam 		goto again;
1407772Ssam 	}
1417772Ssam 	for (cp = mode; *cp; cp++)
1427772Ssam 		if (isupper(*cp))
1437772Ssam 			*cp = tolower(*cp);
1447772Ssam 	for (pf = formats; pf->f_mode; pf++)
1457772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
1467772Ssam 			break;
1477772Ssam 	if (pf->f_mode == 0) {
1487772Ssam 		nak(EBADOP);
1497772Ssam 		exit(1);
1507772Ssam 	}
1517772Ssam 	ecode = (*pf->f_validate)(filename, client, tp->th_opcode);
1527772Ssam 	if (ecode) {
1537772Ssam 		nak(ecode);
1547772Ssam 		exit(1);
1557772Ssam 	}
1567772Ssam 	if (tp->th_opcode == WRQ)
1577772Ssam 		(*pf->f_recv)(pf);
1587772Ssam 	else
1597772Ssam 		(*pf->f_send)(pf);
1607772Ssam 	exit(0);
1617772Ssam }
1627772Ssam 
1637772Ssam /*
1647772Ssam  * Validate file access.  Since we
1657772Ssam  * have no uid or gid, for now require
1667772Ssam  * file to exist and be publicly
1677772Ssam  * readable/writable.
1687772Ssam  * Note also, full path name must be
1697772Ssam  * given as we have no login directory.
1707772Ssam  */
1717772Ssam validate_access(file, client, mode)
1727772Ssam 	char *file;
1737772Ssam 	struct sockaddr_in *client;
1747772Ssam 	int mode;
1757772Ssam {
1767772Ssam 	struct stat stbuf;
1777772Ssam 
1787772Ssam 	if (*file != '/')
1797772Ssam 		return (EACCESS);
1807772Ssam 	if (stat(file, &stbuf) < 0)
1817772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
1827772Ssam 	if (mode == RRQ) {
1837772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
1847772Ssam 			return (EACCESS);
1857772Ssam 	} else {
1867772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
1877772Ssam 			return (EACCESS);
1887772Ssam 	}
1897772Ssam 	fd = open(file, mode == RRQ ? 0 : 1);
1907772Ssam 	if (fd < 0)
1917772Ssam 		return (errno + 100);
1927772Ssam 	return (0);
1937772Ssam }
1947772Ssam 
1957772Ssam int timeout;
1967772Ssam 
1977772Ssam timer()
1987772Ssam {
1997772Ssam 	timeout += TIMEOUT;
2007772Ssam 	if (timeout >= MAXTIMEOUT)
2017772Ssam 		exit(1);
2027772Ssam 	alarm(TIMEOUT);
2037772Ssam }
2047772Ssam 
2057772Ssam /*
2067772Ssam  * Send the requested file.
2077772Ssam  */
2087772Ssam sendfile(pf)
2097772Ssam 	struct format *pf;
2107772Ssam {
2117772Ssam 	register struct tftphdr *tp;
2127772Ssam 	register int block = 1, size, n;
2137772Ssam 
2147772Ssam 	sigset(SIGALRM, timer);
2157772Ssam 	tp = (struct tftphdr *)buf;
2167772Ssam 	do {
2177772Ssam 		size = read(fd, tp->th_data, SEGSIZE);
2187772Ssam 		if (size < 0) {
2197772Ssam 			nak(errno + 100);
2207772Ssam 			break;
2217772Ssam 		}
2227772Ssam 		tp->th_opcode = htons((u_short)DATA);
2237772Ssam 		tp->th_block = htons((u_short)block);
2247772Ssam 		timeout = 0;
2257772Ssam 		alarm(TIMEOUT);
2267772Ssam rexmt:
2277772Ssam 		if (write(f, buf, size + 4) != size + 4) {
2287772Ssam 			perror("send");
2297772Ssam 			break;
2307772Ssam 		}
2317772Ssam again:
2327772Ssam 		n = read(f, buf, sizeof (buf));
2337772Ssam 		if (n <= 0) {
2347772Ssam 			if (n == 0)
2357772Ssam 				goto again;
2367772Ssam 			if (errno == EINTR)
2377772Ssam 				goto rexmt;
2387772Ssam 			alarm(0);
2397772Ssam 			perror("receive");
2407772Ssam 			break;
2417772Ssam 		}
2427772Ssam 		alarm(0);
2437772Ssam #if vax || pdp11
2447772Ssam 		tp->th_opcode = ntohs(tp->th_opcode);
2457772Ssam 		tp->th_block = ntohs(tp->th_block);
2467772Ssam #endif
2477772Ssam 		if (tp->th_opcode == ERROR)
2487772Ssam 			break;
2497772Ssam 		if (tp->th_opcode != ACK || tp->th_block != block)
2507772Ssam 			goto again;
2517772Ssam 		block++;
2527772Ssam 	} while (size == SEGSIZE);
2537772Ssam 	(void) close(fd);
2547772Ssam }
2557772Ssam 
2567772Ssam /*
2577772Ssam  * Receive a file.
2587772Ssam  */
2597772Ssam recvfile(pf)
2607772Ssam 	struct format *pf;
2617772Ssam {
2627772Ssam 	register struct tftphdr *tp;
2637772Ssam 	register int block = 0, n, size;
2647772Ssam 
2657772Ssam 	sigset(SIGALRM, timer);
2667772Ssam 	tp = (struct tftphdr *)buf;
2677772Ssam 	do {
2687772Ssam 		timeout = 0;
2697772Ssam 		alarm(TIMEOUT);
2707772Ssam 		tp->th_opcode = htons((u_short)ACK);
2717772Ssam 		tp->th_block = htons((u_short)block);
2727772Ssam 		block++;
2737772Ssam rexmt:
2747772Ssam 		if (write(f, buf, 4) != 4) {
2757772Ssam 			perror("ack");
2767772Ssam 			break;
2777772Ssam 		}
2787772Ssam again:
2797772Ssam 		n = read(f, buf, sizeof (buf));
2807772Ssam 		if (n <= 0) {
2817772Ssam 			if (n == 0)
2827772Ssam 				goto again;
2837772Ssam 			if (errno == EINTR)
2847772Ssam 				goto rexmt;
2857772Ssam 			alarm(0);
2867772Ssam 			perror("receive");
2877772Ssam 			break;
2887772Ssam 		}
2897772Ssam 		alarm(0);
2907772Ssam #if vax || pdp11
2917772Ssam 		tp->th_opcode = ntohs(tp->th_opcode);
2927772Ssam 		tp->th_block = ntohs(tp->th_block);
2937772Ssam #endif
2947772Ssam 		if (tp->th_opcode == ERROR)
2957772Ssam 			break;
2967772Ssam 		if (tp->th_opcode != DATA || block != tp->th_block)
2977772Ssam 			goto again;
2987772Ssam 		size = write(fd, tp->th_data, n - 4);
2997772Ssam 		if (size < 0) {
3007772Ssam 			nak(errno + 100);
3017772Ssam 			break;
3027772Ssam 		}
3037772Ssam 	} while (size == SEGSIZE);
3047772Ssam 	tp->th_opcode = htons((u_short)ACK);
3057772Ssam 	tp->th_block = htons((u_short)(block));
3067772Ssam 	(void) write(f, buf, 4);
3077772Ssam 	(void) close(fd);
3087772Ssam }
3097772Ssam 
3107772Ssam struct errmsg {
3117772Ssam 	int	e_code;
3127772Ssam 	char	*e_msg;
3137772Ssam } errmsgs[] = {
3147772Ssam 	{ EUNDEF,	"Undefined error code" },
3157772Ssam 	{ ENOTFOUND,	"File not found" },
3167772Ssam 	{ EACCESS,	"Access violation" },
3177772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
3187772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
3197772Ssam 	{ EBADID,	"Unknown transfer ID" },
3207772Ssam 	{ EEXISTS,	"File already exists" },
3217772Ssam 	{ ENOUSER,	"No such user" },
3227772Ssam 	{ -1,		0 }
3237772Ssam };
3247772Ssam 
3257772Ssam /*
3267772Ssam  * Send a nak packet (error message).
3277772Ssam  * Error code passed in is one of the
3287772Ssam  * standard TFTP codes, or a UNIX errno
3297772Ssam  * offset by 100.
3307772Ssam  */
3317772Ssam nak(error)
3327772Ssam 	int error;
3337772Ssam {
3347772Ssam 	register struct tftphdr *tp;
3357772Ssam 	int length;
3367772Ssam 	register struct errmsg *pe;
3377772Ssam 	extern char *sys_errlist[];
3387772Ssam 
3397772Ssam 	tp = (struct tftphdr *)buf;
3407772Ssam 	tp->th_opcode = htons((u_short)ERROR);
3417772Ssam 	tp->th_code = htons((u_short)error);
3427772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
3437772Ssam 		if (pe->e_code == error)
3447772Ssam 			break;
3457772Ssam 	if (pe->e_code < 0)
3467772Ssam 		pe->e_msg = sys_errlist[error - 100];
3477772Ssam 	strcpy(tp->th_msg, pe->e_msg);
3487772Ssam 	length = strlen(pe->e_msg);
3497772Ssam 	tp->th_msg[length] = '\0';
3507772Ssam 	length += 5;
3517772Ssam 	if (write(f, buf, length) != length)
3527772Ssam 		perror("nak");
3537772Ssam }
354