xtrn.cpp 36.7 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
44
45
#ifdef __unix__
	#include <sys/wait.h>	// WEXITSTATUS
#endif

46
47
#define XTRN_IO_BUF_LEN 5000

48
49
50
51
52
53
54
55
56
57
/*****************************************************************************/
/* 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++) {
58
        if(buf[i]==CTRL_C) {	/* WWIV color escape char */
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
            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);
}
107
108

/*****************************************************************************/
109
// Escapes Telnet IAC (255) by doubling the IAC char
110
/*****************************************************************************/
111
BYTE* telnet_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
112
113
114
115
116
117
{
	BYTE*   first_iac;
	ulong	i,outlen;

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

118
	if(first_iac==NULL) {	/* Nothing to expand */
119
120
121
122
		newlen=inlen;
		return(inbuf);
	}

123
124
	outlen=first_iac-inbuf;
	memcpy(outbuf, inbuf, outlen);
125
126
127

    for(i=outlen;i<inlen;i++) {
		if(inbuf[i]==TELNET_IAC)
128
			outbuf[outlen++]=TELNET_IAC;
129
130
131
132
133
		outbuf[outlen++]=inbuf[i];
	}
    newlen=outlen;
    return(outbuf);
}
134

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#define XTRN_LOADABLE_MODULE								\
	if(cmdline[0]=='*') {   /* Baja module or JavaScript */	\
		sprintf(str,"%.*s",sizeof(str)-1,cmdline+1);		\
		p=strchr(str,SP);									\
		if(p) {												\
			strcpy(main_csi.str,p+1);						\
			*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
155
156
157
158
159
160
161

#ifdef _WIN32

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

extern SOCKET node_socket[];

162
163
164
165
166
167
168
169
// -------------------------------------------------------------------------
// 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.
//
170
171
172
typedef HANDLE (WINAPI *OPENVXDHANDLE)(HANDLE);

OPENVXDHANDLE GetAddressOfOpenVxDHandle(void)
173
174
175
{
	HINSTANCE hK32;

176
	if ((hK32 = LoadLibrary("KERNEL32")) == NULL)
177
178
		return NULL;

179
	return((OPENVXDHANDLE)GetProcAddress(hK32, "OpenVxDHandle"));
180
181
}

182
183
184
185
186
187
188
189
190
191
192
/* 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)

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/****************************************************************************/
/* 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];
    BYTE 	wwiv_buf[XTRN_IO_BUF_LEN*2];
    bool	wwiv_flag=false;
208
    bool	native=false;			// DOS program by default
209
	bool	nt=false;				// WinNT/2K? 
210
    bool	was_online=true;
211
	bool	rio_abortable_save;
212
	bool	use_pipes=false;	// NT-compatible console redirection
213
	uint	i;
214
215
216
217
    time_t	hungup=0;
	HANDLE	vxd=INVALID_HANDLE_VALUE;
	HANDLE	rdslot=INVALID_HANDLE_VALUE;
	HANDLE	wrslot=INVALID_HANDLE_VALUE;
218
	HANDLE  start_event=NULL;
219
	HANDLE	hungup_event=NULL;
220
221
	HANDLE	rdoutpipe;
	HANDLE	wrinpipe;
222
223
224
225
226
227
228
229
230
231
232
233
    PROCESS_INFORMATION process_info;
	DWORD	hVM;
	DWORD	rd;
    DWORD	wr;
    DWORD	len;
    DWORD	avail;
	DWORD	dummy;	
	DWORD	retval;
	DWORD	last_error;
	DWORD	loop_since_io=0;
	struct	tm * tm_p;
	sbbsexec_start_t start;
234
	OPENVXDHANDLE OpenVxDHandle;
235

236
237
238
	if(online==ON_LOCAL)
		eprintf("Executing external: %s",cmdline);

239
240
	XTRN_LOADABLE_MODULE;
	XTRN_LOADABLE_JS_MODULE;
241
242
243
244
245
246
247
248

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

    strcpy(str,cmdline);		/* Set str to program name only */
    p=strchr(str,SP);
    if(p) *p=0;
    strcpy(fname,str);

249
250
    p=strrchr(fname,'/');
    if(!p) p=strrchr(fname,'\\');
251
252
253
254
255
256
257
258
259
260
    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
261
	if(mode&EX_SH || strcspn(cmdline,"<>|")!=strlen(cmdline)) 
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
		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);

	strcpy(realcmdline, fullcmdline);	// for errormsg if failed to execute

	now=time(NULL);
	tm_p=gmtime(&now);

277
278
279
280
281
	if((retval=WaitForSingleObject(exec_mutex,5000))!=WAIT_OBJECT_0) {
		errormsg(WHERE, ERR_TIMEOUT, "exec_mutex", retval);
		return(GetLastError());
	}

282
283
284
285
286
287
288
289
	OpenVxDHandle=GetAddressOfOpenVxDHandle();

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

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

290
 	if(native || mode&EX_OFFLINE) { // Native (32-bit) external
291
292
293
294
295

		// Current environment passed to child process
		sprintf(dszlog,"DSZLOG=%sPROTOCOL.LOG",cfg.node_dir);
		sprintf(sbbsnode,"SBBSNODE=%s",cfg.node_dir);
		sprintf(sbbsnnum,"SBBSNNUM=%d",cfg.node_num);
296
		sprintf(sbbsctrl,"SBBSCTRL=%s",cfg.ctrl_dir);
297
298
299
		putenv(dszlog); 		/* Makes the DSZ LOG active */
		putenv(sbbsnode);
		putenv(sbbsnnum);
300
		putenv(sbbsctrl);
301
302
303
304
305
306
		if(tm_p!=NULL) {		/* date/time env vars */
			sprintf(env_day			,"DAY=%02u"			,tm_p->tm_mday);
			sprintf(env_weekday		,"WEEKDAY=%s"		,wday[tm_p->tm_wday]);
			sprintf(env_monthname	,"MONTHNAME=%s"		,mon[tm_p->tm_mon]);
			sprintf(env_month		,"MONTH=%02u"		,tm_p->tm_mon+1);
			sprintf(env_year		,"YEAR=%u"			,1900+tm_p->tm_year);
307
308
309
310
			putenv(env_day);
			putenv(env_weekday);
			putenv(env_monthname);
			putenv(env_month);
311
312
			if(putenv(env_year))
        		errormsg(WHERE,ERR_WRITE,"environment",0);
313
		}
314
315
316
317
318
319
320
321
322
323

    } 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) {
324
			XTRN_CLEANUP;
325
        	errormsg(WHERE, ERR_CREATE, str, 0);
326
            return(errno);
327
328
329
330
331
332
        }
        fprintf(fp, "%s\n", fullcmdline);
		fprintf(fp, "DSZLOG=%sPROTOCOL.LOG\n", cfg.node_dir);
        fprintf(fp, "SBBSCTRL=%s\n", cfg.ctrl_dir);
        fprintf(fp, "SBBSNODE=%s\n", cfg.node_dir);
        fprintf(fp, "SBBSNNUM=%d\n", cfg.node_num);
