/* $Header: /usr/local/src/dhcpd/RCS/dhcp.c,v 1.8 2008/10/23 21:47:21 root Exp $ */

/***********************************************************
        Copyright 1996 by Carnegie Mellon University

                      All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of CMU not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.

ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
******************************************************************/

#include "defs.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/route.h>
#include <netdb.h>
#include <errno.h>
#include <arpa/inet.h>  /* inet_ntoa */


#include "defaults.h"
#include "bootp.h"
#include "bootpd.h"
#include "dhcpd.h"
#include "dhcp.h"
#include "dynamic.h"
#include "readfile.h"
#include "perm.h"
#include "getif.h"
#include "hwaddr.h"
#include "utils.h"
#include "my_syslog.h"


extern u_short bootpc_port; /* host byte order */
extern u_short bootps_port; /* host byte order */
extern int debug,s,pktlen;
extern boolean compact_messages;
extern boolean ignore_cliid_option;
extern boolean allow_serveripopts_in_rebind;
hash_tbl *boundhshtbl;
hash_tbl *ipboundhshtbl;

hash_tbl *known_serverids_hshtbl; /* known_server_identifier values from dhcpd.conf, if any */
int known_serverids_hshtbl_inuse; /* known_serverids_hshtbl has been populated */


struct unknownhost *unknown;
unsigned int num_unknown;

extern struct in_addr my_ip_addr;
static int i;
static unsigned int ui;
int init=0;
unsigned long dhcp_alarm;
unsigned int leases;
char buf[MAXCLIIDLEN];
static unsigned char send_buf[2000];
static struct bootp *rpkt;
u_char htype_ethernet;

extern struct sockaddr_in recv_addr;
void send_dhcp_reply __P((struct bootp *pkt, int32 send_len, unsigned int32 dest, unsigned int delay_usec));
void send_dhcpnack __P((struct bootp *pkt, struct shared_bindata *cliid, char *msg, unsigned int max_len, boolean cliid_explicit));
void order_insert();
struct host *create_stub __P((struct host* hp, struct client_info *cl));
struct host *create_lease __P((struct host* hp, struct shared_bindata *cliid,
				struct shared_bindata *vend, 
				struct shared_bindata *user, unsigned secs));
int do_req_opts __P((struct host *hp, u_char *req_opts, u_char *bp, struct opts *cur, struct flag *sent_options));
int do_opts __P((struct host *hp, u_char *bp, struct opts *cur, struct flag *sent_options));

struct host *hp;
struct shared_bindata *cliid,*vend,*user;
unsigned secs;
unsigned int discovers_received,offers_sent;
unsigned int requests_received,acks_sent,nacks_sent;
unsigned int ips_expired,ips_reclaimed,declines_received;
unsigned int releases_received;
unsigned int informs_received;
unsigned long gcinterval;
unsigned int bootp_requests_received,bootp_replys_sent;
unsigned long expire_infinite_dynamics;
unsigned int delay_reply_dhcpoffer = 0;
unsigned int delay_reply_dhcpack = 0;
unsigned int delay_reply_dhcpnak = 0;
unsigned int delay_reply_bootpreply = 0;
unsigned int delay_reply_default = 0;
unsigned long readtabinterval = 0;
unsigned long readconfinterval = 0;

unsigned long now,starttime;
struct in_addr my_ip_addr;
int pktlen;
void report_cliid();
char * cliid_btoa();
extern char * hostname;

/* like ctime(), but takes a time_t instead of a time_t *.
   Don't call twice in same expr! */
char *cctime(at)
unsigned long at;
{
 char *r;

 r=ctime((time_t*)&at);
 r[strlen(r)-1]=0;
 return r;
}

void init_dhcp()
{
 struct rtentry rt;
 struct sockaddr_in addr;
 struct lastbind *binding;
 struct client_info cl;
 hash_datum *data;
 int len,i,rc;
 unsigned hashcode, hashDel_hashcode;
 struct host *hp, *hp2;
 struct in_addr tmpaddr, hashDel_iaddr;
 char flag;
 boolean force_garbage_collect=FALSE;
 char str1[2*MAXSTRINGLEN], str2[MAXSTRINGLEN];


 /* We will read the lastbindings data from disk, using it to populate our current bindings,
    and our in-memory version of lastbindings.
 */

 lastbindhshtbl=hash_Init(HASHTABLESIZE);
 boundhshtbl=hash_Init(HASHTABLESIZE);
 ipboundhshtbl=hash_Init(HASHTABLESIZE);
 cache2disk=hash_Init(HASHTABLESIZE);

 if(debug)
	report(LOG_INFO,"using last binds dir %s",bindingsdir);
 if((lastbindpermanent=hashf_Init(bindingsdir,HASHTABLESIZE)) == NULL){
	report(LOG_ERR,"Could not open last binds dir : %s",LASTBINDDIR);
	exit(1);
 }

 hash_Reset(boundhshtbl,free_host);
 hash_Reset(ipboundhshtbl,free_host);
 hash_Reset(lastbindhshtbl,free_lastbind);
 hash_Reset(cache2disk,free_lastbind);

 i=0; /* items inserted into lastbindhshtbl */
 leases=0;	 /* items inserted into the bound hash tables */
 unknown=NULL;
 now=time(0);
 tmpaddr.s_addr=0xffffffff;
 if (debug > 11) {
	report(LOG_INFO, " Entries in bindings directory follow...");
	report(LOG_INFO, " Each will be added to lastbindings structure.");
	report(LOG_INFO, " Unexpired/infinite dhcp leases will be reinstated as current bindings.");
	report(LOG_INFO, " BootP assignments for dynamic addresses will be reinstated as current bindings,");
	if (expire_infinite_dynamics)
		report(LOG_INFO, "    (but only if their last_touch is more recent than expire_infinite_dynamics (%d) seconds)", 
			expire_infinite_dynamics);
	else 
		report(LOG_INFO, "    regardless of their last_touch age (expire_infinite_dynamics is not set)");
 }

 /* walk lastbindings data read from disk */
 for(data=hashf_FirstEntry(lastbindpermanent,&len);data != NULL;
			data=hashf_NextEntry(lastbindpermanent,&len)){

	binding=data2bind(data,len); /* frees data on success */
	if(!binding){
		report(LOG_ERR,"  data2bind() returned NULL, corrupt binding entry found on disk, skipping this entry");
		if (data)
			free(data);
		continue;
		}
	binding->linkcount++;

	if(debug>11){
		report_cliid(LOG_INFO,binding->cliid);
		/* must build message up, since cctime() shouldn't be called twice in same expr */
		sprintf(str1, "    IP=%s, expires=%s, code=%s, replies=%d, ", 
			inet_ntoa(binding->last_ip), 
			((binding->expire == INFINITY) ? "never" : cctime(binding->expire)),
			((binding->code < 6) ? codename[binding->code] : "UNKNOWN"),
			binding->replys);
		sprintf(str2, "last touched at %s", cctime(binding->last_touch));
		strcat(str1,str2);
		report(LOG_INFO, str1);

	}

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    init_dhcp(): will insert into lastbindhshtbl...");
#endif
	rc=hash_Insert(lastbindhshtbl, hash_HashFunction(binding->cliid->data,binding->cliid->length), lastbindcmp,binding,binding);
	if (rc) 
		report(LOG_ERR, "    init_dhcp(): error inserting binding in lastbindhshtbl!");

	i++;

	/* look up IP address in bootptab */
	hashcode=hash_HashFunction((u_char*)&(binding->last_ip.s_addr),4);
	hp=(struct host *)hash_Lookup(iphashtable,hashcode,ipcmp, &(binding->last_ip.s_addr));
	if(!hp) {
		/* You'd think we should log this when debug>3, to match 
		   the debug level at which we log invalidating current leases.  Failing to do so
		   means that for some debug levels, when the lease is invalidated in some ways (e.g. 
		   while we are running) the invalidation will be logged, while if the lease is invalidated
		   here (as we are starting up) it will not be logged.
		   But we can't just go ahead and log it here and the same debug level, because at this
		   point, we don't know if the IP address we are looking at in lastbindings had already
		   expired.  If it had already expired, we certainly do NOT want to log it here at a low debug level.
		   While we could fix that by checking to see if the lease has expired here (as we do later) and
		   using that to determine what we log, we don't bother because there's a second, more serious
		   problem with logging this stuff.  That that if we log each lease that has expired when debug>3,
		   we'll repeatedly log the same old leases every time we start (e.g. for clients not seen since 
		   the lease expired).    That's likely to be a lot of useless information.
		*/
		if (debug > 11)
			report(LOG_INFO,"    IP address no longer in bootptab, will not create lease");
		continue;
	}

	if (binding->expire != INFINITY) { /* DHCP assignment, OR BootP dynamic that already expired due to expire_infinite_dynamics */
		if (now >= binding->expire) {
			if ((binding->code == DHCP_FLAG) || (binding->code==RESERVED_FLAG)) {
				if (debug > 11)
					report(LOG_INFO, "    Was for DHCP, but has expired; will not create lease");
				continue;
			} else if (binding->code == BOOTP_FLAG) { 
				if (debug > 11)
					report(LOG_INFO, "    Was for BootP dynamic, but previously expired due to expire_infinite_dynamics; will not create lease");
				continue;
			}
		} else { /* now < binding->expire */
			if (debug > 11) 
				report(LOG_INFO, "    Was for DHCP and has not expired; will create lease"); 
		}

	} else { /*BOOTP assignment */
		if (binding->code == RESERVED_FLAG) { /* static */
			if (debug > 11)
				report(LOG_INFO, "    Was for BOOTP static assignment, will not create lease");
			continue;
		} else if (binding->code == BOOTP_FLAG) { /* dynamic */
			if (expire_infinite_dynamics) {
				if (now >= binding->last_touch + expire_infinite_dynamics) {
					/* It was for a BOOTP dynamic, older than expire_infinite_dynamics.  So we don't want to create a lease.
					   Normally, garbage collection would have found this, and set expire to 'then', expired the lease,
					   and updated lastbinding.  But if the server went down between the time this should have happened
					   and the next garbage collection, or the server's been down for some time, or the expire_infinite_dynamics
					   has only recently been set (or reduced), we'll find entries like this in the lastbind directory.
					   We want to clean them up, by setting their expire value to 'now', so we won't have these in this
					   state forever.  But it is not safe to just do that now, since that could cause a write to this
					   directory, which we are in the middle of reading; we don't have any sort of locking.  So the 
					   solution is a kludge; we'll go ahead and let the binding be created, and allow regular garbage collection
					   to clean it up.  We'll speed that up by forcing garbage collection early. */
					if (debug > 11) {
						report(LOG_INFO, "    Was for BOOTP dynamic, but older than expire_infinite_dynamics...");
						report(LOG_INFO, "     don't want lease, but will create one to allow garbage_collect to clean up expire time");
					}
					force_garbage_collect=TRUE;
				} else {
					if (debug > 11)
						report(LOG_INFO, "    Was for BOOTP dynamic and newer than expire_infinite_dynamics; will create lease");
				}
			} else {
				if (debug > 11)
					report(LOG_INFO, "    Was for BOOTP dynamic, will create lease");
			}
		} else {
			report(LOG_ERR, "    init_dhcp(): invalid binding->code");
			continue;
		}
	} /* DHCP or BOOTP assignment */


	bzero(&cl,sizeof(cl));
	cl.cliid=binding->cliid;
	cl.vend=binding->vend;
	cl.user=binding->user;
	cl.clhashcode=hash_HashFunction(binding->cliid->data,binding->cliid->length);

	/* look for clientid in bootptab (it might not be there) */
	hp2 =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);
	/* if not found, should we look again using haddr as key?  But how, since binding only
	   records cliid, not haddr. */

	if (hp2 && hp2->flags.deny_client) {
		/* As above, we do NOT log this when debug>3 (matching the messages logged when we invalidate a lease),
		   because if we did so, we'd keep logging the same message each time we started for these lastbindings
		   records, until the old lease finally expires.
		*/
		if (debug > 11)
			report(LOG_INFO,"     client is explicitly denied service; will not create lease");
		free_host(hp);
		continue; /* walk lastbindpermanent */
	}

	/* create a struct host, which we *may* use later; if we choose not to, we'll free it */
	if (!(hp=create_stub(hp,&cl))) {
		report(LOG_ERR,"    create_stub() returned NULL; Unable to create stub for client, skipping!");
		continue; /* walk lastbindpermanent */
	}
	if(!create_lease(hp,cl.cliid,cl.vend,cl.user,0)){
		hp->linkcount=1;  /* to force free_host() to free it, as it starts =0 */
		free_host(hp);
		report(LOG_ERR,"    create_lease() returned NULL; Unable to create lease for client, skipping!");
		continue; /* walk lastbindpermanent */
	}

	hp->linkcount=1;	/* it started =0 */

	hp->expire=binding->expire;
	if (hp->expire != INFINITY)
		hp->flags.expire=1;	/* else = 0 */

	hp->code=binding->code;

	/* if IP address is dynamic, make sure access policy still allows this client. */
	if (hp->flags.dynamic) {

		/* build 'flags' to indicate the protocol and privileges associated with the client */
		if (binding->code==DHCP_FLAG || binding->code==RESERVED_FLAG)
			flag = DHCP_ACCESS;
		else if (binding->code==BOOTP_FLAG)
			flag = BOOTP_ACCESS;
		else {
			report(LOG_ERR, "   init_dhcp(): invalid binding->code");
			free_host(hp);
			continue; /* walk lastbindpermanent */
		}
		/* look for clientid in bootptab */
		hp2 =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);
		/* if not found, should we look again using haddr as key?  But how, since binding only
		   records cliid, not haddr. */
		if (hp2 && hp2->flags.deny_client_dynamic) {
			/* As above, we do NOT log this when debug>3 (matching the messages logged when we invalidate a lease),
			   because if we did so, we'd keep logging the same message each time we started for these lastbindings
			   records, until the old lease finally expires.
			*/
			if (debug > 11)
				report(LOG_INFO,"     address is dynamic, but client is explicitly denied use of dynamic addresses; will not create lease");
			free_host(hp);
			continue; /* walk lastbindpermanent */
		}
		if (hp2) {
			flag |=  REG_ACCESS;
			if (hp2->flags.roaming)
				flag |= ROAMING_ACCESS;
		} else {
			flag |=  UNREG_ACCESS;
		}

		/* here's where we actually check that the client satisfies the access policy.
		   We are calling use_addr() instead of access_network() for two reasons.  First,
		   acces_network() requires that we pass the network's restriction code; we could 
		   look that up, but use_addr() will do that for us before it calls access_network().
		   Second, when use_addr() finds that the client meets the access policies, it will
		   take care of removing the IP address from the pool of available dynamics.    
		   Note that because use_addr() normally checks the client's IP address against a giaddr,
		   and we don't care about that here, we tell it to skip that test by calling with force=1. */
		if (!use_addr(hp, tmpaddr, flag, 1)){
			/* As above, we do NOT log this when debug>3 (matching the messages logged when we invalidate a lease),
			   because if we did so, we'd keep logging the same message each time we started for these lastbindings
			   records, until the old lease finally expires.
			*/
			if (debug > 11)
				report(LOG_INFO,"     address is dynamic, but client (now) denied by access policy; will not create lease");
			free_host(hp);
			continue; /* walk lastbindpermanent */
		}
	}

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    Will insert into ipboundhshtbl...");
#endif
	if(hash_Insert(ipboundhshtbl,hashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr),(hash_datum *)hp)){
		report(LOG_ERR,"    Found unexpired lease, but insert into ipboundhshtbl failed (cliid=%s)", cliid_btoa(hp->cl));
		report(LOG_ERR,"    Probably duplicate entry for %s (bindings database corrupt)",inet_ntoa(hp->iaddr));
		report(LOG_ERR,"    could not reinstate lease");
		free_host(hp); /* since hp->linkcount == 1, this will truly free it */
		continue; /* walk lastbindpermanent */
	}
	/* note that hp->linkcount still == 1, now reflecting reality (it is in ipboundhshtbl) */

	hp->linkcount++; /* to reflect the insert we're about to perform */
#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    Will insert into boundhshtbl...");
#endif
	rc=hash_Insert(boundhshtbl,cl.clhashcode,boundcliidcmp,hp->cl,hp);
	if (rc)  {
		report(LOG_ERR, "    error inserting binding into boundhshtbl (cliid=%s)", cliid_btoa(hp->cl));
		report(LOG_ERR, "    could not reinstate lease for %s", inet_ntoa(hp->iaddr));
		report(LOG_ERR, "    will delete binding just added to ipboundhshtbl (ip=%s)", inet_ntoa(hp->iaddr));
		free_host(hp); /* doesn't really free it since linkcount == 2 */
		/* We already inserted it into ipboundhshtbl.  Since we failed to insert it in boundhshtbl, let's
		   remove it from ipboundhshtbl to keep the two in sync. */
		/* Make standalone copy of hashcode and key to pass to hash_Delete(), since hash_Delete() may free hp (thereby
		   freeing hp->iphashcode and hp->iaddr), while hash_Delete() still needs to access those to continue walking the
		   hashtable to find any more members that match. */
		hashDel_hashcode = hp->iphashcode;
		hashDel_iaddr.s_addr = hp->iaddr.s_addr;
		if (hash_Delete(ipboundhshtbl, hashDel_hashcode, ipcmp, (hash_datum *)&(hashDel_iaddr.s_addr), free_host)) {
			report(LOG_ERR, "    error deleting binding from ipboundhshtbl (ip=%s)", inet_ntoa(hp->iaddr));
			/* at this point, things are probably seriously screwed up */
		}
		hp->linkcount--;	/* to 1 */
		free_host(hp);	/* really frees it */
		continue;
	} else {
		if(debug>11)
			report(LOG_INFO,"    Reinstated lease for %s, expires %s",
				inet_ntoa(hp->iaddr),
				(binding->expire==INFINITY) ? "never" : cctime(binding->expire));
		leases++;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "    incremented leases counter to %d", leases);
#endif
	}
 } /* walk lastbindpermanent */

 if(lastbindpermanent->next){
	fclose(lastbindpermanent->next);
	lastbindpermanent->next=NULL;
 }
 if(debug)
 	report(LOG_INFO,"%d lastbindings (%d current bindings) were retrieved from disk",i, leases);
 if(max_cachequeue==0)
 	max_cachequeue=20;
 if(flush_interval==0)
 	flush_interval=30*60L;
 if(gcinterval==0)
	gcinterval=30*60L;
 dhcp_alarm=time(0)+gcinterval;
 flush_alarm=time(0)+flush_interval;
 num_unknown=0;
 informs_received=0;
 discovers_received=0;
 requests_received=0;
 releases_received=0;
 declines_received=0;
 ips_reclaimed=0;
 ips_expired=0;
 acks_sent=0;
 nacks_sent=0;
 offers_sent=0;

 if (force_garbage_collect) {
	if (debug > 11)
		report(LOG_INFO, "   Forcing garbage collection to force expiration of BootP dynamics older than expire_infinite_dynamics");
	dhcp_garbage_collect();
 }

}

/* if this is a dhcp packet, return the type otherwise 0
   if the options in this packet are poorly formed, return -1 
 */
char dhcp_msg_type(pkt)
struct bootp *pkt;
{
 u_char *cp;
 unsigned hashcode;


 if(bcmp(pkt->bp_vend,vm_rfc1048,4))
	return 0;
 for(cp=(u_char*)pkt,i=4+((u_long)pkt->bp_vend-(u_long)pkt); i<pktlen ;){
	if(cp[i]==TAG_PAD){
		i++;
		continue;
	}
	if(cp[i]==TAG_END)
			return 0;

	if(cp[i]==TAG_DHCP_MSG){
		i+=2;
		if(i>=pktlen || cp[i]>DHCPINFORM || cp[i]<DHCPDISCOVER)
			return -1;
		return cp[i];
	}
		
	i++;
	if(i==pktlen || ((int)(cp[i]+i)>=pktlen))
		return -1;
	i+=cp[i]+1;
  }
  return 0;
}


char * 
cliid_btoa(cl)
struct shared_bindata *cl;
/* return string representation of cliid 'cl' */
/* note use of static buffer; don't call more than once from single expr */
{
	boolean printable = TRUE; /* indicates the entire cliid is printable characters */
	static char str[MAXCLIIDLEN];
	unsigned int i;

	if (!cl)
		return "";

	/* set 'printable' */
	for (i=0; i<cl->length && printable; i++)
		if (!(ispunct(cl->data[i])||isalnum(cl->data[i])||isspace(cl->data[i])))
		 printable = FALSE;

	if (printable) {
		bcopy(cl->data,str,cl->length);
		str[cl->length]=0;
	} else
		for (i=0, str[0]=0; i<cl->length; i++)
			sprintf(str+strlen(str),"%02X ",cl->data[i]);
		
	return str;
}

void report_cliid(log,cl)
int log;
struct shared_bindata *cl;
/* print the cliid in 'cl' at log level 'log' */
{
	if (!cl)
		report(LOG_ERR, "report_cliid(): cl is NULL");
	else
		report(log, "  Client id: %s len=%d", cliid_btoa(cl), cl->length);
}


/* Read some info from struct bootp *pkt, filling out various fields in struct client_info *cl.
   Return 0 on success, 1 on error.
   This is only called for DHCP packets, not BootP packets.
   May malloc() various structures pointed to by cl.
   Caller is responsible for deep-freeing the entire 'cl' structure (which will take care of these)
   when the caller is done with this packet.  (This is true even if we return error.)
*/
int 
dhcp_opts(pkt,cl)
	struct bootp *pkt;		/* start of dhcp packet */
	struct client_info *cl; /* partially-completed client_info, which we will modify */
{
	u_char *cp;			/* will always point to the start of the packet, but treats value as u_char* instead of struct bootp* */
	int i;				/* An offset from start of packet.  This is the start of the DHCP option we are presently examining. */ 
	int i_max;			/* The offset for the last byte in the field (options, filename, sname) we are presently examining. */
	int dhcp_opts_field_rc; /* rc from the most-recent field we parsed (options, file, or sname) */
	int file_is_overloaded; /* flag */
	int sname_is_overloaded; /* flag */
	char *errstr;		/* possible errmsg returned by dhcp_opts_field() */
	static char errstr_generic[] = "error parsing DHCP packet";


	if (pkt->bp_htype >= hwinfocnt) {
		/* htype unknown, ignore packet */
		if (debug > 2)
			report(LOG_NOTICE, "  unknown hardware type: %d, ignoring DHCP client (hlen=%d, chaddr=%s, IPsrc=%s)", pkt->bp_htype, pkt->bp_hlen, haddrtoa(pkt->bp_chaddr,BP_CHADDR_LEN), inet_ntoa(recv_addr.sin_addr));
		return 1;
	}

