/************************************************************************
          Copyright 1988, 1991 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 Carnegie Mellon University not be used
in advertising or publicity pertaining to distribution of the software
without specific, written prior permission.

CARNEGIE MELLON UNIVERSITY 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.
************************************************************************/

/*
 * bootpd configuration file reading code.
 *
 * The routines in this file deal with reading, interpreting, and storing
 * the information found in the bootpd configuration file (usually
 * /etc/bootptab).
 */


#include "defs.h"
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>  /* inet_ntoa */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "defaults.h"
#include "bootp.h"
#include "hash.h"
#include "hwaddr.h"
#include "lookup.h"
#include "report.h"
#include "tzone.h"
#include "bootpd.h"
#include "dhcpd.h"
#include "dhcp.h"
#include "readfile.h"
#include "utils.h"
#include "my_syslog.h"


/* Non-standard hardware address type (see bootp.h) */
#define HTYPE_DIRECT	0

/* Error codes returned by eval_symbol: */
#define SUCCESS			  0
#define E_END_OF_ENTRY		(-1)
#define E_SYNTAX_ERROR		(-2)
#define E_UNKNOWN_SYMBOL	(-3)
#define E_BAD_IPADDR		(-4)
#define E_BAD_HWADDR		(-5)
#define E_BAD_LONGWORD		(-6)
#define E_BAD_HWATYPE		(-7)
#define E_BAD_PATHNAME		(-8)
#define E_BAD_VALUE 		(-9)
#define E_ILLEGAL_NULLSTRING	(-10)
#define E_BAD_COMBINATION	(-11)

/* Wide enough to hold a string representation "Txxx", where 'xxx' is the longest possible get_u_long() return value. */
#define READ_OPTION_TAGNAME_BUFSIZE 12
/* Exception codes returned by read_option_tagname: */
#define E_READ_OPTION_TAGNAME_END_OF_STRING             (-1)
#define E_READ_OPTION_TAGNAME_UNKNOWN_SYMBOL            (-2)
#define E_READ_OPTION_TAGNAME_GENERIC_TAG_OUT_OF_RANGE  (-3)
#define E_READ_OPTION_TAGNAME_NOT_OPTION_FIELD          (-4)
#define E_READ_OPTION_TAGNAME_SYMBOL_CASE_MISSING       (-5)


/* Tag idendities. */
#define SYM_NULL		  0
#define SYM_BOOTFILE		  1
#define SYM_COOKIE_SERVER	  2
#define SYM_DOMAIN_SERVER	  3
#define SYM_GATEWAY		  4
#define SYM_HWADDR		  5
#define SYM_HOMEDIR		  6
#define SYM_HTYPE		  7
#define SYM_IMPRESS_SERVER	  8
#define SYM_IPADDR		  9
#define SYM_LOG_SERVER		 10
#define SYM_LPR_SERVER		 11
#define SYM_NAME_SERVER		 12
#define SYM_RLP_SERVER		 13
#define SYM_SUBNET_MASK		 14
#define SYM_TIME_OFFSET		 15
#define SYM_TIME_SERVER		 16
#define SYM_VENDOR_MAGIC	 17
#define SYM_SIMILAR_ENTRY	 18
#define SYM_NAME_SWITCH		 19
#define SYM_BOOTSIZE		 20
#define SYM_BOOT_SERVER		 22
#define SYM_TFTPDIR		 23
#define SYM_DUMP_FILE		 24
#define SYM_DOMAIN_NAME          25
#define SYM_SWAP_SERVER          26
#define SYM_ROOT_PATH            27
#define SYM_EXTEN_FILE           28
#define SYM_REPLY_ADDR           29
#define SYM_NIS_DOMAIN           30	/* RFC 1533 */
#define SYM_NIS_SERVER           31	/* RFC 1533 */
#define SYM_NTP_SERVER           32	/* RFC 1533 */
#define SYM_EXEC_FILE		 33	/* YORK_EX_OPTION */
#define SYM_MSG_SIZE 		 34
#define SYM_MIN_WAIT		 35
#define SYM_NBNS_SERVER			36
#define SYM_NODE_TYPE			37
#define SYM_VENDOR_SIZE			38
#define SYM_DYNAMIC			40
#define SYM_ROAMING			41
#define SYM_LEASE			42
#define SYM_NB_SCOPE			43
#define SYM_RENEW			44
#define SYM_REBIND			45
#define SYM_NOT_RQ			46
#define SYM_CLIID			47
#define SYM_MAX_LEASE		48
#define SYM_BROADCAST_ADDR	49
#define SYM_DENY_CLIENT		50
#define SYM_PERFORM_ROUTER_DISCOVERY	51
#define SYM_STATIC_ROUTE	52
#define SYM_CLIENT_FQDN		53
#define SYM_DENY_CLIENT_DYNAMIC		55
#define SYM_ETHERNET_ENCAPSULATION		56
#define SYM_NBDD_SERVER		57
#define SYM_SMTP_SERVER		58
#define SYM_POP3_SERVER		59
#define SYM_NNTP_SERVER		60
#define SYM_WWW_SERVER		61
#define SYM_FINGER_SERVER	62
#define SYM_IRC_SERVER		63
#define SYM_BOOTP_EXCLUDE	64
#define SYM_IP_FORWARDING	65
#define SYM_IP_SOURCE_ROUTING	66
#define SYM_IP_TTL			67
#define SYM_ALL_SUBNETS_ARE_LOCAL		68
#define SYM_PERFORM_MASK_DISCOVERY		69
#define SYM_MASK_SUPPLIER		70
#define SYM_ROUTER_SOLICITATION_ADDR		71
#define SYM_TRAILER_ENCAPSULATION		72
#define SYM_TCP_TTL			73
#define SYM_TCP_KEEPALIVE_GARBAGE	74
#define SYM_XWINDOWS_FONT_SERVER	75
#define SYM_XWINDOWS_DISPLAY_MANAGER	76
#define SYM_NISPLUS_DOMAIN	77
#define SYM_NISPLUS_SERVER	78
#define SYM_BOOTP_INCLUDE	79
/* XXX - Add new tags here */

#define OP_ADDITION		  1	/* Operations on tags */
#define OP_DELETION		  2
#define OP_BOOLEAN		  3

#define MAXINADDRS		 16	/* Max size of an IP address list */
#define MAXBUFLEN		256	/* Max temp buffer space */
#define MAXENTRYLEN	       2048	/* Max size of an entire entry */



/*
 * Structure used to map a configuration-file symbol (such as "ds") to a
 * unique integer.
 */

struct symbolmap {
	char *symbol;
	int symbolcode;
};


struct htypename {
	char *name;
	byte htype;
};


PRIVATE int nhosts;				/* Number of hosts (/w hw or IP address) */
PRIVATE int nentries;			/* Total number of entries */
time_t modtime = 0;		/* modification time of bootptab that we read most-recently */
time_t bootptab_lastread_time = 0;		/* time we last read bootptab */
PRIVATE char *current_hostname;	/* Name of the current entry. */
PRIVATE char current_tagname[12]; /* wide enough to hold any 2-char tagname with NUL terminator, or the generic tagname "Tnnn".
								     (nnn should be <=255, but it's possible for erroneous input data to make it as large as 2^32) */

/*
 * List of symbolic names used in the bootptab file.  The order and actual
 * values of the symbol codes (SYM_. . .) are unimportant, but they must
 * all be unique.
 */

PRIVATE struct symbolmap symbol_list[] = {
	{"ba", SYM_BROADCAST_ADDR},
	{"be", SYM_BOOTP_EXCLUDE},
	{"bf", SYM_BOOTFILE},
	{"bi", SYM_BOOTP_INCLUDE},
	{"bs", SYM_BOOTSIZE},
	{"cf", SYM_CLIENT_FQDN},
	{"cl", SYM_CLIID},
	{"cr", SYM_IP_SOURCE_ROUTING},
	{"cs", SYM_COOKIE_SERVER},
	{"de", SYM_DENY_CLIENT},
	{"dd", SYM_DENY_CLIENT_DYNAMIC},
	{"df", SYM_DUMP_FILE},
	{"dl", SYM_LEASE},
	{"dn", SYM_DOMAIN_NAME},
	{"ds", SYM_DOMAIN_SERVER},
	{"dy", SYM_DYNAMIC},
	{"ef", SYM_EXTEN_FILE},
	{"en", SYM_ETHERNET_ENCAPSULATION},
	{"ex", SYM_EXEC_FILE},		/* YORK_EX_OPTION */
	{"fi", SYM_FINGER_SERVER},
	{"gw", SYM_GATEWAY},
	{"ha", SYM_HWADDR},
	{"hd", SYM_HOMEDIR},
	{"hn", SYM_NAME_SWITCH},
	{"ht", SYM_HTYPE},
	{"im", SYM_IMPRESS_SERVER},
	{"if", SYM_IP_FORWARDING},
	{"ip", SYM_IPADDR},
	{"ir", SYM_IRC_SERVER},
	{"kg", SYM_TCP_KEEPALIVE_GARBAGE},
	{"lg", SYM_LOG_SERVER},
	{"lp", SYM_LPR_SERVER},
	{"ma", SYM_SMTP_SERVER},
	{"md", SYM_PERFORM_MASK_DISCOVERY},
	{"ml", SYM_MAX_LEASE},
	{"ms", SYM_MSG_SIZE},
	{"mu", SYM_MASK_SUPPLIER},
	{"mw", SYM_MIN_WAIT},
	{"nd", SYM_NBDD_SERVER},
	{"nn", SYM_NNTP_SERVER},
	{"no", SYM_NODE_TYPE},
	{"nr", SYM_NOT_RQ},
	{"ns", SYM_NAME_SERVER},
	{"nt", SYM_NTP_SERVER},
	{"pd", SYM_NISPLUS_DOMAIN},
	{"po", SYM_POP3_SERVER},
	{"ps", SYM_NISPLUS_SERVER},
	{"ra", SYM_REPLY_ADDR},
	{"rb", SYM_REBIND},
	{"rd", SYM_PERFORM_ROUTER_DISCOVERY},
	{"rl", SYM_RLP_SERVER},
	{"rn", SYM_RENEW},
	{"ro", SYM_ROAMING},
	{"rp", SYM_ROOT_PATH},
	{"rs", SYM_ROUTER_SOLICITATION_ADDR},
	{"sa", SYM_BOOT_SERVER},
	{"sc", SYM_NB_SCOPE},
	{"sl", SYM_ALL_SUBNETS_ARE_LOCAL},
	{"sm", SYM_SUBNET_MASK},
	{"sr", SYM_STATIC_ROUTE},
	{"sw", SYM_SWAP_SERVER},
	{"tc", SYM_SIMILAR_ENTRY},
	{"td", SYM_TFTPDIR},
	{"te", SYM_TRAILER_ENCAPSULATION},
	{"tl", SYM_TCP_TTL},
	{"to", SYM_TIME_OFFSET},
	{"ts", SYM_TIME_SERVER},
	{"tt", SYM_IP_TTL},
	{"vm", SYM_VENDOR_MAGIC},
	{"vs", SYM_VENDOR_SIZE},
	{"ws", SYM_NBNS_SERVER},
	{"ww", SYM_WWW_SERVER},
	{"xd", SYM_XWINDOWS_DISPLAY_MANAGER},
	{"xf", SYM_XWINDOWS_FONT_SERVER},
	{"yd", SYM_NIS_DOMAIN},
	{"ys", SYM_NIS_SERVER},
	/* XXX - Add new tags here */
};


/*
 * List of symbolic names for hardware types.  Name translates into
 * hardware type code listed with it.  Names must begin with a letter
 * and must be all lowercase.  This is searched linearly, so put
 * commonly-used entries near the beginning.
 */

PRIVATE struct htypename htnamemap[] = {
	{"ethernet", HTYPE_ETHERNET},
	{"ethernet3", HTYPE_EXP_ETHERNET},
	{"ether", HTYPE_ETHERNET},
	{"ether3", HTYPE_EXP_ETHERNET},
	{"ieee802", HTYPE_IEEE802},
	{"tr", HTYPE_IEEE802},
	{"token-ring", HTYPE_IEEE802},
	{"pronet", HTYPE_PRONET},
	{"chaos", HTYPE_CHAOS},
	{"arcnet", HTYPE_ARCNET},
	{"ax.25", HTYPE_AX25},
	{"direct", HTYPE_DIRECT},
	{"serial", HTYPE_DIRECT},
	{"slip", HTYPE_DIRECT},
	{"ppp", HTYPE_DIRECT}
};



/*
 * Externals and forward declarations.
 */

#ifdef	__STDC__
#define P(args) args
#else
#define P(args) ()
#endif

extern boolean iplookcmp();
boolean nmcmp P((hash_datum *, hash_datum *));

PRIVATE void
	adjust P((char **));
void
	del_string P((struct shared_string *));
void
	del_bindata P((struct shared_bindata *));
PRIVATE void
	del_iplist P((struct in_addr_list *));
void
	eat_whitespace P((char **));
PRIVATE int
	eval_symbol P((char **, struct host *));
void
	fill_defaults P((struct host *, char **));
void
	free_host P((hash_datum *));
PRIVATE struct in_addr_list *
	get_addresses P((char **));
PRIVATE struct shared_string *
	get_shared_string P((char **));
PRIVATE char *
	get_string P((char **, char *, u_int *));
PRIVATE u_int32
	get_u_long P((char **));
PRIVATE boolean
	goodname P((char *));
PRIVATE boolean
	hwinscmp P((hash_datum *, hash_datum *));
PRIVATE int
	interp_byte P((char **, byte *));
void
	makelower P((char *));
boolean
        nullcmp P((hash_datum *, hash_datum *));
PRIVATE int
	process_bindata P(( char **, struct shared_bindata **));
PRIVATE int
	process_entry P((struct host *, char *));
PRIVATE int
	make_bootp_options_vector P((char *bootp_options_str, struct shared_bindata **bootp_options_vector, char *errmsg_prefix));
PRIVATE int
	read_option_tagname P((char **src, char *tagname));
PRIVATE void
	remove_non_bootp_excludable_options P((Bitvector vector, char *errmsg_prefix));
PRIVATE int
	process_generic P((char **, struct shared_bindata **, u_int));
PRIVATE byte *
	prs_haddr P((char **, u_int));
int
	prs_inetaddr P((char **, u_int32 *));
PRIVATE void
	read_entry P((FILE *, char *, u_int *));

#undef P


/*
 * Vendor magic cookies for CMU and RFC1048
 */
u_char vm_cmu[4] = VM_CMU;
u_char vm_rfc1048[4] = VM_RFC1048;

/*
 * Main hash tables
 */
hash_tbl *hwhashtable;
hash_tbl *iphashtable;
hash_tbl *nmhashtable;
hash_tbl *clhashtable;
extern hash_tbl *ipboundhshtbl;
extern hash_tbl *boundhshtbl;


