/* Expire old messages.
 * Inspired by 'expire.c' by Bernie Roehl.
 * Substantially rewritten for integration into KA9Q NOS,
 * WG7J v1.01 and later
 * by Johan. K. Reinalda, WG7J/PA3DIS, March/April 92
 *
 * Old bid expiry by WG7J, March 92
 */
/* 'expire n' sets the expire interval for n hours.
 * Each time the timer goes off, a new process is started,
 * that expires the old messages...
 *
 * The control file '~/spool/expire.dat' contains lists of
 * filename age
 * where 'filename' is the name of the .txt file under '~/spool/mail'
 * containing the messages (without the .txt extension) and where
 * age = maximum age of message in days.
 *
 * If no age is given, the default age is 21 days.
 *
 * filename can be extended into subdirectories, and can have either
 * '/', '\' or '.' to indicate subdirectories.
 * filename should NOT have the ending '.txt'
 */
#include <stdio.h>
#include <stdlib.h>
#ifdef MSDOS
#include <dir.h>
#include <dos.h>
#endif
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/timeb.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "global.h"
#include "config.h"
#include "timer.h"
#include "proc.h"
#include "bm.h"
#include "files.h"
#include "commands.h"
#include "socket.h"
#ifdef DELEGATE
#include "delegate.h"
#endif

#ifdef linux
extern int unlink __ARGS((char *));
#endif

#ifdef UNIX
#define mktime j_mktime
#endif

int ExpireActive = 0;
char *ExpireArea = NULLCHAR;
time_t ExpireLast = 0;
extern char *skipwhite __ARGS((char *ptr));
extern char *nntp_name_expansion __ARGS((char *name));
extern void statusCtl __ARGS((char *who, char *extension,struct let *info,int entry, int readit));
extern void updateCtl __ARGS((char *who,struct let *info));
extern void sortit __ARGS((char *fname,int entrysize, int searchsize, int strsize,time_t age));
void exp_function __ARGS((char *funcname, char *filename, char *fname,int theage,struct timer *thetimer,int strsize, int sortsize));
int dooldbids __ARGS((int argc,char **argv,void *p));
int dooldwpages __ARGS((int argc,char *argv[],void *p));
static void Oldbidprocess __ARGS((int a,void *v1, void *v2));
void Oldbidtick __ARGS((void *p));
static void Oldwpagesprocess __ARGS((int a,void *v1, void *v2));
void Oldwpagestick __ARGS((void *p));
static int dotimer __ARGS((int argc,char **argv,void *p));
static int doexpkick __ARGS((int argc,char **argv,void *p));
static int doage __ARGS((int argc,char **argv,void *p));


#ifndef __BORLANDC__

time_t j_mktime __ARGS((struct tm *));

/* If you're using BC++ 2.0 or higher, you don't need this,
 * but TCC 2.0 doesn't have it...
 */
/* Simple emulation of the mktime() call (sort-of works :-) )
 * doesn't do any error checking,
 * no timezone adjustments or value adjustments
 * neglects seconds,
 * and might be off by a day for leap year corrections
 * Simply 'sort-a' calculates the number of seconds since 1970 - WG7J
 */
time_t
j_mktime(t)
struct tm *t;
{
    static int total[12]={0,31,59,90,120,151,181,212,243,273,304,334};
    int years;
    int leapyears;
    int days;

    /* time count start at jan 1, 1970, 00:00 hrs */
    years = t->tm_year - 70;
    /* adjust for leap-years */
    leapyears = (years + 2) / 4;
    if (!((years+70) & 3) && (t->tm_mon < 2))
        --leapyears;

#ifdef linux
    days = years*365L + leapyears + total[t->tm_mon] + (t->tm_mday-1);
    return( days*86400L + t->tm_hour*3600L + t->tm_min*60L + t->tm_sec);
#else
#endif
    days = years*365L + leapyears + total[t->tm_mon] + t->tm_mday;
    return( days*86400L + t->tm_hour*3600L + t->tm_min*60L - 19*3600L);
}
#endif

/* Default expiry values: */
#define DEFAULT_AGE 21       /* 21 days from arrival date */
#define MSPHOUR (1000L*60L*60L)
#define MSPMINUTE (1000L*60L)
static struct timer Expiretimer;

