#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifndef TRUE
#define FALSE 0
#define TRUE (!FALSE)
#endif
#include "defines.h"
#include "lprintf.h"
#include "virus.h"
#include "lib.h"
#include "debug.h"
#include "mime.h"

/*

.386
..APP
..ASP
..BAS
..BAT
..BTM
..CHM
..CLASS
..CLA
..CMD
..COM
..CPL
..DLL
..DRV
..EML
..EMAIL
..EXE
..FON
..HLP
..HTA
..INF
..ISP
..INS
..JS
..JSE
..MSI
..MSP
..OCX
..PGM
..SCT
..SHB
..SHS
..SYS
..VB
..VBE
..VBS
..VXD
..WSH
..WSF

What is our database format,  Binary.
*/

enum {VF_APP=1,VF_WORD=2,VF_HTML=3,VF_TEXT};

#define PARTS 6
#define NAMELEN 25
typedef struct VDISK { 
	int id; unsigned int crc[PARTS]; 
	int len; char name[NAMELEN]; int clen; char type; } 
Vdisk;
typedef struct VLINK { Vdisk *vd; struct VLINK *next; int flags; } Vlink;
#define PARTMAX 132000

int virus_scan_show(char *name);
Vdisk *virus_makecrc(Vdisk *vd, char *part,int partlen,char *vname, int type);
int virus_add_list(Vdisk *vd, int i);
int virus_find(Vdisk *v, char *reason);

