/*
 *	binkleyforce -- unix FTN mailer project
 *	
 *	Copyright (c) 1998-2000 Alexander Belkin, 2:5020/1398.11
 *	
 *	This program 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 of the License, or
 *	(at your option) any later version.
 *	
 *	$Id: prot_binkp_misc.c,v 1.1.1.1 2001/08/30 13:33:50 kst Exp $
 */

#include "includes.h"
#include "confread.h"
#include "logger.h"
#include "util.h"
#include "io.h"
#include "session.h"
#include "outbound.h"
#include "prot_common.h"
#include "prot_binkp.h"

/*****************************************************************************
 * Initialise binkp state structure, allocate memory for buffers, etc.
 *
 * Arguments:
 * 	bpi       pointer to the binkp state structure (s_bpinfo)
 *
 * Return value:
 * 	None
 */
void binkp_init_bpinfo(s_bpinfo *bpi)
{
	memset(bpi, '\0', sizeof(s_bpinfo));

	bpi->obuf  = (char*)xmalloc(BINKP_MAX_BLKSIZE + BINKP_BLK_HDRSIZE + 1);
	bpi->opos  = 0;
	bpi->optr  = bpi->obuf;
	
	bpi->inew  = TRUE;
	bpi->isize = -1;
	bpi->ibuf  = (char*)xmalloc(BINKP_MAX_BLKSIZE + BINKP_BLK_HDRSIZE + 1);

	bpi->timeout = conf_number(cf_binkp_timeout);
	if( !bpi->timeout )
		bpi->timeout = 300;
}

/*****************************************************************************
 * DeInitialise binkp state structure, release allocated memory, etc.
 *
 * Arguments:
 * 	bpi       pointer to the binkp state structure (s_bpinfo)
 *
 * Return value:
 * 	None
 */
void binkp_deinit_bpinfo(s_bpinfo *bpi)
{
	if( bpi->obuf )
		free(bpi->obuf);
	if( bpi->ibuf )
		free(bpi->ibuf);
	if( bpi->msgqueue )
		free(bpi->msgqueue);
	
	memset(bpi, '\0', sizeof(s_bpinfo));
}

/*****************************************************************************
 * Fills s[0] and s[1] with binkp frame header using value of u
 *
 * Arguments:
 * 	s         pointer to the binkp's frame header (2 bytes)
 * 	val       value to put
 *
 * Return value:
 * 	None
 */
void binkp_puthdr(char *s, unsigned int val)
{
	s[0] = ( (unsigned long) val >> 8 ) & 0xff;
	s[1] = ( (unsigned long) val      ) & 0xff;
}

/*****************************************************************************
 * Fills s[0] with binkp message type value
 *
 * Arguments:
 * 	s         pointer to the destination buffer
 * 	msg       message type
 *
 * Return value:
 * 	None
 */
void binkp_putmsgtype(char *s, e_bpmsg msg)
{
	s[0] = ( (unsigned int) msg ) & 0xff;
}

/*****************************************************************************
 * Extract size from binkp frame header
 *
 * Arguments:
 * 	s         pointer to the source buffer
 *
 * Return value:
 * 	Value, extracted from binkp header
 */
int binkp_gethdr(const char *s)
{
	return ( (unsigned int) (((unsigned char) s[0]) & 0x7f) << 8 )
	     | ( (unsigned int) (((unsigned char) s[1]) & 0xff)      );
}

/*****************************************************************************
 * Extract message type from s[0]
 *
 * Arguments:
 * 	s         pointer to the source buffer (1 byte)
 *
 * Return value:
 * 	Value, extracted from binkp header, or -1 if extracted message type is
 * 	out of range
 */
int binkp_getmsgtype(char ch)
{
	int val = ( ((unsigned char)ch) & 0x7f );
	
	if( BPMSG_MIN > val || val > BPMSG_MAX )
		return -1;
	else
		return val;
}

/*****************************************************************************
 * Puts a message to the output messages queue. These msgs will be send
 * right after the current data block
 *
 * Arguments:
 * 	bpi       binkp state structure
 * 	msg       message type
 * 	s1        message text
 * 	s2        message text (will be concatenated with s1)
 *
 * Return value:
 * 	None
 */
