Skip to content
Snippets Groups Projects
js_socket.c 107 KiB
Newer Older
rswindell's avatar
rswindell committed
/* Synchronet JavaScript "Socket" Object */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
rswindell's avatar
rswindell committed
 *																			*
 * 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										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

deuce's avatar
deuce committed
#include <cryptlib.h>

rswindell's avatar
rswindell committed
#include "sbbs.h"
deuce's avatar
deuce committed
#include "js_socket.h"
deuce's avatar
deuce committed
#include "multisock.h"
rswindell's avatar
rswindell committed

#define TLS_ERROR_LEVEL LOG_WARNING // It'd be nice if this was configurable

// TODO: All log output (lprintf calls) go to the terminal server's lprintf (!)

rswindell's avatar
rswindell committed
#ifdef JAVASCRIPT

static void dbprintf(bool error, js_socket_private_t* p, char* fmt, ...);
static bool do_CryptFlush(js_socket_private_t *p);
static int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val);
static int do_cryptAttributeString(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, void *val, int len);
Deucе's avatar
Deucе committed
static void do_js_close(JSContext *cx, js_socket_private_t *p, bool finalize);
static bool js_DefineSocketOptionsArray(JSContext *cx, JSObject *obj, int type);
static JSBool js_accept(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_bind(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_close(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_connect(JSContext *cx, uintN argc, jsval *arglist);
static void js_finalize_socket(JSContext *cx, JSObject *obj);
static JSBool js_ioctlsocket(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_listen(JSContext *cx, uintN argc, jsval *arglist);
Deucе's avatar
Deucе committed
static js_callback_t * js_get_callback(JSContext *cx);
static JSBool js_getsockopt(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_peek(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_poll(JSContext *cx, uintN argc, jsval *arglist);
static ushort js_port(JSContext* cx, jsval val, int type);
static JSBool js_recv(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvbin(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvfrom(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_recvline(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_send(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendbin(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendfile(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendline(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_sendto(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_setsockopt(JSContext *cx, uintN argc, jsval *arglist);
static int js_sock_read_check(js_socket_private_t *p, time_t start, int32 timeout, int i);
static JSBool js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_socket_enumerate(JSContext *cx, JSObject *obj);
static bool js_socket_peek_byte(JSContext *cx, js_socket_private_t *p);
static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
Deucе's avatar
Deucе committed
static ptrdiff_t js_socket_recv(JSContext *cx, js_socket_private_t *p, void *buf, size_t len, int flags, int timeout);
static JSBool js_socket_resolve(JSContext *cx, JSObject *obj, jsid id);
static off_t js_socket_sendfilesocket(js_socket_private_t *p, int file);
static ptrdiff_t js_socket_sendsocket(js_socket_private_t *p, const void *msg, size_t len, int flush);
static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp);
static JSBool js_install_event(JSContext *cx, uintN argc, jsval *arglist, bool once);
Deucе's avatar
Deucе committed
static JSBool js_on(JSContext *cx, uintN argc, jsval *arglist);
static JSBool js_once(JSContext *cx, uintN argc, jsval *arglist);
static void store_socket_error(js_socket_private_t* p, int error_num, const char* error_str)
{
	p->last_error = error_num;
	if (error_str != NULL)
		strlcpy(p->last_error_str, error_str, sizeof p->last_error_str);
	else
		socket_strerror(error_num, p->last_error_str, sizeof p->last_error_str);
}

static int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val)
{
deuce's avatar
deuce committed

	/* Force "sane" values (requirements) */
deuce's avatar
deuce committed
		case CRYPT_OPTION_NET_READTIMEOUT:
			if (val < 0)
				val = 0;
			if (val > 300)
				val = 300;
			break;
		default:
			break;
	}

	ret = cryptSetAttribute(session, attr, val);
	if (ret != CRYPT_OK) {
		sprintf(action, "setting attribute %d", attr);
		get_crypt_error_string(ret, session, &estr, action, &level);
			if (level < TLS_ERROR_LEVEL)
				level = TLS_ERROR_LEVEL;
			lprintf(level, "TLS %s", estr);
			free_crypt_attrstr(estr);
deuce's avatar
deuce committed
static int do_cryptAttributeString(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, void *val, int len)
{
	int   ret = cryptSetAttributeString(session, attr, val, len);
	if (ret != CRYPT_OK) {
		sprintf(action, "setting attribute string %d", attr);
		get_crypt_error_string(ret, session, &estr, "setting attribute string", &level);
		if (estr) {
			if (level < TLS_ERROR_LEVEL)
				level = TLS_ERROR_LEVEL;
			lprintf(level, "TLS %s", estr);
			free_crypt_attrstr(estr);
deuce's avatar
deuce committed
	return ret;
}

#define GCES(status, pdata, estr, action) do {                                               \
			int GCES_level;                                                                      \
			get_crypt_error_string(status, pdata->session, &estr, action, &GCES_level); \
			if (estr) {                                                                          \
				if (GCES_level < TLS_ERROR_LEVEL)                                                \
				GCES_level = TLS_ERROR_LEVEL;                                                \
				lprintf(GCES_level, "%04d TLS %s", p->sock, estr);                               \
				free_crypt_attrstr(estr);                                                                  \
			}                                                                                    \
} while (0)

#define GCESH(status, socket, handle, estr, action) do {                                     \
			int GCESH_level;                                                                     \
			get_crypt_error_string(status, handle, &estr, action, &GCESH_level);                 \
			if (estr) {                                                                          \
				if (GCESH_level < TLS_ERROR_LEVEL)                                               \
				GCESH_level = TLS_ERROR_LEVEL;                                               \
				lprintf(GCESH_level, "%04d TLS %s", socket, estr);                               \
				free_crypt_attrstr(estr);                                                                  \
			}                                                                                    \
} while (0)
static bool do_CryptFlush(js_socket_private_t *p)
deuce's avatar
deuce committed

deuce's avatar
deuce committed
	if (p->unflushed) {
		ret = cryptFlushData(p->session);
		if (ret == CRYPT_OK) {
deuce's avatar
deuce committed
			p->unflushed = 0;
			return true;
deuce's avatar
deuce committed
		}
		if (ret != CRYPT_ERROR_COMPLETE)
			GCES(ret, p, estr, "flushing data");
		return false;
deuce's avatar
deuce committed
	}
	return true;
deuce's avatar
deuce committed
}

Deucе's avatar
Deucе committed
static void
remove_js_socket_event(JSContext *cx, js_callback_t *cb, SOCKET sock)
deuce's avatar
deuce committed
{
Deucе's avatar
Deucе committed
	struct js_event_list *ev;
	struct js_event_list *nev;

	if (!cb->events_supported) {
		return;
	}

	for (ev = cb->events; ev; ev = nev) {
		nev = ev->next;
		if (ev->type == JS_EVENT_SOCKET_READABLE || ev->type == JS_EVENT_SOCKET_READABLE_ONCE
		    || ev->type == JS_EVENT_SOCKET_WRITABLE || ev->type == JS_EVENT_SOCKET_WRITABLE_ONCE) {
Deucе's avatar
Deucе committed
			if (ev->data.sock == sock) {
				if (ev->next)
					ev->next->prev = ev->prev;
				if (ev->prev)
					ev->prev->next = ev->next;
				else
					cb->events = ev->next;
				JS_RemoveObjectRoot(cx, &ev->cx);
				free(ev);
			}
		}
		else if (ev->type == JS_EVENT_SOCKET_CONNECT) {
Deucе's avatar
Deucе committed
			if (ev->data.connect.sock == sock) {
				if (ev->next)
					ev->next->prev = ev->prev;
				if (ev->prev)
					ev->prev->next = ev->next;
				else
					cb->events = ev->next;
				closesocket(ev->data.connect.sv[0]);
				JS_RemoveObjectRoot(cx, &ev->cx);
				free(ev);
			}
		}
	}
}

static void do_js_close(JSContext *cx, js_socket_private_t *p, bool finalize)
{
	size_t i;

	if (p->session != -1) {
		if (p->tls_server)
			destroy_session(lprintf, p->session);
		else
			cryptDestroySession(p->session);
deuce's avatar
deuce committed
	}
Deucе's avatar
Deucе committed

	// Delete any event handlers for the socket
	if (p->js_cb) {
		if (p->set) {
			for (i = 0; i < p->set->sock_count; i++) {
				if (p->set->socks[i].sock != INVALID_SOCKET)
					remove_js_socket_event(cx, p->js_cb, p->set->socks[i].sock);
			}
		}
		else {
			if (p->sock != INVALID_SOCKET)
				remove_js_socket_event(cx, p->js_cb, p->sock);
		}
	}

	if (p->sock == INVALID_SOCKET) {
		p->is_connected = false;
deuce's avatar
deuce committed
		return;
deuce's avatar
deuce committed
	}
	if (p->external == false) {
		close_socket(p->sock);
		store_socket_error(p, SOCKET_ERRNO, NULL);
	else {
		if (!finalize)
			shutdown(p->sock, SHUT_RDWR);
	}
	// This is a lie for external sockets... don't tell anyone.
	p->sock = INVALID_SOCKET;
	p->is_connected = false;
static bool js_socket_peek_byte(JSContext *cx, js_socket_private_t *p)
{
	if (do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, 0) != CRYPT_OK)
Deucе's avatar
Deucе committed
	if (js_socket_recv(cx, p, &p->peeked_byte, 1, 0, 0) == 1) {
/* Returns > 0 upon successful data received (even if there was an error or disconnection) */
/* Returns -1 upon error (and no data received) */
/* Returns 0 upon timeout or disconnection (and no data received) */
Deucе's avatar
Deucе committed
static ptrdiff_t js_socket_recv(JSContext *cx, js_socket_private_t *p, void *buf, size_t len, int flags, int timeout)
deuce's avatar
deuce committed
{
	ptrdiff_t total = 0;
	int       copied, ret;
	char *    estr;
	time_t    now = time(NULL);
	int       status;
	if (len == 0)
		return total;
Deucе's avatar
Deucе committed
			js_socket_peek_byte(cx, p);
		if (p->peeked) {
			*(uint8_t *)buf = p->peeked_byte;
			buf = ((uint8_t *)buf) + 1;
				p->peeked = false;
			total++;
			len--;
			if (len == 0)
				return total;
		}
		if (flags & MSG_PEEK)
			return total;
		if (do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, p->nonblocking?0:timeout) != CRYPT_OK)
			return -1;
	}
deuce's avatar
deuce committed
	do {
		if (p->session == -1) {
			if (p->sock == INVALID_SOCKET)
				ret = -1;
			else {
Deucе's avatar
Deucе committed
				ret = 0;
				if (socket_readable(p->sock, timeout * 1000))
					ret = recv(p->sock, buf, len, flags);
			}
deuce's avatar
deuce committed
		}
deuce's avatar
deuce committed
		else {
			status = cryptPopData(p->session, buf, len, &copied);
			if (cryptStatusOK(status))
				ret = copied;
			else {
				ret = -1;
				if (status == CRYPT_ERROR_TIMEOUT)
					ret = 0;
				else if (status != CRYPT_ERROR_COMPLETE) {
					GCES(ret, p, estr, "popping data");
Deucе's avatar
Deucе committed
					do_js_close(cx, p, false);
			}
		}
		if (ret == -1) {
			if (total > 0)
				return total;
			return ret;
		}
		total += ret;
		if ((!(flags & MSG_WAITALL)) || p->nonblocking)
			return total;
		if (ret >= (ptrdiff_t)len)
		len -= ret;
		buf = ((uint8_t *)buf) + ret;
		if (!socket_check(p->sock, NULL, NULL, 0)) {
deuce's avatar
deuce committed
			if (total > 0)
				return total;
			return -1;
deuce's avatar
deuce committed
		}
		if (now + timeout > time(NULL))
			return total;
deuce's avatar
deuce committed
}

deuce's avatar
deuce committed
static ptrdiff_t js_socket_sendsocket(js_socket_private_t *p, const void *msg, size_t len, int flush)
deuce's avatar
deuce committed
{
	ptrdiff_t total = 0;
	int       copied = 0, ret;
	char *    estr;
	if (p->session == -1)
deuce's avatar
deuce committed
		return sendsocket(p->sock, msg, len);
	do {
		// If we don't limit this, we occasionally get errors on large sends...
		if ((ret = cryptPushData(p->session, msg, len > 0x2000 ? 0x2000 : len, &copied)) == CRYPT_OK) {
deuce's avatar
deuce committed
			p->unflushed += copied;
				do_CryptFlush(p);
			if (p->nonblocking)
deuce's avatar
deuce committed
				return copied;
			total += copied;
			if (copied >= (ptrdiff_t)len)
deuce's avatar
deuce committed
				return total;
deuce's avatar
deuce committed
			do_CryptFlush(p);
deuce's avatar
deuce committed
			len -= copied;
			msg = ((uint8_t *)msg) + copied;
deuce's avatar
deuce committed
		}
		else {
			if (ret != CRYPT_ERROR_COMPLETE)
				GCES(ret, p, estr, "pushing data");
				do_CryptFlush(p);
deuce's avatar
deuce committed
			return total;
		}
		if (!socket_check(p->sock, NULL, NULL, 0))
	} while (len);
	if (flush)
		do_CryptFlush(p);
deuce's avatar
deuce committed
}

static off_t js_socket_sendfilesocket(js_socket_private_t *p, int file)
deuce's avatar
deuce committed
{
	char  buf[1024 * 16];
	off_t total = 0;
deuce's avatar
deuce committed

	for (;;) {
		ssize_t rd = read(file, buf, sizeof(buf));
		if (rd < 0) {
			if (p->session != -1)
				do_CryptFlush(p);
deuce's avatar
deuce committed
		}
		if (rd == 0)
deuce's avatar
deuce committed
			break;
		while (sent < rd) {
			ptrdiff_t wr = js_socket_sendsocket(p, buf + sent, rd - sent, false);
			if (wr > 0) {
				sent += wr;
			}
			else if (wr == SOCKET_ERROR && SOCKET_ERRNO == EWOULDBLOCK) {
deuce's avatar
deuce committed
				SLEEP(1);
			}
			else {
				if (p->session != -1)
					do_CryptFlush(p);
deuce's avatar
deuce committed
		}
		if (sent != rd) {
			if (p->session != -1)
				do_CryptFlush(p);
deuce's avatar
deuce committed
		}
		total += rd;
deuce's avatar
deuce committed
	}

	if (p->session != -1)
		do_CryptFlush(p);
deuce's avatar
deuce committed
}

static void dbprintf(bool error, js_socket_private_t* p, char* fmt, ...)
	if (p == NULL || (!p->debug /*&& !error */))
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0;
	va_end(argptr);
	lprintf(LOG_DEBUG, "%04d Socket %s%s", p->sock, error ? "ERROR: ":"", sbuf);
rswindell's avatar
rswindell committed
/* Socket Destructor */

static void js_finalize_socket(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed
	js_socket_private_t* p;
	if ((p = (js_socket_private_t*)JS_GetPrivate(cx, obj)) == NULL)
Deucе's avatar
Deucе committed
	if (p->tls_psk)
		JS_RemoveObjectRoot(cx, &p->tls_psk);

Deucе's avatar
Deucе committed
	do_js_close(cx, p, true);
		free(p->set);
	free(p->hostname);
rswindell's avatar
rswindell committed

rswindell's avatar
rswindell committed
}


/* Socket Object Methods */

js_close(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	js_socket_private_t* p;
	jsrefcount           rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
	rc = JS_SUSPENDREQUEST(cx);
Deucе's avatar
Deucе committed
	do_js_close(cx, p, false);
	dbprintf(false, p, "closed");
static ushort js_port(JSContext* cx, jsval val, int type)
{
	char*           cp;
	JSString*       str;
	int32           i = 0;
	struct servent* serv;
	jsrefcount      rc;
	if (JSVAL_IS_NUMBER(val)) {
		JS_ValueToInt32(cx, val, &i);
	if (JSVAL_IS_STRING(val)) {
		str = JS_ValueToString(cx, val);
deuce's avatar
deuce committed
		JSSTRING_TO_ASTRING(cx, str, cp, 16, NULL);
		if (cp != NULL) {
			if (IS_DIGIT(*cp))
				return (ushort)strtol(cp, NULL, 0);
			rc = JS_SUSPENDREQUEST(cx);
			serv = getservbyname(cp, type == SOCK_STREAM ? "tcp":"udp");
			JS_RESUMEREQUEST(cx, rc);
				return htons(serv->s_port);
SOCKET js_socket(JSContext *cx, jsval val)
	void*    vp;
	JSClass* cl;
	SOCKET   sock = INVALID_SOCKET;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE)
			if ((vp = JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL)
				sock = *(SOCKET*)vp;
	} else if (val != JSVAL_VOID) {
		int32 i;
		if (JS_ValueToInt32(cx, val, &i))
#ifdef PREFER_POLL
size_t js_socket_numsocks(JSContext *cx, jsval val)
deuce's avatar
deuce committed
{
	js_socket_private_t *p;
	JSClass*             cl;
	SOCKET               sock = INVALID_SOCKET;
	size_t               i;
	int32_t              intval;
	size_t               ret = 0;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE) {
			if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
				if (p->set) {
					for (i = 0; i < p->set->sock_count; i++) {
						if (p->set->socks[i].sock == INVALID_SOCKET)
deuce's avatar
deuce committed
							continue;
deuce's avatar
deuce committed
					}
				}
				else {
					sock = p->sock;
					if (sock != INVALID_SOCKET)
deuce's avatar
deuce committed
				}
			}
		}
	} else if (val != JSVAL_VOID) {
		if (JS_ValueToInt32(cx, val, &intval)) {
			if (intval != INVALID_SOCKET)
				ret = 1;
deuce's avatar
deuce committed
		}
	}
deuce's avatar
deuce committed
}

size_t js_socket_add(JSContext *cx, jsval val, struct pollfd *fds, short events)
deuce's avatar
deuce committed
{
	js_socket_private_t *p;
	JSClass*             cl;
	SOCKET               sock = INVALID_SOCKET;
	size_t               i;
	int32_t              intval;
	size_t               ret = 0;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE) {
			if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
				if (p->set) {
					for (i = 0; i < p->set->sock_count; i++) {
						if (p->set->socks[i].sock == INVALID_SOCKET)
deuce's avatar
deuce committed
							continue;
						fds[ret].events = events;
						fds[ret++].fd = p->set->socks[i].sock;
deuce's avatar
deuce committed
					}
				}
				else {
					if (sock != INVALID_SOCKET) {
						fds[ret].events = events;
						fds[ret++].fd = sock;
deuce's avatar
deuce committed
				}
			}
		}
	} else if (val != JSVAL_VOID) {
		if (JS_ValueToInt32(cx, val, &intval)) {
			if (sock != INVALID_SOCKET) {
				fds[ret].events = events;
				fds[ret++].fd = sock;
			}
Deucе's avatar
Deucе committed
#else
SOCKET js_socket_add(JSContext *cx, jsval val, fd_set *fds)
Deucе's avatar
Deucе committed
{
	js_socket_private_t *p;
	JSClass*             cl;
	SOCKET               sock = INVALID_SOCKET;
	size_t               i;
	int32_t              intval;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE) {
			if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
				if (p->set) {
					for (i = 0; i < p->set->sock_count; i++) {
						if (p->set->socks[i].sock == INVALID_SOCKET)
Deucе's avatar
Deucе committed
							continue;
						FD_SET(p->set->socks[i].sock, fds);
						if (p->set->socks[i].sock > sock)
							sock = p->set->socks[i].sock;
Deucе's avatar
Deucе committed
					}
				}
				else {
					sock = p->sock;
					if (sock != INVALID_SOCKET)
						FD_SET(p->sock, fds);
Deucе's avatar
Deucе committed
				}
			}
		}
	} else if (val != JSVAL_VOID) {
		if (JS_ValueToInt32(cx, val, &intval)) {
			sock = intval;
			FD_SET(sock, fds);
Deucе's avatar
Deucе committed
		}
	}
Deucе's avatar
Deucе committed
}

bool  js_socket_isset(JSContext *cx, jsval val, fd_set *fds)
Deucе's avatar
Deucе committed
{
	js_socket_private_t *p;
	JSClass*             cl;
	size_t               i;
	int                  intval;

	if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && (cl = JS_GetClass(cx, JSVAL_TO_OBJECT(val))) != NULL) {
		if (cl->flags & JSCLASS_HAS_PRIVATE) {
			if ((p = (js_socket_private_t *)JS_GetInstancePrivate(cx, JSVAL_TO_OBJECT(val), &js_socket_class, NULL)) != NULL) {
				if (p->set) {
					for (i = 0; i < p->set->sock_count; i++) {
						if (p->set->socks[i].sock == INVALID_SOCKET)
Deucе's avatar
Deucе committed
							continue;
						if (FD_ISSET(p->set->socks[i].sock, fds))
Deucе's avatar
Deucе committed
					}
				}
				else {
					if (p->sock == INVALID_SOCKET)
						return TRUE;
					else {
						if (FD_ISSET(p->sock, fds))
	} else if (val != JSVAL_VOID) {
		if (JS_ValueToInt32(cx, val, &intval)) {
			if (FD_ISSET(intval, fds))
void js_timeval(JSContext* cx, jsval val, struct timeval* tv)
	if (JSVAL_IS_INT(val))
		tv->tv_sec = JSVAL_TO_INT(val);
	else if (JSVAL_IS_DOUBLE(val)) {
		if (JS_ValueToNumber(cx, val, &jsd)) {
			tv->tv_sec = (int)jsd;
			tv->tv_usec = (int)(jsd * 1000000.0) % 1000000;
int js_polltimeout(JSContext* cx, jsval val)
Deucе's avatar
Deucе committed
{
	jsdouble jsd;

	if (JSVAL_IS_INT(val))
Deucе's avatar
Deucе committed
		return JSVAL_TO_INT(val) * 1000;

	if (JSVAL_IS_DOUBLE(val)) {
		if (JS_ValueToNumber(cx, val, &jsd))
Deucе's avatar
Deucе committed
	}

	return 0;
}
rswindell's avatar
rswindell committed
static JSBool
js_bind(JSContext *cx, uintN argc, jsval *arglist)
rswindell's avatar
rswindell committed
{
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	js_socket_private_t* p;
	ushort               port = 0;
	union xp_sockaddr    addr;
	jsrefcount           rc;
	char *               cstr = NULL;
	char                 portstr[6];
	struct addrinfo      hints, *res, *tres;
	int                  ret;
rswindell's avatar
rswindell committed

deuce's avatar
deuce committed
	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
	memset(&addr, 0, sizeof(addr));
deuce's avatar
deuce committed
	memset(&hints, 0, sizeof(hints));
rswindell's avatar
rswindell committed

	if (argc)
		port = js_port(cx, argv[0], p->type);
	if (argc > 1 && argv[1] != JSVAL_VOID) {
deuce's avatar
deuce committed
		JSVALUE_TO_ASTRING(cx, argv[1], cstr, INET6_ADDRSTRLEN, NULL);
	}

	hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;
deuce's avatar
deuce committed
	hints.ai_socktype = p->type;

	/* We need servname to be non-NULL so we can use a NULL hostname */
	sprintf(portstr, "%hu", port);
rswindell's avatar
rswindell committed

	rc = JS_SUSPENDREQUEST(cx);
	if ((ret = getaddrinfo(cstr, portstr, &hints, &res)) != 0) {
		JS_RESUMEREQUEST(cx, rc);
		dbprintf(TRUE, p, "getaddrinfo(%s, %s) failed with error %d", cstr, portstr, ret);
		store_socket_error(p, ret, gai_strerror(ret));
rswindell's avatar
rswindell committed
	}
	for (tres = res; tres; tres = tres->ai_next) {
		if (bind(p->sock, tres->ai_addr, tres->ai_addrlen) != 0) {
deuce's avatar
deuce committed
			if (tres->ai_next == NULL) {
				store_socket_error(p, SOCKET_ERRNO, NULL);
				dbprintf(TRUE, p, "bind failed with error %d", SOCKET_ERRNO);
deuce's avatar
deuce committed
				freeaddrinfo(res);
				JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
			}
		}
deuce's avatar
deuce committed
	}
	freeaddrinfo(res);
rswindell's avatar
rswindell committed

	dbprintf(false, p, "bound to port %u", port);
	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
rswindell's avatar
rswindell committed
}

js_listen(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	jsval *              argv = JS_ARGV(cx, arglist);
	js_socket_private_t* p;
	int32                backlog = 1;
	jsrefcount           rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
	if (argc && argv[0] != JSVAL_VOID)
		backlog = JS_ValueToInt32(cx, argv[0], &backlog);
	rc = JS_SUSPENDREQUEST(cx);
	if (listen(p->sock, backlog) != 0) {
		store_socket_error(p, SOCKET_ERRNO, NULL);
		dbprintf(TRUE, p, "listen failed with error %d", SOCKET_ERRNO);
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
	dbprintf(false, p, "listening, backlog=%d", backlog);
	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
js_accept(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *           obj = JS_THIS_OBJECT(cx, arglist);
	js_socket_private_t* p;
	js_socket_private_t* new_p;
	JSObject*            sockobj;
	SOCKET               new_socket;
	socklen_t            addrlen;
	jsrefcount           rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class)) == NULL) {
	addrlen = sizeof(p->remote_addr);
	rc = JS_SUSPENDREQUEST(cx);
	if (p->set) {
		if ((new_socket = xpms_accept(p->set, &(p->remote_addr), &addrlen, XPMS_FOREVER, XPMS_FLAGS_NONE, NULL)) == INVALID_SOCKET) {
			store_socket_error(p, SOCKET_ERRNO, NULL);
			dbprintf(TRUE, p, "accept failed with error %d", SOCKET_ERRNO);
deuce's avatar
deuce committed
			JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
		}
deuce's avatar
deuce committed
	}
	else {
		if ((new_socket = accept_socket(p->sock, &(p->remote_addr), &addrlen)) == INVALID_SOCKET) {
			store_socket_error(p, SOCKET_ERRNO, NULL);
			dbprintf(TRUE, p, "accept failed with error %d", SOCKET_ERRNO);
deuce's avatar
deuce committed
			JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
		}
	if ((sockobj = js_CreateSocketObject(cx, obj, "new_socket", new_socket, -1)) == NULL) {
		JS_ReportError(cx, "Error creating new socket object");
	if ((new_p = (js_socket_private_t*)JS_GetPrivate(cx, sockobj)) == NULL) {
	new_p->type = p->type;
Deucе's avatar
Deucе committed
	if (new_p->type == 0)
		new_p->type = SOCK_STREAM;
	new_p->debug = p->debug;
	new_p->nonblocking = p->nonblocking;
	new_p->external = false;      /* let destructor close socket */
	new_p->is_connected = TRUE;
	dbprintf(false, p, "accepted connection");
	JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(sockobj));
Deucе's avatar
Deucе committed
struct js_connect_event_args {
	SOCKET sv[2];
	SOCKET sock;
	int socktype;
	char *host;
	bool nonblocking;
Deucе's avatar
Deucе committed
	ushort port;
};

static void
js_connect_event_thread(void *args)
{
	struct js_connect_event_args *a = args;
	struct addrinfo               hints, *res = NULL, *cur;
	int                           result;
	ulong                         val;
	char                          sresult;
Deucе's avatar
Deucе committed

	SetThreadName("sbbs/jsConnect");
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = a->socktype;
	hints.ai_flags = AI_ADDRCONFIG;
	result = getaddrinfo(a->host, NULL, &hints, &res);
Deucе's avatar
Deucе committed
		goto done;
	/* always set to blocking here */
	val = 0;
	ioctlsocket(a->sock, FIONBIO, &val);
	result = SOCKET_ERROR;
	for (cur = res; cur != NULL; cur = cur->ai_next) {
Deucе's avatar
Deucе committed
		inet_setaddrport((void *)cur->ai_addr, a->port);

		result = connect(a->sock, cur->ai_addr, cur->ai_addrlen);
Deucе's avatar
Deucе committed
			break;
	}
	sresult = result;
	/* Restore original setting here */
	ioctlsocket(a->sock, FIONBIO, (ulong*)&(a->nonblocking));
Deucе's avatar
Deucе committed
	send(a->sv[1], &sresult, 1, 0);

done:
	closesocket(a->sv[1]);
	freeaddrinfo(res);
Deucе's avatar
Deucе committed
	free(a);
}

static JSBool
js_connect_event(JSContext *cx, uintN argc, jsval *arglist, js_socket_private_t *p, ushort port, JSObject *obj)
{
	SOCKET                        sv[2];
	struct js_event_list *        ev;
	JSFunction *                  ecb;
	js_callback_t *               cb = js_get_callback(cx);
Deucе's avatar
Deucе committed
	struct js_connect_event_args *args;
	jsval *                       argv = JS_ARGV(cx, arglist);
Deucе's avatar
Deucе committed

	if (p->sock == INVALID_SOCKET) {
		JS_ReportError(cx, "invalid socket");
Deucе's avatar
Deucе committed
		return JS_FALSE;
	}

	if (cb == NULL) {
		return JS_FALSE;
	}

	if (!cb->events_supported) {
		JS_ReportError(cx, "events not supported");
Deucе's avatar
Deucе committed
		return JS_FALSE;
	}

	ecb = JS_ValueToFunction(cx, argv[2]);
	if (ecb == NULL) {
		return JS_FALSE;
	}

	// Create socket pair...
#ifdef _WIN32
	if (socketpair(AF_INET, SOCK_STREAM, 0, sv) == -1) {
#else
	if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
#endif
		JS_ReportError(cx, "Error %d creating socket pair", SOCKET_ERRNO);
Deucе's avatar
Deucе committed
		return JS_FALSE;
	}

	// Create event
	ev = malloc(sizeof(*ev));
	if (ev == NULL) {
		JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
Deucе's avatar
Deucе committed
		closesocket(sv[0]);
		closesocket(sv[1]);
		return JS_FALSE;
	}
	ev->prev = NULL;
	ev->next = cb->events;
	if (ev->next)
		ev->next->prev = ev;
	ev->type = JS_EVENT_SOCKET_CONNECT;
	ev->cx = obj;
	JS_AddObjectRoot(cx, &ev->cx);
	ev->cb = ecb;
	ev->data.connect.sv[0] = sv[0];
	ev->data.connect.sv[1] = sv[1];
	ev->data.connect.sock = p->sock;
Deucе's avatar
Deucе committed
	ev->id = cb->next_eid++;
	p->js_cb = cb;
	cb->events = ev;

	// Start thread
	args = malloc(sizeof(*args));
	if (args == NULL) {
		JS_ReportError(cx, "error allocating %lu bytes", sizeof(*args));
Deucе's avatar
Deucе committed
		closesocket(sv[0]);