/* 9tftpd: tftpd for plan9 usage: 9tftpd [-d] options: -d: debug 9tftpd follows tftp protocol but the use is limited only to boot. Tftpd answers everything and therefore has security problems if it is applied on the authentication server. Tftpd should be replaced by 9tftpd. 9tftpd answers only GET request and looks ndb database to confirm the file he may offer. 9tftpd transmits bootf of the requester. Sunkernel should work but has not been tested yet. Much of the program codes comes from tftpd.c REFERENCE: [1] /sys/src/cmd/ip/tftpd.c [2] man: bootp(8) [3] RFC 1350 1998/08/14 Kenji Arisawa (Kenar) E-mail: arisawa@ar.aichi-u.ac.jp */ #include #include #include #include #include #include int dbg; int tftpreq; int tftpaddr; int tftpctl; void openlisten(void); void sendfile(int, char*); void nak(int, int, char*); void ack(int, ushort); void clrcon(void); void setuser(void); char* sunkernel(char*); char *ip2bootf(char*); void fatal(int syserr, char *fmt, ...); char mbuf[32768]; char raddr[32]; char flog[] = "ipboot"; enum { Tftp_READ = 1, Tftp_WRITE = 2, Tftp_DATA = 3, Tftp_ACK = 4, Tftp_ERROR = 5, Segsize = 512, }; void main(int argc, char **argv) { int n, dlen, clen; char connect[64], buf[64], datadir[64]; char *mode, *p, *s; short op; int ctl, data; ARGBEGIN{ case 'd': dbg++; break; default: fprint(2, "usage: 9tftpd [-d]\n"); exits("usage"); }ARGEND USED(argc); USED(argv); fmtinstall('E', eipconv); fmtinstall('I', eipconv); switch(rfork(RFNOTEG|RFPROC|RFFDG)) { case -1: fatal(1, "fork"); case 0: break; default: exits(0); } syslog(dbg, flog, "started"); openlisten(); setuser(); for(;;) { dlen = read(tftpreq, mbuf, sizeof(mbuf)); if(dlen < 0) fatal(1, "listen read"); seek(tftpaddr, 0, 0); clen = read(tftpaddr, raddr, sizeof(raddr)); if(clen < 0) fatal(1, "request address read"); raddr[clen-1] = '\0'; clrcon(); /* -------------------------- * Here we have his ip address in raddr * The form is: 192.168.1.1!2643 * -Kenar- */ ctl = open("/net/udp/clone", ORDWR); if(ctl < 0) fatal(1, "open udp clone"); n = read(ctl, buf, sizeof(buf)); if(n < 0) fatal(1, "read udp ctl"); buf[n] = 0; clen = sprint(connect, "connect %s", raddr); n = write(ctl, connect, clen); if(n < 0) fatal(1, "udp %s", raddr); sprint(datadir, "/net/udp/%s/data", buf); data = open(datadir, ORDWR); if(data < 0) fatal(1, "open udp data"); close(ctl); dlen -= 2; mode = mbuf+2; while(*mode != '\0' && dlen){ mode++; dlen--; } mode++; p = mode; while(*p && dlen){ p++; dlen--; } if(dlen == 0) { nak(data, 0, "bad tftpmode"); close(data); syslog(dbg, flog, "bad mode %s", raddr); continue; } /* ------------------- * mode: netascii | octet * and we will ignore the transmission mode. * -Kenar- */ op = mbuf[0]<<8 | mbuf[1]; if(op != Tftp_READ) { nak(data, 4, "Illegal TFTP operation"); close(data); syslog(dbg, flog, "bad request %d %s", op, raddr); continue; } /* -------------------------------- * He requies the file: (mbuf+2) * so we check the validity of his request: * 1. the request must be his bootf * 2. the request is given by absolute path * 3. sun is special. the request is not a path * -Kenar- */ s = mbuf+2; p = 0; if(*s == '/'){ p = strchr(raddr,'!'); if(p) *p = 0; s = ip2bootf(raddr); if(p) *p = '!'; p = 0; if(s == 0 || strcmp(s,mbuf+2) != 0) p = "bad request"; } else{/* we suspect sun kernel */ s= sunkernel(s); if(s == 0 || *s != '/') p = "bad request"; } if(p){ nak(data, 4, p); syslog(dbg, flog, "%s %s %s", p, mbuf+2, raddr); close(data); continue; } switch(fork()) { case -1: fatal(1, "fork"); case 0: sendfile(data, s); exits("done"); default: close(data); } } } void catcher(void *junk, char *msg) { USED(junk); if(strncmp(msg, "exit", 4) == 0) noted(NDFLT); noted(NCONT); } void sendfile(int fd, char *name) { int file; uchar buf[Segsize+4]; uchar ack[1024]; char errbuf[ERRLEN]; int ackblock, block, ret; int rexmit, n, al, txtry, rxl; short op; syslog(dbg, flog, "send file '%s to %s", name, raddr); notify(catcher); file = open(name, OREAD); if(file < 0) { errstr(errbuf); nak(fd, 0, errbuf); return; } block = 0; rexmit = 0; n = 0; for(txtry = 0; txtry < 5;) { if(rexmit == 0) { block++; buf[0] = 0; buf[1] = Tftp_DATA; buf[2] = block>>8; buf[3] = block; n = read(file, buf+4, Segsize); if(n < 0) { errstr(errbuf); nak(fd, 0, errbuf); return; } txtry = 0; } else txtry++; ret = write(fd, buf, 4+n); if(ret < 0) fatal(1, "tftp: network write error"); for(rxl = 0; rxl < 10; rxl++) { rexmit = 0; alarm(500); al = read(fd, ack, sizeof(ack)); alarm(0); if(al < 0) { rexmit = 1; break; } op = ack[0]<<8|ack[1]; if(op == Tftp_ERROR) goto error; ackblock = ack[2]<<8|ack[3]; if(ackblock == block) break; if(ackblock == 0xffff) { rexmit = 1; break; } } if(ret != Segsize+4 && rexmit == 0) break; } error: close(fd); close(file); } void ack(int fd, ushort block) { uchar ack[4]; int n; ack[0] = 0; ack[1] = Tftp_ACK; ack[2] = block>>8; ack[3] = block; n = write(fd, ack, 4); if(n < 0) fatal(1, "network write"); } void nak(int fd, int code, char *msg) { char buf[128]; int n; buf[0] = 0; buf[1] = Tftp_ERROR; buf[2] = 0; buf[3] = code; strcpy(buf+4, msg); n = strlen(msg) + 4 + 1; n = write(fd, buf, n); if(n < 0) fatal(1, "write nak"); } void fatal(int syserr, char *fmt, ...) { char buf[ERRLEN], sysbuf[ERRLEN]; doprint(buf, buf+sizeof(buf), fmt, (&fmt+1)); if(syserr) { errstr(sysbuf); fprint(2, "tftpd: %s: %s\n", buf, sysbuf); } else fprint(2, "tftpd: %s\n", buf); exits(buf); } void openlisten(void) { char buf[128], data[128]; int n; tftpctl = open("/net/udp/clone", ORDWR); if(tftpctl < 0) fatal(1, "open udp clone"); n = read(tftpctl, buf, sizeof(buf)); if(n < 0) fatal(1, "read clone"); buf[n] = 0; n = write(tftpctl, "announce 69", sizeof("announce 69")); if(n < 0) fatal(1, "can't announce"); sprint(data, "/net/udp/%s/data", buf); tftpreq = open(data, ORDWR); if(tftpreq < 0) fatal(1, "open udp/data"); sprint(data, "/net/udp/%s/remote", buf); tftpaddr = open(data, OREAD); if(tftpaddr < 0) fatal(1, "open udp/remote"); } void clrcon(void) { int n; n = write(tftpctl, "connect 0.0.0.0!0!r", sizeof("connect 0.0.0.0!0!r")); if(n < 0) fatal(1, "clear connect"); } void setuser(void) { int f; f = open("/dev/user", OWRITE); if(f < 0) return; write(f, "none", sizeof("none")); close(f); newns("none", 0); } /* * 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 ipaddr[4]; char buf[32]; if(strlen(name) != 14 || strncmp(name + 8, ".SUN", 4) != 0) return name; addr = strtoul(name, 0, 16); ipaddr[0] = addr>>24; ipaddr[1] = addr>>16; ipaddr[2] = addr>>8; ipaddr[3] = addr; sprint(buf, "%I", ipaddr); /* then we get in buf: 202.250.160.2 for example*/ return ip2bootf(buf); } /*-------------------- * get bootf from ip * example: * ip2boot("202.250.160.2") * return: /386/9pc * -kenar- */ char * ip2bootf(char *ip){ static Ipinfo info; static Ndb *db; char *s = 0; db = ndbopen(0); if(db == 0) return 0; if(ipinfo(db, 0, ip, 0, &info) == 0 && info.bootf[0]) s = info.bootf; ndbclose(db); return s; }