Newer
Older
/* 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.
#include <libutil.h> // forkpty()
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DARWIN__)
#include <pty.h>
#elif defined(__QNX__)
#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)
#define CEOF CTRL('d')
#define CEOL 0xff /* XXX avoid _POSIX_VDISABLE */
#define CERASE 0177
#define CERASE2 CTRL('h')
#define CINTR CTRL('c')
#define CSTATUS CTRL('t')
#define CKILL CTRL('u')
#define CMIN 1
#define CQUIT 034 /* FS, ^\ */
#define CSUSP CTRL('z')
#define CTIME 0
#define CDSUSP CTRL('y')
#define CSTART CTRL('q')
#define CSTOP CTRL('s')
#define CLNEXT CTRL('v')
#define CDISCARD CTRL('o')
#define CWERASE CTRL('w')
#define CREPRINT CTRL('r')
#define CEOT CEOF
#define CBRK CEOL
#define CRPRNT CREPRINT
#define CFLUSH CDISCARD
#endif
#define TTYDEF_IFLAG (BRKINT | ICRNL | IMAXBEL | IXON | IXANY)
#define TTYDEF_OFLAG (OPOST | ONLCR)
#define TTYDEF_LFLAG (ECHO | ICANON | ISIG | IEXTEN | ECHOE|ECHOKE|ECHOCTL)
#define TTYDEF_CFLAG (CREAD | CS8 | HUPCL)
#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, uint buflen, BYTE* outbuf, ulong& newlen
,uint user_misc, bool& ctrl_c)
if(buf[i]==CTRL_C) { /* WWIV color escape char */
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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, uint len)
{
buf[i] = cp437_to_petscii(buf[i]);
}
}
BOOL native_executable(scfg_t* cfg, const char* cmdline, int mode)
{
char* p;
char str[MAX_PATH+1];
char name[64];
char base[64];

Rob Swindell
committed
int 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));
#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
#include "vdd_func.h" /* DOSXTRN.EXE API */
extern SOCKET node_socket[];
/*****************************************************************************/
// Expands Single CR to CRLF
/*****************************************************************************/
BYTE* cr_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
{
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(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, int 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;

rswindell
committed
bool native=false; // DOS program by default
bool rio_abortable_save=rio_abortable;
bool use_pipes=false; // NT-compatible console redirection
BOOL success;
BOOL processTerminated=false;

rswindell
committed
uint i;
time_t hungup=0;
HANDLE rdslot=INVALID_HANDLE_VALUE;
HANDLE wrslot=INVALID_HANDLE_VALUE;
HANDLE start_event=NULL;
HANDLE hangup_event=NULL;
HANDLE rdoutpipe;
HANDLE wrinpipe;
PROCESS_INFORMATION process_info;
unsigned long wr;
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_OFFLINE)
native = true; // We don't need to invoke our virtual UART/FOSSIL driver
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);
SAFEPRINTF2(fullcmdline, "%s%s", comspec_str, cmdline);
SAFECOPY(realcmdline, fullcmdline); // for errormsg if failed to execute
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
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 */
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);
SAFEPRINTF(path,"%sDOSXTRN.ERR", cfg.node_dir);
(void)remove(path);
// Create temporary environment file
SAFEPRINTF(path,"%sDOSXTRN.ENV", node_dir);
FILE* fp=fopen(path,"w");

