main.cpp 155 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 2010 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;

rswindell's avatar
rswindell committed
82
static	ulong 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
158
		if(startup!=NULL && startup->errormsg!=NULL)
			startup->errormsg(startup->cbdata,level,str);
	}
159

160
	if(startup==NULL || startup->lputs==NULL || str==NULL || level > startup->log_level)
161
162
    	return(0);

163
164
165
166
167
#if defined(_WIN32)
	if(IsBadCodePtr((FARPROC)startup->lputs))
		return(0);
#endif

168
    return(startup->lputs(startup->cbdata,level,str));
169
170
}

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

    va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
180
    return(lputs(level,sbuf));
181
182
}

183
int eprintf(int level, const char *fmt, ...)
184
185
186
187
188
189
190
191
{
	va_list argptr;
	char sbuf[1024];

    va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
192
193
194
195
196
197
198
199
200
201

	if(level <= LOG_ERR) {
		errorlog(&scfg,startup==NULL ? NULL:startup->host_name, sbuf);
		if(startup!=NULL && startup->errormsg!=NULL)
			startup->errormsg(startup->cbdata,level,sbuf);
	}

    if(startup==NULL || startup->event_lputs==NULL || level > startup->log_level)
        return(0);

202
	strip_ctrl(sbuf, sbuf);
203
    return(startup->event_lputs(startup->event_cbdata,level,sbuf));
204
205
}

206
SOCKET open_socket(int type, const char* protocol)
207
208
209
210
211
212
{
	SOCKET	sock;
	char	error[256];

	sock=socket(AF_INET, type, IPPROTO_IP);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
213
		startup->socket_open(startup->cbdata,TRUE);
214
	if(sock!=INVALID_SOCKET && set_socket_options(&scfg, sock, protocol, error, sizeof(error)))
215
		lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
216
217
218
219
220
221
222
223
224
225

	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) 
226
		startup->socket_open(startup->cbdata,TRUE);
227
228
229
230
231
232
233
234
235
236
237
238
239

	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);
240
	if(startup!=NULL && startup->socket_open!=NULL)
241
		startup->socket_open(startup->cbdata,FALSE);
242
	if(result!=0 && ERROR_VALUE!=ENOTSOCK)
243
		lprintf(LOG_WARNING,"!ERROR %d closing socket %d",ERROR_VALUE,sock);
244
245
246
247
248
249
250
251
252
253
	return(result);
}


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

	if(*addr==0)
254
		return((u_long)INADDR_NONE);
255
256
257
258
259
260
261

	for(p=addr;*p;p++)
		if(*p!='.' && !isdigit(*p))
			break;
	if(!(*p))
		return(inet_addr(addr));
	if((host=gethostbyname(addr))==NULL) 
262
		return((u_long)INADDR_NONE);
263
264
265
266
267
	return(*((ulong*)host->h_addr_list[0]));
}

} /* extern "C" */

268
269
270
271
272
273
274
275
276
277
278
#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) {
279
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
280
281
282
283
		WSAInitialized=TRUE;
		return(TRUE);
	}

284
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
285
286
287
288
289
290
291
292
293
294
	return(FALSE);
}

#else /* No WINSOCK */

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

#endif

295
296
DLLEXPORT void DLLCALL sbbs_srand()
{
297
	DWORD seed;
298

299
	xp_randomize();
300
301
302
303
304
305
306
#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);
	}
307
308
#else
	seed = time(NULL) ^ (DWORD)GetCurrentThreadId()
309
310
311
#endif

 	srand(seed);
312
313
314
	sbbs_random(10);	/* Throw away first number */
}

315
int DLLCALL sbbs_random(int n)
316
317
318
319
{
	return(xp_random(n));
}

320
321
#ifdef JAVASCRIPT

322
323
static js_server_props_t js_server_props;

324
325
326
327
328
329
330
331
332
333
334
335
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
336
337
		if((array=JS_NewArrayObject(cx, 0, NULL))==NULL)	/* Assertion here, in _heap_alloc_dbg, June-21-2004 */
			return(JS_FALSE);								/* Caused by nntpservice.js? */
338

339
340
341
342
	if(!JS_DefineProperty(cx, parent, name, OBJECT_TO_JSVAL(array)
		,NULL,NULL,flags))
		return(JS_FALSE);

