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