/*****************************************************************************
 *
 * $Id: filelist.c,v 1.31 2005/10/11 20:49:46 mbse Exp $
 * Purpose ...............: fidonet mailer 
 *
 *****************************************************************************
 * Copyright (C) 1997-2005
 *   
 * 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/nodelist.h"
#include "../lib/users.h"
#include "../lib/mbsedb.h"
#include "config.h"
#include "session.h"
#include "filelist.h"


#ifndef PATH_MAX
#define PATH_MAX 512
#endif

extern int  master;
extern int  Loaded;
int	    made_request;



static char *tmpkname(void);
static char *tmpkname(void)
{
    static char buf[16];

    snprintf(buf,16,"%08x.pkt", sequencer());
    return buf;
}



char *xtodos(char *orig)
{
    char	buf[13], *copy, *p;

    if (orig == NULL) 
	return NULL;

    if ((remote_flags & SESSION_FNC) == 0) {
	Syslog('o', "No filename conversion for \"%s\"", MBSE_SS(orig));
	return xstrcpy(orig);
    }

    copy = xstrcpy(orig);
    if ((p = strrchr(copy,'/'))) 
	p++;
    else 
	p = copy;

    name_mangle(p);
    memset(&buf, 0, sizeof(buf));
    strncpy(buf, p, 12);
    Syslog('o', "name \"%s\" converted to \"%s\"", MBSE_SS(orig), MBSE_SS(buf));
    free(copy);
    return xstrcpy(buf);
}



/*
 * Add the specified entry to the list of files which should be sent
 * to the remote system.
 * 1. lst		file list to add entry to
 * 2. local		local filename
 * 3. remote		remote filename
 * 4. disposition	disposition
 * 5. floff		offset of entry in flo-file (-1 if this is a flo file)
 * 6. flofp		FILE * of flo file
 * 7. toend		append to end of list
 */
void add_list(file_list **lst, char *local, char *Remote, int disposition, off_t floff, FILE *flofp, int toend)
{
    file_list **tmpl, *tmp;

    Syslog('o', "add_list(\"%s\",\"%s\",%d,%s)", MBSE_SS(local),MBSE_SS(Remote),disposition,toend?"to end":"to beg");

    if (toend) 
	for (tmpl = lst; *tmpl; tmpl =&((*tmpl)->next));
    else 
	tmpl = &tmp;
    *tmpl = (file_list*)malloc(sizeof(file_list));
    if (toend) {
	(*tmpl)->next = NULL;
    } else {
	(*tmpl)->next = *lst;
	*lst = *tmpl;
    }

    (*tmpl)->remote      = xtodos(Remote);
    (*tmpl)->local       = xstrcpy(local);
    (*tmpl)->disposition = disposition;
    (*tmpl)->floff       = floff;
    (*tmpl)->flofp       = flofp;
    return;
}