343
344
345
346
347
348
349
350
351
352
353
	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;
	}

354
	return(JS_TRUE);
355
356
}

357
/* Convert from Synchronet-specific jsSyncMethodSpec to JSAPI's JSFunctionSpec */
358
359

JSBool
360
DLLCALL js_DescribeSyncObject(JSContext* cx, JSObject* obj, const char* str, int ver)
361
362
363
364
365
366
{
	JSString* js_str = JS_NewStringCopyZ(cx, str);

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

rswindell's avatar
rswindell committed
367
368
369
	if(ver < 10000)		/* auto convert 313 to 31300 */
		ver*=100;

370
	return(JS_DefineProperty(cx,obj,"_description"
371
372
373
			,STRING_TO_JSVAL(js_str),NULL,NULL,JSPROP_READONLY)
		&& JS_DefineProperty(cx,obj,"_ver"
			,INT_TO_JSVAL(ver),NULL,NULL,JSPROP_READONLY));
374
375
376
}

JSBool
377
DLLCALL js_DescribeSyncConstructor(JSContext* cx, JSObject* obj, const char* str)
378
379
380
381
382
383
384
385
386
387
{
	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));
}

388
#ifdef BUILD_JSDOCS
389
390

static const char* method_array_name = "_method_list";
391
static const char* propver_array_name = "_property_ver_list";
392
393
394
395
396
397
398
399
400
401
402
403
404

/*
 * 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",
405
406
	"array",
	"alias",
rswindell's avatar
rswindell committed
407
408
	"undefined",
	"null"
409
410
};

411
412
413
414
JSBool
DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
	uint		i;
415
	long		ver;
416
417
418
419
420
421
422
423
424
425
426
427
	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++) {
428
429
		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))
430
431
			return(JS_FALSE);
		if(props[i].flags&JSPROP_ENUMERATE) {	/* No need to version invisible props */
432
433
434
			if((ver=props[i].ver) < 10000)		/* auto convert 313 to 31300 */
				ver*=100;
			val = INT_TO_JSVAL(ver);
435
436
437
438
439
440
441
442
			if(!JS_SetElement(cx, array, len++, &val))
				return(JS_FALSE);
		}
	}

	return(JS_TRUE);
}

443
JSBool 
444
DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs, BOOL append)
445
446
447
{
	int			i;
	jsuint		len=0;
448
	long		ver;
449
450
451
452
453
454
455
456
457
458
459
460
	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);

461
462
463
464
	if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
		, NULL, NULL, 0))
		return(JS_FALSE);

465
466
467
468
469
470
	if(append)
		if(!JS_GetArrayLength(cx, method_array, &len))
			return(JS_FALSE);

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

471
472
		if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
			return(JS_FALSE);
473
474
475
476

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

477
		method = JS_NewObject(cx, NULL, NULL, method_array);	/* exception here June-7-2003 */
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511

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

512
		if(funcs[i].ver) {
513
			if((ver=funcs[i].ver) < 10000)		/* auto convert 313 to 31300 */
514
515
				ver*=100;
			val = INT_TO_JSVAL(ver);
516
517
518
			JS_SetProperty(cx,method, "ver", &val);
		}

519
520
521
522
523
524
525
526
		val=OBJECT_TO_JSVAL(method);
		if(!JS_SetElement(cx, method_array, len+i, &val))
			return(JS_FALSE);
	}

	return(JS_TRUE);
}

527
528
529
530
531
/*
 * Always resolve all here since
 * 1) We'll always be enumerating anyways
 * 2) The speed penalty won't be seen in production code anyways
 */
532
533
JSBool
DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
534
535
536
{
	JSBool	ret=JS_TRUE;

deuce's avatar
deuce committed
537
	if(props) {
538
539
		if(!js_DefineSyncProperties(cx, obj, props))
			ret=JS_FALSE;
deuce's avatar
deuce committed
540
	}
541
		
deuce's avatar
deuce committed
542
543
	if(funcs) {
		if(!js_DefineSyncMethods(cx, obj, funcs, 0))
544
			ret=JS_FALSE;
deuce's avatar
deuce committed
545
	}
546

deuce's avatar
deuce committed
547
548
549
550
	if(consts) {
		if(!js_DefineConstIntegers(cx, obj, consts, flags))
			ret=JS_FALSE;
	}
551

552
553
554
	return(ret);
}

