From dc688b8b025861c82a860f9e0dd83adedcde06c8 Mon Sep 17 00:00:00 2001
From: deuce <>
Date: Mon, 5 Aug 2019 23:32:40 +0000
Subject: [PATCH] Add bindaddrs support to ConnectedSocket constructor.

---
 src/sbbs3/js_socket.c | 168 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 156 insertions(+), 12 deletions(-)

diff --git a/src/sbbs3/js_socket.c b/src/sbbs3/js_socket.c
index e42a6d1568..0eb410a87a 100644
--- a/src/sbbs3/js_socket.c
+++ b/src/sbbs3/js_socket.c
@@ -2294,10 +2294,85 @@ JSObject* DLLCALL js_CreateSocketObjectWithoutParent(JSContext* cx, SOCKET sock,
 	return(obj);
 }
 
+static BOOL
+handle_addrs(char *host, struct sockaddr_in *addr4, struct sockaddr_in6 *addr6)
+{
+	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 (addr4->sin_len == 0) {
+			addr4->sin_addr.s_addr = ia;
+			addr4->sin_len = sizeof(struct sockaddr_in);
+		}
+		return TRUE;
+	}
+
+	if (inet_ptoaddr(host, &ia6, sizeof(ia6)) != NULL) {
+		if (addr6->sin6_len == 0) {
+			addr6->sin6_addr = ia6.in6.sin6_addr;
+			addr6->sin6_len = 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 && (addr4->sin_len == 0 || addr6->sin6_len == 0); cur=cur->ai_next) {
+		switch (cur->ai_family) {
+			case AF_INET:
+				if (addr4->sin_len == 0) {
+					addr4->sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
+					addr4->sin_len = sizeof(struct sockaddr_in);
+				}
+				break;
+			case AF_INET6:
+				if (addr6->sin6_len == 0) {
+					addr6->sin6_addr = ((struct sockaddr_in6 *)cur->ai_addr)->sin6_addr;
+					addr6->sin6_len = sizeof(struct sockaddr_in6);
+				}
+				break;
+		}
+	}
+	freeaddrinfo(res);
+
+	return TRUE;
+}
+
 static JSBool
 js_connected_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
 {
 	JSObject *obj;
+	JSObject *ao;
 	jsval *argv=JS_ARGV(cx, arglist);
 	int32	type=SOCK_STREAM;	/* default = TCP */
 	int32		domain = PF_UNSPEC; /* default = IPvAny */
@@ -2320,7 +2395,11 @@ js_connected_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
 	scfg_t *scfg;
 	char error[256];
 	uint16_t bindport = 0;
-	union xp_sockaddr addr;
+	struct sockaddr_in addr4;
+	struct sockaddr_in6 addr6;
+	struct sockaddr *addr;
+	BOOL sockbind = FALSE;
+	jsuint count;
 
 	scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));
 	if (scfg == NULL) {
@@ -2362,6 +2441,71 @@ js_connected_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
 		}
 		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;
+			addr4.sin_len = sizeof(addr4);
+			addr4.sin_port = htons(bindport);
+			memset(&addr6, 0, sizeof(addr6));
+			addr6.sin6_family = AF_INET6;
+			addr6.sin6_addr = in6addr_any;
+			addr6.sin6_len = 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;
+			}
+			addr4.sin_len = 0;
+			addr6.sin6_len = 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, &addr6)) {
+						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, &addr6)) {
+					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);
@@ -2405,22 +2549,21 @@ js_connected_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
 				JS_ReportError(cx, error);
 				goto fail;
 			}
-			if (bindport) {
-				memset(&addr, 0, sizeof(addr));
-				addr.addr.sa_family = cur->ai_family;
+			if (sockbind) {
+				addr = NULL;
 				switch(cur->ai_family) {
 					case PF_INET:
-						addr.in.sin_len = sizeof(addr.in);
-						addr.in.sin_addr.s_addr = INADDR_ANY;
-						addr.in.sin_port = htons(bindport);
+						addr = (struct sockaddr *)&addr4;
 						break;
 					case PF_INET6:
-						addr.in6.sin6_len = sizeof(addr.in6);
-						addr.in6.sin6_addr = in6addr_any;
-						addr.in6.sin6_port = htons(bindport);
+						addr = (struct sockaddr *)&addr6;
 						break;
 				}
-				if (bind(p->sock, (struct sockaddr *)&addr, addr.addr.sa_len) != 0) {
+				if (addr == NULL)
+					continue;
+				if (addr->sa_len == 0)
+					continue;
+				if (bind(p->sock, addr, addr->sa_len) != 0) {
 					lprintf(LOG_WARNING, "Unable to bind to local address");
 					closesocket(p->sock);
 					p->sock = INVALID_SOCKET;
@@ -2515,12 +2658,13 @@ connected:
 #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>, bindport:<i>port</i>})</tt><br>"
+		"<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>, 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>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
-- 
GitLab