/* Initialize an Ethernet interface
   Copyright (C) 1999, 2000 Free Software Foundation, Inc.
   Written by Thomas Bushnell, BSG.

   This file is part of the GNU Hurd.

   The GNU Hurd is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2, or (at
   your option) any later version.

   The GNU Hurd is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */

/*
 * Copyright 1999 Red Hat Software, Inc.
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* 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. */

#define DHCP_FLAG_NODAEMON	(1 << 0)
#define DHCP_FLAG_NOCONFIG	(1 << 1)
#define DHCP_FLAG_FORCEHNLOOKUP	(1 << 2)

#define RESULT_OKAY		0
#define RESULT_FAILED		1
#define RESULT_UNKNOWNIFACE	2

#define N_(foo) (foo)

#define PROGNAME "pump"
#define CONTROLSOCKET "/var/run/pump.sock"

#define _(foo) ((foo))
#include <stdarg.h>

void logMessage(char * mess, ...) {
    va_list args;

    va_start(args, mess);
    vprintf(mess, args);
    va_end(args);
    puts("");
}

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_OPTIONREQ		55
#define DHCP_OPTION_MAXSIZE		57
#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 NORESPONSE		-10
#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	312

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];
} ;

struct psuedohUdpHeader {
    bp_int32 source, dest;
    char zero;
    char protocol;
    bp_int16 len;
};

struct command {
    enum { CMD_STARTIFACE, CMD_RESULT, CMD_DIE, CMD_STOPIFACE, 
	   CMD_FORCERENEW, CMD_REQSTATUS, CMD_STATUS } type;
    union {
	struct {
	    char device[20];
	    int flags;
	    int reqLease;			/* in hours */
	    char reqHostname[200];
	} start;
	int result;				/* 0 for success */
	struct {
	    char device[20];
	} stop;
	struct {
	    char device[20];
	} renew;
	struct {
	    char device[20];
	} reqstatus;
	struct {
	    struct intfInfo intf;
	    char hostname[1024];
	    char domain[1024];
	    char bootFile[1024];
	} status;
    } u;
};

static char * doDhcp(char * device, int flags, int lease,
		     char * reqHostname, struct intfInfo * intf,
		     struct overrideInfo * override);

static const char vendCookie[] = { 99, 130, 83, 99, 255 };

static char * perrorstr(char * msg);
static char * setupInterface(struct intfInfo * intf, int s);
static char * prepareInterface(struct intfInfo * intf, int s);
static char * getInterfaceInfo(struct intfInfo * intf, int s);
static char * disableInterface(char * device);
static void parseReply(struct bootpRequest * breq, struct intfInfo * intf);
static char * prepareRequest(struct bootpRequest * breq,
			     int sock, char * device, time_t startTime);
static void parseLease(struct bootpRequest * bresp, struct intfInfo * intf);
static void initVendorCodes(struct bootpRequest * breq);
static char * handleTransaction(int s, struct overrideInfo * override, 
				struct bootpRequest * breq,
			        struct bootpRequest * bresp, 
			        struct sockaddr_in * serverAddr,
			        struct sockaddr_in * respondant,
				int useBootPacket, int dhcpResponseType);
static int dhcpMessageType(struct bootpRequest * response);
static void setMissingIpInfo(struct intfInfo * intf);
static int openControlSocket(char * configFile);
static int dhcpRenew(struct intfInfo * intf);
static int dhcpRelease(struct intfInfo * intf);

/* Various hurd-specific decls here */
#define _HURD_PFINET _HURD "pfinet"
io_t underlying_pfinet;
device_t ethernet_device;
char ethernet_address[ETH_ALEN];


/* Open the ethernet device */
char *
prepareInterface (struct intfInfo *intf, int s);
{
  u_int count;
  int addr[2];
  
  err = device_open (master_device, D_WRITE|D_READ, 
		     intf->device, &ethernet_device);
  if (err)
    return err;
  
  err = device_get_status (ethernet_device, NET_ADDRESS, addr, &count);
  if (err)
    return err;
  
  addr[0] = ntohl (addr[0]);
  addr[1] = ntohl (addr[1]);
  bcopy (addr, ethernet_address, ETH_ALEN);
  return 0;
}

/* Open the node underlying the pfinet translator, and set
   underlying_pfinet to a port to that file. */
error_t
open_underlying_pfinet ()
{
  char *path;
  
  if (underlying_pfinet != MACH_PORT_NULL)
    return 0;
  
  asprintf (&path, "%s/%d", _SERVERS_SOCKET, PF_INET);
  
  underlying_pfinet = file_name_lookup (path, O_NOTRANS, 0);
  free (path);
  return underlying_pfinet == MACH_PORT_NULL ? errno : 0;
}

/* Start pfinet with the specified arguments */
error_t
start_pfinet (char *argz, int argz_len)
{
  error_t open_function (int flags,
			 mach_port_t *underlying,
			 mach_msg_type_name_t *underlying_type,
			 task_t task, void *cookie)
    {
      int err;
      
      err = open_underlying_pfinet ();
      if (err)
	return err;
      
      *underlying = underlying_pfinet;
      *underlying_type = MACH_MSG_TYPE_COPY_SEND;
      return 0;
    }
  
  err = fshelp_start_translator (open_function, NULL,
				   _HURD_PFINET, argz, argz_len,
				   60 * 1000, &control);
  if (err)
    return err;
  
  err = file_set_translator (underlying_pfinet,
			     0, FS_TRANS_SET,
			     FSYS_GOAWAY_FORCE,
			     argz, argz_len,
			     control, MACH_MSG_TYPE_COPY_SEND);
  mach_port_deallocate (mach_task_self (), control);

  /* Force the C library to forget about any old cached server
     access port. */
  _hurd_socket_server (PFINET, 1);
}



int newKernel(void) {
#ifdef NOTHURD
    struct utsname ubuf;
    int major1, major2;

    uname(&ubuf);
    if (!strcasecmp(ubuf.sysname, "linux")) {
	if (sscanf(ubuf.release, "%d.%d", &major1, &major2) != 2 ||
		(major1 < 2) || (major1 == 2 && major2 == 0)) {
	    return 0;
	}
    }
#endif

    return 1;
}

