/* 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 // kill() #ifdef __unix__ #include // WEXITSTATUS #define TTYDEFCHARS // needed for ttydefchars definition #include // Linux - it's motherfucked. #if defined(__FreeBSD__) #include // forkpty() #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DARWIN__) #include #elif defined(__linux__) #include #elif defined(__QNX__) #if 0 #include #else #define NEEDS_FORKPTY #endif #endif #ifdef NEEDS_FORKPTY #include #endif #include /* * 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;itotal_natvpgms;i++) if(stricmp(name,cfg->natvpgm[i]->name)==0 || stricmp(base,cfg->natvpgm[i]->name)==0) break; return(itotal_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 "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) { ulong i,j; for(i=j=0;ioptions&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); 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_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) { 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 | 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(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()); } #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 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) { 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); } } 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(rdsizeof(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(!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); } 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;ic_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]; BYTE wwiv_buf[XTRN_IO_BUF_LEN*2]; bool wwiv_flag=false; char* p; #ifdef PREFER_POLL struct pollfd fds[2]; #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)); 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, */ 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)) { if(mkdir(str, 0755) != 0) { errormsg(WHERE,ERR_MKDIR, str, 0755); fclose(dosemubatfp); return -1; } } 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 | 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; winsize.ws_col=cols; 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); 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); 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 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 */ fds[0].fd = out_pipe[0]; fds[0].events = POLLIN; if(!(mode&EX_NOLOG)) { fds[1].fd = err_pipe[0]; fds[1].events = POLLIN; } 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); } bp=buf; i=0; if(mode&EX_NOLOG) poll(fds, 1, 1); else { while (poll(fds, 2, 1) > 0 && (fds[1].revents) && (i < (int)sizeof(buf) - 1)) { 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) { #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) { bp=buf; i=0; while (socket_readable(err_pipe[0], 1000) && (i0) { 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, 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; icode, 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,_ui64toa(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; case '+': /* Real name */ strncat(cmd, quoted_string(useron.name, str, sizeof(str)), 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); }