diff options
Diffstat (limited to 'src/cmd/ip/dhcpd/dhcpd.c')
-rwxr-xr-x | src/cmd/ip/dhcpd/dhcpd.c | 1632 |
1 files changed, 1632 insertions, 0 deletions
diff --git a/src/cmd/ip/dhcpd/dhcpd.c b/src/cmd/ip/dhcpd/dhcpd.c new file mode 100755 index 00000000..3b0873c2 --- /dev/null +++ b/src/cmd/ip/dhcpd/dhcpd.c @@ -0,0 +1,1632 @@ +#include <u.h> +#include <sys/socket.h> +#include <net/if_arp.h> +#include <netinet/ip.h> +#include <sys/ioctl.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include "dat.h" + +int bwfd; +int wfd; + +// +// ala rfc2131 +// + +typedef struct Req Req; +struct Req +{ + int fd; /* for reply */ + Bootp *bp; + Udphdr uh; + Udphdr *up; + uchar *e; /* end of received message */ + uchar *p; /* options pointer */ + uchar *max; /* max end of reply */ + + /* expanded to v6 */ + uchar ciaddr[IPaddrlen]; + uchar giaddr[IPaddrlen]; + + /* parsed options */ + int p9request; /* true if this is a bootp with plan9 options */ + int genrequest; /* true if this is a bootp with generic options */ + int dhcptype; /* dhcp message type */ + int leasetime; /* dhcp lease */ + uchar ip[IPaddrlen]; /* requested address */ + uchar server[IPaddrlen]; /* server address */ + char msg[ERRMAX]; /* error message */ + char vci[32]; /* vendor class id */ + char *id; /* client id */ + uchar requested[32]; /* requested params */ + uchar vendorclass[32]; + char cputype[32-3]; + + Info gii; /* about target network */ + Info ii; /* about target system */ + int staticbinding; + + uchar buf[2*1024]; /* message buffer */ +}; + +#define TFTP "/lib/tftpd" +char *blog = "ipboot"; +char mysysname[64]; +Ipifc *ipifcs; +int debug; +int nobootp; +long now; +int slow; +char net[256]; +uchar xmyipaddr[IPaddrlen]; + +int pptponly; // only answer request that came from the pptp server +int mute; +int minlease = MinLease; + +ulong start; + +/* option magic */ +char plan9opt[4] = { 'p', '9', ' ', ' ' }; +char genericopt[4] = { 0x63, 0x82, 0x53, 0x63 }; + +/* well known addresses */ +uchar zeros[Maxhwlen]; + +/* option debug buffer */ +char optbuf[1024]; +char *op; +char *oe = optbuf + sizeof(optbuf); + +char *optname[256] = +{ +[OBend] "end", +[OBpad] "pad", +[OBmask] "mask", +[OBtimeoff] "timeoff", +[OBrouter] "router", +[OBtimeserver] "time", +[OBnameserver] "name", +[OBdnserver] "dns", +[OBlogserver] "log", +[OBcookieserver] "cookie", +[OBlprserver] "lpr", +[OBimpressserver] "impress", +[OBrlserver] "rl", +[OBhostname] "host", +[OBbflen] "bflen", +[OBdumpfile] "dumpfile", +[OBdomainname] "dom", +[OBswapserver] "swap", +[OBrootpath] "rootpath", +[OBextpath] "extpath", +[OBipforward] "ipforward", +[OBnonlocal] "nonlocal", +[OBpolicyfilter] "policyfilter", +[OBmaxdatagram] "maxdatagram", +[OBttl] "ttl", +[OBpathtimeout] "pathtimeout", +[OBpathplateau] "pathplateau", +[OBmtu] "mtu", +[OBsubnetslocal] "subnetslocal", +[OBbaddr] "baddr", +[OBdiscovermask] "discovermask", +[OBsupplymask] "supplymask", +[OBdiscoverrouter] "discoverrouter", +[OBrsserver] "rsserver", +[OBstaticroutes] "staticroutes", +[OBtrailerencap] "trailerencap", +[OBarptimeout] "arptimeout", +[OBetherencap] "etherencap", +[OBtcpttl] "tcpttl", +[OBtcpka] "tcpka", +[OBtcpkag] "tcpkag", +[OBnisdomain] "nisdomain", +[OBniserver] "niserver", +[OBntpserver] "ntpserver", +[OBvendorinfo] "vendorinfo", +[OBnetbiosns] "NBns", +[OBnetbiosdds] "NBdds", +[OBnetbiostype] "NBtype", +[OBnetbiosscope] "NBscope", +[OBxfontserver] "xfont", +[OBxdispmanager] "xdisp", +[OBnisplusdomain] "NPdomain", +[OBnisplusserver] "NP", +[OBhomeagent] "homeagent", +[OBsmtpserver] "smtp", +[OBpop3server] "pop3", +[OBnntpserver] "nntp", +[OBwwwserver] "www", +[OBfingerserver] "finger", +[OBircserver] "ircserver", +[OBstserver] "stserver", +[OBstdaserver] "stdaserver", + +/* dhcp options */ +[ODipaddr] "ip", +[ODlease] "leas", +[ODoverload] "overload", +[ODtype] "typ", +[ODserverid] "sid", +[ODparams] "params", +[ODmessage] "message", +[ODmaxmsg] "maxmsg", +[ODrenewaltime] "renewaltime", +[ODrebindingtime] "rebindingtime", +[ODvendorclass] "vendorclass", +[ODclientid] "cid", +[ODtftpserver] "tftpserver", +[ODbootfile] "bf", +}; + +void addropt(Req*, int, uchar*); +void addrsopt(Req*, int, uchar**, int); +void arpenter(uchar*, uchar*); +void bootp(Req*); +void byteopt(Req*, int, uchar); +void dhcp(Req*); +void fatal(int, char*, ...); +void hexopt(Req*, int, char*); +void longopt(Req*, int, long); +void maskopt(Req*, int, uchar*); +void miscoptions(Req*, uchar*); +int openlisten(char *net); +void parseoptions(Req*); +void proto(Req*, int); +void rcvdecline(Req*); +void rcvdiscover(Req*); +void rcvinform(Req*); +void rcvrelease(Req*); +void rcvrequest(Req*); +char* readsysname(void); +void remrequested(Req*, int); +void sendack(Req*, uchar*, int, int); +void sendnak(Req*, char*); +void sendoffer(Req*, uchar*, int); +void stringopt(Req*, int, char*); +void termopt(Req*); +int validip(uchar*); +void vectoropt(Req*, int, uchar*, int); +void warning(int, char*, ...); +void logdhcp(Req*); +void logdhcpout(Req *, char *); +int readlast(int, Udphdr*, uchar*, int); + +void +timestamp(char *tag) +{ + ulong t; + + t = nsec()/1000; + syslog(0, blog, "%s %lud", tag, t - start); +} + +void +usage(void) +{ + fprint(2, "usage: dhcp [-dmsnp] [-f directory] [-x netmtpt] [-M minlease] addr n [addr n ...]\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int i, n, fd; + char *p; + uchar ip[IPaddrlen]; + Req r; + + fmtinstall('E', eipfmt); + fmtinstall('I', eipfmt); + fmtinstall('V', eipfmt); + fmtinstall('M', eipfmt); + ARGBEGIN { + case 'm': + mute = 1; + break; + case 'd': + debug = 1; + break; + case 'f': + p = ARGF(); + if(p == nil) + usage(); + ndbfile = p; + break; + case 's': + slow = 1; + break; + case 'n': + nobootp = 1; + break; + case 'i': + parseip(xmyipaddr,EARGF(usage())); + break; + case 'p': + pptponly = 1; + break; + case 'M': + p = ARGF(); + if(p == nil) + usage(); + minlease = atoi(p); + if(minlease <= 0) + minlease = MinLease; + break; + } ARGEND; + + while(argc > 1){ + parseip(ip, argv[0]); + if(!validip(ip)) + usage(); + n = atoi(argv[1]); + if(n <= 0) + usage(); + initbinding(ip, n); + argc -= 2; + argv += 2; + } + + /* for debugging */ + for(i = 0; i < 256; i++) + if(optname[i] == 0) + optname[i] = smprint("%d", i); + + /* what is my name? */ + p = readsysname(); + strcpy(mysysname, p); + + /* put process in background */ + if(!debug) switch(rfork(RFNOTEG|RFPROC|RFFDG)) { + case -1: + fatal(1, "fork"); + case 0: + break; + default: + exits(0); + } + + chdir(TFTP); + fd = openlisten(net); + wfd = fd; + bwfd = fd; + if(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 5) < 0) + print("setsockopt: %r\n"); + + for(;;){ + memset(&r, 0, sizeof(r)); + r.fd = fd; + n = readlast(fd, &r.uh, r.buf, sizeof(r.buf)); + if(n < 0) + fatal(1, "error reading requests"); + start = nsec()/1000; + op = optbuf; + *op = 0; + proto(&r, n); + if(r.id != nil) + free(r.id); + } +} + +void +proto(Req *rp, int n) +{ + uchar relip[IPaddrlen]; + char buf[64]; + + now = time(0); + + rp->e = rp->buf + n; + rp->bp = (Bootp*)rp->buf; + rp->up = &rp->uh; + rp->max = rp->buf + MINSUPPORTED - IPUDPHDRSIZE; + rp->p = rp->bp->optdata; + v4tov6(rp->giaddr, rp->bp->giaddr); + v4tov6(rp->ciaddr, rp->bp->ciaddr); + + if(pptponly && rp->bp->htype != 0) + return; + + ipifcs = readipifc(net, ipifcs, -1); + if(validip(rp->giaddr)) + ipmove(relip, rp->giaddr); + else if(validip(rp->up->raddr)) + ipmove(relip, rp->up->raddr); + else + ipmove(relip, xmyipaddr); + ipmove(rp->up->laddr, xmyipaddr); + if(rp->e < (uchar*)rp->bp->sname){ + warning(0, "packet too short"); + return; + } + if(rp->bp->op != Bootrequest){ + warning(0, "not bootrequest"); + return; + } + + if(rp->e >= rp->bp->optdata){ + if(memcmp(rp->bp->optmagic, plan9opt, sizeof(rp->bp->optmagic)) == 0) + rp->p9request = 1; + if(memcmp(rp->bp->optmagic, genericopt, sizeof(rp->bp->optmagic)) == 0) { + rp->genrequest = 1; + parseoptions(rp); + } + } + rp->p = rp->bp->optdata; + + /* If no id is specified, make one from the hardware address + * of the target. We assume all zeros is not a hardware address + * which could be a mistake. + */ + if(rp->id == nil){ + if(rp->bp->hlen > Maxhwlen){ + warning(0, "hlen %d", rp->bp->hlen); + return; + } + if(memcmp(zeros, rp->bp->chaddr, rp->bp->hlen) == 0){ + warning(0, "no chaddr"); + return; + } + sprint(buf, "hwa%2.2ux_", rp->bp->htype); + rp->id = tohex(buf, rp->bp->chaddr, rp->bp->hlen); + } + + /* info about gateway */ + if(lookupip(relip, &rp->gii, 1) < 0){ + warning(0, "lookupip failed"); + return; + } + + /* info about target system */ + if(lookup(rp->bp, &rp->ii, &rp->gii) == 0) + if(rp->ii.indb && rp->ii.dhcpgroup[0] == 0) + rp->staticbinding = 1; + + if(rp->dhcptype) + dhcp(rp); + else + bootp(rp); +timestamp("done"); +} + +void +dhcp(Req *rp) +{ + logdhcp(rp); + + switch(rp->dhcptype){ + case Discover: + if(slow) + sleep(500); + rcvdiscover(rp); + break; + case Request: + rcvrequest(rp); + break; + case Decline: + rcvdecline(rp); + break; + case Release: + rcvrelease(rp); + break; + case Inform: + rcvinform(rp); + break; + } +} + +void +rcvdiscover(Req *rp) +{ + Binding *b, *nb; + + if(rp->staticbinding){ + sendoffer(rp, rp->ii.ipaddr, StaticLease); + return; + } + + /* + * first look for an outstanding offer + */ + b = idtooffer(rp->id, &rp->gii); + + /* + * rfc2131 says: + * If an address is available, the new address + * SHOULD be chosen as follows: + * + * o The client's current address as recorded in the client's current + * binding, ELSE + * + * o The client's previous address as recorded in the client's (now + * expired or released) binding, if that address is in the server's + * pool of available addresses and not already allocated, ELSE + * + * o The address requested in the 'Requested IP Address' option, if that + * address is valid and not already allocated, ELSE + * + * o A new address allocated from the server's pool of available + * addresses; the address is selected based on the subnet from which + * the message was received (if 'giaddr' is 0) or on the address of + * the relay agent that forwarded the message ('giaddr' when not 0). + */ + if(b == nil){ + b = idtobinding(rp->id, &rp->gii, 1); + if(b && b->boundto && strcmp(b->boundto, rp->id) != 0) + if(validip(rp->ip) && samenet(rp->ip, &rp->gii)){ + nb = iptobinding(rp->ip, 0); + if(nb && nb->lease < now) + b = nb; + } + } + if(b == nil){ + warning(0, "!Discover(%s via %I): no binding %I", + rp->id, rp->gii.ipaddr, rp->ip); + return; + } + mkoffer(b, rp->id, rp->leasetime); + sendoffer(rp, b->ip, b->offer); +} + +void +rcvrequest(Req *rp) +{ + Binding *b; + + if(validip(rp->server)){ + /* this is a reply to an offer - SELECTING */ + + /* check for hard assignment */ + if(rp->staticbinding){ + if(forme(rp->server)) + sendack(rp, rp->ii.ipaddr, StaticLease, 1); + else + warning(0, "!Request(%s via %I): for server %I not me", + rp->id, rp->gii.ipaddr, rp->server); + return; + } + + b = idtooffer(rp->id, &rp->gii); + + /* if we don't have an offer, nak */ + if(b == nil){ + warning(0, "!Request(%s via %I): no offer", + rp->id, rp->gii.ipaddr); + if(forme(rp->server)) + sendnak(rp, "no offer for you"); + return; + } + + /* if not for me, retract offer */ + if(!forme(rp->server)){ + b->expoffer = 0; + warning(0, "!Request(%s via %I): for server %I not me", + rp->id, rp->gii.ipaddr, rp->server); + return; + } + + /* + * if the client is confused about what we offered, nak. + * client really shouldn't be specifying this when selecting + */ + if(validip(rp->ip) && ipcmp(rp->ip, b->ip) != 0){ + warning(0, "!Request(%s via %I): requests %I, not %I", + rp->id, rp->gii.ipaddr, rp->ip, b->ip); + sendnak(rp, "bad ip address option"); + return; + } + if(commitbinding(b) < 0){ + warning(0, "!Request(%s via %I): can't commit %I", + rp->id, rp->gii.ipaddr, b->ip); + sendnak(rp, "can't commit binding"); + return; + } + sendack(rp, b->ip, b->offer, 1); + } else if(validip(rp->ip)){ + /* + * checking address/net - INIT-REBOOT + * + * This is a rebooting client that remembers its old + * address. + */ + /* check for hard assignment */ + if(rp->staticbinding){ + if(memcmp(rp->ip, rp->ii.ipaddr, IPaddrlen) != 0){ + warning(0, "!Request(%s via %I): %I not valid for %E", + rp->id, rp->gii.ipaddr, rp->ip, rp->bp->chaddr); + sendnak(rp, "not valid"); + } + sendack(rp, rp->ii.ipaddr, StaticLease, 1); + return; + } + + /* make sure the network makes sense */ + if(!samenet(rp->ip, &rp->gii)){ + warning(0, "!Request(%s via %I): bad forward of %I", + rp->id, rp->gii.ipaddr, rp->ip); + sendnak(rp, "wrong network"); + return; + } + b = iptobinding(rp->ip, 0); + if(b == nil){ + warning(0, "!Request(%s via %I): no binding for %I for", + rp->id, rp->gii.ipaddr, rp->ip); + return; + } + if(memcmp(rp->ip, b->ip, IPaddrlen) != 0 || now > b->lease){ + warning(0, "!Request(%s via %I): %I not valid", + rp->id, rp->gii.ipaddr, rp->ip); + sendnak(rp, "not valid"); + return; + } + b->offer = b->lease - now; + sendack(rp, b->ip, b->offer, 1); + } else if(validip(rp->ciaddr)){ + /* + * checking address - RENEWING or REBINDING + * + * these states are indistinguishable in our action. The only + * difference is how close to lease expiration the client is. + * If it is really close, it broadcasts the request hoping that + * some server will answer. + */ + + /* check for hard assignment */ + if(rp->staticbinding){ + if(ipcmp(rp->ciaddr, rp->ii.ipaddr) != 0){ + warning(0, "!Request(%s via %I): %I not valid", + rp->id, rp->gii.ipaddr, rp->ciaddr); + sendnak(rp, "not valid"); + } + sendack(rp, rp->ii.ipaddr, StaticLease, 1); + return; + } + + /* make sure the network makes sense */ + if(!samenet(rp->ciaddr, &rp->gii)){ + warning(0, "!Request(%s via %I): bad forward of %I", + rp->id, rp->gii.ipaddr, rp->ip); + sendnak(rp, "wrong network"); + return; + } + b = iptobinding(rp->ciaddr, 0); + if(b == nil){ + warning(0, "!Request(%s via %I): no binding for %I", + rp->id, rp->gii.ipaddr, rp->ciaddr); + return; + } + if(ipcmp(rp->ciaddr, b->ip) != 0){ + warning(0, "!Request(%I via %s): %I not valid", + rp->id, rp->gii.ipaddr, rp->ciaddr); + sendnak(rp, "invalid ip address"); + return; + } + mkoffer(b, rp->id, rp->leasetime); + if(commitbinding(b) < 0){ + warning(0, "!Request(%s via %I): can't commit %I", + rp->id, rp->gii.ipaddr, b->ip); + sendnak(rp, "can't commit binding"); + return; + } + sendack(rp, b->ip, b->offer, 1); + } +} + +void +rcvdecline(Req *rp) +{ + Binding *b; + char buf[64]; + + if(rp->staticbinding) + return; + + b = idtooffer(rp->id, &rp->gii); + if(b == nil){ + warning(0, "!Decline(%s via %I): no binding", + rp->id, rp->gii.ipaddr); + return; + } + + /* mark ip address as in use */ + snprint(buf, sizeof(buf), "declined by %s", rp->id); + mkoffer(b, buf, 0x7fffffff); + commitbinding(b); +} + +void +rcvrelease(Req *rp) +{ + Binding *b; + + if(rp->staticbinding) + return; + + b = idtobinding(rp->id, &rp->gii, 0); + if(b == nil){ + warning(0, "!Release(%s via %I): no binding", + rp->id, rp->gii.ipaddr); + return; + } + if(strcmp(rp->id, b->boundto) != 0){ + warning(0, "!Release(%s via %I): invalid release of %I", + rp->id, rp->gii.ipaddr, rp->ip); + return; + } + warning(0, "Release(%s via %I): releasing %I", b->boundto, rp->gii.ipaddr, b->ip); + if(releasebinding(b, rp->id) < 0) + warning(0, "release: couldn't release"); +} + +void +rcvinform(Req *rp) +{ + Binding *b; + + if(rp->staticbinding){ + sendack(rp, rp->ii.ipaddr, 0, 0); + return; + } + + b = iptobinding(rp->ciaddr, 0); + if(b == nil){ + warning(0, "!Inform(%s via %I): no binding for %I", + rp->id, rp->gii.ipaddr, rp->ip); + return; + } + sendack(rp, b->ip, 0, 0); +} + +int +setsiaddr(uchar *siaddr, uchar *saddr, uchar *laddr) +{ + if(ipcmp(saddr, IPnoaddr) != 0){ + v6tov4(siaddr, saddr); + return 0; + } else { + v6tov4(siaddr, laddr); + return 1; + } +} + +void +sendoffer(Req *rp, uchar *ip, int offer) +{ + int n; + int fd; + ushort flags; + Bootp *bp; + Udphdr *up; + + bp = rp->bp; + up = rp->up; + + /* + * set destination + */ + flags = nhgets(bp->flags); + fd = wfd; + if(validip(rp->giaddr)){ + ipmove(up->raddr, rp->giaddr); + hnputs(up->rport, 67); + } else if(flags & Fbroadcast){ + fd = bwfd; + ipmove(up->raddr, IPv4bcast); + hnputs(up->rport, 68); + } else { + ipmove(up->raddr, ip); + if(bp->htype == 1) + arpenter(up->raddr, bp->chaddr); + hnputs(up->rport, 68); + } + + /* + * fill in standard bootp part + */ + bp->op = Bootreply; + bp->hops = 0; + hnputs(bp->secs, 0); + memset(bp->ciaddr, 0, sizeof(bp->ciaddr)); + v6tov4(bp->giaddr, rp->giaddr); + v6tov4(bp->yiaddr, ip); + setsiaddr(bp->siaddr, rp->ii.tftp, up->laddr); + strncpy(bp->sname, mysysname, sizeof(bp->sname)); + strncpy(bp->file, rp->ii.bootf, sizeof(bp->file)); + + /* + * set options + */ + byteopt(rp, ODtype, Offer); + longopt(rp, ODlease, offer); + addropt(rp, ODserverid, up->laddr); + miscoptions(rp, ip); + termopt(rp); + + logdhcpout(rp, "Offer"); + + /* + * send + */ + n = rp->p - rp->buf; +print("OFFER: %I %I %d %d\n", rp->up->laddr, rp->up->raddr, nhgets(rp->up->lport), nhgets(rp->up->rport)); + if(!mute && udpwrite(fd, rp->up, rp->buf, n) != n) + warning(0, "offer: write failed: %r"); +} + +void +sendack(Req *rp, uchar *ip, int offer, int sendlease) +{ + int n, fd; + ushort flags; + Bootp *bp; + Udphdr *up; + + bp = rp->bp; + up = rp->up; + + /* + * set destination + */ + fd = wfd; + flags = nhgets(bp->flags); + if(validip(rp->giaddr)){ + ipmove(up->raddr, rp->giaddr); + hnputs(up->rport, 67); + } else if(flags & Fbroadcast){ + fd = bwfd; + ipmove(up->raddr, IPv4bcast); + hnputs(up->rport, 68); + } else { + ipmove(up->raddr, ip); + if(bp->htype == 1) + arpenter(up->raddr, bp->chaddr); + hnputs(up->rport, 68); + } + + /* + * fill in standard bootp part + */ + bp->op = Bootreply; + bp->hops = 0; + hnputs(bp->secs, 0); + v6tov4(bp->giaddr, rp->giaddr); + v6tov4(bp->yiaddr, ip); + setsiaddr(bp->siaddr, rp->ii.tftp, up->laddr); + strncpy(bp->sname, mysysname, sizeof(bp->sname)); + strncpy(bp->file, rp->ii.bootf, sizeof(bp->file)); + + /* + * set options + */ + byteopt(rp, ODtype, Ack); + if(sendlease){ + longopt(rp, ODlease, offer); + } + addropt(rp, ODserverid, up->laddr); + miscoptions(rp, ip); + termopt(rp); + + logdhcpout(rp, "Ack"); + + /* + * send + */ + n = rp->p - rp->buf; + if(!mute && udpwrite(fd, rp->up, rp->buf, n) != n) + warning(0, "ack: write failed: %r"); +} + +void +sendnak(Req *rp, char *msg) +{ + int n, fd; + Bootp *bp; + Udphdr *up; + + bp = rp->bp; + up = rp->up; + + /* + * set destination (always broadcast) + */ + fd = wfd; + if(validip(rp->giaddr)){ + ipmove(up->raddr, rp->giaddr); + hnputs(up->rport, 67); + } else { + fd = bwfd; + ipmove(up->raddr, IPv4bcast); + hnputs(up->rport, 68); + } + + /* + * fill in standard bootp part + */ + bp->op = Bootreply; + bp->hops = 0; + hnputs(bp->secs, 0); + v6tov4(bp->giaddr, rp->giaddr); + memset(bp->ciaddr, 0, sizeof(bp->ciaddr)); + memset(bp->yiaddr, 0, sizeof(bp->yiaddr)); + memset(bp->siaddr, 0, sizeof(bp->siaddr)); + + /* + * set options + */ + byteopt(rp, ODtype, Nak); + addropt(rp, ODserverid, up->laddr); + if(msg) + stringopt(rp, ODmessage, msg); + if(strncmp(rp->id, "id", 2) == 0) + hexopt(rp, ODclientid, rp->id+2); + termopt(rp); + + logdhcpout(rp, "Nak"); + + /* + * send nak + */ + n = rp->p - rp->buf; + if(!mute && udpwrite(fd, rp->up, rp->buf, n) != n) + warning(0, "nak: write failed: %r"); +} + +void +bootp(Req *rp) +{ + int n, fd; + Bootp *bp; + Udphdr *up; + ushort flags; + Iplifc *lifc; + Info *iip; + + warning(0, "bootp %s %I->%I from %s via %I, file %s", + rp->genrequest ? "generic" : (rp->p9request ? "p9" : ""), + rp->up->raddr, rp->up->laddr, + rp->id, rp->gii.ipaddr, + rp->bp->file); + + if(nobootp) + return; + + bp = rp->bp; + up = rp->up; + iip = &rp->ii; + + if(rp->staticbinding == 0){ + warning(0, "bootp from unknown %s via %I", rp->id, rp->gii.ipaddr); + return; + } + + /* ignore if not for us */ + if(*bp->sname){ + if(strcmp(bp->sname, mysysname) != 0){ + bp->sname[20] = 0; + warning(0, "bootp for server %s", bp->sname); + return; + } + } else if(slow) + sleep(500); + + /* ignore if we don't know what file to load */ + if(*bp->file == 0){ + if(rp->genrequest && *iip->bootf2) /* if not plan 9 and we have an alternate file... */ + strncpy(bp->file, iip->bootf2, sizeof(bp->file)); + else if(*iip->bootf) + strncpy(bp->file, iip->bootf, sizeof(bp->file)); + else if(*bp->sname) /* if we were asked, respond no matter what */ + bp->file[0] = '\0'; + else { + warning(0, "no bootfile for %I", iip->ipaddr); + return; + } + } + + /* ignore if the file is unreadable */ + if((!rp->genrequest) && bp->file[0] && access(bp->file, 4) < 0){ + warning(0, "inaccessible bootfile1 %s", bp->file); + return; + } + + bp->op = Bootreply; + v6tov4(bp->yiaddr, iip->ipaddr); + if(rp->p9request){ + warning(0, "p9bootp: %I", iip->ipaddr); + memmove(bp->optmagic, plan9opt, 4); + if(iip->gwip == 0) + v4tov6(iip->gwip, bp->giaddr); + rp->p += sprint((char*)rp->p, "%V %I %I %I", iip->ipmask+IPv4off, iip->fsip, + iip->auip, iip->gwip); + sprint(optbuf, "%s", (char*)(bp->optmagic)); + } else if(rp->genrequest){ + warning(0, "genericbootp: %I", iip->ipaddr); + memmove(bp->optmagic, genericopt, 4); + miscoptions(rp, iip->ipaddr); + termopt(rp); + } else if(iip->vendor[0] != 0) { + warning(0, "bootp vendor field: %s", iip->vendor); + memset(rp->p, 0, 128-4); + rp->p += sprint((char*)bp->optmagic, "%s", iip->vendor); + } else { + memset(rp->p, 0, 128-4); + rp->p += 128-4; + } + + /* + * set destination + */ + fd = wfd; + flags = nhgets(bp->flags); + if(validip(rp->giaddr)){ + ipmove(up->raddr, rp->giaddr); + hnputs(up->rport, 67); + } else if(flags & Fbroadcast){ + fd = bwfd; + ipmove(up->raddr, IPv4bcast); + hnputs(up->rport, 68); + } else { + v4tov6(up->raddr, bp->yiaddr); + if(bp->htype == 1) + arpenter(up->raddr, bp->chaddr); + hnputs(up->rport, 68); + } + + /* + * select best local address if destination is directly connected + */ + lifc = findlifc(up->raddr); + if(lifc) + ipmove(up->laddr, lifc->ip); + + /* + * our identity + */ + strncpy(bp->sname, mysysname, sizeof(bp->sname)); + + /* + * set tftp server + */ + setsiaddr(bp->siaddr, iip->tftp, up->laddr); + if(rp->genrequest && *iip->bootf2) + setsiaddr(bp->siaddr, iip->tftp2, up->laddr); + + /* + * RFC 1048 says that we must pad vendor field with + * zeros until we have a 64 byte field. + */ + n = rp->p - rp->bp->optdata; + if(n < 64-4) { + memset(rp->p, 0, (64-4)-n); + rp->p += (64-4)-n; + } + + /* + * send + */ + n = rp->p - rp->buf; + if(!mute && udpwrite(fd, rp->up, rp->buf, n) != n) + warning(0, "bootp: write failed: %r"); + + warning(0, "bootp via %I: file %s xid(%ux)flag(%ux)ci(%V)gi(%V)yi(%V)si(%V) %s", + up->raddr, bp->file, nhgetl(bp->xid), nhgets(bp->flags), + bp->ciaddr, bp->giaddr, bp->yiaddr, bp->siaddr, + optbuf); +} + +void +parseoptions(Req *rp) +{ + int n, c, code; + uchar *o, *p; + + p = rp->p; + + while(p < rp->e){ + code = *p++; + if(code == 255) + break; + if(code == 0) + continue; + + /* ignore anything that's too long */ + n = *p++; + o = p; + p += n; + if(p > rp->e) + return; + + switch(code){ + case ODipaddr: /* requested ip address */ + if(n == IPv4addrlen) + v4tov6(rp->ip, o); + break; + case ODlease: /* requested lease time */ + rp->leasetime = nhgetl(o); + if(rp->leasetime > MaxLease || rp->leasetime < 0) + rp->leasetime = MaxLease; + break; + case ODtype: + c = *o; + if(c < 10 && c > 0) + rp->dhcptype = c; + break; + case ODserverid: + if(n == IPv4addrlen) + v4tov6(rp->server, o); + break; + case ODmessage: + if(n > sizeof rp->msg-1) + n = sizeof rp->msg-1; + memmove(rp->msg, o, n); + rp->msg[n] = 0; + break; + case ODmaxmsg: + c = nhgets(o); + c -= 28; + if(c > 0) + rp->max = rp->buf + c; + break; + case ODclientid: + if(n <= 1) + break; + rp->id = toid( o, n); + break; + case ODparams: + if(n > sizeof(rp->requested)) + n = sizeof(rp->requested); + memmove(rp->requested, o, n); + break; + case ODvendorclass: + if(n >= sizeof(rp->vendorclass)) + n = sizeof(rp->vendorclass)-1; + memmove(rp->vendorclass, o, n); + rp->vendorclass[n] = 0; + if(strncmp((char*)rp->vendorclass, "p9-", 3) == 0) + strcpy(rp->cputype, (char*)rp->vendorclass+3); + break; + case OBend: + return; + } + } +} + +void +remrequested(Req *rp, int opt) +{ + uchar *p; + + p = memchr(rp->requested, opt, sizeof(rp->requested)); + if(p != nil) + *p = OBpad; +} + +void +miscoptions(Req *rp, uchar *ip) +{ + char *p; + int i, j; + uchar *addrs[2]; + uchar x[2*IPaddrlen]; + uchar vopts[64]; + uchar *op, *omax; + char *attr[100], **a; + int na; + Ndbtuple *t; + + addrs[0] = x; + addrs[1] = x+IPaddrlen; + + /* always supply these */ + maskopt(rp, OBmask, rp->gii.ipmask); + if(validip(rp->gii.gwip)){ + remrequested(rp, OBrouter); + addropt(rp, OBrouter, rp->gii.gwip); + } else if(validip(rp->giaddr)){ + remrequested(rp, OBrouter); + addropt(rp, OBrouter, rp->giaddr); + } + + // OBhostname for the HP4000M switches + // (this causes NT to log infinite errors - tough shit ) + if(*rp->ii.domain){ + remrequested(rp, OBhostname); + stringopt(rp, OBhostname, rp->ii.domain); + } + if(*rp->ii.rootpath) + stringopt(rp, OBrootpath, rp->ii.rootpath); + + /* figure out what we need to lookup */ + na = 0; + a = attr; + if(*rp->ii.domain == 0) + a[na++] = "dom"; + for(i = 0; i < sizeof(rp->requested); i++) + switch(rp->requested[i]){ + case OBrouter: + a[na++] = "@ipgw"; + break; + case OBdnserver: + a[na++] = "@dns"; + break; + case OBnetbiosns: + a[na++] = "@wins"; + break; + case OBsmtpserver: + a[na++] = "@smtp"; + break; + case OBpop3server: + a[na++] = "@pop3"; + break; + case OBwwwserver: + a[na++] = "@www"; + break; + case OBntpserver: + a[na++] = "@ntp"; + break; + case OBtimeserver: + a[na++] = "@time"; + break; + } + if(strncmp((char*)rp->vendorclass, "plan9_", 6) == 0 + || strncmp((char*)rp->vendorclass, "p9-", 3) == 0){ + a[na++] = "@fs"; + a[na++] = "@auth"; + } + t = lookupinfo(ip, a, na); + + /* lookup anything we might be missing */ + if(*rp->ii.domain == 0) + lookupname(rp->ii.domain, t); + + /* add any requested ones that we know about */ + for(i = 0; i < sizeof(rp->requested); i++) + switch(rp->requested[i]){ + case OBrouter: + j = lookupserver("ipgw", addrs, t); + addrsopt(rp, OBrouter, addrs, j); + break; + case OBdnserver: + j = lookupserver("dns", addrs, t); + addrsopt(rp, OBdnserver, addrs, j); + break; + case OBhostname: + if(*rp->ii.domain) + stringopt(rp, OBhostname, rp->ii.domain); + break; + case OBdomainname: + p = strchr(rp->ii.domain, '.'); + if(p) + stringopt(rp, OBdomainname, p+1); + break; + case OBnetbiosns: + j = lookupserver("wins", addrs, t); + addrsopt(rp, OBnetbiosns, addrs, j); + break; + case OBnetbiostype: + /* p-node: peer to peer WINS queries */ + byteopt(rp, OBnetbiostype, 0x2); + break; + case OBsmtpserver: + j = lookupserver("smtp", addrs, t); + addrsopt(rp, OBsmtpserver, addrs, j); + break; + case OBpop3server: + j = lookupserver("pop3", addrs, t); + addrsopt(rp, OBpop3server, addrs, j); + break; + case OBwwwserver: + j = lookupserver("www", addrs, t); + addrsopt(rp, OBwwwserver, addrs, j); + break; + case OBntpserver: + j = lookupserver("ntp", addrs, t); + addrsopt(rp, OBntpserver, addrs, j); + break; + case OBtimeserver: + j = lookupserver("time", addrs, t); + addrsopt(rp, OBtimeserver, addrs, j); + break; + case OBttl: + byteopt(rp, OBttl, 255); + break; + } + + // add plan9 specific options + if(strncmp((char*)rp->vendorclass, "plan9_", 6) == 0 + || strncmp((char*)rp->vendorclass, "p9-", 3) == 0){ + // point to temporary area + op = rp->p; + omax = rp->max; + rp->p = vopts; + rp->max = vopts + sizeof(vopts) - 1; + + j = lookupserver("fs", addrs, t); + addrsopt(rp, OP9fs, addrs, j); + j = lookupserver("auth", addrs, t); + addrsopt(rp, OP9auth, addrs, j); + + // point back + j = rp->p - vopts; + rp->p = op; + rp->max = omax; + vectoropt(rp, OBvendorinfo, vopts, j); + } + + ndbfree(t); +} + +int +openlisten(char *net) +{ + int fd; + char data[128]; + char devdir[40]; + int yes; + + sprint(data, "udp!*!bootps"); + fd = announce(data, devdir); + if(fd < 0) + fatal(1, "can't announce"); + yes = 1; + if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes) < 0) + fatal(1, "can't broadcast"); + return fd; +} + +void +fatal(int syserr, char *fmt, ...) +{ + char buf[ERRMAX]; + va_list arg; + + va_start(arg, fmt); + vseprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + if(syserr) + syslog(1, blog, "%s: %r", buf); + else + syslog(1, blog, "%s", buf); + exits(buf); +} + +extern void +warning(int syserr, char *fmt, ...) +{ + char buf[256]; + va_list arg; + + va_start(arg, fmt); + vseprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + if(syserr){ + syslog(0, blog, "%s: %r", buf); + if(debug) + fprint(2, "%s: %r\n", buf); + } else { + syslog(0, blog, "%s", buf); + if(debug) + fprint(2, "%s\n", buf); + } +} + +char* +readsysname(void) +{ + static char name[128]; + char *p; + int n, fd; + + fd = open("/dev/sysname", OREAD); + if(fd >= 0){ + n = read(fd, name, sizeof(name)-1); + close(fd); + if(n > 0){ + name[n] = 0; + return name; + } + } + p = getenv("sysname"); + if(p == nil || *p == 0) + return "unknown"; + return p; +} + +extern int +validip(uchar *ip) +{ + if(ipcmp(ip, IPnoaddr) == 0) + return 0; + if(ipcmp(ip, v4prefix) == 0) + return 0; + return 1; +} + +void +longopt(Req *rp, int t, long v) +{ + if(rp->p + 6 > rp->max) + return; + *rp->p++ = t; + *rp->p++ = 4; + hnputl(rp->p, v); + rp->p += 4; + + op = seprint(op, oe, "%s(%ld)", optname[t], v); +} + +void +addropt(Req *rp, int t, uchar *ip) +{ + if(rp->p + 6 > rp->max) + return; + *rp->p++ = t; + *rp->p++ = 4; + memmove(rp->p, ip+IPv4off, 4); + rp->p += 4; + + op = seprint(op, oe, "%s(%I)", optname[t], ip); +} + +void +maskopt(Req *rp, int t, uchar *ip) +{ + if(rp->p + 6 > rp->max) + return; + *rp->p++ = t; + *rp->p++ = 4; + memmove(rp->p, ip+IPv4off, 4); + rp->p += 4; + + op = seprint(op, oe, "%s(%M)", optname[t], ip); +} + +void +addrsopt(Req *rp, int t, uchar **ip, int i) +{ + if(i <= 0) + return; + if(rp->p + 2 + 4*i > rp->max) + return; + *rp->p++ = t; + *rp->p++ = 4*i; + op = seprint(op, oe, "%s(", optname[t]); + while(i-- > 0){ + v6tov4(rp->p, *ip); + rp->p += 4; + op = seprint(op, oe, "%I", *ip); + ip++; + if(i > 0) + op = seprint(op, oe, " "); + } + op = seprint(op, oe, ")"); +} + +void +byteopt(Req *rp, int t, uchar v) +{ + if(rp->p + 3 > rp->max) + return; + *rp->p++ = t; + *rp->p++ = 1; + *rp->p++ = v; + + op = seprint(op, oe, "%s(%d)", optname[t], v); +} + +void +termopt(Req *rp) +{ + if(rp->p + 1 > rp->max) + return; + *rp->p++ = OBend; +} + +void +stringopt(Req *rp, int t, char *str) +{ + int n; + + n = strlen(str); + if(n > 255) + n = 255; + if(rp->p+n+2 > rp->max) + return; + *rp->p++ = t; + *rp->p++ = n; + memmove(rp->p, str, n); + rp->p += n; + + op = seprint(op, oe, "%s(%s)", optname[t], str); +} + +void +vectoropt(Req *rp, int t, uchar *v, int n) +{ + int i; + + if(n > 255) + n = 255; + if(rp->p+n+2 > rp->max) + return; + *rp->p++ = t; + *rp->p++ = n; + memmove(rp->p, v, n); + rp->p += n; + + op = seprint(op, oe, "%s(", optname[t]); + if(n > 0) + op = seprint(op, oe, "%ud", 0); + for(i = 1; i < n; i++) + op = seprint(op, oe, " %ud", v[i]); +} + +int +fromhex(int x) +{ + if(x >= '0' && x <= '9') + return x - '0'; + return x - 'a'; +} + +void +hexopt(Req *rp, int t, char *str) +{ + int n; + + n = strlen(str); + n /= 2; + if(n > 255) + n = 255; + if(rp->p+n+2 > rp->max) + return; + *rp->p++ = t; + *rp->p++ = n; + while(n-- > 0){ + *rp->p++ = (fromhex(str[0])<<4)|fromhex(str[1]); + str += 2; + } + + op = seprint(op, oe, "%s(%s)", optname[t], str); +} + +/* + * What a crock it is to do this for real. + * A giant hairy mess of ioctls that differ from + * system to system. Don't get sucked in. + * This need not be fast. + */ +void +arpenter(uchar *ip, uchar *ether) +{ + int pid; + char xip[100], xether[100]; + + switch(pid=fork()){ + case -1: + break; + default: + waitpid(); + break; + case 0: + snprint(xip, sizeof xip, "%I", ip); + snprint(xether, sizeof xether, "%#E", ether); + execl("arp", "arp", "-s", xip, xether, "temp", nil); + _exits("execl"); + } +/* + for comfort - ah, the good old days + + int f; + char buf[256]; + + sprint(buf, "%s/arp", net); + f = open(buf, OWRITE); + if(f < 0){ + syslog(debug, blog, "open %s: %r", buf); + return; + } + fprint(f, "add ether %I %E", ip, ether); + close(f); +*/ +} + +char *dhcpmsgname[] = +{ + [Discover] "Discover", + [Offer] "Offer", + [Request] "Request", + [Decline] "Decline", + [Ack] "Ack", + [Nak] "Nak", + [Release] "Release", + [Inform] "Inform", +}; + +void +logdhcp(Req *rp) +{ + char buf[4096]; + char *p, *e; + int i; + + p = buf; + e = buf + sizeof(buf); + if(rp->dhcptype > 0 && rp->dhcptype <= Inform) + p = seprint(p, e, "%s(", dhcpmsgname[rp->dhcptype]); + else + p = seprint(p, e, "%d(", rp->dhcptype); + p = seprint(p, e, "%I->%I) xid(%ux)flag(%ux)", rp->up->raddr, rp->up->laddr, + nhgetl(rp->bp->xid), nhgets(rp->bp->flags)); + if(rp->bp->htype == 1) + p = seprint(p, e, "ea(%E)", rp->bp->chaddr); + if(validip(rp->ciaddr)) + p = seprint(p, e, "ci(%I)", rp->ciaddr); + if(validip(rp->giaddr)) + p = seprint(p, e, "gi(%I)", rp->giaddr); + if(validip(rp->ip)) + p = seprint(p, e, "ip(%I)", rp->ip); + if(rp->id != nil) + p = seprint(p, e, "id(%s)", rp->id); + if(rp->leasetime) + p = seprint(p, e, "leas(%d)", rp->leasetime); + if(validip(rp->server)) + p = seprint(p, e, "sid(%I)", rp->server); + p = seprint(p, e, "need("); + for(i = 0; i < sizeof(rp->requested); i++) + if(rp->requested[i] != 0) + p = seprint(p, e, "%s ", optname[rp->requested[i]]); + p = seprint(p, e, ")"); + + USED(p); + syslog(0, blog, "%s", buf); +} + +void +logdhcpout(Req *rp, char *type) +{ + syslog(0, blog, "%s(%I->%I)id(%s)ci(%V)gi(%V)yi(%V)si(%V) %s", + type, rp->up->laddr, rp->up->raddr, rp->id, + rp->bp->ciaddr, rp->bp->giaddr, rp->bp->yiaddr, rp->bp->siaddr, optbuf); +} + +/* + * if we get behind, it's useless to try answering since the sender + * will probably have retransmitted with a differnt sequence number. + * So dump all the last message in the queue. + */ +void ding(void *x, char *msg) +{ + USED(x); + + if(strstr(msg, "alarm")) + noted(NCONT); + else + noted(NDFLT); +} + +int +readlast(int fd, Udphdr *hdr, uchar *buf, int len) +{ + int lastn, n; + + notify(ding); + + lastn = 0; + for(;;){ + alarm(20); + n = udpread(fd, hdr, buf, len); + alarm(0); + if(n < 0){ + if(lastn > 0) + return lastn; + break; + } + lastn = n; + } + return udpread(fd, hdr, buf, len); +} |