	cl->cliidhw = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata) + pkt->bp_hlen);
	cl->cliidhw->length = haddrlength(pkt->bp_htype) + 1; /* cl->cliidhw->length is safely < UINT_MAX since pkt->bp_hlen is 8 bits */
	if (cl->cliidhw->length-1 != pkt->bp_hlen) {
		if (compact_messages && debug)
			report(LOG_INFO, "DHCPpkterror %s [%s]: bad address length %d != %d (IPsrc=%s)",
				haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), inet_ntoa(pkt->bp_giaddr), pkt->bp_hlen, cl->cliidhw->length - 1, inet_ntoa(recv_addr.sin_addr));
		if (debug > 2)
			report(LOG_NOTICE, "  bad addr len (BOOTP htype=%d, but hlen=%d) from DHCP client with %s address %s (IPsrc=%s)", 
				pkt->bp_htype, pkt->bp_hlen, netname(pkt->bp_htype), haddrtoa(pkt->bp_chaddr,cl->cliidhw->length-1 ), inet_ntoa(recv_addr.sin_addr));
		return 1;
	}

	if (pkt->bp_htype == HTYPE_ETHERNET) {
		/* rule out common bogus Ethernet addresses of all zeros */
		if (!bcmp((char *) &pkt->bp_chaddr, (char *) &enet_all_zeros, haddrlength(HTYPE_ETHERNET))) {
			if (debug > 2)
				report(LOG_NOTICE, "  bad Ethernet chaddr (all zeros), ignoring DHCP client, (IPsrc=%s)", inet_ntoa(recv_addr.sin_addr));
			return 1;
		}
		/* rule out broadcast and multicast Ethernet addreses, which are bogus client enet addresses */
		if (pkt->bp_chaddr[0] % 2 == 1) {
			if (debug > 2)
				report(LOG_NOTICE, "  bad Ethernet chaddr %s (broadcast or multicast), ignoring DHCP client (IPsrc=%s)", haddrtoa(pkt->bp_chaddr, pkt->bp_hlen), inet_ntoa(recv_addr.sin_addr));
			return 1;
		}
	}

	/* fill in cl->cliidhw based on packet's bp_htype and bp_chaddr fields */
	cl->cliidhw->data[0] = pkt->bp_htype;
	cl->cliidhw->linkcount = 1;
	bcopy(pkt->bp_chaddr, cl->cliidhw->data+1, cl->cliidhw->length-1); 

	/* parse DHCP options.
	   We let dhcp_opts_field_rc accumulate the rc from the most-recent field we tried to parse (options, file, sname).
	   Once we encounter an error, we don't try to parse any more fields.
	*/

	cp = (u_char *) pkt; /* We treat the entire packet as a char array  */
	errstr = NULL;

	/* First parse the 'options' field.
	   Note that VEND_OFFSET takes us 4 bytes into the vend field, skipping over the (assumed) vendor magic cookie.
	   We must express the number of bytes in the 'options' field as 'pktlen - VEND_OFFSET', rather than as 'BP_VEND_LEN',
	   since DHCP allows the option length field to be variable length.
	*/
	if ((dhcp_opts_field_rc = dhcp_opts_field(cp + VEND_OFFSET, pktlen - VEND_OFFSET, cl, &errstr)) >= 0) {
		/* no error parsing 'options' field */

		file_is_overloaded =  (dhcp_opts_field_rc & 1);
		sname_is_overloaded = (dhcp_opts_field_rc & 2);

		if (file_is_overloaded) {
			/* Parse the overloaded 'file' field. */
			if ((dhcp_opts_field_rc = dhcp_opts_field(cp + FILE_OFFSET, BP_FILE_LEN, cl, &errstr)) >= 0) {
				/* no errors parsing 'file' field */

				/* Although 'sname' may not been specified as overloaded earlier, another 'DHCP Options Overload' field
				   within the 'file' field may have specified otherwise.  RFC2131 prohibits this (both by saying that the
				   'DHCP Options Overload' field may only appear in the 'options' field, also that "options may appear only once, 
				   So it's up to us how we'll deal with discovering another 'DHCP Options Overload' field in the 'file' field.
				   We could just discard the entire packet, but we'll be more forgiving.  We'll allow the second instance to
				   say that the 'sname' field is overloaded.  We'll ignore anything else the second instance may claim.
				   (Perhaps we should instead just ignore the second instance entirely; if we decide to do that instead,
				   we need only remove the line below.)
				*/
				sname_is_overloaded += (dhcp_opts_field_rc & 2);
			} /* else error parsing 'file' field */
		} /* file is overloaded */

		if ((dhcp_opts_field_rc >= 0) && sname_is_overloaded) {
			/* no errors parsing fields so far, and we must parse the overloaded 'sname' field */
			dhcp_opts_field_rc = dhcp_opts_field(cp + SNAME_OFFSET, BP_SNAME_LEN, cl, &errstr);
		}

	} /* else error parsing 'options' field, don't bother trying to parse any others */

	/* populate cl->cliid based on cl->cliidhw if necessary */
	if (!cl->cliid) {
		cl->cliid = cl->cliidhw;
		cl->cliid->linkcount++;
		/* cl->cliid_explicit remains FALSE */
	}

	if (dhcp_opts_field_rc >= 0) /* no errors parsing any fields */
		return 0; /* success */

	else {  /* error in the last field we parsed */
		if (!errstr)
			errstr = errstr_generic;

		/* display the error, and try to identify the client as best as we can, given that we may not have even
	   	   managed to determine who the client is.
		*/
		if (cl->cliid) {
			report(LOG_ERR, "  %s; ignoring DHCP client (cliid=%s, but may be inaccurate), (IPsrc=%s)", errstr, cliid_btoa(cl->cliid), inet_ntoa(recv_addr.sin_addr));
		} else if (cl->cliidhw) {
			report(LOG_ERR, "  %s; ignoring DHCP client (chaddr=%s, but may be inaccurate), (IPsrc=%s)", errstr, cliid_btoa(cl->cliidhw), inet_ntoa(recv_addr.sin_addr));
		} else 
			report(LOG_ERR, "  %s; ignoring DHCP client (no cliid nor chaddr available to report), (IPsrc=%s)", errstr, inet_ntoa(recv_addr.sin_addr));

		return 1;  /* failure */
	}
}


/* Parse the DHCP options in a single field (options, file, sname). 
   Called by dhcp_opts(), just for DHCP packets received from clients.
   Return < 0 on error.
   Return 0 on success when no DHCP Overload Options field was encountered.
   Return 1 on success when DHCP Overload Options field encountered with a value of 1.
   Return 2 on success when DHCP Overload Options field encountered with a value of 2.
   Return 3 on success when DHCP Overload Options field encountered with a value of 3.
   May malloc cl->msg, cl->user, cl->vend, cl->cliid, cl->req_opts.
*/
int 
dhcp_opts_field(cp, cp_size, cl, errmsg)
	u_char *cp;			/* start of field to parse for options */
	int cp_size;		/* num bytes */
	struct client_info *cl; /* partially-completed client_info, which we will modify */
	char **errmsg;		/* On error, may be changed to point to a static err msg.  Otherwise is not modified. */
{
	int i;			/* offset into cp[] */
	int overload;	/* value of DHCP Overload Option seen in this field */
	static char errstr_overflow[] = "error parsing DHCP packet: DHCP options overflowed available space in packet";
	static char errstr_cliid_0[] = "bad Client ID option (all zeros)";
	static char errstr_cliid_bcast[] = "bad Client ID option (corresponds to broadcast or multicast Ethernet address)";


	i = 0;			/* start of field */
	overload = 0;	/* no DHCP Overload Option seen in this field yet */

	while (i < cp_size) {

		switch (cp[i]) {
	
			case TAG_PAD:
				/* Unlike most tags, this one lacks a 'length' byte.
				   We must compute the increment of 'i' ourself, rather than use the normal compution at the end of the loop.
				*/
				i++;
				continue; /* while (i < cp_size) */
	
			case TAG_END:
				/* stop processing this field */
				return overload; /* success */

			default:
				;
				/* nop */

		} /* (the first) switch (cp[i]) */
	
		/* At this point, we know the current DHCP option is neither TAG_PAD nor TAG_END, since
		   handling above of either of those resulted in calling 'return' or 'continue'.
		   All DHCP options other than TAG_PAD and TAG_END are more than 1 byte long.
		   They all have an option code, a length byte, then 'length' (possibly 0) bytes of data.
		   Before trying to process the current option, make sure it is wholly-contained within
		   the current field we are examining.
		*/

		if (i == cp_size - 1) {
			/* A multi-byte option can't start on the very last byte of the field, since it would
			   have to extend beyond the end of the field.
			*/
			*errmsg = errstr_overflow;
			return -1; /* error */
		}
		/* now we know i < cp_size - 1 */
		if (i + cp[i+1] + 1 > cp_size - 1) { /* current offset + length of this option + one byte for the the option code */
			/* This option claims to extend beyond the last byte of the field. */
			*errmsg = errstr_overflow;
			return -1; /* error */
		}

		/* Now we know the current multi-byte option is wholly-contained within the current field, so
		   we can try to process it.
		*/

		switch (cp[i]) {

			case TAG_DHCP_OVL:
				/* value (located 2 bytes beyond option code is supposed to be 1, 2, or 3. */
				overload = cp[i+2] & 3;
				break;
	
			case TAG_MESSAGE:
				cl->msg = (char *) smalloc(cp[i+1] + 1);
				bcopy(cp+i+2, cl->msg, cp[i+1]);
				cl->msg[cp[i+1]+1] = 0;
				break;
	
			case TAG_DHCP_USERCLASS:
				cl->user = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata) + cp[i+1] - 1);
				bcopy(&cp[i+2], cl->user->data, cp[i+1]);
				cl->user->linkcount = 1;
				cl->user->length = cp[i+1]; /* XXX cl->user->length is safely < UINT_MAX if sizeof(unsigned char)==1 */
				break;
	
			case TAG_VENDOR:
				cl->vend = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata) + cp[i+1] - 1);
				bcopy(&cp[i+2], cl->vend->data, cp[i+1]);
				cl->vend->linkcount=1 ;
				cl->vend->length = cp[i+1]; /* XXX cl->vend->length is safely < UINT_MAX if sizeof(unsigned char)==1 */
				break;
	
			case TAG_DHCP_SERVERID:
				bcopy(&cp[i+2], &cl->serv_addr.s_addr, 4);
				break;
	
			case TAG_DHCP_IP: /* requested IP address option */
				bcopy(&cp[i+2], &cl->req_addr.s_addr, 4);
				break;
	
			case TAG_MAX_MSGSZ: 
				/* Note that as per spec, this is a 2-byte unsigned quantity.  We are storing it in an 'unsigned int'. */
				{	unsigned short temp_cl_max_len;
					bcopy(&cp[i+2], &temp_cl_max_len, 2); 
					cl->max_len = (unsigned int) ntohs(temp_cl_max_len);
				}
				break;
	
			case TAG_DHCP_CLIID:
				if (ignore_cliid_option) {
					if (debug > 9)
						report(LOG_INFO, "  ignoring cliid option since -I option specified");
					break;	
					/* Note that cl->cliid will get set to match cl->cliidhw at TAG_END. */
					/* We don't even parse the cliid tag - we assume that if you specified -I, you don't
					   want to be bothered with msgs about problems with the value of cliid options. */
				}
				cl->cliid = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata) + cp[i+1] - 1);
				bzero(cl->cliid, sizeof(struct shared_bindata) + cp[i+1] - 1);
				bcopy(&cp[i+2], cl->cliid->data, cp[i+1]);
				cl->cliid->length = cp[i+1]; /* XXX cl->cliid->length is safely < UINT_MAX if sizeof(unsigned char)==1 */
				cl->cliid->linkcount = 1;
				cl->cliid_explicit = TRUE;	/* we didn't have to infer it */
				/* rule out some common bogus Client IDs for htype=10Mb-Ethernet */
				if (cl->cliid->length  == haddrlength(HTYPE_ETHERNET)+1) {
					if (!bcmp((char *) &cl->cliid->data, (char *) &htype_ethernet, 1)) {
						/* rule common bogus Client ID addresses of 0x01000000000000 */
						if (!bcmp((char *) &cl->cliid->data[1], (char *) &enet_all_zeros, haddrlength(HTYPE_ETHERNET))) {
							*errmsg = errstr_cliid_0;
							return -1; /* error */
						}
						/* rule out bogus Client ID corresponding to broadcast or multicast Ethernet addresses */
						if ( cl->cliid->data[1] & 0x01 ) {
							*errmsg = errstr_cliid_bcast;
							return -1; /* error */
						}
					}
				}
				if ((cl->cliid->length != cl->cliidhw->length) || bcmp(cl->cliid->data,  cl->cliidhw->data, cl->cliidhw->length))
					if (debug > 2) {
						/* Must avoid calling cliid_btoa() twice in same expr (returns ptr to static storage),
						   so do one of the calls separately. */
						char cliidhw_str[MAXCLIIDLEN];
						strncpy(cliidhw_str, cliid_btoa(cl->cliidhw), MAXCLIIDLEN);

						report(LOG_WARNING, "  Client ID option (%s) doesn't correspond to chaddr (%s), (IPsrc=%s)", 
							cliid_btoa(cl->cliid), cliidhw_str, inet_ntoa(recv_addr.sin_addr));
					}
				break;
	
			case TAG_DHCP_IPLEASE:
				bcopy(&cp[i+2], &cl->secs, 4);
				cl->secs = ntohl(cl->secs);
				break;
	
			case TAG_DHCP_REQ: /* DHCP Parameter Request List */
				cl->req_opts = (unsigned char *) smalloc(cp[i+1] + 1);
				bcopy(&cp[i+1], cl->req_opts, cp[i+1] + 1);
				break;
	
			case TAG_CLIENT_FQDN:
				/* As per spec, the presence of the "Client FQDN" option in the client's packet means that the
				   server should include a "Client FQDN" option in its reply.  So we'll remember it here.
				   (To really comply with the spec, we'd actually need to do more than just remember that the client
				   sent the option.  We'd need to remember whether the 'E' bit was set in the 'flags' field,
				   so our response will have the domain name encoded the way the client wants; presently we just
				   always encode the domain name in ASCII.   And if we were actually supporting Dynamic DNS (instead
				   of just telling the client "no Dynamic DNS here!"), we'd also need to remember the values of the
				   client's 'S' and 'O' flag bits, and even the fqdn value claimed by the client.  But we don't
				   support any of that today.  So for now, all we have in the struct client_info is a boolean.
				*/
				cl->client_fqdn_option_present = TRUE;
				break;
	
			default:
				/* We don't care about other DHCP options the client might specify. */
				break;
	
		} /* (the second) switch (cp[i]) */

		i++; /* step past the option code.  Now 'i' refers to the 'length' byte */
		i += (1+ cp[i]); /* Step past the option byte and 'length' bytes of option values */
		/* now 'i' refers to the start of the next option...or has advanced to cp_size (past the end of the field) */

	} /* (i < cp_size) */

	return overload; /* success */
}


boolean ipcmp(a, b)
hash_datum *a,*b;
{
char *key;
struct host *data;
	/* something's probably very wrong is a or b are NULL */
	if ((a==NULL) || (b==NULL))
		return FALSE;
	key=(char *)a;
	data=(struct host *)b;
	return (((struct in_addr*)key)->s_addr==data->iaddr.s_addr);
}
boolean cliidhwlookcmp(a, b)
hash_datum *a,*b;
{
struct shared_bindata *key;
struct host *data;
	key=(struct shared_bindata *)a;
	data=(struct host *)b;
	if(data->htype!=key->data[0])
		return FALSE;

	if(bcmp(data->haddr,&key->data[1],key->length-1))
		return FALSE;
	return TRUE;
}
boolean cliidcmp(a,b)
hash_datum *a,*b;
{
struct shared_bindata *key;
struct host *data;
	key=(struct shared_bindata *)a;
	data=(struct host *)b;
	if(data->flags.cliid)
		return (key->length==data->cliid->length && 
			!bcmp(key->data,data->cliid->data,key->length));
	else
		return FALSE;
}
boolean boundcliidcmp(a,b)
hash_datum *a,*b;
{
struct shared_bindata *key;
struct host *data;
	key=(struct shared_bindata *)a;
	data=(struct host *)b;
	return (key->length==data->cl->length && 
			!bcmp(key->data,data->cl->data,key->length));
}

/*
 * Function for comparing two in_addr structs.
 */
boolean
inaddrcmp(d1, d2)
	hash_datum *d1, *d2;
{
	return (((struct in_addr *) d1)->s_addr == ((struct in_addr *) d2)->s_addr);
}

	

/* recover the memory allocated to struct client_info * cl */
void free_client_info(cl)
struct client_info *cl;
{

#ifdef DEBUG
	if (debug > 15) {
		report(LOG_DEBUG, "free_client_info() called");
		report_cliid(LOG_DEBUG, cl->cliid);
	}
#endif

if (!cl) {
	/* this *really* shouldn't ever happen */
	report(LOG_ERR,"free_client_info(): called with NULL pointer! (will not free memory)");
	return;
}

   /* We must call del_bindata() on the structures pointed to by cliid, cliidhw,
      vend, and user fields, because each of those has its OWN linkcount to keep
      track of the number sharers of that data, which del_bindata() updates/frees.
	  We HOPE everyone using any of these fields correctly increments the linkcounter,
	  otherwise we'll free the structure prematurely.  We HOPE everyone no longer using
	  any of these fields calls del_bindata(), or we'll leak memory.  (I suspect the
	  latter is happening, haven't seen the former happen yet.)
	*/
	del_bindata(cl->cliid);
	del_bindata(cl->cliidhw);
	del_bindata(cl->vend);
	del_bindata(cl->user);

	/* these fields are not shared */
	if (cl->msg) 
		free(cl->msg);
	if (cl->req_opts)
		free(cl->req_opts);

}

/*
 * Frees the given struct in_addr.  Does nothing if the passed ptr is NULL.
 */
void
free_inaddr(inaddrp)
	hash_datum *inaddrp;
{
#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "free_inaddr(): called");
#endif

	if (inaddrp == NULL) {
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "free_inaddr(): inaddrp== NULL, will do nothing");
#endif
		return;
	}

#ifdef DEBUG
	if (debug > 15)
		/* XXX inet_ntoa(*) not re-entrant (uses static storage), so a call to us need to never
		  be in an expr that could result in another call inet_ntoa().   There's no assurance of that!
		*/
		report(LOG_DEBUG, "free_inaddr(): in_addr==%s", inet_ntoa(*(struct in_addr *)inaddrp));
#endif

	/* this hash datum is never shared */

	free((char *) inaddrp);
	return;
}


void dhcp_discover(pkt)
struct bootp *pkt;
{
	struct client_info cl;
	struct host *hp, *hp2, *hp3, *registered;
	unsigned int32 dest; /* network byte order */
	int len;
	unsigned iphashcode,hwhashcode,cablehashcode,rc;
	struct opts opt;
	struct lastbind *oldbind;
	int32 bootsize=0;
	int exists;		/* is the binding we choose to offer is one that already exists for this client? */
	struct cable *c;
	boolean inconsistent;
	int roaming=0;
	int deny_dynamic=0;
	struct flag sent_options;
	unsigned int delay_usec = 0;

	bzero(&cl,sizeof(cl));
	cl.max_len=576; 	/* for testing overload functions, temporarily reduce this, e.g. to 300 */

	bzero(&sent_options,sizeof(sent_options));

	if(debug>1)
		report(LOG_INFO," processing DHCPDISCOVER");
	discovers_received++;
	rc=dhcp_opts(pkt,&cl);

	/* we don't need msg here, dhcp_opts() already produces one
	if(!cl.cliid)
		report(LOG_ERR,"  couldn't determine client identifier, ignoring dhcpdiscover");
	*/
	if(rc){
		free_client_info(&cl);
		return;
	}

	if(cl.msg)free(cl.msg);

	if(debug>1)
		report_cliid(LOG_INFO,cl.cliid);

	cl.clhashcode=hash_HashFunction(cl.cliid->data,cl.cliid->length);

	/* We look for an IP address to offer, checking databases in the following order.  In each case,
	   if a potential IP address is found, we check if it is appropriate; if it is not, we discard it and
	   continue our search.  
	   The search order is:
			static IP address assigned in bootptab for this client's clientid
			current binding for this client's clientid
			previous binding for this client's clientid
			requested IP address option in DISCOVER packet, if present
			a new dynamic IP address

		Once we find an IP address to offer, we actually bind the client to this IP address, with
			a very short expiration time.
		If we find no IP address to offer, we are silent.

		Note that we violate spec, which says you search for current or oldbinding first.
		We violate because spec would prevent us from assigning a static IP address to
		a client that previous was bound to a dynamic address, when both are now on the same net.
	*/


	registered=NULL;

	/* Look for clientid in bootptab.  (Remember that if incoming packet doesn't specify a 
	   DHCP Client ID option, we've computed its ClientID by concatenating its htype with its haddr.) */
	hp =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);

#ifdef RETRY_HADDR_AS_CLIID
	/* I've ifdef'd out this because pretending to find a cliid match for clients when there's a mismatch 
	   between the packet's Cliid and the bootptab's cliid can cause problems later as we start storing
	   the inconsistent values in our hashtables.  Besides, we probably shouldn't be pretending to find
	   a match in the first place -- errors in the cliid specified by the bootptab or the client
	   should be corrected, not ignored.
	*/
	if(!hp){ 
		/* We didn't find ClientID in bootptab's clhashtable using ClientID as key.  
		   (Recall that clhashtable contains bootptab entries keyed by cliid (if 'cl' explicitly 
		   specified in bootptab entry) else bootptab entries keyed by haddr.) 
		   Perhaps the incoming packet contains a DHCP ClientID option with a garbage value.
		   Or perhaps the bootptab entry specifies a garbage cliid (via an incorrect 'cl' tag).
		   In either case, we may have more luck if we look up the incoming packet's haddr in
		   bootptab's hwhastable using haddr as key.  
		*/
		hwhashcode=hash_HashFunction(cl.cliidhw->data+1,cl.cliidhw->length-1);
		hp =(struct host *)hash_Lookup(hwhashtable, hwhashcode, cliidhwlookcmp,cl.cliidhw);
	}
