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

CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
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.
******************************************************************/



/*
 * This module reads and interprets the configuration information for dynamic
 * address assignment.  (dhcpd.conf)
 */

#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <arpa/inet.h>  /* inet_ntoa */

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

#define KEYW_NULL		   0
#define KEYW_GATEWAY		   1
#define KEYW_DUMP		   2
#define KEYW_STATIC		   3
#define KEYW_PRIMARY		   4
#define KEYW_SECONDARY		   5
#define KEYW_NETWORK		   6
#define KEYW_DIRECTORY		   7
#define KEYW_FLUSHITEMS		   8
#define KEYW_FLUSHINTERVAL		   9
#define KEYW_BINDINGS		   10
#define KEYW_GCINTERVAL		   11
#define KEYW_EXPIRE_INFINITE_DYNAMICS		   12
#define KEYW_PROXY		   13
#define KEYW_KNOWN_SERVER_IDENTIFIER		   14
#define KEYW_DELAY_REPLY_DHCPOFFER		   15
#define KEYW_DELAY_REPLY_DHCPACK		   16
#define KEYW_DELAY_REPLY_DHCPNAK		   17
#define KEYW_DELAY_REPLY_BOOTPREPLY		   18
#define KEYW_DELAY_REPLY_DEFAULT		   19
#define KEYW_READTABINTERVAL		   20
#define KEYW_READCONFINTERVAL		   21
#define KEYW_CLIENT_STEERING_DELAY_SECS	   22
#define KEYW_CLIENT_STEERING_SERVER_MYSELF	   23
#define KEYW_PRINCE_LOG_INITS_W_UNEXP_LEASE		24
#define KEYW_PRINCE_LOG_LEASE_OFFER_CONVERT		25
#define KEYW_CLIENT_STEERING_ENABLE				26
#define KEYW_PRINCE_LOG_CLIENT_STEERING			27
#define KEYW_CLIENT_STEERING_HASH_SOURCE        28
#define KEYW_CLIENT_STEERING_HASH_FUNCTION		29
#define KEYW_NETWORKALSO						30

#define MAXTOKENS		 256	/* Max tokens per line */
#define MAXENTRYLEN		1024	/* Max line length */




extern void     report();
extern void     eat_whitespace();
extern void     makelower();
extern int      prs_inetaddr();

struct keywordmap {
	char           *keyword;
	int             symbolcode;
};


/*
 * List of keywords used in the dhcpd.conf file.  The order and actual values
 * of the symbol codes (KEYW_. . .) are unimportant, but they must all be
 * unique.  Keywords must be listed in all lowercase.
 */

PRIVATE struct keywordmap keyword_list[] = {
#ifdef DEBUG
	{"dump", KEYW_DUMP},
#endif
	{"gateway", KEYW_GATEWAY},
	{"network", KEYW_NETWORK},
	{"proxy", KEYW_PROXY},
	{"gcinterval", KEYW_GCINTERVAL},
	{"flushitems", KEYW_FLUSHITEMS},
	{"flushinterval", KEYW_FLUSHINTERVAL},
	{"binddir", KEYW_BINDINGS},
	{"expire_infinite_dynamics", KEYW_EXPIRE_INFINITE_DYNAMICS},
	{"known_server_identifier", KEYW_KNOWN_SERVER_IDENTIFIER},
	{"delay_reply_dhcpoffer", KEYW_DELAY_REPLY_DHCPOFFER},
	{"delay_reply_dhcpack", KEYW_DELAY_REPLY_DHCPACK},
	{"delay_reply_dhcpnak", KEYW_DELAY_REPLY_DHCPNAK},
	{"delay_reply_bootpreply", KEYW_DELAY_REPLY_BOOTPREPLY},
	{"delay_reply_default", KEYW_DELAY_REPLY_DEFAULT},
	{"readtabinterval", KEYW_READTABINTERVAL},
	{"readconfinterval", KEYW_READCONFINTERVAL},
	{"client_steering_delay_secs", KEYW_CLIENT_STEERING_DELAY_SECS},
	{"client_steering_server_myself", KEYW_CLIENT_STEERING_SERVER_MYSELF},
	{"princeton_log_inits_with_unexpired_lease", KEYW_PRINCE_LOG_INITS_W_UNEXP_LEASE},
	{"princeton_log_lease_offer_conversions", KEYW_PRINCE_LOG_LEASE_OFFER_CONVERT},
	{"client_steering_enable", KEYW_CLIENT_STEERING_ENABLE},
	{"princeton_log_client_steering", KEYW_PRINCE_LOG_CLIENT_STEERING},
	{"client_steering_hash_source", KEYW_CLIENT_STEERING_HASH_SOURCE},
	{"client_steering_hash_function", KEYW_CLIENT_STEERING_HASH_FUNCTION},
	{"networkalso", KEYW_NETWORKALSO},
};





/*
 * Forward declarations
 */

PRIVATE void    kw_gateway();
PRIVATE void    kw_network();
PRIVATE void    kw_networkalso();
PRIVATE void    kw_proxy();
PRIVATE void    kw_known_server_identifier();
PRIVATE int 	parse_line();
PRIVATE char          **get_tokens();
PRIVATE void	kw_client_steering_delay_secs();
PRIVATE void	kw_client_steering_enable();
char           *savestr();


/*
 * Global variables
 */


hash_tbl * cablehshtbl;
hash_tbl       *gwhshtbl;
extern char *conffile;
time_t   conftime = 0;					/* modification time of conf file that we read most-recently */
time_t   conf_lastread_time = 0;		/* time we last read conf file */

/*
 * Possibly read the conf file.
 * If 'force' is true, we will read the conf file.
 * If 'force' is false, we may read the conf file. 
 */
void 
readconf(force)
	int             force;
{
	FILE           *fp;
	struct stat     st;
	time_t			now;

	if (init) {
		cablehshtbl = hash_Init(HASHTABLESIZE);
		gwhshtbl = hash_Init(HASHTABLESIZE);
		known_serverids_hshtbl = hash_Init(HASHTABLESIZE);
		hash_Reset(cablehshtbl, free_cable);
		hash_Reset(gwhshtbl, free_gw);
		hash_Reset(known_serverids_hshtbl, free_inaddr);
		known_serverids_hshtbl_inuse = 0;
	}

	if (!force) {
		/* Should we skip reading the file? */

		now = time(0);

		if (readconfinterval && ((conf_lastread_time + readconfinterval) > now)) {
			/* readconfinterval feature is enabled, and readconfinterval hasn't passed since we last read conf file */
			/* skip reading the file */
#ifdef DEBUG
			if (debug > 12) {
				char conf_lastread_time_str[28];
				char now_str[28];

				strcpy(conf_lastread_time_str, ctime(&(conf_lastread_time)));
				conf_lastread_time_str[24] = '\0'; /* zap the newline */

				strcpy(now_str, ctime(&(now)));
				now_str[24] = '\0'; /* zap the newline */

				report(LOG_DEBUG, "skipped checking configuration file modification time; now=%s, and %d seconds have not passed since the file was last read (%s)", now_str, readconfinterval, conf_lastread_time_str);

			}
#endif
			return;
		}

		/* We must at least check the file's last modification time */

		if (stat(conffile, &st) < 0) {
			report(LOG_ERR, "stat on \"%s\": %s", conffile, get_errmsg());
			return;
		}

#ifdef DEBUG
		if (debug > 12) {
			char timestr[28];
			strcpy(timestr, ctime(&(st.st_mtime)));
			timestr[24] = '\0'; /* zap the newline */
			report(LOG_INFO, "%s mtime: %s", conffile, timestr);
		}
#endif

		if ((st.st_mtime <= conftime) && st.st_nlink) {
			/* file hasn't been modified since the last time we read it, nor has it been deleted */
			/* XXX can st.st_nlink ever == 0?  Wouldn't the stat() have failed in that case? */
			/* skip reading the file */
			return;
		}

	} /* if (!force) */

	/* We *will* read the file */

	if (debug) {
		report(LOG_INFO, "reading \"%s\"", conffile);
	}

	if ((fp = fopen(conffile, "r")) == NULL) {
		report(LOG_ERR, "error opening \"%s\": %s\n", conffile, get_errmsg());
		exit(1);
	}
	/*
	 * Record file modification time.
	 * Even if we stat()'d the file earlier, we do not re-use that information.
	 * That's so we record the mod time of the file we've actually opened,
	 * as opposed to be (possibly older) file we looked at a moment ago.
	 * Although it's an extra system call, the cost is neglible as this is
	 * only done when we already have decided to read the file.
	 */

	if (fstat(fileno(fp), &st) < 0) {
		report(LOG_ERR, "fstat: %s", get_errmsg());
		fclose(fp);
		return;
	}
	conftime = st.st_mtime;

	conf_lastread_time = time(0);

	hash_Reset(cablehshtbl, free_cable);
	hash_Reset(gwhshtbl, free_gw);
	hash_Reset(known_serverids_hshtbl, free_inaddr);

	known_serverids_hshtbl_inuse = 0;
	expire_infinite_dynamics=0;	/* don't inherit previous value */
	client_steering_hash_source = CLIENT_STEERING_HASH_SOURCE_CLIENTID;
	client_steering_hash_function = CLIENT_STEERING_HASH_FUNCTION_POPX31;
	client_steering_delay_secs_num = 0;
	client_steering_server_myself = 0;
	princeton_log_inits_with_unexpired_lease = FALSE;
	princeton_log_lease_offer_conversions = FALSE;
	princeton_log_client_steering = FALSE;

	client_steering_enable=FALSE; /* true when any of the following are set */
	client_steering_enable_bootpreply_static = FALSE;
	client_steering_enable_bootpreply_dynamic = FALSE;
	client_steering_enable_dhcpoffer_static = FALSE;
	client_steering_enable_dhcpoffer_dynamic = FALSE;

	while (parse_line(fp) >= 0)
		/* All the work is done in parse_line(), which returns true until the file is done. */
		;

	fclose(fp);

	/* Validate those config file items that cannot be validated in isolation. */

	/* client_steering_server_myself is 0 if not specified,
	   else should be in range 1...client_steering_delay_secs_num.
	*/
	if (client_steering_server_myself > client_steering_delay_secs_num) {
		report(LOG_ERR, "Cannot have client_steering_server_myself=%d > client_steering_delay_secs_num=%d.  Discarding client_steering_server_myself setting.", client_steering_server_myself, client_steering_delay_secs_num);
		client_steering_server_myself = 0;
	}
	
	client_steering_enable = (client_steering_enable_bootpreply_dynamic || client_steering_enable_bootpreply_static || client_steering_enable_dhcpoffer_static || client_steering_enable_dhcpoffer_dynamic);

	if (client_steering_enable && !client_steering_server_myself) {
		report(LOG_ERR, "'client_steering_enable' was specified for one or kinds of packets, but 'client_steering_server_myself' is not set.  Disabling Client Steering.");
		client_steering_enable						=	FALSE;
		client_steering_enable_bootpreply_static	=	FALSE;
		client_steering_enable_bootpreply_dynamic	=	FALSE;
		client_steering_enable_dhcpoffer_static		=	FALSE;
		client_steering_enable_dhcpoffer_dynamic	=	FALSE;
	}


	/* Report on those confguration items that could not be reported on as we were parsing them. */
	if (debug > 8) {
		;
	}



	if (debug > 1) {
		report(LOG_INFO, "Finished reading %s", conffile);
	}
}

