/*
 * This file is part of the FEddi package
 *
 * Personal use allowed under the terms of the
 *
 *              GNU GENERAL PUBLIC LICENSE Version 2
 *              (see LICENSE for the complete text)
 *
 *-------------------------------------------------------------------
 *
 *    ENTER AT YOUR OWN RISK !!
 *
 * This source is without any documentation and can drive you mad.
 * In case of sudden epileptic seizures please call your doctor.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/types.h>
#include "regex.h"
#include "juldate.h"
#include "charset.h"
#include "structs.h"
#include "proc.h"
#include "flags.h"
#include "inout.h"
#include "nlfunct.h"

void updatebase();
void utility(int argc, char **argv, void (*)(char *));
void packarea(char *a);
void linkarea(char *a);
void toolarea(char *a);
void rescanarea(char *a);
void dupearea(char *a);
void listarea(char *a);
void infoarea(char *a);
void newmsg(char *a);
void nllookup(char *s);
int argmark(int argc, char **argv);
int datetoint(char *s);
int findtext(MsgHdrType, struct re_pattern_buffer *);
int argcopy(char **argv);
void copyarea(char *a);
int savenewmsg(char *area, MsgHdrType *hdr,
		int *MsgLines, LineListType **MsgTxt,
		int *KludgeLines, LineListType **Kludges);

static int numAction=8;
static char Action[8][12]=
{
	"mark",
	"delete",
	"personal",
	"protect",
	"new",
	"newpersonal",
	"list",
	"display"
};
int ActionOn[8];
int AttributeMask[3];

static int numExprStrings=22;
static char ExprStrings[22][13]=
{
	"mark",
	"delete",
	"personal",
	"protect",
	"new",
	"newpersonal",
	"number",
	"date",
	"keep",
	"age",
	"from",
	"to",
	"subject",
	"text",
	"attribute",
	"#marked",
	"#deleted",
	"#personal",
	"#protected",
	"#new",
	"#newpersonal",
	"all"
};

static char Ops[9][3]={":","+","-","<","<=","=","<>",">=",">"};

typedef struct
{
	int Type; /* ExprStrings */
	int Op; /* 1ifset 2ifnotset actions */
					/* 3< 4<= 5= 6!= 7>= 8> date/number */
					/* 1iftrue 2iffalse rest */
	struct re_pattern_buffer RegEx; /* from to subject text */
	int iValue;   /* other */
	int NextOp; /* 0 == and , 1 == or */
} ExprType;
#define ExprSize sizeof(ExprType)

int numExpression=0;
ExprType *Expression=NULL;

int focus, Keep=0, Date=0;
char string[4096];
int asaved=0, amsgsaved=0, force=0, EL=0;
char formatlist[80]="%5n-%20f %20t %30s";
char formatmsg[80]="htk";

static char *HELP="\nMsgBase Utilities:"\
				"\n  update"\
				"\n  pack [AREAS]"\
				"\n  link [AREAS]"\
				"\n  rescan [AREAS]"\
				"\n  deldupes [AREAS]"\
				"\n  tool ACTION EXPRESSION [AREAS] ('futility tool' for help)"\
				"\n  addmsg [AREA]"\
				"\n  info [AREAS]"\
				"\n  nllookup STRING"\
				"\n\n";

static char *TOOLHELP="\nfutility tool ACTION EXPRESSION AREAS"\
			"\n  ACTION:"\
			"\n    [[+|-|*][mark|delete|personal|protect|new|newpersonal|"\
			"\n             [FLAG]+|list|display],]+"\
			"\n\n  EXPRESSION:"\
			"\n    EXPR[[|| | &&]EXPR]*"\
			"\n    EXPR:"\
			"\n      all[+|-], mark[+|-], delete[+|-], personal[+|-], "\
			"\n      protect[+|-], new[+|-], newpersonal[+|-], "\
			"\n      number[<|<=|=|<>|>=|>]NUM, date[<|<=|=|<>|>=|>]DD.MM.YY, "\
			"\n      keep[+|-]NUM, age[+|-]NUM, from[+|-]REGEX, to[+|-]REGEX, "\
			"\n      subject[+|-]REGEX, text[+|-]REGEX, attribute[+|-][FLAG]+,"\
			"\n      #marked[<|<=|=|<>|>=|>]NUM, #deleted[<|<=|=|<>|>=|>]NUM, "\
			"\n      #personal[<|<=|=|<>|>=|>]NUM, #protected[<|<=|=|<>|>=|>]NUM, "\
			"\n      #new[<|<=|=|<>|>=|>]NUM, #newpersonal[<|<=|=|<>|>=|>]NUM\n\n";

int main(int argc, char **argv)
{
	int shift=0;
	strcpy(ProgName,"futility");
	while (argc>1 && argv[shift+1][0]=='-')
	{
		if (strcmp(argv[shift+1],"--config")==0 || strcmp(argv[shift+1],"-c")==0)
		{
			strcpy(ConfigName,argv[shift+2]);
			shift+=2;
			continue;
		}
		if (strcmp(argv[shift+1],"--force")==0 || strcmp(argv[shift+1],"-f")==0)
		{
			force=1;
			shift++;
			continue;
		}
		if (strcmp(argv[shift+1],"--quiet")==0 || strcmp(argv[shift+1],"-q")==0)
		{
			QUIET=1;
			shift++;
			continue;
		}
		if (strcmp(argv[shift+1],"--message-format")==0 ||
				strcmp(argv[shift+1],"-m")==0)
		{
			strcpy(formatmsg,argv[shift+2]);
			shift+=2;
			continue;
		}
		if (strcmp(argv[shift+1],"--list-format")==0 ||
				strcmp(argv[shift+1],"-l")==0)
		{
			strcpy(formatlist,argv[shift+2]);
			shift+=2;
			continue;
		}
		printERR("Unknown param '%s'!\n",argv[shift+1]);
		exit(1);
	}
	if (argc-shift<2)
	{
		printERR("To few params!\n");
		printERR(HELP);
		exit(1);
	}
	if (argc-shift==2 && argv[shift+1][0]=='t')
	{
		Log("!Too less Params");
		printERR(TOOLHELP);
		exit(1);
	}
	if (loadrc(1))
	{
		switch (tolower(argv[shift+1][0]))
		{
			case 'u':
				Log("*FEddi Utility updating MsgBase");
				updatebase();
				Log("*Leaving Utility\n");
				break;
			case 'p':
				Log("*FEddi Utility packing MsgBase");
				utility(argc-shift-2,&argv[shift+2],packarea);
				if (asaved)
				{
					Log("-Total:");
					Log("-%u Msg%s deleted",amsgsaved,amsgsaved!=1?"s":"");
					Log("-%u Bytes saved",asaved);
				}
				Log("*Leaving Utility\n");
				break;
			case 'l':
				Log("*FEddi Utility linking MsgBase");
				utility(argc-shift-2,&argv[shift+2],linkarea);
				Log("*Leaving Utility\n");
				break;
			case 'c':
				if (argc-shift>=4)
				{
					Log("*FEddi Utility copying MsgBase");
					if (argcopy(&argv[shift+2]))
						utility(argc-shift-4,&argv[shift+4],copyarea);
					else
						Log("!Parameter Error");
					Log("*Leaving Utility\n");
				}
				break;
			case 'r':
				Log("*FEddi Utility rescanning MsgBase");
				utility(argc-shift-2,&argv[shift+2],rescanarea);
				Log("*Leaving Utility\n");
				break;
			case 'd':
				Log("*FEddi Utility deleting dupes");
				utility(argc-shift-2,&argv[shift+2],dupearea);
				Log("*Leaving Utility\n");
				break;
			case 't':
				Log("*FEddi Utility marking MsgBase");
				if (!argmark(argc-shift-2,&argv[shift+2]))
					utility(argc-shift-4,&argv[shift+4],toolarea);
				else
				{
					Log("!Too less Params");
					printERR(TOOLHELP);
					Log("*Leaving Utility\n");
					exit(1);
				}
				Log("*Leaving Utility\n");
				break;
			case 'a':
				Log("*FEddi Utility adding message");
				if (argc-shift-2==1)
					newmsg(argv[shift+2]);
				else
					if (argc-shift-2>1)
						printERR("Illegal Number of Params!\n");
					else
						newmsg(NULL);
				Log("*Leaving Utility\n");
				break;
			case 'i':
				Log("*FEddi Utility area info");
				QUIET=1;
				utility(argc-shift-2,&argv[shift+2],infoarea);
				Log("*Leaving Utility\n");
				break;
			case 'n':
				Log("*FEddi Utility nodelist-lookup");
				QUIET=1;
				if (argc-shift-2==1)
					nllookup(argv[shift+2]);
				else
					printERR("Illegal Number of Params!\n");
				Log("*Leaving Utility\n");
				break;
			default :
				printERR(HELP);
				return 1;
		}
	} else
	{
		printERR("\nError while parsing ~/.feddirc\n");
		return 1;
	}
	return EL;
}

void updatebase()
{
	DIR *dir;
	struct dirent *ent;
	char file[PATH_MAX];
	FILE *f;
	OldMsgDataType omd;
	MsgData07Type omd07;
	MsgHdrType *MsgHdr=NULL;
	int i, version;
	if ((dir=opendir(BasePath))!=NULL)
	{
		while ((ent=readdir(dir))!=NULL)
			if (ent->d_name[0]=='+')
			{
				openareasloppy(ent->d_name+1);
				if (memcmp(&PCODE,&MsgData.ID,6)!=0)
				{
					EL=100;
					if (MsgData.ID[0] == PCODE[0] &&
							MsgData.ID[1] == PCODE[1] &&
							MsgData.ID[2] == PCODE[2])
					{
						sprintf(file,"Updating %s (was V%u.%u)",
								ent->d_name+1,MsgData.VMAJOR,MsgData.VMINOR);
						version=1;
					} else
					{
						sprintf(file,"Updating %s (was V0.6pl3 or lower)",
								ent->d_name+1);
						version=0;
					}
					Log("-%s",file);
					printERR("%s\n",file);
					sprintf(file,"%s%s/data",BasePath,ent->d_name);
					f=fopen(file,"rb");
					switch (version)
					{
						case 0:
							fread(&omd,OldMsgDataSize,1,f);
							fclose(f);
							MsgData.ID[0]=PCODE[0];
							MsgData.ID[1]=PCODE[1];
							MsgData.ID[2]=PCODE[2];
							MsgData.VMAJOR=MBVERhi;
							MsgData.VMINOR=MBVERlo;
							MsgData.VPATCH=MBVERpl;
							MsgData.LastRead=omd.LastRead;
							MsgData.Deleted=omd.Deleted;
							MsgData.New=omd.New;
							MsgData.NewPersonal=omd.NewPersonal;
							MsgData.Marked=0;
							MsgData.CharSet=omd.CharSet>MAXCHRS?0:omd.CharSet;
							MsgData.ReadOnly=omd.ReadOnly;
							MsgData.NetMail=omd.NetMail;
							MsgData.Linked=omd.Linked;
							MsgData.RouteZone=omd.RouteZone;
							MsgData.RouteNet=omd.RouteNet;
							MsgData.RouteNode=omd.RouteNode;
							strcpy(MsgData.Owner,Profile.UserName);
							strcpy(MsgData.RealName,ent->d_name+1);
							loadmsghdr(&MsgHdr);
							for (i=0; i<TotalMsgs; i++)
								if (MsgHdr[i].Marked && !MsgHdr[i].Deleted)
									MsgData.Marked++;
							break;
						case 1:
							fread(&omd07,MsgData07Size,1,f);
							fclose(f);
							memcpy(&MsgData,&omd07,MsgData07Size);
							MsgData.ID[0]=PCODE[0];
							MsgData.ID[1]=PCODE[1];
							MsgData.ID[2]=PCODE[2];
							MsgData.VMAJOR=MBVERhi;
							MsgData.VMINOR=MBVERlo;
							MsgData.VPATCH=MBVERpl;
							strcpy(MsgData.Owner,Profile.UserName);
							strcpy(MsgData.RealName,ent->d_name+1);
							break;
					}
					savemsgdata();
				} else
				{
					sprintf(file,"%s is ok!",ent->d_name+1);
					Log("-%s",file);
					printERR("%s\n",file);
				}
				closearea();
			}
		closedir(dir);
	}
}

void packarea(char *a)
{
	char name[256], log[256], *text=NULL;
	MsgHdrType *amh=NULL;
	FILE *nhf, *ntf;
	int lrajust, i;
	dword start;
	int	saved=0, msgsaved=0;
	struct stat st1, st2;
	openarea(a,NULL);
	if (MsgData.Deleted || force)
	{
		EL=100;
		sprintf(log,"%s+%s/header.new",BasePath,a);
		nhf=fopen(log,"wb");
		sprintf(log,"%s+%s/text.new",BasePath,a);
		ntf=fopen(log,"wb");
		loadmsghdr(&amh);
		if (ntf && nhf && amh)
		{
			for (i=0, start=0, lrajust=0; i<TotalMsgs; i++)
			{
				if (!amh[i].Deleted)
				{
					text=(char *)realloc(text,256*amh[i].NumRecs);
					fseek(mtf,amh[i].StartRec*256,SEEK_SET);
					fread(text,256,amh[i].NumRecs,mtf);
					fwrite(text,256,amh[i].NumRecs,ntf);
					amh[i].StartRec=start;
					amh[i].Mother=amh[i].Child=
					amh[i].prevBrother=amh[i].nextBrother=
					amh[i].prevSubject=amh[i].nextSubject=65535;
					start+=amh[i].NumRecs;
					fwrite(&amh[i],MsgHdrSize,1,nhf);
				} else
				{
					msgsaved++;
					if (i<MsgData.LastRead) lrajust++;
				}
				printERR("\rPacking %s-[%u/%u]",a,msgsaved,i+1);
			}
			if (TotalMsgs) printERR("\n");
			fclose(ntf);
			fclose(nhf);
			free(amh);
			free(text);
			MsgData.LastRead-=lrajust;
			MsgData.Deleted=0;
			MsgData.Linked=0;
			closearea();
			sprintf(log,"%s+%s/header",BasePath,a);
			stat(log,&st1);
			sprintf(name,"%s+%s/header.backup",BasePath,a);
			if (KeepBackups)
				rename(log,name);
			sprintf(name,"%s+%s/header.new",BasePath,a);
			stat(name,&st2);
			rename(name,log);
			chmod(log,FIDO_MOD);
			chown(log,-1,FIDO_GID);
			saved=st1.st_size-st2.st_size;
			sprintf(log,"%s+%s/text",BasePath,a);
			stat(log,&st1);
			sprintf(name,"%s+%s/text.backup",BasePath,a);
			if (KeepBackups)
				rename(log,name);
			sprintf(name,"%s+%s/text.new",BasePath,a);
			stat(name,&st2);
			rename(name,log);
			chmod(log,FIDO_MOD);
			chown(log,-1,FIDO_GID);
			saved+=(st1.st_size-st2.st_size);
			if (saved)
			{
				Log("-Compressing Area %s",a);
				Log("-%u Msg%s deleted",msgsaved,msgsaved!=1?"s":"");
				Log("-%u Bytes saved",saved);
			}
			amsgsaved+=msgsaved;
			asaved+=saved;
		}
		MsgData.Linked=0;
	} else
	{
		Log("-%s is clean",a);
	}
}

void linkarea(char *a)
{
	MsgHdrType *amh=NULL;
	char **Msgid, **Reply;
	char *text, *d1, *d2;
	int i, j, lastBrother, lastSubject, sorted;
	openarea(a,NULL);
	if (MsgData.Linked && !force)
	{
		closearea();
		return;
	}
	Log("-Linking Area %s",a);
	EL=100;
	loadmsghdr(&amh);
	Msgid=(char **)malloc(TotalMsgs*sizeof(char *));
	Reply=(char **)malloc(TotalMsgs*sizeof(char *));
	for (i=0; i<TotalMsgs; i++)
	{
		amh[i].Mother=amh[i].Child=
		amh[i].nextBrother=amh[i].prevBrother=
		amh[i].nextSubject=amh[i].prevSubject=65535;
		fseek(mtf,256*amh[i].StartRec,SEEK_SET);
		text=(char *)malloc(amh[i].NumRecs*256);
		fread(text,256,amh[i].NumRecs,mtf);
		if ((d1=strstr(text,"\001MSGID:"))!=NULL)
		{
			while (*d1!=32) d1++;
			d1++;
			d2=strchr(d1,'\r');
			*d2=0;
			Msgid[i]=(char *)malloc(strlen(d1)+1);
			strcpy(Msgid[i],d1);
			*d2='\r';
		} else Msgid[i]=NULL;
		if ((d1=strstr(text,"\001REPLY:"))!=NULL)
		{
			while (*d1!=32) d1++;
			d1++;
			d2=strchr(d1,0xd);
			*d2=0;
			Reply[i]=(char *)malloc(strlen(d1)+1);
			strcpy(Reply[i],d1);
			*d2='\r';
		}	else Reply[i]=NULL;
		free(text);
		printERR("\rLinking %s-read[%u]",a,i+1);
	}
	sorted=i;
	for (i=0; i<TotalMsgs; i++)
	{
		if (!amh[i].Deleted)
		{
			d1=amh[i].Subj;
			if (strncasecmp(amh[i].Subj,"RE",2)==0)
			{
				if ((d1=strchr(amh[i].Subj,':')))
				{
					d1++;
					while (*d1!=32) d1++;
				} else
					d1=amh[i].Subj;
			}
			lastBrother=0;
			lastSubject=i;
			for (j=i+1; j<TotalMsgs; j++)
				if (!amh[j].Deleted)
				{
					if (Msgid[i]!=NULL && Reply[j]!=NULL)
						if (strcmp(Msgid[i],Reply[j])==0)
						{
							if (lastBrother)
							{
								amh[lastBrother].nextBrother=j;
								amh[j].prevBrother=lastBrother;
							} else amh[i].Child=j;
							amh[j].Mother=i;
							lastBrother=j;
						}
					if (*d1 && strstr(amh[j].Subj,d1)!=NULL)
					{
						amh[j].prevSubject=lastSubject;
						amh[lastSubject].nextSubject=j;
						lastSubject=j;
					}
				}
		}
		printERR("\rLinking %s-read[%u]-link[%u]",a,sorted,i+1);
	}
	if (TotalMsgs) printERR("\n");
	for (i=0; i<TotalMsgs; i++)
	{
		free(Msgid[i]);
		free(Reply[i]);
	}
	free(Msgid);
	free(Reply);
	savemsghdr(&amh);
	MsgData.Linked=1;
	closearea();
}

void rescanarea(char *a)
{
	MsgHdrType *amh=NULL;
	int i;
	openarea(a,NULL);
	loadmsghdr(&amh);
	Log("-Rescanning %s",a);
	EL=100;
	MsgData.Deleted=MsgData.New=MsgData.NewPersonal=MsgData.Marked=0;
	for (i=0; i<TotalMsgs; i++)
	{
		if (amh[i].Deleted)
			MsgData.Deleted++;
		else
		{
			if (amh[i].New) {
				MsgData.New++;
				if(amh[i].Personal)
					amh[i].NewPersonal=1;
			}
			if (amh[i].NewPersonal) MsgData.NewPersonal++;
			if (amh[i].Marked) MsgData.Marked++;
		}
		printERR("\rRescanning %s-[%u]",a,i+1);
	}
	if (TotalMsgs) printERR("\n");
	savemsghdr(&amh);
	closearea();
}

void infoarea(char *a)
{
	int tr;
	tr=openarea(a,NULL);
	EL=100;
	fprintf(stdout,"###AREA###[%s]\n",a);
	fprintf(stdout,"#ID: %X%X%X\n",
			MsgData.ID[0],MsgData.ID[1],MsgData.ID[2]);
	fprintf(stdout,"#Version: %u.%upl%u\n",
			MsgData.VMAJOR,MsgData.VMINOR,MsgData.VPATCH);
	fprintf(stdout,"#Owner: %s\n",MsgData.Owner);
	fprintf(stdout,"#Realname: %s\n",MsgData.RealName);
	fprintf(stdout,"#Route: %u:%u/%u\n",
			MsgData.RouteZone,MsgData.RouteNet,MsgData.RouteNode);
	fprintf(stdout,"#TotalMsgs: %u\n",TotalMsgs);
	fprintf(stdout,"#Lastread: %u\n",MsgData.LastRead+1);
	fprintf(stdout,"#Marked: %u\n",MsgData.Marked);
	fprintf(stdout,"#Deleted: %u\n",MsgData.Deleted);
	fprintf(stdout,"#New: %u\n",MsgData.New);
	fprintf(stdout,"#NewPersonal: %u\n",MsgData.NewPersonal);
	fprintf(stdout,"#Linked: %s\n",MsgData.Linked?"Yes":"No");
	fprintf(stdout,"#Read only: %s\n",MsgData.ReadOnly?"Yes":"No");
	fprintf(stdout,"#Netmail: %s\n",MsgData.NetMail?"Yes":"No");
	fprintf(stdout,"#Charset: %s\n",CHRSETS[MsgData.CharSet][0]);
	fprintf(stdout,"#Size: %u Byte\n",
			TotalMsgs*MsgHdrSize+tr*256+sizeof(MsgDataType));
	fprintf(stdout,"###END_AREA###\n");
	closearea();
}

