/* $Header: /usr/local/src/dhcpd/RCS/main.c,v 1.10 2009/09/01 22:30:29 root Exp $ */
/***********************************************************
        Copyright 1996 by Carnegie Mellon University

                      All Rights Reserved

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

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

/*
 * DHCP (dynamic host configuration protocol) server daemon.
 * BOOTP (bootstrap protocol) server daemon.
 *
 * Answers DHCP/BOOTP request packets from booting client machines.
 * See [SRI-NIC]<RFC>RFC1541.TXT for a description of the DHCP protocol.
 * See [SRI-NIC]<RFC>RFC951.TXT for a description of the protocol.
 * See [SRI-NIC]<RFC>RFC1048.TXT for vendor-information extensions.
 * See RFC 1395 for option tags 14-17.
 *
 * HISTORY: See ./RELEASE_NOTES
 *
 * BUGS: See man/dhcpd.8
 *
 */



#include "defs.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <pwd.h>
#include <grp.h>

#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>  /* inet_ntoa */

#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <netdb.h>
#include <assert.h>


#ifdef __linux__
/* Use sigaction to make signal last... */
inline void (*signal(int sig,void (*handler)(int)))(int) {
	struct sigaction so,sa = {NULL,0,SA_NOMASK|SA_RESTART,NULL};
	sa.sa_handler = handler;
	if (sigaction(sig,&sa,&so)<0) return NULL;
	return so.sa_handler;
}
#endif

#include "defaults.h"
#include "hash.h"
#include "bootp.h"
#include "bootpd.h"
#include "dhcpd.h"
#include "dhcp.h"
#include "hwaddr.h"
#include "dovend.h"
#include "getif.h"
#include "readfile.h"
#include "report.h"
#include "perm.h"
#include "tzone.h"
#include "patchlevel.h"
#include "dynamic.h"
#ifndef NO_SNMP
#include "snmp.h"
#endif
#include "utils.h"
#include "my_syslog.h"


/*
 * Externals, forward declarations, and global variables
 */

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

#define PRIVATE static
extern void dumptab P((char *));
extern time_t modtime;

PRIVATE void catcher P((int));
PRIVATE int chk_access P((char *, int32 *));
PRIVATE void dovend_cmu P((struct bootp *, struct host *));
PRIVATE int  dovend_rfc1048 P((struct bootp *, struct host *, int32));
PRIVATE void handle_reply P((void));
PRIVATE void bootp_request P((void));
PRIVATE void sendreply P((int forward, unsigned int32 dest_override));
PRIVATE void usage P((void));
void check_hwaddr P((struct bootp *));



#undef  P

/*
 * IP port numbers for client and server obtained from /etc/services
 */

u_short bootps_port, bootpc_port; /* host byte order */


/*
 * Internet socket and interface config structures
 */

struct sockaddr_in bind_addr;   /* Listening */
struct sockaddr_in recv_addr;   /* Packet source */
struct sockaddr_in send_addr;   /*  destination */


/*
 * option defaults
 */
int debug = 0;                                  	/* Debugging flag (level) */
boolean dont_fork=FALSE;							/* option flag */
boolean renew_unbound_statics = FALSE;				/* option flag */
int log_assignments=0;								/* option flag/level */
boolean log_expirations=FALSE;						/* option flag */
boolean bootp_siaddr_local_opt=TRUE;				/* option flag */ 
boolean compact_messages=FALSE;						/* option flag */
boolean ignore_cliid_option=FALSE;					/* option flag */
boolean cliid_must_match_chaddr=FALSE;				/* option flag */
boolean allow_serveripopts_in_rebind=FALSE;			/* option flag */
#ifdef CHECK_FILE_ACCESS
boolean check_file_access=TRUE;						/* option flag, default based on compile-time define */
#else
boolean check_file_access=FALSE;					/* option flag, default based on compile-time define */
#endif
boolean lone_dhcp_server=FALSE;						/* option flag */
boolean dont_look_for_default_bootfile=FALSE;		/* option flag */
boolean offer_terminates_unexpired_lease=FALSE;		/* option flag */

int client_steering_hash_source=CLIENT_STEERING_HASH_SOURCE_CHADDR; /* set in config file */
int client_steering_hash_function=CLIENT_STEERING_HASH_FUNCTION_POPX31; /* set in config file */
boolean client_steering_enable=FALSE;						/* any of the following are set */
boolean client_steering_enable_bootpreply_dynamic=FALSE;	/* feature set in config file */
boolean client_steering_enable_bootpreply_static=FALSE;		/* feature set in config file */
boolean client_steering_enable_dhcpoffer_static=FALSE;		/* feature set in config file */
boolean client_steering_enable_dhcpoffer_dynamic=FALSE;		/* feature set in config file */

boolean princeton_log_inits_with_unexpired_lease=FALSE;		/* undocumented feature set in config file */
boolean princeton_log_lease_offer_conversions=FALSE;		/* undocumented feature set in config file */
boolean princeton_log_client_steering=FALSE;				/* undocumented feature set in config file */


/* Undocumented NO_SNMP macro is used to exclude the SNMP agent code, e.g. if you have
 	trouble compiling it.  Since the code isn't there, we must not enable the agent, of course. */
#ifndef NO_SNMP
boolean enable_snmp_agent=TRUE;						/* option flag */
#else
boolean enable_snmp_agent=FALSE;					/* option flag */
#endif


unsigned int bootp_requests_received,bootp_replys_sent;

struct timeval actualtimeout = { /* one minute */
	60L,	/* tv_sec */
	0		/* tv_usec */
};

/*
 * General
 */


int s;                                                  /* Socket file descriptor */
char *pktbuf;                                   /* Receive packet buffer */
char *progname;
char *version;
pid_t pid;
u_char enet_all_zeros[6];
u_char htype_ethernet;

struct utsname my_uname;
char *hostname;

/* name of user and/or group to change to, NULL if no change */
char *runas_user = NULL;
char *runas_group = NULL;
/* uid and/or gid to change to, 0 if no change.  (since we start from 0/0, this is a safe flag value) */
uid_t runas_uid =0;
gid_t runas_gid =0;
struct passwd *passwd = NULL;
struct group *group = NULL;

FILE	*pid_fp;		/* pid file */

/* Flags set by signal catcher. */
int do_readtab = 0;
int do_readconf = 0;
int do_dumptab = 0;
int do_flush = 0;
int do_dumpbindings = 0;
int do_dumpstats = 0;

char *conffile = CONFIG_FILE;
char *bindingsdir = LASTBINDDIR;
char *chdir_path = CHDIR_PATH;

/*
 * Globals below are associated with the bootp database file (bootptab).
 */

char *bootptab = TAB_FILE;
char *bootpd_dump = DUMPTAB_FILE;
char *bootpd_dumpbind = DUMPBIND_FILE;
char *bootpd_dumpstats = DUMPSTATS_FILE;
char *pidfile = PID_FILE;



/*
 * Initialization such as command-line processing is done and then the
 * main server loop is started.
 */

/* is refresh_interval actually used anymore? */
int refresh_interval=1800;

/* is niceness actually used anymore? */
int niceness=0;

/* 0 == do not change */
int socket_rcvbuf_size = 0;