555
#else // NON-JSDOCS
556

557
558
559
560
561
562
563
JSBool
DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
	uint i;

	for(i=0;props[i].name;i++) 
		if(!JS_DefinePropertyWithTinyId(cx, obj, 
564
			props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
565
566
567
568
569
570
			return(JS_FALSE);

	return(JS_TRUE);
}


571
JSBool 
572
DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs, BOOL append)
573
{
574
	uint i;
575
576

	for(i=0;funcs[i].name;i++)
577
578
		if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
			return(JS_FALSE);
579
580
581
	return(JS_TRUE);
}

582
583
JSBool
DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
584
585
{
	uint i;
586
	jsval	val;
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608

	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);
			}
		}
	}
609
	if(consts) {
deuce's avatar
deuce committed
610
		for(i=0;consts[i].name;i++) {
611
			if(name==NULL || strcmp(name, consts[i].name)==0) {
deuce's avatar
deuce committed
612
	        	if(!JS_NewNumberValue(cx, consts[i].val, &val))
613
614
					return(JS_FALSE);

deuce's avatar
deuce committed
615
				if(!JS_DefineProperty(cx, obj, consts[i].name, val ,NULL, NULL, flags))
616
617
618
619
620
621
622
					return(JS_FALSE);

				if(name)
					return(JS_TRUE);
			}
		}
	}
623
624
625
626

	return(JS_TRUE);
}

627
628
#endif

