/*:ts=8*/
/*****************************************************************************
 * FIDOGATE --- Gateway UNIX Mail/News <-> FIDO NetMail/EchoMail
 *
 * $Id: ftntoss.c,v 3.9.2.0 1995/06/12 17:14:03 mj Exp $
 *
 * Toss FTN NetMail/EchoMail
 *
 *****************************************************************************
 * Copyright (C) 1990-1995
 *  _____ _____
 * |     |___  |   Martin Junius             FIDO:      2:2452/110.1
 * | | | |   | |   Republikplatz 3           Internet:  mj@sungate.fido.de
 * |_|_|_|@home|   D-52072 Aachen, Germany   Phone:     ++49-241-86931 (voice)
 *
 * This file is part of FIDOGATE.
 *
 * FIDOGATE 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.
 *
 * FIDOGATE 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 FIDOGATE; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************/

#include "fidogate.h"
#include "getopt.h"

#include <fcntl.h>



#define PROGRAM "ftntoss"
#define VERSION "$Revision: 3.9.2.0 $"

#define MY_CONFIG "%L/config.toss"
#define ROUTING   "%L/routing"



/*
 * Prototypes
 */
void	addtoseenby_init	P((void));
void	zonegate_init		P((void));
void	lon_to_kludge		P((Textlist *, char *, LON *));
void	lon_to_kludge_sorted	P((Textlist *, char *, LON *));
int	toss_echomail		P((Message *, MsgBody *, LON *, LON *, LON *));
int	kludge_to_lon		P((Textlist *, LON *));
int	seenby_eq		P((Node *, Node *));
int	seenby_search		P((LON *, Node *));
int	is_local_addr		P((Node *, int));
int	do_seenby		P((LON *, LON *, LON *));
int	do_path			P((LON *));
int	do_echomail		P((Packet *, Message *, MsgBody *));
void	add_via			P((Textlist *, Node *));
void	change_addr		P((Node *, Node *));
void	do_rewrite		P((Message *));
void	do_remap		P((Message *));
int	check_empty		P((MsgBody *));
int	do_netmail		P((Packet *, Message *, MsgBody *));
int	unpack			P((FILE *, Packet *));
int	rename_bad		P((char *));
int	unpack_file		P((char *));

void	short_usage		P((void));
void	usage			P((void));



/*
 * Command line options
 */
int g_flag = 'n';			/* Processing grade */
int t_flag = FALSE;			/* Insecure tossing enabled */
int n_flag = FALSE;			/* Accept EchoMail messages not */
					/* addressed to our AKA         */
int j_flag = FALSE;			/* Toss EchoMail to local address */
					/* in AREAS.BBS                   */
int s_flag = FALSE;			/* Strip CRASH, HOLD attribute */
int maxmsg = 0;				/* Process maxmsg messages */
int x_flag = FALSE;			/* Exit after maxmsg messages */

static char in_dir[MAXPATH];		/* Input directory */


static int must_exit = FALSE;		/* Flag for -x operation */
static int msg_count = 0;		/* Counter for -m, -x operation */

static int severe_error = OK;		/* ERROR: exit after error */


/*
 * Config options
 */
int kill_empty   = FALSE;		/* config: KillEmpty */
int kill_unknown = FALSE;		/* config: KillUnknown */
int kill_routed  = FALSE;		/* config: KillRouted */
int log_netmail  = FALSE;		/* config: LogNetMail */
int check_path   = FALSE;		/* config: CheckPath */


/*
 * Global stat counts
 */
static long msgs_in       = 0;		/* Input messages */
static long msgs_netmail  = 0;		/* Output messages NetMail */
static long msgs_echomail = 0;		/* Output messages EchoMail */
static long msgs_routed   = 0;		/* Routed EchoMail messages */
static long msgs_insecure = 0;		/* Insecure EchoMail messages */
static long msgs_unknown  = 0;		/* Unknown EchoMail area messages */
static long msgs_empty    = 0;		/* Empty NetMail messages */
static long msgs_path     = 0;		/* Circular path */



/*
 * AddToSeenBy list
 */
typedef struct st_addtoseenby
{
    char *area;				/* Area pattern */
    LON add;				/* Nodes to add to SEEN-BY */
    struct st_addtoseenby *next;
}
AddToSeenBy;

static AddToSeenBy *addto_first = NULL;
static AddToSeenBy *addto_last  = NULL;



/*
 * Init AddToSeenBy list from config file
 */
