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

#include "../config.h"
#include "../lib/mbselib.h"
#include "../lib/users.h"
#include "../lib/nodelist.h"
#include "../lib/mbsedb.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 *bstate[] = {
    (char *)"M_NUL", (char *)"M_ADR", (char *)"M_PWD", (char *)"M_FILE", (char *)"M_OK",
    (char *)"M_EOB", (char *)"M_GOT", (char *)"M_ERR", (char *)"M_BSY", (char *)"M_GET",
    (char *)"M_SKIP"
};



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 {RxWaitF, RxAccF, RxReceD, RxWriteD, RxEOB, RxDone} RxType;
typedef enum {TxGNF, TxTryR, TxReadS, TxWLA, TxDone} TxType;
typedef enum {Ok, Failure, Continue} TrType;
typedef enum {No, WeCan, WeWant, TheyWant, Active} OptionState;
typedef enum {NoState, Sending, IsSent, Got, Skipped, Get} FileState;
typedef enum {InitTransfer, Switch, Receive, Transmit, DeinitTransfer} FtType;
typedef enum {CompNone, CompGZ, CompBZ2, CompPLZ} CompType;

static char *rxstate[] = { (char *)"RxWaitF", (char *)"RxAccF", (char *)"RxReceD", 
			   (char *)"RxWriteD", (char *)"RxEOB", (char *)"RxDone" };
static char *opstate[] = { (char *)"No", (char *)"WeCan", (char *)"WeWant", (char *)"TheyWant", (char *)"Active" };
static char *cpstate[] = { (char *)"No", (char *)"GZ", (char *)"BZ2", (char *)"PLZ" };


static time_t	Timer;
int		ext_rand = 0;


the_queue	    *tql = NULL;		/* The Queue List		    */
file_list	    *tosend = NULL;		/* Files to send		    */
file_list	    *respond = NULL;		/* Requests honored		    */
binkp_list	    *bll = NULL;		/* Files to send with status	    */
static binkp_list   *cursend = NULL;		/* Current file being transmitted   */
struct timeval      txtvstart;			/* Transmitter start time           */
struct timeval      txtvend;			/* Transmitter end time             */
struct timeval      rxtvstart;			/* Receiver start time              */
struct timeval      rxtvend;			/* Receiver end time                */


#if defined(HAVE_ZLIB_H) || defined(HAVE_BZLIB_H)

int compress_init(int type, void **data);
int do_compress(int type, char *dst, int *dst_len, char *src, int *src_len, int finish, void *data);
void compress_deinit(int type, void *data);
void compress_abort(int type, void *data);
int decompress_init(int type, void **data);
int do_decompress(int type, char *dst, int *dst_len, char *src, int *src_len, void *data);
int decompress_deinit(int type, void *data);
int decompress_abort(int type, void *data);

#define ZBLKSIZE        1024    /* read/write file buffer size */
	
#endif


struct binkprec {
    int			Role;			/* 1=orig, 0=answer		    */
    int			RxState;		/* Receiver state		    */
    int			TxState;		/* Transmitter state		    */
    int			FtState;		/* File transfer state		    */
						/* Option flags			    */
    int			CRAMflag;		/* CRAM authentication		    */
						/* Session state		    */
    int			Secure;			/* Secure session		    */
    int			Major;			/* Major protocol version	    */
    int			Minor;			/* Minor protocol version	    */
    unsigned char	*MD_Challenge;		/* Received challenge data	    */
    int			PLZflag;		/* Zlib packet compression	    */
    int			cmpblksize;		/* Ideal next blocksize		    */

						/* Receiver buffer		    */
    char		*rxbuf;			/* Receiver buffer		    */
    int			GotFrame;		/* Frame complete flag		    */
    int			rxlen;			/* Frame length			    */
    int			cmd;			/* Frame command flag		    */
    int			blklen;			/* Frame blocklength		    */
    unsigned short	header;			/* Frame header			    */
    int			rc;			/* General return code		    */
    
    long		rsize;			/* Receiver filesize		    */
    long		roffs;			/* Receiver offset		    */
    char		*rname;			/* Receiver filename		    */
    time_t		rtime;			/* Receiver filetime		    */
    FILE		*rxfp;			/* Receiver file		    */
    off_t		rxbytes;		/* Receiver bytecount		    */
    int			rxpos;			/* Receiver position		    */
    int			rxcompressed;		/* Receiver compressed bytes	    */
    char                *ropts;                 /* Receiver M_FILE optional args    */
    int			rmode;			/* Receiver compression mode	    */

    struct timezone	tz;			/* Timezone			    */
    int			DidSendGET;		/* Receiver send GET status	    */

    char		*txbuf;			/* Transmitter buffer		    */
    int			txlen;			/* Transmitter file length	    */
    FILE		*txfp;			/* Transmitter file		    */
    int			txpos;			/* Transmitter position		    */
    int			stxpos;			/* Transmitter start position	    */
    int			txcompressed;		/* Transmitter compressed bytes	    */
    int			tmode;			/* Transmitter compression mode	    */
    long    		tfsize;			/* Transmitter filesize		    */

    int			local_EOB;		/* Local EOB sent		    */
    int			remote_EOB;		/* Got EOB from remote		    */
    int			messages;		/* Messages sent + rcvd		    */
    unsigned long	nethold;		/* Netmail on hold		    */
    unsigned long	mailhold;		/* Packed mail on hold		    */

    int			batchnr;
    int			msgs_on_queue;		/* Messages on the queue	    */

    int			buggyIrex;		/* Buggy Irex detected		    */

    void		*z_idata;		/* Data for zstream		    */
    void		*z_odata;		/* Data for zstream		    */
    char		*z_obuf;		/* Compression buffer		    */
    int			txcpos;			/* Transmitter compressed position  */
#if defined(HAVE_ZLIB_H) || defined(HAVE_BZLIB2_H)
    int			extcmd;			/* EXTCMD flag			    */
#endif
#ifdef	HAVE_ZLIB_H
    int			GZflag;			/* GZ compression flag		    */
#endif
#ifdef	HAVE_BZLIB_H
    int			BZ2flag;		/* BZ2 compression flag		    */
#endif
};



struct binkprec	bp;				/* Global structure		    */


/*
 * Prototypes
 */
TrType  binkp_receiver(void);                       /* Receiver routine             */
TrType  binkp_transmitter(void);                    /* Transmitter routine          */
int	binkp_send_frame(int, char *, int);	    /* Send cmd/data frame	    */
int	binkp_send_command(int, ...);		    /* Send command frame	    */
void	binkp_settimer(int);			    /* Set timeout timer	    */
int	binkp_expired(void);			    /* Timer expired?		    */
int	binkp_banner(void);			    /* Send system banner	    */
int	binkp_recv_command(char *, unsigned long *, int *);   /* Receive command frame	    */
void	parse_m_nul(char *);			    /* Parse M_NUL message	    */
int	binkp_poll_frame(void);			    /* Poll for a frame		    */
void	binkp_add_message(char *frame);		    /* Add cmd frame to queue	    */
int	binkp_process_messages(void);		    /* Process the queue	    */
int	binkp_resync(off_t);			    /* File resync		    */
char	*unix2binkp(char *);			    /* Binkp -> Unix escape	    */
char	*binkp2unix(char *);			    /* Unix -> Binkp escape	    */
void	fill_binkp_list(binkp_list **, file_list *, off_t); /* Build pending files  */
int	binkp_pendingfiles(void);		    /* Count pending files	    */
void	binkp_clear_filelist(int);		    /* Clear current filelist	    */

static int  orgbinkp(void);			    /* Originate session state	    */
static int  ansbinkp(void);			    /* Answer session state	    */
static int  file_transfer(void);		    /* File transfer state	    */



/************************************************************************************/
/*
 *   General entry point
 */

int binkp(int role)
{
    int	    rc = 0;

    Syslog('+', "Binkp: start session");
    memset(&bp, 0, sizeof(bp));
    bp.Role = role;
    bp.CRAMflag = FALSE;
    bp.Secure = FALSE;
    bp.Major = 1;
    bp.Minor = 0;
    bp.MD_Challenge = NULL;
    bp.rxbuf = calloc(MAX_BLKSIZE + 3, sizeof(unsigned char));
    bp.txbuf = calloc(MAX_BLKSIZE + 3, sizeof(unsigned char));
    bp.rname = calloc(512, sizeof(char));
    bp.ropts = calloc(512, sizeof(char));
    bp.rxfp = NULL;
    bp.DidSendGET = FALSE;
    bp.local_EOB = FALSE;
    bp.remote_EOB = FALSE;
    bp.msgs_on_queue = 0;
    bp.cmpblksize = SND_BLKSIZE;
#ifdef	HAVE_ZLIB_H
    if (localoptions & NOPLZ)
	bp.PLZflag = No;
    else
	bp.PLZflag = WeCan;
    bp.z_obuf = calloc(MAX_BLKSIZE + 3, sizeof(unsigned char));
    if (localoptions & NOGZBZ2)
	bp.GZflag = No;
    else
	bp.GZflag = WeCan;
#else
    bp.PLZflag = No;
#endif
#ifdef	HAVE_BZLIB_H
    if (localoptions & NOGZBZ2)
	bp.BZ2flag = No;
    else
	bp.BZ2flag = WeCan;
#endif
    bp.buggyIrex = FALSE;

    if (role == 1) {
	if (orgbinkp()) {
	    rc = MBERR_SESSION_ERROR;
	}
    } else {
	if (ansbinkp()) {
	    rc = MBERR_SESSION_ERROR;
	}
    }

    if (rc) {
	Syslog('!', "Binkp: session handshake failed");
	goto binkpend;
    }

    if (((Loaded && nodes.NoBinkp11) || bp.buggyIrex) && (bp.Major == 1) && (bp.Minor != 0)) {
	Syslog('+', "Binkp: forcing downgrade to binkp/1.0 protocol");
        bp.Major = 1;
        bp.Minor = 0;
    }

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

    bp.FtState = InitTransfer;
    rc = file_transfer();

binkpend:   
    /*
     * Deinit
     */
    if (bp.rname)
	free(bp.rname);
    if (bp.MD_Challenge)
	free(bp.MD_Challenge);
    if (bp.rxbuf)
	free(bp.rxbuf);
    if (bp.txbuf)
	free(bp.txbuf);
    if (bp.ropts)
	free(bp.ropts);
#if defined(HAVE_ZLIB_H) || defined(HAVE_BZLIB_H)
    if ((bp.rmode != CompNone) && bp.z_idata)
	decompress_deinit(bp.rmode, bp.z_idata);
    if ((bp.tmode != CompNone) && bp.z_odata)
	compress_deinit(bp.tmode, bp.z_odata);
    if (bp.z_obuf)
	free(bp.z_obuf);
#endif
    rc = abs(rc);

    Syslog('+', "Binkp: session finished, rc=%d", rc);
    return rc;
}


