Skip to content
Snippets Groups Projects
xtrn.cpp 64 KiB
Newer Older
			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) {
				fscanf(fp,"%d",&retval);
				fclose(fp);
			}

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

			
			/* Re-enable blocking (incase disabled by xtrn program) */
			ulong l=0;
			ioctlsocket(client_socket, FIONBIO, &l);

			/* Re-set socket options */
			if(set_socket_options(&cfg, client_socket, str))
				lprintf(LOG_ERR,"%04d !ERROR %s",client_socket, str);

			if(input_thread_mutex_locked && input_thread_running) {
				pthread_mutex_unlock(&input_thread_mutex);
				input_thread_mutex_locked=false;
			}
		curatr=~0;			// Can't guarantee current attributes
		attr(LIGHTGRAY);	// Force to "normal"
		rio_abortable=rio_abortable_save;	// Restore abortable state
		request_telnet_opt(TELNET_DONT,TELNET_BINARY_TX);
//	lprintf("%s returned %d",realcmdline, retval);

	errorlevel = retval; // Baja or JS retrievable error value
#else	/* !WIN32 */

/*****************************************************************************/
// Expands Unix LF to CRLF
/*****************************************************************************/
BYTE* lf_expand(BYTE* inbuf, ulong inlen, BYTE* outbuf, ulong& newlen)
{
	ulong	i,j;

	for(i=j=0;i<inlen;i++) {
		if(inbuf[i]=='\n' && (!i || inbuf[i-1]!='\r'))
			outbuf[j++]='\r';
		outbuf[j++]=inbuf[i];
	}
	newlen=j;
    return(outbuf);
}

#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);
		}
deuce's avatar
deuce committed
		/* Note, on some platforms, this can be free()d... */
		sprintf(envstr,"%s=%s",name,value);
		putenv(envstr);
	}
	return(0);
}
#endif

#ifdef NEEDS_CFMAKERAW
void
cfmakeraw(struct termios *t)
{
	t->c_iflag &= ~(IMAXBEL|IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
	t->c_oflag &= ~OPOST;
	t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
	t->c_cflag &= ~(CSIZE|PARENB);
	t->c_cflag |= CS8;
}
#endif

#ifdef NEEDS_FORKPTY
static int login_tty(int fd)
{
	(void) setsid();
	if (!isatty(fd))
		return (-1);
	(void) dup2(fd, 0);
	(void) dup2(fd, 1);
	(void) dup2(fd, 2);
	if (fd > 2)
		(void) close(fd);
	return (0);
}

#ifdef NEEDS_DAEMON
/****************************************************************************/
/* Daemonizes the process                                                   */
/****************************************************************************/
int
daemon(int nochdir, int noclose)
{
    int fd;

    switch (fork()) {
    case -1:
        return (-1);
    case 0:
        break;
    default:
        _exit(0);
    }

    if (setsid() == -1)
        return (-1);

    if (!nochdir)
        (void)chdir("/");

    if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
        (void)dup2(fd, STDIN_FILENO);
        (void)dup2(fd, STDOUT_FILENO);
        (void)dup2(fd, STDERR_FILENO);
        if (fd > 2)
            (void)close(fd);
    }
    return (0);
}
#endif