static void check_flo(file_list **, char *);
static void check_flo(file_list **lst, char *nm)
{
    FILE	*fp;
    off_t	off;
    struct	flock fl;
    char	buf[PATH_MAX], buf2[PATH_MAX], *p, *q;
    int		disposition;
    struct stat stbuf;

    if ((fp = fopen(nm,"r+")) == NULL) {
	/*
	 * If no flo file, return.
	 */
	return;
    }

    Syslog('o', "check_flo(\"%s\")",MBSE_SS(nm));

    fl.l_type   = F_RDLCK;
    fl.l_whence = 0;
    fl.l_start  = 0L;
    fl.l_len    = 0L;

    if (fcntl(fileno(fp), F_SETLK, &fl) != 0) {
	if (errno != EAGAIN)
	    WriteError("$Can't read-lock \"%s\"", MBSE_SS(nm));
	else 
	    Syslog('o',"flo file busy");
	fclose(fp);
	return;
    }

    if (stat(nm, &stbuf) != 0) {
	WriteError("$Can't access \"%s\"", MBSE_SS(nm));
	fclose(fp);
	return;
    }

    while (!feof(fp) && !ferror(fp)) {
	off = ftell(fp);
	if (fgets(buf, sizeof(buf)-1, fp) == NULL) 
	    continue;
	if (buf[0] == '~') 
	    continue; /* skip sent files */
	if (*(p=buf + strlen(buf) -1) == '\n') 
	    *p-- = '\0';
	if (*p == '\r') 
	    *p = '\0';

	switch (buf[0]) {
	    case '#':	p=buf+1; disposition=TFS; break;
	    case '-':
	    case '^':	p=buf+1; disposition=KFS; break;
	    case '@':	p=buf+1; disposition=LEAVE; break;
	    case 0:	continue;
	    default:	p=buf; disposition=LEAVE; break;
	}

	memset(&buf2, 0, sizeof(buf2));
	if (strlen(CFG.dospath)) {
	    if (strncasecmp(p, CFG.dospath, strlen(CFG.dospath)) == 0) {
		strcpy(buf2,uxoutbound);
		for (p+=strlen(CFG.dospath), q=buf2+strlen(buf2); *p; p++, q++)
		    *q = ((*p) == '\\')?'/':tolower(*p);
		*q = '\0';
		p = buf2;
	    }
	}

	if ((q=strrchr(p,'/')))
	    q++;
	else
	    q = p;
	add_list(lst, p, q, disposition, off, fp, 1);
    }

    /*
     * Add flo file to file list
     */
    add_list(lst, nm, NULL, KFS, -1L, fp, 1);

    /* here, we leave .flo file open, it will be closed by */
    /* execute_disposition */

    return;
}



void check_filebox(char *, file_list **);
void check_filebox(char *boxpath, file_list **st)
{
    char	    *temp;
    DIR             *dp;
    struct dirent   *de;
    struct passwd   *pw;
    struct stat     stbuf;

    if ((dp = opendir(boxpath)) == NULL) {
	Syslog('o', "\"%s\" cannot be opened, proceed", MBSE_SS(boxpath));
    } else {
	Syslog('o', "checking filebox \"%s\"", boxpath);
        temp = calloc(PATH_MAX, sizeof(char));
	pw = getpwnam((char *)"mbse");
        while ((de = readdir(dp))) {
            if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
		snprintf(temp, PATH_MAX -1, "%s/%s", boxpath, de->d_name);
                if (stat(temp, &stbuf) == 0) {
                    Syslog('o' ,"checking file \"%s\"", de->d_name);
                    if (S_ISREG(stbuf.st_mode)) {
                        if (pw->pw_uid == stbuf.st_uid) {
                            /*
                             * We own the file
                             */
                            if ((stbuf.st_mode & S_IRUSR) && (stbuf.st_mode & S_IWUSR)) {
                                add_list(st, temp, de->d_name, KFS, 0L, NULL, 1);
                            } else {
                                Syslog('+', "No R/W permission on %s", temp);
                            }
                        } else if (pw->pw_gid == stbuf.st_gid) {
                            /*
                             * We own the file group
                             */
                            if ((stbuf.st_mode & S_IRGRP) && (stbuf.st_mode & S_IWGRP)) {
                                add_list(st, temp, de->d_name, KFS, 0L, NULL, 1);
                            } else {
                                Syslog('+', "No R/W permission on %s", temp);
                            }
                        } else {
                            /*
                             * No owner of file
                             */
                            if ((stbuf.st_mode & S_IROTH) && (stbuf.st_mode & S_IWOTH)) {
                                add_list(st, temp, de->d_name, KFS, 0L, NULL, 1);
                            } else {
                                Syslog('+', "No R/W permission on %s", temp);
                            }
                        }
                    } else {
                        Syslog('+', "Not a regular file %s", temp);
                    }
                } else {
                    WriteError("Can't stat %s", temp);
                }
            }
	}
	closedir(dp);
	free(temp);
    }
}



