Skip to content
Snippets Groups Projects
main.cpp 172 KiB
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 "netwrap.h"
deuce's avatar
deuce committed
#include <multisock.h>
#include <limits.h>		// HOST_NAME_MAX

#ifdef __unix__
	#include <sys/un.h>
#endif

//#define SBBS_TELNET_ENVIRON_SUPPORT 1
//---------------------------------------------------------------------------

#define TELNET_SERVER "Synchronet Terminal Server"
#define STATUS_WFC	"Listening"

#define TIMEOUT_THREAD_WAIT		60			// Seconds (was 15)
#define IO_THREAD_BUF_SIZE	   	20000		// Bytes
#define TIMEOUT_MUTEX_FILE		12*60*60
rswindell's avatar
rswindell committed

	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)"
rswindell's avatar
rswindell committed
				, 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__);	\
rswindell's avatar
rswindell committed
			pthread_mutex_unlock(&sbbs->ssh_mutex);					\
		}															\
	} while(0)
rswindell's avatar
rswindell committed
	#define	SSH_END(x)
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];
deuce's avatar
deuce committed
struct xpms_set				*ts_set;
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="";
};

deuce's avatar
deuce committed
#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) {                                                       \
		lprintf(GCES_level, "Node %d SSH %s from %s", node, GCES_estr, __FUNCTION__);             \
deuce's avatar
deuce committed
		free_crypt_attrstr(GCES_estr);                                       \
} while (0)

deuce's avatar
deuce committed
#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) {                                                       \
		lprintf(GCES_level, "SSH %s from %s", GCES_estr, __FUNCTION__);     \
deuce's avatar
deuce committed
		free_crypt_attrstr(GCES_estr);                                       \
} while (0)

deuce's avatar
deuce committed
#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) {                                                       \
		lprintf(GCES_level, "%04d SSH %s from %s", sock, GCES_estr, __FUNCTION__);                \
deuce's avatar
deuce committed
		free_crypt_attrstr(GCES_estr);                                       \
} while (0)

#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);                \
extern "C" {

static bbs_startup_t* startup=NULL;

rswindell's avatar
rswindell committed
static const char* status(const char* str)
{
	if(startup!=NULL && startup->status!=NULL)
Deucе's avatar
Deucе committed
		startup->status(startup->cbdata,str);
rswindell's avatar
rswindell committed
	return str;
}

static void update_clients()
{
	if(startup!=NULL && startup->clients!=NULL)
		startup->clients(startup->cbdata,protected_uint32_value(node_threads_running));
}

void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(!update)
		listAddNodeData(&current_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);
	listRemoveTaggedNode(&current_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)
		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)
#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)
{
		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);
Deucе's avatar
Deucе committed
	if(startup==NULL || startup->event_lputs==NULL || level > startup->log_level)
		return(0);
Deucе's avatar
Deucе committed
	return(startup->event_lputs(startup->event_cbdata,level,str));
int lprintf(int level, const char *fmt, ...)
Deucе's avatar
Deucе committed
	va_start(argptr,fmt);
	vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
Deucе's avatar
Deucе committed
	va_end(argptr);
	return(lputs(level,sbuf));
/* 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);
	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));
}

deuce's avatar
deuce committed
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)
	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);
// Used by sbbs_t::ftp_put() and js_accept()
deuce's avatar
deuce committed
SOCKET accept_socket(SOCKET s, union xp_sockaddr* addr, socklen_t* addrlen)
deuce's avatar
deuce committed
	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);
deuce's avatar
deuce committed
/* TODO: IPv6 */
u_long resolve_ip(char *addr)
{
	HOSTENT*	host;
	char*		p;

	if(*addr==0)
		if(*p!='.' && !IS_DIGIT(*p))
			break;
	if(!(*p))
		return(inet_addr(addr));
	if((host=gethostbyname(addr))==NULL)
	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);
    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()
#if defined(HAS_DEV_RANDOM) && defined(RANDOM_DEV)
	if((rf=open(RANDOM_DEV, O_RDONLY|O_NONBLOCK))!=-1) {
		rd=read(rf, &seed, sizeof(seed));
deuce's avatar
deuce committed
	if (rd != sizeof(seed))
		seed = time32(NULL) ^ (uintmax_t)GetCurrentThreadId();
	sbbs_random(10);	/* Throw away first number */
}

