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