xtrn.cpp 59.2 KB
Newer Older
1 2 3 4 5 6
/* 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)		*
 *																			*
rswindell's avatar
rswindell committed
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20
 *																			*
 * 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.	*
 ****************************************************************************/
21

22 23 24 25
#include "sbbs.h"
#include "cmdshell.h"
#include "telnet.h"

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

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

deuce's avatar
deuce committed
31 32
	#define TTYDEFCHARS		// needed for ttydefchars definition
	#include <sys/ttydefaults.h>	// Linux - it's motherfucked.
rswindell's avatar
rswindell committed
33
#if defined(__FreeBSD__)
34
	#include <libutil.h>	// forkpty()
deuce's avatar
deuce committed
35
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DARWIN__)
rswindell's avatar
rswindell committed
36 37
	#include <util.h>
#elif defined(__linux__)
38
	#include <pty.h>
39
#elif defined(__QNX__)
40
#if 0
41
	#include <unix.h>
42 43 44
#else
	#define NEEDS_FORKPTY
#endif
45
#endif
46 47 48 49 50

	#ifdef NEEDS_FORKPTY
	#include <grp.h>
	#endif

51
	#include <termios.h>
52

deuce's avatar
deuce committed
53 54 55 56
/*
 * Control Character Defaults
 */
#ifndef CTRL
57
	#define CTRL(x)	(x&037)
deuce's avatar
deuce committed
58 59
#endif
#ifndef CEOF
60
	#define	CEOF		CTRL('d')
deuce's avatar
deuce committed
61 62
#endif
#ifndef CEOL
63
	#define	CEOL		0xff		/* XXX avoid _POSIX_VDISABLE */
deuce's avatar
deuce committed
64 65
#endif
#ifndef CERASE
66
	#define	CERASE		0177
deuce's avatar
deuce committed
67 68
#endif
#ifndef CERASE2
69
	#define	CERASE2		CTRL('h')
deuce's avatar
deuce committed
70 71
#endif
#ifndef CINTR
72
	#define	CINTR		CTRL('c')
deuce's avatar
deuce committed
73 74
#endif
#ifndef CSTATUS
75
	#define	CSTATUS		CTRL('t')
deuce's avatar
deuce committed
76 77
#endif
#ifndef CKILL
78
	#define	CKILL		CTRL('u')
deuce's avatar
deuce committed
79 80
#endif
#ifndef CMIN
81
	#define	CMIN		1
deuce's avatar
deuce committed
82 83
#endif
#ifndef CQUIT
84
	#define	CQUIT		034		/* FS, ^\ */
deuce's avatar
deuce committed
85 86
#endif
#ifndef CSUSP
87
	#define	CSUSP		CTRL('z')
deuce's avatar
deuce committed
88 89
#endif
#ifndef CTIME
90
	#define	CTIME		0
deuce's avatar
deuce committed
91 92
#endif
#ifndef CDSUSP
93
	#define	CDSUSP		CTRL('y')
deuce's avatar
deuce committed
94 95
#endif
#ifndef CSTART
96
	#define	CSTART		CTRL('q')
deuce's avatar
deuce committed
97 98
#endif
#ifndef CSTOP
99
	#define	CSTOP		CTRL('s')
deuce's avatar
deuce committed
100 101
#endif
#ifndef CLNEXT
102
	#define	CLNEXT		CTRL('v')
deuce's avatar
deuce committed
103 104
#endif
#ifndef CDISCARD
105
	#define	CDISCARD 	CTRL('o')
deuce's avatar
deuce committed
106 107
#endif
#ifndef CWERASE
108
	#define	CWERASE 	CTRL('w')
deuce's avatar
deuce committed
109 110
#endif
#ifndef CREPRINT
111
	#define	CREPRINT 	CTRL('r')
deuce's avatar
deuce committed
112 113
#endif
#ifndef CEOT
114
	#define	CEOT		CEOF
deuce's avatar
deuce committed
115 116 117
#endif
/* compat */
#ifndef CBRK
118
	#define	CBRK		CEOL
deuce's avatar
deuce committed
119 120
#endif
#ifndef CRPRNT
121
	#define CRPRNT		CREPRINT
deuce's avatar
deuce committed
122 123
#endif
#ifndef CFLUSH
124 125 126
	#define	CFLUSH		CDISCARD
#endif

deuce's avatar
deuce committed
127
#ifndef TTYDEF_IFLAG
128
	#define TTYDEF_IFLAG    (BRKINT | ICRNL | IMAXBEL | IXON | IXANY)
deuce's avatar
deuce committed
129 130
#endif
#ifndef TTYDEF_OFLAG
131
	#define TTYDEF_OFLAG    (OPOST | ONLCR)
deuce's avatar
deuce committed
132 133
#endif
#ifndef TTYDEF_LFLAG
134
	#define TTYDEF_LFLAG    (ECHO | ICANON | ISIG | IEXTEN | ECHOE|ECHOKE|ECHOCTL)
deuce's avatar
deuce committed
135 136
#endif
#ifndef TTYDEF_CFLAG
137
	#define TTYDEF_CFLAG    (CREAD | CS8 | HUPCL)
deuce's avatar
deuce committed
138
#endif
deuce's avatar
deuce committed
139
#if defined(__QNX__) || defined(__solaris__) || defined(__NetBSD__)
140 141 142
	static cc_t     ttydefchars[NCCS] = {
        CEOF,   CEOL,   CEOL,   CERASE, CWERASE, CKILL, CREPRINT,
        CERASE2, CINTR, CQUIT,  CSUSP,  CDSUSP, CSTART, CSTOP,  CLNEXT,
deuce's avatar
deuce committed
143 144 145 146
        CDISCARD, CMIN, CTIME,  CSTATUS
#ifndef __solaris__
	, _POSIX_VDISABLE
#endif
147
	};
148 149
#endif

150 151
#endif	/* __unix__ */

152
#define XTRN_IO_BUF_LEN 10000	/* 50% of IO_THREAD_BUF_SIZE */
153

154 155 156 157 158 159 160 161 162 163
/*****************************************************************************/
/* 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++) {
164
        if(buf[i]==CTRL_C) {	/* WWIV color escape char */
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
            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);
}
213

214 215 216 217 218 219 220
static void petscii_convert(BYTE* buf, ulong len)
{
    for(ulong i=0; i<len; i++) {
		buf[i] = cp437_to_petscii(buf[i]);
	}
}

221 222 223 224 225 226 227 228 229
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)
230 231 232 233
		return true;

	if(*cmdline == '?' || *cmdline == '*')
		return true;
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248

    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);
}

249
#define XTRN_LOADABLE_MODULE(cmdline,startup_dir)			\
250
	if(cmdline[0]=='*')		/* Baja module or JavaScript */	\
251
		return(exec_bin(cmdline+1,&main_csi,startup_dir));
252
#ifdef JAVASCRIPT
253
	#define XTRN_LOADABLE_JS_MODULE(cmdline,mode,startup_dir)	\
254
	if(cmdline[0]=='?' && (mode&EX_SH))						\
255
		return(js_execxtrn(cmdline+1, startup_dir));		\
256
	if(cmdline[0]=='?')										\
257
		return(js_execfile(cmdline+1,startup_dir));
258 259 260
#else
	#define XTRN_LOADABLE_JS_MODULE
#endif
261 262 263

#ifdef _WIN32

264
#include "vdd_func.h"	/* DOSXTRN.EXE API */
265 266 267

extern SOCKET node_socket[];

268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
/*****************************************************************************/
// 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);
}

284 285 286 287 288 289 290 291
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);
}

292 293 294 295 296 297 298
/* Clean-up resources while preserving current LastError value */
#define XTRN_CLEANUP												\
	last_error=GetLastError();										\
	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);	\
299
	if(hangup_event!=NULL)				CloseHandle(hangup_event);	\
300 301
	SetLastError(last_error)

