/* Synchronet External Plain Old Telephone System (POTS) support */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * 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.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

/* ANSI C */
#include <stdarg.h>
#include <stdio.h>
#if defined(__unix__)
#include <signal.h>
#endif

/* xpdev lib */
#include "dirwrap.h"
#include "datewrap.h"
#include "sockwrap.h"
#include "threadwrap.h"
#include "ini_file.h"

/* comio lib */
#include "comio.h"

/* sbbs */
#include "telnet.h"
#include "git_branch.h"
#include "git_hash.h"

/* constants */
#define NAME					"SEXPOTS"
#define TITLE					"Synchronet External POTS Support"
#define DESCRIPTION				"Connects a communications port (e.g. COM1) to a TCP port (e.g. Telnet)"
#define MDM_TILDE_DELAY			500	/* milliseconds */

/* global vars */
BOOL	daemonize=FALSE;
BOOL	cls=FALSE;
char	prompt[INI_MAX_VALUE_LEN+1];
int		prompt_timeout = 60; // seconds
char	termtype[INI_MAX_VALUE_LEN+1]	= NAME;
char	termspeed[INI_MAX_VALUE_LEN+1]	= "28800,28800";	/* "tx,rx", max length not defined */

char	mdm_init[INI_MAX_VALUE_LEN]		= "AT&F";
char	mdm_autoans[INI_MAX_VALUE_LEN]	= "ATS0=1";
char	mdm_cid[INI_MAX_VALUE_LEN]		= "AT+VCID=1";
char	mdm_cleanup[INI_MAX_VALUE_LEN]	= "ATS0=0";
char	mdm_answer[INI_MAX_VALUE_LEN]	= "ATA";
char	mdm_ring[INI_MAX_VALUE_LEN]		= "RING";
int		mdm_ringcount = 1;
int		mdm_ringinterval = 6000;
BOOL	mdm_null=FALSE;
BOOL	mdm_manswer=FALSE;		/* Manual Answer */

int		mdm_timeout=5;			/* seconds */
int		mdm_reinit=60;			/* minutes */
int		mdm_cmdretry=2;			/* command retries (total attempts-1) */

#ifdef _WIN32
char	com_dev[MAX_PATH+1]				= "COM1";
#else
char	com_dev[MAX_PATH+1]				= "/dev/ttyd0";
#endif
COM_HANDLE	com_handle=COM_HANDLE_INVALID;
BOOL	com_handle_passed=FALSE;
BOOL	com_alreadyconnected=FALSE;
BOOL	com_hangup=TRUE;
BOOL	com_debug=FALSE;
ulong	com_baudrate=0;
BOOL	com_parity=FALSE;
BOOL	com_parity_odd=FALSE;
size_t	com_byte_size=8;
size_t	com_stop_bits=1;
BOOL	dcd_ignore=FALSE;
int		dcd_timeout=10;	/* seconds */
ulong	dtr_delay=100;	/* milliseconds */
int		hangup_attempts=10;

BOOL	terminated=FALSE;
BOOL	terminate_after_one_call=FALSE;

SOCKET	sock=INVALID_SOCKET;
char	host[MAX_PATH+1]				= "localhost";
ushort	port=IPPORT_TELNET;

/* stats */
ulong	total_calls=0;
ulong	bytes_sent=0;
ulong	bytes_received=0;

/* .ini over-rideable */
int		log_level=LOG_INFO;
BOOL	pause_on_exit=FALSE;
BOOL	tcp_nodelay=TRUE;

/* telnet stuff */
BOOL	telnet=TRUE;
BOOL	debug_telnet=FALSE;
uchar	telnet_local_option[0x100];
uchar	telnet_remote_option[0x100];
BYTE	telnet_cmd[64];
int		telnet_cmdlen;
BOOL	telnet_advertise_cid=FALSE;

/* ident (RFC1413) server stuff */
BOOL	ident=FALSE;
ushort	ident_port=IPPORT_IDENT;
ulong	ident_interface=INADDR_ANY;
char	ident_response[INI_MAX_VALUE_LEN]	= "CALLERID:SEXPOTS";

/* Caller-ID stuff */
char	cid_name[64];
char	cid_number[64];

/****************************************************************************/
/****************************************************************************/
int usage(const char* fname)
{
	printf("usage: %s [ini file] [options]\n"
		"\nOptions:"
		"\n"
		"\n-null                 No 'AT commands' sent to modem"
		"\n-com <device>         Specify communications port device"
		"\n-baud <rate>          Specify baud rate for communications port"
		"\n-live [handle]        Communications port is already open/connected"
		"\n-nohangup             Do not hangup (drop DTR) after call"
		"\n-host <addr | name>   Specify TCP server hostname or IP address"
		"\n-port <number>        Specify TCP port number"
		"\n-debug                Enable debug log output"
#if defined(_WIN32)
		"\n\nNT Service\n"
		"\n-install              install and enable NT service (%s)"
		"\n-service              run as an NT service (background execution)"
		"\n-remove               remove NT service"
		"\n-enable               enable NT service (auto-start during boot)"
		"\n-disable              disable NT service"
#else
		"\n-syslog[=ident]       log to syslog rather than stdout/err"
#endif
		"\n"
		,getfname(fname)
#if defined(_WIN32)
		,NAME
#endif
		);

	return 0;
}

#if defined(_WIN32)

static WORD event_type(int level)
{
	switch(level) {
		case LOG_WARNING:
			return(EVENTLOG_WARNING_TYPE);
		case LOG_NOTICE:
		case LOG_INFO:
		case LOG_DEBUG:
			return(EVENTLOG_INFORMATION_TYPE);
	}
/*
	LOG_EMERG
	LOG_ALERT
	LOG_CRIT
	LOG_ERR
*/
	return(EVENTLOG_ERROR_TYPE);
}

static int syslog(int level, char *fmt, ...)
{
	va_list argptr;
	char sbuf[1024];
	char* p=sbuf;
	int	retval;
	static HANDLE event_handle;

    va_start(argptr,fmt);
    retval=vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);

	if(event_handle == NULL)
		event_handle = RegisterEventSource(
			NULL,		// server name for source (NULL = local computer)
			TITLE);		// source name for registered handle

	if(event_handle != NULL)
		ReportEvent(event_handle,	// event log handle
			event_type(level),		// event type
			0,						// category zero
			0,						// event identifier
			NULL,					// no user security identifier
			1,						// one string
			0,						// no data
			(LPCSTR*)&p,			// pointer to string array
			NULL);					// pointer to data

    return(retval);
}

#endif

/****************************************************************************/
/****************************************************************************/
void lputs(int level, const char* str)
{
	time_t		t;
	struct tm	tm;
	char		tstr[32];
#if defined(_WIN32)
	char dbgmsg[1024];
	_snprintf(dbgmsg,sizeof(dbgmsg),"%s %s", NAME, str);
	if(log_level==LOG_DEBUG)
		OutputDebugString(dbgmsg);
#endif

	if(level>log_level)
		return;

	if(daemonize) {
		syslog(level, "%s", str);
	} else {
		t=time(NULL);
		if(localtime_r(&t,&tm)==NULL)
			tstr[0]=0;
		else
			sprintf(tstr,"%d/%d %02d:%02d:%02d "
				,tm.tm_mon+1,tm.tm_mday
				,tm.tm_hour,tm.tm_min,tm.tm_sec);

		fprintf(level>=LOG_NOTICE ? stdout:stderr, "%s %s\n", tstr, str);
	}
}

/****************************************************************************/
/****************************************************************************/
void lprintf(int level, char *fmt, ...)
{
	va_list argptr;
	char sbuf[1024];

    va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
    lputs(level,sbuf);
}


#if defined(_WIN32)

#define NTSVC_TIMEOUT_STARTUP	30000	/* Milliseconds */
#define NTSVC_TIMEOUT_SHUTDOWN	30000	/* Milliseconds */

