Skip to content
Snippets Groups Projects
xtrn.cpp 45.4 KiB
Newer Older
			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(char* cmdline, long mode, char* startup_dir)
{
	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;
rswindell's avatar
rswindell committed
	int		i;

	XTRN_LOADABLE_MODULE;
	XTRN_LOADABLE_JS_MODULE;
	attr(cfg.color[clr_external]);        /* setup default attributes */

    SAFECOPY(str,cmdline);		/* Set str to program name only */
    p=strchr(str,SP);
    if(p) *p=0;

    p=strrchr(fname,'/');
    if(!p) p=strrchr(fname,'\\');
    if(!p) p=strchr(fname,':');
    if(!p) p=fname;
    else   p++;

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

		bprintf("\r\nExternal DOS programs are not yet supported in\r\n%s\r\n",VERSION_NOTICE);

 	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(sbbsnnum,"%u",cfg.node_num);
		if(setenv("SBBSNNUM",sbbsnnum,1))
        	errormsg(WHERE,ERR_WRITE,"environment",0);
#ifdef __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,cmdline);
		sprintf(cmdline,"/usr/bin/doscmd -F %s",str);
	if(!(mode&EX_INR))
		pthread_mutex_lock(&input_thread_mutex);

	if((mode&EX_INR) && (mode&EX_OUTR))  {
		struct winsize winsize;
		struct termios term;
		memset(&term,0,sizeof(term));
		cfsetspeed(&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_row=rows;
		// #warning Currently cols are forced to 80 apparently TODO
		winsize.ws_col=80;
		if((pid=forkpty(&in_pipe[1],NULL,&term,&winsize))==-1) {
			pthread_mutex_unlock(&input_thread_mutex);
			errormsg(WHERE,ERR_EXEC,cmdline,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((pid=fork())==-1) {
			pthread_mutex_unlock(&input_thread_mutex);
			errormsg(WHERE,ERR_EXEC,cmdline,0);
	}
	if(pid==0) {	/* child process */
		sigset_t        sigs;
		sigfillset(&sigs);
		sigprocmask(SIG_UNBLOCK,&sigs,NULL);
		if(!(mode&EX_BIN))  {
			static char	term_env[256];
			sprintf(term_env,"TERM=%s",startup->xtrn_term);
			putenv(term_env);
		}
#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(cmdline,"<>|;")!=strlen(cmdline)) {
			argv[0]=comspec;
			argv[1]="-c";
			argv[2]=cmdline;
			argv[3]=NULL;
		} else {
			argv[0]=cmdline;	/* point to the beginning of the string */
			argc=1;
			for(i=0;cmdline[i] && argc<MAX_ARGS;i++)	/* Break up command line */
				if(cmdline[i]==SP) {
					cmdline[i]=0;			/* insert nulls */
					argv[argc++]=cmdline+i+1; /* point to the beginning of the next arg */
				}
			argv[argc]=NULL;
		}
		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 */
			dup2(out_pipe[1],2);	/* stderr */
			close(out_pipe[1]);		/* close excess file descriptor */
		if(mode&EX_BG)	/* background execution, detach child */
		{
			if(fork())
				exit(0);
			lprintf("Detaching external process pgid=%d",setsid());
		execvp(argv[0],argv);
		sprintf(str,"!ERROR %d executing %s",errno,argv[0]);
		errorlog(str);
		exit(-1);	/* should never get here */
	lprintf("Node %d executing external: %s",cfg.node_num,cmdline);

	/* Disable Ctrl-C checking */
	if(!(mode&EX_OFFLINE)) {
		rio_abortable_save=rio_abortable;
		rio_abortable=false;
	}
		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);
			}
				
			/* Output */
			FD_ZERO(&ibits);
			FD_SET(out_pipe[0],&ibits);
			timeout.tv_sec=0;
			timeout.tv_usec=0;
			if(!select(out_pipe[0]+1,&ibits,NULL,NULL,&timeout))  {
				mswait(1);
				continue;
			}
			avail=RingBufFree(&outbuf)/2;	// Leave room for wwiv/telnet expansion
				lprintf("Node %d !output buffer full (%u bytes)"
						,cfg.node_num,RingBufFull(&outbuf));
				rd=sizeof(buf);

			if((rd=read(out_pipe[0],buf,rd))<1) {
			if(mode&EX_BIN)	/* telnet IAC expansion */
   	       		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);
			RingBufWrite(&outbuf, bp, output_len);
			sem_post(&output_sem);	
		}
		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]);
	waitpid(pid, &i, 0); /* Wait for child to terminate */
	if(!(mode&EX_OFFLINE)) {	/* !off-line execution */

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

		rio_abortable=rio_abortable_save;	// Restore abortable state

		/* Got back to Text/NVT mode */
		send_telnet_cmd(TELNET_DONT,TELNET_BINARY);
		telnet_mode&=~TELNET_MODE_BIN_RX;
	}

	pthread_mutex_unlock(&input_thread_mutex);

	return(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));
                    break;
                case 'I':   /* UART IRQ Line */
                    strcat(cmd,ultoa(cfg.com_irq,str,10));
                    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;
                case 'N':   /* Node Directory (same as SBBSNODE environment var) */
                    strcat(cmd,cfg.node_dir);
                    break;
                case 'O':   /* SysOp */
                    strcat(cmd,cfg.sys_op);
                    break;
                case 'P':   /* COM Port */
                    strcat(cmd,ultoa(online==ON_LOCAL ? 0:cfg.com_port,str,10));
                    break;
                case 'Q':   /* QWK ID */
                    strcat(cmd,cfg.sys_id);
                    break;
                case 'R':   /* Rows */
                    strcat(cmd,ultoa(rows,str,10));
                    break;
                case 'S':   /* File Spec */
                    strcat(cmd,fspec);
                    break;
                case 'T':   /* Time left in seconds */
                    gettimeleft();
                    strcat(cmd,ultoa(timeleft,str,10));
                    break;
                case 'U':   /* UART I/O Address (in hex) */
                    strcat(cmd,ultoa(cfg.com_base,str,16));
                    break;
                case 'V':   /* Synchronet Version */
                    sprintf(str,"%s%c",VERSION,REVISION);
                    break;
                case 'W':   /* Time-slice API type (mswtype) */
#if 0 //ndef __FLAT__
                    strcat(cmd,ultoa(mswtyp,str,10));
#endif
                    break;
                case 'X':
                    strcat(cmd,cfg.shell[useron.shell]->code);
                    break;
                case '&':   /* Address of msr */
                    sprintf(str,"%lu",(DWORD)&fakeriobp);
                    strcat(cmd,str);
                    break;
                case 'Y':
                    break;
                case 'Z':
                    strcat(cmd,cfg.text_dir);
                    break;
				case '~':	/* DOS-compatible (8.3) filename */
#ifdef _WIN32
					GetShortPathName(fpath,sfpath,sizeof(sfpath));
					strcat(cmd,sfpath);
#else
                    strcat(cmd,fpath);
#endif			
					break;
                case '!':   /* EXEC Directory */
                    strcat(cmd,cfg.exec_dir);
                    break;
                case '#':   /* Node number (same as SBBSNNUM environment var) */
                    sprintf(str,"%d",cfg.node_num);
                    strcat(cmd,str);
                    break;
                case '*':
                    sprintf(str,"%03d",cfg.node_num);
                    strcat(cmd,str);
                    break;
                case '$':   /* Credits */
                    strcat(cmd,ultoa(useron.cdt+useron.freecdt,str,10));
                    break;
                case '%':   /* %% for percent sign */
                    strcat(cmd,"%");
                    break;
				case '.':	/* .exe for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
					strcat(cmd,".exe");
#endif
					break;
				case '?':	/* Platform */
#ifdef __OS2__
					strcpy(str,"OS2");
#else
                default:    /* unknown specification */
                    if(isdigit(instr[i])) {
                        sprintf(str,"%0*d",instr[i]&0xf,useron.number);
                        strcat(cmd,str); }
                    break; }
            j=strlen(cmd); }
        else
            cmd[j++]=instr[i]; }
    cmd[j]=0;

    return(cmd);
}

/* The following termcap entry is nice (for Unix):

<---SNIP--->
# Handy for HyperTerminal and such
ansi-bbs|ANSI terminals (emulators):\
	:co#80:li#24:am:\
	:bs:mi:ms:pt:xn:xo:it#8:\
	:bl=^G:cr=^M:ta=^I:\
	:cm=\E[%i%d;%dH:\
	:le=\E[D:up=\E[A:do=\E[B:nd=\E[C:\
	:ho=\E[H:cl=\E[H\E[2J:ce=\E[0K:cb=\E[1K:cd=\E[0J:\
	:sc=\E[s:rc=\E[u:\
	:ic=\E[@:al=\E[L:AL=\E[%dL:\
	:dc=\E[P:DC=\E[%dP:dl=\E[M:DL=\E[%dM:\
	:so=\E[7m:se=\E[m:us=\E[4m:ue=\E[m:\
	:mb=\E[5m:mh=\E[2m:md=\E[1m:mr=\E[7m:me=\E[m:\
	:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:\
	:kD=\177:kH=\E[Y:kN=\E[U:kP=\E[V:\
	:kh=\E[2J:@7=\E[k:\
	:Co#8:pa#64:so=\E[7m:se=\E[27m:AF=\E[3%dm:AB=\E[4%dm:op=\E[39;49m:\
	:k1=\EOP:k2=\EOQ:k3=\EOw:k4=\EOx:\
	:ac=l\332m\300k\277j\331u\264t\303v\301w\302q\304x\263n\305`^Da\260f\370
g\361~\371.^Y-^Xh\261i^U0\333y\363z\362:\
	:LE=\E[%dD:RI=\E[%dC:UP=\E[%dA:DO=\E[%dB: