/*****************************************************************************
 *
 * $Id: emsi.c,v 1.26 2007/08/26 12:21:16 mbse Exp $
 * Purpose ...............: Fidonet mailer
 *
 *****************************************************************************
 * Copyright (C) 1997-2007
 *   
 * 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 "emsidat.h"
#include "hydra.h"
#include "rdoptions.h"
#include "tcp.h"
#include "wazoo.h"
#include "inbound.h"
#include "callstat.h"


#define LOCAL_PROTOS (PROT_ZMO | PROT_ZAP | PROT_HYD | PROT_TCP)

static int rxemsi(void);
static int txemsi(void);
static char *intro;
static int caller;

extern int	laststat;
extern int	most_debug;
extern pid_t	mypid;
extern int	session_state;

int	emsi_local_lcodes;
int	emsi_remote_lcodes;
int	emsi_local_protos;
int	emsi_remote_protos;
int	emsi_local_opts;
int	emsi_remote_opts;
char	*emsi_local_password = NULL;
char	*emsi_remote_password = NULL;
char	emsi_remote_comm[4]="8N1";
int	emsi_akas = 0;


int rx_emsi(char *data)
{
    int		rc, protect = FALSE;
    fa_list	*tmr;
    int		denypw=0;
    callstat	*cst;
    
    emsi_local_lcodes = LCODE_RH1;
    emsi_remote_lcodes=0;

    emsi_local_protos=LOCAL_PROTOS;
    if (localoptions & NOZMODEM) 
	emsi_local_protos &= ~(PROT_ZMO | PROT_ZAP | PROT_DZA);
    if (localoptions & NOZEDZAP) 
	emsi_local_protos &= ~PROT_ZAP;
    if (localoptions & NOJANUS) 
	emsi_local_protos &= ~PROT_JAN;
    if (localoptions & NOHYDRA) 
	emsi_local_protos &= ~PROT_HYD;
    if ((session_flags & SESSION_TCP) == 0) {
	emsi_local_protos &= ~PROT_TCP;
    }

    emsi_remote_protos=0;
    emsi_local_opts = OPT_XMA;
    emsi_remote_opts=0;
    emsi_local_password = NULL;
    emsi_remote_password = NULL;
    intro=data+2;
    caller=0;

    if ((rc=rxemsi())) 
	return MBERR_EMSI;

    Syslog('i', "local  lcodes 0x%04x, protos 0x%04x, opts 0x%04x", emsi_local_lcodes,emsi_local_protos,emsi_local_opts);
    Syslog('i', "remote lcodes 0x%04x, protos 0x%04x, opts 0x%04x", emsi_remote_lcodes,emsi_remote_protos,emsi_remote_opts);

    if (emsi_remote_opts & OPT_EII) {
	emsi_local_opts |= OPT_EII;
    }

    emsi_local_protos &= emsi_remote_protos;
    if (emsi_local_protos & PROT_TCP) 
	emsi_local_protos &= PROT_TCP;
    else if (emsi_local_protos & PROT_HYD) 
	emsi_local_protos &= PROT_HYD;
    else if (emsi_local_protos & PROT_JAN)
	emsi_local_protos &= PROT_JAN;
    else if (emsi_local_protos & PROT_ZAP) 
	emsi_local_protos &= PROT_ZAP;
    else if (emsi_local_protos & PROT_ZMO) 
	emsi_local_protos &= PROT_ZMO;
    else if (emsi_local_protos & PROT_DZA) 
	emsi_local_protos &= PROT_DZA;
    else if (emsi_local_protos & PROT_KER) 
	emsi_local_protos &= PROT_KER;

    emsi_local_password = NULL;

    for (tmr = remote; tmr; tmr = tmr->next) {
	if (((nlent = getnlent(tmr->addr))) && (nlent->pflag != NL_DUMMY)) {
	    Syslog('+', "Remote is a listed system");
	    UserCity(mypid, nlent->sysop, nlent->location);
	    break;
	}
	cst = getstatus(tmr->addr);
	if (cst->trystat)
	    laststat = cst->trystat;
    }
    Syslog('s', "Last connection status %d", laststat);

    if (nlent) 
	rdoptions(TRUE);

    /*
     * Added these options, if they are in the setup for this
     * calling node, then disable these options.
     */
    if (localoptions & NOHYDRA)
	emsi_local_opts &= ~PROT_HYD;
    if (localoptions & NOZEDZAP)
	emsi_local_opts &= ~PROT_ZAP;
    if (localoptions & NOZMODEM)
	emsi_local_opts &= ~(PROT_ZMO | PROT_ZAP | PROT_DZA);

    if (localoptions & NOFREQS)
	emsi_local_opts |= OPT_NRQ;

    if (strlen(nodes.Spasswd)) {
	if ((strncasecmp(emsi_remote_password, nodes.Spasswd, strlen(nodes.Spasswd)) == 0) &&
	    (strlen(emsi_remote_password) == strlen(nodes.Spasswd))) {
	    emsi_local_password = xstrcpy(nodes.Spasswd);
	    protect = TRUE;
	    Syslog('+', "Password correct, protected EMSI session");
	} else {
	    denypw = 1;
	    Syslog('?', "Remote password \"%s\", expected \"%s\"", MBSE_SS(emsi_remote_password), nodes.Spasswd);
	    emsi_local_password = xstrcpy((char *)"BAD_PASS");
	    emsi_local_lcodes = LCODE_HAT;
	}
    } else {
	Syslog('?', "Unexpected remote password \"%s\"", MBSE_SS(emsi_local_password));
    }

    inbound_open(remote->addr, protect, FALSE);
    Syslog('i', "local  lcodes 0x%04x, protos 0x%04x, opts 0x%04x", emsi_local_lcodes,emsi_local_protos,emsi_local_opts);

    if ((rc=txemsi())) 
	return MBERR_EMSI;

    if (denypw || (emsi_local_protos == 0)) {
	Syslog('+', "Refusing remote: %s", emsi_local_protos?"bad password presented": "no common protocols");
	return 0;
    }

    if (protect)
	session_state = STATE_SECURE;
    else
	session_state = STATE_UNSECURE;

    IsDoing("EMSI %s inb", ascfnode(remote->addr, 0x0f));

    if ((emsi_remote_opts & OPT_NRQ) == 0) 
	session_flags |= SESSION_WAZOO;
    else 
	session_flags &= ~SESSION_WAZOO;

    if (emsi_local_protos & PROT_TCP) 
	return rxtcp();
    else if (emsi_local_protos & PROT_HYD)
	return hydra(0);
    else 
	return rxwazoo();
}