int
main(argc, argv)
	int argc;
	char **argv;
{
	struct timeval *timeout,poll;
	struct bootp *bp;
	struct servent *servp;
	struct hostent *hep;
	char *stmp;
	long ltmp;
	int n;
	socklen_t ba_len, ra_len;
	int nfound,readfds;
	int standalone;
	char rc;
#ifdef HAVE_STRUCT_ORTENTRY
	struct ortentry rt;
#else
	struct rtentry rt;
#endif
	struct sockaddr_in addr;
	int i,nfds,block;
	fd_set fds;
#ifndef NO_SNMP
	int snmp_socket;
#endif


#ifdef  SA_NOCLDSTOP    /* Have POSIX sigaction(2). */
	struct sigaction sa;
#endif

#ifndef NO_SNMP
	port_option=161;
#endif

	bzero(&poll,sizeof(poll));
	progname = strrchr(argv[0], '/');
	if (progname) progname++;
	else progname = argv[0];

	/*
	 * Initialize logging.
	 */
	init=1;
	report_init(0);                         /* uses progname */
	umask(022);

	/*
	 * Log startup
	 */
	report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
	report(LOG_INFO, "with %s", MODS);

	/* Debugging for compilers with struct padding. */
	assert(sizeof(struct bootp) == BP_MINPKTSZ);

	/* Get space for receiving packets and composing replies. */
	pktbuf = (char *) smalloc(MAX_MSG_SIZE);
	bp = (struct bootp *) pktbuf;

	/*
	 * Check to see if a socket was passed to us from inetd.
	 *
	 * Use getsockname() to determine if descriptor 0 is indeed a socket
	 * (and thus we are probably a child of inetd) or if it is instead
	 * something else and we are running standalone.
	 * Note that we no longer recommend or support starting up via inetd.
	 */
	s = 0;
	ba_len = sizeof(bind_addr);
	bzero((char *) &bind_addr, ba_len);
	errno = 0;
	standalone = TRUE;
	if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
		/*
		 * Descriptor 0 is a socket.  Assume we are a child of inetd.
		 */
		if (bind_addr.sin_family == AF_INET) {
			standalone = FALSE;
			bootps_port = ntohs(bind_addr.sin_port);
		} else if (debug > 15) { 
			report(LOG_INFO, "getsockname: descriptor 0 is a socket, but not an INET socket...");
			report(LOG_INFO, "so we'll assume we're running standlone (not from inetd).");
		}
	}

	/*
	 * Set defaults that might be changed by option switches.
	 */
	stmp = NULL;
	timeout = &actualtimeout;

	if (uname(&my_uname) < 0) {
		fprintf(stderr, "dhcpd: can't get hostname\n");
		exit(1);
	}
	hostname = my_uname.nodename;

	bzero(&my_ip_addr, sizeof(my_ip_addr));

	/* init some Ethernet addresses we'll want.  */
	bzero((char *) enet_all_zeros, sizeof(enet_all_zeros));
	htype_ethernet = 0x01;	/* IANA htype */

	/*
	 * Read switches.
	 */
	for (argc--, argv++; argc > 0; argc--, argv++) {
		if (argv[0][0] != '-')
			break;
		switch (argv[0][1]) {

		case 'a':								/* turn on log_assignments */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else if (!argv[1] || (argv[1] && argv[1][0] == '-')) {
				/*
				 *  Backwards-compatible behavior:
				 *  no parameter, so just increment the log_assignments flag.
				*/
				log_assignments++;
				if (debug > 4)
					report(LOG_INFO, "-a specified, will log address assignments");
				break;
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
				report(LOG_ERR, "%s: invalid log_assignments level", progname);
				break;
			}
			log_assignments = n;
			if (debug > 4)
				report(LOG_INFO, "-a %d specified, will log address assignments", log_assignments);
			break;

		case 'A':								/* disable check file access feature */
				check_file_access = FALSE;
				if (debug > 4)
					report(LOG_INFO, "-A specified, disabling Check File Access feature");
				break;

		case 'b':								/* last binds dir */
			if(argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if(!stmp || (stmp[0] != '/')) {
				report(LOG_ERR, "invalid bindings dir specification, must be an absolute pathname");
				break;
			}
			bindingsdir=stmp;
			break;

		case 'B':								/* bind.dump file */
			if(argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if(!stmp || (stmp[0] != '/')) {
				report(LOG_ERR, "bindings dump file specification, must be an absolute pathname");
				break;
			}
			bootpd_dumpbind=stmp;
			break;

		case 'c':                               /* chdir_path */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp || (stmp[0] != '/')) {
				report(LOG_ERR, "dhcpd: invalid chdir specification, must be an absolute pathname");
				break;
			}
			chdir_path = stmp;
			break;

		case 'C':								/* add compact log messages */
				compact_messages = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-C specified, will add compact log messages");
				break;

		case 'd':                               /* debug level */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else if (!argv[1] || (argv[1] && argv[1][0] == '-')) {
				/*
				 * Backwards-compatible behavior:
				 * no parameter, so just increment the debug flag.
				 */
				debug++;
				report(LOG_INFO, "debug level %d", debug);
				break;
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
				report(LOG_ERR, "%s: invalid debug level", progname);
				break;
			}
			debug = n;
			report(LOG_INFO, "debug level %d", debug);
			break;

		case 'e':								/* turn on log_expirations */
				log_expirations = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-e specified, will log lease expirations");
				break;

		case 'E':	/* set SO_RCVBUF */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp || (sscanf(stmp, "%i", &n) != 1) || (n < 0)) {
				report(LOG_ERR, "%s: invalid socket receive buffer size", progname);
				break;
			}
			socket_rcvbuf_size = n;
			if (debug > 4)
				report(LOG_INFO, "-E specified, will use socket_rcvbuf_size = %i", socket_rcvbuf_size);
			break;

		case 'f':								/* don't fork */
				dont_fork = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-f specified, will not fork");
				break;

		case 'F':								/* enable the "Don't Look for Default Bootfile" feature */
				dont_look_for_default_bootfile = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-F specified, enabling the 'Don't Look for Default Bootfile' feature");
				break;

		case 'G':								/* groupname or gid to run as */
			if(argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if(!stmp) {
				report(LOG_ERR, "invalid -G option, must specify a valid groupname or gid");
				exit(1);
			}
			runas_group=stmp;
			/* first try to look it up as a name */
			group = getgrnam(runas_group);
			if (group) {
				runas_gid = group->gr_gid;
			} else { /* try to look it up as an id */
				if (sscanf(stmp, "%d", &n) != 1) { /* ensure is all numeric before converting it */
					report(LOG_ERR, "invalid -G option, must specify a valid groupname or gid");
					exit(1);
				}
				errno=0;
				ltmp = atol(runas_group);
				if (errno) {
					report(LOG_ERR, "invalid -G option, must specify a valid groupname or gid");
					exit(1);
				}
				runas_gid = ltmp; /* force implicit conversion from long to uid_t */
				group = getgrgid(runas_gid);
				if (!group) {
					report(LOG_ERR, "invalid -G option, must specify a valid groupname or gid");
					exit(1);
				}
			}
			/* at this time runas_gid contains a valid gid.  */
			break;

		case 'h':                               /* override hostname */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp) {
				report(LOG_ERR, "dhcpd: missing hostname");
				break;
			}
			hostname = stmp;
			if (debug > 4)
				report(LOG_INFO, "-h specified, will use hostname '%s'", hostname);
			break;

		case 'H':								/* Client ID (if present) must match htype/chaddr */
				if (ignore_cliid_option) {
					report(LOG_ERR, "dhcpd: -H and -I cannot both be specified.");
					exit(1);
				}
				cliid_must_match_chaddr = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-H specified, will ignore requests that specify a Client ID that differs from htype/chaddr");
				break;

		case 'I':								/* ignore cliid option */
				if (cliid_must_match_chaddr) {
					report(LOG_ERR, "dhcpd: -H and -I cannot both be specified.");
					exit(1);
				}
				ignore_cliid_option = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-I specified, will ignore Client ID option");
				break;

		case 'L':								/* enable lone dhcp server feature */
				lone_dhcp_server = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-L specified, enabling Lone DHCP Server feature");
				break;

		case 'm':                               /* override value for my_ip_addr */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp) {
				report(LOG_ERR, "dhcpd: -m option requires an IP address");
				break;
			}
			if ((my_ip_addr.s_addr = inet_addr(stmp)) == -1) {
				/* note the syntactically valid address "255.255.255.255" will also return -1.
				   OK to error on this here, since that's not a reasonable value for this purpose. */
				report(LOG_ERR, "cannot parse -m IP address: %s", stmp);
				bzero(&my_ip_addr, sizeof(my_ip_addr)); /* reset */
			} else {
				if (debug > 4)
					report(LOG_INFO, "-m specified, will use my_ip_addr = %s", stmp);
			}
			break;

		/* is niceness actually used anymore? */
		case 'n':                               /* niceness */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
				report(LOG_ERR,"%s: invalid nice\n", progname);
				break;
			}
			niceness = n;
			break;

		case 'p':                               /* snmp port */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
				report(LOG_ERR, "%s: invalid snmp port", progname);
				break;
			}
#ifdef NO_SNMP
			report(LOG_ERR, "ignoring -p option, since SNMP support was not included at compile-time");
#else
			if (n == 0) {
				enable_snmp_agent = FALSE;
				if (debug > 4)
					report(LOG_INFO, "-p 0 specified, will disable SNMP agent");
			} else {
				port_option = n;
				if (debug > 4)
					report(LOG_INFO, "-p specified, will try to use port %d for SNMP agent", n);
			}