/*	Called by readtab() after it re-reads bootptab file.  
	Also called at launch time after all the data structs (including bootptab) have been init'd.

	It's only safe to call this when init==1, as at other times (e.g. early in program launch)
	some of the data struct(s) we expect to read are not yet init'd.

	Walks the boundhshtbl to locate which current bindings should be deleted 
	(as a result of a change to the bootptab since the time the binding was created).

	XXX This is far from perfect -- it only detects the grossest changes that invalidate a 
	current binding, focusing on IP address, hardware address/clientid, and hostname.
	It doesn't look for the many other possible changes to a lease's characteristics
	that you might wish for it to use as a trigger to invalidate the old lease.
	(Detecting those more subtle changes generally requires a kill and restart of server,
	as that will force the server to construct entirely new leases (at start time) based on
	the current bootptab and dhcpd.conf, as per init_dhcp().
*/
void delete_changed()
{
    struct host *hp, *hp2;


	/* Walk current leases */
	for (hp = (struct host *) hash_FirstEntry(boundhshtbl); hp != NULL; hp = (struct host *) hash_NextEntry(boundhshtbl)) {

		/* Using the IP address of the current lease, look for it in bootptab. */
		hp2 = (struct host*) hash_Lookup(iphashtable, hp->iphashcode, ipcmp, &(hp->iaddr.s_addr));

		if	(!hp2) {
			if (debug>2)
				report(LOG_INFO,"Removing binding for %s (cliid=%s): the IP address is no longer in bootptab", inet_ntoa(hp->iaddr), cliid_btoa(hp->cl));
			/* Discard the lease (delete the binding) */
			reuse_addr(hp);
			continue;
		}

		if 	(hp->flags.haddr && ! hp2->flags.haddr) {
			/* Lease is for a static IP address (as hp->flags.haddr is set),
			   but bootptab entry for this IP address has no hardware address.
			*/
			if (debug>2)
				report(LOG_INFO,"Removing binding for %s (cliid=%s): the binding indicates it is for a static IP address, but the bootptab entry for this IP address has no hardware address", inet_ntoa(hp->iaddr), cliid_btoa(hp->cl));
			/* Discard the lease (delete the binding) */
			reuse_addr(hp);
			continue;

		}

		if (hp->flags.haddr && hp->flags.htype && hp2->flags.haddr && hp2->flags.htype) {
			/* Lease has a hardware address and type (implying it must be for a static IP address),
			   bootptab entry for this IP address has a hardware address (implies it too is for a static IP address).
			*/
			if (hp->htype != hp2->htype) {
				if (debug>2)
					report(LOG_INFO,"Removing binding for %s (cliid=%s): the binding and bootptab both say that the IP address is static, but have different hardware types", inet_ntoa(hp->iaddr), cliid_btoa(hp->cl));
				/* Discard the lease (delete the binding) */
				reuse_addr(hp);
				continue;
			} else if (bcmp(hp->haddr, hp2->haddr, haddrlength(hp->htype))) {
				if (debug>2)
					report(LOG_INFO,"Removing binding for %s (cliid=%s): the binding and bootptab both say that the IP address is static, but have different hardware addresses", inet_ntoa(hp->iaddr), cliid_btoa(hp->cl));
				/* Discard the lease (delete the binding) */
				reuse_addr(hp);
				continue;
			}
		}

		if (hp->flags.cliid && hp2->flags.cliid && bcmp(hp->cliid->data, hp2->cliid->data, hp->cliid->length)) {
			/* Lease has a cliid  (no surprise),
			   bootptab entry for this IP address has a cliid (implying it is for a static IP address),
			   but the two cliids do not match.
			*/
			if (debug>2)
				report(LOG_INFO,"Removing binding for %s (cliid=%s): the binding's cliid doesn't match the cliid in the bootptab entry for this IP address", inet_ntoa(hp->iaddr), cliid_btoa(hp->cl));
			/* Discard the lease (delete the binding) */
			reuse_addr(hp);
			continue;
		}

		if (!hp->flags.dynamic && strcmp(hp->hostname->string, hp2->hostname->string)) {
			/* Lease is for a static IP address,
			   but hostname of lease does not match hostname of client.
			*/
			if (debug>2)
				report(LOG_INFO,"Removing binding for %s (cliid=%s): the binding is for a static IP address, but the hostnames in the binding and the bootptab (for this IP address) don't match", inet_ntoa(hp->iaddr), cliid_btoa(hp->cl));
			/* Discard the lease (delete the binding) */
			reuse_addr(hp);
			continue;
		}
	}
}

/*
 * Allocate hash tables for hardware address, ip address, and hostname
 * (shared by bootpd and bootpef)
 */
void
rdtab_init()
{
	hwhashtable = hash_Init(HASHTABLESIZE);
	iphashtable = hash_Init(HASHTABLESIZE);
	nmhashtable = hash_Init(HASHTABLESIZE);
	clhashtable = hash_Init(HASHTABLESIZE);
	if (!(hwhashtable && iphashtable && nmhashtable && clhashtable)) {
		report(LOG_ERR, "Unable to allocate hash tables.");
		exit(1);
	}
}


/*
 * Possibly read the bootptab file.
 * If 'force' is true, we will read the bootptab file.
 * If 'force' is false, we may read the bootptab file. 
 *
 * If we do end up reading the bootptab, we also call readconf(1) to force a read of the conf file.
 */
void
readtab(force)
	int force;
{
	struct host *hp,*hp2;
	FILE *fp;
	struct stat st;
	unsigned nmhashcode, hwhashcode, clhashcode, buflen;
	static char buffer[MAXENTRYLEN];
	struct in_addr_list *tmpaddrlist;
	struct unknownhost **u, *tmp_unknown;
	time_t now;


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

		now = time(0);

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

				strcpy(bootptab_lastread_time_str, ctime(&(bootptab_lastread_time)));
				bootptab_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 bootptab modification time; now=%s, and %d seconds have not passed since the file was last read (%s)", now_str, readtabinterval, bootptab_lastread_time_str);

			}
#endif
			return;
		}

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

		if (stat(bootptab, &st) < 0) {
			report(LOG_ERR, "stat on \"%s\": %s", bootptab, 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", bootptab, timestr);
		}
#endif
		if ((st.st_mtime <= modtime) && 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\"", bootptab);
	}

	if ((fp = fopen(bootptab, "r")) == NULL) {
		report(LOG_ERR, "error opening \"%s\": %s", bootptab, get_errmsg());
		/* XXX shouldn't this be fatal? */
		return;
	}
	/*
	 * 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;
	}
	modtime = st.st_mtime;

	bootptab_lastread_time = time(0);

	/*
	 * Entirely erase all hash tables.
	 */
	hash_Reset(hwhashtable, free_host);
	hash_Reset(iphashtable, free_host);
	hash_Reset(nmhashtable, free_host);
	hash_Reset(clhashtable, free_host);

	nhosts = 0;
	nentries = 0;
	while (TRUE) {
		buflen = sizeof(buffer);
		read_entry(fp, buffer, &buflen);
		if (buflen == 0) {		/* More entries? */
			break;
		}
		hp = (struct host *) smalloc(sizeof(struct host));
		bzero((char *) hp, sizeof(*hp));
		/* the link count it zero */

		/*
		 * Get individual info
		 */
		if (process_entry(hp, buffer) < 0) {
			hp->linkcount = 1;
			free_host((hash_datum *) hp);
			continue;
		}
		/*
		 * If this is not a dummy (a.k.a. template) entry, and the IP or HW
		 * address is not yet set, try to get them here.
		 * Dummy entries have . as first char of name.
		 */
#if 0
		if (goodname(hp->hostname->string)) {
			char *hn = hp->hostname->string;
			u_int32 value;
			if (hp->flags.iaddr == 0) {
				if (lookup_ipa(hn, &value)) {
				/*	report(LOG_ERR, "can not get IP addr for %s", hn);
					report(LOG_ERR, "(dummy names should start with '.')");*/
				} else {
					hp->iaddr.s_addr = value;
					hp->flags.iaddr = TRUE;
				}
			}
			/* Set default subnet mask. */
			if (hp->flags.subnet_mask == 0) {
				if (lookup_netmask(hp->iaddr.s_addr, &value)) {
					report(LOG_ERR, "can not get netmask for %s", hn);
				} else {
					hp->subnet_mask.s_addr = value;
					hp->flags.subnet_mask = TRUE;
				}
			}
		}
#endif

		/* if cliid not explicitly specified, but there's an haddr, compute cliid based on that */
		if(!hp->flags.cliid && hp->flags.haddr){
			hp->flags.cliid=TRUE;
			hp->cliid=(struct shared_bindata*)
				smalloc(sizeof(struct shared_bindata)+haddrlength(hp->htype));
			hp->cliid->length=haddrlength(hp->htype)+1;
			hp->cliid->data[0]=hp->htype;
			bcopy(hp->haddr,hp->cliid->data+1,haddrlength(hp->htype));
			hp->cliid->linkcount=1;
		}

		nentries++; /* this counts total entries parsed, not necessarily all of which are successfully added to hash tables */

		/* before attempting to insert into relevant hash tables, check that it's not already in them, since discovering
		   that half-way through insertions would leave us with inconsistent hash tables */

		/* make sure not already in nmhashtable */
		nmhashcode = hash_HashFunction((u_char *) hp->hostname->string, strlen(hp->hostname->string));
		if (hash_Lookup(nmhashtable,nmhashcode,nmcmp,hp->hostname->string)) {
			report(LOG_ERR, "skipping bootptab entry \"%s\": duplicate entry name", hp->hostname->string);
			hp->linkcount = 1;
			free_host((hash_datum *) hp);
			continue;
		}

		/* if we have a hwaddr, make sure not already in hwhashtable */
		if (hp->flags.htype && hp->flags.haddr) {
			hwhashcode = hash_HashFunction(hp->haddr, haddrlength(hp->htype));
			if (hash_Lookup(hwhashtable,hwhashcode,hwlookcmp,hp)) {
				report(LOG_ERR, "skipping bootptab entry \"%s\": duplicate %s address: %s",
					hp->hostname->string, netname(hp->htype), haddrtoa(hp->haddr, haddrlength(hp->htype)));
				hp->linkcount = 1;
				free_host((hash_datum *) hp);
				continue;
			}
		}

		/* if we have an ipaddr, make sure not already in iphashtable */
		if (hp->flags.iaddr) {
			hp->iphashcode = hash_HashFunction((u_char *) & (hp->iaddr.s_addr), 4);
			if (hash_Lookup(iphashtable,hp->iphashcode,ipcmp,(hash_datum*)&(hp->iaddr.s_addr))) {
				report(LOG_ERR, "skipping bootptab entry \"%s\": duplicate IP address: %s",
					hp->hostname->string, inet_ntoa(hp->iaddr));
				hp->linkcount = 1;
				free_host((hash_datum *) hp);
				continue;
			}
		}

		/* if we have a cliid, make sure not already in clhashtable */
		if (hp->flags.cliid) {
			clhashcode = hash_HashFunction(hp->cliid->data, hp->cliid->length);
			if (hash_Lookup(clhashtable,clhashcode,cliidcmp,hp->cliid)) {
				report(LOG_ERR, "skipping bootptab entry \"%s\": duplicate Client Identifier...", hp->hostname->string);
				report_cliid(LOG_ERR,hp->cliid);
				hp->linkcount = 1;
				free_host((hash_datum *) hp);
				continue;
			}
		}

		/* now we know it's not already in any of the relevant hash tables, so go ahead and attempt the insertions */

		/* Register by HW addr if known. */
		if (hp->flags.htype && hp->flags.haddr) {
			if(hp->flags.dynamic) /* correct common error */
				report(LOG_ERR,"ignoring dynamic flag in bootptab entry \"%s\" because both an IP address AND a hardware address are specified", hp->hostname->string);
			hp->flags.dynamic=FALSE;
			/* We will either insert it or free it. */
			hp->linkcount++;
			if (hash_Insert(hwhashtable, hwhashcode, hwinscmp, hp, hp) < 0) {
				report(LOG_ERR, "readtab(): hash_Insert(hwhashtable) failed for entry \"%s\" with %s address %s",
					hp->hostname->string, netname(hp->htype), haddrtoa(hp->haddr, haddrlength(hp->htype)));
				/* since this is the first hash table we tried to insert into, since we failed here we can skip the
				   other insert attempts and know that the tables are still consistent */
				free_host((hash_datum *) hp);
				continue;
			} else if (!init){
				u=&unknown;
				while(*u && bcmp((*u)->haddr,hp->haddr,BP_CHADDR_LEN))u=&(*u)->next;
				if(*u && !bcmp((*u)->haddr,hp->haddr,BP_CHADDR_LEN)){
				tmp_unknown=*u;
				*u=(*u)->next;
				free(tmp_unknown);
				}
			}
		} 
			
		/* Register by IP addr if known. */
		if (hp->flags.iaddr) {
			if (hash_Insert(iphashtable, hp->iphashcode, nullcmp, hp, hp) < 0) {
				report(LOG_ERR, "readtab(): hash_Insert(iphashtable) failed for entry \"%s\" with IP address %s", hp->hostname->string, inet_ntoa(hp->iaddr));
				report(LOG_ERR, "readtab(): hash tables are likely to be inconsistent");
			} else {
				/* Just inserted the host struct in a new hash list. */
				hp->linkcount++;
			}
		}

		/* Register by Client identifier if known. */
		if(hp->flags.cliid){
			if(hp->flags.dynamic) /* correct common error */
				report(LOG_ERR,"ignoring dynamic flag in bootptab entry \"%s\" because both an IP address AND a client identifier (or hardware address)  are specified", hp->hostname->string);
			hp->flags.dynamic=FALSE;
			if (hash_Insert(clhashtable, clhashcode, nullcmp, hp, hp) < 0) {
				report(LOG_ERR, "readtab(): hash_Insert(clhashtable) failed for entry \"%s\"...", hp->hostname->string);
				report_cliid(LOG_ERR,hp->cliid);
				report(LOG_ERR, "readtab(): hash tables are likely to be inconsistent");
			} else {
				/* Just inserted the host struct in a new hash list. */
				hp->linkcount++;
			}
		}

			
		/* Register by Name (always known) */
		if (hash_Insert(nmhashtable, nmhashcode, nullcmp,
						hp->hostname->string, hp) < 0) {
			report(LOG_ERR, "readtab(): hash_Insert(nmhashtable) failed on insertion of entry name: \"%s\"", hp->hostname->string);
			report(LOG_ERR, "readtab(): hash tables are likely to be inconsistent");
		} else {
			/* Just inserted the host struct in a new hash list. */
			hp->linkcount++;
		}

		if (hp->flags.iaddr) {
			nhosts++;
		}

	}

	fclose(fp);
	if (debug)
		report(LOG_INFO, "read %d entries (%d hosts) from \"%s\"",
			   nentries, nhosts, bootptab);
	readconf(1);

	/* When init is true we must skip delete_changed(), as some of the data structure(s)
	   needed by delete_changed() haven't yet been initialized.
	   This implies that if we were called at program launch (when init==1), we skip
	   delete_changed().  That's bad, as we'll fail to detect bindings that need to be
	   deleted due to a change in bootptab since we last read bootptab.
	   To work around this, at program launch there's an explicit call to delete_changed()
	   after all the data structs have been init'd.
	*/
	if(!init)
		delete_changed();

	return;
}



	


