Skip to content
Snippets Groups Projects
js_socket.c 82.8 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

#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);
static void do_js_close(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);
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(js_socket_private_t *p);
static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
static ptrdiff_t js_socket_recv(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 int js_socket_sendfilesocket(js_socket_private_t *p, int file, off_t *offset, off_t count);
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 int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val)
{
deuce's avatar
deuce committed
	int ret;
	int level;
	char *estr;
	char action[32];
deuce's avatar
deuce committed

	/* Force "sane" values (requirements) */
	switch(attr) {
		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);
			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 level;
	char *estr;
	char action[48];
deuce's avatar
deuce committed
	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) {
			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); \
		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);        \
		lprintf(GCESH_level, "%04d TLS %s", socket, estr);                               \
		free_crypt_attrstr(estr);                                                                  \
static bool do_CryptFlush(js_socket_private_t *p)
deuce's avatar
deuce committed
	int ret;
deuce's avatar
deuce committed

deuce's avatar
deuce committed
	if (p->unflushed) {
		ret = cryptFlushData(p->session);
deuce's avatar
deuce committed
		if(ret==CRYPT_OK) {
			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
}

static void do_js_close(js_socket_private_t *p, bool finalize)
deuce's avatar
deuce committed
{
	if(p->session != -1) {
		cryptDestroySession(p->session);
		p->session=-1;
	}
deuce's avatar
deuce committed
	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);
		p->last_error = ERROR_VALUE;
	}
	else {
		if (!finalize)
			shutdown(p->sock, SHUT_RDWR);
	}
	// This is a lie for external sockets... don't tell anyone.
	p->sock = INVALID_SOCKET;
deuce's avatar
deuce committed
	p->is_connected = FALSE;
static BOOL js_socket_peek_byte(js_socket_private_t *p)
{
	if (do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, 0) != CRYPT_OK)
		return FALSE;
	if (p->peeked)
		return TRUE;
	if (js_socket_recv(p, &p->peeked_byte, 1, 0, 0) == 1) {
		p->peeked = TRUE;
		return TRUE;
	}
	return FALSE;
}

/* 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) */
deuce's avatar
deuce committed
static ptrdiff_t js_socket_recv(js_socket_private_t *p, void *buf, size_t len, int flags, int timeout)
deuce's avatar
deuce committed
{
deuce's avatar
deuce committed
	int	copied,ret;
	fd_set		socket_set;
	struct		timeval tv = {0, 0};
	time_t now = time(NULL);
	int status;
	if (len == 0)
		return total;
		if (flags & MSG_PEEK)
			js_socket_peek_byte(p);
		if (p->peeked) {
			*(uint8_t *)buf = p->peeked_byte;
			buf=((uint8_t *)buf) + 1;
			if (!(flags & MSG_PEEK))
				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->sock == INVALID_SOCKET)
				ret = -1;
			else {
				FD_ZERO(&socket_set);
				FD_SET(p->sock,&socket_set);
				tv.tv_sec = timeout;
				if((ret = select(p->sock+1,&socket_set,NULL,NULL,&tv))==1)
					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");
			}
		}
		if (ret == -1) {
			if (total > 0)
				return total;
			return ret;
		}
		if ((!(flags & MSG_WAITALL)) || p->nonblocking)
		total += ret;
		if(total>=(ptrdiff_t)len)
			return total;
		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
	} while(len);
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
{
deuce's avatar
deuce committed
	int copied=0,ret;
deuce's avatar
deuce committed
	if(p->session==-1)
		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;
			if(flush)
				do_CryptFlush(p);
deuce's avatar
deuce committed
			if(p->nonblocking)
deuce's avatar
deuce committed
				return copied;
			total += copied;
deuce's avatar
deuce committed
			if(total >= (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");
			if(flush)
				do_CryptFlush(p);
deuce's avatar
deuce committed
			return total;
		}
		if(!socket_check(p->sock,NULL,NULL,0))
			break;
deuce's avatar
deuce committed
	} while(len);
	if(flush)
		do_CryptFlush(p);
deuce's avatar
deuce committed
}

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

	if(p->session==-1)
		return sendfilesocket(p->sock, file, offset, count);

	len=filelength(file);

	if(offset!=NULL)
		if(lseek(file,*offset,SEEK_SET)<0)
			return(-1);

	if(count<1 || count>len) {
		count=len;
		count-=tell(file);		/* don't try to read beyond EOF */
	}

	if(count<0) {
		errno=EINVAL;
		return(-1);
	}

	while(total<count) {
		rd=read(file,buf,sizeof(buf));
		if(rd==-1) {
deuce's avatar
deuce committed
			do_CryptFlush(p);
deuce's avatar
deuce committed
			return(-1);
		}
		if(rd==0)
			break;
		for(i=wr=0;i<rd;i+=wr) {
			wr=js_socket_sendsocket(p,buf+i,rd-i,FALSE);
			if(wr>0)
				continue;
			if(wr==SOCKET_ERROR && ERROR_VALUE==EWOULDBLOCK) {
				wr=0;
				SLEEP(1);
				continue;
			}
deuce's avatar
deuce committed
			do_CryptFlush(p);
deuce's avatar
deuce committed
			return(wr);
		}
		if(i!=rd) {
deuce's avatar
deuce committed
			do_CryptFlush(p);
deuce's avatar
deuce committed
			return(-1);
		}
		total+=rd;
	}

	if(offset!=NULL)
		(*offset)+=total;

