Select Git revision
-
Rob Swindell authored
Mostly in error-paths.
Rob Swindell authoredMostly in error-paths.
xtrn.cpp 64.83 KiB
/* Synchronet external program support routines */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include "sbbs.h"
#include "cmdshell.h"
#include "telnet.h"
#include <signal.h> // kill()
#ifdef __unix__
#include <sys/wait.h> // WEXITSTATUS
#define TTYDEFCHARS // needed for ttydefchars definition
#include <sys/ttydefaults.h> // Linux - it's motherfucked.
#if defined(__FreeBSD__)
#include <libutil.h> // forkpty()
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DARWIN__)
#include <util.h>
#elif defined(__linux__)
#include <pty.h>
#elif defined(__QNX__)
#if 0
#include <unix.h>
#else
#define NEEDS_FORKPTY
#endif
#endif
#ifdef NEEDS_FORKPTY
#include <grp.h>
#endif
#include <termios.h>
/*
* Control Character Defaults
*/
#ifndef CTRL
#define CTRL(x) (x&037)
#endif
#ifndef CEOF
#define CEOF CTRL('d')
#endif
#ifndef CEOL
#define CEOL 0xff /* XXX avoid _POSIX_VDISABLE */
#endif
#ifndef CERASE
#define CERASE 0177
#endif
#ifndef CERASE2
#define CERASE2 CTRL('h')
#endif
#ifndef CINTR
#define CINTR CTRL('c')
#endif
#ifndef CSTATUS
#define CSTATUS CTRL('t')
#endif
#ifndef CKILL
#define CKILL CTRL('u')
#endif
#ifndef CMIN
#define CMIN 1
#endif
#ifndef CQUIT
#define CQUIT 034 /* FS, ^\ */
#endif
#ifndef CSUSP
#define CSUSP CTRL('z')
#endif
#ifndef CTIME
#define CTIME 0
#endif
#ifndef CDSUSP
#define CDSUSP CTRL('y')
#endif
#ifndef CSTART
#define CSTART CTRL('q')
#endif
#ifndef CSTOP
#define CSTOP CTRL('s')
#endif
#ifndef CLNEXT
#define CLNEXT CTRL('v')
#endif
#ifndef CDISCARD
#define CDISCARD CTRL('o')
#endif
#ifndef CWERASE
#define CWERASE CTRL('w')
#endif
#ifndef CREPRINT
#define CREPRINT CTRL('r')
#endif
#ifndef CEOT
#define CEOT CEOF
#endif
/* compat */
#ifndef CBRK
#define CBRK CEOL
#endif
#ifndef CRPRNT
#define CRPRNT CREPRINT
#endif
#ifndef CFLUSH
#define CFLUSH CDISCARD
#endif
#ifndef TTYDEF_IFLAG
#define TTYDEF_IFLAG (BRKINT | ICRNL | IMAXBEL | IXON | IXANY)
#endif
#ifndef TTYDEF_OFLAG
#define TTYDEF_OFLAG (OPOST | ONLCR)
#endif
#ifndef TTYDEF_LFLAG
#define TTYDEF_LFLAG (ECHO | ICANON | ISIG | IEXTEN | ECHOE|ECHOKE|ECHOCTL)
#endif
#ifndef TTYDEF_CFLAG
#define TTYDEF_CFLAG (CREAD | CS8 | HUPCL)
#endif
#if defined(__QNX__) || defined(__solaris__) || defined(__NetBSD__)
static cc_t ttydefchars[NCCS] = {
CEOF, CEOL, CEOL, CERASE, CWERASE, CKILL, CREPRINT,
CERASE2, CINTR, CQUIT, CSUSP, CDSUSP, CSTART, CSTOP, CLNEXT,
CDISCARD, CMIN, CTIME, CSTATUS
#ifndef __solaris__
, _POSIX_VDISABLE
#endif
};
#endif
#endif /* __unix__ */
#define XTRN_IO_BUF_LEN 10000 /* 50% of IO_THREAD_BUF_SIZE */
/*****************************************************************************/
/* Interrupt routine to expand WWIV Ctrl-C# codes into ANSI escape sequences */
/*****************************************************************************/
BYTE* wwiv_expand(BYTE* buf, ulong buflen, BYTE* outbuf, ulong& newlen
,ulong user_misc, bool& ctrl_c)
{
char ansi_seq[32];
ulong i,j,k;
for(i=j=0;i<buflen;i++) {
if(buf[i]==CTRL_C) { /* WWIV color escape char */
ctrl_c=true;
continue;
}
if(!ctrl_c) {
outbuf[j++]=buf[i];
continue;
}
ctrl_c=false;
if(user_misc&ANSI) {
switch(buf[i]) {
default:
strcpy(ansi_seq,"\x1b[0m"); /* low grey */
break;
case '1':
strcpy(ansi_seq,"\x1b[0;1;36m"); /* high cyan */
break;
case '2':
strcpy(ansi_seq,"\x1b[0;1;33m"); /* high yellow */
break;
case '3':
strcpy(ansi_seq,"\x1b[0;35m"); /* low magenta */
break;
case '4':
strcpy(ansi_seq,"\x1b[0;1;44m"); /* white on blue */
break;
case '5':
strcpy(ansi_seq,"\x1b[0;32m"); /* low green */
break;
case '6':
strcpy(ansi_seq,"\x1b[0;1;5;31m"); /* high blinking red */
break;
case '7':
strcpy(ansi_seq,"\x1b[0;1;34m"); /* high blue */
break;
case '8':
strcpy(ansi_seq,"\x1b[0;34m"); /* low blue */
break;
case '9':
strcpy(ansi_seq,"\x1b[0;36m"); /* low cyan */
break;
}
for(k=0;ansi_seq[k];k++)
outbuf[j++]=ansi_seq[k];
}
}
newlen=j;
return(outbuf);
}
static void petscii_convert(BYTE* buf, ulong len)
{
for(ulong i=0; i<len; i++) {
buf[i] = cp437_to_petscii(buf[i]);
}
}
static bool native_executable(scfg_t* cfg, const char* cmdline, long mode)
{
char* p;
char str[MAX_PATH+1];
char name[64];
char base[64];
unsigned i;
if(mode&EX_NATIVE)
return true;
if(*cmdline == '?' || *cmdline == '*')
return true;
SAFECOPY(str,cmdline); /* Set str to program name only */
truncstr(str," ");
SAFECOPY(name,getfname(str));
SAFECOPY(base,name);
if((p=getfext(base))!=NULL)
*p=0;
for(i=0;i<cfg->total_natvpgms;i++)
if(stricmp(name,cfg->natvpgm[i]->name)==0
|| stricmp(base,cfg->natvpgm[i]->name)==0)
break;
return(i<cfg->total_natvpgms);
}
#define XTRN_LOADABLE_MODULE(cmdline,startup_dir) \
if(cmdline[0]=='*') /* Baja module or JavaScript */ \
return(exec_bin(cmdline+1,&main_csi,startup_dir));
#ifdef JAVASCRIPT
#define XTRN_LOADABLE_JS_MODULE(cmdline,mode,startup_dir) \
if(cmdline[0]=='?' && (mode&EX_SH)) \
return(js_execxtrn(cmdline+1, startup_dir)); \
if(cmdline[0]=='?') \
return(js_execfile(cmdline+1,startup_dir));
#else
#define XTRN_LOADABLE_JS_MODULE
#endif
#ifdef _WIN32
#include "execvxd.h" /* DOSXTRN.EXE API */
extern SOCKET node_socket[];
/*****************************************************************************/
// Expands Single CR to CRLF
/*****************************************************************************/
BYTE* cr_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
{
ulong i,j;
for(i=j=0;i<inlen;i++) {
outbuf[j++]=inbuf[i];
if(inbuf[i]=='\r')
outbuf[j++]='\n';
}
newlen=j;
return(outbuf);
}
static void add_env_var(str_list_t* list, const char* var, const char* val)
{
char str[MAX_PATH*2];
SetEnvironmentVariable(var,NULL); /* Delete in current process env */
SAFEPRINTF2(str,"%s=%s",var,val);
strListPush(list,str);
}
/* Clean-up resources while preserving current LastError value */
#define XTRN_CLEANUP \
last_error=GetLastError(); \
if(vxd!=INVALID_HANDLE_VALUE) CloseHandle(vxd); \
if(rdslot!=INVALID_HANDLE_VALUE) CloseHandle(rdslot); \
if(wrslot!=INVALID_HANDLE_VALUE) CloseHandle(wrslot); \
if(start_event!=NULL) CloseHandle(start_event); \
if(hungup_event!=NULL) CloseHandle(hungup_event); \
if(hangup_event!=NULL) CloseHandle(hangup_event); \
SetLastError(last_error)
/****************************************************************************/
/* Runs an external program */
/****************************************************************************/
int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
{
char str[MAX_PATH+1];
char* env_block=NULL;
char* env_strings;
const char* p_startup_dir;
char path[MAX_PATH+1];
char fullcmdline[MAX_PATH+1];
char realcmdline[MAX_PATH+1];
char comspec_str[MAX_PATH+1];
char title[MAX_PATH+1];
BYTE buf[XTRN_IO_BUF_LEN],*bp;
BYTE telnet_buf[XTRN_IO_BUF_LEN*2];
BYTE output_buf[XTRN_IO_BUF_LEN*2];
BYTE wwiv_buf[XTRN_IO_BUF_LEN*2];
bool wwiv_flag=false;
bool native=false; // DOS program by default
bool was_online=true;
bool rio_abortable_save=rio_abortable;
bool use_pipes=false; // NT-compatible console redirection
BOOL success;
BOOL processTerminated=false;
uint i;
time_t hungup=0;
HANDLE vxd=INVALID_HANDLE_VALUE;
HANDLE rdslot=INVALID_HANDLE_VALUE;
HANDLE wrslot=INVALID_HANDLE_VALUE;
HANDLE start_event=NULL;
HANDLE hungup_event=NULL;
HANDLE hangup_event=NULL;
HANDLE rdoutpipe;
HANDLE wrinpipe;
PROCESS_INFORMATION process_info;
unsigned long rd;
unsigned long wr;
unsigned long len;
DWORD avail;
unsigned long msglen;
unsigned long retval;
DWORD last_error;
DWORD loop_since_io=0;
struct tm tm;
str_list_t env_list;
xtrn_mode = mode;
lprintf(LOG_DEBUG,"Executing external: %s",cmdline);
if(startup_dir!=NULL && startup_dir[0] && !isdir(startup_dir)) {
errormsg(WHERE, ERR_CHK, startup_dir, 0);
return -1;
}
XTRN_LOADABLE_MODULE(cmdline,startup_dir);
XTRN_LOADABLE_JS_MODULE(cmdline,mode,startup_dir);
attr(cfg.color[clr_external]); /* setup default attributes */
native = native_executable(&cfg, cmdline, mode);
if(!native && (startup->options&BBS_OPT_NO_DOS)) {
lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOS programs not supported: %s", cmdline);
bprintf("Sorry, DOS programs are not supported on this node.\r\n");
return -1;
}
if(mode&EX_SH || strcspn(cmdline,"<>|")!=strlen(cmdline))
sprintf(comspec_str,"%s /C ", comspec);
else
comspec_str[0]=0;
if(startup_dir && cmdline[1]!=':' && cmdline[0]!='/'
&& cmdline[0]!='\\' && cmdline[0]!='.')
SAFEPRINTF3(fullcmdline, "%s%s%s", comspec_str, startup_dir, cmdline);
else
SAFEPRINTF2(fullcmdline, "%s%s", comspec_str, cmdline);
SAFECOPY(realcmdline, fullcmdline); // for errormsg if failed to execute
now=time(NULL);
if(localtime_r(&now,&tm)==NULL)
memset(&tm,0,sizeof(tm));
if(native && mode&EX_STDOUT && !(mode&EX_OFFLINE))
use_pipes=true;
if(native) { // Native (not MS-DOS) external
if((env_list=strListInit())==NULL) {
XTRN_CLEANUP;
errormsg(WHERE, ERR_CREATE, "env_list", 0);
return(errno);
}
// Current environment passed to child process
sprintf(str,"%sprotocol.log",cfg.node_dir);
add_env_var(&env_list,"DSZLOG",str);
add_env_var(&env_list,"SBBSNODE",cfg.node_dir);
add_env_var(&env_list,"SBBSCTRL",cfg.ctrl_dir);
add_env_var(&env_list,"SBBSDATA",cfg.data_dir);
add_env_var(&env_list,"SBBSEXEC",cfg.exec_dir);
sprintf(str,"%d",cfg.node_num);
add_env_var(&env_list,"SBBSNNUM",str);
/* date/time env vars */
sprintf(str,"%02u",tm.tm_mday);
add_env_var(&env_list,"DAY",str);
add_env_var(&env_list,"WEEKDAY",wday[tm.tm_wday]);
add_env_var(&env_list,"MONTHNAME",mon[tm.tm_mon]);
sprintf(str,"%02u",tm.tm_mon+1);
add_env_var(&env_list,"MONTH",str);
sprintf(str,"%u",1900+tm.tm_year);
add_env_var(&env_list,"YEAR",str);
env_strings=GetEnvironmentStrings();
env_block=strListCopyBlock(env_strings);
if(env_strings!=NULL)
FreeEnvironmentStrings(env_strings);
env_block=strListAppendBlock(env_block,env_list);
strListFree(&env_list);
if(env_block==NULL) {
XTRN_CLEANUP;
errormsg(WHERE, ERR_CREATE, "env_block", 0);
return(errno);
}
} else { // DOS external
// DOS-compatible (short) paths
char node_dir[MAX_PATH+1];
char ctrl_dir[MAX_PATH+1];
char data_dir[MAX_PATH+1];
char exec_dir[MAX_PATH+1];
// in case GetShortPathName fails
SAFECOPY(node_dir,cfg.node_dir);
SAFECOPY(ctrl_dir,cfg.ctrl_dir);
SAFECOPY(data_dir,cfg.data_dir);
SAFECOPY(exec_dir,cfg.exec_dir);
GetShortPathName(cfg.node_dir,node_dir,sizeof(node_dir));
GetShortPathName(cfg.ctrl_dir,ctrl_dir,sizeof(node_dir));
GetShortPathName(cfg.data_dir,data_dir,sizeof(data_dir));
GetShortPathName(cfg.exec_dir,exec_dir,sizeof(exec_dir));
SAFEPRINTF(path,"%sDOSXTRN.RET", cfg.node_dir);
(void)remove(path);
// Create temporary environment file
SAFEPRINTF(path,"%sDOSXTRN.ENV", node_dir);
FILE* fp=fopen(path,"w");
if(fp==NULL) {
XTRN_CLEANUP;
errormsg(WHERE, ERR_CREATE, path, 0);
return(errno);
}
fprintf(fp, "%s\n", fullcmdline);
fprintf(fp, "DSZLOG=%sPROTOCOL.LOG\n", node_dir);
fprintf(fp, "SBBSNODE=%s\n", node_dir);
fprintf(fp, "SBBSCTRL=%s\n", ctrl_dir);
fprintf(fp, "SBBSDATA=%s\n", data_dir);
fprintf(fp, "SBBSEXEC=%s\n", exec_dir);
fprintf(fp, "SBBSNNUM=%d\n", cfg.node_num);
fprintf(fp, "PCBNODE=%d\n", cfg.node_num);
fprintf(fp, "PCBDRIVE=%.2s\n", node_dir);
fprintf(fp, "PCBDIR=%s\n", node_dir + 2);
/* date/time env vars */
fprintf(fp, "DAY=%02u\n", tm.tm_mday);
fprintf(fp, "WEEKDAY=%s\n",wday[tm.tm_wday]);
fprintf(fp, "MONTHNAME=%s\n",mon[tm.tm_mon]);
fprintf(fp, "MONTH=%02u\n",tm.tm_mon+1);
fprintf(fp, "YEAR=%u\n",1900+tm.tm_year);
fclose(fp);
SAFEPRINTF2(fullcmdline, "%sDOSXTRN.EXE %s", cfg.exec_dir, path);
if(!(mode&EX_OFFLINE)) {
i=SBBSEXEC_MODE_FOSSIL;
if(mode&EX_STDIN)
i|=SBBSEXEC_MODE_DOS_IN;
if(mode&EX_STDOUT)
i|=SBBSEXEC_MODE_DOS_OUT;
sprintf(str," NT %u %u"
,cfg.node_num,i);
strcat(fullcmdline,str);
sprintf(str,"sbbsexec_hungup%d",cfg.node_num);
if((hungup_event=CreateEvent(
NULL // pointer to security attributes
,TRUE // flag for manual-reset event
,FALSE // flag for initial state
,str // pointer to event-object name
))==NULL) {
XTRN_CLEANUP;
errormsg(WHERE, ERR_CREATE, str, 0);
return(GetLastError());
}
sprintf(str,"sbbsexec_hangup%d",cfg.node_num);
if((hangup_event=CreateEvent(
NULL // pointer to security attributes
,TRUE // flag for manual-reset event
,FALSE // flag for initial state
,str // pointer to event-object name
))==NULL) {
XTRN_CLEANUP;
errormsg(WHERE, ERR_CREATE, str, 0);
return(GetLastError());
}
sprintf(str,"\\\\.\\mailslot\\sbbsexec\\rd%d"
,cfg.node_num);
rdslot=CreateMailslot(str
,sizeof(buf)/2 // Maximum message size (0=unlimited)
,0 // Read time-out
,NULL); // Security
if(rdslot==INVALID_HANDLE_VALUE) {
XTRN_CLEANUP;
errormsg(WHERE, ERR_CREATE, str, 0);
return(GetLastError());
}
}
}
if(startup_dir!=NULL && startup_dir[0])
p_startup_dir=startup_dir;
else
p_startup_dir=NULL;
STARTUPINFO startup_info={0};
startup_info.cb=sizeof(startup_info);
if(mode&EX_OFFLINE)
startup_info.lpTitle=NULL;
else {
SAFEPRINTF3(title,"%s running %s on node %d"
,useron.number ? useron.alias : "Event"
,realcmdline
,cfg.node_num);
startup_info.lpTitle=title;
}
if(startup->options&BBS_OPT_XTRN_MINIMIZED) {
startup_info.wShowWindow=SW_SHOWMINNOACTIVE;
startup_info.dwFlags|=STARTF_USESHOWWINDOW;
}
if(use_pipes) {
// Set up the security attributes struct.
SECURITY_ATTRIBUTES sa;
memset(&sa,0,sizeof(sa));
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
// Create the child output pipe (override default 4K buffer size)
if(!CreatePipe(&rdoutpipe,&startup_info.hStdOutput,&sa,sizeof(buf))) {
errormsg(WHERE,ERR_CREATE,"stdout pipe",0);
strListFreeBlock(env_block);
return(GetLastError());
}
startup_info.hStdError=startup_info.hStdOutput;
// Create the child input pipe.
if(!CreatePipe(&startup_info.hStdInput,&wrinpipe,&sa,sizeof(buf))) {
errormsg(WHERE,ERR_CREATE,"stdin pipe",0);
CloseHandle(rdoutpipe);
strListFreeBlock(env_block);
return(GetLastError());
}
DuplicateHandle(
GetCurrentProcess(), rdoutpipe,
GetCurrentProcess(), &rdslot, 0, FALSE, DUPLICATE_SAME_ACCESS);
DuplicateHandle(
GetCurrentProcess(), wrinpipe,
GetCurrentProcess(), &wrslot, 0, FALSE, DUPLICATE_SAME_ACCESS);
CloseHandle(rdoutpipe);
CloseHandle(wrinpipe);
startup_info.dwFlags|=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
startup_info.wShowWindow=SW_HIDE;
}
if(native && !(mode&EX_OFFLINE)) {
if(!(mode&EX_STDIN)) {
if(passthru_thread_running)
passthru_socket_activate(true);
else
pthread_mutex_lock(&input_thread_mutex);
}
}
success=CreateProcess(
NULL, // pointer to name of executable module
fullcmdline, // pointer to command line string
NULL, // process security attributes
NULL, // thread security attributes
native && !(mode&EX_OFFLINE), // handle inheritance flag
CREATE_NEW_CONSOLE/*|CREATE_SEPARATE_WOW_VDM*/, // creation flags
env_block, // pointer to new environment block
p_startup_dir, // pointer to current directory name
&startup_info, // pointer to STARTUPINFO
&process_info // pointer to PROCESS_INFORMATION
);
strListFreeBlock(env_block);
if(!success) {
XTRN_CLEANUP;
if(!(mode&EX_STDIN)) {
if(passthru_thread_running)
passthru_socket_activate(false);
else
pthread_mutex_unlock(&input_thread_mutex);
}
SetLastError(last_error); /* Restore LastError */
errormsg(WHERE, ERR_EXEC, realcmdline, mode);
SetLastError(last_error); /* Restore LastError */
return(GetLastError());
}
#if 0
char dbgstr[256];
sprintf(dbgstr,"Node %d created: hProcess %X hThread %X processID %X threadID %X\n"
,cfg.node_num
,process_info.hProcess
,process_info.hThread
,process_info.dwProcessId
,process_info.dwThreadId);
OutputDebugString(dbgstr);
#endif
CloseHandle(process_info.hThread);
/* Disable Ctrl-C checking */
if(!(mode&EX_OFFLINE))
rio_abortable=false;
// Executing app in foreground?, monitor
retval=STILL_ACTIVE;
while(!(mode&EX_BG)) {
if(mode&EX_CHKTIME)
gettimeleft();
if(!online && !(mode&EX_OFFLINE)) { // Tell VXD/VDD and external that user hung-up
if(was_online) {
logline(LOG_NOTICE,"X!","hung-up in external program");
hungup=time(NULL);
if(!native) {
SetEvent(hungup_event);
}
was_online=false;
}
if(hungup && time(NULL)-hungup>5 && !processTerminated) {
lprintf(LOG_INFO,"Terminating process from line %d", __LINE__);
processTerminated=TerminateProcess(process_info.hProcess, 2112);
}
}
if((native && !use_pipes) || mode&EX_OFFLINE) {
/* Monitor for process termination only */
if(WaitForSingleObject(process_info.hProcess,1000)==WAIT_OBJECT_0)
break;
} else {
/* Write to VDD */
wr=RingBufPeek(&inbuf,buf,sizeof(buf));
if(wr) {
if(!use_pipes && wrslot==INVALID_HANDLE_VALUE) {
sprintf(str,"\\\\.\\mailslot\\sbbsexec\\wr%d"
,cfg.node_num);
wrslot=CreateFile(str
,GENERIC_WRITE
,FILE_SHARE_READ
,NULL
,OPEN_EXISTING
,FILE_ATTRIBUTE_NORMAL
,(HANDLE) NULL);
if(wrslot==INVALID_HANDLE_VALUE)
lprintf(LOG_DEBUG,"!ERROR %u (%s) opening %s", GetLastError(), strerror(errno), str);
else
lprintf(LOG_DEBUG,"CreateFile(%s)=0x%x", str, wrslot);
}
/* CR expansion */
if(use_pipes)
bp=cr_expand(buf,wr,output_buf,wr);
else
bp=buf;
len=0;
if(wrslot==INVALID_HANDLE_VALUE)
lprintf(LOG_WARNING,"VDD Open failed (not loaded yet?)");
else if(!WriteFile(wrslot,bp,wr,&len,NULL)) {
lprintf(LOG_ERR,"!VDD WriteFile(0x%x, %u) FAILURE (Error=%u)", wrslot, wr, GetLastError());
if(GetMailslotInfo(wrslot,&wr,NULL,NULL,NULL))
lprintf(LOG_DEBUG,"!VDD MailSlot max_msg_size=%u", wr);
else
lprintf(LOG_DEBUG,"!GetMailslotInfo(0x%x)=%u", wrslot, GetLastError());
} else {
if(len!=wr)
lprintf(LOG_WARNING,"VDD short write (%u instead of %u)", len,wr);
RingBufRead(&inbuf, NULL, len);
if(use_pipes && !(mode&EX_NOECHO)) {
/* echo */
RingBufWrite(&outbuf, bp, len);
}
}
wr=len;
}
/* Read from VDD */
rd=0;
len=sizeof(buf);
avail=RingBufFree(&outbuf)/2; // leave room for wwiv/telnet expansion
#if 0
if(avail==0)
lprintf("Node %d !output buffer full (%u bytes)"
,cfg.node_num,RingBufFull(&outbuf));
#endif
if(len>avail)
len=avail;
while(rd<len) {
unsigned long waiting=0;
if(use_pipes)
PeekNamedPipe(
rdslot, // handle to pipe to copy from
NULL, // pointer to data buffer
0, // size, in bytes, of data buffer
NULL, // pointer to number of bytes read
&waiting, // pointer to total number of bytes available
NULL // pointer to unread bytes in this message
);
else
GetMailslotInfo(
rdslot, // mailslot handle
NULL, // address of maximum message size
NULL, // address of size of next message
&waiting, // address of number of messages
NULL // address of read time-out
);
if(!waiting)
break;
if(ReadFile(rdslot,buf+rd,len-rd,&msglen,NULL)==FALSE || msglen<1)
break;
rd+=msglen;
}
if(rd) {
if(mode&EX_WWIV) {
bp=wwiv_expand(buf, rd, wwiv_buf, rd, useron.misc, wwiv_flag);
if(rd>sizeof(wwiv_buf))
lprintf(LOG_ERR,"WWIV_BUF OVERRUN");
} else if(telnet_mode&TELNET_MODE_OFF) {
bp=buf;
} else {
rd = telnet_expand(buf, rd, telnet_buf, sizeof(telnet_buf), /* expand_cr: */false, &bp);
}
if(rd>RingBufFree(&outbuf)) {
lprintf(LOG_ERR,"output buffer overflow");
rd=RingBufFree(&outbuf);
}
if(mode&EX_BIN)
RingBufWrite(&outbuf, bp, rd);
else
rputs((char*)bp, rd);
}
#if defined(_DEBUG) && 0
if(rd>1) {
sprintf(str,"Node %d read %5d bytes from xtrn", cfg.node_num, rd);
OutputDebugString(str);
}
#endif
if((!rd && !wr) || hungup) {
loop_since_io++; /* number of loop iterations with no I/O */
/* only check process termination after 300 milliseconds of no I/O */
/* to allow for last minute reception of output from DOS programs */
if(loop_since_io>=3) {
if(online && hangup_event!=NULL
&& WaitForSingleObject(hangup_event,0)==WAIT_OBJECT_0) {
lprintf(LOG_NOTICE,"Node %d External program requested hangup (dropped DTR)"
,cfg.node_num);
hangup();
}
if(WaitForSingleObject(process_info.hProcess,0)==WAIT_OBJECT_0)
break; /* Process Terminated */
}
/* only check node for interrupt flag every 3 seconds of no I/O */
if((loop_since_io%30)==0) {
// Check if the node has been interrupted
getnodedat(cfg.node_num,&thisnode,0);
if(thisnode.misc&NODE_INTR)
break;
}
/* only send telnet GA every 30 seconds of no I/O */
if((loop_since_io%300)==0) {
#if defined(_DEBUG)
sprintf(str,"Node %d xtrn idle\n",cfg.node_num);
OutputDebugString(str);
#endif
// Let's make sure the socket is up
// Sending will trigger a socket d/c detection
if(!(startup->options&BBS_OPT_NO_TELNET_GA))
send_telnet_cmd(TELNET_GA,0);
}
sem_trywait_block(&inbuf.sem,100);
} else
loop_since_io=0;
}
}
if(!(mode&EX_BG)) { /* !background execution */
if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE)
errormsg(WHERE, ERR_CHK, "ExitCodeProcess",(DWORD)process_info.hProcess);
if(retval==STILL_ACTIVE) {
lprintf(LOG_INFO,"Node %d Terminating process from line %d",cfg.node_num,__LINE__);
TerminateProcess(process_info.hProcess, GetLastError());
}
// Get return value
if(!native) {
sprintf(str,"%sDOSXTRN.RET", cfg.node_dir);
FILE* fp=fopen(str,"r");
if(fp!=NULL) {
if(fscanf(fp,"%d",&retval) != 1) {
lprintf(LOG_ERR, "Node %d Error reading return value from %s", cfg.node_num, str);
retval = -1;
}
fclose(fp);
}
}
}
XTRN_CLEANUP;
CloseHandle(process_info.hProcess);
if(!(mode&EX_OFFLINE)) { /* !off-line execution */
if(native) {
if(!(mode&EX_STDIN)) {
if(passthru_thread_running)
passthru_socket_activate(false);
else
pthread_mutex_unlock(&input_thread_mutex);
}
}
curatr=~0; // Can't guarantee current attributes
attr(LIGHTGRAY); // Force to "normal"
rio_abortable=rio_abortable_save; // Restore abortable state
/* Got back to Text/NVT mode */
request_telnet_opt(TELNET_DONT,TELNET_BINARY_TX);
}
// lprintf("%s returned %d",realcmdline, retval);
errorlevel = retval; // Baja or JS retrievable error value
return(retval);
}
#else /* !WIN32 */
/*****************************************************************************/
// Expands Unix LF to CRLF
/*****************************************************************************/
BYTE* lf_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
{
ulong i,j;
for(i=j=0;i<inlen;i++) {
if(inbuf[i]=='\n' && (!i || inbuf[i-1]!='\r'))
outbuf[j++]='\r';
outbuf[j++]=inbuf[i];
}
newlen=j;
return(outbuf);
}
#define MAX_ARGS 1000
#ifdef NEEDS_SETENV
static int setenv(const char *name, const char *value, int overwrite)
{
char *envstr;
char *oldenv;
if(overwrite || getenv(name)==NULL) {
envstr=(char *)malloc(strlen(name)+strlen(value)+2);
if(envstr==NULL) {
errno=ENOMEM;
return(-1);
}
/* Note, on some platforms, this can be free()d... */
sprintf(envstr,"%s=%s",name,value);
putenv(envstr);
}
return(0);
}
#endif
#ifdef NEEDS_CFMAKERAW
void
cfmakeraw(struct termios *t)
{
t->c_iflag &= ~(IMAXBEL|IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
t->c_oflag &= ~OPOST;
t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
t->c_cflag &= ~(CSIZE|PARENB);
t->c_cflag |= CS8;
}
#endif
#ifdef NEEDS_FORKPTY
static int login_tty(int fd)
{
(void) setsid();
if (!isatty(fd))
return (-1);
(void) dup2(fd, 0);
(void) dup2(fd, 1);
(void) dup2(fd, 2);
if (fd > 2)
(void) close(fd);
return (0);
}
#ifdef NEEDS_DAEMON
/****************************************************************************/
/* Daemonizes the process */
/****************************************************************************/
int
daemon(int nochdir, int noclose)
{
int fd;
switch (fork()) {
case -1:
return (-1);
case 0:
break;
default:
_exit(0);
}
if (setsid() == -1)
return (-1);
if (!nochdir)
(void)chdir("/");
if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > 2)
(void)close(fd);
}
return (0);
}
#endif
static int openpty(int *amaster, int *aslave, char *name, struct termios *termp, winsize *winp)
{
char line[] = "/dev/ptyXX";
const char *cp1, *cp2;
int master, slave, ttygid;
struct group *gr;
if ((gr = getgrnam("tty")) != NULL)
ttygid = gr->gr_gid;
else
ttygid = -1;
for (cp1 = "pqrsPQRS"; *cp1; cp1++) {
line[8] = *cp1;
for (cp2 = "0123456789abcdefghijklmnopqrstuv"; *cp2; cp2++) {
line[5] = 'p';
line[9] = *cp2;
if ((master = open(line, O_RDWR, 0)) == -1) {
if (errno == ENOENT)
break; /* try the next pty group */
} else {
line[5] = 't';
(void) chown(line, getuid(), ttygid);
(void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
/* Hrm... SunOS doesn't seem to have revoke
(void) revoke(line); */
if ((slave = open(line, O_RDWR, 0)) != -1) {
*amaster = master;
*aslave = slave;
if (name)
strcpy(name, line);
if (termp)
(void) tcsetattr(slave,
TCSAFLUSH, termp);
if (winp)
(void) ioctl(slave, TIOCSWINSZ,
(char *)winp);
return (0);
}
(void) close(master);
}
}
}
errno = ENOENT; /* out of ptys */
return (-1);
}
static int forkpty(int *amaster, char *name, termios *termp, winsize *winp)
{
int master, slave, pid;
if (openpty(&master, &slave, name, termp, winp) == -1)
return (-1);
switch (pid = FORK()) {
case -1:
return (-1);
case 0:
/*
* child
*/
(void) close(master);
login_tty(slave);
return (0);
}
/*
* parent
*/
*amaster = master;
(void) close(slave);
return (pid);
}
#endif /* NEED_FORKPTY */
int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
{
char str[MAX_PATH+1];
char fname[MAX_PATH+1];
char fullpath[MAX_PATH+1];
char fullcmdline[MAX_PATH+1];
char* argv[MAX_ARGS + 1];
BYTE* bp;
BYTE buf[XTRN_IO_BUF_LEN];
BYTE output_buf[XTRN_IO_BUF_LEN*2];
ulong avail;
ulong output_len;
bool native=false; // DOS program by default
bool rio_abortable_save=rio_abortable;
int i;
bool data_waiting;
int rd;
int wr;
int argc;
pid_t pid;
int in_pipe[2];
int out_pipe[2];
int err_pipe[2];
fd_set ibits;
int high_fd;
struct timeval timeout;
BYTE wwiv_buf[XTRN_IO_BUF_LEN*2];
bool wwiv_flag=false;
char* p;
xtrn_mode = mode;
lprintf(LOG_DEBUG, "Executing external: %s", cmdline);
if(startup_dir!=NULL && startup_dir[0] && !isdir(startup_dir)) {
errormsg(WHERE, ERR_CHK, startup_dir, 0);
return -1;
}
if(startup_dir==NULL)
startup_dir=nulstr;
XTRN_LOADABLE_MODULE(cmdline,startup_dir);
XTRN_LOADABLE_JS_MODULE(cmdline,mode,startup_dir);
attr(cfg.color[clr_external]); /* setup default attributes */
native = native_executable(&cfg, cmdline, mode);
SAFECOPY(str,cmdline); /* Set fname to program name only */
truncstr(str," ");
SAFECOPY(fname,getfname(str));
sprintf(fullpath,"%s%s",startup_dir,fname);
if(startup_dir!=NULL && cmdline[0]!='/' && cmdline[0]!='.' && fexist(fullpath))
sprintf(fullcmdline,"%s%s",startup_dir,cmdline);
else
SAFECOPY(fullcmdline,cmdline);
if(native) { // Native (not MS-DOS) external
// Current environment passed to child process
sprintf(dszlog,"%sPROTOCOL.LOG",cfg.node_dir);
setenv("DSZLOG",dszlog,1); /* Makes the DSZ LOG active */
setenv("SBBSNODE",cfg.node_dir,1);
setenv("SBBSCTRL",cfg.ctrl_dir,1);
setenv("SBBSDATA",cfg.data_dir,1);
setenv("SBBSEXEC",cfg.exec_dir,1);
sprintf(str,"%u",cfg.node_num);
setenv("SBBSNNUM",str,1);
/* date/time env vars */
now = time(NULL);
struct tm tm;
if(localtime_r(&now, &tm) == NULL)
memset(&tm, 0, sizeof(tm));
sprintf(str," %02u", tm.tm_mday);
setenv("DAY", str, /* overwrite */TRUE);
setenv("WEEKDAY", wday[tm.tm_wday], /* overwrite */TRUE);
setenv("MONTHNAME", mon[tm.tm_mon], /* overwrite */TRUE);
sprintf(str, "%02u", tm.tm_mon + 1);
setenv("MONTH", str, /* overwrite */TRUE);
sprintf(str,"%u", 1900 + tm.tm_year);
if(setenv("YEAR", str, /* overwrite */TRUE) != 0)
errormsg(WHERE,ERR_WRITE,"environment",0);
} else {
if(startup->options&BBS_OPT_NO_DOS) {
lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOS programs not supported: %s", cmdline);
bprintf("Sorry, DOS programs are not supported on this node.\r\n");
return -1;
}
#if defined(__FreeBSD__)
/* ToDo: This seems to work for every door except Iron Ox
ToDo: Iron Ox is unique in that it runs perfectly from
ToDo: tcsh but not at all from anywhere else, complaining
ToDo: about corrupt files. I've ruled out the possibilty
ToDo: of it being a terminal mode issue... no other ideas
ToDo: come to mind. */
FILE * doscmdrc;
sprintf(str,"%s.doscmdrc",cfg.node_dir);
if((doscmdrc=fopen(str,"w+"))==NULL) {
errormsg(WHERE,ERR_CREATE,str,0);
return(-1);
}
if(startup_dir!=NULL && startup_dir[0])
fprintf(doscmdrc,"assign C: %s\n",startup_dir);
else
fprintf(doscmdrc,"assign C: .\n");
fprintf(doscmdrc,"assign D: %s\n",cfg.node_dir);
SAFECOPY(str,cfg.exec_dir);
if((p=strrchr(str,'/'))!=NULL)
*p=0;
if((p=strrchr(str,'/'))!=NULL)
*p=0;
fprintf(doscmdrc,"assign E: %s\n",str);
/* setup doscmd env here */
/* ToDo Note, this assumes that the BBS uses standard dir names */
fprintf(doscmdrc,"DSZLOG=E:\\node%d\\PROTOCOL.LOG\n",cfg.node_num);
fprintf(doscmdrc,"SBBSNODE=D:\\\n");
fprintf(doscmdrc,"SBBSCTRL=E:\\ctrl\\\n");
fprintf(doscmdrc,"SBBSDATA=E:\\data\\\n");
fprintf(doscmdrc,"SBBSEXEC=E:\\exec\\\n");
fprintf(doscmdrc,"SBBSNNUM=%d\n",cfg.node_num);
fprintf(doscmdrc,"PCBNODE=%d\n",cfg.node_num);
fprintf(doscmdrc,"PCBDRIVE=D:\n");
fprintf(doscmdrc,"PCBDIR=\\\n");
fclose(doscmdrc);
SAFECOPY(str,fullcmdline);
sprintf(fullcmdline,"%s -F %s",startup->dosemu_path,str);
#elif defined(__linux__)
/* dosemu integration -- originally by Ryan Underwood, <nemesis @ icequake.net> */
FILE *dosemubatfp;
FILE *externalbatfp;
FILE *de_launch_inifp;
char tok[MAX_PATH+1];
char buf[1024];
char bufout[1024];
char cmdlinebatch[MAX_PATH+1];
char externalbatsrc[MAX_PATH+1];
char externalbat[MAX_PATH+1];
char dosemuconf[MAX_PATH+1];
char de_launch_cmd[INI_MAX_VALUE_LEN];
char dosemubinloc[MAX_PATH+1];
char virtualconf[75];
char dosterm[15];
char log_external[MAX_PATH+1];
const char* runtype;
str_list_t de_launch_ini;
/* on the Unix side. xtrndir is the parent of the door's startup dir. */
char xtrndir[MAX_PATH+1];
/* on the DOS side. */
char xtrndir_dos[MAX_PATH+1];
char ctrldir_dos[MAX_PATH+1];
char datadir_dos[MAX_PATH+1];
char execdir_dos[MAX_PATH+1];
char nodedir_dos[MAX_PATH+1];
/* Default locations that can be overridden by
* the sysop in emusetup.bat */
const char ctrldrive[] = DOSEMU_CTRL_DRIVE;
const char datadrive[] = DOSEMU_DATA_DRIVE;
const char execdrive[] = DOSEMU_EXEC_DRIVE;
const char nodedrive[] = DOSEMU_NODE_DRIVE;
const char external_bat_fn[] = "external.bat";
const char dosemu_cnf_fn[] = "dosemu.conf";
SAFECOPY(str,startup_dir);
if(*(p=lastchar(str))=='/') /* kill trailing slash */
*p=0;
if((p=strrchr(str,'/'))!=NULL) /* kill the last element of the path */
*p=0;
SAFECOPY(xtrndir,str);
SAFECOPY(xtrndir_dos,xtrndir);
REPLACE_CHARS(xtrndir_dos,'/','\\',p);
SAFECOPY(ctrldir_dos,cfg.ctrl_dir);
REPLACE_CHARS(ctrldir_dos,'/','\\',p);
p=lastchar(ctrldir_dos);
if (*p=='\\') *p=0;
SAFECOPY(datadir_dos,cfg.data_dir);
REPLACE_CHARS(datadir_dos,'/','\\',p);
p=lastchar(datadir_dos);
if (*p=='\\') *p=0;
SAFECOPY(execdir_dos,cfg.exec_dir);
REPLACE_CHARS(execdir_dos,'/','\\',p);
p=lastchar(execdir_dos);
if (*p=='\\') *p=0;
SAFECOPY(nodedir_dos,cfg.node_dir);
REPLACE_CHARS(nodedir_dos,'/','\\',p);
p=lastchar(nodedir_dos);
if (*p=='\\') *p=0;
/* must have sbbs.ini bbs useDOSemu=1 (or empty), cannot be =0 */
if (!startup->usedosemu) {
lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOSEMU disabled, program not run");
bprintf("Sorry, DOSEMU is not supported on this node.\r\n");
return -1;
}
/* must have sbbs.ini bbs DOSemuPath set to valid path */
SAFECOPY(dosemubinloc,(cmdstr(startup->dosemu_path,nulstr,nulstr,tok)));
if (dosemubinloc[0] == '\0') {
lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOSEMU invalid DOSEmuPath, program not run");
bprintf("Sorry, DOSEMU is not supported on this node.\r\n");
return -1;
}
if (!fexist(dosemubinloc)) {
lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOSEMU not found: %s", dosemubinloc);
bprintf("Sorry, DOSEMU is not supported on this node.\r\n");
return -1;
}
/* check for existence of a dosemu.conf in the door directory.
* It is a good idea to be able to use separate configs for each
* door.
*
* First check startup_dir, then check cfg.ctrl_dir
*/
SAFEPRINTF2(str,"%s%s",startup_dir, dosemu_cnf_fn);
if (!fexist(str)) {
/* If we can't find it in the door dir, look for the configured one */
SAFECOPY(str,startup->dosemuconf_path);
if (!isabspath(str)) {
SAFEPRINTF2(str,"%s%s", cfg.ctrl_dir, startup->dosemuconf_path);
}
if (!fexist(str)) {
/* If we couldn't find either, try for the system one, then
* error out. */
SAFEPRINTF(str,"/etc/dosemu/%s", dosemu_cnf_fn);
if (!fexist(str)) {
SAFEPRINTF(str,"/etc/%s", dosemu_cnf_fn);
if (!fexist(str)) {
errormsg(WHERE,ERR_READ,str,0);
return(-1);
}
else SAFECOPY(dosemuconf,str); /* using system conf */
}
else SAFECOPY(dosemuconf,str); /* using system conf */
}
else SAFECOPY(dosemuconf,str); /* using global conf */
}
else SAFECOPY(dosemuconf,str); /* using door-specific conf */
/* Create the external bat here to be placed in the node dir. */
SAFEPRINTF2(str,"%s%s",cfg.node_dir,external_bat_fn);
if(!(dosemubatfp=fopen(str,"w+"))) {
errormsg(WHERE,ERR_CREATE,str,0);
return(-1);
}
fprintf(dosemubatfp,"@ECHO OFF\r\n");
fprintf(dosemubatfp,"SET DSZLOG=%s\\PROTOCOL.LOG\r\n",nodedrive);
fprintf(dosemubatfp,"SET SBBSNODE=%s\r\n",nodedrive);
fprintf(dosemubatfp,"SET SBBSNNUM=%d\r\n",cfg.node_num);
fprintf(dosemubatfp,"SET SBBSCTRL=%s\r\n",ctrldrive);
fprintf(dosemubatfp,"SET SBBSDATA=%s\r\n",datadrive);
fprintf(dosemubatfp,"SET SBBSEXEC=%s\r\n",execdrive);
fprintf(dosemubatfp,"SET PCBNODE=%d\r\n",cfg.node_num);
fprintf(dosemubatfp,"SET PCBDRIVE=%s\r\n",nodedrive);
fprintf(dosemubatfp,"SET PCBDIR=\\\r\n");
char gamedir[MAX_PATH+1];
if(startup_dir!=NULL && startup_dir[0]) {
SAFECOPY(str, startup_dir);
*lastchar(str) = 0;
SAFECOPY(gamedir, getfname(str));
}
if(*gamedir == 0) {
lprintf(LOG_ERR, "No startup directory configured for DOS command-line: %s", cmdline);
fclose(dosemubatfp);
return -1;
}
/* external editors use node dir so unset this */
if (startup_dir == cfg.node_dir) {
*gamedir = '\0';
}
fprintf(dosemubatfp,"SET STARTDIR=%s\r\n",gamedir);
/* Check the "Stdio Interception" flag from scfg for this door. If it's
* enabled, we enable doorway mode. Else, it's vmodem for us, unless
* it's a timed event.
*/
if (!(mode&(EX_STDIO)) && online!=ON_LOCAL) {
SAFECOPY(virtualconf,"-I\"serial { virtual com 1 }\"");
runtype = "FOSSIL";
} else {
virtualconf[0] = '\0';
runtype = "STDIO";
}
/* now append exec/external.bat (which is editable) to this
generated file */
SAFEPRINTF2(str,"%s%s",startup_dir,external_bat_fn);
if ((startup_dir == cfg.node_dir) || !fexist(str)) {
SAFEPRINTF2(str,"%s%s",cfg.exec_dir, external_bat_fn);
if (!fexist(str)) {
errormsg(WHERE,ERR_READ,str,0);
fclose(dosemubatfp);
return(-1);
}
}
SAFECOPY(externalbatsrc, str);
if (!(externalbatfp=fopen(externalbatsrc,"r"))) {
errormsg(WHERE,ERR_OPEN,externalbatsrc,0);
fclose(dosemubatfp);
return(-1);
}
/* append the command line to the batch file */
SAFECOPY(tok,cmdline);
truncstr(tok," ");
p = getfext(tok);
/* check if it's a bat file */
if (p != NULL && stricmp(p, ".bat") == 0) {
SAFEPRINTF(cmdlinebatch, "CALL %s", cmdline);
} else {
SAFECOPY(cmdlinebatch, cmdline);
}
named_string_t externalbat_replacements[] = {
{(char*)"CMDLINE", cmdlinebatch},
{(char*)"DSZLOG", (char*)nodedrive},
{(char*)"SBBSNODE", (char*)nodedrive},
{(char*)"SBBSCTRL", (char*)ctrldrive},
{(char*)"SBBSDATA", (char*)datadrive},
{(char*)"SBBSEXEC", (char*)execdrive},
{(char*)"XTRNDIR", xtrndir_dos},
{(char*)"CTRLDIR", ctrldir_dos},
{(char*)"DATADIR", datadir_dos},
{(char*)"EXECDIR", execdir_dos},
{(char*)"NODEDIR", nodedir_dos},
{(char*)"STARTDIR", (char*)gamedir},
{(char*)"RUNTYPE", (char *)runtype},
{NULL, NULL}
};
named_int_t externalbat_int_replacements[] = {
{(char*)"SBBSNNUM", cfg.node_num },
};
while(!feof(externalbatfp)) {
if (fgets(buf, sizeof(buf), externalbatfp)!=NULL) {
replace_named_values(buf, bufout, sizeof(bufout), "$", externalbat_replacements,
externalbat_int_replacements, FALSE);
fprintf(dosemubatfp,"%s",bufout);
}
}
fclose(externalbatfp);
/* Set the interception bits, since we are always going to want Synchronet
* to intercept dos programs under Unix.
*/
mode |= EX_STDIO;
/* Attempt to keep dosemu from prompting for a disclaimer. */
sprintf(str, "%s/.dosemu", cfg.ctrl_dir);
if (!isdir(str)) {
mkdir(str, 0755);
}
strcat(str, "/disclaimer");
ftouch(str);
/* Set up the command for dosemu to execute with 'unix -e'. */
SAFEPRINTF2(externalbat,"%s%s",nodedrive, external_bat_fn);
/* need TERM=linux for maintenance programs to work
* (dosemu won't start with no controlling terminal)
* Also, redirect stdout to a log if it's a timed event.
*/
if (online==ON_LOCAL) {
SAFECOPY(dosterm,"TERM=linux");
safe_snprintf(log_external, sizeof(log_external), ">> %sdosevent_%s.log",cfg.logs_dir,fname);
}
else {
dosterm[0]='\0';
log_external[0] = '\0';
}
/*
* Get the global emu launch command
*/
/* look for file in startup dir */
SAFEPRINTF(str,"%sdosemu.ini",startup_dir);
if (!fexist(str)) {
/* look for file in exec dir */
SAFEPRINTF(str,"%sdosemu.ini",cfg.exec_dir);
if (!fexist(str)) {
errormsg(WHERE,ERR_OPEN,"dosemu.ini", 0);
fclose(dosemubatfp);
return(-1);
}
}
/* if file found, then open and process it */
if ((de_launch_inifp=iniOpenFile(str, false))==NULL) {
errormsg(WHERE,ERR_OPEN,str, 0);
fclose(dosemubatfp);
return(-1);
}
de_launch_ini = iniReadFile(de_launch_inifp);
iniCloseFile(de_launch_inifp);
SAFECOPY(de_launch_cmd, "");
iniGetString(de_launch_ini, ROOT_SECTION, "cmd", nulstr, de_launch_cmd);
if (virtualconf[0] == '\0') {
iniGetString(de_launch_ini, "stdio", "cmd", de_launch_cmd, de_launch_cmd);
}
iniFreeStringList(de_launch_ini);
named_string_t de_ini_replacements[] =
{
{(char*)"TERM", dosterm},
{(char*)"CTRLDIR", cfg.ctrl_dir},
{(char*)"NODEDIR", cfg.node_dir},
{(char*)"DOSEMUBIN", dosemubinloc},
{(char*)"VIRTUALCONF", virtualconf},
{(char*)"DOSEMUCONF", dosemuconf},
{(char*)"EXTBAT", externalbat},
{(char*)"EXTLOG", log_external},
{(char*)"RUNTYPE", (char *)runtype},
{NULL, NULL}
};
named_int_t de_ini_int_replacements[] = {
{(char*)"NNUM", cfg.node_num },
};
replace_named_values(de_launch_cmd, fullcmdline, sizeof(fullcmdline), (char*)"$",
de_ini_replacements, de_ini_int_replacements, FALSE);
/* Drum roll. */
fprintf(dosemubatfp,"REM For debugging: %s\r\n",fullcmdline);
fclose(dosemubatfp);
#else
lprintf((mode&EX_OFFLINE) ? LOG_ERR : LOG_WARNING, "DOS programs not supported: %s", cmdline);
bprintf("Sorry, DOS programs are not supported on this node.\r\n");
return(-1);
#endif
}
if(!(mode&EX_STDIN)) {
if(!(mode&EX_STDIN)) {
if(passthru_thread_running)
passthru_socket_activate(true);
else
pthread_mutex_lock(&input_thread_mutex);
}
}
if(!(mode&EX_NOLOG) && pipe(err_pipe)!=0) {
errormsg(WHERE,ERR_CREATE,"err_pipe",0);
return(-1);
}
if((mode&EX_STDIO)==EX_STDIO) {
struct winsize winsize;
struct termios term;
memset(&term,0,sizeof(term));
cfsetispeed(&term,B19200);
cfsetospeed(&term,B19200);
if(mode&EX_BIN)
cfmakeraw(&term);
else {
term.c_iflag = TTYDEF_IFLAG;
term.c_oflag = TTYDEF_OFLAG;
term.c_lflag = TTYDEF_LFLAG;
term.c_cflag = TTYDEF_CFLAG;
memcpy(term.c_cc,ttydefchars,sizeof(term.c_cc));
}
winsize.ws_row=rows;
winsize.ws_col=cols;
if((pid=forkpty(&in_pipe[1],NULL,&term,&winsize))==-1) {
if(!(mode&EX_STDIN)) {
if(passthru_thread_running)
passthru_socket_activate(false);
else
pthread_mutex_unlock(&input_thread_mutex);
}
errormsg(WHERE,ERR_EXEC,fullcmdline,0);
return(-1);
}
out_pipe[0]=in_pipe[1];
}
else {
if(mode&EX_STDIN)
if(pipe(in_pipe)!=0) {
errormsg(WHERE,ERR_CREATE,"in_pipe",0);
return(-1);
}
if(mode&EX_STDOUT)
if(pipe(out_pipe)!=0) {
errormsg(WHERE,ERR_CREATE,"out_pipe",0);
return(-1);
}
if((pid=FORK())==-1) {
if(!(mode&EX_STDIN)) {
if(passthru_thread_running)
passthru_socket_activate(false);
else
pthread_mutex_unlock(&input_thread_mutex);
}
errormsg(WHERE,ERR_EXEC,fullcmdline,0);
return(-1);
}
}
if(pid==0) { /* child process */
/* Give away all privs for good now */
if(startup->setuid!=NULL)
startup->setuid(TRUE);
sigset_t sigs;
sigfillset(&sigs);
sigprocmask(SIG_UNBLOCK,&sigs,NULL);
if(!(mode&EX_BIN)) {
if(term_supports(ANSI))
SAFEPRINTF(term_env,"TERM=%s",startup->xtrn_term_ansi);
else
SAFEPRINTF(term_env,"TERM=%s",startup->xtrn_term_dumb);
putenv(term_env);
}
#ifdef __FreeBSD__
if(!native)
chdir(cfg.node_dir);
else
#endif
if(startup_dir!=NULL && startup_dir[0])
if(chdir(startup_dir)!=0) {
errormsg(WHERE,ERR_CHDIR,startup_dir,0);
return(-1);
}
if(mode&EX_SH || strcspn(fullcmdline,"<>|;\"")!=strlen(fullcmdline)) {
argv[0]=comspec;
argv[1]=(char*)"-c";
argv[2]=fullcmdline;
argv[3]=NULL;
} else {
argv[0]=fullcmdline; /* point to the beginning of the string */
argc=1;
for(i=0;fullcmdline[i] && argc<MAX_ARGS;i++) /* Break up command line */
if(fullcmdline[i]==' ') {
fullcmdline[i]=0; /* insert nulls */
argv[argc++]=fullcmdline+i+1; /* point to the beginning of the next arg */
}
argv[argc]=NULL;
}
if(mode&EX_STDIN && !(mode&EX_STDOUT)) {
close(in_pipe[1]); /* close write-end of pipe */
dup2(in_pipe[0],0); /* redirect stdin */
close(in_pipe[0]); /* close excess file descriptor */
}
if(mode&EX_STDOUT && !(mode&EX_STDIN)) {
close(out_pipe[0]); /* close read-end of pipe */
dup2(out_pipe[1],1); /* stdout */
if(!(mode&EX_NOLOG))
dup2(out_pipe[1],2); /* stderr */
close(out_pipe[1]); /* close excess file descriptor */
}
if(!(mode & EX_STDIO)) {
int fd;
/* Redirect stdio to /dev/null */
if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
if(!(mode&EX_NOLOG))
dup2(fd, STDERR_FILENO);
if (fd > 2)
close(fd);
}
}
if(mode&EX_BG) /* background execution, detach child */
{
lprintf(LOG_INFO,"Detaching external process");
daemon(TRUE,FALSE);
}
if(!(mode&EX_NOLOG)) {
close(err_pipe[0]); /* close read-end of pipe */
dup2(err_pipe[1],2); /* stderr */
}
execvp(argv[0],argv);
lprintf(LOG_ERR,"!ERROR %d (%s) executing: %s", errno, strerror(errno), argv[0]);
_exit(-1); /* should never get here */
}
if(strcmp(cmdline, fullcmdline) != 0)
lprintf(LOG_DEBUG,"Executing cmd-line: %s", fullcmdline);
/* Disable Ctrl-C checking */
if(!(mode&EX_OFFLINE))
rio_abortable=false;
if(!(mode&EX_NOLOG))
close(err_pipe[1]); /* close write-end of pipe */
if(mode&EX_STDOUT) {
if(!(mode&EX_STDIN))
close(out_pipe[1]); /* close write-end of pipe */
while(!terminated) {
if(waitpid(pid, &i, WNOHANG)!=0) /* child exited */
break;
if(mode&EX_CHKTIME)
gettimeleft();
if(!online && !(mode&EX_OFFLINE)) {
logline(LOG_NOTICE,"X!","hung-up in external program");
break;
}
/* Input */
if(mode&EX_STDIN && RingBufFull(&inbuf)) {
if((wr=RingBufRead(&inbuf,buf,sizeof(buf)))!=0)
write(in_pipe[1],buf,wr);
}
/* Error Output */
FD_ZERO(&ibits);
if(!(mode&EX_NOLOG)) {
FD_SET(err_pipe[0],&ibits);
high_fd=err_pipe[0];
}
FD_SET(out_pipe[0],&ibits);
if(!(mode&EX_NOLOG)) {
if(out_pipe[0]>err_pipe[0])
high_fd=out_pipe[0];
} else
high_fd=out_pipe[0];
timeout.tv_sec=0;
timeout.tv_usec=1000;
bp=buf;
i=0;
if(mode&EX_NOLOG)
select(high_fd+1,&ibits,NULL,NULL,&timeout);
else {
while ((select(high_fd+1,&ibits,NULL,NULL,&timeout)>0) && FD_ISSET(err_pipe[0],&ibits) && (i<(int)sizeof(buf)-1)) {
if((rd=read(err_pipe[0],bp,1))>0) {
i+=rd;
bp++;
if(*(bp-1)=='\n')
break;
}
else
break;
FD_ZERO(&ibits);
FD_SET(err_pipe[0],&ibits);
FD_SET(out_pipe[0],&ibits);
timeout.tv_sec=0;
timeout.tv_usec=1000;
}
if(i > 0) {
buf[i] = '\0';
p = (char*)buf;
truncsp(p);
SKIP_WHITESPACE(p);
if(*p)
lprintf(LOG_NOTICE, "%s: %s", fname, p);
}
/* Eat stderr if mode is EX_BIN */
if(mode&EX_BIN) {
bp=buf;
i=0;
}
}
data_waiting=FD_ISSET(out_pipe[0],&ibits);
if(i==0 && data_waiting==0)
continue;
avail=(RingBufFree(&outbuf)-i)/2; // Leave room for wwiv/telnet expansion
if(avail==0) {
#if 0
lprintf("Node %d !output buffer full (%u bytes)"
,cfg.node_num,RingBufFull(&outbuf));
#endif
YIELD();
continue;
}
rd=avail;
if(rd>((int)sizeof(buf)-i))
rd=sizeof(buf)-i;
if(data_waiting) {
rd=read(out_pipe[0],bp,rd);
if(rd<1 && i==0)
continue;
if(rd<0)
rd=0;
}
else
rd=0;
rd += i;
if(mode&EX_BIN) {
if(telnet_mode&TELNET_MODE_OFF) {
bp=buf;
output_len=rd;
}
else
output_len = telnet_expand(buf, rd, output_buf, sizeof(output_buf), /* expand_cr: */false, &bp);
} else {
if ((mode & EX_STDIO) != EX_STDIO) {
/* LF to CRLF expansion */
bp=lf_expand(buf, rd, output_buf, output_len);
} else if(mode&EX_WWIV) {
bp=wwiv_expand(buf, rd, wwiv_buf, output_len, useron.misc, wwiv_flag);
if(output_len > sizeof(wwiv_buf))
lprintf(LOG_ERR, "WWIV_BUF OVERRUN");
} else {
bp=buf;
output_len=rd;
}
if (term_supports(PETSCII))
petscii_convert(bp, output_len);
}
/* Did expansion overrun the output buffer? */
if(output_len>sizeof(output_buf)) {
lprintf(LOG_ERR,"OUTPUT_BUF OVERRUN");
output_len=sizeof(output_buf);
}
/* Does expanded size fit in the ring buffer? */
if(output_len>RingBufFree(&outbuf)) {
lprintf(LOG_ERR,"output buffer overflow");
output_len=RingBufFree(&outbuf);
}
RingBufWrite(&outbuf, bp, output_len);
}
if(waitpid(pid, &i, WNOHANG)==0) { // Child still running?
kill(pid, SIGHUP); // Tell child user has hung up
time_t start=time(NULL); // Wait up to 10 seconds
while(time(NULL)-start<10) { // for child to terminate
if(waitpid(pid, &i, WNOHANG)!=0)
break;
mswait(500);
}
if(waitpid(pid, &i, WNOHANG)==0) // Child still running?
kill(pid, SIGKILL); // terminate child process
}
/* close unneeded descriptors */
if(mode&EX_STDIN)
close(in_pipe[1]);
close(out_pipe[0]);
}
if(mode&EX_NOLOG)
waitpid(pid, &i, 0);
else {
while(waitpid(pid, &i, WNOHANG)==0) {
FD_ZERO(&ibits);
FD_SET(err_pipe[0],&ibits);
timeout.tv_sec=1;
timeout.tv_usec=0;
bp=buf;
i=0;
while ((select(err_pipe[0]+1,&ibits,NULL,NULL,&timeout)>0) && (i<XTRN_IO_BUF_LEN-1)) {
if((rd=read(err_pipe[0],bp,1))>0) {
i+=rd;
if(*bp=='\n') {
buf[i] = '\0';
p = (char*)buf;
truncsp(p);
SKIP_WHITESPACE(p);
if(*p)
lprintf(LOG_NOTICE, "%s: %s", fname, p);
i=0;
bp=buf;
}
else
bp++;
}
else
break;
}
if(i > 0) {
buf[i] = '\0';
p = (char*)buf;
truncsp(p);
SKIP_WHITESPACE(p);
if(*p)
lprintf(LOG_NOTICE, "%s: %s", fname, p);
}
}
}
if(!(mode&EX_OFFLINE)) { /* !off-line execution */
curatr=~0; // Can't guarantee current attributes
attr(LIGHTGRAY); // Force to "normal"
rio_abortable=rio_abortable_save; // Restore abortable state
/* Got back to Text/NVT mode */
request_telnet_opt(TELNET_DONT,TELNET_BINARY_TX);
}
if(!(mode&EX_NOLOG))
close(err_pipe[0]);
if(!(mode&EX_STDIN)) {
if(passthru_thread_running)
passthru_socket_activate(false);
else
pthread_mutex_unlock(&input_thread_mutex);
}
return(errorlevel = WEXITSTATUS(i));
}
#endif /* !WIN32 */
const char* quoted_string(const char* str, char* buf, size_t maxlen)
{
if(strchr(str,' ')==NULL)
return(str);
safe_snprintf(buf,maxlen,"\"%s\"",str);
return(buf);
}
#define QUOTED_STRING(ch, str, buf, maxlen) \
((IS_ALPHA(ch) && IS_UPPERCASE(ch)) ? str : quoted_string(str,buf,maxlen))
/*****************************************************************************/
/* Returns command line generated from instr with %c replacements */
/*****************************************************************************/
char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, char *outstr, long mode)
{
char str[MAX_PATH+1],*cmd;
int i,j,len;
bool native = (mode == EX_UNSPECIFIED) || native_executable(&cfg, instr, mode);
(void) native;
if(outstr==NULL)
cmd=cmdstr_output;
else
cmd=outstr;
len=strlen(instr);
int maxlen = (int)sizeof(cmdstr_output) - 1;
for(i=j=0; i<len && j < maxlen; i++) {
if(instr[i]=='%') {
i++;
cmd[j]=0;
int avail = maxlen - j;
char ch=instr[i];
if(IS_ALPHA(ch))
ch=toupper(ch);
switch(ch) {
case 'A': /* User alias */
strncat(cmd,QUOTED_STRING(instr[i],useron.alias,str,sizeof(str)), avail);
break;
case 'B': /* Baud (DTE) Rate */
strncat(cmd,ultoa(dte_rate,str,10), avail);
break;
case 'C': /* Connect Description */
strncat(cmd,connection, avail);
break;
case 'D': /* Connect (DCE) Rate */
strncat(cmd,ultoa((ulong)cur_rate,str,10), avail);
break;
case 'E': /* Estimated Rate */
strncat(cmd,ultoa((ulong)cur_cps*10,str,10), avail);
break;
case 'F': /* File path */
#if defined(__linux__)
if(!native && strncmp(fpath, cfg.node_dir, strlen(cfg.node_dir)) == 0) {
strncat(cmd, DOSEMU_NODE_DIR, avail);
strncat(cmd, fpath + strlen(cfg.node_dir), avail);
}
else
#endif
strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
break;
case 'G': /* Temp directory */
#if defined(__linux__)
if(!native)
strncat(cmd, DOSEMU_TEMP_DIR, avail);
else
#endif
strncat(cmd,cfg.temp_dir, avail);
break;
case 'H': /* Socket Handle */
strncat(cmd,ultoa(client_socket_dup,str,10), avail);
break;
case 'I': /* IP address */
strncat(cmd,cid, avail);
break;
case 'J':
#if defined(__linux__)
if(!native)
strncat(cmd, DOSEMU_DATA_DIR, avail);
else
#endif
strncat(cmd,cfg.data_dir, avail);
break;
case 'K':
#if defined(__linux__)
if(!native)
strncat(cmd, DOSEMU_CTRL_DIR, avail);
else
#endif
strncat(cmd,cfg.ctrl_dir, avail);
break;
case 'L': /* Lines per message */
strncat(cmd,ultoa(cfg.level_linespermsg[useron.level],str,10), avail);
break;
case 'M': /* Minutes (credits) for user */
strncat(cmd,ultoa(useron.min,str,10), avail);
break;
case 'N': /* Node Directory (same as SBBSNODE environment var) */
#if defined(__linux__)
if(!native)
strncat(cmd, DOSEMU_NODE_DIR, avail);
else
#endif
strncat(cmd,cfg.node_dir, avail);
break;
case 'O': /* SysOp */
strncat(cmd,QUOTED_STRING(instr[i],cfg.sys_op,str,sizeof(str)), avail);
break;
case 'P': /* Client protocol */
strncat(cmd, passthru_thread_running ? "raw" : client.protocol, avail);
break;
case 'Q': /* QWK ID */
strncat(cmd,cfg.sys_id, avail);
break;
case 'R': /* Rows */
strncat(cmd,ultoa(rows,str,10), avail);
break;
case 'S': /* File Spec (or Baja command str) or startup-directory */
strncat(cmd, fspec, avail);
break;
case 'T': /* Time left in seconds */
gettimeleft();
strncat(cmd,ultoa(timeleft,str,10), avail);
break;
case 'U': /* UART I/O Address (in hex) */
strncat(cmd,ultoa(cfg.com_base,str,16), avail);
break;
case 'V': /* Synchronet Version */
sprintf(str,"%s%c",VERSION,REVISION);
strncat(cmd,str, avail);
break;
case 'W': /* Columns (width) */
strncat(cmd,ultoa(cols,str,10), avail);
break;
case 'X':
strncat(cmd,cfg.shell[useron.shell]->code, avail);
break;
case '&': /* Address of msr */
break;
case 'Y':
strncat(cmd,comspec, avail);
break;
case 'Z':
#if defined(__linux__)
if(!native)
strncat(cmd, DOSEMU_TEXT_DIR, avail);
else
#endif
strncat(cmd,cfg.text_dir, avail);
break;
case '~': /* DOS-compatible (8.3) filename */
#ifdef _WIN32
char sfpath[MAX_PATH+1];
SAFECOPY(sfpath,fpath);
GetShortPathName(fpath,sfpath,sizeof(sfpath));
strncat(cmd,sfpath, avail);
#else
strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
#endif
break;
case '!': /* EXEC Directory */
#if defined(__linux__)
if(!native)
strncat(cmd, DOSEMU_EXEC_DIR, avail);
else
#endif
strncat(cmd,cfg.exec_dir, avail);
break;
case '@': /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
strncat(cmd,cfg.exec_dir, avail);
#endif
break;
case '#': /* Node number (same as SBBSNNUM environment var) */
sprintf(str,"%d",cfg.node_num);
strncat(cmd,str, avail);
break;
case '*':
sprintf(str,"%03d",cfg.node_num);
strncat(cmd,str, avail);
break;
case '$': /* Credits */
strncat(cmd,ultoa(useron.cdt+useron.freecdt,str,10), avail);
break;
case '%': /* %% for percent sign */
strncat(cmd,"%", avail);
break;
case '.': /* .exe for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
strncat(cmd,".exe", avail);
#endif
break;
case '?': /* Platform */
#ifdef __OS2__
strcpy(str,"OS2");
#else
strcpy(str,PLATFORM_DESC);
#endif
strlwr(str);
strncat(cmd,str, avail);
break;
case '^': /* Architecture */
strncat(cmd, ARCHITECTURE_DESC, avail);
break;
default: /* unknown specification */
if(IS_DIGIT(instr[i])) {
sprintf(str,"%0*d",instr[i]&0xf,useron.number);
strncat(cmd,str, avail); }
break; }
j=strlen(cmd); }
else
cmd[j++]=instr[i]; }
cmd[j]=0;
return(cmd);
}
/****************************************************************************/
/* Returns command line generated from instr with %c replacments */
/* This is the C-exported version */
/****************************************************************************/
extern "C"
char* DLLCALL cmdstr(scfg_t* cfg, user_t* user, const char* instr, const char* fpath
,const char* fspec, char* cmd)
{
char str[MAX_PATH+1];
int i,j,len;
static char buf[512];
if(cmd==NULL) cmd=buf;
len=strlen(instr);
int maxlen = (int)sizeof(buf) - 1;
for(i=j=0; i<len && j < maxlen; i++) {
if(instr[i]=='%') {
i++;
cmd[j]=0;
int avail = maxlen - j;
char ch=instr[i];
if(IS_ALPHA(ch))
ch=toupper(ch);
switch(ch) {
case 'A': /* User alias */
if(user!=NULL)
strncat(cmd,QUOTED_STRING(instr[i],user->alias,str,sizeof(str)), avail);
break;
case 'B': /* Baud (DTE) Rate */
break;
case 'C': /* Connect Description */
if(user!=NULL)
strncat(cmd,user->modem, avail);
break;
case 'D': /* Connect (DCE) Rate */
break;
case 'E': /* Estimated Rate */
break;
case 'F': /* File path */
strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
break;
case 'G': /* Temp directory */
strncat(cmd,cfg->temp_dir, avail);
break;
case 'H': /* Port Handle or Hardware Flow Control */
break;
case 'I': /* IP address */
if(user!=NULL)
strncat(cmd,user->note, avail);
break;
case 'J':
strncat(cmd,cfg->data_dir, avail);
break;
case 'K':
strncat(cmd,cfg->ctrl_dir, avail);
break;
case 'L': /* Lines per message */
if(user!=NULL)
strncat(cmd,ultoa(cfg->level_linespermsg[user->level],str,10), avail);
break;
case 'M': /* Minutes (credits) for user */
if(user!=NULL)
strncat(cmd,ultoa(user->min,str,10), avail);
break;
case 'N': /* Node Directory (same as SBBSNODE environment var) */
strncat(cmd,cfg->node_dir, avail);
break;
case 'O': /* SysOp */
strncat(cmd,QUOTED_STRING(instr[i],cfg->sys_op,str,sizeof(str)), avail);
break;
case 'P': /* Client protocol */
break;
case 'Q': /* QWK ID */
strncat(cmd,cfg->sys_id, avail);
break;
case 'R': /* Rows */
if(user!=NULL)
strncat(cmd,ultoa(user->rows,str,10), avail);
break;
case 'S': /* File Spec */
strncat(cmd, fspec, avail);
break;
case 'T': /* Time left in seconds */
break;
case 'U': /* UART I/O Address (in hex) */
strncat(cmd,ultoa(cfg->com_base,str,16), avail);
break;
case 'V': /* Synchronet Version */
sprintf(str,"%s%c",VERSION,REVISION);
strncat(cmd,str, avail);
break;
case 'W': /* Columns/width */
break;
case 'X':
if(user!=NULL)
strncat(cmd,cfg->shell[user->shell]->code, avail);
break;
case '&': /* Address of msr */
break;
case 'Y':
break;
case 'Z':
strncat(cmd,cfg->text_dir, avail);
break;
case '~': /* DOS-compatible (8.3) filename */
#ifdef _WIN32
char sfpath[MAX_PATH+1];
SAFECOPY(sfpath,fpath);
GetShortPathName(fpath,sfpath,sizeof(sfpath));
strncat(cmd,sfpath, avail);
#else
strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
#endif
break;
case '!': /* EXEC Directory */
strncat(cmd,cfg->exec_dir, avail);
break;
case '@': /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
strncat(cmd,cfg->exec_dir, avail);
#endif
break;
case '#': /* Node number (same as SBBSNNUM environment var) */
sprintf(str,"%d",cfg->node_num);
strncat(cmd,str, avail);
break;
case '*':
sprintf(str,"%03d",cfg->node_num);
strncat(cmd,str, avail);
break;
case '$': /* Credits */
if(user!=NULL)
strncat(cmd,ultoa(user->cdt+user->freecdt,str,10), avail);
break;
case '%': /* %% for percent sign */
strncat(cmd,"%", avail);
break;
case '.': /* .exe for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
strncat(cmd,".exe", avail);
#endif
break;
case '?': /* Platform */
#ifdef __OS2__
strcpy(str,"OS2");
#else
strcpy(str,PLATFORM_DESC);
#endif
strlwr(str);
strncat(cmd,str, avail);
break;
case '^': /* Architecture */
strncat(cmd, ARCHITECTURE_DESC, avail);
break;
default: /* unknown specification */
if(IS_DIGIT(instr[i]) && user!=NULL) {
sprintf(str,"%0*d",instr[i]&0xf,user->number);
strncat(cmd,str, avail);
}
break;
}
j=strlen(cmd);
}
else
cmd[j++]=instr[i];
}
cmd[j]=0;
return(cmd);
}