Skip to content
Snippets Groups Projects
xtrn.cpp 68 KiB
Newer Older
/* xtrn.cpp */
// vi: tabstop=4

/* Synchronet external program support routines */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
rswindell's avatar
rswindell committed
 * 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										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/
#define XTERN_LOG_STDERR
#include "sbbs.h"
#include "cmdshell.h"
#include "telnet.h"

#include <signal.h>			// kill()

#ifdef __unix__
	#include <sys/wait.h>	// WEXITSTATUS

deuce's avatar
deuce committed
	#define TTYDEFCHARS		// needed for ttydefchars definition
	#include <sys/ttydefaults.h>	// Linux - it's motherfucked.
rswindell's avatar
rswindell committed
#if defined(__FreeBSD__)
	#include <libutil.h>	// forkpty()
deuce's avatar
deuce committed
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DARWIN__)
rswindell's avatar
rswindell committed
	#include <util.h>
#elif defined(__linux__)
#else
	#define NEEDS_FORKPTY
#endif
/*
 * Control Character Defaults
 */
#ifndef CTRL
#endif
#ifndef CEOF
#endif
#ifndef CEOL
	#define	CEOL		0xff		/* XXX avoid _POSIX_VDISABLE */
#endif
#ifndef CERASE
#endif
#ifndef CERASE2
#endif
#ifndef CINTR
#endif
#ifndef CSTATUS
#endif
#ifndef CKILL
#endif
#ifndef CMIN
#endif
#ifndef CQUIT
	#define	CQUIT		034		/* FS, ^\ */
#endif
#ifndef CSUSP
#endif
#ifndef CTIME
#endif
#ifndef CDSUSP
#endif
#ifndef CSTART
#endif
#ifndef CSTOP
#endif
#ifndef CLNEXT
#endif
#ifndef CDISCARD
#endif
#ifndef CWERASE
#endif
#ifndef CREPRINT
#endif
#ifndef CEOT
#endif
/* compat */
#ifndef CBRK
#endif
#ifndef CRPRNT
#endif
#ifndef CFLUSH
#ifndef TTYDEF_IFLAG
	#define TTYDEF_IFLAG    (BRKINT | ICRNL | IMAXBEL | IXON | IXANY)
#endif
#ifndef TTYDEF_OFLAG
	#define TTYDEF_OFLAG    (OPOST | ONLCR)
#endif
#ifndef TTYDEF_LFLAG
	#define TTYDEF_LFLAG    (ECHO | ICANON | ISIG | IEXTEN | ECHOE|ECHOKE|ECHOCTL)
#endif
#ifndef TTYDEF_CFLAG
	#define TTYDEF_CFLAG    (CREAD | CS8 | HUPCL)