deuce's avatar
deuce committed
	do_CryptFlush(p);
deuce's avatar
deuce committed
	return(total);
}

deuce's avatar
deuce committed
static void dbprintf(BOOL error, js_socket_private_t* p, char* fmt, ...)
{
	va_list argptr;
	char sbuf[1024];

	if(p==NULL || (!p->debug /*&& !error */))
		return;

    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;
deuce's avatar
deuce committed
	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL)
	if(!p->external)
		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);
deuce's avatar
deuce committed
	js_socket_private_t*	p;
deuce's avatar
deuce committed
	jsrefcount	rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((p=(js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class))==NULL) {
static ushort js_port(JSContext* cx, jsval val, int type)
{
deuce's avatar
deuce committed
	jsrefcount		rc;
	if(JSVAL_IS_NUMBER(val)) {
		JS_ValueToInt32(cx,val,&i);
		return((ushort)i);
	}
	if(JSVAL_IS_STRING(val)) {
		str = JS_ValueToString(cx,val);
deuce's avatar
deuce committed
		JSSTRING_TO_ASTRING(cx, str, cp, 16, NULL);
			return((ushort)strtol(cp,NULL,0));
		serv = getservbyname(cp,type==SOCK_STREAM ? "tcp":"udp");
		if(serv!=NULL)
			return(htons(serv->s_port));
	}
	return(0);
}

SOCKET DLLCALL js_socket(JSContext *cx, jsval val)
{
	void*		vp;
	if(JSVAL_IS_OBJECT(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)
	} else if(val!=JSVAL_VOID) {
		int32	i;
		if(JS_ValueToInt32(cx,val,&i))
			sock = i;
	}
deuce's avatar
deuce committed
SOCKET DLLCALL js_socket_add(JSContext *cx, jsval val, fd_set *fds)
{
	js_socket_private_t	*p;
	JSClass*	cl;
	SOCKET		sock=INVALID_SOCKET;
deuce's avatar
deuce committed

	if(JSVAL_IS_OBJECT(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) {
deuce's avatar
deuce committed
				if(p->set) {
					for(i=0; i<p->set->sock_count; i++) {
						if(p->set->socks[i].sock == INVALID_SOCKET)
							continue;
						FD_SET(p->set->socks[i].sock, fds);
						if(p->set->socks[i].sock > sock)
							sock = p->set->socks[i].sock;
					}
				}
				else {
					sock = p->sock;
					if(sock != INVALID_SOCKET)
						FD_SET(p->sock, fds);
				}
			}
		}
	} else if(val!=JSVAL_VOID) {
		if(JS_ValueToInt32(cx,val,&intval)) {
			sock = intval;
deuce's avatar
deuce committed
			FD_SET(sock, fds);
		}
	}
	return sock;
}

BOOL DLLCALL  js_socket_isset(JSContext *cx, jsval val, fd_set *fds)
{
	js_socket_private_t	*p;
	JSClass*	cl;
deuce's avatar
deuce committed

	if(JSVAL_IS_OBJECT(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) {
deuce's avatar
deuce committed
				if(p->set) {
					for(i=0; i<p->set->sock_count; i++) {
						if(p->set->socks[i].sock == INVALID_SOCKET)
							continue;
						if(FD_ISSET(p->set->socks[i].sock, fds))
							return TRUE;
					}
				}
				else {
deuce's avatar
deuce committed
					if (p->sock == INVALID_SOCKET)
deuce's avatar
deuce committed
						return TRUE;
					else {
						if(FD_ISSET(p->sock, fds))
							return TRUE;
					}
deuce's avatar
deuce committed
				}
			}
		}
	} else if(val!=JSVAL_VOID) {
		if(JS_ValueToInt32(cx,val,&intval)) {
			if(FD_ISSET(intval, fds))
deuce's avatar
deuce committed
				return TRUE;
		}
	}
	return FALSE;
}

