Skip to content
Snippets Groups Projects
xtrn.cpp 60.9 KiB
Newer Older
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, 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];
	char*	argv[MAX_ARGS + 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;
    BYTE 	wwiv_buf[XTRN_IO_BUF_LEN*2];
    bool	wwiv_flag=false;
#ifdef PREFER_POLL
Deucе's avatar
Deucе committed
	struct pollfd fds[2];
#else
#error select() implementation was removed in 3971ef4d
#endif
	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");
		SAFECOPY(str,fullcmdline);
		snprintf(fullcmdline, sizeof fullcmdline, "%s -F %s",startup->dosemu_path,str);
#elif defined(__linux__)
		/* dosemu integration  --  originally by Ryan Underwood, <nemesis @ icequake.net> */
		FILE *dosemubatfp;
		FILE *externalbatfp;
		FILE *de_launch_inifp;
		char tok[MAX_PATH+1];
		char buf[1024];
		char bufout[1024];
		char cmdlinebatch[MAX_PATH+1];
		char externalbatsrc[MAX_PATH+1];
		char externalbat[MAX_PATH+1];
		char dosemuconf[MAX_PATH+1];
		char de_launch_cmd[INI_MAX_VALUE_LEN];
		char dosemubinloc[MAX_PATH+1];
		char virtualconf[75];
		char dosterm[15];
		char log_external[MAX_PATH+1];
		const char* runtype;
		str_list_t de_launch_ini;
		
		/*  on the Unix side. xtrndir is the parent of the door's startup dir. */
		char xtrndir[MAX_PATH+1];

		/*  on the DOS side.  */
		char xtrndir_dos[MAX_PATH+1];
		char ctrldir_dos[MAX_PATH+1];
		char datadir_dos[MAX_PATH+1];
		char execdir_dos[MAX_PATH+1];
		char nodedir_dos[MAX_PATH+1];
		/* Default locations that can be overridden by
		 * the sysop in emusetup.bat */

		const char ctrldrive[] = DOSEMU_CTRL_DRIVE;
		const char datadrive[] = DOSEMU_DATA_DRIVE;
		const char execdrive[] = DOSEMU_EXEC_DRIVE;
		const char nodedrive[] = DOSEMU_NODE_DRIVE;
		
		const char external_bat_fn[] = "external.bat";
		const char dosemu_cnf_fn[] = "dosemu.conf";

		SAFECOPY(str,startup_dir);
		if(*(p=lastchar(str))=='/')		/* kill trailing slash */
			*p=0;
		if((p=strrchr(str,'/'))!=NULL)  /* kill the last element of the path */
			*p=0;
		SAFECOPY(xtrndir_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);
			}
runderwo's avatar
 
runderwo committed
			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);
runderwo's avatar
 