void binkp_queuemsg(s_bpinfo *bpi, e_bpmsg msg, const char *s1, const char *s2)
{
	bpi->msgqueue = xrealloc(bpi->msgqueue, sizeof(s_bpmsg)*(bpi->n_msgs + 1));
	memset(&bpi->msgqueue[bpi->n_msgs], '\0', sizeof(s_bpmsg));
	
	/* Set message type */
	bpi->msgqueue[bpi->n_msgs].type = msg;
	
	/* Set message data size (without frame header) */
	bpi->msgqueue[bpi->n_msgs].size = 1;
	if( s1 ) bpi->msgqueue[bpi->n_msgs].size += strlen(s1);
	if( s2 ) bpi->msgqueue[bpi->n_msgs].size += strlen(s2);
	
	/* Set message data */
	bpi->msgqueue[bpi->n_msgs].data = xmalloc(bpi->msgqueue[bpi->n_msgs].size+3);
	binkp_puthdr(bpi->msgqueue[bpi->n_msgs].data,
	             (unsigned)(bpi->msgqueue[bpi->n_msgs].size | 0x8000));
	
	binkp_putmsgtype(bpi->msgqueue[bpi->n_msgs].data + 2, msg);
	
	bpi->msgqueue[bpi->n_msgs].data[3] = '\0';
	if( s1 ) strcat(bpi->msgqueue[bpi->n_msgs].data + 3, s1);
	if( s2 ) strcat(bpi->msgqueue[bpi->n_msgs].data + 3, s2);

	DEB((D_PROT, "binkp_queuemsg: queued message #%d, %ld bytes, \"%s\"",
		(int)bpi->msgqueue[bpi->n_msgs].type,
		(long)bpi->msgqueue[bpi->n_msgs].size,
		(char*)bpi->msgqueue[bpi->n_msgs].data+3));

	++bpi->n_msgs;
}

/*****************************************************************************
 * Sends a message using format string
 *
 * Arguments:
 * 	bpi       binkp state structure
 * 	msg       message type
 * 	fmt       pointer to the format string, (man printf)
 *
 * Return value:
 * 	None
 */
void binkp_queuemsgf(s_bpinfo *bpi, e_bpmsg msg, const char *fmt, ...)
{
	char msg_text[2048];
	va_list ap;
	
	va_start(ap, fmt);
#ifdef HAVE_SNPRINTF
	vsnprintf(msg_text, sizeof(msg_text), fmt, ap);
#else
	vsprintf(msg_text, fmt, ap);
#endif
	va_end(ap);
	
	binkp_queuemsg(bpi, msg, msg_text, NULL);
}

/*****************************************************************************
 * Parse most common message arguments, send error message to remote
 * if needed.
 *
 * Arguments:
 * 	s         pointer to the string to parse
 * 	fn        pointer to the pointer for extracted file name
 * 	sz        pointer to the extracted file size
 * 	tm        pointer to the extracted file modification time
 * 	offs      pointer to the extracted file offset
 *
 * Return value:
 * 	non-zero value on error, zero on success
 */
int binkp_parsfinfo(char *s, char **fn, size_t *sz, time_t *tm, size_t *offs)
{
	char *n;
	char *p_fname = NULL;
	char *p_size  = NULL;
	char *p_time  = NULL;
	char *p_offs  = NULL;
	
	/* Attention, offs may be NULL! */
	
	ASSERT(s != NULL && fn != NULL && sz != NULL && tm != NULL);

	DEB((D_PROT, "binkp_parsemsg: want parse \"%s\"", s));
	
	p_fname = string_token(s, &n, NULL, 0);
	p_size  = string_token(NULL, &n, NULL, 0);
	p_time  = string_token(NULL, &n, NULL, 0);
	if( offs ) p_offs = string_token(NULL, &n, NULL, 0);
	
	if( p_fname && p_size && p_time && (!offs || p_offs) )
	{
		if( ISDEC(p_size) && ISDEC(p_time) && (!offs || ISDEC(p_offs)) )
		{
			(*fn) = p_fname;
			(*sz) = atol(p_size);
			(*tm) = atol(p_time);
			if( offs ) *offs = atol(p_offs);
			DEB((D_PROT, "binkp_parsemsg: file = \"%s\", size = %d, time = %d",
				*fn, *sz, *tm));
			return 0;
		}
	}
	return 1;
}