void DLLCALL js_timeval(JSContext* cx, jsval val, struct timeval* tv)
{
	jsdouble jsd;

	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;
		}
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);
deuce's avatar
deuce committed
	js_socket_private_t*	p;
deuce's avatar
deuce committed
	union xp_sockaddr	addr;
deuce's avatar
deuce committed
	jsrefcount	rc;
deuce's avatar
deuce committed
	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;
	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

deuce's avatar
deuce committed
	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);
		p->last_error=ERROR_VALUE;
		return(JS_TRUE);
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) {
				p->last_error=ERROR_VALUE;
				dbprintf(TRUE, p, "bind failed with error %d",ERROR_VALUE);
				freeaddrinfo(res);
				JS_RESUMEREQUEST(cx, rc);
				return(JS_TRUE);
			}
		}
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);
	return(JS_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);
deuce's avatar
deuce committed
	js_socket_private_t*	p;
rswindell's avatar
rswindell committed
	int32		backlog=1;
deuce's avatar
deuce committed
	jsrefcount	rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((p=(js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class))==NULL) {
		backlog = JS_ValueToInt32(cx,argv[0],&backlog);

	if(listen(p->sock, backlog)!=0) {
		p->last_error=ERROR_VALUE;
		dbprintf(TRUE, p, "listen failed with error %d",ERROR_VALUE);
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
		return(JS_TRUE);
	}

	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);
deuce's avatar
deuce committed
	js_socket_private_t*	p;
	js_socket_private_t*	new_p;
	JSObject*	sockobj;
	SOCKET		new_socket;