runderwo committed
				if (!fexist(str)) {
					SAFEPRINTF(str,"/etc/%s", dosemu_cnf_fn);
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 */

		/* 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));
			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);
        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);
runderwo's avatar
 
runderwo committed
		}

		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);
			}
runderwo's avatar
 
runderwo committed
		}
		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);
runderwo's avatar
 
runderwo committed
		if (!isdir(str)) {
			if(mkdir(str, 0755) != 0) {
				errormsg(WHERE,ERR_MKDIR, str, 0755);
				fclose(dosemubatfp);
				return -1;
			}
		}
		strcat(str, "/disclaimer");
runderwo's avatar
 
runderwo committed
		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);
		/* 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*)"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);
		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(!(mode & (EX_STDIN | EX_OFFLINE))) {
					passthru_socket_activate(false);
				else
					pthread_mutex_unlock(&input_thread_mutex);
			}
			errormsg(WHERE,ERR_EXEC,fullcmdline,0);
			if(pipe(in_pipe)!=0) {
				errormsg(WHERE,ERR_CREATE,"in_pipe",0);
				return(-1);
			}
			if(pipe(out_pipe)!=0) {
				errormsg(WHERE,ERR_CREATE,"out_pipe",0);
				return(-1);
			}


			if(!(mode & (EX_STDIN | EX_OFFLINE))) {
					passthru_socket_activate(false);
				else
					pthread_mutex_unlock(&input_thread_mutex);
			}
			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);
				SAFEPRINTF(term_env,"TERM=%s",startup->xtrn_term_ansi);
				SAFEPRINTF(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])
			if(chdir(startup_dir)!=0) {
				errormsg(WHERE,ERR_CHDIR,startup_dir,0);
				return(-1);
			}
		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_STDIN && !(mode&EX_STDOUT))  {
			close(in_pipe[1]);		/* close write-end of pipe */
			dup2(in_pipe[0],0);		/* redirect stdin */
			close(in_pipe[0]);		/* close excess file descriptor */
		}
		if(mode&EX_STDOUT && !(mode&EX_STDIN)) {
			close(out_pipe[0]);		/* close read-end of pipe */
			dup2(out_pipe[1],1);	/* stdout */
			if(!(mode&EX_NOLOG))
				dup2(out_pipe[1],2);	/* stderr */
			close(out_pipe[1]);		/* close excess file descriptor */
		if(!(mode & EX_STDIO)) {
			int fd;

			/* Redirect stdio to /dev/null */
			if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
				dup2(fd, STDIN_FILENO);
				dup2(fd, STDOUT_FILENO);
				if(!(mode&EX_NOLOG))
					dup2(fd, STDERR_FILENO);
		if(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 */
		}
		execvp(argv[0],argv);
rswindell's avatar
rswindell committed
		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 */
Deucе's avatar
Deucе committed
		fds[0].fd = out_pipe[0];
		fds[0].events = POLLIN;
		if(!(mode&EX_NOLOG)) {
			fds[1].fd = err_pipe[0];
			fds[1].events = POLLIN;
		}
			if(waitpid(pid, &i, WNOHANG)!=0)	/* child exited */
				break;
			if(!online && !(mode&EX_OFFLINE)) {
				logline(LOG_NOTICE,"X!","hung-up in external program");
			/* Input */
			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);
				}
				while (poll(fds, 2, 1) > 0 && (fds[1].revents)
Deucе's avatar
Deucе committed
				    && (i < (int)sizeof(buf) - 1))  {
					if((rd=read(err_pipe[0],bp,1))>0)  {
						i+=rd;
						bp++;
						if(*(bp-1)=='\n')
							break;
					}
					else
					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;
			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(telnet_mode&TELNET_MODE_OFF) {
   	       			output_len = telnet_expand(buf, rd, output_buf, sizeof(output_buf), /* expand_cr: */false, &bp);
				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");
			/* Does expanded size fit in the ring buffer? */
			if(output_len>RingBufFree(&outbuf)) {
				lprintf(LOG_ERR,"output buffer overflow");
		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(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;
Deucе's avatar
Deucе committed
			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') {
						p = (char*)buf;
						truncsp(p);
						SKIP_WHITESPACE(p);
						if(*p)
							lprintf(LOG_NOTICE, "%s: %s", fname, p);
				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));
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)
	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;
                case 'A':   /* User alias */
                    strncat(cmd,QUOTED_STRING(instr[i],useron.alias,str,sizeof(str)), avail);
                    break;
                case 'B':   /* Baud (DTE) Rate */
                    strncat(cmd,ultoa(dte_rate,str,10), avail);
                    break;
                case 'C':   /* Connect Description */
                    strncat(cmd,connection, avail);
                    break;
                case 'D':   /* Connect (DCE) Rate */
                    strncat(cmd,ultoa((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 defined(__linux__)
					if(!native && strncmp(fpath, cfg.node_dir, strlen(cfg.node_dir)) == 0) {
						strncat(cmd, DOSEMU_NODE_DIR, avail);
						strncat(cmd, fpath + strlen(cfg.node_dir), avail);
					}
					else
#endif
						strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);