main.cpp 154 KB
Newer Older
1
2
/* main.cpp */

3
/* Synchronet terminal server thread and related functions */
4
5
6
7
8
9
10

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright 2009 Rob Swindell - http://www.synchro.net/copyright.html		*
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
#include "ident.h"
#include "telnet.h" 
41
#include "netwrap.h"
42
#include "js_rtpool.h"
43
#include "js_request.h"
44
45
46

#ifdef __unix__
	#include <sys/un.h>
deuce's avatar
deuce committed
47
48
49
50
	#ifndef SUN_LEN
		#define SUN_LEN(su) \
	        (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
	#endif
51
52
53
54
#endif

//---------------------------------------------------------------------------

55
#define TELNET_SERVER "Synchronet Terminal Server"
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#define STATUS_WFC	"Listening"

#define TIMEOUT_THREAD_WAIT		60			// Seconds (was 15)
#define IO_THREAD_BUF_SIZE	   	20000		// Bytes

// Globals
#ifdef _WIN32
	HANDLE		exec_mutex=NULL;
	HINSTANCE	hK32=NULL;

	#if defined(_DEBUG) && defined(_MSC_VER)
			HANDLE	debug_log=INVALID_HANDLE_VALUE;
		   _CrtMemState mem_chkpoint;
	#endif // _DEBUG && _MSC_VER

#endif // _WIN32

73
#ifdef USE_CRYPTLIB
rswindell's avatar
rswindell committed
74
	#define SSH_END()	if(ssh)	cryptDestroySession(sbbs->ssh_session);
75
76
77
78
#else
	#define	SSH_END()
#endif

79
80
81
time_t	uptime=0;
DWORD	served=0;

deuce's avatar
deuce committed
82
static	DWORD node_threads_running=0;
83
static	ulong thread_count=0;
84
85
86
87
88
89
90
91
92
93
		
char 	lastuseron[LEN_ALIAS+1];  /* Name of user last online */
RingBuf* node_inbuf[MAX_NODES];
SOCKET	spy_socket[MAX_NODES];
#ifdef __unix__
SOCKET	uspy_socket[MAX_NODES];	  /* UNIX domain spy sockets */
#endif
SOCKET	node_socket[MAX_NODES];
static	SOCKET telnet_socket=INVALID_SOCKET;
static	SOCKET rlogin_socket=INVALID_SOCKET;
94
95
96
#ifdef USE_CRYPTLIB
static	SOCKET ssh_socket=INVALID_SOCKET;
#endif
97
98
99
100
101
static	sbbs_t*	sbbs=NULL;
static	scfg_t	scfg;
static	char *	text[TOTAL_TEXT];
static	WORD	first_node;
static	WORD	last_node;
102
static	bool	terminate_server=false;
103
104
static	str_list_t recycle_semfiles;
static	str_list_t shutdown_semfiles;
105
106
107
#ifdef _THREAD_SUID_BROKEN
int	thread_suid_broken=TRUE;			/* NPTL is no longer broken */
#endif
108
109
110
111
112

extern "C" {

static bbs_startup_t* startup=NULL;

113
static void status(const char* str)
114
115
{
	if(startup!=NULL && startup->status!=NULL)
116
	    startup->status(startup->cbdata,str);
117
118
119
120
121
}

static void update_clients()
{
	if(startup!=NULL && startup->clients!=NULL)
122
		startup->clients(startup->cbdata,node_threads_running);
123
124
125
126
127
}

void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
128
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
129
130
131
132
133
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
134
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
135
136
137
138
139
140
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
141
		startup->thread_up(startup->cbdata,TRUE,setuid);
142
143
144
145
146
147
148
}

static void thread_down()
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
149
		startup->thread_up(startup->cbdata,FALSE,FALSE);
150
151
}

152
int lputs(int level, const char* str)
153
{
154
	if(level <= LOG_ERR)
155
		errorlog(&scfg,startup==NULL ? NULL:startup->host_name, str);
156

157
	if(startup==NULL || startup->lputs==NULL || str==NULL || level > startup->log_level)
158
159
    	return(0);

160
161
162
163
164
#if defined(_WIN32)
	if(IsBadCodePtr((FARPROC)startup->lputs))
		return(0);
#endif

165
    return(startup->lputs(startup->cbdata,level,str));
166
167
}

