/***********************************************************
        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.
******************************************************************/
#include "defs.h"
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h>

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

hash_tbl *lastbindhshtbl,*cache2disk;
hashf_tbl *lastbindpermanent;
hashf_tbl *boundpermanent;
int max_cachequeue;
unsigned long flush_interval,flush_alarm;
static int num_in_queue=0;



void free_lastbind(a)
hash_datum *a;
{
struct lastbind *p=a;
	if(p==NULL)
		return;
	if(p->linkcount--)
		return;
	del_bindata(p->cliid);
	del_bindata(p->cliidhw);
	del_bindata(p->vend);
	del_bindata(p->user);
	free(p);
}

boolean lastbindcliidcmp(a,b)
hash_datum *a,*b;
{
struct shared_bindata *aa=a;
struct lastbind *ba=b;
	return	BINDATA_EQ(aa,ba->cliid);
}
boolean lastbindcmp(a,b)
hash_datum *a,*b;
{
struct lastbind *aa=a,*ba=b;
	return	BINDATA_EQ(aa->cliid,ba->cliid);
}
boolean bindfilecmp(a,b)
hash_datum *a,*b;
{
struct shared_bindata *aa=a;
unsigned char *ba=b;
	if(ba[0]!=aa->length)
		return FALSE;
	return !bcmp(ba+1,aa->data,ba[0]);
}