#endif
			break;

		case 'P':								/* dhcpd.pid file */
			if(argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if(!stmp || (stmp[0] != '/')) {
				report(LOG_ERR, "pid file specification, must be an absolute pathname");
				break;
			}
			pidfile=stmp;
			break;


		/* is refresh_interval actually used anymore? */
		case 'r':                               /* update interval */
			if (argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
				report(LOG_ERR, "%s: invalid refresh time", progname);
				break;
			}
			refresh_interval = n;
			break;

		case 'R':								/* allow_serveripopts_in_rebind option */
				allow_serveripopts_in_rebind = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-R specified, enabling Allow 'Server IP Address' Option in RENEWAL/REBINDING States feature");
				break;

		case 's':								/* dhcpd.statistics file */
			if(argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if(!stmp || (stmp[0] != '/')) {
				report(LOG_ERR, "statistics dump file specification, must be an absolute pathname");
				break;
			}
			bootpd_dumpstats=stmp;
			break;

		case 'S':								/* disable BootP siaddr Local Optimization */
				bootp_siaddr_local_opt = FALSE;
				if (debug > 4)
					report(LOG_INFO, "-S specified, will disable BootP siaddr Local Optimization");
				break;

		case 'T':								/* enable Offer Terminates Unexpired Lease feature */
				offer_terminates_unexpired_lease = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-T specified, will enable Offer Terminates Unexpired Lease feature");
				break;

		case 'u':								/* allow dhcp renewal of unbound statically-assigned addresses */
				renew_unbound_statics = TRUE;
				if (debug > 4)
					report(LOG_INFO, "-u specified, will allow renewal of unbound statically-assigned addresses");
				break;

		case 'U':								/* username or uid to run as */
			/* only available if we can use seteuid() to change back to our original euid from time to time */
#ifndef HAVE_SETEUID
			report(LOG_ERR, "-U option not available because this system does not have seteuid()");
			exit(1);
#endif
			if(argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if(!stmp) {
				report(LOG_ERR, "invalid -U option, must specify a valid username or uid");
				exit(1);
			}
			runas_user=stmp;
			/* first try to look it up as a name */
			passwd = getpwnam(runas_user);
			if (passwd) {
				runas_uid = passwd->pw_uid;
			} else { /* try to look it up as an id */
				if (sscanf(stmp, "%d", &n) != 1) { /* ensure is all numeric before converting it */
					report(LOG_ERR, "invalid -U option, must specify a valid username or uid");
					exit(1);
				}
				errno=0;
				ltmp = atol(runas_user);
				if (errno) {
					report(LOG_ERR, "invalid -U option, must specify a valid username or uid");
					exit(1);
				}
				runas_uid = ltmp; /* force implicit conversion from long to uid_t */
				passwd = getpwuid(runas_uid);
				if (!passwd) {
					report(LOG_ERR, "invalid -U option, must specify a valid username or uid");
					exit(1);
				}
			}
			/* at this time runas_uid contains a valid uid.  */
			break;

		case 'x':								/* dhcpd.conf file */
			if(argv[0][2]) {
				stmp = &(argv[0][2]);
			} else {
				argc--;
				argv++;
				stmp = argv[0];
			}
			if(!stmp || (stmp[0] != '/')) {
				report(LOG_ERR, "invalid configuration file specification, must be an absolute pathname");
				break;
			}
			conffile=stmp;
			break;

		default:
			report(LOG_ERR, "%s: unknown switch: -%c", progname, argv[0][1]);
			usage();
			break;

		} /* switch */
	} /* for args */

	/*
	 * Override default file names if specified on the command line.
	 */
	if (argc > 0) {
		bootptab = argv[0];
		if (debug > 4)
			report(LOG_INFO, "will use bootptab file '%s'", bootptab);
	}

	if (argc > 1) {
		bootpd_dump = argv[1];
		if (debug > 4)
			report(LOG_INFO, "will use bootpd_dump file '%s'", bootptab);
	}

	if (! my_ip_addr.s_addr) { /* user has not already overridden with -m */
		hep = gethostbyname(hostname);
		if (!hep) {
			report(LOG_ERR, "can't determine IP address for my hostname '%s'\n", hostname);
			exit(1);
		}
		bcopy(hep->h_addr, (char *)&(my_ip_addr.s_addr), sizeof (my_ip_addr.s_addr));
		if (debug > 4)
			report(LOG_INFO, "will use my_ip_addr = %s", inet_ntoa(my_ip_addr));
	}
	/* purify will flag every read of my_ip_addr as a UMR, but that's bogus.
	   Maybe it's just confused since it is really a union. */

		/*
		 * Go into background and disassociate from controlling terminal.
		 */
		if (!dont_fork) {
			if (fork())
				exit(0);
#ifdef  NO_SETSID
			setpgrp(0,0);
#ifdef TIOCNOTTY
			n = open("/dev/tty", O_RDWR);
			if (n >= 0) {
				ioctl(n, TIOCNOTTY, (char *) 0);
				(void) close(n);
			}
#endif  /* TIOCNOTTY */
#else   /* SETSID */
			if (setsid() < 0)
				perror("setsid");
#endif  /* SETSID */
		} /* if debug < 3 */

	/* Before writing our pid:
		 ignore USR1 and USR2 and WINCH until our internal data structs are ready for them (dumping data)
		 set up handlers for ABRT (IOT) and SYS (change debuglevel)
	*/
#ifdef SA_NOCLDSTOP			/* Have POSIX sigaction(2) */
	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	if (sigaction(SIGUSR1, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
	if (sigaction(SIGUSR2, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
#ifdef SIGWINCH			/* SIGWINCH not required by POSIX.1 */
	if (sigaction(SIGWINCH, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
#endif	/* SIGWINCH */
	sa.sa_handler = catcher;
#ifdef SIGABRT			/* SIGABRT replaces SIGIOT on newer systems */
	if (sigaction(SIGABRT, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
#else	/* SIGABRT*/
	if (sigaction(SIGIOT, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
#endif	/* SIGABRT*/
#ifdef SIGSYS			/* SIGSYS not required by POSIX.1 */
	if (sigaction(SIGSYS, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
#endif	/* SIGSYS*/
#else	/* SA_NOCLDSTOP */		/* Do not have POSIX sigaction(2) */
	if ((int) signal(SIGUSR1, SIG_IGN) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
	if ((int) signal(SIGUSR2, SIG_IGN) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
#ifdef SIGWINCH			/* SIGWINCH not required by POSIX.1 */
	if ((int) signal(SIGWINCH, SIG_IGN) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
#endif	/* SIGWINCH */
#ifdef SIGABRT			/* SIGABRT replaces SIGIOT on newer systems */
	if ((int) signal(SIGABRT, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
#else	/* SIGABRT*/
	if ((int) signal(SIGIOT, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
#endif	/* SIGABRT*/
#ifdef SIGSYS			/* SIGSYS not required by POSIX.1 */
	if ((int) signal(SIGSYS, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
#endif	/* SIGSYS*/
#endif	/* SA_NOCLDSTOP */

	pid = getpid();
	/* write the pid file as soon as possible after forking */
	if (standalone) {
		pid_fp = open_for_writing(pidfile);
		if (pid_fp != NULL) {
			fprintf(pid_fp, "%d\n", pid);
			(void) fclose(pid_fp);
		} else {
			report(LOG_ERR, "could not open pid file %s for writing", pidfile);
			exit(1);
		}
	}

	/* Set the cwd (i.e. to /tftpboot) */
	if (chdir_path) {
		if (chdir(chdir_path) < 0) {
			report(LOG_ERR, "could not change to directory: %s", chdir_path);
			exit(1);
		} else
			if (debug > 4)
				report(LOG_INFO, "changed to directory %s", chdir_path);
	}

	/* Get the timezone. */
	tzone_init();


	/* Allocate hash tables. */
	rdtab_init();
	readtab(1);                                     /* force read */
	init_dhcp();

		/*
		 * Create a socket.
		 */
		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
			report(LOG_ERR, "socket: %s", get_network_errmsg());
			exit(1);
		}

		if (socket_rcvbuf_size) {
			/* User wants to override default socket receive buffer size. */
			int n;
			socklen_t len;

			len = sizeof(n);
			if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, &len) == -1) {
				report(LOG_WARNING, "unable to determine original socket receive buffer size: getsockopt: %s", get_errmsg());
			} else  {
				if (debug > 4)
					report(LOG_INFO, "original socket receive buffer size: %i", n);
			}

			if (debug > 4)
				report(LOG_INFO, "requesting socket receive buffer size %i", socket_rcvbuf_size);

			len = sizeof(socket_rcvbuf_size);
			if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &socket_rcvbuf_size, len) == -1) {
				report(LOG_ERR, "unable to change socket receive buffer size to %i: setsockopt: %s", socket_rcvbuf_size, get_errmsg());
			} else {
				/* The system may not have obeyed the change, or may have modified it.  
				   So retrieve current value and display it.
				*/
				len = sizeof(n);
				if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, &len) == -1) {
					report(LOG_WARNING, "unable to determine resulting socket receive buffer size: getsockopt: %s", get_errmsg());
				} else  {
					if (debug > 4)
						report(LOG_INFO, "resulting socket receive buffer size: %i", n);
				}
			}
		}

		/*
		 * Get server's listening port number
		 */
		servp = getservbyname("bootps", "udp");
		if (servp) {
			bootps_port = ntohs((u_short) servp->s_port);
		} else {
			bootps_port = (u_short) IPPORT_BOOTPS;
			report(LOG_ERR,
				   "udp/bootps: unknown service -- assuming port %d",
				   bootps_port);
		}

		/*
		 * Bind socket to BOOTPS port.
		 */
		bind_addr.sin_family = AF_INET;
		bind_addr.sin_addr.s_addr = INADDR_ANY;
		bind_addr.sin_port = htons(bootps_port);
		if (bind(s, (struct sockaddr *) &bind_addr,
				 sizeof(bind_addr)) < 0)
		{
			report(LOG_ERR, "bind: %s", get_network_errmsg());
			exit(1);
		}

	/*
	 * Get destination port number so we can reply to client
	 */
	servp = getservbyname("bootpc", "udp");
	if (servp) {
		bootpc_port = ntohs(servp->s_port);
	} else {
		report(LOG_ERR,
			   "udp/bootpc: unknown service -- assuming port %d",
			   IPPORT_BOOTPC);
		bootpc_port = (u_short) IPPORT_BOOTPC;
	}

 bootp_requests_received=bootp_replys_sent=0;
	/*
	 * We often need to broadcast, so enable it.
	 */
	n = 1;
	if (setsockopt(s,SOL_SOCKET,SO_BROADCAST, (char *) &n, sizeof(n)) < 0) {
		report(LOG_ERR, "setsockopt: %s\n", get_errmsg());
		exit(1);
	}

	/* Initialize interfaces linked list */
	init_interfaces(s);

	/*
	 * Set up signals to read the table, or dump the table, bindings, stats
	 */
#ifdef  SA_NOCLDSTOP    /* Have POSIX sigaction(2). */
	sa.sa_handler = catcher;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	if (sigaction(SIGHUP, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
	if (sigaction(SIGUSR1, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
	if (sigaction(SIGUSR2, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
#ifdef SIGWINCH		/* SIGWINCH not required by POSIX.1 */
	if (sigaction(SIGWINCH, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
#endif	/* SIGWINCH */
#else   /* SA_NOCLDSTOP */	/* Do not have POSIX sigaction(2). */
	/* Old-fashioned UNIX signals */
	if ((int) signal(SIGHUP, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
	if ((int) signal(SIGUSR1, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
	if ((int) signal(SIGUSR2, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
#ifdef SIGWINCH		/* SIGWINCH not required by POSIX.1 */
	if ((int) signal(SIGWINCH, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
#endif 	/* SIGWINCH */
#endif  /* SA_NOCLDSTOP */

	/* set up signals to quit gracefully */
#ifdef  SA_NOCLDSTOP    /* Have POSIX sigaction(2). */
	sa.sa_handler = catcher;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	if (sigaction(SIGTERM, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
	if (sigaction(SIGINT, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
	if (sigaction(SIGQUIT, &sa, NULL) < 0) {
		report(LOG_ERR, "sigaction: %s", get_errmsg());
		exit(1);
	}
#else   /* SA_NOCLDSTOP */
	if ((int) signal(SIGTERM, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
	if ((int) signal(SIGINT, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
	if ((int) signal(SIGQUIT, catcher) < 0) {
		report(LOG_ERR, "signal: %s", get_errmsg());
		exit(1);
	}
#endif  /* SA_NOCLDSTOP */



/* We'll be sending broadcast packets to IP dst 255.255.255.255.
   Some platforms need a host route to that dst to be able to send those
   packets.  So if it looks like this systems has route.h, we'll try to
   add a host route to that dst.  If we fail, we'll just note it, and
   continue, and hope for the best.  
   This code isn't as portable as it should be yet.  If it won't compile
   on your system, define the NOBROADCAST symbol in defaults.h to
   exclude this code.  Then, if your system needs that host route to
   let you send broadcast packets to 255.255.255.255, add it by hand
   before starting the dhcpd daemon. */
#ifdef HAVE_NET_ROUTE_H
#ifndef NOBROADCAST
#ifndef _AIX
	bzero(&rt, sizeof(rt));
	rt.rt_flags=RTF_HOST;
	bzero(&addr,sizeof(addr));
	addr.sin_family=AF_INET;
	addr.sin_addr.s_addr=0xffffffff;
	bcopy(&addr,&rt.rt_dst,sizeof(addr));
	bzero(&addr,sizeof(addr));
	addr.sin_family=AF_INET;
	addr.sin_addr.s_addr=my_ip_addr.s_addr;
	bcopy(&addr,&rt.rt_gateway,sizeof(addr));

	rc=ioctl(s,SIOCADDRT,&rt);
	if(rc && (errno != EEXIST))
		perror("Could not add route 255.255.255.255");
#endif
#endif
#endif


	 i=strlen(VERSION);
	 version=(char *)smalloc(i+3);
	 version[0]=0;
	 strcpy(version,VERSION);
	 version[i]='.';
	 version[i+1]=PATCHLEVEL+'0';
	 version[i+2]=0;

#ifndef NO_SNMP
	if (enable_snmp_agent) {
		rc=snmp_init(&snmp_socket);
		if(rc)
			exit(1);
	}
#endif

	/* we've finished all the startup operations that can require root privs.
	   if user specified we should run with different uid and/or gid, switch now.
	   We can only change our effective uid, not real uid, since we need to be able to
	   switch back to root whenever we need to prime the IP ARP table. Sigh. */

	if (runas_gid) {
		rc=change_eid(2, 0, runas_gid);
		if (rc)
			exit(1); /* function already logged err msg */
		if (debug > 4)
			report(LOG_INFO, "changed to gid %d", runas_gid);
	}
	if (runas_uid) {
		rc=change_eid(1, runas_uid, 0);
		if (rc)
			exit(1); /* function already logged err msg */
		if (debug > 4)
			report(LOG_INFO, "changed to uid %d", runas_uid);
	}

	init=0;

	/*	The initial read of bootptab above (in readtab()) skipped calling delete_changed()
		because init was true.  (It had to do so because not all the data struct(s) examined
		by delete_changed() had been initialized yet.)
		At this point all the data structs have been initialized, so it is safe to call delete_changed().
		Do so, to remove bindings that are no longer consistent with the current bootptab.
	*/
	delete_changed();

	starttime=time(0);

	for (;;) { /* MAIN LOOP */
		struct timeval tv;

		FD_ZERO(&fds);
		FD_SET(s,&fds); /* s is the dhcp server socket descriptor */
		nfds=s+1;
#ifndef NO_SNMP
		if (enable_snmp_agent) {
			FD_SET(snmp_socket,&fds);
			if(snmp_socket>=nfds)
				nfds=snmp_socket+1;
		}
#endif

		if (timeout)
			tv = *timeout;

		block=0;
#ifndef NO_SNMP
		if (enable_snmp_agent) {
			snmp_select_info(&nfds,&fds,&tv,&block);
		}
#endif
		nfound = select(nfds, &fds, NULL, NULL, &tv);

		now=time(0);
		readtab(0);               /* maybe re-read bootptab */
	 	readconf(0);


		readfds = 1 << s; /* s is the dhcp server socket descriptor */
		if (select(s+1,(fd_set*)&readfds,NULL,NULL,&poll)==1) { /* poll just the dhcp server socket */
			/* dhcp server socket is ready for reading */
			now=time(0);
			ra_len = sizeof(recv_addr);
			n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
						 (struct sockaddr *) &recv_addr, &ra_len);
			if(n>0){
				if (debug > 2) {
					report(LOG_INFO, "Received packet, IPsrc=%s, %d bytes",
						   inet_ntoa(recv_addr.sin_addr),n);
				}
				/* DHCP packets can have vends < 64   *OT* */
				if ((unsigned)n < sizeof(struct bootp)-59) {
					if (debug > 2) {
						report(LOG_NOTICE, "  packet is too short, ignoring");
					}
					continue; /* back to MAIN LOOP */
				}
				pktlen = n;
				if (debug > 3)
					report(LOG_INFO, " giaddr=%s", inet_ntoa(bp->bp_giaddr));


				switch (bp->bp_op) {
				case BOOTREQUEST:
					switch (rc=dhcp_msg_type(bp)) {
					case 0: bootp_request(); break;
					case DHCPDISCOVER:      dhcp_discover(bp); break;
					case DHCPREQUEST:       dhcp_request(bp); break;
					case DHCPRELEASE:        dhcp_release(bp); break;
					case DHCPDECLINE:        dhcp_decline(bp); break;
					case DHCPINFORM:        dhcp_inform(bp); break;
					default: 
						if (debug > 2)
							report(LOG_INFO, "  can't determine message type, ignoring");
						break;
					}
					break;
				case BOOTREPLY:
					handle_reply(); 
					break;
				default :if (debug > 2)
							report(LOG_INFO,"  unknown bootp/dhcp opcode, ignoring");
				}
#ifdef UNKNOWN
				check_hwaddr(bp);
#endif
			}
		}
		
#ifndef NO_SNMP
		if (enable_snmp_agent) {
			readfds = 1 << snmp_socket;
			if (select(snmp_socket+1,(fd_set*)&readfds,NULL,NULL,&poll)==1) { /* poll just the snmp agent server socket */
				/* snmp agent server socket is ready for reading */
				snmp_read_packet(snmp_socket,NULL);
			}
			snmp_read(&fds);
			snmp_timeout();
		}
#endif

		if(now>=(unsigned)dhcp_alarm)
 			dhcp_garbage_collect();
		if(now>=flush_alarm)
			flush_disk();
		if (do_readtab) {
			do_readtab = 0;
			do_readconf = 0;
			readtab(1);             /* force read */
		}
		if (do_dumptab) {
			do_dumptab = 0;
			dumptab(bootpd_dump);
		}
		if (do_dumpbindings) {
			do_dumpbindings = 0;
			dumpbindings(bootpd_dumpbind);
		}
		if (do_dumpstats) {
			do_dumpstats = 0;
			dumpstats(bootpd_dumpstats);
		}
		if (do_readconf) {
			do_readconf = 0;
			readconf(1);             /* force read */
		}
		if (do_flush) {
			do_flush = 0;
			flush_disk();
		}

	} /* MAIN LOOP */
}




/*
 * Print "usage" message and exit
 */

PRIVATE void
usage()
{
	fprintf(stderr,
			"usage:  dhcpd [-a log_assignmentslevel] [-A] [-b lastbindings-directory] [-B bindingsdumpfile] [-c chdir-path] [-C] [-d debuglevel] [-e] [-E so_rcvbuf] [-f] [-F] [-G groupname|gid] [-h hostname] [-H|-I] [-L] [-m my_ip_addr] [-p snmp-port] [-P pidfile] [-s statisticsdumpfile] [-S] [-u] [-U username|uid] [-x conffile] [bootptabfile [dumpfile]]\n");
	fprintf(stderr, "\t -a \tset address log-assignments level (0-1)\n");
	fprintf(stderr, "\t -A \tdisable the Check File Access feature\n");
	fprintf(stderr, "\t -b \tspecify lastbindings directory\n");
	fprintf(stderr, "\t -B \tspecify bindings dumpfile\n");
	fprintf(stderr, "\t -c \tset current working directory\n");
	fprintf(stderr, "\t -C \tadd some compact messages when debugging is enabldd\n");
	fprintf(stderr, "\t -d \tset debug level (0-20)\n");
	fprintf(stderr, "\t -e \tlog a message whenever a lease is expired or otherwise removed\n");
	fprintf(stderr, "\t -E \tspecify size of the socket receive buffer\n");
	fprintf(stderr, "\t -f \tdon't fork (stay in foreground)\n");
	fprintf(stderr, "\t -F \tenable the Don't Look for Default Bootfile feature\n");
	fprintf(stderr, "\t -G \tspecify effective groupname or gid\n");
	fprintf(stderr, "\t -H \tignore request if Client ID Option (when specified) doesn't match htype/chaddr\n");
	fprintf(stderr, "\t -h \tspecify server hostname\n");
	fprintf(stderr, "\t -I \tignore Client Identifier Options\n");
	fprintf(stderr, "\t -L \tenable the Lone DHCP Server feature\n");
	fprintf(stderr, "\t -m \toverride my_ip_addr\n");
	fprintf(stderr, "\t -p \tspecify SNMP server agent port\n");
	fprintf(stderr, "\t -P \tspecify PIDfile\n");
	fprintf(stderr, "\t -R \tenable Allow Server IP Address Option in RENEWAL/REBINDING States feature\n");
	fprintf(stderr, "\t -s \tspecify statistics dumpfile\n");
	fprintf(stderr, "\t -S \tdisable BOOTPREPLY siaddr Local Optimization feature\n");
	fprintf(stderr, "\t -T \tenable Offer Terminates Unexpired Lease feature\n");
	fprintf(stderr, "\t -u \tenable Renew Unbound Statics feature\n");
	fprintf(stderr, "\t -U \tspecify effective username or uid\n");
	fprintf(stderr, "\t -x \tspecify conf file\n");
	exit(1);
}

/* Signal catchers */
PRIVATE void
catcher(sig)
	int sig;
{
	if ((sig==SIGTERM) || (sig==SIGQUIT) || (sig==SIGINT)) {	/* quit gracefully */
		flush_disk();
		exit(0);
	}
	if (sig == SIGHUP)	/* set flag to force re-read of bootptab and conf */
		do_readtab = 1;
	if (sig == SIGUSR1)	/* set flag to dump parsed bootptab */
		do_dumptab = 1;
	if (sig == SIGUSR2)	/* set flag to dump current bindings */
		do_dumpbindings = 1;
#ifdef SIGWINCH			/* SIGWINCH not required by POSIX.1 */
	if (sig == SIGWINCH) /* set flag to dump statistics */
		do_dumpstats = 1;
#endif
#ifdef SIGABRT			/* SIGABRT replaces SIGIOT on newer systems */
	if (sig == SIGABRT)	/* increment debug level */
		debug++;
#else	/* SIGABRT*/
	if (sig == SIGIOT)	/* increment debug level */
		debug++;
#endif	/* SIGABRT*/
#ifdef SIGSYS			/* SIGSYS not required by POSIX.1 */
	if (sig == SIGSYS)	/* turn off debugging */
		debug = 0;
#endif
	

#if     !defined(SA_NOCLDSTOP) && defined(SYSV)
	/* For older "System V" derivatives with no sigaction(). */
	signal(sig, catcher);
#endif

}



/*
 * Process BOOTREQUEST packet.
 *
 * Note:  This version of the bootpd.c server never forwards
 * a request to another server.  That is the job of a gateway
 * program such as the "bootpgw" program included here.
 *
 * (Also this version does not interpret the hostname field of
 * the request packet;  it COULD do a name->address lookup and
 * forward the request there.)
 */
PRIVATE void
bootp_request()
{
	struct bootp *bp = (struct bootp *) pktbuf;
	struct host *hp = NULL;
	struct host *real = NULL;
	struct host dummyhost;
	unsigned hlen, hashcode, cablehashcode, iphashcode, rc;
	unsigned int32 dest=0; /* network byte order */
	int32 bootsize=0;
	struct client_info cl;
	struct host *hp2, *hp3;
	boolean new_dynamic = FALSE;
	char registered, roaming, deny_dynamic;
	int i,len;
	struct cable *c;
	int client_steering_delay_for_this_client = 0; /* assume Client Steering feature is disabled */

	roaming = registered = deny_dynamic =0;

	/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */

  	bootp_requests_received++;

	if (debug>1)
		report(LOG_INFO," processing BOOTPREQUEST");

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

	if (debug>1)
		report(LOG_INFO,"  Client %s address: %s", netname(bp->bp_htype), haddrtoa(bp->bp_chaddr, bp->bp_hlen));

	/*
	 * If the servername field is set, compare it against us.
	 * If we're not being addressed, ignore this request.
	 * If the server name field is null, throw in our name.
	 */
	if (strlen(bp->bp_sname)) {
		/* Guard against bp_sname not being null-terminated.  Since RFC says bp_sname
		is all null or is null-terminated string, it is safe to overwrite last char of bp_sname with null. */
		bp->bp_sname[BP_SNAME_LEN-1] = '\0';
		if (debug > 3)
			report(LOG_INFO,"  client specified sname '%s'", bp->bp_sname);
		if (strcmp(bp->bp_sname, hostname)) {
			if (debug > 2)
				report(LOG_INFO,"  sname is not me, ignoring");
			/* XXX - Is it correct to ignore such a request? -gwr */
			return;
		}
	} /* BUG - test this works - clean the sname field before writing into it.
		Make sure we get good results for clients that both do and do not specify
		this field
	 else {
		strcpy(bp->bp_sname, hostname);
	} */
	/* always stick our name in */
	/* note strncpy will right-pad with nul, clearing out any garbage - good */
	strncpy(bp->bp_sname, hostname, BP_SNAME_LEN);
	

	/* construct the struct cl from the info in the packet */
	bzero(&cl,sizeof(cl));
	cl.cliid=(struct shared_bindata*)smalloc(sizeof(struct shared_bindata)+bp->bp_hlen);
	cl.cliid->length=bp->bp_hlen+1;
	cl.cliid->linkcount=1;
	cl.cliid->data[0]=bp->bp_htype;
	bcopy(bp->bp_chaddr,cl.cliid->data+1,cl.cliid->length-1);

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

	cl.cliidhw=cl.cliid;
	cl.cliidhw->linkcount++;

	len=i=4;
	/* POSSIBLE BUG: purify says bp_vend[i] is sometimes not init'd, when we are 300 bytes into the 
	   1536-byte malloc'd pktbuf. Maybe this is a boundary condition?  Maybe the condition
	   "bp->bp_vend[i]!=TAG_END" needs to be at the *start* of complex expr, to let the
	   short circuit '&&' do its work?   Let's try making that change.... */
	/* while(bp->bp_vend[i]!=TAG_END && i<100 && (i-4+VEND_OFFSET)<pktlen) LEFTCURLYBRACE */
	while(i<100 && (i-4+VEND_OFFSET)<pktlen && (bp->bp_vend[i]!=TAG_END)){
		if(bp->bp_vend[i]==TAG_PAD)
			i++;
		else {
			i+=1+bp->bp_vend[++i];
			len=i+1;
		}
	}
	/* POSSIBLE BUG: purify says bp_vend[i] is sometimes not init'd, when we are 300 bytes into the 
	   1536-byte malloc'd pktbuf. Maybe this is a boundary condition when the loop we just completed 
	   reached the end of the data we received from the client? */
	if(bp->bp_vend[i]==TAG_END)
		len=i+1;

	if((len+VEND_OFFSET-4)>pktlen)
		len=(pktlen-VEND_OFFSET);
	cl.vend=(struct shared_bindata*)smalloc(sizeof(struct shared_bindata)+len-1);
	bzero(cl.vend,len);
	cl.vend->length=len;
	cl.vend->linkcount=1;
	bcopy(bp->bp_vend,cl.vend->data,len);

	if (client_steering_enable)
		client_steering_delay_for_this_client = client_steering_delay(&cl);
	/* else client_steering_delay_for_this_client remains 0 */


	/* Convert the request packet into a reply, in case we decide to respond */
	bp->bp_op = BOOTREPLY;

	if (bp->bp_ciaddr.s_addr == 0) { /* normally, client doesn't know his IP address, so we search by hardware address. */

		/* make sure chaddr length OK */
		hlen = haddrlength(bp->bp_htype);
		if (hlen != bp->bp_hlen) {
			if (debug > 2)
				report(LOG_NOTICE, "  bad chaddr len from from %s address %s; hp_len is %d, should be %d; ignoring bootp client",
				   	netname(bp->bp_htype), haddrtoa(bp->bp_chaddr, hlen), bp->bp_hlen, hlen);
		    free_client_info(&cl);
			return;
		}

		if (bp->bp_htype == HTYPE_ETHERNET) {
			/* rule out the all-zeros Ethernet address, a common bogus client enet address */
			if (!bcmp((char *) &bp->bp_chaddr, (char *) &enet_all_zeros, haddrlength(HTYPE_ETHERNET))) {
				if (debug > 2)
					report(LOG_NOTICE, "  bad Ethernet chaddr (all zeros), ignoring BootP client");
				free_client_info(&cl);
				return;
			}
			/* rule out broadcast and multicast Ethernet addreses, which are bogus client enet addresses */
			if (bp->bp_chaddr[0] % 2 == 1) {
				if (debug > 2)
					report(LOG_NOTICE, "  bad Ethernet chaddr %s (broadcast or multicast), ignoring BootP client",  haddrtoa(bp->bp_chaddr, hlen));
				free_client_info(&cl);
				return;
			}
		}

		/* we'll use this when looking up client by chaddr */
		dummyhost.htype = bp->bp_htype;
		bcopy(bp->bp_chaddr, dummyhost.haddr, hlen);

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

		/* look up haddr in bootptab */
		hashcode = hash_HashFunction(bp->bp_chaddr, hlen);
		hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp, &dummyhost);

		if (hp == NULL && bp->bp_htype == HTYPE_IEEE802) { /* Try again with address in "canonical" form. */
			haddr_conv802(bp->bp_chaddr, dummyhost.haddr, hlen);
			if (debug > 3) {
				report(LOG_INFO, "  Client HW addr type is IEEE 802, and wasn't found in bootptab.  Converted to %s and checking again...\n",
					   haddrtoa(dummyhost.haddr, bp->bp_hlen));
			}
			hashcode = hash_HashFunction(dummyhost.haddr, hlen);
			hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp, &dummyhost);
		}

		if(hp){ /* found chaddr in bootptab */
			registered=1; /* we'll need to know if client is registered in bootptab */
			roaming=hp->flags.roaming; /* we'll need to know if client has :ro: tag in bootptab */
			deny_dynamic=hp->flags.deny_client_dynamic; /* we'll need to know later if client has :dd: tag in bootptab */
			real=hp;
			if (hp->flags.deny_client) {
				if (debug > 3)
					report(LOG_INFO, "  Client haddr found in bootptab with deny_client tag, ignoring client");
				free_client_info(&cl);
				return;
			}
			if (hp->iaddr.s_addr!=0) { /* bootptab entry is for static IP address */
				if (compact_messages && debug) {
					char giaddrbuf[MAX_INETADDR_STRING_LEN];
					char iaddrbuf[MAX_INETADDR_STRING_LEN];

					strncpy(giaddrbuf, inet_ntoa(bp->bp_giaddr), MAX_INETADDR_STRING_LEN-1);
					strncpy(iaddrbuf, inet_ntoa(hp->iaddr), MAX_INETADDR_STRING_LEN-1);
					report(LOG_INFO, "BOOTPREQUEST %s [%s]: found %s (%s)", haddrtoa2(bp->bp_chaddr, bp->bp_hlen),
						giaddrbuf, iaddrbuf, hp->hostname->string);
				}
				if (debug > 3)
					report(LOG_INFO, "  Client haddr found in bootptab with static IP address %s",inet_ntoa(hp->iaddr));
				if (check_net(&(bp->bp_giaddr),&(hp->iaddr))) {
					if (debug > 3)
						report(LOG_INFO, "  Static IP address is OK for client's network");

					/* if chaddr already bound to something, clear that binding */
					hp2=(struct host*)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp ,cl.cliid);
					if (hp2) {
						if (debug > 3) {
							report(LOG_INFO, " Client haddr presently bound to %s", inet_ntoa(hp2->iaddr));
							report(LOG_INFO, "  Freeing binding");
						}
						reuse_addr(hp2);
					}

					/* if intended IP address already bound to something, we have a problem.  It can't
					   be bound to chaddr, since if it was, we would have just freed that binding.  So
					   the intended IP address is bound to someone else, somehow. */
					iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
					hp2=(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp, (hash_datum*)&(hp->iaddr.s_addr));
					if (hp2) {
						report(LOG_ERR, "  bootp_request(): found chaddr in bootptab assigned to static IP address (%s),...", inet_ntoa(hp->iaddr));
						report(LOG_ERR, "but this IP addr is presently bound to *ANOTHER* cliid (%s)", cliid_btoa(hp2->cl));
						report(LOG_ERR, "freeing binding");
						reuse_addr(hp2);
						free_client_info(&cl);
						return;
					}
				} else {
					if (debug > 3)
						report(LOG_INFO, "  Static IP Address not appropriate for client's network, will not use");
					hp=NULL;
				}
			} else {
				if (debug > 3)
						report(LOG_INFO, "  Client haddr found in bootptab with no static IP address, will not use this bootptab entry");
						hp=NULL;
			}
		} else {
			if (compact_messages && debug)
				report(LOG_INFO, "BOOTPREQUEST %s [%s]: not found", haddrtoa2(bp->bp_chaddr, bp->bp_hlen), inet_ntoa(bp->bp_giaddr));
			if (debug > 3) 
				report(LOG_INFO, "  Client haddr not found in bootptab");
		}

		/* hp either:
		   points to a bootptab entry for the client, containing a static IP address appropriate for the network the client's currently attached to
		     or
		   is NULL
		*/

		
		if (client_steering_enable) {
			/* Apply Client Steering (handles all possible BOOTPREPLY cases). */
		
			if (hp && client_steering_enable_bootpreply_static) {
				/* We have an appropriate static IP address to offer,
		   		and should perform Client Steering when sending BOOTPREPLY for a static IP address.
				*/
				if (bp->bp_secs < client_steering_delay_for_this_client) {
					if ((debug > 3) || princeton_log_client_steering)
						report(LOG_INFO, "  client_steering suppressing any possible reply to %s for a static IP address because bootp_secs(%d) < client_steering_delay_for_this_client(%d)", cliid_btoa(cl.cliid), bp->bp_secs, client_steering_delay_for_this_client);

					free_client_info(&cl);
					return;
				} else {
					if ((debug > 3) || princeton_log_client_steering)
						report(LOG_INFO, "  client_steering not suppressing any possible reply to %s for a static IP address as bootp_secs(%d) >= client_steering_delay_for_this_client(%d)", cliid_btoa(cl.cliid), bp->bp_secs, client_steering_delay_for_this_client);
				}
	
			} else if (!hp && client_steering_enable_bootpreply_dynamic) {
				/* We have no appropriate static IP address to offer, implying that any IP address we MIGHT find to offer would be dynamic;
		   		and we should perform Client Steering when sending BOOTPREPLY for a dynamic IP address.
				*/
				if (bp->bp_secs < client_steering_delay_for_this_client) {
					if ((debug > 3) || princeton_log_client_steering)
						report(LOG_INFO, "  client_steering suppressing any possible reply to %s for a dynamic IP address because bootp_secs(%d) < client_steering_delay_for_this_client(%d)", cliid_btoa(cl.cliid), bp->bp_secs, client_steering_delay_for_this_client);
	
					free_client_info(&cl);
					return;
				} else {
					if ((debug > 3) || princeton_log_client_steering)
						report(LOG_INFO, "  client_steering not suppressing any possible reply to %s for a dynamic IP address as bootp_secs(%d) >= client_steering_delay_for_this_client(%d)", cliid_btoa(cl.cliid), bp->bp_secs, client_steering_delay_for_this_client);
				}
			}
		} /* !client_steering_enable.  No point logging anything. */

		if (!hp) { /* we didn't find an appropriate static IP address in bootptab */
			hp=(struct host*)hash_Lookup(boundhshtbl,cl.clhashcode,boundcliidcmp,cl.cliid);
			if (hp) { /* client is presently bound */
				if (debug > 3)
					report(LOG_INFO, "  Client haddr is currently bound to %s, checking if ok to re-use",inet_ntoa(hp->iaddr));

					/* don't re-use if it was assigned via DHCP */
					if (hp->flags.expire) {
						if (debug > 3) {
							report(LOG_INFO, "  Binding was assigned via DHCP; will not re-use");
							report(LOG_INFO, "  Freeing binding");
						}
						reuse_addr(hp);
						hp=NULL;
					}

					/* check that hp->iaddr is still in bootptab */
					if (hp) {
						iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
						hp2=(struct host *)hash_Lookup(iphashtable,iphashcode,ipcmp, (hash_datum*)&(hp->iaddr.s_addr));
						if (!hp2) {
							if (debug > 3) {
								report(LOG_INFO, "  IP Address no longer in bootptab, will not re-use");
								report(LOG_INFO, "  Freeing binding");
							}
							reuse_addr(hp);
							hp = NULL;
						}
					}

					if (hp) { 
						/* If bootptab entry has a cliid, make sure it matches the requester's */
						if (hp2 && hp2->flags.cliid && !BINDATA_EQ(hp2->cliid,cl.cliid)) {
							report(LOG_ERR, "  Bootptab entry for this IP has different cliid! Something's very wrong!");
							report(LOG_ERR, "  Freeing binding");
							reuse_addr(hp);
							hp = NULL;
						}
					}
					
					if(hp) {
						/* Make sure IP address is still appropriate, given client's present wire. */
						if (!check_net(&(bp->bp_giaddr),&(hp->iaddr))) {
							if (debug > 3) {
								report(LOG_INFO, "  IP Address no longer appropriate (client on different network); will not re-use");
								report(LOG_INFO, "  Freeing binding");
							}
							reuse_addr(hp);
							hp=NULL;
						}
					}


					if (hp && hp2->flags.dynamic) { /* If bootptab entry is a dynamic IP address */
						/* Make sure the dynamic address is a member of a dynamic pool we're handling */
						if (!hp->flags.cable) {
							report(LOG_ERR, "  IP %s is bound, and bootptab indicates it is dynamic, but it is *not* a member of any pool!  Something's very wrong!", inet_ntoa(hp->iaddr));
							report(LOG_ERR, "  freeing binding");
							reuse_addr(hp);
							hp=NULL;
						}
						/* Make sure client is not explicitly disallowed a dynamic IP address. */
						if (deny_dynamic) {
							if (debug > 3) {
								report(LOG_INFO,"  Dynamic IP Address disallowed for this client; will not re-use");
								report(LOG_INFO,"  Freeing binding");
							}
							reuse_addr(hp);
							hp=NULL;
						}
						/* Make sure the IP address is still appropriate, given the policy restrictions for this wire */
						/* We must use access_network(), not use_addr() with force flag, since we know this
						   ip address is already bound (to us). */
						cablehashcode = hash_HashFunction((u_char*)hp2->cable->string, strlen(hp2->cable->string));
						c = (struct cable *) hash_Lookup(cablehshtbl, cablehashcode, cablelcmp, hp2->cable->string);
						if (c) { /* only test if we have an access policy defined for this cable */
							if (roaming) /* client registered with :ro: tag */
								rc=access_network(c->code,ROAMING_ACCESS|REG_ACCESS|BOOTP_ACCESS);
							else if (registered) /* client registered without :ro: tag */
								rc=access_network(c->code,REG_ACCESS|BOOTP_ACCESS);
							else if (!registered) /* client not registered */
								rc=access_network(c->code,UNREG_ACCESS|BOOTP_ACCESS);
							if (!rc) {
								if (debug > 3) {
									report(LOG_INFO,"  Dynamic IP Address disallowed by access policy; will not re-use");
									report(LOG_INFO,"  Freeing binding");
								}
								reuse_addr(hp);
								hp=NULL;
							}
						}
						/* old dynamic address looked ok to re-use */
					}
					if (hp) {
						iphashcode=hash_HashFunction((u_char*)&hp->iaddr.s_addr,4);
						hp3 =(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp,(hash_datum *)&(hp->iaddr.s_addr));
						if (!hp3) {
							report(LOG_ERR, "bootp_request(): found entry for cliid (%s) in ipboundhshtbl, bound to IP address %s...", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
							report(LOG_ERR, "but no entry for this IP addr found in in ipboundhshtbl!");
							report(LOG_ERR, "freeing binding");
							reuse_addr(hp);
							hp=NULL;
						} else {
							if (!hp3->flags.cliid) {
								report(LOG_ERR, "bootp_request():  found entry for cliid (%s) in ipboundhshtbl, bound to IP address %s...", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
								report(LOG_ERR, "but ipboundhshtbl entry for this IP addr lacks a cliid!");
								report(LOG_ERR, "freeing both bindings");
								reuse_addr(hp3);
								reuse_addr(hp);
								hp3 = hp = NULL;
							} else {
								if (!BINDATA_EQ(hp3->cliid, cl.cliid)) {
									report(LOG_ERR, "bootp_request(): found entry for cliid (%s) in ipboundhshtbl, bound to IP address %s...", cliid_btoa(hp->cl), inet_ntoa(hp->iaddr));
									report(LOG_ERR, "but ipboundhshtbl for this IP addr has a *DIFFERENT* cliid (%s)", cliid_btoa(hp3->cl));
									report(LOG_ERR, "freeing both bindings");
									reuse_addr(hp3);
									reuse_addr(hp);
									hp3 = hp = NULL;
								}
							}
						}
					}
			} else if (debug > 3) report(LOG_INFO, "  Client not currently bound");
		}
			
		/* If nothing found yet, try to assign new dynamic IP address  */
		if (!hp) { /* first try based on whether client is registered in bootptab */
			if (deny_dynamic) {
				if (debug > 6) 
					report(LOG_INFO, "  This client is not permitted a dynamic IP address; will not search for one");
			} else if (roaming) {
				if (debug > 6) 
					report(LOG_INFO, "  Searching for a dynamic IP address for this registered roaming client...");
				hp=get_dynamic_ip(bp->bp_giaddr,ROAMING_ACCESS|REG_ACCESS|BOOTP_ACCESS);
			} else if (registered) {
				if (debug > 6) 
					report(LOG_INFO, "  Searching for a dynamic IP address for this registered client...");
				hp=get_dynamic_ip(bp->bp_giaddr,REG_ACCESS|BOOTP_ACCESS);
			} else if (!registered) {
				if (debug > 6) 
					report(LOG_INFO, "  Searching for a dynamic IP address for this unregistered client...");
				hp=get_dynamic_ip(bp->bp_giaddr,UNREG_ACCESS|BOOTP_ACCESS);
			}
			if(hp){
				hp->code=BOOTP_FLAG;
				new_dynamic=TRUE;
			}

		}

		if (hp == NULL) {
			if (compact_messages && debug)
				report(LOG_INFO, "BOOTPREQUEST %s [%s]: no offer", haddrtoa2(bp->bp_chaddr, bp->bp_hlen), inet_ntoa(bp->bp_giaddr));
			if (debug > 3)
				report(LOG_INFO, "  No appropriate dynamic ip addresses available, ignoring client");
			free_client_info(&cl);
			return; /* not found */
		} else {
			if (compact_messages && debug) {
				char giaddrbuf[MAX_INETADDR_STRING_LEN];
				char iaddrbuf[MAX_INETADDR_STRING_LEN];

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

	} else { /* client specified ciaddr, so we must rely on that, instead of chaddr */
		/* I haven't been able to test this block of code at all, since I don't have clients that
		   operate this way. May be some problems to fix. */

		if (debug > 3) {
			report(LOG_INFO, "  Client specified ciaddr %s", inet_ntoa(bp->bp_ciaddr));
		}


		/* If the packet contains a chaddr, we need to look it up in bootptab.  If found,
		   we need to see if its hp->flags.deny_client flag is set; if so, we must ignore the client. */ 
		dummyhost.htype = bp->bp_htype;
		hlen = haddrlength(bp->bp_htype);
		if (hlen == bp->bp_hlen) { /* skip check if bogus or inconsistent bp->bp_htype and bp->bp_hlen */
			bcopy(bp->bp_chaddr, dummyhost.haddr, hlen);
			/* look up haddr in bootptab */
			hashcode = hash_HashFunction(bp->bp_chaddr, hlen);
			hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp, &dummyhost);
			if (hp == NULL && bp->bp_htype == HTYPE_IEEE802) { /* Try again with address in "canonical" form. */
				haddr_conv802(bp->bp_chaddr, dummyhost.haddr, hlen);
				if (debug > 3) {
					report(LOG_INFO, "  Client HW addr type is IEEE 802, and wasn't found in bootptab.  Converted to %s and checking again...\n",
					   	haddrtoa(dummyhost.haddr, bp->bp_hlen));
				}
				hashcode = hash_HashFunction(dummyhost.haddr, hlen);
				hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp, &dummyhost);
			}
			if (hp && hp->flags.deny_client) {
				if (debug > 3)
					report(LOG_INFO, "  Client haddr found in bootptab with deny_client tag, ignoring client");
				free_client_info(&cl);
				return;
			}
			if (hp)
				deny_dynamic = hp->flags.deny_client_dynamic; /* later we'll need to know if the :dd: flag was specified for this client */
		}


		dummyhost.iaddr.s_addr = bp->bp_ciaddr.s_addr;
		/* look up ciaddr in bootptab */
		hashcode = hash_HashFunction((u_char *) &(bp->bp_ciaddr.s_addr), 4);
		hp = (struct host *) hash_Lookup(iphashtable, hashcode, iplookcmp, &dummyhost);

		if (hp == NULL) {
			if (debug > 3) 
				report(LOG_INFO, "  Client-specified ciaddr not found in bootptab");
			free_client_info(&cl);
			return;
		} else 
			if (debug > 3)
				report(LOG_INFO, "  Client-specified ciaddr found in bootptab");


		/* We will not respond if ciaddr is for dynamic IP address (should we?) */
		/* We are only providing dynamic IP address assignment to bootp clients who let *us* select
		   an IP address for them.  */
		if (hp->flags.dynamic) {
			if (debug > 3)
				report(LOG_NOTICE, "  Client-specified ciaddr is for dynamic IP address; ignoring client");
			free_client_info(&cl);
			return;
		}
		
		/* We won't respond if bootptab for this ciaddr lacks a cliid or has a different cliid */
		if (hp->flags.cliid) {
			if (!BINDATA_EQ(hp->cliid,cl.cliid)) {
				if (debug > 3)
					report(LOG_NOTICE, "  Bootptab entry for client-specified ciaddr has different cliid; ignoring client");
				free_client_info(&cl);
				return;
			} 
		} else {
			if (debug > 3)
				report(LOG_NOTICE, "  Bootptab entry for client-specified ciaddr has no cliid; ignoring client");
			free_client_info(&cl);
			return;
		}

		/* We won't respond if the client is on wrong net for this ciaddr */
		if (!check_net(&(bp->bp_giaddr),&(bp->bp_ciaddr))) {
			if (debug > 3) 
				report(LOG_INFO, "  Client-specified ciaddr is on wrong network for this client; ignoring client");
			free_client_info(&cl);
			return;
		}

		/* see if ciaddr already bound to something */
		iphashcode=hash_HashFunction((u_char*)&bp->bp_ciaddr.s_addr,4);
		hp2=(struct host *)hash_Lookup(ipboundhshtbl,iphashcode,ipcmp, (hash_datum*)&(hp->iaddr.s_addr));
		if (hp2) { /* ciaddr already bound */
			if (hp2->flags.cliid) {
				if (!BINDATA_EQ(hp2->cliid,cl.cliid)) {
					report(LOG_ERR, "  Client-specified ciaddr is already bound to a different cliid; ignoring client");
					free_client_info(&cl);
					return;
				} else { /* ciaddr already bound to this client, no error */
					if (debug > 3) {
						report(LOG_INFO, "  Client-specified ciaddr already bound to this chaddr.");
						report(LOG_INFO, "  Freeing binding");
					}
					reuse_addr(hp2);
				}
			} else {
				report(LOG_ERR, "  Client-specified ciaddr is already bound, but binding lacks a cliid; something is very wrong!");
				free_client_info(&cl);
				return;
			}
		}
	}

	if(!hp->flags.dynamic)
		hp->code=RESERVED_FLAG;

	hp->cl=cl.cliid;	/* use shared cliid here too */
	hp->cliid=cl.cliid;
	hp->flags.cliid=TRUE;
	hp->cl->linkcount+=2;

	/* We clear hp->flags.expire to indicate that the assignment is infinite; i.e. don't look at
	   the value in hp->expire.  However, the lastbind structure only records hp->expire, without
	   hp->flags.expire.  SO, we'll still set hp->expire to INFINITY, so that value ends up in
	   the lastbind structure. */
	hp->flags.expire=0;	
	hp->expire = INFINITY;

	if(new_dynamic){ 
		/* note that although DHCP-assignments always get into the bound tables, BOOTP-assignments
		   only go into the bound tables if they are for dynamic IP addresses.  */
		/* When examining current bindnings, you can positively identify BOOTP assignments as those where hp->flags.expire==0.
		   At that point, you should find hp->code == BOOTP_FLAG, which implies dynamic;
		   you should not find hp->code==RESERVED_FLAG, since that implies static, which don't go into the current bindings. */
		hp->linkcount+=2; /* will be inserted into 2 hashtabs */
		if (hash_Insert(boundhshtbl,cl.clhashcode,nullcmp,cl.cliid,hp)) {
			report(LOG_ERR, "  bootp_request(): hash_Insert(boundhshtbl) failed (cliid=%s)", cliid_btoa(cl.cliid));
		}
		if (hash_Insert(ipboundhshtbl,hp->iphashcode,nullcmp,hp,hp)) {
			report(LOG_ERR, "  bootp_request(): hash_Insert(ipboundhshtbl) failed for IP address %s (cliid=%s)", inet_ntoa(hp->iaddr), cliid_btoa(cl.cliid));
		}
		leases++;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "  Is dynamic, so added to current bindings; incremented leases counter to %d", leases);
#endif
	} 

   	update_lastbind(&cl,hp); 	/* note: regardless of dynamic or static */
	free_client_info(&cl);

	(bp->bp_yiaddr).s_addr = hp->iaddr.s_addr;

	if(fill_header(bp,hp,&bootsize))
		return;

	/*
	 * Handle vendor options based on magic number.
	 */

	if (debug > 9) {
		report(LOG_INFO, "  vendor magic field is %d.%d.%d.%d",
			   (int) ((bp->bp_vend)[0]),
			   (int) ((bp->bp_vend)[1]),
			   (int) ((bp->bp_vend)[2]),
			   (int) ((bp->bp_vend)[3]));
	}
	/*
	 * If this host isn't set for automatic vendor info then copy the
	 * specific cookie into the bootp packet, thus forcing a certain
	 * reply format.  Only force reply format if user specified it.
	 */
	if (hp->flags.vm_cookie) {
		/* Slam in the user specified magic number. */
		bcopy(hp->vm_cookie, bp->bp_vend, 4);
	}
	/*
	 * Figure out the format for the vendor-specific info.
	 * Note that bp->bp_vend may have been set above.
	 */
	if (!bcmp(bp->bp_vend, vm_rfc1048, 4)) {
		/* RFC1048 conformant bootp client */
		dovend_rfc1048(bp, hp, bootsize);
		if (debug > 3)
			report(LOG_INFO, "  Sending BOOTPREPLY message (with RFC1048 options)");
	}
	else if (!bcmp(bp->bp_vend, vm_cmu, 4)) {
        /* CMU-cookie bootp client */
        dovend_cmu(bp, hp);
		if (debug > 9)
        	report(LOG_INFO, "  Sending BOOTPREPLY message (with CMU options)");
    }
    else if (debug > 9)
		report(LOG_INFO, "  Sending BOOTPREPLY (with no options)");

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

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

   bootp_replys_sent++;
	/* not forwarded */
	sendreply(0, dest);

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

}

int fill_header(bp,hp,bootsize)
struct bootp *bp;
struct host *hp;
int32 *bootsize;
{
	char realpath[1024];	/* must be > 2*BP_FILE_LEN + MAXSTRINGLENGTH + 5 */
	char *clntpath;
	char *homedir, *bootfile;
	int n;
    /*
     * If there is a response delay threshold, ignore requests
     * with a timestamp lower than the threshold.
     */
    if (hp->flags.min_wait) {
        u_int32 t = (u_int32) ntohs(bp->bp_secs);
        if (t < hp->min_wait) {
            if (debug > 3)
                report(LOG_INFO,
                       "  ignoring request due to timestamp (%d < %d)",
                       t, hp->min_wait);
            return 1;
        }
    }

#ifdef  YORK_EX_OPTION
    /*
     * The need for the "ex" tag arose out of the need to empty
     * shared networked drives on diskless PCs.  This solution is
     * not very clean but it does work fairly well.
     * Written by Edmund J. Sutcliffe <edmund@york.ac.uk>
     *
     * XXX - This could compromise security if a non-trusted user
     * managed to write an entry in the bootptab with :ex=trojan:
     * so I would leave this turned off unless you need it. -gwr
     */
    /* Run a program, passing the client name as a parameter. */
    if (hp->flags.exec_file) {
#define TST_LEN 100
        char tst[TST_LEN];
        /* XXX - Check string lengths? -gwr */
		if (strlen(hp->exec_file->string) + strlen(hp->hostname->string) + 4 <= TST_LEN) {
			strcpy (tst, hp->exec_file->string);
			strcat (tst, " ");
			strcat (tst, hp->hostname->string);
			strcat (tst, " &");
			if (debug)
				report(LOG_INFO, "executing %s", tst);
			system(tst);    /* Hope this finishes soon... */
		} else {
			report(LOG_ERR, "YORK_EX_OPTION: won't execute command, buffer (size %d) too small for command: '%s %s &'", TST_LEN, hp->exec_file->string, hp->hostname->string);
		}
    }
#endif  /* YORK_EX_OPTION */

    /*
     * If a specific TFTP server address was specified in the bootptab file,
     * fill it in, otherwise zero it.
     * XXX - Rather than zero it, should it be the bootpd address? -gwr
     */
    (bp->bp_siaddr).s_addr = (hp->flags.bootserver) ?
        hp->bootserver.s_addr : 0L;
#ifdef  STANFORD_PROM_COMPAT
    /*
     * Stanford bootp PROMs (for a Sun?) have no way to leave
     * the boot file name field blank (because the boot file
     * name is automatically generated from some index).
     * As a work-around, this little hack allows those PROMs to
     * specify "sunboot14" with the same effect as a NULL name.
     * (The user specifies boot device 14 or some such magic.)
     */
    if (strcmp(bp->bp_file, "sunboot14") == 0)
        bp->bp_file[0] = '\0';  /* treat it as unspecified */
#endif

    /*
     * Fill in the client's proper bootfile.
     *
     * If the client specifies an absolute path, try that file with a
     * ".host" suffix and then without.  If the file cannot be found, no
     * reply is made at all.
     *
     * If the client specifies a null or relative file, use the following
     * table to determine the appropriate action:
     *
     *  Homedir      Bootfile    Client's file
     * specified?   specified?   specification   Action
     * -------------------------------------------------------------------
     *      No          No          Null         Send null filename
     *      No          No          Relative     Discard request
     *      No          Yes         Null         Send if absolute else null
     *      No          Yes         Relative     Discard request     *XXX
     *      Yes         No          Null         Send null filename
     *      Yes         No          Relative     Lookup with ".host"
     *      Yes         Yes         Null         Send home/boot or bootfile
     *      Yes         Yes         Relative     Lookup with ".host" *XXX
     *
     */

    /*
     * XXX - I don't like the policy of ignoring a client when the
     * boot file is not accessible.  The TFTP server might not be
     * running on the same machine as the BOOTP server, in which
     * case checking accessibility of the boot file is pointless.
     *
     * Therefore, file accessibility is now demanded ONLY if the
     * Check File Access feature is enabled (it is by default).
     * -gwr, ist
     */

    /*
     * The "real" path is as seen by the BOOTP daemon on this
     * machine, while the client path is relative to the TFTP
     * daemon chroot directory (i.e. /tftpboot).
     */
    if (hp->flags.tftpdir) {
		/* We know strlen(hp->tftpdir->string) <= MAXSTRINGLEN, so it will fit into realpath */
		/* We know that the space remaining in clntpath is at least sizeof(realpath)-MAXSTRINGLEN */
        strcpy(realpath, hp->tftpdir->string);
        clntpath = &realpath[strlen(realpath)];
    } else {
        realpath[0] = '\0';
        clntpath = realpath;
    }

	/* Guard against bp_file not being null-terminated.  Since RFC says bp_file
	is all null or is null-terminated string, it is safe to overwrite last char of bp_file with null. */
	bp->bp_file[BP_FILE_LEN-1] = '\0';

    /*
     * Determine client's requested homedir and bootfile.
     */
    homedir = NULL;
    bootfile = NULL;
    if (bp->bp_file[0]) {
        homedir = bp->bp_file;
        bootfile = strrchr(homedir, '/');
        if (bootfile) {
            if (homedir == bootfile)
                homedir = NULL;
            *bootfile++ = '\0';
        } else {
            /* no "/" in the string */
            bootfile = homedir;
            homedir = NULL;
        }
        if (debug > 9) {
            report(LOG_INFO, "  requested path=\"%s\"  file=\"%s\"",
                   (homedir) ? homedir : "",
                   (bootfile) ? bootfile : "");
        }
    }

    /*
     * Specifications in bootptab override client requested values.
     */
    if (hp->flags.homedir)
        homedir = hp->homedir->string;
    if (hp->flags.bootfile)
        bootfile = hp->bootfile->string;


    /*
     * Construct bootfile path.
     */
    if (homedir) {
        if (homedir[0] != '/')
            strcat(clntpath, "/");
        strcat(clntpath, homedir);
        homedir = NULL;
    }
    if (bootfile) {
        if (bootfile[0] != '/')
            strcat(clntpath, "/");
        strcat(clntpath, bootfile);
        bootfile = NULL;
    }

	if (!clntpath[0] && dont_look_for_default_bootfile) {
		/* No specific bootfile was specified by the host or the bootptab,
		  and the "Don't Look For a Default Bootfile" feature is enabled.
		  Skip any attempt to look a default bootfile (avoids two useless stat() calls).
		  Zero the bootfile field in the packet, and continue processing the reply.
		*/
		bzero(bp->bp_file, sizeof(bp->bp_file));
	} else {
		/* A specific bootfile was specified by the host or the bootptab,
		   or the "Don't Look For a Default Bootfile" feature is not enabled.
		*/
    	/*
     	* First try to find the file with a ".host" suffix
     	*/
    	n = strlen(clntpath);
    	strcat(clntpath, ".");
    	strcat(clntpath, hp->hostname->string);
    	if (chk_access(realpath, bootsize) < 0) {
        	clntpath[n] = 0;            /* Try it without the suffix */
        	if (chk_access(realpath, bootsize) < 0) {
            	/* neither "file.host" nor "file" was found */
				if (check_file_access) {
            		if (bp->bp_file[0]) {
                		/*
                 		* Client wanted specific file
                 		* and we didn't have it.
                 		*/
						if (debug > 2)
                			report(LOG_NOTICE, "  requested file not found: \"%s\"", clntpath);
                		return 1;
            		}
            		/*
             		* Client didn't ask for a specific file and we couldn't
             		* access the default file, so just zero-out the bootfile
             		* field in the packet and continue processing the reply.
             		*/
            		bzero(bp->bp_file, sizeof(bp->bp_file));
            		goto null_file_name;
				} else { /* check_file_access */
            		/* Complain only if boot file size was needed. */
            		if (hp->flags.bootsize_auto) {
                		report(LOG_ERR, "  can not determine size of file \"%s\"",
                       		clntpath);
            		}
				} /* check_file_access */
        	}
    	}
    	strncpy(bp->bp_file, clntpath, BP_FILE_LEN); /* note strncpy right-pads with nul, which is good here */
    	if (debug > 9)
        	report(LOG_INFO, "  bootfile=\"%s\"", clntpath);
	}

null_file_name:
	return 0;
}


/*
 * Process BOOTREPLY packet.
 */
PRIVATE void
handle_reply()
{
	if (debug > 1) {
		report(LOG_INFO, " processing BOOTPREPLY");
	}
	/* forwarded, no destination override */
	sendreply(1, 0);
}


/*
 * Send a reply packet to the client.  'forward' flag is set if we are
 * not the originator of this reply packet.
 */
PRIVATE void
sendreply(forward, dst_override)
	int forward;
	unsigned int32 dst_override; /* network byte order */
{
	struct bootp *bp = (struct bootp *) pktbuf;
	struct in_addr dst;
	u_short port = htons(bootpc_port);
	unsigned char *ha;
	int len, haf;

	/*
	 * If the destination address was specified explicitly,
	 * perhaps with the :ra=ADDR: option
	 * (e.g. the broadcast address for HP compatability)
	 * then send the response to that address.  Otherwise:

		If ciaddr is set, send to ciaddr.  That's as per RFC; a client that fills
		in ciaddr must be prepared to accept a packet sent there.  And if the client's
		not on ciaddr's network, it is understood that the reply will never reach it.

		Else if giaddr is set, send to giaddr, changing the dstPort to bootpserver.
		
		Else if yiaddr==0, send to broadcast.  We understand that this won't reach client
		if client was non-local and unicast the request to us (preventing giaddr from
		being set).  We understand that this won't reach client if client was local
		AND we're multihomed AND the OS chooses to send the broadcast out an interface
		to which the client is not attached.  (We could try to fix that by trying to
		detect this case, and then sending to a directed broadcast address to force it
		out a particular interface.  But that may not be accepted by some clients,
		since spec says that bootp/dhcp broadcasts must be sent to 255.255.255.255.)

		Else if yiaddr is non-local, send to yiaddr.  

		Else if broadcast bit is set, send to broadcast.

		Else send to yiaddr, priming the arp cache.

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

        if (debug > 6)
            report(LOG_INFO, "  setarp %s - %s",
                   inet_ntoa(dst), haddrtoa(ha, len));
        setarp(s, &dst, haf, ha, len);
    }

	/* If we are originating this reply, and bp_siaddr has not already been set
	   (via bootptab), we fill in bp_siaddr with our own address. */
	if ((forward == 0) && (bp->bp_siaddr.s_addr == 0)) {
		struct in_addr siaddr;   /* we'll compute the siaddr here before copying it to bp_siaddr */
		if (bootp_siaddr_local_opt) { /* BOOTPREPLY siaddr Local Optimization feature is enabled, the default */
			/* First determine if the client is "local" to one of the server's interfaces */
			struct in_addr *tmp_addr;
			if (tmp_addr = getif(&dst)) {
				/* Client *is* "local" to one of the server's interfaces, use that interface's IP
				   address to fill in siaddr, as an optimization, since it may be closer to client
				   that my_ip_addr on a multihomed server. */
				siaddr.s_addr = tmp_addr->s_addr;
			} else {
				/* Client is *not* "local" to any of the server's interfaces; just use my_ip_addr
				   to fill in siaddr. */
				siaddr.s_addr = my_ip_addr.s_addr;
			}
		} else  { /* BOOTPREPLY siaddr Local Optimization feature is disabled, just use my_ip_addr */
			siaddr.s_addr = my_ip_addr.s_addr;
		}
		/* Finally, copy the computed siaddr into bp_siaddr */
		bp->bp_siaddr.s_addr = siaddr.s_addr;
	}

	/* Set up socket address for send. */
	send_addr.sin_family = AF_INET;
	send_addr.sin_port = port;
	send_addr.sin_addr = dst;

	if (delay_reply_bootpreply)
		sleep_us(delay_reply_bootpreply);
	else if (delay_reply_default)
		sleep_us(delay_reply_default);

	/* Send reply with same size packet as request used. */
	if (sendto(s, pktbuf, pktlen, 0,
			   (struct sockaddr *) &send_addr,
			   sizeof(send_addr)) < 0)
	{
		report(LOG_ERR, "  sendto: %s", get_network_errmsg());
	}
} /* sendreply */


/* nmatch() - now in getif.c */
/* setarp() - now in hwaddr.c */


/*
 * This call checks read access to a file.  It returns 0 if the file given
 * by "path" exists and is publically readable.  A value of -1 is returned if
 * access is not permitted or an error occurs.  Successful calls also
 * return the file size in bytes using the long pointer "filesize".
 *
 * The read permission bit for "other" users is checked.  This bit must be
 * set for tftpd(8) to allow clients to read the file.
 */

PRIVATE int
chk_access(path, filesize)
	char *path;
	int32 *filesize;
{
	struct stat st;

	if ((stat(path, &st) == 0) && (st.st_mode & (S_IREAD >> 6))) {
		*filesize = (int32) st.st_size;
		return 0;
	} else {
		return -1;
	}
}


/*
 * Now in dumptab.c :
 *      dumptab()
 *      dump_host()
 *      list_ipaddresses()
 */



/*
 * Insert the CMU "vendor" data for the host pointed to by "hp" into the
 * bootp packet pointed to by "bp".
 */

PRIVATE void
dovend_cmu(bp, hp)
    struct bootp *bp;
    struct host *hp;
{
    struct cmu_vend *vendp;
    struct in_addr_list *taddr;

    /*
     * Initialize the entire vendor field to zeroes.
     */
    bzero(bp->bp_vend, sizeof(bp->bp_vend));

    /*
     * Fill in vendor information. Subnet mask, default gateway,
     * domain name server, ien name server, time server
     */
    vendp = (struct cmu_vend *) bp->bp_vend;
    strcpy(vendp->v_magic, (char *)vm_cmu);
    if (hp->flags.subnet_mask) {
        (vendp->v_smask).s_addr = hp->subnet_mask.s_addr;
        (vendp->v_flags) |= VF_SMASK;
        if (hp->flags.gateway) {
            (vendp->v_dgate).s_addr = hp->gateway->addr->s_addr;
        }
    }
    if (hp->flags.domain_server) {
        taddr = hp->domain_server;
        if (taddr->addrcount > 0) {
            (vendp->v_dns1).s_addr = (taddr->addr)[0].s_addr;
            if (taddr->addrcount > 1) {
                (vendp->v_dns2).s_addr = (taddr->addr)[1].s_addr;
            }
        }
    }
    if (hp->flags.name_server) {
        taddr = hp->name_server;
        if (taddr->addrcount > 0) {
            (vendp->v_ins1).s_addr = (taddr->addr)[0].s_addr;
            if (taddr->addrcount > 1) {
                (vendp->v_ins2).s_addr = (taddr->addr)[1].s_addr;
            }
        }
    }
    if (hp->flags.time_server) {
        taddr = hp->time_server;
        if (taddr->addrcount > 0) {
            (vendp->v_ts1).s_addr = (taddr->addr)[0].s_addr;
            if (taddr->addrcount > 1) {
                (vendp->v_ts2).s_addr = (taddr->addr)[1].s_addr;
            }
        }
    }
    /* Log message now done by caller. */
} /* dovend_cmu */






/*
 * Insert the RFC1048 vendor data for the host pointed to by "hp" into the
 * bootp packet pointed to by "bp".
 */
#define NEED(LEN, MSG) do \
	if (bytesleft < (LEN)) { \
		report(LOG_NOTICE, noroom, \
			   hp->hostname->string, MSG); \
		return 0; \
	} while (0)
PRIVATE int
dovend_rfc1048(bp, hp, bootsize)
	struct bootp *bp;
	struct host *hp;
	int32 bootsize;
{
	int bytesleft, len;
	byte *vp;

	static char noroom[] = "%s: No room for \"%s\" option";

	vp = bp->bp_vend;

	if (hp->flags.msg_size) {
		pktlen = hp->msg_size;
	} else {
		/*
		 * If the request was longer than the official length, build
		 * a response of that same length where the additional length
		 * is assumed to be part of the bp_vend (options) area.
		 */
		if ((unsigned)pktlen > sizeof(*bp)) {
			if (debug > 2)
				report(LOG_INFO, "  request message length=%d", pktlen);
		}
		/*
		 * Check whether the request contains the option:
		 * Maximum DHCP Message Size (RFC1533 sec. 9.8)
		 * and if so, override the response length with its value.
		 * This request must lie within the first BP_VEND_LEN
		 * bytes of the option space.
		 */
		{
			byte *p, *ep;
			byte tag, len;
			short msgsz = 0;
			
			p = vp + 4;
			ep = p + BP_VEND_LEN - 4;
			while (p < ep) {
				tag = *p++;
				/* Check for tags with no data first. */
				if (tag == TAG_PAD)
					continue;
				if (tag == TAG_END)
					break;
				/* Now scan the length byte. */
				len = *p++;
				switch (tag) {
				case TAG_MAX_MSGSZ:
					if (len == 2) {
						bcopy(p, (char*)&msgsz, 2);
						msgsz = ntohs(msgsz);
					}
					break;
				case TAG_SUBNET_MASK:
					/* XXX - Should preserve this if given... */
					break;
				} /* switch */
				p += len;
			}

			if ((unsigned)msgsz > sizeof(*bp)) {
				if (debug > 1)
					report(LOG_INFO, "  request has DHCP msglen=%d", msgsz);
				pktlen = msgsz;
			}
		}
	}

	if ((unsigned)pktlen < sizeof(*bp)) {
		if (debug > 9) {
			report(LOG_INFO, "  reply pkt length (%d) < minimum (%d), request pkt invalid;", pktlen, sizeof(*bp));
			report(LOG_INFO, "  ...will lengthen reply pkt to minimum before sending");
		}
		pktlen = sizeof(*bp);
	}
	bytesleft = ((byte*)bp + pktlen) - vp;
	if ((unsigned)pktlen > sizeof(*bp)) {
		if (debug > 9)
			report(LOG_INFO, "  extended reply, length=%d, options=%d",
				   pktlen, bytesleft);
	}

	/* Copy in the magic cookie */
	bcopy(vm_rfc1048, vp, 4);
	vp += 4;
	bytesleft -= 4;

	if (hp->flags.subnet_mask) {
		/* We do not allow you to use a bootp_exclude or bootp_include option to filter this from the reply. */
		/* always enough room here. */
		*vp++ = TAG_SUBNET_MASK;/* -1 byte  */
		*vp++ = 4;                              /* -1 byte  */
		insert_u_long(hp->subnet_mask.s_addr, &vp);     /* -4 bytes */
		bytesleft -= 6;                 /* Fix real count */
		if (hp->flags.gateway) {
			/* We do not allow you to use a bootp_exclude option to exclude this from the reply. */
			(void) insert_ip(TAG_GATEWAY,
							 hp->gateway,
							 &vp, &bytesleft);
		}
	}
	if (hp->flags.bootsize) {
		if (!filter_bootp_option(hp, TAG_BOOT_SIZE)) {
			/* always enough room here */
			bootsize = (hp->flags.bootsize_auto) ?
				((bootsize + 511) / 512) : (hp->bootsize);      /* Round up */
			*vp++ = TAG_BOOT_SIZE;
			*vp++ = 2;
			/* bootsize is in host byte order, so we convert to network byte order here */
			*vp++ = (byte) ((bootsize >> 8) & 0xFF);
			*vp++ = (byte) (bootsize & 0xFF);
			bytesleft -= 4;                 /* Tag, length, and 16 bit blocksize */
		}
	}

    /*
     * This one is special: Remaining options go in the ext file.
     * Only the subnet_mask, bootsize, and gateway should precede.
     */
    if (hp->flags.exten_file) {
		/* We do not allow you to use a bootp_exclude or bootp_include option to filter this from the reply. */
        /*
         * Check for room for exten_file.  Add 3 to account for
         * TAG_EXTEN_FILE, length, and TAG_END.
         */
        len = strlen(hp->exten_file->string);
        NEED((len + 3), "ef");
        *vp++ = TAG_EXTEN_FILE;
        *vp++ = (byte) (len & 0xFF);
        bcopy(hp->exten_file->string, vp, len);
        vp += len;
        *vp++ = TAG_END;
        bytesleft -= len + 3;
        return 1;                 /* no more options here. */
    }
	/*
	 * The remaining options are inserted by the following
	 * function (which is shared with bootpef.c).
	 * Keep back one byte for the TAG_END.
	 */
	len = dovend_rfc1497(hp, vp, bytesleft - 1, PROTO_FLAVOR_BOOTP);
	vp += len;
	bytesleft -= len;

	/* There should be at least one byte left. */
	/* We do not allow you to use a bootp_exclude or bootp_include option to filter this from the reply. */
	NEED(1, "(end)");
	*vp++ = TAG_END;
	bytesleft--;

	/* Log message done by caller. */
	if (bytesleft > 0) {
		/*
		 * Zero out any remaining part of the vendor area.
		 */
		bzero(vp, bytesleft);
	}
	return 1;       /* sent reply */
} /* dovend_rfc1048 */
#undef  NEED



#ifdef UNKNOWN
#define MAX_UNKNOWN 100
void check_hwaddr(pkt)
struct bootp *pkt;
{
 struct unknownhost t,*t2,**u;
 u_char hlen;
 struct host dummyhost;
 unsigned hashcode;
 int i;


 hlen=haddrlength(pkt->bp_htype);
 if(hlen>BP_CHADDR_LEN)
    hlen=BP_CHADDR_LEN;
 dummyhost.htype = pkt->bp_htype;
 bcopy(pkt->bp_chaddr, dummyhost.haddr, hlen);
 hashcode = hash_HashFunction(pkt->bp_chaddr, hlen);
 if(!hash_Exists(hwhashtable, hashcode, hwlookcmp, &dummyhost)){
    bcopy(pkt->bp_chaddr,t.haddr,BP_CHADDR_LEN);
    t.hlen=pkt->bp_hlen;
    t.htype=pkt->bp_htype;
    t.giaddr.s_addr=pkt->bp_giaddr.s_addr;
    t.last_seen=now;
    t.from_ip.s_addr=recv_addr.sin_addr.s_addr;
    t.next=NULL;

    u=&unknown;
    while(*u && bcmp((*u)->haddr,t.haddr,BP_CHADDR_LEN))
        u=&(*u)->next;
    if(!*u){
        *u=(struct unknownhost*)smalloc(sizeof(struct unknownhost));
        bcopy(&t,*u,sizeof(struct unknownhost));

        if(num_unknown==MAX_UNKNOWN){
            t2=unknown;
            unknown=unknown->next;
            free(t2);
        } else
                num_unknown++;
		
    } else{
        t2=*u;
        *u=(*u)->next;
        bcopy(&t,t2,sizeof(struct unknownhost));
        while(*u)u=&(*u)->next;
        *u=t2;
    }

 }
}
#endif
