#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 "chan.h"
#include "hist.h"
char *get_ahead(String *sitem, char *head);

#ifdef WIN32
#define SPAWN_FILTER 1
#endif
#ifdef isunix
#define SPAWN_FILTER 1
#endif

static int filter_total,filter_nrej;
static int total_done;

#ifdef SPAWN_FILTER


#ifdef WIN32
#include <windows.h>
#include <process.h>
#include <io.h>
#include <sys\stat.h>
#include <direct.h>
#else
#include <sys/types.h>
#include <sys/wait.h>
#endif

#include "lib_spawn.h"
#undef mymalloc
#undef myfree
#define mymalloc(x) mymalloc2(x,M_FILTER)
#define myfree(x) myfree2(x,M_FILTER)

static int pid;
static int pin,pout;
int nocem_issuer(char *issuer, char *type);
void filter_start(void);
void filter_init(void)
{
	filter_start();
}
void filter_start(void)
{
        static char *env[100];
	char errmsg[1000];
        env[0] = NULL;
	if (strlen(config_get(C_FILTER_CMD))==0) return ;
        pid = lib_spawn(config_get(C_FILTER_CMD), env, &pin, &pout, errmsg );
	imsg("Spawned Filter (%.80s) pid=%d\n",config_get(C_FILTER_CMD),pid);
	if (!pid) { 
		filemsg("Spawn failed %.80s %.80s\n",config_get(C_FILTER_CMD),errmsg); 
		return ; 
	}
#ifdef WIN32
	_setmode(pin,_O_BINARY);
	_setmode(pout,_O_BINARY);
#endif
}
void filter_idle(void)
{
	if (strlen(config_get(C_FILTER_CMD))==0) return ;
	if (pid>0) return;
	filter_start();
}
char *showbytes(char *s)
{
	static char bf[BFSZ];
	char *out=bf;
	for (;*s!=0;s++) {
		lprintf(out,BFSZ-1,"%d ",*s);
		out += strlen(out);
	}
	return bf;
}
#ifdef WIN32
#define RESULT_LEN 60
#else
#define RESULT_LEN 5
#endif
int filter_isok(char *item, int len)
{
	int r;
	char bf[1000];
	int dosync = config_true(C_FILTER_SYNC);
	int max = atoi(config_get(C_SPAWN_FILTER_MAX));
	static int sync;
	char tmp[BFSZ];
	char *s;
	filter_total++;
	

	if (pid<=0) {
		imsg("filter: Filter dead so accepting message anyway\n");
		return 236;
	}

	if (max>0) {
		if (len>max) return 235;
	}

	dmsg("lib: filter_write %d bytes\n",len);
	sync++;
	if (dosync) {
		lprintf(tmp,100,"%09d\r\n",sync);
		if (spawn_write(pin,tmp,strlen(tmp))!=(int) strlen(tmp)) {
			filemsg("filter: Could not write to filter\n");
			filter_close();
			return 503;
		}
	}
	if (spawn_write(pin,item,len)!=len) {
		filemsg("filter: Could not write to filter\n");
		filter_close();
		return 503;
	}
	if (spawn_write(pin,".\r\n",3)!=3) {
		filemsg("filter: Could not write to filter\n");
		filter_close();
		return 503;
	}

	bf[0] = 0;
	r = spawn_read_timed(pout,bf,RESULT_LEN,60);
	bf[RESULT_LEN] = 0;
	imsg("filter: Response (%s) %d\n",bf,strlen(bf));
	dmsg("lib: filter_read returned %d bytes (%.80s) \n",r,showbytes(bf));
	if (r<5) {
		filemsg("filter: Reading from filter failed\n");
		filter_close();
		return 503;
	}
	s = bf;
	if (dosync) {
		if (atoi(bf)!=sync) {
			filemsg("filter: SYNC LOST read (%s) wanted %d\n",bf,sync);
			filter_close();
			return 503;
		}
		s = strchr(bf,' ');
		if (s==NULL) {
			filemsg("filter: SYNC LOST no space after sync (%s) wanted %d nnn\n",bf,sync);
			filter_close();
			return 503;
		}
	}
	if (atoi(s)<100) {
		emsg("filter: Bad response (%s) %d\n",s,strlen(s));
		filter_close();
		return 503;
	}
	if (atoi(s)>=400) filter_nrej++;
	return atoi(s);
}
void filter_close(void)
{
	if (pid<=0) return;
	imsg("Closing down filter process\n");
	spawn_close(pin);
	spawn_close(pout);
	if (lib_spawn_wait(pid)) spawn_kill_h(pid);
	pin = pout = pid = 0;
}