void addtoseenby_init()
{
    char *s, *parea, *pnodes;
    AddToSeenBy *p;
    
    for(s = cf_get_string("AddToSeenBy",TRUE);
	s && *s;
	s = cf_get_string("AddToSeenBy",FALSE) )
    {
	debug(8, "config: addtoseenby %s", s);
	
	strncpy0(buffer, s, sizeof(buffer));
	parea  = xstrtok(buffer, " \t");
	pnodes = xstrtok(NULL  , "\n");

	if(!parea || !pnodes)
	    continue;
	    
	p = (AddToSeenBy *)xmalloc(sizeof(AddToSeenBy));
	p->next = NULL;
	p->area = strsave(parea);
	lon_init(&p->add);
	lon_add_string(&p->add, pnodes);

	if(addto_first)
	    addto_last->next = p;
	else
	    addto_first = p;
	addto_last = p;
    }
}

    

/*
 * Zonegate list
 */
typedef struct st_zonegate
{
    Node node;
    LON seenby;
    struct st_zonegate *next;
}
ZoneGate;

static ZoneGate *zonegate_first = NULL;
static ZoneGate *zonegate_last  = NULL;



/*
 * Init zonegate list
 */
void zonegate_init()
{
    char *s;
    ZoneGate *p;
    
    for(s = cf_get_string("ZoneGate",TRUE);
	s && *s;
	s = cf_get_string("ZoneGate",FALSE) )
    {
	debug(8, "config: zonegate %s", s);
	
	p = (ZoneGate *)xmalloc(sizeof(ZoneGate));
	p->next = NULL;
	lon_init(&p->seenby);
	lon_add_string(&p->seenby, s);
	if(p->seenby.first)
	{
	    p->node = p->seenby.first->node;
	    lon_remove(&p->seenby, &p->node);
	}
	else
	    node_invalid(&p->node);
	
	if(zonegate_first)
	    zonegate_last->next = p;
	else
	    zonegate_first = p;
	zonegate_last = p;
    }
}    


/*
 * Create new SEEN-BY or ^APATH line from LON
 */
#define MAX_LENGTH 76

void lon_to_kludge(tl, text, lon)
    Textlist *tl;
    char *text;
    LON *lon;
{
    LNode *p;
    Node old;
    char *s = NULL;
    
    strncpy0(buffer, text, sizeof(buffer));
    node_invalid(&old);
    old.zone = cf_zone();

    for(p=lon->first; p; p=p->next)
	if(p->node.point == 0)			/* No 4D Points !!! */
	{
	    p->node.zone = cf_zone();		/* No zone !!! */
	    s = node_to_asc_diff(&p->node, &old);
	    old = p->node;
	    
	    if(strlen(buffer)+strlen(s)+1 > MAX_LENGTH)
	    {
		strncat0(buffer, "\r\n", sizeof(buffer));
		tl_append(tl, buffer);
		
		node_invalid(&old);
		old.zone = cf_zone();
		
		s = node_to_asc_diff(&p->node, &old);
		old = p->node;
		strncpy0(buffer, text, sizeof(buffer));
		strncat0(buffer, " ", sizeof(buffer));
		strncat0(buffer, s  , sizeof(buffer));
	    }
	    else
	    {
		strncat0(buffer, " ", sizeof(buffer));
		strncat0(buffer, s  , sizeof(buffer));
	    }
	}
    
    strncat0(buffer, "\r\n", sizeof(buffer));
    tl_append(tl, buffer);
}


void lon_to_kludge_sorted(tl, text, lon)
    Textlist *tl;
    char *text;
    LON *lon;
{
    Node old;
    char *s = NULL;
    int i;

    lon_sort(lon, 0);
    
    strncpy0(buffer, text, sizeof(buffer));
    node_invalid(&old);
    old.zone = cf_zone();

    for(i=0; i<lon->size; i++)
	if(lon->sorted[i]->point == 0)		/* No 4D Points !!! */
	{
	    lon->sorted[i]->zone = cf_zone();	/* No zone !!! */
	    s = node_to_asc_diff(lon->sorted[i], &old);
	    old = *lon->sorted[i];
	    
	    if(strlen(buffer)+strlen(s)+1 > MAX_LENGTH)
	    {
		strncat0(buffer, "\r\n", sizeof(buffer));
		tl_append(tl, buffer);
		
		node_invalid(&old);
		old.zone = cf_zone();
		
		s = node_to_asc_diff(lon->sorted[i], &old);
		old = *lon->sorted[i];
		strncpy0(buffer, text, sizeof(buffer));
		strncat0(buffer, " ", sizeof(buffer));
		strncat0(buffer, s  , sizeof(buffer));
	    }
	    else
	    {
		strncat0(buffer, " ", sizeof(buffer));
		strncat0(buffer, s  , sizeof(buffer));
	    }
	}
    
    strncat0(buffer, "\r\n", sizeof(buffer));
    tl_append(tl, buffer);
}

    

/*
 * Toss EchoMail, writing message to packet for each downlink
 */