static void Expireprocess __ARGS((int a,void *v1,void *v2));
void Expiretick __ARGS((void *));
static void expire __ARGS((char *,int));

int
#ifdef PROTOTYPES
doexpire(int argc,char **argv,void *p)
#else
doexpire(argc,argv,p)
int argc;
char *argv[];
void *p;
#endif
{
    if(argc < 2) {
        tprintf("timer: %lu/%lu hrs\n",
                read_timer(&Expiretimer)/MSPHOUR,
                dur_timer(&Expiretimer)/MSPHOUR);
        return 0;
	}
    if(*argv[1] == 'n') {
        Expiretick(NULL);
        return 0;
    }
    /* set the timer */
    stop_timer(&Expiretimer); /* Just in case */
    Expiretimer.func = (void (*)__FARGS((void*)))Expiretick;/* what to call on timeout */
    Expiretimer.arg = NULL;        /* dummy value */
    set_timer(&Expiretimer,atol(argv[1])*MSPHOUR); /* set timer duration */
    start_timer(&Expiretimer);
    return 0;
}

void
Expiretick(p)
void *p;
{
    start_timer(&Expiretimer);
    /* Spawn off the process */
    if(!ExpireActive)
	    if (newproc("Expiry", 2048, Expireprocess, 0, NULL, NULL, 0) == NULLPROC)
		log (-1, "Couldn't start Expiration process");
}

static void
Expireprocess(a,v1,v2)
int a;
void *v1, *v2;
{
    char line[80];
    int age = DEFAULT_AGE;
    char *cp;
    FILE *ctl;

    ExpireActive = 1;
    log(-1,"EXPIRE process started");
#ifdef DELEGATE
    purge_delegate();
#endif
    if ((ctl = fopen(Expirefile, "r")) == NULLFILE)	{
        ExpireActive = 0;
        return;
    }
    time (&ExpireLast);
    /* read lines from the control file */
    while(fgets(line, sizeof(line), ctl) != NULLCHAR) {
        pwait(NULL); /* be nice */
        if((*line == '#') || (*line == '\n')) /* comment or blank line */
            continue;
        rip(line);
        age = DEFAULT_AGE;
        /* terminate area name */
        if((cp=strpbrk(line, " \t")) != NULLCHAR) {
            /* there is age info */
            *cp++ = '\0';
            age = atoi(cp);
        }
        pwait(NULL); /* be nice */
	ExpireArea = line;
	nntp_name_expansion (line);
        expire(line,age);
        ExpireArea = NULLCHAR;
    }
    fclose(ctl);
    ExpireActive = 0;
}