void 
free_cable(cable)
	struct cable   *cable;
{
	struct link_list *t;
	if (!cable)
		return;
	if (--cable->linkcount)
		return;

	del_string(cable->name);

	while (cable->head) {
		t = cable->head;
		cable->head = cable->head->next;
		free(t);
	}

	if (cable->net)
		free(cable->net);
	if (cable->mask)
		free(cable->mask);

	free(cable);

}

void 
del_string_list(str)
	struct shared_string_list *str;
{
	if (!str)
		return;
	if (--str->linkcount)
		return;
	while (str->num)
		free(str->str[str->num--]);
	free(str);
}

void 
free_gw(gw)
	struct gateway *gw;
{
	int             i;
	if (!gw)
		return;
	if (--gw->linkcount)
		return;
	for (i = 0; i < gw->num; i++)
		free(gw->nets[i]);
	free(gw->nets);
	if (gw->proxyaddr)
		free(gw->proxyaddr);
	if (gw->proxymask)
		free(gw->proxymask);
	free(gw);
}





/*
 * This function reads a line of text from the given file and interprets it.
 * The first token (word) on the line is taken as a keyword which determines
 * the appropriate action.
 * 
 * Successful calls return 0.  A value of -1 indicates end-of-file.
 */

PRIVATE int 
parse_line(fp)
	FILE           *fp;
{
	static char     linebuf[MAXENTRYLEN], tmpstring[MAXENTRYLEN];
	struct keywordmap *keywd;
	char          **keyvec;
	int             i;

	if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
		return -1;
	}
	keyvec = get_tokens(linebuf);
	if (*keyvec) {
		if ((**keyvec == '#') || (**keyvec == ';')) {
			/*
			 * Comment, just return.
			 */
			return 0;
		}
		strcpy(tmpstring, *keyvec);
		makelower(tmpstring);
		keywd = keyword_list;
		for (i = sizeof(keyword_list) / sizeof(struct keywordmap); i > 0; i--) {
			if (!strcmp(keywd->keyword, tmpstring)) {
				break;
			}
			keywd++;
		}
		if (i <= 0) {
			report(LOG_ERR, "unrecognized keyword: \"%s\"\n", *keyvec);
		} else {
			keyvec++;
			switch (keywd->symbolcode) {
#ifdef DEBUG
			case KEYW_DUMP:
				if (*keyvec) {
					strncpy(bootpd_dump, *keyvec, sizeof(bootpd_dump));
					bootpd_dump[sizeof(bootpd_dump) - 1] = '\0';
					if (debug > 8)
						report(LOG_INFO, "config: dump %s", bootpd_dump);
				}
				break;
#endif
			case KEYW_GATEWAY:
				kw_gateway(keyvec);
				break;
			case KEYW_NETWORK:
				kw_network(keyvec);
				break;
			case KEYW_PROXY:
				kw_proxy(keyvec);
				break;
			case KEYW_NETWORKALSO:
				kw_networkalso(keyvec);
				break;
			case KEYW_GCINTERVAL:
				if (*keyvec) {
					gcinterval = atoi(*keyvec);
					if (debug > 8)
						report(LOG_INFO, "config: gcinterval %lu", gcinterval);
				}
				break;
			case KEYW_FLUSHITEMS:
				if (*keyvec) {
					max_cachequeue = atoi(*keyvec);
					if (debug > 8)
						report(LOG_INFO, "config: flushitems %i", max_cachequeue);
				}
				break;
			case KEYW_FLUSHINTERVAL:
				if (*keyvec) {
					flush_interval = atoi(*keyvec);
					if (debug > 8)
						report(LOG_INFO, "config: flushinterval %lu", flush_interval);
				}
				break;
			case KEYW_BINDINGS:
				if (*keyvec) {
					bindingsdir = savestr(*keyvec);
					if (debug > 8)
						report(LOG_INFO, "config: binddir %s", bindingsdir);
				}
				break;
			case KEYW_EXPIRE_INFINITE_DYNAMICS:
				if (*keyvec) {
					expire_infinite_dynamics = atoi(*keyvec);
					if (debug > 8)
						report(LOG_INFO, "config: expire_infinite_dynamics %lu", expire_infinite_dynamics);
				}
				break;
			case KEYW_KNOWN_SERVER_IDENTIFIER:
				kw_known_server_identifier(keyvec);
				break;
			case KEYW_DELAY_REPLY_DHCPOFFER:
				if (*keyvec) {
					unsigned int tmpuint;
					if (!sscanf(*keyvec, "%u", &tmpuint) || (tmpuint < 0)) {
						report(LOG_ERR, "delay_reply_dhcpoffer value must be an unsigned integer\n");
					} else {
						delay_reply_dhcpoffer = tmpuint;
					}
					if (debug > 8)
						report(LOG_INFO, "config: delay_reply_dhcpoffer %u", delay_reply_dhcpoffer);
				}
				break;
			case KEYW_DELAY_REPLY_DHCPACK:
				if (*keyvec) {
					unsigned int tmpuint;
					if (!sscanf(*keyvec, "%u", &tmpuint) || (tmpuint < 0)) {
						report(LOG_ERR, "delay_reply_dhcpack value must be an unsigned integer\n");
					} else {
						delay_reply_dhcpack = tmpuint;
					}
					if (debug > 8)
						report(LOG_INFO, "config: delay_reply_dhcpack %u", delay_reply_dhcpack);
				}
				break;
			case KEYW_DELAY_REPLY_DHCPNAK:
				if (*keyvec) {
					unsigned int tmpuint;
					if (!sscanf(*keyvec, "%u", &tmpuint) || (tmpuint < 0)) {
						report(LOG_ERR, "delay_reply_dhcpnak value must be an unsigned integer\n");
					} else {
						delay_reply_dhcpnak = tmpuint;
					}
					if (debug > 8)
						report(LOG_INFO, "config: delay_reply_dhcpnak %u", delay_reply_dhcpnak);
				}
				break;
			case KEYW_DELAY_REPLY_BOOTPREPLY:
				if (*keyvec) {
					unsigned int tmpuint;
					if (!sscanf(*keyvec, "%u", &tmpuint) || (tmpuint < 0)) {
						report(LOG_ERR, "delay_reply_bootpreply value must be an unsigned integer\n");
					} else {
						delay_reply_bootpreply = tmpuint;
					}
					if (debug > 8)
						report(LOG_INFO, "config: delay_reply_bootpreply %u", delay_reply_bootpreply);
				}
				break;
			case KEYW_DELAY_REPLY_DEFAULT:
				if (*keyvec) {
					unsigned int tmpuint;
					if (!sscanf(*keyvec, "%u", &tmpuint) || (tmpuint < 0)) {
						report(LOG_ERR, "delay_reply_default value must be an unsigned integer\n");
					} else {
						delay_reply_default = tmpuint;
					}
					if (debug > 8)
						report(LOG_INFO, "config: delay_reply_default %u", delay_reply_default);
				}
				break;
			case KEYW_READTABINTERVAL:
				if (*keyvec) {
					readtabinterval = atoi(*keyvec);
					if (debug > 8)
						report(LOG_INFO, "config: readtabinterval %lu", readtabinterval);
				}
				break;
			case KEYW_READCONFINTERVAL:
				if (*keyvec) {
					readconfinterval = atoi(*keyvec);
					if (debug > 8)
						report(LOG_INFO, "config: readconfinterval %lu", readconfinterval);
				}
				break;
			case KEYW_CLIENT_STEERING_HASH_SOURCE:
				if (*keyvec) {
					if (!strcasecmp(*keyvec, "chaddr")) {
						client_steering_hash_source = CLIENT_STEERING_HASH_SOURCE_CHADDR;
						if (debug > 8)
							report(LOG_INFO, "config: client_steering_hash_source chaddr");
					} else if (!strcasecmp(*keyvec, "clientid")) {
						client_steering_hash_source = CLIENT_STEERING_HASH_SOURCE_CLIENTID;
						if (debug > 8)
							report(LOG_INFO, "config: client_steering_hash_source clientid");
					} else {
						report(LOG_ERR, "client_steering_hash_source value must be 'chaddr' or 'clientid'");
					}
				}
				break;
			case KEYW_CLIENT_STEERING_HASH_FUNCTION:
				if (*keyvec) {
					if (!strcasecmp(*keyvec, "popx31")) {
						client_steering_hash_function = CLIENT_STEERING_HASH_FUNCTION_POPX31;
						if (debug > 8)
							report(LOG_INFO, "config: client_steering_hash_function popx31");
					} else if (!strcasecmp(*keyvec, "lastbyte")) {
						client_steering_hash_function = CLIENT_STEERING_HASH_FUNCTION_LASTBYTE;
						if (debug > 8)
							report(LOG_INFO, "config: client_steering_hash_function lastbyte");
					} else {
						report(LOG_ERR, "client_steering_hash_function value must be 'popx31' or 'lastbyte'");
					}
				}
				break;
			case KEYW_CLIENT_STEERING_ENABLE:
				kw_client_steering_enable(keyvec);
				break;
			case KEYW_CLIENT_STEERING_DELAY_SECS:
				kw_client_steering_delay_secs(keyvec);
				break;
			case KEYW_CLIENT_STEERING_SERVER_MYSELF:
				if (*keyvec) {
					int tmpint;
					if (!sscanf(*keyvec, "%d", &tmpint) || (tmpint < 1)) {
						report(LOG_ERR, "client_steering_server_myself value must be a integer >= 1\n");
					} else {
						client_steering_server_myself = tmpint;
						if (debug > 8)
							report(LOG_INFO, "config: client_steering_server_myself %i", client_steering_server_myself);
					}
				}
				break;
			case KEYW_PRINCE_LOG_CLIENT_STEERING:
				/* undocumented debugging feature:
				      princeton_log_client_steering [on|off|enabled|disabled|true|false]
				   Forces on some log messages involving Client Steering regardless of debug level.
				*/
				if (*keyvec) {
					if (!strcasecmp(*keyvec, "on") || !strcasecmp(*keyvec, "enabled") || !strcasecmp(*keyvec, "true")) {
						princeton_log_client_steering = TRUE;
						if (debug > 8)
							report(LOG_INFO, "config: princeton_log_client_steering enabled");
					} else if (!strcasecmp(*keyvec, "off") || !strcasecmp(*keyvec, "disabled") || !strcasecmp(*keyvec, "false")) {
						princeton_log_client_steering = FALSE;
						if (debug > 8)
							report(LOG_INFO, "config: princeton_log_client_steering disabled");
					} else {
						report(LOG_ERR, "princeton_log_client_steering value must be 'on', 'off', 'enabled', 'disabled', 'true', or 'false'");
					}
				}
				break;

			case KEYW_PRINCE_LOG_INITS_W_UNEXP_LEASE:
				/* undocumented debugging feature: 
				      princeton_log_inits_with_unexpired_lease [on|off|enabled|disabled|true|false]
				   Forces on some some log messages when we see a DHCP client in INIT state and the server has an
				   an unexpired DHCP lease for that lease.
				   Just a temporary(?) feature to learn about some client behavior.
				*/
				if (*keyvec) {
					if (!strcasecmp(*keyvec, "on") || !strcasecmp(*keyvec, "enabled") || !strcasecmp(*keyvec, "true")) {
						princeton_log_inits_with_unexpired_lease = TRUE;
						if (debug > 8)
							report(LOG_INFO, "config: princeton_log_inits_with_unexpired_lease enabled");
					} else if (!strcasecmp(*keyvec, "off") || !strcasecmp(*keyvec, "disabled") || !strcasecmp(*keyvec, "false")) {
						princeton_log_inits_with_unexpired_lease = FALSE;
						if (debug > 8)
							report(LOG_INFO, "config: princeton_log_inits_with_unexpired_lease disabled");
					} else {
						report(LOG_ERR, "princeton_log_inits_with_unexpired_lease value must be 'on', 'off', 'enabled', 'disabled', 'true', or 'false'");
					}
				}
				break;
			case KEYW_PRINCE_LOG_LEASE_OFFER_CONVERT:
				/* undocumented debugging feature: 
				      princeton_log_lease_offer_conversions [on|off|enabled|disabled|true|false]
				   Forces on some log messages describing conversion of unexpired DHCP leases to offers and back again,
				   regardlesss of the debug level.
				*/
				if (*keyvec) {
					if (!strcasecmp(*keyvec, "on") || !strcasecmp(*keyvec, "enabled") || !strcasecmp(*keyvec, "true")) {
						princeton_log_lease_offer_conversions = TRUE;
						if (debug > 8)
							report(LOG_INFO, "config: princeton_log_lease_offer_conversions enabled");
					} else if (!strcasecmp(*keyvec, "off") || !strcasecmp(*keyvec, "disabled") || !strcasecmp(*keyvec, "false")) {
						princeton_log_lease_offer_conversions = FALSE;
						if (debug > 8)
							report(LOG_INFO, "config: princeton_log_lease_offer_conversions disabled");
					} else {
						report(LOG_ERR, "princeton_log_lease_offer_conversions value must be 'on', 'off', 'enabled', 'disabled', 'true', or 'false'");
					}
				}
				break;
			default:
				report(LOG_ERR, "parse_line(): internal error: unknown keyword code\n");
				break;
			}
		}
	}
	return 0;
}


