/*****************************************************************************
 *
 * File ..................: mbcico/session.c
 * Purpose ...............: Fidonet mailer 
 * Last modification date : 15-Nov-1999
 *
 *****************************************************************************
 * Copyright (C) 1997-1999
 *   
 * Michiel Broek		FIDO:		2:2801/16
 * Beekmansbos 10		Internet:	mbroek@ux123.pttnwb.nl
 * 1971 BV IJmuiden
 * the Netherlands
 *
 * This file is part of MBSE BBS.
 *
 * This BBS is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * MBSE BBS is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MBSE BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************/

#include "../include/libs.h"
#include "../include/structs.h"
#include "../include/records.h"
#include "../common/common.h"
#include "ttyio.h"
#include "statetbl.h"
#include "emsi.h"
#include "ftsc.h"
#include "session.h"
#include "yoohoo.h"
#include "mbcico.h"
#include "binkp.h"
#include "callstat.h"


extern	char *version;
extern	char *reldate;
extern	int  tcp_mode;

node	*nlent;
fa_list *remote=NULL;
int	session_flags;
int	remote_flags;

int	tx_define_type(void);
int	rx_define_type(void);

static	int type;
static	char *data=NULL;



int session(faddr *a, node *nl, int role, int tp, char *dt)
{
	int	rc = 1;
	fa_list *tmpl;
	struct	sockaddr_in peeraddr;
	int	addrlen = sizeof(struct sockaddr_in);

	session_flags = 0;
	type = tp;
	nlent = nl;

	Syslog('s', "Start %s session type %d with %s",
		role?"master":"slave",type,ascfnode(a,0x1f));

	if (role == 1)
		IsDoing("Outb %s", ascfnode(a, 0x0f));

	if (getpeername(0,(struct sockaddr*)&peeraddr,&addrlen) == 0) {
		Syslog('s', "%s TCP connection: len=%d, family=%hd, port=%hu, addr=%s",
			role?"outgoing":"incoming",
			addrlen,peeraddr.sin_family,
			peeraddr.sin_port,
			inet_ntoa(peeraddr.sin_addr));
		if (role == 0) {
			if (tcp_mode == TCPMODE_IBN)
				Syslog('+', "Incoming IBN/TCP connection from %s", inet_ntoa(peeraddr.sin_addr));
			else if (tcp_mode == TCPMODE_ITN)
				Syslog('+', "Incoming ITN/TCP connection from %s", inet_ntoa(peeraddr.sin_addr));
			else if (tcp_mode == TCPMODE_IFC)
				Syslog('+', "Incoming IFC/TCP connection from %s", inet_ntoa(peeraddr.sin_addr));
			else if (tcp_mode == TCPMODE_NONE) {
				WriteError("Unknown TCP connection, parameter missing");
				die(101);
			}

			IsDoing("Answer TCP");
		}
		session_flags |= SESSION_TCP;
	}

	if (data)
		free(data);
	data=NULL;

	if (dt)
		data=xstrcpy(dt);

	emsi_local_protos=0;
	emsi_local_opts=0;
	emsi_local_lcodes=0;
	
	tidy_falist(&remote);
	remote=NULL;
	if (a) {
		remote=(fa_list*)xmalloc(sizeof(fa_list));
		remote->next=NULL;
		remote->addr=(faddr*)xmalloc(sizeof(faddr));
		remote->addr->zone=a->zone;
		remote->addr->net=a->net;
		remote->addr->node=a->node;
		remote->addr->point=a->point;
		remote->addr->domain=xstrcpy(a->domain);
		remote->addr->name=NULL;
	} else {
		remote=NULL;
	}

	remote_flags=SESSION_FNC;

	if (role) {
		if (type == SESSION_UNKNOWN) 
			(void)tx_define_type();
		switch(type) {
			case SESSION_UNKNOWN:	rc=20; break;
			case SESSION_FTSC:	rc=tx_ftsc(); break;
			case SESSION_YOOHOO:	rc=tx_yoohoo(); break;
			case SESSION_EMSI:	rc=tx_emsi(data); break;
			case SESSION_BINKP:	rc=binkp(role); break;
		}
	} else {
		if (type == SESSION_FTSC) 
			session_flags |= FTSC_XMODEM_CRC;
		if (type == SESSION_UNKNOWN) 
			(void)rx_define_type();
		switch(type) {
			case SESSION_UNKNOWN:	rc=20; break;
			case SESSION_FTSC:	rc=rx_ftsc(); break;
			case SESSION_YOOHOO:	rc=rx_yoohoo(); break;
			case SESSION_EMSI:	rc=rx_emsi(data); break;
			case SESSION_BINKP:	rc=binkp(role); break;
		}
	}
	sleep(2);
	for (tmpl = remote; tmpl; tmpl = tmpl->next) {
		/*
		 * Unlock all nodes, locks not owned by us are untouched.
		 */
		(void)nodeulock(tmpl->addr);
		/*
		 * If successfull session, reset all status records.
		 */
		if (rc == 0)
			putstatus(tmpl->addr, 0, 0);
	}

	return rc;
}