int tx_emsi(char *data)
{
    int	    rc;
    
    emsi_local_lcodes = LCODE_PUA | LCODE_RH1;
    emsi_remote_lcodes = 0;

    emsi_local_protos=LOCAL_PROTOS;
    if (localoptions & NOZMODEM) 
	emsi_local_protos &= ~(PROT_ZMO | PROT_ZAP | PROT_DZA);
    if (localoptions & NOZEDZAP) 
	emsi_local_protos &= ~PROT_ZAP;
    if (localoptions & NOJANUS)
	emsi_local_protos &= ~PROT_JAN;
    if (localoptions & NOHYDRA) 
	emsi_local_protos &= ~PROT_HYD;
    if ((session_flags & SESSION_TCP) == 0) {
	emsi_local_protos &= ~PROT_TCP;
    }
    emsi_remote_protos=0;
    emsi_local_opts=OPT_XMA | OPT_EII | OPT_NRQ;
    emsi_remote_opts=0;
    emsi_local_password=NULL;
    emsi_remote_password=NULL;
    intro=data+2;
    caller=1;
    emsi_local_password=NULL;

    Syslog('i', "local  lcodes 0x%04x, protos 0x%04x, opts 0x%04x", emsi_local_lcodes,emsi_local_protos,emsi_local_opts);

    if ((rc=txemsi())) 
	return MBERR_EMSI;
    else {
	if ((rc=rxemsi())) 
	    return MBERR_EMSI;
    }

    if ((emsi_remote_opts & OPT_EII) == 0) {
	emsi_local_opts &= ~OPT_EII;
    }

    Syslog('i', "remote lcodes 0x%04x, protos 0x%04x, opts 0x%04x", emsi_remote_lcodes,emsi_remote_protos,emsi_remote_opts);

    if ((emsi_remote_protos == 0) || (emsi_remote_lcodes & LCODE_HAT)) {
	Syslog('+', "Remote refused us: %s", emsi_remote_protos?"traffic held":"no common protos");
	return MBERR_SESSION_ERROR;
    }

    IsDoing("EMSI %s out", ascfnode(remote->addr, 0x0f));
    session_state = STATE_SECURE;

    emsi_local_protos &= emsi_remote_protos;
    if ((emsi_remote_opts & OPT_NRQ) == 0) 
	session_flags |= SESSION_WAZOO;
    else 
	session_flags &= ~SESSION_WAZOO;

    if (emsi_local_protos & PROT_TCP) 
	return txtcp();
    else if (emsi_local_protos & PROT_HYD)
	return hydra(1);
    else 
	return txwazoo();
}