/*
 * Read an entire host entry from the file pointed to by "fp" and insert it
 * into the memory pointed to by "buffer".  Leading whitespace and comments
 * starting with "#" are ignored (removed).  Backslashes (\) always quote
 * the next character except that newlines preceeded by a backslash cause
 * line-continuation onto the next line.  The entry is terminated by a
 * newline character which is not preceeded by a backslash.  Sequences
 * surrounded by double quotes are taken literally (including newlines, but
 * not backslashes).
 *
 * The "bufsiz" parameter points to an unsigned int which specifies the
 * maximum permitted buffer size.  Upon return, this value will be replaced
 * with the actual length of the entry (not including the null terminator).
 *
 * This code is a little scary. . . .  I don't like using gotos in C
 * either, but I first wrote this as an FSM diagram and gotos seemed like
 * the easiest way to implement it.  Maybe later I'll clean it up.
 */

PRIVATE void
read_entry(fp, buffer, bufsiz)
	FILE *fp;
	char *buffer;
	unsigned *bufsiz;
{
	int c;
	unsigned int length;

	length = 0;

	/*
	 * Eat whitespace, blank lines, and comment lines.
	 */
  top:
	c = fgetc(fp);
	if (c < 0) {
		goto done;				/* Exit if end-of-file */
	}
	if (isspace(c)) {
		goto top;				/* Skip over whitespace */
	}
	if (c == '#') {
		while (TRUE) {			/* Eat comments after # */
			c = fgetc(fp);
			if (c < 0) {
				goto done;		/* Exit if end-of-file */
			}
			if (c == '\n') {
				goto top;		/* Try to read the next line */
			}
		}
	}
	ungetc(c, fp);				/* Other character, push it back to reprocess it */


	/*
	 * Now we're actually reading a data entry.  Get each character and
	 * assemble it into the data buffer, processing special characters like
	 * double quotes (") and backslashes (\).
	 */

  mainloop:
	c = fgetc(fp);
	switch (c) {
	case EOF:
	case '\n':
		goto done;				/* Exit on EOF or newline */
	case '\\':
		c = fgetc(fp);			/* Backslash, read a new character */
		if (c < 0) {
			goto done;			/* Exit on EOF */
		}
		*buffer++ = c;			/* Store the literal character */
		length++;
		if (length < *bufsiz - 1) {
			goto mainloop;
		} else {
			goto done;
		}
	case '"':
		*buffer++ = '"';		/* Store double-quote */
		length++;
		if (length >= *bufsiz - 1) {
			goto done;
		}
		while (TRUE) {			/* Special quote processing loop */
			c = fgetc(fp);
			switch (c) {
			case EOF:
				goto done;		/* Exit on EOF . . . */
			case '"':
				*buffer++ = '"';/* Store matching quote */
				length++;
				if (length < *bufsiz - 1) {
					goto mainloop;	/* And continue main loop */
				} else {
					goto done;
				}
			case '\\':
				if ((c = fgetc(fp)) < 0) {	/* Backslash */
					goto done;	/* EOF. . . .*/
				}				/* else fall through */
			default:
				*buffer++ = c;	/* Other character, store it */
				length++;
				if (length >= *bufsiz - 1) {
					goto done;
				}
			}
		}
	case ':':
		*buffer++ = c;			/* Store colons */
		length++;
		if (length >= *bufsiz - 1) {
			goto done;
		}
		do {					/* But remove whitespace after them */
			c = fgetc(fp);
			if ((c < 0) || (c == '\n')) {
				goto done;
			}
		} while (isspace(c));	/* Skip whitespace */

		if (c == '\\') {		/* Backslash quotes next character */
			c = fgetc(fp);
			if (c < 0) {
				goto done;
			}
			if (c == '\n') {
				goto top;		/* Backslash-newline continuation */
			}
		}
		/* fall through if "other" character */
	default:
		*buffer++ = c;			/* Store other characters */
		length++;
		if (length >= *bufsiz - 1) {
			goto done;
		}
	}
	goto mainloop;				/* Keep going */

  done:
	*buffer = '\0';				/* Terminate string */
	*bufsiz = length;			/* Tell the caller its length */
}



/*
 * Parse out all the various tags and parameters in the host entry pointed
 * to by "src".  Stuff all the data into the appropriate fields of the
 * host structure pointed to by "host".  If there is any problem with the
 * entry, an error message is reported via report(), no further processing
 * is done, and -1 is returned.  Successful calls return 0.
 *
 * (Some errors probably shouldn't be so completely fatal. . . .)
 */

PRIVATE int
process_entry(host, src)
	struct host *host;
	char *src;
{
	int retval;
	char *msg;

	if (!host || *src == '\0') {
		return -1;
	}
	host->hostname = get_shared_string(&src);
	/* Be more liberal for the benefit of dummy tag names. 
	if (!goodname(host->hostname->string)) {
		report(LOG_ERR, "bad hostname: \"%s\"", host->hostname->string);
		del_string(host->hostname);
		return -1;
	}*/
	current_hostname = host->hostname->string;
	adjust(&src);
	while (TRUE) {
		retval = eval_symbol(&src, host);
		if (retval == SUCCESS) {
			adjust(&src);
			continue;
		}
		if (retval == E_END_OF_ENTRY) {
			/* The default subnet mask is set in readtab() */
			return 0;
		}
		/* Some kind of error. */
		switch (retval) {
		case E_SYNTAX_ERROR:
			msg = "bad syntax";
			break;
		case E_UNKNOWN_SYMBOL:
			msg = "unknown symbol";
			break;
		case E_BAD_IPADDR:
			msg = "bad INET address";
			break;
		case E_BAD_HWADDR:
			msg = "bad hardware address";
			break;
		case E_BAD_LONGWORD:
			msg = "bad longword value";
			break;
		case E_BAD_HWATYPE:
			msg = "bad HW address type";
			break;
		case E_BAD_PATHNAME:
			msg = "bad pathname (need leading '/')";
		case E_BAD_VALUE:
			msg = "bad value";
			break;
		case E_ILLEGAL_NULLSTRING:
			msg = "null string not allowed";
			break;
		case E_BAD_COMBINATION:
			msg = "not allowed in combination with one or more of this entry's other symbols";
			break;
		default:
			msg = "unknown error";
			break;
		}						/* switch */
		report(LOG_ERR, "in entry named \"%s\", symbol \"%s\": %s",
			   current_hostname, current_tagname, msg);
		return -1;
	}
}



/*
 * Macros for use in the function below:
 */

/* Parse one INET address stored directly in MEMBER. */
#define PARSE_IA1(MEMBER) do \
{ \
	if (optype == OP_BOOLEAN) \
		return E_SYNTAX_ERROR; \
	hp->flags.MEMBER = FALSE; \
	if (optype == OP_ADDITION) { \
		if (prs_inetaddr(symbol, &value) < 0) \
			return E_BAD_IPADDR; \
		hp->MEMBER.s_addr = value; \
		hp->flags.MEMBER = TRUE; \
	} \
} while (0)

/* Parse a list of INET addresses pointed to by MEMBER */
#define PARSE_IAL(MEMBER) do \
{ \
	if (optype == OP_BOOLEAN) \
		return E_SYNTAX_ERROR; \
	if (hp->flags.MEMBER) { \
		hp->flags.MEMBER = FALSE; \
		assert(hp->MEMBER); \
		del_iplist(hp->MEMBER); \
		hp->MEMBER = NULL; \
	} \
	if (optype == OP_ADDITION) { \
		hp->MEMBER = get_addresses(symbol); \
		if (hp->MEMBER == NULL) \
			return E_SYNTAX_ERROR; \
		hp->flags.MEMBER = TRUE; \
	} \
} while (0)

/* Parse a shared string pointed to by MEMBER */
#define PARSE_STR(MEMBER) do \
{ \
	if (optype == OP_BOOLEAN) \
		return E_SYNTAX_ERROR; \
	if (hp->flags.MEMBER) { \
		hp->flags.MEMBER = FALSE; \
		assert(hp->MEMBER); \
		del_string(hp->MEMBER); \
		hp->MEMBER = NULL; \
	} \
	if (optype == OP_ADDITION) { \
		hp->MEMBER = get_shared_string(symbol); \
		if (hp->MEMBER == NULL) \
			return E_SYNTAX_ERROR; \
		hp->flags.MEMBER = TRUE; \
	} \
} while (0)

/* Parse an unsigned integer value for MEMBER */
#define PARSE_UINT(MEMBER) do \
{ \
	if (optype == OP_BOOLEAN) \
		return E_SYNTAX_ERROR; \
	hp->flags.MEMBER = FALSE; \
	if (optype == OP_ADDITION) { \
		value = get_u_long(symbol); \
		hp->MEMBER = value; \
		hp->flags.MEMBER = TRUE; \
	} \
} while (0)

/*
 * Evaluate the two-character tag symbol pointed to by "symbol" and place
 * the data in the structure pointed to by "hp".  The pointer pointed to
 * by "symbol" is updated to point past the source string (but may not
 * point to the next tag entry).
 *
 * Obviously, this need a few more comments. . . .
 */
PRIVATE int
eval_symbol(symbol, hp)
	char **symbol;
	struct host *hp;
{
	char tmpstr[MAXSTRINGLEN];
	byte *tmphaddr;
	struct symbolmap *symbolptr;
	u_int32 value;
	int32 timeoff;
	int i, numsymbols;
	unsigned len;
	int optype;					/* Indicates boolean, addition, or deletion */

	eat_whitespace(symbol);

	/* Make sure this is set before returning. */
	current_tagname[0] = (*symbol)[0];
	current_tagname[1] = (*symbol)[1];
	current_tagname[2] = 0;

	if ((*symbol)[0] == '\0') {
		return E_END_OF_ENTRY;
	}
	if ((*symbol)[0] == ':') {
		return SUCCESS;
	}
	if ((*symbol)[0] == 'T') {	/* generic symbol */
		(*symbol)++;
		value = get_u_long(symbol);
		/* no buffer overflow possible here, current_tagname[] was declared wide enough to hold largest possible get_u_long() return value */
		sprintf(current_tagname, "T%u", value);
		eat_whitespace(symbol);
		if ((*symbol)[0] != '=') {
			return E_SYNTAX_ERROR;
		}
		if (value==0 || value==255)
			/* we do not allow you to mention these tags as they don't follow the usual code/len/data encoding */
			return E_SYNTAX_ERROR;
		(*symbol)++;
		if (!(hp->generic)) {
			hp->generic = (struct shared_bindata *)
				smalloc(sizeof(struct shared_bindata));
		}
		if (process_generic(symbol, &(hp->generic), (byte) (value & 0xFF)))
			return E_SYNTAX_ERROR;
		hp->flags.generic = TRUE;
		return SUCCESS;
	}
	/*
	 * Determine the type of operation to be done on this symbol
	 */
	switch ((*symbol)[2]) {
	case '=':
		optype = OP_ADDITION;
		break;
	case '@':
		optype = OP_DELETION;
		break;
	case ':':
	case '\0':
		optype = OP_BOOLEAN;
		break;
	default:
		return E_SYNTAX_ERROR;
	}

	symbolptr = symbol_list;
	numsymbols = sizeof(symbol_list) / sizeof(struct symbolmap);
	for (i = 0; i < numsymbols; i++) {
		if (((symbolptr->symbol)[0] == (*symbol)[0]) &&
			((symbolptr->symbol)[1] == (*symbol)[1])) {
			break;
		}
		symbolptr++;
	}
	if (i >= numsymbols) {
		return E_UNKNOWN_SYMBOL;
	}
	/*
	 * Skip past the = or @ character (to point to the data) if this
	 * isn't a boolean operation.  For boolean operations, just skip
	 * over the two-character tag symbol (and nothing else. . . .).
	 */
	(*symbol) += (optype == OP_BOOLEAN) ? 2 : 3;

	eat_whitespace(symbol);