/* flush 'cache2disk' entries to bindings directory */
void	flush_disk()
{
	int i,rc;
	unsigned char buf[MAXELEMENTLEN]; /* XXX MAXELEMENTLEN seems to be much too short.  A flattened lastbind can be as large
									   as LASTBIND_FLAT_MAXLEN, which is larger than MAXELEMENTLEN.  So it seems we 
									   should change this decl.  But I'm not sure it's safe to do so, as we pass this
									   buf to hashf_Replace(), and I'm IT appears to requires its arg to be no longer
									   than MAXELEMENTLEN.
									*/
	unsigned long datalen;
	struct lastbind *p;
	unsigned hashDel_hashcode;

#ifdef DEBUG
	if (debug > 15) {
		report(LOG_DEBUG, "    ------flush_disk() start------");
		report(LOG_DEBUG, "    there are %d entries in cache2disk", num_in_queue);
	}
#endif

	if ((debug>10) && (debug<=15))
		report(LOG_INFO,"Flushing %d lastbindings from cache2disk to disk",num_in_queue);

	while(p=(struct lastbind*)hash_FirstEntry(cache2disk)){

		datalen=0;

#ifdef DEBUG
		if (debug > 15) {
			report(LOG_DEBUG, "    read following entry from cache2disk:");
			report_cliid(LOG_DEBUG,p->cliid);
			report(LOG_DEBUG, "      p->cliidhw parsed value is:");
			report_cliid(LOG_DEBUG, p->cliidhw);
			if (p->vend) {
				report(LOG_DEBUG, "      p->vend parsed value is:");
				report_cliid(LOG_DEBUG, p->vend);
			} else report(LOG_DEBUG, "      p->vend is NULL");
			if (p->user) {
				report(LOG_DEBUG, "      p->user parsed value is:");
				report_cliid(LOG_DEBUG, p->user);
			} else report (LOG_DEBUG, "      p->user is NULL");
		}
#endif

		/* Copy the struct lastbind from *p into buf, flattening it to a char string as we go.
		   It contains all the elements of the struct lastbind, in order, EXCEPT clhashcode and linkcount.
		   It does not contain any holes that were in the struct lastbind, if any.
		*/

		buf[datalen++]=p->cliid->length; /* XXX p->cliid->length is safely <= UCHAR_MAX as long as sizeof(char)==1 */
		
		if (p->cliid->length + datalen >= MAXELEMENTLEN) {
			report(LOG_ERR, "      buf overflow, p->cliid->length=%d", p->cliid->length);
			report(LOG_ERR, "      ...cannot flush this binding to disk.");
			continue;
		}
		bcopy(p->cliid->data,&buf[datalen], p->cliid->length);
		datalen+=p->cliid->length;

		if (datalen + 1 >= MAXELEMENTLEN) {
			report(LOG_ERR, "      buf overflow, about to copy p->cliidhw->length");
			report(LOG_ERR, "      ...cannot flush this binding to disk.");
			continue;
		}
		buf[datalen++]=p->cliidhw->length;

		if (p->cliidhw->length + datalen >= MAXELEMENTLEN) {
			report(LOG_ERR, "      buf overflow, p->cliidhw->length=%d", p->cliidhw->length);
			report(LOG_ERR, "      ...cannot flush this binding to disk.");
			continue;
		}
		bcopy(p->cliidhw->data,&buf[datalen], p->cliidhw->length);
		datalen+=p->cliidhw->length;

		if (datalen + 16 >= MAXELEMENTLEN) { /* XXX assumes sizeof(unsigned long) == 4 */
			report(LOG_ERR, "      buf overflow, about to copy p->last_ip, p->replys, p->last_touch, p->expire");
			report(LOG_ERR, "      ...cannot flush this binding to disk.");
			continue;
		}
		bcopy(&p->last_ip,&buf[datalen],4);
		datalen+=4;
		bcopy(&p->replys,&buf[datalen],4);
		datalen+=4;
		bcopy(&p->last_touch,&buf[datalen],4);
		datalen+=4;
		bcopy(&p->expire,&buf[datalen],4);
		datalen+=4;

		buf[datalen++]=p->code;

		if(p->vend){
			if (datalen + 1 >= MAXELEMENTLEN) {
				report(LOG_ERR, "      buf overflow, about to copy p->vend->length");
				report(LOG_ERR, "      cannot flush this binding to disk.");
				continue;
			}
			buf[datalen++]=p->vend->length;
			if (p->vend->length + datalen >= MAXELEMENTLEN) {
				report(LOG_ERR, "      buf overflow, p->vend->length=%d", p->vend->length);
				report(LOG_ERR, "      ...cannot flush this binding to disk.");
				continue;
			}
			bcopy(p->vend->data,&buf[datalen], p->vend->length);
			datalen+=p->vend->length;
		} else {
			if (datalen + 1 >= MAXELEMENTLEN) {
				report(LOG_ERR, "      buf overflow, about to copy p->vend->length (0)");
				report(LOG_ERR, "      ...cannot flush this binding to disk.");
				continue;
			}
			buf[datalen++]=0;
		}

		if(p->user){
			if (datalen + 1 >= MAXELEMENTLEN) {
				report(LOG_ERR, "      buf overflow, about to copy p->user->length");
				report(LOG_ERR, "      ...cannot flush this binding to disk.");
				continue;
			}
			buf[datalen++]=p->user->length;
			if (p->user->length + datalen >= MAXELEMENTLEN) {
				report(LOG_ERR, "      buf overflow, p->user->length=%d", p->user->length);
				report(LOG_ERR, "      ...cannot flush this binding to disk.");
				continue;
			}
			bcopy(p->user->data,&buf[datalen], p->user->length);
			datalen+=p->user->length;
		} else {
			if (datalen + 1 >= MAXELEMENTLEN) {
				report(LOG_ERR, "      buf overflow, about to copy p->user->length (0)");
				report(LOG_ERR, "      ...cannot flush this binding to disk.");
				continue;
			}
			buf[datalen++]=0;
		}

		/* OK, now we have a flattened version of the lastbind in buf, suitable to write to disk */

		rc=hashf_Replace(lastbindpermanent, p->clhashcode, bindfilecmp, p->cliid, buf, datalen);
		if (rc) {
			report(LOG_ERR, "    error updating lastbindpermanent (disk file) for cliid...");
			report_cliid(LOG_ERR, p->cliid);
			report(LOG_ERR, "       ... p->clhashcode was %u ", p->clhashcode);
#ifdef DEBUG
			report(LOG_ERR, "       hashf_Replace() returned %d", rc);
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "    updated entry in lastbindpermanent (disk file)");
#endif

#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "    deleting binding from cache2disk...");
#endif
		/* Make standalone copy of p->clhashcode and p, because hash_Delete() will free p (thereby freeing p->clhashcode as well),
		   while hashDelete() will still need to read both the hashcode and the key as it walks the rest of the hashtable
		   to locate any other matches which should be deleted. */
		hashDel_hashcode = p->clhashcode;
		/* XXX: we still need to make the deep copy of the struct lastbind pointed to by p.  That's gonna be messy... */
		rc=hash_Delete(cache2disk, hashDel_hashcode, lastbindcmp, p, free_lastbind);
		if (rc) 
			report(LOG_ERR, "    error removing binding from cache2disk");
		else { 
			num_in_queue--;
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "    removed entry from cache2disk");
#endif
		}

	}

	if ((debug>10) && (debug <=15))
		report(LOG_INFO,"Flush complete");

	if (num_in_queue) 
		report(LOG_ERR, "    bindings left in queue=%d (not flushed to disk)",num_in_queue);

	flush_alarm=time(0)+flush_interval;

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    ------flush_disk() end------");
#endif
}


