main.cpp 160 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 2014 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
#include "js_socket.h"
45
46
47

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

54
#define SBBS_TELNET_ENVIRON_SUPPORT 1
55
56
//---------------------------------------------------------------------------

57
#define TELNET_SERVER "Synchronet Terminal Server"
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#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

75
#ifdef USE_CRYPTLIB
76
	#define SSH_END()	if(ssh) { pthread_mutex_lock(&sbbs->ssh_mutex); cryptDestroySession(sbbs->ssh_session); pthread_mutex_unlock(&sbbs->ssh_mutex); }
77
78
79
80
#else
	#define	SSH_END()
#endif

81
82
volatile time_t	uptime=0;
volatile ulong	served=0;
83

84
static	protected_uint32_t node_threads_running;
85
86
87
88
89
90
91
92
93
94
		
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;
95
96
97
#ifdef USE_CRYPTLIB
static	SOCKET ssh_socket=INVALID_SOCKET;
#endif
98
99
100
101
102
static	sbbs_t*	sbbs=NULL;
static	scfg_t	scfg;
static	char *	text[TOTAL_TEXT];
static	WORD	first_node;
static	WORD	last_node;
103
static	bool	terminate_server=false;
104
105
static	str_list_t recycle_semfiles;
static	str_list_t shutdown_semfiles;
106
107
108
#ifdef _THREAD_SUID_BROKEN
int	thread_suid_broken=TRUE;			/* NPTL is no longer broken */
#endif
109
110
111
112
113

extern "C" {

static bbs_startup_t* startup=NULL;

rswindell's avatar
rswindell committed
114
static const char* status(const char* str)
115
116
{
	if(startup!=NULL && startup->status!=NULL)
117
	    startup->status(startup->cbdata,str);
rswindell's avatar
rswindell committed
118
	return str;
119
120
121
122
123
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

	if(*addr==0)
253
		return((u_long)INADDR_NONE);
254
255

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

} /* extern "C" */

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

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

#else /* No WINSOCK */

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

#endif

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

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

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

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

319
320
#ifdef JAVASCRIPT

321
322
static js_server_props_t js_server_props;

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

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

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

353
	return(JS_TRUE);
354
355
}

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

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

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

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

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

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

387
#ifdef BUILD_JSDOCS
388
389

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

/*
 * from jsatom.c:
 * Keep this in sync with jspubtd.h -- an assertion below will insist that
 * its length match the JSType enum's JSTYPE_LIMIT limit value.
 */
static const char *js_type_str[] = {
    "void",			// changed from "undefined"
    "object",
    "function",
    "string",
    "number",
    "boolean",
deuce's avatar
deuce committed
404
405
	"null",
	"xml",
406
407
	"array",
	"alias",
deuce's avatar
deuce committed
408
	"undefined"
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) {
612
				val=INT_TO_JSVAL(consts[i].val);
613

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

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

	return(JS_TRUE);
}

626
627
#endif