/*
 * This function checks a candidate IP addr against a gateway (actually a relay agent) 
 * IP addr, to determine if that IP address is on a subnet behind that bootp relay agent.
 * Returns TRUE if the address is, else return FALSE.  
 *
 * If gwaddr==0, we assume packet did not traverse a bootp relay agent (an incorrect
 * assumption if the packet was unicast, so be careful how you use this!), therefore
 * the packet originated on a locally-attached network.  In that case, we rely on
 * is_local() to determine if the packet is local.  Note that this means 
 * a multihomed server will NOT be able to properly NAK or allocate addresses for
 * clients that are attached to local networks.  It will fail to NAK a client
 * attached to a local network if the IP address in question is appropriate for
 * another local network.  And when allocating addresses new addresses, we 
 * pretend we have just a primary interface, which will attempt to assign 
 * inappropriate dynamic addresses to clients attached to nets local to our other
 * interfaces.  The moral is to not run this server on a multihomed host; if you
 * must, do not assign dynamic addresses for the locally-attached nets, and expect
 * some requests that should get NAKed won't be.
 *
 * If the gateway address is non-zero and is NOT listed as a gateway in the dhcpd.conf
 * file, something's probably wrong.  We'll return FALSE.
 *
*/
boolean 
check_net(gwaddr, addr)
	struct in_addr * gwaddr; /* gateway address */
	struct in_addr * addr;	/* address to test */
{
	struct gateway *gw;
	unsigned        hashcode;
	int i;

	if (gwaddr->s_addr == 0) 
		return(is_local(addr));

	hashcode = hash_HashFunction((u_char*)gwaddr, 4);
	gw = (struct gateway *) hash_Lookup(gwhshtbl, hashcode, gwlcmp, gwaddr);
	if (!gw) {
		if (debug)
			report(LOG_WARNING, "  gateway %s not found in %s", inet_ntoa(*gwaddr), conffile);
		return FALSE;
	}
	if ((gw->mask & gwaddr->s_addr) == (gw->mask & addr->s_addr))
		return TRUE;
	for (i=0; i < gw->proxynum; i++) {
		if ((gw->proxymask[i] & gw->proxyaddr[i].s_addr) == (gw->proxymask[i] & addr->s_addr))
			return TRUE;
	}
	return FALSE;
}

/*
 * This function sanity-checks a netmask for validity.  Returns TRUE for a
 * good mask, FALSE for a bad mask.
 */
PRIVATE int 
chk_mask(mask)
	long            mask;
{
	int             count;

	mask = ntohl(mask);

	if ((mask == 0L) || (mask == -1L)) {
		return FALSE;
	}
	for (count = 32; count > 0; count--) {
		if ((mask & 0x80000000L) == 0L) {
			break;
		}
		mask <<= 1;
	}
	if (mask != 0L) {
		return FALSE;
	}
	return TRUE;
}




#define	TOKEN_NAME	tokenvec[0]
#define	TOKEN_NETWORK	tokenvec[1]
#define	TOKEN_MASK	tokenvec[2]
PRIVATE void 
kw_network(tokenvec)
	char          **tokenvec;
{
	struct in_addr  tmpaddr;
	struct cable   *c;
	struct gateway *gw;
	unsigned        hashcode;
	char           *cp, *last, *next, **nets, **router;
	int             i, rc, size;
	unsigned long   mask;
	struct host    *hp;

	if (!*tokenvec) {
		return;		/* Null network definition */
	}
	if (!TOKEN_NETWORK) {
		report(LOG_ERR, "network not specified for %s", TOKEN_NAME);
		return;
	}
	if (!TOKEN_MASK) {
		report(LOG_ERR, "mask not specified for %s", TOKEN_NAME);
		return;
	}
	hashcode = hash_HashFunction((u_char*)TOKEN_NAME, strlen(TOKEN_NAME));
	if (hash_Exists(cablehshtbl, hashcode, cablelcmp, TOKEN_NAME)) {
		report(LOG_ERR, "Network already exists, ignoring %s", TOKEN_NAME);
		return;
	}
	c = (struct cable *) smalloc(sizeof(struct cable));

	bzero(c, sizeof(struct cable));
	c->name = (struct shared_string *) smalloc(sizeof(struct shared_string) + strlen(TOKEN_NAME));
	c->name->linkcount = 1;
	strcpy(c->name->string, TOKEN_NAME);

	/* initialize the c->net array to be a single element */
	c->net = smalloc(sizeof(unsigned long));
	cp = TOKEN_NETWORK;
	rc = prs_inetaddr(&cp, c->net);

	/* initialize the c->mask array to be a single element */
	c->mask = smalloc(sizeof(unsigned long));
	cp = TOKEN_MASK;
	rc = prs_inetaddr(&cp, c->mask);

	c->number_of_nets = 1;

	c->code = DHCP_ACCESS;
	if (tokenvec[3]) {
		next = tokenvec[3];
		do {
			last = next;
			next = strchr(last, '|');
			if (next)
				*next++ = 0;
			if (strcmp(last, "dhcp") == 0) {
				c->code |= DHCP_ACCESS;
				c->code &= (~BOOTP_ACCESS);
			} else if (strcmp(last, "bootp") == 0) {
				c->code |= BOOTP_ACCESS;
				c->code &= (~DHCP_ACCESS);
			} else if (strcmp(last, "both") == 0)
				c->code |= BOOTP_ACCESS | DHCP_ACCESS;
			else if (strcmp(last, "roaming") == 0) {
				c->code |= ROAMING_ACCESS;
				c->code &= (~REG_ACCESS);
				c->code &= (~UNREG_ACCESS);
			} else if (strcmp(last, "registered") == 0) {
				c->code |= REG_ACCESS;
				c->code &= (~ROAMING_ACCESS);
				c->code &= (~UNREG_ACCESS);
			} else if (strcmp(last, "unregistered") == 0) {
				c->code |= UNREG_ACCESS;
				c->code &= (~ROAMING_ACCESS);
				c->code &= (~REG_ACCESS);
			} else if (strcmp(last, "any") == 0)
				c->code |= ANY;
			else
				report(LOG_ERR, "Bad keyword for argument 4, was expecting\
			\"dhcp\",\"bootp\", or \"any\" but received \"%s\"", tokenvec[4]);
		} while (next);
	}
	c->linkcount = 1;
	hash_Insert(cablehshtbl, hashcode, nullcmp, c, c);

	/* Locate each entry 'hp' in iphashtable where the entry's IP address falls within
	   the range (c->net[0] & c->mask[0]).  For each such entry 'hp', if hp->flags.dynamic,
	   "add it" to the cable (pool) 'c'.

	   The way we walk through iphashtable is to set tmpaddr to the first IP address 
	   in the range (c->net[0] & c->mask[0]).  We then loop by incrementing tmpaddr by 1
	   each time, until the resulting IP address falls outside the range (c->net[0] & c->mask[0]).
	*/

	/* Init tmpaddr to the first IP address in the range (c->net & c->mask). */
	/* Also init 'mask' to that value.  That's a poorly named var.  It really represents
	   the "network number", and is used to discover when we've walked beyond the end of the range.
	*/
	mask = tmpaddr.s_addr = c->net[0] & c->mask[0];

	i = 0; /* count of dynamic IP addresses for this cable */

	while ((tmpaddr.s_addr & c->mask[0]) == mask) {
		/* tmpaddr still falls within the range (c->net[0] & c->mask[0]) */

		hashcode = hash_HashFunction((u_char*)&tmpaddr, 4);
		hp = (struct host *) hash_Lookup(iphashtable, hashcode, ipcmp, &(tmpaddr.s_addr));
		if (hp && hp->flags.dynamic) {
			/* There is an entry in iphashtable with that IP address, and that entry is for a dynamic IP address. */

			/* "Add" that entry to the cable. */

			i++; /* count of dynamic IP addresses for this cable */

			/* Modify the entry in iphashtable to show what cable it belongs to. */
			hp->flags.cable = TRUE;
			hp->cable = c->name;
			/* As we've added another thing pointing to c->name, we must update it refcount. */
			c->name->linkcount++;

			/* If we are performing initialization, 
			   or if there is an ipboundhshtbl hashtable and tmpaddr is not in it (i.e. there is no binding for tmpaddr),
			   add tmpaddr to the pool of "free" IP addresses for this cable.
			*/
			if (init || (ipboundhshtbl && !hash_Exists(ipboundhshtbl, hashcode, ipcmp, &(tmpaddr.s_addr))))
				push_ip(c, tmpaddr);
		}

		/* increment tmpaddr walk to the next IP address */
		tmpaddr.s_addr = htonl(ntohl(tmpaddr.s_addr)+1);

	} /* while tmpaddr */

	if (debug > 4)
		report(LOG_INFO, "Network %s %d entries (total %d free)", c->name->string, i, c->free);

	/* If there are remaining tokens in the statement, they should be pairs of (relayagentipaddr relayagentnetmask) values.
	   For each tuple, we locate relayagentipaddr in gwhshtbl, adding a new struct gateway member if it's not already there.
	   We set/append the struct gateway's 'nets' array to have a copy of the cable name.
	*/

	router = NULL;
	if (*tokenvec[3])
		router = tokenvec + 4;

	while (router && router[0] && router[1]) {
		/* We have a (relayagentipaddr relayagentnetmask) tuple. */

		prs_inetaddr(router, &tmpaddr); /* relayagentipaddr */

		/* Look for relayagentipaddr in gwhshtbl */
		hashcode = hash_HashFunction((u_char*)&tmpaddr, 4);
		gw = (struct gateway *) hash_Lookup(gwhshtbl, hashcode, gwlcmp, &tmpaddr);

		if (!gw) {
			/* relayagentipaddr is not already in gwhshtbl*/

			/* "Add it" to gwhshtbl */
			gw = (struct gateway *) smalloc(sizeof(struct gateway));
			gw->addr.s_addr = tmpaddr.s_addr;
			size = sizeof(char *);
			gw->nets = (char **) smalloc(size);
			gw->num = 1; /* number of elements in gw->nets array */
			gw->nets[0] = savestr(tokenvec[0]); /* the cable name */
			gw->proxyaddr = NULL;
			gw->proxymask = NULL;
			gw->proxynum = 0;
			gw->linkcount = 1;
			hash_Insert(gwhshtbl, hashcode, nullcmp, gw, gw);

		} else {
			/* relayagentipaddr is already in gwhshtbl*/

			/* Expand the gw->nets array by one element by mallocing a slightly large space,
			   copying the old array into the new space,
			   then freeing the old array.
			*/
			size = sizeof(char *) * (gw->num + 1);
			nets = (char **) smalloc(size);
			bcopy(gw->nets, nets, size - sizeof(char *));
			nets[gw->num] = savestr(tokenvec[0]); /* insert cable name into the new element at the end of the array. */
			free(gw->nets);
			gw->nets = nets;
			gw->num++;
		}
		/* Copy relayagentnetmask into the struct gateway's 'mask' element.

		   XXX If struct gateway already existed, this overwrites any old 'mask' element.
			   If relayagentipaddr was already in gwhshtbl, shouldn't we compare the existing gw->mask value
			   to the new relayagentnetmask value, and if they differ, report that as an error?
		       Specifying the same relayagentipaddr with two different relayagentnetmask values makes no sense in this context, yes?
		*/
		prs_inetaddr(router + 1, &gw->mask);
		router += 2;
	}

	if (debug > 8) {
		/* XXX We need to add code to report this config statement */
		;
	}

	return;
}


