home *** CD-ROM | disk | FTP | other *** search
- /* This probably doesn't work on anything other then Ethernet! It may work
- on PLIP as well, but ARCnet and Token Ring are unlikely at best. */
-
- #include <errno.h>
- #include <net/if.h>
- #include <net/route.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/ioctl.h>
- #include <sys/time.h>
- #include <sys/types.h>
- #include <unistd.h>
-
- #include "bootpc.h"
- #include "net.h"
-
- #define NUM_RETRIES 3
-
- #ifdef STANDALONE
- #define _(foo) ((foo))
- #include <stdarg.h>
- #else
- #include "log.h"
- #include "intl.h"
- #endif
-
- #ifdef STANDALONE
- void logMessage(char * mess, ...) {
- va_list args;
-
- va_start(args, mess);
- vprintf(mess, args);
- va_end(args);
- puts("");
- }
- #endif
-
- typedef int bp_int32;
- typedef short bp_int16;
-
- #define BOOTP_OPTION_NETMASK 1
- #define BOOTP_OPTION_GATEWAY 3
- #define BOOTP_OPTION_DNS 6
- #define BOOTP_OPTION_HOSTNAME 12
- #define BOOTP_OPTION_BOOTFILE 13
- #define BOOTP_OPTION_DOMAIN 15
- #define BOOTP_OPTION_BROADCAST 28
-
- #define DHCP_OPTION_REQADDR 50
- #define DHCP_OPTION_LEASE 51
- #define DHCP_OPTION_OVERLOAD 52
- #define DHCP_OPTION_TYPE 53
- #define DHCP_OPTION_SERVER 54
- #define DHCP_OPTION_T1 58
-
- #define BOOTP_CLIENT_PORT 68
- #define BOOTP_SERVER_PORT 67
-
- #define BOOTP_OPCODE_REQUEST 1
- #define BOOTP_OPCODE_REPLY 2
-
- #define DHCP_TYPE_DISCOVER 1
- #define DHCP_TYPE_OFFER 2
- #define DHCP_TYPE_REQUEST 3
- #define DHCP_TYPE_DECLINE 4
- #define DHCP_TYPE_ACK 5
- #define DHCP_TYPE_NAK 6
- #define DHCP_TYPE_RELEASE 7
- #define DHCP_TYPE_INFORM 8
-
- #define BOOTP_VENDOR_LENGTH 64
- #define DHCP_VENDOR_LENGTH 340
-
- struct bootpRequest {
- char opcode;
- char hw;
- char hwlength;
- char hopcount;
- bp_int32 id;
- bp_int16 secs;
- bp_int16 flags;
- bp_int32 ciaddr, yiaddr, server_ip, bootp_gw_ip;
- char hwaddr[16];
- char servername[64];
- char bootfile[128];
- char vendor[DHCP_VENDOR_LENGTH];
- } ;
-
- static const char vendCookie[] = { 99, 130, 83, 99, 255 };
-
- static char * perrorstr(char * msg);
- static char * setupInterface(char * device, int s);
- static void parseReply(struct bootpRequest * breq, struct intfInfo * intf,
- struct netInfo * net);
- static char * prepareRequest(struct bootpRequest * breq,
- int sock, char * device, time_t startTime);
- static void initVendorCodes(struct bootpRequest * breq);
- static char * handleTransaction(int s, int timeout, struct bootpRequest * breq,
- struct bootpRequest * bresp,
- struct sockaddr_in * serverAddr,
- struct sockaddr_in * respondant,
- int dhcpType);
- static int dhcpMessageType(struct bootpRequest * response);
-
- static char * setupInterface(char * device, int s) {
- struct sockaddr_in * addrp;
- struct ifreq req;
- struct rtentry route;
- int true = 1;
-
- addrp = (struct sockaddr_in *) &req.ifr_addr;
-
- strcpy(req.ifr_name, device);
- addrp->sin_family = AF_INET;
- addrp->sin_port = 0;
- memset(&addrp->sin_addr, 0, sizeof(addrp->sin_addr));
- memset(&route, 0, sizeof(route));
- memcpy(&route.rt_dst, addrp, sizeof(route.rt_dst));
-
- if (ioctl(s, SIOCSIFADDR, &req))
- return perrorstr("SIOCSIFADDR");
-
- if (ioctl(s, SIOCSIFNETMASK, &req))
- return perrorstr("SIOCSIFNETMASK");
-
- /* the broadcast address is 255.255.255.255 */
- memset(&addrp->sin_addr,255 , sizeof(addrp->sin_addr));
- if (ioctl(s, SIOCSIFBRDADDR, &req))
- return perrorstr("SIOCSIFBRDADDR");
-
-
- req.ifr_flags = IFF_UP | IFF_BROADCAST | IFF_RUNNING;
- if (ioctl(s, SIOCSIFFLAGS, &req))
- return perrorstr("SIOCSIFFLAGS");
-
- route.rt_dev = device;
- route.rt_flags = RTF_UP;
-
- if (ioctl(s, SIOCADDRT, &route))
- return perrorstr("SIOCSADDRT");
-
- if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &true, 1)) {
- close(s);
- return perrorstr("setsockopt");
- }
-
- return NULL;
- }
-
- static int dhcpMessageType(struct bootpRequest * response) {
- unsigned char * chptr;
- unsigned char option, length;
-
- chptr = response->vendor;
-
- chptr += 4;
- while (*chptr != 0xFF) {
- option = *chptr++;
- if (!option) continue;
- length = *chptr++;
- if (option == DHCP_OPTION_TYPE)
- return *chptr;
-
- chptr += length;
- }
-
- return -1;
- }
-
- void setMissingIpInfo(struct intfInfo * intf) {
- bp_int32 ipNum = *((bp_int32 *) &intf->ip);
- bp_int32 nmNum = *((bp_int32 *) &intf->netmask);
- bp_int32 ipRealNum = ntohl(ipNum);
-
- if (!(intf->set & INTFINFO_HAS_NETMASK)) {
- if (((ipRealNum & 0xFF000000) >> 24) <= 127)
- nmNum = 0xFF000000;
- else if (((ipRealNum & 0xFF000000) >> 24) <= 191)
- nmNum = 0xFFFF0000;
- else
- nmNum = 0xFFFFFF00;
- *((bp_int32 *) &intf->netmask) = nmNum = htonl(nmNum);
- }
-
- if (!(intf->set & INTFINFO_HAS_BROADCAST))
- *((bp_int32 *) &intf->broadcast) = (ipNum & nmNum) | ~(nmNum);
-
- if (!(intf->set & INTFINFO_HAS_NETWORK))
- *((bp_int32 *) &intf->network) = ipNum & nmNum;
-
- intf->set |= INTFINFO_HAS_BROADCAST | INTFINFO_HAS_NETWORK |
- INTFINFO_HAS_NETMASK;
- }
-
- static void parseReply(struct bootpRequest * breq, struct intfInfo * intf,
- struct netInfo * net) {
- unsigned int i;
- unsigned char * chptr;
- unsigned char option, length;
-
- i = ~(INTFINFO_HAS_IP | INTFINFO_HAS_NETMASK | INTFINFO_HAS_NETWORK |
- INTFINFO_HAS_BROADCAST);
- intf->set &= i;
- intf->manuallySet &= i;
-
- if (strlen(breq->bootfile)) {
- intf->bootFile = strdup(breq->bootfile);
- intf->set |= INTFINFO_HAS_BOOTFILE;
- } else {
- intf->set &= ~INTFINFO_HAS_BOOTFILE;
- }
-
- if (breq->server_ip) {
- intf->set |= INTFINFO_HAS_BOOTSERVER;
- memcpy(&intf->bootServer, &breq->server_ip, sizeof(breq->server_ip));
- }
-
- memcpy(&intf->ip, &breq->yiaddr, 4);
- intf->set |= INTFINFO_HAS_IP;
-
- chptr = breq->vendor;
- chptr += 4;
- while (*chptr != 0xFF) {
- option = *chptr++;
- if (!option) continue;
- length = *chptr++;
-
- switch (option) {
- case BOOTP_OPTION_DNS:
- net->numDns = 0;
- for (i = 0; i < length; i += 4)
- memcpy(&net->dnsServers[net->numDns++], chptr + i, 4);
- net->set |= NETINFO_HAS_DNS;
- break;
-
- case BOOTP_OPTION_NETMASK:
- memcpy(&intf->netmask, chptr, 4);
- intf->set |= INTFINFO_HAS_NETMASK;
- break;
-
- case BOOTP_OPTION_DOMAIN:
- net->domain = malloc(length + 1);
- memcpy(net->domain, chptr, length);
- net->domain[length] = '\0';
- net->set |= NETINFO_HAS_DOMAIN;
- break;
-
- case BOOTP_OPTION_BROADCAST:
- memcpy(&intf->broadcast, chptr, 4);
- intf->set |= INTFINFO_HAS_BROADCAST;
- break;
-
- case BOOTP_OPTION_GATEWAY:
- memcpy(&net->gateway, chptr, 4);
- net->set |= NETINFO_HAS_GATEWAY;
- break;
-
- case BOOTP_OPTION_HOSTNAME:
- net->hostname = malloc(length + 1);
- memcpy(net->hostname, chptr, length);
- net->hostname[length] = '\0';
- net->set |= NETINFO_HAS_HOSTNAME;
- break;
-
- case BOOTP_OPTION_BOOTFILE:
- /* we ignore this right now */
- break;
-
- case DHCP_OPTION_OVERLOAD:
- /* FIXME: we should pay attention to this */
- logMessage("dhcp overload option is currently ignored!");
- break;
- }
-
- chptr += length;
- }
-
- setMissingIpInfo(intf);
- }
-
- static char * perrorstr(char * msg) {
- static char * err = NULL;
- static int errsize = 0;
- static int newsize;
-
- newsize = strlen(msg) + strlen(strerror(errno)) + 3;
- if (!errsize) {
- errsize = newsize;
- err = malloc(errsize);
- } else if (errsize < newsize) {
- free(err);
- errsize = newsize;
- err = malloc(errsize);
- }
-
- sprintf(err, "%s: %s", msg, strerror(errno));
-
- return err;
- }
-
- static void initVendorCodes(struct bootpRequest * breq) {
- memcpy(breq->vendor, vendCookie, sizeof(vendCookie));
- }
-
- static char * prepareRequest(struct bootpRequest * breq,
- int sock, char * device, time_t startTime) {
- struct ifreq req;
- int i;
-
- memset(breq, 0, sizeof(*breq));
-
- breq->opcode = BOOTP_OPCODE_REQUEST;
-
- strcpy(req.ifr_name, device);
- if (ioctl(sock, SIOCGIFHWADDR, &req))
- return perrorstr("SIOCSIFHWADDR");
-
- breq->hw = 1; /* ethernet */
- breq->hwlength = IFHWADDRLEN;
- memcpy(breq->hwaddr, req.ifr_hwaddr.sa_data, IFHWADDRLEN);
-
- /* we should use something random here, but I don't want to start using
- stuff from the math library */
- breq->id = time(NULL);
- for (i = 0; i < IFHWADDRLEN; i++)
- breq->id ^= breq->hwaddr[i] << (8 * (i % 4));
-
- breq->hopcount = 0;
- breq->secs = time(NULL) - startTime;
-
- initVendorCodes(breq);
-
- return NULL;
- }
-
- static char * handleTransaction(int s, int timeout, struct bootpRequest * breq,
- struct bootpRequest * bresp,
- struct sockaddr_in * serverAddr,
- struct sockaddr_in * respondant,
- int dhcpType) {
- struct timeval tv;
- fd_set readfs;
- int i;
- struct sockaddr_in tmpAddress;
- int gotit = 0;
- int tries = NUM_RETRIES;
- int nextTimeout = 4;
-
- while (!gotit && tries--) {
- i = sizeof(*breq);
- if (dhcpType == -1)
- i -= (DHCP_VENDOR_LENGTH - BOOTP_VENDOR_LENGTH);
-
- if (sendto(s, breq, i, 0, (struct sockaddr *) serverAddr,
- sizeof(*serverAddr)) != i) {
- close(s);
- return perrorstr("sendto");
- }
-
- tv.tv_usec = 0;
- tv.tv_sec = nextTimeout;
- if (tv.tv_sec > timeout) tv.tv_sec = timeout;
- timeout -= tv.tv_sec;
- nextTimeout *= 2;
- switch (time(NULL) & 4) {
- case 0: nextTimeout--; break;
- case 1: nextTimeout++; break;
- }
-
- FD_ZERO(&readfs);
- FD_SET(s, &readfs);
- switch ((select(s + 1, &readfs, NULL, NULL, &tv))) {
- case 0:
- break;
-
- case 1:
- i = sizeof(tmpAddress);
- if (recvfrom(s, bresp, sizeof(*bresp), 0,
- (struct sockaddr *) &tmpAddress, &i) < 0)
- return perrorstr("recvfrom");
-
- /* sanity checks */
- if (bresp->id != breq->id) continue;
- if (bresp->opcode != BOOTP_OPCODE_REPLY) continue;
- if (bresp->hwlength != breq->hwlength) continue;
- if (memcmp(bresp->hwaddr, breq->hwaddr, bresp->hwlength)) continue;
- if (dhcpMessageType(bresp) != dhcpType) continue;
- if (memcmp(bresp->vendor, vendCookie, 4)) continue;
- if (respondant) *respondant = tmpAddress;
- gotit = 1;
-
- break;
-
- default:
- return perrorstr("select");
- }
- }
-
- if (!gotit)
- if (dhcpType == -1)
- return _("No BOOTP reply received");
- else
- return _("No DHCP reply received");
-
- return NULL;
- }
-
- static void addVendorCode(struct bootpRequest * breq, unsigned char option,
- unsigned char length, void * data) {
- unsigned char * chptr;
- int theOption, theLength;
-
- chptr = breq->vendor;
- chptr += 4;
- while (*chptr != 0xFF && *chptr != option) {
- theOption = *chptr++;
- if (!theOption) continue;
- theLength = *chptr++;
- chptr += theLength;
- }
-
- *chptr++ = option;
- *chptr++ = length;
- memcpy(chptr, data, length);
- chptr[length] = 0xff;
- }
-
- static int getVendorCode(struct bootpRequest * bresp, unsigned char option,
- void * data) {
- unsigned char * chptr;
- unsigned int length, theOption;
-
- chptr = bresp->vendor;
- chptr += 4;
- while (*chptr != 0xFF && *chptr != option) {
- theOption = *chptr++;
- if (!theOption) continue;
- length = *chptr++;
- chptr += length;
- }
-
- if (*chptr++ == 0xff) return 1;
-
- length = *chptr++;
- memcpy(data, chptr, length);
-
- return 0;
- }
-
- static void hareyCarey(int signo) {
- logMessage("DHCP FAILED TO RENEW LEASE -- KILLING INSTALL");
- kill(getppid(), SIGFPE);
- }
-
- /* This is somewhat broken. We try only to renew the lease. If we fail,
- we don't try to completely rebind. This doesn't follow the DHCP spec,
- but for the install it should be a reasonable compromise. */
- static void dhcpDaemon(int s, struct bootpRequest * origResult, char * device) {
- unsigned int t1, lease; /* offsets from "now" */
- time_t now;
- struct bootpRequest breq, bresp;
- unsigned char messageType;
- struct sockaddr_in serverAddr;
- char * chptr;
- int renewLease = 1;
-
- while (renewLease) {
- now = time(NULL);
-
- if (getVendorCode(origResult, DHCP_OPTION_LEASE, &lease))
- renewLease = 0;
-
- if (getVendorCode(origResult, DHCP_OPTION_T1, &t1)) {
- /* this is equiv to t1 = 0.875 * lease, which is mandated by RFC */
- t1 = (7 * lease) >> 3;
- }
-
- t1 = 120;
- /* if this goes off, we have a problem! */
- signal(SIGALRM, hareyCarey);
- alarm((now + lease) - time(NULL));
-
- /* go to sleep until we're needed again */
- sleep((now + t1) - time(NULL));
-
- if ((chptr = prepareRequest(&breq, s, device, time(NULL)))) {
- close(s);
- while (1); /* problem */
- }
-
- messageType = DHCP_TYPE_REQUEST;
- addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType);
- breq.ciaddr = origResult->yiaddr;
-
- serverAddr.sin_family = AF_INET;
- serverAddr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */
- getVendorCode(origResult, DHCP_OPTION_SERVER, &serverAddr.sin_addr);
-
- if ((chptr = handleTransaction(s, 60, &breq, &bresp, &serverAddr,
- NULL, DHCP_TYPE_ACK))) {
- renewLease = 0;
- } else {
- *origResult = bresp;
- }
- }
-
- #ifdef STANDALONE
- printf("dhcp renewel failed\n");
- #else
- logMessage("DHCP FAILED TO RENEW LEASE -- KILLING INSTALL");
- kill(getppid(), SIGFPE);
- #endif
- }
-
- char * doDhcp(int timeout, struct intfInfo * intf, struct netInfo * net,
- int shouldDaemonize) {
- int s, i;
- struct sockaddr_in serverAddr;
- struct sockaddr_in clientAddr;
- struct sockaddr_in broadcastAddr;
- struct bootpRequest breq, bresp;
- unsigned char * chptr;
- unsigned char messageType;
- unsigned int lease;
- time_t startTime = time(NULL);
-
- s = socket(AF_INET, SOCK_DGRAM, 0);
- if (s < 0) {
- return perrorstr("socket");
- }
-
- if ((chptr = setupInterface(intf->device, s))) {
- close(s);
- return chptr;
- }
-
- if ((chptr = prepareRequest(&breq, s, intf->device, startTime))) {
- close(s);
- return chptr;
- }
-
- messageType = DHCP_TYPE_DISCOVER;
- addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType);
-
- memset(&clientAddr.sin_addr, 0, sizeof(&clientAddr.sin_addr));
- clientAddr.sin_family = AF_INET;
- clientAddr.sin_port = htons(BOOTP_CLIENT_PORT); /* bootp client */
-
- if (bind(s, (struct sockaddr *) &clientAddr, sizeof(clientAddr))) {
- return perrorstr("bind");
- }
-
- broadcastAddr.sin_family = AF_INET;
- broadcastAddr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */
- memset(&broadcastAddr.sin_addr, 0xff,
- sizeof(broadcastAddr.sin_addr)); /* broadcast */
-
- if ((chptr = handleTransaction(s, timeout, &breq, &bresp, &broadcastAddr,
- NULL, DHCP_TYPE_OFFER))) {
- close(s);
- return chptr;
- }
-
- serverAddr.sin_family = AF_INET;
- serverAddr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */
- if (getVendorCode(&bresp, DHCP_OPTION_SERVER, &serverAddr.sin_addr)) {
- close(s);
- return "DHCPOFFER didn't include server address";
- }
-
- initVendorCodes(&breq);
- messageType = DHCP_TYPE_REQUEST;
- addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType);
- addVendorCode(&breq, DHCP_OPTION_SERVER, 4, &serverAddr.sin_addr);
- addVendorCode(&breq, DHCP_OPTION_REQADDR, 4, &bresp.yiaddr);
-
- /* request a lease of 1 hour */
- i = htonl(60 * 60);
- addVendorCode(&breq, DHCP_OPTION_LEASE, 4, &i);
-
- if ((chptr = handleTransaction(s, timeout, &breq, &bresp, &broadcastAddr,
- NULL, DHCP_TYPE_ACK))) {
- close(s);
- return chptr;
- }
-
- if (getVendorCode(&bresp, DHCP_OPTION_LEASE, &lease))
- return "failed to get lease time\n";
- lease = ntohl(lease);
-
- if (lease && lease != 0xffffffff && shouldDaemonize) {
- #ifndef STANDALONE
- if (!fork())
- #endif
- dhcpDaemon(s, &bresp, intf->device);
- }
-
- close(s);
-
- parseReply(&bresp, intf, net);
-
- if (!(intf->set & INTFINFO_HAS_BOOTSERVER)) {
- intf->bootServer = serverAddr.sin_addr;
- intf->set |= INTFINFO_HAS_BOOTSERVER;
- }
-
- return NULL;
- }
-
- char * doBootp(int timeout, struct intfInfo * intf, struct netInfo * net) {
- int s;
- struct sockaddr_in clientAddr;
- struct sockaddr_in broadcastAddr;
- struct sockaddr_in serverAddr;
- struct bootpRequest breq, bresp;
- unsigned char * chptr;
- time_t startTime = time(NULL);
-
- s = socket(AF_INET, SOCK_DGRAM, 0);
- if (s < 0) {
- return perrorstr("socket");
- }
-
- if ((chptr = setupInterface(intf->device, s))) {
- close(s);
- return chptr;
- }
-
- if ((chptr = prepareRequest(&breq, s, intf->device, startTime))) {
- close(s);
- return chptr;
- }
-
- memset(&clientAddr.sin_addr, 0, sizeof(&clientAddr.sin_addr));
- clientAddr.sin_family = AF_INET;
- clientAddr.sin_port = htons(BOOTP_CLIENT_PORT); /* bootp client */
-
- if (bind(s, (struct sockaddr *) &clientAddr, sizeof(clientAddr))) {
- return perrorstr("bind");
- }
-
- broadcastAddr.sin_family = AF_INET;
- broadcastAddr.sin_port = htons(BOOTP_SERVER_PORT); /* bootp server */
- memset(&broadcastAddr.sin_addr, 0xff,
- sizeof(broadcastAddr.sin_addr)); /* broadcast */
-
- if ((chptr = handleTransaction(s, timeout, &breq, &bresp, &broadcastAddr,
- &serverAddr, -1))) {
- close(s);
- return chptr;
- }
-
- close(s);
-
- parseReply(&bresp, intf, net);
-
- if (!(intf->set & INTFINFO_HAS_BOOTSERVER)) {
- intf->bootServer = serverAddr.sin_addr;
- intf->set |= INTFINFO_HAS_BOOTSERVER;
- }
-
- return NULL;
- }
-
- #ifdef STANDALONE
- void main (void) {
- char * msg;
- struct intfInfo intf;
- struct netInfo net;
-
- memset(&intf, 0, sizeof(intf));
- memset(&net, 0, sizeof(net));
- strcpy(intf.device, "eth0");
-
- #ifndef USEBOOTP
- msg = doDhcp(30, &intf, &net);
- #else
- msg = doBootp(30, &intf, &net);
- #endif
-
- if (msg) {
- puts(msg);
- exit(1);
- }
-
- printf("got address %s\n", inet_ntoa(intf.ip));
- if (intf.set & INTFINFO_HAS_NETMASK)
- printf("got netmask %s\n", inet_ntoa(intf.netmask));
- if (net.set & NETINFO_HAS_DOMAIN)
- printf("got domain %s\n", net.domain);
- }
- #endif
-