302 303 304
/****************************************************************************/
/* Runs an external program 												*/
/****************************************************************************/
305
int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
306
{
307 308 309
	char	str[MAX_PATH+1];
	char*	env_block=NULL;
	char*	env_strings;
310
	const char* p_startup_dir;
311
	char	path[MAX_PATH+1];
312 313 314 315
    char	fullcmdline[MAX_PATH+1];
	char	realcmdline[MAX_PATH+1];
	char	comspec_str[MAX_PATH+1];
	char	title[MAX_PATH+1];
316 317
	BYTE	buf[XTRN_IO_BUF_LEN],*bp;
    BYTE 	telnet_buf[XTRN_IO_BUF_LEN*2];
318
    BYTE 	output_buf[XTRN_IO_BUF_LEN*2];
319 320
    BYTE 	wwiv_buf[XTRN_IO_BUF_LEN*2];
    bool	wwiv_flag=false;
321
    bool	native=false;			// DOS program by default
322
    bool	was_online=true;
323
	bool	rio_abortable_save=rio_abortable;
324
	bool	use_pipes=false;	// NT-compatible console redirection
325
	BOOL	success;
326
	BOOL	processTerminated=false;
327
	uint	i;
328 329 330
    time_t	hungup=0;
	HANDLE	rdslot=INVALID_HANDLE_VALUE;
	HANDLE	wrslot=INVALID_HANDLE_VALUE;
331
	HANDLE  start_event=NULL;
332
	HANDLE	hungup_event=NULL;
333
	HANDLE	hangup_event=NULL;
334 335
	HANDLE	rdoutpipe;
	HANDLE	wrinpipe;
336
    PROCESS_INFORMATION process_info;
deuce's avatar
deuce committed
337
	unsigned long	rd;
338
    unsigned long	wr;
deuce's avatar
deuce committed
339
    unsigned long	len;
340
    DWORD	avail;
deuce's avatar
deuce committed
341 342
	unsigned long	msglen;
	unsigned long	retval;
343 344
	DWORD	last_error;
	DWORD	loop_since_io=0;
345
	struct	tm tm;
346
	str_list_t	env_list;
347

348
	xtrn_mode = mode;
349
	lprintf(LOG_DEBUG,"Executing external: %s",cmdline);
350

351 352 353 354 355
	if(startup_dir!=NULL && startup_dir[0] && !isdir(startup_dir)) {
		errormsg(WHERE, ERR_CHK, startup_dir, 0);
		return -1;
	}

356
	XTRN_LOADABLE_MODULE(cmdline,startup_dir);
357
	XTRN_LOADABLE_JS_MODULE(cmdline,mode,startup_dir);
358

359
	attr(cfg.color[clr_external]);		/* setup default attributes */
360

361
	native = native_executable(&cfg, cmdline, mode);
362

363
	if(!native && (startup->options&BBS_OPT_NO_DOS)) {
364
		lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOS programs not supported: %s", cmdline);
365 366 367 368
		bprintf("Sorry, DOS programs are not supported on this node.\r\n");
		return -1;
	}

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

    if(startup_dir && cmdline[1]!=':' && cmdline[0]!='/'
    	&& cmdline[0]!='\\' && cmdline[0]!='.')
376
       	SAFEPRINTF3(fullcmdline, "%s%s%s", comspec_str, startup_dir, cmdline);
377
    else
378
    	SAFEPRINTF2(fullcmdline, "%s%s", comspec_str, cmdline);
379

380
	SAFECOPY(realcmdline, fullcmdline);	// for errormsg if failed to execute
381 382

	now=time(NULL);
383 384
	if(localtime_r(&now,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
385

386
	if(native && mode&EX_STDOUT && !(mode&EX_OFFLINE))
387 388
		use_pipes=true;

389
 	if(native) { // Native (not MS-DOS) external
390

391 392 393 394 395 396
		if((env_list=strListInit())==NULL) {
			XTRN_CLEANUP;
        	errormsg(WHERE, ERR_CREATE, "env_list", 0);
            return(errno);
		}

397
		// Current environment passed to child process
rswindell's avatar
rswindell committed
398
		sprintf(str,"%sprotocol.log",cfg.node_dir);
399 400 401 402 403 404 405
		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);
406
		/* date/time env vars */
rswindell's avatar
rswindell committed
407
		sprintf(str,"%02u",tm.tm_mday);
408 409 410 411 412 413 414
		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);
415 416 417 418 419 420 421 422 423 424 425 426

		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);
		}