#define	TOKEN_NETWORKALSO_NAME	tokenvec[0]
#define	TOKEN_NETWORKALSO_NETWORK	tokenvec[1]
#define	TOKEN_NETWORKALSO_MASK	tokenvec[2]
PRIVATE void
kw_networkalso(tokenvec)
    char            **tokenvec;
{
	unsigned          hashcode;
	struct cable     *c;
	char             *cp;
	int               size;
	unsigned long     net_new, mask_new; /* parsed from the networkalso statement */
	unsigned long    *net_buf;
	unsigned long    *mask_buf;
	struct in_addr    tmpaddr;
	int               i;
	unsigned long     mask;
	struct host      *hp;

	if (!*tokenvec) {
		return;     /* Null definition */
	}

	if (!TOKEN_NETWORKALSO_NETWORK) {
		report(LOG_ERR, "For networkalso statement '%s' the network number is missing; ignoring.", TOKEN_NETWORKALSO_NAME);
		return;
	}

	if (!TOKEN_NETWORKALSO_MASK) {
		report(LOG_ERR, "For networkalso statement '%s' the netmask is missing; ignoring.", TOKEN_NETWORKALSO_NAME);
		return;
	}

	/* The poolname must have appeared in an earlier 'network' statement. */
	hashcode = hash_HashFunction((u_char*)TOKEN_NETWORKALSO_NAME, strlen(TOKEN_NETWORKALSO_NAME));
	c = (struct cable *) hash_Lookup(cablehshtbl, hashcode, cablelcmp, TOKEN_NETWORKALSO_NAME);
	if (!c) {
		report(LOG_ERR, "For networkalso statement '%s' the network name does not exist; ignoring.", TOKEN_NETWORKALSO_NAME);
		return;
	}

	/* parse network IP address from networkalso statement */
	cp = TOKEN_NETWORKALSO_NETWORK;
	prs_inetaddr(&cp, &net_new);

	/* Expand the c->net array by one element by mallocing a slightly larger space in net_buf,
	   copying the old array into the new space, appending the new element,
	   then freeing the old array.
	*/
	size = sizeof(unsigned long) * (c->number_of_nets + 1);
	net_buf = (unsigned long *) smalloc(size);
	bcopy(c->net, net_buf, size - sizeof(unsigned long));
	bcopy(&net_new, &(net_buf[c->number_of_nets]), sizeof(unsigned long)); /* append new element */
	free(c->net);
	c->net = net_buf;

	/* parse network IP address from networkalso statement */
	cp = TOKEN_NETWORKALSO_MASK;
	prs_inetaddr(&cp, &mask_new);

	/* Expand the c->mask array by one element by mallocing a slightly larger space in mask_buf,
	   copying the old array into the new space, appending the new element,
	   then freeing the old array.
	*/
	size = sizeof(unsigned long) * (c->number_of_nets + 1);
	mask_buf = (unsigned long *) smalloc(size);
	bcopy(c->mask, mask_buf, size - sizeof(unsigned long));
	bcopy(&mask_new, &(mask_buf[c->number_of_nets]), sizeof(unsigned long)); /* append new element */
	free(c->mask);
	c->mask = mask_buf;

	/* the parallel arrays are now longer */
	c->number_of_nets++;

	/* XXX We make no attempt to compare the net/mask with those already specified for this cable or for other cables
	   It is up to the user to specify non-overlapping ranges.
	*/

	/* Locate each entry 'hp' in iphashtable where the entry's IP address falls within
	   the range (net_new & mask_new).  For each such entry 'hp', if hp->flags.dynamic,
	   "add it" to the cable (pool) 'c'.

	   The way we walk through iphashtable is to set tmpaddr to the first IP address 
	   in the range (net_new & mask_new).  We then loop by incrementing tmpaddr by 1
	   each time, until the resulting IP address falls outside the range (net_new & mask_new).
	*/

	/* Init tmpaddr to the first IP address in the range (net_new & mask_new). */
	/* Also init 'mask' to that value.  That's a poorly named var.  It really represents
	   the "network number", and is used to discover when we've walked beyond the end of the range.
	*/
	mask = tmpaddr.s_addr = net_new & mask_new;

	i = 0; /* count of additional dynamic IP addresses for this cable */

	while ((tmpaddr.s_addr & mask_new) == mask) {
		/* tmpaddr still falls within the range (net_new & mask_new) */

		hashcode = hash_HashFunction((u_char*)&tmpaddr, 4);
		hp = (struct host *) hash_Lookup(iphashtable, hashcode, ipcmp, &(tmpaddr.s_addr));
		if (hp && hp->flags.dynamic) {
			/* There is an entry in iphashtable with that IP address, and that entry is for a dynamic IP address. */

			/* "Add" that entry to the cable. */

			i++; /* count of additional dynamic IP addresses for this cable */

			/* Modify the entry in iphashtable to show what cable it belongs to. */
			hp->flags.cable = TRUE;
			hp->cable = c->name;
			/* As we've added another thing pointing to c->name, we must update it refcount. */
			c->name->linkcount++;

			/* If we are performing initialization, 
			   or if there is an ipboundhshtbl hashtable and tmpaddr is not in it (i.e. there is no binding for tmpaddr),
			   add tmpaddr to the pool of "free" IP addresses for this cable.
			*/
			if (init || (ipboundhshtbl && !hash_Exists(ipboundhshtbl, hashcode, ipcmp, &(tmpaddr.s_addr))))
				push_ip(c, tmpaddr);
		}

		/* increment tmpaddr walk to the next IP address */
		tmpaddr.s_addr = htonl(ntohl(tmpaddr.s_addr)+1);

	} /* while tmpaddr */

	if (debug > 4)
		report(LOG_INFO, "Network %s %d additional entries (total %d free)", c->name->string, i, c->free);
	
	return;
}


PRIVATE void 
kw_gateway(tokenvec)
	char          **tokenvec;
{
	struct in_addr  tmpaddr;
	struct cable   *c;
	struct gateway *gw;
	unsigned        hashcode;
	char           *cp, **network;
	int             rc, size;
	unsigned long   mask;
	char          **nets;

	if (!*tokenvec) {
		return;		/* Null network definition */
	}
	if (!tokenvec[1]) {
		report(LOG_ERR, "Need Mask for gateway definition");
		return;
	}
	cp = tokenvec[0];
	prs_inetaddr(&cp, &tmpaddr); /* gateway IP address */

	/* Look for gateway IP address in gwhshtbl */
	hashcode = hash_HashFunction((u_char*)&tmpaddr, 4);
	gw = (struct gateway *) hash_Lookup(gwhshtbl, hashcode, gwlcmp, &tmpaddr);

	if (!gw) {
		/* gateway IP address is not already in gwhshtbl */

		/* "Add it" to gwhshtbl */

		/* It may have already been added if we encountered
		   it as a bootp relay agent in an earlier 'network' statement. */

		gw = (struct gateway *) smalloc(sizeof(struct gateway));
		gw->addr.s_addr = tmpaddr.s_addr;
		cp = tokenvec[1];
		prs_inetaddr(&cp, &tmpaddr);
		gw->mask = tmpaddr.s_addr;
		gw->num = 0; /* number of cable names that appear in gw->nets array */
		gw->proxyaddr = NULL;
		gw->proxymask = NULL;
		gw->proxynum = 0;
		gw->linkcount = 1;
		hash_Insert(gwhshtbl, hashcode, nullcmp, gw, gw);

	} /* else gateway IP address is already in gwhshtbl.
	     This is normal if it appeared as a relayagentipaddr in earlier 'network' statement(s);
		   in that situation, the earlier 'network' statement(s) already initialized the gateway member.
		 This is an error (which we don't catch) if it appeared as a gateway IP address in an earlier 'gateway' statement;
		   the same IP address should no appear in two gateway statements.  
	  */

	/* XXX Copy gateway netmask into gw.
	   If we just init'd gw above, this is duplicate work; why do it?
	   Else gw should already have a mask, and we are overwriting it.
	       In that case, wouldn't it make more sense to compare the existing gw->mask value
		   to the new gateway netmask value, and if they differ, report that as an error?
	       Specifying the same gateway IP address with two different netmasks values makes no sense in this context, yes?
	*/
	cp = tokenvec[1];
	prs_inetaddr(&cp, &gw->mask);

	/* If any networks were specified (names of dynamic address pools. i.e. 'cable names'), store them too.
	   gw->num will be a count of how many of these names.
	   gw->net will point to an array of ptrs to these names; we extend this dynamically */

	network = tokenvec + 2;
	while (*network) {
		/* Expand the gw->nets array by one element by mallocing a slightly large space,
		   copying the old array into the new space,
		   then freeing the old array.
		*/
		size = sizeof(char *) * (gw->num + 1);
		nets = (char **) smalloc(size);
		bcopy(gw->nets, nets, size - sizeof(char *));
		nets[gw->num] = savestr(*network); /* insert cable name into the new element at the end of the array. */
		gw->num++; /* number of cable names that appear in gw->nets array */
		free(gw->nets);
		gw->nets = nets;
		network++;
	}

	if (debug > 8) {
		/* XXX We need to add code to report this config statement */
		;
	}

	return;
}


PRIVATE void 
kw_proxy(tokenvec)
	char          **tokenvec;
{
	struct in_addr  tmpaddr;
	struct gateway *gw;
	unsigned        hashcode;
	char           *cp, **network;
	int             size;
	struct in_addr	*proxyaddr;
	unsigned long	*proxymask;

	if (!*tokenvec) {
		return;		/* Null network definition */
	}
	if (!tokenvec[1]) {
		report(LOG_ERR, "Need Network Address for proxy definition");
		return;
	}
	if (!tokenvec[2]) {
		report(LOG_WARNING, "Need Network Mask for proxy definition");
		return;
	}

	cp = tokenvec[0];
	prs_inetaddr(&cp, &tmpaddr); /* relay agent IP address */

	/* Look for relay agent IP address in gwhshtbl */
	hashcode = hash_HashFunction((u_char*)&tmpaddr, 4);
	gw = (struct gateway *) hash_Lookup(gwhshtbl, hashcode, gwlcmp, &tmpaddr);

	if (!gw) {
		report(LOG_ERR, "interface or relayAgent %s not yet defined, cannot add proxy",
		       inet_ntoa(tmpaddr));
		return;
	}

	/* We expect one more (network,  netmask) tuples. */

	network = tokenvec + 1;
	while (*network) {

		if ( !*(network + 1) ) {
			report(LOG_ERR, "Need Network Mask for proxy definition, proxy=%s, network=%s",
			       tokenvec[0], *network);
			return;
		}

		/* gw->proxyaddr and gw->proxymask are parallel arrays, storing the (network, netmask)
		   tuples of the additional networks behind the relay agent specified by the struct gateway.

			Append network to gw->proxyaddr array, and append netmask to gw->proxymask array.
		*/

		/* Expand the gw->proxyaddr array by one element by mallocing a slightly large space,
		   copying the old array into the new space,
		   then freeing the old array.
		*/
		size = sizeof(struct in_addr) * (gw->proxynum + 1);
		proxyaddr = (struct in_addr *) smalloc(size);
		bcopy(gw->proxyaddr, proxyaddr, size - sizeof(struct in_addr));
		cp = *network;
		prs_inetaddr(&cp, &(proxyaddr[gw->proxynum]));
		free(gw->proxyaddr);
		gw->proxyaddr = proxyaddr;
		network++;

		/* Expand the gw->proxymask array by one element by mallocing a slightly large space,
		   copying the old array into the new space,
		   then freeing the old array.
		*/
		size = sizeof(unsigned long) * (gw->proxynum + 1);
		proxymask = (unsigned long *) smalloc(size);
		bcopy(gw->proxymask, proxymask, size - sizeof(unsigned long));
		cp = *network;
		prs_inetaddr(&cp, &(proxymask[gw->proxynum]));
		free(gw->proxymask);
		gw->proxymask = proxymask;
		network++;

		gw->proxynum++;
  	}

	if (debug > 8) {
		/* XXX We need to add code to report this config statement */
		;
	}
  
  	return;
}

PRIVATE void
kw_known_server_identifier(tokenvec)
	char          **tokenvec;
{
	char           *cp;
	struct in_addr  *tmpinaddr;
	unsigned        hashcode;

	if (!*tokenvec) {
		report(LOG_ERR, "Need IP Address for known_server_identifier definition");
		return;
	}

	cp = tokenvec[0];
	tmpinaddr = smalloc(sizeof(struct in_addr));

	if (prs_inetaddr(&cp, tmpinaddr) != 0) {
		report(LOG_ERR, "Invalid IP Address '%s' for known_server_identifier", cp);
		free(tmpinaddr);
		return;
	}

	hashcode = hash_HashFunction((u_char*)tmpinaddr, sizeof(struct in_addr));

	if (hash_Exists(known_serverids_hshtbl, hashcode, inaddrcmp, tmpinaddr)) {
		report(LOG_ERR, "known_server_identifier already exists, ignoring %s", inet_ntoa(*tmpinaddr));
		free(tmpinaddr);
		return;
	}

	if (hash_Insert(known_serverids_hshtbl, hashcode, inaddrcmp, tmpinaddr, tmpinaddr)) {
		report(LOG_ERR, "error inserting %s into known_serverids_hshtbl", inet_ntoa(*tmpinaddr));
		free(tmpinaddr);
		return;
	}

	if (debug > 8)
		report(LOG_INFO, "config: known_server_identifier %s", inet_ntoa(*tmpinaddr));

	known_serverids_hshtbl_inuse = 1;

	/* we do not free(tmpinaddr); the data it points to is now in the known_serverids_hshtbl hashtbl */

	return;
}


PRIVATE void
kw_client_steering_enable(tokenvec)
	char          **tokenvec;
{
	char **token;

	if (!*tokenvec) {
		report(LOG_ERR, "ignoring empty client_steering_enable definition");
		return;
	}

	token = tokenvec;

	while (*token) {
		if (       !strcasecmp(*token, "bootpreply_static"	)) {
			client_steering_enable_bootpreply_static	= TRUE;
			if (debug > 8)
				report(LOG_INFO, "config: client_steering_enable bootpreply_static");
		} else if (!strcasecmp(*token, "bootpreply_dynamic"	)) {
			client_steering_enable_bootpreply_dynamic	= TRUE;
			if (debug > 8)
				report(LOG_INFO, "config: client_steering_enable bootpreply_dynamic");
		} else if (!strcasecmp(*token, "dhcpoffer_static"	)) {
			client_steering_enable_dhcpoffer_static	= TRUE;
			if (debug > 8)
				report(LOG_INFO, "config: client_steering_enable dhcpoffer_static");
		} else if (!strcasecmp(*token, "dhcpoffer_dynamic"	)) {
			client_steering_enable_dhcpoffer_dynamic	= TRUE;
			if (debug > 8)
				report(LOG_INFO, "config: client_steering_enable dhcpoffer_dynamic");
		} else {
			report(LOG_ERR, "client_steering_enable: ignoring unrecognized value '%s'", *token);
		}

		token++;
	}

	return;
}
	
/* Populate client_steering_delay_secs[] 
   Set client_steering_delay_secs_num to the number of values in that array.
*/
PRIVATE void
kw_client_steering_delay_secs(tokenvec)
	char          **tokenvec;
{
	char	**delay_secs;
	char	value_str[MAXENTRYLEN];
	unsigned long value_ul;

	if (!*tokenvec) {
		report(LOG_ERR, "ignoring empty client_steering_delay_secs definition");
		return;
	}

	delay_secs = tokenvec;
	while (*delay_secs) {
		unsigned long val;

		/* grab a copy of the current token string */
		strncpy(value_str, *delay_secs, sizeof(value_str)-1);

		/* advance to next token in prep for next time through loop */
		delay_secs++;

		if (client_steering_delay_secs_num == CLIENT_STEERING_DELAY_SECS_NUM_MAX) {
			report(LOG_ERR, "more than %d values have been specified for client_steering_delay_secs.  Ignoring extra values.  Increase CLIENT_STEERING_DELAY_SECS_NUM_MAX and recompile.", CLIENT_STEERING_DELAY_SECS_NUM_MAX);
			continue;
		}

		/* As bootp_secs field is an unsigned 16 bit quantity, we should allow
		   user to specify values in the range 0....2^16.
		*/

		/* XXX strtoul() returns 0 or ULONG_MAX on error (and MAY also set errno), 
		   But both are also reasonable return values for this application.
		   So we generally can't catch an error.
		   Still, we'll at least inspect errno in case the error also set it.
		*/
		errno = 0;
		value_ul = strtoul(value_str, NULL, 0);
		if (errno != 0) {
			report(LOG_ERR, "client_steering_delay_secs value '%s' can't be parsed as an unsigned long; ignoring value", *value_str);
			continue;
		}

		if (debug > 8)
			report(LOG_INFO, "config: client_steering_delay_secs[%i] %lu", client_steering_delay_secs_num, value_ul);

		client_steering_delay_secs[client_steering_delay_secs_num++] = value_ul;
	}


	return;
}


/* Determine if a client may be granted a dynamic ip adress on
a network, given the network's access policy and the client's
protocol (BootP or DHCP), client's registered or unregistered state,
and possible presence of :ro: tag in client's registration.
'res' is the access restriction flags for the network.
'client' is the flags associated with the client.
return true/false. */
int 
access_network(res, client)
	unsigned char   res, client;
{

	/* if the client is speaking BootP and the network disallows service to 
	   BootP clients, return false */
	if ((BOOTP_ACCESS & client) && !(BOOTP_ACCESS & res))
		return FALSE;

	/* if the client is speaking DHCP and the network disallows service to
	   DHCP clients, return false */
	if ((DHCP_ACCESS & client) && !(DHCP_ACCESS & res))
		return FALSE;

	/* if the network requires clients have the :ro: tag set, and the client does
	   not have that tag set, return false */
	if ((ROAMING_ACCESS & res) && !(ROAMING_ACCESS & client))
		return FALSE;

	/* if the network requires clients be registered, and the client is not
	   registered, return false */
	if ((REG_ACCESS & res) && !(REG_ACCESS & client))
		return FALSE;

	/* if the network requires clients be unregistered, and the client is 
	   not unregistered, return false */
	if ((UNREG_ACCESS & res) && !(UNREG_ACCESS & client))
		return FALSE;

	return TRUE;
}

struct host    *
get_dynamic_ip(gwaddr, code)
	struct in_addr  gwaddr;
	char            code;
{
	struct cable   *c;
	struct gateway *gw;
	int             rc, found, i, j;
	unsigned        hashcode;
	struct link_list *t, **l;
	struct host    *hp, *old;

#ifdef DEBUG
	if (debug>15)
		report(LOG_DEBUG, "   get_dynamic_ip(gwaddr=%s, code=%d)", inet_ntoa(gwaddr), code);
#endif
	if (gwaddr.s_addr == 0) {
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "   gwaddr==0, so setting gwaddr to my ip address");
#endif
		gwaddr.s_addr = my_ip_addr.s_addr;
	}
		/* Note that dynamic assignment will not work reliably if server is multihomed,
		   and client is on a directly-attached network, as we don't know on what
		   interface the request was received.  giaddr was 0, so we just assume it was
		   received on the same interface as my_ip_addr (first ip addr returned by gethostbyname),
		   which will be WRONG if the client as actually attached to any other interface.  So
		   if the server is multihomed, don't use it to do dynamic assignment of addresses
		   on any locally-attached networks. */
	hashcode = hash_HashFunction((u_char*)&gwaddr, 4);
	gw = (struct gateway *) hash_Lookup(gwhshtbl, hashcode, gwlcmp, &gwaddr);
	if (!gw) {
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "   gateway not found in gwhshtbl, will return NULL");
#endif
		return NULL;
	}
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "   gateway found in gwhshtbl");
#endif

	hp = NULL;
	found = 0;
	for (i = 0; !found; i++) { /* walk through all the address pools associated with this gateway */
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   i loop top: i=%d, gw->num=%d", i, gw->num);
#endif
		if (i == gw->num) {
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   i==gm->num, we've exhausted all pools avail for this gateway, return NULL");
#endif
			return NULL;
		}
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   examining pool: gw->nets[i]==%s", gw->nets[i]);
#endif
		hashcode = hash_HashFunction((u_char*)gw->nets[i], strlen(gw->nets[i]));
		c = (struct cable *) hash_Lookup(cablehshtbl, hashcode, cablelcmp, gw->nets[i]);
		if (!c) { /* this should never happen */
			report(LOG_ERR, "poolname %s not found in cablehshtbl!", gw->nets[i]);
			continue;
		}
		if (!access_network(c->code, code)) {
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   access policy prevents client access to pool %s", gw->nets[i]);
#endif
			continue;
		}
		if (c->free) {
			/* there are free addresses in this dynamic pool */
			if (debug > 3)
				report(LOG_INFO, "   dynamic pool %s has %d free addresses", gw->nets[i], c->free);
		} else {
			/* an empty pool is worthy of logging regardless of debug level, and is worthy of logging at a higher severity. */
			report(LOG_NOTICE, "   dynamic pool %s has %d free addresses", gw->nets[i], c->free);
		}

		if (!c->free) 
			continue;	/* no free addresses */


		for (l = &c->head; *l; l = &(*l)->next) {
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   top of l loop, examining  next free dynamic addr from this net");
#endif

			hashcode = hash_HashFunction((u_char*)(*l)->data, 4);
			hp = (struct host *) hash_Lookup(iphashtable, hashcode, ipcmp, (*l)->data);
			if (!hp) { /* this should never happen */
				report(LOG_ERR, "   get_dynamic_ip(): hp not found in iphashtable!");
				continue;
			}
			if (! hp->flags.iaddr) { /* this should never happen */
				report(LOG_ERR, "   get_dynamic_ip(): hp->flags.iaddr is NULL!");
				continue;
			}
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   candidate hp->iaddr=%s", inet_ntoa(hp->iaddr));
#endif
			if (! hp->flags.dynamic) { /* don't know if that's an error or not (roaming?), but clearly we can't assign it! */
#ifdef DEBUG
				if (debug > 15)
					report(LOG_DEBUG, "   hp->flags.dynamic not set, won't assign this address");
#endif
				continue;
			}
			if (hash_Exists(ipboundhshtbl, hashcode, ipcmp, (*l)->data)) {
				report(LOG_ERR, "Tried to assign an ip which already had a binding %s", inet_ntoa(*((struct in_addr *) (*l)->data)));
				report(LOG_ERR, "...will not try to assign this address");
				if (strcmp(hp->cable->string, c->name->string)) {
					report(LOG_ERR, "...The bound IP address is listed as being in pool %s", hp->cable->string);
					report(LOG_ERR, "...but was found on the free list for pool %s", c->name->string);
					report(LOG_ERR, "...This suggests the conf file has 'network' statements with overlapping IP ranges");
				} else 
					report(LOG_ERR, "...Check the conf file for 'network' statements with overlapping IP ranges");
				continue; /* 'for l' */
			}
			if (!ping(hp->iaddr, 3, 0, 500000)) {
				report(LOG_ERR, "ping says address is already in use: %s", inet_ntoa(hp->iaddr));
				report(LOG_ERR, "...will not try to assign this address");
				continue;
			}
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   candidate address looks OK, will assign");
#endif
			c->free--;
			t = *l;
			*l = (*l)->next;
			if(t == c->tail){
				c->tail=c->head;
				while(c->tail && c->tail->next)
					c->tail=c->tail->next;
			}
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   dynamic pool %s now has %d free addresses remaining", gw->nets[i], c->free);
#endif
			free(t->data);
			free(t);
			found = 1;
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "   set found=1, and  breaking out of l loop");
#endif
			break;

		} /* 'for l' loop - walking through all the free dynamics in this one pool */

#ifdef DEBUG
		if ((debug > 15) && !found)
			report(LOG_DEBUG, "   exhausted free addrs in this pool");
#endif

	} /* i loop; walking through all the pools for this gateway */

	if (!found) {
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "   exhausted all pools for this gateway");
			report(LOG_DEBUG, "   returning NULL");
#endif
		return NULL;
	}

	old = (struct host *) hash_Lookup(ipboundhshtbl, hp->iphashcode, ipcmp, (hash_datum *)&(hp->iaddr.s_addr));
	if (old)
		reuse_addr(old);

	return hp;
}

/* 
Check if proposed dynamic IP address 'hp' is OK for a client to use, given
that the client's request came via relay agent 'gwaddr', the value of the
policy 'flag', and the 'force' flag.

If it is not OK, return NULL.

Only designed to be used with proposed IP addrs that are dynamic (if hp->flags.cable
is FALSE, we return NULL.  So only call this function if hp->flags.dynamic is TRUE.

We check the proposed IP addr against gwaddr, relying on the topology info 
in gwhashtble to determine if the proposed IP address lies behind that
bootp relay agent.  (If gwaddr==0, we assume it came in locally on my_ip_addr,
a good reason to not run the daemon on a multihomed server.)  You can suppress
this check by setting 'force' to TRUE.

We check the access policy flags 'flag' we are passed (which should the access
rights of the particular client) against the access flags associated with the
network on which the proposed IP address resides.

We check if the proposed dynamic IP addr is available by checking the
linked list of unused IP addrs for the cable.  If it is not on the list,
return NULL.  This means you CANNOT use this function to verify whether it is
OK to keep using a currently-bound address, since it won't be on that
linked list.  Instead, use check_net() to do the IP address range check (for
dynamic or static addrs), then use access_net() to do the policy check (for
dynamics only).

*/

struct host    *
use_addr(hp, gwaddr, flag, force)
	struct host    *hp;
	struct in_addr  gwaddr;
	char            flag;
	int             force;
{
	struct cable   *c;
	struct gateway *gw;
	unsigned        hashcode;
	struct link_list *t, **l;
	int             i;

	/* make sure we've been passed a dynamic IP address */
	if (!hp || !hp->flags.cable || !hp->flags.iaddr)
		return NULL; 

	/* if it didn't come through a bootp relay agent, it is local, so treat it as though
	   WE are the bootp relay agent (since all our own interface addrs are recorded the same as
	   bootp relay agents in gwhashtbl.  Note we stick in my_ip_addr here, so we're going to
	   make our decisions as if the packet arrived on that interface.  That's a good reason to
	   only run this daemon on a single-interface server; with a multihomed server, this approach
	   won't work right all the time. */
	if (gwaddr.s_addr == 0)
		gwaddr.s_addr = my_ip_addr.s_addr;

	if (!force) { /* force is used to skip these checks */
		/* Make sure proposed IP addr lies behind the bootp relay agent gwaddr, given the
		   topology stored in gwhashtbl */
		if (!check_net(&gwaddr, &(hp->iaddr)))
			return NULL;
		/* locate the bootp relay agent in gwhashtbl */
		hashcode = hash_HashFunction((u_char*)&gwaddr, 4);
		gw = (struct gateway *) hash_Lookup(gwhshtbl, hashcode, gwlcmp, &gwaddr);
		if (!gw)
			return NULL; /* gwaddr is not a known bootp relay agent */
		/* This dynamic IP address is a member of the dynamic address pool named 'hp->cable->string'.
		   Make sure that's one of the pools that lies behind gwaddr */
		for (i = 0; i < gw->num; i++) /* walk through the pool names behind gwaddr */
			if (strcmp(gw->nets[i], hp->cable->string) == 0)
				break; /* found! */
		if (i == gw->num)
			return NULL; /* not found */
	}
	/* Locate dynamic pool in cablehshtbl */
	hashcode = hash_HashFunction((u_char*)hp->cable->string, strlen(hp->cable->string));
	c = (struct cable *) hash_Lookup(cablehshtbl, hashcode, cablelcmp, hp->cable->string);
	if (!c)
		return hp; /* BUG: ???  Is this an error?  Does it imply an error in conf file? */
	/* Make sure client with access rights 'flag' can access this dynamic pool */
	if (!access_network(c->code, flag))
		return NULL;

	/* Locate the proposed IP addr in the linked list of available IP addrs in this pool, and remove it. */
	l = &c->head;
	while (*l) {
		if (!bcmp((*l)->data, &(hp->iaddr.s_addr), 4)) {
			c->free--;
			t = *l;
			*l = (*l)->next;
			if(t == c->tail){
				c->tail=c->head;
				while(c->tail && c->tail->next)
					c->tail=c->tail->next;
			}
#ifdef DEBUG
			if (debug > 15) {
				report(LOG_DEBUG, "   use_addr(): will use %s, decremented free addresses in %s pool to %d", inet_ntoa(hp->iaddr), c->name->string, c->free);
			}
#endif
			free(t->data);
			free(t);
			return hp; /* SUCCESS */
		}
		l = &(*l)->next;
	}

	/* we walked off end of list of pool of available IP addrs in this pool but did not find the one that's been
	   proposed to use.  So it must not be free. */
	return NULL;
}