int release_lastbind(hp)
struct host *hp;
{
	struct lastbind *b,t;
	char queue;
	int rc;
	unsigned hashcode, hashDel_hashcode;

#ifdef DEBUG
	if (debug > 15) {
		report(LOG_DEBUG, "    ------release_lastbind() start------");
		report_cliid(LOG_DEBUG, hp->cliid);
		if (! hp->cl) {
			report(LOG_ERR, "    hp->cl is NULL, Huh?...skipping");
			return;
		}
	}
#endif
	hashcode=hash_HashFunction(hp->cl->data,hp->cl->length);
	t.cliid=hp->cl;
	b=(struct lastbind*)hash_Lookup(lastbindhshtbl,hashcode,lastbindcmp,&t);
	if(!b) {
#ifdef DEBUG
		if (debug > 15) {
			report(LOG_DEBUG, "    cliid not found in lastbindhshtbl.  Presumably we only sent a");
			report(LOG_DEBUG, "    DHCPOFFER that the client didn't want.  Skipping lastbind update.");
		}
#endif
		return;
	}

#ifdef DEBUG
	else if (debug > 15)
		report(LOG_DEBUG, "    cliid found in lastbindhshtbl");
#endif
	b->expire=now;
	/* need a hash_Replace here, but will do a delete insert */
#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    deleting binding from cache2disk...");
#endif
	/* We do not need to make copies of b->clhashcode and b here, since b came from the lastbindhashtable; it is
	   not the element we are removing from the cache2disk hashtable */
	rc=hash_Delete(cache2disk, b->clhashcode, lastbindcmp, b, free_lastbind);
	if(rc!= 0) {
		num_in_queue++;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "    cliid was not already in cache2disk, fine");
#endif
	}
#ifdef DEBUG
	else if (debug > 15)
		report(LOG_DEBUG, "    cliid was already in cache2disk, that's ok; removed");
#endif
	b->linkcount++;
#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    b->linkcount incremented to %d", b->linkcount);
#endif
	rc=hash_Insert(cache2disk,b->clhashcode,lastbindcmp,b,b);
	if (rc)
		report(LOG_ERR, "    release_lastbind(): hash_Insert(cache2disk) failed!");
#ifdef DEBUG
	else if (debug > 15)
		report(LOG_DEBUG, "    insert into cache2disk succeeded");
	if (debug > 15)
		report(LOG_DEBUG, "    num_in_queue=%d", num_in_queue);
#endif

	if(num_in_queue>=max_cachequeue) {
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "    calling flush_disk()", num_in_queue);
#endif
		flush_disk();
	}

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    -------release_lastbind() end-------");
#endif

}



int update_lastbind(cl,hp)
struct client_info *cl;
struct host *hp;
{
	struct lastbind *b,t;
	char queue;
	int rc;

    queue=0;
	t.cliid=cl->cliid;

#ifdef DEBUG
	if (debug > 15) {
		report(LOG_DEBUG, "    -------update_lastbind() start-------");
		report(LOG_DEBUG, "    update_lastbind(): for following cliid:");
		report_cliid(LOG_DEBUG,cl->cliid);
		report(LOG_DEBUG, "    cl->cliidhw parsed value is:");
		report_cliid(LOG_DEBUG, cl->cliidhw);
		if (cl->vend) {
			report(LOG_DEBUG, "    cl->vend parsed value is:");
			report_cliid(LOG_DEBUG, cl->vend);
		} else report(LOG_DEBUG, "    cl->vend is NULL");
		if (cl->user) {
			report(LOG_DEBUG, "    cl->user parsed value is:");
			report_cliid(LOG_DEBUG, cl->user);
		} else report(LOG_DEBUG, "    cl->user is NULL");
	}
#endif