168
int lprintf(int level, const char *fmt, ...)
169
170
171
172
173
174
175
176
{
	va_list argptr;
	char sbuf[1024];

    va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
177
    return(lputs(level,sbuf));
178
179
}

180
int eprintf(int level, const char *fmt, ...)
181
182
183
184
{
	va_list argptr;
	char sbuf[1024];

185
    if(startup==NULL || startup->event_lputs==NULL || level > startup->log_level)
186
187
188
189
190
191
        return(0);

    va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
192
	strip_ctrl(sbuf, sbuf);
193
    return(startup->event_lputs(startup->event_cbdata,level,sbuf));
194
195
}

196
SOCKET open_socket(int type, const char* protocol)
197
198
199
200
201
202
{
	SOCKET	sock;
	char	error[256];

	sock=socket(AF_INET, type, IPPROTO_IP);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
203
		startup->socket_open(startup->cbdata,TRUE);
204
	if(sock!=INVALID_SOCKET && set_socket_options(&scfg, sock, protocol, error, sizeof(error)))
205
		lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
206
207
208
209
210
211
212
213
214
215

	return(sock);
}

SOCKET accept_socket(SOCKET s, SOCKADDR* addr, socklen_t* addrlen)
{
	SOCKET	sock;

	sock=accept(s,addr,addrlen);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
216
		startup->socket_open(startup->cbdata,TRUE);
217
218
219
220
221
222
223
224
225
226
227
228
229

	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);
230
	if(startup!=NULL && startup->socket_open!=NULL)
231
		startup->socket_open(startup->cbdata,FALSE);
232
	if(result!=0 && ERROR_VALUE!=ENOTSOCK)
233
		lprintf(LOG_WARNING,"!ERROR %d closing socket %d",ERROR_VALUE,sock);
234
235
236
237
238
239
240
241
242
243
	return(result);
}


u_long resolve_ip(char *addr)
{
	HOSTENT*	host;
	char*		p;

	if(*addr==0)
244
		return((u_long)INADDR_NONE);
245
246
247
248
249
250
251

	for(p=addr;*p;p++)
		if(*p!='.' && !isdigit(*p))
			break;
	if(!(*p))
		return(inet_addr(addr));
	if((host=gethostbyname(addr))==NULL) 
252
		return((u_long)INADDR_NONE);
253
254
255
256
257
	return(*((ulong*)host->h_addr_list[0]));
}

} /* extern "C" */

258
259
260
261
262
263
264
265
266
267
268
#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) {
269
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
270
271
272
273
		WSAInitialized=TRUE;
		return(TRUE);
	}

274
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
275
276
277
278
279
280
281
282
283
284
	return(FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
#define SOCKLIB_DESC NULL

#endif

285
286
DLLEXPORT void DLLCALL sbbs_srand()
{
287
288
	DWORD seed = time(NULL) ^ (DWORD)GetCurrentThreadId();

289
	xp_randomize();
290
291
292
293
294
295
296
297
298
299
#if defined(HAS_DEV_RANDOM) && defined(RANDOM_DEV)
	int     rf;

	if((rf=open(RANDOM_DEV, O_RDONLY))!=-1) {
		read(rf, &seed, sizeof(seed));
		close(rf);
	}
#endif

 	srand(seed);
300
301
302
	sbbs_random(10);	/* Throw away first number */
}

303
int DLLCALL sbbs_random(int n)
304
305
306
307
{
	return(xp_random(n));
}

308
309
#ifdef JAVASCRIPT

310
311
static js_server_props_t js_server_props;

312
313
314
315
316
317
318
319
320
321
322
323
JSBool	
DLLCALL js_CreateArrayOfStrings(JSContext* cx, JSObject* parent, const char* name, char* str[],uintN flags)
{
	JSObject*	array;
	JSString*	js_str;
	jsval		val;
	size_t		i;
	jsuint		len=0;
		
	if(JS_GetProperty(cx,parent,name,&val) && val!=JSVAL_VOID)
		array=JSVAL_TO_OBJECT(val);
	else
324
325
		if((array=JS_NewArrayObject(cx, 0, NULL))==NULL)	/* Assertion here, in _heap_alloc_dbg, June-21-2004 */
			return(JS_FALSE);								/* Caused by nntpservice.js? */
326

327
328
329
330
	if(!JS_DefineProperty(cx, parent, name, OBJECT_TO_JSVAL(array)
		,NULL,NULL,flags))
		return(JS_FALSE);

331
332
333
334
335
336
337
338
339
340
341
	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;
	}

342
	return(JS_TRUE);
343
344
}