	/* The cases below are in order by symbolcode value. */
	switch (symbolptr->symbolcode) {

	case SYM_BOOTFILE:
		PARSE_STR(bootfile);
		break;

	case SYM_COOKIE_SERVER:
		PARSE_IAL(cookie_server);
		break;

	case SYM_DOMAIN_SERVER:
		PARSE_IAL(domain_server);
		break;

	case SYM_GATEWAY:
		PARSE_IAL(gateway);
		break;

	case SYM_CLIID:
		if (optype == OP_BOOLEAN) return E_SYNTAX_ERROR;
		if (optype ==  OP_ADDITION){
			process_bindata(symbol,&hp->cliid);
			hp->flags.cliid=TRUE;
			break;
		}
				
	case SYM_HWADDR:
		if (optype == OP_BOOLEAN)
			return E_SYNTAX_ERROR;
		hp->flags.haddr = FALSE;
		if (optype == OP_ADDITION) {
			/* Default the HW type to Ethernet */
			if (hp->flags.htype == 0) {
				hp->flags.htype = TRUE;
				hp->htype = HTYPE_ETHERNET;
			}
			tmphaddr = prs_haddr(symbol, hp->htype);
			if (!tmphaddr)
				return E_BAD_HWADDR;
			bcopy(tmphaddr, hp->haddr, haddrlength(hp->htype));
			hp->flags.haddr = TRUE;
		}
		break;

	case SYM_HOMEDIR:
		PARSE_STR(homedir);
		break;

	case SYM_HTYPE:
		if (optype == OP_BOOLEAN)
			return E_SYNTAX_ERROR;
		hp->flags.htype = FALSE;
		if (optype == OP_ADDITION) {
			value = 0L;			/* Assume an illegal value */
			eat_whitespace(symbol);
			if (isdigit(**symbol)) {
				value = get_u_long(symbol);
			} else {
				len = sizeof(tmpstr);
				(void) get_string(symbol, tmpstr, &len);
				makelower(tmpstr);
				numsymbols = sizeof(htnamemap) /
					sizeof(struct htypename);
				for (i = 0; i < numsymbols; i++) {
					if (!strcmp(htnamemap[i].name, tmpstr)) {
						break;
					}
				}
				if (i < numsymbols) {
					value = htnamemap[i].htype;
				}
			}
			if (value >= hwinfocnt) {
				return E_BAD_HWATYPE;
			}
			hp->htype = (byte) (value & 0xFF);
			hp->flags.htype = TRUE;
		}
		break;

	case SYM_IMPRESS_SERVER:
		PARSE_IAL(impress_server);
		break;

	case SYM_IPADDR:
		PARSE_IA1(iaddr);
		break;

	case SYM_LOG_SERVER:
		PARSE_IAL(log_server);
		break;

	case SYM_LPR_SERVER:
		PARSE_IAL(lpr_server);
		break;

	case SYM_NAME_SERVER:
		PARSE_IAL(name_server);
		break;

	case SYM_RLP_SERVER:
		PARSE_IAL(rlp_server);
		break;

	case SYM_SUBNET_MASK:
		PARSE_IA1(subnet_mask);
		break;

	case SYM_TIME_OFFSET:
		switch (optype) {
		case OP_ADDITION:
			len = sizeof(tmpstr);
			(void) get_string(symbol, tmpstr, &len);
			if (!strncmp(tmpstr, "auto", 4)) {
				hp->time_offset = secondswest;
			} else {
				if (sscanf(tmpstr, "%d", (int*)&timeoff) != 1)
					return E_BAD_LONGWORD;
				hp->time_offset = timeoff;
			}
			hp->flags.time_offset = TRUE;
			break;
		case OP_DELETION:
			hp->flags.time_offset = FALSE;
			break;
		case OP_BOOLEAN:
			hp->flags.time_offset = TRUE;
			hp->time_offset = secondswest;
			break;
		}
		break;

	case SYM_TIME_SERVER:
		PARSE_IAL(time_server);
		break;

	case SYM_VENDOR_MAGIC:
		if (optype == OP_BOOLEAN)
			return E_SYNTAX_ERROR;
		hp->flags.vm_cookie = FALSE;
		if (optype == OP_ADDITION) {
			if (strncmp(*symbol, "auto", 4)) {
				/* The string is not "auto" */
				if (!strncmp(*symbol, "rfc", 3)) {
					bcopy(vm_rfc1048, hp->vm_cookie, 4);
				} else if (!strncmp(*symbol, "cmu", 3)) {
					bcopy(vm_cmu, hp->vm_cookie, 4);
				} else {
					if (!isdigit(**symbol))
						return E_BAD_IPADDR;
					if (prs_inetaddr(symbol, &value) < 0)
						return E_BAD_IPADDR;
					bcopy(&value, hp->vm_cookie, 4);
				}
				hp->flags.vm_cookie = TRUE;
			}
		}
		break;


	case SYM_SIMILAR_ENTRY:
		switch (optype) {
		case OP_ADDITION:
			fill_defaults(hp, symbol);
			break;
		default:
			return E_SYNTAX_ERROR;
		}
		break;

	case SYM_DYNAMIC:
		switch (optype) {
		case OP_ADDITION:
			return E_SYNTAX_ERROR;
		case OP_DELETION:
			hp->flags.dynamic = FALSE;
			break;
		case OP_BOOLEAN:
			hp->flags.dynamic = TRUE;
			break;
		}
		break;

	case SYM_NOT_RQ:
		switch (optype) {
		case OP_ADDITION:
			return E_SYNTAX_ERROR;
		case OP_DELETION:
			hp->flags.not_rq = FALSE;
			break;
		case OP_BOOLEAN:
			hp->flags.not_rq = TRUE;
			break;
		}
		break;

	case SYM_ROAMING:
		switch (optype) {
		case OP_ADDITION:
			return E_SYNTAX_ERROR;
		case OP_DELETION:
			hp->flags.roaming = FALSE;
			break;
		case OP_BOOLEAN:
			hp->flags.roaming = TRUE;
			break;
		}
		break;

	case SYM_NAME_SWITCH:
		switch (optype) {
		case OP_ADDITION:
			return E_SYNTAX_ERROR;
		case OP_DELETION:
			hp->flags.send_name = FALSE;
			hp->flags.name_switch = FALSE;
			break;
		case OP_BOOLEAN:
			hp->flags.send_name = TRUE;
			hp->flags.name_switch = TRUE;
			break;
		}
		break;

	case SYM_NODE_TYPE:
		switch (optype){
		case OP_ADDITION:

			if(isdigit(**symbol)){
				PARSE_UINT(node_type);
				break;
			}
			switch(**symbol){
			case 'b':
			case 'B': hp->node_type = 1;
					  break;
			case 'p':
			case 'P': hp->node_type = 2;
					  break;
			case 'm':
			case 'M': hp->node_type = 4;
					  break;
			case 'h':
			case 'H': hp->node_type = 8;
					  break;
			default:
				return E_SYNTAX_ERROR;
			}
		case OP_BOOLEAN:
			hp->flags.node_type=TRUE;
			break;
		case OP_DELETION:
			hp->flags.node_type=FALSE;
		}
		break;
			
	case SYM_BOOTSIZE:
		switch (optype) {
		case OP_ADDITION:
			if (!strncmp(*symbol, "auto", 4)) {
				hp->flags.bootsize = TRUE;
				hp->flags.bootsize_auto = TRUE;
			} else {
				hp->bootsize = (unsigned int) get_u_long(symbol);
				hp->flags.bootsize = TRUE;
				hp->flags.bootsize_auto = FALSE;
			}
			break;
		case OP_DELETION:
			hp->flags.bootsize = FALSE;
			break;
		case OP_BOOLEAN:
			hp->flags.bootsize = TRUE;
			hp->flags.bootsize_auto = TRUE;
			break;
		}
		break;

	case SYM_BOOT_SERVER:
		PARSE_IA1(bootserver);
		break;

	case SYM_TFTPDIR:
		PARSE_STR(tftpdir);
		if ((hp->tftpdir != NULL) &&
			(hp->tftpdir->string[0] != '/'))
			return E_BAD_PATHNAME;
		break;

	case SYM_DUMP_FILE:
		PARSE_STR(dump_file);
		break;

	case SYM_NB_SCOPE:
		PARSE_STR(nb_scope);
		if ((hp->flags.nb_scope) && !strlen(hp->nb_scope->string)) { /* if specified, it must be non-null */
			del_string(hp->nb_scope);
			hp->flags.nb_scope=FALSE;
			return E_ILLEGAL_NULLSTRING;
		}
		break;

	case SYM_DOMAIN_NAME:
		PARSE_STR(domain_name);
		if ((hp->flags.domain_name) && !strlen(hp->domain_name->string)) { /* if specified, it must be non-null */
			del_string(hp->domain_name);
			hp->flags.domain_name=FALSE;
			return E_ILLEGAL_NULLSTRING;
		}
		break;

	case SYM_SWAP_SERVER:
		PARSE_IA1(swap_server);
		break;

	case SYM_ROOT_PATH:
		PARSE_STR(root_path);
		if ((hp->flags.root_path) && !strlen(hp->root_path->string)) { /* if specified, it must be non-null */
			del_string(hp->root_path);
			hp->flags.root_path=FALSE;
			return E_ILLEGAL_NULLSTRING;
		}
		break;

	case SYM_EXTEN_FILE:
		PARSE_STR(exten_file);
		if ((hp->flags.exten_file) && !strlen(hp->exten_file->string)) { /* if specified, it must be non-null */
			del_string(hp->exten_file);
			hp->flags.exten_file=FALSE;
			return E_ILLEGAL_NULLSTRING;
		}
		break;


	case SYM_REPLY_ADDR:
		PARSE_IA1(reply_addr);
		break;

	case SYM_NIS_DOMAIN:
		PARSE_STR(nis_domain);
		if ((hp->flags.nis_domain) && !strlen(hp->nis_domain->string)) { /* if specified, it must be non-null */
			del_string(hp->nis_domain);
			hp->flags.nis_domain=FALSE;
			return E_ILLEGAL_NULLSTRING;
		}
		break;

	case SYM_NIS_SERVER:
		PARSE_IAL(nis_server);
		break;

	case SYM_NBNS_SERVER:
		PARSE_IAL(nbns_server);
		break;

	case SYM_NTP_SERVER:
		PARSE_IAL(ntp_server);
		break;

#ifdef	YORK_EX_OPTION
	case SYM_EXEC_FILE:
		PARSE_STR(exec_file);
		break;
#endif

	case SYM_LEASE:
		PARSE_UINT(lease);
		break;
	case SYM_VENDOR_SIZE:	/* CMU bootptab compatibility */
	case SYM_MSG_SIZE:
		PARSE_UINT(msg_size);
		if (hp->msg_size < BP_MINPKTSZ ||
			hp->msg_size > MAX_MSG_SIZE)
			return E_BAD_VALUE;
		break;

	case SYM_RENEW:
		PARSE_UINT(rebind);
		break;
	case SYM_REBIND:
		PARSE_UINT(renew);
		break;
	case SYM_MIN_WAIT:
		PARSE_UINT(min_wait);
		break;
	case SYM_MAX_LEASE:
		PARSE_UINT(max_lease);
		break;
	case SYM_BROADCAST_ADDR:
		PARSE_IA1(broadcast_addr);
		break;
	case SYM_DENY_CLIENT:
		switch (optype) {
		case OP_ADDITION:
			return E_SYNTAX_ERROR;
		case OP_DELETION:
			hp->flags.deny_client = FALSE;
			break;
		case OP_BOOLEAN:
			hp->flags.deny_client = TRUE;
			break;
		}
		break;
	case SYM_DENY_CLIENT_DYNAMIC:
		switch (optype) {
		case OP_ADDITION:
			return E_SYNTAX_ERROR;
		case OP_DELETION:
			hp->flags.deny_client_dynamic = FALSE;
			break;
		case OP_BOOLEAN:
			hp->flags.deny_client_dynamic = TRUE;
			break;
		}
		break;

	case SYM_PERFORM_ROUTER_DISCOVERY:
		PARSE_UINT(perform_router_discovery);
		break;

	case SYM_STATIC_ROUTE:
		PARSE_IAL(static_route);
		if ((hp->flags.static_route) && (hp->static_route->addrcount % 2 != 0)) { /* must have even number of addrs */
			del_iplist(hp->static_route);
			hp->flags.static_route=FALSE;
			return E_BAD_VALUE;
		}
		break;

	case SYM_CLIENT_FQDN:
		PARSE_UINT(client_fqdn);
			if ((hp->flags.client_fqdn) /* result of the symbol is an entry with this tag specified */
				&& ( (hp->client_fqdn)  /* the tag value is non-zero (zero is legal) */
						&& 
					(hp->client_fqdn & ~CF_ALL_FLAGS) /* the tag value contains bits that aren't legal */
				   )
				) {	
			hp->flags.client_fqdn=FALSE;
			hp->client_fqdn=0;
			return E_BAD_VALUE;
		}
		break;

	case SYM_ETHERNET_ENCAPSULATION:
		PARSE_UINT(ethernet_encapsulation);
		break;

	case SYM_NBDD_SERVER:
		PARSE_IAL(nbdd_server);
		break;

	case SYM_SMTP_SERVER:
		PARSE_IAL(smtp_server);
		break;

	case SYM_POP3_SERVER:
		PARSE_IAL(pop3_server);
		break;

	case SYM_NNTP_SERVER:
		PARSE_IAL(nntp_server);
		break;

	case SYM_WWW_SERVER:
		PARSE_IAL(www_server);
		break;

	case SYM_FINGER_SERVER:
		PARSE_IAL(finger_server);
		break;

	case SYM_IRC_SERVER:
		PARSE_IAL(irc_server);
		break;

	case SYM_BOOTP_EXCLUDE:
		PARSE_STR(bootp_exclude);
		/* The 'be' bootptab tag provides us with a string, which we stored in hp->bootp_exclude.  Unfortunately,
		   that representation isn't helpful to us; we'll need (later) to be able to see for any arbitrary
		   bootp option code (0...255) whether it was mentioned in that string.  
		   So for OP_ADDITION, we also parse the string to create a bit vector representation in hp->bootp_exclude_vector.  
		   (We don't use the string form again, except for dumping bootptab or bindings to disk.)
		   And for OP_DELETION, we must remember also to destroy the bit vector representation (if present).
		*/
		if ((optype == OP_ADDITION) && (hp->flags.bootp_exclude)) {
			struct shared_bindata *bootp_options_vector;
			char errmsg_prefix[MAX_FQDN_LEN+40];  /* for error msgs produced by make_bootp_options_vector() */

			if (hp->flags.bootp_include) {
				/* you can't specify bootp_exclude if bootp_include also applies to this entry */
				del_string(hp->bootp_exclude);
				hp->bootp_exclude = NULL;
				hp->flags.bootp_exclude = 0;
				return E_BAD_COMBINATION;
			}

			sprintf(errmsg_prefix, "in entry name \"%s\", symbol \"be\": ", current_hostname);

			if (make_bootp_options_vector(hp->bootp_exclude->string, &bootp_options_vector, errmsg_prefix) < 0) {
				/* error */
				/* We are guaranteed bootp_options_vector is not allocated, so there's nothing to cleanup there. */
				/* discard the string version */
				del_string(hp->bootp_exclude);
				hp->bootp_exclude = NULL;
				hp->flags.bootp_exclude = 0;
			} else {
				/* success */
				hp->bootp_exclude_vector = bootp_options_vector;
				hp->flags.bootp_exclude_vector = 1;
				/* Remove from the vector those options we refuse to let you exclude.
				   Unfortunately, they remain in the string version, potentially confusing if you view the string version
				   via a bootptab or bindings dump to disk.
				*/
				remove_non_bootp_excludable_options((Bitvector) &hp->bootp_exclude_vector->data, errmsg_prefix);
			}

		} else if (optype == OP_DELETION) {
			/* Delete the parallel hp->bootp_exclude_vector as well. */
			if (hp->flags.bootp_exclude_vector) {
				hp->flags.bootp_exclude_vector = FALSE;
				assert(hp->bootp_exclude_vector);
				del_bindata(hp->bootp_exclude_vector);
				hp->bootp_exclude_vector=NULL;
			}
		}
		break;

	case SYM_IP_FORWARDING:
		PARSE_UINT(ip_forwarding);
		break;

	case SYM_IP_SOURCE_ROUTING:
		PARSE_UINT(ip_source_routing);
		break;

	case SYM_IP_TTL:
		PARSE_UINT(ip_ttl);
		break;

	case SYM_ALL_SUBNETS_ARE_LOCAL:
		PARSE_UINT(all_subnets_are_local);
		break;

	case SYM_PERFORM_MASK_DISCOVERY:
		PARSE_UINT(perform_mask_discovery);
		break;

	case SYM_MASK_SUPPLIER:
		PARSE_UINT(mask_supplier);
		break;

	case SYM_ROUTER_SOLICITATION_ADDR:
		PARSE_IA1(router_solicitation_addr);
		break;

	case SYM_TRAILER_ENCAPSULATION:
		PARSE_UINT(trailer_encapsulation);
		break;

	case SYM_TCP_TTL:
		PARSE_UINT(tcp_ttl);
		break;

	case SYM_TCP_KEEPALIVE_GARBAGE:
		PARSE_UINT(tcp_keepalive_garbage);
		break;

	case SYM_XWINDOWS_FONT_SERVER:
		PARSE_IAL(xwindows_font_server);
		break;

	case SYM_XWINDOWS_DISPLAY_MANAGER:
		PARSE_IAL(xwindows_display_manager);
		break;

	case SYM_NISPLUS_DOMAIN:
		PARSE_STR(nisplus_domain);
		if ((hp->flags.nisplus_domain) && !strlen(hp->nisplus_domain->string)) { /* if specified, it must be non-null */
			del_string(hp->nisplus_domain);
			hp->flags.nisplus_domain=FALSE;
			return E_ILLEGAL_NULLSTRING;
		}
		break;

	case SYM_NISPLUS_SERVER:
		PARSE_IAL(nisplus_server);
		break;

	case SYM_BOOTP_INCLUDE:
		PARSE_STR(bootp_include);
		/* The 'bi' bootptab tag provides us with a string, which we stored in hp->bootp_include.  Unfortunately,
		   that representation isn't helpful to us; we'll need (later) to be able to see for any arbitrary
		   bootp option code (0...255) whether it was mentioned in that string.  
		   So for OP_ADDITION, we also parse the string to create a bit vector representation in hp->bootp_include_vector.  
		   (We don't use the string form again, except for dumping bootptab or bindings to disk.)
		   And for OP_DELETION, we must remember also to destroy the bit vector representation (if present).
		*/
		if ((optype == OP_ADDITION) && (hp->flags.bootp_include)) {
			struct shared_bindata *bootp_options_vector;
			char errmsg_prefix[MAX_FQDN_LEN+40];  /* for error msgs produced by make_bootp_options_vector() */

			if (hp->flags.bootp_exclude) {
				/* you can't specify bootp_include if bootp_exclude also applies to this entry */
				del_string(hp->bootp_include);
				hp->bootp_include = NULL;
				hp->flags.bootp_include = 0;
				return E_BAD_COMBINATION;
			}

			sprintf(errmsg_prefix, "in entry name \"%s\", symbol \"bi\": ", current_hostname);

			if (make_bootp_options_vector(hp->bootp_include->string, &bootp_options_vector, errmsg_prefix) < 0) {
				/* error */
				/* We are guaranteed bootp_options_vector is not allocated, so there's nothing to cleanup there. */
				/* discard the string version */
				del_string(hp->bootp_include);
				hp->bootp_include = NULL;
				hp->flags.bootp_include = 0;
			} else {
				/* success */
				hp->bootp_include_vector = bootp_options_vector;
				hp->flags.bootp_include_vector = 1;
			}

		} else if (optype == OP_DELETION) {
			/* Delete the parallel hp->bootp_include_vector as well. */
			if (hp->flags.bootp_include_vector) {
				hp->flags.bootp_include_vector = FALSE;
				assert(hp->bootp_include_vector);
				del_bindata(hp->bootp_include_vector);
				hp->bootp_include_vector=NULL;
			}
		}
		break;

		/* XXX - Add new tags here */

	default:
		return E_UNKNOWN_SYMBOL;

	}							/* switch symbolcode */