333
		if(tm_p!=NULL) {	/* date/time env vars */
334
			fprintf(fp, "DAY=%02u\n", tm_p->tm_mday);
335
			fprintf(fp, "WEEKDAY=%s\n",wday[tm_p->tm_wday]);
336
			fprintf(fp, "MONTHNAME=%s\n",mon[tm_p->tm_mon]);
337
			fprintf(fp, "MONTH=%02u\n",tm_p->tm_mon+1);
338
339
340
341
342
343
			fprintf(fp, "YEAR=%u\n",1900+tm_p->tm_year);
		}
        fclose(fp);

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

344
		if(!(mode&EX_OFFLINE) && nt) {	// Windows NT/2000
345
346
347
348
349
			i=SBBSEXEC_MODE_FOSSIL;
			if(mode&EX_INR)
           		i|=SBBSEXEC_MODE_DOS_IN;
			if(mode&EX_OUTR)
        		i|=SBBSEXEC_MODE_DOS_OUT;
350
351
			sprintf(str," NT %u %u %u"
				,cfg.node_num,i,startup->xtrn_polls_before_yield);
352
353
354
355
356
357
358
359
360
			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) {
361
				XTRN_CLEANUP;
362
363
364
365
366
367
368
369
370
371
372
				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) {
373
				XTRN_CLEANUP;
374
375
376
377
378
379
380
381
382
383
384
385
				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) {
386
				XTRN_CLEANUP;
387
388
389
390
391
392
393
394
395
396
				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) {
397
				XTRN_CLEANUP;
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
				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;

413
414
			sprintf(str," 95 %u %u %u"
				,cfg.node_num,start.mode,startup->xtrn_polls_before_yield);
415
416
			strcat(fullcmdline,str);

417
418
419
420
421
422
423
424
425
426
			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
				)) {
427
				XTRN_CLEANUP;
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
				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;
	}
