From e05e2e520b2d79f382bb77592246dbeaaa54b164 Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Sat, 2 Jan 2021 08:27:05 +0100
Subject: [PATCH] libutil: Add sockaddr_snprintf() (taken from NetBSD).

---
 lib/libutil/Makefile            |   4 +-
 lib/libutil/libutil.h           |   2 +
 lib/libutil/sockaddr_snprintf.3 | 235 +++++++++++++++++++++++++
 lib/libutil/sockaddr_snprintf.c | 295 ++++++++++++++++++++++++++++++++
 4 files changed, 534 insertions(+), 2 deletions(-)
 create mode 100644 lib/libutil/sockaddr_snprintf.3
 create mode 100644 lib/libutil/sockaddr_snprintf.c

diff --git a/lib/libutil/Makefile b/lib/libutil/Makefile
index fc4f1ad3013..0d6bc6b909d 100644
--- a/lib/libutil/Makefile
+++ b/lib/libutil/Makefile
@@ -9,7 +9,7 @@ SRCS=	flopen.c login_tty.c logwtmpx.c pty.c \
 	login_cap.c login_class.c login_auth.c login_times.c login_ok.c \
 	login_crypt.c loginx.c logoutx.c _secure_path.c uucplock.c \
 	property.c auth.c \
-	realhostname.c stub.c pidfile.c trimdomain.c \
+	realhostname.c stub.c pidfile.c sockaddr_snprintf.c trimdomain.c \
 	dehumanize_number.c humanize_number.c humanize_unsigned.c pw_util.c \
 	efun.c getmntopts.c
 SRCS+=	gr_util.c hexdump.c
@@ -23,7 +23,7 @@ MAN+=	flopen.3 loginx.3 login_auth.3 login_tty.3 \
 	pty.3 \
 	login_cap.3 login_class.3 login_times.3 login_ok.3 \
 	_secure_path.3 uucplock.3 property.3 auth.3 realhostname.3 \
-	realhostname_sa.3 trimdomain.3 pidfile.3 \
+	realhostname_sa.3 sockaddr_snprintf.3 trimdomain.3 pidfile.3 \
 	humanize_number.3 humanize_unsigned.3 \
 	efun.3 getmntopts.3
 MAN+=	login.conf.5 auth.conf.5
diff --git a/lib/libutil/libutil.h b/lib/libutil/libutil.h
index f5896363c20..c6a0c4a3f73 100644
--- a/lib/libutil/libutil.h
+++ b/lib/libutil/libutil.h
@@ -100,6 +100,8 @@ properties
 	properties_read(int fd);
 int	realhostname(char *, size_t, const struct in_addr *);
 int	realhostname_sa(char *, size_t, struct sockaddr *, int);