deuce's avatar
deuce committed
	jsrefcount	rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((p=(js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class))==NULL) {
deuce's avatar
deuce committed
	if(p->set) {
		if((new_socket=xpms_accept(p->set,&(p->remote_addr),&addrlen,XPMS_FOREVER,XPMS_FLAGS_NONE,NULL))==INVALID_SOCKET) {
deuce's avatar
deuce committed
			p->last_error=ERROR_VALUE;
			dbprintf(TRUE, p, "accept failed with error %d",ERROR_VALUE);
			JS_RESUMEREQUEST(cx, rc);
			return(JS_TRUE);
		}
deuce's avatar
deuce committed
	}
	else {
		if((new_socket=accept_socket(p->sock,&(p->remote_addr),&addrlen))==INVALID_SOCKET) {
			p->last_error=ERROR_VALUE;
			dbprintf(TRUE, p, "accept failed with error %d",ERROR_VALUE);
			JS_RESUMEREQUEST(cx, rc);
			return(JS_TRUE);
		}
	if((sockobj=js_CreateSocketObject(cx, obj, "new_socket", new_socket, -1))==NULL) {
		JS_ReportError(cx,"Error creating new socket object");
deuce's avatar
deuce committed
	if((new_p=(js_socket_private_t*)JS_GetPrivate(cx,sockobj))==NULL) {

	new_p->type=p->type;
	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));
rswindell's avatar
rswindell committed
static JSBool
js_connect(JSContext *cx, uintN argc, jsval *arglist)
rswindell's avatar
rswindell committed
{
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
rswindell's avatar
rswindell committed
	ushort		port;
	JSString*	str;
deuce's avatar
deuce committed
	js_socket_private_t*	p;
	fd_set		socket_set;
	struct		timeval tv = {0, 0};
deuce's avatar
deuce committed
	jsrefcount	rc;
deuce's avatar
deuce committed
	char		ip_str[256];
	struct addrinfo	hints,*res,*cur;
	if((p=(js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class))==NULL) {
		return(JS_FALSE);
rswindell's avatar
rswindell committed

	str = JS_ValueToString(cx, argv[0]);
deuce's avatar
deuce committed
	if(p->hostname)
		free(p->hostname);
deuce's avatar
deuce committed
	JSSTRING_TO_MSTRING(cx, str, p->hostname, NULL);
deuce's avatar
deuce committed
	port = js_port(cx,argv[1],p->type);
deuce's avatar
deuce committed
	rc=JS_SUSPENDREQUEST(cx);
	dbprintf(FALSE, p, "resolving hostname: %s", p->hostname);
deuce's avatar
deuce committed

	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = p->type;
	hints.ai_flags = AI_ADDRCONFIG;
	result = getaddrinfo(p->hostname, NULL, &hints, &res);
	if(result != 0) {
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
		p->last_error = ERROR_VALUE;
		dbprintf(TRUE, p, "getaddrinfo(%s) failed with error %d", p->hostname, result);
		return(JS_TRUE);
rswindell's avatar
rswindell committed
	}
	/* always set to nonblocking here */
	val=1;
	ioctlsocket(p->sock,FIONBIO,&val);
	result = SOCKET_ERROR;
	for(cur=res; cur != NULL; cur=cur->ai_next) {
deuce's avatar
deuce committed
		tv.tv_sec = 10;	/* default time-out */
deuce's avatar
deuce committed
		if(argc>2)	/* time-out value specified */
			js_timeval(cx,argv[2],&tv);
deuce's avatar
deuce committed
		inet_addrtop((void *)cur->ai_addr, ip_str, sizeof(ip_str));
		dbprintf(FALSE, p, "connecting to %s on port %u at %s", ip_str, port, p->hostname);
		inet_setaddrport((void *)cur->ai_addr, port);
		result = connect(p->sock, cur->ai_addr, cur->ai_addrlen);

		if(result == SOCKET_ERROR) {
			result = ERROR_VALUE;
			if(result == EWOULDBLOCK || result == EINPROGRESS) {
				FD_ZERO(&socket_set);
				FD_SET(p->sock,&socket_set);
				result = ETIMEDOUT;
				if(select(p->sock+1,NULL,&socket_set,NULL,&tv)==1) {
					int so_error = -1;
					socklen_t optlen = sizeof(so_error);
					if(getsockopt(p->sock, SOL_SOCKET, SO_ERROR, (void*)&so_error, &optlen) == 0 && so_error == 0)
						result = 0;	/* success */
					else
						result = so_error;
				}
deuce's avatar
deuce committed
		}
deuce's avatar
deuce committed
			break;
	}
	/* Restore original setting here */
	ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));

	if(result!=0) {
deuce's avatar
deuce committed
		freeaddrinfo(res);
		p->last_error = result;
		dbprintf(TRUE, p, "connect failed with error %d", result);
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
		return(JS_TRUE);
rswindell's avatar
rswindell committed
	}
deuce's avatar
deuce committed
	memcpy(&p->remote_addr, cur->ai_addr, cur->ai_addrlen);
	freeaddrinfo(res);
rswindell's avatar
rswindell committed

	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
deuce's avatar
deuce committed
	dbprintf(FALSE, p, "connected to %s on port %u at %s", ip_str, port, p->hostname);
rswindell's avatar
rswindell committed
	return(JS_TRUE);
}

static JSBool
js_send(JSContext *cx, uintN argc, jsval *arglist)
rswindell's avatar
rswindell committed
{
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
	size_t		len;
rswindell's avatar
rswindell committed
	JSString*	str;
deuce's avatar
deuce committed
	js_socket_private_t*	p;
deuce's avatar
deuce committed
	jsrefcount	rc;
rswindell's avatar
rswindell committed

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((p=(js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class))==NULL) {
rswindell's avatar
rswindell committed

	str = JS_ValueToString(cx, argv[0]);
deuce's avatar
deuce committed
	JSSTRING_TO_MSTRING(cx, str, cp, &len);
	HANDLE_PENDING(cx, cp);
deuce's avatar
deuce committed
	if(cp==NULL)
		return JS_TRUE;
rswindell's avatar
rswindell committed

	ret = js_socket_sendsocket(p,cp,len,TRUE);
	if(ret >= 0) {
		dbprintf(FALSE, p, "sent %d of %lu bytes",ret,len);
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ret));
	} else {
		p->last_error=ERROR_VALUE;
		dbprintf(TRUE, p, "send of %u bytes failed",len);
	}
deuce's avatar
deuce committed
	free(cp);
rswindell's avatar
rswindell committed
	return(JS_TRUE);
}

deuce's avatar
deuce committed
static JSBool
js_sendline(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
	size_t		len;
	JSString*	str;
	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) {
deuce's avatar
deuce committed
		return(JS_FALSE);
	}

	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);

	str = JS_ValueToString(cx, argv[0]);
	JSSTRING_TO_MSTRING(cx, str, cp, &len);
	HANDLE_PENDING(cx, cp);
