From: Dirk Jagdmann <doj@cubic.org>
Date: Sun, 6 Mar 2022 21:26:31 -0800
Subject: Add abstract UNIX domain socket support

When using '-U' to connect() or bind() to a UNIX domain socket, if the
address (path) starts with "@", it is read as an abstract namespace
socket (the leading "@" is replaced with a NUL byte before binding).

This feature is Linux-only.

Forwarded: not-needed
---
 nc.1     |  3 +++
 netcat.c | 75 ++++++++++++++++++++++++++++++++++++++++++++--------------------
 2 files changed, 55 insertions(+), 23 deletions(-)

diff --git a/nc.1 b/nc.1
index 3b92227..118ad09 100644
--- a/nc.1
+++ b/nc.1
@@ -235,6 +235,9 @@ Cannot be used together with
 .Fl F
 or
 .Fl x .
+On Linux, if the name starts with an at symbol (`@') it is read as an abstract
+namespace socket: the leading `@' is replaced with a \fBNUL\fR byte
+before binding or connecting.  For details, see \fBunix\fR(7).
 .It Fl u
 Use UDP instead of TCP.
 Cannot be used together with
diff --git a/netcat.c b/netcat.c
index b188911..6f7596c 100644
--- a/netcat.c
+++ b/netcat.c
@@ -98,6 +98,7 @@
 #include <netdb.h>
 #include <poll.h>
 #include <signal.h>
+#include <stddef.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -208,6 +209,7 @@ int	timeout_connect(int, const struct sockaddr *, socklen_t);
 int	socks_connect(const char *, const char *, struct addrinfo,
 	    const char *, const char *, struct addrinfo, int, const char *);
 int	udptest(int);
+int	unix_setup_sockaddr(char *, struct sockaddr_un *, int *);
 void	connection_info(const char *, const char *, const char *, const char *);
 int	unix_bind(char *, int);
 int	unix_connect(char *);
@@ -935,6 +937,46 @@ main(int argc, char *argv[])
 	return ret;
 }
 
+int
+unix_setup_sockaddr(char *path, struct sockaddr_un *s_un, int *addrlen)
+{
+	int sun_path_len;
+
+	*addrlen = offsetof(struct sockaddr_un, sun_path);
+	memset(s_un, 0, *addrlen);
+	s_un->sun_family = AF_UNIX;
+
+	if (path[0] == '\0') {
+		/* Always reject the empty path, aka NUL abstract socket on
+		 * Linux (OTOH the *empty* abstract socket is supported and
+		 * specified as @""). */
+		errno = EINVAL;
+		return -1;
+	}
+#ifdef __linux__
+	/* If the unix domain socket path starts with '@',
+	 * treat it as a Linux abstract name. */
+	else if (path[0] == '@') {
+		if ((sun_path_len = strlen(path)) <= sizeof(s_un->sun_path)) {
+			s_un->sun_path[0] = '\0';
+			strncpy(s_un->sun_path+1, path+1, sun_path_len-1);
+			*addrlen += sun_path_len;
+		} else {
+			errno = ENAMETOOLONG;
+			return -1;
+		}
+	}
+#endif
+	else if ((sun_path_len = strlcpy(s_un->sun_path, path, sizeof(s_un->sun_path))) <
+	    sizeof(s_un->sun_path))
+		*addrlen += sun_path_len + 1; /* account for trailing '\0' */
+	else {
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+	return 0;
+}
+
 /*
  * unix_bind()
  * Returns a unix socket bound to the given path
@@ -943,24 +985,17 @@ int
 unix_bind(char *path, int flags)
 {
 	struct sockaddr_un s_un;
-	int s, save_errno;
+	int s, save_errno, addrlen;
+
+	if (unix_setup_sockaddr(path, &s_un, &addrlen) == -1)
+		return -1;
 
 	/* Create unix domain socket. */
 	if ((s = socket(AF_UNIX, flags | (uflag ? SOCK_DGRAM : SOCK_STREAM),
 	    0)) == -1)
 		return -1;
 
-	memset(&s_un, 0, sizeof(struct sockaddr_un));
-	s_un.sun_family = AF_UNIX;
-
-	if (strlcpy(s_un.sun_path, path, sizeof(s_un.sun_path)) >=
-	    sizeof(s_un.sun_path)) {
-		close(s);
-		errno = ENAMETOOLONG;
-		return -1;
-	}
-
-	if (bind(s, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) {
+	if (bind(s, (struct sockaddr *)&s_un, addrlen) == -1) {
 		save_errno = errno;
 		close(s);
 		errno = save_errno;
@@ -1070,7 +1105,10 @@ int
 unix_connect(char *path)
 {
 	struct sockaddr_un s_un;
-	int s, save_errno;
+	int s, save_errno, addrlen;
+
+	if (unix_setup_sockaddr(path, &s_un, &addrlen) == -1)
+		return -1;
 
 	if (uflag) {
 		if ((s = unix_bind(unix_dg_tmp_socket, SOCK_CLOEXEC)) == -1)
@@ -1080,16 +1118,7 @@ unix_connect(char *path)
 			return -1;
 	}
 
-	memset(&s_un, 0, sizeof(struct sockaddr_un));
-	s_un.sun_family = AF_UNIX;
-
-	if (strlcpy(s_un.sun_path, path, sizeof(s_un.sun_path)) >=
-	    sizeof(s_un.sun_path)) {
-		close(s);
-		errno = ENAMETOOLONG;
-		return -1;
-	}
-	if (connect(s, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) {
+	if (connect(s, (struct sockaddr *)&s_un, addrlen) == -1) {
 		save_errno = errno;
 		close(s);
 		errno = save_errno;