#endif

	if (hp) { /* found in bootptab */
		registered=hp;
		roaming = hp->flags.roaming;
		deny_dynamic = hp->flags.deny_client_dynamic;
		if (hp->flags.deny_client) {
			if (debug > 3)
				report(LOG_INFO, "  client id/haddr found in bootptab with deny_client tag, ignoring client");
			free_client_info(&cl);
			return;
		}
		if (hp->iaddr.s_addr!=0) {
			if (compact_messages && debug) {
				char giaddrbuf[MAX_INETADDR_STRING_LEN];
				char iaddrbuf[MAX_INETADDR_STRING_LEN];

				strncpy(giaddrbuf, inet_ntoa(pkt->bp_giaddr), MAX_INETADDR_STRING_LEN-1);
				strncpy(iaddrbuf, inet_ntoa(hp->iaddr), MAX_INETADDR_STRING_LEN-1);
				report(LOG_INFO, "DHCPDISCOVER %s [%s]: found %s (%s)", 
					haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), giaddrbuf, iaddrbuf, hp->hostname->string);
			}
			if (debug > 3)
				report(LOG_INFO, "  client id/haddr found in bootptab with static IP address %s", inet_ntoa(hp->iaddr));
			if (check_net(&(pkt->bp_giaddr),&(hp->iaddr)))  {
				if (debug > 3) 
					report(LOG_INFO,"  static IP address is OK for client's network");
			}
			else {
				if (debug > 3) 
					report(LOG_INFO,"  static IP Address not appropriate for client's network, will not use");
				hp=NULL;
			}
		} else {
			if (debug > 3)
				report(LOG_INFO, "  client id/haddr found in bootptab with no static IP address, will not use this bootptab entry");
			hp=NULL;
		}
	} else {
		if (compact_messages && debug)
			report(LOG_INFO, "DHCPDISCOVER %s [%s]: not found", 
				haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), inet_ntoa(pkt->bp_giaddr));
		if (debug > 3) 
			report(LOG_INFO, "  client id/haddr not found in bootptab");
	}


	/* check if there is a current binding for this cliid (and possibly ip address, if static found in bootptab) */
	exists=FALSE;	/* assume client has no current binding */
	hp2 =(struct host *)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp,cl.cliid);

	if (hp) { /* we had found an appropriate static IP address in bootptab */
		if (!hp2)  { 
			if (debug > 3)
				report(LOG_INFO, "  client id not presently bound");
		} else { 
			if (debug > 3)
				report(LOG_INFO, "  client id presently bound to %s", inet_ntoa(hp2->iaddr));
			if (!hp2->flags.expire) {
				if (debug > 3 || log_expirations)
					report(LOG_INFO, "  current binding was assigned via BootP (not DHCP), freeing binding");
				reuse_addr(hp2);
				hp2=NULL;
			} else { /* current binding was assigned via DHCP, not BootP */
				if (hp2->expire <= now) {
					if (debug > 3 || log_expirations)
						report(LOG_INFO, "  current binding already expired (awaiting garbage collection), freeing binding");
					reuse_addr(hp2);
					hp2=NULL;
				} else { /* current binding has not yet expired */
					if (hp->iaddr.s_addr == hp2->iaddr.s_addr) {
					exists=TRUE; /* we passed the entire chain of tests leading to this! */
						if (debug > 3) {
							report(LOG_INFO, "  binding has same IP address as bootptab entry; OK to keep using binding");
						}
					} else {
						if (debug > 3 || log_expirations)
							report(LOG_INFO, "  binding is for a different IP address (%s) than bootptab entry, freeing binding", inet_ntoa(hp2->iaddr));
						reuse_addr(hp2);
						hp2 = NULL;
					}
				}
			}
		}
		/* make sure ipboundhshtbl agrees with state of hp2 */
		iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
		hp3 =(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr));
		if (hp2) { /* cliid has current binding */
			if (hp3) { /* ip address statically-assigned to cliid is currently bound */
				/* make sure existing entry in hp3 agrees with hp2 */
				if (!BINDATA_EQ(hp3->cl, cl.cliid)) {
					report(LOG_ERR, "dhcp_discover(): the following cliid...");
					report_cliid(LOG_ERR, hp->cl);
					report(LOG_ERR, "was assigned a static IP (%s) in bootptab, cliid was found bound to this IP in boundhshtbl...", inet_ntoa(hp->iaddr));
					report(LOG_ERR, "but this IP found in ipboundhshtbl bound to a *DIFFERENT* cliid (cliid=%s)", cliid_btoa(hp3->cl));
					report(LOG_ERR, "freeing both bindings");
					reuse_addr(hp3);
					reuse_addr(hp2);
					hp3 = hp2 = NULL;
				} else
					exists=TRUE;
			} else { /* ip address statically-assigned to cliid is not currently bound */
				report(LOG_ERR, "dhcp_discover(): cliid (%s) assigned a static IP (%s) in bootptab, cliid found bound to this IP in boundhshtbl, but this IP *NOT* found in ipboundhshtbl!", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
				report(LOG_ERR, "freeing binding found in boundhshtbl");
				reuse_addr(hp2);
				hp2=NULL;
			}
		} else { /* cliid does not have current binding */
			if (hp3) { /* ip address statically-assigned to cliid is currently bound */
				report(LOG_ERR, "dhcp_discover(): cliid (%s) assigned a static IP (%s) in bootptab, cliid not found in boundhshtbl, but this IP *FOUND* in ipboundhshtbl!", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
				report(LOG_ERR, "the unexpected entry found in ipboundhshtbl had the cliid %s", cliid_btoa(hp3->cl));
				report(LOG_ERR, "freeing unexpected binding found in ipboundhshtbl");
				reuse_addr(hp3);
				hp3=NULL;
			} else { /* ip address statically-assigned to cliid is not currently bound */
				/* this is normal */
			}
		}
	} 

	

	if (!hp) { /* we didn't find an appropriate static IP address in bootptab */
		if (hp2) { /* client is presently bound */
			hp=hp2;
			hp2=NULL;
			exists = TRUE;
			/* Check that current binding is still OK to re-use */
			if (debug > 3)
				report(LOG_INFO,"  client id is currently bound to %s, checking if ok to re-use", inet_ntoa(hp->iaddr));
			/* check that hp->iaddr is still in bootptab */
			iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
			hp2=(struct host *)hash_Lookup(iphashtable,iphashcode,ipcmp, (hash_datum*)&(hp->iaddr.s_addr));
			if (!hp2) {
				if (debug > 3 || log_expirations) {
					report(LOG_INFO,"  IP address %s no longer in bootptab, will not re-use", inet_ntoa(hp->iaddr));
					report(LOG_INFO,"  freeing binding");
				}
				reuse_addr(hp);
				hp = NULL;
				exists = FALSE;
			}
			if (hp) {
				/* check that the binding is also in ipboundhshtbl - it better be! */
				hp3 =(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr));
				if (!hp3) { /* this should not happen! */
					report(LOG_ERR, "dhcp_discover(): cliid (%s) found in boundhshtbl, but corresponding IP (%s) *NOT* found in ipboundhshtbl!", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
					report(LOG_ERR, "freeing binding");
					reuse_addr(hp);
					hp2 = hp = NULL;
					exists=FALSE;
				}
			}
				if (hp && hp3 && !hp3->flags.cliid) {
				report(LOG_ERR, "dhcp_discover(): cliid (%s) found in boundhshtbl, corresponding IP (%s) found in ipboundhshtbl, but latter lacks a cliid!", cliid_btoa(hp->cl), inet_ntoa(hp3->iaddr));
				report(LOG_ERR, "freeing both bindings");
				reuse_addr(hp3);
				reuse_addr(hp);
				hp3 = hp = NULL;
				exists=FALSE;
			}
			if (hp && hp3 && !BINDATA_EQ(cl.cliid, hp3->cliid)) {
				report(LOG_ERR, "dhcp_discover(): cliid (%s) found in boundhshtbl, corresponding IP (%s) found in ipboundhshtbl,...", cliid_btoa(hp->cl), inet_ntoa(hp3->iaddr));
				report(LOG_ERR, "but latter's cliid (%s) does not match!", cliid_btoa(hp3->cl));
				report(LOG_ERR, "freeing both binding");
				reuse_addr(hp3);
				reuse_addr(hp);
				hp3 = hp = NULL;
				exists=FALSE;
			}
			if (hp && !hp->flags.expire) {
				if (debug > 3 || log_expirations) {
					report(LOG_INFO, "  current binding to IP address %s was assigned via BootP (not DHCP), will not re-use", inet_ntoa(hp->iaddr));
					report(LOG_INFO, "  freeing binding");
				}
				reuse_addr(hp);
				hp=NULL;
				exists = FALSE;
			}
			if (hp && (hp->expire <= now)) {
				if (debug > 3 || log_expirations) {
					report(LOG_INFO, "  current binding to IP address %s already expired (awaiting garbage collection)", inet_ntoa(hp->iaddr));
					report(LOG_INFO, "  freeing binding");
				}
				reuse_addr(hp);
				hp=NULL;
				exists = FALSE;
			}
			if (hp) {
				/* If bootptab entry has a cliid, make sure it matches the requester's */
				if (hp2 && hp2->flags.cliid && !BINDATA_EQ(hp2->cliid,cl.cliid)) {
						report(LOG_ERR,"  bootptab entry for this IP address (%s) has different cliid! Something's very wrong!", inet_ntoa(hp->iaddr));
						report(LOG_ERR,"  freeing binding");
						reuse_addr(hp);
						hp=NULL;
						exists = FALSE;
				}
			}
			if(hp) { 
				/* Make sure IP address is still appropriate, given client's present wire. */
				if (!check_net(&(pkt->bp_giaddr),&(hp->iaddr))) {
					if (debug > 3 || log_expirations) {
						report(LOG_INFO,"  IP address %s is no longer appropriate (client on different network); will not re-use", inet_ntoa(hp->iaddr));
						report(LOG_INFO,"  freeing binding");
					}
					reuse_addr(hp);
					hp=NULL;
					exists = FALSE;
				}
			}
			if (hp && hp2->flags.dynamic) { /* If bootptab entry is a dynamic IP address */
				if (deny_dynamic) {
					if (debug > 3) {
						report(LOG_INFO, "  IP address %s is bound, and bootptab indicates it is dynamic, but client is explicity denied dynamic IP addresses; will not re-use", inet_ntoa(hp->iaddr));
						report(LOG_INFO,"  freeing binding");
					}
					reuse_addr(hp);
					hp=NULL;
					exists = FALSE;
				} else {
					/* Make sure the dynamic address is a member of a dynamic pool we're handling */
					if (!hp->flags.cable) {
						report(LOG_ERR, "  IP address %s is bound, and bootptab indicates it is dynamic, but it is *not* a member of any pool!  Something's very wrong!", inet_ntoa(hp->iaddr));
						report(LOG_ERR, "  freeing binding"); 
						reuse_addr(hp);
						hp=NULL;
						exists = FALSE;
					}
					/* Make sure the IP address is still appropriate, given the client's present wire, and
						policy restrictions. */
					/* Note we cannot use use_addr() since it will return FALSE when proposed IP address
				   	is not free, and we *know* this address is not free, since it is bound to us.  We'll have
				   	to go down a level and use access_network(), relying on the fact that we've already
				   	taken care of doing check_net().  */
					cablehashcode = hash_HashFunction((u_char*)hp2->cable->string, strlen(hp2->cable->string));
    				c = (struct cable *) hash_Lookup(cablehshtbl, cablehashcode, cablelcmp, hp2->cable->string);
    				if (c) { /* only test if we have an access policy defined for this cable */
						if (roaming)
							rc=access_network(c->code,ROAMING_ACCESS|REG_ACCESS|DHCP_ACCESS);
						else if (registered)
							rc=access_network(c->code,REG_ACCESS|DHCP_ACCESS);
						else if (!registered)
							rc=access_network(c->code,UNREG_ACCESS|DHCP_ACCESS);
						if (!rc) {
							if (debug > 3 || log_expirations) {
								report(LOG_INFO,"  dynamic IP Address %s disallowed by access policy; will not re-use", inet_ntoa(hp->iaddr));
								report(LOG_INFO,"  freeing binding");
							}
							reuse_addr(hp);
							hp=NULL;
							exists = FALSE;
						}
					}
				}
			}
		} else if (debug > 3) report(LOG_INFO, "  client not currently bound");
	}


	if(!hp){
		oldbind=(struct lastbind*)hash_Lookup(lastbindhshtbl,cl.clhashcode,lastbindcmp,&cl);
		if(oldbind){
			if(debug>3)
				report(LOG_INFO, "  Found previous binding to %s, checking if ok to re-use",inet_ntoa(oldbind->last_ip));
			/* check that hp->iaddr is still in bootptab */
			iphashcode=hash_HashFunction((u_char*)&oldbind->last_ip.s_addr,4);
			hp=(struct host *)hash_Lookup(iphashtable,iphashcode,ipcmp, (hash_datum*)&(oldbind->last_ip.s_addr));
			if (!hp) {
				if (debug > 3)
					report(LOG_INFO, "  IP Address no longer in bootptab, will not re-use");
				hp = NULL;
			}
			/* Make sure this IP address not presently bound.  If it is, we know it is to someone
			   else, since by now we now this client is not presently bound. */
			if (hp && hash_Lookup(ipboundhshtbl, hp->iphashcode, ipcmp, (hash_datum *)&(hp->iaddr.s_addr))) {
				if (debug >3)
					report(LOG_INFO, "  IP address now bound to someone else, will not re-use");
					hp = NULL;
			}
			/* If bootptab entry has a cliid, make sure it matches the requester's */
			if (hp && hp->flags.cliid && !BINDATA_EQ(hp->cliid,cl.cliid)) {
				if (debug > 3)
					report(LOG_INFO, "  Bootptab entry for this IP (%s) has different cliid, will not re-use", inet_ntoa(hp->iaddr));
				hp=NULL;
			}
			if (hp && hp->flags.dynamic) { /* If bootptab entry is a dynamic IP address */
				if (deny_dynamic) {
					if (debug > 3)
						report(LOG_INFO, "  Client is explicity denied dynamic IP addresses");
					hp=NULL;
				/* Make sure the IP address is still appropriate, given the client's present wire, and
					policy restrictions. Note that use_addr(), if sucessful, will actually allocate
				    the address to us -- that is, remove it from pool of available dynamic addrs. */
				} else if (roaming)
					hp=use_addr(hp,pkt->bp_giaddr,ROAMING_ACCESS|REG_ACCESS|DHCP_ACCESS,0);
				else if (registered)
					hp=use_addr(hp,pkt->bp_giaddr,REG_ACCESS|DHCP_ACCESS,0);
				else if (!registered)
					hp=use_addr(hp,pkt->bp_giaddr,UNREG_ACCESS|DHCP_ACCESS,0);
				if (!hp) {
					if (debug > 3)
						report(LOG_INFO, "  Dynamic IP Address no longer appropriate (client on different network, or access policy change); will not re-use");
					hp=NULL;
				}
			}
			if(hp && ! hp->flags.dynamic) { /* If bootptab is a static IP address */
				/* Make sure IP address is still appropriate, given client's present wire. */
				if (!check_net(&(pkt->bp_giaddr),&(hp->iaddr))) {
					if (debug > 3)
						report(LOG_INFO, "  Static IP Address %s no longer appropriate (client on different network); will not re-use)", inet_ntoa(hp->iaddr));
					hp=NULL;
				}
			}
		} else if (debug > 3) report(LOG_INFO, "  No previous binding found");
	}

	if(!hp){
		if(cl.req_addr.s_addr==0){
			if (debug > 3)
				report(LOG_INFO, "  Client did not specify Requested IP address option");
		} else {
			if (debug > 3)
				report(LOG_INFO, "  Client specified option: Requested IP address %s", inet_ntoa(cl.req_addr));
			iphashcode=hash_HashFunction((u_char*)&cl.req_addr.s_addr,4);
			hp=(struct host *)hash_Lookup(iphashtable,iphashcode,ipcmp, (hash_datum*)&(cl.req_addr.s_addr));
			if (!hp) {
				if (debug > 3)
					report(LOG_INFO,"  Requested IP Address not in bootptab, will not use");
				hp = NULL;
			}
			/* Make sure this IP address not presently bound.  If it is, we know it is to someone
			   else, since by now we now this client is not presently bound. */
			if (hp && hash_Lookup(ipboundhshtbl, hp->iphashcode, ipcmp, (hash_datum *)&(hp->iaddr.s_addr))) {
				if (debug >3)
					report(LOG_INFO, "  IP address already bound to someone else, will not re-use");
					hp = NULL;
			}
			/* If bootptab entry has a cliid, make sure it matches the requester's */
			if (hp && hp->flags.cliid && !BINDATA_EQ(hp->cliid,cl.cliid)) {
				if (debug > 3) 
					report(LOG_INFO, "  Bootptab entry for requested IP address has different cliid, will not use");
				hp=NULL;
			}
			if (hp && hp->flags.dynamic) { /* If bootptab entry is a dynamic IP address */
				if (deny_dynamic) {
					if (debug > 3)
						report(LOG_INFO, "  Client is explicity denied dynamic IP addresses");
					hp=NULL;
				/* Make sure the IP address is still appropriate, given the client's present wire, and
					policy restrictions. Note if use_addr() succeds, it allocates the address to us,
				    removing it from pool of available dynamic addrs. */
				} else if (roaming) 
					hp=use_addr(hp,pkt->bp_giaddr,ROAMING_ACCESS|REG_ACCESS|DHCP_ACCESS,0);
				else if(registered)
					hp=use_addr(hp,pkt->bp_giaddr,REG_ACCESS|DHCP_ACCESS,0);
				else if(!registered)
					hp=use_addr(hp,pkt->bp_giaddr,UNREG_ACCESS|DHCP_ACCESS,0);
				if (!hp) {
					if (debug > 3)
						report(LOG_INFO, "  Requested dynamic IP Address not appropriate (client on different network, or access policy restriction); will not use");
					hp=NULL;
				}
			}
			if(hp && ! hp->flags.dynamic) { /* If bootptab is a static IP address */
				/* Make sure IP address is still appropriate, given client's present wire. */
				if (!check_net(&(pkt->bp_giaddr),&(hp->iaddr))) {
					if (debug > 3)
						report(LOG_INFO, "  Requested static IP Address not appropriate (client on different network); will not use");
					hp=NULL;
				}
			}
		}
	}


	/* If nothing found yet, try to assign new dynamic IP address  */
	if(!hp){  /* first try based on whether client is registered in bootptab */
		if (deny_dynamic) {
			if (debug > 6)
				report(LOG_INFO, "  This client is not permitted a dynamic IP address; will not search for one");
		} else if (roaming) {
			if (debug > 6)
				report(LOG_INFO, "  Searching for a dynamic IP address for this roaming client...");
			hp=get_dynamic_ip(pkt->bp_giaddr,ROAMING_ACCESS|REG_ACCESS|DHCP_ACCESS);
		} else if(registered) {
			if (debug > 6)
				report(LOG_INFO, "  Searching for a dynamic IP address for this registered client...");
			hp=get_dynamic_ip(pkt->bp_giaddr,REG_ACCESS|DHCP_ACCESS);
		} else if (!registered) {
			if (debug > 6)
				report(LOG_INFO, "  Searching for a dynamic IP address for this unregistered client...");
			hp=get_dynamic_ip(pkt->bp_giaddr,UNREG_ACCESS|DHCP_ACCESS);
		}
	}

	if(!hp){
		if (compact_messages && debug)
			report(LOG_INFO, "DHCPDISCOVER %s [%s]: no offer", haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), inet_ntoa(pkt->bp_giaddr));
		if (debug > 3)
			report(LOG_INFO,"  No appropriate dynamic ip addresses available, ignoring client");
		free_client_info(&cl);
		return;
	}

	/* At this point, we think we have a good ip address to offer in 'hp'. */

	if (compact_messages && debug) {
		char giaddrbuf[MAX_INETADDR_STRING_LEN];
		char iaddrbuf[MAX_INETADDR_STRING_LEN];

		strncpy(giaddrbuf, inet_ntoa(pkt->bp_giaddr), MAX_INETADDR_STRING_LEN-1);
		strncpy(iaddrbuf, inet_ntoa(hp->iaddr), MAX_INETADDR_STRING_LEN-1);
		report(LOG_INFO, "DHCPDISCOVER %s [%s]: offer %s (%s)",
			haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), giaddrbuf, iaddrbuf, hp->hostname->string);
	}
	if (debug > 1)
		report(LOG_INFO, "  Selected IP address %s (%s) to offer",inet_ntoa(hp->iaddr),
			hp->hostname->string);

	if (!exists) { /* client not presently bound, create a new struct host to contain the binding, using the
					   'hp' that we've located as a template.  (If client already bound, we'll keep using the
					   struct host that is presently bound, updating it as necessary.) */
		if (!(hp=create_stub(hp,&cl))) {	/* create the new struct, fill in a few values */
			report(LOG_ERR, "   dhcp_discover(): create_stub() returned NULL, cannot create new binding");
			/* reuse_addr(hp); we can't call this since hp is toast.  We leak the IP address if dynamic, but no memory */
			free_client_info(&cl);
			return;
		}
		if (!create_lease(hp, cl.cliid, cl.vend, cl.user, 0)) {  /* fills in most values in hp */
			report(LOG_ERR, "   dhcp_discover(): create_lease() returned NULL, cannot create new binding");
			hp->linkcount = 1; /* to force free_host() to free it, as it starts =0 */
			reuse_addr(hp);
			free_client_info(&cl);
			return;
		}
		if(hp->flags.dynamic)
			hp->code=DHCP_FLAG;
		else
			hp->code=RESERVED_FLAG;
		hp->xid=pkt->bp_xid;
		hp->linkcount+=2; /* starts =0; will insert into two hashtabs next */
		/* hp->expire and hp->flags.expire will be set below */
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "  will insert into boundhshtbl...");
#endif
		if(!hash_Insert(boundhshtbl,cl.clhashcode, boundcliidcmp, cl.cliid, (hash_datum*)hp)) {
			leases++;
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "  incremented leases counter to %d", leases);
#endif
        } else {
			report(LOG_ERR, "    dhcp_discover(): hash_Insert(boundhshtbl) failed (cliid=%s)", cliid_btoa(cl.cliid));
			report(LOG_ERR, "    dhcp_discover(): not increasing 'leases' counter...so count may no longer be right");
        }

		/* You can positively identify DHCP leases in the current bindings; they all have hp->flags.expire==1.
		   You can then subdivide them into statics (hp->code==RESERVED_FLAG), or dynamics (hp->code==DHCP_FLAG) */
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "  will insert into ipboundhshtbl...");
#endif
		if (hash_Insert(ipboundhshtbl,hp->iphashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr),(hash_datum*)hp)) {
			report(LOG_ERR, "    dhcp_discover(): hash_Insert(ipboundhshtbl) failed for IP address %s (cliid=%s)", inet_ntoa(hp->iaddr), cliid_btoa(cl.cliid));
		}

	}
	/* We set hp->expire *here*, and not inside the if(!exists) block above, so that if we get multiple
	   DHCPDISCOVERs from this client while we have an outstanding OFFER to it, the server's notion of
	   when the OFFER expires (hp->expire) continues to be updated, even though the OFFER already exists.
	   There is a side effect -- if the lease we are offering is actually one the client already had with
	   us (i.e. not just an OFFER, but a real lease), we are overwriting the old lease's hp->expire value,
	   shortening it presumably.  That's OK here, as since the client is in INIT, he clearly has discarded his
	   old lease. */
	hp->expire=now+TIME_OFFSET;	/* offer will imply a very short lease */
	hp->flags.expire=1;

	/* Report if client left some MBZ flag bits set, and fix it in reply */
	if (ntohs(pkt->bp_flags) & BP_FLAGS_MBZ) {
		if (debug > 9) {
			report(LOG_NOTICE, " some MBZ bits in flags field were set in DHCPDISCOVER (cliid=%s)", cliid_btoa(cl.cliid));
		}
		pkt->bp_flags = htons(ntohs(pkt->bp_flags) & ~ BP_FLAGS_MBZ);  /* clear MBZ bits for reply */
	}

	/* The "lease" represented by hp is not longer a true lease that the client has accepted,
	   but instead is an offer that the client has not yet accepted.
	   That's even true if 'hp' was an existing lease, since by sending a DHCPDISCOVER, the client
	   has implicitly abandoned that lease as DHCPDISCOVER may only be sent by a client in INIT state.
	*/
	hp->flags.offerred_but_not_yet_accepted_by_client = 1;

	/* construct OFFER packet */
	rpkt=(struct bootp *)send_buf;
	bcopy(pkt,rpkt,pktlen);
	rpkt->bp_op=BOOTREPLY;
	rpkt->bp_hops=0;
	rpkt->bp_secs=0;
	rpkt->bp_yiaddr.s_addr=hp->iaddr.s_addr;
   	rpkt->bp_ciaddr.s_addr=0;
	if(fill_header(rpkt,hp,&bootsize)){
		free_client_info(&cl);
		reuse_addr(hp);
		report(LOG_ERR,"  fill_header() returned non-zero, huh?");
		return;
	}
    /* always stick our name in. */
	/* note strncpy will right-pad with nul, clearing out any garbage - good */
	strncpy(rpkt->bp_sname, hostname, BP_SNAME_LEN);	/* may be overwritten if options field overflows */

	dest=hp->flags.reply_addr?hp->reply_addr.s_addr:0;

	opt.cur=0;
	opt.len=cl.max_len;
	/* Insert the DHCP Message Type option first, because some clients incorrectly assume that
	   if this option is not first, the message is BOOTP, or bad. */
	insert_byte_opt(rpkt,&opt,TAG_DHCP_MSG,DHCPOFFER);
	insert_ulong_opt(rpkt,&opt,TAG_DHCP_SERVERID,my_ip_addr.s_addr);
	if(hp->flags.lease) {
		insert_ulong_opt(rpkt,&opt,TAG_DHCP_IPLEASE,htonl(hp->lease));
		if (debug > 9)
			report(LOG_INFO,"  Offered lease would expire in %d seconds (%s)", hp->lease, cctime(now+hp->lease));
	} else { 
		insert_ulong_opt(rpkt,&opt,TAG_DHCP_IPLEASE,htonl(DEFAULT_LEASE));
		if (debug > 9)
			report(LOG_INFO,"  Offered lease would expire in %d seconds (%s)", DEFAULT_LEASE, cctime(now+DEFAULT_LEASE));
	}
	if(hp->flags.renew) {
		insert_ulong_opt(rpkt,&opt,TAG_DHCP_RENEW,htonl(hp->renew));
		if (debug > 9)
			report(LOG_INFO,"  Offered lease would set renewal in %d seconds (%s)", hp->renew, cctime(now+hp->renew));
	}
	if(hp->flags.rebind) {
		insert_ulong_opt(rpkt,&opt,TAG_DHCP_REBIND,htonl(hp->rebind));
		if (debug > 9)
			report(LOG_INFO,"  Offered lease would set rebind in %d seconds (%s)", hp->rebind, cctime(now+hp->rebind));
	}
	if(cl.user){
		insert_string_opt(rpkt,&opt,TAG_DHCP_USERCLASS,
				cl.user->data,cl.user->length);
	}

	/* fill in options requested by client's Parameter Request List option, if any */
    if (rc=do_req_opts(hp, cl.req_opts, (u_char *) rpkt, &opt, &sent_options)) {
		report(LOG_ERR,"  do_req_opts() returned %d, will not send DHCPOFFER to Client ID %s", rc, cliid_btoa(hp->cl));
		free_client_info(&cl);
		reuse_addr(hp);
		return;
	}

	if (cl.client_fqdn_option_present /* "Client FQDN" option was present in the packet we received from client */
			&&
		 ( !hp->flags.client_fqdn		/* bootptab contains no 'cf' tag for this client */
			||
		   (hp->flags.client_fqdn && hp->client_fqdn) /* bootptab contains 'cf' tag for this client and its non-zero */
		 )
		) {
		/* Easiest to just use our existing do_req_opts(), pretending that we have a Parameter Request List
		   asking for this option.
		   request_client_fqdn_option was init'd in a header to a trivial parameter request list value 
		   that includes just TAG_CLIENT_FQDN.
		*/
		if (rc=do_req_opts(hp, (u_char *) &request_client_fqdn_option, (u_char *) rpkt, &opt, &sent_options)) {
			report(LOG_ERR,"  do_req_opts() returned %d, will not send DHCPOFFER to Client ID %s", rc, cliid_btoa(hp->cl));
			free_client_info(&cl);
			reuse_addr(hp);
			return;
		}
	}

	/* The bootptab may contain other option values for this client.  */
	if(hp->flags.not_rq) {
		/* bootptab entry has the NR tag, saying that our DHCP replies should include any values that appear
		   in the bootptab entry, even if they were "Not Requested" by the client in a Parm Request option and
		   are not values we always return */
		if (rc=do_opts(hp, (u_char *) rpkt, &opt, &sent_options)) { 
			report(LOG_ERR,"  do_opts() returned %d, will not send DHCPOFFER to Client ID %s", rc, cliid_btoa(hp->cl));
			free_client_info(&cl);
			reuse_addr(hp);
			return;
		}
	}

	insert_end_opt(rpkt,&opt);

	if (debug > 9)
		report(LOG_INFO,"  Offer is good for %d seconds (%s)", TIME_OFFSET, cctime(hp->expire));

	if (debug > 12)
		report(LOG_INFO,"  Sending DHCPOFFER for %s (max_len=%d, bytes left=%d)" ,inet_ntoa(hp->iaddr),opt.len,opt.len-opt.cur);
	else if (debug > 3)
		report(LOG_INFO,"  Sending DHCPOFFER for %s" ,inet_ntoa(hp->iaddr));
	offers_sent++;
	len=300;
	if(opt.cur<VEND_OFFSET)
		len=cl.max_len;
	if(opt.cur>len)
		len=opt.cur;

	if (delay_reply_dhcpoffer)
		delay_usec = delay_reply_dhcpoffer;
	else if (delay_reply_default)
		delay_usec = delay_reply_default;
	
	send_dhcp_reply(rpkt,len,dest,delay_usec);

	if  (log_assignments>2) 
		report_assignment(RACATEGORY_OFFERRED, 1, hp->flags.dynamic, inet_ntoa(hp->iaddr), hp->hostname->string, &cl);

	free_client_info(&cl);
	/* note we do not update_lastbind() for offers */
}


void dhcp_request(pkt)
struct bootp *pkt;
{
	struct client_info cl;
	struct host *hp,*hp2,*hp3,*t;
	int len;
	unsigned int32 dest; /* network byte order */
	struct opts opt;
	struct flag sent_options;
	unsigned iphashcode,hwhashcode,cablehashcode,rc;
	int32 bootsize=0;
	int registered, exists, roaming, deny_dynamic =0;
	struct cable *c;
	char expiretime[MAXSTRINGLEN];
	char str1[2*MAXSTRINGLEN], str2[MAXSTRINGLEN];
	boolean assigning=FALSE;		/* the ACK is an addr assignment for reporting purposes */
	boolean renewing=FALSE;			/* the ACK is an addr renewal for reporting purposes */
	unsigned int delay_usec = 0;

	bzero(&cl,sizeof(cl));
	bzero(&sent_options,sizeof(sent_options));
	cl.max_len=576;	/* for testing overload functions, temporarily reduce this, e.g. to 300 */
	hp2=NULL;

	if(debug>1)
		report(LOG_INFO," processing DHCPREQUEST");
	requests_received++;
	rc=dhcp_opts(pkt,&cl);

	/* we don't need msg here, dhcp_opts() already produces one
	if(!cl.cliid)
		report(LOG_ERR,"  couldn't determine client identifier, ignoring, dhcprequest");
	*/
	if(rc){
		free_client_info(&cl);
		return;
	}