/************************************************************************************/
/*
 *   Originate Session Setup
 */

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 = 0, cmd, dupe, SendPass = FALSE, akas = 0;
    unsigned long   bufl;
    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));

    rc = binkp_banner();
    if (rc) {
	SM_ERROR;
    }

    /*
     * 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]));
	}
    }

    rc = binkp_send_command(MM_ADR, "%s", p);
    free(p);
    tidy_faddr(primary);
    if (rc) {
	SM_ERROR;
    }

    SM_PROCEED(WaitAddr)

SM_STATE(WaitAddr)

    for (;;) {
	if ((rc = binkp_recv_command(bp.rxbuf, &bufl, &cmd))) {
	    Syslog('!', "Binkp: error receiving remote info");
	    SM_ERROR;
	}
	if (cmd) {
	    if (bp.rxbuf[0] == MM_ADR) {
		p = xstrcpy(bp.rxbuf + 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: unparsable remote address: \"%s\"", printable(q, 0));
			binkp_send_command(MM_ERR, "Unparsable address \"%s\"", printable(q, 0));
			SM_ERROR;
		    }
		}
		free(p);

		tmp = &remote;
		while (*tmp) {
		    if (nodelock((*tmp)->addr, mypid)) {
			Syslog('+', "address : %s is locked, removed from aka list",ascfnode((*tmp)->addr,0x1f));
			tmpa=*tmp;
			*tmp=(*tmp)->next;
			free(tmpa);
		    } else {
			/*
			 * With the loaded flag we prevent removing the noderecord
			 * when the remote presents us an address we don't know about.
			 */
			akas++;
			Syslog('+', "address : %s",ascfnode((*tmp)->addr,0x1f));
			if (!Loaded) {
			    if (noderecord((*tmp)->addr))
				Loaded = TRUE;
			}
			tmp = &((*tmp)->next);
		    }
		}

		if (akas == 0) {
		    binkp_send_command(MM_BSY, "All aka's busy");
		    Syslog('+', "Binkp: abort, all aka's are busy");
		    SM_ERROR;
		}

		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", printable(remote->addr->domain, 0));

		SM_PROCEED(SendPasswd)
	    } else if (bp.rxbuf[0] == MM_BSY) {
		Syslog('!', "Binkp: M_BSY \"%s\"", printable(bp.rxbuf +1, 0));
		SM_ERROR;
	    } else if (bp.rxbuf[0] == MM_ERR) {
		Syslog('!', "Binkp: M_ERR \"%s\"", printable(bp.rxbuf +1, 0));
		SM_ERROR;
	    } else if (bp.rxbuf[0] == MM_NUL) {
		parse_m_nul(bp.rxbuf +1);
	    } else {
		binkp_send_command(MM_ERR, "Unexpected frame");
		SM_ERROR;
	    }
	} else {
	    binkp_send_command(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 (bp.MD_Challenge) {
	char    *tp = NULL;
	tp = MD_buildDigest(pwd, bp.MD_Challenge);
	if (!tp) {
	    Syslog('!', "Unable to build MD5 digest");
	    binkp_send_command(MM_ERR, "CRAM authentication failed, internal error");
	    SM_ERROR;
	}
	bp.CRAMflag = TRUE;
	rc = binkp_send_command(MM_PWD, "%s", tp);
	free(tp);
    } else {
	rc = binkp_send_command(MM_PWD, "%s", pwd);
    }

    free(pwd);
    if (rc) {
	SM_ERROR;
    }
    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_command(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_command(bp.rxbuf, &bufl, &cmd))) {
	    Syslog('!', "Binkp: error waiting for remote acknowledge");
	    SM_ERROR;
	}

	if (cmd) {
	    if (bp.rxbuf[0] == MM_OK) {
		Syslog('b', "Binkp: M_OK \"%s\"", printable(bp.rxbuf +1, 0));
		if (SendPass)
		    bp.Secure = TRUE;
		Syslog('+', "Binkp: %s%sprotected session", bp.CRAMflag ? "MD5 ":"", bp.Secure ? "":"un");
		SM_PROCEED(Opts)
	    
	    } else if (bp.rxbuf[0] == MM_BSY) {
		Syslog('!', "Binkp: M_BSY \"%s\"", printable(bp.rxbuf +1, 0));
		SM_ERROR;

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

	    } else if (bp.rxbuf[0] == MM_NUL) {
		parse_m_nul(bp.rxbuf +1);

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

SM_STATE(Opts)

    IsDoing("Binkp to %s", ascfnode(remote->addr, 0xf));
    Syslog('b', "Binkp: check for extcmd, current %d", bp.extcmd);
    if (bp.extcmd) {
#ifdef  HAVE_BZLIB_H
	if (bp.BZ2flag == TheyWant) {
	    Syslog('+', "Binkp: BZ2 compression active");
	    bp.BZ2flag = Active;
	}
#endif
#ifdef  HAVE_ZLIB_H
	if (bp.GZflag == TheyWant) {
	    bp.GZflag = Active;
	    Syslog('+', "Binkp: GZ compression active");
	}
#endif
    }
#ifdef	HAVE_ZLIB_H
    if (bp.PLZflag == TheyWant) {
        bp.PLZflag = Active;
        Syslog('+', "Binkp: PLZ compression active");
    }
#endif
    SM_SUCCESS;

SM_END
SM_RETURN



/************************************************************************************/
/*
 *   Answer Session Setup
 */

SM_DECL(ansbinkp, (char *)"ansbinkp")
SM_STATES
    ConnInit,
    WaitConn,
    WaitAddr,
    IsPasswd,
    WaitPwd,
    PwdAck,
    Opts
SM_NAMES
    (char *)"ConnInit",
    (char *)"WaitConn",
    (char *)"WaitAddr",
    (char *)"IsPasswd",
    (char *)"WaitPwd",
    (char *)"PwdAck",
    (char *)"Opts"
SM_EDECL
    char	    *p, *q, *pw;
    int		    i, rc, cmd, dupe, we_have_pwd = FALSE, akas = 0;
    unsigned long   bufl;
    fa_list	    **tmp, *tmpa;
    faddr	    *fa;

SM_START(ConnInit)

SM_STATE(ConnInit)

    SM_PROCEED(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_command(MM_BSY, "This system is closed, try again later");
	SM_ERROR;
    }

    if (!CFG.NoMD5 && ((bp.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 + strlen(s), bp.MD_Challenge[0], bp.MD_Challenge+1);
	bp.CRAMflag = TRUE;
	if ((rc = binkp_send_command(MM_NUL, "%s", s))) {
	    SM_ERROR;
	}
    }

    if ((rc = binkp_banner())) {
	SM_ERROR;
    }

    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]));
	}
    }

    rc = binkp_send_command(MM_ADR, "%s", p);
    free(p);
    if (rc) {
	SM_ERROR;
    }

    SM_PROCEED(WaitAddr)

