#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include "domuser.h"
#include <sys/types.h>
#include <sys/stat.h>
#define DMID 14
#include "debug.h"
#include "keep.h"
#ifdef TESTING
#define DMID 6
#include "dmalloc.h"
#endif

#ifndef FALSE
#define FALSE  0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#include "defines.h"
#include "tcplib.h"
#include "../dftp/mail/cluster.h"

int  imsg(const char * arg_list, ...);
int match_wild(char *p,char *text);
int domuser_dropuser(char *dropuser, int rsize, char *ruser);
#define lib_mtime domuser_lib_mtime
#define mystrtok domuser_mystrtok
#define lib_str_hash domuser_str_hash

int lib_str_hash(char *s, int max);
char *mystrtok(char *start, char *toks, char **upto);
int lib_mtime(char *fname);

typedef struct DOMUSER {char *wild; char *user; struct DOMUSER *next; } Domuser;


// go do support
#define MAXHASH 10000

static Domuser *table[MAXHASH];
static int du_rewrite = TRUE;
char du_file[BFSZ] = "";
time_t du_mtime = 0;

int domuser_iterate(void (*fn)(void *,char *, char *), void *data)
{
	Domuser *d;
	int x,c;

	c = 0;
	for (x=0; x<MAXHASH; x++) {
		for (d=table[x];d!=NULL;d=d->next) {
			fn(data,d->wild,d->user);
			c++;
		}
	}
	return c;
}
int domuser_reload(char *fname, char *reason)
{
	int x,del;
	FILE *f;
	char bf[BFSZ];
	char *upto;
	char *s;
	char oldfile[BFSZ];
	int tot=0;
	char wild[BFSZ];
	char newfile[BFSZ];
	Domuser *d;
	time_t start = time(NULL);

	*reason = '\0';
	strcpy(reason,"None");
	if (lib_strlen(fname)==0) return TRUE;

	strcpy(reason,"Already loaded according to date");
	x = lib_mtime(fname);
	if (du_mtime == x) {
		return TRUE;
	}
	du_mtime = x;

	ncpy(du_file,fname,BFSZ-1);
	f = fopen(fname,"r");
	if (f==NULL) {
		sprintf(reason,"Could not open (%s) %s",fname,strerror(errno));
		return FALSE;
	}

	del = 0;
	domuser_free();
	
	for (;!feof(f);) {
		if (fgets(bf,BFSZ-1,f)==NULL) break;
		s = mystrtok(bf," \t\r\n",&upto);
		if (s==NULL) continue;
		if (*s=='#') continue;
		ncpy(wild,s,BFSZ-1);
		s = mystrtok(NULL," \t\r\n",&upto);
		if (s==NULL) continue;
		tot++;
		if (strcmp(wild,"DEL")==0) {
			domuser_del(s);
			del++;
			continue;
		}
		domuser_add(wild,s);		
	}
	fclose(f);
	//imsg("domuser: loaded %d seconds, %d rules, %d del\n",(int) (time(NULL)-start),tot,del);
	if (del <= 1000) return TRUE;

	if (!du_rewrite) return TRUE;	
	/*only do this once per execution*/
	du_rewrite = FALSE; 

	if (!cluster_is_master()) return TRUE;

	ncpy(newfile,du_file,BFSZ-1);
	ncat(newfile,".tmp",BFSZ-1);
	f = fopen(newfile,"w");
	if (!f) {
		sprintf(reason,"Failed to re-write domuser.dat to (%s): %s",newfile,strerror(errno));
		return FALSE;
	}

	tot = 0;
	for (x=0; x<MAXHASH; x++) {
		for (d=table[x];d!=NULL;d=d->next) {
			fprintf(f,"%s %s\n",d->wild,d->user);
			tot++;
		}
	}

	if (fclose(f)!=0) {
		imsg("domuser: rewrite failed %s\n",strerror(errno));
		return FALSE;
	}

	ncpy(oldfile,du_file,BFSZ-1);
	ncat(oldfile,".old",BFSZ-1);
	if (remove(oldfile) != 0 && errno != ENOENT) {		
		sprintf(reason,"Failed to remove (%s): %s",oldfile,strerror(errno));
		return FALSE;
	}
	rename(du_file,oldfile);

/*	if (remove(du_file) != 0 && errno != ENOENT) {		
		sprintf(reason,"Failed to remove (%s): %s",du_file,strerror(errno));
		return FALSE;
	}
*/
	if (rename(newfile,du_file) != 0) {
		sprintf(reason,"Failed to rename (%s)->(%s): %s",newfile,du_file,strerror(errno));
		return FALSE;
	}

	du_mtime = lib_mtime(du_file);
	imsg("domuser: rewritten %d seconds, %d rules\n",(int) (time(NULL)-start),tot);

	cluster_add_send(du_file);

	return TRUE;
}