	if (compact_messages && debug) {
		char giaddrbuf[MAX_INETADDR_STRING_LEN];
		char iaddrbuf[MAX_INETADDR_STRING_LEN*2+1];
		int ciaddr_length;

		strncpy(giaddrbuf, inet_ntoa(pkt->bp_giaddr), MAX_INETADDR_STRING_LEN-1);
		if (pkt->bp_ciaddr.s_addr == 0) { /* report just Requested IP Addr */
			strncpy(iaddrbuf, inet_ntoa(cl.req_addr), MAX_INETADDR_STRING_LEN-1);
		} else {
			/* report ciaddr... */
			strncpy(iaddrbuf, inet_ntoa(pkt->bp_ciaddr), MAX_INETADDR_STRING_LEN-1);
			if (cl.req_addr.s_addr != 0) { /* ...and report Requested IP Addr if present */
				ciaddr_length = strlen(iaddrbuf);
				iaddrbuf[ciaddr_length] = '/';
				strncpy(iaddrbuf+ciaddr_length+1, inet_ntoa(cl.req_addr), MAX_INETADDR_STRING_LEN-1);
			}
		}
		report(LOG_INFO, "DHCPREQUEST  %s [%s]: request %s", haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), giaddrbuf, iaddrbuf);
	}

	if(cl.msg)free(cl.msg);

	if(debug>1)
		report_cliid(LOG_INFO,cl.cliid);

	cl.clhashcode=hash_HashFunction(cl.cliid->data,cl.cliid->length);

	if (allow_serveripopts_in_rebind) {
		/* Windows 95 can generate an invalid DHCPREQUEST packet when in the REBINDING state.  The packet
	   	contains a Server IP Address Option, which only appears in DHCPREQUEST packets sent by clients
	   	in SELECTING state, according to spec.  Our implementation uses the presence of the Server IP Address
	   	option to decide the client must be in SELECTING state.  If you enable the 
	   	"Allow 'Server IP Address' Option in RENEWAL/REBINDING States" feature, then if we see an invalid DHCPREQUEST
	   	packet that appears to have this problem, we'll try to "fix" the packet.  It is possible that
	   	enabling this feature could have unanticpated side effects, and I haven't tested it extensively,
	   	so use it with caution.  
	   	We characterize these packets as those which:
	      	1) have a non-zero Server IP Address Option AND
	      	2) do not have a Requested IP Address Option AND
	      	3) have a non-zero ciaddr header field AND
	      	4) we presently have a binding for the Client ID AND
		  	5) the binding for this Client ID is for same IP Address as the ciaddr 
	   	Criteria 1 is the inappropriately-included characterstic described above.
	   	Criteria 2&3 are what would tell us the client is RENEWING/REBINDING (instead of INIT-REBOOT) if Item 1 wasn't there.
	   	Criteria 4&5 are to try to limit the incorrect decisions we could make in the event we receive an erroneous (by
	      	definition) packet which meets the other criteria.   (Since once we treating the client as RENEWING/REBINDING
	      	we may very well send a DHCPNAK, we want to avoid being too liberal in deciding packets belong in this
	      	category, especially as we may be only one of several DHCP servers.)
	   	Note that a side effect of including criteria 4&5 is that this feature will not be able to help the
	      	client who is bound to another DHCP server and is trying to rebind.  As a result, this feature
	      	will only help the client who is bound to us and is trying to rebind (presumably because the client
	      	did not succeed in renewing with us earlier).
	  	 
	   	We "fix" these packets by ignoring the Server IP Address Option.
		*/ 
		if ((cl.serv_addr.s_addr!=0)  && (cl.req_addr.s_addr==0) && (pkt->bp_ciaddr.s_addr!=0)) { 
			/* locate client in current bindings */
			hp =(struct host *)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp,cl.cliid);
			if (hp && (hp->iaddr.s_addr == pkt->bp_ciaddr.s_addr)) {
				if (debug > 2)
					report(LOG_INFO, "  Allow 'Server IP Address' option in RENEWAL/REBINDING States Feature: ignoring 'Server IP Option'");
				cl.serv_addr.s_addr=0; /* discard Server IP Address Option from our parsed notion of the packet */
			}
		}
	}

	if (cl.serv_addr.s_addr!=0) { /* client is SELECTING */
		if (debug > 3)
			report(LOG_INFO, "  client is in SELECTING state, Server Identifier option is %s", inet_ntoa(cl.serv_addr));
		if (!is_my_ip_addr(& cl.serv_addr)) {
			if (known_serverids_hshtbl_inuse && !is_known_server_identifier(&cl.serv_addr)) {
				/* inet_ntoa() uses static storage, so must build message */
				char saddrbuf[MAX_INETADDR_STRING_LEN];
				char giaddrbuf[MAX_INETADDR_STRING_LEN];
				char rqaddrbuf[MAX_INETADDR_STRING_LEN];
				strncpy(saddrbuf, inet_ntoa(cl.serv_addr), MAX_INETADDR_STRING_LEN-1);
				strncpy(giaddrbuf, inet_ntoa(pkt->bp_giaddr), MAX_INETADDR_STRING_LEN-1);
				strncpy(rqaddrbuf, inet_ntoa(cl.req_addr), MAX_INETADDR_STRING_LEN-1);
				report(LOG_WARNING, "  saw unknown Server Identifier %s from SELECTING client (cliid=%s, giaddr=%s, Requested IP Address=%s)", saddrbuf, cliid_btoa(cl.cliid), giaddrbuf, rqaddrbuf);
			}
			if (debug > 3)
				report(LOG_INFO, "  server identifier not me, ignoring");
			/* should we also see if we have this cliid bound and if so, unbind? */
			free_client_info(&cl);
			return;
		} else if (debug > 3)
			report(LOG_INFO, "  server identifier is me");

		if (cl.req_addr.s_addr==0) {
			if (debug > 2)
				report(LOG_ERR, "  dhcp client appears to be SELECTING, but omitted RequestedIPAddr Option, ignoring (cliid=%s)", cliid_btoa(cl.cliid));
			free_client_info(&cl);
			return;
		}



		/* locate client in current bindings */
		hp =(struct host *)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp,cl.cliid);

		if (hp) { /* we found client in our current bindings */
			if (debug > 3)
				report(LOG_INFO, "  found current lease or offer for cliid");

			if (hp->flags.deny_client) {
				if (debug > 3) {
					report(LOG_INFO, "  client id/haddr found in bootptab with deny_client tag, NAKing client");
					report(LOG_INFO, "  freeing binding to IP address %s", inet_ntoa(hp->iaddr));
				}
				reuse_addr(hp);
				hp=NULL;
				send_dhcpnack(pkt,cl.cliid,"DHCP service denied",cl.max_len,cl.cliid_explicit);
				free_client_info(&cl);
				return;
			}

			if (!hp->flags.expire) {
				if (debug > 3 || log_expirations) {
					report(LOG_INFO, "  current binding to IP address %s was assigned via BootP (not DHCP), will not use", inet_ntoa(hp->iaddr));
					report(LOG_INFO, "  freeing binding");
				}
				reuse_addr(hp);
				hp=NULL;
				send_dhcpnack(pkt,cl.cliid,"Lease wasn't offerred",cl.max_len,cl.cliid_explicit);
				free_client_info(&cl);
				return;
			}
			
			if (hp->expire <= now) {
				if (debug > 3 || log_expirations) {
					report(LOG_INFO, "  current binding to IP address %s already expired (awaiting garbage collection)", inet_ntoa(hp->iaddr));
					report(LOG_INFO, "  freeing binding");
				}
				reuse_addr(hp);
				hp=NULL;
				send_dhcpnack(pkt,cl.cliid,"Lease or offer already expired",cl.max_len,cl.cliid_explicit);
				free_client_info(&cl);
				return;
			}

			/* make sure the binding we found is for the same IP address as the RequestedIPAddr Option  */
			if (hp->iaddr.s_addr != cl.req_addr.s_addr) {
				if (debug > 3 || log_expirations) {
                    /* must build message up, since inet_ntoa shouldn't be called twice in same expr */
                    sprintf(str1, "  client's current binding in boundhshtbl is for %s, ", inet_ntoa(hp->iaddr));
                    sprintf(str2, "but client's RequestedIPAddr=%s, will not use", inet_ntoa(cl.req_addr));
                    strcat(str1,str2);
                    report(LOG_INFO, str1);
					report(LOG_INFO, "  freeing binding");
				}
				reuse_addr(hp);
				hp=NULL;
				send_dhcpnack(pkt,cl.cliid,"Lease wasn't offerred",cl.max_len,cl.cliid_explicit);
				free_client_info(&cl);
				return;
			}

			/* make sure we have this in the ipboundhshtbl as well, and it matches */
			iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
			hp2 =(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr));
			if (!hp2) { /* this should not happen */
				report(LOG_ERR, "  dhcp_request(): SELECTING, cliid (%s) found in boundhshtbl with same ReqIPAddr (%s) , but ipboundhshtbl has no entry for this IP!", cliid_btoa(cl.cliid), inet_ntoa(hp->iaddr));
				report(LOG_ERR, "freeing binding");
				reuse_addr(hp);
				free_client_info(&cl);
				return;
			}
			if (!hp2->flags.cliid) {
				report(LOG_ERR, "  dhcp_request(): SELECTING, cliid (%s) found in boundhshtbl with same ReqIPAddr (%s), but entry in ipboundhshtbl has no cliid!", cliid_btoa(cl.cliid), inet_ntoa(hp->iaddr));
				report(LOG_ERR, "freeing both bindings");
				reuse_addr(hp2);
				reuse_addr(hp);
				free_client_info(&cl);
				return;
			}
			if (!BINDATA_EQ(hp2->cl, cl.cliid)) {
				report(LOG_ERR, "  dhcp_request(): SELECTING, cliid (%s) found in boundhshtbl with same ReqIPAddr (%s)...", cliid_btoa(cl.cliid), inet_ntoa(hp->iaddr));
				report(LOG_ERR, "but entry in ipboundhshtbl for this IP has different cliid (%s)", cliid_btoa(hp2->cl));
				report(LOG_ERR, "freeing both bindings");
				reuse_addr(hp2);
				reuse_addr(hp);
				free_client_info(&cl);
				return;
			}	

			exists=TRUE; /* was found in boundhshtbl and ipboundhshtbl */

			assigning=TRUE;		/* we want to report a client who SELECTs our OFFER */

			/* we'll not do any further checks on validity of request vs. lease */
			/* Fall through to sending ACK below.  At this point: 
			   hp points to a current binding for the IP address we wish to grant, and exists==TRUE */

			if (debug > 3) 
				report(LOG_INFO, "  skipping any further checks");

			/* why not do additional checks, to catch really screwed up clients that
			  pretend to be SELECTING, but aren't? */

		} else { /* client is confused; we did not OFFER */
			if (debug > 2)
				report(LOG_ERR, "  dhcp_request(): no current lease or offer for SELECTING client (requesting %s), sending NAK (cliid=%s)", inet_ntoa(cl.req_addr), cliid_btoa(cl.cliid));
			/* don't know if we should just ignore, or NAK */
			send_dhcpnack(pkt,cl.cliid,"Lease wasn't offerred",cl.max_len,cl.cliid_explicit);
			free_client_info(&cl);
			return;
		}

	} else { /* cl.serv_addr.s_addr==0, so client is not SELECTING */

		if (cl.req_addr.s_addr!=0) { /* INIT-REBOOT */
			dhcprequest_init_reboot:
			if (debug > 3)
				report(LOG_INFO, "  client is in INIT-REBOOT state, Requested IP Address option is %s", inet_ntoa(cl.req_addr));

			hp =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);
			if (hp)
				deny_dynamic=hp->flags.deny_client_dynamic; /* we may need to know later if client has :dd: tag in bootptab */

			/* Make sure cliid isn't one to whom we're supposed to deny service */
			if (hp && hp->flags.deny_client) {
				if (debug > 3)
					report(LOG_INFO, "  client id/haddr found in bootptab with deny_client tag");
				/* do we presently have a binding for the client (so we'll assume the client is bound to us)? */
				hp=(struct host*)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp ,cl.cliid);
				if (hp) {
					if (debug > 3) {
						report(LOG_INFO, "  cliid presently bound to %s", inet_ntoa(hp->iaddr));
						report(LOG_INFO, "  freeing binding, and sending NAK");
					}
					reuse_addr(hp);
					send_dhcpnack(pkt,cl.cliid,"DHCP service denied",cl.max_len,cl.cliid_explicit);
				} else {
					if (debug > 3)
						report(LOG_INFO, "  no binding found for cliid, ignoring client");
				}
				free_client_info(&cl);
				return;
			}

			/* make sure requested ip is on appropriate subnet */
			if (!check_net(&(pkt->bp_giaddr),&(cl.req_addr))) {
				if (debug > 1) {
					/* must build message up, since inet_ntoa shouldn't be called twice in same expr */
					sprintf(str1, "  requested IP address %s ", inet_ntoa(cl.req_addr));
					sprintf(str2, "incorrect for gateway %s, sending NAK", inet_ntoa(pkt->bp_giaddr));
					strcat(str1,str2);
					report(LOG_INFO, str1);
				}
				send_dhcpnack(pkt,cl.cliid,"Requested IP address not appropriate for your subnet",cl.max_len,cl.cliid_explicit);
				free_client_info(&cl);
				return;
			}

			/* lookup requested IP in bootptab */
			iphashcode=hash_HashFunction((u_char*)&cl.req_addr.s_addr,4);
			hp=(struct host *)hash_Lookup(iphashtable,iphashcode,ipcmp,(hash_datum*)&cl.req_addr.s_addr);

			/* ignore if requested IP not in our bootptab.  We may not send NAK as client is in INIT-REBOOT,
				so REQUEST was broadcast and may be for OK by another server */
			if (!hp) {
				if (debug > 3)
					report(LOG_INFO, "  requested IP address %s not in bootptab, ignoring", inet_ntoa(cl.req_addr));
				free_client_info(&cl);
				return;
			}

			if(!hp->flags.dynamic) { /* bootptab is for a static ip address, so make sure bootptab cliid or haddr match requester's */
				if(hp->flags.cliid) { /* bootptab entry has a cliid */
					if(!CLIID_EQ(hp->cliid,cl.cliid)) { 
						if (debug > 3) 
							report(LOG_INFO, "  client identifier for static bootptab %s entry does not match, access denied", inet_ntoa(hp->iaddr));
						send_dhcpnack(pkt,cl.cliid,"Client Identifier doesn't match yours, access denied",cl.max_len,cl.cliid_explicit);
						free_client_info(&cl);
						return;
					} 
				} else if (hp->flags.haddr && hp->flags.htype) { /* bootptab entry has a haddr */
					if((hp->htype!=cl.cliid->data[0]) || ((cl.cliid->length-1)!=haddrlength(hp->htype))
						|| bcmp(hp->haddr,cl.cliid->data+1,cl.cliid->length-1)){ 
						if (debug > 3) 
							report(LOG_INFO, "  hardware address for static bootptab entry %s doesn't match, access denied", inet_ntoa(hp->iaddr));
						send_dhcpnack(pkt,cl.cliid,"Hardware Address doesn't match yours, access denied",cl.max_len,cl.cliid_explicit);
						free_client_info(&cl);
						return;
					}
				} else report(LOG_ERR,"  bootptab static entry for %s has no cliid or haddr, huh?", inet_ntoa(hp->iaddr));
			} else { /* bootptab entry is for a dynamic address */
				if (deny_dynamic) {
					/* We NAK him regardless of whether the IP address is a member of a dynamic pool we're handling,
					   as the :dd: tag applies to this client's use of any dynamic IP address.
					*/
					if (debug > 3)
						report(LOG_INFO, "  client identifier explicitly disallowed dynamic IP addresses, sending NAK");
					send_dhcpnack(pkt,cl.cliid,"Dynamic address disallowed for this client",cl.max_len,cl.cliid_explicit);
					free_client_info(&cl);
					return;
				}
				/* Make sure the dynamic address is a member of a dynamic pool we're handling */
				if (!hp->flags.cable) {
					if (debug > 3)
						report(LOG_INFO, "  requested IP address %s is known, but is dynamic and not a member of a defined pool, ignoring", inet_ntoa(hp->iaddr));
					/* We do *not* NAK, because we're not authoritative for this dynamic IP address.  It's true
					   that it is in our bootptab, but since it is dynamic and is not a member of any defined pool,
					   we could never assign it to anyone.  So we behave the same as if the requested IP address was
					   not in our bootptab at all: we ignore client. */
					free_client_info(&cl);
					return;
				}
				/* Make sure access policy allows this client */
				/* Note we cannot use use_addr() since it will return FALSE when proposed IP address
				   is not free, even though the reason is it is not free is it is bound to us!  We'll have
				   to go down a level and use access_network(), relying on the fact that we've already
				   taken care of doing check_net().  */
				cablehashcode = hash_HashFunction((u_char*)hp->cable->string, strlen(hp->cable->string));
    			c = (struct cable *) hash_Lookup(cablehshtbl, cablehashcode, cablelcmp, hp->cable->string);
    			if (c) { /* only test if we have an access policy defined for this cable */
					hp2 =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);
					if (hp2) {
						if (hp2->flags.roaming)
							rc=access_network(c->code,ROAMING_ACCESS|REG_ACCESS|DHCP_ACCESS);
						else
							rc=access_network(c->code,REG_ACCESS|DHCP_ACCESS);
					} else
						rc=access_network(c->code,UNREG_ACCESS|DHCP_ACCESS);
					if (!rc) {
						if (debug > 3)
							report(LOG_INFO, "  dynamic IP address disallowed by access policy");
						send_dhcpnack(pkt,cl.cliid,"Dynamic address disallowed by access policy",cl.max_len,cl.cliid_explicit);
						free_client_info(&cl);
						return;
					}
					/* Since we were unable to use use_addr() above, we failed to remove this IP address from the
					   pool of available dynamic addresses for this cable.  So now we have to deal with this.
					   If the address is already bound, we don't need to do anything, since when it was bound it was
					   removed from the pool of available dynamic addresses. */
					iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
					hp2 =(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr));
					if (!hp2) {
						/* We call with force=1, so the value of gwaddr is ignored, we pass any struct in_addr there just
						   for type checking.  While the flag check is not of interest to us here (already taken care of
						   earlier when access_network called), we have to provide a flag, so we'll go with the most permissive
						   possible version.    We also set the flag arg to include ROAMING and REG and UNREG here (giving
						   the client all relevant privs) since we've already performed the policy check with
						   a call to access_network() earlier.
						   
						   We ignore the return value, since all we're after is the side-effect of
						   removing hp->iaddr from the appropriate cable's list of avail dynamic ip addresses. */
						use_addr(hp, pkt->bp_giaddr, ROAMING_ACCESS|REG_ACCESS|UNREG_ACCESS|DHCP_ACCESS, 1);
					}
				}
				/* If bootptab also has a static IP address assigned to this client, and it is appropriate
				   for the network to which the client is presently attached, then do NOT allow the client
				   to use the dynamic address he has requested.  Although the spec says we should let the client
				   keep using the dynamic address, in reality, the fact that the bootptab was updated to reflect
				   that the client's "home" is now this network means that we should really have the client
				   use his statically-assigned IP address on that network.  Sending a NAK to the client will force
				   him from INIT-REBOOT back to INIT, at which point we'll offer him the static address.  Note that
				   this is even true if the requested dynamic address is presently bound to this client...and when
				   we see the DHCPDISCOVER from the client, we'll also free the binding.  */
				/* Look for clientid in bootptab.  (Remember that if incoming packet doesn't specify a 
				   DHCP Client ID option, we've computed its ClientID by concatenating its htype with its haddr.) */
				hp2 =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);
#ifdef RETRY_HADDR_AS_CLIID
				/* I've ifdef'd out this because pretending to find a cliid match for clients when there's a mismatch 
				   between the packet's Cliid and the bootptab's cliid can cause problems later as we start storing
				   the inconsistent values in our hashtables.  Besides, we probably shouldn't be pretending to find
				   a match in the first place -- errors in the cliid specified by the bootptab or the client
				   should be corrected, not ignored.
				*/
				if (! hp2) { 
					/* We didn't find ClientID in bootptab's clhashtable using ClientID as key.
					   (Recall that clhashtable contains bootptab entries keyed by cliid (if 'cl' explicitly
					   specified in bootptab entry) else bootptab entries keyed by haddr.)
					   Perhaps the incoming packet contains a DHCP ClientID option with a garbage value.
					   Or perhaps the bootptab entry specifies a garbage cliid (via an incorrect 'cl' tag).
					   In either case, we may have more luck if we look up the incoming packet's haddr in
					   bootptab's hwhastable using haddr as key.
					*/
					hwhashcode=hash_HashFunction(cl.cliidhw->data+1,cl.cliidhw->length-1);
					hp2 =(struct host *)hash_Lookup(hwhashtable, hwhashcode, cliidhwlookcmp,cl.cliidhw);
				}