int toss_echomail(msg, body, seenby, path, nodes)
    Message *msg;
    MsgBody *body;
    LON *seenby;
    LON *path;
    LON *nodes;
{
    LNode *p;
    FILE *fp;
    Textlist save;
    int is_saved;
    
    for(p=nodes->first; p; p=p->next)
    {
	is_saved = FALSE;

	debug(7, "toss_echomail(): message for %s",
	      node_to_asc(&p->node, TRUE));
	
	/* Check for msg addressed to zonegate */
	if(zonegate_first)
	{
	    ZoneGate *pz;
	    
	    for(pz=zonegate_first; pz; pz=pz->next)
		if(node_eq(&p->node, &pz->node))
		{
		    debug(7, "toss_echomail(): message is for zonegate, "
			     "stripping SEEN-BYs");
		    
		    save = body->seenby;
		    tl_init(&body->seenby);
		    is_saved = TRUE;
		    lon_to_kludge_sorted(&body->seenby,
					 "SEEN-BY:", &pz->seenby);
		    break;
		}
	}

	/* Rewrite message header */
	msg->node_from = cf_n_addr();
	msg->node_to   = p->node;
	/* Open packet file, EchoMail, Normal */
	fp = outpkt_open(&p->node, g_flag, 'e', 'n');
	if(fp == NULL)
	    return severe_error=ERROR;
	/* Write message header and body */
	if( pkt_put_msg_hdr(fp, msg, FALSE) != OK )
	    return severe_error=ERROR;
	if( msg_put_msgbody(fp, body, TRUE) != OK )
	    return severe_error=ERROR;

	if(is_saved)
	{
	    tl_clear(&body->seenby);
	    body->seenby = save;
	}
	
	msgs_echomail++;
    }
    
    return OK;
}



/*
 * Convert SEEN-BY or ^APATH lines to LON
 */
int kludge_to_lon(tl, lon)
    Textlist *tl;
    LON *lon;
{
    Node old, node;
    Textline *l;
    char *p;

    for(l=tl->first; l; l=l->next)
    {
	p = l->line;
	/* Skip SEEN-BY:, ^APATH: or whatever, copy to buffer[] */
	while(*p && !is_space(*p) && *p!=':')
	    p++;
	if(*p == ':')
	    p++;
	while(*p && is_space(*p))
	    p++;
	strncpy0(buffer, p, sizeof(buffer));
	strip_crlf(buffer);
	/* Tokenize and convert to node addresses */
	node_invalid(&old);
	old.zone = cf_zone();
	for(p=strtok(buffer, " \t"); p; p=strtok(NULL, " \t"))
	    if(asc_to_node_diff(p, &node, &old) == OK)
	    {
		lon_add(lon, &node);
		old = node;
	    }
    }
	
    return OK;
}



/*
 * seenby_eq() --- compare node adresses, ignoring zone
 */
int seenby_eq(a, b)
    Node *a, *b;
{
    return
	a->net   ==b->net  &&
	a->node == b->node && a->point ==b->point   ;
}



/*
 * Search node in SEEN-BY list, ignoring zone
 */
int seenby_search(lon, node)
    LON *lon;
    Node *node;
{
    LNode *p;
    
    for(p=lon->first; p; p=p->next)
	if(seenby_eq(&p->node, node))
	    return TRUE;
    
    return FALSE;
}



/*
 * Check for local address
 */
int is_local_addr(node, only3d)
    Node *node;
    int only3d;
{
    Node *n;
    int found = FALSE;
    
    for(n=cf_addr_trav(TRUE); n; n=cf_addr_trav(FALSE))
	if(only3d ? seenby_eq(node, n) : node_eq(node, n))
	{
	    found = TRUE;
	    break;
	}
    
    return found;
}



/*
 * Add nodes to SEEN-BY
 */
int do_seenby(seenby, nodes, new)
    LON *seenby;				/* Nodes in SEEN-BY lines */
    LON *nodes;					/* Nodes in AREAS.BBS */
    LON *new;					/* New nodes added */
{
    LNode *p;
    
    for(p=nodes->first; p; p=p->next)
	if(! seenby_search(seenby, &p->node) )
	{
	    lon_add(seenby, &p->node);
	    if(new)
		lon_add(new, &p->node);
	}
	else if(j_flag && is_local_addr(&p->node, FALSE))
	{
	    /*
	     * Special if -j is set: toss message for local address
	     * in AREAS.BBS which is already in SEEN-BY lines.
	     */
	    if(new)
		lon_add(new, &p->node);
	}
	    
    return OK;
}



/*
 * Add current address to ^APATH
 */
int do_path(path)
    LON *path;
{
    if(path->last && seenby_eq(&path->last->node, cf_addr()))
	/* Already there */
	return OK;
    
    lon_add(path, cf_addr());
    return OK;
}



/*
 * Check ^APATH for circular path
 */
