main.cpp 165 KB
Newer Older
1
/* Synchronet terminal server thread and related functions */
2
3

/* $Id$ */
4
// vi: tabstop=4
5
6
7
8
9

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
10
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
11
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
 *																			*
 * 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" 
40
#include "netwrap.h"
41
#include "js_rtpool.h"
42
#include "js_request.h"
43
#include "ssl.h"
deuce's avatar
deuce committed
44
45
#include <multisock.h>
#include <limits.h>		// HOST_NAME_MAX
46
47
48
49
50

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

51
//#define SBBS_TELNET_ENVIRON_SUPPORT 1
52
53
//---------------------------------------------------------------------------

54
#define TELNET_SERVER "Synchronet Terminal Server"
55
56
57
58
#define STATUS_WFC	"Listening"

#define TIMEOUT_THREAD_WAIT		60			// Seconds (was 15)
#define IO_THREAD_BUF_SIZE	   	20000		// Bytes
59
#define TIMEOUT_MUTEX_FILE		12*60*60
60
61
62
63
64
65
66
67
68
69
70
71
72

// 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
74
	#define SSH_END()	if(ssh) { pthread_mutex_lock(&sbbs->ssh_mutex); cryptDestroySession(sbbs->ssh_session); pthread_mutex_unlock(&sbbs->ssh_mutex); }
75
76
77
78
#else
	#define	SSH_END()
#endif

79
80
volatile time_t	uptime=0;
volatile ulong	served=0;
81

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

extern "C" {

static bbs_startup_t* startup=NULL;

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

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

void client_on(SOCKET sock, client_t* client, BOOL update)
{
125
126
	if(!update)
		listAddNodeData(&current_connections, client->addr, strlen(client->addr)+1, sock, LAST_NODE);
127
	if(startup!=NULL && startup->client_on!=NULL)
128
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
129
130
131
132
}

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

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

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

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

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

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

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

169
170
int eputs(int level, const char *str)
{
171
172
173
	if(*str == 0)
		return 0;

174
175
176
177
178
179
180
181
182
183
184
185
	if(level <= LOG_ERR) {
		errorlog(&scfg,startup==NULL ? NULL:startup->host_name, str);
		if(startup!=NULL && startup->errormsg!=NULL)
			startup->errormsg(startup->cbdata,level,str);
	}

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

    return(startup->event_lputs(startup->event_cbdata,level,str));
}

186
int lprintf(int level, const char *fmt, ...)
187
188
189
190
191
192
193
194
{
	va_list argptr;
	char sbuf[1024];

    va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
195
    return(lputs(level,sbuf));
196
197
}

198
int eprintf(int level, const char *fmt, ...)
199
200
201
202
203
204
205
206
{
	va_list argptr;
	char sbuf[1024];

    va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
207

208
	strip_ctrl(sbuf, sbuf);
209

210
    return(eputs(level,truncsp(sbuf)));
211
}
212

213
214
215
216
217
218
/* Picks the right log callback function (event or term) based on the sbbs->cfg.node_num value */
int sbbs_t::lputs(int level, const char* str)
{
	if(cfg.node_num == 0)
		return ::eputs(level, str);
	return ::lputs(level, str);
219
220
}

221
222
223
224
225
226
227
228
229
230
231
232
int sbbs_t::lprintf(int level, const char *fmt, ...)
{
	va_list argptr;
	char sbuf[1024];

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

deuce's avatar
deuce committed
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
struct main_sock_cb_data {
	bbs_startup_t	*startup;
	const char		*protocol;
};

void sock_cb(SOCKET sock, void *cb_data)
{
	char	error_str[256];
	struct main_sock_cb_data *cb=(struct main_sock_cb_data *)cb_data;

	if(cb->startup && cb->startup->socket_open)
		cb->startup->socket_open(cb->startup->cbdata, TRUE);
	if(set_socket_options(&scfg, sock, cb->protocol, error_str, sizeof(error_str)))
		lprintf(LOG_ERR,"%04d !ERROR %s",sock,error_str);
}

void sock_close_cb(SOCKET sock, void *cb_data)
{
	bbs_startup_t	*su=(bbs_startup_t *)cb_data;

	if(su && su->socket_open)
		su->socket_open(su->cbdata, FALSE);
}

257
SOCKET open_socket(int type, const char* protocol)
258
259
260
261
262
263
{
	SOCKET	sock;
	char	error[256];

	sock=socket(AF_INET, type, IPPROTO_IP);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
264
		startup->socket_open(startup->cbdata,TRUE);
265
	if(sock!=INVALID_SOCKET && set_socket_options(&scfg, sock, protocol, error, sizeof(error)))
266
		lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
267
268
269
270

	return(sock);
}

271
// Used by sbbs_t::ftp_put():
deuce's avatar
deuce committed
272
SOCKET accept_socket(SOCKET s, union xp_sockaddr* addr, socklen_t* addrlen)
273
274
275
{
	SOCKET	sock;

deuce's avatar
deuce committed
276
277
	sock=accept(s,&addr->addr,addrlen);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL)
278
		startup->socket_open(startup->cbdata,TRUE);
279
280
281
282
283
284
285
286
287
288
289
290
291

	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);
292
	if(startup!=NULL && startup->socket_open!=NULL)
293
		startup->socket_open(startup->cbdata,FALSE);
294
	if(result!=0 && ERROR_VALUE!=ENOTSOCK)
295
		lprintf(LOG_WARNING,"!ERROR %d closing socket %d",ERROR_VALUE,sock);
296
297
298
	return(result);
}