SERVICE_STATUS_HANDLE	svc_status_handle;
SERVICE_STATUS			svc_status;

void WINAPI ServiceControlHandler(DWORD dwCtrlCode)
{
	switch(dwCtrlCode) {
		case SERVICE_CONTROL_STOP:
		case SERVICE_CONTROL_SHUTDOWN:
			lprintf(LOG_NOTICE,"Received termination control signal: %d", dwCtrlCode);
			svc_status.dwWaitHint=NTSVC_TIMEOUT_SHUTDOWN;
			svc_status.dwCurrentState=SERVICE_STOP_PENDING;
			SetServiceStatus(svc_status_handle, &svc_status);
			terminated=TRUE;
//			SetEvent(service_event);
			break;
		case SERVICE_CONTROL_INTERROGATE:
			lprintf(LOG_DEBUG,"Ignoring service control signal: SERVICE_CONTROL_INTERROGATE");
			break;
		default:
			lprintf(LOG_WARNING,"Received unsupported service control signal: %d", dwCtrlCode);
			break;
	}
}

/* ChangeServiceConfig2 is a Win2K+ API function, must call dynamically */
typedef WINADVAPI BOOL (WINAPI *ChangeServiceConfig2_t)(SC_HANDLE, DWORD, LPCVOID);

static void describe_service(HANDLE hSCMlib, SC_HANDLE hService, char* description)
{
	ChangeServiceConfig2_t changeServiceConfig2;
	static SERVICE_DESCRIPTION service_desc;
  
	if(hSCMlib==NULL)
		return;

	service_desc.lpDescription=description;

	if((changeServiceConfig2 = (ChangeServiceConfig2_t)GetProcAddress(hSCMlib, "ChangeServiceConfig2A"))!=NULL)
		changeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &service_desc);
}

static BOOL register_event_source(char* name, char* path)
{
	char	keyname[256];
	HKEY	hKey;
	DWORD	type;
	DWORD	disp;
	LONG	retval;
	char*	value;

	sprintf(keyname,"system\\CurrentControlSet\\services\\eventlog\\application\\%s",name);

	retval=RegCreateKeyEx(
		HKEY_LOCAL_MACHINE,			/* handle to an open key */
		keyname,			/* address of subkey name */
		0,				/* reserved */
		"",				/* address of class string */
		0,				/* special options flag */
		KEY_ALL_ACCESS, /* desired security access */
		NULL,           /* address of key security structure */
		&hKey,			/* address of buffer for opened handle */
		&disp			/* address of disposition value buffer */
		);

	if(retval!=ERROR_SUCCESS) {
		fprintf(stderr,"!Error %d creating/opening registry key (HKLM\\%s)\n"
			,retval,keyname);
		return(FALSE);
	}

	value="EventMessageFile";
	retval=RegSetValueEx(
		hKey,			/* handle to key to set value for */
		value,			/* name of the value to set */
		0,				/* reserved */
		REG_SZ,			/* flag for value type */
		path,			/* address of value data */
		strlen(path)	/* size of value data */
		);

	if(retval!=ERROR_SUCCESS) {
		RegCloseKey(hKey);
		fprintf(stderr,"!Error %d setting registry key value (%s)\n"
			,retval,value);
		return(FALSE);
	}

	value="TypesSupported";
	type=EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
	retval=RegSetValueEx(
		hKey,			/* handle to key to set value for */
		value,			/* name of the value to set */
		0,				/* reserved */
		REG_DWORD,		/* flag for value type */
		(BYTE*)&type,	/* address of value data */
		sizeof(type)	/* size of value data */
		);

	RegCloseKey(hKey);

	if(retval!=ERROR_SUCCESS) {
		fprintf(stderr,"!Error %d setting registry key value (%s)\n"
			,retval,value);
		return(FALSE);
	}

	return(TRUE);
}


/****************************************************************************/
/* Install NT service														*/
/****************************************************************************/
static int install(void)
{
	HANDLE		hSCMlib;
    SC_HANDLE   hSCManager;
    SC_HANDLE   hService;
    char		path[MAX_PATH+1];
	char		cmdline[MAX_PATH+1];

	printf("Installing service: %-40s ... ", TITLE);

	hSCMlib = LoadLibrary("ADVAPI32.DLL");

    if(GetModuleFileName(NULL,path,sizeof(path))==0)
    {
        fprintf(stderr,"\n!ERROR %d getting module file name\n",GetLastError());
        return(-1);
    }

    hSCManager = OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if(hSCManager==NULL) {
		fprintf(stderr,"\n!ERROR %d opening SC manager\n",GetLastError());
		return(-1);
	}

	/* Install new service */
	wsprintf(cmdline,"%s service", path);
    hService = CreateService(
        hSCManager,						// SCManager database
        NAME,							// name of service
        TITLE,							// name to display
        SERVICE_ALL_ACCESS,				// desired access
        SERVICE_WIN32_OWN_PROCESS,		// service type
		SERVICE_AUTO_START,				// start type (auto or manual)
        SERVICE_ERROR_NORMAL,			// error control type
        cmdline,						// service's binary
        NULL,							// no load ordering group
        NULL,							// no tag identifier
        "",								// dependencies
        NULL,							// LocalSystem account
        NULL);							// no password

	if(hService==NULL)
		fprintf(stderr,"\n!ERROR %d creating service\n",GetLastError());
	else {
		describe_service(hSCMlib, hService, DESCRIPTION);
		CloseServiceHandle(hService);
		printf("Successful\n");

		register_event_source(TITLE,path);
	}


	if(hSCMlib!=NULL)
		FreeLibrary(hSCMlib);

	CloseServiceHandle(hSCManager);

	return(0);
}

/****************************************************************************/
/* Utility function to remove a service cleanly (stopping if necessary)		*/
/****************************************************************************/
static void remove_service(SC_HANDLE hSCManager, SC_HANDLE hService, char* name, char* disp_name)
{
	SERVICE_STATUS	status;

	printf("Removing service: %-40s ... ", disp_name);

	if(hService==NULL) {

		hService = OpenService(hSCManager, name, SERVICE_ALL_ACCESS);

		if(hService==NULL) {
			printf("\n!ERROR %d opening service: %s\n",GetLastError(),name);
			return;
		}
	}

    // try to stop the service
    if(ControlService( hService, SERVICE_CONTROL_STOP, &status))
    {
        printf("\nStopping: %s ... ",name);

        while(QueryServiceStatus(hService, &status) && status.dwCurrentState == SERVICE_STOP_PENDING)
			Sleep(1000);

        if(status.dwCurrentState == SERVICE_STOPPED)
            printf("Stopped.\n");
        else
            printf("FAILED!\n");
    }

    // now remove the service
    if(DeleteService(hService))
		printf("Successful\n");
	else
		printf("!ERROR %d\n",GetLastError());
    CloseServiceHandle(hService);
}

/****************************************************************************/
/* Uninstall one or all services											*/
/****************************************************************************/
static int uninstall(void)
{
    SC_HANDLE   hSCManager;

    hSCManager = OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if(hSCManager==NULL) {
		fprintf(stderr,"!ERROR %d opening SC manager\n",GetLastError());
		return(-1);
	}

	remove_service(hSCManager,NULL,NAME,TITLE);

	CloseServiceHandle(hSCManager);

	return(0);
}

