/*****************************************************************************
 *
 * $Id$
 * Purpose ...............: mbtask - Scan mail outbound status
 *
 *****************************************************************************
 * Copyright (C) 1997-2001
 *   
 * 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, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************/

#include "libs.h"
#include "../lib/structs.h"
#include "taskutil.h"
#include "taskstat.h"
#include "scanout.h"
#include "nodelist.h"
#include "callstat.h"
#include "outstat.h"


extern int		do_quiet;
extern int		internet;	    /* Internet status		*/
extern struct sysconfig	CFG;		    /* Main configuration	*/
extern struct taskrec	TCFG;		    /* Task configuration	*/
extern char		ttyfn[];	    /* TTY file name		*/
extern time_t		tty_time;	    /* TTY update time		*/
int			nxt_hour, nxt_min;  /* Time of next event	*/
int			inet_calls;	    /* Internet calls to make	*/
int			isdn_calls;	    /* ISDN calls to make	*/
int			pots_calls;	    /* POTS calls to make	*/
pp_list			*pl = NULL;	    /* Portlist			*/
_alist_l		*alist = NULL;	    /* Nodes to call list	*/



void set_next(int, int);
void set_next(int hour, int min)
{
    time_t	now;
    struct tm	*etm;
    int		uhour, umin;

    now = time(NULL);
    etm = gmtime(&now);
    uhour = etm->tm_hour; /* For some reason, these intermediate integers are needed */
    umin  = etm->tm_min;

    if ((hour > uhour) || ((hour == uhour) && (min > umin))) {
	if (hour < nxt_hour) {
	    nxt_hour = hour;
	    nxt_min  = min;
	} else if ((hour == nxt_hour) && (min < nxt_min)) {
	    nxt_hour = hour;
	    nxt_min  = min;
	}
    }
}



char *callstatus(int status)
{
    switch (status) {
	case 0:	    return (char *)"Ok     ";
	case 1:	    return (char *)"tty err";
	case 2:	    return (char *)"No conn";
	case 3:	    return (char *)"Mdm err";
	case 4:	    return (char *)"Locked ";
	case 5:	    return (char *)"unknown";
	case 6:	    return (char *)"Unlist ";
	case 7:	    return (char *)"error 7";
	case 8:	    return (char *)"error 8";
	case 9:	    return (char *)"No tty ";
	case 10:    return (char *)"No ZMH ";
	case 30:    return (char *)"Badsess";
	default:    return (char *)"ERROR  ";
    }
}



char *callmode(int mode)
{
    switch (mode) {
	case CM_NONE:	return (char *)"None   ";
	case CM_INET:	return (char *)"Inet   ";
	case CM_ISDN:	return (char *)"ISDN   ";
	case CM_POTS:	return (char *)"POTS   ";
	case MBFIDO:	return (char *)"mbfido ";
	case MBINDEX:	return (char *)"mbindex";
	case MBFILE:	return (char *)"mbfile ";
	case MBINIT:	return (char *)"mbinit ";
	default:	return (char *)"None   ";
    }
}



