File:  [Power 6/32 Unix Tahoe 4.2BSD] / cci / usr / src / etc / tftpd.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Sun Jul 28 12:24:26 2019 UTC (6 years, 11 months ago) by root
Branches: bsd, MAIN
CVS tags: v121, HEAD
Power 6/32 Unix version 1.21

#ifndef lint
static char sccsid[] = "@(#)tftpd.c	4.11 (Berkeley) 7/2/83";
#endif

/*
 * Trivial file transfer protocol server.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/stat.h>

#include <netinet/in.h>

#include <arpa/tftp.h>

#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <netdb.h>
#include <setjmp.h>

#define	TIMEOUT		5

extern	int errno;
extern	int sys_nerr;
extern	char *sys_errlist[];

struct	in_addr	my_machine_addr;
struct	sockaddr_in sin = { AF_INET };
int	f;
int	rexmtval = TIMEOUT;
int	maxtimeout = 5*TIMEOUT;
char	buf[BUFSIZ];
int	reapchild();

char	*inet_ntoa();
/*---------------------------------------------------------
 * Changes
 *
 *  	. change declaration of tftp header for compiler independence.
 *
 *      . truncate the file after file transfer completes.
 *
 *      . the daemon keeps its master socket; it creates a new socket
 * 	  and hands this socket to its children. In the old implementation,
 *	  the daemon hands the socket to its child, and creates a new socket;
 *	  therefore, requests queued at old socket will be discarded, and
 *	  requests arriving before the daemon successfully creates a new
 *	  socket are also discarded.
 *
 *	. change userid and groupid to -2 before verifying the access
 *	  rights. Temporary Fix Until An Official Policy Is Established.
 *
 *	. change perror() to macro PERROR() to ensure that error messages
 *	  come to the console.
 *
 *	. let the system assign address to the child daemon.
 *--------------------------------------------------------*/
#define	PERROR(X) {							\
			int	cfd;					\
			if ((cfd=open("/dev/console",2)) >=0) {		\
				write(cfd,X,strlen(X));			\
				write(cfd,": ", 2);			\
				if (errno >=0 && errno <sys_nerr)	\
					write(cfd,sys_errlist[errno],	\
						strlen(sys_errlist[errno]));\
				write(cfd,"\n",1);			\
				close(cfd);				\
			}						\
		}
	
main(argc, argv)
	char *argv[];
{
	struct sockaddr_in from;
	register struct tftphdr *tp;
	register int n;
	int	bindstat = -1;
	struct servent *sp;

	get_myaddrs();

	sp = getservbyname("tftp", "udp");
	if (sp == 0) {
		fprintf(stderr, "tftpd: udp/tftp: unknown service\n");
		exit(1);
	}
	sin.sin_port = sp->s_port;
#ifndef DEBUG
	if (fork())
		exit(0);
	for (f = 0; f < 10; f++)
		(void) close(f);
	(void) open("/", 0);
	(void) dup2(0, 1);
	(void) dup2(0, 2);
	{ int t = open("/dev/tty", 2);
	  if (t >= 0) {
		ioctl(t, TIOCNOTTY, (char *)0);
		(void) close(t);
	  }
	}
#endif
	signal(SIGCHLD, reapchild);
	do {
		f = socket(AF_INET, SOCK_DGRAM, 0);
		if (f < 0) {
			PERROR("tftpd: socket");
			sleep(5);
		}
	} while (f<0);
	if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0)
		PERROR("tftpd: setsockopt (SO_REUSEADDR)");
	for (n = 0; n<25; n++) {
		if ((bindstat = bind(f, (caddr_t)&sin, sizeof (sin), 0))>= 0) {
			break;
		}
		else {
			PERROR("tftpd: bind");
			sleep(5);
		}
	}
	if (bindstat <0) {
		PERROR("tftpd: unable to bind. Terminated.\n");
		exit(1);
	}

	for (;;) {
		int fromlen;

		do {
			
			fromlen = sizeof (from);
			from.sin_family = AF_INET;
			n = recvfrom(f, buf, sizeof (buf), 0,
			    (caddr_t)&from, &fromlen);
#ifdef DEBUG
			fprintf(stderr,"tftpd: got request fr %s %d.\n",
				inet_ntoa(from.sin_addr), from.sin_port);
#endif
		} while (n <= 0);

		tp = (struct tftphdr *)buf;
		tp->th_opcode = ntohs(tp->th_opcode);
		if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) {
#ifdef DEBUG
			fprintf(stderr,"tftpd: fork a child.\n");
#endif
			if (fork() == 0) {
				close(f);
				do {
					f = socket(AF_INET, SOCK_DGRAM, 0);
					if (f < 0) {
						PERROR("tftpd: socket(child)");
						sleep(5);
					}
				} while (f<0);

				if (setsockopt(f, SOL_SOCKET, 
						SO_REUSEADDR, 0, 0) < 0)
					PERROR("tftpd: setsockopt(REUSEADDR)");

				/* let the system assign the address */
				sin.sin_addr.s_addr = 0;
				sin.sin_port = 0;
				while (bind(f, 
					(caddr_t)&sin, sizeof (sin), 0) < 0) {
					PERROR("tftpd: bind (child)");
					sleep(5);
				}
				tftp(&from, tp, n);
			}
		}
	}
}