deuce's avatar
deuce committed
#if defined(__QNX__) || defined(__solaris__) || defined(__NetBSD__)
	static cc_t     ttydefchars[NCCS] = {
        CEOF,   CEOL,   CEOL,   CERASE, CWERASE, CKILL, CREPRINT,
        CERASE2, CINTR, CQUIT,  CSUSP,  CDSUSP, CSTART, CSTOP,  CLNEXT,
        CDISCARD, CMIN, CTIME,  CSTATUS
#ifndef __solaris__
	, _POSIX_VDISABLE
#endif
#define XTRN_IO_BUF_LEN 10000	/* 50% of IO_THREAD_BUF_SIZE */
/*****************************************************************************/
/* Interrupt routine to expand WWIV Ctrl-C# codes into ANSI escape sequences */
/*****************************************************************************/
BYTE* wwiv_expand(BYTE* buf, ulong buflen, BYTE* outbuf, ulong& newlen
	,ulong user_misc, bool& ctrl_c)
{
    char	ansi_seq[32];
	ulong 	i,j,k;

    for(i=j=0;i<buflen;i++) {
        if(buf[i]==CTRL_C) {	/* WWIV color escape char */
            ctrl_c=true;
            continue;
        }
        if(!ctrl_c) {
            outbuf[j++]=buf[i];
            continue;
        }
        ctrl_c=false;
        if(user_misc&ANSI) {
            switch(buf[i]) {
                default:
                    strcpy(ansi_seq,"\x1b[0m");          /* low grey */
                    break;
                case '1':
                    strcpy(ansi_seq,"\x1b[0;1;36m");     /* high cyan */
                    break;
                case '2':
                    strcpy(ansi_seq,"\x1b[0;1;33m");     /* high yellow */
                    break;
                case '3':
                    strcpy(ansi_seq,"\x1b[0;35m");       /* low magenta */
                    break;
                case '4':
                    strcpy(ansi_seq,"\x1b[0;1;44m");     /* white on blue */
                    break;
                case '5':
                    strcpy(ansi_seq,"\x1b[0;32m");       /* low green */
                    break;
                case '6':
                    strcpy(ansi_seq,"\x1b[0;1;5;31m");   /* high blinking red */
                    break;
                case '7':
                    strcpy(ansi_seq,"\x1b[0;1;34m");     /* high blue */
                    break;
                case '8':
                    strcpy(ansi_seq,"\x1b[0;34m");       /* low blue */
                    break;
                case '9':
                    strcpy(ansi_seq,"\x1b[0;36m");       /* low cyan */
                    break;
            }
            for(k=0;ansi_seq[k];k++)
                outbuf[j++]=ansi_seq[k];
        }
    }
    newlen=j;
    return(outbuf);
}

/*****************************************************************************/
// Escapes Telnet IAC (255) by doubling the IAC char
/*****************************************************************************/
BYTE* telnet_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
{
	BYTE*   first_iac;
	ulong	i,outlen;

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

	if(first_iac==NULL) {	/* Nothing to expand */
		newlen=inlen;
		return(inbuf);
	}

	outlen=first_iac-inbuf;
	memcpy(outbuf, inbuf, outlen);

    for(i=outlen;i<inlen;i++) {
		if(inbuf[i]==TELNET_IAC)
			outbuf[outlen++]=TELNET_IAC;
		outbuf[outlen++]=inbuf[i];
	}
    newlen=outlen;
    return(outbuf);
}
static bool native_executable(scfg_t* cfg, const char* cmdline, long mode)
{
	char*	p;
	char	str[MAX_PATH+1];
	char	name[64];
	char	base[64];
	unsigned i;

	if(mode&EX_NATIVE)
		return(TRUE);

    SAFECOPY(str,cmdline);				/* Set str to program name only */
	truncstr(str," ");
    SAFECOPY(name,getfname(str));
	SAFECOPY(base,name);
	if((p=getfext(base))!=NULL)
		*p=0;

    for(i=0;i<cfg->total_natvpgms;i++)
        if(stricmp(name,cfg->natvpgm[i]->name)==0
		|| stricmp(base,cfg->natvpgm[i]->name)==0)
            break;
    return(i<cfg->total_natvpgms);
}

#define XTRN_LOADABLE_MODULE(cmdline,startup_dir)			\
	if(cmdline[0]=='*')		/* Baja module or JavaScript */	\
		return(exec_bin(cmdline+1,&main_csi,startup_dir))				
	#define XTRN_LOADABLE_JS_MODULE(cmdline,startup_dir)	\
	if(cmdline[0]=='?') 	/* JavaScript */				\
		return(js_execfile(cmdline+1,startup_dir))						
#else
	#define XTRN_LOADABLE_JS_MODULE
#endif

#ifdef _WIN32

#include "execvxd.h"	/* Win9X FOSSIL VxD API */

extern SOCKET node_socket[];

// -------------------------------------------------------------------------
// GetAddressOfOpenVxDHandle
//
// This function returns the address of OpenVxDHandle. OpenVxDHandle is a 
// KERNEL32 function that returns a ring 0 event handle that corresponds to a
// given ring 3 event handle. The ring 0 handle can be used by VxDs to
// synchronize with the Win32 app.
//
typedef HANDLE (WINAPI *OPENVXDHANDLE)(HANDLE);

OPENVXDHANDLE GetAddressOfOpenVxDHandle(void)
	return((OPENVXDHANDLE)GetProcAddress(hK32, "OpenVxDHandle"));
/*****************************************************************************/
// Expands Single CR to CRLF
/*****************************************************************************/
BYTE* cr_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
{
	ulong	i,j;

	for(i=j=0;i<inlen;i++) {
		outbuf[j++]=inbuf[i];
		if(inbuf[i]=='\r')
			outbuf[j++]='\n';
	}
	newlen=j;
    return(outbuf);
}

static void add_env_var(str_list_t* list, const char* var, const char* val)
{
	char	str[MAX_PATH*2];
	SetEnvironmentVariable(var,NULL);	/* Delete in current process env */
	SAFEPRINTF2(str,"%s=%s",var,val);
	strListPush(list,str);
}

/* Clean-up resources while preserving current LastError value */
#define XTRN_CLEANUP												\
	last_error=GetLastError();										\
    if(vxd!=INVALID_HANDLE_VALUE)		CloseHandle(vxd);			\
	if(rdslot!=INVALID_HANDLE_VALUE)	CloseHandle(rdslot);		\
	if(wrslot!=INVALID_HANDLE_VALUE)	CloseHandle(wrslot);		\
	if(start_event!=NULL)				CloseHandle(start_event);	\
	if(hungup_event!=NULL)				CloseHandle(hungup_event);	\
	if(hangup_event!=NULL)				CloseHandle(hangup_event);	\
	ReleaseMutex(exec_mutex);										\
	SetLastError(last_error)

/****************************************************************************/
/* Runs an external program 												*/
/****************************************************************************/
int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
	char	str[MAX_PATH+1];
	char*	env_block=NULL;
	char*	env_strings;
	const char* p_startup_dir;
    char	fullcmdline[MAX_PATH+1];
	char	realcmdline[MAX_PATH+1];
	char	comspec_str[MAX_PATH+1];
	char	title[MAX_PATH+1];
	BYTE	buf[XTRN_IO_BUF_LEN],*bp;
    BYTE 	telnet_buf[XTRN_IO_BUF_LEN*2];
    BYTE 	output_buf[XTRN_IO_BUF_LEN*2];
    BYTE 	wwiv_buf[XTRN_IO_BUF_LEN*2];
    bool	wwiv_flag=false;
    bool	native=false;			// DOS program by default
	bool	nt=false;				// WinNT/2K? 
    bool	was_online=true;
	bool	rio_abortable_save=rio_abortable;
	bool	use_pipes=false;	// NT-compatible console redirection
    time_t	hungup=0;
	HANDLE	vxd=INVALID_HANDLE_VALUE;
	HANDLE	rdslot=INVALID_HANDLE_VALUE;
	HANDLE	wrslot=INVALID_HANDLE_VALUE;
	HANDLE  start_event=NULL;
	HANDLE	hungup_event=NULL;
    PROCESS_INFORMATION process_info;
	DWORD	hVM;
deuce's avatar
deuce committed
	unsigned long	rd;
deuce's avatar
deuce committed
    unsigned long	len;
deuce's avatar
deuce committed
	unsigned long	dummy;
	unsigned long	msglen;
	unsigned long	retval;
	DWORD	last_error;
	DWORD	loop_since_io=0;
	sbbsexec_start_t start;
	OPENVXDHANDLE OpenVxDHandle;
	lprintf(LOG_DEBUG,"Executing external: %s",cmdline);
	if(startup_dir!=NULL && startup_dir[0] && !isdir(startup_dir)) {
		errormsg(WHERE, ERR_CHK, startup_dir, 0);
		return -1;
	}

	XTRN_LOADABLE_MODULE(cmdline,startup_dir);
	XTRN_LOADABLE_JS_MODULE(cmdline,startup_dir);
	attr(cfg.color[clr_external]);		/* setup default attributes */
	native = native_executable(&cfg, cmdline, mode);
	if(!native && (startup->options&BBS_OPT_NO_DOS)) {
		bprintf("Sorry, DOS programs are not supported on this node.\r\n");
		return -1;
	}

rswindell's avatar
rswindell committed
	if(mode&EX_SH || strcspn(cmdline,"<>|")!=strlen(cmdline)) 
		sprintf(comspec_str,"%s /C ", comspec);
	else
		comspec_str[0]=0;

    if(startup_dir && cmdline[1]!=':' && cmdline[0]!='/'
    	&& cmdline[0]!='\\' && cmdline[0]!='.')
       	sprintf(fullcmdline, "%s%s%s", comspec_str, startup_dir, cmdline);
    else
    	sprintf(fullcmdline, "%s%s", comspec_str, cmdline);

	SAFECOPY(realcmdline, fullcmdline);	// for errormsg if failed to execute
	if(localtime_r(&now,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
	OpenVxDHandle=GetAddressOfOpenVxDHandle();

	if(OpenVxDHandle==NULL) 
		nt=true;	// Windows NT/2000

	if(!nt && !native && !(cfg.xtrn_misc&XTRN_NO_MUTEX)
		&& (retval=WaitForSingleObject(exec_mutex,5000))!=WAIT_OBJECT_0) {
		errormsg(WHERE, ERR_TIMEOUT, "exec_mutex", retval);
		return(GetLastError());
	}

	if(native && mode&EX_STDOUT && !(mode&EX_OFFLINE))
 	if(native) { // Native (32-bit) external
		if((env_list=strListInit())==NULL) {
			XTRN_CLEANUP;
        	errormsg(WHERE, ERR_CREATE, "env_list", 0);
            return(errno);
		}

		// Current environment passed to child process
		sprintf(str,"%sprotocol.log",cfg.node_dir);			
		add_env_var(&env_list,"DSZLOG",str);
		add_env_var(&env_list,"SBBSNODE",cfg.node_dir);
		add_env_var(&env_list,"SBBSCTRL",cfg.ctrl_dir);
		add_env_var(&env_list,"SBBSDATA",cfg.data_dir);
		add_env_var(&env_list,"SBBSEXEC",cfg.exec_dir);
		sprintf(str,"%d",cfg.node_num);
		add_env_var(&env_list,"SBBSNNUM",str);
		sprintf(str,"%02u",tm.tm_mday);	
		add_env_var(&env_list,"DAY",str);
		add_env_var(&env_list,"WEEKDAY",wday[tm.tm_wday]);
		add_env_var(&env_list,"MONTHNAME",mon[tm.tm_mon]);
		sprintf(str,"%02u",tm.tm_mon+1);
		add_env_var(&env_list,"MONTH",str);
		sprintf(str,"%u",1900+tm.tm_year);
		add_env_var(&env_list,"YEAR",str);

		env_strings=GetEnvironmentStrings();
		env_block=strListCopyBlock(env_strings);
		if(env_strings!=NULL)
			FreeEnvironmentStrings(env_strings);
		env_block=strListAppendBlock(env_block,env_list);
		strListFree(&env_list);
		if(env_block==NULL) {
			XTRN_CLEANUP;
        	errormsg(WHERE, ERR_CREATE, "env_block", 0);
            return(errno);
		}

    } else { // DOS external

		// DOS-compatible (short) paths
		char node_dir[MAX_PATH+1];
		char ctrl_dir[MAX_PATH+1];
		char data_dir[MAX_PATH+1];
		char exec_dir[MAX_PATH+1];

		// in case GetShortPathName fails
		SAFECOPY(node_dir,cfg.node_dir);
		SAFECOPY(ctrl_dir,cfg.ctrl_dir);
		SAFECOPY(data_dir,cfg.data_dir);
		SAFECOPY(exec_dir,cfg.exec_dir);

		GetShortPathName(cfg.node_dir,node_dir,sizeof(node_dir));
		GetShortPathName(cfg.ctrl_dir,ctrl_dir,sizeof(node_dir));
		GetShortPathName(cfg.data_dir,data_dir,sizeof(data_dir));
		GetShortPathName(cfg.exec_dir,exec_dir,sizeof(exec_dir));

		sprintf(path,"%sDOSXTRN.RET", cfg.node_dir);
		remove(path);

    	// Create temporary environment file
    	sprintf(path,"%sDOSXTRN.ENV", node_dir);
        if(fp==NULL) {
        	errormsg(WHERE, ERR_CREATE, path, 0);
        }
        fprintf(fp, "%s\n", fullcmdline);
		fprintf(fp, "DSZLOG=%sPROTOCOL.LOG\n", node_dir);
        fprintf(fp, "SBBSNODE=%s\n", node_dir);
        fprintf(fp, "SBBSCTRL=%s\n", ctrl_dir);
		fprintf(fp, "SBBSDATA=%s\n", data_dir);
		fprintf(fp, "SBBSEXEC=%s\n", exec_dir);
        fprintf(fp, "SBBSNNUM=%d\n", cfg.node_num);
		fprintf(fp, "PCBNODE=%d\n", cfg.node_num);
		/* date/time env vars */
		fprintf(fp, "DAY=%02u\n", tm.tm_mday);
		fprintf(fp, "WEEKDAY=%s\n",wday[tm.tm_wday]);
		fprintf(fp, "MONTHNAME=%s\n",mon[tm.tm_mon]);
		fprintf(fp, "MONTH=%02u\n",tm.tm_mon+1);
		fprintf(fp, "YEAR=%u\n",1900+tm.tm_year);
        sprintf(fullcmdline, "%sDOSXTRN.EXE %s", cfg.exec_dir, path);
		if(!(mode&EX_OFFLINE) && nt) {	// Windows NT/2000
			i=SBBSEXEC_MODE_FOSSIL;
           		i|=SBBSEXEC_MODE_DOS_IN;
        		i|=SBBSEXEC_MODE_DOS_OUT;
			sprintf(str," NT %u %u"
				,cfg.node_num,i);
			strcat(fullcmdline,str);

			sprintf(str,"sbbsexec_hungup%d",cfg.node_num);
			if((hungup_event=CreateEvent(
				 NULL	// pointer to security attributes
				,TRUE	// flag for manual-reset event
				,FALSE  // flag for initial state
				,str	// pointer to event-object name
				))==NULL) {
				return(GetLastError());
			}

			sprintf(str,"sbbsexec_hangup%d",cfg.node_num);
			if((hangup_event=CreateEvent(
				 NULL	// pointer to security attributes
				,TRUE	// flag for manual-reset event
				,FALSE  // flag for initial state
				,str	// pointer to event-object name
				))==NULL) {
				XTRN_CLEANUP;
				errormsg(WHERE, ERR_CREATE, str, 0);
				return(GetLastError());
			}

			sprintf(str,"\\\\.\\mailslot\\sbbsexec\\rd%d"
				,cfg.node_num);
			rdslot=CreateMailslot(str
				,sizeof(buf)/2			// Maximum message size (0=unlimited)
				,0						// Read time-out
				,NULL);                 // Security
			if(rdslot==INVALID_HANDLE_VALUE) {
				errormsg(WHERE, ERR_CREATE, str, 0);
				return(GetLastError());
			}
		}
		else if(!(mode&EX_OFFLINE)) {

   			// Load vxd to intercept interrupts

			sprintf(str,"\\\\.\\%s%s",cfg.exec_dir, SBBSEXEC_VXD);
			if((vxd=CreateFile(str,0,0,0
				,CREATE_NEW, FILE_FLAG_DELETE_ON_CLOSE,0))
				 ==INVALID_HANDLE_VALUE) {
				errormsg(WHERE, ERR_OPEN, str, 0);
				return(GetLastError());
			}

			if((start_event=CreateEvent(
				 NULL	// pointer to security attributes
				,TRUE	// flag for manual-reset event
				,FALSE  // flag for initial state
				,NULL	// pointer to event-object name
				))==NULL) {
				errormsg(WHERE, ERR_CREATE, "exec start event", 0);
				return(GetLastError());
			}

			if(OpenVxDHandle!=NULL)
				start.event=OpenVxDHandle(start_event);
			else
				start.event=start_event;

			start.mode=SBBSEXEC_MODE_FOSSIL;
           		start.mode|=SBBSEXEC_MODE_DOS_IN;
        		start.mode|=SBBSEXEC_MODE_DOS_OUT;

			sprintf(str," 95 %u %u"
				,cfg.node_num,start.mode);
			if(!DeviceIoControl(
				vxd,					// handle to device of interest
				SBBSEXEC_IOCTL_START,	// control code of operation to perform
				&start,					// pointer to buffer to supply input data
				sizeof(start),			// size of input buffer
				NULL,					// pointer to buffer to receive output data
				0,						// size of output buffer
				&rd,					// pointer to variable to receive output byte count
				NULL 					// Overlapped I/O
				)) {
				errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_START);
				return(GetLastError());
			}
		}
    }

	if(startup_dir!=NULL && startup_dir[0])
		p_startup_dir=startup_dir;
	else
		p_startup_dir=NULL;
    STARTUPINFO startup_info={0};
    startup_info.cb=sizeof(startup_info);
	if(mode&EX_OFFLINE)
		startup_info.lpTitle=NULL;
	else {
		sprintf(title,"%s running %s on node %d"
			,useron.number ? useron.alias : "Event"
			,realcmdline
			,cfg.node_num);
		startup_info.lpTitle=title;
	}
    if(startup->options&BBS_OPT_XTRN_MINIMIZED) {
    	startup_info.wShowWindow=SW_SHOWMINNOACTIVE;
        startup_info.dwFlags|=STARTF_USESHOWWINDOW;
    }
	if(use_pipes) {
		// Set up the security attributes struct.
		SECURITY_ATTRIBUTES sa;
		memset(&sa,0,sizeof(sa));
		sa.nLength= sizeof(SECURITY_ATTRIBUTES);
		sa.lpSecurityDescriptor = NULL;
		sa.bInheritHandle = TRUE;

		// Create the child output pipe (override default 4K buffer size)
		if(!CreatePipe(&rdoutpipe,&startup_info.hStdOutput,&sa,sizeof(buf))) {
			errormsg(WHERE,ERR_CREATE,"stdout pipe",0);
			return(GetLastError());
		}
		startup_info.hStdError=startup_info.hStdOutput;

		// Create the child input pipe.
		if(!CreatePipe(&startup_info.hStdInput,&wrinpipe,&sa,sizeof(buf))) {
			errormsg(WHERE,ERR_CREATE,"stdin pipe",0);
			return(GetLastError());
		}
		DuplicateHandle(
			GetCurrentProcess(), rdoutpipe,
			GetCurrentProcess(), &rdslot, 0, FALSE, DUPLICATE_SAME_ACCESS);

		DuplicateHandle(
			GetCurrentProcess(), wrinpipe,
			GetCurrentProcess(), &wrslot, 0, FALSE, DUPLICATE_SAME_ACCESS);

		CloseHandle(rdoutpipe);
		CloseHandle(wrinpipe);

		startup_info.dwFlags|=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
    	startup_info.wShowWindow=SW_HIDE;
	}
	if(native && !(mode&EX_OFFLINE)) {
		if(!(mode&EX_STDIN) && input_thread_running) {
			pthread_mutex_lock(&input_thread_mutex);
		if(!(mode&EX_STDOUT)) {	 /* Native Socket I/O program */
			/* Enable the Nagle algorithm */
			BOOL nodelay=FALSE;
			setsockopt(client_socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
		}
		NULL,			// pointer to name of executable module
		fullcmdline,  	// pointer to command line string
		NULL,  			// process security attributes
		NULL,   		// thread security attributes
		native && !(mode&EX_OFFLINE),	 			// handle inheritance flag
		CREATE_NEW_CONSOLE/*|CREATE_SEPARATE_WOW_VDM*/, // creation flags
        env_block, 		// pointer to new environment block
		p_startup_dir,	// pointer to current directory name
		&startup_info,  // pointer to STARTUPINFO
		&process_info  	// pointer to PROCESS_INFORMATION
		if(input_thread_mutex_locked && input_thread_running) {
			pthread_mutex_unlock(&input_thread_mutex);
			input_thread_mutex_locked=false;
		}
		SetLastError(last_error);	/* Restore LastError */
        errormsg(WHERE, ERR_EXEC, realcmdline, mode);
        return(GetLastError());
    }

#if 0
	char dbgstr[256];
	sprintf(dbgstr,"Node %d created: hProcess %X hThread %X processID %X threadID %X\n"
		,cfg.node_num
		,process_info.hProcess 
		,process_info.hThread 
		,process_info.dwProcessId 
		,process_info.dwThreadId); 
	OutputDebugString(dbgstr);
#endif

	if(!native) {

		if(!(mode&EX_OFFLINE) && !nt) {
    		// Wait for notification from VXD that new VM has started
			if((retval=WaitForSingleObject(start_event, 5000))!=WAIT_OBJECT_0) {
                TerminateProcess(process_info.hProcess, __LINE__);
				errormsg(WHERE, ERR_TIMEOUT, "start_event", retval);
				return(GetLastError());
			}

			CloseHandle(start_event);
			start_event=NULL;	/* Mark as closed */

			if(!DeviceIoControl(
				vxd,					// handle to device of interest
				SBBSEXEC_IOCTL_COMPLETE,	// control code of operation to perform
				NULL,					// pointer to buffer to supply input data
				0,						// size of input buffer
				&hVM,					// pointer to buffer to receive output data
				sizeof(hVM),			// size of output buffer
				&rd,					// pointer to variable to receive output byte count
				NULL					// Overlapped I/O
				)) {
                TerminateProcess(process_info.hProcess, __LINE__);
				errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_COMPLETE);
				return(GetLastError());
			}
		}
	}
	/* Disable Ctrl-C checking */
	if(!(mode&EX_OFFLINE))
    retval=STILL_ACTIVE;
        if(!online && !(mode&EX_OFFLINE)) { // Tell VXD/VDD and external that user hung-up
        	if(was_online) {
				logline(LOG_NOTICE,"X!","hung-up in external program");
            	hungup=time(NULL);
				if(!native) {
					if(nt)
						SetEvent(hungup_event);
					else if(!DeviceIoControl(
						vxd,		// handle to device of interest
						SBBSEXEC_IOCTL_DISCONNECT,	// operation to perform
						&hVM,		// pointer to buffer to supply input data
						sizeof(hVM),// size of input buffer
						NULL,		// pointer to buffer to receive output data
						0,			// size of output buffer
						&rd,		// pointer to variable to receive output byte count
						NULL		// Overlapped I/O
						)) {
						errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_DISCONNECT);
						break;
					}
				}
	            was_online=false;
            }
            if(hungup && time(NULL)-hungup>5 && !processTerminated) {
				lprintf(LOG_INFO,"Terminating process from line %d", __LINE__);
				processTerminated=TerminateProcess(process_info.hProcess, 2112);
			}
		if((native && !use_pipes) || mode&EX_OFFLINE) {	
			/* Monitor for process termination only */
			if(WaitForSingleObject(process_info.hProcess,1000)==WAIT_OBJECT_0)
				break;
			if(nt || use_pipes) {	// Windows NT/2000

				/* Write to VDD */

				wr=RingBufPeek(&inbuf,buf,sizeof(buf));
				if(wr) {
					if(!use_pipes && wrslot==INVALID_HANDLE_VALUE) {
						sprintf(str,"\\\\.\\mailslot\\sbbsexec\\wr%d"
							,cfg.node_num);
						wrslot=CreateFile(str
							,GENERIC_WRITE
							,FILE_SHARE_READ
							,NULL
							,OPEN_EXISTING
							,FILE_ATTRIBUTE_NORMAL
							,(HANDLE) NULL);
						if(wrslot==INVALID_HANDLE_VALUE)
							lprintf(LOG_DEBUG,"!ERROR %u opening %s", GetLastError(), str);
							lprintf(LOG_DEBUG,"CreateFile(%s)=0x%x", str, wrslot);
					/* CR expansion */
					if(use_pipes) 
						bp=cr_expand(buf,wr,output_buf,wr);
					else
						bp=buf;
					len=0;
					if(wrslot==INVALID_HANDLE_VALUE)
						lprintf(LOG_WARNING,"VDD Open failed (not loaded yet?)");
					else if(!WriteFile(wrslot,bp,wr,&len,NULL)) {
						lprintf(LOG_ERR,"!VDD WriteFile(0x%x, %u) FAILURE (Error=%u)", wrslot, wr, GetLastError());
						if(GetMailslotInfo(wrslot,&wr,NULL,NULL,NULL))
							lprintf(LOG_DEBUG,"!VDD MailSlot max_msg_size=%u", wr);
							lprintf(LOG_DEBUG,"!GetMailslotInfo(0x%x)=%u", wrslot, GetLastError());
							lprintf(LOG_WARNING,"VDD short write (%u instead of %u)", len,wr);
						RingBufRead(&inbuf, NULL, len);
						if(use_pipes && !(mode&EX_NOECHO)) {
							RingBufWrite(&outbuf, bp, len);
				len=sizeof(buf);
				avail=RingBufFree(&outbuf)/2;	// leave room for wwiv/telnet expansion
					lprintf("Node %d !output buffer full (%u bytes)"
						,cfg.node_num,RingBufFull(&outbuf));
				if(len>avail)
            		len=avail;
deuce's avatar
deuce committed
					unsigned long waiting=0;

					if(use_pipes)
						PeekNamedPipe(
							rdslot,             // handle to pipe to copy from
							NULL,               // pointer to data buffer
							0,					// size, in bytes, of data buffer
							NULL,				// pointer to number of bytes read
							&waiting,			// pointer to total number of bytes available
							NULL				// pointer to unread bytes in this message
							);
					else
						GetMailslotInfo(
							rdslot,				// mailslot handle 
 							NULL,				// address of maximum message size 
							NULL,				// address of size of next message 
							&waiting,			// address of number of messages 
 							NULL				// address of read time-out 
							);
					if(!waiting)
						break;
					if(ReadFile(rdslot,buf+rd,len-rd,&msglen,NULL)==FALSE || msglen<1)
						break;
					rd+=msglen;
				}

				if(rd) {
					if(mode&EX_WWIV) {
                		bp=wwiv_expand(buf, rd, wwiv_buf, rd, useron.misc, wwiv_flag);
						if(rd>sizeof(wwiv_buf))
					} else if(telnet_mode&TELNET_MODE_OFF) {
						bp=buf;
                		bp=telnet_expand(buf, rd, telnet_buf, rd);
						if(rd>sizeof(telnet_buf))
							lprintf(LOG_ERR,"TELNET_BUF OVERRUN");
						lprintf(LOG_ERR,"output buffer overflow");
					RingBufWrite(&outbuf, bp, rd);
				}
			} else {	// Windows 9x

				/* Write to VXD */

				wr=RingBufPeek(&inbuf, buf+sizeof(hVM),sizeof(buf)-sizeof(hVM));
				if(wr) {
					*(DWORD*)buf=hVM;
					wr+=sizeof(hVM);
					if(!DeviceIoControl(
						vxd,					// handle to device of interest
						SBBSEXEC_IOCTL_WRITE,	// control code of operation to perform
						buf,					// pointer to buffer to supply input data
						wr,						// size of input buffer
						&rd,					// pointer to buffer to receive output data
						sizeof(rd),				// size of output buffer
						&dummy,	 				// pointer to variable to receive output byte count
						NULL					// Overlapped I/O
						)) {
						errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_READ);
						break;
					}
					RingBufRead(&inbuf, NULL, rd);
					wr=rd;
				}
        		/* Read from VXD */
				rd=0;
				len=sizeof(buf);
				avail=RingBufFree(&outbuf)/2;	// leave room for wwiv/telnet expansion
#if 0
					lprintf("Node %d !output buffer full (%u bytes)"
						,cfg.node_num,RingBufFull(&outbuf));
				if(len>avail)
            		len=avail;
				if(len) {
					if(!DeviceIoControl(
						vxd,					// handle to device of interest
						SBBSEXEC_IOCTL_READ,	// control code of operation to perform
						&hVM,					// pointer to buffer to supply input data
						sizeof(hVM),			// size of input buffer
						buf,					// pointer to buffer to receive output data
						len,					// size of output buffer
						&rd,					// pointer to variable to receive output byte count
						NULL					// Overlapped I/O
						)) {
						errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_READ);
						break;
					}
                		bp=wwiv_expand(buf, rd, wwiv_buf, rd, useron.misc, wwiv_flag);
					} else if(telnet_mode&TELNET_MODE_OFF) {
						bp=buf;
                		bp=telnet_expand(buf, rd, telnet_buf, rd);
							lprintf(LOG_ERR,"TELNET_BUF OVERRUN");
						lprintf(LOG_ERR,"output buffer overflow");
					RingBufWrite(&outbuf, bp, rd);
				}