SM_STATE(WaitAddr)

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

	if (cmd) {
	    if (bp.rxbuf[0] == MM_ADR) {
		p = xstrcpy(bp.rxbuf +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: unparsable remote address: \"%s\"", printable(q, 0));
			binkp_send_command(MM_ERR, "Unparsable address \"%s\"", printable(q, 0));
			SM_ERROR;
		    }
		}

		tmp = &remote;
		while (*tmp) {
		    if (nodelock((*tmp)->addr, mypid)) {
			Syslog('+', "address : %s is locked, removed from aka list",ascfnode((*tmp)->addr,0x1f));
			tmpa=*tmp;
			*tmp=(*tmp)->next;
			free(tmpa);
		    } else {
			/*
			 * With the loaded flag we prevent removing the noderecord
			 * when the remote presents us an address we don't know about.
			 */
			akas++;
			Syslog('+', "address : %s",ascfnode((*tmp)->addr,0x1f));
			if (!Loaded) {
			    if (noderecord((*tmp)->addr))
				Loaded = TRUE;
			}
			tmp = &((*tmp)->next);
		    }
		}

                if (akas == 0) {
		    binkp_send_command(MM_BSY, "All aka's busy");
		    Syslog('+', "Binkp: abort, all aka's are busy");
		    SM_ERROR;
		}

		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);

	        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", printable(remote->addr->domain, 0));

	        SM_PROCEED(IsPasswd)
    
	    } else if (bp.rxbuf[0] == MM_ERR) {
		Syslog('!', "Binkp: M_ERR \"%s\"", printable(bp.rxbuf +1, 0));
		SM_ERROR;

	    } else if (bp.rxbuf[0] == MM_NUL) {
		parse_m_nul(bp.rxbuf +1);

	    } else if (bp.rxbuf[0] <= MM_MAX) {
		binkp_send_command(MM_ERR, "Unexpected frame");
		SM_ERROR;

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

SM_STATE(IsPasswd)

    if (Loaded && strlen(nodes.Spasswd)) {
	we_have_pwd = TRUE;
    }
    SM_PROCEED(WaitPwd)

SM_STATE(WaitPwd)

    for (;;) {
	if ((rc = binkp_recv_command(bp.rxbuf, &bufl, &cmd))) {
	    Syslog('!', "Binkp: error waiting for password");
	    SM_ERROR;
	}

	if (cmd) {
	    if (bp.rxbuf[0] == MM_PWD) {
		SM_PROCEED(PwdAck)
	    } else if (bp.rxbuf[0] == MM_ERR) {
		Syslog('!', "Binkp: M_ERR \"%s\"", printable(bp.rxbuf +1, 0));
		SM_ERROR;
	    } else if (bp.rxbuf[0] == MM_NUL) {
		parse_m_nul(bp.rxbuf +1);
	    } else if (bp.rxbuf[0] <= MM_MAX) {
		binkp_send_command(MM_ERR, "Unexpected frame");
		SM_ERROR;
	    }
	} else {
	    binkp_send_command(MM_ERR, "Unexpected frame");
	    SM_ERROR;
	}
    }

SM_STATE(PwdAck)

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

    if ((strncmp(bp.rxbuf +1, "CRAM-", 5) == 0) && bp.CRAMflag) {
	char    *sp;
	sp = MD_buildDigest(pw, bp.MD_Challenge);
	if (sp != NULL) {
	    if (strcmp(bp.rxbuf +1, sp)) {
		Syslog('+', "Binkp: bad MD5 crypted password");
		binkp_send_command(MM_ERR, "Bad password");
		free(sp);
		sp = NULL;
		free(pw);
		SM_ERROR;
	    } else {
		free(sp);
		sp = NULL;
		if (we_have_pwd)
		    bp.Secure = TRUE;
	    }
	} else {
	    free(pw);
	    Syslog('!', "Binkp: could not build MD5 digest");
	    binkp_send_command(MM_ERR, "*** Internal error ***");
	    SM_ERROR;
	}
    } else if ((strcmp(bp.rxbuf +1, pw) == 0)) {
	if (we_have_pwd)
	    bp.Secure = TRUE;
    } else {
	free(pw);
	Syslog('?', "Binkp: password error: expected \"%s\", got \"%s\"", nodes.Spasswd, bp.rxbuf +1);
	binkp_send_command(MM_ERR, "Bad password");
	SM_ERROR;
    }
    free(pw);
    Syslog('+', "Binkp: %s%sprotected session", bp.CRAMflag ? "MD5 ":"", bp.Secure ? "":"un");
    inbound_open(remote->addr, bp.Secure);
    binkp_send_command(MM_OK, "%ssecure", bp.Secure ? "":"non-");
    SM_PROCEED(Opts)

SM_STATE(Opts)

    IsDoing("Binkp from %s", ascfnode(remote->addr, 0xf));
    Syslog('b', "Binkp: check for extcmd, current %d", bp.extcmd);
    if (bp.extcmd) {

#ifdef  HAVE_BZLIB_H
	if (bp.BZ2flag == TheyWant) {
	    Syslog('+', "Binkp: BZ2 compression active");
	    bp.BZ2flag = Active;
	}
#endif
#ifdef	HAVE_ZLIB_H
	if (bp.GZflag == TheyWant) {
	    bp.GZflag = Active;
	    Syslog('+', "Binkp: GZ compression active");
	}
#endif
    } else {
#ifdef	HAVE_ZLIB_H
	bp.GZflag = No;
#endif
#ifdef	HAVE_BZLIB_H
	bp.BZ2flag = No;
#endif
    }
#ifdef	HAVE_ZLIB_H
    if (bp.PLZflag == TheyWant) {
	bp.PLZflag = Active;
	Syslog('+', "Binkp: PLZ compression active");
    }
#endif
    SM_SUCCESS;

SM_END
SM_RETURN



/************************************************************************************/
/*
 *   File Transfer State
 */

/*
 * We do not use the normal state machine because that produces a lot
 * of debug logging that will drive up the CPU usage.
 */
int file_transfer(void)
{
    int		rc = 0;
    TrType	Trc = Ok;
    
    for (;;) {
	switch (bp.FtState) {
	    case InitTransfer:	binkp_settimer(BINKP_TIMEOUT);
				bp.RxState = RxWaitF;
				bp.TxState = TxGNF;
				bp.FtState = Switch;
				bp.messages = 0;
				break;

	    case Switch:	if ((bp.RxState == RxDone) && (bp.TxState == TxDone)) {
				    bp.FtState = DeinitTransfer;
				    break;
				}

				rc = binkp_poll_frame();
				if (rc == -1) {
				    Syslog('b', "Binkp: receiver error detected");
				    bp.rc = rc = MBERR_FTRANSFER;
				    bp.FtState = DeinitTransfer;
				    break;
				} else if (rc == 1) {
				    bp.FtState = Receive;
				    break;
				}

				/*
				 * Check if there is room in the output buffer
				 */
				if (WAITPUTGET(-1) & 2) {
				    bp.FtState = Transmit;
				    break;
				}

				if (binkp_expired()) {
				    Syslog('+', "Binkp: transfer timeout");
				    bp.rc = 1;
				    bp.FtState = DeinitTransfer;
				    break;
				}

				/*
				 * Nothing done, release
				 */
				msleep(1);
				break;

	    case Receive:	Trc = binkp_receiver();
				if (Trc == Ok) {
				    if (bp.local_EOB && bp.remote_EOB)
					bp.FtState = Transmit;
				    else
					bp.FtState = Switch;
				} else if (Trc == Failure) {
				    Syslog('+', "Binkp: receiver failure");
				    bp.rc = 1;
				    bp.FtState = DeinitTransfer;
				}
				break;

	    case Transmit:	Trc = binkp_transmitter();
				if (Trc == Ok) {
				    bp.FtState = Switch;
				} else if (Trc == Failure) {
				    Syslog('+', "Binkp: transmitter failure");
				    bp.rc = 1;
				    bp.FtState = DeinitTransfer;
				}
				break;

	    case DeinitTransfer:/*
				 * In case of a transfer error the filelist is not yet cleared
				 */
				binkp_clear_filelist(bp.rc);
				if (bp.rc)
				    return MBERR_FTRANSFER;
				else
				    return 0;
				break;
	}
    }
}



/************************************************************************************/
/*
 *  Receiver routine
 */

TrType binkp_receiver(void)
{
    int		    bcmd, rc = 0;
    struct statfs   sfs;
    long	    written;
    off_t	    rxbytes;

    if (bp.RxState == RxWaitF) {

	if (! bp.GotFrame)
	    return Ok;

	bp.GotFrame = FALSE;
	bp.rxlen = 0;
	bp.header = 0;

        if (! bp.cmd) {
            Syslog('b', "Binkp: got %d bytes DATA frame in %s state, ignored", bp.blklen, rxstate[bp.RxState]);
	    bp.blklen = 0;
            return Ok;
        }
	bp.blklen = 0;

        bcmd = bp.rxbuf[0];
        if (bcmd == MM_ERR) {
            Syslog('+', "Binkp: got M_ERR \"%s\"", printable(bp.rxbuf +1, 0));
            bp.RxState = RxDone;
            return Failure;
        } else if ((bcmd == MM_GET) || (bcmd == MM_GOT) || (bcmd == MM_SKIP)) {
            binkp_add_message(bp.rxbuf);
            return Ok;
        } else if (bcmd == MM_NUL) {
            parse_m_nul(bp.rxbuf +1);
            return Ok;
        } else if (bcmd == MM_EOB) {
	    if ((bp.Major == 1) && (bp.Minor != 0)) {
		if (bp.local_EOB && bp.remote_EOB) {
		    Syslog('b', "Binkp: receiver detects both sides in EOB state");
		    if ((bp.messages < 3) || binkp_pendingfiles()) {
			Syslog('b', "Binkp: receiver detected end of session or pendingfiles, stay in RxWaitF");
			return Ok;
		    } else {
			bp.batchnr++;
			bp.local_EOB = FALSE;
			bp.remote_EOB = FALSE;
			bp.messages = 0;
			bp.TxState = TxGNF;
			bp.RxState = RxWaitF;
			Syslog('+', "Binkp: receiver starts batch %d", bp.batchnr + 1);
			binkp_clear_filelist(0);
			return Ok;
		    }
		} else {
		    return Ok;
		}
	    } else {
		Syslog('b', "Binkp/1.0 mode and got M_EOB, goto RxEOB");
		bp.RxState = RxEOB;
		return Ok;
	    }
        } else if (bcmd == MM_FILE) {
            bp.RxState = RxAccF;
            return Continue;
        } else if (bcmd <= MM_MAX) {
            Syslog('+', "Binkp: ERROR got unexpected %s frame in %s state", bstate[bcmd], rxstate[bp.RxState]);
            bp.RxState = RxDone;
            return Failure;
        } else {
            Syslog('+', "Binkp: got unexpected unknown frame, ignore");
            return Ok;
        }
    } else if (bp.RxState == RxAccF) {
	
	if (((bp.rmode == CompGZ) || (bp.rmode == CompBZ2)) && bp.z_idata) {
	    decompress_deinit(bp.rmode, bp.z_idata);
	    bp.z_idata = NULL;
	}

	bp.rmode = CompNone;
	if (strlen(bp.rxbuf) < 512) {
	    sprintf(bp.rname, "%s", strtok(bp.rxbuf+1, " \n\r"));
	    bp.rsize = atoi(strtok(NULL, " \n\r"));
	    bp.rtime = atoi(strtok(NULL, " \n\r"));
	    bp.roffs = atoi(strtok(NULL, " \n\r"));
	    Syslog('b', "Binkp: b4 critical point");
	    sprintf(bp.ropts, "%s", printable(strtok(NULL, " \n\r"), 0));
	    if (strcmp((char *)"GZ", bp.ropts) == 0)
		bp.rmode = CompGZ;
	    else if (strcmp((char *)"BZ2", bp.ropts) == 0)
		bp.rmode = CompBZ2;
	    Syslog('b', "Binkp: compress check %s", bp.ropts);
	} else {
	    /*
	     * Corrupted command, in case this was serious, send the M_GOT back so it's
	     * deleted at the remote.
	     */
	    Syslog('+', "Binkp: got corrupted FILE frame, size %d bytes", strlen(bp.rxbuf));
	    bp.RxState = RxWaitF;
	    rc = binkp_send_command(MM_GOT, bp.rxbuf +1);
	    if (rc)
		return Failure;
	    else
		return Ok;
	}
	Syslog('+', "Binkp: receive file \"%s\" date %s size %ld offset %ld comp %s", 
		bp.rname, date(bp.rtime), bp.rsize, bp.roffs, cpstate[bp.rmode]);
	(void)binkp2unix(bp.rname);
	rxbytes = bp.rxbytes;
	bp.rxfp = openfile(binkp2unix(bp.rname), bp.rtime, bp.rsize, &rxbytes, binkp_resync);
	bp.rxbytes = rxbytes;
	bp.rxcompressed = 0;

	if (bp.DidSendGET) {
	    Syslog('b', "Binkp: DidSendGET is set");
	    /*
	     * 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 = RxWaitF;
	    return Ok;
	}

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

	if (enoughspace(CFG.freespace) == 0) {
	    Syslog('+', "Binkp: low diskspace, sending BSY");
	    binkp_send_command(MM_BSY, "Low diskspace, try again later");
	    bp.RxState = RxDone;
	    bp.TxState = TxDone;
	    bp.rc = MBERR_FTRANSFER;
	    return Failure;
	}

	if (statfs(tempinbound, &sfs) == 0) {
	    if ((bp.rsize / (sfs.f_bsize + 1)) >= sfs.f_bfree) {
		Syslog('!', "Binkp: only %lu blocks free (need %lu) in %s for this file", 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);
	    rc = binkp_send_command(MM_GOT, "%s %ld %ld", bp.rname, bp.rsize, bp.rtime);
	    bp.RxState = RxWaitF;
	    bp.rxfp = NULL;
	    if (rc)
		return Failure;
	    else
		return Ok;
	} else if (!bp.rxfp) {
	    /*
	     * Some error, request to skip it
	     */
	    Syslog('+', "Binkp: error file %s, sending SKIP", bp.rname);
	    rc = binkp_send_command(MM_SKIP, "%s %ld %ld", bp.rname, bp.rsize, bp.rtime);
	    bp.RxState = RxWaitF;
	    if (rc)
		return Failure;
	    else
		return Ok;
	} else {
	    Syslog('b', "rsize=%d, rxbytes=%d, roffs=%d", bp.rsize, bp.rxbytes, bp.roffs);
	    bp.RxState = RxReceD;
	    return Ok;
	}

    } else if (bp.RxState == RxReceD) {

	if (! bp.GotFrame)
	    return Ok;

	if (! bp.cmd) {
	    bp.RxState = RxWriteD;
	    return Continue;
	}

	bp.GotFrame = FALSE;
	bp.rxlen = 0;
	bp.header = 0;
	bp.blklen = 0;

	bcmd = bp.rxbuf[0];
        if (bcmd == MM_ERR) {
	    Syslog('+', "Binkp: got M_ERR \"%s\"", printable(bp.rxbuf +1, 0));
	    bp.RxState = RxDone;
	    return Failure;
	} else if ((bcmd == MM_GET) || (bcmd == MM_GOT) || (bcmd == MM_SKIP)) {
	    binkp_add_message(bp.rxbuf);
	    return Ok;
	} else if (bcmd == MM_NUL) {
	    parse_m_nul(bp.rxbuf +1);
	    return Ok;
	} else if (bcmd == MM_FILE) {
	    Syslog('+', "Binkp: partial received file, saving");
	    closefile();
	    bp.rxfp = NULL;
	    bp.RxState = RxAccF;
	    return Continue;
	} else if (bcmd <= MM_MAX) {
	    Syslog('+', "Binkp: ERROR got unexpected %s frame in %s state", bstate[bcmd], rxstate[bp.RxState]);
	    bp.RxState = RxDone;
	    return Failure;
	} else {
	    Syslog('+', "Binkp: got unexpected unknown frame, ignore");
	    return Ok;
	}
    } else if (bp.RxState == RxWriteD) {

	if (bp.rxfp == NULL) {
	    /*
	     * After sending M_GET for restart at offset, the file is closed
	     * but some data frames may be underway, so ignore these.
	     */
	    Syslog('b', "Binkp: file is closed, ignore data");
	    bp.GotFrame = FALSE;
	    bp.rxlen = 0;
	    bp.header = 0;
	    bp.RxState = RxReceD;
	    return Ok;
	}

	written = 0;
        if ((bp.rmode == CompBZ2) || (bp.rmode == CompGZ)) {
	    /*
	     * Receive stream compressed data
	     */
	    int rc1 = 0, nget = bp.blklen, zavail, nput;
	    char zbuf[ZBLKSIZE];
	    char *buf = bp.rxbuf;

	    if (bp.z_idata == NULL) {
		if (decompress_init(bp.rmode, &bp.z_idata)) {
		    Syslog('+', "Binkp: can't init decompress");
		    bp.RxState = RxDone;
		    return Failure;
		} else
		    Syslog('b', "Binkp: decompress_init success");
	    }
	    while (nget) {
		zavail = ZBLKSIZE;
		nput = nget;
		rc1 = do_decompress(bp.rmode, zbuf, &zavail, buf, &nput, bp.z_idata);
		if (rc1 < 0) {
		    Syslog('+', "Binkp: decompress %s error %d", bp.rname, rc1);
		    bp.RxState = RxDone;
		    return Failure;
		} else {
//		    Syslog('b', "Binkp: %d bytes of data decompressed to %d", nput, zavail);
		}
		if (zavail != 0 && fwrite(zbuf, zavail, 1, bp.rxfp) < 1) {
		    Syslog('+', "$Binkp: write error");
		    decompress_abort(bp.rmode, bp.z_idata);
		    bp.z_idata = NULL;
		    bp.RxState = RxDone;
		    return Failure;
		}
		buf += nput;
		nget -= nput;
		written += zavail;
		bp.rxcompressed += zavail - nput;
	    }
	    bp.blklen = written;    /* Correct physical to virtual blocklength */
//	    Syslog('b', "Binkp: set bp.blklen %d rc=%d", written, rc1);
	    if (rc1 == 1) {
		if ((rc1 = decompress_deinit(bp.rmode, bp.z_idata)) < 0)
		    Syslog('+', "Binkp: decompress_deinit retcode %d", rc1);
		else
		    Syslog('b', "Binkp: decompress_deinit done");
		bp.z_idata = NULL;
	    }
	} else {
	    written = fwrite(bp.rxbuf, 1, bp.blklen, bp.rxfp);
	}

	bp.GotFrame = FALSE;
	bp.rxlen = 0;
	bp.header = 0;
	
	if (bp.blklen && (bp.blklen != written)) {
	    Syslog('+', "Binkp: ERROR file write error");
	    bp.RxState = RxDone;
	    bp.blklen = 0;
	    return Failure;
	}
	bp.blklen = 0;
	bp.rxpos += written;

	if (bp.rxpos == bp.rsize) {
	    rc = binkp_send_command(MM_GOT, "%s %ld %ld", bp.rname, bp.rsize, bp.rtime);
	    closefile();
	    bp.rxpos = bp.rxpos - bp.rxbytes;
	    gettimeofday(&rxtvend, &bp.tz);
#if defined(HAVE_ZLIB_H) || defined(HAVE_BZLIB_H)
	    if (bp.rxcompressed)
		Syslog('+', "Binkp: %s", compress_stat(bp.rxpos, bp.rxcompressed));
#endif
	    Syslog('+', "Binkp: OK %s", transfertime(rxtvstart, rxtvend, bp.rxpos, FALSE));
	    rcvdbytes += bp.rxpos;
	    bp.RxState = RxWaitF;
	    if (rc)
		return Failure;
	    else
		return Ok;
	}
	bp.RxState = RxReceD;
	return Ok;
    } else if (bp.RxState == RxEOB) {

	if (! bp.GotFrame)
	    return Ok;

	bp.GotFrame = FALSE;
	bp.rxlen = 0;
	bp.header = 0;
	bp.blklen = 0;

	if (bp.cmd) {
	    bcmd = bp.rxbuf[0];
	    if (bcmd == MM_ERR) {
		Syslog('+', "Binkp: got M_ERR \"%s\"", printable(bp.rxbuf +1, 0));
		bp.RxState = RxDone;
		return Failure;
	    } else if ((bcmd == MM_GET) || (bcmd == MM_GOT) || (bcmd == MM_SKIP)) {
		binkp_add_message(bp.rxbuf);
		return Ok;
	    } else if (bcmd == MM_NUL) {
		parse_m_nul(bp.rxbuf +1);
		return Ok;
	    } else if (bcmd <= MM_MAX) {
		Syslog('+', "Binkp: ERROR got unexpected %s frame in %s state", bstate[bcmd], rxstate[bp.RxState]);
		bp.RxState = RxDone;
		return Failure;
	    } else {
		Syslog('+', "Binkp: got unexpected unknown frame, ignore");
		return Ok;
	    }
	} else {
	    Syslog('+', "Binkp: ERROR got unexpected data frame in %s state", rxstate[bp.RxState]);
	    bp.RxState = RxDone;
	    return Failure;
	}

    } else if (bp.RxState == RxDone) {
	return Ok;
    }

    /*
     * Cannot be here
     */
    bp.RxState = RxDone;
    return Failure;
}


/************************************************************************************/
/*
 *  Transmitter routine
 */

TrType binkp_transmitter(void)
{
    int		rc = 0, eof = FALSE, sz, rc1 = 0;
    char	*nonhold_mail, *extra;
    fa_list	*eff_remote;
    file_list	*tsl;
    static binkp_list	*tmp;

    if (bp.TxState == TxGNF) {
	/*
	 * If we do not have a filelist yet, create one.
	 */
	if (tosend == NULL) {
	    Syslog('b', "Creating filelist");
	    nonhold_mail = (char *)ALL_MAIL;
	    bp.nethold = bp.mailhold = 0L;
	    cursend = NULL;

	    /*
	     * If remote doesn't have the 8.3 flag set, allow long names
	     */
	    if (!nodes.FNC)
		remote_flags &= ~SESSION_FNC;

	    eff_remote = remote;
	    tosend = create_filelist(eff_remote, nonhold_mail, 0);
	    if ((respond = respond_wazoo()) != NULL) {
		for (tsl = tosend; tsl->next; tsl = tsl ->next);
		tsl->next = respond;
		Syslog('b', "Binkp: added requested files");
	    }

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

	    if ((bp.nethold || bp.mailhold) || (bp.batchnr == 0)) {
		Syslog('+', "Binkp: mail %ld, files %ld bytes", bp.nethold, bp.mailhold);
		if ((rc = binkp_send_command(MM_NUL, "TRF %ld %ld", bp.nethold, bp.mailhold))) {
		    bp.TxState = TxDone;
		    return Failure;
		}
	    }
	}
	
	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.tmode = CompNone;
		extra = (char *)"";
		
		if ((tmp->compress == CompGZ) || (tmp->compress == CompBZ2)) {
		    bp.tmode = tmp->compress;
		    if ((rc1 = compress_init(bp.tmode, &bp.z_odata))) {
			Syslog('+', "Binkp: compress_init failed (rc=%d)", rc1);
			tmp->compress = CompNone;
			bp.tmode = CompNone;
		    } else {
			if (bp.tmode == CompBZ2)
			    extra = (char *)" BZ2";
			else if (bp.tmode == CompGZ)
			    extra = (char *)" GZ";
			Syslog('b', "Binkp: compress_init ok, extra=%s", extra);
		    }
		}

		bp.txpos = bp.txcpos = bp.stxpos = tmp->offset;
		bp.txcompressed = 0;
		bp.tfsize = tmp->size;
		Syslog('+', "Binkp: send \"%s\" as \"%s\"", MBSE_SS(tmp->local), MBSE_SS(tmp->remote));
		Syslog('+', "Binkp: size %lu bytes, dated %s, comp %s", 
			(unsigned long)tmp->size, date(tmp->date), cpstate[bp.tmode]);
		rc = binkp_send_command(MM_FILE, "%s %lu %ld %ld%s", MBSE_SS(tmp->remote), 
			(unsigned long)tmp->size, (long)tmp->date, (unsigned long)tmp->offset, extra);
		if (rc) {
		    bp.TxState = TxDone;
		    return Failure;
		}
		gettimeofday(&txtvstart, &bp.tz);
		tmp->state = Sending;
		cursend = tmp;
		bp.TxState = TxTryR;
		return Continue;
	    } /* if state == NoState */
	} /* for */

	if (tmp == NULL) {
	    /*
	     * No more files
	     */
	    rc = binkp_send_command(MM_EOB, "");
	    bp.TxState = TxWLA;
	    if (rc)
		return Failure;
	    else
		return Continue;
	}
	return Ok;

    } else if (bp.TxState == TxTryR) {

	if (bp.msgs_on_queue == 0) {
	    bp.TxState = TxReadS;
	    return Continue;
	} else {
	    if (binkp_process_messages()) {
		bp.TxState = TxDone;
		return Failure;
	    } else 
		return Continue;
	}

    } else if (bp.TxState == TxReadS) {

	bp.TxState = TxTryR;

#if defined(HAVE_ZLIB_H) || defined(HAVE_BZLIB_H)
	if ((bp.tmode == CompBZ2) || (bp.tmode == CompGZ)) {
	    int nput = 0;  /* number of compressed bytes */
	    int nget = 0;  /* number of read uncompressed bytes from buffer */
	    int ocnt;      /* number of bytes compressed by one call */
	    int rc2;
	    off_t fleft;

	    sz = bp.tfsize - ftell(bp.txfp);
	    if (bp.cmpblksize < sz)
		sz = bp.cmpblksize;
//	    Syslog('b', "Binkp: sz=%d", sz);
	    while (TRUE) {
		ocnt = bp.cmpblksize - nput;
		nget = sz;
		if (nget > ZBLKSIZE)
		    nget = ZBLKSIZE;
		fleft = bp.tfsize - ftell(bp.txfp);
		fseek(bp.txfp, bp.txpos, SEEK_SET);
		nget = fread(bp.z_obuf, 1, nget, bp.txfp);
//		Syslog('b', "Binkp: fread pos=%d nget=%d ocnt=%d", bp.txpos, nget, ocnt);
		rc2 = do_compress(bp.tmode, bp.txbuf + nput, &ocnt, bp.z_obuf, &nget, fleft ? 0 : 1, bp.z_odata);
		Syslog('b', "Binkp: do_compress ocnt=%d nget=%d fleft=%d rc=%d", ocnt, nget, fleft, rc2);
		if (rc2 == -1) {
		    Syslog('+', "Binkp: compression error rc=%d", rc2);
		    return Failure;
		}
		bp.txpos += nget;
		bp.txcpos += ocnt;
		nput += ocnt;
//		Syslog('b', "Binkp: txpos=%d txcpos=%d nput=%d", bp.txpos, bp.txcpos, nput);
    
		/*
		 * Compressed block is filled for transmission
		 */
		if ((nput == bp.cmpblksize) || (fleft == 0)) {
		    bp.txlen = nput;
		    rc = binkp_send_frame(FALSE, bp.txbuf, bp.txlen);
		    if (rc)
			return Failure;
		    sentbytes += bp.txlen;
		    
		    if (rc2 == 1) {
			/*
			 * Last compressed block is sent, set eof.
			 */
			eof = TRUE;
			bp.txcompressed = bp.tfsize - bp.txcpos;
		    }
		    break;
		}
	    }

	} else {
#endif
	    /*
	     * Send uncompressed block
	     */
	    fseek(bp.txfp, bp.txpos, SEEK_SET);
	    bp.txlen = fread(bp.txbuf, 1, bp.cmpblksize, bp.txfp);
	    eof = feof(bp.txfp);
	    if (ferror(bp.txfp)) {
		WriteError("$Binkp: error reading from file");
		bp.TxState = TxDone;
		cursend->state = Skipped;
		return Failure;
	    }
	    if (bp.txlen) {
		bp.txpos += bp.txlen;
		sentbytes += bp.txlen;
		rc = binkp_send_frame(FALSE, bp.txbuf, bp.txlen);
		if (rc)
		    return Failure;
	    }

#if defined(HAVE_ZLIB_H) || defined(HAVE_BZLIB_H)
	}
#endif

	if ((bp.txlen == 0) || eof) {

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

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

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

	    if ((bp.tmode == CompBZ2) || (bp.tmode == CompGZ)) {
		compress_deinit(bp.tmode, bp.z_odata);
		bp.z_odata = NULL;
		bp.tmode = CompNone;
		Syslog('b', "Binkp: compress_deinit done");
	    }
	    
	    cursend->state = IsSent;
	    cursend = NULL;
	    bp.TxState = TxGNF;
	    return Ok;
	} 
		
	return Ok;

    } else if (bp.TxState == TxWLA) {

	if ((bp.msgs_on_queue == 0) && (binkp_pendingfiles() == 0)) {

	    if ((bp.RxState >= RxEOB) && (bp.Major == 1) && (bp.Minor == 0)) {
		bp.TxState = TxDone;
		if (bp.local_EOB && bp.remote_EOB) {
		    Syslog('b', "Binkp: binkp/1.0 session seems complete");
		    bp.RxState = RxDone;
		}

		binkp_clear_filelist(rc);
		return Ok;
	    }

	    if ((bp.RxState < RxEOB) && (bp.Major == 1) && (bp.Minor == 0)) {
		bp.TxState = TxWLA;
		return Ok;
	    }

	    if ((bp.Major == 1) && (bp.Minor != 0)) {
		if (bp.local_EOB && bp.remote_EOB) {
		    /*
		     * We did send EOB and got a EOB
		     */
		    if (bp.messages < 3) {
			/*
			 * Nothing sent anymore, finish
			 */
			Syslog('b', "Binkp: binkp/1.1 session seems complete");
			bp.RxState = RxDone;
			bp.TxState = TxDone;
		    } else {
			/*
			 * Start new batch
			 */
			bp.batchnr++;
			bp.local_EOB = FALSE;
			bp.remote_EOB = FALSE;
			bp.messages = 0;
			bp.TxState = TxGNF;
			bp.RxState = RxWaitF;
			Syslog('+', "Binkp: transmitter starts batch %d", bp.batchnr + 1);
			binkp_clear_filelist(rc);
			return Ok; /* Continue is not good here, troubles with binkd on slow links. */
		    }
		}

		binkp_clear_filelist(rc);
		return Ok;
	    }
	}

	if (bp.msgs_on_queue) {
	    if (binkp_process_messages()) {
		return Failure;
	    } else {
		return Continue;
	    }
	}
	return Ok;

    } else if (bp.TxState == TxDone) {
	return Ok;
    }

    /*
     * Cannot be here
     */
    bp.TxState = TxDone;
    return Failure;
}


