xtrn.cpp 59.3 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
		bprintf("Sorry, DOS programs are not supported on this node.\r\n");
		return -1;
	}
368 369
	if(mode & EX_OFFLINE)
		native = true;	// We don't need to invoke our virtual UART/FOSSIL driver
370

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

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

382
	SAFECOPY(realcmdline, fullcmdline);	// for errormsg if failed to execute
383 384

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

388
	if(native && mode&EX_STDOUT && !(mode&EX_OFFLINE))
389 390
		use_pipes=true;

391
 	if(native) { // Native (not MS-DOS) external
392

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

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

		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);
		}
429 430 431

    } else { // DOS external

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

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

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

478
        SAFEPRINTF2(fullcmdline, "%sDOSXTRN.EXE %s", cfg.exec_dir, path);
479

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

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

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

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

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

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

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

	strListFreeBlock(env_block);

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

627 628 629 630
#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
631 632 633 634
		,process_info.hProcess
		,process_info.hThread
		,process_info.dwProcessId
		,process_info.dwThreadId);
635 636 637
	OutputDebugString(dbgstr);
#endif

638 639
	CloseHandle(process_info.hThread);

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

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

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

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

				len=0;
696 697 698 699
				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)) {
700 701 702 703 704 705 706
					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());
					}
707 708 709 710 711 712 713
				} 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);
714
					}
715
				}
716 717
				wr=len;
			}
718

719
			/* Read from VDD */
720

721 722 723
			rd=0;
			len=sizeof(buf);
			avail=RingBufFree(&outbuf)/2;	// leave room for wwiv/telnet expansion
724
#if 0
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 756 757
			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;
			}
758

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

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

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

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

828
    if(!(mode&EX_BG)) {			/* !background execution */
829

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

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

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

852
	XTRN_CLEANUP;
853
	CloseHandle(process_info.hProcess);
854 855 856

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

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

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

870
		rio_abortable=rio_abortable_save;	// Restore abortable state
871

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

876 877
//	lprintf("%s returned %d",realcmdline, retval);

878
	errorlevel = retval; // Baja or JS retrievable error value
879

880 881 882
	return(retval);
}

883 884
#else	/* !WIN32 */

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

901 902
#define MAX_ARGS 1000

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

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

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 981 982
#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

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