deuce's avatar
deuce committed
299
/* TODO: IPv6 */
300
301
302
303
304
305
u_long resolve_ip(char *addr)
{
	HOSTENT*	host;
	char*		p;

	if(*addr==0)
306
		return((u_long)INADDR_NONE);
307
308

	for(p=addr;*p;p++)
309
		if(*p!='.' && !isdigit((uchar)*p))
310
311
312
313
			break;
	if(!(*p))
		return(inet_addr(addr));
	if((host=gethostbyname(addr))==NULL) 
314
		return((u_long)INADDR_NONE);
315
316
317
318
319
	return(*((ulong*)host->h_addr_list[0]));
}

} /* extern "C" */

320
321
322
323
324
325
326
327
328
329
330
#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) {
331
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
332
333
334
335
		WSAInitialized=TRUE;
		return(TRUE);
	}

336
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
337
338
339
340
341
342
343
344
345
346
	return(FALSE);
}

#else /* No WINSOCK */

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

#endif

347
348
DLLEXPORT void DLLCALL sbbs_srand()
{
349
	DWORD seed;
350

351
	xp_randomize();
352
#if defined(HAS_DEV_RANDOM) && defined(RANDOM_DEV)
353
	int     rf,rd=0;
354

355
356
	if((rf=open(RANDOM_DEV, O_RDONLY|O_NONBLOCK))!=-1) {
		rd=read(rf, &seed, sizeof(seed));
357
358
		close(rf);
	}
deuce's avatar
deuce committed
359
	if (rd != sizeof(seed))
360
#endif
361
		seed = time32(NULL) ^ (uintmax_t)GetCurrentThreadId();
362
363

 	srand(seed);
364
365
366
	sbbs_random(10);	/* Throw away first number */
}

367
int DLLCALL sbbs_random(int n)
368
369
370
371
{
	return(xp_random(n));
}

372
373
#ifdef JAVASCRIPT

374
375
static js_server_props_t js_server_props;

376
377
378
379
380
381
382
383
384
385
386
387
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
388
389
		if((array=JS_NewArrayObject(cx, 0, NULL))==NULL)	/* Assertion here, in _heap_alloc_dbg, June-21-2004 */
			return(JS_FALSE);								/* Caused by nntpservice.js? */
390

391
392
393
394
	if(!JS_DefineProperty(cx, parent, name, OBJECT_TO_JSVAL(array)
		,NULL,NULL,flags))
		return(JS_FALSE);

395
396
397
398
399
400
401
402
403
404
405
	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;
	}

406
	return(JS_TRUE);
407
408
}

409
/* Convert from Synchronet-specific jsSyncMethodSpec to JSAPI's JSFunctionSpec */
410
411

