/*****************************************************************************
 *
 * $Id$
 * Purpose .................: Fidonet binkp protocol
 * Binkp protocol copyright : Dima Maloff.
 *
 *****************************************************************************
 * Copyright (C) 1997-2003
 *   
 * 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"

#ifndef USE_NEWBINKP

#include "../lib/libs.h"
#include "../lib/structs.h"
#include "../lib/users.h"
#include "../lib/records.h"
#include "../lib/common.h"
#include "../lib/nodelist.h"
#include "../lib/dbnode.h"
#include "../lib/clcomm.h"
#include "../lib/mberrors.h"
#include "ttyio.h"
#include "session.h"
#include "statetbl.h"
#include "config.h"
#include "emsi.h"
#include "openfile.h"
#include "respfreq.h"
#include "filelist.h"
#include "opentcp.h"
#include "rdoptions.h"
#include "lutil.h"
#include "binkp.h"
#include "config.h"
#include "md5b.h"
#include "inbound.h"


/*
 * Safe characters for binkp filenames, the rest will be escaped.
 */
#define BNKCHARS    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@&=+%$-_.!()#|"


static char rbuf[MAX_BLKSIZE + 1];

static char *bstate[] = {
    (char *)"NUL", (char *)"ADR", (char *)"PWD", (char *)"FILE", (char *)"OK",
    (char *)"EOB", (char *)"GOT", (char *)"ERR", (char *)"BSY", (char *)"GET",
    (char *)"SKIP"
};



/*
 * Local prototypes
 */
void		binkp_init(void);
void		binkp_deinit(void);
char		*unix2binkp(char *);
char		*binkp2unix(char *);
int		binkp_expired(void);
void		b_banner(void);
void		b_nul(char *);
void		fill_binkp_list(binkp_list **, file_list *, off_t);
void		debug_binkp_list(binkp_list **);
void		binkp_send_data(char *, int);
void		binkp_send_control(int id, ...);
int		binkp_recv_frame(char *, int *, int *);
void		binkp_settimer(int);
int		resync(off_t);
static int	orgbinkp(void);
static int	ansbinkp(void);
static int	binkp_batch(file_list *);


extern char	*tempinbound;
extern char	*ttystat[];
extern int	Loaded;
extern pid_t	mypid;
extern struct sockaddr_in   peeraddr;
extern int	most_debug;


extern unsigned long	sentbytes;
extern unsigned long	rcvdbytes;

typedef enum {RxWaitFile, RxAcceptFile, RxReceData, RxWriteData, RxEndOfBatch, RxDone} RxType;
typedef enum {TxGetNextFile, TxTryRead, TxReadSend, TxWaitLastAck, TxDone} TxType;
typedef enum {InitTransfer, Switch, Receive, Transmit} TransferType;
typedef enum {Ok, Failure, Continue} TrType;
typedef enum {No, WeCan, WeWant, TheyWant, Active} OptionState;

static char *rxstate[] = { (char *)"RxWaitFile", (char *)"RxAccpetFile", (char *)"RxReceData", 
			   (char *)"RxWriteData", (char *)"RxEndOfBatch", (char *)"RxDone" };
static char *txstate[] = { (char *)"TxGetNextFile", (char *)"TryRread", (char *)"ReadSent", 
			   (char *)"WaitLastAck", (char *)"TxDone" };
//static char *tfstate[] = { (char *)"InitTransfer", (char *)"Switch", (char *)"Receive", (char *)"Transmit" };
//static char *trstate[] = { (char *)"Ok", (char *)"Failure", (char *)"Continue" };
static char *opstate[] = { (char *)"No", (char *)"WeCan", (char *)"WeWant", (char *)"TheyWant", (char *)"Active" };


//static int	TfState;
static time_t	Timer;
static int	CRAMflag = FALSE;		/* CRAM option flag		    */
static int	Secure = FALSE;			/* Secure session		    */
int		transferred = FALSE;		/* Anything transferred in batch    */
unsigned char	*MD_challenge = NULL;		/* Received CRAM challenge data	    */
int		ext_rand = 0;



struct binkprec {
    int			role;			/* 1=orig, 0=answer		    */
    int			RxState;		/* Receiver state		    */
    int			TxState;		/* Transmitter state		    */
    int			DidSendGET;		/* Did we send a GET command	    */
    long		rsize;			/* Receiver filesize		    */
    long		roffs;			/* Receiver offset		    */
    char		*rname;			/* Receiver filename		    */
    time_t		rtime;			/* Receiver filetime		    */
    long		lsize;			/* Local filesize		    */
    char		*lname;			/* Local filename		    */
    time_t		ltime;			/* Local filetime		    */
    long		gsize;			/* GET filesize			    */
    long		goffset;		/* GET offset			    */
    char		*gname;			/* GET filename			    */
    time_t		gtime;			/* GET filetime			    */
    int			MBflag;			/* MB option flag                   */
    int			Major;			/* Remote major protocol version    */
    int			Minor;			/* Remote minor protocol version    */
    int			rc;			/* Protocol return code		    */
    int			rxlen;			/* Receive buffer length	    */
    int			txlen;			/* Length of transmitted data block */
    char		*txbuf;			/* Transmitter buffer		    */
    char		*rxbuf;			/* Receiver buffer		    */
    FILE		*txfp;			/* File in transmitter		    */
    FILE		*rxfp;			/* File in receiver		    */
    int			txpos;			/* Transmitter position		    */
    int			rxpos;			/* Receiver position		    */
    int			stxpos;			/* Start transmitter position	    */
    int			cmd;			/* Command flag			    */
    int			GotFrame;		/* Got Frame flag		    */
    unsigned short	header;			/* Frame header			    */
    int			blklen;			/* Block length			    */
    off_t		rxbytes;		/* Receiver bytecount		    */
    struct timeval	rxtvstart;		/* Receive file start		    */
    struct timeval	rxtvend;		/* Receiver file end		    */
    struct timeval	txtvstart;		/* Transmit file start		    */
    struct timeval	txtvend;		/* Transmit file end		    */
    struct timezone	tz;			/* Timezone			    */
    unsigned long	nethold;		/* Netmail on hold		    */
    unsigned long	mailhold;		/* Packed mail (files) on hold	    */
    int			batchnr;		/* Batch number			    */
};

struct binkprec	bp;				/* Global structure		    */



void binkp_init(void)
{
    bp.rname = calloc(512, sizeof(char));
    bp.lname = calloc(512, sizeof(char));
    bp.gname = calloc(512, sizeof(char));
    bp.MBflag = WeCan;
    bp.Major = 1;
    bp.Minor = 0;
    bp.DidSendGET = FALSE;
    bp.rc = 0;
    bp.rxlen = 0;
    bp.txbuf = calloc(MAX_BLKSIZE + 3, sizeof(unsigned char));
    bp.rxbuf = calloc(MAX_BLKSIZE + 3, sizeof(unsigned char));
    bp.txfp = NULL;
    bp.rxfp = NULL;
    bp.txpos = 0;
    bp.rxpos = 0;
    bp.stxpos = 0;
    bp.cmd = FALSE;
    bp.GotFrame = FALSE;
    bp.header = 0;
    bp.batchnr = 0;
}


