diff --git a/src/sbbs3/js_console.cpp b/src/sbbs3/js_console.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e04cb4d432f7bacba2cf1bb878f669763746038b
--- /dev/null
+++ b/src/sbbs3/js_console.cpp
@@ -0,0 +1,905 @@
+/* js_console.cpp */
+
+/* Synchronet JavaScript "Console" Object */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2001 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.	*
+ ****************************************************************************/
+
+#include "sbbs.h"
+
+#ifdef JAVASCRIPT
+
+/*****************************/
+/* Console Object Properites */
+/*****************************/
+enum {
+	 CON_PROP_ONLINE
+	,CON_PROP_STATUS
+	,CON_PROP_LNCNTR 
+	,CON_PROP_TOS
+	,CON_PROP_ROWS
+	,CON_PROP_AUTOTERM
+	,CON_PROP_TIMEOUT			/* User inactivity timeout reference */
+	,CON_PROP_TIMELEFT_WARN		/* low timeleft warning flag */
+};
+
+static JSBool js_console_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+	ulong		val;
+    jsint       tiny;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+    tiny = JSVAL_TO_INT(id);
+
+	switch(tiny) {
+		case CON_PROP_ONLINE:
+			val=sbbs->online;
+			break;
+		case CON_PROP_STATUS:
+			val=sbbs->console;
+			break;
+		case CON_PROP_LNCNTR:
+			val=sbbs->lncntr;
+			break;
+		case CON_PROP_TOS:
+			val=sbbs->tos;
+			break;
+		case CON_PROP_ROWS:
+			val=sbbs->rows;
+			break;
+		case CON_PROP_AUTOTERM:
+			val=sbbs->autoterm;
+			break;
+		case CON_PROP_TIMEOUT:
+			val=sbbs->timeout;
+			break;
+		case CON_PROP_TIMELEFT_WARN:
+			val=sbbs->timeleft_warn;
+			break;
+		default:
+			return(JS_TRUE);
+	}
+
+	*vp = INT_TO_JSVAL(val);
+
+	return(JS_TRUE);
+}
+
+static JSBool js_console_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+	long		val;
+    jsint       tiny;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+    tiny = JSVAL_TO_INT(id);
+
+	JS_ValueToInt32(cx, *vp, &val);
+
+	switch(tiny) {
+		case CON_PROP_ONLINE:
+			sbbs->online=val;
+			break;
+		case CON_PROP_STATUS:
+			sbbs->console=val;
+			break;
+		case CON_PROP_LNCNTR:
+			sbbs->lncntr=val;
+			break;
+		case CON_PROP_TOS:
+			sbbs->tos=val;
+			break;
+		case CON_PROP_ROWS:
+			sbbs->rows=val;
+			break;
+		case CON_PROP_AUTOTERM:
+			sbbs->autoterm=val;
+			break;
+		case CON_PROP_TIMEOUT:
+			sbbs->timeout=val;
+			break;
+		case CON_PROP_TIMELEFT_WARN:
+			sbbs->timeleft_warn=val;
+			break;
+		default:
+			return(JS_TRUE);
+	}
+
+	return(JS_TRUE);
+}
+
+#define CON_PROP_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY
+
+static struct JSPropertySpec js_console_properties[] = {
+/*		 name				,tinyid					,flags			,getter,setter	*/
+
+	{	"online"			,CON_PROP_ONLINE		,CON_PROP_FLAGS	,NULL,NULL},
+	{	"status"			,CON_PROP_STATUS		,CON_PROP_FLAGS	,NULL,NULL},
+	{	"line_counter"		,CON_PROP_LNCNTR 		,CON_PROP_FLAGS	,NULL,NULL},
+	{	"top_of_screen"		,CON_PROP_TOS			,CON_PROP_FLAGS	,NULL,NULL},
+	{	"rows"				,CON_PROP_ROWS			,CON_PROP_FLAGS	,NULL,NULL},
+	{	"autoterm"			,CON_PROP_AUTOTERM		,CON_PROP_FLAGS	,NULL,NULL},
+	{	"timeout"			,CON_PROP_TIMEOUT		,CON_PROP_FLAGS	,NULL,NULL},
+	{	"timeleft_warning"	,CON_PROP_TIMELEFT_WARN	,CON_PROP_FLAGS	,NULL,NULL},
+	{0}
+};
+
+/**************************/
+/* Console Object Methods */
+/**************************/
+
+static JSBool
+js_inkey(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char		key[2];
+	long		mode=0;
+	sbbs_t*		sbbs;
+    JSString*	js_str;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if(argc && JSVAL_IS_INT(argv[0]))
+		mode=JSVAL_TO_INT(argv[0]);
+	key[0]=sbbs->inkey(mode);
+	key[1]=0;
+
+	js_str = JS_NewStringCopyZ(cx, key);
+	*rval = STRING_TO_JSVAL(js_str);
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_getkey(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char		key[2];
+	long		mode=0;
+	sbbs_t*		sbbs;
+    JSString*	js_str;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if(argc && JSVAL_IS_INT(argv[0]))
+		mode=JSVAL_TO_INT(argv[0]);
+	key[0]=sbbs->getkey(mode);
+	key[1]=0;
+
+	js_str = JS_NewStringCopyZ(cx, key);
+	*rval = STRING_TO_JSVAL(js_str);
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_getstr(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char		*p;
+	long		mode=0;
+	uintN		i;
+	size_t		maxlen=0;
+	sbbs_t*		sbbs;
+    JSString*	js_str=NULL;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	for(i=0;i<argc;i++) {
+		if(JSVAL_IS_INT(argv[i])) {
+			if(!maxlen)
+				maxlen=JSVAL_TO_INT(argv[i]);
+			else
+				mode=JSVAL_TO_INT(argv[i]);
+			continue;
+		}
+		if(JSVAL_IS_STRING(argv[i])) {
+			js_str = JS_ValueToString(cx, argv[i]);
+			if (!js_str)
+			    return(JS_FALSE);
+		}
+	}
+
+	if(!maxlen) maxlen=128;
+
+	if((p=(char *)calloc(1,maxlen+1))==NULL)
+		return(JS_FALSE);
+
+	if(js_str!=NULL)
+		sprintf(p,"%.*s",maxlen,JS_GetStringBytes(js_str));
+
+	sbbs->getstr(p,maxlen,mode);
+
+	js_str = JS_NewStringCopyZ(cx, p);
+
+	free(p);
+
+	*rval = STRING_TO_JSVAL(js_str);
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_getnum(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	ulong		maxnum=~0;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if(argc && JSVAL_IS_INT(argv[0]))
+		maxnum=JSVAL_TO_INT(argv[0]);
+
+	*rval = INT_TO_JSVAL(sbbs->getnum(maxnum));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_getkeys(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char		key[2];
+	uintN		i;
+	long		val;
+	ulong		maxnum=~0;
+	sbbs_t*		sbbs;
+    JSString*	js_str=NULL;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	for(i=0;i<argc;i++) {
+		if(JSVAL_IS_INT(argv[i])) {
+			maxnum=JSVAL_TO_INT(argv[i]);
+			continue;
+		}
+		if(JSVAL_IS_STRING(argv[i])) {
+			js_str = JS_ValueToString(cx, argv[i]);
+		}
+	}
+	if (!js_str)
+		return(JS_FALSE);
+
+	val=sbbs->getkeys(JS_GetStringBytes(js_str),maxnum);
+
+	if(val==-1) {			// abort
+		*rval = INT_TO_JSVAL(0);
+	} else if(val<0) {		// number
+		val&=~0x80000000;
+		*rval = INT_TO_JSVAL(val);
+	} else {				// key
+		key[0]=(uchar)val;
+		key[1]=0;
+		js_str = JS_NewStringCopyZ(cx, key);
+		*rval = STRING_TO_JSVAL(js_str);
+	}
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_gettemplate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char		str[128];
+	long		mode=0;
+	uintN		i;
+	sbbs_t*		sbbs;
+    JSString*	js_str=NULL;
+    JSString*	js_fmt=NULL;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	for(i=0;i<argc;i++) {
+		if(JSVAL_IS_STRING(argv[i])) {
+			if(js_fmt==NULL)
+				js_fmt = JS_ValueToString(cx, argv[i]);
+			else
+				js_str = JS_ValueToString(cx, argv[i]);
+		} else if (JSVAL_IS_INT(argv[i]))
+			mode=JSVAL_TO_INT(argv[i]);
+	}
+
+	if(js_fmt==NULL)
+		return(JS_FALSE);
+
+	if(js_str==NULL)
+		str[0]=0;
+	else
+		sprintf(str, "%.*s", sizeof(str)-1, JS_GetStringBytes(js_str));
+
+	sbbs->gettmplt(str,JS_GetStringBytes(js_fmt),mode);
+	js_str = JS_NewStringCopyZ(cx, str);
+	*rval = STRING_TO_JSVAL(js_str);
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_ungetstr(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char*		p;
+	sbbs_t*		sbbs;
+    JSString*	js_str;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+	
+	if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
+		return(JS_FALSE);
+
+	p=JS_GetStringBytes(js_str);
+
+	while(p && *p)
+		sbbs->ungetkey(*(p++));
+	
+    return(JS_TRUE);
+}
+
+static JSBool
+js_yesno(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+    JSString*	js_str;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+	
+	if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
+		return(JS_FALSE);
+
+	*rval = BOOLEAN_TO_JSVAL(sbbs->yesno(JS_GetStringBytes(js_str)));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_noyes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+    JSString*	js_str;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+	
+	if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
+		return(JS_FALSE);
+
+	*rval = BOOLEAN_TO_JSVAL(sbbs->noyes(JS_GetStringBytes(js_str)));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_mnemonics(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+    JSString*	js_str;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
+		return(JS_FALSE);
+	
+	sbbs->mnemonics(JS_GetStringBytes(js_str));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_clear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->CLS;
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_clearline(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->clearline();
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_crlf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->outchar(CR);
+	sbbs->outchar(LF);
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_attr(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->attr(JSVAL_TO_INT(argv[0]));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_pause(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->pause();
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    JSString*	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	str = JS_ValueToString(cx, argv[0]);
+	if (!str)
+		return(JS_FALSE);
+
+	sbbs->bputs(JS_GetStringBytes(str));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    JSString*	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	str = JS_ValueToString(cx, argv[0]);
+	if (!str)
+		return(JS_FALSE);
+
+	sbbs->rputs(JS_GetStringBytes(str));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_putmsg(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	long		mode=0;
+    JSString*	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	str = JS_ValueToString(cx, argv[0]);
+	if (!str)
+		return(JS_FALSE);
+
+	if(argc>1 && JSVAL_IS_INT(argv[1]))
+		mode=JSVAL_TO_INT(argv[1]);
+
+	sbbs->putmsg(JS_GetStringBytes(str),mode);
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_printfile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	long		mode=0;
+    JSString*	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	str = JS_ValueToString(cx, argv[0]);
+	if (!str)
+		return(JS_FALSE);
+
+	if(argc>1 && JSVAL_IS_INT(argv[1]))
+		mode=JSVAL_TO_INT(argv[1]);
+
+	sbbs->printfile(JS_GetStringBytes(str),mode);
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_printtail(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	int			lines=0;
+	long		mode=0;
+	uintN		i;
+	sbbs_t*		sbbs;
+    JSString*	js_str=NULL;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	for(i=0;i<argc;i++) {
+		if(JSVAL_IS_INT(argv[i])) {
+			if(!lines)
+				lines=JSVAL_TO_INT(argv[i]);
+			else
+				mode=JSVAL_TO_INT(argv[i]);
+		} else if(JSVAL_IS_STRING(argv[i]))
+			js_str = JS_ValueToString(cx, argv[i]);
+	}
+
+	if(js_str==NULL)
+		return(JS_FALSE);
+
+	if(!lines) 
+		lines=5;
+
+	sbbs->printtail(JS_GetStringBytes(js_str),lines,mode);
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_menu(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    JSString*	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	str = JS_ValueToString(cx, argv[0]);
+	if (!str)
+		return(JS_FALSE);
+
+	sbbs->menu(JS_GetStringBytes(str));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_uselect(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	uintN		i;
+	int			num=0;
+	char*		title;
+	char*		item;
+	char*		ar_str;
+	uchar*		ar;
+	sbbs_t*		sbbs;
+    JSString*	js_str;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+	
+	if(!argc) {
+		*rval = INT_TO_JSVAL(sbbs->uselect(0,0,NULL,NULL,NULL));
+		return(JS_TRUE);
+	}
+	
+	for(i=0;i<argc;i++) {
+		if(!JSVAL_IS_STRING(argv[i])) {
+			num = JSVAL_TO_INT(argv[i]);
+			continue;
+		}
+		if((js_str=JS_ValueToString(cx, argv[i]))==NULL)
+			return(JS_FALSE);
+
+		if(title==NULL) 
+			title=JS_GetStringBytes(js_str);
+		else if(item==NULL)
+			item=JS_GetStringBytes(js_str);
+		else {
+			ar_str=JS_GetStringBytes(js_str);
+			ar=arstr(NULL,ar_str,&sbbs->cfg);
+		}
+	}
+
+	*rval = INT_TO_JSVAL(sbbs->uselect(1, num, title, item, ar));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_center(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    JSString*	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	str = JS_ValueToString(cx, argv[0]);
+	if (!str)
+		return(JS_FALSE);
+
+	sbbs->center(JS_GetStringBytes(str));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_saveline(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->slatr[sbbs->slcnt]=sbbs->latr; 
+	sprintf(sbbs->slbuf[sbbs->slcnt<SAVE_LINES ? sbbs->slcnt++ : sbbs->slcnt] 
+			,"%.*s",sbbs->lbuflen,sbbs->lbuf); 
+	sbbs->lbuflen=0; 
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_restoreline(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->lbuflen=0; 
+	sbbs->attr(sbbs->slatr[--sbbs->slcnt]);
+	sbbs->rputs(sbbs->slbuf[sbbs->slcnt]); 
+	sbbs->curatr=LIGHTGRAY;
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_ansi_save(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->ANSI_SAVE();
+
+    return(JS_TRUE);
+}
+
+
+static JSBool
+js_ansi_restore(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->ANSI_RESTORE();
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_ansi_gotoxy(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if(argc<2)
+		return(JS_FALSE);
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->GOTOXY(JSVAL_TO_INT(argv[0]),JSVAL_TO_INT(argv[1]));
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_ansi_up(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if(argc)
+		sbbs->rprintf("\x1b[%dA",JSVAL_TO_INT(argv[0]));
+	else
+		sbbs->rputs("\x1b[A");
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_ansi_down(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if(argc)
+		sbbs->rprintf("\x1b[%dB",JSVAL_TO_INT(argv[0]));
+	else
+		sbbs->rputs("\x1b[B");
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_ansi_right(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if(argc)
+		sbbs->rprintf("\x1b[%dC",JSVAL_TO_INT(argv[0]));
+	else
+		sbbs->rputs("\x1b[C");
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_ansi_left(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if(argc)
+		sbbs->rprintf("\x1b[%dD",JSVAL_TO_INT(argv[0]));
+	else
+		sbbs->rputs("\x1b[D");
+
+    return(JS_TRUE);
+}
+
+static JSBool
+js_ansi_getlines(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	sbbs->ansi_getlines();
+
+    return(JS_TRUE);
+}
+
+static JSFunctionSpec js_console_functions[] = {
+	{"inkey",			js_inkey,			0},		// get key - no wait 
+	{"getkey",			js_getkey,			0},		// get key - with wait 
+	{"getstr",			js_getstr,			0},		// get string 
+	{"getnum",			js_getnum,			0},		// get number 
+	{"getkeys",			js_getkeys,			1},		// get one of a list of keys 
+	{"gettemplate",		js_gettemplate,		1},		// get a string based on template 
+	{"ungetstr",		js_ungetstr,		1},		// put a string in the keyboard buffer 
+	{"yesno",			js_yesno,			1},		// Y/n question 
+	{"noyes",			js_noyes,			1},		// N/y question 
+	{"mnemonics",		js_mnemonics,		1},		// mnemonics input 
+	{"clear",           js_clear,			0},		// clear screen 
+	{"clearline",       js_clearline,		0},		// clear current line 
+	{"crlf",            js_crlf,			0},		// output cr/lf 
+	{"attr",			js_attr,			1},		// set current text attribute 
+	{"pause",			js_pause,			0},		// pause 
+	{"print",			js_print,			1},		// display a string (supports ^A and @-codes) 
+	{"write",			js_write,			1},		// display a raw string 
+	{"putmsg",			js_putmsg,			1},		// display message text (^A, @-codes, etc) with mode 
+	{"center",			js_center,			1},		// display a string centered on the screen 
+	{"printfile",		js_printfile,		1},		// print a file, optional mode
+	{"printtail",		js_printtail,		2},		// print last x lines of file, optional mode
+	{"menu",			js_menu,			1},		// print menu (auto-extension) 
+	{"uselect",			js_uselect,			0},		// user selection menu
+	{"saveline",		js_saveline,		0},		// save last output line 
+	{"restoreline",		js_restoreline,		0},		// restore last output line 
+	{"ansi_pushxy",		js_ansi_save,		0},
+	{"ansi_popxy",		js_ansi_restore,	0},
+	{"ansi_gotoxy",		js_ansi_gotoxy,		2},
+	{"ansi_up",			js_ansi_up,			0},
+	{"ansi_down",		js_ansi_down,		0},
+	{"ansi_right",		js_ansi_right,		0},
+	{"ansi_left",		js_ansi_left,		0},
+	{"ansi_getlines",	js_ansi_getlines,	0},
+	{0}
+};
+
+
+static JSClass js_console_class = {
+     "Console"				/* name			*/
+    ,0						/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,js_console_get		/* getProperty	*/
+	,js_console_set		/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,JS_FinalizeStub		/* finalize		*/
+};
+
+JSObject* js_CreateConsoleObject(JSContext* cx, JSObject* parent)
+{
+	JSObject* obj;
+
+	obj = JS_DefineObject(cx, parent, "console", &js_console_class, NULL, 0);
+
+	if(obj==NULL)
+		return(NULL);
+
+	if(!JS_DefineProperties(cx, obj, js_console_properties))
+		return(NULL);
+
+	if (!JS_DefineFunctions(cx, obj, js_console_functions)) 
+		return(NULL);
+
+	return(obj);
+}
+
+#endif	/* JAVSCRIPT */
\ No newline at end of file
diff --git a/src/sbbs3/js_file_area.c b/src/sbbs3/js_file_area.c
new file mode 100644
index 0000000000000000000000000000000000000000..efcf98aafa1d41bc27994072c922c9871541d28a
--- /dev/null
+++ b/src/sbbs3/js_file_area.c
@@ -0,0 +1,158 @@
+/* js_file_area.c */
+
+/* Synchronet JavaScript "File Area" Object */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2001 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.	*
+ ****************************************************************************/
+
+#include "sbbs.h"
+
+#ifdef JAVASCRIPT
+
+static JSClass js_file_area_class = {
+     "FileArea"				/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,JS_PropertyStub		/* getProperty	*/
+	,JS_PropertyStub		/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,JS_FinalizeStub		/* finalize		*/
+};
+
+JSObject* DLLCALL js_CreateFileAreaObject(scfg_t* cfg, JSContext* cx, JSObject* parent, user_t* user
+										  ,char* html_index_file)
+{
+	char		vpath[MAX_PATH+1];
+	JSObject*	areaobj;
+	JSObject*	libobj;
+	JSObject*	dirobj;
+	JSObject*	lib_list;
+	JSObject*	dir_list;
+	jsval		val;
+	jsint		index;
+	uint		l,d;
+
+	areaobj = JS_DefineObject(cx, parent, "file_area", &js_file_area_class, NULL, 0);
+
+	if(areaobj==NULL)
+		return(NULL);
+
+	/* lib_list[] */
+	if((lib_list=JS_NewArrayObject(cx, 0, NULL))==NULL) 
+		return(NULL);
+
+	val=OBJECT_TO_JSVAL(lib_list);
+	if(!JS_SetProperty(cx, areaobj, "lib_list", &val)) 
+		return(NULL);
+
+	for(l=0;l<cfg->total_libs;l++) {
+
+		if(!chk_ar(cfg,cfg->lib[l]->ar,user))
+			continue;
+
+		if((libobj=JS_NewObject(cx, &js_file_area_class, NULL, NULL))==NULL)
+			return(NULL);
+
+		val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->lib[l]->sname));
+		if(!JS_SetProperty(cx, libobj, "name", &val))
+			return(NULL);
+
+		val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->lib[l]->lname));
+		if(!JS_SetProperty(cx, libobj, "description", &val))
+			return(NULL);
+
+		sprintf(vpath,"/%s/%s",cfg->lib[l]->sname,html_index_file);
+		val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx, vpath));
+		if(!JS_SetProperty(cx, libobj, "link", &val))
+			return(NULL);
+
+		/* dir_list[] */
+		if((dir_list=JS_NewArrayObject(cx, 0, NULL))==NULL) 
+			return(NULL);
+
+		val=OBJECT_TO_JSVAL(dir_list);
+		if(!JS_SetProperty(cx, libobj, "dir_list", &val)) 
+			return(NULL);
+
+
+		for(d=0;d<cfg->total_dirs;d++) {
+			if(cfg->dir[d]->lib!=l)
+				continue;
+			if(!chk_ar(cfg,cfg->dir[d]->ar,user))
+				continue;
+
+			if((dirobj=JS_NewObject(cx, &js_file_area_class, NULL, NULL))==NULL)
+				return(NULL);
+
+			val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->dir[d]->code));
+			if(!JS_SetProperty(cx, dirobj, "code", &val))
+				return(NULL);
+
+			val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->dir[d]->sname));
+			if(!JS_SetProperty(cx, dirobj, "name", &val))
+				return(NULL);
+
+			val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->dir[d]->lname));
+			if(!JS_SetProperty(cx, dirobj, "description", &val))
+				return(NULL);
+
+			sprintf(vpath,"/%s/%s/%s"
+				,cfg->lib[l]->sname
+				,cfg->dir[d]->code
+				,html_index_file);
+
+			val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx, vpath));
+			if(!JS_SetProperty(cx, dirobj, "link", &val))
+				return(NULL);
+
+			if(!JS_GetArrayLength(cx, dir_list, &index))
+				return(NULL);
+
+			val=OBJECT_TO_JSVAL(dirobj);
+			JS_SetElement(cx, dir_list, index, &val);
+		}
+
+		if(!JS_GetArrayLength(cx, lib_list, &index))
+			return(NULL);
+
+		val=OBJECT_TO_JSVAL(libobj);
+		JS_SetElement(cx, lib_list, index, &val);
+	}
+
+	return(areaobj);
+}
+
+#endif	/* JAVSCRIPT */
\ No newline at end of file
diff --git a/src/sbbs3/js_global.c b/src/sbbs3/js_global.c
new file mode 100644
index 0000000000000000000000000000000000000000..793e96ec191c421ae579ed61a74cb801cfb3815f
--- /dev/null
+++ b/src/sbbs3/js_global.c
@@ -0,0 +1,188 @@
+/* js_global.c */
+
+/* Synchronet JavaScript "global" object properties/methods for all servers */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2001 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.	*
+ ****************************************************************************/
+
+#include "sbbs.h"
+
+#ifdef JAVASCRIPT
+
+static JSBool
+js_load(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char		path[MAX_PATH+1];
+    uintN		i;
+    JSString*	str;
+    const char*	filename;
+    JSScript*	script;
+    JSBool		ok;
+    jsval		result;
+	scfg_t*		cfg;
+
+	if((cfg=(scfg_t*)JS_GetPrivate(cx,obj))==NULL)
+		return JS_FALSE;
+
+    for (i = 0; i < argc; i++) {
+		str = JS_ValueToString(cx, argv[i]);
+		if (!str)
+			return JS_FALSE;
+		argv[i] = STRING_TO_JSVAL(str);
+		filename = JS_GetStringBytes(str);
+		errno = 0;
+		if(!strchr(filename,BACKSLASH))
+			sprintf(path,"%s%s",cfg->exec_dir,filename);
+		else
+			strcpy(path,filename);
+		script = JS_CompileFile(cx, obj, path);
+		if (!script)
+			ok = JS_FALSE;
+		else {
+			ok = JS_ExecuteScript(cx, obj, script, &result);
+			JS_DestroyScript(cx, script);
+			}
+		if (!ok)
+			return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+static JSBool
+js_format(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char*		p;
+    uintN		i;
+	JSString *	fmt;
+    JSString *	str;
+	va_list		arglist[64];
+
+	fmt = JS_ValueToString(cx, argv[0]);
+	if (!fmt)
+		return JS_FALSE;
+
+    for (i = 1; i < argc && i<sizeof(arglist)/sizeof(arglist[0]); i++) {
+		if(JSVAL_IS_STRING(argv[i])) {
+			str = JS_ValueToString(cx, argv[i]);
+			if (!str)
+			    return JS_FALSE;
+			arglist[i-1]=JS_GetStringBytes(str);
+		} else if(JSVAL_IS_INT(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);
+
+	str = JS_NewStringCopyZ(cx, p);
+	JS_smprintf_free(p);
+
+	*rval = STRING_TO_JSVAL(str);
+
+    return JS_TRUE;
+}
+
+static JSBool
+js_mswait(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	int val=1;
+
+	if(argc)
+		val=JSVAL_TO_INT(argv[0]);
+	mswait(val);
+
+	return(JS_TRUE);
+}
+
+static JSBool
+js_beep(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	int freq=500;
+	int	dur=500;
+
+	if(argc)
+		freq=JSVAL_TO_INT(argv[0]);
+	if(argc>1)
+		dur=JSVAL_TO_INT(argv[1]);
+
+	sbbs_beep(freq,dur);
+
+	return(JS_TRUE);
+}
+
+static JSBool
+js_exit(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	return(JS_FALSE);
+}
+
+static JSClass js_global_class ={
+        "Global",
+		JSCLASS_HAS_PRIVATE, /* needed for scfg_t ptr */
+        JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub, 
+        JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub 
+    }; 
+
+static JSFunctionSpec js_global_functions[] = {
+	{"exit",			js_exit,			0},		/* stop execution */
+	{"load",            js_load,            1},		/* Load and execute a javascript file */
+	{"format",			js_format,			1},		/* return a formatted string (ala printf) */
+	{"mswait",			js_mswait,			0},		/* millisecond wait/sleep routine */
+	{"sleep",			js_mswait,			0},		/* millisecond wait/sleep routine */
+	{"beep",			js_beep,			0},		/* local beep (freq, dur) */
+	{0}
+};
+
+JSObject* DLLCALL js_CreateGlobalObject(scfg_t* cfg, JSContext* cx)
+{
+	JSObject*	glob;
+
+	if((glob = JS_NewObject(cx, &js_global_class, NULL, NULL)) ==NULL)
+		return(NULL);
+
+	if (!JS_InitStandardClasses(cx, glob))
+		return(NULL);
+
+	if (!JS_DefineFunctions(cx, glob, js_global_functions)) 
+		return(NULL);
+
+	if(!JS_SetPrivate(cx, glob, cfg))	/* Store a pointer to scfg_t */
+		return(NULL);
+
+	return(glob);
+}
+
+#endif	/* JAVSCRIPT */
\ No newline at end of file
diff --git a/src/sbbs3/js_system.c b/src/sbbs3/js_system.c
new file mode 100644
index 0000000000000000000000000000000000000000..756a819b1b64d54ff4dace6ae3eb803ebe8164a6
--- /dev/null
+++ b/src/sbbs3/js_system.c
@@ -0,0 +1,623 @@
+/* js_system.c */
+
+/* Synchronet JavaScript "system" Object */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2001 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.	*
+ ****************************************************************************/
+
+#include "sbbs.h"
+
+#ifdef JAVASCRIPT
+
+/* System Object Properites */
+enum {
+	 SYS_PROP_NAME
+	,SYS_PROP_OP
+	,SYS_PROP_ID
+	,SYS_PROP_MISC
+	,SYS_PROP_PSNAME
+	,SYS_PROP_PSNUM
+	,SYS_PROP_INETADDR
+	,SYS_PROP_LOCATION
+	,SYS_PROP_TIMEZONE
+	,SYS_PROP_PWDAYS
+	,SYS_PROP_DELDAYS
+
+	,SYS_PROP_LASTUSERON
+	,SYS_PROP_FREEDISKSPACE
+
+	,SYS_PROP_NODES
+	,SYS_PROP_LASTNODE
+
+};
+
+static JSBool js_system_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+    jsint       tiny;
+	scfg_t*		cfg;
+
+	if((cfg=(scfg_t*)JS_GetPrivate(cx,obj))==NULL)
+		return JS_FALSE;
+
+    tiny = JSVAL_TO_INT(id);
+
+	switch(tiny) {
+		case SYS_PROP_NAME:
+	        *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->sys_name));
+			break;
+		case SYS_PROP_OP:
+			*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->sys_op));
+			break;
+		case SYS_PROP_ID:
+			*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->sys_id));
+			break;
+		case SYS_PROP_MISC:
+			*vp = INT_TO_JSVAL(cfg->sys_misc);
+			break;
+		case SYS_PROP_PSNAME:
+			*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->sys_psname));
+			break;
+		case SYS_PROP_PSNUM:
+			*vp = INT_TO_JSVAL(cfg->sys_psnum);
+			break;
+		case SYS_PROP_INETADDR:
+			*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->sys_inetaddr));
+			break;
+		case SYS_PROP_LOCATION:
+			*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cfg->sys_location));
+			break;
+		case SYS_PROP_TIMEZONE:
+			*vp = INT_TO_JSVAL(cfg->sys_timezone);
+			break;
+		case SYS_PROP_NODES:
+			*vp = INT_TO_JSVAL(cfg->sys_nodes);
+			break;
+		case SYS_PROP_LASTNODE:
+			*vp = INT_TO_JSVAL(cfg->sys_lastnode);
+			break;
+		case SYS_PROP_PWDAYS:
+			*vp = INT_TO_JSVAL(cfg->sys_pwdays);
+			break;
+		case SYS_PROP_DELDAYS:
+			*vp = INT_TO_JSVAL(cfg->sys_deldays);
+			break;
+
+		case SYS_PROP_LASTUSERON:
+			*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, lastuseron));
+			break;
+		case SYS_PROP_FREEDISKSPACE:
+			*vp = INT_TO_JSVAL(getfreediskspace(cfg->temp_dir));
+			break;
+
+	}
+
+	return(TRUE);
+}
+
+static JSBool js_system_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+    jsint       tiny;
+	scfg_t*		cfg;
+
+	if((cfg=(scfg_t*)JS_GetPrivate(cx,obj))==NULL)
+		return JS_FALSE;
+
+    tiny = JSVAL_TO_INT(id);
+
+	switch(tiny) {
+		case SYS_PROP_MISC:
+			JS_ValueToInt32(cx, *vp, &cfg->sys_misc);
+			break;
+	}
+
+	return(TRUE);
+}
+
+
+#define SYSOBJ_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY
+
+static struct JSPropertySpec js_system_properties[] = {
+/*		 name,		tinyid,				flags,				getter,	setter	*/
+
+	{	"name",		SYS_PROP_NAME,		SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"operator",	SYS_PROP_OP,		SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"qwk_id",	SYS_PROP_ID,		SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"settings",	SYS_PROP_MISC,		JSPROP_ENUMERATE,	NULL,	NULL },
+	{	"psname",	SYS_PROP_PSNAME,	SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"psnum",	SYS_PROP_PSNUM,		SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"inetaddr",	SYS_PROP_INETADDR,	SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"location",	SYS_PROP_LOCATION,	SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"timezone",	SYS_PROP_TIMEZONE,	SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"pwdays",	SYS_PROP_PWDAYS,	SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"deldays",	SYS_PROP_DELDAYS,	SYSOBJ_FLAGS,		NULL,	NULL },
+
+	{	"nodes",	SYS_PROP_NODES,		SYSOBJ_FLAGS,		NULL,	NULL },
+	{	"lastnode",	SYS_PROP_LASTNODE,	SYSOBJ_FLAGS,		NULL,	NULL },
+
+	{	"freediskspace", SYS_PROP_FREEDISKSPACE,	SYSOBJ_FLAGS,	NULL,	NULL },
+	{0}
+};
+
+static JSClass js_system_class = {
+     "System"				/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,js_system_get			/* getProperty	*/
+	,js_system_set			/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,JS_FinalizeStub		/* finalize		*/
+};
+
+/* System Stats Propertiess */
+enum {
+	 SYSSTAT_PROP_LOGONS
+	,SYSSTAT_PROP_LTODAY
+	,SYSSTAT_PROP_TIMEON
+	,SYSSTAT_PROP_TTODAY
+	,SYSSTAT_PROP_ULS
+	,SYSSTAT_PROP_ULB
+	,SYSSTAT_PROP_DLS
+	,SYSSTAT_PROP_DLB
+	,SYSSTAT_PROP_PTODAY
+	,SYSSTAT_PROP_ETODAY
+	,SYSSTAT_PROP_FTODAY
+	,SYSSTAT_PROP_NUSERS
+
+	,SYSSTAT_PROP_TOTALUSERS
+	,SYSSTAT_PROP_TOTALFILES
+	,SYSSTAT_PROP_TOTALMSGS
+	,SYSSTAT_PROP_TOTALMAIL
+	,SYSSTAT_PROP_FEEDBACK
+
+};
+
+static JSBool js_sysstats_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+    jsint       tiny;
+	scfg_t*		cfg;
+	stats_t		stats;
+	uint		i;
+	ulong		l;
+
+	if((cfg=(scfg_t*)JS_GetPrivate(cx,obj))==NULL)
+		return JS_FALSE;
+
+    tiny = JSVAL_TO_INT(id);
+
+	if(!getstats(cfg, 0, &stats))
+		return(FALSE);
+
+	switch(tiny) {
+		case SYSSTAT_PROP_LOGONS:
+			*vp = INT_TO_JSVAL(stats.logons);
+			break;
+		case SYSSTAT_PROP_LTODAY:
+			*vp = INT_TO_JSVAL(stats.ltoday);
+			break;
+		case SYSSTAT_PROP_TIMEON:
+			*vp = INT_TO_JSVAL(stats.timeon);
+			break;
+		case SYSSTAT_PROP_TTODAY:
+			*vp = INT_TO_JSVAL(stats.ttoday);
+			break;
+		case SYSSTAT_PROP_ULS:
+			*vp = INT_TO_JSVAL(stats.uls);
+			break;
+		case SYSSTAT_PROP_ULB:
+			*vp = INT_TO_JSVAL(stats.ulb);
+			break;
+		case SYSSTAT_PROP_DLS:
+			*vp = INT_TO_JSVAL(stats.dls);
+			break;
+		case SYSSTAT_PROP_DLB:
+			*vp = INT_TO_JSVAL(stats.dlb);
+			break;
+		case SYSSTAT_PROP_PTODAY:
+			*vp = INT_TO_JSVAL(stats.ptoday);
+			break;
+		case SYSSTAT_PROP_ETODAY:
+			*vp = INT_TO_JSVAL(stats.etoday);
+			break;
+		case SYSSTAT_PROP_FTODAY:
+			*vp = INT_TO_JSVAL(stats.ftoday);
+			break;
+		case SYSSTAT_PROP_NUSERS:
+			*vp = INT_TO_JSVAL(stats.nusers);
+			break;
+
+		case SYSSTAT_PROP_TOTALUSERS:
+			*vp = INT_TO_JSVAL(lastuser(cfg));
+			break;
+		case SYSSTAT_PROP_TOTALMSGS:
+			l=0;
+			for(i=0;i<cfg->total_subs;i++)
+				l+=getposts(cfg,i); 
+			*vp = INT_TO_JSVAL(l); 
+			break;
+		case SYSSTAT_PROP_TOTALFILES:
+			l=0;
+			for(i=0;i<cfg->total_dirs;i++)
+				l+=getfiles(cfg,i);
+			*vp = INT_TO_JSVAL(l);
+			break;
+		case SYSSTAT_PROP_TOTALMAIL:
+			*vp = INT_TO_JSVAL(getmail(cfg, 0,0));
+			break;
+		case SYSSTAT_PROP_FEEDBACK:
+			*vp = INT_TO_JSVAL(getmail(cfg, 1,0));
+			break;
+	}
+
+	return(TRUE);
+}
+
+#define SYSSTAT_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY
+
+static struct JSPropertySpec js_sysstats_properties[] = {
+/*		 name,						tinyid,						flags,			getter,	setter	*/
+
+	{	"total_logons",				SYSSTAT_PROP_LOGONS,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"logons_today",				SYSSTAT_PROP_LTODAY,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"total_timeon",				SYSSTAT_PROP_TIMEON,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"timeon_today",				SYSSTAT_PROP_TTODAY,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"total_files",				SYSSTAT_PROP_TOTALFILES,	SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"files_uploaded_today",		SYSSTAT_PROP_ULS,			SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"bytes_uploaded_today",		SYSSTAT_PROP_ULB,			SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"files_downloaded_today",	SYSSTAT_PROP_DLS,			SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"bytes_downloaded_today",	SYSSTAT_PROP_DLB,			SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"total_messages",			SYSSTAT_PROP_TOTALMSGS,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"messages_posted_today",	SYSSTAT_PROP_PTODAY,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"total_email",				SYSSTAT_PROP_TOTALMAIL,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"email_sent_today",			SYSSTAT_PROP_ETODAY,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"total_feedback",			SYSSTAT_PROP_FEEDBACK,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"feedback_sent_today",		SYSSTAT_PROP_FTODAY,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"total_users",				SYSSTAT_PROP_TOTALUSERS,	SYSSTAT_FLAGS,	NULL,	NULL },
+	{	"new_users_today",			SYSSTAT_PROP_NUSERS,		SYSSTAT_FLAGS,	NULL,	NULL },
+	{0}
+};
+
+static JSClass js_sysstats_class = {
+     "Stats"				/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,js_sysstats_get		/* getProperty	*/
+	,JS_PropertyStub		/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,JS_FinalizeStub		/* finalize		*/
+};
+
+/* node properties */
+enum {
+	/* raw node_t fields */
+	 NODE_PROP_STATUS
+	,NODE_PROP_ERRORS
+	,NODE_PROP_ACTION
+	,NODE_PROP_USERON
+	,NODE_PROP_CONNECTION
+	,NODE_PROP_MISC
+	,NODE_PROP_AUX
+	,NODE_PROP_EXTAUX
+
+	/* convenience strings */
+	,NODE_PROP_USERON_NAME
+};
+
+static JSBool js_node_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+	char		str[128];
+	uint		node_num;
+    jsint       tiny;
+	node_t		node;
+	scfg_t*		cfg;
+	JSObject*	sysobj;
+	JSObject*	node_list;
+
+	tiny = JSVAL_TO_INT(id);
+
+	if((node_list=JS_GetParent(cx, obj))==NULL)
+		return(JS_FALSE);
+
+	if((sysobj=JS_GetParent(cx, node_list))==NULL)
+		return(JS_FALSE);
+
+	if((cfg=(scfg_t*)JS_GetPrivate(cx,sysobj))==NULL)
+		return(JS_FALSE);
+
+	node_num=(uint)JS_GetPrivate(cx,obj)>>1;
+
+	memset(&node,0,sizeof(node));
+	if(getnodedat(cfg, node_num, &node, 0)) {
+		*vp = INT_TO_JSVAL(0);
+		return(JS_TRUE);
+	}
+	
+    switch(tiny) {
+		case NODE_PROP_STATUS:
+			*vp = INT_TO_JSVAL((int)node.status);
+			break;
+		case NODE_PROP_ERRORS:	
+			*vp = INT_TO_JSVAL((int)node.errors);
+			break;
+		case NODE_PROP_ACTION:	
+			*vp = INT_TO_JSVAL((int)node.action);
+			break;
+		case NODE_PROP_USERON:	
+			*vp = INT_TO_JSVAL((int)node.useron);
+			break;
+		case NODE_PROP_CONNECTION:
+			*vp = INT_TO_JSVAL((int)node.connection);
+			break;
+		case NODE_PROP_MISC:		
+			*vp = INT_TO_JSVAL((int)node.misc);
+			break;
+		case NODE_PROP_AUX:		
+			*vp = INT_TO_JSVAL((int)node.aux);
+			break;
+		case NODE_PROP_EXTAUX:	
+			*vp = INT_TO_JSVAL((int)node.extaux);
+			break;
+
+		case NODE_PROP_USERON_NAME:
+			*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,username(cfg,node.useron,str)));
+			break;
+
+	}
+	return(JS_TRUE);
+}
+
+static JSBool js_node_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+	uint		node_num;
+	uint		val;
+    jsint       tiny;
+	node_t		node;
+	scfg_t*		cfg;
+	JSObject*	sysobj;
+	JSObject*	node_list;
+
+	if((node_list=JS_GetParent(cx, obj))==NULL)
+		return(JS_FALSE);
+
+	if((sysobj=JS_GetParent(cx, node_list))==NULL)
+		return(JS_FALSE);
+
+	if((cfg=(scfg_t*)JS_GetPrivate(cx,sysobj))==NULL)
+		return(JS_FALSE);
+
+	node_num=(uint)JS_GetPrivate(cx,obj)>>1;
+
+	memset(&node,0,sizeof(node));
+	if(getnodedat(cfg, node_num, &node, 1)) 
+		return(JS_TRUE);
+
+	JS_ValueToInt32(cx, *vp, &val);
+
+	tiny = JSVAL_TO_INT(id);
+	
+    switch(tiny) {
+		case NODE_PROP_STATUS:
+			node.status=val;
+			break;
+		case NODE_PROP_ERRORS:	
+			node.errors=val;
+			break;
+		case NODE_PROP_ACTION:	
+			node.action=val;
+			break;
+		case NODE_PROP_USERON:	
+			node.useron=val;
+			break;
+		case NODE_PROP_CONNECTION:
+			node.connection=val;
+			break;
+		case NODE_PROP_MISC:		
+			node.misc=val;
+			break;
+		case NODE_PROP_AUX:		
+			node.aux=val;
+			break;
+		case NODE_PROP_EXTAUX:	
+			node.extaux=val;
+			break;
+	}
+	putnodedat(cfg,node_num,&node);
+
+	return(JS_TRUE);
+}
+
+static struct JSPropertySpec js_node_properties[] = {
+/*		 name,						tinyid,					flags,				getter,	setter	*/
+
+/* raw node_t fields */
+	{	"status",					NODE_PROP_STATUS,		JSPROP_ENUMERATE,	NULL,	NULL },
+	{	"errors",					NODE_PROP_ERRORS,		JSPROP_ENUMERATE,	NULL,	NULL },
+	{	"action",					NODE_PROP_ACTION,		JSPROP_ENUMERATE,	NULL,	NULL },
+	{	"useron",					NODE_PROP_USERON,		JSPROP_ENUMERATE,	NULL,	NULL },
+	{	"connection",				NODE_PROP_CONNECTION,	JSPROP_ENUMERATE,	NULL,	NULL },
+	{	"misc",						NODE_PROP_MISC,			JSPROP_ENUMERATE,	NULL,	NULL },
+	{	"aux",						NODE_PROP_AUX,			JSPROP_ENUMERATE,	NULL,	NULL },
+	{	"extaux",					NODE_PROP_EXTAUX,		JSPROP_ENUMERATE,	NULL,	NULL },
+
+/* convenience strings */
+	{	"useron_name",				NODE_PROP_USERON_NAME,	JSPROP_ENUMERATE|JSPROP_READONLY,	NULL,	NULL },
+
+	{0}
+};
+
+static JSClass js_node_class = {
+     "Node"					/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,js_node_get			/* getProperty	*/
+	,js_node_set			/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,JS_FinalizeStub		/* finalize		*/
+};
+
+extern const char* beta_version;
+
+JSObject* DLLCALL js_CreateSystemObject(scfg_t* cfg, JSContext* cx, JSObject* parent)
+{
+	char		str[256];
+	uint		i;
+	jsval		val;
+	JSObject*	sysobj;
+	JSObject*	statsobj;
+	JSObject*	nodeobj;
+	JSObject*	node_list;
+
+	sysobj = JS_DefineObject(cx, parent, "system", &js_system_class, NULL, 0);
+
+	if(sysobj==NULL)
+		return(NULL);
+
+	if(!JS_SetPrivate(cx, sysobj, cfg))	/* Store a pointer to scfg_t */
+		return(NULL);
+
+	/****************************/
+	/* static string properties */
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, VERSION));
+	if(!JS_SetProperty(cx, sysobj, "version", &val))
+		return(NULL);
+
+	sprintf(str,"%c",REVISION);
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
+	if(!JS_SetProperty(cx, sysobj, "revision", &val))
+		return(NULL);
+
+	sprintf(str,"%s%c%s",VERSION,REVISION,beta_version);
+	truncsp(str);
+#if defined(_DEBUG)
+	strcat(str," Debug");
+#endif
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
+	if(!JS_SetProperty(cx, sysobj, "full_version", &val))
+		return(NULL);
+
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, VERSION_NOTICE));
+	if(!JS_SetProperty(cx, sysobj, "version_notice", &val))
+		return(NULL);
+
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, PLATFORM_DESC));
+	if(!JS_SetProperty(cx, sysobj, "platform", &val))
+		return(NULL);
+
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, socklib_version(str)));
+	if(!JS_SetProperty(cx, sysobj, "socket_lib", &val))
+		return(NULL);
+
+	sprintf(str,"SMBLIB %s",smb_lib_ver());
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
+	if(!JS_SetProperty(cx, sysobj, "msgbase_lib", &val))
+		return(NULL);
+
+	COMPILER_DESC(str);
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
+	if(!JS_SetProperty(cx, sysobj, "compiled_with", &val))
+		return(NULL);
+
+	sprintf(str,"%s %.5s",__DATE__,__TIME__);
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
+	if(!JS_SetProperty(cx, sysobj, "compiled_when", &val))
+		return(NULL);
+
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, COPYRIGHT_NOTICE));
+	if(!JS_SetProperty(cx, sysobj, "copyright", &val))
+		return(NULL);
+
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx
+			,(char *)JS_GetImplementationVersion()));
+	if(!JS_SetProperty(cx, sysobj, "js_version", &val))
+		return(NULL);
+
+	val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,os_version(str)));
+	if(!JS_SetProperty(cx, sysobj, "os_version", &val))
+		return(NULL);
+
+	/***********************/
+
+	if(!JS_DefineProperties(cx, sysobj, js_system_properties))
+		return(NULL);
+
+	statsobj = JS_DefineObject(cx, sysobj, "stats", &js_sysstats_class, NULL, 0);
+
+	if(statsobj==NULL)
+		return(NULL);
+
+	JS_SetPrivate(cx, statsobj, cfg);	/* Store a pointer to scfg_t */
+
+	if(!JS_DefineProperties(cx, statsobj, js_sysstats_properties))
+		return(NULL);
+
+	/* node_list property */
+
+	if((node_list=JS_NewArrayObject(cx, 0, NULL))==NULL) 
+		return(NULL);
+
+	for(i=0;i<cfg->sys_nodes && i<cfg->sys_lastnode;i++) {
+
+		nodeobj = JS_NewObject(cx, &js_node_class, NULL, node_list);
+
+		if(nodeobj==NULL)
+			return(NULL);
+
+		/* Store node number */
+		/* We have to shift it to make it look like a pointer to JS. :-( */
+		if(!JS_SetPrivate(cx, nodeobj, (char*)((i+1)<<1)))	
+			return(NULL);
+
+		if(!JS_DefineProperties(cx, nodeobj, js_node_properties))
+			return(NULL);
+
+		val=OBJECT_TO_JSVAL(nodeobj);
+		if(!JS_SetElement(cx, node_list, i, &val))
+			return(NULL);
+	}	
+
+	if(!JS_DefineProperty(cx, sysobj, "node_list", OBJECT_TO_JSVAL(node_list)
+		, NULL, NULL, 0))
+		return(NULL);
+
+	return(sysobj);
+}
+
+#endif	/* JAVSCRIPT */
\ No newline at end of file
diff --git a/src/sbbs3/js_user.c b/src/sbbs3/js_user.c
new file mode 100644
index 0000000000000000000000000000000000000000..8b911acf70db9e87dc8a9aa92bf1cb6c9bc578cd
--- /dev/null
+++ b/src/sbbs3/js_user.c
@@ -0,0 +1,478 @@
+/* userobj.c */
+
+/* Synchronet JavaScript "User" Object */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2001 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.	*
+ ****************************************************************************/
+
+#include "sbbs.h"
+
+#ifdef JAVASCRIPT
+
+/* User Object Properites */
+enum {
+	 USER_PROP_ALIAS 	
+	,USER_PROP_NAME		
+	,USER_PROP_HANDLE	
+	,USER_PROP_NOTE		
+	,USER_PROP_COMP		
+	,USER_PROP_COMMENT	
+	,USER_PROP_NETMAIL	
+	,USER_PROP_ADDRESS	
+	,USER_PROP_LOCATION	
+	,USER_PROP_ZIPCODE	
+	,USER_PROP_PASS		
+	,USER_PROP_PHONE  	
+	,USER_PROP_BIRTH  	
+	,USER_PROP_MODEM     
+	,USER_PROP_LASTON	
+	,USER_PROP_FIRSTON	
+	,USER_PROP_EXPIRE    
+	,USER_PROP_PWMOD     
+	,USER_PROP_LOGONS    
+	,USER_PROP_LTODAY    
+	,USER_PROP_TIMEON    
+	,USER_PROP_TEXTRA  	
+	,USER_PROP_TTODAY    
+	,USER_PROP_TLAST     
+	,USER_PROP_POSTS     
+	,USER_PROP_EMAILS    
+	,USER_PROP_FBACKS    
+	,USER_PROP_ETODAY	
+	,USER_PROP_PTODAY	
+	,USER_PROP_ULB       
+	,USER_PROP_ULS       
+	,USER_PROP_DLB       
+	,USER_PROP_DLS       
+	,USER_PROP_CDT		
+	,USER_PROP_MIN		
+	,USER_PROP_LEVEL 	
+	,USER_PROP_FLAGS1	
+	,USER_PROP_FLAGS2	
+	,USER_PROP_FLAGS3	
+	,USER_PROP_FLAGS4	
+	,USER_PROP_EXEMPT	
+	,USER_PROP_REST		
+	,USER_PROP_ROWS		
+	,USER_PROP_SEX		
+	,USER_PROP_MISC		
+	,USER_PROP_LEECH 	
+	,USER_PROP_CURSUB	
+	,USER_PROP_CURDIR	
+	,USER_PROP_FREECDT	
+	,USER_PROP_XEDIT 	
+	,USER_PROP_SHELL 	
+	,USER_PROP_QWK		
+	,USER_PROP_TMPEXT	
+	,USER_PROP_CHAT		
+	,USER_PROP_NS_TIME	
+	,USER_PROP_PROT		
+};
+
+static JSBool js_user_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+    jsint       tiny;
+	user_t*		user;
+
+	if((user=(user_t*)JS_GetPrivate(cx,obj))==NULL)
+		return JS_FALSE;
+
+    tiny = JSVAL_TO_INT(id);
+
+	switch(tiny) {
+		case USER_PROP_MISC:
+			JS_ValueToInt32(cx, *vp, &user->misc);
+			break;
+	}
+
+	return(TRUE);
+}
+
+static JSBool js_user_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+	char*		p=NULL;
+	char		tmp[128];
+	ulong		val;
+    jsint       tiny;
+	user_t*		user;
+
+	if((user=(user_t*)JS_GetPrivate(cx,obj))==NULL)
+		return JS_FALSE;
+
+    tiny = JSVAL_TO_INT(id);
+
+	switch(tiny) {
+	case USER_PROP_ALIAS: 
+		p=user->alias;
+		break;
+	case USER_PROP_NAME:
+		p=user->name;
+		break;
+	case USER_PROP_HANDLE:
+		p=user->handle;
+		break;
+	case USER_PROP_NOTE:
+		p=user->note;
+		break;
+	case USER_PROP_COMP:
+		p=user->comp;
+		break;
+	case USER_PROP_COMMENT:
+		p=user->comment;
+		break;
+	case USER_PROP_NETMAIL:
+		p=user->netmail;
+		break;
+	case USER_PROP_ADDRESS:
+		p=user->address;
+		break;
+	case USER_PROP_LOCATION:
+		p=user->location;
+		break;
+	case USER_PROP_ZIPCODE:
+		p=user->zipcode;
+		break;
+	case USER_PROP_PASS:
+		p=user->pass;
+		break;
+	case USER_PROP_PHONE:
+		p=user->phone;
+		break;
+	case USER_PROP_BIRTH:
+		p=user->birth;
+		break;
+	case USER_PROP_MODEM:
+		p=user->modem;
+		break;
+	case USER_PROP_LASTON:
+		val=user->laston;
+		break;
+	case USER_PROP_FIRSTON:
+		val=user->firston;
+		break;
+	case USER_PROP_EXPIRE:
+		val=user->expire;
+		break;
+	case USER_PROP_PWMOD: 
+		val=user->pwmod;
+		break;
+	case USER_PROP_LOGONS:
+		val=user->logons;
+		break;
+	case USER_PROP_LTODAY:
+		val=user->ltoday;
+		break;
+	case USER_PROP_TIMEON:
+		val=user->timeon;
+		break;
+	case USER_PROP_TEXTRA:
+		val=user->textra;
+		break;
+	case USER_PROP_TTODAY:
+		val=user->ttoday;
+		break;
+	case USER_PROP_TLAST: 
+		val=user->tlast;
+		break;
+	case USER_PROP_POSTS: 
+		val=user->posts;
+		break;
+	case USER_PROP_EMAILS: 
+		val=user->emails;
+		break;
+	case USER_PROP_FBACKS: 
+		val=user->fbacks;
+		break;
+	case USER_PROP_ETODAY:	
+		val=user->etoday;
+		break;
+	case USER_PROP_PTODAY:
+		val=user->ptoday;
+		break;
+	case USER_PROP_ULB:
+		val=user->ulb;
+		break;
+	case USER_PROP_ULS:
+		val=user->uls;
+		break;
+	case USER_PROP_DLB:
+		val=user->dlb;
+		break;
+	case USER_PROP_DLS:
+		val=user->dls;
+		break;
+	case USER_PROP_CDT:
+		val=user->cdt;
+		break;
+	case USER_PROP_MIN:
+		val=user->min;
+		break;
+	case USER_PROP_LEVEL:
+		val=user->level;
+		break;
+	case USER_PROP_FLAGS1:
+		val=user->flags1;
+		break;
+	case USER_PROP_FLAGS2:
+		val=user->flags2;
+		break;
+	case USER_PROP_FLAGS3:
+		val=user->flags3;
+		break;
+	case USER_PROP_FLAGS4:
+		val=user->flags4;
+		break;
+	case USER_PROP_EXEMPT:
+		val=user->exempt;
+		break;
+	case USER_PROP_REST:
+		val=user->rest;
+		break;
+	case USER_PROP_ROWS:
+		val=user->rows;
+		break;
+	case USER_PROP_SEX:
+		sprintf(tmp,"%c",user->sex);
+		p=tmp;
+		break;
+	case USER_PROP_MISC:
+		val=user->misc;
+		break;
+	case USER_PROP_LEECH:
+		val=user->leech;
+		break;
+	case USER_PROP_CURSUB:
+		p=user->cursub;
+		break;
+	case USER_PROP_CURDIR:
+		p=user->curdir;
+		break;
+	case USER_PROP_FREECDT:
+		val=user->freecdt;
+		break;
+	case USER_PROP_XEDIT:
+		val=user->xedit;
+		break;
+	case USER_PROP_SHELL:
+		val=user->shell;
+		break;
+	case USER_PROP_QWK:
+		val=user->qwk;
+		break;
+	case USER_PROP_TMPEXT:
+		p=user->tmpext;
+		break;
+	case USER_PROP_CHAT:
+		val=user->laston;
+		break;
+	case USER_PROP_NS_TIME:
+		val=user->laston;
+		break;
+	case USER_PROP_PROT:
+		sprintf(tmp,"%c",user->prot);
+		p=tmp;
+		break;
+	default:
+		return(TRUE);
+	}
+	if(p!=NULL) 
+		*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p));
+	else
+		*vp = INT_TO_JSVAL(val);
+
+	return(TRUE);
+}
+
+#define USEROBJ_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY
+
+static struct JSPropertySpec js_user_properties[] = {
+/*		 name				,tinyid					,flags,				getter,	setter	*/
+
+	{	"alias"				,USER_PROP_ALIAS 		,USEROBJ_FLAGS,		NULL,NULL},
+	{	"name"				,USER_PROP_NAME		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"handle"			,USER_PROP_HANDLE	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"note"				,USER_PROP_NOTE		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"ip_address"		,USER_PROP_NOTE		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"computer"			,USER_PROP_COMP		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"host_name"			,USER_PROP_COMP		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"comment"			,USER_PROP_COMMENT	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"netmail"			,USER_PROP_NETMAIL	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"email"				,USER_PROP_NETMAIL	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"address"			,USER_PROP_ADDRESS	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"location"			,USER_PROP_LOCATION	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"zipcode"			,USER_PROP_ZIPCODE	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"phone"				,USER_PROP_PHONE  	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"birthdate"			,USER_PROP_BIRTH  	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"modem"				,USER_PROP_MODEM      	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"connection"		,USER_PROP_MODEM      	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"screen_rows"		,USER_PROP_ROWS		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"gender"			,USER_PROP_SEX		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"cursub"			,USER_PROP_CURSUB	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"curdir"			,USER_PROP_CURDIR	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"editor"			,USER_PROP_XEDIT 	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"command_shell"		,USER_PROP_SHELL 	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"settings"			,USER_PROP_MISC		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"qwk_settings"		,USER_PROP_QWK		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"chat_settings"		,USER_PROP_CHAT		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"temp_file_ext"		,USER_PROP_TMPEXT	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"newscan_date"		,USER_PROP_NS_TIME	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"download_protocol"	,USER_PROP_PROT		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{0}
+};
+
+/* user.stats: These should be READ ONLY by nature */
+static struct JSPropertySpec js_user_stats_properties[] = {
+/*		 name				,tinyid					,flags,				getter,	setter	*/
+
+	{	"laston_date"		,USER_PROP_LASTON	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"firston_date"		,USER_PROP_FIRSTON	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"total_logons"		,USER_PROP_LOGONS     	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"logons_today"		,USER_PROP_LTODAY     	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"total_timeon"		,USER_PROP_TIMEON     	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"timeon_today"		,USER_PROP_TTODAY     	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"timeon_last_logon"	,USER_PROP_TLAST      	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"total_posts"		,USER_PROP_POSTS      	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"total_emails"		,USER_PROP_EMAILS     	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"total_feedbacks"	,USER_PROP_FBACKS     	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"email_today"		,USER_PROP_ETODAY	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"posts_today"		,USER_PROP_PTODAY	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"bytes_uploaded"	,USER_PROP_ULB        	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"files_uploaded"	,USER_PROP_ULS        	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"bytes_downloaded"	,USER_PROP_DLB        	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"files_downloaded"	,USER_PROP_DLS        	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"leech_attempts"	,USER_PROP_LEECH 	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{0}
+};
+
+/* user.security */
+static struct JSPropertySpec js_user_security_properties[] = {
+/*		 name				,tinyid					,flags,				getter,	setter	*/
+
+	{	"password"			,USER_PROP_PASS		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"password_date"		,USER_PROP_PWMOD      	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"level"				,USER_PROP_LEVEL 	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"flags1"			,USER_PROP_FLAGS1	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"flags2"			,USER_PROP_FLAGS2	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"flags3"			,USER_PROP_FLAGS3	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"flags4"			,USER_PROP_FLAGS4	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"exemptions"		,USER_PROP_EXEMPT	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"restrictions"		,USER_PROP_REST		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"credits"			,USER_PROP_CDT		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"free_credits"		,USER_PROP_FREECDT	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"minutes"			,USER_PROP_MIN		 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"extra_time"		,USER_PROP_TEXTRA  	 	,USEROBJ_FLAGS,		NULL,NULL},
+	{	"expiration_date"	,USER_PROP_EXPIRE     	,USEROBJ_FLAGS,		NULL,NULL},
+	{0}
+};
+
+static JSClass js_user_class = {
+     "User"					/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,js_user_get			/* getProperty	*/
+	,js_user_set			/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,JS_FinalizeStub		/* finalize		*/
+};
+
+static JSClass js_user_stats_class = {
+     "UserStats"			/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,js_user_get			/* getProperty	*/
+	,js_user_set			/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,JS_FinalizeStub		/* finalize		*/
+};
+
+
+static JSClass js_user_security_class = {
+     "UserSecurity"			/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,js_user_get			/* getProperty	*/
+	,js_user_set			/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,JS_FinalizeStub		/* finalize		*/
+};
+
+JSObject* DLLCALL js_CreateUserObject(scfg_t* cfg, JSContext* cx, JSObject* parent, char* name, user_t* user)
+{
+	JSObject*	userobj;
+	JSObject*	statsobj;
+	JSObject*	securityobj;
+
+	userobj = JS_DefineObject(cx, parent, name, &js_user_class, NULL, 0);
+
+	if(userobj==NULL)
+		return(NULL);
+
+	JS_SetPrivate(cx, userobj, user);	/* Store a pointer to user_t */
+
+	JS_DefineProperties(cx, userobj, js_user_properties);
+
+	/* user.stats */
+	statsobj = JS_DefineObject(cx, userobj, "stats"
+		,&js_user_stats_class, NULL, 0);
+
+	if(statsobj==NULL)
+		return(NULL);
+
+	JS_SetPrivate(cx, statsobj, user);	/* Store a pointer to user_t */
+
+	JS_DefineProperties(cx, statsobj, js_user_stats_properties);
+
+	/* user.security */
+	securityobj = JS_DefineObject(cx, userobj, "security"
+		,&js_user_security_class, NULL, 0);
+
+	if(securityobj==NULL)
+		return(NULL);
+
+	JS_SetPrivate(cx, securityobj, user);	/* Store a pointer to user_t */
+
+	JS_DefineProperties(cx, securityobj, js_user_security_properties);
+
+	return(userobj);
+}
+
+#endif	/* JAVSCRIPT */
\ No newline at end of file