static int openpty(int *amaster, int *aslave, char *name, struct termios *termp, winsize *winp)
{
	char line[] = "/dev/ptyXX";
	const char *cp1, *cp2;
	int master, slave, ttygid;
	struct group *gr;

	if ((gr = getgrnam("tty")) != NULL)
		ttygid = gr->gr_gid;
	else
		ttygid = -1;

	for (cp1 = "pqrsPQRS"; *cp1; cp1++) {
		line[8] = *cp1;
		for (cp2 = "0123456789abcdefghijklmnopqrstuv"; *cp2; cp2++) {
			line[5] = 'p';
			line[9] = *cp2;
			if ((master = open(line, O_RDWR, 0)) == -1) {
				if (errno == ENOENT)
					break; /* try the next pty group */
			} else {
				line[5] = 't';
				(void) chown(line, getuid(), ttygid);
				(void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
				/* Hrm... SunOS doesn't seem to have revoke
				(void) revoke(line); */
				if ((slave = open(line, O_RDWR, 0)) != -1) {
					*amaster = master;
					*aslave = slave;
					if (name)
						strcpy(name, line);
					if (termp)
						(void) tcsetattr(slave,
							TCSAFLUSH, termp);
					if (winp)
						(void) ioctl(slave, TIOCSWINSZ,
							(char *)winp);
					return (0);
				}
				(void) close(master);
			}
		}
	}
	errno = ENOENT;	/* out of ptys */
	return (-1);
}

static int forkpty(int *amaster, char *name, termios *termp, winsize *winp)
{
	int master, slave, pid;

	if (openpty(&master, &slave, name, termp, winp) == -1)
		return (-1);
	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];
	BYTE*	bp;
	BYTE	buf[XTRN_IO_BUF_LEN];
    BYTE 	output_buf[XTRN_IO_BUF_LEN*2];
    ulong	output_len;
	bool	native=false;			// DOS program by default
	bool	rio_abortable_save=rio_abortable;
rswindell's avatar
rswindell committed
	int		i;
		eprintf(LOG_INFO,"Executing external: %s",cmdline);
	if(startup_dir==NULL)
		startup_dir=nulstr;
	XTRN_LOADABLE_MODULE;
	XTRN_LOADABLE_JS_MODULE;
	attr(cfg.color[clr_external]);  /* setup default attributes */
    SAFECOPY(str,cmdline);			/* Set fname to program name only */
	truncstr(str," ");
    SAFECOPY(fname,getfname(str));

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

	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 (32-bit) 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);
		if(setenv("SBBSNNUM",str,1))
        	errormsg(WHERE,ERR_WRITE,"environment",0);
#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);

		fclose(doscmdrc);
		SAFECOPY(str,fullcmdline);
		sprintf(fullcmdline,"%s -F %s",startup->dosemu_path,str);

#elif defined(__linux__) && defined(USE_DOSEMU)

		/* dosemu integration  --  Ryan Underwood, <nemesis @ icequake.net> */

		FILE *dosemubat;
		int setup_override;
		char tok[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];

		/*  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 nodedrive[] = "D:";
		const char xtrndrive[] = "E:";
		const char ctrldrive[] = "F:";
		const char datadrive[] = "G:";
		const char execdrive[] = "H:";

		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);

		/* construct DOS equivalents for the unix directories */

		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(xtrndir_dos,xtrndir);
		REPLACE_CHARS(xtrndir_dos,'/','\\',p);

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

		sprintf(str,"%sdosemu.conf",startup_dir);
runderwo's avatar
 