SM_DECL(tx_define_type,(char *)"tx_define_type")
SM_STATES
	skipjunk,
	wake,
	waitchar,
	nextchar,
	checkintro,
	sendinq
SM_NAMES
	(char *)"skipjunk",
	(char *)"wake",
	(char *)"waitchar",
	(char *)"nextchar",
	(char *)"checkintro",
	(char *)"sendinq"
SM_EDECL
	int c=0;
	int count=0;
	char buf[256],*p;
	char ebuf[13],*ep;
	int standby=0;

	int maybeftsc=0;
	int maybeyoohoo=0;

	type=SESSION_UNKNOWN;
	ebuf[0]='\0';
	ep=ebuf;
	buf[0]='\0';
	p=buf;

SM_START(skipjunk)

SM_STATE(skipjunk)

	while ((c=GETCHAR(1)) >= 0) /*nothing*/ ;
	if (c == TIMEOUT) {
		SM_PROCEED(wake);
	} else {
		SM_ERROR;
	}

SM_STATE(wake)

	if (count++ > 20) {
		Syslog('+', "Remote did not respond");
		SM_ERROR;
	}

	p=buf;
	PUTCHAR('\r');
	if ((c=GETCHAR(2)) == TIMEOUT) {
		SM_PROCEED(wake);
	} else if (c < 0) {
		WriteError("Error while waking remote");
		SM_ERROR;
	} else {
		count = 0;
		SM_PROCEED(nextchar);
	}

SM_STATE(waitchar)

	if ((c=GETCHAR(4)) == TIMEOUT) {
		standby = 0;
		ep = ebuf;
		ebuf[0] = '\0';
		if (count++ > 8) {
			Syslog('+', "Too many tries waking remote");
			SM_ERROR;
		}
		SM_PROCEED(sendinq);
	} else if (c < 0) {
		Syslog('+', "Error while getting intro from remote");
		SM_ERROR;
	} else {
		SM_PROCEED(nextchar);
	}

