/***********************************************************
        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 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},
};





/*
 * Forward declarations
 */

PRIVATE void    kw_gateway();
PRIVATE void    kw_network();
PRIVATE void    kw_proxy();
PRIVATE void    kw_known_server_identifier();
PRIVATE int 	parse_line();
PRIVATE char          **get_tokens();
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 */

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

	fclose(fp);

	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);
	}
	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';
				}
				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_GCINTERVAL:
				if (*keyvec)
					gcinterval = atoi(*keyvec);
				break;
			case KEYW_FLUSHITEMS:
				if (*keyvec)
					max_cachequeue = atoi(*keyvec);
				break;
			case KEYW_FLUSHINTERVAL:
				if (*keyvec)
					flush_interval = atoi(*keyvec);
				break;
			case KEYW_BINDINGS:
				if (*keyvec)
					bindingsdir = savestr(*keyvec);
				break;
			case KEYW_EXPIRE_INFINITE_DYNAMICS:
				if (*keyvec)
					expire_infinite_dynamics = atoi(*keyvec);
				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;
					}
				}
				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;
					}
				}
				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;
					}
				}
				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;
					}
				}
				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;
					}
				}
				break;
			case KEYW_READTABINTERVAL:
				if (*keyvec)
					readtabinterval = atoi(*keyvec);
				break;
			case KEYW_READCONFINTERVAL:
				if (*keyvec)
					readconfinterval = atoi(*keyvec);
				break;
			default:
				report(LOG_ERR, "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);

	cp = TOKEN_NETWORK;
	rc = prs_inetaddr(&cp, &c->net);

	cp = TOKEN_MASK;
	rc = prs_inetaddr(&cp, &c->mask);


	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 arguement 4, was expecting\
			\"dhcp\",\"bootp\", or \"any\" but received \"%s\"", tokenvec[4]);
		} while (next);
	}
	c->linkcount = 1;
	hash_Insert(cablehshtbl, hashcode, nullcmp, c, c);
	mask = tmpaddr.s_addr = c->net & c->mask;

	i = 0;
	while ((tmpaddr.s_addr & c->mask) == mask) {
		hashcode = hash_HashFunction((u_char*)&tmpaddr, 4);
		hp = (struct host *) hash_Lookup(iphashtable, hashcode, ipcmp, &(tmpaddr.s_addr));
		if (hp && hp->flags.dynamic) {
			i++;
			hp->flags.cable = TRUE;
			hp->cable = c->name;
			c->name->linkcount++;
			if (init || (ipboundhshtbl && !hash_Exists(ipboundhshtbl, hashcode, ipcmp, &(tmpaddr.s_addr))))
				push_ip(c, tmpaddr);
		}
		tmpaddr.s_addr = htonl(ntohl(tmpaddr.s_addr)+1);

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

	router = NULL;
	if (*tokenvec[3])
		router = tokenvec + 4;
	while (router && router[0] && router[1]) {
		prs_inetaddr(router, &tmpaddr);
		hashcode = hash_HashFunction((u_char*)&tmpaddr, 4);
		gw = (struct gateway *) hash_Lookup(gwhshtbl, hashcode, gwlcmp, &tmpaddr);
		if (!gw) {
			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;
			gw->nets[0] = savestr(tokenvec[0]);
			gw->linkcount = 1;
			hash_Insert(gwhshtbl, hashcode, nullcmp, gw, gw);
		} else {
			size = sizeof(char *) * (gw->num + 1);
			nets = (char **) smalloc(size);
			bcopy(gw->nets, nets, size - sizeof(char *));
			nets[gw->num] = savestr(tokenvec[0]);
			free(gw->nets);
			gw->nets = nets;
			gw->num++;
		}
		prs_inetaddr(router + 1, &gw->mask);
		router += 2;
	}


	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);
	hashcode = hash_HashFunction((u_char*)&tmpaddr, 4);
	gw = (struct gateway *) hash_Lookup(gwhshtbl, hashcode, gwlcmp, &tmpaddr);
	if (!gw) {
		/* add it to the gwhashtabl.  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;
		gw->proxyaddr = NULL;
		gw->proxymask = NULL;
		gw->proxynum = 0;
		gw->linkcount = 1;
		hash_Insert(gwhshtbl, hashcode, nullcmp, gw, gw);
	}
	cp = tokenvec[1];
	prs_inetaddr(&cp, &gw->mask);
	/* if any networks were specified (names of dynamic address pools), 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) {
		size = sizeof(char *) * (gw->num + 1);
		nets = (char **) smalloc(size);
		bcopy(gw->nets, nets, size - sizeof(char *));
		nets[gw->num] = savestr(*network);
		gw->num++;
		free(gw->nets);
		gw->nets = nets;
		network++;
	}

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

	/* parse tokens to "proxy" keyword as network and subnet mask pairs */
	network = tokenvec + 1;
	while (*network) {
		if ( !*(network + 1) ) {
			report(LOG_ERR, "Need Network Mask for proxy definition, network=%s",
			       *network);
			return;
		}
		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++;

		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++;
  	}
  
  	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 for known_server_identifier");
		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;
	}

	known_serverids_hshtbl_inuse = 1;

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

	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 (debug > 3)
			report(LOG_INFO, "   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->mask & cable->net) != (cable->mask & addr.s_addr))
		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;
}
