--- sbbs/sbbs3/main.cpp 2018/04/24 16:39:34 1.1.1.1 +++ sbbs/sbbs3/main.cpp 2018/04/24 16:39:50 1.1.1.2 @@ -2,13 +2,13 @@ /* Synchronet main/telnet server thread and related functions */ -/* $Id: main.cpp,v 1.1.1.1 2018/04/24 16:39:34 root Exp $ */ +/* $Id: main.cpp,v 1.1.1.2 2018/04/24 16:39:50 root Exp $ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * - * Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html * + * Copyright 2004 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 * @@ -35,13 +35,17 @@ * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ -#ifdef __unix__ - #include /* do we need bsd/signal on Linux? */ -#endif - #include "sbbs.h" +#include "ident.h" #include "telnet.h" -#include "bbs_thrd.h" + +#ifdef __unix__ + #include + #ifndef SUN_LEN + #define SUN_LEN(su) \ + (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) + #endif +#endif //--------------------------------------------------------------------------- @@ -49,53 +53,47 @@ int mswtyp=0; uint riobp; +#define TELNET_SERVER "Synchronet Telnet Server" #define STATUS_WFC "Listening" -#define TIMEOUT_THREAD_WAIT 30 // Seconds -#define IO_THREAD_BUF_SIZE 10000 // Bytes +#define TIMEOUT_THREAD_WAIT 60 // Seconds (was 15) +#define IO_THREAD_BUF_SIZE 20000 // Bytes // Globals #ifdef _WIN32 -HANDLE exec_mutex; -#endif - -static uint node_threads_running=0; - -RingBuf* node_inbuf[MAX_NODES]; -SOCKET spy_socket[MAX_NODES]; -SOCKET node_socket[MAX_NODES]; -static SOCKET telnet_socket=INVALID_SOCKET; -static SOCKET rlogin_socket=INVALID_SOCKET; -static pthread_mutex_t event_mutex; -static sbbs_t* sbbs=NULL; -static scfg_t scfg; -static bool scfg_reloaded=true; -static char * text[TOTAL_TEXT]; - -#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); - } + HANDLE exec_mutex=NULL; + HINSTANCE hK32=NULL; - lprintf("!WinSock startup ERROR %d", status); - return(FALSE); -} + #if defined(_DEBUG) && defined(_MSC_VER) + HANDLE debug_log=INVALID_HANDLE_VALUE; + _CrtMemState mem_chkpoint; + #endif // _DEBUG && _MSC_VER -#else /* No WINSOCK */ +#endif // _WIN32 -#define winsock_startup() (TRUE) +time_t uptime=0; +DWORD served=0; +static ulong node_threads_running=0; +static ulong 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 sbbs_t* sbbs=NULL; +static scfg_t scfg; +static char * text[TOTAL_TEXT]; +static WORD first_node; +static WORD last_node; +static bool terminate_server=false; +static link_list_t recycle_semfiles; +static link_list_t shutdown_semfiles; extern "C" { @@ -104,46 +102,100 @@ static bbs_startup_t* startup=NULL; static void status(char* str) { if(startup!=NULL && startup->status!=NULL) - startup->status(str); + startup->status(startup->cbdata,str); } static void update_clients() { if(startup!=NULL && startup->clients!=NULL) - startup->clients(node_threads_running); + startup->clients(startup->cbdata,node_threads_running); } -void client_on(SOCKET sock, client_t* client) +void client_on(SOCKET sock, client_t* client, BOOL update) { if(startup!=NULL && startup->client_on!=NULL) - startup->client_on(TRUE,sock,client); + startup->client_on(startup->cbdata,TRUE,sock,client,update); } static void client_off(SOCKET sock) { if(startup!=NULL && startup->client_on!=NULL) - startup->client_on(FALSE,sock,NULL); + startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE); } -static void thread_up() +static void thread_up(BOOL setuid) { + thread_count++; if(startup!=NULL && startup->thread_up!=NULL) - startup->thread_up(TRUE); + startup->thread_up(startup->cbdata,TRUE,setuid); } static void thread_down() { + if(thread_count>0) + thread_count--; if(startup!=NULL && startup->thread_up!=NULL) - startup->thread_up(FALSE); + startup->thread_up(startup->cbdata,FALSE,FALSE); +} + +int lputs(int level, char* str) +{ + if(startup==NULL || startup->lputs==NULL || str==NULL) + return(0); + + return(startup->lputs(startup->cbdata,level,str)); +} + +int lprintf(int level, char *fmt, ...) +{ + va_list argptr; + char sbuf[1024]; + + va_start(argptr,fmt); + vsnprintf(sbuf,sizeof(sbuf),fmt,argptr); + sbuf[sizeof(sbuf)-1]=0; + va_end(argptr); + return(lputs(level,sbuf)); +} + +int eprintf(int level, char *fmt, ...) +{ + va_list argptr; + char sbuf[1024]; + + if(startup==NULL || startup->event_lputs==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_lputs(level,sbuf)); } SOCKET open_socket(int type) { - SOCKET sock; + 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); + startup->socket_open(startup->cbdata,TRUE); + if(sock!=INVALID_SOCKET && set_socket_options(&scfg, sock, error)) + lprintf(LOG_ERR,"%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(startup->cbdata,TRUE); + return(sock); } @@ -151,50 +203,751 @@ 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) - lprintf("!ERROR %d closing socket %d",ERROR_VALUE,sock); + if(startup!=NULL && startup->socket_open!=NULL) + startup->socket_open(startup->cbdata,FALSE); + if(result!=0 && ERROR_VALUE!=ENOTSOCK) + lprintf(LOG_ERR,"!ERROR %d closing socket %d",ERROR_VALUE,sock); return(result); } -int lputs(char* str) + +u_long resolve_ip(char *addr) { - if(startup==NULL || startup->lputs==NULL) - return(0); - return(startup->lputs(str)); + HOSTENT* host; + char* p; + + if(*addr==0) + return((u_long)INADDR_NONE); + + for(p=addr;*p;p++) + if(*p!='.' && !isdigit(*p)) + break; + if(!(*p)) + return(inet_addr(addr)); + if((host=gethostbyname(addr))==NULL) + return((u_long)INADDR_NONE); + return(*((ulong*)host->h_addr_list[0])); } -int lprintf(char *fmt, ...) +} /* extern "C" */ + +#ifdef _WINSOCKAPI_ + +WSADATA WSAData; +#define SOCKLIB_DESC WSAData.szDescription +static BOOL WSAInitialized=FALSE; + +static BOOL winsock_startup(void) { - va_list argptr; - char sbuf[1024]; + int status; /* Status Code */ - if(startup==NULL || startup->lputs==NULL) - return(0); - va_start(argptr,fmt); - vsprintf(sbuf,fmt,argptr); - va_end(argptr); - return(startup->lputs(sbuf)); + if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) { + lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus); + WSAInitialized=TRUE; + return(TRUE); + } + + lprintf(LOG_ERR,"!WinSock startup ERROR %d", status); + return(FALSE); } -} /* extern "C" */ +#else /* No WINSOCK */ + +#define winsock_startup() (TRUE) +#define SOCKLIB_DESC NULL + +#endif + +#ifdef JAVASCRIPT + +static js_server_props_t js_server_props; + +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) /* Assertion here, in _heap_alloc_dbg, June-21-2004 */ + return(JS_FALSE); /* Caused by nntpservice.js? */ + + if(!JS_DefineProperty(cx, parent, name, OBJECT_TO_JSVAL(array) + ,NULL,NULL,flags)) + 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_TRUE); +} + +/* Convert from Synchronet-specific jsSyncMethodSpec to JSAPI's JSFunctionSpec */ + +JSBool +DLLCALL js_DescribeSyncObject(JSContext* cx, JSObject* obj, const char* str, int ver) +{ + 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) + && JS_DefineProperty(cx,obj,"_ver" + ,INT_TO_JSVAL(ver),NULL,NULL,JSPROP_READONLY)); +} + +JSBool +DLLCALL js_DescribeSyncConstructor(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 + +#if 0 +static char* server_prop_desc[] = { + + "server name and version number" + ,"detailed version/build information" + ,NULL +}; +#endif + + +static const char* method_array_name = "_method_list"; +static const char* propver_array_name = "_property_ver_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", + "alias", + "undefined" +}; + +JSBool +DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props) +{ + uint i; + jsval val; + jsuint len=0; + JSObject* array; + + if((array=JS_NewArrayObject(cx, 0, NULL))==NULL) + return(JS_FALSE); + + if(!JS_DefineProperty(cx, obj, propver_array_name, OBJECT_TO_JSVAL(array) + ,NULL,NULL,JSPROP_READONLY)) + return(JS_FALSE); + + for(i=0;props[i].name;i++) { + if(!JS_DefinePropertyWithTinyId(cx, obj, /* Never reserve any "slots" for properties */ + props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED)) + return(JS_FALSE); + if(props[i].flags&JSPROP_ENUMERATE) { /* No need to version invisible props */ + val = INT_TO_JSVAL(props[i].ver); + if(!JS_SetElement(cx, array, len++, &val)) + return(JS_FALSE); + } + } + + return(JS_TRUE); +} + +JSBool +DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *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(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array) + , NULL, NULL, 0)) + return(JS_FALSE); + + if(append) + if(!JS_GetArrayLength(cx, method_array, &len)) + return(JS_FALSE); + + for(i=0;funcs[i].name;i++) { + + if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0)) + return(JS_FALSE); + + if(funcs[i].type==JSTYPE_ALIAS) + continue; + + method = JS_NewObject(cx, NULL, NULL, method_array); /* exception here June-7-2003 */ + + 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); + } + + if(funcs[i].ver) { + val = INT_TO_JSVAL(funcs[i].ver); + JS_SetProperty(cx,method, "ver", &val); + } + + val=OBJECT_TO_JSVAL(method); + if(!JS_SetElement(cx, method_array, len+i, &val)) + return(JS_FALSE); + } + + return(JS_TRUE); +} + +#else // NON-DEBUG + +JSBool +DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props) +{ + uint i; + + for(i=0;props[i].name;i++) + if(!JS_DefinePropertyWithTinyId(cx, obj, + props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED)) + return(JS_FALSE); + + return(JS_TRUE); +} + + +JSBool +DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs, BOOL append) +{ + uint i; + + for(i=0;funcs[i].name;i++) + if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0)) + return(JS_FALSE); + return(JS_TRUE); +} + +#endif + +/* This is a stream-lined version of JS_DefineConstDoubles */ +JSBool +DLLCALL js_DefineConstIntegers(JSContext* cx, JSObject* obj, jsConstIntSpec* ints, int flags) +{ + uint i; + jsval val; + + for(i=0;ints[i].name;i++) { + if(!JS_NewNumberValue(cx, ints[i].val, &val)) + return(JS_FALSE); + + if(!JS_DefineProperty(cx, obj, ints[i].name, val ,NULL, NULL, flags)) + return(JS_FALSE); + } + + return(JS_TRUE); +} + +static JSBool +js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i=0; + int32 level=LOG_INFO; + JSString* str=NULL; + sbbs_t* sbbs; + + if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL) + return(JS_FALSE); + + if(JSVAL_IS_NUMBER(argv[i])) + JS_ValueToInt32(cx,argv[i++],&level); + + for(; ionline==ON_LOCAL) { + if(startup!=NULL && startup->event_lputs!=NULL) + startup->event_lputs(level,JS_GetStringBytes(str)); + } else + lputs(level,JS_GetStringBytes(str)); + } + + if(str==NULL) + *rval = JSVAL_VOID; + else + *rval = STRING_TO_JSVAL(str); + return(JS_TRUE); +} + +static JSBool +js_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uchar* buf; + int32 len=128; + sbbs_t* sbbs; + + if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL) + return(JS_FALSE); + + *rval = JSVAL_VOID; + + if(argc) + JS_ValueToInt32(cx,argv[0],&len); + + if((buf=(uchar*)malloc(len))==NULL) + return(JS_TRUE); + + len=RingBufRead(&sbbs->inbuf,buf,len); + + if(len>0) + *rval = STRING_TO_JSVAL(JS_NewStringCopyN(cx,(char*)buf,len)); + + free(buf); + return(JS_TRUE); +} + +static JSBool +js_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + char* buf; + int32 len=128; + sbbs_t* sbbs; + + if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL) + return(JS_FALSE); + + *rval = JSVAL_VOID; + + if(argc) + JS_ValueToInt32(cx,argv[0],&len); + + if((buf=(char*)malloc(len))==NULL) + return(JS_TRUE); + + len=sbbs->getstr(buf,len,K_NONE); + + if(len>0) + *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,buf)); + + free(buf); + return(JS_TRUE); +} + +static JSBool +js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + JSString* str=NULL; + 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(LOG_INFO,"%s",JS_GetStringBytes(str)); + else + sbbs->bputs(JS_GetStringBytes(str)); + } + + if(str==NULL) + *rval = JSVAL_VOID; + else + *rval = STRING_TO_JSVAL(str); + return(JS_TRUE); +} + +static JSBool +js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + sbbs_t* sbbs; + + if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL) + return(JS_FALSE); + + js_write(cx,obj,argc,argv,rval); + if(sbbs->online==ON_REMOTE) + sbbs->bputs(crlf); + + 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 && ionline==ON_LOCAL) + eprintf(LOG_INFO,"%s",p); + else + sbbs->bputs(p); + + *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p)); + + JS_smprintf_free(p); + + 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 jsSyncMethodSpec js_global_functions[] = { + {"log", js_log, 1, JSTYPE_STRING, JSDOCSTR("[level,] value [,value]") + ,JSDOCSTR("add a line of text to the server and/or system log, " + "values are typically string constants or variables, " + "level is the debug level/priority (default: LOG_INFO)") + ,311 + }, + {"read", js_read, 0, JSTYPE_STRING, JSDOCSTR("[count]") + ,JSDOCSTR("read up to count characters from input stream") + ,311 + }, + {"readln", js_readln, 0, JSTYPE_STRING, JSDOCSTR("[count]") + ,JSDOCSTR("read a single line, up to count characters, from input stream") + ,311 + }, + {"write", js_write, 0, JSTYPE_VOID, JSDOCSTR("value [,value]") + ,JSDOCSTR("send one or more values (typically strings) to the server output") + ,311 + }, + {"print", js_writeln, 0, JSTYPE_ALIAS }, + {"writeln", js_writeln, 0, JSTYPE_VOID, JSDOCSTR("value [,value]") + ,JSDOCSTR("send a line of text to the console or event log with automatic line termination (CRLF), " + "values are typically string constants or variables (AKA print)") + ,311 + }, + {"printf", js_printf, 1, JSTYPE_STRING, JSDOCSTR("string format [,value][,value]") + ,JSDOCSTR("print a formatted string - CAUTION: for experienced C programmers ONLY") + ,310 + }, + {"alert", js_alert, 1, JSTYPE_VOID, JSDOCSTR("value") + ,JSDOCSTR("print an alert message (ala client-side JS)") + ,310 + }, + {"prompt", js_prompt, 1, JSTYPE_STRING, JSDOCSTR("[value]") + ,JSDOCSTR("displays a prompt (value) and returns a string of user input (ala clent-side JS)") + ,310 + }, + {"confirm", js_confirm, 1, JSTYPE_BOOLEAN, JSDOCSTR("value") + ,JSDOCSTR("displays a Yes/No prompt and returns true or false " + "based on users confirmation (ala client-side JS)") + ,310 + }, + {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(LOG_ERR,"!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(LOG_ERR,"!JavaScript %s%s%s: %s",warning,file,line,message); + else { + lprintf(LOG_ERR,"!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]; + + if(cfg.node_num) + sprintf(node,"Node %d",cfg.node_num); + else + strcpy(node,client_name); + + lprintf(LOG_DEBUG,"%s JavaScript: Creating runtime: %lu bytes" + ,node,startup->js_max_bytes); + + if((js_runtime = JS_NewRuntime(startup->js_max_bytes))==NULL) + return(false); + + lprintf(LOG_DEBUG,"%s JavaScript: Initializing context (stack: %lu bytes)" + ,node,startup->js_cx_stack); + + if((js_cx = JS_NewContext(js_runtime, startup->js_cx_stack))==NULL) + return(false); + + memset(&js_branch,0,sizeof(js_branch)); + js_branch.limit = startup->js_branch_limit; + js_branch.gc_interval = startup->js_gc_interval; + js_branch.yield_interval = startup->js_yield_interval; + js_branch.terminated = &terminated; + js_branch.auto_terminate = TRUE; + + bool success=false; + + do { + + JS_SetErrorReporter(js_cx, js_ErrorReporter); + + JS_SetContextPrivate(js_cx, this); /* Store a pointer to sbbs_t instance */ + + /* Global Objects (including system, js, client, Socket, MsgBase, File, User, etc. */ + if((js_glob=js_CreateCommonObjects(js_cx, &scfg, &cfg, js_global_functions + ,uptime, startup->host_name, SOCKLIB_DESC /* system */ + ,&js_branch /* js */ + ,&client, client_socket /* client */ + ,&js_server_props /* server */ + ))==NULL) + break; + +#ifdef _DEBUG + JS_DefineProperty(js_cx, js_glob, "_global", OBJECT_TO_JSVAL(js_glob) + ,NULL,NULL,JSPROP_READONLY); +#endif + + + /* BBS Object */ + if(js_CreateBbsObject(js_cx, js_glob)==NULL) + break; + + /* Console Object */ + if(js_CreateConsoleObject(js_cx, js_glob)==NULL) + break; + + 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(LOG_ERR,"!JavaScript ERROR creating user objects"); +} +#endif /* JAVASCRIPT */ -BYTE* telnet_interpret(sbbs_t* sbbs, BYTE* inbuf, int inlen, +static BYTE* telnet_interpret(sbbs_t* sbbs, BYTE* inbuf, int inlen, BYTE* outbuf, int& outlen) { - char str[32]; BYTE* first_iac=NULL; BYTE* first_cr=NULL; int i; + if(inlen<1) { + outlen=0; + return(inbuf); // no length? No interpretation + } first_iac=(BYTE*)memchr(inbuf, TELNET_IAC, inlen); - if(!(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE)) + if(!(sbbs->telnet_mode&TELNET_MODE_GATE) + && sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL && !(sbbs->console&CON_RAW_IN)) { if(sbbs->telnet_last_rxch==CR) first_cr=inbuf; @@ -217,10 +970,15 @@ BYTE* telnet_interpret(sbbs_t* sbbs, BYT outlen=0; for(i=outlen;itelnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE)) + if(!(sbbs->telnet_mode&TELNET_MODE_GATE) + && sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL && !(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(LOG_INFO,"Node %d CR/%02Xh detected and ignored" + ,sbbs->cfg.node_num, inbuf[i]); +#endif sbbs->telnet_last_rxch=inbuf[i]; continue; } @@ -233,40 +991,127 @@ BYTE* telnet_interpret(sbbs_t* sbbs, BYT 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_cmdlentelnet_cmd)) + sbbs->telnet_cmd[sbbs->telnet_cmdlen++]=inbuf[i]; + + uchar command = sbbs->telnet_cmd[1]; + uchar option = sbbs->telnet_cmd[2]; + + if(sbbs->telnet_cmdlen>=2 && command==TELNET_SB) { + if(inbuf[i]==TELNET_SE + && sbbs->telnet_cmd[sbbs->telnet_cmdlen-2]==TELNET_IAC) { + /* sub-option terminated */ + if(option==TELNET_TERM_TYPE + && sbbs->telnet_cmd[3]==TELNET_TERM_IS) { + sprintf(sbbs->terminal,"%.*s",(int)sbbs->telnet_cmdlen-6,sbbs->telnet_cmd+4); + lprintf(LOG_DEBUG,"Node %d %s telnet terminal type: %s" + ,sbbs->cfg.node_num + ,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received" + ,sbbs->terminal); + + } else if(option==TELNET_TERM_SPEED + && sbbs->telnet_cmd[3]==TELNET_TERM_IS) { + char speed[128]; + sprintf(speed,"%.*s",(int)sbbs->telnet_cmdlen-6,sbbs->telnet_cmd+4); + lprintf(LOG_DEBUG,"Node %d %s telnet terminal speed: %s" + ,sbbs->cfg.node_num + ,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received" + ,speed); + + } else if(option==TELNET_NEGOTIATE_WINDOW_SIZE) { + long cols = (sbbs->telnet_cmd[3]<<8) | sbbs->telnet_cmd[4]; + long rows = (sbbs->telnet_cmd[5]<<8) | sbbs->telnet_cmd[6]; + lprintf(LOG_DEBUG,"Node %d %s telnet window size: %ux%u" + ,sbbs->cfg.node_num + ,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received" + ,sbbs->cols + ,sbbs->rows); + if(rows && !sbbs->useron.rows) /* auto-detect rows */ + sbbs->rows=rows; + if(cols) + sbbs->cols=cols; + + } else if(startup->options&BBS_OPT_DEBUG_TELNET) + lprintf(LOG_DEBUG,"Node %d %s unsupported telnet sub-negotiation cmd: %s" + ,sbbs->cfg.node_num + ,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received" + ,telnet_opt_desc(option)); + sbbs->telnet_cmdlen=0; + } + } + else if(sbbs->telnet_cmdlen==2 && inbuf[i]options&BBS_OPT_DEBUG_TELNET) - lprintf("Node %d %s telnet command: %s" + lprintf(LOG_DEBUG,"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])); + ,telnet_cmd_desc(option)); 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)) { - sprintf(str,"%c%c%c",TELNET_IAC,TELNET_WILL,TELNET_ECHO); - sbbs->putcom(str,3); + else if(sbbs->telnet_cmdlen>=3) { /* telnet option negotiation */ + + if(startup->options&BBS_OPT_DEBUG_TELNET) + lprintf(LOG_DEBUG,"Node %d %s telnet cmd: %s %s" + ,sbbs->cfg.node_num + ,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received" + ,telnet_cmd_desc(command) + ,telnet_opt_desc(option)); + + if(!(sbbs->telnet_mode&TELNET_MODE_GATE)) { + if(command==TELNET_DO || command==TELNET_DONT) { /* local options */ + if(sbbs->telnet_local_option[option]!=command) { + sbbs->telnet_local_option[option]=command; + sbbs->send_telnet_cmd(telnet_opt_ack(command),option); + } + } else { /* WILL/WONT (remote options) */ + if(sbbs->telnet_remote_option[option]!=command) { + + switch(option) { + case TELNET_BINARY_TX: + case TELNET_ECHO: + case TELNET_TERM_TYPE: + case TELNET_TERM_SPEED: + case TELNET_SUP_GA: + case TELNET_NEGOTIATE_WINDOW_SIZE: + sbbs->telnet_remote_option[option]=command; + sbbs->send_telnet_cmd(telnet_opt_ack(command),option); + break; + default: /* unsupported remote options */ + if(command==TELNET_WILL) /* NAK */ + sbbs->send_telnet_cmd(telnet_opt_nak(command),option); + break; + } + } + + if(command==TELNET_WILL && option==TELNET_TERM_TYPE) { + if(startup->options&BBS_OPT_DEBUG_TELNET) + lprintf(LOG_DEBUG,"Node %d requesting telnet terminal type" + ,sbbs->cfg.node_num); + + char buf[64]; + sprintf(buf,"%c%c%c%c%c%c" + ,TELNET_IAC,TELNET_SB + ,TELNET_TERM_TYPE,TELNET_TERM_SEND + ,TELNET_IAC,TELNET_SE); + sbbs->putcom(buf,6); + } + else if(command==TELNET_WILL && option==TELNET_TERM_SPEED) { + if(startup->options&BBS_OPT_DEBUG_TELNET) + lprintf(LOG_DEBUG,"Node %d requesting telnet terminal speed" + ,sbbs->cfg.node_num); + + char buf[64]; + sprintf(buf,"%c%c%c%c%c%c" + ,TELNET_IAC,TELNET_SB + ,TELNET_TERM_SPEED,TELNET_TERM_SEND + ,TELNET_IAC,TELNET_SE); + sbbs->putcom(buf,6); } } } - if(startup->options&BBS_OPT_DEBUG_TELNET) - lprintf("Node %d %s telnet command: %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]; @@ -276,6 +1121,45 @@ BYTE* telnet_interpret(sbbs_t* sbbs, BYT return(outbuf); } +void sbbs_t::send_telnet_cmd(uchar cmd, uchar opt) +{ + char buf[16]; + + if(telnet_mode&TELNET_MODE_OFF) + return; + + if(cmdoptions&BBS_OPT_DEBUG_TELNET) + lprintf(LOG_DEBUG,"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(LOG_DEBUG,"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 sbbs_t::request_telnet_opt(uchar cmd, uchar opt) +{ + if(cmd==TELNET_DO || cmd==TELNET_DONT) { /* remote option */ + if(telnet_remote_option[opt]==telnet_opt_ack(cmd)) + return; /* already set in this mode, do nothing */ + telnet_remote_option[opt]=telnet_opt_ack(cmd); + } else { /* local option */ + if(telnet_local_option[opt]==telnet_opt_ack(cmd)) + return; /* already set in this mode, do nothing */ + telnet_local_option[opt]=telnet_opt_ack(cmd); + } + send_telnet_cmd(cmd,opt); +} + void input_thread(void *arg) { BYTE inbuf[4000]; @@ -283,53 +1167,114 @@ void input_thread(void *arg) 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 high_socket; + SOCKET sock; - thread_up(); + thread_up(TRUE /* setuid */); - lprintf("Node %d input thread started",sbbs->cfg.node_num); +#ifdef _DEBUG + lprintf(LOG_DEBUG,"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; - while(sbbs->online) { + while(sbbs->online && sbbs->client_socket!=INVALID_SOCKET + && node_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) { - pthread_mutex_lock(&sbbs->input_thread_mutex); + if(pthread_mutex_lock(&sbbs->input_thread_mutex)!=0) + sbbs->errormsg(WHERE,ERR_LOCK,"input_thread_mutex",0); FD_ZERO(&socket_set); FD_SET(sbbs->client_socket,&socket_set); - + high_socket=sbbs->client_socket; +#ifdef __unix__ + if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) { + FD_SET(uspy_socket[sbbs->cfg.node_num-1],&socket_set); + if(uspy_socket[sbbs->cfg.node_num-1] > high_socket) + high_socket=uspy_socket[sbbs->cfg.node_num-1]; + } +#endif + tv.tv_sec=1; tv.tv_usec=0; - if((i=select(sbbs->client_socket+1,&socket_set,NULL,NULL,&tv))<1) { - pthread_mutex_unlock(&sbbs->input_thread_mutex); + if((i=select(high_socket+1,&socket_set,NULL,NULL,&tv))<1) { + if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0) + sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0); if(i==0) { - mswait(1); + YIELD(); /* This kludge is necessary on some Linux distros */ + continue; /* to allow other threads to lock the input_thread_mutex */ + } + + if(sbbs->client_socket==INVALID_SOCKET) + break; + + if(FD_ISSET(sbbs->client_socket,&socket_set)) { + if(ERROR_VALUE == ENOTSOCK) + lprintf(LOG_NOTICE,"Node %d socket closed by peer on input->select", sbbs->cfg.node_num); + else if(ERROR_VALUE==ESHUTDOWN) + lprintf(LOG_NOTICE,"Node %d socket shutdown on input->select", sbbs->cfg.node_num); + else if(ERROR_VALUE==EINTR) + lprintf(LOG_NOTICE,"Node %d input thread interrupted",sbbs->cfg.node_num); + else if(ERROR_VALUE==ECONNRESET) + lprintf(LOG_NOTICE,"Node %d connection reset by peer on input->select", sbbs->cfg.node_num); + else if(ERROR_VALUE==ECONNABORTED) + lprintf(LOG_NOTICE,"Node %d connection aborted by peer on input->select", sbbs->cfg.node_num); + else + lprintf(LOG_WARNING,"Node %d !ERROR %d input->select socket %d" + ,sbbs->cfg.node_num, ERROR_VALUE, sbbs->client_socket); + break; + } +#ifdef __unix__ + else if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET && + FD_ISSET(uspy_socket[sbbs->cfg.node_num-1],&socket_set)) { + if(ERROR_VALUE != EAGAIN) { + lprintf(LOG_ERR,"Node %d !ERROR %d on local spy socket %d input->select" + , sbbs->cfg.node_num, errno, uspy_socket[sbbs->cfg.node_num-1]); + close_socket(uspy_socket[sbbs->cfg.node_num-1]); + uspy_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET; + } continue; } +#endif + } - if(ERROR_VALUE == ENOTSOCK) - lprintf("Node %d socket closed by peer 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, sbbs->client_socket); - sbbs->online=0; + if(sbbs->client_socket==INVALID_SOCKET) + break; + + if(FD_ISSET(sbbs->client_socket,&socket_set)) + sock=sbbs->client_socket; +#ifdef __unix__ + else if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET + && FD_ISSET(uspy_socket[sbbs->cfg.node_num-1],&socket_set)) { + if(!socket_check(uspy_socket[sbbs->cfg.node_num-1],NULL,NULL,0)) { + close_socket(uspy_socket[sbbs->cfg.node_num-1]); + lprintf(LOG_NOTICE,"Closing local spy socket: %d",uspy_socket[sbbs->cfg.node_num-1]); + uspy_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET; + continue; + } + sock=uspy_socket[sbbs->cfg.node_num-1]; + } +#endif + else + continue; + + if(sbbs->client_socket==INVALID_SOCKET) { + if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0) + sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0); break; } rd=RingBufFree(&sbbs->inbuf); if(!rd) { // input buffer full + lprintf(LOG_WARNING,"Node %d !WARNING input buffer full", sbbs->cfg.node_num); // wait up to 5 seconds to empty (1 byte min) time_t start=time(NULL); while((rd=RingBufFree(&sbbs->inbuf))==0) { @@ -337,241 +1282,360 @@ void input_thread(void *arg) rd=1; break; } - mswait(1); + YIELD(); } } if(rd > (int)sizeof(inbuf)) rd=sizeof(inbuf); - rd = recv(sbbs->client_socket, (char*)inbuf, rd, 0); + rd = recv(sock, (char*)inbuf, rd, 0); - pthread_mutex_unlock(&sbbs->input_thread_mutex); + if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0) + sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0); if(rd == SOCKET_ERROR) { - 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==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, sbbs->client_socket); - sbbs->online=0; - break; +#ifdef __unix__ + if(sock==sbbs->client_socket) { +#endif + if(ERROR_VALUE == ENOTSOCK) + lprintf(LOG_NOTICE,"Node %d socket closed by peer on receive", sbbs->cfg.node_num); + else if(ERROR_VALUE==ECONNRESET) + lprintf(LOG_NOTICE,"Node %d connection reset by peer on receive", sbbs->cfg.node_num); + else if(ERROR_VALUE==ESHUTDOWN) + lprintf(LOG_NOTICE,"Node %d socket shutdown on receive", sbbs->cfg.node_num); + else if(ERROR_VALUE==ECONNABORTED) + lprintf(LOG_NOTICE,"Node %d connection aborted by peer on receive", sbbs->cfg.node_num); + else + lprintf(LOG_WARNING,"Node %d !ERROR %d receiving from socket %d" + ,sbbs->cfg.node_num, ERROR_VALUE, sock); + break; +#ifdef __unix__ + } else { + if(ERROR_VALUE != EAGAIN) { + lprintf(LOG_ERR,"Node %d !ERROR %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) + if(rd == 0 && sock==sbbs->client_socket) { - lprintf("Node %d disconnected", sbbs->cfg.node_num); - sbbs->online=0; + lprintf(LOG_NOTICE,"Node %d disconnected", sbbs->cfg.node_num); break; } total_recv+=rd; + total_pkts++; // telbuf and wr are modified to reflect telnet escaped data - wrbuf=telnet_interpret(sbbs, inbuf, rd, telbuf, wr); + wr=rd; +#ifdef __unix__ + if(sock!=sbbs->client_socket) + wrbuf=inbuf; + else +#endif + if(sbbs->telnet_mode&TELNET_MODE_OFF) + wrbuf=inbuf; + else + wrbuf=telnet_interpret(sbbs, inbuf, rd, telbuf, wr); if(wr > (int)sizeof(telbuf)) - lprintf("!TELBUF OVERFLOW (%d>%d)",wr,sizeof(telbuf)); + lprintf(LOG_ERR,"!TELBUF OVERFLOW (%d>%d)",wr,sizeof(telbuf)); + + /* First level Ctrl-C checking */ + if(!(sbbs->cfg.ctrlkey_passthru&(1<rio_abortable + && !(sbbs->telnet_mode&TELNET_MODE_GATE) + && sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL + && memchr(wrbuf, CTRL_C, wr)) { + if(RingBufFull(&sbbs->inbuf)) + lprintf(LOG_DEBUG,"Node %d Ctrl-C hit with %lu bytes in input buffer" + ,sbbs->cfg.node_num,RingBufFull(&sbbs->inbuf)); + if(RingBufFull(&sbbs->outbuf)) + lprintf(LOG_DEBUG,"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(availinbuf, wrbuf, wr); // if(wr>100) // mswait(500); // Throttle sender } + sbbs->online=0; + sbbs->sys_status|=SS_ABORT; /* as though Ctrl-C were hit */ 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); - lprintf("Node %d input thread terminated (total bytes received: %lu)" - ,sbbs->cfg.node_num, total_recv); - thread_down(); + lprintf(LOG_DEBUG,"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 short_sends=0; ulong bufbot=0; ulong buftop=0; sbbs_t* sbbs = (sbbs_t*) arg; + fd_set socket_set; + struct timeval tv; - thread_up(); + thread_up(TRUE /* setuid */); if(sbbs->cfg.node_num) sprintf(node,"Node %d",sbbs->cfg.node_num); else strcpy(node,sbbs->client_name); - lprintf("%s output thread started",node); +#ifdef _DEBUG + lprintf(LOG_DEBUG,"%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) { + while(sbbs->client_socket!=INVALID_SOCKET && !terminate_server) { + if(bufbot==buftop) avail=RingBufFull(&sbbs->outbuf); else avail=buftop-bufbot; if(!avail) { - sem_init(&sbbs->output_sem,0,0); - sem_wait(&sbbs->output_sem); - continue; } + 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(LOG_ERR,"!%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"); + lprintf(LOG_WARNING,"!%s: Insufficient linear output buffer (%lu > %lu)" + ,node, avail, sizeof(buf)); avail=sizeof(buf); } buftop=RingBufRead(&sbbs->outbuf, buf, avail); bufbot=0; } - i = send(sbbs->client_socket, (char*)buf+bufbot, buftop-bufbot, 0); - if(i == SOCKET_ERROR) - { + 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); + lprintf(LOG_NOTICE,"%s client socket closed on send", node); else if(ERROR_VALUE==ECONNRESET) - lprintf("%s connection reset by peer on send", node); + lprintf(LOG_NOTICE,"%s connection reset by peer on send", node); else if(ERROR_VALUE==ECONNABORTED) - lprintf("%s connection aborted by peer on send", node); + lprintf(LOG_NOTICE,"%s connection aborted by peer on send", node); else - lprintf("!%s: ERROR %d sending on socket %d" + lprintf(LOG_WARNING,"!%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 && !(sbbs->sys_status&SS_FILEXFER)) { + 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) + && 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) - send(spy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i,0); + 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 } + if(i!=(int)(buftop-bufbot)) { + lprintf(LOG_WARNING,"!%s: Short socket send (%u instead of %u)" + ,node, i ,buftop-bufbot); + short_sends++; + } bufbot+=i; total_sent+=i; + total_pkts++; } sbbs->spymsg("Disconnected"); sbbs->output_thread_running = false; - lprintf("%s output thread terminated (total bytes sent: %lu)", node, total_sent); + if(total_sent) + sprintf(stats,"(sent %lu bytes in %lu blocks, %lu average, %lu short)" + ,total_sent, total_pkts, total_sent/total_pkts, short_sends); + else + stats[0]=0; thread_down(); + lprintf(LOG_DEBUG,"%s output thread terminated %s", node, stats); } void event_thread(void* arg) { - char str[256]; - char semfile[128]; + 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; + bool packed_rep; ulong l; time_t now; - struct tm now_tm; - struct tm* tm; - time_t lastnodechk; + 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; - lprintf("BBS Events thread started"); + eprintf(LOG_DEBUG,"BBS Events thread started"); sbbs->event_thread_running = true; - thread_up(); - - while(1) { - - pthread_mutex_lock(&event_mutex); - - if(sbbs->terminated || telnet_socket==INVALID_SOCKET) - break; - - if(scfg_reloaded==true) { + srand(time(NULL)); /* Seed random number generator */ + sbbs_random(10); /* Throw away first number */ - for(i=0;itext[i]=sbbs->text_sav[i]=text[i]; + thread_up(TRUE /* setuid */); - memcpy(&sbbs->cfg,&scfg,sizeof(scfg_t)); - - prep_dir(sbbs->cfg.data_dir, sbbs->cfg.temp_dir); +#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(LOG_ERR,"!JavaScript Initialization FAILURE"); + } +#endif - // 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); - return; - } - for(i=0;icfg.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 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); + else { + for(i=0;icfg.total_events;i++) { + sbbs->cfg.event[i]->last=0; + if(filelength(file)<(long)(sizeof(time_t)*(i+1))) { + eprintf(LOG_WARNING,"Initializing last run time for event: %s" + ,sbbs->cfg.event[i]->code); + write(file,&sbbs->cfg.event[i]->last,sizeof(time_t)); + } else { + if(read(file,&sbbs->cfg.event[i]->last,sizeof(time_t))!=sizeof(time_t)) + sbbs->errormsg(WHERE,ERR_READ,str,sizeof(time_t)); } - read(file,&lastprepack,sizeof(time_t)); - close(file); + /* Event always runs after initialization? */ + if(sbbs->cfg.event[i]->misc&EVENT_INIT) + sbbs->cfg.event[i]->last=-1; + } + lastprepack=0; + read(file,&lastprepack,sizeof(time_t)); /* expected to fail first time */ + 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;icfg.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)); + // 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); + else { + for(i=0;icfg.total_qhubs;i++) { + sbbs->cfg.qhub[i]->last=0; + if(filelength(file)<(long)(sizeof(time_t)*(i+1))) { + eprintf(LOG_WARNING,"Initializing last call-out time for QWKnet hub: %s" + ,sbbs->cfg.qhub[i]->id); + write(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t)); + } else { + if(read(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t))!=sizeof(time_t)) + sbbs->errormsg(WHERE,ERR_READ,str,sizeof(time_t)); } - close(file); + } + 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); - return; - } - for(i=0;icfg.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); + // 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); + else { + for(i=0;icfg.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; + while(!sbbs->terminated && !terminate_server) { + + if(startup->options&BBS_OPT_NO_EVENTS) { + SLEEP(1000); + continue; } now=time(NULL); - now_tm=*gmtime(&now); + localtime_r(&now,&now_tm); + + if(now-lastsemchk>=sbbs->cfg.node_sem_check) { + check_semaphores=true; + lastsemchk=now; + } else + check_semaphores=false; + + sbbs->online=0; /* reset this from ON_LOCAL */ /* QWK events */ - if(!(startup->options&BBS_OPT_NO_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); @@ -581,12 +1645,19 @@ void event_thread(void* arg) sbbs->useron.number=atoi(g.gl_pathv[i]+offset); getuserdat(&sbbs->cfg,&sbbs->useron); if(sbbs->useron.number && flength(g.gl_pathv[i])>0) { + sprintf(semfile,"%s.lock",g.gl_pathv[i]); + if(!fmutex(semfile,startup->host_name)) + continue; sbbs->online=ON_LOCAL; - lprintf("Un-packing QWK Reply packet from %s",sbbs->useron.alias); + eprintf(LOG_INFO,"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]); + remove(semfile); } } globfree(&g); @@ -597,39 +1668,49 @@ void event_thread(void* arg) glob(str,0,NULL,&g); for(i=0;i<(int)g.gl_pathc;i++) { sbbs->useron.number=atoi(g.gl_pathv[i]+offset); + sprintf(semfile,"%spack%04u.lock",sbbs->cfg.data_dir,sbbs->useron.number); + if(!fmutex(semfile,startup->host_name)) + continue; getuserdat(&sbbs->cfg,&sbbs->useron); if(sbbs->useron.number && !(sbbs->useron.misc&(DELETED|INACTIVE))) { - lprintf("Packing QWK Message Packet for %s",sbbs->useron.alias); + eprintf(LOG_INFO,"Packing QWK Message Packet for %s",sbbs->useron.alias); sbbs->online=ON_LOCAL; - sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + 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,1)) { - lprintf("Packing completed"); + ,sbbs->cfg.data_dir,PATH_DELIM,sbbs->useron.number); + if(sbbs->pack_qwk(str,&l,true /* pre-pack/off-line */)) { + eprintf(LOG_INFO,"Packing completed"); sbbs->qwk_success(l,0,1); sbbs->putmsgptrs(); + remove(bat_list); } else - lprintf("No packet created (no new messages)"); - sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + eprintf(LOG_INFO,"No packet created (no new messages)"); + delfiles(sbbs->cfg.temp_dir,ALLFILES); sbbs->online=0; } remove(g.gl_pathv[i]); + remove(semfile); } 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] - && (fexist(semfile) || (now-lastprepack)/60>(60*24))) { + && (fexistcase(semfile) || (now-lastprepack)/60>(60*24))) { j=lastuser(&sbbs->cfg); - lprintf("Pre-packing QWK Message packets..."); + eprintf(LOG_INFO,"Pre-packing QWK Message packets..."); for(i=1;i<=j;i++) { sprintf(str,"%5u of %-5u",i,j); - status(str); + //status(str); sbbs->useron.number=i; getuserdat(&sbbs->cfg,&sbbs->useron); @@ -637,26 +1718,27 @@ void event_thread(void* arg) && !(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++) { - sbbs->getnodedat(k,&node,0); + 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; - lprintf("Pre-packing QWK for %s",sbbs->useron.alias); + eprintf(LOG_INFO,"Pre-packing QWK for %s",sbbs->useron.alias); sbbs->online=ON_LOCAL; - sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + 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,1)) { + ,sbbs->cfg.data_dir,PATH_DELIM,sbbs->useron.number); + if(sbbs->pack_qwk(str,&l,true /* pre-pack */)) { sbbs->qwk_success(l,0,1); sbbs->putmsgptrs(); } - sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + delfiles(sbbs->cfg.temp_dir,ALLFILES); sbbs->online=0; } } @@ -664,111 +1746,144 @@ void event_thread(void* arg) 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; } + break; + } lseek(file,(long)sbbs->cfg.total_events*4L,SEEK_SET); write(file,&lastprepack,sizeof(time_t)); close(file); remove(semfile); - status(STATUS_WFC); + //status(STATUS_WFC); } } - /* Node Daily Events */ - for(i=startup->first_node;i<=startup->last_node;i++) { - // Node Daily Event - sbbs->getnodedat(i,&node,0); - 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]); + 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]); - status("Running node daily event"); - sbbs->logentry("!:","Run node daily event"); - sbbs->external( - sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL) - ,EX_OFFLINE); - status(STATUS_WFC); - } - sbbs->getnodedat(i,&node,1); - node.misc&=~NODE_EVENT; - node.status=NODE_WFC; - node.useron=0; - sbbs->putnodedat(i,&node); + eprintf(LOG_INFO,"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;icfg.total_qhubs;i++) - if(sbbs->cfg.qhub[i]->node>=startup->first_node - && sbbs->cfg.qhub[i]->node<=startup->last_node) { + /* QWK Networking Call-out sempahores */ + for(i=0;icfg.total_qhubs;i++) { + if(sbbs->cfg.qhub[i]->nodecfg.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(fexist(str)) + if(fexistcase(str)) { + strcpy(str,sbbs->cfg.qhub[i]->id); + eprintf(LOG_INFO,"Semaphore signaled for QWK Network Hub: %s",strupr(str)); sbbs->cfg.qhub[i]->last=-1; + } } - /* Timed Event sempahores */ - for(i=0;icfg.total_events;i++) - if((sbbs->cfg.event[i]->node>=startup->first_node - && sbbs->cfg.event[i]->node<=startup->last_node) - || sbbs->cfg.event[i]->misc&EVENT_EXCL) { + /* Timed Event sempahores */ + for(i=0;icfg.total_events;i++) { + if((sbbs->cfg.event[i]->nodecfg.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]->misc&EVENT_DISABLED) + continue; + 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(fexist(str)) + if(fexistcase(str)) { + strcpy(str,sbbs->cfg.event[i]->code); + eprintf(LOG_INFO,"Semaphore signaled for Timed Event: %s",strupr(str)); sbbs->cfg.event[i]->last=-1; + } } + } /* QWK Networking Call-out Events */ for(i=0;icfg.total_qhubs;i++) { - if(sbbs->cfg.qhub[i]->nodefirst_node || - sbbs->cfg.qhub[i]->node>startup->last_node) + if(sbbs->cfg.qhub[i]->nodecfg.qhub[i]->node>last_node) continue; - // 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'); - if(fexist(str)) { - sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); - if(sbbs->unpack_qwk(str,i)==false) { - char newname[MAX_PATH+1]; - sprintf(newname,"%s.%lx.bad",str,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); + 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'); + if(fexistcase(str) && flength(str)>0) { /* silently ignore 0-byte QWK packets */ + delfiles(sbbs->cfg.temp_dir,ALLFILES); + sbbs->online=ON_LOCAL; + sbbs->console|=CON_L_ECHO; + 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); + } } - } - remove(str); - } + sbbs->console&=~CON_L_ECHO; + sbbs->online=0; + remove(str); + } + } } - tm=gmtime(&sbbs->cfg.qhub[i]->last); /* Qnet call out based on time */ - if((tm==NULL || sbbs->cfg.qhub[i]->last==-1L /* or frequency */ + /* 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))) + && (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon))) && sbbs->cfg.qhub[i]->days&(1<cfg.data_dir,sbbs->cfg.qhub[i]->id); - remove(str); /* Remove semaphore file */ + 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;jcfg.qhub[i]->subs;j++) { - sbbs->sub_ptr[sbbs->cfg.qhub[i]->sub[j]]=0; - lseek(file,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*4L,SEEK_SET); - read(file,&sbbs->sub_ptr[sbbs->cfg.qhub[i]->sub[j]],4); + 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)) { + sbbs->console|=CON_L_ECHO; + packed_rep=sbbs->pack_rep(i); + sbbs->console&=~CON_L_ECHO; + if(packed_rep) { if((file=sbbs->nopen(str,O_WRONLY|O_CREAT))==-1) sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_CREAT); else { @@ -776,10 +1891,15 @@ void event_thread(void* arg) 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*4L,SEEK_SET); - write(file,&sbbs->sub_ptr[sbbs->cfg.qhub[i]->sub[j]],4); } - close(file); } } - sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + 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); @@ -793,35 +1913,33 @@ void event_thread(void* arg) 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]); - status("QWK Networking"); -#if 0 - sbbs->getnodedat(sbbs->cfg.qhub[i]->node,&node,1); - node.status=NODE_NETTING; - sbbs->putnodedat(sbbs->cfg.qhub[i]->node,&node); -#endif + eprintf(LOG_INFO,"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); - status(STATUS_WFC); + ,EX_OFFLINE|EX_SH); /* sh for Unix perl scripts */ } } } /* PostLink Networking Call-out Events */ for(i=0;icfg.total_phubs;i++) { - if(sbbs->cfg.phub[i]->nodefirst_node - || sbbs->cfg.phub[i]->node>startup->last_node) + if(sbbs->cfg.phub[i]->nodecfg.phub[i]->node>last_node) continue; - tm=localtime(&sbbs->cfg.phub[i]->last); /* PostLink call out based on time */ - if(tm==NULL) - continue; - if(((sbbs->cfg.phub[i]->freq /* or frequency */ + /* 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<cfg.phub[i]->days&(1<cfg.phub[i]->last=time(NULL); sprintf(str,"%spnet.dab",sbbs->cfg.ctrl_dir); @@ -834,90 +1952,107 @@ void event_thread(void* arg) close(file); if(sbbs->cfg.phub[i]->call[0]) { - sbbs->cfg.node_num=sbbs->cfg.event[i]->node; + 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]); - status("PostLink Networking"); -#if 0 - sbbs->getnodedat(sbbs->cfg.phub[i]->node,&node,1); - node.status=NODE_NETTING; - sbbs->putnodedat(sbbs->cfg.phub[i]->node,&node); -#endif + eprintf(LOG_INFO,"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); - status(STATUS_WFC); + ,EX_OFFLINE|EX_SH); /* sh for Unix perl scripts */ } } } /* Timed Events */ for(i=0;icfg.total_events;i++) { - if(!sbbs->cfg.event[i]->node || sbbs->cfg.event[i]->node>sbbs->cfg.sys_nodes) + 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]->misc&EVENT_DISABLED) continue; - tm=gmtime(&sbbs->cfg.event[i]->last); - if(tm==NULL || sbbs->cfg.event[i]->last==-1 - || ((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<cfg.event[i]->nodecfg.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<cfg.event[i]->mdays==0 + || sbbs->cfg.event[i]->mdays&(1<cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */ - if(sbbs->cfg.event[i]->nodefirst_node - || sbbs->cfg.event[i]->node>startup->last_node) { - sprintf(str,"Waiting for node %d to run timed event." - ,sbbs->cfg.event[i]->node); - status(str); + if(sbbs->cfg.event[i]->nodecfg.event[i]->node>last_node) { + eprintf(LOG_INFO,"Waiting for node %d to run timed event: %s" + ,sbbs->cfg.event[i]->node,sbbs->cfg.event[i]->code); + eprintf(LOG_DEBUG,"%s event last run: %s (0x%08lx)" + ,sbbs->cfg.event[i]->code + ,timestr(&sbbs->cfg, &sbbs->cfg.event[i]->last, str) + ,sbbs->cfg.event[i]->last); lastnodechk=0; /* really last event time check */ + start=time(NULL); while(!sbbs->terminated) { mswait(1000); now=time(NULL); - if(now-lastnodechk<10) + if(now-start>10 && now-lastnodechk<10) continue; - for(j=startup->first_node;j<=startup->last_node;j++) { - sbbs->getnodedat(j,&node,1); + 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); } -#if 0 - getnodedat(node_num,&thisnode,0); - if(thisnode.misc&NODE_DOWN) - return(0); -#endif 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; } + 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; } + break; + if(now-start>(90*60)) { + eprintf(LOG_WARNING,"!TIMEOUT waiting for event to complete"); + break; + } + } sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code); - remove(str); + if(fexistcase(str)) + remove(str); sbbs->cfg.event[i]->last=now; } else { // Exclusive event to run on a node under our control - status("Waiting for all nodes to become inactive before " - "running timed event."); + eprintf(LOG_INFO,"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) + if(now-start>10 && now-lastnodechk<10) continue; lastnodechk=now; -#if 0 - getnodedat(node_num,&thisnode,0); - if(thisnode.misc&NODE_DOWN) - return(0); -#endif // Check/change the status of the nodes that we're in control of - for(j=startup->first_node;j<=startup->last_node;j++) { - sbbs->getnodedat(j,&node,1); + 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; @@ -929,7 +2064,8 @@ void event_thread(void* arg) } for(j=1;j<=sbbs->cfg.sys_nodes;j++) { - sbbs->getnodedat(j,&node,0); + if(sbbs->getnodedat(j,&node,0)!=0) + continue; if(j==sbbs->cfg.event[i]->node) { if(node.status!=NODE_EVENT_WAITING) break; @@ -941,108 +2077,140 @@ void event_thread(void* arg) } if(j>sbbs->cfg.sys_nodes) /* all nodes either offline or in limbo */ break; - sprintf(str,"Waiting for node %d (status=%d)" - ,j,node.status); - status(str); + eprintf(LOG_DEBUG,"Waiting for node %d (status=%d)",j,node.status); + if(now-start>(90*60)) { + eprintf(LOG_WARNING,"!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; } - if(sbbs->cfg.event[i]->nodefirst_node - || sbbs->cfg.event[i]->node>startup->last_node) { +#endif + if(sbbs->cfg.event[i]->nodecfg.event[i]->node>last_node) { + eprintf(LOG_NOTICE,"Changing node status for nodes %d through %d to WFC" + ,first_node,last_node); sbbs->cfg.event[i]->last=now; - for(j=startup->first_node;j<=startup->last_node;j++) { - sbbs->getnodedat(j,&node,1); + 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); - remove(str); + 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); } - lprintf("Running event: %s",sbbs->cfg.event[i]->code); + strcpy(str,sbbs->cfg.event[i]->code); + eprintf(LOG_INFO,"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,nulstr,NULL) - ,EX_OFFLINE + 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; } + 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=startup->first_node;j<=startup->last_node;j++) { - sbbs->getnodedat(j,&node,1); + 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); } } } - status(STATUS_WFC); } } - pthread_mutex_unlock(&event_mutex); - mswait(1000); } + sbbs->cfg.node_num=0; sbbs->event_thread_running = false; - lprintf("BBS Event thread terminated"); - thread_down(); - + eprintf(LOG_DEBUG,"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[]) + scfg_t* global_cfg, char* global_text[], client_t* client_info) { char nodestr[32]; uint i; if(node_num) - sprintf(nodestr,"BBS Node %d",node_num); + sprintf(nodestr,"Node %d",node_num); else strcpy(nodestr,name); - lprintf("%s constructor using socket %d", nodestr, sd); + lprintf(LOG_DEBUG,"%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) { + if(node_num>0) { strcpy(cfg.node_dir, cfg.node_path[node_num-1]); - prep_dir(cfg.node_dir, cfg.temp_dir); + 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); + 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; - sprintf(client_name, "%.*s", (int)sizeof(client_name)-1, name); + SAFECOPY(client_name, name); client_socket_dup=INVALID_SOCKET; + client_ident[0]=0; + + terminal[0]=0; /* Init some important variables */ @@ -1052,6 +2220,9 @@ sbbs_t::sbbs_t(ushort node_num, DWORD ad 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; @@ -1059,22 +2230,30 @@ sbbs_t::sbbs_t(ushort node_num, DWORD ad uselect_total = 0; lbuflen = 0; connection="Telnet"; + + ZERO_VAR(telnet_local_option); + ZERO_VAR(telnet_remote_option); + telnet_cmdlen=0; telnet_mode=0; telnet_last_rxch=0; - strcpy(cap_fname,"CAPTURE.TXT"); + sys_status=lncntr=tos=criterrs=keybufbot=keybuftop=lbuflen=slcnt=0L; - debug=1; curatr=LIGHTGRAY; + attr_sp=0; /* attribute stack pointer */ errorlevel=0; logcol=1; - next_event=0; - lastuseron[0]=0; 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;i0) { 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) { @@ -1163,11 +2340,11 @@ bool sbbs_t::init() addr_len=sizeof(addr); if((result=getsockname(client_socket, (struct sockaddr *)&addr,&addr_len))!=0) { - lprintf("Node %d !ERROR %d (%d) getting address/port" + lprintf(LOG_ERR,"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" + lprintf(LOG_INFO,"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; @@ -1181,28 +2358,25 @@ bool sbbs_t::init() #endif ); if(comspec==NULL) { - errormsg(WHERE, ERR_CHK, "shell/comspec", 0); - return(false); - } - -#ifdef _WIN32 - output_sem=CreateEvent( - NULL // pointer to security attributes - ,true // flag for manual-reset event - ,false // flag for initial state - ,NULL // pointer to event-object name - ); - if(output_sem==NULL) { - errormsg(WHERE, ERR_CREATE, "output_sem", 0); + errormsg(WHERE, ERR_CHK, +#ifdef __unix__ + "SHELL" +#else + "COMSPEC" +#endif + " environment variable", 0); +#if defined(__unix__) + #if defined(_PATH_BSHELL) + comspec = _PATH_BSHELL; + #else + comspec = "/bin/sh"; + #endif +#else return(false); - } #endif - sem_init(&output_sem,0,0); + } - strcpy(str,cfg.temp_dir); - if(strcmp(str+1,":\\") && strcmp(str+1,":/")) /* not root directory */ - str[strlen(str)-1]=0; /* chop off '\' */ - md(str); + md(cfg.temp_dir); /* Shared NODE files */ sprintf(str,"%s%s",cfg.ctrl_dir,"node.dab"); @@ -1214,43 +2388,46 @@ bool sbbs_t::init() node.status=NODE_OFFLINE; while(filelength(nodefile)<(long)(cfg.sys_nodes*sizeof(node_t))) { lseek(nodefile,0L,SEEK_END); - write(nodefile,&node,sizeof(node_t)); + if(write(nodefile,&node,sizeof(node_t))!=sizeof(node_t)) { + errormsg(WHERE,ERR_WRITE,str,sizeof(node_t)); + break; + } } - if(cfg.node_num) { - if(lock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t)) - || unlock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t))) { - errormsg(WHERE, ERR_LOCK, str, cfg.node_num); - return(false); + for(i=0; cfg.node_num>0 && i=LOOP_NODEDAB) { + errormsg(WHERE, ERR_LOCK, str, cfg.node_num); return(false); } -#endif 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); } + lprintf(LOG_ERR,"Perhaps this node is already running"); + return(false); + } if(filelength(fileno(logfile_fp))) { log(crlf); now=time(NULL); - struct tm * tm=localtime(&now); - if(tm!=NULL) - sprintf(str,"%02d:%02d%c %s %s %02d %u " + struct tm tm; + localtime_r(&now,&tm); + sprintf(str,"%s %s %s %02d %u " "End of preexisting log entry (possible crash)" - ,tm->tm_hour>12 ? tm->tm_hour-12 : tm->tm_hour==0 ? 12 : tm->tm_hour - ,tm->tm_min,tm->tm_hour>=12 ? 'p' : 'a',wday[tm->tm_wday] - ,mon[tm->tm_mon],tm->tm_mday,tm->tm_year+1900); + ,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); @@ -1261,7 +2438,7 @@ bool sbbs_t::init() thisnode.action=0; thisnode.useron=0; thisnode.aux=0; - thisnode.misc&=(NODE_EVENT|NODE_LOCK); + thisnode.misc&=(NODE_EVENT|NODE_LOCK|NODE_RRUN); criterrs=thisnode.errors; putnodedat(cfg.node_num,&thisnode); } @@ -1281,59 +2458,34 @@ bool sbbs_t::init() if(cfg.total_grps) { - if((cursub=(uint *)MALLOC(sizeof(uint)*cfg.total_grps))==NULL) { - errormsg(WHERE, ERR_ALLOC, "cursub", sizeof(uint)*cfg.total_grps); - return(false); - } - - if((usrgrp=(uint *)MALLOC(sizeof(uint)*cfg.total_grps))==NULL) { - errormsg(WHERE, ERR_ALLOC, "usrgrp", sizeof(uint)*cfg.total_grps); - return(false); - } + usrgrp_total = cfg.total_grps; - if((usrsubs=(uint *)MALLOC(sizeof(uint)*cfg.total_grps))==NULL) { - errormsg(WHERE, ERR_ALLOC, "usrsubs", sizeof(uint)*cfg.total_grps); + if((cursub=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) { + errormsg(WHERE, ERR_ALLOC, "cursub", sizeof(uint)*usrgrp_total); return(false); } - if((usrsub=(uint **)MALLOC(sizeof(uint *)*cfg.total_grps))==NULL) { - errormsg(WHERE, ERR_ALLOC, "usrsub", sizeof(uint)*cfg.total_grps); - return(false); - } - - if((sub_cfg=(ushort *)MALLOC(sizeof(ushort)*cfg.total_subs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "sub_cfg", sizeof(ushort)*cfg.total_subs); + if((usrgrp=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrgrp", sizeof(uint)*usrgrp_total); return(false); } - if((sub_ptr=(ulong *)MALLOC(sizeof(ulong)*cfg.total_subs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "sub_ptr", sizeof(ulong)*cfg.total_subs); + if((usrsubs=(uint *)MALLOC(sizeof(uint)*usrgrp_total))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrsubs", sizeof(uint)*usrgrp_total); return(false); } - if((sub_last=(ulong *)MALLOC(sizeof(ulong)*cfg.total_subs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "sub_last", sizeof(ulong)*cfg.total_subs); - return(false); - } - - if((sav_sub_cfg=(ushort *)MALLOC(sizeof(ushort)*cfg.total_subs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "sav_sub_cfg", sizeof(ushort)*cfg.total_subs); + if((usrsub=(uint **)calloc(usrgrp_total,sizeof(uint *)))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrsub", sizeof(uint)*usrgrp_total); return(false); } - - if((sav_sub_ptr=(ulong *)MALLOC(sizeof(ulong)*cfg.total_subs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "sav_sub_ptr", sizeof(ulong)*cfg.total_subs); + + 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); } - - if((sav_sub_last=(ulong *)MALLOC(sizeof(ulong)*cfg.total_subs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "sav_sub_last", sizeof(ulong)*cfg.total_subs); - return(false); - } - } - for(i=l=0;i<(uint)cfg.total_grps;i++) { for(j=k=0;jgrp==i) @@ -1349,23 +2501,25 @@ bool sbbs_t::init() if(cfg.total_libs) { - if((curdir=(uint *)MALLOC(sizeof(uint)*cfg.total_libs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "curdir", sizeof(uint)*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)*cfg.total_libs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "usrlib", sizeof(uint)*cfg.total_libs); + 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)*cfg.total_libs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "usrdirs", sizeof(uint)*cfg.total_libs); + if((usrdirs=(uint *)MALLOC(sizeof(uint)*usrlib_total))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrdirs", sizeof(uint)*usrlib_total); return(false); } - if((usrdir=(uint **)MALLOC(sizeof(uint *)*cfg.total_libs))==NULL) { - errormsg(WHERE, ERR_ALLOC, "usrdir", sizeof(uint)*cfg.total_libs); + if((usrdir=(uint **)calloc(usrlib_total,sizeof(uint *)))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrdir", sizeof(uint)*usrlib_total); return(false); } } @@ -1454,15 +2608,6 @@ bool sbbs_t::init() reset_logon_vars(); -#if 0 - lprintf("temp_dir: %s", cfg.temp_dir); - lprintf("node_dir: %s", cfg.node_dir); - lprintf("ctrl_dir: %s", cfg.ctrl_dir); - lprintf("data_dir: %s", cfg.data_dir); - lprintf("text_dir: %s", cfg.text_dir); - lprintf("exec_dir: %s", cfg.exec_dir); -#endif - online=ON_REMOTE; return(true); @@ -1478,35 +2623,24 @@ sbbs_t::~sbbs_t() sprintf(node,"Node %d", cfg.node_num); else strcpy(node,client_name); +#ifdef _DEBUG + lprintf(LOG_DEBUG,"%s destructor begin", node); +#endif - lprintf("%s destructor", node); - - hangup(); /* close socket handle */ +// if(!cfg.node_num) +// rmdir(cfg.temp_dir); if(client_socket_dup!=INVALID_SOCKET) closesocket(client_socket_dup); /* close duplicate handle */ - sem_post(&output_sem); /* just incase someone's waiting */ - sem_destroy(&output_sem); - - if(cfg.node_num) + if(cfg.node_num>0) node_inbuf[cfg.node_num-1]=NULL; - if(!input_thread_running) + if(cfg.node_num>0 && !input_thread_running) RingBufDispose(&inbuf); if(!output_thread_running) RingBufDispose(&outbuf); /* Close all open files */ -#if 0 /* old way, not compatible with Linux Samba client */ - if(nodefile_fp!=NULL) { - fclose(nodefile_fp); - nodefile_fp=NULL; - } - if(node_ext_fp!=NULL) { - fclose(node_ext_fp); - node_ext_fp=NULL; - } -#else if(nodefile!=-1) { close(nodefile); nodefile=-1; @@ -1515,7 +2649,6 @@ sbbs_t::~sbbs_t() close(node_ext); node_ext=-1; } -#endif if(logfile_fp!=NULL) { fclose(logfile_fp); logfile_fp=NULL; @@ -1525,6 +2658,21 @@ sbbs_t::~sbbs_t() /* Free allocated class members */ /********************************/ +#ifdef JAVASCRIPT + /* Free Context */ + if(js_cx!=NULL) { + lprintf(LOG_DEBUG,"%s JavaScript: Destroying context",node); + JS_DestroyContext(js_cx); + js_cx=NULL; + } + + if(js_runtime!=NULL) { + lprintf(LOG_DEBUG,"%s JavaScript: Destroying runtime",node); + JS_DestroyRuntime(js_runtime); + js_runtime=NULL; + } +#endif + /* Reset text.dat */ for(i=0;i10) - mswait(55); + 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/2) && count<=LOOP_NOPEN) { - sprintf(logstr,"NOPEN COLLISION - File: %s Count: %d" + sprintf(logstr,"NOPEN COLLISION - File: \"%s\" Count: %d" ,str,count); - logline("!!",logstr); } - if(file==-1 && errno==EACCES) - bputs("\7\r\nNOPEN: ACCESS DENIED\r\n\7"); + 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) +void sbbs_t::spymsg(char* msg) { char str[512]; struct in_addr addr; - if(!cfg.node_num) + 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) + && 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) - send(spy_socket[cfg.node_num-1],str,strlen(str),0); + 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 @@ -1660,7 +2824,7 @@ void sbbs_t::spymsg(char*msg) /****************************************************************************/ int sbbs_t::mv(char *src, char *dest, char copy) { - char str[256],*buf,atr=curatr; + char str[MAX_PATH+1],*buf,atr=curatr; int ind,outd; uint chunk=MV_BUFLEN; ulong length,l; @@ -1669,51 +2833,63 @@ int sbbs_t::mv(char *src, char *dest, ch if(!stricmp(src,dest)) /* source and destination are the same! */ return(0); - if(!fexist(src)) { + if(!fexistcase(src)) { bprintf("\r\n\7MV ERROR: Source doesn't exist\r\n'%s'\r\n" ,src); - return(-1); } - if(!copy && fexist(dest)) { + return(-1); + } + if(!copy && fexistcase(dest)) { bprintf("\r\n\7MV ERROR: Destination already exists\r\n'%s'\r\n" ,dest); - return(-1); } + 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); } + return(-1); + } + return(0); + } +#endif attr(WHITE); if((ind=nopen(src,O_RDONLY))==-1) { errormsg(WHERE,ERR_OPEN,src,O_RDONLY); - return(-1); } + return(-1); + } if((inp=fdopen(ind,"rb"))==NULL) { close(ind); errormsg(WHERE,ERR_FDOPEN,str,O_RDONLY); - return(-1); } + 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); } + 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); } + 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); } + return(-1); + } if((buf=(char *)MALLOC(MV_BUFLEN))==NULL) { fclose(inp); fclose(outp); errormsg(WHERE,ERR_ALLOC,nulstr,MV_BUFLEN); - return(-1); } + return(-1); + } l=0L; while(lnode_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) { - mswait(1000); /* Give socket output buffer time to flush */ - riosync(0); if(client_socket!=INVALID_SOCKET) { -// lprintf("Hangup: closing socket %d",client_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(&output_sem); + sem_post(&outbuf.sem); online=0; } -int sbbs_t::incom(void) +int sbbs_t::incom(unsigned long timeout) { uchar ch; - if(!RingBufRead(&inbuf, &ch, 1)) - return(NOINP); - - if(rio_abortable && ch==3) { /* Ctrl-C */ - lprintf("Node %d Ctrl-C hit",cfg.node_num); - sys_status|=SS_ABORT; - rioctl(IOFO); - return(NOINP); } +#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); } @@ -1801,8 +2966,8 @@ int sbbs_t::outcom(uchar ch) { if(!RingBufFree(&outbuf)) return(TXBOF); - RingBufWrite(&outbuf, &ch, 1); - sem_post(&output_sem); + if(!RingBufWrite(&outbuf, &ch, 1)) + return(TXBOF); return(0); } @@ -1818,19 +2983,23 @@ void sbbs_t::putcom(char *str, int len) void sbbs_t::riosync(char abortable) { - if(useron.misc&(RIP|WIP)) /* don't allow abort with RIP or WIP */ - abortable=0; /* mainly because of ANSI cursor posistion response */ - if(sys_status&SS_ABORT) /* no need to sync if already aborting */ + 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 */ - rioctl(IOCS|PAUSE); + logline("!!","riosync timeout"); break; } mswait(100); @@ -1857,11 +3026,11 @@ int sbbs_t::rioctl(ushort action) // ioctlsocket (client_socket,FIONREAD,&cnt); return(/* cnt+ */RingBufFull(&inbuf)); case RXBS: /* Get receive buffer size */ - return(RingBufFree(&inbuf)); + return(inbuf.size); case TXBC: /* Get transmit buffer count */ return(RingBufFull(&outbuf)); case TXBS: /* Get transmit buffer size */ - return(RingBufFree(&outbuf)); + return(outbuf.size); case TXBF: /* Get transmit buffer free space */ return(RingBufFree(&outbuf)); case IOMODE: @@ -1893,53 +3062,26 @@ int sbbs_t::rioctl(ushort action) if((action&0xff)==IOSM) { /* Get/Set/Clear mode */ if(action&ABORT) rio_abortable=true; - return(0); } - + return(0); + } if((action&0xff)==IOCM) { /* Get/Set/Clear mode */ if(action&ABORT) rio_abortable=false; - return(0); } + return(0); + } if((action&0xff)==IOSS) { /* Set state */ if(action&ABORT) sys_status|=SS_ABORT; - return(0); } + return(0); + } if((action&0xff)==IOCS) { /* Clear state */ if(action&ABORT) sys_status&=~SS_ABORT; - return(0); } - - if((action&0xff)==TXSYNC) { /* Synchronize transmition */ - #if 0 /* TXSYNC */ - - c=action>>8; /* Number of seconds */ - w=110+(c*1000); /* Number of milliseconds */ - start=clock(); - while(clock()-starttm_mon+1,tm->tm_mday - ,TM_YEAR(tm->tm_year)); + 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; } + return; + } if(lwrite(file,buf,length)!=length) { close(file); errormsg(WHERE,ERR_WRITE,str,length); FREE((char *)buf); - return; } + return; + } close(file); if(crash) { for(i=0;i<2;i++) { - sprintf(str,"%scrash.log",i ? cfg.data_dir : cfg.node_dir); + 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; } + return; + } if(lwrite(file,buf,length)!=length) { close(file); errormsg(WHERE,ERR_WRITE,str,length); FREE((char *)buf); - return; } - close(file); } } - FREE((char *)buf); } + return; + } + close(file); + } + } + FREE((char *)buf); + } fclose(logfile_fp); @@ -2052,15 +3201,10 @@ void sbbs_t::catsyslog(int crash) void sbbs_t::logoffstats() { - char str[256]; + char str[MAX_PATH+1]; int i,file; stats_t stats; -#if 0 - if(thisnode.status==NODE_QUIET) /* Quiet users aren't counted */ - return; -#endif - if(REALSYSOP && !(cfg.sys_misc&SM_SYSSTAT)) return; @@ -2068,18 +3212,11 @@ void sbbs_t::logoffstats() 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; } - lseek(file,12L,SEEK_SET); /* Skip timestamp, logons and logons today */ - read(file,&stats.timeon,4); /* Total time on system */ - read(file,&stats.ttoday,4); /* Time today on system */ - read(file,&stats.uls,4); /* Uploads today */ - read(file,&stats.ulb,4); /* Upload bytes today */ - read(file,&stats.dls,4); /* Downloads today */ - read(file,&stats.dlb,4); /* Download bytes today */ - read(file,&stats.ptoday,4); /* Posts today */ - read(file,&stats.etoday,4); /* Emails today */ - read(file,&stats.ftoday,4); /* Feedback sent today */ - read(file,&stats.nusers,2); /* New users today */ + 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; @@ -2092,20 +3229,14 @@ void sbbs_t::logoffstats() 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,12L,SEEK_SET); - write(file,&stats.timeon,4); /* Total time on system */ - write(file,&stats.ttoday,4); /* Time today on system */ - write(file,&stats.uls,4); /* Uploads today */ - write(file,&stats.ulb,4); /* Upload bytes today */ - write(file,&stats.dls,4); /* Downloads today */ - write(file,&stats.dlb,4); /* Download bytes today */ - write(file,&stats.ptoday,4); /* Posts today */ - write(file,&stats.etoday,4); /* Emails today */ - write(file,&stats.ftoday,4); /* Feedback sent today */ - write(file,&stats.nusers,2); /* New users today */ + lseek(file,4L,SEEK_SET); + write(file,&stats,sizeof(stats)); close(file); } } @@ -2115,16 +3246,31 @@ void node_thread(void* arg) char str[128]; char uname[LEN_ALIAS+1]; int file; - uint i,j; + uint i; + uint usernum; + uint lastusernum; uint curshell=0; time_t now; node_t node; user_t user; sbbs_t* sbbs = (sbbs_t*) arg; - node_threads_running++; update_clients(); - thread_up(); + thread_up(TRUE /* setuid */); + +#ifdef _DEBUG + lprintf(LOG_DEBUG,"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(LOG_ERR,"!Node %d !JavaScript Initialization FAILURE",sbbs->cfg.node_num); + } +#endif if(sbbs->answer()) { @@ -2137,12 +3283,16 @@ void node_thread(void* arg) 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.exec_dir + 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; } + break; + } FREE_AND_NULL(sbbs->main_csi.cs); sbbs->freevars(&sbbs->main_csi); sbbs->clearvars(&sbbs->main_csi); @@ -2152,7 +3302,8 @@ void node_thread(void* arg) close(file); sbbs->errormsg(WHERE,ERR_ALLOC,str,sbbs->main_csi.length); sbbs->hangup(); - break; } + break; + } if(lread(file,sbbs->main_csi.cs,sbbs->main_csi.length) !=(int)sbbs->main_csi.length) { @@ -2161,7 +3312,8 @@ void node_thread(void* arg) FREE(sbbs->main_csi.cs); sbbs->main_csi.cs=NULL; sbbs->hangup(); - break; } + break; + } close(file); curshell=sbbs->useron.shell; @@ -2171,7 +3323,6 @@ void node_thread(void* arg) } if(sbbs->exec(&sbbs->main_csi)) break; - } } @@ -2180,8 +3331,7 @@ void node_thread(void* arg) PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME); #endif - sbbs->hangup(); /* just to be sure we shut down the output_thread */ - client_off(node_socket[sbbs->cfg.node_num-1]); + sbbs->hangup(); /* closes sockets, calls client_off, and shuts down the output_thread */ node_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET; sbbs->logout(); @@ -2195,32 +3345,64 @@ void node_thread(void* arg) node.status=NODE_EVENT_RUNNING; sbbs->putnodedat(sbbs->cfg.node_num,&node); -// status("Running system daily maintenance"); sbbs->logentry("!:","Ran system daily maintenance"); - lprintf("Checking users..."); - j=lastuser(&sbbs->cfg); - for(i=1;i<=j;i++) { - sprintf(str,"%5u of %-5u",i,j); + if(sbbs->cfg.user_backup_level) { + lprintf(LOG_INFO,"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(LOG_INFO,"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(LOG_INFO,"Node %d Checking for inactive/expired user records..." + ,sbbs->cfg.node_num); + lastusernum=lastuser(&sbbs->cfg); + for(usernum=2;usernum<=lastusernum;usernum++) { + + sprintf(str,"%5u of %-5u",usernum,lastusernum); status(str); - user.number=i; - getuserdat(&sbbs->cfg,&user); + user.number=usernum; + if((i=getuserdat(&sbbs->cfg,&user))!=0) { + sprintf(str,"user record %u",usernum); + sbbs->errormsg(WHERE,ERR_READ,str,i); + continue; + } /***********************************************/ /* Fix name (name.dat and user.dat) mismatches */ /***********************************************/ - username(&sbbs->cfg,i,uname); + username(&sbbs->cfg,user.number,uname); if(user.misc&DELETED) { if(strcmp(uname,"DELETED USER")) - sbbs->putusername(i,nulstr); - continue; } + putusername(&sbbs->cfg,user.number,nulstr); + continue; + } if(strcmp(user.alias,uname)) - sbbs->putusername(i,user.alias); + putusername(&sbbs->cfg,user.number,user.alias); if(!(user.misc&(DELETED|INACTIVE)) && user.expire && (ulong)user.expire<=(ulong)now) { - putsmsg(&sbbs->cfg,i,sbbs->text[AccountHasExpired]); + putsmsg(&sbbs->cfg,user.number,sbbs->text[AccountHasExpired]); sprintf(str,"%s #%u Expired",user.alias,user.number); sbbs->logentry("!%",str); if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOVAL @@ -2236,7 +3418,8 @@ void node_thread(void* arg) +(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]]; } + 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]; @@ -2248,21 +3431,23 @@ void node_thread(void* arg) 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)); + user.expire=0; + } + putuserrec(&sbbs->cfg,user.number,U_LEVEL,2,ultoa(user.level,str,10)); + putuserrec(&sbbs->cfg,user.number,U_FLAGS1,8,ultoa(user.flags1,str,16)); + putuserrec(&sbbs->cfg,user.number,U_FLAGS2,8,ultoa(user.flags2,str,16)); + putuserrec(&sbbs->cfg,user.number,U_FLAGS3,8,ultoa(user.flags3,str,16)); + putuserrec(&sbbs->cfg,user.number,U_FLAGS4,8,ultoa(user.flags4,str,16)); + putuserrec(&sbbs->cfg,user.number,U_EXPIRE,8,ultoa(user.expire,str,16)); + putuserrec(&sbbs->cfg,user.number,U_EXEMPT,8,ultoa(user.exempt,str,16)); + putuserrec(&sbbs->cfg,user.number,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; } + sbbs->online=0; } + } /***********************************************************/ /* Auto deletion based on expiration date or days inactive */ @@ -2273,25 +3458,28 @@ void node_thread(void* arg) > sbbs->cfg.sys_autodel)) { /* Inactive too long */ sprintf(str,"Auto-Deleted %s #%u",user.alias,user.number); sbbs->logentry("!*",str); - sbbs->delallmail(i); - sbbs->putusername(i,nulstr); - putuserrec(&sbbs->cfg,i,U_MISC,8,ultoa(user.misc|DELETED,str,16)); } + sbbs->delallmail(user.number); + putusername(&sbbs->cfg,user.number,nulstr); + putuserrec(&sbbs->cfg,user.number,U_MISC,8,ultoa(user.misc|DELETED,str,16)); + } } - lprintf("Purging deleted/expired e-mail"); + lprintf(LOG_INFO,"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((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); + 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"); @@ -2301,6 +3489,7 @@ void node_thread(void* arg) } } +#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) { @@ -2308,7 +3497,6 @@ void node_thread(void* arg) node.status=NODE_EVENT_RUNNING; sbbs->putnodedat(sbbs->cfg.node_num,&node); if(sbbs->cfg.node_daily[0]) { -// status("Running node daily event"); sbbs->logentry("!:","Run node daily event"); sbbs->external( sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL) @@ -2318,10 +3506,11 @@ void node_thread(void* arg) 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("Waiting for node %d %s to terminate..." + lprintf(LOG_INFO,"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 @@ -2330,8 +3519,8 @@ void node_thread(void* arg) while(sbbs->input_thread_running || sbbs->output_thread_running) { if(time(NULL)-start>TIMEOUT_THREAD_WAIT) { - lprintf("!TIMEOUT waiting for node %d " - "%s to terminate", sbbs->cfg.node_num + lprintf(LOG_NOTICE,"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 @@ -2351,17 +3540,22 @@ void node_thread(void* arg) node.status=NODE_OFFLINE; else node.status=NODE_WFC; - node.misc&=~NODE_DOWN; - node.useron=0; + 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(!sbbs->input_thread_running && !sbbs->output_thread_running - && !sbbs->event_thread_running) { + if(node_threads_running>0) + node_threads_running--; + lprintf(LOG_DEBUG,"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; - node_threads_running--; - update_clients(); - thread_down(); - } + else + lprintf(LOG_WARNING,"Node %d !ORPHANED I/O THREAD(s)",sbbs->cfg.node_num); + + update_clients(); + thread_down(); } time_t checktime(void) @@ -2374,38 +3568,61 @@ time_t checktime(void) return(mktime(&tm)-0x2D24BD00L); } -char* DLLCALL bbs_ver(void) +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]; - COMPILER_DESC(compiler); + if(ver[0]==0) { /* uninitialized */ + DESCRIBE_COMPILER(compiler); - sprintf(ver,"Synchronet BBS/Telnet Server v%s%c%s SMBLIB v%s Compiled %s %s with %s" - ,VERSION, REVISION + sprintf(ver,"%s %s%c%s SMBLIB %s Compiled %s %s with %s" + ,TELNET_SERVER + ,VERSION, REVISION #ifdef _DEBUG - ," Debug" + ," Debug" #else - ,"" + ,"" #endif - ,smb_lib_ver() - ,__DATE__, __TIME__, compiler - ); - + ,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) { - if(telnet_socket!=INVALID_SOCKET) { - lprintf("BBS Terminate: closing telnet socket %d",telnet_socket); - close_socket(telnet_socket); - telnet_socket=INVALID_SOCKET; - } + lprintf(LOG_DEBUG,"BBS Server terminate",telnet_socket); + terminate_server=true; } static void cleanup(int code) { + lputs(LOG_INFO,"BBS System thread terminating"); + if(telnet_socket!=INVALID_SOCKET) { close_socket(telnet_socket); telnet_socket=INVALID_SOCKET; @@ -2418,45 +3635,74 @@ static void cleanup(int code) #ifdef _WINSOCKAPI_ if(WSAInitialized && WSACleanup()!=0) - lprintf("!WSACleanup ERROR %d",ERROR_VALUE); + lprintf(LOG_ERR,"!WSACleanup ERROR %d",ERROR_VALUE); #endif free_cfg(&scfg); free_text(text); + semfile_list_free(&recycle_semfiles); + semfile_list_free(&shutdown_semfiles); + #ifdef _WIN32 - CloseHandle(exec_mutex); -#endif - pthread_mutex_destroy(&event_mutex); + 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 - lputs("BBS System thread terminated"); status("Down"); - if(startup->terminated!=NULL) - startup->terminated(code); thread_down(); + if(terminate_server || code) + lprintf(LOG_INFO,"BBS System thread terminated (%u threads remain, %lu clients served)" + ,thread_count, served); + if(startup->terminated!=NULL) + startup->terminated(startup->cbdata,code); } void DLLCALL bbs_thread(void* arg) { - char * host_name; - char str[256]; + char* host_name; + char* identity; + char* p; + 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; + SOCKET client_socket=INVALID_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; + 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 uspy_addr_len; +#endif if(startup==NULL) { sbbs_beep(100,500); @@ -2472,70 +3718,96 @@ void DLLCALL bbs_thread(void* arg) return; } - thread_up(); +#ifdef _THREAD_SUID_BROKEN + startup->seteuid(TRUE); +#endif + + /* 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; + if(startup->js_cx_stack==0) startup->js_cx_stack=JAVASCRIPT_CONTEXT_STACK; +#endif + if(startup->outbuf_drain_timeout==0) startup->outbuf_drain_timeout=10; + if(startup->sem_chk_freq==0) startup->sem_chk_freq=2; + if(startup->temp_dir[0]) backslash(startup->temp_dir); + + sprintf(js_server_props.version,"%s %s%c",TELNET_SERVER,VERSION,REVISION); + js_server_props.version_detail=bbs_ver(); + js_server_props.clients=&node_threads_running; + js_server_props.options=&startup->options; + js_server_props.interface_addr=&startup->telnet_interface; + + uptime=0; + served=0; + startup->recycle_now=FALSE; + startup->shutdown_now=FALSE; + terminate_server=false; + + do { + + thread_up(FALSE /* setuid */); status("Initializing"); -#ifdef __unix__ /* Ignore "Broken Pipe" signal */ - signal(SIGPIPE,SIG_IGN); -#endif + /* 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(LOG_ERR,"!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]; - COMPILER_DESC(compiler); + DESCRIBE_COMPILER(compiler); - lprintf("Synchronet BBS/Telnet Server Version %s Revision %c%s" - ,VERSION,REVISION + lprintf(LOG_INFO,"%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 v%s (format %x.%02x)",smb_lib_ver(),smb_ver()>>8,smb_ver()&0xff); + lprintf(LOG_INFO,"Compiled %s %s with %s", __DATE__, __TIME__, compiler); + lprintf(LOG_INFO,"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)" + lprintf(LOG_ERR,"!ILLEGAL node configuration (first: %d, last: %d)" ,startup->first_node, startup->last_node); cleanup(1); return; } +#ifdef __BORLANDC__ + #pragma warn -8008 /* Disable "Condition always false" warning */ + #pragma warn -8066 /* Disable "Unreachable code" warning */ +#endif if(sizeof(node_t)!=SIZEOF_NODE_T) { - lprintf("!COMPILER ERROR: sizeof(node_t)=%d instead of %d" + lprintf(LOG_ERR,"!COMPILER ERROR: sizeof(node_t)=%d instead of %d" ,sizeof(node_t),SIZEOF_NODE_T); cleanup(1); return; } - srand(time(NULL)); - - if(!(startup->options&BBS_OPT_LOCAL_TIMEZONE)) { - if(PUTENV("TZ=UCT0")) - lprintf("!putenv() FAILED"); - tzset(); - - if((t=checktime())!=0) { /* Check binary time */ - lprintf("!TIME PROBLEM (%ld)",t); - cleanup(1); - return; - } - } - #ifdef _WIN32 if((exec_mutex=CreateMutex(NULL,false,NULL))==NULL) { - lprintf("!ERROR %d creating exec_mutex", GetLastError()); + lprintf(LOG_ERR,"!ERROR %d creating exec_mutex", GetLastError()); cleanup(1); return; } -#endif - - pthread_mutex_init(&event_mutex,NULL); + hK32 = LoadLibrary("KERNEL32"); +#endif // _WIN32 if(!winsock_startup()) { cleanup(1); @@ -2543,42 +3815,65 @@ void DLLCALL bbs_thread(void* arg) } t=time(NULL); - lprintf("Initializing on %.24s with options: %lx" - ,ctime(&t),startup->options); + lprintf(LOG_INFO,"Initializing on %.24s with options: %lx" + ,CTIME_R(&t,str),startup->options); + + if(chdir(startup->ctrl_dir)!=0) + lprintf(LOG_ERR,"!ERROR %d changing directory to: %s", errno, startup->ctrl_dir); /* Initial configuration and load from CNF files */ - scfg.startup=startup; - sprintf(scfg.ctrl_dir, "%.*s", (int)sizeof(scfg.ctrl_dir)-1 - ,startup->ctrl_dir); - lprintf("Loading configuration files from %s", scfg.ctrl_dir); + SAFECOPY(scfg.ctrl_dir,startup->ctrl_dir); + lprintf(LOG_INFO,"Loading configuration files from %s", scfg.ctrl_dir); + scfg.size=sizeof(scfg); scfg.node_num=startup->first_node; - if(!load_cfg(&scfg, text)) { - lprintf("!Failed to load configuration files"); + SAFECOPY(logstr,UNKNOWN_LOAD_ERROR); + if(!load_cfg(&scfg, text, TRUE, logstr)) { + lprintf(LOG_ERR,"!ERROR %s",logstr); + lprintf(LOG_ERR,"!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(LOG_ERR,"!putenv() FAILED"); + tzset(); + + if((t=checktime())!=0) { /* Check binary time */ + lprintf(LOG_ERR,"!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" + lprintf(LOG_NOTICE,"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(LOG_INFO,"Verifying/creating data directories"); make_data_dirs(&scfg); /* Create missing node directories and dsts.dab files */ + lprintf(LOG_INFO,"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)node_inbuf=node_inbuf; @@ -2600,41 +3899,12 @@ void DLLCALL bbs_thread(void* arg) telnet_socket = open_socket(SOCK_STREAM); if(telnet_socket == INVALID_SOCKET) { - lprintf("!ERROR %d creating Telnet socket", ERROR_VALUE); + lprintf(LOG_ERR,"!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; - } - - } - - LINGER linger; - - linger.l_onoff=true; - linger.l_linger=5; /* seconds */ - - result = setsockopt(telnet_socket, SOL_SOCKET, SO_LINGER - ,(char *)&linger, sizeof(linger)); - - if(result != 0) { - lprintf("!ERROR %d (%d) setting Telnet socket options.", result, ERROR_VALUE); - cleanup(1); - return; - } + lprintf(LOG_INFO,"Telnet socket %d opened",telnet_socket); /*****************************/ /* Listen for incoming calls */ @@ -2645,12 +3915,14 @@ void DLLCALL bbs_thread(void* arg) server_addr.sin_family = AF_INET; server_addr.sin_port = htons(startup->telnet_port); - result = bind(telnet_socket, (struct sockaddr *)&server_addr - ,sizeof(server_addr)); - + if(startup->seteuid!=NULL) + startup->seteuid(FALSE); + result = retry_bind(telnet_socket,(struct sockaddr *)&server_addr,sizeof(server_addr) + ,startup->bind_retry_count,startup->bind_retry_delay,"Telnet Server",lprintf); + 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(LOG_NOTICE,"%s",BIND_FAILURE_HELP); cleanup(1); return; } @@ -2658,11 +3930,11 @@ void DLLCALL bbs_thread(void* arg) result = listen(telnet_socket, 1); if(result != 0) { - lprintf("!ERROR %d (%d) listening on Telnet socket", result, ERROR_VALUE); + lprintf(LOG_ERR,"!ERROR %d (%d) listening on Telnet socket", result, ERROR_VALUE); cleanup(1); return; } - lprintf("Telnet server listening on port %d",startup->telnet_port); + lprintf(LOG_INFO,"Telnet server listening on port %d",startup->telnet_port); if(startup->options&BBS_OPT_ALLOW_RLOGIN) { @@ -2671,26 +3943,12 @@ void DLLCALL bbs_thread(void* arg) rlogin_socket = open_socket(SOCK_STREAM); if(rlogin_socket == INVALID_SOCKET) { - lprintf("!ERROR %d creating RLogin socket", ERROR_VALUE); + lprintf(LOG_ERR,"!ERROR %d creating RLogin socket", ERROR_VALUE); cleanup(1); return; } - lprintf("RLogin socket %d opened",rlogin_socket); - - LINGER linger; - - linger.l_onoff=true; - linger.l_linger=5; /* seconds */ - - result = setsockopt(telnet_socket, SOL_SOCKET, SO_LINGER - ,(char *)&linger, sizeof(linger)); - - if(result != 0) { - lprintf("!ERROR %d (%d) setting RLogin socket options.", result, ERROR_VALUE); - cleanup(1); - return; - } + lprintf(LOG_INFO,"RLogin socket %d opened",rlogin_socket); /*****************************/ /* Listen for incoming calls */ @@ -2701,12 +3959,14 @@ void DLLCALL bbs_thread(void* arg) server_addr.sin_family = AF_INET; server_addr.sin_port = htons(startup->rlogin_port); - result = bind(rlogin_socket, (struct sockaddr *)&server_addr - ,sizeof(server_addr)); - + if(startup->seteuid!=NULL) + startup->seteuid(FALSE); + result = retry_bind(rlogin_socket,(struct sockaddr *)&server_addr,sizeof(server_addr) + ,startup->bind_retry_count,startup->bind_retry_delay,"RLogin Server",lprintf); + 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(LOG_NOTICE,"%s",BIND_FAILURE_HELP); cleanup(1); return; } @@ -2714,35 +3974,40 @@ void DLLCALL bbs_thread(void* arg) result = listen(rlogin_socket, 1); if(result != 0) { - lprintf("!ERROR %d (%d) listening on RLogin socket", result, ERROR_VALUE); + lprintf(LOG_ERR,"!ERROR %d (%d) listening on RLogin socket", result, ERROR_VALUE); cleanup(1); return; } - lprintf("RLogin server listening on port %d",startup->rlogin_port); + lprintf(LOG_INFO,"RLogin server listening on port %d",startup->rlogin_port); } - sbbs = new sbbs_t(0, server_addr.sin_addr.s_addr - ,"BBS System", telnet_socket, &scfg, text); + ,"BBS System", telnet_socket, &scfg, text, NULL); sbbs->online = 0; if(sbbs->init()==false) { - lputs("!BBS initialization failed"); + lputs(LOG_ERR,"!BBS initialization failed"); cleanup(1); return; } _beginthread(output_thread, 0, sbbs); - events = new sbbs_t(0, server_addr.sin_addr.s_addr - ,"BBS Events", INVALID_SOCKET, &scfg, text); - events->online = 0; - if(events->init()==false) { - lputs("!Events initialization failed"); - cleanup(1); - return; + 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(LOG_ERR,"!Events initialization failed"); + cleanup(1); + return; + } + _beginthread(event_thread, 0, events); } - _beginthread(event_thread, 0, events); - for(i=startup->first_node;i<=startup->last_node;i++) { + /* 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; @@ -2750,39 +4015,142 @@ void DLLCALL bbs_thread(void* arg) sbbs->putnodedat(i,&node); } - lprintf("BBS System thread started for nodes %d through %d" - ,startup->first_node, startup->last_node); + lprintf(LOG_INFO,"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(LOG_ERR,"!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 + + /* Setup recycle/shutdown semaphore file lists */ + semfile_list_init(&shutdown_semfiles,scfg.ctrl_dir,"shutdown","telnet"); + semfile_list_init(&recycle_semfiles,scfg.ctrl_dir,"recycle","telnet"); + SAFEPRINTF(str,"%stelnet.rec",scfg.ctrl_dir); /* legacy */ + semfile_list_add(&recycle_semfiles,str); + if(!initialized) { + semfile_list_check(&initialized,&recycle_semfiles); + semfile_list_check(&initialized,&shutdown_semfiles); + } + +#ifdef __unix__ // unix-domain spy sockets + for(i=first_node;i<=last_node && !(startup->options&BBS_OPT_NO_SPY_SOCKETS);i++) { + if((uspy_listen_socket[i-1]=socket(PF_UNIX,SOCK_STREAM,0))==INVALID_SOCKET) + lprintf(LOG_ERR,"Node %d !ERROR %d creating local spy socket" + , i, errno); + else { + lprintf(LOG_INFO,"Node %d local spy using socket %d", i, uspy_listen_socket[i-1]); + if(startup!=NULL && startup->socket_open!=NULL) + startup->socket_open(startup->cbdata,TRUE); + } + + uspy_addr.sun_family=AF_UNIX; + 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) { + uspy_addr_len=SUN_LEN(&uspy_addr); + if(bind(uspy_listen_socket[i-1], (struct sockaddr *) &uspy_addr, uspy_addr_len)) { + lprintf(LOG_ERR,"!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]); + uspy_listen_socket[i-1]=INVALID_SOCKET; + continue; + } + lprintf(LOG_INFO,"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(LOG_ERR,"Node %d !ERROR %d listening local spy socket %d", i, errno); + close_socket(uspy_listen_socket[i-1]); + uspy_listen_socket[i-1]=INVALID_SOCKET; + continue; + } + uspy_addr_len=sizeof(uspy_addr); + } + } +#endif // __unix__ (unix-domain spy sockets) + /* signal caller that we've started up successfully */ if(startup->started!=NULL) - startup->started(); + startup->started(startup->cbdata); - while(telnet_socket!=INVALID_SOCKET) { + + while(!terminate_server) { if(node_threads_running==0) { /* check for re-run flags */ bool rerun=false; - for(i=startup->first_node;i<=startup->last_node;i++) { - sbbs->getnodedat(i,&node,0); + 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); + lprintf(LOG_INFO,"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=startup->first_node; - pthread_mutex_lock(&event_mutex); - if(!load_cfg(&scfg, text)) { - lprintf("!Failed to load configuration files"); + if(rerun) + break; + if(!(startup->options&BBS_OPT_NO_RECYCLE)) { + if((p=semfile_list_check(&initialized,&recycle_semfiles))!=NULL) { + lprintf(LOG_INFO,"%04d Recycle semaphore file (%s) detected" + ,telnet_socket,p); break; } - scfg_reloaded=true; - pthread_mutex_unlock(&event_mutex); +#if 0 /* unused */ + if(startup->recycle_sem!=NULL && sem_trywait(&startup->recycle_sem)==0) + startup->recycle_now=TRUE; +#endif + if(startup->recycle_now==TRUE) { + lprintf(LOG_INFO,"%04d Recycle semaphore signaled",telnet_socket); + startup->recycle_now=FALSE; + break; + } + } + if(((p=semfile_list_check(&initialized,&shutdown_semfiles))!=NULL + && lprintf(LOG_INFO,"%04d Shutdown semaphore file (%s) detected" + ,telnet_socket,p)) + || (startup->shutdown_now==TRUE + && lprintf(LOG_INFO,"%04d Shutdown semaphore signaled" + ,telnet_socket))) { + startup->shutdown_now=FALSE; + terminate_server=TRUE; + break; } } @@ -2792,106 +4160,159 @@ void DLLCALL bbs_thread(void* arg) /* 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) { + high_socket_set=0; + if(telnet_socket!=INVALID_SOCKET) { + 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_listen_socket[i-1]+1; + } + } +#endif struct timeval tv; - tv.tv_sec=2; + tv.tv_sec=startup->sem_chk_freq; tv.tv_usec=0; if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) { - if(i==0) { - mswait(1); + if(i==0) continue; - } if(ERROR_VALUE==EINTR) - lprintf("Telnet Server listening interrupted"); + lprintf(LOG_NOTICE,"Telnet Server listening interrupted"); else if(ERROR_VALUE == ENOTSOCK) - lprintf("Telnet Server sockets closed"); + lprintf(LOG_NOTICE,"Telnet Server sockets closed"); else - lprintf("!ERROR %d selecting sockets",ERROR_VALUE); - break; + lprintf(LOG_WARNING,"!ERROR %d selecting sockets",ERROR_VALUE); + continue; } + if(terminate_server) /* terminated */ + break; + client_addr_len = sizeof(client_addr); bool rlogin = false; - if(FD_ISSET(telnet_socket,&socket_set)) - client_socket = accept(telnet_socket, (struct sockaddr *)&client_addr + 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); - else if(FD_ISSET(rlogin_socket,&socket_set)) { - client_socket = accept(rlogin_socket, (struct sockaddr *)&client_addr + 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 { - lprintf("!No sockets set by select"); - continue; +#ifdef __unix__ + for(i=first_node;i<=last_node;i++) { + if(uspy_socket[i-1]!=INVALID_SOCKET + && FD_ISSET(uspy_socket[i-1],&socket_set)) { + if(node_socket[i-1]==INVALID_SOCKET) + read(uspy_socket[i-1],str,sizeof(str)); + if(!socket_check(uspy_socket[i-1],NULL,NULL,0)) { + lprintf(LOG_NOTICE,"Spy socket for node %d disconnected",i); + close_socket(uspy_socket[i-1]); + uspy_socket[i-1]=INVALID_SOCKET; + } + } + 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 *)&uspy_addr + ,&uspy_addr_len); + if(new_socket < 0) { + lprintf(LOG_ERR,"!ERROR Spy socket for node %d unable to accept()",i); + close_socket(uspy_listen_socket[i-1]); + uspy_listen_socket[i-1]=INVALID_SOCKET; + } + fcntl(new_socket,F_SETFL,fcntl(new_socket,F_GETFL)|O_NONBLOCK); + if(already_connected) { + lprintf(LOG_ERR,"!ERROR Spy socket %s already in use",uspy_addr.sun_path); + send(new_socket,"Spy socket already in use.\r\n",27,0); + close_socket(new_socket); + } + else { + lprintf(LOG_ERR,"!Spy socket %s (%d) connected",uspy_addr.sun_path,new_socket); + uspy_socket[i-1]=new_socket; + sprintf(str,"Spy connection established to node %d\r\n",i); + send(uspy_socket[i-1],str,strlen(str),0); + } + } + } +#else + lprintf(LOG_ERR,"!NO SOCKETS set by select"); +#endif } + if(!is_client) + continue; - if(client_socket == INVALID_SOCKET) - { - if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR) - lputs("BBS socket closed"); - else - lprintf("!ERROR %d accept failed", ERROR_VALUE); - break; - } - lprintf("New client socket: %d",client_socket); - if(startup->socket_open!=NULL) - startup->socket_open(TRUE); - + if(client_socket == INVALID_SOCKET) { +#if 0 /* is this necessary still? */ + if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR || ERROR_VALUE == EINVAL) { + lputs(LOG_NOTICE,"BBS socket closed"); + break; + } +#endif + lprintf(LOG_ERR,"!ERROR %d accepting connection", ERROR_VALUE); #ifdef _WIN32 - if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) - PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME); + if(WSAGetLastError()==WSAENOBUFS) /* recycle (re-init WinSock) on this error */ + break; #endif + continue; + } + char host_ip[32]; - linger.l_onoff=true; - linger.l_linger=5; /* seconds */ - - result = setsockopt(client_socket, SOL_SOCKET, SO_LINGER - ,(char *)&linger, sizeof(linger)); + strcpy(host_ip,inet_ntoa(client_addr.sin_addr)); - if(result != 0) { - lprintf("!ERROR %d (%d) setting socket options.", result, ERROR_VALUE); + if(trashcan(&scfg,host_ip,"ip-silent")) { close_socket(client_socket); continue; } - char host_ip[32]; + lprintf(LOG_INFO,"%04d %s connection accepted from: %s port %u" + ,client_socket + ,rlogin ? "RLogin" : "Telnet", host_ip, ntohs(client_addr.sin_port)); - strcpy(host_ip,inet_ntoa(client_addr.sin_addr)); - - lprintf("%s connection accepted from: %s" - , rlogin ? "RLogin" : "Telnet", host_ip); +#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; // require for output to the user + 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("IP blocked in ip.can"); + lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can" + ,client_socket); sprintf(logstr, "Blocked IP: %s",host_ip); - sbbs->logline("@!",logstr); + sbbs->syslog("@!",logstr); continue; } - if(rlogin) { - if(!sbbs->trashcan(host_ip,"rlogin")) { - close_socket(client_socket); - lprintf("IP not listed in rlogin.can"); - sprintf(logstr, "Invalid RLogin from: %s",host_ip); - sbbs->logline("@!",logstr); - continue; - } + if(rlogin) sbbs->outcom(0); /* acknowledge RLogin per RFC 1282 */ - } sbbs->putcom(crlf); sbbs->putcom(VERSION_NOTICE); @@ -2903,40 +4324,57 @@ void DLLCALL bbs_thread(void* arg) if(startup->options&BBS_OPT_NO_HOST_LOOKUP) h=NULL; else { - sbbs->bprintf("Resolving host name..."); + 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=""; - lprintf("Host name: %s", host_name); + if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) { + lprintf(LOG_INFO,"%04d Hostname: %s", client_socket, host_name); + for(i=0;h!=NULL && h->h_aliases!=NULL && h->h_aliases[i]!=NULL;i++) + lprintf(LOG_INFO,"%04d HostAlias: %s", client_socket, h->h_aliases[i]); + } if(sbbs->trashcan(host_name,"host")) { close_socket(client_socket); - lprintf("Host name blocked in host.can"); - sprintf(logstr, "Blocked Host Name: %s",host_name); - sbbs->logline("@!",logstr); + lprintf(LOG_NOTICE,"%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<=' ') /* point to user name */ + identity++; + lprintf(LOG_INFO,"%04d Identity: %s",client_socket, identity); + } + } /* Initialize client display */ client.size=sizeof(client); client.time=time(NULL); - sprintf(client.addr,"%.*s",(int)sizeof(client.addr)-1,host_ip); - sprintf(client.host,"%.*s",(int)sizeof(client.host)-1,host_name); + SAFECOPY(client.addr,host_ip); + SAFECOPY(client.host,host_name); client.port=ntohs(client_addr.sin_port); client.protocol=rlogin ? "RLogin":"Telnet"; client.user=""; - client_on(client_socket,&client); + client_on(client_socket,&client,FALSE /* update */); - sprintf(logstr, "%s %s", host_name, host_ip); - sbbs->logline("@*",logstr); - - for(i=startup->first_node;i<=startup->last_node;i++) { - sbbs->getnodedat(i,&node,1); + 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); @@ -2945,68 +4383,99 @@ void DLLCALL bbs_thread(void* arg) sbbs->putnodedat(i,&node); } - if(i>startup->last_node) { - sbbs->putcom("\r\nSorry, all telnet nodes are in use or otherwise unavailable.\r\n"); - sbbs->putcom("Please try again later.\r\n"); - lprintf("No nodes available for login."); + if(i>last_node) { + lprintf(LOG_WARNING,"%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); - close_socket(client_socket); client_off(client_socket); + close_socket(client_socket); continue; } node_socket[i-1]=client_socket; - lprintf("Connecting client to node %d",i); - sbbs_t* new_node = new sbbs_t(i, client_addr.sin_addr.s_addr, host_name - ,client_socket, &scfg, text); + ,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("!Node %d Initialization failure",new_node->cfg.node_num); - sbbs->putcom("\r\nSorry, initialization failed. Try again later.\r\n"); + lprintf(LOG_INFO,"%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; - close_socket(client_socket); client_off(client_socket); + close_socket(client_socket); continue; } if(rlogin==true) { new_node->connection="RLogin"; new_node->sys_status|=SS_RLOGIN; + new_node->telnet_mode|=TELNET_MODE_OFF; // RLogin does not use Telnet commands } + 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;itemp_dir, i+1); + if(fexist(str)) + unlink(str); + } + if(uspy_socket[i]!=INVALID_SOCKET) { + close_socket(uspy_socket[i]); + uspy_socket[i]=INVALID_SOCKET; + } +#endif + } sbbs->client_socket=INVALID_SOCKET; - events->terminated=true; + if(events!=NULL) + events->terminated=true; // Wake-up BBS output thread so it can terminate - sem_post(&sbbs->output_sem); + 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); + lprintf(LOG_INFO,"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 " + lprintf(LOG_ERR,"!TIMEOUT waiting for %d node thread(s) to " "terminate", node_threads_running); break; } @@ -3015,13 +4484,12 @@ void DLLCALL bbs_thread(void* arg) } // Wait for Events thread to terminate - if(events->event_thread_running) { - pthread_mutex_unlock(&event_mutex); - lprintf("Waiting for event thread to terminate..."); + if(events!=NULL && events->event_thread_running) { + lprintf(LOG_INFO,"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 " + lprintf(LOG_ERR,"!TIMEOUT waiting for BBS event thread to " "terminate"); break; } @@ -3031,11 +4499,11 @@ void DLLCALL bbs_thread(void* arg) // Wait for BBS output thread to terminate if(sbbs->output_thread_running) { - lprintf("Waiting for system output thread to terminate..."); + lprintf(LOG_INFO,"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 " + lprintf(LOG_ERR,"!TIMEOUT waiting for BBS output thread to " "terminate"); break; } @@ -3044,19 +4512,29 @@ void DLLCALL bbs_thread(void* arg) } // Set all nodes' status to OFFLINE - for(i=startup->first_node;i<=startup->last_node;i++) { + for(i=first_node;i<=last_node;i++) { sbbs->getnodedat(i,&node,1); node.status=NODE_OFFLINE; sbbs->putnodedat(i,&node); } - if(!events->event_thread_running) + if(events!=NULL && !events->event_thread_running) delete events; if(!sbbs->output_thread_running) delete sbbs; cleanup(0); + + if(!terminate_server) { + lprintf(LOG_INFO,"Recycling server..."); + mswait(2000); + if(startup->recycle!=NULL) + startup->recycle(startup->cbdata); + } + + } while(!terminate_server); + }