/****************************************************************************/
/* Utility function to disable a service									*/
/****************************************************************************/
static void set_service_start_type(SC_HANDLE hSCManager, DWORD start_type)
{
    SC_HANDLE		hService;

	printf("%s service: %-40s ... "
		,start_type==SERVICE_DISABLED ? "Disabling" : "Enabling", TITLE);

    hService = OpenService(hSCManager, NAME, SERVICE_ALL_ACCESS);

	if(hService==NULL) {
		printf("\n!ERROR %d opening service: %s\n",GetLastError(),NAME);
		return;
	}

	if(!ChangeServiceConfig(
		hService,				// handle to service
		SERVICE_NO_CHANGE,		// type of service
		start_type,				// when to start service
		SERVICE_NO_CHANGE,		// severity if service fails to start
		NULL,					// pointer to service binary file name
		NULL,					// pointer to load ordering group name
		NULL,					// pointer to variable to get tag identifier
		NULL,					// pointer to array of dependency names
		NULL,					// pointer to account name of service
		NULL,					// pointer to password for service account
		NULL					// pointer to display name
		))
		printf("\n!ERROR %d changing service config for: %s\n",GetLastError(),NAME);
	else
		printf("Successful\n");

    CloseServiceHandle(hService);
}

/****************************************************************************/
/* Enable (set to auto-start) or disable one or all services				*/
/****************************************************************************/
static int enable(BOOL enabled)
{
    SC_HANDLE   hSCManager;

    hSCManager = OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if(hSCManager==NULL) {
		fprintf(stderr,"!ERROR %d opening SC manager\n",GetLastError());
		return(-1);
	}

	set_service_start_type(hSCManager
		,enabled ? SERVICE_AUTO_START : SERVICE_DISABLED);

	CloseServiceHandle(hSCManager);

	return(0);
}

#endif

#ifdef _WINSOCKAPI_

/* Note: Don't call WSACleanup() or TCP session will close! */
WSADATA WSAData;	

static BOOL winsock_startup(void)
{
	int		status;             /* Status Code */

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
		return(TRUE);
	}

    lprintf(LOG_ERR,"WinSock startup ERROR %d", status);
	return(FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)	

#endif

/****************************************************************************/
/****************************************************************************/
BOOL modem_send(COM_HANDLE com_handle, const char* str)
{
	const char* p;
	char		ch;

	lprintf(LOG_INFO,"Modem Command: %s", str);
	for(p=str; *p; p++) {
		ch=*p;
		if(ch=='~') {
			SLEEP(MDM_TILDE_DELAY);
			continue;
		}
		if(ch=='^' && *(p+1)) {	/* Support ^X for control characters embedded in modem command strings */
			p++;
			ch=*p;
			if(ch!='^' && ch>='@')	/* ^^ to send an '^' char to the modem */
				ch-='@';
		}
		if(!comWriteByte(com_handle,ch)) {
			YIELD();
			if(!comWriteByte(com_handle,ch))
				return FALSE;
		}
	}
	SLEEP(100);
	comPurgeInput(com_handle);
	if(!comWriteByte(com_handle, '\r')) {
		YIELD();
		return comWriteByte(com_handle, '\r');
	}
	return TRUE;
}

/****************************************************************************/
/****************************************************************************/
BOOL modem_response(COM_HANDLE com_handle, char *str, size_t maxlen)
{
	BYTE	ch;
	size_t	len=0;
	time_t	start;

	lprintf(LOG_DEBUG,"Waiting for Modem Response ...");
	start=time(NULL);
	while(1){
		if(time(NULL)-start >= mdm_timeout) {
			lprintf(LOG_WARNING,"Modem Response TIMEOUT (%lu seconds) on %s", mdm_timeout, com_dev);
			return FALSE;
		}
		if(len >= maxlen) {
			lprintf(LOG_WARNING,"Modem Response too long (%u >= %u) from %s"
				,len, maxlen, com_dev);
			return FALSE;
		}
		if(!comReadByte(com_handle, &ch)) {
			YIELD();
			continue;
		}
		if(ch<' ' && len==0)	/* ignore prepended control chars */
			continue;

		if(ch=='\r') {
			while(comReadByte(com_handle,&ch))	/* eat trailing ctrl chars (e.g. 'LF') */
#if 0
				lprintf(LOG_DEBUG, "eating ch=%02X", ch)
#endif
				;
			break;
		}
		str[len++]=ch;
	}
	str[len]=0;

	return TRUE;
}

int modem_status(COM_HANDLE com_handle);

/****************************************************************************/
/****************************************************************************/
BOOL modem_command(COM_HANDLE com_handle, const char* cmd)
{
	char	resp[128];
	int		i;

	for(i=0;i<=mdm_cmdretry;i++) {
		if(terminated)
			return FALSE;
		if(i) {
			lprintf(LOG_WARNING,"Retry #%u: sending modem command (%s) on %s (status: %x)"
				, i, cmd, com_dev, modem_status(com_handle));
			lprintf(LOG_DEBUG,"Dropping DTR on %s", com_dev);
			if(!comLowerDTR(com_handle))
				lprintf(LOG_ERR,"ERROR %u lowering DTR on %s", COM_ERROR_VALUE, com_dev);
			SLEEP(dtr_delay);
			lprintf(LOG_DEBUG,"Raising DTR on %s (status: %x)", com_dev, modem_status(com_handle));
			if(!comRaiseDTR(com_handle))
				lprintf(LOG_ERR,"ERROR %u raising DTR on %s", COM_ERROR_VALUE, com_dev);
			SLEEP(5000);
		}
		if(!modem_send(com_handle, cmd)) {
			lprintf(LOG_ERR,"ERROR %u sending modem command (%s) on %s (status: %x)"
				,COM_ERROR_VALUE, cmd, com_dev, modem_status(com_handle));
			continue;
		}

		if(modem_response(com_handle, resp, sizeof(resp)))
			break;
	}

	if(i<=mdm_cmdretry) {
		lprintf(LOG_INFO,"Modem Response: %s", resp);
		return TRUE;
	}
	lprintf(LOG_ERR,"Modem command (%s) failure on %s (%u attempts)", cmd, com_dev, i);
	return FALSE;
}

/****************************************************************************/
/****************************************************************************/
void close_socket(SOCKET* sock)
{
	if(*sock != INVALID_SOCKET) {
		shutdown(*sock,SHUT_RDWR);	/* required on Unix */
		closesocket(*sock);
		*sock=INVALID_SOCKET;
	}
}

/****************************************************************************/
/****************************************************************************/
void cleanup(void)
{
	terminated=TRUE;

	lprintf(LOG_INFO,"Cleaning up ...");


	if(com_handle!=COM_HANDLE_INVALID) {
		if(!mdm_null && mdm_cleanup[0])
			modem_command(com_handle, mdm_cleanup);
		if(!com_handle_passed)
			comClose(com_handle);
	}

	close_socket(&sock);

#ifdef _WINSOCKAPI_
	WSACleanup();
#endif

	lprintf(LOG_INFO,"Done (handled %lu calls).", total_calls);

#if defined(_WIN32)
	if(daemonize && svc_status_handle!=0) {
		svc_status.dwCurrentState=SERVICE_STOPPED;
		SetServiceStatus(svc_status_handle, &svc_status);
	} else
#endif
	if(pause_on_exit) {
		printf("Hit enter to continue...");
		getchar();
	}
}

/* Returns 0 on error, Modem status bit-map value otherwise */
int modem_status(COM_HANDLE com_handle)
{
	int	mdm_status;

	if((mdm_status=comGetModemStatus(com_handle)) == COM_ERROR) {
		lprintf(LOG_ERR,"ERROR %u getting modem status"
			,COM_ERROR_VALUE);
		return 0;
	}
	return mdm_status;
}

/* Returns TRUE if DCD (Data Carrier Detect) is high */
BOOL carrier_detect(COM_HANDLE com_handle)
{
	return (dcd_ignore || (modem_status(com_handle)&COM_DCD)) ? TRUE:FALSE;
}

