766 lines · 15.2 KB · C
RawDownload
/* * tftpd - tftp service, see /lib/rfc/rfc783 (now rfc1350 + 234[789]) */#include <u.h>#include <libc.h>#include <auth.h>#include <bio.h>#include <ip.h>#include <ndb.h> enum{	Maxpath=	128,	Maxerr=		256, 	Debug=		0, 	Opsize=		sizeof(short),	Blksize=	sizeof(short),	Hdrsize=	Opsize + Blksize, 	Ackerr=		-1,	Ackok=		0,	Ackrexmit=	1, 	/* op codes */	Tftp_READ	= 1,	Tftp_WRITE	= 2,	Tftp_DATA	= 3,	Tftp_ACK	= 4,	Tftp_ERROR	= 5,	Tftp_OACK	= 6,		/* option acknowledge */ 	Errnotdef	= 0,		/* see textual error instead */	Errnotfound	= 1,	Errnoaccess	= 2,	Errdiskfull	= 3,	Errbadop	= 4,	Errbadtid	= 5,	Errexists	= 6,	Errnouser	= 7,	Errbadopt	= 8,		/* really bad option value */ 	Defsegsize	= 512,	Maxsegsize	= 65464,	/* from rfc2348 */ 	/*	 * bandt (viaduct) tunnels use smaller mtu than ether's	 * (1400 bytes for tcp mss of 1300 bytes).	 */	Bandtmtu	= 1400,	/*	 * maximum size of block's data content, excludes hdrs,	 * notably IP/UDP and TFTP, using worst-case (IPv6) sizes.	 */	Bandtblksz	= Bandtmtu - 40 - 8,	Bcavium		= 1432,		/* cavium's u-boot demands this size */}; typedef struct Opt Opt;struct Opt {	char	*name;	int	*valp;		/* set to client's value if within bounds */	int	min;	int	max;}; int 	dbg;int	restricted;int	pid; /* options */int	blksize = Defsegsize;		/* excluding 4-byte header */int	timeout = 5;			/* seconds */int	tsize;static Opt option[] = {	"timeout",	&timeout,	1,	255,	/* see "hack" below */	"blksize",	&blksize,	8,	Maxsegsize,	"tsize",	&tsize,		0,	~0UL >> 1,}; void	sendfile(int, char*, char*, int);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]; static char *opnames[] = {[Tftp_READ]	"read",[Tftp_WRITE]	"write",[Tftp_DATA]	"data",[Tftp_ACK]	"ack",[Tftp_ERROR]	"error",[Tftp_OACK]	"oack",}; voidusage(void){	fprint(2, "usage: %s [-dr] [-h homedir] [-s svc] [-x netmtpt]\n",		argv0);	exits("usage");} voidmain(int argc, char **argv){	char buf[64];	char adir[64], ldir[64];	int cfd, lcfd, dfd;	char *svc = "69"; 	setnetmtpt(net, sizeof net, nil);	ARGBEGIN{	case 'd':		dbg++;		break;	case 'h':		dir = EARGF(usage());		break;	case 'r':		restricted = 1;		break;	case 's':		svc = EARGF(usage());		break;	case 'x':		setnetmtpt(net, sizeof net, EARGF(usage()));		break;	default:		usage();	}ARGEND 	snprint(buf, sizeof buf, "%s/", dir);	dirsl = strdup(buf);	dirsllen = strlen(dirsl); 	fmtinstall('E', eipfmt);	fmtinstall('I', eipfmt); 	/*	 * setuser calls newns, and typical /lib/namespace files contain	 * "cd /usr/$user", so call setuser before chdir.	 */	setuser();	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));			pid = getpid();			syslog(0, flog, "tftp %d connection from %s dir %s",				pid, raddr, ldir);			doserve(dfd);			exits("done");			break;		default:			close(lcfd);			continue;		}	}} static Opt *handleopt(int fd, char *name, char *val){	int n;	Opt *op; 	for (op = option; op < option + nelem(option); op++)		if(cistrcmp(name, op->name) == 0) {			n = strtol(val, nil, 10);			if (n < op->min || n > op->max) {				nak(fd, Errbadopt, "option value out of range");				syslog(dbg, flog, "tftp bad option value from "					"client: %s %s", name, val);				sysfatal("bad option value from client: %s %s",					name, val);			}			*op->valp = n;			/* incoming 0 for tsize is uninteresting */			if(cistrcmp("tsize", op->name) != 0)				syslog(dbg, flog, "tftpd %d setting %s to client's %d",					pid, name, n);			return op;		}	return nil;} static vlongfilesize(char *file){	vlong size;	Dir *dp; 	dp = dirstat(file);	if (dp == nil)		return -1;	size = dp->length;	free(dp);	return size;} /* copy word into bp iff it fits before ep, returns bytes to advance bp. */static intemits(char *word, char *bp, char *ep){	int len; 	len = strlen(word) + 1;	if (bp + len >= ep)		return -1;	strcpy(bp, word);	return len;} /* format number into bp iff it fits before ep. */static intemitn(vlong n, char *bp, char *ep){	char numb[32]; 	snprint(numb, sizeof numb, "%lld", n);	return emits(numb, bp, ep);} /* * send an OACK packet to respond to options.  bail early with -1 on error. * p is the packet containing the options. * * hack: bandt (viaducts) uses smaller mtu than ether's * (1400 bytes for tcp mss of 1300 bytes), * so offer at most bandt's mtu minus headers, * to avoid failure of pxe booting via viaduct. * there's an exception for the cavium's u-boot. */static intoptions(int fd, char *buf, int bufsz, char *file, ushort oper, char *p, int dlen){	int nmlen, vallen, olen, nopts;	vlong size;	char *val, *bp, *ep;	Opt *op; 	buf[0] = 0;	buf[1] = Tftp_OACK;	bp = buf + Opsize;	ep = buf + bufsz;	nopts = 0;	for (; dlen > 0 && *p != '\0'; p = val + vallen, bp += olen) {		nmlen = strlen(p) + 1;		/* include NUL */		if (nmlen > dlen)			break;		dlen -= nmlen;		val = p + nmlen;		if (dlen <= 0 || *val == '\0')			break; 		vallen = strlen(val) + 1;		if (vallen > dlen)			break;		dlen -= vallen; 		nopts++;		olen = 0;		op = handleopt(fd, p, val);		if (op == nil)			continue; 		/* append OACK response to buf */		nmlen = emits(p, bp, ep);	/* option name */		if (nmlen < 0)			return -1;		bp += nmlen; 		if (oper == Tftp_READ && cistrcmp(p, "tsize") == 0) {			size = filesize(file);			if (size == -1) {				nak(fd, Errnotfound, "no such file");				syslog(dbg, flog, "tftpd tsize for "					"non-existent file %s", file);				// *op->valp = 0;				// olen = emits("0", bp, ep);				return -1;			}			*op->valp = size;			olen = emitn(size, bp, ep);			syslog(dbg, flog, "tftpd %d %s tsize is %,lld",				pid, file, size);		} else if (oper == Tftp_READ && cistrcmp(p, "blksize") == 0 &&		    blksize > Bandtblksz && blksize != Bcavium) {			*op->valp = blksize = Bandtblksz;			olen = emitn(blksize, bp, ep);			syslog(dbg, flog, "tftpd %d overriding blksize to %d",				pid, blksize);		} else			olen = emits(val, bp, ep);  /* use requested value */	}	if (nopts == 0)		return 0;		/* no options actually seen */ 	if (bp + 3 >= ep)		return -1;	*bp++ = '\0';	*bp++ = '\0';			/* overkill */	*bp++ = '\0'; 	if (write(fd, buf, bp - buf) < bp - buf) {		syslog(dbg, flog, "tftpd network write error on oack to %s: %r",			raddr);		sysfatal("tftpd: network write error: %r");	}	if(Debug)		syslog(dbg, flog, "tftpd oack: options to %s", raddr);	return nopts;} static voidoptlog(char *bytes, char *p, int dlen){	char *bp; 	bp = bytes;	sprint(bp, "tftpd %d option bytes: ", dlen);	bp += strlen(bp);	for (; dlen > 0; dlen--, p++)		*bp++ = *p? *p: ' ';	*bp = '\0';	syslog(dbg, flog, "%s", bytes);} voiddoserve(int fd){	int dlen, opts;	char *mode, *p, *file;	short op; 	dlen = read(fd, bigbuf, sizeof(bigbuf)-1);	if(dlen < 0)		sysfatal("listen read: %r"); 	bigbuf[dlen] = '\0';	op = (bigbuf[0]<<8) | bigbuf[1];	dlen -= Opsize;	mode = file = bigbuf + Opsize;	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, "tftpd %d bad mode %s for file %s from %s",			pid, mode, file, raddr);		return;	} 	if(op != Tftp_READ && op != Tftp_WRITE) {		nak(fd, Errbadop, "Illegal TFTP operation");		close(fd);		syslog(dbg, flog, "tftpd %d bad request %d (%s) %s", pid, op,			(op < nelem(opnames)? opnames[op]: "gok"), raddr);		return;	} 	if(restricted){		if(file[0] == '#' || strncmp(file, "../", 3) == 0 ||		  strstr(file, "/../") != nil ||		  (file[0] == '/' && strncmp(file, dirsl, dirsllen) != 0)){			nak(fd, Errnoaccess, "Permission denied");			close(fd);			syslog(dbg, flog, "tftpd %d bad request %d from %s file %s",				pid, op, raddr, file);			return;		}	} 	/*	 * options are supposed to be negotiated, but the cavium board's	 * u-boot really wants us to use a block size of 1432 bytes and won't	 * take `no' for an answer.	 */	p++;				/* skip NUL after mode */	dlen--;	opts = 0;	if(dlen > 0) {			/* might have options */		char bytes[32*1024]; 		if(Debug)			optlog(bytes, p, dlen);		opts = options(fd, bytes, sizeof bytes, file, op, p, dlen);		if (opts < 0)			return;	}	if(op == Tftp_READ)		sendfile(fd, file, mode, opts);	else		recvfile(fd, file, mode);} voidcatcher(void *junk, char *msg){	USED(junk); 	if(strncmp(msg, "exit", 4) == 0)		noted(NDFLT);	noted(NCONT);} static intawaitack(int fd, int block){	int ackblock, al, rxl;	ushort op;	uchar ack[1024]; 	for(rxl = 0; rxl < 10; rxl++) {		memset(ack, 0, Hdrsize);		alarm(1000);		al = read(fd, ack, sizeof(ack));		alarm(0);		if(al < 0) {			if (Debug)				syslog(dbg, flog, "tftpd %d timed out "					"waiting for ack from %s", pid, raddr);			return Ackrexmit;		}		op = ack[0]<<8|ack[1];		if(op == Tftp_ERROR) {			if (Debug)				syslog(dbg, flog, "tftpd %d got error "					"waiting for ack from %s", pid, raddr);			return Ackerr;		} else if(op != Tftp_ACK) {			syslog(dbg, flog, "tftpd %d rcvd %s op from %s", pid,				(op < nelem(opnames)? opnames[op]: "gok"),				raddr);			return Ackerr;		}		ackblock = ack[2]<<8|ack[3];		if (Debug)			syslog(dbg, flog, "tftpd %d read ack of %d bytes "				"for block %d", pid, al, ackblock);		if(ackblock == block)			return Ackok;		/* for block just sent */		else if(ackblock == block + 1)	/* intel pxe eof bug */			return Ackok;		else if(ackblock == 0xffff)			return Ackrexmit;		else			/* ack is for some other block; ignore it, try again */			syslog(dbg, flog, "tftpd %d expected ack for block %d, "				"got %d", pid, block, ackblock);	}	return Ackrexmit;

Lines 1–500 of 766.