diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index 80e368d1d4ef15be2c906da8cf87d0c1efd33f29..34b8c8a1f4f8d9b151a24df784fa19e29c4f2d12 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -1,4205 +1,4219 @@
-/* main.cpp */
-
-/* Synchronet main/telnet server thread and related functions */
-
-/* $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.	*
- ****************************************************************************/
-
-#include "sbbs.h"
-#include "ident.h"
-#include "telnet.h" 
-
-#ifdef __unix__
-	#include <sys/un.h>
-#endif
-
-//---------------------------------------------------------------------------
-
-/* Temporary */
-int	mswtyp=0;
-uint riobp;
-
-#define TELNET_SERVER "Synchronet Telnet Server"
-#define STATUS_WFC	"Listening"
-
-#define TIMEOUT_THREAD_WAIT		60			// Seconds (was 15)
-#define IO_THREAD_BUF_SIZE	   	20000		// Bytes
-
-// Globals
-#ifdef _WIN32
-	HANDLE		exec_mutex=NULL;
-	HINSTANCE	hK32=NULL;
-
-	#if defined(_DEBUG) && defined(_MSC_VER)
-			HANDLE	debug_log=INVALID_HANDLE_VALUE;
-		   _CrtMemState mem_chkpoint;
-	#endif // _DEBUG && _MSC_VER
-
-#endif // _WIN32
-
-time_t	uptime=0;
-DWORD	served=0;
-
-static	uint node_threads_running=0;
-static	uint thread_count=0;
-		
-char 	lastuseron[LEN_ALIAS+1];  /* Name of user last online */
-RingBuf* node_inbuf[MAX_NODES];
-SOCKET	spy_socket[MAX_NODES];
-#ifdef __unix__
-SOCKET	uspy_socket[MAX_NODES];	  /* UNIX domain spy sockets */
-#endif
-SOCKET	node_socket[MAX_NODES];
-static	SOCKET telnet_socket=INVALID_SOCKET;
-static	SOCKET rlogin_socket=INVALID_SOCKET;
-static	pthread_mutex_t event_mutex;
-static  bool	event_mutex_locked;
-static	sbbs_t*	sbbs=NULL;
-static	scfg_t	scfg;
-static	bool	scfg_reloaded=true;
-static	char *	text[TOTAL_TEXT];
-static	WORD	first_node;
-static	WORD	last_node;
-static	bool	recycle_server=false;
-
-extern "C" {
-
-static bbs_startup_t* startup=NULL;
-
-static void status(char* str)
-{
-	if(startup!=NULL && startup->status!=NULL)
-	    startup->status(str);
-}
-
-static void update_clients()
-{
-	if(startup!=NULL && startup->clients!=NULL)
-		startup->clients(node_threads_running);
-}
-
-void client_on(SOCKET sock, client_t* client, BOOL update)
-{
-	if(startup!=NULL && startup->client_on!=NULL)
-		startup->client_on(TRUE,sock,client,update);
-}
-
-static void client_off(SOCKET sock)
-{
-	if(startup!=NULL && startup->client_on!=NULL)
-		startup->client_on(FALSE,sock,NULL,FALSE);
-}
-
-static void thread_up(BOOL setuid)
-{
-	thread_count++;
-	if(startup!=NULL && startup->thread_up!=NULL)
-		startup->thread_up(TRUE,setuid);
-}
-
-static void thread_down()
-{
-	if(thread_count>0)
-		thread_count--;
-	if(startup!=NULL && startup->thread_up!=NULL)
-		startup->thread_up(FALSE,FALSE);
-}
-
-int lputs(char* str)
-{
-	if(startup==NULL || startup->lputs==NULL)
-    	return(0);
-
-#if defined(_WIN32) && defined(_DEBUG)
-	if(IsBadCodePtr((FARPROC)startup->lputs)) {
-		DebugBreak();
-		return(0);
-	}
-#endif
-
-    return(startup->lputs(str));
-}
-
-int lprintf(char *fmt, ...)
-{
-	va_list argptr;
-	char sbuf[1024];
-
-    if(startup==NULL || startup->lputs==NULL)
-        return(0);
-
-    va_start(argptr,fmt);
-    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
-	sbuf[sizeof(sbuf)-1]=0;
-    va_end(argptr);
-    return(startup->lputs(sbuf));
-}
-
-int eprintf(char *fmt, ...)
-{
-	va_list argptr;
-	char sbuf[1024];
-
-    if(startup==NULL || startup->event_log==NULL)
-        return(0);
-
-    va_start(argptr,fmt);
-    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
-	sbuf[sizeof(sbuf)-1]=0;
-    va_end(argptr);
-	strip_ctrl(sbuf);
-    return(startup->event_log(sbuf));
-}
-
-SOCKET open_socket(int type)
-{
-	SOCKET	sock;
-	char	error[256];
-
-	sock=socket(AF_INET, type, IPPROTO_IP);
-	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
-		startup->socket_open(TRUE);
-	if(sock!=INVALID_SOCKET && set_socket_options(&scfg, sock, error))
-		lprintf("%04d !ERROR %s",sock,error);
-
-	return(sock);
-}
-
-SOCKET accept_socket(SOCKET s, SOCKADDR* addr, socklen_t* addrlen)
-{
-	SOCKET	sock;
-
-	sock=accept(s,addr,addrlen);
-	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
-		startup->socket_open(TRUE);
-
-	return(sock);
-}
-
-int close_socket(SOCKET sock)
-{
-	int		result;
-
-	if(sock==INVALID_SOCKET || sock==0)
-		return(0);
-
-	shutdown(sock,SHUT_RDWR);	/* required on Unix */
-	result=closesocket(sock);
-	if(result==0 && startup!=NULL && startup->socket_open!=NULL) 
-		startup->socket_open(FALSE);
-	if(result!=0 && ERROR_VALUE!=ENOTSOCK)
-		lprintf("!ERROR %d closing socket %d",ERROR_VALUE,sock);
-	return(result);
-}
-
-
-u_long resolve_ip(char *addr)
-{
-	HOSTENT*	host;
-	char*		p;
-
-	if(*addr==0)
-		return(INADDR_NONE);
-
-	for(p=addr;*p;p++)
-		if(*p!='.' && !isdigit(*p))
-			break;
-	if(!(*p))
-		return(inet_addr(addr));
-	if((host=gethostbyname(addr))==NULL) 
-		return(INADDR_NONE);
-	return(*((ulong*)host->h_addr_list[0]));
-}
-
-} /* extern "C" */
-
-#ifdef JAVASCRIPT
-
-JSBool	
-DLLCALL js_CreateArrayOfStrings(JSContext* cx, JSObject* parent, const char* name, char* str[],uintN flags)
-{
-	JSObject*	array;
-	JSString*	js_str;
-	jsval		val;
-	size_t		i;
-	jsuint		len=0;
-		
-	if(JS_GetProperty(cx,parent,name,&val) && val!=JSVAL_VOID)
-		array=JSVAL_TO_OBJECT(val);
-	else
-		if((array=JS_NewArrayObject(cx, 0, NULL))==NULL)
-			return(JS_FALSE);
-
-	if(!JS_GetArrayLength(cx, array, &len))
-		return(JS_FALSE);
-
-	for(i=0;str[i]!=NULL;i++) {
-		if((js_str = JS_NewStringCopyZ(cx, str[i]))==NULL)
-			break;
-		val = STRING_TO_JSVAL(js_str);
-		if(!JS_SetElement(cx, array, len+i, &val))
-			break;
-	}
-
-	return(JS_DefineProperty(cx, parent, name, OBJECT_TO_JSVAL(array)
-		,NULL,NULL,flags));
-}
-
-/* Convert from Synchronet-specific jsMethodSpec to JSAPI's JSFunctionSpec */
-
-JSBool
-DLLCALL js_DescribeObject(JSContext* cx, JSObject* obj, const char* str)
-{
-	JSString* js_str = JS_NewStringCopyZ(cx, str);
-
-	if(js_str==NULL)
-		return(JS_FALSE);
-
-	return(JS_DefineProperty(cx,obj,"_description"
-		,STRING_TO_JSVAL(js_str),NULL,NULL,JSPROP_READONLY));
-}
-
-JSBool
-DLLCALL js_DescribeConstructor(JSContext* cx, JSObject* obj, const char* str)
-{
-	JSString* js_str = JS_NewStringCopyZ(cx, str);
-
-	if(js_str==NULL)
-		return(JS_FALSE);
-
-	return(JS_DefineProperty(cx,obj,"_constructor"
-		,STRING_TO_JSVAL(js_str),NULL,NULL,JSPROP_READONLY));
-}
-
-#ifdef _DEBUG
-
-static char* server_prop_desc[] = {
-
-	 "server name and version number"
-	,"detailed version/build information"
-	,NULL
-};
-
-
-static const char* method_array_name = "_method_list";
-
-/*
- * from jsatom.c:
- * Keep this in sync with jspubtd.h -- an assertion below will insist that
- * its length match the JSType enum's JSTYPE_LIMIT limit value.
- */
-static const char *js_type_str[] = {
-    "void",			// changed from "undefined"
-    "object",
-    "function",
-    "string",
-    "number",
-    "boolean",
-	"array",		// JSTYPE_LIMIT
-};
-
-JSBool 
-DLLCALL js_DefineMethods(JSContext* cx, JSObject* obj, jsMethodSpec *funcs, BOOL append)
-{
-	int			i;
-	jsuint		len=0;
-	jsval		val;
-	JSObject*	method;
-	JSObject*	method_array;
-	JSString*	js_str;
-
-	/* Return existing method_list array if it's already been created */
-	if(JS_GetProperty(cx,obj,method_array_name,&val) && val!=JSVAL_VOID)
-		method_array=JSVAL_TO_OBJECT(val);
-	else
-		if((method_array=JS_NewArrayObject(cx, 0, NULL))==NULL) 
-			return(JS_FALSE);
-
-	if(append)
-		if(!JS_GetArrayLength(cx, method_array, &len))
-			return(JS_FALSE);
-
-	for(i=0;funcs[i].name;i++) {
-
-		JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0);
-
-		if(funcs[i].type==JSTYPE_ALIAS)
-			continue;
-
-		method = JS_NewObject(cx, NULL, NULL, method_array);
-
-		if(method==NULL)
-			return(JS_FALSE);
-
-		if(funcs[i].name!=NULL) {
-			if((js_str=JS_NewStringCopyZ(cx,funcs[i].name))==NULL)
-				return(JS_FALSE);
-			val = STRING_TO_JSVAL(js_str);
-			JS_SetProperty(cx, method, "name", &val);
-		}
-
-		val = INT_TO_JSVAL(funcs[i].nargs);
-		if(!JS_SetProperty(cx, method, "nargs", &val))
-			return(JS_FALSE);
-
-		if((js_str=JS_NewStringCopyZ(cx,js_type_str[funcs[i].type]))==NULL)
-			return(JS_FALSE);
-		val = STRING_TO_JSVAL(js_str);
-		JS_SetProperty(cx, method, "type", &val);
-
-		if(funcs[i].args!=NULL) {
-			if((js_str=JS_NewStringCopyZ(cx,funcs[i].args))==NULL)
-				return(JS_FALSE);
-			val = STRING_TO_JSVAL(js_str);
-			JS_SetProperty(cx, method, "args", &val);
-		}
-
-		if(funcs[i].desc!=NULL) { 
-			if((js_str=JS_NewStringCopyZ(cx,funcs[i].desc))==NULL)
-				return(JS_FALSE);
-			val = STRING_TO_JSVAL(js_str);
-			JS_SetProperty(cx, method, "desc", &val);
-		}
-
-		val=OBJECT_TO_JSVAL(method);
-		if(!JS_SetElement(cx, method_array, len+i, &val))
-			return(JS_FALSE);
-	}
-
-	if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
-		, NULL, NULL, 0))
-		return(JS_FALSE);
-
-	return(JS_TRUE);
-}
-
-#else // NON-DEBUG
-
-JSBool 
-DLLCALL js_DefineMethods(JSContext* cx, JSObject* obj, jsMethodSpec *funcs, BOOL append)
-{
-	int			i;
-
-	for(i=0;funcs[i].name;i++)
-		JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0);
-	return(JS_TRUE);
-}
-
-#endif
-
-/* 
- * @method: log
- * @syntax: log([value][,value][...])
- * @arg:	value Variable or constant of any type
- * @desc:	Print one or more values (typically Strings) in the local log window/display.
- */
-static JSBool
-js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
-{
-    uintN		i;
-    JSString *	str;
-	sbbs_t*		sbbs;
-
-	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-
-    for (i = 0; i < argc; i++) {
-		if((str=JS_ValueToString(cx, argv[i]))==NULL)
-		    return(JS_FALSE);
-		if(sbbs->online==ON_LOCAL) {
-			if(startup!=NULL && startup->event_log!=NULL)
-				startup->event_log(JS_GetStringBytes(str));
-		} else
-			lputs(JS_GetStringBytes(str));
-		}
-
-	*rval = JSVAL_VOID;
-    return(JS_TRUE);
-}
-
-/*
- * @method: print
- * @usage: print([value][,value][...])
- */
-
-static JSBool
-js_print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
-{
-    uintN		i;
-    JSString *	str;
-	sbbs_t*		sbbs;
-
-	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-
-    for (i = 0; i < argc; i++) {
-		if((str=JS_ValueToString(cx, argv[i]))==NULL)
-		    return(JS_FALSE);
-		if(sbbs->online==ON_LOCAL)
-			eprintf("%s",JS_GetStringBytes(str));
-		else
-			sbbs->bputs(JS_GetStringBytes(str));
-		}
-	if(sbbs->online==ON_REMOTE)
-		sbbs->bputs(crlf);
-
-	*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;
-	sbbs_t*		sbbs;
-	va_list		arglist[64];
-
-	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-
-	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);
-
-	if(sbbs->online==ON_LOCAL)
-		eprintf("%s",p);
-	else
-		sbbs->bputs(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;
-	sbbs_t*		sbbs;
-
-	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-
-	if((str=JS_ValueToString(cx, argv[0]))==NULL)
-	    return(JS_FALSE);
-
-	sbbs->attr(sbbs->cfg.color[clr_err]);
-	sbbs->bputs(JS_GetStringBytes(str));
-	sbbs->attr(LIGHTGRAY);
-	sbbs->bputs(crlf);
-
-	*rval = JSVAL_VOID;
-    return(JS_TRUE);
-}
-
-static JSBool
-js_confirm(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);
-
-	if((str=JS_ValueToString(cx, argv[0]))==NULL)
-	    return(JS_FALSE);
-
-	*rval = BOOLEAN_TO_JSVAL(sbbs->yesno(JS_GetStringBytes(str)));
-	return(JS_TRUE);
-}
-
-static JSBool
-js_prompt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
-{
-	char		instr[81];
-    JSString *	prompt;
-    JSString *	str;
-	sbbs_t*		sbbs;
-
-	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-
-	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;
-
-	sbbs->bprintf("\1n\1y\1h%s\1w: ",JS_GetStringBytes(prompt));
-
-	if(!sbbs->getstr(instr,sizeof(instr)-1,K_EDIT)) {
-		*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,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
-	,JSDOCSTR("add a line of text to the server and/or system log, "
-		"<i>values</i> are typically string constants or variables")
-	},
-    {"print",           js_print,           0,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
-	,JSDOCSTR("print a line of text to the console or event log with automatic line termination (CRLF), "
-		"<i>values</i> are typically string constants or variables")
-	},
-    {"printf",          js_printf,          1,	JSTYPE_VOID,	JSDOCSTR("string format [,value][,value]")
-	,JSDOCSTR("print a formatted string - <small>CAUTION: for experienced C programmers ONLY</small>")
-	},	
-	{"alert",			js_alert,			1,	JSTYPE_VOID,	JSDOCSTR("value")
-	,JSDOCSTR("print an alert message (ala client-side JS)")
-	},
-	{"prompt",			js_prompt,			1,	JSTYPE_STRING,	JSDOCSTR("[value]")
-	,JSDOCSTR("displays a prompt (<i>value</i>) and returns a string of user input (ala clent-side JS)")
-	},
-	{"confirm",			js_confirm,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("value")
-	,JSDOCSTR("displays a Yes/No prompt and returns <i>true</i> or <i>false</i> "
-		"based on users confirmation (ala client-side JS)")
-	},
-    {0}
-};
-
-static void
-js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
-{
-	char	line[64];
-	char	file[MAX_PATH+1];
-	sbbs_t*	sbbs;
-	const char*	warning;
-
-	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
-		return;
-	
-	if(report==NULL) {
-		lprintf("!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=nulstr;
-
-	if(sbbs->online==ON_LOCAL) 
-		eprintf("!JavaScript %s%s%s: %s",warning,file,line,message);
-	else {
-		lprintf("!JavaScript %s%s%s: %s",warning,file,line,message);
-		sbbs->bprintf("!JavaScript %s%s%s: %s\r\n",warning,file,line,message);
-	}
-}
-
-bool sbbs_t::js_init()
-{
-	char		node[128];
-	char		ver[256];
-	jsval		val;
-	JSObject*	server;
-	JSString*	js_str;
-
-    if(cfg.node_num)
-    	sprintf(node,"Node %d",cfg.node_num);
-    else
-    	strcpy(node,client_name);
-
-	lprintf("%s JavaScript: Creating runtime: %lu bytes"
-		,node,startup->js_max_bytes);
-
-	if((js_runtime = JS_NewRuntime(startup->js_max_bytes))==NULL)
-		return(false);
-
-	lprintf("%s JavaScript: Initializing context (stack: %lu bytes)"
-		,node,JAVASCRIPT_CONTEXT_STACK);
-
-    if((js_cx = JS_NewContext(js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
-		return(false);
-
-	js_loop = 0;	/* loop counter */
-
-	bool success=false;
-
-	do {
-
-		JS_SetErrorReporter(js_cx, js_ErrorReporter);
-
-		JS_SetContextPrivate(js_cx, this);	/* Store a pointer to sbbs_t instance */
-
-		/* Global Object */
-		if((js_glob=js_CreateGlobalObject(js_cx, &cfg, js_global_functions))==NULL)
-			break;
-
-#ifdef _DEBUG
-		JS_DefineProperty(js_cx, js_glob, "_global", OBJECT_TO_JSVAL(js_glob)
-			,NULL,NULL,JSPROP_READONLY);
-#endif
-
-		/* System Object */
-		if(js_CreateSystemObject(js_cx, js_glob, &cfg, uptime, startup->host_name)==NULL)
-			break;
-
-		/* Client Object */
-		if(js_CreateClientObject(js_cx, js_glob, "client", &client, client_socket)==NULL)
-			break;
-
-		/* BBS Object */
-		if(js_CreateBbsObject(js_cx, js_glob)==NULL)
-			break;
-
-		/* Console Object */
-		if(js_CreateConsoleObject(js_cx, js_glob)==NULL)
-			break;
-
-		/* Socket Class */
-		if(js_CreateSocketClass(js_cx, js_glob)==NULL)
-			break;
-
-		/* MsgBase Class */
-		if(js_CreateMsgBaseClass(js_cx, js_glob, &scfg)==NULL)
-			break;
-
-		/* File Class */
-		if(js_CreateFileClass(js_cx, js_glob)==NULL)
-			break;
-
-		/* User class */
-		if(js_CreateUserClass(js_cx, js_glob, &scfg)==NULL) 
-			break;
-
-		/* Area Objects */
-		if(!js_CreateUserObjects(js_cx, js_glob, &scfg, NULL, NULL, NULL)) 
-			break;
-
-		/* Server Object */
-		if((server=JS_DefineObject(js_cx, js_glob, "server", NULL
-			,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL)
-			break;
-
-		sprintf(ver,"%s %s%c",TELNET_SERVER,VERSION,REVISION);
-		if((js_str=JS_NewStringCopyZ(js_cx, ver))==NULL)
-			break;
-		val = STRING_TO_JSVAL(js_str);
-		if(!JS_SetProperty(js_cx, server, "version", &val))
-			break;
-
-		if((js_str=JS_NewStringCopyZ(js_cx, bbs_ver()))==NULL)
-			break;
-		val = STRING_TO_JSVAL(js_str);
-		if(!JS_SetProperty(js_cx, server, "version_detail", &val))
-			break;
-
-#ifdef _DEBUG
-		js_DescribeObject(js_cx,server,"Server-specifc properties");
-		js_CreateArrayOfStrings(js_cx, server, "_property_desc_list", server_prop_desc, JSPROP_READONLY);
-#endif
-
-		success=true;
-
-	} while(0);
-
-	if(!success) {
-		JS_DestroyContext(js_cx);
-		js_cx=NULL;
-		return(false);
-	}
-
-	return(true);
-}
-
-void sbbs_t::js_create_user_objects(void)
-{
-	if(js_cx==NULL)
-		return;
-
-	if(!js_CreateUserObjects(js_cx, js_glob, &cfg, &useron, NULL, subscan)) 
-		lprintf("!JavaScript ERROR creating user objects");
-}
-
-#endif	/* JAVASCRIPT */
-
-#ifdef _WINSOCKAPI_
-
-WSADATA WSAData;
-static BOOL WSAInitialized=FALSE;
-
-static BOOL winsock_startup(void)
-{
-	int		status;             /* Status Code */
-
-    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
-		lprintf("%s %s",WSAData.szDescription, WSAData.szSystemStatus);
-		WSAInitialized=TRUE;
-		return(TRUE);
-	}
-
-    lprintf("!WinSock startup ERROR %d", status);
-	return(FALSE);
-}
-
-#else /* No WINSOCK */
-
-#define winsock_startup()	(TRUE)	
-
-#endif
-
-static BYTE* telnet_interpret(sbbs_t* sbbs, BYTE* inbuf, int inlen,
-  									BYTE* outbuf, int& outlen)
-{
-	BYTE*   first_iac=NULL;
-	BYTE*	first_cr=NULL;
-	int 	i;
-
-    first_iac=(BYTE*)memchr(inbuf, TELNET_IAC, inlen);
-
-	if(!(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE)) 
-		&& !(sbbs->console&CON_RAW_IN)) {
-		if(sbbs->telnet_last_rxch==CR)
-			first_cr=inbuf;
-		else
-			first_cr=(BYTE*)memchr(inbuf, CR, inlen);
-	}
-
-    if(!sbbs->telnet_cmdlen	&& first_iac==NULL && first_cr==NULL) {
-        outlen=inlen;
-        return(inbuf);	// no interpretation needed
-    }
-
-    if(first_iac!=NULL || first_cr!=NULL) {
-		if(first_iac!=NULL && (first_cr==NULL || first_iac<first_cr))
-   			outlen=first_iac-inbuf;
-		else
-			outlen=first_cr-inbuf;
-	    memcpy(outbuf, inbuf, outlen);
-    } else
-    	outlen=0;
-
-    for(i=outlen;i<inlen;i++) {
-		if(!(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE)) 
-			&& !(sbbs->console&CON_RAW_IN)) {
-			if(sbbs->telnet_last_rxch==CR
-				&& (inbuf[i]==LF || inbuf[i]==0)) { // CR/LF or CR/NUL, ignore 2nd char
-#if 0 /* Debug CR/LF problems */
-				lprintf("Node %d CR/%02Xh detected and ignored"
-					,sbbs->cfg.node_num, inbuf[i]);
-#endif
-				sbbs->telnet_last_rxch=inbuf[i];
-				continue;
-			}
-			sbbs->telnet_last_rxch=inbuf[i];
-		}
-
-        if(inbuf[i]==TELNET_IAC && sbbs->telnet_cmdlen==1) { /* escaped 255 */
-            sbbs->telnet_cmdlen=0;
-            outbuf[outlen++]=TELNET_IAC;
-            continue;
-        }
-        if(inbuf[i]==TELNET_IAC || sbbs->telnet_cmdlen) {
-            sbbs->telnet_cmd[sbbs->telnet_cmdlen++]=inbuf[i];
-            if(sbbs->telnet_cmdlen==2 && inbuf[i]<TELNET_WILL) {
-				if(startup->options&BBS_OPT_DEBUG_TELNET)
-            		lprintf("Node %d %s telnet cmd: %s"
-	                	,sbbs->cfg.node_num
-						,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
-                		,telnet_cmd_desc(sbbs->telnet_cmd[2]));
-                sbbs->telnet_cmdlen=0;
-            }
-            else if(sbbs->telnet_cmdlen>=3) {
-				if(sbbs->telnet_cmd[2]==TELNET_BINARY) {
-					if(sbbs->telnet_cmd[1]==TELNET_WILL)
-						sbbs->telnet_mode|=TELNET_MODE_BIN_RX;
-					else if(sbbs->telnet_cmd[1]==TELNET_WONT)
-						sbbs->telnet_mode&=~TELNET_MODE_BIN_RX;
-				}
-				if(sbbs->telnet_cmd[2]==TELNET_ECHO) {
-					if(sbbs->telnet_cmd[1]==TELNET_DO)
-						sbbs->telnet_mode|=TELNET_MODE_ECHO;
-					else if(sbbs->telnet_cmd[1]==TELNET_DONT) {
-						sbbs->telnet_mode&=~TELNET_MODE_ECHO;
-						if(!(sbbs->telnet_mode&TELNET_MODE_GATE))
-							sbbs->send_telnet_cmd(TELNET_WILL,TELNET_ECHO);
-					}
-				}
-				if(startup->options&BBS_OPT_DEBUG_TELNET)
-					lprintf("Node %d %s telnet cmd: %s %s"
-	                    ,sbbs->cfg.node_num
-						,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
-						,telnet_cmd_desc(sbbs->telnet_cmd[1])
-						,telnet_opt_desc(sbbs->telnet_cmd[2]));
-                sbbs->telnet_cmdlen=0;
-            }
-			if(sbbs->telnet_mode&TELNET_MODE_GATE)	// Pass-through commads
-				outbuf[outlen++]=inbuf[i];
-        } else
-        	outbuf[outlen++]=inbuf[i];
-    }
-    return(outbuf);
-}
-
-void sbbs_t::send_telnet_cmd(uchar cmd, uchar opt)
-{
-	char buf[16];
-
-	if(cmd<TELNET_WILL) {
-		if(startup->options&BBS_OPT_DEBUG_TELNET)
-            lprintf("Node %d sending telnet cmd: %s"
-	            ,cfg.node_num
-                ,telnet_cmd_desc(cmd));
-		sprintf(buf,"%c%c",TELNET_IAC,cmd);
-		putcom(buf,2);
-	} else {
-		if(startup->options&BBS_OPT_DEBUG_TELNET)
-			lprintf("Node %d sending telnet cmd: %s %s"
-				,cfg.node_num
-				,telnet_cmd_desc(cmd)
-				,telnet_opt_desc(opt));
-		sprintf(buf,"%c%c%c",TELNET_IAC,cmd,opt);
-		putcom(buf,3);
-	}
-}
-
-void input_thread(void *arg)
-{
-	BYTE		inbuf[4000];
-   	BYTE		telbuf[sizeof(inbuf)];
-    BYTE		*wrbuf;
-    int			i,rd,wr,avail;
-	ulong		total_recv=0;
-	ulong		total_pkts=0;
-	fd_set		socket_set;
-	sbbs_t*		sbbs = (sbbs_t*) arg;
-	struct timeval	tv;
-	SOCKET		sock=INVALID_SOCKET;
-
-	thread_up(TRUE /* setuid */);
-
-#ifdef _DEBUG
-	lprintf("Node %d input thread started",sbbs->cfg.node_num);
-#endif
-
-	pthread_mutex_init(&sbbs->input_thread_mutex,NULL);
-    sbbs->input_thread_running = true;
-	sbbs->console|=CON_R_INPUT;
-
-	sock=sbbs->client_socket;
-	while(sbbs->online && sbbs->client_socket!=INVALID_SOCKET
-		&& node_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) {
-
-		pthread_mutex_lock(&sbbs->input_thread_mutex);
-
-#ifdef __unix__
-		if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET)  {
-			if(sock==sbbs->client_socket)
-				sock=uspy_socket[sbbs->cfg.node_num-1];
-			else
-				sock=sbbs->client_socket;
-		}
-#endif
-
-		FD_ZERO(&socket_set);
-		FD_SET(sock,&socket_set);
-
-		if(sock==sbbs->client_socket)  {
-			tv.tv_sec=0;
-			tv.tv_usec=250000;
-		}
-		else  {
-			tv.tv_sec=0;
-			tv.tv_usec=0;
-		}
-
-		if((i=select(sock+1,&socket_set,NULL,NULL,&tv))<1) {
-			pthread_mutex_unlock(&sbbs->input_thread_mutex);
-			if(i==0 && sock==sbbs->client_socket)
-				continue;
-
-			if(sbbs->client_socket==INVALID_SOCKET)
-				break;
-
-			if(sock==sbbs->client_socket)  {
-	        	if(ERROR_VALUE == ENOTSOCK)
-    	            lprintf("Node %d socket closed by peer on input->select", sbbs->cfg.node_num);
-				else if(ERROR_VALUE==ESHUTDOWN)
-					lprintf("Node %d socket shutdown on input->select", sbbs->cfg.node_num);
-				else if(ERROR_VALUE==EINTR)
-					lprintf("Node %d input thread interrupted",sbbs->cfg.node_num);
-        	    else if(ERROR_VALUE==ECONNRESET) 
-					lprintf("Node %d connection reset by peer on input->select", sbbs->cfg.node_num);
-	            else if(ERROR_VALUE==ECONNABORTED) 
-					lprintf("Node %d connection aborted by peer on input->select", sbbs->cfg.node_num);
-				else
-					lprintf("Node %d !ERROR %d input->select socket %d"
-                		,sbbs->cfg.node_num, ERROR_VALUE, sock);
-			}
-#ifdef __unix__
-			else  {
-				if(ERROR_VALUE != EAGAIN)  {
-					lprintf("Node %d !ERROR %d on local spy socket %d input->select"
-						, sbbs->cfg.node_num, errno, sock);
-					close_socket(uspy_socket[sbbs->cfg.node_num-1]);
-					uspy_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
-				}
-			}
-#endif
-			break;
-		}
-
-		if(sbbs->client_socket==INVALID_SOCKET) {
-			pthread_mutex_unlock(&sbbs->input_thread_mutex);
-			break;
-		}
-
-    	rd=RingBufFree(&sbbs->inbuf);
-
-		if(!rd) { // input buffer full
-        	// wait up to 5 seconds to empty (1 byte min)
-			time_t start=time(NULL);
-            while((rd=RingBufFree(&sbbs->inbuf))==0) {
-            	if(time(NULL)-start>=5) {
-                	rd=1;
-                	break;
-                }
-                YIELD();
-            }
-		}
-		
-	    if(rd > (int)sizeof(inbuf))
-        	rd=sizeof(inbuf);
-
-    	rd = recv(sock, (char*)inbuf, rd, 0);
-
-		pthread_mutex_unlock(&sbbs->input_thread_mutex);
-
-		if(rd == SOCKET_ERROR)
-		{
-			if(sock==sbbs->client_socket)  {
-	        	if(ERROR_VALUE == ENOTSOCK)
-    	            lprintf("Node %d socket closed by peer on receive", sbbs->cfg.node_num);
-        	    else if(ERROR_VALUE==ECONNRESET) 
-					lprintf("Node %d connection reset by peer on receive", sbbs->cfg.node_num);
-				else if(ERROR_VALUE==ESHUTDOWN)
-					lprintf("Node %d socket shutdown on receive", sbbs->cfg.node_num);
-        	    else if(ERROR_VALUE==ECONNABORTED) 
-					lprintf("Node %d connection aborted by peer on receive", sbbs->cfg.node_num);
-				else
-					lprintf("Node %d !ERROR %d receiving from socket %d"
-        	        	,sbbs->cfg.node_num, ERROR_VALUE, sock);
-			}
-#ifdef __unix__
-			else  {
-				if(ERROR_VALUE != EAGAIN)  {
-					lprintf("Node %d !ERRRO %d on local spy socket %d receive"
-						,sbbs->cfg.node_num, errno, sock);
-					close_socket(uspy_socket[sbbs->cfg.node_num-1]);
-					uspy_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
-				}
-			}
-#endif
-			break;
-		}
-
-		if(rd == 0 && sock==sbbs->client_socket)
-		{
-			lprintf("Node %d disconnected", sbbs->cfg.node_num);
-			break;
-		}
-
-		total_recv+=rd;
-		total_pkts++;
-
-        // telbuf and wr are modified to reflect telnet escaped data
-#ifdef __unix__
-		if(sock!=sbbs->client_socket)  {
-			wr=rd;
-			wrbuf=inbuf;
-		}
-		else
-#endif
-			wrbuf=telnet_interpret(sbbs, inbuf, rd, telbuf, wr);
-		if(wr > (int)sizeof(telbuf)) 
-			lprintf("!TELBUF OVERFLOW (%d>%d)",wr,sizeof(telbuf));
-
-		/* First level Ctrl-C checking */
-		if(!(sbbs->cfg.ctrlkey_passthru&(1<<CTRL_C))
-			&& sbbs->rio_abortable 
-			&& !(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE))
-			&& memchr(wrbuf, CTRL_C, wr)) {	
-			if(RingBufFull(&sbbs->inbuf))
-    			lprintf("Node %d Ctrl-C hit with %lu bytes in input buffer"
-					,sbbs->cfg.node_num,RingBufFull(&sbbs->inbuf));
-			if(RingBufFull(&sbbs->outbuf))
-    			lprintf("Node %d Ctrl-C hit with %lu bytes in output buffer"
-					,sbbs->cfg.node_num,RingBufFull(&sbbs->outbuf));
-			sbbs->sys_status|=SS_ABORT;
-			RingBufReInit(&sbbs->inbuf);	/* Purge input buffer */
-    		RingBufReInit(&sbbs->outbuf);	/* Purge output buffer */
-			sem_post(&sbbs->inbuf.sem);
-			continue;	// Ignore the entire buffer
-		}
-
-		avail=RingBufFree(&sbbs->inbuf);
-
-        if(avail<wr)
-			lprintf("!INPUT BUFFER FULL (%d free)", avail);
-        else
-			RingBufWrite(&sbbs->inbuf, wrbuf, wr);
-//		if(wr>100)
-//			mswait(500);	// Throttle sender
-	}
-	sbbs->online=0;
-
-    sbbs->input_thread_running = false;
-	if(node_socket[sbbs->cfg.node_num-1]==INVALID_SOCKET)	// Shutdown locally
-		sbbs->terminated = true;	// Signal JS to stop execution
-
-	pthread_mutex_destroy(&sbbs->input_thread_mutex);
-
-	thread_down();
-	lprintf("Node %d input thread terminated (received %lu bytes in %lu blocks)"
-		,sbbs->cfg.node_num, total_recv, total_pkts);
-}
-
-void output_thread(void* arg)
-{
-	char		node[128];
-	char		stats[128];
-    BYTE		buf[IO_THREAD_BUF_SIZE];
-	int			i;
-    ulong		avail;
-	ulong		total_sent=0;
-	ulong		total_pkts=0;
-    ulong		bufbot=0;
-    ulong		buftop=0;
-	sbbs_t*		sbbs = (sbbs_t*) arg;
-	fd_set		socket_set;
-	struct timeval tv;
-
-	thread_up(TRUE /* setuid */);
-
-    if(sbbs->cfg.node_num)
-    	sprintf(node,"Node %d",sbbs->cfg.node_num);
-    else
-    	strcpy(node,sbbs->client_name);
-#ifdef _DEBUG
-	lprintf("%s output thread started",node);
-#endif
-
-    sbbs->output_thread_running = true;
-	sbbs->console|=CON_R_ECHO;
-
-	while(sbbs->client_socket!=INVALID_SOCKET && telnet_socket!=INVALID_SOCKET) {
-
-    	if(bufbot==buftop)
-	    	avail=RingBufFull(&sbbs->outbuf);
-        else
-        	avail=buftop-bufbot;
-
-		if(!avail) {
-			sem_wait(&sbbs->outbuf.sem);
-			if(sbbs->outbuf.highwater_mark)
-				sem_trywait_block(&sbbs->outbuf.highwater_sem,startup->outbuf_drain_timeout);
-			continue; 
-		}
-
-		/* Check socket for writability (using select) */
-		tv.tv_sec=0;
-		tv.tv_usec=1000;
-
-		FD_ZERO(&socket_set);
-		FD_SET(sbbs->client_socket,&socket_set);
-
-		i=select(sbbs->client_socket+1,NULL,&socket_set,NULL,&tv);
-		if(i==SOCKET_ERROR) {
-			lprintf("!%s: ERROR %d selecting socket %u for send"
-				,node,ERROR_VALUE,sbbs->client_socket);
-			if(sbbs->cfg.node_num)	/* Only break if node output (not server) */
-				break;
-			RingBufReInit(&sbbs->outbuf);	/* Flush output buffer */
-			continue;
-		}
-		if(i<1) {
-			continue;
-		}
-
-        if(bufbot==buftop) { // linear buf empty, read from ring buf
-            if(avail>sizeof(buf)) {
-                lprintf("Reducing output buffer");
-                avail=sizeof(buf);
-            }
-            buftop=RingBufRead(&sbbs->outbuf, buf, avail);
-            bufbot=0;
-        }
-		i=sendsocket(sbbs->client_socket, (char*)buf+bufbot, buftop-bufbot);
-		if(i==SOCKET_ERROR) {
-        	if(ERROR_VALUE == ENOTSOCK)
-                lprintf("%s client socket closed on send", node);
-            else if(ERROR_VALUE==ECONNRESET) 
-				lprintf("%s connection reset by peer on send", node);
-            else if(ERROR_VALUE==ECONNABORTED) 
-				lprintf("%s connection aborted by peer on send", node);
-			else
-				lprintf("!%s: ERROR %d sending on socket %d"
-                	,node, ERROR_VALUE, sbbs->client_socket);
-			sbbs->online=0;
-			/* was break; on 4/7/00 */
-			i=buftop-bufbot;	// Pretend we sent it all
-		}
-
-		if(sbbs->cfg.node_num>0 && !(sbbs->sys_status&SS_FILEXFER)) {
-			/* Spy on the user locally */
-			if(startup->node_spybuf!=NULL 
-				&& startup->node_spybuf[sbbs->cfg.node_num-1]!=NULL) {
-				RingBufWrite(startup->node_spybuf[sbbs->cfg.node_num-1],buf+bufbot,i);
-				/* Signal spy output semaphore? */
-				if(startup->node_spysem!=NULL 
-					&& startup->node_spysem[sbbs->cfg.node_num-1]!=NULL)
-					sem_post(startup->node_spysem[sbbs->cfg.node_num-1]);
-			}
-			/* Spy on the user remotely */
-			if(spy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) 
-				sendsocket(spy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i);
-#ifdef __unix__
-			if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET)
-				sendsocket(uspy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i);
-#endif
-		}
-
-		bufbot+=i;
-		total_sent+=i;
-		total_pkts++;
-    }
-
-	sbbs->spymsg("Disconnected");
-
-    sbbs->output_thread_running = false;
-
-	if(total_sent)
-		sprintf(stats,"(sent %lu bytes in %lu blocks, %lu average)"
-			,total_sent, total_pkts, total_sent/total_pkts);
-	else
-		stats[0]=0;
-
-	thread_down();
-	lprintf("%s output thread terminated %s", node, stats);
-}
-
-void event_thread(void* arg)
-{
-	char		str[MAX_PATH+1];
-	char		bat_list[MAX_PATH+1];
-	char		semfile[MAX_PATH+1];
-	int			i,j,k;
-	int			file;
-	int			offset;
-	bool		check_semaphores;
-	ulong		l;
-	time_t		now;
-	time_t		start;
-	time_t		lastsemchk=0;
-	time_t		lastnodechk=0;
-	time_t		lastprepack=0;
-	node_t		node;
-	glob_t		g;
-	sbbs_t*		sbbs = (sbbs_t*) arg;
-	struct tm	now_tm;
-	struct tm	tm;
-
-	eprintf("BBS Events thread started");
-
-	sbbs->event_thread_running = true;
-
-	srand(time(NULL));	/* Seed random number generator */
-	sbbs_random(10);	/* Throw away first number */
-
-	thread_up(TRUE /* setuid */);
-
-#ifdef JAVASCRIPT
-	if(!(startup->options&BBS_OPT_NO_JAVASCRIPT)) {
-		if(!sbbs->js_init())	/* This must be done in the context of the node thread */
-			lprintf("!JavaScript Initialization FAILURE");
-	}
-#endif
-
-	while(!sbbs->terminated && telnet_socket!=INVALID_SOCKET) {
-
-		now=time(NULL);
-		localtime_r(&now,&now_tm);
-
-		if(now-lastsemchk>=sbbs->cfg.node_sem_check) {
-			check_semaphores=true;
-			lastsemchk=now;
-		} else
-			check_semaphores=false;
-
-		pthread_mutex_lock(&event_mutex);
-		event_mutex_locked=true;
-
-		sbbs->online=0;	/* reset this from ON_LOCAL */
-
-		if(scfg_reloaded==true) {
-
-			for(i=0;i<TOTAL_TEXT;i++)
-				sbbs->text[i]=sbbs->text_sav[i]=text[i];
-
-			memcpy(&sbbs->cfg,&scfg,sizeof(scfg_t));
-
-			if(startup->temp_dir[0]) {
-				SAFECOPY(sbbs->cfg.temp_dir,startup->temp_dir);
-			} else
-				prep_dir(sbbs->cfg.data_dir, sbbs->cfg.temp_dir, sizeof(sbbs->cfg.temp_dir));
-
-			// Read TIME.DAB
-			sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir);
-			if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) {
-				sbbs->errormsg(WHERE,ERR_OPEN,str,0);
-				break; 
-			}
-			for(i=0;i<sbbs->cfg.total_events;i++) {
-				sbbs->cfg.event[i]->last=0;
-				if(filelength(file)<(long)(sizeof(time_t)*(i+1)))
-					write(file,&sbbs->cfg.event[i]->last,sizeof(time_t));
-				else
-					read(file,&sbbs->cfg.event[i]->last,sizeof(time_t)); 
-			}
-			read(file,&lastprepack,sizeof(time_t));
-			close(file);
-
-			// Read QNET.DAB
-			sprintf(str,"%sqnet.dab",sbbs->cfg.ctrl_dir);
-			if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) {
-				sbbs->errormsg(WHERE,ERR_OPEN,str,0);
-				return;
-			}
-			for(i=0;i<sbbs->cfg.total_qhubs;i++) {
-				sbbs->cfg.qhub[i]->last=0;
-				if(filelength(file)<(long)(sizeof(time_t)*(i+1)))
-					write(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t));
-				else
-					read(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t)); 
-			}
-			close(file);
-
-			// Read PNET.DAB
-			sprintf(str,"%spnet.dab",sbbs->cfg.ctrl_dir);
-			if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) {
-				sbbs->errormsg(WHERE,ERR_OPEN,str,0);
-				break;
-			}
-			for(i=0;i<sbbs->cfg.total_phubs;i++) {
-				sbbs->cfg.phub[i]->last=0;
-				if(filelength(file)<(long)(sizeof(time_t)*(i+1)))
-					write(file,&sbbs->cfg.phub[i]->last,sizeof(time_t));
-				else
-					read(file,&sbbs->cfg.phub[i]->last,sizeof(time_t)); 
-			}
-			close(file);
-
-			scfg_reloaded=false;
-		}
-
-		/* QWK events */
-		if(check_semaphores && !(startup->options&BBS_OPT_NO_QWK_EVENTS)) {
-			/* Import any REP files that have magically appeared (via FTP perhaps) */
-			sprintf(str,"%sfile/",sbbs->cfg.data_dir);
-			offset=strlen(str);
-			strcat(str,"*.rep");
-			glob(str,0,NULL,&g);
-			for(i=0;i<(int)g.gl_pathc;i++) {
-				sbbs->useron.number=atoi(g.gl_pathv[i]+offset);
-				getuserdat(&sbbs->cfg,&sbbs->useron);
-				if(sbbs->useron.number && flength(g.gl_pathv[i])>0) {
-					sbbs->online=ON_LOCAL;
-					eprintf("Un-packing QWK Reply packet from %s",sbbs->useron.alias);
-					sbbs->getusrsubs();
-					sbbs->unpack_rep(g.gl_pathv[i]);
-					sbbs->batch_create_list();	/* FREQs? */
-					sbbs->batdn_total=0;
-					
-					/* putuserdat? */
-					remove(g.gl_pathv[i]);
-				}
-			}
-			globfree(&g);
-
-			/* Create any QWK files that have magically appeared (via FTP perhaps) */
-			sprintf(str,"%spack*.now",sbbs->cfg.data_dir);
-			offset=strlen(sbbs->cfg.data_dir)+4;
-			glob(str,0,NULL,&g);
-			for(i=0;i<(int)g.gl_pathc;i++) {
-				sbbs->useron.number=atoi(g.gl_pathv[i]+offset);
-				getuserdat(&sbbs->cfg,&sbbs->useron);
-				if(sbbs->useron.number && !(sbbs->useron.misc&(DELETED|INACTIVE))) {
-					eprintf("Packing QWK Message Packet for %s",sbbs->useron.alias);
-					sbbs->online=ON_LOCAL;
-					delfiles(sbbs->cfg.temp_dir,ALLFILES);
-					sbbs->getmsgptrs();
-					sbbs->getusrsubs();
-					sbbs->batdn_total=0;
-
-					sbbs->last_ns_time=sbbs->ns_time=sbbs->useron.ns_time;
-					sprintf(bat_list,"%sfile/%04u.dwn",sbbs->cfg.data_dir,sbbs->useron.number);
-					sbbs->batch_add_list(bat_list);
-
-					sprintf(str,"%sfile%c%04u.qwk"
-						,sbbs->cfg.data_dir,BACKSLASH,sbbs->useron.number);
-					if(sbbs->pack_qwk(str,&l,true /* pre-pack/off-line */)) {
-						eprintf("Packing completed");
-						sbbs->qwk_success(l,0,1);
-						sbbs->putmsgptrs(); 
-						remove(bat_list);
-					} else
-						eprintf("No packet created (no new messages)");
-					delfiles(sbbs->cfg.temp_dir,ALLFILES);
-					sbbs->online=0;
-				}
-				remove(g.gl_pathv[i]);
-			}
-			globfree(&g);
-
-			/* Create (pre-pack) QWK files for users configured as such */
-			sprintf(semfile,"%sprepack.now",sbbs->cfg.data_dir);
-			if(sbbs->cfg.preqwk_ar[0] 
-				&& (fexistcase(semfile) || (now-lastprepack)/60>(60*24))) {
-				j=lastuser(&sbbs->cfg);
-				eprintf("Pre-packing QWK Message packets...");
-				for(i=1;i<=j;i++) {
-
-					sprintf(str,"%5u of %-5u",i,j);
-					//status(str);
-					sbbs->useron.number=i;
-					getuserdat(&sbbs->cfg,&sbbs->useron);
-
-					if(sbbs->useron.number
-						&& !(sbbs->useron.misc&(DELETED|INACTIVE))	 /* Pre-QWK */
-						&& sbbs->chk_ar(sbbs->cfg.preqwk_ar,&sbbs->useron)) { 
-						for(k=1;k<=sbbs->cfg.sys_nodes;k++) {
-							if(sbbs->getnodedat(k,&node,0)!=0)
-								continue;
-							if((node.status==NODE_INUSE || node.status==NODE_QUIET
-								|| node.status==NODE_LOGON) && node.useron==i)
-								break; 
-						}
-						if(k<=sbbs->cfg.sys_nodes)	/* Don't pre-pack with user online */
-							continue;
-						eprintf("Pre-packing QWK for %s",sbbs->useron.alias);
-						sbbs->online=ON_LOCAL;
-						delfiles(sbbs->cfg.temp_dir,ALLFILES);
-						sbbs->getmsgptrs();
-						sbbs->getusrsubs();
-						sbbs->batdn_total=0;
-						sprintf(str,"%sfile%c%04u.qwk"
-							,sbbs->cfg.data_dir,BACKSLASH,sbbs->useron.number);
-						if(sbbs->pack_qwk(str,&l,true /* pre-pack */)) {
-							sbbs->qwk_success(l,0,1);
-							sbbs->putmsgptrs(); 
-						}
-						delfiles(sbbs->cfg.temp_dir,ALLFILES);
-						sbbs->online=0;
-					} 
-				}
-				lastprepack=now;
-				sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir);
-				if((file=sbbs->nopen(str,O_WRONLY))==-1) {
-					sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
-					break; 
-				}
-				lseek(file,(long)sbbs->cfg.total_events*4L,SEEK_SET);
-				write(file,&lastprepack,sizeof(time_t));
-				close(file);
-
-				remove(semfile);
-				//status(STATUS_WFC);
-			}
-		}
-
-		if(check_semaphores) {
-			/* Node Daily Events */
-			for(i=first_node;i<=last_node;i++) {
-				// Node Daily Event
-				node.status=NODE_INVALID_STATUS;
-				if(sbbs->getnodedat(i,&node,0)!=0)
-					continue;
-				if(node.misc&NODE_EVENT && node.status==NODE_WFC) {
-					sbbs->getnodedat(i,&node,1);
-					node.status=NODE_EVENT_RUNNING;
-					sbbs->putnodedat(i,&node);
-					if(sbbs->cfg.node_daily[0]) {
-						sbbs->cfg.node_num=i;
-						strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[i-1]);
-
-						eprintf("Running node %d daily event",i);
-						sbbs->online=ON_LOCAL;
-						sbbs->logentry("!:","Run node daily event");
-						sbbs->external(
-							 sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL)
-							,EX_OFFLINE);
-					}
-					sbbs->getnodedat(i,&node,1);
-					node.misc&=~NODE_EVENT;
-					node.status=NODE_WFC;
-					node.useron=0;
-					sbbs->putnodedat(i,&node); 
-				}
-			}
-
-			/* QWK Networking Call-out sempahores */
-			for(i=0;i<sbbs->cfg.total_qhubs;i++) {
-				if(sbbs->cfg.qhub[i]->node<first_node 
-					|| sbbs->cfg.qhub[i]->node>last_node)
-					continue;
-				if(sbbs->cfg.qhub[i]->last==-1) // already signaled
-					continue;
-				sprintf(str,"%sqnet/%s.now",sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
-				if(fexistcase(str)) {
-					strcpy(str,sbbs->cfg.qhub[i]->id);
-					eprintf("Semaphore signaled for QWK Network Hub: %s",strupr(str));
-					sbbs->cfg.qhub[i]->last=-1; 
-				}
-			}
-
-			/* Timed Event sempahores */
-			for(i=0;i<sbbs->cfg.total_events;i++) {
-				if((sbbs->cfg.event[i]->node<first_node
-					|| sbbs->cfg.event[i]->node>last_node)
-					&& !(sbbs->cfg.event[i]->misc&EVENT_EXCL))
-					continue;	// ignore non-exclusive events for other instances
-				if(sbbs->cfg.event[i]->last==-1) // already signaled
-					continue;
-				sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
-				if(fexistcase(str)) {
-					strcpy(str,sbbs->cfg.event[i]->code);
-					eprintf("Semaphore signaled for Timed Event: %s",strupr(str));
-					sbbs->cfg.event[i]->last=-1; 
-				}
-			}
-		}
-
-		/* QWK Networking Call-out Events */
-		for(i=0;i<sbbs->cfg.total_qhubs;i++) {
-			if(sbbs->cfg.qhub[i]->node<first_node ||
-				sbbs->cfg.qhub[i]->node>last_node)
-				continue;
-
-			if(check_semaphores) {
-				// See if any packets have come in
-				for(j=0;j<101;j++) {
-					sprintf(str,"%s%s.q%c%c",sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id
-						,j>10 ? ((j-1)/10)+'0' : 'w'
-						,j ? ((j-1)%10)+'0' : 'k');
-					fexistcase(str);		/* fix wrong-case filenames on Unix */
-					if(flength(str)>0) {	/* silently ignore 0-byte QWK packets */
-						delfiles(sbbs->cfg.temp_dir,ALLFILES);
-						sbbs->online=ON_LOCAL;
-						if(sbbs->unpack_qwk(str,i)==false) {
-							char newname[MAX_PATH+1];
-							sprintf(newname,"%s.%lx.bad",str,(long)now);
-							remove(newname);
-							if(rename(str,newname)==0) {
-								char logmsg[MAX_PATH*3];
-								sprintf(logmsg,"%s renamed to %s",str,newname);
-								sbbs->logline("Q!",logmsg);
-							}
-						}
-						sbbs->online=0;
-						remove(str);
-					} 
-				}
-			}
-
-			/* Qnet call out based on time */
-			if(localtime_r(&sbbs->cfg.qhub[i]->last,&tm)==NULL)
-				memset(&tm,0,sizeof(tm));
-			if((sbbs->cfg.qhub[i]->last==-1L					/* or frequency */
-				|| ((sbbs->cfg.qhub[i]->freq
-					&& (now-sbbs->cfg.qhub[i]->last)/60>sbbs->cfg.qhub[i]->freq)
-					|| (sbbs->cfg.qhub[i]->time
-						&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.qhub[i]->time
-						&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
-						&& sbbs->cfg.qhub[i]->days&(1<<now_tm.tm_wday))) {
-				sprintf(str,"%sqnet/%s.now"
-					,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
-				if(fexistcase(str))
-					remove(str);					/* Remove semaphore file */
-				sprintf(str,"%sqnet/%s.ptr"
-					,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
-				file=sbbs->nopen(str,O_RDONLY);
-				for(j=0;j<sbbs->cfg.qhub[i]->subs;j++) {
-					sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr=0;
-					if(file!=-1) {
-						lseek(file,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*sizeof(long),SEEK_SET);
-						read(file,&sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr,sizeof(long)); 
-					}
-				}
-				if(file!=-1)
-					close(file);
-				if(sbbs->pack_rep(i)) {
-					if((file=sbbs->nopen(str,O_WRONLY|O_CREAT))==-1)
-						sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_CREAT);
-					else {
-						for(j=l=0;j<sbbs->cfg.qhub[i]->subs;j++) {
-							while(filelength(file)<
-								sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*4L)
-								write(file,&l,4);		/* initialize ptrs to null */
-							lseek(file
-								,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*sizeof(long)
-								,SEEK_SET);
-							write(file,&sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr,sizeof(long)); 
-						}
-						close(file); 
-					} 
-				}
-				delfiles(sbbs->cfg.temp_dir,ALLFILES);
-
-				sbbs->cfg.qhub[i]->last=time(NULL);
-				sprintf(str,"%sqnet.dab",sbbs->cfg.ctrl_dir);
-				if((file=sbbs->nopen(str,O_WRONLY))==-1) {
-					sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
-					break; 
-				}
-				lseek(file,sizeof(time_t)*i,SEEK_SET);
-				write(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t));
-				close(file);
-
-				if(sbbs->cfg.qhub[i]->call[0]) {
-					sbbs->cfg.node_num=sbbs->cfg.qhub[i]->node;
-					if(sbbs->cfg.node_num<1) 
-						sbbs->cfg.node_num=1;
-					strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
-					eprintf("QWK Network call-out: %s",sbbs->cfg.qhub[i]->id); 
-					sbbs->online=ON_LOCAL;
-					sbbs->external(
-						 sbbs->cmdstr(sbbs->cfg.qhub[i]->call,nulstr,nulstr,NULL)
-						,EX_OFFLINE|EX_SH);	/* sh for Unix perl scripts */
-				}
-			} 
-		}
-
-		/* PostLink Networking Call-out Events */
-		for(i=0;i<sbbs->cfg.total_phubs;i++) {
-			if(sbbs->cfg.phub[i]->node<first_node 
-				|| sbbs->cfg.phub[i]->node>last_node)
-				continue;
-			/* PostLink call out based on time */
-			if(localtime_r(&sbbs->cfg.phub[i]->last,&tm)==NULL)
-				memset(&tm,0,sizeof(tm));
-			if(sbbs->cfg.phub[i]->last==-1
-				|| (((sbbs->cfg.phub[i]->freq								/* or frequency */
-					&& (now-sbbs->cfg.phub[i]->last)/60>sbbs->cfg.phub[i]->freq)
-				|| (sbbs->cfg.phub[i]->time
-					&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.phub[i]->time
-				&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
-				&& sbbs->cfg.phub[i]->days&(1<<now_tm.tm_wday))) {
-
-				sbbs->cfg.phub[i]->last=time(NULL);
-				sprintf(str,"%spnet.dab",sbbs->cfg.ctrl_dir);
-				if((file=sbbs->nopen(str,O_WRONLY))==-1) {
-					sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
-					break; 
-				}
-				lseek(file,sizeof(time_t)*i,SEEK_SET);
-				write(file,&sbbs->cfg.phub[i]->last,sizeof(time_t));
-				close(file);
-
-				if(sbbs->cfg.phub[i]->call[0]) {
-					sbbs->cfg.node_num=sbbs->cfg.phub[i]->node;
-					if(sbbs->cfg.node_num<1) 
-						sbbs->cfg.node_num=1;
-					strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
-					eprintf("PostLink Network call-out: %s",sbbs->cfg.phub[i]->name); 
-					sbbs->online=ON_LOCAL;
-					sbbs->external(
-						 sbbs->cmdstr(sbbs->cfg.phub[i]->call,nulstr,nulstr,NULL)
-						,EX_OFFLINE|EX_SH);	/* sh for Unix perl scripts */
-				} 
-			}
-		}
-
-		/* Timed Events */
-		for(i=0;i<sbbs->cfg.total_events;i++) {
-			if(!sbbs->cfg.event[i]->node 
-				|| sbbs->cfg.event[i]->node>sbbs->cfg.sys_nodes)
-				continue;	// ignore events for invalid nodes
-
-			if((sbbs->cfg.event[i]->node<first_node
-				|| sbbs->cfg.event[i]->node>last_node)
-				&& !(sbbs->cfg.event[i]->misc&EVENT_EXCL))
-				continue;	// ignore non-exclusive events for other instances
-
-			if(localtime_r(&sbbs->cfg.event[i]->last,&tm)==NULL)
-				memset(&tm,0,sizeof(tm));
-			if(sbbs->cfg.event[i]->last==-1 ||
-				(((sbbs->cfg.event[i]->freq 
-					&& (now-sbbs->cfg.event[i]->last)/60>sbbs->cfg.event[i]->freq)
-				|| 	(!sbbs->cfg.event[i]->freq 
-					&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.event[i]->time
-				&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
-				&& sbbs->cfg.event[i]->days&(1<<now_tm.tm_wday)
-				&& (sbbs->cfg.event[i]->mdays==0 
-					|| sbbs->cfg.event[i]->mdays&(1<<now_tm.tm_mday)))) 
-			{
-				if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */
-
-					if(sbbs->cfg.event[i]->node<first_node
-						|| sbbs->cfg.event[i]->node>last_node) {
-						eprintf("Waiting for node %d to run timed event: %s"
-							,sbbs->cfg.event[i]->node,sbbs->cfg.event[i]->code);
-						lastnodechk=0;	 /* really last event time check */
-						start=time(NULL);
-						while(!sbbs->terminated) {
-							mswait(1000);
-							now=time(NULL);
-							if(now-lastnodechk<10)
-								continue;
-							for(j=first_node;j<=last_node;j++) {
-								if(sbbs->getnodedat(j,&node,1)!=0)
-									continue;
-								if(node.status==NODE_WFC)
-									node.status=NODE_EVENT_LIMBO;
-								node.aux=sbbs->cfg.event[i]->node;
-								sbbs->putnodedat(j,&node);
-							}
-
-							lastnodechk=now;
-							sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir);
-							if((file=sbbs->nopen(str,O_RDONLY))==-1) {
-								sbbs->errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
-								sbbs->cfg.event[i]->last=now;
-								continue; 
-							}
-							lseek(file,(long)i*4L,SEEK_SET);
-							read(file,&sbbs->cfg.event[i]->last,sizeof(time_t));
-							close(file);
-							if(now-sbbs->cfg.event[i]->last<(60*60))	/* event is done */
-								break; 
-							if(now-start>(90*60)) {
-								eprintf("!TIMEOUT waiting for event to complete");
-								break;
-							}
-						}
-						sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
-						if(fexistcase(str))
-							remove(str);
-						sbbs->cfg.event[i]->last=now;
-					} else {	// Exclusive event to run on a node under our control
-						eprintf("Waiting for all nodes to become inactive before "
-							"running timed event: %s",sbbs->cfg.event[i]->code);
-						lastnodechk=0;
-						start=time(NULL);
-						while(!sbbs->terminated) {
-							mswait(1000);
-							now=time(NULL);
-							if(now-lastnodechk<10)
-								continue;
-							lastnodechk=now;
-							// Check/change the status of the nodes that we're in control of
-							for(j=first_node;j<=last_node;j++) {
-								if(sbbs->getnodedat(j,&node,1)!=0)
-									continue;
-								if(node.status==NODE_WFC) {
-									if(j==sbbs->cfg.event[i]->node)
-										node.status=NODE_EVENT_WAITING;
-									else
-										node.status=NODE_EVENT_LIMBO;
-									node.aux=sbbs->cfg.event[i]->node;
-								}
-								sbbs->putnodedat(j,&node);
-							}
-
-							for(j=1;j<=sbbs->cfg.sys_nodes;j++) {
-								if(sbbs->getnodedat(j,&node,0)!=0)
-									continue;
-								if(j==sbbs->cfg.event[i]->node) {
-									if(node.status!=NODE_EVENT_WAITING)
-										break;
-								} else {
-									if(node.status!=NODE_OFFLINE
-										&& node.status!=NODE_EVENT_LIMBO)
-										break; 
-								}
-							}
-							if(j>sbbs->cfg.sys_nodes) /* all nodes either offline or in limbo */
-								break;
-							eprintf("Waiting for node %d (status=%d)",j,node.status);
-							if(now-start>(90*60)) {
-								eprintf("!TIMEOUT waiting for node %d to become inactive",j);
-								break;
-							}
-						} 
-					} 
-				}
-#if 0 // removed Jun-23-2002
-				else {	/* non-exclusive */
-					sbbs->getnodedat(sbbs->cfg.event[i]->node,&node,0);
-					if(node.status!=NODE_WFC)
-						continue;
-				}
-#endif
-				if(sbbs->cfg.event[i]->node<first_node 
-					|| sbbs->cfg.event[i]->node>last_node) {
-					eprintf("Changing node status for nodes %d through %d to WFC"
-						,first_node,last_node);
-					sbbs->cfg.event[i]->last=now;
-					for(j=first_node;j<=last_node;j++) {
-						node.status=NODE_INVALID_STATUS;
-						if(sbbs->getnodedat(j,&node,1)!=0)
-							continue;
-						node.status=NODE_WFC;
-						sbbs->putnodedat(j,&node);
-					}
-				}
-				else {
-					sbbs->cfg.node_num=sbbs->cfg.event[i]->node;
-					if(sbbs->cfg.node_num<1) 
-						sbbs->cfg.node_num=1;
-					strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
-				
-					sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
-					if(fexistcase(str))
-						remove(str);
-					if(sbbs->cfg.event[i]->misc&EVENT_EXCL) {
-						sbbs->getnodedat(sbbs->cfg.event[i]->node,&node,1);
-						node.status=NODE_EVENT_RUNNING;
-						sbbs->putnodedat(sbbs->cfg.event[i]->node,&node);
-					}
-					strcpy(str,sbbs->cfg.event[i]->code);
-					eprintf("Running timed event: %s",strupr(str));
-					int ex_mode = EX_OFFLINE;
-					if(!(sbbs->cfg.event[i]->misc&EVENT_EXCL)
-						&& sbbs->cfg.event[i]->misc&EX_BG)
-						ex_mode |= EX_BG;
-					if(sbbs->cfg.event[i]->misc&XTRN_SH)
-						ex_mode |= EX_SH;
-					ex_mode|=(sbbs->cfg.event[i]->misc&EX_NATIVE);
-					sbbs->online=ON_LOCAL;
-					sbbs->external(
-						 sbbs->cmdstr(sbbs->cfg.event[i]->cmd,nulstr,sbbs->cfg.event[i]->dir,NULL)
-						,ex_mode
-						,sbbs->cfg.event[i]->dir);
-					sbbs->cfg.event[i]->last=time(NULL);
-					sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir);
-					if((file=sbbs->nopen(str,O_WRONLY))==-1) {
-						sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
-						break; 
-					}
-					lseek(file,(long)i*4L,SEEK_SET);
-					write(file,&sbbs->cfg.event[i]->last,sizeof(time_t));
-					close(file);
-
-					if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */
-						// Check/change the status of the nodes that we're in control of
-						for(j=first_node;j<=last_node;j++) {
-							node.status=NODE_INVALID_STATUS;
-							if(sbbs->getnodedat(j,&node,1)!=0)
-								continue;
-							node.status=NODE_WFC;
-							sbbs->putnodedat(j,&node);
-						}
-					}
-				} 
-			} 
-		}
-		event_mutex_locked=false;
-		pthread_mutex_unlock(&event_mutex);
-
-		mswait(1000);
-	}
-	sbbs->cfg.node_num=0;
-    sbbs->event_thread_running = false;
-
-	thread_down();
-	eprintf("BBS Event thread terminated (%u threads remain)", thread_count);
-}
-
-
-//****************************************************************************
-sbbs_t::sbbs_t(ushort node_num, DWORD addr, char* name, SOCKET sd,
-			   scfg_t* global_cfg, char* global_text[], client_t* client_info)
-{
-	char	nodestr[32];
-	uint	i;
-
-    if(node_num)
-    	sprintf(nodestr,"Node %d",node_num);
-    else
-    	strcpy(nodestr,name);
-
-	lprintf("%s constructor using socket %d (settings=%lx)"
-		, nodestr, sd, global_cfg->node_misc);
-
-	startup = ::startup;	// Convert from global to class member
-
-	memcpy(&cfg, global_cfg, sizeof(cfg));
-
-	cfg.node_num=node_num;
-	if(node_num>0) {
-		strcpy(cfg.node_dir, cfg.node_path[node_num-1]);
-		prep_dir(cfg.node_dir, cfg.temp_dir, sizeof(cfg.temp_dir));
-	} else if(startup->temp_dir[0]) {
-		SAFECOPY(cfg.temp_dir,startup->temp_dir);
-	} else
-    	prep_dir(cfg.data_dir, cfg.temp_dir, sizeof(cfg.temp_dir));
-
-	terminated = false;
-	event_thread_running = false;
-    input_thread_running = false;
-    output_thread_running = false;
-	input_thread_mutex_locked = false;
-
-	if(client_info==NULL)
-		memset(&client,0,sizeof(client));
-	else
-		memcpy(&client,client_info,sizeof(client));
-	client_addr = addr;
-	client_socket = sd;
-	SAFECOPY(client_name, name);
-	client_socket_dup=INVALID_SOCKET;
-	client_ident[0]=0;
-
-	/* Init some important variables */
-
-	rio_abortable=false;
-
-	console = 0;
-	online = 0;
-	outchar_esc = 0;
-	nodemsg_inside = 0;	/* allows single nest */
-	hotkey_inside = 0;	/* allows single nest */
-	event_time = 0;
-	event_code = nulstr;
-	nodesync_inside = false;
-	errorlog_inside = false;
-	errormsg_inside = false;
-	gettimeleft_inside = false;
-	uselect_total = 0;
-	lbuflen = 0;
-	connection="Telnet";
-    telnet_cmdlen=0;
-	telnet_mode=0;
-	telnet_last_rxch=0;
-	sys_status=lncntr=tos=criterrs=keybufbot=keybuftop=lbuflen=slcnt=0L;
-	curatr=LIGHTGRAY;
-	errorlevel=0;
-	logcol=1;
-	logfile_fp=NULL;
-	nodefile=-1;
-	node_ext=-1;
-	nodefile_fp=NULL;
-	node_ext_fp=NULL;
-	current_msg=NULL;
-
-#ifdef JAVASCRIPT
-	js_runtime=NULL;	/* runtime */
-	js_cx=NULL;			/* context */
-#endif
-
-	for(i=0;i<TOTAL_TEXT;i++)
-		text[i]=text_sav[i]=global_text[i];
-
-	memset(&main_csi,0,sizeof(main_csi));
-	memset(&thisnode,0,sizeof(thisnode));
-	memset(&useron,0,sizeof(useron));
-	memset(&inbuf,0,sizeof(inbuf));
-	memset(&outbuf,0,sizeof(outbuf));
-	memset(&smb,0,sizeof(smb));
-
-	global_str_vars=0;
-	global_str_var=NULL;
-	global_str_var_name=NULL;
-	global_int_vars=0;
-	global_int_var=NULL;
-	global_int_var_name=NULL;
-	sysvar_li=0;
-	sysvar_pi=0;
-
-	cursub=NULL;
-	usrgrp=NULL;
-	usrsubs=NULL;
-	usrsub=NULL;
-	usrgrp_total=0;
-
-	subscan=NULL;
-
-	curdir=NULL;
-	usrlib=NULL;
-	usrdirs=NULL;
-	usrdir=NULL;
-	usrlib_total=0;
-
-	batup_desc=NULL;
-	batup_name=NULL;
-	batup_misc=NULL;
-	batup_dir=NULL;
-	batup_alt=NULL;
-
-	batdn_name=NULL;
-	batdn_dir=NULL;
-	batdn_offset=NULL;
-	batdn_size=NULL;
-	batdn_alt=NULL;
-	batdn_cdt=NULL;
-
-	spymsg("Connected");
-}
-
-//****************************************************************************
-bool sbbs_t::init()
-{
-	char		str[MAX_PATH+1];
-	char		tmp[128];
-	int			result;
-	uint		i,j,k,l;
-	node_t		node;
-	socklen_t	addr_len;
-	SOCKADDR_IN	addr;
-
-	if(cfg.node_num>0) {
-		RingBufInit(&inbuf, IO_THREAD_BUF_SIZE);
-		node_inbuf[cfg.node_num-1]=&inbuf;
-	}
-
-    RingBufInit(&outbuf, IO_THREAD_BUF_SIZE);
-	outbuf.highwater_mark=startup->outbuf_highwater_mark;
-
-	if(cfg.node_num && client_socket!=INVALID_SOCKET) {
-
-#ifdef _WIN32
-		if(!DuplicateHandle(GetCurrentProcess(),
-			(HANDLE)client_socket,
-			GetCurrentProcess(),
-			(HANDLE*)&client_socket_dup,
-			0,
-			TRUE, // Inheritable
-			DUPLICATE_SAME_ACCESS)) {
-			errormsg(WHERE,ERR_CREATE,"duplicate socket handle",client_socket);
-			return(false);
-		}
-#endif
-
-		addr_len=sizeof(addr);
-		if((result=getsockname(client_socket, (struct sockaddr *)&addr,&addr_len))!=0) {
-			lprintf("Node %d !ERROR %d (%d) getting address/port"
-				,cfg.node_num, result, ERROR_VALUE);
-			return(false);
-		} 
-		lprintf("Node %d attached to local interface %s port %d"
-			,cfg.node_num, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
-
-		local_addr=addr.sin_addr.s_addr;
-	}
-
-	comspec=getenv(
-#ifdef __unix__
-		"SHELL"
-#else
-		"COMSPEC"
-#endif
-		);
-	if(comspec==NULL) {
-		errormsg(WHERE, ERR_CHK, "shell/comspec", 0);
-		return(false);
-	}
-
-	md(cfg.temp_dir);
-
-	/* Shared NODE files */
-	sprintf(str,"%s%s",cfg.ctrl_dir,"node.dab");
-	if((nodefile=nopen(str,O_DENYNONE|O_RDWR|O_CREAT))==-1) {
-		errormsg(WHERE, ERR_OPEN, str, cfg.node_num);
-		return(false); 
-	}
-	memset(&node,0,sizeof(node_t));  /* write NULL to node struct */
-	node.status=NODE_OFFLINE;
-	while(filelength(nodefile)<(long)(cfg.sys_nodes*sizeof(node_t))) {
-		lseek(nodefile,0L,SEEK_END);
-		if(write(nodefile,&node,sizeof(node_t))!=sizeof(node_t)) {
-			errormsg(WHERE,ERR_WRITE,str,sizeof(node_t));
-			break; 
-		}
-	}
-	for(i=0; cfg.node_num>0 && i<LOOP_NODEDAB; i++) {
-		if(lock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t))==0) {
-			unlock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t));
-			break;
-		}
-		mswait(100);
-	}
-	if(cfg.node_misc&NM_CLOSENODEDAB) {
-		close(nodefile);
-		nodefile=-1;
-	}
-
-	if(i>=LOOP_NODEDAB) {
-		errormsg(WHERE, ERR_LOCK, str, cfg.node_num);
-		return(false); 
-	}
-
-	if(cfg.node_num) {
-		sprintf(str,"%snode.log",cfg.node_dir);
-		if((logfile_fp=fopen(str,"a+b"))==NULL) {
-			errormsg(WHERE, ERR_OPEN, str, 0);
-			lprintf("Perhaps this node is already running");
-			return(false); 
-		}
-
-		if(filelength(fileno(logfile_fp))) {
-			log(crlf);
-			now=time(NULL);
-			struct tm tm;
-			localtime_r(&now,&tm);
-			sprintf(str,"%s  %s %s %02d %u  "
-				"End of preexisting log entry (possible crash)"
-				,hhmmtostr(&cfg,&tm,tmp)
-				,wday[tm.tm_wday]
-				,mon[tm.tm_mon],tm.tm_mday,tm.tm_year+1900);
-			logline("L!",str);
-			log(crlf);
-			catsyslog(1); 
-		}
-
-		getnodedat(cfg.node_num,&thisnode,1);
-		/* thisnode.status=0; */
-		thisnode.action=0;
-		thisnode.useron=0;
-		thisnode.aux=0;
-		thisnode.misc&=(NODE_EVENT|NODE_LOCK|NODE_RRUN);
-		criterrs=thisnode.errors;
-		putnodedat(cfg.node_num,&thisnode);
-	}
-
-/** Put in if(cfg.node_num) ? (not needed for server and event threads) */
-	backout();
-
-	/* Reset COMMAND SHELL */
-
-	main_csi.str=(char *)MALLOC(1024);
-	if(main_csi.str==NULL) {
-		errormsg(WHERE,ERR_ALLOC,"main_csi.str",1024);
-		return(false); 
-	}
-	memset(main_csi.str,0,1024);
-/***/
-
-	if(cfg.total_grps) {
-
-		usrgrp_total = cfg.total_grps;
-
-		if((cursub=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "cursub", sizeof(uint)*usrgrp_total);
-			return(false);
-		}
-
-		if((usrgrp=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "usrgrp", sizeof(uint)*usrgrp_total);
-			return(false);
-		}
-
-		if((usrsubs=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "usrsubs", sizeof(uint)*usrgrp_total);
-			return(false);
-		}
-
-		if((usrsub=(uint **)calloc(usrgrp_total,sizeof(uint *)))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "usrsub", sizeof(uint)*usrgrp_total);
-			return(false);
-		}
- 
-		if((subscan=(subscan_t *)MALLOC(sizeof(subscan_t)*cfg.total_subs))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "subscan", sizeof(subscan_t)*cfg.total_subs);
-			return(false);
-		}
-	}
-
-	for(i=l=0;i<(uint)cfg.total_grps;i++) {
-		for(j=k=0;j<cfg.total_subs;j++)
-			if(cfg.sub[j]->grp==i)
-				k++;	/* k = number of subs per grp[i] */
-		if(k>l) l=k;  	/* l = the largest number of subs per grp */
-	}
-	if(l)
-		for(i=0;i<cfg.total_grps;i++)
-			if((usrsub[i]=(uint *)MALLOC(sizeof(uint)*l))==NULL) {
-				errormsg(WHERE, ERR_ALLOC, "usrsub[x]", sizeof(uint)*l);
-				return(false);
-			}
-
-	if(cfg.total_libs) {
-
-		usrlib_total = cfg.total_libs;
-
-		if((curdir=(uint *)MALLOC(sizeof(uint)*usrlib_total))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "curdir", sizeof(uint)*usrlib_total);
-			return(false);
-		}
-
-		if((usrlib=(uint *)MALLOC(sizeof(uint)*usrlib_total))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "usrlib", sizeof(uint)*usrlib_total);
-			return(false);
-		}
-
-		if((usrdirs=(uint *)MALLOC(sizeof(uint)*usrlib_total))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "usrdirs", sizeof(uint)*usrlib_total);
-			return(false);
-		}
-
-		if((usrdir=(uint **)calloc(usrlib_total,sizeof(uint *)))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "usrdir", sizeof(uint)*usrlib_total);
-			return(false);
-		}
-	}
-
-	for(i=l=0;i<cfg.total_libs;i++) {
-		for(j=k=0;j<cfg.total_dirs;j++)
-			if(cfg.dir[j]->lib==i)
-				k++;
-		if(k>l) l=k; 	/* l = largest number of dirs in a lib */
-	}
-	if(l) {
-		l++;	/* for temp dir */
-		for(i=0;i<cfg.total_libs;i++)
-			if((usrdir[i]=(uint *)MALLOC(sizeof(uint)*l))==NULL) {
-				errormsg(WHERE, ERR_ALLOC, "usrdir[x]", sizeof(uint)*l);
-				return(false);
-			}
-	}
- 
-	if(cfg.max_batup) {
-
-		if((batup_desc=(char **)MALLOC(sizeof(char *)*cfg.max_batup))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batup_desc", sizeof(char *)*cfg.max_batup);
-			return(false);
-		}
-		if((batup_name=(char **)MALLOC(sizeof(char *)*cfg.max_batup))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batup_name", sizeof(char *)*cfg.max_batup);
-			return(false);
-		}
-		if((batup_misc=(long *)MALLOC(sizeof(long)*cfg.max_batup))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batup_misc", sizeof(char *)*cfg.max_batup);
-			return(false);
-		}
-		if((batup_dir=(uint *)MALLOC(sizeof(uint)*cfg.max_batup))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batup_dir", sizeof(char *)*cfg.max_batup);
-			return(false);
-		}
-		if((batup_alt=(ushort *)MALLOC(sizeof(ushort)*cfg.max_batup))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batup_alt", sizeof(char *)*cfg.max_batup);
-			return(false);
-		}
-		for(i=0;i<cfg.max_batup;i++) {
-			if((batup_desc[i]=(char *)MALLOC(59))==NULL) {
-				errormsg(WHERE, ERR_ALLOC, "batup_desc[x]", 59);
-				return(false);
-			}
-			if((batup_name[i]=(char *)MALLOC(13))==NULL) {
-				errormsg(WHERE, ERR_ALLOC, "batup_name[x]", 13);
-				return(false);
-			} 
-		} 
-	}
-
-	if(cfg.max_batdn) {
-
-		if((batdn_name=(char **)MALLOC(sizeof(char *)*cfg.max_batdn))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batdn_name", sizeof(char *)*cfg.max_batdn);
-			return(false);
-		}
-		if((batdn_dir=(uint *)MALLOC(sizeof(uint)*cfg.max_batdn))==NULL)  {
-			errormsg(WHERE, ERR_ALLOC, "batdn_dir", sizeof(uint)*cfg.max_batdn);
-			return(false);
-		}
-		if((batdn_offset=(long *)MALLOC(sizeof(long)*cfg.max_batdn))==NULL)  {
-			errormsg(WHERE, ERR_ALLOC, "batdn_offset", sizeof(long)*cfg.max_batdn);
-			return(false);
-		}
-		if((batdn_size=(ulong *)MALLOC(sizeof(ulong)*cfg.max_batdn))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batdn_size", sizeof(ulong)*cfg.max_batdn);
-			return(false);
-		}
-		if((batdn_cdt=(ulong *)MALLOC(sizeof(ulong)*cfg.max_batdn))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batdn_cdt", sizeof(long)*cfg.max_batdn);
-			return(false);
-		}
-		if((batdn_alt=(ushort *)MALLOC(sizeof(ushort)*cfg.max_batdn))==NULL) {
-			errormsg(WHERE, ERR_ALLOC, "batdn_alt", sizeof(ushort)*cfg.max_batdn);
-			return(false);
-		}
-		for(i=0;i<cfg.max_batdn;i++)
-			if((batdn_name[i]=(char *)MALLOC(13))==NULL) {
-				errormsg(WHERE, ERR_ALLOC, "batdn_name[x]", 13);
-				return(false);
-			} 
-	}
-
-	reset_logon_vars();
-
-	online=ON_REMOTE;
-
-	return(true);
-}
-
-//****************************************************************************
-sbbs_t::~sbbs_t()
-{
-	uint i;
-	char node[32];
-
-    if(cfg.node_num)
-    	sprintf(node,"Node %d", cfg.node_num);
-    else
-    	strcpy(node,client_name);
-#ifdef _DEBUG
-	lprintf("%s destructor begin", node);
-#endif
-
-//	if(!cfg.node_num)
-//		rmdir(cfg.temp_dir);
-
-	if(client_socket_dup!=INVALID_SOCKET)
-		closesocket(client_socket_dup);	/* close duplicate handle */
-
-	if(cfg.node_num>0)
-		node_inbuf[cfg.node_num-1]=NULL;
-	if(cfg.node_num>0 && !input_thread_running)
-		RingBufDispose(&inbuf);
-	if(!output_thread_running)
-		RingBufDispose(&outbuf);
-
-	/* Close all open files */
-	if(nodefile!=-1) {
-		close(nodefile);
-		nodefile=-1;
-	}
-	if(node_ext!=-1) {
-		close(node_ext);
-		node_ext=-1;
-	}
-	if(logfile_fp!=NULL) {
-		fclose(logfile_fp);
-		logfile_fp=NULL;
-	}
-
-	/********************************/
-	/* Free allocated class members */
-	/********************************/
-
-#ifdef JAVASCRIPT
-	/* Free Context */
-	if(js_cx!=NULL) {	
-		lprintf("%s JavaScript: Destroying context",node);
-		JS_DestroyContext(js_cx);
-		js_cx=NULL;
-	}
-
-	if(js_runtime!=NULL) {
-		lprintf("%s JavaScript: Destroying runtime",node);
-		JS_DestroyRuntime(js_runtime);
-		js_runtime=NULL;
-	}
-#endif
-
-	/* Reset text.dat */
-
-	for(i=0;i<TOTAL_TEXT && text!=NULL;i++)
-		if(text[i]!=text_sav[i]) {
-			if(text[i]!=nulstr)
-				FREE(text[i]); 
-		}
-
-	/* Global command shell vars */
-
-	freevars(&main_csi);
-	clearvars(&main_csi);
-	FREE_AND_NULL(main_csi.str);	/* crash */
-	FREE_AND_NULL(main_csi.cs);
-
-	for(i=0;i<global_str_vars && global_str_var!=NULL;i++)
-		FREE_AND_NULL(global_str_var[i]);
-
-	FREE_AND_NULL(global_str_var);
-	FREE_AND_NULL(global_str_var_name);
-	global_str_vars=0;
-
-	FREE_AND_NULL(global_int_var);
-	FREE_AND_NULL(global_int_var_name);
-	global_int_vars=0;
-
-	/* Sub-board variables */
-	for(i=0;i<usrgrp_total && usrsub!=NULL;i++)
-		FREE_AND_NULL(usrsub[i]);	/* exception here (ptr=0xfdfdfdfd) on exit July-10-2002 */
-
-	FREE_AND_NULL(cursub);
-	FREE_AND_NULL(usrgrp);
-	FREE_AND_NULL(usrsubs);
-	FREE_AND_NULL(usrsub);
-	FREE_AND_NULL(subscan);
-
-	/* File Directory variables */
-	for(i=0;i<usrlib_total && usrdir!=NULL;i++)
-		FREE_AND_NULL(usrdir[i]);
-
-	FREE_AND_NULL(curdir);
-	FREE_AND_NULL(usrlib);
-	FREE_AND_NULL(usrdirs);
-	FREE_AND_NULL(usrdir);
-
-	/* Batch upload vars */
-	for(i=0;i<cfg.max_batup && batup_desc!=NULL && batup_name!=NULL;i++) {
-		FREE_AND_NULL(batup_desc[i]);
-		FREE_AND_NULL(batup_name[i]);
-	}
-
-	FREE_AND_NULL(batup_desc);
-	FREE_AND_NULL(batup_name);
-	FREE_AND_NULL(batup_misc);
-	FREE_AND_NULL(batup_dir);
-	FREE_AND_NULL(batup_alt);
-
-	/* Batch download vars */
-	for(i=0;i<cfg.max_batdn && batdn_name!=NULL;i++)
-		FREE_AND_NULL(batdn_name[i]); 
-
-	FREE_AND_NULL(batdn_name);
-	FREE_AND_NULL(batdn_dir);
-	FREE_AND_NULL(batdn_offset);
-	FREE_AND_NULL(batdn_size);
-	FREE_AND_NULL(batdn_cdt);
-	FREE_AND_NULL(batdn_alt);
-
-#if 0 && defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
-	if(!_CrtCheckMemory())
-		lprintf("!MEMORY ERRORS REPORTED IN DATA/DEBUG.LOG!");
-#endif
-
-#ifdef _DEBUG
-	lprintf("%s destructor end", node);
-#endif
-}
-
-/****************************************************************************/
-/* Network open function. Opens all files DENYALL and retries LOOP_NOPEN    */
-/* number of times if the attempted file is already open or denying access  */
-/* for some other reason.													*/
-/* All files are opened in BINARY mode, unless O_TEXT access bit is set.	*/
-/****************************************************************************/
-int sbbs_t::nopen(char *str, int access)
-{
-	char logstr[256];
-	int file,share,count=0;
-
-    if(access&O_DENYNONE) {
-        share=SH_DENYNO;
-        access&=~O_DENYNONE; 
-	}
-    else if(access==O_RDONLY) share=SH_DENYWR;
-    else share=SH_DENYRW;
-	if(!(access&O_TEXT))
-		access|=O_BINARY;
-    while(((file=sopen(str,access,share,S_IREAD|S_IWRITE))==-1)
-        && (errno==EACCES || errno==EAGAIN) && count++<LOOP_NOPEN)
-	    mswait(100);
-    if(count>(LOOP_NOPEN/2) && count<=LOOP_NOPEN) {
-        sprintf(logstr,"NOPEN COLLISION - File: \"%s\" Count: %d"
-            ,str,count);
-        logline("!!",logstr); 
-	}
-    if(file==-1 && (errno==EACCES || errno==EAGAIN)) {
-        sprintf(logstr,"NOPEN ACCESS DENIED - File: \"%s\" errno: %d"
-			,str,errno);
-		logline("!!",logstr);
-		bputs("\7\r\nNOPEN: ACCESS DENIED\r\n\7");
-	}
-    return(file);
-}
-
-void sbbs_t::spymsg(char* msg)
-{
-	char str[512];
-	struct in_addr addr;
-
-	if(cfg.node_num<1)
-		return;
-
-	addr.s_addr=client_addr;
-	sprintf(str,"\r\n\r\n*** Spy Message ***\r\nNode %d: %s [%s]\r\n*** %s ***\r\n\r\n"
-		,cfg.node_num,client_name,inet_ntoa(addr),msg);
-	if(startup->node_spybuf!=NULL 
-		&& startup->node_spybuf[cfg.node_num-1]!=NULL) {
-		RingBufWrite(startup->node_spybuf[cfg.node_num-1],(uchar*)str,strlen(str));
-		/* Signal spy output semaphore? */
-		if(startup->node_spysem!=NULL 
-			&& startup->node_spysem[sbbs->cfg.node_num-1]!=NULL)
-			sem_post(startup->node_spysem[sbbs->cfg.node_num-1]);
-	}
-
-	if(cfg.node_num && spy_socket[cfg.node_num-1]!=INVALID_SOCKET) 
-		sendsocket(spy_socket[cfg.node_num-1],str,strlen(str));
-#ifdef __unix__
-	if(cfg.node_num && uspy_socket[cfg.node_num-1]!=INVALID_SOCKET) 
-		sendsocket(uspy_socket[cfg.node_num-1],str,strlen(str));
-#endif
-}
-
-#define MV_BUFLEN	4096
-
-/****************************************************************************/
-/* Moves or copies a file from one dir to another                           */
-/* both 'src' and 'dest' must contain full path and filename                */
-/* returns 0 if successful, -1 if error                                     */
-/****************************************************************************/
-int sbbs_t::mv(char *src, char *dest, char copy)
-{
-	char	str[MAX_PATH+1],*buf,atr=curatr;
-	int		ind,outd;
-	uint	chunk=MV_BUFLEN;
-	ulong	length,l;
-	/* struct ftime ftime; */
-	FILE *inp,*outp;
-
-    if(!stricmp(src,dest))	 /* source and destination are the same! */
-        return(0);
-    if(!fexistcase(src)) {
-        bprintf("\r\n\7MV ERROR: Source doesn't exist\r\n'%s'\r\n"
-            ,src);
-        return(-1); 
-	}
-    if(!copy && fexistcase(dest)) {
-        bprintf("\r\n\7MV ERROR: Destination already exists\r\n'%s'\r\n"
-            ,dest);
-        return(-1); 
-	}
-#ifndef __unix__	/* need to determine if on same mount device */
-    if(!copy && ((src[1]!=':' && dest[1]!=':')
-        || (src[1]==':' && dest[1]==':' && toupper(src[0])==toupper(dest[0])))) {
-        if(rename(src,dest)) {						/* same drive, so move */
-            bprintf("\r\nMV ERROR: Error renaming '%s'"
-                    "\r\n                      to '%s'\r\n\7",src,dest);
-            return(-1); 
-		}
-        return(0); 
-	}
-#endif
-    attr(WHITE);
-    if((ind=nopen(src,O_RDONLY))==-1) {
-        errormsg(WHERE,ERR_OPEN,src,O_RDONLY);
-        return(-1); 
-	}
-    if((inp=fdopen(ind,"rb"))==NULL) {
-        close(ind);
-        errormsg(WHERE,ERR_FDOPEN,str,O_RDONLY);
-        return(-1); 
-	}
-    setvbuf(inp,NULL,_IOFBF,32*1024);
-    if((outd=nopen(dest,O_WRONLY|O_CREAT|O_TRUNC))==-1) {
-        fclose(inp);
-        errormsg(WHERE,ERR_OPEN,dest,O_WRONLY|O_CREAT|O_TRUNC);
-        return(-1); 
-	}
-    if((outp=fdopen(outd,"wb"))==NULL) {
-        close(outd);
-        fclose(inp);
-        errormsg(WHERE,ERR_FDOPEN,dest,O_WRONLY|O_CREAT|O_TRUNC);
-        return(-1); 
-	}
-    setvbuf(outp,NULL,_IOFBF,8*1024);
-    length=filelength(ind);
-    if(!length) {
-        fclose(inp);
-        fclose(outp);
-        errormsg(WHERE,ERR_LEN,src,0);
-        return(-1); 
-	}
-    if((buf=(char *)MALLOC(MV_BUFLEN))==NULL) {
-        fclose(inp);
-        fclose(outp);
-        errormsg(WHERE,ERR_ALLOC,nulstr,MV_BUFLEN);
-        return(-1); 
-	}
-    l=0L;
-    while(l<length) {
-        bprintf("%2lu%%",l ? (long)(100.0/((float)length/l)) : 0L);
-        if(l+chunk>length)
-            chunk=length-l;
-        if(fread(buf,1,chunk,inp)!=chunk) {
-            FREE(buf);
-            fclose(inp);
-            fclose(outp);
-            errormsg(WHERE,ERR_READ,src,chunk);
-            return(-1); 
-		}
-        if(fwrite(buf,1,chunk,outp)!=chunk) {
-            FREE(buf);
-            fclose(inp);
-            fclose(outp);
-            errormsg(WHERE,ERR_WRITE,dest,chunk);
-            return(-1); 
-		}
-        l+=chunk;
-        bputs("\b\b\b"); 
-	}
-    bputs("   \b\b\b");  /* erase it */
-    attr(atr);
-    /* getftime(ind,&ftime);
-    setftime(outd,&ftime); */
-    FREE(buf);
-    fclose(inp);
-    fclose(outp);
-    if(!copy && remove(src)) {
-        errormsg(WHERE,ERR_REMOVE,src,0);
-        return(-1); 
-	}
-    return(0);
-}
-
-/****************************************************************************/
-/* Reads data from dsts.dab into stats structure                            */
-/* If node is zero, reads from ctrl\dsts.dab, otherwise from each node		*/
-/****************************************************************************/
-BOOL DLLCALL getstats(scfg_t* cfg, char node, stats_t* stats)
-{
-    char str[MAX_PATH+1];
-    int file;
-
-    sprintf(str,"%sdsts.dab",node ? cfg->node_path[node-1] : cfg->ctrl_dir);
-    if((file=nopen(str,O_RDONLY))==-1) {
-//        errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
-        return(FALSE); 
-	}
-    lseek(file,4L,SEEK_SET);    /* Skip update time/date */
-    read(file,stats,sizeof(stats_t));
-    close(file);
-	return(TRUE);
-}
-
-
-void sbbs_t::hangup(void)
-{
-	if(client_socket!=INVALID_SOCKET) {
-		mswait(1000);	/* Give socket output buffer time to flush */
-		riosync(0);
-		client_off(client_socket);
-		close_socket(client_socket);
-		client_socket=INVALID_SOCKET;
-	}
-	if(client_socket_dup!=INVALID_SOCKET) {
-		closesocket(client_socket_dup);
-		client_socket_dup=INVALID_SOCKET;
-	}
-	sem_post(&outbuf.sem);
-	online=0;
-}
-
-int sbbs_t::incom(unsigned long timeout)
-{
-	uchar	ch;
-
-#if 0	/* looping version */
-	while(!RingBufRead(&inbuf, &ch, 1))
-		if(sem_trywait_block(&inbuf.sem,timeout)!=0 || sys_status&SS_ABORT)
-			return(NOINP);
-#else
-	if(!RingBufRead(&inbuf, &ch, 1)) {
-		if(sem_trywait_block(&inbuf.sem,timeout)!=0)
-			return(NOINP);
-		if(!RingBufRead(&inbuf, &ch, 1))
-			return(NOINP);
-	}
-#endif
-	return(ch);
-}
-
-int sbbs_t::outcom(uchar ch)
-{
-	if(!RingBufFree(&outbuf))
-		return(TXBOF);
-    if(!RingBufWrite(&outbuf, &ch, 1))
-		return(TXBOF);
-	return(0);
-}
-
-void sbbs_t::putcom(char *str, int len)
-{
-	int i;
-
-    if(!len)
-    	len=strlen(str);
-    for(i=0;i<len && online; i++)
-        outcom(str[i]);
-}
-
-void sbbs_t::riosync(char abortable)
-{
-	if(useron.misc&(RIP|WIP|HTML))	/* don't allow abort with RIP or WIP */
-		abortable=0;			/* mainly because of ANSI cursor position response */
-	if(sys_status&SS_ABORT)		/* no need to sync if already aborting */
-		return;
-	time_t start=time(NULL);
-	while(online && rioctl(TXBC)) {				/* wait up to three minutes for tx buf empty */
-		if(sys_status&SS_ABORT)
-			break;
-#if 0	/* this isn't necessary (or desired) on a TCP/IP connection */
-		if(abortable && rioctl(RXBC)) { 		/* incoming characer */
-			rioctl(IOFO);						/* flush output */
-			sys_status|=SS_ABORT;				/* set abort flag so no pause */
-			break;								/* abort sync */
-		}
-#endif
-		if(time(NULL)-start>180) {				/* timeout */
-			logline("!!","riosync timeout"); 
-			break;
-		}
-		mswait(100);
-	}
-}
-
-/* Legacy Remote I/O Control Interface */
-int sbbs_t::rioctl(ushort action)
-{
-	int		mode;
-	int		state;
-
-	switch(action) {
-		case GVERS: 	/* Get version */
-			return(0x200);
-		case GUART: 	/* Get UART I/O address, not available */
-			return(0xffff);
-		case GIRQN: 	/* Get IRQ number, not available */
-			return((int)client_socket);
-		case GBAUD: 	/* Get current bit rate */
-			return(0xffff);
-		case RXBC:		/* Get receive buffer count */
-			// ulong	cnt;
-			// ioctlsocket (client_socket,FIONREAD,&cnt);
- 			return(/* cnt+ */RingBufFull(&inbuf));
-		case RXBS:		/* Get receive buffer size */
-			return(inbuf.size);
-		case TXBC:		/* Get transmit buffer count */
-			return(RingBufFull(&outbuf));
-		case TXBS:		/* Get transmit buffer size */
-			return(outbuf.size);
-		case TXBF:		/* Get transmit buffer free space */
- 			return(RingBufFree(&outbuf));
-		case IOMODE:
-			mode=0;
-			if(rio_abortable)
-				mode|=ABORT;
-			return(mode);
-		case IOSTATE:
-			state=0;
-			if(sys_status&SS_ABORT)
-				state|=ABORT;
-			return(state);
-		case IOFI:		/* Flush input buffer */
-			RingBufReInit(&inbuf);
-			break;
-		case IOFO:		/* Flush output buffer */
-    		RingBufReInit(&outbuf);
-			break;
-		case IOFB:		/* Flush both buffers */
-			RingBufReInit(&inbuf);
-			RingBufReInit(&outbuf);
-			break;
-		case LFN81:
-		case LFE71:
-		case FIFOCTL:
-			return(0);
-		}
-
-	if((action&0xff)==IOSM) {	/* Get/Set/Clear mode */
-		if(action&ABORT)
-			rio_abortable=true;
-		return(0); 
-	}
-
-	if((action&0xff)==IOCM) {	/* Get/Set/Clear mode */
-		if(action&ABORT)
-			rio_abortable=false;
-		return(0); 
-	}
-
-	if((action&0xff)==IOSS) {	/* Set state */
-		if(action&ABORT)
-			sys_status|=SS_ABORT;
-		return(0); 
-	}
-
-	if((action&0xff)==IOCS) {	/* Clear state */
-		if(action&ABORT)
-			sys_status&=~SS_ABORT;
-		return(0); 
-	}
-
-	return(0);
-}
-
-void sbbs_t::reset_logon_vars(void)
-{
-	int i;
-
-    /* bools */
-    qwklogon=false;
-
-    sys_status&=~(SS_USERON|SS_TMPSYSOP|SS_LCHAT|SS_ABORT
-        |SS_PAUSEON|SS_PAUSEOFF|SS_EVENT|SS_NEWUSER|SS_NEWDAY);
-    cid[0]=0;
-    wordwrap[0]=0;
-    question[0]=0;
-    menu_dir[0]=0;
-    menu_file[0]=0;
-    rows=0;
-    lncntr=0;
-    autoterm=0;
-    keybufbot=keybuftop=lbuflen=0;
-    slcnt=0;
-    altul=0;
-    timeleft_warn=0;
-    logon_uls=logon_ulb=logon_dls=logon_dlb=0;
-    logon_posts=logon_emails=logon_fbacks=0;
-    batdn_total=batup_total=0;
-    usrgrps=usrlibs=0;
-    curgrp=curlib=0;
-    for(i=0;i<cfg.total_libs;i++)
-        curdir[i]=0;
-    for(i=0;i<cfg.total_grps;i++)
-        cursub[i]=0;
-	cur_cps=3000;
-    cur_rate=30000;
-    dte_rate=38400;
-	main_cmds=xfer_cmds=posts_read=0;
-	lastnodemsg=0;
-	lastnodemsguser[0]=0;
-}
-
-/****************************************************************************/
-/* Writes NODE.LOG at end of SYSTEM.LOG										*/
-/****************************************************************************/
-void sbbs_t::catsyslog(int crash)
-{
-	char str[MAX_PATH+1];
-	char HUGE16 *buf;
-	int  i,file;
-	long length;
-	struct tm tm;
-
-	if(logfile_fp==NULL) {
-		sprintf(str,"%snode.log",cfg.node_dir);
-		if((logfile_fp=fopen(str,"rb"))==NULL) {
-			errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
-			return; 
-		}
-	}
-	length=ftell(logfile_fp);
-	if(length) {
-		if((buf=(char HUGE16 *)LMALLOC(length))==NULL) {
-			errormsg(WHERE,ERR_ALLOC,str,length);
-			return; 
-		}
-		rewind(logfile_fp);
-		if(fread(buf,1,length,logfile_fp)!=(size_t)length) {
-			errormsg(WHERE,ERR_READ,"log file",length);
-			FREE((char *)buf);
-			return; 
-		}
-		now=time(NULL);
-		localtime_r(&now,&tm);
-		sprintf(str,"%slogs/%2.2d%2.2d%2.2d.log",cfg.logs_dir,tm.tm_mon+1,tm.tm_mday
-			,TM_YEAR(tm.tm_year));
-		if((file=nopen(str,O_WRONLY|O_APPEND|O_CREAT))==-1) {
-			errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT);
-			FREE((char *)buf);
-			return; 
-		}
-		if(lwrite(file,buf,length)!=length) {
-			close(file);
-			errormsg(WHERE,ERR_WRITE,str,length);
-			FREE((char *)buf);
-			return; 
-		}
-		close(file);
-		if(crash) {
-			for(i=0;i<2;i++) {
-				sprintf(str,"%scrash.log",i ? cfg.logs_dir : cfg.node_dir);
-				if((file=nopen(str,O_WRONLY|O_APPEND|O_CREAT))==-1) {
-					errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT);
-					FREE((char *)buf);
-					return; 
-				}
-				if(lwrite(file,buf,length)!=length) {
-					close(file);
-					errormsg(WHERE,ERR_WRITE,str,length);
-					FREE((char *)buf);
-					return; 
-				}
-				close(file); 
-			} 
-		}
-		FREE((char *)buf); 
-	}
-
-	fclose(logfile_fp);
-
-	sprintf(str,"%snode.log",cfg.node_dir);
-	if((logfile_fp=fopen(str,"w+b"))==NULL) /* Truncate NODE.LOG */
-		errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_TRUNC);
-}
-
-
-void sbbs_t::logoffstats()
-{
-    char str[MAX_PATH+1];
-    int i,file;
-    stats_t stats;
-
-	if(REALSYSOP && !(cfg.sys_misc&SM_SYSSTAT))
-		return;
-	
-	for(i=0;i<2;i++) {
-		sprintf(str,"%sdsts.dab",i ? cfg.ctrl_dir : cfg.node_dir);
-		if((file=nopen(str,O_RDWR))==-1) {
-			errormsg(WHERE,ERR_OPEN,str,O_RDWR);
-			return; 
-		}
-		memset(&stats,0,sizeof(stats));
-		lseek(file,4L,SEEK_SET);   /* Skip timestamp, logons and logons today */
-		read(file,&stats,sizeof(stats));  
-
-		if(!(useron.rest&FLAG('Q'))) {	/* Don't count QWKnet nodes */
-			stats.timeon+=(now-logontime)/60;
-			stats.ttoday+=(now-logontime)/60;
-			stats.ptoday+=logon_posts;
-		}
-		stats.uls+=logon_uls;
-		stats.ulb+=logon_ulb;
-		stats.dls+=logon_dls;
-		stats.dlb+=logon_dlb;
-		stats.etoday+=logon_emails;
-		stats.ftoday+=logon_fbacks;
-
-#if 0 // This is now handled in newuserdat()
-		if(sys_status&SS_NEWUSER)
-			stats.nusers++;
-#endif
-
-		lseek(file,4L,SEEK_SET);
-		write(file,&stats,sizeof(stats));
-		close(file); 
-	}
-}
-
-void node_thread(void* arg)
-{
-	char			str[128];
-	char			uname[LEN_ALIAS+1];
-	int				file;
-	uint			i,j;
-	uint			curshell=0;
-	time_t			now;
-	node_t			node;
-	user_t			user;
-	sbbs_t*			sbbs = (sbbs_t*) arg;
-
-	update_clients();
-	thread_up(TRUE /* setuid */);
-
-#ifdef _DEBUG
-	lprintf("Node %d thread started",sbbs->cfg.node_num);
-#endif
-
-	srand(time(NULL));	/* Seed random number generator */
-	sbbs_random(10);	/* Throw away first number */
-
-#ifdef JAVASCRIPT
-	if(!(startup->options&BBS_OPT_NO_JAVASCRIPT)) {
-		if(!sbbs->js_init())	/* This must be done in the context of the node thread */
-			lprintf("Node %d !JavaScript Initialization FAILURE",sbbs->cfg.node_num);
-	}
-#endif
-
-	if(sbbs->answer()) {
-
-		if(sbbs->qwklogon) {
-			sbbs->getsmsg(sbbs->useron.number);
-			sbbs->qwk_sec();
-		} else while(sbbs->useron.number 
-			&& (sbbs->main_csi.misc&CS_OFFLINE_EXEC || sbbs->online)) {
-
-			if(!sbbs->main_csi.cs || curshell!=sbbs->useron.shell) {
-				if(sbbs->useron.shell>=sbbs->cfg.total_shells)
-					sbbs->useron.shell=0;
-				sprintf(str,"%s%s.bin",sbbs->cfg.mods_dir
-					,sbbs->cfg.shell[sbbs->useron.shell]->code);
-				if(sbbs->cfg.mods_dir[0]==0 || !fexistcase(str))
-					sprintf(str,"%s%s.bin",sbbs->cfg.exec_dir
-						,sbbs->cfg.shell[sbbs->useron.shell]->code);
-				if((file=sbbs->nopen(str,O_RDONLY))==-1) {
-					sbbs->errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
-					sbbs->hangup();
-					break; 
-				}
-				FREE_AND_NULL(sbbs->main_csi.cs);
-				sbbs->freevars(&sbbs->main_csi);
-				sbbs->clearvars(&sbbs->main_csi);
-
-				sbbs->main_csi.length=filelength(file);
-				if((sbbs->main_csi.cs=(uchar *)MALLOC(sbbs->main_csi.length))==NULL) {
-					close(file);
-					sbbs->errormsg(WHERE,ERR_ALLOC,str,sbbs->main_csi.length);
-					sbbs->hangup();
-					break; 
-				}
-
-				if(lread(file,sbbs->main_csi.cs,sbbs->main_csi.length)
-					!=(int)sbbs->main_csi.length) {
-					sbbs->errormsg(WHERE,ERR_READ,str,sbbs->main_csi.length);
-					close(file);
-					FREE(sbbs->main_csi.cs);
-					sbbs->main_csi.cs=NULL;
-					sbbs->hangup();
-					break; 
-				}
-				close(file);
-
-				curshell=sbbs->useron.shell;
-				sbbs->main_csi.ip=sbbs->main_csi.cs;
-				sbbs->menu_dir[0]=0;
-				sbbs->menu_file[0]=0;
-				}
-			if(sbbs->exec(&sbbs->main_csi))
-				break;
-		}
-	}
-
-#ifdef _WIN32
-	if(startup->hangup_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
-		PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME);
-#endif
-
-	sbbs->hangup();	/* closes sockets, calls client_off, and shuts down the output_thread */
-    node_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
-
-	sbbs->logout();
-	sbbs->logoffstats();	/* Updates both system and node dsts.dab files */
-
-	if(sbbs->sys_status&SS_DAILY) {	// New day, run daily events/maintenance
-
-		now=time(NULL);
-
-		sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
-		node.status=NODE_EVENT_RUNNING;
-		sbbs->putnodedat(sbbs->cfg.node_num,&node);
-
-		sbbs->logentry("!:","Ran system daily maintenance");
-
-		if(sbbs->cfg.user_backup_level) {
-			lprintf("Node %d Backing-up user data..."
-				,sbbs->cfg.node_num);
-			sprintf(str,"%suser/user.dat",sbbs->cfg.data_dir);
-			backup(str,sbbs->cfg.user_backup_level,FALSE);
-			sprintf(str,"%suser/name.dat",sbbs->cfg.data_dir);
-			backup(str,sbbs->cfg.user_backup_level,FALSE);
-		}
-
-		if(sbbs->cfg.mail_backup_level) {
-			lprintf("Node %d Backing-up mail data..."
-				,sbbs->cfg.node_num);
-			sprintf(str,"%smail.shd",sbbs->cfg.data_dir);
-			backup(str,sbbs->cfg.mail_backup_level,FALSE);
-			sprintf(str,"%smail.sha",sbbs->cfg.data_dir);
-			backup(str,sbbs->cfg.mail_backup_level,FALSE);
-			sprintf(str,"%smail.sdt",sbbs->cfg.data_dir);
-			backup(str,sbbs->cfg.mail_backup_level,FALSE);
-			sprintf(str,"%smail.sda",sbbs->cfg.data_dir);
-			backup(str,sbbs->cfg.mail_backup_level,FALSE);
-			sprintf(str,"%smail.sid",sbbs->cfg.data_dir);
-			backup(str,sbbs->cfg.mail_backup_level,FALSE);
-			sprintf(str,"%smail.sch",sbbs->cfg.data_dir);
-			backup(str,sbbs->cfg.mail_backup_level,FALSE);
-		}
-
-		lprintf("Node %d Checking for inactive/expired user records..."
-			,sbbs->cfg.node_num);
-		j=lastuser(&sbbs->cfg);
-		for(i=1;i<=j;i++) {
-
-			sprintf(str,"%5u of %-5u",i,j);
-			status(str);
-			user.number=i;
-			getuserdat(&sbbs->cfg,&user);
-
-			/***********************************************/
-			/* Fix name (name.dat and user.dat) mismatches */
-			/***********************************************/
-			username(&sbbs->cfg,i,uname);
-			if(user.misc&DELETED) {
-				if(strcmp(uname,"DELETED USER"))
-					putusername(&sbbs->cfg,i,nulstr);
-				continue; 
-			}
-
-			if(strcmp(user.alias,uname))
-				putusername(&sbbs->cfg,i,user.alias);
-
-			if(!(user.misc&(DELETED|INACTIVE))
-				&& user.expire && (ulong)user.expire<=(ulong)now) {
-				putsmsg(&sbbs->cfg,i,sbbs->text[AccountHasExpired]);
-				sprintf(str,"%s #%u Expired",user.alias,user.number);
-				sbbs->logentry("!%",str);
-				if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOVAL
-					&& sbbs->cfg.level_expireto[user.level]<10) {
-					user.flags1=sbbs->cfg.val_flags1[sbbs->cfg.level_expireto[user.level]];
-					user.flags2=sbbs->cfg.val_flags2[sbbs->cfg.level_expireto[user.level]];
-					user.flags3=sbbs->cfg.val_flags3[sbbs->cfg.level_expireto[user.level]];
-					user.flags4=sbbs->cfg.val_flags4[sbbs->cfg.level_expireto[user.level]];
-					user.exempt=sbbs->cfg.val_exempt[sbbs->cfg.level_expireto[user.level]];
-					user.rest=sbbs->cfg.val_rest[sbbs->cfg.level_expireto[user.level]];
-					if(sbbs->cfg.val_expire[sbbs->cfg.level_expireto[user.level]])
-						user.expire=now
-							+(sbbs->cfg.val_expire[sbbs->cfg.level_expireto[user.level]]*24*60*60);
-					else
-						user.expire=0;
-					user.level=sbbs->cfg.val_level[sbbs->cfg.level_expireto[user.level]]; 
-				}
-				else {
-					if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOLVL)
-						user.level=sbbs->cfg.level_expireto[user.level];
-					else
-						user.level=sbbs->cfg.expired_level;
-					user.flags1&=~sbbs->cfg.expired_flags1; /* expired status */
-					user.flags2&=~sbbs->cfg.expired_flags2; /* expired status */
-					user.flags3&=~sbbs->cfg.expired_flags3; /* expired status */
-					user.flags4&=~sbbs->cfg.expired_flags4; /* expired status */
-					user.exempt&=~sbbs->cfg.expired_exempt;
-					user.rest|=sbbs->cfg.expired_rest;
-					user.expire=0; 
-				}
-				putuserrec(&sbbs->cfg,i,U_LEVEL,2,ultoa(user.level,str,10));
-				putuserrec(&sbbs->cfg,i,U_FLAGS1,8,ultoa(user.flags1,str,16));
-				putuserrec(&sbbs->cfg,i,U_FLAGS2,8,ultoa(user.flags2,str,16));
-				putuserrec(&sbbs->cfg,i,U_FLAGS3,8,ultoa(user.flags3,str,16));
-				putuserrec(&sbbs->cfg,i,U_FLAGS4,8,ultoa(user.flags4,str,16));
-				putuserrec(&sbbs->cfg,i,U_EXPIRE,8,ultoa(user.expire,str,16));
-				putuserrec(&sbbs->cfg,i,U_EXEMPT,8,ultoa(user.exempt,str,16));
-				putuserrec(&sbbs->cfg,i,U_REST,8,ultoa(user.rest,str,16));
-				if(sbbs->cfg.expire_mod[0]) {
-					sbbs->useron=user;
-					sbbs->online=ON_LOCAL;
-					sbbs->exec_bin(sbbs->cfg.expire_mod,&sbbs->main_csi);
-					sbbs->online=0; 
-				}
-			}
-
-			/***********************************************************/
-			/* Auto deletion based on expiration date or days inactive */
-			/***********************************************************/
-			if(!(user.exempt&FLAG('P'))     /* Not a permanent account */
-				&& !(user.misc&(DELETED|INACTIVE))	 /* alive */
-				&& (sbbs->cfg.sys_autodel && (now-user.laston)/(long)(24L*60L*60L)
-				> sbbs->cfg.sys_autodel)) {			/* Inactive too long */
-				sprintf(str,"Auto-Deleted %s #%u",user.alias,user.number);
-				sbbs->logentry("!*",str);
-				sbbs->delallmail(i);
-				putusername(&sbbs->cfg,i,nulstr);
-				putuserrec(&sbbs->cfg,i,U_MISC,8,ultoa(user.misc|DELETED,str,16)); 
-			}
-		}
-
-		lprintf("Node %d Purging deleted/expired e-mail",sbbs->cfg.node_num);
-		sprintf(sbbs->smb.file,"%smail",sbbs->cfg.data_dir);
-		sbbs->smb.retry_time=sbbs->cfg.smb_retry_time;
-		sbbs->smb.subnum=INVALID_SUB;
-		if((i=smb_open(&sbbs->smb))!=0)
-			sbbs->errormsg(WHERE,ERR_OPEN,sbbs->smb.file,i,sbbs->smb.last_error);
-		else {
-			if(filelength(fileno(sbbs->smb.shd_fp))>0) {
-				if((i=smb_locksmbhdr(&sbbs->smb))!=0)
-					sbbs->errormsg(WHERE,ERR_LOCK,sbbs->smb.file,i,sbbs->smb.last_error);
-				else
-					sbbs->delmail(0,MAIL_ALL);
-			}
-			smb_close(&sbbs->smb); 
-		}
-
-		sbbs->sys_status&=~SS_DAILY;
-		if(sbbs->cfg.sys_daily[0]) {
-//			status("Running system daily event");
-			sbbs->logentry("!:","Ran system daily event");
-			sbbs->external(sbbs->cmdstr(sbbs->cfg.sys_daily,nulstr,nulstr,NULL)
-				,EX_OFFLINE); 
-		}
-	}
-
-#if 0	/* this is handled in the event_thread now */
-	// Node Daily Event
-	sbbs->getnodedat(sbbs->cfg.node_num,&node,0);
-	if(node.misc&NODE_EVENT) {
-		sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
-		node.status=NODE_EVENT_RUNNING;
-		sbbs->putnodedat(sbbs->cfg.node_num,&node);
-		if(sbbs->cfg.node_daily[0]) {
-			sbbs->logentry("!:","Run node daily event");
-			sbbs->external(
-				 sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL)
-				,EX_OFFLINE);
-		}
-		sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
-		node.misc&=~NODE_EVENT;
-		sbbs->putnodedat(sbbs->cfg.node_num,&node); 
-	}
-#endif
-
-    // Wait for all node threads to terminate
-	if(sbbs->input_thread_running || sbbs->output_thread_running) {
-		lprintf("Node %d Waiting for %s to terminate..."
-			,sbbs->cfg.node_num
-			,(sbbs->input_thread_running && sbbs->output_thread_running) ?
-               	"I/O threads" : sbbs->input_thread_running
-				? "input thread" : "output thread");
-		time_t start=time(NULL);
-		while(sbbs->input_thread_running
-    		|| sbbs->output_thread_running) {
-			if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
-				lprintf("Node %d !TIMEOUT waiting for %s to terminate"
-					, sbbs->cfg.node_num
-					,(sbbs->input_thread_running && sbbs->output_thread_running) ?
-                  		"I/O threads"
-					: sbbs->input_thread_running
-                		? "input thread" : "output thread");
-				break;
-			}
-			mswait(100);
-		}
-	}
-
-	sbbs->catsyslog(0);
-
-	status(STATUS_WFC);
-
-	sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
-	if(node.misc&NODE_DOWN)
-		node.status=NODE_OFFLINE;
-	else
-		node.status=NODE_WFC;
-	node.misc&=~(NODE_DOWN|NODE_INTR|NODE_MSGW|NODE_NMSG
-				|NODE_UDAT|NODE_POFF|NODE_AOFF|NODE_EXT);
-/*	node.useron=0; needed for hang-ups while in multinode chat */
-	sbbs->putnodedat(sbbs->cfg.node_num,&node);
-
-	if(node_threads_running>0)
-		node_threads_running--;
-	lprintf("Node %d thread terminated (%u node threads remain, %lu clients served)"
-		,sbbs->cfg.node_num, node_threads_running, served);
-    if(!sbbs->input_thread_running && !sbbs->output_thread_running)
-		delete sbbs;
-    else
-		lprintf("Node %d !ORPHANED I/O THREAD(s)",sbbs->cfg.node_num);
-
-	update_clients();
-	thread_down();
-}
-
-time_t checktime(void)
-{
-	struct tm tm;
-
-    memset(&tm,0,sizeof(tm));
-    tm.tm_year=94;
-    tm.tm_mday=1;
-    return(mktime(&tm)-0x2D24BD00L);
-}
-
-const char* DLLCALL js_ver(void)
-{
-#ifdef JAVASCRIPT
-	return(JS_GetImplementationVersion());
-#else
-	return("");
-#endif
-}
-
-/* Returns char string of version and revision */
-const char* DLLCALL bbs_ver(void)
-{
-	static char ver[256];
-	char compiler[32];
-
-	DESCRIBE_COMPILER(compiler);
-
-	sprintf(ver,"%s %s%c%s  SMBLIB %s  Compiled %s %s with %s"
-		,TELNET_SERVER
-		,VERSION, REVISION
-#ifdef _DEBUG
-		," Debug"
-#else
-		,""
-#endif
-		,smb_lib_ver()
-		,__DATE__, __TIME__, compiler
-		);
-
-	return(ver);
-}
-
-/* Returns binary-coded version and revision (e.g. 0x31000 == 3.10a) */
-long DLLCALL bbs_ver_num(void)
-{
-	char*	minor;
-
-	if((minor=(char *)strchr(VERSION,'.'))==NULL)
-		return(0);
-	minor++;
-
-	return((strtoul(VERSION,NULL,16)<<16)|(strtoul(minor,NULL,16)<<8)|(REVISION-'A'));
-}
-
-void DLLCALL bbs_terminate(void)
-{
-	recycle_server=false;
-	if(telnet_socket!=INVALID_SOCKET) {
-    	lprintf("BBS Terminate: closing telnet socket %d",telnet_socket);
-		close_socket(telnet_socket);
-		telnet_socket=INVALID_SOCKET;
-    }
-}
-
-static void cleanup(int code)
-{
-    lputs("BBS System thread terminating");
-
-	if(telnet_socket!=INVALID_SOCKET) {
-		close_socket(telnet_socket);
-		telnet_socket=INVALID_SOCKET;
-	}
-	if(rlogin_socket!=INVALID_SOCKET) {
-		close_socket(rlogin_socket);
-		rlogin_socket=INVALID_SOCKET;
-	}
-
-
-#ifdef _WINSOCKAPI_
-	if(WSAInitialized && WSACleanup()!=0) 
-		lprintf("!WSACleanup ERROR %d",ERROR_VALUE);
-#endif
-
-	free_cfg(&scfg);
-	free_text(text);
-
-#ifdef _WIN32
-	if(exec_mutex!=NULL) {
-		CloseHandle(exec_mutex);
-		exec_mutex=NULL;
-	}
-
-	if(hK32!=NULL) {
-		FreeLibrary(hK32);
-		hK32=NULL;
-	}
-
-#if 0 && defined(_DEBUG) && defined(_MSC_VER)
-	_CrtMemDumpAllObjectsSince(&mem_chkpoint);
-
-	if(debug_log!=INVALID_HANDLE_VALUE) {
-		CloseHandle(debug_log);
-		debug_log=INVALID_HANDLE_VALUE;
-	}
-#endif // _DEBUG && _MSC_VER
-#endif // _WIN32
-
-	pthread_mutex_destroy(&event_mutex);
-
-	status("Down");
-	thread_down();
-    lprintf("BBS System thread terminated (%u threads remain, %lu clients served)"
-		,thread_count, served);
-	if(startup->terminated!=NULL)
-		startup->terminated(code);
-}
-
-void DLLCALL bbs_thread(void* arg)
-{
-	char *			host_name;
-	char *			identity;
-    char			str[MAX_PATH+1];
-	char			logstr[256];
-	SOCKADDR_IN		server_addr={0};
-	SOCKADDR_IN		client_addr;
-	socklen_t		client_addr_len;
-	SOCKET			client_socket;
-	fd_set			socket_set;
-	SOCKET			high_socket_set;
-	int				i;
-    int				file;
-	int				result;
-	BOOL			option;
-	time_t			t;
-	time_t			start;
-	time_t			initialized=0;
-	node_t			node;
-	sbbs_t*			events=NULL;
-	client_t		client;
-	startup=(bbs_startup_t*)arg;
-	BOOL			is_client=FALSE;
-#ifdef __unix__
-	SOCKET	uspy_listen_socket[MAX_NODES];
-	struct sockaddr_un uspy_addr;
-	socklen_t		addr_len;
-#endif
-
-    if(startup==NULL) {
-    	sbbs_beep(100,500);
-    	fprintf(stderr, "No startup structure passed!\n");
-    	return;
-    }
-
-	if(startup->size!=sizeof(bbs_startup_t)) {	// verify size
-		sbbs_beep(100,500);
-		sbbs_beep(300,500);
-		sbbs_beep(100,500);
-		fprintf(stderr, "Invalid startup structure!\n");
-		return;
-	}
-
-	/* Setup intelligent defaults */
-	if(startup->telnet_port==0)				startup->telnet_port=IPPORT_TELNET;
-	if(startup->rlogin_port==0)				startup->rlogin_port=513;
-	if(startup->xtrn_polls_before_yield==0)	startup->xtrn_polls_before_yield=10;
-#ifdef JAVASCRIPT
-	if(startup->js_max_bytes==0)			startup->js_max_bytes=JAVASCRIPT_MAX_BYTES;
-#endif
-	if(startup->outbuf_drain_timeout==0)	startup->outbuf_drain_timeout=10;
-	if(startup->temp_dir[0])				backslash(startup->temp_dir);
-
-	uptime=0;
-	served=0;
-	startup->recycle_now=FALSE;
-	recycle_server=true;
-	do {
-
-	thread_up(FALSE /* setuid */);
-
-	status("Initializing");
-
-	/* Defeat the lameo hex0rs - the name and copyright must remain intact */
-	if(crc32(COPYRIGHT_NOTICE,0)!=COPYRIGHT_CRC 
-		|| crc32(VERSION_NOTICE,10)!=SYNCHRONET_CRC) {
-		lprintf("!CORRUPTED LIBRARY FILE");
-		cleanup(1);
-		return;
-	}
-
-	memset(text, 0, sizeof(text));
-    memset(&scfg, 0, sizeof(scfg));
-
-	node_threads_running=0;
-	lastuseron[0]=0;
-
-	char compiler[32];
-	DESCRIBE_COMPILER(compiler);
-
-	lprintf("%s Version %s Revision %c%s"
-		,TELNET_SERVER
-		,VERSION
-		,toupper(REVISION)
-#ifdef _DEBUG
-		," Debug"
-#else
-		,""
-#endif
-		);
-	lprintf("Compiled %s %s with %s", __DATE__, __TIME__, compiler);
-	lprintf("SMBLIB %s (format %x.%02x)",smb_lib_ver(),smb_ver()>>8,smb_ver()&0xff);
-
-    if(startup->first_node<1 || startup->first_node>startup->last_node) {
-    	lprintf("!ILLEGAL node configuration (first: %d, last: %d)"
-        	,startup->first_node, startup->last_node);
-		cleanup(1);
-        return;
-    }
-
-	if(sizeof(node_t)!=SIZEOF_NODE_T) {
-		lprintf("!COMPILER ERROR: sizeof(node_t)=%d instead of %d"
-			,sizeof(node_t),SIZEOF_NODE_T);
-		cleanup(1);
-		return;
-	}
-
-#ifdef _WIN32
-    if((exec_mutex=CreateMutex(NULL,false,NULL))==NULL) {
-    	lprintf("!ERROR %d creating exec_mutex", GetLastError());
-		cleanup(1);
-        return;
-    }
-	hK32 = LoadLibrary("KERNEL32");
-#endif // _WIN32
-
-	pthread_mutex_init(&event_mutex,NULL);
-
-	if(!winsock_startup()) {
-		cleanup(1);
-		return;
-	}
-
-	t=time(NULL);
-	lprintf("Initializing on %.24s with options: %lx"
-		,CTIME_R(&t,str),startup->options);
-
-	if(chdir(startup->ctrl_dir)!=0)
-		lprintf("!ERROR %d changing directory to: %s", errno, startup->ctrl_dir);
-
-	/* Initial configuration and load from CNF files */
-    SAFECOPY(scfg.ctrl_dir,startup->ctrl_dir);
-    lprintf("Loading configuration files from %s", scfg.ctrl_dir);
-	scfg.size=sizeof(scfg);
-	scfg.node_num=startup->first_node;
-	SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
-	if(!load_cfg(&scfg, text, TRUE, logstr)) {
-		lprintf("!ERROR %s",logstr);
-		lprintf("!FAILED to load configuration files");
-		cleanup(1);
-		return;
-	}
-	scfg_reloaded=true;
-
-	if(startup->host_name[0]==0)
-		SAFECOPY(startup->host_name,scfg.sys_inetaddr);
-
-	if(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&BBS_OPT_LOCAL_TIMEZONE)) {
-		if(putenv("TZ=UTC0"))
-			lprintf("!putenv() FAILED");
-		tzset();
-
-		if((t=checktime())!=0) {   /* Check binary time */
-			lprintf("!TIME PROBLEM (%ld)",t);
-			cleanup(1);
-			return;
-		}
-	}
-
-	if(uptime==0)
-		uptime=time(NULL);	/* this must be done *after* setting the timezone */
-
-    if(startup->last_node>scfg.sys_nodes) {
-    	lprintf("Specified last_node (%d) > sys_nodes (%d), auto-corrected"
-        	,startup->last_node, scfg.sys_nodes);
-        startup->last_node=scfg.sys_nodes;
-    }
-
-	/* Create missing directories */
-	lprintf("Verifying/creating data directories");
-	make_data_dirs(&scfg);
-
-	/* Create missing node directories and dsts.dab files */
-	lprintf("Verifying/creating node directories");
-	for(i=0;i<=scfg.sys_nodes;i++) {
-		if(i)
-			md(scfg.node_path[i-1]);
-		sprintf(str,"%sdsts.dab",i ? scfg.node_path[i-1] : scfg.ctrl_dir);
-		if(flength(str)<DSTSDABLEN) {
-			if((file=sopen(str,O_WRONLY|O_CREAT|O_APPEND, SH_DENYNO, S_IREAD|S_IWRITE))==-1) {
-				lprintf("!ERROR %d creating %s",errno, str);
-				cleanup(1);
-				return; 
-			}
-			while(filelength(file)<DSTSDABLEN)
-				if(write(file,"\0",1)!=1)
-					break;				/* Create NULL system dsts.dab */
-			close(file); 
-		} 
-	}
-
-	/* Initial global node variables */
-	for(i=0;i<MAX_NODES;i++) {
-		node_inbuf[i]=NULL;
-    	node_socket[i]=INVALID_SOCKET;
-		spy_socket[i]=INVALID_SOCKET;
-#ifdef __unix__
-		uspy_socket[i]=INVALID_SOCKET;
-		uspy_listen_socket[i]=INVALID_SOCKET;
-#endif
-	}
-
-	startup->node_inbuf=node_inbuf;
-
-    /* open a socket and wait for a client */
-
-    telnet_socket = open_socket(SOCK_STREAM);
-
-	if(telnet_socket == INVALID_SOCKET) {
-		lprintf("!ERROR %d creating Telnet socket", ERROR_VALUE);
-		cleanup(1);
-		return;
-	}
-
-    lprintf("Telnet socket %d opened",telnet_socket);
-
-	if(startup->options&BBS_OPT_KEEP_ALIVE) {
-		lprintf("Enabling WinSock Keep Alives");
-		option = TRUE;
-
-		result = setsockopt(telnet_socket, SOL_SOCKET, SO_KEEPALIVE
-    		,(char *)&option, sizeof(option));
-
-		if(result != 0) {
-			lprintf("!ERROR %d (%d) setting Telnet socket option", result, ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
-
-	}
-
-	/*****************************/
-	/* Listen for incoming calls */
-	/*****************************/
-    memset(&server_addr, 0, sizeof(server_addr));
-
-	server_addr.sin_addr.s_addr = htonl(startup->telnet_interface);
-    server_addr.sin_family = AF_INET;
-    server_addr.sin_port   = htons(startup->telnet_port);
-
-	if(startup->seteuid!=NULL)
-		startup->seteuid(FALSE);
-    result = bind(telnet_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
-	if(startup->seteuid!=NULL)
-		startup->seteuid(TRUE);
-	if(result != 0) {
-		lprintf("!ERROR %d (%d) binding Telnet socket to port %d"
-			,result, ERROR_VALUE,startup->telnet_port);
-		lprintf(BIND_FAILURE_HELP);
-		cleanup(1);
-		return;
-	}
-
-    result = listen(telnet_socket, 1);
-
-	if(result != 0) {
-		lprintf("!ERROR %d (%d) listening on Telnet socket", result, ERROR_VALUE);
-		cleanup(1);
-		return;
-	}
-	lprintf("Telnet server listening on port %d",startup->telnet_port);
-
-	if(startup->options&BBS_OPT_ALLOW_RLOGIN) {
-
-		/* open a socket and wait for a client */
-
-		rlogin_socket = open_socket(SOCK_STREAM);
-
-		if(rlogin_socket == INVALID_SOCKET) {
-			lprintf("!ERROR %d creating RLogin socket", ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
-
-		lprintf("RLogin socket %d opened",rlogin_socket);
-
-		/*****************************/
-		/* Listen for incoming calls */
-		/*****************************/
-		memset(&server_addr, 0, sizeof(server_addr));
-
-		server_addr.sin_addr.s_addr = htonl(startup->rlogin_interface);
-		server_addr.sin_family = AF_INET;
-		server_addr.sin_port   = htons(startup->rlogin_port);
-
-		if(startup->seteuid!=NULL)
-			startup->seteuid(FALSE);
-		result = bind(rlogin_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
-		if(startup->seteuid!=NULL)
-			startup->seteuid(TRUE);
-		if(result != 0) {
-			lprintf("!ERROR %d (%d) binding RLogin socket to port %d"
-				,result, ERROR_VALUE,startup->rlogin_port);
-			lprintf(BIND_FAILURE_HELP);
-			cleanup(1);
-			return;
-		}
-
-		result = listen(rlogin_socket, 1);
-
-		if(result != 0) {
-			lprintf("!ERROR %d (%d) listening on RLogin socket", result, ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
-		lprintf("RLogin server listening on port %d",startup->rlogin_port);
-	}
-
-	/* signal caller that we've started up successfully */
-    if(startup->started!=NULL)
-    	startup->started();
-
-	sbbs = new sbbs_t(0, server_addr.sin_addr.s_addr
-		,"BBS System", telnet_socket, &scfg, text, NULL);
-    sbbs->online = 0;
-	if(sbbs->init()==false) {
-		lputs("!BBS initialization failed");
-		cleanup(1);
-		return;
-	}
-	_beginthread(output_thread, 0, sbbs);
-
-	if(!(startup->options&BBS_OPT_NO_EVENTS)) {
-		events = new sbbs_t(0, server_addr.sin_addr.s_addr
-			,"BBS Events", INVALID_SOCKET, &scfg, text, NULL);
-		events->online = 0;
-		if(events->init()==false) {
-			lputs("!Events initialization failed");
-			cleanup(1);
-			return;
-		}
-		_beginthread(event_thread, 0, events);
-	}
-
-	/* Save these values incase they're changed dynamically */
-	first_node=startup->first_node;
-	last_node=startup->last_node;
-
-	for(i=first_node;i<=last_node;i++) {
-		sbbs->getnodedat(i,&node,1);
-		node.status=NODE_WFC;
-		node.misc&=NODE_EVENT;
-		node.action=0;
-		sbbs->putnodedat(i,&node);
-	}
-
-	lprintf("BBS System thread started for nodes %d through %d", first_node, last_node);
-	status(STATUS_WFC);
-
-#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
-	
-	sprintf(str,"%sDEBUG.LOG",scfg.logs_dir);
-	if((debug_log=CreateFile(
-		str,				// pointer to name of the file
-		GENERIC_READ|GENERIC_WRITE,
-		FILE_SHARE_READ|FILE_SHARE_WRITE,
-		NULL,               // pointer to security attributes
-		OPEN_ALWAYS,		// how to create
-		FILE_ATTRIBUTE_NORMAL, // file attributes
-		NULL				// handle to file with attributes to 
-		))==INVALID_HANDLE_VALUE) {
-		lprintf("!ERROR %ld creating %s",GetLastError(),str);
-		cleanup(1);
-		return;
-	}
-
-	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
-	_CrtSetReportFile(_CRT_WARN, debug_log);
-	_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE|_CRTDBG_MODE_WNDW);
-	_CrtSetReportFile(_CRT_ERROR, debug_log);
-	_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE|_CRTDBG_MODE_WNDW);
-	_CrtSetReportFile(_CRT_ASSERT, debug_log);
-
-	/* Turns on memory leak checking during program termination */
-//	_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
-
-	/* Save this allocation point for comparison */
-	_CrtMemCheckpoint(&mem_chkpoint);
-
-#endif // _WIN32 && _DEBUG && _MSC_VER
-
-	if(!initialized) {
-		initialized=time(NULL);
-		sprintf(str,"%stelnet.rec",scfg.ctrl_dir);
-		t=fdate(str);
-		if(t!=-1 && t>initialized)
-			initialized=t;
-	}
-
-#ifdef __unix__	//	unix-domain spy sockets
-	for(i=first_node;i<=last_node;i++)  {
-	    if((uspy_listen_socket[i-1]=socket(PF_LOCAL,SOCK_STREAM,0))==INVALID_SOCKET)
-	        lprintf("Node %d ERROR %d creating local spy socket"
-	            , i, errno);
-	    else {
-	        lprintf("Node %d local spy using socket %d", i, uspy_listen_socket[i-1]);
-	        if(startup!=NULL && startup->socket_open!=NULL)
-	            startup->socket_open(TRUE);
-	    }
-	
-	    uspy_addr.sun_family=AF_LOCAL;
-	    if((unsigned int)snprintf(str,sizeof(uspy_addr.sun_path),
-	            "%slocalspy%d.sock", startup->temp_dir, i)
-	            >=sizeof(uspy_addr.sun_path))
-	        uspy_listen_socket[i-1]=INVALID_SOCKET;
-	    else  {
-	        strcpy(uspy_addr.sun_path,str);
-	        if(fexist(str))
-	            unlink(str);
-	    }
-	    if(uspy_listen_socket[i-1]!=INVALID_SOCKET) {
-	        addr_len=SUN_LEN(&uspy_addr);
-	        if(bind(uspy_listen_socket[i-1], (struct sockaddr *) &uspy_addr, addr_len)) {
-	            lprintf("Node %d !ERROR %d binding local spy socket %d to %s"
-	                , i, errno, uspy_listen_socket[i-1], uspy_addr.sun_path);
-	            close_socket(uspy_listen_socket[i-1]);
-	            continue;
-	        }
-            lprintf("Node %d local spy socket %d bound to %s"
-                , i, uspy_listen_socket[i-1], uspy_addr.sun_path);
-	        if(listen(uspy_listen_socket[i-1],1))  {
-	            lprintf("Node %d !ERROR %d listening local spy socket %d", i, errno);
-	            close_socket(uspy_listen_socket[i-1]);
-	            continue;
-			}
-	        addr_len=sizeof(uspy_addr);
-	    }
-	}
-#endif // __unix__ (unix-domain spy sockets)
-
-	while(telnet_socket!=INVALID_SOCKET) {
-
-		if(node_threads_running==0 && !event_mutex_locked) {	/* check for re-run flags */
-			bool rerun=false;
-			for(i=first_node;i<=last_node;i++) {
-				if(sbbs->getnodedat(i,&node,0)!=0)
-					continue;
-				if(node.misc&NODE_RRUN) {
-					sbbs->getnodedat(i,&node,1);
-					if(!rerun)
-						lprintf("Node %d flagged for re-run",i);
-					rerun=true;
-					node.misc&=~NODE_RRUN;
-					sbbs->putnodedat(i,&node);
-				}
-			}
-			if(rerun) {
-				lprintf("Loading configuration files from %s", scfg.ctrl_dir);
-				scfg.node_num=first_node;
-				pthread_mutex_lock(&event_mutex);
-				SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
-				if(!load_cfg(&scfg, text, TRUE, logstr)) {
-					lprintf("!ERROR %s",logstr);
-					lprintf("!FAILED to load configuration files");
-					break;
-				}
-				scfg_reloaded=true;
-				pthread_mutex_unlock(&event_mutex);
-			}
-			if(!(startup->options&BBS_OPT_NO_RECYCLE)) {
-				sprintf(str,"%stelnet.rec",scfg.ctrl_dir);
-				t=fdate(str);
-				if(t!=-1 && t>initialized) {
-					lprintf("0000 Recycle semaphore file (%s) detected",str);
-					initialized=t;
-					break;
-				}
-				if(startup->recycle_now==TRUE) {
-					lprintf("0000 Recycle semaphore signaled");
-					startup->recycle_now=FALSE;
-					break;
-				}
-			}
-		}
-
-
-    	sbbs->online=0;
-
-		/* now wait for connection */
-
-		FD_ZERO(&socket_set);
-		FD_SET(telnet_socket,&socket_set);
-		high_socket_set=telnet_socket+1;
-		if(startup->options&BBS_OPT_ALLOW_RLOGIN 
-			&& rlogin_socket!=INVALID_SOCKET) {
-			FD_SET(rlogin_socket,&socket_set);
-			if(rlogin_socket+1>high_socket_set)
-				high_socket_set=rlogin_socket+1;
-		}
-#ifdef __unix__
-		for(i=first_node;i<=last_node;i++)  {
-			if(uspy_listen_socket[i-1]!=INVALID_SOCKET)  {
-				FD_SET(uspy_listen_socket[i-1],&socket_set);
-				if(uspy_listen_socket[i-1]+1>high_socket_set)
-					high_socket_set=uspy_listen_socket[i-1]+1;
-			}
-		}
-#endif
-
-		struct timeval tv;
-		tv.tv_sec=2;
-		tv.tv_usec=0;
-
-		if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
-			if(i==0)
-				continue;
-			if(ERROR_VALUE==EINTR)
-				lprintf("Telnet Server listening interrupted");
-			else if(ERROR_VALUE == ENOTSOCK)
-            	lprintf("Telnet Server sockets closed");
-			else
-				lprintf("!ERROR %d selecting sockets",ERROR_VALUE);
-			break;
-		}
-
-		if(telnet_socket==INVALID_SOCKET)	/* terminated */
-			break;
-
-		client_addr_len = sizeof(client_addr);
-
-		bool rlogin = false;
-
-		is_client=FALSE;
-		if(telnet_socket!=INVALID_SOCKET 
-			&& FD_ISSET(telnet_socket,&socket_set)) {
-			client_socket = accept_socket(telnet_socket, (struct sockaddr *)&client_addr
-	        	,&client_addr_len);
-	        is_client=TRUE;
-		} else if(rlogin_socket!=INVALID_SOCKET 
-			&& FD_ISSET(rlogin_socket,&socket_set)) {
-			client_socket = accept_socket(rlogin_socket, (struct sockaddr *)&client_addr
-	        	,&client_addr_len);
-			rlogin = true;
-			is_client=TRUE;
-		} else {
-#ifdef __unix__
-			for(i=first_node;i<=last_node;i++)  {
-				if(uspy_listen_socket[i-1]!=INVALID_SOCKET
-				&& FD_ISSET(uspy_listen_socket[i-1],&socket_set)) {
-					BOOL already_connected=(uspy_socket[i-1]!=INVALID_SOCKET);
-					SOCKET new_socket=INVALID_SOCKET;
-					new_socket = accept(uspy_listen_socket[i-1], (struct sockaddr *)&client_addr
-						,&client_addr_len);
-					if(already_connected)  {
-						send(new_socket,"Spy socket already in use.\n",27,0);
-						close_socket(new_socket);
-					}
-					else  {
-						uspy_socket[i-1]=new_socket;
-						sprintf(str,"Spy connection established to node %d\n",i);
-						send(uspy_socket[i-1],str,strlen(str),0);
-					}
-				}
-			}
-#else
-			lprintf("!NO SOCKETS set by select");
-#endif
-		}
-
-		if(!is_client)
-			continue;
-
-		if(client_socket == INVALID_SOCKET)	{
-			if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR || ERROR_VALUE == EINVAL) {
-            	lputs("BBS socket closed");
-				break;
-			}
-			lprintf("!ERROR %d accepting connection", ERROR_VALUE);
-			break;	// was continue, July-01-2002
-		}
-		char host_ip[32];
-
-		strcpy(host_ip,inet_ntoa(client_addr.sin_addr));
-
-		if(trashcan(&scfg,host_ip,"ip-silent")) {
-			close_socket(client_socket);
-			continue;
-		}
-
-		lprintf("%04d %s connection accepted from: %s port %u"
-			,client_socket
-			,rlogin ? "RLogin" : "Telnet", host_ip, ntohs(client_addr.sin_port));
-
-#ifdef _WIN32
-		if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
-			PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
-#endif
-
-   		sbbs->client_socket=client_socket;	// required for output to the user
-        sbbs->online=ON_REMOTE;
-
-		if(sbbs->trashcan(host_ip,"ip")) {
-			close_socket(client_socket);
-			lprintf("%04d !CLIENT BLOCKED in ip.can"
-				,client_socket);
-			sprintf(logstr, "Blocked IP: %s",host_ip);
-			sbbs->syslog("@!",logstr);
-			continue;
-		}
-
-		if(rlogin) {
-			if(!trashcan(&scfg,host_ip,"rlogin")) {
-				close_socket(client_socket);
-				lprintf("%04d !CLIENT IP NOT LISTED in rlogin.can",client_socket);
-				sprintf(logstr, "Invalid RLogin from: %s",host_ip);
-				sbbs->syslog("@!",logstr);
-				continue;
-			}
-			sbbs->outcom(0); /* acknowledge RLogin per RFC 1282 */
-		}
-
-		sbbs->putcom(crlf);
-		sbbs->putcom(VERSION_NOTICE);
-		sbbs->putcom(crlf);
-
-		sbbs->bprintf("Connection from: %s\r\n", host_ip);
-
-		struct hostent* h;
-		if(startup->options&BBS_OPT_NO_HOST_LOOKUP)
-			h=NULL;
-		else {
-			sbbs->bprintf("Resolving hostname...");
-			h=gethostbyaddr((char *)&client_addr.sin_addr
-				,sizeof(client_addr.sin_addr),AF_INET);
-			sbbs->putcom(crlf);
-		}
-		if(h!=NULL && h->h_name!=NULL)
-			host_name=h->h_name;
-		else
-			host_name="<no name>";
-
-		if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) {
-			lprintf("%04d Hostname: %s", client_socket, host_name);
-			for(i=0;h!=NULL && h->h_aliases!=NULL && h->h_aliases[i]!=NULL;i++)
-				lprintf("%04d HostAlias: %s", client_socket, h->h_aliases[i]);
-		}
-
-		if(sbbs->trashcan(host_name,"host")) {
-			close_socket(client_socket);
-			lprintf("%04d !CLIENT BLOCKED in host.can",client_socket);
-			sprintf(logstr, "Blocked Hostname: %s",host_name);
-			sbbs->syslog("@!",logstr);
-			continue;
-		}
-
-		identity=NULL;
-		if(startup->options&BBS_OPT_GET_IDENT) {
-			sbbs->bprintf("Resolving identity...");
-			identify(&client_addr, startup->telnet_port, str, sizeof(str)-1,0);
-			identity=strrchr(str,':');
-			if(identity!=NULL) {
-				identity++;	/* skip colon */
-				while(*identity && *identity<=SP) /* point to user name */
-					identity++;
-				lprintf("%04d Identity: %s",client_socket, identity);
-			}
-		}
-		/* Initialize client display */
-		client.size=sizeof(client);
-		client.time=time(NULL);
-		SAFECOPY(client.addr,host_ip);
-		SAFECOPY(client.host,host_name);
-		client.port=ntohs(client_addr.sin_port);
-		client.protocol=rlogin ? "RLogin":"Telnet";
-		client.user="<unknown>";
-		client_on(client_socket,&client,FALSE /* update */);
-
-		for(i=first_node;i<=last_node;i++) {
-			/* paranoia: make sure node.status!=NODE_WFC by default */
-			node.status=NODE_INVALID_STATUS;	
-			if(sbbs->getnodedat(i,&node,1)!=0)
-				continue;
-			if(node.status==NODE_WFC) {
-				node.status=NODE_LOGON;
-				sbbs->putnodedat(i,&node);
-				break;
-			}
-			sbbs->putnodedat(i,&node);
-		}
-
-		if(i>last_node) {
-			lprintf("%04d !No nodes available for login.",client_socket);
-			sprintf(str,"%snonodes.txt",scfg.text_dir);
-			if(fexist(str))
-				sbbs->printfile(str,P_NOABORT);
-			else {
-				sbbs->putcom("\r\nSorry, all telnet nodes are in use or otherwise unavailable.\r\n");
-				sbbs->putcom("Please try again later.\r\n");
-			}
-			mswait(3000);
-			client_off(client_socket);
-			close_socket(client_socket);
-			continue;
-		}
-
-        node_socket[i-1]=client_socket;
-
-		sbbs_t* new_node = new sbbs_t(i, client_addr.sin_addr.s_addr, host_name
-        	,client_socket, &scfg, text, &client);
-
-		new_node->client=client;
-
-		/* copy the IDENT response, if any */
-		if(identity!=NULL)
-			SAFECOPY(new_node->client_ident,identity);
-
-		if(new_node->init()==false) {
-			lprintf("%04d !Node %d Initialization failure"
-				,client_socket,new_node->cfg.node_num);
-			sprintf(str,"%snonodes.txt",scfg.text_dir);
-			if(fexist(str))
-				sbbs->printfile(str,P_NOABORT);
-			else 
-				sbbs->putcom("\r\nSorry, initialization failed. Try again later.\r\n");
-			mswait(3000);
-			sbbs->getnodedat(new_node->cfg.node_num,&node,1);
-			node.status=NODE_WFC;
-			sbbs->putnodedat(new_node->cfg.node_num,&node);
-			delete new_node;
-			node_socket[i-1]=INVALID_SOCKET;
-			client_off(client_socket);
-			close_socket(client_socket);
-			continue;
-		}
-
-		if(rlogin==true) {
-			new_node->connection="RLogin";
-			new_node->sys_status|=SS_RLOGIN;
-		}
-
-	    node_threads_running++;
-		new_node->input_thread=(HANDLE)_beginthread(input_thread,0, new_node);
-		_beginthread(output_thread, 0, new_node);
-		_beginthread(node_thread, 0, new_node);
-		served++;
-	}
-
-    // Close all open sockets
-    for(i=0;i<MAX_NODES;i++)
-    	if(node_socket[i]!=INVALID_SOCKET) {
-        	lprintf("Closing node %d socket %d", i+1, node_socket[i]);
-        	close_socket(node_socket[i]);
-			node_socket[i]=INVALID_SOCKET;
-        }
-
-	sbbs->client_socket=INVALID_SOCKET;
-	if(events!=NULL)
-		events->terminated=true;
-    // Wake-up BBS output thread so it can terminate
-    sem_post(&sbbs->outbuf.sem);
-
-    // Wait for all node threads to terminate
-	if(node_threads_running) {
-		lprintf("Waiting for %d node threads to terminate...", node_threads_running);
-		start=time(NULL);
-		while(node_threads_running) {
-			if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
-				lprintf("!TIMEOUT waiting for %d node thread(s) to "
-            		"terminate", node_threads_running);
-				break;
-			}
-			mswait(100);
-		}
-	}
-
-	// Wait for Events thread to terminate
-	if(events!=NULL && events->event_thread_running) {
-		pthread_mutex_unlock(&event_mutex);
-		lprintf("Waiting for event thread to terminate...");
-		start=time(NULL);
-		while(events->event_thread_running) {
-			if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
-				lprintf("!TIMEOUT waiting for BBS event thread to "
-            		"terminate");
-				break;
-			}
-			mswait(100);
-		}
-	}
-
-    // Wait for BBS output thread to terminate
-	if(sbbs->output_thread_running) {
-		lprintf("Waiting for system output thread to terminate...");
-		start=time(NULL);
-		while(sbbs->output_thread_running) {
-			if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
-				lprintf("!TIMEOUT waiting for BBS output thread to "
-            		"terminate");
-				break;
-			}
-			mswait(100);
-		}
-	}
-
-    // Set all nodes' status to OFFLINE
-    for(i=first_node;i<=last_node;i++) {
-        sbbs->getnodedat(i,&node,1);
-        node.status=NODE_OFFLINE;
-        sbbs->putnodedat(i,&node);
-    }
-
-	if(events!=NULL && !events->event_thread_running)
-		delete events;
-
-    if(!sbbs->output_thread_running)
-	    delete sbbs;
-
-	cleanup(0);
-
-	if(recycle_server) {
-		lprintf("Recycling server...");
-		mswait(2000);
-	}
-
-	} while(recycle_server);
-
-}
-
-
-
+/* main.cpp */
+
+/* Synchronet main/telnet server thread and related functions */
+
+/* $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.	*
+ ****************************************************************************/
+
+#include "sbbs.h"
+#include "ident.h"
+#include "telnet.h" 
+
+#ifdef __unix__
+	#include <sys/un.h>
+#endif
+
+//---------------------------------------------------------------------------
+
+/* Temporary */
+int	mswtyp=0;
+uint riobp;
+
+#define TELNET_SERVER "Synchronet Telnet Server"
+#define STATUS_WFC	"Listening"
+
+#define TIMEOUT_THREAD_WAIT		60			// Seconds (was 15)
+#define IO_THREAD_BUF_SIZE	   	20000		// Bytes
+
+// Globals
+#ifdef _WIN32
+	HANDLE		exec_mutex=NULL;
+	HINSTANCE	hK32=NULL;
+
+	#if defined(_DEBUG) && defined(_MSC_VER)
+			HANDLE	debug_log=INVALID_HANDLE_VALUE;
+		   _CrtMemState mem_chkpoint;
+	#endif // _DEBUG && _MSC_VER
+
+#endif // _WIN32
+
+time_t	uptime=0;
+DWORD	served=0;
+
+static	uint node_threads_running=0;
+static	uint thread_count=0;
+		
+char 	lastuseron[LEN_ALIAS+1];  /* Name of user last online */
+RingBuf* node_inbuf[MAX_NODES];
+SOCKET	spy_socket[MAX_NODES];
+#ifdef __unix__
+SOCKET	uspy_socket[MAX_NODES];	  /* UNIX domain spy sockets */
+#endif
+SOCKET	node_socket[MAX_NODES];
+static	SOCKET telnet_socket=INVALID_SOCKET;
+static	SOCKET rlogin_socket=INVALID_SOCKET;
+static	pthread_mutex_t event_mutex;
+static  bool	event_mutex_locked;
+static	sbbs_t*	sbbs=NULL;
+static	scfg_t	scfg;
+static	bool	scfg_reloaded=true;
+static	char *	text[TOTAL_TEXT];
+static	WORD	first_node;
+static	WORD	last_node;
+static	bool	recycle_server=false;
+
+extern "C" {
+
+static bbs_startup_t* startup=NULL;
+
+static void status(char* str)
+{
+	if(startup!=NULL && startup->status!=NULL)
+	    startup->status(str);
+}
+
+static void update_clients()
+{
+	if(startup!=NULL && startup->clients!=NULL)
+		startup->clients(node_threads_running);
+}
+
+void client_on(SOCKET sock, client_t* client, BOOL update)
+{
+	if(startup!=NULL && startup->client_on!=NULL)
+		startup->client_on(TRUE,sock,client,update);
+}
+
+static void client_off(SOCKET sock)
+{
+	if(startup!=NULL && startup->client_on!=NULL)
+		startup->client_on(FALSE,sock,NULL,FALSE);
+}
+
+static void thread_up(BOOL setuid)
+{
+	thread_count++;
+	if(startup!=NULL && startup->thread_up!=NULL)
+		startup->thread_up(TRUE,setuid);
+}
+
+static void thread_down()
+{
+	if(thread_count>0)
+		thread_count--;
+	if(startup!=NULL && startup->thread_up!=NULL)
+		startup->thread_up(FALSE,FALSE);
+}
+
+int lputs(char* str)
+{
+	if(startup==NULL || startup->lputs==NULL)
+    	return(0);
+
+#if defined(_WIN32) && defined(_DEBUG)
+	if(IsBadCodePtr((FARPROC)startup->lputs)) {
+		DebugBreak();
+		return(0);
+	}
+#endif
+
+    return(startup->lputs(str));
+}
+
+int lprintf(char *fmt, ...)
+{
+	va_list argptr;
+	char sbuf[1024];
+
+    if(startup==NULL || startup->lputs==NULL)
+        return(0);
+
+    va_start(argptr,fmt);
+    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
+	sbuf[sizeof(sbuf)-1]=0;
+    va_end(argptr);
+    return(startup->lputs(sbuf));
+}
+
+int eprintf(char *fmt, ...)
+{
+	va_list argptr;
+	char sbuf[1024];
+
+    if(startup==NULL || startup->event_log==NULL)
+        return(0);
+
+    va_start(argptr,fmt);
+    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
+	sbuf[sizeof(sbuf)-1]=0;
+    va_end(argptr);
+	strip_ctrl(sbuf);
+    return(startup->event_log(sbuf));
+}
+
+SOCKET open_socket(int type)
+{
+	SOCKET	sock;
+	char	error[256];
+
+	sock=socket(AF_INET, type, IPPROTO_IP);
+	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
+		startup->socket_open(TRUE);
+	if(sock!=INVALID_SOCKET && set_socket_options(&scfg, sock, error))
+		lprintf("%04d !ERROR %s",sock,error);
+
+	return(sock);
+}
+
+SOCKET accept_socket(SOCKET s, SOCKADDR* addr, socklen_t* addrlen)
+{
+	SOCKET	sock;
+
+	sock=accept(s,addr,addrlen);
+	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
+		startup->socket_open(TRUE);
+
+	return(sock);
+}
+
+int close_socket(SOCKET sock)
+{
+	int		result;
+
+	if(sock==INVALID_SOCKET || sock==0)
+		return(0);
+
+	shutdown(sock,SHUT_RDWR);	/* required on Unix */
+	result=closesocket(sock);
+	if(result==0 && startup!=NULL && startup->socket_open!=NULL) 
+		startup->socket_open(FALSE);
+	if(result!=0 && ERROR_VALUE!=ENOTSOCK)
+		lprintf("!ERROR %d closing socket %d",ERROR_VALUE,sock);
+	return(result);
+}
+
+
+u_long resolve_ip(char *addr)
+{
+	HOSTENT*	host;
+	char*		p;
+
+	if(*addr==0)
+		return(INADDR_NONE);
+
+	for(p=addr;*p;p++)
+		if(*p!='.' && !isdigit(*p))
+			break;
+	if(!(*p))
+		return(inet_addr(addr));
+	if((host=gethostbyname(addr))==NULL) 
+		return(INADDR_NONE);
+	return(*((ulong*)host->h_addr_list[0]));
+}
+
+} /* extern "C" */
+
+#ifdef JAVASCRIPT
+
+JSBool	
+DLLCALL js_CreateArrayOfStrings(JSContext* cx, JSObject* parent, const char* name, char* str[],uintN flags)
+{
+	JSObject*	array;
+	JSString*	js_str;
+	jsval		val;
+	size_t		i;
+	jsuint		len=0;
+		
+	if(JS_GetProperty(cx,parent,name,&val) && val!=JSVAL_VOID)
+		array=JSVAL_TO_OBJECT(val);
+	else
+		if((array=JS_NewArrayObject(cx, 0, NULL))==NULL)
+			return(JS_FALSE);
+
+	if(!JS_GetArrayLength(cx, array, &len))
+		return(JS_FALSE);
+
+	for(i=0;str[i]!=NULL;i++) {
+		if((js_str = JS_NewStringCopyZ(cx, str[i]))==NULL)
+			break;
+		val = STRING_TO_JSVAL(js_str);
+		if(!JS_SetElement(cx, array, len+i, &val))
+			break;
+	}
+
+	return(JS_DefineProperty(cx, parent, name, OBJECT_TO_JSVAL(array)
+		,NULL,NULL,flags));
+}
+
+/* Convert from Synchronet-specific jsMethodSpec to JSAPI's JSFunctionSpec */
+
+JSBool
+DLLCALL js_DescribeObject(JSContext* cx, JSObject* obj, const char* str)
+{
+	JSString* js_str = JS_NewStringCopyZ(cx, str);
+
+	if(js_str==NULL)
+		return(JS_FALSE);
+
+	return(JS_DefineProperty(cx,obj,"_description"
+		,STRING_TO_JSVAL(js_str),NULL,NULL,JSPROP_READONLY));
+}
+
+JSBool
+DLLCALL js_DescribeConstructor(JSContext* cx, JSObject* obj, const char* str)
+{
+	JSString* js_str = JS_NewStringCopyZ(cx, str);
+
+	if(js_str==NULL)
+		return(JS_FALSE);
+
+	return(JS_DefineProperty(cx,obj,"_constructor"
+		,STRING_TO_JSVAL(js_str),NULL,NULL,JSPROP_READONLY));
+}
+
+#ifdef _DEBUG
+
+static char* server_prop_desc[] = {
+
+	 "server name and version number"
+	,"detailed version/build information"
+	,NULL
+};
+
+
+static const char* method_array_name = "_method_list";
+
+/*
+ * from jsatom.c:
+ * Keep this in sync with jspubtd.h -- an assertion below will insist that
+ * its length match the JSType enum's JSTYPE_LIMIT limit value.
+ */
+static const char *js_type_str[] = {
+    "void",			// changed from "undefined"
+    "object",
+    "function",
+    "string",
+    "number",
+    "boolean",
+	"array",		// JSTYPE_LIMIT
+};
+
+JSBool 
+DLLCALL js_DefineMethods(JSContext* cx, JSObject* obj, jsMethodSpec *funcs, BOOL append)
+{
+	int			i;
+	jsuint		len=0;
+	jsval		val;
+	JSObject*	method;
+	JSObject*	method_array;
+	JSString*	js_str;
+
+	/* Return existing method_list array if it's already been created */
+	if(JS_GetProperty(cx,obj,method_array_name,&val) && val!=JSVAL_VOID)
+		method_array=JSVAL_TO_OBJECT(val);
+	else
+		if((method_array=JS_NewArrayObject(cx, 0, NULL))==NULL) 
+			return(JS_FALSE);
+
+	if(append)
+		if(!JS_GetArrayLength(cx, method_array, &len))
+			return(JS_FALSE);
+
+	for(i=0;funcs[i].name;i++) {
+
+		JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0);
+
+		if(funcs[i].type==JSTYPE_ALIAS)
+			continue;
+
+		method = JS_NewObject(cx, NULL, NULL, method_array);
+
+		if(method==NULL)
+			return(JS_FALSE);
+
+		if(funcs[i].name!=NULL) {
+			if((js_str=JS_NewStringCopyZ(cx,funcs[i].name))==NULL)
+				return(JS_FALSE);
+			val = STRING_TO_JSVAL(js_str);
+			JS_SetProperty(cx, method, "name", &val);
+		}
+
+		val = INT_TO_JSVAL(funcs[i].nargs);
+		if(!JS_SetProperty(cx, method, "nargs", &val))
+			return(JS_FALSE);
+
+		if((js_str=JS_NewStringCopyZ(cx,js_type_str[funcs[i].type]))==NULL)
+			return(JS_FALSE);
+		val = STRING_TO_JSVAL(js_str);
+		JS_SetProperty(cx, method, "type", &val);
+
+		if(funcs[i].args!=NULL) {
+			if((js_str=JS_NewStringCopyZ(cx,funcs[i].args))==NULL)
+				return(JS_FALSE);
+			val = STRING_TO_JSVAL(js_str);
+			JS_SetProperty(cx, method, "args", &val);
+		}
+
+		if(funcs[i].desc!=NULL) { 
+			if((js_str=JS_NewStringCopyZ(cx,funcs[i].desc))==NULL)
+				return(JS_FALSE);
+			val = STRING_TO_JSVAL(js_str);
+			JS_SetProperty(cx, method, "desc", &val);
+		}
+
+		val=OBJECT_TO_JSVAL(method);
+		if(!JS_SetElement(cx, method_array, len+i, &val))
+			return(JS_FALSE);
+	}
+
+	if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
+		, NULL, NULL, 0))
+		return(JS_FALSE);
+
+	return(JS_TRUE);
+}
+
+#else // NON-DEBUG
+
+JSBool 
+DLLCALL js_DefineMethods(JSContext* cx, JSObject* obj, jsMethodSpec *funcs, BOOL append)
+{
+	int			i;
+
+	for(i=0;funcs[i].name;i++)
+		JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0);
+	return(JS_TRUE);
+}
+
+#endif
+
+/* 
+ * @method: log
+ * @syntax: log([value][,value][...])
+ * @arg:	value Variable or constant of any type
+ * @desc:	Print one or more values (typically Strings) in the local log window/display.
+ */
+static JSBool
+js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    uintN		i;
+    JSString *	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+    for (i = 0; i < argc; i++) {
+		if((str=JS_ValueToString(cx, argv[i]))==NULL)
+		    return(JS_FALSE);
+		if(sbbs->online==ON_LOCAL) {
+			if(startup!=NULL && startup->event_log!=NULL)
+				startup->event_log(JS_GetStringBytes(str));
+		} else
+			lputs(JS_GetStringBytes(str));
+		}
+
+	*rval = JSVAL_VOID;
+    return(JS_TRUE);
+}
+
+/*
+ * @method: print
+ * @usage: print([value][,value][...])
+ */
+
+static JSBool
+js_print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    uintN		i;
+    JSString *	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+    for (i = 0; i < argc; i++) {
+		if((str=JS_ValueToString(cx, argv[i]))==NULL)
+		    return(JS_FALSE);
+		if(sbbs->online==ON_LOCAL)
+			eprintf("%s",JS_GetStringBytes(str));
+		else
+			sbbs->bputs(JS_GetStringBytes(str));
+		}
+	if(sbbs->online==ON_REMOTE)
+		sbbs->bputs(crlf);
+
+	*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;
+	sbbs_t*		sbbs;
+	va_list		arglist[64];
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	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);
+
+	if(sbbs->online==ON_LOCAL)
+		eprintf("%s",p);
+	else
+		sbbs->bputs(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;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	if((str=JS_ValueToString(cx, argv[0]))==NULL)
+	    return(JS_FALSE);
+
+	sbbs->attr(sbbs->cfg.color[clr_err]);
+	sbbs->bputs(JS_GetStringBytes(str));
+	sbbs->attr(LIGHTGRAY);
+	sbbs->bputs(crlf);
+
+	*rval = JSVAL_VOID;
+    return(JS_TRUE);
+}
+
+static JSBool
+js_confirm(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);
+
+	if((str=JS_ValueToString(cx, argv[0]))==NULL)
+	    return(JS_FALSE);
+
+	*rval = BOOLEAN_TO_JSVAL(sbbs->yesno(JS_GetStringBytes(str)));
+	return(JS_TRUE);
+}
+
+static JSBool
+js_prompt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char		instr[81];
+    JSString *	prompt;
+    JSString *	str;
+	sbbs_t*		sbbs;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return(JS_FALSE);
+
+	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;
+
+	sbbs->bprintf("\1n\1y\1h%s\1w: ",JS_GetStringBytes(prompt));
+
+	if(!sbbs->getstr(instr,sizeof(instr)-1,K_EDIT)) {
+		*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,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
+	,JSDOCSTR("add a line of text to the server and/or system log, "
+		"<i>values</i> are typically string constants or variables")
+	},
+    {"print",           js_print,           0,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
+	,JSDOCSTR("print a line of text to the console or event log with automatic line termination (CRLF), "
+		"<i>values</i> are typically string constants or variables")
+	},
+    {"printf",          js_printf,          1,	JSTYPE_VOID,	JSDOCSTR("string format [,value][,value]")
+	,JSDOCSTR("print a formatted string - <small>CAUTION: for experienced C programmers ONLY</small>")
+	},	
+	{"alert",			js_alert,			1,	JSTYPE_VOID,	JSDOCSTR("value")
+	,JSDOCSTR("print an alert message (ala client-side JS)")
+	},
+	{"prompt",			js_prompt,			1,	JSTYPE_STRING,	JSDOCSTR("[value]")
+	,JSDOCSTR("displays a prompt (<i>value</i>) and returns a string of user input (ala clent-side JS)")
+	},
+	{"confirm",			js_confirm,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("value")
+	,JSDOCSTR("displays a Yes/No prompt and returns <i>true</i> or <i>false</i> "
+		"based on users confirmation (ala client-side JS)")
+	},
+    {0}
+};
+
+static void
+js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
+{
+	char	line[64];
+	char	file[MAX_PATH+1];
+	sbbs_t*	sbbs;
+	const char*	warning;
+
+	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
+		return;
+	
+	if(report==NULL) {
+		lprintf("!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=nulstr;
+
+	if(sbbs->online==ON_LOCAL) 
+		eprintf("!JavaScript %s%s%s: %s",warning,file,line,message);
+	else {
+		lprintf("!JavaScript %s%s%s: %s",warning,file,line,message);
+		sbbs->bprintf("!JavaScript %s%s%s: %s\r\n",warning,file,line,message);
+	}
+}
+
+bool sbbs_t::js_init()
+{
+	char		node[128];
+	char		ver[256];
+	jsval		val;
+	JSObject*	server;
+	JSString*	js_str;
+
+    if(cfg.node_num)
+    	sprintf(node,"Node %d",cfg.node_num);
+    else
+    	strcpy(node,client_name);
+
+	lprintf("%s JavaScript: Creating runtime: %lu bytes"
+		,node,startup->js_max_bytes);
+
+	if((js_runtime = JS_NewRuntime(startup->js_max_bytes))==NULL)
+		return(false);
+
+	lprintf("%s JavaScript: Initializing context (stack: %lu bytes)"
+		,node,JAVASCRIPT_CONTEXT_STACK);
+
+    if((js_cx = JS_NewContext(js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
+		return(false);
+
+	js_loop = 0;	/* loop counter */
+
+	bool success=false;
+
+	do {
+
+		JS_SetErrorReporter(js_cx, js_ErrorReporter);
+
+		JS_SetContextPrivate(js_cx, this);	/* Store a pointer to sbbs_t instance */
+
+		/* Global Object */
+		if((js_glob=js_CreateGlobalObject(js_cx, &cfg, js_global_functions))==NULL)
+			break;
+
+#ifdef _DEBUG
+		JS_DefineProperty(js_cx, js_glob, "_global", OBJECT_TO_JSVAL(js_glob)
+			,NULL,NULL,JSPROP_READONLY);
+#endif
+
+		/* System Object */
+		if(js_CreateSystemObject(js_cx, js_glob, &cfg, uptime, startup->host_name)==NULL)
+			break;
+
+		/* Client Object */
+		if(js_CreateClientObject(js_cx, js_glob, "client", &client, client_socket)==NULL)
+			break;
+
+		/* BBS Object */
+		if(js_CreateBbsObject(js_cx, js_glob)==NULL)
+			break;
+
+		/* Console Object */
+		if(js_CreateConsoleObject(js_cx, js_glob)==NULL)
+			break;
+
+		/* Socket Class */
+		if(js_CreateSocketClass(js_cx, js_glob)==NULL)
+			break;
+
+		/* MsgBase Class */
+		if(js_CreateMsgBaseClass(js_cx, js_glob, &scfg)==NULL)
+			break;
+
+		/* File Class */
+		if(js_CreateFileClass(js_cx, js_glob)==NULL)
+			break;
+
+		/* User class */
+		if(js_CreateUserClass(js_cx, js_glob, &scfg)==NULL) 
+			break;
+
+		/* Area Objects */
+		if(!js_CreateUserObjects(js_cx, js_glob, &scfg, NULL, NULL, NULL)) 
+			break;
+
+		/* Server Object */
+		if((server=JS_DefineObject(js_cx, js_glob, "server", NULL
+			,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL)
+			break;
+
+		sprintf(ver,"%s %s%c",TELNET_SERVER,VERSION,REVISION);
+		if((js_str=JS_NewStringCopyZ(js_cx, ver))==NULL)
+			break;
+		val = STRING_TO_JSVAL(js_str);
+		if(!JS_SetProperty(js_cx, server, "version", &val))
+			break;
+
+		if((js_str=JS_NewStringCopyZ(js_cx, bbs_ver()))==NULL)
+			break;
+		val = STRING_TO_JSVAL(js_str);
+		if(!JS_SetProperty(js_cx, server, "version_detail", &val))
+			break;
+
+#ifdef _DEBUG
+		js_DescribeObject(js_cx,server,"Server-specifc properties");
+		js_CreateArrayOfStrings(js_cx, server, "_property_desc_list", server_prop_desc, JSPROP_READONLY);
+#endif
+
+		success=true;
+
+	} while(0);
+
+	if(!success) {
+		JS_DestroyContext(js_cx);
+		js_cx=NULL;
+		return(false);
+	}
+
+	return(true);
+}
+
+void sbbs_t::js_create_user_objects(void)
+{
+	if(js_cx==NULL)
+		return;
+
+	if(!js_CreateUserObjects(js_cx, js_glob, &cfg, &useron, NULL, subscan)) 
+		lprintf("!JavaScript ERROR creating user objects");
+}
+
+#endif	/* JAVASCRIPT */
+
+#ifdef _WINSOCKAPI_
+
+WSADATA WSAData;
+static BOOL WSAInitialized=FALSE;
+
+static BOOL winsock_startup(void)
+{
+	int		status;             /* Status Code */
+
+    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
+		lprintf("%s %s",WSAData.szDescription, WSAData.szSystemStatus);
+		WSAInitialized=TRUE;
+		return(TRUE);
+	}
+
+    lprintf("!WinSock startup ERROR %d", status);
+	return(FALSE);
+}
+
+#else /* No WINSOCK */
+
+#define winsock_startup()	(TRUE)	
+
+#endif
+
+static BYTE* telnet_interpret(sbbs_t* sbbs, BYTE* inbuf, int inlen,
+  									BYTE* outbuf, int& outlen)
+{
+	BYTE*   first_iac=NULL;
+	BYTE*	first_cr=NULL;
+	int 	i;
+
+    first_iac=(BYTE*)memchr(inbuf, TELNET_IAC, inlen);
+
+	if(!(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE)) 
+		&& !(sbbs->console&CON_RAW_IN)) {
+		if(sbbs->telnet_last_rxch==CR)
+			first_cr=inbuf;
+		else
+			first_cr=(BYTE*)memchr(inbuf, CR, inlen);
+	}
+
+    if(!sbbs->telnet_cmdlen	&& first_iac==NULL && first_cr==NULL) {
+        outlen=inlen;
+        return(inbuf);	// no interpretation needed
+    }
+
+    if(first_iac!=NULL || first_cr!=NULL) {
+		if(first_iac!=NULL && (first_cr==NULL || first_iac<first_cr))
+   			outlen=first_iac-inbuf;
+		else
+			outlen=first_cr-inbuf;
+	    memcpy(outbuf, inbuf, outlen);
+    } else
+    	outlen=0;
+
+    for(i=outlen;i<inlen;i++) {
+		if(!(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE)) 
+			&& !(sbbs->console&CON_RAW_IN)) {
+			if(sbbs->telnet_last_rxch==CR
+				&& (inbuf[i]==LF || inbuf[i]==0)) { // CR/LF or CR/NUL, ignore 2nd char
+#if 0 /* Debug CR/LF problems */
+				lprintf("Node %d CR/%02Xh detected and ignored"
+					,sbbs->cfg.node_num, inbuf[i]);
+#endif
+				sbbs->telnet_last_rxch=inbuf[i];
+				continue;
+			}
+			sbbs->telnet_last_rxch=inbuf[i];
+		}
+
+        if(inbuf[i]==TELNET_IAC && sbbs->telnet_cmdlen==1) { /* escaped 255 */
+            sbbs->telnet_cmdlen=0;
+            outbuf[outlen++]=TELNET_IAC;
+            continue;
+        }
+        if(inbuf[i]==TELNET_IAC || sbbs->telnet_cmdlen) {
+            sbbs->telnet_cmd[sbbs->telnet_cmdlen++]=inbuf[i];
+            if(sbbs->telnet_cmdlen==2 && inbuf[i]<TELNET_WILL) {
+				if(startup->options&BBS_OPT_DEBUG_TELNET)
+            		lprintf("Node %d %s telnet cmd: %s"
+	                	,sbbs->cfg.node_num
+						,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
+                		,telnet_cmd_desc(sbbs->telnet_cmd[2]));
+                sbbs->telnet_cmdlen=0;
+            }
+            else if(sbbs->telnet_cmdlen>=3) {
+				if(sbbs->telnet_cmd[2]==TELNET_BINARY) {
+					if(sbbs->telnet_cmd[1]==TELNET_WILL)
+						sbbs->telnet_mode|=TELNET_MODE_BIN_RX;
+					else if(sbbs->telnet_cmd[1]==TELNET_WONT)
+						sbbs->telnet_mode&=~TELNET_MODE_BIN_RX;
+				}
+				if(sbbs->telnet_cmd[2]==TELNET_ECHO) {
+					if(sbbs->telnet_cmd[1]==TELNET_DO)
+						sbbs->telnet_mode|=TELNET_MODE_ECHO;
+					else if(sbbs->telnet_cmd[1]==TELNET_DONT) {
+						sbbs->telnet_mode&=~TELNET_MODE_ECHO;
+						if(!(sbbs->telnet_mode&TELNET_MODE_GATE))
+							sbbs->send_telnet_cmd(TELNET_WILL,TELNET_ECHO);
+					}
+				}
+				if(startup->options&BBS_OPT_DEBUG_TELNET)
+					lprintf("Node %d %s telnet cmd: %s %s"
+	                    ,sbbs->cfg.node_num
+						,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
+						,telnet_cmd_desc(sbbs->telnet_cmd[1])
+						,telnet_opt_desc(sbbs->telnet_cmd[2]));
+                sbbs->telnet_cmdlen=0;
+            }
+			if(sbbs->telnet_mode&TELNET_MODE_GATE)	// Pass-through commads
+				outbuf[outlen++]=inbuf[i];
+        } else
+        	outbuf[outlen++]=inbuf[i];
+    }
+    return(outbuf);
+}
+
+void sbbs_t::send_telnet_cmd(uchar cmd, uchar opt)
+{
+	char buf[16];
+
+	if(cmd<TELNET_WILL) {
+		if(startup->options&BBS_OPT_DEBUG_TELNET)
+            lprintf("Node %d sending telnet cmd: %s"
+	            ,cfg.node_num
+                ,telnet_cmd_desc(cmd));
+		sprintf(buf,"%c%c",TELNET_IAC,cmd);
+		putcom(buf,2);
+	} else {
+		if(startup->options&BBS_OPT_DEBUG_TELNET)
+			lprintf("Node %d sending telnet cmd: %s %s"
+				,cfg.node_num
+				,telnet_cmd_desc(cmd)
+				,telnet_opt_desc(opt));
+		sprintf(buf,"%c%c%c",TELNET_IAC,cmd,opt);
+		putcom(buf,3);
+	}
+}
+
+void input_thread(void *arg)
+{
+	BYTE		inbuf[4000];
+   	BYTE		telbuf[sizeof(inbuf)];
+    BYTE		*wrbuf;
+    int			i,rd,wr,avail;
+	ulong		total_recv=0;
+	ulong		total_pkts=0;
+	fd_set		socket_set;
+	sbbs_t*		sbbs = (sbbs_t*) arg;
+	struct timeval	tv;
+	SOCKET		sock=INVALID_SOCKET;
+
+	thread_up(TRUE /* setuid */);
+
+#ifdef _DEBUG
+	lprintf("Node %d input thread started",sbbs->cfg.node_num);
+#endif
+
+	pthread_mutex_init(&sbbs->input_thread_mutex,NULL);
+    sbbs->input_thread_running = true;
+	sbbs->console|=CON_R_INPUT;
+
+	sock=sbbs->client_socket;
+	while(sbbs->online && sbbs->client_socket!=INVALID_SOCKET
+		&& node_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) {
+
+		pthread_mutex_lock(&sbbs->input_thread_mutex);
+
+#ifdef __unix__
+		if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET)  {
+			if(sock==sbbs->client_socket)
+				sock=uspy_socket[sbbs->cfg.node_num-1];
+			else
+				sock=sbbs->client_socket;
+		}
+#endif
+
+		FD_ZERO(&socket_set);
+		FD_SET(sock,&socket_set);
+
+		if(sock==sbbs->client_socket)  {
+			tv.tv_sec=0;
+			tv.tv_usec=250000;
+		}
+		else  {
+			tv.tv_sec=0;
+			tv.tv_usec=0;
+		}
+
+		if((i=select(sock+1,&socket_set,NULL,NULL,&tv))<1) {
+			pthread_mutex_unlock(&sbbs->input_thread_mutex);
+			if(i==0 && sock==sbbs->client_socket)
+				continue;
+
+			if(sbbs->client_socket==INVALID_SOCKET)
+				break;
+
+			if(sock==sbbs->client_socket)  {
+	        	if(ERROR_VALUE == ENOTSOCK)
+    	            lprintf("Node %d socket closed by peer on input->select", sbbs->cfg.node_num);
+				else if(ERROR_VALUE==ESHUTDOWN)
+					lprintf("Node %d socket shutdown on input->select", sbbs->cfg.node_num);
+				else if(ERROR_VALUE==EINTR)
+					lprintf("Node %d input thread interrupted",sbbs->cfg.node_num);
+        	    else if(ERROR_VALUE==ECONNRESET) 
+					lprintf("Node %d connection reset by peer on input->select", sbbs->cfg.node_num);
+	            else if(ERROR_VALUE==ECONNABORTED) 
+					lprintf("Node %d connection aborted by peer on input->select", sbbs->cfg.node_num);
+				else
+					lprintf("Node %d !ERROR %d input->select socket %d"
+                		,sbbs->cfg.node_num, ERROR_VALUE, sock);
+				break;
+			}
+#ifdef __unix__
+			else  {
+				if(ERROR_VALUE != EAGAIN)  {
+					lprintf("Node %d !ERROR %d on local spy socket %d input->select"
+						, sbbs->cfg.node_num, errno, sock);
+					close_socket(uspy_socket[sbbs->cfg.node_num-1]);
+					uspy_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
+				}
+				continue;
+			}
+#endif
+		}
+
+		if(sbbs->client_socket==INVALID_SOCKET) {
+			pthread_mutex_unlock(&sbbs->input_thread_mutex);
+			break;
+		}
+
+    	rd=RingBufFree(&sbbs->inbuf);
+
+		if(!rd) { // input buffer full
+        	// wait up to 5 seconds to empty (1 byte min)
+			time_t start=time(NULL);
+            while((rd=RingBufFree(&sbbs->inbuf))==0) {
+            	if(time(NULL)-start>=5) {
+                	rd=1;
+                	break;
+                }
+                YIELD();
+            }
+		}
+		
+	    if(rd > (int)sizeof(inbuf))
+        	rd=sizeof(inbuf);
+
+    	rd = recv(sock, (char*)inbuf, rd, 0);
+
+		pthread_mutex_unlock(&sbbs->input_thread_mutex);
+
+		if(rd == SOCKET_ERROR)
+		{
+			if(sock==sbbs->client_socket)  {
+	        	if(ERROR_VALUE == ENOTSOCK)
+    	            lprintf("Node %d socket closed by peer on receive", sbbs->cfg.node_num);
+        	    else if(ERROR_VALUE==ECONNRESET) 
+					lprintf("Node %d connection reset by peer on receive", sbbs->cfg.node_num);
+				else if(ERROR_VALUE==ESHUTDOWN)
+					lprintf("Node %d socket shutdown on receive", sbbs->cfg.node_num);
+        	    else if(ERROR_VALUE==ECONNABORTED) 
+					lprintf("Node %d connection aborted by peer on receive", sbbs->cfg.node_num);
+				else
+					lprintf("Node %d !ERROR %d receiving from socket %d"
+        	        	,sbbs->cfg.node_num, ERROR_VALUE, sock);
+				break;
+			}
+#ifdef __unix__
+			else  {
+				if(ERROR_VALUE != EAGAIN)  {
+					lprintf("Node %d !ERRRO %d on local spy socket %d receive"
+						,sbbs->cfg.node_num, errno, sock);
+					close_socket(uspy_socket[sbbs->cfg.node_num-1]);
+					uspy_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
+				}
+				continue;
+			}
+#endif
+		}
+
+		if(rd == 0 && sock==sbbs->client_socket)
+		{
+			lprintf("Node %d disconnected", sbbs->cfg.node_num);
+			break;
+		}
+
+		total_recv+=rd;
+		total_pkts++;
+
+        // telbuf and wr are modified to reflect telnet escaped data
+#ifdef __unix__
+		if(sock!=sbbs->client_socket)  {
+			wr=rd;
+			wrbuf=inbuf;
+		}
+		else
+#endif
+			wrbuf=telnet_interpret(sbbs, inbuf, rd, telbuf, wr);
+		if(wr > (int)sizeof(telbuf)) 
+			lprintf("!TELBUF OVERFLOW (%d>%d)",wr,sizeof(telbuf));
+
+		/* First level Ctrl-C checking */
+		if(!(sbbs->cfg.ctrlkey_passthru&(1<<CTRL_C))
+			&& sbbs->rio_abortable 
+			&& !(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE))
+			&& memchr(wrbuf, CTRL_C, wr)) {	
+			if(RingBufFull(&sbbs->inbuf))
+    			lprintf("Node %d Ctrl-C hit with %lu bytes in input buffer"
+					,sbbs->cfg.node_num,RingBufFull(&sbbs->inbuf));
+			if(RingBufFull(&sbbs->outbuf))
+    			lprintf("Node %d Ctrl-C hit with %lu bytes in output buffer"
+					,sbbs->cfg.node_num,RingBufFull(&sbbs->outbuf));
+			sbbs->sys_status|=SS_ABORT;
+			RingBufReInit(&sbbs->inbuf);	/* Purge input buffer */
+    		RingBufReInit(&sbbs->outbuf);	/* Purge output buffer */
+			sem_post(&sbbs->inbuf.sem);
+			continue;	// Ignore the entire buffer
+		}
+
+		avail=RingBufFree(&sbbs->inbuf);
+
+        if(avail<wr)
+			lprintf("!INPUT BUFFER FULL (%d free)", avail);
+        else
+			RingBufWrite(&sbbs->inbuf, wrbuf, wr);
+//		if(wr>100)
+//			mswait(500);	// Throttle sender
+	}
+	sbbs->online=0;
+
+    sbbs->input_thread_running = false;
+	if(node_socket[sbbs->cfg.node_num-1]==INVALID_SOCKET)	// Shutdown locally
+		sbbs->terminated = true;	// Signal JS to stop execution
+
+	pthread_mutex_destroy(&sbbs->input_thread_mutex);
+
+	thread_down();
+	lprintf("Node %d input thread terminated (received %lu bytes in %lu blocks)"
+		,sbbs->cfg.node_num, total_recv, total_pkts);
+}
+
+void output_thread(void* arg)
+{
+	char		node[128];
+	char		stats[128];
+    BYTE		buf[IO_THREAD_BUF_SIZE];
+	int			i;
+    ulong		avail;
+	ulong		total_sent=0;
+	ulong		total_pkts=0;
+    ulong		bufbot=0;
+    ulong		buftop=0;
+	sbbs_t*		sbbs = (sbbs_t*) arg;
+	fd_set		socket_set;
+	struct timeval tv;
+
+	thread_up(TRUE /* setuid */);
+
+    if(sbbs->cfg.node_num)
+    	sprintf(node,"Node %d",sbbs->cfg.node_num);
+    else
+    	strcpy(node,sbbs->client_name);
+#ifdef _DEBUG
+	lprintf("%s output thread started",node);
+#endif
+
+    sbbs->output_thread_running = true;
+	sbbs->console|=CON_R_ECHO;
+
+	while(sbbs->client_socket!=INVALID_SOCKET && telnet_socket!=INVALID_SOCKET) {
+
+    	if(bufbot==buftop)
+	    	avail=RingBufFull(&sbbs->outbuf);
+        else
+        	avail=buftop-bufbot;
+
+		if(!avail) {
+			sem_wait(&sbbs->outbuf.sem);
+			if(sbbs->outbuf.highwater_mark)
+				sem_trywait_block(&sbbs->outbuf.highwater_sem,startup->outbuf_drain_timeout);
+			continue; 
+		}
+
+		/* Check socket for writability (using select) */
+		tv.tv_sec=0;
+		tv.tv_usec=1000;
+
+		FD_ZERO(&socket_set);
+		FD_SET(sbbs->client_socket,&socket_set);
+
+		i=select(sbbs->client_socket+1,NULL,&socket_set,NULL,&tv);
+		if(i==SOCKET_ERROR) {
+			lprintf("!%s: ERROR %d selecting socket %u for send"
+				,node,ERROR_VALUE,sbbs->client_socket);
+			if(sbbs->cfg.node_num)	/* Only break if node output (not server) */
+				break;
+			RingBufReInit(&sbbs->outbuf);	/* Flush output buffer */
+			continue;
+		}
+		if(i<1) {
+			continue;
+		}
+
+        if(bufbot==buftop) { // linear buf empty, read from ring buf
+            if(avail>sizeof(buf)) {
+                lprintf("Reducing output buffer");
+                avail=sizeof(buf);
+            }
+            buftop=RingBufRead(&sbbs->outbuf, buf, avail);
+            bufbot=0;
+        }
+		i=sendsocket(sbbs->client_socket, (char*)buf+bufbot, buftop-bufbot);
+		if(i==SOCKET_ERROR) {
+        	if(ERROR_VALUE == ENOTSOCK)
+                lprintf("%s client socket closed on send", node);
+            else if(ERROR_VALUE==ECONNRESET) 
+				lprintf("%s connection reset by peer on send", node);
+            else if(ERROR_VALUE==ECONNABORTED) 
+				lprintf("%s connection aborted by peer on send", node);
+			else
+				lprintf("!%s: ERROR %d sending on socket %d"
+                	,node, ERROR_VALUE, sbbs->client_socket);
+			sbbs->online=0;
+			/* was break; on 4/7/00 */
+			i=buftop-bufbot;	// Pretend we sent it all
+		}
+
+		if(sbbs->cfg.node_num>0 && !(sbbs->sys_status&SS_FILEXFER)) {
+			/* Spy on the user locally */
+			if(startup->node_spybuf!=NULL 
+				&& startup->node_spybuf[sbbs->cfg.node_num-1]!=NULL) {
+				RingBufWrite(startup->node_spybuf[sbbs->cfg.node_num-1],buf+bufbot,i);
+				/* Signal spy output semaphore? */
+				if(startup->node_spysem!=NULL 
+					&& startup->node_spysem[sbbs->cfg.node_num-1]!=NULL)
+					sem_post(startup->node_spysem[sbbs->cfg.node_num-1]);
+			}
+			/* Spy on the user remotely */
+			if(spy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) 
+				sendsocket(spy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i);
+#ifdef __unix__
+			if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET)
+				sendsocket(uspy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i);
+#endif
+		}
+
+		bufbot+=i;
+		total_sent+=i;
+		total_pkts++;
+    }
+
+	sbbs->spymsg("Disconnected");
+
+    sbbs->output_thread_running = false;
+
+	if(total_sent)
+		sprintf(stats,"(sent %lu bytes in %lu blocks, %lu average)"
+			,total_sent, total_pkts, total_sent/total_pkts);
+	else
+		stats[0]=0;
+
+	thread_down();
+	lprintf("%s output thread terminated %s", node, stats);
+}
+
+void event_thread(void* arg)
+{
+	char		str[MAX_PATH+1];
+	char		bat_list[MAX_PATH+1];
+	char		semfile[MAX_PATH+1];
+	int			i,j,k;
+	int			file;
+	int			offset;
+	bool		check_semaphores;
+	ulong		l;
+	time_t		now;
+	time_t		start;
+	time_t		lastsemchk=0;
+	time_t		lastnodechk=0;
+	time_t		lastprepack=0;
+	node_t		node;
+	glob_t		g;
+	sbbs_t*		sbbs = (sbbs_t*) arg;
+	struct tm	now_tm;
+	struct tm	tm;
+
+	eprintf("BBS Events thread started");
+
+	sbbs->event_thread_running = true;
+
+	srand(time(NULL));	/* Seed random number generator */
+	sbbs_random(10);	/* Throw away first number */
+
+	thread_up(TRUE /* setuid */);
+
+#ifdef JAVASCRIPT
+	if(!(startup->options&BBS_OPT_NO_JAVASCRIPT)) {
+		if(!sbbs->js_init())	/* This must be done in the context of the node thread */
+			lprintf("!JavaScript Initialization FAILURE");
+	}
+#endif
+
+	while(!sbbs->terminated && telnet_socket!=INVALID_SOCKET) {
+
+		now=time(NULL);
+		localtime_r(&now,&now_tm);
+
+		if(now-lastsemchk>=sbbs->cfg.node_sem_check) {
+			check_semaphores=true;
+			lastsemchk=now;
+		} else
+			check_semaphores=false;
+
+		pthread_mutex_lock(&event_mutex);
+		event_mutex_locked=true;
+
+		sbbs->online=0;	/* reset this from ON_LOCAL */
+
+		if(scfg_reloaded==true) {
+
+			for(i=0;i<TOTAL_TEXT;i++)
+				sbbs->text[i]=sbbs->text_sav[i]=text[i];
+
+			memcpy(&sbbs->cfg,&scfg,sizeof(scfg_t));
+
+			if(startup->temp_dir[0]) {
+				SAFECOPY(sbbs->cfg.temp_dir,startup->temp_dir);
+			} else
+				prep_dir(sbbs->cfg.data_dir, sbbs->cfg.temp_dir, sizeof(sbbs->cfg.temp_dir));
+
+			// Read TIME.DAB
+			sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir);
+			if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) {
+				sbbs->errormsg(WHERE,ERR_OPEN,str,0);
+				break; 
+			}
+			for(i=0;i<sbbs->cfg.total_events;i++) {
+				sbbs->cfg.event[i]->last=0;
+				if(filelength(file)<(long)(sizeof(time_t)*(i+1)))
+					write(file,&sbbs->cfg.event[i]->last,sizeof(time_t));
+				else
+					read(file,&sbbs->cfg.event[i]->last,sizeof(time_t)); 
+			}
+			read(file,&lastprepack,sizeof(time_t));
+			close(file);
+
+			// Read QNET.DAB
+			sprintf(str,"%sqnet.dab",sbbs->cfg.ctrl_dir);
+			if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) {
+				sbbs->errormsg(WHERE,ERR_OPEN,str,0);
+				return;
+			}
+			for(i=0;i<sbbs->cfg.total_qhubs;i++) {
+				sbbs->cfg.qhub[i]->last=0;
+				if(filelength(file)<(long)(sizeof(time_t)*(i+1)))
+					write(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t));
+				else
+					read(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t)); 
+			}
+			close(file);
+
+			// Read PNET.DAB
+			sprintf(str,"%spnet.dab",sbbs->cfg.ctrl_dir);
+			if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) {
+				sbbs->errormsg(WHERE,ERR_OPEN,str,0);
+				break;
+			}
+			for(i=0;i<sbbs->cfg.total_phubs;i++) {
+				sbbs->cfg.phub[i]->last=0;
+				if(filelength(file)<(long)(sizeof(time_t)*(i+1)))
+					write(file,&sbbs->cfg.phub[i]->last,sizeof(time_t));
+				else
+					read(file,&sbbs->cfg.phub[i]->last,sizeof(time_t)); 
+			}
+			close(file);
+
+			scfg_reloaded=false;
+		}
+
+		/* QWK events */
+		if(check_semaphores && !(startup->options&BBS_OPT_NO_QWK_EVENTS)) {
+			/* Import any REP files that have magically appeared (via FTP perhaps) */
+			sprintf(str,"%sfile/",sbbs->cfg.data_dir);
+			offset=strlen(str);
+			strcat(str,"*.rep");
+			glob(str,0,NULL,&g);
+			for(i=0;i<(int)g.gl_pathc;i++) {
+				sbbs->useron.number=atoi(g.gl_pathv[i]+offset);
+				getuserdat(&sbbs->cfg,&sbbs->useron);
+				if(sbbs->useron.number && flength(g.gl_pathv[i])>0) {
+					sbbs->online=ON_LOCAL;
+					eprintf("Un-packing QWK Reply packet from %s",sbbs->useron.alias);
+					sbbs->getusrsubs();
+					sbbs->unpack_rep(g.gl_pathv[i]);
+					sbbs->batch_create_list();	/* FREQs? */
+					sbbs->batdn_total=0;
+					
+					/* putuserdat? */
+					remove(g.gl_pathv[i]);
+				}
+			}
+			globfree(&g);
+
+			/* Create any QWK files that have magically appeared (via FTP perhaps) */
+			sprintf(str,"%spack*.now",sbbs->cfg.data_dir);
+			offset=strlen(sbbs->cfg.data_dir)+4;
+			glob(str,0,NULL,&g);
+			for(i=0;i<(int)g.gl_pathc;i++) {
+				sbbs->useron.number=atoi(g.gl_pathv[i]+offset);
+				getuserdat(&sbbs->cfg,&sbbs->useron);
+				if(sbbs->useron.number && !(sbbs->useron.misc&(DELETED|INACTIVE))) {
+					eprintf("Packing QWK Message Packet for %s",sbbs->useron.alias);
+					sbbs->online=ON_LOCAL;
+					delfiles(sbbs->cfg.temp_dir,ALLFILES);
+					sbbs->getmsgptrs();
+					sbbs->getusrsubs();
+					sbbs->batdn_total=0;
+
+					sbbs->last_ns_time=sbbs->ns_time=sbbs->useron.ns_time;
+					sprintf(bat_list,"%sfile/%04u.dwn",sbbs->cfg.data_dir,sbbs->useron.number);
+					sbbs->batch_add_list(bat_list);
+
+					sprintf(str,"%sfile%c%04u.qwk"
+						,sbbs->cfg.data_dir,BACKSLASH,sbbs->useron.number);
+					if(sbbs->pack_qwk(str,&l,true /* pre-pack/off-line */)) {
+						eprintf("Packing completed");
+						sbbs->qwk_success(l,0,1);
+						sbbs->putmsgptrs(); 
+						remove(bat_list);
+					} else
+						eprintf("No packet created (no new messages)");
+					delfiles(sbbs->cfg.temp_dir,ALLFILES);
+					sbbs->online=0;
+				}
+				remove(g.gl_pathv[i]);
+			}
+			globfree(&g);
+
+			/* Create (pre-pack) QWK files for users configured as such */
+			sprintf(semfile,"%sprepack.now",sbbs->cfg.data_dir);
+			if(sbbs->cfg.preqwk_ar[0] 
+				&& (fexistcase(semfile) || (now-lastprepack)/60>(60*24))) {
+				j=lastuser(&sbbs->cfg);
+				eprintf("Pre-packing QWK Message packets...");
+				for(i=1;i<=j;i++) {
+
+					sprintf(str,"%5u of %-5u",i,j);
+					//status(str);
+					sbbs->useron.number=i;
+					getuserdat(&sbbs->cfg,&sbbs->useron);
+
+					if(sbbs->useron.number
+						&& !(sbbs->useron.misc&(DELETED|INACTIVE))	 /* Pre-QWK */
+						&& sbbs->chk_ar(sbbs->cfg.preqwk_ar,&sbbs->useron)) { 
+						for(k=1;k<=sbbs->cfg.sys_nodes;k++) {
+							if(sbbs->getnodedat(k,&node,0)!=0)
+								continue;
+							if((node.status==NODE_INUSE || node.status==NODE_QUIET
+								|| node.status==NODE_LOGON) && node.useron==i)
+								break; 
+						}
+						if(k<=sbbs->cfg.sys_nodes)	/* Don't pre-pack with user online */
+							continue;
+						eprintf("Pre-packing QWK for %s",sbbs->useron.alias);
+						sbbs->online=ON_LOCAL;
+						delfiles(sbbs->cfg.temp_dir,ALLFILES);
+						sbbs->getmsgptrs();
+						sbbs->getusrsubs();
+						sbbs->batdn_total=0;
+						sprintf(str,"%sfile%c%04u.qwk"
+							,sbbs->cfg.data_dir,BACKSLASH,sbbs->useron.number);
+						if(sbbs->pack_qwk(str,&l,true /* pre-pack */)) {
+							sbbs->qwk_success(l,0,1);
+							sbbs->putmsgptrs(); 
+						}
+						delfiles(sbbs->cfg.temp_dir,ALLFILES);
+						sbbs->online=0;
+					} 
+				}
+				lastprepack=now;
+				sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir);
+				if((file=sbbs->nopen(str,O_WRONLY))==-1) {
+					sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
+					break; 
+				}
+				lseek(file,(long)sbbs->cfg.total_events*4L,SEEK_SET);
+				write(file,&lastprepack,sizeof(time_t));
+				close(file);
+
+				remove(semfile);
+				//status(STATUS_WFC);
+			}
+		}
+
+		if(check_semaphores) {
+			/* Node Daily Events */
+			for(i=first_node;i<=last_node;i++) {
+				// Node Daily Event
+				node.status=NODE_INVALID_STATUS;
+				if(sbbs->getnodedat(i,&node,0)!=0)
+					continue;
+				if(node.misc&NODE_EVENT && node.status==NODE_WFC) {
+					sbbs->getnodedat(i,&node,1);
+					node.status=NODE_EVENT_RUNNING;
+					sbbs->putnodedat(i,&node);
+					if(sbbs->cfg.node_daily[0]) {
+						sbbs->cfg.node_num=i;
+						strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[i-1]);
+
+						eprintf("Running node %d daily event",i);
+						sbbs->online=ON_LOCAL;
+						sbbs->logentry("!:","Run node daily event");
+						sbbs->external(
+							 sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL)
+							,EX_OFFLINE);
+					}
+					sbbs->getnodedat(i,&node,1);
+					node.misc&=~NODE_EVENT;
+					node.status=NODE_WFC;
+					node.useron=0;
+					sbbs->putnodedat(i,&node); 
+				}
+			}
+
+			/* QWK Networking Call-out sempahores */
+			for(i=0;i<sbbs->cfg.total_qhubs;i++) {
+				if(sbbs->cfg.qhub[i]->node<first_node 
+					|| sbbs->cfg.qhub[i]->node>last_node)
+					continue;
+				if(sbbs->cfg.qhub[i]->last==-1) // already signaled
+					continue;
+				sprintf(str,"%sqnet/%s.now",sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
+				if(fexistcase(str)) {
+					strcpy(str,sbbs->cfg.qhub[i]->id);
+					eprintf("Semaphore signaled for QWK Network Hub: %s",strupr(str));
+					sbbs->cfg.qhub[i]->last=-1; 
+				}
+			}
+
+			/* Timed Event sempahores */
+			for(i=0;i<sbbs->cfg.total_events;i++) {
+				if((sbbs->cfg.event[i]->node<first_node
+					|| sbbs->cfg.event[i]->node>last_node)
+					&& !(sbbs->cfg.event[i]->misc&EVENT_EXCL))
+					continue;	// ignore non-exclusive events for other instances
+				if(sbbs->cfg.event[i]->last==-1) // already signaled
+					continue;
+				sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
+				if(fexistcase(str)) {
+					strcpy(str,sbbs->cfg.event[i]->code);
+					eprintf("Semaphore signaled for Timed Event: %s",strupr(str));
+					sbbs->cfg.event[i]->last=-1; 
+				}
+			}
+		}
+
+		/* QWK Networking Call-out Events */
+		for(i=0;i<sbbs->cfg.total_qhubs;i++) {
+			if(sbbs->cfg.qhub[i]->node<first_node ||
+				sbbs->cfg.qhub[i]->node>last_node)
+				continue;
+
+			if(check_semaphores) {
+				// See if any packets have come in
+				for(j=0;j<101;j++) {
+					sprintf(str,"%s%s.q%c%c",sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id
+						,j>10 ? ((j-1)/10)+'0' : 'w'
+						,j ? ((j-1)%10)+'0' : 'k');
+					fexistcase(str);		/* fix wrong-case filenames on Unix */
+					if(flength(str)>0) {	/* silently ignore 0-byte QWK packets */
+						delfiles(sbbs->cfg.temp_dir,ALLFILES);
+						sbbs->online=ON_LOCAL;
+						if(sbbs->unpack_qwk(str,i)==false) {
+							char newname[MAX_PATH+1];
+							sprintf(newname,"%s.%lx.bad",str,(long)now);
+							remove(newname);
+							if(rename(str,newname)==0) {
+								char logmsg[MAX_PATH*3];
+								sprintf(logmsg,"%s renamed to %s",str,newname);
+								sbbs->logline("Q!",logmsg);
+							}
+						}
+						sbbs->online=0;
+						remove(str);
+					} 
+				}
+			}
+
+			/* Qnet call out based on time */
+			if(localtime_r(&sbbs->cfg.qhub[i]->last,&tm)==NULL)
+				memset(&tm,0,sizeof(tm));
+			if((sbbs->cfg.qhub[i]->last==-1L					/* or frequency */
+				|| ((sbbs->cfg.qhub[i]->freq
+					&& (now-sbbs->cfg.qhub[i]->last)/60>sbbs->cfg.qhub[i]->freq)
+					|| (sbbs->cfg.qhub[i]->time
+						&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.qhub[i]->time
+						&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
+						&& sbbs->cfg.qhub[i]->days&(1<<now_tm.tm_wday))) {
+				sprintf(str,"%sqnet/%s.now"
+					,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
+				if(fexistcase(str))
+					remove(str);					/* Remove semaphore file */
+				sprintf(str,"%sqnet/%s.ptr"
+					,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
+				file=sbbs->nopen(str,O_RDONLY);
+				for(j=0;j<sbbs->cfg.qhub[i]->subs;j++) {
+					sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr=0;
+					if(file!=-1) {
+						lseek(file,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*sizeof(long),SEEK_SET);
+						read(file,&sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr,sizeof(long)); 
+					}
+				}
+				if(file!=-1)
+					close(file);
+				if(sbbs->pack_rep(i)) {
+					if((file=sbbs->nopen(str,O_WRONLY|O_CREAT))==-1)
+						sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_CREAT);
+					else {
+						for(j=l=0;j<sbbs->cfg.qhub[i]->subs;j++) {
+							while(filelength(file)<
+								sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*4L)
+								write(file,&l,4);		/* initialize ptrs to null */
+							lseek(file
+								,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*sizeof(long)
+								,SEEK_SET);
+							write(file,&sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr,sizeof(long)); 
+						}
+						close(file); 
+					} 
+				}
+				delfiles(sbbs->cfg.temp_dir,ALLFILES);
+
+				sbbs->cfg.qhub[i]->last=time(NULL);
+				sprintf(str,"%sqnet.dab",sbbs->cfg.ctrl_dir);
+				if((file=sbbs->nopen(str,O_WRONLY))==-1) {
+					sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
+					break; 
+				}
+				lseek(file,sizeof(time_t)*i,SEEK_SET);
+				write(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t));
+				close(file);
+
+				if(sbbs->cfg.qhub[i]->call[0]) {
+					sbbs->cfg.node_num=sbbs->cfg.qhub[i]->node;
+					if(sbbs->cfg.node_num<1) 
+						sbbs->cfg.node_num=1;
+					strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
+					eprintf("QWK Network call-out: %s",sbbs->cfg.qhub[i]->id); 
+					sbbs->online=ON_LOCAL;
+					sbbs->external(
+						 sbbs->cmdstr(sbbs->cfg.qhub[i]->call,nulstr,nulstr,NULL)
+						,EX_OFFLINE|EX_SH);	/* sh for Unix perl scripts */
+				}
+			} 
+		}
+
+		/* PostLink Networking Call-out Events */
+		for(i=0;i<sbbs->cfg.total_phubs;i++) {
+			if(sbbs->cfg.phub[i]->node<first_node 
+				|| sbbs->cfg.phub[i]->node>last_node)
+				continue;
+			/* PostLink call out based on time */
+			if(localtime_r(&sbbs->cfg.phub[i]->last,&tm)==NULL)
+				memset(&tm,0,sizeof(tm));
+			if(sbbs->cfg.phub[i]->last==-1
+				|| (((sbbs->cfg.phub[i]->freq								/* or frequency */
+					&& (now-sbbs->cfg.phub[i]->last)/60>sbbs->cfg.phub[i]->freq)
+				|| (sbbs->cfg.phub[i]->time
+					&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.phub[i]->time
+				&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
+				&& sbbs->cfg.phub[i]->days&(1<<now_tm.tm_wday))) {
+
+				sbbs->cfg.phub[i]->last=time(NULL);
+				sprintf(str,"%spnet.dab",sbbs->cfg.ctrl_dir);
+				if((file=sbbs->nopen(str,O_WRONLY))==-1) {
+					sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
+					break; 
+				}
+				lseek(file,sizeof(time_t)*i,SEEK_SET);
+				write(file,&sbbs->cfg.phub[i]->last,sizeof(time_t));
+				close(file);
+
+				if(sbbs->cfg.phub[i]->call[0]) {
+					sbbs->cfg.node_num=sbbs->cfg.phub[i]->node;
+					if(sbbs->cfg.node_num<1) 
+						sbbs->cfg.node_num=1;
+					strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
+					eprintf("PostLink Network call-out: %s",sbbs->cfg.phub[i]->name); 
+					sbbs->online=ON_LOCAL;
+					sbbs->external(
+						 sbbs->cmdstr(sbbs->cfg.phub[i]->call,nulstr,nulstr,NULL)
+						,EX_OFFLINE|EX_SH);	/* sh for Unix perl scripts */
+				} 
+			}
+		}
+
+		/* Timed Events */
+		for(i=0;i<sbbs->cfg.total_events;i++) {
+			if(!sbbs->cfg.event[i]->node 
+				|| sbbs->cfg.event[i]->node>sbbs->cfg.sys_nodes)
+				continue;	// ignore events for invalid nodes
+
+			if((sbbs->cfg.event[i]->node<first_node
+				|| sbbs->cfg.event[i]->node>last_node)
+				&& !(sbbs->cfg.event[i]->misc&EVENT_EXCL))
+				continue;	// ignore non-exclusive events for other instances
+
+			if(localtime_r(&sbbs->cfg.event[i]->last,&tm)==NULL)
+				memset(&tm,0,sizeof(tm));
+			if(sbbs->cfg.event[i]->last==-1 ||
+				(((sbbs->cfg.event[i]->freq 
+					&& (now-sbbs->cfg.event[i]->last)/60>sbbs->cfg.event[i]->freq)
+				|| 	(!sbbs->cfg.event[i]->freq 
+					&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.event[i]->time
+				&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
+				&& sbbs->cfg.event[i]->days&(1<<now_tm.tm_wday)
+				&& (sbbs->cfg.event[i]->mdays==0 
+					|| sbbs->cfg.event[i]->mdays&(1<<now_tm.tm_mday)))) 
+			{
+				if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */
+
+					if(sbbs->cfg.event[i]->node<first_node
+						|| sbbs->cfg.event[i]->node>last_node) {
+						eprintf("Waiting for node %d to run timed event: %s"
+							,sbbs->cfg.event[i]->node,sbbs->cfg.event[i]->code);
+						lastnodechk=0;	 /* really last event time check */
+						start=time(NULL);
+						while(!sbbs->terminated) {
+							mswait(1000);
+							now=time(NULL);
+							if(now-lastnodechk<10)
+								continue;
+							for(j=first_node;j<=last_node;j++) {
+								if(sbbs->getnodedat(j,&node,1)!=0)
+									continue;
+								if(node.status==NODE_WFC)
+									node.status=NODE_EVENT_LIMBO;
+								node.aux=sbbs->cfg.event[i]->node;
+								sbbs->putnodedat(j,&node);
+							}
+
+							lastnodechk=now;
+							sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir);
+							if((file=sbbs->nopen(str,O_RDONLY))==-1) {
+								sbbs->errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
+								sbbs->cfg.event[i]->last=now;
+								continue; 
+							}
+							lseek(file,(long)i*4L,SEEK_SET);
+							read(file,&sbbs->cfg.event[i]->last,sizeof(time_t));
+							close(file);
+							if(now-sbbs->cfg.event[i]->last<(60*60))	/* event is done */
+								break; 
+							if(now-start>(90*60)) {
+								eprintf("!TIMEOUT waiting for event to complete");
+								break;
+							}
+						}
+						sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
+						if(fexistcase(str))
+							remove(str);
+						sbbs->cfg.event[i]->last=now;
+					} else {	// Exclusive event to run on a node under our control
+						eprintf("Waiting for all nodes to become inactive before "
+							"running timed event: %s",sbbs->cfg.event[i]->code);
+						lastnodechk=0;
+						start=time(NULL);
+						while(!sbbs->terminated) {
+							mswait(1000);
+							now=time(NULL);
+							if(now-lastnodechk<10)
+								continue;
+							lastnodechk=now;
+							// Check/change the status of the nodes that we're in control of
+							for(j=first_node;j<=last_node;j++) {
+								if(sbbs->getnodedat(j,&node,1)!=0)
+									continue;
+								if(node.status==NODE_WFC) {
+									if(j==sbbs->cfg.event[i]->node)
+										node.status=NODE_EVENT_WAITING;
+									else
+										node.status=NODE_EVENT_LIMBO;
+									node.aux=sbbs->cfg.event[i]->node;
+								}
+								sbbs->putnodedat(j,&node);
+							}
+
+							for(j=1;j<=sbbs->cfg.sys_nodes;j++) {
+								if(sbbs->getnodedat(j,&node,0)!=0)
+									continue;
+								if(j==sbbs->cfg.event[i]->node) {
+									if(node.status!=NODE_EVENT_WAITING)
+										break;
+								} else {
+									if(node.status!=NODE_OFFLINE
+										&& node.status!=NODE_EVENT_LIMBO)
+										break; 
+								}
+							}
+							if(j>sbbs->cfg.sys_nodes) /* all nodes either offline or in limbo */
+								break;
+							eprintf("Waiting for node %d (status=%d)",j,node.status);
+							if(now-start>(90*60)) {
+								eprintf("!TIMEOUT waiting for node %d to become inactive",j);
+								break;
+							}
+						} 
+					} 
+				}
+#if 0 // removed Jun-23-2002
+				else {	/* non-exclusive */
+					sbbs->getnodedat(sbbs->cfg.event[i]->node,&node,0);
+					if(node.status!=NODE_WFC)
+						continue;
+				}
+#endif
+				if(sbbs->cfg.event[i]->node<first_node 
+					|| sbbs->cfg.event[i]->node>last_node) {
+					eprintf("Changing node status for nodes %d through %d to WFC"
+						,first_node,last_node);
+					sbbs->cfg.event[i]->last=now;
+					for(j=first_node;j<=last_node;j++) {
+						node.status=NODE_INVALID_STATUS;
+						if(sbbs->getnodedat(j,&node,1)!=0)
+							continue;
+						node.status=NODE_WFC;
+						sbbs->putnodedat(j,&node);
+					}
+				}
+				else {
+					sbbs->cfg.node_num=sbbs->cfg.event[i]->node;
+					if(sbbs->cfg.node_num<1) 
+						sbbs->cfg.node_num=1;
+					strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
+				
+					sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
+					if(fexistcase(str))
+						remove(str);
+					if(sbbs->cfg.event[i]->misc&EVENT_EXCL) {
+						sbbs->getnodedat(sbbs->cfg.event[i]->node,&node,1);
+						node.status=NODE_EVENT_RUNNING;
+						sbbs->putnodedat(sbbs->cfg.event[i]->node,&node);
+					}
+					strcpy(str,sbbs->cfg.event[i]->code);
+					eprintf("Running timed event: %s",strupr(str));
+					int ex_mode = EX_OFFLINE;
+					if(!(sbbs->cfg.event[i]->misc&EVENT_EXCL)
+						&& sbbs->cfg.event[i]->misc&EX_BG)
+						ex_mode |= EX_BG;
+					if(sbbs->cfg.event[i]->misc&XTRN_SH)
+						ex_mode |= EX_SH;
+					ex_mode|=(sbbs->cfg.event[i]->misc&EX_NATIVE);
+					sbbs->online=ON_LOCAL;
+					sbbs->external(
+						 sbbs->cmdstr(sbbs->cfg.event[i]->cmd,nulstr,sbbs->cfg.event[i]->dir,NULL)
+						,ex_mode
+						,sbbs->cfg.event[i]->dir);
+					sbbs->cfg.event[i]->last=time(NULL);
+					sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir);
+					if((file=sbbs->nopen(str,O_WRONLY))==-1) {
+						sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
+						break; 
+					}
+					lseek(file,(long)i*4L,SEEK_SET);
+					write(file,&sbbs->cfg.event[i]->last,sizeof(time_t));
+					close(file);
+
+					if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */
+						// Check/change the status of the nodes that we're in control of
+						for(j=first_node;j<=last_node;j++) {
+							node.status=NODE_INVALID_STATUS;
+							if(sbbs->getnodedat(j,&node,1)!=0)
+								continue;
+							node.status=NODE_WFC;
+							sbbs->putnodedat(j,&node);
+						}
+					}
+				} 
+			} 
+		}
+		event_mutex_locked=false;
+		pthread_mutex_unlock(&event_mutex);
+
+		mswait(1000);
+	}
+	sbbs->cfg.node_num=0;
+    sbbs->event_thread_running = false;
+
+	thread_down();
+	eprintf("BBS Event thread terminated (%u threads remain)", thread_count);
+}
+
+
+//****************************************************************************
+sbbs_t::sbbs_t(ushort node_num, DWORD addr, char* name, SOCKET sd,
+			   scfg_t* global_cfg, char* global_text[], client_t* client_info)
+{
+	char	nodestr[32];
+	uint	i;
+
+    if(node_num)
+    	sprintf(nodestr,"Node %d",node_num);
+    else
+    	strcpy(nodestr,name);
+
+	lprintf("%s constructor using socket %d (settings=%lx)"
+		, nodestr, sd, global_cfg->node_misc);
+
+	startup = ::startup;	// Convert from global to class member
+
+	memcpy(&cfg, global_cfg, sizeof(cfg));
+
+	cfg.node_num=node_num;
+	if(node_num>0) {
+		strcpy(cfg.node_dir, cfg.node_path[node_num-1]);
+		prep_dir(cfg.node_dir, cfg.temp_dir, sizeof(cfg.temp_dir));
+	} else if(startup->temp_dir[0]) {
+		SAFECOPY(cfg.temp_dir,startup->temp_dir);
+	} else
+    	prep_dir(cfg.data_dir, cfg.temp_dir, sizeof(cfg.temp_dir));
+
+	terminated = false;
+	event_thread_running = false;
+    input_thread_running = false;
+    output_thread_running = false;
+	input_thread_mutex_locked = false;
+
+	if(client_info==NULL)
+		memset(&client,0,sizeof(client));
+	else
+		memcpy(&client,client_info,sizeof(client));
+	client_addr = addr;
+	client_socket = sd;
+	SAFECOPY(client_name, name);
+	client_socket_dup=INVALID_SOCKET;
+	client_ident[0]=0;
+
+	/* Init some important variables */
+
+	rio_abortable=false;
+
+	console = 0;
+	online = 0;
+	outchar_esc = 0;
+	nodemsg_inside = 0;	/* allows single nest */
+	hotkey_inside = 0;	/* allows single nest */
+	event_time = 0;
+	event_code = nulstr;
+	nodesync_inside = false;
+	errorlog_inside = false;
+	errormsg_inside = false;
+	gettimeleft_inside = false;
+	uselect_total = 0;
+	lbuflen = 0;
+	connection="Telnet";
+    telnet_cmdlen=0;
+	telnet_mode=0;
+	telnet_last_rxch=0;
+	sys_status=lncntr=tos=criterrs=keybufbot=keybuftop=lbuflen=slcnt=0L;
+	curatr=LIGHTGRAY;
+	errorlevel=0;
+	logcol=1;
+	logfile_fp=NULL;
+	nodefile=-1;
+	node_ext=-1;
+	nodefile_fp=NULL;
+	node_ext_fp=NULL;
+	current_msg=NULL;
+
+#ifdef JAVASCRIPT
+	js_runtime=NULL;	/* runtime */
+	js_cx=NULL;			/* context */
+#endif
+
+	for(i=0;i<TOTAL_TEXT;i++)
+		text[i]=text_sav[i]=global_text[i];
+
+	memset(&main_csi,0,sizeof(main_csi));
+	memset(&thisnode,0,sizeof(thisnode));
+	memset(&useron,0,sizeof(useron));
+	memset(&inbuf,0,sizeof(inbuf));
+	memset(&outbuf,0,sizeof(outbuf));
+	memset(&smb,0,sizeof(smb));
+
+	global_str_vars=0;
+	global_str_var=NULL;
+	global_str_var_name=NULL;
+	global_int_vars=0;
+	global_int_var=NULL;
+	global_int_var_name=NULL;
+	sysvar_li=0;
+	sysvar_pi=0;
+
+	cursub=NULL;
+	usrgrp=NULL;
+	usrsubs=NULL;
+	usrsub=NULL;
+	usrgrp_total=0;
+
+	subscan=NULL;
+
+	curdir=NULL;
+	usrlib=NULL;
+	usrdirs=NULL;
+	usrdir=NULL;
+	usrlib_total=0;
+
+	batup_desc=NULL;
+	batup_name=NULL;
+	batup_misc=NULL;
+	batup_dir=NULL;
+	batup_alt=NULL;
+
+	batdn_name=NULL;
+	batdn_dir=NULL;
+	batdn_offset=NULL;
+	batdn_size=NULL;
+	batdn_alt=NULL;
+	batdn_cdt=NULL;
+
+	spymsg("Connected");
+}
+
+//****************************************************************************
+bool sbbs_t::init()
+{
+	char		str[MAX_PATH+1];
+	char		tmp[128];
+	int			result;
+	uint		i,j,k,l;
+	node_t		node;
+	socklen_t	addr_len;
+	SOCKADDR_IN	addr;
+
+	if(cfg.node_num>0) {
+		RingBufInit(&inbuf, IO_THREAD_BUF_SIZE);
+		node_inbuf[cfg.node_num-1]=&inbuf;
+	}
+
+    RingBufInit(&outbuf, IO_THREAD_BUF_SIZE);
+	outbuf.highwater_mark=startup->outbuf_highwater_mark;
+
+	if(cfg.node_num && client_socket!=INVALID_SOCKET) {
+
+#ifdef _WIN32
+		if(!DuplicateHandle(GetCurrentProcess(),
+			(HANDLE)client_socket,
+			GetCurrentProcess(),
+			(HANDLE*)&client_socket_dup,
+			0,
+			TRUE, // Inheritable
+			DUPLICATE_SAME_ACCESS)) {
+			errormsg(WHERE,ERR_CREATE,"duplicate socket handle",client_socket);
+			return(false);
+		}
+#endif
+
+		addr_len=sizeof(addr);
+		if((result=getsockname(client_socket, (struct sockaddr *)&addr,&addr_len))!=0) {
+			lprintf("Node %d !ERROR %d (%d) getting address/port"
+				,cfg.node_num, result, ERROR_VALUE);
+			return(false);
+		} 
+		lprintf("Node %d attached to local interface %s port %d"
+			,cfg.node_num, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+
+		local_addr=addr.sin_addr.s_addr;
+	}
+
+	comspec=getenv(
+#ifdef __unix__
+		"SHELL"
+#else
+		"COMSPEC"
+#endif
+		);
+	if(comspec==NULL) {
+		errormsg(WHERE, ERR_CHK, "shell/comspec", 0);
+		return(false);
+	}
+
+	md(cfg.temp_dir);
+
+	/* Shared NODE files */
+	sprintf(str,"%s%s",cfg.ctrl_dir,"node.dab");
+	if((nodefile=nopen(str,O_DENYNONE|O_RDWR|O_CREAT))==-1) {
+		errormsg(WHERE, ERR_OPEN, str, cfg.node_num);
+		return(false); 
+	}
+	memset(&node,0,sizeof(node_t));  /* write NULL to node struct */
+	node.status=NODE_OFFLINE;
+	while(filelength(nodefile)<(long)(cfg.sys_nodes*sizeof(node_t))) {
+		lseek(nodefile,0L,SEEK_END);
+		if(write(nodefile,&node,sizeof(node_t))!=sizeof(node_t)) {
+			errormsg(WHERE,ERR_WRITE,str,sizeof(node_t));
+			break; 
+		}
+	}
+	for(i=0; cfg.node_num>0 && i<LOOP_NODEDAB; i++) {
+		if(lock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t))==0) {
+			unlock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t));
+			break;
+		}
+		mswait(100);
+	}
+	if(cfg.node_misc&NM_CLOSENODEDAB) {
+		close(nodefile);
+		nodefile=-1;
+	}
+
+	if(i>=LOOP_NODEDAB) {
+		errormsg(WHERE, ERR_LOCK, str, cfg.node_num);
+		return(false); 
+	}
+
+	if(cfg.node_num) {
+		sprintf(str,"%snode.log",cfg.node_dir);
+		if((logfile_fp=fopen(str,"a+b"))==NULL) {
+			errormsg(WHERE, ERR_OPEN, str, 0);
+			lprintf("Perhaps this node is already running");
+			return(false); 
+		}
+
+		if(filelength(fileno(logfile_fp))) {
+			log(crlf);
+			now=time(NULL);
+			struct tm tm;
+			localtime_r(&now,&tm);
+			sprintf(str,"%s  %s %s %02d %u  "
+				"End of preexisting log entry (possible crash)"
+				,hhmmtostr(&cfg,&tm,tmp)
+				,wday[tm.tm_wday]
+				,mon[tm.tm_mon],tm.tm_mday,tm.tm_year+1900);
+			logline("L!",str);
+			log(crlf);
+			catsyslog(1); 
+		}
+
+		getnodedat(cfg.node_num,&thisnode,1);
+		/* thisnode.status=0; */
+		thisnode.action=0;
+		thisnode.useron=0;
+		thisnode.aux=0;
+		thisnode.misc&=(NODE_EVENT|NODE_LOCK|NODE_RRUN);
+		criterrs=thisnode.errors;
+		putnodedat(cfg.node_num,&thisnode);
+	}
+
+/** Put in if(cfg.node_num) ? (not needed for server and event threads) */
+	backout();
+
+	/* Reset COMMAND SHELL */
+
+	main_csi.str=(char *)MALLOC(1024);
+	if(main_csi.str==NULL) {
+		errormsg(WHERE,ERR_ALLOC,"main_csi.str",1024);
+		return(false); 
+	}
+	memset(main_csi.str,0,1024);
+/***/
+
+	if(cfg.total_grps) {
+
+		usrgrp_total = cfg.total_grps;
+
+		if((cursub=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "cursub", sizeof(uint)*usrgrp_total);
+			return(false);
+		}
+
+		if((usrgrp=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "usrgrp", sizeof(uint)*usrgrp_total);
+			return(false);
+		}
+
+		if((usrsubs=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "usrsubs", sizeof(uint)*usrgrp_total);
+			return(false);
+		}
+
+		if((usrsub=(uint **)calloc(usrgrp_total,sizeof(uint *)))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "usrsub", sizeof(uint)*usrgrp_total);
+			return(false);
+		}
+ 
+		if((subscan=(subscan_t *)MALLOC(sizeof(subscan_t)*cfg.total_subs))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "subscan", sizeof(subscan_t)*cfg.total_subs);
+			return(false);
+		}
+	}
+
+	for(i=l=0;i<(uint)cfg.total_grps;i++) {
+		for(j=k=0;j<cfg.total_subs;j++)
+			if(cfg.sub[j]->grp==i)
+				k++;	/* k = number of subs per grp[i] */
+		if(k>l) l=k;  	/* l = the largest number of subs per grp */
+	}
+	if(l)
+		for(i=0;i<cfg.total_grps;i++)
+			if((usrsub[i]=(uint *)MALLOC(sizeof(uint)*l))==NULL) {
+				errormsg(WHERE, ERR_ALLOC, "usrsub[x]", sizeof(uint)*l);
+				return(false);
+			}
+
+	if(cfg.total_libs) {
+
+		usrlib_total = cfg.total_libs;
+
+		if((curdir=(uint *)MALLOC(sizeof(uint)*usrlib_total))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "curdir", sizeof(uint)*usrlib_total);
+			return(false);
+		}
+
+		if((usrlib=(uint *)MALLOC(sizeof(uint)*usrlib_total))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "usrlib", sizeof(uint)*usrlib_total);
+			return(false);
+		}
+
+		if((usrdirs=(uint *)MALLOC(sizeof(uint)*usrlib_total))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "usrdirs", sizeof(uint)*usrlib_total);
+			return(false);
+		}
+
+		if((usrdir=(uint **)calloc(usrlib_total,sizeof(uint *)))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "usrdir", sizeof(uint)*usrlib_total);
+			return(false);
+		}
+	}
+
+	for(i=l=0;i<cfg.total_libs;i++) {
+		for(j=k=0;j<cfg.total_dirs;j++)
+			if(cfg.dir[j]->lib==i)
+				k++;
+		if(k>l) l=k; 	/* l = largest number of dirs in a lib */
+	}
+	if(l) {
+		l++;	/* for temp dir */
+		for(i=0;i<cfg.total_libs;i++)
+			if((usrdir[i]=(uint *)MALLOC(sizeof(uint)*l))==NULL) {
+				errormsg(WHERE, ERR_ALLOC, "usrdir[x]", sizeof(uint)*l);
+				return(false);
+			}
+	}
+ 
+	if(cfg.max_batup) {
+
+		if((batup_desc=(char **)MALLOC(sizeof(char *)*cfg.max_batup))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batup_desc", sizeof(char *)*cfg.max_batup);
+			return(false);
+		}
+		if((batup_name=(char **)MALLOC(sizeof(char *)*cfg.max_batup))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batup_name", sizeof(char *)*cfg.max_batup);
+			return(false);
+		}
+		if((batup_misc=(long *)MALLOC(sizeof(long)*cfg.max_batup))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batup_misc", sizeof(char *)*cfg.max_batup);
+			return(false);
+		}
+		if((batup_dir=(uint *)MALLOC(sizeof(uint)*cfg.max_batup))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batup_dir", sizeof(char *)*cfg.max_batup);
+			return(false);
+		}
+		if((batup_alt=(ushort *)MALLOC(sizeof(ushort)*cfg.max_batup))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batup_alt", sizeof(char *)*cfg.max_batup);
+			return(false);
+		}
+		for(i=0;i<cfg.max_batup;i++) {
+			if((batup_desc[i]=(char *)MALLOC(59))==NULL) {
+				errormsg(WHERE, ERR_ALLOC, "batup_desc[x]", 59);
+				return(false);
+			}
+			if((batup_name[i]=(char *)MALLOC(13))==NULL) {
+				errormsg(WHERE, ERR_ALLOC, "batup_name[x]", 13);
+				return(false);
+			} 
+		} 
+	}
+
+	if(cfg.max_batdn) {
+
+		if((batdn_name=(char **)MALLOC(sizeof(char *)*cfg.max_batdn))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batdn_name", sizeof(char *)*cfg.max_batdn);
+			return(false);
+		}
+		if((batdn_dir=(uint *)MALLOC(sizeof(uint)*cfg.max_batdn))==NULL)  {
+			errormsg(WHERE, ERR_ALLOC, "batdn_dir", sizeof(uint)*cfg.max_batdn);
+			return(false);
+		}
+		if((batdn_offset=(long *)MALLOC(sizeof(long)*cfg.max_batdn))==NULL)  {
+			errormsg(WHERE, ERR_ALLOC, "batdn_offset", sizeof(long)*cfg.max_batdn);
+			return(false);
+		}
+		if((batdn_size=(ulong *)MALLOC(sizeof(ulong)*cfg.max_batdn))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batdn_size", sizeof(ulong)*cfg.max_batdn);
+			return(false);
+		}
+		if((batdn_cdt=(ulong *)MALLOC(sizeof(ulong)*cfg.max_batdn))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batdn_cdt", sizeof(long)*cfg.max_batdn);
+			return(false);
+		}
+		if((batdn_alt=(ushort *)MALLOC(sizeof(ushort)*cfg.max_batdn))==NULL) {
+			errormsg(WHERE, ERR_ALLOC, "batdn_alt", sizeof(ushort)*cfg.max_batdn);
+			return(false);
+		}
+		for(i=0;i<cfg.max_batdn;i++)
+			if((batdn_name[i]=(char *)MALLOC(13))==NULL) {
+				errormsg(WHERE, ERR_ALLOC, "batdn_name[x]", 13);
+				return(false);
+			} 
+	}
+
+	reset_logon_vars();
+
+	online=ON_REMOTE;
+
+	return(true);
+}
+
+//****************************************************************************
+sbbs_t::~sbbs_t()
+{
+	uint i;
+	char node[32];
+
+    if(cfg.node_num)
+    	sprintf(node,"Node %d", cfg.node_num);
+    else
+    	strcpy(node,client_name);
+#ifdef _DEBUG
+	lprintf("%s destructor begin", node);
+#endif
+
+//	if(!cfg.node_num)
+//		rmdir(cfg.temp_dir);
+
+	if(client_socket_dup!=INVALID_SOCKET)
+		closesocket(client_socket_dup);	/* close duplicate handle */
+
+	if(cfg.node_num>0)
+		node_inbuf[cfg.node_num-1]=NULL;
+	if(cfg.node_num>0 && !input_thread_running)
+		RingBufDispose(&inbuf);
+	if(!output_thread_running)
+		RingBufDispose(&outbuf);
+
+	/* Close all open files */
+	if(nodefile!=-1) {
+		close(nodefile);
+		nodefile=-1;
+	}
+	if(node_ext!=-1) {
+		close(node_ext);
+		node_ext=-1;
+	}
+	if(logfile_fp!=NULL) {
+		fclose(logfile_fp);
+		logfile_fp=NULL;
+	}
+
+	/********************************/
+	/* Free allocated class members */
+	/********************************/
+
+#ifdef JAVASCRIPT
+	/* Free Context */
+	if(js_cx!=NULL) {	
+		lprintf("%s JavaScript: Destroying context",node);
+		JS_DestroyContext(js_cx);
+		js_cx=NULL;
+	}
+
+	if(js_runtime!=NULL) {
+		lprintf("%s JavaScript: Destroying runtime",node);
+		JS_DestroyRuntime(js_runtime);
+		js_runtime=NULL;
+	}
+#endif
+
+	/* Reset text.dat */
+
+	for(i=0;i<TOTAL_TEXT && text!=NULL;i++)
+		if(text[i]!=text_sav[i]) {
+			if(text[i]!=nulstr)
+				FREE(text[i]); 
+		}
+
+	/* Global command shell vars */
+
+	freevars(&main_csi);
+	clearvars(&main_csi);
+	FREE_AND_NULL(main_csi.str);	/* crash */
+	FREE_AND_NULL(main_csi.cs);
+
+	for(i=0;i<global_str_vars && global_str_var!=NULL;i++)
+		FREE_AND_NULL(global_str_var[i]);
+
+	FREE_AND_NULL(global_str_var);
+	FREE_AND_NULL(global_str_var_name);
+	global_str_vars=0;
+
+	FREE_AND_NULL(global_int_var);
+	FREE_AND_NULL(global_int_var_name);
+	global_int_vars=0;
+
+	/* Sub-board variables */
+	for(i=0;i<usrgrp_total && usrsub!=NULL;i++)
+		FREE_AND_NULL(usrsub[i]);	/* exception here (ptr=0xfdfdfdfd) on exit July-10-2002 */
+
+	FREE_AND_NULL(cursub);
+	FREE_AND_NULL(usrgrp);
+	FREE_AND_NULL(usrsubs);
+	FREE_AND_NULL(usrsub);
+	FREE_AND_NULL(subscan);
+
+	/* File Directory variables */
+	for(i=0;i<usrlib_total && usrdir!=NULL;i++)
+		FREE_AND_NULL(usrdir[i]);
+
+	FREE_AND_NULL(curdir);
+	FREE_AND_NULL(usrlib);
+	FREE_AND_NULL(usrdirs);
+	FREE_AND_NULL(usrdir);
+
+	/* Batch upload vars */
+	for(i=0;i<cfg.max_batup && batup_desc!=NULL && batup_name!=NULL;i++) {
+		FREE_AND_NULL(batup_desc[i]);
+		FREE_AND_NULL(batup_name[i]);
+	}
+
+	FREE_AND_NULL(batup_desc);
+	FREE_AND_NULL(batup_name);
+	FREE_AND_NULL(batup_misc);
+	FREE_AND_NULL(batup_dir);
+	FREE_AND_NULL(batup_alt);
+
+	/* Batch download vars */
+	for(i=0;i<cfg.max_batdn && batdn_name!=NULL;i++)
+		FREE_AND_NULL(batdn_name[i]); 
+
+	FREE_AND_NULL(batdn_name);
+	FREE_AND_NULL(batdn_dir);
+	FREE_AND_NULL(batdn_offset);
+	FREE_AND_NULL(batdn_size);
+	FREE_AND_NULL(batdn_cdt);
+	FREE_AND_NULL(batdn_alt);
+
+#if 0 && defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
+	if(!_CrtCheckMemory())
+		lprintf("!MEMORY ERRORS REPORTED IN DATA/DEBUG.LOG!");
+#endif
+
+#ifdef _DEBUG
+	lprintf("%s destructor end", node);
+#endif
+}
+
+/****************************************************************************/
+/* Network open function. Opens all files DENYALL and retries LOOP_NOPEN    */
+/* number of times if the attempted file is already open or denying access  */
+/* for some other reason.													*/
+/* All files are opened in BINARY mode, unless O_TEXT access bit is set.	*/
+/****************************************************************************/
+int sbbs_t::nopen(char *str, int access)
+{
+	char logstr[256];
+	int file,share,count=0;
+
+    if(access&O_DENYNONE) {
+        share=SH_DENYNO;
+        access&=~O_DENYNONE; 
+	}
+    else if(access==O_RDONLY) share=SH_DENYWR;
+    else share=SH_DENYRW;
+	if(!(access&O_TEXT))
+		access|=O_BINARY;
+    while(((file=sopen(str,access,share,S_IREAD|S_IWRITE))==-1)
+        && (errno==EACCES || errno==EAGAIN) && count++<LOOP_NOPEN)
+	    mswait(100);
+    if(count>(LOOP_NOPEN/2) && count<=LOOP_NOPEN) {
+        sprintf(logstr,"NOPEN COLLISION - File: \"%s\" Count: %d"
+            ,str,count);
+        logline("!!",logstr); 
+	}
+    if(file==-1 && (errno==EACCES || errno==EAGAIN)) {
+        sprintf(logstr,"NOPEN ACCESS DENIED - File: \"%s\" errno: %d"
+			,str,errno);
+		logline("!!",logstr);
+		bputs("\7\r\nNOPEN: ACCESS DENIED\r\n\7");
+	}
+    return(file);
+}
+
+void sbbs_t::spymsg(char* msg)
+{
+	char str[512];
+	struct in_addr addr;
+
+	if(cfg.node_num<1)
+		return;
+
+	addr.s_addr=client_addr;
+	sprintf(str,"\r\n\r\n*** Spy Message ***\r\nNode %d: %s [%s]\r\n*** %s ***\r\n\r\n"
+		,cfg.node_num,client_name,inet_ntoa(addr),msg);
+	if(startup->node_spybuf!=NULL 
+		&& startup->node_spybuf[cfg.node_num-1]!=NULL) {
+		RingBufWrite(startup->node_spybuf[cfg.node_num-1],(uchar*)str,strlen(str));
+		/* Signal spy output semaphore? */
+		if(startup->node_spysem!=NULL 
+			&& startup->node_spysem[sbbs->cfg.node_num-1]!=NULL)
+			sem_post(startup->node_spysem[sbbs->cfg.node_num-1]);
+	}
+
+	if(cfg.node_num && spy_socket[cfg.node_num-1]!=INVALID_SOCKET) 
+		sendsocket(spy_socket[cfg.node_num-1],str,strlen(str));
+#ifdef __unix__
+	if(cfg.node_num && uspy_socket[cfg.node_num-1]!=INVALID_SOCKET) 
+		sendsocket(uspy_socket[cfg.node_num-1],str,strlen(str));
+#endif
+}
+
+#define MV_BUFLEN	4096
+
+/****************************************************************************/
+/* Moves or copies a file from one dir to another                           */
+/* both 'src' and 'dest' must contain full path and filename                */
+/* returns 0 if successful, -1 if error                                     */
+/****************************************************************************/
+int sbbs_t::mv(char *src, char *dest, char copy)
+{
+	char	str[MAX_PATH+1],*buf,atr=curatr;
+	int		ind,outd;
+	uint	chunk=MV_BUFLEN;
+	ulong	length,l;
+	/* struct ftime ftime; */
+	FILE *inp,*outp;
+
+    if(!stricmp(src,dest))	 /* source and destination are the same! */
+        return(0);
+    if(!fexistcase(src)) {
+        bprintf("\r\n\7MV ERROR: Source doesn't exist\r\n'%s'\r\n"
+            ,src);
+        return(-1); 
+	}
+    if(!copy && fexistcase(dest)) {
+        bprintf("\r\n\7MV ERROR: Destination already exists\r\n'%s'\r\n"
+            ,dest);
+        return(-1); 
+	}
+#ifndef __unix__	/* need to determine if on same mount device */
+    if(!copy && ((src[1]!=':' && dest[1]!=':')
+        || (src[1]==':' && dest[1]==':' && toupper(src[0])==toupper(dest[0])))) {
+        if(rename(src,dest)) {						/* same drive, so move */
+            bprintf("\r\nMV ERROR: Error renaming '%s'"
+                    "\r\n                      to '%s'\r\n\7",src,dest);
+            return(-1); 
+		}
+        return(0); 
+	}
+#endif
+    attr(WHITE);
+    if((ind=nopen(src,O_RDONLY))==-1) {
+        errormsg(WHERE,ERR_OPEN,src,O_RDONLY);
+        return(-1); 
+	}
+    if((inp=fdopen(ind,"rb"))==NULL) {
+        close(ind);
+        errormsg(WHERE,ERR_FDOPEN,str,O_RDONLY);
+        return(-1); 
+	}
+    setvbuf(inp,NULL,_IOFBF,32*1024);
+    if((outd=nopen(dest,O_WRONLY|O_CREAT|O_TRUNC))==-1) {
+        fclose(inp);
+        errormsg(WHERE,ERR_OPEN,dest,O_WRONLY|O_CREAT|O_TRUNC);
+        return(-1); 
+	}
+    if((outp=fdopen(outd,"wb"))==NULL) {
+        close(outd);
+        fclose(inp);
+        errormsg(WHERE,ERR_FDOPEN,dest,O_WRONLY|O_CREAT|O_TRUNC);
+        return(-1); 
+	}
+    setvbuf(outp,NULL,_IOFBF,8*1024);
+    length=filelength(ind);
+    if(!length) {
+        fclose(inp);
+        fclose(outp);
+        errormsg(WHERE,ERR_LEN,src,0);
+        return(-1); 
+	}
+    if((buf=(char *)MALLOC(MV_BUFLEN))==NULL) {
+        fclose(inp);
+        fclose(outp);
+        errormsg(WHERE,ERR_ALLOC,nulstr,MV_BUFLEN);
+        return(-1); 
+	}
+    l=0L;
+    while(l<length) {
+        bprintf("%2lu%%",l ? (long)(100.0/((float)length/l)) : 0L);
+        if(l+chunk>length)
+            chunk=length-l;
+        if(fread(buf,1,chunk,inp)!=chunk) {
+            FREE(buf);
+            fclose(inp);
+            fclose(outp);
+            errormsg(WHERE,ERR_READ,src,chunk);
+            return(-1); 
+		}
+        if(fwrite(buf,1,chunk,outp)!=chunk) {
+            FREE(buf);
+            fclose(inp);
+            fclose(outp);
+            errormsg(WHERE,ERR_WRITE,dest,chunk);
+            return(-1); 
+		}
+        l+=chunk;
+        bputs("\b\b\b"); 
+	}
+    bputs("   \b\b\b");  /* erase it */
+    attr(atr);
+    /* getftime(ind,&ftime);
+    setftime(outd,&ftime); */
+    FREE(buf);
+    fclose(inp);
+    fclose(outp);
+    if(!copy && remove(src)) {
+        errormsg(WHERE,ERR_REMOVE,src,0);
+        return(-1); 
+	}
+    return(0);
+}
+
+/****************************************************************************/
+/* Reads data from dsts.dab into stats structure                            */
+/* If node is zero, reads from ctrl\dsts.dab, otherwise from each node		*/
+/****************************************************************************/
+BOOL DLLCALL getstats(scfg_t* cfg, char node, stats_t* stats)
+{
+    char str[MAX_PATH+1];
+    int file;
+
+    sprintf(str,"%sdsts.dab",node ? cfg->node_path[node-1] : cfg->ctrl_dir);
+    if((file=nopen(str,O_RDONLY))==-1) {
+//        errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
+        return(FALSE); 
+	}
+    lseek(file,4L,SEEK_SET);    /* Skip update time/date */
+    read(file,stats,sizeof(stats_t));
+    close(file);
+	return(TRUE);
+}
+
+
+void sbbs_t::hangup(void)
+{
+	if(client_socket!=INVALID_SOCKET) {
+		mswait(1000);	/* Give socket output buffer time to flush */
+		riosync(0);
+		client_off(client_socket);
+		close_socket(client_socket);
+		client_socket=INVALID_SOCKET;
+	}
+	if(client_socket_dup!=INVALID_SOCKET) {
+		closesocket(client_socket_dup);
+		client_socket_dup=INVALID_SOCKET;
+	}
+	sem_post(&outbuf.sem);
+	online=0;
+}
+
+int sbbs_t::incom(unsigned long timeout)
+{
+	uchar	ch;
+
+#if 0	/* looping version */
+	while(!RingBufRead(&inbuf, &ch, 1))
+		if(sem_trywait_block(&inbuf.sem,timeout)!=0 || sys_status&SS_ABORT)
+			return(NOINP);
+#else
+	if(!RingBufRead(&inbuf, &ch, 1)) {
+		if(sem_trywait_block(&inbuf.sem,timeout)!=0)
+			return(NOINP);
+		if(!RingBufRead(&inbuf, &ch, 1))
+			return(NOINP);
+	}
+#endif
+	return(ch);
+}
+
+int sbbs_t::outcom(uchar ch)
+{
+	if(!RingBufFree(&outbuf))
+		return(TXBOF);
+    if(!RingBufWrite(&outbuf, &ch, 1))
+		return(TXBOF);
+	return(0);
+}
+
+void sbbs_t::putcom(char *str, int len)
+{
+	int i;
+
+    if(!len)
+    	len=strlen(str);
+    for(i=0;i<len && online; i++)
+        outcom(str[i]);
+}
+
+void sbbs_t::riosync(char abortable)
+{
+	if(useron.misc&(RIP|WIP|HTML))	/* don't allow abort with RIP or WIP */
+		abortable=0;			/* mainly because of ANSI cursor position response */
+	if(sys_status&SS_ABORT)		/* no need to sync if already aborting */
+		return;
+	time_t start=time(NULL);
+	while(online && rioctl(TXBC)) {				/* wait up to three minutes for tx buf empty */
+		if(sys_status&SS_ABORT)
+			break;
+#if 0	/* this isn't necessary (or desired) on a TCP/IP connection */
+		if(abortable && rioctl(RXBC)) { 		/* incoming characer */
+			rioctl(IOFO);						/* flush output */
+			sys_status|=SS_ABORT;				/* set abort flag so no pause */
+			break;								/* abort sync */
+		}
+#endif
+		if(time(NULL)-start>180) {				/* timeout */
+			logline("!!","riosync timeout"); 
+			break;
+		}
+		mswait(100);
+	}
+}
+
+/* Legacy Remote I/O Control Interface */
+int sbbs_t::rioctl(ushort action)
+{
+	int		mode;
+	int		state;
+
+	switch(action) {
+		case GVERS: 	/* Get version */
+			return(0x200);
+		case GUART: 	/* Get UART I/O address, not available */
+			return(0xffff);
+		case GIRQN: 	/* Get IRQ number, not available */
+			return((int)client_socket);
+		case GBAUD: 	/* Get current bit rate */
+			return(0xffff);
+		case RXBC:		/* Get receive buffer count */
+			// ulong	cnt;
+			// ioctlsocket (client_socket,FIONREAD,&cnt);
+ 			return(/* cnt+ */RingBufFull(&inbuf));
+		case RXBS:		/* Get receive buffer size */
+			return(inbuf.size);
+		case TXBC:		/* Get transmit buffer count */
+			return(RingBufFull(&outbuf));
+		case TXBS:		/* Get transmit buffer size */
+			return(outbuf.size);
+		case TXBF:		/* Get transmit buffer free space */
+ 			return(RingBufFree(&outbuf));
+		case IOMODE:
+			mode=0;
+			if(rio_abortable)
+				mode|=ABORT;
+			return(mode);
+		case IOSTATE:
+			state=0;
+			if(sys_status&SS_ABORT)
+				state|=ABORT;
+			return(state);
+		case IOFI:		/* Flush input buffer */
+			RingBufReInit(&inbuf);
+			break;
+		case IOFO:		/* Flush output buffer */
+    		RingBufReInit(&outbuf);
+			break;
+		case IOFB:		/* Flush both buffers */
+			RingBufReInit(&inbuf);
+			RingBufReInit(&outbuf);
+			break;
+		case LFN81:
+		case LFE71:
+		case FIFOCTL:
+			return(0);
+		}
+
+	if((action&0xff)==IOSM) {	/* Get/Set/Clear mode */
+		if(action&ABORT)
+			rio_abortable=true;
+		return(0); 
+	}
+
+	if((action&0xff)==IOCM) {	/* Get/Set/Clear mode */
+		if(action&ABORT)
+			rio_abortable=false;
+		return(0); 
+	}
+
+	if((action&0xff)==IOSS) {	/* Set state */
+		if(action&ABORT)
+			sys_status|=SS_ABORT;
+		return(0); 
+	}
+
+	if((action&0xff)==IOCS) {	/* Clear state */
+		if(action&ABORT)
+			sys_status&=~SS_ABORT;
+		return(0); 
+	}
+
+	return(0);
+}
+
+void sbbs_t::reset_logon_vars(void)
+{
+	int i;
+
+    /* bools */
+    qwklogon=false;
+
+    sys_status&=~(SS_USERON|SS_TMPSYSOP|SS_LCHAT|SS_ABORT
+        |SS_PAUSEON|SS_PAUSEOFF|SS_EVENT|SS_NEWUSER|SS_NEWDAY);
+    cid[0]=0;
+    wordwrap[0]=0;
+    question[0]=0;
+    menu_dir[0]=0;
+    menu_file[0]=0;
+    rows=0;
+    lncntr=0;
+    autoterm=0;
+    keybufbot=keybuftop=lbuflen=0;
+    slcnt=0;
+    altul=0;
+    timeleft_warn=0;
+    logon_uls=logon_ulb=logon_dls=logon_dlb=0;
+    logon_posts=logon_emails=logon_fbacks=0;
+    batdn_total=batup_total=0;
+    usrgrps=usrlibs=0;
+    curgrp=curlib=0;
+    for(i=0;i<cfg.total_libs;i++)
+        curdir[i]=0;
+    for(i=0;i<cfg.total_grps;i++)
+        cursub[i]=0;
+	cur_cps=3000;
+    cur_rate=30000;
+    dte_rate=38400;
+	main_cmds=xfer_cmds=posts_read=0;
+	lastnodemsg=0;
+	lastnodemsguser[0]=0;
+}
+
+/****************************************************************************/
+/* Writes NODE.LOG at end of SYSTEM.LOG										*/
+/****************************************************************************/
+void sbbs_t::catsyslog(int crash)
+{
+	char str[MAX_PATH+1];
+	char HUGE16 *buf;
+	int  i,file;
+	long length;
+	struct tm tm;
+
+	if(logfile_fp==NULL) {
+		sprintf(str,"%snode.log",cfg.node_dir);
+		if((logfile_fp=fopen(str,"rb"))==NULL) {
+			errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
+			return; 
+		}
+	}
+	length=ftell(logfile_fp);
+	if(length) {
+		if((buf=(char HUGE16 *)LMALLOC(length))==NULL) {
+			errormsg(WHERE,ERR_ALLOC,str,length);
+			return; 
+		}
+		rewind(logfile_fp);
+		if(fread(buf,1,length,logfile_fp)!=(size_t)length) {
+			errormsg(WHERE,ERR_READ,"log file",length);
+			FREE((char *)buf);
+			return; 
+		}
+		now=time(NULL);
+		localtime_r(&now,&tm);
+		sprintf(str,"%slogs/%2.2d%2.2d%2.2d.log",cfg.logs_dir,tm.tm_mon+1,tm.tm_mday
+			,TM_YEAR(tm.tm_year));
+		if((file=nopen(str,O_WRONLY|O_APPEND|O_CREAT))==-1) {
+			errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT);
+			FREE((char *)buf);
+			return; 
+		}
+		if(lwrite(file,buf,length)!=length) {
+			close(file);
+			errormsg(WHERE,ERR_WRITE,str,length);
+			FREE((char *)buf);
+			return; 
+		}
+		close(file);
+		if(crash) {
+			for(i=0;i<2;i++) {
+				sprintf(str,"%scrash.log",i ? cfg.logs_dir : cfg.node_dir);
+				if((file=nopen(str,O_WRONLY|O_APPEND|O_CREAT))==-1) {
+					errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT);
+					FREE((char *)buf);
+					return; 
+				}
+				if(lwrite(file,buf,length)!=length) {
+					close(file);
+					errormsg(WHERE,ERR_WRITE,str,length);
+					FREE((char *)buf);
+					return; 
+				}
+				close(file); 
+			} 
+		}
+		FREE((char *)buf); 
+	}
+
+	fclose(logfile_fp);
+
+	sprintf(str,"%snode.log",cfg.node_dir);
+	if((logfile_fp=fopen(str,"w+b"))==NULL) /* Truncate NODE.LOG */
+		errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_TRUNC);
+}
+
+
+void sbbs_t::logoffstats()
+{
+    char str[MAX_PATH+1];
+    int i,file;
+    stats_t stats;
+
+	if(REALSYSOP && !(cfg.sys_misc&SM_SYSSTAT))
+		return;
+	
+	for(i=0;i<2;i++) {
+		sprintf(str,"%sdsts.dab",i ? cfg.ctrl_dir : cfg.node_dir);
+		if((file=nopen(str,O_RDWR))==-1) {
+			errormsg(WHERE,ERR_OPEN,str,O_RDWR);
+			return; 
+		}
+		memset(&stats,0,sizeof(stats));
+		lseek(file,4L,SEEK_SET);   /* Skip timestamp, logons and logons today */
+		read(file,&stats,sizeof(stats));  
+
+		if(!(useron.rest&FLAG('Q'))) {	/* Don't count QWKnet nodes */
+			stats.timeon+=(now-logontime)/60;
+			stats.ttoday+=(now-logontime)/60;
+			stats.ptoday+=logon_posts;
+		}
+		stats.uls+=logon_uls;
+		stats.ulb+=logon_ulb;
+		stats.dls+=logon_dls;
+		stats.dlb+=logon_dlb;
+		stats.etoday+=logon_emails;
+		stats.ftoday+=logon_fbacks;
+
+#if 0 // This is now handled in newuserdat()
+		if(sys_status&SS_NEWUSER)
+			stats.nusers++;
+#endif
+
+		lseek(file,4L,SEEK_SET);
+		write(file,&stats,sizeof(stats));
+		close(file); 
+	}
+}
+
+void node_thread(void* arg)
+{
+	char			str[128];
+	char			uname[LEN_ALIAS+1];
+	int				file;
+	uint			i,j;
+	uint			curshell=0;
+	time_t			now;
+	node_t			node;
+	user_t			user;
+	sbbs_t*			sbbs = (sbbs_t*) arg;
+
+	update_clients();
+	thread_up(TRUE /* setuid */);
+
+#ifdef _DEBUG
+	lprintf("Node %d thread started",sbbs->cfg.node_num);
+#endif
+
+	srand(time(NULL));	/* Seed random number generator */
+	sbbs_random(10);	/* Throw away first number */
+
+#ifdef JAVASCRIPT
+	if(!(startup->options&BBS_OPT_NO_JAVASCRIPT)) {
+		if(!sbbs->js_init())	/* This must be done in the context of the node thread */
+			lprintf("Node %d !JavaScript Initialization FAILURE",sbbs->cfg.node_num);
+	}
+#endif
+
+	if(sbbs->answer()) {
+
+		if(sbbs->qwklogon) {
+			sbbs->getsmsg(sbbs->useron.number);
+			sbbs->qwk_sec();
+		} else while(sbbs->useron.number 
+			&& (sbbs->main_csi.misc&CS_OFFLINE_EXEC || sbbs->online)) {
+
+			if(!sbbs->main_csi.cs || curshell!=sbbs->useron.shell) {
+				if(sbbs->useron.shell>=sbbs->cfg.total_shells)
+					sbbs->useron.shell=0;
+				sprintf(str,"%s%s.bin",sbbs->cfg.mods_dir
+					,sbbs->cfg.shell[sbbs->useron.shell]->code);
+				if(sbbs->cfg.mods_dir[0]==0 || !fexistcase(str))
+					sprintf(str,"%s%s.bin",sbbs->cfg.exec_dir
+						,sbbs->cfg.shell[sbbs->useron.shell]->code);
+				if((file=sbbs->nopen(str,O_RDONLY))==-1) {
+					sbbs->errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
+					sbbs->hangup();
+					break; 
+				}
+				FREE_AND_NULL(sbbs->main_csi.cs);
+				sbbs->freevars(&sbbs->main_csi);
+				sbbs->clearvars(&sbbs->main_csi);
+
+				sbbs->main_csi.length=filelength(file);
+				if((sbbs->main_csi.cs=(uchar *)MALLOC(sbbs->main_csi.length))==NULL) {
+					close(file);
+					sbbs->errormsg(WHERE,ERR_ALLOC,str,sbbs->main_csi.length);
+					sbbs->hangup();
+					break; 
+				}
+
+				if(lread(file,sbbs->main_csi.cs,sbbs->main_csi.length)
+					!=(int)sbbs->main_csi.length) {
+					sbbs->errormsg(WHERE,ERR_READ,str,sbbs->main_csi.length);
+					close(file);
+					FREE(sbbs->main_csi.cs);
+					sbbs->main_csi.cs=NULL;
+					sbbs->hangup();
+					break; 
+				}
+				close(file);
+
+				curshell=sbbs->useron.shell;
+				sbbs->main_csi.ip=sbbs->main_csi.cs;
+				sbbs->menu_dir[0]=0;
+				sbbs->menu_file[0]=0;
+				}
+			if(sbbs->exec(&sbbs->main_csi))
+				break;
+		}
+	}
+
+#ifdef _WIN32
+	if(startup->hangup_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
+		PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME);
+#endif
+
+	sbbs->hangup();	/* closes sockets, calls client_off, and shuts down the output_thread */
+    node_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
+
+	sbbs->logout();
+	sbbs->logoffstats();	/* Updates both system and node dsts.dab files */
+
+	if(sbbs->sys_status&SS_DAILY) {	// New day, run daily events/maintenance
+
+		now=time(NULL);
+
+		sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
+		node.status=NODE_EVENT_RUNNING;
+		sbbs->putnodedat(sbbs->cfg.node_num,&node);
+
+		sbbs->logentry("!:","Ran system daily maintenance");
+
+		if(sbbs->cfg.user_backup_level) {
+			lprintf("Node %d Backing-up user data..."
+				,sbbs->cfg.node_num);
+			sprintf(str,"%suser/user.dat",sbbs->cfg.data_dir);
+			backup(str,sbbs->cfg.user_backup_level,FALSE);
+			sprintf(str,"%suser/name.dat",sbbs->cfg.data_dir);
+			backup(str,sbbs->cfg.user_backup_level,FALSE);
+		}
+
+		if(sbbs->cfg.mail_backup_level) {
+			lprintf("Node %d Backing-up mail data..."
+				,sbbs->cfg.node_num);
+			sprintf(str,"%smail.shd",sbbs->cfg.data_dir);
+			backup(str,sbbs->cfg.mail_backup_level,FALSE);
+			sprintf(str,"%smail.sha",sbbs->cfg.data_dir);
+			backup(str,sbbs->cfg.mail_backup_level,FALSE);
+			sprintf(str,"%smail.sdt",sbbs->cfg.data_dir);
+			backup(str,sbbs->cfg.mail_backup_level,FALSE);
+			sprintf(str,"%smail.sda",sbbs->cfg.data_dir);
+			backup(str,sbbs->cfg.mail_backup_level,FALSE);
+			sprintf(str,"%smail.sid",sbbs->cfg.data_dir);
+			backup(str,sbbs->cfg.mail_backup_level,FALSE);
+			sprintf(str,"%smail.sch",sbbs->cfg.data_dir);
+			backup(str,sbbs->cfg.mail_backup_level,FALSE);
+		}
+
+		lprintf("Node %d Checking for inactive/expired user records..."
+			,sbbs->cfg.node_num);
+		j=lastuser(&sbbs->cfg);
+		for(i=1;i<=j;i++) {
+
+			sprintf(str,"%5u of %-5u",i,j);
+			status(str);
+			user.number=i;
+			getuserdat(&sbbs->cfg,&user);
+
+			/***********************************************/
+			/* Fix name (name.dat and user.dat) mismatches */
+			/***********************************************/
+			username(&sbbs->cfg,i,uname);
+			if(user.misc&DELETED) {
+				if(strcmp(uname,"DELETED USER"))
+					putusername(&sbbs->cfg,i,nulstr);
+				continue; 
+			}
+
+			if(strcmp(user.alias,uname))
+				putusername(&sbbs->cfg,i,user.alias);
+
+			if(!(user.misc&(DELETED|INACTIVE))
+				&& user.expire && (ulong)user.expire<=(ulong)now) {
+				putsmsg(&sbbs->cfg,i,sbbs->text[AccountHasExpired]);
+				sprintf(str,"%s #%u Expired",user.alias,user.number);
+				sbbs->logentry("!%",str);
+				if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOVAL
+					&& sbbs->cfg.level_expireto[user.level]<10) {
+					user.flags1=sbbs->cfg.val_flags1[sbbs->cfg.level_expireto[user.level]];
+					user.flags2=sbbs->cfg.val_flags2[sbbs->cfg.level_expireto[user.level]];
+					user.flags3=sbbs->cfg.val_flags3[sbbs->cfg.level_expireto[user.level]];
+					user.flags4=sbbs->cfg.val_flags4[sbbs->cfg.level_expireto[user.level]];
+					user.exempt=sbbs->cfg.val_exempt[sbbs->cfg.level_expireto[user.level]];
+					user.rest=sbbs->cfg.val_rest[sbbs->cfg.level_expireto[user.level]];
+					if(sbbs->cfg.val_expire[sbbs->cfg.level_expireto[user.level]])
+						user.expire=now
+							+(sbbs->cfg.val_expire[sbbs->cfg.level_expireto[user.level]]*24*60*60);
+					else
+						user.expire=0;
+					user.level=sbbs->cfg.val_level[sbbs->cfg.level_expireto[user.level]]; 
+				}
+				else {
+					if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOLVL)
+						user.level=sbbs->cfg.level_expireto[user.level];
+					else
+						user.level=sbbs->cfg.expired_level;
+					user.flags1&=~sbbs->cfg.expired_flags1; /* expired status */
+					user.flags2&=~sbbs->cfg.expired_flags2; /* expired status */
+					user.flags3&=~sbbs->cfg.expired_flags3; /* expired status */
+					user.flags4&=~sbbs->cfg.expired_flags4; /* expired status */
+					user.exempt&=~sbbs->cfg.expired_exempt;
+					user.rest|=sbbs->cfg.expired_rest;
+					user.expire=0; 
+				}
+				putuserrec(&sbbs->cfg,i,U_LEVEL,2,ultoa(user.level,str,10));
+				putuserrec(&sbbs->cfg,i,U_FLAGS1,8,ultoa(user.flags1,str,16));
+				putuserrec(&sbbs->cfg,i,U_FLAGS2,8,ultoa(user.flags2,str,16));
+				putuserrec(&sbbs->cfg,i,U_FLAGS3,8,ultoa(user.flags3,str,16));
+				putuserrec(&sbbs->cfg,i,U_FLAGS4,8,ultoa(user.flags4,str,16));
+				putuserrec(&sbbs->cfg,i,U_EXPIRE,8,ultoa(user.expire,str,16));
+				putuserrec(&sbbs->cfg,i,U_EXEMPT,8,ultoa(user.exempt,str,16));
+				putuserrec(&sbbs->cfg,i,U_REST,8,ultoa(user.rest,str,16));
+				if(sbbs->cfg.expire_mod[0]) {
+					sbbs->useron=user;
+					sbbs->online=ON_LOCAL;
+					sbbs->exec_bin(sbbs->cfg.expire_mod,&sbbs->main_csi);
+					sbbs->online=0; 
+				}
+			}
+
+			/***********************************************************/
+			/* Auto deletion based on expiration date or days inactive */
+			/***********************************************************/
+			if(!(user.exempt&FLAG('P'))     /* Not a permanent account */
+				&& !(user.misc&(DELETED|INACTIVE))	 /* alive */
+				&& (sbbs->cfg.sys_autodel && (now-user.laston)/(long)(24L*60L*60L)
+				> sbbs->cfg.sys_autodel)) {			/* Inactive too long */
+				sprintf(str,"Auto-Deleted %s #%u",user.alias,user.number);
+				sbbs->logentry("!*",str);
+				sbbs->delallmail(i);
+				putusername(&sbbs->cfg,i,nulstr);
+				putuserrec(&sbbs->cfg,i,U_MISC,8,ultoa(user.misc|DELETED,str,16)); 
+			}
+		}
+
+		lprintf("Node %d Purging deleted/expired e-mail",sbbs->cfg.node_num);
+		sprintf(sbbs->smb.file,"%smail",sbbs->cfg.data_dir);
+		sbbs->smb.retry_time=sbbs->cfg.smb_retry_time;
+		sbbs->smb.subnum=INVALID_SUB;
+		if((i=smb_open(&sbbs->smb))!=0)
+			sbbs->errormsg(WHERE,ERR_OPEN,sbbs->smb.file,i,sbbs->smb.last_error);
+		else {
+			if(filelength(fileno(sbbs->smb.shd_fp))>0) {
+				if((i=smb_locksmbhdr(&sbbs->smb))!=0)
+					sbbs->errormsg(WHERE,ERR_LOCK,sbbs->smb.file,i,sbbs->smb.last_error);
+				else
+					sbbs->delmail(0,MAIL_ALL);
+			}
+			smb_close(&sbbs->smb); 
+		}
+
+		sbbs->sys_status&=~SS_DAILY;
+		if(sbbs->cfg.sys_daily[0]) {
+//			status("Running system daily event");
+			sbbs->logentry("!:","Ran system daily event");
+			sbbs->external(sbbs->cmdstr(sbbs->cfg.sys_daily,nulstr,nulstr,NULL)
+				,EX_OFFLINE); 
+		}
+	}
+
+#if 0	/* this is handled in the event_thread now */
+	// Node Daily Event
+	sbbs->getnodedat(sbbs->cfg.node_num,&node,0);
+	if(node.misc&NODE_EVENT) {
+		sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
+		node.status=NODE_EVENT_RUNNING;
+		sbbs->putnodedat(sbbs->cfg.node_num,&node);
+		if(sbbs->cfg.node_daily[0]) {
+			sbbs->logentry("!:","Run node daily event");
+			sbbs->external(
+				 sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL)
+				,EX_OFFLINE);
+		}
+		sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
+		node.misc&=~NODE_EVENT;
+		sbbs->putnodedat(sbbs->cfg.node_num,&node); 
+	}
+#endif
+
+    // Wait for all node threads to terminate
+	if(sbbs->input_thread_running || sbbs->output_thread_running) {
+		lprintf("Node %d Waiting for %s to terminate..."
+			,sbbs->cfg.node_num
+			,(sbbs->input_thread_running && sbbs->output_thread_running) ?
+               	"I/O threads" : sbbs->input_thread_running
+				? "input thread" : "output thread");
+		time_t start=time(NULL);
+		while(sbbs->input_thread_running
+    		|| sbbs->output_thread_running) {
+			if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
+				lprintf("Node %d !TIMEOUT waiting for %s to terminate"
+					, sbbs->cfg.node_num
+					,(sbbs->input_thread_running && sbbs->output_thread_running) ?
+                  		"I/O threads"
+					: sbbs->input_thread_running
+                		? "input thread" : "output thread");
+				break;
+			}
+			mswait(100);
+		}
+	}
+
+	sbbs->catsyslog(0);
+
+	status(STATUS_WFC);
+
+	sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
+	if(node.misc&NODE_DOWN)
+		node.status=NODE_OFFLINE;
+	else
+		node.status=NODE_WFC;
+	node.misc&=~(NODE_DOWN|NODE_INTR|NODE_MSGW|NODE_NMSG
+				|NODE_UDAT|NODE_POFF|NODE_AOFF|NODE_EXT);
+/*	node.useron=0; needed for hang-ups while in multinode chat */
+	sbbs->putnodedat(sbbs->cfg.node_num,&node);
+
+	if(node_threads_running>0)
+		node_threads_running--;
+	lprintf("Node %d thread terminated (%u node threads remain, %lu clients served)"
+		,sbbs->cfg.node_num, node_threads_running, served);
+    if(!sbbs->input_thread_running && !sbbs->output_thread_running)
+		delete sbbs;
+    else
+		lprintf("Node %d !ORPHANED I/O THREAD(s)",sbbs->cfg.node_num);
+
+	update_clients();
+	thread_down();
+}
+
+time_t checktime(void)
+{
+	struct tm tm;
+
+    memset(&tm,0,sizeof(tm));
+    tm.tm_year=94;
+    tm.tm_mday=1;
+    return(mktime(&tm)-0x2D24BD00L);
+}
+
+const char* DLLCALL js_ver(void)
+{
+#ifdef JAVASCRIPT
+	return(JS_GetImplementationVersion());
+#else
+	return("");
+#endif
+}
+
+/* Returns char string of version and revision */
+const char* DLLCALL bbs_ver(void)
+{
+	static char ver[256];
+	char compiler[32];
+
+	DESCRIBE_COMPILER(compiler);
+
+	sprintf(ver,"%s %s%c%s  SMBLIB %s  Compiled %s %s with %s"
+		,TELNET_SERVER
+		,VERSION, REVISION
+#ifdef _DEBUG
+		," Debug"
+#else
+		,""
+#endif
+		,smb_lib_ver()
+		,__DATE__, __TIME__, compiler
+		);
+
+	return(ver);
+}
+
+/* Returns binary-coded version and revision (e.g. 0x31000 == 3.10a) */
+long DLLCALL bbs_ver_num(void)
+{
+	char*	minor;
+
+	if((minor=(char *)strchr(VERSION,'.'))==NULL)
+		return(0);
+	minor++;
+
+	return((strtoul(VERSION,NULL,16)<<16)|(strtoul(minor,NULL,16)<<8)|(REVISION-'A'));
+}
+
+void DLLCALL bbs_terminate(void)
+{
+	recycle_server=false;
+	if(telnet_socket!=INVALID_SOCKET) {
+    	lprintf("BBS Terminate: closing telnet socket %d",telnet_socket);
+		close_socket(telnet_socket);
+		telnet_socket=INVALID_SOCKET;
+    }
+}
+
+static void cleanup(int code)
+{
+    lputs("BBS System thread terminating");
+
+	if(telnet_socket!=INVALID_SOCKET) {
+		close_socket(telnet_socket);
+		telnet_socket=INVALID_SOCKET;
+	}
+	if(rlogin_socket!=INVALID_SOCKET) {
+		close_socket(rlogin_socket);
+		rlogin_socket=INVALID_SOCKET;
+	}
+
+
+#ifdef _WINSOCKAPI_
+	if(WSAInitialized && WSACleanup()!=0) 
+		lprintf("!WSACleanup ERROR %d",ERROR_VALUE);
+#endif
+
+	free_cfg(&scfg);
+	free_text(text);
+
+#ifdef _WIN32
+	if(exec_mutex!=NULL) {
+		CloseHandle(exec_mutex);
+		exec_mutex=NULL;
+	}
+
+	if(hK32!=NULL) {
+		FreeLibrary(hK32);
+		hK32=NULL;
+	}
+
+#if 0 && defined(_DEBUG) && defined(_MSC_VER)
+	_CrtMemDumpAllObjectsSince(&mem_chkpoint);
+
+	if(debug_log!=INVALID_HANDLE_VALUE) {
+		CloseHandle(debug_log);
+		debug_log=INVALID_HANDLE_VALUE;
+	}
+#endif // _DEBUG && _MSC_VER
+#endif // _WIN32
+
+	pthread_mutex_destroy(&event_mutex);
+
+	status("Down");
+	thread_down();
+    lprintf("BBS System thread terminated (%u threads remain, %lu clients served)"
+		,thread_count, served);
+	if(startup->terminated!=NULL)
+		startup->terminated(code);
+}
+
+void DLLCALL bbs_thread(void* arg)
+{
+	char *			host_name;
+	char *			identity;
+    char			str[MAX_PATH+1];
+	char			logstr[256];
+	SOCKADDR_IN		server_addr={0};
+	SOCKADDR_IN		client_addr;
+	socklen_t		client_addr_len;
+	SOCKET			client_socket;
+	fd_set			socket_set;
+	SOCKET			high_socket_set;
+	int				i;
+    int				file;
+	int				result;
+	BOOL			option;
+	time_t			t;
+	time_t			start;
+	time_t			initialized=0;
+	node_t			node;
+	sbbs_t*			events=NULL;
+	client_t		client;
+	startup=(bbs_startup_t*)arg;
+	BOOL			is_client=FALSE;
+#ifdef __unix__
+	SOCKET	uspy_listen_socket[MAX_NODES];
+	struct sockaddr_un uspy_addr;
+	socklen_t		addr_len;
+#endif
+
+    if(startup==NULL) {
+    	sbbs_beep(100,500);
+    	fprintf(stderr, "No startup structure passed!\n");
+    	return;
+    }
+
+	if(startup->size!=sizeof(bbs_startup_t)) {	// verify size
+		sbbs_beep(100,500);
+		sbbs_beep(300,500);
+		sbbs_beep(100,500);
+		fprintf(stderr, "Invalid startup structure!\n");
+		return;
+	}
+
+	/* Setup intelligent defaults */
+	if(startup->telnet_port==0)				startup->telnet_port=IPPORT_TELNET;
+	if(startup->rlogin_port==0)				startup->rlogin_port=513;
+	if(startup->xtrn_polls_before_yield==0)	startup->xtrn_polls_before_yield=10;
+#ifdef JAVASCRIPT
+	if(startup->js_max_bytes==0)			startup->js_max_bytes=JAVASCRIPT_MAX_BYTES;
+#endif
+	if(startup->outbuf_drain_timeout==0)	startup->outbuf_drain_timeout=10;
+	if(startup->temp_dir[0])				backslash(startup->temp_dir);
+
+	uptime=0;
+	served=0;
+	startup->recycle_now=FALSE;
+	recycle_server=true;
+	do {
+
+	thread_up(FALSE /* setuid */);
+
+	status("Initializing");
+
+	/* Defeat the lameo hex0rs - the name and copyright must remain intact */
+	if(crc32(COPYRIGHT_NOTICE,0)!=COPYRIGHT_CRC 
+		|| crc32(VERSION_NOTICE,10)!=SYNCHRONET_CRC) {
+		lprintf("!CORRUPTED LIBRARY FILE");
+		cleanup(1);
+		return;
+	}
+
+	memset(text, 0, sizeof(text));
+    memset(&scfg, 0, sizeof(scfg));
+
+	node_threads_running=0;
+	lastuseron[0]=0;
+
+	char compiler[32];
+	DESCRIBE_COMPILER(compiler);
+
+	lprintf("%s Version %s Revision %c%s"
+		,TELNET_SERVER
+		,VERSION
+		,toupper(REVISION)
+#ifdef _DEBUG
+		," Debug"
+#else
+		,""
+#endif
+		);
+	lprintf("Compiled %s %s with %s", __DATE__, __TIME__, compiler);
+	lprintf("SMBLIB %s (format %x.%02x)",smb_lib_ver(),smb_ver()>>8,smb_ver()&0xff);
+
+    if(startup->first_node<1 || startup->first_node>startup->last_node) {
+    	lprintf("!ILLEGAL node configuration (first: %d, last: %d)"
+        	,startup->first_node, startup->last_node);
+		cleanup(1);
+        return;
+    }
+
+	if(sizeof(node_t)!=SIZEOF_NODE_T) {
+		lprintf("!COMPILER ERROR: sizeof(node_t)=%d instead of %d"
+			,sizeof(node_t),SIZEOF_NODE_T);
+		cleanup(1);
+		return;
+	}
+
+#ifdef _WIN32
+    if((exec_mutex=CreateMutex(NULL,false,NULL))==NULL) {
+    	lprintf("!ERROR %d creating exec_mutex", GetLastError());
+		cleanup(1);
+        return;
+    }
+	hK32 = LoadLibrary("KERNEL32");
+#endif // _WIN32
+
+	pthread_mutex_init(&event_mutex,NULL);
+
+	if(!winsock_startup()) {
+		cleanup(1);
+		return;
+	}
+
+	t=time(NULL);
+	lprintf("Initializing on %.24s with options: %lx"
+		,CTIME_R(&t,str),startup->options);
+
+	if(chdir(startup->ctrl_dir)!=0)
+		lprintf("!ERROR %d changing directory to: %s", errno, startup->ctrl_dir);
+
+	/* Initial configuration and load from CNF files */
+    SAFECOPY(scfg.ctrl_dir,startup->ctrl_dir);
+    lprintf("Loading configuration files from %s", scfg.ctrl_dir);
+	scfg.size=sizeof(scfg);
+	scfg.node_num=startup->first_node;
+	SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
+	if(!load_cfg(&scfg, text, TRUE, logstr)) {
+		lprintf("!ERROR %s",logstr);
+		lprintf("!FAILED to load configuration files");
+		cleanup(1);
+		return;
+	}
+	scfg_reloaded=true;
+
+	if(startup->host_name[0]==0)
+		SAFECOPY(startup->host_name,scfg.sys_inetaddr);
+
+	if(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&BBS_OPT_LOCAL_TIMEZONE)) {
+		if(putenv("TZ=UTC0"))
+			lprintf("!putenv() FAILED");
+		tzset();
+
+		if((t=checktime())!=0) {   /* Check binary time */
+			lprintf("!TIME PROBLEM (%ld)",t);
+			cleanup(1);
+			return;
+		}
+	}
+
+	if(uptime==0)
+		uptime=time(NULL);	/* this must be done *after* setting the timezone */
+
+    if(startup->last_node>scfg.sys_nodes) {
+    	lprintf("Specified last_node (%d) > sys_nodes (%d), auto-corrected"
+        	,startup->last_node, scfg.sys_nodes);
+        startup->last_node=scfg.sys_nodes;
+    }
+
+	/* Create missing directories */
+	lprintf("Verifying/creating data directories");
+	make_data_dirs(&scfg);
+
+	/* Create missing node directories and dsts.dab files */
+	lprintf("Verifying/creating node directories");
+	for(i=0;i<=scfg.sys_nodes;i++) {
+		if(i)
+			md(scfg.node_path[i-1]);
+		sprintf(str,"%sdsts.dab",i ? scfg.node_path[i-1] : scfg.ctrl_dir);
+		if(flength(str)<DSTSDABLEN) {
+			if((file=sopen(str,O_WRONLY|O_CREAT|O_APPEND, SH_DENYNO, S_IREAD|S_IWRITE))==-1) {
+				lprintf("!ERROR %d creating %s",errno, str);
+				cleanup(1);
+				return; 
+			}
+			while(filelength(file)<DSTSDABLEN)
+				if(write(file,"\0",1)!=1)
+					break;				/* Create NULL system dsts.dab */
+			close(file); 
+		} 
+	}
+
+	/* Initial global node variables */
+	for(i=0;i<MAX_NODES;i++) {
+		node_inbuf[i]=NULL;
+    	node_socket[i]=INVALID_SOCKET;
+		spy_socket[i]=INVALID_SOCKET;
+#ifdef __unix__
+		uspy_socket[i]=INVALID_SOCKET;
+		uspy_listen_socket[i]=INVALID_SOCKET;
+#endif
+	}
+
+	startup->node_inbuf=node_inbuf;
+
+    /* open a socket and wait for a client */
+
+    telnet_socket = open_socket(SOCK_STREAM);
+
+	if(telnet_socket == INVALID_SOCKET) {
+		lprintf("!ERROR %d creating Telnet socket", ERROR_VALUE);
+		cleanup(1);
+		return;
+	}
+
+    lprintf("Telnet socket %d opened",telnet_socket);
+
+	if(startup->options&BBS_OPT_KEEP_ALIVE) {
+		lprintf("Enabling WinSock Keep Alives");
+		option = TRUE;
+
+		result = setsockopt(telnet_socket, SOL_SOCKET, SO_KEEPALIVE
+    		,(char *)&option, sizeof(option));
+
+		if(result != 0) {
+			lprintf("!ERROR %d (%d) setting Telnet socket option", result, ERROR_VALUE);
+			cleanup(1);
+			return;
+		}
+
+	}
+
+	/*****************************/
+	/* Listen for incoming calls */
+	/*****************************/
+    memset(&server_addr, 0, sizeof(server_addr));
+
+	server_addr.sin_addr.s_addr = htonl(startup->telnet_interface);
+    server_addr.sin_family = AF_INET;
+    server_addr.sin_port   = htons(startup->telnet_port);
+
+	if(startup->seteuid!=NULL)
+		startup->seteuid(FALSE);
+    result = bind(telnet_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
+	if(startup->seteuid!=NULL)
+		startup->seteuid(TRUE);
+	if(result != 0) {
+		lprintf("!ERROR %d (%d) binding Telnet socket to port %d"
+			,result, ERROR_VALUE,startup->telnet_port);
+		lprintf(BIND_FAILURE_HELP);
+		cleanup(1);
+		return;
+	}
+
+    result = listen(telnet_socket, 1);
+
+	if(result != 0) {
+		lprintf("!ERROR %d (%d) listening on Telnet socket", result, ERROR_VALUE);
+		cleanup(1);
+		return;
+	}
+	lprintf("Telnet server listening on port %d",startup->telnet_port);
+
+	if(startup->options&BBS_OPT_ALLOW_RLOGIN) {
+
+		/* open a socket and wait for a client */
+
+		rlogin_socket = open_socket(SOCK_STREAM);
+
+		if(rlogin_socket == INVALID_SOCKET) {
+			lprintf("!ERROR %d creating RLogin socket", ERROR_VALUE);
+			cleanup(1);
+			return;
+		}
+
+		lprintf("RLogin socket %d opened",rlogin_socket);
+
+		/*****************************/
+		/* Listen for incoming calls */
+		/*****************************/
+		memset(&server_addr, 0, sizeof(server_addr));
+
+		server_addr.sin_addr.s_addr = htonl(startup->rlogin_interface);
+		server_addr.sin_family = AF_INET;
+		server_addr.sin_port   = htons(startup->rlogin_port);
+
+		if(startup->seteuid!=NULL)
+			startup->seteuid(FALSE);
+		result = bind(rlogin_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
+		if(startup->seteuid!=NULL)
+			startup->seteuid(TRUE);
+		if(result != 0) {
+			lprintf("!ERROR %d (%d) binding RLogin socket to port %d"
+				,result, ERROR_VALUE,startup->rlogin_port);
+			lprintf(BIND_FAILURE_HELP);
+			cleanup(1);
+			return;
+		}
+
+		result = listen(rlogin_socket, 1);
+
+		if(result != 0) {
+			lprintf("!ERROR %d (%d) listening on RLogin socket", result, ERROR_VALUE);
+			cleanup(1);
+			return;
+		}
+		lprintf("RLogin server listening on port %d",startup->rlogin_port);
+	}
+
+	/* signal caller that we've started up successfully */
+    if(startup->started!=NULL)
+    	startup->started();
+
+	sbbs = new sbbs_t(0, server_addr.sin_addr.s_addr
+		,"BBS System", telnet_socket, &scfg, text, NULL);
+    sbbs->online = 0;
+	if(sbbs->init()==false) {
+		lputs("!BBS initialization failed");
+		cleanup(1);
+		return;
+	}
+	_beginthread(output_thread, 0, sbbs);
+
+	if(!(startup->options&BBS_OPT_NO_EVENTS)) {
+		events = new sbbs_t(0, server_addr.sin_addr.s_addr
+			,"BBS Events", INVALID_SOCKET, &scfg, text, NULL);
+		events->online = 0;
+		if(events->init()==false) {
+			lputs("!Events initialization failed");
+			cleanup(1);
+			return;
+		}
+		_beginthread(event_thread, 0, events);
+	}
+
+	/* Save these values incase they're changed dynamically */
+	first_node=startup->first_node;
+	last_node=startup->last_node;
+
+	for(i=first_node;i<=last_node;i++) {
+		sbbs->getnodedat(i,&node,1);
+		node.status=NODE_WFC;
+		node.misc&=NODE_EVENT;
+		node.action=0;
+		sbbs->putnodedat(i,&node);
+	}
+
+	lprintf("BBS System thread started for nodes %d through %d", first_node, last_node);
+	status(STATUS_WFC);
+
+#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
+	
+	sprintf(str,"%sDEBUG.LOG",scfg.logs_dir);
+	if((debug_log=CreateFile(
+		str,				// pointer to name of the file
+		GENERIC_READ|GENERIC_WRITE,
+		FILE_SHARE_READ|FILE_SHARE_WRITE,
+		NULL,               // pointer to security attributes
+		OPEN_ALWAYS,		// how to create
+		FILE_ATTRIBUTE_NORMAL, // file attributes
+		NULL				// handle to file with attributes to 
+		))==INVALID_HANDLE_VALUE) {
+		lprintf("!ERROR %ld creating %s",GetLastError(),str);
+		cleanup(1);
+		return;
+	}
+
+	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
+	_CrtSetReportFile(_CRT_WARN, debug_log);
+	_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE|_CRTDBG_MODE_WNDW);
+	_CrtSetReportFile(_CRT_ERROR, debug_log);
+	_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE|_CRTDBG_MODE_WNDW);
+	_CrtSetReportFile(_CRT_ASSERT, debug_log);
+
+	/* Turns on memory leak checking during program termination */
+//	_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
+
+	/* Save this allocation point for comparison */
+	_CrtMemCheckpoint(&mem_chkpoint);
+
+#endif // _WIN32 && _DEBUG && _MSC_VER
+
+	if(!initialized) {
+		initialized=time(NULL);
+		sprintf(str,"%stelnet.rec",scfg.ctrl_dir);
+		t=fdate(str);
+		if(t!=-1 && t>initialized)
+			initialized=t;
+	}
+
+#ifdef __unix__	//	unix-domain spy sockets
+	for(i=first_node;i<=last_node;i++)  {
+	    if((uspy_listen_socket[i-1]=socket(PF_LOCAL,SOCK_STREAM,0))==INVALID_SOCKET)
+	        lprintf("Node %d ERROR %d creating local spy socket"
+	            , i, errno);
+	    else {
+	        lprintf("Node %d local spy using socket %d", i, uspy_listen_socket[i-1]);
+	        if(startup!=NULL && startup->socket_open!=NULL)
+	            startup->socket_open(TRUE);
+	    }
+	
+	    uspy_addr.sun_family=AF_LOCAL;
+	    if((unsigned int)snprintf(str,sizeof(uspy_addr.sun_path),
+	            "%slocalspy%d.sock", startup->temp_dir, i)
+	            >=sizeof(uspy_addr.sun_path))
+	        uspy_listen_socket[i-1]=INVALID_SOCKET;
+	    else  {
+	        strcpy(uspy_addr.sun_path,str);
+	        if(fexist(str))
+	            unlink(str);
+	    }
+	    if(uspy_listen_socket[i-1]!=INVALID_SOCKET) {
+	        addr_len=SUN_LEN(&uspy_addr);
+	        if(bind(uspy_listen_socket[i-1], (struct sockaddr *) &uspy_addr, addr_len)) {
+	            lprintf("Node %d !ERROR %d binding local spy socket %d to %s"
+	                , i, errno, uspy_listen_socket[i-1], uspy_addr.sun_path);
+	            close_socket(uspy_listen_socket[i-1]);
+	            continue;
+	        }
+            lprintf("Node %d local spy socket %d bound to %s"
+                , i, uspy_listen_socket[i-1], uspy_addr.sun_path);
+	        if(listen(uspy_listen_socket[i-1],1))  {
+	            lprintf("Node %d !ERROR %d listening local spy socket %d", i, errno);
+	            close_socket(uspy_listen_socket[i-1]);
+	            continue;
+			}
+	        addr_len=sizeof(uspy_addr);
+	    }
+	}
+#endif // __unix__ (unix-domain spy sockets)
+
+	while(telnet_socket!=INVALID_SOCKET) {
+
+		if(node_threads_running==0 && !event_mutex_locked) {	/* check for re-run flags */
+			bool rerun=false;
+			for(i=first_node;i<=last_node;i++) {
+				if(sbbs->getnodedat(i,&node,0)!=0)
+					continue;
+				if(node.misc&NODE_RRUN) {
+					sbbs->getnodedat(i,&node,1);
+					if(!rerun)
+						lprintf("Node %d flagged for re-run",i);
+					rerun=true;
+					node.misc&=~NODE_RRUN;
+					sbbs->putnodedat(i,&node);
+				}
+			}
+			if(rerun) {
+				lprintf("Loading configuration files from %s", scfg.ctrl_dir);
+				scfg.node_num=first_node;
+				pthread_mutex_lock(&event_mutex);
+				SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
+				if(!load_cfg(&scfg, text, TRUE, logstr)) {
+					lprintf("!ERROR %s",logstr);
+					lprintf("!FAILED to load configuration files");
+					break;
+				}
+				scfg_reloaded=true;
+				pthread_mutex_unlock(&event_mutex);
+			}
+			if(!(startup->options&BBS_OPT_NO_RECYCLE)) {
+				sprintf(str,"%stelnet.rec",scfg.ctrl_dir);
+				t=fdate(str);
+				if(t!=-1 && t>initialized) {
+					lprintf("0000 Recycle semaphore file (%s) detected",str);
+					initialized=t;
+					break;
+				}
+				if(startup->recycle_now==TRUE) {
+					lprintf("0000 Recycle semaphore signaled");
+					startup->recycle_now=FALSE;
+					break;
+				}
+			}
+		}
+
+
+    	sbbs->online=0;
+
+		/* now wait for connection */
+
+		FD_ZERO(&socket_set);
+		FD_SET(telnet_socket,&socket_set);
+		high_socket_set=telnet_socket+1;
+		if(startup->options&BBS_OPT_ALLOW_RLOGIN 
+			&& rlogin_socket!=INVALID_SOCKET) {
+			FD_SET(rlogin_socket,&socket_set);
+			if(rlogin_socket+1>high_socket_set)
+				high_socket_set=rlogin_socket+1;
+		}
+#ifdef __unix__
+		for(i=first_node;i<=last_node;i++)  {
+			if(uspy_listen_socket[i-1]!=INVALID_SOCKET)  {
+				FD_SET(uspy_listen_socket[i-1],&socket_set);
+				if(uspy_listen_socket[i-1]+1>high_socket_set)
+					high_socket_set=uspy_listen_socket[i-1]+1;
+			}
+			if(uspy_socket[i-1]!=INVALID_SOCKET)  {
+				FD_SET(uspy_socket[i-1],&socket_set);
+				if(uspy_socket[i-1]+1>high_socket_set)
+					high_socket_set=uspy_socket[i-1]+1;
+			}
+		}
+#endif
+
+		struct timeval tv;
+		tv.tv_sec=2;
+		tv.tv_usec=0;
+
+		if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
+			if(i==0)
+				continue;
+			if(ERROR_VALUE==EINTR)
+				lprintf("Telnet Server listening interrupted");
+			else if(ERROR_VALUE == ENOTSOCK)
+            	lprintf("Telnet Server sockets closed");
+			else
+				lprintf("!ERROR %d selecting sockets",ERROR_VALUE);
+			break;
+		}
+
+		if(telnet_socket==INVALID_SOCKET)	/* terminated */
+			break;
+
+		client_addr_len = sizeof(client_addr);
+
+		bool rlogin = false;
+
+		is_client=FALSE;
+		if(telnet_socket!=INVALID_SOCKET 
+			&& FD_ISSET(telnet_socket,&socket_set)) {
+			client_socket = accept_socket(telnet_socket, (struct sockaddr *)&client_addr
+	        	,&client_addr_len);
+	        is_client=TRUE;
+		} else if(rlogin_socket!=INVALID_SOCKET 
+			&& FD_ISSET(rlogin_socket,&socket_set)) {
+			client_socket = accept_socket(rlogin_socket, (struct sockaddr *)&client_addr
+	        	,&client_addr_len);
+			rlogin = true;
+			is_client=TRUE;
+		} else {
+#ifdef __unix__
+			for(i=first_node;i<=last_node;i++)  {
+				if(uspy_listen_socket[i-1]!=INVALID_SOCKET
+				&& FD_ISSET(uspy_listen_socket[i-1],&socket_set)) {
+					BOOL already_connected=(uspy_socket[i-1]!=INVALID_SOCKET);
+					SOCKET new_socket=INVALID_SOCKET;
+					new_socket = accept(uspy_listen_socket[i-1], (struct sockaddr *)&client_addr
+						,&client_addr_len);
+					if(already_connected)  {
+						send(new_socket,"Spy socket already in use.\n",27,0);
+						close_socket(new_socket);
+					}
+					else  {
+						uspy_socket[i-1]=new_socket;
+						sprintf(str,"Spy connection established to node %d\n",i);
+						send(uspy_socket[i-1],str,strlen(str),0);
+					}
+				}
+				if(uspy_socket[i-1]!=INVALID_SOCKET
+				&& FD_ISSET(uspy_socket[i-1],&socket_set))  {
+					if(!socket_check(uspy_socket[i-1],NULL,NULL,0)) {
+						close_socket(uspy_socket[i-1]);
+						uspy_socket[i-1]=INVALID_SOCKET;
+					}
+				}
+			}
+#else
+			lprintf("!NO SOCKETS set by select");
+#endif
+		}
+
+		if(!is_client)
+			continue;
+
+		if(client_socket == INVALID_SOCKET)	{
+			if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR || ERROR_VALUE == EINVAL) {
+            	lputs("BBS socket closed");
+				break;
+			}
+			lprintf("!ERROR %d accepting connection", ERROR_VALUE);
+			break;	// was continue, July-01-2002
+		}
+		char host_ip[32];
+
+		strcpy(host_ip,inet_ntoa(client_addr.sin_addr));
+
+		if(trashcan(&scfg,host_ip,"ip-silent")) {
+			close_socket(client_socket);
+			continue;
+		}
+
+		lprintf("%04d %s connection accepted from: %s port %u"
+			,client_socket
+			,rlogin ? "RLogin" : "Telnet", host_ip, ntohs(client_addr.sin_port));
+
+#ifdef _WIN32
+		if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
+			PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
+#endif
+
+   		sbbs->client_socket=client_socket;	// required for output to the user
+        sbbs->online=ON_REMOTE;
+
+		if(sbbs->trashcan(host_ip,"ip")) {
+			close_socket(client_socket);
+			lprintf("%04d !CLIENT BLOCKED in ip.can"
+				,client_socket);
+			sprintf(logstr, "Blocked IP: %s",host_ip);
+			sbbs->syslog("@!",logstr);
+			continue;
+		}
+
+		if(rlogin) {
+			if(!trashcan(&scfg,host_ip,"rlogin")) {
+				close_socket(client_socket);
+				lprintf("%04d !CLIENT IP NOT LISTED in rlogin.can",client_socket);
+				sprintf(logstr, "Invalid RLogin from: %s",host_ip);
+				sbbs->syslog("@!",logstr);
+				continue;
+			}
+			sbbs->outcom(0); /* acknowledge RLogin per RFC 1282 */
+		}
+
+		sbbs->putcom(crlf);
+		sbbs->putcom(VERSION_NOTICE);
+		sbbs->putcom(crlf);
+
+		sbbs->bprintf("Connection from: %s\r\n", host_ip);
+
+		struct hostent* h;
+		if(startup->options&BBS_OPT_NO_HOST_LOOKUP)
+			h=NULL;
+		else {
+			sbbs->bprintf("Resolving hostname...");
+			h=gethostbyaddr((char *)&client_addr.sin_addr
+				,sizeof(client_addr.sin_addr),AF_INET);
+			sbbs->putcom(crlf);
+		}
+		if(h!=NULL && h->h_name!=NULL)
+			host_name=h->h_name;
+		else
+			host_name="<no name>";
+
+		if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) {
+			lprintf("%04d Hostname: %s", client_socket, host_name);
+			for(i=0;h!=NULL && h->h_aliases!=NULL && h->h_aliases[i]!=NULL;i++)
+				lprintf("%04d HostAlias: %s", client_socket, h->h_aliases[i]);
+		}
+
+		if(sbbs->trashcan(host_name,"host")) {
+			close_socket(client_socket);
+			lprintf("%04d !CLIENT BLOCKED in host.can",client_socket);
+			sprintf(logstr, "Blocked Hostname: %s",host_name);
+			sbbs->syslog("@!",logstr);
+			continue;
+		}
+
+		identity=NULL;
+		if(startup->options&BBS_OPT_GET_IDENT) {
+			sbbs->bprintf("Resolving identity...");
+			identify(&client_addr, startup->telnet_port, str, sizeof(str)-1,0);
+			identity=strrchr(str,':');
+			if(identity!=NULL) {
+				identity++;	/* skip colon */
+				while(*identity && *identity<=SP) /* point to user name */
+					identity++;
+				lprintf("%04d Identity: %s",client_socket, identity);
+			}
+		}
+		/* Initialize client display */
+		client.size=sizeof(client);
+		client.time=time(NULL);
+		SAFECOPY(client.addr,host_ip);
+		SAFECOPY(client.host,host_name);
+		client.port=ntohs(client_addr.sin_port);
+		client.protocol=rlogin ? "RLogin":"Telnet";
+		client.user="<unknown>";
+		client_on(client_socket,&client,FALSE /* update */);
+
+		for(i=first_node;i<=last_node;i++) {
+			/* paranoia: make sure node.status!=NODE_WFC by default */
+			node.status=NODE_INVALID_STATUS;	
+			if(sbbs->getnodedat(i,&node,1)!=0)
+				continue;
+			if(node.status==NODE_WFC) {
+				node.status=NODE_LOGON;
+				sbbs->putnodedat(i,&node);
+				break;
+			}
+			sbbs->putnodedat(i,&node);
+		}
+
+		if(i>last_node) {
+			lprintf("%04d !No nodes available for login.",client_socket);
+			sprintf(str,"%snonodes.txt",scfg.text_dir);
+			if(fexist(str))
+				sbbs->printfile(str,P_NOABORT);
+			else {
+				sbbs->putcom("\r\nSorry, all telnet nodes are in use or otherwise unavailable.\r\n");
+				sbbs->putcom("Please try again later.\r\n");
+			}
+			mswait(3000);
+			client_off(client_socket);
+			close_socket(client_socket);
+			continue;
+		}
+
+        node_socket[i-1]=client_socket;
+
+		sbbs_t* new_node = new sbbs_t(i, client_addr.sin_addr.s_addr, host_name
+        	,client_socket, &scfg, text, &client);
+
+		new_node->client=client;
+
+		/* copy the IDENT response, if any */
+		if(identity!=NULL)
+			SAFECOPY(new_node->client_ident,identity);
+
+		if(new_node->init()==false) {
+			lprintf("%04d !Node %d Initialization failure"
+				,client_socket,new_node->cfg.node_num);
+			sprintf(str,"%snonodes.txt",scfg.text_dir);
+			if(fexist(str))
+				sbbs->printfile(str,P_NOABORT);
+			else 
+				sbbs->putcom("\r\nSorry, initialization failed. Try again later.\r\n");
+			mswait(3000);
+			sbbs->getnodedat(new_node->cfg.node_num,&node,1);
+			node.status=NODE_WFC;
+			sbbs->putnodedat(new_node->cfg.node_num,&node);
+			delete new_node;
+			node_socket[i-1]=INVALID_SOCKET;
+			client_off(client_socket);
+			close_socket(client_socket);
+			continue;
+		}
+
+		if(rlogin==true) {
+			new_node->connection="RLogin";
+			new_node->sys_status|=SS_RLOGIN;
+		}
+
+	    node_threads_running++;
+		new_node->input_thread=(HANDLE)_beginthread(input_thread,0, new_node);
+		_beginthread(output_thread, 0, new_node);
+		_beginthread(node_thread, 0, new_node);
+		served++;
+	}
+
+    // Close all open sockets
+    for(i=0;i<MAX_NODES;i++)
+    	if(node_socket[i]!=INVALID_SOCKET) {
+        	lprintf("Closing node %d socket %d", i+1, node_socket[i]);
+        	close_socket(node_socket[i]);
+			node_socket[i]=INVALID_SOCKET;
+        }
+
+	sbbs->client_socket=INVALID_SOCKET;
+	if(events!=NULL)
+		events->terminated=true;
+    // Wake-up BBS output thread so it can terminate
+    sem_post(&sbbs->outbuf.sem);
+
+    // Wait for all node threads to terminate
+	if(node_threads_running) {
+		lprintf("Waiting for %d node threads to terminate...", node_threads_running);
+		start=time(NULL);
+		while(node_threads_running) {
+			if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
+				lprintf("!TIMEOUT waiting for %d node thread(s) to "
+            		"terminate", node_threads_running);
+				break;
+			}
+			mswait(100);
+		}
+	}
+
+	// Wait for Events thread to terminate
+	if(events!=NULL && events->event_thread_running) {
+		pthread_mutex_unlock(&event_mutex);
+		lprintf("Waiting for event thread to terminate...");
+		start=time(NULL);
+		while(events->event_thread_running) {
+			if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
+				lprintf("!TIMEOUT waiting for BBS event thread to "
+            		"terminate");
+				break;
+			}
+			mswait(100);
+		}
+	}
+
+    // Wait for BBS output thread to terminate
+	if(sbbs->output_thread_running) {
+		lprintf("Waiting for system output thread to terminate...");
+		start=time(NULL);
+		while(sbbs->output_thread_running) {
+			if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
+				lprintf("!TIMEOUT waiting for BBS output thread to "
+            		"terminate");
+				break;
+			}
+			mswait(100);
+		}
+	}
+
+    // Set all nodes' status to OFFLINE
+    for(i=first_node;i<=last_node;i++) {
+        sbbs->getnodedat(i,&node,1);
+        node.status=NODE_OFFLINE;
+        sbbs->putnodedat(i,&node);
+    }
+
+	if(events!=NULL && !events->event_thread_running)
+		delete events;
+
+    if(!sbbs->output_thread_running)
+	    delete sbbs;
+
+	cleanup(0);
+
+	if(recycle_server) {
+		lprintf("Recycling server...");
+		mswait(2000);
+	}
+
+	} while(recycle_server);
+
+}
+
+
+