int do_check_path(path)
    LON *path;
{
    LNode *p;
    
    for(p=path->first; p; p=p->next)
	if(p->next)		/* Don't check last node in list */
	    if(is_local_addr(&p->node, TRUE))
		return ERROR;
    
    return OK;
}




/*
 * Process EchoMail message
 */
int do_echomail(pkt, msg, body)
    Packet *pkt;
    Message *msg;
    MsgBody *body;
{
    AreasBBS *area;
    AddToSeenBy *addto;
    LON seenby, path, new;
    int ret;

    /*
     * Lookup area and set zone
     */
    strncpy0(buffer, body->area + strlen("AREA:"), sizeof(buffer));
    strip_crlf(buffer);
    debug(5, "EchoMail AREA: %s", buffer);
    
    if( (area = areasbbs_lookup(buffer)) == NULL )
    {
	/* Unknown area: log it, dump it. ;-) */
	log("unknown area %s from %s", buffer,
	    node_to_asc(&msg->node_from, TRUE) );
	msgs_unknown++;
	return OK;
    }
    cf_set_zone(area->zone);

    /*
     * Lookup area in AddToSeenBy list
     */
    for(addto=addto_first; addto; addto=addto->next)
	if(addto->area[0]==buffer[0] && wildmat(buffer, addto->area))
	    break;
    
    /*
     * Check that this message is addressed to one of our AKAs
     */
    if(!n_flag)
    {
	if(! is_local_addr(&msg->node_to, FALSE) )
	{
	    /* Routed EchoMail */
	    log("routed echomail area %s from %s to %s", buffer,
		node_to_asc(&msg->node_from, TRUE),
		node_to_asc(&msg->node_to, TRUE)                 );
	    msgs_routed++;
	    return OK;
	}
    }
    
    /*
     * Check that origin is listed in AREAS.BBS
     */
    if(!t_flag)
    {
	if(! lon_search(&area->nodes, &msg->node_from) )
	{
	    /* Insecure EchoMail */
	    log("insecure echomail area %s from %s", buffer,
		node_to_asc(&msg->node_from, TRUE)          );
	    msgs_insecure++;
	    return OK;
	}
    }
    
    /*
     * Convert SEEN-BY and ^APATH to LON
     */
    lon_init(&seenby);
    lon_init(&path);
    lon_init(&new);
    kludge_to_lon(&body->seenby, &seenby);
    kludge_to_lon(&body->path  , &path);
    tl_clear(&body->seenby);
    tl_clear(&body->path);
    /*
     * If from 4D point, add address to SEEN-BY to prevent sending
     * message back to this point. Won't show up in output SEEN-BY
     * because all point address are stripped then.
     */
    if(msg->node_from.point)
	lon_add(&seenby, &msg->node_from);

    lon_debug(9, "SEEN-BY: ", &seenby, TRUE);
    lon_debug(9, "Path   : ", &path,   TRUE);
    
    /*
     * Add nodes not already in SEEN-BY to seenby and new.
     */
    do_seenby(&seenby, &area->nodes, &new);
    /*
     * Add extra nodes to SEEN-BY
     */
    if(addto)
	do_seenby(&seenby, &addto->add, NULL);

    /*
     * Add our address to end of ^APATH, if not already there.
     */
    do_path(&path);

    lon_debug(9, "SEEN-BY: ", &seenby, TRUE);
    lon_debug(9, "Path   : ", &path,   TRUE);
    lon_debug(9, "New    : ", &new,    TRUE);

    /*
     * Check for circular ^APATH
     */
    if(check_path && do_check_path(&path)==ERROR)
    {
	    /* Routed EchoMail */
	    log("circular path echomail area %s from %s to %s",
		area->area, node_to_asc(&msg->node_from, TRUE),
		node_to_asc(&msg->node_to, TRUE)                 );
	    msgs_path++;

	    lon_delete(&seenby);
	    lon_delete(&path);
	    lon_delete(&new);

	    return OK;
    }
    
    /*
     * Add new SEEN-BY and ^APATH lines
     */
    lon_to_kludge_sorted(&body->seenby, "SEEN-BY:", &seenby);
    lon_to_kludge       (&body->path  , "\001PATH:", &path );

    /*
     * Send message to all downlinks in new
     */
    ret = toss_echomail(msg, body, &seenby, &path, &new);

    lon_delete(&seenby);
    lon_delete(&path);
    lon_delete(&new);

    return ret;
}



/*
 * Add our ^AVia line
 */
void add_via(list, gate)
    Textlist *list;
    Node *gate;
{
    tl_appendf(list, "\001Via FIDOGATE/%s %s, %s\r\n",
		     PROGRAM, node_to_asc(gate, FALSE),
		     date("%a %b %d %Y at %H:%M:%S %Z", NULL)  );
}



/*
 * Change address according to rewrite pattern
 */
void change_addr(node, newpat)
    Node *node;
    Node *newpat;
{
    if(newpat->zone  != -1)
	node->zone  = newpat->zone;
    if(newpat->net   != -1)
	node->net   = newpat->net;
    if(newpat->node  != -1)
	node->node  = newpat->node;
    if(newpat->point != -1)
	node->point = newpat->point;
}



/*
 * Perform REWRITE commands
 */
void do_rewrite(msg)
    Message *msg;
{
    Rewrite *r;
    Node node;
    
    /* From */
    for(r=rewrite_first; r; r=r->next)
	if(node_match(&msg->node_from, &r->from))
	{
	    node = msg->node_from;
	    change_addr(&msg->node_from, &r->to);
	    log("rewrite: %s -> %s",
		node_to_asc(&node, TRUE), node_to_asc(&msg->node_from, TRUE) );
	    break;
	}

    /* To */
    for(r=rewrite_first; r; r=r->next)
	if(node_match(&msg->node_to, &r->from))
	{
	    node = msg->node_to;
	    change_addr(&msg->node_to, &r->to);
	    log("rewrite: %s -> %s",
		node_to_asc(&node, TRUE), node_to_asc(&msg->node_to, TRUE) );
	    break;
	}
}



/*
 * Perform REMAP commands
 */
void do_remap(msg)
    Message *msg;
{
    Remap *r;
    Node node;
    
    for(r=remap_first; r; r=r->next)
	if(node_match(&msg->node_to, &r->from) &&
	   wildmatch(msg->name_to, r->name, TRUE) )
	{
	    node = msg->node_to;
	    change_addr(&msg->node_to, &r->to);
	    log("remap: %s @ %s -> %s", msg->name_to,
		node_to_asc(&node, TRUE), node_to_asc(&msg->node_to, TRUE) );
	    break;
	}
}



/*
 * Check for empty NetMail
 *
 * An empty NetMail is a message comprising only ^A kludges and empty lines.
 */
int check_empty(body)
    MsgBody *body;
{
    Textline *pl;
    
    if(body->rfc.n)
	return FALSE;
    if(body->tear || body->origin)
	return FALSE;
    if(body->seenby.n)
	return FALSE;

    for(pl=body->body.first; pl; pl=pl->next)
	if(pl->line[0] && pl->line[0]!='\r')
	    return FALSE;
    
    return TRUE;
}



/*
 * Process NetMail message
 */
int do_netmail(pkt, msg, body)
    Packet *pkt;
    Message *msg;
    MsgBody *body;
{
    FILE *fp;
    int flav;


    if(log_netmail)
	log("netmail: %s @ %s -> %s @ %s",
	    msg->name_from, node_to_asc(&msg->node_from, TRUE),
	    msg->name_to  , node_to_asc(&msg->node_to  , TRUE) );

    /*
     * Check for file attach
     */
    if(msg->attr & MSG_FILE)
    {
	int i, len;
	
	len = strlen(msg->subject);
	for(i=0; i<len; i++)
	    buffer[i] = tolower(msg->subject[i]);
	buffer[i] = 0;

	strncpy0(msg->subject, in_dir, sizeof(msg->subject));
	strncat0(msg->subject, "/"   , sizeof(msg->subject));
	strncat0(msg->subject, buffer, sizeof(msg->subject));

	log("file attach %s", msg->subject);
    }
    
    /*
     * Check for empty NetMail message addressed to one of our AKAs
     */
    if(kill_empty && check_empty(body))
    {
	if( is_local_addr(&msg->node_to, FALSE) )
	{
	    log("killing empty msg from %s @ %s",
		msg->name_from, node_to_asc(&msg->node_from, TRUE));
	    msgs_empty++;
	    
	    return OK;
	}
    }
    
    /*
     * Rewrite from/to addresses according to ROUTING rules
     */
    do_rewrite(msg);
    
    /*
     * Remap to address according to ROUTING rules
     */
    do_remap(msg);
    
    /*
     * Write to output packet
     */
    cf_set_zone(msg->node_to.zone);

    /* Get outbound flavor from msg attributes */
    flav = 'n';
    if(!s_flag)
    {
	if(msg->attr & MSG_HOLD)
	    flav = 'h';
	if(msg->attr & MSG_CRASH)
	    flav = 'c';
    }

    /* Open output packet */
    fp = outpkt_open(&msg->node_to, g_flag, 'n', flav);
    if(fp == NULL)
	return severe_error=ERROR;

    /* Add ftntoss ^AVia line */
    add_via(&body->via, cf_addr());

    /* Write message header and body */
    if( pkt_put_msg_hdr(fp, msg, TRUE) != OK )
	return severe_error=ERROR;
    if( msg_put_msgbody(fp, body, TRUE) != OK )
	return severe_error=ERROR;

    msgs_netmail++;
    
    return OK;
}