static char * disableInterface(char * device) {
#ifdef NOTHURD
    struct ifreq req;
    int s;

    s = socket(AF_INET, SOCK_DGRAM, 0);

    strcpy(req.ifr_name, device);
    if (ioctl(s, SIOCGIFFLAGS, &req)) {
	close(s);
	return perrorstr("SIOCGIFFLAGS");
    }

    req.ifr_flags &= ~(IFF_UP | IFF_RUNNING);
    if (ioctl(s, SIOCSIFFLAGS, &req)) {
	close(s);
	return perrorstr("SIOCSIFFLAGS");
    }

    close(s);

    return NULL;
#else
    error_t err;

    err = open_underlying_pfinet ();
    if (err)
      return strerror (err);
    
    err = file_set_translator (underlying_pfinet,
			       0, FS_TRANS_SET | FS_TRANS_FORCE,
			       FSYS_GOAWAY_FORCE,
			       0, 0, MACH_PORT_NULL);
    return err ? strerror (err) : 0;
#endif
}

static char * setupInterface(struct intfInfo * intf, int s) {
    char * rc;
    char * s;
    char * argz = 0;
    int argz_len = 0;

    /* Construct the args to pfinet. */
    /* /hurd/pfinet */
    argz_add (&argz, &argz_len, _HURD_PFINET);

    /* Interface name */
    asprintf (&s, "--interface=%s", intf->decive);
    argz_add (&argz, &argz_len, s);
    free (s);
    
    /* Address */
    asprintf (&s, "--address=%s", inet_ntoa (intf->ip));
    argz_add (&argz, &argz_len, s);
    free (s);
    
    /* Netmask */
    asprintf (&s, "--netmask=%s", inet_ntoa (intf->netmask));
    argz_add (&argz, &argz_len, s);
    free (s);
    
    /* Gateway */
    asprintf (&s, "--gateway=%s", inet_ntoa (intf->gateway));
    argz_add (&argz, &argz_len, s);
    free (s);
    
    /* Now set it up */
    err = start_pfinet (argz, argz_len);
    free (argz);

    return err ? strerror (err) : 0;


#ifdef NOTHURD
    struct sockaddr_in * addrp;
    struct ifreq req;
    struct rtentry route;

    if ((rc = disableInterface(intf->device))) return rc;

    /* we have to have basic information to get this far */
    addrp = (struct sockaddr_in *) &req.ifr_addr;
    addrp->sin_family = AF_INET;
    strcpy(req.ifr_name, intf->device);
   
    addrp->sin_addr = intf->ip;
    if (ioctl(s, SIOCSIFADDR, &req))
	return perrorstr("SIOCSIFADDR");

    addrp->sin_addr = intf->netmask;
    if (ioctl(s, SIOCSIFNETMASK, &req))
	return perrorstr("SIOCSIFNETMASK");

    addrp->sin_addr = intf->broadcast;
    if (ioctl(s, SIOCSIFBRDADDR, &req))
	return perrorstr("SIOCSIFBRDADDR");

    /* bring up the device, and specifically allow broadcasts through it */
    req.ifr_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST;
    if (ioctl(s, SIOCSIFFLAGS, &req))
	return perrorstr("SIOCSIFFLAGS");

    /* add a route for this network */
    route.rt_dev = intf->device;
    route.rt_flags = RTF_UP;
    route.rt_metric = 0;

    if (!newKernel()) {
	addrp->sin_family = AF_INET;
	addrp->sin_port = 0;
	addrp->sin_addr = intf->network;
	memcpy(&route.rt_dst, addrp, sizeof(*addrp));
	addrp->sin_addr = intf->netmask;
	memcpy(&route.rt_genmask, addrp, sizeof(*addrp));

	if (ioctl(s, SIOCADDRT, &route)) {
	    /* the route cannot already exist, as we've taken the device down */
	    return perrorstr("SIOCADDRT 1");
	}
    }
    return NULL;
}

#ifdef NOTHURD
static char * setupDefaultGateway(struct in_addr * gw, int s) {
    struct sockaddr_in addr;
    struct rtentry route;

    addr.sin_family = AF_INET;
    addr.sin_port = 0;
    addr.sin_addr.s_addr = INADDR_ANY;
    memcpy(&route.rt_dst, &addr, sizeof(addr));
    memcpy(&route.rt_genmask, &addr, sizeof(addr));
    addr.sin_addr = *gw;
    memcpy(&route.rt_gateway, &addr, sizeof(addr));
    
    route.rt_flags = RTF_UP | RTF_GATEWAY;
    route.rt_metric = 0;
    route.rt_dev = NULL;

    if (ioctl(s, SIOCADDRT, &route)) {
	/* the route cannot already exist, as we've taken the device 
	   down */
	return perrorstr("SIOCADDRT 2");
    }

    return NULL;
}

static char * getInterfaceInfo(struct intfInfo * intf, int s) {
    struct ifreq req;
    struct sockaddr_in * addrp;

    strcpy(req.ifr_name, intf->device);
    if (ioctl(s, SIOCGIFBRDADDR, &req))
	return perrorstr("SIOCGIFBRDADDR");

    addrp = (struct sockaddr_in *) &req.ifr_addr;
    intf->broadcast = addrp->sin_addr;
    intf->set = INTFINFO_HAS_BROADCAST;

    return NULL;
}

static char * prepareInterface(struct intfInfo * intf, int s) {
    struct sockaddr_in * addrp;
    struct ifreq req;
    struct rtentry route;

    addrp = (struct sockaddr_in *) &req.ifr_addr;

    strcpy(req.ifr_name, intf->device);
    addrp->sin_family = AF_INET;
    addrp->sin_port = 0;
    memset(&addrp->sin_addr, 0, sizeof(addrp->sin_addr));

    addrp->sin_family = AF_INET;
    addrp->sin_addr.s_addr = htonl(0);

    if (ioctl(s, SIOCSIFADDR, &req))
	return perrorstr("SIOCSIFADDR");

    if (!newKernel()) {
	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");

    memset(&route, 0, sizeof(route));
    memcpy(&route.rt_gateway, addrp, sizeof(*addrp));

    addrp->sin_family = AF_INET;
    addrp->sin_port = 0;
    addrp->sin_addr.s_addr = INADDR_ANY;
    memcpy(&route.rt_dst, addrp, sizeof(*addrp));
    memcpy(&route.rt_genmask, addrp, sizeof(*addrp));

    route.rt_dev = intf->device;
    route.rt_flags = RTF_UP;
    route.rt_metric = 0;

    if (ioctl(s, SIOCADDRT, &route)) {
	if (errno != EEXIST) {
	    close(s);
	    return perrorstr("SIOCADDRT 3");
	}
    }

    return NULL;
}
#endif

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;
}

static 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);
	syslog (LOG_DEBUG, "intf: netmask: %s", inet_ntoa (intf->netmask));
    }

    if (!(intf->set & INTFINFO_HAS_BROADCAST)) {
	*((bp_int32 *) &intf->broadcast) = (ipNum & nmNum) | ~(nmNum);
	syslog (LOG_DEBUG, "intf: broadcast: %s", inet_ntoa (intf->broadcast));
    }

    if (!(intf->set & INTFINFO_HAS_NETWORK)) {
	*((bp_int32 *) &intf->network) = ipNum & nmNum;
	syslog (LOG_DEBUG, "intf: network: %s", inet_ntoa (intf->network));
    }

    intf->set |= INTFINFO_HAS_BROADCAST | INTFINFO_HAS_NETWORK | 
		 INTFINFO_HAS_NETMASK;
}

static void parseReply(struct bootpRequest * breq, struct intfInfo * intf) {
    unsigned int i;
    unsigned char * chptr;
    unsigned char option, length;

    syslog (LOG_DEBUG, "intf: device: %s", intf->device);
    syslog (LOG_DEBUG, "intf: set: %i", intf->set);
    syslog (LOG_DEBUG, "intf: bootServer: %s", inet_ntoa (intf->bootServer));
    syslog (LOG_DEBUG, "intf: reqLease: %i", intf->reqLease);

    i = ~(INTFINFO_HAS_IP | INTFINFO_HAS_NETMASK | INTFINFO_HAS_NETWORK |
	  INTFINFO_HAS_BROADCAST);
    intf->set &= i;

    if (strlen(breq->bootfile)) {
	intf->bootFile = strdup(breq->bootfile);
	intf->set |= INTFINFO_HAS_BOOTFILE;
    } else {
	intf->set &= ~INTFINFO_HAS_BOOTFILE;
    }
    syslog (LOG_DEBUG, "intf: bootFile: %s", intf->bootFile);

    memcpy(&intf->ip, &breq->yiaddr, 4);
    intf->set |= INTFINFO_HAS_IP;
    syslog (LOG_DEBUG, "intf: ip: %s", inet_ntoa (intf->ip));

    chptr = breq->vendor;
    chptr += 4;
    while (*chptr != 0xFF && (void *) chptr < (void *) breq->vendor + DHCP_VENDOR_LENGTH) {
	option = *chptr++;
	if (!option) continue;
	length = *chptr++;

	switch (option) {
	    case BOOTP_OPTION_DNS:
		intf->numDns = 0;
		for (i = 0; i < length; i += 4) {
		    if (intf->numDns < MAX_DNS_SERVERS) {
			memcpy(&intf->dnsServers[intf->numDns++], chptr + i, 4);
			syslog(LOG_DEBUG, "intf: dnsServers[%i]: %s", 
			       i/4, inet_ntoa (intf->dnsServers[i/4]));
		    }
		}
		intf->set |= NETINFO_HAS_DNS;
		syslog (LOG_DEBUG, "intf: numDns: %i", intf->numDns);
		break;

	    case BOOTP_OPTION_NETMASK:
		memcpy(&intf->netmask, chptr, 4);
		intf->set |= INTFINFO_HAS_NETMASK;
		syslog (LOG_DEBUG, "intf: netmask: %s", inet_ntoa (intf->netmask));
		break;

	    case BOOTP_OPTION_DOMAIN:
		if ((intf->domain = malloc(length + 1))) {
		    memcpy(intf->domain, chptr, length);
		    intf->domain[length] = '\0';
		    intf->set |= NETINFO_HAS_DOMAIN;
		    syslog (LOG_DEBUG, "intf: domain: %s", intf->domain);
		}
		break;

	    case BOOTP_OPTION_BROADCAST:
		memcpy(&intf->broadcast, chptr, 4);
		intf->set |= INTFINFO_HAS_BROADCAST;
		syslog (LOG_DEBUG, "intf: broadcast: %s", inet_ntoa (intf->broadcast));
		break;

	    case BOOTP_OPTION_GATEWAY:
		memcpy(&intf->gateway, chptr, 4);
		intf->set |= NETINFO_HAS_GATEWAY;
		syslog (LOG_DEBUG, "intf: gateway: %s", inet_ntoa (intf->gateway));
		break;

	    case BOOTP_OPTION_HOSTNAME:
		if ((intf->hostname = malloc(length + 1))) {
		    memcpy(intf->hostname, chptr, length);
		    intf->hostname[length] = '\0';
		    intf->set |= NETINFO_HAS_HOSTNAME;
		    syslog (LOG_DEBUG, "intf: hostname: %s", intf->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);
    } 
 
    if (err)
        sprintf(err, "%s: %s", msg, strerror(errno));
    else
	err = "out of memory!";

    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) {
#ifdef NOTHURD
    struct ifreq req;
#endif
    int i;

    memset(breq, 0, sizeof(*breq));

    breq->opcode = BOOTP_OPCODE_REQUEST;

#ifdef NOTHURD
    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);
#else
    breq->hw = 1; 		/* ethernet */
    breq->hwlength = IFHWADDRLEN;	
    memcpy(breq->hwaddr, ethernet_address, IFHWADDRLEN);
#endif

    /* 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 unsigned int verifyChecksum(void * buf, int length, void * buf2,
				   int length2) {
    unsigned int csum;
    unsigned short * sp;

    csum = 0;
    for (sp = (unsigned short *) buf; length > 0; (length -= 2), sp++)
	csum += *sp;

    /* this matches rfc 1071, but not Steven's */
    if (length)
	csum += *((unsigned char *) sp);

    for (sp = (unsigned short *) buf2; length2 > 0; (length2 -= 2), sp++)
	csum += *sp;

    /* this matches rfc 1071, but not Steven's */
    if (length)
	csum += *((unsigned char *) sp);

    while (csum >> 16)
	csum = (csum & 0xffff) + (csum >> 16);

    if (csum!=0x0000 && csum != 0xffff) return 0; else return 1;
}

void debugbootpRequest(char *name, struct bootpRequest *breq)  {
    char vendor[28], vendor2[28];
    int i;
    struct in_addr address;
    unsigned char *vndptr;
    unsigned char option, length;
    
    syslog (LOG_DEBUG, "%s: opcode: %i", name, breq->opcode);
    syslog (LOG_DEBUG, "%s: hw: %i", name, breq->hw);
    syslog (LOG_DEBUG, "%s: hwlength: %i", name, breq->hwlength);
    syslog (LOG_DEBUG, "%s: hopcount: %i", name, breq->hopcount);
    syslog (LOG_DEBUG, "%s: id: 0x%8x", name, breq->id);
    syslog (LOG_DEBUG, "%s: secs: %i", name, breq->secs);
    syslog (LOG_DEBUG, "%s: flags: 0x%4x", name, breq->flags);
    
    address.s_addr = breq->ciaddr;
    syslog (LOG_DEBUG, "%s: ciaddr: %s", name, inet_ntoa (address));
    
    address.s_addr = breq->yiaddr;
    syslog (LOG_DEBUG, "%s: yiaddr: %s", name, inet_ntoa (address));
    
    address.s_addr = breq->server_ip;
    syslog (LOG_DEBUG, "%s: server_ip: %s", name, inet_ntoa (address));
    
    address.s_addr = breq->bootp_gw_ip;
    syslog (LOG_DEBUG, "%s: bootp_gw_ip: %s", name, inet_ntoa (address));
    
    syslog (LOG_DEBUG, "%s: hwaddr: %s", name, breq->hwaddr);
    syslog (LOG_DEBUG, "%s: servername: %s", name, breq->servername);
    syslog (LOG_DEBUG, "%s: bootfile: %s", name, breq->bootfile);
    
    vndptr = breq->vendor;
    sprintf (vendor, "0x%2x 0x%2x 0x%2x 0x%2x", *vndptr++, *vndptr++, *vndptr++, *vndptr++);
    syslog (LOG_DEBUG, "%s: vendor: %s", name, vendor);
    
    
    for (; (void *) vndptr < (void *) breq->vendor + DHCP_VENDOR_LENGTH;)
      {
	option = *vndptr++;
	if (option == 0xFF)
	  {
	    sprintf (vendor, "0x%2x", option);
	    vndptr = breq->vendor + DHCP_VENDOR_LENGTH;
	  }
	else if (option == 0x00)
	  {
	    for (i = 1; *vndptr == 0x00; i++, vndptr++);
	    sprintf (vendor, "0x%2x x %i", option, i);
	  }
	else
	  {
	    length = *vndptr++;
	    sprintf (vendor, "%3u %3u", option, length);
	    for (i = 0; i < length; i++)
	      {
		if (strlen (vendor) > 22)
		  {
		    syslog (LOG_DEBUG, "%s: vendor: %s", name, vendor);
		    strcpy (vendor, "++++++");
		  }
		snprintf (vendor2, 27, "%s 0x%2x", vendor, *vndptr++);
		strcpy (vendor, vendor2);
		
	      }
	  }
	
	syslog (LOG_DEBUG, "%s: vendor: %s", name, vendor);
      }

    return;

}

static char * handleTransaction(int s, struct overrideInfo * override, 
				struct bootpRequest * breq,
			        struct bootpRequest * bresp, 
			        struct sockaddr_in * serverAddr,
				struct sockaddr_in * respondant,
				int useBootpPacket, int dhcpResponseType) {
    struct timeval tv;
    fd_set readfs;
    int i, j;
    struct sockaddr_pkt tmpAddress;
    int gotit = 0;
    int tries;
    int nextTimeout = 2;
    time_t timeoutTime;
    int sin;
    int resend = 1;
    struct ethhdr;
    char ethPacket[ETH_FRAME_LEN];
    struct iphdr * ipHdr;
    struct udphdr * udpHdr;
    struct psuedohUdpHeader pHdr;
    time_t start = time(NULL);
    
    debugbootpRequest("breq", breq);

    if (!override) {
	override = alloca(sizeof(*override));
	initOverride(override);
    }

    tries = override->numRetries + 1;

    sin = socket(AF_PACKET, SOCK_DGRAM, ntohs(ETH_P_IP));
    if (sin < 0) {
	return strerror(errno);
    }

    while (!gotit && tries) {
	i = sizeof(*breq);
	if (useBootpPacket)
	    i -= (DHCP_VENDOR_LENGTH - BOOTP_VENDOR_LENGTH);

	if (resend) {
	    if (sendto(s, breq, i, 0, (struct sockaddr *) serverAddr, 
		       sizeof(*serverAddr)) != i) {
		close(sin);
		return perrorstr("sendto");
	    }

	    tries--;
	    nextTimeout *= 2;

	    switch (time(NULL) & 4) {
		case 0:	if (nextTimeout >= 2) nextTimeout--; break;
		case 1:	nextTimeout++; break;
	    }

	    timeoutTime = time(NULL) + nextTimeout;
	    i = override->timeout + start;
	    if (timeoutTime > i) timeoutTime = i;

	    resend = 0;
	}

	if (dhcpResponseType == NORESPONSE) {
	    close(sin);
	    return NULL;
	}

	tv.tv_usec = 0;
 	tv.tv_sec = timeoutTime - time(NULL);
	if (timeoutTime < time(NULL)) {
	    tries = 0;
	    continue;
	}

	FD_ZERO(&readfs);
	FD_SET(sin, &readfs);
	switch ((select(sin + 1, &readfs, NULL, NULL, &tv))) {
	  case 0:
	    resend = 1;
	    break;

	  case 1:
	    i = sizeof(tmpAddress);
	    if ((j = recvfrom(sin, ethPacket, sizeof(ethPacket), 0, 
		     (struct sockaddr *) &tmpAddress, &i)) < 0)
		return perrorstr("recvfrom");

	    /* We need to do some basic sanity checking of the header */
	    if (j < (sizeof(*ipHdr) + sizeof(*udpHdr))) continue;

	    ipHdr = (void *) ethPacket;
	    if (!verifyChecksum(NULL, 0, ipHdr, sizeof(*ipHdr)))
		continue;

	    if (ntohs(ipHdr->tot_len) > j)
		continue;
	    j = ntohs(ipHdr->tot_len);

	    if (ipHdr->protocol != IPPROTO_UDP) continue;

	    udpHdr = (void *) (ethPacket + sizeof(*ipHdr));
	    pHdr.source = ipHdr->saddr;
	    pHdr.dest = ipHdr->daddr;
	    pHdr.zero = 0;
	    pHdr.protocol = ipHdr->protocol;
	    pHdr.len = udpHdr->len;

/*
	    egcs bugs make this problematic

	    if (udpHdr->check && !verifyChecksum(&pHdr, sizeof(pHdr), 
 				udpHdr, j - sizeof(*ipHdr)))
	    continue;
*/

	    if (ntohs(udpHdr->source) != BOOTP_SERVER_PORT)
		continue;
	    if (ntohs(udpHdr->dest) != BOOTP_CLIENT_PORT) 
		continue;
	    /* Go on with this packet; it looks sane */

	  /* Originally copied sizeof (*bresp) - this is a security
	     problem due to a potential underflow of the source
	     buffer.  Also, it trusted that the packet was properly
	     0xFF terminated, which is not true in the case of the
	     DHCP server on Cisco 800 series ISDN router. */

	  memset (bresp, 0xFF, sizeof (*bresp));
	  memcpy (bresp, (char *) udpHdr + sizeof (*udpHdr), j - sizeof (*ipHdr) - sizeof (*udpHdr));

	    /* 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;
	    i = dhcpMessageType(bresp);
	    if (!(i == -1 && useBootpPacket) && (i != dhcpResponseType))
		continue;
	    if (memcmp(bresp->vendor, vendCookie, 4)) continue;
	    /* if (respondant) *respondant = tmpAddress; */
	    gotit = 1;

	    break;

	  default:
	    close(sin);
	    return perrorstr("select");
	}
    }

    if (!gotit) {
	close(sin);
	return _("No DHCP reply received");
    }

    close(sin);

    debugbootpRequest("bresp", bresp);

    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 int createSocket(void) {
    struct sockaddr_in clientAddr;
    int s;

    s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s < 0)
	return -1;

    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))) {
	close(s); 
	return -1;
    }

    return s;
}

