Newer
Older
/* 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 "petdefs.h"
#include "js_rtpool.h"
#include "js_request.h"
#include "ver.h"
#ifdef __unix__
#include <sys/un.h>
#endif
//#define SBBS_TELNET_ENVIRON_SUPPORT 1
//---------------------------------------------------------------------------
#define TELNET_SERVER "Synchronet Terminal Server"
#define STATUS_WFC "Listening"
#define TIMEOUT_THREAD_WAIT 60 // Seconds (was 15)
#define IO_THREAD_BUF_SIZE 20000 // Bytes
#define TIMEOUT_MUTEX_FILE 12*60*60
#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; \
#endif
volatile time_t uptime=0;
volatile ulong served=0;
static protected_uint32_t node_threads_running;
char lastuseron[LEN_ALIAS+1]; /* Name of user last online */
RingBuf* node_inbuf[MAX_NODES];
SOCKET spy_socket[MAX_NODES];
#ifdef __unix__
SOCKET uspy_socket[MAX_NODES]; /* UNIX domain spy sockets */
#endif
SOCKET node_socket[MAX_NODES];
static sbbs_t* sbbs=NULL;
static scfg_t scfg;
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 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="";
};
char *GCES_estr; \
int GCES_level; \
get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);\
lprintf(GCES_level, "Node %d SSH %s from %s", node, GCES_estr, __FUNCTION__); \
char *GCES_estr; \
int GCES_level; \
get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);\
lprintf(GCES_level, "SSH %s from %s", GCES_estr, __FUNCTION__); \
char *GCES_estr; \
int GCES_level; \
get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);\
lprintf(GCES_level, "%04d SSH %s from %s", sock, GCES_estr, __FUNCTION__); \
#define GCESSTR(status, str, log_level, sess, action) do { \
char *GCES_estr; \
int GCES_level; \
get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);\
if (GCES_estr) { \
lprintf(log_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;
{
if(startup!=NULL && startup->status!=NULL)
}
static void update_clients()
{
if(startup!=NULL && startup->clients!=NULL)
startup->clients(startup->cbdata,protected_uint32_value(node_threads_running));
}
void client_on(SOCKET sock, client_t* client, BOOL update)
{
if(!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);
}
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);
}
static void thread_up(BOOL setuid)
{
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,TRUE,setuid);
}
static void thread_down()
{
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,FALSE,FALSE);
int lputs(int level, const char* str)
if(level <= LOG_ERR) {
char errmsg[1024];
SAFEPRINTF(errmsg, "term %s", str);
errorlog(&scfg, 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;
if(level <= LOG_ERR) {
char errmsg[1024];
SAFEPRINTF(errmsg, "evnt %s", str);
errorlog(&scfg, 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);
}
int lprintf(int level, const char *fmt, ...)
{
va_list argptr;
char sbuf[1024];
sbuf[sizeof(sbuf)-1]=0;
/* 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;
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);
}
u_long resolve_ip(char *addr)
{
HOSTENT* host;
char* p;
if(*addr==0)
return((u_long)INADDR_NONE);
for(p=addr;*p;p++)
if(*p!='.' && !IS_DIGIT(*p))
break;
if(!(*p))
return(inet_addr(addr));
if((host=gethostbyname(addr))==NULL)
return((u_long)INADDR_NONE);
return(*((ulong*)host->h_addr_list[0]));
}
} /* extern "C" */
#ifdef _WINSOCKAPI_
WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL WSAInitialized=FALSE;
static BOOL winsock_startup(void)
{
int status; /* Status Code */
if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
WSAInitialized=TRUE;
return(TRUE);
}
lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
return(FALSE);
}
#else /* No WINSOCK */
#define winsock_startup() (TRUE)
#define SOCKLIB_DESC NULL
#endif
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);
}
#endif
seed = time32(NULL) ^ (uintmax_t)GetCurrentThreadId();
srand(seed);
sbbs_random(10); /* Throw away first number */
}
{
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;
}
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_DefineProperty(cx,obj,"_ver"
,INT_TO_JSVAL(ver),NULL,NULL,JSPROP_READONLY));
}
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* propver_array_name = "_property_ver_list";
/*
* from jsatom.c:
* Keep this in sync with jspubtd.h -- an assertion below will insist that
* its length match the JSType enum's JSTYPE_LIMIT limit value.
*/
static const char *js_type_str[] = {
"void", // changed from "undefined"
"object",
"function",
"string",
"number",
"boolean",
"array",
"alias",
JSBool
js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
uint i;
long ver;
jsval val;
jsuint len=0;
JSObject* array;
if((array=JS_NewArrayObject(cx, 0, NULL))==NULL)
return(JS_FALSE);
if(!JS_DefineProperty(cx, obj, propver_array_name, OBJECT_TO_JSVAL(array)
,NULL,NULL,JSPROP_READONLY))
return(JS_FALSE);
for(i=0;props[i].name;i++) {
if (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 version invisible props */
if((ver=props[i].ver) < 10000) /* auto convert 313 to 31300 */
ver*=100;
val = INT_TO_JSVAL(ver);
if(!JS_SetElement(cx, array, len++, &val))
return(JS_FALSE);
}
}
return(JS_TRUE);
}
js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
{
int i;
jsuint len=0;
long ver;
jsval val;
JSObject* method;
JSObject* method_array;
JSString* js_str;
/* 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);
if(JS_GetElement(cx, method_array, i, &val)!=JS_TRUE || val == JSVAL_VOID)
continue;
JS_GetProperty(cx, JSVAL_TO_OBJECT(val), "name", &val);
JSVALUE_TO_RASTRING(cx, val, str, &str_len, NULL);
if(str==NULL)
continue;
if(strcmp(str, funcs[0].name)==0)
return(JS_TRUE);
}
}
else {
if((method_array=JS_NewArrayObject(cx, 0, NULL))==NULL)
return(JS_FALSE);
if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
, NULL, NULL, 0))
return(JS_FALSE);
for(i=0;funcs[i].name;i++) {
if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
return(JS_FALSE);
if(funcs[i].type==JSTYPE_ALIAS)
continue;
method = JS_NewObject(cx, NULL, NULL, method_array); /* exception here June-7-2003 */
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
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
*/
js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
if(!js_DefineSyncProperties(cx, obj, props))
ret=JS_FALSE;
if(consts) {
if(!js_DefineConstIntegers(cx, obj, consts, flags))
ret=JS_FALSE;
}
#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);
}
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);
}
js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
/*
* 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(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);
}
}
}
#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;
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);
JSSTRING_TO_RASTRING(cx, str, line, &line_sz, NULL);
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
sbbs->lputs(level, line);
JS_RESUMEREQUEST(cx, rc);
}
if(str==NULL)
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
else
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
return(JS_TRUE);
}
static JSBool
js_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);
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);
}
JSSTRING_TO_RASTRING(cx, str, cstr, &cstr_sz, NULL);
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
if(sbbs->online != ON_REMOTE)
sbbs->lputs(LOG_INFO, cstr);
JS_RESUMEREQUEST(cx, rc);
}
if(str==NULL)
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
else
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
return(JS_TRUE);
}
static JSBool
js_write_raw(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
uintN i;
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);
return(JS_FALSE);
if(len < 1)
continue;
rc=JS_SUSPENDREQUEST(cx);
sbbs->putcom(str, len);
JS_RESUMEREQUEST(cx, rc);
}
return(JS_TRUE);
}
static JSBool
js_writeln(JSContext *cx, uintN argc, jsval *arglist)
{
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
js_write(cx,argc,arglist);
rc=JS_SUSPENDREQUEST(cx);
if(sbbs->online==ON_REMOTE)
sbbs->bputs(crlf);
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
static JSBool
js_printf(JSContext *cx, uintN argc, jsval *arglist)
jsval *argv=JS_ARGV(cx, arglist);
char* p;
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
if((p = js_sprintf(cx, 0, argc, argv))==NULL) {
JS_ReportError(cx,"js_sprintf failed");
return(JS_FALSE);
}
rc=JS_SUSPENDREQUEST(cx);
if(sbbs->online != ON_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;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
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);
}
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
static JSBool
js_confirm(JSContext *cx, uintN argc, jsval *arglist)
jsval *argv=JS_ARGV(cx, arglist);
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->yesno(cstr)));
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
js_deny(JSContext *cx, uintN argc, jsval *arglist)
jsval *argv=JS_ARGV(cx, arglist);
sbbs_t* sbbs;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->noyes(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;
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("[level,] value [,value]")
,JSDOCSTR("add a line of text to the server and/or system log, "
"<i>values</i> are typically string constants or variables, "
"<i>level</i> is the debug level/priority (default: <tt>LOG_INFO</tt>)")
,311
{"read", js_read, 0, JSTYPE_STRING, JSDOCSTR("[count]")
,JSDOCSTR("read up to count characters from input stream")
,311
},
{"readln", js_readln, 0, JSTYPE_STRING, JSDOCSTR("[count]")
,JSDOCSTR("read a single line, up to count characters, from input stream")
,311
},
{"write", js_write, 0, JSTYPE_VOID, JSDOCSTR("value [,value]")
,JSDOCSTR("send one or more values (typically strings) to the server output")
,311
},
{"write_raw", js_write_raw, 0, JSTYPE_VOID, JSDOCSTR("value [,value]")
,JSDOCSTR("send a stream of bytes (possibly containing NULLs or special control code sequences) to the server output")
},
{"print", js_writeln, 0, JSTYPE_ALIAS },
{"writeln", js_writeln, 0, JSTYPE_VOID, JSDOCSTR("value [,value]")
,JSDOCSTR("send a line of text to the console or event log with automatic line termination (CRLF), "
"<i>values</i> are typically string constants or variables (AKA print)")
,311
{"printf", js_printf, 1, JSTYPE_STRING, JSDOCSTR("string format [,value][,value]")
,JSDOCSTR("print a formatted string - <small>CAUTION: for experienced C programmers ONLY</small>")
,310
{"alert", js_alert, 1, JSTYPE_VOID, JSDOCSTR("value")
,JSDOCSTR("print an alert message (ala client-side JS)")
,310
{"prompt", js_prompt, 1, JSTYPE_STRING, JSDOCSTR("[text] [,value] [,mode=K_EDIT]")
,JSDOCSTR("displays a prompt (<i>text</i>) and returns a string of user input (ala client-side JS)")
,310
},
{"confirm", js_confirm, 1, JSTYPE_BOOLEAN, JSDOCSTR("value")
,JSDOCSTR("displays a Yes/No prompt and returns <i>true</i> or <i>false</i> "
"based on user's confirmation (ala client-side JS, <i>true</i> = yes)")
,310
{"deny", js_deny, 1, JSTYPE_BOOLEAN, JSDOCSTR("value")
,JSDOCSTR("displays a No/Yes prompt and returns <i>true</i> or <i>false</i> "
"based on user's denial (<i>true</i> = no)")
,31501
},
{0}
};
static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
char line[64];
char file[MAX_PATH+1];
sbbs_t* sbbs;
const char* warning;
jsrefcount rc;
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 ";
warning="warning ";
log_level = LOG_WARNING;
} else {
warning=nulstr;
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: %lu bytes"
,desc, startup->js.max_bytes);
if((*runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__)) == NULL)
return NULL;
if((js_cx = JS_NewContext(*runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
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;
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 */
,client_socket == INVALID_SOCKET ? NULL : &client, client_socket, -1 /* client */
,&js_server_props /* server */
,glob
))
rooted=true;
js_CreateUifcObject(js_cx, *glob);
js_CreateConioObject(js_cx, *glob);
/* 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, NULL, subscan))
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
)
{
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)==NULL)
break;
/* Internal JS Object */
if(cb!=NULL
&& js_CreateInternalJsObject(js_cx, *glob, cb, js_startup)==NULL)
break;
/* Client Object */
if(client!=NULL
&& js_CreateClientObject(js_cx, *glob, "client", client, client_socket, session)==NULL)
break;
/* Server */
if(props!=NULL
&& js_CreateServerObject(js_cx, *glob, props)==NULL)
break;
/* Socket Class */
if(js_CreateSocketClass(js_cx, *glob)==NULL)
break;
/* Queue Class */
if(js_CreateQueueClass(js_cx, *glob)==NULL)
break;
/* MsgBase Class */
if(js_CreateMsgBaseClass(js_cx, *glob, cfg)==NULL)
break;
/* FileBase Class */
if(js_CreateFileBaseClass(js_cx, *glob, node_cfg)==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, cfg)==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;
/* Area Objects */
if(!js_CreateUserObjects(js_cx, *glob, cfg, /* user: */NULL, client, /* html_index_fname: */NULL, /* subscan: */NULL))
break;
success=TRUE;
} while(0);
if(!success)
JS_RemoveObjectRoot(js_cx, glob);
return(success);
}
#endif /* JAVASCRIPT */
static BYTE* telnet_interpret(sbbs_t* sbbs, BYTE* inbuf, int inlen,
BYTE* outbuf, int& outlen)
{
BYTE* first_iac=NULL;
BYTE* first_cr=NULL;
int i;
if(inlen<1) {
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"
} 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);
#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) {
long cols = (sbbs->telnet_cmd[3]<<8) | sbbs->telnet_cmd[4];
long rows = (sbbs->telnet_cmd[5]<<8) | sbbs->telnet_cmd[6];
lprintf(LOG_DEBUG,"Node %d %s telnet window size: %ldx%ld"
,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;
}
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;
}
}
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
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];
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);
(void)sendsocket(client_socket, buf, 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);
(void)sendsocket(client_socket, buf, 3);
}
}
bool sbbs_t::request_telnet_opt(uchar cmd, uchar opt, unsigned waitforack)
if(telnet_mode&TELNET_MODE_OFF)
return false;
if(cmd==TELNET_DO || cmd==TELNET_DONT) { /* remote option */
if(telnet_remote_option[opt]==telnet_opt_ack(cmd))
return true; /* already set in this mode, do nothing */
telnet_remote_option[opt]=telnet_opt_ack(cmd);
} else { /* local option */
if(telnet_local_option[opt]==telnet_opt_ack(cmd))
return true; /* already set in this mode, do nothing */
telnet_local_option[opt]=telnet_opt_ack(cmd);
}
if(waitforack)
ResetEvent(telnet_ack_event);
send_telnet_cmd(cmd,opt);
if(waitforack)
return WaitForEvent(telnet_ack_event, waitforack)==WAIT_OBJECT_0;
return true;
}
static int crypt_pop_channel_data(sbbs_t *sbbs, char *inbuf, int want, int *got)
{
int status;
int cid;
char *cname;
int ret;
int closing_channel = -1;
*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, got);
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->session_channel) {
if (cryptStatusError(status = cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, cid))) {
GCESS(status, sbbs->client_socket, sbbs->ssh_session, "setting channel");
return status;
}
cname = get_crypt_attribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_TYPE);
lprintf(LOG_WARNING, "Node %d SSH WARNING: attempt to use channel '%s' (%d != %d)"
, sbbs->cfg.node_num, cname ? cname : "<unknown>", cid, sbbs->session_channel);
if (cname)
free_crypt_attrstr(cname);
closing_channel = cid;
if (cryptStatusError(status = cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0))) {
GCESS(status, sbbs->client_socket, sbbs->ssh_session, "closing channel");
return status;
}
continue;
}
}
else {
GCESS(status, sbbs->client_socket, sbbs->ssh_session, "getting channel id");
}
}
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;
ulong total_recv=0;
ulong total_pkts=0;
sbbs_t* sbbs = (sbbs_t*) arg;
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) {
#ifdef _WIN32 // No spy sockets
if (!socket_readable(sbbs->client_socket, 1000))
continue;
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;
#else
#error Spy sockets without poll() was removed in commit 3971ef4dcc3db19f400a648b6110718e56a64cf3
#endif
/* ^ ^
* \ * \ / * /
* /__\ \----/
* \______________/
* \/\/\/\/\/\/\/
* ------------
#ifdef _WIN32 // No spy sockets
#ifdef PREFER_POLL
if (fds[0].revents & POLLIN)
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
#error Spy sockets without poll() was removed in commit 3971ef4dcc3db19f400a648b6110718e56a64cf3
#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();
}
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;
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_TIMEOUT)
continue;
/* Handle the SSH error here... */
GCES(err, sbbs->cfg.node_num, sbbs->ssh_session, "popping data");
break;
}
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);
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);
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
#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));
if(sbbs->passthru_socket_active == true) {
BOOL writable = FALSE;
if(socket_check(sbbs->passthru_socket, NULL, &writable, 1000) && writable)
(void)sendsocket(sbbs->passthru_socket, (char*)wrbuf, wr);
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 */
sem_post(&sbbs->inbuf.sem);
continue; // Ignore the entire buffer
}
avail=RingBufFree(&sbbs->inbuf);
if(avail<wr)
lprintf(LOG_ERR,"!INPUT BUFFER FULL (%d free)", avail);
else
RingBufWrite(&sbbs->inbuf, wrbuf, wr);
// if(wr>100)
// mswait(500); // Throttle sender
}
sbbs->online=FALSE;
sbbs->sys_status|=SS_ABORT; /* as though Ctrl-C were hit */
sbbs->input_thread_running = false;
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 %lu bytes in %lu 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)
{
if(activate) {
BOOL rd = FALSE;
while(socket_check(client_socket_dup, &rd, /* wr_p */NULL, /* timeout */0) && rd) {
char ch;
if(recv(client_socket_dup, &ch, sizeof(ch), /* flags: */0) != sizeof(ch))
break;
}
/* Re-enable blocking (in case disabled by external program) */
ulong 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(&sbbs->outbuf));
}
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;
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;
rd = RingBufFree(&sbbs->outbuf) / 2;
if(rd == SOCKET_ERROR)
Loading
Loading full blame...