/****************************************************************************/
/****************************************************************************/
BOOL wait_for_call(COM_HANDLE com_handle)
{
	char		str[128];
	char*		p;
	time_t		start=time(NULL);
	msclock_t	now;
	msclock_t	lastring=0;
	int			ring_count=0;

	ZERO_VAR(cid_name);
	ZERO_VAR(cid_number);

	if(!comRaiseDTR(com_handle))
		lprintf(LOG_ERR,"ERROR %u raising DTR", COM_ERROR_VALUE);

	if(com_alreadyconnected || dcd_ignore)
		return TRUE;

	if(!mdm_null) {
		if(mdm_init[0]) {
			lprintf(LOG_INFO,"Initializing modem:");
			if(!modem_command(com_handle, mdm_init))
				return FALSE;
		}
		if(!mdm_manswer && mdm_autoans[0]) {
			lprintf(LOG_INFO,"Setting modem to auto-answer:");
			if(!modem_command(com_handle, mdm_autoans))
				return FALSE;
		}
		if(mdm_cid[0]) {
			lprintf(LOG_INFO,"Enabling modem Caller-ID:");
			if(!modem_command(com_handle, mdm_cid))
				return FALSE;
		}
	}

	lprintf(LOG_INFO,"Waiting for incoming call (%s) ...", mdm_manswer ? "Ring Indication" : "Carrier Detect");
	while(1) {
		if(terminated)
			return FALSE;
		if(comReadLine(com_handle, str, sizeof(str), /* timeout (ms): */250) > 0) {
			truncsp(str);
			if(str[0]==0)
				continue;
			lprintf(LOG_DEBUG,"Received from modem: '%s'", str);
			p=str;
			SKIP_WHITESPACE(p);
			if(*p) {
				lprintf(LOG_INFO, "Modem Message: %s", p);
				if(strncmp(p,"CONNECT ",8)==0) {
					long rate=atoi(p+8);
					if(rate)
						SAFEPRINTF2(termspeed,"%ld,%ld", rate, rate);
				}
				else if(strncmp(p,"NMBR",4)==0 || strncmp(p,"MESG",4)==0) {
					p+=4;
					FIND_CHAR(p,'=');
					SKIP_CHAR(p,'=');
					SKIP_WHITESPACE(p);
					if(cid_number[0]==0)	/* Don't overwrite, if multiple messages received */
						SAFECOPY(cid_number, p);
				}
				else if(strncmp(p,"NAME",4)==0) {
					p+=4;
					FIND_CHAR(p,'=');
					SKIP_CHAR(p,'=');
					SKIP_WHITESPACE(p);
					SAFECOPY(cid_name, p);
				}
				else if(strcmp(p,"NO CARRIER")==0) {
					ZERO_VAR(cid_name);
					ZERO_VAR(cid_number);
				}
				else if(mdm_ring[0] && strcmp(p,mdm_ring)==0 && mdm_manswer && mdm_answer[0]) {
					now = msclock();
					if(now < lastring || now - lastring > mdm_ringinterval)
						ring_count = 0;
					lastring = now;
					if(++ring_count >= mdm_ringcount) {
						if(!modem_send(com_handle, mdm_answer)) {
							lprintf(LOG_ERR,"ERROR %u sending modem command (%s) on %s"
								,COM_ERROR_VALUE, mdm_answer, com_dev);
						}
					}
				}
			}
			continue;	/* don't check DCD until we've received all the modem msgs */
		}
		if(carrier_detect(com_handle))
			break;
		if(mdm_reinit && (time(NULL)-start)/60 >= mdm_reinit) {
			lprintf(LOG_INFO,"Re-initialization timer elapsed: %u minutes", mdm_reinit);
			return TRUE;
		}
	}

	if(strcmp(cid_name,"P")==0)
		SAFECOPY(cid_name,"Private");
	else if(strcmp(cid_name,"O")==0)
		SAFECOPY(cid_name,"Out-of-area");

	if(strcmp(cid_number,"P")==0)
		SAFECOPY(cid_number,"Private");
	else if(strcmp(cid_number,"O")==0)
		SAFECOPY(cid_number,"Out-of-area");

	lprintf(LOG_INFO,"Carrier detected");
	return TRUE;
}

/****************************************************************************/
/****************************************************************************/
in_addr_t resolve_ip(const char *addr)
{
	HOSTENT*	host;
	const char*	p;

	if(*addr==0)
		return INADDR_NONE;

	for(p=addr;*p;p++)
		if(*p!='.' && !isdigit(*p))
			break;
	if(!(*p))
		return(inet_addr(addr));
	if((host=gethostbyname(addr))==NULL)
		return INADDR_NONE;
	if(host->h_addr_list == NULL)
		return INADDR_NONE;
	return *((in_addr_t*)host->h_addr_list[0]);
}

/****************************************************************************/
/****************************************************************************/
SOCKET connect_socket(const char* host, ushort port)
{
	SOCKET		sock;
	SOCKADDR_IN	addr;

	if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
		lprintf(LOG_ERR,"ERROR %u creating socket", ERROR_VALUE);
		return INVALID_SOCKET;
	}

	lprintf(LOG_DEBUG,"Setting TCP_NODELAY to %d",tcp_nodelay);
	setsockopt(sock,IPPROTO_TCP,TCP_NODELAY,(char*)&tcp_nodelay,sizeof(tcp_nodelay));

	ZERO_VAR(addr);
	addr.sin_addr.s_addr = resolve_ip(host);
	addr.sin_family = AF_INET;
	addr.sin_port   = htons(port);

	lprintf(LOG_INFO,"Connecting to %s port %u", host, port);
	if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
		lprintf(LOG_INFO,"Connected from COM Port (handle %u) to %s TCP port %u using socket descriptor %u"
			,com_handle, host, port, sock);
		return sock;
	}

	lprintf(LOG_ERR,"SOCKET ERROR %u connecting to %s port %u", ERROR_VALUE, host, port);
	closesocket(sock);

	return INVALID_SOCKET;
}

BOOL call_terminated;
BOOL input_thread_terminated;

char* chr(BYTE ch, char* str, size_t size)
{
	if(ch < ' ')
		snprintf(str, size, "^%c   (%d)", '@' + ch, ch);
	else if(ch > '~')
		snprintf(str, size, "0x%02X (%d)", ch, ch);
	else
		snprintf(str, size, "'%c'  (%d)", ch, ch);
	return str;
}

void sendbuf(SOCKET sock, void* buf, int len)
{
	int wr = sendsocket(sock, buf, len);
	if (wr != len)
		lprintf(LOG_ERR, "!send returned %d instead of %d", wr, len);
}

/****************************************************************************/
/****************************************************************************/
void input_thread(void* arg)
{
	char		dbg[32];
	BYTE		ch;
	BYTE		last_ch = 0;
	BYTE		nl = '\n';

	lprintf(LOG_DEBUG,"Input thread started");
	while(!call_terminated) {
		bool result = comReadByte(com_handle, &ch);
		if(telnet && telnet_local_option[TELNET_BINARY_TX] != TELNET_WILL && last_ch == '\r'
			&& (result == false || ch != '\n')) {
			sendbuf(sock, &nl, sizeof nl);
			last_ch = 0;
		}
		if(!result) {
			YIELD();
			continue;
		}
		if(com_byte_size < 8)
			ch &= (1 << com_byte_size) - 1;
		if(com_debug)
			lprintf(LOG_DEBUG, "Received char from COM port (%s): %s", com_dev, chr(ch, dbg, sizeof(dbg)));
		if(telnet && ch==TELNET_IAC)
			sendbuf(sock, &ch, sizeof(ch));	/* escape Telnet IAC char (255) when in telnet mode */
		sendbuf(sock, &ch, sizeof(ch));
		last_ch = ch;
		bytes_received++;
	}
	lprintf(LOG_DEBUG,"Input thread terminated");
	input_thread_terminated=TRUE;
}

void ident_server_thread(void* arg)
{
	int				result;
	SOCKET			sock;
	SOCKADDR_IN		server_addr={0};
	fd_set			socket_set;
	struct timeval tv;

	lprintf(LOG_DEBUG,"Ident server thread started");

	if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
		lprintf(LOG_ERR,"ERROR %u creating socket", ERROR_VALUE);
		return;
	}
	
    memset(&server_addr, 0, sizeof(server_addr));

	server_addr.sin_addr.s_addr = ident_interface;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port   = htons(ident_port);

    if(bind(sock,(struct sockaddr *)&server_addr,sizeof(server_addr))!=0) {
		lprintf(LOG_ERR,"ERROR %u binding ident server socket", ERROR_VALUE);
		close_socket(&sock);
		return;
	}

    if(listen(sock, 1)) {
		lprintf(LOG_ERR,"!ERROR %u listening on ident server socket", ERROR_VALUE);
		close_socket(&sock);
		return;
	}

	while(!terminated) {
		/* now wait for connection */

		FD_ZERO(&socket_set);
		FD_SET(sock,&socket_set);

		tv.tv_sec=5;
		tv.tv_usec=0;

		if((result=select(sock+1,&socket_set,NULL,NULL,&tv))<1) {
			if(result==0)
				continue;
			if(ERROR_VALUE==EINTR)
				lprintf(LOG_DEBUG,"Ident Server listening interrupted");
			else if(ERROR_VALUE == ENOTSOCK)
            	lprintf(LOG_NOTICE,"Ident Server sockets closed");
			else
				lprintf(LOG_WARNING,"!ERROR %d selecting ident socket",ERROR_VALUE);
			continue;
		}

		if(FD_ISSET(sock,&socket_set)) {
			SOCKADDR_IN		client_addr;
			socklen_t		client_addr_len;
			SOCKET			client_socket=INVALID_SOCKET;
			char			request[128];
			char			response[256];
			int				rd;

			client_addr_len = sizeof(client_addr);
			client_socket = accept(sock, (struct sockaddr *)&client_addr, &client_addr_len);
			if(client_socket != INVALID_SOCKET) {
				lprintf(LOG_INFO,"Ident request from %s : %u"
					,inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
				FD_ZERO(&socket_set);
				FD_SET(client_socket,&socket_set);
				tv.tv_sec=5;
				tv.tv_usec=0;
				if(select(client_socket+1,&socket_set,NULL,NULL,&tv)==1) {
					lprintf(LOG_DEBUG,"Ident select");
					if((rd=recv(client_socket, request, sizeof(request), 0)) > 0) {
						request[rd]=0;
						truncsp(request);
						lprintf(LOG_INFO,"Ident request: %s", request);
						/* example response: "40931,23:USERID:UNIX:cyan" */
						SAFEPRINTF4(response,"%s:%s:%s %s\r\n"
							,request, ident_response, cid_number, cid_name);
						sendbuf(client_socket,response,strlen(response));
					} else
						lprintf(LOG_DEBUG,"ident recv=%d %d", rd, ERROR_VALUE);
				}
				close_socket(&client_socket);
			}
		}
	}

	close_socket(&sock);
	lprintf(LOG_DEBUG,"Ident server thread terminated");
}

static void send_telnet_cmd(uchar cmd, uchar opt)
{
	char buf[16];

	if(cmd<TELNET_WILL) {
		if(debug_telnet)
			lprintf(LOG_INFO,"TX Telnet command: %s"
				,telnet_cmd_desc(cmd));
		sprintf(buf,"%c%c",TELNET_IAC,cmd);
		sendbuf(sock,buf,2);
	} else {
		if(debug_telnet)
			lprintf(LOG_INFO,"TX Telnet command: %s %s"
				,telnet_cmd_desc(cmd), telnet_opt_desc(opt));
		sprintf(buf,"%c%c%c",TELNET_IAC,cmd,opt);
		sendbuf(sock,buf,3);
	}
}

void request_telnet_opt(uchar cmd, uchar opt)
{
	if(cmd==TELNET_DO || cmd==TELNET_DONT) {	/* remote option */
		if(telnet_remote_option[opt]==telnet_opt_ack(cmd))
			return;	/* already set in this mode, do nothing */
		telnet_remote_option[opt]=telnet_opt_ack(cmd);
	} else {	/* local option */
		if(telnet_local_option[opt]==telnet_opt_ack(cmd))
			return;	/* already set in this mode, do nothing */
		telnet_local_option[opt]=telnet_opt_ack(cmd);
	}
	send_telnet_cmd(cmd,opt);
}

/****************************************************************************/
/****************************************************************************/
BYTE* telnet_interpret(BYTE* inbuf, int inlen, BYTE* outbuf, int *outlen)
{
	BYTE	command;
	BYTE	option;
	BYTE*   first_iac;
	int 	i;

	if(inlen<1) {
		*outlen=0;
		return(inbuf);	/* no length? No interpretation */
	}

    first_iac=(BYTE*)memchr(inbuf, TELNET_IAC, inlen);

    if(!telnet_cmdlen && first_iac==NULL) {
        *outlen=inlen;
        return(inbuf);	/* no interpretation needed */
    }

    if(first_iac!=NULL) {
   		*outlen=first_iac-inbuf;
	    memcpy(outbuf, inbuf, *outlen);
    } else
    	*outlen=0;

    for(i=*outlen;i<inlen;i++) {
        if(inbuf[i]==TELNET_IAC && telnet_cmdlen==1) { /* escaped 255 */
            telnet_cmdlen=0;
            outbuf[(*outlen)++]=TELNET_IAC;
            continue;
        }
        if(inbuf[i]==TELNET_IAC || telnet_cmdlen) {

			if(telnet_cmdlen<sizeof(telnet_cmd))
				telnet_cmd[telnet_cmdlen++]=inbuf[i];

			command	= telnet_cmd[1];
			option	= telnet_cmd[2];

			if(telnet_cmdlen>=2 && command==TELNET_SB) {
				if(inbuf[i]==TELNET_SE 
					&& telnet_cmd[telnet_cmdlen-2]==TELNET_IAC) {
					if(debug_telnet)
						lprintf(LOG_INFO,"RX Telnet sub-negotiation command: %s"
							,telnet_opt_desc(option));
					/* sub-option terminated */
					if(option==TELNET_TERM_TYPE && telnet_cmd[3]==TELNET_TERM_SEND) {
						char buf[sizeof(termtype) + 32];
						int len=sprintf(buf,"%c%c%c%c%s%c%c"
							,TELNET_IAC,TELNET_SB
							,TELNET_TERM_TYPE,TELNET_TERM_IS
							,termtype
							,TELNET_IAC,TELNET_SE);
						if(debug_telnet)
							lprintf(LOG_INFO,"TX Telnet command: Terminal Type is %s", termtype);
						sendbuf(sock,buf,len);
/*						request_telnet_opt(TELNET_WILL, TELNET_TERM_SPEED); */
					} else if(option==TELNET_TERM_SPEED && telnet_cmd[3]==TELNET_TERM_SEND) {
						char buf[sizeof(termspeed) + 32];
						int len=sprintf(buf,"%c%c%c%c%s%c%c"
							,TELNET_IAC,TELNET_SB
							,TELNET_TERM_SPEED,TELNET_TERM_IS
							,termspeed
							,TELNET_IAC,TELNET_SE);
						if(debug_telnet)
							lprintf(LOG_INFO,"TX Telnet command: Terminal Speed is %s", termspeed);
						sendbuf(sock,buf,len);
					}

					telnet_cmdlen=0;
				}
			}
            else if(telnet_cmdlen==2 && inbuf[i]<TELNET_WILL) {
                telnet_cmdlen=0;
            }
            else if(telnet_cmdlen>=3) {	/* telnet option negotiation */

				if(debug_telnet)
					lprintf(LOG_INFO,"RX Telnet command: %s %s"
						,telnet_cmd_desc(command),telnet_opt_desc(option));

				if(command==TELNET_DO || command==TELNET_DONT) {	/* local options */
					if(telnet_local_option[option]!=command) {
						switch(option) {
							case TELNET_BINARY_TX:
							case TELNET_ECHO:
							case TELNET_TERM_TYPE:
							case TELNET_TERM_SPEED:
							case TELNET_SUP_GA:
								telnet_local_option[option]=command;
								send_telnet_cmd(telnet_opt_ack(command),option);
								break;
							case TELNET_SEND_LOCATION:
								if(command==TELNET_DO) {
									char buf[128];
									int len=safe_snprintf(buf,sizeof(buf),"%c%c%c%s %s%c%c"
										,TELNET_IAC,TELNET_SB
										,TELNET_SEND_LOCATION
										,cid_number, cid_name
										,TELNET_IAC,TELNET_SE);
									if(debug_telnet)
										lprintf(LOG_INFO,"TX Telnet command: Location is %s %s", cid_number, cid_name);
									sendbuf(sock,buf,len);
								} else
									send_telnet_cmd(telnet_opt_ack(command),option);
								break;
		
							default: /* unsupported local options */
								if(command==TELNET_DO) /* NAK */
									send_telnet_cmd(telnet_opt_nak(command),option);
								break;
						}
					}
				} else { /* WILL/WONT (remote options) */ 
					if(telnet_remote_option[option]!=command) {	

						switch(option) {
							case TELNET_BINARY_TX:
							case TELNET_ECHO:
							case TELNET_TERM_TYPE:
							case TELNET_SUP_GA:
								telnet_remote_option[option]=command;
								send_telnet_cmd(telnet_opt_ack(command),option);
								break;
							default: /* unsupported remote options */
								if(command==TELNET_WILL) /* NAK */
									send_telnet_cmd(telnet_opt_nak(command),option);
								break;
						}
					}
				}

                telnet_cmdlen=0;

            }
        } else
        	outbuf[(*outlen)++]=inbuf[i];
    }
    return(outbuf);
}