void
expire(filename,age)
char *filename;
int age;
{
    char file[128], bckfile[128], bckctl[128], *cp;
    char buf[128];
    int keep,copy,expired;
    FILE *old;
    FILE *new;
    long pos;
    time_t now;
    time_t then;
    struct tm t;
    struct let lt;
    int entrynum = 1;

    /* first replace all '.' and '\' with '/' */
    for(cp=filename;*cp != '\0'; cp++)
        if((*cp == '.') || (*cp == '\\'))
            *cp = '/';

    if (mlock(Mailspool,filename)) {
        /* can't get a lock */
        return;
    }

    /* rename the old *.ctl file */
    sprintf(file,"%s/control/%s.ctl",Mailspool,filename);
    sprintf(bckctl,"%s/control/%s.exp",Mailspool,filename);
    unlink (bckctl);
    if(rename(file,bckctl) == -1) {
        rmlock(Mailspool, filename);
        return;
    }

    /* now append the 'home dir' in front of name */
    strcpy(file,Mailspool);
    strcat(file,"/");
    strcat(file, filename);

    /* get the name for the backup file */
    strcpy(bckfile,file);
    strcat(bckfile,".exp");
    strcat(file,".txt");

    /* rename the file */
    unlink(bckfile);
    if(rename(file,bckfile) == -1) {
        rmlock(Mailspool, filename);
        return;
    }

    pwait (NULL);
    /* open the backup file and the new txt file */
    if((old = fopen(bckfile, READ_TEXT)) == NULLFILE) {
        rmlock(Mailspool, filename);
		return;
    }
    if((new = fopen(file, WRITE_TEXT)) == NULLFILE) {
        rmlock(Mailspool, filename);
		return;
    }
    pwait (NULL);
    time(&now);
    copy = expired = 0;
    pos=ftell(old);
    while(fgets(buf,sizeof(buf),old) != NULLCHAR) {
        pwait(NULL);
        if (!strncmp(buf,"From ",5)) {
            /* start of next message; at this point
             * pos has the offset of the start of this line
             */
            keep = copy = 0;
            while(fgets(buf,sizeof(buf),old) != NULLCHAR) {
		pwait (NULL);
                if(*buf == '\n')
                    break; /* end of headers */
                if(htype(buf) == DATE) {
                    /* find age from ARPA style date */
                    /* check to see if there is a "Day, " field */
                    if((cp=strchr(buf,',')) != NULLCHAR)  {
                        /* probably standard ARPA style header */
                        cp = &buf[11]; /* get past header and DAY field */
                    } else {
                        /* probably a NNTP style message, that has no
                         * "Day, " part in the date line
                         */
                         cp = &buf[6];
                    }
                    /* now we should be at the start of the
                     * "14 Apr 92 08:14:32" string
                     */
                    if(strlen(cp) < 17) /* Too short */
                        break;
                    t.tm_mday = atoi(cp);
                    if(*(++cp) != ' ')
                        ++cp;
                    ++cp;
                    for(t.tm_mon=0; t.tm_mon < 12; t.tm_mon++)
                        if(strnicmp(Months[t.tm_mon],cp,3) == 0)
                            break;
                    if(t.tm_mon == 12)
                        break; /* invalid */
                    t.tm_year = atoi(cp+4);
                    t.tm_hour = atoi(cp+7);
                    t.tm_min = atoi(cp+10);
                    t.tm_sec = atoi(cp+13);
                    /*
                    printf("%d %d %d, %d %d %d\n",
                    t.tm_mday,t.tm_mon,t.tm_year,t.tm_hour,t.tm_min,t.tm_sec);
                     */
                     if((then=mktime(&t)) == -1)
                        break; /* invalid, delete */
                    /* Now check against age */
                    if(now-then < (age*86400L))
                        keep = 1;
                    break;
                }
            }
            statusCtl (filename, "exp", &lt, entrynum, 1);
	    if (!keep && (lt.status & BM_PERMANENT))
    		keep = 1;		/* don't expire permanent messages */
            if(keep) {
                /* rewind to start of this message,
                 * write from-line and copy the rest
                 */
                pwait (NULL);
                if (lt.status & BM_DELETE)
                	expired++;
                else	{
                	lt.start = ftell (new);
                	updateCtl (filename, &lt);
	                fseek(old,pos,SEEK_SET);
        	        fgets(buf,sizeof(buf),old);
                	fputs(buf,new);
	                copy = 1;
	        }
            } else
                expired++;
            entrynum++;
        } else { /* Any none 'from' line */
            if(copy)
                /* we're in the middle of copying a message */
                fputs(buf,new);
        }
        pwait (NULL);
        pos=ftell(old);
    }
    fclose(new);
    fclose(old);
    pwait (NULL);
    unlink(bckfile);
    unlink (bckctl);
    rmlock(Mailspool,filename);
    if(expired)
        log(-1,"Expired: %d in %s",expired,filename);
}


void
exp_function(funcname, filename, fname, theage, thetimer, strsize, sortsize)
char *funcname, *filename, *fname;
int theage;
struct timer *thetimer;
int strsize, sortsize;
{
extern int expired;
time_t age;
int didit;

    stop_timer(thetimer);
    log(-1,"%s: processing",funcname);
    pwait (NULL);

    didit = merge (filename);		/* add in contents of '.new' file */
    if (!didit)
	return;		/* skip expire if no NEW extries - for performance */
    age = (time_t)(theage*86400L);
    sortit (filename, strsize + 17, sortsize, strsize,age);	/* sort entire file */

    if(expired)
        log(-1,"%s: %d expired",funcname, expired);
    start_timer(thetimer);
    return;
}