SM_DECL(rxemsi,(char *)"rxemsi")
SM_STATES
    init,
    waitpkt,
    waitchar,
    checkemsi,
    getdat,
    checkpkt,
    checkdat,
    sendnak,
    sendack
SM_NAMES
    (char *)"init",
    (char *)"waitpkt",
    (char *)"waitchar",
    (char *)"checkemsi",
    (char *)"getdat",
    (char *)"checkpkt",
    (char *)"checkdat",
    (char *)"sendnak",
    (char *)"sendack"
SM_EDECL

    int		    c = 0;
    unsigned short  lcrc, rcrc;
    int		    len;
    int		    standby = 0, tries = 0;
    char	    buf[13], *p;
    char	    *databuf = NULL;

    p = buf;
    databuf = xstrcpy(intro);

SM_START(init)

SM_STATE(init)
    
    gpt_resettimers();
    gpt_settimer(0, 60);
    gpt_settimer(1, 20);
    SM_PROCEED(checkpkt);

SM_STATE(waitpkt)

    standby = 0;
    gpt_settimer(1, 20);
    SM_PROCEED(waitchar);

SM_STATE(waitchar)

    Syslog('i', "RXEMSI: waitchar, tries=%d", tries);

    if (gpt_expired(0)) {
	Syslog('+', "EMSI receive 60 seconds timeout");
	SM_ERROR;
    }

    if (gpt_expired(1)) {
	Syslog('s', "20 sec timeout");
	SM_PROCEED(sendnak);
    }

    c = GETCHAR(1);
    if (c == TIMEOUT) {
	SM_PROCEED(waitchar);
    } else if (c < 0) {
	SM_ERROR;
    } else if ((c >= ' ') && (c <= '~')) {
	if (c == '*') {
	    standby = 1;
	    p = buf;
	    *p = '\0';
	} else if (standby) {
	    if ((p - buf) < (sizeof(buf) - 1)) {
		*p++ = c;
		*p = '\0';
	    } if ((p - buf) >= (sizeof(buf) - 1)) {
		standby = 0;
		SM_PROCEED(checkemsi);
	    }
	}
    } else {
	switch(c) {
	    case DC1:	break;
	    case '\n':
	    case '\r':	standby = 0;
			break;
	    default:	standby = 0;
			break;
	}
    }
    SM_PROCEED(waitchar);

SM_STATE(checkemsi)

    Syslog('i', "RXEMSI: rcvd %s", printable(buf, 0));

    if (strncasecmp(buf, "EMSI_DAT",8) == 0) {
	SM_PROCEED(getdat);
    } else if (strncasecmp(buf, "EMSI_",5) == 0) {
	if (databuf) 
	    free(databuf);
	databuf = xstrcpy(buf);
	SM_PROCEED(checkpkt);
    } else {
	SM_PROCEED(waitpkt);
    }