static int dhcpRelease(struct intfInfo * intf) {
    struct bootpRequest breq, bresp;
    unsigned char messageType;
    struct sockaddr_in serverAddr;
    char * chptr;
    int s;

    if (!(intf->set & INTFINFO_HAS_LEASE)) {
	disableInterface(intf->device);
	syslog(LOG_INFO, "disabling interface %s", intf->device);

	return 0;
    }

    if ((s = createSocket()) < 0) return 1;

    if ((chptr = prepareRequest(&breq, s, intf->device, time(NULL)))) {
	close(s);
	while (1) {
	    disableInterface(intf->device);
	    return 0;
	}
    }

    messageType = DHCP_TYPE_RELEASE;
    addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType);
    memcpy(&breq.ciaddr, &intf->ip, sizeof(breq.ciaddr));

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(BOOTP_SERVER_PORT);	/* bootp server */
    serverAddr.sin_addr = intf->bootServer;

    if (!handleTransaction(s, NULL, &breq, &bresp, &serverAddr, NULL, 0,
			   NORESPONSE)) {
	disableInterface(intf->device);
	close(s);
	return 0;
    }

    disableInterface(intf->device);
    close(s);

    syslog(LOG_INFO, "disabling interface %s", intf->device);

    return 1;
}
    
/* 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 int dhcpRenew(struct intfInfo * intf) {
    struct bootpRequest breq, bresp;
    unsigned char messageType;
    struct sockaddr_in serverAddr;
    char * chptr;
    int s;
    int i;

    s = createSocket();

    if ((chptr = prepareRequest(&breq, s, intf->device, time(NULL)))) {
	close(s);
	while (1);	/* problem */
    }

    messageType = DHCP_TYPE_REQUEST;
    addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType);
    memcpy(&breq.ciaddr, &intf->ip, sizeof(breq.ciaddr));

    i = htonl(intf->reqLease);
    addVendorCode(&breq, DHCP_OPTION_LEASE, 4, &i);

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(BOOTP_SERVER_PORT);	/* bootp server */
    serverAddr.sin_addr = intf->bootServer;

    if (handleTransaction(s, NULL, &breq, &bresp, &serverAddr, NULL, 0,
			   DHCP_TYPE_ACK)) {
	close(s);
	return 1;
    }

    parseLease(&bresp, intf);

    syslog(LOG_INFO, "renewed lease for interface %s", intf->device);

    close(s);
    return 0;
}