SM_STATE(nextchar)

	if (c == 'C') {
		session_flags |= FTSC_XMODEM_CRC;
		maybeftsc++;
	} 
	if (c == NAK) {
		session_flags &= ~FTSC_XMODEM_CRC;
		maybeftsc++;
	} 
	if (c == ENQ) 
		maybeyoohoo++;

	if (((localoptions & NOWAZOO) == 0) && (maybeyoohoo > 1)) {
		type = SESSION_YOOHOO;
		SM_SUCCESS;
	}

	if (maybeftsc > 1) {
		type = SESSION_FTSC;
		SM_SUCCESS;
	}

	if ((c >= ' ') && (c <= '~')) {
		if (c != 'C') 
			maybeftsc = 0;
		maybeyoohoo = 0;

		if ((p-buf) < (sizeof(buf)-1)) {
			*p++ = c;
			*p = '\0';
		}

		if (c == '*') {
			standby = 1;
			ep = ebuf;
			buf[0] = '\0';
		} else if (standby) {
			if ((ep - ebuf) < (sizeof(ebuf) - 1)) {
				*ep++ = c;
				*ep = '\0';
			} 
			if ((ep - ebuf) >= (sizeof(ebuf) - 1)) {
				standby = 0;
				SM_PROCEED(checkintro);
			}
		}
	} else switch (c) {
	case DC1:	break;
	case '\r':
	case '\n':	standby = 0;
			ep = ebuf;
			ebuf[0] = '\0';
			if (buf[0]) 
				Syslog('+', "Intro: \"%s\"",buf);
			p = buf;
			buf[0] = '\0';
			break;
	default:	standby = 0;
			ep = ebuf;
			ebuf[0] = '\0';
			Syslog('i', "Got '%s' reading intro", printablec(c));
			break;
	}

	SM_PROCEED(waitchar);

SM_STATE(checkintro)

	Syslog('i', "Check \"%s\" for being EMSI request",ebuf);

	if (((localoptions & NOEMSI) == 0) &&
	    (strncasecmp(ebuf,"EMSI_REQA77E",12) == 0)) {
		type = SESSION_EMSI;
		data = xstrcpy((char *)"**EMSI_REQA77E");
		PUTSTR((char *)"\r**EMSI_INQC816\r**EMSI_INQC816\r\021");
		SM_SUCCESS;
	} else {
		p = buf;
		buf[0] = '\0';
		SM_PROCEED(waitchar);
	}

SM_STATE(sendinq)

	PUTCHAR(DC1);
	if ((localoptions & NOEMSI) == 0)
		PUTSTR((char *)"\r**EMSI_INQC816**EMSI_INQC816");
	if ((localoptions & NOWAZOO) == 0)
		PUTCHAR(YOOHOO);
	PUTCHAR(TSYNC);
	if ((localoptions & NOEMSI) == 0)
		PUTSTR((char *)"\r\021");
	SM_PROCEED(waitchar);

SM_END
SM_RETURN


SM_DECL(rx_define_type,(char *)"rx_define_type")
SM_STATES
	sendintro,
	waitchar,
	nextchar,
	checkemsi,
	getdat
SM_NAMES
	(char *)"sendintro",
	(char *)"waitchar",
	(char *)"nextchar",
	(char *)"checkemsi",
	(char *)"getdat"
SM_EDECL
	int count=0;
	int c=0;
	int maybeftsc=0,maybeyoohoo=0;
	char ebuf[13],*ep;
	char *p;
	int standby=0;
	int datasize;

	type=SESSION_UNKNOWN;
	session_flags|=FTSC_XMODEM_CRC;
	ebuf[0]='\0';
	ep=ebuf;


SM_START(sendintro)

SM_STATE(sendintro)

	if (count++ > 6) {  /* Was 16, is 6 according to the EMSI spec */
		Syslog('+', "Too many tries to get anything from the caller");
		SM_ERROR;
	}

	standby = 0;
	ep = ebuf;
	ebuf[0] = '\0';

	if ((localoptions & NOEMSI) == 0) {
		PUTSTR((char *)"**EMSI_REQA77E\r\021");
	}
	PUTSTR((char *)"\r\rAddress: ");
	PUTSTR(aka2str(CFG.aka[0]));
	PUTSTR((char *)" using mbcico ");
	PUTSTR(version);
	PUTSTR((char *)" of ");
	PUTSTR(reldate);
	switch (tcp_mode) {
	case TCPMODE_IFC:	PUTSTR((char *)"; IFC");
				break;
	case TCPMODE_ITN:	PUTSTR((char *)"; ITN");
				break;
	case TCPMODE_IBN:	PUTSTR((char *)"; IBN");
				break;
	}
	PUTCHAR('\r');
	if (STATUS) {
		SM_ERROR;
	} else {
		SM_PROCEED(waitchar);
	}