file_list *create_filelist(fa_list *al, char *fl, int create)
{
    file_list	    *st = NULL, *tmpf;
    fa_list	    *tmpa;
    char	    flavor, *tmpfl, *nm, *temp, tmpreq[13], digit[6], *temp2;
    struct stat	    stbuf;
    int		    packets = 0;
    FILE	    *fp;
    unsigned char   buffer[2];
    faddr	    *fa;
    DIR		    *dp = NULL;
    struct dirent   *de;
    struct stat     sb;

    Syslog('o', "Create_filelist(%s,\"%s\",%d)", al?ascfnode(al->addr,0x1f):"<none>", MBSE_SS(fl), create);
    made_request = 0;

    for (tmpa = al; tmpa; tmpa = tmpa->next) {
	Syslog('o', "Check address %s", ascfnode(tmpa->addr, 0x1f));

	/*
	 * For the main aka, check the outbox.
	 */
	if ((tmpa->addr) && Loaded && strlen(nodes.OutBox) &&
	    (tmpa->addr->zone == nodes.Aka[0].zone) && (tmpa->addr->net == nodes.Aka[0].net) &&
	    (tmpa->addr->node == nodes.Aka[0].node) && (tmpa->addr->point == nodes.Aka[0].point)) {
	    check_filebox(nodes.OutBox, &st);
	}

	/*
	 * Check spool files, these are more or less useless but they
	 * create a filelist of files to send which are never send.
	 * It gives the transfer protocols something "to do".
	 */
	if (strchr(fl, 'o')) {
	    nm = splname(tmpa->addr);
	    if ((fp = fopen(nm, "w"))) 
		fclose(fp);
	    if ((nm != NULL) && (stat(nm, &stbuf) == 0))
		add_list(&st, nm, NULL, DSF, 0L, NULL, 1);
	}

	/*
	 * Check .pol files
	 */
	nm = polname(tmpa->addr);
	if ((nm != NULL) && (stat(nm,&stbuf) == 0))
	    add_list(&st,nm,NULL,DSF,0L,NULL,1);

	/*
	 * Check other files, all flavors
	 */
	tmpfl = fl;
	while ((flavor = *tmpfl++)) {

	    Syslog('o', "Check flavor %c", flavor);
	    /*
	     * Check normal mail packets
	     */
	    nm = pktname(tmpa->addr,flavor);
	    if ((nm != NULL) && (stat(nm,&stbuf) == 0)) {
		Syslog('o', "found %s", nm);
		packets++;
		add_list(&st, nm, tmpkname(), KFS, 0L, NULL, 1);
	    }

	    /*
	     * Check .flo files for file attaches
	     */
	    nm = floname(tmpa->addr,flavor);
	    check_flo(&st, nm);
	}

	if ((session_flags & SESSION_WAZOO) &&
	    ((session_flags & SESSION_HYDRA) == 0) && (master || ((session_flags & SESSION_IFNA) == 0))) {
	    /*
	     * we don't distinguish flavoured reqs
	     */
	    nm = reqname(tmpa->addr);
	    if ((nm != NULL) && (stat(nm, &stbuf) == 0)) {
		snprintf(tmpreq, 13, "%04X%04X.REQ", tmpa->addr->net, tmpa->addr->node);
		add_list(&st, nm, tmpreq, DSF, 0L, NULL, 1);
		made_request = 1;
	    }
	}
    }

    /*
     * Check T-Mail style fileboxes
     */
    temp = calloc(PATH_MAX, sizeof(char));
    if (strlen(CFG.tmailshort) && (dp = opendir(CFG.tmailshort))) {
       Syslog('o', "Checking T-Mail short box \"%s\"", CFG.tmailshort);
       while ((de = readdir(dp))) {
           if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
               snprintf(temp, PATH_MAX -1, "%s/%s", CFG.tmailshort, de->d_name);
               if (stat(temp, &sb) == 0) {
                   Syslog('o' ,"checking \"%s\"", de->d_name);
                   if (S_ISDIR(sb.st_mode)) {
                       int i;
                       char b=0;
                       for (i=0; (i<8) && (!b); ++i) {
                           char c = tolower(de->d_name[i]);
                           if ( (c<'0') || (c>'v') || ((c>'9') && (c<'a')) ) 
			       b=1;
                       }
                       if (de->d_name[8]!='.') 
			   b=1;
                       for (i=9; (i<11) && (!b); ++i) {
                           char c = tolower(de->d_name[i]);
                           if ( (c<'0') || (c>'v') || ((c>'9') && (c<'a')) ) 
			       b=1;
                       }
                       if (b) 
			   continue;
                       if (de->d_name[11]==0) 
			   flavor='o';
                       else if ((tolower(de->d_name[11])=='h') && (de->d_name[12]==0)) 
			   flavor='h';
                       else 
			   continue;
                       fa = (faddr*)malloc(sizeof(faddr));
                       fa->name = NULL;
                       fa->domain = NULL;
                       memset(&digit, 0, sizeof(digit));
                       digit[0] = de->d_name[0];
                       digit[1] = de->d_name[1];
                       fa->zone = strtol(digit, NULL, 32);
                       memset(&digit, 0, sizeof(digit));
                       digit[0] = de->d_name[2];
                       digit[1] = de->d_name[3];
                       digit[2] = de->d_name[4];
                       fa->net = strtol(digit, NULL, 32);
                       memset(&digit, 0, sizeof(digit));
                       digit[0] = de->d_name[5];
                       digit[1] = de->d_name[6];
                       digit[2] = de->d_name[7];
                       fa->node = strtol(digit, NULL, 32);
                       memset(&digit, 0, sizeof(digit));
                       digit[0] = de->d_name[9];
                       digit[1] = de->d_name[10];
                       fa->point = strtol(digit, NULL, 32);
                       for (tmpa = al; tmpa; tmpa = tmpa->next) {
                           if ((fa->zone==tmpa->addr->zone) && (fa->net==tmpa->addr->net) && 
			       (fa->node==tmpa->addr->node) && (fa->point==tmpa->addr->point) && strchr(fl, flavor))
				if (SearchFidonet(tmpa->addr->zone))
				    check_filebox(temp, &st);
                       }
                       tidy_faddr(fa);
                   }
               }
           }
       }
       closedir(dp);
    }

    if (strlen(CFG.tmaillong) && (dp = opendir(CFG.tmaillong))) {
       temp2 = calloc(PATH_MAX, sizeof(char));
       Syslog('o', "Checking T-Mail long box \"%s\"", CFG.tmaillong);
       while ((de = readdir(dp))) {
           if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
               snprintf(temp, PATH_MAX -1, "%s/%s", CFG.tmaillong, de->d_name);
               if (stat(temp, &sb) == 0) {
                   Syslog('o' ,"checking \"%s\"", de->d_name);
                   if (S_ISDIR(sb.st_mode)) {
                       char c, d;
                       int n;
                       snprintf(temp2, PATH_MAX -1, "%s", de->d_name);
                       fa = (faddr*)malloc(sizeof(faddr));
                       fa->name = NULL;
                       fa->domain = NULL;
                       n = sscanf(temp2, "%u.%u.%u.%u.%c%c", &(fa->zone), &(fa->net), &(fa->node), &(fa->point), &c, &d);
                       if ((n==4) || ((n==5) && (tolower(c)=='h'))) {
                           if (n==4) 
			       flavor = 'o';
                           else 
			       flavor = 'h';
                           for (tmpa = al; tmpa; tmpa = tmpa->next) {
                               if ((fa->zone==tmpa->addr->zone) && (fa->net==tmpa->addr->net) && 
				    (fa->node==tmpa->addr->node) && (fa->point==tmpa->addr->point) && 
				    strchr(fl, flavor))
				  if (SearchFidonet(tmpa->addr->zone)) 
					check_filebox(temp, &st);
                           }
                       }
                       tidy_faddr(fa);
                   }   
               }
           }
       }
       closedir(dp);
       free(temp2);
    }
    free(temp);


    /*
     * For FTS-0001 we need to create at least one packet.
     */
    if (((st == NULL) && (create > 1)) || ((st != NULL) && (packets == 0) && (create > 0))) {
	Syslog('o', "Create packet for %s", ascfnode(al->addr,0x1f));
	if ((fp = openpkt(NULL, al->addr, 'o', TRUE))) {
	    memset(&buffer, 0, sizeof(buffer));
	    fwrite(buffer, 1, 2, fp);
	    fclose(fp);
	}
	add_list(&st, pktname(al->addr,'o'), tmpkname(), KFS, 0L, NULL, 0);
    }

    for (tmpf = st; tmpf; tmpf = tmpf->next)
	Syslog('o',"flist: \"%s\" -> \"%s\" dsp:%d flofp:%u floff:%u",
		MBSE_SS(tmpf->local), MBSE_SS(tmpf->remote), tmpf->disposition,
		(unsigned int)tmpf->flofp, (unsigned int)tmpf->floff);

    return st;
}