/*****************************************************************************
 * Put message to a buffer
 *
 * Arguments:
 * 	bpi       binkp state structure
 * 	msg       message
 *
 * Return value:
 * 	Zero on success, and non-zero value if no more free space left
 * 	in the buffer
 */
int binkp_buffer_message(s_bpinfo *bpi, s_bpmsg *msg)
{
	DEB((D_PROT, "binkp_buffer_message: buffer message #%d, %ld byte(s)",
		(int)msg->type, (long)msg->size));
	
	/* Check for possible internal error */
	if( msg->size > BINKP_MAX_BLKSIZE )
	{
		log("size of msg we want to send is too big (%db)", msg->size);
		return 1;
	}
	
	/* Is there space for the new msg? */
	if( bpi->opos + msg->size > BINKP_MAX_BLKSIZE ) return 1;
	
	if( msg->data )
	{
		memcpy(bpi->obuf + bpi->opos, msg->data, msg->size + BINKP_BLK_HDRSIZE);
		bpi->opos += msg->size + BINKP_BLK_HDRSIZE;
		free(msg->data);
		msg->data = NULL;
	}
	
	return 0;
}

/*****************************************************************************
 * Send as much buffered data as possible (non-blocking)
 *
 * Arguments:
 * 	bpi       binkp state structure
 *
 * Return value:
 * 	Zero on success, and non-zero value on errors
 */
int binkp_send_buffer(s_bpinfo *bpi)
{
	int n;

	DEB((D_PROT, "binkp_send_buffer: sending %ld byte(s)", (long)bpi->opos));
	
	n = tty_write(bpi->optr, bpi->opos);
	
	if( n >= bpi->opos )
		{ bpi->optr = bpi->obuf; bpi->opos = 0; }
	else if( n > 0 )
		{ bpi->optr += n; bpi->opos -= n; }
	else
		return -1;
	
	return 0;
}

/*****************************************************************************
 * Send as much buffered data or messages from the queue as possible
 *
 * Arguments:
 * 	bpi       binkp state structure
 *
 * Return value:
 * 	Zero on success, and non-zero value on errors
 */
int binkp_send(s_bpinfo *bpi)
{
	int i;
	
	if( bpi->opos == 0 && bpi->msgqueue )
	{
		/*
		 *  Buffer is empty and there are unsent msgs
		 */
		for( i = 0; i < bpi->n_msgs; i++ )
		{
			if( bpi->msgqueue[i].sent == FALSE )
			{
				if( binkp_buffer_message(bpi, &bpi->msgqueue[i]) )
					break;
				else
					bpi->msgqueue[i].sent = TRUE;
			}
		}
		/* If the message queue is empty, free it */
		if( i >= bpi->n_msgs )
		{
			if( bpi->msgqueue )
				{ free(bpi->msgqueue); bpi->msgqueue = NULL; }
			bpi->n_msgs = 0;
		}
	}

	return bpi->opos ? binkp_send_buffer(bpi) : 0;
}

/*****************************************************************************
 * Flush all buffers and queues that can contain unsent data
 *
 * Arguments:
 * 	bpi       binkp state structure
 * 	timeout   abort flushing if this time will be exceeded
 *
 * Return value:
 * 	Zero on success, and non-zero value on errors
 */
int binkp_flush_queue(s_bpinfo *bpi, int timeout)
{
	bool send_ready = FALSE;
	time_t flush_timer;
	
	timer_set(&flush_timer, timeout);
	
	while( bpi->opos > 0 || bpi->n_msgs > 0 )
	{
		if( timer_expired(flush_timer)
		 || tty_select(NULL, &send_ready, 10) < 0 )
			return -1;
		 
		if( send_ready && binkp_send(bpi) < 0 )
			return -1;
	}

	return 0;
}