#endif
				if (hp2 && hp2->flags.iaddr) { /* found clientid in bootptab, and has a static ip assigned */
					/* Note that we know that the static IP addr in bootptab is not the same as the requested IP addr,
					   since we've already determined that the requested IP addr is dynamic according to bootptab. */
					/* Is the static IP addr appropriate given the client's present network? */
					if (check_net(&(pkt->bp_giaddr),&(hp2->iaddr)))  {
						/* static IP addr is ok for client's present network, so DO NOT allow client to have the dynamic addr */
						if (debug > 3)
							report(LOG_INFO, "  dynamic address disallowed since an appropriate static address (%s) is available", inet_ntoa(hp2->iaddr));
						send_dhcpnack(pkt,cl.cliid," Requested address is dynamic, but an appropriate static IP address is now available",cl.max_len,cl.cliid_explicit);
						free_client_info(&cl);
						return;
					} else { 
						/* static not ok for client's present network, so DO allow client to have the dynamic addr */
					}
				}
			}

			/* see if IP address presently bound */
			exists=FALSE;
			iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
			hp2 =(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr));
			if (hp2) { /* IP address is presently bound */
				if (hp2->flags.cliid) { /* binding has a cliid */
					 if (!CLIID_EQ(hp2->cliid,cl.cliid)) { /* make sure binding's cliid == requester's cliid */
						if (debug > 3)
							report(LOG_INFO, "  requested IP Address %s already leased to another Client ID", inet_ntoa(hp2->iaddr));
						send_dhcpnack(pkt,cl.cliid,"Requested IP Address leased to someone else",cl.max_len,cl.cliid_explicit);
						free_client_info(&cl);
						return;
					}
				} else {
					report(LOG_ERR, "  IP address %s presently bound, but binding has no cliid!", inet_ntoa(hp2->iaddr));
					report(LOG_ERR, "  freeing binding");
					reuse_addr(hp);
					send_dhcpnack(pkt,cl.cliid,"Requested IP Address leased to unknown client",cl.max_len,cl.cliid_explicit);
					free_client_info(&cl);
					return;
				}
				/* at this point we know that that the RequestedIPAddr is presently bound to this cliid */
				if (!hp2->flags.expire) {
					if (debug > 3 || log_expirations) {
						report(LOG_INFO, "  current binding to IP address %s was assigned via BootP (not DHCP), will not re-use", inet_ntoa(hp2->iaddr));
						report(LOG_INFO, "  freeing binding");
					}
					reuse_addr(hp);
					send_dhcpnack(pkt,cl.cliid,"Requested IP Address was assigned via BootP (not DHCP)",cl.max_len,cl.cliid_explicit);
					free_client_info(&cl);
					return;
				}
				/* It is possible that hp2->expire <= now, meaning that the current binding has already
				   expired.  (That can happen if it expired by garbage collection hasn't happend yet to remove it.
				   Let's be safe and discard that binding (I'm not sure it is safe to extend it if it happens
				   to be just an OFFER that was never accepted, instead of a real lease), then start processing
				   the packet again....so we won't find the expired binding. */
				if (hp2->expire <= now) {
					if (debug > 3 || log_expirations) {
						report(LOG_INFO, "  current binding to IP address %s has expired (awaiting garbage collection)", inet_ntoa(hp2->iaddr));
						report(LOG_INFO, "  freeing binding");
					}
					reuse_addr(hp2);
					hp2=NULL;
						if (debug > 3)
							report(LOG_INFO, "  restarting processing of this packet");
					goto dhcprequest_init_reboot;
				}
			}

			/* see if cliid is presently bound */
			hp3 =(struct host *)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp,cl.cliid);
			if (hp3) { /* cliid found in boundhshtbl */
				if (hp2) { /* requested IP addr found in ipboundhshtbl */
					/* make sure the IP address in the bound cliid record matches the requested IP addr */
					if (hp3->iaddr.s_addr == hp->iaddr.s_addr)  {
						/* this is normal, all matches */
						exists=TRUE;
					} else {
						/* not sure what this means.  We found cliid found, and requested IP bound, but cliid was bound
					   to a different IP.  Unbind cliid  */
						report(LOG_ERR, "  dhcp_request(): INIT-REBOOT: ReqIP (%s) found in ipboundhshtbl with correct cliid...", cl.req_addr);
						report_cliid(LOG_ERR, cl.cliid);
						report(LOG_ERR, "and cliid found in boundhshtbl, but bound to *DIFFERENT* IP address (%s)!", inet_ntoa(hp3->iaddr));
						report(LOG_ERR, "freeing both bindings");
						reuse_addr(hp3);
						reuse_addr(hp2);
						send_dhcpnack(pkt,cl.cliid,"Error",cl.max_len,cl.cliid_explicit);
						free_client_info(&cl);
						return;
					}
				} else { /* requested IP addr not found in ipboundhshtbl */
					/* We found the cliid bound, but the requested IP was not bound. 
					   That implies that the cliid is not presently bound to the requested IP.  Given that the client is
					   asking for a different IP address, he's clearly given up his old binding.  So we'll unbind the cliid. */
					if (debug > 3 || log_expirations) {
						report(LOG_INFO, "  dhcp_request(): cliid presently bound to %s...",inet_ntoa(hp3->iaddr));
						report(LOG_INFO, "  dhcp_request(): but is requesting %s; will free old binding", inet_ntoa(cl.req_addr));
					}
					reuse_addr(hp3);
					free_client_info(&cl);
					/* We ignore the client, which forces him to timeout and resend the request, or fall back to INIT.  Either way,
					   he'll send another packet.  When we get that one, we won't find the old binding (we just removed it),
					   so we'll be able to handle it appropriately.  We do *not* NAK him here, since it's possible that what
					   what he's asking for is OK. */
					return;
				}
			} else { /* cliid not found in boundhshtbl */
				if (hp2) { /* requested IP addr found in ipboundhshtbl */
					/* this should never happen, since we already know that the ipboundhshtbl record specifies this cliid */
					/* not much we can do here, other than log the error */
					report(LOG_ERR, "  dhcp_request(): INIT-REBOOT: ReqIp (%s), found in ipboundhshtbl with correct cliid...", inet_ntoa(cl.req_addr));
					report_cliid(LOG_ERR, cl.cliid);
					report(LOG_ERR, "but cliid *NOT* found in boundhshtbl!");
					report(LOG_ERR, "freeing binding");
					reuse_addr(hp2);
					send_dhcpnack(pkt,cl.cliid,"Error",cl.max_len,cl.cliid_explicit);
					free_client_info(&cl);
					return;
				} else { /* requested IP addr not found in ipboundhshtbl */
					/* this is normal; neither cliid nor req ip was bound; exists still == FALSE */
				}
			}

			if (exists) {
				/* set hp to point to entry in boundhashtables */
				hp = hp3;
			} /* else leave hp pointing to entry in bootptab */

			assigning=TRUE; /* we want to report INIT-REBOOT assignments as ASSIGNING */

			/* all looks good, fall through to sending ACK below.  At this point: 
			   hp points to a current binding for the IP address we wish to grant, and exists==TRUE,  or
			   hp points to a bootptab entry for the IP address we wish to grant,  and exists=FALSE */

			
		} /* INIT-REBOOT */

		else { /* RENEWING/REBINDING/BOUND/confused */
			if (debug > 4) {
				report(LOG_INFO, "  Neither ServerAddr option nor RequestedIPAddr option specified,");
				report(LOG_INFO, "  ...so client must be RENEWING, REBINDING, BOUND (or confused)");
			}
			if (pkt->bp_ciaddr.s_addr==0) {
				if (debug > 2)
					report(LOG_ERR, "  client is RENEWING or REBINDING, but ciaddr==0.0.0.0, ignoring (cliid=%s)", cliid_btoa(cl.cliid));
				free_client_info(&cl);
				return;
			}
			/* At this point:
					Client is RENEWING if ((giaddr==0 && pktDstIP==myIP))
					Client is REBINDING if ((giaddr!=0) || (pktDstIP==broadcast))
					else client is in unknown state
			   However, we don't have access to pktDstIP here, (we're getting datagrams from the socket,
				not raw packets) so we can't tell for sure which case applies.
			   So we will lump them all together.   The effects are:
					Some RENEWING clients that are wrong would normally get NAKed; here we'll be silent,
					   so they get to keep using an inappropriate lease until it expires.
					Clients in unknown state will be treated as RENEWING or REBINDING.
					Our debug messages won't be as detailed as they could be.
			*/
			     
			if (pkt->bp_giaddr.s_addr!=0)  {
				if (debug > 4)
					report(LOG_INFO, "  giaddr!=0, so client must be REBINDING", inet_ntoa(pkt->bp_giaddr));
			} else if (debug > 4) {
				report(LOG_INFO, "  giaddr==0, so client must be RENEWING,");
				report(LOG_INFO, "  ...or is on local network and is REBINDING (or is confused)");
			}

			/* If we have a non-zero giaddr to work with, make sure requested address is appropriate. */
			/* Note that as per spec, if client is RENEWING, we cannot perform this test
			      as giaddr will always be 0.
			   Note also that if client is REBINDING and is on a directly-attached network,
				  we cannot perform this test as giaddr will be 0. */
			if (pkt->bp_giaddr.s_addr) {
				if (!check_net(&(pkt->bp_giaddr),&(pkt->bp_ciaddr))) {
					if (debug > 2) {
                    	/* must build message up, since inet_ntoa shouldn't be called twice in same expr */
                    	sprintf(str1, "  requested IP address (ciaddr) %s ", inet_ntoa(pkt->bp_ciaddr));
                    	sprintf(str2, "incorrect for gateway %s, sending NAK", inet_ntoa(pkt->bp_giaddr));
                    	strcat(str1,str2);
                    	report(LOG_INFO, str1);
					}
					send_dhcpnack(pkt,cl.cliid,"Requested IP address not appropriate for your current subnet",cl.max_len,cl.cliid_explicit);
					free_client_info(&cl);
					return;
				}
			}

			hp =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);
			if (hp)
				deny_dynamic = hp->flags.deny_client_dynamic; /* we may need to know later if client has :dd: tag in bootptab */

			/* Make sure cliid isn't one to whom we're supposed to deny service */
			if (hp && hp->flags.deny_client) {
				if (debug > 3)
					report(LOG_INFO, "  client id/haddr found in bootptab with deny_client tag");
				/* do we presently have a binding for the client (so we'll assume the client is bound to us)? */
				hp=(struct host*)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp ,cl.cliid);
				if (hp) {
					if (debug > 3) {
						report(LOG_INFO, "  cliid presently bound to %s", inet_ntoa(hp->iaddr));
						report(LOG_INFO, "  sending NAK (can't free binding as client might not return to INIT until expiration)");
					}
					send_dhcpnack(pkt,cl.cliid,"DHCP service denied",cl.max_len,cl.cliid_explicit);
					/* We'd also like to free the binding by calling 'reuse_addr(hp)', but we cannot because
					   the NAK might not force the client back to INIT; the client might keep using the old
					   lease until it expires.  So we must keep the binding until it expires normally, to 
					   avoid re-assigning the IP address while it may still be in-use by the client.
					*/
				} else {
					if (debug > 3)
						report(LOG_INFO, "  no binding found for cliid, ignoring client");
				}
				free_client_info(&cl);
				return;
			}

            /* make sure ciaddr is in bootptab */
        	iphashcode=hash_HashFunction((u_char*)&pkt->bp_ciaddr.s_addr,4);
	        hp=(struct host *)hash_Lookup(iphashtable,iphashcode,ipcmp,(hash_datum*)&(pkt->bp_ciaddr.s_addr));
            if (!hp) {
				if (debug > 3)
					report(LOG_INFO, "  client's ciaddr %s is not in bootptab", inet_ntoa(pkt->bp_ciaddr));
				if (lone_dhcp_server) {
					/* As there's no other DHCP server that could have provided this IP address, assume
					   that there's no value in being silent, since no other DHCP server will respond 
					   helpfully to the client.  So we instead NAK, to try to force the client back to INIT.  
					   Note that many clients will ignore this NAK, and continue to use whatever time they believe 
					   is remaining on the lease.
					*/
					if (debug > 3)
						report(LOG_INFO, "  sending NAK since Lone DHCP Server feature is enabled");
                   	send_dhcpnack(pkt, cl.cliid, "Requested IP address is not in bootptab, renewal denied",cl.max_len,cl.cliid_explicit);
                	free_client_info(&cl);
                	return;
				} else {
					/* if we knew for SURE the client was RENEWING, we'd like to NAK, but this
				   	client may be REBINDING, so the best we can do is be silent.  The RENEWING
				   	clients will get to keep using their invalid leases until they expire at the client end,
						unfortunately, but at least we won't break REBINDING clients. */
					if (debug > 3)
						report(LOG_INFO, "  Want to NAK if client is RENEWING, but client may be REBINDING, so will ignore");
                	free_client_info(&cl);
                	return;
				}
			}

            if(!hp->flags.dynamic) { /* bootptab entry is for a static ip address, so make sure bootptab cliid or haddr match requester's */
                if(hp->flags.cliid) { /* bootptab entry has a cliid */
					if (!CLIID_EQ(hp->cliid,cl.cliid)) { 
                    	if (debug > 3) 
							report(LOG_INFO, "  client identifier for %s bootptab static entry doesn't match, access denied", inet_ntoa(hp->iaddr));
                    	send_dhcpnack(pkt,cl.cliid,"Client ID for bootptab static entry doesn't match yours, access denied",cl.max_len,cl.cliid_explicit);
                    	free_client_info(&cl);
                    	return;
					}
                } else if(hp->flags.haddr && hp->flags.htype) { /* bootptab entry has a haddr */
					if((hp->htype!=cl.cliid->data[0]) || ((cl.cliid->length-1)!=haddrlength(hp->htype))
						|| bcmp(hp->haddr,cl.cliid->data+1,cl.cliid->length-1)){
						if (debug > 3) 
							report(LOG_INFO, "  hardware address for %s bootptab static entry doesn't match, access denied", inet_ntoa(hp->iaddr));
						send_dhcpnack(pkt,cl.cliid,"Hardware Address for bootptab static entry doesn't match yours, access denied",cl.max_len,cl.cliid_explicit);
						free_client_info(&cl);
						return;
					}
				} else report(LOG_ERR, "  Bootptab static entry for %s has no cliid or haddr, huh?", inet_ntoa(hp->iaddr));

            } else { /* bootptab entry is for dynamic address */ 
				if (deny_dynamic) {
					/* We NAK him regardless of whether the IP address is a member of a dynamic pool we're handling,
					   as the :dd: tag applies to this client's use of any dynamic IP address.
					*/
					if (debug > 3)
						report(LOG_INFO, "  client identifier explicitly disallowed dynamic IP addresses, sending NAK");
					send_dhcpnack(pkt,cl.cliid,"Dynamic address disallowed for this client",cl.max_len,cl.cliid_explicit);
					free_client_info(&cl);
					return;
				}
				/* Make sure the dynamic address is a member of a dynamic pool we're handling */
				if (!hp->flags.cable) {
					/* We're not authoritative for this dynamic IP address .  It's true that it is in our
					   our bootptab, but since it is dynamic and is not a member of any defined pool, we could 
						never assign it to anyone.  So we behave the same as if the ciaddr was not in our bootptab at all. */
					if (debug > 3)
						report(LOG_INFO, "  client ciaddr %s is known, but is dynamic and not a member of a defined pool", inet_ntoa(hp->iaddr));
					if (lone_dhcp_server) {
						/* As there's no other DHCP server that could have provided this IP address, assume
						   that there's no value in being silent, since no other DHCP server will respond 
					  	   helpfully to the client.  So we instead NAK, to try to force the client back to INIT.  
						   Note that many clients will ignore this NAK, and continue to use whatever time they believe 
						   is remaining on the lease.
						*/
						if (debug > 3)
							report(LOG_INFO, "  sending NAK since Lone DHCP Server feature is enabled");
                    	send_dhcpnack(pkt, cl.cliid, "Requested IP address is not available for assignment, renewal denied",cl.max_len,cl.cliid_explicit);
                		free_client_info(&cl);
                		return;
					} else {
						if (debug > 3)
							report(LOG_INFO, "  Want to NAK if client is RENEWING, but client may be REBINDING, so will ignore");
						free_client_info(&cl);
						return;
					}
				}
				/* make sure access policy allows this client */
				/* Note we cannot use use_addr() since it will return FALSE when proposed IP address
				   is not free, even though the reason is it is not free is it is bound to us!  We'll have
				   to go down a level and use access_network(), relying on the fact that we've already
				   taken care of doing check_net(). */
				cablehashcode = hash_HashFunction((u_char*)hp->cable->string, strlen(hp->cable->string));
    			c = (struct cable *) hash_Lookup(cablehshtbl, cablehashcode, cablelcmp, hp->cable->string);
    			if (c) { /* only test if we have an access policy defined for this cable */
					hp2 =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);
					if (hp2) {
						if (hp2->flags.roaming) 
							  rc=access_network(c->code,ROAMING_ACCESS|REG_ACCESS|DHCP_ACCESS);
						else
								rc=access_network(c->code,REG_ACCESS|DHCP_ACCESS);
					} else
							rc=access_network(c->code,UNREG_ACCESS|DHCP_ACCESS);
					if(!rc) {
						if (debug > 3)
                			report(LOG_INFO, "  dynamic IP address client requested is disallowed by access policy");
                		send_dhcpnack(pkt,cl.cliid,"Dynamic IP address you requested is disallowed by access policy",cl.max_len,cl.cliid_explicit);
                		free_client_info(&cl);
                		return;
            		}
				}

				/* If the address the client is trying to renew ('ciaddr') is not among our current bindings,
				   we NAK the client.  Recall that we know the client is RENEWING or REBINDING, the requested
				   IP address is in our bootptab, the bootptab entry says its a dynamic IP address, and we also
				   know it is a member of a dynamic pool we are handling.  Since we are authoritative for this IP
				   address and know there is no lease for it, we know that the client claiming to have a lease on it
				   he wants to renew must be wrong.  Although we don't know whether the client is RENEWING or REBINDING,
				   we do know that we will never renew this lease (we don't renew unbound dynamics), nor will any other
				   DHCP server (since we're authoritative for this IP address).  So even if the client is REBINDING, he's
				   not going to get an affirmative answer from some other DHCP server.  Since we know at this point the
				   client is broken (he's using one of OUR addresses has has no lease on), we can safely NAK the client.
				   We do this in the hope that it will drive the client back to INIT (though the client may be so broken
				   that it may ignore us entirely). 
				   Note that if we do find there IS presently a lease in our current bindings for the requested IP
				   address, we don't do anything here, like check if it makes sense.  That's because we'll handle that
				   case later on (e.g. make sure current lease's cliid == this client's cliid).
				*/
				{
					struct host *hp_tmp;

        			iphashcode = hash_HashFunction((u_char*)&pkt->bp_ciaddr.s_addr, 4);
	        		hp_tmp = (struct host *) hash_Lookup(ipboundhshtbl, iphashcode, ipcmp, (hash_datum*) &(pkt->bp_ciaddr.s_addr));
					if (!hp_tmp) {
						if (debug > 2)
                			report(LOG_INFO, "  client %s in RENEWING/REBINDING/BOUND state tried to renew dynamic IP address %s ...",  cliid_btoa(cl.cliid), inet_ntoa(pkt->bp_ciaddr));
                			report(LOG_INFO, "  but this IP address is not presently bound and is a member of one of my pools, so we will NAK the client");
                		send_dhcpnack(pkt,cl.cliid,"Dynamic IP address you tried to renew is not presently bound",cl.max_len,cl.cliid_explicit);
                		free_client_info(&cl);
                		return;
					}
				}

				/* If bootptab also has a static IP address assigned to this client, and it is appropriate
				   for the network to which the client is presently attached, then we'd LIKE to not allow the
				   client to keep using the dynamic address he has requested; we'd like to get the client to
				   use the static IP address.  (The spec actually says we should let the client keep using the
				   dynamic IP address, but that's not what you normally want.)  We'd do this by NAKing him to 
				   force him back to INIT or INIT-REBOOT, so he'd then send a DHCPDISCOVER (which would cause
				   us to free the existing dynamic binding) and send an offer for the static IP address.  
				   You'll notice that we do this for the INIT-REBOOT case.
				   UNFORTUNATELY, we can't do this here for the RENEWING/REBINDING/BROKEN case.  To determine whether a
				   client's assigned static IP address (in bootptab) is appropriate for his current
				   network, we must compare it with the packet's giaddr only when we know the packet was broadcast
				   (since that's the only way to ensure that giaddr was set appropriate by a BootP Relay Agent if
				   it is nonlocal).  Now at this point, we're handing clients who are
				   either RENEWING or REBINDING (we can't always be sure of which)...  Packets from clients who are
				   RENEWING unicast their packets to us, so we cannot rely on the giaddr.  Thus, we can't
				   use the check_net() test we'd like to do.
				   The upshot is that a RENEWING/REBINDING client will be allowed to keep using a dynamic IP
				   address it is already bound to, even if a static IP address appropriate for its current
				   network is available.  To force the device to fall back to its static IP address, the
				   customer will need to reboot it, which forces it to start in INIT or INIT-REBOOT.
				*/
			}

			/* locate ciaddr client wants to renew in our current leases */
			hp2 = hp;	/* save bootptab entry, we may need it again */
			exists = FALSE;
        	iphashcode=hash_HashFunction((u_char*)&pkt->bp_ciaddr.s_addr,4);
	        hp=(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp,(hash_datum*)&(pkt->bp_ciaddr.s_addr));

			if (hp) { /* we have a current lease for this ciaddr */
				/* make sure cliid in current binding == requester's cliid */
				if (hp->flags.cliid) { /* binding has cliid */
					if (!CLIID_EQ(hp->cliid,cl.cliid)) {
						if (debug > 2)
							report(LOG_INFO, "  requested IP address %s already leased to another client ID", inet_ntoa(pkt->bp_ciaddr));
						send_dhcpnack(pkt,cl.cliid,"IP Address leased to someone else",cl.max_len,cl.cliid_explicit);
						free_client_info(&cl);
						return;
					}
				} else {
					report(LOG_ERR,"  IP address %s presently bound, but binding has no cliid!", inet_ntoa(hp->iaddr));
					send_dhcpnack(pkt,cl.cliid,"Server error: requested IP address bound, but binding has no cliid",cl.max_len,cl.cliid_explicit);
					report(LOG_ERR, "  freeing binding");
					reuse_addr(hp);
					free_client_info(&cl);
					return;
				}
				if (!hp->flags.expire) {
					if (debug > 3 || log_expirations) {
						report(LOG_INFO, "  current binding was assigned via BootP (not DHCP), will not use");
						report(LOG_INFO, "  Freeing binding");
					}
					send_dhcpnack(pkt,cl.cliid,"Current binding was assigned via Bootp (not DHCP)",cl.max_len,cl.cliid_explicit);
					reuse_addr(hp);
					free_client_info(&cl);
					return;
				}
				if (hp->expire <= now) {
				/* It is possible that hp->expire <= now, meaning that the current binding has already
				   expired.  (That can happen if it expired by garbage collection hasn't happened yet to remove it.)
				   Let's be safe and discard that binding (I'm not sure it is safe to extend it if it happens
				   to be just an OFFER that was never accepted, instead of a real lease).  Note that we don't
				   send a NAK here....we continue processing the REQUEST, just as if the client is not presently
				   bound. */
					if (debug > 3 || log_expirations) {
						report(LOG_INFO, "  current binding expired (awaiting garbage collection), will not use");
						report(LOG_INFO, "  Freeing binding, will proceed (treating client as unbound)");
					}
					reuse_addr(hp);
					hp=NULL;
					/* It is safe to proceed here.  Basically, we're going to end up deciding to return() without responding to
					   the client, since although we don't have a lease for the client, the client might be REBINDING under which
					   case we should be silent.  But note that if the ciaddr is for a static IP address, and renew_unbound_statics
					   feature is enabled, we're going to end up deciding to ACK the client and create a new lease. */
				}
			} else { 
				/* we can't just exit with error here as we'd like, since this could be a renew_unbound_statics case */
			}

			/* locate cliid in our current leases */
			hp3 =(struct host *)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp,cl.cliid);
			if (hp3) { /* cliid found in boundhshtbl */
				if (!hp) { /* must check since we couldn't rule out this case earlier, due to renew_unbound_statics possibility */
					/* perhaps there are two clients trying to use same cliid */
					report(LOG_ERR, "  dhcp_request(): RENEWING/REBINDING/BOUND: did *NOT* find client's claimed IP (ciaddr=%s) in ipboundhshtbl,...", inet_ntoa(pkt->bp_ciaddr));
					report(LOG_ERR, "  but did find entry for the cliid (%s) in boundhshtbl, assigned IP address %s!...", cliid_btoa(cl.cliid), inet_ntoa(hp3->iaddr));
					/* This isn't necessarily a server error (e.g. corrupted hash tables).
					   It can be caused by a bound client trying to renew a non-existant lease. 
					   Perhaps we shouldn't log this at LOG_ERR?
					*/
					report(LOG_ERR, "  I.e. the client is trying to renew a lease on an IP address (%s) that is not leased to it", inet_ntoa(pkt->bp_ciaddr));
					report(LOG_ERR, "freeing binding");
					reuse_addr(hp3);
					if (lone_dhcp_server) {
						if (debug > 3)
							report(LOG_INFO, "  sending NAK since the Lone DHCP Server feature is enabled");
                		send_dhcpnack(pkt, cl.cliid, "The IP address you are trying to renew is not presently leased to you", cl.max_len, cl.cliid_explicit);
						free_client_info(&cl);
						return;
					} else {
						/* don't NAK since may be REBINDING */
						free_client_info(&cl);
						return;
					}
				}
				/* make sure ip address in current binding == requested IP */
				if (hp3->iaddr.s_addr == hp->iaddr.s_addr)  {
						/* this is normal, all matches */
						exists=TRUE;
				} else {
					/* not sure what this means.  cliid apparently bound a different IP.  Unbind cliid and ignore */
					report(LOG_ERR, "  dhcp_request(): RENEWING/REBINDING/BOUND: Found client's IP (%s) in ipboundhshtbl for this cliid...", inet_ntoa(pkt->bp_ciaddr));
					report_cliid(LOG_ERR, cl.cliid);
					report(LOG_ERR, "but entry for this cliid in ipboundhshtbl is for *DIFFERENT* IP address (%s)!", inet_ntoa(hp3->iaddr));
					report(LOG_ERR, "freeing both bindings");
					reuse_addr(hp);
					reuse_addr(hp3);
					if (lone_dhcp_server) {
						if (debug > 3)
							report(LOG_INFO, "  sending NAK since the Lone DHCP Server feature is enabled");
                		send_dhcpnack(pkt, cl.cliid, "The IP address you are trying to renew is not presently leased to you", cl.max_len, cl.cliid_explicit);
						free_client_info(&cl);
						return;
					} else {
						/* don't NAK since may be REBINDING */
						free_client_info(&cl);
						return;
					}
				}
			} else { /* cliid not found in boundhshtbl */
				if (hp) { /* requested IP addr found in ipboundhshtbl */
					/* this should never happen, since we already know that the ipboundhshtbl record specifies this cliid */
					/* not much we can do here, other than log the error, and ignore the request */
					report(LOG_ERR, "  dhcp_request(): RENEWING/REBINDING/BOUND: Found client's IP (%s) in ipboundhshtbl for this cliid...", inet_ntoa(pkt->bp_ciaddr));
					report(LOG_ERR, cl.cliid);
					report(LOG_ERR, "but did *NOT* find entry for this cliid in boundhshtbl!");
					report(LOG_ERR, "freeing binding");
					reuse_addr(hp);
					if (lone_dhcp_server) {
						if (debug > 3)
							report(LOG_INFO, "  sending NAK since the Lone DHCP Server feature is enabled");
                		send_dhcpnack(pkt, cl.cliid, "The IP address you are trying to renew is not presently leased to you", cl.max_len, cl.cliid_explicit);
						free_client_info(&cl);
						return;
					} else {
						/* don't NAK since may be REBINDING */
						free_client_info(&cl);
						return;
					}
				} else { /* requested IP addr not found in ipboundhshtbl */
					/* this may be normal; neither cliid nor req ip was bound, exists still == FALSE */
				}
			}

			if (!hp) { /* client not presently bound to us -- we have no current lease for this IP address */
				if (debug > 3)
					report(LOG_INFO, "  client's ciaddr %s is not presently bound", inet_ntoa(pkt->bp_ciaddr));
				if (!hp2->flags.dynamic && renew_unbound_statics) {
					/* According to spec, we should NOT renew, as we have no current lease for client.
					   However, if client is trying to renew an IP address that appears in our bootptab
					   as statically-assigned to this cliid (and the request has passed all validity checks
					   up to this point), and the server was started with the -u (renew_unbound_statics) option,
					   we will go ahead and issue a lease for this client anyway.  (Internally, we are creating
					   a new lease; from the client's point of view, we are renewing an existing lease.  So
					   this could certainly be a problem for the client if the client's idea of the lease include some info
					   that does not agree with the what we provide in the ACK.)  Note that this behavior violates
					   the spec, so it is not default.  It is provided for two reasons:  (1) to allow you to more
					   easily migrate from an older dhcp server code (we didn't inherit oustanding lease info from
					   it, but clients still think they are bound to us, and (2) to allow multiple DHCP servers
					   that serve the same static IP addresses to provide some redundancy for each other.  In
					   case (2), if the client is bound to a server that is temporarily unavailable, this means
					   that when the client enters REBINDING state, the other servers can RENEW the client's
					   lease.  
					   We don't provide this functionality for dynamically-assigned addresses, as for case (1)
					   that seems less safe, and for case (2) we must assume, in the absence of a server-to-server
					   protocol, that each server has an independant range of dynamic addresses to distribute.
					   Note that this functionality will be entirely superfluous once there is a server-to-server protocol. */
					hp = hp2;	/* we'll use the assignment provided by the bootptab */
					if (debug > 3)
						report(LOG_INFO, "  -u option specified, and ciaddr is in bootptab as static for this cliid; will use");
					assigning=TRUE;	/* we normally don't report assignments for RENEWAL, but renewal of unbound statics
									   is really a server assignment, so we report it */
					/* If we have a current binding for this cliid, clear it; client obviously not still using it */
					hp2=(struct host*)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp ,cl.cliid);
					if (hp2) {
						if (debug > 3 || log_expirations) {
							report(LOG_INFO, "  cliid presently bound to %s", inet_ntoa(hp2->iaddr));
							report(LOG_INFO, "  freeing binding");
						}
						reuse_addr(hp2);
					}
				} else {
					if (lone_dhcp_server) {
						if (debug > 3)
							report(LOG_INFO, "  sending NAK since the Lone DHCP Server feature is enabled");
                		send_dhcpnack(pkt, cl.cliid, "The IP address you are trying to renew is not presently leased to you", cl.max_len, cl.cliid_explicit);
						free_client_info(&cl);
						return;
					} else {
						/* if we knew for SURE the client was RENEWING, we'd like to NAK, but this
				   			client may be REBINDING, so the best we can do is be silent.  The RENEWING
				   			clients will get to keep using their invalid leases until they expire at the client end,
							unfortunately, but at least we won't break REBINDING clients. */
						/* Given the lack of a server-to-server protocol, each server that has a pool
				   			of dynamic IP addresses must have a separate such pool, which means that this
				   			server *is* all-knowing for its dynamic IP addresses.  If you make that assumption,
				   			you could then add code here to check if ciaddr's entry in bootptab has the dynamic
				   			flag set; if it does, then you can safely send a NAK instead of being silent.  Unfortunately,
				   			that will stop working once there is a server-to-server protocol so multiple servers can
				   			share the same pool of dynamic IP addresses.  So I haven't implemented that change here. */
						if (debug > 3)
							report(LOG_INFO, "  want to NAK if client is RENEWING, but client may be REBINDING, so will ignore");
						free_client_info(&cl);
						return;
					}
				}
			}

			/* all ok, fall through to sending ACK below.  At this point:
			   hp points to a current binding for the IP address we wish to grant, and exists==TRUE,  or
			   hp points to a bootptab entry for the IP address we wish to grant,  and exists=FALSE */

			if (!assigning) /* not a renewal of an unbound static */
				renewing=TRUE; /* we want to report INIT-REBOOT assignments as RENEWING */

			} /* RENEWING/REBINDING/BOUND/confused */

		} /* (cl.serv_addr.s_addr==0) */
	
	/* Grant/renew lease 'hp' to client */


	/* At this point, hp points to an entry in bootptab (exists==FALSE) or to a current binding (exists==TRUE).
	   Either way, it reflects the IP address we wish to grant. */

	if (!hp) { /* debugging code */
		report(LOG_ERR, "  Error! About to send DHCPACK, but hp==NULL!");
		free_client_info(&cl);
		return;
	}


	if (!exists) { /* it wasn't in the bound tables (or we removed it since it was a bootp binding) */
		if (debug > 1)
			report(LOG_INFO,"  Will grant new lease on IP address %s (%s)",inet_ntoa(hp->iaddr), hp->hostname->string);
		if (!(hp=create_stub(hp,&cl))) { /* create the new struct host for the current bindings */
            report(LOG_ERR, "   dhcp_request(): create_stub() returned NULL, cannot create new binding");
            /* reuse_addr(hp); we can't call this since hp is toast.  We leak the IP address if dynamic, but no memory */
            free_client_info(&cl);
            return;
		}
		
		if (hp->flags.dynamic) {
			/* Remove dynamic address from pool of free dynamics. */
			/* We call with force=1, so the value of gwaddr is ignored, we pass any struct in_addr there just
			   for type checking.  While the flag check is not of interest to us here (already taken care of
			   earlier when access_network called), we have to provide a flag, so we'll go with the most permissive
			   possible version.    We also set the flag arg to ROAMING and REG and UNREG here (giving
			   the client all relevant privs) since we've already performed the policy check with
			   a call to access_network() earlier.
			   
			   We ignore the return value, since all we're after is the side-effect of
			   removing hp->iaddr from the appropriate cable's list of avail dynamic ip addresses. */
			use_addr(hp, pkt->bp_giaddr, ROAMING_ACCESS|REG_ACCESS|UNREG_ACCESS|DHCP_ACCESS, 1);
		}

		hp->xid=pkt->bp_xid;
		hp->linkcount+=2; /* starts =0; about to insert into 2 hashtabs */
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "  will insert into boundhshtbl...");
#endif
		if (!hash_Insert(boundhshtbl,cl.clhashcode, boundcliidcmp, cl.cliid,(hash_datum*)hp)) {
			leases++;
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "  incremented leases counter to %d", leases);
#endif
		} else {
			report(LOG_ERR, "    dhcp_request(): hash_Insert(boundhshtbl) failed (cliid=%s)", cliid_btoa(cl.cliid));
			report(LOG_ERR, "    dhcp_request(): not increasing 'leases' counter...so count may no longer be right");
		}
		/* You can positively identify dhcp assignments in current bindings; they have hp->flags.expire==1.
		   You can then subdivide them as static (hp->code==RESERVED_FLAG) or dynamic (hp->code==DHCP_FLAG). */
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "  will insert into ipboundhshtbl...");
#endif
		if (hash_Insert(ipboundhshtbl,hp->iphashcode, ipcmp,(hash_datum *)&(hp->iaddr.s_addr),(hash_datum*)hp)) {
			report(LOG_ERR, "    dhcp_request(): hash_Insert(ipboundhshtbl) failed for IP address %s (cliid=%s)", inet_ntoa(hp->iaddr), cliid_btoa(cl.cliid));
		}
	} else /* if (!exists) */
		if (debug > 1)
			report(LOG_INFO, "  Will renew existing lease on IP address %s (%s)",inet_ntoa(hp->iaddr), hp->hostname->string);

	/* we may have to do this even in exists==TRUE.  It *will* correctly update the hp in the current bindings */
	if (!hp->flags.complete) {  /* fill out more of the new struct host */
		if (!create_lease(hp,cl.cliid,cl.vend,cl.user,0)) {
           	report(LOG_ERR, "   dhcp_request(): create_lease() returned NULL, cannot create new binding");
			reuse_addr(hp);
           	free_client_info(&cl);
           	return;
		}
	}

	if(hp->flags.dynamic)
		hp->code=DHCP_FLAG;
	else
		hp->code=RESERVED_FLAG;
 
	hp->expire = now + hp->lease;
	hp->flags.expire = 1;

	/* Report if client left some MBZ flag bits set, and fix it in reply */
	if (ntohs(pkt->bp_flags) & BP_FLAGS_MBZ) {
		if (debug > 9)
			report(LOG_NOTICE, " some MBZ bits in flags field were set in DHCPREQUEST (cliid=%s)", cliid_btoa(cl.cliid));
		pkt->bp_flags = htons(ntohs(pkt->bp_flags) & ~ BP_FLAGS_MBZ);  /* clear MBZ bits for reply */
	}

	/* The lease represented by 'hp' is a regular lease -- not an offer that the client has no yet accepted.
	   It may have begun as an offer the client has not yet accepted, but if so, the DHCPREQUEST we just received
	   changes that.
	*/
	hp->flags.offerred_but_not_yet_accepted_by_client = 0;

    rpkt=(struct bootp *)send_buf;
    bcopy(pkt,rpkt,pktlen);
    rpkt->bp_op=BOOTREPLY;
    rpkt->bp_hops=0;
    rpkt->bp_secs=0;
    rpkt->bp_yiaddr.s_addr=hp->iaddr.s_addr;
    rpkt->bp_siaddr.s_addr = hp->bootserver.s_addr;
	if(fill_header(rpkt,hp,&bootsize)){
		free_client_info(&cl);
		reuse_addr(hp);
		report(LOG_ERR,"  fill_header() returned non-zero, huh?");
		return;
	}
    /* always stick our name in */
	/* note strncpy will right-pad with nul, clearing out any garbage - good */
	strncpy(rpkt->bp_sname, hostname, BP_SNAME_LEN);	/* may be overwritten if options field overflows */

	dest=hp->flags.reply_addr?hp->reply_addr.s_addr:0;


	opt.cur=0;
	opt.len=cl.max_len;
	/* Insert the DHCP Message Type option first, because some clients incorrectly assume that
	   if this option is not first, the message is BOOTP, or bad. */
	insert_byte_opt(rpkt,&opt,TAG_DHCP_MSG,DHCPACK);
	insert_ulong_opt(rpkt,&opt,TAG_DHCP_SERVERID,my_ip_addr.s_addr);
	insert_ulong_opt(rpkt,&opt,TAG_DHCP_IPLEASE,htonl(hp->lease));
	if (compact_messages && debug) {
		char giaddrbuf[MAX_INETADDR_STRING_LEN];
		char iaddrbuf[MAX_INETADDR_STRING_LEN];

		strncpy(giaddrbuf, inet_ntoa(pkt->bp_giaddr), MAX_INETADDR_STRING_LEN-1);
		strncpy(iaddrbuf, inet_ntoa(hp->iaddr), MAX_INETADDR_STRING_LEN-1);
		report(LOG_INFO, "DHCPREQUEST  %s [%s]: lease %s (%s) until %s (%d seconds)",
			haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), giaddrbuf, iaddrbuf, hp->hostname->string, cctime(now+hp->lease), hp->lease);
	}
	if (debug > 9)
			report(LOG_INFO, "  Lease expires in %d seconds (%s)", hp->lease, cctime(now+hp->lease));
	if(hp->flags.renew) {
		insert_ulong_opt(rpkt,&opt,TAG_DHCP_RENEW,htonl(hp->renew));
		if (debug > 9) 
			report(LOG_INFO, "  Lease renew set for %d seconds (%s)", hp->renew, cctime(now+hp->renew));
	}
	if(hp->flags.rebind) {
		insert_ulong_opt(rpkt,&opt,TAG_DHCP_REBIND,htonl(hp->rebind));
		if (debug > 9) 
			report(LOG_INFO, "  Lease rebind set for %d seconds (%s)", hp->rebind, cctime(now+hp->rebind));
	}
	if(cl.user){
		insert_string_opt(rpkt,&opt,TAG_DHCP_USERCLASS,
					cl.user->data,cl.user->length);
	}

	/* fill in options requested by client's Parameter Request List option, if any */
    if (rc=do_req_opts(hp, cl.req_opts, (u_char *) rpkt, &opt, &sent_options)) {
		report(LOG_ERR,"  do_req_opts() returned %d, will not send DHCPACK to Client ID %s", rc, cliid_btoa(hp->cl));
		free_client_info(&cl);
		reuse_addr(hp);
		return;
	}

	if (cl.client_fqdn_option_present /* "Client FQDN" option was present in the packet we received from client */
			&&
		 ( !hp->flags.client_fqdn		/* bootptab contains no 'cf' tag for this client */
			||
		   (hp->flags.client_fqdn && hp->client_fqdn) /* bootptab contains 'cf' tag for this client and its non-zero */
		 )
		) {
		/* Easiest to just use our existing do_req_opts(), pretending that we have a Paramter Request List
		   asking for this option.
		   request_client_fqdn_option was init'd in a header to a trivial parameter request list value 
		   that includes just TAG_CLIENT_FQDN.
		*/
		if (rc=do_req_opts(hp, (u_char *) &request_client_fqdn_option, (u_char *) rpkt, &opt, &sent_options)) {
			report(LOG_ERR,"  do_req_opts() returned %d, will not send DHCPACK to Client ID %s", rc, cliid_btoa(hp->cl));
			free_client_info(&cl);
			reuse_addr(hp);
			return;
		}
	}

	if(hp->flags.not_rq) {
		/* bootptab entry has the NR tag, saying that our DHCP replies should include any values that appear
		   in the bootptab entry, even if they were "Not Requested" by the client in a Parm Request option and
		   are not values we always return */
		if (rc=do_opts(hp, (u_char *) rpkt, &opt, &sent_options)) { 
			report(LOG_ERR,"  do_opts() returned %d, will not send DHCPACK to Client ID %s", rc, cliid_btoa(hp->cl));
			free_client_info(&cl);
			reuse_addr(hp);
			return;
		}
	}

	insert_end_opt(rpkt,&opt);

	/* We're done building the DHCPACK, send it */
	if (debug > 12)
		report(LOG_INFO,"  Sending DHCPACK (max_len=%d, bytes left=%d)", opt.len,opt.len-opt.cur);
	else if (debug > 3)
		report(LOG_INFO,"  Sending DHCPACK");
	acks_sent++;
	len=300;
	if(opt.cur<VEND_OFFSET)
		len=cl.max_len;
	if(opt.cur>len)
		len=opt.cur;

	if (delay_reply_dhcpack)
		delay_usec = delay_reply_dhcpack;
	else if (delay_reply_default)
		delay_usec = delay_reply_default;

	send_dhcp_reply(rpkt,len,dest,delay_usec);

	if  ((log_assignments>0) && assigning)
		report_assignment(RACATEGORY_ASSIGNED, 1, hp->flags.dynamic, inet_ntoa(hp->iaddr), hp->hostname->string, &cl);
	else if ((log_assignments>1) && renewing)
			report_assignment(RACATEGORY_RENEWED, 1, hp->flags.dynamic, inet_ntoa(hp->iaddr), hp->hostname->string, &cl);

	update_lastbind(&cl,hp);
	free_client_info(&cl);

}