int sbbs_random(int n)
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;
	}

/* Convert from Synchronet-specific jsSyncMethodSpec to JSAPI's JSFunctionSpec */
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);

rswindell's avatar
rswindell committed
	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));
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));
}


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",
deuce's avatar
deuce committed
	"null",
	"xml",
deuce's avatar
deuce committed
	"undefined"
js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
	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)
	jsval		val;
	JSObject*	method;
	JSObject*	method_array;
	JSString*	js_str;
deuce's avatar
deuce committed
	size_t		str_len=0;
	char		*str=NULL;

	/* Return existing method_list array if it's already been created */
deuce's avatar
deuce committed
	if(JS_GetProperty(cx,obj,method_array_name,&val) && val!=JSVAL_VOID) {
		method_array=JSVAL_TO_OBJECT(val);
deuce's avatar
deuce committed
		// 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++) {
deuce's avatar
deuce committed
			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)
deuce's avatar
deuce committed
		if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
				, NULL, NULL, 0))
deuce's avatar
deuce committed
	}
		if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
			return(JS_FALSE);

		if(funcs[i].type==JSTYPE_ALIAS)
			continue;

		method = JS_NewObject(cx, NULL, NULL, method_array);	/* exception here June-7-2003 */

		if(method==NULL)
			return(JS_FALSE);

		if(funcs[i].name!=NULL) {
			if((js_str=JS_NewStringCopyZ(cx,funcs[i].name))==NULL)
				return(JS_FALSE);
			val = STRING_TO_JSVAL(js_str);
			JS_SetProperty(cx, method, "name", &val);
		}

		val = INT_TO_JSVAL(funcs[i].nargs);
		if(!JS_SetProperty(cx, method, "nargs", &val))
			return(JS_FALSE);

		if((js_str=JS_NewStringCopyZ(cx,js_type_str[funcs[i].type]))==NULL)
			return(JS_FALSE);
		val = STRING_TO_JSVAL(js_str);
		JS_SetProperty(cx, method, "type", &val);

		if(funcs[i].args!=NULL) {
			if((js_str=JS_NewStringCopyZ(cx,funcs[i].args))==NULL)
				return(JS_FALSE);
			val = STRING_TO_JSVAL(js_str);
			JS_SetProperty(cx, method, "args", &val);
		}

			if((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((ver=funcs[i].ver) < 10000)		/* auto convert 313 to 31300 */
			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)
{
	JSBool	ret=JS_TRUE;

deuce's avatar
deuce committed
	if(props) {
		if(!js_DefineSyncProperties(cx, obj, props))
			ret=JS_FALSE;
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	if(funcs) {
deuce's avatar
deuce committed
		if(!js_DefineSyncMethods(cx, obj, funcs))
			ret=JS_FALSE;
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	if(consts) {
		if(!js_DefineConstIntegers(cx, obj, consts, flags))
			ret=JS_FALSE;
	}
js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
	/*
	 * 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);
		}
	}
js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
	/*
	 * NOTE: Any changes here should also be added to the same function in jsdoor.c
	 *       (ie: anything not Synchronet specific).
	 */
		if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
			return(JS_FALSE);
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(consts) {
deuce's avatar
deuce committed
		for(i=0;consts[i].name;i++) {
			if(name==NULL || strcmp(name, consts[i].name)==0) {
				val=INT_TO_JSVAL(consts[i].val);
deuce's avatar
deuce committed
				if(!JS_DefineProperty(cx, obj, consts[i].name, val ,NULL, NULL, flags))
					return(JS_FALSE);

				if(name)
					return(JS_TRUE);
			}
		}
	}
/* This is a stream-lined version of JS_DefineConstDoubles */
JSBool
js_DefineConstIntegers(JSContext* cx, JSObject* obj, jsConstIntSpec* ints, int flags)
		val=INT_TO_JSVAL(ints[i].val);

		if(!JS_DefineProperty(cx, obj, ints[i].name, val ,NULL, NULL, flags))
			return(JS_FALSE);
	}
js_log(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
	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;
	}
		if((str=JS_ValueToString(cx, argv[i]))==NULL) {
			FREE_AND_NULL(line);
			return(JS_FALSE);
deuce's avatar
deuce committed
		JSSTRING_TO_RASTRING(cx, str, line, &line_sz, NULL);
		if(line==NULL)
	if(line != NULL)
		free(line);
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
js_read(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	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);

	len=RingBufRead(&sbbs->inbuf,buf,len);
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyN(cx,(char*)buf,len)));
js_readln(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	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);

	len=sbbs->getstr(buf,len,K_NONE);
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,buf)));
js_write(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	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);
deuce's avatar
deuce committed
			return(JS_FALSE);