SM_STATE(getdat)

    if (sscanf(buf+8,"%04x",&len) != 1) {
	SM_PROCEED(sendnak);
    }

    len += 16; /* strlen("EMSI_DATxxxxyyyy"), include CRC */
    if (databuf) 
	free(databuf);
    databuf = malloc(len + 1);
    strcpy(databuf, buf);
    p = databuf + strlen(databuf);

    while (((p-databuf) < len) && ((c=GETCHAR(8)) >= 0)) {
	*p++ = c;
	*p = '\0';
    }

    Syslog('i', "RXEMSI: rcvd %s (%d bytes)", databuf, len);

    if (c == TIMEOUT) {
	SM_PROCEED(sendnak);
    } else if (c < 0) {
	Syslog('+', "Error while reading EMSI_DAT packet");
	SM_ERROR;
    }

    SM_PROCEED(checkdat);

SM_STATE(checkpkt)

    if (strncasecmp(databuf,"EMSI_DAT",8) == 0) {
	SM_PROCEED(checkdat);
    }

    lcrc = crc16xmodem(databuf, 8);
    sscanf(databuf + 8, "%04hx", &rcrc);
    if (lcrc != rcrc) {
	Syslog('+', "Got EMSI packet \"%s\" with bad crc: %04x/%04x", printable(databuf, 0), lcrc, rcrc);
	SM_PROCEED(sendnak);
    } if (strncasecmp(databuf, "EMSI_HBT", 8) == 0) {
	tries = 0;
	SM_PROCEED(waitpkt);
    } else if (strncasecmp(databuf, "EMSI_INQ", 8) == 0) {
	SM_PROCEED(sendnak);
    } else {
	SM_PROCEED(waitpkt);
    }

SM_STATE(checkdat)

    sscanf(databuf + 8, "%04x", &len);
    if (len != (strlen(databuf) - 16)) {
	Syslog('+', "Bad EMSI_DAT length: %d/%d", len, strlen(databuf));
	SM_PROCEED(sendnak);
    }
    /* Some FD versions send length of the packet including the
       trailing CR.  Arrrgh!  Dirty overwork follows: */
    if (*(p = databuf + strlen(databuf) - 1) == '\r') 
	*p='\0';
    sscanf(databuf + strlen(databuf) - 4, "%04hx", &rcrc);
    *(databuf + strlen(databuf) - 4) = '\0';
    lcrc = crc16xmodem(databuf, strlen(databuf));
    if (lcrc != rcrc) {
	Syslog('+', "Got EMSI_DAT packet \"%s\" with bad crc: %04x/%04x", printable(databuf, 0), lcrc, rcrc);
	SM_PROCEED(sendnak);
    } if (scanemsidat(databuf + 12) == 0) {
	if (emsi_akas == 0) {
	    Syslog('+', "All akas busy, abort");
	    SM_ERROR;
	}
	SM_PROCEED(sendack);
    } else {
	Syslog('+', "Could not parse EMSI_DAT packet \"%s\"",databuf);
	SM_ERROR;
    }

SM_STATE(sendnak)

    /*
     * The FSC-0059 says 6 attempts, but to accept maindoor sessions
     * we seem to need about 11, we set it to 20 since we also have
     * a global 60 seconds timer running.
     */
    if (++tries > 19) {
	Syslog('+', "Too many tries getting EMSI_DAT");
	SM_ERROR;
    } if (caller) {
	PUTSTR((char *)"**EMSI_NAKEEC3\r\021");
	Syslog('i', "RXEMSI: send **EMSI_NAKEEC3");
    } else {
	PUTSTR((char *)"**EMSI_REQA77E\r\021");
	Syslog('i', "RXEMSI: send **EMSI_REQA77E");
	if (tries > 1) {
	    PUTSTR((char *)"**EMSI_NAKEEC3\r\021");
	    Syslog('i', "RXEMSI: send **EMSI_NAKEEC3");
	}
    }
    SM_PROCEED(waitpkt);

SM_STATE(sendack)

    PUTSTR((char *)"**EMSI_ACKA490\r\021");
    PUTSTR((char *)"**EMSI_ACKA490\r\021");
    SM_SUCCESS;

