// Sam module, simple user database.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "debug.h"
#include "lib.h"
#include "lprintf.h"
#include "keep.h"
#include "userdb.h"

#define DATFILE "userdb.dat"
#define ADDFILE "userdb.add"

static char datfile[BFSZ];
static char addfile[BFSZ];
static int nusers;
// test2@gold:{ssha}q01QJBRQRqa+trCjbfOSEPszZqmqb4X8:created="1221000198" fwd="test1,fred@here.com,test3,$user"
// Format of file.. (lets match nwauth format?)

static Keeplist *k;
typedef struct REC {char *user; char *pass; Pair *info; struct REC *next;} Rec;
#define HASH 7000
int userdb_decrypt(Pair *info);
static Rec *table[HASH]; 
Rec *userdb_find(char *user);
int userdb_encrypt(char *var, char *val, char *out);
int userdb_compress(char *datfile, char *addfile);
int userdb_rec_add(Rec *r)
{
	int h = lib_str_hash(r->user,HASH);
	r->next = table[h];
	table[h] = r;
	return TRUE;
}
Rec *userdb_add(char *user)
{
	Rec *r;
	r = calloc(1,sizeof(Rec));
	r->user = keep_strdup(&k,user);
	userdb_rec_add(r);
	return r;
}
int userdb_exists(char *user)
{
	if (userdb_find(user)==NULL) return FALSE;
	return TRUE;
}
int userdb_load_line(char *bf)
{
	Rec *r;
	char *s;
	char *upto;
	char info[BFSZ];
	char pass[BFSZ];
	char user[BFSZ];
	Pair *p;
	strcpy(info,"");
	s = mystrtok(bf,":\r\n",&upto); if (s==NULL) return FALSE;
	ncpy(user,s,BFSZ-1);
	s = mystrtok(NULL,":\r\n",&upto);  if (s==NULL) return FALSE;
	ncpy(pass,s,BFSZ-1);
	s = mystrtok(NULL,"\r\n",&upto); 
	if (s!=NULL) ncpy(info,s,BFSZ-1);
	
	r = userdb_find(user);
	if (r==NULL) {
		r = userdb_add(user);
	}
	// don't need to free as not using malloc ?   but do need to free the 'pair' info... and should compress this data at some point...

	if (strcmp(pass,"___DELETE___")==0) strcpy(r->user,"*"); // DELETED USER 
//	r->user = keep_strdup(&k,user);
	r->pass = keep_strdup(&k,pass);
	p = r->info; r->info = NULL;
	if (p) pair_free(&p); 
	r->info = pair_token_decode(info);
	userdb_decrypt(r->info);

	nusers++;
	return TRUE;
}
int userdb_load(char *fname)
{
	FILE *f;
	char bf[BFSZ];
	f = fopen(fname,"r");
	if (f==NULL) return FALSE;
	for (;!feof(f);) {
		if (fgets(bf,BFSZ-1,f)==NULL) break;
		userdb_load_line(bf);
	}
	fclose(f);
	return TRUE;
}
static char datfile[BFSZ];
static char addfile[BFSZ];
static char encrypt_fields[BFSZ]; // any matching field is encrypted in the database...
int userdb_set_encrypt(char *fields)
{
	ncpy(encrypt_fields,fields,BFSZ-1);
	return TRUE;
}
int userdb_init(char *path)
{
	// Load in main file and add file.
	lib_mkdir(path,0770);
	lprintf(datfile,BFSZ-1,"%s/%s",path,DATFILE);
	lprintf(addfile,BFSZ-1,"%s/%s",path,ADDFILE);
	userdb_load(datfile);
	userdb_load(addfile);
	printf("Database loaded, %d users found\n",nusers);
	userdb_compress(datfile,addfile);
	return TRUE;
}
int userdb_compress(char *datfile, char *addfile)
{
	// write out new file, if good then delete old and rename...
	FILE *f;
	int h;
	Rec *r;
	Pair *p;
	int i=0;
	char newfile[BFSZ];
	lprintf(newfile,BFSZ-1,"%s_new",datfile);
	f = fopen(newfile,"wb");
	if (f==NULL) return FALSE;
	
	for (h=0; h<HASH; h++) {
		for (r=table[h];r!=NULL;r=r->next) {
			fprintf(f,"%s:%s:",r->user,r->pass);
			for (p=r->info;p!=NULL;p=p->next) {
				char out[BFSZ];
				if (strlen(p->val)==0) continue;
				userdb_encrypt(p->var,p->val,out); // encrypt secret things.
				fprintf(f," %s=\"%s\"",p->var,out); // SHOULD ESCAPE QUOTES HERE.
			}
			fprintf(f,"\n");
		}
	}	
	if (fclose(f)==0) {
		//imsg("Renaming (%s) to (%s)\n",newfile,datfile);
		remove(datfile);
		remove(addfile);
		rename(newfile,datfile);
	}
	return TRUE;
}