345
/* Convert from Synchronet-specific jsSyncMethodSpec to JSAPI's JSFunctionSpec */
346
347

JSBool
348
DLLCALL js_DescribeSyncObject(JSContext* cx, JSObject* obj, const char* str, int ver)
349
350
351
352
353
354
{
	JSString* js_str = JS_NewStringCopyZ(cx, str);

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

rswindell's avatar
rswindell committed
355
356
357
	if(ver < 10000)		/* auto convert 313 to 31300 */
		ver*=100;

358
	return(JS_DefineProperty(cx,obj,"_description"
359
360
361
			,STRING_TO_JSVAL(js_str),NULL,NULL,JSPROP_READONLY)
		&& JS_DefineProperty(cx,obj,"_ver"
			,INT_TO_JSVAL(ver),NULL,NULL,JSPROP_READONLY));
362
363
364
}

JSBool
365
DLLCALL js_DescribeSyncConstructor(JSContext* cx, JSObject* obj, const char* str)
366
367
368
369
370
371
372
373
374
375
{
	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));
}

376
#ifdef BUILD_JSDOCS
377
378

static const char* method_array_name = "_method_list";
379
static const char* propver_array_name = "_property_ver_list";
380
381
382
383
384
385
386
387
388
389
390
391
392

/*
 * 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",
393
394
	"array",
	"alias",
rswindell's avatar
rswindell committed
395
396
	"undefined",
	"null"
397
398
};

399
400
401
402
JSBool
DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
	uint		i;
403
	long		ver;
404
405
406
407
408
409
410
411
412
413
414
415
	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++) {
416
417
		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))
418
419
			return(JS_FALSE);
		if(props[i].flags&JSPROP_ENUMERATE) {	/* No need to version invisible props */
420
421
422
			if((ver=props[i].ver) < 10000)		/* auto convert 313 to 31300 */
				ver*=100;
			val = INT_TO_JSVAL(ver);
423
424
425
426
427
428
429
430
			if(!JS_SetElement(cx, array, len++, &val))
				return(JS_FALSE);
		}
	}

	return(JS_TRUE);
}

431
JSBool 
432
DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs, BOOL append)
433
434
435
{
	int			i;
	jsuint		len=0;
436
	long		ver;
437
438
439
440
441
442
443
444
445
446
447
448
	jsval		val;
	JSObject*	method;
	JSObject*	method_array;
	JSString*	js_str;

	/* Return existing method_list array if it's already been created */
	if(JS_GetProperty(cx,obj,method_array_name,&val) && val!=JSVAL_VOID)
		method_array=JSVAL_TO_OBJECT(val);
	else
		if((method_array=JS_NewArrayObject(cx, 0, NULL))==NULL) 
			return(JS_FALSE);

449
450
451
452
	if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
		, NULL, NULL, 0))
		return(JS_FALSE);

453
454
455
456
457
458
	if(append)
		if(!JS_GetArrayLength(cx, method_array, &len))
			return(JS_FALSE);

	for(i=0;funcs[i].name;i++) {

459
460
		if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
			return(JS_FALSE);
461
462
463
464

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

465
		method = JS_NewObject(cx, NULL, NULL, method_array);	/* exception here June-7-2003 */
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499

		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);
		}

500
		if(funcs[i].ver) {
501
			if((ver=funcs[i].ver) < 10000)		/* auto convert 313 to 31300 */
502
503
				ver*=100;
			val = INT_TO_JSVAL(ver);
504
505
506
			JS_SetProperty(cx,method, "ver", &val);
		}

507
508
509
510
511
512
513
514
		val=OBJECT_TO_JSVAL(method);
		if(!JS_SetElement(cx, method_array, len+i, &val))
			return(JS_FALSE);
	}

	return(JS_TRUE);
}

515
516
517
518
519
/*
 * Always resolve all here since
 * 1) We'll always be enumerating anyways
 * 2) The speed penalty won't be seen in production code anyways
 */
520
521
JSBool
DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
522
523
524
{
	JSBool	ret=JS_TRUE;

deuce's avatar
deuce committed
525
	if(props) {
526
527
		if(!js_DefineSyncProperties(cx, obj, props))
			ret=JS_FALSE;
deuce's avatar
deuce committed
528
	}
529
		
deuce's avatar
deuce committed
530
531
	if(funcs) {
		if(!js_DefineSyncMethods(cx, obj, funcs, 0))
532
			ret=JS_FALSE;
deuce's avatar
deuce committed
533
	}
534

deuce's avatar
deuce committed
535
536
537
538
	if(consts) {
		if(!js_DefineConstIntegers(cx, obj, consts, flags))
			ret=JS_FALSE;
	}
539

540
541
542
	return(ret);
}

543
#else // NON-JSDOCS
544

545
546
547
548
549
550
551
JSBool
DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
	uint i;

	for(i=0;props[i].name;i++) 
		if(!JS_DefinePropertyWithTinyId(cx, obj, 
552
			props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
553
554
555
556
557
558
			return(JS_FALSE);

	return(JS_TRUE);
}


559
JSBool 
560
DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs, BOOL append)
561
{
562
	uint i;
563
564

	for(i=0;funcs[i].name;i++)
565
566
		if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
			return(JS_FALSE);
567
568
569
	return(JS_TRUE);
}

570
571
JSBool
DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
572
573
{
	uint i;
574
	jsval	val;
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596

	if(props) {
		for(i=0;props[i].name;i++) {
			if(name==NULL || strcmp(name, props[i].name)==0) {
				if(!JS_DefinePropertyWithTinyId(cx, obj, 
						props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
					return(JS_FALSE);
				if(name)
					return(JS_TRUE);
			}
		}
	}
	if(funcs) {
		for(i=0;funcs[i].name;i++) {
			if(name==NULL || strcmp(name, funcs[i].name)==0) {
				if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
					return(JS_FALSE);
				if(name)
					return(JS_TRUE);
			}
		}
	}
597
	if(consts) {
deuce's avatar
deuce committed
598
		for(i=0;consts[i].name;i++) {
599
			if(name==NULL || strcmp(name, consts[i].name)==0) {
deuce's avatar
deuce committed
600
	        	if(!JS_NewNumberValue(cx, consts[i].val, &val))
601
602
					return(JS_FALSE);

deuce's avatar
deuce committed
603
				if(!JS_DefineProperty(cx, obj, consts[i].name, val ,NULL, NULL, flags))
604
605
606
607
608
609
610
					return(JS_FALSE);

				if(name)
					return(JS_TRUE);
			}
		}
	}
611
612
613
614

	return(JS_TRUE);
}

615
616
#endif

617
618
619
620
621
622
623
624
625
626
627
628
629
630
/* This is a stream-lined version of JS_DefineConstDoubles */
JSBool
DLLCALL js_DefineConstIntegers(JSContext* cx, JSObject* obj, jsConstIntSpec* ints, int flags)
{
	uint	i;
	jsval	val;

	for(i=0;ints[i].name;i++) {
        if(!JS_NewNumberValue(cx, ints[i].val, &val))
			return(JS_FALSE);

		if(!JS_DefineProperty(cx, obj, ints[i].name, val ,NULL, NULL, flags))
			return(JS_FALSE);
	}
631

632
633
634
	return(JS_TRUE);
}

635
636
637
638
639
640
641
642
643
644
645
646
647
648
char*
DLLCALL js_ValueToStringBytes(JSContext* cx, jsval val, size_t* len)
{
	JSString* str;
	
	if((str=JS_ValueToString(cx, val))==NULL)
		return(NULL);

	if(len!=NULL)
		*len = JS_GetStringLength(str);

	return(JS_GetStringBytes(str));
}

649
650
651
static JSBool
js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
652
653
    uintN		i=0;
	int32		level=LOG_INFO;
654
    JSString*	str=NULL;
655
	sbbs_t*		sbbs;
656
	jsrefcount	rc;
657
658
659
660

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

661
	if(argc > 1 && JSVAL_IS_NUMBER(argv[i]))
662
663
664
		JS_ValueToInt32(cx,argv[i++],&level);

    for(; i<argc; i++) {
665
		if((str=JS_ValueToString(cx, argv[i]))==NULL) {
666
			JS_RESUMEREQUEST(cx, rc);
667
		    return(JS_FALSE);
668
		}
669
		rc=JS_SUSPENDREQUEST(cx);
670
		if(sbbs->online==ON_LOCAL) {
671
			if(startup!=NULL && startup->event_lputs!=NULL && level <= startup->log_level)
672
				startup->event_lputs(startup->event_cbdata,level,JS_GetStringBytes(str));
673
		} else
674
			lprintf(level,"Node %d %s", sbbs->cfg.node_num, JS_GetStringBytes(str));
675
		JS_RESUMEREQUEST(cx, rc);
676
	}
677

678
679
680
681
	if(str==NULL)
		*rval = JSVAL_VOID;
	else
		*rval = STRING_TO_JSVAL(str);
682
683
684
    return(JS_TRUE);
}

685
686
687
688
689
690
static JSBool
js_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	uchar*		buf;
	int32		len=128;
	sbbs_t*		sbbs;
691
	jsrefcount	rc;
692
693
694
695
696
697
698
699
700
701

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

	if(argc)
		JS_ValueToInt32(cx,argv[0],&len);

	if((buf=(uchar*)malloc(len))==NULL)
		return(JS_TRUE);

702
	rc=JS_SUSPENDREQUEST(cx);
703
	len=RingBufRead(&sbbs->inbuf,buf,len);
704
	JS_RESUMEREQUEST(cx, rc);
705
706
707
708
709
710
711
712
713
714
715
716
717
718

	if(len>0)
		*rval = STRING_TO_JSVAL(JS_NewStringCopyN(cx,(char*)buf,len));

	free(buf);
	return(JS_TRUE);
}

