From 0bf855436c1a258551e86acc8e6f7744cc8b8531 Mon Sep 17 00:00:00 2001
From: Michael Long <mlong@mlong.us>
Date: Thu, 17 Dec 2020 09:47:46 -0800
Subject: [PATCH] Improvements to linux dosemu support The hardcoded
 external.bat file has been moved to a templated /exec/external.bat which can
 be modified by the sysop. ENV vars are in there to be able to customize as
 needed. emusetup.bat is no longer needed, but still supported. The actual
 dosemu command line is now stored in /exec/dosemulaunch.ini and can be
 customized for fossil or i/o command lines. the keystroke is now only sent
 for i/o, and only sent as \n instead of \r which would trigger undesired
 behavior in certain programs. The above 2 files can also be placed in door
 dirs to override. ansi.com has been removed as it was not needed. default
 external.bat includes cmd lines for share, x00, etc. (user will still need to
 provide these in xtrn/dosutils).

---
 ctrl/dosemu.conf             |  20 ++
 ctrl/sbbs.ini                |   8 +-
 exec/dosemu.ini              |  22 +++
 exec/external.bat            |  47 +++++
 install/GNUmakefile          |   5 -
 src/sbbs3/CMakeLists.txt     |   5 -
 src/sbbs3/GNUmakefile        |   6 -
 src/sbbs3/chk_ar.cpp         |   5 +-
 src/sbbs3/install/sbbsinst.c |  35 +---
 src/sbbs3/sbbs_ini.c         |  10 +-
 src/sbbs3/startup.h          |   2 +
 src/sbbs3/str_util.c         |  73 ++++++++
 src/sbbs3/str_util.h         |   3 +
 src/sbbs3/userdat.c          |   2 +-
 src/sbbs3/xtrn.cpp           | 344 +++++++++++++++++++++--------------
 src/sbbs3/xtrn_sec.cpp       |   2 +-
 16 files changed, 396 insertions(+), 193 deletions(-)
 create mode 100644 ctrl/dosemu.conf
 create mode 100644 exec/dosemu.ini
 create mode 100644 exec/external.bat

diff --git a/ctrl/dosemu.conf b/ctrl/dosemu.conf
new file mode 100644
index 0000000000..811ae914c6
--- /dev/null
+++ b/ctrl/dosemu.conf
@@ -0,0 +1,20 @@
+$_cpu = "80486"
+$_cpu_emu = "fullsim"
+$_floppy_a = ""
+$_cdrom = ""
+$_xms = (1024)
+$_ems = (1024)
+$_ems_frame = (0xe000)
+$_external_char_set = "cp437"
+$_internal_char_set = "cp437"
+$_term_updfreq = (8)
+$_layout = "us"
+$_rawkeyboard = (auto)
+$_mouse_internal = (on)
+$_mouse_dev = ""
+$_joy_device = ""
+$_lpt1 = ""
+$_lp2 = ""
+$_speaker = ""
+$_sound = (off)
+$_sb_dsp = ""
diff --git a/ctrl/sbbs.ini b/ctrl/sbbs.ini
index b83519c868..31c73dfe1c 100644
--- a/ctrl/sbbs.ini
+++ b/ctrl/sbbs.ini
@@ -98,8 +98,12 @@
 ; Must install install/termcap or terminfo to use the following TERM setting:
 ; ExternalTermANSI = ansi-bbs
 	ExternalTermDumb = dumb
-; To change the default dosemu/doscmd path, uncomment and set:
-;       DOSemuPath =
+
+; To setup the dosemu cmd path and ini:
+UseDOSemu = false
+DOSemuPath = /usr/bin/dosemu.bin
+; leave off dir path to use ctrl dir
+DOSemuConfPath = dosemu.conf
 
 ; At what size to send the current output buffer regardless of timeout
 ; ie: Send output whenever there are at least this many bytes waiting.
