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
298
	DWORD seed = time(NULL) ^ (DWORD)GetCurrentThreadId();

299
	xp_randomize();
300
301
302
303
304
305
306
307
308
309
#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);
310
311
312
	sbbs_random(10);	/* Throw away first number */
}

313
int DLLCALL sbbs_random(int n)
314
315
316
317
{
	return(xp_random(n));
}

318
319
#ifdef JAVASCRIPT

320
321
static js_server_props_t js_server_props;

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

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

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

352
	return(JS_TRUE);
353
354
}

355
/* Convert from Synchronet-specific jsSyncMethodSpec to JSAPI's JSFunctionSpec */
356
357

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

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

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

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

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

386
#ifdef BUILD_JSDOCS
387
388

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

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

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

	return(JS_TRUE);
}

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

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

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

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

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

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

475
		method = JS_NewObject(cx, NULL, NULL, method_array);	/* exception here June-7-2003 */
476
477
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

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

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

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

	return(JS_TRUE);
}

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

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

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

550
551
552
	return(ret);
}

553
#else // NON-JSDOCS
554

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

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

	return(JS_TRUE);
}


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

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

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

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

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

				if(name)
					return(JS_TRUE);
			}
		}
	}
621
622
623
624

	return(JS_TRUE);
}

625
626
#endif

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

642
643
644
	return(JS_TRUE);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

	free(buf);
	return(JS_TRUE);
}

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

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

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

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

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

    return(JS_TRUE);
}

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

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

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

    return(JS_TRUE);
}

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

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

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

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

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

845
	js_sprintf_free(p);
846
847
848
849
850
851
852
853
854

    return(JS_TRUE);
}

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

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

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

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

    return(JS_TRUE);
}

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

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

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

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

	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;

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

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

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

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

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

	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return;
993
994
995
996
997

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

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

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

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

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

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

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

1044
1045
1046
	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;

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

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

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

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

	bool success=false;

	do {

		JS_SetErrorReporter(js_cx, js_ErrorReporter);

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

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

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

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

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

1107
1108
1109
1110
		success=true;

	} while(0);

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

	return(true);
}

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

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

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

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

1162
1163
    first_iac=(BYTE*)memchr(inbuf, TELNET_IAC, inlen);

1164
1165
	if(!(sbbs->telnet_mode&TELNET_MODE_GATE) 
		&& sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
		&& !(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++) {
1188
1189
		if(!(sbbs->telnet_mode&TELNET_MODE_GATE) 
			&& sbbs->telnet_remote_option[TELNET_BINARY_TX]!=TELNET_WILL
1190
1191
1192
1193
			&& !(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
1194
				lprintf(LOG_INFO,"Node %d CR/%02Xh detected and ignored"