void debug_log_com_write(BYTE* buf, int len)
{
	int i;

	for(i=0; i < len; ++i) {
		char dbg[32];
		BYTE ch = buf[i];
		lprintf(LOG_DEBUG, "Writing char to COM port (%s): %s", com_dev, chr(ch, dbg, sizeof(dbg)));
	}
}

/****************************************************************************/
/****************************************************************************/
BOOL handle_call(void)
{
	BYTE		buf[10000];
	BYTE		telnet_buf[sizeof(buf)];
	BYTE*		p;
	int			result;
	int			rd;
	int			wr;
	fd_set		socket_set;
	struct		timeval tv = {0, 0};

	bytes_sent=0;
	bytes_received=0;
	call_terminated=FALSE;

	/* Reset Telnet state information */
	telnet_cmdlen=0;
	ZERO_VAR(telnet_local_option);
	ZERO_VAR(telnet_remote_option);

	if(telnet && telnet_advertise_cid && (cid_number[0] || cid_name[0]))	/* advertise the ability to send our location */
		send_telnet_cmd(TELNET_WILL, TELNET_SEND_LOCATION);

	input_thread_terminated=FALSE;
	_beginthread(input_thread, 0, NULL);

	while(!terminated) {

		if(!carrier_detect(com_handle)) {
			lprintf(LOG_WARNING,"Loss of Carrier Detect (DCD) detected");
			break;
		}
#if 0	/* single-threaded mode: */
		if(comReadByte(com_handle, &ch)) {
			lprintf(LOG_DEBUG,"read byte: %c", ch);
			send(sock, &ch, sizeof(ch), 0);
		}
#endif

		FD_ZERO(&socket_set);
		FD_SET(sock,&socket_set);
		if((result = select(sock+1,&socket_set,NULL,NULL,&tv)) == 0) {
			YIELD();
			continue;
		}
		if(result == SOCKET_ERROR) {
			lprintf(LOG_ERR,"SOCKET ERROR %u on select", ERROR_VALUE);
			break;
		}

		rd=recv(sock,buf,sizeof(buf),0);

		if(rd < 1) {
			if(rd==0) {
				lprintf(LOG_WARNING,"Socket Disconnected");
				break;
			}
			if(ERROR_VALUE == EAGAIN)
				continue;
        	else if(ERROR_VALUE == ENOTSOCK)
   	            lprintf(LOG_WARNING,"Socket closed by peer on receive");
       	    else if(ERROR_VALUE==ECONNRESET) 
				lprintf(LOG_WARNING,"Connection reset by peer on receive");
			else if(ERROR_VALUE==ESHUTDOWN)
				lprintf(LOG_WARNING,"Socket shutdown on receive");
       	    else if(ERROR_VALUE==ECONNABORTED) 
				lprintf(LOG_WARNING,"Connection aborted by peer on receive");
			else
				lprintf(LOG_ERR,"SOCKET RECV ERROR %d",ERROR_VALUE);
			break;
		}

		if(telnet)
			p=telnet_interpret(buf,rd,telnet_buf,&rd);
		else
			p=buf;

		if((wr=comWriteBuf(com_handle, p, rd)) == COM_ERROR)
			lprintf(LOG_WARNING, "%s Error sending %d bytes to COM port", __FUNCTION__, rd);
		else {
			if(com_debug)
				debug_log_com_write(p, wr);
			bytes_sent += wr;
		}
	}

	call_terminated=TRUE;	/* terminate input_thread() */
	while(!input_thread_terminated) {
		YIELD();
	}

	lprintf(LOG_INFO,"Bytes sent-to, received-from COM Port: %lu, %lu"
		,bytes_sent, bytes_received);

	return TRUE;
}

/****************************************************************************/
/****************************************************************************/
BOOL hangup_call(COM_HANDLE com_handle)
{
	time_t	start;
	int		attempt;
	int		mdm_status = -1;

	if(dcd_ignore || !carrier_detect(com_handle))/* DCD already low */
		return TRUE;

	lprintf(LOG_DEBUG,"Waiting for transmit buffer to empty");
	SLEEP(dtr_delay);
	for(attempt=0; attempt<hangup_attempts; attempt++) {
		lprintf(LOG_INFO,"Dropping DTR (attempt #%d)", attempt+1);
		if(!comLowerDTR(com_handle)) {
			lprintf(LOG_ERR,"ERROR %u lowering DTR", COM_ERROR);
			continue;
		}
		lprintf(LOG_DEBUG,"Waiting for loss of Carrier Detect (DCD)");
		start=time(NULL);
		while(time(NULL)-start <= dcd_timeout) {
			if(((mdm_status=modem_status(com_handle))&COM_DCD)==0)
				return TRUE;
			SLEEP(1000); 
		}
		lprintf(LOG_ERR,"TIMEOUT waiting for DCD to drop (attempt #%d of %d)"
			,attempt+1, hangup_attempts);
		lprintf(LOG_DEBUG,"Modem status: 0x%lX", mdm_status);
	}

	return FALSE;
}

/****************************************************************************/
/****************************************************************************/
void break_handler(int type)
{
	lprintf(LOG_NOTICE,"-> Terminated Locally (signal: %d)",type);
	terminated=TRUE;
}

#if defined(_WIN32)
BOOL WINAPI ControlHandler(DWORD CtrlType)
{
	break_handler((int)CtrlType);
	return TRUE;
}
#endif

/****************************************************************************/
/****************************************************************************/
char* iniGetExistingWord(str_list_t list, const char* section, const char* key
					,const char* deflt, char* value)
{
	char* p;

	if((p=iniGetExistingString(list, section, key, deflt, value)) !=NULL) {
		FIND_WHITESPACE(value);
		*value=0;
	}
	return p;
}

str_list_t	ini = NULL;