diff --git a/exec/dosemu.ini b/exec/dosemu.ini
new file mode 100644
index 0000000000..72ff5e7237
--- /dev/null
+++ b/exec/dosemu.ini
@@ -0,0 +1,22 @@
+; This contains the command line to execute DOSEMU
+;
+; You can copy this file to customize for any specific external programs
+; that need changes by placing a copy of this file into the external program's 
+; startup directory.
+;
+; The following substitutions will be performed on this file:
+; $TERM sets 'TERM=linux' (used on events, etc.)
+; $CTRLDIR = path to synchronet ctrl dir
+; $NODEDIR = path to synchronet node dir
+; $DOSEMUBIN = path to dosemu.bin
+; $VIRTCONF = appends virtual when needed (ie fossil) (-I"serial { virtual com 1 }")
+; $DOSEMUCONF = path to global dosemu conf
+; $EXTBAT = batch file to execute (external.bat)
+; $EXTLOG = external log (used on events)
+; add -I'keystroke "\r"' if you need that behavior back
+;
+; for com/uart/fossil i/o external programs, put it in the global "cmd" key
+; for intercept i/o programs, you can override by putting "cmd" key under [stdio] section
+cmd=/usr/bin/env $TERM HOME=$CTRLDIR QUIET=1 DOSDRIVE_D=$NODEDIR NODEDIR=$NODEDIR $DOSEMUBIN -I"video { none }" $VIRTUALCONF -f$DOSEMUCONF -E$EXTBAT -o$NODEDIRdosemu_boot.log $EXTLOG
+[stdio]
+cmd=/usr/bin/env $TERM HOME=$CTRLDIR QUIET=1 DOSDRIVE_D=$NODEDIR NODEDIR=$NODEDIR $DOSEMUBIN -I"video { none }" -I'keystroke "\n"' $VIRTUALCONF -f$DOSEMUCONF -E$EXTBAT -o$NODEDIRdosemu_boot.log $EXTLOG				 
diff --git a/exec/external.bat b/exec/external.bat
new file mode 100644
index 0000000000..9b6ffeaed5
--- /dev/null
+++ b/exec/external.bat
@@ -0,0 +1,47 @@
+@lredir D: linux\fs$NODEDIR >NUL
+@lredir E: linux\fs$XTRNDIR >NUL
+@lredir F: linux\fs$CTRLDIR >NUL
+@lredir G: linux\fs$DATADIR >NUL
+@lredir H: linux\fs$EXECDIR >NUL
+
+E:
+
+REM Switch to startup dir, unless its not defined
+REM If not defined, go to node dir (external editors use this)
+IF "%STARTDIR%"=="" D:
+IF NOT "%STARTDIR%"=="" CD %STARTDIR%
+
+REM Optionally call emusetup.bat or put that stuff here for global (in NOEMU)
+REM Looks in startup dir, then ctrl dir
+IF EXIST EMUSETUP.BAT GOTO EMULOCAL
+IF EXIST F:\EMUSETUP.BAT GOTO EMUGLOBAL
+IF EXIST E:\DOSUTILS\NUL GOTO NOEMU
+IF EXIST H:\DOSUTILS\NUL GOTO NOEMU
+ECHO ERROR: No emusetup.bat in E:\%STARTDIR% or F, or DOSUTILS is missing
+GOTO EXEC
+
+:EMULOCAL
+CALL EMUSETUP.BAT
+GOTO EXEC
+
+:EMUGLOBAL
+CALL F:\EMUSETUP.BAT
+GOTO EXEC
+
+:NOEMU
+@set PATH=%PATH%;E:\dosutils;H:\dosutils
+REM fossil driver, such as x00, bnu, or dosemu fossil.com
+rem IF "$RUNTYPE" == "FOSSIL" @fossil.com >NUL
+rem IF "$RUNTYPE" == "FOSSIL" bnu.com /P1 /L0=11520 >NUL
+IF "$RUNTYPE" == "FOSSIL" x00.exe eliminate >NUL
+REM share.exe for multinode file locking
+@share >NUL
+
+GOTO EXEC
+
+:EXEC
+$CMDLINE
+
+IF NOT "%1" == "TEST" exitemu
+
+
diff --git a/install/GNUmakefile b/install/GNUmakefile
index 7b6abd6a4c..e10ce423e6 100644
--- a/install/GNUmakefile
+++ b/install/GNUmakefile
@@ -28,7 +28,6 @@
 # NO_X = Don't include build conio library (ciolib) for X
 # NO_GTK = Don't build GTK-based sysop tools
 # X_PATH = /path/to/X (if not /usr/X11R6)
-# USE_DOSEMU = Set to 1 to enable Linux-DOSEMU support
 
 # the magic bit:
 MKFLAGS += MAKEFLAGS=
@@ -131,10 +130,6 @@ ifdef X_PATH
  MKFLAGS	+=	X_PATH=$(X_PATH)
 endif
 
-ifdef USE_DOSEMU
- MKFLAGS	+=	USE_DOSEMU=$(USE_DOSEMU)
-endif
-
 # Check for GLADE
 ifndef NO_GTK
  ifeq ($(shell pkg-config libglade-2.0 --exists && echo YES),YES)
diff --git a/src/sbbs3/CMakeLists.txt b/src/sbbs3/CMakeLists.txt
index 88947d97ea..a475dc7854 100644
--- a/src/sbbs3/CMakeLists.txt
+++ b/src/sbbs3/CMakeLists.txt
@@ -5,11 +5,6 @@ cmake_minimum_required(VERSION 2.8.11)
 INCLUDE (../build/SynchronetMacros.cmake)
 INCLUDE (CheckFunctionExists)
 
-if(UNIX)
-	set(SBBS_USE_DOSEMU FALSE
-		CACHE BOOL "Set if you intend on using dosemu on Linux"
-	)
-endif()
 set(SBBS_BUILD_JSDOCS FALSE
 	CACHE INTERNAL "DEVELOPER ONLY - Build only to run jsdocs.js (not a BBS)"
 )
diff --git a/src/sbbs3/GNUmakefile b/src/sbbs3/GNUmakefile
index b5446d7b75..6a7f6b068a 100644
--- a/src/sbbs3/GNUmakefile
+++ b/src/sbbs3/GNUmakefile
@@ -11,8 +11,6 @@
 # Optional build targets: dlls, utils, mono, all (default)				#
 #########################################################################
 
-# $Id: GNUmakefile,v 1.248 2020/04/03 19:54:31 rswindell Exp $
-
 PWD	:=	$(shell pwd)
 SRC_ROOT	?=	${PWD}/..
 include $(SRC_ROOT)/build/Common.gmake
@@ -43,10 +41,6 @@ ifdef PREFIX
  CFLAGS += -DPREFIX=$(PREFIX)
 endif
 
-ifdef USE_DOSEMU
- CFLAGS += -DUSE_DOSEMU
-endif
-
 ifdef DONT_BLAME_SYNCHRONET
  CFLAGS += -DDONT_BLAME_SYNCHRONET
 endif
diff --git a/src/sbbs3/chk_ar.cpp b/src/sbbs3/chk_ar.cpp
index 88842c7a03..95f36075e9 100644
--- a/src/sbbs3/chk_ar.cpp
+++ b/src/sbbs3/chk_ar.cpp
@@ -223,8 +223,11 @@ bool sbbs_t::ar_exp(const uchar **ptrptr, user_t* user, client_t* client)
 				result=_not;
 				if(startup->options&BBS_OPT_NO_DOS)
 					break;
-				#if defined(_WIN32) || (defined(__linux__) && defined(USE_DOSEMU)) || defined(__FreeBSD__)
+				#if defined(_WIN32) || defined(__FreeBSD__)
 					result=!_not;
+				#elif defined(__linux__)
+					if (startup->usedosemu) 
+						result=!_not;
 				#endif
 				break;
 			case AR_WIN32:
diff --git a/src/sbbs3/install/sbbsinst.c b/src/sbbs3/install/sbbsinst.c
index 60fa9a9d79..9cdb03536f 100644
--- a/src/sbbs3/install/sbbsinst.c
+++ b/src/sbbs3/install/sbbsinst.c
@@ -121,9 +121,6 @@ struct {
 	struct utsname	name;	
 	char	sbbsuser[9];		/* Historical UName limit of 8 chars */
 	char	sbbsgroup[17];		/* Can't find historical limit for group names */
-#ifdef __linux__
-	BOOL	use_dosemu;
-#endif
 } params; /* Build parameters */
 
 #define MAKEFILE "/tmp/SBBSmakefile"
@@ -230,9 +227,6 @@ int main(int argc, char **argv)
 		SAFECOPY(params.sbbsuser,p);
 	if((p=getenv("GROUP"))!=NULL)
 		SAFECOPY(params.sbbsgroup,p);
-#ifdef __linux__
-	params.use_dosemu=FALSE;
-#endif
 
 	sscanf("$Revision: 1.100 $", "%*s %s", revision);
 	umask(077);
@@ -380,9 +374,7 @@ int main(int argc, char **argv)
 		sprintf(mopt[i++],"%-27.27s%s","Make Command-line",params.make_cmdline);
 		sprintf(mopt[i++],"%-27.27s%s","File Owner",params.sbbsuser);
 		sprintf(mopt[i++],"%-27.27s%s","File Group",params.sbbsgroup);
-#ifdef __linux__
-		sprintf(mopt[i++],"%-27.27s%s","Integrate DOSEmu support",params.use_dosemu?"Yes":"No");
-#endif
+
 		sprintf(mopt[i++],"%-27.27s","Start Installation...");
 		mopt[i][0]=0;
 
@@ -488,26 +480,8 @@ int main(int argc, char **argv)
 								"\n";
 				uifc.input(WIN_MID,0,0,"",params.sbbsgroup,32,K_EDIT);
 				break;
-#ifdef __linux__
-			case 11:
-				strcpy(opt[0],"Yes");
-				strcpy(opt[1],"No");
-				opt[2][0]=0;
-				i=params.use_dosemu?0:1;
-				uifc.helpbuf=	"`Include DOSEmu Support`\n"
-								"\nToDo: Add help.";
-				i=uifc.list(WIN_MID|WIN_SAV,0,0,0,&i,0
-					,"Integrate DOSEmu support into Synchronet?",opt);
-				if(!i)
-					params.use_dosemu=TRUE;
-				else if(i==1)
-					params.use_dosemu=FALSE;
-				i=0;
-				break;
-			case 12:
-#else
+
 			case 11:
-#endif
 				install_sbbs(distlist[dist],distlist[dist]->type==LOCAL_FILE?NULL:distlist[dist]->servers[server]);
 				bail(0);
 				break;
@@ -754,11 +728,6 @@ void install_sbbs(dist_t *dist,struct server_ent_t *server)  {
 		sprintf(sbbsgroup,"SBBSGROUP=%s",params.sbbsgroup);
 		putenv(sbbsgroup);
 	}
-#ifdef __linux__
-	if(params.use_dosemu==TRUE) {
-		putenv("USE_DOSEMU=1");
-	}
-#endif
 
 	if(params.usebcc)
 		putenv("bcc=1");
diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c
index 0454ec9566..f42808d34e 100644
--- a/src/sbbs3/sbbs_ini.c
+++ b/src/sbbs3/sbbs_ini.c
@@ -251,6 +251,7 @@ void sbbs_read_ini(
 	const char*	section;
 	const char* default_term_ansi;
 	const char*	default_dosemu_path;
+	const char*	default_dosemuconf_path;
 	char		value[INI_MAX_VALUE_LEN];
 	str_list_t	list;
 	global_startup_t global_buf;
@@ -366,10 +367,14 @@ void sbbs_read_ini(
 		default_dosemu_path="/usr/local/bin/doscmd";
 	#else
 		default_dosemu_path="/usr/bin/dosemu.bin";
+		default_dosemuconf_path="";
 	#endif
 
+		bbs->usedosemu=iniGetBool(list,section,"UseDOSemu",TRUE);
 		SAFECOPY(bbs->dosemu_path
 			,iniGetString(list,section,"DOSemuPath",default_dosemu_path,value));
+		SAFECOPY(bbs->dosemuconf_path
+			,iniGetString(list,section,"DOSemuConfPath",default_dosemuconf_path,value));			
 
 		SAFECOPY(bbs->answer_sound
 			,iniGetString(list,section,strAnswerSound,nulstr,value));
@@ -839,7 +844,10 @@ BOOL sbbs_write_ini(
 			break;
 		if(!iniSetString(lp,section,"DOSemuPath",bbs->dosemu_path,&style))
 			break;
-
+		if(!iniSetString(lp,section,"DOSemuConfPath",bbs->dosemuconf_path,&style))
+			break;
+		if(!iniSetBool(lp,section,"UseDOSemu",bbs->usedosemu,&style))
+			break;
 		if(!iniSetString(lp,section,strAnswerSound,bbs->answer_sound,&style))
 			break;
 		if(!iniSetString(lp,section,strHangupSound,bbs->hangup_sound,&style))
diff --git a/src/sbbs3/startup.h b/src/sbbs3/startup.h
index ad869edfe8..e4febaaa63 100644
--- a/src/sbbs3/startup.h
+++ b/src/sbbs3/startup.h
@@ -113,12 +113,14 @@ typedef struct {
 	/* Paths */
     char    ctrl_dir[128];
     char	dosemu_path[128];
+    char    dosemuconf_path[128];
     char	temp_dir[128];
 	char	answer_sound[128];
 	char	hangup_sound[128];
 	char	ini_fname[128];
 
 	/* Miscellaneous */
+	BOOL    usedosemu;
 	char	xtrn_term_ansi[32];		/* external ANSI terminal type (e.g. "ansi-bbs") */
 	char	xtrn_term_dumb[32];		/* external dumb terminal type (e.g. "dumb") */
 	char	host_name[128];
diff --git a/src/sbbs3/str_util.c b/src/sbbs3/str_util.c
index 23457c2535..67ab24cfa4 100644
--- a/src/sbbs3/str_util.c
+++ b/src/sbbs3/str_util.c
@@ -649,6 +649,79 @@ char* ascii_str(uchar* str)
 	return((char*)str);
 }
 
+char* replace_named_values(const char* src	 
+    ,char* buf	 
+    ,size_t buflen       /* includes '\0' terminator */	 
+    ,const char* escape_seq	 
+    ,named_string_t* string_list	 
+    ,named_int_t* int_list	 
+    ,BOOL case_sensitive)	 
+ {	 
+     char    val[32];	 
+     size_t  i;	 
+     size_t  esc_len=0;	 
+     size_t  name_len;	 
+     size_t  value_len;	 
+     char*   p = buf;	 
+     int (*cmp)(const char*, const char*, size_t);	 
+ 
+     if(case_sensitive)	 
+         cmp=strncmp;	 
+     else	 
+         cmp=strnicmp;	 
+ 
+     if(escape_seq!=NULL)	 
+         esc_len = strlen(escape_seq);	 
+ 
+     while(*src && (size_t)(p-buf) < buflen-1) {	 
+         if(esc_len) {	 
+             if(cmp(src, escape_seq, esc_len)!=0) {	 
+                 *p++ = *src++;	 
+                 continue;	 
+             }	 
+             src += esc_len; /* skip the escape seq */	 
+         }	 
+         if(string_list) {	 
+             for(i=0; string_list[i].name!=NULL /* terminator */; i++) {	 
+                 name_len = strlen(string_list[i].name);	 
+                 if(cmp(src, string_list[i].name, name_len)==0) {	 
+                     value_len = strlen(string_list[i].value);	 
+                     if((p-buf)+value_len > buflen-1)        /* buffer overflow? */	 
+                         value_len = (buflen-1)-(p-buf); /* truncate value */	 
+                     memcpy(p, string_list[i].value, value_len);	 
+                     p += value_len;	 
+                     src += name_len;	 
+                     break;	 
+                 }	 
+             }	 
+             if(string_list[i].name!=NULL) /* variable match */	 
+                 continue;	 
+         }	 
+         if(int_list) {	 
+             for(i=0; int_list[i].name!=NULL /* terminator */; i++) {	 
+                 name_len = strlen(int_list[i].name);	 
+                 if(cmp(src, int_list[i].name, name_len)==0) {	 
+                     SAFEPRINTF(val,"%d",int_list[i].value);	 
+                     value_len = strlen(val);	 
+                     if((p-buf)+value_len > buflen-1)        /* buffer overflow? */	 
+                         value_len = (buflen-1)-(p-buf); /* truncate value */	 
+                     memcpy(p, val, value_len);	 
+                     p += value_len;	 
+                     src += name_len;	 
+                     break;	 
+                 }	 
+             }	 
+             if(int_list[i].name!=NULL) /* variable match */	 
+                 continue;	 
+         }	 
+
+         *p++ = *src++;	 
+     }	 
+     *p=0;   /* terminate string in destination buffer */	 
+ 
+     return(buf);	 
+ }	 
+ 
 /****************************************************************************/
 /* Condense consecutive white-space chars in a string to single spaces		*/
 /****************************************************************************/
diff --git a/src/sbbs3/str_util.h b/src/sbbs3/str_util.h
index 4ffac35a80..e0657bfe2a 100644
--- a/src/sbbs3/str_util.h
+++ b/src/sbbs3/str_util.h
@@ -39,6 +39,9 @@ DLLEXPORT char *	remove_ctrl_a(const char* instr, char* outstr);
 DLLEXPORT char 		ctrl_a_to_ascii_char(char code);
 DLLEXPORT char *	truncstr(char* str, const char* set);
 DLLEXPORT char *	ascii_str(uchar* str);
+DLLEXPORT char *    replace_named_values(const char* src ,char* buf, size_t buflen,	 
+                       const char* escape_seq, named_string_t* string_list,	 
+                       named_int_t* int_list, BOOL case_sensitive);
 DLLEXPORT char *	condense_whitespace(char* str);
 DLLEXPORT char		exascii_to_ascii_char(uchar ch);
 DLLEXPORT BOOL		findstr(const char *insearch, const char *fname);
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index 855d05724a..44f6079cef 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -1763,7 +1763,7 @@ static BOOL ar_exp(scfg_t* cfg, uchar **ptrptr, user_t* user, client_t* client)
 				#endif
 				break;
 			case AR_DOS:
-				#if defined(_WIN32) || (defined(__linux__) && defined(USE_DOSEMU)) || defined(__FreeBSD__)
+				#if defined(_WIN32) || defined(__linux__) || defined(__FreeBSD__)
 					result=!not;
 				#else
 					result=not;
diff --git a/src/sbbs3/xtrn.cpp b/src/sbbs3/xtrn.cpp
index 79a7ef73c6..9f39b4d633 100644
--- a/src/sbbs3/xtrn.cpp
+++ b/src/sbbs3/xtrn.cpp
@@ -1174,20 +1174,29 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
 		SAFECOPY(str,fullcmdline);
 		sprintf(fullcmdline,"%s -F %s",startup->dosemu_path,str);
 
-#elif defined(__linux__) && defined(USE_DOSEMU)
+#elif defined(__linux__)
 
-		/* dosemu integration  --  Ryan Underwood, <nemesis @ icequake.net> */
+		/* dosemu integration  --  originally by Ryan Underwood, <nemesis @ icequake.net> */
 
-		FILE *dosemubat;
-		int setup_override;
+		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];
 
@@ -1196,15 +1205,18 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
 		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 nodedrive[] = DOSEMU_NODE_DRIVE;
-		const char xtrndrive[] = DOSEMU_XTRN_DRIVE;
 		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 */
@@ -1214,8 +1226,9 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
 
 		SAFECOPY(xtrndir,str);
 
-		/* construct DOS equivalents for the unix directories */
-
+		SAFECOPY(xtrndir_dos,xtrndir);
+		REPLACE_CHARS(xtrndir_dos,'/','\\',p);
+		
 		SAFECOPY(ctrldir_dos,cfg.ctrl_dir);
 		REPLACE_CHARS(ctrldir_dos,'/','\\',p);
 
@@ -1234,28 +1247,52 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
 		p=lastchar(execdir_dos);
 		if (*p=='\\') *p=0;
 
-		SAFECOPY(xtrndir_dos,xtrndir);
-		REPLACE_CHARS(xtrndir_dos,'/','\\',p);
+		SAFECOPY(nodedir_dos,cfg.node_dir);
+		REPLACE_CHARS(nodedir_dos,'/','\\',p);
+ 
+		p=lastchar(nodedir_dos);
+		if (*p=='\\') *p=0;
 
-		/* 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. */
+		/* 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;
+		}
 
-		sprintf(str,"%sdosemu.conf",startup_dir);
-		if (!fexist(str)) {
+		/* 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 we can't find it in the door dir, look for a global one
-		 * in the ctrl dir. */
+		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;
+		}
 
-			sprintf(str,"%sdosemu.conf",cfg.ctrl_dir);
+		/* 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. */
-				SAFECOPY(str,"/etc/dosemu/dosemu.conf");
+				/* 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)) {
-
-					SAFECOPY(str,"/etc/dosemu.conf");
+					SAFEPRINTF(str,"/etc/%s", dosemu_cnf_fn);
 					if (!fexist(str)) {
 						errormsg(WHERE,ERR_READ,str,0);
 						return(-1);
@@ -1268,144 +1305,134 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
 		}
 		else SAFECOPY(dosemuconf,str);  /* using door-specific conf */
 
-		/* same deal for emusetup.bat. */
-
-		sprintf(str,"%semusetup.bat",startup_dir);
-		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);
-			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+"))) {
+		SAFEPRINTF2(str,"%s%s",cfg.node_dir,external_bat_fn);
+		if(!(dosemubatfp=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);
-		fprintf(dosemubat,"set PCBNODE=%d\r\n",cfg.node_num);
-		fprintf(dosemubat,"set PCBDRIVE=%s\r\n",nodedrive);
-		fprintf(dosemubat,"set PCBDIR=\\\r\n");
-
-		// let's do this cleanly like dosemu's default autoexec.bat does -wk42
-		/* clear existing redirections on dos side and */
-		/* redirect necessary drive letters to unix paths */
-		fprintf(dosemubat,"unix -s DOSDRIVE_E\r\n");
-		fprintf(dosemubat,"if '%%DOSDRIVE_E%%' == '' goto nodriveE\r\n");
-		fprintf(dosemubat,"lredir del %s\r\n",xtrndrive);
-		fprintf(dosemubat,":nodriveE\r\n");
-		fprintf(dosemubat,"lredir %s linux\\fs%s\r\n",xtrndrive,xtrndir_dos);
-
-		fprintf(dosemubat,"unix -s DOSDRIVE_F\r\n");
-		fprintf(dosemubat,"if '%%DOSDRIVE_F%%' == '' goto nodriveF\r\n");
-		fprintf(dosemubat,"lredir del %s\r\n",ctrldrive);
-		fprintf(dosemubat,":nodriveF\r\n");
-		fprintf(dosemubat,"lredir %s linux\\fs%s\r\n",ctrldrive,ctrldir_dos);
-
-		fprintf(dosemubat,"unix -s DOSDRIVE_G\r\n");
-		fprintf(dosemubat,"if '%%DOSDRIVE_G%%' == '' goto nodriveG\r\n");
-		fprintf(dosemubat,"lredir del %s\r\n",datadrive);
-		fprintf(dosemubat,":nodriveG\r\n");
-		fprintf(dosemubat,"lredir %s linux\\fs%s\r\n",datadrive,datadir_dos);
-
-		fprintf(dosemubat,"unix -s DOSDRIVE_H\r\n");
-		fprintf(dosemubat,"if '%%DOSDRIVE_H%%' == '' goto nodriveH\r\n");
-		fprintf(dosemubat,"lredir del %s\r\n",execdrive);
-		fprintf(dosemubat,":nodriveH\r\n");
-		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);
-
-		const char* gamedir = "";
+		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;
-			gamedir = getfname(str);
+			SAFECOPY(gamedir, getfname(str));
 		}
+ 
 		if(*gamedir == 0) {
 			lprintf(LOG_ERR, "No startup directory configured for DOS command-line: %s", cmdline);
 			return -1;
 		}
-		fprintf(dosemubat,"cd %s\r\n", gamedir);
-
-		if (setup_override == 1)
-			fprintf(dosemubat,"call %s\\%s\\emusetup.bat %s\r\n",xtrndrive,gamedir,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 = getfext(tok);  /*  check if it's a bat file  */
-		if (p != NULL && stricmp(p, ".bat") == 0)
-			fprintf(dosemubat,"call ");  /* if so, "call" it */
-
-		fprintf(dosemubat,"%s\r\n",cmdline);
-		fprintf(dosemubat,"exitemu\r\n");
+		/* 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)
+		if (!(mode&(EX_STDIO)) && online!=ON_LOCAL) {
 			SAFECOPY(virtualconf,"-I\"serial { virtual com 1 }\"");
-		else
+			runtype = "FOSSIL";
+		} else {
 			virtualconf[0] = '\0';
+			runtype = "STDIO";
+		}
 
-		/* Set the interception bits, since we are always going to want Synchronet
-		 * to intercept dos programs under Unix.
-		 */
+		/* now append exec/external.bat (which is editable) to this 
+		 generated file */
+		SAFEPRINTF2(str,"%s%s",startup_dir,external_bat_fn);
 
-		mode |= EX_STDIO;
+		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);
+				return(-1);
+			} 
+		}
 