rswindell
committed
XTRN_CLEANUP;
errormsg(WHERE, ERR_CREATE, path, 0);
}
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);
SAFEPRINTF2(fullcmdline, "%sDOSXTRN.EXE %s", cfg.exec_dir, path);
i = SBBSEXEC_MODE_UNSPECIFIED;
if(mode & EX_UART)
i |= SBBSEXEC_MODE_UART;
if(mode & EX_FOSSIL)
i |= SBBSEXEC_MODE_FOSSIL;
if(mode & EX_STDIN)
i |= SBBSEXEC_MODE_DOS_IN;
if(mode & EX_STDOUT)
i |= SBBSEXEC_MODE_DOS_OUT;
BOOL x64 = FALSE;
IsWow64Process(GetCurrentProcess(), &x64);
sprintf(str," %s %u %u"
,x64 ? "x64" : "NT", 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) {
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) {
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);
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 | EX_STDIN))) {
if(passthru_thread_running)
passthru_socket_activate(true);
else
pthread_mutex_lock(&input_thread_mutex);
DWORD creation_flags = (mode & EX_NODISPLAY) ? CREATE_NO_WINDOW : CREATE_NEW_CONSOLE;
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
creation_flags, // 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) {
if(native && !(mode & (EX_OFFLINE | 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());
}

rswindell
committed
#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);

rswindell
committed
OutputDebugString(dbgstr);
#endif
CloseHandle(process_info.hThread);
/* Disable Ctrl-C checking */

rswindell
committed
rio_abortable=false;

rswindell
committed
// Executing app in foreground?, monitor

rswindell
committed
while(!(mode&EX_BG)) {
if(mode&EX_CHKTIME)
gettimeleft();
if(!online && !(mode&EX_OFFLINE)) { // Tell VDD and external that user hung-up
logline(LOG_NOTICE,"X!","hung-up in external program");
hungup=time(NULL);
if(!native) {
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;
/* 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);
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) {
if(WaitForSingleObject(process_info.hProcess, 0) != WAIT_OBJECT_0) // Process still running?
lprintf(LOG_WARNING,"VDD Open failed (not loaded yet?)");
} else if(!WriteFile(wrslot,bp,wr,&len,NULL)) {
if(WaitForSingleObject(process_info.hProcess, 0) != WAIT_OBJECT_0) { // Process still running?
lprintf(LOG_ERR,"!VDD WriteFile(0x%x, %u) FAILURE (Error=%u)", wrslot, wr, GetLastError());
if(GetMailslotInfo(wrslot,&wr,NULL,NULL,NULL))
lprintf(LOG_DEBUG,"!VDD MailSlot max_msg_size=%u", wr);
else
lprintf(LOG_DEBUG,"!GetMailslotInfo(0x%x)=%u", wrslot, GetLastError());
}
} 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);
rd=0;
len=sizeof(buf);
avail=RingBufFree(&outbuf)/2; // leave room for wwiv/telnet expansion
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
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);
RingBufWrite(&outbuf, 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) {
&& WaitForSingleObject(hangup_event,0)==WAIT_OBJECT_0) {
lputs(LOG_NOTICE, "External program requested hangup (dropped DTR)");
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 */
// 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);
} 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,"Terminating process from line %d", __LINE__);
processTerminated=TerminateProcess(process_info.hProcess, GetLastError());
if(!native && !processTerminated) {
SAFEPRINTF(path, "%sDOSXTRN.RET", cfg.node_dir);
FILE* fp=fopen(path,"r");
if(fp == NULL) {
lprintf(LOG_ERR, "Error %d opening %s after running %s", errno, path, cmdline);
lprintf(LOG_ERR, "Error reading return value from %s", path);
retval = -2;
fclose(fp);
}
if(retval == -1) {
SAFEPRINTF(path, "%sDOSXTRN.ERR", cfg.node_dir);
fp = fopen(path, "r");
if(fp == NULL) {
lprintf(LOG_ERR, "Error %d opening %s after DOSXTRN.RET contained -1", errno, path);
} else {
char errstr[256] = "";
int errval = 0;
if(fscanf(fp, "%d\n", &errval) == 1) {
fgets(errstr, sizeof(errstr), fp);
truncsp(errstr);
lprintf(LOG_ERR, "DOSXTRN Error %d (%s) executing: %s", errval, errstr, cmdline);
} else
lprintf(LOG_ERR, "DOSXTRN.RET contained -1 and we failed to parse: %s", path);
fclose(fp);
}
}
CloseHandle(process_info.hProcess);
if(!(mode&EX_OFFLINE)) { /* !off-line execution */
if(!WaitForOutbufEmpty(5000))
lprintf(LOG_WARNING, "%s Timeout waiting for output buffer to empty", __FUNCTION__);
if(native && !(mode & EX_STDIN)) {
if(passthru_thread_running)
passthru_socket_activate(false);
else
pthread_mutex_unlock(&input_thread_mutex);

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

rswindell
committed
rio_abortable=rio_abortable_save; // Restore abortable state

rswindell
committed
/* Got back to Text/NVT mode */
request_telnet_opt(TELNET_DONT,TELNET_BINARY_TX);

rswindell
committed
}
// lprintf("%s returned %d",realcmdline, retval);
errorlevel = retval; // Baja or JS retrievable error value

rswindell
committed
/*****************************************************************************/
// Expands Unix LF to CRLF
/*****************************************************************************/
BYTE* lf_expand(BYTE* inbuf, uint inlen, BYTE* outbuf, ulong& newlen)

rswindell
committed
{

rswindell
committed
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);
}
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
#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
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
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, int 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];

rswindell
committed
BYTE* bp;
BYTE buf[XTRN_IO_BUF_LEN];
BYTE output_buf[XTRN_IO_BUF_LEN*2];

rswindell
committed
bool native=false; // DOS program by default
bool rio_abortable_save=rio_abortable;
bool data_waiting;

rswindell
committed
int rd;
int wr;

rswindell
committed
int argc;

rswindell
committed
int in_pipe[2];
int out_pipe[2];
int err_pipe[2];
BYTE wwiv_buf[XTRN_IO_BUF_LEN*2];
bool wwiv_flag=false;
#else
#error select() implementation was removed in 3971ef4d
#endif
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));
snprintf(fullpath, sizeof fullpath, "%s%s",startup_dir,fname);
if(startup_dir!=NULL && cmdline[0]!='/' && cmdline[0]!='.' && fexist(fullpath))
snprintf(fullcmdline, sizeof fullcmdline, "%s%s",startup_dir,cmdline);
else
SAFECOPY(fullcmdline,cmdline);
if(native) { // Native (not MS-DOS) external
// Current environment passed to child process
snprintf(dszlog, sizeof 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);
snprintf(str, sizeof 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));
snprintf(str, sizeof 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);
snprintf(str, sizeof str, "%02u", tm.tm_mon + 1);
setenv("MONTH", str, /* overwrite */TRUE);
snprintf(str, sizeof str, "%u", 1900 + tm.tm_year);
if(setenv("YEAR", str, /* overwrite */TRUE) != 0)
errormsg(WHERE,ERR_WRITE,"environment",0);
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;
snprintf(str, sizeof 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);
snprintf(fullcmdline, sizeof fullcmdline, "%s -F %s",startup->dosemu_path,str);
/* dosemu integration -- originally by Ryan Underwood, <nemesis @ icequake.net> */
FILE *dosemubatfp;
FILE *externalbatfp;
FILE *de_launch_inifp;
char cmdlinebatch[MAX_PATH+1];
char externalbatsrc[MAX_PATH+1];
char externalbat[MAX_PATH+1];
char dosemuconf[MAX_PATH+1];
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];
/* 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 we couldn't find either, try for the system one, then
* error out. */
SAFEPRINTF(str,"/etc/dosemu/%s", dosemu_cnf_fn);
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");
if(startup_dir!=NULL && startup_dir[0]) {
SAFECOPY(str, startup_dir);
*lastchar(str) = 0;
if(*gamedir == 0) {
lprintf(LOG_ERR, "No startup directory configured for DOS command-line: %s", cmdline);
/* 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 }\"");
/* 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);
if (!(externalbatfp=fopen(externalbatsrc,"r"))) {
errormsg(WHERE,ERR_OPEN,externalbatsrc,0);
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);
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
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. */
snprintf(str, sizeof str, "%s/.dosemu", cfg.ctrl_dir);
if(mkdir(str, 0755) != 0) {
errormsg(WHERE,ERR_MKDIR, str, 0755);
fclose(dosemubatfp);
return -1;
}
}
strcat(str, "/disclaimer");
/* 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);
/* if file found, then open and process it */
if ((de_launch_inifp=iniOpenFile(str, false))==NULL) {
errormsg(WHERE,ERR_OPEN,str, 0);
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*)"EXECDIR", cfg.exec_dir},
{(char*)"DATADIR", cfg.data_dir},
{(char*)"XTRNDIR", xtrndir},
{(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);
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");
if(!(mode & (EX_STDIN | EX_OFFLINE))) {
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;
if((pid=forkpty(&in_pipe[1],NULL,&term,&winsize))==-1) {
if(!(mode & (EX_STDIN | EX_OFFLINE))) {
if(passthru_thread_running)
passthru_socket_activate(false);
else
pthread_mutex_unlock(&input_thread_mutex);
}
errormsg(WHERE,ERR_EXEC,fullcmdline,0);

rswindell
committed
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 | EX_OFFLINE))) {
if(passthru_thread_running)
passthru_socket_activate(false);
else
pthread_mutex_unlock(&input_thread_mutex);
}
errormsg(WHERE,ERR_EXEC,fullcmdline,0);

