Select Git revision
main.cpp 159.86 KiB
/* Synchronet terminal server thread and related functions */
/* $Id$ */
// vi: tabstop=4
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* Anonymous FTP access to the most recent released source is available at *
* ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
* *
* Anonymous CVS access to the development source and modification history *
* is available at cvs.synchro.net:/cvsroot/sbbs, example: *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login *
* (just hit return, no password is necessary) *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* You are encouraged to submit any modifications (preferably in Unix diff *
* format) via e-mail to mods@synchro.net *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include "sbbs.h"
#include "ident.h"
#include "telnet.h"
#include "netwrap.h"
#include "js_rtpool.h"
#include "js_request.h"
#include "ssl.h"
#include <multisock.h>
#include <limits.h> // HOST_NAME_MAX
#ifdef __unix__
#include <sys/un.h>
#endif
//#define SBBS_TELNET_ENVIRON_SUPPORT 1
//---------------------------------------------------------------------------
#define TELNET_SERVER "Synchronet Terminal Server"
#define STATUS_WFC "Listening"
#define TIMEOUT_THREAD_WAIT 60 // Seconds (was 15)
#define IO_THREAD_BUF_SIZE 20000 // Bytes
#define TIMEOUT_MUTEX_FILE 12*60*60
// Globals
#ifdef _WIN32
HANDLE exec_mutex=NULL;
HINSTANCE hK32=NULL;
#if defined(_DEBUG) && defined(_MSC_VER)
HANDLE debug_log=INVALID_HANDLE_VALUE;
_CrtMemState mem_chkpoint;
#endif // _DEBUG && _MSC_VER
#endif // _WIN32
#ifdef USE_CRYPTLIB
#define SSH_END() if(ssh) { pthread_mutex_lock(&sbbs->ssh_mutex); cryptDestroySession(sbbs->ssh_session); pthread_mutex_unlock(&sbbs->ssh_mutex); }
#else
#define SSH_END()
#endif
volatile time_t uptime=0;
volatile ulong served=0;
static protected_uint32_t node_threads_running;
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];
struct xpms_set *ts_set;
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 str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
#ifdef _THREAD_SUID_BROKEN
int thread_suid_broken=TRUE; /* NPTL is no longer broken */
#endif
extern "C" {
static bbs_startup_t* startup=NULL;
static const char* status(const char* str)
{
if(startup!=NULL && startup->status!=NULL)
startup->status(startup->cbdata,str);
return str;
}
static void update_clients()
{
if(startup!=NULL && startup->clients!=NULL)
startup->clients(startup->cbdata,protected_uint32_value(node_threads_running));
}
void client_on(SOCKET sock, client_t* client, BOOL update)
{
if(startup!=NULL && startup->client_on!=NULL)
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(startup->cbdata,FALSE,sock,NULL,FALSE);
}
static void thread_up(BOOL setuid)
{
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,TRUE,setuid);
}
static void thread_down()
{
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,FALSE,FALSE);
}
int lputs(int level, const char* str)
{
if(level <= LOG_ERR) {
errorlog(&scfg,startup==NULL ? NULL:startup->host_name, str);
if(startup!=NULL && startup->errormsg!=NULL)
startup->errormsg(startup->cbdata,level,str);
}
if(startup==NULL || startup->lputs==NULL || str==NULL || level > startup->log_level)
return(0);
#if defined(_WIN32)
if(IsBadCodePtr((FARPROC)startup->lputs))
return(0);
#endif
return(startup->lputs(startup->cbdata,level,str));
}
int lprintf(int level, const 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, const 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);
if(level <= LOG_ERR) {
errorlog(&scfg,startup==NULL ? NULL:startup->host_name, sbuf);
if(startup!=NULL && startup->errormsg!=NULL)
startup->errormsg(startup->cbdata,level,sbuf);
}
if(startup==NULL || startup->event_lputs==NULL || level > startup->log_level)
return(0);
strip_ctrl(sbuf, sbuf);
return(startup->event_lputs(startup->event_cbdata,level,sbuf));
}
struct main_sock_cb_data {
bbs_startup_t *startup;
const char *protocol;
};
void sock_cb(SOCKET sock, void *cb_data)
{
char error_str[256];
struct main_sock_cb_data *cb=(struct main_sock_cb_data *)cb_data;
if(cb->startup && cb->startup->socket_open)
cb->startup->socket_open(cb->startup->cbdata, TRUE);
if(set_socket_options(&scfg, sock, cb->protocol, error_str, sizeof(error_str)))
lprintf(LOG_ERR,"%04d !ERROR %s",sock,error_str);
}
void sock_close_cb(SOCKET sock, void *cb_data)
{
bbs_startup_t *su=(bbs_startup_t *)cb_data;
if(su && su->socket_open)
su->socket_open(su->cbdata, FALSE);
}
SOCKET open_socket(int type, const char* protocol)
{
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(startup->cbdata,TRUE);
if(sock!=INVALID_SOCKET && set_socket_options(&scfg, sock, protocol, error, sizeof(error)))
lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
return(sock);
}
SOCKET accept_socket(SOCKET s, union xp_sockaddr* addr, socklen_t* addrlen)
{
SOCKET sock;
sock=accept(s,&addr->addr,addrlen);
if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
return(sock);
}
int close_socket(SOCKET sock)
{
int result;
if(sock==INVALID_SOCKET || sock==0)
return(0);
shutdown(sock,SHUT_RDWR); /* required on Unix */
result=closesocket(sock);
if(startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,FALSE);
if(result!=0 && ERROR_VALUE!=ENOTSOCK)
lprintf(LOG_WARNING,"!ERROR %d closing socket %d",ERROR_VALUE,sock);
return(result);
}
/* TODO: IPv6 */
u_long resolve_ip(char *addr)
{
HOSTENT* host;
char* p;
if(*addr==0)
return((u_long)INADDR_NONE);
for(p=addr;*p;p++)
if(*p!='.' && !isdigit((uchar)*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]));
}
} /* extern "C" */
#ifdef _WINSOCKAPI_
WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL WSAInitialized=FALSE;
static BOOL winsock_startup(void)
{
int status; /* Status Code */
if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
WSAInitialized=TRUE;
return(TRUE);
}
lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
return(FALSE);
}
#else /* No WINSOCK */
#define winsock_startup() (TRUE)
#define SOCKLIB_DESC NULL
#endif
DLLEXPORT void DLLCALL sbbs_srand()
{
DWORD seed;
xp_randomize();
#if defined(HAS_DEV_RANDOM) && defined(RANDOM_DEV)
int rf,rd=0;
if((rf=open(RANDOM_DEV, O_RDONLY|O_NONBLOCK))!=-1) {
rd=read(rf, &seed, sizeof(seed));
close(rf);
}
if (rd != sizeof(seed))
#endif
seed = time32(NULL) ^ (uintmax_t)GetCurrentThreadId();
srand(seed);
sbbs_random(10); /* Throw away first number */
}
int DLLCALL sbbs_random(int n)
{
return(xp_random(n));
}
#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);
if(ver < 10000) /* auto convert 313 to 31300 */
ver*=100;
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 BUILD_JSDOCS
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",
"null",
"xml",
"array",
"alias",
"undefined"
};
JSBool
DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
uint i;
long ver;
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 */
if((ver=props[i].ver) < 10000) /* auto convert 313 to 31300 */
ver*=100;
val = INT_TO_JSVAL(ver);
if(!JS_SetElement(cx, array, len++, &val))
return(JS_FALSE);
}
}
return(JS_TRUE);
}
JSBool
DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
{
int i;
jsuint len=0;
long ver;
jsval val;
JSObject* method;
JSObject* method_array;
JSString* js_str;
size_t str_len=0;
char *str=NULL;
/* 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);
// If the first item is already in the list, don't do anything.
if(!JS_GetArrayLength(cx, method_array, &len))
return(JS_FALSE);
for(i=0; i<len; i++) {
if(JS_GetElement(cx, method_array, i, &val)!=JS_TRUE || val == JSVAL_VOID)
continue;
JS_GetProperty(cx, JSVAL_TO_OBJECT(val), "name", &val);
JSVALUE_TO_RASTRING(cx, val, str, &str_len, NULL);
if(str==NULL)
continue;
if(strcmp(str, funcs[0].name)==0)
return(JS_TRUE);
}
}
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);
}
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) {
if((ver=funcs[i].ver) < 10000) /* auto convert 313 to 31300 */
ver*=100;
val = INT_TO_JSVAL(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);
}
/*
* Always resolve all here since
* 1) We'll always be enumerating anyways
* 2) The speed penalty won't be seen in production code anyways
*/
JSBool
DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
{
JSBool ret=JS_TRUE;
if(props) {
if(!js_DefineSyncProperties(cx, obj, props))
ret=JS_FALSE;
}
if(funcs) {
if(!js_DefineSyncMethods(cx, obj, funcs))
ret=JS_FALSE;
}
if(consts) {
if(!js_DefineConstIntegers(cx, obj, consts, flags))
ret=JS_FALSE;
}
return(ret);
}
#else // NON-JSDOCS
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)
{
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);
}
JSBool
DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
{
uint i;
jsval val;
if(props) {
for(i=0;props[i].name;i++) {
if(name==NULL || strcmp(name, props[i].name)==0) {
if(!JS_DefinePropertyWithTinyId(cx, obj,
props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
return(JS_FALSE);
if(name)
return(JS_TRUE);
}
}
}
if(funcs) {
for(i=0;funcs[i].name;i++) {
if(name==NULL || strcmp(name, funcs[i].name)==0) {
if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
return(JS_FALSE);
if(name)
return(JS_TRUE);
}
}
}
if(consts) {
for(i=0;consts[i].name;i++) {
if(name==NULL || strcmp(name, consts[i].name)==0) {
val=INT_TO_JSVAL(consts[i].val);
if(!JS_DefineProperty(cx, obj, consts[i].name, val ,NULL, NULL, flags))
return(JS_FALSE);
if(name)
return(JS_TRUE);
}
}
}
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++) {
val=INT_TO_JSVAL(ints[i].val);
if(!JS_DefineProperty(cx, obj, ints[i].name, val ,NULL, NULL, flags))
return(JS_FALSE);
}
return(JS_TRUE);
}
static JSBool
js_log(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
uintN i=0;
int32 level=LOG_INFO;
JSString* str=NULL;
sbbs_t* sbbs;
jsrefcount rc;
char *line=NULL;
size_t line_sz=0;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
if(argc > 1 && JSVAL_IS_NUMBER(argv[i])) {
if(!JS_ValueToInt32(cx,argv[i++],&level))
return JS_FALSE;
}
for(; i<argc; i++) {
if((str=JS_ValueToString(cx, argv[i]))==NULL)
return(JS_FALSE);
JSSTRING_TO_RASTRING(cx, str, line, &line_sz, NULL);
if(line==NULL)
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
if(sbbs->online==ON_LOCAL) {
if(startup!=NULL && startup->event_lputs!=NULL && level <= startup->log_level) {
startup->event_lputs(startup->event_cbdata,level,line);
}
} else
lprintf(level,"Node %d %s", sbbs->cfg.node_num, line);
JS_RESUMEREQUEST(cx, rc);
}
free(line);
if(str==NULL)
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
else
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
return(JS_TRUE);
}
static JSBool
js_read(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
uchar* buf;
int32 len=128;
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
if(argc) {
if(!JS_ValueToInt32(cx,argv[0],&len))
return JS_FALSE;
}
if((buf=(uchar*)malloc(len))==NULL)
return(JS_TRUE);
rc=JS_SUSPENDREQUEST(cx);
len=RingBufRead(&sbbs->inbuf,buf,len);
JS_RESUMEREQUEST(cx, rc);
if(len>0)
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyN(cx,(char*)buf,len)));
free(buf);
return(JS_TRUE);
}
static JSBool
js_readln(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
char* buf;
int32 len=128;
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
if(argc) {
if(!JS_ValueToInt32(cx,argv[0],&len))
return JS_FALSE;
}
if((buf=(char*)malloc(len))==NULL)
return(JS_TRUE);
rc=JS_SUSPENDREQUEST(cx);
len=sbbs->getstr(buf,len,K_NONE);
JS_RESUMEREQUEST(cx, rc);
if(len>0)
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,buf)));
free(buf);
return(JS_TRUE);
}
static JSBool
js_write(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
uintN i;
JSString* str=NULL;
sbbs_t* sbbs;
jsrefcount rc;
char *cstr=NULL;
size_t cstr_sz=0;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
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);
JSSTRING_TO_RASTRING(cx, str, cstr, &cstr_sz, NULL);
if(cstr==NULL)
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
if(sbbs->online==ON_LOCAL)
eprintf(LOG_INFO,"%s",cstr);
else
sbbs->bputs(cstr);
JS_RESUMEREQUEST(cx, rc);
}
if(str==NULL)
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
else
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
return(JS_TRUE);
}
static JSBool
js_write_raw(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
uintN i;
char* str=NULL;
size_t str_sz=0;
size_t len;
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
for (i = 0; i < argc; i++) {
JSVALUE_TO_RASTRING(cx, argv[i], str, &str_sz, &len);
if(str==NULL)
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
sbbs->putcom(str, len);
JS_RESUMEREQUEST(cx, rc);
}
return(JS_TRUE);
}
static JSBool
js_writeln(JSContext *cx, uintN argc, jsval *arglist)
{
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
js_write(cx,argc,arglist);
rc=JS_SUSPENDREQUEST(cx);
if(sbbs->online==ON_REMOTE)
sbbs->bputs(crlf);
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
static JSBool
js_printf(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
char* p;
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
if((p = js_sprintf(cx, 0, argc, argv))==NULL) {
JS_ReportError(cx,"js_sprintf failed");
return(JS_FALSE);
}
rc=JS_SUSPENDREQUEST(cx);
if(sbbs->online==ON_LOCAL)
eprintf(LOG_INFO,"%s",p);
else
sbbs->bputs(p);
JS_RESUMEREQUEST(cx, rc);
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p)));
js_sprintf_free(p);
return(JS_TRUE);
}
static JSBool
js_alert(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
sbbs_t* sbbs;
jsrefcount rc;
char *cstr;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
JSVALUE_TO_MSTRING(cx, argv[0], cstr, NULL);
if(cstr==NULL)
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
sbbs->attr(sbbs->cfg.color[clr_err]);
sbbs->bputs(cstr);
free(cstr);
sbbs->attr(LIGHTGRAY);
sbbs->bputs(crlf);
JS_RESUMEREQUEST(cx, rc);
JS_SET_RVAL(cx, arglist, argv[0]);
return(JS_TRUE);
}
static JSBool
js_confirm(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
sbbs_t* sbbs;
jsrefcount rc;
char *cstr;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
JSVALUE_TO_MSTRING(cx, argv[0], cstr, NULL);
if(cstr==NULL)
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->yesno(cstr)));
free(cstr);
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
static JSBool
js_deny(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
sbbs_t* sbbs;
jsrefcount rc;
char *cstr;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
JSVALUE_TO_MSTRING(cx, argv[0], cstr, NULL);
if(cstr==NULL)
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->noyes(cstr)));
free(cstr);
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
static JSBool
js_prompt(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
char instr[81];
JSString * str;
sbbs_t* sbbs;
jsrefcount rc;
char* prompt=NULL;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
if(argc) {
JSVALUE_TO_MSTRING(cx, argv[0], prompt, NULL);
if(prompt==NULL)
return(JS_FALSE);
}
if(argc>1) {
JSVALUE_TO_STRBUF(cx, argv[1], instr, sizeof(instr), NULL);
} else
instr[0]=0;
rc=JS_SUSPENDREQUEST(cx);
if(prompt != NULL) {
sbbs->bprintf("\1n\1y\1h%s\1w: ",prompt);
free(prompt);
}
if(!sbbs->getstr(instr,sizeof(instr)-1,K_EDIT)) {
JS_SET_RVAL(cx, arglist, JSVAL_NULL);
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
JS_RESUMEREQUEST(cx, rc);
if((str=JS_NewStringCopyZ(cx, instr))==NULL)
return(JS_FALSE);
JS_SET_RVAL(cx, arglist, 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, "
"<i>values</i> are typically string constants or variables, "
"<i>level</i> is the debug level/priority (default: <tt>LOG_INFO</tt>)")
,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
},
{"write_raw", js_write_raw, 0, JSTYPE_VOID, JSDOCSTR("value [,value]")
,JSDOCSTR("send a stream of bytes (possibly containing NULLs or special control code sequences) to the server output")
,314
},
{"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), "
"<i>values</i> 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 - <small>CAUTION: for experienced C programmers ONLY</small>")
,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 (<i>value</i>) 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 <i>true</i> or <i>false</i> "
"based on user's confirmation (ala client-side JS, <i>true</i> = yes)")
,310
},
{"deny", js_deny, 1, JSTYPE_BOOLEAN, JSDOCSTR("value")
,JSDOCSTR("displays a No/Yes prompt and returns <i>true</i> or <i>false</i> "
"based on user's denial (<i>true</i> = no)")
,31501
},
{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;
jsrefcount rc;
int log_level;
char nodestr[128];
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return;
if(sbbs->cfg.node_num)
SAFEPRINTF(nodestr,"Node %d",sbbs->cfg.node_num);
else
SAFECOPY(nodestr,sbbs->client_name);
if(report==NULL) {
lprintf(LOG_ERR,"%s !JavaScript: %s", nodestr, message);
return;
}
if(report->filename)
SAFEPRINTF(file," %s",report->filename);
else
file[0]=0;
if(report->lineno)
SAFEPRINTF(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";
log_level = LOG_WARNING;
} else {
warning=nulstr;
log_level = LOG_ERR;
}
rc=JS_SUSPENDREQUEST(cx);
if(sbbs->online==ON_LOCAL)
eprintf(log_level,"!JavaScript %s%s%s: %s",warning,file,line,message);
else {
lprintf(log_level,"%s !JavaScript %s%s%s: %s",nodestr,warning,file,line,message);
sbbs->bprintf("!JavaScript %s%s%s: %s\r\n",warning,file,line,message);
}
JS_RESUMEREQUEST(cx, rc);
}
bool sbbs_t::js_init(ulong* stack_frame)
{
char node[128];
if(cfg.node_num)
SAFEPRINTF(node,"Node %d",cfg.node_num);
else
SAFECOPY(node,client_name);
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;
lprintf(LOG_DEBUG,"%s JavaScript: Creating runtime: %lu bytes"
,node,startup->js.max_bytes);
if((js_runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__))==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);
JS_BEGINREQUEST(js_cx);
memset(&js_callback,0,sizeof(js_callback));
js_callback.limit = startup->js.time_limit;
js_callback.gc_interval = startup->js.gc_interval;
js_callback.yield_interval = startup->js.yield_interval;
js_callback.terminated = &terminated;
js_callback.auto_terminate = TRUE;
bool success=false;
bool rooted=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_CreateCommonObjects(js_cx, &scfg, &cfg, js_global_functions
,uptime, startup->host_name, SOCKLIB_DESC /* system */
,&js_callback /* js */
,&startup->js
,&client, client_socket, -1 /* client */
,&js_server_props /* server */
,&js_glob
))
break;
rooted=true;
#ifdef BUILD_JSDOCS
js_CreateUifcObject(js_cx, js_glob);
js_CreateConioObject(js_cx, js_glob);
#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) {
if(rooted)
JS_RemoveObjectRoot(js_cx, &js_glob);
JS_ENDREQUEST(js_cx);
JS_DestroyContext(js_cx);
js_cx=NULL;
return(false);
}
else
JS_ENDREQUEST(js_cx);
return(true);
}
void sbbs_t::js_cleanup(const char* node)
{
/* Free Context */
if(js_cx!=NULL) {
lprintf(LOG_DEBUG,"%s JavaScript: Destroying context",node);
JS_BEGINREQUEST(js_cx);
JS_RemoveObjectRoot(js_cx, &js_glob);
JS_ENDREQUEST(js_cx);
JS_DestroyContext(js_cx);
js_cx=NULL;
}
if(js_runtime!=NULL) {
lprintf(LOG_DEBUG,"%s JavaScript: Destroying runtime",node);
jsrt_Release(js_runtime);
js_runtime=NULL;
}
}
void sbbs_t::js_create_user_objects(void)
{
if(js_cx==NULL)
return;
JS_BEGINREQUEST(js_cx);
if(!js_CreateUserObjects(js_cx, js_glob, &cfg, &useron, &client, NULL, subscan))
lprintf(LOG_ERR,"!JavaScript ERROR creating user objects");
JS_ENDREQUEST(js_cx);
}
extern "C" BOOL DLLCALL js_CreateCommonObjects(JSContext* js_cx
,scfg_t* cfg /* common */
,scfg_t* node_cfg /* node-specific */
,jsSyncMethodSpec* methods /* global */
,time_t uptime /* system */
,char* host_name /* system */
,char* socklib_desc /* system */
,js_callback_t* cb /* js */
,js_startup_t* js_startup /* js */
,client_t* client /* client */
,SOCKET client_socket /* client */
,CRYPT_CONTEXT session /* client */
,js_server_props_t* props /* server */
,JSObject** glob
)
{
BOOL success=FALSE;
if(node_cfg==NULL)
node_cfg=cfg;
/* Global Object */
if(!js_CreateGlobalObject(js_cx, cfg, methods, js_startup, glob))
return(FALSE);
do {
/* System Object */
if(js_CreateSystemObject(js_cx, *glob, node_cfg, uptime, host_name, socklib_desc)==NULL)
break;
/* Internal JS Object */
if(cb!=NULL
&& js_CreateInternalJsObject(js_cx, *glob, cb, js_startup)==NULL)
break;
/* Client Object */
if(client!=NULL
&& js_CreateClientObject(js_cx, *glob, "client", client, client_socket, session)==NULL)
break;
/* Server */
if(props!=NULL
&& js_CreateServerObject(js_cx, *glob, props)==NULL)
break;
/* Socket Class */
if(js_CreateSocketClass(js_cx, *glob)==NULL)
break;
/* Queue Class */
if(js_CreateQueueClass(js_cx, *glob)==NULL)
break;
/* MsgBase Class */
if(js_CreateMsgBaseClass(js_cx, *glob, cfg)==NULL)
break;
/* File Class */
if(js_CreateFileClass(js_cx, *glob)==NULL)
break;
/* User class */
if(js_CreateUserClass(js_cx, *glob, cfg)==NULL)
break;
/* COM Class */
if(js_CreateCOMClass(js_cx, *glob)==NULL)
break;
/* CryptContext Class */
if(js_CreateCryptContextClass(js_cx, *glob)==NULL)
break;
/* Area Objects */
if(!js_CreateUserObjects(js_cx, *glob, cfg, /* user: */NULL, client, /* html_index_fname: */NULL, /* subscan: */NULL))
break;
success=TRUE;
} while(0);
if(!success)
JS_RemoveObjectRoot(js_cx, glob);
return(success);
}
#endif /* JAVASCRIPT */
static BYTE* telnet_interpret(sbbs_t* sbbs, BYTE* inbuf, int inlen,
BYTE* outbuf, int& outlen)
{
BYTE* first_iac=NULL;
BYTE* first_cr=NULL;
int i;
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_GATE)
&& sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL
&& !(sbbs->console&CON_RAW_IN)) {
if(sbbs->telnet_last_rxch==CR)
first_cr=inbuf;
else
first_cr=(BYTE*)memchr(inbuf, CR, inlen);
}
if(!sbbs->telnet_cmdlen && first_iac==NULL && first_cr==NULL) {
outlen=inlen;
return(inbuf); // no interpretation needed
}
if(first_iac!=NULL || first_cr!=NULL) {
if(first_iac!=NULL && (first_cr==NULL || first_iac<first_cr))
outlen=first_iac-inbuf;
else
outlen=first_cr-inbuf;
memcpy(outbuf, inbuf, outlen);
} else
outlen=0;
for(i=outlen;i<inlen;i++) {
if(!(sbbs->telnet_mode&TELNET_MODE_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;
}
sbbs->telnet_last_rxch=inbuf[i];
}
if(inbuf[i]==TELNET_IAC && sbbs->telnet_cmdlen==1) { /* escaped 255 */
sbbs->telnet_cmdlen=0;
outbuf[outlen++]=TELNET_IAC;
continue;
}
if(inbuf[i]==TELNET_IAC || sbbs->telnet_cmdlen) {
if(sbbs->telnet_cmdlen<sizeof(sbbs->telnet_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) {
if(startup->options&BBS_OPT_DEBUG_TELNET)
lprintf(LOG_DEBUG,"Node %d %s telnet sub-negotiation command: %s"
,sbbs->cfg.node_num
,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
,telnet_opt_desc(option));
/* sub-option terminated */
if(option==TELNET_TERM_TYPE
&& sbbs->telnet_cmd[3]==TELNET_TERM_IS) {
safe_snprintf(sbbs->terminal,sizeof(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];
safe_snprintf(speed,sizeof(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);
sbbs->cur_rate=atoi(speed);
sbbs->cur_cps=sbbs->cur_rate/10;
#ifdef SBBS_TELNET_ENVIRON_SUPPORT
} else if(option==TELNET_NEW_ENVIRON
&& sbbs->telnet_cmd[3]==TELNET_ENVIRON_IS) {
BYTE* p;
BYTE* end=sbbs->telnet_cmd+(sbbs->telnet_cmdlen-2);
for(p=sbbs->telnet_cmd+4; p < end; ) {
if(*p==TELNET_ENVIRON_VAR || *p==TELNET_ENVIRON_USERVAR) {
BYTE type=*p++;
char* name=(char*)p;
/* RFC 1572: The characters following a "type" up to the next "type" or VALUE specify the variable name. */
while(p < end) {
if(*p==TELNET_ENVIRON_VAR || *p==TELNET_ENVIRON_USERVAR || *p == TELNET_ENVIRON_VALUE)
break;
p++;
}
if(p < end) {
char* value=(char*)p+1;
*(p++)=0;
while(p < end) {
if(*p==TELNET_ENVIRON_VAR || *p==TELNET_ENVIRON_USERVAR || *p == TELNET_ENVIRON_VALUE)
break;
p++;
}
*p=0;
lprintf(LOG_DEBUG,"Node %d telnet %s %s environment variable '%s' = '%s'"
,sbbs->cfg.node_num
,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
,type==TELNET_ENVIRON_VAR ? "well-known" : "user-defined"
,name
,value);
if(strcmp(name,"USER") == 0) {
SAFECOPY(sbbs->rlogin_name, value);
}
}
} else
p++;
}
#endif
} else if(option==TELNET_SEND_LOCATION) {
safe_snprintf(sbbs->telnet_location
,sizeof(sbbs->telnet_location)
,"%.*s",(int)sbbs->telnet_cmdlen-5,sbbs->telnet_cmd+3);
lprintf(LOG_DEBUG,"Node %d %s telnet location: %s"
,sbbs->cfg.node_num
,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
,sbbs->telnet_location);
} 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"
,cols
,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, 0x%02X"
,sbbs->cfg.node_num
,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
,telnet_opt_desc(option)
,sbbs->telnet_cmd[3]);
sbbs->telnet_cmdlen=0;
}
}
else if(sbbs->telnet_cmdlen==2 && inbuf[i]<TELNET_WILL) {
if(startup->options&BBS_OPT_DEBUG_TELNET)
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(option));
sbbs->telnet_cmdlen=0;
}
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)
SetEvent(sbbs->telnet_ack_event);
else {
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)
SetEvent(sbbs->telnet_ack_event);
else {
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:
case TELNET_SEND_LOCATION:
#ifdef SBBS_TELNET_ENVIRON_SUPPORT
case TELNET_NEW_ENVIRON:
#endif
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);
}
#ifdef SBBS_TELNET_ENVIRON_SUPPORT
else if(command==TELNET_WILL && option==TELNET_NEW_ENVIRON) {
if(startup->options&BBS_OPT_DEBUG_TELNET)
lprintf(LOG_DEBUG,"Node %d requesting USER environment variable value"
,sbbs->cfg.node_num);
char buf[64];
int len=sprintf(buf,"%c%c%c%c%c%c"
,TELNET_IAC,TELNET_SB
,TELNET_NEW_ENVIRON,TELNET_ENVIRON_SEND //,TELNET_ENVIRON_VAR
,TELNET_IAC,TELNET_SE);
sbbs->putcom(buf,len);
}
#endif
}
}
sbbs->telnet_cmdlen=0;
}
if(sbbs->telnet_mode&TELNET_MODE_GATE) // Pass-through commads
outbuf[outlen++]=inbuf[i];
} else
outbuf[outlen++]=inbuf[i];
}
return(outbuf);
}
void sbbs_t::send_telnet_cmd(uchar cmd, uchar opt)
{
char buf[16];
if(telnet_mode&TELNET_MODE_OFF)
return;
if(cmd<TELNET_WILL) {
if(startup->options&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);
}
}
bool sbbs_t::request_telnet_opt(uchar cmd, uchar opt, unsigned waitforack)
{
if(telnet_mode&TELNET_MODE_OFF)
return false;
if(cmd==TELNET_DO || cmd==TELNET_DONT) { /* remote option */
if(telnet_remote_option[opt]==telnet_opt_ack(cmd))
return true; /* 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 true; /* already set in this mode, do nothing */
telnet_local_option[opt]=telnet_opt_ack(cmd);
}
if(waitforack)
ResetEvent(telnet_ack_event);
send_telnet_cmd(cmd,opt);
if(waitforack)
return WaitForEvent(telnet_ack_event, waitforack)==WAIT_OBJECT_0;
return true;
}
void input_thread(void *arg)
{
BYTE inbuf[4000];
BYTE telbuf[sizeof(inbuf)];
BYTE *wrbuf;
int i,rd,wr,avail;
ulong total_recv=0;
ulong total_pkts=0;
fd_set socket_set;
sbbs_t* sbbs = (sbbs_t*) arg;
struct timeval tv;
SOCKET high_socket;
SOCKET sock;
SetThreadName("sbbs/termInput");
thread_up(TRUE /* setuid */);
#ifdef _DEBUG
lprintf(LOG_DEBUG,"Node %d input thread started",sbbs->cfg.node_num);
#endif
sbbs->console|=CON_R_INPUT;
while(sbbs->online && sbbs->client_socket!=INVALID_SOCKET
&& node_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) {
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(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) {
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;
#ifdef __unix__
if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) {
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;
}
}
#endif
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_DEBUG,"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;
}
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;
}
/* ^ ^
* \______ ______/
* \ * \ / * /
* ----- ------ /----\
* || -< Boo! |
* /__\ \----/
* \______________/
* \/\/\/\/\/\/\/
* ------------
*/
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;
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
continue;
}
sock=uspy_socket[sbbs->cfg.node_num-1];
}
#endif
else {
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
continue;
}
rd=RingBufFree(&sbbs->inbuf);
if(rd==0) { // 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 && time(NULL)-start<5) {
YIELD();
}
if(rd==0) { /* input buffer still full */
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
continue;
}
}
if(rd > (int)sizeof(inbuf))
rd=sizeof(inbuf);
#ifdef USE_CRYPTLIB
if(sbbs->ssh_mode && sock==sbbs->client_socket) {
int err;
pthread_mutex_lock(&sbbs->ssh_mutex);
if(!cryptStatusOK((err=cryptPopData(sbbs->ssh_session, (char*)inbuf, rd, &i)))) {
pthread_mutex_unlock(&sbbs->ssh_mutex);
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
if(err==CRYPT_ERROR_TIMEOUT)
continue;
/* Handle the SSH error here... */
lprintf(LOG_WARNING,"Node %d !ERROR %d receiving on Cryptlib session", sbbs->cfg.node_num, err);
break;
}
else {
pthread_mutex_unlock(&sbbs->ssh_mutex);
if(!i) {
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
continue;
}
rd=i;
}
}
else
#endif
rd = recv(sock, (char*)inbuf, rd, 0);
if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
if(rd == SOCKET_ERROR)
{
#ifdef __unix__
if(sock==sbbs->client_socket) {
#endif
if(!sbbs->online) // sbbs_t::hangup() called?
break;
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 && sock==sbbs->client_socket)
{
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
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(LOG_ERR,"!TELBUF OVERFLOW (%d>%d)",wr,sizeof(telbuf));
/* First level Ctrl-C checking */
if(!(sbbs->cfg.ctrlkey_passthru&(1<<CTRL_C))
&& sbbs->rio_abortable
&& !(sbbs->telnet_mode&TELNET_MODE_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(avail<wr)
lprintf(LOG_ERR,"!INPUT BUFFER FULL (%d free)", avail);
else
RingBufWrite(&sbbs->inbuf, wrbuf, wr);
// if(wr>100)
// mswait(500); // Throttle sender
}
sbbs->online=FALSE;
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
thread_down();
lprintf(LOG_DEBUG,"Node %d input thread terminated (received %lu bytes in %lu blocks)"
,sbbs->cfg.node_num, total_recv, total_pkts);
}
#ifdef USE_CRYPTLIB
/*
* This thread copies anything received from the client to the passthru_socket
* It can only do that when the input thread is locked.
* Luckily, the input thread is currently locked exactly when we want it to be.
* Since the passthru socket is 8-bit clean and does NOT use a protocol,
* we must handle telnet stuff HERE.
* However, for JS stuff, direct operations on client_socket should generally
* be done to the passthru_socket instead... THIS is the biggest problem here.
*/
void passthru_output_thread(void* arg)
{
fd_set socket_set;
sbbs_t *sbbs = (sbbs_t*) arg;
struct timeval tv;
int i;
BYTE inbuf[4000];
BYTE telbuf[sizeof(inbuf)];
BYTE *wrbuf;
int rd;
int wr;
SetThreadName("sbbs/ptOutput");
thread_up(FALSE /* setuid */);
while(sbbs->client_socket!=INVALID_SOCKET && sbbs->passthru_socket!=INVALID_SOCKET && !terminate_server) {
if(!sbbs->input_thread_mutex_locked) {
SLEEP(1);
continue;
}
FD_ZERO(&socket_set);
FD_SET(sbbs->client_socket,&socket_set);
tv.tv_sec=1;
tv.tv_usec=0;
if((i=select(sbbs->client_socket+1,&socket_set,NULL,NULL,&tv))<1) {
if(i==0) {
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(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_DEBUG,"Node %d passthru output 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 ->select socket %d"
,sbbs->cfg.node_num, ERROR_VALUE, sbbs->client_socket);
break;
}
if(sbbs->client_socket==INVALID_SOCKET)
break;
rd=sizeof(inbuf);
#ifdef USE_CRYPTLIB
if(sbbs->ssh_mode) {
pthread_mutex_lock(&sbbs->ssh_mutex);
if(!cryptStatusOK(cryptPopData(sbbs->ssh_session, (char*)inbuf, rd, &i)))
rd=0;
else {
if(!i) {
pthread_mutex_unlock(&sbbs->ssh_mutex);
continue;
}
rd=i;
}
pthread_mutex_unlock(&sbbs->ssh_mutex);
}
else
#endif
rd = recv(sbbs->client_socket, (char*)inbuf, rd, 0);
if(rd == SOCKET_ERROR)
{
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, sbbs->client_socket);
break;
}
if(rd == 0)
{
lprintf(LOG_DEBUG,"Node %d passthru input socket disconnected", sbbs->cfg.node_num);
break;
}
// telbuf and wr are modified to reflect telnet escaped data
wr=rd;
if(sbbs->telnet_mode&TELNET_MODE_OFF)
wrbuf=inbuf;
else
wrbuf=telnet_interpret(sbbs, inbuf, rd, telbuf, wr);
if(wr > (int)sizeof(telbuf))
lprintf(LOG_ERR,"!TELBUF OVERFLOW (%d>%d)",wr,sizeof(telbuf));
/*
* TODO: This should check for writability etc.
*/
sendsocket(sbbs->passthru_socket, (char*)wrbuf, wr);
}
sbbs->passthru_output_thread_running = false;
}
/*
* This thread simply copies anything it manages to read from the
* passthru_socket into the output ringbuffer.
*/
void passthru_input_thread(void* arg)
{
fd_set r_set;
sbbs_t *sbbs = (sbbs_t*) arg;
struct timeval tv;
BYTE ch;
int i;
SetThreadName("sbbs/ptInput");
thread_up(FALSE /* setuid */);
while(sbbs->passthru_socket!=INVALID_SOCKET && !terminate_server) {
tv.tv_sec=1;
tv.tv_usec=0;
FD_ZERO(&r_set);
FD_SET(sbbs->passthru_socket,&r_set);
if((i=select(sbbs->passthru_socket+1,&r_set,NULL,NULL,&tv))<1) {
if(i==0) {
YIELD(); /* This kludge is necessary on some Linux distros */
continue; /* to allow other threads to lock the input_thread_mutex */
}
if(sbbs->passthru_socket==INVALID_SOCKET)
break;
if(ERROR_VALUE == ENOTSOCK)
lprintf(LOG_NOTICE,"Node %d socket closed by peer on passthru->select", sbbs->cfg.node_num);
else if(ERROR_VALUE==ESHUTDOWN)
lprintf(LOG_NOTICE,"Node %d socket shutdown on passthru->select", sbbs->cfg.node_num);
else if(ERROR_VALUE==EINTR)
lprintf(LOG_DEBUG,"Node %d passthru thread interrupted",sbbs->cfg.node_num);
else if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_NOTICE,"Node %d connection reset by peer on passthru->select", sbbs->cfg.node_num);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_NOTICE,"Node %d connection aborted by peer on passthru->select", sbbs->cfg.node_num);
else
lprintf(LOG_WARNING,"Node %d !ERROR %d passthru->select socket %d"
,sbbs->cfg.node_num, ERROR_VALUE, sbbs->passthru_socket);
break;
}
if(!RingBufFree(&sbbs->outbuf))
continue;
i = recv(sbbs->passthru_socket, (char*)(&ch), 1, 0);
if(i == SOCKET_ERROR)
{
if(ERROR_VALUE == ENOTSOCK)
lprintf(LOG_NOTICE,"Node %d passthru socket closed by peer on receive", sbbs->cfg.node_num);
else if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_NOTICE,"Node %d passthru connection reset by peer on receive", sbbs->cfg.node_num);
else if(ERROR_VALUE==ESHUTDOWN)
lprintf(LOG_NOTICE,"Node %d passthru socket shutdown on receive", sbbs->cfg.node_num);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_NOTICE,"Node %d passthru connection aborted by peer on receive", sbbs->cfg.node_num);
else
lprintf(LOG_WARNING,"Node %d !ERROR %d receiving from passthru socket %d"
,sbbs->cfg.node_num, ERROR_VALUE, sbbs->passthru_socket);
break;
}
if(i == 0)
{
lprintf(LOG_NOTICE,"Node %d SSH passthru disconnected", sbbs->cfg.node_num);
break;
}
if(!RingBufWrite(&sbbs->outbuf, &ch, 1)) {
lprintf(LOG_ERR,"Cannot pass from passthru socket to outbuf");
break;
}
}
if(sbbs->passthru_socket!=INVALID_SOCKET) {
close_socket(sbbs->passthru_socket);
sbbs->passthru_socket=INVALID_SOCKET;
}
thread_down();
sbbs->passthru_input_thread_running = false;
}
#endif
void output_thread(void* arg)
{
char node[128];
char stats[128];
BYTE buf[IO_THREAD_BUF_SIZE];
int i=0; // Assignment to silence Valgrind
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;
ulong mss=IO_THREAD_BUF_SIZE;
SetThreadName("sbbs/termOutput");
thread_up(TRUE /* setuid */);
if(sbbs->cfg.node_num)
SAFEPRINTF(node,"Node %d",sbbs->cfg.node_num);
else
SAFECOPY(node,sbbs->client_name);
#ifdef _DEBUG
lprintf(LOG_DEBUG,"%s output thread started",node);
#endif
sbbs->console|=CON_R_ECHO;
#ifdef TCP_MAXSEG
/*
* Auto-tune the highwater mark to be the negotiated MSS for the
* socket (when possible)
*/
if(!sbbs->outbuf.highwater_mark) {
socklen_t sl;
sl=sizeof(i);
if(!getsockopt(sbbs->client_socket, IPPROTO_TCP, TCP_MAXSEG,
#ifdef _WIN32
(char *)
#endif
&i, &sl)) {
/* Check for sanity... */
if(i>100) {
sbbs->outbuf.highwater_mark=i;
lprintf(LOG_DEBUG,"Autotuning outbuf highwater mark to %d based on MSS",i);
mss=sbbs->outbuf.highwater_mark;
if(mss>IO_THREAD_BUF_SIZE) {
mss=IO_THREAD_BUF_SIZE;
lprintf(LOG_DEBUG,"MSS (%d) is higher than IO_THREAD_BUF_SIZE (%d)",i,IO_THREAD_BUF_SIZE);
}
}
}
}
#endif
/* Note: do not terminate when online==FALSE, that is expected for the terminal server output_thread */
while(sbbs->client_socket!=INVALID_SOCKET && !terminate_server) {
/*
* I'd like to check the linear buffer against the highwater
* at this point, but it would get too clumsy imho - Deuce
*
* Actually, another option would just be to have the size
* of the linear buffer equal to the MSS... any larger and
* you could have small sends off the end. this would
* probobly be even clumbsier
*/
if(bufbot == buftop) {
/* Wait for something to output in the RingBuffer */
if((avail=RingBufFull(&sbbs->outbuf))==0) { /* empty */
if(sem_trywait_block(&sbbs->outbuf.sem,1000))
continue;
/* Check for spurious sem post... */
if((avail=RingBufFull(&sbbs->outbuf))==0)
continue;
}
else
sem_trywait(&sbbs->outbuf.sem);
/* Wait for full buffer or drain timeout */
if(sbbs->outbuf.highwater_mark) {
if(avail<sbbs->outbuf.highwater_mark) {
sem_trywait_block(&sbbs->outbuf.highwater_sem,startup->outbuf_drain_timeout);
/* We (potentially) blocked, so get fill level again */
avail=RingBufFull(&sbbs->outbuf);
} else
sem_trywait(&sbbs->outbuf.highwater_sem);
}
/*
* At this point, there's something to send and,
* if the highwater mark is set, the timeout has
* passed or we've hit highwater. Read ring buffer
* into linear buffer.
*/
if(avail>sizeof(buf)) {
lprintf(LOG_WARNING,"%s !Insufficient linear output buffer (%lu > %lu)"
,node, avail, sizeof(buf));
avail=sizeof(buf);
}
/* If we know the MSS, use it as the max send() size. */
if(avail>mss)
avail=mss;
buftop=RingBufRead(&sbbs->outbuf, buf, avail);
bufbot=0;
if (buftop == 0)
continue;
}
/* Check socket for writability (using select) */
tv.tv_sec=0;
tv.tv_usec=1000;
FD_ZERO(&socket_set);
if(sbbs->client_socket==INVALID_SOCKET) // Make the race condition less likely to actually happen... TODO: Fix race
continue;
FD_SET(sbbs->client_socket,&socket_set);
i=select(sbbs->client_socket+1,NULL,&socket_set,NULL,&tv);
if(i==SOCKET_ERROR) {
if(sbbs->client_socket!=INVALID_SOCKET)
lprintf(LOG_WARNING,"%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); /* Purge output ring buffer */
bufbot=buftop=0; /* Purge linear buffer */
continue;
}
if(i<1) {
continue;
}
#ifdef USE_CRYPTLIB
if(sbbs->ssh_mode) {
int err;
pthread_mutex_lock(&sbbs->ssh_mutex); /* exception here May-22-2013:
sbbs.dll!pthread_mutex_lock(_RTL_CRITICAL_SECTION * mutex=0x0a45e95c) Line 147 + 0xc bytes C
> sbbs.dll!output_thread(void * arg=0x0a458c68) Line 2126 + 0x11 bytes C++
sbbs.dll!_callthreadstart() Line 259 + 0xf bytes C
sbbs.dll!_threadstart(void * ptd=0x04b20c40) Line 243 C
- sbbs->ssh_mutex {DebugInfo=0x00000000 LockCount=-4 RecursionCount=0 ...} _RTL_CRITICAL_SECTION
+ DebugInfo 0x00000000 {Type=??? CreatorBackTraceIndex=??? CriticalSection=??? ...} _RTL_CRITICAL_SECTION_DEBUG *
LockCount -4 long
RecursionCount 0 long
OwningThread 0x00000000 void *
LockSemaphore 0x00006354 void *
SpinCount 0 unsigned long
*/
if(!cryptStatusOK((err=cryptPushData(sbbs->ssh_session, (char*)buf+bufbot, buftop-bufbot, &i)))) {
/* Handle the SSH error here... */
lprintf(LOG_WARNING,"%s !ERROR %d sending on Cryptlib session", node, err);
sbbs->online=FALSE;
i=buftop-bufbot; // Pretend we sent it all
}
else {
if(!cryptStatusOK((err=cryptFlushData(sbbs->ssh_session)))) {
lprintf(LOG_WARNING,"%s !ERROR %d flushing Cryptlib session", node, err);
sbbs->online=FALSE;
i=buftop-bufbot; // Pretend we sent it all
}
}
pthread_mutex_unlock(&sbbs->ssh_mutex);
}
else
#endif
i=sendsocket(sbbs->client_socket, (char*)buf+bufbot, buftop-bufbot);
if(i==SOCKET_ERROR) {
if(ERROR_VALUE == ENOTSOCK)
lprintf(LOG_NOTICE,"%s client socket closed on send", node);
else if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_NOTICE,"%s connection reset by peer on send", node);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_NOTICE,"%s connection aborted by peer on send", node);
else
lprintf(LOG_WARNING,"%s !ERROR %d sending on socket %d"
,node, ERROR_VALUE, sbbs->client_socket);
sbbs->online=FALSE;
/* was break; on 4/7/00 */
i=buftop-bufbot; // Pretend we sent it all
}
if(sbbs->cfg.node_num>0 && !(sbbs->sys_status&SS_FILEXFER)) {
/* Spy on the user locally */
if(startup->node_spybuf!=NULL
&& startup->node_spybuf[sbbs->cfg.node_num-1]!=NULL) {
RingBufWrite(startup->node_spybuf[sbbs->cfg.node_num-1],buf+bufbot,i);
/* Signal spy output semaphore? */
if(startup->node_spysem!=NULL
&& startup->node_spysem[sbbs->cfg.node_num-1]!=NULL)
sem_post(startup->node_spysem[sbbs->cfg.node_num-1]);
}
/* Spy on the user remotely */
if(spy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET)
sendsocket(spy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i);
#ifdef __unix__
if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET)
sendsocket(uspy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i);
#endif
}
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;
if(total_sent)
safe_snprintf(stats,sizeof(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)
{
ulong stack_frame;
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;
/* TODO: This is a silly hack... */
uint32_t l32;
time_t now;
time_t start;
time_t lastsemchk=0;
time_t lastnodechk=0;
time32_t lastprepack=0;
time_t tmptime;
node_t node;
glob_t g;
sbbs_t* sbbs = (sbbs_t*) arg;
struct tm now_tm;
struct tm tm;
eprintf(LOG_INFO,"BBS Events thread started");
sbbs_srand(); /* Seed random number generator */
SetThreadName("sbbs/events");
thread_up(TRUE /* setuid */);
#ifdef JAVASCRIPT
if(!(startup->options&BBS_OPT_NO_JAVASCRIPT)) {
if(!sbbs->js_init(&stack_frame)) /* This must be done in the context of the events thread */
lprintf(LOG_ERR,"!JavaScript Initialization FAILURE");
}
#endif
// Read TIME.DAB
SAFEPRINTF(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;i<sbbs->cfg.total_events;i++) {
sbbs->cfg.event[i]->last=0;
if(filelength(file)<(long)(sizeof(time32_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(sbbs->cfg.event[i]->last));
} else {
if(read(file,&sbbs->cfg.event[i]->last,sizeof(sbbs->cfg.event[i]->last))!=sizeof(sbbs->cfg.event[i]->last))
sbbs->errormsg(WHERE,ERR_READ,str,sizeof(time32_t));
}
/* 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(lastprepack)); /* expected to fail first time */
close(file);
}
// Read QNET.DAB
SAFEPRINTF(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;i<sbbs->cfg.total_qhubs;i++) {
sbbs->cfg.qhub[i]->last=0;
if(filelength(file)<(long)(sizeof(time32_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(sbbs->cfg.qhub[i]->last));
} else {
if(read(file,&sbbs->cfg.qhub[i]->last,sizeof(sbbs->cfg.qhub[i]->last))!=sizeof(sbbs->cfg.qhub[i]->last))
sbbs->errormsg(WHERE,ERR_READ,str,sizeof(sbbs->cfg.qhub[i]->last));
}
}
close(file);
}
// Read PNET.DAB
SAFEPRINTF(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;i<sbbs->cfg.total_phubs;i++) {
sbbs->cfg.phub[i]->last=0;
if(filelength(file)<(long)(sizeof(time32_t)*(i+1)))
write(file,&sbbs->cfg.phub[i]->last,sizeof(sbbs->cfg.phub[i]->last));
else
read(file,&sbbs->cfg.phub[i]->last,sizeof(sbbs->cfg.phub[i]->last));
}
close(file);
}
while(!sbbs->terminated && !terminate_server) {
if(startup->options&BBS_OPT_NO_EVENTS) {
SLEEP(1000);
continue;
}
now=time(NULL);
localtime_r(&now,&now_tm);
if(now-lastsemchk>=sbbs->cfg.node_sem_check) {
check_semaphores=true;
lastsemchk=now;
} else
check_semaphores=false;
sbbs->online=FALSE; /* reset this from ON_LOCAL */
/* QWK events */
if(check_semaphores && !(startup->options&BBS_OPT_NO_QWK_EVENTS)) {
/* Import any REP files that have magically appeared (via FTP perhaps) */
SAFEPRINTF(str,"%sfile/",sbbs->cfg.data_dir);
offset=strlen(str);
strcat(str,"*.rep");
glob(str,0,NULL,&g);
for(i=0;i<(int)g.gl_pathc && !sbbs->terminated;i++) {
sbbs->useron.number=atoi(g.gl_pathv[i]+offset);
getuserdat(&sbbs->cfg,&sbbs->useron);
if(sbbs->useron.number && flength(g.gl_pathv[i])>0) {
SAFEPRINTF(semfile,"%s.lock",g.gl_pathv[i]);
if(!fmutex(semfile,startup->host_name,TIMEOUT_MUTEX_FILE)) {
eprintf(LOG_INFO,"%s exists (unpack in progress?)", semfile);
continue;
}
sbbs->online=ON_LOCAL;
eprintf(LOG_INFO,"Un-packing QWK Reply packet from %s",sbbs->useron.alias);
sbbs->getusrsubs();
sbbs->unpack_rep(g.gl_pathv[i]);
delfiles(sbbs->cfg.temp_dir,ALLFILES); /* clean-up temp_dir after unpacking */
sbbs->batch_create_list(); /* FREQs? */
sbbs->batdn_total=0;
/* putuserdat? */
remove(g.gl_pathv[i]);
remove(semfile);
}
}
globfree(&g);
/* Create any QWK files that have magically appeared (via FTP perhaps) */
SAFEPRINTF(str,"%spack*.now",sbbs->cfg.data_dir);
offset=strlen(sbbs->cfg.data_dir)+4;
glob(str,0,NULL,&g);
for(i=0;i<(int)g.gl_pathc && !sbbs->terminated;i++) {
eprintf(LOG_INFO,"QWK pack semaphore signaled: %s", g.gl_pathv[i]);
sbbs->useron.number=atoi(g.gl_pathv[i]+offset);
SAFEPRINTF2(semfile,"%spack%04u.lock",sbbs->cfg.data_dir,sbbs->useron.number);
if(!fmutex(semfile,startup->host_name,TIMEOUT_MUTEX_FILE)) {
eprintf(LOG_INFO,"%s exists (pack in progress?)", semfile);
continue;
}
getuserdat(&sbbs->cfg,&sbbs->useron);
if(sbbs->useron.number && !(sbbs->useron.misc&(DELETED|INACTIVE))) {
eprintf(LOG_INFO,"Packing QWK Message Packet for %s",sbbs->useron.alias);
sbbs->online=ON_LOCAL;
sbbs->getmsgptrs();
sbbs->getusrsubs();
sbbs->batdn_total=0;
sbbs->last_ns_time=sbbs->ns_time=sbbs->useron.ns_time;
SAFEPRINTF2(bat_list,"%sfile/%04u.dwn",sbbs->cfg.data_dir,sbbs->useron.number);
sbbs->batch_add_list(bat_list);
SAFEPRINTF3(str,"%sfile%c%04u.qwk"
,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
eprintf(LOG_INFO,"No packet created (no new messages)");
delfiles(sbbs->cfg.temp_dir,ALLFILES);
sbbs->online=FALSE;
}
remove(g.gl_pathv[i]);
remove(semfile);
}
globfree(&g);
/* Create (pre-pack) QWK files for users configured as such */
SAFEPRINTF(semfile,"%sprepack.now",sbbs->cfg.data_dir);
if(sbbs->cfg.preqwk_ar[0]
&& (fexistcase(semfile) || (now-lastprepack)/60>(60*24))) {
j=lastuser(&sbbs->cfg);
eprintf(LOG_INFO,"Pre-packing QWK Message packets...");
int userfile = openuserdat(&sbbs->cfg, /* for_modify: */FALSE);
for(i=1;i<=j;i++) {
SAFEPRINTF2(str,"%5u of %-5u",i,j);
//status(str);
sbbs->useron.number=i;
fgetuserdat(&sbbs->cfg,&sbbs->useron, userfile);
if(sbbs->useron.number
&& !(sbbs->useron.misc&(DELETED|INACTIVE)) /* Pre-QWK */
&& sbbs->chk_ar(sbbs->cfg.preqwk_ar,&sbbs->useron,/* client: */NULL)) {
for(k=1;k<=sbbs->cfg.sys_nodes;k++) {
if(sbbs->getnodedat(k,&node,0)!=0)
continue;
if((node.status==NODE_INUSE || node.status==NODE_QUIET
|| node.status==NODE_LOGON) && node.useron==i)
break;
}
if(k<=sbbs->cfg.sys_nodes) /* Don't pre-pack with user online */
continue;
eprintf(LOG_INFO,"Pre-packing QWK for %s",sbbs->useron.alias);
sbbs->online=ON_LOCAL;
sbbs->getmsgptrs();
sbbs->getusrsubs();
sbbs->batdn_total=0;
SAFEPRINTF3(str,"%sfile%c%04u.qwk"
,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();
}
delfiles(sbbs->cfg.temp_dir,ALLFILES);
sbbs->online=FALSE;
}
}
close(userfile);
lastprepack=(time32_t)now;
SAFEPRINTF(str,"%stime.dab",sbbs->cfg.ctrl_dir);
if((file=sbbs->nopen(str,O_WRONLY))==-1) {
sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
break;
}
lseek(file,(long)sbbs->cfg.total_events*4L,SEEK_SET);
write(file,&lastprepack,sizeof(lastprepack));
close(file);
remove(semfile);
//status(STATUS_WFC);
}
}
if(check_semaphores) {
/* Run daily maintenance? */
sbbs->cfg.node_num=0;
sbbs->logonstats();
if(sbbs->sys_status&SS_DAILY)
sbbs->daily_maint();
/* Node Daily Events */
for(i=first_node;i<=last_node;i++) {
// Node Daily Event
node.status=NODE_INVALID_STATUS;
if(sbbs->getnodedat(i,&node,0)!=0)
continue;
if(node.misc&NODE_EVENT && node.status==NODE_WFC) {
sbbs->getnodedat(i,&node,1);
node.status=NODE_EVENT_RUNNING;
sbbs->putnodedat(i,&node);
if(sbbs->cfg.node_daily[0]) {
sbbs->cfg.node_num=i;
strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[i-1]);
eprintf(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;i<sbbs->cfg.total_qhubs;i++) {
if(sbbs->cfg.qhub[i]->node<first_node
|| sbbs->cfg.qhub[i]->node>last_node)
continue;
if(sbbs->cfg.qhub[i]->last==-1) // already signaled
continue;
SAFEPRINTF2(str,"%sqnet/%s.now",sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
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;i<sbbs->cfg.total_events;i++) {
if((sbbs->cfg.event[i]->node<first_node
|| sbbs->cfg.event[i]->node>last_node)
&& !(sbbs->cfg.event[i]->misc&EVENT_EXCL))
continue; // ignore non-exclusive events for other instances
if(sbbs->cfg.event[i]->misc&EVENT_DISABLED)
continue;
if(sbbs->cfg.event[i]->last==-1) // already signaled
continue;
SAFEPRINTF2(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
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;i<sbbs->cfg.total_qhubs && !sbbs->terminated;i++) {
if(sbbs->cfg.qhub[i]->node<first_node ||
sbbs->cfg.qhub[i]->node>last_node)
continue;
if(check_semaphores) {
// See if any packets have come in
SAFEPRINTF2(str,"%s%s.q??",sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
glob(str,GLOB_NOSORT,NULL,&g);
for(j=0;j<(int)g.gl_pathc;j++) {
SAFECOPY(str,g.gl_pathv[j]);
if(flength(str)>0) { /* silently ignore 0-byte QWK packets */
eprintf(LOG_DEBUG,"Inbound QWK Packet detected: %s", str);
sbbs->online=ON_LOCAL;
sbbs->console|=CON_L_ECHO;
if(sbbs->unpack_qwk(str,i)==false) {
char newname[MAX_PATH+1];
SAFEPRINTF2(newname,"%s.%lx.bad",str,(long)now);
remove(newname);
if(rename(str,newname)==0) {
char logmsg[MAX_PATH*3];
SAFEPRINTF2(logmsg,"%s renamed to %s",str,newname);
sbbs->logline(LOG_NOTICE,"Q!",logmsg);
}
}
delfiles(sbbs->cfg.temp_dir,ALLFILES);
sbbs->console&=~CON_L_ECHO;
sbbs->online=FALSE;
remove(str);
}
}
globfree(&g);
}
/* Qnet call out based on time */
tmptime=sbbs->cfg.qhub[i]->last;
if(localtime_r(&tmptime,&tm)==NULL)
memset(&tm,0,sizeof(tm));
if(sbbs->cfg.qhub[i]->last==-1L /* or frequency */
|| (((sbbs->cfg.qhub[i]->freq && (now-sbbs->cfg.qhub[i]->last)/60>=sbbs->cfg.qhub[i]->freq)
|| (sbbs->cfg.qhub[i]->time
&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.qhub[i]->time
&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
&& sbbs->cfg.qhub[i]->days&(1<<now_tm.tm_wday))) {
SAFEPRINTF2(str,"%sqnet/%s.now"
,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
if(fexistcase(str))
remove(str); /* Remove semaphore file */
SAFEPRINTF2(str,"%sqnet/%s.ptr"
,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
file=sbbs->nopen(str,O_RDONLY);
for(j=0;j<sbbs->cfg.qhub[i]->subs;j++) {
sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr=0;
if(file!=-1) {
lseek(file,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*sizeof(int32_t),SEEK_SET);
read(file,&sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr,sizeof(sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr));
}
}
if(file!=-1)
close(file);
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 {
for(j=l=0;j<sbbs->cfg.qhub[i]->subs;j++) {
while(filelength(file)<
sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*4L) {
l32=l;
write(file,&l32,4); /* initialize ptrs to null */
}
lseek(file
,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*sizeof(int32_t)
,SEEK_SET);
write(file,&sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr,sizeof(sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]].ptr));
}
close(file);
}
}
delfiles(sbbs->cfg.temp_dir,ALLFILES);
sbbs->cfg.qhub[i]->last=time32(NULL);
SAFEPRINTF(str,"%sqnet.dab",sbbs->cfg.ctrl_dir);
if((file=sbbs->nopen(str,O_WRONLY))==-1) {
sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
break;
}
lseek(file,sizeof(time32_t)*i,SEEK_SET);
write(file,&sbbs->cfg.qhub[i]->last,sizeof(sbbs->cfg.qhub[i]->last));
close(file);
if(sbbs->cfg.qhub[i]->call[0]) {
sbbs->cfg.node_num=sbbs->cfg.qhub[i]->node;
if(sbbs->cfg.node_num<1)
sbbs->cfg.node_num=1;
strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
eprintf(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
,sbbs->cfg.qhub[i]->id,sbbs->cfg.qhub[i]->id,NULL)
,EX_OFFLINE|EX_SH); /* sh for Unix perl scripts */
}
}
}
/* PostLink Networking Call-out Events */
for(i=0;i<sbbs->cfg.total_phubs && !sbbs->terminated;i++) {
if(sbbs->cfg.phub[i]->node<first_node
|| sbbs->cfg.phub[i]->node>last_node)
continue;
/* PostLink call out based on time */
tmptime=sbbs->cfg.phub[i]->last;
if(localtime_r(&tmptime,&tm)==NULL)
memset(&tm,0,sizeof(tm));
if(sbbs->cfg.phub[i]->last==-1
|| (((sbbs->cfg.phub[i]->freq /* or frequency */
&& (now-sbbs->cfg.phub[i]->last)/60>=sbbs->cfg.phub[i]->freq)
|| (sbbs->cfg.phub[i]->time
&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.phub[i]->time
&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
&& sbbs->cfg.phub[i]->days&(1<<now_tm.tm_wday))) {
sbbs->cfg.phub[i]->last=time32(NULL);
SAFEPRINTF(str,"%spnet.dab",sbbs->cfg.ctrl_dir);
if((file=sbbs->nopen(str,O_WRONLY))==-1) {
sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
break;
}
lseek(file,sizeof(time32_t)*i,SEEK_SET);
write(file,&sbbs->cfg.phub[i]->last,sizeof(sbbs->cfg.phub[i]->last));
close(file);
if(sbbs->cfg.phub[i]->call[0]) {
sbbs->cfg.node_num=sbbs->cfg.phub[i]->node;
if(sbbs->cfg.node_num<1)
sbbs->cfg.node_num=1;
strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
eprintf(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|EX_SH); /* sh for Unix perl scripts */
}
}
}
/* Timed Events */
for(i=0;i<sbbs->cfg.total_events && !sbbs->terminated;i++) {
if(!sbbs->cfg.event[i]->node
|| sbbs->cfg.event[i]->node>sbbs->cfg.sys_nodes)
continue; // ignore events for invalid nodes
if(sbbs->cfg.event[i]->misc&EVENT_DISABLED)
continue;
if((sbbs->cfg.event[i]->node<first_node
|| sbbs->cfg.event[i]->node>last_node)
&& !(sbbs->cfg.event[i]->misc&EVENT_EXCL))
continue; // ignore non-exclusive events for other instances
tmptime=sbbs->cfg.event[i]->last;
if(localtime_r(&tmptime,&tm)==NULL)
memset(&tm,0,sizeof(tm));
if(sbbs->cfg.event[i]->last==-1 ||
(((sbbs->cfg.event[i]->freq
&& (now-sbbs->cfg.event[i]->last)/60>=sbbs->cfg.event[i]->freq)
|| (!sbbs->cfg.event[i]->freq
&& (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.event[i]->time
&& (now_tm.tm_mday!=tm.tm_mday || now_tm.tm_mon!=tm.tm_mon)))
&& sbbs->cfg.event[i]->days&(1<<now_tm.tm_wday)
&& (sbbs->cfg.event[i]->mdays==0
|| sbbs->cfg.event[i]->mdays&(1<<now_tm.tm_mday))
&& (sbbs->cfg.event[i]->months==0
|| sbbs->cfg.event[i]->months&(1<<now_tm.tm_mon))))
{
if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */
if(sbbs->cfg.event[i]->node<first_node
|| sbbs->cfg.event[i]->node>last_node) {
eprintf(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-start>10 && now-lastnodechk<10)
continue;
for(j=first_node;j<=last_node;j++) {
if(sbbs->getnodedat(j,&node,1)!=0)
continue;
if(node.status==NODE_WFC)
node.status=NODE_EVENT_LIMBO;
node.aux=sbbs->cfg.event[i]->node;
sbbs->putnodedat(j,&node);
}
lastnodechk=now;
SAFEPRINTF(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=(time32_t)now;
continue;
}
lseek(file,(long)i*4L,SEEK_SET);
read(file,&sbbs->cfg.event[i]->last,sizeof(sbbs->cfg.event[i]->last));
close(file);
if(now-sbbs->cfg.event[i]->last<(60*60)) /* event is done */
break;
if(now-start>(90*60)) {
eprintf(LOG_WARNING,"!TIMEOUT waiting for event to complete");
break;
}
}
SAFEPRINTF2(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
if(fexistcase(str))
remove(str);
sbbs->cfg.event[i]->last=(time32_t)now;
} else { // Exclusive event to run on a node under our control
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-start>10 && now-lastnodechk<10)
continue;
lastnodechk=now;
// Check/change the status of the nodes that we're in control of
for(j=first_node;j<=last_node;j++) {
if(sbbs->getnodedat(j,&node,1)!=0)
continue;
if(node.status==NODE_WFC) {
if(j==sbbs->cfg.event[i]->node)
node.status=NODE_EVENT_WAITING;
else
node.status=NODE_EVENT_LIMBO;
node.aux=sbbs->cfg.event[i]->node;
}
sbbs->putnodedat(j,&node);
}
for(j=1;j<=sbbs->cfg.sys_nodes;j++) {
if(sbbs->getnodedat(j,&node,0)!=0)
continue;
if(j==sbbs->cfg.event[i]->node) {
if(node.status!=NODE_EVENT_WAITING)
break;
} else {
if(node.status!=NODE_OFFLINE
&& node.status!=NODE_EVENT_LIMBO)
break;
}
}
if(j>sbbs->cfg.sys_nodes) /* all nodes either offline or in limbo */
break;
eprintf(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;
}
#endif
if(sbbs->cfg.event[i]->node<first_node
|| sbbs->cfg.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=(time32_t)now;
for(j=first_node;j<=last_node;j++) {
node.status=NODE_INVALID_STATUS;
if(sbbs->getnodedat(j,&node,1)!=0)
continue;
node.status=NODE_WFC;
sbbs->putnodedat(j,&node);
}
}
else {
sbbs->cfg.node_num=sbbs->cfg.event[i]->node;
if(sbbs->cfg.node_num<1)
sbbs->cfg.node_num=1;
strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
SAFEPRINTF2(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code);
if(fexistcase(str))
remove(str);
if(sbbs->cfg.event[i]->misc&EVENT_EXCL) {
sbbs->getnodedat(sbbs->cfg.event[i]->node,&node,1);
node.status=NODE_EVENT_RUNNING;
sbbs->putnodedat(sbbs->cfg.event[i]->node,&node);
}
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;
strcpy(str,sbbs->cfg.event[i]->code);
eprintf(LOG_INFO,"Running %s%stimed event: %s"
,(ex_mode&EX_NATIVE) ? "native ":""
,(ex_mode&EX_BG) ? "background ":""
,strupr(str));
{
int result=
sbbs->external(
sbbs->cmdstr(sbbs->cfg.event[i]->cmd,nulstr,sbbs->cfg.event[i]->dir,NULL)
,ex_mode
,sbbs->cfg.event[i]->dir);
if(!(ex_mode&EX_BG))
eprintf(result ? LOG_ERR : LOG_INFO,"Timed event: %s returned %d",strupr(str), result);
}
sbbs->cfg.event[i]->last=time32(NULL);
SAFEPRINTF(str,"%stime.dab",sbbs->cfg.ctrl_dir);
if((file=sbbs->nopen(str,O_WRONLY))==-1) {
sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY);
break;
}
lseek(file,(long)i*4L,SEEK_SET);
write(file,&sbbs->cfg.event[i]->last,sizeof(sbbs->cfg.event[i]->last));
close(file);
if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */
// Check/change the status of the nodes that we're in control of
for(j=first_node;j<=last_node;j++) {
node.status=NODE_INVALID_STATUS;
if(sbbs->getnodedat(j,&node,1)!=0)
continue;
node.status=NODE_WFC;
sbbs->putnodedat(j,&node);
}
}
}
}
}
mswait(1000);
}
sbbs->cfg.node_num=0;
sbbs->js_cleanup(sbbs->client_name);
sbbs->event_thread_running = false;
thread_down();
eprintf(LOG_INFO,"BBS Events thread terminated");
}
//****************************************************************************
sbbs_t::sbbs_t(ushort node_num, union xp_sockaddr *addr, size_t addr_len, const char* name, SOCKET sd,
scfg_t* global_cfg, char* global_text[], client_t* client_info)
{
char nodestr[32];
char path[MAX_PATH+1];
uint i;
if(node_num)
SAFEPRINTF(nodestr,"Node %d",node_num);
else
SAFECOPY(nodestr,name);
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>0) {
strcpy(cfg.node_dir, cfg.node_path[node_num-1]);
prep_dir(cfg.node_dir, cfg.temp_dir, sizeof(cfg.temp_dir));
} else { /* event thread needs exclusive-use temp_dir */
if(startup->temp_dir[0])
SAFECOPY(cfg.temp_dir,startup->temp_dir);
else
SAFECOPY(cfg.temp_dir,"../temp");
prep_dir(cfg.ctrl_dir, cfg.temp_dir, sizeof(cfg.temp_dir));
md(cfg.temp_dir);
if(sd==INVALID_SOCKET) { /* events thread */
if(startup->first_node==1)
SAFEPRINTF(path,"%sevent",cfg.temp_dir);
else
SAFEPRINTF2(path,"%sevent%u",cfg.temp_dir,startup->first_node);
backslash(path);
SAFECOPY(cfg.temp_dir,path);
}
}
lprintf(LOG_DEBUG,"%s temporary file directory: %s", nodestr, 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.store = addr->store;
client_socket = sd;
SAFECOPY(client_name, name);
client_socket_dup=INVALID_SOCKET;
client_ident[0]=0;
telnet_location[0]=0;
terminal[0]=0;
rlogin_name[0]=0;
rlogin_pass[0]=0;
rlogin_term[0]=0;
/* Init some important variables */
input_thread_mutex_created = false;
#ifdef USE_CRYPTLIB
ssh_mode=false;
ssh_mutex_created=false;
passthru_input_thread_running = false;
passthru_output_thread_running = false;
#endif
rio_abortable=false;
console = 0;
online = 0;
outchar_esc = 0;
nodemsg_inside = 0; /* allows single nest */
hotkey_inside = 0; /* allows single nest */
event_time = 0;
event_code = nulstr;
nodesync_inside = false;
errormsg_inside = false;
gettimeleft_inside = false;
readmail_inside = false;
scanposts_inside = false;
scansubs_inside = false;
timeleft = 60*10; /* just incase this is being used for calling gettimeleft() */
uselect_total = 0;
lbuflen = 0;
keybufbot=keybuftop=0; /* initialize [unget]keybuf pointers */
SAFECOPY(connection,"Telnet");
node_connection=NODE_CONNECTION_TELNET;
ZERO_VAR(telnet_local_option);
ZERO_VAR(telnet_remote_option);
telnet_cmdlen=0;
telnet_mode=0;
telnet_last_rxch=0;
telnet_ack_event=CreateEvent(NULL, /* Manual Reset: */FALSE,/* InitialState */FALSE,NULL);
sys_status=lncntr=tos=criterrs=slcnt=0L;
column=0;
curatr=LIGHTGRAY;
attr_sp=0; /* attribute stack pointer */
errorlevel=0;
logcol=1;
logfile_fp=NULL;
nodefile=-1;
pthread_mutex_init(&nodefile_mutex, NULL);
node_ext=-1;
nodefile_fp=NULL;
node_ext_fp=NULL;
current_msg=NULL;
mnestr=NULL;
#ifdef JAVASCRIPT
js_runtime=NULL; /* runtime */
js_cx=NULL; /* context */
#endif
for(i=0;i<TOTAL_TEXT;i++)
text[i]=text_sav[i]=global_text[i];
ZERO_VAR(main_csi);
ZERO_VAR(thisnode);
ZERO_VAR(useron);
ZERO_VAR(inbuf);
ZERO_VAR(outbuf);
ZERO_VAR(smb);
ZERO_VAR(nodesync_user);
action=NODE_MAIN;
global_str_vars=0;
global_str_var=NULL;
global_str_var_name=NULL;
global_int_vars=0;
global_int_var=NULL;
global_int_var_name=NULL;
sysvar_li=0;
sysvar_pi=0;
cursub=NULL;
cursubnum=INVALID_SUB;
usrgrp=NULL;
usrsubs=NULL;
usrsub=NULL;
usrgrp_total=0;
subscan=NULL;
curdir=NULL;
curdirnum=INVALID_SUB;
usrlib=NULL;
usrdirs=NULL;
usrdir=NULL;
usrlib_total=0;
batup_desc=NULL;
batup_name=NULL;
batup_misc=NULL;
batup_dir=NULL;
batup_alt=NULL;
batdn_name=NULL;
batdn_dir=NULL;
batdn_offset=NULL;
batdn_size=NULL;
batdn_alt=NULL;
batdn_cdt=NULL;
/* used by update_qwkroute(): */
qwknode=NULL;
total_qwknodes=0;
spymsg("Connected");
}
//****************************************************************************
bool sbbs_t::init()
{
char str[MAX_PATH+1];
char tmp[128];
int result;
uint i,j,k,l;
node_t node;
socklen_t addr_len;
union xp_sockaddr addr;
RingBufInit(&inbuf, IO_THREAD_BUF_SIZE);
if(cfg.node_num>0)
node_inbuf[cfg.node_num-1]=&inbuf;
RingBufInit(&outbuf, IO_THREAD_BUF_SIZE);
outbuf.highwater_mark=startup->outbuf_highwater_mark;
if(cfg.node_num && client_socket!=INVALID_SOCKET) {
#ifdef _WIN32
if(!DuplicateHandle(GetCurrentProcess(),
(HANDLE)client_socket,
GetCurrentProcess(),
(HANDLE*)&client_socket_dup,
0,
TRUE, // Inheritable
DUPLICATE_SAME_ACCESS)) {
errormsg(WHERE,ERR_CREATE,"duplicate socket handle",client_socket);
return(false);
}
lprintf(LOG_DEBUG,"Node %d socket %u duplicated as %u", cfg.node_num, client_socket, client_socket_dup);
#else
client_socket_dup = client_socket;
#endif
addr_len=sizeof(addr);
if((result=getsockname(client_socket, &addr.addr, &addr_len))!=0) {
lprintf(LOG_ERR,"Node %d !ERROR %d (%d) getting address/port"
,cfg.node_num, result, ERROR_VALUE);
return(false);
}
inet_addrtop(&addr, local_addr, sizeof(local_addr));
inet_addrtop(&client_addr, client_ipaddr, sizeof(client_ipaddr));
lprintf(LOG_INFO,"Node %d attached to local interface %s port %u"
,cfg.node_num, local_addr, inet_addrport(&addr));
}
if((comspec=os_cmdshell())==NULL) {
errormsg(WHERE, ERR_CHK, OS_CMD_SHELL_ENV_VAR" environment variable", 0);
return(false);
}
md(cfg.temp_dir);
/* Shared NODE files */
SAFEPRINTF2(str,"%s%s",cfg.ctrl_dir,"node.dab");
pthread_mutex_lock(&nodefile_mutex);
if((nodefile=nopen(str,O_DENYNONE|O_RDWR|O_CREAT))==-1) {
errormsg(WHERE, ERR_OPEN, str, cfg.node_num);
pthread_mutex_unlock(&nodefile_mutex);
return(false);
}
memset(&node,0,sizeof(node_t)); /* write NULL to node struct */
node.status=NODE_OFFLINE;
while(filelength(nodefile)<(long)(cfg.sys_nodes*sizeof(node_t))) {
lseek(nodefile,0L,SEEK_END);
if(write(nodefile,&node,sizeof(node_t))!=sizeof(node_t)) {
errormsg(WHERE,ERR_WRITE,str,sizeof(node_t));
break;
}
}
for(i=0; cfg.node_num>0 && i<LOOP_NODEDAB; i++) {
if(lock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t))==0) {
unlock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t));
break;
}
mswait(100);
}
if(cfg.node_misc&NM_CLOSENODEDAB) {
close(nodefile);
nodefile=-1;
}
pthread_mutex_unlock(&nodefile_mutex);
if(i>=LOOP_NODEDAB) {
errormsg(WHERE, ERR_LOCK, str, cfg.node_num);
return(false);
}
if(cfg.node_num) {
SAFEPRINTF(str,"%snode.log",cfg.node_dir);
if((logfile_fp=fopen(str,"a+b"))==NULL) {
errormsg(WHERE, ERR_OPEN, str, 0);
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_r(&now,&tm);
safe_snprintf(str,sizeof(str),"%s %s %s %02d %u "
"End of preexisting log entry (possible crash)"
,hhmmtostr(&cfg,&tm,tmp)
,wday[tm.tm_wday]
,mon[tm.tm_mon],tm.tm_mday,tm.tm_year+1900);
logline(LOG_NOTICE,"L!",str);
log(crlf);
catsyslog(1);
}
getnodedat(cfg.node_num,&thisnode,1);
/* thisnode.status=0; */
thisnode.action=0;
thisnode.useron=0;
thisnode.aux=0;
thisnode.misc&=(NODE_EVENT|NODE_LOCK|NODE_RRUN);
criterrs=thisnode.errors;
putnodedat(cfg.node_num,&thisnode);
}
/** Put in if(cfg.node_num) ? (not needed for server and event threads) */
backout();
/* Reset COMMAND SHELL */
main_csi.str=(char *)malloc(1024);
if(main_csi.str==NULL) {
errormsg(WHERE,ERR_ALLOC,"main_csi.str",1024);
return(false);
}
memset(main_csi.str,0,1024);
/***/
if(cfg.total_grps) {
usrgrp_total = cfg.total_grps;
if((cursub=(uint *)malloc(sizeof(uint)*usrgrp_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "cursub", sizeof(uint)*usrgrp_total);
return(false);
}
if((usrgrp=(uint *)malloc(sizeof(uint)*usrgrp_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrgrp", sizeof(uint)*usrgrp_total);
return(false);
}
if((usrsubs=(uint *)malloc(sizeof(uint)*usrgrp_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrsubs", sizeof(uint)*usrgrp_total);
return(false);
}
if((usrsub=(uint **)calloc(usrgrp_total,sizeof(uint *)))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrsub", sizeof(uint)*usrgrp_total);
return(false);
}
if((subscan=(subscan_t *)malloc(sizeof(subscan_t)*cfg.total_subs))==NULL) {
errormsg(WHERE, ERR_ALLOC, "subscan", sizeof(subscan_t)*cfg.total_subs);
return(false);
}
}
for(i=l=0;i<(uint)cfg.total_grps;i++) {
for(j=k=0;j<cfg.total_subs;j++)
if(cfg.sub[j]->grp==i)
k++; /* k = number of subs per grp[i] */
if(k>l) l=k; /* l = the largest number of subs per grp */
}
if(l)
for(i=0;i<cfg.total_grps;i++)
if((usrsub[i]=(uint *)malloc(sizeof(uint)*l))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrsub[x]", sizeof(uint)*l);
return(false);
}
if(cfg.total_libs) {
usrlib_total = cfg.total_libs;
if((curdir=(uint *)malloc(sizeof(uint)*usrlib_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "curdir", sizeof(uint)*usrlib_total);
return(false);
}
if((usrlib=(uint *)malloc(sizeof(uint)*usrlib_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrlib", sizeof(uint)*usrlib_total);
return(false);
}
if((usrdirs=(uint *)malloc(sizeof(uint)*usrlib_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrdirs", sizeof(uint)*usrlib_total);
return(false);
}
if((usrdir=(uint **)calloc(usrlib_total,sizeof(uint *)))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrdir", sizeof(uint)*usrlib_total);
return(false);
}
}
for(i=l=0;i<cfg.total_libs;i++) {
for(j=k=0;j<cfg.total_dirs;j++)
if(cfg.dir[j]->lib==i)
k++;
if(k>l) l=k; /* l = largest number of dirs in a lib */
}
if(l) {
l++; /* for temp dir */
for(i=0;i<cfg.total_libs;i++)
if((usrdir[i]=(uint *)malloc(sizeof(uint)*l))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrdir[x]", sizeof(uint)*l);
return(false);
}
}
if(cfg.max_batup) {
if((batup_desc=(char **)malloc(sizeof(char *)*cfg.max_batup))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batup_desc", sizeof(char *)*cfg.max_batup);
return(false);
}
if((batup_name=(char **)malloc(sizeof(char *)*cfg.max_batup))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batup_name", sizeof(char *)*cfg.max_batup);
return(false);
}
if((batup_misc=(long *)malloc(sizeof(long)*cfg.max_batup))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batup_misc", sizeof(char *)*cfg.max_batup);
return(false);
}
if((batup_dir=(uint *)malloc(sizeof(uint)*cfg.max_batup))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batup_dir", sizeof(char *)*cfg.max_batup);
return(false);
}
if((batup_alt=(ushort *)malloc(sizeof(ushort)*cfg.max_batup))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batup_alt", sizeof(char *)*cfg.max_batup);
return(false);
}
for(i=0;i<cfg.max_batup;i++) {
if((batup_desc[i]=(char *)malloc(LEN_FDESC+1))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batup_desc[x]", LEN_FDESC+1);
return(false);
}
if((batup_name[i]=(char *)malloc(13))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batup_name[x]", 13);
return(false);
}
}
}
if(cfg.max_batdn) {
if((batdn_name=(char **)malloc(sizeof(char *)*cfg.max_batdn))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batdn_name", sizeof(char *)*cfg.max_batdn);
return(false);
}
if((batdn_dir=(uint *)malloc(sizeof(uint)*cfg.max_batdn))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batdn_dir", sizeof(uint)*cfg.max_batdn);
return(false);
}
if((batdn_offset=(long *)malloc(sizeof(long)*cfg.max_batdn))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batdn_offset", sizeof(long)*cfg.max_batdn);
return(false);
}
if((batdn_size=(ulong *)malloc(sizeof(ulong)*cfg.max_batdn))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batdn_size", sizeof(ulong)*cfg.max_batdn);
return(false);
}
if((batdn_cdt=(ulong *)malloc(sizeof(ulong)*cfg.max_batdn))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batdn_cdt", sizeof(long)*cfg.max_batdn);
return(false);
}
if((batdn_alt=(ushort *)malloc(sizeof(ushort)*cfg.max_batdn))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batdn_alt", sizeof(ushort)*cfg.max_batdn);
return(false);
}
for(i=0;i<cfg.max_batdn;i++)
if((batdn_name[i]=(char *)malloc(13))==NULL) {
errormsg(WHERE, ERR_ALLOC, "batdn_name[x]", 13);
return(false);
}
}
#ifdef USE_CRYPTLIB
pthread_mutex_init(&ssh_mutex,NULL);
ssh_mutex_created = true;
#endif
pthread_mutex_init(&input_thread_mutex,NULL);
input_thread_mutex_created = true;
reset_logon_vars();
online=ON_REMOTE;
return(true);
}
//****************************************************************************
sbbs_t::~sbbs_t()
{
uint i;
char node[32];
if(cfg.node_num)
SAFEPRINTF(node,"Node %d", cfg.node_num);
else
SAFECOPY(node,client_name);
#ifdef _DEBUG
lprintf(LOG_DEBUG,"%s destructor begin", node);
#endif
// if(!cfg.node_num)
// rmdir(cfg.temp_dir);
if(client_socket_dup!=INVALID_SOCKET && client_socket_dup!=client_socket)
closesocket(client_socket_dup); /* close duplicate handle */
if(cfg.node_num>0)
node_inbuf[cfg.node_num-1]=NULL;
if(!input_thread_running)
RingBufDispose(&inbuf);
if(!output_thread_running)
RingBufDispose(&outbuf);
if(telnet_ack_event!=NULL)
CloseEvent(telnet_ack_event);
/* Close all open files */
pthread_mutex_lock(&nodefile_mutex);
if(nodefile!=-1) {
close(nodefile);
nodefile=-1;
pthread_mutex_unlock(&nodefile_mutex);
}
if(node_ext!=-1) {
close(node_ext);
node_ext=-1;
}
if(logfile_fp!=NULL) {
fclose(logfile_fp);
logfile_fp=NULL;
}
/********************************/
/* Free allocated class members */
/********************************/
js_cleanup(node);
/* Reset text.dat */
for(i=0;i<TOTAL_TEXT;i++)
if(text[i]!=text_sav[i]) {
if(text[i]!=nulstr)
free(text[i]);
}
/* Global command shell vars */
freevars(&main_csi);
clearvars(&main_csi);
FREE_AND_NULL(main_csi.str); /* crash */
FREE_AND_NULL(main_csi.cs);
for(i=0;i<global_str_vars && global_str_var!=NULL;i++)
FREE_AND_NULL(global_str_var[i]);
FREE_AND_NULL(global_str_var);
FREE_AND_NULL(global_str_var_name);
global_str_vars=0;
FREE_AND_NULL(global_int_var);
FREE_AND_NULL(global_int_var_name);
global_int_vars=0;
/* Sub-board variables */
for(i=0;i<usrgrp_total && usrsub!=NULL;i++)
FREE_AND_NULL(usrsub[i]); /* exception here (ptr=0xfdfdfdfd) on exit July-10-2002 */
FREE_AND_NULL(cursub);
FREE_AND_NULL(usrgrp);
FREE_AND_NULL(usrsubs);
FREE_AND_NULL(usrsub);
FREE_AND_NULL(subscan);
/* File Directory variables */
for(i=0;i<usrlib_total && usrdir!=NULL;i++)
FREE_AND_NULL(usrdir[i]);
FREE_AND_NULL(curdir);
FREE_AND_NULL(usrlib);
FREE_AND_NULL(usrdirs);
FREE_AND_NULL(usrdir);
/* Batch upload vars */
for(i=0;i<cfg.max_batup && batup_desc!=NULL && batup_name!=NULL;i++) {
FREE_AND_NULL(batup_desc[i]);
FREE_AND_NULL(batup_name[i]);
}
FREE_AND_NULL(batup_desc);
FREE_AND_NULL(batup_name);
FREE_AND_NULL(batup_misc);
FREE_AND_NULL(batup_dir);
FREE_AND_NULL(batup_alt);
/* Batch download vars */
for(i=0;i<cfg.max_batdn && batdn_name!=NULL;i++)
FREE_AND_NULL(batdn_name[i]);
FREE_AND_NULL(batdn_name);
FREE_AND_NULL(batdn_dir);
FREE_AND_NULL(batdn_offset);
FREE_AND_NULL(batdn_size);
FREE_AND_NULL(batdn_cdt);
FREE_AND_NULL(batdn_alt);
#ifdef USE_CRYPTLIB
while(ssh_mutex_created && pthread_mutex_destroy(&ssh_mutex)==EBUSY)
mswait(1);
#endif
while(input_thread_mutex_created && pthread_mutex_destroy(&input_thread_mutex)==EBUSY)
mswait(1);
#if 0 && defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
if(!_CrtCheckMemory())
lprintf(LOG_ERR,"!MEMORY ERRORS REPORTED IN DATA/DEBUG.LOG!");
#endif
#ifdef _DEBUG
lprintf(LOG_DEBUG,"%s destructor end", node);
#endif
}
/****************************************************************************/
/* Network open function. Opens all files DENYALL and retries LOOP_NOPEN */
/* number of times if the attempted file is already open or denying access */
/* for some other reason. */
/* All files are opened in BINARY mode, unless O_TEXT access bit is set. */
/****************************************************************************/
int sbbs_t::nopen(char *str, int access)
{
char logstr[256];
int file,share,count=0;
if(access&O_DENYNONE) {
share=SH_DENYNO;
access&=~O_DENYNONE;
}
else if(access==O_RDONLY) share=SH_DENYWR;
else share=SH_DENYRW;
if(!(access&O_TEXT))
access|=O_BINARY;
while(((file=sopen(str,access,share,DEFFILEMODE))==-1)
&& (errno==EACCES || errno==EAGAIN) && count++<LOOP_NOPEN)
mswait(100);
if(count>(LOOP_NOPEN/2) && count<=LOOP_NOPEN) {
SAFEPRINTF2(logstr,"NOPEN COLLISION - File: \"%s\" Count: %d"
,str,count);
logline(LOG_WARNING,"!!",logstr);
}
if(file==-1 && (errno==EACCES || errno==EAGAIN)) {
SAFEPRINTF2(logstr,"NOPEN ACCESS DENIED - File: \"%s\" errno: %d"
,str,errno);
logline(LOG_WARNING,"!!",logstr);
bputs("\7\r\nNOPEN: ACCESS DENIED\r\n\7");
}
return(file);
}
void sbbs_t::spymsg(const char* msg)
{
char str[512];
if(cfg.node_num<1)
return;
SAFEPRINTF4(str,"\r\n\r\n*** Spy Message ***\r\nNode %d: %s [%s]\r\n*** %s ***\r\n\r\n"
,cfg.node_num,client_name,client_ipaddr,msg);
if(startup->node_spybuf!=NULL
&& startup->node_spybuf[cfg.node_num-1]!=NULL) {
RingBufWrite(startup->node_spybuf[cfg.node_num-1],(uchar*)str,strlen(str));
/* Signal spy output semaphore? */
if(startup->node_spysem!=NULL
&& startup->node_spysem[sbbs->cfg.node_num-1]!=NULL)
sem_post(startup->node_spysem[sbbs->cfg.node_num-1]);
}
if(cfg.node_num && spy_socket[cfg.node_num-1]!=INVALID_SOCKET)
sendsocket(spy_socket[cfg.node_num-1],str,strlen(str));
#ifdef __unix__
if(cfg.node_num && uspy_socket[cfg.node_num-1]!=INVALID_SOCKET)
sendsocket(uspy_socket[cfg.node_num-1],str,strlen(str));
#endif
}
#define MV_BUFLEN 4096
/****************************************************************************/
/* Moves or copies a file from one dir to another */
/* both 'src' and 'dest' must contain full path and filename */
/* returns 0 if successful, -1 if error */
/****************************************************************************/
int sbbs_t::mv(char *src, char *dest, char copy)
{
char str[MAX_PATH+1],*buf,atr=curatr;
int ind,outd;
uint chunk=MV_BUFLEN;
ulong length,l;
time_t ftime;
FILE *inp,*outp;
if(!stricmp(src,dest)) /* source and destination are the same! */
return(0);
if(!fexistcase(src)) {
bprintf("\r\n\7MV ERROR: Source doesn't exist\r\n'%s'\r\n"
,src);
return(-1);
}
if(!copy && fexistcase(dest)) {
bprintf("\r\n\7MV ERROR: Destination already exists\r\n'%s'\r\n"
,dest);
return(-1);
}
#ifndef __unix__ /* need to determine if on same mount device */
if(!copy && ((src[1]!=':' && dest[1]!=':')
|| (src[1]==':' && dest[1]==':' && toupper(src[0])==toupper(dest[0])))) {
if(rename(src,dest)) { /* same drive, so move */
bprintf("\r\nMV ERROR: Error renaming '%s'"
"\r\n to '%s'\r\n\7",src,dest);
return(-1);
}
return(0);
}
#endif
attr(WHITE);
if((ind=nopen(src,O_RDONLY))==-1) {
errormsg(WHERE,ERR_OPEN,src,O_RDONLY);
return(-1);
}
if((inp=fdopen(ind,"rb"))==NULL) {
close(ind);
errormsg(WHERE,ERR_FDOPEN,str,O_RDONLY);
return(-1);
}
setvbuf(inp,NULL,_IOFBF,32*1024);
if((outd=nopen(dest,O_WRONLY|O_CREAT|O_TRUNC))==-1) {
fclose(inp);
errormsg(WHERE,ERR_OPEN,dest,O_WRONLY|O_CREAT|O_TRUNC);
return(-1);
}
if((outp=fdopen(outd,"wb"))==NULL) {
close(outd);
fclose(inp);
errormsg(WHERE,ERR_FDOPEN,dest,O_WRONLY|O_CREAT|O_TRUNC);
return(-1);
}
setvbuf(outp,NULL,_IOFBF,8*1024);
ftime=filetime(ind);
length=(long)filelength(ind);
if(length) { /* Something to copy */
if((buf=(char *)malloc(MV_BUFLEN))==NULL) {
fclose(inp);
fclose(outp);
errormsg(WHERE,ERR_ALLOC,nulstr,MV_BUFLEN);
return(-1);
}
l=0L;
while(l<length) {
bprintf("%2lu%%",l ? (long)(100.0/((float)length/l)) : 0L);
if(l+chunk>length)
chunk=length-l;
if(fread(buf,1,chunk,inp)!=chunk) {
free(buf);
fclose(inp);
fclose(outp);
errormsg(WHERE,ERR_READ,src,chunk);
return(-1);
}
if(fwrite(buf,1,chunk,outp)!=chunk) {
free(buf);
fclose(inp);
fclose(outp);
errormsg(WHERE,ERR_WRITE,dest,chunk);
return(-1);
}
l+=chunk;
bputs("\b\b\b");
}
bputs(" \b\b\b"); /* erase it */
attr(atr);
free(buf);
}
fclose(inp);
fclose(outp);
setfdate(dest,ftime); /* Would be nice if we could use futime() instead */
if(!copy && remove(src)) {
errormsg(WHERE,ERR_REMOVE,src,0);
return(-1);
}
return(0);
}
void sbbs_t::hangup(void)
{
if(online) {
lprintf(LOG_DEBUG,"Node %d disconnecting client", cfg.node_num);
online=FALSE; // moved from the bottom of this function on Jan-25-2009
}
if(client_socket_dup!=INVALID_SOCKET && client_socket_dup!=client_socket)
closesocket(client_socket_dup);
client_socket_dup=INVALID_SOCKET;
if(client_socket!=INVALID_SOCKET) {
mswait(1000); /* Give socket output buffer time to flush */
client_off(client_socket);
if(ssh_mode) {
pthread_mutex_lock(&sbbs->ssh_mutex);
cryptDestroySession(sbbs->ssh_session);
pthread_mutex_unlock(&sbbs->ssh_mutex);
}
close_socket(client_socket);
client_socket=INVALID_SOCKET;
}
sem_post(&outbuf.sem);
}
int sbbs_t::incom(unsigned long timeout)
{
uchar ch;
#if 0 /* looping version */
while(!RingBufRead(&inbuf, &ch, 1))
if(sem_trywait_block(&inbuf.sem,timeout)!=0 || sys_status&SS_ABORT)
return(NOINP);
#else
if(!RingBufRead(&inbuf, &ch, 1)) {
if(sem_trywait_block(&inbuf.sem,timeout)!=0)
return(NOINP);
if(!RingBufRead(&inbuf, &ch, 1))
return(NOINP);
}
#endif
return(ch);
}
int sbbs_t::outcom(uchar ch)
{
if(!RingBufFree(&outbuf))
return(TXBOF);
if(!RingBufWrite(&outbuf, &ch, 1))
return(TXBOF);
return(0);
}
int sbbs_t::putcom(const char *str, size_t len)
{
size_t i;
if(!len)
len=strlen(str);
for(i=0;i<len && online;i++)
if(outcom(str[i])!=0)
break;
return i;
}
/* Legacy Remote I/O Control Interface:
* This function mimics the RCIOL MS-DOS library written in 8086 assembler by Steven B. Deppe (1958-2014).
* This function prototype shall remain the same in tribute to Steve (Ille Homine Albe).
*/
int sbbs_t::rioctl(ushort action)
{
int mode;
int state;
switch(action) {
case GVERS: /* Get version */
return(0x200);
case GUART: /* Get UART I/O address, not available */
return(0xffff);
case GIRQN: /* Get IRQ number, not available */
return((int)client_socket);
case GBAUD: /* Get current bit rate */
return(0xffff);
case RXBC: /* Get receive buffer count */
// ulong cnt;
// ioctlsocket (client_socket,FIONREAD,&cnt);
return(/* cnt+ */RingBufFull(&inbuf));
case RXBS: /* Get receive buffer size */
return(inbuf.size);
case TXBC: /* Get transmit buffer count */
return(RingBufFull(&outbuf));
case TXBS: /* Get transmit buffer size */
return(outbuf.size);
case TXBF: /* Get transmit buffer free space */
return(RingBufFree(&outbuf));
case IOMODE:
mode=0;
if(rio_abortable)
mode|=ABORT;
return(mode);
case IOSTATE:
state=0;
if(sys_status&SS_ABORT)
state|=ABORT;
return(state);
case IOFI: /* Flush input buffer */
RingBufReInit(&inbuf);
break;
case IOFO: /* Flush output buffer */
RingBufReInit(&outbuf);
break;
case IOFB: /* Flush both buffers */
RingBufReInit(&inbuf);
RingBufReInit(&outbuf);
break;
case LFN81:
case LFE71:
case FIFOCTL:
return(0);
}
if((action&0xff)==IOSM) { /* Get/Set/Clear mode */
if(action&ABORT)
rio_abortable=true;
return(0);
}
if((action&0xff)==IOCM) { /* Get/Set/Clear mode */
if(action&ABORT)
rio_abortable=false;
return(0);
}
if((action&0xff)==IOSS) { /* Set state */
if(action&ABORT)
sys_status|=SS_ABORT;
return(0);
}
if((action&0xff)==IOCS) { /* Clear state */
if(action&ABORT)
sys_status&=~SS_ABORT;
return(0);
}
return(0);
}
void sbbs_t::reset_logon_vars(void)
{
int i;
/* bools */
qwklogon=false;
sys_status&=~(SS_USERON|SS_TMPSYSOP|SS_LCHAT|SS_ABORT
|SS_PAUSEON|SS_PAUSEOFF|SS_EVENT|SS_NEWUSER|SS_NEWDAY);
cid[0]=0;
wordwrap[0]=0;
question[0]=0;
menu_dir[0]=0;
menu_file[0]=0;
rows=24;
cols=80;
lncntr=0;
autoterm=0;
lbuflen=0;
slcnt=0;
altul=0;
timeleft_warn=0;
keybufbot=keybuftop=0;
logon_uls=logon_ulb=logon_dls=logon_dlb=0;
logon_posts=logon_emails=logon_fbacks=0;
batdn_total=batup_total=0;
usrgrps=usrlibs=0;
curgrp=curlib=0;
for(i=0;i<cfg.total_libs;i++)
curdir[i]=0;
for(i=0;i<cfg.total_grps;i++)
cursub[i]=0;
cur_cps=3000;
cur_rate=30000;
dte_rate=38400;
main_cmds=xfer_cmds=posts_read=0;
lastnodemsg=0;
lastnodemsguser[0]=0;
}
/****************************************************************************/
/* Writes NODE.LOG at end of SYSTEM.LOG */
/****************************************************************************/
void sbbs_t::catsyslog(int crash)
{
char str[MAX_PATH+1];
char *buf;
int i,file;
long length;
struct tm tm;
if(logfile_fp==NULL) {
SAFEPRINTF(str,"%snode.log",cfg.node_dir);
if((logfile_fp=fopen(str,"rb"))==NULL) {
errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
return;
}
}
length=(long)ftell(logfile_fp);
if(length) {
if((buf=(char *)malloc(length))==NULL) {
errormsg(WHERE,ERR_ALLOC,str,length);
return;
}
rewind(logfile_fp);
if(fread(buf,1,length,logfile_fp)!=(size_t)length) {
errormsg(WHERE,ERR_READ,"log file",length);
free((char *)buf);
return;
}
now=time(NULL);
localtime_r(&now,&tm);
SAFEPRINTF4(str,"%slogs/%2.2d%2.2d%2.2d.log",cfg.logs_dir,tm.tm_mon+1,tm.tm_mday
,TM_YEAR(tm.tm_year));
if((file=nopen(str,O_WRONLY|O_APPEND|O_CREAT))==-1) {
errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT);
free((char *)buf);
return;
}
if(lwrite(file,buf,length)!=length) {
close(file);
errormsg(WHERE,ERR_WRITE,str,length);
free((char *)buf);
return;
}
close(file);
if(crash) {
for(i=0;i<2;i++) {
SAFEPRINTF(str,"%scrash.log",i ? cfg.logs_dir : cfg.node_dir);
if((file=nopen(str,O_WRONLY|O_APPEND|O_CREAT))==-1) {
errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT);
free((char *)buf);
return;
}
if(lwrite(file,buf,length)!=length) {
close(file);
errormsg(WHERE,ERR_WRITE,str,length);
free((char *)buf);
return;
}
close(file);
}
}
free((char *)buf);
}
fclose(logfile_fp);
SAFEPRINTF(str,"%snode.log",cfg.node_dir);
if((logfile_fp=fopen(str,"w+b"))==NULL) /* Truncate NODE.LOG */
errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_TRUNC);
}
void sbbs_t::logoffstats()
{
char str[MAX_PATH+1];
int i,file;
stats_t stats;
if(REALSYSOP && !(cfg.sys_misc&SM_SYSSTAT))
return;
for(i=0;i<2;i++) {
SAFEPRINTF(str,"%sdsts.dab",i ? cfg.ctrl_dir : cfg.node_dir);
if((file=nopen(str,O_RDWR))==-1) {
errormsg(WHERE,ERR_OPEN,str,O_RDWR);
return;
}
memset(&stats,0,sizeof(stats));
lseek(file,4L,SEEK_SET); /* Skip timestamp, logons and logons today */
read(file,&stats,sizeof(stats));
if(!(useron.rest&FLAG('Q'))) { /* Don't count QWKnet nodes */
stats.timeon+=(uint32_t)(now-logontime)/60;
stats.ttoday+=(uint32_t)(now-logontime)/60;
stats.ptoday+=logon_posts;
}
stats.uls+=logon_uls;
stats.ulb+=logon_ulb;
stats.dls+=logon_dls;
stats.dlb+=logon_dlb;
stats.etoday+=logon_emails;
stats.ftoday+=logon_fbacks;
#if 0 // This is now handled in newuserdat()
if(sys_status&SS_NEWUSER)
stats.nusers++;
#endif
lseek(file,4L,SEEK_SET);
write(file,&stats,sizeof(stats));
close(file);
}
}
void node_thread(void* arg)
{
ulong stack_frame;
char str[128];
int file;
uint curshell=0;
node_t node;
ulong login_attempts;
sbbs_t* sbbs = (sbbs_t*) arg;
update_clients();
SetThreadName("sbbs/termNode");
thread_up(TRUE /* setuid */);
#ifdef _DEBUG
lprintf(LOG_DEBUG,"Node %d thread started",sbbs->cfg.node_num);
#endif
sbbs_srand(); /* Seed random number generator */
#ifdef JAVASCRIPT
if(!(startup->options&BBS_OPT_NO_JAVASCRIPT)) {
if(!sbbs->js_init(&stack_frame)) /* 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(startup->login_attempt.throttle
&& (login_attempts=loginAttempts(startup->login_attempt_list, &sbbs->client_addr)) > 1) {
lprintf(LOG_DEBUG,"Node %d Throttling suspicious connection from: %s (%u login attempts)"
,sbbs->cfg.node_num, sbbs->client_ipaddr, login_attempts);
mswait(login_attempts*startup->login_attempt.throttle);
}
if(sbbs->answer()) {
if(sbbs->qwklogon) {
sbbs->getsmsg(sbbs->useron.number);
sbbs->qwk_sec();
} else while(sbbs->useron.number
&& (sbbs->main_csi.misc&CS_OFFLINE_EXEC || sbbs->online)) {
if(!sbbs->main_csi.cs || curshell!=sbbs->useron.shell) {
if(sbbs->useron.shell>=sbbs->cfg.total_shells)
sbbs->useron.shell=0;
SAFEPRINTF2(str,"%s%s.bin",sbbs->cfg.mods_dir
,sbbs->cfg.shell[sbbs->useron.shell]->code);
if(sbbs->cfg.mods_dir[0]==0 || !fexistcase(str))
SAFEPRINTF2(str,"%s%s.bin",sbbs->cfg.exec_dir
,sbbs->cfg.shell[sbbs->useron.shell]->code);
if((file=sbbs->nopen(str,O_RDONLY))==-1) {
sbbs->errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
sbbs->hangup();
break;
}
FREE_AND_NULL(sbbs->main_csi.cs);
sbbs->freevars(&sbbs->main_csi);
sbbs->clearvars(&sbbs->main_csi);
sbbs->main_csi.length=(long)filelength(file);
if((sbbs->main_csi.cs=(uchar *)malloc(sbbs->main_csi.length))==NULL) {
close(file);
sbbs->errormsg(WHERE,ERR_ALLOC,str,sbbs->main_csi.length);
sbbs->hangup();
break;
}
if(lread(file,sbbs->main_csi.cs,sbbs->main_csi.length)
!=(int)sbbs->main_csi.length) {
sbbs->errormsg(WHERE,ERR_READ,str,sbbs->main_csi.length);
close(file);
free(sbbs->main_csi.cs);
sbbs->main_csi.cs=NULL;
sbbs->hangup();
break;
}
close(file);
curshell=sbbs->useron.shell;
sbbs->main_csi.ip=sbbs->main_csi.cs;
sbbs->menu_dir[0]=0;
sbbs->menu_file[0]=0;
}
if(sbbs->exec(&sbbs->main_csi))
break;
}
}
#ifdef _WIN32
if(startup->hangup_sound[0] && !(startup->options&BBS_OPT_MUTE))
PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif
sbbs->hangup(); /* closes sockets, calls client_off, and shuts down the output_thread */
node_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
sbbs->logout();
sbbs->logoffstats(); /* Updates both system and node dsts.dab files */
if(sbbs->sys_status&SS_DAILY) { // New day, run daily events/maintenance
sbbs->daily_maint();
}
#if 0 /* this is handled in the event_thread now */
// Node Daily Event
sbbs->getnodedat(sbbs->cfg.node_num,&node,0);
if(node.misc&NODE_EVENT) {
sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
node.status=NODE_EVENT_RUNNING;
sbbs->putnodedat(sbbs->cfg.node_num,&node);
if(sbbs->cfg.node_daily[0]) {
sbbs->logentry("!:","Run node daily event");
sbbs->external(
sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL)
,EX_OFFLINE);
}
sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
node.misc&=~NODE_EVENT;
sbbs->putnodedat(sbbs->cfg.node_num,&node);
}
#endif
// Wait for all node threads to terminate
if(sbbs->input_thread_running || sbbs->output_thread_running
#ifdef USE_CRYPTLIB
|| sbbs->passthru_input_thread_running || sbbs->passthru_output_thread_running
#endif
) {
lprintf(LOG_DEBUG,"Node %d Waiting for %s to terminate..."
,sbbs->cfg.node_num
,(sbbs->input_thread_running && sbbs->output_thread_running) ?
"I/O threads" : sbbs->input_thread_running
? "input thread" : "output thread");
time_t start=time(NULL);
while(sbbs->input_thread_running
|| sbbs->output_thread_running
#ifdef USE_CRYPTLIB
|| sbbs->passthru_input_thread_running || sbbs->passthru_output_thread_running
#endif
) {
if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
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
? "input thread" : "output thread");
break;
}
mswait(100);
}
}
sbbs->catsyslog(0);
status(STATUS_WFC);
sbbs->getnodedat(sbbs->cfg.node_num,&node,1);
if(node.misc&NODE_DOWN)
node.status=NODE_OFFLINE;
else
node.status=NODE_WFC;
node.misc&=~(NODE_DOWN|NODE_INTR|NODE_MSGW|NODE_NMSG
|NODE_UDAT|NODE_POFF|NODE_AOFF|NODE_EXT);
/* node.useron=0; needed for hang-ups while in multinode chat */
sbbs->putnodedat(sbbs->cfg.node_num,&node);
{
/* crash here on Aug-4-2015:
node_thread_running already destroyed
bbs_thread() timed out waiting for 1 node thread(s) to terminate */
int32_t remain = protected_uint32_adjust(&node_threads_running, -1);
lprintf(LOG_INFO,"Node %d thread terminated (%u node threads remain, %lu clients served)"
,sbbs->cfg.node_num, remain, served);
}
if(!sbbs->input_thread_running && !sbbs->output_thread_running)
delete sbbs;
else
lprintf(LOG_WARNING,"Node %d !ORPHANED I/O THREAD(s)",sbbs->cfg.node_num);
update_clients();
thread_down();
}
void sbbs_t::daily_maint(void)
{
char str[128];
char uname[LEN_ALIAS+1];
uint i;
uint usernum;
uint lastusernum;
user_t user;
now=time(NULL);
if(sbbs->cfg.node_num) {
if((i=sbbs->getnodedat(sbbs->cfg.node_num,&sbbs->thisnode,true)) != 0)
sbbs->errormsg(WHERE,ERR_LOCK,"node file",i);
else {
sbbs->thisnode.status=NODE_EVENT_RUNNING;
sbbs->putnodedat(sbbs->cfg.node_num,&sbbs->thisnode);
}
}
sbbs->logentry("!:","Ran system daily maintenance");
if(sbbs->cfg.user_backup_level) {
lputs(LOG_INFO,"Backing-up user data...");
SAFEPRINTF(str,"%suser/user.dat",sbbs->cfg.data_dir);
backup(str,sbbs->cfg.user_backup_level,FALSE);
SAFEPRINTF(str,"%suser/name.dat",sbbs->cfg.data_dir);
backup(str,sbbs->cfg.user_backup_level,FALSE);
}
if(sbbs->cfg.mail_backup_level) {
lputs(LOG_INFO,"Backing-up mail data...");
SAFEPRINTF(str,"%smail.shd",sbbs->cfg.data_dir);
backup(str,sbbs->cfg.mail_backup_level,FALSE);
SAFEPRINTF(str,"%smail.sha",sbbs->cfg.data_dir);
backup(str,sbbs->cfg.mail_backup_level,FALSE);
SAFEPRINTF(str,"%smail.sdt",sbbs->cfg.data_dir);
backup(str,sbbs->cfg.mail_backup_level,FALSE);
SAFEPRINTF(str,"%smail.sda",sbbs->cfg.data_dir);
backup(str,sbbs->cfg.mail_backup_level,FALSE);
SAFEPRINTF(str,"%smail.sid",sbbs->cfg.data_dir);
backup(str,sbbs->cfg.mail_backup_level,FALSE);
SAFEPRINTF(str,"%smail.sch",sbbs->cfg.data_dir);
backup(str,sbbs->cfg.mail_backup_level,FALSE);
}
lputs(LOG_INFO,status("Checking for inactive/expired user records..."));
lastusernum=lastuser(&sbbs->cfg);
int userfile=openuserdat(&sbbs->cfg, /* for_modify: */FALSE);
for(usernum=1;usernum<=lastusernum;usernum++) {
SAFEPRINTF2(str,"%5u of %-5u",usernum,lastusernum);
status(str);
user.number = usernum;
if((i=fgetuserdat(&sbbs->cfg, &user, userfile)) != 0) {
SAFEPRINTF(str,"user record %u",usernum);
sbbs->errormsg(WHERE, ERR_READ, str, i);
continue;
}
/***********************************************/
/* Fix name (name.dat and user.dat) mismatches */
/***********************************************/
username(&sbbs->cfg,user.number,uname);
if(user.misc&DELETED) {
if(strcmp(uname,"DELETED USER"))
putusername(&sbbs->cfg,user.number,nulstr);
continue;
}
if(strcmp(user.alias,uname))
putusername(&sbbs->cfg,user.number,user.alias);
if(user.number==1)
continue; /* skip expiration/inactivity checks for user #1 */
if(!(user.misc&(DELETED|INACTIVE))
&& user.expire && (ulong)user.expire<=(ulong)now) {
putsmsg(&sbbs->cfg,user.number,sbbs->text[AccountHasExpired]);
SAFEPRINTF2(str,"%s #%u Expired",user.alias,user.number);
sbbs->logentry("!%",str);
if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOVAL
&& sbbs->cfg.level_expireto[user.level]<10) {
user.flags1=sbbs->cfg.val_flags1[sbbs->cfg.level_expireto[user.level]];
user.flags2=sbbs->cfg.val_flags2[sbbs->cfg.level_expireto[user.level]];
user.flags3=sbbs->cfg.val_flags3[sbbs->cfg.level_expireto[user.level]];
user.flags4=sbbs->cfg.val_flags4[sbbs->cfg.level_expireto[user.level]];
user.exempt=sbbs->cfg.val_exempt[sbbs->cfg.level_expireto[user.level]];
user.rest=sbbs->cfg.val_rest[sbbs->cfg.level_expireto[user.level]];
if(sbbs->cfg.val_expire[sbbs->cfg.level_expireto[user.level]])
user.expire=(time32_t)now
+(sbbs->cfg.val_expire[sbbs->cfg.level_expireto[user.level]]*24*60*60);
else
user.expire=0;
user.level=sbbs->cfg.val_level[sbbs->cfg.level_expireto[user.level]];
}
else {
if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOLVL)
user.level=sbbs->cfg.level_expireto[user.level];
else
user.level=sbbs->cfg.expired_level;
user.flags1&=~sbbs->cfg.expired_flags1; /* expired status */
user.flags2&=~sbbs->cfg.expired_flags2; /* expired status */
user.flags3&=~sbbs->cfg.expired_flags3; /* expired status */
user.flags4&=~sbbs->cfg.expired_flags4; /* expired status */
user.exempt&=~sbbs->cfg.expired_exempt;
user.rest|=sbbs->cfg.expired_rest;
user.expire=0;
}
putuserrec(&sbbs->cfg,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((ulong)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=FALSE;
}
}
/***********************************************************/
/* Auto deletion based on expiration date or days inactive */
/***********************************************************/
if(!(user.exempt&FLAG('P')) /* Not a permanent account */
&& !(user.misc&(DELETED|INACTIVE)) /* alive */
&& (sbbs->cfg.sys_autodel && (now-user.laston)/(long)(24L*60L*60L)
> sbbs->cfg.sys_autodel)) { /* Inactive too long */
SAFEPRINTF2(str,"Auto-Deleted %s #%u",user.alias,user.number);
sbbs->logentry("!*",str);
sbbs->delallmail(user.number, MAIL_ANY);
putusername(&sbbs->cfg,user.number,nulstr);
putuserrec(&sbbs->cfg,user.number,U_MISC,8,ultoa(user.misc|DELETED,str,16));
}
}
close(userfile);
lputs(LOG_INFO,status("Purging deleted/expired e-mail"));
SAFEPRINTF(sbbs->smb.file,"%smail",sbbs->cfg.data_dir);
sbbs->smb.retry_time=sbbs->cfg.smb_retry_time;
sbbs->smb.subnum=INVALID_SUB;
if((i=smb_open(&sbbs->smb))!=0)
sbbs->errormsg(WHERE,ERR_OPEN,sbbs->smb.file,i,sbbs->smb.last_error);
else {
if(filelength(fileno(sbbs->smb.shd_fp))>0) {
if((i=smb_locksmbhdr(&sbbs->smb))!=0)
sbbs->errormsg(WHERE,ERR_LOCK,sbbs->smb.file,i,sbbs->smb.last_error);
else
sbbs->delmail(0,MAIL_ALL);
}
smb_close(&sbbs->smb);
}
sbbs->sys_status&=~SS_DAILY;
if(sbbs->cfg.sys_daily[0]) {
// status("Running system daily event");
sbbs->logentry("!:","Ran system daily event");
sbbs->external(sbbs->cmdstr(sbbs->cfg.sys_daily,nulstr,nulstr,NULL)
,EX_OFFLINE);
}
status(STATUS_WFC);
}
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];
if(ver[0]==0) { /* uninitialized */
DESCRIBE_COMPILER(compiler);
safe_snprintf(ver,sizeof(ver),"%s %s%c%s SMBLIB %s Compiled %s %s with %s"
,TELNET_SERVER
,VERSION, REVISION
#ifdef _DEBUG
," Debug"
#else
,""
#endif
,smb_lib_ver()
,__DATE__, __TIME__, compiler
);
}
return(ver);
}
/* Returns binary-coded version and revision (e.g. 0x31000 == 3.10a) */
long DLLCALL bbs_ver_num(void)
{
return(VERSION_HEX);
}
void DLLCALL bbs_terminate(void)
{
lprintf(LOG_INFO,"BBS Server terminate");
terminate_server=true;
}
extern bbs_startup_t bbs_startup;
static void cleanup(int code)
{
lputs(LOG_INFO,"Terminal Server thread terminating");
xpms_destroy(ts_set, sock_close_cb, startup);
#ifdef _WINSOCKAPI_
if(WSAInitialized && WSACleanup()!=0)
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);
protected_uint32_destroy(node_threads_running);
#ifdef _WIN32
if(exec_mutex!=NULL) {
CloseHandle(exec_mutex);
exec_mutex=NULL;
}
if(hK32!=NULL) {
FreeLibrary(hK32);
hK32=NULL;
}
#if 0 && defined(_DEBUG) && defined(_MSC_VER)
_CrtMemDumpAllObjectsSince(&mem_chkpoint);
if(debug_log!=INVALID_HANDLE_VALUE) {
CloseHandle(debug_log);
debug_log=INVALID_HANDLE_VALUE;
}
#endif // _DEBUG && _MSC_VER
#endif // _WIN32
status("Down");
thread_down();
if(terminate_server || code)
lprintf(LOG_INFO,"Terminal Server thread terminated (%lu clients served)", served);
if(startup->terminated!=NULL)
startup->terminated(startup->cbdata,code);
}
void DLLCALL bbs_thread(void* arg)
{
char host_name[256];
char* identity;
char* p;
char str[MAX_PATH+1];
char logstr[256];
union xp_sockaddr server_addr={0};
union xp_sockaddr client_addr;
socklen_t client_addr_len;
SOCKET client_socket=INVALID_SOCKET;
int i;
int file;
int result;
time_t t;
time_t start;
time_t initialized=0;
node_t node;
sbbs_t* events=NULL;
client_t client;
startup=(bbs_startup_t*)arg;
BOOL is_client=FALSE;
#ifdef __unix__
SOCKET uspy_listen_socket[MAX_NODES];
struct main_sock_cb_data uspy_cb[MAX_NODES]={};
union xp_sockaddr uspy_addr;
#endif
#ifdef USE_CRYPTLIB
CRYPT_CONTEXT ssh_context;
#endif
struct main_sock_cb_data telnet_cb;
struct main_sock_cb_data ssh_cb;
struct main_sock_cb_data rlogin_cb;
void *ts_cb;
int err;
if(startup==NULL) {
sbbs_beep(100,500);
fprintf(stderr, "No startup structure passed!\n");
return;
}
if(startup->size!=sizeof(bbs_startup_t)) { // verify size
sbbs_beep(100,500);
sbbs_beep(300,500);
sbbs_beep(100,500);
fprintf(stderr, "Invalid startup structure!\n");
return;
}
#ifdef _THREAD_SUID_BROKEN
if(thread_suid_broken)
startup->seteuid(TRUE);
#endif
ZERO_VAR(js_server_props);
SAFEPRINTF3(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.value;
js_server_props.options=&startup->options;
js_server_props.interfaces=&startup->telnet_interfaces;
uptime=0;
served=0;
startup->recycle_now=FALSE;
startup->shutdown_now=FALSE;
terminate_server=false;
SetThreadName("sbbs/termServer");
do {
/* Setup intelligent defaults */
if(startup->telnet_port==0) startup->telnet_port=IPPORT_TELNET;
if(startup->rlogin_port==0) startup->rlogin_port=513;
#ifdef USE_CRYPTLIB
if(startup->ssh_port==0) startup->ssh_port=22;
#endif
if(startup->outbuf_drain_timeout==0) startup->outbuf_drain_timeout=10;
if(startup->sem_chk_freq==0) startup->sem_chk_freq=DEFAULT_SEM_CHK_FREQ;
if(startup->temp_dir[0]) backslash(startup->temp_dir);
protected_uint32_init(&node_threads_running,0);
thread_up(FALSE /* setuid */);
if(startup->seteuid!=NULL)
startup->seteuid(TRUE);
status("Initializing");
/* Defeat the lameo hex0rs - the name and copyright must remain intact */
if(crc32(COPYRIGHT_NOTICE,0)!=COPYRIGHT_CRC
|| crc32(VERSION_NOTICE,10)!=SYNCHRONET_CRC) {
lprintf(LOG_CRIT,"!CORRUPTED LIBRARY FILE");
cleanup(1);
return;
}
memset(text, 0, sizeof(text));
memset(&scfg, 0, sizeof(scfg));
lastuseron[0]=0;
char compiler[32];
DESCRIBE_COMPILER(compiler);
lprintf(LOG_INFO,"%s Version %s Revision %c%s"
,TELNET_SERVER
,VERSION
,toupper(REVISION)
#ifdef _DEBUG
," Debug"
#else
,""
#endif
);
lprintf(LOG_INFO,"Compiled %s %s with %s", __DATE__, __TIME__, compiler);
lprintf(LOG_DEBUG,"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(LOG_CRIT,"!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(LOG_CRIT,"!COMPILER ERROR: sizeof(node_t)=%d instead of %d"
,sizeof(node_t),SIZEOF_NODE_T);
cleanup(1);
return;
}
#ifdef _WIN32
if((exec_mutex=CreateMutex(NULL,false,NULL))==NULL) {
lprintf(LOG_CRIT,"!ERROR %d creating exec_mutex", GetLastError());
cleanup(1);
return;
}
hK32 = LoadLibrary("KERNEL32");
#endif // _WIN32
if(!winsock_startup()) {
cleanup(1);
return;
}
t=time(NULL);
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 */
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;
SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
if(!load_cfg(&scfg, text, TRUE, logstr)) {
lprintf(LOG_CRIT,"!ERROR %s",logstr);
lprintf(LOG_CRIT,"!FAILED to load configuration files");
cleanup(1);
return;
}
if(startup->host_name[0]==0)
SAFECOPY(startup->host_name,scfg.sys_inetaddr);
if((t=checktime())!=0) { /* Check binary time */
lprintf(LOG_ERR,"!TIME PROBLEM (%ld)",t);
}
if(smb_tzutc(sys_timezone(&scfg)) != xpTimeZone_local()) {
lprintf(LOG_WARNING,"Configured time zone (%s, 0x%04hX, offset: %d) does not match system-local time zone offset: %d"
,smb_zonestr(scfg.sys_timezone,str), scfg.sys_timezone, smb_tzutc(scfg.sys_timezone), xpTimeZone_local());
}
if(uptime==0)
uptime=time(NULL); /* this must be done *after* setting the timezone */
if(startup->last_node>scfg.sys_nodes) {
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]);
SAFEPRINTF(str,"%sdsts.dab",i ? scfg.node_path[i-1] : scfg.ctrl_dir);
if(flength(str)<DSTSDABLEN) {
if((file=sopen(str,O_WRONLY|O_CREAT|O_APPEND, SH_DENYNO, DEFFILEMODE))==-1) {
lprintf(LOG_CRIT,"!ERROR %d creating %s",errno, str);
cleanup(1);
return;
}
while(filelength(file)<DSTSDABLEN)
if(write(file,"\0",1)!=1)
break; /* Create NULL system dsts.dab */
close(file);
}
}
/* Initial global node variables */
for(i=0;i<MAX_NODES;i++) {
node_inbuf[i]=NULL;
node_socket[i]=INVALID_SOCKET;
spy_socket[i]=INVALID_SOCKET;
#ifdef __unix__
uspy_socket[i]=INVALID_SOCKET;
uspy_listen_socket[i]=INVALID_SOCKET;
#endif
}
startup->node_inbuf=node_inbuf;
/* open a socket and wait for a client */
ts_set = xpms_create(startup->bind_retry_count, startup->bind_retry_delay, lprintf);
if(ts_set==NULL) {
lprintf(LOG_CRIT,"!ERROR %d creating Terminal Server socket set", ERROR_VALUE);
cleanup(1);
return;
}
telnet_cb.protocol="telnet";
telnet_cb.startup=startup;
/*
* Add interfaces
*/
xpms_add_list(ts_set, PF_UNSPEC, SOCK_STREAM, 0, startup->telnet_interfaces, startup->telnet_port, "Telnet Server", sock_cb, startup->seteuid, &telnet_cb);
lprintf(LOG_INFO,"Telnet Server listening");
if(startup->options&BBS_OPT_ALLOW_RLOGIN) {
/* open a socket and wait for a client */
rlogin_cb.protocol="rlogin";
rlogin_cb.startup=startup;
xpms_add_list(ts_set, PF_UNSPEC, SOCK_STREAM, 0, startup->rlogin_interfaces, startup->rlogin_port, "RLogin Server", sock_cb, startup->seteuid, &rlogin_cb);
lprintf(LOG_INFO,"RLogin Server listening");
}
#ifdef USE_CRYPTLIB
#if CRYPTLIB_VERSION < 3300
#warning This version of Cryptlib is known to crash Synchronet. Upgrade to at least version 3.3 or do not build with Cryptlib support.
#endif
if(startup->options&BBS_OPT_ALLOW_SSH) {
CRYPT_KEYSET ssh_keyset;
if(!do_cryptInit()) {
lprintf(LOG_ERR, "SSH cryptInit failure");
goto NO_SSH;
}
/* Get the private key... first try loading it from a file... */
SAFEPRINTF2(str,"%s%s",scfg.ctrl_dir,"cryptlib.key");
if(cryptStatusOK(cryptKeysetOpen(&ssh_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, str, CRYPT_KEYOPT_NONE))) {
if(!cryptStatusOK(cryptGetPrivateKey(ssh_keyset, &ssh_context, CRYPT_KEYID_NAME, "ssh_server", scfg.sys_pass))) {
lprintf(LOG_ERR, "SSH cryptGetPrivateKey failure");
goto NO_SSH;
}
}
else {
/* Couldn't do that... create a new context and use the key from there... */
if(!cryptStatusOK(i=cryptCreateContext(&ssh_context, CRYPT_UNUSED, CRYPT_ALGO_RSA))) {
lprintf(LOG_ERR,"SSH Cryptlib error %d creating context",i);
goto NO_SSH;
}
if(!cryptStatusOK(i=cryptSetAttributeString(ssh_context, CRYPT_CTXINFO_LABEL, "ssh_server", 10))) {
lprintf(LOG_ERR,"SSH Cryptlib error %d setting key label",i);
goto NO_SSH;
}
if(!cryptStatusOK(i=cryptGenerateKey(ssh_context))) {
lprintf(LOG_ERR,"SSH Cryptlib error %d generating key",i);
goto NO_SSH;
}
/* Ok, now try saving this one... use the syspass to enctrpy it. */
if(cryptStatusOK(cryptKeysetOpen(&ssh_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, str, CRYPT_KEYOPT_CREATE))) {
if(!cryptStatusOK(cryptAddPrivateKey(ssh_keyset, ssh_context, scfg.sys_pass)))
lprintf(LOG_ERR,"SSH Cryptlib error %d saving key",i);
if(!cryptStatusOK(cryptKeysetClose(ssh_keyset)))
lprintf(LOG_ERR,"SSH Cryptlib error %d closing keyset",i);
}
}
/* open a socket and wait for a client */
ssh_cb.protocol="ssh";
ssh_cb.startup=startup;
xpms_add_list(ts_set, PF_UNSPEC, SOCK_STREAM, 0, startup->ssh_interfaces, startup->ssh_port, "SSH Server", sock_cb, startup->seteuid, &ssh_cb);
lprintf(LOG_INFO,"SSH Server listening");
}
NO_SSH:
#endif
sbbs = new sbbs_t(0, &server_addr, sizeof(server_addr)
,"Terminal Server", ts_set->socks[0].sock, &scfg, text, NULL);
if(sbbs->init()==false) {
lputs(LOG_CRIT,"!BBS initialization failed");
cleanup(1);
return;
}
sbbs->output_thread_running = true;
_beginthread(output_thread, 0, sbbs);
if(!(startup->options&BBS_OPT_NO_EVENTS)) {
events = new sbbs_t(0, &server_addr, sizeof(server_addr)
,"BBS Events", INVALID_SOCKET, &scfg, text, NULL);
if(events->init()==false) {
lputs(LOG_CRIT,"!Events initialization failed");
cleanup(1);
return;
}
events->event_thread_running = true;
_beginthread(event_thread, 0, events);
}
/* Save these values incase they're changed dynamically */
first_node=startup->first_node;
last_node=startup->last_node;
for(i=first_node;i<=last_node;i++) {
sbbs->getnodedat(i,&node,1);
node.status=NODE_WFC;
node.misc&=NODE_EVENT; /* Note: Turns-off NODE_RRUN flag (and others) */
node.action=0;
sbbs->putnodedat(i,&node);
}
status(STATUS_WFC);
#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
SAFEPRINTF(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_CRIT,"!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 */
shutdown_semfiles=semfile_list_init(scfg.ctrl_dir,"shutdown","telnet");
recycle_semfiles=semfile_list_init(scfg.ctrl_dir,"recycle","telnet");
SAFEPRINTF(str,"%stelnet.rec",scfg.ctrl_dir); /* legacy */
semfile_list_add(&recycle_semfiles,str);
SAFEPRINTF(str,"%stext.dat",scfg.ctrl_dir);
semfile_list_add(&recycle_semfiles,str);
SAFEPRINTF(str,"%sattr.cfg",scfg.ctrl_dir);
semfile_list_add(&recycle_semfiles,str);
if(!initialized)
semfile_list_check(&initialized,shutdown_semfiles);
semfile_list_check(&initialized,recycle_semfiles);
#ifdef __unix__ // unix-domain spy sockets
for(i=first_node;i<=last_node && !(startup->options&BBS_OPT_NO_SPY_SOCKETS);i++) {
if((unsigned int)snprintf(str,sizeof(uspy_addr.un.sun_path),
"%slocalspy%d.sock", startup->temp_dir, i)
>=sizeof(uspy_addr.un.sun_path)) {
lprintf(LOG_ERR,"Node %d !ERROR local spy socket path \"%slocalspy%d.sock\" too long."
, i, startup->temp_dir, i);
continue;
}
else {
if(xpms_add(ts_set, PF_UNIX, SOCK_STREAM, 0, str, 0, "Spy Socket", sock_cb, NULL, &uspy_cb[i-1]))
lprintf(LOG_INFO,"Node %d local spy using socket %s", i, str);
else
lprintf(LOG_ERR,"Node %d !ERROR %d creating local spy socket %s"
, i, errno, str);
}
}
#endif // __unix__ (unix-domain spy sockets)
/* signal caller that we've started up successfully */
if(startup->started!=NULL)
startup->started(startup->cbdata);
lprintf(LOG_INFO,"Terminal Server thread started for nodes %d through %d", first_node, last_node);
while(!terminate_server) {
YIELD();
if(protected_uint32_value(node_threads_running)==0) { /* check for re-run flags and recycle/shutdown sem files */
if(!(startup->options&BBS_OPT_NO_RECYCLE)) {
bool rerun=false;
for(i=first_node;i<=last_node;i++) {
if(sbbs->getnodedat(i,&node,0)!=0)
continue;
if(node.misc&NODE_RRUN) {
sbbs->getnodedat(i,&node,1);
if(!rerun)
lprintf(LOG_INFO,"Node %d flagged for re-run",i);
rerun=true;
node.misc&=~NODE_RRUN;
sbbs->putnodedat(i,&node);
}
}
if(rerun)
break;
if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) {
lprintf(LOG_INFO,"Recycle semaphore file (%s) detected"
,p);
break;
}
if(startup->recycle_now==TRUE) {
lprintf(LOG_INFO,"Recycle semaphore signaled");
startup->recycle_now=FALSE;
break;
}
}
if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL
&& lprintf(LOG_INFO,"Shutdown semaphore file (%s) detected"
,p))
|| (startup->shutdown_now==TRUE
&& lprintf(LOG_INFO,"Shutdown semaphore signaled"))) {
startup->shutdown_now=FALSE;
terminate_server=TRUE;
break;
}
}
sbbs->online=FALSE;
// sbbs->client_socket=INVALID_SOCKET;
#ifdef USE_CRYPTLIB
sbbs->ssh_mode=false;
#endif
/* now wait for connection */
client_addr_len = sizeof(client_addr);
client_socket=xpms_accept(ts_set, &client_addr
,&client_addr_len, startup->sem_chk_freq*1000, &ts_cb);
if(terminate_server) /* terminated */
break;
if(client_socket == INVALID_SOCKET)
continue;
bool rlogin = false;
#ifdef USE_CRYPTLIB
bool ssh = false;
#endif
is_client=FALSE;
if(ts_cb == &telnet_cb) {
is_client=TRUE;
} else if(ts_cb == &rlogin_cb) {
rlogin = true;
is_client=TRUE;
#ifdef USE_CRYPTLIB
} else if(ts_cb == &ssh_cb) {
ssh = true;
is_client=TRUE;
sbbs->ssh_mode=true;
#endif
} else {
#ifdef __unix__
for(i=first_node;i<=last_node;i++) {
if(&uspy_cb[i-1] == ts_cb) {
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_cb[i-1] == ts_cb) {
BOOL already_connected=(uspy_socket[i-1]!=INVALID_SOCKET);
SOCKET new_socket=client_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.un.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.un.sun_path,new_socket);
uspy_socket[i-1]=new_socket;
SAFEPRINTF(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) {
/* Do not need to close_socket(client_socket) here */
continue;
}
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(WSAGetLastError()==WSAENOBUFS) { /* recycle (re-init WinSock) on this error */
break;
}
#endif
continue;
}
char host_ip[INET6_ADDRSTRLEN];
inet_addrtop(&client_addr, host_ip, sizeof(host_ip));
if(trashcan(&scfg,host_ip,"ip-silent")) {
SSH_END();
close_socket(client_socket);
continue;
}
lprintf(LOG_INFO,"%04d %s connection accepted from: %s port %u"
,client_socket
#ifdef USE_CRYPTLIB
,rlogin ? "RLogin" : (ssh ? "SSH" : "Telnet")
#else
,rlogin ? "RLogin" : "Telnet"
#endif
, host_ip, inet_addrport(&client_addr));
login_attempt_t attempted;
ulong banned = loginBanned(&scfg, startup->login_attempt_list, client_socket, /* host_name: */NULL, startup->login_attempt, &attempted);
if(banned || sbbs->trashcan(host_ip,"ip")) {
if(banned) {
char ban_duration[128];
lprintf(LOG_NOTICE, "%04d !TEMPORARY BAN of %s (%u login attempts, last: %s) - remaining: %s"
,client_socket, host_ip, attempted.count, attempted.user, seconds_to_str(banned, ban_duration));
} else
lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", client_socket, host_ip);
SSH_END();
close_socket(client_socket);
SAFEPRINTF(logstr, "Blocked IP: %s",host_ip);
sbbs->syslog("@!",logstr);
continue;
}
#ifdef _WIN32
if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE))
PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif
/* Purge (flush) any pending input or output data */
sbbs->rioctl(IOFB);
/* Do SSH stuff here */
#ifdef USE_CRYPTLIB
if(ssh) {
int ssh_failed=0;
if(!cryptStatusOK(i=cryptCreateSession(&sbbs->ssh_session, CRYPT_UNUSED, CRYPT_SESSION_SSH_SERVER))) {
lprintf(LOG_WARNING,"%04d SSH Cryptlib error %d creating session", client_socket, i);
close_socket(client_socket);
continue;
}
if(!cryptStatusOK(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_PRIVATEKEY, ssh_context))) {
lprintf(LOG_WARNING,"%04d SSH Cryptlib error %d setting private key",client_socket, i);
cryptDestroySession(sbbs->ssh_session);
close_socket(client_socket);
continue;
}
if(!cryptStatusOK(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_NETWORKSOCKET, client_socket))) {
lprintf(LOG_WARNING,"%04d SSH Cryptlib error %d setting socket",client_socket, i);
cryptDestroySession(sbbs->ssh_session);
close_socket(client_socket);
continue;
}
for(ssh_failed=0; ssh_failed < 2; ssh_failed++) {
/* Accept any credentials */
if(!cryptStatusOK(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_AUTHRESPONSE, 1))) {
ssh_failed=1;
break;
}
if(!cryptStatusOK(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_ACTIVE, 1))) {
if(i != CRYPT_ENVELOPE_RESOURCE) {
ssh_failed=2;
break;
}
}
else {
ssh_failed=0;
break;
}
}
switch(ssh_failed) {
case 1:
lprintf(LOG_WARNING,"%04d SSH Cryptlib error %d setting AUTHRESPONSE",client_socket, i);
break;
case 2:
switch(i) {
case CRYPT_ERROR_BADDATA:
lprintf(LOG_NOTICE,"%04d SSH Bad/unrecognized data format", client_socket);
break;
case CRYPT_ERROR_READ:
lprintf(LOG_WARNING,"%04d SSH Read failure", client_socket);
break;
case CRYPT_ERROR_WRITE:
lprintf(LOG_WARNING,"%04d SSH Write failure", client_socket);
break;
default:
lprintf(LOG_WARNING,"%04d SSH Cryptlib error %d setting session active",client_socket, i);
break;
}
break;
}
if(ssh_failed) {
cryptDestroySession(sbbs->ssh_session);
close_socket(client_socket);
continue;
}
if(!cryptStatusOK(err=cryptPopData(sbbs->ssh_session, str, sizeof(str), &i))) {
lprintf(LOG_WARNING,"Node %d !ERROR %d receiving on Cryptlib session", sbbs->cfg.node_num, err);
i=0;
}
}
#endif
sbbs->client_socket=client_socket; // required for output to the user
sbbs->online=ON_REMOTE;
if(rlogin)
sbbs->outcom(0); /* acknowledge RLogin per RFC 1282 */
sbbs->putcom(crlf);
sbbs->putcom(VERSION_NOTICE);
sbbs->putcom(crlf);
sbbs->bprintf("Connection from: %s\r\n", host_ip);
SAFECOPY(host_name, "<no name>");
if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) {
sbbs->bprintf("Resolving hostname...");
getnameinfo(&client_addr.addr, client_addr_len, host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD);
sbbs->putcom(crlf);
lprintf(LOG_INFO,"%04d Hostname: %s", client_socket, host_name);
}
if(sbbs->trashcan(host_name,"host")) {
SSH_END();
close_socket(client_socket);
lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s"
,client_socket, host_name);
SAFEPRINTF(logstr, "Blocked Hostname: %s",host_name);
sbbs->syslog("@!",logstr);
continue;
}
identity=NULL;
if(startup->options&BBS_OPT_GET_IDENT) {
sbbs->bprintf("Resolving identity...");
/* ToDo: Make ident timeout configurable */
if(identify(&client_addr, inet_addrport(&client_addr), str, sizeof(str)-1, /* timeout: */1)) {
lprintf(LOG_DEBUG,"%04d Ident Response: %s",client_socket, str);
identity=strrchr(str,':');
if(identity!=NULL) {
identity++; /* skip colon */
SKIP_WHITESPACE(identity);
if(*identity)
lprintf(LOG_INFO,"%04d Identity: %s",client_socket, identity);
}
}
sbbs->putcom(crlf);
}
/* Initialize client display */
client.size=sizeof(client);
client.time=time32(NULL);
SAFECOPY(client.addr,host_ip);
SAFECOPY(client.host,host_name);
client.port=inet_addrport(&client_addr);
#ifdef USE_CRYPTLIB
client.protocol=rlogin ? "RLogin":(ssh ? "SSH" : "Telnet");
#else
client.protocol=rlogin ? "RLogin":"Telnet";
#endif
client.user="<unknown>";
client_on(client_socket,&client,FALSE /* update */);
for(i=first_node;i<=last_node;i++) {
/* paranoia: make sure node.status!=NODE_WFC by default */
node.status=NODE_INVALID_STATUS;
if(sbbs->getnodedat(i,&node,1)!=0)
continue;
if(node.status==NODE_WFC) {
node.status=NODE_LOGON;
#ifdef USE_CRYPTLIB
if(ssh)
node.connection=NODE_CONNECTION_SSH;
else
#endif
if(rlogin)
node.connection=NODE_CONNECTION_RLOGIN;
else
node.connection=NODE_CONNECTION_TELNET;
sbbs->putnodedat(i,&node);
break;
}
sbbs->putnodedat(i,&node);
}
if(i>last_node) {
lprintf(LOG_WARNING,"%04d !No nodes available for login.",client_socket);
SAFEPRINTF(str,"%snonodes.txt",scfg.text_dir);
if(fexist(str))
sbbs->printfile(str,P_NOABORT);
else {
sbbs->putcom("\r\nSorry, all telnet nodes are in use or otherwise unavailable.\r\n");
sbbs->putcom("Please try again later.\r\n");
}
mswait(3000);
client_off(client_socket);
SSH_END();
close_socket(client_socket);
continue;
}
node_socket[i-1]=client_socket;
sbbs_t* new_node = new sbbs_t(i, &client_addr, client_addr_len, host_name
,client_socket
,&scfg, text, &client);
new_node->client=client;
#ifdef USE_CRYPTLIB
if(ssh) {
new_node->ssh_session=sbbs->ssh_session;
new_node->ssh_mode=true;
}
#endif
/* copy the IDENT response, if any */
if(identity!=NULL)
SAFECOPY(new_node->client_ident,identity);
if(new_node->init()==false) {
lprintf(LOG_INFO,"%04d Node %d !Initialization failure"
,client_socket,new_node->cfg.node_num);
SAFEPRINTF(str,"%snonodes.txt",scfg.text_dir);
if(fexist(str))
sbbs->printfile(str,P_NOABORT);
else
sbbs->putcom("\r\nSorry, initialization failed. Try again later.\r\n");
mswait(3000);
sbbs->getnodedat(new_node->cfg.node_num,&node,1);
node.status=NODE_WFC;
sbbs->putnodedat(new_node->cfg.node_num,&node);
delete new_node;
node_socket[i-1]=INVALID_SOCKET;
client_off(client_socket);
SSH_END();
close_socket(client_socket);
continue;
}
if(rlogin==true) {
SAFECOPY(new_node->connection,"RLogin");
new_node->node_connection=NODE_CONNECTION_RLOGIN;
new_node->sys_status|=SS_RLOGIN;
new_node->telnet_mode|=TELNET_MODE_OFF; // RLogin does not use Telnet commands
}
#ifdef USE_CRYPTLIB
if(ssh) {
/* TODO: IPv6? */
SOCKET tmp_sock;
SOCKADDR_IN tmp_addr={0};
socklen_t tmp_addr_len;
/* open a socket and connect to yourself */
tmp_sock = open_socket(SOCK_STREAM, "passthru");
if(tmp_sock == INVALID_SOCKET) {
lprintf(LOG_ERR,"!ERROR %d creating passthru listen socket", ERROR_VALUE);
goto NO_PASSTHRU;
}
lprintf(LOG_DEBUG,"passthru listen socket %d opened",tmp_sock);
/*****************************/
/* Listen for incoming calls */
/*****************************/
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.sin_addr.s_addr = htonl(IPv4_LOCALHOST);
tmp_addr.sin_family = AF_INET;
tmp_addr.sin_port = 0;
result = bind(tmp_sock,(struct sockaddr *)&tmp_addr,sizeof(tmp_addr));
if(result != 0) {
lprintf(LOG_NOTICE,"%s",BIND_FAILURE_HELP);
close_socket(tmp_sock);
goto NO_PASSTHRU;
}
result = listen(tmp_sock, 1);
if(result != 0) {
lprintf(LOG_ERR,"!ERROR %d (%d) listening on passthru socket", result, ERROR_VALUE);
close_socket(tmp_sock);
goto NO_PASSTHRU;
}
lprintf(LOG_INFO,"Listening passthru socket listening on port %u",htons(tmp_addr.sin_port));
new_node->passthru_socket = open_socket(SOCK_STREAM, "passthru");
if(new_node->passthru_socket == INVALID_SOCKET) {
lprintf(LOG_ERR,"!ERROR %d creating passthru connecting socket", ERROR_VALUE);
close_socket(tmp_sock);
goto NO_PASSTHRU;
}
lprintf(LOG_DEBUG,"passthru connect socket %d opened",new_node->passthru_socket);
tmp_addr_len=sizeof(tmp_addr);
if(getsockname(tmp_sock, (struct sockaddr *)&tmp_addr, &tmp_addr_len)) {
lprintf(LOG_ERR,"!ERROR %d getting passthru listener address", ERROR_VALUE);
close_socket(tmp_sock);
close_socket(new_node->passthru_socket);
new_node->passthru_socket=INVALID_SOCKET;
goto NO_PASSTHRU;
}
result = connect(new_node->passthru_socket, (struct sockaddr *)&tmp_addr, tmp_addr_len);
if(result != 0) {
lprintf(LOG_ERR,"!ERROR %d (%d) connecting to passthru socket", result, ERROR_VALUE);
close_socket(new_node->passthru_socket);
new_node->passthru_socket=INVALID_SOCKET;
close_socket(tmp_sock);
goto NO_PASSTHRU;
}
new_node->client_socket_dup=accept(tmp_sock, (struct sockaddr *)&tmp_addr, &tmp_addr_len);
if(new_node->client_socket_dup == INVALID_SOCKET) {
lprintf(LOG_ERR,"!ERROR (%d) connecting accept()ing on passthru socket", ERROR_VALUE);
lprintf(LOG_WARNING,"!WARNING native doors which use sockets will not function");
close_socket(new_node->passthru_socket);
new_node->passthru_socket=INVALID_SOCKET;
close_socket(tmp_sock);
goto NO_PASSTHRU;
}
close_socket(tmp_sock);
new_node->passthru_output_thread_running = true;
_beginthread(passthru_output_thread, 0, new_node);
new_node->passthru_input_thread_running = true;
_beginthread(passthru_input_thread, 0, new_node);
NO_PASSTHRU:
SAFECOPY(new_node->connection,"SSH");
new_node->node_connection=NODE_CONNECTION_SSH;
new_node->sys_status|=SS_SSH;
new_node->telnet_mode|=TELNET_MODE_OFF; // SSH does not use Telnet commands
new_node->ssh_session=sbbs->ssh_session;
/* Wait for pending data to be sent then turn off ssh_mode for uber-output */
while(sbbs->output_thread_running && RingBufFull(&sbbs->outbuf))
SLEEP(1);
if(!cryptStatusOK(err=cryptPopData(sbbs->ssh_session, str, sizeof(str), &i))) {
lprintf(LOG_WARNING,"Node %d !ERROR %d receiving on Cryptlib session", sbbs->cfg.node_num, err);
i=0;
}
sbbs->ssh_mode=false;
}
#endif
protected_uint32_adjust(&node_threads_running, 1);
new_node->input_thread_running = true;
new_node->input_thread=(HANDLE)_beginthread(input_thread,0, new_node);
new_node->output_thread_running = true;
_beginthread(output_thread, 0, new_node);
_beginthread(node_thread, 0, new_node);
served++;
}
// Close all open sockets
for(i=0;i<MAX_NODES;i++) {
if(node_socket[i]!=INVALID_SOCKET) {
lprintf(LOG_INFO,"Closing node %d socket %d", i+1, node_socket[i]);
close_socket(node_socket[i]);
node_socket[i]=INVALID_SOCKET;
}
#ifdef __unix__
if(uspy_listen_socket[i]!=INVALID_SOCKET) {
close_socket(uspy_listen_socket[i]);
uspy_listen_socket[i]=INVALID_SOCKET;
snprintf(str,sizeof(uspy_addr.un.sun_path),"%slocalspy%d.sock", startup->temp_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;
if(events!=NULL)
events->terminated=true;
// Wake-up BBS output thread so it can terminate
sem_post(&sbbs->outbuf.sem);
// Wait for all node threads to terminate
if(protected_uint32_value(node_threads_running)) {
lprintf(LOG_INFO,"Waiting for %d node threads to terminate...", protected_uint32_value(node_threads_running));
start=time(NULL);
while(protected_uint32_value(node_threads_running)) {
if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
lprintf(LOG_ERR,"!TIMEOUT waiting for %d node thread(s) to "
"terminate", protected_uint32_value(node_threads_running));
break;
}
mswait(100);
}
}
// Wait for Events thread to terminate
if(events!=NULL && events->event_thread_running) {
lprintf(LOG_INFO,"Waiting for events thread to terminate...");
start=time(NULL);
while(events->event_thread_running) {
#if 0 /* the events thread can/will segfault if it continues to run and dereference sbbs->cfg */
if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
lprintf(LOG_ERR,"!TIMEOUT waiting for BBS events thread to "
"terminate");
break;
}
#endif
mswait(100);
}
}
// Wait for BBS output thread to terminate
if(sbbs->output_thread_running) {
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(LOG_ERR,"!TIMEOUT waiting for BBS output thread to "
"terminate");
break;
}
mswait(100);
}
}
// Set all nodes' status to OFFLINE
for(i=first_node;i<=last_node;i++) {
sbbs->getnodedat(i,&node,1);
node.status=NODE_OFFLINE;
sbbs->putnodedat(i,&node);
}
if(events!=NULL) {
if(events->event_thread_running)
lprintf(LOG_ERR,"!Events thread still running, can't delete");
else
delete events;
}
if(sbbs->output_thread_running)
lprintf(LOG_ERR,"!Output thread still running, can't delete");
else
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);
}