-		/* 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.
-		 */
+        SAFECOPY(externalbatsrc, str); 
 
-		sprintf(str,"%sdosemu.bin",startup_dir);
-		if (!fexist(str)) {
-			SAFECOPY(dosemubinloc,(cmdstr(startup->dosemu_path,nulstr,nulstr,tok)));
+		if (!(externalbatfp=fopen(externalbatsrc,"r"))) {
+			errormsg(WHERE,ERR_OPEN,externalbatsrc,0);
+			return(-1);
+		} 
+
+		/* append the command line to the batch file */
+		SAFECOPY(tok,cmdline);
+		truncstr(tok," ");
+		p = getfext(tok);  
+		/*  check if it's a bat file  */
+		if (p != NULL && stricmp(p, ".bat") == 0) {
+			SAFEPRINTF(cmdlinebatch, "CALL %s", cmdline);
+		} else {
+			SAFECOPY(cmdlinebatch, cmdline);
 		}
-		else {
-			SAFECOPY(dosemubinloc,str);
+
+		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)) {
 			mkdir(str, 0755);
 		}
-
 		strcat(str, "/disclaimer");
 		ftouch(str);
 
 		/* Set up the command for dosemu to execute with 'unix -e'. */
-
-		sprintf(str,"%sexternal.bat",nodedrive);
+		SAFEPRINTF2(externalbat,"%s%s",nodedrive, external_bat_fn);
 
 		/* need TERM=linux for maintenance programs to work
 		 * (dosemu won't start with no controlling terminal)
@@ -1421,15 +1448,56 @@ int sbbs_t::external(const char* cmdline, long mode, const char* startup_dir)
 			log_external[0] = '\0';
 		}
 
-		/* Drum roll. */
+		/*
+		 * 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);
+				return(-1);
+			}
+		}
 
-		safe_snprintf(fullcmdline, sizeof(fullcmdline),
-		// remove unneeded redirection and fix faulty keystroke command -wk42
-		"/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_boot.log %s",
-			dosterm,cfg.ctrl_dir,cfg.node_dir,dosemubinloc,virtualconf,dosemuconf,str,cfg.node_dir,log_external);
+		/* if file found, then open and process it */
+		if ((de_launch_inifp=iniOpenFile(str, false))==NULL) {
+			errormsg(WHERE,ERR_OPEN,str, 0);
+			return(-1);
+		}         
+		de_launch_ini = iniReadFile(de_launch_inifp);
+		iniCloseFile(de_launch_inifp);
+		SAFECOPY(de_launch_cmd, "");
+		iniGetString(de_launch_ini, ROOT_SECTION, "cmd", nulstr, de_launch_cmd);
+		if (virtualconf[0] == '\0') {
+			iniGetString(de_launch_ini, "stdio", "cmd", de_launch_cmd, de_launch_cmd);
+		}
+		iniFreeStringList(de_launch_ini);
 