/* reuse_addr(hp): Remove the current binding for 'hp'.  
Locates hp in the boundhshtbl and ipboundhshtbl and
deletes it.  If hp was for a dynamic address, pushes it back into the appropriate pool of
available dynamic addresses.  If 'hp' has a lastbind record, let release-lastbind() change
its expiration time to now.  Update the count of current leases.  */
void 
reuse_addr(hp)
	struct host    *hp;
{
	struct cable   *c;
	unsigned        hashcode, clhashcode, hashDel_hashcode;
    struct lastbind *b,t;
	boolean found_ipboundhshtbl, found_boundhshtbl;
	struct host *hp2;
	struct shared_bindata * hashDel_hpcl;
	struct in_addr hashDel_iaddr;

		if (!hp) {
	#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "reuse_addr(): hp==NULL, ignoring");
			return;
	#endif
		}

		if (! hp->flags.iaddr) {
			report(LOG_ERR, "reuse_addr(): called with hp where hp->flags.iaddr == FALSE");
			return;
			/* tables in memory are now screwed up */
		}

	#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "reuse_addr(): called for %s", inet_ntoa(hp->iaddr));
	#endif

		/* just set 'found' flags to indicate what we found for now, to make some tests below easier.  We postpone
		   deleting them from the hashtables until we're done with all the other housekeeping.  */
		if (! hash_Exists(ipboundhshtbl, hp->iphashcode, ipcmp, &(hp->iaddr.s_addr))) {
			report(LOG_ERR, "reuse_addr(): hp not in ipboundhshtbl!  IP address=%s (Client ID=%s)", inet_ntoa(hp->iaddr), cliid_btoa(hp->cl));
			found_ipboundhshtbl = FALSE;
		} else found_ipboundhshtbl = TRUE;

		clhashcode=hash_HashFunction((u_char*)hp->cl->data,hp->cl->length);
		if (! hash_Lookup(boundhshtbl,clhashcode,boundcliidcmp,hp->cl)) {
			report(LOG_ERR, "reuse_addr(): hp not in boundhshtbl!  Client ID=%s (IP address=%s)", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
			found_boundhshtbl = FALSE;
		} else found_boundhshtbl = TRUE;

		if (! found_ipboundhshtbl && ! found_boundhshtbl) {
			report(LOG_ERR, "reuse_addr(): hp was not found in BOTH current bindings table; ignoring");
			return;
		}

		/* learn if ip addr still in bootptab */
		hp2=(struct host*)hash_Lookup(iphashtable, hp->iphashcode,ipcmp, (hash_datum *)&(hp->iaddr.s_addr));
 
	if (hp->flags.cable) {
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "reuse_addr(): IP was dynamically-assigned");
#endif
		hashcode = hash_HashFunction((u_char*)hp->cable->string, strlen(hp->cable->string));
		c = (struct cable *) hash_Lookup(cablehshtbl, hashcode, cablelcmp, hp->cable->string);
		if (!c) {
			report(LOG_ERR, "reuse_addr(): can't find cable '%s' in cablehshtbl", hp->cable->string);
			report(LOG_ERR, "...so cannot make %s available for re-use", inet_ntoa(hp->iaddr));
		} else if (!hp2) {
			report(LOG_NOTICE, "reuse_addr(): %s no longer in bootptab, will not make available for re-use", inet_ntoa(hp->iaddr));
		} else if (!hp2->flags.dynamic) {
			report(LOG_NOTICE, "reuse_addr(): %s no longer dynamic, will not make available for re-use", inet_ntoa(hp->iaddr));
		} else { 
			push_ip(c, hp->iaddr);
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "reuse_addr(): returned %s to dynamic pool '%s'", inet_ntoa(hp->iaddr), hp->cable->string);
#endif
		}
		ips_reclaimed++;	/* or should we only count this if we did a push_ip(); i.e. only if the address was made
							   available for re-use? */
	}

	/* There *may* be a lastbind record for hp->cliid; if there is, we need to
	   set its expire time to now.  (There won't be a lastbind record if we only
	   sent a a DHCPOFFER but the client didn't follow-up with a REQUEST.) */

    t.cliid=hp->cl;
    b=(struct lastbind*)hash_Lookup(lastbindhshtbl,clhashcode,lastbindcmp,&t);
    if (b) {
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "reuse_addr(): found in lastbindhshtbl, calling release_lastbind()");
#endif
		release_lastbind(hp);
	}
#ifdef DEBUG
	else if (debug > 15) {
			report(LOG_DEBUG, "reuse_addr(): not found in lastbindhshtbl (probably DHCPOFFER that client didn't then REQUEST)");
			report(LOG_DEBUG, "reuse_addr(): ... so don't need to call release_lastbind()");
	}
#endif

	/* leases does not count statically-assigned bootp clients */
	if ( !(hp->code==BOOTP_FLAG && !hp->flags.dynamic) ) {
		leases--;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "reuse_addr(): wasn't for statically-assigned BOOTP, decremented 'leases' count to %d", leases);
#endif
	}
#ifdef DEBUG
	else if (debug > 15)
			report(LOG_DEBUG, "reuse_addr(): was for statically-assigned BOOTP, leaving lease counter unchanged at %d", leases);
#endif

	if (found_boundhshtbl) {
#ifdef DEBUG
		if (debug > 15) 
			report(LOG_DEBUG, "reuse_addr(): found_boundhshtbl==TRUE, will delete from boundhshtbl...");
#endif
		/* Make a standalone copy of hp->cl, since hash_Delete() may free hp (thereby freeing hp->cl)....while hash_Delete()
		   is still walking the hashtable looking for any more matches on this key */ 
		hashDel_hpcl = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata)+hp->cl->length);
		hashDel_hpcl->length = hp->cl->length;
		bcopy(hp->cl->data, hashDel_hpcl->data, hp->cl->length);
		hashDel_hpcl->linkcount = 1;

		if (hash_Delete(boundhshtbl, clhashcode, boundcliidcmp, hashDel_hpcl, free_host))
			report(LOG_ERR, "reuse_addr(): hash_Delete(boundhshtbl) failed for cliid=%s", cliid_btoa(hashDel_hpcl));
		else  {
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "reuse_addr(): ...succesfully deleted from boundhshtbl");
#endif
		}
		free(hashDel_hpcl); /* we're done with this standalone copy */
	}
	if (found_ipboundhshtbl) {
#ifdef DEBUG
		if (debug > 15) 
			report(LOG_DEBUG, "reuse_addr(): found_ipboundhshtbl=TRUE, will delete from ipboundhshtbl...");
#endif
		/* Make standalone copies of hp->iphashcode and hp->iaddr, since hashDelete() may free hp (thereby freeing both
		   hp->iphashcode and hp->iaddr)...while hash_Delete() is still walking the hashtable looking for any more matches
		   on this key and bucket. */
		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, "reuse_addr(): hash_Delete(ipboundhshtbl) failed for IP=%s", inet_ntoa(hashDel_iaddr));
		else {
#ifdef DEBUG
			if (debug > 15) 
				report(LOG_DEBUG, "reuse_addr(): ...succesfully deleted from ipboundhshtbl");
#endif
		}
	}

}


void 
push_ip(cable, addr)
	struct cable   *cable;
	struct in_addr  addr;
{

	if (!cable)
		return;

	if (!cable->head) {
		cable->tail = (struct link_list *) smalloc(sizeof(struct link_list));
		cable->head = cable->tail;
	} else {
		cable->tail->next = (struct link_list *) smalloc(sizeof(struct link_list));
		cable->tail = cable->tail->next;
	}
	bzero(cable->tail, sizeof(struct link_list));

	cable->tail->data = (char *) smalloc(sizeof(struct in_addr));
	bcopy(&addr, cable->tail->data, sizeof(struct in_addr));
	cable->free++;
#ifdef DEBUG
			if (debug > 15) {
				report(LOG_DEBUG, "   push_ip(): added %s to pool %s, incremented free addresses in pool to %d", inet_ntoa(addr), cable->name->string, cable->free);
			}
#endif
}



boolean 
cablelcmp(key, data)
	char           *key;
	struct cable   *data;
{
	return !strcmp(key, data->name->string);
}
boolean 
gwlcmp(key, data)
	struct in_addr *key;
	struct gateway *data;
{

	return (data->addr.s_addr == key->s_addr);
}




/*
 * This function takes a string of whitespace-separated words (tokens) and
 * returns a vector of pointers to the various tokens.  The strings pointed
 * to by the vector have leading and trailing whitespace removed and are
 * null-terminated.
 * 
 * THIS FUNCTION MODIFIES THE ARGUMENT STRING!  Nulls are inserted into the
 * original string to terminate the tokens.  This may cause orphaning of
 * memory if the caller is not careful.
 */

PRIVATE char  **
get_tokens(buff)
	char           *buff;
{
	static char    *token_vector[MAXTOKENS];
	char          **vector;
	int             i;

	i = 0;
	vector = token_vector;
	while (*buff) {
		eat_whitespace(&buff);
		if (*buff) {
			if (i < MAXTOKENS) {
				*vector++ = buff;
				i++;
				while (*buff && !isspace(*buff)) {
					buff++;
				}
				if (*buff == '\0') {
					break;
				}
				*buff++ = '\0';
			} else {
				break;
			}
		}
	}
	*vector = NULL;
	return token_vector;
}



char           *
savestr(s)
	char           *s;
{
	char           *t;

	t = (char *) smalloc(strlen(s) + 1);
	strcpy(t, s);
	return t;
}