rswindell
committed
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);
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 */
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)) {

rswindell
committed
close(in_pipe[1]); /* close write-end of pipe */
dup2(in_pipe[0],0); /* redirect stdin */
close(in_pipe[0]); /* close excess file descriptor */
}

rswindell
committed
if(mode&EX_STDOUT && !(mode&EX_STDIN)) {

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

rswindell
committed
close(out_pipe[1]); /* close excess file descriptor */

rswindell
committed
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");
if(daemon(TRUE,FALSE) != 0)
lprintf(LOG_ERR, "!ERROR %d (%s) daemonizing: %s", errno, strerror(errno), argv[0]);
}
if(!(mode&EX_NOLOG)) {
close(err_pipe[0]); /* close read-end of pipe */
dup2(err_pipe[1],2); /* stderr */
}
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);
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 */
if(!(mode&EX_NOLOG)) {
fds[1].fd = err_pipe[0];
fds[1].events = POLLIN;
}
while(!terminated) {

rswindell
committed
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;
}
if(mode&EX_STDIN && RingBufFull(&inbuf)) {
if((wr=RingBufRead(&inbuf,buf,sizeof(buf)))!=0) {
if(write(in_pipe[1],buf,wr) != wr)
lprintf(LOG_ERR, "ERROR %d writing to pipe", errno);
}

rswindell
committed
}
bp=buf;
i=0;
if(mode&EX_NOLOG)
poll(fds, 1, 1);
else {
while (poll(fds, 2, 1) > 0 && (fds[1].revents)
if((rd=read(err_pipe[0],bp,1))>0) {
i+=rd;
bp++;
if(*(bp-1)=='\n')
break;
}
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);
/* Eat stderr if mode is EX_BIN */
if(mode&EX_BIN) {
bp=buf;
i=0;
}
data_waiting=fds[0].revents;
if(i==0 && data_waiting==0)
continue;
avail=(RingBufFree(&outbuf)-i)/2; // Leave room for wwiv/telnet expansion
if(avail==0) {
lprintf("Node %d !output buffer full (%u bytes)"
,cfg.node_num,RingBufFull(&outbuf));
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);

rswindell
committed
RingBufWrite(&outbuf, bp, output_len);

rswindell
committed
}
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 5 seconds
while(time(NULL)-start<5 ) { // for child to terminate
if(waitpid(pid, &i, WNOHANG)!=0)
break;
mswait(500);
}
if(waitpid(pid, &i, WNOHANG)==0) { // Child still running?
kill(pid, SIGTERM); // terminate child process (gracefully)
start=time(NULL); // Wait up to 5 (more) seconds
while(time(NULL)-start<5 ) { // 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 (ungracefully)
}
}
/* close unneeded descriptors */
if(mode&EX_STDIN)
close(in_pipe[1]);
close(out_pipe[0]);

rswindell
committed
}
if(mode&EX_NOLOG)
waitpid(pid, &i, 0);
else {
while(waitpid(pid, &i, WNOHANG)==0) {
bp=buf;
i=0;
while (socket_readable(err_pipe[0], 1000) && (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 */
if(!WaitForOutbufEmpty(5000))
lprintf(LOG_WARNING, "%s Timeout waiting for output buffer to empty", __FUNCTION__);
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);
if(!(mode&EX_NOLOG))
close(err_pipe[0]);
return(errorlevel = WEXITSTATUS(i));
}
#endif /* !WIN32 */
static 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, int mode)
char str[MAX_PATH+1],*cmd;
bool native = (mode == EX_UNSPECIFIED) || native_executable(&cfg, instr, mode);
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) {
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((uint)cur_rate,str,10), avail);
break;
case 'E': /* Estimated Rate */
strncat(cmd,ultoa((uint)cur_cps*10,str,10), avail);
break;
case 'F': /* File path */
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(!native)
strncat(cmd, DOSEMU_TEMP_DIR, avail);
else
#endif
strncat(cmd,cfg.temp_dir, avail);
case 'H': /* Socket Handle */
strncat(cmd,ultoa(client_socket_dup,str,10), avail);
case 'I': /* IP address */
strncat(cmd,cid, avail);
if(!native)
strncat(cmd, DOSEMU_DATA_DIR, avail);
else
#endif
strncat(cmd,cfg.data_dir, avail);
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(!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);
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);
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 */
snprintf(str, sizeof str, "%s%c",VERSION,REVISION);
strncat(cmd,str, avail);
case 'W': /* Columns (width) */
strncat(cmd,ultoa(cols,str,10), avail);
strncat(cmd,cfg.shell[useron.shell]->code, avail);
break;
case '&': /* Address of msr */
break;
case 'Y':
strncat(cmd,comspec, avail);
if(!native)
strncat(cmd, DOSEMU_TEXT_DIR, avail);
else
#endif
strncat(cmd,cfg.text_dir, avail);
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);
strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
case '!': /* EXEC Directory */
if(!native)
strncat(cmd, DOSEMU_EXEC_DIR, avail);
else
#endif
strncat(cmd,cfg.exec_dir, avail);
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) */
snprintf(str, sizeof str, "%d",cfg.node_num);
strncat(cmd,str, avail);
snprintf(str, sizeof str, "%03d",cfg.node_num);
strncat(cmd,str, avail);
break;
case '$': /* Credits */
strncat(cmd,_ui64toa(user_available_credits(&useron),str,10), avail);
break;
case '%': /* %% for percent sign */
strncat(cmd,"%", avail);
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;
case '+': /* Real name */
strncat(cmd, quoted_string(useron.name, str, sizeof(str)), avail);
break;
default: /* unknown specification */
if(IS_DIGIT(instr[i])) {
snprintf(str, sizeof str, "%0*d",instr[i]&0xf,useron.number);
strncat(cmd,str, avail); }