Skip to content
Snippets Groups Projects
js_socket.c 84.9 KiB
Newer Older
deuce's avatar
deuce committed
			else {
				if(p->session != -1) {
					cryptDestroySession(p->session);
					p->session=-1;
					ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
deuce's avatar
deuce committed
				}
			}
			JS_RESUMEREQUEST(cx, rc);
			break;
rswindell's avatar
rswindell committed
	return(JS_TRUE);
}

static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
rswindell's avatar
rswindell committed
{
rswindell's avatar
rswindell committed
    jsint       tiny;
deuce's avatar
deuce committed
	js_socket_private_t*	p;
	JSString*	js_str;
deuce's avatar
deuce committed
	union xp_sockaddr	addr;
	socklen_t	len=sizeof(addr);
deuce's avatar
deuce committed
	jsrefcount	rc;
deuce's avatar
deuce committed
	char		str[256];
rswindell's avatar
rswindell committed

deuce's avatar
deuce committed
	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
		// Protoype access
		return(JS_TRUE);
rswindell's avatar
rswindell committed

    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
rswindell's avatar
rswindell committed

#if 0 /* just too much */
rswindell's avatar
rswindell committed
	dbprintf(FALSE, p, "getting property %d",tiny);
rswindell's avatar
rswindell committed
	switch(tiny) {
		case SOCK_PROP_LAST_ERROR:
rswindell's avatar
rswindell committed
			break;
		case SOCK_PROP_ERROR_STR:
			if((js_str=JS_NewStringCopyZ(cx, socket_strerror(p->last_error, str, sizeof(str))))==NULL)
				return JS_FALSE;
			*vp = STRING_TO_JSVAL(js_str);
			break;
rswindell's avatar
rswindell committed
		case SOCK_PROP_IS_CONNECTED:
				*vp = BOOLEAN_TO_JSVAL(socket_check(p->sock,NULL,NULL,0));
rswindell's avatar
rswindell committed
			break;
deuce's avatar
deuce committed
			if(p->sock==INVALID_SOCKET && p->set)
				wr = FALSE;
			else
				socket_check(p->sock,NULL,&wr,0);
		case SOCK_PROP_DATA_WAITING:
deuce's avatar
deuce committed
			if(p->sock==INVALID_SOCKET && p->set)
				rd = FALSE;
			else {
				if (p->peeked)
					rd = TRUE;
				else if (p->session != -1)
					rd = js_socket_peek_byte(p);
				else
					socket_check(p->sock,&rd,NULL,0);
			}
			*vp = BOOLEAN_TO_JSVAL(rd);
			break;
		case SOCK_PROP_NREAD:
deuce's avatar
deuce committed
			if(p->sock==INVALID_SOCKET && p->set) {
				*vp = JSVAL_ZERO;
				break;
			}
			if (p->session != -1) {
				if (js_socket_peek_byte(p))
					*vp=DOUBLE_TO_JSVAL((double)1);
				else
					*vp = JSVAL_ZERO;
			}
			else if(ioctlsocket(p->sock, FIONREAD, &cnt)==0) {
				*vp=DOUBLE_TO_JSVAL((double)cnt);
				*vp = JSVAL_ZERO;
		case SOCK_PROP_DEBUG:
		case SOCK_PROP_DESCRIPTOR:
		case SOCK_PROP_NONBLOCKING:
			*vp = BOOLEAN_TO_JSVAL(p->nonblocking);
			break;
			if(p->sock != INVALID_SOCKET) {
				if(getsockname(p->sock, (struct sockaddr *)&addr,&len)!=0)
					return(JS_FALSE);
deuce's avatar
deuce committed
				inet_addrtop(&addr, str, sizeof(str));
				if((js_str=JS_NewStringCopyZ(cx,str))==NULL)
					return(JS_FALSE);
				*vp = STRING_TO_JSVAL(js_str);
			if(p->local_port != 0) {
				*vp = INT_TO_JSVAL(p->local_port);
			} else if(p->sock != INVALID_SOCKET) {
deuce's avatar
deuce committed
				if(getsockname(p->sock, &addr.addr,&len)!=0)
					return(JS_FALSE);
deuce's avatar
deuce committed
				*vp = INT_TO_JSVAL(inet_addrport(&addr));
			break;
		case SOCK_PROP_REMOTE_IP:
deuce's avatar
deuce committed
				inet_addrtop(&p->remote_addr, str, sizeof(str));
				if((js_str=JS_NewStringCopyZ(cx,str))==NULL)
					return(JS_FALSE);
				*vp = STRING_TO_JSVAL(js_str);
			break;
		case SOCK_PROP_REMOTE_PORT:
deuce's avatar
deuce committed
				*vp = INT_TO_JSVAL(inet_addrport(&p->remote_addr));
		case SOCK_PROP_TYPE:
			*vp = INT_TO_JSVAL(p->type);
			break;
		case SOCK_PROP_FAMILY:
			if(p->sock != INVALID_SOCKET) {
				if(getsockname(p->sock, &addr.addr, &len)!=0)
					return(JS_FALSE);
				JS_RESUMEREQUEST(cx, rc);
				*vp = INT_TO_JSVAL(addr.addr.sa_family);
				rc=JS_SUSPENDREQUEST(cx);
			}
			else
				*vp=JSVAL_VOID;
			break;
		case SOCK_PROP_NETWORK_ORDER:
			*vp = BOOLEAN_TO_JSVAL(p->network_byte_order);
deuce's avatar
deuce committed
		case SOCK_PROP_SSL_SESSION:
			*vp = BOOLEAN_TO_JSVAL(p->session != -1);
			break;
deuce's avatar
deuce committed
		case SOCK_PROP_SSL_SERVER:
			*vp = BOOLEAN_TO_JSVAL(p->session != -1 && p->tls_server);
deuce's avatar
deuce committed
			break;
rswindell's avatar
rswindell committed
	return(TRUE);
}

#define SOCK_PROP_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY
rswindell's avatar
rswindell committed

static jsSyncPropertySpec js_socket_properties[] = {
/*		 name				,tinyid					,flags,				ver	*/

	{	"error"				,SOCK_PROP_LAST_ERROR	,SOCK_PROP_FLAGS,	311 },
	{	"last_error"		,SOCK_PROP_LAST_ERROR	,JSPROP_READONLY,	310 },	/* alias */
	{	"error_str"			,SOCK_PROP_ERROR_STR	,SOCK_PROP_FLAGS,	318 },
	{	"is_connected"		,SOCK_PROP_IS_CONNECTED	,SOCK_PROP_FLAGS,	310 },
	{	"is_writeable"		,SOCK_PROP_IS_WRITEABLE	,JSPROP_ENUMERATE,	311 },
	{	"is_writable"		,SOCK_PROP_IS_WRITEABLE	,JSPROP_ENUMERATE,	312 },	/* alias */
	{	"data_waiting"		,SOCK_PROP_DATA_WAITING	,SOCK_PROP_FLAGS,	310 },
	{	"nread"				,SOCK_PROP_NREAD		,SOCK_PROP_FLAGS,	310 },
	{	"debug"				,SOCK_PROP_DEBUG		,JSPROP_ENUMERATE,	310 },
	{	"descriptor"		,SOCK_PROP_DESCRIPTOR	,JSPROP_ENUMERATE,	310 },
	{	"nonblocking"		,SOCK_PROP_NONBLOCKING	,JSPROP_ENUMERATE,	310 },
	{	"local_ip_address"	,SOCK_PROP_LOCAL_IP		,SOCK_PROP_FLAGS,	310 },
	{	"local_port"		,SOCK_PROP_LOCAL_PORT	,SOCK_PROP_FLAGS,	310 },
	{	"remote_ip_address"	,SOCK_PROP_REMOTE_IP	,SOCK_PROP_FLAGS,	310 },
	{	"remote_port"		,SOCK_PROP_REMOTE_PORT	,SOCK_PROP_FLAGS,	310 },
	{	"type"				,SOCK_PROP_TYPE			,SOCK_PROP_FLAGS,	310 },
	{	"family"			,SOCK_PROP_FAMILY		,SOCK_PROP_FLAGS,	318 },
	{	"network_byte_order",SOCK_PROP_NETWORK_ORDER,JSPROP_ENUMERATE,	311 },
deuce's avatar
deuce committed
	{	"ssl_session"		,SOCK_PROP_SSL_SESSION	,JSPROP_ENUMERATE,	316	},
deuce's avatar
deuce committed
	{	"ssl_server"		,SOCK_PROP_SSL_SERVER	,JSPROP_ENUMERATE,	316	},
static jsSyncMethodSpec js_socket_functions[] = {
	{"close",		js_close,		0,	JSTYPE_VOID,	""
	,JSDOCSTR("close (shutdown) the socket immediately")
	{"bind",		js_bind,		0,	JSTYPE_BOOLEAN,	JSDOCSTR("[port] [,ip_address]")
	,JSDOCSTR("bind socket to a TCP or UDP <i>port</i> (number or service name), "
		"optionally specifying a network interface (via <i>ip_address</i>)")
	,311
	{"connect",     js_connect,     2,	JSTYPE_BOOLEAN,	JSDOCSTR("host, port [,timeout=<tt>10.0</tt>]")
	,JSDOCSTR("connect to a remote port (number or service name) on the specified host (IP address or host name)"
	", default <i>timeout</i> value is <i>10.0</i> (seconds)")
	{"listen",		js_listen,		0,	JSTYPE_BOOLEAN,	JSDOCSTR("")
	,JSDOCSTR("place socket in a state to listen for incoming connections (use before an accept)")
	{"accept",		js_accept,		0,	JSTYPE_OBJECT,	JSDOCSTR("")
	,JSDOCSTR("accept an incoming connection, returns a new <i>Socket</i> object representing the new connection")
	{"write",		js_send,		1,	JSTYPE_ALIAS },
	{"send",		js_send,		1,	JSTYPE_NUMBER,	JSDOCSTR("data")
	,JSDOCSTR("send a string (AKA write).  Returns the number of bytes sent or undefined if an error occured.  "
	"Versions before 3.17 returned a bool true if all bytes were sent and false otherwise.")
deuce's avatar
deuce committed
	{"writeln",		js_sendline,		1,	JSTYPE_ALIAS },
	{"sendline",	js_sendline,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("data")
	,JSDOCSTR("send a string (AKA write) with a carriage return line feed appended")
	,317
	},
	{"sendto",		js_sendto,		3,	JSTYPE_BOOLEAN,	JSDOCSTR("data, address, port")
	,JSDOCSTR("send data to a specific host (IP address or host name) and port (number or service name), for UDP sockets")
	{"sendfile",	js_sendfile,	1,	JSTYPE_BOOLEAN,	JSDOCSTR("path/filename")
	,JSDOCSTR("send an entire file over the socket")
	{"writeBin",	js_sendbin,		1,	JSTYPE_ALIAS },
	{"sendBin",		js_sendbin,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("value [,bytes=<tt>4</tt>]")
	,JSDOCSTR("send a binary integer over the socket, default number of bytes is 4 (32-bits)")
	{"read",		js_recv,		1,	JSTYPE_ALIAS },
	{"recv",		js_recv,		1,	JSTYPE_STRING,	JSDOCSTR("[maxlen=<tt>512</tt>, [timeout_sec=<tt>120</tt>]]")
	,JSDOCSTR("receive a string, default maxlen is 512 characters (AKA read)")
	{"peek",		js_peek,		0,	JSTYPE_STRING,	JSDOCSTR("[maxlen=<tt>512</tt>]")
	,JSDOCSTR("receive a string, default maxlen is 512 characters, leaves string in receive buffer (TLS sockets will never return more than one byte)")
	{"readline",	js_recvline,	0,	JSTYPE_ALIAS },
	{"readln",		js_recvline,	0,	JSTYPE_ALIAS },
	{"recvline",	js_recvline,	0,	JSTYPE_STRING,	JSDOCSTR("[maxlen=<tt>512</tt>] [,timeout=<tt>30.0</tt>]")
	,JSDOCSTR("receive a line-feed terminated string, default maxlen is 512 characters, default timeout is 30 seconds (AKA readline and readln)")
	{"recvfrom",	js_recvfrom,	0,	JSTYPE_OBJECT,	JSDOCSTR("[binary=<tt>false</tt>] [,maxlen=<tt>512</tt> or int_size=<tt>4</tt>]")
	,JSDOCSTR("receive data (string or integer) from a socket (typically UDP)"
rswindell's avatar
rswindell committed
	"<p>returns object with <i>ip_address</i> and <i>port</i> of sender along with <i>data</i> properties"
	"<p><i>binary</i> defaults to <i>false</i>, <i>maxlen</i> defaults to 512 chars, <i>int_size</i> defaults to 4 bytes (32-bits)")
	},
	{"readBin",		js_recvbin,		0,	JSTYPE_ALIAS },
	{"recvBin",		js_recvbin,		0,	JSTYPE_NUMBER,	JSDOCSTR("[bytes=<tt>4</tt>]")
	,JSDOCSTR("receive a binary integer from the socket, default number of bytes is 4 (32-bits)")
	{"getoption",	js_getsockopt,	1,	JSTYPE_NUMBER,	JSDOCSTR("option")
	,JSDOCSTR("get socket option value, option may be socket option name "
	"(see <tt>sockopts</tt> in <tt>sockdefs.js</tt>) or number")
	{"setoption",	js_setsockopt,	2,	JSTYPE_BOOLEAN,	JSDOCSTR("option, value")
	,JSDOCSTR("set socket option value, option may be socket option name "
	"(see <tt>sockopts</tt> in <tt>sockdefs.js</tt>) or number")
	{"ioctl",		js_ioctlsocket,	1,	JSTYPE_NUMBER,	JSDOCSTR("command [,argument=<tt>0</tt>]")
	,JSDOCSTR("send socket IOCTL (advanced)")
	{"poll",		js_poll,		1,	JSTYPE_NUMBER,	JSDOCSTR("[timeout=<tt>0</tt>] [,write=<tt>false</tt>]")
	,JSDOCSTR("poll socket for read or write ability (default is <i>read</i>), "
	"default timeout value is 0.0 seconds (immediate timeout)")
static JSBool js_socket_resolve(JSContext *cx, JSObject *obj, jsid id)
{
	char*			name=NULL;
deuce's avatar
deuce committed
	JSBool			ret;
deuce's avatar
deuce committed
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
deuce's avatar
deuce committed
		JS_IdToValue(cx, id, &idval);
deuce's avatar
deuce committed
		if(JSVAL_IS_STRING(idval)) {
			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
			HANDLE_PENDING(cx, name);
deuce's avatar
deuce committed
		}
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	ret=js_SyncResolve(cx, obj, name, js_socket_properties, js_socket_functions, NULL, 0);
	if(name)
		free(name);
	return ret;
}

static JSBool js_socket_enumerate(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed
	return(js_socket_resolve(cx, obj, JSID_VOID));
     "Socket"				/* name			*/
    ,JSCLASS_HAS_PRIVATE	/* flags		*/
	,JS_PropertyStub		/* addProperty	*/
	,JS_PropertyStub		/* delProperty	*/
	,js_socket_get			/* getProperty	*/
	,js_socket_set			/* setProperty	*/
	,js_socket_enumerate	/* enumerate	*/
	,js_socket_resolve		/* resolve		*/
	,JS_ConvertStub			/* convert		*/
	,js_finalize_socket		/* finalize		*/
};

JSClass js_connected_socket_class = {
     "ConnectedSocket"				/* name			*/
    ,JSCLASS_HAS_PRIVATE	/* flags		*/
	,JS_PropertyStub		/* addProperty	*/
	,JS_PropertyStub		/* delProperty	*/
	,js_socket_get			/* getProperty	*/
	,js_socket_set			/* setProperty	*/
	,js_socket_enumerate	/* enumerate	*/
	,js_socket_resolve		/* resolve		*/
	,JS_ConvertStub			/* convert		*/
	,js_finalize_socket		/* finalize		*/
};

JSClass js_listening_socket_class = {
     "ListeningSocket"				/* name			*/
    ,JSCLASS_HAS_PRIVATE	/* flags		*/
	,JS_PropertyStub		/* addProperty	*/
	,JS_PropertyStub		/* delProperty	*/
	,js_socket_get			/* getProperty	*/
	,js_socket_set			/* setProperty	*/
	,js_socket_enumerate	/* enumerate	*/
	,js_socket_resolve		/* resolve		*/
	,JS_ConvertStub			/* convert		*/
	,js_finalize_socket		/* finalize		*/
};

static BOOL js_DefineSocketOptionsArray(JSContext *cx, JSObject *obj, int type)
{
	size_t		i;
	size_t		count=0;
	jsval		val;
	JSObject*	array;
	socket_option_t* options;

	if((options=getSocketOptionList())==NULL)
		return(FALSE);

	if((array=JS_NewArrayObject(cx, 0, NULL))==NULL)
		return(FALSE);

	if(!JS_DefineProperty(cx, obj, "option_list", OBJECT_TO_JSVAL(array)
		, NULL, NULL, JSPROP_ENUMERATE))
		return(FALSE);

	for(i=0; options[i].name!=NULL; i++) {
		if(options[i].type && options[i].type!=type)
			continue;
		val=STRING_TO_JSVAL(JS_NewStringCopyZ(cx,options[i].name));
		JS_SetElement(cx, array, count++, &val);
	}
	return(TRUE);
}

/* Socket Constructor (creates socket descriptor) */

JSObject* DLLCALL js_CreateSocketObjectWithoutParent(JSContext* cx, SOCKET sock, CRYPT_CONTEXT session)
{
	JSObject*	obj;
	js_socket_private_t*	p;
	int			type=SOCK_STREAM;
	socklen_t	len;

	obj=JS_NewObject(cx, &js_socket_class, NULL, NULL);

	if(obj==NULL)
		return(NULL);

	len = sizeof(type);
	getsockopt(sock,SOL_SOCKET,SO_TYPE,(void*)&type,&len);

	if(!js_DefineSocketOptionsArray(cx, obj, type))
		return(NULL);

	if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL)
		return(NULL);
	memset(p,0,sizeof(js_socket_private_t));

	p->sock = sock;
	p->external = TRUE;
	p->network_byte_order = TRUE;
deuce's avatar
deuce committed
	p->unflushed = 0;

	if (p->sock != INVALID_SOCKET) {
		len=sizeof(p->remote_addr);
		if(getpeername(p->sock, &p->remote_addr.addr,&len)==0)
			p->is_connected=TRUE;
	}

	if(!JS_SetPrivate(cx, obj, p)) {
		dbprintf(TRUE, p, "JS_SetPrivate failed");
		return(NULL);
	}

	dbprintf(FALSE, p, "object created");

	return(obj);
}

handle_addrs(char *host, struct sockaddr_in *addr4, socklen_t *addr4len, struct sockaddr_in6 *addr6, socklen_t *addr6len)
{
	in_addr_t ia;
	union xp_sockaddr ia6;
	char *p, *p2;
	struct addrinfo	hints;
	struct addrinfo	*res=NULL;
	struct addrinfo	*cur;
	int i;

	// First, clean up for host:port style...
	p = strrchr(host, ':');
	/*
	 * If there isn't a [, and the first and last colons aren't the same
	 * it's assumed to be an IPv6 address
	 */
	if(host[0] != '[' && p != NULL && strchr(host, ':') != p)
		p=NULL;
	if(host[0]=='[') {
		host++;
		p2=strrchr(host,']');
		if(p2)
			*p2=0;
		if(p2 > p)
			p=NULL;
	}
	if(p!=NULL)
		*p=0;

	ia = inet_addr(host);
	if (ia != INADDR_NONE) {
		if (*addr4len == 0) {
			addr4->sin_addr.s_addr = ia;
			*addr4len = sizeof(struct sockaddr_in);
		}
		return TRUE;
	}

	if (inet_ptoaddr(host, &ia6, sizeof(ia6)) != NULL) {
deuce's avatar
deuce committed
		if (*addr6len == 0) {
			addr6->sin6_addr = ia6.in6.sin6_addr;
			*addr6len = sizeof(struct sockaddr_in6);
		}
		return TRUE;
	}

	// So it's a hostname... resolve it into addr4 and addr6 if possible.
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
	if((i = getaddrinfo(host, NULL, &hints, &res)) != 0)
		return FALSE;
	for(cur=res; cur && (*addr4len == 0 || *addr6len == 0); cur=cur->ai_next) {
		switch (cur->ai_family) {
			case AF_INET:
				if (*addr4len == 0) {
					addr4->sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
					*addr4len = sizeof(struct sockaddr_in);
				if (*addr6len == 0) {
					addr6->sin6_addr = ((struct sockaddr_in6 *)cur->ai_addr)->sin6_addr;
					*addr6len = sizeof(struct sockaddr_in6);
static JSBool
js_connected_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *obj;
	jsval *argv=JS_ARGV(cx, arglist);
	int32	type=SOCK_STREAM;	/* default = TCP */
	int32		domain = PF_UNSPEC; /* default = IPvAny */
	int32		proto = IPPROTO_IP;
	char*	protocol=NULL;
	uintN	i;
	js_socket_private_t* p = NULL;
	jsval v;
	struct addrinfo	hints;
	struct addrinfo	*res=NULL;
	struct addrinfo	*cur;
	char *host = NULL;
	uint16_t port;
	char	pstr[6];
	jsrefcount	rc;
	int nonblock;
	scfg_t *scfg;
	char error[256];
	struct sockaddr_in addr4;
	struct sockaddr_in6 addr6;
	socklen_t addr4len;
	socklen_t addr6len;
	struct sockaddr *addr;
	socklen_t *addrlen;
	BOOL sockbind = FALSE;
	jsuint count;

	scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
	if (scfg == NULL) {
		JS_ReportError(cx, "Unable to get private runtime");
		return JS_FALSE;
	}

	if (argc < 2) {
			JS_ReportError(cx, "At least two arguments required (hostname and port)");
			return JS_FALSE;
	}
	// Optional arguments in an object...
	if (argc > 2) {
		if (!JS_ValueToObject(cx, argv[2], &obj)) {
			JS_ReportError(cx, "Invalid third argument");
			return JS_FALSE;
		}
		if (JS_GetProperty(cx, obj, "domain", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &domain)) {
				JS_ReportError(cx, "Invalid domain property");
				return JS_FALSE;
			}
		}
		if (JS_GetProperty(cx, obj, "type", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &type)) {
				JS_ReportError(cx, "Invalid type property");
				return JS_FALSE;
			}
		}
		if (JS_GetProperty(cx, obj, "proto", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &proto)) {
				JS_ReportError(cx, "Invalid proto property");
				return JS_FALSE;
			}
		}
		if (JS_GetProperty(cx, obj, "timeout", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &timeout)) {
				JS_ReportError(cx, "Invalid timeout property");
				return JS_FALSE;
			}
		}
		if (JS_GetProperty(cx, obj, "protocol", &v) && !JSVAL_IS_VOID(v)) {
			JSVALUE_TO_MSTRING(cx, v, protocol, NULL);
			HANDLE_PENDING(cx, protocol);
		}
		if (JS_GetProperty(cx, obj, "bindport", &v) && !JSVAL_IS_VOID(v)) {
			bindport = js_port(cx, v, type);
			memset(&addr4, 0, sizeof(addr4));
			addr4.sin_family = AF_INET;
			addr4.sin_addr.s_addr = INADDR_ANY;
			addr4len = sizeof(addr4);
			addr4.sin_port = htons(bindport);
			memset(&addr6, 0, sizeof(addr6));
			addr6.sin6_family = AF_INET6;
			addr6.sin6_addr = in6addr_any;
			addr6len = sizeof(addr6);
			addr6.sin6_port = htons(bindport);
			sockbind = TRUE;
		}
		if (JS_GetProperty(cx, obj, "bindaddrs", &v) && !JSVAL_IS_VOID(v)) {
			if (!sockbind) {
				memset(&addr4, 0, sizeof(addr4));
				addr4.sin_family = AF_INET;
				addr4.sin_addr.s_addr = INADDR_ANY;
				addr4.sin_port = htons(bindport);
				memset(&addr6, 0, sizeof(addr6));
				addr6.sin6_family = AF_INET6;
				addr6.sin6_addr = in6addr_any;
				addr6.sin6_port = htons(bindport);
				sockbind = TRUE;
			}
			addr4len = 0;
			addr6len = 0;
			if (JSVAL_IS_OBJECT(v)) {
				ao = JSVAL_TO_OBJECT(v);
				if (ao == NULL || !JS_IsArrayObject(cx, ao)) {
					JS_ReportError(cx, "Invalid bindaddrs list");
					goto fail;
				}
				if (!JS_GetArrayLength(cx, ao, &count)) {
					JS_ReportError(cx, "Unable to get bindaddrs length");
					goto fail;
				}
				for (i = 0; i < count; i++) {
					if (!JS_GetElement(cx, ao, i, &v)) {
						JS_ReportError(cx, "Invalid bindaddrs entry");
						goto fail;
					}
					JSVALUE_TO_MSTRING(cx, v, host, NULL);
					HANDLE_PENDING(cx, host);
					rc = JS_SUSPENDREQUEST(cx);
					if (!handle_addrs(host, &addr4, &addr4len, &addr6, &addr6len)) {
						JS_RESUMEREQUEST(cx, rc);
						JS_ReportError(cx, "Unparsable bindaddrs entry");
						goto fail;
					}
					FREE_AND_NULL(host);
					JS_RESUMEREQUEST(cx, rc);
				}
			}
			else {
				JSVALUE_TO_MSTRING(cx, v, host, NULL);
				HANDLE_PENDING(cx, host);
				rc = JS_SUSPENDREQUEST(cx);
				if (!handle_addrs(host, &addr4, &addr4len, &addr6, &addr6len)) {
					JS_RESUMEREQUEST(cx, rc);
					JS_ReportError(cx, "Unparsable bindaddrs entry");
					goto fail;
				}
				FREE_AND_NULL(host);
				JS_RESUMEREQUEST(cx, rc);
			}
	}
	JSVALUE_TO_MSTRING(cx, argv[0], host, NULL);
	HANDLE_PENDING(cx, host);
	port = js_port(cx, argv[1], type);
	if (port == 0) {
			JS_ReportError(cx, "Invalid port");
			goto fail;
	}

	obj=JS_NewObject(cx, &js_socket_class, NULL, NULL);
	JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));

	if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL) {
		JS_ReportError(cx,"malloc failed");
		goto fail;
	}
	memset(p,0,sizeof(js_socket_private_t));

	rc = JS_SUSPENDREQUEST(cx);
	sprintf(pstr, "%hu", port);
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = domain;
	hints.ai_socktype = type;
	hints.ai_protocol = proto;
	hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
	if((i = getaddrinfo(host, pstr, &hints, &res)) != 0) {
		JS_RESUMEREQUEST(cx, rc);
		JS_ReportError(cx, gai_strerror(i));
		goto fail;
	}
	p->sock = INVALID_SOCKET;
	for(cur=res; cur && p->sock == INVALID_SOCKET; cur=cur->ai_next) {
		if(p->sock==INVALID_SOCKET) {
			p->sock=socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
			if(p->sock==INVALID_SOCKET)
				continue;
			if (set_socket_options(scfg, p->sock, protocol, error, sizeof(error))) {
				JS_RESUMEREQUEST(cx, rc);
				JS_ReportError(cx, error);
				goto fail;
			}
			if (sockbind) {
				addr = NULL;
				switch(cur->ai_family) {
					case PF_INET:
						addr = (struct sockaddr *)&addr4;
						addrlen = &addr4len;
						addr = (struct sockaddr *)&addr6;
						addrlen = &addr6len;
				if (addr == NULL)
					continue;
				if (*addrlen == 0)
				if (bind(p->sock, addr, *addrlen) != 0) {
					lprintf(LOG_WARNING, "Unable to bind to local address");
					closesocket(p->sock);
					p->sock = INVALID_SOCKET;
					continue;
				}
			}
			/* Set to non-blocking for the connect */
			nonblock=-1;
			ioctlsocket(p->sock, FIONBIO, &nonblock);
		}

		if(connect(p->sock, cur->ai_addr, cur->ai_addrlen)) {
			switch(ERROR_VALUE) {
				case EINPROGRESS:
				case EINTR:
				case EAGAIN:
#if (EAGAIN!=EWOULDBLOCK)
				case EWOULDBLOCK:
#endif
					for(;p->sock!=INVALID_SOCKET;) {
						// TODO: Do clever timeout stuff here.
Deucе's avatar
Deucе committed
						if (socket_writable(p->sock, timeout * 1000)) {
							if(!socket_recvdone(p->sock, 0))
								goto connected;
							closesocket(p->sock);
							p->sock=INVALID_SOCKET;
							continue;
						}
						else {
							closesocket(p->sock);
							p->sock=INVALID_SOCKET;
							continue;
						}
					}

connected:
					memcpy(&p->remote_addr, cur->ai_addr, cur->ai_addrlen);
					break;
				default:
					closesocket(p->sock);
					p->sock=INVALID_SOCKET;
					continue;
			}
		}
	}
	freeaddrinfo(res);
	JS_RESUMEREQUEST(cx, rc);

	if (p->sock == INVALID_SOCKET) {
		JS_ReportError(cx, "Unable to connect");
		goto fail;
	}
	ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
	call_socket_open_callback(TRUE);
	if(protocol)
		free(protocol);
	p->hostname = host;
	p->type = type;
	p->network_byte_order = TRUE;
	p->session=-1;
	p->unflushed = 0;
	p->is_connected = TRUE;

	if(!JS_SetPrivate(cx, obj, p)) {
		JS_ReportError(cx,"JS_SetPrivate failed");
		free(p);
		return(JS_FALSE);
	}

#ifdef BUILD_JSDOCS
	js_DescribeSyncObject(cx,obj,"Class used for outgoing TCP/IP socket communications",317);
	js_DescribeSyncConstructor(cx,obj,"To create a new ConnectedSocket object: "
		"<tt>load('sockdefs.js'); var s = new ConnectedSocket(<i>hostname</i>, <i>port</i>, {domain:<i>domain</i>, proto:<i>proto</i> ,type:<i>type</i>, protocol:<i>protocol</i>, timeout:<i>timeout</i>, bindport:<i>port</i>, bindaddrs:<i>bindaddrs</i>})</tt><br>"
		"where <i>domain</i> (optional) = <tt>PF_UNSPEC</tt> (default) for IPv4 or IPv6, <tt>PF_INET</tt> for IPv4, or <tt>PF_INET6</tt> for IPv6<br>"
		"<i>proto</i> (optional) = <tt>IPPROTO_IP</tt> (default)<br>"
		"<i>type</i> (optional) = <tt>SOCK_STREAM</tt> for TCP (default) or <tt>SOCK_DGRAM</tt> for UDP<br>"
		"<i>protocol</i> (optional) = the name of the protocol or service the socket is to be used for<br>"
		"<i>timeout</i> (optional) = 10 (default) the number of seconds to wait for each connect() call to complete.<br>"
		"<i>bindport</i> (optional) = the name or number of the source port to bind to<br>"
		"<i>bindaddrs</i> (optional) = the name or number of the source addresses to bind to.  The first of each IPv4 or IPv6 type is used for that family.<br>"
		);
	JS_DefineProperty(cx,obj,"_dont_document",JSVAL_TRUE,NULL,NULL,JSPROP_READONLY);
#endif

	if(!js_DefineSocketOptionsArray(cx, obj, type))
		return(JS_FALSE);

	dbprintf(FALSE, p, "object constructed");
	return(JS_TRUE);

fail:
	if (p)
		free(p);
	if (protocol)
		free(protocol);
	if (host)
		free(host);
	return JS_FALSE;
}

struct ls_cb_data {
	char *protocol;
	scfg_t *scfg;
};

static void
ls_cb(SOCKET sock, void *cbptr)
{
	struct ls_cb_data *cb = cbptr;
	char error[256];

	call_socket_open_callback(TRUE);
	if(set_socket_options(cb->scfg, sock, cb->protocol, error, sizeof(error)))
		lprintf(LOG_ERR,"%04d !ERROR %s", sock, error);
}

static JSBool
js_listening_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject *obj;
	jsval *argv=JS_ARGV(cx, arglist);
	int32 type=SOCK_STREAM;	/* default = TCP */
	int32 domain = PF_UNSPEC; /* default = IPvAny */
	int32 proto = IPPROTO_IP;
	int retry_count = 0;
	int retry_delay = 15;
	char* protocol=NULL;
	char *interface=NULL;
	js_socket_private_t* p = NULL;
	jsval v;
	uint16_t port;
	jsrefcount rc;
	scfg_t *scfg;
	struct xpms_set *set = NULL;
	struct ls_cb_data cb;
	jsuint count;
	int i;

	scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
	if (scfg == NULL) {
		JS_ReportError(cx, "Unable to get private runtime");
		goto fail;
	}
	cb.scfg = scfg;
	if (argc < 3) {
		JS_ReportError(cx, "At least three arguments required (interfaces, port, and protocol)");
		goto fail;
	}
	if (argc > 3) {
		if (!JS_ValueToObject(cx, argv[3], &obj)) {
			JS_ReportError(cx, "Invalid fourth argument");
			goto fail;
		}
		if (JS_GetProperty(cx, obj, "domain", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &domain)) {
				JS_ReportError(cx, "Invalid domain property");
				goto fail;
			}
		}
		if (JS_GetProperty(cx, obj, "type", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &type)) {
				JS_ReportError(cx, "Invalid type property");
				goto fail;
			}
		}
		if (JS_GetProperty(cx, obj, "proto", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &proto)) {
				JS_ReportError(cx, "Invalid proto property");
				goto fail;
			}
		}
		if (JS_GetProperty(cx, obj, "retry_count", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &retry_count)) {
				JS_ReportError(cx, "Invalid retry_count property");
				goto fail;
			}
		}
		if (JS_GetProperty(cx, obj, "retry_delay", &v) && !JSVAL_IS_VOID(v)) {
			if (!JS_ValueToInt32(cx, v, &retry_delay)) {
				JS_ReportError(cx, "Invalid retry_delay property");
				goto fail;
			}
		}
	}
	obj = NULL;
	port = js_port(cx, argv[1], type);
	JSVALUE_TO_MSTRING(cx, argv[2], protocol, NULL);
	HANDLE_PENDING(cx, protocol);
	cb.protocol = protocol;
	if (JSVAL_IS_OBJECT(argv[0])) {
		obj = JSVAL_TO_OBJECT(argv[0]);
		if (obj == NULL || !JS_IsArrayObject(cx, obj)) {
			JS_ReportError(cx, "Invalid interface list");
			goto fail;
		}
	}
	set = xpms_create(retry_count, retry_delay, lprintf);
	if (set == NULL) {
		JS_ReportError(cx, "Unable to create socket set");
		goto fail;
	}
	if (obj == NULL) {
		JSVALUE_TO_MSTRING(cx, argv[0], interface, NULL);
		HANDLE_PENDING(cx, interface);
		rc = JS_SUSPENDREQUEST(cx);
		if (!xpms_add(set, domain, type, proto, interface, port, protocol, ls_cb, NULL, &cb)) {
			JS_RESUMEREQUEST(cx, rc);
			JS_ReportError(cx, "Unable to add host to socket set");
			goto fail;
		}
		JS_RESUMEREQUEST(cx, rc);
	}
	else {
		if (!JS_GetArrayLength(cx, obj, &count)) {
			free(protocol);
			return JS_FALSE;
		}
		for (i = 0; (jsuint)i < count; i++) {
			if (!JS_GetElement(cx, obj, i, &v)) {
				lprintf(LOG_WARNING, "Unable to get element %d from interface array", i);
				continue;
			}
			JSVALUE_TO_MSTRING(cx, v, interface, NULL);
			HANDLE_PENDING(cx, interface);
			rc = JS_SUSPENDREQUEST(cx);
			if (!xpms_add(set, domain, type, proto, interface, port, protocol, ls_cb, NULL, &cb)) {
				free(interface);
				JS_RESUMEREQUEST(cx, rc);
				JS_ReportError(cx, "Unable to add host to socket set");
				goto fail;
			}
			free(interface);
			JS_RESUMEREQUEST(cx, rc);
		}
	}

	obj=JS_NewObject(cx, &js_socket_class, NULL, NULL);
	JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));

	if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL) {
		JS_ReportError(cx,"malloc failed");
		free(protocol);
		free(set);
		return(JS_FALSE);
	}
	memset(p,0,sizeof(js_socket_private_t));
	p->type = type;
	p->set = set;
	p->sock = INVALID_SOCKET;
	p->external = TRUE;
	p->network_byte_order = TRUE;
	p->session=-1;
	p->unflushed = 0;

	if(!JS_SetPrivate(cx, obj, p)) {
		JS_ReportError(cx,"JS_SetPrivate failed");
		free(p);
		free(set);
	if(!js_DefineSocketOptionsArray(cx, obj, type)) {
		free(p);
		free(set);

#ifdef BUILD_JSDOCS
	js_DescribeSyncObject(cx,obj,"Class used for incoming TCP/IP socket communications",317);
	js_DescribeSyncConstructor(cx,obj,"To create a new ListeningSocket object: "
rswindell's avatar
rswindell committed
		"<tt>load('sockdefs.js'); var s = new ListeningSocket(<i>interface</i>, <i>port</i> ,<i>protocol</i>, {domain:<i>domain</i>, type:<i>type</i>, proto:<i>proto</i>, retry_count:<i>retry_count</i>, retry_delay:<i>retry_delay</i>})</tt><br>"
		"where <i>interface</i> = A array or strings or a single string of hostnames or address optionally including a :port suffix<br>"
		"<i>port</i> = a port to use when the interface doesn't specify one<br>"
		"<i>protocol</i> = protocol name, used for socket options and logging.<br>"
		"<i>domain</i> (optional) = <tt>PF_UNSPEC</tt> (default) for IPv4 or IPv6, <tt>AF_INET</tt> for IPv4, or <tt>AF_INET6</tt> for IPv6<br>"
		"<i>proto</i> (optional) = <tt>IPPROTO_IP</tt> (default)<br>"
		"<i>type</i> (optional) = <tt>SOCK_STREAM</tt> for TCP (default) or <tt>SOCK_DGRAM</tt> for UDP<br>"
		"<i>retry_count</i> (optional) = <tt>0</tt> (default) number of times to retry binding<br>"
		"and <i>retry_delay</i> (optional) = <tt>15</tt> (default) seconds to wait before a retry<br>"
		);
	JS_DefineProperty(cx,obj,"_dont_document",JSVAL_TRUE,NULL,NULL,JSPROP_READONLY);