static void parseLease(struct bootpRequest * bresp, struct intfInfo * intf) {
    int lease;
    time_t now;

    intf->set &= INTFINFO_HAS_LEASE;
    if (getVendorCode(bresp, DHCP_OPTION_LEASE, &lease)) 
	return;

    lease = ntohl(lease);

    if (lease && lease != 0xffffffff) {
	now = time(NULL);
	intf->set |= INTFINFO_HAS_LEASE;
	intf->leaseExpiration = now + lease;
	intf->renewAt = now + (7 * lease / 8);
    }
}

char * readSearchPath(void) {
    int fd;
    struct stat sb;
    char * buf;
    char * start;

    fd = open("/etc/resolv.conf", O_RDONLY);
    if (fd < 0) return NULL;

    fstat(fd, &sb);
    buf = alloca(sb.st_size + 2);
    if (read(fd, buf, sb.st_size) != sb.st_size) return NULL;
    buf[sb.st_size] = '\n';
    buf[sb.st_size + 1] = '\0';
    close(fd);

    start = buf;
    while (start && *start) {
	while (isspace(*start) && (*start != '\n')) start++;
	if (*start == '\n') {
	    start++;
	    continue;
	}

	if (!strncmp("search", start, 6) && isspace(start[6])) {
	    start += 6;
	    while (isspace(*start) && *start != '\n') start++;
	    if (*start == '\n') return NULL;

	    buf = strchr(start, '\n');
	    *buf = '\0';
	    return strdup(start);
	}
    }

    return NULL;
}

static void createResolvConf(struct intfInfo * intf, char * domain,
			     int isSearchPath) {
    FILE * f;
    int i;
    char * chptr;

    /* force a reread of /etc/resolv.conf if we need it again */
    res_close();

    if (!domain) {
	domain = readSearchPath();
 	if (domain) {
	    chptr = alloca(strlen(domain) + 1);
	    strcpy(chptr, domain);
	    free(domain);
	    domain = chptr;
	    isSearchPath = 1;
	}
    }

    f = fopen("/etc/resolv.conf", "w");
    if (!f) {
	syslog(LOG_ERR, "cannot create /etc/resolv.conf: %s\n",
	       strerror(errno));
	return;
    }

    if (domain && isSearchPath) {
	fprintf(f, "search %s\n", domain);
    } else if (domain) {
	fprintf(f, "search");
	chptr = domain;
	do {
	    fprintf(f, " %s", chptr);
	    chptr = strchr(chptr, '.');
	    if (chptr) {
		chptr++;
		if (!strchr(chptr, '.'))
		    chptr = NULL;
	    }
	} while (chptr);

	fprintf(f, "\n");
    }

    for (i = 0; i < intf->numDns; i++)
	fprintf(f, "nameserver %s\n", inet_ntoa(intf->dnsServers[i]));

    fclose(f);

    /* force a reread of /etc/resolv.conf */
    endhostent();
}