629
630
631
632
633
634
635
636
637
638
639
640
641
642
/* 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);
	}
643

644
645
646
	return(JS_TRUE);
}

647
648
649
650
651
652
653
654
655
656
657
658
659
660
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));
}

661
662
663
static JSBool
js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
664
665
    uintN		i=0;
	int32		level=LOG_INFO;
666
    JSString*	str=NULL;
667
	sbbs_t*		sbbs;
668
	jsrefcount	rc;
669
670
671
672

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

673
	if(argc > 1 && JSVAL_IS_NUMBER(argv[i]))
674
675
676
		JS_ValueToInt32(cx,argv[i++],&level);

    for(; i<argc; i++) {
677
		if((str=JS_ValueToString(cx, argv[i]))==NULL) {
678
			JS_RESUMEREQUEST(cx, rc);
679
		    return(JS_FALSE);
680
		}
681
		rc=JS_SUSPENDREQUEST(cx);
682
		if(sbbs->online==ON_LOCAL) {
683
			if(startup!=NULL && startup->event_lputs!=NULL && level <= startup->log_level)
684
				startup->event_lputs(startup->event_cbdata,level,JS_GetStringBytes(str));
685
		} else
686
			lprintf(level,"Node %d %s", sbbs->cfg.node_num, JS_GetStringBytes(str));
687
		JS_RESUMEREQUEST(cx, rc);
688
	}
689

690
691
692
693
	if(str==NULL)
		*rval = JSVAL_VOID;
	else
		*rval = STRING_TO_JSVAL(str);
694
695
696
    return(JS_TRUE);
}

697
698
699
700
701
702
static JSBool
js_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	uchar*		buf;
	int32		len=128;
	sbbs_t*		sbbs;
703
	jsrefcount	rc;
704
705
706
707
708
709
710
711
712
713

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

714
	rc=JS_SUSPENDREQUEST(cx);
715
	len=RingBufRead(&sbbs->inbuf,buf,len);
716
	JS_RESUMEREQUEST(cx, rc);
717
718
719
720
721
722
723
724
725
726
727
728
729
730

	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;
731
	jsrefcount	rc;
732
733
734
735
736
737
738
739
740
741

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

742
	rc=JS_SUSPENDREQUEST(cx);
743
	len=sbbs->getstr(buf,len,K_NONE);
744
	JS_RESUMEREQUEST(cx, rc);
745
746
747
748
749
750
751
752

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

	free(buf);
	return(JS_TRUE);
}

753
static JSBool
754
js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
755
756
{
    uintN		i;
757
    JSString*	str=NULL;
758
	sbbs_t*		sbbs;
759
	jsrefcount	rc;
760
761
762
763
764
765
766

	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);
767
		rc=JS_SUSPENDREQUEST(cx);
768
		if(sbbs->online==ON_LOCAL)
769
			eprintf(LOG_INFO,"%s",JS_GetStringBytes(str));
770
771
		else
			sbbs->bputs(JS_GetStringBytes(str));
772
		JS_RESUMEREQUEST(cx, rc);
773
774
775
776
777
778
779
780
781
	}

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

782
783
784
785
786
787
788
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;
789
	jsrefcount	rc;
790
791
792
793
794
795
796

	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);
797
		rc=JS_SUSPENDREQUEST(cx);
798
		sbbs->putcom(str, len);
799
		JS_RESUMEREQUEST(cx, rc);
800
801
802
803
804
	}

    return(JS_TRUE);
}

805
static JSBool
806
js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
807
808
{
	sbbs_t*		sbbs;
809
	jsrefcount	rc;
810
811
812
813
814

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

	js_write(cx,obj,argc,argv,rval);
815
	rc=JS_SUSPENDREQUEST(cx);
816
817
	if(sbbs->online==ON_REMOTE)
		sbbs->bputs(crlf);
818
	JS_RESUMEREQUEST(cx, rc);
819
820
821
822
823
824
825
826
827

    return(JS_TRUE);
}

static JSBool
js_printf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
	sbbs_t*		sbbs;
828
	jsrefcount	rc;
829
830
831
832

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

833
834
	if((p = js_sprintf(cx, 0, argc, argv))==NULL) {
		JS_ReportError(cx,"js_sprintf failed");
835
836
837
		return(JS_FALSE);
	}

838
	rc=JS_SUSPENDREQUEST(cx);
839
	if(sbbs->online==ON_LOCAL)
840
		eprintf(LOG_INFO,"%s",p);
841
842
	else
		sbbs->bputs(p);
843
	JS_RESUMEREQUEST(cx, rc);
844
845
846

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

847
	js_sprintf_free(p);
848
849
850
851
852
853
854
855
856

    return(JS_TRUE);
}

static JSBool
js_alert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *	str;
	sbbs_t*		sbbs;
857
	jsrefcount	rc;
858
859
860
861
862
863
864

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

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

865
	rc=JS_SUSPENDREQUEST(cx);
866
867
868
869
	sbbs->attr(sbbs->cfg.color[clr_err]);
	sbbs->bputs(JS_GetStringBytes(str));
	sbbs->attr(LIGHTGRAY);
	sbbs->bputs(crlf);
870
	JS_RESUMEREQUEST(cx, rc);
871
872
873
874
875
876
877
878
879

    return(JS_TRUE);
}

static JSBool
js_confirm(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *	str;
	sbbs_t*		sbbs;
880
	jsrefcount	rc;
881
882
883
884
885
886
887

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

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

888
	rc=JS_SUSPENDREQUEST(cx);
889
	*rval = BOOLEAN_TO_JSVAL(sbbs->yesno(JS_GetStringBytes(str)));
890
	JS_RESUMEREQUEST(cx, rc);
891
892
893
894
895
896
897
898
899
900
	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;
901
	jsrefcount	rc;
902
903
904
905
906
907
908
909
910
911
912
913
914
915

	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;

916
	rc=JS_SUSPENDREQUEST(cx);
917
918
919
920
	sbbs->bprintf("\1n\1y\1h%s\1w: ",JS_GetStringBytes(prompt));

	if(!sbbs->getstr(instr,sizeof(instr)-1,K_EDIT)) {
		*rval = JSVAL_NULL;
921
		JS_RESUMEREQUEST(cx, rc);
922
923
		return(JS_TRUE);
	}
924
	JS_RESUMEREQUEST(cx, rc);
925
926
927
928
929
930
931
932

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

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

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

	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return;
995
996
997
998
999

    if(sbbs->cfg.node_num)
    	SAFEPRINTF(nodestr,"Node %d",sbbs->cfg.node_num);
    else
    	SAFECOPY(nodestr,sbbs->client_name);
1000
1001
	
	if(report==NULL) {
1002
		lprintf(LOG_ERR,"%s !JavaScript: %s", nodestr, message);
1003
1004
1005
1006
		return;
    }

	if(report->filename)
1007
		SAFEPRINTF(file," %s",report->filename);
1008
1009
1010
1011
	else
		file[0]=0;

	if(report->lineno)
1012
		SAFEPRINTF(line," line %d",report->lineno);
1013
1014
1015
1016
1017
1018
1019
1020
	else
		line[0]=0;

	if(JSREPORT_IS_WARNING(report->flags)) {
		if(JSREPORT_IS_STRICT(report->flags))
			warning="strict warning";
		else
			warning="warning";
1021
1022
		log_level = LOG_WARNING;
	} else {
1023
		warning=nulstr;
1024
1025
		log_level = LOG_ERR;
	}
1026

1027
	rc=JS_SUSPENDREQUEST(cx);
1028
	if(sbbs->online==ON_LOCAL) 
1029
		eprintf(log_level,"!JavaScript %s%s%s: %s",warning,file,line,message);
1030
	else {
1031
		lprintf(log_level,"%s !JavaScript %s%s%s: %s",nodestr,warning,file,line,message);
1032
1033
		sbbs->bprintf("!JavaScript %s%s%s: %s\r\n",warning,file,line,message);
	}
1034
	JS_RESUMEREQUEST(cx, rc);
1035
1036
}

1037
bool sbbs_t::js_init(ulong* stack_frame)
1038
1039
1040
1041
{
	char		node[128];

    if(cfg.node_num)
1042
    	SAFEPRINTF(node,"Node %d",cfg.node_num);
1043
    else
1044
    	SAFECOPY(node,client_name);
1045

1046
1047
1048
	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;

1049
	lprintf(LOG_DEBUG,"%s JavaScript: Creating runtime: %lu bytes"
1050
		,node,startup->js.max_bytes);
1051

1052
	if((js_runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__))==NULL)
1053
1054
		return(false);

1055
	lprintf(LOG_DEBUG,"%s JavaScript: Initializing context (stack: %lu bytes)"
1056
		,node,startup->js.cx_stack);
1057

1058
    if((js_cx = JS_NewContext(js_runtime, startup->js.cx_stack))==NULL)
1059
		return(false);
1060
	JS_BEGINREQUEST(js_cx);
rswindell's avatar
rswindell committed
1061
1062
	
	memset(&js_branch,0,sizeof(js_branch));
1063
1064
1065
	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
1066
	js_branch.terminated = &terminated;
1067
	js_branch.auto_terminate = TRUE;
1068
1069
1070
1071
1072
1073
1074
1075
1076

	bool success=false;

	do {

		JS_SetErrorReporter(js_cx, js_ErrorReporter);

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

1077
		/* Global Objects (including system, js, client, Socket, MsgBase, File, User, etc. */
