#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef WIN32
#include <io.h>
#include <fcntl.h>
#include <windows.h>
#else
#include <signal.h>
#endif
#include "dnews.h"
#include "lib.h"
#include "db.h"
#include "group.h"
#include "memdebug.h"
#include "conf.h"
#include "domain.h"
#include "chan.h"
#include "hist.h"
#include "str.h"
#include "ml.h"
/*

  One files
	ml_limit.dat	Number of users from that ip address or username or area.
  char name[30], short slave_nusers[40], int nibbles; (100 bytes)


if (strlen(elimit)>0) wmsg("Exceeded: %s\n",elimit);
Too many connections: %s %d, limit is %s",thisarea,narea,op[j]);
Too many connections: %s %d, limit is %s",ipname,nsim,op[j]);


*/

#define MAXNAME 50
#define MAXSLAVES 32
#define VER 2
typedef struct REC {
	int ver,rev;
	char name[MAXNAME];
	short nusers[MAXSLAVES];
	int fill[4];
	int nibbles;
} Rec;

typedef struct UNAME {
	char name[MAXNAME]; 
	int ncon; int nibbles; int changed; int fileat;
	int fncon; int fnibbles; /* The file entries, minus our contribution */
	int isuser;
	struct UNAME *next;
} Uname;

int ml_clearat(void);
int ml_zero(void);
int ml_log_failure(char *user,char *area, char *reason);
int ml_blank_file(void);

int ml_isok(int chan, char *reason);
int ml_logout(int chan);
int ml_decrease(Uname *u);
int ml_add(int chan, int nitems, int nbytes, char *reason) ;
FILE *ml_open(void);
int ml_newday(void);
int ml_seekadd(FILE *f, Uname *u);
int ml_update(Uname *u);
char *emsg_time(void);

#undef mymalloc
#undef myfree
#define mymalloc(x) mymalloc2(x,M_ML)
#define myfree(x) myfree2(x,M_ML)

int ml_isearly(void);

static Uname *chan_ip[MAX_CHAN];
static Uname *chan_area[MAX_CHAN];
static int chan_maxcon[MAX_CHAN];
static int chan_maxnib[MAX_CHAN];
static int chan_areanib[MAX_CHAN];
static int chan_maxusers[MAX_CHAN];

static Uname *names;
static int rev;
#define FILE_USERS file_log("users.log")
#define FILE_USERS_OLD file_log("users_old.log")
#define FILE_USERS_OLD2 file_log("users_old2.log")

int ml_login(int chan, char *user, char *area,int maxip, int maxarea, int maxnib, int areanib, char *reason, int isuser)
{
	int r;
        stat_start(X_ML);
	r = ml_connect(chan,user,area,maxip,maxarea,maxnib,areanib, reason,isuser);
	if (!r) {
		ml_log_failure(user,area,reason);
	}
        stat_end(X_ML);
	return r;
}
typedef struct MREPORT {char *user; int times; int day; int used; int max; struct MREPORT *next;} Mreport;
static Mreport *report;
static int nreport;
int ml_report_add(char *user, int used, int max)
{
	Mreport *m;
	int day = 0; /*ml_day();*/
	
	for (m=report;m!=NULL;m=m->next) {
		if (strcmp(m->user,user)==0) {
			if (m->day!=day) m->times++;
			m->day = day;
			return TRUE;
		}
	}
	nreport++;
	if (nreport>5000) return TRUE;
	m = mymalloc(sizeof(Mreport));
	memset(m,0,sizeof(Mreport));
	m->next = report;
	m->user = mystrdup(user,SD_ML);
	m->used = used;
	m->max = max;
	m->times = 0;
	m->day = day;
	report = m;
	return TRUE;
}
int ml_report_free(void)
{
	Mreport *m,*mnext;
	
	nreport = 0;
	for (m=report;m!=NULL;m=mnext) {
		mnext = m->next;
		mystrfree(m->user,SD_ML);
		myfree(m);
	}
	report = NULL;
	return TRUE;
}
String *ml_report_show(void)
{
	static String *x;
	Mreport *m;
	char bf[BFSZ];
	if (x==NULL) x = str_new();
	
	lprintf(bf,BFSZ-1,"                          User        Used       Max\r\n");
	str_addsn(x,bf);
	for (m=report;m!=NULL;m=m->next) {
		lprintf(bf,BFSZ-1,"%30s %10dk %10dk\r\n",m->user,m->used,m->max);
		str_addsn(x,bf);
	}
	return x;
}
int ml_get_current(char *xname, int *nib, int *con)
{
	Uname *u;
	char name[MAXNAME];

	ncpy(name,xname,MAXNAME-1);
	for (u=names; u!=NULL; u=u->next) {
		if (strcmp(u->name,name)==0) break;
	}
	if (u==NULL) {*nib = 0; *con = 0; return TRUE;}
	*nib = u->nibbles+u->fnibbles; 
	*con = u->ncon+u->fncon;
	return TRUE;
}
int ml_isok(int chan, char *reason)
{
	Uname *u;
	char ipname[BFSZ];
	strcpy(reason,"");
	u = chan_area[chan];

	if (ml_isearly()) return TRUE; /* Don't apply rules for first 3 minutes of new day */

	if (strlen(u->name)>0) if (u->ncon>chan_maxusers[chan]) if (chan_maxusers[chan]>0) {
		lprintf(reason,BFSZ-1,"Too many connections: %s %d, limit is %d (area)",u->name,u->ncon+u->fncon,chan_maxusers[chan]);	
		return FALSE;
	}
	u = chan_ip[chan];
	ncpy(ipname,u->name,BFSZ-1);
//printf("connectined ncon %d fncon %d chan maxcon %d (%s)\n",u->ncon,u->fncon,chan_maxcon[chan],u->name);
	if ((u->ncon+u->fncon)>chan_maxcon[chan]) if (chan_maxcon[chan]>0) {
		if (strcmp(u->name,"(noarea)")!=0) 
		if (strcmp(u->name,"127.0.0.1")!=0) 
		if (strcmp(u->name,config_get(C_MYNAME))!=0) {
			lprintf(reason,BFSZ-1,"Too many connections: %s %d, limit is %d",u->name,u->ncon+u->fncon,chan_maxcon[chan]);	
//printf("rejecting user\n");
			return FALSE;
		}
	}
	if (!config_true(C_LIMIT_IP)) {
		if (config_true(C_LIMIT_USERS) || chan_area[chan]->isuser) {
		    if (chan_areanib[chan]!=0) {
			if ((u->nibbles+u->fnibbles)>chan_maxnib[chan]) if (chan_maxnib[chan]>0){
			 	lprintf(reason,BFSZ-1,"Too much downloaded-1: %s %dk, limit is %dk",u->name,u->nibbles/10 +u->fnibbles/10,chan_maxnib[chan]/10);	
			    ml_report_add(u->name,u->nibbles/10 +u->fnibbles/10,chan_maxnib[chan]/10);
			    return FALSE;
			}
    	    	        u = chan_area[chan];
			if ((u->nibbles+u->fnibbles)>chan_areanib[chan]) if (chan_areanib[chan]>0){
			 	lprintf(reason,BFSZ-1,"Too much downloaded-2: %s %dk, limit is %dk",u->name,u->nibbles/10 +u->fnibbles/10,chan_areanib[chan]/10);	
			    ml_report_add(u->name,u->nibbles/10 +u->fnibbles/10,chan_areanib[chan]/10);
			    return FALSE;
			}
			return TRUE;
		    }
		    u = chan_area[chan];
		}
	}
	if ((u->nibbles+u->fnibbles)>chan_maxnib[chan]) if (chan_maxnib[chan]>0){
		lprintf(reason,BFSZ-1,"Too much downloaded-3: %s %s %dk, limit is %dk",ipname,u->name,u->nibbles/10 +u->fnibbles/10,chan_maxnib[chan]/10);	
		ml_report_add(u->name,u->nibbles/10 +u->fnibbles/10,chan_maxnib[chan]/10);
		return FALSE;
	}
	return TRUE;
}

/*int ml_throttle(int chan, char *reason)
{
	Uname *u;
	strcpy(reason,"");
	u = chan_ip[chan];
	if (((u->nibbles+u->fnibbles)/2)>chan_maxnib[chan]) if (chan_maxnib[chan]>0){
		lprintf(reason,BFSZ-1,"ml: too much data %s %dk, limit is %dk",u->name,u->nibbles/10 +u->fnibbles/10,chan_maxnib[chan]);	
		return TRUE;
	}
	return FALSE;
}*/

Uname *ml_findadd(char *xname)
{
	char name[BFSZ];
	FILE *f;
	Uname *u;
	ncpy(name,xname,MAXNAME-1);
	for (u=names; u!=NULL; u=u->next) {
		if (strcmp(u->name,name)==0) return u;
	}
	u = mymalloc(sizeof(Uname));
	memset(u,0,sizeof(Uname));
	ncpy(u->name,name,MAXNAME-1);
	u->fileat = -1;
	u->next = names;
	names = u;

	f = ml_open();
	if (f==NULL) return u;
	ml_seekadd(f,u);
	fclosep(f);
	return u;
}
int ml_clear(int chan, char *user, char *reason)
{
	Uname *u;
/*	ml_zero_user(user);*/
	u = ml_findadd(user);
	lprintf(reason,100,"Was %s %d %d\n",u->name,u->nibbles,u->fnibbles);
	u->nibbles = u->ncon = u->fncon = u->fnibbles= 0;
	u->changed = TRUE;
	u->nibbles = -1;
	if (!ml_update(u)) lprintf(reason,100,"ml_update failed");
	ml_flush();
	return TRUE;
/*

	if (!ml_connect(chan,user,user,999,999,9999999,reason)) return FALSE;
	ml_add(chan,0,int chan, int nitems, int nbytes, char *reason) 
	ml_add(atoi(p1),1,atoi(p2),bf);
	*/
}
int ml_connect(int chan, char *ipnum, char *area,int maxcon, int maxusers, int maxnib, int areanib, char *reason, int isuser)
{
/*	Web Mail with Automatic User Sign Up

	Logout user.
	Add ip number to current list (or inc) and return pointer to it.
	Add area to area list (or inc) and return pointer
	Store pointers from this channel to those two things.
	If either exceeds limits then logoff message and return false
*/
	Uname *u;
	static time_t last;
	static int done,done2;
	if (!done) {
		done = TRUE;
		rev = time(NULL) % 1000;
		imsg("ml: Starting ml initial revision id for file will be %d\n",rev);
	}
	if (!done2) {
		done2 = TRUE;
		imsg("Trying to zero figures for this slave in accounting file\n");
		if (!ml_zero()) done2 =FALSE; /* zero figures for this slave or master */
	}
	dmsg("ml: ml_connect(%s,%s)\n",ipnum,area);
	strcpy(reason,"");
	if ((chan<0) || (chan>=MAX_CHAN)) return FALSE;
	ml_logout(chan); /* Just to be sure or if user login occurs */
	u = ml_findadd(ipnum);
	u->ncon++;
	u->changed = TRUE;
	chan_ip[chan] = u;
	ml_update(u);

	u = ml_findadd(area);
	u->ncon++;
	u->changed = TRUE;
	u->isuser = isuser;
	chan_area[chan] = u;
	chan_maxcon[chan] = maxcon;
	chan_maxusers[chan] = maxusers;
	chan_maxnib[chan] = maxnib;
	chan_areanib[chan] = areanib;
	imsg("chan: Currently %d users (%d,%d) from %s %s\n",u->ncon+u->fncon,u->ncon,u->fncon,u->name,ipnum);
	if (!ml_isok(chan,reason)) {

    	    FILE *f; 
    	    f =  fopen(FILE_USERS,"a");
	    if (f!=NULL) {
    		fprintf(f,"%d ip=%s area=%s con=%d user=%d mb=%d area=%d isuser=%d\n",
		    chan,ipnum,area,maxcon,maxusers,maxnib/100000,areanib/100000,isuser);
		fclose(f);
	    }
	    return FALSE;
	}
	return TRUE;
}
int ml_logout(int chan)
{
	Uname *u;
	if (chan<0) return TRUE;
        stat_start(X_ML);
	u = chan_ip[chan];
	if (u!=NULL) ml_decrease(u);
	u = chan_area[chan];
	if (u!=NULL) imsg("chan: Currently %d users from %s\n",u->ncon+u->fncon-1,u->name);
	if (u!=NULL) ml_decrease(u);
	chan_ip[chan] = chan_area[chan] = NULL;
        stat_end(X_ML);
	return TRUE;
}
int ml_decrease(Uname *u)
{
	if (u->ncon>1) u->ncon--; 
	else u->ncon = 0;
	u->changed = TRUE;
	ml_update(u);
	/* Remove at some later point from linked list if ncon=0 */
	return TRUE;
}
int xml_add(int chan, int nitems, int nbytes, char *reason) ;
int ml_add(int chan, int nitems, int nbytes, char *reason) 
{
	int r;
	stat_start(X_ML);
	r = xml_add(chan,nitems,nbytes,reason);
	stat_end(X_ML);
	return r;
}

int xml_add(int chan, int nitems, int nbytes, char *reason) 
{
	Uname *u;

	dmsg("ml_add: [%d] %d\n",chan,nbytes);
	if (chan_area[chan]==NULL) return TRUE;
	if (!ml_isok(chan,reason)) return FALSE;

	u = chan_ip[chan];
	if (u==NULL) return TRUE;
	u->nibbles += nbytes/100;
	u->changed = TRUE;

	u = chan_area[chan];
	if (u==NULL) return TRUE;
	u->nibbles += nbytes/100;
	u->changed = TRUE;
	return TRUE;
}
char *ml_file(void)
{
	static char bf[BFSZ];
	if (is_reader()) {
		lprintf(bf,BFSZ-1,"ml_limits_%d.dat",atoi(config_get(C_READONLY_ID)));
	} else {
		lprintf(bf,BFSZ-1,"ml_limits.dat");
	}
	return file_work(bf);
}
#define ML_FILE ml_file()
FILE *ml_open(void)
{
	int xtry = 0;	
	Rec rec;
	FILE *f=NULL;

again:
	f = fopen(ML_FILE,"r+b");
	if (f==NULL) {
		if (errno==ENOENT) {
			f = fopen(ML_FILE,"w+b");
			if (f!=NULL) {
				lprintf(rec.name,BFSZ-1,"Version %d",VER);
				rec.ver = VER;
				rec.rev = rev;
				fwrite(&rec,sizeof(rec),1,f);
				fseek(f,0,SEEK_SET);
			}
		}
		if (f==NULL) return NULL;
	}
	if (fread(&rec,sizeof(Rec),1,f)!=1) {
		emsg("Could not read header from %s %s\n",ML_FILE,strerror(errno));
		fclosep(f);
		return NULL;
	}
	if (rec.ver != VER) {
		fclosep(f);
		emsg("Wrong version of %s file, deleting it\n",ML_FILE);
		remove(ML_FILE);
		xtry++;
		if (xtry<3) goto again;
	}
	if (rec.rev!=rev) {
		imsg("Revision number has changed from %d to %d (This is ok)\n",rev,rec.rev);
		ml_clearat();
		rev = rec.rev;
	}
	return f;
}
static int tryagain;
int ml_clearat(void)
{
	Uname *u;
	for (u=names; u!=NULL; u=u->next) {
		u->fileat = -1; 
		u->nibbles = 0;
		u->changed = TRUE;
	}
	return TRUE;
}
int ml_isearly(void)
{
	time_t now;
	struct tm atm;
        struct tm *t; 

        now = time(NULL);
        t = safe_localtime(&now,&atm);
	if (t->tm_hour <2) if (t->tm_min<3) return TRUE;
	return FALSE;

}
int ml_newday(void)
{
	time_t now;
	int k=0;
	struct tm atm;
        struct tm *t; 
	static int last= -2;

	if (is_slave()) {
	    for (;lib_exists(file_work("newday.tmp"));) {
	     lib_msleep(1000);
	     imsg("ml: Waiting for 'newday.tmp' to be removed\n");
	     k++;
	     if (k>300) {
	    	remove(file_work("newday.tmp"));
	    	break;
	     }
	    }
	}
        now = time(NULL);
        t = safe_localtime(&now,&atm);
	if (tryagain) {ml_do_newday(); return TRUE;}
//	if (t->tm_min!=42) return TRUE;
	if (t->tm_hour > 3) return TRUE;
	if (t->tm_wday == last) return TRUE;
	last = t->tm_wday;

	if (is_slave()) {
	    imsg("ml: waiting 3 seconds for ml data to clear\n");
	    lib_msleep(1000*5);
	}
	
	
	if (!is_slave()) {
	    FILE *f;
	    f = fopen(file_work("newday.tmp"),"w");
    	    if (f!=NULL) fclose(f);
	    imsg("ml: Waiting 3 seconds after creating newday.tmp so slaves will notice\n");
    	    lib_msleep(3000);
	}
	ml_do_newday();
	if (!is_slave()) {
    	    remove(file_work("newday.tmp"));
	    remove(file_work("newday.tmp"));
	}
	return TRUE;
} 
int ml_do_newday(void)
{
	FILE *f;
	char fname[BFSZ];
	Uname **last,*next,*u;
	tryagain = FALSE;
	
	if (slave_n()==0) {
		ml_show_areas(-1,TRUE);
		remove(FILE_USERS_OLD2);
		ncpy(fname,FILE_USERS_OLD,BFSZ-1);
		rename(fname,FILE_USERS_OLD2);

		ncpy(fname,FILE_USERS,BFSZ-1);
		rename(fname,FILE_USERS_OLD);
	}

	wmsg("ml: Newday, deleting daily account limit file etc\n");
	ml_report_free();
	last = &names;
	ml_blank_file(); /* Incase removing the fiel fails, blank it as well */
	rev++;
	for (u=names; u!=NULL; u=next) {
		next = u->next;
		u->fileat = -1; 
		u->nibbles = 0;
		u->changed = TRUE;
		if (u->ncon==0) {
			*last = u->next;
			myfree(u);
			imsg("Freed disconnected user info %s\n",u->name);
		} else {
			last = &u->next;
		}
	}
	if (slave_n()!=0) return TRUE;
	if (remove(ML_FILE)!=0) {
		if (errno==ENOENT) return TRUE;
		imsg("Remove of %s failed %s\n",ML_FILE,strerror(errno));
		ml_log_failure("newday failed","newday failed",strerror(errno));
		tryagain = TRUE;
	}
	imsg("Creating a new daily accounting file\n");
	f = ml_open();
	if (f!=NULL) fclosep(f);
	return TRUE;
}
int ml_flush(void)
{
	Uname *u;
	Rec rec;
	FILE *f;
	int t;
	int i;
	int sn = slave_n();
	/* 
		Write changes entries table to data files.
		Read changes into memory
		Open file, For each uname seek, read it, write if changed. 
		If master, if new day create new empty file 
	*/

	int r;
	static time_t last;
	if (sn<0) return FALSE;
	if (sn>=MAXSLAVES) return FALSE;
	if (time(NULL)-last<30) return TRUE;
	last = time(NULL);
	ml_newday();
	f = ml_open();
	if (f==NULL) return TRUE;
	for (u=names; u!=NULL; u=u->next) {
		/*dmsg("Flush %s changed=%d at=%d\n",u->name,u->changed,u->fileat);*/
		if (u->fileat<=0) {
			/* Scan thru file, find or add entry if needed.*/
			/*imsg("Seekadd %s\n",u->name);*/
			ml_seekadd(f,u);
		} 
		if (fseek(f,u->fileat,SEEK_SET)!=0) {
			/*imsg("Seek failed, trying seedadd\n");*/
			ml_seekadd(f,u);
			continue;
		}
		r = fread(&rec,sizeof(rec),1,f);
		if (r!=1) {imsg("fread failed, trying seedadd\n");
				ml_seekadd(f,u); continue;}
		if (strcmp(u->name,rec.name)!=0) {
			imsg("name failed, %d (%s) (%s) trying seedadd\n",u->fileat,u->name,rec.name);
			ml_seekadd(f,u); continue;}
		if (rev!=rec.rev) {
			emsg("Fatal error, revision wrong %d %d\n",rev,rec.rev);
			break;
		}
		if (VER!=rec.ver) {
			imsg("version wrong\n");
			continue;		
		}
		if (u->changed) {
writeit:		rec.nusers[sn] = u->ncon;
			rec.nibbles += u->nibbles; u->nibbles = 0;
			u->fnibbles = rec.nibbles;
			if (fseek(f,u->fileat,SEEK_SET)!=0) filemsg("Fileat failed\n");
			if (fwrite(&rec,sizeof(rec),1,f)!=1) filemsg("Write failed\n");
		} else {
			u->fnibbles = rec.nibbles;
			if (rec.nusers[sn] != u->ncon) {
				emsg("Accounting error, (%s) file %d disagrees with in memory number %d\n",u->name,rec.nusers[sn],u->ncon);
				goto writeit;
			}
		}
		t=0; for (i=0; i<MAXSLAVES; i++) t += rec.nusers[i]; 
		u->fncon = t - u->ncon;
		u->changed = FALSE;
	}
	fclosep(f);
	return TRUE;

}
int ml_update(Uname *u)
{
	
	Rec rec;
	FILE *f;
	int t;
	int i;
	int sn = slave_n();
	int r;
	static time_t last;
	if (sn<0) return FALSE;
	if (sn>=MAXSLAVES) return FALSE;
/*	if (time(NULL)-last<1) return TRUE;*/
	last = time(NULL);
	if (u->fileat<=0) return FALSE;
	f = ml_open();
	if (f==NULL) {
		imsg("ml_update user (%s) could not open file\n",u->name);
		return TRUE;
	}
	if (u->fileat<=0) { /* Must have been reset in ml_open */
		fclosep(f);
		return FALSE;
	}
	if (fseek(f,u->fileat,SEEK_SET)!=0) {
		imsg("Seek failed thats odd %d %s\n",u->fileat,strerror(errno));
		fclosep(f);
		return FALSE;
	} 
	r = fread(&rec,sizeof(rec),1,f);
	if (r!=1) {
		imsg("fread failed\n");
		fclosep(f); 
		return FALSE;
	}
	if (strcmp(u->name,rec.name)!=0) {
		imsg("name failed, %d (%s) (%s) trying seedadd\n",u->fileat,u->name,rec.name);
		fclosep(f);
		return FALSE;
	}
	if (rev!=rec.rev) {
		emsg("Fatal error, revision wrong %d %d\n",rev,rec.rev);
		fclosep(f);
		return FALSE;
	}
	if (VER!=rec.ver) {
		imsg("version wrong ml\n");
		fclosep(f);
		return FALSE;
	}
	rec.nusers[sn] = u->ncon;
	rec.nibbles += u->nibbles;
	if (u->nibbles<0) rec.nibbles = 0;
	u->nibbles = 0;
	u->fnibbles = rec.nibbles;
	if (fseek(f,u->fileat,SEEK_SET)!=0) filemsg("Fileat failed\n");
	if (fwrite(&rec,sizeof(rec),1,f)!=1) filemsg("Write failed\n");
	t=0; for (i=0; i<MAXSLAVES; i++) t += rec.nusers[i]; 
	u->fncon = t - u->ncon;
	u->changed = FALSE;
	fclosep(f);
	return TRUE;
}
int ml_seekadd(FILE *f,Uname *u)
{
	int sn = slave_n();
	Rec rec;
	int r;
	int t;
	int i;
	if (sn<0) return FALSE;
	if (sn>=MAXSLAVES) return FALSE;
	/* Find existing entry, or add one and set fileat */
	fseek(f,0,SEEK_SET);
	for (;!feof(f);) {
		r = fread(&rec,sizeof(rec),1,f);
		if (r<1) break;
		if (strcmp(rec.name,u->name)==0) {
			u->fileat = ftell(f)-sizeof(rec);
			u->fnibbles = rec.nibbles;
			rec.nusers[sn] = u->ncon;
			t=0; for (i=0; i<MAXSLAVES; i++) t += rec.nusers[i]; 
			u->fncon = t - u->ncon;
/*			fseek(f,-sizeof(rec),SEEK_CUR);
			fwrite(&rec,sizeof(rec),1,f);*/
			return TRUE;
		}
	}
	fseek(f,0,SEEK_END);
	u->fileat = ftell(f);
	memset(&rec,0,sizeof(rec));
	ncpy(rec.name,u->name,MAXNAME-1);
	rec.ver = VER;
	rec.rev = rev;
	rec.nusers[sn] = u->ncon;
	rec.nibbles = u->nibbles;
	fwrite(&rec,sizeof(rec),1,f);
	return TRUE;
}

int ml_zero(void)
{
	int sn = slave_n();
	Rec rec;
	int r;
	int e;
	FILE *f;
	int xat;
	if (sn<0) return TRUE;
	if (sn>=MAXSLAVES) return TRUE;

	f = ml_open();
	if (f==NULL) return FALSE;

	fseek(f,0,SEEK_SET);
	r = fread(&rec,sizeof(rec),1,f);
	e = feof(f);
	for (;(!feof(f)) && (!e);) {
		xat = ftell(f);
		r = fread(&rec,sizeof(rec),1,f);
		e = feof(f);
		if (r<1) break;
		if (rec.nusers[sn]!=0) {
			rec.nusers[sn] = 0;
			fseek(f,xat,SEEK_SET);
			fwrite(&rec,sizeof(rec),1,f);
			fseek(f,xat+sizeof(rec),SEEK_SET);
		}
	}
	fclosep(f);
	return TRUE;
}
int ml_blank_file(void)
{
	int sn = slave_n();
	Rec rec;
	int r;
	int e;
	FILE *f;
	int xat;

	f = ml_open();
	if (f==NULL) return FALSE;
	imsg("ml_blank_file called\n");
	fseek(f,0,SEEK_SET);
	r = fread(&rec,sizeof(rec),1,f);
	e = feof(f);
	for (;(!feof(f)) && (!e);) {
		xat = ftell(f);
		r = fread(&rec,sizeof(rec),1,f);
		e = feof(f);
		if (r<1) break;
		rec.nibbles = 0;
		fseek(f,xat,SEEK_SET);
		fwrite(&rec,sizeof(rec),1,f);
		fseek(f,xat+sizeof(rec),SEEK_SET);
	}
	fclosep(f);
	return TRUE;
}

