xref: /plan9/sys/src/cmd/ip/tftpd.c (revision a4796b5c1610f7b608d0b7db4f22b18e62a61ef4)
1162f803dSDavid du Colombier /*
23e5d0078SDavid du Colombier  * tftpd - tftp service, see /lib/rfc/rfc783 (now rfc1350 + 234[789])
3162f803dSDavid du Colombier  */
43e12c5d1SDavid du Colombier #include <u.h>
53e12c5d1SDavid du Colombier #include <libc.h>
6c0eadb1cSDavid du Colombier #include <auth.h>
73e12c5d1SDavid du Colombier #include <bio.h>
83e12c5d1SDavid du Colombier #include <ip.h>
93e12c5d1SDavid du Colombier #include <ndb.h>
103e12c5d1SDavid du Colombier 
119a747e4fSDavid du Colombier enum
129a747e4fSDavid du Colombier {
139a747e4fSDavid du Colombier 	Maxpath=	128,
149a747e4fSDavid du Colombier 	Maxerr=		256,
153e5d0078SDavid du Colombier 
163e5d0078SDavid du Colombier 	Debug=		0,
173e5d0078SDavid du Colombier 
183e5d0078SDavid du Colombier 	Opsize=		sizeof(short),
193e5d0078SDavid du Colombier 	Blksize=	sizeof(short),
203e5d0078SDavid du Colombier 	Hdrsize=	Opsize + Blksize,
213e5d0078SDavid du Colombier 
223e5d0078SDavid du Colombier 	Ackerr=		-1,
233e5d0078SDavid du Colombier 	Ackok=		0,
243e5d0078SDavid du Colombier 	Ackrexmit=	1,
253e5d0078SDavid du Colombier 
263e5d0078SDavid du Colombier 	/* op codes */
273e5d0078SDavid du Colombier 	Tftp_READ	= 1,
283e5d0078SDavid du Colombier 	Tftp_WRITE	= 2,
293e5d0078SDavid du Colombier 	Tftp_DATA	= 3,
303e5d0078SDavid du Colombier 	Tftp_ACK	= 4,
313e5d0078SDavid du Colombier 	Tftp_ERROR	= 5,
323e5d0078SDavid du Colombier 	Tftp_OACK	= 6,		/* option acknowledge */
333e5d0078SDavid du Colombier 
343e5d0078SDavid du Colombier 	Errnotdef	= 0,		/* see textual error instead */
353e5d0078SDavid du Colombier 	Errnotfound	= 1,
363e5d0078SDavid du Colombier 	Errnoaccess	= 2,
373e5d0078SDavid du Colombier 	Errdiskfull	= 3,
383e5d0078SDavid du Colombier 	Errbadop	= 4,
393e5d0078SDavid du Colombier 	Errbadtid	= 5,
403e5d0078SDavid du Colombier 	Errexists	= 6,
413e5d0078SDavid du Colombier 	Errnouser	= 7,
423e5d0078SDavid du Colombier 	Errbadopt	= 8,		/* really bad option value */
433e5d0078SDavid du Colombier 
443e5d0078SDavid du Colombier 	Defsegsize	= 512,
453e5d0078SDavid du Colombier 	Maxsegsize	= 65464,	/* from rfc2348 */
46*a4796b5cSDavid du Colombier 
47*a4796b5cSDavid du Colombier 	/*
48*a4796b5cSDavid du Colombier 	 * bandt (viaduct) tunnels use smaller mtu than ether's
49*a4796b5cSDavid du Colombier 	 * (1400 bytes for tcp mss of 1300 bytes).
50*a4796b5cSDavid du Colombier 	 */
51*a4796b5cSDavid du Colombier 	Bandtmtu	= 1400,
52*a4796b5cSDavid du Colombier 	/*
53*a4796b5cSDavid du Colombier 	 * maximum size of block's data content, excludes hdrs,
54*a4796b5cSDavid du Colombier 	 * notably IP/UDP and TFTP, using worst-case (IPv6) sizes.
55*a4796b5cSDavid du Colombier 	 */
56*a4796b5cSDavid du Colombier 	Bandtblksz	= Bandtmtu - 40 - 8,
573e5d0078SDavid du Colombier };
583e5d0078SDavid du Colombier 
593e5d0078SDavid du Colombier typedef struct Opt Opt;
603e5d0078SDavid du Colombier struct Opt {
613e5d0078SDavid du Colombier 	char	*name;
623e5d0078SDavid du Colombier 	int	*valp;		/* set to client's value if within bounds */
633e5d0078SDavid du Colombier 	int	min;
643e5d0078SDavid du Colombier 	int	max;
659a747e4fSDavid du Colombier };
669a747e4fSDavid du Colombier 
673e12c5d1SDavid du Colombier int 	dbg;
68219b2ee8SDavid du Colombier int	restricted;
693e5d0078SDavid du Colombier int	pid;
703e5d0078SDavid du Colombier 
713e5d0078SDavid du Colombier /* options */
72*a4796b5cSDavid du Colombier int	blksize = Defsegsize;		/* excluding 4-byte header */
733e5d0078SDavid du Colombier int	timeout = 5;			/* seconds */
743e5d0078SDavid du Colombier int	tsize;
753e5d0078SDavid du Colombier static Opt option[] = {
763e5d0078SDavid du Colombier 	"timeout",	&timeout,	1,	255,
77*a4796b5cSDavid du Colombier 	/* see "hack" below */
783e5d0078SDavid du Colombier 	"blksize",	&blksize,	8,	Maxsegsize,
793e5d0078SDavid du Colombier 	"tsize",	&tsize,		0,	~0UL >> 1,
803e5d0078SDavid du Colombier };
813e5d0078SDavid du Colombier 
823e5d0078SDavid du Colombier void	sendfile(int, char*, char*, int);
833e12c5d1SDavid du Colombier void	recvfile(int, char*, char*);
843e12c5d1SDavid du Colombier void	nak(int, int, char*);
853e12c5d1SDavid du Colombier void	ack(int, ushort);
863e12c5d1SDavid du Colombier void	clrcon(void);
873e12c5d1SDavid du Colombier void	setuser(void);
883e12c5d1SDavid du Colombier char*	sunkernel(char*);
897dd7cddfSDavid du Colombier void	remoteaddr(char*, char*, int);
907dd7cddfSDavid du Colombier void	doserve(int);
913e12c5d1SDavid du Colombier 
927dd7cddfSDavid du Colombier char	bigbuf[32768];
937dd7cddfSDavid du Colombier char	raddr[64];
943e12c5d1SDavid du Colombier 
953e12c5d1SDavid du Colombier char	*dir = "/lib/tftpd";
96219b2ee8SDavid du Colombier char	*dirsl;
97219b2ee8SDavid du Colombier int	dirsllen;
983e12c5d1SDavid du Colombier char	flog[] = "ipboot";
999a747e4fSDavid du Colombier char	net[Maxpath];
1003e12c5d1SDavid du Colombier 
1013e5d0078SDavid du Colombier static char *opnames[] = {
1023e5d0078SDavid du Colombier [Tftp_READ]	"read",
1033e5d0078SDavid du Colombier [Tftp_WRITE]	"write",
1043e5d0078SDavid du Colombier [Tftp_DATA]	"data",
1053e5d0078SDavid du Colombier [Tftp_ACK]	"ack",
1063e5d0078SDavid du Colombier [Tftp_ERROR]	"error",
1073e5d0078SDavid du Colombier [Tftp_OACK]	"oack",
1083e12c5d1SDavid du Colombier };
1093e12c5d1SDavid du Colombier 
1103e12c5d1SDavid du Colombier void
1117dd7cddfSDavid du Colombier usage(void)
1127dd7cddfSDavid du Colombier {
113c0eadb1cSDavid du Colombier 	fprint(2, "usage: %s [-dr] [-h homedir] [-s svc] [-x netmtpt]\n",
114c0eadb1cSDavid du Colombier 		argv0);
1157dd7cddfSDavid du Colombier 	exits("usage");
1167dd7cddfSDavid du Colombier }
1177dd7cddfSDavid du Colombier 
1187dd7cddfSDavid du Colombier void
1193e12c5d1SDavid du Colombier main(int argc, char **argv)
1203e12c5d1SDavid du Colombier {
1217dd7cddfSDavid du Colombier 	char buf[64];
1227dd7cddfSDavid du Colombier 	char adir[64], ldir[64];
1237dd7cddfSDavid du Colombier 	int cfd, lcfd, dfd;
124162f803dSDavid du Colombier 	char *svc = "69";
1253e12c5d1SDavid du Colombier 
126162f803dSDavid du Colombier 	setnetmtpt(net, sizeof net, nil);
1273e12c5d1SDavid du Colombier 	ARGBEGIN{
1283e12c5d1SDavid du Colombier 	case 'd':
1293e12c5d1SDavid du Colombier 		dbg++;
1303e12c5d1SDavid du Colombier 		break;
1313e12c5d1SDavid du Colombier 	case 'h':
132162f803dSDavid du Colombier 		dir = EARGF(usage());
1333e12c5d1SDavid du Colombier 		break;
134219b2ee8SDavid du Colombier 	case 'r':
135219b2ee8SDavid du Colombier 		restricted = 1;
136219b2ee8SDavid du Colombier 		break;
137c0eadb1cSDavid du Colombier 	case 's':
138c0eadb1cSDavid du Colombier 		svc = EARGF(usage());
139c0eadb1cSDavid du Colombier 		break;
1407dd7cddfSDavid du Colombier 	case 'x':
141162f803dSDavid du Colombier 		setnetmtpt(net, sizeof net, EARGF(usage()));
1427dd7cddfSDavid du Colombier 		break;
1433e12c5d1SDavid du Colombier 	default:
1447dd7cddfSDavid du Colombier 		usage();
1453e12c5d1SDavid du Colombier 	}ARGEND
1463e12c5d1SDavid du Colombier 
147219b2ee8SDavid du Colombier 	snprint(buf, sizeof buf, "%s/", dir);
148219b2ee8SDavid du Colombier 	dirsl = strdup(buf);
149219b2ee8SDavid du Colombier 	dirsllen = strlen(dirsl);
150219b2ee8SDavid du Colombier 
1519a747e4fSDavid du Colombier 	fmtinstall('E', eipfmt);
1529a747e4fSDavid du Colombier 	fmtinstall('I', eipfmt);
1533e12c5d1SDavid du Colombier 
15464eb6bc1SDavid du Colombier 	/*
15564eb6bc1SDavid du Colombier 	 * setuser calls newns, and typical /lib/namespace files contain
15664eb6bc1SDavid du Colombier 	 * "cd /usr/$user", so call setuser before chdir.
15764eb6bc1SDavid du Colombier 	 */
15864eb6bc1SDavid du Colombier 	setuser();
1593e12c5d1SDavid du Colombier 	if(chdir(dir) < 0)
1607dd7cddfSDavid du Colombier 		sysfatal("can't get to directory %s: %r", dir);
1613e12c5d1SDavid du Colombier 
1627dd7cddfSDavid du Colombier 	if(!dbg)
1633e12c5d1SDavid du Colombier 		switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
1643e12c5d1SDavid du Colombier 		case -1:
1657dd7cddfSDavid du Colombier 			sysfatal("fork: %r");
1663e12c5d1SDavid du Colombier 		case 0:
1673e12c5d1SDavid du Colombier 			break;
1683e12c5d1SDavid du Colombier 		default:
1693e12c5d1SDavid du Colombier 			exits(0);
1703e12c5d1SDavid du Colombier 		}
1713e12c5d1SDavid du Colombier 
172c0eadb1cSDavid du Colombier 	snprint(buf, sizeof buf, "%s/udp!*!%s", net, svc);
1737dd7cddfSDavid du Colombier 	cfd = announce(buf, adir);
174c0eadb1cSDavid du Colombier 	if (cfd < 0)
175c0eadb1cSDavid du Colombier 		sysfatal("announcing on %s: %r", buf);
176c0eadb1cSDavid du Colombier 	syslog(dbg, flog, "tftpd started on %s dir %s", buf, adir);
17764eb6bc1SDavid du Colombier //	setuser();
1783e12c5d1SDavid du Colombier 	for(;;) {
1797dd7cddfSDavid du Colombier 		lcfd = listen(adir, ldir);
1807dd7cddfSDavid du Colombier 		if(lcfd < 0)
181c0eadb1cSDavid du Colombier 			sysfatal("listening on %s: %r", adir);
1827dd7cddfSDavid du Colombier 
1837dd7cddfSDavid du Colombier 		switch(fork()) {
1847dd7cddfSDavid du Colombier 		case -1:
1857dd7cddfSDavid du Colombier 			sysfatal("fork: %r");
1867dd7cddfSDavid du Colombier 		case 0:
187c0eadb1cSDavid du Colombier 			dfd = accept(lcfd, ldir);
1887dd7cddfSDavid du Colombier 			if(dfd < 0)
1897dd7cddfSDavid du Colombier  				exits(0);
1907dd7cddfSDavid du Colombier 			remoteaddr(ldir, raddr, sizeof(raddr));
1913e5d0078SDavid du Colombier 			pid = getpid();
1923e5d0078SDavid du Colombier 			syslog(0, flog, "tftp %d connection from %s dir %s",
1933e5d0078SDavid du Colombier 				pid, raddr, ldir);
1947dd7cddfSDavid du Colombier 			doserve(dfd);
1957dd7cddfSDavid du Colombier 			exits("done");
1967dd7cddfSDavid du Colombier 			break;
1977dd7cddfSDavid du Colombier 		default:
1987dd7cddfSDavid du Colombier 			close(lcfd);
1997dd7cddfSDavid du Colombier 			continue;
2007dd7cddfSDavid du Colombier 		}
2017dd7cddfSDavid du Colombier 	}
2027dd7cddfSDavid du Colombier }
2037dd7cddfSDavid du Colombier 
2043e5d0078SDavid du Colombier static Opt *
2053e5d0078SDavid du Colombier handleopt(int fd, char *name, char *val)
2063e5d0078SDavid du Colombier {
2073e5d0078SDavid du Colombier 	int n;
2083e5d0078SDavid du Colombier 	Opt *op;
2093e5d0078SDavid du Colombier 
2103e5d0078SDavid du Colombier 	for (op = option; op < option + nelem(option); op++)
2113e5d0078SDavid du Colombier 		if(cistrcmp(name, op->name) == 0) {
2123e5d0078SDavid du Colombier 			n = strtol(val, nil, 10);
2133e5d0078SDavid du Colombier 			if (n < op->min || n > op->max) {
2143e5d0078SDavid du Colombier 				nak(fd, Errbadopt, "option value out of range");
2153e5d0078SDavid du Colombier 				syslog(dbg, flog, "tftp bad option value from "
2163e5d0078SDavid du Colombier 					"client: %s %s", name, val);
2173e5d0078SDavid du Colombier 				sysfatal("bad option value from client: %s %s",
2183e5d0078SDavid du Colombier 					name, val);
2193e5d0078SDavid du Colombier 			}
2203e5d0078SDavid du Colombier 			*op->valp = n;
2213e5d0078SDavid du Colombier 			syslog(dbg, flog, "tftpd %d setting %s to %d",
2223e5d0078SDavid du Colombier 				pid, name, n);
2233e5d0078SDavid du Colombier 			return op;
2243e5d0078SDavid du Colombier 		}
2253e5d0078SDavid du Colombier 	return nil;
2263e5d0078SDavid du Colombier }
2273e5d0078SDavid du Colombier 
2283e5d0078SDavid du Colombier static vlong
2293e5d0078SDavid du Colombier filesize(char *file)
2303e5d0078SDavid du Colombier {
2313e5d0078SDavid du Colombier 	vlong size;
2323e5d0078SDavid du Colombier 	Dir *dp;
2333e5d0078SDavid du Colombier 
2343e5d0078SDavid du Colombier 	dp = dirstat(file);
2353e5d0078SDavid du Colombier 	if (dp == nil)
2363e5d0078SDavid du Colombier 		return 0;
2373e5d0078SDavid du Colombier 	size = dp->length;
2383e5d0078SDavid du Colombier 	free(dp);
2393e5d0078SDavid du Colombier 	return size;
2403e5d0078SDavid du Colombier }
2413e5d0078SDavid du Colombier 
2423e5d0078SDavid du Colombier static int
2433e5d0078SDavid du Colombier options(int fd, char *buf, char *file, ushort oper, char *p, int dlen)
2443e5d0078SDavid du Colombier {
2453e5d0078SDavid du Colombier 	int nmlen, vallen, nopts;
2463e5d0078SDavid du Colombier 	vlong size;
2473e5d0078SDavid du Colombier 	char *val, *bp;
2483e5d0078SDavid du Colombier 	Opt *op;
2493e5d0078SDavid du Colombier 
2503e5d0078SDavid du Colombier 	buf[0] = 0;
2513e5d0078SDavid du Colombier 	buf[1] = Tftp_OACK;
2523e5d0078SDavid du Colombier 	bp = buf + Opsize;
2533e5d0078SDavid du Colombier 	nopts = 0;
2543e5d0078SDavid du Colombier 	while (dlen > 0 && *p != '\0') {
2553e5d0078SDavid du Colombier 		nmlen = strlen(p) + 1;		/* include NUL */
2563e5d0078SDavid du Colombier 		if (nmlen > dlen)
2573e5d0078SDavid du Colombier 			break;
2583e5d0078SDavid du Colombier 		dlen -= nmlen;
2593e5d0078SDavid du Colombier 		val = p + nmlen;
2603e5d0078SDavid du Colombier 		if (dlen <= 0 || *val == '\0')
2613e5d0078SDavid du Colombier 			break;
2623e5d0078SDavid du Colombier 
2633e5d0078SDavid du Colombier 		vallen = strlen(val) + 1;
2643e5d0078SDavid du Colombier 		if (vallen > dlen)
2653e5d0078SDavid du Colombier 			break;
2663e5d0078SDavid du Colombier 		dlen -= vallen;
2673e5d0078SDavid du Colombier 
2683e5d0078SDavid du Colombier 		nopts++;
2693e5d0078SDavid du Colombier 		op = handleopt(fd, p, val);
2703e5d0078SDavid du Colombier 		if (op) {
2713e5d0078SDavid du Colombier 			/* append OACK response to buf */
2723e5d0078SDavid du Colombier 			sprint(bp, "%s", p);
2733e5d0078SDavid du Colombier 			bp += nmlen;
2743e5d0078SDavid du Colombier 			if (oper == Tftp_READ && cistrcmp(p, "tsize") == 0) {
2753e5d0078SDavid du Colombier 				size = filesize(file);
2763e5d0078SDavid du Colombier 				sprint(bp, "%lld", size);
2773e5d0078SDavid du Colombier 				syslog(dbg, flog, "tftpd %d %s tsize is %,lld",
2783e5d0078SDavid du Colombier 					pid, file, size);
279*a4796b5cSDavid du Colombier 			}
280*a4796b5cSDavid du Colombier 			/*
281*a4796b5cSDavid du Colombier 			 * hack: bandt (viaducts) uses smaller mtu than ether's
282*a4796b5cSDavid du Colombier 			 * (1400 bytes for tcp mss of 1300 bytes),
283*a4796b5cSDavid du Colombier 			 * so offer at most bandt's mtu minus headers,
284*a4796b5cSDavid du Colombier 			 * to avoid failure of pxe booting via viaduct.
285*a4796b5cSDavid du Colombier 			 */
286*a4796b5cSDavid du Colombier 			else if (oper == Tftp_READ &&
287*a4796b5cSDavid du Colombier 			    cistrcmp(p, "blksize") == 0 &&
288*a4796b5cSDavid du Colombier 			    blksize > Bandtblksz) {
289*a4796b5cSDavid du Colombier 				blksize = Bandtblksz;
290*a4796b5cSDavid du Colombier 				sprint(bp, "%d", blksize);
291*a4796b5cSDavid du Colombier 				syslog(dbg, flog,
292*a4796b5cSDavid du Colombier 					"tftpd %d overriding blksize to %d",
293*a4796b5cSDavid du Colombier 					pid, blksize);
2943e5d0078SDavid du Colombier 			} else
295*a4796b5cSDavid du Colombier 				strcpy(bp, val);  /* use requested value */
2963e5d0078SDavid du Colombier 			bp += strlen(bp) + 1;
2973e5d0078SDavid du Colombier 		}
2983e5d0078SDavid du Colombier 		p = val + vallen;
2993e5d0078SDavid du Colombier 	}
3003e5d0078SDavid du Colombier 	if (nopts == 0)
3013e5d0078SDavid du Colombier 		return 0;		/* no options actually seen */
3023e5d0078SDavid du Colombier 	*bp++ = '\0';
3033e5d0078SDavid du Colombier 	*bp++ = '\0';			/* overkill */
3043e5d0078SDavid du Colombier 	*bp++ = '\0';
3053e5d0078SDavid du Colombier 	if (write(fd, buf, bp - buf) < bp - buf) {
3063e5d0078SDavid du Colombier 		syslog(dbg, flog, "tftpd network write error on oack to %s: %r",
3073e5d0078SDavid du Colombier 			raddr);
3083e5d0078SDavid du Colombier 		sysfatal("tftpd: network write error: %r");
3093e5d0078SDavid du Colombier 	}
3103e5d0078SDavid du Colombier 	if(Debug)
3113e5d0078SDavid du Colombier 		syslog(dbg, flog, "tftpd oack: options to %s", raddr);
3123e5d0078SDavid du Colombier 	return nopts;
3133e5d0078SDavid du Colombier }
3143e5d0078SDavid du Colombier 
3153e5d0078SDavid du Colombier /* this doesn't stop the cavium from barging ahead */
3163e5d0078SDavid du Colombier //static void
3173e5d0078SDavid du Colombier //sendnoopts(int fd, char *name)
3183e5d0078SDavid du Colombier //{
3193e5d0078SDavid du Colombier //	char buf[64];
3203e5d0078SDavid du Colombier //
3213e5d0078SDavid du Colombier //	memset(buf, 0, sizeof buf);
3223e5d0078SDavid du Colombier //	buf[0] = 0;
3233e5d0078SDavid du Colombier //	buf[1] = Tftp_OACK;
3243e5d0078SDavid du Colombier //
3253e5d0078SDavid du Colombier //	if(write(fd, buf, sizeof buf) < sizeof buf) {
3263e5d0078SDavid du Colombier //		syslog(dbg, flog, "tftpd network write error on %s oack to %s: %r",
3273e5d0078SDavid du Colombier //			name, raddr);
3283e5d0078SDavid du Colombier //		sysfatal("tftpd: network write error: %r");
3293e5d0078SDavid du Colombier //	}
3303e5d0078SDavid du Colombier //	if(Debug)
3313e5d0078SDavid du Colombier //		syslog(dbg, flog, "tftpd oack: no options to %s", raddr);
3323e5d0078SDavid du Colombier //}
3333e5d0078SDavid du Colombier 
3343e5d0078SDavid du Colombier static void
3353e5d0078SDavid du Colombier optlog(char *bytes, char *p, int dlen)
3363e5d0078SDavid du Colombier {
3373e5d0078SDavid du Colombier 	char *bp;
3383e5d0078SDavid du Colombier 
3393e5d0078SDavid du Colombier 	bp = bytes;
3403e5d0078SDavid du Colombier 	sprint(bp, "tftpd %d option bytes: ", dlen);
3413e5d0078SDavid du Colombier 	bp += strlen(bp);
3423e5d0078SDavid du Colombier 	for (; dlen > 0; dlen--, p++)
3433e5d0078SDavid du Colombier 		*bp++ = *p? *p: ' ';
3443e5d0078SDavid du Colombier 	*bp = '\0';
3453e5d0078SDavid du Colombier 	syslog(dbg, flog, "%s", bytes);
3463e5d0078SDavid du Colombier }
3473e5d0078SDavid du Colombier 
3487dd7cddfSDavid du Colombier void
3497dd7cddfSDavid du Colombier doserve(int fd)
3507dd7cddfSDavid du Colombier {
3513e5d0078SDavid du Colombier 	int dlen, opts;
3523e5d0078SDavid du Colombier 	char *mode, *p, *file;
3537dd7cddfSDavid du Colombier 	short op;
3547dd7cddfSDavid du Colombier 
3553e5d0078SDavid du Colombier 	dlen = read(fd, bigbuf, sizeof(bigbuf)-1);
3563e12c5d1SDavid du Colombier 	if(dlen < 0)
3577dd7cddfSDavid du Colombier 		sysfatal("listen read: %r");
3583e12c5d1SDavid du Colombier 
3593e5d0078SDavid du Colombier 	bigbuf[dlen] = '\0';
3607dd7cddfSDavid du Colombier 	op = (bigbuf[0]<<8) | bigbuf[1];
3613e5d0078SDavid du Colombier 	dlen -= Opsize;
3623e5d0078SDavid du Colombier 	mode = file = bigbuf + Opsize;
3633e12c5d1SDavid du Colombier 	while(*mode != '\0' && dlen--)
3643e12c5d1SDavid du Colombier 		mode++;
3653e12c5d1SDavid du Colombier 	mode++;
3663e12c5d1SDavid du Colombier 	p = mode;
3673e12c5d1SDavid du Colombier 	while(*p && dlen--)
3683e12c5d1SDavid du Colombier 		p++;
3693e12c5d1SDavid du Colombier 	if(dlen == 0) {
3707dd7cddfSDavid du Colombier 		nak(fd, 0, "bad tftpmode");
3717dd7cddfSDavid du Colombier 		close(fd);
3723e5d0078SDavid du Colombier 		syslog(dbg, flog, "tftpd %d bad mode %s for file %s from %s",
3733e5d0078SDavid du Colombier 			pid, mode, file, raddr);
3747dd7cddfSDavid du Colombier 		return;
3753e12c5d1SDavid du Colombier 	}
3763e12c5d1SDavid du Colombier 
3773e12c5d1SDavid du Colombier 	if(op != Tftp_READ && op != Tftp_WRITE) {
3783e5d0078SDavid du Colombier 		nak(fd, Errbadop, "Illegal TFTP operation");
3797dd7cddfSDavid du Colombier 		close(fd);
3803e5d0078SDavid du Colombier 		syslog(dbg, flog, "tftpd %d bad request %d %s", pid, op, raddr);
3817dd7cddfSDavid du Colombier 		return;
3823e12c5d1SDavid du Colombier 	}
3837dd7cddfSDavid du Colombier 
384219b2ee8SDavid du Colombier 	if(restricted){
3853e5d0078SDavid du Colombier 		if(file[0] == '#' || strncmp(file, "../", 3) == 0 ||
3863e5d0078SDavid du Colombier 		  strstr(file, "/../") != nil ||
3873e5d0078SDavid du Colombier 		  (file[0] == '/' && strncmp(file, dirsl, dirsllen) != 0)){
3883e5d0078SDavid du Colombier 			nak(fd, Errnoaccess, "Permission denied");
3897dd7cddfSDavid du Colombier 			close(fd);
3903e5d0078SDavid du Colombier 			syslog(dbg, flog, "tftpd %d bad request %d from %s file %s",
3913e5d0078SDavid du Colombier 				pid, op, raddr, file);
3927dd7cddfSDavid du Colombier 			return;
393219b2ee8SDavid du Colombier 		}
394219b2ee8SDavid du Colombier 	}
3957dd7cddfSDavid du Colombier 
3963e5d0078SDavid du Colombier 	/*
3973e5d0078SDavid du Colombier 	 * options are supposed to be negotiated, but the cavium board's
3983e5d0078SDavid du Colombier 	 * u-boot really wants us to use a block size of 1432 bytes and won't
3993e5d0078SDavid du Colombier 	 * take `no' for an answer.
4003e5d0078SDavid du Colombier 	 */
4013e5d0078SDavid du Colombier 	p++;				/* skip NUL after mode */
4023e5d0078SDavid du Colombier 	dlen--;
4033e5d0078SDavid du Colombier 	opts = 0;
4043e5d0078SDavid du Colombier 	if(dlen > 0) {			/* might have options */
4053e5d0078SDavid du Colombier 		char bytes[32*1024];
4063e5d0078SDavid du Colombier 
4073e5d0078SDavid du Colombier 		if(Debug)
4083e5d0078SDavid du Colombier 			optlog(bytes, p, dlen);
4093e5d0078SDavid du Colombier 		opts = options(fd, bytes, file, op, p, dlen);
4103e5d0078SDavid du Colombier 	}
4113e12c5d1SDavid du Colombier 	if(op == Tftp_READ)
4123e5d0078SDavid du Colombier 		sendfile(fd, file, mode, opts);
4133e12c5d1SDavid du Colombier 	else
4143e5d0078SDavid du Colombier 		recvfile(fd, file, mode);
4153e12c5d1SDavid du Colombier }
4163e12c5d1SDavid du Colombier 
4173e12c5d1SDavid du Colombier void
4183e12c5d1SDavid du Colombier catcher(void *junk, char *msg)
4193e12c5d1SDavid du Colombier {
4203e12c5d1SDavid du Colombier 	USED(junk);
4213e12c5d1SDavid du Colombier 
4223e12c5d1SDavid du Colombier 	if(strncmp(msg, "exit", 4) == 0)
4233e12c5d1SDavid du Colombier 		noted(NDFLT);
4243e12c5d1SDavid du Colombier 	noted(NCONT);
4253e12c5d1SDavid du Colombier }
4263e12c5d1SDavid du Colombier 
4273e5d0078SDavid du Colombier static int
4283e5d0078SDavid du Colombier awaitack(int fd, int block)
4293e12c5d1SDavid du Colombier {
4303e5d0078SDavid du Colombier 	int ackblock, al, rxl;
4313e5d0078SDavid du Colombier 	ushort op;
4323e12c5d1SDavid du Colombier 	uchar ack[1024];
4333e12c5d1SDavid du Colombier 
4343e5d0078SDavid du Colombier 	for(rxl = 0; rxl < 10; rxl++) {
4353e5d0078SDavid du Colombier 		memset(ack, 0, Hdrsize);
4363e5d0078SDavid du Colombier 		alarm(1000);
4373e5d0078SDavid du Colombier 		al = read(fd, ack, sizeof(ack));
4383e5d0078SDavid du Colombier 		alarm(0);
4393e5d0078SDavid du Colombier 		if(al < 0) {
4403e5d0078SDavid du Colombier 			if (Debug)
4413e5d0078SDavid du Colombier 				syslog(dbg, flog, "tftpd %d timed out "
4423e5d0078SDavid du Colombier 					"waiting for ack from %s", pid, raddr);
4433e5d0078SDavid du Colombier 			return Ackrexmit;
4443e5d0078SDavid du Colombier 		}
4453e5d0078SDavid du Colombier 		op = ack[0]<<8|ack[1];
4463e5d0078SDavid du Colombier 		if(op == Tftp_ERROR) {
4473e5d0078SDavid du Colombier 			if (Debug)
4483e5d0078SDavid du Colombier 				syslog(dbg, flog, "tftpd %d got error "
4493e5d0078SDavid du Colombier 					"waiting for ack from %s", pid, raddr);
4503e5d0078SDavid du Colombier 			return Ackerr;
4513e5d0078SDavid du Colombier 		} else if(op != Tftp_ACK) {
4523e5d0078SDavid du Colombier 			syslog(dbg, flog, "tftpd %d rcvd %s op from %s", pid,
4533e5d0078SDavid du Colombier 				(op < nelem(opnames)? opnames[op]: "gok"),
4543e5d0078SDavid du Colombier 				raddr);
4553e5d0078SDavid du Colombier 			return Ackerr;
4563e5d0078SDavid du Colombier 		}
4573e5d0078SDavid du Colombier 		ackblock = ack[2]<<8|ack[3];
4583e5d0078SDavid du Colombier 		if (Debug)
4593e5d0078SDavid du Colombier 			syslog(dbg, flog, "tftpd %d read ack of %d bytes "
4603e5d0078SDavid du Colombier 				"for block %d", pid, al, ackblock);
4613e5d0078SDavid du Colombier 		if(ackblock == block)
4623e5d0078SDavid du Colombier 			return Ackok;		/* for block just sent */
4633e5d0078SDavid du Colombier 		else if(ackblock == block + 1)	/* intel pxe eof bug */
4643e5d0078SDavid du Colombier 			return Ackok;
4653e5d0078SDavid du Colombier 		else if(ackblock == 0xffff)
4663e5d0078SDavid du Colombier 			return Ackrexmit;
4673e5d0078SDavid du Colombier 		else
4683e5d0078SDavid du Colombier 			/* ack is for some other block; ignore it, try again */
4693e5d0078SDavid du Colombier 			syslog(dbg, flog, "tftpd %d expected ack for block %d, "
4703e5d0078SDavid du Colombier 				"got %d", pid, block, ackblock);
4713e5d0078SDavid du Colombier 	}
4723e5d0078SDavid du Colombier 	return Ackrexmit;
4733e5d0078SDavid du Colombier }
4743e5d0078SDavid du Colombier 
4753e5d0078SDavid du Colombier void
4763e5d0078SDavid du Colombier sendfile(int fd, char *name, char *mode, int opts)
4773e5d0078SDavid du Colombier {
4783e5d0078SDavid du Colombier 	int file, block, ret, rexmit, n, txtry;
4793e5d0078SDavid du Colombier 	uchar buf[Maxsegsize+Hdrsize];
4803e5d0078SDavid du Colombier 	char errbuf[Maxerr];
4813e5d0078SDavid du Colombier 
4823e5d0078SDavid du Colombier 	syslog(dbg, flog, "tftpd %d send file '%s' %s to %s",
4833e5d0078SDavid du Colombier 		pid, name, mode, raddr);
4843e12c5d1SDavid du Colombier 	name = sunkernel(name);
4853e12c5d1SDavid du Colombier 	if(name == 0){
4863e12c5d1SDavid du Colombier 		nak(fd, 0, "not in our database");
4873e12c5d1SDavid du Colombier 		return;
4883e12c5d1SDavid du Colombier 	}
4893e12c5d1SDavid du Colombier 
4903e12c5d1SDavid du Colombier 	notify(catcher);
4913e12c5d1SDavid du Colombier 
4923e12c5d1SDavid du Colombier 	file = open(name, OREAD);
4933e12c5d1SDavid du Colombier 	if(file < 0) {
4949a747e4fSDavid du Colombier 		errstr(errbuf, sizeof errbuf);
4953e12c5d1SDavid du Colombier 		nak(fd, 0, errbuf);
4963e12c5d1SDavid du Colombier 		return;
4973e12c5d1SDavid du Colombier 	}
4983e12c5d1SDavid du Colombier 	block = 0;
4993e5d0078SDavid du Colombier 	rexmit = Ackok;
5003e12c5d1SDavid du Colombier 	n = 0;
5013e5d0078SDavid du Colombier 	/*
5023e5d0078SDavid du Colombier 	 * if we sent an oack previously, wait for the client's ack or error.
5033e5d0078SDavid du Colombier 	 * if we get no ack for our oack, it could be that we returned
5043e5d0078SDavid du Colombier 	 * a tsize that the client can't handle, or it could be intel
5053e5d0078SDavid du Colombier 	 * pxe just read-with-tsize to get size, couldn't be bothered to
5063e5d0078SDavid du Colombier 	 * ack our oack and has just gone ahead and issued another read.
5073e5d0078SDavid du Colombier 	 */
5083e5d0078SDavid du Colombier 	if(opts && awaitack(fd, 0) != Ackok)
5093e5d0078SDavid du Colombier 		goto error;
5103e5d0078SDavid du Colombier 
5113e5d0078SDavid du Colombier 	for(txtry = 0; txtry < timeout;) {
5123e5d0078SDavid du Colombier 		if(rexmit == Ackok) {
5133e12c5d1SDavid du Colombier 			block++;
5143e12c5d1SDavid du Colombier 			buf[0] = 0;
5153e12c5d1SDavid du Colombier 			buf[1] = Tftp_DATA;
5163e12c5d1SDavid du Colombier 			buf[2] = block>>8;
5173e12c5d1SDavid du Colombier 			buf[3] = block;
5183e5d0078SDavid du Colombier 			n = read(file, buf+Hdrsize, blksize);
5193e12c5d1SDavid du Colombier 			if(n < 0) {
5209a747e4fSDavid du Colombier 				errstr(errbuf, sizeof errbuf);
5213e12c5d1SDavid du Colombier 				nak(fd, 0, errbuf);
5223e12c5d1SDavid du Colombier 				return;
5233e12c5d1SDavid du Colombier 			}
524219b2ee8SDavid du Colombier 			txtry = 0;
5253e12c5d1SDavid du Colombier 		}
5267dd7cddfSDavid du Colombier 		else {
5273e5d0078SDavid du Colombier 			syslog(dbg, flog, "tftpd %d rexmit %d %s:%d to %s",
5283e5d0078SDavid du Colombier 				pid, Hdrsize+n, name, block, raddr);
5293e12c5d1SDavid du Colombier 			txtry++;
5307dd7cddfSDavid du Colombier 		}
5317dd7cddfSDavid du Colombier 
5323e5d0078SDavid du Colombier 		ret = write(fd, buf, Hdrsize+n);
5333e5d0078SDavid du Colombier 		if(ret < Hdrsize+n) {
5343e5d0078SDavid du Colombier 			syslog(dbg, flog,
5353e5d0078SDavid du Colombier 				"tftpd network write error on %s to %s: %r",
5363e5d0078SDavid du Colombier 				name, raddr);
5377dd7cddfSDavid du Colombier 			sysfatal("tftpd: network write error: %r");
5383e5d0078SDavid du Colombier 		}
5393e5d0078SDavid du Colombier 		if (Debug)
5403e5d0078SDavid du Colombier 			syslog(dbg, flog, "tftpd %d sent block %d", pid, block);
5413e12c5d1SDavid du Colombier 
5423e5d0078SDavid du Colombier 		rexmit = awaitack(fd, block);
5433e5d0078SDavid du Colombier 		if (rexmit == Ackerr)
5443e12c5d1SDavid du Colombier 			break;
5453e5d0078SDavid du Colombier 		if(ret != blksize+Hdrsize && rexmit == Ackok)
5463e12c5d1SDavid du Colombier 			break;
5473e12c5d1SDavid du Colombier 	}
5483e12c5d1SDavid du Colombier error:
5493e12c5d1SDavid du Colombier 	close(fd);
5503e12c5d1SDavid du Colombier 	close(file);
5513e12c5d1SDavid du Colombier }
5523e12c5d1SDavid du Colombier 
5533e12c5d1SDavid du Colombier void
5543e12c5d1SDavid du Colombier recvfile(int fd, char *name, char *mode)
5553e12c5d1SDavid du Colombier {
5563e12c5d1SDavid du Colombier 	ushort op, block, inblock;
5573e5d0078SDavid du Colombier 	uchar buf[Maxsegsize+8];
5589a747e4fSDavid du Colombier 	char errbuf[Maxerr];
5593e12c5d1SDavid du Colombier 	int n, ret, file;
5603e12c5d1SDavid du Colombier 
5613e12c5d1SDavid du Colombier 	syslog(dbg, flog, "receive file '%s' %s from %s", name, mode, raddr);
5623e12c5d1SDavid du Colombier 
5633e12c5d1SDavid du Colombier 	file = create(name, OWRITE, 0666);
5643e12c5d1SDavid du Colombier 	if(file < 0) {
5659a747e4fSDavid du Colombier 		errstr(errbuf, sizeof errbuf);
5663e12c5d1SDavid du Colombier 		nak(fd, 0, errbuf);
567162f803dSDavid du Colombier 		syslog(dbg, flog, "can't create %s: %r", name);
5683e12c5d1SDavid du Colombier 		return;
5693e12c5d1SDavid du Colombier 	}
5703e12c5d1SDavid du Colombier 
5713e12c5d1SDavid du Colombier 	block = 0;
5723e12c5d1SDavid du Colombier 	ack(fd, block);
5733e12c5d1SDavid du Colombier 	block++;
5743e12c5d1SDavid du Colombier 
5753e12c5d1SDavid du Colombier 	for (;;) {
5763e12c5d1SDavid du Colombier 		alarm(15000);
5773e5d0078SDavid du Colombier 		n = read(fd, buf, blksize+8);
5783e12c5d1SDavid du Colombier 		alarm(0);
579162f803dSDavid du Colombier 		if(n < 0) {
580162f803dSDavid du Colombier 			syslog(dbg, flog, "tftpd: network error reading %s: %r",
581162f803dSDavid du Colombier 				name);
5823e12c5d1SDavid du Colombier 			goto error;
583162f803dSDavid du Colombier 		}
584162f803dSDavid du Colombier 		if(n <= Hdrsize) {
585162f803dSDavid du Colombier 			syslog(dbg, flog,
586162f803dSDavid du Colombier 				"tftpd: short read from network, reading %s",
587162f803dSDavid du Colombier 				name);
588162f803dSDavid du Colombier 			goto error;
589162f803dSDavid du Colombier 		}
5903e12c5d1SDavid du Colombier 		op = buf[0]<<8|buf[1];
591162f803dSDavid du Colombier 		if(op == Tftp_ERROR) {
592162f803dSDavid du Colombier 			syslog(dbg, flog, "tftpd: tftp error reading %s", name);
5933e12c5d1SDavid du Colombier 			goto error;
594162f803dSDavid du Colombier 		}
5953e12c5d1SDavid du Colombier 
596162f803dSDavid du Colombier 		n -= Hdrsize;
5973e12c5d1SDavid du Colombier 		inblock = buf[2]<<8|buf[3];
5983e12c5d1SDavid du Colombier 		if(op == Tftp_DATA) {
5993e12c5d1SDavid du Colombier 			if(inblock == block) {
600162f803dSDavid du Colombier 				ret = write(file, buf+Hdrsize, n);
601162f803dSDavid du Colombier 				if(ret != n) {
6029a747e4fSDavid du Colombier 					errstr(errbuf, sizeof errbuf);
6033e12c5d1SDavid du Colombier 					nak(fd, 0, errbuf);
604162f803dSDavid du Colombier 					syslog(dbg, flog,
605162f803dSDavid du Colombier 					    "tftpd: error writing %s: %s",
606162f803dSDavid du Colombier 						name, errbuf);
6073e12c5d1SDavid du Colombier 					goto error;
6083e12c5d1SDavid du Colombier 				}
6093e12c5d1SDavid du Colombier 				ack(fd, block);
6103e12c5d1SDavid du Colombier 				block++;
61178307a5fSDavid du Colombier 			} else
61278307a5fSDavid du Colombier 				ack(fd, 0xffff);	/* tell him to resend */
6133e12c5d1SDavid du Colombier 		}
6143e12c5d1SDavid du Colombier 	}
6153e12c5d1SDavid du Colombier error:
6163e12c5d1SDavid du Colombier 	close(file);
6173e12c5d1SDavid du Colombier }
6183e12c5d1SDavid du Colombier 
6193e12c5d1SDavid du Colombier void
6203e12c5d1SDavid du Colombier ack(int fd, ushort block)
6213e12c5d1SDavid du Colombier {
6223e12c5d1SDavid du Colombier 	uchar ack[4];
6233e12c5d1SDavid du Colombier 	int n;
6243e12c5d1SDavid du Colombier 
6253e12c5d1SDavid du Colombier 	ack[0] = 0;
6263e12c5d1SDavid du Colombier 	ack[1] = Tftp_ACK;
6273e12c5d1SDavid du Colombier 	ack[2] = block>>8;
6283e12c5d1SDavid du Colombier 	ack[3] = block;
6293e12c5d1SDavid du Colombier 
6303e12c5d1SDavid du Colombier 	n = write(fd, ack, 4);
631162f803dSDavid du Colombier 	if(n < 4)
6327dd7cddfSDavid du Colombier 		sysfatal("network write: %r");
6333e12c5d1SDavid du Colombier }
6343e12c5d1SDavid du Colombier 
6353e12c5d1SDavid du Colombier void
6363e12c5d1SDavid du Colombier nak(int fd, int code, char *msg)
6373e12c5d1SDavid du Colombier {
6383e12c5d1SDavid du Colombier 	char buf[128];
6393e12c5d1SDavid du Colombier 	int n;
6403e12c5d1SDavid du Colombier 
6413e12c5d1SDavid du Colombier 	buf[0] = 0;
6423e12c5d1SDavid du Colombier 	buf[1] = Tftp_ERROR;
6433e12c5d1SDavid du Colombier 	buf[2] = 0;
6443e12c5d1SDavid du Colombier 	buf[3] = code;
6453e12c5d1SDavid du Colombier 	strcpy(buf+4, msg);
6463e12c5d1SDavid du Colombier 	n = strlen(msg) + 4 + 1;
6473e5d0078SDavid du Colombier 	if(write(fd, buf, n) < n)
6487dd7cddfSDavid du Colombier 		sysfatal("write nak: %r");
6493e12c5d1SDavid du Colombier }
6503e12c5d1SDavid du Colombier 
6513e12c5d1SDavid du Colombier void
6523e12c5d1SDavid du Colombier setuser(void)
6533e12c5d1SDavid du Colombier {
654c0eadb1cSDavid du Colombier 	int fd;
6553e12c5d1SDavid du Colombier 
656c0eadb1cSDavid du Colombier 	fd = open("#c/user", OWRITE);
657c0eadb1cSDavid du Colombier 	if(fd < 0 || write(fd, "none", strlen("none")) < 0)
658c0eadb1cSDavid du Colombier 		sysfatal("can't become none: %r");
659c0eadb1cSDavid du Colombier 	close(fd);
660c0eadb1cSDavid du Colombier 	if(newns("none", nil) < 0)
661c0eadb1cSDavid du Colombier 		sysfatal("can't build namespace: %r");
6623e12c5d1SDavid du Colombier }
6633e12c5d1SDavid du Colombier 
6647dd7cddfSDavid du Colombier char*
665da51d93aSDavid du Colombier lookup(char *sattr, char *sval, char *tattr, char *tval, int len)
6667dd7cddfSDavid du Colombier {
6677dd7cddfSDavid du Colombier 	static Ndb *db;
6687dd7cddfSDavid du Colombier 	char *attrs[1];
6697dd7cddfSDavid du Colombier 	Ndbtuple *t;
6707dd7cddfSDavid du Colombier 
6717dd7cddfSDavid du Colombier 	if(db == nil)
6727dd7cddfSDavid du Colombier 		db = ndbopen(0);
6737dd7cddfSDavid du Colombier 	if(db == nil)
6747dd7cddfSDavid du Colombier 		return nil;
6757dd7cddfSDavid du Colombier 
6767dd7cddfSDavid du Colombier 	if(sattr == nil)
6777dd7cddfSDavid du Colombier 		sattr = ipattr(sval);
6787dd7cddfSDavid du Colombier 
6797dd7cddfSDavid du Colombier 	attrs[0] = tattr;
6807dd7cddfSDavid du Colombier 	t = ndbipinfo(db, sattr, sval, attrs, 1);
6817dd7cddfSDavid du Colombier 	if(t == nil)
6827dd7cddfSDavid du Colombier 		return nil;
683da51d93aSDavid du Colombier 	strncpy(tval, t->val, len);
684da51d93aSDavid du Colombier 	tval[len-1] = 0;
6857dd7cddfSDavid du Colombier 	ndbfree(t);
6867dd7cddfSDavid du Colombier 	return tval;
6877dd7cddfSDavid du Colombier }
6887dd7cddfSDavid du Colombier 
6893e12c5d1SDavid du Colombier /*
6903e12c5d1SDavid du Colombier  *  for sun kernel boots, replace the requested file name with
6913e12c5d1SDavid du Colombier  *  a one from our database.  If the database doesn't specify a file,
6923e12c5d1SDavid du Colombier  *  don't answer.
6933e12c5d1SDavid du Colombier  */
6943e12c5d1SDavid du Colombier char*
6953e12c5d1SDavid du Colombier sunkernel(char *name)
6963e12c5d1SDavid du Colombier {
6973e12c5d1SDavid du Colombier 	ulong addr;
6987dd7cddfSDavid du Colombier 	uchar v4[IPv4addrlen];
6997dd7cddfSDavid du Colombier 	uchar v6[IPaddrlen];
700da51d93aSDavid du Colombier 	char buf[256];
701da51d93aSDavid du Colombier 	char ipbuf[128];
70227e10919SDavid du Colombier 	char *suffix;
7033e12c5d1SDavid du Colombier 
70427e10919SDavid du Colombier 	addr = strtoul(name, &suffix, 16);
70527e10919SDavid du Colombier 	if(suffix-name != 8 || (strcmp(suffix, "") != 0 && strcmp(suffix, ".SUN") != 0))
7063e12c5d1SDavid du Colombier 		return name;
7073e12c5d1SDavid du Colombier 
7087dd7cddfSDavid du Colombier 	v4[0] = addr>>24;
7097dd7cddfSDavid du Colombier 	v4[1] = addr>>16;
7107dd7cddfSDavid du Colombier 	v4[2] = addr>>8;
7117dd7cddfSDavid du Colombier 	v4[3] = addr;
7127dd7cddfSDavid du Colombier 	v4tov6(v6, v4);
7137dd7cddfSDavid du Colombier 	sprint(ipbuf, "%I", v6);
714da51d93aSDavid du Colombier 	return lookup("ip", ipbuf, "bootf", buf, sizeof buf);
7157dd7cddfSDavid du Colombier }
7167dd7cddfSDavid du Colombier 
7177dd7cddfSDavid du Colombier void
7187dd7cddfSDavid du Colombier remoteaddr(char *dir, char *raddr, int len)
7197dd7cddfSDavid du Colombier {
7207dd7cddfSDavid du Colombier 	char buf[64];
7217dd7cddfSDavid du Colombier 	int fd, n;
7227dd7cddfSDavid du Colombier 
7237dd7cddfSDavid du Colombier 	snprint(buf, sizeof(buf), "%s/remote", dir);
7247dd7cddfSDavid du Colombier 	fd = open(buf, OREAD);
7257dd7cddfSDavid du Colombier 	if(fd < 0){
7267dd7cddfSDavid du Colombier 		snprint(raddr, sizeof(raddr), "unknown");
7277dd7cddfSDavid du Colombier 		return;
7287dd7cddfSDavid du Colombier 	}
7297dd7cddfSDavid du Colombier 	n = read(fd, raddr, len-1);
7307dd7cddfSDavid du Colombier 	close(fd);
7317dd7cddfSDavid du Colombier 	if(n <= 0){
7327dd7cddfSDavid du Colombier 		snprint(raddr, sizeof(raddr), "unknown");
7337dd7cddfSDavid du Colombier 		return;
7347dd7cddfSDavid du Colombier 	}
7357dd7cddfSDavid du Colombier 	if(n > 0)
7367dd7cddfSDavid du Colombier 		n--;
7377dd7cddfSDavid du Colombier 	raddr[n] = 0;
7383e12c5d1SDavid du Colombier }
739