void setupDns(struct intfInfo * intf, struct overrideInfo * override) {
    char * hn, * dn = NULL;
    struct hostent * he;

    if (override->flags & OVERRIDE_FLAG_NODNS) {
	return;
    }

    if (override->searchPath) {
	createResolvConf(intf, override->searchPath, 1);
	return;
    }

    if (intf->set & NETINFO_HAS_DNS) {
	if (!(intf->set & NETINFO_HAS_DOMAIN))  {
	    if (intf->set & NETINFO_HAS_HOSTNAME) {
		hn = intf->hostname;
	    } else {
		createResolvConf(intf, NULL, 0);

		he = gethostbyaddr((char *) &intf->ip, sizeof(intf->ip),
				   AF_INET);
		if (he) {
		    hn = he->h_name;
		} else {
		    hn = NULL;
		}
	    }

	    if (hn) {
		dn = strchr(hn, '.');
		if (dn)
		    dn++;
	    }
	} else {
	    dn = intf->domain;
	}

	createResolvConf(intf, dn, 0);
    }
}

static char * doDhcp(char * device, int flags, int reqLease,
		     char * reqHostname, struct intfInfo * intf,
		     struct overrideInfo * override) {
    int s, i;
    struct sockaddr_in serverAddr;
    struct sockaddr_in clientAddr;
    struct sockaddr_in broadcastAddr;
    struct bootpRequest breq, bresp;
    struct bootpRequest protoReq;
    unsigned char * chptr;
    unsigned char messageType;
    time_t startTime = time(NULL);
    int true = 1;
    char optionsRequested[50];
    int numOptions;
    short aShort;

    memset(intf, 0, sizeof(*intf));
    strcpy(intf->device, device);
    intf->reqLease = reqLease;
    intf->set |= INTFINFO_HAS_REQLEASE;

    s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s < 0) {
	return perrorstr("socket");
    }

    if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &true, sizeof(true))) {
	close(s);
	return perrorstr("setsockopt");
    }

    if (flags & DHCP_FLAG_NOCONFIG) {
	if ((chptr = getInterfaceInfo(intf, s))) {
	    close(s);
 	    return chptr;
	}
    } else if ((chptr = prepareInterface(intf, s))) {
	close(s);
	return chptr;
    }

    if ((chptr = prepareRequest(&breq, s, intf->device, startTime))) {
	close(s);
	disableInterface(intf->device);
	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))) {
	disableInterface(intf->device);
	close(s);
	return perrorstr("bind");
    }

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(BOOTP_SERVER_PORT);	/* bootp server */

#if 0
    /* seems like a good idea?? */
    if (intf->set & INTFINFO_HAS_BOOTSERVER)
	serverAddr.sin_addr = intf->bootServer;
#endif 

    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_port = htons(BOOTP_SERVER_PORT);

#if 0
    /* this too! */
    if (intf->set & INTFINFO_HAS_BROADCAST)
	broadcastAddr.sin_addr = intf->broadcast;
#endif

    memset(&broadcastAddr.sin_addr, 0xff, 
	   sizeof(broadcastAddr.sin_addr));  /* all 1's broadcast */

    syslog (LOG_DEBUG, "PUMP: sending discover\n");

    if ((chptr = handleTransaction(s, override, &breq, &bresp, &broadcastAddr,
				   NULL, 1, DHCP_TYPE_OFFER))) {
	close(s);
	disableInterface(intf->device);
	return chptr;
    }

    /* Otherwise we're in the land of bootp */
    if (dhcpMessageType(&bresp) == DHCP_TYPE_OFFER) {
	/* Admittedly, this seems a bit odd. If we find a dhcp server, we
	   rerun the dhcp discover broadcast, but with the proper option
	   field this time. This makes me rfc compliant. */
	syslog (LOG_DEBUG, "got dhcp offer\n");

	initVendorCodes(&breq);

	aShort = ntohs(sizeof(struct bootpRequest));
	addVendorCode(&breq, DHCP_OPTION_MAXSIZE, 2, &aShort);

	numOptions = 0;
	optionsRequested[numOptions++] = BOOTP_OPTION_NETMASK;
	optionsRequested[numOptions++] = BOOTP_OPTION_GATEWAY;
	optionsRequested[numOptions++] = BOOTP_OPTION_DNS;
	optionsRequested[numOptions++] = BOOTP_OPTION_DOMAIN;
	optionsRequested[numOptions++] = BOOTP_OPTION_BROADCAST;
	optionsRequested[numOptions++] = BOOTP_OPTION_HOSTNAME;
	addVendorCode(&breq, DHCP_OPTION_OPTIONREQ, numOptions, 
		      optionsRequested);

	if (reqHostname) {
	    syslog(LOG_DEBUG, "HOSTNAME: requesting %s\n", reqHostname);
	    addVendorCode(&breq, BOOTP_OPTION_HOSTNAME, strlen(reqHostname), 
			  reqHostname);
	}

	i = htonl(intf->reqLease);
	addVendorCode(&breq, DHCP_OPTION_LEASE, 4, &i);

	protoReq = breq;

	syslog (LOG_DEBUG, "PUMP: sending second discover");

	messageType = DHCP_TYPE_DISCOVER;
	addVendorCode(&breq, DHCP_OPTION_TYPE, 1, &messageType);

	/* Send another DHCP_REQUEST with the proper option list */
	if ((chptr = handleTransaction(s, override, &breq, &bresp, 
				       &broadcastAddr, NULL, 1, 
				       DHCP_TYPE_OFFER))) {
	    close(s);
	    disableInterface(intf->device);
	    return chptr;
	}


	if (dhcpMessageType(&bresp) != DHCP_TYPE_OFFER) {
	    close(s);
	    disableInterface(intf->device);
	    return "dhcp offer expected";
	}

	syslog (LOG_DEBUG, "PUMP: got an offer");

	if (getVendorCode(&bresp, DHCP_OPTION_SERVER, &serverAddr.sin_addr)) {
	    syslog (LOG_DEBUG, "DHCPOFFER didn't include server address");
	    intf->bootServer = broadcastAddr.sin_addr;
	}

	breq = protoReq;
	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);

	/* why do we need to use the broadcast address here? better reread the
	   spec! */
	if ((chptr = handleTransaction(s, override, &breq, &bresp, 
				       &broadcastAddr, NULL, 0, 
				       DHCP_TYPE_ACK))) {
	    close(s);
	    disableInterface(intf->device);
	    return chptr;
	}

	syslog (LOG_DEBUG, "PUMP: got lease");

	parseLease(&bresp, intf);

	if (getVendorCode(&bresp, DHCP_OPTION_SERVER, &intf->bootServer)) {
	    syslog (LOG_DEBUG, "DHCPACK didn't include server address");
	    intf->bootServer = broadcastAddr.sin_addr;
	}

	intf->set |= INTFINFO_HAS_BOOTSERVER;
    }

    parseReply(&bresp, intf);
    if (flags & DHCP_FLAG_FORCEHNLOOKUP)
	intf->set &= ~(NETINFO_HAS_DOMAIN | NETINFO_HAS_HOSTNAME);

    chptr = setupInterface(intf, s);
    if (chptr) {
	close(s);
	disableInterface(intf->device);
	return chptr;
    }

    syslog(LOG_INFO, "configured interface %s", intf->device);

    if (intf->set & NETINFO_HAS_GATEWAY) {
	chptr = setupDefaultGateway(&intf->gateway, s);
    }

    setupDns(intf, override);

    close(s);

    return NULL;
}