628
629
630
631
632
633
634
635
/* 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++) {
636
		val=INT_TO_JSVAL(ints[i].val);
637
638
639
640

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

642
643
644
	return(JS_TRUE);
}

645
static JSBool
646
js_log(JSContext *cx, uintN argc, jsval *arglist)
647
{
648
	jsval *argv=JS_ARGV(cx, arglist);
649
650
    uintN		i=0;
	int32		level=LOG_INFO;
651
    JSString*	str=NULL;
652
	sbbs_t*		sbbs;
653
	jsrefcount	rc;
deuce's avatar
deuce committed
654
655
	char		*line=NULL;
	size_t		line_sz=0;
656

657
658
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

662
663
664
665
	if(argc > 1 && JSVAL_IS_NUMBER(argv[i])) {
		if(!JS_ValueToInt32(cx,argv[i++],&level))
			return JS_FALSE;
	}
666
667

    for(; i<argc; i++) {
deuce's avatar
deuce committed
668
669
		if((str=JS_ValueToString(cx, argv[i]))==NULL)
			return(JS_FALSE);
deuce's avatar
deuce committed
670
		JSSTRING_TO_RASTRING(cx, str, line, &line_sz, NULL);
deuce's avatar
deuce committed
671
		if(line==NULL)
672
		    return(JS_FALSE);
673
		rc=JS_SUSPENDREQUEST(cx);
674
		if(sbbs->online==ON_LOCAL) {
deuce's avatar
deuce committed
675
676
677
			if(startup!=NULL && startup->event_lputs!=NULL && level <= startup->log_level) {
				startup->event_lputs(startup->event_cbdata,level,line);
			}
678
		} else
deuce's avatar
deuce committed
679
			lprintf(level,"Node %d %s", sbbs->cfg.node_num, line);
680
		JS_RESUMEREQUEST(cx, rc);
681
	}
deuce's avatar
deuce committed
682
	free(line);
683

684
	if(str==NULL)
685
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
686
	else
687
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
688
689
690
    return(JS_TRUE);
}

691
static JSBool
692
js_read(JSContext *cx, uintN argc, jsval *arglist)
693
{
694
	jsval *argv=JS_ARGV(cx, arglist);
695
696
697
	uchar*		buf;
	int32		len=128;
	sbbs_t*		sbbs;
698
	jsrefcount	rc;
699

700
701
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

705
706
707
708
	if(argc) {
		if(!JS_ValueToInt32(cx,argv[0],&len))
			return JS_FALSE;
	}
709
710
711
712

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

713
	rc=JS_SUSPENDREQUEST(cx);
714
	len=RingBufRead(&sbbs->inbuf,buf,len);
715
	JS_RESUMEREQUEST(cx, rc);
716
717

	if(len>0)
718
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyN(cx,(char*)buf,len)));
719
720
721
722
723
724

	free(buf);
	return(JS_TRUE);
}

static JSBool
725
js_readln(JSContext *cx, uintN argc, jsval *arglist)
726
{
727
	jsval *argv=JS_ARGV(cx, arglist);
728
729
730
	char*		buf;
	int32		len=128;
	sbbs_t*		sbbs;
731
	jsrefcount	rc;
732

733
734
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

738
739
740
741
	if(argc) {
		if(!JS_ValueToInt32(cx,argv[0],&len))
			return JS_FALSE;
	}
742
743
744
745

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

746
	rc=JS_SUSPENDREQUEST(cx);
747
	len=sbbs->getstr(buf,len,K_NONE);
748
	JS_RESUMEREQUEST(cx, rc);
749
750

	if(len>0)
751
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,buf)));
752
753
754
755
756

	free(buf);
	return(JS_TRUE);
}

757
static JSBool
758
js_write(JSContext *cx, uintN argc, jsval *arglist)
759
{
760
	jsval *argv=JS_ARGV(cx, arglist);
761
    uintN		i;
762
    JSString*	str=NULL;
763
	sbbs_t*		sbbs;
764
	jsrefcount	rc;
765
766
	char		*cstr=NULL;
	size_t		cstr_sz=0;
767

768
769
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

770
771
772
773
	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

    for (i = 0; i < argc; i++) {
deuce's avatar
deuce committed
774
775
		if((str=JS_ValueToString(cx, argv[i]))==NULL)
			return(JS_FALSE);
deuce's avatar
deuce committed
776
		JSSTRING_TO_RASTRING(cx, str, cstr, &cstr_sz, NULL);
deuce's avatar
deuce committed
777
		if(cstr==NULL)
778
		    return(JS_FALSE);
779
		rc=JS_SUSPENDREQUEST(cx);
780
		if(sbbs->online==ON_LOCAL)
deuce's avatar
deuce committed
781
			eprintf(LOG_INFO,"%s",cstr);
782
		else
deuce's avatar
deuce committed
783
			sbbs->bputs(cstr);
784
		JS_RESUMEREQUEST(cx, rc);
785
786
787
	}

	if(str==NULL)
788
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
789
	else
790
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
791
792
793
    return(JS_TRUE);
}

794
static JSBool
795
js_write_raw(JSContext *cx, uintN argc, jsval *arglist)
796
{
797
	jsval *argv=JS_ARGV(cx, arglist);
798
    uintN		i;
deuce's avatar
deuce committed
799
800
    char*		str=NULL;
    size_t		str_sz=0;
801
802
	size_t		len;
	sbbs_t*		sbbs;
803
	jsrefcount	rc;
804

805
806
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

807
808
809
810
	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

    for (i = 0; i < argc; i++) {
deuce's avatar
deuce committed
811
		JSVALUE_TO_RASTRING(cx, argv[i], str, &str_sz, &len);
812
		if(str==NULL)
813
		    return(JS_FALSE);
814
		rc=JS_SUSPENDREQUEST(cx);
815
		sbbs->putcom(str, len);
816
		JS_RESUMEREQUEST(cx, rc);
817
818
819
820
821
	}

    return(JS_TRUE);
}

822
static JSBool
823
js_writeln(JSContext *cx, uintN argc, jsval *arglist)
824
825
{
	sbbs_t*		sbbs;
826
	jsrefcount	rc;
827

828
829
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

833
	js_write(cx,argc,arglist);
834
	rc=JS_SUSPENDREQUEST(cx);
835
836
	if(sbbs->online==ON_REMOTE)
		sbbs->bputs(crlf);
837
	JS_RESUMEREQUEST(cx, rc);
838
839
840
841
842

    return(JS_TRUE);
}

static JSBool
843
js_printf(JSContext *cx, uintN argc, jsval *arglist)
844
{
845
	jsval *argv=JS_ARGV(cx, arglist);
846
847
	char*		p;
	sbbs_t*		sbbs;
848
	jsrefcount	rc;
849

850
851
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

855
856
	if((p = js_sprintf(cx, 0, argc, argv))==NULL) {
		JS_ReportError(cx,"js_sprintf failed");
857
858
859
		return(JS_FALSE);
	}

860
	rc=JS_SUSPENDREQUEST(cx);
861
	if(sbbs->online==ON_LOCAL)
862
		eprintf(LOG_INFO,"%s",p);
863
864
	else
		sbbs->bputs(p);
865
	JS_RESUMEREQUEST(cx, rc);
866

867
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p)));
868

869
	js_sprintf_free(p);
870
871
872
873
874

    return(JS_TRUE);
}

static JSBool
875
js_alert(JSContext *cx, uintN argc, jsval *arglist)
876
{
877
	jsval *argv=JS_ARGV(cx, arglist);
878
	sbbs_t*		sbbs;
879
	jsrefcount	rc;
deuce's avatar
deuce committed
880
	char		*cstr;
881

882
883
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

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

891
	rc=JS_SUSPENDREQUEST(cx);
892
	sbbs->attr(sbbs->cfg.color[clr_err]);
deuce's avatar
deuce committed
893
	sbbs->bputs(cstr);
deuce's avatar
deuce committed
894
	free(cstr);
895
896
	sbbs->attr(LIGHTGRAY);
	sbbs->bputs(crlf);
897
	JS_RESUMEREQUEST(cx, rc);
898

deuce's avatar
deuce committed
899
900
	JS_SET_RVAL(cx, arglist, argv[0]);

901
902
903
904
    return(JS_TRUE);
}

static JSBool
905
js_confirm(JSContext *cx, uintN argc, jsval *arglist)
906
{
907
	jsval *argv=JS_ARGV(cx, arglist);
908
	sbbs_t*		sbbs;
909
	jsrefcount	rc;
deuce's avatar
deuce committed
910
	char		*cstr;
911

912
913
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

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

921
	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
922
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->yesno(cstr)));
deuce's avatar
deuce committed
923
	free(cstr);
924
	JS_RESUMEREQUEST(cx, rc);
925
926
927
	return(JS_TRUE);
}

928
static JSBool
929
js_deny(JSContext *cx, uintN argc, jsval *arglist)
930
{
931
	jsval *argv=JS_ARGV(cx, arglist);
932
933
	sbbs_t*		sbbs;
	jsrefcount	rc;
deuce's avatar
deuce committed
934
	char		*cstr;
935

936
937
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

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

	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
946
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->noyes(cstr)));
deuce's avatar
deuce committed
947
	free(cstr);
948
949
950
951
952
	JS_RESUMEREQUEST(cx, rc);
	return(JS_TRUE);
}


953
static JSBool
954
js_prompt(JSContext *cx, uintN argc, jsval *arglist)
955
{
956
	jsval *argv=JS_ARGV(cx, arglist);
957
958
959
	char		instr[81];
    JSString *	str;
	sbbs_t*		sbbs;
960
	jsrefcount	rc;
deuce's avatar
deuce committed
961
    char 		*prompt;
962

963
964
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

deuce's avatar
deuce committed
968
	JSVALUE_TO_MSTRING(cx, argv[0], prompt, NULL);
deuce's avatar
deuce committed
969
	if(prompt==NULL)
970
971
972
	    return(JS_FALSE);

	if(argc>1) {
deuce's avatar
deuce committed
973
		JSVALUE_TO_STRBUF(cx, argv[1], instr, sizeof(instr), NULL);
974
975
976
	} else
		instr[0]=0;

977
	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
978
	sbbs->bprintf("\1n\1y\1h%s\1w: ",prompt);
deuce's avatar
deuce committed
979
	free(prompt);
980
981

	if(!sbbs->getstr(instr,sizeof(instr)-1,K_EDIT)) {
982
		JS_SET_RVAL(cx, arglist, JSVAL_NULL);
983
		JS_RESUMEREQUEST(cx, rc);
984
985
		return(JS_TRUE);
	}
986
	JS_RESUMEREQUEST(cx, rc);
987
988
989
990

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

991
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
992
993
994
    return(JS_TRUE);
}

995
static jsSyncMethodSpec js_global_functions[] = {
996
	{"log",				js_log,				1,	JSTYPE_STRING,	JSDOCSTR("[level,] value [,value]")
997
	,JSDOCSTR("add a line of text to the server and/or system log, "
998
999
		"<i>values</i> are typically string constants or variables, "
		"<i>level</i> is the debug level/priority (default: <tt>LOG_INFO</tt>)")
1000
	,311
1001
	},
1002
1003
	{"read",			js_read,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read up to count characters from input stream")
1004
	,311
1005
1006
1007
	},
	{"readln",			js_readln,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read a single line, up to count characters, from input stream")
1008
	,311
1009
	},
1010
	{"write",			js_write,			0,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
1011
	,JSDOCSTR("send one or more values (typically strings) to the server output")
1012
	,311
1013
	},