#ifndef LINT
static char *rcsid="serv_common.c,v 1.2 1999/08/30 01:39:53 woods Exp";
#endif

/*
	serv_common.c,v
	Revision 1.2  1999/08/30 01:39:53  woods
	- version.h is not needed with automake
	
	Revision 1.12  1998/07/26 20:17:39  crosser
	LIST command

	Revision 1.11  1998/07/26 14:06:40  crosser
	stupid change made by mistake

	Revision 1.10  1998/07/12 16:43:57  crosser
	Change protocol: responce now is terminated with empty line

	Revision 1.9  1998/07/05 00:26:18  crosser
	Change copyright

	Revision 1.8  1998/07/05 00:01:27  crosser
	add user and group global parms

	Revision 1.7  1998/07/02 18:17:12  crosser
	make expiry work again

	Revision 1.6  1998/07/02 18:01:15  crosser
	change API

	Revision 1.5  1998/07/02 15:37:07  crosser
	make right responce if req invalid

	Revision 1.4  1998/07/01 20:01:05  crosser
	fix chain corruption when replacing

	Revision 1.3  1998/07/01 08:04:58  crosser
	Use lhash,
	make global pseudo-server

	Revision 1.2  1998/07/01 05:18:09  crosser
	minor warnings fix

	Revision 1.1  1998/07/01 05:01:22  crosser
	Initial revision

	Revision 1.3  1998/05/05 19:08:16  crosser
	allow multiline entry (and ignore)

	Revision 1.2  1998/04/29 10:02:21  crosser
	fix TODO list

	Revision 1.1  1998/04/28 18:37:21  crosser
	Initial revision

*/

/*
	WHAT IS IT:
		Implementation of experimental "whoson" protocol
	AUTHOR:
		Eugene G. Crosser <crosser@average.org>
	COPYRIGHT:
		Public domain
*/

#include "config.h"

#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>

#include "lhash.h"

#include "whosond.h"
#include "serv_common.h"
#include "rtconfig.h"
#include "report.h"

#define MAXREQL 1024
#define MAXADDR 32
#define MAXNAME 128
#define CACHESIZE 999999999L

uid_t runuid=0;
gid_t rungid=0;
unsigned long cachesize=CACHESIZE;
unsigned long incache=0L,maxcache=0L;
time_t starttime;
struct timeval oldtime;
double intvl1=0L,intvl2=0L,intvl3=0L;
unsigned long requests=0L;

static int ttl=300; /* ttl is 5 min by default; may set in global section */

typedef struct _crec {
	struct _crec *next;
	struct _crec *prev;
	time_t ttl;
	char addr[MAXADDR];
	char name[MAXNAME];
} crec_t;

static crec_t *first=NULL,*last=NULL;
static time_t now;
static LHASH *chash=(LHASH*)0;

static unsigned long ch_hash(crec_t *entry1)
{
	return lh_strhash(entry1->addr);
}

static int ch_cmp(crec_t *entry1,crec_t *entry2)
{
	return strcmp(entry1->addr,entry2->addr);
}

static char *do_login(char *arg1,char *arg2)
{
	crec_t *newrec,*tmp;

	if (!*arg1) return "*LOGIN addr-spec required";

	if (incache >= cachesize)
		return "-LOGIN Too many entries";
	if ((newrec=(crec_t*)malloc(sizeof(crec_t))) == NULL)
		return "-LOGIN no memory for the new entry";
	strncpy(newrec->addr,arg1,MAXADDR);
	strncpy(newrec->name,arg2,MAXNAME);
	if ((tmp=(crec_t*)lh_insert(chash,(char*)newrec))) {
		incache--;
		DPRINT(("replacing \"%s\" -> \"%s\" with \"%s\" -> \"%s\"\n",
			tmp->addr,tmp->name,newrec->addr,newrec->name))
		if (tmp->next) tmp->next->prev=tmp->prev;
		else last=tmp->prev;
		if (tmp->prev) tmp->prev->next=tmp->next;
		else first=tmp->next;
		free(tmp);
	}
	incache++;
	if (incache > maxcache) maxcache=incache;
	newrec->next=NULL;
	newrec->prev=last;
	newrec->ttl=now+ttl;
	if (last) {
		last->next=newrec;
		last=newrec;
	} else {
		first=last=newrec;
	}
	return "+LOGIN OK";
}

static char *do_logout(char *arg1,char *arg2) {
	crec_t *tmp,dummy_entry;

	if (!*arg1) return "*LOGOUT addr-spec required";

	strncpy(dummy_entry.addr,arg1,sizeof(dummy_entry.addr)-1);
	dummy_entry.addr[sizeof(dummy_entry.addr)-1]='\0';
	tmp=(crec_t*)lh_delete(chash,(char*)&dummy_entry);
	if (tmp) {
		incache--;
		if (tmp->next) tmp->next->prev=tmp->prev;
		else last=tmp->prev;
		if (tmp->prev) tmp->prev->next=tmp->next;
		else first=tmp->next;
		free(tmp);
		return "+LOGOUT record deleted";
	}

	return "+LOGOUT no such record, nothing done";
}