/*
 * Read and process FTN packets
 */
int unpack(pkt_file, pkt)
    FILE *pkt_file;
    Packet *pkt;
{
    Message msg;			/* Message header */
    Textlist tl;			/* Textlist for message body */
    MsgBody body;			/* Message body of FTN message */
    int type;
    
    /*
     * Initialize
     */
    tl_init(&tl);
    msg_body_init(&body);


    /*
     * Read packet
     */
    type = pkt_get_int16(pkt_file);
    if(type == ERROR)
    {
	if(feof(pkt_file))
	{
	    log("WARNING: premature EOF reading input packet");
	    return OK;
	}
	
	log("ERROR: reading input packet");
	return ERROR;
    }

    while(type == MSG_TYPE)
    {
	/*
	 * Read message header
	 */
	msg.node_from = pkt->from;
	msg.node_to   = pkt->to;
	if(pkt_get_msg_hdr(pkt_file, &msg) == ERROR)
	{
	    log("ERROR: reading input packet");
	    return ERROR;
	}
	
	/*
	 * Read message body
	 */
	type = pkt_get_body(pkt_file, &tl);
	if(type == ERROR)
	{
	    if(feof(pkt_file))
	    {
		log("WARNING: premature EOF reading input packet");
	    }
	    else
	    {
		log("ERROR: reading input packet");
		return ERROR;
	    }
	}
	msgs_in++;
	msg_count++;
	
	/*
	 * Parse message body
	 */
	if( msg_body_parse(&tl, &body) == -2 )
	    log("ERROR: parsing message body");
	/* Retrieve address information from kludges for NetMail */
	if(body.area == NULL)
	{
	    /* Retrieve complete address from kludges */
	    kludge_pt_intl(&body, &msg);
	    msg.node_orig = msg.node_from;

	    debug(5, "NetMail: %s -> %s",
		  node_to_asc(&msg.node_from, TRUE),
		  node_to_asc(&msg.node_to  , TRUE) );
	    if(do_netmail(pkt, &msg, &body) == ERROR)
		return ERROR;
	}
	else 
	{
	    msg.node_orig = msg.node_from;

	    debug(5, "EchoMail: %s -> %s",
		  node_to_asc(&msg.node_from, TRUE),
		  node_to_asc(&msg.node_to  , TRUE) );
	    if(do_echomail(pkt, &msg, &body) == ERROR)
		return ERROR;
	}

	/*
	 * Check for number of messages exceeding maxmsg
	 */
	if(maxmsg  &&  msg_count >= maxmsg)
	{
	    if(x_flag)
		must_exit = TRUE;
	    else
		outpkt_close();
	    msg_count = 0;
	}

    } /**while(type == MSG_TYPE)**/

    return OK;
}



/*
 * Rename .pkt -> .bad
 */
int rename_bad(name)
    char *name;
{
    char bad[MAXPATH];
    int len;
    
    strncpy0(bad, name, sizeof(bad));
    len = strlen(bad) - 4;
    if(len < 0)
	len = 0;
    strcpy(bad + len, ".bad");
    
    log("ERROR: bad packet renamed %s", bad);
    if(rename(name, bad) == ERROR)
    {
	log("$ERROR: can't rename %s -> %s", name, bad);
	return ERROR;
    }
    
    return OK;
}



/*
 * Unpack one packet file
 */
int unpack_file(pkt_name)
    char *pkt_name;
{
    Packet pkt;
    FILE *pkt_file;

    /*
     * Open packet and read header
     */
    pkt_file = fopen(pkt_name, R_MODE);
    if(!pkt_file) {
	log("$ERROR: can't open packet %s", pkt_name);
	rename_bad(pkt_name);
	return OK;
    }
    if(pkt_get_hdr(pkt_file, &pkt) == ERROR)
    {
	log("ERROR: reading header from %s", pkt_name);
	fclose(pkt_file);
	rename_bad(pkt_name);
	return OK;
    }
    
    /*
     * Unpack it
     */
    log("packet %s (%ldb) from %s for %s", pkt_name, check_size(pkt_name),
	node_to_asc(&pkt.from, TRUE), node_to_asc(&pkt.to, TRUE) );
    
    if(unpack(pkt_file, &pkt) == ERROR) 
    {
	log("ERROR: processing %s", pkt_name);
	fclose(pkt_file);
	rename_bad(pkt_name);
	return severe_error;
    }
    
    fclose(pkt_file);

    if (unlink(pkt_name)) {
	log("$ERROR: can't unlink %s", pkt_name);
	rename_bad(pkt_name);
	return ERROR;
    }

    return OK;
}



/*
 * Usage messages
 */