int domuser_hash(char *wild)
{
	char *s;
	char tmp[BFSZ];
	s = strchr(wild,'@'); if (s==NULL) s = wild;
	ncpy(tmp,s,BFSZ-1);
	strlwr(tmp);
	return lib_str_hash(tmp,MAXHASH);
}
static Keeplist *keeplist;
int domuser_add(char *wild, char *user)
{
	int h;
	Domuser *d;

	/* RMH: moved to domuser_reload
	if (strcmp(wild,"DEL")==0) {
		domuser_del(user);
		return TRUE;
	}*/
	h = domuser_hash(wild);
	d = keep_malloc_align(sizeof(Domuser),&keeplist);
	memset(d,0,sizeof(Domuser));
	d->user = keep_strdup(&keeplist,user);
	strlwr(d->user);
	d->wild = keep_strdup(&keeplist,wild);
	strlwr(d->wild);
	d->next = table[h];
	table[h] = d;
	return TRUE;
}
int domuser_free(void)
{
	Domuser *d,*dnext;
	int i;
	for (i=0; i<MAXHASH; i++) {
	  for (d=table[i];d!=NULL;d=dnext) {
		dnext = d->next;
//		free(d->user);
//		free(d->wild);
//		free(d);
	  }
	  table[i] = NULL;
	}
	keep_freeall(&keeplist);
	return TRUE;
}
int domuser_count_aliases(char *userdom)
{
	int h;
	Domuser *d;
	int tot=0;
	for (h=0;h<MAXHASH;h++) {
		for (d=table[h];d!=NULL;d=d->next) {
			if (strcmpnc(d->user,userdom)==0) tot++;
		}
	}
	return tot;
}

int domuser_find(char *userdom, char *realuserdom, char *dropuser, int rsize)
{
	int h;
	Domuser *d;
	char *s;
	char xuser[BFSZ];
	if (userdom[0]=='<') userdom++;
	ncpy(xuser,userdom,BFSZ-1);
	strlwr(xuser);
	s = strchr(xuser,'>');
	if (s!=NULL) *s = 0;
	h = domuser_hash(xuser);
	for (d=table[h];d!=NULL;d=d->next) {
		if (match_wildnc(d->wild,xuser)) {
			if (rsize>0) ncpy(realuserdom,d->user,rsize-1); // if rsize is < 0 then realuserdom buffer may not exist. 
			domuser_dropuser(dropuser,rsize,d->user);
			return TRUE;
		}
	}
	return FALSE;
}
Wordlist *domuser_aliases(char *realuserdom)
{
	Wordlist *wlist=NULL;
	Domuser *d;
	int h;
	for (h=0; h<MAXHASH;h++) {
		for (d=table[h];d!=NULL;d=d->next) {
			if (strcmp(realuserdom,d->user)==0) {
				word_add(&wlist,d->wild);
			}
		}
	}
	return wlist;
}
int domuser_count(char *wild, int match_account, int stopat)
{
	Domuser *d;
	int count;
	int h;

	count = 0;
	for(h = 0; h < MAXHASH && (stopat == -1 || count < stopat); h++) {
		for(d = table[h]; d && (stopat == -1 || count < stopat); d = d->next) {
			if (match_wildnc(wild,(match_account)?d->user:d->wild))
				count++;
		}
	}
	return count;
}