	return SUCCESS;
}
#undef	PARSE_IA1
#undef	PARSE_IAL
#undef	PARSE_STR



/* Given a string of space-delimited bootp/dhcp option tagnames (e.g. "ws nt T144 lp") in bootp_options_str,
   construct a bit vector representation in a struct shared_bindata, returning a ptr to it in bootp_options_vector.
   It is the caller's responsibility to see that the structure we allocate is freed when it comes time to do so.
   If the caller provides an errmsg_prefix string (even a null string), we prefix it to any error messages we produce;
     if the caller does not provide an errmsg_prefix string at all (NULL), we do not emit any error messages.
   Return 0 on success, <0 if anything goes fatally wrong.
   For fatal errors, we guarantee that any bootp_options_vector structure we created is freed.
*/

PRIVATE int
make_bootp_options_vector(char *bootp_options_str, struct shared_bindata **bootp_options_vector, char *errmsg_prefix)
{
	char *walk_tags_string;
	int retval;
	char *msg;
	size_t bitvector_len;
	
	if (bootp_options_str == NULL)
		return -1;

	/* Using colons (instead of spaces) in the string is such a common error, and would lead to such a bewildering
	   error message if allowed past here, look for it specially and issue a more informative error message.
	*/
	if (strchr(bootp_options_str, ':')) {
		if (errmsg_prefix != NULL)  /* NULL means caller does not want us to produce error messages at all */
			report(LOG_ERR, "%s value=\"%s\": colons are not permitted in this value (use spaces to separate elements instead), ignoring entire value", errmsg_prefix, bootp_options_str);
		return -2;
	}

	/* allocate and init the structure */
	bitvector_len = size_of_bitvector_for_n_bits(BOOTP_NUM_TAGS);
	*bootp_options_vector = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata) + bitvector_len);
	(*bootp_options_vector)->length = bitvector_len;
	(*bootp_options_vector)->linkcount = 1;
	/* not needed, smalloc() zeroed it
	  bzero(&((*bootp_options_vector)->data), bitvector_len);
	*/

	walk_tags_string = bootp_options_str;
	while (TRUE) {
		char tagname[READ_OPTION_TAGNAME_BUFSIZE];

		retval = read_option_tagname(&walk_tags_string, tagname);
		switch (retval) {
			case E_READ_OPTION_TAGNAME_END_OF_STRING:
				return 0; /* normal return */

			case E_READ_OPTION_TAGNAME_GENERIC_TAG_OUT_OF_RANGE:
				msg = "generic tag value is outside the valid range for BootP and DHCP options";
				break;

			case E_READ_OPTION_TAGNAME_UNKNOWN_SYMBOL:
				msg = "unknown symbol";
				break;

			case E_READ_OPTION_TAGNAME_NOT_OPTION_FIELD:
				msg = "this symbol name is known, but is not one that would be returned in a reply packet's \"options\" field";
				break;
				
			case E_READ_OPTION_TAGNAME_SYMBOL_CASE_MISSING:
				/* this should never happen.  It indicates a programming error. */
				msg = "this symbol name is known, but we have no code to handle it (internal error)";
				break;

			default:
				/* 'retval' is an int 0...255 indicating the bootp option code you wish to exclude from BOOTREPLY */
				/* Finally record the option code in the bit vector */
				set_bitvector((Bitvector) &((*bootp_options_vector)->data), retval);
				continue; /* proceed to read next tag */
		}

		/* There was some problem with the tag we just read.  msg has been set. */

		if (errmsg_prefix != NULL) { /* NULL means caller does not want us to produce error messages at all */
			report(LOG_ERR, "%signoring element \"%s\": %s", errmsg_prefix, tagname, msg);
		}
	}
	/* NOTREACHED */
	return 0;
}




/* Given a src string (e.g. "ws T144 lp") , skip over leading whitespace and read the next tag name.
   The tag name may use any of the defined names, or the generic Txxx syntax.
   Advance the caller's src ptr to point to the first unread char. 
   The (possibly invalid) tagname string is written to 'tagname', a buffer of at least READ_OPTION_TAGNAME_BUFSIZE bytes provided by the caller.
   If the tag name corresponds to a true BootP/DHCP option code, return the option code
      (which is guaranteed to be in the range 0...BOOTP_NUM_TAGS-1).
   Otherwise return an exception.
   Exceptions are returned as negative values.
   Yes, we've stolen much of this from eval_symbol().
*/
PRIVATE
int
read_option_tagname(char **src, char *tagname)
{
	u_int32 value;
	struct symbolmap *symbolptr;
	int i, numsymbols;

	eat_whitespace(src);

	tagname[0] = (*src)[0];

	if (tagname[0] == '\0') {
		(*src)++;
		return E_READ_OPTION_TAGNAME_END_OF_STRING;
	}

	tagname[1] = (*src)[1];
	tagname[2] = 0;

	if ((*src)[0] == 'T') {  /* generic tag */
		(*src)++;
		value = get_u_long(src); /* also advances src as needed */
		/* no buffer overflow possible here as long as, long as tagname[] was declared at least READ_OPTION_TAGNAME_BUFSIZE long */
		sprintf(tagname, "T%u", value);
		if ((value < 0) || (value >= BOOTP_NUM_TAGS))
			return E_READ_OPTION_TAGNAME_GENERIC_TAG_OUT_OF_RANGE;
		return (int) value;
	}

	/* look up two-char tagname in the symbol_list{} */
	symbolptr = symbol_list;
	numsymbols = sizeof(symbol_list) / sizeof(struct symbolmap);
	for (i = 0; i < numsymbols; i++) {
		if (((symbolptr->symbol)[0] == (*src)[0]) &&
			((symbolptr->symbol)[1] == (*src)[1])) {
				break;
		}
		symbolptr++;
	}
	if (i >= numsymbols) {
		/* There are a few possibilities here:
		   1) src began with two non-NUL chars (which we failed to match to a known symbol).
			  They must be followed either by a NUL or at least one non-NUL char.
			  We can advance the ptr past these two chars with no danger of walking off the string.
		   2) src began with one char followed by a NUL.
		      We must only advance the ptr by one char.  The next call to us will handle end of string.
		*/
		(*src)++;
		if ((*src)[0] != '\0')
			(*src)++;
		return E_READ_OPTION_TAGNAME_UNKNOWN_SYMBOL;
	}

	(*src) += 2;

	switch (symbolptr->symbolcode) {

		/* First we handle tags associated with valid bootp/dhcp options. */

		case SYM_COOKIE_SERVER:
			return TAG_COOKIE_SERVER;
			break;
		case SYM_DOMAIN_SERVER:
			return TAG_DOMAIN_SERVER;
			break;
		case SYM_GATEWAY:
			return TAG_GATEWAY;
			break;
		case SYM_NAME_SWITCH:
			return TAG_HOST_NAME;
			break;
		case SYM_IMPRESS_SERVER:
			return TAG_IMPRESS_SERVER;
			break;
		case SYM_LOG_SERVER:
			return TAG_LOG_SERVER;
			break;
		case SYM_LPR_SERVER:
			return TAG_LPR_SERVER;
			break;
		case SYM_NAME_SERVER:
			return TAG_NAME_SERVER;
			break;
		case SYM_RLP_SERVER:
			return TAG_RLP_SERVER;
			break;
		case SYM_SUBNET_MASK:
			return TAG_SUBNET_MASK;
			break;
		case SYM_TIME_OFFSET:
			return TAG_TIME_OFFSET;
			break;
		case SYM_TIME_SERVER:
			return TAG_TIME_SERVER;
			break;
		case SYM_BOOTSIZE:
			return TAG_BOOT_SIZE;
			break;
		case SYM_DUMP_FILE:
			return TAG_DUMP_FILE;
			break;
		case SYM_DOMAIN_NAME:
			return TAG_DOMAIN_NAME;
			break;
		case SYM_SWAP_SERVER:
			return TAG_SWAP_SERVER;
			break;
		case SYM_ROOT_PATH:
			return TAG_ROOT_PATH;
			break;
		case SYM_EXTEN_FILE:
			return TAG_EXTEN_FILE;
			break;
		case SYM_NIS_DOMAIN:
			return TAG_NIS_DOMAIN;
			break;
		case SYM_NIS_SERVER:
			return TAG_NIS_SERVER;
			break;
		case SYM_NTP_SERVER:
			return TAG_NTP_SERVER;
			break;
		case SYM_NBNS_SERVER:
			return TAG_NBNS_SERVER;
			break;
		case SYM_NODE_TYPE:
			return TAG_NODE_TYPE;
			break;
		case SYM_LEASE:
			return TAG_DHCP_IPLEASE; /* don't allow exclude */
			break;
		case SYM_NB_SCOPE:
			return TAG_NB_SCOPE;
			break;
		case SYM_RENEW:   /* don't allow exclude */
			return TAG_DHCP_RENEW;
			break;
		case SYM_REBIND:  /* don't allow eclude */
			return TAG_DHCP_REBIND;
			break;
		case SYM_BROADCAST_ADDR:
			return TAG_BROADCAST_ADDR;
			break;
		case SYM_PERFORM_ROUTER_DISCOVERY:
			return TAG_PERFORM_ROUTER_DISCOVERY;
			break;
		case SYM_STATIC_ROUTE:
			return TAG_STATIC_ROUTE;
			break;
		case SYM_ETHERNET_ENCAPSULATION:
			return TAG_ETHERNET_ENCAPSULATION;
			break;
		case SYM_NBDD_SERVER:
			return TAG_NBDD_SERVER;
			break;
		case SYM_SMTP_SERVER:
			return TAG_SMTP_SERVER;
			break;
		case SYM_POP3_SERVER:
			return TAG_POP3_SERVER;
			break;
		case SYM_NNTP_SERVER:
			return TAG_NNTP_SERVER;
			break;
		case SYM_WWW_SERVER:
			return TAG_WWW_SERVER;
			break;
		case SYM_FINGER_SERVER:
			return TAG_FINGER_SERVER;
			break;
		case SYM_IRC_SERVER:
			return TAG_IRC_SERVER;
			break;
		case SYM_IP_FORWARDING:
			return TAG_IP_FORWARDING;
			break;
		case SYM_IP_SOURCE_ROUTING:
			return TAG_IP_SOURCE_ROUTING;
			break;
		case SYM_IP_TTL:
			return TAG_IP_TTL;
			break;
		case SYM_ALL_SUBNETS_ARE_LOCAL:
			return TAG_ALL_SUBNETS_ARE_LOCAL;
			break;
		case SYM_PERFORM_MASK_DISCOVERY:
			return TAG_PERFORM_MASK_DISCOVERY;
			break;
		case SYM_MASK_SUPPLIER:
			return TAG_MASK_SUPPLIER;
			break;
		case SYM_ROUTER_SOLICITATION_ADDR:
			return TAG_ROUTER_SOLICITATION_ADDR;
			break;
		case SYM_TRAILER_ENCAPSULATION:
			return TAG_TRAILER_ENCAPSULATION;
			break;
		case SYM_TCP_TTL:
			return TAG_TCP_TTL;
			break;
		case SYM_TCP_KEEPALIVE_GARBAGE:
			return TAG_TCP_KEEPALIVE_GARBAGE;
			break;
		case SYM_XWINDOWS_FONT_SERVER:
			return TAG_XWINDOWS_FONT_SERVER;
			break;
		case SYM_XWINDOWS_DISPLAY_MANAGER:
			return TAG_XWINDOWS_DISPLAY_MANAGER;
			break;
		case SYM_NISPLUS_DOMAIN:
			return TAG_NISPLUS_DOMAIN;
			break;
		case SYM_NISPLUS_SERVER:
			return TAG_NISPLUS_SERVER;
			break;
		/* add new tags for valid bootp/dhcp options here */


		/* Next we handle tags associated with things that are not bootp/dhcp options.
		   This includes tags for fields that are returned elsewhere in the packet (not in the options field).
		   This includes tags that are directives to the server.
		*/
		case SYM_NULL:
		case SYM_BOOTFILE:
		case SYM_HWADDR:
		case SYM_HOMEDIR:
		case SYM_HTYPE:
		case SYM_IPADDR:
		case SYM_VENDOR_MAGIC:
		case SYM_SIMILAR_ENTRY:
		case SYM_BOOT_SERVER:
		case SYM_TFTPDIR:
		case SYM_REPLY_ADDR:
		case SYM_EXEC_FILE:
		case SYM_MSG_SIZE:
		case SYM_MIN_WAIT:
		case SYM_VENDOR_SIZE:
		case SYM_DYNAMIC:
		case SYM_ROAMING:
		case SYM_NOT_RQ:
		case SYM_CLIID:
		case SYM_MAX_LEASE:
		case SYM_DENY_CLIENT:
		case SYM_CLIENT_FQDN:
		case SYM_DENY_CLIENT_DYNAMIC:
		case SYM_BOOTP_EXCLUDE:
		case SYM_BOOTP_INCLUDE:
		/* add new tags that are not associated with valid bootp/dhcp options here */
			return E_READ_OPTION_TAGNAME_NOT_OPTION_FIELD;
			break;

		default:
			;
	}
	/* Reaching here means we DID find the tag name in symbol_list{}, but we neglected to write a case (above)
	   for this tag's symbol value in the 'switch' statement.  This is a programming error; we should supply
	   a case for every tag name that's in symbol_list{}.
	*/
	return E_READ_OPTION_TAGNAME_SYMBOL_CASE_MISSING;
}