int argmark(int argc, char **argv)
{
	char *d1, *d2, *d3, sval[512];
	int i, j, val, nxop, type=0, op, madeAction=0, flag=0;
	for (i=0; i<numAction; i++) ActionOn[i]=0;
	sval[0]=0;
	AttributeMask[0]=AttributeMask[1]=AttributeMask[2]=0;
	if (argc>=2)
	{
		for (d1=argv[0]; d1; d1=d2)
		{
			d2=strchr(d1,',');
			if (d2) *d2++=0;
			if (d1[0]=='+')
			{
				d1++;
				val=1;              /* set */
			} else
				if (d1[0]=='-')
				{
					d1++;
					val=2;            /* reset */
				} else
					if (d1[0]=='*')
					{
						d1++;
						val=3;          /* toggle */
					} else
						val=1;          /* default: set */
			for (i=0; i<numAction; i++)
				if (strcmp(d1,Action[i])==0)
				{
					ActionOn[i]=val;
					if (i>5)
					{
						QUIET=1;
					}
					if (*sval) strcat(sval,",");
					switch (val)
					{
						case 1:
							strcat(sval,"+");
							break;
						case 2:
							strcat(sval,"-");
							break;
						case 3:
							strcat(sval,"*");
							break;
					}
					strcat(sval,Action[i]);
					madeAction++;
					break;
				}
			if (i==numAction)
			{
				for (; *d1; d1+=2)
					for (i=0; i<16; i++)
						if (strncasecmp(d1,NAMEFLAG[i],2)==0)
						{
							flag=1;
							AttributeMask[val-1]|=(1<<i);
							continue;
						}
				if (!flag)
				{
					Log("!Unknown action: %s",d1);
					printERR("Unknown action: %s\n",d1);
				} else
					madeAction++;
			}
		}
		if (sval[0]) Log("+%s",sval);
		strcpy(sval,"Attributes: ");
		for (j=0; j<3; j++)
			if (AttributeMask[j])
			{
				strcat(sval,j==0?"+":(j==1?"-":"*"));
				for (i=0; i<16; i++)
					if (AttributeMask[j]&(1<<i))
						strcat(sval,NAMEFLAG[i]);
				strcat(sval," ");
			}
		if (sval[0]) Log("+%s",sval);
		for (d1=argv[1]; d1; d1=d2)
		{
			for (d2=strpbrk(d1,"&|"); d2; d2=strpbrk(d2,"&|"))
				if (d2[1]=='|' || d2[1]=='&')
				{
					*d2++=0;
					break;
				} else
					d2++;
			if (d2)
			{
				nxop=(*d2=='|');
				*d2++=0;
			} else
				nxop=-1;
			for (i=0; i<numExprStrings; i++)
				if (strncmp(d1,ExprStrings[i],strlen(ExprStrings[i]))==0)
				{
					type=i;
					d1+=strlen(ExprStrings[i]);
					break;
				}
			if (i==numExprStrings)
			{
				Log("!Unknown expression: %s",d1);
				printERR("Unknown expression: %s\n",d1);
				continue;
			}
			val=0;
			sval[0]=0;
			switch (type)
			{
				case 0 ... 5:
				case 21:
					op=(*d1=='-')+1;
					if (*d1=='-' || *d1=='+') d1++;
					break;
				case 6 ... 7:
				case 15 ... 20:
					switch (*d1)
					{
						case '=':
							op=5;
							d1++;
							break;
						case '!':
							d1++;
							if (*d1=='=')
							{
								op=6;
								d1++;
							} else
							{
								d1--;
								goto unknown;
							}
							break;
						case '<':
							d1++;
							switch (*d1)
							{
								case '=':
									d1++;
									op=4;
									break;
								default:
									op=3;
									break;
							}
							break;
						case '>':
							d1++;
							switch (*d1)
							{
								case '=':
									d1++;
									op=7;
									break;
								default:
									op=8;
									break;
							}
							break;
						default:
							unknown:
							Log("!Unknown operator: %s",d1);
							printERR("Unknown operator: %s\n",d1);
							continue;
							break;
					}
					if (type==7)
					{
						val=datetoint(d1);
						*d1=0;
					} else
					{
						val=strtol(d1,&d3,0);
						d1=d3;
					}
					break;
				case 14:
					switch (*d1)
					{
						case '+':
							op=1;
							break;
						case '-':
							op=2;
							break;
						default:
							Log("!Unknown operator: %s",d1);
							printERR("Unknown operator: %s\n",d1);
							continue;
							break;
					}
					for (d1++, val=0; *d1; d1+=2)
						for (i=0; i<16; i++)
							if (strncasecmp(d1,NAMEFLAG[i],2)==0)
							{
								val|=(1<<i);
								break;
							}
					break;
				default:
					switch (*d1)
					{
						case '+':
							op=1;
							break;
						case '-':
							op=2;
							break;
						default:
							Log("!Unknown operator: %s",d1);
							printERR("Unknown operator: %s\n",d1);
							continue;
							break;
					}
					if (type<10)
					{
						val=strtol(d1+1,&d3,0);
						d1=d3;
					} else
					{
						strcpy(sval,d1+1);
						*d1=0;
					}
					break;
			}
			if (d1)
				if (*d1)
				{
					Log("!Ignored: %s",d1);
					printERR("Ignored: %s\n",d1);
				}
			Expression=(ExprType *)realloc(Expression,ExprSize*(numExpression+1));
			Expression[numExpression].Type=type;
			Expression[numExpression].Op=op;
			if (sval)
			{
				memset(&Expression[numExpression].RegEx,0,
						sizeof(struct re_pattern_buffer));
				Expression[numExpression].RegEx.translate=
						(unsigned char *)RE_ICASE_TRANS_MAP;
				re_set_syntax(RE_SYNTAX_POSIX_EGREP);
				if (re_compile_pattern(sval,strlen(sval),
						&Expression[numExpression].RegEx))
				{
					Log("!Regex: '%s'",sval);
					printERR("Regex: '%s'!\n",sval);
					Expression[numExpression].Op=-1;
				}
			}
			Expression[numExpression].iValue=val;
			Expression[numExpression].NextOp=nxop;
			numExpression++;
			if (*sval==0)
			{
				if (type==14)
				{
					for (i=0; i<16; i++)
						if (val&(1<<i))
							strcat(sval,NAMEFLAG[i]);
				} else
					sprintf(sval,"%u",val);
			}
			if (type<6) sval[0]=0;
			Log("+%s%s%s %s",ExprStrings[type],Ops[op],sval,
					nxop==-1?"":(nxop?"||":"&&"));
		}
		if (numExpression && madeAction)
			return 0;
		else
			return 1;
	}
	return 1;
}

int evalExpression(ExprType expr, MsgHdrType mh, int cur)
{
	struct re_registers regs;
	char dummy[80];
	int i;
	switch (expr.Type)
	{
		case 0:
			if (expr.Op==1)
				return mh.Marked;
			else
				return !mh.Marked;
		case 1:
			if (expr.Op==1)
				return mh.Deleted;
			else
				return !mh.Deleted;
		case 2:
			if (expr.Op==1)
				return mh.Personal;
			else
				return !mh.Personal;
		case 3:
			if (expr.Op==1)
				return mh.Protected;
			else
				return !mh.Protected;
		case 4:
			if (expr.Op==1)
				return mh.New;
			else
				return !mh.New;
		case 5:
			if (expr.Op==1)
				return mh.NewPersonal;
			else
				return !mh.NewPersonal;
		case 6:
			switch (expr.Op)
			{
				case 3:
					return cur<expr.iValue;
				case 4:
					return cur<=expr.iValue;
				case 5:
					return cur==expr.iValue;
				case 6:
					return cur!=expr.iValue;
				case 7:
					return cur>=expr.iValue;
				case 8:
					return cur>expr.iValue;
			}
			break;
		case 7:
			switch (expr.Op)
			{
				case 3:
					return Date<expr.iValue;
				case 4:
					return Date<=expr.iValue;
				case 5:
					return Date==expr.iValue;
				case 6:
					return Date!=expr.iValue;
				case 7:
					return Date>=expr.iValue;
				case 8:
					return Date>expr.iValue;
			}
			break;
		case 8:
			if (cur<Keep+1)
				if (expr.Op==1)
					return 1;
			break;
		case 9:
			if (mh.Recvdt[0])
			{
				if (datetoint(mh.Recvdt)<Date-expr.iValue)
					if (expr.Op==1)
						return 1;
			} else
			{
				if (datetoint(mh.DateTime)<Date-expr.iValue)
					if (expr.Op==1)
						return 1;
			}
			break;
		case 10:
			if (expr.Op!=-1)
			{
				sprintf(dummy,"%s (%u:%u/%u.%u)",mh.WhoFrom,
						mh.OrigZone,mh.OrigNet,mh.OrigNode,mh.OrigPoint);
				i=(re_search(&expr.RegEx,dummy,strlen(dummy),0,
						strlen(dummy),&regs)>=0);
				if (expr.Op==1)
					return i;
				else
					return (!i);
			}
			break;
		case 11:
			if (expr.Op!=-1)
			{
				sprintf(dummy,"%s (%u:%u/%u.%u)",mh.WhoTo,
						mh.DestZone,mh.DestNet,mh.DestNode,mh.DestPoint);
				i=(re_search(&expr.RegEx,dummy,strlen(dummy),0,
						strlen(dummy),&regs)>=0);
				if (expr.Op==1)
					return i;
				else
					return (!i);
			}
			break;
		case 12:
			if (expr.Op!=-1)
			{
				strcpy(dummy,mh.Subj);
				i=(re_search(&expr.RegEx,dummy,strlen(dummy),0,
						strlen(dummy),&regs)>=0);
				if (expr.Op==1)
					return i;
				else
					return (!i);
			}
			break;
		case 13:
			i=findtext(mh,expr.Op==-1?NULL:&expr.RegEx);
			if (expr.Op==1)
				return (i);
			else
				return (!i);
		case 14:
			if (expr.Op==1)
				return ((mh.Attribute&expr.iValue)==expr.iValue);
			else
				return !(mh.Attribute&expr.iValue);
		case 15 ... 20:
			return 1;
		case 21:
			return (expr.Op==1);
	}
	return 0;
}

void doActions(MsgHdrType *mh, int mn, char *area)
{
	int num;
	char *d1, *d2, *d3, out[PATH_MAX], dummy[PATH_MAX], dummy2[PATH_MAX];
	char form[100];
	int mtl=0, mkl=0, mdl=0;
	LineListType *mt=NULL, *mk=NULL, *md=NULL;
	switch (ActionOn[0])
	{
		case 1:
			mh->Marked=1;
			break;
		case 2:
			mh->Marked=0;
			break;
		case 3:
			mh->Marked=1-mh->Marked;
			break;
	}
	switch (ActionOn[1])
	{
		case 1:
			if (!mh->Protected)
				mh->Deleted=1;
			break;
		case 2:
			mh->Deleted=0;
			break;
		case 3:
			mh->Deleted=1-mh->Deleted;
			if (mh->Protected && mh->Deleted) mh->Deleted=0;
			break;
	}
	switch (ActionOn[2])
	{
		case 1:
			mh->Personal=1;
			break;
		case 2:
			mh->Personal=0;
			break;
		case 3:
			mh->Personal=1-mh->Personal;
			break;
	}
	switch (ActionOn[3])
	{
		case 1:
			mh->Protected=1;
			break;
		case 2:
			mh->Protected=0;
			break;
		case 3:
			mh->Protected=1-mh->Protected;
			break;
	}
	switch (ActionOn[4])
	{
		case 1:
			mh->New=1;
			break;
		case 2:
			mh->New=0;
			break;
		case 3:
			mh->New=1-mh->New;
			break;
	}
	switch (ActionOn[5])
	{
		case 1:
			mh->NewPersonal=1;
			break;
		case 2:
			mh->NewPersonal=0;
			break;
		case 3:
			mh->NewPersonal=1-mh->NewPersonal;
			break;
	}
	if (AttributeMask[0])
		mh->Attribute=mh->Attribute|AttributeMask[0];
	if (AttributeMask[1])
		mh->Attribute=mh->Attribute&(~AttributeMask[1]);
	if (AttributeMask[2])
		mh->Attribute=mh->Attribute^AttributeMask[2];
	if (ActionOn[6])
	{
		out[0]=0;
		strcpy(form,formatlist);
		for (d1=form-1; d1; d1=d3)
		{
			d1++;
			d2=strchr(d1,'%');
			if (d2)
			{
				*d2++=0;
				strcat(out,d1);
				num=strtol(d2,&d3,10);
				switch (*d3)
				{
					case 'n':
						if (num)
						{
							sprintf(dummy2,"%%%uu",num);
							sprintf(dummy,dummy2,mn+1);
						} else
							sprintf(dummy,"%u",mn+1);
						break;
					case 'f':
						if (num)
						{
							strcpy(dummy,mh->WhoFrom);
							memset(dummy+strlen(mh->WhoFrom),32,num);
							dummy[num]=0;
						} else
							strcpy(dummy,mh->WhoFrom);
						break;
					case 't':
						if (num)
						{
							strcpy(dummy,mh->WhoTo);
							memset(dummy+strlen(mh->WhoTo),32,num);
							dummy[num]=0;
						} else
							strcpy(dummy,mh->WhoTo);
						break;
					case 's':
						if (num)
						{
							strcpy(dummy,mh->Subj);
							memset(dummy+strlen(mh->Subj),32,num);
							dummy[num]=0;
						} else
							strcpy(dummy,mh->Subj);
						break;
					case 'd':
						strcpy(dummy,mh->DateTime);
						if (num)
						{
							memset(dummy+strlen(dummy),32,num);
							dummy[num]=0;
						}
						break;
					case 'D':
						strcpy(dummy,mh->Recvdt);
						if (num)
						{
							memset(dummy+strlen(dummy),32,num);
							dummy[num]=0;
						}
						break;
					case 'F':
						sprintf(dummy,"%u:%u/%u.%u",
							mh->OrigNet,mh->OrigNet,
							mh->OrigNode,mh->OrigPoint);
						if (num)
						{
							memset(dummy+strlen(dummy),32,num);
							dummy[num]=0;
						}
						break;
					case 'T':
						sprintf(dummy,"%u:%u/%u.%u",
							mh->DestNet,mh->DestNet,
							mh->DestNode,mh->DestPoint);
						if (num)
						{
							memset(dummy+strlen(dummy),32,num);
							dummy[num]=0;
						}
						break;
					case '%':
						strcpy(dummy,"%");
						break;
					default:
						strcpy(dummy,"");
						break;
				}
				strcat(out,dummy);
			} else
			{
				strcat(out,d1);
				d3=NULL;
			}
		}
		fprintf(stdout,"%s\n",out);
	}
	if (ActionOn[7])
	{
		if (strchr(formatmsg,'t') || strchr(formatmsg,'k'))
			loadTextOfMsg(80,mh,&mtl,&mt,&mkl,&mk,&mdl,&md);
		outputMsg(stdout,mn+1,area,
				strchr(formatmsg,'h')?mh:NULL,
				mtl,strchr(formatmsg,'t')?&mt:NULL,
				mkl,strchr(formatmsg,'k')?&mk:NULL);
		killTextOfMsg(&mtl,&mt,&mkl,&mk,&mdl,&md);
	}
}

void toolarea(char *a)
{
	char log[80];
	int nummark=0, i, value, j, ok, toKeep=0;
	MsgHdrType *amh=NULL;
	openarea(a,NULL);
	loadmsghdr(&amh);
	DateTime(log);
	Date=datetoint(log);
	for (j=0; j<numExpression; j++)
	{
		switch (Expression[j].Type)
		{
			case 8:
				if (toKeep<Expression[j].iValue) toKeep=Expression[j].iValue;
				break;
			case 15:
				switch (Expression[j].Op)
				{
					case 3:
						if (!(MsgData.Marked<Expression[j].iValue)) goto ende;
					case 4:
						if (!(MsgData.Marked<=Expression[j].iValue)) goto ende;
					case 5:
						if (!(MsgData.Marked==Expression[j].iValue)) goto ende;
					case 6:
						if (!(MsgData.Marked!=Expression[j].iValue)) goto ende;
					case 7:
						if (!(MsgData.Marked>=Expression[j].iValue)) goto ende;
					case 8:
						if (!(MsgData.Marked>Expression[j].iValue)) goto ende;
				}
				break;
			case 16:
				switch (Expression[j].Op)
				{
					case 3:
						if (!(MsgData.Deleted<Expression[j].iValue)) goto ende;
					case 4:
						if (!(MsgData.Deleted<=Expression[j].iValue)) goto ende;
					case 5:
						if (!(MsgData.Deleted==Expression[j].iValue)) goto ende;
					case 6:
						if (!(MsgData.Deleted!=Expression[j].iValue)) goto ende;
					case 7:
						if (!(MsgData.Deleted>=Expression[j].iValue)) goto ende;
					case 8:
						if (!(MsgData.Deleted>Expression[j].iValue)) goto ende;
				}
				break;
			case 17:
				for (i=value=0; i<TotalMsgs; i++)
					if (amh[i].Personal) value++;
				switch (Expression[j].Op)
				{
					case 3:
						if (!(value<Expression[j].iValue)) goto ende;
					case 4:
						if (!(value<=Expression[j].iValue)) goto ende;
					case 5:
						if (!(value==Expression[j].iValue)) goto ende;
					case 6:
						if (!(value!=Expression[j].iValue)) goto ende;
					case 7:
						if (!(value>=Expression[j].iValue)) goto ende;
					case 8:
						if (!(value>Expression[j].iValue)) goto ende;
				}
				break;
			case 18:
				for (i=value=0; i<TotalMsgs; i++)
					if (amh[i].Protected) value++;
				switch (Expression[j].Op)
				{
					case 3:
						if (!(value<Expression[j].iValue)) goto ende;
					case 4:
						if (!(value<=Expression[j].iValue)) goto ende;
					case 5:
						if (!(value==Expression[j].iValue)) goto ende;
					case 6:
						if (!(value!=Expression[j].iValue)) goto ende;
					case 7:
						if (!(value>=Expression[j].iValue)) goto ende;
					case 8:
						if (!(value>Expression[j].iValue)) goto ende;
				}
				break;
			case 19:
				switch (Expression[j].Op)
				{
					case 3:
						if (!(MsgData.New<Expression[j].iValue)) goto ende;
					case 4:
						if (!(MsgData.New<=Expression[j].iValue)) goto ende;
					case 5:
						if (!(MsgData.New==Expression[j].iValue)) goto ende;
					case 6:
						if (!(MsgData.New!=Expression[j].iValue)) goto ende;
					case 7:
						if (!(MsgData.New>=Expression[j].iValue)) goto ende;
					case 8:
						if (!(MsgData.New>Expression[j].iValue)) goto ende;
				}
				break;
			case 20:
				switch (Expression[j].Op)
				{
					case 3:
						if (!(MsgData.NewPersonal<Expression[j].iValue)) goto ende;
					case 4:
						if (!(MsgData.NewPersonal<=Expression[j].iValue)) goto ende;
					case 5:
						if (!(MsgData.NewPersonal==Expression[j].iValue)) goto ende;
					case 6:
						if (!(MsgData.NewPersonal!=Expression[j].iValue)) goto ende;
					case 7:
						if (!(MsgData.NewPersonal>=Expression[j].iValue)) goto ende;
					case 8:
						if (!(MsgData.NewPersonal>Expression[j].iValue)) goto ende;
				}
				break;
		}
	}
	MsgData.Deleted=MsgData.Marked=MsgData.New=MsgData.NewPersonal=0;
	for (Keep=TotalMsgs-1, i=0; Keep>=0 && i<toKeep; Keep--)
		if (!amh[Keep].Deleted) i++;
	Keep++;
	if (ActionOn[6])
		fprintf(stdout,"###AREA###[%s]\n",a);
	for (i=0; i<TotalMsgs; i++, ok=0)
	{
		for (value=evalExpression(Expression[0],amh[i],i+1), j=1;
				j<numExpression; j++)
			if (Expression[j-1].NextOp)
				value=value||evalExpression(Expression[j],amh[i],i+1);
			else
				value=value&&evalExpression(Expression[j],amh[i],i+1);
		if (value)
		{
			doActions(&(amh[i]),i,a);
			EL=100;
			nummark++;
		}
		if (amh[i].Deleted)
			MsgData.Deleted++;
		else
		{
			if (amh[i].New) MsgData.New++;
			if (amh[i].NewPersonal) MsgData.NewPersonal++;
			if (amh[i].Marked) MsgData.Marked++;
		}
		printERR("\rMarking %s-[%u/%u]",a,nummark,i+1);
	}
	if (TotalMsgs) printERR("\n");
	savemsghdr(&amh);
	ende:
	closearea();
	if (ActionOn[6])
		fprintf(stdout,"###END_AREA###[%u]\n",nummark);
	Log("-%s [%u]",a,nummark);
}