void short_usage()
{
    fprintf(stderr, "usage: %s [-options] [packet ...]\n", PROGRAM);
    fprintf(stderr, "       %s --help  for more information\n", PROGRAM);
}


void usage()
{
    fprintf(stderr, "FIDOGATE %s  %s %s\n\n",
	    version_global(), PROGRAM, version_local(VERSION) );
    
    fprintf(stderr, "usage:   %s [-options] [packet ...]\n\n", PROGRAM);
    fprintf(stderr, "\
options: -g --grade G                 processing grade\n\
         -I --in-dir name             set input packet directory\n\
         -O --out-dir name            set output packet directory\n\
         -l --lock-file               create lock file while processing\n\
         -t --insecure                insecure tossing (no AREAS.BBS check)\n\
         -n --toss-all                toss all EchoMail messages\n\
         -j --toss-local              toss for local address (in AREAS.BBS)\n\
         -r --routing-file NAME       read routing file\n\
         -s --strip-attribute         strip crash, hold message attribute\n\
         -m --maxmsg N                close output after N msgs\n\
         -x --maxmsg-exit N           close output and exit after N msgs\n\
         -M --maxopen N               set max # of open packet files\n\
\n\
	 -v --verbose                 more verbose\n\
	 -h --help                    this help\n\
         -c --config name             read config file (\"\" = none)\n\
	 -L --lib-dir name            set lib directory\n\
	 -S --spool-dir name          set spool directory\n\
	 -a --addr Z:N/F.P            set FTN address\n\
	 -u --uplink-addr Z:N/F.P     set FTN uplink address\n");
    
    exit(0);
}



/***** main() ****************************************************************/