1078
		if((js_glob=js_CreateCommonObjects(js_cx, &scfg, &cfg, js_global_functions
1079
1080
					,uptime, startup->host_name, SOCKLIB_DESC	/* system */
					,&js_branch									/* js */
1081
					,&startup->js
1082
					,&client, client_socket						/* client */
1083
					,&js_server_props							/* server */
1084
			))==NULL)
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
			break;

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

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

1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
		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);
		}

1109
1110
1111
1112
		success=true;

	} while(0);

1113
	JS_ENDREQUEST(js_cx);
1114
1115
1116
1117
1118
1119
1120
1121
1122
	if(!success) {
		JS_DestroyContext(js_cx);
		js_cx=NULL;
		return(false);
	}

	return(true);
}

1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
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;
	}
}

1139
1140
1141
1142
void sbbs_t::js_create_user_objects(void)
{
	if(js_cx==NULL)
		return;
1143
	
1144
	JS_BEGINREQUEST(js_cx);
rswindell's avatar
rswindell committed
1145
	if(!js_CreateUserObjects(js_cx, js_glob, &cfg, &useron, &client, NULL, subscan)) 
1146
		lprintf(LOG_ERR,"!JavaScript ERROR creating user objects");
1147
	JS_ENDREQUEST(js_cx);
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
}

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

1159
1160
1161
1162
1163
	if(inlen<1) {
		outlen=0;
		return(inbuf);	// no length? No interpretation
	}

1164
1165
    first_iac=(BYTE*)memchr(inbuf, TELNET_IAC, inlen);

1166
1167
	if(!(sbbs->telnet_mode&TELNET_MODE_GATE) 
		&& sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
		&& !(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++) {
1190
1191
		if(!(sbbs->telnet_mode&TELNET_MODE_GATE) 
			&& sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL