// --------------------------------------------------------------------------
// Citadel: Net.CPP
//
// General-purpse networking code; not specific to any one protocol.

#include "ctdl.h"
#pragma hdrstop

#include "log.h"
#include "net.h"
#include "msg.h"
#include "filecmd.h"
#include "aplic.h"
#include "cwindows.h"
#include "term.h"


// --------------------------------------------------------------------------
// Contents
//
// net_slave()		network entry point from LOGIN
// net_master() 	entry point to call a node
// n_dial() 		call the bbs in the node buffer
// n_login()		Login to the bbs with the macro in the node file
// wait_for()		wait for a string to come from the modem
// net_callout()	Entry point from CRON.CPP

static int n_dial(void);
static Bool n_login(void);

// In Net1.CPP
Bool Net1Slave(void);
Bool Net1Master(void);
void Net1Cleanup(void);


// --------------------------------------------------------------------------
// net_slave(): network entry point from LOGIN
//
// Return value:
//	TRUE if worked, FALSE if not.

Bool net_slave(void)
	{
	TI()callout = TRUE;

	if (!read_net_messages())
		{
		cPrintf(getmsg(59));
		TI()callout = FALSE;
		return (FALSE);
		}

	if (!readnode(TRUE))
		{
		cPrintf(getmsg(188), getnetmsg(186));
		TI()callout = FALSE;
		dump_net_messages();
		return (FALSE);
		}

	if (!*TI()CurrentUser->GetAlias() || !*TI()CurrentUser->GetLocID())
		{
		dump_net_messages();
		TI()callout = FALSE;
		return (FALSE);
		}

	if (!onConsole)
		{
		TI()netError = FALSE;

		// cleanup
		changedir(cfg.temppath);
		ambigUnlink(getnetmsg(162), FALSE);
		ambigUnlink(getnetmsg(163), FALSE);
		unlink(roomreqIn);
		unlink(roomreqOut);
		unlink(roomdataIn);
		unlink(roomdataOut);
		unlink(getnetmsg(164));
		unlink(getnetmsg(165));

		switch (TI()node->GetNetworkType())
			{
			default:
			case NET_DCIT10:
				{
				Net1Slave();
				if (Net1Master())
					{
					Net1Cleanup();
					buildaddress();
					did_net(TI()node->GetName());
					}
				else
					{
					dump_net_messages();
					TI()callout = FALSE;
					return (FALSE);
					}

				break;
				}

			case NET_DCIT15:
			case NET_DCIT16:
				{
				if (dc15network(FALSE))
					{
					Net1Cleanup();
					buildaddress();
					did_net(TI()node->GetName());
					}
				else
					{
					dump_net_messages();
					TI()callout = FALSE;
					return (FALSE);
					}

				break;
				}

			case NET_6_9:
				{
				if (net69(FALSE))
					{
					did_net(TI()node->GetName());
					}
				else
					{
					dump_net_messages();
					TI()callout = FALSE;
					return (FALSE);
					}

				break;
				}

			case NET_C86:
				{
				if (!Citadel86Network(FALSE))
					{
					dump_net_messages();
					TI()callout = FALSE;
					return (FALSE);
					}

				did_net(TI()node->GetName());
				break;
				}

#ifdef HENGE
			case NET_HENGE:
				{
				if (!hengeNet(FALSE))
					{
					dump_net_messages();
					TI()callout = FALSE;
					return (FALSE);
					}

				break;
				}
#endif
			}
		}
	else
		{
		dump_net_messages();
		TI()callout = FALSE;
		return (FALSE);
		}

	dump_net_messages();
	TI()callout = FALSE;
	return (TRUE);
	}

// --------------------------------------------------------------------------
// net_master(): Entry point to call a node
//
// Return value:
//	TRUE: Network worked.
//	FALSE: It didn't.