/************************************************************************************/
/*
 *   Functions
 */


/*
 * Send a binkp frame
 */
int binkp_send_frame(int cmd, char *buf, int len)
{
    unsigned short  header = 0;
    int		    rc, id;
#ifdef HAVE_ZLIB_H
    int		    rcz, last;
    unsigned long   zlen;
    char	    *zbuf;

    if ((len >= BINKP_PLZ_BLOCK) && (bp.PLZflag == Active)) {
	WriteError("Can't send block of %d bytes in PLZ mode", len);
	return 1;
    }
#endif

    if (cmd) {
	header = ((BINKP_CONTROL_BLOCK + len) & 0xffff);
	bp.messages++;
	id = buf[0];
	if (id == MM_EOB) {
	    bp.local_EOB = TRUE;
	}
	if (len == 1)
	    Syslog('b', "Binkp: send %s", bstate[id]);
	else
	    Syslog('b', "Binkp: send %s %s", bstate[id], printable(buf +1, len -1));
    } else {
	header = ((BINKP_DATA_BLOCK + len) & 0xffff);
	Syslog('b', "Binkp: send data (%d)", len);
    }

#ifdef HAVE_ZLIB_H
    last = bp.cmpblksize;
    /*
     * Only use compression for DATA blocks larger then 20 bytes.
     * Also, don't send PLZ blocks if GZ or BZ2 mode is active.
     * This means if the current file is send uncompressed we still
     * might send some blocks compressed. The receiver needs to deal
     * with this if it showed PLZ, GZ and BZ2 options.
     */
    if ((bp.PLZflag == Active) && (len > 20) && (!cmd) && (bp.tmode != CompGZ) && (bp.tmode != CompBZ2)) {
	zbuf = calloc(BINKP_ZIPBUFLEN, sizeof(char));
	zlen = BINKP_PLZ_BLOCK -1;
	rcz = compress2(zbuf, &zlen, buf, len, 9);
	if (rcz == Z_OK) {
	    Syslog('b', "Binkp: compressed OK, srclen=%d, destlen=%d, will send compressed=%s", 
		    len, zlen, (zlen < len) ?"yes":"no");
	    if (zlen < len) {
		bp.txcompressed += (len - zlen);
		/*
		 * Calculate the perfect blocksize for the next block
		 * using the current compression ratio. This gives
		 * a dynamic optimal blocksize. The average maximum
		 * blocksize on the line will be 4096 bytes.
		 */
		bp.cmpblksize = ((len * 4) / zlen) * 512;
		if (bp.cmpblksize < SND_BLKSIZE)
		    bp.cmpblksize = SND_BLKSIZE;
		if (bp.cmpblksize > (BINKP_PLZ_BLOCK -1))
		    bp.cmpblksize = (BINKP_PLZ_BLOCK -1);

		/*
		 * Rebuild header for compressed block
		 */
		header = ((BINKP_DATA_BLOCK + BINKP_PLZ_BLOCK + zlen) & 0xffff);
		rc = PUTCHAR((header >> 8) & 0x00ff);
		if (!rc)
		    rc = PUTCHAR(header & 0x00ff);
		if (zlen && !rc)
		    rc = PUT(zbuf, zlen);
	    } else {
		rc = PUTCHAR((header >> 8) & 0x00ff);
		if (!rc)
		    rc = PUTCHAR(header & 0x00ff);
		if (len && !rc)
		    rc = PUT(buf, len);
		if (!cmd)
		    bp.cmpblksize = SND_BLKSIZE;
	    }
	} else {
	    Syslog('+', "Binkp: compress error %d", rcz);
	    rc = PUTCHAR((header >> 8) & 0x00ff);
	    if (!rc)
		rc = PUTCHAR(header & 0x00ff);
	    if (len && !rc)
		rc = PUT(buf, len);
	    if (!cmd)
		bp.cmpblksize = SND_BLKSIZE;
	}
	free(zbuf);
    } else {
	rc = PUTCHAR((header >> 8) & 0x00ff);
	if (!rc)
	    rc = PUTCHAR(header & 0x00ff);
	if (len && !rc)
	    rc = PUT(buf, len);
	if (!cmd)
	    bp.cmpblksize = SND_BLKSIZE;
    }
#else
    rc = PUTCHAR((header >> 8) & 0x00ff);
    if (!rc)
	rc = PUTCHAR(header & 0x00ff);
    if (len && !rc)
	rc = PUT(buf, len);
    bp.cmpblksize = SND_BLKSIZE;
#endif

    FLUSHOUT();
    binkp_settimer(BINKP_TIMEOUT);
    Nopper();
    return rc;
}



