]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/ip/tftpd.c
ip/tftpd: add -n namespace-file flag (thanks sam-d)
[plan9front.git] / sys / src / cmd / ip / tftpd.c
old mode 100755 (executable)
new mode 100644 (file)
index c005f01..07ca3b3
@@ -6,12 +6,10 @@
 #include <auth.h>
 #include <bio.h>
 #include <ip.h>
-#include <ndb.h>
 
 enum
 {
        Maxpath=        128,
-       Maxerr=         256,
 
        Debug=          0,
 
@@ -54,6 +52,7 @@ enum
         * 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;
@@ -85,16 +84,16 @@ 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   *homedir = "/";
+char   *nsfile = nil;
 char   flog[] = "ipboot";
 char   net[Maxpath];
 
@@ -129,7 +128,7 @@ main(int argc, char **argv)
                dbg++;
                break;
        case 'h':
-               dir = EARGF(usage());
+               homedir = EARGF(usage());
                break;
        case 'r':
                restricted = 1;
@@ -140,13 +139,17 @@ main(int argc, char **argv)
        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);
@@ -156,8 +159,8 @@ main(int argc, char **argv)
         * "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)) {
@@ -218,8 +221,10 @@ handleopt(int fd, char *name, char *val)
                                        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;
@@ -233,25 +238,59 @@ filesize(char *file)
 
        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;
@@ -265,43 +304,45 @@ options(int fd, char *buf, char *file, ushort oper, char *p, int dlen)
                        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);
@@ -312,25 +353,6 @@ options(int fd, char *buf, char *file, ushort oper, char *p, int dlen)
        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)
 {
@@ -345,6 +367,65 @@ 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)
 {
@@ -366,6 +447,9 @@ 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);
@@ -377,7 +461,8 @@ doserve(int 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;
        }
 
@@ -406,7 +491,9 @@ doserve(int fd)
 
                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);
@@ -458,9 +545,9 @@ awaitack(int fd, int block)
                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;
@@ -477,15 +564,10 @@ sendfile(int fd, char *name, char *mode, int opts)
 {
        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);
 
@@ -493,7 +575,7 @@ sendfile(int fd, char *name, char *mode, int opts)
        if(file < 0) {
                errstr(errbuf, sizeof errbuf);
                nak(fd, 0, errbuf);
-               return;
+               goto error;
        }
        block = 0;
        rexmit = Ackok;
@@ -519,7 +601,7 @@ sendfile(int fd, char *name, char *mode, int opts)
                        if(n < 0) {
                                errstr(errbuf, sizeof errbuf);
                                nak(fd, 0, errbuf);
-                               return;
+                               goto error;
                        }
                        txtry = 0;
                }
@@ -545,6 +627,8 @@ sendfile(int fd, char *name, char *mode, int opts)
                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);
@@ -555,7 +639,7 @@ recvfile(int fd, char *name, char *mode)
 {
        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);
@@ -564,7 +648,7 @@ recvfile(int fd, char *name, char *mode)
        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;
        }
 
@@ -581,7 +665,11 @@ recvfile(int fd, char *name, char *mode)
                                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);
@@ -638,82 +726,28 @@ nak(int fd, int code, char *msg)
        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)
 {