henesyinit from p9fd787fda5 years agoHistory
437 lines · 7.0 KB · C
RawDownload
#include <u.h>#include <libc.h>#include <bio.h>#include <ip.h>#include <ndb.h> enum{	Maxpath=	128,	Maxerr=		256,}; int 	dbg;int	restricted;void	sendfile(int, char*, char*);void	recvfile(int, char*, char*);void	nak(int, int, char*);void	ack(int, ushort);void	clrcon(void);void	setuser(void);char*	sunkernel(char*);void	remoteaddr(char*, char*, int);void	doserve(int); char	bigbuf[32768];char	raddr[64]; char	*dir = "/lib/tftpd";char	*dirsl;int	dirsllen;char	flog[] = "ipboot";char	net[Maxpath]; enum{	Tftp_READ	= 1,	Tftp_WRITE	= 2,	Tftp_DATA	= 3,	Tftp_ACK	= 4,	Tftp_ERROR	= 5,	Segsize		= 512,}; voidusage(void){	fprint(2, "usage: %s [-dr] [-h homedir] [-x netmtpt]\n", argv0);	exits("usage");} voidmain(int argc, char **argv){	char buf[64];	char adir[64], ldir[64];	int cfd, lcfd, dfd;	char *p; 	setnetmtpt(net, sizeof(net), nil);	ARGBEGIN{	case 'd':		dbg++;		break;	case 'h':		dir = ARGF();		break;	case 'r':		restricted = 1;		break;	case 'x':		p = ARGF();		if(p == nil)			usage();		setnetmtpt(net, sizeof(net), p);		break;	default:		usage();	}ARGEND 	snprint(buf, sizeof buf, "%s/", dir);	dirsl = strdup(buf);	dirsllen = strlen(dirsl); 	fmtinstall('E', eipfmt);	fmtinstall('I', eipfmt); 	if(chdir(dir) < 0)		sysfatal("can't get to directory %s: %r", dir); 	if(!dbg)		switch(rfork(RFNOTEG|RFPROC|RFFDG)) {		case -1:			sysfatal("fork: %r");		case 0:			break;		default:			exits(0);		} 	syslog(dbg, flog, "started"); 	sprint(buf, "%s/udp!*!69", net);	cfd = announce(buf, adir);	setuser();	for(;;) {		lcfd = listen(adir, ldir);		if(lcfd < 0)			sysfatal("listening: %r"); 		switch(fork()) {		case -1:			sysfatal("fork: %r");		case 0:			dfd = accept(cfd, ldir);			if(dfd < 0) 				exits(0);			remoteaddr(ldir, raddr, sizeof(raddr));			doserve(dfd);			exits("done");			break;		default:			close(lcfd);			continue;		}	}} voiddoserve(int fd){	int dlen;	char *mode, *p;	short op; 	dlen = read(fd, bigbuf, sizeof(bigbuf));	if(dlen < 0)		sysfatal("listen read: %r"); 	op = (bigbuf[0]<<8) | bigbuf[1];	dlen -= 2;	mode = bigbuf+2;	while(*mode != '\0' && dlen--)		mode++;	mode++;	p = mode;	while(*p && dlen--)		p++;	if(dlen == 0) {		nak(fd, 0, "bad tftpmode");		close(fd);		syslog(dbg, flog, "bad mode %s", raddr);		return;	} 	if(op != Tftp_READ && op != Tftp_WRITE) {		nak(fd, 4, "Illegal TFTP operation");		close(fd);		syslog(dbg, flog, "bad request %d %s", op, raddr);		return;	} 	if(restricted){		if(strncmp(bigbuf+2, "../", 3) || strstr(bigbuf+2, "/../") ||		  (bigbuf[2] == '/' && strncmp(bigbuf+2, dirsl, dirsllen)!=0)){			nak(fd, 4, "Permission denied");			close(fd);			syslog(dbg, flog, "bad request %d %s", op, raddr);			return;		}	} 	if(op == Tftp_READ)		sendfile(fd, bigbuf+2, mode);	else		recvfile(fd, bigbuf+2, mode);} voidcatcher(void *junk, char *msg){	USED(junk); 	if(strncmp(msg, "exit", 4) == 0)		noted(NDFLT);	noted(NCONT);} voidsendfile(int fd, char *name, char *mode){	int file;	uchar buf[Segsize+4];	uchar ack[1024];	char errbuf[Maxerr];	int ackblock, block, ret;	int rexmit, n, al, txtry, rxl;	short op; 	syslog(dbg, flog, "send file '%s' %s to %s", name, mode, raddr);	name = sunkernel(name);	if(name == 0){		nak(fd, 0, "not in our database");		return;	} 	notify(catcher); 	file = open(name, OREAD);	if(file < 0) {		errstr(errbuf, sizeof errbuf);		nak(fd, 0, errbuf);		return;	}	block = 0;	rexmit = 0;	n = 0;	for(txtry = 0; txtry < 5;) {		if(rexmit == 0) {			block++;			buf[0] = 0;			buf[1] = Tftp_DATA;			buf[2] = block>>8;			buf[3] = block;			n = read(file, buf+4, Segsize);			if(n < 0) {				errstr(errbuf, sizeof errbuf);				nak(fd, 0, errbuf);				return;			}			txtry = 0;		}		else {			syslog(dbg, flog, "rexmit %d %s:%d to %s", 4+n, name, block, raddr);			txtry++;		} 		ret = write(fd, buf, 4+n);		if(ret < 0)			sysfatal("tftpd: network write error: %r"); 		for(rxl = 0; rxl < 10; rxl++) {			rexmit = 0;			alarm(500);			al = read(fd, ack, sizeof(ack));			alarm(0);			if(al < 0) {				rexmit = 1;				break;			}			op = ack[0]<<8|ack[1];			if(op == Tftp_ERROR)				goto error;			ackblock = ack[2]<<8|ack[3];			if(ackblock == block)				break;			if(ackblock == 0xffff) {				rexmit = 1;				break;			}		}		if(ret != Segsize+4 && rexmit == 0)			break;	}error:		close(fd);	close(file);} voidrecvfile(int fd, char *name, char *mode){	ushort op, block, inblock;	uchar buf[Segsize+8];	char errbuf[Maxerr];	int n, ret, file; 	syslog(dbg, flog, "receive file '%s' %s from %s", name, mode, raddr); 	file = create(name, OWRITE, 0666);	if(file < 0) {		errstr(errbuf, sizeof errbuf);		nak(fd, 0, errbuf);		return;	} 	block = 0;	ack(fd, block);	block++; 	for(;;) {		alarm(15000);		n = read(fd, buf, sizeof(buf));		alarm(0);		if(n < 0)			goto error;		op = buf[0]<<8|buf[1];		if(op == Tftp_ERROR)			goto error; 		n -= 4;		inblock = buf[2]<<8|buf[3];		if(op == Tftp_DATA) {			if(inblock == block) {				ret = write(file, buf, n);				if(ret < 0) {					errstr(errbuf, sizeof errbuf);					nak(fd, 0, errbuf);					goto error;				}				ack(fd, block);				block++;			}			ack(fd, 0xffff);		}	}error:	close(file);} voidack(int fd, ushort block){	uchar ack[4];	int n; 	ack[0] = 0;	ack[1] = Tftp_ACK;	ack[2] = block>>8;	ack[3] = block; 	n = write(fd, ack, 4);	if(n < 0)		sysfatal("network write: %r");} voidnak(int fd, int code, char *msg){	char buf[128];	int n; 	buf[0] = 0;	buf[1] = Tftp_ERROR;	buf[2] = 0;	buf[3] = code;	strcpy(buf+4, msg);	n = strlen(msg) + 4 + 1;	n = write(fd, buf, n);	if(n < 0)		sysfatal("write nak: %r");} voidsetuser(void){	int f; 	f = open("/dev/user", OWRITE);	if(f < 0)		return;	write(f, "none", sizeof("none"));	close(f);} char*lookup(char *sattr, char *sval, char *tattr, char *tval){	static Ndb *db;	char *attrs[1];	Ndbtuple *t; 	if(db == nil)		db = ndbopen(0);	if(db == nil)		return nil; 	if(sattr == nil)		sattr = ipattr(sval); 	attrs[0] = tattr;	t = ndbipinfo(db, sattr, sval, attrs, 1);	if(t == nil)		return nil;	strcpy(tval, t->val);	ndbfree(t);	return tval;} /* *  for sun kernel boots, replace the requested file name with *  a one from our database.  If the database doesn't specify a file, *  don't answer. */char*sunkernel(char *name){	ulong addr;	uchar v4[IPv4addrlen];	uchar v6[IPaddrlen];	char buf[Ndbvlen];	char ipbuf[Ndbvlen]; 	if(strlen(name) != 14 || strncmp(name + 8, ".SUN", 4) != 0)		return name; 	addr = strtoul(name, 0, 16);	v4[0] = addr>>24;	v4[1] = addr>>16;	v4[2] = addr>>8;	v4[3] = addr;	v4tov6(v6, v4);	sprint(ipbuf, "%I", v6);	return lookup("ip", ipbuf, "bootf", buf);} voidremoteaddr(char *dir, char *raddr, int len){	char buf[64];	int fd, n; 	snprint(buf, sizeof(buf), "%s/remote", dir);	fd = open(buf, OREAD);	if(fd < 0){		snprint(raddr, sizeof(raddr), "unknown");		return;	}	n = read(fd, raddr, len-1);	close(fd);	if(n <= 0){		snprint(raddr, sizeof(raddr), "unknown");		return;	}	if(n > 0)		n--;	raddr[n] = 0;}