runderwo committed
		if (!fexist(str)) {

		/* If we can't find it in the door dir, look for a global one
		 * in the ctrl dir. */

			sprintf(str,"%sdosemu.conf",cfg.ctrl_dir);
runderwo's avatar
 
runderwo committed
			if (!fexist(str)) {

		/* If we couldn't find either, try for the system one, then
		 * error out. */
				SAFECOPY(str,"/etc/dosemu/dosemu.conf");
runderwo's avatar
 
runderwo committed
				if (!fexist(str)) {
				
					SAFECOPY(str,"/etc/dosemu.conf");
runderwo's avatar
 
runderwo committed
					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 */

		/* same deal for emusetup.bat. */

		sprintf(str,"%semusetup.bat",startup_dir);
		fprintf(stderr, str);
runderwo's avatar
 
runderwo committed
		if (!fexist(str)) {

		/* If we can't find it in the door dir, look for a global one
		 * in the ctrl dir. */

			sprintf(str,"%semusetup.bat",cfg.ctrl_dir);
runderwo's avatar
 
runderwo committed
			if (!fexist(str)) {

		/* If we couldn't find either, set an error condition. */
				setup_override = -1;
			}
			else setup_override = 0;  /* using global bat */
		}
		else setup_override = 1;  /* using door-specific bat */

		/* Create the external bat here to be placed in the node dir. */

		sprintf(str,"%sexternal.bat",cfg.node_dir);
		if(!(dosemubat=fopen(str,"w+"))) {
			errormsg(WHERE,ERR_CREATE,str,0);
			return(-1);
		}

		fprintf(dosemubat,"@echo off\r\n");
		fprintf(dosemubat,"set DSZLOG=%s\\PROTOCOL.LOG\r\n",nodedrive);
		fprintf(dosemubat,"set SBBSNODE=%s\r\n",nodedrive);
		fprintf(dosemubat,"set SBBSNNUM=%d\r\n",cfg.node_num);
		fprintf(dosemubat,"set SBBSCTRL=%s\r\n",ctrldrive);
		fprintf(dosemubat,"set SBBSDATA=%s\r\n",datadrive);
		fprintf(dosemubat,"set SBBSEXEC=%s\r\n",execdrive);

		/* clear existing redirections on dos side */
		fprintf(dosemubat,"lredir del %s\r\nlredir del %s\r\nlredir del %s\r\nlredir del %s\r\n",xtrndrive,ctrldrive,datadrive,execdrive);

		/* redirect necessary drive letters to unix paths */
		fprintf(dosemubat,"lredir %s linux\\fs%s\r\n",xtrndrive,xtrndir_dos);
		fprintf(dosemubat,"lredir %s linux\\fs%s\r\n",ctrldrive,ctrldir_dos);
		fprintf(dosemubat,"lredir %s linux\\fs%s\r\n",datadrive,datadir_dos);
		fprintf(dosemubat,"lredir %s linux\\fs%s\r\n",execdrive,execdir_dos);

		/* change to the drive where the parent of the startup_dir is mounted */
		fprintf(dosemubat,"%s\r\n",xtrndrive);

		if(startup_dir!=NULL && startup_dir[0]) {

			SAFECOPY(str,startup_dir);

		/* if theres a trailing slash, dump it */

			p=lastchar(str);
			if (*p=='/') *p=0;

			if ((p=strrchr(str, '/'))!=NULL)
				SAFECOPY(str,p+1);  /* str = game's starting dir */

			else str[0] = '\0';
		}

		else str[0] = '\0';

		fprintf(dosemubat,"cd %s\r\n",str);  /* startup_dir  */

		if (setup_override == 1)
			fprintf(dosemubat,"call %s\\%s\\emusetup.bat %s\r\n",xtrndrive,str,cmdline);
		else if (setup_override == 0)
			fprintf(dosemubat,"call %s\\emusetup.bat\r\n",ctrldrive);
		/* if (setup_override == -1) do_nothing */

		/*  Check if it's a bat file, to prepend "call" to the command  */

		SAFECOPY(tok,cmdline);
		truncstr(tok," ");

		p = strstr(tok, ".bat");  /*  check if it's a bat file  */
		if (p)
			fprintf(dosemubat,"call ");  /* if so, "call" it */

		fprintf(dosemubat,"%s\r\n",cmdline);
		fprintf(dosemubat,"exitemu\r\n");

		/* 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_INR|EX_OUTR)) && online!=ON_LOCAL)
			SAFECOPY(virtualconf,"-I\"serial { virtual com 1 }\"");
		else
			virtualconf[0] = '\0';

		/* Set the interception bits, since we are always going to want Synchronet
		 * to intercept dos programs under Unix.
		 */

		mode |= (EX_INR|EX_OUTR);

		/* See if we have the dosemu link in the door's dir.  If so, use the dosemu
		 * that it points to as our command to execute.  If not, use DOSemuPath.
		 */
 
		sprintf(str,"%sdosemu.bin",startup_dir);
runderwo's avatar
 
runderwo committed
		if (!fexist(str)) {
			SAFECOPY(dosemubinloc,(cmdstr(startup->dosemu_path,nulstr,nulstr,tok)));
runderwo's avatar
 
runderwo committed
		}
		else {
			SAFECOPY(dosemubinloc,str);
runderwo's avatar
 
runderwo committed
		}

		/* Attempt to keep dosemu from prompting for a disclaimer. */

		sprintf(str, "%s/.dosemu", cfg.ctrl_dir);
runderwo's avatar
 
runderwo committed
		if (!isdir(str)) {
			mkdir(str, 0755);
		}

		strcat(str, "/disclaimer");
runderwo's avatar
 
runderwo committed
		ftouch(str);

		/* Set up the command for dosemu to execute with 'unix -e'. */

		sprintf(str,"%sexternal.bat",nodedrive);

		/* 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");
			sprintf(log_external,">> %sdosevent_%s.log",cfg.logs_dir,fname);
		}
		else {
			dosterm[0]='\0';
			log_external[0] = '\0';
		}

		/* Drum roll. */

		sprintf(fullcmdline,
		"/usr/bin/env %s HOME=%s QUIET=1 DOSDRIVE_D=%s %s -I\"video { none }\" -I\"keystroke \\r\" %s -f%s -E%s -o%sdosemu.log 2> %sdosemu_boot.log %s",
			dosterm,cfg.ctrl_dir,cfg.node_dir,dosemubinloc,virtualconf,dosemuconf,str,cfg.node_dir,cfg.node_dir,log_external);

		fprintf(dosemubat,"REM For debugging: %s\r\n",fullcmdline);
		fclose(dosemubat);

rswindell's avatar
rswindell committed
		bprintf("\r\nExternal DOS programs are not yet supported in \r\n%s\r\n"
			,VERSION_NOTICE);
		return(-1);
	if(!(mode&EX_INR) && input_thread_running) {
		lprintf(LOG_DEBUG,"Locking input thread mutex"); 
		if(pthread_mutex_lock(&input_thread_mutex)!=0)
			errormsg(WHERE,ERR_LOCK,"input_thread_mutex",0);
	if(pipe(err_pipe)!=0) {
		errormsg(WHERE,ERR_CREATE,"err_pipe",0);
		return(-1);
	if((mode&EX_INR) && (mode&EX_OUTR))  {
		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;
			memcpy(term.c_cc,ttydefchars,sizeof(term.c_cc));
		winsize.ws_col=cols;
		if((pid=forkpty(&in_pipe[1],NULL,&term,&winsize))==-1) {
			if(input_thread_mutex_locked && input_thread_running) {
				if(pthread_mutex_unlock(&input_thread_mutex)!=0)
					errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
			errormsg(WHERE,ERR_EXEC,fullcmdline,0);
		out_pipe[0]=in_pipe[1];
	}
	else  {
		if(mode&EX_INR)
			if(pipe(in_pipe)!=0) {
				errormsg(WHERE,ERR_CREATE,"in_pipe",0);
				return(-1);
			}
		if(mode&EX_OUTR)
			if(pipe(out_pipe)!=0) {
				errormsg(WHERE,ERR_CREATE,"out_pipe",0);
				return(-1);
			}


			if(input_thread_mutex_locked && input_thread_running) {
				if(pthread_mutex_unlock(&input_thread_mutex)!=0)
					errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
			errormsg(WHERE,ERR_EXEC,fullcmdline,0);
	}
	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))  {
			static char	term_env[256];
			if(useron.misc&ANSI)
				sprintf(term_env,"TERM=%s",startup->xtrn_term_ansi);
			else
				sprintf(term_env,"TERM=%s",startup->xtrn_term_dumb);
#ifdef __FreeBSD__
		if(!native)
			chdir(cfg.node_dir);
		else
#endif
		if(startup_dir!=NULL && startup_dir[0])
			chdir(startup_dir);
		if(mode&EX_SH || strcspn(fullcmdline,"<>|;\"")!=strlen(fullcmdline)) {
			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 */
		if(mode&EX_INR && !(mode&EX_OUTR))  {
			close(in_pipe[1]);		/* close write-end of pipe */
			dup2(in_pipe[0],0);		/* redirect stdin */
			close(in_pipe[0]);		/* close excess file descriptor */
		}
		if(mode&EX_OUTR && !(mode&EX_INR)) {
			close(out_pipe[0]);		/* close read-end of pipe */
			dup2(out_pipe[1],1);	/* stdout */
#ifndef XTERN_LOG_STDERR
			dup2(out_pipe[1],2);	/* stderr */
#endif
			close(out_pipe[1]);		/* close excess file descriptor */
		if(mode&EX_BG)	/* background execution, detach child */
		{
			lprintf(LOG_INFO,"Detaching external process");
		close(err_pipe[0]);		/* close read-end of pipe */
		dup2(err_pipe[1],2);	/* stderr */
		execvp(argv[0],argv);
		sprintf(str,"!ERROR %d executing %s",errno,argv[0]);
		errorlog(str);
		_exit(-1);	/* should never get here */
	if(online!=ON_LOCAL)
		lprintf(LOG_INFO,"Node %d executing external: %s",cfg.node_num,fullcmdline);

	/* Disable Ctrl-C checking */
	if(!(mode&EX_OFFLINE))
		rio_abortable=false;
	close(err_pipe[1]);	/* close write-end of pipe */
		if(!(mode&EX_INR))
			close(out_pipe[1]);	/* close write-end of pipe */
			if(waitpid(pid, &i, WNOHANG)!=0)	/* child exited */
				break;
			if(!online && !(mode&EX_OFFLINE)) {
				sprintf(str,"%s hung-up in external program",useron.alias);
				logline("X!",str);
				break;
			}

			/* Input */	
			if(mode&EX_INR && RingBufFull(&inbuf)) {
				if((wr=RingBufRead(&inbuf,buf,sizeof(buf)))!=0)
					write(in_pipe[1],buf,wr);
			}
				
			/* Error Output */
			FD_ZERO(&ibits);
			high_fd=err_pipe[0];
			if(out_pipe[0]>err_pipe[0])
				high_fd=out_pipe[0];
			timeout.tv_sec=0;
			timeout.tv_usec=1000;
			bp=buf;
			i=0;
#ifdef XTERN_LOG_STDERR
			select(high_fd+1,&ibits,NULL,NULL,&timeout);
#else
			while ((select(high_fd+1,&ibits,NULL,NULL,&timeout)>0) && FD_ISSET(err_pipe[0],&ibits) && (i<(int)sizeof(buf)-1))  {
				if((rd=read(err_pipe[0],bp,1))>0)  {
					i+=rd;
					bp++;
					if(*(bp-1)=='\n')
						break;
				}
				else
					break;
				FD_ZERO(&ibits);
				FD_SET(err_pipe[0],&ibits);
				FD_SET(out_pipe[0],&ibits);
				timeout.tv_sec=0;
				timeout.tv_usec=1000;
				lprintf(LOG_NOTICE,"%.*s",i,buf);		/* lprintf mangles i? */

			/* Eat stderr if mode is EX_BIN */
			if(mode&EX_BIN)  {
				bp=buf;
				i=0;
			}
			data_waiting=FD_ISSET(out_pipe[0],&ibits);
			avail=(RingBufFree(&outbuf)-i)/2;	// Leave room for wwiv/telnet expansion
				lprintf("Node %d !output buffer full (%u bytes)"
						,cfg.node_num,RingBufFull(&outbuf));
			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;
				else
   	       			bp=telnet_expand(buf, rd, output_buf, output_len);
			} else			/* LF to CRLF expansion */
				bp=lf_expand(buf, rd, output_buf, output_len);

			/* Did expansion overrun the output buffer? */
			if(output_len>sizeof(output_buf)) {
				errorlog("OUTPUT_BUF OVERRUN");
				output_len=sizeof(output_buf);
			}
			/* Does expanded size fit in the ring buffer? */
			if(output_len>RingBufFree(&outbuf)) {
				errorlog("output buffer overflow");
				output_len=RingBufFree(&outbuf);
		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_INR)
			close(in_pipe[1]);
		close(out_pipe[0]);
		/* Enable the Nagle algorithm */
		int nodelay=FALSE;
		setsockopt(client_socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
		while(waitpid(pid, &i, WNOHANG)==0)  {
#ifdef XTERN_LOG_STDERR
			FD_ZERO(&ibits);
			FD_SET(err_pipe[0],&ibits);
			timeout.tv_sec=1;
			timeout.tv_usec=0;
			bp=buf;
			i=0;
			while ((select(err_pipe[0]+1,&ibits,NULL,NULL,&timeout)>0) && (i<XTRN_IO_BUF_LEN-1))  {
				if((rd=read(err_pipe[0],bp,1))>0)  {
					i+=rd;
					if(*bp=='\n') {
						lprintf(LOG_NOTICE,"%.*s",i-1,buf);
						i=0;
						bp=buf;
					}
					else
						bp++;
			if(i)
				lprintf(LOG_NOTICE,"%.*s",i,buf);
#endif
	if(!(mode&EX_OFFLINE)) {	/* !off-line execution */
		/* Re-enable blocking (incase disabled by xtrn program) */
		ulong l=0;
		ioctlsocket(client_socket, FIONBIO, &l);
		/* Re-set socket options */
		if(set_socket_options(&cfg, client_socket, str))
			lprintf(LOG_ERR,"%04d !ERROR %s",client_socket, str);

		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(input_thread_mutex_locked && input_thread_running) {
		if(pthread_mutex_unlock(&input_thread_mutex)!=0)
			errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
	return(errorlevel = WEXITSTATUS(i));
uint fakeriobp=0xffff;

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

    if(outstr==NULL)
        cmd=cmdstr_output;
    else
        cmd=outstr;
    len=strlen(instr);
    for(i=j=0;i<len && j<(int)sizeof(cmdstr_output);i++) {
        if(instr[i]=='%') {
            i++;
            cmd[j]=0;
			char ch=instr[i];
			if(isalpha(ch))
				ch=toupper(ch);
            switch(ch) {
                case 'A':   /* User alias */
                    strcat(cmd,useron.alias);
                    break;
                case 'B':   /* Baud (DTE) Rate */
                    strcat(cmd,ultoa(dte_rate,str,10));
                    break;
                case 'C':   /* Connect Description */
                    strcat(cmd,connection);
                    break;
                case 'D':   /* Connect (DCE) Rate */
                    strcat(cmd,ultoa((ulong)cur_rate,str,10));
                    break;
                case 'E':   /* Estimated Rate */
                    strcat(cmd,ultoa((ulong)cur_cps*10,str,10));
                    break;
                case 'F':   /* File path */
                    strcat(cmd,fpath);
                    break;
                case 'G':   /* Temp directory */
                    strcat(cmd,cfg.temp_dir);
                    break;
                case 'H':   /* Port Handle or Hardware Flow Control */
#if defined(__unix__)
					strcat(cmd,ultoa(client_socket,str,10));
#else
                    strcat(cmd,ultoa(client_socket_dup,str,10));
                case 'I':   /* IP address */
                    strcat(cmd,cid);
                    break;
                case 'J':
                    strcat(cmd,cfg.data_dir);
                    break;
                case 'K':
                    strcat(cmd,cfg.ctrl_dir);
                    break;
                case 'L':   /* Lines per message */
                    strcat(cmd,ultoa(cfg.level_linespermsg[useron.level],str,10));
                    break;
                case 'M':   /* Minutes (credits) for user */
                    strcat(cmd,ultoa(useron.min,str,10));
                    break;