int outstat()
{
	int		rc, first = TRUE, T_window, iszmh = FALSE;
	struct _alist	*tmp, *old;
	char		flstr[13];
	char		temp[81];
	char		as[6], be[6], utc[6];
	time_t		now;
	struct tm	*tm;
	int		uhour, umin, thour, tmin;
	pp_list		*tpl;

	now = time(NULL);
	tm = gmtime(&now); /* UTC time */
	uhour = tm->tm_hour;
	umin  = tm->tm_min;
	sprintf(utc, "%02d:%02d", uhour, umin);
	tasklog('+', "Scanning outbound at %s UTC.", utc);
	nxt_hour = 24;
	nxt_min  = 0;
	inet_calls = isdn_calls = pots_calls = 0;

	/*
	 *  Clear current table
	 */
        for (tmp = alist; tmp; tmp = old) {
                old = tmp->next;
                free(tmp);
        }
        alist = NULL;

	if ((rc = scanout(each))) {
		tasklog('?', "Error scanning outbound, aborting");
		return rc;
	}

	/*
	 * During processing the outbound list, determine when the next event will occur,
	 * ie. the time when the callout status of a node changes because of starting a
	 * ZMH, or changeing the time window for Txx flags.
	 */
	for (tmp = alist; tmp; tmp = tmp->next) {
		if (first) {
			tasklog('+', "Flavor Out        Size   Online    Modem     ISDN   TCP/IP Calls Status  Mode    Address");
			first = FALSE;
		}

		/*
		 * Zone Mail Hours, only use Fidonet Hours.
		 * Other nets use your default ZMH.
		 */
		T_window = iszmh = FALSE;
		switch (tmp->addr.zone) {
		    case 1:	if (uhour == 9)
				    iszmh = TRUE;
				set_next(9, 0);
				set_next(10, 0);
				break;
		    case 2:	if (((uhour == 2) && (umin >= 30)) || ((uhour == 3) && (umin < 30)))
				    iszmh = TRUE;
				set_next(2, 30);
				set_next(3, 30);
				break;
		    case 3:	if (uhour == 18)
				    iszmh = TRUE;
				set_next(18, 0);
				set_next(19, 0);
				break;
		    case 4:	if (uhour == 8)
				    iszmh = TRUE;
				set_next(8, 0);
				set_next(9, 0);
				break;
		    case 5:	if (uhour == 1)
				    iszmh = TRUE;
				set_next(1, 0);
				set_next(2, 0);
				break;
		    case 6:	if (uhour == 20)
				    iszmh = TRUE;
				set_next(20, 0);
				set_next(21, 0);
				break;
		    default:	if (get_zmh())
				    iszmh = TRUE;
				break;
		}

		if (tmp->t1 && tmp->t2) {
		    /*
		     * Txx flags, check callwindow
		     */
		    thour = toupper(tmp->t1) - 'A';
		    if (isupper(tmp->t1))
			tmin = 0;
		    else
			tmin = 30;
		    sprintf(as, "%02d:%02d", thour, tmin);
		    set_next(thour, tmin);
		    thour = toupper(tmp->t2) - 'A';
		    if (isupper(tmp->t2))
			tmin = 0;
		    else
			tmin = 30;
		    sprintf(be, "%02d:%02d", thour, tmin);
		    set_next(thour, tmin);
		    if (strcmp(as, be) > 0) {
			/*
			 * Time window is passing midnight
			 */
			if ((strcmp(utc, as) >= 0) || (strcmp(utc, be) < 0))
			    T_window = TRUE;
		    } else {
			/*
			 * Time window is not passing midnight
			 */
			if ((strcmp(utc, as) >= 0) && (strcmp(utc, be) < 0))
			    T_window = TRUE;
		    }
		}
		tasklog('o', "T_window=%s, iszmh=%s", T_window?"true":"false", iszmh?"true":"false");
		strcpy(flstr,"...... ... ..");
		/*
		 * If the node has internet and we have internet available, check if we can send
		 * immediatly.
		 */
		if (internet && TCFG.max_tcp && 
			(((tmp->flavors) & F_IMM) || ((tmp->flavors) & F_CRASH) || ((tmp->flavors) & F_NORMAL)) &&
			((tmp->ipflags & IP_IBN) || (tmp->ipflags & IP_IFC) || (tmp->ipflags & IP_ITN))) {
		    tmp->flavors |= F_CALL;
		}
		if ((tmp->flavors) & F_IMM   ) {
		    flstr[0]='I';
		    /*
		     * Immediate mail, send if node is CM or is in a Txx window or is in ZMH.
		     */
		    if ((tmp->olflags & OL_CM) || T_window || iszmh) {
			tmp->flavors |= F_CALL;
		    }
		}
		if ((tmp->flavors) & F_CRASH ) {
		    flstr[1]='C';
		    /*
		     * Crash mail, send if node is CM or is in a Txx window or is in ZMH.
		     */
		    if ((tmp->olflags & OL_CM) || T_window || iszmh) {
			tmp->flavors |= F_CALL;
		    }
		}
		if ((tmp->flavors) & F_NORMAL) {
		    flstr[2]='N';
		    /*
		     * Normal mail, send during ZMH or if node has a Txx window.
		     */
		    if (iszmh || T_window) {
			tmp->flavors |= F_CALL;
		    }
		}
		if ((tmp->flavors) & F_HOLD  ) 
		    flstr[3]='H';
		if ((tmp->flavors) & F_FREQ  ) 
		    flstr[4]='R';
		if ((tmp->flavors) & F_POLL  ) {
		    flstr[5]='P';
		    tmp->flavors |= F_CALL;
		}
		if ((tmp->flavors) & F_ISPKT ) 
		    flstr[7]='M';
		if ((tmp->flavors) & F_ISFLO ) 
		    flstr[8]='F';
		if (tmp->cst.tryno >= 30) {
		    /*
		     * Node is undialable, clear callflag
		     */
		    tmp->flavors &= ~F_CALL;
		}
		/*  Check retry timer also here */
		if ((tmp->flavors) & F_CALL  ) 
		    flstr[9]='C';
		if (tmp->t1) 
		    flstr[11] = tmp->t1;
		if (tmp->t2) 
		    flstr[12] = tmp->t2;

		/*
		 * If we must call this node, figure out how to call this node.
		 */
		if ((tmp->flavors) & F_CALL) {
		    /*
		     * Get options for this node
		     */


		    tmp->callmode = CM_NONE;
		    if (internet && TCFG.max_tcp && 
			    ((tmp->ipflags & IP_IBN) || (tmp->ipflags & IP_IFC) || (tmp->ipflags & IP_ITN))) {
			inet_calls++;
			tmp->callmode = CM_INET;
		    }
		    if (!TCFG.ipblocks || (TCFG.ipblocks && !internet)) {
			/*
			 * If TCP/IP blocks other trafic, (you only have one dialup line),
			 * then don't add normal dial trafic. If not blocking, add lines.
			 */
			if ((tmp->callmode == CM_NONE) && TCFG.max_isdn) {
			    /*
			     * Dialup node, check available dialout ports
			     */
			    for (tpl = pl; tpl; tpl = tpl->next) {
				if (tpl->dflags & tmp->diflags) {
				    isdn_calls++;
				    tmp->callmode = CM_ISDN;
				    break;
				}
			    }
			}
			if ((tmp->callmode == CM_NONE) && TCFG.max_pots) {
			    for (tpl = pl; tpl; tpl = tpl->next) {
				if (tpl->mflags & tmp->moflags) {
				    pots_calls++;
				    tmp->callmode = CM_POTS;
				    break;
				}
			    }
			}
		    }
		}
		sprintf(temp, "%s %8lu %08x %08x %08x %08x %5d %s %s %s", flstr, (long)tmp->size,
			(unsigned int)tmp->olflags, (unsigned int)tmp->moflags,
			(unsigned int)tmp->diflags, (unsigned int)tmp->ipflags,
			tmp->cst.tryno, callstatus(tmp->cst.trystat), callmode(tmp->callmode), ascfnode(tmp->addr, 0x0f));
		tasklog('+', "%s", temp);
	}
	
	if (nxt_hour == 24) {
	    /*
	     * 24:00 hours doesn't exist
	     */
	    nxt_hour = 0;
	    nxt_min  = 0;
	}

	tasklog('o', "Call inet=%d, isdn=%d, pots=%d", inet_calls, isdn_calls, pots_calls);
	tasklog('+', "Next event at %02d:%02d UTC", nxt_hour, nxt_min);
	return 0;
}