427 428 429

    } else { // DOS external

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
		// 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));

447 448
		SAFEPRINTF(path,"%sDOSXTRN.RET", cfg.node_dir);
		(void)remove(path);
449 450

    	// Create temporary environment file
451
    	SAFEPRINTF(path,"%sDOSXTRN.ENV", node_dir);
452
        FILE* fp=fopen(path,"w");
453
        if(fp==NULL) {
454
			XTRN_CLEANUP;
455
        	errormsg(WHERE, ERR_CREATE, path, 0);
456
            return(errno);
457 458
        }
        fprintf(fp, "%s\n", fullcmdline);
459 460 461 462 463
		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);
464
        fprintf(fp, "SBBSNNUM=%d\n", cfg.node_num);
465
		fprintf(fp, "PCBNODE=%d\n", cfg.node_num);
466 467
		fprintf(fp, "PCBDRIVE=%.2s\n", node_dir);
		fprintf(fp, "PCBDIR=%s\n", node_dir + 2);
468 469 470 471 472 473
		/* 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);
474 475
        fclose(fp);

476
        SAFEPRINTF2(fullcmdline, "%sDOSXTRN.EXE %s", cfg.exec_dir, path);
477

478
		if(!(mode&EX_OFFLINE)) {
479
			i = SBBSEXEC_MODE_UNSPECIFIED;
480
			if(mode & EX_UART)
481 482 483 484 485 486 487
				i |= SBBSEXEC_MODE_UART;
			if(mode & EX_FOSSIL)
				i |= SBBSEXEC_MODE_FOSSIL;
			if(mode & EX_STDIN)
           		i |= SBBSEXEC_MODE_DOS_IN;
			if(mode & EX_STDOUT)
        		i |= SBBSEXEC_MODE_DOS_OUT;
Rob Swindell's avatar
Rob Swindell committed
488 489 490 491
			BOOL x64 = FALSE;
			IsWow64Process(GetCurrentProcess(), &x64);
			sprintf(str," %s %u %u"
				,x64 ? "x64" : "NT", cfg.node_num,i);
492 493 494 495 496 497 498 499 500
			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) {
501
				XTRN_CLEANUP;
502
				errormsg(WHERE, ERR_CREATE, str, 0);
503 504 505
				return(GetLastError());
			}

506 507 508 509 510 511 512 513 514 515 516 517
			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());
			}

518 519 520
			sprintf(str,"\\\\.\\mailslot\\sbbsexec\\rd%d"
				,cfg.node_num);
			rdslot=CreateMailslot(str
521
				,sizeof(buf)/2			// Maximum message size (0=unlimited)
522 523 524
				,0						// Read time-out
				,NULL);                 // Security
			if(rdslot==INVALID_HANDLE_VALUE) {
525
				XTRN_CLEANUP;
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
				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 {
541
		SAFEPRINTF3(title,"%s running %s on node %d"
542 543 544 545 546
			,useron.number ? useron.alias : "Event"
			,realcmdline
			,cfg.node_num);
		startup_info.lpTitle=title;
	}
547
    if(startup->options&BBS_OPT_XTRN_MINIMIZED) {
548 549 550
    	startup_info.wShowWindow=SW_SHOWMINNOACTIVE;
        startup_info.dwFlags|=STARTF_USESHOWWINDOW;
    }
551 552 553 554 555 556 557 558
	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;

559 560
		// Create the child output pipe (override default 4K buffer size)
		if(!CreatePipe(&rdoutpipe,&startup_info.hStdOutput,&sa,sizeof(buf))) {
561
			errormsg(WHERE,ERR_CREATE,"stdout pipe",0);
562
			strListFreeBlock(env_block);
563 564 565 566 567
			return(GetLastError());
		}
		startup_info.hStdError=startup_info.hStdOutput;

		// Create the child input pipe.
568
		if(!CreatePipe(&startup_info.hStdInput,&wrinpipe,&sa,sizeof(buf))) {
569
			errormsg(WHERE,ERR_CREATE,"stdin pipe",0);
570 571
			CloseHandle(rdoutpipe);
			strListFreeBlock(env_block);
572 573
			return(GetLastError());
		}
574

575 576 577 578 579 580 581 582 583 584 585 586 587 588
		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;
	}
589 590 591 592 593
	if(native && !(mode & (EX_OFFLINE | EX_STDIN))) {
		if(passthru_thread_running)
			passthru_socket_activate(true);
		else
			pthread_mutex_lock(&input_thread_mutex);
594 595
	}

596
    success=CreateProcess(
597 598 599 600
		NULL,			// pointer to name of executable module
		fullcmdline,  	// pointer to command line string
		NULL,  			// process security attributes
		NULL,   		// thread security attributes
601
		native && !(mode&EX_OFFLINE),	 			// handle inheritance flag
602
		CREATE_NEW_CONSOLE/*|CREATE_SEPARATE_WOW_VDM*/, // creation flags