deuce's avatar
deuce committed
		JSSTRING_TO_RASTRING(cx, str, cstr, &cstr_sz, NULL);
deuce's avatar
deuce committed
		if(cstr==NULL)
			sbbs->lputs(LOG_INFO, cstr);
deuce's avatar
deuce committed
			sbbs->bputs(cstr);
	FREE_AND_NULL(cstr);
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
js_write_raw(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
    char*		str=NULL;
    size_t		str_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++) {
deuce's avatar
deuce committed
		JSVALUE_TO_RASTRING(cx, argv[i], str, &str_sz, &len);
		if(str==NULL)
	if (str != NULL)
		free(str);
js_writeln(JSContext *cx, uintN argc, jsval *arglist)
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	if(sbbs->online==ON_REMOTE)
		sbbs->bputs(crlf);
js_printf(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	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");
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p)));
js_alert(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
	char		*cstr;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

deuce's avatar
deuce committed
	JSVALUE_TO_MSTRING(cx, argv[0], cstr, NULL);
deuce's avatar
deuce committed
	if(cstr==NULL)
	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);
	}
deuce's avatar
deuce committed
	free(cstr);
deuce's avatar
deuce committed
	JS_SET_RVAL(cx, arglist, argv[0]);

js_confirm(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
	char		*cstr;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

deuce's avatar
deuce committed
	JSVALUE_TO_MSTRING(cx, argv[0], cstr, NULL);
deuce's avatar
deuce committed
	if(cstr==NULL)
deuce's avatar
deuce committed
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->yesno(cstr)));
deuce's avatar
deuce committed
	free(cstr);
js_deny(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	sbbs_t*		sbbs;
	jsrefcount	rc;
deuce's avatar
deuce committed
	char		*cstr;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

deuce's avatar
deuce committed
	JSVALUE_TO_MSTRING(cx, argv[0], cstr, NULL);
deuce's avatar
deuce committed
	if(cstr==NULL)
	    return(JS_FALSE);

	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->noyes(cstr)));
deuce's avatar
deuce committed
	free(cstr);
	JS_RESUMEREQUEST(cx, rc);
	return(JS_TRUE);
}