/* Given a bitvector representing a set of bootp option codes the user wants excluded from a BOOTPREPLY,
   clear the ones we refuse to exclude.
   If the caller provides an errmsg_prefix string (even a null string), we prefix it to any error messages we produce;
     if the caller does not provide an errmsg_prefix string at all (NULL), we do not emit any error messages.
*/
PRIVATE void
remove_non_bootp_excludable_options(Bitvector vector, char *errmsg_prefix)
{
		/* List of option codes we refuse to let you exclude from a BOOTPREPLY via the bootp_exclude ('be') mechanism.
		   You may add new ones here.
		*/
		static int options_tags_not_excludable_from_bootpreply[] = { 
			TAG_END,
			TAG_PAD,
			TAG_SUBNET_MASK,
			TAG_GATEWAY,
			TAG_EXTEN_FILE
		};
		int num_options_tags_not_excludable_from_bootpreply = sizeof(options_tags_not_excludable_from_bootpreply) / sizeof(int);
		int i;

		for (i = 0; i < num_options_tags_not_excludable_from_bootpreply; i++) {
			int option_code;

			option_code = options_tags_not_excludable_from_bootpreply[i];
			if (get_bitvector(vector, option_code)) {
				clear_bitvector(vector, option_code);
				if (errmsg_prefix != NULL) { /* NULL means caller does not want us to produce error messages at all */
					report(LOG_ERR, "%swe do not allow you to exclude option code %d from BOOTPREPLY messages", errmsg_prefix, option_code);
				}
			}
		}
		return;
}



/*
 * Read a string from the buffer indirectly pointed to through "src" and
 * move it into the buffer pointed to by "dest".  A pointer to the maximum
 * allowable length of the string (including null-terminator) is passed as
 * "length".  The actual length of the string which was read is returned in
 * the unsigned integer pointed to by "length".  This value is the same as
 * that which would be returned by applying the strlen() function on the
 * destination string (i.e the terminating null is not counted as a
 * character).  Trailing whitespace is removed from the string.  For
 * convenience, the function returns the new value of "dest".
 *
 * The string is read until the maximum number of characters, an unquoted
 * colon (:), or a null character is read.  The return string in "dest" is
 * null-terminated.
 */

PRIVATE char *
get_string(src, dest, length)
	char **src, *dest;
	unsigned *length;
{
	int n, len, quoteflag;

	quoteflag = FALSE;
	n = 0;
	len = *length - 1;
	while ((n < len) && (**src)) {
		if (!quoteflag && (**src == ':')) {
			break;
		}
		if (**src == '"') {
			(*src)++;
			quoteflag = !quoteflag;
			continue;
		}
		if (**src == '\\') {
			(*src)++;
			if (!**src) {
				break;
			}
		}
		*dest++ = *(*src)++;
		n++;
	}

	/*
	 * Remove that troublesome trailing whitespace. . .
	 */
	while ((n > 0) && isspace(dest[-1])) {
		dest--;
		n--;
	}

	*dest = '\0';
	*length = n;
	return dest;
}



/*
 * Read the string indirectly pointed to by "src", update the caller's
 * pointer, and return a pointer to a malloc'ed shared_string structure
 * containing the string.
 *
 * The string is read using the same rules as get_string() above.
 */

PRIVATE struct shared_string *
get_shared_string(src)
	char **src;
{
	char retstring[MAXSTRINGLEN];
	struct shared_string *s;
	unsigned length;

	length = sizeof(retstring);
	(void) get_string(src, retstring, &length);

	s = (struct shared_string *) smalloc(sizeof(struct shared_string)
										 + length);
	s->linkcount = 1;
	strcpy(s->string, retstring);

	return s;
}



/*
 * Load RFC1048 generic information directly into a memory buffer.
 *
 * "src" indirectly points to the ASCII representation of the generic data.
 * "dest" points to a string structure which is updated to point to a new
 * string with the new data appended to the old string.  The old string is
 * freed.
 *
 * The given tag value is inserted with the new data.
 *
 * The data may be represented as either a stream of hexadecimal numbers
 * representing bytes (any or all bytes may optionally start with '0x' and
 * be separated with periods ".") or as a quoted string of ASCII
 * characters (the quotes are required).
 *
 * Return 0 on success, non-zero on failure.
 */

PRIVATE int
process_generic(src, dest, tagvalue)
	char **src;
	struct shared_bindata **dest;
	u_int tagvalue;
{
	byte tmpbuf[MAXBUFLEN];
	byte *str;
	struct shared_bindata *bdata;
	u_int newlength, oldlength;

	str = tmpbuf;
	*str++ = (tagvalue & 0xFF);	/* Store tag value */
	str++;						/* Skip over where we will store length field (for now) */
	if ((*src)[0] == '"') {		/* ASCII data */
		newlength = sizeof(tmpbuf) - 2;	/* Set maximum allowed length */
		(void) get_string(src, (char *) str, &newlength);
		newlength++;			/* null terminator */
	} else {					/* Numeric data */
		newlength = 0;
		while (newlength < sizeof(tmpbuf) - 2) {
			if (interp_byte(src, str++) !=  0)
				break;
			newlength++;
			if (**src == '.') {
				(*src)++;
			}
		}
	}
/*	if ((*src)[0] != ':')
		return -1;
*/
	/* Options other than 0 and 255 are not permitted to have zero data-length.
	   As we don't support the use of generic tag syntax for either option 0 or 255,
	   disallow zero data-length generic options.
	*/
	if (newlength == 0)
		return -1;
	tmpbuf[1] = (newlength & 0xFF);
	oldlength = ((*dest)->length);
	bdata = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata)
											+ oldlength + newlength + 1);
	if (oldlength > 0) {
		bcopy((*dest)->data, bdata->data, oldlength);
	}
	bcopy(tmpbuf, bdata->data + oldlength, newlength + 2);
	bdata->length = oldlength + newlength + 2;
	bdata->linkcount = 1;
	if (*dest) {
		del_bindata(*dest);
	}
	*dest = bdata;
	return 0;
}

PRIVATE int
process_bindata(src, dest)
	char **src;
	struct shared_bindata **dest;
{
	byte tmpbuf[MAXBUFLEN];
	byte *str;
	struct shared_bindata *bdata;
	u_int newlength;

	str = tmpbuf;
	if ((*src)[0] == '"') {		/* ASCII data */
		newlength = sizeof(tmpbuf) - 2;	/* Set maximum allowed length */
		(void) get_string(src, (char *) str, &newlength);
		newlength++;			/* null terminator */
	} else {					/* Numeric data */
		newlength = 0;
		while (newlength < sizeof(tmpbuf) - 2) {
			if (interp_byte(src, str++) !=  0)
				break;
			newlength++;
			if (**src == '.') {
				(*src)++;
			}
		}
	}
/*	if ((*src)[0] != ':')
		return -1;
*/
	bdata = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata)
											+ newlength - 1);
	bcopy(tmpbuf, bdata->data, newlength);
	bdata->length =  newlength;
	bdata->linkcount = 1;
	if (*dest) {
		del_bindata(*dest);
	}
	*dest = bdata;
	return 0;
}



/*
 * Verify that the given string makes sense as a hostname (according to
 * Appendix 1, page 29 of RFC882).
 *
 * Return TRUE for good names, FALSE otherwise.
 */

PRIVATE boolean
goodname(hostname)
	register char *hostname;
{
	do {
		if (!isalpha(*hostname++)) {	/* First character must be a letter */
			return FALSE;
		}
		while (isalnum(*hostname) ||
			   (*hostname == '-') ||
			   (*hostname == '_') )
		{
			hostname++;			/* Alphanumeric or a hyphen */
		}
		if (!isalnum(hostname[-1])) {	/* Last must be alphanumeric */
			return FALSE;
		}
		if (*hostname == '\0') {/* Done? */
			return TRUE;
		}
	} while (*hostname++ == '.');	/* Dot, loop for next label */

	return FALSE;				/* If it's not a dot, lose */
}



/*
 * Null compare function -- always returns FALSE so an element is always
 * inserted into a hash table (i.e. there is never a collision with an
 * existing element).
 */

boolean
nullcmp(d1, d2)
	hash_datum *d1, *d2;
{
	return FALSE;
}


/*
 * Function for comparing a string with the hostname field of a host
 * structure.
 */

boolean
nmcmp(d1, d2)
	hash_datum *d1, *d2;
{
	char *name = (char *) d1;	/* XXX - OK? */
	struct host *hp = (struct host *) d2;

	return !strcmp(name, hp->hostname->string);
}


/*
 * Compare function to determine whether two hardware addresses are
 * equivalent.  Returns TRUE if "host1" and "host2" are equivalent, FALSE
 * otherwise.
 *
 * If the hardware addresses of "host1" and "host2" are identical, but
 * they are on different IP subnets, this function returns FALSE.
 *
 * This function is used when inserting elements into the hardware address
 * hash table.
 */

PRIVATE boolean
hwinscmp(d1, d2)
	hash_datum *d1, *d2;
{
	struct host *host1 = (struct host *) d1;
	struct host *host2 = (struct host *) d2;

	if (host1->htype != host2->htype) {
		return FALSE;
	}
	if (bcmp(host1->haddr, host2->haddr, haddrlength(host1->htype))) {
		return FALSE;
	}
	/* XXX - Is the subnet_mask field set yet? */
	if ((host1->subnet_mask.s_addr) == (host2->subnet_mask.s_addr)) {
		if (((host1->iaddr.s_addr) & (host1->subnet_mask.s_addr)) !=
			((host2->iaddr.s_addr) & (host2->subnet_mask.s_addr)))
		{
			return FALSE;
		}
	}
	return TRUE;
}


/*
 * Macros for use in the function below:
 */

/* copy MEMBER from struct host * hp2 into struct host * hp, and set hp->flags.MEMBER.
   Note we only do this if hp->flags.MEMBER is not already set. */
#define DUP_COPY(MEMBER) do \
{ \
	if (!hp->flags.MEMBER) { \
		if ((hp->flags.MEMBER = hp2->flags.MEMBER) != 0) { \
			hp->MEMBER = hp2->MEMBER; \
		} \
	} \
} while (0)

/* copy MEMBER from struct host * hp2 into struct host * hp, and set hp->flags.MEMBER.
   Use this instead of DUP_COPY if MEMBER is a structure with a linkcount field, since this
   macro increments that field.
   Note we only do this if hp->flags.MEMBER is not already set. */
#define DUP_LINK(MEMBER) do \
{ \
	if (!hp->flags.MEMBER) { \
		if ((hp->flags.MEMBER = hp2->flags.MEMBER) != 0) { \
			assert(hp2->MEMBER); \
			hp->MEMBER = hp2->MEMBER; \
			(hp->MEMBER->linkcount)++; \
		} \
	} \
} while (0)


/* Copy most information from host (template) hp2 into host hp */
void
fill_defaults2(hp, hp2)
	struct host *hp,*hp2;
{
	DUP_LINK(bootfile);
	DUP_LINK(cookie_server);
	DUP_LINK(domain_server);
	DUP_LINK(gateway);
	if(!hp->flags.haddr && hp2->flags.haddr){
		bcopy(hp2->haddr,hp->haddr,MAXHADDRLEN);
		hp->flags.haddr=hp2->flags.haddr;
	}
	/* haddr copied */
	DUP_LINK(homedir);
	DUP_COPY(htype);
	DUP_COPY(node_type);
	DUP_COPY(iaddr);

	DUP_LINK(impress_server);
	DUP_LINK(log_server);
	DUP_LINK(lpr_server);
	DUP_LINK(name_server);
	DUP_LINK(rlp_server);
	DUP_LINK(cliid);

	DUP_COPY(subnet_mask);
	DUP_COPY(time_offset);
	DUP_COPY(lease); /* that's the length of a lease in seconds, not the expiration time. */
	DUP_LINK(time_server);
	if(!hp->flags.roaming)
		hp->flags.roaming=hp2->flags.roaming;
	if(!hp->flags.not_rq)
		hp->flags.not_rq=hp2->flags.not_rq;

	if (!hp->flags.vm_cookie) {
		if ((hp->flags.vm_cookie = hp2->flags.vm_cookie)) {
			bcopy(hp2->vm_cookie, hp->vm_cookie, 4);
		}
	}
	if (!hp->flags.name_switch) {
		if ((hp->flags.name_switch = hp2->flags.name_switch)) {
			hp->flags.send_name = hp2->flags.send_name;
		}
	}
	if (!hp->flags.bootsize) {
		if ((hp->flags.bootsize = hp2->flags.bootsize)) {
			hp->flags.bootsize_auto = hp2->flags.bootsize_auto;
			hp->bootsize = hp2->bootsize;
		}
	}
	if (!hp->flags.dynamic)
		hp->flags.dynamic=hp2->flags.dynamic;
	if (!hp->flags.deny_client)
		hp->flags.deny_client=hp2->flags.deny_client;
	if (!hp->flags.deny_client_dynamic)
		hp->flags.deny_client_dynamic=hp2->flags.deny_client_dynamic;
		
	DUP_COPY(bootserver);

	DUP_LINK(tftpdir);
	DUP_LINK(dump_file);
	DUP_LINK(domain_name);
	DUP_LINK(nb_scope);

	DUP_COPY(swap_server);
	DUP_LINK(root_path);
	DUP_LINK(exten_file);

	DUP_COPY(reply_addr);
	DUP_COPY(expire);
	DUP_COPY(expire_prior_to_offer);

	DUP_LINK(nis_domain);
	DUP_LINK(nis_server);
	DUP_LINK(ntp_server);
	DUP_LINK(nbns_server);

#ifdef	YORK_EX_OPTION
	DUP_LINK(exec_file);
#endif

	DUP_COPY(msg_size);
	DUP_COPY(min_wait);

	DUP_LINK(generic);
	DUP_LINK(cable);
	DUP_COPY(broadcast_addr);
	DUP_COPY(perform_router_discovery);
	DUP_LINK(static_route);
	DUP_COPY(client_fqdn);
	DUP_COPY(ethernet_encapsulation);
	DUP_LINK(nbdd_server);
	DUP_LINK(smtp_server);
	DUP_LINK(pop3_server);
	DUP_LINK(nntp_server);
	DUP_LINK(www_server);
	DUP_LINK(finger_server);
	DUP_LINK(irc_server);
	DUP_LINK(bootp_exclude);
	DUP_LINK(bootp_include);
	DUP_LINK(bootp_exclude_vector);
	DUP_LINK(bootp_include_vector);
	DUP_COPY(ip_forwarding);
	DUP_COPY(ip_source_routing);
	DUP_COPY(ip_ttl);
	DUP_COPY(all_subnets_are_local);
	DUP_COPY(perform_mask_discovery);
	DUP_COPY(mask_supplier);
	DUP_COPY(router_solicitation_addr);
	DUP_COPY(trailer_encapsulation);
	DUP_COPY(tcp_ttl);
	DUP_COPY(tcp_keepalive_garbage);
	DUP_LINK(xwindows_font_server);
	DUP_LINK(xwindows_display_manager);
	DUP_LINK(nisplus_domain);
	DUP_LINK(nisplus_server);

	if (!hp->flags.offerred_but_not_yet_accepted_by_client)
		hp->flags.offerred_but_not_yet_accepted_by_client=hp2->flags.offerred_but_not_yet_accepted_by_client;

	/* XXX - Add new tags here */

}
/*
 * Process the "similar entry" symbol.
 *
 * The host specified as the value of the "tc" symbol is used as a template
 * for the current host entry.  Symbol values not explicitly set in the
 * current host entry are inferred from the template entry.
 */
void
fill_defaults(hp, src)
	struct host *hp;
	char **src;
{
	unsigned int tlen, hashcode;
	struct host *hp2;
	char tstring[MAXSTRINGLEN];

	tlen = sizeof(tstring);
	(void) get_string(src, tstring, &tlen);
	hashcode = hash_HashFunction((u_char *) tstring, tlen);
	hp2 = (struct host *) hash_Lookup(nmhashtable, hashcode, nmcmp, tstring);

	if (hp2 == NULL) {
		report(LOG_ERR, "can't find tc=\"%s\"", tstring);
		return;
	}
	DUP_LINK(bootfile);
	DUP_LINK(cookie_server);
	DUP_LINK(domain_server);
	DUP_LINK(gateway);
	/* haddr not copied */
	DUP_LINK(homedir);
	DUP_COPY(htype);
	DUP_COPY(node_type);

	DUP_LINK(impress_server);
	/* iaddr not copied */
	DUP_LINK(log_server);
	DUP_LINK(lpr_server);
	DUP_LINK(name_server);
	DUP_LINK(rlp_server);

	DUP_COPY(subnet_mask);
	DUP_COPY(time_offset);
	DUP_COPY(lease);
	DUP_LINK(time_server);
	/* cliid is not copied */
	if(!hp->flags.roaming)
		hp->flags.roaming=hp2->flags.roaming;
	if(!hp->flags.not_rq)
		hp->flags.not_rq=hp2->flags.not_rq;
	if (!hp->flags.vm_cookie) {
		if ((hp->flags.vm_cookie = hp2->flags.vm_cookie)) {
			bcopy(hp2->vm_cookie, hp->vm_cookie, 4);
		}
	}
	if (!hp->flags.name_switch) {
		if ((hp->flags.name_switch = hp2->flags.name_switch)) {
			hp->flags.send_name = hp2->flags.send_name;
		}
	}
	if (!hp->flags.bootsize) {
		if ((hp->flags.bootsize = hp2->flags.bootsize)) {
			hp->flags.bootsize_auto = hp2->flags.bootsize_auto;
			hp->bootsize = hp2->bootsize;
		}
	}
	if (!hp->flags.dynamic)
		hp->flags.dynamic=hp2->flags.dynamic;
	if (!hp->flags.deny_client)
		hp->flags.deny_client=hp2->flags.deny_client;
	if (!hp->flags.deny_client_dynamic)
		hp->flags.deny_client_dynamic=hp2->flags.deny_client_dynamic;
		
	DUP_COPY(bootserver);

	DUP_LINK(tftpdir);
	DUP_LINK(dump_file);
	DUP_LINK(domain_name);
	DUP_LINK(nb_scope);

	DUP_COPY(swap_server);
	DUP_LINK(root_path);
	DUP_LINK(exten_file);

	DUP_COPY(reply_addr);

	DUP_LINK(nis_domain);
	DUP_LINK(nis_server);
	DUP_LINK(ntp_server);
	DUP_LINK(nbns_server);

#ifdef	YORK_EX_OPTION
	DUP_LINK(exec_file);
#endif

	DUP_COPY(msg_size);
	DUP_COPY(min_wait);

	DUP_LINK(generic);
	DUP_LINK(cable);
	DUP_COPY(broadcast_addr);
	DUP_COPY(perform_router_discovery);
	DUP_LINK(static_route);
	DUP_COPY(client_fqdn);
	DUP_COPY(ethernet_encapsulation);
	DUP_LINK(nbdd_server);
	DUP_LINK(smtp_server);
	DUP_LINK(nntp_server);
	DUP_LINK(www_server);
	DUP_LINK(finger_server);
	DUP_LINK(irc_server);
	DUP_LINK(bootp_exclude);
	DUP_LINK(bootp_exclude_vector);
	DUP_LINK(bootp_include);
	DUP_LINK(bootp_include_vector);
	DUP_COPY(ip_forwarding);
	DUP_COPY(ip_source_routing);
	DUP_COPY(ip_ttl);
	DUP_COPY(all_subnets_are_local);
	DUP_COPY(perform_mask_discovery);
	DUP_COPY(mask_supplier);
	DUP_COPY(router_solicitation_addr);
	DUP_COPY(trailer_encapsulation);
	DUP_COPY(tcp_ttl);
	DUP_COPY(tcp_keepalive_garbage);
	DUP_LINK(xwindows_font_server);
	DUP_LINK(xwindows_display_manager);
	DUP_LINK(nisplus_domain);
	DUP_LINK(nisplus_server);

	if (!hp->flags.offerred_but_not_yet_accepted_by_client)
		hp->flags.offerred_but_not_yet_accepted_by_client=hp2->flags.offerred_but_not_yet_accepted_by_client;

	/* XXX - Add new tags here */

}
#undef	DUP_COPY
#undef	DUP_LINK



/*
 * This function adjusts the caller's pointer to point just past the
 * first-encountered colon.  If it runs into a null character, it leaves
 * the pointer pointing to it.
 */

PRIVATE void
adjust(s)
	char **s;
{
	register char *t;

	t = *s;
	while (*t && (*t != ':')) {
		t++;
	}
	if (*t) {
		t++;
	}
	*s = t;
}




/*
 * This function adjusts the caller's pointer to point to the first
 * non-whitespace character.  If it runs into a null character, it leaves
 * the pointer pointing to it.
 */

void
eat_whitespace(s)
	char **s;
{
	register char *t;

	t = *s;
	while (*t && isspace(*t)) {
		t++;
	}
	*s = t;
}



/*
 * This function converts the given string to all lowercase.
 */

void
makelower(s)
	char *s;
{
	while (*s) {
		if (isupper(*s)) {
			*s = tolower(*s);
		}
		s++;
	}
}



/*
 *
 *	N O T E :
 *
 *	In many of the functions which follow, a parameter such as "src" or
 *	"symbol" is passed as a pointer to a pointer to something.  This is
 *	done for the purpose of letting the called function update the
 *	caller's copy of the parameter (i.e. to effect call-by-reference
 *	parameter passing).  The value of the actual parameter is only used
 *	to locate the real parameter of interest and then update this indirect
 *	parameter.
 *
 *	I'm sure somebody out there won't like this. . . .
 *  (Yea, because it usually makes code slower... -gwr)
 *
 */



/*
 * "src" points to a character pointer which points to an ASCII string of
 * whitespace-separated IP addresses.  A pointer to an in_addr_list
 * structure containing the list of addresses is returned.  NULL is
 * returned if no addresses were found at all.  The pointer pointed to by
 * "src" is updated to point to the first non-address (illegal) character.
 */

PRIVATE struct in_addr_list *
get_addresses(src)
	char **src;
{
	struct in_addr tmpaddrlist[MAXINADDRS];
	struct in_addr *address1, *address2;
	struct in_addr_list *result;
	unsigned addrcount, totalsize;

	address1 = tmpaddrlist;
	for (addrcount = 0; addrcount < MAXINADDRS; addrcount++) {
		while (isspace(**src) || (**src == ',')) {
			(*src)++;
		}
		if (!**src) {			/* Quit if nothing more */
			break;
		}
		if (prs_inetaddr(src, (u_int32 *) &(address1->s_addr)) < 0) {
			break;
		}
		address1++;				/* Point to next address slot */
	}
	if (addrcount < 1) {
		result = NULL;
	} else {
		totalsize = sizeof(struct in_addr_list)
		+			(addrcount - 1) * sizeof(struct in_addr);
		result = (struct in_addr_list *) smalloc(totalsize);
		result->linkcount = 1;
		result->addrcount = addrcount;
		address1 = tmpaddrlist;
		address2 = result->addr;
		for (; addrcount > 0; addrcount--) {
			address2->s_addr = address1->s_addr;
			address1++;
			address2++;
		}
	}
	return result;
}



/*
 * prs_inetaddr(src, result)
 *
 * "src" is a value-result parameter; the pointer it points to is updated
 * to point to the next data position.   "result" points to an unsigned long
 * in which an address is returned.
 *
 * This function parses the IP address string in ASCII "dot notation" pointed
 * to by (*src) and places the result (in network byte order) in the unsigned
 * long pointed to by "result".  For malformed addresses, -1 is returned,
 * (*src) points to the first illegal character, and the unsigned long pointed
 * to by "result" is unchanged.  Successful calls return 0.
 */

int
prs_inetaddr(src, result)
	char **src;
	u_int32 *result;
{
	char tmpstr[MAXSTRINGLEN];
	register u_int32 value;
	u_int32 parts[4], *pp;
	int n;
	char *s, *t;

	/* Leading alpha char causes IP addr lookup. */
	if (isalpha(**src)) {
		/* Lookup IP address. */
		s = *src;
		t = tmpstr;
		while ((isalnum(*s) || (*s == '.') ||
				(*s == '-') || (*s == '_') ) &&
			   (t < &tmpstr[MAXSTRINGLEN - 1]) )
			*t++ = *s++;
		*t = '\0';
		*src = s;

		n = lookup_ipa(tmpstr, result);
		if (n < 0)
			report(LOG_ERR, "can not get IP addr for %s", tmpstr);
		return n;
	}

	/*
	 * Parse an address in Internet format:
	 *	a.b.c.d
	 *	a.b.c	(with c treated as 16-bits)
	 *	a.b	(with b treated as 24 bits)
	 */
	pp = parts;
  loop:
	/* If it's not a digit, return error. */
	if (!isdigit(**src))
		return -1;
	*pp++ = get_u_long(src);
	if (**src == '.') {
		if (pp < (parts + 4)) {
			(*src)++;
			goto loop;
		}
		return (-1);
	}
	/* This is handled by the caller. */
	if (**src && !(isspace(**src) || (**src == ':'))) {
		return (-1);
	}

	/*
	 * Construct the address according to
	 * the number of parts specified.
	 */
	n = pp - parts;
	switch (n) {
	case 1:					/* a -- 32 bits */
		value = parts[0];
		break;
	case 2:					/* a.b -- 8.24 bits */
		value = (parts[0] << 24) | (parts[1] & 0xFFFFFF);
		break;
	case 3:					/* a.b.c -- 8.8.16 bits */
		value = (parts[0] << 24) | ((parts[1] & 0xFF) << 16) |
			(parts[2] & 0xFFFF);
		break;
	case 4:					/* a.b.c.d -- 8.8.8.8 bits */
		value = (parts[0] << 24) | ((parts[1] & 0xFF) << 16) |
			((parts[2] & 0xFF) << 8) | (parts[3] & 0xFF);
		break;
	default:
		return (-1);
	}
	*result = htonl(value);
	return (0);
}



/*
 * "src" points to a pointer which in turn points to a hexadecimal ASCII
 * string.  This string is interpreted as a hardware address and returned
 * as a pointer to the actual hardware address, represented as an array of
 * bytes.
 *
 * The ASCII string must have the proper number of digits for the specified
 * hardware type (e.g. twelve digits for a 48-bit Ethernet address).
 * Two-digit sequences (bytes) may be separated with periods (.)  and/or
 * prefixed with '0x' for readability, but this is not required.
 *
 * For bad addresses, the pointer which "src" points to is updated to point
 * to the start of the first two-digit sequence which was bad, and the
 * function returns a NULL pointer.
 */

PRIVATE byte *
prs_haddr(src, htype)
	char **src;
	u_int htype;
{
	static byte haddr[MAXHADDRLEN];
	byte *hap;
	char tmpstr[MAXSTRINGLEN];
	u_int tmplen;
	unsigned hal;
	char *p;

	hal = haddrlength(htype);	/* Get length of this address type */
	if (hal <= 0) {
		report(LOG_ERR, "Invalid addr type for HW addr parse");
		return NULL;
	}
	tmplen = sizeof(tmpstr);
	get_string(src, tmpstr, &tmplen);
	p = tmpstr;

	/*  If it's a valid host name, try to lookup the HW address. */
	if (goodname(p)) {
		/* Lookup Hardware Address for hostname. */
		if ((hap = lookup_hwa(p, htype)) != NULL)
			return hap; /* success */
		if(debug>5)
			report(LOG_ERR, "Add 0x prefix if hex value starts with A-F");
		/* OK, assume it must be numeric. */
	}

	hap = haddr;
	while (hap < haddr + hal) {
		if ((*p == '.') || (*p == ':'))
			p++;
		if (interp_byte(&p, hap++) < 0) {
			return NULL;
		}
	}
	return haddr;
}



/*
 * "src" is a pointer to a character pointer which in turn points to a
 * hexadecimal ASCII representation of a byte.  This byte is read, the
 * character pointer is updated, and the result is deposited into the
 * byte pointed to by "retbyte".
 *
 * The usual '0x' notation is allowed but not required.  The number must be
 * a two digit hexadecimal number.  If the number is invalid, "src" and
 * "retbyte" are left untouched and -1 is returned as the function value.
 * Successful calls return 0.
 */

PRIVATE int
interp_byte(src, retbyte)
	char **src;
	byte *retbyte;
{
	int v;

	if ((*src)[0] == '0' &&
		((*src)[1] == 'x' ||
		 (*src)[1] == 'X')) {
		(*src) += 2;			/* allow 0x for hex, but don't require it */
	}
	if (!isxdigit((*src)[0]) || !isxdigit((*src)[1])) {
		return -1;
	}
	if (sscanf(*src, "%2X", &v) != 1) {
		return -1;
	}
	(*src) += 2;
	*retbyte = (byte) (v & 0xFF);
	return 0;
}