-		fprintf(dosemubat,"REM For debugging: %s\r\n",fullcmdline);
-		fclose(dosemubat);
+		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);
@@ -1875,7 +1943,7 @@ char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, ch
                     strncat(cmd,ultoa((ulong)cur_cps*10,str,10), avail);
                     break;
                 case 'F':   /* File path */
-#if defined(__linux__) && defined(USE_DOSEMU)
+#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);
@@ -1885,7 +1953,7 @@ char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, ch
 						strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
                     break;
                 case 'G':   /* Temp directory */
-#if defined(__linux__) && defined(USE_DOSEMU)
+#if defined(__linux__)
 					if(!native)
 						strncat(cmd, DOSEMU_TEMP_DIR, avail);
 					else
@@ -1899,7 +1967,7 @@ char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, ch
                     strncat(cmd,cid, avail);
                     break;
                 case 'J':
-#if defined(__linux__) && defined(USE_DOSEMU)
+#if defined(__linux__)
 					if(!native)
 						strncat(cmd, DOSEMU_DATA_DIR, avail);
 					else
@@ -1907,7 +1975,7 @@ char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, ch
 						strncat(cmd,cfg.data_dir, avail);
                     break;
                 case 'K':
-#if defined(__linux__) && defined(USE_DOSEMU)
+#if defined(__linux__)
 					if(!native)
 						strncat(cmd, DOSEMU_CTRL_DIR, avail);
 					else
@@ -1921,7 +1989,7 @@ char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, ch
                     strncat(cmd,ultoa(useron.min,str,10), avail);
                     break;
                 case 'N':   /* Node Directory (same as SBBSNODE environment var) */
-#if defined(__linux__) && defined(USE_DOSEMU)
+#if defined(__linux__)
 					if(!native)
 						strncat(cmd, DOSEMU_NODE_DIR, avail);
 					else
@@ -1966,7 +2034,7 @@ char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, ch
                     strncat(cmd,comspec, avail);
                     break;
                 case 'Z':
-#if defined(__linux__) && defined(USE_DOSEMU)
+#if defined(__linux__)
 					if(!native)
 						strncat(cmd, DOSEMU_TEXT_DIR, avail);
 					else
@@ -1984,7 +2052,7 @@ char* sbbs_t::cmdstr(const char *instr, const char *fpath, const char *fspec, ch
 #endif
 					break;
                 case '!':   /* EXEC Directory */
-#if defined(__linux__) && defined(USE_DOSEMU)
+#if defined(__linux__)
 					if(!native)
 						strncat(cmd, DOSEMU_EXEC_DIR, avail);
 					else
diff --git a/src/sbbs3/xtrn_sec.cpp b/src/sbbs3/xtrn_sec.cpp
index 9c71e51743..859e5e2b69 100644
--- a/src/sbbs3/xtrn_sec.cpp
+++ b/src/sbbs3/xtrn_sec.cpp
@@ -1595,7 +1595,7 @@ bool sbbs_t::exec_xtrn(uint xtrnnum)
 
 	char drop_file[MAX_PATH + 1];
 	char startup_dir[MAX_PATH + 1];
-#if defined(__linux__) && defined(USE_DOSEMU)
+#if defined(__linux__)
 	if(cfg.xtrn[xtrnnum]->cmd[0] != '?' && cfg.xtrn[xtrnnum]->cmd[0] != '*'	&& !(cfg.xtrn[xtrnnum]->misc & XTRN_NATIVE)) {
 		SAFEPRINTF2(startup_dir, "%s\\%s", DOSEMU_XTRN_DRIVE, getdirname(cfg.xtrn[xtrnnum]->path));
 		backslash(startup_dir);
-- 
GitLab