/*****************************************************************************
 *
 * $Id: tic.c,v 1.58 2007/10/12 19:19:01 mbse Exp $
 * Purpose ...............: Process .tic files
 *
 *****************************************************************************
 * Copyright (C) 1997-2007
 *   
 * Michiel Broek		FIDO:		2:280/2802
 * Beekmansbos 10
 * 1971 BV IJmuiden
 * the Netherlands
 *
 * This file is part of MBSE BBS.
 *
 * This BBS is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * MBSE BBS is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MBSE BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *****************************************************************************/

#include "../config.h"
#include "../lib/mbselib.h"
#include "../lib/users.h"
#include "../lib/mbsedb.h"
#include "unpack.h"
#include "fsort.h"
#include "orphans.h"
#include "ptic.h"
#include "mover.h"
#include "tic.h"

#define	UNPACK_FACTOR 300

int	tic_in  = 0;				/* .tic files received	     */
int	tic_imp = 0;				/* imported .tic files	     */
int	tic_out = 0;				/* .tic files sent	     */
int	tic_bad = 0;				/* bad .tic files	     */
int	tic_dup = 0;				/* dupe .tic files	     */


extern	int do_unprot;
extern	int do_quiet;
extern	int tic_in;
extern	int do_flush;


/*
 *  returns:	-1 = Errors.
 *		0  = No files processed
 *		1  = Processed file(s)
 */
int Tic()
{
    char	    *inbound, *fname;
    DIR		    *dp;
    struct dirent   *de;
    struct stat	    sbuf;
    int		    i, rc = 0, Age;
    fd_list	    *fdl = NULL;
    orphans	    *opl = NULL, *tmp;
    time_t	    Now, Fdate;

    IsDoing("Process .tic files");
    CompileNL = FALSE;

    if (do_unprot) {
	inbound = xstrcpy(CFG.inbound);
    } else {
	inbound = xstrcpy(CFG.pinbound);
    }
    Syslog('+', "Pass: process ticfiles (%s)", inbound);

    if (enoughspace(CFG.freespace) == 0) {
	Syslog('+', "Low diskspace, abort tic processing");
	free(inbound);
	return -1;
    }

    if (chdir(inbound) == -1) {
	WriteError("$Can't chdir(%s)", inbound);
	free(inbound);
	return -1;
    }

    if ((dp = opendir(inbound)) == NULL) {
	WriteError("$Can't opendir(%s)", inbound);
	free(inbound);
	return -1;
    }

    while ((de = readdir(dp))) {
	if ((strlen(de->d_name) == 12) && (strncasecmp(de->d_name+11, "c", 1) == 0)) {
	    if ((strncasecmp(de->d_name+8, ".a", 2) == 0) || (strncasecmp(de->d_name+8, ".c", 2) == 0) ||
		(strncasecmp(de->d_name+8, ".z", 2) == 0) || (strncasecmp(de->d_name+8, ".l", 2) == 0) ||
		(strncasecmp(de->d_name+8, ".r", 2) == 0) || (strncasecmp(de->d_name+8, ".0", 2) == 0)) {
		if (checkspace(inbound, de->d_name, UNPACK_FACTOR)) {
		    if ((unpack(de->d_name)) != 0) {
			WriteError("Error unpacking %s", de->d_name);
		    }
		} else {
		    Syslog('+', "Insufficient space to unpack file %s", de->d_name);
		}
	    }
	}
    }

    rewinddir(dp);
    while ((de = readdir(dp))) {
	if ((strlen(de->d_name) == 12) && (strncasecmp(de->d_name+8, ".tic", 4) == 0)) {
	    stat(de->d_name, &sbuf);
	    fill_fdlist(&fdl, de->d_name, sbuf.st_mtime);
	}
    }

    closedir(dp);
    sort_fdlist(&fdl);

    while ((fname = pull_fdlist(&fdl)) != NULL) {
	if (LoadTic(inbound, fname, &opl) == 0)
	    rc = 1;
	if (IsSema((char *)"upsalarm")) {
	    rc = 0;
	    Syslog('+', "Detected upsalarm semafore, aborting tic processing");
	    break;
	}
	if (enoughspace(CFG.freespace) == 0) {
	    Syslog('+', "Low diskspace, aborting tic processing");
	    rc = 0;
	    break;
	}
    }

    if (!do_quiet) {
	printf("\r");
	for (i = 0; i < 79; i++)
	    printf(" ");
	printf("\r");
	fflush(stdout);
    }

    if (rc)
	do_flush = TRUE;

    if (CompileNL) 
	CreateSema((char *)"mbindex");

    /*
     * Handle the array with orphaned and bad crc ticfiles.
     */
    Now = time(NULL);
    for (tmp = opl; tmp; tmp = tmp->next) {

	/*
	 * Bad CRC and not marked purged are real crc errors.
	 */
	if (tmp->BadCRC && (! tmp->Purged)) {
	    Syslog('+', "Moving %s and %s to badtic directory", tmp->TicName, tmp->FileName);
	    mover(tmp->TicName);
	    mover(tmp->FileName);
	    tic_bad++;
	}

	/*
	 * Orphans that are not marked purged are real orphans, check age.
	 */
	if (tmp->Orphaned && (! tmp->Purged)) {
	    fname = calloc(PATH_MAX, sizeof(char));
	    snprintf(fname, PATH_MAX, "%s/%s", inbound, tmp->TicName);
	    Fdate = file_time(fname);
	    Age = (Now - Fdate) / 84400;
	    if (Age > 21) {
		Syslog('+', "Moving %s of %d days old to badtic", tmp->TicName, Age);
		tic_bad++;
		mover(tmp->TicName);
	    } else {
		Syslog('+', "Keeping %s of %d days old for %d days", tmp->TicName, Age, 21 - Age);
	    }
	    free(fname);
	}

	/*
	 * If marked to purge, remove the ticfile
	 */
	if (tmp->Purged) {
	    fname = calloc(PATH_MAX, sizeof(char));
	    snprintf(fname, PATH_MAX, "%s/%s", inbound, tmp->TicName);
	    unlink(fname);
	    Syslog('+', "Removing obsolete %s", tmp->TicName);
	    free(fname);
	}
    }
    tidy_orphans(&opl);

    free(inbound);
    return rc;
}