int domuser_del(char *xuser)
{
	int h;
	int ret = FALSE;
	Domuser *d;
	Domuser **last;
	h = domuser_hash(xuser);
	last = &table[h];
	for (d=table[h];d!=NULL;d=d->next) {
		if (strcmpnc(xuser,d->wild)==0)  {
			//free(d->user);
			//free(d->wild);
			*last = d->next;
			d->next = NULL;
			//free(d);
			//return TRUE;
			ret = TRUE; // keep going so we remove all duplicates too...
		}
		last = &(d->next);
	}
	return ret;
}
int domuser_dropuser(char *dropuser, int rsize, char *ruser)
{
	char *s,*s2;
	ncpy(dropuser,ruser,BFSZ-1);
	s = strchr(ruser,'@');
	if (s==NULL) return TRUE;
	s2 = strchr(dropuser,'@');
	if (s2==NULL) return TRUE;
	*s2 = 0;
	if ((int) lib_strlen(ruser)+30>rsize) return TRUE;
	strcat(dropuser,"__");
	strcat(dropuser,s+1);
	return TRUE;

}




char *mystrtok(char *start, char *toks, char **upto)
{
	char *s=start;
	if (start!=NULL) *upto = NULL;
	if (s==NULL) s = *upto;
	if (s==NULL) return NULL;
	for (;*s!=0;s++) {
		if (strchr(toks,*s)==NULL) break;
	}
	if (*s==0) return NULL;
	start = s;
	for (;*s!=0;s++) {
		if (strchr(toks,*s)!=NULL) break;
	}
	if (*s==0) {
		*upto = NULL;
		return start;
	}
	*s = 0;
	*upto = s+1;
	return start;
}
#ifdef WIN32
#ifndef __BORLANDC__
//#define _stat stat
//#define _fstat fstat
#endif
#endif
int lib_mtime(char *fname)
{
	struct stat st;
	if (my_stat(fname,&st)!=0) return 0;

	return (int) st.st_mtime;
}

int lib_str_hash(char *s, int max)
{
        int total=0;
        int r;
        int i;
        for (i=0;*s!=0;s++,i++) {
		total = total << 3;
                total += *s;
        }
        r = total % max;
        if (r<0) return -r;
        return r;
}

#ifdef TESTING
int main()
{
	char reason[BFSZ];
	char realuserdom[BFSZ];
	char dropuser[BFSZ];

	d_malloc_init(printf);

	printf("Loading test.dat\n");
	if (!domuser_reload("test.dat",reason)) {
		printf("Not loaded %s\n",reason);
		return FALSE;
	}
	printf("Loading test.dat 2\n");
	if (!domuser_reload("test.dat",reason)) {
		printf("Not loaded %s\n",reason);
		return FALSE;
	}

	if (domuser_find("bob@dom1",realuserdom,dropuser,BFSZ-1)) {
		printf("Found %s = (%s) (%s)\n","bob@dom1",realuserdom,dropuser);
	}
	if (domuser_find("fred@dom2",realuserdom,dropuser,BFSZ-1)) {
		printf("Found %s = (%s) (%s)\n","fred@dom2",realuserdom,dropuser);
	}
	if (domuser_find("fred@dom1",realuserdom,dropuser,BFSZ-1)) {
		printf("Found %s = (%s) (%s)\n","fred@dom1",realuserdom,dropuser);
	}
	domuser_free();
	d_check();

/*
Example output of working program:

  D:\src\testdom\Debug>testdom
Loading test.dat
Loading test.dat 2
Found bob@dom1 = (abob@domain.one) (abob__domain.one)
Found fred@dom2 = (abob@d3omain.one) (abob__d3omain.one)
Found fred@dom1 = (abob@domain.one) (abob__domain.one)
Maximum number of allocated blocks 0
Maximum memory allocated 0 bytes
Number of calls to malloc:0 strdup:0 realloc:0 free:0
All memory correctly freed

D:\src\testdom\Debug>type test.dat
*@dom1 abob@domain.one
xxxb@dom1 abob@2domain.one
fred@dom2 abob@d3omain.one
xx@asdf.asdf asdfsadf


*/

}
#endif