reapchild()
{
	union wait status;

	while (wait3(&status, WNOHANG, 0) > 0)
		;
}

int	validate_access();
int	sendfile(), recvfile();

struct formats {
	char	*f_mode;
	int	(*f_validate)();
	int	(*f_send)();
	int	(*f_recv)();
} formats[] = {
	{ "netascii",	validate_access,	sendfile,	recvfile },
	{ "octet",	validate_access,	sendfile,	recvfile },
#ifdef notdef
	{ "mail",	validate_user,		sendmail,	recvmail },
#endif
	{ 0 }
};

FILE	*fd;			/* file being transferred */

/*
 * Handle initial connection protocol.
 */
tftp(client, tp, size)
	struct sockaddr_in *client;
	struct tftphdr *tp;
	int size;
{
	register char *cp;
	int first = 1, ecode;
	register struct formats *pf;
	char *filename, *mode;

#ifdef DEBUG
	fprintf(stderr,"tftpd:sockfd %d, connect to %s %d\n",
		f, inet_ntoa(client->sin_addr), client->sin_port);
#endif
	if (connect(f, (caddr_t)client, sizeof (*client), 0) < 0) {
		PERROR("tftpd:connect");
		exit(1);
	}
#ifdef DEBUG
	{
	struct sockaddr_in myname;
	struct sockaddr_in peername;
	int	t;
	int	err = 0;

	myname.sin_family = AF_INET;
	t = sizeof(myname);
	if (getsockname(f, (caddr_t)&myname, &t) < 0) {
		PERROR("tftpd:getsockname");
		err = 1;
	}
	if (err!= 1)
		fprintf(stderr,"tftpd:myname is %s %d\n",
			inet_ntoa(myname.sin_addr),
			myname.sin_port);

	peername.sin_family = AF_INET;
	t = sizeof(peername);
	if (getpeername(f, (caddr_t)&peername, &t) < 0) {
		PERROR("tftpd:getpeername");
		err = 2;
	}
	if (err!=2)
		fprintf(stderr,"tftpd:peername is %s %d\n",
			inet_ntoa(peername.sin_addr),
			peername.sin_port);
	if (err)
		exit(1);
	}
#endif
	filename = cp = tp->th_stuff;
again:
	while (cp < buf + size) {
		if (*cp == '\0')
			break;
		cp++;
	}
	if (*cp != '\0') {
		nak(EBADOP);
		exit(1);
	}
	if (first) {
		mode = ++cp;
		first = 0;
		goto again;
	}
	for (cp = mode; *cp; cp++)
		if (isupper(*cp))
			*cp = tolower(*cp);
	for (pf = formats; pf->f_mode; pf++)
		if (strcmp(pf->f_mode, mode) == 0)
			break;
	if (pf->f_mode == 0) {
		nak(EBADOP);
		exit(1);
	}
	ecode = (*pf->f_validate)(filename, client, tp->th_opcode);
	if (ecode) {
		nak(ecode);
		exit(1);
	}
	if (tp->th_opcode == WRQ)
		(*pf->f_recv)(pf);
	else
		(*pf->f_send)(pf);
	exit(0);
}

/*
 * Validate file access.  Since we
 * have no uid or gid, for now require
 * file to exist and be publicly
 * readable/writable.
 * Note also, full path name must be
 * given as we have no login directory.
 */