449
    if(startup->options&BBS_OPT_XTRN_MINIMIZED) {
450
451
452
    	startup_info.wShowWindow=SW_SHOWMINNOACTIVE;
        startup_info.dwFlags|=STARTF_USESHOWWINDOW;
    }
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
	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.
		if (!CreatePipe(&rdoutpipe,&startup_info.hStdOutput,&sa,0)) {
			errormsg(WHERE,ERR_CREATE,"stdout pipe",0);
			return(GetLastError());
		}
		startup_info.hStdError=startup_info.hStdOutput;

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

474
475
476
477
478
479
480
481
482
483
484
485
486
487
		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;
	}
488
	if(native && !(mode&EX_OFFLINE)) {
489
		/* temporary */
490
491
492
		FILE* fp;
		sprintf(fname,"%sDOOR32.SYS",cfg.node_dir);
		fp=fopen(fname,"wb");
493
		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"
494
			"%d\r\n%d\r\n"
495
496
			,mode&EX_OUTR ? 0 /* Local */ : 2 /* Telnet */
			,mode&EX_OUTR ? INVALID_SOCKET : client_socket_dup
497
			,VERSION_NOTICE,REVISION
498
499
500
501
502
503
504
505
			,useron.number
			,useron.name
			,useron.alias
			,useron.level
			,timeleft/60
			,useron.misc&ANSI ? 1 : 0
			,cfg.node_num);
		fclose(fp);
506
507

		/* not temporary */
508
509
		if(!(mode&EX_INR)) 
			pthread_mutex_lock(&input_thread_mutex);
510
511
512
513
514
515
516
	}

    if(!CreateProcess(
		NULL,			// pointer to name of executable module
		fullcmdline,  	// pointer to command line string
		NULL,  			// process security attributes
		NULL,   		// thread security attributes
517
		native && !(mode&EX_OFFLINE),	 			// handle inheritance flag
518
		CREATE_NEW_CONSOLE/*|CREATE_SEPARATE_WOW_VDM*/, // creation flags
519
520
521
522
523
        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
		)) {
524
		XTRN_CLEANUP;
525
		if(native && !(mode&EX_OFFLINE))
526
527
			pthread_mutex_unlock(&input_thread_mutex);
		SetLastError(last_error);	/* Restore LastError */
528
529
530
531
        errormsg(WHERE, ERR_EXEC, realcmdline, mode);
        return(GetLastError());
    }

532
533
534
535
536
537
538
539
540
541
542
#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

543
544
545
546
547
	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) {
548
				XTRN_CLEANUP;
549
                TerminateProcess(process_info.hProcess, __LINE__);
550
551
552
553
554
				errormsg(WHERE, ERR_TIMEOUT, "start_event", retval);
				return(GetLastError());
			}

			CloseHandle(start_event);
555
			start_event=NULL;	/* Mark as closed */
556
557
558
559
560
561
562
563
564
565
566

			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
				)) {
567
				XTRN_CLEANUP;
568
                TerminateProcess(process_info.hProcess, __LINE__);
569
570
571
572
573
				errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_COMPLETE);
				return(GetLastError());
			}
		}
	}
574
    ReleaseMutex(exec_mutex);
575

576
	/* Disable Ctrl-C checking */
577
578
579
580
	if(!(mode&EX_OFFLINE)) {
		rio_abortable_save=rio_abortable;
		rio_abortable=false;
	}
581

582
    // Executing app in foreground?, monitor
583
    retval=STILL_ACTIVE;
584
    while(!(mode&EX_BG)) {
585
586
        if(!online && !(mode&EX_OFFLINE)) { // Tell VXD/VDD and external that user hung-up
        	if(was_online) {
587
588
				sprintf(str,"%s hung-up in external program",useron.alias);
				logline("X!",str);
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
            	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);
        }
612
613
		if((native && !use_pipes) || mode&EX_OFFLINE) {	
			/* Monitor for process termination only */
614
615
616
617
618
619
620
621
622
623
            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 {

624
			if(nt || use_pipes) {	// Windows NT/2000
625
626
627
628
629

				/* Write to VDD */

				wr=RingBufPeek(&inbuf,buf,sizeof(buf));
				if(wr) {
630
					if(!use_pipes && wrslot==INVALID_HANDLE_VALUE) {
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
						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
					}
647
648
649
650
651
					
					/* LF expansion */
					if(use_pipes && wr==1 && buf[0]=='\r') 
						buf[wr++]='\n';

652
653
654
655
					if(wrslot!=INVALID_HANDLE_VALUE
						&& WriteFile(wrslot,buf,wr,&len,NULL)==TRUE) {
						RingBufRead(&inbuf, NULL, len);
						wr=len;
656
657
658
659
660
						if(use_pipes) {
							/* echo */
							RingBufWrite(&outbuf, buf, wr);
							sem_post(&output_sem);
						}
661
662
663
664
665
666
667
668
					} else		// VDD not loaded yet
						wr=0;
				}

				/* Read from VDD */

				len=sizeof(buf);
				avail=RingBufFree(&outbuf);
669
				if(avail==0) 
670
					lprintf("Node %d !output buffer full (%u bytes)"
671
						,cfg.node_num,RingBufFull(&outbuf));
672
673
				if(len>avail)
            		len=avail;
674
675
676
677
678
679
680
681
682
683
684
685
686
687
				rd=0;
				DWORD waiting=1;

				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
						);
 
				if(avail>=RingBufFull(&outbuf) && waiting 
688
					&& ReadFile(rdslot,buf,len,&rd,NULL)==TRUE && rd>0) {
689
690
691
692
693
					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 {
694
                		bp=telnet_expand(buf, rd, telnet_buf, rd);
695
696
697
698
						if(rd>sizeof(telnet_buf))
							errorlog("TELNET_BUF OVERRUN");
					}
					RingBufWrite(&outbuf, bp, rd);
699
					sem_post(&output_sem);
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
				}
			} else {	// Windows 9x

				/* Write to VXD */

				wr=RingBufPeek(&inbuf, buf+sizeof(hVM),sizeof(buf)-sizeof(hVM));
				if(wr) {
					*(DWORD*)buf=hVM;
					wr+=sizeof(hVM);
					if(!DeviceIoControl(
						vxd,					// handle to device of interest
						SBBSEXEC_IOCTL_WRITE,	// control code of operation to perform
						buf,					// pointer to buffer to supply input data
						wr,						// size of input buffer
						&rd,					// pointer to buffer to receive output data
						sizeof(rd),				// size of output buffer
						&dummy,	 				// pointer to variable to receive output byte count
						NULL					// Overlapped I/O
						)) {
						errormsg(WHERE, ERR_IOCTL, SBBSEXEC_VXD, SBBSEXEC_IOCTL_READ);
						break;
					}
					RingBufRead(&inbuf, NULL, rd);
					wr=rd;
				}
        		/* Read from VXD */
				rd=0;
				len=sizeof(buf);
				avail=RingBufFree(&outbuf);
729
				if(avail==0) 
730
					lprintf("Node %d !output buffer full (%u bytes)"
731
732
						,cfg.node_num,RingBufFull(&outbuf));

733
734
				if(len>avail)
            		len=avail;
735
				if(avail>=RingBufFull(&outbuf)) {
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
					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

					if(mode&EX_WWIV)
                		bp=wwiv_expand(buf, rd, wwiv_buf, rd, useron.misc, wwiv_flag);
					else
757
                		bp=telnet_expand(buf, rd, telnet_buf, rd);
758
					RingBufWrite(&outbuf, bp, rd);
759
					sem_post(&output_sem);
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
				}
			}
            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;
					}
776
777
					

778
					if(!(loop_since_io%3000)) {
779
780
781
782
783
784
785
786
						OutputDebugString(".");

						// Let's make sure the socket is up
						// Sending will trigger a socket d/c detection
						sprintf(str,"%c%c",TELNET_IAC,TELNET_GA);
						putcom(str,2);

						// Check if the node has been interrupted
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
						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);
		}
	}

813
    if(!(mode&EX_BG)) {			/* !background execution */
814

815
816
		if(retval==STILL_ACTIVE)
			TerminateProcess(process_info.hProcess, GetLastError());
817

818
	 	// Get return value
819
820
821
822
823
824
825
826
    	sprintf(str,"%sDOSXTRN.RET", cfg.node_dir);
        FILE* fp=fopen(str,"r");
        if(fp!=NULL) {
			fscanf(fp,"%d",&retval);
			fclose(fp);
		}
	}

827
828
829
830
831
832
	XTRN_CLEANUP;

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

		if(native)
			pthread_mutex_unlock(&input_thread_mutex);
833
834

		curatr=0;	// Can't guarantee current attributes
835

836
		rio_abortable=rio_abortable_save;	// Restore abortable state
837

838
839
840
841
842
		/* Got back to Text/NVT mode */
		sprintf(str,"%c%c%c",TELNET_IAC,TELNET_DONT,TELNET_BINARY);
		putcom(str,3);
		telnet_mode&=~TELNET_MODE_BIN_RX;
	}
843

844
845
//	lprintf("%s returned %d",realcmdline, retval);

846
847
	errorlevel = retval; // Baja-retrievable error value

848
849
850
	return(retval);
}

851
852
#else	/* !WIN32 */

853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
/*****************************************************************************/
// 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);
}

869
870
#define MAX_ARGS 1000

871
872
int sbbs_t::external(char* cmdline, long mode, char* startup_dir)
{
873
	char	str[256];
874
	char	fname[128];
875
	char*	argv[MAX_ARGS];
876
	char*	p;
877
878
879
	BYTE*	bp;
	BYTE	buf[XTRN_IO_BUF_LEN];
    BYTE 	output_buf[XTRN_IO_BUF_LEN*2];
880
	ulong	avail;
881
882
    ulong	output_len;
	bool	native=false;			// DOS program by default
rswindell's avatar
rswindell committed
883
	int		i;
884
885
	int		rd;
	int		wr;
886
	int		argc;
887
	pid_t	pid;
888
889
	int		in_pipe[2];
	int		out_pipe[2];
890
891
892

	XTRN_LOADABLE_MODULE;
	XTRN_LOADABLE_JS_MODULE;
893

894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
	attr(cfg.color[clr_external]);        /* setup default attributes */

    strcpy(str,cmdline);		/* Set str to program name only */
    p=strchr(str,SP);
    if(p) *p=0;
    strcpy(fname,str);

    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;

	if(!native) {
914
		bprintf("\r\nExternal DOS programs are not yet supported in\r\n%s\r\n",VERSION_NOTICE);
915
916
917
918
919
920
921
922
923
		return(-1);
	}

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

		// Current environment passed to child process
		sprintf(dszlog,"DSZLOG=%sPROTOCOL.LOG",cfg.node_dir);
		sprintf(sbbsnode,"SBBSNODE=%s",cfg.node_dir);
		sprintf(sbbsnnum,"SBBSNNUM=%d",cfg.node_num);
924
925
926
927
928
929
		sprintf(sbbsctrl,"SBBSCTRL=%s",cfg.ctrl_dir);
		putenv(dszlog); 		/* Makes the DSZ LOG active */
		putenv(sbbsnode);
		putenv(sbbsnnum);
		if(putenv(sbbsctrl))
        	errormsg(WHERE,ERR_WRITE,"environment",0);
930
931
932
933
934

	} else {
		/* setup DOSemu env here */
	}

935
936
937
938
939
940
941
942
943
944
945
946
947
948
	if(!(mode&EX_INR))
		pthread_mutex_lock(&input_thread_mutex);
#if 1	
	if(mode&EX_INR)
		if(pipe(in_pipe)!=0) {
			errormsg(WHERE,ERR_CREATE,"in_pipe",0);
			return(-1);
		}
#endif		
	if(mode&EX_OUTR)
		if(pipe(out_pipe)!=0) {
			errormsg(WHERE,ERR_CREATE,"out_pipe",0);
			return(-1);
		}
949

950
	if((pid=fork())==-1) {
951
		pthread_mutex_unlock(&input_thread_mutex);
952
		errormsg(WHERE,ERR_EXEC,cmdline,0);
953
954
		return(-1);
	}
955
	
956
	if(pid==0) {	/* child process */
957
958
		if(startup_dir!=NULL && startup_dir[0])
			chdir(startup_dir);
959

960
		lprintf("Node %d executing external: %s",cfg.node_num,cmdline);
rswindell's avatar
rswindell committed
961

962
		if(mode&EX_SH || strcspn(cmdline,"<>|;")!=strlen(cmdline)) {
963
964
965
966
967
968
969
			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;
970
			for(i=0;cmdline[i] && argc<MAX_ARGS;i++)	/* Break up command line */
971
972
973
974
975
976
				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;
		}
977

978
979
980
981
982
		if(mode&EX_INR)  {
			close(in_pipe[1]);		/* close write-end of pipe */
			dup2(in_pipe[0],0);		/* redirect stdin */
			close(in_pipe[0]);		/* close excess file descriptor */
		}
983
984

		if(mode&EX_OUTR) {
985
986
987
988
			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 */
989
990
		}	

991
		execvp(argv[0],argv);
992
993
		sprintf(str,"!ERROR %d executing %s",errno,argv[0]);
		errorlog(str);
994
		exit(-1);	/* should never get here */
995
	}
996
997
998
	
	if(mode&EX_OUTR) {
		close(out_pipe[1]);	/* close write-end of pipe */
999
		while(!terminated) {
1000
1001
1002
			if(waitpid(pid, &i, WNOHANG)!=0)	/* child exited */
				break;
			
1003
1004
1005
1006
1007
1008
			if(!online && !(mode&EX_OFFLINE)) {
				sprintf(str,"%s hung-up in external program",useron.alias);
				logline("X!",str);
				break;
			}

1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
			/* Input */	
			if(mode&EX_INR && RingBufFull(&inbuf)) {
				if((wr=RingBufRead(&inbuf,buf,sizeof(buf)))!=0)
					write(in_pipe[1],buf,wr);
			}
				
			/* Output */
			ioctl(out_pipe[0],FIONREAD,&rd);	/* peek at input */
			if(!rd) {
				mswait(1);
				continue;
			}
1021
1022
1023

			avail=RingBufFree(&outbuf);
			if(avail==0) {
1024
				lprintf("Node %d !output buffer full (%u bytes)"
1025
1026
1027
1028
1029
1030
1031
					,cfg.node_num,RingBufFull(&outbuf));
				mswait(1);
				continue;
			}

			if(rd>avail)
				rd=avail;
rswindell's avatar
rswindell committed
1032
			if(rd>sizeof(buf))
1033
1034
1035
				rd=sizeof(buf);

			if((rd=read(out_pipe[0],buf,rd))<1) {
1036
1037
1038
1039
				mswait(1);
				continue;
			}
			if(mode&EX_BIN)	/* telnet IAC expansion */
1040
           		bp=telnet_expand(buf, rd, output_buf, output_len);
1041
1042
1043
			else			/* LF to CRLF expansion */
				bp=lf_expand(buf, rd, output_buf, output_len);
			
1044
1045
			/* Does expanded size fit? */
			if(output_len>RingBufFree(&outbuf)) {
1046
				lprintf("Node %d !output buffer overflow (%u bytes)"
1047
1048
1049
1050
1051
					,cfg.node_num,output_len);
				mswait(1);
				continue;
			}

1052
1053
1054
			RingBufWrite(&outbuf, bp, output_len);
			sem_post(&output_sem);	
		}
1055

1056
1057
1058
		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
1059
			while(time(NULL)-start<10) {		// for child to terminate
1060
1061
1062
1063
1064
1065
1066
				if(waitpid(pid, &i, WNOHANG)!=0)
					break;
				mswait(500);
			}
			if(waitpid(pid, &i, WNOHANG)==0)	// Child still running?
				kill(pid, SIGKILL);				// terminate child process
		}
1067
1068
1069
1070
		/* close unneeded descriptors */
		if(mode&EX_INR)
			close(in_pipe[1]);
		close(out_pipe[0]);
1071
	}
1072

1073
1074
	if(!(mode&EX_BG))	/* !background execution, wait for child to terminate */
		waitpid(pid, &i, 0);
1075
1076
1077
1078

	pthread_mutex_unlock(&input_thread_mutex);

	return(WEXITSTATUS(i));
1079
1080
1081
1082
}

#endif	/* !WIN32 */

1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
uint fakeriobp=0xffff;

