Index | Thread | Search

From:
Damien Miller <djm@mindrot.org>
Subject:
nc(1): SOCKS4A support
To:
tech@openbsd.org
Date:
Wed, 21 May 2025 18:29:56 +1000

Download raw body.

Thread
  • Damien Miller:

    nc(1): SOCKS4A support

Hi,

OpenSSH supports the SOCKS protocol via ssh(1)'s -D option. This
supports SOCKS4, SOCKS4A and SOCKS5.

nc(1) supports SOCKS4 and SOCKS5 (and HTTP CONNECT) but not SOCKS4A,
but I'd like to ensure that SOCKS4A is tested while I refactor some
code in ssh(1).

So this adds SOCKS4A support for nc(1) and adds use of it to the
ssh(1) regression tests.

FYI SOCKS4A is a simple extension to SOCKS4 that allows passing
the destination host as a hostname (SOCKS4 only allows passing IPv4
addresses).

ok?

Index: regress/usr.bin/ssh/dynamic-forward.sh
===================================================================
RCS file: /cvs/src/regress/usr.bin/ssh/dynamic-forward.sh,v
diff -u -p -r1.17 dynamic-forward.sh
--- regress/usr.bin/ssh/dynamic-forward.sh	8 Mar 2024 11:34:10 -0000	1.17
+++ regress/usr.bin/ssh/dynamic-forward.sh	21 May 2025 08:26:04 -0000
@@ -48,7 +48,7 @@ stop_ssh() {
 check_socks() {
 	direction=$1
 	expect_success=$2
-	for s in 4 5; do
+	for s in 4A 4 5; do
 	    for h in 127.0.0.1 localhost; do
 		trace "testing ssh socks version $s host $h (-$direction)"
 		${REAL_SSH} -q -F $OBJ/ssh_config -o \
Index: usr.bin/nc/nc.1
===================================================================
RCS file: /cvs/src/usr.bin/nc/nc.1,v
diff -u -p -r1.98 nc.1
--- usr.bin/nc/nc.1	1 Apr 2024 12:40:18 -0000	1.98
+++ usr.bin/nc/nc.1	21 May 2025 08:26:04 -0000
@@ -338,12 +338,18 @@ when talking to the proxy server.
 Supported protocols are
 .Cm 4
 (SOCKS v.4),
+.Cm 4A
+(SOCKS v.4A),
 .Cm 5
 (SOCKS v.5)
 and
 .Cm connect
 (HTTPS proxy).
 If the protocol is not specified, SOCKS version 5 is used.
+Note that the SOCKS v.4 protocol is very limited and can only be used when
+the destination host can be resolved to an IPv4 address.
+The other protocols pass the destination as a string to be interpreted
+by the remote proxy and do not have this limitiation.
 .It Fl x Ar proxy_address Ns Op : Ns Ar port
 Connect to
 .Ar destination
Index: usr.bin/nc/netcat.c
===================================================================
RCS file: /cvs/src/usr.bin/nc/netcat.c,v
diff -u -p -r1.229 netcat.c
--- usr.bin/nc/netcat.c	2 Nov 2024 17:19:27 -0000	1.229
+++ usr.bin/nc/netcat.c	21 May 2025 08:26:04 -0000
@@ -190,6 +190,8 @@ main(int argc, char *argv[])
 				socksv = -1; /* HTTP proxy CONNECT */
 			else if (strcmp(optarg, "4") == 0)
 				socksv = 4; /* SOCKS v.4 */
+			else if (strcasecmp(optarg, "4A") == 0)
+				socksv = 44; /* SOCKS v.4A */
 			else if (strcmp(optarg, "5") == 0)
 				socksv = 5; /* SOCKS v.5 */
 			else
Index: usr.bin/nc/socks.c
===================================================================
RCS file: /cvs/src/usr.bin/nc/socks.c,v
diff -u -p -r1.31 socks.c
--- usr.bin/nc/socks.c	8 Jun 2022 20:20:26 -0000	1.31
+++ usr.bin/nc/socks.c	21 May 2025 08:26:04 -0000
@@ -293,7 +293,7 @@ socks_connect(const char *host, const ch
 		default:
 			errx(1, "connection failed, unsupported address type");
 		}
-	} else if (socksv == 4) {
+	} else if (socksv == 4 || socksv == 44) {
 		/* This will exit on lookup failure */
 		decode_addrport(host, port, (struct sockaddr *)&addr,
 		    sizeof(addr), 1, 0);
@@ -302,10 +302,22 @@ socks_connect(const char *host, const ch
 		buf[0] = SOCKS_V4;
 		buf[1] = SOCKS_CONNECT;	/* connect */
 		memcpy(buf + 2, &in4->sin_port, sizeof in4->sin_port);
-		memcpy(buf + 4, &in4->sin_addr, sizeof in4->sin_addr);
+		if (socksv == 4) {
+			memcpy(buf + 4, &in4->sin_addr, sizeof in4->sin_addr);
+		} else {
+			/* SOCKS4A uses addr of 0.0.0.x, and hostname later */
+			buf[4] = buf[5] = buf[6] = 0;
+			buf[7] = 1;
+		}
 		buf[8] = 0;	/* empty username */
 		wlen = 9;
-
+		if (socksv == 44) {
+			/* SOCKS4A has nul-terminated hostname after user */
+			if (strlcpy(buf + 9, host,
+			    sizeof(buf) - 9) >= sizeof(buf) - 9)
+				errx(1, "hostname too big");
+			wlen = 9 + strlen(host) + 1;
+		}
 		cnt = atomicio(vwrite, proxyfd, buf, wlen);
 		if (cnt != wlen)
 			err(1, "write failed (%zu/%zu)", cnt, wlen);