/*
 * Send a command message
 */
int binkp_send_command(int id, ...)
{
    va_list     args;
    char        *fmt;
    static char buf[1024];
    int         sz, rc;

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

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

    memmove(buf+1, buf, sz);
    buf[0] = id & 0xff;
    sz++;

    rc = binkp_send_frame(TRUE, buf, sz);
    va_end(args);
    return rc;
}



/*
 * Set binkp master timer
 */
void binkp_settimer(int interval)
{
    Timer = time((time_t*)NULL) + interval;
}



/*
 * Test if master timer is expired
 */
int binkp_expired(void)
{
    time_t      now;

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



/*
 * Send system info to remote
 */
int binkp_banner(void)
{
    time_t  t;
    int	    rc;
    char    *p;

    rc = binkp_send_command(MM_NUL,"SYS %s", CFG.bbs_name);
    if (!rc)
	rc = binkp_send_command(MM_NUL,"ZYZ %s", CFG.sysop_name);
    if (!rc)
	rc = binkp_send_command(MM_NUL,"LOC %s", CFG.location);
    if (!rc)
	rc = binkp_send_command(MM_NUL,"NDL %s", CFG.IP_Flags);
    t = time(NULL);
    if (!rc)
	rc = binkp_send_command(MM_NUL,"TIME %s", rfcdate(t));
    if (!rc) {
	if (nodes.NoBinkp11 || bp.buggyIrex)
	    rc = binkp_send_command(MM_NUL,"VER mbcico/%s/%s-%s %s/%s", VERSION, OsName(), OsCPU(), PRTCLNAME, PRTCLOLD);
	else
	    rc = binkp_send_command(MM_NUL,"VER mbcico/%s/%s-%s %s/%s", VERSION, OsName(), OsCPU(), PRTCLNAME, PRTCLVER);
    }
    if (strlen(CFG.IP_Phone) && !rc)
	rc = binkp_send_command(MM_NUL,"PHN %s", CFG.IP_Phone);
    if (strlen(CFG.comment) && !rc)
	rc = binkp_send_command(MM_NUL,"OPM %s", CFG.comment);

    /*
     * Send extra options
     */
    if (!rc) {
	if (localoptions & NOGZBZ2)
	    p = xstrcpy((char *)"OPT");
	else
	    p = xstrcpy((char *)"OPT EXTCMD");
#ifdef  HAVE_BZLIB_H
	if (! (localoptions & NOGZBZ2))
	    p = xstrcat(p, (char *)" BZ2");
#endif
#ifdef	HAVE_ZLIB_H
	if (! (localoptions & NOPLZ))
	    p = xstrcat(p, (char *)" PLZ");
	if (! (localoptions & NOGZBZ2))
	    p = xstrcat(p, (char *)" GZ");
#endif
	if (strcmp(p, "OPT"))
	    rc = binkp_send_command(MM_NUL,"%s", p);
	free(p);
    }

    return rc;
}



/*
 *  Receive command frame
 */
int binkp_recv_command(char *buf, unsigned long *len, int *cmd)
{
    int	    b0, b1;

    *len = *cmd = 0;

    b0 = GETCHAR(BINKP_TIMEOUT);
    if (tty_status) {
	Syslog('-', "Binkp: tty_status with b0");
	goto to;
    }
    if (b0 & 0x80)
	*cmd = 1;

    b1 = GETCHAR(BINKP_TIMEOUT / 2);
    if (tty_status) {
	Syslog('-', "Binkp: tty_status with b1");
	goto to;
    }

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

    GET(buf, *len, BINKP_TIMEOUT / 2);
    buf[*len] = '\0';
    if (tty_status) {
	Syslog('-', "Binkp: tty_status with block len=%d", *len);
	goto to;
    }
    binkp_settimer(BINKP_TIMEOUT);
    Nopper();

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



/*
 * Parse a received M_NUL message
 */
void parse_m_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);
	}
	/*
	 * Irex 2.24 upto 2.29 claims binkp/1.1 while it is binkp/1.0
	 * Set a flag so we can activate a workaround. This only works
	 * for incoming sessions.
	 */
	if ((p = strstr(msg+4, "Internet Rex 2."))) {
	    q = strtok(p + 15, (char *)" \0");
	    if ((atoi(q) >= 24) && (atoi(q) <= 29)) {
		Syslog('b', "        : Irex bug detected, workaround activated");
		bp.buggyIrex = TRUE;
	    }
	}
    } 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);
	p = msg;
	q = strtok(p, " \n\r\0");
	while ((q = strtok(NULL, " \r\n\0"))) {
	    if (strncmp(q, (char *)"CRAM-MD5-", 9) == 0) { /* No SHA-1 support */
		if (CFG.NoMD5) {
		    Syslog('+', "Binkp: Remote supports MD5, but it's turned off here");
		} else {
		    if (bp.MD_Challenge)
			free(bp.MD_Challenge);
		    bp.MD_Challenge = MD_getChallenge(q, NULL);
		}
	    } else if (strncmp(q, (char *)"EXTCMD", 6) == 0) {
		bp.extcmd = TRUE;
		Syslog('b', "Binkp: remote supports EXTCMD mode");

#ifdef	HAVE_BZLIB_H
	    } else if (strncmp(q, (char *)"BZ2", 3) == 0) {
		if (bp.BZ2flag == WeCan) {
		    bp.BZ2flag = TheyWant;
		    Syslog('b', "Binkp: remote supports BZ2 mode");
		} else {
		    Syslog('b', "BZ2flag is %s and received BZ2 option", opstate[bp.BZ2flag]);
		}
#endif
#ifdef	HAVE_ZLIB_H
	    } else if (strncmp(q, (char *)"GZ", 2) == 0) {
		if (bp.GZflag == WeCan) {
		    bp.GZflag = TheyWant;
		    Syslog('b', "Binkp: remote supports GZ mode");
		} else {
		    Syslog('b', "GZflag is %s and received GZ option", opstate[bp.GZflag]);
		}
#endif
#ifdef	HAVE_ZLIB_H
	    } else if (strncmp(q, (char *)"PLZ", 3) == 0) {
		if (bp.PLZflag == WeCan) {
		    bp.PLZflag = TheyWant;
		    Syslog('b', "Binkp: remote supports PLZ mode");
		} else {
		    Syslog('b', "PLZflag is %s and received PLZ option", opstate[bp.PLZflag]);
		}
#endif
	    }
	}

    } else {
	Syslog('+', "Binkp: M_NUL \"%s\"", msg);
    }
}