/*
 * Create file request list for the Hydra or Binkp protocol.
 */
file_list *create_freqlist(fa_list *al)
{
    file_list	*st = NULL, *tmpf;
    fa_list	*tmpa;
    char	*nm, tmpreq[13];
    struct stat	stbuf;

    Syslog('o', "create_freqlist(%s)", al?ascfnode(al->addr, 0x1f):"<none>");
    made_request = 0;

    for (tmpa = al; tmpa; tmpa = tmpa->next) {
	nm = reqname(tmpa->addr);
	if ((nm != NULL) && (stat(nm, &stbuf) == 0)) {
	    snprintf(tmpreq, 13, "%04X%04X.REQ", tmpa->addr->net, tmpa->addr->node);
	    add_list(&st, nm, tmpreq, DSF, 0L, NULL, 1);
	    made_request = 1;
	}
    }

    if (made_request) {
	for (tmpf = st; tmpf; tmpf = tmpf->next)
	    Syslog('o', "flist: \"%s\" -> \"%s\" dsp:%d flofp:%lu floff:%lu",
			MBSE_SS(tmpf->local), MBSE_SS(tmpf->remote), tmpf->disposition, tmpf->flofp, tmpf->floff);
    }

    return st;
}



void tidy_filelist(file_list *fl, int dsf)
{
    file_list *tmp;

    if (fl == NULL)
	return;

    for (tmp=fl;fl;fl=tmp) {
	tmp=fl->next;
	if (dsf && (fl->disposition == DSF)) {
	    Syslog('o',"Removing sent file \"%s\"",MBSE_SS(fl->local));
	    if (unlink(fl->local) != 0) {
		if (errno == ENOENT)
		    Syslog('o',"Cannot unlink nonexistent file \"%s\"", MBSE_SS(fl->local));
		else
		    WriteError("$Cannot unlink file \"%s\"", MBSE_SS(fl->local));
	    }
	}
	if (fl->local) 
	    free(fl->local);
	if (fl->remote) 
	    free(fl->remote);
	else if (fl->flofp) 
	    fclose(fl->flofp);
	free(fl);
    }
    return;
}