void dhcp_release(pkt)
struct bootp *pkt;
{
	struct client_info cl;
	struct host *hp,*hp2;
	unsigned int32 dest; /* network byte order */
	unsigned iphashcode,hwhashcode,rc;

	bzero(&cl,sizeof(cl));
	cl.max_len=576;		/* for testing overload functions, temporarily reduce this, e.g. to 300 */

	if(debug>1)
		report(LOG_INFO," processing DHCPRELEASE");
	releases_received++;
	rc=dhcp_opts(pkt,&cl);

	/* we don't need msg here, dhcp_opts() already produces one
	if(!cl.cliid)
		report(LOG_ERR,"  couldn't determine client identifier, ignoring dhcprelease");
	*/
	if(rc){
		free_client_info(&cl);
		return;
	}

	if (compact_messages && debug)
		report(LOG_INFO, "DHCPRELEASE  %s [%s]: request", haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), inet_ntoa(pkt->bp_giaddr));

	if(debug>1)
		report_cliid(LOG_INFO,cl.cliid);

	if (cl.serv_addr.s_addr!=0 && !is_my_ip_addr(& cl.serv_addr)) {
			if (known_serverids_hshtbl_inuse && !is_known_server_identifier(&cl.serv_addr)) {
				/* inet_ntoa() uses static storage, so must build message */
				char saddrbuf[MAX_INETADDR_STRING_LEN];
				char ciaddrbuf[MAX_INETADDR_STRING_LEN];
				strncpy(saddrbuf, inet_ntoa(cl.serv_addr), MAX_INETADDR_STRING_LEN-1);
				strncpy(ciaddrbuf, inet_ntoa(pkt->bp_ciaddr), MAX_INETADDR_STRING_LEN-1);
				report(LOG_WARNING, "  saw unknown Server Identifier %s in DHCPRELEASE from client (cliid=%s, ciaddr=%s)", saddrbuf, cliid_btoa(cl.cliid), ciaddrbuf);
			}
		if(debug>3)
			report(LOG_INFO,"  message is not for me (Server Identifier option is %s), ignoring",inet_ntoa(cl.serv_addr));
		free_client_info(&cl);
		return;
	}

	cl.clhashcode=hash_HashFunction(cl.cliid->data,cl.cliid->length);
	hp =(struct host *)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp,cl.cliid);
	if(!hp){
		if(debug>3)
			report(LOG_INFO,"  binding doesn't exist for this client id, ignoring");
		free_client_info(&cl);
		return;
	}

	if (hp->flags.offerred_but_not_yet_accepted_by_client) {
 		if(debug>3)
 			report(LOG_INFO,"  binding for this client is only an offer not yet accepted by the client, ignoring");
 		free_client_info(&cl);
 		return;
	}

	if(cl.msg)
		if (debug > 3)
			report(LOG_INFO,"  client specified message option=%s",cl.msg);

	iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
	hp2 =(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr));
	if (!hp2) {
		report(LOG_ERR, "dhcp_release(): entry for cliid (%s) found in boundhshtbl specifying IP address %s...", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
		report(LOG_ERR, "but no entry for this IP addr found in ipboundhshtbl!");
		report(LOG_ERR, "will still free binding");
	} else {
		if (!hp2->flags.cliid) {
			report(LOG_ERR, "dhcp_release(): entry for cliid (%s) found in boundhshtbl specifying IP address %s...", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
			report(LOG_ERR, "but ipboundhshtbl entry for this IP addr lacks a cliid!");
			report(LOG_ERR, "will still free both bindings");
		} else if (!BINDATA_EQ(hp2->cliid, cl.cliid)) {
			report(LOG_ERR, "dhcp_release(): entry for cliid (%s) found in boundhshtbl specifying IP address %s...", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
			report(LOG_ERR, "but ipboundhshtbl entry for this IP addr has different cliid (%s)", cliid_btoa(hp2->cl));
			report(LOG_ERR, "will still free both bindings");
		}
	}

	if (compact_messages && debug) {
		char giaddrbuf[MAX_INETADDR_STRING_LEN];
		char iaddrbuf[MAX_INETADDR_STRING_LEN];

		strncpy(giaddrbuf, inet_ntoa(pkt->bp_giaddr), MAX_INETADDR_STRING_LEN-1);
		strncpy(iaddrbuf, inet_ntoa(hp->iaddr), MAX_INETADDR_STRING_LEN-1);
		report(LOG_INFO, "DHCPRELEASE  %s [%s]: release %s (%s)",
			haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), giaddrbuf, iaddrbuf, hp->hostname->string);
	}

	if(debug>2 || log_expirations)
		report(LOG_INFO,"  freeing binding to %s",inet_ntoa(hp->iaddr));
	reuse_addr(hp);

	free_client_info(&cl);

	return;
}

void dhcp_decline(pkt)
struct bootp *pkt;
{
	struct client_info cl;
	struct host *hp,*hp2;
	unsigned int32 dest;
	struct order_list **p;
	unsigned rc;

	bzero(&cl,sizeof(cl));
	cl.max_len=576; 	/* for testing overload functions, temporarily reduce this, e.g. to 300 */
	if(debug>1)
		report(LOG_INFO," processing DHCPDECLINE");
	declines_received++;
	rc=dhcp_opts(pkt,&cl);

	/* we don't need msg here, dhcp_opts() already produces one
	if(!cl.cliid)
		report(LOG_ERR,"  couldn't determine client identifier, ignoring dhcpdecline");
	*/
	if(rc){
		free_client_info(&cl);
		return;
	}

	if(debug>1)
		report_cliid(LOG_INFO,cl.cliid);

	/* Spec says that DHCPDECLINE message MUST have a Server Identifier option, but does not
	   specify what the server should do with this field.  E.g. should we ignore packets that lack
	   the option?  And should we ignore packets that specify a Server Identifier other than ourself?
	   Presently, we choose to go ahead and accept (process) packets that lack the option; if the
	   packet contains the option, we ignore the packet if the value is not me.
	*/
	if (cl.serv_addr.s_addr == 0) {
		if (debug > 9)
			report(LOG_NOTICE, "  Server Identifier missing from DHCPDECLINE (will still process)");
		} else { /* Server Identifier option exists */
			if (!is_my_ip_addr(& cl.serv_addr)) {
				if (known_serverids_hshtbl_inuse && !is_known_server_identifier(&cl.serv_addr)) {
					/* inet_ntoa() uses static storage, so must build message */
					char saddrbuf[MAX_INETADDR_STRING_LEN];
					char rqaddrbuf[MAX_INETADDR_STRING_LEN];
					strncpy(saddrbuf, inet_ntoa(cl.serv_addr), MAX_INETADDR_STRING_LEN-1);
					strncpy(rqaddrbuf, inet_ntoa(cl.req_addr), MAX_INETADDR_STRING_LEN-1);
					report(LOG_WARNING, "  saw unknown Server Identifier %s in DHCPDECLINE from client (cliid=%s, Requested IP Address=%s)", saddrbuf, cliid_btoa(cl.cliid), rqaddrbuf);
				}
				if (cl.serv_addr.s_addr != INADDR_BROADCAST) { /* Some DHCP client (Win98?) mistakenly puts 255.255.255.255 in
																  its ServerID Option for DHCPDECLINE.  Rather than insist that
																  these are not for me, we'll continue to accept them, to 
																  accomodate those clients.
																*/
					if (debug > 3)
						report(LOG_INFO, "  message is not for me (Server Identifier option is %s), will ignore", inet_ntoa(cl.serv_addr));
					free_client_info(&cl);
					return;
				}
		} /* Server Identifier option exists and is not me */
	} /* Server Identifier option exists */

	cl.clhashcode=hash_HashFunction(cl.cliid->data,cl.cliid->length);
	hp =(struct host *)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp,cl.cliid);

	if(hp) {
		if (compact_messages && debug) {
			char giaddrbuf[MAX_INETADDR_STRING_LEN];
			char iaddrbuf[MAX_INETADDR_STRING_LEN];

			strncpy(giaddrbuf, inet_ntoa(pkt->bp_giaddr), MAX_INETADDR_STRING_LEN-1);
			strncpy(iaddrbuf, inet_ntoa(hp->iaddr), MAX_INETADDR_STRING_LEN-1);
			report(LOG_INFO, "DHCPDECLINE  %s [%s]: freeing %s (%s)",
				haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), giaddrbuf, iaddrbuf, hp->hostname->string);
		}
		if (debug>1 || log_expirations)
			report(LOG_INFO,"  freeing binding for IP address %s",inet_ntoa(hp->iaddr));
		reuse_addr(hp);
	} else { /* hp */
		if (compact_messages && debug)
			report(LOG_INFO, "DHCPDECLINE  %s [%s]; binding not found",
				haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), inet_ntoa(pkt->bp_giaddr));
		if (debug > 1)
			report(LOG_INFO,"  no binding for this clientid found, ignoring");
	}

	free_client_info(&cl);

}

void dhcp_inform(pkt)
struct bootp *pkt;
{
	struct client_info cl;
	struct host *hp,*hp2,generic;
	struct opts opt;
	char override[2000];
	int len;
	unsigned rc,iphashcode,hwhashcode,nmhashcode;
	unsigned int delay_usec = 0;


	bzero(&cl,sizeof(cl));
	cl.max_len=576; 	/* for testing overload functions, temporarily reduce this, e.g. to 300 */
	if(debug>1)
		report(LOG_INFO," processing DHCPINFORM");
	informs_received++;
	rc=dhcp_opts(pkt,&cl);

	/* we don't need msg here, dhcp_opts() already produces one
	if(!cl.cliid)
		report(LOG_ERR,"  couldn't determine client identifier, ignoring dhcpinform");
	*/
	if(rc){
		free_client_info(&cl);
		return;
	}

	if (compact_messages && debug)
		report(LOG_INFO, "DHCPINFORM   %s [%s]: ignoring", haddrtoa2(pkt->bp_chaddr, pkt->bp_hlen), inet_ntoa(pkt->bp_giaddr));

	if(debug>1)
		report_cliid(LOG_INFO,cl.cliid);

	/* Make sure cliid isn't one to whom we're supposed to deny service */
	hp =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);
	if (hp && hp->flags.deny_client) {
		if (debug > 3)
			report(LOG_INFO, "  client id/haddr found in bootptab with deny_client tag");
		/* do we presently have a binding for the client (so we'll assume the client is bound to us)? */
		hp=(struct host*)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp ,cl.cliid);
		if (hp) {
			if (debug > 3) {
				report(LOG_INFO, "  cliid presently bound to %s", inet_ntoa(hp->iaddr));
				report(LOG_INFO, "  freeing binding, and sending NAK");
			}
			reuse_addr(hp);
			send_dhcpnack(pkt,cl.cliid,"DHCP service denied",cl.max_len,cl.cliid_explicit);
		} else {
			if (debug > 3)
				report(LOG_INFO, "  no binding found for cliid, ignoring client");
		}
		free_client_info(&cl);
		return;
	}

	/* the entire dhcp_inform() function appears to be so screwed-up at this time,
   I'm temporarily replacing it with a no-op.  The function didn't appear to actually
   do anything resembling what the spec says it should do.  The response packet
   it sent seemed to be little more than an echo of the INFORM packet it got, with a
   few fields cleared or changed.  The response packet seemed to have little to do with
   the information obtained from the bootptab.  */

#if 0
	cl.clhashcode=hash_HashFunction(cl.cliid->data,cl.cliid->length);
	/* Look for clientid in bootptab.  (Remember that if incoming packet doesn't specify a 
	   DHCP Client ID option, we've computed its ClientID by concatenating its htype with its haddr.) */
	hp =(struct host *)hash_Lookup(clhashtable, cl.clhashcode,cliidcmp,cl.cliid);

#ifdef RETRY_HADDR_AS_CLIID
	/* I've ifdef'd out this because pretending to find a cliid match for clients when there's a mismatch 
	   between the packet's Cliid and the bootptab's cliid can cause problems later as we start storing
	   the inconsistent values in our hashtables.  Besides, we probably shouldn't be pretending to find
	   a match in the first place -- errors in the cliid specified by the bootptab or the client
	   should be corrected, not ignored.
	*/
	if(!hp){
		/* We didn't find ClientID in bootptab's clhashtable using ClientID as key.  
		   (Recall that clhashtable contains bootptab entries keyed by cliid (if 'cl' explicitly 
		   specified in bootptab entry) else bootptab entries keyed by haddr.) 
		   Perhaps the incoming packet contains a DHCP ClientID option with a garbage value.
		   Or perhaps the bootptab entry specifies a garbage cliid (via an incorrect 'cl' tag).
		   In either case, we may have more luck if we look up the incoming packet's haddr in
		   bootptab's hwhastable using haddr as key.  
		*/
		hwhashcode=hash_HashFunction(cl.cliid->data+1,cl.cliid->length-1);
		hp =(struct host *)hash_Lookup(hwhashtable, hwhashcode, cliidhwlookcmp,cl.cliid);
	}