int main(argc, argv)
    int argc;
    char *argv[];
{
    int c, ret;
    int l_flag = FALSE;
    char *I_flag=NULL, *O_flag=NULL, *r_flag=NULL;
    char *c_flag=NULL;
    char *S_flag=NULL, *L_flag=NULL;
    char *a_flag=NULL, *u_flag=NULL;
    char *pkt_name;

    int option_index;
    static struct option long_options[] =
    {
	{ "grade",        1, 0, 'g'},	/* grade */
	{ "in-dir",       1, 0, 'I'},	/* Set inbound packets directory */
	{ "lock-file",    0, 0, 'l'},	/* Create lock file while processing */
	{ "out-dir",      1, 0, 'O'},	/* Set packet directory */
	{ "insecure",     0, 0, 't'},	/* Insecure */
	{ "toss-all",     0, 0, 'n'},	/* Toss all EchoMail */
	{ "toss-local",   0, 0, 'j'},	/* Toss for local address */
	{ "routing-file", 1, 0, 'r'},	/* Set routing file */
	{ "strip-attribute",0,0,'s'},	/* Strip attribute */
	{ "maxmsg",       1, 0, 'm'},	/* Close after N messages */
	{ "maxmsg-exit",  1, 0, 'x'},	/* Exit after N messages */
	{ "maxopen",      1, 0, 'M'},	/* Set max # open packet files */

	{ "verbose",      0, 0, 'v'},	/* More verbose */
	{ "help",         0, 0, 'h'},	/* Help */
	{ "config",       1, 0, 'c'},	/* Config file */
	{ "spool-dir",    1, 0, 'S'},	/* Set FIDOGATE spool directory */
	{ "lib-dir",      1, 0, 'L'},	/* Set FIDOGATE lib directory */
	{ "addr",         1, 0, 'a'},	/* Set FIDO address */
	{ "uplink-addr",  1, 0, 'u'},	/* Set FIDO uplink address */
	{ 0,              0, 0, 0  }
    };

    log_program(PROGRAM);
    
    /* Init configuration */
    cf_initialize();


    while ((c = getopt_long(argc, argv, "g:O:I:ltnjr:sm:x:M:vhc:S:L:a:u:",
			    long_options, &option_index     )) != EOF)
	switch (c) {
	/***** ftntoss options *****/
	case 'g':
	    g_flag = *optarg;
	    break;
	case 'I':
	    I_flag = optarg;
	    break;
        case 'l':
	    l_flag = TRUE;
            break;
	case 'O':
	    O_flag = optarg;
	    break;
	case 't':
	    t_flag = TRUE;
	    break;
	case 'n':
	    n_flag = TRUE;
	    break;
	case 'j':
	    j_flag = TRUE;
	    break;
	case 'r':
	    r_flag = optarg;
	    break;
	case 's':
	    s_flag = TRUE;
	    break;
	case 'm':
	    maxmsg = atoi(optarg);
	    break;
	case 'x':
	    maxmsg = atoi(optarg);
	    x_flag = TRUE;
	    break;
	case 'M':
	    outpkt_set_maxopen(atoi(optarg));
	    break;
	    
	/***** Common options *****/
	case 'v':
	    verbose++;
	    break;
	case 'h':
	    usage();
	    exit(0);
	    break;
	case 'c':
	    c_flag = optarg;
	    break;
	case 'S':
	    S_flag = optarg;
	    break;
	case 'L':
	    L_flag = optarg;
	    break;
	case 'a':
	    a_flag = optarg;
	    break;
	case 'u':
	    u_flag = optarg;
	    break;
	default:
	    short_usage();
	    exit(EX_USAGE);
	    break;
	}

    /*
     * Read config file
     */
    if(L_flag)				/* Must set libdir beforehand */
	cf_set_libdir(L_flag);
    cf_read_config_file(c_flag ? c_flag : MY_CONFIG);

    /*
     * Process config options
     */
    if(L_flag)
	cf_set_libdir(L_flag);
    if(S_flag)
	cf_set_spooldir(S_flag);
    if(a_flag)
	cf_set_addr(a_flag);
    if(u_flag)
	cf_set_uplink(u_flag);

    cf_debug();
    
    /*
     * Process optional config statements
     */
    if(cf_get_string("KillEmpty", TRUE) || cf_get_string("KillBlank", TRUE))
    {
	debug(8, "config: killempty");
	kill_empty = TRUE;
    }
    if(cf_get_string("KillUnknown", TRUE))
    {
	debug(8, "config: killunknown");
	kill_unknown = TRUE;
    }
    if(cf_get_string("KillRouted", TRUE))
    {
	debug(8, "config: killrouted");
	kill_routed = TRUE;
    }
    if(cf_get_string("LogNetMail", TRUE) || cf_get_string("Track", TRUE))
    {
	debug(8, "config: lognetmail");
	log_netmail = TRUE;
    }
    if(cf_get_string("CheckPath", TRUE))
    {
	debug(8, "config: checkpath");
	check_path = TRUE;
    }
    zonegate_init();
    addtoseenby_init();

    /*
     * Process local options
     */
    if(I_flag)
	strncpy0(in_dir, I_flag, sizeof(in_dir));
    else 
	sprintf(in_dir, "%s/%s", cf_spooldir(), TOSS_IN);
    if(O_flag)
	pkt_outdir(O_flag, NULL);
    else
	pkt_outdir(cf_spooldir(), TOSS_TMP);

    routing_init(r_flag ? r_flag : ROUTING);
    areasbbs_init( cf_get_string("AreasBBS", TRUE) );
    passwd_init();

    
    ret = EXIT_OK;
    
    if(optind >= argc)
    {
	/*
	 * No packet file args, process in directory
	 */
	dir_sortmode(DIR_SORTMTIME);
	if(dir_open(in_dir, "*.[pP][kK][tT]") == ERROR)
	{
	    log("$ERROR: can't open directory %s", in_dir);
	    exit(EX_OSERR);
	}
    
	/* Lock file */
	if(l_flag)
	    if(lock_program(PROGRAM, FALSE) == ERROR)
		/* Already busy */
		exit(EXIT_BUSY);

	for(pkt_name=dir_get(TRUE); pkt_name; pkt_name=dir_get(FALSE))
	{
	    if(unpack_file(pkt_name) == ERROR)
	    {
		ret = EXIT_ERROR;
		break;
	    }
	    if(must_exit)
	    {
		ret = EXIT_CONTINUE;
		break;
	    }
	}
	
	dir_close();

	/* Lock file */
	if(l_flag)
	    unlock_program(PROGRAM);
    }
    else
    {
	/* Lock file */
	if(l_flag)
	    if(lock_program(PROGRAM, FALSE) == ERROR)
		/* Already busy */
		exit(EXIT_BUSY);
	
	/*
	 * Process packet files on command line
	 */
	for(; optind<argc; optind++)
	{
	    if(unpack_file(argv[optind]) == ERROR)
	    {
		ret = EXIT_ERROR;
		break;
	    }
	    if(must_exit)
	    {
		ret = EXIT_CONTINUE;
		break;
	    }
	}
	
	/* Lock file */
	if(l_flag)
	    unlock_program(PROGRAM);
    }
    
    outpkt_close();

    if(msgs_in)
	log("msgs processed: %ld in, %ld out (%ld mail, %ld echo)",
	    msgs_in, msgs_netmail+msgs_echomail, msgs_netmail, msgs_echomail);
    if(msgs_unknown || msgs_routed || msgs_insecure || msgs_empty)
	log("msgs killed:    %ld empty, %ld unknown, %ld routed, %ld insecure",
	    msgs_empty, msgs_unknown, msgs_routed, msgs_insecure             );
    if(check_path & msgs_path)
	log("                %ld circular path", msgs_path);
    
    exit(ret);

    /**NOT REACHED**/
    return 1;
}