static int extract;
int virus_clen(int partlen)
{
	int p;
	int clen;
//	return 100;
	p = partlen / 100;
	p *= 100; 
	if (p>4000) {
		p = partlen / 1000;
		p *= 1000; 
	}
	if (p>20000) {
		p = partlen / 5000;
		p *= 5000; 
	}
	clen = p/PARTS;
	clen = clen & 0xfff6;
	return clen;
}
unsigned int virus_part_crc(char *part, int clen)
{
	unsigned int crc=0;
	int i;
	unsigned char *upart = (unsigned char *) part;
	for (i=0; i<clen; i++) {
		crc += upart[i++];
		crc += upart[i]*256;
	}
	return crc;
}
static int nrules;
#define HASHSZ 32000
static Vlink *vtable[HASHSZ];
int virus_add(Vdisk *xvd)
{
	Vlink *vl;
	unsigned int h;
	int i;
	Vdisk *newvd;	
	nrules++;
	newvd = calloc(1,sizeof(Vdisk));
	memcpy(newvd,xvd,sizeof(Vdisk));
	vl = calloc(PARTS,sizeof(Vlink));
	for (i=0; i<PARTS; i++) {
		h = newvd->crc[i] % HASHSZ;
		vl[i].vd = newvd;
		vl[i].next = vtable[h];
		vtable[h] = &vl[i];
	}
	return TRUE;
}
int virus_find(Vdisk *v, char *reason)
{
	Vlink *k;
	int diff;
	unsigned int h = v->crc[0] % HASHSZ;
	for (k=vtable[h]; k!=NULL; k=k->next) {
		if (k->vd->crc[0] == v->crc[0]) 
		if (k->vd->crc[1] == v->crc[1]) 
		if (k->vd->crc[2] == v->crc[2]) 
		if (k->vd->clen==v->clen) {
			diff = (k->vd->len)/10 - (v->len)/10;	
			if (diff<2) {
				ncpy(reason,k->vd->name,BFSZ-1);
				return TRUE;
			}
		}
	}
	return FALSE;
}
static int ntot;
int virus_scan_check_one(int x,unsigned int crc);
int virus_scan_one(char *part, int partlen, char *vname, int type)
{
	int clen = virus_clen(partlen);
	unsigned char *upart = (unsigned char *) part;
	unsigned int crc=0;
	int i;
	int k;
	if (clen==0) return FALSE;
	if (clen>partlen/PARTS) return FALSE;

//printf("CLEN SCAN %d %d\n",clen,partlen);
	for (i=0; i<clen;) {
		crc += upart[i++];
		crc += upart[i++]*256;
	}

	virus_scan_check_one(0,crc);
	for (k=i;i<partlen;) {
		crc -= upart[i - clen];
		crc -= upart[i+1 - clen]*256;
		crc += upart[i++];
		crc += upart[i++]*256;
		virus_scan_check_one(i-k,crc);
	}

	return virus_scan_show(vname);
}
int virus_scan_check_one(int at,unsigned int crc)
{
	Vlink *k;
	
	unsigned int h = crc % HASHSZ;
	for (k=vtable[h]; k!=NULL; k=k->next) {
		int i;
		for (i=0; i<PARTS; i++) {
			if (k->vd->crc[i] == crc) {
				virus_add_list(k->vd,i);
			}
		}
	}	
	return TRUE;
}
static Vlink *found;
int virus_score(int flags)
{
	int i;
	int t=0;
	for (i=0; i<7; i++) {
		if (1<<i & flags) t++;
	}
	return t;
}
int virus_free_list(Vlink **list)
{
	Vlink *vl,*next;
	for (vl=*list;vl!=NULL;vl=next) {
		next = vl->next;
		free(vl);
	}
	*list = NULL;
	return TRUE;
}
int virus_scan_show(char *name)
{
	Vlink *vl;
	Vdisk *vd;
	int score;
	int best=0;
	int bad=0;
	for (vl=found;vl!=NULL;vl=vl->next) {
		vd = vl->vd;
		score = virus_score(vl->flags);
		if (score<2) continue;
		if (score>best) {
			best = score;
			bad = TRUE;
			lprintf(name,BFSZ-1,"id=%d flags=%d %d name=%s len=%d type=%d",vl->vd->id,vl->flags,score,vl->vd->name,vd->len,vd->type);
		}
	}
	virus_free_list(&found);
	return best;
}
int virus_add_list(Vdisk *vd, int i)
{
	Vlink *vl;
	for (vl=found;vl!=NULL;vl=vl->next) {
		if (vl->vd==vd) {
			vl->flags |= (1<<i);
			return TRUE;
		}
	}
	vl = calloc(1,sizeof(Vlink));
	vl->next = found;
	vl->vd = vd;
	vl->flags = (1<<i);
	found = vl;
	return TRUE;
}
int virus_add_pending(Vdisk *v);
int virus_analyze_one(int analyze, char *part,int partlen,char *vname, int type)
{
	Vdisk vd,*v;

	if (extract) {
		FILE *f;
		char fname[BFSZ];
		static int n;
		n++;
		lprintf(fname,100,"part_%d.txt",n);
		f = fopen(fname,"wb");
		if (f!=NULL) {
			fwrite(part,1,partlen,f);
			fclose(f);
		}
	}
	if (!analyze) return virus_scan_one(part,partlen,vname,type);
	v = virus_makecrc(&vd,part,partlen,vname,type);
	if (v==NULL) return FALSE;
	virus_add_pending(v);	
	return FALSE;
}
static Vlink *pending;
int virus_add_pending(Vdisk *v)
{
	Vlink *p;
	p = calloc(1,sizeof(Vlink));
	p->vd = calloc(1,sizeof(Vdisk));
	memcpy(p->vd,v,sizeof(Vdisk));
	p->next = pending;
	pending = p;
	return TRUE;
}

int virus_add_one(Vdisk *v);
int virus_add_best(void)
{
	int best=99;
	Vdisk *vbest=NULL;
	Vlink *p;
	int done=FALSE;

	for (p=pending;p!=NULL;p=p->next)  {
		if (p->vd->type < best) {
			best = p->vd->type;
			vbest = p->vd;
		}
		if (p->vd->type==VF_APP) {
			virus_add_one(p->vd);
			done = TRUE;
		}
	}
	if (!done) if (vbest!=NULL) {
		virus_add_one(vbest);
	}
	virus_free_list(&pending);
	return TRUE;
}
static int newid=1;
static int nadd;
int virus_add_one(Vdisk *v)
{
	char reason[BFSZ];
	nadd++;
	if (virus_find(v,reason)) return FALSE;
	ntot++;
	v->id = newid++;
	virus_add(v);
	return TRUE;
}

Vdisk *virus_makecrc(Vdisk *vd, char *part,int partlen,char *vname, int type)
{
	int i;
	int clen = virus_clen(partlen);
	unsigned char *upart = (unsigned char *) part;
	unsigned int crc=0;
	if (clen==0) return NULL;
	if (clen>partlen/PARTS) return NULL;
	for (i=0; i<PARTS-1; i++) {
		vd->crc[i] = virus_part_crc(part+i*clen,clen);
	}
	vd->crc[PARTS-1] = virus_part_crc(part+partlen-clen,clen);
	vd->clen = clen;
	ncpy(vd->name,vname,NAMELEN-1);
	vd->len = partlen;
	vd->type = type;
	return vd;
}

int virus_doscan(int analyze, char *fname, char *vname, char *reason);

int virus_analyze(char *fname,char *vname,char *reason)
{
	return virus_doscan(TRUE,fname,vname,reason);
}
int virus_scan(char *fname,char *vname,char *reason)
{
	return virus_doscan(FALSE,fname,vname,reason);
}
int virus_extract(int t)
{
	extract = t;
	return TRUE;
}
static int issafe;
int virus_set_safe(int x)
{
	issafe = x;
	return TRUE;
}
int virus_doscan(int analyze, char *fname, char *vname, char *reason)
{
	/*	Process message, take first 32K of each
		mime part and store in buffer, then generate 'crc's for 
		that 
	*/	
	Mime *m;
	FILE *f;
	int k;
	char *s2;
	char ext[BFSZ];
	char raw[BFSZ];
	char bin[BFSZ];
	char part[PARTMAX];
	int partlen=0;
	int len;
	FILE *fout;
	char *s;
	int ret=FALSE;
	int type=VF_TEXT;
	int inhead=TRUE;
	char bf[BFSZ];
	mime_new(&m);
	strcpy(reason,"");
	strcpy(ext,"");

	strcpy(vname,"");
//	if (analyze) printf("Analyzing file (%s) %s\n",fname,vname);
//	else printf("Scanning file (%s) %s\n",fname,vname);

	if (analyze || issafe) {
		f = fopen(fname,"rb");
		if (f==NULL) return FALSE;
		fout = fopen("an.txt","wb");
		for (;!feof(f);) {
			if (fgets(bf,BFSZ-1,f)==NULL) break;
			if (inhead) {
				if (strncmp(bf,"Subject: ",9)==0) {
					s = strstr(bf,"(");
					if (s!=NULL) {
						s2 = strstr(s,"detected virus - "); 
						if (s2!=NULL) s = s2 +17;
						ncpy(vname,s,BFSZ-1);
						s = strchr(vname,')');
						if (s!=NULL) *s = 0;
					}
				}
			}
			if (strncmp(bf,"+safe+",6)==0) fprintf(fout,"%s",bf+6);
		}
		fclose(f);
		fclose(fout);
		fname = "an.txt";
	}
//	printf("Analyzing virus file (%s) %s\n",vname,fname);

	f = fopen(fname,"rb");
	if (f==NULL) {
		lprintf(reason,BFSZ-1,"virus_extract FAILED %s %s",fname,strerror(errno));
		mime_free(&m);
		return FALSE;
	}
	setvbuf(f,NULL,_IOFBF,32768);
	for (k=TRUE;k;) {
		k = mime_line(m,f,raw,bin,&len,BFSZ-1);
		switch (k) {
		case MIME_END:
			break;
		case MIME_HEADER:
			break;
		case MIME_CONTENT:
			break;
		case MIME_HEADER_END:
			partlen = 0;
			strlwr(m->content);
			type=VF_TEXT;
			if (strstr(m->content,"text")!=NULL) type = VF_TEXT;
			if (strstr(m->content,"html")!=NULL) type = VF_HTML;
			if (strstr(m->content,"application")!=NULL) type = VF_APP;
			if (strstr(m->content,".exe")) type = VF_APP;
			if (strstr(m->content,".scr")) type = VF_APP;
			if (strstr(m->content,".bat")) type = VF_APP;
			if (strstr(m->content,".com")) type = VF_APP;
			if (strstr(m->content,".cmd")) type = VF_APP;
			if (strstr(m->content,".dll")) type = VF_APP;
			if (strstr(m->content,".vb")) type = VF_APP;
			if (strstr(m->content,".pif")) type = VF_APP;
			break;
		case MIME_BODY:
//			printf("Body (%s) \n",raw);
			if (partlen+len<PARTMAX) {
				memcpy(part+partlen,bin,len);
				partlen += len;
				break;
			}
		case MIME_BOUNDARY:
			ret = virus_analyze_one(analyze,part,partlen,vname,type);
			if (ret) goto done;
			partlen = 0;
			strcpy(ext,"");
			break;
		}
	}
	if (!ret) ret = virus_analyze_one(analyze,part,partlen,vname,type);
	if (analyze) virus_add_best();

done:
	if (f!=NULL) fclose(f); f = NULL;
	mime_free(&m);
	if (ret) lprintf(reason,BFSZ-1,"%s",vname);
	return ret;
}

int virus_init(char *dbname, char *reason)
{
	/* Read database into memory */
	virus_read(dbname,reason);
	return TRUE;
}
char *virus_encode_vd(Vdisk *vd, char *tmp)
{
	int i;
	char bf[BFSZ];
	char tmp2[BFSZ];
	strcpy(bf,"");
	for (i=0; i<PARTS; i++) {
		lprintf(tmp2,BFSZ-1,"%d\t",vd->crc[i]);
		strcat(bf,tmp2);
	}
	lprintf(tmp,BFSZ-1,"vd\t%d\t%d\t%d\t%d\t%s%s\t",vd->id,vd->len,vd->clen,vd->type,bf,vd->name);
	return tmp;
}
int virus_read(char *dbname, char *reason)
{
	int i;
	FILE *f;
	char bf[BFSZ];
	char *p[100];
	int np;
	Vdisk vd;
	f = fopen(dbname,"r");
	if (f==NULL) return FALSE;
	for (;!feof(f);) {
		if (fgets(bf,BFSZ-1,f)==NULL) break;
		np = token_split(bf,p,"\t",20);
		if (np<1) continue;
		if (strcmp(p[0],"vd")==0) {
			if (np<13) continue;
			vd.id = atol(p[1]);
			if (newid < vd.id) newid = vd.id;
			vd.len = atol(p[2]);
			vd.clen = atol(p[3]);
			vd.type = (char ) atol(p[4]);
			for (i=0; i<PARTS; i++) {
				vd.crc[i] = atol(p[5+i]);
			}
			ncpy(vd.name,p[5+6],NAMELEN-1);	
			virus_add(&vd);
		}
	}
	lprintf(reason,BFSZ-1,"Loaded %d rules",nrules);
	return FALSE;
}
int virus_write(char *dbname)
{
	Vlink *l;
	int h;
	FILE *f;
	char tmp[BFSZ];
	f = fopen(dbname,"w");
	if (f==NULL) return FALSE;
	for (h=0; h<HASHSZ; h++) {
		for (l=vtable[h]; l!=NULL;l=l->next) {
			if (l->vd->id>=0) {
				fprintf(f,"%s\n",virus_encode_vd(l->vd,tmp));
				l->vd->id = -l->vd->id;
			}
		}
	}
	for (h=0; h<HASHSZ; h++) {
		for (l=vtable[h]; l!=NULL;l=l->next) {
			if (l->vd->id<0) l->vd->id = -l->vd->id;
		}
	}
	fclose(f);
	return TRUE;
}
char * virus_status(char *info)
{
	lprintf(info,BFSZ-1,"Nunique %d, nadded %d\n",ntot,nadd);
	return info;
}