/*
 * The parameter "src" points to a character pointer which points to an
 * ASCII string representation of an unsigned number.  The number is
 * returned as an unsigned long and the character pointer is updated to
 * point to the first illegal character.
 */

PRIVATE u_int32
get_u_long(src)
	char **src;
{
	register u_int32 value, base;
	char c;

	/*
	 * Collect number up to first illegal character.  Values are specified
	 * as for C:  0x=hex, 0=octal, other=decimal.
	 */
	value = 0;
	base = 10;
	if (**src == '0') {
		base = 8;
		(*src)++;
	}
	if (**src == 'x' || **src == 'X') {
		base = 16;
		(*src)++;
	}
	while ((c = **src)) {
		if (isdigit(c)) {
			value = (value * base) + (c - '0');
			(*src)++;
			continue;
		}
		if (base == 16 && isxdigit(c)) {
			value = (value << 4) + ((c & ~32) + 10 - 'A');
			(*src)++;
			continue;
		}
		break;
	}
	return value;
}



/*
 * Routines for deletion of data associated with the main data structure.
 */


/*
 * Frees the entire host data structure given.  Does nothing if the passed
 * pointer is NULL.
 */

void
free_host(hmp)
	hash_datum *hmp;
{
	struct host *hostptr = (struct host *) hmp;

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "free_host(): called");
#endif

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

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "free_host(): hmp->hostname == %s", hostptr->hostname->string);
#endif

	if(hostptr->linkcount <= 0)
		report(LOG_ERR,"Assert error, host freed too many times");
	assert(hostptr->linkcount > 0);

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "free_host(): linkcount was %d, will decrement", hostptr->linkcount);
#endif

	if (--(hostptr->linkcount)) {
#ifdef DEBUG
		if (debug > 15) 
			report(LOG_DEBUG, "free_host(): resulting linkcount still > 0, will do nothing more");
#endif
		return;					/* Still has references */
	} 

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "free_host(): linkcount now 0, will really free host structure");
#endif

	del_iplist(hostptr->cookie_server);
	del_iplist(hostptr->domain_server);
	del_iplist(hostptr->gateway);
	del_iplist(hostptr->impress_server);
	del_iplist(hostptr->log_server);
	del_iplist(hostptr->lpr_server);
	del_iplist(hostptr->name_server);
	del_iplist(hostptr->rlp_server);
	del_iplist(hostptr->time_server);
	del_iplist(hostptr->nis_server);
	del_iplist(hostptr->ntp_server);
	del_iplist(hostptr->nbns_server);
	del_iplist(hostptr->static_route);
	del_iplist(hostptr->nbdd_server);
	del_iplist(hostptr->smtp_server);
	del_iplist(hostptr->pop3_server);
	del_iplist(hostptr->nntp_server);
	del_iplist(hostptr->www_server);
	del_iplist(hostptr->finger_server);
	del_iplist(hostptr->irc_server);
	del_iplist(hostptr->xwindows_font_server);
	del_iplist(hostptr->xwindows_display_manager);
	del_iplist(hostptr->nisplus_server);
	/*
	 * XXX - Add new tags here
	 * (if the value is an IP list)
	 */

	del_string(hostptr->hostname);
	del_string(hostptr->homedir);
	del_string(hostptr->bootfile);
	del_string(hostptr->tftpdir);
	del_string(hostptr->root_path);
	del_string(hostptr->domain_name);
	del_string(hostptr->dump_file);
	del_string(hostptr->exten_file);
	del_string(hostptr->nis_domain);
	del_string(hostptr->nb_scope);
	del_string(hostptr->cable);
	del_string(hostptr->bootp_exclude);
	del_string(hostptr->bootp_include);
#ifdef	YORK_EX_OPTION
	del_string(hostptr->exec_file);
#endif
	del_string(hostptr->nisplus_domain);
	/*
	 * XXX - Add new tags here
	 * (if it is a shared string)
	 */

	del_bindata(hostptr->generic);
	del_bindata(hostptr->cliid);
	del_bindata(hostptr->cl);
	del_bindata(hostptr->bootp_exclude_vector);
	del_bindata(hostptr->bootp_include_vector);
	/*
	 * XXX - Add new tags here
	 * (if it is a shared bindata)
	 */

	free((char *) hostptr);
}



/*
 * Decrements the linkcount on the given IP address data structure.  If the
 * linkcount goes to zero, the memory associated with the data is freed.
 */

PRIVATE void
del_iplist(iplist)
	struct in_addr_list *iplist;
{
	if (iplist) {
		if (!(--(iplist->linkcount))) {
			free((char *) iplist);
		}
	}
}



/*
 * Decrements the linkcount on a string data structure.  If the count
 * goes to zero, the memory associated with the string is freed.  Does
 * nothing if the passed pointer is NULL.
 */

void
del_string(stringptr)
	struct shared_string *stringptr;
{
	if (stringptr) {
		if (!(--(stringptr->linkcount))) {
			free((char *) stringptr);
		}
	}
}



/*
 * Decrements the linkcount on a shared_bindata data structure.  If the
 * count goes to zero, the memory associated with the data is freed.  Does
 * nothing if the passed pointer is NULL.
 */

void
del_bindata(dataptr)
	struct shared_bindata *dataptr;
{
	if (dataptr) {
		if (!(--(dataptr->linkcount))) {
			free((char *) dataptr);
		}
	}
}


/*
 * Compare function to determine whether two hardware addresses are
 * equivalent.  Returns TRUE if "host1" and "host2" are equivalent, FALSE
 * otherwise.
 *
 * This function is used when retrieving elements from the hardware address
 * hash table.
 */

boolean
hwlookcmp(d1, d2)
	hash_datum *d1, *d2;
{
	struct host *host1 = (struct host *) d1;
	struct host *host2 = (struct host *) d2;

	if (host1->htype != host2->htype) {
		return FALSE;
	}
	if (bcmp(host1->haddr, host2->haddr, haddrlength(host1->htype))) {
		return FALSE;
	}
	return TRUE;
}


/*
 * Compare function for doing IP address hash table lookup.
 */

boolean
iplookcmp(d1, d2)
	hash_datum *d1, *d2;
{
	struct host *host1 = (struct host *) d1;
	struct host *host2 = (struct host *) d2;

	return (host1->iaddr.s_addr == host2->iaddr.s_addr);
}

#if 0
boolean
host_cmp(h1, h2)
struct host *h1, *h2;
{
	struct in_addr_list *p,*p2;

	if(bcmp(&h1->flags,&h2->flags,sizeof(struct flag)))
		return FALSE;
	if(h1->flags.node_type && (h1->node_type!=h2->node_type))
		return FALSE;
	if(h1->flags.htype && (h1->htype!=h2->htype))
		return FALSE;
	if(h1->flags.time_offset && (h1->time_offset!=h2->time_offset))
		return FALSE;
	if(h1->flags.bootsize && (h1->bootsize!=h2->bootsize))
		return FALSE;
	if(h1->flags.msg_size && (h1->msg_size!=h2->msg_size))
		return FALSE;
	if(h1->flags.min_wait && (h1->min_wait!=h2->min_wait))
		return FALSE;
	if(h1->flags.lease && (h1->lease!=h2->lease))
		return FALSE;
	if(h1->flags.iaddr && (h1->iaddr.s_addr!=h2->iaddr.s_addr))
		return FALSE;
	if(h1->flags.subnet_mask && (h1->subnet_mask.s_addr!=h2->subnet_mask.s_addr))
		return FALSE;
	if(h1->flags.broadcast_addr && (h1->broadcast_addr.s_addr!=h2->broadcast_addr.s_addr))
		return FALSE;
	if(h1->flags.cookie_server &&
		bcmp(h1->cookie_server,h2->cookie_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->cookie_server->addrcount-1)))
		return FALSE;
	if(h1->flags.domain_server &&
		bcmp(h1->domain_server,h2->domain_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->domain_server->addrcount-1)))
		return FALSE;
	if(h1->flags.gateway &&
		bcmp(h1->gateway,h2->gateway, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->gateway->addrcount-1)))
		return FALSE;
	if(h1->flags.impress_server &&
		bcmp(h1->impress_server,h2->impress_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->impress_server->addrcount-1)))
		return FALSE;
	if(h1->flags.log_server &&
		bcmp(h1->log_server,h2->log_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->log_server->addrcount-1)))
		return FALSE;
	if(h1->flags.lpr_server &&
		bcmp(h1->lpr_server,h2->lpr_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->lpr_server->addrcount-1)))
		return FALSE;
	if(h1->flags.name_server &&
		bcmp(h1->name_server,h2->name_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->name_server->addrcount-1)))
		return FALSE;
	if(h1->flags.rlp_server &&
		bcmp(h1->rlp_server,h2->rlp_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->rlp_server->addrcount-1)))
		return FALSE;
	if(h1->flags.time_server &&
		bcmp(h1->time_server,h2->time_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->time_server->addrcount-1)))
		return FALSE;
	if(h1->flags.nis_server &&
		bcmp(h1->nis_server,h2->nis_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->nis_server->addrcount-1)))
		return FALSE;
	if(h1->flags.nbns_server &&
		bcmp(h1->nbns_server,h2->nbns_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->nbns_server->addrcount-1)))
		return FALSE;
	if(h1->flags.ntp_server &&
		bcmp(h1->ntp_server,h2->ntp_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->ntp_server->addrcount-1)))
		return FALSE;
	if(h1->flags.ntp_server &&
		bcmp(h1->ntp_server,h2->ntp_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->ntp_server->addrcount-1)))
		return FALSE;
	if(h1->flags.cliid && (h1->cliid->length!=h2->cliid->length || 
			bcmp(h1->cliid->data,h2->cliid->data,h1->cliid->length)))
		return FALSE;
	if(h1->flags.perform_router_discovery && (h1->perform_router_discovery!=h2->perform_router_discovery))
		return FALSE;
	if(h1->flags.static_route &&
		bcmp(h1->static_route,h2->static_route, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->static_route->addrcount-1)))
		return FALSE;
	if(h1->flags.client_fqdn && (h1->client_fqdn!=h2->client_fqdn))
		return FALSE;
	if(h1->flags.generic && (h1->generic->length!=h2->generic->length || 
			bcmp(h1->generic->data,h2->generic->data,h1->generic->length)))
		return FALSE;
	if(h1->flags.ethernet_encapsulation && (h1->ethernet_encapsulation!=h2->ethernet_encapsulation))
		return FALSE;
	if(h1->flags.nbdd_server &&
		bcmp(h1->nbdd_server,h2->nbdd_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->nbdd_server->addrcount-1)))
		return FALSE;
	if(h1->flags.smtp_server &&
		bcmp(h1->smtp_server,h2->smtp_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->smtp_server->addrcount-1)))
		return FALSE;
	if(h1->flags.pop3_server &&
		bcmp(h1->pop3_server,h2->pop3_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->pop3_server->addrcount-1)))
		return FALSE;
	if(h1->flags.nntp_server &&
		bcmp(h1->nntp_server,h2->nntp_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->nntp_server->addrcount-1)))
		return FALSE;
	if(h1->flags.www_server &&
		bcmp(h1->www_server,h2->www_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->www_server->addrcount-1)))
		return FALSE;
	if(h1->flags.finger_server &&
		bcmp(h1->finger_server,h2->finger_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->finger_server->addrcount-1)))
		return FALSE;
	if(h1->flags.irc_server &&
		bcmp(h1->irc_server,h2->irc_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->irc_server->addrcount-1)))
		return FALSE;
	if(h1->flags.bootp_exclude && (h1->bootp_exclude->length!=h2->bootp_exclude->length || 
			bcmp(h1->bootp_exclude->data,h2->bootp_exclude->data,h1->bootp_exclude->length)))
		return FALSE;
	if(h1->flags.bootp_exclude_vector && (h1->bootp_exclude_vector->length!=h2->bootp_exclude_vector->length || 
			bcmp(h1->bootp_exclude_vector->data,h2->bootp_exclude_vector->data,h1->bootp_exclude_vector->length)))
		return FALSE;
	if(h1->flags.bootp_include && (h1->bootp_include->length!=h2->bootp_include->length || 
			bcmp(h1->bootp_include->data,h2->bootp_include->data,h1->bootp_include->length)))
		return FALSE;
	if(h1->flags.bootp_include_vector && (h1->bootp_include_vector->length!=h2->bootp_include_vector->length || 
			bcmp(h1->bootp_include_vector->data,h2->bootp_include_vector->data,h1->bootp_include_vector->length)))
		return FALSE;
	if(h1->flags.ip_forwarding && (h1->ip_forwarding!=h2->ip_forwarding))
		return FALSE;
	if(h1->flags.ip_source_routing && (h1->ip_source_routing!=h2->ip_source_routing))
		return FALSE;
	if(h1->flags.ip_ttl && (h1->ip_ttl!=h2->ip_ttl))
		return FALSE;
	if(h1->flags.all_subnets_are_local && (h1->all_subnets_are_local!=h2->all_subnets_are_local))
		return FALSE;
	if(h1->flags.perform_mask_discovery && (h1->perform_mask_discovery!=h2->perform_mask_discovery))
		return FALSE;
	if(h1->flags.mask_supplier && (h1->mask_supplier!=h2->mask_supplier))
		return FALSE;
	if(h1->flags.router_solicitation_addr && (h1->router_solicitation_addr.s_addr!=h2->router_solicitation_addr.s_addr))
		return FALSE;
	if(h1->flags.trailer_encapsulation && (h1->trailer_encapsulation!=h2->trailer_encapsulation))
		return FALSE;
	if(h1->flags.tcp_ttl && (h1->tcp_ttl!=h2->tcp_ttl))
		return FALSE;
	if(h1->flags.tcp_keepalive_garbage && (h1->tcp_keepalive_garbage!=h2->tcp_keepalive_garbage))
		return FALSE;
	if(h1->flags.xwindows_font_server &&
		bcmp(h1->xwindows_font_server,h2->xwindows_font_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->xwindows_font_server->addrcount-1)))
		return FALSE;
	if(h1->flags.xwindows_display_manager &&
		bcmp(h1->xwindows_display_manager,h2->xwindows_display_manager, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->xwindows_display_manager->addrcount-1)))
		return FALSE;
	if(h1->flags.nisplus_server &&
		bcmp(h1->nisplus_server,h2->nisplus_server, sizeof(struct in_addr_list) 
			 + sizeof(struct in_addr) * (h1->nisplus_server->addrcount-1)))
		return FALSE;

	return TRUE;
}
#endif

/*
 * Local Variables:
 * tab-width: 4
 * c-indent-level: 4
 * c-argdecl-indent: 4
 * c-continued-statement-offset: 4
 * c-continued-brace-offset: -4
 * c-label-offset: -4
 * c-brace-offset: 0
 * End:
 */
