Select Git revision
-
Deucе authored
This should fix the sftp hangs... While FreeBSD will return EDEADLOCK when a mutex would deadlock, Linux will do what I ask and deadlock. Since we're not checking the return value of pthread_mutex_lock(), this would go completely unnoticed on FreeBSD, resulting in the mutex being unlocked early and all sorts of unprotected accesses would happen, potentially doing crazy things. On Linux it just deadlocks, and taking a peek at the thread backtraces makes the problem obvious. So, point to Linux for making life more correct for people who do incorrect things (locking without checking the return value). I'd also like to thank nelgin, for sticking with me on this issue and being my gdb puppet. And really, this commit should be shared by all the contributors who... 𝆺𝅥𝅮𝆺𝅥𝅮𝆹𝅥𝅯𝆹𝅥𝅯
Deucе authoredThis should fix the sftp hangs... While FreeBSD will return EDEADLOCK when a mutex would deadlock, Linux will do what I ask and deadlock. Since we're not checking the return value of pthread_mutex_lock(), this would go completely unnoticed on FreeBSD, resulting in the mutex being unlocked early and all sorts of unprotected accesses would happen, potentially doing crazy things. On Linux it just deadlocks, and taking a peek at the thread backtraces makes the problem obvious. So, point to Linux for making life more correct for people who do incorrect things (locking without checking the return value). I'd also like to thank nelgin, for sticking with me on this issue and being my gdb puppet. And really, this commit should be shared by all the contributors who... 𝆺𝅥𝅮𝆺𝅥𝅮𝆹𝅥𝅯𝆹𝅥𝅯
main.cpp 178.59 KiB
/* Synchronet terminal server thread and related functions */
/****************************************************************************
* @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 *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* 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 "petdefs.h"
#include "filedat.h"
#include "js_rtpool.h"
#include "js_request.h"
#include "ssl.h"
#include "ver.h"
#include "eventwrap.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"
static const char* server_abbrev = "term";
#define TIMEOUT_THREAD_WAIT 60 // Seconds (was 15)
#define IO_THREAD_BUF_SIZE 20000 // Bytes
#define TIMEOUT_MUTEX_FILE 12*60*60
#ifdef USE_CRYPTLIB
static protected_uint32_t ssh_sessions;
void ssh_session_destroy(SOCKET sock, CRYPT_SESSION session, int line)
{
int result = cryptDestroySession(session);
if(result != 0)
lprintf(LOG_ERR, "%04d SSH ERROR %d destroying Cryptlib Session %d from line %d"
, sock, result, session, line);
else {
uint32_t remain = protected_uint32_adjust_fetch(&ssh_sessions, -1);
lprintf(LOG_DEBUG, "%04d SSH Cryptlib Session: %d destroyed from line %d (%u remain)"
, sock, session, line, remain);
}
}
#define SSH_END(sock) do { \
if(ssh) { \
pthread_mutex_lock(&sbbs->ssh_mutex); \
ssh_session_destroy(sock, sbbs->ssh_session, __LINE__); \
sbbs->ssh_mode = false; \
pthread_mutex_unlock(&sbbs->ssh_mutex); \
} \
} while(0)
#else
#define SSH_END(x)
#endif
volatile time_t uptime=0;
volatile uint 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 scfg_t scfg;
static struct mqtt mqtt;
static char * text[TOTAL_TEXT];
static scfg_t node_scfg[MAX_NODES];
static char * node_text[MAX_NODES][TOTAL_TEXT];
static WORD first_node;
static WORD last_node;
static bool terminate_server=false;
static str_list_t pause_semfiles;
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
static str_list_t clear_attempts_semfiles;
static link_list_t current_logins;
static link_list_t current_connections;
#ifdef _THREAD_SUID_BROKEN
int thread_suid_broken=true; /* NPTL is no longer broken */
#endif
/* convenient space-saving global variables */
extern "C" {
const char* crlf="\r\n";
const char* nulstr="";
};
#define GCES(status, node, sess, action) do { \
char *GCES_estr; \
int GCES_level; \
get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);\
if (GCES_estr) { \
if (GCES_level < startup->ssh_error_level) \
GCES_level = startup->ssh_error_level; \
lprintf(GCES_level, "Node %d SSH %s from %s", node, GCES_estr, __FUNCTION__); \
free_crypt_attrstr(GCES_estr); \
} \
} while (0)
#define GCESNN(status, sess, action) do { \
char *GCES_estr; \
int GCES_level; \
get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);\
if (GCES_estr) { \
if (GCES_level < startup->ssh_error_level) \
GCES_level = startup->ssh_error_level; \
lprintf(GCES_level, "SSH %s from %s", GCES_estr, __FUNCTION__); \
free_crypt_attrstr(GCES_estr); \
} \
} while (0)
#define GCESS(status, sock, sess, action) do { \
char *GCES_estr; \
int GCES_level; \
get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);\
if (GCES_estr) { \
if (GCES_level < startup->ssh_error_level) \
GCES_level = startup->ssh_error_level; \
lprintf(GCES_level, "%04d SSH %s from %s", sock, GCES_estr, __FUNCTION__); \
free_crypt_attrstr(GCES_estr); \
} \
} while (0)
#define GCESSTR(status, str, sess, action) do { \
char *GCES_estr; \
int GCES_level; \
get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);\
if (GCES_estr) { \
if (GCES_level < startup->ssh_error_level) \
GCES_level = startup->ssh_error_level; \
lprintf(GCES_level, "%s SSH %s from %s (session %d)", str, GCES_estr, __FUNCTION__, sess); \
free_crypt_attrstr(GCES_estr); \
} \
} while (0)
extern "C" {
static bbs_startup_t* startup=NULL;
static void set_state(enum server_state state)
{
static int curr_state;
if(state == curr_state)
return;
if(startup != NULL) {
if(startup->set_state != NULL)
startup->set_state(startup->cbdata, state);
mqtt_server_state(&mqtt, state);
}
curr_state = state;
}
static void update_clients()
{
if(startup != NULL) {
if(startup->clients != NULL)
startup->clients(startup->cbdata,protected_uint32_value(node_threads_running));
}
}
void client_on(SOCKET sock, client_t* client, BOOL update)
{
if(!update)
listAddNodeData(¤t_connections, client->addr, strlen(client->addr)+1, sock, LAST_NODE);
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,true,sock,client,update);
mqtt_client_on(&mqtt, true, sock, client, update);
}
static void client_off(SOCKET sock)
{
listRemoveTaggedNode(¤t_connections, sock, /* free_data */true);
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,false,sock,NULL,false);
mqtt_client_on(&mqtt, 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)
{
mqtt_lputs(&mqtt, TOPIC_SERVER, level, str);
if(level <= LOG_ERR) {
char errmsg[1024];
SAFEPRINTF2(errmsg, "%-4s %s", server_abbrev, str);
errorlog(&scfg, &mqtt, level, startup==NULL ? NULL:startup->host_name, errmsg);
if(startup!=NULL && startup->errormsg!=NULL)
startup->errormsg(startup->cbdata,level,errmsg);
}
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 eputs(int level, const char *str)
{
if(*str == 0)
return 0;
mqtt_lputs(&mqtt, TOPIC_HOST_EVENT, level, str);
if(level <= LOG_ERR) {
char errmsg[1024];
SAFEPRINTF(errmsg, "evnt %s", str);
errorlog(&scfg, &mqtt, level, startup==NULL ? NULL:startup->host_name, errmsg);
if(startup!=NULL && startup->errormsg!=NULL)
startup->errormsg(startup->cbdata, level, errmsg);
}
if(startup==NULL || startup->event_lputs==NULL || level > startup->log_level)
return(0);
return(startup->event_lputs(startup->event_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));
}
void
sbbs_t::log_crypt_error_status_sock(int status, const char *action)
{
char *estr;
int level;
if (cryptStatusError(status)) {
get_crypt_error_string(status, ssh_session, &estr, action, &level);
if (estr) {
if (level < startup->ssh_error_level)
level = startup->ssh_error_level;
lprintf(level, "%04d SSH %s", client_socket, estr);
free_crypt_attrstr(estr);
}
}
}
/* Picks the right log callback function (event or term) based on the sbbs->cfg.node_num value */
/* Prepends the current node number and user alias (if applicable) */
int sbbs_t::lputs(int level, const char* str)
{
char msg[2048];
char prefix[32] = "";
char user_str[64] = "";
if(is_event_thread && event_code != NULL && *event_code)
SAFEPRINTF(prefix, "%s ", event_code);
else if(cfg.node_num && !is_event_thread)
SAFEPRINTF(prefix, "Node %d ", cfg.node_num);
else if(client_name[0])
SAFEPRINTF(prefix, "%s ", client_name);
if(useron.number)
SAFEPRINTF(user_str, "<%s> ", useron.alias);
SAFEPRINTF3(msg, "%s%s%s", prefix, user_str, str);
strip_ctrl(msg, msg);
if(is_event_thread)
return ::eputs(level, msg);
return ::lputs(level, msg);
}
int sbbs_t::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));
}
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);
}
void call_socket_open_callback(bool open)
{
if(startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata, open);
}
SOCKET open_socket(int domain, int type, const char* protocol)
{
SOCKET sock;
char error[256];
sock=socket(domain, type, IPPROTO_IP);
if(sock!=INVALID_SOCKET)
call_socket_open_callback(true);
if(sock!=INVALID_SOCKET && set_socket_options(&scfg, sock, protocol, error, sizeof(error)))
lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
return(sock);
}
// Used by sbbs_t::ftp_put() and js_accept()
SOCKET accept_socket(SOCKET s, union xp_sockaddr* addr, socklen_t* addrlen)
{
SOCKET sock;
sock=accept(s,&addr->addr,addrlen);
if(sock!=INVALID_SOCKET)
call_socket_open_callback(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);
call_socket_open_callback(false);
if(result!=0 && ERROR_VALUE!=ENOTSOCK)
lprintf(LOG_WARNING,"!ERROR %d closing socket %d",ERROR_VALUE,sock);
return(result);
}
/* TODO: IPv6 */
in_addr_t resolve_ip(char *addr)
{
HOSTENT* host;
char* p;
if(*addr==0)
return INADDR_NONE;
for(p=addr;*p;p++)
if(*p!='.' && !IS_DIGIT(*p))
break;
if(!(*p))
return(inet_addr(addr));
if((host=gethostbyname(addr))==NULL)
return INADDR_NONE;
if(host->h_addr_list[0] == NULL)
return INADDR_NONE;
return *((in_addr_t*)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 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 sbbs_random(int n)
{
return(xp_random(n));
}
#ifdef JAVASCRIPT
static js_server_props_t js_server_props;
void* js_GetClassPrivate(JSContext *cx, JSObject *obj, JSClass* cls)
{
void *ret = JS_GetInstancePrivate(cx, obj, cls, NULL);
/*
* NOTE: Any changes here should also be added to the same function in jsdoor.c
* (ie: anything not Synchronet specific).
*/
if(ret == NULL)
JS_ReportError(cx, "'%s' instance: No Private Data or Class Mismatch"
, cls == NULL ? "???" : cls->name);
return ret;
}
JSBool
js_CreateArrayOfStrings(JSContext* cx, JSObject* parent, const char* name, const 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
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_FALSE
&& JS_DefineProperty(cx,obj,"_ver"
,INT_TO_JSVAL(ver),NULL,NULL,JSPROP_READONLY) != JS_FALSE);
}
JSBool
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* property_array_name = "_property_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
js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
uint i;
int ver;
jsval val;
JSString* js_str;
JSObject* array;
JSObject* prop;
if((array=JS_NewObject(cx, NULL, NULL, obj))==NULL)
return(JS_FALSE);
for(i=0; props[i].name != NULL; ++i) {
if (props[i].tinyid < 256 && props[i].tinyid > -129) {
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);
}
else {
if(!JS_DefineProperty(cx, obj, props[i].name, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
return(JS_FALSE);
}
if (!(props[i].flags & JSPROP_ENUMERATE)) /* No need to document invisible props */
continue;
prop = JS_NewObject(cx, NULL, NULL, array);
if (prop == NULL)
return JS_FALSE;
if((ver=props[i].ver) < 10000) /* auto convert 313 to 31300 */
ver*=100;
val = INT_TO_JSVAL(ver);
JS_SetProperty(cx, prop, "ver", &val);
if (props[i].desc != NULL) {
if ((js_str = JS_NewStringCopyZ(cx, props[i].desc)) == NULL)
return JS_FALSE;
val = STRING_TO_JSVAL(js_str);
JS_SetProperty(cx, prop, "desc", &val);
}
if (!JS_DefineProperty(cx, array, props[i].name, OBJECT_TO_JSVAL(prop), NULL, NULL, JSPROP_READONLY | JSPROP_ENUMERATE))
return JS_FALSE;
}
return JS_DefineProperty(cx, obj, property_array_name, OBJECT_TO_JSVAL(array), NULL, NULL, JSPROP_READONLY);
}
JSBool
js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
{
int i;
jsuint len=0;
int 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 < (int)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 != NULL; ++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);
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
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
js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
uint i;
/*
* NOTE: Any changes here should also be added to the same function in jsdoor.c
* (ie: anything not Synchronet specific).
*/
for(i=0;props[i].name;i++) {
if (props[i].tinyid < 256 && props[i].tinyid > -129) {
if(!JS_DefinePropertyWithTinyId(cx, obj,
props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
return(JS_FALSE);
}
else {
if(!JS_DefineProperty(cx, obj, props[i].name, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
return(JS_FALSE);
}
}
return(JS_TRUE);
}
JSBool
js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
{
uint i;
/*
* NOTE: Any changes here should also be added to the same function in jsdoor.c
* (ie: anything not Synchronet specific).
*/
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
js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
{
uint i;
jsval val;
/*
* NOTE: Any changes here should also be added to the same function in jsdoor.c
* (ie: anything not Synchronet specific).
*/
if(props) {
for(i=0;props[i].name;i++) {
if(name==NULL || strcmp(name, props[i].name)==0) {
if (props[i].tinyid < 256 && props[i].tinyid > -129) {
if(!JS_DefinePropertyWithTinyId(cx, obj,
props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
return(JS_FALSE);
}
else {
if(!JS_DefineProperty(cx, obj, props[i].name, 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
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) {
FREE_AND_NULL(line);
return(JS_FALSE);
}
JSSTRING_TO_RASTRING(cx, str, line, &line_sz, NULL);
if(line==NULL)
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
sbbs->lputs(level, line);
JS_RESUMEREQUEST(cx, rc);
}
if(line != NULL)
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) {
FREE_AND_NULL(cstr);
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_REMOTE)
sbbs->lputs(LOG_INFO, cstr);
else
sbbs->bputs(cstr);
JS_RESUMEREQUEST(cx, rc);
}
FREE_AND_NULL(cstr);
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);
if(len < 1)
continue;
rc=JS_SUSPENDREQUEST(cx);
sbbs->putcom(str, len);
JS_RESUMEREQUEST(cx, rc);
}
if (str != NULL)
free(str);
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_REMOTE)
sbbs->lputs(LOG_INFO, 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);
if(sbbs->online != ON_REMOTE)
sbbs->lputs(LOG_WARNING, cstr);
else {
sbbs->attr(sbbs->cfg.color[clr_err]);
sbbs->bputs(cstr);
sbbs->attr(LIGHTGRAY);
sbbs->bputs(crlf);
}
free(cstr);
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[128] = "";
JSString * str;
sbbs_t* sbbs;
jsrefcount rc;
char* prompt=NULL;
int32 mode = K_EDIT;
size_t result;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
uintN argn = 0;
if(argc > argn && JSVAL_IS_STRING(argv[argn])) {
JSVALUE_TO_MSTRING(cx, argv[argn], prompt, NULL);
if(prompt==NULL)
return(JS_FALSE);
argn++;
}
if(argc > argn && JSVAL_IS_STRING(argv[argn])) {
JSVALUE_TO_STRBUF(cx, argv[argn], instr, sizeof(instr), NULL);
argn++;
}
if(argc > argn && JSVAL_IS_NUMBER(argv[argn])) {
if(!JS_ValueToInt32(cx,argv[argn], &mode)) {
free(prompt);
return JS_FALSE;
}
argn++;
}
rc=JS_SUSPENDREQUEST(cx);
if(prompt != NULL) {
sbbs->bprintf("\1n\1y\1h%s\1w: ",prompt);
free(prompt);
}
result = sbbs->getstr(instr, sizeof(instr)-1, mode);
sbbs->attr(LIGHTGRAY);
if(!result) {
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("[<i>number</i> level=LOG_INFO,] value [,value]")
,JSDOCSTR("Add a line of text to the server and/or system log.<br>"
"<i>values</i> are typically string constants or variables (each logged as a separate log message),<br>"
"<i>level</i> is the severity of the message to be logged, one of the globally-defined values, in decreasing severity:<br>"
"<tt>LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, and LOG_DEBUG</tt> (default: <tt>LOG_INFO</tt>)")
,311
},
{"read", js_read, 0, JSTYPE_STRING, JSDOCSTR("[count=128]")
,JSDOCSTR("Read up to <tt>count</tt> characters from input stream and return as a string or <tt>undefined</tt> upon error")
,311
},
{"readln", js_readln, 0, JSTYPE_STRING, JSDOCSTR("[count=128]")
,JSDOCSTR("Read a single line, up to <tt>count</tt> characters, from input stream and return as a string or <tt>undefined</tt> upon error")
,311
},
{"write", js_write, 0, JSTYPE_VOID, JSDOCSTR("value [,value]")
,JSDOCSTR("Send one or more values (typically strings) to the output stream")
,311
},
{"write_raw", js_write_raw, 0, JSTYPE_VOID, JSDOCSTR("value [,value]")
,JSDOCSTR("Send a stream of bytes (possibly containing NUL or special control code sequences) to the output stream")
,314
},
{"print", js_writeln, 0, JSTYPE_ALIAS },
{"writeln", js_writeln, 0, JSTYPE_VOID, JSDOCSTR("value [,value]")
,JSDOCSTR("Send a line of text to the output stream with automatic line termination (CRLF), "
"<i>values</i> are typically string constants or variables (AKA print)")
,311
},
{"printf", js_printf, 1, JSTYPE_STRING, JSDOCSTR("<i>string</i> format [,value][,value]")
,JSDOCSTR("Send a C-style formatted string of text to the output stream. See also the <tt>format()</tt> function.")
,310
},
{"alert", js_alert, 1, JSTYPE_VOID, JSDOCSTR("value")
,JSDOCSTR("Send an alert message (ala client-side JS) to the output stream")
,310
},
{"prompt", js_prompt, 1, JSTYPE_STRING, JSDOCSTR("[<i>string</i> text] [,<i>string</i> value] [,<i>number</i> k_mode=K_EDIT]")
,JSDOCSTR("Display a prompt (<tt>text</tt>) and return a string of user input (ala client-side JS) or <tt>null</tt> upon no-input<br>"
"<tt>value</tt> is an optional default string to be edited (used with the <tt>k_mode K_EDIT</tt> flag)<br>"
"See <tt>sbbsdefs.js</tt> for all valid <tt>K_</tt> (keyboard-input) mode flags.")
,310
},
{"confirm", js_confirm, 1, JSTYPE_BOOLEAN, JSDOCSTR("value")
,JSDOCSTR("Display a Yes/No prompt and return <tt>true</tt> or <tt>false</tt> "
"based on user's confirmation (ala client-side JS, <tt>true</tt> = yes)<br>"
"see also <tt>console.yesno()<tt>")
,310
},
{"deny", js_deny, 1, JSTYPE_BOOLEAN, JSDOCSTR("value")
,JSDOCSTR("Display a No/Yes prompt and returns <tt>true</tt> or <tt>false</tt> "
"based on user's denial (<tt>true</tt> = no)<br>"
"see also <tt>console.noyes()<tt>")
,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;
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return;
if(report==NULL) {
sbbs->lprintf(LOG_ERR,"!JavaScript: %s", 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);
sbbs->lprintf(log_level, "!JavaScript %s%s%s: %s",warning,file,line,message);
if(sbbs->online==ON_REMOTE)
sbbs->bprintf("!JavaScript %s%s%s: %s\r\n",warning,getfname(file),line,message);
JS_RESUMEREQUEST(cx, rc);
}
JSContext* sbbs_t::js_init(JSRuntime** runtime, JSObject** glob, const char* desc)
{
JSContext* js_cx;
if(startup->js.max_bytes==0) startup->js.max_bytes=JAVASCRIPT_MAX_BYTES;
lprintf(LOG_DEBUG,"JavaScript: Creating %s runtime: %u bytes"
,desc, startup->js.max_bytes);
if((*runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__))==NULL) {
lprintf(LOG_NOTICE, "Failed to created new JavaScript runtime");
return NULL;
}
if((js_cx = JS_NewContext(*runtime, JAVASCRIPT_CONTEXT_STACK))==NULL) {
lprintf(LOG_NOTICE, "Failed to create new JavaScript context");
return NULL;
}
JS_SetOptions(js_cx, startup->js.options);
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;
js_callback.events_supported = 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, server_host_name(), SOCKLIB_DESC /* system */
,&js_callback /* js */
,&startup->js
,client_socket == INVALID_SOCKET ? NULL : &client, client_socket, -1 /* client */
,&js_server_props /* server */
,glob
,mqtt
))
break;
rooted=true;
#ifdef BUILD_JSDOCS
js_CreateUifcObject(js_cx, *glob);
js_CreateConioObject(js_cx, *glob);
#endif
/* BBS Object */
if(js_CreateBbsObject(js_cx, *glob)==NULL)
break;
/* Console Object */
if(js_CreateConsoleObject(js_cx, *glob)==NULL)
break;
success=true;
} while(0);
if(!success) {
if(rooted)
JS_RemoveObjectRoot(js_cx, glob);
JS_ENDREQUEST(js_cx);
JS_DestroyContext(js_cx);
js_cx=NULL;
return NULL;
}
else
JS_ENDREQUEST(js_cx);
return js_cx;
}
void sbbs_t::js_cleanup(void)
{
/* Free Context */
if(js_cx!=NULL) {
lprintf(LOG_DEBUG,"JavaScript: Destroying context");
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,"JavaScript: Destroying runtime");
jsrt_Release(js_runtime);
js_runtime=NULL;
}
if(js_hotkey_cx!=NULL) {
lprintf(LOG_DEBUG,"JavaScript: Destroying HotKey context");
JS_BEGINREQUEST(js_hotkey_cx);
JS_RemoveObjectRoot(js_hotkey_cx, &js_hotkey_glob);
JS_ENDREQUEST(js_hotkey_cx);
JS_DestroyContext(js_hotkey_cx);
js_hotkey_cx=NULL;
}
if(js_hotkey_runtime!=NULL) {
lprintf(LOG_DEBUG,"JavaScript: Destroying HotKey runtime");
jsrt_Release(js_hotkey_runtime);
js_hotkey_runtime=NULL;
}
}
bool sbbs_t::js_create_user_objects(JSContext* cx, JSObject* glob)
{
bool result = false;
if(cx != NULL) {
JS_BEGINREQUEST(cx);
if(!js_CreateUserObjects(cx, glob, &cfg, &useron, &client, startup == NULL ? NULL :startup->web_file_vpath_prefix, subscan, mqtt))
lprintf(LOG_ERR,"!JavaScript ERROR creating user objects");
else
result = true;
JS_ENDREQUEST(cx);
}
return result;
}
extern "C" bool 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
,struct mqtt* mqtt
)
{
bool success=false;
if(node_cfg==NULL)
node_cfg=cfg;
/* Global Object */
if(!js_CreateGlobalObject(js_cx, node_cfg, methods, js_startup, glob))
return(false);
do {
/*
* NOTE: Where applicable, anything added here should also be added to
* the same function in jsdoor.c (ie: anything not Synchronet
* specific).
*/
/* System Object */
if(js_CreateSystemObject(js_cx, *glob, node_cfg, uptime, host_name, socklib_desc, mqtt)==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)==NULL)
break;
/* FileBase Class */
if(js_CreateFileBaseClass(js_cx, *glob)==NULL)
break;
/* File Class */
if(js_CreateFileClass(js_cx, *glob)==NULL)
break;
/* Archive Class */
if(js_CreateArchiveClass(js_cx, *glob)==NULL)
break;
/* User class */
if(js_CreateUserClass(js_cx, *glob)==NULL)
break;
/* COM Class */
if(js_CreateCOMClass(js_cx, *glob)==NULL)
break;
/* CryptContext Class */
if(js_CreateCryptContextClass(js_cx, *glob)==NULL)
break;
/* CryptKeyset Class */
if(js_CreateCryptKeysetClass(js_cx, *glob)==NULL)
break;
/* CryptCert Class */
if(js_CreateCryptCertClass(js_cx, *glob)==NULL)
break;
#if defined USE_MOSQUITTO
if(js_CreateMQTTClass(js_cx, *glob) == NULL)
break;
#endif
/* Area Objects */
if(!js_CreateUserObjects(js_cx, *glob, cfg, /* user: */NULL, client, startup == NULL ? NULL :startup->web_file_vpath_prefix, /* subscan: */NULL, mqtt))
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;
outlen=0;
if(inlen<1) {
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) {
if(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);
}
}
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];
else {
lprintf(LOG_WARNING, "Node %d telnet command (%d, %d) buffer limit reached (%u bytes)"
,sbbs->cfg.node_num, sbbs->telnet_cmd[1], sbbs->telnet_cmd[2], sbbs->telnet_cmdlen);
sbbs->telnet_cmdlen = 0;
}
uchar command = sbbs->telnet_cmd[1];
uchar option = sbbs->telnet_cmd[2];
if(sbbs->telnet_cmdlen == 2 && command == TELNET_SE) {
lprintf(LOG_WARNING, "Node %d unexpected telnet sub-negotiation END command"
,sbbs->cfg.node_num);
sbbs->telnet_cmdlen = 0;
}
else if(sbbs->telnet_cmdlen>=2 && command==TELNET_SB) {
if(inbuf[i]==TELNET_SE
&& sbbs->telnet_cmd[sbbs->telnet_cmdlen-2]==TELNET_IAC) {
sbbs->telnet_cmds_received++;
if(startup->options&BBS_OPT_DEBUG_TELNET)
lprintf(LOG_DEBUG,"Node %d %s telnet sub-negotiation command: %s (%u bytes)"
,sbbs->cfg.node_num
,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
,telnet_opt_desc(option)
,sbbs->telnet_cmdlen);
/* sub-option terminated */
if(option==TELNET_TERM_TYPE
&& sbbs->telnet_cmd[3]==TELNET_TERM_IS) {
safe_snprintf(sbbs->telnet_terminal,sizeof(sbbs->telnet_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->telnet_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->telnet_speed=atoi(speed);
#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_TERM_LOCATION_NUMBER && sbbs->telnet_cmd[3] == 0) {
SAFEPRINTF4(sbbs->telnet_location, "%u.%u.%u.%u"
,sbbs->telnet_cmd[4]
,sbbs->telnet_cmd[5]
,sbbs->telnet_cmd[6]
,sbbs->telnet_cmd[7]
);
lprintf(LOG_DEBUG,"Node %d %s telnet location number (IP address): %s"
,sbbs->cfg.node_num
,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
,sbbs->telnet_location);
} else if(option==TELNET_NEGOTIATE_WINDOW_SIZE) {
int cols = (sbbs->telnet_cmd[3]<<8) | sbbs->telnet_cmd[4];
int rows = (sbbs->telnet_cmd[5]<<8) | sbbs->telnet_cmd[6];
lprintf(LOG_DEBUG,"Node %d %s telnet window size: %dx%d"
,sbbs->cfg.node_num
,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
,cols
,rows);
sbbs->telnet_cols = cols;
sbbs->telnet_rows = rows;
} 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;
}
} // Sub-negotiation command
else if(sbbs->telnet_cmdlen==2 && inbuf[i]<TELNET_WILL) {
sbbs->telnet_cmds_received++;
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 */
sbbs->telnet_cmds_received++;
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:
case TELNET_TERM_LOCATION_NUMBER:
#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 commands
outbuf[outlen++]=inbuf[i];
} else
outbuf[outlen++]=inbuf[i];
}
return(outbuf);
}
void sbbs_t::send_telnet_cmd(uchar cmd, uchar opt)
{
char buf[16];
int sz;
int result;
if(telnet_mode&TELNET_MODE_OFF)
return;
if(cmd<TELNET_WILL) {
if(startup->options&BBS_OPT_DEBUG_TELNET)
lprintf(LOG_DEBUG,"sending telnet cmd: %s"
,telnet_cmd_desc(cmd));
sprintf(buf,"%c%c",TELNET_IAC,cmd);
result = sendsocket(client_socket, buf, sz = 2);
} else {
if(startup->options&BBS_OPT_DEBUG_TELNET)
lprintf(LOG_DEBUG,"sending telnet cmd: %s %s"
,telnet_cmd_desc(cmd)
,telnet_opt_desc(opt));
sprintf(buf,"%c%c%c",TELNET_IAC,cmd,opt);
result = sendsocket(client_socket, buf, sz = 3);
}
if(result != sz)
lprintf(LOG_DEBUG, "ERROR sending telnet command (%s): send returned %d instead of %d"
,telnet_cmd_desc(cmd)
,result
,sz);
}
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;
}
static bool channel_open(sbbs_t *sbbs, int channel)
{
int rval;
if (cryptStatusError(cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, sbbs->session_channel)))
return false;
if (cryptStatusError(cryptGetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_OPEN, &rval)))
return false;
return rval;
}
static int crypt_pop_channel_data(sbbs_t *sbbs, char *inbuf, int want, int *got)
{
int status;
int cid;
char *cname = nullptr;
char *ssname = nullptr;
int ret;
int closing_channel = -1;
int tgot;
*got=0;
while(sbbs->online && sbbs->client_socket!=INVALID_SOCKET
&& node_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) {
ret = cryptPopData(sbbs->ssh_session, inbuf, want, &tgot);
if (ret == CRYPT_OK) {
status = cryptGetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, &cid);
if (status == CRYPT_OK) {
if (cid == closing_channel)
continue;
if (cid == sbbs->sftp_channel) {
pthread_mutex_unlock(&sbbs->ssh_mutex);
if (!sftps_recv(sbbs->sftp_state, reinterpret_cast<uint8_t *>(inbuf), tgot))
sbbs->sftp_end();
pthread_mutex_lock(&sbbs->ssh_mutex);
}
else if (cid == sbbs->session_channel) {
*got = tgot;
}
else {
if (cryptStatusError(status = cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, cid))) {
sbbs->log_crypt_error_status_sock(status, "setting channel");
return status;
}
cname = get_crypt_attribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_TYPE);
if (strcmp(cname, "subsystem") == 0) {
ssname = get_crypt_attribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ARG1);
}
if (((startup->options & (BBS_OPT_ALLOW_SFTP | BBS_OPT_SSH_ANYAUTH)) == BBS_OPT_ALLOW_SFTP) && ssname && cname && sbbs->sftp_channel == -1 && strcmp(ssname, "sftp") == 0) {
if (sbbs->init_sftp(cid)) {
if (tgot > 0) {
pthread_mutex_lock(&sbbs->ssh_mutex);
if (!sftps_recv(sbbs->sftp_state, reinterpret_cast<uint8_t *>(inbuf), tgot))
sbbs->sftp_end();
pthread_mutex_lock(&sbbs->ssh_mutex);
}
sbbs->sftp_channel = cid;
}
}
if (cname && sbbs->session_channel == -1 && strcmp(cname, "shell") == 0) {
sbbs->session_channel = cid;
}
if (cid != sbbs->sftp_channel && cid != sbbs->session_channel) {
if (cname == nullptr || strcmp(cname, "session") != 0) {
lprintf(LOG_WARNING, "Node %d SSH WARNING: attempt to use channel '%s' (%d != %d or %d)"
, sbbs->cfg.node_num, cname ? cname : "<unknown>", cid, sbbs->session_channel, sbbs->sftp_channel);
if (cname) {
free_crypt_attrstr(cname);
cname = nullptr;
}
if (ssname) {
free_crypt_attrstr(ssname);
ssname = nullptr;
}
closing_channel = cid;
if (cryptStatusError(status = cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0))) {
sbbs->log_crypt_error_status_sock(status, "closing channel");
return status;
}
}
}
free_crypt_attrstr(cname);
cname = nullptr;
free_crypt_attrstr(ssname);
ssname = nullptr;
continue;
}
if (cid != -1) {
if (cryptStatusOK(cryptGetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_OPEN, &ret))) {
if (!ret) {
closing_channel = -1;
ret = CRYPT_ERROR_NOTFOUND;
}
}
}
}
else {
/*
* CRYPT_ERROR_NOTFOUND indicates this is the last data on the channel (whatever it was)
* and it was destroyed, so it's no longer possible to get the channel id.
*/
bool closed {false};
if (status != CRYPT_ERROR_NOTFOUND)
sbbs->log_crypt_error_status_sock(status, "getting channel id");
closing_channel = -1;
if (sbbs->sftp_channel != -1) {
if (!channel_open(sbbs, sbbs->sftp_channel)) {
if (cryptStatusOK(cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, sbbs->sftp_channel)))
cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0);
sbbs->sftp_channel = -1;
closed = true;
}
}
if (sbbs->session_channel != -1) {
if (!channel_open(sbbs, sbbs->session_channel)) {
if (cryptStatusOK(cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, sbbs->session_channel)))
cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0);
sbbs->session_channel = -1;
closed = true;
}
}
// All channels are now closed.
if (closed && sbbs->sftp_channel == -1 && sbbs->session_channel == -1)
return CRYPT_ERROR_COMPLETE;
}
}
if (ret == CRYPT_ENVELOPE_RESOURCE)
return CRYPT_ERROR_TIMEOUT;
return ret;
}
return CRYPT_ERROR_TIMEOUT;
}
void input_thread(void *arg)
{
BYTE inbuf[4000];
BYTE telbuf[sizeof(inbuf)];
BYTE *wrbuf;
int i,rd,wr,avail;
uint total_recv=0;
uint total_pkts=0;
sbbs_t* sbbs = (sbbs_t*) arg;
SOCKET sock;
#ifdef PREFER_POLL
struct pollfd fds[2];
int nfds;
#endif
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(sbbs->max_socket_inactivity && !(sbbs->console & CON_NO_INACT)) {
if(sbbs->cfg.inactivity_warn
&& sbbs->socket_inactive >= sbbs->max_socket_inactivity * (sbbs->cfg.inactivity_warn / 100.0)
&& !sbbs->socket_inactivity_warning_sent) {
sbbs->bputs(text[InactivityAlert]);
sbbs->socket_inactivity_warning_sent = true;
}
if(sbbs->socket_inactive > sbbs->max_socket_inactivity) {
lprintf(LOG_NOTICE, "Node %d maximum socket inactivity exceeded: %u seconds"
,sbbs->cfg.node_num, sbbs->max_socket_inactivity);
sbbs->bputs(text[CallBackWhenYoureThere]);
break;
}
}
#ifdef _WIN32 // No spy sockets
if (!socket_readable(sbbs->client_socket, 1000)) {
++sbbs->socket_inactive;
continue;
}
#else
#ifdef PREFER_POLL
fds[0].fd = sbbs->client_socket;
fds[0].events = POLLIN;
nfds = 1;
if (uspy_socket[sbbs->cfg.node_num-1] != INVALID_SOCKET) {
fds[1].fd = uspy_socket[sbbs->cfg.node_num-1];
fds[1].events = POLLIN;
nfds++;
}
if (poll(fds, nfds, 1000) < 1) {
++sbbs->socket_inactive;
continue;
}
#else
#error Spy sockets without poll() was removed in commit 3971ef4dcc3db19f400a648b6110718e56a64cf3
#endif
#endif
if(sbbs->client_socket==INVALID_SOCKET)
break;
sbbs->socket_inactive = 0;
sbbs->socket_inactivity_warning_sent = false;
/* ^ ^
* \______ ______/
* \ * \ / * /
* ----- ------ /----\
* || -< Boo! |
* /__\ \----/
* \______________/
* \/\/\/\/\/\/\/
* ------------
*/
#ifdef _WIN32 // No spy sockets
sock=sbbs->client_socket;
#else
#ifdef PREFER_POLL
if (fds[0].revents & POLLIN)
sock = sbbs->client_socket;
else if(uspy_socket[sbbs->cfg.node_num - 1] != INVALID_SOCKET && fds[1].revents & POLLIN) {
if(socket_recvdone(uspy_socket[sbbs->cfg.node_num-1], 0)) {
close_socket(uspy_socket[sbbs->cfg.node_num-1]);
lprintf(LOG_NOTICE,"Closing local spy socket: %d",uspy_socket[sbbs->cfg.node_num-1]);
uspy_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET;
continue;
}
sock=uspy_socket[sbbs->cfg.node_num-1];
}
else {
continue;
}
#else
#error Spy sockets without poll() was removed in commit 3971ef4dcc3db19f400a648b6110718e56a64cf3
#endif
#endif
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 */
continue;
}
if(rd > (int)sizeof(inbuf))
rd=sizeof(inbuf);
if(pthread_mutex_lock(&sbbs->input_thread_mutex)!=0)
sbbs->errormsg(WHERE,ERR_LOCK,"input_thread_mutex",0);
#ifdef USE_CRYPTLIB
if(sbbs->ssh_mode && sock==sbbs->client_socket) {
int err;
if (WaitForEvent(sbbs->ssh_active, 1000) == WAIT_TIMEOUT) {
pthread_mutex_unlock(&sbbs->input_thread_mutex);
continue;
}
pthread_mutex_lock(&sbbs->ssh_mutex);
if(cryptStatusError((err=crypt_pop_channel_data(sbbs, (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_COMPLETE)
break;
if(err==CRYPT_ERROR_TIMEOUT)
continue;
/* Handle the SSH error here... */
GCES(err, sbbs->cfg.node_num, sbbs->ssh_session, "popping data");
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 == 0 && !socket_recvdone(sock, 0))
continue;
if(rd == SOCKET_ERROR)
{
#ifdef __unix__
if(sock==sbbs->client_socket) {
#endif
if(!sbbs->online) // sbbs_t::hangup() called?
break;
if(ERROR_VALUE == EAGAIN)
continue;
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 (%s) on local spy socket %d receive"
, sbbs->cfg.node_num, errno, strerror(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,(int)sizeof(telbuf));
wr = sizeof(telbuf);
}
if(!(sbbs->console & CON_RAW_IN))
sbbs->translate_input(wrbuf, wr);
if(sbbs->passthru_socket_active == true) {
bool writable = false;
if(socket_check(sbbs->passthru_socket, NULL, &writable, 1000) && writable) {
if(sendsocket(sbbs->passthru_socket, (char*)wrbuf, wr) != wr)
lprintf(LOG_ERR, "Node %d ERROR %d writing to passthru socket"
,sbbs->cfg.node_num, ERROR_VALUE);
} else
lprintf(LOG_WARNING, "Node %d could not write to passthru socket (writable=%d)"
, sbbs->cfg.node_num, (int)writable);
continue;
}
/* 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 %u bytes in input buffer"
,sbbs->cfg.node_num,RingBufFull(&sbbs->inbuf));
if(RingBufFull(&sbbs->outbuf))
lprintf(LOG_DEBUG,"Node %d Ctrl-C hit with %u 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 */
SetEvent(sbbs->inbuf.data_event);
SetEvent(sbbs->inbuf.highwater_event);
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 */
SetEvent(sbbs->inbuf.data_event); // terminate incom() wait
sbbs->input_thread_running = false;
sbbs->terminate_output_thread = true;
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 %u bytes in %u blocks)"
,sbbs->cfg.node_num, total_recv, total_pkts);
}
// Flush the duplicate client_socket when activating the passthru socket
// to eliminate any stale data from the previous passthru session
void sbbs_t::passthru_socket_activate(bool activate)
{
time_t timeout = time(NULL) + 60;
if(activate) {
bool rd = false;
while(socket_check(client_socket_dup, &rd, /* wr_p */NULL, /* timeout */0) && rd && time(NULL) < timeout) {
char ch;
if(recv(client_socket_dup, &ch, sizeof(ch), /* flags: */0) != sizeof(ch))
break;
}
} else {
/* Re-enable blocking (in case disabled by external program) */
u_long l=0;
ioctlsocket(client_socket_dup, FIONBIO, &l);
/* Re-set socket options */
char err[512];
if(set_socket_options(&cfg, client_socket_dup, "passthru", err, sizeof(err)))
lprintf(LOG_ERR,"%04d !ERROR %s setting passthru socket options", client_socket, err);
do { // Allow time for the passthru_thread to move any pending socket data to the outbuf
SLEEP(100); // Before the node_thread starts sending its own data to the outbuf
} while(RingBufFull(&outbuf) && time(NULL) < timeout);
}
passthru_socket_active = activate;
}
/*
* This thread simply copies anything it manages to read from the
* passthru_socket into the output ringbuffer.
*/
void passthru_thread(void* arg)
{
sbbs_t *sbbs = (sbbs_t*) arg;
int rd;
char inbuf[IO_THREAD_BUF_SIZE / 2];
SetThreadName("sbbs/passthru");
thread_up(false /* setuid */);
while(sbbs->online && sbbs->passthru_socket!=INVALID_SOCKET && !terminate_server) {
if (!socket_readable(sbbs->passthru_socket, 1000))
continue;
if(sbbs->passthru_socket==INVALID_SOCKET)
break;
rd = RingBufFree(&sbbs->outbuf) / 2;
if(rd > (int)sizeof(inbuf))
rd = sizeof(inbuf);
rd = recv(sbbs->passthru_socket, inbuf, rd, 0);
if(rd == 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(rd == 0)
{
char ch;
if(recv(sbbs->passthru_socket, &ch, sizeof(ch), MSG_PEEK) == 0) {
lprintf(sbbs->online ? LOG_WARNING : LOG_DEBUG
,"Node %d passthru socket disconnected", sbbs->cfg.node_num);
break;
}
YIELD();
continue;
}
if(sbbs->xtrn_mode & EX_BIN) {
BYTE telnet_buf[sizeof(inbuf) * 2];
BYTE* bp = (BYTE*)inbuf;
if(!(sbbs->telnet_mode&TELNET_MODE_OFF))
rd = telnet_expand((BYTE*)inbuf, rd, telnet_buf, sizeof(telnet_buf), /* expand_cr */false, &bp);
int wr = RingBufWrite(&sbbs->outbuf, bp, rd);
if(wr != rd) {
lprintf(LOG_ERR,"Short-write (%d of %d bytes) from passthru socket to outbuf"
,wr, rd);
break;
}
} else {
sbbs->rputs(inbuf, rd);
}
}
if(sbbs->passthru_socket!=INVALID_SOCKET) {
close_socket(sbbs->passthru_socket);
sbbs->passthru_socket=INVALID_SOCKET;
}
thread_down();
sbbs->passthru_thread_running = false;
sbbs->passthru_socket_active = false;
}
void output_thread(void* arg)
{
char node[128];
char stats[128];
char spy_topic[128];
BYTE buf[IO_THREAD_BUF_SIZE];
int i=0; // Assignment to silence Valgrind
uint avail;
uint total_sent=0;
uint total_pkts=0;
uint short_sends=0;
uint bufbot=0;
uint buftop=0;
sbbs_t* sbbs = (sbbs_t*) arg;
uint mss=IO_THREAD_BUF_SIZE;
uint ssh_errors = 0;
SetThreadName("sbbs/termOutput");
thread_up(true /* setuid */);
if(sbbs->cfg.node_num) {
SAFEPRINTF(node,"Node %d",sbbs->cfg.node_num);
SAFEPRINTF(spy_topic, "node/%d/output", 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)
*/
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) {
#ifdef _WIN32
#ifdef TCP_TIMESTAMPS
DWORD ts;
sl = sizeof(ts);
if (!getsockopt(sbbs->client_socket, IPPROTO_TCP, TCP_TIMESTAMPS, (char *)&ts, &sl)) {
if (ts)
i -= 12;
}
#endif
#else
#if (defined(TCP_INFO) && defined(TCPI_OPT_TIMESTAMPS))
struct tcp_info tcpi;
sl = sizeof(tcpi);
if (!getsockopt(sbbs->client_socket, IPPROTO_TCP, TCP_INFO,&tcpi, &sl)) {
if (tcpi.tcpi_options & TCPI_OPT_TIMESTAMPS)
i -= 12;
}
#endif
#endif
if(i>IO_THREAD_BUF_SIZE)
i=IO_THREAD_BUF_SIZE;
sbbs->outbuf.highwater_mark=i;
mss=sbbs->outbuf.highwater_mark;
lprintf(LOG_DEBUG, "%s outbuf highwater mark tuned to %d based on MSS", node, sbbs->outbuf.highwater_mark);
}
}
#endif
sbbs->terminate_output_thread = false;
/* Note: do not terminate when online==false, that is expected for the terminal server output_thread */
while (sbbs->client_socket != INVALID_SOCKET && !terminate_server && !sbbs->terminate_output_thread) {
/*
* 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
* probably be even clumsier
*/
if(bufbot == buftop) {
/* Wait for something to output in the RingBuffer */
if (WaitForEvent(sbbs->outbuf.data_event, 1000) != WAIT_OBJECT_0)
continue;
/* Wait for full buffer or drain timeout */
if(sbbs->outbuf.highwater_mark)
WaitForEvent(sbbs->outbuf.highwater_event, startup->outbuf_drain_timeout);
/* Get fill level */
avail = RingBufFull(&sbbs->outbuf);
// If flushing or terminating, there will be nothing available
if (avail == 0) {
// Reset data/highwater events
RingBufRead(&sbbs->outbuf, NULL, 0);
continue;
}
/*
* 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 (%u > %d)"
,node, avail, (int)sizeof(buf));
avail = sizeof(buf);
}
/* If we know the MSS, use it as the max linear buffer size. */
if (avail > mss)
avail = mss;
buftop = RingBufRead(&sbbs->outbuf, buf, avail);
bufbot = 0;
if (buftop == 0)
continue;
}
/* Check socket for writability */
if (!socket_writable(sbbs->client_socket, 1)) {
continue;
}
#ifdef USE_CRYPTLIB
if(sbbs->ssh_mode) {
int err;
pthread_mutex_lock(&sbbs->ssh_mutex);
if(sbbs->terminate_output_thread) {
pthread_mutex_unlock(&sbbs->ssh_mutex);
break;
}
if(!sbbs->ssh_mode) {
pthread_mutex_unlock(&sbbs->ssh_mutex);
continue;
}
if (sbbs->session_channel == -1) {
i=buftop-bufbot; // Pretend we sent it all
}
else {
if (cryptStatusError((err=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, sbbs->session_channel)))) {
GCESSTR(err, node, sbbs->ssh_session, "setting channel");
ssh_errors++;
sbbs->online=false;
i=buftop-bufbot; // Pretend we sent it all
}
else {
/*
* Limit as per js_socket.c.
* Sure, this is TLS, not SSH, but we see weird stuff here in sz file transfers.
*/
size_t sendbytes = buftop-bufbot;
if (sendbytes > 0x2000)
sendbytes = 0x2000;
if(cryptStatusError((err=cryptPushData(sbbs->ssh_session, (char*)buf+bufbot, buftop-bufbot, &i)))) {
/* Handle the SSH error here... */
GCESSTR(err, node, sbbs->ssh_session, "pushing data");
ssh_errors++;
sbbs->online=false;
i=buftop-bufbot; // Pretend we sent it all
}
else {
// READ = WRITE TIMEOUT HACK... REMOVE WHEN FIXED
/* This sets the write timeout for the flush, then sets it to zero
* afterward... presumably because the read timeout gets set to
* what the current write timeout is.
*/
if(cryptStatusError(err=cryptSetAttribute(sbbs->ssh_session, CRYPT_OPTION_NET_WRITETIMEOUT, 5)))
GCESSTR(err, node, sbbs->ssh_session, "setting write timeout");
if(cryptStatusError((err=cryptFlushData(sbbs->ssh_session)))) {
GCESSTR(err, node, sbbs->ssh_session, "flushing data");
ssh_errors++;
if (err != CRYPT_ERROR_TIMEOUT) {
sbbs->online=false;
i=buftop-bufbot; // Pretend we sent it all
}
}
// READ = WRITE TIMEOUT HACK... REMOVE WHEN FIXED
if(cryptStatusError(err=cryptSetAttribute(sbbs->ssh_session, CRYPT_OPTION_NET_WRITETIMEOUT, 0)))
GCESSTR(err, node, sbbs->ssh_session, "setting write timeout");
}
}
}
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) {
int result = RingBufWrite(startup->node_spybuf[sbbs->cfg.node_num-1],buf+bufbot,i);
if(result != i)
lprintf(LOG_WARNING, "%s SHORT WRITE to spy ring buffer (%u < %u)"
,node, result, i);
}
/* Spy on the user remotely */
if(sbbs->cfg.mqtt.enabled && mqtt.handle != NULL) {
int result = mqtt_pub_message(&mqtt, TOPIC_BBS, spy_topic, buf+bufbot, i, /* retain: */false);
if(result != MQTT_SUCCESS)
lprintf(LOG_WARNING, "%s ERROR %d (%d) publishing node output (%u bytes): %s"
,node, result, errno, i, spy_topic);
}
if(spy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET)
if(sendsocket(spy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i) != i && ERROR_VALUE != EPIPE)
lprintf(LOG_ERR, "%s ERROR %d writing to spy socket", node, ERROR_VALUE);
#ifdef __unix__
if(uspy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET)
if(sendsocket(uspy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i) != i)
lprintf(LOG_ERR, "%s ERROR %d writing to UNIX spy socket", node, ERROR_VALUE);
#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 %u bytes in %u blocks, %u average, %u 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);
}
static bool is_day_to_run(const struct tm* now, uint8_t days, uint16_t months = 0, uint32_t mdays = 0)
{
if(!(days & (1 << now->tm_wday)))
return false;
if((mdays > 1) && !(mdays & (1 << now->tm_mday)))
return false;
if((months != 0) && !(months & (1 << now->tm_mon)))
return false;
return true;
}
static bool is_time_to_run(time_t now, const struct tm* now_tm, time_t last, uint16_t freq, uint16_t time)
{
if(freq != 0) {
if((now - last) / 60 < freq)
return false;
} else {
struct tm last_tm = {};
localtime_r(&last, &last_tm);
if(now_tm->tm_mday == last_tm.tm_mday && now_tm->tm_mon == last_tm.tm_mon) // Already ran today
return false;
if(((now_tm->tm_hour * 60) + now_tm->tm_min) < time)
return false;
}
return true;
}
static bool is_time_to_run(time_t now, const event_t* event)
{
if(event->last == -1) // Always Run after init/re-init or signaled via semaphore
return true;
struct tm now_tm = {};
localtime_r(&now, &now_tm);
if(!is_day_to_run(&now_tm, event->days, event->months, event->mdays))
return false;
if(!is_time_to_run(now, &now_tm, event->last, event->freq, event->time))
return false;
return true;
}
static bool is_time_to_run(time_t now, const qhub_t* hub)
{
if(hub->last == -1) // Always Run when signaled via semaphore
return true;
struct tm now_tm = {};
localtime_r(&now, &now_tm);
if(!is_day_to_run(&now_tm, hub->days))
return false;
if(!is_time_to_run(now, &now_tm, hub->last, hub->freq, hub->time))
return false;
return true;
}
void event_thread(void* arg)
{
char str[MAX_PATH+1];
char semfile[MAX_PATH+1];
int i,j,k;
int file;
int offset;
bool check_semaphores;
bool packed_rep;
uint 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;
node_t node;
glob_t g;
sbbs_t* sbbs = (sbbs_t*) arg;
char event_code[LEN_CODE+1];
sbbs->lprintf(LOG_INFO,"BBS Events thread started");
sbbs_srand(); /* Seed random number generator */
SetThreadName("sbbs/events");
thread_up(true /* setuid */);
mqtt_pub_strval(&mqtt, TOPIC_HOST, "event", "thread started");
#ifdef JAVASCRIPT
if(!(startup->options&BBS_OPT_NO_JAVASCRIPT)) {
if((sbbs->js_cx = sbbs->js_init(&sbbs->js_runtime, &sbbs->js_glob, "event")) == NULL) /* This must be done in the context of the events thread */
sbbs->lprintf(LOG_CRIT,"!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)<(int)(sizeof(time32_t)*(i+1))) {
sbbs->lprintf(LOG_WARNING,"Initializing last run time for event: %s"
,sbbs->cfg.event[i]->code);
if(write(file,&sbbs->cfg.event[i]->last,sizeof(sbbs->cfg.event[i]->last)) != sizeof sbbs->cfg.event[i]->last)
sbbs->errormsg(WHERE, ERR_WRITE, str, 4);
} 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;
}
if(read(file,&lastprepack,sizeof(lastprepack)) != sizeof lastprepack) /* expected to fail first time */
lastprepack = 0;
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)<(int)(sizeof(time32_t)*(i+1))) {
sbbs->lprintf(LOG_WARNING,"Initializing last call-out time for QWKnet hub: %s"
,sbbs->cfg.qhub[i]->id);
if(write(file,&sbbs->cfg.qhub[i]->last,sizeof(sbbs->cfg.qhub[i]->last)) != sizeof sbbs->cfg.qhub[i]->last)
sbbs->errormsg(WHERE, ERR_WRITE, str, 4);
} 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);
}
while(!sbbs->terminated && !terminate_server) {
if(startup->options&BBS_OPT_NO_EVENTS) {
SLEEP(1000);
continue;
}
now=time(NULL);
if(now-lastsemchk >= startup->sem_chk_freq) {
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)) {
sbbs->event_code = "unpackREP";
/* 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++) {
if(flength(g.gl_pathv[i]) < 1)
continue;
sbbs->useron.number = 0;
sbbs->lprintf(LOG_DEBUG, "Inbound QWK Reply Packet detected: %s", g.gl_pathv[i]);
sbbs->useron.number = atoi(g.gl_pathv[i]+offset);
getuserdat(&sbbs->cfg,&sbbs->useron);
if(sbbs->useron.number != 0 && !(sbbs->useron.misc&(DELETED|INACTIVE))) {
SAFEPRINTF(semfile,"%s.lock",g.gl_pathv[i]);
if(!fmutex(semfile,startup->host_name,TIMEOUT_MUTEX_FILE)) {
sbbs->lprintf(LOG_INFO," %s exists (unpack in progress?)", semfile);
continue;
}
sbbs->online=ON_LOCAL;
sbbs->console|=CON_L_ECHO;
sbbs->getusrsubs();
bool success = sbbs->unpack_rep(g.gl_pathv[i]);
sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); /* clean-up temp_dir after unpacking */
sbbs->online=false;
sbbs->console&=~CON_L_ECHO;
/* putuserdat? */
if(success) {
if(remove(g.gl_pathv[i]))
sbbs->errormsg(WHERE, ERR_REMOVE, g.gl_pathv[i], 0);
} else {
char badpkt[MAX_PATH+1];
SAFEPRINTF2(badpkt, "%s.%" PRIx64 ".bad", g.gl_pathv[i], (uint64_t)time(NULL));
(void)remove(badpkt);
if(rename(g.gl_pathv[i], badpkt) == 0)
sbbs->lprintf(LOG_NOTICE, "%s renamed to %s", g.gl_pathv[i], badpkt);
else
sbbs->lprintf(LOG_ERR, "!ERROR %d (%s) renaming %s to %s"
,errno, strerror(errno), g.gl_pathv[i], badpkt);
SAFEPRINTF(badpkt, "%u.rep.*.bad", sbbs->useron.number);
SAFEPRINTF(str,"%sfile/", sbbs->cfg.data_dir);
sbbs->delfiles(str, badpkt, /* keep: */10);
}
if(remove(semfile))
sbbs->errormsg(WHERE, ERR_REMOVE, semfile, 0);
}
else {
sbbs->lprintf(LOG_INFO, "Removing: %s", g.gl_pathv[i]);
if(remove(g.gl_pathv[i]))
sbbs->errormsg(WHERE, ERR_REMOVE, g.gl_pathv[i], 0);
}
}
globfree(&g);
sbbs->useron.number = 0;
/* Create any QWK files that have magically appeared (via FTP perhaps) */
sbbs->event_code = "packQWK";
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++) {
sbbs->useron.number = 0;
sbbs->lprintf(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)) {
sbbs->lprintf(LOG_INFO,"%s exists (pack in progress?)", semfile);
continue;
}
getuserdat(&sbbs->cfg,&sbbs->useron);
if(sbbs->useron.number != 0 && !(sbbs->useron.misc&(DELETED|INACTIVE))) {
sbbs->lprintf(LOG_INFO, "Packing QWK Message Packet");
sbbs->online=ON_LOCAL;
sbbs->console|=CON_L_ECHO;
sbbs->getmsgptrs();
sbbs->getusrsubs();
sbbs->last_ns_time=sbbs->ns_time=sbbs->useron.ns_time;
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 */)) {
sbbs->lprintf(LOG_INFO, "Packing completed: %s", str);
sbbs->qwk_success(l,0,1);
sbbs->putmsgptrs();
batch_list_clear(&sbbs->cfg, sbbs->useron.number, XFER_BATCH_DOWNLOAD);
} else
sbbs->lputs(LOG_INFO, "No packet created (no new messages)");
sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES);
sbbs->console&=~CON_L_ECHO;
sbbs->online=false;
}
if(fexist(g.gl_pathv[i]) && remove(g.gl_pathv[i]) != 0)
sbbs->errormsg(WHERE, ERR_REMOVE, g.gl_pathv[i], 0);
if(remove(semfile))
sbbs->errormsg(WHERE, ERR_REMOVE, semfile, 0);
}
globfree(&g);
sbbs->useron.number = 0;
/* Create (pre-pack) QWK files for users configured as such */
sbbs->event_code = "prepackQWK";
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);
sbbs->lputs(LOG_INFO,"Pre-packing QWK Message packets...");
int userfile = openuserdat(&sbbs->cfg, /* for_modify: */false);
for(i=1;i<=j;i++) {
sbbs->useron.number=i;
if(fgetuserdat(&sbbs->cfg,&sbbs->useron, userfile) != 0)
continue;
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;
sbbs->lprintf(LOG_INFO, "Pre-packing QWK");
sbbs->online=ON_LOCAL;
sbbs->console|=CON_L_ECHO;
sbbs->getmsgptrs();
sbbs->getusrsubs();
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();
batch_list_clear(&sbbs->cfg, sbbs->useron.number, XFER_BATCH_DOWNLOAD);
}
sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES);
sbbs->console&=~CON_L_ECHO;
sbbs->online=false;
}
}
closeuserdat(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,(int)sbbs->cfg.total_events*4L,SEEK_SET);
if(write(file,&lastprepack,sizeof(lastprepack)) != sizeof lastprepack)
sbbs->errormsg(WHERE, ERR_WRITE, str, sizeof lastprepack);
close(file);
remove(semfile);
}
sbbs->useron.number = 0;
}
if(check_semaphores) {
/* Run daily maintenance? */
sbbs->cfg.node_num=0;
if(!(startup->options&BBS_OPT_NO_NEWDAY_EVENTS)) {
sbbs->event_code = "";
sbbs->logonstats();
if(sbbs->sys_status&SS_DAILY)
sbbs->daily_maint();
}
/* Node Daily Events */
sbbs->event_code = "DAILY";
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;
SAFECOPY(sbbs->cfg.node_dir, sbbs->cfg.node_path[i-1]);
sbbs->lprintf(LOG_INFO,"Running node %d daily event",i);
sbbs->online=ON_LOCAL;
sbbs->console|=CON_L_ECHO;
sbbs->logentry("!:","Run node daily event");
const char* cmd = sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL);
int result = sbbs->external(cmd, EX_OFFLINE);
sbbs->lprintf(result ? LOG_ERR : LOG_INFO, "Node daily event: '%s' returned %d", cmd, result);
sbbs->console&=~CON_L_ECHO;
sbbs->online=false;
}
sbbs->getnodedat(i,&node,1);
node.misc&=~NODE_EVENT;
node.status=NODE_WFC;
node.useron=0;
sbbs->putnodedat(i,&node);
}
}
sbbs->event_code = nulstr;
/* QWK Networking Call-out semaphores */
for(i=0;i<sbbs->cfg.total_qhubs;i++) {
if(!sbbs->cfg.qhub[i]->enabled)
continue;
if(sbbs->cfg.qhub[i]->node != NODE_ANY
&& (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)) {
SAFECOPY(str,sbbs->cfg.qhub[i]->id);
sbbs->lprintf(LOG_INFO,"Semaphore signaled for QWK Network Hub: %s",strupr(str));
sbbs->cfg.qhub[i]->last=-1;
}
}
/* Timed Event semaphores */
for(i=0;i<sbbs->cfg.total_events;i++) {
if(sbbs->cfg.event[i]->node != NODE_ANY
&& (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)) {
SAFECOPY(str,sbbs->cfg.event[i]->code);
sbbs->lprintf(LOG_INFO,"Semaphore signaled for Timed Event: %s",strupr(str));
sbbs->cfg.event[i]->last=-1;
}
}
}
/* QWK Networking Call-out Events */
sbbs->event_code = "QNET";
for(i=0;i<sbbs->cfg.total_qhubs && !sbbs->terminated;i++) {
if(!sbbs->cfg.qhub[i]->enabled)
continue;
if(sbbs->cfg.qhub[i]->node != NODE_ANY
&& (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 */
sbbs->lprintf(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.%x.bad",str,(int)now);
remove(newname);
if(rename(str,newname)==0)
sbbs->lprintf(LOG_NOTICE, "%s renamed to %s", str, newname);
else
sbbs->lprintf(LOG_ERR, "!ERROR %d (%s) renaming %s to %s"
,errno, strerror(errno), str, newname);
SAFEPRINTF(newname, "%s.q??.*.bad", sbbs->cfg.qhub[i]->id);
sbbs->delfiles(sbbs->cfg.data_dir, newname, /* keep: */10);
}
sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES);
sbbs->console&=~CON_L_ECHO;
sbbs->online=false;
if(fexist(str) && remove(str))
sbbs->errormsg(WHERE, ERR_REMOVE, str, 0);
}
}
globfree(&g);
}
/* Qnet call out based on time */
if(is_time_to_run(now, sbbs->cfg.qhub[i])) {
SAFEPRINTF2(str,"%sqnet/%s.now"
,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id);
if(fexistcase(str)) {
if(remove(str)) /* Remove semaphore file */
sbbs->errormsg(WHERE, ERR_REMOVE, str, 0);
}
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]->subnum].ptr=0;
if(file!=-1) {
lseek(file,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]->subnum]->ptridx*sizeof(int32_t),SEEK_SET);
if(read(file,&sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]->subnum].ptr
,sizeof(sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]->subnum].ptr)) != sizeof(uint32_t))
sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]->subnum].ptr = 0;
}
}
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]->subnum]->ptridx*4L) {
l32=l;
if(write(file,&l32,4) != 4) /* initialize ptrs to null */
sbbs->errormsg(WHERE, ERR_WRITE, str, 4);
}
lseek(file
,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]->subnum]->ptridx*sizeof(int32_t)
,SEEK_SET);
if(write(file,&sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]->subnum].ptr,sizeof(sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]->subnum].ptr)) != sizeof sbbs->subscan[sbbs->cfg.qhub[i]->sub[j]->subnum].ptr)
sbbs->errormsg(WHERE, ERR_WRITE, str, 4);
}
close(file);
}
}
sbbs->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);
if(write(file,&sbbs->cfg.qhub[i]->last,sizeof(sbbs->cfg.qhub[i]->last)) != sizeof sbbs->cfg.qhub[i]->last)
sbbs->errormsg(WHERE, ERR_WRITE, str, 4);
close(file);
if(sbbs->cfg.qhub[i]->call[0]) {
if(sbbs->cfg.qhub[i]->node == NODE_ANY)
sbbs->cfg.node_num = startup->first_node;
else
sbbs->cfg.node_num=sbbs->cfg.qhub[i]->node;
if(sbbs->cfg.node_num<1)
sbbs->cfg.node_num=1;
SAFECOPY(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]);
sbbs->lprintf(LOG_INFO,"Call-out: %s",sbbs->cfg.qhub[i]->id);
sbbs->online=ON_LOCAL;
sbbs->console|=CON_L_ECHO;
int ex_mode = EX_OFFLINE|EX_SH; /* sh for Unix perl scripts */
if(sbbs->cfg.qhub[i]->misc & QHUB_NATIVE)
ex_mode |= EX_NATIVE;
int result = sbbs->external(
sbbs->cmdstr(sbbs->cfg.qhub[i]->call
,sbbs->cfg.qhub[i]->id,sbbs->cfg.qhub[i]->id,NULL)
,ex_mode);
sbbs->lprintf(result ? LOG_ERR : LOG_INFO, "Call-out to: %s returned %d", sbbs->cfg.qhub[i]->id, result);
sbbs->console&=~CON_L_ECHO;
sbbs->online=false;
}
}
}
/* Timed Events */
for(i=0;i<sbbs->cfg.total_events && !sbbs->terminated;i++) {
if(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 != NODE_ANY
&& (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
SAFECOPY(event_code, sbbs->cfg.event[i]->code);
strupr(event_code);
sbbs->event_code = event_code;
if(is_time_to_run(now, sbbs->cfg.event[i]))
{
if(sbbs->cfg.event[i]->node != NODE_ANY && (sbbs->cfg.event[i]->misc&EVENT_EXCL)) { /* exclusive event */
if(sbbs->cfg.event[i]->node<first_node
|| sbbs->cfg.event[i]->node>last_node) {
sbbs->lprintf(LOG_INFO,"Waiting for node %d to run timed event: %s"
,sbbs->cfg.event[i]->node, event_code);
sbbs->lprintf(LOG_DEBUG,"event last run: %s (0x%08x)"
,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,(int)i*4L,SEEK_SET);
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, 4);
close(file);
if(now-sbbs->cfg.event[i]->last<(60*60)) /* event is done */
break;
if(now-start>(90*60)) {
sbbs->lprintf(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)) {
if(remove(str))
sbbs->errormsg(WHERE, ERR_REMOVE, str, 0);
}
sbbs->cfg.event[i]->last=(time32_t)now;
} else { // Exclusive event to run on a node under our control
sbbs->lprintf(LOG_INFO,"Waiting for all nodes to become inactive before running timed event");
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;
sbbs->lprintf(LOG_DEBUG,"Waiting for node %d (status=%d)"
,j, node.status);
if (now - start > (60 * 60) && node_socket[j - 1] != INVALID_SOCKET) {
sbbs->lprintf(LOG_WARNING, "!TIRED of waiting for node %d to become inactive (status=%d), closing socket %d"
,j, node.status, node_socket[j - 1]);
close_socket(node_socket[j - 1]);
node_socket[j - 1] = INVALID_SOCKET;
}
if(now-start>(90*60)) {
sbbs->lprintf(LOG_WARNING,"!TIMEOUT waiting for node %d to become inactive (status=%d), aborting wait"
,j, node.status);
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 != NODE_ANY
&& (sbbs->cfg.event[i]->node<first_node || sbbs->cfg.event[i]->node>last_node)) {
sbbs->lprintf(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 {
if(sbbs->cfg.event[i]->node == NODE_ANY)
sbbs->cfg.node_num = startup->first_node;
else
sbbs->cfg.node_num=sbbs->cfg.event[i]->node;
if(sbbs->cfg.node_num<1)
sbbs->cfg.node_num=1;
SAFECOPY(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)) {
if(remove(str))
sbbs->errormsg(WHERE, ERR_REMOVE, str, 0);
}
if(sbbs->cfg.event[i]->node != NODE_ANY && (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);
}
const char* cmd = sbbs->cfg.event[i]->cmd;
int ex_mode = EX_OFFLINE;
if(!(sbbs->cfg.event[i]->misc&EVENT_EXCL)
&& sbbs->cfg.event[i]->misc&EX_BG)
ex_mode |= EX_BG;
if(sbbs->cfg.event[i]->misc&XTRN_SH)
ex_mode |= EX_SH;
ex_mode|=(sbbs->cfg.event[i]->misc&EX_NATIVE);
sbbs->online=ON_LOCAL;
sbbs->console|=CON_L_ECHO;
sbbs->lprintf(LOG_INFO,"Running %s%stimed event: %s"
,native_executable(&sbbs->cfg, cmd, ex_mode) ? "native ":"16-bit DOS "
,(ex_mode&EX_BG) ? "background ":""
,event_code);
{
int result=
sbbs->external(
sbbs->cmdstr(cmd,nulstr,sbbs->cfg.event[i]->dir,NULL)
,ex_mode
,sbbs->cfg.event[i]->dir);
if(!(ex_mode&EX_BG))
sbbs->lprintf(result ? sbbs->cfg.event[i]->errlevel : LOG_INFO, "Timed event: %s returned %d", event_code, result);
}
sbbs->console&=~CON_L_ECHO;
sbbs->online=false;
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,(int)i*4L,SEEK_SET);
if(write(file,&sbbs->cfg.event[i]->last,sizeof(sbbs->cfg.event[i]->last)) != sizeof sbbs->cfg.event[i]->last)
sbbs->errormsg(WHERE, ERR_WRITE, str, 4);
close(file);
if(sbbs->cfg.event[i]->node != NODE_ANY
&& 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);
}
}
}
}
}
sbbs->event_code = nulstr;
mswait(1000);
}
sbbs->cfg.node_num=0;
sbbs->useron.number = 0;
sbbs->js_cleanup();
sbbs->event_thread_running = false;
mqtt_pub_strval(&mqtt, TOPIC_HOST, "event", "thread stopped");
thread_down();
sbbs->lprintf(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, bool is_event_thread)
{
char path[MAX_PATH+1];
uint i;
// Initialize some member variables used by lputs:
this->is_event_thread = is_event_thread;
cfg.node_num = node_num;
SAFECOPY(client_name, name);
lprintf(LOG_DEBUG,"constructor using socket %d (settings=%x)"
,sd, global_cfg->node_misc);
startup = ::startup; // Convert from global to class member
mqtt = &::mqtt;
memcpy(&cfg, global_cfg, sizeof(cfg));
cfg.node_num = node_num; // Restore the node_num passed to the constructor
if(node_num>0) {
SAFECOPY(cfg.node_dir, cfg.node_path[node_num-1]);
prep_dir(cfg.node_dir, cfg.temp_dir, sizeof(cfg.temp_dir));
SAFEPRINTF2(syspage_semfile, "%ssyspage.%u", cfg.ctrl_dir, node_num);
(void)remove(syspage_semfile);
} 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));
if((i = md(cfg.temp_dir)) != 0)
lprintf(LOG_CRIT,"!ERROR %d (%s) creating directory: %s", i, strerror(i), 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);
}
syspage_semfile[0] = 0;
}
lprintf(LOG_DEBUG, "temporary file directory: %s", cfg.temp_dir);
if(client_info != nullptr)
memcpy(&client,client_info,sizeof(client));
client_addr.store = addr->store;
client_socket = sd;
client_socket_dup=INVALID_SOCKET;
/* Init some important variables */
SAFECOPY(connection, "Telnet");
telnet_ack_event=CreateEvent(NULL, /* Manual Reset: */false,/* InitialState */false,NULL);
listInit(&savedlines, /* flags: */0);
listInit(&smb_list, /* flags: */0);
listInit(&mouse_hotspots, /* flags: */0);
pthread_mutex_init(&nodefile_mutex, NULL);
for(i=0;i<TOTAL_TEXT;i++)
text[i]=text_sav[i]=global_text[i];
}
//****************************************************************************
bool sbbs_t::init()
{
char str[MAX_PATH+1];
char tmp[128];
int result;
int i,j,k,l;
node_t node;
socklen_t addr_len;
union xp_sockaddr addr;
if(cfg.total_shells < 1) {
errormsg(WHERE, ERR_CHK, "At least one command shell must be configured (e.g. in SCFG->Command Shells)"
,cfg.total_shells);
return false;
}
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) {
addr_len=sizeof(addr);
if((result=getsockname(client_socket, &addr.addr, &addr_len))!=0) {
lprintf(LOG_CRIT,"%04d %s !ERROR %d (%d) getting local address/port of socket"
,client_socket, client.protocol, result, ERROR_VALUE);
return(false);
}
inet_addrtop(&addr, local_addr, sizeof(local_addr));
inet_addrtop(&client_addr, client_ipaddr, sizeof(client_ipaddr));
SAFEPRINTF(str, "%sclient.ini", cfg.node_dir);
FILE* fp = fopen(str, "wt");
if(fp != NULL) {
fprintf(fp, "sock=%d\n", client_socket);
fprintf(fp, "addr=%s\n", client.addr);
fprintf(fp, "host=%s\n", client.host);
fprintf(fp, "port=%u\n", (uint)client.port);
fprintf(fp, "time=%u\n", (uint)client.time);
fprintf(fp, "prot=%s\n", client.protocol);
fprintf(fp, "local_addr=%s\n", local_addr);
fprintf(fp, "local_port=%u\n", (uint)inet_addrport(&addr));
fclose(fp);
}
spymsg("Connected");
}
if((comspec=os_cmdshell())==NULL) {
errormsg(WHERE, ERR_CHK, OS_CMD_SHELL_ENV_VAR" environment variable", 0);
return(false);
}
if((i = md(cfg.temp_dir)) != 0) {
lprintf(LOG_CRIT,"!ERROR %d (%s) creating directory: %s", i, strerror(i), cfg.temp_dir);
return false;
}
/* 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)<(int)(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;
}
}
if(chsize(nodefile, (off_t)(cfg.sys_nodes*sizeof(node_t))) != 0)
errormsg(WHERE, ERR_LEN, str, cfg.sys_nodes*sizeof(node_t));
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_NOTICE, "Perhaps this node is already running");
return(false);
}
if(filelength(fileno(logfile_fp))) {
log(crlf);
time_t ftime = fdate(str);
safe_snprintf(str,sizeof(str),
"End of preexisting log entry (possible crash on %.24s)"
,ctime_r(&ftime, tmp));
logline(LOG_NOTICE,"L!",str);
log(crlf);
catsyslog(true);
}
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);
// remove any pending node messages
safe_snprintf(str, sizeof(str), "%smsgs/n%3.3u.msg",cfg.data_dir,cfg.node_num);
(void)remove(str);
// Delete any stale temporary files (with potentially sensitive content)
delfiles(cfg.temp_dir,ALLFILES);
safe_snprintf(str, sizeof(str), "%sMSGTMP", cfg.node_dir);
removecase(str);
safe_snprintf(str, sizeof(str), "%sQUOTES.TXT", cfg.node_dir);
removecase(str);
}
/* 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=(int *)malloc(sizeof(int)*usrgrp_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "cursub", sizeof(int)*usrgrp_total);
return(false);
}
if((usrgrp=(int *)malloc(sizeof(int)*usrgrp_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrgrp", sizeof(int)*usrgrp_total);
return(false);
}
if((usrsubs=(int *)malloc(sizeof(int)*usrgrp_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrsubs", sizeof(int)*usrgrp_total);
return(false);
}
if((usrsub=(int **)calloc(usrgrp_total,sizeof(int *)))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrsub", sizeof(int)*usrgrp_total);
return(false);
}
}
if(cfg.total_subs) {
if((subscan=(subscan_t *)calloc(cfg.total_subs, sizeof(subscan_t)))==NULL) {
errormsg(WHERE, ERR_ALLOC, "subscan", sizeof(subscan_t)*cfg.total_subs);
return(false);
}
}
for(i=l=0;i<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]=(int *)malloc(sizeof(int)*l))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrsub[x]", sizeof(int)*l);
return(false);
}
if(cfg.total_libs) {
usrlib_total = cfg.total_libs;
if((curdir=(int *)malloc(sizeof(int)*usrlib_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "curdir", sizeof(int)*usrlib_total);
return(false);
}
if((usrlib=(int *)malloc(sizeof(int)*usrlib_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrlib", sizeof(int)*usrlib_total);
return(false);
}
if((usrdirs=(int *)malloc(sizeof(uint)*usrlib_total))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrdirs", sizeof(int)*usrlib_total);
return(false);
}
if((usrdir=(int **)calloc(usrlib_total,sizeof(int *)))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrdir", sizeof(int)*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]=(int *)malloc(sizeof(int)*l))==NULL) {
errormsg(WHERE, ERR_ALLOC, "usrdir[x]", sizeof(int)*l);
return(false);
}
}
#ifdef USE_CRYPTLIB
pthread_mutex_init(&ssh_mutex,NULL);
ssh_mutex_created = true;
ssh_active = CreateEvent(nullptr, true, false, nullptr);
#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()
{
int i;
useron.number = 0;
#ifdef _DEBUG
lprintf(LOG_DEBUG,"destructor begin");
#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 && !passthru_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;
}
if(syspage_semfile[0])
remove(syspage_semfile);
/********************************/
/* Free allocated class members */
/********************************/
js_cleanup();
/* 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);
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]);
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);
FREE_AND_NULL(qwknode);
total_qwknodes = 0;
listFree(&savedlines);
listFree(&smb_list);
listFree(&mouse_hotspots);
#ifdef USE_CRYPTLIB
while(ssh_mutex_created && pthread_mutex_destroy(&ssh_mutex)==EBUSY)
mswait(1);
if (ssh_active) {
SetEvent(ssh_active);
while ((!CloseEvent(ssh_active)) && errno == EBUSY)
mswait(1);
ssh_active = nullptr;
}
#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, "destructor end");
#endif
}
int sbbs_t::smb_stack(smb_t* smb, bool push)
{
if(push) {
if(smb == NULL || !SMB_IS_OPEN(smb))
return SMB_SUCCESS; /* Msg base not open, do nothing */
if(listPushNodeData(&smb_list, smb, sizeof(*smb)) == NULL)
return SMB_FAILURE;
return SMB_SUCCESS;
}
/* pop */
smb_t* data = (smb_t*)listPopNode(&smb_list);
if(data == NULL) /* Nothing on the stack, so do nothing */
return SMB_SUCCESS;
*smb = *data;
free(data);
return SMB_SUCCESS;
}
/****************************************************************************/
/* 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));
}
if(cfg.node_num && spy_socket[cfg.node_num-1]!=INVALID_SOCKET)
if(sendsocket(spy_socket[cfg.node_num-1],str,strlen(str)) < 1)
lprintf(LOG_ERR, "Node %d ERROR %d writing to spy socket", cfg.node_num, ERROR_VALUE);
#ifdef __unix__
if(cfg.node_num && uspy_socket[cfg.node_num-1]!=INVALID_SOCKET)
if(sendsocket(uspy_socket[cfg.node_num-1],str,strlen(str)) < 1)
lprintf(LOG_ERR, "Node %d ERROR %d writing to spy socket", cfg.node_num, ERROR_VALUE);
#endif
}
/****************************************************************************/
/* 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(const char* path, const char* dest, bool copy)
{
char src[MAX_PATH + 1];
if(!stricmp(path, dest)) /* source and destination are the same! */
return(0);
SAFECOPY(src, path);
if(!fexistcase(src)) {
bprintf("\r\n\7MV ERROR: Source doesn't exist\r\n'%s'\r\n"
,src);
return(-1);
}
if(!copy && fexist(dest)) {
bprintf("\r\n\7MV ERROR: Destination already exists\r\n'%s'\r\n"
,dest);
return(-1);
}
if(!copy && rename(src, dest) == 0)
return 0;
if(!CopyFile(src, dest, /* fail if exists: */!copy)) {
errormsg(WHERE, "CopyFile", src, 0, dest);
return -1;
}
if(!copy && remove(src)) {
errormsg(WHERE,ERR_REMOVE,src,0);
return(-1);
}
return(0);
}
void sbbs_t::hangup(void)
{
if(online) {
clear_hotspots();
lprintf(LOG_DEBUG,"disconnecting client");
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(&ssh_mutex);
ssh_session_destroy(client_socket, ssh_session, __LINE__);
ssh_mode = false;
pthread_mutex_unlock(&ssh_mutex);
}
close_socket(client_socket);
client_socket=INVALID_SOCKET;
}
SetEvent(outbuf.data_event);
SetEvent(outbuf.highwater_event);
}
int sbbs_t::incom(unsigned int timeout)
{
uchar ch;
if(!online)
return NOINP;
// If we think we may have some input, send all our output
if (RingBufFull(&outbuf) != 0) {
SetEvent(outbuf.highwater_event);
SetEvent(outbuf.data_event);
}
#if 0 /* looping version */
while(!RingBufRead(&inbuf, &ch, 1))
if(WaitForEvent(inbuf.data_event, timeout) != WAIT_OBJECT_0 || sys_status&SS_ABORT)
return(NOINP);
#else
if(!RingBufRead(&inbuf, &ch, 1)) {
if (WaitForEvent(inbuf.data_event, timeout) != WAIT_OBJECT_0)
return(NOINP);
if(!RingBufRead(&inbuf, &ch, 1))
return(NOINP);
}
#endif
return(ch);
}
// Steve's original implementation (in RCIOL) did not incorporate a retry
// ... so this function does not either. :-P
int sbbs_t::_outcom(uchar ch)
{
if(!RingBufFree(&outbuf))
return(TXBOF);
if(!RingBufWrite(&outbuf, &ch, 1))
return(TXBOF);
return(0);
}
// This outcom version retries - copied loop from sbbs_t::outchar()
int sbbs_t::outcom(uchar ch, int max_attempts)
{
int i = 0;
while(_outcom(ch) != 0) {
if(!online)
break;
i++;
if(i >= max_attempts) { /* timeout - beep flush outbuf */
lprintf(LOG_NOTICE, "timeout(outcom) %04X %04X", rioctl(TXBC), rioctl(IOFO));
_outcom(BEL);
rioctl(IOCS|PAUSE);
return TXBOF;
}
if(sys_status&SS_SYSPAGE)
sbbs_beep(i, OUTCOM_RETRY_DELAY);
else
mswait(OUTCOM_RETRY_DELAY);
}
return 0; // Success
}
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;
sys_status&=~(SS_USERON|SS_TMPSYSOP|SS_LCHAT|SS_ABORT
|SS_PAUSEON|SS_PAUSEOFF|SS_EVENT|SS_NEWUSER|SS_NEWDAY|SS_QWKLOGON|SS_FASTLOGON);
cid[0]=0;
wordwrap[0]=0;
question[0]=0;
menu_dir[0]=0;
menu_file[0]=0;
row = 0;
rows = startup->default_term_height;
cols = startup->default_term_width;
lncntr=0;
autoterm=0;
cterm_version = 0;
lbuflen=0;
timeleft_warn=0;
keybufbot=keybuftop=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_rate=30000;
dte_rate=38400;
cur_output_rate = output_rate_unlimited;
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] = "node.log";
char *buf;
FILE* fp;
int i,file;
int 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 = ftell(logfile_fp);
if(length > 0) {
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(write(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((fp = fopenlog(&cfg, str)) == NULL) {
errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT);
free((char *)buf);
return;
}
if(fwrite(buf, sizeof(uint8_t), length, fp) != (size_t)length) {
fcloselog(fp);
errormsg(WHERE,ERR_WRITE,str,length);
free((char *)buf);
return;
}
fcloselog(fp);
}
}
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()
{
int i;
stats_t stats;
if(REALSYSOP && !(cfg.sys_misc&SM_SYSSTAT))
return;
if(useron.rest&FLAG('Q')) /* Don't count QWKnet nodes */
return;
now = time(NULL);
if(now <= logontime) {
char tmp[128];
lprintf(LOG_WARNING, "Logoff time (%u) <= logon time (%u): %s"
,(uint)now, (uint)logontime, ctime_r(&logontime, tmp));
return;
}
uint minutes_used = (uint)(now-logontime)/60;
if(minutes_used < 1)
return;
for(i=0;i<2;i++) {
FILE* fp = fopen_dstats(&cfg, i ? 0 : cfg.node_num, /* for_write: */true);
if(fp == NULL)
continue;
if(fread_dstats(fp, &stats)) {
stats.total.timeon += minutes_used;
stats.today.timeon += minutes_used;
fwrite_dstats(fp, &stats);
}
fclose_dstats(fp);
}
}
void node_thread(void* arg)
{
char str[MAX_PATH + 1];
int file;
int curshell=-1;
node_t node;
uint 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_cx = sbbs->js_init(&sbbs->js_runtime, &sbbs->js_glob, "node")) == NULL) /* This must be done in the context of the node thread */
lprintf(LOG_CRIT,"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);
}
bool login_success = false;
if(sbbs->answer()) {
login_success = true;
if(sbbs->useron.pass[0])
listAddNodeData(¤t_logins, sbbs->client.addr, strlen(sbbs->client.addr)+1, sbbs->cfg.node_num, LAST_NODE);
if(sbbs->sys_status&SS_QWKLOGON) {
sbbs->getsmsg(sbbs->useron.number);
sbbs->qwk_sec();
} else while(sbbs->useron.number
&& (sbbs->main_csi.misc&CS_OFFLINE_EXEC || sbbs->online)) {
if(curshell != sbbs->useron.shell) {
if(sbbs->useron.shell>=sbbs->cfg.total_shells)
sbbs->useron.shell=0;
if(sbbs->cfg.mods_dir[0]) {
SAFEPRINTF2(str,"%s%s.js",sbbs->cfg.mods_dir
,sbbs->cfg.shell[sbbs->useron.shell]->code);
if(fexistcase(str)) {
sbbs->js_execfile(str);
continue;
}
}
SAFEPRINTF2(str, "%s%s.js", sbbs->cfg.exec_dir
,sbbs->cfg.shell[sbbs->useron.shell]->code);
if(fexistcase(str)) {
sbbs->js_execfile(str);
continue;
}
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=(int)filelength(file);
if(sbbs->main_csi.length < 1) {
close(file);
sbbs->errormsg(WHERE,ERR_LEN, str, sbbs->main_csi.length);
sbbs->hangup();
break;
}
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(read(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;
}
listRemoveTaggedNode(¤t_logins, sbbs->cfg.node_num, /* free_data */true);
sbbs->logoffstats(); /* Updates both system and node dsts.ini (daily statistics) files */
}
#ifdef _WIN32
if(startup->sound.hangup[0] && !sound_muted(&scfg))
PlaySound(startup->sound.hangup, 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(login_success);
time_t now = time(NULL);
SAFEPRINTF(str, "%sclient.ini", sbbs->cfg.node_dir);
FILE* fp = fopen(str, "at");
if(fp != NULL) {
fprintf(fp, "user=%u\n", sbbs->useron.number);
fprintf(fp, "name=%s\n", sbbs->useron.alias);
fprintf(fp, "done=%u\n", (uint)now);
fclose(fp);
}
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
|| sbbs->passthru_thread_running
) {
lprintf(LOG_INFO,"Node %d Waiting for %s to terminate..."
,sbbs->cfg.node_num
,(sbbs->input_thread_running && sbbs->output_thread_running) ?
"I/O threads" : sbbs->input_thread_running
? "input thread" : "output thread");
time_t start=time(NULL);
while(sbbs->input_thread_running
|| sbbs->output_thread_running
|| sbbs->passthru_thread_running
) {
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);
}
}
if(login_success)
sbbs->catsyslog(/* Crash: */false);
else {
rewind(sbbs->logfile_fp);
if(chsize(fileno(sbbs->logfile_fp), 0) != 0)
sbbs->errormsg(WHERE, "truncating", "logfile", 0);
}
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 */
uint32_t remain = protected_uint32_adjust_fetch(&node_threads_running, -1);
lprintf(LOG_INFO,"Node %d thread terminated (%u node threads remain, %u 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);
/* crash here July-27-2018:
ntdll.dll!77282e19() Unknown
[Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll]
[External Code]
sbbs.dll!pthread_mutex_lock(_RTL_CRITICAL_SECTION * mutex) Line 171 C
sbbs.dll!protected_uint32_adjust(protected_uint32_t * i, int adjustment) Line 244 C
sbbs.dll!update_clients() Line 185 C++
> sbbs.dll!node_thread(void * arg) Line 4568 C++
[External Code]
node_threads_running {value=0 mutex={DebugInfo=0x00000000 <NULL> LockCount=-6 RecursionCount=0 ...} } protected_uint32_t
and again on July-10-2019:
ntdll.dll!RtlpWaitOnCriticalSection() Unknown
ntdll.dll!RtlpEnterCriticalSectionContended() Unknown
ntdll.dll!_RtlEnterCriticalSection@4() Unknown
sbbs.dll!pthread_mutex_lock(_RTL_CRITICAL_SECTION * mutex) Line 171 C
sbbs.dll!protected_uint32_adjust(protected_uint32_t * i, int adjustment) Line 244 C
sbbs.dll!update_clients() Line 187 C++
> sbbs.dll!node_thread(void * arg) Line 4668 C++
*/
update_clients();
thread_down();
}
bool sbbs_t::backup(const char* fname, int backup_level, bool rename)
{
if(!fexist(fname))
return false;
lprintf(LOG_DEBUG, "Backing-up %s (%u bytes)", fname, (int)flength(fname));
return ::backup(fname, backup_level, rename) ? true : false;
}
void sbbs_t::daily_maint(void)
{
char str[128];
char uname[LEN_ALIAS+1];
uint i;
uint usernum;
uint lastusernum;
user_t user;
lputs(LOG_INFO, "DAILY: System maintenance begun");
now=time(NULL);
if(cfg.node_num) {
if((i=getnodedat(cfg.node_num,&thisnode,true)) != 0)
errormsg(WHERE,ERR_LOCK,"node file",i);
else {
thisnode.status=NODE_EVENT_RUNNING;
putnodedat(cfg.node_num,&thisnode);
}
}
if(cfg.user_backup_level) {
lputs(LOG_INFO,"DAILY: Backing-up user data...");
SAFEPRINTF(str,"%suser/" USER_DATA_FILENAME, cfg.data_dir);
backup(str,cfg.user_backup_level,false);
SAFEPRINTF(str,"%suser/name.dat",cfg.data_dir);
backup(str,cfg.user_backup_level,false);
}
if(cfg.mail_backup_level) {
lputs(LOG_INFO,"DAILY: Backing-up mail data...");
smb_t mail;
int result = smb_open_sub(&cfg, &mail, INVALID_SUB);
if(result != SMB_SUCCESS)
lprintf(LOG_ERR, "ERROR %d (%s) opening mail base", result, mail.last_error);
else {
result = smb_lock(&mail);
if(result != SMB_SUCCESS)
lprintf(LOG_ERR, "ERROR %d (%s) locking mail base", result, mail.last_error);
else {
SAFEPRINTF(str,"%smail.shd",cfg.data_dir);
backup(str,cfg.mail_backup_level,false);
SAFEPRINTF(str,"%smail.sha",cfg.data_dir);
backup(str,cfg.mail_backup_level,false);
SAFEPRINTF(str,"%smail.sdt",cfg.data_dir);
backup(str,cfg.mail_backup_level,false);
SAFEPRINTF(str,"%smail.sda",cfg.data_dir);
backup(str,cfg.mail_backup_level,false);
SAFEPRINTF(str,"%smail.sid",cfg.data_dir);
backup(str,cfg.mail_backup_level,false);
SAFEPRINTF(str,"%smail.sch",cfg.data_dir);
backup(str,cfg.mail_backup_level,false);
SAFEPRINTF(str,"%smail.hash",cfg.data_dir);
backup(str,cfg.mail_backup_level,false);
SAFEPRINTF(str,"%smail.ini",cfg.data_dir);
backup(str,cfg.mail_backup_level,false);
result = smb_unlock(&mail);
if(result != SMB_SUCCESS)
lprintf(LOG_ERR, "ERROR %d (%s) unlocking mail base", result, mail.last_error);
}
smb_close(&mail);
}
}
lputs(LOG_INFO, "DAILY: Checking for inactive/expired user records...");
lastusernum=lastuser(&cfg);
int userfile=openuserdat(&cfg, /* for_modify: */false);
for(usernum=1;usernum<=lastusernum;usernum++) {
user.number = usernum;
if((i=fgetuserdat(&cfg, &user, userfile)) != 0) {
SAFEPRINTF(str,"user record %u",usernum);
errormsg(WHERE, ERR_READ, str, i);
continue;
}
/***********************************************/
/* Fix name (name.dat and user.tab) mismatches */
/***********************************************/
username(&cfg,user.number,uname);
if(user.misc&DELETED) {
if(strcmp(uname,"DELETED USER"))
putusername(&cfg,user.number,nulstr);
continue;
}
if(strcmp(user.alias,uname))
putusername(&cfg,user.number,user.alias);
if(user.number==1)
continue; /* skip expiration/inactivity checks for user #1 */
if(!(user.misc&(DELETED|INACTIVE))
&& user.expire && (uint)user.expire<=(uint)now) {
putsmsg(user.number,text[AccountHasExpired]);
SAFEPRINTF2(str,"DAILY: %s #%u Expired",user.alias,user.number);
lputs(LOG_NOTICE, str);
if(cfg.level_misc[user.level]&LEVEL_EXPTOVAL
&& cfg.level_expireto[user.level]<10) {
user.flags1=cfg.val_flags1[cfg.level_expireto[user.level]];
user.flags2=cfg.val_flags2[cfg.level_expireto[user.level]];
user.flags3=cfg.val_flags3[cfg.level_expireto[user.level]];
user.flags4=cfg.val_flags4[cfg.level_expireto[user.level]];
user.exempt=cfg.val_exempt[cfg.level_expireto[user.level]];
user.rest=cfg.val_rest[cfg.level_expireto[user.level]];
if(cfg.val_expire[cfg.level_expireto[user.level]])
user.expire=(time32_t)now
+(cfg.val_expire[cfg.level_expireto[user.level]]*24*60*60);
else
user.expire=0;
user.level=cfg.val_level[cfg.level_expireto[user.level]];
}
else {
if(cfg.level_misc[user.level]&LEVEL_EXPTOLVL)
user.level=cfg.level_expireto[user.level];
else
user.level=cfg.expired_level;
user.flags1&=~cfg.expired_flags1; /* expired status */
user.flags2&=~cfg.expired_flags2; /* expired status */
user.flags3&=~cfg.expired_flags3; /* expired status */
user.flags4&=~cfg.expired_flags4; /* expired status */
user.exempt&=~cfg.expired_exempt;
user.rest|=cfg.expired_rest;
user.expire=0;
}
putuserdec32(user.number, USER_LEVEL, user.level);
putuserflags(user.number, USER_FLAGS1, user.flags1);
putuserflags(user.number, USER_FLAGS2, user.flags2);
putuserflags(user.number, USER_FLAGS3, user.flags3);
putuserflags(user.number, USER_FLAGS4, user.flags4);
putuserdatetime(user.number, USER_EXPIRE, user.expire);
putuserflags(user.number, USER_EXEMPT, user.exempt);
putuserflags(user.number, USER_REST, user.rest);
if(cfg.expire_mod[0]) {
useron=user;
online=ON_LOCAL;
exec_bin(cfg.expire_mod,&main_csi);
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 */
&& (cfg.sys_autodel && (now-user.laston)/(int)(24L*60L*60L)
> cfg.sys_autodel)) { /* Inactive too long */
SAFEPRINTF4(str,"DAILY: Auto-Deleted %s (%s) #%u due to inactivity > %u days"
,user.alias, user.name, user.number, cfg.sys_autodel);
lputs(LOG_NOTICE, str);
delallmail(user.number, MAIL_ANY);
putusername(&cfg,user.number,nulstr);
putusermisc(user.number, user.misc | DELETED);
}
}
closeuserdat(userfile);
lputs(LOG_INFO,"DAILY: Purging deleted/expired e-mail");
SAFEPRINTF(smb.file,"%smail",cfg.data_dir);
smb.retry_time=cfg.smb_retry_time;
smb.subnum=INVALID_SUB;
if((i=smb_open(&smb))!=0)
errormsg(WHERE,ERR_OPEN,smb.file,i,smb.last_error);
else {
if(filelength(fileno(smb.shd_fp))>0) {
if((i=smb_locksmbhdr(&smb))!=0)
errormsg(WHERE,ERR_LOCK,smb.file,i,smb.last_error);
else
lprintf(LOG_INFO, "DAILY: Removed %d messages", delmail(0, MAIL_ALL));
}
smb_close(&smb);
}
if(cfg.sys_daily[0]) {
lputs(LOG_INFO, "DAILY: Running system event");
const char* cmd = cmdstr(cfg.sys_daily,nulstr,nulstr,NULL);
online = ON_LOCAL;
int result = external(cmd, EX_OFFLINE);
online = false;
lprintf(result ? LOG_ERR : LOG_INFO, "Daily event: '%s' returned %d", cmd, result);
}
lputs(LOG_INFO, "DAILY: System maintenance ended");
sys_status&=~SS_DAILY;
}
const char* js_ver(void)
{
#ifdef JAVASCRIPT
return(JS_GetImplementationVersion());
#else
return("");
#endif
}
/* Returns char string of version and revision */
const char* 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 Compiled %s/%s %s %s with %s"
,TELNET_SERVER
,VERSION, REVISION
#ifdef _DEBUG
," Debug"
#else
,""
#endif
,git_branch, git_hash
,__DATE__, __TIME__, compiler
);
}
return(ver);
}
/* Returns binary-coded version and revision (e.g. 0x31000 == 3.10a) */
int bbs_ver_num(void)
{
return(VERSION_HEX);
}
void 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);
ts_set = NULL;
#ifdef _WINSOCKAPI_
if(WSAInitialized && WSACleanup()!=0)
lprintf(LOG_ERR,"!WSACleanup ERROR %d",ERROR_VALUE);
#endif
free_cfg(&scfg);
free_text(text);
for(int i = 0; i < MAX_NODES; i++) {
free_text(node_text[i]);
free_cfg(&node_scfg[i]);
memset(&node_scfg[i], 0, sizeof(node_scfg[i]));
}
semfile_list_free(&pause_semfiles);
semfile_list_free(&recycle_semfiles);
semfile_list_free(&shutdown_semfiles);
semfile_list_free(&clear_attempts_semfiles);
listFree(¤t_logins);
listFree(¤t_connections);
protected_uint32_destroy(node_threads_running);
protected_uint32_destroy(ssh_sessions);
thread_down();
if(terminate_server || code)
lprintf(LOG_INFO,"Terminal Server thread terminated (%u clients served)", served);
set_state(SERVER_STOPPED);
mqtt_shutdown(&mqtt);
if(startup->terminated!=NULL)
startup->terminated(startup->cbdata,code);
}
void 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 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__
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;
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;
}
set_state(SERVER_INIT);
#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;
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);
protected_uint32_init(&ssh_sessions,0);
thread_up(false /* setuid */);
if(startup->seteuid!=NULL)
startup->seteuid(true);
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%c%s"
,TELNET_SERVER
,VERSION
,REVISION
#ifdef _DEBUG
," Debug"
#else
,""
#endif
);
lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", git_branch, git_hash, __DATE__, __TIME__, compiler);
#ifdef _DEBUG
lprintf(LOG_DEBUG, "sizeof: int=%d, long=%d, off_t=%d, time_t=%d"
,(int)sizeof(int), (int)sizeof(long), (int)sizeof(off_t), (int)sizeof(time_t));
memset(str, 0xff, sizeof(str));
snprintf(str, 2, "\x01\x02");
if(str[1] != '\0') {
lprintf(LOG_CRIT, "Non-terminating (unsafe) snprintf function detected (0x%02X instead of 0x00)", (uchar)str[1]);
cleanup(1);
return;
}
#endif
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"
,(int)sizeof(node_t),SIZEOF_NODE_T);
cleanup(1);
return;
}
if(!winsock_startup()) {
cleanup(1);
return;
}
t=time(NULL);
lprintf(LOG_INFO,"Initializing on %.24s with options: %x"
,ctime_r(&t,str),startup->options);
if(chdir(startup->ctrl_dir)!=0)
lprintf(LOG_ERR,"!ERROR %d (%s) changing directory to: %s", errno, strerror(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, /* prep: */true, /* node_req: */true, logstr, sizeof(logstr))) {
lprintf(LOG_CRIT,"!ERROR %s",logstr);
lprintf(LOG_CRIT,"!FAILED to load configuration files");
cleanup(1);
return;
}
mqtt_startup(&mqtt, &scfg, (struct startup*)startup, bbs_ver(), lputs);
if(scfg.total_shells < 1) {
lprintf(LOG_CRIT, "At least one command shell must be configured (e.g. in SCFG->Command Shells)");
cleanup(1);
return;
}
if((t=checktime())!=0) { /* Check binary time */
lprintf(LOG_ERR,"!TIME PROBLEM (%" PRId64 ")",(int64_t)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;
}
SAFEPRINTF(str, "%u total", scfg.sys_nodes);
mqtt_pub_strval(&mqtt, TOPIC_BBS, "node", str);
/* Create missing directories */
lprintf(LOG_INFO,"Verifying/creating data directories");
make_data_dirs(&scfg);
/* Create missing node directories */
lprintf(LOG_INFO,"Verifying/creating node directories");
for(i=0;i<=scfg.sys_nodes;i++) {
char newfn[MAX_PATH + 1];
char oldfn[MAX_PATH + 1];
if(i) {
int err;
if((err = md(scfg.node_path[i-1])) != 0) {
lprintf(LOG_CRIT,"!ERROR %d (%s) creating directory: %s", err, strerror(err), scfg.node_path[i-1]);
cleanup(1);
return;
}
}
// Convert old dsts.dab -> dsts.ini
SAFEPRINTF(oldfn, "%sdsts.dab", i ? scfg.node_path[i-1] : scfg.ctrl_dir);
if(fexist(oldfn) && !fexist(dstats_fname(&scfg, i, newfn, sizeof(newfn)))) {
lprintf(LOG_NOTICE, "Auto-upgrading daily statistics data file: %s", oldfn);
stats_t stats;
getstats(&scfg, i, &stats);
putstats(&scfg, i, &stats);
}
SAFEPRINTF(oldfn, "%scsts.dab", i ? scfg.node_path[i-1] : scfg.ctrl_dir);
if(fexist(oldfn) && !fexist(cstats_fname(&scfg, i, newfn, sizeof(newfn)))) {
uint record = 0;
lprintf(LOG_NOTICE, "Auto-upgrading cumulative statistics log file: %s", oldfn);
stats_t stats;
FILE* out = fopen_cstats(&scfg, i, /* write: */true);
if(out == NULL) {
lprintf(LOG_ERR, "!ERROR %d (%s) creating: %s", errno, strerror(errno), newfn);
continue;
}
FILE* in = fopen(oldfn, "rb");
if(in == NULL)
lprintf(LOG_ERR, "!ERROR %d (%s) opening: %s", errno, strerror(errno), oldfn);
else {
ZERO_VAR(stats);
while(!feof(in)) {
struct { /* System/Node Statistics */
uint32_t date, /* When last rolled-over */
logons, /* Total Logons on System */
timeon, /* Total Time on System */
uls, /* Total Uploads Today */
ulb, /* Total Upload Bytes Today */
dls, /* Total Downloads Today */
dlb, /* Total Download Bytes Today */
ptoday, /* Total Posts Today */
etoday, /* Total Emails Today */
ftoday; /* Total Feedbacks Today */
} legacy_stats;
if(fread(&legacy_stats, sizeof(legacy_stats), 1, in) != 1)
break;
record++;
if(legacy_stats.logons > 1000
|| legacy_stats.timeon > 10000) {
lprintf(LOG_WARNING, "Skipped corrupted record #%u in %s", record, oldfn);
continue;
}
ZERO_VAR(stats);
stats.date = legacy_stats.date;
stats.today.logons = legacy_stats.logons;
stats.today.timeon = legacy_stats.timeon;
stats.today.uls = legacy_stats.uls;
stats.today.ulb = legacy_stats.ulb;
stats.today.dls = legacy_stats.dls;
stats.today.dlb = legacy_stats.dlb;
stats.today.posts = legacy_stats.ptoday;
stats.today.email = legacy_stats.etoday;
stats.today.fbacks = legacy_stats.ftoday;
if(!fwrite_cstats(out, &stats)) {
lprintf(LOG_ERR, "!WRITE ERROR, %s line %d", __FILE__, __LINE__);
break;
}
}
fclose(in);
}
fclose_cstats(out);
lprintf(LOG_INFO, "Done (%u daily-statistics records converted)", record);
}
}
/* 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;
#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;
}
if (!(startup->options & BBS_OPT_NO_TELNET)) {
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);
}
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);
}
#ifdef USE_CRYPTLIB
#if CRYPTLIB_VERSION < 3300 && CRYPTLIB_VERSION > 999
#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)) {
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_READONLY))) {
if(cryptStatusError(i=cryptGetPrivateKey(ssh_keyset, &ssh_context, CRYPT_KEYID_NAME, "ssh_server", scfg.sys_pass))) {
GCESNN(i, ssh_keyset, "getting private key");
goto NO_SSH;
}
}
else {
/* Couldn't do that... create a new context and use the key from there... */
if(cryptStatusError(i=cryptCreateContext(&ssh_context, CRYPT_UNUSED, CRYPT_ALGO_RSA))) {
GCESNN(i, CRYPT_UNUSED, "creating context");
goto NO_SSH;
}
if(cryptStatusError(i=cryptSetAttributeString(ssh_context, CRYPT_CTXINFO_LABEL, "ssh_server", 10))) {
GCESNN(i, ssh_context, "setting label");
goto NO_SSH;
}
if(cryptStatusError(i=cryptGenerateKey(ssh_context))) {
GCESNN(i, ssh_context, "generating key");
goto NO_SSH;
}
/* Ok, now try saving this one... use the syspass to encrypt it. */
if(cryptStatusOK(i=cryptKeysetOpen(&ssh_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, str, CRYPT_KEYOPT_CREATE))) {
if(cryptStatusError(i=cryptAddPrivateKey(ssh_keyset, ssh_context, scfg.sys_pass)))
GCESNN(i, ssh_keyset, "adding private key");
if(cryptStatusError(i=cryptKeysetClose(ssh_keyset)))
GCESNN(i, ssh_keyset, "closing keyset");
}
else
GCESNN(i, CRYPT_UNUSED, "creating keyset");
}
/* 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);
}
NO_SSH:
#endif
sbbs_t* 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, true);
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 in case 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);
}
/* Setup recycle/shutdown semaphore file lists */
shutdown_semfiles = semfile_list_init(scfg.ctrl_dir,"shutdown", server_abbrev);
recycle_semfiles = semfile_list_init(scfg.ctrl_dir,"recycle", server_abbrev);
pause_semfiles = semfile_list_init(scfg.ctrl_dir,"pause", server_abbrev);
clear_attempts_semfiles = semfile_list_init(scfg.ctrl_dir,"clear", server_abbrev);
semfile_list_add(&recycle_semfiles,startup->ini_fname);
strListAppendFormat(&recycle_semfiles, "%stext.dat", scfg.ctrl_dir);
strListAppendFormat(&recycle_semfiles, "%stext.ini", scfg.ctrl_dir);
strListAppendFormat(&recycle_semfiles, "%sattr.cfg", scfg.ctrl_dir);
if(!initialized)
semfile_list_check(&initialized,shutdown_semfiles);
semfile_list_check(&initialized,recycle_semfiles);
semfile_list_check(&initialized,clear_attempts_semfiles);
listInit(¤t_logins, LINK_LIST_MUTEX);
listInit(¤t_connections, LINK_LIST_MUTEX);
#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 (%s) creating local spy socket %s"
, i, errno, strerror(errno), str);
}
}
#endif // __unix__ (unix-domain spy sockets)
lprintf(LOG_INFO,"Terminal Server thread started for nodes %d through %d", first_node, last_node);
mqtt_client_max(&mqtt, (last_node - first_node) + 1);
while(!terminate_server) {
YIELD();
/* check for re-run flags and recycle/shutdown sem files */
if(!(startup->options&BBS_OPT_NO_RECYCLE)
&& protected_uint32_value(node_threads_running) == 0) {
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;
}
if(((p = semfile_list_check(NULL, pause_semfiles)) != NULL
&& lprintf(LOG_INFO, "Pause semaphore file (%s) detected"
,p))
|| (startup->paused
&& lprintf(LOG_INFO, "Pause semaphore signaled"))) {
set_state(SERVER_PAUSED);
SLEEP(startup->sem_chk_freq * 1000);
continue;
}
/* signal caller that we've started up successfully */
set_state(SERVER_READY);
sbbs->online=false;
#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, (startup->options & BBS_OPT_HAPROXY_PROTO) ? XPMS_ACCEPT_FLAG_HAPROXY : XPMS_FLAGS_NONE, &ts_cb);
if(terminate_server) /* terminated */
break;
if((p=semfile_list_check(&initialized,clear_attempts_semfiles))!=NULL) {
lprintf(LOG_INFO,"Clear Failed Login Attempts semaphore file (%s) detected", p);
loginAttemptListClear(startup->login_attempt_list);
}
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;
#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)
if(read(uspy_socket[i-1],str,sizeof(str)) < 1)
*str = '\0';
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_INFO, "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;
}
// Count the socket:
call_socket_open_callback(true);
char host_ip[INET6_ADDRSTRLEN];
inet_addrtop(&client_addr, host_ip, sizeof(host_ip));
if(trashcan(&scfg,host_ip,"ip-silent")) {
close_socket(client_socket);
continue;
}
#ifdef USE_CRYPTLIB
client.protocol=rlogin ? "RLogin":(ssh ? "SSH" : "Telnet");
#else
client.protocol=rlogin ? "RLogin":"Telnet";
#endif
union xp_sockaddr local_addr;
memset(&local_addr, 0, sizeof(local_addr));
socklen_t addr_len = sizeof(local_addr);
if(getsockname(client_socket, (struct sockaddr *)&local_addr, &addr_len) != 0) {
lprintf(LOG_CRIT,"%04d %s [%s] !ERROR %d getting local address/port of socket"
,client_socket, client.protocol, host_ip, ERROR_VALUE);
close_socket(client_socket);
continue;
}
char local_ip[INET6_ADDRSTRLEN];
inet_addrtop(&local_addr, local_ip, sizeof local_ip);
lprintf(LOG_INFO,"%04d %s [%s] Connection accepted on %s port %u from port %u"
,client_socket, client.protocol, host_ip, local_ip, inet_addrport(&local_addr), inet_addrport(&client_addr));
if(startup->max_concurrent_connections > 0) {
int ip_len = strlen(host_ip)+1;
int connections = listCountMatches(¤t_connections, host_ip, ip_len);
int logins = listCountMatches(¤t_logins, host_ip, ip_len);
if(connections - logins >= (int)startup->max_concurrent_connections
&& !is_host_exempt(&scfg, host_ip, /* host_name */NULL)) {
lprintf(LOG_NOTICE, "%04d %s !Maximum concurrent connections without login (%u) reached from host: %s"
,client_socket, client.protocol, startup->max_concurrent_connections, host_ip);
close_socket(client_socket);
continue;
}
}
sbbs->client_socket=client_socket; // required for output to the user
if(!ssh)
sbbs->online=ON_REMOTE;
login_attempt_t attempted;
uint banned = loginBanned(&scfg, startup->login_attempt_list, client_socket, /* host_name: */NULL, startup->login_attempt, &attempted);
if(banned) {
char ban_duration[128];
lprintf(LOG_NOTICE, "%04d %s [%s] !TEMPORARY BAN (%lu login attempts%s%s) - remaining: %s"
,client_socket, client.protocol, host_ip, attempted.count-attempted.dupes
,attempted.user[0] ? ", last: " : "", attempted.user, duration_estimate_to_vstr(banned, ban_duration, sizeof ban_duration, 1, 1));
close_socket(client_socket);
continue;
}
struct trash trash;
if(sbbs->trashcan(host_ip, "ip", &trash)) {
char details[128];
lprintf(LOG_NOTICE,"%04d %s [%s] !CLIENT BLOCKED in ip.can %s", client_socket, client.protocol, host_ip, trash_details(&trash, details, sizeof details));
close_socket(client_socket);
continue;
}
#ifdef _WIN32
if(startup->sound.answer[0] && !sound_muted(&scfg))
PlaySound(startup->sound.answer, 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) {
BOOL nodelay = true;
ulong nb = 0;
if(cryptStatusError(i=cryptCreateSession(&sbbs->ssh_session, CRYPT_UNUSED, CRYPT_SESSION_SSH_SERVER))) {
GCESS(i, client_socket, CRYPT_UNUSED, "creating SSH session");
close_socket(client_socket);
continue;
}
lprintf(LOG_DEBUG, "%04d SSH Cryptlib Session: %d created", client_socket, sbbs->ssh_session);
protected_uint32_adjust(&ssh_sessions, 1);
sbbs->ssh_mode = true;
if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_OPTION_NET_CONNECTTIMEOUT, startup->ssh_connect_timeout)))
sbbs->log_crypt_error_status_sock(i, "setting connect timeout");
if (startup->options & BBS_OPT_SSH_ANYAUTH) {
if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_OPTIONS, CRYPT_SSHOPTION_NONE_AUTH)))
sbbs->log_crypt_error_status_sock(i, "setting none auth");
}
if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_PRIVATEKEY, ssh_context))) {
sbbs->log_crypt_error_status_sock(i, "setting private key");
SSH_END(client_socket);
close_socket(client_socket);
continue;
}
setsockopt(client_socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
ioctlsocket(client_socket,FIONBIO,&nb);
if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_NETWORKSOCKET, client_socket))) {
sbbs->log_crypt_error_status_sock(i, "setting network socket");
SSH_END(client_socket);
close_socket(client_socket);
continue;
}
if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_OPTION_NET_READTIMEOUT, 0)))
sbbs->log_crypt_error_status_sock(i, "setting read timeout");
// READ = WRITE TIMEOUT HACK... REMOVE WHEN FIXED
if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_OPTION_NET_WRITETIMEOUT, 0)))
sbbs->log_crypt_error_status_sock(i, "setting write timeout");
#if 0
if(cryptStatusError(err=crypt_pop_channel_data(sbbs, str, sizeof(str), &i))) {
GCES(i, sbbs->cfg.node_num, sbbs->ssh_session, "popping data");
i=0;
}
#endif
}
#endif
if(rlogin)
sbbs->outcom(0); /* acknowledge RLogin per RFC 1282 */
sbbs->autoterm=0;
sbbs->cols = startup->default_term_width;
if (inet_addrport(&local_addr) == startup->pet40_port || inet_addrport(&local_addr) == startup->pet80_port) {
sbbs->autoterm = PETSCII;
sbbs->cols = inet_addrport(&local_addr) == startup->pet40_port ? 40 : 80;
sbbs->outcom(PETSCII_UPPERLOWER);
}
sbbs->bprintf("\r\n%s\r\n", VERSION_NOTICE);
sbbs->bprintf("%s connection from: %s\r\n", client.protocol, host_ip);
SAFECOPY(host_name, STR_NO_HOSTNAME);
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 %s [%s] Hostname: %s", client_socket, client.protocol, host_ip, host_name);
}
if(sbbs->trashcan(host_name,"host", &trash)) {
SSH_END(client_socket);
close_socket(client_socket);
char details[128];
lprintf(LOG_NOTICE,"%04d %s [%s] !CLIENT BLOCKED in host.can: %s %s"
,client_socket, client.protocol, host_ip, host_name, trash_details(&trash, details, sizeof details));
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 %s [%s] Ident Response: %s",client_socket, client.protocol, host_ip, str);
identity=strrchr(str,':');
if(identity!=NULL) {
identity++; /* skip colon */
SKIP_WHITESPACE(identity);
if(*identity)
lprintf(LOG_INFO,"%04d %s [%s] Identity: %s",client_socket, client.protocol, host_ip, 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);
SAFECOPY(client.user, STR_UNKNOWN_USER);
client.usernum = 0;
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) {
if(node_socket[i - 1] != INVALID_SOCKET) {
lprintf(LOG_CRIT, "%04d !Node %d status is WFC, but the node socket (%d) and thread are still in use!"
,client_socket, i, node_socket[i - 1]);
sbbs->putnodedat(i, &node);
continue;
}
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 %s [%s] !No nodes available for login.", client_socket, client.protocol, host_ip);
SAFEPRINTF(str,"%snonodes.txt",scfg.text_dir);
if(fexist(str))
sbbs->printfile(str,P_NOABORT);
else {
sbbs->putcom("\r\nSorry, all terminal 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(client_socket);
close_socket(client_socket);
continue;
}
lprintf(LOG_INFO, "%04d %s [%s] Attaching to Node %d", client_socket, client.protocol, host_ip, i);
// Load the configuration files for this node, only if/when needed/updated
scfg_t* cfg = &node_scfg[i - 1];
if(cfg->size != sizeof(*cfg) || (node.misc & NODE_RRUN)) {
sbbs->bprintf("Loading configuration...");
cfg->size = sizeof(*cfg);
cfg->node_num = i;
SAFECOPY(cfg->ctrl_dir, startup->ctrl_dir);
lprintf(LOG_INFO,"Node %d Loading configuration files from %s", cfg->node_num, cfg->ctrl_dir);
SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
if(!load_cfg(cfg, node_text[i - 1], /* prep: */true, /* node_req: */true, logstr, sizeof(logstr))) {
lprintf(LOG_WARNING, "Node %d LOAD ERROR: %s, falling back to Node %d", cfg->node_num, logstr, first_node);
cfg->node_num = first_node;
if(!load_cfg(cfg, node_text[i - 1], /* prep: */true, /* node: */true, logstr, sizeof(logstr))) {
lprintf(LOG_CRIT,"!ERROR %s",logstr);
lprintf(LOG_CRIT,"!FAILED to load configuration files");
sbbs->bprintf("\r\nFAILED: %s", logstr);
client_off(client_socket);
SSH_END(client_socket);
close_socket(client_socket);
sbbs->getnodedat(cfg->node_num,&node,true);
node.status = NODE_WFC;
sbbs->putnodedat(cfg->node_num,&node);
continue;
}
}
cfg->node_num = i; // correct the node number
if(node.misc & NODE_RRUN) {
sbbs->getnodedat(cfg->node_num,&node,true);
node.misc &= ~NODE_RRUN;
sbbs->putnodedat(cfg->node_num,&node);
}
sbbs->bputs(crlf);
}
// Copy event last-run info from global config
for(int e=0; e < cfg->total_events && e < scfg.total_events; e++)
cfg->event[e]->last = scfg.event[e]->last;
node_socket[i-1]=client_socket;
sbbs_t* new_node = new sbbs_t(/* node_num: */i, &client_addr, client_addr_len, host_name
,client_socket
,cfg, node_text[i-1], &client);
new_node->client=client;
#ifdef USE_CRYPTLIB
if(ssh) {
new_node->ssh_session=sbbs->ssh_session; // This is done again later, after NO_PASSTHRU: Why?
new_node->ssh_mode=true;
new_node->session_channel = sbbs->session_channel;
}
#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 %s Node %d !Initialization failure"
,client_socket, client.protocol, 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(client_socket);
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
}
// Passthru socket creation/connection
if(true) {
/* 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(PF_INET, SOCK_STREAM, "passthru");
if(tmp_sock == INVALID_SOCKET) {
lprintf(LOG_ERR,"Node %d !ERROR %d creating passthru listen socket"
,new_node->cfg.node_num, ERROR_VALUE);
goto NO_PASSTHRU;
}
lprintf(LOG_DEBUG,"Node %d passthru listen socket %d opened"
,new_node->cfg.node_num, 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,"Node %d !ERROR %d (%d) listening on passthru socket"
,new_node->cfg.node_num, result, ERROR_VALUE);
close_socket(tmp_sock);
goto NO_PASSTHRU;
}
tmp_addr_len=sizeof(tmp_addr);
if(getsockname(tmp_sock, (struct sockaddr *)&tmp_addr, &tmp_addr_len)) {
lprintf(LOG_CRIT,"Node %d !ERROR %d getting passthru listener address/port of socket"
,new_node->cfg.node_num, ERROR_VALUE);
close_socket(tmp_sock);
goto NO_PASSTHRU;
}
lprintf(LOG_DEBUG,"Node %d passthru socket listening on port %u"
,new_node->cfg.node_num, htons(tmp_addr.sin_port));
new_node->passthru_socket = open_socket(PF_INET, SOCK_STREAM, "passthru");
if(new_node->passthru_socket == INVALID_SOCKET) {
lprintf(LOG_ERR,"Node %d !ERROR %d creating passthru connecting socket"
,new_node->cfg.node_num, ERROR_VALUE);
close_socket(tmp_sock);
goto NO_PASSTHRU;
}
lprintf(LOG_DEBUG,"Node %d passthru connect socket %d opened"
,new_node->cfg.node_num, new_node->passthru_socket);
result = connect(new_node->passthru_socket, (struct sockaddr *)&tmp_addr, tmp_addr_len);
if(result != 0) {
lprintf(LOG_ERR,"Node %d !ERROR %d (%d) connecting to passthru socket: %s port %u"
,new_node->cfg.node_num, result, ERROR_VALUE, inet_ntoa(tmp_addr.sin_addr), htons(tmp_addr.sin_port));
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,"Node %d !ERROR (%d) accepting on passthru socket"
,new_node->cfg.node_num, ERROR_VALUE);
lprintf(LOG_WARNING,"Node %d !WARNING native doors which use sockets will not function"
,new_node->cfg.node_num);
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_thread_running = true;
_beginthread(passthru_thread, 0, new_node);
NO_PASSTHRU:
if(ssh) {
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;
new_node->online = ON_REMOTE;
}
/* 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);
pthread_mutex_lock(&sbbs->ssh_mutex);
sbbs->ssh_mode=false;
sbbs->ssh_session=0; // Don't allow subsequent SSH connections to affect this one (!)
pthread_mutex_unlock(&sbbs->ssh_mutex);
}
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;
new_node->autoterm = sbbs->autoterm;
new_node->cols = sbbs->cols;
_beginthread(output_thread, 0, new_node);
_beginthread(node_thread, 0, new_node);
served++;
}
set_state(terminate_server ? SERVER_STOPPING : SERVER_RELOADING);
// 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_socket[i]!=INVALID_SOCKET) {
close_socket(uspy_socket[i]);
uspy_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);
#endif
}
sbbs->client_socket=INVALID_SOCKET;
if(events!=NULL)
events->terminated=true;
// Wake-up BBS output thread so it can terminate
SetEvent(sbbs->outbuf.data_event);
SetEvent(sbbs->outbuf.highwater_event);
// 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);
}
lprintf(LOG_INFO, "Done waiting for node threads to terminate");
}
// 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);
}
lprintf(LOG_INFO, "Done waiting for events thread to terminate");
}
// 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);
}
lprintf(LOG_INFO, "Done waiting for system output thread to terminate");
}
// Wait for BBS passthru thread to terminate
if(sbbs->passthru_thread_running) {
lprintf(LOG_INFO,"Waiting for passthru thread to terminate...");
start=time(NULL);
while(sbbs->passthru_thread_running) {
if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
lprintf(LOG_ERR,"!TIMEOUT waiting for passthru thread to terminate");
break;
}
mswait(100);
}
lprintf(LOG_INFO, "Done waiting for passthru threads to terminate");
}
// 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->passthru_thread_running || sbbs->output_thread_running)
lprintf(LOG_ERR,"!System I/O 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);
}