Plan 9 from Bell Labs’s /usr/web/sources/contrib/cinap_lenrek/tftpd.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <auth.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	oack(int, char*, 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,
	Tftp_OACK	= 6,
	Segsize		= 512,
};

void
usage(void)
{
	fprint(2, "usage: %s [-dr] [-h homedir] [-s svc] [-x netmtpt]\n",
		argv0);
	exits("usage");
}


int
getlist(char *buf, int nbuf, char **list, int nlist)
{
	int s, c;
	char *p;

	p = nil;
	c = 0;
	s = 0;
	while(nbuf-- > 0){
		switch(s){
		case 0:
			p = buf;
			s = 1;

		case 1:
			if(*buf == '\0'){
				s = 0;
				if(--nlist <= 0)
					goto outout;
				*list = p; 
				list++;
				c++;
			}
			break;
		}
		buf++;
	}
outout:
	*list = nil;
	return c;
}

void
main(int argc, char **argv)
{
	char buf[64];
	char adir[64], ldir[64];
	int cfd, lcfd, dfd;
	char *p, *svc = "69";

	setnetmtpt(net, sizeof(net), nil);
	ARGBEGIN{
	case 'd':
		dbg++;
		break;
	case 'h':
		dir = ARGF();
		break;
	case 'r':
		restricted = 1;
		break;
	case 's':
		svc = EARGF(usage());
		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);
		}

	snprint(buf, sizeof buf, "%s/udp!*!%s", net, svc);
	cfd = announce(buf, adir);
	if (cfd < 0)
		sysfatal("announcing on %s: %r", buf);
	syslog(dbg, flog, "tftpd started on %s dir %s", buf, adir);
	setuser();
	for(;;) {
		lcfd = listen(adir, ldir);
		if(lcfd < 0)
			sysfatal("listening on %s: %r", adir);

		switch(fork()) {
		case -1:
			sysfatal("fork: %r");
		case 0:
			dfd = accept(lcfd, ldir);
			if(dfd < 0)
 				exits(0);
			remoteaddr(ldir, raddr, sizeof(raddr));
			syslog(0, flog, "tftp connection from %s dir %s",
				raddr, ldir);
			doserve(dfd);
			exits("done");
			break;
		default:
			close(lcfd);
			continue;
		}
	}
}

void
doserve(int fd)
{
	int dlen;
	char *field[10];
	char tmp[128];
	int nfield;
	char *name, *mode;
	char **opt;
	short op;
	Dir *dir;

	dlen = read(fd, bigbuf, sizeof(bigbuf));
	if(dlen < 0)
		sysfatal("listen read: %r");

	op = (bigbuf[0]<<8) | bigbuf[1];
	dlen -= 2;

	nfield = getlist(bigbuf+2, dlen, field, sizeof(field)/sizeof(field[0]));
	if(nfield < 2){
		nak(fd, 0, "bad request");
		close(fd);
		syslog(dbg, flog, "bad request from %s", raddr);
		return;
	}
	name = field[0];
	mode = field[1];
	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(*name == '#' ||
		  strncmp(name, "../", 3)==0 || strstr(name) ||
		  (*name == '/' && strncmp(name, dirsl, dirsllen)!=0)){
			nak(fd, 4, "Permission denied");
			close(fd);
			syslog(dbg, flog, "bad request %d from %s file %s", op, raddr, name);
			return;
		}
	}*/

	name = sunkernel(name);
	if(name == 0){
		nak(fd, 0, "file not found");
		close(fd);
		syslog(dbg, flog, "file %s not found for %s", name, raddr);
		return;		
	}

	if((nfield>2) && ((nfield%2)==0)){
		/* handle options */
		nfield -= 2;
		nfield /= 2;
		opt = &field[2];
		while(nfield-- > 0){
			if(cistrcmp(opt[0], "tsize")==0){
				if(op == Tftp_READ){
					dir=dirstat(name);
					if(dir != nil){
						snprint(tmp, sizeof(tmp), "%lld", dir->length);
						free(dir);
						oack(fd, opt[0], tmp);
					}
				}
			}
			opt+=2;
		}
	}

	if(op == Tftp_READ)
		sendfile(fd, name, mode);
	else
		recvfile(fd, name, mode);
}

void
catcher(void *junk, char *msg)
{
	USED(junk);

	if(strncmp(msg, "exit", 4) == 0)
		noted(NDFLT);
	noted(NCONT);
}

void
sendfile(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);
	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);
}

void
recvfile(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);
}

void
ack(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");
}

void
oack(int fd, char *option, char *value)
{
	char buf[128];
	int l1, l2, n;

	buf[0] = 0;
	buf[1] = Tftp_OACK;

	l1=strlen(option);
	l2=strlen(value);
	n = 2 + l1+1 + l2+1;

	if(n < sizeof(buf)){
		memcpy(buf+2, option, l1+1);
		memcpy(buf+2+l1+1, value, l2+1);
		n = write(fd, buf, n);
		if(n < 0)
			sysfatal("write oack: %r");
	}
}

void
nak(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");
}

void
setuser(void)
{
	int fd;

	fd = open("#c/user", OWRITE);
	if(fd < 0 || write(fd, "none", strlen("none")) < 0)
		sysfatal("can't become none: %r");
	close(fd);
	if(newns("none", nil) < 0)
		sysfatal("can't build namespace: %r");
	bind(dir, "/", MAFTER);
}

char*
lookup(char *sattr, char *sval, char *tattr, char *tval, int len)
{
	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;
	strncpy(tval, t->val, len);
	tval[len-1] = 0;
	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[256];
	char ipbuf[128];
	char *suffix;

	addr = strtoul(name, &suffix, 16);
	if(suffix-name != 8 || (strcmp(suffix, "") != 0 && strcmp(suffix, ".SUN") != 0))
		return name;

	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, sizeof buf);
}

void
remoteaddr(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;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.