deuce's avatar
deuce committed
	if(cp==NULL)
		return JS_TRUE;

	rc=JS_SUSPENDREQUEST(cx);
	if(js_socket_sendsocket(p,cp,len,FALSE)==len && js_socket_sendsocket(p,"\r\n",2,TRUE)==2) {
		dbprintf(FALSE, p, "sent %u bytes",len+2);
		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
	} else {
		p->last_error=ERROR_VALUE;
		dbprintf(TRUE, p, "send of %u bytes failed",len+2);
	}
	free(cp);
	JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
	return(JS_TRUE);
}

js_sendto(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
	size_t		len;
	ushort		port;
	JSString*	data_str;
	JSString*	ip_str;
deuce's avatar
deuce committed
	js_socket_private_t*	p;
deuce's avatar
deuce committed
	jsrefcount	rc;
deuce's avatar
deuce committed
	struct addrinfo hints,*res,*cur;
	int			result;
	char		ip_addr[INET6_ADDRSTRLEN];
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((p=(js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class))==NULL) {
	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);

	/* data */
	data_str = JS_ValueToString(cx, argv[0]);
deuce's avatar
deuce committed
	JSSTRING_TO_MSTRING(cx, data_str, cp, &len);
	HANDLE_PENDING(cx, cp);
deuce's avatar
deuce committed
	if(cp==NULL)
		return JS_TRUE;

	/* address */
	ip_str = JS_ValueToString(cx, argv[1]);
deuce's avatar
deuce committed
	if(p->hostname)
		free(p->hostname);
deuce's avatar
deuce committed
	JSSTRING_TO_MSTRING(cx, ip_str, p->hostname, NULL);
	if(JS_IsExceptionPending(cx)) {
		free(cp);
		return JS_FALSE;
	}
	if(p->hostname==NULL) {
		free(cp);
		return JS_TRUE;
	}
deuce's avatar
deuce committed
	port = js_port(cx,argv[2],p->type);
deuce's avatar
deuce committed
	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed

	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = p->type;
	hints.ai_flags = AI_ADDRCONFIG;
deuce's avatar
deuce committed
	dbprintf(FALSE, p, "resolving hostname: %s", p->hostname);
deuce's avatar
deuce committed

	if((result=getaddrinfo(p->hostname, NULL, &hints, &res) != 0)) {
		p->last_error = ERROR_VALUE;
		dbprintf(TRUE, p, "getaddrinfo(%s) failed with error %d", p->hostname, result);
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
deuce's avatar
deuce committed
		free(cp);
deuce's avatar
deuce committed
	for(cur=res; cur; cur=cur->ai_next) {
		inet_addrtop((void *)cur->ai_addr, ip_addr, sizeof(ip_addr));
		dbprintf(FALSE, p, "sending %d bytes to %s port %u at %s"
			,len, ip_addr, port, p->hostname);
		inet_setaddrport((void *)cur->ai_addr, port);
		if(sendto(p->sock,cp,len,0 /* flags */,cur->ai_addr,cur->ai_addrlen)==len) {
			dbprintf(FALSE, p, "sent %u bytes",len);
			JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
		} else {
			p->last_error=ERROR_VALUE;
			dbprintf(TRUE, p, "send of %u bytes failed to %s",len, ip_addr);
		}
deuce's avatar
deuce committed
	free(cp);
deuce's avatar
deuce committed
	freeaddrinfo(res);
static JSBool
js_sendfile(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
	long		len;
	int			file;
deuce's avatar
deuce committed
	js_socket_private_t*	p;
deuce's avatar
deuce committed
	jsrefcount	rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((p=(js_socket_private_t*)js_GetClassPrivate(cx, obj, &js_socket_class))==NULL) {
		return(JS_FALSE);
	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
deuce's avatar
deuce committed
	JSVALUE_TO_MSTRING(cx, argv[0], fname, NULL);
	HANDLE_PENDING(cx, fname);
deuce's avatar
deuce committed
	if(fname==NULL) {
		JS_ReportError(cx,"Failure reading filename");
		return(JS_FALSE);