844 lines · 17.4 KB · C
RawDownload
/*  * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * 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 (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);} /* * replace one occurrence of %[ICE] with ip, cfgpxe name, or ether mac, resp. * we can't easily use $ because u-boot has stranger quoting rules than sh. */char *mapname(char *file){	int nf;	char *p, *newnm, *cur, *arpf, *ln, *remip, *bang;	char *fields[4];	Biobuf *arp; 	p = strchr(file, '%');	if (p == nil || p[1] == '\0')		return strdup(file); 	remip = strdup(raddr);	newnm = mallocz(strlen(file) + Maxpath, 1);	if (remip == nil || newnm == nil)		sysfatal("out of memory"); 	bang = strchr(remip, '!');	if (bang)		*bang = '\0';			/* remove !port */ 	memmove(newnm, file, p - file);		/* copy up to % */	cur = newnm + strlen(newnm);	switch(p[1]) {	case 'I':		strcpy(cur, remip);		/* remote's IP */		break;	case 'C':		strcpy(cur, "/cfg/pxe/");		cur += strlen(cur);		/* fall through */	case 'E':		/* look up remote's IP in /net/arp to get mac. */		arpf = smprint("%s/arp", net);		arp = Bopen(arpf, OREAD);		free(arpf);		if (arp == nil)			break;		/* read lines looking for remip in 3rd field of 4 */		while ((ln = Brdline(arp, '\n')) != nil) {			ln[Blinelen(arp)-1] = 0;			nf = tokenize(ln, fields, nelem(fields));			if (nf >= 4 && strcmp(fields[2], remip) == 0) {				strcpy(cur, fields[3]);				break;			}		}		Bterm(arp);		break;	}	strcat(newnm, p + 2);			/* tail following %x */	free(remip);	return newnm;} 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++; 	file = mapname(file);	/* we don't free the result; minor leak */ 	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);

Lines 1–500 of 844.