JSBool
412
DLLCALL js_DescribeSyncObject(JSContext* cx, JSObject* obj, const char* str, int ver)
413
414
415
416
417
418
{
	JSString* js_str = JS_NewStringCopyZ(cx, str);

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

rswindell's avatar
rswindell committed
419
420
421
	if(ver < 10000)		/* auto convert 313 to 31300 */
		ver*=100;

422
	return(JS_DefineProperty(cx,obj,"_description"
423
424
425
			,STRING_TO_JSVAL(js_str),NULL,NULL,JSPROP_READONLY)
		&& JS_DefineProperty(cx,obj,"_ver"
			,INT_TO_JSVAL(ver),NULL,NULL,JSPROP_READONLY));
426
427
428
}

JSBool
429
DLLCALL js_DescribeSyncConstructor(JSContext* cx, JSObject* obj, const char* str)
430
431
432
433
434
435
436
437
438
439
{
	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));
}

440
#ifdef BUILD_JSDOCS
441
442

static const char* method_array_name = "_method_list";
443
static const char* propver_array_name = "_property_ver_list";
444
445
446
447
448
449
450
451
452
453
454
455
456

/*
 * 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
457
458
	"null",
	"xml",
459
460
	"array",
	"alias",
deuce's avatar
deuce committed
461
	"undefined"
462
463
};

464
465
466
467
JSBool
DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
	uint		i;
468
	long		ver;
469
470
471
472
473
474
475
476
477
478
479
480
	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++) {
481
482
		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))
483
484
			return(JS_FALSE);
		if(props[i].flags&JSPROP_ENUMERATE) {	/* No need to version invisible props */
485
486
487
			if((ver=props[i].ver) < 10000)		/* auto convert 313 to 31300 */
				ver*=100;
			val = INT_TO_JSVAL(ver);
488
489
490
491
492
493
494
495
			if(!JS_SetElement(cx, array, len++, &val))
				return(JS_FALSE);
		}
	}

	return(JS_TRUE);
}

496
JSBool 
deuce's avatar
deuce committed
497
DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
498
499
500
{
	int			i;
	jsuint		len=0;
501
	long		ver;
502
503
504
505
	jsval		val;
	JSObject*	method;
	JSObject*	method_array;
	JSString*	js_str;
deuce's avatar
deuce committed
506
507
	size_t		str_len=0;
	char		*str=NULL;
508
509

	/* Return existing method_list array if it's already been created */
deuce's avatar
deuce committed
510
	if(JS_GetProperty(cx,obj,method_array_name,&val) && val!=JSVAL_VOID) {
511
		method_array=JSVAL_TO_OBJECT(val);
deuce's avatar
deuce committed
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
		// If the first item is already in the list, don't do anything.
		if(!JS_GetArrayLength(cx, method_array, &len))
			return(JS_FALSE);
		for(i=0; i<len; i++) {
			if(JS_GetElement(cx, method_array, i, &val)!=JS_TRUE || val == JSVAL_VOID)
				continue;
			JS_GetProperty(cx, JSVAL_TO_OBJECT(val), "name", &val);
			JSVALUE_TO_RASTRING(cx, val, str, &str_len, NULL);
			if(str==NULL)
				continue;
			if(strcmp(str, funcs[0].name)==0)
				return(JS_TRUE);
		}
	}
	else {
527
528
		if((method_array=JS_NewArrayObject(cx, 0, NULL))==NULL) 
			return(JS_FALSE);
deuce's avatar
deuce committed
529
530
		if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
				, NULL, NULL, 0))
531
			return(JS_FALSE);
deuce's avatar
deuce committed
532
	}
533
534
535

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

536
537
		if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
			return(JS_FALSE);
538
539
540
541

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

542
		method = JS_NewObject(cx, NULL, NULL, method_array);	/* exception here June-7-2003 */
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576

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

577
		if(funcs[i].ver) {
578
			if((ver=funcs[i].ver) < 10000)		/* auto convert 313 to 31300 */
579
580
				ver*=100;
			val = INT_TO_JSVAL(ver);
581
582
583
			JS_SetProperty(cx,method, "ver", &val);
		}

584
585
586
587
588
589
590
591
		val=OBJECT_TO_JSVAL(method);
		if(!JS_SetElement(cx, method_array, len+i, &val))
			return(JS_FALSE);
	}

	return(JS_TRUE);
}

592
593
594
595
596
/*
 * Always resolve all here since
 * 1) We'll always be enumerating anyways
 * 2) The speed penalty won't be seen in production code anyways
 */
597
598
JSBool
DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
599
600
601
{
	JSBool	ret=JS_TRUE;

deuce's avatar
deuce committed
602
	if(props) {
603
604
		if(!js_DefineSyncProperties(cx, obj, props))
			ret=JS_FALSE;
deuce's avatar
deuce committed
605
	}
606
		
deuce's avatar
deuce committed
607
	if(funcs) {
deuce's avatar
deuce committed
608
		if(!js_DefineSyncMethods(cx, obj, funcs))
609
			ret=JS_FALSE;
deuce's avatar
deuce committed
610
	}
611

deuce's avatar
deuce committed
612
613
614
615
	if(consts) {
		if(!js_DefineConstIntegers(cx, obj, consts, flags))
			ret=JS_FALSE;
	}
616

617
618
619
	return(ret);
}

620
#else // NON-JSDOCS
621

622
623
624
625
626
627
628
JSBool
DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec* props)
{
	uint i;

	for(i=0;props[i].name;i++) 
		if(!JS_DefinePropertyWithTinyId(cx, obj, 
629
			props[i].name,props[i].tinyid, JSVAL_VOID, NULL, NULL, props[i].flags|JSPROP_SHARED))
630
631
632
633
634
635
			return(JS_FALSE);

	return(JS_TRUE);
}


636
JSBool 
deuce's avatar
deuce committed
637
DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
638
{
639
	uint i;
640
641

	for(i=0;funcs[i].name;i++)
642
643
		if(!JS_DefineFunction(cx, obj, funcs[i].name, funcs[i].call, funcs[i].nargs, 0))
			return(JS_FALSE);
644
645
646
	return(JS_TRUE);
}

647
648
JSBool
DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags)
649
650
{
	uint i;
651
	jsval	val;
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673

	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);
			}
		}
	}
674
	if(consts) {
deuce's avatar
deuce committed
675
		for(i=0;consts[i].name;i++) {
676
			if(name==NULL || strcmp(name, consts[i].name)==0) {
677
				val=INT_TO_JSVAL(consts[i].val);
678

deuce's avatar
deuce committed
679
				if(!JS_DefineProperty(cx, obj, consts[i].name, val ,NULL, NULL, flags))
680
681
682
683
684
685
686
					return(JS_FALSE);

				if(name)
					return(JS_TRUE);
			}
		}
	}
687
688
689
690

	return(JS_TRUE);
}

691
692
#endif