SM_STATE(waitchar)

	if ((c=GETCHAR(20)) == TIMEOUT) { /* Timeout was 8, must be 20. */
		SM_PROCEED(sendintro);
	} else if (c < 0) {
		Syslog('+', "EMSI error while getting from caller");
		SM_ERROR;
	} else {
		SM_PROCEED(nextchar);
	}

SM_STATE(nextchar)

	if ((c >= ' ') && (c <= 'z')) {
		if (c == '*') {
			standby = 1;
			ep = ebuf;
			ebuf[0] = '\0';
		} else if (standby) {
			if ((ep - ebuf) < (sizeof(ebuf) - 1)) {
				*ep++ = c;
				*ep = '\0';
			}
			if ((ep - ebuf) >= (sizeof(ebuf) - 1)) {
				standby = 0;
				SM_PROCEED(checkemsi);
			}
		}
		SM_PROCEED(waitchar);
	} else switch (c) {
	case DC1:	SM_PROCEED(waitchar);
			break;
	case TSYNC:	standby = 0;
			ep = ebuf;
			ebuf[0] = '\0';
			if (++maybeftsc > 1) {
				type = SESSION_FTSC;
				SM_SUCCESS;
			} else {
				SM_PROCEED(waitchar);
			}
			break;
	case YOOHOO:	standby = 0;
			ep = ebuf;
			ebuf[0] = '\0';
			if (++maybeyoohoo > 1) {
				type = SESSION_YOOHOO;
				SM_SUCCESS;
			} else {
				SM_PROCEED(waitchar);
			}
			break;
	case '\r':
	case '\n':	standby = 0;
			ep = ebuf;
			ebuf[0] = '\0';
			if (ebuf[0]) {
				SM_PROCEED(checkemsi);
			} else {
				SM_PROCEED(sendintro);
			}
			break;
	default:	standby = 0;
			ep = ebuf;
			ebuf[0] = '\0';
			Syslog('i', "Got '%s' from remote", printablec(c));
			SM_PROCEED(waitchar);
			break;
	}

SM_STATE(checkemsi)

	Syslog('i', "check \"%s\" for being EMSI inquery or data",ebuf);

	if (localoptions & NOEMSI) {
		SM_PROCEED(sendintro);
	}

	if (strncasecmp(ebuf, "EMSI_INQC816", 12) == 0) {
		type = SESSION_EMSI;
		data = xstrcpy((char *)"**EMSI_INQC816");
		SM_SUCCESS;
	} else if (strncasecmp(ebuf, "EMSI_DAT", 8) == 0) {
		SM_PROCEED(getdat);
	} else {
		SM_PROCEED(sendintro);
	}

SM_STATE(getdat)

	Syslog('i', "Try get emsi_dat packet starting with \"%s\"",ebuf);

	if (sscanf(ebuf+8, "%04x", &datasize) != 1) {
		SM_PROCEED(sendintro);
	}

	datasize += 18; /* strlen("**EMSI_DATxxxxyyyy"), include CRC */
	data=xmalloc(datasize+1);
	strcpy(data,"**");
	strcat(data, ebuf);
	p = data + strlen(data);

	while (((p - data) < datasize) && ((c = GETCHAR(8)) >= 0)) {
		*p++ = c;
		*p= '\0';
	}
	if (c == TIMEOUT) {
		SM_PROCEED(sendintro);
	} else if (c < 0) {
		Syslog('+', "Error while reading EMSI_DAT from the caller");
		SM_ERROR;
	}
	type = SESSION_EMSI;
	SM_SUCCESS;

SM_END
SM_RETURN