js_prompt(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	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);
	if(prompt != NULL) {
		sbbs->bprintf("\1n\1y\1h%s\1w: ",prompt);
		free(prompt);
	}
	result = sbbs->getstr(instr, sizeof(instr)-1, mode);
		JS_SET_RVAL(cx, arglist, JSVAL_NULL);

	if((str=JS_NewStringCopyZ(cx, instr))==NULL)
	    return(JS_FALSE);

	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
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>)")
	{"read",			js_read,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read up to count characters from input stream")
	},
	{"readln",			js_readln,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read a single line, up to count characters, from input stream")
	{"write",			js_write,			0,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
	,JSDOCSTR("send one or more values (typically strings) to the server output")
	{"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")
rswindell's avatar
rswindell committed
	,314
	{"print",			js_writeln,			0,	JSTYPE_ALIAS },
    {"writeln",         js_writeln,         0,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
	,JSDOCSTR("send a line of text to the console or event log with automatic line termination (CRLF), "
		"<i>values</i> are typically string constants or variables (AKA print)")
    {"printf",          js_printf,          1,	JSTYPE_STRING,	JSDOCSTR("string format [,value][,value]")
	,JSDOCSTR("print a formatted string - <small>CAUTION: for experienced C programmers ONLY</small>")
	{"alert",			js_alert,			1,	JSTYPE_VOID,	JSDOCSTR("value")
	,JSDOCSTR("print an alert message (ala client-side JS)")
	{"prompt",			js_prompt,			1,	JSTYPE_STRING,	JSDOCSTR("[text] [,value] [,mode=K_EDIT]")
	,JSDOCSTR("displays a prompt (<i>text</i>) and returns a string of user input (ala client-side JS)")
	},
	{"confirm",			js_confirm,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("value")
	,JSDOCSTR("displays a Yes/No prompt and returns <i>true</i> or <i>false</i> "
		"based on user's confirmation (ala client-side JS, <i>true</i> = yes)")
	{"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;

	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return;
		sbbs->lprintf(LOG_ERR,"!JavaScript: %s", message);
		SAFEPRINTF(file," %s",report->filename);
		SAFEPRINTF(line," line %d",report->lineno);
	else
		line[0]=0;

	if(JSREPORT_IS_WARNING(report->flags)) {
		if(JSREPORT_IS_STRICT(report->flags))
		log_level = LOG_WARNING;
	} else {
		log_level = LOG_ERR;
	}
	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);
JSContext* sbbs_t::js_init(JSRuntime** runtime, JSObject** glob, const char* desc)
	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)
	JS_SetOptions(js_cx, startup->js.options);
	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;
Deucе's avatar
Deucе committed
	js_callback.events_supported = TRUE;

	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 */
deuce's avatar
deuce committed
#ifdef BUILD_JSDOCS
		js_CreateUifcObject(js_cx, *glob);
		js_CreateConioObject(js_cx, *glob);
deuce's avatar
deuce committed
#endif

		if(js_CreateBbsObject(js_cx, *glob)==NULL)
		if(js_CreateConsoleObject(js_cx, *glob)==NULL)
		JS_DestroyContext(js_cx);
		js_cx=NULL;
void sbbs_t::js_cleanup(void)
		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))
		/*
		 * 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 */
			&& js_CreateInternalJsObject(js_cx, *glob, cb, js_startup)==NULL)
			break;

		/* Client Object */
			&& 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;

		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;

deuce's avatar
deuce committed
		/* CryptKeyset Class */
		if(js_CreateCryptKeysetClass(js_cx, *glob)==NULL)
			break;

		/* CryptCert Class */
		if(js_CreateCryptCertClass(js_cx, *glob)==NULL)
			break;

		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);
		}
	}
		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) {
					&& sbbs->telnet_cmd[sbbs->telnet_cmdlen-2]==TELNET_IAC) {
					if(startup->options&BBS_OPT_DEBUG_TELNET)
						lprintf(LOG_DEBUG,"Node %d %s telnet sub-negotiation command: %s (%u bytes)"
	                		,sbbs->cfg.node_num
							,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
							,telnet_opt_desc(option)
							,sbbs->telnet_cmdlen);
						&& sbbs->telnet_cmd[3]==TELNET_TERM_IS) {
rswindell's avatar
rswindell committed
						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"
rswindell's avatar
rswindell committed
							,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);
rswindell's avatar
rswindell committed
						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 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"
					} 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"