static JSBool
js_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		buf;
	int32		len=128;
	sbbs_t*		sbbs;
719
	jsrefcount	rc;
720
721
722
723
724
725
726
727
728
729

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

	if(argc)
		JS_ValueToInt32(cx,argv[0],&len);

	if((buf=(char*)malloc(len))==NULL)
		return(JS_TRUE);

730
	rc=JS_SUSPENDREQUEST(cx);
731
	len=sbbs->getstr(buf,len,K_NONE);
732
	JS_RESUMEREQUEST(cx, rc);
733
734
735
736
737
738
739
740

	if(len>0)
		*rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,buf));

	free(buf);
	return(JS_TRUE);
}

741
static JSBool
742
js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
743
744
{
    uintN		i;
745
    JSString*	str=NULL;
746
	sbbs_t*		sbbs;
747
	jsrefcount	rc;
748
749
750
751
752
753
754

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

    for (i = 0; i < argc; i++) {
		if((str=JS_ValueToString(cx, argv[i]))==NULL)
		    return(JS_FALSE);
755
		rc=JS_SUSPENDREQUEST(cx);
756
		if(sbbs->online==ON_LOCAL)
757
			eprintf(LOG_INFO,"%s",JS_GetStringBytes(str));
758
759
		else
			sbbs->bputs(JS_GetStringBytes(str));
760
		JS_RESUMEREQUEST(cx, rc);
761
762
763
764
765
766
767
768
769
	}

	if(str==NULL)
		*rval = JSVAL_VOID;
	else
		*rval = STRING_TO_JSVAL(str);
    return(JS_TRUE);
}

770
771
772
773
774
775
776
static JSBool
js_write_raw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    uintN		i;
    char*	str=NULL;
	size_t		len;
	sbbs_t*		sbbs;
777
	jsrefcount	rc;
778
779
780
781
782
783
784

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

    for (i = 0; i < argc; i++) {
		if((str=js_ValueToStringBytes(cx, argv[i], &len))==NULL)
		    return(JS_FALSE);
785
		rc=JS_SUSPENDREQUEST(cx);
786
		sbbs->putcom(str, len);
787
		JS_RESUMEREQUEST(cx, rc);
788
789
790
791
792
	}

    return(JS_TRUE);
}

793
static JSBool
794
js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
795
796
{
	sbbs_t*		sbbs;
797
	jsrefcount	rc;
798
799
800
801
802

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

	js_write(cx,obj,argc,argv,rval);
803
	rc=JS_SUSPENDREQUEST(cx);
804
805
	if(sbbs->online==ON_REMOTE)
		sbbs->bputs(crlf);
806
	JS_RESUMEREQUEST(cx, rc);
807
808
809
810
811
812
813
814
815

    return(JS_TRUE);
}