/******************************************************************/
/* This program will deleted old BID's from the history file,
 * after making a backup copy.
 *
 *  Eg. 'oldbids 24 30' will try to delete all bids older then 30 days
 *      every 24 hours.
 *      'oldbids now' will do it now, with set value of age.
 *
 * Copyright 1992, Johan. K. Reinalda, WG7J/PA3DIS
 *      email : johan@ece.orst.edu
 *      packet: wg7j@wg7j.or.usa.na
 *
 * Any part of this source may be freely distributed for none-commercial,
 * amateur radio use only, as long as credit is given to the author.
 *
 * v1.0 920325
 * Modified to add oldwpages using shared code TNOS 1.0     BAL
 */
static struct timer Oldbidtimer;
static int Oldbid_age = 30;
static struct timer Oldwpagestimer;
static int Oldwpages_age = 30;

static void
Oldbidprocess(a,v1,v2)
int a;
void *v1, *v2;
{
    exp_function ("Oldbid", Historyfile, "history", Oldbid_age, &Oldbidtimer, 13, 12);
}

void
Oldbidtick(p)
void *p;
{
	if (newproc("Oldbid", 2048, Oldbidprocess, 0, NULL, NULL, 0) == NULLPROC)
		log (-1, "Couldn't start Oldbid process");
}

void
RenewWPages ()
{
    exp_function ("Oldwpages", WhitePages, "wpages", Oldwpages_age, &Oldwpagestimer, 13, 6);
}


static void
Oldwpagesprocess(a,v1,v2)
int a;
void *v1, *v2;
{
    exp_function ("Oldwpages", WhitePagesBBS, "wpagebbs", Oldwpages_age, &Oldwpagestimer, 32, 6);
    RenewWPages();
}


void
Oldwpagestick(p)
void *p;
{
	if (newproc("Oldwpages", 2048, Oldwpagesprocess, 0, NULL, NULL, 0) == NULLPROC)
		log (-1, "Couldn't start Oldwpages process");
}

static char *names[] = {"White Pages", "Bulletin ID"};

static int
dotimer(argc,argv,p)
int argc;
char *argv[];
void *p;
{
struct timer *thetimer;
void (*thefunc) __ARGS((void *));	/* Function to call at expiration */

	thetimer = (p) ? &Oldbidtimer : &Oldwpagestimer;
	thefunc = (p) ? Oldbidtick : Oldwpagestick;
	if(argc < 2){
		tprintf("%s timer: %lu/%lu minutes\n", names[(int)p],
			read_timer(thetimer)/MSPMINUTE,
			dur_timer(thetimer)/MSPMINUTE);
		return 0;
	}
	stop_timer (thetimer);	/* just in case */
	thetimer->func = (void (*)__ARGS((void *)))thefunc;/* what to call on timeout */
	thetimer->arg = NULL;		/* dummy value */
	set_timer(thetimer,atol(argv[1])*MSPMINUTE); /* set timer duration */
/*	(*thefunc)(NULL);    */   /*  Do one now and start it all!*/
	start_timer(thetimer);     /* fire it up */
	return 0;
}

static int
doexpkick(argc,argv,p)
int argc;
char *argv[];
void *p;
{
void (*thefunc) __ARGS((void *));	/* Function to call at expiration */

	thefunc = (p) ? Oldbidtick : Oldwpagestick;
	(*thefunc) (NULL);
	return 0;
}


static int
doage(argc,argv,p)
int argc;
char *argv[];
void *p;
{
int *theage;

	theage = (p) ? &Oldbid_age : &Oldwpages_age;
	if(argc < 2)
		tprintf("%s age: %d days\n", names[(int)p], *theage);
	else
	        *theage = atoi(argv[1]);
	return 0;
}




static struct cmds DFAR OLDcmds[] = {
	"age",		doage,		0,	0,	NULLCHAR,
	"kick",		doexpkick,	0,	0,	NULLCHAR,
	"timer",	dotimer,	0,	0,	NULLCHAR,
	NULLCHAR,
};


int
#ifdef PROTOTYPES
dooldbids(int argc,char **argv,void *p)
#else
dooldbids(argc,argv,p)
int argc;
char *argv[];
void *p;
#endif
{
	return (subcmd (OLDcmds, argc, argv, (void *)1));
}


int
#ifdef PROTOTYPES
dooldwpages(int argc,char *argv[],void *p)
#else
dooldwpages(argc,argv,p)
int argc;
char *argv[];
void *p;
#endif
{
	return (subcmd (OLDcmds, argc, argv, (void *)0));
}