	b=(struct lastbind*)hash_Lookup(lastbindhshtbl,cl->clhashcode,lastbindcmp,&t);
	if(!b){
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "    cliid not found in lastbindhshtbl, creating new record");
#endif
		queue++;
		b=(struct lastbind*)smalloc(sizeof(struct lastbind));
		/* bzero(b,sizeof(struct lastbind)); */ /* now is zeroed by smalloc */

		b->clhashcode=cl->clhashcode;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->clhashcode=%d", b->clhashcode);
		if (! cl->clhashcode)
			report(LOG_ERR, "      hashcode should not be zero!");
#endif
		b->cliid=cl->cliid;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->cliid=%p", b->cliid);
		if (! cl->cliid)
			report(LOG_ERR, "      cliid should not be NULL!");
#endif
		b->cliid->linkcount++;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->cliid->linkcount=%d",b->cliid->linkcount);
#endif
		b->cliidhw=cl->cliidhw;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->cliidhw=%p",b->cliidhw);
#endif
		b->cliidhw->linkcount++;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->cliidhw->linkcount=%d",b->cliidhw->linkcount);
#endif
		if(cl->vend){
			b->vend=cl->vend;
			b->vend->linkcount++;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->vend=%p, b->vend->linkcount=%d", b->vend, b->vend->linkcount);
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "      cl->vend==NULL, not setting b->vend, b->vend->linkcount");
#endif
		if(cl->user){
			b->user=cl->user;
			b->user->linkcount++;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->user=%p, b->user->linkcount=%d", b->user, b->user->linkcount);
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "      cl->user==NULL, not setting b->user, b->user->linkcount");
#endif
		b->last_ip.s_addr=hp->iaddr.s_addr;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->last_ip=%s", inet_ntoa(b->last_ip));
#endif
		b->expire=hp->expire;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->expire=%d (%s)", b->expire, cctime(b->expire));
#endif
		b->linkcount=1;
		rc=hash_Insert(lastbindhshtbl,b->clhashcode,nullcmp,b,b);
		if (rc)  {
			report(LOG_ERR, "      update_lastbind(): hash_Insert(lastbindhshtbl) failed for cliid:");
			report_cliid(LOG_ERR,cl->cliid);
		}
	} else {
#ifdef DEBUG
		if (debug > 15) {
			report(LOG_DEBUG, "    cliid was found in lastbindhshtbl, updating existing record");
			report(LOG_DEBUG, "      b->cliidhw parsed value is:");
			report_cliid(LOG_DEBUG, b->cliidhw);
			if (b->vend) {
				report(LOG_DEBUG, "      b->vend parsed value is:");
				report_cliid(LOG_DEBUG, b->vend);
			} else report(LOG_DEBUG, "      b->vend is NULL");
			if (b->user) {
				report(LOG_DEBUG, "      b->user parsed value is:");
				report_cliid(LOG_DEBUG, b->user);
			} else report(LOG_DEBUG, "      b->user is NULL");
		}
#endif
		if(!BINDATA_EQ(b->cliidhw,cl->cliidhw)){
			queue++;
			del_bindata(b->cliidhw);
			b->cliidhw=cl->cliidhw;
			b->cliidhw->linkcount++;
#ifdef DEBUG
			if (debug > 15) {
				report(LOG_DEBUG, "      b->cliidhw != cl->cliidhw; updated b->cliidhw and b->cliidhw->linkcount...");
				report(LOG_DEBUG, "      b->cliidhw=%p, b->cliidhw->linkcount=%d",b->cliidhw, b->cliidhw->linkcount);
			}
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "      b->cliidhw==cl->cliidhw, not changing b->cliidhw and b->cliidhw->linkcount");
#endif
		if(cl->vend && (!b->vend || !BINDATA_EQ(b->vend,cl->vend))){
			queue++;
			del_bindata(b->vend);
			b->vend=cl->vend;
			b->vend->linkcount++;
#ifdef DEBUG
			if (debug > 15) {
				report(LOG_DEBUG, "      cl->vend set, and either !b->vend or b->vend!=cl->vend; updated b->vend and b->vend->linkcount");
				report(LOG_DEBUG, "      b->vend=%p, b->vend->linkcount=%d", b->vend, b->vend->linkcount);
			}
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "      cl->vend==NULL or cl->vend==b->vend; don't need to update b->vend and b->vend->linkcount");
#endif
		if(!cl->vend && b->vend){
			del_bindata(b->vend);
			b->vend=NULL;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "      b->vend set, but cl->vend not; set b->vend to NULL");
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "      not true: !cl->vend && b->vend; don't need to clear b->vend");
#endif
		if(cl->user && (!b->user || !BINDATA_EQ(b->user,cl->user))){
			queue++;
			del_bindata(b->user);
			b->user=cl->user;
			b->user->linkcount++;
#ifdef DEBUG
			if (debug > 15) {
				report(LOG_DEBUG, "      cl->user set, and either !b->user or b->user!=cl->user; updated b->user and b->user->linkcount");
				report(LOG_DEBUG, "      b->user=%p, b->user->linkcount=%d", b->user, b->user->linkcount);
			}
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "      cl->user==NULL, or b->user==cl->user; no need to set b->user and b->user->linkcount");
#endif
		if(!cl->user && b->user){
			del_bindata(b->user);
			b->user=NULL;
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "      b->user set, but cl->user not; set b->user to NULL");
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "      not true: !cl->user && b->user; don't need to clear b->user");
#endif
		if(bcmp(&b->last_ip,&(hp->iaddr.s_addr),4)){
			queue++;
			b->last_ip.s_addr=hp->iaddr.s_addr;
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "      b->last_ip != hp->iaddr; set b->last_ip to %s", inet_ntoa(b->last_ip));
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "      b->last_ip==hp->iaddr; don't need to update b->last_ip");
#endif
		
	}
	b->replys++;
	b->last_touch=now;
	b->expire=hp->expire;
	b->code=hp->code;
