xtrn.cpp 45.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* xtrn.cpp */

/* Synchronet external program support routines */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html		*
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
#include "cmdshell.h"
#include "telnet.h"

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

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

rswindell's avatar
rswindell committed
47
#if defined(__FreeBSD__)
48
	#include <libutil.h>	// forkpty()
rswindell's avatar
rswindell committed
49
50
51
#elif defined(__OpenBSD__)
	#include <util.h>
#elif defined(__linux__)
52
53
	#include <pty.h>
#endif
54
55
56
57
58

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

59
60
	#include <termios.h>
#endif
61
62
#define XTRN_IO_BUF_LEN 5000

63
64
65
66
67
68
#ifdef __solaris__
#    define TTYDEF_IFLAG    (BRKINT | ICRNL | IMAXBEL | IXON | IXANY)
#    define TTYDEF_OFLAG    (OPOST | ONLCR)
#    define TTYDEF_LFLAG    (ECHO | ICANON | ISIG | IEXTEN | ECHOE|ECHOKE|ECHOCTL)
#endif

69
70
71
72
73
74
75
76
77
78
/*****************************************************************************/
/* 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++) {
79
        if(buf[i]==CTRL_C) {	/* WWIV color escape char */
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
            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);
}
128
129

/*****************************************************************************/
130
// Escapes Telnet IAC (255) by doubling the IAC char
131
/*****************************************************************************/
132
BYTE* telnet_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
133
134
135
136
137
138
{
	BYTE*   first_iac;
	ulong	i,outlen;

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

139
	if(first_iac==NULL) {	/* Nothing to expand */
140
141
142
143
		newlen=inlen;
		return(inbuf);
	}

144
145
	outlen=first_iac-inbuf;
	memcpy(outbuf, inbuf, outlen);
146
147
148

    for(i=outlen;i<inlen;i++) {
		if(inbuf[i]==TELNET_IAC)
149
			outbuf[outlen++]=TELNET_IAC;
150
151
152
153
154
		outbuf[outlen++]=inbuf[i];
	}
    newlen=outlen;
    return(outbuf);
}
155

156
157
#define XTRN_LOADABLE_MODULE								\
	if(cmdline[0]=='*') {   /* Baja module or JavaScript */	\
158
		SAFECOPY(str,cmdline+1);							\
159
160
		p=strchr(str,SP);									\
		if(p) {												\
161
			strcpy(main_csi.str,p+1);						\
162
163
164
165
166
167
168
169
170
171
172
173
174
175
			*p=0; 											\
		} else												\
			main_csi.str[0]=0;								\
		if(!strchr(str,'.'))								\
			strcat(str,".bin");								\
		return(exec_bin(str,&main_csi));					\
	}														
#ifdef JAVASCRIPT
	#define XTRN_LOADABLE_JS_MODULE							\
	if(cmdline[0]=='?') 	/* JavaScript */				\
		return(js_execfile(cmdline+1))						
#else
	#define XTRN_LOADABLE_JS_MODULE
#endif
176
177
178
179
180
181
182

#ifdef _WIN32

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

extern SOCKET node_socket[];

183
184
185
186
187
188
189
190
// -------------------------------------------------------------------------
// GetAddressOfOpenVxDHandle
//
// This function returns the address of OpenVxDHandle. OpenVxDHandle is a 
// KERNEL32 function that returns a ring 0 event handle that corresponds to a
// given ring 3 event handle. The ring 0 handle can be used by VxDs to
// synchronize with the Win32 app.
//
191
192
193
typedef HANDLE (WINAPI *OPENVXDHANDLE)(HANDLE);

OPENVXDHANDLE GetAddressOfOpenVxDHandle(void)
194
{
195
	return((OPENVXDHANDLE)GetProcAddress(hK32, "OpenVxDHandle"));
196
197
}

198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*****************************************************************************/
// 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);
}