+int	sockaddr_snprintf(char *, size_t, const char *,
+	    const struct sockaddr *);
 
 #ifdef _PWD_H_
 int	pw_copy(int _ffd, int _tfd, const struct passwd *_pw,
diff --git a/lib/libutil/sockaddr_snprintf.3 b/lib/libutil/sockaddr_snprintf.3
new file mode 100644
index 00000000000..5c0c789384d
--- /dev/null
+++ b/lib/libutil/sockaddr_snprintf.3
@@ -0,0 +1,235 @@
+.\"     $NetBSD: sockaddr_snprintf.3,v 1.8 2013/06/07 17:23:26 christos Exp $
+.\"
+.\" Copyright (c) 2004 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Christos Zoulas.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd January 2, 2021
+.Dt SOCKADDR_SNPRINTF 3
+.Os
+.Sh NAME
+.Nm sockaddr_snprintf
+.Nd formatting function for socket address structures
+.Sh LIBRARY
+.Lb libutil
+.Sh SYNOPSIS
+.In util.h
+.Ft int
+.Fn sockaddr_snprintf "char *buf" "size_t buflen" "const char *fmt" "const struct sockaddr *sa"
+.Sh DESCRIPTION
+The
+.Fn sockaddr_snprintf
+function formats a socket address into a form suitable for printing.
+.Pp
+This function is convenient because it is protocol independent, i.e. one does
+not need to know the address family of the sockaddr in order to print it.
+The
+.Xr printf 3
+like format string specifies how the address is going to be printed.
+Some formatting characters are only supported by some address families.
+If a certain formatting character is not supported, then the string
+.Dq N/A
+is printed.
+.Pp
+The resulting formatted string is placed into
+.Fa buf .
+Up to
+.Fa buflen
+characters are placed in
+.Fa buf .
+.Pp
+The following formatting characters are supported (immediately
+after a percent
+.Pq Sq %
+sign):
+.Bl -tag -width %a
+.It a
+The node address portion of the socket address is printed numerically.
+For
+.Dv AF_INET
+the address is printed as:
+.Dq A.B.C.D
+and
+for AF_INET6
+the address is printed as:
+.Dq A:B...[%if]
+using
+.Xr getnameinfo 3
+internally with
+.Dv NI_NUMERICHOST .
+For
+.Dv AF_LOCAL
+.Pq Dv AF_UNIX
+the address is printed as:
+.Dq socket-path
+For
+.Dv AF_LINK
+the address is printed as:
+.Dq a.b.c.d.e.f
+using
+.Xr link_ntoa 3 ,
+but the interface portion is skipped (see below).
+For
+.Dv AF_UNSPEC
+nothing is printed.
+.It A
+The symbolic name of the address is printed.
+For
+.Dv AF_INET
+and
+.Dv AF_INET6
+this is the hostname associated with the address.
+For all other address families, it is the same as the
+.Dq a
+format.
+.It D
+Debugging output:
+For all addresses, print all their fields as
+.Dq field_name=value .
+.It f
+The numeric value of the family of the address is printed.
+.It l
+The length of the socket address is printed.
+.It p
+For
+.Dv AF_INET
+and
+.Dv AF_INET6
+the numeric value of the port portion of the address is printed.
+.It P
+For
+.Dv AF_INET
+and
+.Dv AF_INET6
+this is the name of the service associated with the port number, if
+available.
+For all other address families, it is the same as the
+.Dq p
+format.
+.It I
+For
+.Dv AF_LINK
+addresses, the interface name portion is printed.
+.It F
+For
+.Dv AF_INET6
+addresses, the flowinfo portion of the address is printed numerically.
+.It R
+Not supported on
+.Dx .
+Always prints
+.Dq N/A .
+.It S
+For
+.Dv AF_INET6
+addresses, the scope portion of the address is printed numerically.
+.It ?
+If present between
+.Dq %
+and the format character, and the selected format does not apply to
+the given address family, the
+.Dq N/A
+string is elided and no output results.
+.El
+.Sh RETURN VALUES
+The
+.Fn sockaddr_snprintf
+function returns the number of characters that are required to format the
+value
+.Fa val
+given the format string
+.Fa fmt
+excluding the terminating NUL.
+The returned string in
+.Fa buf
+is always NUL-terminated.
+If the address family is not supported,
+.Fn sockaddr_snprintf
+returns \-1 and sets
+.Va errno
+to
+.Dv EAFNOSUPPORT .
+For
+.Dv AF_INET
+and
+.Dv AF_INET6
+addresses
+.Fn sockaddr_snprintf
+returns \-1 if the
+.Xr getnameinfo 3
+conversion failed, and
+.Fa errno
+is set to the error value from
+.Xr getnameinfo 3 .
+.Sh ERRORS
+If the buffer
+.Fa buf
+is too small to hold the formatted output,
+.Fn sockaddr_snprintf
+will still return the buffer, containing a truncated string.
+.Sh SEE ALSO
+.Xr getaddrinfo 3 ,
+.Xr getnameinfo 3 ,
+.Xr link_ntoa 3 ,
+.Xr snprintf 3
+.Sh HISTORY
+The
+.Fn sockaddr_snprintf
+first appeared in
+.Nx 3.0 .
+.Sh BUGS
+The
+.Fn sockaddr_snprintf
+interface is experimental and might change in the future.
+.Pp
+There is no way to specify different formatting styles for particular
+addresses.
+For example it would be useful to print
+.Dv AF_LINK
+addresses as
+.Dq %.2x:%.2x...
+instead of
+.Dq %x.%x...
+.Pp
+This function is supposed to be quick, but
+.Xr getnameinfo 3
+might use system calls to convert the scope number to an interface
+name and the
+.Dq A
+and
+.Dq P
+format characters call
+.Xr getaddrinfo 3
+which may block for a noticeable period of time.
+.Pp
+Not all formatting characters are supported by all address families and
+printing
+.Dq N/A
+is not very convenient.
+The
+.Dq \&?
+character can suppress this, but other formatting (e.g., spacing or
+punctuation) will remain.
diff --git a/lib/libutil/sockaddr_snprintf.c b/lib/libutil/sockaddr_snprintf.c
new file mode 100644
index 00000000000..100452daff6
--- /dev/null
+++ b/lib/libutil/sockaddr_snprintf.c
@@ -0,0 +1,295 @@
+/*	$NetBSD: sockaddr_snprintf.c,v 1.14 2016/12/29 18:30:55 christos Exp $	*/
+
+/*-
+ * Copyright (c) 2004, 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+#include <net/if_dl.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <libutil.h>
+#include <netdb.h>
+
+#ifdef BSD4_4
+# define SALEN(sa)	((sa)->sa ## _len)
+#else
+# define SALEN(sa)	((unsigned)sizeof(*sa))
+#endif
+
+static int
+debug_in(char *str, size_t len, const struct sockaddr_in *sin)
+{
+	return snprintf(str, len, "sin_len=%u, sin_family=%u, sin_port=%u, "
+	    "sin_addr.s_addr=%08x",
+	    SALEN(sin), sin->sin_family, sin->sin_port,
+	    sin->sin_addr.s_addr);
+}
+
+static int
+debug_in6(char *str, size_t len, const struct sockaddr_in6 *sin6)
+{
+	const uint8_t *s = sin6->sin6_addr.s6_addr;
+
+	return snprintf(str, len, "sin6_len=%u, sin6_family=%u, sin6_port=%u, "
+	    "sin6_flowinfo=%u, "
+	    "sin6_addr=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
+	    "%02x:%02x:%02x:%02x:%02x:%02x, sin6_scope_id=%u",
+	    SALEN(sin6), sin6->sin6_family, sin6->sin6_port,
+	    sin6->sin6_flowinfo, s[0x0], s[0x1], s[0x2], s[0x3], s[0x4], s[0x5],
+	    s[0x6], s[0x7], s[0x8], s[0x9], s[0xa], s[0xb], s[0xc], s[0xd],
+	    s[0xe], s[0xf], sin6->sin6_scope_id);
+}
+
+static int
+debug_un(char *str, size_t len, const struct sockaddr_un *sun)
+{
+	return snprintf(str, len, "sun_len=%u, sun_family=%u, sun_path=%*s",
+	    SALEN(sun), sun->sun_family, (int)sizeof(sun->sun_path),
+	    sun->sun_path);
+}
+
+static int
+debug_dl(char *str, size_t len, const struct sockaddr_dl *sdl)
+{
+	const uint8_t *s = (const void *)sdl->sdl_data;
+
+	return snprintf(str, len, "sdl_len=%u, sdl_family=%u, sdl_index=%u, "
+	    "sdl_type=%u, sdl_nlen=%u, sdl_alen=%u, sdl_slen=%u, sdl_data="
+	    "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+	    SALEN(sdl), sdl->sdl_family, sdl->sdl_index,
+	    sdl->sdl_type, sdl->sdl_nlen, sdl->sdl_alen, sdl->sdl_slen,
+	    s[0x0], s[0x1], s[0x2], s[0x3], s[0x4], s[0x5],
+	    s[0x6], s[0x7], s[0x8], s[0x9], s[0xa], s[0xb]);
+}
+
+int
+sockaddr_snprintf(char * const sbuf, const size_t len, const char * const fmt,
+    const struct sockaddr * const sa)
+{
+	const void *a = NULL;
+	char abuf[1024], nbuf[1024], *addr = NULL;
+	char Abuf[1024], pbuf[32], *name = NULL, *port = NULL;
+	char *ebuf = &sbuf[len - 1], *buf = sbuf;
+	const char *ptr, *s;
+	size_t salen;
+	int p = -1;
+	const struct sockaddr_in *sin4 = NULL;
+	const struct sockaddr_in6 *sin6 = NULL;
+	const struct sockaddr_un *sun = NULL;
+	const struct sockaddr_dl *sdl = NULL;
+	char *w = NULL;
+	int na = 1;
+
+#define ADDC(c) do { if (buf < ebuf) *buf++ = c; else buf++; } \
+	while (/*CONSTCOND*/0)
+#define ADDS(p) do { for (s = p; *s; s++) ADDC(*s); } \
+	while (/*CONSTCOND*/0)
+#define ADDNA() do { if (na) ADDS("N/A"); } \
+	while (/*CONSTCOND*/0)
+
+	switch (sa->sa_family) {
+	case AF_UNSPEC:
+		goto done;
+	case AF_LOCAL:
+		salen = sizeof(*sun);
+		sun = ((const struct sockaddr_un *)(const void *)sa);
+		(void)strlcpy(addr = abuf, sun->sun_path, sizeof(abuf));
+		break;
+	case AF_INET:
+		salen = sizeof(*sin4);
+		sin4 = ((const struct sockaddr_in *)(const void *)sa);
+		p = ntohs(sin4->sin_port);
+		a = &sin4->sin_addr;
+		break;
+	case AF_INET6:
+		salen = sizeof(*sin6);
+		sin6 = ((const struct sockaddr_in6 *)(const void *)sa);
+		p = ntohs(sin6->sin6_port);
+		a = &sin6->sin6_addr;
+		break;
+	case AF_LINK:
+		sdl = ((const struct sockaddr_dl *)(const void *)sa);
+		addr = abuf;
+		if (sdl->sdl_slen == 0 && sdl->sdl_nlen == 0
+		    && sdl->sdl_alen == 0) {
+			salen = sizeof(*sdl);
+			(void)snprintf(abuf, sizeof(abuf), "link#%hu",
+			    sdl->sdl_index);
+		} else {
+			salen = sdl->sdl_slen + sdl->sdl_nlen +  sdl->sdl_alen;
+			if (salen < sizeof(*sdl))
+				salen = sizeof(*sdl);
+			(void)strlcpy(abuf, link_ntoa(sdl), sizeof(abuf));
+			if ((w = strchr(addr, ':')) != NULL) {
+			    *w++ = '\0';
+			    addr = w;
+			}
+		}
+		break;
+	default:
+		errno = EAFNOSUPPORT;
+		return -1;
+	}
+
+	if (addr == abuf)
+		name = addr;
+
+	if (a && getnameinfo(sa, (socklen_t)salen, addr = abuf,
+	    (unsigned int)sizeof(abuf), NULL, 0,
+	    NI_NUMERICHOST|NI_NUMERICSERV) != 0)
+		return -1;
+
+	for (ptr = fmt; *ptr; ptr++) {
+		if (*ptr != '%') {
+			ADDC(*ptr);
+			continue;
+		}
+	  next_char:
+		switch (*++ptr) {
+		case '?':
+			na = 0;
+			goto next_char;
+		case 'a':
+			ADDS(addr);
+			break;
+		case 'p':
+			if (p != -1) {
+				(void)snprintf(nbuf, sizeof(nbuf), "%d", p);
+				ADDS(nbuf);
+			} else
+				ADDNA();
+			break;
+		case 'f':
+			(void)snprintf(nbuf, sizeof(nbuf), "%d", sa->sa_family);
+			ADDS(nbuf);
+			break;
+		case 'l':
+			(void)snprintf(nbuf, sizeof(nbuf), "%zu", salen);
+			ADDS(nbuf);
+			break;
+		case 'A':
+			if (name)
+				ADDS(name);
+			else if (!a)
+				ADDNA();
+			else {
+				getnameinfo(sa, (socklen_t)salen, name = Abuf,
+					(unsigned int)sizeof(nbuf), NULL, 0, 0);
+				ADDS(name);
+			}
+			break;
+		case 'P':
+			if (port)
+				ADDS(port);
+			else if (p == -1)
+				ADDNA();
+			else {
+				getnameinfo(sa, (socklen_t)salen, NULL, 0,
+					port = pbuf,
+					(unsigned int)sizeof(pbuf), 0);
+				ADDS(port);
+			}
+			break;
+		case 'I':
+			if (sdl && addr != abuf) {
+				ADDS(abuf);
+			} else {
+				ADDNA();
+			}
+			break;
+		case 'F':
+			if (sin6) {
+				(void)snprintf(nbuf, sizeof(nbuf), "%d",
+				    sin6->sin6_flowinfo);
+				ADDS(nbuf);
+				break;
+			} else {
+				ADDNA();
+			}
+			break;
+		case 'S':
+			if (sin6) {
+				(void)snprintf(nbuf, sizeof(nbuf), "%d",
+				    sin6->sin6_scope_id);
+				ADDS(nbuf);
+				break;
+			} else {
+				ADDNA();
+			}
+			break;
+		case 'R':
+			ADDNA();
+			break;
+		case 'D':
+			switch (sa->sa_family) {
+			case AF_LOCAL:
+				debug_un(nbuf, sizeof(nbuf), sun);
+				break;
+			case AF_INET:
+				debug_in(nbuf, sizeof(nbuf), sin4);
+				break;
+			case AF_INET6:
+				debug_in6(nbuf, sizeof(nbuf), sin6);
+				break;
+			case AF_LINK:
+				debug_dl(nbuf, sizeof(nbuf), sdl);
+				break;
+			default:
+				abort();
+			}
+			ADDS(nbuf);
+			break;
+		default:
+			ADDC('%');
+			if (na == 0)
+				ADDC('?');
+			if (*ptr == '\0')
+				goto done;
+			/*FALLTHROUGH*/
+		case '%':
+			ADDC(*ptr);
+			break;
+		}
+		na = 1;
+	}
+done:
+	if (buf < ebuf)
+		*buf = '\0';
+	else if (len != 0)
+		sbuf[len - 1] = '\0';
+	return (int)(buf - sbuf);
+}
-- 
2.28.0