#ifdef DEBUG
	if (debug > 15) {
		report(LOG_DEBUG, "      b->replys=%d, b->code=%d", b->replys, b->code);
		report(LOG_DEBUG, "      b->last_touch=%d (%s)", b->last_touch, cctime(b->last_touch));
		report(LOG_DEBUG, "      b->expire=%d (%s)", b->expire, cctime(b->expire));
	}
#endif
	queue++;

	if(queue){	/* test is pointless, but leave for future use */

		/* need a hash_Replace here, but will do a delete insert */
#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    deleting binding from cache2disk...");
#endif
		/* We do not need to make copies of b->clhashcode and b here, since b came from the lastbindhashtable; it is
		   not the element we are removing from the cache2disk hashtable */
		rc=hash_Delete(cache2disk, b->clhashcode, lastbindcmp, b, free_lastbind);
		if(rc!= 0) {
			num_in_queue++;
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "    cliid was not already in cache2disk, so it didn't need to be removed");
#endif
		}
#ifdef DEBUG
		else if (debug > 15)
			report(LOG_DEBUG, "    cliid was already in cache2disk; removed");
#endif
		b->linkcount++;
#ifdef DEBUG
		if (debug > 15)
			report(LOG_DEBUG, "    b->linkcount incremented to %d", b->linkcount);
#endif
		rc=hash_Insert(cache2disk,b->clhashcode,lastbindcmp,b,b);
		if (rc)
			report(LOG_ERR, "    update_lastbind(): hash_Insert(cache2disk) failed");
#ifdef DEBUG
		else
			if (debug > 15)
				report(LOG_DEBUG, "    insert into cache2disk succeeded");
		if (debug > 15)
			report(LOG_DEBUG, "    num_in_queue=%d", num_in_queue);
#endif
		if(num_in_queue>=max_cachequeue) {
#ifdef DEBUG
			if (debug > 15)
				report(LOG_DEBUG, "    calling flush_disk()");
#endif
			flush_disk();
		}
	}

#ifdef DEBUG
	if (debug > 15)
		report(LOG_DEBUG, "    -------update_lastbind() end-------");
#endif
		
}