static Bool net_master(void)
	{
	int tries = 1;
	int connected = FALSE;

	if (!readnode(FALSE))
		{
		doccr();
		cPrintf(getmsg(206), TI()CurrentUser->GetName());
		doccr();
		return (FALSE);
		}

	// cleanup
	changedir(cfg.temppath);
	ambigUnlink(getnetmsg(162), FALSE);
	ambigUnlink(getnetmsg(163), FALSE);
	unlink(roomreqIn);
	unlink(roomreqOut);
	unlink(roomdataIn);
	unlink(roomdataOut);
	unlink(getnetmsg(164));
	unlink(getnetmsg(165));

	do
		{
		if (tries > 1)
			{
			doccr();
			cPrintf(getnetmsg(130), ltoac(tries));
			}

		if ((connected = n_dial()) == CERROR)
			{
			return (FALSE);
			}
		} while (tries++ < TI()node->GetRedial() && !connected);

	if (!connected)
		{
		return (FALSE);
		}

	if (TI()node->GetNetworkType() == NET_C86)
		{
		if (!net86_fetch())
			{
			return (FALSE);
			}
		}

	if (!n_login())
		{
		return (FALSE);
		}

	// We have logged in: Call it a new call.
	slideLTab(TI()ThisSlot);
	cfg.callno++;

	TI()netError = FALSE;

	switch (TI()node->GetNetworkType())
		{
		default:
		case NET_DCIT10:
			{
			if (Net1Master() == FALSE)
				{
				return (FALSE);
				}

			if (Net1Slave() == FALSE)
				{
				return (FALSE);
				}

			Net1Cleanup();
			buildaddress();
			break;
			}

		case NET_DCIT15:
		case NET_DCIT16:
			{
			if (!dc15network(TRUE))
				{
				return (FALSE);
				}

			Net1Cleanup();
			buildaddress();
			break;
			}

		case NET_6_9:
			{
			if (!net69(TRUE))
				{
				return (FALSE);
				}

			break;
			}

		case NET_C86:
			{
			if (!Citadel86Network(TRUE))
				{
				return (FALSE);
				}

			break;
			}

#ifdef HENGE
		case NET_HENGE:
			{
			if (!hengeNet(TRUE))
				{
				return (FALSE);
				}

			break;
			}
#endif
		}

	CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
			CitWindowsMsg(NULL, getmsg(1611));

	Initport();

	if (w)
		{
		destroyCitWindow(w, FALSE);
		}

	return (TRUE);
	}


// --------------------------------------------------------------------------
// wait_for(): Wait for a string to come from the modem
//
// Input:
//	const char *str: The string to wait for
//	int timeout: The number of seconds to wait

Bool wait_for(const char *str, int timeout)
	{
	char line[80];
	long st;
	int i, stl;

	stl = strlen(str);

	if (stl)
		{
		for (i = 0; i < stl; i++)
			{
			line[i] = '\0';
			}

		time(&st);

		for (;;)
			{
			if (TI()SerialPort.IsInputReady())
				{
				memcpy(line, line+1, stl);
				line[stl-1] = (char) TI()SerialPort.Input();
				line[stl] = '\0';

				if (debug)
					{
					outCon(line[stl-1]);
					}

				if (SameString(line, str))
					{
					return (TRUE);
					}
				}
			else
				{
				if ((time(NULL) - st) > (long)timeout || !SerialPort.HaveCarrier())
					{
					return (FALSE);
					}

				if (KBReady())	// Aborted by user
					{
					ciChar();
					return (FALSE);
					}
				}
			}
		}
	else
		{
		return (FALSE);
		}
	}

// --------------------------------------------------------------------------
// net_callout(): Entry point from RUNCRON.CPP
//
// Input:
//	const char *nodenm: Name of node to net with.
//
// Return value:
//	TRUE: It worked.
//	FALSE: It didn't work.

