changeset 1464:ad637c9e0f6f

Server chosen tcpfwd ports (#43) Server chosen tcpfwd ports
author houseofkodai <karthik@houseofkodai.in>
date Thu, 25 Jan 2018 19:51:41 +0530
parents a3479d0d7e79
children f7a53832501d 7279a633cc50
files netio.c netio.h svr-tcpfwd.c
diffstat 3 files changed, 84 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/netio.c	Thu Jan 25 21:58:00 2018 +0800
+++ b/netio.c	Thu Jan 25 19:51:41 2018 +0530
@@ -348,6 +348,37 @@
 
 }
 
+/* from openssh/canohost.c avoid premature-optimization */
+int get_sock_port(int sock) {
+	struct sockaddr_storage from;
+	socklen_t fromlen;
+	char strport[NI_MAXSERV];
+	int r;
+
+	/* Get IP address of client. */
+	fromlen = sizeof(from);
+	memset(&from, 0, sizeof(from));
+	if (getsockname(sock, (struct sockaddr *)&from, &fromlen) < 0) {
+		TRACE(("getsockname failed: %d", errno))
+		return 0;
+	}
+
+	/* Work around Linux IPv6 weirdness */
+	if (from.ss_family == AF_INET6)
+		fromlen = sizeof(struct sockaddr_in6);
+
+	/* Non-inet sockets don't have a port number. */
+	if (from.ss_family != AF_INET && from.ss_family != AF_INET6)
+		return 0;
+
+	/* Return port number. */
+	if ((r = getnameinfo((struct sockaddr *)&from, fromlen, NULL, 0,
+	    strport, sizeof(strport), NI_NUMERICSERV)) != 0) {
+		TRACE(("netio.c/get_sock_port/getnameinfo NI_NUMERICSERV failed: %d", r))
+	}
+	return atoi(strport);
+}
+
 /* Listen on address:port. 
  * Special cases are address of "" listening on everything,
  * and address of NULL listening on localhost only.
@@ -400,11 +431,29 @@
 		return -1;
 	}
 
+	/*
+	 * when listening on server-assigned-port 0
+	 * the assigned ports may differ for address families (v4/v6)
+	 * causing problems for tcpip-forward
+	 * caller can do a get_socket_address to discover assigned-port
+	 * hence, use same port for all address families
+	 */
+	u_int16_t *allocated_lport_p = 0;
+	int allocated_lport = 0;
 
 	nsock = 0;
 	for (res = res0; res != NULL && nsock < sockcount;
 			res = res->ai_next) {
 
+		if (allocated_lport > 0) {
+			if (AF_INET == res->ai_family) {
+				allocated_lport_p = &((struct sockaddr_in *)res->ai_addr)->sin_port;
+			} else if (AF_INET6 == res->ai_family) {
+				allocated_lport_p = &((struct sockaddr_in6 *)res->ai_addr)->sin6_port;
+			}
+			*allocated_lport_p = htons(allocated_lport);
+		}
+
 		/* Get a socket */
 		socks[nsock] = socket(res->ai_family, res->ai_socktype,
 				res->ai_protocol);
@@ -451,6 +500,10 @@
 			continue;
 		}
 
+		if (0 == allocated_lport) {
+			allocated_lport = get_sock_port(sock);
+		}
+
 		*maxfd = MAX(*maxfd, sock);
 
 		nsock++;
--- a/netio.h	Thu Jan 25 21:58:00 2018 +0800
+++ b/netio.h	Thu Jan 25 19:51:41 2018 +0530
@@ -14,6 +14,7 @@
 void set_sock_nodelay(int sock);
 void set_sock_priority(int sock, enum dropbear_prio prio);
 
+int get_sock_port(int sock);
 void get_socket_address(int fd, char **local_host, char **local_port,
 		char **remote_host, char **remote_port, int host_lookup);
 void getaddrstring(struct sockaddr_storage* addr, 
--- a/svr-tcpfwd.c	Thu Jan 25 21:58:00 2018 +0800
+++ b/svr-tcpfwd.c	Thu Jan 25 19:51:41 2018 +0530
@@ -47,7 +47,7 @@
 #endif /* !DROPBEAR_SVR_REMOTETCPFWD */
 
 static int svr_cancelremotetcp(void);
-static int svr_remotetcpreq(void);
+static int svr_remotetcpreq(int *allocated_listen_port);
 static int newtcpdirect(struct Channel * channel);
 
 #if DROPBEAR_SVR_REMOTETCPFWD
@@ -86,7 +86,16 @@
 	}
 
 	if (strcmp("tcpip-forward", reqname) == 0) {
-		ret = svr_remotetcpreq();
+		int allocated_listen_port;
+		ret = svr_remotetcpreq(&allocated_listen_port);
+		/* client expects-port-number-to-make-use-of-server-allocated-ports */
+		if (DROPBEAR_SUCCESS == ret) {
+			CHECKCLEARTOWRITE();
+			buf_putbyte(ses.writepayload, SSH_MSG_REQUEST_SUCCESS);
+			buf_putint(ses.writepayload, allocated_listen_port);
+			encrypt_packet();
+			wantreply = 0; //so out does not do so
+	  }
 	} else if (strcmp("cancel-tcpip-forward", reqname) == 0) {
 		ret = svr_cancelremotetcp();
 	} else {
@@ -152,7 +161,7 @@
 	return ret;
 }
 
-static int svr_remotetcpreq() {
+static int svr_remotetcpreq(int *allocated_listen_port) {
 
 	int ret = DROPBEAR_FAILURE;
 	char * request_addr = NULL;
@@ -170,19 +179,16 @@
 
 	port = buf_getint(ses.payload);
 
-	if (port == 0) {
-		dropbear_log(LOG_INFO, "Server chosen tcpfwd ports are unsupported");
-		goto out;
-	}
+	if (port != 0) {
+		if (port < 1 || port > 65535) {
+			TRACE(("invalid port: %d", port))
+			goto out;
+		}
 
-	if (port < 1 || port > 65535) {
-		TRACE(("invalid port: %d", port))
-		goto out;
-	}
-
-	if (!ses.allowprivport && port < IPPORT_RESERVED) {
-		TRACE(("can't assign port < 1024 for non-root"))
-		goto out;
+		if (!ses.allowprivport && port < IPPORT_RESERVED) {
+			TRACE(("can't assign port < 1024 for non-root"))
+			goto out;
+		}
 	}
 
 	tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener));
@@ -203,6 +209,13 @@
 	}
 
 	ret = listen_tcpfwd(tcpinfo);
+	if (DROPBEAR_SUCCESS == ret) {
+		tcpinfo->listenport = get_sock_port(ses.listeners[0]->socks[0]);
+		*allocated_listen_port = tcpinfo->listenport;
+		dropbear_log(LOG_INFO, "tcpip-forward %s:%d '%s'", 
+			((NULL == tcpinfo->listenaddr)?"localhost":tcpinfo->listenaddr), 
+			tcpinfo->listenport, ses.authstate.pw_name);
+	}
 
 out:
 	if (ret == DROPBEAR_FAILURE) {
@@ -211,7 +224,9 @@
 		m_free(request_addr);
 		m_free(tcpinfo);
 	}
+
 	TRACE(("leave remotetcpreq"))
+
 	return ret;
 }