int ml_zero_user(char *user)
{
	int sn = slave_n();
	Rec rec;
	int r;
	int e;
	FILE *f;
	int xat;
	int ndone=0;
	if (sn<0) return TRUE;
	if (sn>=MAXSLAVES) return TRUE;

	f = ml_open();
	if (f==NULL) return FALSE;

	fseek(f,0,SEEK_SET);
	r = fread(&rec,sizeof(rec),1,f);
	e = feof(f);
	for (;(!feof(f)) && (!e);) {
		xat = ftell(f);
		r = fread(&rec,sizeof(rec),1,f);
		e = feof(f);
		if (r<1) break;
		if (strcmp(rec.name,user)==0) {
			rec.nusers[sn] = 0;
			fseek(f,xat,SEEK_SET);
			fwrite(&rec,sizeof(rec),1,f);
			fseek(f,xat+sizeof(rec),SEEK_SET);
			ndone++;
		}
	}
	fclosep(f);
	imsg("ml_blank_file called - done %d records blanked\n",ndone);
	return TRUE;
}
int ml_show(int c)
{
	Rec rec;
	int i;
	Uname *u;
	int r;
	char bf[BFSZ];
	char tmp[BFSZ];
	FILE *f; 
	lprintf(bf,BFSZ-1,"In Memory channels connected for this slave \r\n");chan_write_s(c,bf);

	for (i=0; i<MAX_CHAN; i++) {
		if (chan_area[i]!=NULL) {
			u = chan_area[i];

			lprintf(bf,BFSZ-1,"[%d]   ncon=%d %d /(%d,%d), nibbles=%d %d / %d, changed=%d, fileat=%d %.55s\r\n"
				,i,u->ncon,u->fncon,chan_maxcon[i],chan_maxusers[i]
				,u->nibbles,u->fnibbles,chan_maxnib[i]
				,u->changed,u->fileat,u->name);
			chan_write_s(c,bf);
		}
		if (chan_ip[i]!=NULL) {
			u = chan_ip[i];
			lprintf(bf,BFSZ-1,"[%d]   ncon=%d %d /(%d,%d), nibbles=%d %d / %d, changed=%d, fileat=%d %.55s\r\n"
				,i,u->ncon,u->fncon,chan_maxcon[i],chan_maxusers[i]
				,u->nibbles,u->fnibbles,chan_maxnib[i]
				,u->changed,u->fileat,u->name);
			chan_write_s(c,bf);
		}
	}
	lprintf(bf,BFSZ-1,"In Memory for this slave \r\n");chan_write_s(c,bf);

	for (u=names; u!=NULL; u=u->next) {
		lprintf(bf,BFSZ-1,"   ncon=%d %d, nibbles=%d %d, changed=%d, fileat=%d %.55s\r\n",
			u->ncon,u->fncon,u->nibbles,u->fnibbles,u->changed,u->fileat,u->name);
		chan_write_s(c,bf);
	}

	lprintf(bf,BFSZ-1,"On Disk for this slave \r\n");chan_write_s(c,bf);
	f = ml_open();
	if (f==NULL) return TRUE;
	for (;!feof(f);) {
		r = fread(&rec,sizeof(rec),1,f);
		if (r<1) break;
		lprintf(bf,BFSZ-1,"   %dk %.40s ",rec.nibbles/10,rec.name);
		for (i=0; i<MAXSLAVES; i++) {
			if (rec.nusers[i]!=0) {
				lprintf(tmp,BFSZ-1,"[%d]=%d ",i,rec.nusers[i]);
				strcat(bf,tmp);
			}
		}
		strcat(bf,"\r\n");
		chan_write_s(c,bf);
	}
	fclosep(f);
	chan_write_s(c,"\r\n");
	ml_show_areas(c,FALSE);
	chan_write_s(c,"\r\n");
	return TRUE;
}
int ml_show_chans(int n);
int ml_idle(void)
{
	static int last;

	if (lib_gmtime()-60*90 < last) return TRUE;
	last = lib_gmtime();
	ml_flush();
	ml_show_chans(slave_n());	
	if (slave_n()!=0) return TRUE;
	ml_show_areas(-1,FALSE);
	return TRUE;
}
int ml_show_chans(int n)
{
	String *str;
	FILE *f;
	char fname[BFSZ];
	str = chan_status();
	lprintf(fname,BFSZ-1,"chan_status_%d.log",n);
	f = fopen(file_log(fname),"a");
	if (f==NULL) return FALSE;
	fprintf(f,"---Recorded at: %s\n%s",emsg_time(),str_s(str));
	fclose(f);	

	if (lib_filesize(file_log(fname))>100000) remove(file_log(fname));
	return TRUE;
}
int ml_log_failure(char *user,char *area, char *reason)
{
	FILE *f; 
	f = fopen(FILE_USERS,"a");
	if (f==NULL) return FALSE;
	fprintf(f,"%s Rej: %s %s %s\n",emsg_time(),user,area,reason);
	fclose(f);


	return TRUE;
}