Bool net_callout(const char *nodenm)
	{
	int slot;
	int tmp, tmp2;
	char String[80];

	if (!read_net_messages())
		{
		cPrintf(getmsg(59));
		return (FALSE);
		}

	SetDoWhat(NETWORKING);

	strftime(String, sizeof(String) - 1, cfg.vdatestamp, 0l);
	cPrintf(getnetmsg(170), String);
	doccr();

	if (!getnode(&TI()node, nodenm, MODEM, FALSE))
		{
		cPrintf(getmsg(206), nodenm);
		doccr();
		dump_net_messages();
		return (FALSE);
		}

	// login user
	TI()MS.Read = 0;
	TI()MS.Entered = 0;

	slot = FindPersonByName(TI()node->GetName());

	if (slot == CERROR)
		{
		cPrintf(getnetmsg(13));
		dump_net_messages();
		return (FALSE);
		}

	TI()ThisSlot = slot;
	TI()ThisLog = LogTab->GetEntry(slot)->GetLogIndex();
	TI()loggedIn = TRUE;

	if (TI()CurrentUser->Load(TI()ThisLog))
		{
		// How silly can you get???
		TI()OC.Psycho = TI()CurrentUser->IsPsycho();

		setlogTerm();
		setlbvisit();
		StatusLine.Update();

		TI()callout = TRUE;

		sprintf(String, getnetmsg(14), TI()CurrentUser->GetName());
		trap(String, T_NETWORK);

		// node logged in
		tmp = net_master();

		// be sure to hang up
		if (SerialPort.HaveCarrier())
			{
			CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
					CitWindowsMsg(NULL, getmsg(1643));

			Hangup();

			if (w)
				{
				destroyCitWindow(w, FALSE);
				}
			}

		// terminate user
		if (tmp == TRUE)
			{
			TI()CurrentUser->SetCallNumber(cfg.callno);
			time(&TI()logtimestamp);
			TI()CurrentUser->SetCallTime(TI()logtimestamp);

			TI()CurrentUser->SetLastMessage(cfg.newest);

			storeLog();

			TI()loggedIn = FALSE;

			// trap it
			sprintf(String, getnetmsg(15), TI()CurrentUser->GetName());
			trap(String, T_NETWORK);

			TI()UserControl.SetOutFlag(IMPERVIOUS);

			cPrintf(getnetmsg(16), TI()CurrentUser->GetName());

			if (TI()MS.AbortedMessage)
				{
				delete TI()MS.AbortedMessage;
				TI()MS.AbortedMessage = NULL;
				}

			sprintf(String, getnetmsg(17), TI()MS.Entered);
			trap(String, T_NETWORK);

			sprintf(String, getnetmsg(18), TI()MS.Read);
			trap(String, T_NETWORK);

			sprintf(String, getnetmsg(19), TI()expired);
			trap(String, T_NETWORK);

			sprintf(String, getnetmsg(20), TI()duplicate);
			trap(String, T_NETWORK);
			}
		else
			{
			TI()loggedIn = FALSE;

			sprintf(String, getnetmsg(21), TI()CurrentUser->GetName());

			tmp2 = cfg.trapit[T_NETWORK];

			if (TI()node->GetNetFail() > 0)
				{
				cfg.trapit[T_NETWORK] = TRUE;
				}

			if (TI()node->GetNetFail() < 0)
				{
				cfg.trapit[T_NETWORK] = FALSE;
				}

			trap(String, T_NETWORK);

			cfg.trapit[T_NETWORK] = tmp2;
			}
		}
	else
		{
		TI()loggedIn = FALSE;
		}

	setdefaultconfig(FALSE);

	// user terminated
	TI()callout = FALSE;

	CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
			CitWindowsMsg(NULL, getmsg(1611));

	Initport();

	if (w)
		{
		destroyCitWindow(w, FALSE);
		}

	freeNode(&TI()node);

	dump_net_messages();
	return (tmp);
	}


// --------------------------------------------------------------------------
// n_dial(): Call the bbs in the node buffer
//
// Return value:
//	TRUE: Connected
//	FALSE: Didn't connect; retry if #REDIAL set in NODES.CIT
//	CERROR: User abort; don't retry