603
        env_block, 		// pointer to new environment block
604 605 606
		p_startup_dir,	// pointer to current directory name
		&startup_info,  // pointer to STARTUPINFO
		&process_info  	// pointer to PROCESS_INFORMATION
607 608 609 610 611
		);

	strListFreeBlock(env_block);

	if(!success) {
612
		XTRN_CLEANUP;
613
		if(native && !(mode & (EX_OFFLINE | EX_STDIN))) {
614
			if(passthru_thread_running)
615
				passthru_socket_activate(false);
616 617 618
			else
				pthread_mutex_unlock(&input_thread_mutex);
		}
619
		SetLastError(last_error);	/* Restore LastError */
620
        errormsg(WHERE, ERR_EXEC, realcmdline, mode);
621
		SetLastError(last_error);	/* Restore LastError */
622 623 624
        return(GetLastError());
    }

625 626 627 628
#if 0
	char dbgstr[256];
	sprintf(dbgstr,"Node %d created: hProcess %X hThread %X processID %X threadID %X\n"
		,cfg.node_num
rswindell's avatar
rswindell committed
629 630 631 632
		,process_info.hProcess
		,process_info.hThread
		,process_info.dwProcessId
		,process_info.dwThreadId);
633 634 635
	OutputDebugString(dbgstr);
#endif

636 637
	CloseHandle(process_info.hThread);

638
	/* Disable Ctrl-C checking */
639
	if(!(mode&EX_OFFLINE))
640
		rio_abortable=false;
641

642
    // Executing app in foreground?, monitor
643
    retval=STILL_ACTIVE;
644
    while(!(mode&EX_BG)) {
645 646
		if(mode&EX_CHKTIME)
			gettimeleft();
647
        if(!online && !(mode&EX_OFFLINE)) { // Tell VDD and external that user hung-up
648
        	if(was_online) {
649
				logline(LOG_NOTICE,"X!","hung-up in external program");
650 651
            	hungup=time(NULL);
				if(!native) {
652
					SetEvent(hungup_event);
653 654 655
				}
	            was_online=false;
            }
656
            if(hungup && time(NULL)-hungup>5 && !processTerminated) {
657
				lprintf(LOG_INFO,"Terminating process from line %d", __LINE__);
658 659
				processTerminated=TerminateProcess(process_info.hProcess, 2112);
			}
660
        }
rswindell's avatar
rswindell committed
661
		if((native && !use_pipes) || mode&EX_OFFLINE) {
662
			/* Monitor for process termination only */
663 664
			if(WaitForSingleObject(process_info.hProcess,1000)==WAIT_OBJECT_0)
				break;
665 666
		} else {

667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
			/* 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);
683
					else
684 685
						lprintf(LOG_DEBUG,"CreateFile(%s)=0x%x", str, wrslot);
				}
686

687 688 689 690 691 692 693
				/* CR expansion */
				if(use_pipes)
					bp=cr_expand(buf,wr,output_buf,wr);
				else
					bp=buf;

				len=0;
694 695 696 697
				if(wrslot==INVALID_HANDLE_VALUE) {
					if(WaitForSingleObject(process_info.hProcess, 0) != WAIT_OBJECT_0)  // Process still running?
						lprintf(LOG_WARNING,"VDD Open failed (not loaded yet?)");
				} else if(!WriteFile(wrslot,bp,wr,&len,NULL)) {
698 699 700 701 702 703 704
					if(WaitForSingleObject(process_info.hProcess, 0) != WAIT_OBJECT_0) { // Process still running?
						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());
					}
705 706 707 708 709 710 711
				} 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);