void binkp_deinit(void)
{
    if (bp.rname)
	free(bp.rname);
    if (bp.lname)
	free(bp.lname);
    if (bp.gname)
	free(bp.gname);
    if (bp.txbuf)
	free(bp.txbuf);
    if (bp.rxbuf)
	free(bp.rxbuf);
}


int binkp(int role)
{
    int		rc = MBERR_OK;
    fa_list	*eff_remote;
    file_list	*tosend = NULL, *request = NULL, *respond = NULL, *tmpfl;
    char	*nonhold_mail;

    binkp_init();

    most_debug = TRUE;
    if (role == 1) {
	if (orgbinkp()) {
	    rc = MBERR_SESSION_ERROR;
	}
    } else {
	if (ansbinkp()) {
	    rc = MBERR_SESSION_ERROR;
	}
    }
	
    if (rc) {
	Syslog('!', "Binkp: session failed");
	binkp_deinit();
	return rc;
    }

//    if (localoptions & NOFREQS)
//	session_flags &= ~SESSION_WAZOO;
//    else
	session_flags |= SESSION_WAZOO;

    Syslog('b', "Binkp: WAZOO requests: %s", (session_flags & SESSION_WAZOO) ? "True":"False");

    nonhold_mail = (char *)ALL_MAIL;
    eff_remote = remote;
    /*
     * If remote doesn't have the 8.3 flag set, allow long filenames.
     */
    if (!nodes.FNC)
	remote_flags &= ~SESSION_FNC;
	
    tosend = create_filelist(eff_remote, nonhold_mail, 0);

    if (request != NULL) {
	Syslog('b', "Binkp: inserting request list");
	tmpfl = tosend;
	tosend = request;
	for (; request->next; request = request->next);
	request->next = tmpfl;

	request = NULL;
    }

    rc = binkp_batch(tosend);
    tidy_filelist(tosend, (rc == 0));
    tosend = NULL;

    if ((rc == 0) && transferred && (bp.MBflag == Active)) {
	/*
	 * Running Multiple Batch, only if last batch actually
	 * did transfer some data.
	 */
	respond = respond_wazoo();
	/*
	 * Just create the tosend list again, there may be something
	 * ready again for this node.
	 */
	tosend = create_filelist(eff_remote, nonhold_mail, 0);
	for (tmpfl = tosend; tmpfl->next; tmpfl = tmpfl->next);
	tmpfl->next = respond;
	rc = binkp_batch(tosend);
	tmpfl->next = NULL;
    }

    Syslog('+', "Binkp: end transfer rc=%d", rc);
    closetcp();

    Syslog('b', "2nd batch or not, MB flag is %s", opstate[bp.MBflag]);

    if (bp.MBflag != Active) {
	/*
	 *  In singe batch mode we process filerequests after the batch.
	 *  The results will be put on hold for the calling node.
	 *  This method is also known as "dual session mode".
	 */
	respond = respond_wazoo();
	for (tmpfl = respond; tmpfl; tmpfl = tmpfl->next) {
	    if (strncmp(tmpfl->local, "/tmp", 4)) {
		attach(*remote->addr, tmpfl->local, LEAVE, 'h');
		Syslog('+', "Binkp: put on hold: %s", MBSE_SS(tmpfl->local));
	    } else {
		file_mv(tmpfl->local, pktname(remote->addr, 'h'));
		Syslog('+', "Binkp: new netmail: %s", pktname(remote->addr, 'h'));
	    }
	}
    }

    tidy_filelist(request, (rc == 0));
    tidy_filelist(tosend, (rc == 0));
    tidy_filelist(respond, 0);

    binkp_deinit();
    rc = abs(rc);
    return rc;
}



/*
 * Translate string to binkp escaped string, unsafe characters are escaped.
 */
char *unix2binkp(char *fn)
{
    static char	buf[PATH_MAX];
    char	*p, *q;

    memset(&buf, 0, sizeof(buf));
    p = fn;
    q = buf;

    while (*p) {
	if (strspn(p, (char *)BNKCHARS)) {
	    *q++ = *p; 
	    *q = '\0';
	} else {
	    if (nodes.WrongEscape) {
		sprintf(q, "\\%2x", p[0]);
	    } else {
		sprintf(q, "\\x%2x", p[0]);
	    }
	}
	while (*q)
	    q++;
	p++;
    }
    *q = '\0';
    
    return buf;
}



/*
 * Translate escaped binkp string to normal string.
 */
char *binkp2unix(char *fn)
{
    static char buf[PATH_MAX];
    char	*p, *q, hex[3];
    int		c;

    memset(&buf, 0, sizeof(buf));
    p = fn;
    q = buf;

    while (*p) {
	if (p[0] == '\\') {
	    p++;
	    if (*p == '\\') {
		/*
		 * A backslash is transmitted
		 */
		*q++ = '\\';
		*q = '\0';
	    } else {
		/*
		 * If remote sends \x0a method instead of \0a, eat the x character.
		 * Remotes should send the x character, But some (Argus and Irex) don't.
		 */
		if ((*p == 'x') || (*p == 'X'))
		    p++;
		/*
		 * Decode hex characters
		 */
		hex[0] = *p++;
		hex[1] = *p;
		hex[2] = '\0';
		sscanf(hex, "%2x", &c);
		*q++ = c;
		*q = '\0';
	    }
	} else {
	    *q++ = *p;
	    *q = '\0';
	}
	p++;
    }
    *q = '\0';

    return buf;
}



/*
 *  Transmit data frame
 */
void binkp_send_data(char *buf, int len)
{
    unsigned short	header = 0;

    Syslog('B', "Binkp: send_data len=%d", len);

    header = ((BINKP_DATA_BLOCK + len) & 0xffff);

    PUTCHAR((header >> 8) & 0x00ff);
    PUTCHAR(header & 0x00ff);
    if (len)
	PUT(buf, len);
    FLUSHOUT();
    binkp_settimer(BINKP_TIMEOUT);
}



/*
 *  Transmit control frame
 */
void binkp_send_control(int id,...)
{
    va_list	args;
    char	*fmt, *s;
    binkp_frame	frame;
    static char	buf[1024];
    int		sz;

    va_start(args, id);
    fmt = va_arg(args, char*);

    if (fmt) {
	vsprintf(buf, fmt, args);
	sz = ((1 + strlen(buf)) & 0x7fff);
    } else {
	buf[0]='\0';
	sz = 1;
    }

    Syslog('b', "Binkp: send_ctl %s \"%s\"", bstate[id], buf);
    frame.header = ((BINKP_CONTROL_BLOCK + sz) & 0xffff);
    frame.id = (char)id;
    frame.data = buf;

    s = (unsigned char *)malloc(sz + 2 + 1);
    s[sz + 2] = '\0';
    s[0] = ((frame.header >> 8)&0xff);
    s[1] = (frame.header & 0xff);
    s[2] = frame.id;
    if (frame.data[0])
	strncpy(s + 3, frame.data, sz-1);
	
    PUT(s, sz+2);
    FLUSHOUT();

    free(s);
    va_end(args);
    binkp_settimer(BINKP_TIMEOUT);
}