SM_END

    free(databuf);

SM_RETURN




SM_DECL(txemsi,(char *)"txemsi")
SM_STATES
    senddata,
    waitpkt,
    waitchar,
    checkpkt,
    sendack
SM_NAMES
    (char *)"senddata",
    (char *)"waitpkt",
    (char *)"waitchar",
    (char *)"checkpkt",
    (char *)"sendack"
SM_EDECL

    int		    c;
    unsigned short  lcrc, rcrc;
    int		    standby = 0, tries = 0;
    char	    buf[13], *p;
    char	    trailer[8];

    p = buf;
    memset(&buf, 0, sizeof(buf));
    strncpy(buf, intro, sizeof(buf) - 1);
    gpt_resettimers();
    gpt_settimer(0, 60);
    Syslog('i', "TXEMSI: 60 seconds timer set");

SM_START(senddata)

SM_STATE(senddata)

    p = mkemsidat(caller);
    PUTCHAR('*');
    PUTCHAR('*');
    PUTSTR(p);
    snprintf(trailer, 8, "%04X\r\021", crc16xmodem(p, strlen(p)));
    PUTSTR(trailer);
    Syslog('i', "TXEMSI: send **%s%04X", p, crc16xmodem(p, strlen(p)));
    free(p);
    gpt_settimer(1, 20);
    SM_PROCEED(waitpkt);

SM_STATE(waitpkt)

    standby = 0;
    SM_PROCEED(waitchar);

SM_STATE(waitchar)

    if (gpt_expired(0)) {
	Syslog('+', "EMSI transmit 60 seconds timeout");
	SM_ERROR;
    }

    if (gpt_expired(1)) {
	Syslog('i', "TXEMSI: 20 seconds timeout");
	if (++tries > 19) {
	    Syslog('+', "too many tries sending EMSI");
	    SM_ERROR;
	} else {
	    SM_PROCEED(senddata);
	}
    }

    c = GETCHAR(1);
    if (c == TIMEOUT) {
	SM_PROCEED(waitchar);
    } else if (c < 0) {
	SM_ERROR;
    } else if ((c >= ' ') && (c <= '~')) {
	if (c == '*') {
	    standby = 1;
	    p = buf;
	    *p = '\0';
	} else if (standby) {
	    if ((p - buf) < (sizeof(buf) - 1)) {
		*p++ = c;
		*p = '\0';
	    } if ((p - buf) >= (sizeof(buf) - 1)) {
		standby = 0;
		SM_PROCEED(checkpkt);
	    }
	}
    } else 
	switch(c) {
	    case DC1:	SM_PROCEED(waitchar);
				break;
	    case '\n':
	    case '\r':	standby = 0;
				break;
	    default:	standby = 0;
				break;
    }
    SM_PROCEED(waitchar);

SM_STATE(checkpkt)

    Syslog('i', "TXEMSI: rcvd %s", buf);
    if (strncasecmp(buf, "EMSI_DAT", 8) == 0) {
	SM_PROCEED(sendack);
    } else if (strncasecmp(buf, "EMSI_", 5) == 0) {
	lcrc = crc16xmodem(buf, 8);
	sscanf(buf + 8, "%04hx", &rcrc);
	if (lcrc != rcrc) {
	    Syslog('+', "Got EMSI packet \"%s\" with bad crc: %04x/%04x", printable(buf, 0), lcrc, rcrc);
	    SM_PROCEED(senddata);
	} if (strncasecmp(buf, "EMSI_REQ", 8) == 0) {
	    SM_PROCEED(waitpkt);
	} if (strncasecmp(buf, "EMSI_ACK", 8) == 0) {
	    SM_SUCCESS;
	} else {
	    SM_PROCEED(senddata);
	}
    } else {
	SM_PROCEED(waitpkt);
    }

SM_STATE(sendack)

    PUTSTR((char *)"**EMSI_ACKA490\r\021");
    PUTSTR((char *)"**EMSI_ACKA490\r\021");
    SM_PROCEED(waitpkt);

SM_END

SM_RETURN