214
215
216
217
218
219
220
221
222
223
224
/* 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);	\
	ReleaseMutex(exec_mutex);										\
	SetLastError(last_error)

225
226
227
228
229
230
231
232
233
234
235
236
237
/****************************************************************************/
/* Runs an external program 												*/
/****************************************************************************/
int sbbs_t::external(char* cmdline, long mode, char* startup_dir)
{
	char	str[256],*p,*p_startup_dir;
	char	fname[128];
    char	fullcmdline[256];
	char	realcmdline[256];
	char	comspec_str[128];
	char	title[256];
	BYTE	buf[XTRN_IO_BUF_LEN],*bp;
    BYTE 	telnet_buf[XTRN_IO_BUF_LEN*2];
238
    BYTE 	output_buf[XTRN_IO_BUF_LEN*2];
239
240
    BYTE 	wwiv_buf[XTRN_IO_BUF_LEN*2];
    bool	wwiv_flag=false;
241
    bool	native=false;			// DOS program by default
242
	bool	nt=false;				// WinNT/2K? 
243
    bool	was_online=true;
244
	bool	rio_abortable_save;
245
	bool	use_pipes=false;	// NT-compatible console redirection
246
	uint	i;
247
248
249
250
    time_t	hungup=0;
	HANDLE	vxd=INVALID_HANDLE_VALUE;
	HANDLE	rdslot=INVALID_HANDLE_VALUE;
	HANDLE	wrslot=INVALID_HANDLE_VALUE;
251
	HANDLE  start_event=NULL;
252
	HANDLE	hungup_event=NULL;
253
254
	HANDLE	rdoutpipe;
	HANDLE	wrinpipe;
255
256
257
258
259
260
    PROCESS_INFORMATION process_info;
	DWORD	hVM;
	DWORD	rd;
    DWORD	wr;
    DWORD	len;
    DWORD	avail;
261
262
	DWORD	dummy;
	DWORD	msglen;
263
264
265
	DWORD	retval;
	DWORD	last_error;
	DWORD	loop_since_io=0;
266
	struct	tm tm;
267
	sbbsexec_start_t start;
268
	OPENVXDHANDLE OpenVxDHandle;
269

270
271
272
	if(online==ON_LOCAL)
		eprintf("Executing external: %s",cmdline);

273
274
	XTRN_LOADABLE_MODULE;
	XTRN_LOADABLE_JS_MODULE;
275
276
277

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

278
    SAFECOPY(str,cmdline);		/* Set str to program name only */
279
280
    p=strchr(str,SP);
    if(p) *p=0;
281
    SAFECOPY(fname,str);
282

283
284
    p=strrchr(fname,'/');
    if(!p) p=strrchr(fname,'\\');
285
286
287
288
289
290
291
292
293
294
    if(!p) p=strchr(fname,':');
    if(!p) p=fname;
    else   p++;

    for(i=0;i<cfg.total_natvpgms;i++)
        if(!stricmp(p,cfg.natvpgm[i]->name))
            break;
    if(i<cfg.total_natvpgms || mode&EX_NATIVE)
        native=true;

rswindell's avatar
rswindell committed
295
	if(mode&EX_SH || strcspn(cmdline,"<>|")!=strlen(cmdline)) 
296
297
298
299
300
301
302
303
304
305
		sprintf(comspec_str,"%s /C ", comspec);
	else
		comspec_str[0]=0;

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

306
	SAFECOPY(realcmdline, fullcmdline);	// for errormsg if failed to execute
307
308

	now=time(NULL);
309
310
	if(localtime_r(&now,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
311

312
313
314
315
316
	OpenVxDHandle=GetAddressOfOpenVxDHandle();

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

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

323
324
325
	if(native && mode&EX_OUTR && !(mode&EX_OFFLINE))
		use_pipes=true;

326
 	if(native) { // Native (32-bit) external
327
328
329
330

		// Current environment passed to child process
		sprintf(dszlog,"DSZLOG=%sPROTOCOL.LOG",cfg.node_dir);
		sprintf(sbbsnode,"SBBSNODE=%s",cfg.node_dir);
331
		sprintf(sbbsctrl,"SBBSCTRL=%s",cfg.ctrl_dir);
332
333
334
		sprintf(sbbsdata,"SBBSDATA=%s",cfg.data_dir);
		sprintf(sbbsexec,"SBBSEXEC=%s",cfg.exec_dir);
		sprintf(sbbsnnum,"SBBSNNUM=%d",cfg.node_num);
335
336
		putenv(dszlog); 		/* Makes the DSZ LOG active */
		putenv(sbbsnode);
337
		putenv(sbbsctrl);
338
339
340
		putenv(sbbsdata);
		putenv(sbbsexec);
		putenv(sbbsnnum);
341
342
343
344
345
346
347
348
349
350
351
352
		/* date/time env vars */
		sprintf(env_day			,"DAY=%02u"			,tm.tm_mday);
		sprintf(env_weekday		,"WEEKDAY=%s"		,wday[tm.tm_wday]);
		sprintf(env_monthname	,"MONTHNAME=%s"		,mon[tm.tm_mon]);
		sprintf(env_month		,"MONTH=%02u"		,tm.tm_mon+1);
		sprintf(env_year		,"YEAR=%u"			,1900+tm.tm_year);
		putenv(env_day);
		putenv(env_weekday);
		putenv(env_monthname);
		putenv(env_month);
		if(putenv(env_year))
        	errormsg(WHERE,ERR_WRITE,"environment",0);
353
354
355
356
357
358
359
360
361
362

    } else { // DOS external

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

    	// Create temporary environment file
    	sprintf(str,"%sDOSXTRN.ENV", cfg.node_dir);
        FILE* fp=fopen(str,"w");
        if(fp==NULL) {
363
			XTRN_CLEANUP;
364
        	errormsg(WHERE, ERR_CREATE, str, 0);
365
            return(errno);
366
367
368
369
        }
        fprintf(fp, "%s\n", fullcmdline);
		fprintf(fp, "DSZLOG=%sPROTOCOL.LOG\n", cfg.node_dir);
        fprintf(fp, "SBBSNODE=%s\n", cfg.node_dir);
370
371
372
        fprintf(fp, "SBBSCTRL=%s\n", cfg.ctrl_dir);
		fprintf(fp, "SBBSDATA=%s\n", cfg.data_dir);
		fprintf(fp, "SBBSEXEC=%s\n", cfg.exec_dir);
373
        fprintf(fp, "SBBSNNUM=%d\n", cfg.node_num);
374
375
376
377
378
379
		/* 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);
380
381
382
383
        fclose(fp);

        sprintf(fullcmdline, "%sDOSXTRN.EXE %s", cfg.exec_dir, str);

384
		if(!(mode&EX_OFFLINE) && nt) {	// Windows NT/2000
385
386
387
388
389
			i=SBBSEXEC_MODE_FOSSIL;
			if(mode&EX_INR)
           		i|=SBBSEXEC_MODE_DOS_IN;
			if(mode&EX_OUTR)
        		i|=SBBSEXEC_MODE_DOS_OUT;
390
391
			sprintf(str," NT %u %u %u"
				,cfg.node_num,i,startup->xtrn_polls_before_yield);
392
393
394
395
396
397
398
399
400
			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) {
401
				XTRN_CLEANUP;
402
403
404
405
406
407
408
409
410
411
412
				errormsg(WHERE, ERR_CREATE, "exec start event", 0);
				return(GetLastError());
			}

			sprintf(str,"\\\\.\\mailslot\\sbbsexec\\rd%d"
				,cfg.node_num);
			rdslot=CreateMailslot(str
				,sizeof(buf)			// Maximum message size (0=unlimited)
				,0						// Read time-out
				,NULL);                 // Security
			if(rdslot==INVALID_HANDLE_VALUE) {
413
				XTRN_CLEANUP;
414
415
416
417
418
419
420
421
422
423
424
425
				errormsg(WHERE, ERR_CREATE, str, 0);
				return(GetLastError());
			}
		}
		else if(!(mode&EX_OFFLINE)) {

   			// Load vxd to intercept interrupts

			sprintf(str,"\\\\.\\%s%s",cfg.exec_dir, SBBSEXEC_VXD);
			if((vxd=CreateFile(str,0,0,0
				,CREATE_NEW, FILE_FLAG_DELETE_ON_CLOSE,0))
				 ==INVALID_HANDLE_VALUE) {
426
				XTRN_CLEANUP;
427
428
429
430
431
432
433
434
435
436
				errormsg(WHERE, ERR_OPEN, str, 0);
				return(GetLastError());
			}

			if((start_event=CreateEvent(
				 NULL	// pointer to security attributes
				,TRUE	// flag for manual-reset event
				,FALSE  // flag for initial state
				,NULL	// pointer to event-object name
				))==NULL) {
437
				XTRN_CLEANUP;
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
				errormsg(WHERE, ERR_CREATE, "exec start event", 0);
				return(GetLastError());
			}

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

			start.mode=SBBSEXEC_MODE_FOSSIL;
			if(mode&EX_INR)
           		start.mode|=SBBSEXEC_MODE_DOS_IN;
			if(mode&EX_OUTR)
        		start.mode|=SBBSEXEC_MODE_DOS_OUT;

453
454
			sprintf(str," 95 %u %u %u"
				,cfg.node_num,start.mode,startup->xtrn_polls_before_yield);
455
456
			strcat(fullcmdline,str);

457
458
459
460
461
462
463
464
465
466
			if(!DeviceIoControl(
				vxd,					// handle to device of interest
				SBBSEXEC_IOCTL_START,	// control code of operation to perform
				&start,					// pointer to buffer to supply input data
				sizeof(start),			// size of input buffer
				NULL,					// pointer to buffer to receive output data
				0,						// size of output buffer
				&rd,					// pointer to variable to receive output byte count
				NULL 					// Overlapped I/O
				)) {
467
				XTRN_CLEANUP;
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
				errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_START);
				return(GetLastError());
			}
		}
    }

	if(startup_dir!=NULL && startup_dir[0])
		p_startup_dir=startup_dir;
	else
		p_startup_dir=NULL;
    STARTUPINFO startup_info={0};
    startup_info.cb=sizeof(startup_info);
	if(mode&EX_OFFLINE)
		startup_info.lpTitle=NULL;
	else {
		sprintf(title,"%s running %s on node %d"
			,useron.number ? useron.alias : "Event"
			,realcmdline
			,cfg.node_num);
		startup_info.lpTitle=title;
	}
489
    if(startup->options&BBS_OPT_XTRN_MINIMIZED) {
490
491
492
    	startup_info.wShowWindow=SW_SHOWMINNOACTIVE;
        startup_info.dwFlags|=STARTF_USESHOWWINDOW;
    }
493
494
495
496
497
498
499
500
501
	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.
502
		if(!CreatePipe(&rdoutpipe,&startup_info.hStdOutput,&sa,0)) {
503
504
505
506
507
508
			errormsg(WHERE,ERR_CREATE,"stdout pipe",0);
			return(GetLastError());
		}
		startup_info.hStdError=startup_info.hStdOutput;

		// Create the child input pipe.
509
		if(!CreatePipe(&startup_info.hStdInput,&wrinpipe,&sa,0)) {
510
511
512
			errormsg(WHERE,ERR_CREATE,"stdin pipe",0);
			return(GetLastError());
		}
513

514
515
516
517
518
519
520
521
522
523
524
525
526
527
		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;
	}
528
	if(native && !(mode&EX_OFFLINE)) {
529
		/* temporary */
530
531
532
		FILE* fp;
		sprintf(fname,"%sDOOR32.SYS",cfg.node_dir);
		fp=fopen(fname,"wb");
533
		fprintf(fp,"%d\r\n%d\r\n38400\r\n%s%c\r\n%d\r\n%s\r\n%s\r\n%d\r\n%d\r\n"
534
			"%d\r\n%d\r\n"
535
536
			,mode&EX_OUTR ? 0 /* Local */ : 2 /* Telnet */
			,mode&EX_OUTR ? INVALID_SOCKET : client_socket_dup
537
			,VERSION_NOTICE,REVISION
538
539
540
541
542
543
544
545
			,useron.number
			,useron.name
			,useron.alias
			,useron.level
			,timeleft/60
			,useron.misc&ANSI ? 1 : 0
			,cfg.node_num);
		fclose(fp);
546
547

		/* not temporary */
548
549
		if(!(mode&EX_INR)) 
			pthread_mutex_lock(&input_thread_mutex);
550
551
552
553
554
555
556
	}

    if(!CreateProcess(
		NULL,			// pointer to name of executable module
		fullcmdline,  	// pointer to command line string
		NULL,  			// process security attributes
		NULL,   		// thread security attributes
557
		native && !(mode&EX_OFFLINE),	 			// handle inheritance flag
558
		CREATE_NEW_CONSOLE/*|CREATE_SEPARATE_WOW_VDM*/, // creation flags
559
560
561
562
563
        NULL,  			// pointer to new environment block
		p_startup_dir,	// pointer to current directory name
		&startup_info,  // pointer to STARTUPINFO
		&process_info  	// pointer to PROCESS_INFORMATION
		)) {
564
		XTRN_CLEANUP;
565
		if(native && !(mode&EX_OFFLINE))
566
567
			pthread_mutex_unlock(&input_thread_mutex);
		SetLastError(last_error);	/* Restore LastError */
568
569
570
571
        errormsg(WHERE, ERR_EXEC, realcmdline, mode);
        return(GetLastError());
    }

572
573
574
575
576
577
578
579
580
581
582
#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

583
584
	CloseHandle(process_info.hThread);

585
586
587
588
589
	if(!native) {

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

			CloseHandle(start_event);
598
			start_event=NULL;	/* Mark as closed */
599
600
601
602
603
604
605
606
607
608
609

			if(!DeviceIoControl(
				vxd,					// handle to device of interest
				SBBSEXEC_IOCTL_COMPLETE,	// control code of operation to perform
				NULL,					// pointer to buffer to supply input data
				0,						// size of input buffer
				&hVM,					// pointer to buffer to receive output data
				sizeof(hVM),			// size of output buffer
				&rd,					// pointer to variable to receive output byte count
				NULL					// Overlapped I/O
				)) {
610
				XTRN_CLEANUP;
611
                TerminateProcess(process_info.hProcess, __LINE__);
612
				CloseHandle(process_info.hProcess);
613
614
615
616
617
				errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_COMPLETE);
				return(GetLastError());
			}
		}
	}
618
    ReleaseMutex(exec_mutex);
619

620
	/* Disable Ctrl-C checking */
621
622
623
624
	if(!(mode&EX_OFFLINE)) {
		rio_abortable_save=rio_abortable;
		rio_abortable=false;
	}
625

626
    // Executing app in foreground?, monitor
627
    retval=STILL_ACTIVE;
628
    while(!(mode&EX_BG)) {
629
630
		if(mode&EX_CHKTIME)
			gettimeleft();
631
632
        if(!online && !(mode&EX_OFFLINE)) { // Tell VXD/VDD and external that user hung-up
        	if(was_online) {
633
634
				sprintf(str,"%s hung-up in external program",useron.alias);
				logline("X!",str);
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
            	hungup=time(NULL);
				if(!native) {
					if(nt)
						SetEvent(hungup_event);
					else if(!DeviceIoControl(
						vxd,		// handle to device of interest
						SBBSEXEC_IOCTL_DISCONNECT,	// operation to perform
						&hVM,		// pointer to buffer to supply input data
						sizeof(hVM),// size of input buffer
						NULL,		// pointer to buffer to receive output data
						0,			// size of output buffer
						&rd,		// pointer to variable to receive output byte count
						NULL		// Overlapped I/O
						)) {
						errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_DISCONNECT);
						break;
					}
				}
	            was_online=false;
            }
            if(hungup && time(NULL)-hungup>5) 
                TerminateProcess(process_info.hProcess, 2112);
        }
658
659
		if((native && !use_pipes) || mode&EX_OFFLINE) {	
			/* Monitor for process termination only */
660
661
662
663
664
665
666
667
668
669
            if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE) {
                errormsg(WHERE, ERR_CHK, "ExitCodeProcess"
                   ,(DWORD)process_info.hProcess);
                break;
            }
            if(retval!=STILL_ACTIVE)
                break;
        	Sleep(500);
		} else {

670
			if(nt || use_pipes) {	// Windows NT/2000
671
672
673
674
675

				/* Write to VDD */

				wr=RingBufPeek(&inbuf,buf,sizeof(buf));
				if(wr) {
676
					if(!use_pipes && wrslot==INVALID_HANDLE_VALUE) {
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
						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 0
						if(wrslot==INVALID_HANDLE_VALUE) {
							errormsg(WHERE,ERR_OPEN,str,0);
							break;
						}
#endif
					}
693
					
694
695
696
697
698
					/* CR expansion */
					if(use_pipes) 
						bp=cr_expand(buf,wr,output_buf,wr);
					else
						bp=buf;
699

700
					if(wrslot!=INVALID_HANDLE_VALUE
701
						&& WriteFile(wrslot,bp,wr,&len,NULL)==TRUE) {
702
703
						RingBufRead(&inbuf, NULL, len);
						wr=len;
704
705
						if(use_pipes) {
							/* echo */
706
							RingBufWrite(&outbuf, bp, wr);
707
708
							sem_post(&output_sem);
						}
709
710
711
712
713
714
					} else		// VDD not loaded yet
						wr=0;
				}

				/* Read from VDD */

715
				rd=0;
716
				len=sizeof(buf);
717
				avail=RingBufFree(&outbuf)/2;	// leave room for wwiv/telnet expansion
718
#if 0
719
				if(avail==0)
720
					lprintf("Node %d !output buffer full (%u bytes)"
721
						,cfg.node_num,RingBufFull(&outbuf));
722
#endif
723
724
				if(len>avail)
            		len=avail;
725

726
				while(rd<len) {
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
					DWORD 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) {
754
755
756
757
758
					if(mode&EX_WWIV) {
                		bp=wwiv_expand(buf, rd, wwiv_buf, rd, useron.misc, wwiv_flag);
						if(rd>sizeof(wwiv_buf))
							errorlog("WWIV_BUF OVERRUN");
					} else {
759
                		bp=telnet_expand(buf, rd, telnet_buf, rd);
760
761
762
						if(rd>sizeof(telnet_buf))
							errorlog("TELNET_BUF OVERRUN");
					}
763
764
765
					if(rd>RingBufFree(&outbuf)) {
						errorlog("output buffer overflow");
						rd=RingBufFree(&outbuf);
766
					}
767
					RingBufWrite(&outbuf, bp, rd);
768
					sem_post(&output_sem);
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
				}
			} else {	// Windows 9x

				/* Write to VXD */

				wr=RingBufPeek(&inbuf, buf+sizeof(hVM),sizeof(buf)-sizeof(hVM));
				if(wr) {
					*(DWORD*)buf=hVM;
					wr+=sizeof(hVM);
					if(!DeviceIoControl(
						vxd,					// handle to device of interest
						SBBSEXEC_IOCTL_WRITE,	// control code of operation to perform
						buf,					// pointer to buffer to supply input data
						wr,						// size of input buffer
						&rd,					// pointer to buffer to receive output data
						sizeof(rd),				// size of output buffer
						&dummy,	 				// pointer to variable to receive output byte count
						NULL					// Overlapped I/O
						)) {
						errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_READ);
						break;
					}
					RingBufRead(&inbuf, NULL, rd);
					wr=rd;
				}
        		/* Read from VXD */
				rd=0;
				len=sizeof(buf);
797
798
				avail=RingBufFree(&outbuf)/2;	// leave room for wwiv/telnet expansion
#if 0
799
				if(avail==0) 
800
					lprintf("Node %d !output buffer full (%u bytes)"
801
						,cfg.node_num,RingBufFull(&outbuf));
802
#endif
803

804
805
				if(len>avail)
            		len=avail;
806
				if(len) {
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
					if(!DeviceIoControl(
						vxd,					// handle to device of interest
						SBBSEXEC_IOCTL_READ,	// control code of operation to perform
						&hVM,					// pointer to buffer to supply input data
						sizeof(hVM),			// size of input buffer
						buf,					// pointer to buffer to receive output data
						len,					// size of output buffer
						&rd,					// pointer to variable to receive output byte count
						NULL					// Overlapped I/O
						)) {
						errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_READ);
						break;
					}
#if 0
					if(rd>1)
                		lprintf("read %d bytes from xtrn", rd);
#endif

825
					if(mode&EX_WWIV) {
826
                		bp=wwiv_expand(buf, rd, wwiv_buf, rd, useron.misc, wwiv_flag);
827
828
829
						if(rd>sizeof(wwiv_buf))
							errorlog("WWIV_BUF OVERRUN");
					} else {
830
                		bp=telnet_expand(buf, rd, telnet_buf, rd);
831
832
833
834
835
836
837
						if(rd>sizeof(telnet_buf))
							errorlog("TELNET_BUF OVERRUN");
					}
					if(rd>RingBufFree(&outbuf)) {
						errorlog("output buffer overflow");
						rd=RingBufFree(&outbuf);
					}
838
					RingBufWrite(&outbuf, bp, rd);
839
					sem_post(&output_sem);
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
				}
			}
            if(!rd && !wr) {
				if(++loop_since_io>=1000) {
	                if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE) {
	                    errormsg(WHERE, ERR_CHK, "ExitCodeProcess"
						,(DWORD)process_info.hProcess);
						break;
					}
					if(retval!=STILL_ACTIVE)  {
#if 0
						if(hungup)
							Sleep(5000);	// Give the application time to close files
#endif
	                    break;
					}
856
857
					

858
					if(!(loop_since_io%3000)) {
859
860
861
862
						OutputDebugString(".");

						// Let's make sure the socket is up
						// Sending will trigger a socket d/c detection
rswindell's avatar
rswindell committed
863
						send_telnet_cmd(TELNET_GA,0);
864
865

						// Check if the node has been interrupted
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
						getnodedat(cfg.node_num,&thisnode,0);
						if(thisnode.misc&NODE_INTR)
							break;
					}
					Sleep(1);
				}
            } else
				loop_since_io=0;
        }
	}

	if(!native && !(mode&EX_OFFLINE) && !nt) {
		if(!DeviceIoControl(
			vxd,					// handle to device of interest
			SBBSEXEC_IOCTL_STOP,	// control code of operation to perform
			&hVM,					// pointer to buffer to supply input data
			sizeof(hVM),			// size of input buffer
			NULL,					// pointer to buffer to receive output data
			0,						// size of output buffer
			&rd,					// pointer to variable to receive output byte count
			NULL					// Overlapped I/O
			)) {
			errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_STOP);
		}
	}

892
    if(!(mode&EX_BG)) {			/* !background execution */
893

894
895
		if(retval==STILL_ACTIVE)
			TerminateProcess(process_info.hProcess, GetLastError());
896

897
	 	// Get return value
898
899
900
901
902
903
904
905
    	sprintf(str,"%sDOSXTRN.RET", cfg.node_dir);
        FILE* fp=fopen(str,"r");
        if(fp!=NULL) {
			fscanf(fp,"%d",&retval);
			fclose(fp);
		}
	}

906
	XTRN_CLEANUP;
907
	CloseHandle(process_info.hProcess);
908
909
910

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

911
912
913
		if(native) {
			ulong l=0;
			ioctlsocket(client_socket, FIONBIO, &l);
914
			pthread_mutex_unlock(&input_thread_mutex);
915
		}
916

917
918
		curatr=~0;			// Can't guarantee current attributes
		attr(LIGHTGRAY);	// Force to "normal"
919

920
		rio_abortable=rio_abortable_save;	// Restore abortable state
921

922
		/* Got back to Text/NVT mode */
rswindell's avatar
rswindell committed
923
		send_telnet_cmd(TELNET_DONT,TELNET_BINARY);
924
925
		telnet_mode&=~TELNET_MODE_BIN_RX;
	}
926