int datetoint(char *s)
{
	char *d1, *d2, date[20];
	int t, m=0, j;
	strcpy(date,s);
	d1=strchr(date,'.');
	if (d1)
	{
		*d1++=0;
		t=atoi(date);
		d2=strchr(d1,'.');
		*d2++=0;
		m=atoi(d1);
	} else
	{
		d1=strchr(date,32);
		if (d1) *d1++=0;
		t=atoi(date);
		d2=strchr(d1,32);
		*d2++=0;
		if (strcasecmp(d1,"JAN")==0) m=1;
		if (strcasecmp(d1,"FEB")==0) m=2;
		if (strcasecmp(d1,"MAR")==0) m=3;
		if (strcasecmp(d1,"APR")==0) m=4;
		if (strcasecmp(d1,"MAY")==0) m=5;
		if (strcasecmp(d1,"JUN")==0) m=6;
		if (strcasecmp(d1,"JUL")==0) m=7;
		if (strcasecmp(d1,"AUG")==0) m=8;
		if (strcasecmp(d1,"SEP")==0) m=9;
		if (strcasecmp(d1,"OCT")==0) m=10;
		if (strcasecmp(d1,"NOV")==0) m=11;
		if (strcasecmp(d1,"DEC")==0) m=12;
	}
	for (d1=d2; *d1>='0' && *d1<='9'; d1++);
	*d1=0;
	j=atoi(d2);
	return (juldat(t,m,j));
}

int findtext(MsgHdrType mh, struct re_pattern_buffer *regex)
{
	char *text;
	int ok;
	struct re_registers regs;
	text=(char *)malloc(mh.NumRecs*256);
	fseek(mtf,mh.StartRec*256,SEEK_SET);
	fread(text,256,mh.NumRecs,mtf);
	if (regex)
		ok=(re_search(regex,text,strlen(text),0,strlen(text),&regs)>=0);
	else
		ok=0;
	free(text);
	return ok;
}

void utility(int argc, char **argv, void (*areafunct)(char *))
{
	FolderType *areas=NULL;
	FILE *af;
	int numareas=1, i, j, k;
	char file[PATH_MAX], *d1;
	strcpy(file,BasePath);
	strcat(file,"areas");
	if ((af=fopen(file,"rt"))!=NULL)
	{
		areas=(FolderType *)malloc(FolderSize);
		strcpy(areas[0].name,"NETMAIL");
		areas[0].num=0;
		while (fgets(file,80,af))
		{
			d1=strchr(file,'\n');
			if (d1) *d1=0;
			if (file[0]!='-')
				if (strchr(file,32)) continue;
			numareas++;
			areas=(FolderType *)realloc(areas,FolderSize*numareas);
			strncpy(areas[numareas-1].name,file,40);
			areas[numareas-1].name[39]=0;
			areas[numareas-1].marked=0;
			areas[numareas-1].unread=0;
		}
		fclose(af);
		if (argc)
		{
			for (i=0, j=1; i<argc; i++)
				if (argv[i][0]!='-') j=0;
			if (j)
				for (i=0; i<numareas; i++)
					if (areas[i].name[0]!='-') areas[i].marked=1;
			for (i=0; i<argc; i++)
			{
				if (argv[i][0]=='-')
					k=1;
				else
					k=0;
				if (argv[i][k]=='+')
				{
					for (j=0; j<numareas; j++)
						if (areas[j].name[0]=='-' &&
								strcasestr(areas[j].name,argv[i]+k+1)!=NULL)
							for (j++; j<numareas; j++)
							{
								if (areas[j].name[0]=='-')
								{
									j--;
									break;
								}
								if (k)
									areas[j].unread=1;
								else
									areas[j].marked=1;
							}
				} else
				{
					for (j=0; j<numareas; j++)
						if (strncasecmp(areas[j].name,argv[i]+k,strlen(argv[i]+k))==0)
						{
							if (k)
								areas[j].unread=1;
							else
								areas[j].marked=1;
						}
				}
			}
		} else
			for (i=0; i<numareas; i++)
				if (areas[i].name[0]!='-') areas[i].marked=1;
		for (i=0; i<numareas; i++)
			if (areas[i].marked && !areas[i].unread)
				(*areafunct)(areas[i].name);
		free(areas);
	}
}

int argcopy(char **argv)
{
	focus=-1;
	if (strncasecmp(argv[0],"all",3)==0) focus=0;
	if (strncasecmp(argv[0],"marked",6)==0) focus=1;
	if (strncasecmp(argv[0],"deleted",7)==0) focus=2;
	if (strncasecmp(argv[0],"unmarked",8)==0) focus=3;
	if (strncasecmp(argv[0],"undeleted",9)==0) focus=4;
	if (strncasecmp(argv[0],"protected",9)==0) focus=5;
	if (strncasecmp(argv[0],"unprotected",11)==0) focus=6;
	if (strncasecmp(argv[0],"new",3)==0) focus=7;
	if (strncasecmp(argv[0],"old",3)==0) focus=8;
	if (strncasecmp(argv[0],"private",7)==0) focus=9;
	if (strncasecmp(argv[0],"newpriv",7)==0) focus=10;
	strcpy(string,argv[1]);
	if (focus==-1) return 0;
	return 1;
}

void copyarea(char *a)
{
	MsgHdrType *amh=NULL, *nmh=NULL;
	MsgDataType nmd;
	int i, ok, newmsgs, tend, copied=0;
	FILE *ntf;
	char file[PATH_MAX], *text=NULL;
	tend=openarea(string,NULL);
	nmh=(MsgHdrType *)malloc(MsgHdrSize*TotalMsgs);
	loadmsghdr(&nmh);
	newmsgs=TotalMsgs;
	MsgData.ReadOnly=1;
	nmd=MsgData;
	openarea(a,NULL);
	amh=(MsgHdrType *)malloc(MsgHdrSize*TotalMsgs);
	loadmsghdr(&amh);
	sprintf(file,"%s+%s/text",BasePath,string);
	ntf=fopen(file,"r+b");
	fseek(ntf,256*tend,SEEK_SET);
	for (i=0; i<TotalMsgs; i++)
	{
		switch(focus)
		{
			case 0: ok=1; break;
			case 1: ok=amh[i].Marked; break;
			case 2: ok=amh[i].Deleted; break;
			case 3: ok=!amh[i].Marked; break;
			case 4: ok=!amh[i].Deleted; break;
			case 5: ok=amh[i].Protected; break;
			case 6: ok=!amh[i].Protected; break;
			case 7: ok=amh[i].New; break;
			case 8: ok=!amh[i].New; break;
			case 9: ok=amh[i].Personal; break;
			case 10: ok=amh[i].NewPersonal; break;
			default: ok=0; break;
		}
		if (ok)
		{
			EL=100;
			copied++;
			newmsgs++;
			nmh=(MsgHdrType *)realloc(nmh,MsgHdrSize*newmsgs);
			nmh[newmsgs-1]=amh[i];
			text=(char *)realloc(text,256*amh[i].NumRecs);
			fseek(mtf,256*amh[i].StartRec,SEEK_SET);
			fread(text,256,amh[i].NumRecs,mtf);
			fwrite(text,256,amh[i].NumRecs,ntf);
			nmh[newmsgs-1].StartRec=tend;
			tend+=amh[i].NumRecs;
			if (amh[i].Deleted) nmd.Deleted++;
			if (amh[i].New) nmd.New++;
			if (amh[i].NewPersonal) nmd.NewPersonal++;
			strncpy(nmh[newmsgs-1].Recvdt,a,20);
			nmh[newmsgs-1].Recvdt[19]=0;
		}
		printERR("\r%s[%u] -> %s[+%u]",a,i+1,string,copied);
	}
	if (TotalMsgs) printERR("\n");
	closearea();
	fclose(ntf);
	nmd.Linked=0;
	openarea(string,NULL);
	TotalMsgs=newmsgs;
	savemsghdr(&nmh);
	MsgData=nmd;
	closearea();
	free(text);
	free(amh);
	free(nmh);
	sprintf(file,"%s[%u] -> %s",a,copied,string);
}