static void runDaemon(int sock, char * configFile) {
    int conn;
    struct sockaddr_un addr;
    int addrLength;
    struct command cmd;
    struct intfInfo intf[20];
    int numInterfaces = 0;
    int i;
    int closest;
    struct timeval tv;
    fd_set fds;
    struct overrideInfo * overrides = NULL;
    struct overrideInfo emptyOverride, * o;

    readPumpConfig(configFile, &overrides);
    if (!overrides) {
	overrides = &emptyOverride;
	overrides->intf.device[0] = '\0';
    }

    while (1) {
	FD_ZERO(&fds);
	FD_SET(sock, &fds);

	tv.tv_sec = tv.tv_usec = 0;
	closest = -1;
	if (numInterfaces) {
	    for (i = 0; i < numInterfaces; i++)
		if ((intf[i].set & INTFINFO_HAS_LEASE) && 
			(closest == -1 || 
			       (intf[closest].renewAt > intf[i].renewAt)))
		    closest = i;
	    if (closest != -1) {
		tv.tv_sec = intf[closest].renewAt - time(NULL);
		if (tv.tv_sec <= 0) {
		    dhcpRenew(intf + closest);
		    continue;
		}
	    }
	}

	if (select(sock + 1, &fds, NULL, NULL, 
		   closest != -1 ? &tv : NULL) > 0) {
	    conn = accept(sock, &addr, &addrLength);

	    if (read(conn, &cmd, sizeof(cmd)) != sizeof(cmd)) {
		close(conn);
		continue;
	    }

	    switch (cmd.type) {
	      case CMD_DIE:
		for (i = 0; i < numInterfaces; i++)
		    dhcpRelease(intf + i);

		syslog(LOG_INFO, "terminating at root's request");

		cmd.type = CMD_RESULT;
		cmd.u.result = 0;
		write(conn, &cmd, sizeof(cmd));
		exit(0);

	      case CMD_STARTIFACE:
		o = overrides; 
		while (*o->intf.device && 
			strcmp(o->intf.device, cmd.u.start.device)) {
		    o++;
		}
		if (!*o->intf.device) o = overrides;

		if (doDhcp(cmd.u.start.device,
			   cmd.u.start.flags, cmd.u.start.reqLease, 
			   cmd.u.start.reqHostname[0] ? 
			       cmd.u.start.reqHostname : NULL,
			   intf + numInterfaces, o)) {
		    cmd.u.result = 1;
		} else {
		    cmd.u.result = 0;
		    numInterfaces++;
		}
		break;

	      case CMD_FORCERENEW:
		for (i = 0; i < numInterfaces; i++)
		    if (!strcmp(intf[i].device, cmd.u.renew.device)) break;
		if (i == numInterfaces)
		    cmd.u.result = RESULT_UNKNOWNIFACE;
		else
		    cmd.u.result = dhcpRenew(intf + i);
		break;

	      case CMD_STOPIFACE:
		for (i = 0; i < numInterfaces; i++)
		    if (!strcmp(intf[i].device, cmd.u.stop.device)) break;
		if (i == numInterfaces)
		    cmd.u.result = RESULT_UNKNOWNIFACE;
		else {
		    cmd.u.result = dhcpRelease(intf + i);
		    if (numInterfaces == 1) {
			cmd.type = CMD_RESULT;
			write(conn, &cmd, sizeof(cmd));

			syslog(LOG_INFO, "terminating as there are no "
				"more devices under management");

			exit(0);
		    }

		    intf[i] = intf[numInterfaces - 1];
		    numInterfaces--;
		}
		break;

	      case CMD_REQSTATUS:
		for (i = 0; i < numInterfaces; i++)
		    if (!strcmp(intf[i].device, cmd.u.stop.device)) break;
		if (i == numInterfaces) {
		    cmd.u.result = RESULT_UNKNOWNIFACE;
		} else {
		    cmd.type = CMD_STATUS;
		    cmd.u.status.intf = intf[i];
		    if (intf[i].set & NETINFO_HAS_HOSTNAME)
			strncpy(cmd.u.status.hostname,
			    intf->hostname, sizeof(cmd.u.status.hostname));
		    cmd.u.status.hostname[sizeof(cmd.u.status.hostname)] = '\0';

		    if (intf[i].set & NETINFO_HAS_DOMAIN)
			strncpy(cmd.u.status.domain,
			    intf->domain, sizeof(cmd.u.status.domain));
		    cmd.u.status.domain[sizeof(cmd.u.status.domain) - 1] = '\0';

		    if (intf[i].set & INTFINFO_HAS_BOOTFILE)
			strncpy(cmd.u.status.bootFile,
			    intf->bootFile, sizeof(cmd.u.status.bootFile));
		    cmd.u.status.bootFile[sizeof(cmd.u.status.bootFile) - 1] = 
		    							'\0';
		}

	      case CMD_STATUS:
	      case CMD_RESULT:
		/* can't happen */
		break;
	    }

	    if (cmd.type != CMD_STATUS) cmd.type = CMD_RESULT;
	    write(conn, &cmd, sizeof(cmd));

	    close(conn);
	}
    }

    exit(0);
}

static int openControlSocket(char * configFile) {
    struct sockaddr_un addr;
    int sock;
    size_t addrLength;
    pid_t child;
    int status;

    if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
	return -1;

    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, CONTROLSOCKET);
    addrLength = sizeof(addr.sun_family) + strlen(addr.sun_path);

    if (!connect(sock, (struct sockaddr *) &addr, addrLength)) 
	return sock;

    if (errno != ENOENT && errno != ECONNREFUSED) {
	fprintf(stderr, "failed to connect to %s: %s\n", CONTROLSOCKET,
		strerror(errno));
	close(sock);
	return -1;
    }

    if (!(child = fork())) {
	close(sock);

	if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
	    fprintf(stderr, "failed to create socket: %s\n", strerror(errno));
	    exit(1);
	}

	unlink(CONTROLSOCKET);
	umask(077);
	if (bind(sock, (struct sockaddr *) &addr, addrLength)) {
	    fprintf(stderr, "bind to %s failed: %s\n", CONTROLSOCKET,
		    strerror(errno));
	    exit(1);
	}
	umask(033);

	listen(sock, 5);

	if (fork()) _exit(0);

	close(0);
	close(1);
	close(2);

	openlog("pumpd", LOG_PID, LOG_DAEMON);
	{
	    time_t t;

	    t = time(NULL);
	    syslog(LOG_INFO, "starting at %s\n", ctime(&t));
	}

	runDaemon(sock, configFile);
    }

    waitpid(child, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status))
	return -1;

    if (!connect(sock, (struct sockaddr *) &addr, addrLength)) 
	return sock;

    fprintf(stderr, "failed to connect to %s: %s\n", CONTROLSOCKET,
	    strerror(errno));

    return 0;
}