static JSBool
js_printf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
	sbbs_t*		sbbs;
816
	jsrefcount	rc;
817
818
819
820

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

821
822
	if((p = js_sprintf(cx, 0, argc, argv))==NULL) {
		JS_ReportError(cx,"js_sprintf failed");
823
824
825
		return(JS_FALSE);
	}

826
	rc=JS_SUSPENDREQUEST(cx);
827
	if(sbbs->online==ON_LOCAL)
828
		eprintf(LOG_INFO,"%s",p);
829
830
	else
		sbbs->bputs(p);
831
	JS_RESUMEREQUEST(cx, rc);
832
833
834

	*rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p));

835
	js_sprintf_free(p);
836
837
838
839
840
841
842
843
844

    return(JS_TRUE);
}

static JSBool
js_alert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *	str;
	sbbs_t*		sbbs;
845
	jsrefcount	rc;
846
847
848
849
850
851
852

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

	if((str=JS_ValueToString(cx, argv[0]))==NULL)
	    return(JS_FALSE);

853
	rc=JS_SUSPENDREQUEST(cx);
854
855
856
857
	sbbs->attr(sbbs->cfg.color[clr_err]);
	sbbs->bputs(JS_GetStringBytes(str));
	sbbs->attr(LIGHTGRAY);
	sbbs->bputs(crlf);
858
	JS_RESUMEREQUEST(cx, rc);
859
860
861
862
863
864
865
866
867

    return(JS_TRUE);
}

static JSBool
js_confirm(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *	str;
	sbbs_t*		sbbs;
868
	jsrefcount	rc;
869
870
871
872
873
874
875

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

	if((str=JS_ValueToString(cx, argv[0]))==NULL)
	    return(JS_FALSE);

876
	rc=JS_SUSPENDREQUEST(cx);
877
	*rval = BOOLEAN_TO_JSVAL(sbbs->yesno(JS_GetStringBytes(str)));
878
	JS_RESUMEREQUEST(cx, rc);
879
880
881
882
883
884
885
886
887
888
	return(JS_TRUE);
}

static JSBool
js_prompt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char		instr[81];
    JSString *	prompt;
    JSString *	str;
	sbbs_t*		sbbs;
889
	jsrefcount	rc;
890
891
892
893
894
895
896
897
898
899
900
901
902
903

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

	if((prompt=JS_ValueToString(cx, argv[0]))==NULL)
	    return(JS_FALSE);

	if(argc>1) {
		if((str=JS_ValueToString(cx, argv[1]))==NULL)
		    return(JS_FALSE);
		SAFECOPY(instr,JS_GetStringBytes(str));
	} else
		instr[0]=0;

904
	rc=JS_SUSPENDREQUEST(cx);
905
906
907
908
	sbbs->bprintf("\1n\1y\1h%s\1w: ",JS_GetStringBytes(prompt));

	if(!sbbs->getstr(instr,sizeof(instr)-1,K_EDIT)) {
		*rval = JSVAL_NULL;
909
		JS_RESUMEREQUEST(cx, rc);
910
911
		return(JS_TRUE);
	}
912
	JS_RESUMEREQUEST(cx, rc);
913
914
915
916
917
918
919
920

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

	*rval = STRING_TO_JSVAL(str);
    return(JS_TRUE);
}

921
static jsSyncMethodSpec js_global_functions[] = {
922
	{"log",				js_log,				1,	JSTYPE_STRING,	JSDOCSTR("[level,] value [,value]")
923
	,JSDOCSTR("add a line of text to the server and/or system log, "
924
925
		"<i>values</i> are typically string constants or variables, "
		"<i>level</i> is the debug level/priority (default: <tt>LOG_INFO</tt>)")
926
	,311
927
	},
928
929
	{"read",			js_read,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read up to count characters from input stream")
930
	,311
931
932
933
	},
	{"readln",			js_readln,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read a single line, up to count characters, from input stream")
934
	,311
935
	},
936
	{"write",			js_write,			0,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
937
	,JSDOCSTR("send one or more values (typically strings) to the server output")
938
	,311
939
	},
940
941
	{"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
942
	,314
943
	},
944
945
946
947
	{"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)")
948
	,311
949
	},
950
    {"printf",          js_printf,          1,	JSTYPE_STRING,	JSDOCSTR("string format [,value][,value]")
951
	,JSDOCSTR("print a formatted string - <small>CAUTION: for experienced C programmers ONLY</small>")
952
	,310
953
954
955
	},	
	{"alert",			js_alert,			1,	JSTYPE_VOID,	JSDOCSTR("value")
	,JSDOCSTR("print an alert message (ala client-side JS)")
956
	,310
957
958
959
	},
	{"prompt",			js_prompt,			1,	JSTYPE_STRING,	JSDOCSTR("[value]")
	,JSDOCSTR("displays a prompt (<i>value</i>) and returns a string of user input (ala clent-side JS)")
960
	,310
961
962
963
964
	},
	{"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 users confirmation (ala client-side JS)")
965
	,310
966
967
968
969
970
971
972
973
974
975
976
	},
    {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;
977
	jsrefcount	rc;
978
	int		log_level;
979
	char	nodestr[128];
980
981
982

	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return;
983
984
985
986
987

    if(sbbs->cfg.node_num)
    	SAFEPRINTF(nodestr,"Node %d",sbbs->cfg.node_num);
    else
    	SAFECOPY(nodestr,sbbs->client_name);
988
989
	
	if(report==NULL) {
990
		lprintf(LOG_ERR,"%s !JavaScript: %s", nodestr, message);
991
992
993
994
		return;
    }

	if(report->filename)
995
		SAFEPRINTF(file," %s",report->filename);
996
997
998
999
	else
		file[0]=0;

	if(report->lineno)
1000
		SAFEPRINTF(line," line %d",report->lineno);
1001
1002
1003
1004
1005
1006
1007
1008
	else
		line[0]=0;

	if(JSREPORT_IS_WARNING(report->flags)) {
		if(JSREPORT_IS_STRICT(report->flags))
			warning="strict warning";
		else
			warning="warning";
1009
1010
		log_level = LOG_WARNING;
	} else {
1011
		warning=nulstr;
1012
1013
		log_level = LOG_ERR;
	}
1014

1015
	rc=JS_SUSPENDREQUEST(cx);
1016
	if(sbbs->online==ON_LOCAL) 
1017
		eprintf(log_level,"!JavaScript %s%s%s: %s",warning,file,line,message);
1018
	else {
1019
		lprintf(log_level,"%s !JavaScript %s%s%s: %s",nodestr,warning,file,line,message);
1020
1021
		sbbs->bprintf("!JavaScript %s%s%s: %s\r\n",warning,file,line,message);
	}
1022
	JS_RESUMEREQUEST(cx, rc);
1023
1024
}

1025
bool sbbs_t::js_init(ulong* stack_frame)
1026
1027
1028
1029
{
	char		node[128];

    if(cfg.node_num)
1030
    	SAFEPRINTF(node,"Node %d",cfg.node_num);
1031
    else
1032
    	SAFECOPY(node,client_name);
1033

1034
1035
1036
	if(startup->js.max_bytes==0)			startup->js.max_bytes=JAVASCRIPT_MAX_BYTES;
	if(startup->js.cx_stack==0)				startup->js.cx_stack=JAVASCRIPT_CONTEXT_STACK;

1037
	lprintf(LOG_DEBUG,"%s JavaScript: Creating runtime: %lu bytes"
1038
		,node,startup->js.max_bytes);
1039

1040
	if((js_runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__))==NULL)
1041
1042
		return(false);

1043
	lprintf(LOG_DEBUG,"%s JavaScript: Initializing context (stack: %lu bytes)"
1044
		,node,startup->js.cx_stack);
1045

1046
    if((js_cx = JS_NewContext(js_runtime, startup->js.cx_stack))==NULL)
1047
		return(false);
1048
	JS_BEGINREQUEST(js_cx);
rswindell's avatar
rswindell committed
1049
1050
	
	memset(&js_branch,0,sizeof(js_branch));
1051
1052
1053
	js_branch.limit = startup->js.branch_limit;
	js_branch.gc_interval = startup->js.gc_interval;
	js_branch.yield_interval = startup->js.yield_interval;
rswindell's avatar
rswindell committed
1054
	js_branch.terminated = &terminated;
1055
	js_branch.auto_terminate = TRUE;
1056
1057
1058
1059
1060
1061
1062
1063
1064

	bool success=false;

	do {

		JS_SetErrorReporter(js_cx, js_ErrorReporter);

		JS_SetContextPrivate(js_cx, this);	/* Store a pointer to sbbs_t instance */

1065
		/* Global Objects (including system, js, client, Socket, MsgBase, File, User, etc. */
1066
		if((js_glob=js_CreateCommonObjects(js_cx, &scfg, &cfg, js_global_functions
1067
1068
					,uptime, startup->host_name, SOCKLIB_DESC	/* system */
					,&js_branch									/* js */
1069
					,&startup->js
1070
					,&client, client_socket						/* client */
1071
					,&js_server_props							/* server */
1072
			))==NULL)
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
			break;

		/* BBS Object */
		if(js_CreateBbsObject(js_cx, js_glob)==NULL)
			break;

		/* Console Object */
		if(js_CreateConsoleObject(js_cx, js_glob)==NULL)
			break;

1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
		if(startup->js.thread_stack) {
			ulong stack_limit;

#if JS_STACK_GROWTH_DIRECTION > 0
			stack_limit=((ulong)stack_frame)+startup->js.thread_stack;
#else
			stack_limit=((ulong)stack_frame)-startup->js.thread_stack;
#endif
			JS_SetThreadStackLimit(js_cx, stack_limit);

			lprintf(LOG_DEBUG,"%s JavaScript: Thread stack limit: %lu bytes"
				,node, startup->js.thread_stack);
		}

1097
1098
1099
1100
		success=true;

	} while(0);

1101
	JS_ENDREQUEST(js_cx);
1102
1103
1104
1105
1106
1107
1108
1109
1110
	if(!success) {
		JS_DestroyContext(js_cx);
		js_cx=NULL;
		return(false);
	}

	return(true);
}

1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
void sbbs_t::js_cleanup(const char* node)
{
	/* Free Context */
	if(js_cx!=NULL) {	
		lprintf(LOG_DEBUG,"%s JavaScript: Destroying context",node);
		JS_DestroyContext(js_cx);
		js_cx=NULL;
	}

	if(js_runtime!=NULL) {
		lprintf(LOG_DEBUG,"%s JavaScript: Destroying runtime",node);
		jsrt_Release(js_runtime);
		js_runtime=NULL;
	}
}

1127
1128
1129
1130
void sbbs_t::js_create_user_objects(void)
{
	if(js_cx==NULL)
		return;
1131
	
1132
	JS_BEGINREQUEST(js_cx);
rswindell's avatar
rswindell committed
1133
	if(!js_CreateUserObjects(js_cx, js_glob, &cfg, &useron, &client, NULL, subscan)) 
1134
		lprintf(LOG_ERR,"!JavaScript ERROR creating user objects");
1135
	JS_ENDREQUEST(js_cx);
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
}

#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;

1147
1148
1149
1150
1151
	if(inlen<1) {
		outlen=0;
		return(inbuf);	// no length? No interpretation
	}

1152
1153
    first_iac=(BYTE*)memchr(inbuf, TELNET_IAC, inlen);

1154
1155
	if(!(sbbs->telnet_mode&TELNET_MODE_GATE) 
		&& sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
		&& !(sbbs->console&CON_RAW_IN)) {
		if(sbbs->telnet_last_rxch==CR)
			first_cr=inbuf;
		else
			first_cr=(BYTE*)memchr(inbuf, CR, inlen);
	}

    if(!sbbs->telnet_cmdlen	&& first_iac==NULL && first_cr==NULL) {
        outlen=inlen;
        return(inbuf);	// no interpretation needed
    }

    if(first_iac!=NULL || first_cr!=NULL) {
		if(first_iac!=NULL && (first_cr==NULL || first_iac<first_cr))
   			outlen=first_iac-inbuf;
		else
			outlen=first_cr-inbuf;
	    memcpy(outbuf, inbuf, outlen);
    } else
    	outlen=0;

    for(i=outlen;i<inlen;i++) {
1178
1179
		if(!(sbbs->telnet_mode&TELNET_MODE_GATE) 
			&& sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL
1180
1181
1182
1183
			&& !(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 */
rswindell's avatar
rswindell committed
1184
				lprintf(LOG_INFO,"Node %d CR/%02Xh detected and ignored"
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
					,sbbs->cfg.node_num, inbuf[i]);
#endif
				sbbs->telnet_last_rxch=inbuf[i];
				continue;