/*
 * Poll for a frame, returns:
 *  -1 = Error
 *   0 = Nothing yet
 *   1 = Got a frame
 *   2 = Frame not processed
 *   3 = Uncompress error
 */
int binkp_poll_frame(void)
{
    int		    c, rc = 0, bcmd;
#ifdef HAVE_ZLIB_H
    unsigned long   zlen;
    char	    *zbuf;
#endif

    for (;;) {
	if (bp.GotFrame) {
	    Syslog('b', "Binkp: WARNING: frame not processed");
	    rc = 2;
	    break;
	} else {
	    c = GETCHAR(0);
	    if (c < 0) {
		c = -c;
		if (c == STAT_TIMEOUT) {
		    msleep(1);
		    rc = 0;
		    break;
		}
		Syslog('?', "Binkp: receiver status %s", ttystat[c]);
		bp.rc = (MBERR_TTYIO + (-c));
		rc = -1;
		break;
	    } else {
		switch (bp.rxlen) {
		    case 0: bp.header = c << 8;
			    rc = 0;
			    break;
		    case 1: bp.header += c;
			    rc = 0;
			    break;
		    default:bp.rxbuf[bp.rxlen-2] = c;
		}
		if (bp.rxlen == 1) {
		    bp.cmd = bp.header & BINKP_CONTROL_BLOCK;
#ifdef HAVE_ZLIB_H
		    if ((bp.PLZflag == Active) && (bp.rmode == CompNone)) {
			bp.blklen = bp.header & 0x3fff;
		    } else {
			bp.blklen = bp.header & 0x7fff;
		    }
#else
		    bp.blklen = bp.header & 0x7fff;
#endif
		}
		if ((bp.rxlen == (bp.blklen + 1) && (bp.rxlen >= 1))) {
		    bp.GotFrame = TRUE;
#ifdef HAVE_ZLIB_H
		    /*
		     * Got a PLZ compressed block
		     */
		    if ((bp.PLZflag == Active) && (bp.header & BINKP_PLZ_BLOCK) && (bp.rmode == CompNone) && bp.blklen) {
			zbuf = calloc(BINKP_ZIPBUFLEN, sizeof(char));
			zlen = BINKP_PLZ_BLOCK -1;
			rc = uncompress(zbuf, &zlen, bp.rxbuf, bp.rxlen -1);
			if (rc == Z_OK) {
			    bp.rxcompressed += (zlen - (bp.rxlen -1));
			    memmove(bp.rxbuf, zbuf, zlen);
			    bp.rxlen = zlen +1;
			    bp.blklen = zlen;
			} else {
			    free(zbuf);
			    Syslog('!', "Binkp: uncompress error %d", rc);
			    return 3;
			}
			free(zbuf);
		    }
#endif
		    bp.rxbuf[bp.rxlen-1] = '\0';
		    if (bp.cmd) {
			bp.messages++;
			bcmd = bp.rxbuf[0];
			Syslog('b', "Binkp: rcvd %s %s", bstate[bcmd], printable(bp.rxbuf+1, 0));
			if (bcmd == MM_EOB) {
			    bp.remote_EOB = TRUE;
			}
		    } else {
			Syslog('b', "Binkp: rcvd data (%d)", bp.rxlen -1);
		    }
		    binkp_settimer(BINKP_TIMEOUT);
		    Nopper();
		    rc = 1;
		    break;
		}
		bp.rxlen++;
	    }
	}
    }

    return rc;
}



