xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 26108)
121183Sdist /*
221183Sdist  * Copyright (c) 1983 Regents of the University of California.
321183Sdist  * All rights reserved.  The Berkeley software License Agreement
421183Sdist  * specifies the terms and conditions for redistribution.
521183Sdist  */
621183Sdist 
713609Ssam #ifndef lint
821183Sdist char copyright[] =
921183Sdist "@(#) Copyright (c) 1983 Regents of the University of California.\n\
1021183Sdist  All rights reserved.\n";
1121183Sdist #endif not lint
127772Ssam 
1321183Sdist #ifndef lint
14*26108Sminshall static char sccsid[] = "@(#)tftpd.c	5.3 (Berkeley) 02/07/86";
1521183Sdist #endif not lint
1621183Sdist 
17*26108Sminshall 
187772Ssam /*
197772Ssam  * Trivial file transfer protocol server.
20*26108Sminshall  *
21*26108Sminshall  * This version includes many modifications by Jim Guyton <guyton@rand-unix>
227772Ssam  */
23*26108Sminshall 
247772Ssam #include <sys/types.h>
257772Ssam #include <sys/socket.h>
269220Ssam #include <sys/ioctl.h>
2713609Ssam #include <sys/wait.h>
2813609Ssam #include <sys/stat.h>
299220Ssam 
309220Ssam #include <netinet/in.h>
319220Ssam 
3212217Ssam #include <arpa/tftp.h>
3312217Ssam 
347772Ssam #include <signal.h>
357772Ssam #include <stdio.h>
367772Ssam #include <errno.h>
377772Ssam #include <ctype.h>
388385Ssam #include <netdb.h>
3913020Ssam #include <setjmp.h>
4017188Sralph #include <syslog.h>
419220Ssam 
4213020Ssam #define	TIMEOUT		5
4313020Ssam 
447772Ssam extern	int errno;
458385Ssam struct	sockaddr_in sin = { AF_INET };
4616372Skarels int	peer;
4713020Ssam int	rexmtval = TIMEOUT;
4813020Ssam int	maxtimeout = 5*TIMEOUT;
49*26108Sminshall 
50*26108Sminshall #define	PKTSIZE	SEGSIZE+4
51*26108Sminshall char	buf[PKTSIZE];
52*26108Sminshall char	ackbuf[PKTSIZE];
5316372Skarels struct	sockaddr_in from;
5416372Skarels int	fromlen;
557772Ssam 
5616372Skarels main()
577772Ssam {
587772Ssam 	register struct tftphdr *tp;
597772Ssam 	register int n;
607772Ssam 
6124852Seric 	openlog("tftpd", LOG_PID, LOG_DAEMON);
6216372Skarels 	alarm(10);
6316372Skarels 	fromlen = sizeof (from);
6416372Skarels 	n = recvfrom(0, buf, sizeof (buf), 0,
6516372Skarels 	    (caddr_t)&from, &fromlen);
6616372Skarels 	if (n < 0) {
6716372Skarels 		perror("tftpd: recvfrom");
688385Ssam 		exit(1);
698385Ssam 	}
7016372Skarels 	from.sin_family = AF_INET;
7116372Skarels 	alarm(0);
7216372Skarels 	close(0);
7316372Skarels 	close(1);
7416372Skarels 	peer = socket(AF_INET, SOCK_DGRAM, 0);
7516372Skarels 	if (peer < 0) {
7617188Sralph 		syslog(LOG_ERR, "socket: %m");
7716372Skarels 		exit(1);
787772Ssam 	}
7916372Skarels 	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
8017188Sralph 		syslog(LOG_ERR, "bind: %m");
8116372Skarels 		exit(1);
8216372Skarels 	}
8316372Skarels 	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
8417188Sralph 		syslog(LOG_ERR, "connect: %m");
8516372Skarels 		exit(1);
867772Ssam 	}
8716372Skarels 	tp = (struct tftphdr *)buf;
8816372Skarels 	tp->th_opcode = ntohs(tp->th_opcode);
8916372Skarels 	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
9016372Skarels 		tftp(tp, n);
9116372Skarels 	exit(1);
927772Ssam }
937772Ssam 
947772Ssam int	validate_access();
957772Ssam int	sendfile(), recvfile();
967772Ssam 
977772Ssam struct formats {
987772Ssam 	char	*f_mode;
997772Ssam 	int	(*f_validate)();
1007772Ssam 	int	(*f_send)();
1017772Ssam 	int	(*f_recv)();
102*26108Sminshall 	int	f_convert;
1037772Ssam } formats[] = {
104*26108Sminshall 	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
105*26108Sminshall 	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
1067772Ssam #ifdef notdef
107*26108Sminshall 	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
1087772Ssam #endif
1097772Ssam 	{ 0 }
1107772Ssam };
1117772Ssam 
1127772Ssam /*
1137772Ssam  * Handle initial connection protocol.
1147772Ssam  */
11516372Skarels tftp(tp, size)
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 
1247772Ssam 	filename = cp = tp->th_stuff;
1257772Ssam again:
1267772Ssam 	while (cp < buf + size) {
1277772Ssam 		if (*cp == '\0')
1287772Ssam 			break;
1297772Ssam 		cp++;
1307772Ssam 	}
1317772Ssam 	if (*cp != '\0') {
1327772Ssam 		nak(EBADOP);
1337772Ssam 		exit(1);
1347772Ssam 	}
1357772Ssam 	if (first) {
1367772Ssam 		mode = ++cp;
1377772Ssam 		first = 0;
1387772Ssam 		goto again;
1397772Ssam 	}
1407772Ssam 	for (cp = mode; *cp; cp++)
1417772Ssam 		if (isupper(*cp))
1427772Ssam 			*cp = tolower(*cp);
1437772Ssam 	for (pf = formats; pf->f_mode; pf++)
1447772Ssam 		if (strcmp(pf->f_mode, mode) == 0)
1457772Ssam 			break;
1467772Ssam 	if (pf->f_mode == 0) {
1477772Ssam 		nak(EBADOP);
1487772Ssam 		exit(1);
1497772Ssam 	}
15016372Skarels 	ecode = (*pf->f_validate)(filename, tp->th_opcode);
1517772Ssam 	if (ecode) {
1527772Ssam 		nak(ecode);
1537772Ssam 		exit(1);
1547772Ssam 	}
1557772Ssam 	if (tp->th_opcode == WRQ)
1567772Ssam 		(*pf->f_recv)(pf);
1577772Ssam 	else
1587772Ssam 		(*pf->f_send)(pf);
1597772Ssam 	exit(0);
1607772Ssam }
1617772Ssam 
16216372Skarels 
163*26108Sminshall FILE *file;
164*26108Sminshall 
1657772Ssam /*
1667772Ssam  * Validate file access.  Since we
1677772Ssam  * have no uid or gid, for now require
1687772Ssam  * file to exist and be publicly
1697772Ssam  * readable/writable.
1707772Ssam  * Note also, full path name must be
1717772Ssam  * given as we have no login directory.
1727772Ssam  */
173*26108Sminshall validate_access(filename, mode)
174*26108Sminshall 	char *filename;
1757772Ssam 	int mode;
1767772Ssam {
1777772Ssam 	struct stat stbuf;
178*26108Sminshall 	int	fd;
1797772Ssam 
180*26108Sminshall 	if (*filename != '/')
1817772Ssam 		return (EACCESS);
182*26108Sminshall 	if (stat(filename, &stbuf) < 0)
1837772Ssam 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
1847772Ssam 	if (mode == RRQ) {
1857772Ssam 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
1867772Ssam 			return (EACCESS);
1877772Ssam 	} else {
1887772Ssam 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
1897772Ssam 			return (EACCESS);
1907772Ssam 	}
191*26108Sminshall 	fd = open(filename, mode == RRQ ? 0 : 1);
1927772Ssam 	if (fd < 0)
1937772Ssam 		return (errno + 100);
194*26108Sminshall 	file = fdopen(fd, (mode == RRQ)? "r":"w");
195*26108Sminshall 	if (file == NULL) {
196*26108Sminshall 		return errno+100;
197*26108Sminshall 	}
1987772Ssam 	return (0);
1997772Ssam }
2007772Ssam 
20113020Ssam int	timeout;
20213020Ssam jmp_buf	timeoutbuf;
2037772Ssam 
2047772Ssam timer()
2057772Ssam {
20613020Ssam 
20713020Ssam 	timeout += rexmtval;
20813020Ssam 	if (timeout >= maxtimeout)
2097772Ssam 		exit(1);
21013020Ssam 	longjmp(timeoutbuf, 1);
2117772Ssam }
2127772Ssam 
2137772Ssam /*
2147772Ssam  * Send the requested file.
2157772Ssam  */
2167772Ssam sendfile(pf)
217*26108Sminshall 	struct formats *pf;
2187772Ssam {
219*26108Sminshall 	struct tftphdr *dp, *r_init();
220*26108Sminshall 	register struct tftphdr *ap;    /* ack packet */
2217772Ssam 	register int block = 1, size, n;
2227772Ssam 
22313020Ssam 	signal(SIGALRM, timer);
224*26108Sminshall 	dp = r_init();
225*26108Sminshall 	ap = (struct tftphdr *)ackbuf;
2267772Ssam 	do {
227*26108Sminshall 		size = readit(file, &dp, pf->f_convert);
2287772Ssam 		if (size < 0) {
2297772Ssam 			nak(errno + 100);
230*26108Sminshall 			goto abort;
2317772Ssam 		}
232*26108Sminshall 		dp->th_opcode = htons((u_short)DATA);
233*26108Sminshall 		dp->th_block = htons((u_short)block);
2347772Ssam 		timeout = 0;
23513020Ssam 		(void) setjmp(timeoutbuf);
236*26108Sminshall 
237*26108Sminshall 		if (send(peer, dp, size + 4, 0) != size + 4) {
238*26108Sminshall 			perror("tftpd: write");
239*26108Sminshall 			goto abort;
2407772Ssam 		}
241*26108Sminshall 		read_ahead(file, pf->f_convert);
24213020Ssam 		do {
243*26108Sminshall 			alarm(rexmtval);        /* read the ack */
244*26108Sminshall 			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
2457772Ssam 			alarm(0);
24613020Ssam 			if (n < 0) {
247*26108Sminshall 				perror("tftpd: read");
248*26108Sminshall 				goto abort;
24913020Ssam 			}
250*26108Sminshall 			ap->th_opcode = ntohs((u_short)ap->th_opcode);
251*26108Sminshall 			ap->th_block = ntohs((u_short)ap->th_block);
252*26108Sminshall 
253*26108Sminshall 			if (ap->th_opcode == ERROR)
254*26108Sminshall 				goto abort;
255*26108Sminshall 
256*26108Sminshall 		}  while (ap->th_opcode != ACK || ap->th_block != block);
2577772Ssam 		block++;
2587772Ssam 	} while (size == SEGSIZE);
259*26108Sminshall abort:
260*26108Sminshall 	(void) fclose(file);
2617772Ssam }
2627772Ssam 
263*26108Sminshall justquit()
264*26108Sminshall {
265*26108Sminshall 	exit(0);
266*26108Sminshall }
267*26108Sminshall 
268*26108Sminshall 
2697772Ssam /*
2707772Ssam  * Receive a file.
2717772Ssam  */
2727772Ssam recvfile(pf)
273*26108Sminshall 	struct formats *pf;
2747772Ssam {
275*26108Sminshall 	struct tftphdr *dp, *w_init();
276*26108Sminshall 	register struct tftphdr *ap;    /* ack buffer */
2777772Ssam 	register int block = 0, n, size;
2787772Ssam 
27913020Ssam 	signal(SIGALRM, timer);
280*26108Sminshall 	dp = w_init();
281*26108Sminshall 	ap = (struct tftphdr *)ackbuf;
2827772Ssam 	do {
2837772Ssam 		timeout = 0;
284*26108Sminshall 		ap->th_opcode = htons((u_short)ACK);
285*26108Sminshall 		ap->th_block = htons((u_short)block);
2867772Ssam 		block++;
28713020Ssam 		(void) setjmp(timeoutbuf);
288*26108Sminshall send_ack:
289*26108Sminshall 		if (send(peer, ackbuf, 4, 0) != 4) {
290*26108Sminshall 			perror("tftpd: write");
29113020Ssam 			goto abort;
2927772Ssam 		}
293*26108Sminshall 		write_behind(file, pf->f_convert);
294*26108Sminshall 		for ( ; ; ) {
29513020Ssam 			alarm(rexmtval);
296*26108Sminshall 			n = recv(peer, dp, PKTSIZE, 0);
2977772Ssam 			alarm(0);
298*26108Sminshall 			if (n < 0) {            /* really? */
299*26108Sminshall 				perror("tftpd: read");
30013020Ssam 				goto abort;
30113020Ssam 			}
302*26108Sminshall 			dp->th_opcode = ntohs((u_short)dp->th_opcode);
303*26108Sminshall 			dp->th_block = ntohs((u_short)dp->th_block);
304*26108Sminshall 			if (dp->th_opcode == ERROR)
30513020Ssam 				goto abort;
306*26108Sminshall 			if (dp->th_opcode == DATA) {
307*26108Sminshall 				if (dp->th_block == block) {
308*26108Sminshall 					break;   /* normal */
309*26108Sminshall 				}
310*26108Sminshall 				if (dp->th_block == (block-1))
311*26108Sminshall 					goto send_ack;          /* rexmit */
312*26108Sminshall 			}
313*26108Sminshall 		}
314*26108Sminshall 		/*  size = write(file, dp->th_data, n - 4); */
315*26108Sminshall 		size = writeit(file, &dp, n - 4, pf->f_convert);
316*26108Sminshall 		if (size != (n-4)) {                    /* ahem */
317*26108Sminshall 			if (size < 0) nak(errno + 100);
318*26108Sminshall 			else nak(ENOSPACE);
31913020Ssam 			goto abort;
3207772Ssam 		}
3217772Ssam 	} while (size == SEGSIZE);
322*26108Sminshall 	write_behind(file, pf->f_convert);
323*26108Sminshall 	(void) fclose(file);            /* close data file */
324*26108Sminshall 
325*26108Sminshall 	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
326*26108Sminshall 	ap->th_block = htons((u_short)(block));
327*26108Sminshall 	(void) send(peer, ackbuf, 4, 0);
328*26108Sminshall 
329*26108Sminshall 	signal(SIGALRM, justquit);      /* just quit on timeout */
330*26108Sminshall 	alarm(rexmtval);
331*26108Sminshall 	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
332*26108Sminshall 	alarm(0);
333*26108Sminshall 	if (n >= 4 &&                   /* if read some data */
334*26108Sminshall 	    dp->th_opcode == DATA &&    /* and got a data block */
335*26108Sminshall 	    block == dp->th_block) {	/* then my last ack was lost */
336*26108Sminshall 		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
337*26108Sminshall 	}
33813020Ssam abort:
339*26108Sminshall 	return;
3407772Ssam }
3417772Ssam 
3427772Ssam struct errmsg {
3437772Ssam 	int	e_code;
3447772Ssam 	char	*e_msg;
3457772Ssam } errmsgs[] = {
3467772Ssam 	{ EUNDEF,	"Undefined error code" },
3477772Ssam 	{ ENOTFOUND,	"File not found" },
3487772Ssam 	{ EACCESS,	"Access violation" },
3497772Ssam 	{ ENOSPACE,	"Disk full or allocation exceeded" },
3507772Ssam 	{ EBADOP,	"Illegal TFTP operation" },
3517772Ssam 	{ EBADID,	"Unknown transfer ID" },
3527772Ssam 	{ EEXISTS,	"File already exists" },
3537772Ssam 	{ ENOUSER,	"No such user" },
3547772Ssam 	{ -1,		0 }
3557772Ssam };
3567772Ssam 
3577772Ssam /*
3587772Ssam  * Send a nak packet (error message).
3597772Ssam  * Error code passed in is one of the
3607772Ssam  * standard TFTP codes, or a UNIX errno
3617772Ssam  * offset by 100.
3627772Ssam  */
3637772Ssam nak(error)
3647772Ssam 	int error;
3657772Ssam {
3667772Ssam 	register struct tftphdr *tp;
3677772Ssam 	int length;
3687772Ssam 	register struct errmsg *pe;
3697772Ssam 	extern char *sys_errlist[];
3707772Ssam 
3717772Ssam 	tp = (struct tftphdr *)buf;
3727772Ssam 	tp->th_opcode = htons((u_short)ERROR);
3737772Ssam 	tp->th_code = htons((u_short)error);
3747772Ssam 	for (pe = errmsgs; pe->e_code >= 0; pe++)
3757772Ssam 		if (pe->e_code == error)
3767772Ssam 			break;
377*26108Sminshall 	if (pe->e_code < 0) {
3787772Ssam 		pe->e_msg = sys_errlist[error - 100];
379*26108Sminshall 		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
380*26108Sminshall 	}
3817772Ssam 	strcpy(tp->th_msg, pe->e_msg);
3827772Ssam 	length = strlen(pe->e_msg);
3837772Ssam 	tp->th_msg[length] = '\0';
3847772Ssam 	length += 5;
38516372Skarels 	if (send(peer, buf, length, 0) != length)
3867772Ssam 		perror("nak");
3877772Ssam }
388