validate_access(file, client, mode)
	char *file;
	struct sockaddr_in *client;
	int mode;
{
	struct stat stbuf;

	if (*file != '/')
		return (EACCESS);

	/* temporary fix */
	if (setuid(-2) <0 ||
	    setgid(-2) <0) {
		PERROR("tftpd: cannot setuid or setpid");
	}

	if (stat(file, &stbuf) < 0)
		return (errno == ENOENT ? ENOTFOUND : EACCESS);
	if (mode == RRQ) {
		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
			return (EACCESS);
	} else {
		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
			return (EACCESS);
	}
	fd = fopen(file, mode == RRQ ? "r" : "w");
	if (!fd)
		return (errno + 100);
	if (mode == WRQ) {
		if ((ftruncate(fileno(fd),0))== -1) {
			PERROR("tftpd : truncate ");
			return(errno + 100);
			}
	}
	return (0);
}

int	timeout;
jmp_buf	timeoutbuf;

timer()
{

	timeout += rexmtval;
	if (timeout >= maxtimeout)
		exit(1);
	longjmp(timeoutbuf, 1);
}

/*
 * Send the requested file.
 */
sendfile(pf)
	struct format *pf;
{
	register struct tftphdr *tp;
	register int block = 1, size, n;
	int	fsize;

	signal(SIGALRM, timer);
	tp = (struct tftphdr *)buf;
	do {
		size = tftpread(fd, tp->th_data, SEGSIZE, &fsize);
		if (size < 0) {
			nak(errno + 100);
			goto abort;
		}
		tp->th_opcode = htons((u_short)DATA);
		tp->th_block = htons((u_short)block);
		timeout = 0;
		(void) setjmp(timeoutbuf);
		if (write(f, buf, size + 4) != size + 4) {
			PERROR("tftpd: write");
			goto abort;
		}
		do {
			alarm(rexmtval);
			n = read(f, buf, sizeof (buf));
			alarm(0);
			if (n < 0) {
				PERROR("tftpd: read");
				goto abort;
			}
			tp->th_opcode = ntohs((u_short)tp->th_opcode);
			tp->th_block = ntohs((u_short)tp->th_block);
			if (tp->th_opcode == ERROR)
				goto abort;
		} while (tp->th_opcode != ACK || tp->th_block != block);
		block++;
	} while (size == SEGSIZE);
abort:
	(void) fclose(fd);
}

/*
 * Receive a file.
 */
recvfile(pf)
	struct format *pf;
{
	register struct tftphdr *tp;
	register int block = 0, n, size;
	int	fsize;

	signal(SIGALRM, timer);
	tp = (struct tftphdr *)buf;
	do {
		timeout = 0;
		tp->th_opcode = htons((u_short)ACK);
		tp->th_block = htons((u_short)block);
		block++;
		(void) setjmp(timeoutbuf);
		if (write(f, buf, 4) != 4) {
			PERROR("tftpd: write");
			goto abort;
		}
		do {
			alarm(rexmtval);
			n = read(f, buf, sizeof (buf));
			alarm(0);
			if (n < 0) {
				PERROR("tftpd: read");
				goto abort;
			}
			tp->th_opcode = ntohs((u_short)tp->th_opcode);
			tp->th_block = ntohs((u_short)tp->th_block);
			if (tp->th_opcode == ERROR)
				goto abort;
		} while (tp->th_opcode != DATA || block != tp->th_block);
		size = n -4;
		n = tftpwrite(fd, tp->th_data, size, &fsize);
		if (n < 0) {
			nak(errno + 100);
			goto abort;
		}
	} while (size == SEGSIZE);
abort:
	tp->th_opcode = htons((u_short)ACK);
	tp->th_block = htons((u_short)(block));
	(void) write(f, buf, 4);
	(void) fclose(fd);
}

struct errmsg {
	int	e_code;
	char	*e_msg;
} errmsgs[] = {
	{ EUNDEF,	"Undefined error code" },
	{ ENOTFOUND,	"File not found" },
	{ EACCESS,	"Access violation" },
	{ ENOSPACE,	"Disk full or allocation exceeded" },
	{ EBADOP,	"Illegal TFTP operation" },
	{ EBADID,	"Unknown transfer ID" },
	{ EEXISTS,	"File already exists" },
	{ ENOUSER,	"No such user" },
	{ -1,		0 }
};

/*
 * Send a nak packet (error message).
 * Error code passed in is one of the
 * standard TFTP codes, or a UNIX errno
 * offset by 100.
 */