int each(faddr *addr, char flavor, int isflo, char *fname)
{
	struct		_alist **tmp;
	struct		stat st;
	FILE		*fp;
	char		buf[256], *p;
	node		*nlent;
	callstat	*cst;

	if ((isflo != OUT_PKT) && (isflo != OUT_FLO) && (isflo != OUT_REQ) && (isflo != OUT_POL))
		return 0;

	for (tmp = &alist; *tmp; tmp = &((*tmp)->next))
		if (((*tmp)->addr.zone  == addr->zone) && ((*tmp)->addr.net   == addr->net) &&
		    ((*tmp)->addr.node  == addr->node) && ((*tmp)->addr.point == addr->point) &&
		    (((*tmp)->addr.domain == NULL) || (addr->domain == NULL) ||
		     (strcasecmp((*tmp)->addr.domain,addr->domain) == 0)))
			break;
	if (*tmp == NULL) {
		nlent = getnlent(addr);
		*tmp = (struct _alist *)malloc(sizeof(struct _alist));
		(*tmp)->next = NULL;
		(*tmp)->addr.zone   = addr->zone;
		(*tmp)->addr.net    = addr->net;
		(*tmp)->addr.node   = addr->node;
		(*tmp)->addr.point  = addr->point;
		sprintf((*tmp)->addr.domain, "%s", addr->domain);
		if (nlent->addr.domain)
			free(nlent->addr.domain);
		(*tmp)->flavors = 0;
		if (nlent->pflag != NL_DUMMY) {
			(*tmp)->olflags = nlent->oflags;
			(*tmp)->moflags = nlent->mflags;
			(*tmp)->diflags = nlent->dflags;
			(*tmp)->ipflags = nlent->iflags;
			(*tmp)->t1 = nlent->t1;
			(*tmp)->t2 = nlent->t2;
		} else {
			(*tmp)->olflags = 0L;
			(*tmp)->moflags = 0L;
			(*tmp)->diflags = 0L;
			(*tmp)->ipflags = 0L;
			(*tmp)->t1 = '\0';
			(*tmp)->t2 = '\0';
		}
		(*tmp)->time = time(NULL);
		(*tmp)->size = 0L;
	}

	cst = getstatus(addr);
	(*tmp)->cst.trytime = cst->trytime;
	(*tmp)->cst.tryno   = cst->tryno;
	(*tmp)->cst.trystat = cst->trystat;

	if ((isflo == OUT_FLO) || (isflo == OUT_PKT)) 
		switch (flavor) {
			case '?':	break;
			case 'i':	(*tmp)->flavors |= F_IMM; break;
			case 'o':	(*tmp)->flavors |= F_NORMAL; break;
			case 'c':	(*tmp)->flavors |= F_CRASH; break;
			case 'h':	(*tmp)->flavors |= F_HOLD; break;
			default:	tasklog('?', "Unknown flavor: '%c'\n",flavor); break;
		}

	if (stat(fname,&st) != 0) {
		tasklog('?', "$Can't stat %s", fname);
		st.st_size  = 0L;
		st.st_mtime = time(NULL);
	}

	/*
	 * Find the oldest time
	 */
	if (st.st_mtime < (*tmp)->time) 
		(*tmp)->time = st.st_mtime;

	if (isflo == OUT_FLO) {
		(*tmp)->flavors |= F_ISFLO;
		if ((fp = fopen(fname,"r"))) {
			while (fgets(buf, sizeof(buf) - 1, fp)) {
				if (*(p = buf + strlen(buf) - 1) == '\n') 
					*p-- = '\0';
				while (isspace(*p)) 
					*p-- = '\0';
				for (p = buf; *p; p++) 
					if (*p == '\\') 
						*p='/';
				for (p = buf; *p && isspace(*p); p++);
				if (*p == '~') continue;
				if ((*p == '#') || (*p == '-') || (*p == '^') || (*p == '@')) 
					p++;
				if (stat(p, &st) != 0) {
					if (strlen(CFG.dospath)) {
						if (stat(Dos2Unix(p), &st) != 0) {
							/*
							 * Fileattach dissapeared, maybe
							 * the node doesn't poll enough and
							 * is losing mail or files.
							 */
							st.st_size  = 0L;
							st.st_mtime = time(NULL);
						}
					} else {
						if (stat(p, &st) != 0) {
							st.st_size  = 0L;
							st.st_mtime = time(NULL);
						}
					}
				}

				if ((p = strrchr(fname,'/'))) 
					p++;
				else 
					p = fname;
				if ((strlen(p) == 12) && (strspn(p,"0123456789abcdefABCDEF") == 8) && (p[8] == '.')) {
					if (st.st_mtime < (*tmp)->time) 
						(*tmp)->time = st.st_mtime;
				}
				(*tmp)->size += st.st_size;
			}
			fclose(fp);
		} else 
			tasklog('?', "Can't open %s", fname);

	} else if (isflo == OUT_PKT) {
		(*tmp)->size += st.st_size;
		(*tmp)->flavors |= F_ISPKT;
	} else if (isflo == OUT_REQ) {
		(*tmp)->flavors |= F_FREQ;
	} else if (isflo == OUT_POL) {
		(*tmp)->flavors |= F_POLL;
	}

	return 0;
}