void parse_tcp_section(const char* section)
{
	iniGetExistingWord(ini, section, "Host", NULL, host);
	port					= iniGetShortInt(ini, section, "Port", port);
	tcp_nodelay				= iniGetBool(ini,section,"NODELAY", tcp_nodelay);
	telnet					= iniGetBool(ini,section,"Telnet", telnet);
}

BOOL parse_com_section(const char* section)
{
	if(!iniSectionExists(ini, section))
		return FALSE;
	iniGetExistingWord(ini, section, "Device", NULL, com_dev);
	com_baudrate    = iniGetLongInt(ini, section, "BaudRate", com_baudrate);
	com_parity		= iniGetBool(ini, section, "Parity", com_parity);
	com_parity_odd	= iniGetBool(ini, section, "ParityOdd", com_parity_odd);
	com_byte_size	= iniGetInteger(ini, section, "ByteSize", com_byte_size);
	com_stop_bits	= iniGetInteger(ini, section, "StopBits", com_stop_bits);
	com_hangup	    = iniGetBool(ini, section, "Hangup", com_hangup);
	com_debug	    = iniGetBool(ini, section, "Debug", com_debug);
	hangup_attempts = iniGetInteger(ini, section, "HangupAttempts", hangup_attempts);
	dcd_timeout     = iniGetInteger(ini, section, "DCDTimeout", dcd_timeout);
	dcd_ignore      = iniGetBool(ini, section, "IgnoreDCD", dcd_ignore);
	dtr_delay		= iniGetLongInt(ini, section, "DTRDelay", dtr_delay);
	mdm_null	    = iniGetBool(ini, section, "NullModem", mdm_null);
	return TRUE;
}

void parse_ini_file(const char* ini_fname)
{
	FILE* fp;
	char* section;

	strListFree(&ini);
	if((fp=fopen(ini_fname,"r"))!=NULL) {
		lprintf(LOG_INFO,"Reading %s",ini_fname);
		ini = iniReadFile(fp);
		fclose(fp);
	}

	/* Root section */
	pause_on_exit			= iniGetBool(ini,ROOT_SECTION,"PauseOnExit",FALSE);
	log_level				= iniGetLogLevel(ini,ROOT_SECTION,"LogLevel",log_level);
	cls = iniGetBool(ini, ROOT_SECTION, "CLS", cls);
	iniGetString(ini, ROOT_SECTION, "Prompt", NULL, prompt);
	prompt_timeout = iniGetInteger(ini, ROOT_SECTION, "PromptTimeout", prompt_timeout);

	if(iniGetBool(ini,ROOT_SECTION,"Debug",FALSE))
		log_level=LOG_DEBUG;
	
	/* [COM] Section */
	parse_com_section("COM");

	/* [Modem] Section */
	section="Modem";
	iniGetExistingWord(ini, section, "Init", "", mdm_init);
	iniGetExistingWord(ini, section, "AutoAnswer", "", mdm_autoans);
	iniGetExistingWord(ini, section, "Cleanup", "", mdm_cleanup);
	iniGetExistingWord(ini, section, "EnableCallerID", "", mdm_cid);
	iniGetExistingWord(ini, section, "Answer", "", mdm_answer);
	iniGetExistingWord(ini, section, "Ring", "", mdm_ring);
	mdm_timeout     = iniGetInteger(ini, section, "Timeout", mdm_timeout);
	mdm_reinit		= iniGetInteger(ini, section, "ReInit", mdm_reinit);
	mdm_cmdretry	= iniGetInteger(ini, section, "CmdRetry", mdm_cmdretry);
	mdm_ringcount	= iniGetInteger(ini, section, "RingCount", mdm_ringcount);
	mdm_ringinterval= iniGetInteger(ini, section, "RingInterval", mdm_ringinterval);
	mdm_manswer		= iniGetBool(ini,section,"ManualAnswer", mdm_manswer);

	/* [TCP] Section */
	parse_tcp_section("TCP");

	/* [Telnet] Section */
	section="Telnet";
	telnet					= iniGetBool(ini,section,"Enabled", telnet);
	debug_telnet			= iniGetBool(ini,section,"Debug", debug_telnet);
	telnet_advertise_cid	= iniGetBool(ini,section,"AdvertiseLocation", telnet_advertise_cid);
	iniGetExistingWord(ini, section, "TermType", NULL, termtype);
	iniGetExistingWord(ini, section, "TermSpeed", NULL, termspeed);

	/* [Ident] Section */
	section="Ident";
	ident					= iniGetBool(ini,section,"Enabled", ident);
	ident_port				= iniGetShortInt(ini, section, "Port", ident_port);
	ident_interface			= iniGetIpAddress(ini, section, "Interface", ident_interface);
	iniGetExistingWord(ini, section, "Response", NULL, ident_response);

}

void com_setup(void)
{
	if(com_baudrate!=0) {
		if(!comSetBaudRate(com_handle,com_baudrate))
			lprintf(LOG_ERR,"ERROR %u setting DTE rate to %lu bps"
				,COM_ERROR_VALUE, com_baudrate);
	}
	if(!comSetParity(com_handle, com_parity, com_parity_odd))
		lprintf(LOG_ERR,"ERROR %u setting parity type"
			,COM_ERROR_VALUE);
	if(!comSetBits(com_handle, com_byte_size, com_stop_bits))
		lprintf(LOG_ERR,"ERROR %u setting bits: %u / %u"
			,COM_ERROR_VALUE, com_byte_size, com_stop_bits);
}

char	banner[128];

static void 
#if defined(_WIN32)
	WINAPI
