#include <auth.h>
#include <bio.h>
#include <ip.h>
-#include <ndb.h>
enum
{
Maxpath= 128,
- Maxerr= 256,
Debug= 0,
* 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;
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 *homedir = "/";
+char *nsfile = nil;
char flog[] = "ipboot";
char net[Maxpath];
dbg++;
break;
case 'h':
- dir = EARGF(usage());
+ homedir = EARGF(usage());
break;
case 'r':
restricted = 1;
case 'x':
setnetmtpt(net, sizeof net, EARGF(usage()));
break;
+ case 'n':
+ nsfile = EARGF(usage());
+ break;
default:
usage();
}ARGEND
- snprint(buf, sizeof buf, "%s/", dir);
- dirsl = strdup(buf);
- dirsllen = strlen(dirsl);
+ dirsllen = strlen(homedir);
+ while(dirsllen > 0 && homedir[dirsllen-1] == '/')
+ dirsllen--;
+ dirsl = smprint("%.*s/", utfnlen(homedir, dirsllen), homedir);
fmtinstall('E', eipfmt);
fmtinstall('I', eipfmt);
* "cd /usr/$user", so call setuser before chdir.
*/
setuser();
- if(chdir(dir) < 0)
- sysfatal("can't get to directory %s: %r", dir);
+ if(chdir(homedir) < 0)
+ sysfatal("can't get to directory %s: %r", homedir);
if(!dbg)
switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
name, val);
}
*op->valp = n;
- syslog(dbg, flog, "tftpd %d setting %s to %d",
- pid, name, 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;
dp = dirstat(file);
if (dp == nil)
- return 0;
+ return -1;
size = dp->length;
free(dp);
return size;
}
+/* copy word into bp iff it fits before ep, returns bytes to advance bp. */
+static int
+emits(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 int
+emitn(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 int
-options(int fd, char *buf, char *file, ushort oper, char *p, int dlen)
+options(int fd, char *buf, int bufsz, char *file, ushort oper, char *p, int dlen)
{
- int nmlen, vallen, nopts;
+ int nmlen, vallen, olen, nopts;
vlong size;
- char *val, *bp;
+ char *val, *bp, *ep;
Opt *op;
buf[0] = 0;
buf[1] = Tftp_OACK;
bp = buf + Opsize;
+ ep = buf + bufsz;
nopts = 0;
- while (dlen > 0 && *p != '\0') {
+ for (; dlen > 0 && *p != '\0'; p = val + vallen, bp += olen) {
nmlen = strlen(p) + 1; /* include NUL */
if (nmlen > dlen)
break;
break;
dlen -= vallen;
- nopts++;
+ olen = 0;
op = handleopt(fd, p, val);
- if (op) {
- /* append OACK response to buf */
- sprint(bp, "%s", p);
- bp += nmlen;
- if (oper == Tftp_READ && cistrcmp(p, "tsize") == 0) {
- size = filesize(file);
- sprint(bp, "%lld", size);
- syslog(dbg, flog, "tftpd %d %s tsize is %,lld",
- pid, file, size);
+ if (op == nil)
+ continue;
+
+ nopts++;
+
+ /* 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;
}
- /*
- * 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.
- */
- else if (oper == Tftp_READ &&
- cistrcmp(p, "blksize") == 0 &&
- blksize > Bandtblksz) {
- blksize = Bandtblksz;
- sprint(bp, "%d", blksize);
- syslog(dbg, flog,
- "tftpd %d overriding blksize to %d",
- pid, blksize);
- } else
- strcpy(bp, val); /* use requested value */
- bp += strlen(bp) + 1;
- }
- p = val + vallen;
+ *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 */
- *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);
return nopts;
}
-/* this doesn't stop the cavium from barging ahead */
-//static void
-//sendnoopts(int fd, char *name)
-//{
-// char buf[64];
-//
-// memset(buf, 0, sizeof buf);
-// buf[0] = 0;
-// buf[1] = Tftp_OACK;
-//
-// if(write(fd, buf, sizeof buf) < sizeof buf) {
-// syslog(dbg, flog, "tftpd network write error on %s oack to %s: %r",
-// name, raddr);
-// sysfatal("tftpd: network write error: %r");
-// }
-// if(Debug)
-// syslog(dbg, flog, "tftpd oack: no options to %s", raddr);
-//}
-
static void
optlog(char *bytes, char *p, int dlen)
{
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;
+}
+
void
doserve(int fd)
{
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);
if(op != Tftp_READ && op != Tftp_WRITE) {
nak(fd, Errbadop, "Illegal TFTP operation");
close(fd);
- syslog(dbg, flog, "tftpd %d bad request %d %s", pid, op, raddr);
+ syslog(dbg, flog, "tftpd %d bad request %d (%s) %s", pid, op,
+ (op < nelem(opnames)? opnames[op]: "gok"), raddr);
return;
}
if(Debug)
optlog(bytes, p, dlen);
- opts = options(fd, bytes, file, op, 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);
if (Debug)
syslog(dbg, flog, "tftpd %d read ack of %d bytes "
"for block %d", pid, al, ackblock);
- if(ackblock == block)
+ if(ackblock == (block & 0xffff))
return Ackok; /* for block just sent */
- else if(ackblock == block + 1) /* intel pxe eof bug */
+ else if(ackblock == (block + 1 & 0xffff)) /* intel pxe eof bug */
return Ackok;
else if(ackblock == 0xffff)
return Ackrexmit;
{
int file, block, ret, rexmit, n, txtry;
uchar buf[Maxsegsize+Hdrsize];
- char errbuf[Maxerr];
+ char errbuf[ERRMAX];
syslog(dbg, flog, "tftpd %d send file '%s' %s to %s",
pid, name, mode, raddr);
- name = sunkernel(name);
- if(name == 0){
- nak(fd, 0, "not in our database");
- return;
- }
notify(catcher);
if(file < 0) {
errstr(errbuf, sizeof errbuf);
nak(fd, 0, errbuf);
- return;
+ goto error;
}
block = 0;
rexmit = Ackok;
if(n < 0) {
errstr(errbuf, sizeof errbuf);
nak(fd, 0, errbuf);
- return;
+ goto error;
}
txtry = 0;
}
if(ret != blksize+Hdrsize && rexmit == Ackok)
break;
}
+ syslog(dbg, flog, "tftpd %d done sending file '%s' %s to %s",
+ pid, name, mode, raddr);
error:
close(fd);
close(file);
{
ushort op, block, inblock;
uchar buf[Maxsegsize+8];
- char errbuf[Maxerr];
+ char errbuf[ERRMAX];
int n, ret, file;
syslog(dbg, flog, "receive file '%s' %s from %s", name, mode, raddr);
if(file < 0) {
errstr(errbuf, sizeof errbuf);
nak(fd, 0, errbuf);
- syslog(dbg, flog, "can't create %s: %r", name);
+ syslog(dbg, flog, "can't create %s: %s", name, errbuf);
return;
}
name);
goto error;
}
- if(n <= Hdrsize) {
+ /*
+ * NB: not `<='; just a header is legal and happens when
+ * file being read is a multiple of segment-size bytes long.
+ */
+ if(n < Hdrsize) {
syslog(dbg, flog,
"tftpd: short read from network, reading %s",
name);
char buf[128];
int n;
+ n = 5 + strlen(msg);
+ if(n > sizeof(buf))
+ n = sizeof(buf);
buf[0] = 0;
buf[1] = Tftp_ERROR;
buf[2] = 0;
buf[3] = code;
- strcpy(buf+4, msg);
- n = strlen(msg) + 4 + 1;
- if(write(fd, buf, n) < n)
+ memmove(buf+4, msg, n - 5);
+ buf[n-1] = 0;
+ if(write(fd, buf, n) != n)
sysfatal("write nak: %r");
}
void
setuser(void)
{
- int fd;
-
- fd = open("#c/user", OWRITE);
- if(fd < 0 || write(fd, "none", strlen("none")) < 0)
+ if(procsetuser("none") < 0)
sysfatal("can't become none: %r");
- close(fd);
- if(newns("none", nil) < 0)
+ if(newns("none", nsfile) < 0)
sysfatal("can't build namespace: %r");
}
-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)
{