927
928
//	lprintf("%s returned %d",realcmdline, retval);

929
930
	errorlevel = retval; // Baja-retrievable error value

931
932
933
	return(retval);
}

934
935
#else	/* !WIN32 */

936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
/*****************************************************************************/
// 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);
}

952
953
#define MAX_ARGS 1000

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
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
1032
1033
1034
1035
1036
1037
1038
1039
1040
#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);
}

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 */

1041
1042
int sbbs_t::external(char* cmdline, long mode, char* startup_dir)
{
1043
	char	str[256];
1044
	char	fname[128];
1045
	char*	argv[MAX_ARGS];
1046
	char*	p;
1047
1048
1049
	BYTE*	bp;
	BYTE	buf[XTRN_IO_BUF_LEN];
    BYTE 	output_buf[XTRN_IO_BUF_LEN*2];
1050
	ulong	avail;
1051
1052
    ulong	output_len;
	bool	native=false;			// DOS program by default
rswindell's avatar
rswindell committed
1053
	bool	rio_abortable_save;
rswindell's avatar
rswindell committed
1054
	int		i;
1055
1056
	int		rd;
	int		wr;
1057
	int		argc;
1058
	pid_t	pid;
1059
1060
	int		in_pipe[2];
	int		out_pipe[2];
1061
1062
	fd_set ibits;
	struct timeval timeout;
1063
1064
1065

	XTRN_LOADABLE_MODULE;
	XTRN_LOADABLE_JS_MODULE;
1066

1067
1068
	attr(cfg.color[clr_external]);        /* setup default attributes */

1069
    SAFECOPY(str,cmdline);		/* Set str to program name only */
1070
1071
    p=strchr(str,SP);
    if(p) *p=0;
1072
    SAFECOPY(fname,str);
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085

    p=strrchr(fname,'/');
    if(!p) p=strrchr(fname,'\\');
    if(!p) p=strchr(fname,':');
    if(!p) p=fname;
    else   p++;

    for(i=0;i<cfg.total_natvpgms;i++)
        if(!stricmp(p,cfg.natvpgm[i]->name))
            break;
    if(i<cfg.total_natvpgms || mode&EX_NATIVE)
        native=true;

1086
#ifndef __FreeBSD__
1087
	if(!native) {
1088
		bprintf("\r\nExternal DOS programs are not yet supported in\r\n%s\r\n",VERSION_NOTICE);
1089
1090
		return(-1);
	}
1091
#endif
1092
1093
1094
1095

 	if(native) { // Native (32-bit) external

		// Current environment passed to child process
1096
1097
1098
1099
1100
1101
1102
1103
		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(sbbsnnum,"%u",cfg.node_num);
		if(setenv("SBBSNNUM",sbbsnnum,1))
1104
        	errormsg(WHERE,ERR_WRITE,"environment",0);
1105
1106

	} else {
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
#ifdef __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);

		fclose(doscmdrc);
		SAFECOPY(str,cmdline);
		sprintf(cmdline,"/usr/bin/doscmd -xFQ %s",str);
#endif
1148
1149
	}

1150
1151
	if(!(mode&EX_INR))
		pthread_mutex_lock(&input_thread_mutex);
1152
1153
1154

	if((mode&EX_INR) && (mode&EX_OUTR))  {
		struct winsize winsize;
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
		struct termios term;
		memset(&term,0,sizeof(term));
		cfsetspeed(&term,B19200);
		if(mode&EX_BIN)
			cfmakeraw(&term);
		else {
			term.c_iflag = TTYDEF_IFLAG;
			term.c_oflag = TTYDEF_OFLAG;
			term.c_lflag = TTYDEF_LFLAG;
		}
1165
1166
1167
		winsize.ws_row=rows;
		// #warning Currently cols are forced to 80 apparently TODO
		winsize.ws_col=80;
1168
		if((pid=forkpty(&in_pipe[1],NULL,&term,&winsize))==-1) {
1169
1170
			pthread_mutex_unlock(&input_thread_mutex);
			errormsg(WHERE,ERR_EXEC,cmdline,0);
1171
1172
			return(-1);
		}
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
		out_pipe[0]=in_pipe[1];
	}
	else  {
		if(mode&EX_INR)
			if(pipe(in_pipe)!=0) {
				errormsg(WHERE,ERR_CREATE,"in_pipe",0);
				return(-1);
			}
		if(mode&EX_OUTR)
			if(pipe(out_pipe)!=0) {
				errormsg(WHERE,ERR_CREATE,"out_pipe",0);
				return(-1);
			}


		if((pid=fork())==-1) {
			pthread_mutex_unlock(&input_thread_mutex);
			errormsg(WHERE,ERR_EXEC,cmdline,0);
1191
1192
			return(-1);
		}
1193
1194
	}
	if(pid==0) {	/* child process */
1195
1196
1197
1198
1199
		if(!(mode&EX_BIN))  {
			static char	term_env[256];
			sprintf(term_env,"TERM=%s",startup->xtrn_term);
			putenv(term_env);
		}
1200
1201
1202
1203
1204
#ifdef __FreeBSD__
		if(!native)
			chdir(cfg.node_dir);
		else
#endif
1205
1206
		if(startup_dir!=NULL && startup_dir[0])
			chdir(startup_dir);
1207

1208
		if(mode&EX_SH || strcspn(cmdline,"<>|;")!=strlen(cmdline)) {
1209
1210
1211
1212
1213
1214
1215
			argv[0]=comspec;
			argv[1]="-c";
			argv[2]=cmdline;
			argv[3]=NULL;
		} else {
			argv[0]=cmdline;	/* point to the beginning of the string */
			argc=1;
1216
			for(i=0;cmdline[i] && argc<MAX_ARGS;i++)	/* Break up command line */
1217
1218
1219
1220
1221
1222
				if(cmdline[i]==SP) {
					cmdline[i]=0;			/* insert nulls */
					argv[argc++]=cmdline+i+1; /* point to the beginning of the next arg */
				}
			argv[argc]=NULL;
		}
1223

1224
		if(mode&EX_INR && !(mode&EX_OUTR))  {
1225
1226
1227
1228
			close(in_pipe[1]);		/* close write-end of pipe */
			dup2(in_pipe[0],0);		/* redirect stdin */
			close(in_pipe[0]);		/* close excess file descriptor */
		}
1229

1230
		if(mode&EX_OUTR && !(mode&EX_INR)) {
1231
1232
1233
1234
			close(out_pipe[0]);		/* close read-end of pipe */
			dup2(out_pipe[1],1);	/* stdout */
			dup2(out_pipe[1],2);	/* stderr */
			close(out_pipe[1]);		/* close excess file descriptor */
1235
1236
		}	

1237
1238
1239
1240
1241
		if(mode&EX_BG)	/* background execution, detach child */
		{
			if(fork())
				exit(0);
			lprintf("Detaching external process pgid=%d",setsid());
1242
1243
   	    }
	
1244
		execvp(argv[0],argv);
1245
1246
		sprintf(str,"!ERROR %d executing %s",errno,argv[0]);
		errorlog(str);
1247
		exit(-1);	/* should never get here */
1248
	}
1249

1250
	lprintf("Node %d executing external: %s",cfg.node_num,cmdline);
rswindell's avatar
rswindell committed
1251
1252
1253
1254
1255
1256

	/* Disable Ctrl-C checking */
	if(!(mode&EX_OFFLINE)) {
		rio_abortable_save=rio_abortable;
		rio_abortable=false;
	}
1257
	
1258
	if(mode&EX_OUTR) {
1259
1260
		if(!(mode&EX_INR))
			close(out_pipe[1]);	/* close write-end of pipe */
1261
		while(!terminated) {
1262
1263
			if(waitpid(pid, &i, WNOHANG)!=0)	/* child exited */
				break;
1264
1265
1266

			if(mode&EX_CHKTIME)
				gettimeleft();
1267
			
1268
1269
1270
1271
1272
1273
			if(!online && !(mode&EX_OFFLINE)) {
				sprintf(str,"%s hung-up in external program",useron.alias);
				logline("X!",str);
				break;
			}

1274
1275
1276
1277
1278
1279
1280
			/* Input */	
			if(mode&EX_INR && RingBufFull(&inbuf)) {
				if((wr=RingBufRead(&inbuf,buf,sizeof(buf)))!=0)
					write(in_pipe[1],buf,wr);
			}
				
			/* Output */
1281
1282
1283
1284
1285
1286
1287
1288
			FD_ZERO(&ibits);
			FD_SET(out_pipe[0],&ibits);
			timeout.tv_sec=0;
			timeout.tv_usec=0;
			if(!select(out_pipe[0]+1,&ibits,NULL,NULL,&timeout))  {
				mswait(1);
				continue;
			}
1289

1290
			avail=RingBufFree(&outbuf)/2;	// Leave room for wwiv/telnet expansion
1291
			if(avail==0) {
1292
#if 0
1293
				lprintf("Node %d !output buffer full (%u bytes)"
1294
						,cfg.node_num,RingBufFull(&outbuf));
1295
#endif
1296
1297
1298
1299
				mswait(1);
				continue;
			}

1300
			rd=avail;
1301

1302
			if(rd>(int)sizeof(buf))
1303
1304
1305
				rd=sizeof(buf);

			if((rd=read(out_pipe[0],buf,rd))<1) {
1306
1307
1308
				mswait(1);
				continue;
			}
1309
				
1310
			if(mode&EX_BIN)	/* telnet IAC expansion */
1311
   	       		bp=telnet_expand(buf, rd, output_buf, output_len);
1312
1313
			else			/* LF to CRLF expansion */
				bp=lf_expand(buf, rd, output_buf, output_len);
1314
1315
1316
1317
1318
1319

			/* Did expansion overrun the output buffer? */
			if(output_len>sizeof(output_buf)) {
				errorlog("OUTPUT_BUF OVERRUN");
				output_len=sizeof(output_buf);
			}
1320
			
1321
			/* Does expanded size fit in the ring buffer? */
1322
			if(output_len>RingBufFree(&outbuf)) {
1323
1324
				errorlog("output buffer overflow");
				output_len=RingBufFree(&outbuf);
1325
			}
1326
	
1327
1328
1329
			RingBufWrite(&outbuf, bp, output_len);
			sem_post(&output_sem);	
		}
1330

1331
1332
1333
		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
1334
			while(time(NULL)-start<10) {		// for child to terminate
1335
1336