#endif

	if (hp) {
		if (debug>3)
			report(LOG_INFO, "  found cliid in bootptab");
	} else {
		if (debug>3)
			report(LOG_INFO, "  did not find cliid in bootptab");
		iphashcode=hash_HashFunction((u_char*)&pkt->bp_ciaddr.s_addr,4);
		hp=(struct host *)hash_Lookup(iphashtable,iphashcode,ipcmp, &(pkt->bp_ciaddr.s_addr));
		if (debug>3)
			if (hp)
				report(LOG_INFO, "  found ciaddr (%s) in bootptab", inet_ntoa(pkt->bp_ciaddr));
			else
				report(LOG_INFO, "  did not find ciaddr (%s) in bootptab", inet_ntoa(pkt->bp_ciaddr));
	}

	if (hp) { /* client is registered */
		if (!hp->flags.iaddr) {
			hp=NULL;
			if (debug > 3)
				report(LOG_INFO, "  will ignore bootptab entry as it lacks an IP address");
		} else {
			if (!(hp=create_stub(hp, &cl))) {    /* create the new struct, fill in a few values */
				report(LOG_ERR, "   dhcp_inform(): create_stub() returned NULL, cannot create new stub");
				free_client_info(&cl);
				return;
			}
			/* we use create_lease() to get some inheritance */
			if (!create_lease(hp, cl.cliid, cl.vend, cl.user, 0)) { 
        		report(LOG_ERR, "   dhcp_inform(): create_lease() returned NULL, cannot build response");
				hp->linkcount = 1; /* to force free_host() to free it, as it starts =0 */
				free_host(hp);
        		free_client_info(&cl);
        		return;
			}
		}
	} else { /* client isn't registered, or bootptab entry lacks an IP address */
		/*  construct a generic client based on $C, $V, or $DHCP bootptab entries if possible */
		if (debug>3)
				report(LOG_INFO, "  will try to create generic response");
		if(cl.user){
			override[0]='$';
			override[1]='C';
			override[2]=0;
			for(i=0;i<cl.user->length;i++)
				sprintf(override+strlen(override),"%02X",cl.user->data[i]);
			if(debug>3)
				report(LOG_INFO, "  will look for $C bootptab override entry");
		} else if(cl.vend){
			override[0]='$';
			override[1]='V';
			override[2]=0;
			for(i=0;i<cl.vend->length;i++)
				sprintf(override+strlen(override),"%02X",cl.vend->data[i]);
			if(debug>3)
				report(LOG_INFO, "  will look for $V bootptab override entry");
		} else  {
			strcpy(override,"$DHCP");
			if(debug>3)
				report(LOG_INFO, "  will look for $DHCP bootptab override entry");
		} 
		bzero(&generic,sizeof(generic));
		nmhashcode=hash_HashFunction((u_char*)override,strlen(override));
		hp2=(struct host *)hash_Lookup(nmhashtable,nmhashcode,nmcmp,override);
		if(hp2) {
			if(debug>3)
				report(LOG_INFO, "  found bootptab override entry, will use that");
			fill_defaults2(&generic,hp2);	/* copies from hp2 into generic */
		} else {
			if(debug>3)
				report(LOG_INFO, "  no bootptab override entry found, ignoring client");
			free_client_info(&cl);
			return;
		}
	}


	/* At this point, either hp is set, based upon finding the client in the bootptab,
	   otherwise generic is set, based upon finding a template entry in the bootptab. */

	/* Remove lease time parameters, if any; those should not be in a response to a 
	   DHCPINFORM packet */
	if (hp)
		hp->flags.lease = hp->flags.renew = hp->flags.renew = 0;
	else
		generic.flags.lease = generic.flags.renew = generic.flags.renew = 0;

	/* Report if client left some MBZ flag bits set, and fix it in reply */
	if (ntohs(pkt->bp_flags) & BP_FLAGS_MBZ) {
		if (debug > 9) {
			report(LOG_NOTICE, " some MBZ bits in flags field were set in DHCPINFORM (cliid=%s)", cliid_btoa(cl.cliid));
		}
		pkt->bp_flags = htons(ntohs(pkt->bp_flags) & ~ BP_FLAGS_MBZ);  /* clear MBZ bits for reply */
	}

	/* Why does the response packet we build have little to do with the info we have
	   gotten into 'hp' or 'generic' ? */

	/* why do we malloc here instead of using send_buf ?*/
	rpkt=(struct bootp *)smalloc(cl.max_len);
	bcopy(pkt,rpkt,pktlen);
	rpkt->bp_op=BOOTREPLY;
	rpkt->bp_hops=0;
	rpkt->bp_secs=0;
	/* spec says we do NOT fill in yiaddr.  */
	/* rpkt->bp_yiaddr.s_addr=rpkt->bp_ciaddr.s_addr; */
	if (hp)
		rpkt->bp_siaddr.s_addr = hp->bootserver.s_addr;
	else
		rpkt->bp_siaddr.s_addr = generic.bootserver.s_addr;

	opt.cur=0;
	opt.len=cl.max_len;
	/* Insert the DHCP Message Type option first, because some clients incorrectly assume that
	   if this option is not first, the message is BOOTP, or bad. */
	insert_byte_opt(rpkt,&opt,TAG_DHCP_MSG,DHCPACK);
	insert_ulong_opt(rpkt,&opt,TAG_DHCP_SERVERID,my_ip_addr.s_addr);
	if(cl.user){
		insert_string_opt(rpkt,&opt,TAG_DHCP_USERCLASS,
					cl.user->data,cl.user->length);
		del_bindata(cl.user);
	}
	if(cl.vend)
		del_bindata(cl.vend);
	insert_end_opt(rpkt,&opt);

	if (debug > 12)
		report(LOG_INFO,"  Sending DHCPACK (max_len=%d bytes left=%d)", opt.len,opt.len-opt.cur);
	else if (debug > 3)
		report(LOG_INFO,"  Sending DHCPACK");
	acks_sent++;
	len=300;
	if(opt.cur<VEND_OFFSET)
		len=cl.max_len;
	if(opt.cur>len)
		len=opt.cur;

	if (delay_reply_dhcpack)
		delay_usec = delay_reply_dhcpack;
	else if (delay_reply_default)
		delay_usec = delay_reply_default;

	send_dhcp_reply(rpkt,len,rpkt->bp_ciaddr.s_addr,delay_usec);

	free(rpkt);

	free_client_info(&cl);

	if (hp) {
		hp->linkcount = 1; /* was 0, this will force it to be freed */
		free_host(hp);
	}

#else

	if(debug>1)
		report(LOG_INFO, "  dhcp_inform() not implemented, ignoring");

	free_client_info(&cl);

	return; 

#endif
}



/* Macro for use in the function below */
/* adds a request for TAGNAME to req_opts[] if MEMBER's exists in hp
   but hasn't been inserted into the reply packet yet */
#define ADD_REQUEST(MEMBER,TAGNAME) do \
{ \
	if (hp->flags.MEMBER && ! f->MEMBER) \
		req_opts[i++] = TAGNAME ; \
} while (0)


/* Fill in vendor options field in DHCP reply packet for those items we know
about but have not yet been previously filled in.  Return 0 on complete success, nonzero
on any error inserting any option.
Avoid inserting any requested option that has already been inserted.
*/

int
do_opts(hp, bp, cur, f)
struct host *hp;		/* client info, e.g. from bindings */
u_char *bp;				/* reply packet we are building */
struct opts *cur;		/* records current location & length of the options data in reply packet */
struct flag *f;			/* indicates which options have *already* been inserted into reply packet */
{
	
	static u_char req_opts[256];	/* which options to try to insert */
	int rc;

	i = 1;				/* we start inserting at req_opts[1], NOT 0 */

	/* since do_req_opts() already does what we need, for a specific list of requested options,
	   we'll just build a list of which options we want it to try to insert, and then let do_req_opts()
	   do the rest of the work. 
	   We don't need to bother with any of the tags that are ALWAYS inserted by us in DHCP reply
	   packets (e.g. TAG_DHCP_SERVERID, TAG_DHCP_IPLEASE, TAG_DHCP_REBIND, TAG_DHCP_RENEW, TAG_DHCP_USERCLASS),
	   since we know those have already been inserted if defined */

	/* these will be inserted into the reply packet in the order listed below, so
	   you may wish to list more important ones first.  Of course, these only get
	   inserted after the always-inserted and the Requested options are inserted, anwway. */

	ADD_REQUEST(gateway, TAG_GATEWAY);
	ADD_REQUEST(subnet_mask, TAG_SUBNET_MASK);
	ADD_REQUEST(domain_server, TAG_DOMAIN_SERVER);
	ADD_REQUEST(domain_name, TAG_DOMAIN_NAME);
	ADD_REQUEST(broadcast_addr, TAG_BROADCAST_ADDR);
	ADD_REQUEST(send_name, TAG_HOST_NAME);
	ADD_REQUEST(nis_server, TAG_NIS_SERVER);
	ADD_REQUEST(nis_domain, TAG_NIS_DOMAIN);
	ADD_REQUEST(root_path, TAG_ROOT_PATH);
	ADD_REQUEST(swap_server, TAG_SWAP_SERVER);
	ADD_REQUEST(exten_file, TAG_EXTEN_FILE);
	ADD_REQUEST(log_server, TAG_LOG_SERVER);
	ADD_REQUEST(ntp_server, TAG_NTP_SERVER);
	ADD_REQUEST(time_server, TAG_TIME_SERVER);
	ADD_REQUEST(time_offset, TAG_TIME_OFFSET);
	ADD_REQUEST(lpr_server, TAG_LPR_SERVER);
	ADD_REQUEST(node_type, TAG_NODE_TYPE);
	ADD_REQUEST(nb_scope, TAG_NB_SCOPE);
	ADD_REQUEST(nbns_server, TAG_NBNS_SERVER);
	ADD_REQUEST(name_server, TAG_NAME_SERVER);
	ADD_REQUEST(rlp_server, TAG_RLP_SERVER);
	ADD_REQUEST(cookie_server, TAG_COOKIE_SERVER);
	ADD_REQUEST(impress_server, TAG_IMPRESS_SERVER);
	ADD_REQUEST(bootsize, TAG_BOOT_SIZE);
	ADD_REQUEST(dump_file, TAG_DUMP_FILE);
	ADD_REQUEST(perform_router_discovery, TAG_PERFORM_ROUTER_DISCOVERY);
	ADD_REQUEST(static_route, TAG_STATIC_ROUTE);
	if (  !hp->flags.client_fqdn  /* bootptab entry for client didn't contain 'cf' tag */
			|| 
		  (hp->flags.client_fqdn && hp->client_fqdn) /* bootptab entry for client did contain 'cf' tag and it was nonzero */
		)
		ADD_REQUEST(client_fqdn, TAG_CLIENT_FQDN);
	ADD_REQUEST(ethernet_encapsulation, TAG_ETHERNET_ENCAPSULATION);
	ADD_REQUEST(nbdd_server, TAG_NBDD_SERVER);
	ADD_REQUEST(smtp_server, TAG_SMTP_SERVER);
	ADD_REQUEST(pop3_server, TAG_POP3_SERVER);
	ADD_REQUEST(nntp_server, TAG_NNTP_SERVER);
	ADD_REQUEST(www_server, TAG_WWW_SERVER);
	ADD_REQUEST(finger_server, TAG_FINGER_SERVER);
	ADD_REQUEST(irc_server, TAG_IRC_SERVER);
	ADD_REQUEST(ip_forwarding, TAG_IP_FORWARDING);
	ADD_REQUEST(ip_source_routing, TAG_IP_SOURCE_ROUTING);
	ADD_REQUEST(ip_ttl, TAG_IP_TTL);
	ADD_REQUEST(all_subnets_are_local, TAG_ALL_SUBNETS_ARE_LOCAL);
	ADD_REQUEST(perform_mask_discovery, TAG_PERFORM_MASK_DISCOVERY);
	ADD_REQUEST(mask_supplier, TAG_MASK_SUPPLIER);
	ADD_REQUEST(router_solicitation_addr, TAG_ROUTER_SOLICITATION_ADDR);
	ADD_REQUEST(trailer_encapsulation, TAG_TRAILER_ENCAPSULATION);
	ADD_REQUEST(tcp_ttl, TAG_TCP_TTL);
	ADD_REQUEST(tcp_keepalive_garbage, TAG_TCP_KEEPALIVE_GARBAGE);
	ADD_REQUEST(xwindows_font_server, TAG_XWINDOWS_FONT_SERVER);
	ADD_REQUEST(xwindows_display_manager, TAG_XWINDOWS_DISPLAY_MANAGER);
	ADD_REQUEST(nisplus_domain, TAG_NISPLUS_DOMAIN);
	ADD_REQUEST(nisplus_server, TAG_NISPLUS_SERVER);

	req_opts[0] = i ;	/* put count of requested options in first byte */
	if (rc = do_req_opts(hp, req_opts, bp, cur, f)) {
		return rc; /* error, don't try to continue */
	}

	if (hp->flags.generic) {
		/* Add all the generic tags.
		*/
		unsigned int generic_length_remaining; 
		byte * generic_data; 

		generic_length_remaining = hp->generic->length;
		generic_data = hp->generic->data;

		while ( generic_length_remaining ) {
			/* Work with the tag (code/len/data) starting at generic_data.
			   We do not try to verify the ptrs are non-NULL or the length fits within the remaining
			   shared_bindata{}, as we constructed this structure when parsing bootptab, ensuring it is valid.
			*/ 
			unsigned char tag_code;
			byte tag_datalen;
			byte * tag_data;

			tag_code = *generic_data;
			tag_datalen = *(generic_data+1);
			tag_data = generic_data+2;

			/* XXX We should check to see if this option has already been inserted into the
			   reply, and if so, skip it.  But since the structure we use to keep track of
			   that (struct flags f) is "indexed" by well-known name instead of option code,
			   we can't do that for generic options.  We should replace that structure with
			   a struct Bitvector of size 256 to solve this.
			*/

			if (rc = insert_generic_opt((struct bootp *) bp, cur, tag_code, tag_data, (int) tag_datalen))
				return rc; /* error, do not try to continue inserting options*/

			/* XXX We should record that we've now inserted this option into the reply,
			   so we may avoid doing so again.  See the previous comment. 
			*/

			generic_data += (2 + tag_datalen);
			generic_length_remaining -= (2 + tag_datalen);
		}
	}

	return 0; /* success */

}



/* Fill in vendor options field in DHCP reply packet for those items
requested by client in a Parameter Request List option.  Return 0 on success.
Avoid inserting any requested option that has already been inserted.
Return 0 on complete success.
As soon as we run into an error, give up inserting any more options, return non-zero.
*/

int do_req_opts(hp, req_opts, bp, cur, f)
struct host *hp;		/* client info, e.g. from bindings */
u_char *req_opts;		/* list of options in Parameter Request List specified by client */
						/* Note that first byte is the count of (one-byte) options that follow */
u_char *bp;				/* reply packet we are building */
struct opts *cur;		/* records current location & length of the options data in reply packet */
struct flag *f;			/* An input var indicating which options have *already* been inserted into reply packet,
						   and an ouput var, since we update it as we go along. */
{
	int i,j,rc;

	if (!req_opts)
		return 0;

	for (i=1; i < (int) (req_opts[0]+1); i++) {
		rc=0;
		switch(req_opts[i]){

			case TAG_DHCP_SERVERID:
			case TAG_DHCP_IPLEASE:
			case TAG_DHCP_REBIND:
			case TAG_DHCP_RENEW:
			case TAG_DHCP_USERCLASS:
			case TAG_VENDOR:
				/* These options are ones that we handle elsewhere, either because they cause
			   	   values to be inserted elsewhere (not in the options field), or because they
			   	   request options that we will always return in DHCPACK even if not requested. 
			   	   So we simply ignore them if requested here.
				*/
				break;

			case TAG_PAD:
				break;
			case TAG_NODE_TYPE:
				if (!f->node_type && hp->flags.node_type) {
					f->node_type=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_NODE_TYPE,hp->node_type);
				}
				break;
			case TAG_SUBNET_MASK:
				if (!f->subnet_mask && hp->flags.subnet_mask) {
					f->subnet_mask=1;
					rc=insert_ulong_opt((struct bootp *)bp,cur,TAG_SUBNET_MASK,hp->subnet_mask.s_addr);
				}
				break;
			case TAG_NB_SCOPE:
				if (!f->nb_scope && hp->flags.nb_scope) {
					f->nb_scope=1;
					rc=insert_str_opt((struct bootp *)bp,cur,TAG_NB_SCOPE,hp->nb_scope->string);
				}
				break;
			case TAG_DOMAIN_NAME:
				if (!f->domain_name) { 
					f->domain_name=1;
					if (hp->flags.domain_name)
						rc=insert_str_opt((struct bootp *)bp,cur,TAG_DOMAIN_NAME,hp->domain_name->string);
					else { /* no domain name tag was specified */
						/* infer a domain name by grabbing all but the first component of the hostname */
						for (j=0; hp->hostname->string[j] && hp->hostname->string[j] != '.'; j++)
							/* walk down hostname through the first dot */
							;
						if (strlen(hp->hostname->string+j+1))
							rc=insert_str_opt((struct bootp *)bp,cur,TAG_DOMAIN_NAME,hp->hostname->string+j+1);
						/* else hostname had just one component, so we cannot infer a domain name, don't insert one */
					}
				}
				break;
			case TAG_HOST_NAME:
				if (!f->name_switch) {
					f->name_switch=1;
					f->send_name=1;	/* I'm guessing this is useless duplicate info that tracks var above */
					rc=insert_str_opt((struct bootp *)bp,cur,TAG_HOST_NAME,hp->hostname->string);
				}
				break;
			case TAG_COOKIE_SERVER:
		    	if (!f->cookie_server && hp->flags.cookie_server) {
		    		f->cookie_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_COOKIE_SERVER,hp->cookie_server->addr,
						hp->cookie_server->addrcount);
				}
				break;
			case TAG_IMPRESS_SERVER:
		    	if (!f->impress_server && hp->flags.impress_server) {
		    		f->impress_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_IMPRESS_SERVER,hp->impress_server->addr,
						hp->impress_server->addrcount);
				}
				break;
			case TAG_LOG_SERVER:
		    	if (!f->log_server && hp->flags.log_server) {
		    		f->log_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_LOG_SERVER,hp->log_server->addr,
						hp->log_server->addrcount);
				}
				break;
			case TAG_LPR_SERVER:
		    	if (!f->lpr_server && hp->flags.lpr_server) {
		    		f->lpr_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_LPR_SERVER,hp->lpr_server->addr,
						hp->lpr_server->addrcount);
				}
				break;
			case TAG_NAME_SERVER:
		    	if (!f->name_server && hp->flags.name_server) {
		    		f->name_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_NAME_SERVER,hp->name_server->addr,
						hp->name_server->addrcount);
				}
				break;
			case TAG_RLP_SERVER:
		    	if (!f->rlp_server && hp->flags.rlp_server) {
		    		f->rlp_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_RLP_SERVER,hp->rlp_server->addr,
						hp->rlp_server->addrcount);
				}
				break;
			case TAG_TIME_SERVER:
		    	if (!f->time_server && hp->flags.time_server) {
		    		f->time_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_TIME_SERVER,hp->time_server->addr,
						hp->time_server->addrcount);
				}
				break;
			case TAG_NIS_SERVER:
		    	if (!f->nis_server && hp->flags.nis_server) {
		    		f->nis_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_NIS_SERVER,hp->nis_server->addr,
						hp->nis_server->addrcount);
				}
				break;
			case TAG_NTP_SERVER:
		    	if (!f->ntp_server && hp->flags.ntp_server) {
		    		f->ntp_server=1; 
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_NTP_SERVER,hp->ntp_server->addr,
						hp->ntp_server->addrcount);
				}
				break;
			case TAG_GATEWAY:
		    	if (!f->gateway && hp->flags.gateway) {
					f->gateway=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_GATEWAY,hp->gateway->addr,
						hp->gateway->addrcount);
				}
				break;
			case TAG_DOMAIN_SERVER:
		    	if (!f->domain_server && hp->flags.domain_server) {
		    		f->domain_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_DOMAIN_SERVER,hp->domain_server->addr,
						hp->domain_server->addrcount);
				}
				break;
			case TAG_NBNS_SERVER: 
		    	if (!f->nbns_server && hp->flags.nbns_server) {
		    		f->nbns_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_NBNS_SERVER,hp->nbns_server->addr,
						hp->nbns_server->addrcount);
				}
				break;
			case TAG_BROADCAST_ADDR:
				if (!f->broadcast_addr && hp->flags.broadcast_addr) {
					f->broadcast_addr=1;
					rc=insert_ulong_opt((struct bootp *)bp,cur,TAG_BROADCAST_ADDR,hp->broadcast_addr.s_addr);
				}
				break;
			case TAG_SWAP_SERVER:
				if (!f->swap_server && hp->flags.swap_server) {
					f->swap_server=1;
					rc=insert_ulong_opt((struct bootp *)bp,cur,TAG_SWAP_SERVER,hp->swap_server.s_addr);
				}
				break;
			case TAG_BOOT_SIZE: /* BUG - what if bootptab specify bootsize auto option - see how bootp handles this */
				if (!f->bootsize && hp->flags.bootsize) {
					f->bootsize=1;
					/* we've been carrying hp->bootsize as a uint32, but the option is really a uint16.  So before
					   sending it, lop off the high bytes and cast it.  */
					rc=insert_ushort_opt((struct bootp *)bp,cur,TAG_BOOT_SIZE, htons( (unsigned short) (hp->bootsize & 0xFFFFFFFF)));
				}
				break;
			case TAG_TIME_OFFSET: 
				if (!f->time_offset && hp->flags.time_offset) {
					f->time_offset=1;
					rc=insert_ulong_opt((struct bootp *)bp,cur,TAG_TIME_OFFSET,htonl(hp->time_offset));
				}
				break;
			case TAG_DUMP_FILE:
				if (!f->dump_file && hp->flags.dump_file) {
					f->dump_file=1;
					rc=insert_str_opt((struct bootp *)bp,cur,TAG_DUMP_FILE,hp->dump_file->string);
				}
				break;
			case TAG_EXTEN_FILE:
				if (!f->exten_file && hp->flags.exten_file) {
					f->exten_file=1;
					rc=insert_str_opt((struct bootp *)bp,cur,TAG_EXTEN_FILE,hp->exten_file->string);
				}
				break;
			case TAG_ROOT_PATH:
				if (!f->root_path && hp->flags.root_path) {
					f->root_path=1;
					rc=insert_str_opt((struct bootp *)bp,cur,TAG_ROOT_PATH,hp->root_path->string);
				}
				break;
			case TAG_NIS_DOMAIN:
				if (!f->nis_domain && hp->flags.nis_domain) {
					f->nis_domain=1;
					rc=insert_str_opt((struct bootp *)bp,cur,TAG_NIS_DOMAIN,hp->nis_domain->string);
				}
				break;
			case TAG_PERFORM_ROUTER_DISCOVERY:
				if (!f->perform_router_discovery && hp->flags.perform_router_discovery) {
					f->perform_router_discovery=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_PERFORM_ROUTER_DISCOVERY,hp->perform_router_discovery);
				}
				break;
			case TAG_STATIC_ROUTE:
		    	if (!f->static_route && hp->flags.static_route) {
		    		f->static_route=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_STATIC_ROUTE,hp->static_route->addr,
						hp->static_route->addrcount);
				}
				break;

			case TAG_CLIENT_FQDN:
				/* if this client's bootptab entry specified a cf tag with value zero, we are not allowed
				   to send a Client FQDN option to this client */
				if (hp->flags.client_fqdn && !hp->client_fqdn)
					break;

				if (!f->client_fqdn) {

					int hostname_len; /* tmp storage for length of hp->hostname->string */

					f->client_fqdn=1;

					/* There's no check to verify that hp->hostname exists.  By def it always exists, we don't
					   even have a flags member for it.  So we don't need to check this. */

					hostname_len = strlen(hp->hostname->string); 
					if (hostname_len > MAX_FQDN_LEN) { /* ensures strcpy() below is safe */
						report(LOG_ERR, "  cannot insert option %d, length of client hostname '%s' is greater than %d ", 
							TAG_CLIENT_FQDN, hp->hostname->string, MAX_FQDN_LEN);
					} else {
						byte cf_flags;
						char client_fqdn_buf[MAX_FQDN_LEN + 4]; /* tmp space to construct Client FQDN Option value:
																	1 for 'flags'
																	1 for 'rcode1'
																	1 for 'rcode2'
																	MAX_FQDN_LEN for the ASCII name
																	1 for null terminator for name that strcpy() will copy (not actually sent to client)
																*/

						/* construct the value of the option in the char* client_fqdn_buf. */

						cf_flags = 0x0; /* init */
						if (!hp->flags.client_fqdn) {
							/* no :cf: tag in bootptab entry, default is "No Dynamic DNS" */
							cf_flags |= (TAGOPTS_CLIENT_FQDN_FLAG_S | TAGOPTS_CLIENT_FQDN_FLAG_O);
						} else if (hp->client_fqdn & CF_STATIC) {
							/* :cf: tag specified in entry says "No Dynamic DNS" */
							cf_flags |= (TAGOPTS_CLIENT_FQDN_FLAG_S | TAGOPTS_CLIENT_FQDN_FLAG_O);
						} else {
							/* we don't support any other values for :cf: tag at this time */
							report(LOG_ERR, "  cannot insert option %d, 'cf' value 0X%02X is not valid for bootptab entry '%s'",
								TAG_CLIENT_FQDN, hp->client_fqdn, hp->hostname->string);
							break;
						}
							
						client_fqdn_buf[0] = cf_flags;
						client_fqdn_buf[1] = 0x0; /* rcode1 */
						client_fqdn_buf[2] = 0x0; /* rcode2 */
						strcpy(client_fqdn_buf+3, hp->hostname->string); /* safe, see above */

						/* although this option is not a string, we'll take advantage of insert_string_opt() to insert it.
						   That routine fortunately can handle arbitrary binary data, as it takes a bytecount as its last arg.
						   Note that we pass a bytecount that is 1 less than the actual length of the char buffer we've just
						   constructed.  That's because we don't want the last byte (a NULL that strcpy() copied from hp->hostname->string)
						   in the actual option we send to the client, as the spec for this option says the ASCII string is not to
						   be null-delimited.
						*/
						rc=insert_string_opt((struct bootp *)bp, cur, TAG_CLIENT_FQDN, client_fqdn_buf, hostname_len+3);
					}
				}
				break;

			case TAG_ETHERNET_ENCAPSULATION:
				if (!f->ethernet_encapsulation && hp->flags.ethernet_encapsulation) {
					f->ethernet_encapsulation=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_ETHERNET_ENCAPSULATION,hp->ethernet_encapsulation);
				}
				break;

			case TAG_NBDD_SERVER:
		    	if (!f->nbdd_server && hp->flags.nbdd_server) {
		    		f->nbdd_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_NBDD_SERVER,hp->nbdd_server->addr,
						hp->nbdd_server->addrcount);
				}
				break;

			case TAG_SMTP_SERVER:
		    	if (!f->smtp_server && hp->flags.smtp_server) {
		    		f->smtp_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_SMTP_SERVER,hp->smtp_server->addr,
						hp->smtp_server->addrcount);
				}
				break;
			case TAG_POP3_SERVER:
		    	if (!f->pop3_server && hp->flags.pop3_server) {
		    		f->pop3_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_POP3_SERVER,hp->pop3_server->addr,
						hp->pop3_server->addrcount);
				}
				break;
			case TAG_NNTP_SERVER:
		    	if (!f->nntp_server && hp->flags.nntp_server) {
		    		f->nntp_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_NNTP_SERVER,hp->nntp_server->addr,
						hp->nntp_server->addrcount);
				}
				break;
			case TAG_WWW_SERVER:
		    	if (!f->www_server && hp->flags.www_server) {
		    		f->www_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_WWW_SERVER,hp->www_server->addr,
						hp->www_server->addrcount);
				}
				break;
			case TAG_FINGER_SERVER:
		    	if (!f->finger_server && hp->flags.finger_server) {
		    		f->finger_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_FINGER_SERVER,hp->finger_server->addr,
						hp->finger_server->addrcount);
				}
				break;
			case TAG_IRC_SERVER:
		    	if (!f->irc_server && hp->flags.irc_server) {
		    		f->irc_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_IRC_SERVER,hp->irc_server->addr,
						hp->irc_server->addrcount);
				}
				break;
			case TAG_IP_FORWARDING:
				if (!f->ip_forwarding && hp->flags.ip_forwarding) {
					f->ip_forwarding=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_IP_FORWARDING,hp->ip_forwarding);
				}
				break;
			case TAG_IP_SOURCE_ROUTING:
				if (!f->ip_source_routing && hp->flags.ip_source_routing) {
					f->ip_source_routing=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_IP_SOURCE_ROUTING,hp->ip_source_routing);
				}
				break;
			case TAG_IP_TTL:
				if (!f->ip_ttl && hp->flags.ip_ttl) {
					f->ip_ttl=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_IP_TTL,hp->ip_ttl);
				}
				break;
			case TAG_ALL_SUBNETS_ARE_LOCAL:
				if (!f->all_subnets_are_local && hp->flags.all_subnets_are_local) {
					f->all_subnets_are_local=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_ALL_SUBNETS_ARE_LOCAL,hp->all_subnets_are_local);
				}
				break;
			case TAG_PERFORM_MASK_DISCOVERY:
				if (!f->perform_mask_discovery && hp->flags.perform_mask_discovery) {
					f->perform_mask_discovery=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_PERFORM_MASK_DISCOVERY,hp->perform_mask_discovery);
				}
				break;
			case TAG_MASK_SUPPLIER:
				if (!f->mask_supplier && hp->flags.mask_supplier) {
					f->mask_supplier=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_MASK_SUPPLIER,hp->mask_supplier);
				}
				break;
			case TAG_ROUTER_SOLICITATION_ADDR:
				if (!f->router_solicitation_addr && hp->flags.router_solicitation_addr) {
					f->router_solicitation_addr=1;
					rc=insert_ulong_opt((struct bootp *)bp,cur,TAG_ROUTER_SOLICITATION_ADDR,hp->router_solicitation_addr.s_addr);
				}
				break;
			case TAG_TRAILER_ENCAPSULATION:
				if (!f->trailer_encapsulation && hp->flags.trailer_encapsulation) {
					f->trailer_encapsulation=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_TRAILER_ENCAPSULATION,hp->trailer_encapsulation);
				}
				break;
			case TAG_TCP_TTL:
				if (!f->tcp_ttl && hp->flags.tcp_ttl) {
					f->tcp_ttl=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_TCP_TTL,hp->tcp_ttl);
				}
				break;
			case TAG_TCP_KEEPALIVE_GARBAGE:
				if (!f->tcp_keepalive_garbage && hp->flags.tcp_keepalive_garbage) {
					f->tcp_keepalive_garbage=1;
					rc=insert_byte_opt((struct bootp *)bp,cur,TAG_TCP_KEEPALIVE_GARBAGE,hp->tcp_keepalive_garbage);
				}
				break;
			case TAG_XWINDOWS_FONT_SERVER:
		    	if (!f->xwindows_font_server && hp->flags.xwindows_font_server) {
		    		f->xwindows_font_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_XWINDOWS_FONT_SERVER,hp->xwindows_font_server->addr,
						hp->xwindows_font_server->addrcount);
				}
				break;
			case TAG_XWINDOWS_DISPLAY_MANAGER:
		    	if (!f->xwindows_display_manager && hp->flags.xwindows_display_manager) {
		    		f->xwindows_display_manager=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_XWINDOWS_DISPLAY_MANAGER,hp->xwindows_display_manager->addr,
						hp->xwindows_display_manager->addrcount);
				}
				break;
			case TAG_NISPLUS_DOMAIN:
				if (!f->nisplus_domain && hp->flags.nisplus_domain) {
					f->nisplus_domain=1;
					rc=insert_str_opt((struct bootp *)bp,cur,TAG_NISPLUS_DOMAIN,hp->nisplus_domain->string);
				}
				break;
			case TAG_NISPLUS_SERVER:
		    	if (!f->nisplus_server && hp->flags.nisplus_server) {
		    		f->nisplus_server=1;
					rc=insert_addrs_opt((struct bootp *)bp,cur,TAG_NISPLUS_SERVER,hp->nisplus_server->addr,
						hp->nisplus_server->addrcount);
				}
				break;


			/* BUG: we should also see if the requested option matches any of the generic-specified
			   options (if hp->flags.generic); if found, and this option has not already been inserted
			   (e.g. by name), we should insert it.  This needs to be implemented.
			   One challenge is that the generic options were stored as a single flat array of binary data, not
			   as a set of individual options.  To determine if option number N is present among the specified
			   generic options, presently we'd have to walk the flat array, parsing out each generic option
			   until we find it (if present), then grab the data from it to send.  We really need to change the
			   way we store generic options read from bootptab into a more amenable form that allows quick
			   access to each one.
			*/

			default:
				if (debug > 9)
					report(LOG_INFO,"  ignoring unknown or unexpected option (%d) in client's Parameter Request List option", req_opts[i]);
				break;
		}
		if (rc) 
			return rc;	/* error inserting some option, give up */
	}

	return 0;
}