static int n_dial(void)
	{
	int checkbaud;

	doccr();

	TI()SerialPort.SetSpeed(TI()node->GetBaud());
	StatusLine.Update();

	outstring(cfg.dialsetup);
	outstring(br);

	if (*TI()node->GetPreDial())
		{
		if (debug)
			{
			cPrintf(getdbmsg(3), TI()node->GetPreDial());
			doccr();
			}

		outstring(TI()node->GetPreDial());
		outstring(br);
		}

	if (debug)
		{
		cPrintf(getdbmsg(4), cfg.dialpref, TI()node->GetDialOut());
		}
	else
		{
		cPrintf(getnetmsg(40));
		}

	pause(100);

	TI()SerialPort.Flush();

	checkbaud = dial(TI()node->GetDialOut(), TI()node->GetDialTimeout());

	if (checkbaud && (cfg.dumbmodem == 4 || cfg.dumbmodem == 5 ||
			cfg.dumbmodem == 6 || cfg.dumbmodem == 7))
		{
		TI()SerialPort.SetSpeed(TI()node->GetBaud());
		}

	if (checkbaud == TRUE)
		{
		pause(100);
		}

	StatusLine.Update();

	if (checkbaud != FALSE) // TRUE || CERROR
		{
		char TrapString[256];

		sprintf(TrapString, getnetmsg(44), TI()modem_result);
		trap(TrapString, T_NETWORK);
		}

	switch (checkbaud)
		{
		case TRUE:
			{
			TI()OC.ansiattr = cfg.wattr;
			cPrintf(getnetmsg(45));
			TI()OC.ansiattr = cfg.attr;
			cPrintf(getnetmsg(170), TI()modem_result);
			return (TRUE);
			}

		case CERROR:
			{
			TI()OC.ansiattr = cfg.wattr;
			cPrintf(getnetmsg(46));
			TI()OC.ansiattr = cfg.attr;
			cPrintf(getnetmsg(170), TI()modem_result);
			return (FALSE);
			}
		case FALSE:
			{
			return (FALSE);
			}
		default:	// -3
			{
			return (CERROR);
			}
		}

	}


// --------------------------------------------------------------------------
// dial(): Actually make the call.
//
// Input:
//	const char *dialstring: String to send to modem to call
//	int timeout: How long to wait for carrier, in seconds
//
// Return value:
//	Whatever smartbaud() returns, or -3 if manually aborted.

int dial(const char *dialstring, int timeout)
	{
	time_t ts, tx, ty;
	int checkbaud;

	pause(100);
	TI()SerialPort.Flush();

	outstring(cfg.dialpref);
	outstring(dialstring);
	outstring(br);

	tx = time(&ts);

	for (;;)
		{
		if ((int) (time(&ty) - ts) > timeout)
			{
			TI()OC.ansiattr = cfg.wattr;
			cPrintf(getnetmsg(41));
			TI()OC.ansiattr = cfg.attr;

			return (FALSE);
			}

		if (tx != ty)
			{
			if (debug)
				{
				cPrintf(br);
				cPrintf(getdbmsg(5), cfg.dialpref, dialstring,
						ltoac(ty - ts));
				}

			tx = ty;
			}

		if (KBReady())	// User abort
			{
			ciChar();
			TI()OC.ansiattr = cfg.wattr;
			cPrintf(getnetmsg(43));
			TI()OC.ansiattr = cfg.attr;

			return (-3);
			}

		if (TI()SerialPort.IsInputReady())
			{
			checkbaud = smartbaud();

			if (checkbaud != FALSE) // TRUE || CERROR
				{
				return (checkbaud);
				}
			}
		}
	}


// --------------------------------------------------------------------------
// n_login(): Parse login macro
//
// Input:
//	char *line: Macro for loggin in. Will be modified.
//
// Return value:
//	TRUE if was able to parse and execute
//	FALSE if failed