693
694
695
696
697
698
699
700
/* 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++) {
701
		val=INT_TO_JSVAL(ints[i].val);
702
703
704
705

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

707
708
709
	return(JS_TRUE);
}

710
static JSBool
711
js_log(JSContext *cx, uintN argc, jsval *arglist)
712
{
713
	jsval *argv=JS_ARGV(cx, arglist);
714
715
    uintN		i=0;
	int32		level=LOG_INFO;
716
    JSString*	str=NULL;
717
	sbbs_t*		sbbs;
718
	jsrefcount	rc;
deuce's avatar
deuce committed
719
720
	char		*line=NULL;
	size_t		line_sz=0;
721

722
723
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

727
728
729
730
	if(argc > 1 && JSVAL_IS_NUMBER(argv[i])) {
		if(!JS_ValueToInt32(cx,argv[i++],&level))
			return JS_FALSE;
	}
731
732

    for(; i<argc; i++) {
rswindell's avatar
rswindell committed
733
734
		if((str=JS_ValueToString(cx, argv[i]))==NULL) {
			FREE_AND_NULL(line);
deuce's avatar
deuce committed
735
			return(JS_FALSE);
rswindell's avatar
rswindell committed
736
		}
deuce's avatar
deuce committed
737
		JSSTRING_TO_RASTRING(cx, str, line, &line_sz, NULL);
deuce's avatar
deuce committed
738
		if(line==NULL)
739
		    return(JS_FALSE);
740
		rc=JS_SUSPENDREQUEST(cx);
741
		if(sbbs->online==ON_LOCAL) {
deuce's avatar
deuce committed
742
743
744
			if(startup!=NULL && startup->event_lputs!=NULL && level <= startup->log_level) {
				startup->event_lputs(startup->event_cbdata,level,line);
			}
745
		} else
deuce's avatar
deuce committed
746
			lprintf(level,"Node %d %s", sbbs->cfg.node_num, line);
747
		JS_RESUMEREQUEST(cx, rc);
748
	}
rswindell's avatar
rswindell committed
749
750
	if(line != NULL)
		free(line);
751

752
	if(str==NULL)
753
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
754
	else
755
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
756
757
758
    return(JS_TRUE);
}

759
static JSBool
760
js_read(JSContext *cx, uintN argc, jsval *arglist)
761
{
762
	jsval *argv=JS_ARGV(cx, arglist);
763
764
765
	uchar*		buf;
	int32		len=128;
	sbbs_t*		sbbs;
766
	jsrefcount	rc;
767

768
769
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

773
774
775
776
	if(argc) {
		if(!JS_ValueToInt32(cx,argv[0],&len))
			return JS_FALSE;
	}
777
778
779
780

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

781
	rc=JS_SUSPENDREQUEST(cx);
782
	len=RingBufRead(&sbbs->inbuf,buf,len);
783
	JS_RESUMEREQUEST(cx, rc);
784
785

	if(len>0)
786
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyN(cx,(char*)buf,len)));
787
788
789
790
791
792

	free(buf);
	return(JS_TRUE);
}

static JSBool
793
js_readln(JSContext *cx, uintN argc, jsval *arglist)
794
{
795
	jsval *argv=JS_ARGV(cx, arglist);
796
797
798
	char*		buf;
	int32		len=128;
	sbbs_t*		sbbs;
799
	jsrefcount	rc;
800

801
802
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

806
807
808
809
	if(argc) {
		if(!JS_ValueToInt32(cx,argv[0],&len))
			return JS_FALSE;
	}
810
811
812
813

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

814
	rc=JS_SUSPENDREQUEST(cx);
815
	len=sbbs->getstr(buf,len,K_NONE);
816
	JS_RESUMEREQUEST(cx, rc);
817
818

	if(len>0)
819
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,buf)));
820
821
822
823
824

	free(buf);
	return(JS_TRUE);
}

825
static JSBool
826
js_write(JSContext *cx, uintN argc, jsval *arglist)
827
{
828
	jsval *argv=JS_ARGV(cx, arglist);
829
    uintN		i;
830
    JSString*	str=NULL;
831
	sbbs_t*		sbbs;
832
	jsrefcount	rc;
833
834
	char		*cstr=NULL;
	size_t		cstr_sz=0;
835

836
837
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

838
839
840
841
	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

    for (i = 0; i < argc; i++) {
deuce's avatar
deuce committed
842
843
		if((str=JS_ValueToString(cx, argv[i]))==NULL)
			return(JS_FALSE);
deuce's avatar
deuce committed
844
		JSSTRING_TO_RASTRING(cx, str, cstr, &cstr_sz, NULL);
deuce's avatar
deuce committed
845
		if(cstr==NULL)
846
		    return(JS_FALSE);
847
		rc=JS_SUSPENDREQUEST(cx);
848
		if(sbbs->online==ON_LOCAL)
deuce's avatar
deuce committed
849
			eprintf(LOG_INFO,"%s",cstr);
850
		else
deuce's avatar
deuce committed
851
			sbbs->bputs(cstr);
852
		JS_RESUMEREQUEST(cx, rc);
853
	}
rswindell's avatar
rswindell committed
854
	FREE_AND_NULL(cstr);
855
856

	if(str==NULL)
857
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
858
	else
859
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
860
861
862
    return(JS_TRUE);
}

863
static JSBool
864
js_write_raw(JSContext *cx, uintN argc, jsval *arglist)
865
{
866
	jsval *argv=JS_ARGV(cx, arglist);
867
    uintN		i;
deuce's avatar
deuce committed
868
869
    char*		str=NULL;
    size_t		str_sz=0;
870
871
	size_t		len;
	sbbs_t*		sbbs;
872
	jsrefcount	rc;
873

874
875
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

876
877
878
879
	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

    for (i = 0; i < argc; i++) {
deuce's avatar
deuce committed
880
		JSVALUE_TO_RASTRING(cx, argv[i], str, &str_sz, &len);
881
		if(str==NULL)
882
		    return(JS_FALSE);
883
884
		if(len < 1)
			continue;
885
		rc=JS_SUSPENDREQUEST(cx);
886
		sbbs->putcom(str, len);
887
		JS_RESUMEREQUEST(cx, rc);
888
	}
rswindell's avatar
rswindell committed
889
890
	if (str != NULL)
		free(str);
891
892
893
894

    return(JS_TRUE);
}

895
static JSBool
896
js_writeln(JSContext *cx, uintN argc, jsval *arglist)
897
898
{
	sbbs_t*		sbbs;
899
	jsrefcount	rc;
900

901
902
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

906
	js_write(cx,argc,arglist);
907
	rc=JS_SUSPENDREQUEST(cx);
908
909
	if(sbbs->online==ON_REMOTE)
		sbbs->bputs(crlf);
910
	JS_RESUMEREQUEST(cx, rc);
911
912
913
914
915

    return(JS_TRUE);
}

static JSBool
916
js_printf(JSContext *cx, uintN argc, jsval *arglist)
917
{
918
	jsval *argv=JS_ARGV(cx, arglist);
919
920
	char*		p;
	sbbs_t*		sbbs;
921
	jsrefcount	rc;
922

923
924
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

928
929
	if((p = js_sprintf(cx, 0, argc, argv))==NULL) {
		JS_ReportError(cx,"js_sprintf failed");
930
931
932
		return(JS_FALSE);
	}

933
	rc=JS_SUSPENDREQUEST(cx);
934
	if(sbbs->online==ON_LOCAL)
935
		eprintf(LOG_INFO,"%s",p);
936
937
	else
		sbbs->bputs(p);
938
	JS_RESUMEREQUEST(cx, rc);
939

940
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p)));
941

942
	js_sprintf_free(p);
943
944
945
946
947

    return(JS_TRUE);
}

static JSBool
948
js_alert(JSContext *cx, uintN argc, jsval *arglist)
949
{
950
	jsval *argv=JS_ARGV(cx, arglist);
951
	sbbs_t*		sbbs;
952
	jsrefcount	rc;
deuce's avatar
deuce committed
953
	char		*cstr;
954

955
956
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

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

964
	rc=JS_SUSPENDREQUEST(cx);
965
966
967
968
969
970
971
972
	if(sbbs->online==ON_LOCAL)
		eprintf(LOG_WARNING, "%s", cstr);
	else {
		sbbs->attr(sbbs->cfg.color[clr_err]);
		sbbs->bputs(cstr);
		sbbs->attr(LIGHTGRAY);
		sbbs->bputs(crlf);
	}
deuce's avatar
deuce committed
973
	free(cstr);
974
	JS_RESUMEREQUEST(cx, rc);
975

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

978
979
980
981
    return(JS_TRUE);
}

static JSBool
982
js_confirm(JSContext *cx, uintN argc, jsval *arglist)
983
{
984
	jsval *argv=JS_ARGV(cx, arglist);
985
	sbbs_t*		sbbs;
986
	jsrefcount	rc;
deuce's avatar
deuce committed
987
	char		*cstr;
988

989
990
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

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

998
	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
999
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->yesno(cstr)));
deuce's avatar
deuce committed
1000
	free(cstr);
1001
	JS_RESUMEREQUEST(cx, rc);
1002
1003
1004
	return(JS_TRUE);
}

1005
static JSBool
1006
js_deny(JSContext *cx, uintN argc, jsval *arglist)
1007
{
1008
	jsval *argv=JS_ARGV(cx, arglist);
1009
1010
	sbbs_t*		sbbs;
	jsrefcount	rc;
deuce's avatar
deuce committed
1011
	char		*cstr;
1012

1013
1014
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

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

	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
1023
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->noyes(cstr)));
deuce's avatar
deuce committed
1024
	free(cstr);
1025
1026
1027
1028
1029
	JS_RESUMEREQUEST(cx, rc);
	return(JS_TRUE);
}


1030
static JSBool
1031
js_prompt(JSContext *cx, uintN argc, jsval *arglist)
1032
{
1033
	jsval *argv=JS_ARGV(cx, arglist);
1034
1035
1036
	char		instr[81];
    JSString *	str;
	sbbs_t*		sbbs;
1037
	jsrefcount	rc;
1038
    char*		prompt=NULL;
1039

1040
1041
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

1045
1046
1047
1048
1049
	if(argc) {
		JSVALUE_TO_MSTRING(cx, argv[0], prompt, NULL);
		if(prompt==NULL)
			return(JS_FALSE);
	}
1050
1051

	if(argc>1) {
deuce's avatar
deuce committed
1052
		JSVALUE_TO_STRBUF(cx, argv[1], instr, sizeof(instr), NULL);
1053
1054
1055
	} else
		instr[0]=0;

1056
	rc=JS_SUSPENDREQUEST(cx);
1057
1058
1059
1060
	if(prompt != NULL) {
		sbbs->bprintf("\1n\1y\1h%s\1w: ",prompt);
		free(prompt);
	}
1061
1062

	if(!sbbs->getstr(instr,sizeof(instr)-1,K_EDIT)) {
1063
		JS_SET_RVAL(cx, arglist, JSVAL_NULL);
1064
		JS_RESUMEREQUEST(cx, rc);
1065
1066
		return(JS_TRUE);
	}
1067
	JS_RESUMEREQUEST(cx, rc);
1068
1069
1070
1071

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

1072
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
1073
1074
1075
    return(JS_TRUE);
}

1076
static jsSyncMethodSpec js_global_functions[] = {
1077
	{"log",				js_log,				1,	JSTYPE_STRING,	JSDOCSTR("[level,] value [,value]")
1078
	,JSDOCSTR("add a line of text to the server and/or system log, "
1079
1080
		"<i>values</i> are typically string constants or variables, "
		"<i>level</i> is the debug level/priority (default: <tt>LOG_INFO</tt>)")
1081
	,311
1082
	},
1083
1084
	{"read",			js_read,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read up to count characters from input stream")
1085
	,311
1086
1087
1088
	},
	{"readln",			js_readln,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read a single line, up to count characters, from input stream")
1089
	,311
1090
	},
1091
	{"write",			js_write,			0,	JSTYPE_VOID,	JSDOCSTR("value [,value]")
1092
	,JSDOCSTR("send one or more values (typically strings) to the server output")
1093
	,311
1094
	},
1095
1096
	{"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
1097
	,314
1098
	},
1099
1100
1101
1102
	{"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)")
1103
	,311
1104
	},
1105
    {"printf",          js_printf,          1,	JSTYPE_STRING,	JSDOCSTR("string format [,value][,value]")
1106
	,JSDOCSTR("print a formatted string - <small>CAUTION: for experienced C programmers ONLY</small>")
1107
	,310
1108
1109
1110
	},	
	{"alert",			js_alert,			1,	JSTYPE_VOID,	JSDOCSTR("value")
	,JSDOCSTR("print an alert message (ala client-side JS)")
1111
	,310
1112
1113
1114
	},
	{"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)")
1115
	,310
1116
1117
1118
	},
	{"confirm",			js_confirm,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("value")
	,JSDOCSTR("displays a Yes/No prompt and returns <i>true</i> or <i>false</i> "
1119
		"based on user's confirmation (ala client-side JS, <i>true</i> = yes)")
1120
	,310
1121
	},
1122
1123
1124
1125
1126
	{"deny",			js_deny,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("value")
	,JSDOCSTR("displays a No/Yes prompt and returns <i>true</i> or <i>false</i> "
		"based on user's denial (<i>true</i> = no)")
	,31501
	},
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
    {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;
1137
	jsrefcount	rc;
1138
	int		log_level;
1139
	char	nodestr[128];