void dupearea(char *a)
{
	MsgHdrType *amh=NULL;
	char **Msgid;
	char *text, *d1, *d2;
	int i, j, sorted, killed=0;
	Log("-DeDupeing Area %s",a);
	openarea(a,NULL);
	loadmsghdr(&amh);
	Msgid=(char **)malloc(TotalMsgs*sizeof(char *));
	for (i=0; i<TotalMsgs; i++)
	{
		fseek(mtf,256*amh[i].StartRec,SEEK_SET);
		text=(char *)malloc(amh[i].NumRecs*256);
		fread(text,256,amh[i].NumRecs,mtf);
		if ((d1=strstr(text,"\001MSGID:"))!=NULL)
		{
			while (*d1!=32) d1++;
			d1++;
			d2=strchr(d1,'\r');
			*d2=0;
			Msgid[i]=(char *)malloc(strlen(d1)+1);
			strcpy(Msgid[i],d1);
			*d2='\r';
		} else
		{
			Msgid[i]=(char *)malloc(1);
			Msgid[i][0]=0;
		}
		free(text);
		printERR("\rDeDupeing %s-read[%u]",a,i+1);
	}
	sorted=i;
	for (i=0; i<TotalMsgs; i++)
	{
		if (!amh[i].Deleted)
			for (j=i+1; j<TotalMsgs; j++)
				if (strcmp(Msgid[i],Msgid[j])==0 && Msgid[i][0])
					if (!amh[j].Deleted && !amh[j].Protected)
					{
						EL=100;
						amh[j].Deleted=1;
						MsgData.Deleted++;
						if (amh[j].New) MsgData.New--;
						if (amh[j].NewPersonal) MsgData.NewPersonal--;
						if (amh[j].Marked) MsgData.Marked--;
						killed++;
					}
		printERR("\rDeDupeing %s-read[%u]-kill[%u/%u]",a,sorted,killed,i+1);
	}
	Log("-DeDuped Area %s[%u/%u]",a,killed,i+1);
	if (TotalMsgs) printERR("\n");
	for (i=0; i<TotalMsgs; i++)
	{
		free(Msgid[i]);
	}
	free(Msgid);
	savemsghdr(&amh);
	closearea();
}

void newmsg(char *overarea)
{
	MsgHdrType hdr;
	LineListType *msgtxt=NULL, *kludges=NULL;
	int mln=0, kln=0, save=0, error=0, success=0, i;
	char area[80];
	memset(&hdr,0,MsgHdrSize);
	hdr.Mother=hdr.Child=hdr.nextBrother=hdr.prevBrother=
	hdr.nextSubject=hdr.prevSubject=65535;
	while (inputMsg(stdin,80,&save,area,&hdr,&mln,&msgtxt,&kln,&kludges,
			NULL,NULL))
	{
		if (savenewmsg(overarea?overarea:area,&hdr,&mln,&msgtxt,&kln,&kludges))
		{
			error++;
			Log("!Can't add to %s",overarea?overarea:area);
		} else
		{
			success++;
			Log("-message added to %s",overarea?overarea:area);
		}
		for (i=0; i<kln; i++) free(kludges[i].line);
		free(kludges);
		kludges=NULL;
		kln=0;
		for (i=0; i<mln; i++) free(msgtxt[i].line);
		free(msgtxt);
		msgtxt=NULL;
		mln=0;
		memset(&hdr,0,MsgHdrSize);
		hdr.Mother=hdr.Child=hdr.nextBrother=hdr.prevBrother=
		hdr.nextSubject=hdr.prevSubject=65535;
	}
	if (success)
	{
		Log("+Added %u messages",success);
		printERR("Added %u messages\n",success);
		EL=100;
	}
	if (error)
	{
		Log("!Encountered %u errors",error);
		printERR("Encountered %u errors!\n",error);
		EL=1;
	}
}

void lastline(char *s, LineListType **MsgTxt, int *MsgLines)
{
	(*MsgTxt)=(LineListType *)realloc((*MsgTxt),((*MsgLines)+1)*sizeof(LineListType));
	(*MsgTxt)[(*MsgLines)].line=(char *)malloc(strlen(s)+1);
	strcpy((*MsgTxt)[(*MsgLines)].line,s);
	(*MsgTxt)[(*MsgLines)].Quote=(*MsgTxt)[(*MsgLines)].CutOrigin=0;
	(*MsgTxt)[(*MsgLines)].CR=(*MsgTxt)[(*MsgLines)-1].CR=1;
	(*MsgLines)++;
}