/*****************************************************************************
 * Try to receive next message or data block in non-blocking mode
 *
 * Arguments:
 * 	bpi       binkp state structure
 *
 * Return value:
 * 	One of the BPMSG_* values
 */
int binkp_recv(s_bpinfo *bpi)
{
	int n = 0;
	int size;

	if( bpi->inew || bpi->isize == -1 )
	{
		bpi->inew  = FALSE;
		bpi->isize = -1;
		size = BINKP_BLK_HDRSIZE;
	}
	else
		size = bpi->isize;
	
	if( size > 0 )
	{
		n = tty_read(bpi->ibuf + bpi->ipos, size - bpi->ipos);
		if( n <= 0 ) return BPMSG_EXIT;
	}
	
	bpi->ipos += n;
	
	if( bpi->ipos == size )
	{
		if( bpi->isize == -1 )
		{
			/* We got block header */
			bpi->ipos  = 0;
			bpi->imsg  = ((bpi->ibuf[0] >> 7) & 0377) ? TRUE : FALSE;
			bpi->isize = binkp_gethdr(bpi->ibuf);
			DEB((D_PROT, "binkp_recv: received header: %ld byte(s) (%s)",
				(long)bpi->isize, bpi->imsg ? "msg" : "data"));
			if( bpi->isize > BINKP_MAX_BLKSIZE )
			{
				log("internal error: got %ld bytes block size", bpi->isize);
				return BPMSG_EXIT;
			}
			else if( bpi->isize == 0 )
			{
				bpi->ipos  = 0;
				bpi->inew  = TRUE;
				bpi->isize = -1;
				if( bpi->imsg == TRUE )
					log("zero length message from remote");
			}
		}
		else /* We got whole data block/message */
		{
			if( bpi->imsg == TRUE ) /* Is it message? */
			{
				bpi->ipos = 0;
				bpi->inew = TRUE;
				bpi->ibuf[size] = '\0';
				bpi->imsgtype = binkp_getmsgtype(bpi->ibuf[0]);
				DEB((D_PROT, "binkp_recv: got message #%d, %ld byte(s), \"%s\"",
					(int)bpi->imsgtype, bpi->isize, bpi->ibuf+1));
				if( bpi->imsgtype < 0 )
				{
					bpi->isize = -1;
					log("got incorrect message type");
					if( ++bpi->junkcount >= 5 )
					{
						log("junk count exceeded");
						return BPMSG_EXIT;
					}
					return BPMSG_NONE;
				}
				return bpi->imsgtype;
			}
			else /* It is data */
			{
				bpi->ipos = 0;
				bpi->inew = TRUE;
				bpi->ibuf[size] = '\0';
				DEB((D_PROT, "binkp_recv: got data block, %ld byte(s)",
					(long)bpi->isize));
				return BPMSG_DATA;
			}
		}
	}
	
	return BPMSG_NONE;
}

/*****************************************************************************
 * Send (queue) system information to the remote
 *
 * Arguments:
 * 	bpi       binkp state structure
 * 	binkp     structure with the system information
 *
 * Return value:
 * 	None
 */
void binkp_queue_sysinfo(s_bpinfo *bpi, s_binkp_sysinfo *binkp)
{
	int i;
	char *astr = NULL;
	char abuf[BF_MAXADDRSTR+1];
	
	if( !state.caller && binkp->challenge_length > 0 )
	{
		char challenge[128];
		string_bin_to_hex(challenge, binkp->challenge, binkp->challenge_length);
		binkp_queuemsgf(bpi, BPMSG_NUL, "OPT CRAM-MD5-%s", challenge);
	}	
	
	binkp_queuemsg(bpi, BPMSG_NUL, "SYS ", binkp->systname);
	binkp_queuemsg(bpi, BPMSG_NUL, "ZYZ ", binkp->sysop);
	binkp_queuemsg(bpi, BPMSG_NUL, "LOC ", binkp->location);
	binkp_queuemsg(bpi, BPMSG_NUL, "PHN ", binkp->phone);
	binkp_queuemsg(bpi, BPMSG_NUL, "NDL ", binkp->flags);

	binkp_queuemsg(bpi, BPMSG_NUL, "TIME ", binkp->timestr);
	
	binkp_queuemsgf(bpi, BPMSG_NUL, "VER %s %s/%d.%d",
		binkp->progname, binkp->protname,
		binkp->majorver, binkp->minorver);

	for( i = 0; i < binkp->anum; i++ )
	{
		if( i ) astr = xstrcat(astr, " ");
		astr = xstrcat(astr, ftn_addrstr(abuf, binkp->addrs[i].addr));
	}
	
	if( astr )
	{
		binkp_queuemsg(bpi, BPMSG_ADR, NULL, astr);
		free(astr);
	}
}

/*****************************************************************************
 * Write system information to the log
 *
 * Arguments:
 * 	binkp     structure with the system information
 *
 * Return value:
 * 	None
 */
void binkp_log_sysinfo(s_binkp_sysinfo *binkp)
{
	int i;
	char abuf[BF_MAXADDRSTR+1];
	
	if( binkp->anum )
		for( i = 0; i < binkp->anum; i++ )
		{
			log("   Address : %s", ftn_addrstr(abuf, binkp->addrs[i].addr));
		}
	
	if( *binkp->systname && *binkp->phone )
		log("    System : %s (%s)",
			string_printable(binkp->systname),
			string_printable(binkp->phone));
	else if( *binkp->systname )
		log("    System : %s",
			string_printable(binkp->systname));
	else if( *binkp->phone )
		log("     Phone : %s",
			string_printable(binkp->phone));

#ifdef BFORCE_LOG_PASSWD
	if( *binkp->passwd )
		log("  Password : %s", string_printable(binkp->passwd));
#endif

	if( *binkp->opt )
		log("   Options : %s", string_printable(binkp->opt));

	if( *binkp->sysop && *binkp->location )
		log("     SysOp : %s from %s",
			string_printable(binkp->sysop),
			string_printable(binkp->location));
	else if( *binkp->sysop )
		log("     SysOp : %s",
			string_printable(binkp->sysop));
	else if( *binkp->location )
		log("  Location : %s",
			string_printable(binkp->location));

	if( *binkp->progname )
	{
		log("    Mailer : %s (%s/%d.%d)",
			*binkp->progname ? string_printable(binkp->progname) : "?",
			*binkp->protname ? string_printable(binkp->protname) : "?",
			binkp->majorver, binkp->minorver);
	}
	if( *binkp->flags )
		log("     Flags : %s", string_printable(binkp->flags));
	if( *binkp->timestr )
		log("      Time : %s", string_printable(binkp->timestr));
}

/*****************************************************************************
 * Set our local system information 
 *
 * Arguments:
 * 	binkp       structure where to store system information
 * 	remote_addr remote main address (for setting best AKA)
 * 	caller      are we calling system?
 *
 * Return value:
 * 	None
 */