static char *do_query(char *arg1,char *arg2) {
	crec_t *tmp,dummy_entry;
	static char buf[MAXNAME+4];

	if (!(*arg1)) return "*QUERY addr-spec required";
	if (*arg2) return "*QUERY only one argument allowed";

	strncpy(dummy_entry.addr,arg1,sizeof(dummy_entry.addr)-1);
	dummy_entry.addr[sizeof(dummy_entry.addr)-1]='\0';
	tmp=(crec_t*)lh_retrieve(chash,(char*)&dummy_entry);
	if (tmp) {
		buf[0]='+';
		strncpy(buf+1,tmp->name,MAXNAME);
		buf[MAXNAME+1]='\0';
		return buf;
	}

	return "-Not logged in";
}

static char *do_cmd(char *verb,char *arg1,char *arg2) {
	static char buf[512];

	sprintf(buf," version %s build %s\r\n",VERSION,__DATE__);
	sprintf(buf+strlen(buf)," running from %s total %lu requests, cache now %lu, max %lu\r\n",
		asctime(localtime(&starttime)),
		requests,
		incache,
		maxcache);
	sprintf(buf+strlen(buf)," load %10.3f %10.3f %10.3f %10.3f",
		1.0e+6/intvl1,
		1.0e+6/intvl2,
		1.0e+6/intvl3,
		(double)requests/(now-starttime));
	return buf;
}

static void do_cleanup(void) {
	crec_t *prev;

	while (first) {
		if (first->ttl > now) break;

		DPRINT(("expiring \"%s\" -> \"%s\"\n",
				first->addr,first->name))
		prev=first;
		first=first->next;
		if (first) first->prev=NULL;
		(void)lh_delete(chash,(char*)prev);
		free(prev);
		incache--;
	}
	if (!first) last=NULL;
}

void do_request(char *req,char *retbuf,int retsize) {
	char buf[MAXREQL];
	char *p,*endb,*verb,*arg1,*arg2,*rc;
	struct timeval cmdtime;
	unsigned long intvl;

	gettimeofday(&cmdtime,NULL);
	(void)time(&now);
	intvl=(cmdtime.tv_sec-oldtime.tv_sec)*1000000+
		(cmdtime.tv_sec-oldtime.tv_sec);
	intvl1=intvl*100.0e-3+intvl1*900.0e-3;
	intvl2=intvl*010.0e-3+intvl2*990.0e-3;
	intvl3=intvl*001.0e-3+intvl3*999.0e-3;
	oldtime=cmdtime;
	requests++;

	strncpy(buf,req,sizeof(buf)-1);
	buf[sizeof(buf)-1]='\0';
	if ((endb=strchr(buf,'\n'))) *endb='\0';
	if ((endb=strchr(buf,'\r'))) *endb='\0';
	while (isspace(*(endb-1))) *(--endb)='\0';
	DPRINT(("Got: \"%s\"\n",buf))

	p=buf;
	while (p<endb && isspace(*p)) p++;
	verb=p;
	while (p<endb && !isspace(*p)) p++;
	*p='\0';p++;
	while (p<endb && isspace(*p)) p++;
	arg1=p;
	while (p<endb && !isspace(*p)) p++;
	*p='\0';p++;
	while (p<endb && isspace(*p)) p++;
	arg2=p;
	DPRINT(("verb=\"%s\" arg1=\"%s\" arg2=\"%s\"\n",verb,arg1,arg2))

	do_cleanup();

	if (strcasecmp(verb,"LOGIN") == 0) rc=do_login(arg1,arg2);
	else if (strcasecmp(verb,"LOGOUT") == 0) rc=do_logout(arg1,arg2);
	else if (strcasecmp(verb,"QUERY") == 0) rc=do_query(arg1,arg2);
	else if (strcasecmp(verb,"LIST") == 0) rc=do_cmd(verb,arg1,arg2);
	else rc="*Protocol error: no valid verb found";
	DPRINT(("returning: \"%s\"\n",rc))

	strncpy(retbuf,rc,retsize-5);
	retbuf[retsize-5]='\0';
	strcat(retbuf,"\r\n\r\n");
}

int global_serv_cfg_init(void **priv)
{
	chash=lh_new(ch_hash,ch_cmp);
	return 0;
}

int global_serv_cfg_next(char *key,char *val,void **priv)
{
	struct passwd *pw;
	struct group *gr;

	if (strcasecmp(key,"ttl") == 0) {
		ttl=atoi(val);
	} else if (strcasecmp(key,"cachesize") == 0) {
		cachesize=atol(val);
	} else if (strcasecmp(key,"user") == 0) {
		if ((pw=getpwnam(val))) {
			runuid=pw->pw_uid;
			if (!rungid) rungid=pw->pw_gid;
		} else {
			ERRLOG((LOG_ERR,"bad user name \"%s\"",key))
		}
	} else if (strcasecmp(key,"group") == 0) {
		if ((gr=getgrnam(val))) {
			rungid=gr->gr_gid;
		} else {
			ERRLOG((LOG_ERR,"bad group name \"%s\"",key))
		}
	} else {
		ERRLOG((LOG_ERR,"bad keyword \"%s\"\n",key))
		return 1;
	}

	return 0;
}

int global_serv_cfg_end(void **priv)
{
	(void)time(&starttime);
	gettimeofday(&oldtime,NULL);
	return 0;
}

struct _evdesc global_serv_init(void *priv)
{
	struct _evdesc evdesc;

	memset(&evdesc,0,sizeof(struct _evdesc));
	evdesc.fd=-1;
	return evdesc;
}