int ml_show_areas(int c, int showall)
{
	Rec rec;
	FILE *f;
	int i;
	char bf[BFSZ];
	int tot;
	int r;
	double tout=0;
	int gcon=0;

	if (c<0) if (slave_n()!=0) return TRUE;
	f = ml_open();
	if (f==NULL) return TRUE;
   
	lprintf(bf,BFSZ-1,"Bytes Read,  Current Connections,  Area/IPrange");
	ml_printf(c,bf);
  	for (;!feof(f);) {
		r = fread(&rec,sizeof(rec),1,f);
		if (r<1) break;
		tot = 0;
		for (i=0; i<MAXSLAVES; i++) tot += rec.nusers[i];
		if (!showall) if (tot==0) continue;
		lprintf(bf,BFSZ-1,"%9dk                %6d   %s ",rec.nibbles/10,tot,rec.name);
		

		ml_printf(c,bf);

		
		gcon += tot;
		tout += rec.nibbles/10;
	}
	lprintf(bf,BFSZ-1,"%9dmb               %6d   Grand Totals",(int) (tout/1000),gcon);
	ml_printf(c,bf);
	fclosep(f);
	if (lib_filesize(FILE_USERS)>9000000) remove(FILE_USERS);
	return TRUE;
}

int ml_printf(int chan, char *bf)
{
	char fname[BFSZ];
	FILE *f;
	char tmp[BFSZ];

	ncpy(fname,FILE_USERS,BFSZ-1);
	if (chan<0) {
		f = fopen(fname,"a");
		if (f!=NULL) {
			fprintf(f,"%s %s\n",emsg_time(),bf);
			fclose(f);
		}
	} else {
		lprintf(tmp,200,"%s\r\n",bf);
		chan_write_s(chan,tmp);
	}
	return TRUE;
}
/*
Test commands 

  ml_show
  ml_login chan username 
  ml_logout chan
  ml_read chan bytes
  ml_newday int ml_do_newday(void)
  
*/


/* dmulti spam_n counting, unique posts per ip address per 30 minutes */

#define FILE_COUNT file_work("postip.dat")
#define MAXPOST 1000
static FILE *pf;
int pf_open(void)
{
	int i;
	if (pf!=NULL) {
		emsg("pf was not null\n");
		abort();
	}
	for (i=0; i<10; i++) {
		pf = fopen(FILE_COUNT,"r+b");
		if (errno==ENOENT) if (pf==NULL) {
			pf = fopen(FILE_COUNT,"w+b");
			if (pf==NULL) {
				filemsg("Could not create %s\n",FILE_COUNT);
				return FALSE;
			}
		}
		if (pf!=NULL) return TRUE;
	}
	return FALSE;
}
typedef struct {char ip[24]; int npost;} Postrec;
int pf_clear(void)
{
	Postrec r;
	int i;
	if (!pf_open()) return FALSE;
	strcpy(r.ip,"");
	r.npost = 0;
	for (i=0; i<MAXPOST; i++) {
		fwrite(&r,sizeof(r),1,pf);
	}
	fclosep(pf); pf = NULL;
	return TRUE;
}
int pf_show(int chan) 
{
	Postrec r;
	int i;
	char bf[BFSZ];
	if (!pf_open()) return FALSE;
	for (i=0; i<MAXPOST; i++) {
		if (fread(&r,sizeof(r),1,pf)!=1) break;
		lprintf(bf,BFSZ-1," [%d] %d %.80s\r\n",i,r.npost,r.ip);
		if (strlen(r.ip)>0) chan_write_s(chan,bf);
	}
	fclosep(pf); pf = NULL;
	return TRUE;
}
int pf_age(int nage)
{
	Postrec r;
	int i;
	if (!pf_open()) return FALSE;
	for (i=0; i<MAXPOST; i++) {
		if (fseek(pf,i*sizeof(r),SEEK_SET)!=0) filemsg("couldn't seek?? \n");	
		fread(&r,sizeof(r),1,pf);
		if (fseek(pf,i*sizeof(r),SEEK_SET)!=0) filemsg("couldn't seek?? \n");	
		r.npost -= nage;
		if (r.npost<0) r.npost = 0;
		fwrite(&r,sizeof(r),1,pf);
	}
	fclosep(pf); pf = NULL;
	return TRUE;
}
int pf_addone(char *ip, int nadd)
{
	/* Find ip in file, add if missing, add one to counter, return counter */
	Postrec r;
	int nslave,islave;
	static int upto; 
	if (!pf_open()) return 1;
	for (;!feof(pf);) {
		if (fread(&r,sizeof(r),1,pf)!=1) break;
		if (strcmp(r.ip,ip)==0) {
			if (fseek(pf,- (int) sizeof(r),SEEK_CUR)!=0) {
				filemsg("Could not seek back in pfaddone\n");	
				break;
			}
			r.npost += nadd;
			if (r.npost<0) r.npost = 0;
			if (fwrite(&r,sizeof(r),1,pf)!=1) {
				filemsg("Could not update record in pf data\n");
			}
			fclosep(pf); pf = NULL;
			return r.npost;
		}
	}
	nslave = atoi(config_get(C_SLAVE_N));
	islave = slave_n();
	if (nslave<=0) nslave = 1;
	if (upto==0) upto = islave;
	upto += nslave;
	if (upto>=MAXPOST) upto = islave;

	if (fseek(pf,upto*sizeof(r),SEEK_SET)!=0) filemsg("couldn't seek?? \n");
	ncpy(r.ip,ip,22);
	r.npost = nadd;
	if (fwrite(&r,sizeof(r),1,pf)!=1) filemsg("Could not write to pf file\n");	
	fclosep(pf); pf = NULL;
	return r.npost;
}