/*
 * This function is called two times if a partial file exists from openfile.
 *  1. A partial file is detected, send a GET to the remote, set DidSendGET flag.
 *  2. DidSendGET is set, return 0 and let openfile open the file in append mode.
 */
int resync(off_t off)
{
    Syslog('b', "Binkp: resync(%d) DidSendGET=%s", off, bp.DidSendGET ?"TRUE":"FALSE");
    if (!bp.DidSendGET) {
	binkp_send_control(MM_GET, "%s %ld %ld %ld", bp.rname, bp.rsize, bp.rtime, off);
	bp.DidSendGET = TRUE;
	Syslog('+', "Binkp: already %lu bytes received, requested restart with offset", (unsigned long)off);
	return -1;  /* Signal openfile not to open the file */
    }
    bp.DidSendGET = FALSE;
    return 0;	    /* Signal openfile to open the file in append mode	*/
}



/*
 *  Receive control frame
 */
int binkp_recv_frame(char *buf, int *len, int *cmd)
{
    int	b0, b1;

    *len = *cmd = 0;

    b0 = GETCHAR(BINKP_TIMEOUT);
    if (tty_status)
	goto to;
    if (b0 & 0x80)
	*cmd = 1;

    b1 = GETCHAR(1);
    if (tty_status)
	goto to;

    *len = (b0 & 0x7f) << 8;
    *len += b1;

    GET(buf, *len, BINKP_TIMEOUT / 2);
    buf[*len] = '\0';
    if (tty_status)
	goto to;

to:
    if (tty_status)
	WriteError("Binkp: TCP receive error: %d %s", tty_status, ttystat[tty_status]);
    return tty_status;
}



void binkp_settimer(int interval)
{
    Timer = time((time_t*)NULL) + interval;
}



int binkp_expired(void)
{
    time_t	now;

    now = time(NULL);
    if (now >= Timer)
	Syslog('+', "Binkp: timeout");
    return (now >= Timer);
}



void b_banner(void)
{
    time_t  t;

    binkp_send_control(MM_NUL,"SYS %s", CFG.bbs_name);
    binkp_send_control(MM_NUL,"ZYZ %s", CFG.sysop_name);
    binkp_send_control(MM_NUL,"LOC %s", CFG.location);
    binkp_send_control(MM_NUL,"NDL %s", CFG.Flags);
    t = time(NULL);
    binkp_send_control(MM_NUL,"TIME %s", rfcdate(t));
    binkp_send_control(MM_NUL,"VER mbcico/%s/%s-%s %s/%s", VERSION, OsName(), OsCPU(), PRTCLNAME, PRTCLVER);
    if (strlen(CFG.Phone))
	binkp_send_control(MM_NUL,"PHN %s", CFG.Phone);
    if (strlen(CFG.comment))
	binkp_send_control(MM_NUL,"OPM %s", CFG.comment);
}



void b_nul(char *msg)
{
    char    *p, *q;

    if (strncmp(msg, "SYS ", 4) == 0) {
	Syslog('+', "System  : %s", msg+4);
	strncpy(history.system_name, msg+4, 35);
    } else if (strncmp(msg, "ZYZ ", 4) == 0) {
	Syslog('+', "Sysop   : %s", msg+4);
	strncpy(history.sysop, msg+4, 35);
    } else if (strncmp(msg, "LOC ", 4) == 0) {
	Syslog('+', "Location: %s", msg+4);
	strncpy(history.location, msg+4, 35);
    } else if (strncmp(msg, "NDL ", 4) == 0)
	Syslog('+', "Flags   : %s", msg+4);
    else if (strncmp(msg, "TIME ", 5) == 0)
	Syslog('+', "Time    : %s", msg+5);
    else if (strncmp(msg, "VER ", 4) == 0) {
	Syslog('+', "Uses    : %s", msg+4);
	if ((p = strstr(msg+4, PRTCLNAME "/")) && (q = strstr(p, "."))) {
	    bp.Major = atoi(p + 6);
	    bp.Minor = atoi(q + 1);
	    Syslog('b', "Remote protocol version %d.%d", bp.Major, bp.Minor);
	    /*
	     * Disable MB if protocol > 1.0 and MB was not yet active.
	     */
	    if ((bp.MBflag != Active) && (((bp.Major * 10) + bp.Minor) > 10)) {
		Syslog('b', "MBflag %s => No", opstate[bp.MBflag]);
		bp.MBflag = No;
	    }
	}
    }
    else if (strncmp(msg, "PHN ", 4) == 0)
	Syslog('+', "Phone   : %s", msg+4);
    else if (strncmp(msg, "OPM ", 4) == 0)
	Syslog('+', "Remark  : %s", msg+4);
    else if (strncmp(msg, "TRF ", 4) == 0)
	Syslog('+', "Binkp: remote has %s mail/files for us", msg+4);
    else if (strncmp(msg, "OPT ", 4) == 0) {
	Syslog('+', "Options : %s", msg+4);
	if (strstr(msg, (char *)"MB") != NULL) {
	    Syslog('b', "Remote requests MB, current state = %s", opstate[bp.MBflag]);
	    if ((bp.MBflag == WeCan) && (bp.Major == 1) && (bp.Minor == 0)) {	/* Answering session and do binkp/1.0   */
		bp.MBflag = TheyWant;
		Syslog('b', "MBflag WeCan => TheyWant");
		binkp_send_control(MM_NUL,"OPT MB");
		Syslog('b', "MBflag TheyWant => Active");
		bp.MBflag = Active;
	    } else if ((bp.MBflag == WeWant) && (bp.Major == 1) && (bp.Minor == 0)) {  /* Originating session and do binkp/1.0 */
		bp.MBflag = Active;
		Syslog('b', "MBflag WeWant => Active");
	    } else {
		Syslog('b', "MBflag is %s and received MB option", opstate[bp.MBflag]);
	    }
	}
	if (strstr(msg, (char *)"CRAM-MD5-") != NULL) {	/* No SHA-1 support */
	    if (CFG.NoMD5) {
		Syslog('+', "Binkp: Remote supports MD5, but it's turned off here");
	    } else {
		if (MD_challenge)
		    free(MD_challenge);
		MD_challenge = MD_getChallenge(msg, NULL);
	    }
	}
    } else
	Syslog('+', "Binkp: M_NUL \"%s\"", msg);
}



/*
 * Originate a binkp session
 */
SM_DECL(orgbinkp, (char *)"orgbinkp")
SM_STATES
    ConnInit,
    WaitConn,
    SendPasswd,
    WaitAddr,
    AuthRemote,
    IfSecure,
    WaitOk,
    Opts
SM_NAMES
    (char *)"ConnInit",
    (char *)"WaitConn",
    (char *)"SendPasswd",
    (char *)"WaitAddr",
    (char *)"AuthRemote",
    (char *)"IfSecure",
    (char *)"WaitOk",
    (char *)"Opts"
SM_EDECL
    faddr   *primary;
    char    *p, *q, *pwd;
    int	    i, rc, bufl, cmd, dupe, SendPass = FALSE;
    fa_list **tmp, *tmpa;
    faddr   *fa, ra;

SM_START(ConnInit)

SM_STATE(ConnInit)

    SM_PROCEED(WaitConn)

SM_STATE(WaitConn)

    Loaded = FALSE;
    Syslog('+', "Binkp: node %s", ascfnode(remote->addr, 0x1f));
    IsDoing("Connect binkp %s", ascfnode(remote->addr, 0xf));

    /*
     * Build options we want
     */
//    p = xstrcpy((char *)"OPT");
//    if ((noderecord(remote->addr)) && nodes.CRC32 && (bp.CRCflag == WeCan)) {
//	p = xstrcat(p, (char *)" CRC");
//	bp.CRCflag = WeWant;
//	Syslog('b', "CRCflag WeCan => WeWant");
//    }
//    if (strcmp(p, (char *)"OPT"))
//	binkp_send_control(MM_NUL, p);
//    free(p);
    b_banner();

    /*
     * Build a list of aka's to send, the primary aka first.
     */
    ra.zone  = remote->addr->zone;
    ra.net   = remote->addr->net;
    ra.node  = remote->addr->node;
    ra.point = remote->addr->point;

    primary = bestaka_s(remote->addr);
    p = xstrcpy(ascfnode(primary, 0x1f));

    /*
     * Add all other aka's exept primary aka.
     */
    for (i = 0; i < 40; i++)
	if ((CFG.aka[i].zone) && (CFG.akavalid[i]) &&
	    ((CFG.aka[i].zone != primary->zone) || (CFG.aka[i].net  != primary->net)  ||
	     (CFG.aka[i].node != primary->node) || (CFG.aka[i].point!= primary->point))) {
		p = xstrcat(p, (char *)" ");
		p = xstrcat(p, aka2str(CFG.aka[i]));
	}

    binkp_send_control(MM_ADR, "%s", p);
    free(p);
    tidy_faddr(primary);
    SM_PROCEED(WaitAddr)

SM_STATE(WaitAddr)
	
    for (;;) {
	if ((rc = binkp_recv_frame(rbuf, &bufl, &cmd))) {
	    Syslog('!', "Binkp: error receiving remote info");
	    SM_ERROR;
	}

	if (cmd) {
	    if (rbuf[0] == MM_ADR) {
		p = xstrcpy(&rbuf[1]);
		tidy_falist(&remote);
	        remote = NULL;
	        tmp = &remote;

	        for (q = strtok(p, " "); q; q = strtok(NULL, " ")) {
		   if ((fa = parsefnode(q))) {
		        dupe = FALSE;
		        for (tmpa = remote; tmpa; tmpa = tmpa->next) {
			    if ((tmpa->addr->zone == fa->zone) && (tmpa->addr->net == fa->net) &&
			        (tmpa->addr->node == fa->node) && (tmpa->addr->point == fa->point) &&
			        (strcmp(tmpa->addr->domain, fa->domain) == 0)) {
			        dupe = TRUE;
			        Syslog('b', "Binkp: double address %s", ascfnode(tmpa->addr, 0x1f));
			        break;
			    }
			}
		        if (!dupe) {
			    *tmp = (fa_list*)malloc(sizeof(fa_list));
			    (*tmp)->next = NULL;
			    (*tmp)->addr = fa;
			    tmp = &((*tmp)->next);
			}
		    } else {
			Syslog('!', "Binkp: bad remote address: \"%s\"", printable(q, 0));
		        binkp_send_control(MM_ERR, "Bad address");
		    }
		}

	        for (tmpa = remote; tmpa; tmpa = tmpa->next) {
		    Syslog('+', "Address : %s", ascfnode(tmpa->addr, 0x1f));
		    if (nodelock(tmpa->addr, mypid)) {
		        binkp_send_control(MM_BSY, "Address %s locked", ascfnode(tmpa->addr, 0x1f));
		        SM_ERROR;
		    }

		    /*
		     * With the loaded flag we prevent removing the noderecord
		     * when the remote presents us an address we don't know about.
		     */
		    if (!Loaded) {
		        if (noderecord(tmpa->addr))
			   Loaded = TRUE;
		    }
		}

	        history.aka.zone  = remote->addr->zone;
	        history.aka.net   = remote->addr->net;
	        history.aka.node  = remote->addr->node;
	        history.aka.point = remote->addr->point;
	        sprintf(history.aka.domain, "%s", remote->addr->domain);

	        SM_PROCEED(SendPasswd)
		    
	    } else if (rbuf[0] == MM_BSY) {
		Syslog('!', "Binkp: M_BSY \"%s\"", printable(&rbuf[1], 0));
	        SM_ERROR;
		
	    } else if (rbuf[0] == MM_ERR) {
		Syslog('!', "Binkp: M_ERR \"%s\"", printable(&rbuf[1], 0));
		SM_ERROR;

	    } else if (rbuf[0] == MM_NUL) {
		b_nul(&rbuf[1]);

	    } else {
		binkp_send_control(MM_ERR, "Unexpected frame");
		SM_ERROR;
	    }
	}
    }

SM_STATE(SendPasswd)

    if (Loaded && strlen(nodes.Spasswd)) {
	pwd = xstrcpy(nodes.Spasswd);
	SendPass = TRUE;
    } else {
	pwd = xstrcpy((char *)"-");
    }

    if (MD_challenge) {
	char	*tp = NULL;
	tp = MD_buildDigest(pwd, MD_challenge);
	if (!tp) {
	    Syslog('!', "Unable to build MD5 digest");
	    SM_ERROR;
	}
	CRAMflag = TRUE;
	binkp_send_control(MM_PWD, "%s", tp);
	free(tp);
    } else {
	binkp_send_control(MM_PWD, "%s", pwd);
    }

    free(pwd);
    SM_PROCEED(AuthRemote)

SM_STATE(AuthRemote)

    rc = 0;
    for (tmpa = remote; tmpa; tmpa = tmpa->next) {
	if ((tmpa->addr->zone  == ra.zone) && (tmpa->addr->net   == ra.net) &&
	    (tmpa->addr->node  == ra.node) && (tmpa->addr->point == ra.point)) {
		rc = 1;		
	}
    }

    if (rc) {
	SM_PROCEED(IfSecure)
    } else {
	Syslog('!', "Binkp: error, the wrong node is reached");
	binkp_send_control(MM_ERR, "No AKAs in common or all AKAs busy");
	SM_ERROR;
    }

SM_STATE(IfSecure)

    SM_PROCEED(WaitOk)

SM_STATE(WaitOk)

    for (;;) {
	if ((rc = binkp_recv_frame(rbuf, &bufl, &cmd))) {
	    Syslog('!', "Binkp: error waiting for remote acknowledge");
	    SM_ERROR;
	}

	if (cmd) {
	    if (rbuf[0] == MM_OK) {
		Syslog('b', "Binkp: M_OK \"%s\"", printable(&rbuf[1], 0));
	        if (SendPass)
		   Secure = TRUE;
		Syslog('+', "Binkp: %s%sprotected session", CRAMflag ? "MD5 ":"", Secure ? "":"un");
		SM_PROCEED(Opts)

	    } else if (rbuf[0] == MM_BSY) {
		Syslog('!', "Binkp: M_BSY \"%s\"", printable(&rbuf[1], 0));
	        SM_ERROR;

	    } else if (rbuf[0] == MM_ERR) {
		Syslog('!', "Binkp: M_ERR \"%s\"", printable(&rbuf[1], 0));
	        SM_ERROR;

	    } else if (rbuf[0] == MM_NUL) {
		b_nul(&rbuf[1]);

	    } else {
		binkp_send_control(MM_ERR, "Unexpected frame");
	        SM_ERROR;
	    }
	}
    }

SM_STATE(Opts)

    /*
     *  Try to initiate the MB option if the remote is binkp/1.0
     */
    if ((bp.MBflag == WeCan) && (bp.Major == 1) && (bp.Minor == 0)) {
	bp.MBflag = WeWant;
	Syslog('b', "MBflag WeCan => WeWant");
	binkp_send_control(MM_NUL, "OPT MB");
    }
    SM_SUCCESS;

SM_END
SM_RETURN
	


/*
 * Answer a binkp session
 */
SM_DECL(ansbinkp, (char *)"ansbinkp")
SM_STATES
    WaitConn,
    WaitAddr,
    IsPasswd,
    WaitPwd,
    PwdAck,
    Opts
SM_NAMES
    (char *)"WaitConn",
    (char *)"WaitAddr",
    (char *)"IsPasswd",
    (char *)"WaitPwd",
    (char *)"PwdAck",
    (char *)"Opts"

SM_EDECL
    char    *p, *q, *pw;
    int     i, rc, bufl, cmd, dupe, we_have_pwd = FALSE;
    fa_list **tmp, *tmpa;
    faddr   *fa;

SM_START(WaitConn)

SM_STATE(WaitConn)

    Loaded = FALSE;

    if (strncmp(SockR("SBBS:0;"), "100:2,1", 7) == 0) {
	Syslog('+', "Binkp: system is closed, sending M_BSY");
	binkp_send_control(MM_BSY, "This system is closed, try again later");
	SM_ERROR;
    }

    if (!CFG.NoMD5 && ((MD_challenge = MD_getChallenge(NULL, &peeraddr)) != NULL)) {
	/*
	 * Answering site MUST send CRAM message as very first M_NUL
	 */
	char s[MD5_DIGEST_LEN*2+15]; /* max. length of opt string */
	strcpy(s, "OPT ");
	MD_toString(s+4, MD_challenge[0], MD_challenge+1);
	CRAMflag = TRUE;
	binkp_send_control(MM_NUL, "%s", s);
    }
    b_banner();
    p = xstrcpy((char *)"");

    for (i = 0; i < 40; i++)
	if ((CFG.aka[i].zone) && (CFG.akavalid[i])) {
	    p = xstrcat(p, (char *)" ");
	    p = xstrcat(p, aka2str(CFG.aka[i]));
	}

    binkp_send_control(MM_ADR, "%s", p);
    free(p);
    SM_PROCEED(WaitAddr)

SM_STATE(WaitAddr)

    for (;;) {
	if ((rc = binkp_recv_frame(rbuf, &bufl, &cmd))) {
	    Syslog('!', "Binkp: error waiting for remote info");
	    SM_ERROR;
	}

	if (cmd) {
	    if (rbuf[0] == MM_ADR) {
		p = xstrcpy(&rbuf[1]);
		tidy_falist(&remote);
		remote = NULL;
		tmp = &remote;

		for (q = strtok(p, " "); q; q = strtok(NULL, " "))
		    if ((fa = parsefnode(q))) {
			dupe = FALSE;
			for (tmpa = remote; tmpa; tmpa = tmpa->next) {
			    if ((tmpa->addr->zone == fa->zone) && (tmpa->addr->net == fa->net) &&
				(tmpa->addr->node == fa->node) && (tmpa->addr->point == fa->point) &&
				(strcmp(tmpa->addr->domain, fa->domain) == 0)) {
				dupe = TRUE;
				Syslog('b', "Binkp: double address %s", ascfnode(tmpa->addr, 0x1f));
				break;
			    }
			}
			if (!dupe) {
			    *tmp = (fa_list*)malloc(sizeof(fa_list));
			    (*tmp)->next = NULL;
			    (*tmp)->addr = fa;
			    tmp = &((*tmp)->next);
			}
		    } else {
			Syslog('!', "Binkp: bad remote address: \"%s\"", printable(q, 0));
			binkp_send_control(MM_ERR, "Bad address");
		    }

		for (tmpa = remote; tmpa; tmpa = tmpa->next) {
		    Syslog('+', "Address : %s", ascfnode(tmpa->addr, 0x1f));
		    if (nodelock(tmpa->addr, mypid)) {
			binkp_send_control(MM_BSY, "Address %s locked", ascfnode(tmpa->addr, 0x1f));
			SM_ERROR;
		    }

		    /*
		     * With the loaded flag we prevent removing the noderecord
		     * when the remote presents us an address we don't know about.
		     */
		    if (!Loaded) {
			if (noderecord(tmpa->addr))
			    Loaded = TRUE;
		    }
		}

		for (tmpa = remote; tmpa; tmpa = tmpa->next) {
		    if (((nlent = getnlent(tmpa->addr))) && (nlent->pflag != NL_DUMMY)) {
			Syslog('+', "Binkp: remote is a listed system");
			UserCity(mypid, nlent->sysop, nlent->location);
			break;
		    }
		}
		if (nlent)
		    rdoptions(Loaded);

//		if (bp.MBflag == TheyWant) {
//		    Syslog('b', "Binkp: remote supports MB");
//		    binkp_send_control(MM_NUL,"OPT MB");
//		    bp.MBflag = Active;
//		}

		history.aka.zone  = remote->addr->zone;
		history.aka.net   = remote->addr->net;
		history.aka.node  = remote->addr->node;
		history.aka.point = remote->addr->point;
		sprintf(history.aka.domain, "%s", remote->addr->domain);

		SM_PROCEED(IsPasswd)

	    } else if (rbuf[0] == MM_ERR) {
		Syslog('!', "Binkp: M_ERR \"%s\"", printable(&rbuf[1], 0));
		SM_ERROR;

	    } else if (rbuf[0] == MM_NUL) {
		b_nul(&rbuf[1]);
	    } else if (rbuf[0] <= MM_MAX) {
		binkp_send_control(MM_ERR, "Unexpected frame");
		SM_ERROR;
	    }
	}
    }

SM_STATE(IsPasswd)

    if (Loaded && strlen(nodes.Spasswd)) {
	we_have_pwd = TRUE;
    }
    
    Syslog('b', "We %s have a password", we_have_pwd ?"do":"don't");
    SM_PROCEED(WaitPwd)

SM_STATE(WaitPwd)

    for (;;) {
	if ((rc = binkp_recv_frame(rbuf, &bufl, &cmd))) {
	    Syslog('!', "Binkp: error waiting for password");
	    SM_ERROR;
	}

        if (cmd) {
	    if (rbuf[0] == MM_PWD) {
		SM_PROCEED(PwdAck)

	    } else if (rbuf[0] == MM_ERR) {
		Syslog('!', "Binkp: M_ERR \"%s\"", printable(&rbuf[1], 0));
                SM_ERROR;
	    } else if (rbuf[0] == MM_NUL) {
                b_nul(&rbuf[1]);
	    } else if (rbuf[0] <= MM_MAX) {
		binkp_send_control(MM_ERR, "Unexpected frame");
		SM_ERROR;
	    }
	}
    }

SM_STATE(PwdAck)

    if (we_have_pwd) {
	pw = xstrcpy(nodes.Spasswd);
    } else {
	pw = xstrcpy((char *)"-");
    }

    if ((strncmp(&rbuf[1], "CRAM-", 5) == 0) && CRAMflag) {
	char	*sp;
	sp = MD_buildDigest(pw, MD_challenge);
	if (sp != NULL) {
	    if (strcmp(&rbuf[1], sp)) {
		Syslog('+', "Binkp: bad MD5 crypted password");
		binkp_send_control(MM_ERR, "Bad password");
		free(sp);
		sp = NULL;
		free(pw);
		SM_ERROR;
	    } else {
		free(sp);
		sp = NULL;
		if (we_have_pwd)
		    Secure = TRUE;
	    }
	} else {
	    free(pw);
	    Syslog('!', "Binkp: could not build MD5 digest");
	    binkp_send_control(MM_ERR, "*** Internal error ***");
	    SM_ERROR;
	}
    } else if ((strcmp(&rbuf[1], pw) == 0)) {
	if (we_have_pwd)
	    Secure = TRUE;
    } else {
	free(pw);
	Syslog('?', "Binkp: password error: expected \"%s\", got \"%s\"", nodes.Spasswd, &rbuf[1]);
	binkp_send_control(MM_ERR, "Bad password");
	SM_ERROR;
    }

    free(pw);
    Syslog('+', "Binkp: %s%sprotected session", CRAMflag ? "MD5 ":"", Secure ? "":"un");
    inbound_open(remote->addr, Secure);
    binkp_send_control(MM_OK, "%ssecure", Secure ? "":"non-");
    SM_PROCEED(Opts)

SM_STATE(Opts)

    SM_SUCCESS;

SM_END
SM_RETURN



void fill_binkp_list(binkp_list **bll, file_list *fal, off_t offs)
{
    binkp_list	**tmpl;
    struct stat	tstat;

    if (stat(fal->local, &tstat) != 0) {
	Syslog('!', "$Can't add %s to sendlist", fal->local);
	return;
    }
    if (strstr(fal->remote, (char *)".pkt"))
	bp.nethold += tstat.st_size;
    else
	bp.mailhold += tstat.st_size;
	
    for (tmpl = bll; *tmpl; tmpl = &((*tmpl)->next));
    *tmpl = (binkp_list *)malloc(sizeof(binkp_list));

    (*tmpl)->next   = NULL;
    (*tmpl)->state  = NoState;
    (*tmpl)->get    = FALSE;
    (*tmpl)->local  = xstrcpy(fal->local);
    (*tmpl)->remote = xstrcpy(unix2binkp(fal->remote));
    (*tmpl)->offset = offs;
    (*tmpl)->size   = tstat.st_size;
    (*tmpl)->date   = tstat.st_mtime;
}



char *lbstat[]={(char *)"None",
		(char *)"Sending",
		(char *)"IsSent",
		(char *)"Got",
		(char *)"Skipped",
		(char *)"Get"};



void debug_binkp_list(binkp_list **bll)
{
    binkp_list	*tmpl;

    Syslog('B', "Current filelist:");

    for (tmpl = *bll; tmpl; tmpl = tmpl->next)
	Syslog('B', "%s %s %s %ld", MBSE_SS(tmpl->local), MBSE_SS(tmpl->remote), lbstat[tmpl->state], tmpl->offset);
}


/*
 * Get a fram for the batch
 */
void batch_receive_frame(void);
void batch_receive_frame(void)
{
    int	    c;

    for (;;) {
	if (bp.GotFrame) {
	    Syslog('b', "Binkp: WARNING: frame not processed");
	    break;
	} else {
	    c = GETCHAR(0);
	    if (c < 0) {
		c = -c;
		if (c == STAT_TIMEOUT) {
		    usleep(1);
		    break;
		}
		Syslog('?', "Binkp: receiver status %s", ttystat[c]);
		bp.TxState = TxDone;
		bp.RxState = RxDone;
		bp.rc = (MBERR_TTYIO + (-c));
		break;
	    } else {
		switch (bp.rxlen) {
		    case 0: bp.header = c << 8;
			    break;
		    case 1: bp.header += c;
			    break;
		    default:bp.rxbuf[bp.rxlen-2] = c;
		}
		if (bp.rxlen == 1) {
		    bp.cmd = bp.header & 0x8000;
		    bp.blklen = bp.header & 0x7fff;
		}
		if ((bp.rxlen == (bp.blklen + 1) && (bp.rxlen >= 1))) {
		    bp.GotFrame = TRUE;
		    binkp_settimer(BINKP_TIMEOUT);
		    bp.rxbuf[bp.rxlen-1] = '\0';
		    break;
		}
		bp.rxlen++;
	    }
	}
    }
}