Rec *userdb_find(char *user)
{	
	int h = lib_str_hash(user,HASH);
	Rec *r;
	for (r=table[h];r!=NULL;r=r->next) {
		if (strcmp(r->user,user)==0) return r;
	}
	return NULL;
}
int userdb_check(char *user, char *pass, Pair **info)
{
	Rec *r = userdb_find(user);
	*info = NULL;
	if (r==NULL) return FALSE;
	*info = r->info; // I tink this should make a copy and the 'caller' should free it, but need to change function to do that...
	if (pass) {
		if (strcmp(pass,r->pass)==0) return TRUE;
		return FALSE;
	}
	return TRUE;
}
int userdb_delete(char *user)
{
	Rec *r = userdb_find(user);
	if (r==NULL) return FALSE;
	strcpy(r->user,"*");
	userdb_set(user,"___DELETE___",NULL);
	return TRUE;
}
int userdb_encrypt(char *var, char *val, char *out)
{
	ncpy(out,val,BFSZ-1);
	if (strstrnc(encrypt_fields,var)!=NULL) {
		if (strncmpnc(val,"{enc}",5)!=0) {
				int len = strlen(val);
				char bin[BFSZ];
				char tmp[BFSZ];
				ncpy(bin,val,BFSZ-1);
				lib_encrypt(bin,len,"MagicSecretKeyGoesHere"); // do not change! 
				lprintf(out,BFSZ-1,"{enc}%s",lib_bin_to_hex(bin,len,tmp,BFSZ-1));
		}
	}
	return TRUE;
}
int userdb_decrypt(Pair *info)
{
	Pair *p;
	for (p=info;p!=NULL;p=p->next) {
	 if (strstrnc(encrypt_fields,p->var)!=NULL) {
		if (strncmpnc(p->val,"{enc}",5)==0) {
				char bin[BFSZ];
				int len;
				lib_hex_to_bin(p->val+5,bin,&len);
				lib_decrypt(bin,len,"MagicSecretKeyGoesHere");
				bin[len] = 0;
				//printf("Decrypted (%s) to (%s)\n",p->val,bin);
				free(p->val); p->val = strdup(bin);

		}
	 } 
	}
	return TRUE;
}
int userdb_set(char *user, char *pass, Pair *info)
{
	Pair *p;
	FILE *f;
	Rec *r = userdb_find(user);
	if (r==NULL) r = userdb_add(user);
	r->info = pair_dup(info);
	if (strlen(pass) > 0) r->pass = keep_strdup(&k,pass);
	
	f = fopen(addfile,"a");
	if (f==NULL) return FALSE;
	fprintf(f,"%s:%s:",user,r->pass);
	for (p=info;p!=NULL;p=p->next) {
		char out[BFSZ];
		if (strlen(p->val)==0) continue;
		userdb_encrypt(p->var,p->val,out); // encrypt secret things.
		fprintf(f," %s=\"%s\"",p->var,out); // SHOULD ESCAPE QUOTES HERE.
	}
	fprintf(f,"\n");
	fclose(f);
	return TRUE;

}
int userdb_scan(int (*callback)(void *base, char *user, char *pass, Pair *info), void *base, char *match, int max)
{
	int h;
	Rec *r;
	int i=0;
	for (h=0; h<HASH; h++) {
		for (r=table[h];r!=NULL;r=r->next) {
			if (strlen(match)>0) if (strstrnc(r->user,match)==NULL) continue;
			i++;
			if (i>max) continue;
			if (strcmp(r->user,"*")==0) continue;
			if (!callback(base,r->user,r->pass,r->info)) break;
		}
	}	// Traverse users and callback function for each user.
	return TRUE;
}
int userdb_count(void)
{
	int h;
	Rec *r;
	int tot=0;
	for (h=0; h<HASH; h++) {
		for (r=table[h];r!=NULL;r=r->next) {
			tot++;
		}
	}	// Traverse users and callback function for each user.
	return tot;

}
char *userdb_path(char *user, char *file, char *tmp)
{
	lprintf(tmp,BFSZ-1,"userfiles/%.3s/%s/%s",user,user,file);
	return tmp;
}
int userdb_file_write(char *user, char *file, char *extra, char *reason)
{
	char fname[BFSZ];
	FILE *f;
	lprintf(fname,BFSZ-1,"userfiles/%.3s/%s/%s",user,user,file);
	f = lib_fopen_create(fname,"wb",0777);
	if (f==NULL) {lprintf(reason,BFSZ-1,"%s %s",fname,strerror(errno)); return FALSE;}
	fwrite(extra,1,strlen(extra),f);
	fclose(f);
	return TRUE;
}
char *userdb_file_read(char *user, char *file, char *reason) 
{
	char fname[BFSZ];
	FILE *f;
	char *ex=NULL;
	int sz;
	lprintf(fname,BFSZ-1,"userfiles/%.3s/%s/%s",user,user,file);
	f = fopen(fname,"rb");
	if (f==NULL) {lprintf(reason,BFSZ-1,"%s %s",fname,strerror(errno)); return NULL;}
	sz = lib_file_osize(f); 
	if (sz<=0) sz = 1;
	ex = malloc(sz+2);
	fread(ex,1,sz,f);
	ex[sz] = 0;
	fclose(f);
	return ex;
}