/*
 * Returns > 0 if error, 0 if ok.
 */
int LoadTic(char *inb, char *tfn, orphans **opl)
{
    FILE	    *tfp;
    char	    *Temp, *Temp2, *Buf, *Log = NULL, RealName[256];
    int		    i, j, rc, bufsize, DescCnt = FALSE;
    fa_list	    *sbl = NULL;

    if (CFG.slow_util && do_quiet)
	msleep(1);

    memset(&RealName, 0, sizeof(RealName));
    memset(&TIC, 0, sizeof(TIC));
    memset(&T_File, 0, sizeof(T_File));

    snprintf(TIC.Inbound, PATH_MAX, "%s", inb);
    strncpy(TIC.TicName, tfn, 12);

    chdir(inb);
    if ((tfp = fopen(tfn, "r")) == NULL) {
	WriteError("$Cannot open %s", tfn);
	return 1;
    }

    /*
     * Although a TIC line may only be 255 characters long,
     * nobody seems to care and lines are up to 1024 characters
     * long.
     */
    if (PATH_MAX > 1024)
	bufsize = PATH_MAX;
    else
	bufsize = 1024;
    Temp = calloc(bufsize+1, sizeof(char));
    Buf  = calloc(bufsize+1, sizeof(char));

    while ((fgets(Buf, bufsize, tfp)) != NULL) {

	if (strlen(Buf) == bufsize)
	    Syslog('!', "Detected a TIC file line of %d characters long", bufsize);

	/*
	 * Remove all garbage from this tic line.
	 */
	Temp[0] = '\0';
	j = 0;
	for (i = 0; i < strlen(Buf); i++) {
	    if (isprint(Buf[i] & 0x7f)) {
		Temp[j] = Buf[i] & 0x7f;
		j++;
	    }
	}
	Temp[j] = '\0';

	if (strncasecmp(Temp, "hatch", 5) == 0) {
	    TIC.TicIn.Hatch = TRUE;

	} else if (TIC.TicIn.Hatch && (strncasecmp(Temp, "pth ", 4) == 0)) {
	    strncpy(TIC.TicIn.Pth, Temp+4, PATH_MAX);

	} else if (strncasecmp(Temp, "area ", 5) == 0) {
	    strncpy(TIC.TicIn.Area, Temp+5, 20);
	    strncpy(T_File.Echo, Temp+5, 20);

	} else if (strncasecmp(Temp, "origin ", 7) == 0) {
	    strncpy(TIC.TicIn.Origin, Temp+7, 80);
	    strncpy(T_File.Origin, Temp+7, 80);

	} else if (strncasecmp(Temp, "from ", 5) == 0) {
	    strncpy(TIC.TicIn.From, Temp+5, 80);
	    strncpy(T_File.From, Temp+5, 80);

	} else if (strncasecmp(Temp, "file ", 5) == 0) {
	    strncpy(TIC.TicIn.File, Temp+5, 80);
	    for (i = 0; i < strlen(TIC.TicIn.File); i++)
		TIC.TicIn.File[i] = toupper(TIC.TicIn.File[i]);
	    
	} else if (strncasecmp(Temp, "fullname ", 9) == 0) {
	    strncpy(TIC.TicIn.FullName, Temp+9, 80);

	} else if (strncasecmp(Temp, "created ", 8) == 0) {
	    strncpy(TIC.TicIn.Created, Temp+8, 80);

	} else if (strncasecmp(Temp, "magic ", 6) == 0) {
	    strncpy(TIC.TicIn.Magic, Temp+6, 20);
	    strncpy(T_File.Magic, Temp+6, 20);

	} else if (strncasecmp(Temp, "crc ", 4) == 0) {
	    TIC.Crc_Int = strtoul(Temp+4, (char **)NULL, 16);
	    snprintf(TIC.TicIn.Crc, 9, "%08X", TIC.Crc_Int);
	    strcpy(T_File.Crc, TIC.TicIn.Crc);

	} else if (strncasecmp(Temp, "pw ", 3) == 0) {
	    strncpy(TIC.TicIn.Pw, Temp+3, 20);

	} else if (strncasecmp(Temp, "replaces ", 9) == 0) {
	    strncpy(TIC.TicIn.Replace, Temp+9, 80);
	    strncpy(T_File.Replace, Temp+9, 80);

	} else if (strncasecmp(Temp, "desc ", 5) == 0) {
	    if (!DescCnt) {
		strncpy(TIC.TicIn.Desc, Temp+5, 1023);
		strncpy(T_File.Desc, TIC.TicIn.Desc, 255);
		DescCnt = TRUE;
	    } else {
		Syslog('!', "More than one \"Desc\" line");
	    }
	
	} else if (strncasecmp(Temp, "path ", 5) == 0) {
	    if (strchr(Temp+5, ':') && strchr(Temp+5, '/')) {
		strncpy(TIC.TicIn.Path[TIC.TicIn.TotPath], Temp+5, 80);
		TIC.TicIn.TotPath++;
		TIC.Aka.zone = atoi(strtok(Temp+5, ":"));
		TIC.Aka.net  = atoi(strtok(NULL, "/"));
		TIC.Aka.node = atoi(strtok(NULL, "\0"));
		for (i = 0; i < 40; i++)
		    if ((CFG.akavalid[i]) && (CFG.aka[i].zone  == TIC.Aka.zone) && (CFG.aka[i].net   == TIC.Aka.net) &&
			(CFG.aka[i].node  == TIC.Aka.node) && (!CFG.aka[i].point)) {
			TIC.TicIn.PathError = TRUE;
			Syslog('+', "Aka %d: %s in path", i + 1, aka2str(CFG.aka[i]));
		    }
	    } else {
		WriteError("No valid AKA in Path line: \"%s\"", printable(Temp, 0));
		WriteError("Report this to author of that program");
	    }
		
	} else if (strncasecmp(Temp, "seenby ", 7) == 0) {
	    if (strchr(Temp+7, ':') && strchr(Temp+7, '/')) {
		fill_list(&sbl, Temp+7, NULL);
	    } else {
		WriteError("No valid AKA in Seenby line: \"%s\"", printable(Temp, 0));
	    }

	} else if (strncasecmp(Temp, "areadesc ", 9) == 0) {
	    strncpy(TIC.TicIn.AreaDesc, Temp+9, 60);

	} else if (strncasecmp(Temp, "to ", 3) == 0) {
	    /*
	     * Drop this one
	     * FIXME: should check if this is for us.
	     */
	} else if (strncasecmp(Temp, "size ", 5) == 0) {
	    TIC.TicIn.Size = atoi(Temp+5);

	} else if (strncasecmp(Temp, "date ", 5) == 0) {
	    /*
	     * Drop this one (HTick writes these)
	     */
	} else if (strncasecmp(Temp, "cost ", 5) == 0) {
	    TIC.TicIn.Cost = atoi(Temp+5);

	} else if (strncasecmp(Temp, "ldesc ", 6) == 0) {
	    if (TIC.TicIn.TotLDesc < 25) {
		strncpy(TIC.TicIn.LDesc[TIC.TicIn.TotLDesc], Temp+6, 80);
		TIC.TicIn.TotLDesc++;
	    } else {
		Syslog('f', "Too many LDesc lines in TIC file");
	    }
	    
	} else if (strncasecmp(Temp, "destination ", 12) == 0) {
	    /*
	     * Drop this one
	     */
	} else {
	    /*
	     * If we didn't find a matching keyword it is a line we
	     * will just remember and forward if there are downlinks.
	     */
	    if (strlen(Temp) > 127) {
		Syslog('+', "Unknown too long TIC line dropped");
	    } else if (TIC.TicIn.Unknowns < 25) {
		strncpy(TIC.TicIn.Unknown[TIC.TicIn.Unknowns], Temp, 127);
		TIC.TicIn.Unknowns++;
	    }
	}
    }
    fclose(tfp);

    /*
     * Do some basic checks on the loaded ticfile.
     */
    if ( (strlen(TIC.TicIn.File) == 0) || (strlen(TIC.TicIn.Area) == 0) ||
	 (strlen(TIC.TicIn.From) == 0) || (strlen(TIC.TicIn.Origin) == 0)) {
	WriteError("TIC file %s misses important information", TIC.TicName);
	tidy_falist(&sbl);
	mover(TIC.TicName);
	tic_in++;
	tic_bad++;
	return 1;
    }

    if (TIC.TicIn.TotLDesc) {
	/*
	 * First check for a bug in Harald Harms Allfix program that
	 * lets Allfix forward dummy Ldesc lines with the contents:
	 * "Long description not available"
	 */
	if (strstr(TIC.TicIn.LDesc[0], "ion not avail") != NULL) {
	    Syslog('!', "Killing invalid Ldesc line(s)");
	    TIC.TicIn.TotLDesc = 0;
	}
    }
    if (TIC.TicIn.TotLDesc) {
	T_File.TotLdesc = TIC.TicIn.TotLDesc;
	for (i = 0; i < TIC.TicIn.TotLDesc; i++) {
	    strncpy(T_File.LDesc[i], TIC.TicIn.LDesc[i], 48);
	}
    }

    /*
     * Show on screen what we are doing
     */
    if (!do_quiet) {
	mbse_colour(CYAN, BLACK);
	printf("\r");
	for (i = 0; i < 79; i++)
	    printf(" ");
	printf("\rTic: %12s  File: %-14s Area: %-12s ", TIC.TicName, TIC.TicIn.File, TIC.TicIn.Area);
	fflush(stdout);
    }

    /*
     * Show in logfile what we are doing
     */
    Syslog('+', "Processing %s, %s area %s from %s", TIC.TicName, TIC.TicIn.File, TIC.TicIn.Area, TIC.TicIn.From);
    Syslog('+', "+- %s", TIC.TicIn.Created);
    Log = NULL;

    if (strlen(TIC.TicIn.Replace)) {
	Log = xstrcpy((char *)"Replace ");
	Log = xstrcat(Log, TIC.TicIn.Replace);
    }
    if (strlen(TIC.TicIn.Magic)) {
	if (Log != NULL)
	    Log = xstrcat(Log, (char *)", Magic ");
	else
	    Log = xstrcpy((char *)"Magic ");
	Log = xstrcat(Log, TIC.TicIn.Magic);
    }
    if (Log != NULL) {
	Syslog('+', "%s", Log);
	free(Log);
	Log = NULL;
    }

    strcpy(Temp, TIC.TicIn.From);
    TIC.Aka.zone = atoi(strtok(Temp, ":"));
    TIC.Aka.net  = atoi(strtok(NULL, "/"));
    TIC.Aka.node = atoi(strtok(NULL, "@\0"));
    if (SearchFidonet(TIC.Aka.zone))
	strcpy(TIC.Aka.domain, fidonet.domain);
    strcpy(Temp, TIC.TicIn.Origin);
    TIC.OrgAka.zone = atoi(strtok(Temp, ":"));
    TIC.OrgAka.net  = atoi(strtok(NULL, "/"));
    TIC.OrgAka.node = atoi(strtok(NULL, "@\0"));
    if (SearchFidonet(TIC.OrgAka.zone))
	strcpy(TIC.OrgAka.domain, fidonet.domain);

    Temp2 = calloc(PATH_MAX, sizeof(char));

    if (TIC.TicIn.Hatch) {
	/*
	 * Try to move the hatched file to the inbound
	 */
	snprintf(Temp, bufsize, "%s/%s", TIC.TicIn.Pth, TIC.TicIn.FullName);
	if (file_exist(Temp, R_OK) == 0) {
	    strcpy(RealName, TIC.TicIn.FullName);
	} else {
	    WriteError("Can't find %s", Temp);
	    tidy_falist(&sbl);
	    return 2;
	}
	snprintf(Temp2, PATH_MAX, "%s/%s", TIC.Inbound, TIC.TicIn.FullName);
	if ((rc = file_mv(Temp, Temp2))) {
	    WriteError("Can't move %s to inbound: %s", Temp, strerror(rc));
	    tidy_falist(&sbl);
	    return 1;
	}
	if (!strlen(TIC.TicIn.File)) {
	    strcpy(Temp, TIC.TicIn.FullName);
	    name_mangle(Temp);
	    strncpy(TIC.TicIn.File, Temp, 12);
	    Syslog('f', "Local hatch created 8.3 name %s", Temp);
	}
    } else {
	/*
	 * Find out what the real name of the file is,
	 * most likely this is a 8.3 filename.
	 */
	strncpy(RealName, TIC.TicIn.File, 255);
	Syslog('f', "getfilecase(%s, %s)", TIC.Inbound, RealName);
	if (! getfilecase(TIC.Inbound, RealName)) {
	    strncpy(RealName, TIC.TicIn.FullName, 255);
	    Syslog('f', "getfilecase(%s, %s)", TIC.Inbound, RealName);
	    if (! getfilecase(TIC.Inbound, RealName)) {
		memset(&RealName, 0, sizeof(RealName));
	    }
	}
    }

    if (strlen(RealName) == 0) {
	/*
	 * We leave RealName empty, the ProcessTic function
	 * will handle this orphaned tic file.
	 */
	TIC.Orphaned = TRUE;
	Syslog('+', "Can't find file in inbound, will check later");
    } else {
	/*
	 * If no LFN received in the ticfile and the file in the inbound is the same as the 8.3 name
	 * but only the case is different, then treat the real filename as LFN.
	 */
	if ((strlen(TIC.TicIn.FullName) == 0) && strcmp(TIC.TicIn.File, RealName) && (strcasecmp(TIC.TicIn.File, RealName) == 0)) {
	    Syslog('f', "Real filename possible LFN, faking it");
	    strcpy(TIC.TicIn.FullName, RealName);
	}
	Syslog('f', "Real filename in inbound is \"%s\"", RealName);
	if ((strlen(TIC.TicIn.FullName)) == 0) {
	    Syslog('f', "LFN is empty, create lowercase one");
	    strncpy(TIC.TicIn.FullName, RealName, 255);
	    for (i = 0; i < strlen(TIC.TicIn.FullName); i++)
		TIC.TicIn.FullName[i] = tolower(TIC.TicIn.FullName[i]);
	}

	Syslog('+', "8.3 name \"%s\", LFN \"%s\"", TIC.TicIn.File, TIC.TicIn.FullName);
	if (strcmp(RealName, TIC.TicIn.File)) {
	    /*
	     * File in inbound has not the same name as the name on disk.
	     * It may be a LFN but also a case difference. The whole tic
	     * processing is based on 8.3 filenames.
	     */
	    snprintf(Temp, bufsize, "%s/%s", TIC.Inbound, RealName);
	    snprintf(Temp2, PATH_MAX, "%s/%s", TIC.Inbound, TIC.TicIn.File);
	    if (rename(Temp, Temp2))
		WriteError("$Can't rename %s to %s", Temp, Temp2);
	    else
		Syslog('f', "Renamed %s to %s", Temp, Temp2);
	}
    }
    strncpy(TIC.NewFile, TIC.TicIn.File, 80);
    strncpy(TIC.NewFullName, TIC.TicIn.FullName, 255);

    free(Temp2);
    free(Temp);
    free(Buf);

    tic_in++;
    rc = ProcessTic(&sbl, opl);
    tidy_falist(&sbl);

    return rc;
}