/*****************************************************************************/
/* Returns command line generated from instr with %c replacments             */
/*****************************************************************************/
char * sbbs_t::cmdstr(char *instr, char *fpath, char *fspec, char *outstr)
{
	char	str[256],*cmd;
    int		i,j,len;

    if(outstr==NULL)
        cmd=cmdstr_output;
    else
        cmd=outstr;
    len=strlen(instr);
    for(i=j=0;i<len && j<sizeof(cmdstr_output);i++) {
        if(instr[i]=='%') {
            i++;
            cmd[j]=0;
            switch(toupper(instr[i])) {
                case 'A':   /* User alias */
                    strcat(cmd,useron.alias);
                    break;
                case 'B':   /* Baud (DTE) Rate */
                    strcat(cmd,ultoa(dte_rate,str,10));
                    break;
                case 'C':   /* Connect Description */
                    strcat(cmd,connection);
                    break;
                case 'D':   /* Connect (DCE) Rate */
                    strcat(cmd,ultoa((ulong)cur_rate,str,10));
                    break;
                case 'E':   /* Estimated Rate */
                    strcat(cmd,ultoa((ulong)cur_cps*10,str,10));
                    break;
                case 'F':   /* File path */
                    strcat(cmd,fpath);
                    break;
                case 'G':   /* Temp directory */
                    strcat(cmd,cfg.temp_dir);
                    break;
                case 'H':   /* Port Handle or Hardware Flow Control */
1125
1126
1127
#if defined(__unix__)
					strcat(cmd,ultoa(client_socket,str,10));
#else
1128
                    strcat(cmd,ultoa(client_socket_dup,str,10));
1129
#endif
1130
1131
                    break;
                case 'I':   /* UART IRQ Line */
1132
                    strcat(cmd,ultoa(cfg.com_irq,str,10));
1133
1134
1135
1136
1137
1138
1139
1140
                    break;
                case 'J':
                    strcat(cmd,cfg.data_dir);
                    break;
                case 'K':
                    strcat(cmd,cfg.ctrl_dir);
                    break;
                case 'L':   /* Lines per message */
1141
                    strcat(cmd,ultoa(cfg.level_linespermsg[useron.level],str,10));
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
                    break;
                case 'M':   /* Minutes (credits) for user */
                    strcat(cmd,ultoa(useron.min,str,10));
                    break;
                case 'N':   /* Node Directory (same as SBBSNODE environment var) */
                    strcat(cmd,cfg.node_dir);
                    break;
                case 'O':   /* SysOp */
                    strcat(cmd,cfg.sys_op);
                    break;
                case 'P':   /* COM Port */
1153
                    strcat(cmd,ultoa(online==ON_LOCAL ? 0:cfg.com_port,str,10));
1154
1155
1156
1157
1158
                    break;
                case 'Q':   /* QWK ID */
                    strcat(cmd,cfg.sys_id);
                    break;
                case 'R':   /* Rows */
1159
                    strcat(cmd,ultoa(rows,str,10));
1160
1161
1162
1163
1164
1165
                    break;
                case 'S':   /* File Spec */
                    strcat(cmd,fspec);
                    break;
                case 'T':   /* Time left in seconds */
                    gettimeleft();
1166
                    strcat(cmd,ultoa(timeleft,str,10));
1167
1168
                    break;
                case 'U':   /* UART I/O Address (in hex) */
1169
                    strcat(cmd,ultoa(cfg.com_base,str,16));
1170
1171
1172
1173
1174
1175
                    break;
                case 'V':   /* Synchronet Version */
                    sprintf(str,"%s%c",VERSION,REVISION);
                    break;
                case 'W':   /* Time-slice API type (mswtype) */
#if 0 //ndef __FLAT__
1176
                    strcat(cmd,ultoa(mswtyp,str,10));
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
#endif
                    break;
                case 'X':
                    strcat(cmd,cfg.shell[useron.shell]->code);
                    break;
                case '&':   /* Address of msr */
                    sprintf(str,"%lu",&fakeriobp);
                    strcat(cmd,str);
                    break;
                case 'Y':
1187
                    strcat(cmd,comspec);
1188
1189
1190
1191
                    break;
                case 'Z':
                    strcat(cmd,cfg.text_dir);
                    break;
1192
1193
				case '~':	/* DOS-compatible (8.3) filename */
#ifdef _WIN32
1194
					char sfpath[MAX_PATH+1];
1195
1196
1197
1198
1199
1200
1201
					strcpy(sfpath,fpath);
					GetShortPathName(fpath,sfpath,sizeof(sfpath));
					strcat(cmd,sfpath);
#else
                    strcat(cmd,fpath);
#endif			
					break;
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
                case '!':   /* EXEC Directory */
                    strcat(cmd,cfg.exec_dir);
                    break;
                case '#':   /* Node number (same as SBBSNNUM environment var) */
                    sprintf(str,"%d",cfg.node_num);
                    strcat(cmd,str);
                    break;
                case '*':
                    sprintf(str,"%03d",cfg.node_num);
                    strcat(cmd,str);
                    break;
                case '$':   /* Credits */
                    strcat(cmd,ultoa(useron.cdt+useron.freecdt,str,10));
                    break;
                case '%':   /* %% for percent sign */
                    strcat(cmd,"%");
                    break;
1219
1220
1221
1222
				case '?':	/* Platform */
#ifdef __OS2__
					strcpy(str,"OS2");
#else
1223
					strcpy(str,PLATFORM_DESC);
1224
1225
1226
1227
#endif
					strlwr(str);
					strcat(cmd,str);
					break;
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
                default:    /* unknown specification */
                    if(isdigit(instr[i])) {
                        sprintf(str,"%0*d",instr[i]&0xf,useron.number);
                        strcat(cmd,str); }
                    break; }
            j=strlen(cmd); }
        else
            cmd[j++]=instr[i]; }
    cmd[j]=0;

    return(cmd);
}