int binkp_batch(file_list *to_send)
{
    int		    NotDone, written, Found = FALSE;
    binkp_list	    *bll = NULL, *tmp, *tmpg, *cursend = NULL;
    file_list	    *tsl;
    struct statfs   sfs;

    bp.rxtvstart.tv_sec = bp.rxtvstart.tv_usec = 0;
    bp.rxtvend.tv_sec   = bp.rxtvend.tv_usec   = 0;
    bp.txtvstart.tv_sec = bp.txtvstart.tv_usec = 0;
    bp.txtvend.tv_sec   = bp.txtvend.tv_usec   = 0;
    bp.tz.tz_minuteswest = bp.tz.tz_dsttime = 0;

    bp.batchnr++;
    Syslog('+', "Binkp: starting batch %d", bp.batchnr);
    IsDoing("Binkp %s %s", (bp.role == 1)?"out":"inb", ascfnode(remote->addr, 0xf));
//    TfState = Switch;
    bp.RxState = RxWaitFile;
    bp.TxState = TxGetNextFile;
    binkp_settimer(BINKP_TIMEOUT);
    bp.nethold = bp.mailhold = 0L;
    transferred = FALSE;

    /*
     *  Build a new filelist from the existing filelist.
     *  This one is special for binkp behaviour.
     */
    for (tsl = to_send; tsl; tsl = tsl->next) {
	if (tsl->remote != NULL)
	    fill_binkp_list(&bll, tsl, 0L);
    }
    debug_binkp_list(&bll);

    Syslog('+', "Binkp: mail %ld, files %ld bytes", bp.nethold, bp.mailhold);
    binkp_send_control(MM_NUL, "TRF %ld %ld", bp.nethold, bp.mailhold);

    while ((bp.RxState != RxDone) || (bp.TxState != TxDone)) {

	Nopper();
	if (binkp_expired()) {
	    Syslog('!', "Binkp: Transfer timeout");
	    Syslog('b', "Binkp: TxState=%s, RxState=%s, rxlen=%d", txstate[bp.TxState], rxstate[bp.RxState], bp.rxlen);
	    bp.RxState = RxDone;
	    bp.TxState = TxDone;
	    binkp_send_control(MM_ERR, "Transfer timeout");
	    bp.rc = MBERR_FTRANSFER;
	    break;
	}

	/*
	 * Receiver binkp frame
	 */
	batch_receive_frame();

	/*
	 * Transmitter state machine
	 */
	switch (bp.TxState) {
	case TxGetNextFile:
	    for (tmp = bll; tmp; tmp = tmp->next) {
		if (tmp->state == NoState) {
		    /*
		     * There is something to send
		     */
		    struct flock	txflock;

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

		    bp.txfp = fopen(tmp->local, "r");
		    if (bp.txfp == NULL) {
			if ((errno == ENOENT) || (errno == EINVAL)) {
			    Syslog('+', "Binkp: file %s doesn't exist, removing", MBSE_SS(tmp->local));
			    tmp->state = Got;
			} else {
			    WriteError("$Binkp: can't open %s, skipping", MBSE_SS(tmp->local));
			    tmp->state = Skipped;
			}
			break;
		    }

		    if (fcntl(fileno(bp.txfp), F_SETLK, &txflock) != 0) {
			WriteError("$Binkp: can't lock file %s, skipping", MBSE_SS(tmp->local));
			fclose(bp.txfp);
			bp.txfp = NULL;
			tmp->state = Skipped;
			break;
		    }

		    bp.txpos = bp.stxpos = tmp->offset;
		    Syslog('+', "Binkp: send \"%s\" as \"%s\"", MBSE_SS(tmp->local), MBSE_SS(tmp->remote));
		    Syslog('+', "Binkp: size %lu bytes, dated %s", (unsigned long)tmp->size, date(tmp->date));
		    binkp_send_control(MM_FILE, "%s %lu %ld %ld", MBSE_SS(tmp->remote), 
			    (unsigned long)tmp->size, (long)tmp->date, (unsigned long)tmp->offset);
		    gettimeofday(&bp.txtvstart, &bp.tz);
		    tmp->state = Sending;
		    cursend = tmp;
		    bp.TxState = TxTryRead;
		    transferred = TRUE;
		    break;
		} /* if state == NoState */
	    } /* for */

	    if (tmp == NULL) {
		/*
		 * No more files to send
		 */
		bp.TxState = TxWaitLastAck;
		Syslog('b', "Binkp: transmitter to WaitLastAck");
	    }
	    break;

	case TxTryRead:
	    /*
	     * Check if there is room in the output buffer
	     */
	    if ((WAITPUTGET(-1) & 2) != 0)
		bp.TxState = TxReadSend;
	    break;

	case TxReadSend:
	    fseek(bp.txfp, bp.txpos, SEEK_SET);
	    bp.txlen = fread(bp.txbuf, 1, SND_BLKSIZE, bp.txfp);

	    if (bp.txlen == 0) {

		if (ferror(bp.txfp)) {
		    WriteError("$Binkp: error reading from file");
		    bp.TxState = TxGetNextFile;
		    cursend->state = Skipped;
		    debug_binkp_list(&bll);
		    break;
		}

		/*
		 * calculate time needed and bytes transferred
		 */
		gettimeofday(&bp.txtvend, &bp.tz);

		/*
		 * Close transmitter file
		 */
		fclose(bp.txfp);
		bp.txfp = NULL;

		if (bp.txpos >= 0) {
		   bp.stxpos = bp.txpos - bp.stxpos;
		    Syslog('+', "Binkp: OK %s", transfertime(bp.txtvstart, bp.txtvend, bp.stxpos, TRUE));
		} else {
		    Syslog('+', "Binkp: transmitter skipped file after %ld seconds", bp.txtvend.tv_sec - bp.txtvstart.tv_sec);
		}

		cursend->state = IsSent;
		bp.TxState = TxGetNextFile;
		break;
	    } else {
		bp.txpos += bp.txlen;
		sentbytes += bp.txlen;
		binkp_send_data(bp.txbuf, bp.txlen);
	    }

	    bp.TxState = TxTryRead;
	    break;

	case TxWaitLastAck:
	    debug_binkp_list(&bll);
	    NotDone = FALSE;
	    for (tmp = bll; tmp; tmp = tmp->next)
		if ((tmp->state != Got) && (tmp->state != Skipped)) {
		    NotDone = TRUE;
		    break;
		}
	    if (tmp == NULL) {
		bp.TxState = TxDone;
		binkp_send_control(MM_EOB, "");
		Syslog('b', "Binkp: sending EOB");
	    }
	    break;

	case TxDone:
	    break;
	}

	/*
	 * Process received frame
	 */
	if (bp.GotFrame) {
	    if (bp.cmd) {
		switch (bp.rxbuf[0]) {
		case MM_ERR:    Syslog('+', "Binkp: got ERR: %s", bp.rxbuf+1);
				bp.RxState = RxDone;
				bp.TxState = TxDone;
				bp.rc = MBERR_FTRANSFER;
				break;

		case MM_BSY:	Syslog('+', "Binkp: got BSY: %s", bp.rxbuf+1);
				bp.RxState = RxDone;
				bp.TxState = TxDone;
				bp.rc = MBERR_FTRANSFER;
				break;

		case MM_SKIP:   Syslog('+', "Binkp: got SKIP: %s", bp.rxbuf+1);
				break;

		case MM_GET:    Syslog('+', "Binkp: got GET: %s", bp.rxbuf+1);
				sscanf(bp.rxbuf+1, "%s %ld %ld %ld", bp.gname, &bp.gsize, &bp.gtime, &bp.goffset);
				for (tmpg = bll; tmpg; tmpg = tmpg->next) {
				    if (strcasecmp(tmpg->remote, bp.gname) == 0) {
					tmpg->state = NoState;
					tmpg->offset = bp.goffset;
					if (bp.goffset) {
					    Syslog('+', "Remote wants %s for resync at offset %ld", bp.gname, bp.goffset);
					} else {
					    Syslog('+', "Remote wants %s again", bp.gname);
					}
					bp.TxState = TxGetNextFile;
				    }
				}
				break;

		case MM_GOT:    Syslog('+', "Binkp: got GOT: %s", bp.rxbuf+1);
				sscanf(bp.rxbuf+1, "%s %ld %ld", bp.lname, &bp.lsize, &bp.ltime);
				Found = FALSE;
				for (tmp = bll; tmp; tmp = tmp->next)
				    if ((strcmp(bp.lname, tmp->remote) == 0) && (bp.lsize == tmp->size) && 
					    (bp.ltime == tmp->date)) {
					Syslog('+', "Binkp: remote GOT \"%s\"", tmp->remote);
					tmp->state = Got;
					Found = TRUE;
				    }
				if (!Found) {
				    Syslog('!', "Binkp: unexpected GOT \"%s\"", bp.rxbuf+1);
				}
				break;

		case MM_NUL:    b_nul(bp.rxbuf+1);
				break;

		case MM_EOB:    Syslog('+', "Binkp: got EOB");
				bp.RxState = RxEndOfBatch;
				break;

		case MM_FILE:   Syslog('b', "Binkp: got FILE: %s", bp.rxbuf+1);
				if (bp.RxState == RxReceData ) {
				    /*
				     * If we get a m_file command during receive, the current file is
				     * considered interrupted. Save the partial received file and accept
				     * the new file. After this file the transmitter should continue
				     * with the original file and this mailer should send m_get to set
				     * the offset to the point were we left off.
				     */
				    Syslog('+', "Binkp: new file during receive, saving %s", bp.rname);
				    closefile();
				    bp.rxfp = NULL;
				    bp.RxState = RxWaitFile;
				}
				if ((bp.RxState == RxWaitFile) || (bp.RxState == RxEndOfBatch)) {
				    bp.RxState = RxAcceptFile;
				    /*
				     * Check against buffer overflow
				     */
				    if (strlen(bp.rxbuf) < 512) {
					sscanf(bp.rxbuf+1, "%s %ld %ld %ld", bp.rname, &bp.rsize, &bp.rtime, &bp.roffs);
				    } else {
					Syslog('+', "Binkp: got corrupted FILE frame, size %d bytes", strlen(bp.rxbuf));
				    }
				} else {
				    Syslog('+', "Binkp: got unexpected FILE frame %s", bp.rxbuf+1);
				}
				break;

		default:        Syslog('+', "Binkp: Unexpected frame %d \"%s\"", bp.rxbuf[0], printable(bp.rxbuf+1, 0));
		}
	    } else {
		if (bp.blklen) {
		    if (bp.RxState == RxReceData) {
			written = fwrite(bp.rxbuf, 1, bp.blklen, bp.rxfp);
			if (!written && bp.blklen) {
			    Syslog('+', "Binkp: file write error");
			    bp.RxState = RxDone;
			}
			bp.rxpos += written;
			if (bp.rxpos == bp.rsize) {
			    bp.RxState = RxWaitFile;
			    binkp_send_control(MM_GOT, "%s %ld %ld", bp.rname, bp.rsize, bp.rtime);
			    closefile();
			    bp.rxpos = bp.rxpos - bp.rxbytes;
			    gettimeofday(&bp.rxtvend, &bp.tz);
			    Syslog('+', "Binkp: OK %s", transfertime(bp.rxtvstart, bp.rxtvend, bp.rxpos, FALSE));
			    rcvdbytes += bp.rxpos;
			    bp.RxState = RxWaitFile;
			    transferred = TRUE;
			}
		    } else {
			if (!bp.DidSendGET) {
			    /*
			     * Do not log after a GET command, there will be data packets
			     * in the pipeline that must be ignored.
			     */
			    Syslog('+', "Binkp: unexpected DATA frame %d", bp.rxbuf[0]);
			}
		    }
		}
	    }
	    bp.GotFrame = FALSE;
	    bp.rxlen = 0;
	    bp.header = 0;
	    bp.blklen = 0;
	}

	/*
	 * Receiver state machine
	 */
	switch (bp.RxState) {
	case RxWaitFile:
	    break;

	case RxAcceptFile:
	    Syslog('+', "Binkp: receive file \"%s\" date %s size %ld offset %ld", bp.rname, date(bp.rtime), bp.rsize, bp.roffs);
	    (void)binkp2unix(bp.rname);
	    bp.rxfp = openfile(binkp2unix(bp.rname), bp.rtime, bp.rsize, &bp.rxbytes, resync);

	    if (bp.DidSendGET) {
		/*
		 * The file was partly received, via the openfile the resync function
		 * has send a GET command to start this file with a offset. This means
		 * we will get a new FILE command to open this file with a offset.
		 */
		bp.RxState = RxWaitFile;
		break;
	    }

	    gettimeofday(&bp.rxtvstart, &bp.tz);
	    bp.rxpos = bp.roffs;

	    if (!diskfree(CFG.freespace)) {
		Syslog('+', "Binkp: low diskspace, sending BSY");
		binkp_send_control(MM_BSY, "Low diskspace, try again later");
		bp.RxState = RxDone;
		bp.TxState = TxDone;
		bp.rc = MBERR_FTRANSFER;
		break;
	    }

	    if (statfs(tempinbound, &sfs) == 0) {
		Syslog('b', "blocksize %lu free blocks %lu", sfs.f_bsize, sfs.f_bfree);
		Syslog('b', "need %lu blocks", (unsigned long)(bp.rsize / (sfs.f_bsize + 1)));
		if ((bp.rsize / (sfs.f_bsize + 1)) >= sfs.f_bfree) {
		    Syslog('!', "Only %lu blocks free (need %lu) in %s", sfs.f_bfree, 
			    (unsigned long)(bp.rsize / (sfs.f_bsize + 1)), tempinbound);
		    closefile();
		    bp.rxfp = NULL; /* Force SKIP command	*/
		}
	    }

	    if (bp.rsize == bp.rxbytes) {
		/*
		 * We already got this file, send GOT so it will
		 * be deleted at the remote.
		 */
		Syslog('+', "Binkp: already got %s, sending GOT", bp.rname);
		binkp_send_control(MM_GOT, "%s %ld %ld", bp.rname, bp.rsize, bp.rtime);
		bp.RxState = RxWaitFile;
		bp.rxfp = NULL;
	    } else if (!bp.rxfp) {
		/*
		 * Some error, request to skip it
		 */
		Syslog('+', "Binkp: error file %s, sending SKIP", bp.rname);
		binkp_send_control(MM_SKIP, "%s %ld %ld", bp.rname, bp.rsize, bp.rtime);
		bp.RxState = RxWaitFile;
	    } else {
		Syslog('b', "rsize=%d, rxbytes=%d, roffs=%d", bp.rsize, bp.rxbytes, bp.roffs);
		bp.RxState = RxReceData;
	    }
	    break;

	case RxReceData:
	    break;

	case RxEndOfBatch:
	    if (bp.TxState == TxDone)
		bp.RxState = RxDone;
	    break;

	case RxDone:
	    break;
	}
    }

    debug_binkp_list(&bll);

    /*
     *  Process all send files.
     */
    for (tsl = to_send; tsl; tsl = tsl->next) {
	if (tsl->remote == NULL) {
	    execute_disposition(tsl);
	} else {
	    for (tmp = bll; tmp; tmp = tmp->next) {
		if ((strcmp(tmp->local, tsl->local) == 0) && (tmp->state == Got)) {
		    execute_disposition(tsl);
		}
	    }
	}
    }

    for (tmp = bll; bll; bll = tmp) {
	tmp = bll->next;
	if (bll->local)
	    free(bll->local);
	if (bll->remote)
	    free(bll->remote);
	free(bll);
    }

    /*
     * If there was an error, try to close a possible incomplete file in
     * the temp inbound so we can resume the next time we have a session
     * with this node.
     */
    if (bp.rc)
	closefile();

    Syslog('+', "Binkp: batch %d completed rc=%d", bp.batchnr, bp.rc);
    return bp.rc;
}

#endif