#endif
service_loop(int argc, char** argv)
{
	int		argn;
	char*	arg;
	char	str[128];
	char	compiler[128];

	for(argn=1; argn<(int)argc; argn++) {
		arg=argv[argn];
		if(*arg!='-') {	/* .ini file specified */
			if(!fexist(arg)) {
				lprintf(LOG_ERR,"Initialization file does not exist: %s", arg);
				exit(usage(argv[0]));
			}
			parse_ini_file(arg);
			continue;
		}
		while(*arg=='-') 
			arg++;
		if(stricmp(arg,"null")==0)
			mdm_null=TRUE;
		else if(stricmp(arg,"com")==0 && argc > argn+1)
			SAFECOPY(com_dev, argv[++argn]);
		else if(stricmp(arg,"baud")==0 && argc > argn+1)
			com_baudrate = (ulong)strtol(argv[++argn],NULL,0);
		else if(stricmp(arg,"host")==0 && argc > argn+1)
			SAFECOPY(host, argv[++argn]);
		else if(stricmp(arg,"port")==0 && argc > argn+1)
			port = (ushort)strtol(argv[++argn], NULL, 0);
		else if(stricmp(arg,"live")==0) {
			if(argc > argn+1 &&
#ifdef _BORLANDC_
				(com_handle = (COM_HANDLE)strtol(argv[argn+1], NULL, 0)) != 0) {
#else
				(com_handle = (COM_HANDLE)strtoll(argv[argn+1], NULL, 0)) != 0) {
#endif
				argn++;
				com_handle_passed=TRUE;
			}
			com_alreadyconnected=TRUE;
			terminate_after_one_call=TRUE;
			mdm_null=TRUE;
		}
		else if(stricmp(arg,"nohangup")==0) {
			com_hangup=FALSE;
		}
		else if(stricmp(arg,"debug")==0) {
			log_level=LOG_DEBUG;
		}
		else if(stricmp(arg,"help")==0 || *arg=='?')
			exit(usage(argv[0]));
		else if(stricmp(arg,"syslog") != 0 && strnicmp(arg,"syslog=",7) != 0) {
			fprintf(stderr,"Invalid option: %s\n", arg);
			exit(usage(argv[0]));
		}
	}

#if defined(_WIN32)
	/* Convert "1" to "COM1" for Windows */
	{
		int i;

		if((i=atoi(com_dev)) != 0)
			SAFEPRINTF(com_dev, "COM%d", i);
	}

	if(daemonize) {

		if((svc_status_handle = RegisterServiceCtrlHandler(NAME, ServiceControlHandler))==0) {
			lprintf(LOG_ERR,"!ERROR %d registering service control handler",GetLastError());
			return;
		}

		svc_status.dwServiceType=SERVICE_WIN32_OWN_PROCESS;
		svc_status.dwControlsAccepted=SERVICE_ACCEPT_SHUTDOWN;
		svc_status.dwWaitHint=NTSVC_TIMEOUT_STARTUP;

		svc_status.dwCurrentState=SERVICE_START_PENDING;
		SetServiceStatus(svc_status_handle, &svc_status);
	}

#endif

	lprintf(LOG_INFO,"%s", comVersion(str,sizeof(str)));
	DESCRIBE_COMPILER(compiler);
	lprintf(LOG_INFO,"Build %s %s %s", __DATE__, __TIME__, compiler);

	/************************************/
	/* Inititalize WinSock and COM Port */
	/************************************/

	if(!winsock_startup())
		exit(1);

	/* Install clean-up callback */
	atexit(cleanup);

	lprintf(LOG_INFO,"TCP Host: %s", host);
	lprintf(LOG_INFO,"TCP Port: %u", port);
	
	if(!com_handle_passed) {
		lprintf(LOG_INFO,"Opening Communications Device (COM Port): %s", com_dev);
		if((com_handle=comOpen(com_dev)) == COM_HANDLE_INVALID) {
			lprintf(LOG_ERR,"ERROR %u opening communications device/port: '%s'", COM_ERROR_VALUE, com_dev);
			exit(1);
		}
	}
	lprintf(LOG_INFO,"COM Port device handle: %u", com_handle);

	com_setup();

	lprintf(LOG_INFO,"COM Port DTE rate: %ld bps", comGetBaudRate(com_handle));
	comSetFlowControl(com_handle, COM_FLOW_CONTROL_RTS_CTS);
	lprintf(LOG_INFO,"COM Port flow control: %d", comGetFlowControl(com_handle));

	if(ident)
		_beginthread(ident_server_thread, 0, NULL);

#if defined(_WIN32)
	if(daemonize) {
		svc_status.dwCurrentState=SERVICE_RUNNING;
		svc_status.dwControlsAccepted|=SERVICE_ACCEPT_STOP;
		SetServiceStatus(svc_status_handle, &svc_status);
	}
#endif

	/***************************/
	/* Initialization Complete */
	/***************************/

	/* Main service loop: */
	while(!terminated && wait_for_call(com_handle)) {
		if(!carrier_detect(com_handle))	/* re-initialization timer time-out? */
			continue;
		if(cls)
			comWriteByte(com_handle, FF);
		comWriteByte(com_handle, '\r');
		comWriteString(com_handle, banner);
		comWriteString(com_handle, "\r\n");
		if(prompt[0] != '\0') {
			int ptimeout;
			if (prompt_timeout == 0)
				ptimeout = COM_INFINITE_TIMEOUT;
			else {
				if (prompt_timeout <= INT_MAX / 1000)
					ptimeout = prompt_timeout * 1000;
				else
					ptimeout = INT_MAX;
			}
			parse_tcp_section("TCP");
			comWriteString(com_handle, prompt);
			char ch;
			if(comReadBuf(com_handle, &ch, sizeof(ch), NULL, ptimeout)) {
				if(IS_CONTROL(ch))
					lprintf(LOG_WARNING, "Received a control character (%d) in response to prompt", ch);
				else {
					lprintf(LOG_DEBUG, "Received character '%c' (%d) in response to prompt", ch, ch);
					SAFEPRINTF(str, "TCP:%c", ch);
					parse_tcp_section(str);
					SAFEPRINTF(str, "COM:%c", ch);
					if(parse_com_section(str))
						com_setup();
				}
			} else
				lprintf(LOG_NOTICE, "Timeout (%d seconds) waiting for response to prompt", prompt_timeout);
		}
		if((sock=connect_socket(host, port)) == INVALID_SOCKET) {
			comWriteString(com_handle,"\7\r\n!ERROR connecting to TCP port\r\n");
		} else {
			handle_call();
			close_socket(&sock);
			total_calls++;
			lprintf(LOG_INFO,"Call completed (%lu total)", total_calls);
		}
		if(com_hangup && !hangup_call(com_handle))
			break;
		if(terminate_after_one_call)
			break;
	}

	exit(0);
}

/****************************************************************************/
/****************************************************************************/
int main(int argc, char** argv)
{
	int		argn;
	char*	arg;
	char*	p;
	char	path[MAX_PATH+1];
	char	fname[MAX_PATH+1];
	char	ini_fname[MAX_PATH+1];

	/*******************************/
	/* Generate and display banner */
	/*******************************/

	sprintf(banner,"\n%s/%-7s "
		"v2.1  Copyright %s Rob Swindell"
		,TITLE
		,PLATFORM_DESC
		,&__DATE__[7]
		);

	fprintf(stdout,"%s\n", banner);
	fprintf(stdout,"https://gitlab.synchro.net - %s/%s %s\n\n", GIT_BRANCH, GIT_HASH, GIT_DATE);

	/**********************/
	/* Parse command-line */
	/**********************/

	for(argn=1; argn<argc; argn++) {
		arg=argv[argn];
		while(*arg=='-')
			arg++;
		if(stricmp(arg,"help")==0 || *arg=='?')
			return usage(argv[0]);
#ifdef _WIN32
		else if(stricmp(arg,"service")==0)
			daemonize=TRUE;
		else if(stricmp(arg,"install")==0)
			return install();
		else if(stricmp(arg,"remove")==0)
			return uninstall();
		else if(stricmp(arg,"disable")==0)
			return enable(FALSE);
		else if(stricmp(arg,"enable")==0)
			return enable(TRUE);
#else
		else if(stricmp(arg,"syslog")==0)
			daemonize=TRUE;
		else if(strnicmp(arg,"syslog=", 7)==0) {
			daemonize=TRUE;
			openlog(arg+7, LOG_CONS, LOG_USER);
		}
#endif
	}

	/******************/
	/* Read .ini file */
	/******************/
	/* Generate path/sexpots[.host].ini from path/sexpots[.exe] */
	SAFECOPY(path,argv[0]);
	p=getfname(path);
	SAFECOPY(fname,p);
	*p=0;
	if((p=getfext(fname))!=NULL) 
		*p=0;
	strcat(fname,".ini");

	iniFileName(ini_fname,sizeof(ini_fname),path,fname);
	parse_ini_file(ini_fname);

#if defined(_WIN32)
	if(daemonize) {

		SERVICE_TABLE_ENTRY  ServiceDispatchTable[] = 
		{ 
			{ NAME,	(void(WINAPI*)(DWORD, char**))service_loop	}, 
			{ NULL,			NULL										}	/* Terminator */
		};

		printf("Starting service control dispatcher.\n" );
		printf("This may take several seconds.  Please wait.\n" );

		if(!StartServiceCtrlDispatcher(ServiceDispatchTable)) {
			lprintf(LOG_ERR,"StartServiceCtrlDispatcher ERROR %d",GetLastError());
			return -1;
		}
		return 0;
	}
	SetConsoleCtrlHandler(ControlHandler, TRUE /* Add */);

#elif defined(__unix__)
    signal(SIGQUIT,break_handler);
    signal(SIGINT,break_handler);
    signal(SIGTERM,break_handler);

    signal(SIGHUP,SIG_IGN);

    /* Don't die on SIGPIPE  */
    signal(SIGPIPE,SIG_IGN);
#endif

	service_loop(argc,argv);

	return 0;
}