rswindell's avatar
rswindell committed
						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]);
			} // Sub-negotiation command
            else if(sbbs->telnet_cmdlen==2 && inbuf[i]<TELNET_WILL) {
				if(startup->options&BBS_OPT_DEBUG_TELNET)
            		lprintf(LOG_DEBUG,"Node %d %s telnet cmd: %s"
	                	,sbbs->cfg.node_num
						,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
            else if(sbbs->telnet_cmdlen>=3) {	/* telnet option negotiation */
				if(startup->options&BBS_OPT_DEBUG_TELNET)
					lprintf(LOG_DEBUG,"Node %d %s telnet cmd: %s %s"
						,sbbs->cfg.node_num
						,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received"
						,telnet_cmd_desc(command)
						,telnet_opt_desc(option));
				if(!(sbbs->telnet_mode&TELNET_MODE_GATE)) {
					if(command==TELNET_DO || command==TELNET_DONT) {	/* local options */
						if(sbbs->telnet_local_option[option]==command)
							SetEvent(sbbs->telnet_ack_event);
						else {
							sbbs->telnet_local_option[option]=command;
							sbbs->send_telnet_cmd(telnet_opt_ack(command),option);
						}
					} else { /* WILL/WONT (remote options) */
						if(sbbs->telnet_remote_option[option]==command)
							SetEvent(sbbs->telnet_ack_event);
						else {
							switch(option) {
								case TELNET_BINARY_TX:
								case TELNET_ECHO:
								case TELNET_TERM_TYPE:
								case TELNET_TERM_SPEED:
								case TELNET_SUP_GA:
								case TELNET_NEGOTIATE_WINDOW_SIZE:
								case TELNET_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_NEW_ENVIRON,TELNET_ENVIRON_SEND //,TELNET_ENVIRON_VAR
								,TELNET_IAC,TELNET_SE);
							sbbs->putcom(buf,len);
						}
#endif
			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(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)
	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);
	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;

	*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);
rswindell's avatar
rswindell committed
					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);
					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;
Deucе's avatar
Deucе committed
	SOCKET sock;
#ifdef PREFER_POLL
Deucе's avatar
Deucе committed
	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) {

#ifdef _WIN32	// No spy sockets
Deucе's avatar
Deucе committed
		if (!socket_readable(sbbs->client_socket, 1000))
			continue;
Deucе's avatar
Deucе committed
#else
#ifdef PREFER_POLL
Deucе's avatar
Deucе committed
		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;
Deucе's avatar
Deucе committed
			nfds++;
Deucе's avatar
Deucе committed
		if (poll(fds, nfds, 1000) < 1)
			continue;
#else
#error Spy sockets without poll() was removed in commit 3971ef4dcc3db19f400a648b6110718e56a64cf3
#endif
Deucе's avatar
Deucе committed
#endif
Deucе's avatar
Deucе committed
		if(sbbs->client_socket==INVALID_SOCKET)
deuce's avatar
deuce committed
 *      \______    ______/
deuce's avatar
deuce committed
 *        -----   ------           /----\
 *              ||               -< Boo! |
 *       \______________/
 *        \/\/\/\/\/\/\/
 *         ------------
#ifdef _WIN32	// No spy sockets
Deucе's avatar
Deucе committed
		sock=sbbs->client_socket;
#else
#ifdef PREFER_POLL
		if (fds[0].revents & POLLIN)
Deucе's avatar
Deucе committed
			sock = sbbs->client_socket;
		else if(uspy_socket[sbbs->cfg.node_num - 1] != INVALID_SOCKET && fds[1].revents & POLLIN) {
Deucе's avatar
Deucе committed
			if(socket_recvdone(uspy_socket[sbbs->cfg.node_num-1], 0)) {
				close_socket(uspy_socket[sbbs->cfg.node_num-1]);
deuce's avatar
deuce committed
				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];
			continue;
#else
#error Spy sockets without poll() was removed in commit 3971ef4dcc3db19f400a648b6110718e56a64cf3
#endif
Deucе's avatar
Deucе committed
#endif
			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) {
Deucе's avatar
Deucе committed
			if(rd==0)	/* input buffer still full */
	    if(rd > (int)sizeof(inbuf))
        	rd=sizeof(inbuf);

Deucе's avatar
Deucе committed
		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) {
			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");
				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);

rswindell's avatar
rswindell committed
		if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
			sbbs->errormsg(WHERE,ERR_UNLOCK,"input_thread_mutex",0);
Deucе's avatar
Deucе committed
		if (rd == 0 && !socket_recvdone(sock, 0))
			continue;

			if(sock==sbbs->client_socket)  {
				if(!sbbs->online)	// sbbs_t::hangup() called?
					break;
    	            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__
					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
		if(sock!=sbbs->client_socket)
		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);
		/* First level Ctrl-C checking */
		if(!(sbbs->cfg.ctrlkey_passthru&(1<<CTRL_C))
			&& !(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->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));
/*
 * This thread simply copies anything it manages to read from the
 * passthru_socket into the output ringbuffer.
 */
	char	inbuf[IO_THREAD_BUF_SIZE / 2];
	while(sbbs->online && sbbs->passthru_socket!=INVALID_SOCKET && !terminate_server) {
Deucе's avatar
Deucе committed
		if (!socket_readable(sbbs->passthru_socket, 1000))
			continue;
Deucе's avatar
Deucе committed
		if(sbbs->passthru_socket==INVALID_SOCKET)
Deucе's avatar
Deucе committed

		rd = RingBufFree(&sbbs->outbuf) / 2;
		if(rd > (int)sizeof(inbuf))
rswindell's avatar
rswindell committed
			rd = sizeof(inbuf);
rswindell's avatar
rswindell committed
    	rd = recv(sbbs->passthru_socket, inbuf, rd, 0);
Loading
Loading full blame...