#else
int filter_isok(char *item, int len)
{
	if (strlen(config_get(C_FILTER_CMD))==0) return TRUE;
	imsg("Filters not implemented in this version of DNEWS sorry\n");
	return TRUE;
}
void filter_close(void)
{
}
void filter_init(void)
{
}
#endif

/* 
	if item is not in news.filters or alt.nocem.misc, then ignore it.

	Scan for References header, if it exists then ignore

	Scan for @@NCM (not sure about NCM, check)

	Search for "-----BEGIN PGP SIGNED MESSAGE-----"
	Save in temp file, until you get to
	Stop saving after "-----END PGP SIGNATURE-----"
	Search for "@@BEGIN NCM HEADERS"
	Extract "Issuer:" "Type:"
	Check "Issuer and type are approved by file"
		Process linked config file.
			AutoMoose-1		spam		yes
			clewis@ferret		spam		yes
			clewis@ferret		mmf		no
	Check PGP signature
	Reopen file and pull
		Search for "@@BEGIN NCM BODY"
		Append message-id's and groups in nocem_q.dat
	Done.

*/	
#define NOCEM_QUE file_work("nocem.que")
#define NOCEM_BSY file_work("nocem.bsy")
#define FILE_PGP file_work("nocem.tmp")
#define FILE_RULES file_config("nocem.conf")
#define matchstr(bf,x) strncmp(bf,x,strlen(x))
int nocem_pgp(char *issuer);
int nocem_issuer(char *issuer, char *type) ;
void nocem_rules_free(void);
static int total_qued;
static int gotsome;
static int nocem_accepted,nocem_rejected;
int nocem_filter(String *item, char *grps[], int ngrps)
{
	char *s;
	char bf[BFSZ];
	char issuer[BFSZ];
	char type[BFSZ];
	int inpgp = FALSE;
	int inhdrs = FALSE;
	int initems = FALSE;
	int nqued = 0;
	int upto;
	int i;
	char mid[BFSZ];
	char filter_groups[BFSZ];
	FILE *fpgp,*fque;
        if (item==NULL) return FALSE;
	strcpy(issuer,"None");
	strcpy(type,"None");
	
	ncpy(filter_groups,config_get(C_NOCEM_GROUPS),BFSZ-1);
	if (strlen(filter_groups)==0) strcpy(filter_groups,"news.lists.filters,news.filters.lists,alt.nocem.misc");

	for (i=0; i<ngrps; i++) {
		if (group_match(filter_groups,grps[i])) goto doit;
	}
	dmsg("nocem: NoCem, Not to any of required groups\n");
	return FALSE;
	
doit:
	ncpy(mid,get_ahead(item,"Message-ID:"),100);
	
	if (strstr(get_ahead(item,"Subject:"),"@@NCM")==NULL) {
		dmsg("nocem: NoCem, No @NCM found in subject\n");
		return FALSE;		
	}
	if (strlen(get_ahead(item,"References:"))>0) {
		dmsg("nocem: NoCem, Ignoring as contained references header\n");
		return FALSE;		
	}
	s = str_s(item);
        if (s==NULL) {
		imsg("NoCem: null item\n");
		return FALSE;
	}
	
	upto = 0;
	fpgp = fopen(FILE_PGP,"w");
	if (fpgp==NULL) {
		filemsg("NoCem: Unable to write to %.80s\n",FILE_PGP);
		return FALSE;
	}
	for (;TRUE;) {
		if (!str_getline(item,&upto,bf,BFSZ-10)) break;
		if (matchstr(bf,"-----BEGIN PGP SIGNED MESSAGE-----")==0) inpgp = TRUE;
		if (inpgp) fprintf(fpgp,"%s\n",bf);
		if (matchstr(bf,"@@BEGIN NCM HEADERS")==0) inhdrs = TRUE;
		if (inhdrs) {
			if (matchstr(bf,"Issuer: ")==0) ncpy(issuer,bf+8,200);
			if (matchstr(bf,"Type: ")==0) ncpy(type,bf+6,200);
			if (matchstr(bf,"@@BEGIN NCM BODY")==0) inhdrs = FALSE;
		}
		if (matchstr(bf,"-----END PGP SIGNATURE-----")==0) inpgp = FALSE;
	}
	fclosep(fpgp); fpgp = NULL;

	if (!nocem_issuer(issuer,type)) {
		imsg("Ignoring NoCem from (%.80s) type (%.80s), not allowed in (%.80s)\n",issuer,type,FILE_RULES);
		nocem_rejected++;
		return FALSE;
	}

	if (!nocem_pgp(issuer)) {
		imsg("Ignoring NoCem from (%.80s) type (%.80s), PGP verification failed\n",issuer,type);
		nocem_rejected++;
		return FALSE;
	}
	
	fque = myfopen(NOCEM_QUE,"a"); 
	if (fque==NULL) {filemsg("NoCem Can't append to %.80s\n",NOCEM_QUE); return FALSE; }
	fpgp = myfopen(FILE_PGP,"r");

	if (fpgp==NULL) {filemsg("NoCem can't read %.80s",FILE_PGP); fclosep(fque); return FALSE; }
	for (;!feof(fpgp);) {
		if (fgets(bf,BFSZ-10,fpgp)==NULL) break;
		if (matchstr(bf,"@@BEGIN NCM BODY")==0) {initems = TRUE; continue;}
		if (matchstr(bf,"@@END NCM BODY")==0) initems = FALSE;
		if (!initems) continue;
		if (bf[0]=='<') {
		 nqued++;
		 fprintf(fque,".\n");
		}
		fprintf(fque,"%s",bf);
	}
	fprintf(fque,".\n");
	fclosep(fpgp);
	fclosep(fque);
	total_qued += nqued;
	gotsome = TRUE;
	imsg("NoCem: %.80s Accepted (%.80s) type (%.80s) %d qued, total=%d\n",mid,issuer,type,nqued,total_qued);
	nocem_accepted++;
	return TRUE;
}
/*
AutoMoose-1		spam		yes
clewis@ferret		spam		yes
clewis@ferret		mmf		yes
*/
int nocem_nqued(void)
{
	return total_qued;
}
typedef struct RLIST {char *issuer; char *type; struct RLIST *next;} Rlist;
static Rlist *rules;
int nocem_load(void)
{
	FILE *f;
	char *p[200];
	int n;
	char bf[BFSZ];
	Rlist *r;

	gotsome = TRUE;
	nocem_rules_free();
	f = fopen(FILE_RULES,"r");
	if (f==NULL) {
		fimsg("Could not find (%.80s), this file must exist for nocem processing\n",FILE_RULES);
		return FALSE;
	}
	for (;!feof(f);) {
		if (fgets(bf,BFSZ-100,f)==NULL) break;
		n = token_split(bf,p," \t\r\n",100);
		if (n<3) continue;
		if (strncmp(p[0],"#",1)==0) continue;
		if (strcmp(p[2],"yes")!=0) continue;
		if (strlen(p[0])<2) continue;
		r = mymalloc(sizeof(Rlist));
		r->issuer = mystrdup(p[0],SD_FILTER2);
		strlwr(r->issuer);
		r->type = mystrdup(p[1],SD_FILTER2);
		r->next = rules;
		rules = r;
	}
	fclosep(f);
	return TRUE;
}
char *filter_status(void)
{
	static char bf[BFSZ];
	char *out=bf;
	strcpy(bf,"");
	if (config_true(C_NOCEM)) {
	 lprintf(out,BFSZ-1,"NoCem deleted %d, queued %d From %d posts, Rejected %d posts\r\n",total_done,total_qued-total_done,nocem_accepted,nocem_rejected);
	 out+= strlen(out);
	}

	if (strlen(config_get(C_FILTER_CMD))>0) {
	 if (filter_total>0) lprintf(out,BFSZ-1,"Filter_Cmd rejected %d %3.2f%%\r\n",filter_nrej,100.0 * (double) filter_nrej/ (double) filter_total);
	 out+= strlen(out);	
	}
	return bf;
}
int nocem_issuer(char *xissuer, char *type) 
{
	Rlist *r;
	char issuer[BFSZ];
	ncpy(issuer,xissuer,200);
	strlwr(issuer);
	for (r = rules; r!=NULL; r=r->next) {
		if (strstr(issuer,r->issuer)!=NULL) {
		  if (strcmpnc(type,r->type)==0) return TRUE;
		  if (strcmp(r->type,"*")==0) return TRUE;
		}
	}
	return FALSE;
}
void nocem_rules_free(void)
{
	Rlist *r,*rnext;
	for (r=rules; r!=NULL; r=rnext) {
		rnext = r->next;
		mystrfree(r->issuer,SD_FILTER2);
		mystrfree(r->type,SD_FILTER2);
		myfree(r);
	}
	rules = NULL;
}
int nocem_pgp(char *issuer)
{
	int r;
	String *xitem;

	if (config_true(C_NOCEM_SKIPPGP)) return TRUE;
	xitem = str_new();
	str_copysn(xitem,FILE_PGP);
	r = pgp_verify(xitem,issuer);
	str_free(xitem);
	return r;
}
/*
	#define NOCEM_QUE file_work("nocem.que")
	#define NOCEM_BSY file_work("nocem.bsy")
*/
typedef struct GLIST {char *gname; struct GLIST *next; } Glist;
void glist_add(Glist **glist, char *s)
{
	Glist *g;
	g = mymalloc(sizeof(Glist));
	g->gname = mystrdup(s,SD_FILTER2);
	g->next = *glist;
	*glist = g;
}
void glist_free(Glist *g)
{
	Glist *gnext;

	for (;g!=NULL;g=gnext) {
		gnext = g->next;
		mystrfree(g->gname,SD_FILTER2);
		myfree(g);
	}
}
int nocem_next_one(char *mid, Glist **g)
{
	static Glist *glist,*gl;
	static FILE *f;
	char bsy[BFSZ];
	char bf[BFSZ];
	char *s;

	glist_free(glist); glist = NULL;
again:
	if (f==NULL) {
		strcpy(bsy,NOCEM_BSY);
		rename(NOCEM_QUE,bsy);
		f = myfopen(bsy,"r");
		if (f==NULL) return FALSE;
	}
	if (feof(f)) goto done_file;
	if (fgets(bf,200,f)==NULL) goto done_file;
	if (strncmp(bf,".",1)==0) goto again;		
	s = strtok(bf,"\r\n \t");
	if (s==NULL) goto again;
	ncpy(mid,s,200);
	if (mid[0]=='#') {goto again;}
	if (mid[0]!='<') {imsg("NoCem: Invalid msgid (%.80s)\n",mid); goto again;}
	if (s!=NULL) s = strtok(NULL,"\r\n \t");

	if (s!=NULL) glist_add(&glist,s);
	for (;TRUE;) {
		if (fgets(bf,200,f)==NULL) break;
		if (strncmp(bf,".",1)==0) break;
		s = strtok(bf,"\r\n \t");
		if (s==NULL) continue;
		glist_add(&glist,s);
	}
	*g = glist;
	return TRUE;
done_file:
	fclosep(f); f = NULL;


	remove(NOCEM_BSY);
	goto again;

}
int nocem_background(void)
{
	static Glist *glist,*gl;
	char *h;
	char *gname;
	int num;
	int ng;
	int g;
	int ndone = 0;
	int start;
	char *cfrom="NoCem";
	char mid[BFSZ];
	if (!gotsome) return TRUE;
	start = lib_gmtime();	

	vtrace();
	for (;((lib_gmtime()-start)<1) || (ndone<20);) {
		if (!nocem_next_one(mid,&glist)) {gotsome = FALSE; goto finished;}
		total_done++;
		h = hist_exists(mid); ndone++;
		if (h == NULL) {
			hist_add(mid,"1-Jan-95 1:01:01 GMT","","");
			dmsg("nocem_del: Wont accept %.80s\n",mid);
			continue;
		} 
		dmsg("nocem_del: Deleting %.80s\n",mid);
		gname = hist_groupfirst(h);
		for (ng=0;(gname!=NULL) && (ng<200);ng++) {
	                g = group_find(gname);
	                if (g<0) goto next2;
			for (gl=glist; gl!=NULL; gl=gl->next) {
				if (strcmpnc(gl->gname,gname)==0) goto foundit;
			}
			goto next2;
foundit:	        num = hist_groupnextnum();
			if (ng==0) db_cancel_fast(g,num,cfrom,FALSE);
			else       db_cancel_fast(g,num,cfrom,TRUE);
next2:		        gname = hist_groupnext();
		}
	}
finished:
	vtrace();
	if (ndone==0) return TRUE;
	if ((total_qued-total_done)<0) total_qued = total_done;

	imsg("NoCem Processed %d, total done %d, still queued %d\n",ndone,total_done,total_qued-total_done);
	return TRUE;
}
void nocem_test(int chan, char *fname)
{
	String *xitem;
	int r;
	char bf[BFSZ];
	char *grps[10];
	char xname[BFSZ];
	grps[0] = "alt.nocem.misc";
	grps[1] = "news.lists.filters";
	xitem = str_new();

	if ((strchr(fname,'/')==NULL) && (strchr(fname,'\\')==NULL))  strcpy(xname,file_work(fname));
	else        strcpy(xname,fname);
	   
	str_read(xitem,xname);
	if (str_len(xitem)==0) {
		lprintf(bf,BFSZ-1,"    Could not read %.80s\r\n",xname); chan_write_s(chan,bf);
	}
	r = nocem_filter(xitem,grps,2);
	if (r) 	{lprintf(bf,200,"    Accepted NoCem message, see dnews.log\r\n",file_work("fname")); chan_write_s(chan,bf);}
	else	{lprintf(bf,200,"    Rejected NoCem message, see dnews.log\r\n",file_work("fname")); chan_write_s(chan,bf);}
        str_free(xitem);
}