int savenewmsg(char *area, MsgHdrType *hdr,
		int *MsgLines, LineListType **MsgTxt,
		int *KludgeLines, LineListType **Kludges)
{
	int tend, count, i, OriginOk=0, CutOk=0, dest, destn;
	char line[256], help[80], *d1, *d2, addrstr[40];
	Addr4dType addr;
	UserAkaType *aka;
	NLentryType nlent;
	tend=openarea(area,NULL);
	if (!mtf || !mhf) return 1;
	if (!MsgData.NetMail)
	{
		for (aka=Profile.Aka; aka; aka=aka->next)
			if (aka->UserZone==MsgData.RouteZone &&
					aka->UserNet==MsgData.RouteNet &&
					aka->UserNode==MsgData.RouteNode) break;
		if (aka)
		{
			hdr->OrigZone=aka->UserZone;
			hdr->OrigNet=aka->UserNet;
			hdr->OrigNode=aka->UserNode;
			hdr->OrigPoint=aka->UserPoint;
			Log("-route address substituted");
		} else
		{
			Log("!Can't find matching address");
			return 1;
		}
		if (!hdr->WhoFrom[0]) strcpy(hdr->WhoFrom,Profile.UserName);
		if (!hdr->WhoTo[0])
		{
			if (hdr->DestZone==0 && hdr->DestNet==0 &&
					hdr->DestNode==0 && hdr->DestPoint==0)
			{
				strcpy(hdr->WhoTo,"All");
				Log("-'All' substituted for destination");
			} else
			{
				sprintf(addrstr,"%u:%u/%u.%u",hdr->DestZone,
						hdr->DestNet,hdr->DestNode,hdr->DestPoint);
				if (keyentry(addrstr,&nlent))
				{
					Log("!Can't find destination in nl");
					return 1;
				} else
				{
					if ((d1=strchr(nlent.Sysop,32)))
					{
						*d1++=0;
						strcpy(hdr->WhoTo,d1);
						strcat(hdr->WhoTo," ");
						strcat(hdr->WhoTo,nlent.Sysop);
					} else strcpy(hdr->WhoTo,nlent.Sysop);
					Log("-got dest name from nl");
				}
			}
		}
		hdr->DestZone=hdr->DestNet=hdr->DestNode=hdr->DestPoint=0;
		for (i=0; i<(*MsgLines); i++)
		{
			if (strncmp((*MsgTxt)[i].line," * Origin:",10)==0)
			{
				strcpy(line,(*MsgTxt)[i].line);
				d1=strrchr(line,'(');
				if (d1)
				{
					d1++;
					d2=strchr(d1,')');
					if (d2)
					{
						*d2=0;
						Str2Addr(d1,&addr);
						for (aka=Profile.Aka; aka; aka=aka->next)
							if (aka->UserZone==addr.Zone && \
									aka->UserNet==addr.Net && \
									aka->UserNode==addr.Node && \
									aka->UserPoint==addr.Point) break;
						if (aka)
						{
							OriginOk=1;
							continue;
						}
					}
				}
				(*MsgTxt)[i].line[0]=0;
			}
			if (strncmp((*MsgTxt)[i].line,"--- ",4)==0 ||
					strcmp((*MsgTxt)[i].line,"---")==0)
				CutOk=1;
		}
		for (i=(*MsgLines)-1; i>=0 && (*MsgTxt)[i].line[0]==0; i--)
		{
			free((*MsgTxt)[i].line);
			(*MsgTxt)=(LineListType *)realloc((*MsgTxt),i*sizeof(LineListType));
			(*MsgLines)--;
		}
		if (!CutOk && !OriginOk)
			lastline("",MsgTxt,MsgLines);
		if (!CutOk && OriginOk)
		{
			for (i=(*MsgLines)-1; i>=0; i--)
				if (strncmp((*MsgTxt)[i].line," * Origin:",10)==0)
				{
					free((*MsgTxt)[i].line);
					if (i!=(*MsgLines)-1)
						memmove(&((*MsgTxt)[i]),&((*MsgTxt)[i+1]),\
								sizeof(LineListType)*((*MsgLines)-i-1));
					(*MsgLines)--;
					(*MsgTxt)=(LineListType *)realloc((*MsgTxt),sizeof(LineListType)*(*MsgLines));
					OriginOk=0;
					break;
				}
		}
		if (!CutOk)
		{
			sprintf(line,CUTline,VERSION,Mailer?"BinkleyTerm":"ifcico");
			lastline(line,MsgTxt,MsgLines);
		}
		if (!OriginOk)
		{
			sprintf(help," (%u:%u/%u.%u)",\
					hdr->OrigZone,hdr->OrigNet,hdr->OrigNode,hdr->OrigPoint);
			sprintf(line," * Origin: %s",\
					Origlines?OrigLine[rand()%Origlines].line:StandardOrig);
			if (strlen(help)+strlen(line)>78)
				line[79-strlen(help)]=0;
			strcat(line,help);
			lastline(line,MsgTxt,MsgLines);
		}
	} else
	{
		dest=(hdr->DestZone==0 && hdr->DestNet==0 &&
				hdr->DestNode==0 && hdr->DestPoint==0);
		destn=hdr->WhoTo[0]==0;
		if (!hdr->WhoFrom[0])
		{
			strcpy(hdr->WhoFrom,Profile.UserName);
			Log("-user name substituted");
		}
		if (dest && destn)
		{
			Log("!No destination");
			return 1;
		}
		if (dest)
		{
			if (nameentry(hdr->WhoTo,&nlent,1))
			{
				Log("!Can't find destination in nl");
				return 1;
			} else
			{
				hdr->DestZone=nlent.ad.Zone;
				hdr->DestNet=nlent.ad.Net;
				hdr->DestNode=nlent.ad.Node;
				hdr->DestPoint=nlent.ad.Point;
				Log("-got dest addr from nl");
			}
		}
		if (destn)
		{
			sprintf(addrstr,"%u:%u/%u.%u",hdr->DestZone,
					hdr->DestNet,hdr->DestNode,hdr->DestPoint);
			if (keyentry(addrstr,&nlent))
			{
				Log("!Can't find destination in nl");
				return 1;
			} else
			{
				if ((d1=strchr(nlent.Sysop,32)))
				{
					*d1++=0;
					strcpy(hdr->WhoTo,d1);
					strcat(hdr->WhoTo," ");
					strcat(hdr->WhoTo,nlent.Sysop);
				} else strcpy(hdr->WhoTo,nlent.Sysop);
				Log("-got dest name from nl");
			}
		}
		MatchAddr(hdr,0);
	}
	(*MsgTxt)[(*MsgLines)-1].CR=1;
	fseek(mtf,tend*256,SEEK_SET);
	for (i=count=0; i<(*KludgeLines); i++)
	{
		if (strncasecmp((*Kludges)[i].line,"SEEN-BY:",8)!=0 &&
				strncasecmp((*Kludges)[i].line,"\001PATH:",6)!=0)
		{
			fwrite((*Kludges)[i].line,strlen((*Kludges)[i].line),1,mtf);
			fputc((char)13,mtf);
			count+=strlen((*Kludges)[i].line)+1;
		}
	}
	for (i=0; i<(*MsgLines); i++)
	{
		fwrite((*MsgTxt)[i].line,strlen((*MsgTxt)[i].line),1,mtf);
		if ((*MsgTxt)[i].CR)
		{
			fputc((char)13,mtf);
			count++;
		} else
			if ((*MsgTxt)[i].line[strlen((*MsgTxt)[i].line)-1]!=32)
			{
				fputc((char)32,mtf);
				count++;
			}
		count+=strlen((*MsgTxt)[i].line);
	}
	for (i=0; i<(*KludgeLines); i++)
	{
		if (strncasecmp((*Kludges)[i].line,"SEEN-BY:",8)==0 ||
				strncasecmp((*Kludges)[i].line,"\001PATH:",6)==0)
		{
			fwrite((*Kludges)[i].line,strlen((*Kludges)[i].line),1,mtf);
			fputc((char)13,mtf);
			count+=strlen((*Kludges)[i].line)+1;
		}
	}
	fputc((char)0,mtf);
	count++;
	for (; count%256; count++) fputc((char)0,mtf);
	fflush(mtf);
	hdr->StartRec=tend;
	hdr->NumRecs=count/256;
	fseek(mhf,TotalMsgs*MsgHdrSize,SEEK_SET);
	fwrite(hdr,MsgHdrSize,1,mhf);
	fflush(mhf);
	if (hdr->Deleted)
		MsgData.Deleted++;
	else
	{
		if (hdr->Marked) MsgData.Marked++;
		if (hdr->New) MsgData.New++;
		if (hdr->NewPersonal) MsgData.NewPersonal++;
	}
	closearea();
	return 0;
}

static char NodeType[9][7]={"Zone","Region","Host","Hub","Pvt","Hold","Down",
"Node","Point"};

void printentry(NLentryType *nlent)
{
	char *d1;
	fprintf(stdout,"##NODELIST_ENTRY##\n");
	fprintf(stdout,"#Type: %s\n",NodeType[nlent->Type]);
	fprintf(stdout,"#Address: %u:%u/%u.%u\n",
			nlent->ad.Zone,nlent->ad.Net,nlent->ad.Node,nlent->ad.Point);
	fprintf(stdout,"#Box: %s\n",nlent->Box);
	if ((d1=strchr(nlent->Sysop,32)))
	{
		*d1++=0;
		fprintf(stdout,"#Sysop: %s %s\n",d1,nlent->Sysop);
	} else
		fprintf(stdout,"#Sysop: %s\n",nlent->Sysop);
	fprintf(stdout,"#Phone: %s\n",nlent->Phone);
	fprintf(stdout,"#Speed: %u\n",nlent->Speed);
	fprintf(stdout,"#Flags: %s\n",nlent->Flags);
}

void nllookup(char *s)
{
	Addr4dType addr;
	NLentryType nlent;
	char addrs[80];
	int i;
	Aka=Profile.Aka;
	fprintf(stdout,"###NODELIST_LOOKUP###[%s]\n",s);
	if (Str2Addr(s,&addr))
	{
		nameentry(s,&nlent,0);
		for (i=0; i<maxEntries; i++)
			printentry(&(Entries[i]));
	} else
	{
		sprintf(addrs,"%u:%u/%u.%u",addr.Zone,addr.Net,addr.Node,addr.Point);
		if (!keyentry(addrs,&nlent))
			printentry(&nlent);
	}
	fprintf(stdout,"###NODELIST_END###[%u]\n",maxEntries);
}

void spezExt(char *s)
{
}