712
					}
713
				}
714 715
				wr=len;
			}
716

717
			/* Read from VDD */
718

719 720 721
			rd=0;
			len=sizeof(buf);
			avail=RingBufFree(&outbuf)/2;	// leave room for wwiv/telnet expansion
722
#if 0
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
			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;
			}
756

757 758 759 760 761 762 763 764 765
			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);
766
				}
767 768 769
				if(rd>RingBufFree(&outbuf)) {
					lprintf(LOG_ERR,"output buffer overflow");
					rd=RingBufFree(&outbuf);
770
				}
771
				if(mode&EX_BIN)
772
					RingBufWrite(&outbuf, bp, rd);
773 774
				else
					rputs((char*)bp, rd);
775
			}
776
#if defined(_DEBUG) && 0
777 778 779 780 781 782
			if(rd>1) {
				sprintf(str,"Node %d read %5d bytes from xtrn", cfg.node_num, rd);
				OutputDebugString(str);
			}
#endif
            if((!rd && !wr) || hungup) {
783 784 785 786 787

				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 */
788 789
				if(loop_since_io>=3) {

790
					if(online && hangup_event!=NULL
791 792 793 794 795 796 797 798 799
						&& 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 */
				}
800 801

				/* only check node for interrupt flag every 3 seconds of no I/O */
rswindell's avatar
rswindell committed
802
				if((loop_since_io%30)==0) {
803 804 805 806 807 808 809
					// 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 */
810
				if((loop_since_io%300)==0) {
811
#if defined(_DEBUG)
812 813 814 815 816 817 818
					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);
819
				}
820
				sem_trywait_block(&inbuf.sem,100);
821 822 823 824 825
            } else
				loop_since_io=0;
        }
	}

826
    if(!(mode&EX_BG)) {			/* !background execution */
827

828 829 830
        if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE)
            errormsg(WHERE, ERR_CHK, "ExitCodeProcess",(DWORD)process_info.hProcess);

831
		if(retval==STILL_ACTIVE) {
832
			lprintf(LOG_INFO,"Node %d Terminating process from line %d",cfg.node_num,__LINE__);
833
			TerminateProcess(process_info.hProcess, GetLastError());
rswindell's avatar
rswindell committed
834
		}
835

836
	 	// Get return value
837 838 839 840
		if(!native) {
    		sprintf(str,"%sDOSXTRN.RET", cfg.node_dir);
			FILE* fp=fopen(str,"r");
			if(fp!=NULL) {
841 842 843 844
				if(fscanf(fp,"%d",&retval) != 1) {
					lprintf(LOG_ERR, "Node %d Error reading return value from %s", cfg.node_num, str);
					retval = -1;
				}
845 846
				fclose(fp);
			}
847 848 849
		}
	}

850
	XTRN_CLEANUP;
851
	CloseHandle(process_info.hProcess);
852 853 854

	if(!(mode&EX_OFFLINE)) {	/* !off-line execution */

855 856 857 858 859 860 861 862
		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);
863
		}
864

865 866
		curatr=~0;			// Can't guarantee current attributes
		attr(LIGHTGRAY);	// Force to "normal"
867

868
		rio_abortable=rio_abortable_save;	// Restore abortable state
869

870
		/* Got back to Text/NVT mode */
871
		request_telnet_opt(TELNET_DONT,TELNET_BINARY_TX);
872
	}
873

874 875
//	lprintf("%s returned %d",realcmdline, retval);

876
	errorlevel = retval; // Baja or JS retrievable error value
877

878 879 880
	return(retval);
}

881 882
#else	/* !WIN32 */

883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898
/*****************************************************************************/
// 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);
}

899 900
#define MAX_ARGS 1000

deuce's avatar
deuce committed
901 902 903 904 905 906 907 908 909 910 911
#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);
		}
deuce's avatar
deuce committed
912 913
		/* Note, on some platforms, this can be free()d... */
		sprintf(envstr,"%s=%s",name,value);
deuce's avatar
deuce committed
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
		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

932 933 934 935 936 937 938 939 940 941 942 943 944 945
#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);
}

946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980
#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

981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031