void execute_disposition(file_list *fl)
{
    FILE    *fp=NULL;
    char    *nm, tpl='~';

    Syslog('o', "execute_disposition(%s)", fl->local);
    nm = fl->local;
    if (fl->flofp) {
	/*
	 * Check for special case: flo-file 
	 */
	if (fl->floff == -1) {
	    /*
	     * We check if there are any files left for transmission
	     * in the flo-file to decide whether to remove or leave
	     * it on disk.
	     */
	    char    buf[PATH_MAX];
	    int	    files_remain = 0;

	    if (fseek(fl->flofp, 0L, 0) == 0) {
		while (!feof(fl->flofp) && !ferror(fl->flofp)) {
		    if (fgets(buf, sizeof(buf)-1, fl->flofp) == NULL)
			continue;

		    /*
		     * Count nr of files which haven't been sent yet
		     */
		    if (buf[0] != '~')
			files_remain++;
		}

	    } else {
		WriteError("$Error seeking in .flo to 0");
		files_remain = -1; /* Keep flo-file */
	    }

	    if (files_remain) {
		Syslog('o', "Leaving flo-file \"%s\", %d files remaining", MBSE_SS(nm), files_remain);
		fl->disposition = LEAVE;
	    } else {
		fl->disposition = KFS;
	    }
	} else {
	    /*
	     * Mark files as sent in flo-file
	     */
	    if (fseek(fl->flofp, fl->floff, 0) == 0) {
		if (fwrite(&tpl,1,1,fl->flofp) != 1) {
		    WriteError("$Error writing '~' to .flo at %u", (unsigned int)fl->floff);
		} else {
		    Syslog('o', "Marked file \"%s\" as sent", MBSE_SS(nm));
		}
		fflush(fl->flofp);
#ifdef HAVE_FDATASYNC
		fdatasync(fileno(fl->flofp));
#else
#ifdef HAVE_FSYNC		
		fsync(fileno(fl->flofp));
#endif
#endif
	    } else 
		WriteError("$error seeking in .flo to %u", (unsigned int)fl->floff);
	}
    }

    switch (fl->disposition) {
	case DSF:
	case LEAVE: break;

	case TFS:   Syslog('o', "Truncating sent file \"%s\"",MBSE_SS(nm));
		    if ((fp=fopen(nm,"w"))) 
			fclose(fp);
		    else 
			WriteError("$Cannot truncate file \"%s\"",MBSE_SS(nm));
		    break;

	case KFS:   Syslog('o', "Removing sent file \"%s\"",MBSE_SS(nm));
		    if (unlink(nm) != 0) {
			if (errno == ENOENT)
			    Syslog('o', "Cannot unlink nonexistent file \"%s\"", MBSE_SS(nm));
			else
			    WriteError("$Cannot unlink file \"%s\"", MBSE_SS(nm));
		    }
		    break;

	default:    WriteError("execute_disposition: unknown disp %d for \"%s\"", fl->disposition,MBSE_SS(nm));
		    break;
    }

    return;
}



char *transfertime(struct timeval start, struct timeval end, int bytes, int sent)
{
    static char	    resp[81];
    double long	    startms, endms, elapsed;

    startms = (start.tv_sec * 1000) + (start.tv_usec / 1000);
    endms = (end.tv_sec * 1000) + (end.tv_usec / 1000);
    elapsed = endms - startms;
    memset(&resp, 0, sizeof(resp));
    if (!elapsed)
	elapsed = 1L;
    if (bytes > 1000000)
	snprintf(resp, 81, "%d bytes %s in %0.3Lf seconds (%0.3Lf Kb/s)",
	    bytes, sent?"sent":"received", elapsed / 1000.000, ((bytes / elapsed) * 1000) / 1024);
    else
	snprintf(resp, 81, "%d bytes %s in %0.3Lf seconds (%0.3Lf Kb/s)", 
	    bytes, sent?"sent":"received", elapsed / 1000.000, ((bytes * 1000) / elapsed) / 1024);   
    return resp;
}



char *compress_stat(int original, int saved)
{
    static char	    resp[81];

    snprintf(resp, 81, "compressed %d bytes, compression %0.1f%%", saved, ((saved * 100.0) / original));
    return resp;
}


