diff --git a/src/sbbs3/jsexec.c b/src/sbbs3/jsexec.c
new file mode 100644
index 0000000000000000000000000000000000000000..3b81c83b4d778e0f26264654fd717783cb527e8a
--- /dev/null
+++ b/src/sbbs3/jsexec.c
@@ -0,0 +1,427 @@
+/* jsexec.c */
+
+/* Execute a Synchronet JavaScript from the command-line */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2003 Rob Swindell - http://www.synchro.net/copyright.html		*
+ *																			*
+ * This program is free software; you can redistribute it and/or			*
+ * modify it under the terms of the GNU General Public License				*
+ * as published by the Free Software Foundation; either version 2			*
+ * of the License, or (at your option) any later version.					*
+ * See the GNU General Public License for more details: gpl.txt or			*
+ * http://www.fsf.org/copyleft/gpl.html										*
+ *																			*
+ * Anonymous FTP access to the most recent released source is available at	*
+ * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
+ *																			*
+ * Anonymous CVS access to the development source and modification history	*
+ * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
+ * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
+ *     (just hit return, no password is necessary)							*
+ * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
+ *																			*
+ * For Synchronet coding style and modification guidelines, see				*
+ * http://www.synchro.net/source.html										*
+ *																			*
+ * You are encouraged to submit any modifications (preferably in Unix diff	*
+ * format) via e-mail to mods@synchro.net									*
+ *																			*
+ * Note: If this box doesn't appear square, then you need to fix your tabs.	*
+ ****************************************************************************/
+
+#define JAVASCRIPT
+
+#include "sbbs.h"
+
+JSRuntime*	js_runtime;
+JSContext*	js_cx;
+JSObject*	js_glob;
+ulong		js_loop=0;
+scfg_t		scfg;
+ulong		js_max_bytes=JAVASCRIPT_MAX_BYTES;
+
+char *usage="\nusage: jsexec [-opts] module[.js] [args]\n"
+	"\navailable opts:"
+	"\n"
+	;
+
+static JSBool
+js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    uintN		i;
+    JSString*	str;
+
+    for (i = 0; i < argc; i++) {
+		if((str=JS_ValueToString(cx, argv[i]))==NULL)
+		    return(JS_FALSE);
+		puts(JS_GetStringBytes(str));
+	}
+
+	*rval = JSVAL_VOID;
+    return(JS_TRUE);
+}
+
+static JSBool
+js_print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    uintN		i;
+    JSString *	str;
+
+    for (i = 0; i < argc; i++) {
+		if((str=JS_ValueToString(cx, argv[i]))==NULL)
+		    return(JS_FALSE);
+		printf("%s",JS_GetStringBytes(str));
+	}
+	printf("\n");
+
+	*rval = JSVAL_VOID;
+    return(JS_TRUE);
+}
+
+static JSBool
+js_printf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char*		p;
+    uintN		i;
+	JSString *	fmt;
+    JSString *	str;
+	va_list		arglist[64];
+
+	if((fmt = JS_ValueToString(cx, argv[0]))==NULL)
+		return(JS_FALSE);
+
+	memset(arglist,0,sizeof(arglist));	// Initialize arglist to NULLs
+
+    for (i = 1; i < argc && i<sizeof(arglist)/sizeof(arglist[0]); i++) {
+		if(JSVAL_IS_STRING(argv[i])) {
+			if((str=JS_ValueToString(cx, argv[i]))==NULL)
+			    return(JS_FALSE);
+			arglist[i-1]=JS_GetStringBytes(str);
+		} 
+		else if(JSVAL_IS_DOUBLE(argv[i]))
+			arglist[i-1]=(char*)(unsigned long)*JSVAL_TO_DOUBLE(argv[i]);
+		else if(JSVAL_IS_INT(argv[i]) || JSVAL_IS_BOOLEAN(argv[i]))
+			arglist[i-1]=(char *)JSVAL_TO_INT(argv[i]);
+		else
+			arglist[i-1]=NULL;
+	}
+	
+	if((p=JS_vsmprintf(JS_GetStringBytes(fmt),(char*)arglist))==NULL)
+		return(JS_FALSE);
+
+	printf("%s",p);
+	JS_smprintf_free(p);
+
+	*rval = JSVAL_VOID;
+    return(JS_TRUE);
+}
+
+static JSBool
+js_alert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    JSString *	str;
+
+	if((str=JS_ValueToString(cx, argv[0]))==NULL)
+	    return(JS_FALSE);
+
+	printf("!%s\n",JS_GetStringBytes(str));
+
+	*rval = JSVAL_VOID;
+    return(JS_TRUE);
+}
+
+static JSBool
+js_confirm(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    JSString *	str;
+
+	if((str=JS_ValueToString(cx, argv[0]))==NULL)
+	    return(JS_FALSE);
+
+	*rval = BOOLEAN_TO_JSVAL(FALSE);
+	return(JS_TRUE);
+}
+
+static JSBool
+js_prompt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char		instr[81];
+    JSString *	prompt;
+    JSString *	str;
+
+	if((prompt=JS_ValueToString(cx, argv[0]))==NULL)
+	    return(JS_FALSE);
+
+	if(argc>1) {
+		if((str=JS_ValueToString(cx, argv[1]))==NULL)
+		    return(JS_FALSE);
+		SAFECOPY(instr,JS_GetStringBytes(str));
+	} else
+		instr[0]=0;
+
+	printf("%s: ",JS_GetStringBytes(prompt));
+
+	if(!fgets(instr,sizeof(instr),stdin)) {
+		*rval = JSVAL_NULL;
+		return(JS_TRUE);
+	}
+
+	if((str=JS_NewStringCopyZ(cx, instr))==NULL)
+	    return(JS_FALSE);
+
+	*rval = STRING_TO_JSVAL(str);
+    return(JS_TRUE);
+}
+
+static jsMethodSpec js_global_functions[] = {
+	{"log",				js_log,				1},
+    {"print",           js_print,           0},
+    {"printf",          js_printf,          1},	
+	{"alert",			js_alert,			1},
+	{"prompt",			js_prompt,			1},
+	{"confirm",			js_confirm,			1},
+    {0}
+};
+
+static void
+js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
+{
+	char	line[64];
+	char	file[MAX_PATH+1];
+	const char*	warning;
+
+	if(report==NULL) {
+		fprintf(stderr,"!JavaScript: %s", message);
+		return;
+    }
+
+	if(report->filename)
+		sprintf(file," %s",report->filename);
+	else
+		file[0]=0;
+
+	if(report->lineno)
+		sprintf(line," line %d",report->lineno);
+	else
+		line[0]=0;
+
+	if(JSREPORT_IS_WARNING(report->flags)) {
+		if(JSREPORT_IS_STRICT(report->flags))
+			warning="strict warning";
+		else
+			warning="warning";
+	} else
+		warning="";
+
+	fprintf(stderr,"!JavaScript %s%s%s: %s",warning,file,line,message);
+}
+
+static JSBool
+js_BranchCallback(JSContext *cx, JSScript *script)
+{
+	js_loop++;
+
+#if 0 
+	/* Terminated? */
+	if(sbbs->terminated) {
+		JS_ReportError(cx,"Terminated");
+		sbbs->js_loop=0;
+		return(JS_FALSE);
+	}
+#endif
+	/* Infinite loop? */
+	if(js_loop>JAVASCRIPT_BRANCH_LIMIT) {
+		JS_ReportError(cx,"Infinite loop (%lu branches) detected",js_loop);
+		js_loop=0;
+		return(JS_FALSE);
+	}
+	/* Give up timeslices every once in a while */
+	if(!(js_loop%JAVASCRIPT_YIELD_FREQUENCY))
+		YIELD();
+
+	if(!(js_loop%JAVASCRIPT_GC_FREQUENCY))
+		JS_MaybeGC(cx);
+
+    return(JS_TRUE);
+}
+
+static BOOL js_init(void)
+{
+	fprintf(stderr,"JavaScript: Creating runtime: %lu bytes\n"
+		,js_max_bytes);
+
+	if((js_runtime = JS_NewRuntime(js_max_bytes))==NULL)
+		return(FALSE);
+
+	fprintf(stderr,"JavaScript: Initializing context (stack: %lu bytes)\n"
+		,JAVASCRIPT_CONTEXT_STACK);
+
+    if((js_cx = JS_NewContext(js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
+		return(FALSE);
+
+	JS_SetErrorReporter(js_cx, js_ErrorReporter);
+
+	/* Global Object */
+	if((js_glob=js_CreateGlobalObject(js_cx, &scfg, js_global_functions))==NULL)
+		return(FALSE);
+
+	/* System Object */
+	if(js_CreateSystemObject(js_cx, js_glob, &scfg, time(NULL), scfg.sys_inetaddr)==NULL)
+		return(FALSE);
+
+	/* Socket Class */
+	if(js_CreateSocketClass(js_cx, js_glob)==NULL)
+		return(FALSE);
+
+	/* MsgBase Class */
+	if(js_CreateMsgBaseClass(js_cx, js_glob, &scfg)==NULL)
+		return(FALSE);
+
+	/* File Class */
+	if(js_CreateFileClass(js_cx, js_glob)==NULL)
+		return(FALSE);
+
+	/* User class */
+	if(js_CreateUserClass(js_cx, js_glob, &scfg)==NULL) 
+		return(FALSE);
+
+	/* Area Objects */
+	if(!js_CreateUserObjects(js_cx, js_glob, &scfg, NULL, NULL, NULL)) 
+		return(FALSE);
+
+	return(TRUE);
+}
+
+long js_exec(const char *fname, char** args)
+{
+	int			argc=0;
+	char		path[MAX_PATH+1];
+	JSObject*	js_scope=NULL;
+	JSScript*	js_script=NULL;
+	JSString*	arg;
+	JSObject*	argv;
+	jsval		val;
+	jsval		rval;
+	
+	if(strcspn(fname,"/\\")==strlen(fname)) {
+		sprintf(path,"%s%s",scfg.mods_dir,fname);
+		if(scfg.mods_dir[0]==0 || !fexistcase(path))
+			sprintf(path,"%s%s",scfg.exec_dir,fname);
+	} else
+		sprintf(path,"%.*s",(int)sizeof(path)-4,fname);
+	/* Add extension if not specified */
+	if(!strchr(path,'.'))
+		strcat(path,".js");
+
+	if(!fexistcase(path)) {
+		fprintf(stderr,"!Module file (%s) doesn't exist\n",path);
+		return(-1); 
+	}
+
+	js_scope=JS_NewObject(js_cx, NULL, NULL, js_glob);
+
+	if(js_scope==NULL) {
+		fprintf(stderr,"!Error creating JS scope\n");
+		return(-1);
+	}
+
+	argv=JS_NewArrayObject(js_cx, 0, NULL);
+
+	for(argc=0;args[argc];argc++) {
+		arg = JS_NewStringCopyZ(js_cx, args[argc]);
+		if(arg==NULL)
+			break;
+		val=STRING_TO_JSVAL(arg);
+		if(!JS_SetElement(js_cx, argv, argc, &val))
+			break;
+	}
+	JS_DefineProperty(js_cx, js_scope, "argv", OBJECT_TO_JSVAL(argv)
+		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
+	JS_DefineProperty(js_cx, js_scope, "argc", INT_TO_JSVAL(argc)
+		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
+
+	if((js_script=JS_CompileFile(js_cx, js_scope, path))==NULL) {
+		fprintf(stderr,"!Error executing %s\n",fname);
+		return(-1);
+	}
+
+	JS_SetBranchCallback(js_cx, js_BranchCallback);
+
+	JS_ExecuteScript(js_cx, js_scope, js_script, &rval);
+
+	JS_DestroyScript(js_cx, js_script);
+
+	JS_ClearScope(js_cx, js_scope);
+
+	JS_GC(js_cx);
+
+	return(JSVAL_TO_INT(rval));
+}
+
+/*********************/
+/* Entry point (duh) */
+/*********************/
+int main(int argc, char **argv)
+{
+	char error[512];
+	char revision[16];
+	char* module=NULL;
+	char* p;
+
+	sscanf("$Revision$", "%*s %s", revision);
+
+	fprintf(stderr,"\nJSexec v%s%c-%s (rev %s) - "
+		"Execute Synchronet JavaScript Modules\n"
+		,VERSION,REVISION
+		,PLATFORM_DESC
+		,revision
+		);
+
+	if(argc<2) {
+		fprintf(stderr,usage);
+		return(1); 
+	}
+
+	module=argv[1];
+
+	if(module==NULL) {
+		fprintf(stderr,usage);
+		return(1); 
+	}
+
+	p=getenv("SBBSCTRL");
+	if(p==NULL) {
+		fprintf(stderr,"\nSBBSCTRL environment variable not set.\n");
+		fprintf(stderr,"\nExample: SET SBBSCTRL=/sbbs/ctrl\n");
+		exit(1); 
+	}
+
+	memset(&scfg,0,sizeof(scfg));
+	scfg.size=sizeof(scfg);
+	SAFECOPY(scfg.ctrl_dir,p);
+
+	if(chdir(scfg.ctrl_dir)!=0)
+		fprintf(stderr,"!ERROR changing directory to: %s", scfg.ctrl_dir);
+
+	printf("\nLoading configuration files from %s\n",scfg.ctrl_dir);
+	if(!load_cfg(&scfg,NULL,TRUE,error)) {
+		fprintf(stderr,"!ERROR loading configuration files: %s\n",error);
+		exit(1);
+	}
+	prep_dir(scfg.data_dir, scfg.temp_dir, sizeof(scfg.temp_dir));
+
+	if(!(scfg.sys_misc&SM_LOCAL_TZ))
+		putenv("TZ=UTC0");
+
+	if(!js_init())
+		return(1);
+
+	return(js_exec(module,&argv[2]));
+}
+
diff --git a/src/sbbs3/jsexec.dsp b/src/sbbs3/jsexec.dsp
new file mode 100644
index 0000000000000000000000000000000000000000..9781d9b08b239c9b0c219b8ac8cf30da26b5a1e9
--- /dev/null
+++ b/src/sbbs3/jsexec.dsp
@@ -0,0 +1,90 @@
+# Microsoft Developer Studio Project File - Name="jsexec" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=jsexec - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE 
+!MESSAGE NMAKE /f "jsexec.mak".
+!MESSAGE 
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE 
+!MESSAGE NMAKE /f "jsexec.mak" CFG="jsexec - Win32 Debug"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "jsexec - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "jsexec - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE 
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "jsexec - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /W3 /GX /O2 /I "..\..\include\mozilla\js" /I "../xpdev" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib  kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib msvc.win32.dll.debug/sbbs.lib ../../lib/mozilla/js/win32.debug/js32.lib /nologo /subsystem:console /machine:I386 /out:"msvc.win32.exe.release/jsexec.exe"
+
+!ELSEIF  "$(CFG)" == "jsexec - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "jsexec___Win32_Debug"
+# PROP BASE Intermediate_Dir "jsexec___Win32_Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "jsexec___Win32_Debug"
+# PROP Intermediate_Dir "jsexec___Win32_Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ  /c
+# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\include\mozilla\js" /I "../xpdev" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ  /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib  kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib msvc.win32.dll.debug/sbbs.lib ../../lib/mozilla/js/win32.debug/js32.lib /nologo /subsystem:console /debug /machine:I386 /out:"msvc.win32.exe.debug/jsexec.exe" /pdbtype:sept
+
+!ENDIF 
+
+# Begin Target
+
+# Name "jsexec - Win32 Release"
+# Name "jsexec - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\jsexec.c
+# End Source File
+# End Target
+# End Project