void binkp_set_sysinfo(s_binkp_sysinfo *binkp, s_faddr *remote_addr, bool caller)
{
	s_cval_entry *addr_ptr;
	s_cval_entry *hide_ptr;
	s_faddr *primary = NULL;
	
	const char *p_systname  = conf_string(cf_system_name);
	const char *p_location  = conf_string(cf_location);
	const char *p_sysop     = conf_string(cf_sysop_name);
	const char *p_phone     = conf_string(cf_phone);
	const char *p_flags     = conf_string(cf_flags);
	
	/* free previously allocated memory */
	if( binkp->addrs )
		free(binkp->addrs);

	memset(binkp, '\0', sizeof(s_binkp_sysinfo));
	
	/* Set best primary address */
	if( remote_addr )
	{
		primary = session_get_bestaka(*remote_addr);
		
		/* Add primary address */
		if( primary )
			session_addrs_add(&binkp->addrs, &binkp->anum, *primary);
	}

	/* Add other AKAs */
	for( addr_ptr = conf_first(cf_address); addr_ptr;
	     addr_ptr = conf_next(addr_ptr) )
	{
		for( hide_ptr = conf_first(cf_hide_our_aka); hide_ptr;
		     hide_ptr = conf_next(hide_ptr) )
		{
			if( !ftn_addrcomp(hide_ptr->d.falist.addr, addr_ptr->d.falist.addr) )
				break;
		}
		
		if( !hide_ptr && primary != &addr_ptr->d.falist.addr )
			session_addrs_add(&binkp->addrs, &binkp->anum,
					addr_ptr->d.falist.addr);
	}
	
	if( binkp->anum == 0 )
		log("warning: no addresses will be presented to remote");

	/* session password */
	if( caller )
		session_get_password(state.node.addr, binkp->passwd, sizeof(binkp->passwd));
	
	binkp->majorver = BINKP_MAJOR;
	binkp->minorver = BINKP_MINOR;
	
	strnxcpy(binkp->progname, BF_NAME"/"BF_VERSION"/"BF_OS, sizeof(binkp->progname));
	strnxcpy(binkp->protname, BINKP_NAME, sizeof(binkp->protname));
	
	strnxcpy(binkp->sysop,    p_sysop    ? p_sysop    : "Unknown", sizeof(binkp->sysop));
	strnxcpy(binkp->systname, p_systname ? p_systname : "Unknown", sizeof(binkp->systname));
	strnxcpy(binkp->location, p_location ? p_location : "Unknown", sizeof(binkp->location));
	strnxcpy(binkp->phone,    p_phone    ? p_phone    : "Unknown", sizeof(binkp->phone));
	strnxcpy(binkp->flags,    p_flags    ? p_flags    : "BINKP",   sizeof(binkp->flags));

	time_string_format(binkp->timestr, sizeof(binkp->timestr), "%Y/%m/%d %H:%M:%S", 0);
	
	/* Set session challenge string */
	if( !caller )
	{
		long rnd = (long)random();
		int  pid = ((int)getpid()) ^ ((int)random());
		long utm = (long)time(0);

		binkp->options |= BINKP_OPT_MD5;
		binkp->challenge[0] = (unsigned char)(rnd      );
		binkp->challenge[1] = (unsigned char)(rnd >> 8 );
		binkp->challenge[2] = (unsigned char)(rnd >> 16);
		binkp->challenge[3] = (unsigned char)(rnd >> 24);
		binkp->challenge[4] = (unsigned char)(pid      );
		binkp->challenge[5] = (unsigned char)(pid >> 8 );
		binkp->challenge[6] = (unsigned char)(utm      );
		binkp->challenge[7] = (unsigned char)(utm >> 8 );
		binkp->challenge[8] = (unsigned char)(utm >> 16);
		binkp->challenge[9] = (unsigned char)(utm >> 24);

		binkp->challenge_length = 10;
	}
}

void binkp_parse_options(s_binkp_sysinfo *binkp, char *options)
{
	char *p, *n;

	for( p = string_token(options, &n, NULL, 0); p;
	     p = string_token(NULL, &n, NULL, 0) )
	{
		if( !strcmp(p, "NR") )
			binkp->options |= BINKP_OPT_NR;
		else if( !strcmp(p, "MB") )
			binkp->options |= BINKP_OPT_MB;
		else if( !strcmp(p, "MPWD") )
			binkp->options |= BINKP_OPT_MPWD;
		else if( !strncmp(p, "CRAM-", 5) )
		{
			char *hash_types = p + 5;
			char *challenge = strchr(hash_types, '-');

			if( challenge )
			{
				char *pp, *nn;
				
				*challenge++ = '\0';
				for( pp = string_token(hash_types, &nn, "/", 0); pp;
				     pp = string_token(NULL, &nn, "/", 0) )
				{
					if( !strcmp(pp, "SHA1") )
						binkp->options |= BINKP_OPT_SHA1;
					else if( !strcmp(pp, "MD5") )
						binkp->options |= BINKP_OPT_MD5;
					else if( !strcmp(pp, "DES") )
						binkp->options |= BINKP_OPT_DES;
				}
				
				if( strlen(challenge) > (2*sizeof(binkp->challenge)) )
					log("binkp got too long challenge string");
				else
					binkp->challenge_length = string_hex_to_bin(
							binkp->challenge, challenge);
			}
			else
				log("binkp got invalid option: \"%s\"", string_printable(p));
		}
	} 
}