void printStatus(struct intfInfo i, char * hostname, char * domain,
		 char * bootFile) {
    int j;

    printf("Device %s\n", i.device);
    printf("\tIP: %s\n", inet_ntoa(i.ip));
    printf("\tNetmask: %s\n", inet_ntoa(i.netmask));
    printf("\tBroadcast: %s\n", inet_ntoa(i.broadcast));
    printf("\tNetwork: %s\n", inet_ntoa(i.network));
    printf("\tBoot server %s\n", inet_ntoa(i.bootServer));

    if (i.set & NETINFO_HAS_GATEWAY)
	printf("\tGateway: %s\n", inet_ntoa(i.gateway));

    if (i.set & INTFINFO_HAS_BOOTFILE)
	printf("\tBoot file: %s\n", bootFile);

    if (i.set & NETINFO_HAS_HOSTNAME)
	printf("\tHostname: %s\n", hostname);

    if (i.set & NETINFO_HAS_DOMAIN)
	printf("\tDomain: %s\n", domain);

    if (i.numDns) {
	printf("\tNameservers:");
	for (j = 0; j < i.numDns; j++)
	    printf(" %s", inet_ntoa(i.dnsServers[j]));
	printf("\n");
    }

    if (i.set & INTFINFO_HAS_LEASE) {
	printf("\tRenewal time: %s", ctime(&i.renewAt)); 
	printf("\tExpiration time: %s", ctime(&i.leaseExpiration)); 
    }
}

int main (int argc, char ** argv) {
    char * device = "eth0";
    char * hostname = "";
    poptContext optCon;
    int rc;
    int test = 0;
    int flags = 0;
    int lease = 6;
    int killDaemon = 0;
    int release = 0, renew = 0, status = 0, lookupHostname = 0;
    struct command cmd, response;
    char * configFile = "/etc/pump.conf";
    struct overrideInfo * overrides;
    int cont;
    struct poptOption options[] = {
	    { "config-file", 'c', POPT_ARG_STRING, &configFile, 0,
			N_("Configuration file to use instead of "
			   "/etc/pump.conf") },
            { "hostname", 'h', POPT_ARG_STRING, &hostname, 0, 
			N_("Hostname to request"), N_("hostname") },
            { "interface", 'i', POPT_ARG_STRING, &device, 0, 
			N_("Interface to configure (normally eth0)"), 
			N_("iface") },
	    { "kill", 'k', POPT_ARG_NONE, &killDaemon, 0,
			N_("Kill daemon (and disable all interfaces)"), NULL },
	    { "lease", 'l', POPT_ARG_INT, &lease, 0,
			N_("Lease time to request (in hours)"), N_("hours") },
	    { "lookup-hostname", '\0', POPT_ARG_NONE, &lookupHostname, 0,
			N_("Force lookup of hostname") },
	    { "release", 'r', POPT_ARG_NONE, &release, 0,
			N_("Release interface"), NULL },
	    { "renew", 'R', POPT_ARG_NONE, &renew, 0,
			N_("Force immediate lease renewal"), NULL },
	    { "status", 's', POPT_ARG_NONE, &status, 0,
			N_("Display interface status"), NULL },
	    /*{ "test", 't', POPT_ARG_NONE, &test, 0,
			N_("Don't change the interface configuration or "
			   "run as a deamon.") },*/
	    POPT_AUTOHELP
	    { NULL, '\0', 0, NULL, 0 }
        };

    optCon = poptGetContext(PROGNAME, argc, argv, options,0);
    poptReadDefaultConfig(optCon, 1);

    if ((rc = poptGetNextOpt(optCon)) < -1) {
	fprintf(stderr, _("%s: bad argument %s: %s\n"), PROGNAME,
		poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
		poptStrerror(rc));
	return 1;
    }

    if (poptGetArg(optCon)) {
	fprintf(stderr, _("%s: no extra parameters are expected\n"), PROGNAME);
	return 1;
    }

    /* make sure the config file is parseable before going on any further */
    if (readPumpConfig(configFile, &overrides)) return 1;

    if (geteuid()) {
	fprintf(stderr, _("%s: must be run as root\n"), PROGNAME);
	exit(1);
    }

    if (test)
	flags = DHCP_FLAG_NODAEMON | DHCP_FLAG_NOCONFIG;
    if (lookupHostname)
	flags |= DHCP_FLAG_FORCEHNLOOKUP;

    cont = openControlSocket(configFile);
    if (cont < 0) 
	exit(1);

    if (killDaemon) {
	cmd.type = CMD_DIE;
    } else if (status) {
	cmd.type = CMD_REQSTATUS;
	strcpy(cmd.u.reqstatus.device, device);
    } else if (renew) {
	cmd.type = CMD_FORCERENEW;
	strcpy(cmd.u.renew.device, device);
    } else if (release) {
	cmd.type = CMD_STOPIFACE;
	strcpy(cmd.u.stop.device, device);
    } else {
	cmd.type = CMD_STARTIFACE;
	strcpy(cmd.u.start.device, device);
	cmd.u.start.flags = flags;
	cmd.u.start.reqLease = lease * 60 * 60;
	strcpy(cmd.u.start.reqHostname, hostname);
    }

    write(cont, &cmd, sizeof(cmd));
    read(cont, &response, sizeof(response));

    if (response.type == CMD_RESULT && response.u.result &&
	    cmd.type == CMD_STARTIFACE) {
	cont = openControlSocket(configFile);
	if (cont < 0) 
	    exit(1);
	write(cont, &cmd, sizeof(cmd));
	read(cont, &response, sizeof(response));
    }

    if (response.type == CMD_RESULT) {
	if (response.u.result) {
	    fprintf(stderr, "Operation failed.\n");
	    return 1;
	}
    } else if (response.type == CMD_STATUS) {
	printStatus(response.u.status.intf, response.u.status.hostname, 
		    response.u.status.domain, response.u.status.bootFile);
    }

    return 0;
}