/*
 * Add received command frame to the queue, will be processed by the transmitter.
 */
void binkp_add_message(char *frame)
{
    the_queue	**tmpl;
    int		bcmd;
    
    bcmd = frame[0];
    Syslog('b', "Binkp: add message %s %s", bstate[bcmd], printable(frame +1, 0));

    for (tmpl = &tql; *tmpl; tmpl = &((*tmpl)->next));
    *tmpl = (the_queue *)malloc(sizeof(the_queue));

    (*tmpl)->next = NULL;
    (*tmpl)->cmd  = frame[0];
    (*tmpl)->data = xstrcpy(frame +1);

    bp.msgs_on_queue++;
}



/*
 * Process all messages on the queue, the oldest are on top, after
 * processing release memory and reset the messages queue.
 */
int binkp_process_messages(void)
{
    the_queue	*tmpq, *oldq;
    binkp_list	*tmp;
    file_list	*tsl;
    int		Found, rmode;
    char	*lname, *ropts;
    time_t	ltime;
    long	lsize, loffs;

    Syslog('b', "Binkp: Process The Messages Queue Start");

    lname = calloc(512, sizeof(char));
    ropts = calloc(512, sizeof(char));

    for (tmpq = tql; tmpq; tmpq = tmpq->next) {
	Syslog('+', "Binkp: %s \"%s\"", bstate[tmpq->cmd], printable(tmpq->data, 0));
	if (tmpq->cmd == MM_GET) {
	    rmode = CompNone;
	    sprintf(lname, "%s", strtok(tmpq->data, " \n\r"));
	    lsize = atoi(strtok(NULL, " \n\r"));
	    ltime = atoi(strtok(NULL, " \n\r"));
	    loffs = atoi(strtok(NULL, " \n\r"));
	    sprintf(ropts, "%s", strtok(NULL, " \n\r"));
	    Syslog('b', "Binkp: m_file options \"%s\"", ropts);
	    if (strcmp((char *)"GZ", ropts) == 0)
    		rmode = CompGZ;
	    else if (strcmp((char *)"BZ2", ropts) == 0)
		rmode = CompBZ2;
	    else if (strcmp((char *)"NZ", ropts) == 0) {
		rmode = CompNone;
#ifdef	HAVE_ZLIB_H
		bp.GZflag = No;
#endif
#ifdef	HAVE_BZLIB_H
		bp.BZ2flag = No;
#endif
		Syslog('+', "Binkp: received NZ on M_GET command, compression turned off");
	    }
	    Found = FALSE;
	    for (tmp = bll; tmp; tmp = tmp->next) {
		if ((strcmp(lname, tmp->remote) == 0) && (lsize == tmp->size) && (ltime == tmp->date)) {
		    if (lsize == loffs) {
			/*
			 * Requested offset is filesize, close file.
			 */
			if (cursend && (strcmp(lname, cursend->remote) == 0) && 
				(lsize == cursend->size) && (ltime == cursend->date)) {
			    Syslog('b', "Got M_GET with offset == filesize for current file, close");

			    /*
			     * calculate time needed and bytes transferred
			     */
			    gettimeofday(&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(txtvstart, txtvend, bp.stxpos, TRUE));
			    } else {
				Syslog('+', "Binkp: transmitter skipped file after %ld seconds", 
					txtvend.tv_sec - txtvstart.tv_sec);
			    }

			    cursend->state = Got; // 12-03-2004 changed from IsSent, bug from LdG with FT.
			    cursend = NULL;
			    bp.TxState = TxGNF;

			    for (tsl = tosend; tsl; tsl = tsl->next) { // Added 12-03
				if (tsl->remote == NULL) {
				    execute_disposition(tsl);
				} else {
				    if (strcmp(cursend->local, tsl->local) == 0) {
					execute_disposition(tsl);
				    }
				}
			    }
			} else {
			    Syslog('+', "Binkp: requested offset = filesize, but is not the current file");
			    if (tmp->state == IsSent) { // Added 12-03
				Syslog('b', "Binkp: file is sent, treat as m_got");
				tmp->state = Got;
				for (tsl = tosend; tsl; tsl = tsl->next) {
				    if (tsl->remote == NULL) {
					execute_disposition(tsl);
				    } else {
					if (strcmp(tmp->local, tsl->local) == 0) {
					    execute_disposition(tsl);
					}
				    }
				}
			    }
			}
		    } else if (loffs < lsize) {
			tmp->state = NoState;
			tmp->offset = loffs;
			if (loffs) {
			    Syslog('+', "Binkp: Remote wants %s for resync at offset %ld", tmp->remote, tmp->offset);
			} else {
			    Syslog('+', "Binkp: Remote wants %s again from start", tmp->remote);
			}
			bp.TxState = TxGNF;
		    } else {
			Syslog('+', "Binkp: requested offset > filesize, ignore");
		    }
		    Found = TRUE;
		    break;
		}
		if (!Found) {
		    Syslog('+', "Binkp: unexpected M_GET \"%s\"", tmpq->data); /* Ignore frame */
		}
	    }
	} else if (tmpq->cmd == MM_GOT) {
	    sprintf(lname, "%s", strtok(tmpq->data, " \n\r"));
	    lsize = atoi(strtok(NULL, " \n\r"));
	    ltime = atoi(strtok(NULL, " \n\r"));
	    Found = FALSE;
	    for (tmp = bll; tmp; tmp = tmp->next) {
		if ((strcmp(lname, tmp->remote) == 0) && (lsize == tmp->size) && (ltime == tmp->date)) {
		    Found = TRUE;
		    if (tmp->state == Sending) {
			Syslog('+', "Binkp: remote refused %s", tmp->remote);
			fclose(bp.txfp);
			bp.txfp = NULL;
			bp.TxState = TxGNF;
			cursend = NULL;
		    } else {
			Syslog('+', "Binkp: remote GOT \"%s\"", tmp->remote);
		    }
		    tmp->state = Got;
		    for (tsl = tosend; tsl; tsl = tsl->next) {
			if (tsl->remote == NULL) {
			    execute_disposition(tsl);
			} else {
			    if (strcmp(tmp->local, tsl->local) == 0) {
				execute_disposition(tsl);
			    }
			}
		    }
		    break;
		}
	    }
	    if (!Found) {
		Syslog('+', "Binkp: unexpected M_GOT \"%s\"", tmpq->data); /* Ignore frame */
	    }
	} else if (tmpq->cmd == MM_SKIP) {
	    sprintf(lname, "%s", strtok(tmpq->data, " \n\r"));
	    lsize = atoi(strtok(NULL, " \n\r"));
	    ltime = atoi(strtok(NULL, " \n\r"));
	    Found = FALSE;
	    for (tmp = bll; tmp; tmp = tmp->next) {
		if ((strcmp(lname, tmp->remote) == 0) && (lsize == tmp->size) && (ltime == tmp->date)) {
		    Found = TRUE;
		    if (tmp->state == Sending) {
			Syslog('+', "Binkp: remote skipped %s, may be accepted later", tmp->remote);
			fclose(bp.txfp);
			bp.txfp = NULL;
			cursend = NULL;
			bp.TxState = TxGNF;
		    } else {
			Syslog('+', "Binkp: remote refused %s", tmp->remote);
		    }
		    tmp->state = Skipped;
		    break;
		}
	    }
	    if (!Found) {
		Syslog('+', "Binkp: unexpected M_SKIP \"%s\"", tmpq->data); /* Ignore frame */
	    }
	} else {
	    /* Illegal message on the queue */
	}
    }

    /*
     * Tidy the messages queue and reset.
     */
    for (tmpq = tql; tmpq; tmpq = oldq) {
	oldq = tmpq->next;
	if (tmpq->data)
	    free(tmpq->data);
	free(tmpq);
    }
    tql = NULL;
    free(lname);
    bp.msgs_on_queue = 0;

    Syslog('b', "Binkp: Process The Messages Queue End");
    return 0;
}



/*
 * Count number of pending files
 */
int binkp_pendingfiles(void)
{
    binkp_list  *tmpl;
    int         count = 0;

    for (tmpl = bll; tmpl; tmpl = tmpl->next) {
	if ((tmpl->state != Got) && (tmpl->state != Skipped))
	    count++;
    }

    if (count)
	Syslog('b', "Binkp: %d pending files on queue", count);
    return count;
}



/*
 * 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 binkp_resync(off_t off)
{
    Syslog('b', "Binkp: resync(%d) DidSendGET=%s", off, bp.DidSendGET ?"TRUE":"FALSE");
    if (!bp.DidSendGET) {
	binkp_send_command(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  */
}



/*
 * 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;
}



/*
 * Fill internal binkp filelist
 */