void send_dhcpnack(pkt,cliid,msg,max_len,cliid_explicit)
struct bootp *pkt;
struct shared_bindata *cliid;
char *msg;
unsigned int max_len;
boolean cliid_explicit;
{
	unsigned char *opts;
	int32 len;
	struct opts opt;
	struct bootp *tmp;
	unsigned int delay_usec = 0;

	tmp=(struct bootp*)send_buf;
	bcopy(pkt,tmp,pktlen);

	tmp->bp_op=BOOTREPLY;
	tmp->bp_hops=0;
	tmp->bp_secs=0;
	tmp->bp_ciaddr.s_addr=0;
	tmp->bp_siaddr.s_addr=0;
	tmp->bp_yiaddr.s_addr=0;
	tmp->bp_flags|=0x8000;

	opt.cur=0;
	opt.len=max_len;
	/* Insert the DHCP Message Type option first, because some clients incorrectly assume that
	   if this option is not first, the message is BOOTP, or bad. */
	insert_byte_opt(tmp,&opt,TAG_DHCP_MSG,DHCPNACK);
	insert_ulong_opt(tmp,&opt,TAG_DHCP_SERVERID,my_ip_addr.s_addr);
	if (cliid_explicit)
		insert_string_opt(tmp,&opt,TAG_DHCP_CLIID,cliid->data,cliid->length);
	insert_str_opt(tmp,&opt,TAG_MESSAGE,msg);
	insert_end_opt(tmp,&opt);
	if (debug>1)
		report(LOG_INFO,"Sending DHCPNACK: %s",msg);
	nacks_sent++;

	len=300;
	if(opt.cur<VEND_OFFSET)
		len=max_len;
	if(opt.cur>len)
		len=opt.cur;

	if (delay_reply_dhcpnak)
		delay_usec = delay_reply_dhcpnak;
	else if (delay_reply_default)
		delay_usec = delay_reply_default;

	send_dhcp_reply(tmp,len,0,delay_usec);
}

		

void send_dhcp_reply(pkt,send_len,dest,delay_usec)
struct bootp *pkt;
int32 send_len;
unsigned int32 dest; /* network byte order */
unsigned int delay_usec;
{
	struct sockaddr_in dest_addr;
    unsigned char *ha;
    int len, haf;
    struct in_addr dst;

	dest_addr.sin_family = AF_INET;
	dest_addr.sin_port = htons(bootpc_port);


	/* Here's our strategy for determine where to send the packet:

		If we were explicitly told where to send the packet, use that.

		Else if ciaddr is set, and broadcast bit is off, send to ciaddr.  
		(Ignoring the broadcast bit test, that's as per RFC; a client that fills
		in ciaddr must be prepared to accept a packet sent there.  And if the client's
		not on ciaddr's network, it is understood that the reply will never reach it.
		I added the broadcast bit test.)
		
		Else if giaddr is set, send to giaddr, changing the dstPort to bootpserver.
		
		Else if yiaddr==0, send to broadcast.  We understand that this won't reach client
		if client was non-local and unicast the request to us (preventing giaddr from
		being set).  We understand that this won't reach client if client was local
		AND we're multihomed AND the OS chooses to send the broadcast out an interface
		to which the client is not attached.  (We could try to fix that by trying to 
		detect this case, and then sending to a directed broadcast address to force it
		out a particular interface.  But that may not be accepted by some bootp clients,
		since bootp spec says that bootp broadcast must be sent to 255.255.255.255.)
		
		Else if yiaddr is non-local, send to yiaddr.  We understand that the test for
		non-local may not return an appropriate value if the server is multihomed.
		
		Else if broadcast bit is set, send to broadcast.
		
		Else send to yiaddr, priming the arp cache
	*/

	if (dest) {
		dest_addr.sin_addr.s_addr=dest;
		if (debug > 6)
			report(LOG_INFO, "  sending reply to specified reply address override: %s",
				inet_ntoa(dest_addr.sin_addr));
	} else if ((pkt->bp_ciaddr.s_addr) && !(ntohs(pkt->bp_flags) & 0x8000)){
		dest_addr.sin_addr.s_addr=pkt->bp_ciaddr.s_addr;
		if (debug > 6)
			report(LOG_INFO, "  sending reply to client-specified ciaddr %s",
				inet_ntoa(dest_addr.sin_addr));
	} else if (pkt->bp_giaddr.s_addr) {
		dest_addr.sin_addr.s_addr=pkt->bp_giaddr.s_addr;
		dest_addr.sin_port=htons(bootps_port);
		if (debug > 6)
			report(LOG_INFO, "  sending reply to giaddr: %s",
				inet_ntoa(dest_addr.sin_addr));
	} else if (! pkt->bp_yiaddr.s_addr) {
		dest_addr.sin_addr.s_addr = INADDR_BROADCAST;
		if (debug > 6)
			report(LOG_INFO, "  yiaddr==0, sending reply to broadcast");
	} else if (! is_local(& pkt->bp_yiaddr)) {
		dest_addr.sin_addr.s_addr = pkt->bp_yiaddr.s_addr;
		if (debug > 6)
			report(LOG_INFO, "  yiaddr is non-local, sending reply there: %s",
				inet_ntoa(dest_addr.sin_addr));
	} else if (ntohs(pkt->bp_flags) & 0x8000) {
		dest_addr.sin_addr.s_addr = INADDR_BROADCAST;
		if (debug > 6)
			report(LOG_INFO, "  yiaddr (%s) is local and bootp broadcast bit is set, sending reply to broadcast",
			inet_ntoa(pkt->bp_yiaddr));
	} else {
		if (debug > 6)
			report(LOG_INFO, "  yiaddr is local, sending reply there: %s", inet_ntoa(pkt->bp_yiaddr));
		dst = pkt->bp_yiaddr;
		ha = pkt->bp_chaddr;
		len = pkt->bp_hlen;
		if (len > MAXHADDRLEN)
	    	len = MAXHADDRLEN;
		haf = (int) pkt->bp_htype;
		if (haf == 0)
	    	haf = HTYPE_ETHERNET;
		if (debug > 6)
	    	report(LOG_INFO, "  setarp %s - %s", inet_ntoa(dst), haddrtoa(ha, len));
		setarp(s, &dst, haf, ha, len);
		dest_addr.sin_addr=dst;
	}

	if (delay_usec)
		sleep_us(delay_usec);

	if(sendto(s,(char*)pkt,send_len,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr)) <0)
		 report(LOG_ERR, "  sendto: %s", get_network_errmsg());
}



void dhcp_garbage_collect()
{
 unsigned hashcode,iphashcode;
 struct host *hp,*hp2;
 struct order_list *t;
 struct cable *c;
 struct lastbind *oldbinding, tempoldbinding;


	if(debug>10)
		report(LOG_INFO,"DHCP garbage collect begin");

	for (hp=(struct host*)hash_FirstEntry(boundhshtbl); hp; hp=(struct host*)hash_NextEntry(boundhshtbl)) {

#ifdef DEBUG
		if (debug > 15) {
			if (! hp->cliid) {
				report(LOG_ERR, "dhcp_garbage_collect(): entry's hp->cliid is NULL, skipping!");
				continue;
			}
			report_cliid(LOG_DEBUG, hp->cliid);
		}
#endif

		if (!hp->flags.expire)  { /* lease is infinite */
			if (expire_infinite_dynamics) {	/* we want to expire it anyway, after some time */
				tempoldbinding.cliid = hp->cl;
				hashcode = hash_HashFunction(hp->cl->data,hp->cl->length);
				oldbinding = (struct lastbind*)hash_Lookup(lastbindhshtbl,hashcode,lastbindcmp,&tempoldbinding);
				if (!oldbinding) {
					report(LOG_ERR, "dhcp_garbage_collect(): entry in boundhshtbl has infinite lease, but no entry in lastbindhshtbl!");
					report(LOG_ERR, "...something is very wrong!");
					continue;	/* not much we can do */
				}
				if (now > oldbinding->last_touch + expire_infinite_dynamics) {
					if (debug>11 || log_expirations)
						report(LOG_INFO, "%s is infinite, but older than expire_infinite_dynamics; will remove binding", inet_ntoa(hp->iaddr));
					/* should we increment ips_expired here ? */
					reuse_addr(hp); 
					/* note reuse_addr() also sets hp->expire to NOW in the lastbinding entry.  That's good, as it means
					   that the next time we start, we won't re-instate the binding, even if expire_infinite_dynamics is unset at
					   that time.  But it does mean that examining the lastbind info will show the entry had a non-infinite
					   lease, which (for static addresses) makes them appear to have been assigned via DHCP instead of BOOTP.
					   That's confusing, but not a real problem, since we're not going to re-instate them when restarting
					   anyway, as they have expired. */
 
				} else {
					if (debug>11)
						report(LOG_INFO, "%s is infinite, and newer than expire_infinite_dynamics; retained", inet_ntoa(hp->iaddr));
				}
				continue;
			} else {	/* we never expire infinite leases */
#ifdef DEBUG
				if (debug > 15)
					report(LOG_DEBUG, " %s is infinite; retained", inet_ntoa(hp->iaddr));
#endif
				continue;
			}
		} else { /* lease is finite */

			if (hp->expire > now) {
#ifdef DEBUG
				if (debug > 15)
					report(LOG_DEBUG, " %s is finite and hasn't expired; retained", inet_ntoa(hp->iaddr));
#endif
				continue; /* keep lease, so proceed to next entry in bound table */
			} else {
				ips_expired++;
				if(debug>11 || log_expirations)
					report(LOG_INFO," %s is finite and has expired, will remove binding",inet_ntoa(hp->iaddr));
				reuse_addr(hp);
			}
 		}

	} /* walk through bound table */


	if(debug>10)
		report(LOG_INFO,"DHCP garbage collect end");

	now=time(0);
	dhcp_alarm=now+gcinterval;
}



/* Given a struct host * hp (a record that is a current binding, or intended to become a current
binding), modify it to inherit appropriate information from the bootptab.  First take into account any 
$C, $V, or $DHCP override entries in the bootptab.   Then take into account the bootptab entry for
hp's IP address.  We make *sure* the entry has a lease time, limiting it to no more than 'secs' if specified.
We also make *sure* the entry has a renew time and a rebind time, setting each to the default fractions
of the lease time (I'm not sure we really need to do this).
In addition to modifying it, we return it; return NULL on error. */

struct host *create_lease(hp,cliid,vend,user,secs)
struct host *hp;	/* the struct host we modify */
struct shared_bindata *cliid,*vend,*user;
unsigned secs;		/* if nonzero, ensure the lease is no more than this */
{
	struct host *hp2;
	unsigned hashcode;
	char override[2000];
	

	/* If request packet contained user class info option, vendor specific info option, else is DHCP, take into
	   account $C, $V, or $DHCP entry in bootptab.  Do this by setting 'override' to the name of the bootptab
	   entry we should look for */
	if(user){ /* request packet contains a user class info option */
		override[0]='$';
		override[1]='C';
		override[2]=0;
		for(ui=0;ui<user->length;ui++)
			sprintf(override+strlen(override),"%02X",user->data[ui]);
	} else if(vend){ /* request packet contains a vendor specific info option */
		override[0]='$';
		override[1]='V';
		override[2]=0;
		for(ui=0;ui<vend->length;ui++)
			sprintf(override+strlen(override),"%02X",vend->data[ui]);
	} else  /* request packet contains neither of above, but is dhcp */
		strcpy(override,"$DHCP");

	/* look for bootptab entry with name that matches value of 'override' */
	hashcode=hash_HashFunction((u_char*)override,strlen(override));
	hp2=(struct host *)hash_Lookup(nmhashtable,hashcode,nmcmp,override);
	if(hp2) /* there was an entry with this name! */
		fill_defaults2(hp,hp2); /* modify hp; info from hp2 is used to override info in hp */


	/* Locate the bootptab entry corresponding to hp's IP address, if any */
	if (hp->flags.iaddr) {
		hp2=(struct host*)hash_Lookup(iphashtable,hp->iphashcode,ipcmp, &(hp->iaddr.s_addr));
		if(!hp2)
			return NULL;	/* BUG: is that right? */
		fill_defaults2(hp,hp2); /* modify hp; info from hp2 is used to override info in hp */
	} else {
		/* BUG: should we try to find a bootptab entry corresponding to hp's hw address/cliid ?? */
		report(LOG_ERR, "create_lease(): called with hp where hp->flags.iaddr is FALSE");
		return NULL;
	}
	

	if(!hp->flags.lease){	/* ensure entry has a lease time */
		/* this only happens if we didn't fill in lease length via the steps above */
		hp->flags.lease=TRUE;
		hp->lease=DEFAULT_LEASE;
	}

	if(secs){	/* caller wants lease limited to no more than this */
		if(hp->flags.max_lease)
			if(secs<hp->flags.max_lease)
				hp->lease=secs;
		else
			if(secs<hp->lease)
				hp->lease=secs;
	}

	if(!hp->flags.renew){ /* ensure the entry has a renew time; I'm not sure this is really necessary */
		/* this only happens if we didn't fill in renew length via the steps above */
		hp->renew=hp->lease/2; 	/* we're using the default percentage from rfc */
		hp->flags.renew=TRUE;
	}
	if(!hp->flags.rebind){ /* ensure the entry has a rebind time; I'm not sure this is really necessary */
		/* this only happens if we didn't fill in rebind length via the steps above */
		hp->rebind=hp->lease*0.85;	/* we're using the default percentage from rfc */
		hp->flags.rebind=TRUE;
	}
		
	hp->flags.complete=TRUE; /* this flag seems to indicate that create_lease() has been used to 'complete'
							    this entry. */
	return hp;
}


/* Create a new struct host.  We read 'hp' as template to copy a few of the most critical items;
hp should contain hp->iaddr, hp->hostname, hp_iphashcode.
We'll actually use 'cl' (increasing its linkcount).
We return a ptr to an entirely new struct host we malloc. This is the primary way to create new struct hosts
that have to stick around (i.e. not just for temporary storage within a routine). 
It is the responsibility of the caller to set hp->linkcount to 1 (or more).
When you are done with it, call free_host() to destroy it. */

struct host *create_stub(hp,cl)
struct host *hp;		/* template host */
struct client_info *cl;	/* cl to link in */
{
	struct host *t;		

	if (! hp->flags.iaddr) {
		report(LOG_ERR, "create_stub(): called with hp where hp->flags.iaddr is FALSE");
		return NULL;
	}

	t=(struct host*)smalloc(sizeof(struct host));  /* that is where we build the one to return */
	bzero(t,sizeof(struct host));

	t->linkcount=0; 	/* yes, zero not one; it gets incremented later by the caller */

	t->flags.iaddr=TRUE;
	t->iaddr.s_addr=hp->iaddr.s_addr;

	t->hostname=hp->hostname;	/* note that we link in this shared string, not copy it */
	t->hostname->linkcount++;

	if(hp->flags.reply_addr) {
		t->reply_addr.s_addr=hp->reply_addr.s_addr;
		t->flags.reply_addr=TRUE; 
	}

	t->cl=cl->cliid;		/* note we link it in, not copy it */
	t->cl->linkcount+=2;	/* to account for the link we just made, and the one we're just about to */
	t->cliid = cl->cliid;
	t->flags.cliid=TRUE;

	t->iphashcode=hp->iphashcode;

	return t;
}


/* write an ASSIGNED report showing the vitals of an IP address assignment */
void
report_assignment(category, is_dhcp, is_dynamic, ipaddr, ipaddr_name, cl)
int category;
int is_dhcp;		/* 0=BOOTP 1=DHCP */
int is_dynamic;		/* 0=STATIC, 1=DYNAMIC */
char * ipaddr;		/* ip address being assigned */
char * ipaddr_name;	/* bootptab hostname for ip address being assigned */
struct client_info *cl;	/* from request packet */
{
	char message[2*MAXSTRINGLEN]; 
	struct host *hp;

	/* translate cliid to bootptab entry name (hostname) */
	/* Look for clientid in bootptab.  (Remember that if incoming packet doesn't specify a 
	   DHCP Client ID option, we've computed its ClientID by concatenating its htype with its haddr.) */
	hp =(struct host *)hash_Lookup(clhashtable, cl->clhashcode,cliidcmp,cl->cliid);

#ifdef RETRY_HADDR_AS_CLIID
	/* I've ifdef'd out this because pretending to find a cliid match for clients when there's a mismatch 
	   between the packet's Cliid and the bootptab's cliid can cause problems later as we start storing
	   the inconsistent values in our hashtables.  Besides, we probably shouldn't be pretending to find
	   a match in the first place -- errors in the cliid specified by the bootptab or the client
	   should be corrected, not ignored.
	*/
	if(!hp){ 
		/* We didn't find ClientID in bootptab's clhashtable using ClientID as key.  
		   (Recall that clhashtable contains bootptab entries keyed by cliid (if 'cl' explicitly 
		   specified in bootptab entry) else bootptab entries keyed by haddr.) 
		   Perhaps the incoming packet contains a DHCP ClientID option with a garbage value.
		   Or perhaps the bootptab entry specifies a garbage cliid (via an incorrect 'cl' tag).
		   In either case, we may have more luck if we look up the incoming packet's haddr in
		   bootptab's hwhastable using haddr as key.  
		*/
		unsigned hwhashcode;
		hwhashcode=hash_HashFunction(cl->cliidhw->data+1,cl->cliidhw->length-1);
		hp =(struct host *)hash_Lookup(hwhashtable, hwhashcode,cliidhwlookcmp,cl->cliidhw);
	}
#endif

	if ((category > RACATEGORY_MAX) || (category < 0))
		category = 0;

	sprintf(message, "%s %s %s %s (%s) TO CLIENT (%s) %s",
		report_assignment_category[category],
		(is_dhcp ? "DHCP" : "BOOTP"),
		(is_dynamic ? "DYNAMIC" : "STATIC"),
		ipaddr,
		ipaddr_name,
		(hp ? hp->hostname->string : ""),
		cliid_btoa(cl->cliid));
	report(LOG_INFO, message);
	return;
}

/* Return true if 'inaddr' can be found in known_serverids_hshtbl, else return false.
   Note that since this hashtable may not be populated, you should only call us if 
   known_serverids_hshtbl_inuse is true.
*/
boolean
is_known_server_identifier(inaddr)
	struct in_addr *inaddr;
{
	unsigned hashcode;

	hashcode = hash_HashFunction((u_char*)inaddr, sizeof(struct in_addr));
	return(hash_Exists(known_serverids_hshtbl, hashcode, inaddrcmp, inaddr));
}