static Bool parseLine(char *line)
	{
	int i, j, k;
	char *words[128];
	char newline[82];
	char done;

	const int count = parse_it(words, line);

	i = -1;

	while (++i < count)
		{
		switch (tolower(*words[i++]))
			{
			case 'd':
				{
				if (debug)
					{
					TI()OC.ansiattr = cfg.wattr;
					cPrintf(getdbmsg(6), words[i], words[i + 1],
							words[i + 2]);
					TI()OC.ansiattr = cfg.attr;
					cPrintf(spc);
					}
				else
					{
					cPrintf(getmsg(1433));
					}

				const protocols *theProt = GetProtocolByKey(words[i][0],
						TRUE);

				if (!theProt)
					{
					return (FALSE);
					}

				wxrcv(words[i + 1], words[i + 2], theProt);

				i += 2;
				break;
				}

			case 'p':
				{
				if (debug)
					{
					TI()OC.ansiattr = cfg.wattr;
					cPrintf(getdbmsg(7), words[i]);
					TI()OC.ansiattr = cfg.attr;
					cPrintf(spc);
					}
				else
					{
					cPrintf(getmsg(1433));
					}

				netpause(atoi(words[i]) * 100);
				break;
				}

			case 'r':
				{
				if (debug)
					{
					TI()OC.ansiattr = cfg.wattr;
					cPrintf(getdbmsg(8), words[i], words[i+1], words[i+2]);
					TI()OC.ansiattr = cfg.attr;
					cPrintf(spc);
					}
				else
					{
					cPrintf(getmsg(1433));
					}

				k = atoi(words[i+2]);

				for (done = FALSE, j = 0; j < k && !done; j++)
					{
					if (debug)
						{
						TI()OC.ansiattr = cfg.wattr;
						cPrintf(getdbmsg(9), words[i]);
						TI()OC.ansiattr = cfg.attr;
						cPrintf(spc);
						}
					else
						{
						cPrintf(getmsg(1433));
						}

					if (!wait_for(words[i], TI()node->GetWaitTimeout()))
						{
						if (debug)
							{
							TI()OC.ansiattr = cfg.wattr;
							cPrintf(getdbmsg(10), words[i+1]);
							TI()OC.ansiattr = cfg.attr;
							cPrintf(spc);
							}
						else
							{
							cPrintf(getmsg(1433));
							}

						OutStringWithPacing(words[i+1], TI()node->GetOutputPace());
						}
					else
						{
						done = TRUE;
						}
					}

				i += 2;

				if (!done)
					{
					return (FALSE);
					}

				break;
				}

			case 's':
				{
				if (debug)
					{
					TI()OC.ansiattr = cfg.wattr;
					cPrintf(getdbmsg(10), words[i]);
					TI()OC.ansiattr = cfg.attr;
					cPrintf(spc);
					}
				else
					{
					cPrintf(getmsg(1433));
					}

				OutStringWithPacing(words[i], TI()node->GetOutputPace());
				break;
				}

			case 'u':
				{
				if (debug)
					{
					TI()OC.ansiattr = cfg.wattr;
					cPrintf(getdbmsg(11), words[i], words[i + 1],
							words[i + 2]);
					TI()OC.ansiattr = cfg.attr;
					cPrintf(spc);
					}
				else
					{
					cPrintf(getmsg(1433));
					}

				const protocols *theProt = GetProtocolByKey(words[i][0],
						TRUE);

				if (!theProt)
					{
					return (FALSE);
					}

				wxsnd(words[i + 1], words[i + 2], theProt, 0);

				i += 2;
				break;
				}

			case 'w':
				{
				if (debug)
					{
					TI()OC.ansiattr = cfg.wattr;
					cPrintf(getdbmsg(9), words[i]);
					TI()OC.ansiattr = cfg.attr;
					cPrintf(spc);
					}
				else
					{
					cPrintf(getmsg(1433));
					}

				if (!wait_for(words[i], TI()node->GetWaitTimeout()))
					{
					return (FALSE);
					}

				break;
				}

			case '!':
				{
				RunApplication(words[i], NULL, TRUE, TRUE);
				break;
				}

			case '@':
				{
				FILE *file;

				if ((file = fopen(words[i], FO_R)) != NULL)
					{
					while (fgets(newline, 80, file))
						{
						if (!parseLine(newline))
							{
							fclose(file);
							return (FALSE);
							}
						}

					fclose(file);
					}
				else
					{
					return (FALSE);
					}

				break;
				}

			default:
				{
				TI()OC.ansiattr = (uchar)(cfg.cattr | 128);
				cPrintf(getnetmsg(52), words[i-1], words[i]);
				TI()OC.ansiattr = cfg.attr;
				cPrintf(spc);
				break;
				}
			}
		}

	return (TRUE);
	}


// --------------------------------------------------------------------------
// n_login(): Login to the bbs with the #LOGIN macro in node buffer
//
// Return value:
//	TRUE if worked
//	FALSE if not

static Bool n_login(void)
	{
	changedir(cfg.homepath);	// for @ command
	doccr();
	cPrintf(getnetmsg(53));

	char Macro[256];

	strcpy(Macro, TI()node->GetLoginMacro());

	if (!parseLine(Macro))
		{
		TI()OC.ansiattr = cfg.wattr;
		cPrintf(getnetmsg(54));
		TI()OC.ansiattr = cfg.attr;

		return (FALSE);
		}

	TI()OC.ansiattr = cfg.wattr;
	cPrintf(getnetmsg(45));
	TI()OC.ansiattr = cfg.attr;
	doccr();
	doccr();

	return (TRUE);
	}
