Skip to content
Snippets Groups Projects
Select Git revision
  • dd_msg_reader_list_personal_email_in_reverse_choose_msg_fix
  • dailybuild_linux-x64
  • dailybuild_win32
  • master default protected
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

xtrn.cpp

Blame
    • Rob Swindell's avatar
      6b5de04a
      Add support for NTVDMx64 · 6b5de04a
      Rob Swindell authored
      Yes, you can run 16-bit DOS doors on 64-bit (x64) Windows now.
      
      Install NTVDMx64 (http://www.columbia.edu/~em36/ntvdmx64.html, it's not as onerous as it sounds) and re-enable DOS program support in SBBS (i.e. make sure "NO_DOS" is not in your sbbs.ini [bbs] Options value) and voila: DOS doors work.
      
      This change adds an empty init routine to sbbsexec.dll since older versions of NTVDM (which NTVDMx64 is based on) required one. Also, the sbbsexec.dll should be located in your Synchronet "exec" directory when using NTVDMx64 (in addition to or instead of your Windows/System32 directory).
      6b5de04a
      History
      Add support for NTVDMx64
      Rob Swindell authored
      Yes, you can run 16-bit DOS doors on 64-bit (x64) Windows now.
      
      Install NTVDMx64 (http://www.columbia.edu/~em36/ntvdmx64.html, it's not as onerous as it sounds) and re-enable DOS program support in SBBS (i.e. make sure "NO_DOS" is not in your sbbs.ini [bbs] Options value) and voila: DOS doors work.
      
      This change adds an empty init routine to sbbsexec.dll since older versions of NTVDM (which NTVDMx64 is based on) required one. Also, the sbbsexec.dll should be located in your Synchronet "exec" directory when using NTVDMx64 (in addition to or instead of your Windows/System32 directory).
    xtrn.cpp 59.06 KiB
    /* Synchronet external program support routines */
    
    /****************************************************************************
     * @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.	*
     ****************************************************************************/
    
    #include "sbbs.h"
    #include "cmdshell.h"
    #include "telnet.h"
    
    #include <signal.h>			// kill()
    
    #ifdef __unix__
    	#include <sys/wait.h>	// WEXITSTATUS
    
    	#define TTYDEFCHARS		// needed for ttydefchars definition
    	#include <sys/ttydefaults.h>	// Linux - it's motherfucked.
    #if defined(__FreeBSD__)
    	#include <libutil.h>	// forkpty()
    #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DARWIN__)
    	#include <util.h>
    #elif defined(__linux__)
    	#include <pty.h>
    #elif defined(__QNX__)
    #if 0
    	#include <unix.h>
    #else
    	#define NEEDS_FORKPTY
    #endif
    #endif
    
    	#ifdef NEEDS_FORKPTY
    	#include <grp.h>
    	#endif
    
    	#include <termios.h>
    
    /*
     * Control Character Defaults
     */
    #ifndef CTRL
    	#define CTRL(x)	(x&037)
    #endif
    #ifndef CEOF
    	#define	CEOF		CTRL('d')
    #endif
    #ifndef CEOL
    	#define	CEOL		0xff		/* XXX avoid _POSIX_VDISABLE */
    #endif
    #ifndef CERASE
    	#define	CERASE		0177
    #endif
    #ifndef CERASE2
    	#define	CERASE2		CTRL('h')
    #endif
    #ifndef CINTR
    	#define	CINTR		CTRL('c')
    #endif
    #ifndef CSTATUS
    	#define	CSTATUS		CTRL('t')
    #endif
    #ifndef CKILL
    	#define	CKILL		CTRL('u')
    #endif
    #ifndef CMIN
    	#define	CMIN		1
    #endif
    #ifndef CQUIT
    	#define	CQUIT		034		/* FS, ^\ */
    #endif
    #ifndef CSUSP
    	#define	CSUSP		CTRL('z')
    #endif
    #ifndef CTIME
    	#define	CTIME		0
    #endif
    #ifndef CDSUSP
    	#define	CDSUSP		CTRL('y')
    #endif
    #ifndef CSTART
    	#define	CSTART		CTRL('q')
    #endif
    #ifndef CSTOP
    	#define	CSTOP		CTRL('s')
    #endif
    #ifndef CLNEXT
    	#define	CLNEXT		CTRL('v')
    #endif
    #ifndef CDISCARD
    	#define	CDISCARD 	CTRL('o')
    #endif
    #ifndef CWERASE
    	#define	CWERASE 	CTRL('w')
    #endif
    #ifndef CREPRINT
    	#define	CREPRINT 	CTRL('r')
    #endif
    #ifndef CEOT
    	#define	CEOT		CEOF
    #endif
    /* compat */
    #ifndef CBRK
    	#define	CBRK		CEOL
    #endif
    #ifndef CRPRNT
    	#define CRPRNT		CREPRINT
    #endif
    #ifndef CFLUSH
    	#define	CFLUSH		CDISCARD
    #endif
    
    #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)
    #endif
    #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
    	};
    #endif
    
    #endif	/* __unix__ */
    
    #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);
    }
    
    static void petscii_convert(BYTE* buf, ulong len)
    {
        for(ulong i=0; i<len; i++) {
    		buf[i] = cp437_to_petscii(buf[i]);
    	}
    }
    
    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;
    
    	if(*cmdline == '?' || *cmdline == '*')
    		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));
    #ifdef JAVASCRIPT
    	#define XTRN_LOADABLE_JS_MODULE(cmdline,mode,startup_dir)	\
    	if(cmdline[0]=='?' && (mode&EX_SH))						\
    		return(js_execxtrn(cmdline+1, startup_dir));		\
    	if(cmdline[0]=='?')										\
    		return(js_execfile(cmdline+1,startup_dir));
    #else
    	#define XTRN_LOADABLE_JS_MODULE
    #endif
    
    #ifdef _WIN32
    
    #include "execvxd.h"	/* DOSXTRN.EXE API */
    
    extern SOCKET node_socket[];
    
    /*****************************************************************************/
    // 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);	\
    	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	path[MAX_PATH+1];
        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	was_online=true;
    	bool	rio_abortable_save=rio_abortable;
    	bool	use_pipes=false;	// NT-compatible console redirection
    	BOOL	success;
    	BOOL	processTerminated=false;
    	uint	i;
        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;
    	HANDLE	hangup_event=NULL;
    	HANDLE	rdoutpipe;
    	HANDLE	wrinpipe;
        PROCESS_INFORMATION process_info;
    	unsigned long	rd;
        unsigned long	wr;
        unsigned long	len;
        DWORD	avail;
    	unsigned long	msglen;
    	unsigned long	retval;
    	DWORD	last_error;
    	DWORD	loop_since_io=0;
    	struct	tm tm;
    	str_list_t	env_list;
    
    	xtrn_mode = mode;
    	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,mode,startup_dir);
    
    	attr(cfg.color[clr_external]);		/* setup default attributes */
    
    	native = native_executable(&cfg, cmdline, mode);
    
    	if(!native && (startup->options&BBS_OPT_NO_DOS)) {
    		lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOS programs not supported: %s", cmdline);
    		bprintf("Sorry, DOS programs are not supported on this node.\r\n");
    		return -1;
    	}
    
    	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]!='.')
           	SAFEPRINTF3(fullcmdline, "%s%s%s", comspec_str, startup_dir, cmdline);
        else
        	SAFEPRINTF2(fullcmdline, "%s%s", comspec_str, cmdline);
    
    	SAFECOPY(realcmdline, fullcmdline);	// for errormsg if failed to execute
    
    	now=time(NULL);
    	if(localtime_r(&now,&tm)==NULL)
    		memset(&tm,0,sizeof(tm));
    
    	if(native && mode&EX_STDOUT && !(mode&EX_OFFLINE))
    		use_pipes=true;
    
     	if(native) { // Native (not MS-DOS) 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);
    		/* date/time env vars */
    		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));
    
    		SAFEPRINTF(path,"%sDOSXTRN.RET", cfg.node_dir);
    		(void)remove(path);
    
        	// Create temporary environment file
        	SAFEPRINTF(path,"%sDOSXTRN.ENV", node_dir);
            FILE* fp=fopen(path,"w");
            if(fp==NULL) {
    			XTRN_CLEANUP;
            	errormsg(WHERE, ERR_CREATE, path, 0);
                return(errno);
            }
            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);
    		fprintf(fp, "PCBDRIVE=%.2s\n", node_dir);
    		fprintf(fp, "PCBDIR=%s\n", node_dir + 2);
    		/* 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);
            fclose(fp);
    
            SAFEPRINTF2(fullcmdline, "%sDOSXTRN.EXE %s", cfg.exec_dir, path);
    
    		if(!(mode&EX_OFFLINE)) {
    			if(mode & EX_UART)
    				i=SBBSEXEC_MODE_UART;
    			else {
    				i=SBBSEXEC_MODE_FOSSIL;
    				if(mode&EX_STDIN)
               			i|=SBBSEXEC_MODE_DOS_IN;
    				if(mode&EX_STDOUT)
            			i|=SBBSEXEC_MODE_DOS_OUT;
    			}
    			BOOL x64 = FALSE;
    			IsWow64Process(GetCurrentProcess(), &x64);
    			sprintf(str," %s %u %u"
    				,x64 ? "x64" : "NT", 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) {
    				XTRN_CLEANUP;
    				errormsg(WHERE, ERR_CREATE, str, 0);
    				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) {
    				XTRN_CLEANUP;
    				errormsg(WHERE, ERR_CREATE, str, 0);
    				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 {
    		SAFEPRINTF3(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);
    			strListFreeBlock(env_block);
    			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);
    			CloseHandle(rdoutpipe);
    			strListFreeBlock(env_block);
    			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 | EX_STDIN))) {
    		if(passthru_thread_running)
    			passthru_socket_activate(true);
    		else
    			pthread_mutex_lock(&input_thread_mutex);
    	}
    
        success=CreateProcess(
    		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
    		);
    
    	strListFreeBlock(env_block);
    
    	if(!success) {
    		XTRN_CLEANUP;
    		if(native && !(mode & (EX_OFFLINE | EX_STDIN))) {
    			if(passthru_thread_running)
    				passthru_socket_activate(false);
    			else
    				pthread_mutex_unlock(&input_thread_mutex);
    		}
    		SetLastError(last_error);	/* Restore LastError */
            errormsg(WHERE, ERR_EXEC, realcmdline, mode);
    		SetLastError(last_error);	/* Restore LastError */
            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
    
    	CloseHandle(process_info.hThread);
    
    	/* Disable Ctrl-C checking */
    	if(!(mode&EX_OFFLINE))
    		rio_abortable=false;
    
        // Executing app in foreground?, monitor
        retval=STILL_ACTIVE;
        while(!(mode&EX_BG)) {
    		if(mode&EX_CHKTIME)
    			gettimeleft();
            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) {
    					SetEvent(hungup_event);
    				}
    	            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;
    		} else {
    
    			/* 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 (%s) opening %s", GetLastError(), strerror(errno), str);
    					else
    						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);
    					else
    						lprintf(LOG_DEBUG,"!GetMailslotInfo(0x%x)=%u", wrslot, GetLastError());
    				} else {
    					if(len!=wr)
    						lprintf(LOG_WARNING,"VDD short write (%u instead of %u)", len,wr);
    					RingBufRead(&inbuf, NULL, len);
    					if(use_pipes && !(mode&EX_NOECHO)) {
    						/* echo */
    						RingBufWrite(&outbuf, bp, len);
    					}
    				}
    				wr=len;
    			}
    
    			/* Read from VDD */
    
    			rd=0;
    			len=sizeof(buf);
    			avail=RingBufFree(&outbuf)/2;	// leave room for wwiv/telnet expansion
    #if 0
    			if(avail==0)
    				lprintf("Node %d !output buffer full (%u bytes)"
    					,cfg.node_num,RingBufFull(&outbuf));
    #endif
    			if(len>avail)
                	len=avail;
    
    			while(rd<len) {
    				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))
    						lprintf(LOG_ERR,"WWIV_BUF OVERRUN");
    				} else if(telnet_mode&TELNET_MODE_OFF) {
    					bp=buf;
    				} else {
                    	rd = telnet_expand(buf, rd, telnet_buf, sizeof(telnet_buf), /* expand_cr: */false, &bp);
    				}
    				if(rd>RingBufFree(&outbuf)) {
    					lprintf(LOG_ERR,"output buffer overflow");
    					rd=RingBufFree(&outbuf);
    				}
    				if(mode&EX_BIN)
    					RingBufWrite(&outbuf, bp, rd);
    				else
    					rputs((char*)bp, rd);
    			}
    #if defined(_DEBUG) && 0
    			if(rd>1) {
    				sprintf(str,"Node %d read %5d bytes from xtrn", cfg.node_num, rd);
    				OutputDebugString(str);
    			}
    #endif
                if((!rd && !wr) || hungup) {
    
    				loop_since_io++;	/* number of loop iterations with no I/O */
    
    				/* only check process termination after 300 milliseconds of no I/O */
    				/* to allow for last minute reception of output from DOS programs */
    				if(loop_since_io>=3) {
    
    					if(online && hangup_event!=NULL
    						&& WaitForSingleObject(hangup_event,0)==WAIT_OBJECT_0) {
    						lprintf(LOG_NOTICE,"Node %d External program requested hangup (dropped DTR)"
    							,cfg.node_num);
    						hangup();
    					}
    
    					if(WaitForSingleObject(process_info.hProcess,0)==WAIT_OBJECT_0)
    						break;	/* Process Terminated */
    				}
    
    				/* only check node for interrupt flag every 3 seconds of no I/O */
    				if((loop_since_io%30)==0) {
    					// Check if the node has been interrupted
    					getnodedat(cfg.node_num,&thisnode,0);
    					if(thisnode.misc&NODE_INTR)
    						break;
    				}
    
    				/* only send telnet GA every 30 seconds of no I/O */
    				if((loop_since_io%300)==0) {
    #if defined(_DEBUG)
    					sprintf(str,"Node %d xtrn idle\n",cfg.node_num);
    					OutputDebugString(str);
    #endif
    					// Let's make sure the socket is up
    					// Sending will trigger a socket d/c detection
    					if(!(startup->options&BBS_OPT_NO_TELNET_GA))
    						send_telnet_cmd(TELNET_GA,0);
    				}
    				sem_trywait_block(&inbuf.sem,100);
                } else
    				loop_since_io=0;
            }
    	}
    
        if(!(mode&EX_BG)) {			/* !background execution */
    
            if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE)
                errormsg(WHERE, ERR_CHK, "ExitCodeProcess",(DWORD)process_info.hProcess);
    
    		if(retval==STILL_ACTIVE) {
    			lprintf(LOG_INFO,"Node %d Terminating process from line %d",cfg.node_num,__LINE__);
    			TerminateProcess(process_info.hProcess, GetLastError());
    		}
    
    	 	// Get return value
    		if(!native) {
        		sprintf(str,"%sDOSXTRN.RET", cfg.node_dir);
    			FILE* fp=fopen(str,"r");
    			if(fp!=NULL) {
    				if(fscanf(fp,"%d",&retval) != 1) {
    					lprintf(LOG_ERR, "Node %d Error reading return value from %s", cfg.node_num, str);
    					retval = -1;
    				}
    				fclose(fp);
    			}
    		}
    	}
    
    	XTRN_CLEANUP;
    	CloseHandle(process_info.hProcess);
    
    	if(!(mode&EX_OFFLINE)) {	/* !off-line execution */
    
    		if(!WaitForOutbufEmpty(5000))
    			lprintf(LOG_WARNING, "%s Timeout waiting for output buffer to empty", __FUNCTION__);
    
    		if(native && !(mode & EX_STDIN)) {
    			if(passthru_thread_running)
    				passthru_socket_activate(false);
    			else
    				pthread_mutex_unlock(&input_thread_mutex);
    		}
    
    		curatr=~0;			// Can't guarantee current attributes
    		attr(LIGHTGRAY);	// Force to "normal"
    
    		rio_abortable=rio_abortable_save;	// Restore abortable state
    
    		/* Got back to Text/NVT mode */
    		request_telnet_opt(TELNET_DONT,TELNET_BINARY_TX);
    	}
    
    //	lprintf("%s returned %d",realcmdline, retval);
    
    	errorlevel = retval; // Baja or JS retrievable error value
    
    	return(retval);
    }
    
    #else	/* !WIN32 */
    
    /*****************************************************************************/
    // Expands Unix LF to CRLF
    /*****************************************************************************/
    BYTE* lf_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
    {
    	ulong	i,j;
    
    	for(i=j=0;i<inlen;i++) {
    		if(inbuf[i]=='\n' && (!i || inbuf[i-1]!='\r'))
    			outbuf[j++]='\r';
    		outbuf[j++]=inbuf[i];
    	}
    	newlen=j;
        return(outbuf);
    }
    
    #define MAX_ARGS 1000
    
    #ifdef NEEDS_SETENV
    static int setenv(const char *name, const char *value, int overwrite)
    {
    	char *envstr;
    	char *oldenv;
    	if(overwrite || getenv(name)==NULL) {
    		envstr=(char *)malloc(strlen(name)+strlen(value)+2);
    		if(envstr==NULL) {
    			errno=ENOMEM;
    			return(-1);
    		}
    		/* Note, on some platforms, this can be free()d... */
    		sprintf(envstr,"%s=%s",name,value);
    		putenv(envstr);
    	}
    	return(0);
    }
    #endif
    
    #ifdef NEEDS_CFMAKERAW
    void
    cfmakeraw(struct termios *t)
    {
    	t->c_iflag &= ~(IMAXBEL|IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
    	t->c_oflag &= ~OPOST;
    	t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
    	t->c_cflag &= ~(CSIZE|PARENB);
    	t->c_cflag |= CS8;
    }
    #endif
    
    #ifdef NEEDS_FORKPTY
    static int login_tty(int fd)
    {
    	(void) setsid();
    	if (!isatty(fd))
    		return (-1);
    	(void) dup2(fd, 0);
    	(void) dup2(fd, 1);
    	(void) dup2(fd, 2);
    	if (fd > 2)
    		(void) close(fd);
    	return (0);
    }
    
    #ifdef NEEDS_DAEMON
    /****************************************************************************/
    /* Daemonizes the process                                                   */
    /****************************************************************************/
    int
    daemon(int nochdir, int noclose)
    {
        int fd;
    
        switch (fork()) {
        case -1:
            return (-1);
        case 0:
            break;
        default:
            _exit(0);
        }
    
        if (setsid() == -1)
            return (-1);
    
        if (!nochdir)
            (void)chdir("/");
    
        if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
            (void)dup2(fd, STDIN_FILENO);
            (void)dup2(fd, STDOUT_FILENO);
            (void)dup2(fd, STDERR_FILENO);
            if (fd > 2)
                (void)close(fd);
        }
        return (0);
    }
    #endif
    
    static int openpty(int *amaster, int *aslave, char *name, struct termios *termp, winsize *winp)
    {
    	char line[] = "/dev/ptyXX";
    	const char *cp1, *cp2;
    	int master, slave, ttygid;
    	struct group *gr;
    
    	if ((gr = getgrnam("tty")) != NULL)
    		ttygid = gr->gr_gid;
    	else
    		ttygid = -1;
    
    	for (cp1 = "pqrsPQRS"; *cp1; cp1++) {
    		line[8] = *cp1;
    		for (cp2 = "0123456789abcdefghijklmnopqrstuv"; *cp2; cp2++) {
    			line[5] = 'p';
    			line[9] = *cp2;
    			if ((master = open(line, O_RDWR, 0)) == -1) {
    				if (errno == ENOENT)
    					break; /* try the next pty group */
    			} else {
    				line[5] = 't';
    				(void) chown(line, getuid(), ttygid);
    				(void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
    				/* Hrm... SunOS doesn't seem to have revoke
    				(void) revoke(line); */
    				if ((slave = open(line, O_RDWR, 0)) != -1) {
    					*amaster = master;
    					*aslave = slave;
    					if (name)
    						strcpy(name, line);
    					if (termp)
    						(void) tcsetattr(slave,
    							TCSAFLUSH, termp);
    					if (winp)
    						(void) ioctl(slave, TIOCSWINSZ,
    							(char *)winp);
    					return (0);
    				}
    				(void) close(master);
    			}
    		}
    	}
    	errno = ENOENT;	/* out of ptys */
    	return (-1);
    }
    
    static int forkpty(int *amaster, char *name, termios *termp, winsize *winp)
    {
    	int master, slave, pid;
    
    	if (openpty(&master, &slave, name, termp, winp) == -1)
    		return (-1);
    	switch (pid = FORK()) {
    	case -1:
    		return (-1);
    	case 0:
    		/*
    		 * child
    		 */
    		(void) close(master);
    		login_tty(slave);
    		return (0);
    	}
    	/*
    	 * parent
    	 */
    	*amaster = master;
    	(void) close(slave);
    	return (pid);
    }
    #endif /* NEED_FORKPTY */
    
    int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
    {
    	char	str[MAX_PATH+1];
    	char	fname[MAX_PATH+1];
    	char	fullpath[MAX_PATH+1];
    	char	fullcmdline[MAX_PATH+1];
    	char*	argv[MAX_ARGS + 1];
    	BYTE*	bp;
    	BYTE	buf[XTRN_IO_BUF_LEN];
        BYTE 	output_buf[XTRN_IO_BUF_LEN*2];
    	ulong	avail;
        ulong	output_len;
    	bool	native=false;			// DOS program by default
    	bool	rio_abortable_save=rio_abortable;
    	int		i;
    	bool	data_waiting;
    	int		rd;
    	int		wr;
    	int		argc;
    	pid_t	pid;
    	int		in_pipe[2];
    	int		out_pipe[2];
    	int		err_pipe[2];
        BYTE 	wwiv_buf[XTRN_IO_BUF_LEN*2];
        bool	wwiv_flag=false;
     	char* p;
    #ifdef PREFER_POLL
    	struct pollfd fds[2];
    #else
    #error select() implementation was removed in 3971ef4d
    #endif
    
    	xtrn_mode = mode;
    	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;
    	}
    
    	if(startup_dir==NULL)
    		startup_dir=nulstr;
    
    	XTRN_LOADABLE_MODULE(cmdline,startup_dir);
    	XTRN_LOADABLE_JS_MODULE(cmdline,mode,startup_dir);
    
    	attr(cfg.color[clr_external]);  /* setup default attributes */
    
    	native = native_executable(&cfg, cmdline, mode);
    
        SAFECOPY(str,cmdline);			/* Set fname to program name only */
    	truncstr(str," ");
        SAFECOPY(fname,getfname(str));
    
    	sprintf(fullpath,"%s%s",startup_dir,fname);
    	if(startup_dir!=NULL && cmdline[0]!='/' && cmdline[0]!='.' && fexist(fullpath))
    		sprintf(fullcmdline,"%s%s",startup_dir,cmdline);
    	else
    		SAFECOPY(fullcmdline,cmdline);
    
     	if(native) { // Native (not MS-DOS) external
    
    		// Current environment passed to child process
    		sprintf(dszlog,"%sPROTOCOL.LOG",cfg.node_dir);
    		setenv("DSZLOG",dszlog,1); 		/* Makes the DSZ LOG active */
    		setenv("SBBSNODE",cfg.node_dir,1);
    		setenv("SBBSCTRL",cfg.ctrl_dir,1);
    		setenv("SBBSDATA",cfg.data_dir,1);
    		setenv("SBBSEXEC",cfg.exec_dir,1);
    		sprintf(str,"%u",cfg.node_num);
    		setenv("SBBSNNUM",str,1);
    
    		/* date/time env vars */
    		now = time(NULL);
    		struct	tm tm;
    		if(localtime_r(&now, &tm) == NULL)
    			memset(&tm, 0, sizeof(tm));
    		sprintf(str," %02u", tm.tm_mday);
    		setenv("DAY", str, /* overwrite */TRUE);
    		setenv("WEEKDAY", wday[tm.tm_wday], /* overwrite */TRUE);
    		setenv("MONTHNAME", mon[tm.tm_mon], /* overwrite */TRUE);
    		sprintf(str, "%02u", tm.tm_mon + 1);
    		setenv("MONTH", str, /* overwrite */TRUE);
    		sprintf(str,"%u", 1900 + tm.tm_year);
    		if(setenv("YEAR", str, /* overwrite */TRUE) != 0)
    			errormsg(WHERE,ERR_WRITE,"environment",0);
    
    	} else {
    		if(startup->options&BBS_OPT_NO_DOS) {
    			lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOS programs not supported: %s", cmdline);
    			bprintf("Sorry, DOS programs are not supported on this node.\r\n");
    			return -1;
    		}
    #if defined(__FreeBSD__)
    		/* ToDo: This seems to work for every door except Iron Ox
    		   ToDo: Iron Ox is unique in that it runs perfectly from
    		   ToDo: tcsh but not at all from anywhere else, complaining
    		   ToDo: about corrupt files.  I've ruled out the possibilty
    		   ToDo: of it being a terminal mode issue... no other ideas
    		   ToDo: come to mind. */
    
    		FILE * doscmdrc;
    
    		sprintf(str,"%s.doscmdrc",cfg.node_dir);
    		if((doscmdrc=fopen(str,"w+"))==NULL)  {
    			errormsg(WHERE,ERR_CREATE,str,0);
    			return(-1);
    		}
    		if(startup_dir!=NULL && startup_dir[0])
    			fprintf(doscmdrc,"assign C: %s\n",startup_dir);
    		else
    			fprintf(doscmdrc,"assign C: .\n");
    
    		fprintf(doscmdrc,"assign D: %s\n",cfg.node_dir);
    		SAFECOPY(str,cfg.exec_dir);
    		if((p=strrchr(str,'/'))!=NULL)
    			*p=0;
    		if((p=strrchr(str,'/'))!=NULL)
    			*p=0;
    		fprintf(doscmdrc,"assign E: %s\n",str);
    
    		/* setup doscmd env here */
    		/* ToDo Note, this assumes that the BBS uses standard dir names */
    		fprintf(doscmdrc,"DSZLOG=E:\\node%d\\PROTOCOL.LOG\n",cfg.node_num);
    		fprintf(doscmdrc,"SBBSNODE=D:\\\n");
    		fprintf(doscmdrc,"SBBSCTRL=E:\\ctrl\\\n");
    		fprintf(doscmdrc,"SBBSDATA=E:\\data\\\n");
    		fprintf(doscmdrc,"SBBSEXEC=E:\\exec\\\n");
    		fprintf(doscmdrc,"SBBSNNUM=%d\n",cfg.node_num);
    		fprintf(doscmdrc,"PCBNODE=%d\n",cfg.node_num);
    		fprintf(doscmdrc,"PCBDRIVE=D:\n");
    		fprintf(doscmdrc,"PCBDIR=\\\n");
    
    		fclose(doscmdrc);
    		SAFECOPY(str,fullcmdline);
    		sprintf(fullcmdline,"%s -F %s",startup->dosemu_path,str);
    
    #elif defined(__linux__)
    
    		/* dosemu integration  --  originally by Ryan Underwood, <nemesis @ icequake.net> */
    
    		FILE *dosemubatfp;
    		FILE *externalbatfp;
    		FILE *de_launch_inifp;
    		char tok[MAX_PATH+1];
    		char buf[1024];
    		char bufout[1024];
    
    		char cmdlinebatch[MAX_PATH+1];
    		char externalbatsrc[MAX_PATH+1];
    		char externalbat[MAX_PATH+1];
    		char dosemuconf[MAX_PATH+1];
    		char de_launch_cmd[INI_MAX_VALUE_LEN];
    		char dosemubinloc[MAX_PATH+1];
    		char virtualconf[75];
    		char dosterm[15];
    		char log_external[MAX_PATH+1];
    		const char* runtype;
    		str_list_t de_launch_ini;
    		
    		/*  on the Unix side. xtrndir is the parent of the door's startup dir. */
    		char xtrndir[MAX_PATH+1];
    
    		/*  on the DOS side.  */
    		char xtrndir_dos[MAX_PATH+1];
    		char ctrldir_dos[MAX_PATH+1];
    		char datadir_dos[MAX_PATH+1];
    		char execdir_dos[MAX_PATH+1];
    		char nodedir_dos[MAX_PATH+1];
    
    		/* Default locations that can be overridden by
    		 * the sysop in emusetup.bat */
    
    		const char ctrldrive[] = DOSEMU_CTRL_DRIVE;
    		const char datadrive[] = DOSEMU_DATA_DRIVE;
    		const char execdrive[] = DOSEMU_EXEC_DRIVE;
    		const char nodedrive[] = DOSEMU_NODE_DRIVE;
    		
    		const char external_bat_fn[] = "external.bat";
    		const char dosemu_cnf_fn[] = "dosemu.conf";
    
    		SAFECOPY(str,startup_dir);
    		if(*(p=lastchar(str))=='/')		/* kill trailing slash */
    			*p=0;
    		if((p=strrchr(str,'/'))!=NULL)  /* kill the last element of the path */
    			*p=0;
    
    		SAFECOPY(xtrndir,str);
    
    		SAFECOPY(xtrndir_dos,xtrndir);
    		REPLACE_CHARS(xtrndir_dos,'/','\\',p);
    		
    		SAFECOPY(ctrldir_dos,cfg.ctrl_dir);
    		REPLACE_CHARS(ctrldir_dos,'/','\\',p);
    
    		p=lastchar(ctrldir_dos);
    		if (*p=='\\') *p=0;
    
    		SAFECOPY(datadir_dos,cfg.data_dir);
    		REPLACE_CHARS(datadir_dos,'/','\\',p);
    
    		p=lastchar(datadir_dos);
    		if (*p=='\\') *p=0;
    
    		SAFECOPY(execdir_dos,cfg.exec_dir);
    		REPLACE_CHARS(execdir_dos,'/','\\',p);
    
    		p=lastchar(execdir_dos);
    		if (*p=='\\') *p=0;
    
    		SAFECOPY(nodedir_dos,cfg.node_dir);
    		REPLACE_CHARS(nodedir_dos,'/','\\',p);
     
    		p=lastchar(nodedir_dos);
    		if (*p=='\\') *p=0;
    
    		/* must have sbbs.ini bbs useDOSemu=1 (or empty), cannot be =0 */
    		if (!startup->usedosemu) {
    			lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOSEMU disabled, program not run");
    			bprintf("Sorry, DOSEMU is not supported on this node.\r\n");
    			return -1;
    		}
    
    		/* must have sbbs.ini bbs DOSemuPath set to valid path */
    		SAFECOPY(dosemubinloc,(cmdstr(startup->dosemu_path,nulstr,nulstr,tok)));
    		if (dosemubinloc[0] == '\0') {
    			lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOSEMU invalid DOSEmuPath, program not run");
    			bprintf("Sorry, DOSEMU is not supported on this node.\r\n");
    			return -1;
    		}
    
    		if (!fexist(dosemubinloc)) {
    			lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOSEMU not found: %s", dosemubinloc);
    			bprintf("Sorry, DOSEMU is not supported on this node.\r\n");
    			return -1;
    		}
    
    		/* check for existence of a dosemu.conf in the door directory.
    		 * It is a good idea to be able to use separate configs for each
    		 * door. 
    		 *
    		 * First check startup_dir, then check cfg.ctrl_dir
    		 */
    		SAFEPRINTF2(str,"%s%s",startup_dir, dosemu_cnf_fn);
    		if (!fexist(str)) {
    			/* If we can't find it in the door dir, look for the configured one */
    			SAFECOPY(str,startup->dosemuconf_path);
    			if (!isabspath(str)) {
    				SAFEPRINTF2(str,"%s%s", cfg.ctrl_dir, startup->dosemuconf_path);
    			}
    			if (!fexist(str)) {
    				/* If we couldn't find either, try for the system one, then
    				 * error out. */
    				SAFEPRINTF(str,"/etc/dosemu/%s", dosemu_cnf_fn);
    				if (!fexist(str)) {
    					SAFEPRINTF(str,"/etc/%s", dosemu_cnf_fn);
    					if (!fexist(str)) {
    						errormsg(WHERE,ERR_READ,str,0);
    						return(-1);
    					}
    					else SAFECOPY(dosemuconf,str);  /* using system conf */
    				}
    				else SAFECOPY(dosemuconf,str);  /* using system conf */
    			}
    			else SAFECOPY(dosemuconf,str);   /* using global conf */
    		}
    		else SAFECOPY(dosemuconf,str);  /* using door-specific conf */
    
    		/* Create the external bat here to be placed in the node dir. */
    		SAFEPRINTF2(str,"%s%s",cfg.node_dir,external_bat_fn);
    		if(!(dosemubatfp=fopen(str,"w+"))) {
    			errormsg(WHERE,ERR_CREATE,str,0);
    			return(-1);
    		}
    
    		fprintf(dosemubatfp,"@ECHO OFF\r\n");
    		fprintf(dosemubatfp,"SET DSZLOG=%s\\PROTOCOL.LOG\r\n",nodedrive);
    		fprintf(dosemubatfp,"SET SBBSNODE=%s\r\n",nodedrive);
    		fprintf(dosemubatfp,"SET SBBSNNUM=%d\r\n",cfg.node_num);
    		fprintf(dosemubatfp,"SET SBBSCTRL=%s\r\n",ctrldrive);
    		fprintf(dosemubatfp,"SET SBBSDATA=%s\r\n",datadrive);
    		fprintf(dosemubatfp,"SET SBBSEXEC=%s\r\n",execdrive);
    		fprintf(dosemubatfp,"SET PCBNODE=%d\r\n",cfg.node_num);
    		fprintf(dosemubatfp,"SET PCBDRIVE=%s\r\n",nodedrive);
    		fprintf(dosemubatfp,"SET PCBDIR=\\\r\n");
    
    		char gamedir[MAX_PATH+1];
    		if(startup_dir!=NULL && startup_dir[0]) {
    			SAFECOPY(str, startup_dir);
    			*lastchar(str) = 0;
    			SAFECOPY(gamedir, getfname(str));
    		}
     
    		if(*gamedir == 0) {
    			lprintf(LOG_ERR, "No startup directory configured for DOS command-line: %s", cmdline);
    			fclose(dosemubatfp);
    			return -1;
    		}
    
    		/* external editors use node dir so unset this */
    		if (startup_dir == cfg.node_dir) {
    			*gamedir = '\0';
    		}
    		
    		fprintf(dosemubatfp,"SET STARTDIR=%s\r\n",gamedir);
    
    		/* Check the "Stdio Interception" flag from scfg for this door.  If it's
    		 * enabled, we enable doorway mode.  Else, it's vmodem for us, unless
    		 * it's a timed event.
    		 */
    
    		if (!(mode&(EX_STDIO)) && online!=ON_LOCAL) {
    			SAFECOPY(virtualconf,"-I\"serial { virtual com 1 }\"");
    			runtype = "FOSSIL";
    		} else {
    			virtualconf[0] = '\0';
    			runtype = "STDIO";
    		}
    
    		/* now append exec/external.bat (which is editable) to this 
    		 generated file */
    		SAFEPRINTF2(str,"%s%s",startup_dir,external_bat_fn);
    
    		if ((startup_dir == cfg.node_dir) || !fexist(str)) {
    			SAFEPRINTF2(str,"%s%s",cfg.exec_dir, external_bat_fn);
    			if (!fexist(str)) {
    				errormsg(WHERE,ERR_READ,str,0);
    				fclose(dosemubatfp);
    				return(-1);
    			} 
    		}
    
            SAFECOPY(externalbatsrc, str); 
    
    		if (!(externalbatfp=fopen(externalbatsrc,"r"))) {
    			errormsg(WHERE,ERR_OPEN,externalbatsrc,0);
    			fclose(dosemubatfp);
    			return(-1);
    		} 
    
    		/* append the command line to the batch file */
    		SAFECOPY(tok,cmdline);
    		truncstr(tok," ");
    		p = getfext(tok);  
    		/*  check if it's a bat file  */
    		if (p != NULL && stricmp(p, ".bat") == 0) {
    			SAFEPRINTF(cmdlinebatch, "CALL %s", cmdline);
    		} else {
    			SAFECOPY(cmdlinebatch, cmdline);
    		}
    
    		named_string_t externalbat_replacements[] = {
    			{(char*)"CMDLINE", cmdlinebatch},
    			{(char*)"DSZLOG", (char*)nodedrive},
    			{(char*)"SBBSNODE", (char*)nodedrive},
    			{(char*)"SBBSCTRL", (char*)ctrldrive},
    			{(char*)"SBBSDATA", (char*)datadrive},
    			{(char*)"SBBSEXEC", (char*)execdrive},
    			{(char*)"XTRNDIR", xtrndir_dos},
    			{(char*)"CTRLDIR", ctrldir_dos},
    			{(char*)"DATADIR", datadir_dos},
    			{(char*)"EXECDIR", execdir_dos},
    			{(char*)"NODEDIR", nodedir_dos},
    			{(char*)"STARTDIR", (char*)gamedir},
    			{(char*)"RUNTYPE", (char *)runtype},
    			{NULL, NULL}
    		};
    
    		named_int_t externalbat_int_replacements[] = {
    		    {(char*)"SBBSNNUM", cfg.node_num },
    		};
    
    		while(!feof(externalbatfp)) {
    			if (fgets(buf, sizeof(buf), externalbatfp)!=NULL) {
    				replace_named_values(buf, bufout, sizeof(bufout), "$", externalbat_replacements, 
    					externalbat_int_replacements, FALSE);
    				fprintf(dosemubatfp,"%s",bufout);
    			}
    		}
    
    		fclose(externalbatfp);
    
    		/* Set the interception bits, since we are always going to want Synchronet
    		 * to intercept dos programs under Unix.
    		 */
    
    		mode |= EX_STDIO;
    
    		/* Attempt to keep dosemu from prompting for a disclaimer. */
    
    		sprintf(str, "%s/.dosemu", cfg.ctrl_dir);
    		if (!isdir(str)) {
    			if(mkdir(str, 0755) != 0) {
    				errormsg(WHERE,ERR_MKDIR, str, 0755);
    				fclose(dosemubatfp);
    				return -1;
    			}
    		}
    		strcat(str, "/disclaimer");
    		ftouch(str);
    
    		/* Set up the command for dosemu to execute with 'unix -e'. */
    		SAFEPRINTF2(externalbat,"%s%s",nodedrive, external_bat_fn);
    
    		/* need TERM=linux for maintenance programs to work
    		 * (dosemu won't start with no controlling terminal)
    		 * Also, redirect stdout to a log if it's a timed event.
    		 */
    
    		if (online==ON_LOCAL) {
    			SAFECOPY(dosterm,"TERM=linux");
    			safe_snprintf(log_external, sizeof(log_external), ">> %sdosevent_%s.log",cfg.logs_dir,fname);
    		}
    		else {
    			dosterm[0]='\0';
    			log_external[0] = '\0';
    		}
    
    		/*
    		 * Get the global emu launch command
    		 */
    		 /* look for file in startup dir */
    		SAFEPRINTF(str,"%sdosemu.ini",startup_dir);
    		if (!fexist(str)) {
    			/* look for file in exec dir */        
        		SAFEPRINTF(str,"%sdosemu.ini",cfg.exec_dir);
    			if (!fexist(str)) {
    				errormsg(WHERE,ERR_OPEN,"dosemu.ini", 0);
    				fclose(dosemubatfp);
    				return(-1);
    			}
    		}
    
    		/* if file found, then open and process it */
    		if ((de_launch_inifp=iniOpenFile(str, false))==NULL) {
    			errormsg(WHERE,ERR_OPEN,str, 0);
    			fclose(dosemubatfp);
    			return(-1);
    		}         
    		de_launch_ini = iniReadFile(de_launch_inifp);
    		iniCloseFile(de_launch_inifp);
    		SAFECOPY(de_launch_cmd, "");
    		iniGetString(de_launch_ini, ROOT_SECTION, "cmd", nulstr, de_launch_cmd);
    		if (virtualconf[0] == '\0') {
    			iniGetString(de_launch_ini, "stdio", "cmd", de_launch_cmd, de_launch_cmd);
    		}
    		iniFreeStringList(de_launch_ini);
    
    		named_string_t de_ini_replacements[] = 
    		{
    			{(char*)"TERM", dosterm}, 
    			{(char*)"CTRLDIR", cfg.ctrl_dir},
    			{(char*)"NODEDIR", cfg.node_dir},
    			{(char*)"DOSEMUBIN", dosemubinloc},
    			{(char*)"VIRTUALCONF", virtualconf},
    			{(char*)"DOSEMUCONF", dosemuconf},
    			{(char*)"EXTBAT", externalbat},
    			{(char*)"EXTLOG", log_external},
    			{(char*)"RUNTYPE", (char *)runtype},
    			{NULL, NULL}
    		};
    		named_int_t de_ini_int_replacements[] = {
                {(char*)"NNUM", cfg.node_num },
            };
    		replace_named_values(de_launch_cmd, fullcmdline, sizeof(fullcmdline), (char*)"$", 
    			de_ini_replacements, de_ini_int_replacements, FALSE);
    
    		/* Drum roll. */      
    		fprintf(dosemubatfp,"REM For debugging: %s\r\n",fullcmdline);
    		fclose(dosemubatfp);
    
    #else
    		lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOS programs not supported: %s", cmdline);
    		bprintf("Sorry, DOS programs are not supported on this node.\r\n");
    
    		return(-1);
    #endif
    	}
    
    	if(!(mode & (EX_STDIN | EX_OFFLINE))) {
    		if(passthru_thread_running)
    			passthru_socket_activate(true);
    		else
    			pthread_mutex_lock(&input_thread_mutex);
    	}
    
    	if(!(mode&EX_NOLOG) && pipe(err_pipe)!=0) {
    		errormsg(WHERE,ERR_CREATE,"err_pipe",0);
    		return(-1);
    	}
    
    	if((mode&EX_STDIO)==EX_STDIO)  {
    		struct winsize winsize;
    		struct termios term;
    		memset(&term,0,sizeof(term));
    		cfsetispeed(&term,B19200);
    		cfsetospeed(&term,B19200);
    		if(mode&EX_BIN)
    			cfmakeraw(&term);
    		else {
    			term.c_iflag = TTYDEF_IFLAG;
    			term.c_oflag = TTYDEF_OFLAG;
    			term.c_lflag = TTYDEF_LFLAG;
    			term.c_cflag = TTYDEF_CFLAG;
    			memcpy(term.c_cc,ttydefchars,sizeof(term.c_cc));
    		}
    		winsize.ws_row=rows;
    		winsize.ws_col=cols;
    		if((pid=forkpty(&in_pipe[1],NULL,&term,&winsize))==-1) {
    			if(!(mode & (EX_STDIN | EX_OFFLINE))) {
    				if(passthru_thread_running)
    					passthru_socket_activate(false);
    				else
    					pthread_mutex_unlock(&input_thread_mutex);
    			}
    			errormsg(WHERE,ERR_EXEC,fullcmdline,0);
    			return(-1);
    		}
    		out_pipe[0]=in_pipe[1];
    	}
    	else  {
    		if(mode&EX_STDIN)
    			if(pipe(in_pipe)!=0) {
    				errormsg(WHERE,ERR_CREATE,"in_pipe",0);
    				return(-1);
    			}
    		if(mode&EX_STDOUT)
    			if(pipe(out_pipe)!=0) {
    				errormsg(WHERE,ERR_CREATE,"out_pipe",0);
    				return(-1);
    			}
    
    
    		if((pid=FORK())==-1) {
    			if(!(mode & (EX_STDIN | EX_OFFLINE))) {
    				if(passthru_thread_running)
    					passthru_socket_activate(false);
    				else
    					pthread_mutex_unlock(&input_thread_mutex);
    			}
    			errormsg(WHERE,ERR_EXEC,fullcmdline,0);
    			return(-1);
    		}
    	}
    	if(pid==0) {	/* child process */
    		/* Give away all privs for good now */
    		if(startup->setuid!=NULL)
    			startup->setuid(TRUE);
    
    		sigset_t        sigs;
    		sigfillset(&sigs);
    		sigprocmask(SIG_UNBLOCK,&sigs,NULL);
    		if(!(mode&EX_BIN))  {
    			if(term_supports(ANSI))
    				SAFEPRINTF(term_env,"TERM=%s",startup->xtrn_term_ansi);
    			else
    				SAFEPRINTF(term_env,"TERM=%s",startup->xtrn_term_dumb);
    			putenv(term_env);
    		}
    #ifdef __FreeBSD__
    		if(!native)
    			chdir(cfg.node_dir);
    		else
    #endif
    		if(startup_dir!=NULL && startup_dir[0])
    			if(chdir(startup_dir)!=0) {
    				errormsg(WHERE,ERR_CHDIR,startup_dir,0);
    				return(-1);
    			}
    
    		if(mode&EX_SH || strcspn(fullcmdline,"<>|;\"")!=strlen(fullcmdline)) {
    			argv[0]=comspec;
    			argv[1]=(char*)"-c";
    			argv[2]=fullcmdline;
    			argv[3]=NULL;
    		} else {
    			argv[0]=fullcmdline;	/* point to the beginning of the string */
    			argc=1;
    			for(i=0;fullcmdline[i] && argc<MAX_ARGS;i++)	/* Break up command line */
    				if(fullcmdline[i]==' ') {
    					fullcmdline[i]=0;			/* insert nulls */
    					argv[argc++]=fullcmdline+i+1; /* point to the beginning of the next arg */
    				}
    			argv[argc]=NULL;
    		}
    
    		if(mode&EX_STDIN && !(mode&EX_STDOUT))  {
    			close(in_pipe[1]);		/* close write-end of pipe */
    			dup2(in_pipe[0],0);		/* redirect stdin */
    			close(in_pipe[0]);		/* close excess file descriptor */
    		}
    
    		if(mode&EX_STDOUT && !(mode&EX_STDIN)) {
    			close(out_pipe[0]);		/* close read-end of pipe */
    			dup2(out_pipe[1],1);	/* stdout */
    			if(!(mode&EX_NOLOG))
    				dup2(out_pipe[1],2);	/* stderr */
    			close(out_pipe[1]);		/* close excess file descriptor */
    		}
    
    		if(!(mode & EX_STDIO)) {
    			int fd;
    
    			/* Redirect stdio to /dev/null */
    			if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
    				dup2(fd, STDIN_FILENO);
    				dup2(fd, STDOUT_FILENO);
    				if(!(mode&EX_NOLOG))
    					dup2(fd, STDERR_FILENO);
    				if (fd > 2)
    					close(fd);
    			}
    		}
    
    		if(mode&EX_BG)	/* background execution, detach child */
    		{
    			lprintf(LOG_INFO,"Detaching external process");
    			daemon(TRUE,FALSE);
       	    }
    
    		if(!(mode&EX_NOLOG)) {
    			close(err_pipe[0]);		/* close read-end of pipe */
    			dup2(err_pipe[1],2);	/* stderr */
    		}
    
    		execvp(argv[0],argv);
    		lprintf(LOG_ERR,"!ERROR %d (%s) executing: %s", errno, strerror(errno), argv[0]);
    		_exit(-1);	/* should never get here */
    	}
    
    	if(strcmp(cmdline, fullcmdline) != 0)
    		lprintf(LOG_DEBUG,"Executing cmd-line: %s", fullcmdline);
    
    	/* Disable Ctrl-C checking */
    	if(!(mode&EX_OFFLINE))
    		rio_abortable=false;
    
    	if(!(mode&EX_NOLOG))
    		close(err_pipe[1]);	/* close write-end of pipe */
    
    	if(mode&EX_STDOUT) {
    		if(!(mode&EX_STDIN))
    			close(out_pipe[1]);	/* close write-end of pipe */
    		fds[0].fd = out_pipe[0];
    		fds[0].events = POLLIN;
    		if(!(mode&EX_NOLOG)) {
    			fds[1].fd = err_pipe[0];
    			fds[1].events = POLLIN;
    		}
    		while(!terminated) {
    			if(waitpid(pid, &i, WNOHANG)!=0)	/* child exited */
    				break;
    
    			if(mode&EX_CHKTIME)
    				gettimeleft();
    
    			if(!online && !(mode&EX_OFFLINE)) {
    				logline(LOG_NOTICE,"X!","hung-up in external program");
    				break;
    			}
    
    			/* Input */
    			if(mode&EX_STDIN && RingBufFull(&inbuf)) {
    				if((wr=RingBufRead(&inbuf,buf,sizeof(buf)))!=0)
    					write(in_pipe[1],buf,wr);
    			}
    
    			bp=buf;
    			i=0;
    			if(mode&EX_NOLOG)
    				poll(fds, 1, 1);
    			else {
    				while (poll(fds, 2, 1) > 0 && (fds[1].revents)
    				    && (i < (int)sizeof(buf) - 1))  {
    					if((rd=read(err_pipe[0],bp,1))>0)  {
    						i+=rd;
    						bp++;
    						if(*(bp-1)=='\n')
    							break;
    					}
    					else
    						break;
    				}
    				if(i > 0) {
    					buf[i] = '\0';
    					p = (char*)buf;
    					truncsp(p);
    					SKIP_WHITESPACE(p);
    					if(*p)
    						lprintf(LOG_NOTICE, "%s: %s", fname, p);
    				}
    
    				/* Eat stderr if mode is EX_BIN */
    				if(mode&EX_BIN)  {
    					bp=buf;
    					i=0;
    				}
    			}
    
    			data_waiting=fds[0].revents;
    			if(i==0 && data_waiting==0)
    				continue;
    
    			avail=(RingBufFree(&outbuf)-i)/2;	// Leave room for wwiv/telnet expansion
    			if(avail==0) {
    #if 0
    				lprintf("Node %d !output buffer full (%u bytes)"
    						,cfg.node_num,RingBufFull(&outbuf));
    #endif
    				YIELD();
    				continue;
    			}
    
    			rd=avail;
    
    			if(rd>((int)sizeof(buf)-i))
    				rd=sizeof(buf)-i;
    
    			if(data_waiting)  {
    				rd=read(out_pipe[0],bp,rd);
    				if(rd<1 && i==0)
    					continue;
    				if(rd<0)
    					rd=0;
    			}
    			else
    				rd=0;
    
    			rd += i;
    
    			if(mode&EX_BIN) {
    				if(telnet_mode&TELNET_MODE_OFF) {
    					bp=buf;
    					output_len=rd;
    				}
    				else
       	       			output_len = telnet_expand(buf, rd, output_buf, sizeof(output_buf), /* expand_cr: */false, &bp);
    			} else {
    				if ((mode & EX_STDIO) != EX_STDIO) {
    					/* LF to CRLF expansion */
    					bp=lf_expand(buf, rd, output_buf, output_len);
    				} else if(mode&EX_WWIV) {
    					bp=wwiv_expand(buf, rd, wwiv_buf, output_len, useron.misc, wwiv_flag);
    					if(output_len > sizeof(wwiv_buf))
    						lprintf(LOG_ERR, "WWIV_BUF OVERRUN");
    				} else {
    					bp=buf;
    					output_len=rd;
    				}
    				if (term_supports(PETSCII))
    					petscii_convert(bp, output_len);
    			}
    			/* Did expansion overrun the output buffer? */
    			if(output_len>sizeof(output_buf)) {
    				lprintf(LOG_ERR,"OUTPUT_BUF OVERRUN");
    				output_len=sizeof(output_buf);
    			}
    
    			/* Does expanded size fit in the ring buffer? */
    			if(output_len>RingBufFree(&outbuf)) {
    				lprintf(LOG_ERR,"output buffer overflow");
    				output_len=RingBufFree(&outbuf);
    			}
    
    			RingBufWrite(&outbuf, bp, output_len);
    
    		}
    
    		if(waitpid(pid, &i, WNOHANG)==0)  {		// Child still running?
    			kill(pid, SIGHUP);					// Tell child user has hung up
    			time_t start=time(NULL);			// Wait up to 10 seconds
    			while(time(NULL)-start<10) {		// for child to terminate
    				if(waitpid(pid, &i, WNOHANG)!=0)
    					break;
    				mswait(500);
    			}
    			if(waitpid(pid, &i, WNOHANG)==0)	// Child still running?
    				kill(pid, SIGKILL);				// terminate child process
    		}
    		/* close unneeded descriptors */
    		if(mode&EX_STDIN)
    			close(in_pipe[1]);
    		close(out_pipe[0]);
    	}
    	if(mode&EX_NOLOG)
    		waitpid(pid, &i, 0);
    	else {
    		while(waitpid(pid, &i, WNOHANG)==0)  {
    			bp=buf;
    			i=0;
    			while (socket_readable(err_pipe[0], 1000) && (i<XTRN_IO_BUF_LEN-1))  {
    				if((rd=read(err_pipe[0],bp,1))>0)  {
    					i+=rd;
    					if(*bp=='\n') {
    						buf[i] = '\0';
    						p = (char*)buf;
    						truncsp(p);
    						SKIP_WHITESPACE(p);
    						if(*p)
    							lprintf(LOG_NOTICE, "%s: %s", fname, p);
    						i=0;
    						bp=buf;
    					}
    					else
    						bp++;
    				}
    				else
    					break;
    			}
    			if(i > 0) {
    				buf[i] = '\0';
    				p = (char*)buf;
    				truncsp(p);
    				SKIP_WHITESPACE(p);
    				if(*p)
    					lprintf(LOG_NOTICE, "%s: %s", fname, p);
    			}
    		}
    	}
    	if(!(mode&EX_OFFLINE)) {	/* !off-line execution */
    
    		if(!WaitForOutbufEmpty(5000))
    			lprintf(LOG_WARNING, "%s Timeout waiting for output buffer to empty", __FUNCTION__);
    
    		if(!(mode&EX_STDIN)) {
    			if(passthru_thread_running)
    				passthru_socket_activate(false);
    			else
    				pthread_mutex_unlock(&input_thread_mutex);
    		}
    
    		curatr=~0;			// Can't guarantee current attributes
    		attr(LIGHTGRAY);	// Force to "normal"
    
    		rio_abortable=rio_abortable_save;	// Restore abortable state
    
    		/* Got back to Text/NVT mode */
    		request_telnet_opt(TELNET_DONT,TELNET_BINARY_TX);
    	}
    
    	if(!(mode&EX_NOLOG))
    		close(err_pipe[0]);
    
    	return(errorlevel = WEXITSTATUS(i));
    }
    
    #endif	/* !WIN32 */
    
    static const char* quoted_string(const char* str, char* buf, size_t maxlen)
    {
    	if(strchr(str,' ')==NULL)
    		return(str);
    	safe_snprintf(buf,maxlen,"\"%s\"",str);
    	return(buf);
    }
    
    #define QUOTED_STRING(ch, str, buf, maxlen) \
    	((IS_ALPHA(ch) && IS_UPPERCASE(ch)) ? str : quoted_string(str,buf,maxlen))
    
    /*****************************************************************************/
    /* Returns command line generated from instr with %c replacements            */
    /*****************************************************************************/
    char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, char *outstr, long mode)
    {
    	char	str[MAX_PATH+1],*cmd;
        int		i,j,len;
    	bool	native = (mode == EX_UNSPECIFIED) || native_executable(&cfg, instr, mode);
    	(void) native;
    
        if(outstr==NULL)
            cmd=cmdstr_output;
        else
            cmd=outstr;
        len=strlen(instr);
    	int maxlen = (int)sizeof(cmdstr_output) - 1;
        for(i=j=0; i<len && j < maxlen; i++) {
            if(instr[i]=='%') {
                i++;
                cmd[j]=0;
    			int avail = maxlen - j;
    			char ch=instr[i];
    			if(IS_ALPHA(ch))
    				ch=toupper(ch);
                switch(ch) {
                    case 'A':   /* User alias */
                        strncat(cmd,QUOTED_STRING(instr[i],useron.alias,str,sizeof(str)), avail);
                        break;
                    case 'B':   /* Baud (DTE) Rate */
                        strncat(cmd,ultoa(dte_rate,str,10), avail);
                        break;
                    case 'C':   /* Connect Description */
                        strncat(cmd,connection, avail);
                        break;
                    case 'D':   /* Connect (DCE) Rate */
                        strncat(cmd,ultoa((ulong)cur_rate,str,10), avail);
                        break;
                    case 'E':   /* Estimated Rate */
                        strncat(cmd,ultoa((ulong)cur_cps*10,str,10), avail);
                        break;
                    case 'F':   /* File path */
    #if defined(__linux__)
    					if(!native && strncmp(fpath, cfg.node_dir, strlen(cfg.node_dir)) == 0) {
    						strncat(cmd, DOSEMU_NODE_DIR, avail);
    						strncat(cmd, fpath + strlen(cfg.node_dir), avail);
    					}
    					else
    #endif
    						strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
                        break;
                    case 'G':   /* Temp directory */
    #if defined(__linux__)
    					if(!native)
    						strncat(cmd, DOSEMU_TEMP_DIR, avail);
    					else
    #endif
    	                    strncat(cmd,cfg.temp_dir, avail);
                        break;
                    case 'H':   /* Socket Handle */
                        strncat(cmd,ultoa(client_socket_dup,str,10), avail);
                        break;
                    case 'I':   /* IP address */
                        strncat(cmd,cid, avail);
                        break;
                    case 'J':
    #if defined(__linux__)
    					if(!native)
    						strncat(cmd, DOSEMU_DATA_DIR, avail);
    					else
    #endif
    						strncat(cmd,cfg.data_dir, avail);
                        break;
                    case 'K':
    #if defined(__linux__)
    					if(!native)
    						strncat(cmd, DOSEMU_CTRL_DIR, avail);
    					else
    #endif
    	                    strncat(cmd,cfg.ctrl_dir, avail);
                        break;
                    case 'L':   /* Lines per message */
                        strncat(cmd,ultoa(cfg.level_linespermsg[useron.level],str,10), avail);
                        break;
                    case 'M':   /* Minutes (credits) for user */
                        strncat(cmd,ultoa(useron.min,str,10), avail);
                        break;
                    case 'N':   /* Node Directory (same as SBBSNODE environment var) */
    #if defined(__linux__)
    					if(!native)
    						strncat(cmd, DOSEMU_NODE_DIR, avail);
    					else
    #endif
    	                    strncat(cmd,cfg.node_dir, avail);
                        break;
                    case 'O':   /* SysOp */
                        strncat(cmd,QUOTED_STRING(instr[i],cfg.sys_op,str,sizeof(str)), avail);
                        break;
                    case 'P':   /* Client protocol */
                        strncat(cmd, passthru_thread_running ? "raw" : client.protocol, avail);
                        break;
                    case 'Q':   /* QWK ID */
                        strncat(cmd,cfg.sys_id, avail);
                        break;
                    case 'R':   /* Rows */
                        strncat(cmd,ultoa(rows,str,10), avail);
                        break;
                    case 'S':   /* File Spec (or Baja command str) or startup-directory */
                        strncat(cmd, fspec, avail);
                        break;
                    case 'T':   /* Time left in seconds */
                        gettimeleft();
                        strncat(cmd,ultoa(timeleft,str,10), avail);
                        break;
                    case 'U':   /* UART I/O Address (in hex) */
                        strncat(cmd,ultoa(cfg.com_base,str,16), avail);
                        break;
                    case 'V':   /* Synchronet Version */
                        sprintf(str,"%s%c",VERSION,REVISION);
    					strncat(cmd,str, avail);
                        break;
                    case 'W':   /* Columns (width) */
                        strncat(cmd,ultoa(cols,str,10), avail);
                        break;
                    case 'X':
                        strncat(cmd,cfg.shell[useron.shell]->code, avail);
                        break;
                    case '&':   /* Address of msr */
                        break;
                    case 'Y':
                        strncat(cmd,comspec, avail);
                        break;
                    case 'Z':
    #if defined(__linux__)
    					if(!native)
    						strncat(cmd, DOSEMU_TEXT_DIR, avail);
    					else
    #endif
    	                    strncat(cmd,cfg.text_dir, avail);
                        break;
    				case '~':	/* DOS-compatible (8.3) filename */
    #ifdef _WIN32
    					char sfpath[MAX_PATH+1];
    					SAFECOPY(sfpath,fpath);
    					GetShortPathName(fpath,sfpath,sizeof(sfpath));
    					strncat(cmd,sfpath, avail);
    #else
                        strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
    #endif
    					break;
                    case '!':   /* EXEC Directory */
    #if defined(__linux__)
    					if(!native)
    						strncat(cmd, DOSEMU_EXEC_DIR, avail);
    					else
    #endif
    	                    strncat(cmd,cfg.exec_dir, avail);
                        break;
                    case '@':   /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
    #ifndef __unix__
                        strncat(cmd,cfg.exec_dir, avail);
    #endif
                        break;
    
                    case '#':   /* Node number (same as SBBSNNUM environment var) */
                        sprintf(str,"%d",cfg.node_num);
                        strncat(cmd,str, avail);
                        break;
                    case '*':
                        sprintf(str,"%03d",cfg.node_num);
                        strncat(cmd,str, avail);
                        break;
                    case '$':   /* Credits */
                        strncat(cmd,ultoa(useron.cdt+useron.freecdt,str,10), avail);
                        break;
                    case '%':   /* %% for percent sign */
                        strncat(cmd,"%", avail);
                        break;
    				case '.':	/* .exe for DOS/OS2/Win32, blank for Unix */
    #ifndef __unix__
    					strncat(cmd,".exe", avail);
    #endif
    					break;
    				case '?':	/* Platform */
    #ifdef __OS2__
    					strcpy(str,"OS2");
    #else
    					strcpy(str,PLATFORM_DESC);
    #endif
    					strlwr(str);
    					strncat(cmd,str, avail);
    					break;
    				case '^':	/* Architecture */
    					strncat(cmd, ARCHITECTURE_DESC, avail);
    					break;
    				case '+':	/* Real name */
    					strncat(cmd, quoted_string(useron.name, str, sizeof(str)), avail);
    					break;
                    default:    /* unknown specification */
                        if(IS_DIGIT(instr[i])) {
                            sprintf(str,"%0*d",instr[i]&0xf,useron.number);
                            strncat(cmd,str, avail); }
                        break; }
                j=strlen(cmd); }
            else
                cmd[j++]=instr[i]; }
        cmd[j]=0;
    
        return(cmd);
    }