nak(error)
	int error;
{
	register struct tftphdr *tp;
	int length;
	register struct errmsg *pe;

	tp = (struct tftphdr *)buf;
	tp->th_opcode = htons((u_short)ERROR);
	tp->th_code = htons((u_short)error);
	for (pe = errmsgs; pe->e_code >= 0; pe++)
		if (pe->e_code == error)
			break;
	if (pe->e_code < 0)
		pe->e_msg = sys_errlist[error - 100];
	strcpy(tp->th_msg, pe->e_msg);
	length = strlen(pe->e_msg);
	tp->th_msg[length] = '\0';
	length += 5;
	if (write(f, buf, length) != length)
		PERROR("tftpd:nak");
}
get_myaddrs()
{
    struct hostent *hp;
    char	my_machine_name[BUFSIZ];

    /* look up the address of the local host */
    if (gethostname(my_machine_name, sizeof(my_machine_name)) <0) {
	perror("tftpd:cannot get this host name");
	exit(-1);
    }

    hp = gethostbyname(my_machine_name);
    if (hp == (struct hostent *) 0) {
	fprintf(stderr,"tftpd:cannot get host address");
	exit(-2);
    }

    if (hp->h_addrtype != AF_INET) {
	fprintf(stderr,"Protocal mix up with local machine address\n");
	exit(-1);
    }

    bcopy(hp->h_addr, (char *)&my_machine_addr, hp->h_length);
}

bcopy(fr, to, size)
register char	*fr;
register char	*to;
register int	size;
{
	while (size-->0)
		*to++ = *fr++;
}

#include <stdio.h>

/*-----------------------------------------
  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  >>>>>>>>>>>> NOTE >>>>>>>>>>>>> Must be identical to those in "tftp"
  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  read from local file and transform lines ending with '\n' to lines
  ending with '\r' and '\n'; also transform '\r' to '\r' and '\0'
  ----------------------------------------*/
tftpread(fd, buf, bufsize, fsize)
FILE	*fd;
register char	*buf;
int	bufsize;
int	*fsize;			/* count # bytes read from file */
{

	register char	*p;
	int	i;

	/* oldc is to indicate if the transformation is unfinished in the
	   last block */
	static	char	oldc;

	*fsize = 0;
	for (p=buf; p < buf+bufsize;) {
		switch(oldc) {
		case '\n':
			*p++ = '\n';		/* already output '\r' */
			oldc = '\0';
			break;
		case '\r':
			*p++ = '\0';		/* already output '\r' */
			oldc = '\0';
			break;
		default:
	       		i = fread(&oldc, sizeof(oldc), 1, fd);
			if (i<0)
				return(i);
			if (i==0)
				goto endit;

			++(*fsize);
			switch(oldc) {
			case '\n':
			case '\r':
				*p++ = '\r';
				/* will complete the transformation in next
				   ... loop iteration */
				break;
			default:
				*p++ = oldc;
				break;
			}
		}

	}
endit:
	return(p-buf);
}
/*-----------------------------------------
  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  >>>>>>>>>>>>>>>> NOTE >>>>>>>>> Must be identical to those "tftp"
  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  write to local file and transform lines ending with '\r''\n' to lines
  ending with and '\n'; also transform '\r' '\0' to '\r'.
  ----------------------------------------*/
tftpwrite(fd, buf, bufsize, fsize)
FILE	*fd;
register char	*buf;
int	bufsize;
int	*fsize;			/* count # of bytes write to file */
{
	register char	*p;
	char	c;
	int	i;

	/* oldc is to indicate if the transformation is unfinished in the
	   last block */
	static	char	oldc;

	*fsize = 0;
	for  (p=buf; p<buf + bufsize; p++) {
		c = *p;
		if (oldc == '\r') {
			char	writethisc;

			switch(c) {
			case '\0':
				writethisc = oldc;
				break;
			case '\n':
				writethisc = '\n';
				break;
			default:
				writethisc = oldc;
				break;
			}
			i = fwrite(&writethisc, sizeof(writethisc), 1, fd);
			if (i!=1)
				return(-1);
			++(*fsize);
			if (c=='\0' ||
			    c=='\n') {
				oldc = '\0';
				continue;
			}
		}
		if ((oldc = c) == '\r')
			continue;
		i = fwrite(&c, sizeof(c), 1, fd);
		if (i!=1)
			return(-1);
		++(*fsize);
	}
	return(*fsize);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.