/*
 * Tidy the portlist
 */
void tidy_portlist(pp_list **);
void tidy_portlist(pp_list ** fdp)
{
    pp_list *tmp, *old;

    tasklog('p', "tidy_portlist");
    for (tmp = *fdp; tmp; tmp = old) {
	old = tmp->next;
	free(tmp);
    }
    *fdp = NULL;
}



/*
 * Add a port to the portlist
 */
void fill_portlist(pp_list **, pp_list *);
void fill_portlist(pp_list **fdp, pp_list *new)
{
    pp_list *tmp, *ta;

    tmp = (pp_list *)malloc(sizeof(pp_list));
    tmp->next = NULL;
    strncpy(tmp->tty, new->tty, 6);
    tmp->mflags = new->mflags;
    tmp->dflags = new->dflags;

    if (*fdp == NULL) {
	*fdp = tmp;
    } else {
	for (ta = *fdp; ta; ta = ta->next)
	    if (ta->next == NULL) {
		ta->next = (pp_list *)tmp;
		break;
	    }
    }
}



/*
 * Build a list of available dialout ports.
 */
void load_ports()
{
    FILE    *fp;
    pp_list new;
    int	    count = 0, j, stdflag;
    char    *p, *q;

    tidy_portlist(&pl);
    if ((fp = fopen(ttyfn, "r")) == NULL) {
	tasklog('?', "$Can't open %s", ttyfn);
	return;
    }
    fread(&ttyinfohdr, sizeof(ttyinfohdr), 1, fp);
    
    tasklog('p', "Building portlist...");
    while (fread(&ttyinfo, ttyinfohdr.recsize, 1, fp) == 1) {
	if (((ttyinfo.type == POTS) || (ttyinfo.type == ISDN)) && (ttyinfo.available) && (ttyinfo.callout)) {
	    memset(&new, 0, sizeof(new));
	    strncpy(new.tty, ttyinfo.tty, 6);

	    stdflag = TRUE;
	    q = xstrcpy(ttyinfo.flags);
	    for (p = q; p; p = q) {
		if ((q = strchr(p, ',')))
		    *q++ = '\0';
		if ((strncasecmp(p, "U", 1) == 0) && (strlen(p) == 1)) {
		    stdflag = FALSE;
		} else {
		    for (j = 0; fkey[j].key; j++)
			if (strcasecmp(p, fkey[j].key) == 0)
			    new.mflags |= fkey[j].flag;
		    for (j = 0; dkey[j].key; j++)
			if (strcasecmp(p, dkey[j].key) == 0)
			    new.dflags |= dkey[j].flag;
		}
	    }
	    tasklog('p', "port %s modem %08lx ISDN %08lx", new.tty, new.mflags, new.dflags);
	    fill_portlist(&pl, &new);
	    count++;
	}
    }
    fclose(fp);
    tty_time = file_time(ttyfn);
    tasklog('p', "make_portlist %d ports", count);
}