void fill_binkp_list(binkp_list **bkll, file_list *fal, off_t offs)
{
    binkp_list  **tmpl;
    FILE	*fp;
    struct stat tstat;
    int		comp;
    char	*unp;

    for (tmpl = bkll; *tmpl; tmpl = &((*tmpl)->next));
    *tmpl = (binkp_list *)malloc(sizeof(binkp_list));

    (*tmpl)->next   = NULL;
    (*tmpl)->state  = NoState;
    if ((fp = fopen(fal->local, "r")) == NULL) {
	if ((errno == ENOENT) || (errno == EINVAL)) {
	    Syslog('+', "Binkp: file %s doesn't exist, removing", MBSE_SS(fal->local));
	    (*tmpl)->state  = Got;
	    execute_disposition(fal);
	}
    } else {
	fclose(fp);
	stat(fal->local, &tstat);
	if (strstr(fal->remote, (char *)".pkt"))
	    bp.nethold += tstat.st_size;
	else
	    bp.mailhold += tstat.st_size;
    }
    (*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;
    (*tmpl)->compress = CompNone;

    /*
     * Search compression method, but only if GZ or BZ2 compression is active.
     */
    comp = FALSE;
#ifdef	HAVE_ZLIB_H
    if (bp.GZflag == Active)
	comp = TRUE;
#endif
#ifdef	HAVE_BZLIB_H
    if (bp.BZ2flag == Active)
	comp = TRUE;
#endif
    if (!comp)
	return;
    
    /*
     * Don't compress small files
     */
    if (tstat.st_size <= 1024)
	return;

    /*
     * Check file type
     */
    unp = unpacker(fal->local);
    if (unp && strcmp((char *)"TAR", unp)) {
	Syslog('b', "Binkp: %s send as-is", fal->local);
	return;
    }

#ifdef	HAVE_BZLIB_H
    /*
     * Use BZ2 for files > 200K
     */
    if ((bp.BZ2flag == Active) && (tstat.st_size > 202400)) {
	(*tmpl)->compress = CompBZ2;
	Syslog('b', "Binkp: %s compressor BZ2", fal->local);
	return;
    }
#endif
#ifdef	HAVE_ZLIB_H
    if (bp.GZflag == Active) {
	(*tmpl)->compress = CompGZ;
	Syslog('b', "Binkp: %s compressor GZ", fal->local);
	return;
    }
#endif
    Syslog('+', "Binkp: compressor select internal error");
}



/*
 * Clear current filelist
 */
void binkp_clear_filelist(int rc)
{
    binkp_list	*tmp;
    file_list	*fal;

    if (tosend != NULL) {
	Syslog('b', "Binkp: binkp_clear_filelist(%d)", rc);

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

	/* WARNING: Added 16-07-2004 to see if this is safe to clean /flo files.
	 *
	 *  Remove sent fake files like .spl and .flo
	 */
	for (fal = tosend; fal; fal = fal->next) {
	    if ((fal->remote == NULL) && (rc == 0))
		execute_disposition(fal);
	}
	tidy_filelist(tosend, TRUE);
	tosend = NULL;
	respond = NULL;
    }
}



/*****************************************************************************
 *
 *  Compression support for GZ and BZ2 modes. Routines from the original
 *  binkd package, slightly adopted for mbcico.
 *
 *  Original written by val khokhlov, FIDONet 2:550/180
 *
 */


int compress_init(int type, void **data)
{
    int	    lvl;

    switch (type) {
#ifdef HAVE_BZLIB_H
	case CompBZ2: {
	    *data = calloc(1, sizeof(bz_stream));
	    if (*data == NULL) {
		Syslog('+', "Binkp: compress_init: not enough memory (%lu needed)", sizeof(bz_stream));
		return BZ_MEM_ERROR;
	    }
	    lvl = 1; /* default is small (100K) buffer */
	    return BZ2_bzCompressInit((bz_stream *)*data, lvl, 0, 0);
	}
#endif
#ifdef HAVE_ZLIB_H
	case CompGZ: {
	    *data = calloc(1, sizeof(z_stream));
	    if (*data == NULL) {
		Syslog('+', "Binkp: compress_init: not enough memory (%lu needed)", sizeof(z_stream));
		return Z_MEM_ERROR;
	    }
	    lvl = 9; /* Maximum compression */
	    if (lvl <= 0) lvl = Z_DEFAULT_COMPRESSION;
		return deflateInit((z_stream *)*data, lvl);
	}
#endif
	default:
	    Syslog('+', "Binkp: unknown compression method: %d; data lost", type);
    }
    return -1;
}



int do_compress(int type, char *dst, int *dst_len, char *src, int *src_len, int finish, void *data)
{
    int rc;

    switch (type) {
#ifdef HAVE_BZLIB_H
	case CompBZ2: {
	    bz_stream *zstrm = (bz_stream *)data;
	    zstrm->next_in = (char *)src;
	    zstrm->avail_in = (unsigned int)*src_len;
	    zstrm->next_out = (char *)dst;
	    zstrm->avail_out = (unsigned int)*dst_len;
	    rc = BZ2_bzCompress(zstrm, finish ? BZ_FINISH : 0);
	    *src_len -= (int)zstrm->avail_in;
	    *dst_len -= (int)zstrm->avail_out;
	    if (rc == BZ_RUN_OK || rc == BZ_FLUSH_OK || rc == BZ_FINISH_OK) 
		rc = 0;
	    if (rc == BZ_STREAM_END) 
		rc = 1;
	    return rc;
	}
#endif
#ifdef HAVE_ZLIB_H
	case CompGZ: {
	    z_stream *zstrm = (z_stream *)data;
	    zstrm->next_in = (Bytef *)src;
	    zstrm->avail_in = (uLong)*src_len;
	    zstrm->next_out = (Bytef *)dst;
	    zstrm->avail_out = (uLong)*dst_len;
	    rc = deflate(zstrm, finish ? Z_FINISH : 0);
	    *src_len -= (int)zstrm->avail_in;
	    *dst_len -= (int)zstrm->avail_out;
	    if (rc == Z_STREAM_END) 
		rc = 1;
	    return rc;
	}
#endif
	default:
	    Syslog('+', "Binkp: unknown compression method: %d; data lost", type);
    }
    return -1;
}



void compress_deinit(int type, void *data)
{
    switch (type) {
#ifdef HAVE_BZLIB_H
	case CompBZ2: {
	    int rc = BZ2_bzCompressEnd((bz_stream *)data);
	    if (rc < 0) 
		Syslog('+', "Binkp: BZ2_bzCompressEnd error: %d", rc);
	    break;
	}
#endif
#ifdef HAVE_ZLIB_H
	case CompGZ: {
	    int rc = deflateEnd((z_stream *)data);
	    if (rc < 0) 
		Syslog('+', "Binkp: deflateEnd error: %d", rc);
	    break;
	}
#endif
	default:
	    Syslog('+', "Binkp: unknown compression method: %d", type);
    }
    free(data);
    return;
}



void compress_abort(int type, void *data)
{
    char    buf[1024];
    int	    i, j;

    if (data) {
	Syslog('b', "Binkp: purge compress buffers");
	do {
	    i = sizeof(buf);
	    j = 0;
	} while (do_compress(type, buf, &i, NULL, &j, 1, data) == 0 && i > 0);
	compress_deinit(type, data);
    }
}



int decompress_init(int type, void **data)
{
    switch (type) {
#ifdef HAVE_BZLIB_H
	case CompBZ2: {
	    *data = calloc(1, sizeof(bz_stream));
	    if (*data == NULL) {
		Syslog('+', "Binkp: decompress_init: not enough memory (%lu needed)", sizeof(bz_stream));
		return BZ_MEM_ERROR;
	    }
	    return BZ2_bzDecompressInit((bz_stream *)*data, 0, 0);
	}
#endif
#ifdef HAVE_ZLIB_H
	case CompGZ: {
	    *data = calloc(1, sizeof(z_stream));
	    if (*data == NULL) {
		Syslog('+', "Binkp: decompress_init: not enough memory (%lu needed)", sizeof(z_stream));
		return Z_MEM_ERROR;
	    }
	    return inflateInit((z_stream *)*data);
	}
#endif
	default:
	    Syslog('+', "Binkp: unknown compression method: %d; data lost", type);
    }
    return -1;
}



int do_decompress(int type, char *dst, int *dst_len, char *src, int *src_len, void *data) 
{
    int	    rc;

    switch (type) {
#ifdef HAVE_BZLIB_H
	case CompBZ2: {
	    bz_stream *zstrm = (bz_stream *)data;
	    zstrm->next_in = (char *)src;
	    zstrm->avail_in = (unsigned int)*src_len;
	    zstrm->next_out = (char *)dst;
	    zstrm->avail_out = (unsigned int)*dst_len;
	    rc = BZ2_bzDecompress(zstrm);
	    *src_len -= (int)zstrm->avail_in;
	    *dst_len -= (int)zstrm->avail_out;
	    if (rc == BZ_RUN_OK || rc == BZ_FLUSH_OK) 
		rc = 0;
	    if (rc == BZ_STREAM_END) 
		rc = 1;
	    return rc;
	}
#endif
#ifdef HAVE_ZLIB_H
	case CompGZ: {
	    z_stream *zstrm = (z_stream *)data;
	    zstrm->next_in = (Bytef *)src;
	    zstrm->avail_in = (uLong)*src_len;
	    zstrm->next_out = (Bytef *)dst;
	    zstrm->avail_out = (uLong)*dst_len;
	    rc = inflate(zstrm, 0);
	    *src_len -= (int)zstrm->avail_in;
	    *dst_len -= (int)zstrm->avail_out;
	    if (rc == Z_STREAM_END) 
		rc = 1;
	    return rc;
	}
#endif
	default:
	    Syslog('+', "Binkp: unknown compression method: %d; data lost", type);
    }
    return 0;
}



int decompress_deinit(int type, void *data)
{
    int	    rc = -1;

    switch (type) {
#ifdef HAVE_BZLIB_H
	case CompBZ2: {
	    rc = BZ2_bzDecompressEnd((bz_stream *)data);
	    break;
	}
#endif
#ifdef HAVE_ZLIB_H
	case CompGZ: {
	    rc = inflateEnd((z_stream *)data);
	    break;
	}
#endif
	default:
	    Syslog('+', "Binkp: unknown compression method: %d", type);
	    break;
    }
    free(data);
    return rc;
}



int decompress_abort(int type, void *data) 
{
    char    buf[1024];
    int	    i, j;

    if (data) {
	Syslog('b', "Binkp: purge decompress buffers");
	do {
	    i = sizeof(buf);
	    j = 0;
	} while (do_decompress(type, buf, &i, NULL, &j, data) == 0 && i > 0);
	return decompress_deinit(type, data);
    }
    return 0;
}