diff --git a/ctrl/dosemu.conf b/ctrl/dosemu.conf
new file mode 100644
index 0000000000000000000000000000000000000000..811ae914c64f82e620f87b5a64cf20fba650d27e
--- /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 b83519c86805740880cea7947befd6ee790a052f..31c73dfe1cf2a1d316b71248d2af8e929ec4207e 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 0000000000000000000000000000000000000000..72ff5e7237d0c09eb9f17d35c0dedd16ef8d0ace
--- /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 0000000000000000000000000000000000000000..9b6ffeaed53858ff56f53caa159d4ec574d7cab5
--- /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 7b6abd6a4ce375d8e5642c4e61e1938533cfeff0..e10ce423e64b9b4aa82445d4f3296df610c4dc5a 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 88947d97ea87867411fabdda0c742540e437b116..a475dc78542ba9ff94e1b5baaad1a372edd5db61 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 b5446d7b7580efefb4df80e7601df0765e8ba507..6a7f6b068ae32e9b6563c16e78d44055276e309c 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 88842c7a03ebd0af0f770366c6e0fb9f95976f7e..95f36075e94a78531e4056e81a5f383a952e4503 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 60fa9a9d79c35b0b52cb925bf207e1d1f2575331..9cdb03536f38491bcf04a58d8c3177c81c8bbd0d 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 0454ec9566fd1b7d286fcf3a209b98636ca787f8..f42808d34ee226f9c6725e3e6f40707f6d5e693f 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 ad869edfe8adf0e48dceca8fe219877d4ef4c1db..e4febaaa63fdb9c4628f96c14908248804de37dc 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 23457c253542eec9cc217a7236e82c8095d78497..67ab24cfa4e193c45175fa882c201872e5b884df 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 4ffac35a80b52a563ae6547b526e589ecd0bfae2..e0657bfe2a17eb7a633b4bc06dca93ec012ad6f7 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 855d05724ac410df25afd4931042a55ff4d2b861..44f6079cef58e0b753a7dc5541d7e7c10506ab84 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 79a7ef73c696b453799d641cb2ac1e89847b44e1..9f39b4d633ec903a13e2e2c33037a60c7b8a43ec 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 9c71e517439c19ce743cfec0eaa787f561cc1076..859e5e2b69626a8178037c95306e634964e2a415 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);