]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/tftpd.c
ip/tftpd: add -n namespace-file flag (thanks sam-d)
[plan9front.git] / sys / src / cmd / ip / tftpd.c
1 /*
2  * tftpd - tftp service, see /lib/rfc/rfc783 (now rfc1350 + 234[789])
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <auth.h>
7 #include <bio.h>
8 #include <ip.h>
9
10 enum
11 {
12         Maxpath=        128,
13
14         Debug=          0,
15
16         Opsize=         sizeof(short),
17         Blksize=        sizeof(short),
18         Hdrsize=        Opsize + Blksize,
19
20         Ackerr=         -1,
21         Ackok=          0,
22         Ackrexmit=      1,
23
24         /* op codes */
25         Tftp_READ       = 1,
26         Tftp_WRITE      = 2,
27         Tftp_DATA       = 3,
28         Tftp_ACK        = 4,
29         Tftp_ERROR      = 5,
30         Tftp_OACK       = 6,            /* option acknowledge */
31
32         Errnotdef       = 0,            /* see textual error instead */
33         Errnotfound     = 1,
34         Errnoaccess     = 2,
35         Errdiskfull     = 3,
36         Errbadop        = 4,
37         Errbadtid       = 5,
38         Errexists       = 6,
39         Errnouser       = 7,
40         Errbadopt       = 8,            /* really bad option value */
41
42         Defsegsize      = 512,
43         Maxsegsize      = 65464,        /* from rfc2348 */
44
45         /*
46          * bandt (viaduct) tunnels use smaller mtu than ether's
47          * (1400 bytes for tcp mss of 1300 bytes).
48          */
49         Bandtmtu        = 1400,
50         /*
51          * maximum size of block's data content, excludes hdrs,
52          * notably IP/UDP and TFTP, using worst-case (IPv6) sizes.
53          */
54         Bandtblksz      = Bandtmtu - 40 - 8,
55         Bcavium         = 1432,         /* cavium's u-boot demands this size */
56 };
57
58 typedef struct Opt Opt;
59 struct Opt {
60         char    *name;
61         int     *valp;          /* set to client's value if within bounds */
62         int     min;
63         int     max;
64 };
65
66 int     dbg;
67 int     restricted;
68 int     pid;
69
70 /* options */
71 int     blksize = Defsegsize;           /* excluding 4-byte header */
72 int     timeout = 5;                    /* seconds */
73 int     tsize;
74 static Opt option[] = {
75         "timeout",      &timeout,       1,      255,
76         /* see "hack" below */
77         "blksize",      &blksize,       8,      Maxsegsize,
78         "tsize",        &tsize,         0,      ~0UL >> 1,
79 };
80
81 void    sendfile(int, char*, char*, int);
82 void    recvfile(int, char*, char*);
83 void    nak(int, int, char*);
84 void    ack(int, ushort);
85 void    clrcon(void);
86 void    setuser(void);
87 void    remoteaddr(char*, char*, int);
88 void    doserve(int);
89
90 char    bigbuf[32768];
91 char    raddr[64];
92
93 char    *dirsl;
94 int     dirsllen;
95 char    *homedir = "/";
96 char    *nsfile = nil;
97 char    flog[] = "ipboot";
98 char    net[Maxpath];
99
100 static char *opnames[] = {
101 [Tftp_READ]     "read",
102 [Tftp_WRITE]    "write",
103 [Tftp_DATA]     "data",
104 [Tftp_ACK]      "ack",
105 [Tftp_ERROR]    "error",
106 [Tftp_OACK]     "oack",
107 };
108
109 void
110 usage(void)
111 {
112         fprint(2, "usage: %s [-dr] [-h homedir] [-s svc] [-x netmtpt]\n",
113                 argv0);
114         exits("usage");
115 }
116
117 void
118 main(int argc, char **argv)
119 {
120         char buf[64];
121         char adir[64], ldir[64];
122         int cfd, lcfd, dfd;
123         char *svc = "69";
124
125         setnetmtpt(net, sizeof net, nil);
126         ARGBEGIN{
127         case 'd':
128                 dbg++;
129                 break;
130         case 'h':
131                 homedir = EARGF(usage());
132                 break;
133         case 'r':
134                 restricted = 1;
135                 break;
136         case 's':
137                 svc = EARGF(usage());
138                 break;
139         case 'x':
140                 setnetmtpt(net, sizeof net, EARGF(usage()));
141                 break;
142         case 'n':
143                 nsfile = EARGF(usage());
144                 break;
145         default:
146                 usage();
147         }ARGEND
148
149         dirsllen = strlen(homedir);
150         while(dirsllen > 0 && homedir[dirsllen-1] == '/')
151                 dirsllen--;
152         dirsl = smprint("%.*s/", utfnlen(homedir, dirsllen), homedir);
153
154         fmtinstall('E', eipfmt);
155         fmtinstall('I', eipfmt);
156
157         /*
158          * setuser calls newns, and typical /lib/namespace files contain
159          * "cd /usr/$user", so call setuser before chdir.
160          */
161         setuser();
162         if(chdir(homedir) < 0)
163                 sysfatal("can't get to directory %s: %r", homedir);
164
165         if(!dbg)
166                 switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
167                 case -1:
168                         sysfatal("fork: %r");
169                 case 0:
170                         break;
171                 default:
172                         exits(0);
173                 }
174
175         snprint(buf, sizeof buf, "%s/udp!*!%s", net, svc);
176         cfd = announce(buf, adir);
177         if (cfd < 0)
178                 sysfatal("announcing on %s: %r", buf);
179         syslog(dbg, flog, "tftpd started on %s dir %s", buf, adir);
180 //      setuser();
181         for(;;) {
182                 lcfd = listen(adir, ldir);
183                 if(lcfd < 0)
184                         sysfatal("listening on %s: %r", adir);
185
186                 switch(fork()) {
187                 case -1:
188                         sysfatal("fork: %r");
189                 case 0:
190                         dfd = accept(lcfd, ldir);
191                         if(dfd < 0)
192                                 exits(0);
193                         remoteaddr(ldir, raddr, sizeof(raddr));
194                         pid = getpid();
195                         syslog(0, flog, "tftp %d connection from %s dir %s",
196                                 pid, raddr, ldir);
197                         doserve(dfd);
198                         exits("done");
199                         break;
200                 default:
201                         close(lcfd);
202                         continue;
203                 }
204         }
205 }
206
207 static Opt *
208 handleopt(int fd, char *name, char *val)
209 {
210         int n;
211         Opt *op;
212
213         for (op = option; op < option + nelem(option); op++)
214                 if(cistrcmp(name, op->name) == 0) {
215                         n = strtol(val, nil, 10);
216                         if (n < op->min || n > op->max) {
217                                 nak(fd, Errbadopt, "option value out of range");
218                                 syslog(dbg, flog, "tftp bad option value from "
219                                         "client: %s %s", name, val);
220                                 sysfatal("bad option value from client: %s %s",
221                                         name, val);
222                         }
223                         *op->valp = n;
224                         /* incoming 0 for tsize is uninteresting */
225                         if(cistrcmp("tsize", op->name) != 0)
226                                 syslog(dbg, flog, "tftpd %d setting %s to client's %d",
227                                         pid, name, n);
228                         return op;
229                 }
230         return nil;
231 }
232
233 static vlong
234 filesize(char *file)
235 {
236         vlong size;
237         Dir *dp;
238
239         dp = dirstat(file);
240         if (dp == nil)
241                 return -1;
242         size = dp->length;
243         free(dp);
244         return size;
245 }
246
247 /* copy word into bp iff it fits before ep, returns bytes to advance bp. */
248 static int
249 emits(char *word, char *bp, char *ep)
250 {
251         int len;
252
253         len = strlen(word) + 1;
254         if (bp + len >= ep)
255                 return -1;
256         strcpy(bp, word);
257         return len;
258 }
259
260 /* format number into bp iff it fits before ep. */
261 static int
262 emitn(vlong n, char *bp, char *ep)
263 {
264         char numb[32];
265
266         snprint(numb, sizeof numb, "%lld", n);
267         return emits(numb, bp, ep);
268 }
269
270 /*
271  * send an OACK packet to respond to options.  bail early with -1 on error.
272  * p is the packet containing the options.
273  *
274  * hack: bandt (viaducts) uses smaller mtu than ether's
275  * (1400 bytes for tcp mss of 1300 bytes),
276  * so offer at most bandt's mtu minus headers,
277  * to avoid failure of pxe booting via viaduct.
278  * there's an exception for the cavium's u-boot.
279  */
280 static int
281 options(int fd, char *buf, int bufsz, char *file, ushort oper, char *p, int dlen)
282 {
283         int nmlen, vallen, olen, nopts;
284         vlong size;
285         char *val, *bp, *ep;
286         Opt *op;
287
288         buf[0] = 0;
289         buf[1] = Tftp_OACK;
290         bp = buf + Opsize;
291         ep = buf + bufsz;
292         nopts = 0;
293         for (; dlen > 0 && *p != '\0'; p = val + vallen, bp += olen) {
294                 nmlen = strlen(p) + 1;          /* include NUL */
295                 if (nmlen > dlen)
296                         break;
297                 dlen -= nmlen;
298                 val = p + nmlen;
299                 if (dlen <= 0 || *val == '\0')
300                         break;
301
302                 vallen = strlen(val) + 1;
303                 if (vallen > dlen)
304                         break;
305                 dlen -= vallen;
306
307                 olen = 0;
308                 op = handleopt(fd, p, val);
309                 if (op == nil)
310                         continue;
311
312                 nopts++;
313
314                 /* append OACK response to buf */
315                 nmlen = emits(p, bp, ep);       /* option name */
316                 if (nmlen < 0)
317                         return -1;
318                 bp += nmlen;
319
320                 if (oper == Tftp_READ && cistrcmp(p, "tsize") == 0) {
321                         size = filesize(file);
322                         if (size == -1) {
323                                 nak(fd, Errnotfound, "no such file");
324                                 syslog(dbg, flog, "tftpd tsize for "
325                                         "non-existent file %s", file);
326                                 // *op->valp = 0;
327                                 // olen = emits("0", bp, ep);
328                                 return -1;
329                         }
330                         *op->valp = size;
331                         olen = emitn(size, bp, ep);
332                         syslog(dbg, flog, "tftpd %d %s tsize is %,lld",
333                                 pid, file, size);
334                 } else if (oper == Tftp_READ && cistrcmp(p, "blksize") == 0 &&
335                     blksize > Bandtblksz && blksize != Bcavium) {
336                         *op->valp = blksize = Bandtblksz;
337                         olen = emitn(blksize, bp, ep);
338                         syslog(dbg, flog, "tftpd %d overriding blksize to %d",
339                                 pid, blksize);
340                 } else
341                         olen = emits(val, bp, ep);  /* use requested value */
342         }
343         if (nopts == 0)
344                 return 0;               /* no options actually seen */
345
346         if (write(fd, buf, bp - buf) < bp - buf) {
347                 syslog(dbg, flog, "tftpd network write error on oack to %s: %r",
348                         raddr);
349                 sysfatal("tftpd: network write error: %r");
350         }
351         if(Debug)
352                 syslog(dbg, flog, "tftpd oack: options to %s", raddr);
353         return nopts;
354 }
355
356 static void
357 optlog(char *bytes, char *p, int dlen)
358 {
359         char *bp;
360
361         bp = bytes;
362         sprint(bp, "tftpd %d option bytes: ", dlen);
363         bp += strlen(bp);
364         for (; dlen > 0; dlen--, p++)
365                 *bp++ = *p? *p: ' ';
366         *bp = '\0';
367         syslog(dbg, flog, "%s", bytes);
368 }
369
370 /*
371  * replace one occurrence of %[ICE] with ip, cfgpxe name, or ether mac, resp.
372  * we can't easily use $ because u-boot has stranger quoting rules than sh.
373  */
374 char *
375 mapname(char *file)
376 {
377         int nf;
378         char *p, *newnm, *cur, *arpf, *ln, *remip, *bang;
379         char *fields[4];
380         Biobuf *arp;
381
382         p = strchr(file, '%');
383         if (p == nil || p[1] == '\0')
384                 return strdup(file);
385
386         remip = strdup(raddr);
387         newnm = mallocz(strlen(file) + Maxpath, 1);
388         if (remip == nil || newnm == nil)
389                 sysfatal("out of memory");
390
391         bang = strchr(remip, '!');
392         if (bang)
393                 *bang = '\0';                   /* remove !port */
394
395         memmove(newnm, file, p - file);         /* copy up to % */
396         cur = newnm + strlen(newnm);
397         switch(p[1]) {
398         case 'I':
399                 strcpy(cur, remip);             /* remote's IP */
400                 break;
401         case 'C':
402                 strcpy(cur, "/cfg/pxe/");
403                 cur += strlen(cur);
404                 /* fall through */
405         case 'E':
406                 /* look up remote's IP in /net/arp to get mac. */
407                 arpf = smprint("%s/arp", net);
408                 arp = Bopen(arpf, OREAD);
409                 free(arpf);
410                 if (arp == nil)
411                         break;
412                 /* read lines looking for remip in 3rd field of 4 */
413                 while ((ln = Brdline(arp, '\n')) != nil) {
414                         ln[Blinelen(arp)-1] = 0;
415                         nf = tokenize(ln, fields, nelem(fields));
416                         if (nf >= 4 && strcmp(fields[2], remip) == 0) {
417                                 strcpy(cur, fields[3]);
418                                 break;
419                         }
420                 }
421                 Bterm(arp);
422                 break;
423         }
424         strcat(newnm, p + 2);                   /* tail following %x */
425         free(remip);
426         return newnm;
427 }
428
429 void
430 doserve(int fd)
431 {
432         int dlen, opts;
433         char *mode, *p, *file;
434         short op;
435
436         dlen = read(fd, bigbuf, sizeof(bigbuf)-1);
437         if(dlen < 0)
438                 sysfatal("listen read: %r");
439
440         bigbuf[dlen] = '\0';
441         op = (bigbuf[0]<<8) | bigbuf[1];
442         dlen -= Opsize;
443         mode = file = bigbuf + Opsize;
444         while(*mode != '\0' && dlen--)
445                 mode++;
446         mode++;
447         p = mode;
448         while(*p && dlen--)
449                 p++;
450
451         file = mapname(file);   /* we don't free the result; minor leak */
452
453         if(dlen == 0) {
454                 nak(fd, 0, "bad tftpmode");
455                 close(fd);
456                 syslog(dbg, flog, "tftpd %d bad mode %s for file %s from %s",
457                         pid, mode, file, raddr);
458                 return;
459         }
460
461         if(op != Tftp_READ && op != Tftp_WRITE) {
462                 nak(fd, Errbadop, "Illegal TFTP operation");
463                 close(fd);
464                 syslog(dbg, flog, "tftpd %d bad request %d (%s) %s", pid, op,
465                         (op < nelem(opnames)? opnames[op]: "gok"), raddr);
466                 return;
467         }
468
469         if(restricted){
470                 if(file[0] == '#' || strncmp(file, "../", 3) == 0 ||
471                   strstr(file, "/../") != nil ||
472                   (file[0] == '/' && strncmp(file, dirsl, dirsllen) != 0)){
473                         nak(fd, Errnoaccess, "Permission denied");
474                         close(fd);
475                         syslog(dbg, flog, "tftpd %d bad request %d from %s file %s",
476                                 pid, op, raddr, file);
477                         return;
478                 }
479         }
480
481         /*
482          * options are supposed to be negotiated, but the cavium board's
483          * u-boot really wants us to use a block size of 1432 bytes and won't
484          * take `no' for an answer.
485          */
486         p++;                            /* skip NUL after mode */
487         dlen--;
488         opts = 0;
489         if(dlen > 0) {                  /* might have options */
490                 char bytes[32*1024];
491
492                 if(Debug)
493                         optlog(bytes, p, dlen);
494                 opts = options(fd, bytes, sizeof bytes, file, op, p, dlen);
495                 if (opts < 0)
496                         return;
497         }
498         if(op == Tftp_READ)
499                 sendfile(fd, file, mode, opts);
500         else
501                 recvfile(fd, file, mode);
502 }
503
504 void
505 catcher(void *junk, char *msg)
506 {
507         USED(junk);
508
509         if(strncmp(msg, "exit", 4) == 0)
510                 noted(NDFLT);
511         noted(NCONT);
512 }
513
514 static int
515 awaitack(int fd, int block)
516 {
517         int ackblock, al, rxl;
518         ushort op;
519         uchar ack[1024];
520
521         for(rxl = 0; rxl < 10; rxl++) {
522                 memset(ack, 0, Hdrsize);
523                 alarm(1000);
524                 al = read(fd, ack, sizeof(ack));
525                 alarm(0);
526                 if(al < 0) {
527                         if (Debug)
528                                 syslog(dbg, flog, "tftpd %d timed out "
529                                         "waiting for ack from %s", pid, raddr);
530                         return Ackrexmit;
531                 }
532                 op = ack[0]<<8|ack[1];
533                 if(op == Tftp_ERROR) {
534                         if (Debug)
535                                 syslog(dbg, flog, "tftpd %d got error "
536                                         "waiting for ack from %s", pid, raddr);
537                         return Ackerr;
538                 } else if(op != Tftp_ACK) {
539                         syslog(dbg, flog, "tftpd %d rcvd %s op from %s", pid,
540                                 (op < nelem(opnames)? opnames[op]: "gok"),
541                                 raddr);
542                         return Ackerr;
543                 }
544                 ackblock = ack[2]<<8|ack[3];
545                 if (Debug)
546                         syslog(dbg, flog, "tftpd %d read ack of %d bytes "
547                                 "for block %d", pid, al, ackblock);
548                 if(ackblock == (block & 0xffff))
549                         return Ackok;           /* for block just sent */
550                 else if(ackblock == (block + 1 & 0xffff))       /* intel pxe eof bug */
551                         return Ackok;
552                 else if(ackblock == 0xffff)
553                         return Ackrexmit;
554                 else
555                         /* ack is for some other block; ignore it, try again */
556                         syslog(dbg, flog, "tftpd %d expected ack for block %d, "
557                                 "got %d", pid, block, ackblock);
558         }
559         return Ackrexmit;
560 }
561
562 void
563 sendfile(int fd, char *name, char *mode, int opts)
564 {
565         int file, block, ret, rexmit, n, txtry;
566         uchar buf[Maxsegsize+Hdrsize];
567         char errbuf[ERRMAX];
568
569         syslog(dbg, flog, "tftpd %d send file '%s' %s to %s",
570                 pid, name, mode, raddr);
571
572         notify(catcher);
573
574         file = open(name, OREAD);
575         if(file < 0) {
576                 errstr(errbuf, sizeof errbuf);
577                 nak(fd, 0, errbuf);
578                 goto error;
579         }
580         block = 0;
581         rexmit = Ackok;
582         n = 0;
583         /*
584          * if we sent an oack previously, wait for the client's ack or error.
585          * if we get no ack for our oack, it could be that we returned
586          * a tsize that the client can't handle, or it could be intel
587          * pxe just read-with-tsize to get size, couldn't be bothered to
588          * ack our oack and has just gone ahead and issued another read.
589          */
590         if(opts && awaitack(fd, 0) != Ackok)
591                 goto error;
592
593         for(txtry = 0; txtry < timeout;) {
594                 if(rexmit == Ackok) {
595                         block++;
596                         buf[0] = 0;
597                         buf[1] = Tftp_DATA;
598                         buf[2] = block>>8;
599                         buf[3] = block;
600                         n = read(file, buf+Hdrsize, blksize);
601                         if(n < 0) {
602                                 errstr(errbuf, sizeof errbuf);
603                                 nak(fd, 0, errbuf);
604                                 goto error;
605                         }
606                         txtry = 0;
607                 }
608                 else {
609                         syslog(dbg, flog, "tftpd %d rexmit %d %s:%d to %s",
610                                 pid, Hdrsize+n, name, block, raddr);
611                         txtry++;
612                 }
613
614                 ret = write(fd, buf, Hdrsize+n);
615                 if(ret < Hdrsize+n) {
616                         syslog(dbg, flog,
617                                 "tftpd network write error on %s to %s: %r",
618                                 name, raddr);
619                         sysfatal("tftpd: network write error: %r");
620                 }
621                 if (Debug)
622                         syslog(dbg, flog, "tftpd %d sent block %d", pid, block);
623
624                 rexmit = awaitack(fd, block);
625                 if (rexmit == Ackerr)
626                         break;
627                 if(ret != blksize+Hdrsize && rexmit == Ackok)
628                         break;
629         }
630         syslog(dbg, flog, "tftpd %d done sending file '%s' %s to %s",
631                 pid, name, mode, raddr);
632 error:
633         close(fd);
634         close(file);
635 }
636
637 void
638 recvfile(int fd, char *name, char *mode)
639 {
640         ushort op, block, inblock;
641         uchar buf[Maxsegsize+8];
642         char errbuf[ERRMAX];
643         int n, ret, file;
644
645         syslog(dbg, flog, "receive file '%s' %s from %s", name, mode, raddr);
646
647         file = create(name, OWRITE, 0666);
648         if(file < 0) {
649                 errstr(errbuf, sizeof errbuf);
650                 nak(fd, 0, errbuf);
651                 syslog(dbg, flog, "can't create %s: %s", name, errbuf);
652                 return;
653         }
654
655         block = 0;
656         ack(fd, block);
657         block++;
658
659         for (;;) {
660                 alarm(15000);
661                 n = read(fd, buf, blksize+8);
662                 alarm(0);
663                 if(n < 0) {
664                         syslog(dbg, flog, "tftpd: network error reading %s: %r",
665                                 name);
666                         goto error;
667                 }
668                 /*
669                  * NB: not `<='; just a header is legal and happens when
670                  * file being read is a multiple of segment-size bytes long.
671                  */
672                 if(n < Hdrsize) {
673                         syslog(dbg, flog,
674                                 "tftpd: short read from network, reading %s",
675                                 name);
676                         goto error;
677                 }
678                 op = buf[0]<<8|buf[1];
679                 if(op == Tftp_ERROR) {
680                         syslog(dbg, flog, "tftpd: tftp error reading %s", name);
681                         goto error;
682                 }
683
684                 n -= Hdrsize;
685                 inblock = buf[2]<<8|buf[3];
686                 if(op == Tftp_DATA) {
687                         if(inblock == block) {
688                                 ret = write(file, buf+Hdrsize, n);
689                                 if(ret != n) {
690                                         errstr(errbuf, sizeof errbuf);
691                                         nak(fd, 0, errbuf);
692                                         syslog(dbg, flog,
693                                             "tftpd: error writing %s: %s",
694                                                 name, errbuf);
695                                         goto error;
696                                 }
697                                 ack(fd, block);
698                                 block++;
699                         } else
700                                 ack(fd, 0xffff);        /* tell him to resend */
701                 }
702         }
703 error:
704         close(file);
705 }
706
707 void
708 ack(int fd, ushort block)
709 {
710         uchar ack[4];
711         int n;
712
713         ack[0] = 0;
714         ack[1] = Tftp_ACK;
715         ack[2] = block>>8;
716         ack[3] = block;
717
718         n = write(fd, ack, 4);
719         if(n < 4)
720                 sysfatal("network write: %r");
721 }
722
723 void
724 nak(int fd, int code, char *msg)
725 {
726         char buf[128];
727         int n;
728
729         n = 5 + strlen(msg);
730         if(n > sizeof(buf))
731                 n = sizeof(buf);
732         buf[0] = 0;
733         buf[1] = Tftp_ERROR;
734         buf[2] = 0;
735         buf[3] = code;
736         memmove(buf+4, msg, n - 5);
737         buf[n-1] = 0;
738         if(write(fd, buf, n) != n)
739                 sysfatal("write nak: %r");
740 }
741
742 void
743 setuser(void)
744 {
745         if(procsetuser("none") < 0)
746                 sysfatal("can't become none: %r");
747         if(newns("none", nsfile) < 0)
748                 sysfatal("can't build namespace: %r");
749 }
750
751 void
752 remoteaddr(char *dir, char *raddr, int len)
753 {
754         char buf[64];
755         int fd, n;
756
757         snprint(buf, sizeof(buf), "%s/remote", dir);
758         fd = open(buf, OREAD);
759         if(fd < 0){
760                 snprint(raddr, sizeof(raddr), "unknown");
761                 return;
762         }
763         n = read(fd, raddr, len-1);
764         close(fd);
765         if(n <= 0){
766                 snprint(raddr, sizeof(raddr), "unknown");
767                 return;
768         }
769         if(n > 0)
770                 n--;
771         raddr[n] = 0;
772 }