Commit Diff


commit - e8c445814acc14577e11dd1160ee4342c9c34264
commit + 45ea00cefb6cb3e120c5fdb33b5c1bd812dfef77
blob - /dev/null
blob + a38707df0b2ccdd05830769ebc14a753fe3439bf (mode 644)
--- /dev/null
+++ Makefile.gnu
@@ -0,0 +1,139 @@
+LOCALBASE?=	/usr/
+
+PROG=		filter-auth
+MAN=		filter-auth.8
+BINDIR=		${LOCALBASE}/libexec/opensmtpd/
+MANDIR=		${LOCALBASE}/share/man/man8
+
+SRCS+=		main.c ltok.c unpack_dns.c
+
+ifdef HAVE_ED25519
+CFLAGS+=	-DHAVE_ED25519
+endif
+ifdef LIBCRYPTOPC
+CRYPT_CFLAGS!=	pkg-config --cflags ${LIBCRYPTOPC}
+CRYPT_LDFLAGS_L!=pkg-config --libs-only-L ${LIBCRYPTOPC}
+CRYPT_LDFLAGS_libdir!=pkg-config --variable libdir ${LIBCRYPTOPC}
+CRYPT_LDFLAGS=	${CRYPT_LDFLAGS_L}
+CRYPT_LDFLAGS+=	-Wl,-rpath,${CRYPT_LDFLAGS_libdir}
+CRYPT_LDADD!=	pkg-config --libs-only-l ${LIBCRYPTOPC}
+else
+CRYPT_CFLAGS=
+CRYPT_LDFLAGS=
+CRYPT_LDADD=	-lcrypto
+endif
+
+CFLAGS+=	-I${LOCALBASE}/include
+CFLAGS+=	-Wall -I${.CURDIR}
+CFLAGS+=	-Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+=	-Wmissing-declarations
+CFLAGS+=	-Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+=	-Wsign-compare
+CFLAGS+=	${CRYPT_CFLAGS}
+CFLAGS+=	-I${CURDIR} -I${CURDIR}/openbsd-compat/
+
+LDFLAGS+=	-L${LOCALBASE}/lib
+LDFLAGS+=	${CRYPT_LDFLAGS}
+LDADD+=		${CRYPT_LDADD} -lopensmtpd -levent
+
+INSTALL?=	install
+
+NEED_RECALLOCARRAY?=	1
+NEED_STRTONUM?=		1
+NEED_PLEDGE?=		1
+NEED_INET_NET_PTON?=	1
+NEED_FGETLN?=		1
+NEED_LIBASR?=		1
+
+MANFORMAT?=		mangz
+
+BINOWN?=	root
+BINGRP?=	root
+BINPERM?=	755
+MANOWN?=	root
+MANGRP?=	root
+MANPERM?=	644
+
+ifeq (${MANFORMAT}, mangz)
+TARGET_MAN=		${MAN}.gz
+CLEANFILES+=		${TARGET_MAN}
+${TARGET_MAN}: ${MAN}
+	mandoc -Tman ${MAN} | gzip > $@
+else
+TARGET_MAN=		${MAN}
+endif
+
+ifeq (${NEED_RECALLOCARRAY}, 1)
+SRCS+=		${CURDIR}/openbsd-compat/recallocarray.c
+CFLAGS+=	-DNEED_RECALLOCARRAY=1
+
+recallocarray.o: ${CURDIR}/openbsd-compat/recallocarray.c
+	${CC} ${CFLAGS} -c -o recallocarray.o ${CURDIR}/openbsd-compat/recallocarray.c
+endif
+ifeq (${NEED_STRTONUM}, 1)
+SRCS+=		${CURDIR}/openbsd-compat/strtonum.c
+CFLAGS+=	-DNEED_STRTONUM=1
+
+strtonum.o: ${CURDIR}/openbsd-compat/strtonum.c
+	${CC} ${CFLAGS} -c -o strtonum.o ${CURDIR}/openbsd-compat/strtonum.c
+endif
+ifeq (${NEED_PLEDGE}, 1)
+CFLAGS+=	-DNEED_PLEDGE=1
+endif
+ifeq (${NEED_INET_NET_PTON}, 1)
+SRCS+=		${CURDIR}/openbsd-compat/inet_net_pton.c
+CFLAGS+=	-DNEED_INET_NET_PTON=1
+
+inet_net_pton.o: ${CURDIR}/openbsd-compat/inet_net_pton.c
+	${CC} ${CFLAGS} -c -o inet_net_pton.o ${CURDIR}/openbsd-compat/inet_net_pton.c
+endif
+ifeq (${NEED_FGETLN}, 1)
+SRCS+=		${CURDIR}/openbsd-compat/fgetln.c
+CFLAGS+=	-DNEED_FGETLN=1
+
+fgetln.o: ${CURDIR}/openbsd-compat/fgetln.c
+	${CC} ${CFLAGS} -c -o fgetln.o ${CURDIR}/openbsd-compat/fgetln.c
+endif
+ifeq (${NEED_LIBASR}, 1)
+SRCS+=		${CURDIR}/openbsd-compat/event_asr_run.c
+SRCS+=		${CURDIR}/openbsd-compat/libasr/asr.c
+SRCS+=		${CURDIR}/openbsd-compat/libasr/asr_utils.c
+SRCS+=		${CURDIR}/openbsd-compat/libasr/res_search_async.c
+SRCS+=		${CURDIR}/openbsd-compat/libasr/res_send_async.c
+CFLAGS+=	-I${CURDIR}/openbsd-compat/libasr -DNEED_LIBASR=1
+
+event_asr_run.o: ${CURDIR}/openbsd-compat/event_asr_run.c
+	${CC} ${CFLAGS} -c -o event_asr_run.o ${CURDIR}/openbsd-compat/event_asr_run.c
+asr.o: ${CURDIR}/openbsd-compat/libasr/asr.c
+	${CC} ${CFLAGS} -c -o asr.o ${CURDIR}/openbsd-compat/libasr/asr.c
+asr_utils.o: ${CURDIR}/openbsd-compat/libasr/asr_utils.c
+	${CC} ${CFLAGS} -c -o asr_utils.o ${CURDIR}/openbsd-compat/libasr/asr_utils.c
+res_search_async.o: ${CURDIR}/openbsd-compat/libasr/res_search_async.c
+	${CC} ${CFLAGS} -c -o res_search_async.o ${CURDIR}/openbsd-compat/libasr/res_search_async.c
+res_send_async.o: ${CURDIR}/openbsd-compat/libasr/res_send_async.c
+	${CC} ${CFLAGS} -c -o res_send_async.o ${CURDIR}/openbsd-compat/libasr/res_send_async.c
+endif
+
+${SRCS:.c=.d}:%.d:%.c
+	 ${CC} ${CFLAGS} -MM $< >$@
+CLEANFILES+=	${SRCS:.c=.d}
+
+OBJS=		${notdir ${SRCS:.c=.o}}
+CLEANFILES+=	${OBJS}
+
+${PROG}: ${OBJS}
+	${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD}
+
+.DEFAULT_GOAL=		all
+.PHONY: all
+all: ${PROG} ${TARGET_MAN}
+CLEANFILES+=	${PROG}
+
+.PHONY: clean
+clean:
+	rm -f ${CLEANFILES}
+
+.PHONY: install
+install: ${PROG}
+	${INSTALL} -D -o ${BINOWN} -g ${BINGRP} -m ${BINPERM} ${PROG} ${DESTDIR}${BINDIR}/${PROG}
+	${INSTALL} -D -o ${MANOWN} -g ${MANGRP} -m ${MANPERM} ${TARGET_MAN} ${DESTDIR}${MANDIR}/${TARGET_MAN}
blob - 0957e827087dd85686be6c5bacd7b005491fbd28
blob + fb6db0bbfc8bdf343569d8bd77b33424befeff20
--- main.c
+++ main.c
@@ -41,6 +41,7 @@
 #include <unistd.h>
 #include <asr.h>
 
+#include "openbsd-compat.h"
 #include "unpack_dns.h"
 #include "ltok.h"
 
@@ -158,8 +159,13 @@ struct header {
 };
 
 #define AUTHENTICATION_RESULTS_LINELEN 78
+#ifndef MIN
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
 
+#ifndef s6_addr32
+#define s6_addr32 __u6_addr.__u6_addr32
+#endif
 
 /* RFC 8617 Section 4.2.1 */
 #define ARC_MIN_I 1
@@ -2716,8 +2722,8 @@ spf_check_cidr6(struct spf_record *spf, struct in6_add
 
 	addr = &(((struct sockaddr_in6 *)(&ses->src))->sin6_addr);
 
-	a = addr->__u6_addr.__u6_addr32;
-	n = net->__u6_addr.__u6_addr32;
+	a = addr->s6_addr32;
+	n = net->s6_addr32;
 
 	whole = bits >> 5;
 	incomplete = bits & 0x1f;
blob - /dev/null
blob + dc5f765f0375b556181636e7a784f2f7232d2eff (mode 644)
--- /dev/null
+++ openbsd-compat/event_asr_run.c
@@ -0,0 +1,88 @@
+/*	$OpenBSD$	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "openbsd-compat.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <event.h>
+#include <stdlib.h>
+
+struct event_asr {
+	struct event	 ev;
+	struct asr_query *async;
+	void		(*cb)(struct asr_result *, void *);
+	void		*arg;
+};
+
+struct event_asr * event_asr_run(struct asr_query *,
+    void (*)(struct asr_result *, void *), void *);
+void event_asr_abort(struct event_asr *);
+
+static void
+event_asr_dispatch(int fd __attribute__((__unused__)),
+    short ev __attribute__((__unused__)), void *arg)
+{
+	struct event_asr	*eva = arg;
+	struct asr_result	 ar;
+	struct timeval		 tv;
+
+	event_del(&eva->ev);
+
+	if (asr_run(eva->async, &ar)) {
+		eva->cb(&ar, eva->arg);
+		free(eva);
+	} else {
+		event_set(&eva->ev, ar.ar_fd,
+		    ar.ar_cond == ASR_WANT_READ ? EV_READ : EV_WRITE,
+		    event_asr_dispatch, eva);
+		tv.tv_sec = ar.ar_timeout / 1000;
+		tv.tv_usec = (ar.ar_timeout % 1000) * 1000;
+		event_add(&eva->ev, &tv);
+	}
+}
+
+struct event_asr *
+event_asr_run(struct asr_query *async, void (*cb)(struct asr_result *, void *),
+    void *arg)
+{
+	struct event_asr *eva;
+	struct timeval tv;
+
+	eva = calloc(1, sizeof *eva);
+	if (eva == NULL)
+		return (NULL);
+	eva->async = async;
+	eva->cb = cb;
+	eva->arg = arg;
+	tv.tv_sec = 0;
+	tv.tv_usec = 0;
+	evtimer_set(&eva->ev, event_asr_dispatch, eva);
+	evtimer_add(&eva->ev, &tv);
+	return (eva);
+}
+
+void
+event_asr_abort(struct event_asr *eva)
+{
+	asr_abort(eva->async);
+	event_del(&eva->ev);
+	free(eva);
+}
blob - /dev/null
blob + 35233f5f2393673d5624e76fe0451556294d7a12 (mode 644)
--- /dev/null
+++ openbsd-compat/explicit_bzero.c
@@ -0,0 +1,15 @@
+/*  $OpenBSD: explicit_bzero.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */
+/*
+ * Public domain.
+ * Written by Matthew Dempsky.
+ */
+
+#include "openbsd-compat.h"
+
+#include <string.h>
+
+void
+explicit_bzero(void *buf, size_t len)
+{
+	memset(buf, 0, len);
+}
blob - /dev/null
blob + df2c60c8d45bda0389e8320f09eef1a114cd1052 (mode 644)
--- /dev/null
+++ openbsd-compat/fgetln.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015 Joerg Jung <jung@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * portable fgetln() version, NOT reentrant
+ */
+
+#include "openbsd-compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+void *reallocarray(void *, size_t, size_t);
+
+char *
+fgetln(FILE *fp, size_t *len)
+{
+	static char *buf = NULL;
+	static size_t bufsz = 0;
+	size_t r = 0;
+	char *p;
+	int c, e;
+
+	if (buf == NULL) {
+		if ((buf = calloc(1, BUFSIZ)) == NULL)
+			return NULL;
+		bufsz = BUFSIZ;
+	}
+
+	while ((c = getc(fp)) != EOF) {
+		buf[r++] = c;
+		if (r == bufsz) {
+			if (!(p = reallocarray(buf, 2, bufsz))) {
+				e = errno;
+				free(buf);
+				errno = e;
+				buf = NULL, bufsz = 0;
+				return NULL;
+			}
+			buf = p, bufsz = 2 * bufsz;
+		}
+		if (c == '\n')
+			break;
+	}
+	return (*len = r) ? buf : NULL;
+}
+
blob - /dev/null
blob + 46babd8088793077d4680ff85c551f698a1eda61 (mode 644)
--- /dev/null
+++ openbsd-compat/inet_net_pton.c
@@ -0,0 +1,252 @@
+/*	$OpenBSD: inet_net_pton.c,v 1.14 2022/12/27 17:10:06 jmc Exp $	*/
+
+/*
+ * Copyright (c) 2012 by Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 1996,1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_INET_NET_PTON) || defined(HAVE_LIBBSD)
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+static int	inet_net_pton_ipv4(const char *, u_char *, size_t);
+static int	inet_net_pton_ipv6(const char *, u_char *, size_t);
+
+/*
+ * static int
+ * inet_net_pton(af, src, dst, size)
+ *	convert network number from presentation to network format.
+ *	accepts hex octets, hex strings, decimal octets, and /CIDR.
+ *	"size" is in bytes and describes "dst".
+ * return:
+ *	number of bits, either imputed classfully or specified with /CIDR,
+ *	or -1 if some failure occurred (check errno).  ENOENT means it was
+ *	not a valid network specification.
+ * author:
+ *	Paul Vixie (ISC), June 1996
+ */
+int
+inet_net_pton(int af, const char *src, void *dst, size_t size)
+{
+	switch (af) {
+	case AF_INET:
+		return (inet_net_pton_ipv4(src, dst, size));
+	case AF_INET6:
+		return (inet_net_pton_ipv6(src, dst, size));
+	default:
+		errno = EAFNOSUPPORT;
+		return (-1);
+	}
+}
+
+/*
+ * static int
+ * inet_net_pton_ipv4(src, dst, size)
+ *	convert IPv4 network number from presentation to network format.
+ *	accepts hex octets, hex strings, decimal octets, and /CIDR.
+ *	"size" is in bytes and describes "dst".
+ * return:
+ *	number of bits, either imputed classfully or specified with /CIDR,
+ *	or -1 if some failure occurred (check errno).  ENOENT means it was
+ *	not an IPv4 network specification.
+ * note:
+ *	network byte order assumed.  this means 192.5.5.240/28 has
+ *	0x11110000 in its fourth octet.
+ * author:
+ *	Paul Vixie (ISC), June 1996
+ */
+static int
+inet_net_pton_ipv4(const char *src, u_char *dst, size_t size)
+{
+	static const char
+		xdigits[] = "0123456789abcdef",
+		digits[] = "0123456789";
+	int n, ch, tmp, dirty, bits;
+	const u_char *odst = dst;
+
+	ch = (unsigned char)*src++;
+	if (ch == '0' && (src[0] == 'x' || src[0] == 'X')
+	    && isascii((unsigned char)src[1]) && isxdigit((unsigned char)src[1])) {
+		/* Hexadecimal: Eat nybble string. */
+		if (size == 0)
+			goto emsgsize;
+		tmp = 0, dirty = 0;
+		src++;	/* skip x or X. */
+		while ((ch = (unsigned char)*src++) != '\0' &&
+		    isascii(ch) && isxdigit(ch)) {
+			if (isupper(ch))
+				ch = tolower(ch);
+			n = strchr(xdigits, ch) - xdigits;
+			assert(n >= 0 && n <= 15);
+			if (dirty == 0)
+				tmp = n;
+			else
+				tmp = (tmp << 4) | n;
+			if (++dirty == 2) {
+				if (size-- == 0)
+					goto emsgsize;
+				*dst++ = (u_char) tmp;
+				dirty = 0;
+			}
+		}
+		if (dirty) {  /* Odd trailing nybble? */
+			if (size-- == 0)
+				goto emsgsize;
+			*dst++ = (u_char) (tmp << 4);
+		}
+	} else if (isascii(ch) && isdigit(ch)) {
+		/* Decimal: eat dotted digit string. */
+		for (;;) {
+			tmp = 0;
+			do {
+				n = strchr(digits, ch) - digits;
+				assert(n >= 0 && n <= 9);
+				tmp *= 10;
+				tmp += n;
+				if (tmp > 255)
+					goto enoent;
+			} while ((ch = (unsigned char)*src++) != '\0' &&
+				 isascii(ch) && isdigit(ch));
+			if (size-- == 0)
+				goto emsgsize;
+			*dst++ = (u_char) tmp;
+			if (ch == '\0' || ch == '/')
+				break;
+			if (ch != '.')
+				goto enoent;
+			ch = (unsigned char)*src++;
+			if (!isascii(ch) || !isdigit(ch))
+				goto enoent;
+		}
+	} else
+		goto enoent;
+
+	bits = -1;
+	if (ch == '/' && isascii((unsigned char)src[0]) &&
+	    isdigit((unsigned char)src[0]) && dst > odst) {
+		/* CIDR width specifier.  Nothing can follow it. */
+		ch = (unsigned char)*src++;	/* Skip over the /. */
+		bits = 0;
+		do {
+			n = strchr(digits, ch) - digits;
+			assert(n >= 0 && n <= 9);
+			bits *= 10;
+			bits += n;
+			if (bits > 32)
+				goto emsgsize;
+		} while ((ch = (unsigned char)*src++) != '\0' &&
+			 isascii(ch) && isdigit(ch));
+		if (ch != '\0')
+			goto enoent;
+	}
+
+	/* Fiery death and destruction unless we prefetched EOS. */
+	if (ch != '\0')
+		goto enoent;
+
+	/* If nothing was written to the destination, we found no address. */
+	if (dst == odst)
+		goto enoent;
+	/* If no CIDR spec was given, infer width from net class. */
+	if (bits == -1) {
+		if (*odst >= 240)	/* Class E */
+			bits = 32;
+		else if (*odst >= 224)	/* Class D */
+			bits = 4;
+		else if (*odst >= 192)	/* Class C */
+			bits = 24;
+		else if (*odst >= 128)	/* Class B */
+			bits = 16;
+		else			/* Class A */
+			bits = 8;
+		/* If imputed mask is narrower than specified octets, widen. */
+		if (bits < ((dst - odst) * 8))
+			bits = (dst - odst) * 8;
+	}
+	/* Extend network to cover the actual mask. */
+	while (bits > ((dst - odst) * 8)) {
+		if (size-- == 0)
+			goto emsgsize;
+		*dst++ = '\0';
+	}
+	return (bits);
+
+ enoent:
+	errno = ENOENT;
+	return (-1);
+
+ emsgsize:
+	errno = EMSGSIZE;
+	return (-1);
+}
+
+
+static int
+inet_net_pton_ipv6(const char *src, u_char *dst, size_t size)
+{
+	struct in6_addr	 in6;
+	int		 ret;
+	int		 bits;
+	size_t		 bytes;
+	char		 buf[INET6_ADDRSTRLEN + sizeof("/128")];
+	char		*sep;
+	const char	*errstr;
+
+	if (strlcpy(buf, src, sizeof buf) >= sizeof buf) {
+		errno = EMSGSIZE;
+		return (-1);
+	}
+
+	sep = strchr(buf, '/');
+	if (sep != NULL)
+		*sep++ = '\0';
+
+	ret = inet_pton(AF_INET6, buf, &in6);
+	if (ret != 1)
+		return (-1);
+
+	if (sep == NULL)
+		bits = 128;
+	else {
+		bits = strtonum(sep, 0, 128, &errstr);
+		if (errstr) {
+			errno = EINVAL;
+			return (-1);
+		}
+	}
+
+	bytes = (bits + 7) / 8;
+	if (bytes > size) {
+		errno = EMSGSIZE;
+		return (-1);
+	}
+	memcpy(dst, &in6.s6_addr, bytes);
+	return (bits);
+}
+
+#endif
blob - /dev/null
blob + dd787a8b45a8923f1947546941026c359d7a91d7 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/asr.c
@@ -0,0 +1,957 @@
+/*	$OpenBSD: asr.c,v 1.68 2022/01/20 14:18:10 naddy Exp $	*/
+/*
+ * Copyright (c) 2010-2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "openbsd-compat.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <resolv.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "asr_private.h"
+
+#include "thread_private.h"
+
+#define DEFAULT_CONF		"lookup file\n"
+#define DEFAULT_LOOKUP		"lookup bind file"
+
+#define RELOAD_DELAY		15 /* seconds */
+
+static void asr_check_reload(struct asr *);
+static struct asr_ctx *asr_ctx_create(void);
+static void asr_ctx_ref(struct asr_ctx *);
+static void asr_ctx_free(struct asr_ctx *);
+static int asr_ctx_add_searchdomain(struct asr_ctx *, const char *);
+static int asr_ctx_from_file(struct asr_ctx *, const char *);
+static int asr_ctx_from_string(struct asr_ctx *, const char *);
+static int asr_ctx_parse(struct asr_ctx *, const char *);
+static int asr_parse_nameserver(struct sockaddr *, const char *);
+static int asr_ndots(const char *);
+static void pass0(char **, int, struct asr_ctx *);
+static int strsplit(char *, char **, int);
+static void asr_ctx_envopts(struct asr_ctx *);
+static void *__THREAD_NAME(_asr);
+
+static struct asr *_asr = NULL;
+
+#ifndef HAVE_ISSETUGID
+#define issetugid() ((getuid() != geteuid()))
+#endif
+
+/* Allocate and configure an async "resolver". */
+static void *
+_asr_resolver(void)
+{
+	static int	 init = 0;
+	struct asr	*asr;
+
+	if (init == 0) {
+#ifdef DEBUG
+		if (getenv("ASR_DEBUG"))
+			_asr_debug = stderr;
+#endif
+		init = 1;
+	}
+
+	if ((asr = calloc(1, sizeof(*asr))) == NULL)
+		goto fail;
+
+	asr_check_reload(asr);
+	if (asr->a_ctx == NULL) {
+		if ((asr->a_ctx = asr_ctx_create()) == NULL)
+			goto fail;
+		if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1)
+			goto fail;
+		asr_ctx_envopts(asr->a_ctx);
+	}
+
+#ifdef DEBUG
+	_asr_dump_config(_asr_debug, asr);
+#endif
+	return (asr);
+
+    fail:
+	if (asr) {
+		if (asr->a_ctx)
+			asr_ctx_free(asr->a_ctx);
+		free(asr);
+	}
+
+	return (NULL);
+}
+
+/*
+ * Free the "asr" async resolver (or the thread-local resolver if NULL).
+ * Drop the reference to the current context.
+ */
+void
+_asr_resolver_done(void *arg)
+{
+	struct asr_ctx *ac = arg;
+	struct asr *asr;
+	struct asr **priv;
+
+	if (ac) {
+		_asr_ctx_unref(ac);
+		return;
+	} else {
+		priv = _THREAD_PRIVATE_DT(_asr, _asr, NULL, &_asr);
+		if (*priv == NULL)
+			return;
+		asr = *priv;
+		*priv = NULL;
+	}
+
+	_asr_ctx_unref(asr->a_ctx);
+	free(asr);
+}
+
+static void
+_asr_resolver_done_tp(void *arg)
+{
+	struct asr **priv = arg;
+	struct asr *asr;
+
+	if (*priv == NULL)
+		return;
+	asr = *priv;
+
+	_asr_ctx_unref(asr->a_ctx);
+	free(asr);
+	free(priv);
+}
+
+void *
+asr_resolver_from_string(const char *str)
+{
+	struct asr_ctx *ac;
+
+	if ((ac = asr_ctx_create()) == NULL)
+		return NULL;
+
+	if (asr_ctx_from_string(ac, str) == -1) {
+		asr_ctx_free(ac);
+		return NULL;
+	}
+
+	return ac;
+}
+DEF_WEAK(asr_resolver_from_string);
+
+void
+asr_resolver_free(void *arg)
+{
+	_asr_ctx_unref(arg);
+}
+DEF_WEAK(asr_resolver_free);
+
+/*
+ * Cancel an async query.
+ */
+void
+asr_abort(struct asr_query *as)
+{
+	_asr_async_free(as);
+}
+
+/*
+ * Resume the "as" async query resolution.  Return one of ASYNC_COND,
+ * or ASYNC_DONE and put query-specific return values in the user-allocated
+ * memory at "ar".
+ */
+int
+asr_run(struct asr_query *as, struct asr_result *ar)
+{
+	int	r, saved_errno = errno;
+
+	memset(ar, 0, sizeof(*ar));
+
+	DPRINT("asr: asr_run(%p, %p) %s ctx=[%p]\n", as, ar,
+	    _asr_querystr(as->as_type), as->as_ctx);
+	r = as->as_run(as, ar);
+
+	DPRINT("asr: asr_run(%p, %p) -> %s", as, ar, _asr_transitionstr(r));
+#ifdef DEBUG
+	if (r == ASYNC_COND)
+#endif
+		DPRINT(" fd=%i timeout=%i", ar->ar_fd, ar->ar_timeout);
+	DPRINT("\n");
+	if (r == ASYNC_DONE)
+		_asr_async_free(as);
+
+	errno = saved_errno;
+
+	return (r);
+}
+DEF_WEAK(asr_run);
+
+static int
+poll_intrsafe(struct pollfd *fds, nfds_t nfds, int timeout)
+{
+	struct timespec pollstart, pollend, elapsed;
+	int r;
+
+	if (clock_gettime(CLOCK_MONOTONIC, &pollstart))
+		return -1;
+
+	while ((r = poll(fds, 1, timeout)) == -1 && errno == EINTR) {
+		if (clock_gettime(CLOCK_MONOTONIC, &pollend))
+			return -1;
+		timespecsub(&pollend, &pollstart, &elapsed);
+		timeout -= elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000;
+		if (timeout < 1)
+			return 0;
+	}
+
+	return r;
+}
+
+/*
+ * Same as asr_run, but run in a loop that handles the fd conditions result.
+ */
+int
+asr_run_sync(struct asr_query *as, struct asr_result *ar)
+{
+	struct pollfd	 fds[1];
+	int		 r, saved_errno = errno;
+
+	while ((r = asr_run(as, ar)) == ASYNC_COND) {
+		fds[0].fd = ar->ar_fd;
+		fds[0].events = (ar->ar_cond == ASR_WANT_READ) ? POLLIN:POLLOUT;
+
+		if (poll_intrsafe(fds, 1, ar->ar_timeout) == -1) {
+			memset(ar, 0, sizeof(*ar));
+			ar->ar_errno = errno;
+			ar->ar_h_errno = NETDB_INTERNAL;
+			ar->ar_gai_errno = EAI_SYSTEM;
+			ar->ar_rrset_errno = NETDB_INTERNAL;
+			_asr_async_free(as);
+			errno = saved_errno;
+			return ASYNC_DONE;
+		}
+
+		/*
+		 * Otherwise, just ignore the error and let asr_run()
+		 * catch the failure.
+		 */
+	}
+
+	errno = saved_errno;
+
+	return (r);
+}
+DEF_WEAK(asr_run_sync);
+
+/*
+ * Create a new async request of the given "type" on the async context "ac".
+ * Take a reference on it so it does not get deleted while the async query
+ * is running.
+ */
+struct asr_query *
+_asr_async_new(struct asr_ctx *ac, int type)
+{
+	struct asr_query	*as;
+
+	DPRINT("asr: asr_async_new(ctx=%p) type=%i refcount=%i\n", ac, type,
+	    ac ? ac->ac_refcount : 0);
+	if (ac == NULL || (as = calloc(1, sizeof(*as))) == NULL)
+		return (NULL);
+
+	ac->ac_refcount += 1;
+	as->as_ctx = ac;
+	as->as_fd = -1;
+	as->as_type = type;
+	as->as_state = ASR_STATE_INIT;
+
+	return (as);
+}
+
+/*
+ * Free an async query and unref the associated context.
+ */
+void
+_asr_async_free(struct asr_query *as)
+{
+	DPRINT("asr: asr_async_free(%p)\n", as);
+
+	if (as->as_subq)
+		_asr_async_free(as->as_subq);
+
+	switch (as->as_type) {
+	case ASR_SEND:
+		if (as->as_fd != -1)
+			close(as->as_fd);
+		if (as->as.dns.obuf && !(as->as_flags & ASYNC_EXTOBUF))
+			free(as->as.dns.obuf);
+		if (as->as.dns.ibuf)
+			free(as->as.dns.ibuf);
+		if (as->as.dns.dname)
+			free(as->as.dns.dname);
+		break;
+
+	case ASR_SEARCH:
+		if (as->as.search.name)
+			free(as->as.search.name);
+		break;
+
+	case ASR_GETRRSETBYNAME:
+		if (as->as.rrset.name)
+			free(as->as.rrset.name);
+		break;
+
+	case ASR_GETHOSTBYNAME:
+	case ASR_GETHOSTBYADDR:
+		if (as->as.hostnamadr.name)
+			free(as->as.hostnamadr.name);
+		break;
+
+	case ASR_GETADDRINFO:
+		if (as->as.ai.aifirst)
+			freeaddrinfo(as->as.ai.aifirst);
+		if (as->as.ai.hostname)
+			free(as->as.ai.hostname);
+		if (as->as.ai.servname)
+			free(as->as.ai.servname);
+		if (as->as.ai.fqdn)
+			free(as->as.ai.fqdn);
+		break;
+
+	case ASR_GETNAMEINFO:
+		break;
+	}
+
+	_asr_ctx_unref(as->as_ctx);
+	free(as);
+}
+
+/*
+ * Get a context from the given resolver. This takes a new reference to
+ * the returned context, which *must* be explicitly dropped when done
+ * using this context.
+ */
+struct asr_ctx *
+_asr_use_resolver(void *arg)
+{
+	struct asr_ctx *ac = arg;
+	struct asr *asr;
+	struct asr **priv;
+
+	if (ac) {
+		asr_ctx_ref(ac);
+		return ac;
+	}
+	else {
+		DPRINT("using thread-local resolver\n");
+		priv = _THREAD_PRIVATE_DT(_asr, _asr, _asr_resolver_done_tp,
+		    &_asr);
+		if (*priv == NULL) {
+			DPRINT("setting up thread-local resolver\n");
+			*priv = _asr_resolver();
+		}
+		asr = *priv;
+	}
+	if (asr != NULL) {
+		asr_check_reload(asr);
+		asr_ctx_ref(asr->a_ctx);
+		return (asr->a_ctx);
+	}
+	return (NULL);
+}
+
+static void
+asr_ctx_ref(struct asr_ctx *ac)
+{
+	DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount);
+	ac->ac_refcount += 1;
+}
+
+/*
+ * Drop a reference to an async context, freeing it if the reference
+ * count drops to 0.
+ */
+void
+_asr_ctx_unref(struct asr_ctx *ac)
+{
+	DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac,
+	    ac ? ac->ac_refcount : 0);
+	if (ac == NULL)
+		return;
+	if (--ac->ac_refcount)
+		return;
+
+	asr_ctx_free(ac);
+}
+
+static void
+asr_ctx_free(struct asr_ctx *ac)
+{
+	int i;
+
+	if (ac->ac_domain)
+		free(ac->ac_domain);
+	for (i = 0; i < ASR_MAXNS; i++)
+		free(ac->ac_ns[i]);
+	for (i = 0; i < ASR_MAXDOM; i++)
+		free(ac->ac_dom[i]);
+
+	free(ac);
+}
+
+/*
+ * Reload the configuration file if it has changed on disk.
+ */
+static void
+asr_check_reload(struct asr *asr)
+{
+	struct asr_ctx	*ac;
+	struct stat	 st;
+	struct timespec	 ts;
+	pid_t		 pid;
+
+	pid = getpid();
+	if (pid != asr->a_pid) {
+		asr->a_pid = pid;
+		asr->a_rtime = 0;
+	}
+
+	if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
+		return;
+
+	if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0)
+		return;
+	asr->a_rtime = ts.tv_sec;
+
+	DPRINT("asr: checking for update of \"%s\"\n", _PATH_RESCONF);
+	if (stat(_PATH_RESCONF, &st) == -1 ||
+	    asr->a_mtime == st.st_mtime ||
+	    (ac = asr_ctx_create()) == NULL)
+		return;
+	asr->a_mtime = st.st_mtime;
+
+	DPRINT("asr: reloading config file\n");
+	if (asr_ctx_from_file(ac, _PATH_RESCONF) == -1) {
+		asr_ctx_free(ac);
+		return;
+	}
+
+	asr_ctx_envopts(ac);
+	if (asr->a_ctx)
+		_asr_ctx_unref(asr->a_ctx);
+	asr->a_ctx = ac;
+}
+
+/*
+ * Construct a fully-qualified domain name for the given name and domain.
+ * If "name" ends with a '.' it is considered as a FQDN by itself.
+ * Otherwise, the domain, which must be a FQDN, is appended to "name" (it
+ * may have a leading dot which would be ignored). If the domain is null,
+ * then "." is used. Return the length of the constructed FQDN or (0) on
+ * error.
+ */
+size_t
+_asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen)
+{
+	size_t	len;
+
+	if (domain == NULL)
+		domain = ".";
+	else if ((len = strlen(domain)) == 0)
+		return (0);
+	else if (domain[len -1] != '.')
+		return (0);
+
+	len = strlen(name);
+	if (len == 0) {
+		if (strlcpy(buf, domain, buflen) >= buflen)
+			return (0);
+	} else if (name[len - 1] !=  '.') {
+		if (domain[0] == '.')
+			domain += 1;
+		if (strlcpy(buf, name, buflen) >= buflen ||
+		    strlcat(buf, ".", buflen) >= buflen ||
+		    strlcat(buf, domain, buflen) >= buflen)
+			return (0);
+	} else {
+		if (strlcpy(buf, name, buflen) >= buflen)
+			return (0);
+	}
+
+	return (strlen(buf));
+}
+
+/*
+ * Count the dots in a string.
+ */
+static int
+asr_ndots(const char *s)
+{
+	int n;
+
+	for (n = 0; *s; s++)
+		if (*s == '.')
+			n += 1;
+
+	return (n);
+}
+
+/*
+ * Allocate a new empty context.
+ */
+static struct asr_ctx *
+asr_ctx_create(void)
+{
+	struct asr_ctx	*ac;
+
+	if ((ac = calloc(1, sizeof(*ac))) == NULL)
+		return (NULL);
+
+	ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
+	ac->ac_refcount = 1;
+	ac->ac_ndots = 1;
+
+	/*
+	 * opensmtpd-portable knob to prefer IPv6 over IPv4.
+	 *
+	 * See: https://github.com/poolpOrg/OpenSMTPD/issues/182
+	 */
+#ifndef ASR_IPV4_BEFORE_IPV6
+	ac->ac_family[0] = AF_INET6;
+	ac->ac_family[1] = AF_INET;
+#else
+	ac->ac_family[0] = AF_INET;
+	ac->ac_family[1] = AF_INET6;
+#endif
+	ac->ac_family[2] = -1;
+
+	ac->ac_nscount = 0;
+	ac->ac_nstimeout = 5;
+	ac->ac_nsretries = 4;
+
+	return (ac);
+}
+
+struct asr_ctx *
+_asr_no_resolver(void)
+{
+	return asr_ctx_create();
+}
+
+/*
+ * Add a search domain to the async context.
+ */
+static int
+asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain)
+{
+	char buf[MAXDNAME];
+
+	if (ac->ac_domcount == ASR_MAXDOM)
+		return (-1);
+
+	if (_asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0)
+		return (-1);
+
+	if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL)
+		return (0);
+
+	ac->ac_domcount += 1;
+
+	return (1);
+}
+
+static int
+strsplit(char *line, char **tokens, int ntokens)
+{
+	int	ntok;
+	char	*cp, **tp;
+
+	for (cp = line, tp = tokens, ntok = 0;
+	    ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; )
+		if (**tp != '\0') {
+			tp++;
+			ntok++;
+		}
+
+	return (ntok);
+}
+
+/*
+ * Pass on a split config line.
+ */
+static void
+pass0(char **tok, int n, struct asr_ctx *ac)
+{
+	int		 i, j, d;
+	const char	*e;
+	struct sockaddr_storage	ss;
+
+	if (!strcmp(tok[0], "nameserver")) {
+		if (ac->ac_nscount == ASR_MAXNS)
+			return;
+		if (n != 2)
+			return;
+		if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1]))
+			return;
+		if ((ac->ac_ns[ac->ac_nscount] = calloc(1, SS_LEN(&ss))) == NULL)
+			return;
+		memmove(ac->ac_ns[ac->ac_nscount], &ss, SS_LEN(&ss));
+		ac->ac_nscount += 1;
+
+	} else if (!strcmp(tok[0], "domain")) {
+		if (n != 2)
+			return;
+		if (ac->ac_domain)
+			return;
+		ac->ac_domain = strdup(tok[1]);
+
+	} else if (!strcmp(tok[0], "lookup")) {
+		/* ensure that each lookup is only given once */
+		for (i = 1; i < n; i++)
+			for (j = i + 1; j < n; j++)
+				if (!strcmp(tok[i], tok[j]))
+					return;
+		ac->ac_dbcount = 0;
+		for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) {
+			if (!strcmp(tok[i], "yp")) {
+				/* silently deprecated */
+			} else if (!strcmp(tok[i], "bind"))
+				ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS;
+			else if (!strcmp(tok[i], "file"))
+				ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE;
+		}
+	} else if (!strcmp(tok[0], "search")) {
+		/* resolv.conf says the last line wins */
+		for (i = 0; i < ASR_MAXDOM; i++) {
+			free(ac->ac_dom[i]);
+			ac->ac_dom[i] = NULL;
+		}
+		ac->ac_domcount = 0;
+		for (i = 1; i < n; i++)
+			asr_ctx_add_searchdomain(ac, tok[i]);
+
+	} else if (!strcmp(tok[0], "family")) {
+		if (n == 1 || n > 3)
+			return;
+		for (i = 1; i < n; i++)
+			if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6"))
+				return;
+		for (i = 1; i < n; i++)
+			ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \
+			    AF_INET6 : AF_INET;
+		ac->ac_family[i - 1] = -1;
+
+	} else if (!strcmp(tok[0], "options")) {
+		for (i = 1; i < n; i++) {
+			if (!strcmp(tok[i], "tcp"))
+				ac->ac_options |= RES_USEVC;
+			else if (!strcmp(tok[i], "edns0"))
+				ac->ac_options |= RES_USE_EDNS0;
+			else if ((!strncmp(tok[i], "ndots:", 6))) {
+				e = NULL;
+				d = strtonum(tok[i] + 6, 1, 16, &e);
+				if (e == NULL)
+					ac->ac_ndots = d;
+			} else if (!strcmp(tok[i], "trust-ad"))
+#ifdef RES_TRUSTAD
+				ac->ac_options |= RES_TRUSTAD;
+#else
+				/* nop */ ;
+#endif
+		}
+	}
+}
+
+/*
+ * Setup an async context with the config specified in the string "str".
+ */
+static int
+asr_ctx_from_string(struct asr_ctx *ac, const char *str)
+{
+	struct sockaddr_in6	*sin6;
+	struct sockaddr_in	*sin;
+	int			 i, trustad;
+	char			 buf[512], *ch;
+
+	asr_ctx_parse(ac, str);
+
+	if (ac->ac_dbcount == 0) {
+		/* No lookup directive */
+		asr_ctx_parse(ac, DEFAULT_LOOKUP);
+	}
+
+	if (ac->ac_nscount == 0)
+		asr_ctx_parse(ac, "nameserver 127.0.0.1");
+
+	if (ac->ac_domain == NULL)
+		if (gethostname(buf, sizeof buf) == 0) {
+			ch = strchr(buf, '.');
+			if (ch)
+				ac->ac_domain = strdup(ch + 1);
+			else /* Assume root. see resolv.conf(5) */
+				ac->ac_domain = strdup("");
+		}
+
+	/* If no search domain was specified, use the local subdomains */
+	if (ac->ac_domcount == 0)
+		for (ch = ac->ac_domain; ch; ) {
+			asr_ctx_add_searchdomain(ac, ch);
+			ch = strchr(ch, '.');
+			if (ch && asr_ndots(++ch) == 0)
+				break;
+		}
+
+	trustad = 1;
+	for (i = 0; i < ac->ac_nscount && trustad; i++) {
+		switch (ac->ac_ns[i]->sa_family) {
+		case AF_INET:
+			sin = (struct sockaddr_in *)ac->ac_ns[i];
+			if (sin->sin_addr.s_addr != htonl(INADDR_LOOPBACK))
+				trustad = 0;
+			break;
+		case AF_INET6:
+			sin6 = (struct sockaddr_in6 *)ac->ac_ns[i];
+			if (!IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr))
+				trustad = 0;
+			break;
+		default:
+			trustad = 0;
+			break;
+		}
+	}
+	if (trustad)
+#ifdef RES_TRUSTAD
+		ac->ac_options |= RES_TRUSTAD;
+#else
+		/* nop */ ;
+#endif
+
+	return (0);
+}
+
+/*
+ * Setup the "ac" async context from the file at location "path".
+ */
+static int
+asr_ctx_from_file(struct asr_ctx *ac, const char *path)
+{
+	FILE	*cf;
+	char	 buf[4096];
+	ssize_t	 r;
+
+	cf = fopen(path, "re");
+	if (cf == NULL)
+		return (-1);
+
+	r = fread(buf, 1, sizeof buf - 1, cf);
+	if (feof(cf) == 0) {
+		DPRINT("asr: config file too long: \"%s\"\n", path);
+		r = -1;
+	}
+	fclose(cf);
+	if (r == -1)
+		return (-1);
+	buf[r] = '\0';
+
+	return asr_ctx_from_string(ac, buf);
+}
+
+/*
+ * Parse lines in the configuration string. For each one, split it into
+ * tokens and pass them to "pass0" for processing.
+ */
+static int
+asr_ctx_parse(struct asr_ctx *ac, const char *str)
+{
+	size_t		 len;
+	const char	*line;
+	char		 buf[1024];
+	char		*tok[10];
+	int		 ntok;
+
+	line = str;
+	while (*line) {
+		len = strcspn(line, "\n\0");
+		if (len < sizeof buf) {
+			memmove(buf, line, len);
+			buf[len] = '\0';
+		} else
+			buf[0] = '\0';
+		line += len;
+		if (*line == '\n')
+			line++;
+		buf[strcspn(buf, ";#")] = '\0';
+		if ((ntok = strsplit(buf, tok, 10)) == 0)
+			continue;
+
+		pass0(tok, ntok, ac);
+	}
+
+	return (0);
+}
+
+/*
+ * Check for environment variables altering the configuration as described
+ * in resolv.conf(5).  Although not documented there, this feature is disabled
+ * for setuid/setgid programs.
+ */
+static void
+asr_ctx_envopts(struct asr_ctx *ac)
+{
+	char	buf[4096], *e;
+	size_t	s;
+
+	if (issetugid()) {
+		ac->ac_options |= RES_NOALIASES;
+		return;
+	}
+
+	if ((e = getenv("RES_OPTIONS")) != NULL) {
+		strlcpy(buf, "options ", sizeof buf);
+		strlcat(buf, e, sizeof buf);
+		s = strlcat(buf, "\n", sizeof buf);
+		if (s < sizeof buf)
+			asr_ctx_parse(ac, buf);
+	}
+
+	if ((e = getenv("LOCALDOMAIN")) != NULL) {
+		strlcpy(buf, "search ", sizeof buf);
+		strlcat(buf, e, sizeof buf);
+		s = strlcat(buf, "\n", sizeof buf);
+		if (s < sizeof buf)
+			asr_ctx_parse(ac, buf);
+	}
+}
+
+/*
+ * Parse a resolv.conf(5) nameserver string into a sockaddr.
+ */
+static int
+asr_parse_nameserver(struct sockaddr *sa, const char *s)
+{
+	in_port_t	 portno = 53;
+
+	if (_asr_sockaddr_from_str(sa, PF_UNSPEC, s) == -1)
+		return (-1);
+
+	if (sa->sa_family == PF_INET)
+		((struct sockaddr_in *)sa)->sin_port = htons(portno);
+	else if (sa->sa_family == PF_INET6)
+		((struct sockaddr_in6 *)sa)->sin6_port = htons(portno);
+
+	return (0);
+}
+
+/*
+ * Turn a (uncompressed) DNS domain name into a regular nul-terminated string
+ * where labels are separated by dots. The result is put into the "buf" buffer,
+ * truncated if it exceeds "max" chars. The function returns "buf".
+ */
+char *
+_asr_strdname(const char *_dname, char *buf, size_t max)
+{
+	const unsigned char *dname = _dname;
+	char	*res;
+	size_t	 left, count;
+
+	if (_dname[0] == 0) {
+		strlcpy(buf, ".", max);
+		return buf;
+	}
+
+	res = buf;
+	left = max - 1;
+	while (dname[0] && left) {
+		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
+		memmove(buf, dname + 1, count);
+		dname += dname[0] + 1;
+		left -= count;
+		buf += count;
+		if (left) {
+			left -= 1;
+			*buf++ = '.';
+		}
+	}
+	buf[0] = 0;
+
+	return (res);
+}
+
+/*
+ * Read and split the next line from the given namedb file.
+ * Return -1 on error, or put the result in the "tokens" array of
+ * size "ntoken" and returns the number of token on the line.
+ */
+int
+_asr_parse_namedb_line(FILE *file, char **tokens, int ntoken, char *lbuf, size_t sz)
+{
+	size_t	  len;
+	char	 *buf;
+	int	  ntok;
+
+    again:
+	if ((buf = fgetln(file, &len)) == NULL)
+		return (-1);
+
+	if (len >= sz)
+		goto again;
+
+	if (buf[len - 1] == '\n')
+		len--;
+	else {
+		memcpy(lbuf, buf, len);
+		buf = lbuf;
+	}
+
+	buf[len] = '\0';
+	buf[strcspn(buf, "#")] = '\0';
+	if ((ntok = strsplit(buf, tokens, ntoken)) == 0)
+		goto again;
+
+	return (ntok);
+}
+
+/*
+ * Update the async context so that it uses the next configured DB.
+ * Return 0 on success, or -1 if no more DBs is available.
+ */
+int
+_asr_iter_db(struct asr_query *as)
+{
+	if (as->as_db_idx >= as->as_ctx->ac_dbcount) {
+		DPRINT("asr_iter_db: done\n");
+		return (-1);
+	}
+
+	as->as_db_idx += 1;
+	DPRINT("asr_iter_db: %i\n", as->as_db_idx);
+
+	return (0);
+}
blob - /dev/null
blob + e9725e6be7ad1cfb8e4fbdd95c1b468552853553 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/asr.h
@@ -0,0 +1,95 @@
+/*	$OpenBSD: asr.h,v 1.1 2014/03/26 18:13:15 eric Exp $	*/
+/*
+ * Copyright (c) 2012-2014 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Expected fd conditions
+ */
+#define ASR_WANT_READ	1
+#define ASR_WANT_WRITE	2
+
+/*
+ * Structure through which asynchronous query results are returned when
+ * calling asr_run().
+ */
+struct asr_result {
+	/* Fields set if the query is not done yet (asr_run returns 0) */
+	int	 ar_cond;	/* ASR_WANT_READ or ASR_WANT_WRITE */
+	int	 ar_fd;		/* the fd waiting for io condition */
+	int	 ar_timeout;	/* time to wait for in milliseconds */
+
+	/* Error fields.  Depends on the query type. */
+	int	 ar_errno;
+	int	 ar_h_errno;
+	int	 ar_gai_errno;
+	int	 ar_rrset_errno;
+
+	/* Result for res_*_async() calls */
+	int	 ar_count;	/* number of answers in the dns reply */
+	int	 ar_rcode;	/* response code in the dns reply */
+	void	*ar_data;	/* raw reply packet (must be freed) */
+	int	 ar_datalen;	/* reply packet length */
+	struct sockaddr_storage ar_ns; /* nameserver that responded */
+
+	/* Result for other calls. Must be freed properly. */
+	struct addrinfo	 *ar_addrinfo;
+	struct rrsetinfo *ar_rrsetinfo;
+	struct hostent	 *ar_hostent;
+	struct netent	 *ar_netent;
+};
+
+/*
+ * Asynchronous query management.
+ */
+
+/* Forward declaration. The API uses opaque pointers as query handles. */
+struct asr_query;
+
+int asr_run(struct asr_query *, struct asr_result *);
+int asr_run_sync(struct asr_query *, struct asr_result *);
+void asr_abort(struct asr_query *);
+
+/*
+ * Asynchronous version of the resolver functions. Similar prototypes, with
+ * an extra context parameter at the end which must currently be set to NULL.
+ * All functions return a handle suitable for use with the management functions
+ * above.
+ */
+struct asr_query *res_send_async(const unsigned char *, int, void *);
+struct asr_query *res_query_async(const char *, int, int, void *);
+struct asr_query *res_search_async(const char *, int, int, void *);
+
+struct asr_query *getrrsetbyname_async(const char *, unsigned int, unsigned int,
+    unsigned int, void *);
+
+struct asr_query *gethostbyname_async(const char *, void *);
+struct asr_query *gethostbyname2_async(const char *, int, void *);
+struct asr_query *gethostbyaddr_async(const void *, socklen_t, int, void *);
+
+struct asr_query *getnetbyname_async(const char *, void *);
+struct asr_query *getnetbyaddr_async(in_addr_t, int, void *);
+
+struct asr_query *getaddrinfo_async(const char *, const char *,
+    const struct addrinfo *, void *);
+struct asr_query *getnameinfo_async(const struct sockaddr *, socklen_t, char *,
+    size_t, char *, size_t, int, void *);
+
+/* only there for -portable */
+void asr_freeaddrinfo(struct addrinfo *);
+
+/* from in event.h */
+struct event_asr * event_asr_run(struct asr_query *,
+    void (*)(struct asr_result *, void *), void *);
blob - /dev/null
blob + ee9583573aaf336454f2e53e7a5c3c5d95117483 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/asr_compat.c
@@ -0,0 +1,102 @@
+/*	$OpenBSD: asr_debug.c,v 1.25 2018/04/28 15:16:49 schwarze Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+
+#include "asr_compat.h"
+
+#ifndef HAVE___P_CLASS
+const char *
+__p_class(int c)
+{
+	switch(c) {
+	case C_IN:	return "IN";
+	case C_CHAOS:	return "CHAOS";
+	case C_HS:	return "HESIOD";
+	case C_ANY:	return "ANY";
+	default:	return "?";
+	}
+};
+#endif /* !HAVE___P_CLASS */
+
+#ifndef HAVE___P_TYPE
+const char *
+__p_type(int t)
+{
+	switch(t) {
+	case T_A:	return "A";
+	case T_NS:	return "NS";
+	case T_MD:	return "MD";
+	case T_MF:	return "MF";
+	case T_CNAME:	return "CNAME";
+	case T_SOA:	return "SOA";
+	case T_MB:	return "MB";
+	case T_MG:	return "MG";
+	case T_MR:	return "MR";
+	case T_NULL:	return "NULL";
+	case T_WKS:	return "WKS";
+	case T_PTR:	return "PTR";
+	case T_HINFO:	return "HINFO";
+	case T_MINFO:	return "MINFO";
+	case T_MX:	return "MX";
+	case T_TXT:	return "TXT";
+	case T_RP:	return "RP";
+	case T_AFSDB:	return "AFSDB";
+	case T_X25:	return "X25";
+	case T_ISDN:	return "ISDN";
+	case T_RT:	return "RT";
+	case T_NSAP:	return "NSAP";
+	case T_NSAP_PTR:return"NSAP_PTR";
+	case T_SIG:	return "SIG";
+	case T_KEY:	return "KEY";
+	case T_PX:	return "PX";
+	case T_GPOS:	return "GPOS";
+	case T_AAAA:	return "AAAA";
+	case T_LOC:	return "LOC";
+	case T_NXT:	return "NXT";
+	case T_EID:	return "EID";
+	case T_NIMLOC:	return "NIMLOC";
+	case T_SRV:	return "SRV";
+	case T_ATMA:	return "ATMA";
+	case T_OPT:	return "OPT";
+	case T_IXFR:	return "IXFR";
+	case T_AXFR:	return "AXFR";
+	case T_MAILB:	return "MAILB";
+	case T_MAILA:	return "MAILA";
+#ifdef T_UINFO
+	case T_UINFO:	return "UINFO";
+#endif
+#ifdef T_UID
+	case T_UID:	return "UID";
+#endif
+#ifdef T_GID
+	case T_GID:	return "GID";
+#endif
+	case T_NAPTR:	return "NAPTR";
+#ifdef T_UNSPEC
+	case T_UNSPEC:	return "UNSPEC";
+#endif
+	case T_ANY:	return "ANY";
+	default:	return "?";
+	}
+}
+#endif /* !HAVE___P_TYPE */
blob - /dev/null
blob + af17b976512b52cf7e8c901e38447a6d42261204 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/asr_compat.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+/* source compat */
+#define ASR_BUFSIZ	1024
+
+#define DEF_WEAK(x)
+#define __THREAD_NAME(x) __thread_name_ ## x
+
+#ifndef __BEGIN_HIDDEN_DECLS
+#define __BEGIN_HIDDEN_DECLS
+#endif
+#ifndef __END_HIDDEN_DECLS
+#define __END_HIDDEN_DECLS
+#endif
+
+/*
+ * sys/socket.h
+ */
+#ifndef SOCK_DNS
+#define SOCK_DNS 0
+#endif
+
+/*
+ * netdb.h
+ */
+#ifndef NETDB_SUCCESS
+#define NETDB_SUCCESS 0
+#endif
+
+#ifndef NETDB_INTERNAL
+#define NETDB_INTERNAL -1
+#endif
+
+#ifndef AI_FQDN
+#define AI_FQDN AI_CANONNAME
+#endif
+
+#ifndef AI_MASK
+#define AI_MASK \
+    (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_NUMERICSERV | AI_ADDRCONFIG | AI_FQDN)
+#endif
+
+#ifndef SCOPE_DELIMITER
+#define SCOPE_DELIMITER '%'
+#endif
+
+#ifndef _PATH_HOSTS
+#define _PATH_HOSTS "/etc/hosts"
+#endif
+
+#ifndef _PATH_NETWORKS
+#define _PATH_NETWORKS "/etc/networks"
+#endif
+
+#ifndef EAI_BADHINTS
+#define EAI_BADHINTS EAI_FAIL
+#endif
+
+/*
+ * arpa/nameserv.h
+ */
+#ifndef T_OPT
+#define T_OPT 41
+#endif
+
+#ifndef	DNS_MESSAGEEXTFLAG_DO
+#define	DNS_MESSAGEEXTFLAG_DO	0x8000U
+#endif
+
+#ifndef HAVE___P_CLASS
+const char * __p_class(int);
+#endif
+
+#ifndef HAVE___P_TYPE
+const char * __p_type(int);
+#endif
+
+/*
+ * netinet6/in6.h
+ */
+#ifndef IN6_IS_ADDR_MC_INTFACELOCAL
+#define IN6_IS_ADDR_MC_INTFACELOCAL(x) IN6_IS_ADDR_MC_NODELOCAL(x)
+#endif
blob - /dev/null
blob + 1d842a6fa5255be90e3d74fa65d1697b33acafab (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/asr_debug.c
@@ -0,0 +1,368 @@
+/*	$OpenBSD: asr_debug.c,v 1.28 2021/11/22 20:18:27 jca Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <resolv.h>
+#include <string.h>
+
+#include "asr_private.h"
+
+static const char *rcodetostr(uint16_t);
+static const char *print_dname(const char *, char *, size_t);
+static const char *print_header(const struct asr_dns_header *, char *, size_t);
+static const char *print_query(const struct asr_dns_query *, char *, size_t);
+static const char *print_rr(const struct asr_dns_rr *, char *, size_t);
+
+FILE *_asr_debug = NULL;
+
+#define OPCODE_SHIFT	11
+
+static const char *
+rcodetostr(uint16_t v)
+{
+	switch (v) {
+	case NOERROR:	return "NOERROR";
+	case FORMERR:	return "FORMERR";
+	case SERVFAIL:	return "SERVFAIL";
+	case NXDOMAIN:	return "NXDOMAIN";
+	case NOTIMP:	return "NOTIMP";
+	case REFUSED:	return "REFUSED";
+	default:	return "?";
+	}
+}
+
+static const char *
+print_dname(const char *_dname, char *buf, size_t max)
+{
+	return (_asr_strdname(_dname, buf, max));
+}
+
+static const char *
+print_rr(const struct asr_dns_rr *rr, char *buf, size_t max)
+{
+	char	*res;
+	char	 tmp[256];
+	char	 tmp2[256];
+	int	 r;
+
+	res = buf;
+
+	r = snprintf(buf, max, "%s %u %s %s ",
+	    print_dname(rr->rr_dname, tmp, sizeof tmp),
+	    rr->rr_ttl,
+	    __p_class(rr->rr_class),
+	    __p_type(rr->rr_type));
+	if (r < 0 || (size_t)r >= max) {
+		buf[0] = '\0';
+		return (buf);
+	}
+
+	if ((size_t)r >= max)
+		return (buf);
+
+	max -= r;
+	buf += r;
+
+	switch (rr->rr_type) {
+	case T_CNAME:
+		print_dname(rr->rr.cname.cname, buf, max);
+		break;
+	case T_MX:
+		snprintf(buf, max, "%lu %s",
+		    (unsigned long)rr->rr.mx.preference,
+		    print_dname(rr->rr.mx.exchange, tmp, sizeof tmp));
+		break;
+	case T_NS:
+		print_dname(rr->rr.ns.nsname, buf, max);
+		break;
+	case T_PTR:
+		print_dname(rr->rr.ptr.ptrname, buf, max);
+		break;
+	case T_SOA:
+		snprintf(buf, max, "%s %s %lu %lu %lu %lu %lu",
+		    print_dname(rr->rr.soa.mname, tmp, sizeof tmp),
+		    print_dname(rr->rr.soa.rname, tmp2, sizeof tmp2),
+		    (unsigned long)rr->rr.soa.serial,
+		    (unsigned long)rr->rr.soa.refresh,
+		    (unsigned long)rr->rr.soa.retry,
+		    (unsigned long)rr->rr.soa.expire,
+		    (unsigned long)rr->rr.soa.minimum);
+		break;
+	case T_A:
+		if (rr->rr_class != C_IN)
+			goto other;
+		snprintf(buf, max, "%s", inet_ntop(AF_INET,
+		    &rr->rr.in_a.addr, tmp, sizeof tmp));
+		break;
+	case T_AAAA:
+		if (rr->rr_class != C_IN)
+			goto other;
+		snprintf(buf, max, "%s", inet_ntop(AF_INET6,
+		    &rr->rr.in_aaaa.addr6, tmp, sizeof tmp));
+		break;
+	default:
+	other:
+		snprintf(buf, max, "(rdlen=%i)", (int)rr->rr.other.rdlen);
+		break;
+	}
+
+	return (res);
+}
+
+static const char *
+print_query(const struct asr_dns_query *q, char *buf, size_t max)
+{
+	char b[256];
+
+	snprintf(buf, max, "%s	%s %s",
+	    print_dname(q->q_dname, b, sizeof b),
+	    __p_class(q->q_class), __p_type(q->q_type));
+
+	return (buf);
+}
+
+static const char *
+print_header(const struct asr_dns_header *h, char *buf, size_t max)
+{
+	snprintf(buf, max,
+	"id:0x%04x %s op:%i %s %s %s %s z:%i %s %s r:%s qd:%i an:%i ns:%i ar:%i",
+	    ((int)h->id),
+	    (h->flags & QR_MASK) ? "QR":"  ",
+	    (int)(OPCODE(h->flags) >> OPCODE_SHIFT),
+	    (h->flags & AA_MASK) ? "AA":"  ",
+	    (h->flags & TC_MASK) ? "TC":"  ",
+	    (h->flags & RD_MASK) ? "RD":"  ",
+	    (h->flags & RA_MASK) ? "RA":"  ",
+	    (h->flags & Z_MASK),
+	    (h->flags & AD_MASK) ? "AD":"  ",
+	    (h->flags & CD_MASK) ? "CD":"  ",
+	    rcodetostr(RCODE(h->flags)),
+	    h->qdcount, h->ancount, h->nscount, h->arcount);
+
+	return (buf);
+}
+
+void
+_asr_dump_packet(FILE *f, const void *data, size_t len)
+{
+	char			buf[1024];
+	struct asr_unpack	p;
+	struct asr_dns_header	h;
+	struct asr_dns_query	q;
+	struct asr_dns_rr	rr;
+	int			i, an, ns, ar, n;
+
+	if (f == NULL)
+		return;
+
+	_asr_unpack_init(&p, data, len);
+
+	if (_asr_unpack_header(&p, &h) == -1) {
+		fprintf(f, ";; BAD PACKET: %s\n", strerror(p.err));
+		return;
+	}
+
+	fprintf(f, ";; HEADER %s\n", print_header(&h, buf, sizeof buf));
+
+	if (h.qdcount)
+		fprintf(f, ";; QUERY SECTION:\n");
+	for (i = 0; i < h.qdcount; i++) {
+		if (_asr_unpack_query(&p, &q) == -1)
+			goto error;
+		fprintf(f, "%s\n", print_query(&q, buf, sizeof buf));
+	}
+
+	an = 0;
+	ns = an + h.ancount;
+	ar = ns + h.nscount;
+	n = ar + h.arcount;
+
+	for (i = 0; i < n; i++) {
+		if (i == an)
+			fprintf(f, "\n;; ANSWER SECTION:\n");
+		if (i == ns)
+			fprintf(f, "\n;; AUTHORITY SECTION:\n");
+		if (i == ar)
+			fprintf(f, "\n;; ADDITIONAL SECTION:\n");
+
+		if (_asr_unpack_rr(&p, &rr) == -1)
+			goto error;
+		fprintf(f, "%s\n", print_rr(&rr, buf, sizeof buf));
+	}
+
+	if (p.offset != len)
+		fprintf(f, ";; REMAINING GARBAGE %zu\n", len - p.offset);
+
+    error:
+	if (p.err)
+		fprintf(f, ";; ERROR AT OFFSET %zu/%zu: %s\n", p.offset, p.len,
+		    strerror(p.err));
+}
+
+const char *
+_asr_print_sockaddr(const struct sockaddr *sa, char *buf, size_t len)
+{
+	char	h[256];
+	int	portno;
+	union {
+		const struct sockaddr		*sa;
+		const struct sockaddr_in	*sin;
+		const struct sockaddr_in6	*sin6;
+	}	s;
+
+	s.sa = sa;
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		inet_ntop(AF_INET, &s.sin->sin_addr, h, sizeof h);
+		portno = ntohs(s.sin->sin_port);
+		break;
+	case AF_INET6:
+		inet_ntop(AF_INET6, &s.sin6->sin6_addr, h, sizeof h);
+		portno = ntohs(s.sin6->sin6_port);
+		break;
+	default:
+		snprintf(buf, len, "?");
+		return (buf);
+	}
+
+	snprintf(buf, len, "%s:%i", h, portno);
+	return (buf);
+}
+
+void
+_asr_dump_config(FILE *f, struct asr *a)
+{
+	char		 buf[256];
+	int		 i;
+	struct asr_ctx	*ac;
+	unsigned int	 o;
+
+	if (f == NULL)
+		return;
+
+	ac = a->a_ctx;
+
+	fprintf(f, "--------- ASR CONFIG ---------------\n");
+	fprintf(f, "DOMAIN \"%s\"\n", ac->ac_domain);
+	fprintf(f, "SEARCH\n");
+	for (i = 0; i < ac->ac_domcount; i++)
+		fprintf(f, "   \"%s\"\n", ac->ac_dom[i]);
+	fprintf(f, "OPTIONS\n");
+	fprintf(f, " options:");
+	o = ac->ac_options;
+
+#define PRINTOPT(flag, n) if (o & (flag)) { fprintf(f, " " n); o &= ~(flag); }
+	PRINTOPT(RES_INIT, "INIT");
+	PRINTOPT(RES_DEBUG, "DEBUG");
+	PRINTOPT(RES_USEVC, "USEVC");
+	PRINTOPT(RES_IGNTC, "IGNTC");
+	PRINTOPT(RES_RECURSE, "RECURSE");
+	PRINTOPT(RES_DEFNAMES, "DEFNAMES");
+	PRINTOPT(RES_STAYOPEN, "STAYOPEN");
+	PRINTOPT(RES_DNSRCH, "DNSRCH");
+	PRINTOPT(RES_NOALIASES, "NOALIASES");
+	PRINTOPT(RES_USE_EDNS0, "USE_EDNS0");
+	PRINTOPT(RES_USE_DNSSEC, "USE_DNSSEC");
+#ifdef RES_USE_CD
+	PRINTOPT(RES_USE_CD, "USE_CD");
+#endif
+#ifdef RES_TRUSTAD
+	PRINTOPT(RES_TRUSTAD, "TRUSTAD");
+#endif
+	if (o)
+		fprintf(f, " 0x%08x", o);
+	fprintf(f, "\n");
+
+	fprintf(f, " ndots: %i\n", ac->ac_ndots);
+	fprintf(f, " family:");
+	for (i = 0; ac->ac_family[i] != -1; i++)
+		fprintf(f, " %s", (ac->ac_family[i] == AF_INET)?"inet4":"inet6");
+	fprintf(f, "\n");
+	fprintf(f, "NAMESERVERS timeout=%i retry=%i\n",
+		    ac->ac_nstimeout,
+		    ac->ac_nsretries);
+	for (i = 0; i < ac->ac_nscount; i++)
+		fprintf(f, "	%s\n", _asr_print_sockaddr(ac->ac_ns[i], buf,
+		    sizeof buf));
+	fprintf(f, "LOOKUP %s", ac->ac_db);
+	fprintf(f, "\n------------------------------------\n");
+}
+
+#define CASE(n) case n: return #n
+
+const char *
+_asr_statestr(int state)
+{
+	switch (state) {
+	CASE(ASR_STATE_INIT);
+	CASE(ASR_STATE_NEXT_DOMAIN);
+	CASE(ASR_STATE_NEXT_DB);
+	CASE(ASR_STATE_SAME_DB);
+	CASE(ASR_STATE_NEXT_FAMILY);
+	CASE(ASR_STATE_NEXT_NS);
+	CASE(ASR_STATE_UDP_SEND);
+	CASE(ASR_STATE_UDP_RECV);
+	CASE(ASR_STATE_TCP_WRITE);
+	CASE(ASR_STATE_TCP_READ);
+	CASE(ASR_STATE_PACKET);
+	CASE(ASR_STATE_SUBQUERY);
+	CASE(ASR_STATE_NOT_FOUND);
+	CASE(ASR_STATE_HALT);
+	default:
+		return "?";
+	}
+};
+
+const char *
+_asr_querystr(int type)
+{
+	switch (type) {
+	CASE(ASR_SEND);
+	CASE(ASR_SEARCH);
+	CASE(ASR_GETRRSETBYNAME);
+	CASE(ASR_GETHOSTBYNAME);
+	CASE(ASR_GETHOSTBYADDR);
+	CASE(ASR_GETADDRINFO);
+	CASE(ASR_GETNAMEINFO);
+	default:
+		return "?";
+	}
+}
+
+const char *
+_asr_transitionstr(int type)
+{
+	switch (type) {
+	CASE(ASYNC_COND);
+	CASE(ASYNC_DONE);
+	default:
+		return "?";
+	}
+}
blob - /dev/null
blob + 7f551d78d1e2609821b2fe702a90ee670257b1a5 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/asr_private.h
@@ -0,0 +1,361 @@
+/*	$OpenBSD: asr_private.h,v 1.49 2023/11/20 12:15:16 florian Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+
+#include "asr_compat.h"
+
+#define QR_MASK		(0x1 << 15)
+#define OPCODE_MASK	(0xf << 11)
+#define AA_MASK		(0x1 << 10)
+#define TC_MASK		(0x1 <<  9)
+#define RD_MASK		(0x1 <<  8)
+#define RA_MASK		(0x1 <<  7)
+#define Z_MASK		(0x1 <<  6)
+#define AD_MASK		(0x1 <<  5)
+#define CD_MASK		(0x1 <<  4)
+#define RCODE_MASK	(0xf)
+
+#define OPCODE(v)	((v) & OPCODE_MASK)
+#define RCODE(v)	((v) & RCODE_MASK)
+
+
+struct asr_pack {
+	char		*buf;
+	size_t		 len;
+	size_t		 offset;
+	int		 err;
+};
+
+struct asr_unpack {
+	const char	*buf;
+	size_t		 len;
+	size_t		 offset;
+	int		 err;
+};
+
+struct asr_dns_header {
+	uint16_t	id;
+	uint16_t	flags;
+	uint16_t	qdcount;
+	uint16_t	ancount;
+	uint16_t	nscount;
+	uint16_t	arcount;
+};
+
+struct asr_dns_query {
+	char		q_dname[MAXDNAME];
+	uint16_t	q_type;
+	uint16_t	q_class;
+};
+
+struct asr_dns_rr {
+	char		rr_dname[MAXDNAME];
+	uint16_t	rr_type;
+	uint16_t	rr_class;
+	uint32_t	rr_ttl;
+	union {
+		struct {
+			char	cname[MAXDNAME];
+		} cname;
+		struct {
+			uint16_t	preference;
+			char		exchange[MAXDNAME];
+		} mx;
+		struct {
+			char	nsname[MAXDNAME];
+		} ns;
+		struct {
+			char	ptrname[MAXDNAME];
+		} ptr;
+		struct {
+			char		mname[MAXDNAME];
+			char		rname[MAXDNAME];
+			uint32_t	serial;
+			uint32_t	refresh;
+			uint32_t	retry;
+			uint32_t	expire;
+			uint32_t	minimum;
+		} soa;
+		struct {
+			struct in_addr	addr;
+		} in_a;
+		struct {
+			struct in6_addr	addr6;
+		} in_aaaa;
+		struct {
+			uint16_t	 rdlen;
+			const void	*rdata;
+		} other;
+	} rr;
+};
+
+
+#define ASR_MAXNS	5
+#define ASR_MAXDB	3
+#define ASR_MAXDOM	10
+
+enum async_type {
+	ASR_SEND,
+	ASR_SEARCH,
+	ASR_GETRRSETBYNAME,
+	ASR_GETHOSTBYNAME,
+	ASR_GETHOSTBYADDR,
+	ASR_GETADDRINFO,
+	ASR_GETNAMEINFO,
+};
+
+#define	ASR_DB_FILE	'f'
+#define	ASR_DB_DNS	'b'
+
+struct asr_ctx {
+	int		 ac_refcount;
+	int		 ac_options;
+	int		 ac_ndots;
+	char		*ac_domain;
+	int		 ac_domcount;
+	char		*ac_dom[ASR_MAXDOM];
+	int		 ac_dbcount;
+	char		 ac_db[ASR_MAXDB + 1];
+	int		 ac_family[3];
+
+	int		 ac_nscount;
+	int		 ac_nstimeout;
+	int		 ac_nsretries;
+	struct sockaddr *ac_ns[ASR_MAXNS];
+
+};
+
+struct asr {
+	pid_t		 a_pid;
+	time_t		 a_mtime;
+	time_t		 a_rtime;
+	struct asr_ctx	*a_ctx;
+};
+
+#define ASYNC_COND		0
+#define ASYNC_DONE		1
+
+#define	ASYNC_DOM_FQDN		0x00000001
+#define	ASYNC_DOM_NDOTS		0x00000002
+#define	ASYNC_DOM_DOMAIN	0x00000004
+#define ASYNC_DOM_ASIS		0x00000008
+
+#define	ASYNC_NODATA		0x00000100
+#define	ASYNC_AGAIN		0x00000200
+
+#define	ASYNC_GETNET		0x00001000
+#define	ASYNC_EXTOBUF		0x00002000
+
+#define	ASYNC_NO_INET		0x00010000
+#define	ASYNC_NO_INET6		0x00020000
+
+struct asr_query {
+	int		(*as_run)(struct asr_query *, struct asr_result *);
+	struct asr_ctx	*as_ctx;
+	int		 as_type;
+	int		 as_flags;
+	int		 as_state;
+
+	/* cond */
+	int		 as_timeout;
+	int		 as_fd;
+	struct asr_query *as_subq;
+
+	/* loop indices in ctx */
+	int		 as_dom_step;
+	int		 as_dom_idx;
+	int		 as_dom_flags;
+	int		 as_family_idx;
+	int		 as_db_idx;
+
+	int		 as_count;
+
+	union {
+		struct {
+			uint16_t	 reqid;
+			int		 class;
+			int		 type;
+			char		*dname;		/* not fqdn! */
+			int		 rcode;		/* response code */
+			int		 ancount;	/* answer count */
+
+			int		 nsidx;
+			int		 nsloop;
+
+			/* io buffers for query/response */
+			unsigned char	*obuf;
+			size_t		 obuflen;
+			size_t		 obufsize;
+			unsigned char	*ibuf;
+			size_t		 ibuflen;
+			size_t		 ibufsize;
+			size_t		 datalen; /* for tcp io */
+			uint16_t	 pktlen;
+		} dns;
+
+		struct {
+			int		 class;
+			int		 type;
+			char		*name;
+			int		 saved_h_errno;
+		} search;
+
+		struct {
+			int		 flags;
+			int		 class;
+			int		 type;
+			char		*name;
+		} rrset;
+
+		struct {
+			char		*name;
+			int		 family;
+			char		 addr[16];
+			int		 addrlen;
+			int		 subq_h_errno;
+		} hostnamadr;
+
+		struct {
+			char		*hostname;
+			char		*servname;
+			int		 port_tcp;
+			int		 port_udp;
+			union {
+				struct sockaddr		sa;
+				struct sockaddr_in	sain;
+				struct sockaddr_in6	sain6;
+			}		 sa;
+
+			struct addrinfo	 hints;
+			char		*fqdn;
+			struct addrinfo	*aifirst;
+			struct addrinfo	*ailast;
+		} ai;
+
+		struct {
+			char		*hostname;
+			char		*servname;
+			size_t		 hostnamelen;
+			size_t		 servnamelen;
+			union {
+				struct sockaddr		sa;
+				struct sockaddr_in	sain;
+				struct sockaddr_in6	sain6;
+			}		 sa;
+			int		 flags;
+		} ni;
+#define MAXTOKEN 10
+	} as;
+
+};
+
+#define AS_DB(p) ((p)->as_ctx->ac_db[(p)->as_db_idx - 1])
+#define AS_FAMILY(p) ((p)->as_ctx->ac_family[(p)->as_family_idx])
+
+enum asr_state {
+	ASR_STATE_INIT,
+	ASR_STATE_NEXT_DOMAIN,
+	ASR_STATE_NEXT_DB,
+	ASR_STATE_SAME_DB,
+	ASR_STATE_NEXT_FAMILY,
+	ASR_STATE_NEXT_NS,
+	ASR_STATE_UDP_SEND,
+	ASR_STATE_UDP_RECV,
+	ASR_STATE_TCP_WRITE,
+	ASR_STATE_TCP_READ,
+	ASR_STATE_PACKET,
+	ASR_STATE_SUBQUERY,
+	ASR_STATE_NOT_FOUND,
+	ASR_STATE_HALT,
+};
+
+#define MAXPACKETSZ	4096
+
+__BEGIN_HIDDEN_DECLS
+
+/* asr_utils.c */
+void _asr_pack_init(struct asr_pack *, char *, size_t);
+int _asr_pack_header(struct asr_pack *, const struct asr_dns_header *);
+int _asr_pack_query(struct asr_pack *, uint16_t, uint16_t, const char *);
+int _asr_pack_edns0(struct asr_pack *, uint16_t, int);
+void _asr_unpack_init(struct asr_unpack *, const char *, size_t);
+int _asr_unpack_header(struct asr_unpack *, struct asr_dns_header *);
+int _asr_unpack_query(struct asr_unpack *, struct asr_dns_query *);
+int _asr_unpack_rr(struct asr_unpack *, struct asr_dns_rr *);
+int _asr_sockaddr_from_str(struct sockaddr *, int, const char *);
+ssize_t _asr_dname_from_fqdn(const char *, char *, size_t);
+ssize_t _asr_addr_as_fqdn(const char *, int, char *, size_t);
+int hnok_lenient(const char *);
+int _asr_is_localhost(const char*);
+
+/* asr.c */
+void _asr_resolver_done(void *);
+struct asr_ctx *_asr_use_resolver(void *);
+struct asr_ctx *_asr_no_resolver(void);
+void _asr_ctx_unref(struct asr_ctx *);
+struct asr_query *_asr_async_new(struct asr_ctx *, int);
+void _asr_async_free(struct asr_query *);
+size_t _asr_make_fqdn(const char *, const char *, char *, size_t);
+char *_asr_strdname(const char *, char *, size_t);
+int _asr_iter_db(struct asr_query *);
+int _asr_parse_namedb_line(FILE *, char **, int, char *, size_t);
+
+/* *_async.c */
+struct asr_query *_res_query_async_ctx(const char *, int, int, struct asr_ctx *);
+struct asr_query *_res_search_async_ctx(const char *, int, int, struct asr_ctx *);
+struct asr_query *_gethostbyaddr_async_ctx(const void *, socklen_t, int,
+    struct asr_ctx *);
+
+int _asr_iter_domain(struct asr_query *, const char *, char *, size_t);
+
+#ifdef DEBUG
+
+#define DPRINT(...)		do { if(_asr_debug) {		\
+		fprintf(_asr_debug, __VA_ARGS__);		\
+	} } while (0)
+#define DPRINT_PACKET(n, p, s)	do { if(_asr_debug) {		\
+		fprintf(_asr_debug, "----- %s -----\n", n);	\
+		_asr_dump_packet(_asr_debug, (p), (s));		\
+		fprintf(_asr_debug, "--------------\n");		\
+	} } while (0)
+
+#else /* DEBUG */
+
+#define DPRINT(...)
+#define DPRINT_PACKET(...)
+
+#endif /* DEBUG */
+
+const char *_asr_querystr(int);
+const char *_asr_statestr(int);
+const char *_asr_transitionstr(int);
+const char *_asr_print_sockaddr(const struct sockaddr *, char *, size_t);
+void _asr_dump_config(FILE *, struct asr *);
+void _asr_dump_packet(FILE *, const void *, size_t);
+
+extern FILE *_asr_debug;
+
+#define async_set_state(a, s) do {		\
+	DPRINT("asr: [%s@%p] %s -> %s\n",	\
+		_asr_querystr((a)->as_type),	\
+		as,				\
+		_asr_statestr((a)->as_state),	\
+		_asr_statestr((s)));		\
+	(a)->as_state = (s); } while (0)
+
+__END_HIDDEN_DECLS
blob - /dev/null
blob + d9b3282f3b7526bd775c90a862986fd97050115c (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/asr_run.3
@@ -0,0 +1,335 @@
+.\"	$OpenBSD: asr_run.3,v 1.5 2022/03/31 17:27:15 naddy Exp $
+.\"
+.\" Copyright (c) 2012-2014, Eric Faurot <eric@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: March 31 2022 $
+.Dt ASR_RUN 3
+.Os
+.Sh NAME
+.Nm asr_run ,
+.Nm asr_run_sync ,
+.Nm asr_abort ,
+.Nm asr_resolver_from_string ,
+.Nm asr_resolver_free ,
+.Nm res_send_async ,
+.Nm res_query_async ,
+.Nm res_search_async ,
+.Nm getrrsetbyname_async ,
+.Nm gethostbyname_async ,
+.Nm gethostbyname2_async ,
+.Nm gethostbyaddr_async ,
+.Nm getnetbyname_async ,
+.Nm getnetbyaddr_async ,
+.Nm getaddrinfo_async ,
+.Nm getnameinfo_async
+.Nd asynchronous resolver functions
+.Sh SYNOPSIS
+.In sys/types.h
+.In sys/socket.h
+.In netdb.h
+.In asr.h
+.Ft int
+.Fn asr_run "struct asr_query *aq" "struct asr_result *ar"
+.Ft int
+.Fn asr_run_sync "struct asr_query *aq" "struct asr_result *ar"
+.Ft void
+.Fn asr_abort "struct asr_query *aq"
+.Ft void *
+.Fn asr_resolver_from_string "const char *str"
+.Ft void
+.Fn asr_resolver_free "void *asr"
+.Ft struct asr_query *
+.Fn res_send_async "const unsigned char *pkt" "int pktlen" "void *asr"
+.Ft struct asr_query *
+.Fn res_query_async "const char *name" "int class" "int type" "void *asr"
+.Ft struct asr_query *
+.Fn res_search_async "const char *name" "int class" "int type" "void *asr"
+.Ft struct asr_query *
+.Fn getrrsetbyname_async "const char *hostname" "unsigned int rdclass" "unsigned int rdtype" "unsigned int flags" "void *asr"
+.Ft struct asr_query *
+.Fn gethostbyname_async "const char *name" "void *asr"
+.Ft struct asr_query *
+.Fn gethostbyname2_async "const char *name" "int af" "void *asr"
+.Ft struct asr_query *
+.Fn gethostbyaddr_async "const void *addr" "socklen_t len" "int af" "void *asr"
+.Ft struct asr_query *
+.Fn getnetbyname_async "const char *name" "void *asr"
+.Ft struct asr_query *
+.Fn getnetbyaddr_async "in_addr_t net" "int type" "void *asr"
+.Ft struct asr_query *
+.Fn getaddrinfo_async "const char *hostname" "const char *servname" "const struct addrinfo *hints" "void *asr"
+.Ft struct asr_query *
+.Fn getnameinfo_async "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags" "void *asr"
+.Sh DESCRIPTION
+The
+.Nm asr
+functions provide a simple interface for asynchronous address
+resolution and nameserver querying.
+They should be used in place of the classical resolver functions
+of libc when blocking is not desirable.
+.Pp
+The principle of operation is as follows:
+All async requests are made against an
+.Nm asr
+context which basically defines a list of sources to query and a
+strategy to do so.
+The user creates a query through one of the dedicated functions, and
+gets a handle representing the internal query.
+A query is a state-machine that can be run to try to fulfill a
+particular request.
+This is done by calling in a generic API that performs the state
+transitions until it needs to give the control back to the user,
+either because a result is available, or because the next transition
+implies a blocking call (a file descriptor needs to be read from or
+written to).
+The user is responsible for dealing with the situation: either get
+the result, or wait until the fd conditions are met, and then call
+back into the resolving machinery when it is ready to proceed.
+.Pp
+The
+.Fn asr_run
+function drives the resolving process.
+It runs the asynchronous query represented by the
+.Fa aq
+handle until a result is available, or until it cannot continue
+without blocking.
+The results are returned to the user through the
+.Fa ar
+parameter, which must be a valid pointer to user allocated memory.
+.Fa ar
+is defined as:
+.Bd -literal
+struct asr_result {
+
+	/* Fields set if the query is not done yet (asr_run returns 0) */
+	int	 ar_cond;	/* ASR_WANT_READ or ASR_WANT_WRITE */
+	int	 ar_fd;		/* the fd waiting for io condition */
+	int	 ar_timeout;	/* time to wait for in milliseconds */
+
+	/* Error fields.  Depends on the query type. */
+	int	 ar_errno;
+	int	 ar_h_errno;
+	int	 ar_gai_errno;
+	int	 ar_rrset_errno;
+
+	/* Result for res_*_async() calls */
+	int	 ar_count;	/* number of answers in the dns reply */
+	int	 ar_rcode;	/* response code in the dns reply */
+	void	*ar_data;	/* raw reply packet (must be freed) */
+	int	 ar_datalen;	/* reply packet length */
+	struct sockaddr_storage ar_ns; /* nameserver that responded */
+
+	/* Result for other calls. Must be freed properly. */
+	struct addrinfo	 *ar_addrinfo;
+	struct rrsetinfo *ar_rrsetinfo;
+	struct hostent	 *ar_hostent;
+	struct netent	 *ar_netent;
+};
+.Ed
+.Pp
+The function returns one of the following values:
+.Bl -tag -width "0 " -offset indent
+.It 0
+The query cannot be processed further until a specific condition on a
+file descriptor becomes true.
+The following members of the
+.Fa ar
+structure are filled:
+.Pp
+.Bl -tag -width "ar_timeout " -compact
+.It Fa ar_cond
+one of ASR_WANT_READ or ASR_WANT_WRITE,
+.It Fa ar_fd
+the file descriptor waiting for an IO operation,
+.It Fa ar_timeout
+the amount of time to wait for in milliseconds.
+.El
+.Pp
+The caller is expected to call
+.Fn asr_run
+again once the condition holds or the timeout expires.
+.It 1
+The query is completed.
+The members relevant to the actual async query type are set accordingly,
+including error conditions.
+In any case, the query is cleared and its handle is invalidated.
+.El
+.Pp
+Note that although the query itself may fail (the error being properly reported
+in the
+.Fa ar
+structure), the
+.Fn asr_run
+function itself cannot fail and it always preserves errno.
+.Pp
+The
+.Fn asr_run_sync
+function is a wrapper around
+.Fn asr_run
+that handles the read/write conditions, thus falling back to a blocking
+interface.
+It only returns 1.
+It also preserves errno.
+.Pp
+The
+.Fn asr_abort
+function clears a running query.
+It can be called when the query is waiting on a file descriptor.
+Note that a completed query is already cleared when
+.Fn asr_run
+returns, so
+.Fn asr_abort
+must not be called in this case.
+.Pp
+The
+.Fn asr_resolver_from_string
+function constructs an asr context from a string that conforms to the
+.Xr resolv.conf 5
+file format.
+.Fn asr_resolver_free
+frees an asr context obtained from
+.Fn asr_resolver_from_string .
+.Pp
+The remaining functions are used to initiate different kinds of query
+on the
+.Fa asr
+resolver context.
+The specific operational details for each of them are described below.
+All functions return a handle to an internal query, or NULL if they could
+not allocate the necessary resources to initiate the query.
+All other errors (especially invalid parameters) are reported when calling
+.Fn asr_run .
+They usually have the same interface as an existing resolver function, with
+an additional
+.Ar asr
+argument, which specifies the context to use for this request.
+An
+.Ar asr
+argument of NULL will use the default context for the current thread.
+This is constructed from
+.Pa /etc/resolv.conf
+and takes care of reloading the file when it changes.
+.Pp
+The
+.Fn res_send_async ,
+.Fn res_query_async
+and
+.Fn res_search_async
+functions are asynchronous versions of the standard libc resolver routines.
+Their interface is very similar, except that the response buffer is always
+allocated internally.
+The return value is found upon completion in the
+.Fa ar_datalen
+member of the response structure.
+In addition, the
+.Fa ar_ns
+structure contains the address of the DNS server that sent the response,
+.Fa ar_rcode
+contains the code returned by the server in the DNS response packet, and
+.Fa ar_count
+contains the number of answers in the packet.
+If a response is received, it is placed in a newly allocated buffer
+and returned as
+.Fa ar_data
+member.
+This buffer must be freed by the caller.
+On error, the
+.Fa ar_errno
+and
+.Fa ar_h_errno
+members are set accordingly.
+.Pp
+The
+.Fn getrrsetbyname_async
+function is an asynchronous version of
+.Xr getrrsetbyname 3 .
+Upon completion, the return code is found in
+.Fa ar_rrset_errno
+and the address to the newly allocated result set is set in
+.Fa ar_rrsetinfo .
+As for the blocking function, it must be freed by calling
+.Xr freerrset 3 .
+.Pp
+The
+.Fn gethostbyname_async ,
+.Fn gethostbyname2_async
+and
+.Fn gethostbyaddr_async
+functions provide an asynchronous version of the network host entry functions.
+Upon completion,
+.Ar ar_h_errno
+is set and the resulting hostent address, if found, is set
+in the
+.Ar ar_hostent
+field.
+Note that unlike their blocking counterparts, these functions always return a
+pointer to newly allocated memory, which must be released by the caller using
+.Xr free 3 .
+.Pp
+Similarly, the
+.Fn getnetbyname_async
+and
+.Fn getnetbyaddr_async
+functions provide an asynchronous version of the network entry functions.
+Upon completion,
+.Ar ar_h_errno
+is set and the resulting netent address, if found, is set
+in the
+.Ar ar_netent
+field.
+The memory there is also allocated for the request, and it must be freed by
+.Xr free 3 .
+.Pp
+The
+.Fn getaddrinfo_async
+function is an asynchronous version of the
+.Xr getaddrinfo 3
+call.
+It provides a chain of addrinfo structures with all valid combinations of
+socket address for the given
+.Fa hostname ,
+.Fa servname
+and
+.Fa hints .
+Those three parameters have the same meaning as for the blocking counterpart.
+Upon completion the return code is set in
+.Fa ar_gai_errno .
+The
+.Fa ar_errno
+member may also be set.
+On success, the
+.Fa ar_addrinfo
+member points to a newly allocated list of addrinfo.
+This list must be freed with
+.Xr freeaddrinfo 3 .
+.Sh WORKING WITH THREADS
+This implementation of the asynchronous resolver interface is thread-safe
+and lock-free internally, but the following restriction applies:
+Two different threads must not create queries on the same context or
+run queries originating from the same context at the same time.
+If they want to do that, all calls must be protected by a mutex around
+that context.
+.Pp
+It is generally not a problem since the main point of the asynchronous
+resolver is to multiplex queries within a single thread of control,
+so sharing a resolver among threads is not useful.
+.Sh SEE ALSO
+.Xr getaddrinfo 3 ,
+.Xr gethostbyname 3 ,
+.Xr getnameinfo 3 ,
+.Xr getnetbyname 3 ,
+.Xr getrrsetbyname 3 ,
+.Xr res_send 3 ,
+.Xr resolv.conf 5
blob - /dev/null
blob + 40052573b2ac0505b7c59baf7bed58cbe58976f9 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/asr_utils.c
@@ -0,0 +1,625 @@
+/*	$OpenBSD: asr_utils.c,v 1.22 2023/11/20 12:15:16 florian Exp $	*/
+/*
+ * Copyright (c) 2009-2012	Eric Faurot	<eric@faurot.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "openbsd-compat.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <netdb.h>
+
+#include <asr.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asr_private.h"
+
+static int dname_check_label(const char *, size_t);
+static ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *,
+    char *, size_t);
+
+static int unpack_data(struct asr_unpack *, void *, size_t);
+static int unpack_u16(struct asr_unpack *, uint16_t *);
+static int unpack_u32(struct asr_unpack *, uint32_t *);
+static int unpack_inaddr(struct asr_unpack *, struct in_addr *);
+static int unpack_in6addr(struct asr_unpack *, struct in6_addr *);
+static int unpack_dname(struct asr_unpack *, char *, size_t);
+
+static int pack_data(struct asr_pack *, const void *, size_t);
+static int pack_u16(struct asr_pack *, uint16_t);
+static int pack_dname(struct asr_pack *, const char *);
+
+static int
+dname_check_label(const char *s, size_t l)
+{
+	if (l == 0 || l > 63)
+		return (-1);
+
+	return (0);
+}
+
+ssize_t
+_asr_dname_from_fqdn(const char *str, char *dst, size_t max)
+{
+	ssize_t	 res;
+	size_t	 l, n;
+	char	*d;
+
+	res = 0;
+
+	/* special case: the root domain */
+	if (str[0] == '.') {
+		if (str[1] != '\0')
+			return (-1);
+		if (dst && max >= 1)
+			*dst = '\0';
+		return (1);
+	}
+
+	for (; *str; str = d + 1) {
+
+		d = strchr(str, '.');
+		if (d == NULL || d == str)
+			return (-1);
+
+		l = (d - str);
+
+		if (dname_check_label(str, l) == -1)
+			return (-1);
+
+		res += l + 1;
+
+		if (dst) {
+			*dst++ = l;
+			max -= 1;
+			n = (l > max) ? max : l;
+			memmove(dst, str, n);
+			max -= n;
+			if (max == 0)
+				dst = NULL;
+			else
+				dst += n;
+		}
+	}
+
+	if (dst)
+		*dst++ = '\0';
+
+	return (res + 1);
+}
+
+static ssize_t
+dname_expand(const unsigned char *data, size_t len, size_t offset,
+    size_t *newoffset, char *dst, size_t max)
+{
+	size_t		 n, count, end, ptr, start;
+	ssize_t		 res;
+
+	if (offset >= len)
+		return (-1);
+
+	res = 0;
+	end = start = offset;
+
+	for (; (n = data[offset]); ) {
+		if ((n & 0xc0) == 0xc0) {
+			if (offset + 1 >= len)
+				return (-1);
+			ptr = 256 * (n & ~0xc0) + data[offset + 1];
+			if (ptr >= start)
+				return (-1);
+			if (end < offset + 2)
+				end = offset + 2;
+			offset = start = ptr;
+			continue;
+		}
+		if (offset + n + 1 >= len)
+			return (-1);
+
+		if (dname_check_label(data + offset + 1, n) == -1)
+			return (-1);
+
+		/* copy n + at offset+1 */
+		if (dst != NULL && max != 0) {
+			count = (max < n + 1) ? (max) : (n + 1);
+			memmove(dst, data + offset, count);
+			dst += count;
+			max -= count;
+		}
+		res += n + 1;
+		offset += n + 1;
+		if (end < offset)
+			end = offset;
+	}
+	if (end < offset + 1)
+		end = offset + 1;
+
+	if (dst != NULL && max != 0)
+		dst[0] = 0;
+	if (newoffset)
+		*newoffset = end;
+	return (res + 1);
+}
+
+void
+_asr_pack_init(struct asr_pack *pack, char *buf, size_t len)
+{
+	pack->buf = buf;
+	pack->len = len;
+	pack->offset = 0;
+	pack->err = 0;
+}
+
+void
+_asr_unpack_init(struct asr_unpack *unpack, const char *buf, size_t len)
+{
+	unpack->buf = buf;
+	unpack->len = len;
+	unpack->offset = 0;
+	unpack->err = 0;
+}
+
+static int
+unpack_data(struct asr_unpack *p, void *data, size_t len)
+{
+	if (p->err)
+		return (-1);
+
+	if (p->len - p->offset < len) {
+		p->err = EOVERFLOW;
+		return (-1);
+	}
+
+	memmove(data, p->buf + p->offset, len);
+	p->offset += len;
+
+	return (0);
+}
+
+static int
+unpack_u16(struct asr_unpack *p, uint16_t *u16)
+{
+	if (unpack_data(p, u16, 2) == -1)
+		return (-1);
+
+	*u16 = ntohs(*u16);
+
+	return (0);
+}
+
+static int
+unpack_u32(struct asr_unpack *p, uint32_t *u32)
+{
+	if (unpack_data(p, u32, 4) == -1)
+		return (-1);
+
+	*u32 = ntohl(*u32);
+
+	return (0);
+}
+
+static int
+unpack_inaddr(struct asr_unpack *p, struct in_addr *a)
+{
+	return (unpack_data(p, a, 4));
+}
+
+static int
+unpack_in6addr(struct asr_unpack *p, struct in6_addr *a6)
+{
+	return (unpack_data(p, a6, 16));
+}
+
+static int
+unpack_dname(struct asr_unpack *p, char *dst, size_t max)
+{
+	ssize_t e;
+
+	if (p->err)
+		return (-1);
+
+	e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max);
+	if (e == -1) {
+		p->err = EINVAL;
+		return (-1);
+	}
+	if (e < 0 || e > MAXDNAME) {
+		p->err = ERANGE;
+		return (-1);
+	}
+
+	return (0);
+}
+
+int
+_asr_unpack_header(struct asr_unpack *p, struct asr_dns_header *h)
+{
+	if (unpack_data(p, h, HFIXEDSZ) == -1)
+		return (-1);
+
+	h->flags = ntohs(h->flags);
+	h->qdcount = ntohs(h->qdcount);
+	h->ancount = ntohs(h->ancount);
+	h->nscount = ntohs(h->nscount);
+	h->arcount = ntohs(h->arcount);
+
+	return (0);
+}
+
+int
+_asr_unpack_query(struct asr_unpack *p, struct asr_dns_query *q)
+{
+	unpack_dname(p, q->q_dname, sizeof(q->q_dname));
+	unpack_u16(p, &q->q_type);
+	unpack_u16(p, &q->q_class);
+
+	return (p->err) ? (-1) : (0);
+}
+
+int
+_asr_unpack_rr(struct asr_unpack *p, struct asr_dns_rr *rr)
+{
+	uint16_t	rdlen;
+	size_t		save_offset;
+
+	unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname));
+	unpack_u16(p, &rr->rr_type);
+	unpack_u16(p, &rr->rr_class);
+	unpack_u32(p, &rr->rr_ttl);
+	unpack_u16(p, &rdlen);
+
+	if (p->err)
+		return (-1);
+
+	if (p->len - p->offset < rdlen) {
+		p->err = EOVERFLOW;
+		return (-1);
+	}
+
+	save_offset = p->offset;
+
+	switch (rr->rr_type) {
+
+	case T_CNAME:
+		unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname));
+		break;
+
+	case T_MX:
+		unpack_u16(p, &rr->rr.mx.preference);
+		unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange));
+		break;
+
+	case T_NS:
+		unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname));
+		break;
+
+	case T_PTR:
+		unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname));
+		break;
+
+	case T_SOA:
+		unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname));
+		unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname));
+		unpack_u32(p, &rr->rr.soa.serial);
+		unpack_u32(p, &rr->rr.soa.refresh);
+		unpack_u32(p, &rr->rr.soa.retry);
+		unpack_u32(p, &rr->rr.soa.expire);
+		unpack_u32(p, &rr->rr.soa.minimum);
+		break;
+
+	case T_A:
+		if (rr->rr_class != C_IN)
+			goto other;
+		unpack_inaddr(p, &rr->rr.in_a.addr);
+		break;
+
+	case T_AAAA:
+		if (rr->rr_class != C_IN)
+			goto other;
+		unpack_in6addr(p, &rr->rr.in_aaaa.addr6);
+		break;
+	default:
+	other:
+		rr->rr.other.rdata = p->buf + p->offset;
+		rr->rr.other.rdlen = rdlen;
+		p->offset += rdlen;
+	}
+
+	if (p->err)
+		return (-1);
+
+	/* make sure that the advertised rdlen is really ok */
+	if (p->offset - save_offset != rdlen)
+		p->err = EINVAL;
+
+	return (p->err) ? (-1) : (0);
+}
+
+static int
+pack_data(struct asr_pack *p, const void *data, size_t len)
+{
+	if (p->err)
+		return (-1);
+
+	if (p->len < p->offset + len) {
+		p->err = EOVERFLOW;
+		return (-1);
+	}
+
+	memmove(p->buf + p->offset, data, len);
+	p->offset += len;
+
+	return (0);
+}
+
+static int
+pack_u16(struct asr_pack *p, uint16_t v)
+{
+	v = htons(v);
+
+	return (pack_data(p, &v, 2));
+}
+
+static int
+pack_dname(struct asr_pack *p, const char *dname)
+{
+	/* dname compression would be nice to have here.
+	 * need additional context.
+	 */
+	return (pack_data(p, dname, strlen(dname) + 1));
+}
+
+int
+_asr_pack_header(struct asr_pack *p, const struct asr_dns_header *h)
+{
+	struct asr_dns_header c;
+
+	c.id = h->id;
+	c.flags = htons(h->flags);
+	c.qdcount = htons(h->qdcount);
+	c.ancount = htons(h->ancount);
+	c.nscount = htons(h->nscount);
+	c.arcount = htons(h->arcount);
+
+	return (pack_data(p, &c, HFIXEDSZ));
+}
+
+int
+_asr_pack_query(struct asr_pack *p, uint16_t type, uint16_t class, const char *dname)
+{
+	pack_dname(p, dname);
+	pack_u16(p, type);
+	pack_u16(p, class);
+
+	return (p->err) ? (-1) : (0);
+}
+
+int
+_asr_pack_edns0(struct asr_pack *p, uint16_t pktsz, int dnssec_do)
+{
+	DPRINT("asr EDNS0 pktsz:%hu dnssec:%s\n", pktsz,
+	    dnssec_do ? "yes" : "no");
+
+	pack_dname(p, "");	/* root */
+	pack_u16(p, T_OPT);	/* OPT */
+	pack_u16(p, pktsz);	/* UDP payload size */
+
+	/* extended RCODE and flags */
+	pack_u16(p, 0);
+	pack_u16(p, dnssec_do ? DNS_MESSAGEEXTFLAG_DO : 0);
+
+	pack_u16(p, 0);		/* RDATA len */
+
+	return (p->err) ? (-1) : (0);
+}
+
+int
+_asr_sockaddr_from_str(struct sockaddr *sa, int family, const char *str)
+{
+	struct in_addr		 ina;
+	struct in6_addr		 in6a;
+	struct sockaddr_in	*sin;
+	struct sockaddr_in6	*sin6;
+	char			*cp, *str2;
+	const char		*errstr;
+
+	switch (family) {
+	case PF_UNSPEC:
+		if (_asr_sockaddr_from_str(sa, PF_INET, str) == 0)
+			return (0);
+		return _asr_sockaddr_from_str(sa, PF_INET6, str);
+
+	case PF_INET:
+		if (inet_pton(PF_INET, str, &ina) != 1)
+			return (-1);
+
+		sin = (struct sockaddr_in *)sa;
+		memset(sin, 0, sizeof *sin);
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+		sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+		sin->sin_family = PF_INET;
+		sin->sin_addr.s_addr = ina.s_addr;
+		return (0);
+
+	case PF_INET6:
+		cp = strchr(str, SCOPE_DELIMITER);
+		if (cp) {
+			str2 = strdup(str);
+			if (str2 == NULL)
+				return (-1);
+			str2[cp - str] = '\0';
+			if (inet_pton(PF_INET6, str2, &in6a) != 1) {
+				free(str2);
+				return (-1);
+			}
+			cp++;
+			free(str2);
+		} else if (inet_pton(PF_INET6, str, &in6a) != 1)
+			return (-1);
+
+		sin6 = (struct sockaddr_in6 *)sa;
+		memset(sin6, 0, sizeof *sin6);
+#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN
+		sin6->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+		sin6->sin6_family = PF_INET6;
+		sin6->sin6_addr = in6a;
+
+		if (cp == NULL)
+			return (0);
+
+		if (IN6_IS_ADDR_LINKLOCAL(&in6a) ||
+		    IN6_IS_ADDR_MC_LINKLOCAL(&in6a) ||
+		    IN6_IS_ADDR_MC_INTFACELOCAL(&in6a))
+			if ((sin6->sin6_scope_id = if_nametoindex(cp)))
+				return (0);
+
+		sin6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr);
+		if (errstr)
+			return (-1);
+		return (0);
+
+	default:
+		break;
+	}
+
+	return (-1);
+}
+
+ssize_t
+_asr_addr_as_fqdn(const char *addr, int family, char *dst, size_t max)
+{
+	const struct in6_addr	*in6_addr;
+	in_addr_t		 in_addr;
+
+	switch (family) {
+	case AF_INET:
+		in_addr = ntohl(*((const in_addr_t *)addr));
+		snprintf(dst, max,
+		    "%d.%d.%d.%d.in-addr.arpa.",
+		    in_addr & 0xff,
+		    (in_addr >> 8) & 0xff,
+		    (in_addr >> 16) & 0xff,
+		    (in_addr >> 24) & 0xff);
+		break;
+	case AF_INET6:
+		in6_addr = (const struct in6_addr *)addr;
+		snprintf(dst, max,
+		    "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x."
+		    "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x."
+		    "ip6.arpa.",
+		    in6_addr->s6_addr[15] & 0xf,
+		    (in6_addr->s6_addr[15] >> 4) & 0xf,
+		    in6_addr->s6_addr[14] & 0xf,
+		    (in6_addr->s6_addr[14] >> 4) & 0xf,
+		    in6_addr->s6_addr[13] & 0xf,
+		    (in6_addr->s6_addr[13] >> 4) & 0xf,
+		    in6_addr->s6_addr[12] & 0xf,
+		    (in6_addr->s6_addr[12] >> 4) & 0xf,
+		    in6_addr->s6_addr[11] & 0xf,
+		    (in6_addr->s6_addr[11] >> 4) & 0xf,
+		    in6_addr->s6_addr[10] & 0xf,
+		    (in6_addr->s6_addr[10] >> 4) & 0xf,
+		    in6_addr->s6_addr[9] & 0xf,
+		    (in6_addr->s6_addr[9] >> 4) & 0xf,
+		    in6_addr->s6_addr[8] & 0xf,
+		    (in6_addr->s6_addr[8] >> 4) & 0xf,
+		    in6_addr->s6_addr[7] & 0xf,
+		    (in6_addr->s6_addr[7] >> 4) & 0xf,
+		    in6_addr->s6_addr[6] & 0xf,
+		    (in6_addr->s6_addr[6] >> 4) & 0xf,
+		    in6_addr->s6_addr[5] & 0xf,
+		    (in6_addr->s6_addr[5] >> 4) & 0xf,
+		    in6_addr->s6_addr[4] & 0xf,
+		    (in6_addr->s6_addr[4] >> 4) & 0xf,
+		    in6_addr->s6_addr[3] & 0xf,
+		    (in6_addr->s6_addr[3] >> 4) & 0xf,
+		    in6_addr->s6_addr[2] & 0xf,
+		    (in6_addr->s6_addr[2] >> 4) & 0xf,
+		    in6_addr->s6_addr[1] & 0xf,
+		    (in6_addr->s6_addr[1] >> 4) & 0xf,
+		    in6_addr->s6_addr[0] & 0xf,
+		    (in6_addr->s6_addr[0] >> 4) & 0xf);
+		break;
+	default:
+		return (-1);
+	}
+	return (0);
+}
+
+int
+hnok_lenient(const char *dn)
+{
+	int pch = '\0', ch = *dn++;
+
+	while (ch != '\0') {
+		/* can't start with . or - */
+		if (pch == '\0' && (ch == '.' || ch == '-'))
+			return 0;
+		if (pch == '.' && ch == '.')
+			return 0;
+		if (!(isalpha((unsigned char)ch) || isdigit((unsigned char)ch) ||
+		    ch == '.' || ch == '-' || ch == '_'))
+			return 0;
+		pch = ch; ch = *dn++;
+	}
+	return 1;
+}
+
+/* Check if the hostname is localhost or if it's in the localhost domain */
+int
+_asr_is_localhost(const char *hn)
+{
+	size_t	 hnlen, localhostlen;
+
+	if (hn == NULL)
+		return 0;
+
+	if (strcasecmp(hn, "localhost") == 0 ||
+	    strcasecmp(hn, "localhost.") == 0)
+		return 1;
+
+	hnlen = strlen(hn);
+	localhostlen = strlen(".localhost");
+
+	if (hnlen < localhostlen)
+		return 0;
+
+	if (strcasecmp(hn + hnlen - localhostlen, ".localhost") == 0)
+		return 1;
+
+	localhostlen++;
+	if (hnlen < localhostlen)
+		return 0;
+
+	if (strcasecmp(hn + hnlen - localhostlen, ".localhost.") == 0)
+		return 1;
+
+	return 0;
+}
blob - /dev/null
blob + 37fe2d4d4493b4bafb263693b81676e062f977c6 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/getaddrinfo.c
@@ -0,0 +1,55 @@
+/*	$OpenBSD: getaddrinfo.c,v 1.9 2015/10/08 14:08:44 eric Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+
+int
+getaddrinfo(const char *hostname, const char *servname,
+    const struct addrinfo *hints, struct addrinfo **res)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+	int		 saved_errno = errno;
+
+	if (hints == NULL || (hints->ai_flags & AI_NUMERICHOST) == 0)
+		res_init();
+
+	as = getaddrinfo_async(hostname, servname, hints, NULL);
+	if (as == NULL) {
+		if (errno == ENOMEM) {
+			errno = saved_errno;
+			return (EAI_MEMORY);
+		}
+		return (EAI_SYSTEM);
+	}
+
+	asr_run_sync(as, &ar);
+
+	*res = ar.ar_addrinfo;
+	if (ar.ar_gai_errno == EAI_SYSTEM)
+		errno = ar.ar_errno;
+
+	return (ar.ar_gai_errno);
+}
+DEF_WEAK(getaddrinfo);
blob - /dev/null
blob + d32707cb82152a6178aeb1a382d182437d9b1c4b (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/getaddrinfo_async.c
@@ -0,0 +1,771 @@
+/*	$OpenBSD: getaddrinfo_async.c,v 1.62 2024/01/15 18:03:39 florian Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <net/if.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "asr_private.h"
+
+struct match {
+	int family;
+	int socktype;
+	int protocol;
+};
+
+static int getaddrinfo_async_run(struct asr_query *, struct asr_result *);
+static int get_port(const char *, const char *, int);
+static int iter_family(struct asr_query *, int);
+static int addrinfo_add(struct asr_query *, const struct sockaddr *, const char *);
+static int addrinfo_from_file(struct asr_query *, int,  FILE *);
+static int addrinfo_from_pkt(struct asr_query *, char *, size_t);
+static int addrconfig_setup(struct asr_query *);
+
+static const struct match matches[] = {
+	{ PF_INET,	SOCK_DGRAM,	IPPROTO_UDP	},
+	{ PF_INET,	SOCK_STREAM,	IPPROTO_TCP	},
+	{ PF_INET,	SOCK_RAW,	0		},
+	{ PF_INET6,	SOCK_DGRAM,	IPPROTO_UDP	},
+	{ PF_INET6,	SOCK_STREAM,	IPPROTO_TCP	},
+	{ PF_INET6,	SOCK_RAW,	0		},
+	{ -1,		0,		0,		},
+};
+
+#define MATCH_FAMILY(a, b) ((a) == matches[(b)].family || (a) == PF_UNSPEC)
+#define MATCH_PROTO(a, b) ((a) == matches[(b)].protocol || (a) == 0 || matches[(b)].protocol == 0)
+/* Do not match SOCK_RAW unless explicitly specified */
+#define MATCH_SOCKTYPE(a, b) ((a) == matches[(b)].socktype || ((a) == 0 && \
+				matches[(b)].socktype != SOCK_RAW))
+
+enum {
+	DOM_INIT,
+	DOM_DOMAIN,
+	DOM_DONE
+};
+
+struct asr_query *
+getaddrinfo_async(const char *hostname, const char *servname,
+	const struct addrinfo *hints, void *asr)
+{
+	struct asr_ctx		*ac;
+	struct asr_query	*as;
+
+	if (hints == NULL || (hints->ai_flags & AI_NUMERICHOST) == 0)
+		ac = _asr_use_resolver(asr);
+	else
+		ac = _asr_no_resolver();
+	if ((as = _asr_async_new(ac, ASR_GETADDRINFO)) == NULL)
+		goto abort; /* errno set */
+	as->as_run = getaddrinfo_async_run;
+
+	if (hostname) {
+		if ((as->as.ai.hostname = strdup(hostname)) == NULL)
+			goto abort; /* errno set */
+	}
+	if (servname && (as->as.ai.servname = strdup(servname)) == NULL)
+		goto abort; /* errno set */
+	if (hints)
+		memmove(&as->as.ai.hints, hints, sizeof *hints);
+	else {
+		memset(&as->as.ai.hints, 0, sizeof as->as.ai.hints);
+		as->as.ai.hints.ai_family = PF_UNSPEC;
+		as->as.ai.hints.ai_flags = AI_ADDRCONFIG;
+	}
+
+	_asr_ctx_unref(ac);
+	return (as);
+    abort:
+	if (as)
+		_asr_async_free(as);
+	_asr_ctx_unref(ac);
+	return (NULL);
+}
+DEF_WEAK(getaddrinfo_async);
+
+static int
+getaddrinfo_async_run(struct asr_query *as, struct asr_result *ar)
+{
+	char		 fqdn[MAXDNAME];
+	const char	*str;
+	struct addrinfo	*ai;
+	int		 i, family, r, is_localhost = 0;
+	FILE		*f;
+	union {
+		struct sockaddr		sa;
+		struct sockaddr_in	sain;
+		struct sockaddr_in6	sain6;
+	} sa;
+
+    next:
+	switch (as->as_state) {
+
+	case ASR_STATE_INIT:
+
+		/*
+		 * First, make sure the parameters are valid.
+		 */
+
+		as->as_count = 0;
+
+		if (as->as.ai.hostname == NULL &&
+		    as->as.ai.servname == NULL) {
+			ar->ar_gai_errno = EAI_NONAME;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (as->as.ai.hostname && as->as.ai.hostname[0] == '\0') {
+			ar->ar_gai_errno = EAI_NODATA;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+		
+		ai = &as->as.ai.hints;
+
+		if (ai->ai_addrlen ||
+		    ai->ai_canonname ||
+		    ai->ai_addr ||
+		    ai->ai_next) {
+			ar->ar_gai_errno = EAI_BADHINTS;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (ai->ai_flags & ~AI_MASK ||
+		    (ai->ai_flags & AI_CANONNAME && ai->ai_flags & AI_FQDN)) {
+			ar->ar_gai_errno = EAI_BADFLAGS;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (ai->ai_family != PF_UNSPEC &&
+		    ai->ai_family != PF_INET &&
+		    ai->ai_family != PF_INET6) {
+			ar->ar_gai_errno = EAI_FAMILY;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (ai->ai_socktype &&
+		    ai->ai_socktype != SOCK_DGRAM  &&
+		    ai->ai_socktype != SOCK_STREAM &&
+		    ai->ai_socktype != SOCK_RAW) {
+			ar->ar_gai_errno = EAI_SOCKTYPE;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (ai->ai_socktype == SOCK_RAW &&
+		    get_port(as->as.ai.servname, NULL, 1) != 0) {
+			ar->ar_gai_errno = EAI_SERVICE;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/* Restrict result set to configured address families */
+		if (ai->ai_flags & AI_ADDRCONFIG) {
+			if (addrconfig_setup(as) == -1) {
+				ar->ar_errno = errno;
+				ar->ar_gai_errno = EAI_SYSTEM;
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+		}
+
+		/* Make sure there is at least a valid combination */
+		for (i = 0; matches[i].family != -1; i++)
+			if (MATCH_FAMILY(ai->ai_family, i) &&
+			    MATCH_SOCKTYPE(ai->ai_socktype, i) &&
+			    MATCH_PROTO(ai->ai_protocol, i))
+				break;
+		if (matches[i].family == -1) {
+			ar->ar_gai_errno = EAI_BADHINTS;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_UDP)
+			as->as.ai.port_udp = get_port(as->as.ai.servname, "udp",
+			    as->as.ai.hints.ai_flags & AI_NUMERICSERV);
+		if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_TCP)
+			as->as.ai.port_tcp = get_port(as->as.ai.servname, "tcp",
+			    as->as.ai.hints.ai_flags & AI_NUMERICSERV);
+		if (as->as.ai.port_tcp == -2 || as->as.ai.port_udp == -2 ||
+		    (as->as.ai.port_tcp == -1 && as->as.ai.port_udp == -1) ||
+		    (ai->ai_protocol && (as->as.ai.port_udp == -1 ||
+					 as->as.ai.port_tcp == -1))) {
+			ar->ar_gai_errno = EAI_SERVICE;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		ar->ar_gai_errno = 0;
+
+		if (!(ai->ai_flags & AI_NUMERICHOST))
+			is_localhost = _asr_is_localhost(as->as.ai.hostname);
+		/*
+		 * If hostname is NULL, "localhost" or falls within the
+		 * ".localhost." domain, use local address.
+		 * RFC 6761, 6.3:
+		 * 3. Name resolution APIs and libraries SHOULD recognize
+		 * localhost names as special and SHOULD always return the IP
+		 * loopback address for address queries and negative responses
+		 * for all other query types.  Name resolution APIs SHOULD NOT
+		 * send queries for localhost names to their configured caching
+		 * DNS server(s).
+		 */
+		if (as->as.ai.hostname == NULL || is_localhost) {
+			for (family = iter_family(as, 1);
+			    family != -1;
+			    family = iter_family(as, 0)) {
+				/*
+				 * We could use statically built sockaddrs for
+				 * those, rather than parsing over and over.
+				 */
+				if (family == PF_INET)
+					str = (ai->ai_flags & AI_PASSIVE &&
+					    !is_localhost) ? "0.0.0.0" :
+					    "127.0.0.1";
+				else /* PF_INET6 */
+					str = (ai->ai_flags & AI_PASSIVE &&
+					    !is_localhost) ? "::" : "::1";
+				 /* This can't fail */
+				_asr_sockaddr_from_str(&sa.sa, family, str);
+				if ((r = addrinfo_add(as, &sa.sa,
+				    "localhost."))) {
+					ar->ar_gai_errno = r;
+					break;
+				}
+			}
+			if (ar->ar_gai_errno == 0 && as->as_count == 0) {
+				ar->ar_gai_errno = EAI_NODATA;
+			}
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/* Try numeric addresses first */
+		for (family = iter_family(as, 1);
+		    family != -1;
+		    family = iter_family(as, 0)) {
+
+			if (_asr_sockaddr_from_str(&sa.sa, family,
+			    as->as.ai.hostname) == -1)
+				continue;
+
+			if ((r = addrinfo_add(as, &sa.sa, NULL)))
+				ar->ar_gai_errno = r;
+			break;
+		}
+		if (ar->ar_gai_errno || as->as_count) {
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (ai->ai_flags & AI_NUMERICHOST) {
+			ar->ar_gai_errno = EAI_NONAME;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/* make sure there are no funny characters in hostname */
+		if (!hnok_lenient(as->as.ai.hostname)) {
+			ar->ar_gai_errno = EAI_FAIL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		async_set_state(as, ASR_STATE_NEXT_DB);
+		break;
+
+	case ASR_STATE_NEXT_DB:
+		if (_asr_iter_db(as) == -1) {
+			async_set_state(as, ASR_STATE_NOT_FOUND);
+			break;
+		}
+		as->as_family_idx = 0;
+		async_set_state(as, ASR_STATE_SAME_DB);
+		break;
+
+	case ASR_STATE_NEXT_FAMILY:
+		as->as_family_idx += 1;
+		if (as->as.ai.hints.ai_family != AF_UNSPEC ||
+		    AS_FAMILY(as) == -1) {
+			/* The family was specified, or we have tried all
+			 * families with this DB.
+			 */
+			if (as->as_count) {
+				ar->ar_gai_errno = 0;
+				async_set_state(as, ASR_STATE_HALT);
+			} else
+				async_set_state(as, ASR_STATE_NEXT_DOMAIN);
+			break;
+		}
+		async_set_state(as, ASR_STATE_SAME_DB);
+		break;
+
+	case ASR_STATE_NEXT_DOMAIN:
+		/* domain search is only for dns */
+		if (AS_DB(as) != ASR_DB_DNS) {
+			async_set_state(as, ASR_STATE_NEXT_DB);
+			break;
+		}
+		as->as_family_idx = 0;
+
+		free(as->as.ai.fqdn);
+		as->as.ai.fqdn = NULL;
+		r = _asr_iter_domain(as, as->as.ai.hostname, fqdn, sizeof(fqdn));
+		if (r == -1) {
+			async_set_state(as, ASR_STATE_NEXT_DB);
+			break;
+		}
+		if (r == 0) {
+			ar->ar_gai_errno = EAI_FAIL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+		as->as.ai.fqdn = strdup(fqdn);
+		if (as->as.ai.fqdn == NULL) {
+			ar->ar_gai_errno = EAI_MEMORY;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		async_set_state(as, ASR_STATE_SAME_DB);
+		break;
+
+	case ASR_STATE_SAME_DB:
+		/* query the current DB again */
+		switch (AS_DB(as)) {
+		case ASR_DB_DNS:
+			if (as->as.ai.fqdn == NULL) {
+				/* First try, initialize domain iteration */
+				as->as_dom_flags = 0;
+				as->as_dom_step = DOM_INIT;
+				async_set_state(as, ASR_STATE_NEXT_DOMAIN);
+				break;
+			}
+
+			family = (as->as.ai.hints.ai_family == AF_UNSPEC) ?
+			    AS_FAMILY(as) : as->as.ai.hints.ai_family;
+
+			if (family == AF_INET &&
+			    as->as_flags & ASYNC_NO_INET) {
+				async_set_state(as, ASR_STATE_NEXT_FAMILY);
+				break;
+			} else if (family == AF_INET6 &&
+			    as->as_flags & ASYNC_NO_INET6) {
+				async_set_state(as, ASR_STATE_NEXT_FAMILY);
+				break;
+			}
+
+			as->as_subq = _res_query_async_ctx(as->as.ai.fqdn,
+			    C_IN, (family == AF_INET6) ? T_AAAA : T_A,
+			    as->as_ctx);
+
+			if (as->as_subq == NULL) {
+				if (errno == ENOMEM)
+					ar->ar_gai_errno = EAI_MEMORY;
+				else
+					ar->ar_gai_errno = EAI_FAIL;
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+			async_set_state(as, ASR_STATE_SUBQUERY);
+			break;
+
+		case ASR_DB_FILE:
+			f = fopen(_PATH_HOSTS, "re");
+			if (f == NULL) {
+				async_set_state(as, ASR_STATE_NEXT_DB);
+				break;
+			}
+			family = (as->as.ai.hints.ai_family == AF_UNSPEC) ?
+			    AS_FAMILY(as) : as->as.ai.hints.ai_family;
+
+			r = addrinfo_from_file(as, family, f);
+			if (r == -1) {
+				if (errno == ENOMEM)
+					ar->ar_gai_errno = EAI_MEMORY;
+				else
+					ar->ar_gai_errno = EAI_FAIL;
+				async_set_state(as, ASR_STATE_HALT);
+			} else
+				async_set_state(as, ASR_STATE_NEXT_FAMILY);
+			fclose(f);
+			break;
+
+		default:
+			async_set_state(as, ASR_STATE_NEXT_DB);
+		}
+		break;
+
+	case ASR_STATE_SUBQUERY:
+		if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
+			return (ASYNC_COND);
+
+		as->as_subq = NULL;
+
+		if (ar->ar_datalen == -1) {
+			async_set_state(as, ASR_STATE_NEXT_FAMILY);
+			break;
+		}
+
+		r = addrinfo_from_pkt(as, ar->ar_data, ar->ar_datalen);
+		if (r == -1) {
+			if (errno == ENOMEM)
+				ar->ar_gai_errno = EAI_MEMORY;
+			else
+				ar->ar_gai_errno = EAI_FAIL;
+			async_set_state(as, ASR_STATE_HALT);
+		} else
+			async_set_state(as, ASR_STATE_NEXT_FAMILY);
+		free(ar->ar_data);
+		break;
+
+	case ASR_STATE_NOT_FOUND:
+		/* No result found. Maybe we can try again. */
+		if (as->as_flags & ASYNC_AGAIN)
+			ar->ar_gai_errno = EAI_AGAIN;
+		else
+			ar->ar_gai_errno = EAI_NODATA;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+
+	case ASR_STATE_HALT:
+		if (ar->ar_gai_errno == 0) {
+			ar->ar_count = as->as_count;
+			ar->ar_addrinfo = as->as.ai.aifirst;
+			as->as.ai.aifirst = NULL;
+		} else {
+			ar->ar_count = 0;
+			ar->ar_addrinfo = NULL;
+		}
+		return (ASYNC_DONE);
+
+	default:
+		ar->ar_errno = EOPNOTSUPP;
+		ar->ar_gai_errno = EAI_SYSTEM;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+	}
+	goto next;
+}
+
+/*
+ * Retrieve the port number for the service name "servname" and
+ * the protocol "proto".
+ */
+static int
+get_port(const char *servname, const char *proto, int numonly)
+{
+#ifdef HAVE_GETSERVBYNAME_R_4_ARGS
+	struct servent		se;
+#endif
+#ifdef HAVE_STRUCT_SERVENT_DATA
+	struct servent_data	sed;
+#endif
+	int			port;
+	const char		*e;
+
+	if (servname == NULL)
+		return (0);
+
+	e = NULL;
+	port = strtonum(servname, 0, USHRT_MAX, &e);
+	if (e == NULL)
+		return (port);
+	if (errno == ERANGE)
+		return (-2); /* invalid */
+	if (numonly)
+		return (-2);
+
+	port = -1;
+#ifdef HAVE_STRUCT_SERVENT_DATA
+	memset(&sed, 0, sizeof(sed));
+#endif
+#ifdef HAVE_GETSERVBYNAME_R_4_ARGS
+	if (getservbyname_r(servname, proto, &se, &sed) != -1)
+		port = ntohs(se.s_port);
+#endif
+#ifdef HAVE_ENDSERVENT_R
+	endservent_r(&sed);
+#endif
+
+	return (port);
+}
+
+/*
+ * Iterate over the address families that are to be queried. Use the
+ * list on the async context, unless a specific family was given in hints.
+ */
+static int
+iter_family(struct asr_query *as, int first)
+{
+	if (first) {
+		as->as_family_idx = 0;
+		if (as->as.ai.hints.ai_family != PF_UNSPEC)
+			return as->as.ai.hints.ai_family;
+		return AS_FAMILY(as);
+	}
+
+	if (as->as.ai.hints.ai_family != PF_UNSPEC)
+		return (-1);
+
+	as->as_family_idx++;
+
+	return AS_FAMILY(as);
+}
+
+/*
+ * Use the sockaddr at "sa" to extend the result list on the "as" context,
+ * with the specified canonical name "cname". This function adds one
+ * entry per protocol/socktype match.
+ */
+static int
+addrinfo_add(struct asr_query *as, const struct sockaddr *sa, const char *cname)
+{
+	struct addrinfo		*ai;
+	int			 i, port, proto;
+
+	for (i = 0; matches[i].family != -1; i++) {
+		if (matches[i].family != sa->sa_family ||
+		    !MATCH_SOCKTYPE(as->as.ai.hints.ai_socktype, i) ||
+		    !MATCH_PROTO(as->as.ai.hints.ai_protocol, i))
+			continue;
+
+		proto = as->as.ai.hints.ai_protocol;
+		if (!proto)
+			proto = matches[i].protocol;
+
+		if (proto == IPPROTO_TCP)
+			port = as->as.ai.port_tcp;
+		else if (proto == IPPROTO_UDP)
+			port = as->as.ai.port_udp;
+		else
+			port = 0;
+
+		/* servname specified, but not defined for this protocol */
+		if (port == -1)
+			continue;
+
+		ai = calloc(1, sizeof(*ai) + SA_LEN(sa));
+		if (ai == NULL)
+			return (EAI_MEMORY);
+		ai->ai_family = sa->sa_family;
+		ai->ai_socktype = matches[i].socktype;
+		ai->ai_protocol = proto;
+		ai->ai_flags = as->as.ai.hints.ai_flags;
+		ai->ai_addrlen = SA_LEN(sa);
+		ai->ai_addr = (void *)(ai + 1);
+		if (cname &&
+		    as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) {
+			if ((ai->ai_canonname = strdup(cname)) == NULL) {
+				free(ai);
+				return (EAI_MEMORY);
+			}
+		}
+		memmove(ai->ai_addr, sa, SA_LEN(sa));
+		if (sa->sa_family == PF_INET)
+			((struct sockaddr_in *)ai->ai_addr)->sin_port =
+			    htons(port);
+		else if (sa->sa_family == PF_INET6)
+			((struct sockaddr_in6 *)ai->ai_addr)->sin6_port =
+			    htons(port);
+
+		if (as->as.ai.aifirst == NULL)
+			as->as.ai.aifirst = ai;
+		if (as->as.ai.ailast)
+			as->as.ai.ailast->ai_next = ai;
+		as->as.ai.ailast = ai;
+		as->as_count += 1;
+	}
+
+	return (0);
+}
+
+void
+asr_freeaddrinfo(struct addrinfo *ai)
+{
+	struct addrinfo *ai_next;
+
+	while (ai) {
+		ai_next = ai->ai_next;
+		if (ai->ai_canonname)
+			free(ai->ai_canonname);
+		free(ai);
+		ai = ai_next;
+	}
+}
+
+static int
+addrinfo_from_file(struct asr_query *as, int family, FILE *f)
+{
+	char		*tokens[MAXTOKEN], *c, buf[ASR_BUFSIZ + 1];
+	int		 n, i;
+	union {
+		struct sockaddr		sa;
+		struct sockaddr_in	sain;
+		struct sockaddr_in6	sain6;
+	} u;
+
+	for (;;) {
+		n = _asr_parse_namedb_line(f, tokens, MAXTOKEN, buf, sizeof(buf));
+		if (n == -1)
+			break; /* ignore errors reading the file */
+
+		for (i = 1; i < n; i++) {
+			if (strcasecmp(as->as.ai.hostname, tokens[i]))
+				continue;
+			if (_asr_sockaddr_from_str(&u.sa, family, tokens[0]) == -1)
+				continue;
+			break;
+		}
+		if (i == n)
+			continue;
+
+		if (as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN))
+			c = tokens[1];
+		else
+			c = NULL;
+
+		if (addrinfo_add(as, &u.sa, c))
+			return (-1); /* errno set */
+	}
+	return (0);
+}
+
+static int
+addrinfo_from_pkt(struct asr_query *as, char *pkt, size_t pktlen)
+{
+	struct asr_unpack	 p;
+	struct asr_dns_header	 h;
+	struct asr_dns_query	 q;
+	struct asr_dns_rr	 rr;
+	int			 i;
+	union {
+		struct sockaddr		sa;
+		struct sockaddr_in	sain;
+		struct sockaddr_in6	sain6;
+	} u;
+	char		 buf[MAXDNAME], *c;
+
+	_asr_unpack_init(&p, pkt, pktlen);
+	_asr_unpack_header(&p, &h);
+	for (; h.qdcount; h.qdcount--)
+		_asr_unpack_query(&p, &q);
+
+	for (i = 0; i < h.ancount; i++) {
+		_asr_unpack_rr(&p, &rr);
+		if (rr.rr_type != q.q_type ||
+		    rr.rr_class != q.q_class)
+			continue;
+
+		memset(&u, 0, sizeof u);
+		if (rr.rr_type == T_A) {
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+			u.sain.sin_len = sizeof u.sain;
+#endif
+			u.sain.sin_family = AF_INET;
+			u.sain.sin_addr = rr.rr.in_a.addr;
+			u.sain.sin_port = 0;
+		} else if (rr.rr_type == T_AAAA) {
+#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN
+			u.sain6.sin6_len = sizeof u.sain6;
+#endif
+			u.sain6.sin6_family = AF_INET6;
+			u.sain6.sin6_addr = rr.rr.in_aaaa.addr6;
+			u.sain6.sin6_port = 0;
+		} else
+			continue;
+
+		if (as->as.ai.hints.ai_flags & AI_CANONNAME) {
+			_asr_strdname(rr.rr_dname, buf, sizeof buf);
+			buf[strlen(buf) - 1] = '\0';
+			c = res_hnok(buf) ? buf : NULL;
+		} else if (as->as.ai.hints.ai_flags & AI_FQDN)
+			c = as->as.ai.fqdn;
+		else
+			c = NULL;
+
+		if (addrinfo_add(as, &u.sa, c))
+			return (-1); /* errno set */
+	}
+	return (0);
+}
+
+static int
+addrconfig_setup(struct asr_query *as)
+{
+	struct ifaddrs		*ifa, *ifa0;
+	struct sockaddr_in	*sinp;
+	struct sockaddr_in6	*sin6p;
+
+	if (getifaddrs(&ifa0) == -1)
+		return (-1);
+
+	as->as_flags |= ASYNC_NO_INET | ASYNC_NO_INET6;
+
+	for (ifa = ifa0; ifa != NULL; ifa = ifa->ifa_next) {
+		if (ifa->ifa_addr == NULL)
+			continue;
+
+		switch (ifa->ifa_addr->sa_family) {
+		case PF_INET:
+			sinp = (struct sockaddr_in *)ifa->ifa_addr;
+
+			if (sinp->sin_addr.s_addr == htonl(INADDR_LOOPBACK))
+				continue;
+
+			as->as_flags &= ~ASYNC_NO_INET;
+			break;
+		case PF_INET6:
+			sin6p = (struct sockaddr_in6 *)ifa->ifa_addr;
+
+			if (IN6_IS_ADDR_LOOPBACK(&sin6p->sin6_addr))
+				continue;
+
+			if (IN6_IS_ADDR_LINKLOCAL(&sin6p->sin6_addr))
+				continue;
+
+			as->as_flags &= ~ASYNC_NO_INET6;
+			break;
+		}
+	}
+
+	freeifaddrs(ifa0);
+
+	return (0);
+}
blob - /dev/null
blob + 2fce46b37fdef84a1c82ec3c1ccf9edbe8019321 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/gethostnamadr.c
@@ -0,0 +1,200 @@
+/*	$OpenBSD: gethostnamadr.c,v 1.13 2015/09/14 07:38:37 guenther Exp $	*/
+/*
+ * Copyright (c) 2012,2013 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>	/* ALIGN */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int _gethostbyname(const char *, int, struct hostent *, char *, size_t,
+    int *);
+static int _fillhostent(const struct hostent *, struct hostent *, char *,
+    size_t);
+
+static struct hostent	 _hostent;
+static char		 _entbuf[4096];
+
+static char *_empty[] = { NULL, };
+
+static int
+_fillhostent(const struct hostent *h, struct hostent *r, char *buf, size_t len)
+{
+	char	**ptr, *end, *pos;
+	size_t	n, i;
+	int	naliases, naddrs;
+
+	bzero(buf, len);
+	bzero(r, sizeof(*r));
+	r->h_aliases = _empty;
+	r->h_addr_list = _empty;
+
+	end = buf + len;
+	ptr = (char **)ALIGN(buf);
+
+	if ((char *)ptr >= end)
+		return (ERANGE);
+
+	for (naliases = 0; h->h_aliases[naliases]; naliases++)
+		;
+	for (naddrs = 0; h->h_addr_list[naddrs]; naddrs++)
+		;
+
+	pos = (char *)(ptr + (naliases + 1) + (naddrs + 1));
+	if (pos >= end)
+		return (ERANGE);
+
+	r->h_name = NULL;
+	r->h_addrtype = h->h_addrtype;
+	r->h_length = h->h_length;
+	r->h_aliases = ptr;
+	r->h_addr_list = ptr + naliases + 1;
+
+	n = strlcpy(pos, h->h_name, end - pos);
+	if (n >= end - pos)
+		return (ERANGE);
+	r->h_name = pos;
+	pos += n + 1;
+
+	for (i = 0; i < naliases; i++) {
+		n = strlcpy(pos, h->h_aliases[i], end - pos);
+		if (n >= end - pos)
+			return (ERANGE);
+		r->h_aliases[i] = pos;
+		pos += n + 1;
+	}
+
+	pos = (char *)ALIGN(pos);
+	if (pos >= end)
+		return (ERANGE);
+
+	for (i = 0; i < naddrs; i++) {
+		if (r->h_length > end - pos)
+			return (ERANGE);
+		memmove(pos, h->h_addr_list[i], r->h_length);
+		r->h_addr_list[i] = pos;
+		pos += r->h_length;
+	}
+
+	return (0);
+}
+
+static int
+_gethostbyname(const char *name, int af, struct hostent *ret, char *buf,
+    size_t buflen, int *h_errnop)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+	int r;
+
+	if (af == -1)
+		as = gethostbyname_async(name, NULL);
+	else
+		as = gethostbyname2_async(name, af, NULL);
+
+	if (as == NULL)
+		return (errno);
+
+	asr_run_sync(as, &ar);
+
+	errno = ar.ar_errno;
+	*h_errnop = ar.ar_h_errno;
+	if (ar.ar_hostent == NULL)
+		return (0);
+
+	r = _fillhostent(ar.ar_hostent, ret, buf, buflen);
+	free(ar.ar_hostent);
+
+	return (r);
+}
+
+struct hostent *
+gethostbyname(const char *name)
+{
+	struct hostent	*h;
+
+	res_init();
+
+	if (_res.options & RES_USE_INET6 &&
+	    (h = gethostbyname2(name, AF_INET6)))
+		return (h);
+
+	return gethostbyname2(name, AF_INET);
+}
+DEF_WEAK(gethostbyname);
+
+struct hostent *
+gethostbyname2(const char *name, int af)
+{
+	int	r;
+
+	res_init();
+
+	r = _gethostbyname(name, af, &_hostent, _entbuf, sizeof(_entbuf),
+	    &h_errno);
+	if (r) {
+		h_errno = NETDB_INTERNAL;
+		errno = r;
+	}
+
+	if (h_errno)
+		return (NULL);
+
+	return (&_hostent);
+}
+DEF_WEAK(gethostbyname2);
+
+struct hostent *
+gethostbyaddr(const void *addr, socklen_t len, int af)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+	int r;
+
+	res_init();
+
+	as = gethostbyaddr_async(addr, len, af, NULL);
+	if (as == NULL) {
+		h_errno = NETDB_INTERNAL;
+		return (NULL);
+	}
+
+	asr_run_sync(as, &ar);
+
+	errno = ar.ar_errno;
+	h_errno = ar.ar_h_errno;
+	if (ar.ar_hostent == NULL)
+		return (NULL);
+
+	r = _fillhostent(ar.ar_hostent, &_hostent, _entbuf, sizeof(_entbuf));
+	free(ar.ar_hostent);
+
+	if (r) {
+		h_errno = NETDB_INTERNAL;
+		errno = r;
+		return (NULL);
+	}
+
+	return (&_hostent);
+}
blob - /dev/null
blob + caa62dcb70db097e0bc139762696d8704d467641 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/gethostnamadr_async.c
@@ -0,0 +1,714 @@
+/*	$OpenBSD: gethostnamadr_async.c,v 1.50 2024/09/03 18:20:35 op Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <netdb.h>
+
+#include <asr.h>
+#include <ctype.h>
+#include <errno.h>
+#include <resolv.h> /* for res_hnok */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "asr_private.h"
+
+#define MAXALIASES	35
+#define MAXADDRS	35
+
+struct hostent_ext {
+	struct hostent	 h;
+	char		*aliases[MAXALIASES + 1];
+	char		*addrs[MAXADDRS + 1];
+	char		*end;
+	char		*pos;
+};
+
+struct netent_ext {
+	struct netent	 n;
+	char		*aliases[MAXALIASES + 1];
+	char		*end;
+	char		*pos;
+};
+
+static int gethostnamadr_async_run(struct asr_query *, struct asr_result *);
+static struct hostent_ext *hostent_alloc(int);
+static int hostent_set_cname(struct hostent_ext *, const char *, int);
+static int hostent_add_alias(struct hostent_ext *, const char *, int);
+static int hostent_add_addr(struct hostent_ext *, const void *, size_t);
+static struct hostent_ext *hostent_from_addr(int, const char *, const char *);
+static struct hostent_ext *hostent_file_match(FILE *, int, int, const char *,
+    int);
+static struct hostent_ext *hostent_from_packet(int, int, char *, size_t);
+static void netent_from_hostent(struct asr_result *ar);
+
+struct asr_query *
+gethostbyname_async(const char *name, void *asr)
+{
+	return gethostbyname2_async(name, AF_INET, asr);
+}
+DEF_WEAK(gethostbyname_async);
+
+struct asr_query *
+gethostbyname2_async(const char *name, int af, void *asr)
+{
+	struct asr_ctx	 *ac;
+	struct asr_query *as;
+
+	/* the original segfaults */
+	if (name == NULL) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	ac = _asr_use_resolver(asr);
+	if ((as = _asr_async_new(ac, ASR_GETHOSTBYNAME)) == NULL)
+		goto abort; /* errno set */
+	as->as_run = gethostnamadr_async_run;
+
+	as->as.hostnamadr.family = af;
+	if (af == AF_INET)
+		as->as.hostnamadr.addrlen = INADDRSZ;
+	else if (af == AF_INET6)
+		as->as.hostnamadr.addrlen = IN6ADDRSZ;
+	as->as.hostnamadr.name = strdup(name);
+	if (as->as.hostnamadr.name == NULL)
+		goto abort; /* errno set */
+
+	_asr_ctx_unref(ac);
+	return (as);
+
+    abort:
+	if (as)
+		_asr_async_free(as);
+	_asr_ctx_unref(ac);
+	return (NULL);
+}
+DEF_WEAK(gethostbyname2_async);
+
+struct asr_query *
+gethostbyaddr_async(const void *addr, socklen_t len, int af, void *asr)
+{
+	struct asr_ctx	 *ac;
+	struct asr_query *as;
+
+	ac = _asr_use_resolver(asr);
+	as = _gethostbyaddr_async_ctx(addr, len, af, ac);
+	_asr_ctx_unref(ac);
+
+	return (as);
+}
+DEF_WEAK(gethostbyaddr_async);
+
+struct asr_query *
+_gethostbyaddr_async_ctx(const void *addr, socklen_t len, int af,
+    struct asr_ctx *ac)
+{
+	struct asr_query *as;
+
+	if ((as = _asr_async_new(ac, ASR_GETHOSTBYADDR)) == NULL)
+		goto abort; /* errno set */
+	as->as_run = gethostnamadr_async_run;
+
+	as->as.hostnamadr.family = af;
+	as->as.hostnamadr.addrlen = len;
+	if (len > 0)
+		memmove(as->as.hostnamadr.addr, addr, (len > 16) ? 16 : len);
+
+	return (as);
+
+    abort:
+	if (as)
+		_asr_async_free(as);
+	return (NULL);
+}
+
+static int
+gethostnamadr_async_run(struct asr_query *as, struct asr_result *ar)
+{
+	struct hostent_ext	*h;
+	int			 r, type, saved_errno;
+	FILE			*f;
+	char			 name[MAXDNAME], *data, addr[16], *c;
+
+    next:
+	switch (as->as_state) {
+
+	case ASR_STATE_INIT:
+
+		if (as->as.hostnamadr.family != AF_INET &&
+		    as->as.hostnamadr.family != AF_INET6) {
+			ar->ar_h_errno = NETDB_INTERNAL;
+			ar->ar_errno = EAFNOSUPPORT;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if ((as->as.hostnamadr.family == AF_INET &&
+		     as->as.hostnamadr.addrlen != INADDRSZ) ||
+		    (as->as.hostnamadr.family == AF_INET6 &&
+		     as->as.hostnamadr.addrlen != IN6ADDRSZ)) {
+			ar->ar_h_errno = NETDB_INTERNAL;
+			ar->ar_errno = EINVAL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (as->as_type == ASR_GETHOSTBYNAME) {
+
+			if (as->as.hostnamadr.name[0] == '\0') {
+				ar->ar_h_errno = NO_DATA;
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+
+			/* Name might be an IP address string */
+			for (c = as->as.hostnamadr.name; *c; c++)
+				if (!isdigit((unsigned char)*c) &&
+				     *c != '.' && *c != ':')
+					break;
+			if (*c == 0 &&
+			    inet_pton(as->as.hostnamadr.family,
+			    as->as.hostnamadr.name, addr) == 1) {
+				h = hostent_from_addr(as->as.hostnamadr.family,
+				    as->as.hostnamadr.name, addr);
+				if (h == NULL) {
+					ar->ar_errno = errno;
+					ar->ar_h_errno = NETDB_INTERNAL;
+				}
+				else {
+					ar->ar_hostent = &h->h;
+					ar->ar_h_errno = NETDB_SUCCESS;
+				}
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+
+			if (!hnok_lenient(as->as.hostnamadr.name)) {
+				ar->ar_h_errno = NETDB_INTERNAL;
+				ar->ar_errno = EINVAL;
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+
+			/*
+			 * If hostname is "localhost" or falls within the
+			 * ".localhost." domain, use local address.
+			 * RFC 6761, 6.3:
+			 * 3. Name resolution APIs and libraries SHOULD
+			 * recognize localhost names as special and SHOULD
+			 * always return the IP loopback address for address
+			 * queries and negative responses for all other query
+			 * types.  Name resolution APIs SHOULD NOT send queries
+			 * for localhost names to their configured caching DNS
+			 * server(s).
+			 */
+
+			if (_asr_is_localhost(as->as.hostnamadr.name)) {
+				inet_pton(as->as.hostnamadr.family,
+				    as->as.hostnamadr.family == AF_INET ?
+				    "127.0.0.1" : "::1", addr);
+				h = hostent_from_addr(as->as.hostnamadr.family,
+				    as->as.hostnamadr.name, addr);
+				if (h == NULL) {
+					ar->ar_errno = errno;
+					ar->ar_h_errno = NETDB_INTERNAL;
+				}
+				else {
+					ar->ar_hostent = &h->h;
+					ar->ar_h_errno = NETDB_SUCCESS;
+				}
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+		}
+		async_set_state(as, ASR_STATE_NEXT_DB);
+		break;
+
+	case ASR_STATE_NEXT_DB:
+
+		if (_asr_iter_db(as) == -1) {
+			async_set_state(as, ASR_STATE_NOT_FOUND);
+			break;
+		}
+
+		switch (AS_DB(as)) {
+
+		case ASR_DB_DNS:
+
+			/* Create a subquery to do the DNS lookup */
+
+			if (as->as_type == ASR_GETHOSTBYNAME) {
+				type = (as->as.hostnamadr.family == AF_INET) ?
+				    T_A : T_AAAA;
+				as->as_subq = _res_search_async_ctx(
+				    as->as.hostnamadr.name,
+				    C_IN, type, as->as_ctx);
+			} else {
+				_asr_addr_as_fqdn(as->as.hostnamadr.addr,
+				    as->as.hostnamadr.family,
+				    name, sizeof(name));
+				as->as_subq = _res_query_async_ctx(
+				    name, C_IN, T_PTR, as->as_ctx);
+			}
+
+			if (as->as_subq == NULL) {
+				ar->ar_errno = errno;
+				ar->ar_h_errno = NETDB_INTERNAL;
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+
+			async_set_state(as, ASR_STATE_SUBQUERY);
+			break;
+
+		case ASR_DB_FILE:
+
+			/* Try to find a match in the host file */
+
+			if ((f = fopen(_PATH_HOSTS, "re")) == NULL)
+				break;
+
+			if (as->as_type == ASR_GETHOSTBYNAME)
+				data = as->as.hostnamadr.name;
+			else
+				data = as->as.hostnamadr.addr;
+
+			h = hostent_file_match(f, as->as_type,
+			    as->as.hostnamadr.family, data,
+			    as->as.hostnamadr.addrlen);
+			saved_errno = errno;
+			fclose(f);
+			errno = saved_errno;
+
+			if (h == NULL) {
+				if (errno) {
+					ar->ar_errno = errno;
+					ar->ar_h_errno = NETDB_INTERNAL;
+					async_set_state(as, ASR_STATE_HALT);
+				}
+				/* otherwise not found */
+				break;
+			}
+			ar->ar_hostent = &h->h;
+			ar->ar_h_errno = NETDB_SUCCESS;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+		break;
+
+	case ASR_STATE_SUBQUERY:
+
+		/* Run the DNS subquery. */
+
+		if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
+			return (ASYNC_COND);
+
+		/* Done. */
+		as->as_subq = NULL;
+
+		/*
+		 * We either got no packet or a packet without an answer.
+		 * Safeguard the h_errno and use the next DB.
+		 */
+		if (ar->ar_count == 0) {
+			free(ar->ar_data);
+			as->as.hostnamadr.subq_h_errno = ar->ar_h_errno;
+			async_set_state(as, ASR_STATE_NEXT_DB);
+			break;
+		}
+
+		/* Read the hostent from the packet. */
+
+		h = hostent_from_packet(as->as_type,
+		    as->as.hostnamadr.family, ar->ar_data, ar->ar_datalen);
+		free(ar->ar_data);
+		if (h == NULL) {
+			ar->ar_errno = errno;
+			ar->ar_h_errno = NETDB_INTERNAL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (as->as_type == ASR_GETHOSTBYADDR) {
+			if (hostent_add_addr(h, as->as.hostnamadr.addr,
+			    as->as.hostnamadr.addrlen) == -1) {
+				free(h);
+				ar->ar_errno = errno;
+				ar->ar_h_errno = NETDB_INTERNAL;
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+		}
+
+		/*
+		 * No valid hostname or address found in the dns packet.
+		 * Ignore it.
+		 */
+		if ((as->as_type == ASR_GETHOSTBYNAME &&
+		     h->h.h_addr_list[0] == NULL) ||
+		    h->h.h_name == NULL) {
+			free(h);
+			async_set_state(as, ASR_STATE_NEXT_DB);
+			break;
+		}
+
+		ar->ar_hostent = &h->h;
+		ar->ar_h_errno = NETDB_SUCCESS;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+
+	case ASR_STATE_NOT_FOUND:
+		ar->ar_errno = 0;
+		if (as->as.hostnamadr.subq_h_errno)
+			ar->ar_h_errno = as->as.hostnamadr.subq_h_errno;
+		else
+			ar->ar_h_errno = HOST_NOT_FOUND;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+
+	case ASR_STATE_HALT:
+		if (ar->ar_h_errno == NETDB_SUCCESS &&
+		    as->as_flags & ASYNC_GETNET)
+			netent_from_hostent(ar);
+		if (ar->ar_h_errno) {
+			ar->ar_hostent = NULL;
+			ar->ar_netent = NULL;
+		} else
+			ar->ar_errno = 0;
+		return (ASYNC_DONE);
+
+	default:
+		ar->ar_errno = EOPNOTSUPP;
+		ar->ar_h_errno = NETDB_INTERNAL;
+		ar->ar_gai_errno = EAI_SYSTEM;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+	}
+	goto next;
+}
+
+/*
+ * Create a hostent from a numeric address string.
+ */
+static struct hostent_ext *
+hostent_from_addr(int family, const char *name, const char *addr)
+{
+	struct	 hostent_ext *h;
+
+	if ((h = hostent_alloc(family)) == NULL)
+		return (NULL);
+	if (hostent_set_cname(h, name, 0) == -1)
+		goto fail;
+	if (hostent_add_addr(h, addr, h->h.h_length) == -1)
+		goto fail;
+	return (h);
+fail:
+	free(h);
+	return (NULL);
+}
+
+/*
+ * Lookup the first matching entry in the hostfile, either by address or by
+ * name depending on reqtype, and build a hostent from the line.
+ */
+static struct hostent_ext *
+hostent_file_match(FILE *f, int reqtype, int family, const char *data,
+    int datalen)
+{
+	char	*tokens[MAXTOKEN], addr[16], buf[ASR_BUFSIZ + 1];
+	struct	 hostent_ext *h;
+	int	 n, i;
+
+	for (;;) {
+		n = _asr_parse_namedb_line(f, tokens, MAXTOKEN, buf, sizeof(buf));
+		if (n == -1) {
+			errno = 0; /* ignore errors reading the file */
+			return (NULL);
+		}
+
+		/* there must be an address and at least one name */
+		if (n < 2)
+			continue;
+
+		if (reqtype == ASR_GETHOSTBYNAME) {
+			for (i = 1; i < n; i++) {
+				if (strcasecmp(data, tokens[i]))
+					continue;
+				if (inet_pton(family, tokens[0], addr) == 1)
+					goto found;
+			}
+		} else {
+			if (inet_pton(family, tokens[0], addr) == 1 &&
+			    memcmp(addr, data, datalen) == 0)
+				goto found;
+		}
+	}
+
+found:
+	if ((h = hostent_alloc(family)) == NULL)
+		return (NULL);
+	if (hostent_set_cname(h, tokens[1], 0) == -1)
+		goto fail;
+	for (i = 2; i < n; i ++)
+		if (hostent_add_alias(h, tokens[i], 0) == -1)
+			goto fail;
+	if (hostent_add_addr(h, addr, h->h.h_length) == -1)
+		goto fail;
+	return (h);
+fail:
+	free(h);
+	return (NULL);
+}
+
+/*
+ * Fill the hostent from the given DNS packet.
+ */
+static struct hostent_ext *
+hostent_from_packet(int reqtype, int family, char *pkt, size_t pktlen)
+{
+	struct hostent_ext	*h;
+	struct asr_unpack	 p;
+	struct asr_dns_header	 hdr;
+	struct asr_dns_query	 q;
+	struct asr_dns_rr	 rr;
+	char			 dname[MAXDNAME];
+
+	if ((h = hostent_alloc(family)) == NULL)
+		return (NULL);
+
+	_asr_unpack_init(&p, pkt, pktlen);
+	_asr_unpack_header(&p, &hdr);
+	for (; hdr.qdcount; hdr.qdcount--)
+		_asr_unpack_query(&p, &q);
+	strlcpy(dname, q.q_dname, sizeof(dname));
+
+	for (; hdr.ancount; hdr.ancount--) {
+		_asr_unpack_rr(&p, &rr);
+		if (rr.rr_class != C_IN)
+			continue;
+		switch (rr.rr_type) {
+
+		case T_CNAME:
+			if (reqtype == ASR_GETHOSTBYNAME) {
+				if (hostent_add_alias(h, rr.rr_dname, 1) == -1)
+					goto fail;
+			} else {
+				if (strcasecmp(rr.rr_dname, dname) == 0)
+					strlcpy(dname, rr.rr.cname.cname,
+					    sizeof(dname));
+			}
+			break;
+
+		case T_PTR:
+			if (reqtype != ASR_GETHOSTBYADDR)
+				break;
+			if (strcasecmp(rr.rr_dname, dname) != 0)
+				continue;
+			if (hostent_set_cname(h, rr.rr.ptr.ptrname, 1) == -1)
+				hostent_add_alias(h, rr.rr.ptr.ptrname, 1);
+			break;
+
+		case T_A:
+			if (reqtype != ASR_GETHOSTBYNAME)
+				break;
+			if (family != AF_INET)
+				break;
+			if (hostent_set_cname(h, rr.rr_dname, 1) == -1)
+				;
+			if (hostent_add_addr(h, &rr.rr.in_a.addr, 4) == -1)
+				goto fail;
+			break;
+
+		case T_AAAA:
+			if (reqtype != ASR_GETHOSTBYNAME)
+				break;
+			if (family != AF_INET6)
+				break;
+			if (hostent_set_cname(h, rr.rr_dname, 1) == -1)
+				;
+			if (hostent_add_addr(h, &rr.rr.in_aaaa.addr6, 16) == -1)
+				goto fail;
+			break;
+		}
+	}
+
+	return (h);
+fail:
+	free(h);
+	return (NULL);
+}
+
+static struct hostent_ext *
+hostent_alloc(int family)
+{
+	struct hostent_ext	*h;
+	size_t			alloc;
+
+	alloc = sizeof(*h) + 1024;
+	if ((h = calloc(1, alloc)) == NULL)
+		return (NULL);
+
+	h->h.h_addrtype = family;
+	h->h.h_length = (family == AF_INET) ? 4 : 16;
+	h->h.h_aliases = h->aliases;
+	h->h.h_addr_list = h->addrs;
+	h->pos = (char *)(h) + sizeof(*h);
+	h->end = h->pos + 1024;
+
+	return (h);
+}
+
+static int
+hostent_set_cname(struct hostent_ext *h, const char *name, int isdname)
+{
+	char	buf[MAXDNAME];
+	size_t	n;
+
+	if (h->h.h_name)
+		return (-1);
+
+	if (isdname) {
+		_asr_strdname(name, buf, sizeof buf);
+		buf[strlen(buf) - 1] = '\0';
+		if (!res_hnok(buf))
+			return (-1);
+		name = buf;
+	}
+
+	n = strlen(name) + 1;
+	if (h->pos + n >= h->end)
+		return (-1);
+
+	h->h.h_name = h->pos;
+	memmove(h->pos, name, n);
+	h->pos += n;
+	return (0);
+}
+
+static int
+hostent_add_alias(struct hostent_ext *h, const char *name, int isdname)
+{
+	char	buf[MAXDNAME];
+	size_t	i, n;
+
+	for (i = 0; i < MAXALIASES; i++)
+		if (h->aliases[i] == NULL)
+			break;
+	if (i == MAXALIASES)
+		return (0);
+
+	if (isdname) {
+		_asr_strdname(name, buf, sizeof buf);
+		buf[strlen(buf)-1] = '\0';
+		if (!res_hnok(buf))
+			return (-1);
+		name = buf;
+	}
+
+	n = strlen(name) + 1;
+	if (h->pos + n >= h->end)
+		return (0);
+
+	h->aliases[i] = h->pos;
+	memmove(h->pos, name, n);
+	h->pos += n;
+	return (0);
+}
+
+static int
+hostent_add_addr(struct hostent_ext *h, const void *addr, size_t size)
+{
+	int	i;
+
+	for (i = 0; i < MAXADDRS; i++)
+		if (h->addrs[i] == NULL)
+			break;
+	if (i == MAXADDRS)
+		return (0);
+
+	if (h->pos + size >= h->end)
+		return (0);
+
+	h->addrs[i] = h->pos;
+	memmove(h->pos, addr, size);
+	h->pos += size;
+	return (0);
+}
+
+static void
+netent_from_hostent(struct asr_result *ar)
+{
+	struct in_addr		 *addr;
+	struct netent_ext	 *n;
+	struct hostent_ext	 *h;
+	char			**na, **ha;
+	size_t			  sz;
+
+	/* Allocate and initialize the output. */
+	if ((n = calloc(1, sizeof(*n) + 1024)) == NULL) {
+		ar->ar_h_errno = NETDB_INTERNAL;
+		ar->ar_errno = errno;
+		goto out;
+	}
+	n->pos = (char *)(n) + sizeof(*n);
+	n->end = n->pos + 1024;
+	n->n.n_name = n->pos;
+	n->n.n_aliases = n->aliases;
+
+	/* Copy the fixed-size data. */
+	h = (struct hostent_ext *)ar->ar_hostent;
+	addr = (struct in_addr *)h->h.h_addr;
+	n->n.n_net = ntohl(addr->s_addr);
+	n->n.n_addrtype = h->h.h_addrtype;
+
+	/* Copy the network name. */
+	sz = strlen(h->h.h_name) + 1;
+	memcpy(n->pos, h->h.h_name, sz);
+	n->pos += sz;
+
+	/*
+	 * Copy the aliases.
+	 * No overflow check is needed because we are merely copying
+	 * a part of the data from a structure of the same size.
+	 */
+	na = n->aliases;
+	for (ha = h->aliases; *ha != NULL; ha++) {
+		sz = strlen(*ha) + 1;
+		memcpy(n->pos, *ha, sz);
+		*na++ = n->pos;
+		n->pos += sz;
+	}
+	*na = NULL;
+
+	/* Handle the return values. */
+	ar->ar_netent = &n->n;
+out:
+	free(ar->ar_hostent);
+	ar->ar_hostent = NULL;
+}
blob - /dev/null
blob + 72564cb8d574fecf19c5fb470f85f817ea045302 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/getnameinfo.c
@@ -0,0 +1,206 @@
+/*	$OpenBSD: getnameinfo.c,v 1.11 2022/12/27 17:10:06 jmc Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <string.h>
+
+static size_t asr_print_addr(const struct sockaddr *, char *, size_t);
+static size_t asr_print_port(const struct sockaddr *, const char *, char *, size_t);
+
+#define SA_IN(sa) ((struct sockaddr_in*)(sa))
+#define SA_IN6(sa) ((struct sockaddr_in6*)(sa))
+
+/*
+ * Print the textual representation (as given by inet_ntop(3)) of the address
+ * set in "sa".
+ *
+ * Return the total length of the string it tried to create or 0 if an error
+ * occurred, in which case errno is set.  On success, the constructed string
+ * is guaranteed to be NUL-terminated.  Overflow must be detected by checking
+ * the returned size against buflen.
+ *
+ */
+static size_t
+asr_print_addr(const struct sockaddr *sa, char *buf, size_t buflen)
+{
+	unsigned int ifidx;
+	char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+	char scope[IF_NAMESIZE + 1], *ifname;
+	const void *addr;
+	size_t s;
+
+	switch(sa->sa_family) {
+	case AF_INET:
+		addr = &SA_IN(sa)->sin_addr;
+		break;
+	case AF_INET6:
+		addr = &SA_IN6(sa)->sin6_addr;
+		break;
+	default:
+		errno = EINVAL;
+		return (0);
+	}
+
+	if (inet_ntop(sa->sa_family, addr, tmp, sizeof(tmp)) == NULL)
+		return (0); /* errno set */
+
+	s = strlcpy(buf, tmp, buflen);
+
+	if (sa->sa_family == AF_INET6 && SA_IN6(sa)->sin6_scope_id) {
+
+		scope[0] = SCOPE_DELIMITER;
+		scope[1] = '\0';
+
+		ifidx = SA_IN6(sa)->sin6_scope_id;
+		ifname = NULL;
+
+		if (IN6_IS_ADDR_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) ||
+		    IN6_IS_ADDR_MC_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) ||
+		    IN6_IS_ADDR_MC_INTFACELOCAL(&(SA_IN6(sa)->sin6_addr)))
+			ifname = if_indextoname(ifidx, scope + 1);
+
+		if (ifname == NULL)
+			(void)snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx);
+
+		if (s < buflen)
+			(void)strlcat(buf, scope, buflen);
+
+		s += strlen(scope);
+	}
+
+	return (s);
+}
+
+/* 
+ * Print the textual representation of the port set on "sa".
+ *
+ * If proto is not NULL, it is used as parameter to "getservbyport_r(3)" to
+ * return a service name. If it's not set, or if no matching service is found,
+ * it prints the portno.
+ *
+ * Return the total length of the string it tried to create or 0 if an error
+ * occurred, in which case errno is set.  On success, the constructed string
+ * is guaranteed to be NUL-terminated.  Overflow must be detected by checking
+ * the returned size against buflen.
+ */
+static size_t
+asr_print_port(const struct sockaddr *sa, const char *proto, char *buf, size_t buflen)
+{
+	struct servent s;
+	struct servent_data sd;
+	int port, r, saved_errno;
+	size_t n;
+
+	switch(sa->sa_family) {
+	case AF_INET:
+		port = SA_IN(sa)->sin_port;
+		break;
+	case AF_INET6:
+		port = SA_IN6(sa)->sin6_port;
+		break;
+	default:
+		errno = EINVAL;
+		return (0);
+	}
+
+	if (proto) {
+		memset(&sd, 0, sizeof (sd));
+		saved_errno = errno;
+		r = getservbyport_r(port, proto, &s, &sd);
+		if (r == 0)
+			n = strlcpy(buf, s.s_name, buflen);
+		endservent_r(&sd);
+		errno = saved_errno;
+		if (r == 0)
+			return (n);
+	}
+
+	r = snprintf(buf, buflen, "%u", ntohs(port));
+	if (r < 0 || r >= buflen) 	/* Actually, this can not happen */
+		return (0);
+
+	return (r);
+}
+
+int
+getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host,
+    size_t hostlen, char *serv, size_t servlen, int flags)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+	int saved_errno = errno;
+	const char *proto;
+	size_t r;
+
+	/*
+	 * Take a shortcut if we don't care about hostname,
+	 * or if NI_NUMERICHOST is set.
+	 */
+	if (host == NULL || hostlen == 0 ||
+	    (host && hostlen && (flags & NI_NUMERICHOST))) {
+		if (host) {
+			r = asr_print_addr(sa, host, hostlen);
+			if (r == 0)
+				return (EAI_SYSTEM); /* errno set */
+			if (r >= hostlen)
+				return (EAI_OVERFLOW);
+		}
+
+		if (serv && servlen) {
+			if (flags & NI_NUMERICSERV)
+				proto = NULL;
+			else
+				proto = (flags & NI_DGRAM) ? "udp" : "tcp";
+			r = asr_print_port(sa, proto, serv, servlen);
+			if (r == 0)
+				return (EAI_SYSTEM); /* errno set */
+			if (r >= servlen)
+				return (EAI_OVERFLOW);
+		}
+
+		errno = saved_errno;
+		return (0);
+	}
+
+	res_init();
+
+	as = getnameinfo_async(sa, salen, host, hostlen, serv, servlen, flags,
+	    NULL);
+	if (as == NULL) {
+		if (errno == ENOMEM) {
+			errno = saved_errno;
+			return (EAI_MEMORY);
+		}
+		return (EAI_SYSTEM);
+	}
+
+	asr_run_sync(as, &ar);
+	if (ar.ar_gai_errno == EAI_SYSTEM)
+		errno = ar.ar_errno;
+
+	return (ar.ar_gai_errno);
+}
+DEF_WEAK(getnameinfo);
blob - /dev/null
blob + be07c0f12f8a94abcc8facc491a910c9ac754d69 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/getnameinfo_async.c
@@ -0,0 +1,302 @@
+/*	$OpenBSD: getnameinfo_async.c,v 1.15 2020/12/21 09:40:35 eric Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asr_private.h"
+
+static int getnameinfo_async_run(struct asr_query *, struct asr_result *);
+static int _servname(struct asr_query *);
+static int _numerichost(struct asr_query *);
+
+struct asr_query *
+getnameinfo_async(const struct sockaddr *sa, socklen_t slen, char *host,
+    size_t hostlen, char *serv, size_t servlen, int flags, void *asr)
+{
+	struct asr_ctx	 *ac;
+	struct asr_query *as;
+
+	ac = _asr_use_resolver(asr);
+	if ((as = _asr_async_new(ac, ASR_GETNAMEINFO)) == NULL)
+		goto abort; /* errno set */
+	as->as_run = getnameinfo_async_run;
+
+	if (sa->sa_family == AF_INET)
+		memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain));
+	else if (sa->sa_family == AF_INET6)
+		memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain6));
+
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+	as->as.ni.sa.sa.sa_len = slen;
+#endif
+	as->as.ni.hostname = host;
+	as->as.ni.hostnamelen = hostlen;
+	as->as.ni.servname = serv;
+	as->as.ni.servnamelen = servlen;
+	as->as.ni.flags = flags;
+
+	_asr_ctx_unref(ac);
+	return (as);
+
+    abort:
+	if (as)
+		_asr_async_free(as);
+	_asr_ctx_unref(ac);
+	return (NULL);
+}
+DEF_WEAK(getnameinfo_async);
+
+static int
+getnameinfo_async_run(struct asr_query *as, struct asr_result *ar)
+{
+	void		*addr;
+	socklen_t	 addrlen;
+	int		 r;
+
+    next:
+	switch (as->as_state) {
+
+	case ASR_STATE_INIT:
+
+		/* Make sure the parameters are all valid. */
+
+		if (as->as.ni.sa.sa.sa_family != AF_INET &&
+		    as->as.ni.sa.sa.sa_family != AF_INET6) {
+			ar->ar_gai_errno = EAI_FAMILY;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if ((as->as.ni.sa.sa.sa_family == AF_INET &&
+		    (SA_LEN(&as->as.ni.sa.sa) != sizeof (as->as.ni.sa.sain))) ||
+		    (as->as.ni.sa.sa.sa_family == AF_INET6 &&
+		    (SA_LEN(&as->as.ni.sa.sa) != sizeof (as->as.ni.sa.sain6)))) {
+			ar->ar_gai_errno = EAI_FAIL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/* Set the service name first, if needed. */
+		if (_servname(as) == -1) {
+			ar->ar_gai_errno = EAI_OVERFLOW;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (as->as.ni.hostname == NULL || as->as.ni.hostnamelen == 0) {
+			ar->ar_gai_errno = 0;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (as->as.ni.flags & NI_NUMERICHOST) {
+			if (_numerichost(as) == -1) {
+				if (errno == ENOMEM)
+					ar->ar_gai_errno = EAI_MEMORY;
+				else if (errno == ENOSPC)
+					ar->ar_gai_errno = EAI_OVERFLOW;
+				else {
+					ar->ar_errno = errno;
+					ar->ar_gai_errno = EAI_SYSTEM;
+				}
+			} else
+				ar->ar_gai_errno = 0;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (as->as.ni.sa.sa.sa_family == AF_INET) {
+			addrlen = sizeof(as->as.ni.sa.sain.sin_addr);
+			addr = &as->as.ni.sa.sain.sin_addr;
+		} else {
+			addrlen = sizeof(as->as.ni.sa.sain6.sin6_addr);
+			addr = &as->as.ni.sa.sain6.sin6_addr;
+		}
+
+		/*
+		 * Create a subquery to lookup the address.
+		 */
+		as->as_subq = _gethostbyaddr_async_ctx(addr, addrlen,
+		    as->as.ni.sa.sa.sa_family,
+		    as->as_ctx);
+		if (as->as_subq == NULL) {
+			ar->ar_gai_errno = EAI_MEMORY;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		async_set_state(as, ASR_STATE_SUBQUERY);
+		break;
+
+	case ASR_STATE_SUBQUERY:
+
+		if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
+			return (ASYNC_COND);
+
+		/*
+		 * Request done.
+		 */
+		as->as_subq = NULL;
+
+		if (ar->ar_hostent == NULL) {
+			if (as->as.ni.flags & NI_NAMEREQD) {
+				ar->ar_gai_errno = EAI_NONAME;
+			} else if (_numerichost(as) == -1) {
+				if (errno == ENOMEM)
+					ar->ar_gai_errno = EAI_MEMORY;
+				else if (errno == ENOSPC)
+					ar->ar_gai_errno = EAI_OVERFLOW;
+				else {
+					ar->ar_errno = errno;
+					ar->ar_gai_errno = EAI_SYSTEM;
+				}
+			} else
+				ar->ar_gai_errno = 0;
+		} else {
+			if (strlcpy(as->as.ni.hostname,
+			    ar->ar_hostent->h_name,
+			    as->as.ni.hostnamelen) >= as->as.ni.hostnamelen)
+				ar->ar_gai_errno = EAI_OVERFLOW;
+			else
+				ar->ar_gai_errno = 0;
+			free(ar->ar_hostent);
+		}
+
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+
+	case ASR_STATE_HALT:
+		return (ASYNC_DONE);
+
+	default:
+		ar->ar_errno = EOPNOTSUPP;
+		ar->ar_gai_errno = EAI_SYSTEM;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+	}
+	goto next;
+}
+
+
+/*
+ * Set the service name on the result buffer is not NULL.
+ * return (-1) if the buffer is too small.
+ */
+static int
+_servname(struct asr_query *as)
+{
+	struct servent		 s;
+#ifdef HAVE_STRUCT_SERVENT_DATA
+	struct servent_data	 sd;
+#endif
+	int			 port, r;
+	char			*buf = as->as.ni.servname;
+	size_t			 n, buflen = as->as.ni.servnamelen;
+
+	if (as->as.ni.servname == NULL || as->as.ni.servnamelen == 0)
+		return (0);
+
+	if (as->as.ni.sa.sa.sa_family == AF_INET)
+		port = as->as.ni.sa.sain.sin_port;
+	else
+		port = as->as.ni.sa.sain6.sin6_port;
+
+	if (!(as->as.ni.flags & NI_NUMERICSERV)) {
+#ifdef HAVE_STRUCT_SERVENT_DATA
+		memset(&sd, 0, sizeof (sd));
+#endif
+#ifdef HAVE_GETSERVBYPORT_R_4_ARGS
+		r = getservbyport_r(port, (as->as.ni.flags & NI_DGRAM) ?
+		    "udp" : "tcp", &s, &sd);
+		if (r == 0)
+			n = strlcpy(buf, s.s_name, buflen);
+#else
+		r = -1;
+#endif
+#ifdef HAVE_ENDSERVENT_R
+		endservent_r(&sd);
+#endif
+		if (r == 0) {
+			if (n >= buflen)
+				return (-1);
+			return (0);
+		}
+	}
+
+	r = snprintf(buf, buflen, "%u", ntohs(port));
+	if (r < 0 || (size_t)r >= buflen)
+		return (-1);
+
+	return (0);
+}
+
+/*
+ * Write the numeric address
+ */
+static int
+_numerichost(struct asr_query *as)
+{
+	unsigned int	ifidx;
+	char		scope[IF_NAMESIZE + 1], *ifname;
+	void		*addr;
+	char		*buf = as->as.ni.hostname;
+	size_t		 buflen = as->as.ni.hostnamelen;
+
+	if (as->as.ni.sa.sa.sa_family == AF_INET)
+		addr = &as->as.ni.sa.sain.sin_addr;
+	else
+		addr = &as->as.ni.sa.sain6.sin6_addr;
+
+	if (inet_ntop(as->as.ni.sa.sa.sa_family, addr, buf, buflen) == NULL)
+		return (-1); /* errno set */
+
+	if (as->as.ni.sa.sa.sa_family == AF_INET6 &&
+	    as->as.ni.sa.sain6.sin6_scope_id) {
+
+		scope[0] = SCOPE_DELIMITER;
+		scope[1] = '\0';
+
+		ifidx = as->as.ni.sa.sain6.sin6_scope_id;
+		ifname = NULL;
+
+		if (IN6_IS_ADDR_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) ||
+		    IN6_IS_ADDR_MC_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) ||
+		    IN6_IS_ADDR_MC_INTFACELOCAL(&as->as.ni.sa.sain6.sin6_addr))
+			ifname = if_indextoname(ifidx, scope + 1);
+
+		if (ifname == NULL)
+			snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx);
+
+		strlcat(buf, scope, buflen);
+	}
+
+	return (0);
+}
blob - /dev/null
blob + 141b4a9ab8fa6c639942b2a863ca16877f8e15a4 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/getnetnamadr.c
@@ -0,0 +1,134 @@
+/*	$OpenBSD: getnetnamadr.c,v 1.9 2015/01/16 16:48:51 deraadt Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>	/* ALIGN */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void _fillnetent(const struct netent *, struct netent *, char *buf,
+    size_t);
+
+static struct netent	 _netent;
+static char		 _entbuf[4096];
+
+static char *_empty[] = { NULL, };
+
+static void
+_fillnetent(const struct netent *e, struct netent *r, char *buf, size_t len)
+{
+	char	**ptr, *end, *pos;
+	size_t	n, i;
+	int	naliases;
+
+	bzero(buf, len);
+	bzero(r, sizeof(*r));
+	r->n_aliases = _empty;
+
+	end = buf + len;
+	ptr = (char **)ALIGN(buf);
+
+	if ((char *)ptr >= end)
+		return;
+
+	for (naliases = 0; e->n_aliases[naliases]; naliases++)
+		;
+
+	r->n_name = NULL;
+	r->n_addrtype = e->n_addrtype;
+	r->n_net = e->n_net;
+	r->n_aliases = ptr;
+
+	pos = (char *)(ptr + (naliases + 1));
+	if (pos > end)
+		r->n_aliases = _empty;
+
+	n = strlcpy(pos, e->n_name, end - pos);
+	if (n >= end - pos)
+		return;
+	r->n_name = pos;
+	pos += n + 1;
+
+	for (i = 0; i < naliases; i++) {
+		n = strlcpy(pos, e->n_aliases[i], end - pos);
+		if (n >= end - pos)
+			return;
+		r->n_aliases[i] = pos;
+		pos += n + 1;
+	}
+}
+
+struct netent *
+getnetbyname(const char *name)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+
+	res_init();
+
+	as = getnetbyname_async(name, NULL);
+	if (as == NULL) {
+		h_errno = NETDB_INTERNAL;
+		return (NULL);
+	}
+
+	asr_run_sync(as, &ar);
+
+	errno = ar.ar_errno;
+	h_errno = ar.ar_h_errno;
+	if (ar.ar_netent == NULL)
+		return (NULL);
+
+	_fillnetent(ar.ar_netent, &_netent, _entbuf, sizeof(_entbuf));
+	free(ar.ar_netent);
+
+	return (&_netent);
+}
+
+struct netent *
+getnetbyaddr(in_addr_t net, int type)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+
+	res_init();
+
+	as = getnetbyaddr_async(net, type, NULL);
+	if (as == NULL) {
+		h_errno = NETDB_INTERNAL;
+		return (NULL);
+	}
+
+	asr_run_sync(as, &ar);
+
+	errno = ar.ar_errno;
+	h_errno = ar.ar_h_errno;
+	if (ar.ar_netent == NULL)
+		return (NULL);
+
+	_fillnetent(ar.ar_netent, &_netent, _entbuf, sizeof(_entbuf));
+	free(ar.ar_netent);
+
+	return (&_netent);
+}
blob - /dev/null
blob + 1e02d0082861eb2bd864b376eea127902feab6ef (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/getnetnamadr_async.c
@@ -0,0 +1,52 @@
+/*	$OpenBSD: getnetnamadr_async.c,v 1.26 2018/04/28 15:16:49 schwarze Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+#include <asr.h>
+
+#include "asr_private.h"
+
+struct asr_query *
+getnetbyname_async(const char *name, void *asr)
+{
+	struct asr_query *as;
+
+	if ((as = gethostbyname_async(name, asr)) != NULL)
+		as->as_flags |= ASYNC_GETNET;
+	return (as);
+}
+DEF_WEAK(getnetbyname_async);
+
+struct asr_query *
+getnetbyaddr_async(in_addr_t net, int family, void *asr)
+{
+	struct in_addr	  in;
+	struct asr_query *as;
+
+	in.s_addr = htonl(net);
+	as = gethostbyaddr_async(&in, sizeof(in), family, asr);
+	if (as != NULL)
+		as->as_flags |= ASYNC_GETNET;
+	return (as);
+}
+DEF_WEAK(getnetbyaddr_async);
blob - /dev/null
blob + 24df2c8bfa03cf6ac7926454216cfacda9e72613 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/getrrsetbyname.c
@@ -0,0 +1,83 @@
+/*	$OpenBSD: getrrsetbyname.c,v 1.6 2015/09/14 07:38:37 guenther Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <stdlib.h>
+
+int
+getrrsetbyname(const char *name, unsigned int class, unsigned int type,
+    unsigned int flags, struct rrsetinfo **res)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+	int r, saved_errno = errno;
+
+	res_init();
+
+	as = getrrsetbyname_async(name, class, type, flags, NULL);
+	if (as == NULL) {
+		r = (errno == ENOMEM) ? ERRSET_NOMEMORY : ERRSET_FAIL;
+		errno = saved_errno;
+		return (r);
+	}
+
+	asr_run_sync(as, &ar);
+
+	*res = ar.ar_rrsetinfo;
+
+	return (ar.ar_rrset_errno);
+}
+
+/* from net/getrrsetbyname.c */
+void
+freerrset(struct rrsetinfo *rrset)
+{
+	u_int16_t i;
+
+	if (rrset == NULL)
+		return;
+
+	if (rrset->rri_rdatas) {
+		for (i = 0; i < rrset->rri_nrdatas; i++) {
+			if (rrset->rri_rdatas[i].rdi_data == NULL)
+				break;
+			free(rrset->rri_rdatas[i].rdi_data);
+		}
+		free(rrset->rri_rdatas);
+	}
+
+	if (rrset->rri_sigs) {
+		for (i = 0; i < rrset->rri_nsigs; i++) {
+			if (rrset->rri_sigs[i].rdi_data == NULL)
+				break;
+			free(rrset->rri_sigs[i].rdi_data);
+		}
+		free(rrset->rri_sigs);
+	}
+
+	if (rrset->rri_name)
+		free(rrset->rri_name);
+	free(rrset);
+}
+DEF_WEAK(freerrset);
blob - /dev/null
blob + 494855b7e63e050c69aadbc47fa66f90c3b86956 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/getrrsetbyname_async.c
@@ -0,0 +1,621 @@
+/*	$OpenBSD: getrrsetbyname_async.c,v 1.13 2023/03/15 22:12:00 millert Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asr_private.h"
+
+static int getrrsetbyname_async_run(struct asr_query *, struct asr_result *);
+static void get_response(struct asr_result *, const char *, int);
+
+struct asr_query *
+getrrsetbyname_async(const char *hostname, unsigned int rdclass,
+    unsigned int rdtype, unsigned int flags, void *asr)
+{
+	struct asr_ctx	 *ac;
+	struct asr_query *as;
+
+	ac = _asr_use_resolver(asr);
+	if ((as = _asr_async_new(ac, ASR_GETRRSETBYNAME)) == NULL)
+		goto abort; /* errno set */
+	as->as_run = getrrsetbyname_async_run;
+
+	as->as.rrset.flags = flags;
+	as->as.rrset.class = rdclass;
+	as->as.rrset.type = rdtype;
+	as->as.rrset.name = strdup(hostname);
+	if (as->as.rrset.name == NULL)
+		goto abort; /* errno set */
+
+	_asr_ctx_unref(ac);
+	return (as);
+    abort:
+	if (as)
+		_asr_async_free(as);
+
+	_asr_ctx_unref(ac);
+	return (NULL);
+}
+DEF_WEAK(getrrsetbyname_async);
+
+static int
+getrrsetbyname_async_run(struct asr_query *as, struct asr_result *ar)
+{
+    next:
+	switch (as->as_state) {
+
+	case ASR_STATE_INIT:
+
+		/* Check for invalid class and type. */
+		if (as->as.rrset.class > 0xffff || as->as.rrset.type > 0xffff) {
+			ar->ar_rrset_errno = ERRSET_INVAL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/* Do not allow queries of class or type ANY. */
+		if (as->as.rrset.class == 0xff || as->as.rrset.type == 0xff) {
+			ar->ar_rrset_errno = ERRSET_INVAL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/* Do not allow flags yet, unimplemented. */
+		if (as->as.rrset.flags) {
+			ar->ar_rrset_errno = ERRSET_INVAL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/* Create a delegate the lookup to a subquery. */
+		as->as_subq = _res_query_async_ctx(
+		    as->as.rrset.name,
+		    as->as.rrset.class,
+		    as->as.rrset.type,
+		    as->as_ctx);
+		if (as->as_subq == NULL) {
+			ar->ar_rrset_errno = ERRSET_FAIL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		async_set_state(as, ASR_STATE_SUBQUERY);
+		break;
+
+	case ASR_STATE_SUBQUERY:
+
+		if ((asr_run(as->as_subq, ar)) == ASYNC_COND)
+			return (ASYNC_COND);
+
+		as->as_subq = NULL;
+
+		/* No packet received.*/
+		if (ar->ar_datalen == -1) {
+			switch (ar->ar_h_errno) {
+			case HOST_NOT_FOUND:
+				ar->ar_rrset_errno = ERRSET_NONAME;
+				break;
+			case NO_DATA:
+				ar->ar_rrset_errno = ERRSET_NODATA;
+				break;
+			default:
+				ar->ar_rrset_errno = ERRSET_FAIL;
+				break;
+			}
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/* Got a packet but no answer. */
+		if (ar->ar_count == 0) {
+			free(ar->ar_data);
+			switch (ar->ar_rcode) {
+			case NXDOMAIN:
+				ar->ar_rrset_errno = ERRSET_NONAME;
+				break;
+			case NOERROR:
+				ar->ar_rrset_errno = ERRSET_NODATA;
+				break;
+			default:
+				ar->ar_rrset_errno = ERRSET_FAIL;
+				break;
+			}
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		get_response(ar, ar->ar_data, ar->ar_datalen);
+		free(ar->ar_data);
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+
+	case ASR_STATE_HALT:
+		if (ar->ar_rrset_errno)
+			ar->ar_rrsetinfo = NULL;
+		return (ASYNC_DONE);
+
+	default:
+		ar->ar_rrset_errno = ERRSET_FAIL;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+	}
+	goto next;
+}
+
+/* The rest of this file is taken from the original implementation. */
+
+/* $OpenBSD: getrrsetbyname_async.c,v 1.13 2023/03/15 22:12:00 millert Exp $ */
+
+/*
+ * Copyright (c) 2001 Jakob Schlyter. All rights reserved.
+ *
+ * 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 AUTHOR ``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 AUTHOR 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.
+ */
+
+/*
+ * Portions Copyright (c) 1999-2001 Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define MAXPACKET 1024*64
+
+struct dns_query {
+	char			*name;
+	u_int16_t		type;
+	u_int16_t		class;
+	struct dns_query	*next;
+};
+
+struct dns_rr {
+	char			*name;
+	u_int16_t		type;
+	u_int16_t		class;
+	u_int16_t		ttl;
+	u_int16_t		size;
+	void			*rdata;
+	struct dns_rr		*next;
+};
+
+struct dns_response {
+	HEADER			header;
+	struct dns_query	*query;
+	struct dns_rr		*answer;
+	struct dns_rr		*authority;
+	struct dns_rr		*additional;
+};
+
+static struct dns_response *parse_dns_response(const u_char *, int);
+static struct dns_query *parse_dns_qsection(const u_char *, int,
+    const u_char **, int);
+static struct dns_rr *parse_dns_rrsection(const u_char *, int, const u_char **,
+    int);
+
+static void free_dns_query(struct dns_query *);
+static void free_dns_rr(struct dns_rr *);
+static void free_dns_response(struct dns_response *);
+
+static int count_dns_rr(struct dns_rr *, u_int16_t, u_int16_t);
+
+static void
+get_response(struct asr_result *ar, const char *pkt, int pktlen)
+{
+	struct rrsetinfo *rrset = NULL;
+	struct dns_response *response = NULL;
+	struct dns_rr *rr;
+	struct rdatainfo *rdata;
+	unsigned int index_ans, index_sig;
+
+	/* parse result */
+	response = parse_dns_response(pkt, pktlen);
+	if (response == NULL) {
+		ar->ar_rrset_errno = ERRSET_FAIL;
+		goto fail;
+	}
+
+	if (response->header.qdcount != 1) {
+		ar->ar_rrset_errno = ERRSET_FAIL;
+		goto fail;
+	}
+
+	/* initialize rrset */
+	rrset = calloc(1, sizeof(struct rrsetinfo));
+	if (rrset == NULL) {
+		ar->ar_rrset_errno = ERRSET_NOMEMORY;
+		goto fail;
+	}
+	rrset->rri_rdclass = response->query->class;
+	rrset->rri_rdtype = response->query->type;
+	rrset->rri_ttl = response->answer->ttl;
+	rrset->rri_nrdatas = response->header.ancount;
+
+	/* check for authenticated data */
+	if (response->header.ad == 1)
+		rrset->rri_flags |= RRSET_VALIDATED;
+
+	/* copy name from answer section */
+	rrset->rri_name = strdup(response->answer->name);
+	if (rrset->rri_name == NULL) {
+		ar->ar_rrset_errno = ERRSET_NOMEMORY;
+		goto fail;
+	}
+
+	/* count answers */
+	rrset->rri_nrdatas = count_dns_rr(response->answer, rrset->rri_rdclass,
+	    rrset->rri_rdtype);
+	rrset->rri_nsigs = count_dns_rr(response->answer, rrset->rri_rdclass,
+	    T_RRSIG);
+
+	/* allocate memory for answers */
+	rrset->rri_rdatas = calloc(rrset->rri_nrdatas,
+	    sizeof(struct rdatainfo));
+	if (rrset->rri_rdatas == NULL) {
+		ar->ar_rrset_errno = ERRSET_NOMEMORY;
+		goto fail;
+	}
+
+	/* allocate memory for signatures */
+	rrset->rri_sigs = calloc(rrset->rri_nsigs, sizeof(struct rdatainfo));
+	if (rrset->rri_sigs == NULL) {
+		ar->ar_rrset_errno = ERRSET_NOMEMORY;
+		goto fail;
+	}
+
+	/* copy answers & signatures */
+	for (rr = response->answer, index_ans = 0, index_sig = 0;
+	    rr; rr = rr->next) {
+
+		rdata = NULL;
+
+		if (rr->class == rrset->rri_rdclass &&
+		    rr->type  == rrset->rri_rdtype)
+			rdata = &rrset->rri_rdatas[index_ans++];
+
+		if (rr->class == rrset->rri_rdclass &&
+		    rr->type  == T_RRSIG)
+			rdata = &rrset->rri_sigs[index_sig++];
+
+		if (rdata) {
+			rdata->rdi_length = rr->size;
+			rdata->rdi_data   = malloc(rr->size);
+
+			if (rdata->rdi_data == NULL) {
+				ar->ar_rrset_errno = ERRSET_NOMEMORY;
+				goto fail;
+			}
+			memcpy(rdata->rdi_data, rr->rdata, rr->size);
+		}
+	}
+	free_dns_response(response);
+
+	ar->ar_rrsetinfo = rrset;
+	ar->ar_rrset_errno = ERRSET_SUCCESS;
+	return;
+
+fail:
+	if (rrset != NULL)
+		freerrset(rrset);
+	if (response != NULL)
+		free_dns_response(response);
+}
+
+/*
+ * DNS response parsing routines
+ */
+static struct dns_response *
+parse_dns_response(const u_char *answer, int size)
+{
+	struct dns_response *resp;
+	const u_char *cp;
+
+	if (size <= HFIXEDSZ)
+		return (NULL);
+
+	/* allocate memory for the response */
+	resp = calloc(1, sizeof(*resp));
+	if (resp == NULL)
+		return (NULL);
+
+	/* initialize current pointer */
+	cp = answer;
+
+	/* copy header */
+	memcpy(&resp->header, cp, HFIXEDSZ);
+	cp += HFIXEDSZ;
+
+	/* fix header byte order */
+	resp->header.qdcount = ntohs(resp->header.qdcount);
+	resp->header.ancount = ntohs(resp->header.ancount);
+	resp->header.nscount = ntohs(resp->header.nscount);
+	resp->header.arcount = ntohs(resp->header.arcount);
+
+	/* there must be at least one query */
+	if (resp->header.qdcount < 1) {
+		free_dns_response(resp);
+		return (NULL);
+	}
+
+	/* parse query section */
+	resp->query = parse_dns_qsection(answer, size, &cp,
+	    resp->header.qdcount);
+	if (resp->header.qdcount && resp->query == NULL) {
+		free_dns_response(resp);
+		return (NULL);
+	}
+
+	/* parse answer section */
+	resp->answer = parse_dns_rrsection(answer, size, &cp,
+	    resp->header.ancount);
+	if (resp->header.ancount && resp->answer == NULL) {
+		free_dns_response(resp);
+		return (NULL);
+	}
+
+	/* parse authority section */
+	resp->authority = parse_dns_rrsection(answer, size, &cp,
+	    resp->header.nscount);
+	if (resp->header.nscount && resp->authority == NULL) {
+		free_dns_response(resp);
+		return (NULL);
+	}
+
+	/* parse additional section */
+	resp->additional = parse_dns_rrsection(answer, size, &cp,
+	    resp->header.arcount);
+	if (resp->header.arcount && resp->additional == NULL) {
+		free_dns_response(resp);
+		return (NULL);
+	}
+
+	return (resp);
+}
+
+static struct dns_query *
+parse_dns_qsection(const u_char *answer, int size, const u_char **cp, int count)
+{
+	struct dns_query *head, *curr, *prev;
+	int i, length;
+	char name[MAXDNAME];
+
+#define NEED(need) \
+	do { \
+		if (*cp + need > answer + size) \
+			goto fail; \
+	} while (0)
+
+	for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) {
+		if (*cp >= answer + size) {
+ fail:
+			free_dns_query(head);
+			return (NULL);
+		}
+		/* allocate and initialize struct */
+		curr = calloc(1, sizeof(struct dns_query));
+		if (curr == NULL)
+			goto fail;
+		if (head == NULL)
+			head = curr;
+		if (prev != NULL)
+			prev->next = curr;
+
+		/* name */
+		length = dn_expand(answer, answer + size, *cp, name,
+		    sizeof(name));
+		if (length < 0) {
+			free_dns_query(head);
+			return (NULL);
+		}
+		curr->name = strdup(name);
+		if (curr->name == NULL) {
+			free_dns_query(head);
+			return (NULL);
+		}
+		NEED(length);
+		*cp += length;
+
+		/* type */
+		NEED(INT16SZ);
+		curr->type = _getshort(*cp);
+		*cp += INT16SZ;
+
+		/* class */
+		NEED(INT16SZ);
+		curr->class = _getshort(*cp);
+		*cp += INT16SZ;
+	}
+#undef NEED
+
+	return (head);
+}
+
+static struct dns_rr *
+parse_dns_rrsection(const u_char *answer, int size, const u_char **cp,
+    int count)
+{
+	struct dns_rr *head, *curr, *prev;
+	int i, length;
+	char name[MAXDNAME];
+
+#define NEED(need) \
+	do { \
+		if (*cp + need > answer + size) \
+			goto fail; \
+	} while (0)
+
+	for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) {
+		if (*cp >= answer + size) {
+ fail:
+			free_dns_rr(head);
+			return (NULL);
+		}
+
+		/* allocate and initialize struct */
+		curr = calloc(1, sizeof(struct dns_rr));
+		if (curr == NULL)
+			goto fail;
+		if (head == NULL)
+			head = curr;
+		if (prev != NULL)
+			prev->next = curr;
+
+		/* name */
+		length = dn_expand(answer, answer + size, *cp, name,
+		    sizeof(name));
+		if (length < 0) {
+			free_dns_rr(head);
+			return (NULL);
+		}
+		curr->name = strdup(name);
+		if (curr->name == NULL) {
+			free_dns_rr(head);
+			return (NULL);
+		}
+		NEED(length);
+		*cp += length;
+
+		/* type */
+		NEED(INT16SZ);
+		curr->type = _getshort(*cp);
+		*cp += INT16SZ;
+
+		/* class */
+		NEED(INT16SZ);
+		curr->class = _getshort(*cp);
+		*cp += INT16SZ;
+
+		/* ttl */
+		NEED(INT32SZ);
+		curr->ttl = _getlong(*cp);
+		*cp += INT32SZ;
+
+		/* rdata size */
+		NEED(INT16SZ);
+		curr->size = _getshort(*cp);
+		*cp += INT16SZ;
+
+		/* rdata itself */
+		NEED(curr->size);
+		curr->rdata = malloc(curr->size);
+		if (curr->rdata == NULL) {
+			free_dns_rr(head);
+			return (NULL);
+		}
+		memcpy(curr->rdata, *cp, curr->size);
+		*cp += curr->size;
+	}
+#undef NEED
+
+	return (head);
+}
+
+static void
+free_dns_query(struct dns_query *p)
+{
+	if (p == NULL)
+		return;
+
+	if (p->name)
+		free(p->name);
+	free_dns_query(p->next);
+	free(p);
+}
+
+static void
+free_dns_rr(struct dns_rr *p)
+{
+	if (p == NULL)
+		return;
+
+	if (p->name)
+		free(p->name);
+	if (p->rdata)
+		free(p->rdata);
+	free_dns_rr(p->next);
+	free(p);
+}
+
+static void
+free_dns_response(struct dns_response *p)
+{
+	if (p == NULL)
+		return;
+
+	free_dns_query(p->query);
+	free_dns_rr(p->answer);
+	free_dns_rr(p->authority);
+	free_dns_rr(p->additional);
+	free(p);
+}
+
+static int
+count_dns_rr(struct dns_rr *p, u_int16_t class, u_int16_t type)
+{
+	int n = 0;
+
+	while (p) {
+		if (p->class == class && p->type == type)
+			n++;
+		p = p->next;
+	}
+
+	return (n);
+}
blob - /dev/null
blob + 71346e8abb339823cdf79992c302fedac9c3a0a5 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/libasr.la
@@ -0,0 +1,41 @@
+# libasr.la - a libtool library file
+# Generated by libtool (GNU libtool) 2.4.6
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='libasr.0.dylib'
+
+# Names of this library.
+library_names='libasr.0.dylib libasr.dylib'
+
+# The name of the static archive.
+old_library='libasr.a'
+
+# Linker flags that cannot go in dependency_libs.
+inherited_linker_flags=' '
+
+# Libraries that this one depends upon.
+dependency_libs=' -L/usr/local/Cellar/openssl@1.1/1.1.1d//lib -L/usr/local/lib -lz -lcrypto -lssl -levent -lresolv'
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libasr.
+current=0
+age=0
+revision=3
+
+# Is this an already installed library?
+installed=no
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/tmp/lib'
blob - /dev/null
blob + ca9c5ee0f1a2fce370d3cffbf343d102d96a3129 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/res_debug.c
@@ -0,0 +1,2 @@
+/*	$OpenBSD: res_debug.c,v 1.1 2012/09/08 11:08:21 eric Exp $	*/
+/* NOTHING */
blob - /dev/null
blob + 04243c47bef132e21171db2f15e33d590ec1457f (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/res_init.c
@@ -0,0 +1,103 @@
+/*	$OpenBSD: res_init.c,v 1.11 2019/06/17 05:54:45 otto Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/nameser.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <resolv.h>
+#include <string.h>
+
+#include "asr_private.h"
+#include "thread_private.h"
+
+
+struct __res_state _res;
+struct __res_state_ext _res_ext;
+
+int h_errno;
+
+int
+res_init(void)
+{
+	static void *resinit_mutex;
+	struct asr_ctx	*ac;
+	int i;
+
+	ac = _asr_use_resolver(NULL);
+
+	/*
+	 * The first thread to call res_init() will setup the global _res
+	 * structure from the async context, not overriding fields set early
+	 * by the user.
+	 */
+	_MUTEX_LOCK(&resinit_mutex);
+	if (!(_res.options & RES_INIT)) {
+		if (_res.retry == 0)
+			_res.retry = ac->ac_nsretries;
+		if (_res.retrans == 0)
+			_res.retrans = ac->ac_nstimeout;
+		if (_res.options == 0)
+			_res.options = ac->ac_options;
+		if (_res.lookups[0] == '\0')
+			strlcpy(_res.lookups, ac->ac_db, sizeof(_res.lookups));
+
+		for (i = 0; i < ac->ac_nscount && i < MAXNS; i++) {
+			/*
+			 * No need to check for length since we copy to a
+			 * struct sockaddr_storage with a size of 256 bytes
+			 * and sa_len has only 8 bits.
+			 */
+			memcpy(&_res_ext.nsaddr_list[i], ac->ac_ns[i],
+			    ac->ac_ns[i]->sa_len);
+			if (ac->ac_ns[i]->sa_len <= sizeof(_res.nsaddr_list[i]))
+				memcpy(&_res.nsaddr_list[i], ac->ac_ns[i],
+				    ac->ac_ns[i]->sa_len);
+			else
+				memset(&_res.nsaddr_list[i], 0,
+				    sizeof(_res.nsaddr_list[i]));
+		}
+		_res.nscount = i;
+		_res.options |= RES_INIT;
+	}
+	_MUTEX_UNLOCK(&resinit_mutex);
+
+	/*
+	 * If the program is not threaded, we want to reflect (some) changes
+	 * made by the user to the global _res structure.
+	 * This is a bit of a hack: if there is already an async query on
+	 * this context, it might change things in its back.  It is ok
+	 * as long as the user only uses the blocking resolver API.
+	 * If needed we could consider cloning the context if there is
+	 * a running query.
+	 */
+	if (!__isthreaded) {
+		ac->ac_nsretries = _res.retry;
+		ac->ac_nstimeout = _res.retrans;
+		ac->ac_options = _res.options;
+		strlcpy(ac->ac_db, _res.lookups, sizeof(ac->ac_db));
+		ac->ac_dbcount = strlen(ac->ac_db);
+	}
+
+	_asr_ctx_unref(ac);
+
+	return (0);
+}
+DEF_WEAK(res_init);
blob - /dev/null
blob + bc69203c28c8be6bd26c7f6a005367a5b66a4559 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/res_mkquery.c
@@ -0,0 +1,123 @@
+/*	$OpenBSD: res_mkquery.c,v 1.14 2021/11/22 20:18:27 jca Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h> /* for MAXDNAME */
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <string.h>
+
+#include "asr_private.h"
+
+/* This function is apparently needed by some ports. */
+int
+res_mkquery(int op, const char *dname, int class, int type,
+    const unsigned char *data, int datalen, const unsigned char *newrr,
+    unsigned char *buf, int buflen)
+{
+	struct asr_ctx		*ac;
+	struct asr_pack		 p;
+	struct asr_dns_header	 h;
+	char			 fqdn[MAXDNAME];
+	char			 dn[MAXDNAME];
+
+	/* we currently only support QUERY */
+	if (op != QUERY || data)
+		return (-1);
+
+	if (dname[0] == '\0' || dname[strlen(dname) - 1] != '.') {
+		if (strlcpy(fqdn, dname, sizeof(fqdn)) >= sizeof(fqdn) ||
+		    strlcat(fqdn, ".", sizeof(fqdn)) >= sizeof(fqdn))
+			return (-1);
+		dname = fqdn;
+	}
+
+	if (_asr_dname_from_fqdn(dname, dn, sizeof(dn)) == -1)
+		return (-1);
+
+	ac = _asr_use_resolver(NULL);
+
+	memset(&h, 0, sizeof h);
+	h.id = res_randomid();
+	if (ac->ac_options & RES_RECURSE)
+		h.flags |= RD_MASK;
+#ifdef RES_USE_CD
+	if (ac->ac_options & RES_USE_CD)
+		h.flags |= CD_MASK;
+#endif
+#ifdef RES_TRUSTAD
+	if (ac->ac_options & RES_TRUSTAD)
+		h.flags |= AD_MASK;
+#endif
+	h.qdcount = 1;
+	if (ac->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC))
+		h.arcount = 1;
+
+	_asr_pack_init(&p, buf, buflen);
+	_asr_pack_header(&p, &h);
+	_asr_pack_query(&p, type, class, dn);
+	if (ac->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC))
+		_asr_pack_edns0(&p, MAXPACKETSZ,
+		    ac->ac_options & RES_USE_DNSSEC);
+
+	_asr_ctx_unref(ac);
+
+	if (p.err)
+		return (-1);
+
+	return (p.offset);
+}
+
+/*
+ * This function is not documented, but used by sendmail.
+ * Put here because it uses asr_private.h too.
+ */
+int
+res_querydomain(const char *name,
+    const char *domain,
+    int class,
+    int type,
+    u_char *answer,
+    int anslen)
+{
+	char	fqdn[MAXDNAME], ndom[MAXDNAME];
+	size_t	n;
+
+	/* we really want domain to end with a dot for now */
+	if (domain && ((n = strlen(domain)) == 0 || domain[n - 1 ] != '.')) {
+		if (strlcpy(ndom, domain, sizeof(ndom)) >= sizeof(ndom) ||
+		    strlcat(ndom, ".", sizeof(ndom)) >= sizeof(ndom)) {
+			h_errno = NETDB_INTERNAL;
+			errno = EINVAL;
+			return (-1);
+		}
+		domain = ndom;
+	}
+
+	if (_asr_make_fqdn(name, domain, fqdn, sizeof fqdn) == 0) {
+		h_errno = NETDB_INTERNAL;
+		errno = EINVAL;
+		return (-1);
+	}
+
+	return (res_query(fqdn, class, type, answer, anslen));
+}
blob - /dev/null
blob + 3f8914163873f519d60f7e2d80b325a4981b5017 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/res_query.c
@@ -0,0 +1,112 @@
+/*	$OpenBSD: res_query.c,v 1.9 2015/10/05 02:57:16 guenther Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <string.h>
+#include <stdlib.h>
+
+int
+res_query(const char *name, int class, int type, u_char *ans, int anslen)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+	size_t		 len;
+
+	res_init();
+
+	if (ans == NULL || anslen <= 0) {
+		h_errno = NO_RECOVERY;
+		errno = EINVAL;
+		return (-1);
+	}
+
+	as = res_query_async(name, class, type, NULL);
+	if (as == NULL) {
+		if (errno == EINVAL)
+			h_errno = NO_RECOVERY;
+		else
+			h_errno = NETDB_INTERNAL;
+		return (-1); /* errno set */
+	}
+
+	asr_run_sync(as, &ar);
+
+	if (ar.ar_errno)
+		errno = ar.ar_errno;
+	h_errno = ar.ar_h_errno;
+
+	if (ar.ar_h_errno != NETDB_SUCCESS)
+		return (-1);
+
+	len = anslen;
+	if (ar.ar_datalen < len)
+		len = ar.ar_datalen;
+	memmove(ans, ar.ar_data, len);
+	free(ar.ar_data);
+
+	return (ar.ar_datalen);
+}
+DEF_WEAK(res_query);
+
+int
+res_search(const char *name, int class, int type, u_char *ans, int anslen)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+	size_t		 len;
+
+	res_init();
+
+	if (ans == NULL || anslen <= 0) {
+		h_errno = NO_RECOVERY;
+		errno = EINVAL;
+		return (-1);
+	}
+
+	as = res_search_async(name, class, type, NULL);
+	if (as == NULL) {
+		if (errno == EINVAL)
+			h_errno = NO_RECOVERY;
+		else
+			h_errno = NETDB_INTERNAL;
+		return (-1); /* errno set */
+	}
+
+	asr_run_sync(as, &ar);
+
+	if (ar.ar_errno)
+		errno = ar.ar_errno;
+	h_errno = ar.ar_h_errno;
+
+	if (ar.ar_h_errno != NETDB_SUCCESS)
+		return (-1);
+
+	len = anslen;
+	if (ar.ar_datalen < len)
+		len = ar.ar_datalen;
+	memmove(ans, ar.ar_data, len);
+	free(ar.ar_data);
+
+	return (ar.ar_datalen);
+}
blob - /dev/null
blob + 3430f9578f50e74310bab02d72177f81896c5224 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/res_search_async.c
@@ -0,0 +1,327 @@
+/*	$OpenBSD: res_search_async.c,v 1.21 2017/02/27 10:44:46 jca Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "openbsd-compat.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asr_private.h"
+
+static int res_search_async_run(struct asr_query *, struct asr_result *);
+static size_t domcat(const char *, const char *, char *, size_t);
+
+/*
+ * Unlike res_query_async(), this function returns a valid packet only if
+ * h_errno is NETDB_SUCCESS.
+ */
+struct asr_query *
+res_search_async(const char *name, int class, int type, void *asr)
+{
+	struct asr_ctx	 *ac;
+	struct asr_query *as;
+
+	DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type);
+
+	ac = _asr_use_resolver(asr);
+	as = _res_search_async_ctx(name, class, type, ac);
+	_asr_ctx_unref(ac);
+
+	return (as);
+}
+DEF_WEAK(res_search_async);
+
+struct asr_query *
+_res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac)
+{
+	struct asr_query	*as;
+
+	DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class,
+	    type);
+
+	if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL)
+		goto err; /* errno set */
+	as->as_run  = res_search_async_run;
+	if ((as->as.search.name = strdup(name)) == NULL)
+		goto err; /* errno set */
+
+	as->as.search.class = class;
+	as->as.search.type = type;
+
+	return (as);
+    err:
+	if (as)
+		_asr_async_free(as);
+	return (NULL);
+}
+
+#define HERRNO_UNSET	-2
+
+static int
+res_search_async_run(struct asr_query *as, struct asr_result *ar)
+{
+	int	r;
+	char	fqdn[MAXDNAME];
+
+    next:
+	switch (as->as_state) {
+
+	case ASR_STATE_INIT:
+
+		if (as->as.search.name[0] == '\0') {
+			ar->ar_h_errno = NO_DATA;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		as->as.search.saved_h_errno = HERRNO_UNSET;
+		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
+		break;
+
+	case ASR_STATE_NEXT_DOMAIN:
+		/*
+		 * Reset flags to be able to identify the case in
+		 * STATE_SUBQUERY.
+		 */
+		as->as_dom_flags = 0;
+
+		r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn));
+		if (r == -1) {
+			async_set_state(as, ASR_STATE_NOT_FOUND);
+			break;
+		}
+		if (r == 0) {
+			ar->ar_errno = EINVAL;
+			ar->ar_h_errno = NO_RECOVERY;
+			ar->ar_datalen = -1;
+			ar->ar_data = NULL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+		as->as_subq = _res_query_async_ctx(fqdn,
+		    as->as.search.class, as->as.search.type, as->as_ctx);
+		if (as->as_subq == NULL) {
+			ar->ar_errno = errno;
+			if (errno == EINVAL)
+				ar->ar_h_errno = NO_RECOVERY;
+			else
+				ar->ar_h_errno = NETDB_INTERNAL;
+			ar->ar_datalen = -1;
+			ar->ar_data = NULL;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+		async_set_state(as, ASR_STATE_SUBQUERY);
+		break;
+
+	case ASR_STATE_SUBQUERY:
+
+		if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
+			return (ASYNC_COND);
+		as->as_subq = NULL;
+
+		if (ar->ar_h_errno == NETDB_SUCCESS) {
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		/*
+		 * The original res_search() does this in the domain search
+		 * loop, but only for ECONNREFUSED. I think we can do better
+		 * because technically if we get an errno, it means
+		 * we couldn't reach any nameserver, so there is no point
+		 * in trying further.
+		 */
+		if (ar->ar_errno) {
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		free(ar->ar_data);
+
+		/*
+		 * The original resolver does something like this.
+		 */
+		if (as->as_dom_flags & ASYNC_DOM_NDOTS)
+			as->as.search.saved_h_errno = ar->ar_h_errno;
+
+		if (as->as_dom_flags & ASYNC_DOM_DOMAIN) {
+			if (ar->ar_h_errno == NO_DATA)
+				as->as_flags |= ASYNC_NODATA;
+			else if (ar->ar_h_errno == TRY_AGAIN)
+				as->as_flags |= ASYNC_AGAIN;
+		}
+
+		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
+		break;
+
+	case ASR_STATE_NOT_FOUND:
+
+		if (as->as.search.saved_h_errno != HERRNO_UNSET)
+			ar->ar_h_errno = as->as.search.saved_h_errno;
+		else if (as->as_flags & ASYNC_NODATA)
+			ar->ar_h_errno = NO_DATA;
+		else if (as->as_flags & ASYNC_AGAIN)
+			ar->ar_h_errno = TRY_AGAIN;
+		/*
+		 * Else, we got the ar_h_errno value set by res_query_async()
+		 * for the last domain.
+		 */
+		ar->ar_datalen = -1;
+		ar->ar_data = NULL;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+
+	case ASR_STATE_HALT:
+
+		return (ASYNC_DONE);
+
+	default:
+		ar->ar_errno = EOPNOTSUPP;
+		ar->ar_h_errno = NETDB_INTERNAL;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+	}
+	goto next;
+}
+
+/*
+ * Concatenate a name and a domain name. The result has no trailing dot.
+ * Return the resulting string length, or 0 in case of error.
+ */
+static size_t
+domcat(const char *name, const char *domain, char *buf, size_t buflen)
+{
+	size_t	r;
+
+	r = _asr_make_fqdn(name, domain, buf, buflen);
+	if (r == 0)
+		return (0);
+	buf[r - 1] = '\0';
+
+	return (r - 1);
+}
+
+enum {
+	DOM_INIT,
+	DOM_DOMAIN,
+	DOM_DONE
+};
+
+/*
+ * Implement the search domain strategy.
+ *
+ * This function works as a generator that constructs complete domains in
+ * buffer "buf" of size "len" for the given host name "name", according to the
+ * search rules defined by the resolving context.  It is supposed to be called
+ * multiple times (with the same name) to generate the next possible domain
+ * name, if any.
+ *
+ * It returns -1 if all possibilities have been exhausted, 0 if there was an
+ * error generating the next name, or the resulting name length.
+ */
+int
+_asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len)
+{
+	const char	*c;
+	int		 dots;
+
+	switch (as->as_dom_step) {
+
+	case DOM_INIT:
+		/* First call */
+
+		/*
+		 * If "name" is an FQDN, that's the only result and we
+		 * don't try anything else.
+		 */
+		if (strlen(name) && name[strlen(name) - 1] ==  '.') {
+			DPRINT("asr: iter_domain(\"%s\") fqdn\n", name);
+			as->as_dom_flags |= ASYNC_DOM_FQDN;
+			as->as_dom_step = DOM_DONE;
+			return (domcat(name, NULL, buf, len));
+		}
+
+		/*
+		 * Otherwise, we iterate through the specified search domains.
+		 */
+		as->as_dom_step = DOM_DOMAIN;
+		as->as_dom_idx = 0;
+
+		/*
+		 * If "name" as enough dots, use it as-is first, as indicated
+		 * in resolv.conf(5).
+		 */
+		dots = 0;
+		for (c = name; *c; c++)
+			dots += (*c == '.');
+		if (dots >= as->as_ctx->ac_ndots) {
+			DPRINT("asr: iter_domain(\"%s\") ndots\n", name);
+			as->as_dom_flags |= ASYNC_DOM_NDOTS;
+			if (strlcpy(buf, name, len) >= len)
+				return (0);
+			return (strlen(buf));
+		}
+		/* Otherwise, starts using the search domains */
+		/* FALLTHROUGH */
+
+	case DOM_DOMAIN:
+		if (as->as_dom_idx < as->as_ctx->ac_domcount &&
+		    (as->as_ctx->ac_options & RES_DNSRCH || (
+			as->as_ctx->ac_options & RES_DEFNAMES &&
+			as->as_dom_idx == 0 &&
+			strchr(name, '.') == NULL))) {
+			DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n",
+			    name, as->as_ctx->ac_dom[as->as_dom_idx]);
+			as->as_dom_flags |= ASYNC_DOM_DOMAIN;
+			return (domcat(name,
+			    as->as_ctx->ac_dom[as->as_dom_idx++], buf, len));
+		}
+
+		/* No more domain to try. */
+
+		as->as_dom_step = DOM_DONE;
+
+		/*
+		 * If the name was not tried as an absolute name before,
+		 * do it now.
+		 */
+		if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) {
+			DPRINT("asr: iter_domain(\"%s\") as is\n", name);
+			as->as_dom_flags |= ASYNC_DOM_ASIS;
+			if (strlcpy(buf, name, len) >= len)
+				return (0);
+			return (strlen(buf));
+		}
+		/* Otherwise, we are done. */
+
+	case DOM_DONE:
+	default:
+		DPRINT("asr: iter_domain(\"%s\") done\n", name);
+		return (-1);
+	}
+}
blob - /dev/null
blob + 32c940815afeaffc8626888c49dd5b21a3d6e7fb (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/res_send.c
@@ -0,0 +1,61 @@
+/*	$OpenBSD: res_send.c,v 1.8 2014/03/26 18:13:15 eric Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <resolv.h>
+#include <string.h>
+#include <stdlib.h>
+
+int
+res_send(const u_char *buf, int buflen, u_char *ans, int anslen)
+{
+	struct asr_query *as;
+	struct asr_result ar;
+	size_t len;
+
+	res_init();
+
+	if (ans == NULL || anslen <= 0) {
+		errno = EINVAL;
+		return (-1);
+	}
+
+	as = res_send_async(buf, buflen, NULL);
+	if (as == NULL)
+		return (-1); /* errno set */
+
+	asr_run_sync(as, &ar);
+
+	if (ar.ar_errno) {
+		errno = ar.ar_errno;
+		return (-1);
+	}
+
+	len = anslen;
+	if (ar.ar_datalen < len)
+		len = ar.ar_datalen;
+	memmove(ans, ar.ar_data, len);
+	free(ar.ar_data);
+
+	return (ar.ar_datalen);
+}
blob - /dev/null
blob + 63fc231f247211a58d08f5e5ee12b47f2877e4c6 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/res_send_async.c
@@ -0,0 +1,836 @@
+/*	$OpenBSD: res_send_async.c,v 1.41 2022/06/20 06:45:31 jca Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "openbsd-compat.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <resolv.h> /* for res_random */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asr_private.h"
+
+#define OP_QUERY    (0)
+
+static int res_send_async_run(struct asr_query *, struct asr_result *);
+static int sockaddr_connect(const struct sockaddr *, int);
+static int udp_send(struct asr_query *);
+static int udp_recv(struct asr_query *);
+static int tcp_write(struct asr_query *);
+static int tcp_read(struct asr_query *);
+static int validate_packet(struct asr_query *);
+#ifdef RES_TRUSTAD
+static void clear_ad(struct asr_result *);
+#endif
+static int setup_query(struct asr_query *, const char *, const char *, int, int);
+static int ensure_ibuf(struct asr_query *, size_t);
+static int iter_ns(struct asr_query *);
+
+#define AS_NS_SA(p) ((p)->as_ctx->ac_ns[(p)->as.dns.nsidx - 1])
+
+
+struct asr_query *
+res_send_async(const unsigned char *buf, int buflen, void *asr)
+{
+	struct asr_ctx		*ac;
+	struct asr_query	*as;
+	struct asr_unpack	 p;
+	struct asr_dns_header	 h;
+	struct asr_dns_query	 q;
+
+	DPRINT_PACKET("asr: res_send_async()", buf, buflen);
+
+	ac = _asr_use_resolver(asr);
+	if ((as = _asr_async_new(ac, ASR_SEND)) == NULL) {
+		_asr_ctx_unref(ac);
+		return (NULL); /* errno set */
+	}
+	as->as_run = res_send_async_run;
+
+	as->as_flags |= ASYNC_EXTOBUF;
+	as->as.dns.obuf = (unsigned char *)buf;
+	as->as.dns.obuflen = buflen;
+	as->as.dns.obufsize = buflen;
+
+	_asr_unpack_init(&p, buf, buflen);
+	_asr_unpack_header(&p, &h);
+	_asr_unpack_query(&p, &q);
+	if (p.err) {
+		errno = EINVAL;
+		goto err;
+	}
+	as->as.dns.reqid = h.id;
+	as->as.dns.type = q.q_type;
+	as->as.dns.class = q.q_class;
+	as->as.dns.dname = strdup(q.q_dname);
+	if (as->as.dns.dname == NULL)
+		goto err; /* errno set */
+
+	_asr_ctx_unref(ac);
+	return (as);
+    err:
+	if (as)
+		_asr_async_free(as);
+	_asr_ctx_unref(ac);
+	return (NULL);
+}
+DEF_WEAK(res_send_async);
+
+/*
+ * Unlike res_query(), this version will actually return the packet
+ * if it has received a valid one (errno == 0) even if h_errno is
+ * not NETDB_SUCCESS. So the packet *must* be freed if necessary.
+ */
+struct asr_query *
+res_query_async(const char *name, int class, int type, void *asr)
+{
+	struct asr_ctx	 *ac;
+	struct asr_query *as;
+
+	DPRINT("asr: res_query_async(\"%s\", %i, %i)\n", name, class, type);
+
+	ac = _asr_use_resolver(asr);
+	as = _res_query_async_ctx(name, class, type, ac);
+	_asr_ctx_unref(ac);
+
+	return (as);
+}
+DEF_WEAK(res_query_async);
+
+struct asr_query *
+_res_query_async_ctx(const char *name, int class, int type, struct asr_ctx *a_ctx)
+{
+	struct asr_query	*as;
+
+	DPRINT("asr: res_query_async_ctx(\"%s\", %i, %i)\n", name, class, type);
+
+	if ((as = _asr_async_new(a_ctx, ASR_SEND)) == NULL)
+		return (NULL); /* errno set */
+	as->as_run = res_send_async_run;
+
+	/* This adds a "." to name if it doesn't already have one.
+	 * That's how res_query() behaves (through res_mkquery).
+	 */
+	if (setup_query(as, name, NULL, class, type) == -1)
+		goto err; /* errno set */
+
+	return (as);
+
+    err:
+	if (as)
+		_asr_async_free(as);
+
+	return (NULL);
+}
+
+static int
+res_send_async_run(struct asr_query *as, struct asr_result *ar)
+{
+    next:
+	switch (as->as_state) {
+
+	case ASR_STATE_INIT:
+
+		if (as->as_ctx->ac_nscount == 0) {
+			ar->ar_errno = ECONNREFUSED;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		async_set_state(as, ASR_STATE_NEXT_NS);
+		break;
+
+	case ASR_STATE_NEXT_NS:
+
+		if (iter_ns(as) == -1) {
+			ar->ar_errno = ETIMEDOUT;
+			async_set_state(as, ASR_STATE_HALT);
+			break;
+		}
+
+		if (as->as_ctx->ac_options & RES_USEVC ||
+		    as->as.dns.obuflen > PACKETSZ)
+			async_set_state(as, ASR_STATE_TCP_WRITE);
+		else
+			async_set_state(as, ASR_STATE_UDP_SEND);
+		break;
+
+	case ASR_STATE_UDP_SEND:
+
+		if (udp_send(as) == -1) {
+			async_set_state(as, ASR_STATE_NEXT_NS);
+			break;
+		}
+		async_set_state(as, ASR_STATE_UDP_RECV);
+		ar->ar_cond = ASR_WANT_READ;
+		ar->ar_fd = as->as_fd;
+		ar->ar_timeout = as->as_timeout;
+		return (ASYNC_COND);
+		break;
+
+	case ASR_STATE_UDP_RECV:
+
+		if (udp_recv(as) == -1) {
+			if (errno == ENOMEM) {
+				ar->ar_errno = errno;
+				async_set_state(as, ASR_STATE_HALT);
+				break;
+			}
+			if (errno != EOVERFLOW) {
+				/* Fail or timeout */
+				async_set_state(as, ASR_STATE_NEXT_NS);
+				break;
+			}
+			if (as->as_ctx->ac_options & RES_IGNTC)
+				async_set_state(as, ASR_STATE_PACKET);
+			else
+				async_set_state(as, ASR_STATE_TCP_WRITE);
+		} else
+			async_set_state(as, ASR_STATE_PACKET);
+		break;
+
+	case ASR_STATE_TCP_WRITE:
+
+		switch (tcp_write(as)) {
+		case -1: /* fail or timeout */
+			async_set_state(as, ASR_STATE_NEXT_NS);
+			break;
+		case 0:
+			async_set_state(as, ASR_STATE_TCP_READ);
+			ar->ar_cond = ASR_WANT_READ;
+			ar->ar_fd = as->as_fd;
+			ar->ar_timeout = as->as_timeout;
+			return (ASYNC_COND);
+		case 1:
+			ar->ar_cond = ASR_WANT_WRITE;
+			ar->ar_fd = as->as_fd;
+			ar->ar_timeout = as->as_timeout;
+			return (ASYNC_COND);
+		}
+		break;
+
+	case ASR_STATE_TCP_READ:
+
+		switch (tcp_read(as)) {
+		case -1: /* Fail or timeout */
+			if (errno == ENOMEM) {
+				ar->ar_errno = errno;
+				async_set_state(as, ASR_STATE_HALT);
+			} else
+				async_set_state(as, ASR_STATE_NEXT_NS);
+			break;
+		case 0:
+			async_set_state(as, ASR_STATE_PACKET);
+			break;
+		case 1:
+			ar->ar_cond = ASR_WANT_READ;
+			ar->ar_fd = as->as_fd;
+			ar->ar_timeout = as->as_timeout;
+			return (ASYNC_COND);
+		}
+		break;
+
+	case ASR_STATE_PACKET:
+
+		memmove(&ar->ar_ns, AS_NS_SA(as), SA_LEN(AS_NS_SA(as)));
+		ar->ar_datalen = as->as.dns.ibuflen;
+		ar->ar_data = as->as.dns.ibuf;
+		as->as.dns.ibuf = NULL;
+		ar->ar_errno = 0;
+		ar->ar_rcode = as->as.dns.rcode;
+#ifdef RES_TRUSTAD
+		if (!(as->as_ctx->ac_options & RES_TRUSTAD))
+			clear_ad(ar);
+#endif
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+
+	case ASR_STATE_HALT:
+
+		if (ar->ar_errno) {
+			ar->ar_h_errno = TRY_AGAIN;
+			ar->ar_count = 0;
+			ar->ar_datalen = -1;
+			ar->ar_data = NULL;
+		} else if (as->as.dns.ancount) {
+			ar->ar_h_errno = NETDB_SUCCESS;
+			ar->ar_count = as->as.dns.ancount;
+		} else {
+			ar->ar_count = 0;
+			switch (as->as.dns.rcode) {
+			case NXDOMAIN:
+				ar->ar_h_errno = HOST_NOT_FOUND;
+				break;
+			case SERVFAIL:
+				ar->ar_h_errno = TRY_AGAIN;
+				break;
+			case NOERROR:
+				ar->ar_h_errno = NO_DATA;
+				break;
+			default:
+				ar->ar_h_errno = NO_RECOVERY;
+			}
+		}
+		return (ASYNC_DONE);
+
+	default:
+
+		ar->ar_errno = EOPNOTSUPP;
+		ar->ar_h_errno = NETDB_INTERNAL;
+		async_set_state(as, ASR_STATE_HALT);
+		break;
+	}
+	goto next;
+}
+
+static int
+sockaddr_connect(const struct sockaddr *sa, int socktype)
+{
+	int errno_save, sock, flags;
+
+	if ((sock = socket(sa->sa_family,
+	    socktype | SOCK_DNS, 0)) == -1)
+		goto fail;
+
+	if ((flags = fcntl(sock, F_GETFL, 0)) == -1)
+		goto fail;
+
+	flags |= O_NONBLOCK;
+
+	if ((flags = fcntl(sock, F_SETFL, flags)) == -1)
+		goto fail;
+
+	if (connect(sock, sa, SA_LEN(sa)) == -1) {
+		/*
+		 * In the TCP case, the caller will be asked to poll for
+		 * POLLOUT so that we start writing the packet in tcp_write()
+		 * when the connection is established, or fail there on error.
+		 */
+		if (errno == EINPROGRESS)
+			return (sock);
+		goto fail;
+	}
+
+	return (sock);
+
+    fail:
+
+	if (sock != -1) {
+		errno_save = errno;
+		close(sock);
+		errno = errno_save;
+	}
+
+	return (-1);
+}
+
+/*
+ * Prepare the DNS packet for the query type "type", class "class" and domain
+ * name created by the concatenation on "name" and "dom".
+ * Return 0 on success, set errno and return -1 on error.
+ */
+static int
+setup_query(struct asr_query *as, const char *name, const char *dom,
+	int class, int type)
+{
+	struct asr_pack		p;
+	struct asr_dns_header	h;
+	char			fqdn[MAXDNAME];
+	char			dname[MAXDNAME];
+
+	if (as->as_flags & ASYNC_EXTOBUF) {
+		errno = EINVAL;
+		DPRINT("attempting to write in user packet");
+		return (-1);
+	}
+
+	if (_asr_make_fqdn(name, dom, fqdn, sizeof(fqdn)) > sizeof(fqdn)) {
+		errno = EINVAL;
+		DPRINT("asr_make_fqdn: name too long\n");
+		return (-1);
+	}
+
+	if (_asr_dname_from_fqdn(fqdn, dname, sizeof(dname)) == -1) {
+		errno = EINVAL;
+		DPRINT("asr_dname_from_fqdn: invalid\n");
+		return (-1);
+	}
+
+	if (as->as.dns.obuf == NULL) {
+		as->as.dns.obufsize = PACKETSZ;
+		as->as.dns.obuf = malloc(as->as.dns.obufsize);
+		if (as->as.dns.obuf == NULL)
+			return (-1); /* errno set */
+	}
+	as->as.dns.obuflen = 0;
+
+	memset(&h, 0, sizeof h);
+	h.id = res_randomid();
+	if (as->as_ctx->ac_options & RES_RECURSE)
+		h.flags |= RD_MASK;
+#ifdef RES_USE_CD
+	if (as->as_ctx->ac_options & RES_USE_CD)
+		h.flags |= CD_MASK;
+#endif
+#ifdef RES_TRUSTAD
+	if (as->as_ctx->ac_options & RES_TRUSTAD)
+		h.flags |= AD_MASK;
+#endif
+
+	h.qdcount = 1;
+	if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC))
+		h.arcount = 1;
+
+	_asr_pack_init(&p, as->as.dns.obuf, as->as.dns.obufsize);
+	_asr_pack_header(&p, &h);
+	_asr_pack_query(&p, type, class, dname);
+	if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC))
+		_asr_pack_edns0(&p, MAXPACKETSZ,
+		    as->as_ctx->ac_options & RES_USE_DNSSEC);
+	if (p.err) {
+		DPRINT("error packing query");
+		errno = EINVAL;
+		return (-1);
+	}
+
+	/* Remember the parameters. */
+	as->as.dns.reqid = h.id;
+	as->as.dns.type = type;
+	as->as.dns.class = class;
+	if (as->as.dns.dname)
+		free(as->as.dns.dname);
+	as->as.dns.dname = strdup(dname);
+	if (as->as.dns.dname == NULL) {
+		DPRINT("strdup");
+		return (-1); /* errno set */
+	}
+	as->as.dns.obuflen = p.offset;
+
+	DPRINT_PACKET("asr_setup_query", as->as.dns.obuf, as->as.dns.obuflen);
+
+	return (0);
+}
+
+/*
+ * Create a connect UDP socket and send the output packet.
+ *
+ * Return 0 on success, or -1 on error (errno set).
+ */
+static int
+udp_send(struct asr_query *as)
+{
+	ssize_t	n;
+	int	save_errno;
+#ifdef DEBUG
+	char		buf[256];
+#endif
+
+	DPRINT("asr: [%p] connecting to %s UDP\n", as,
+	    _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf));
+
+	as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_DGRAM);
+	if (as->as_fd == -1)
+		return (-1); /* errno set */
+
+	n = send(as->as_fd, as->as.dns.obuf, as->as.dns.obuflen, 0);
+	if (n == -1) {
+		save_errno = errno;
+		close(as->as_fd);
+		errno = save_errno;
+		as->as_fd = -1;
+		return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ * Try to receive a valid packet from the current UDP socket.
+ *
+ * Return 0 if a full packet could be read, or -1 on error (errno set).
+ */
+static int
+udp_recv(struct asr_query *as)
+{
+	ssize_t		 n;
+	int		 save_errno;
+
+	if (ensure_ibuf(as, MAXPACKETSZ) == -1) {
+		save_errno = errno;
+		close(as->as_fd);
+		errno = save_errno;
+		as->as_fd = -1;
+		return (-1);
+	}
+
+	n = recv(as->as_fd, as->as.dns.ibuf, as->as.dns.ibufsize, 0);
+	save_errno = errno;
+	close(as->as_fd);
+	errno = save_errno;
+	as->as_fd = -1;
+	if (n == -1)
+		return (-1);
+
+	as->as.dns.ibuflen = n;
+
+	DPRINT_PACKET("asr_udp_recv()", as->as.dns.ibuf, as->as.dns.ibuflen);
+
+	if (validate_packet(as) == -1)
+		return (-1); /* errno set */
+
+	return (0);
+}
+
+/*
+ * Write the output packet to the TCP socket.
+ *
+ * Return 0 when all bytes have been sent, 1 there is no buffer space on the
+ * socket or it is not connected yet, or -1 on error (errno set).
+ */
+static int
+tcp_write(struct asr_query *as)
+{
+	struct msghdr	msg;
+	struct iovec	iov[2];
+	uint16_t	len;
+	ssize_t		n;
+	size_t		offset;
+	int		i;
+#ifdef DEBUG
+	char		buf[256];
+#endif
+
+	/* First try to connect if not already */
+	if (as->as_fd == -1) {
+		DPRINT("asr: [%p] connecting to %s TCP\n", as,
+		    _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf));
+		as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_STREAM);
+		if (as->as_fd == -1)
+			return (-1); /* errno set */
+/*
+ * Some systems (MacOS X) have SO_NOSIGPIPE instead of MSG_NOSIGNAL.
+ * If neither is available the system is probably broken. We might
+ * want to detect this at configure time.
+ */
+#ifdef SO_NOSIGPIPE
+		i = 1;
+		if (setsockopt(as->as_fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&i,
+		    sizeof(i)) == -1)
+			return (-1); /* errno set */
+#endif
+		as->as.dns.datalen = 0; /* bytes sent */
+		return (1);
+	}
+
+	i = 0;
+
+	/* Prepend de packet length if not sent already. */
+	if (as->as.dns.datalen < sizeof(len)) {
+		offset = 0;
+		len = htons(as->as.dns.obuflen);
+		iov[i].iov_base = (char *)(&len) + as->as.dns.datalen;
+		iov[i].iov_len = sizeof(len) - as->as.dns.datalen;
+		i++;
+	} else
+		offset = as->as.dns.datalen - sizeof(len);
+
+	iov[i].iov_base = as->as.dns.obuf + offset;
+	iov[i].iov_len = as->as.dns.obuflen - offset;
+	i++;
+
+	memset(&msg, 0, sizeof msg);
+	msg.msg_iov = iov;
+	msg.msg_iovlen = i;
+
+    send_again:
+/* See above. */
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+	n = sendmsg(as->as_fd, &msg, MSG_NOSIGNAL);
+	if (n == -1) {
+		if (errno == EINTR)
+			goto send_again;
+		goto close; /* errno set */
+	}
+
+	as->as.dns.datalen += n;
+
+	if (as->as.dns.datalen == as->as.dns.obuflen + sizeof(len)) {
+		/* All sent. Prepare for TCP read */
+		as->as.dns.datalen = 0;
+		return (0);
+	}
+
+	/* More data to write */
+	return (1);
+
+close:
+	close(as->as_fd);
+	as->as_fd = -1;
+	return (-1);
+}
+
+/*
+ * Try to read a valid packet from the current TCP socket.
+ *
+ * Return 0 if a full packet could be read, 1 if more data is needed and the
+ * socket must be read again, or -1 on error (errno set).
+ */
+static int
+tcp_read(struct asr_query *as)
+{
+	ssize_t		 n;
+	size_t		 offset, len;
+	char		*pos;
+	int		 save_errno, nfds;
+	struct pollfd	 pfd;
+
+	/* We must read the packet len first */
+	if (as->as.dns.datalen < sizeof(as->as.dns.pktlen)) {
+
+		pos = (char *)(&as->as.dns.pktlen) + as->as.dns.datalen;
+		len = sizeof(as->as.dns.pktlen) - as->as.dns.datalen;
+
+    read_again0:
+		n = read(as->as_fd, pos, len);
+		if (n == -1) {
+			if (errno == EINTR)
+				goto read_again0;
+			goto close; /* errno set */
+		}
+		if (n == 0) {
+			errno = ECONNRESET;
+			goto close;
+		}
+		as->as.dns.datalen += n;
+		if (as->as.dns.datalen < sizeof(as->as.dns.pktlen))
+			return (1); /* need more data */
+
+		as->as.dns.ibuflen = ntohs(as->as.dns.pktlen);
+		if (ensure_ibuf(as, as->as.dns.ibuflen) == -1)
+			goto close; /* errno set */
+
+		pfd.fd = as->as_fd;
+		pfd.events = POLLIN;
+	    poll_again:
+		nfds = poll(&pfd, 1, 0);
+		if (nfds == -1) {
+			if (errno == EINTR)
+				goto poll_again;
+			goto close; /* errno set */
+		}
+		if (nfds == 0)
+			return (1); /* no more data available */
+	}
+
+	offset = as->as.dns.datalen - sizeof(as->as.dns.pktlen);
+	pos = as->as.dns.ibuf + offset;
+	len =  as->as.dns.ibuflen - offset;
+
+    read_again:
+	n = read(as->as_fd, pos, len);
+	if (n == -1) {
+		if (errno == EINTR)
+			goto read_again;
+		goto close; /* errno set */
+	}
+	if (n == 0) {
+		errno = ECONNRESET;
+		goto close;
+	}
+	as->as.dns.datalen += n;
+
+	/* See if we got all the advertised bytes. */
+	if (as->as.dns.datalen != as->as.dns.ibuflen + sizeof(as->as.dns.pktlen))
+		return (1);
+
+	DPRINT_PACKET("asr_tcp_read()", as->as.dns.ibuf, as->as.dns.ibuflen);
+
+	if (validate_packet(as) == -1)
+		goto close; /* errno set */
+
+	errno = 0;
+close:
+	save_errno = errno;
+	close(as->as_fd);
+	errno = save_errno;
+	as->as_fd = -1;
+	return (errno ? -1 : 0);
+}
+
+/*
+ * Make sure the input buffer is at least "n" bytes long, and allocate or
+ * extend it if necessary. Return 0 on success, or set errno and return -1.
+ */
+static int
+ensure_ibuf(struct asr_query *as, size_t n)
+{
+	char	*t;
+
+	if (as->as.dns.ibufsize >= n)
+		return (0);
+
+	t = recallocarray(as->as.dns.ibuf, as->as.dns.ibufsize, n, 1);
+	if (t == NULL)
+		return (-1); /* errno set */
+	as->as.dns.ibuf = t;
+	as->as.dns.ibufsize = n;
+
+	return (0);
+}
+
+/*
+ * Check if the received packet is valid.
+ * Return 0 on success, or set errno and return -1.
+ */
+static int
+validate_packet(struct asr_query *as)
+{
+	struct asr_unpack	 p;
+	struct asr_dns_header	 h;
+	struct asr_dns_query	 q;
+	struct asr_dns_rr	 rr;
+	int			 r;
+
+	_asr_unpack_init(&p, as->as.dns.ibuf, as->as.dns.ibuflen);
+
+	_asr_unpack_header(&p, &h);
+	if (p.err)
+		goto inval;
+
+	if (h.id != as->as.dns.reqid) {
+		DPRINT("incorrect reqid\n");
+		goto inval;
+	}
+	if (h.qdcount != 1)
+		goto inval;
+	/* Should be zero, we could allow this */
+	if ((h.flags & Z_MASK) != 0)
+		goto inval;
+	/* Actually, it depends on the request but we only use OP_QUERY */
+	if (OPCODE(h.flags) != OP_QUERY)
+		goto inval;
+	/* Must be a response */
+	if ((h.flags & QR_MASK) == 0)
+		goto inval;
+
+	as->as.dns.rcode = RCODE(h.flags);
+	as->as.dns.ancount = h.ancount;
+
+	_asr_unpack_query(&p, &q);
+	if (p.err)
+		goto inval;
+
+	if (q.q_type != as->as.dns.type ||
+	    q.q_class != as->as.dns.class ||
+	    strcasecmp(q.q_dname, as->as.dns.dname)) {
+		DPRINT("incorrect type/class/dname '%s' != '%s'\n",
+		    q.q_dname, as->as.dns.dname);
+		goto inval;
+	}
+
+	/* Check for truncation */
+	if (h.flags & TC_MASK && !(as->as_ctx->ac_options & RES_IGNTC)) {
+		DPRINT("truncated\n");
+		errno = EOVERFLOW;
+		return (-1);
+	}
+
+	/* Validate the rest of the packet */
+	for (r = h.ancount + h.nscount + h.arcount; r; r--)
+		_asr_unpack_rr(&p, &rr);
+
+	/* Report any error found when unpacking the RRs. */
+	if (p.err) {
+		DPRINT("unpack: %s\n", strerror(p.err));
+		errno = p.err;
+		return (-1);
+	}
+
+	if (p.offset != as->as.dns.ibuflen) {
+		DPRINT("trailing garbage\n");
+		errno = EMSGSIZE;
+		return (-1);
+	}
+
+	return (0);
+
+    inval:
+	errno = EINVAL;
+	return (-1);
+}
+
+#ifdef RES_TRUSTAD
+/*
+ * Clear AD flag in the answer.
+ */
+static void
+clear_ad(struct asr_result *ar)
+{
+	struct asr_dns_header	*h;
+	uint16_t		 flags;
+
+	h = (struct asr_dns_header *)ar->ar_data;
+	flags = ntohs(h->flags);
+	flags &= ~(AD_MASK);
+	h->flags = htons(flags);
+}
+#endif
+
+/*
+ * Set the async context nameserver index to the next nameserver, cycling
+ * over the list until the maximum retry counter is reached.  Return 0 on
+ * success, or -1 if all nameservers were used.
+ */
+static int
+iter_ns(struct asr_query *as)
+{
+	for (;;) {
+		if (as->as.dns.nsloop >= as->as_ctx->ac_nsretries)
+			return (-1);
+
+		as->as.dns.nsidx += 1;
+		if (as->as.dns.nsidx <= as->as_ctx->ac_nscount)
+			break;
+		as->as.dns.nsidx = 0;
+		as->as.dns.nsloop++;
+		DPRINT("asr: iter_ns(): cycle %i\n", as->as.dns.nsloop);
+	}
+
+	as->as_timeout = 1000 * (as->as_ctx->ac_nstimeout << as->as.dns.nsloop);
+	if (as->as.dns.nsloop > 0)
+		as->as_timeout /= as->as_ctx->ac_nscount;
+	if (as->as_timeout < 1000)
+		as->as_timeout = 1000;
+
+	return (0);
+}
blob - /dev/null
blob + 61fa3e2fbc97a49a6db32d3ad20d8d895f842dae (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/sethostent.c
@@ -0,0 +1,36 @@
+/*	$OpenBSD: sethostent.c,v 1.2 2018/04/28 15:09:35 schwarze Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <netdb.h>
+#include <stddef.h>
+
+void
+sethostent(int stayopen)
+{
+}
+
+void
+endhostent(void)
+{
+}
+
+struct hostent *
+gethostent(void)
+{
+	h_errno = NETDB_INTERNAL;
+	return NULL;
+}
blob - /dev/null
blob + 6edbcd71ba3bf35f4f6ac3f8731eb22ac5029d91 (mode 644)
--- /dev/null
+++ openbsd-compat/libasr/thread_private.h
@@ -0,0 +1,9 @@
+/*
+ *
+ */
+#define __is_threaded (0)
+#define _THREAD_PRIVATE_MUTEX(x)
+#define _THREAD_PRIVATE_MUTEX_LOCK(x)
+#define _THREAD_PRIVATE_MUTEX_UNLOCK(x)
+#define _THREAD_PRIVATE(a, b, c)  (c)
+#define _THREAD_PRIVATE_DT(a, b, c, d) (d)
blob - /dev/null
blob + 4ced2710643edfae8d1aedf1c9616da974571135 (mode 644)
--- /dev/null
+++ openbsd-compat/openbsd-compat.h
@@ -0,0 +1,110 @@
+/* $Id: openbsd-compat.h,v 1.51 2010/10/07 10:25:29 djm Exp $ */
+
+/*
+ * Copyright (c) 1999-2003 Damien Miller.  All rights reserved.
+ * Copyright (c) 2003 Ben Lindstrom. All rights reserved.
+ * Copyright (c) 2002 Tim Rice.  All rights reserved.
+ *
+ * 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 AUTHOR ``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 AUTHOR 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 <stdlib.h>
+
+#ifdef NEED_EXPLICIT_BZERO
+void explicit_bzero(void *p, size_t n);
+#endif
+#ifdef NEED_RECALLOCARRAY
+void *recallocarray(void *, size_t, size_t, size_t);
+#endif
+#ifdef NEED_REALLOCARRAY
+void *reallocarray(void *, size_t, size_t);
+#endif
+#ifdef NEED_STRLCAT
+size_t strlcat(char *dst, const char *src, size_t size);
+#endif
+#ifdef NEED_STRLCPY
+size_t strlcpy(char *dst, const char *src, size_t size);
+#endif
+#ifdef NEED_STRTONUM
+long long strtonum(const char *nptr, long long minval, long long maxval, const char **errstr);
+#endif
+#ifdef NEED_PLEDGE
+static inline int
+pledge(const char *promises, const char *execpromises)
+{
+	return 0;
+}
+#endif
+#ifdef NEED_INET_NET_PTON
+int
+inet_net_pton(int af, const char *src, void *dst, size_t size);
+#endif
+#ifdef NEED_FGETLN
+#include <stdio.h>
+#include <string.h>
+char * fgetln(FILE *stream, size_t *len);
+#endif
+#ifdef NEED_LIBASR
+struct event_asr;
+void event_asr_abort(struct event_asr *);
+
+/* From OpenNTPD portable */
+#if !defined(SA_LEN)
+# if defined(HAVE_STRUCT_SOCKADDR_SA_LEN)
+#  define SA_LEN(x)	((x)->sa_len)
+# else
+#  define SA_LEN(x)     ((x)->sa_family == AF_INET6 ? \
+			sizeof(struct sockaddr_in6) : \
+			sizeof(struct sockaddr_in))
+# endif
+#endif
+#endif
+
+/* From OpenBGPD portable */
+#if !defined(SS_LEN)
+# if defined(HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN)
+#  define SS_LEN(x)  ((x)->ss_len)
+# else
+#  define SS_LEN(x)  SA_LEN((struct sockaddr *)(x))
+# endif
+#endif
+
+#ifdef HAVE_SS_LEN
+# define STORAGE_LEN(X) ((X).ss_len)
+# define SET_STORAGE_LEN(X, Y) do { STORAGE_LEN(X) = (Y); } while(0)
+#elif defined(HAVE___SS_LEN)
+# define STORAGE_LEN(X) ((X).__ss_len)
+# define SET_STORAGE_LEN(X, Y) do { STORAGE_LEN(X) = (Y); } while(0)
+#else
+# define STORAGE_LEN(X) (STORAGE_FAMILY(X) == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))
+# define SET_STORAGE_LEN(X, Y) (void) 0
+#endif
+
+#ifndef timespecsub
+#define timespecsub(a, b, result)				\
+   do {								\
+      (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;		\
+      (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec;		\
+      if ((result)->tv_nsec < 0) {				\
+	 --(result)->tv_sec;					\
+	 (result)->tv_nsec += 1000000000L;			\
+      }								\
+   } while (0)
+#endif
blob - /dev/null
blob + 840171219741eae591837d011ac207ea241b3507 (mode 644)
--- /dev/null
+++ openbsd-compat/reallocarray.c
@@ -0,0 +1,42 @@
+/*	$OpenBSD: reallocarray.c,v 1.1 2014/05/08 21:43:49 deraadt Exp $	*/
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/stdlib/reallocarray.c */
+
+#include "openbsd-compat.h"
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW	(1UL << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+	if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+	    nmemb > 0 && SIZE_MAX / nmemb < size) {
+		errno = ENOMEM;
+		return NULL;
+	}
+	return realloc(optr, size * nmemb);
+}
blob - /dev/null
blob + df7a23bb369a3be4684464cc641b4509badb6ae0 (mode 644)
--- /dev/null
+++ openbsd-compat/recallocarray.c
@@ -0,0 +1,84 @@
+/*	$OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $	*/
+/*
+ * Copyright (c) 2008, 2017 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/stdlib/recallocarray.c */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "openbsd-compat.h"
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size)
+{
+	size_t oldsize, newsize;
+	void *newptr;
+
+	if (ptr == NULL)
+		return calloc(newnmemb, size);
+
+	if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+	    newnmemb > 0 && SIZE_MAX / newnmemb < size) {
+		errno = ENOMEM;
+		return NULL;
+	}
+	newsize = newnmemb * size;
+
+	if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+	    oldnmemb > 0 && SIZE_MAX / oldnmemb < size) {
+		errno = EINVAL;
+		return NULL;
+	}
+	oldsize = oldnmemb * size;
+	
+	/*
+	 * Don't bother too much if we're shrinking just a bit,
+	 * we do not shrink for series of small steps, oh well.
+	 */
+	if (newsize <= oldsize) {
+		size_t d = oldsize - newsize;
+
+		if (d < oldsize / 2 && d < (size_t)getpagesize()) {
+			memset((char *)ptr + newsize, 0, d);
+			return ptr;
+		}
+	}
+
+	newptr = malloc(newsize);
+	if (newptr == NULL)
+		return NULL;
+
+	if (newsize > oldsize) {
+		memcpy(newptr, ptr, oldsize);
+		memset((char *)newptr + oldsize, 0, newsize - oldsize);
+	} else
+		memcpy(newptr, ptr, newsize);
+
+	explicit_bzero(ptr, oldsize);
+	free(ptr);
+
+	return newptr;
+}
blob - /dev/null
blob + 53b6d0bec6d5dd2cf949b74d769903e073196b6e (mode 644)
--- /dev/null
+++ openbsd-compat/strlcat.c
@@ -0,0 +1,59 @@
+/*	$OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $	*/
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/string/strlcat.c */
+
+#include "openbsd-compat.h"
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left).  At most siz-1 characters
+ * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+	char *d = dst;
+	const char *s = src;
+	size_t n = siz;
+	size_t dlen;
+
+	/* Find the end of dst and adjust bytes left but don't go past end */
+	while (n-- != 0 && *d != '\0')
+		d++;
+	dlen = d - dst;
+	n = siz - dlen;
+
+	if (n == 0)
+		return(dlen + strlen(s));
+	while (*s != '\0') {
+		if (n != 1) {
+			*d++ = *s;
+			n--;
+		}
+		s++;
+	}
+	*d = '\0';
+
+	return(dlen + (s - src));	/* count does not include NUL */
+}
blob - /dev/null
blob + b393c327d29109876ac8ec112af1a2ad58f3cce6 (mode 644)
--- /dev/null
+++ openbsd-compat/strlcpy.c
@@ -0,0 +1,55 @@
+/*	$OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $	*/
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/string/strlcpy.c */
+
+#include "openbsd-compat.h"
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz.  At most siz-1 characters
+ * will be copied.  Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+	char *d = dst;
+	const char *s = src;
+	size_t n = siz;
+
+	/* Copy as many bytes as will fit */
+	if (n != 0) {
+		while (--n != 0) {
+			if ((*d++ = *s++) == '\0')
+				break;
+		}
+	}
+
+	/* Not enough room in dst, add NUL and traverse rest of src */
+	if (n == 0) {
+		if (siz != 0)
+			*d = '\0';		/* NUL-terminate dst */
+		while (*s++)
+			;
+	}
+
+	return(s - src - 1);	/* count does not include NUL */
+}
blob - /dev/null
blob + bb37f2eff144c2e8d273be0f4ddc07134712d929 (mode 644)
--- /dev/null
+++ openbsd-compat/strtonum.c
@@ -0,0 +1,72 @@
+/*	$OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $	*/
+
+/*
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/stdlib/strtonum.c */
+
+#include "openbsd-compat.h"
+
+#ifndef HAVE_STRTONUM
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+
+#define INVALID 	1
+#define TOOSMALL 	2
+#define TOOLARGE 	3
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+    const char **errstrp)
+{
+	long long ll = 0;
+	char *ep;
+	int error = 0;
+	struct errval {
+		const char *errstr;
+		int err;
+	} ev[4] = {
+		{ NULL,		0 },
+		{ "invalid",	EINVAL },
+		{ "too small",	ERANGE },
+		{ "too large",	ERANGE },
+	};
+
+	ev[0].err = errno;
+	errno = 0;
+	if (minval > maxval)
+		error = INVALID;
+	else {
+		ll = strtoll(numstr, &ep, 10);
+		if (numstr == ep || *ep != '\0')
+			error = INVALID;
+		else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+			error = TOOSMALL;
+		else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+			error = TOOLARGE;
+	}
+	if (errstrp != NULL)
+		*errstrp = ev[error].errstr;
+	errno = ev[error].err;
+	if (error)
+		ll = 0;
+
+	return (ll);
+}
+
+#endif /* HAVE_STRTONUM */
blob - /dev/null
blob + 28aaaa37a8ce5f186d0efae40695fe4bd5c7fde4 (mode 644)
--- /dev/null
+++ openbsd-compat/sys/queue.h
@@ -0,0 +1,653 @@
+/*	$OpenBSD: queue.h,v 1.36 2012/04/11 13:29:14 naddy Exp $	*/
+/*	$NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $	*/
+
+/*
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ *	@(#)queue.h	8.5 (Berkeley) 8/20/94
+ */
+
+/* OPENBSD ORIGINAL: sys/sys/queue.h */
+
+#ifndef	_FAKE_QUEUE_H_
+#define	_FAKE_QUEUE_H_
+
+/*
+ * Require for OS/X and other platforms that have old/broken/incomplete
+ * <sys/queue.h>.
+ */
+#undef SLIST_HEAD
+#undef SLIST_HEAD_INITIALIZER
+#undef SLIST_ENTRY
+#undef SLIST_FOREACH_PREVPTR
+#undef SLIST_FIRST
+#undef SLIST_END
+#undef SLIST_EMPTY
+#undef SLIST_NEXT
+#undef SLIST_FOREACH
+#undef SLIST_INIT
+#undef SLIST_INSERT_AFTER
+#undef SLIST_INSERT_HEAD
+#undef SLIST_REMOVE_HEAD
+#undef SLIST_REMOVE
+#undef SLIST_REMOVE_NEXT
+#undef LIST_HEAD
+#undef LIST_HEAD_INITIALIZER
+#undef LIST_ENTRY
+#undef LIST_FIRST
+#undef LIST_END
+#undef LIST_EMPTY
+#undef LIST_NEXT
+#undef LIST_FOREACH
+#undef LIST_INIT
+#undef LIST_INSERT_AFTER
+#undef LIST_INSERT_BEFORE
+#undef LIST_INSERT_HEAD
+#undef LIST_REMOVE
+#undef LIST_REPLACE
+#undef SIMPLEQ_HEAD
+#undef SIMPLEQ_HEAD_INITIALIZER
+#undef SIMPLEQ_ENTRY
+#undef SIMPLEQ_FIRST
+#undef SIMPLEQ_END
+#undef SIMPLEQ_EMPTY
+#undef SIMPLEQ_NEXT
+#undef SIMPLEQ_FOREACH
+#undef SIMPLEQ_INIT
+#undef SIMPLEQ_INSERT_HEAD
+#undef SIMPLEQ_INSERT_TAIL
+#undef SIMPLEQ_INSERT_AFTER
+#undef SIMPLEQ_REMOVE_HEAD
+#undef TAILQ_HEAD
+#undef TAILQ_HEAD_INITIALIZER
+#undef TAILQ_ENTRY
+#undef TAILQ_FIRST
+#undef TAILQ_END
+#undef TAILQ_NEXT
+#undef TAILQ_LAST
+#undef TAILQ_PREV
+#undef TAILQ_EMPTY
+#undef TAILQ_FOREACH
+#undef TAILQ_FOREACH_REVERSE
+#undef TAILQ_INIT
+#undef TAILQ_INSERT_HEAD
+#undef TAILQ_INSERT_TAIL
+#undef TAILQ_INSERT_AFTER
+#undef TAILQ_INSERT_BEFORE
+#undef TAILQ_REMOVE
+#undef TAILQ_REPLACE
+#undef CIRCLEQ_HEAD
+#undef CIRCLEQ_HEAD_INITIALIZER
+#undef CIRCLEQ_ENTRY
+#undef CIRCLEQ_FIRST
+#undef CIRCLEQ_LAST
+#undef CIRCLEQ_END
+#undef CIRCLEQ_NEXT
+#undef CIRCLEQ_PREV
+#undef CIRCLEQ_EMPTY
+#undef CIRCLEQ_FOREACH
+#undef CIRCLEQ_FOREACH_REVERSE
+#undef CIRCLEQ_INIT
+#undef CIRCLEQ_INSERT_AFTER
+#undef CIRCLEQ_INSERT_BEFORE
+#undef CIRCLEQ_INSERT_HEAD
+#undef CIRCLEQ_INSERT_TAIL
+#undef CIRCLEQ_REMOVE
+#undef CIRCLEQ_REPLACE
+
+/*
+ * This file defines five types of data structures: singly-linked lists, 
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction.  Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALIDATE(a) (a) = ((void *)-1)
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type)						\
+struct name {								\
+	struct type *slh_first;	/* first element */			\
+}
+ 
+#define	SLIST_HEAD_INITIALIZER(head)					\
+	{ NULL }
+ 
+#define SLIST_ENTRY(type)						\
+struct {								\
+	struct type *sle_next;	/* next element */			\
+}
+ 
+/*
+ * Singly-linked List access methods.
+ */
+#define	SLIST_FIRST(head)	((head)->slh_first)
+#define	SLIST_END(head)		NULL
+#define	SLIST_EMPTY(head)	(SLIST_FIRST(head) == SLIST_END(head))
+#define	SLIST_NEXT(elm, field)	((elm)->field.sle_next)
+
+#define	SLIST_FOREACH(var, head, field)					\
+	for((var) = SLIST_FIRST(head);					\
+	    (var) != SLIST_END(head);					\
+	    (var) = SLIST_NEXT(var, field))
+
+#define	SLIST_FOREACH_SAFE(var, head, field, tvar)			\
+	for ((var) = SLIST_FIRST(head);				\
+	    (var) && ((tvar) = SLIST_NEXT(var, field), 1);		\
+	    (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define	SLIST_INIT(head) {						\
+	SLIST_FIRST(head) = SLIST_END(head);				\
+}
+
+#define	SLIST_INSERT_AFTER(slistelm, elm, field) do {			\
+	(elm)->field.sle_next = (slistelm)->field.sle_next;		\
+	(slistelm)->field.sle_next = (elm);				\
+} while (0)
+
+#define	SLIST_INSERT_HEAD(head, elm, field) do {			\
+	(elm)->field.sle_next = (head)->slh_first;			\
+	(head)->slh_first = (elm);					\
+} while (0)
+
+#define	SLIST_REMOVE_AFTER(elm, field) do {				\
+	(elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;	\
+} while (0)
+
+#define	SLIST_REMOVE_HEAD(head, field) do {				\
+	(head)->slh_first = (head)->slh_first->field.sle_next;		\
+} while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do {			\
+	if ((head)->slh_first == (elm)) {				\
+		SLIST_REMOVE_HEAD((head), field);			\
+	} else {							\
+		struct type *curelm = (head)->slh_first;		\
+									\
+		while (curelm->field.sle_next != (elm))			\
+			curelm = curelm->field.sle_next;		\
+		curelm->field.sle_next =				\
+		    curelm->field.sle_next->field.sle_next;		\
+		_Q_INVALIDATE((elm)->field.sle_next);			\
+	}								\
+} while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type)						\
+struct name {								\
+	struct type *lh_first;	/* first element */			\
+}
+
+#define LIST_HEAD_INITIALIZER(head)					\
+	{ NULL }
+
+#define LIST_ENTRY(type)						\
+struct {								\
+	struct type *le_next;	/* next element */			\
+	struct type **le_prev;	/* address of previous next element */	\
+}
+
+/*
+ * List access methods
+ */
+#define	LIST_FIRST(head)		((head)->lh_first)
+#define	LIST_END(head)			NULL
+#define	LIST_EMPTY(head)		(LIST_FIRST(head) == LIST_END(head))
+#define	LIST_NEXT(elm, field)		((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field)					\
+	for((var) = LIST_FIRST(head);					\
+	    (var)!= LIST_END(head);					\
+	    (var) = LIST_NEXT(var, field))
+
+#define	LIST_FOREACH_SAFE(var, head, field, tvar)			\
+	for ((var) = LIST_FIRST(head);				\
+	    (var) && ((tvar) = LIST_NEXT(var, field), 1);		\
+	    (var) = (tvar))
+
+/*
+ * List functions.
+ */
+#define	LIST_INIT(head) do {						\
+	LIST_FIRST(head) = LIST_END(head);				\
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do {			\
+	if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)	\
+		(listelm)->field.le_next->field.le_prev =		\
+		    &(elm)->field.le_next;				\
+	(listelm)->field.le_next = (elm);				\
+	(elm)->field.le_prev = &(listelm)->field.le_next;		\
+} while (0)
+
+#define	LIST_INSERT_BEFORE(listelm, elm, field) do {			\
+	(elm)->field.le_prev = (listelm)->field.le_prev;		\
+	(elm)->field.le_next = (listelm);				\
+	*(listelm)->field.le_prev = (elm);				\
+	(listelm)->field.le_prev = &(elm)->field.le_next;		\
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do {				\
+	if (((elm)->field.le_next = (head)->lh_first) != NULL)		\
+		(head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+	(head)->lh_first = (elm);					\
+	(elm)->field.le_prev = &(head)->lh_first;			\
+} while (0)
+
+#define LIST_REMOVE(elm, field) do {					\
+	if ((elm)->field.le_next != NULL)				\
+		(elm)->field.le_next->field.le_prev =			\
+		    (elm)->field.le_prev;				\
+	*(elm)->field.le_prev = (elm)->field.le_next;			\
+	_Q_INVALIDATE((elm)->field.le_prev);				\
+	_Q_INVALIDATE((elm)->field.le_next);				\
+} while (0)
+
+#define LIST_REPLACE(elm, elm2, field) do {				\
+	if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)	\
+		(elm2)->field.le_next->field.le_prev =			\
+		    &(elm2)->field.le_next;				\
+	(elm2)->field.le_prev = (elm)->field.le_prev;			\
+	*(elm2)->field.le_prev = (elm2);				\
+	_Q_INVALIDATE((elm)->field.le_prev);				\
+	_Q_INVALIDATE((elm)->field.le_next);				\
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type)					\
+struct name {								\
+	struct type *sqh_first;	/* first element */			\
+	struct type **sqh_last;	/* addr of last next element */		\
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head)					\
+	{ NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type)						\
+struct {								\
+	struct type *sqe_next;	/* next element */			\
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define	SIMPLEQ_FIRST(head)	    ((head)->sqh_first)
+#define	SIMPLEQ_END(head)	    NULL
+#define	SIMPLEQ_EMPTY(head)	    (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define	SIMPLEQ_NEXT(elm, field)    ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field)				\
+	for((var) = SIMPLEQ_FIRST(head);				\
+	    (var) != SIMPLEQ_END(head);					\
+	    (var) = SIMPLEQ_NEXT(var, field))
+
+#define	SIMPLEQ_FOREACH_SAFE(var, head, field, tvar)			\
+	for ((var) = SIMPLEQ_FIRST(head);				\
+	    (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1);		\
+	    (var) = (tvar))
+
+/*
+ * Simple queue functions.
+ */
+#define	SIMPLEQ_INIT(head) do {						\
+	(head)->sqh_first = NULL;					\
+	(head)->sqh_last = &(head)->sqh_first;				\
+} while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do {			\
+	if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)	\
+		(head)->sqh_last = &(elm)->field.sqe_next;		\
+	(head)->sqh_first = (elm);					\
+} while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do {			\
+	(elm)->field.sqe_next = NULL;					\
+	*(head)->sqh_last = (elm);					\
+	(head)->sqh_last = &(elm)->field.sqe_next;			\
+} while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\
+	if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+		(head)->sqh_last = &(elm)->field.sqe_next;		\
+	(listelm)->field.sqe_next = (elm);				\
+} while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do {			\
+	if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+		(head)->sqh_last = &(head)->sqh_first;			\
+} while (0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do {			\
+	if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+	    == NULL)							\
+		(head)->sqh_last = &(elm)->field.sqe_next;		\
+} while (0)
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type)						\
+struct name {								\
+	struct type *tqh_first;	/* first element */			\
+	struct type **tqh_last;	/* addr of last next element */		\
+}
+
+#define TAILQ_HEAD_INITIALIZER(head)					\
+	{ NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type)						\
+struct {								\
+	struct type *tqe_next;	/* next element */			\
+	struct type **tqe_prev;	/* address of previous next element */	\
+}
+
+/* 
+ * tail queue access methods 
+ */
+#define	TAILQ_FIRST(head)		((head)->tqh_first)
+#define	TAILQ_END(head)			NULL
+#define	TAILQ_NEXT(elm, field)		((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname)					\
+	(*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field)				\
+	(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define	TAILQ_EMPTY(head)						\
+	(TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field)					\
+	for((var) = TAILQ_FIRST(head);					\
+	    (var) != TAILQ_END(head);					\
+	    (var) = TAILQ_NEXT(var, field))
+
+#define	TAILQ_FOREACH_SAFE(var, head, field, tvar)			\
+	for ((var) = TAILQ_FIRST(head);					\
+	    (var) != TAILQ_END(head) &&					\
+	    ((tvar) = TAILQ_NEXT(var, field), 1);			\
+	    (var) = (tvar))
+
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field)		\
+	for((var) = TAILQ_LAST(head, headname);				\
+	    (var) != TAILQ_END(head);					\
+	    (var) = TAILQ_PREV(var, headname, field))
+
+#define	TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)	\
+	for ((var) = TAILQ_LAST(head, headname);			\
+	    (var) != TAILQ_END(head) &&					\
+	    ((tvar) = TAILQ_PREV(var, headname, field), 1);		\
+	    (var) = (tvar))
+
+/*
+ * Tail queue functions.
+ */
+#define	TAILQ_INIT(head) do {						\
+	(head)->tqh_first = NULL;					\
+	(head)->tqh_last = &(head)->tqh_first;				\
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do {			\
+	if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)	\
+		(head)->tqh_first->field.tqe_prev =			\
+		    &(elm)->field.tqe_next;				\
+	else								\
+		(head)->tqh_last = &(elm)->field.tqe_next;		\
+	(head)->tqh_first = (elm);					\
+	(elm)->field.tqe_prev = &(head)->tqh_first;			\
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do {			\
+	(elm)->field.tqe_next = NULL;					\
+	(elm)->field.tqe_prev = (head)->tqh_last;			\
+	*(head)->tqh_last = (elm);					\
+	(head)->tqh_last = &(elm)->field.tqe_next;			\
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {		\
+	if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+		(elm)->field.tqe_next->field.tqe_prev =			\
+		    &(elm)->field.tqe_next;				\
+	else								\
+		(head)->tqh_last = &(elm)->field.tqe_next;		\
+	(listelm)->field.tqe_next = (elm);				\
+	(elm)->field.tqe_prev = &(listelm)->field.tqe_next;		\
+} while (0)
+
+#define	TAILQ_INSERT_BEFORE(listelm, elm, field) do {			\
+	(elm)->field.tqe_prev = (listelm)->field.tqe_prev;		\
+	(elm)->field.tqe_next = (listelm);				\
+	*(listelm)->field.tqe_prev = (elm);				\
+	(listelm)->field.tqe_prev = &(elm)->field.tqe_next;		\
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do {				\
+	if (((elm)->field.tqe_next) != NULL)				\
+		(elm)->field.tqe_next->field.tqe_prev =			\
+		    (elm)->field.tqe_prev;				\
+	else								\
+		(head)->tqh_last = (elm)->field.tqe_prev;		\
+	*(elm)->field.tqe_prev = (elm)->field.tqe_next;			\
+	_Q_INVALIDATE((elm)->field.tqe_prev);				\
+	_Q_INVALIDATE((elm)->field.tqe_next);				\
+} while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do {			\
+	if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)	\
+		(elm2)->field.tqe_next->field.tqe_prev =		\
+		    &(elm2)->field.tqe_next;				\
+	else								\
+		(head)->tqh_last = &(elm2)->field.tqe_next;		\
+	(elm2)->field.tqe_prev = (elm)->field.tqe_prev;			\
+	*(elm2)->field.tqe_prev = (elm2);				\
+	_Q_INVALIDATE((elm)->field.tqe_prev);				\
+	_Q_INVALIDATE((elm)->field.tqe_next);				\
+} while (0)
+
+/*
+ * Circular queue definitions.
+ */
+#define CIRCLEQ_HEAD(name, type)					\
+struct name {								\
+	struct type *cqh_first;		/* first element */		\
+	struct type *cqh_last;		/* last element */		\
+}
+
+#define CIRCLEQ_HEAD_INITIALIZER(head)					\
+	{ CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
+
+#define CIRCLEQ_ENTRY(type)						\
+struct {								\
+	struct type *cqe_next;		/* next element */		\
+	struct type *cqe_prev;		/* previous element */		\
+}
+
+/*
+ * Circular queue access methods 
+ */
+#define	CIRCLEQ_FIRST(head)		((head)->cqh_first)
+#define	CIRCLEQ_LAST(head)		((head)->cqh_last)
+#define	CIRCLEQ_END(head)		((void *)(head))
+#define	CIRCLEQ_NEXT(elm, field)	((elm)->field.cqe_next)
+#define	CIRCLEQ_PREV(elm, field)	((elm)->field.cqe_prev)
+#define	CIRCLEQ_EMPTY(head)						\
+	(CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
+
+#define CIRCLEQ_FOREACH(var, head, field)				\
+	for((var) = CIRCLEQ_FIRST(head);				\
+	    (var) != CIRCLEQ_END(head);					\
+	    (var) = CIRCLEQ_NEXT(var, field))
+
+#define	CIRCLEQ_FOREACH_SAFE(var, head, field, tvar)			\
+	for ((var) = CIRCLEQ_FIRST(head);				\
+	    (var) != CIRCLEQ_END(head) &&				\
+	    ((tvar) = CIRCLEQ_NEXT(var, field), 1);			\
+	    (var) = (tvar))
+
+#define CIRCLEQ_FOREACH_REVERSE(var, head, field)			\
+	for((var) = CIRCLEQ_LAST(head);					\
+	    (var) != CIRCLEQ_END(head);					\
+	    (var) = CIRCLEQ_PREV(var, field))
+
+#define	CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)	\
+	for ((var) = CIRCLEQ_LAST(head, headname);			\
+	    (var) != CIRCLEQ_END(head) && 				\
+	    ((tvar) = CIRCLEQ_PREV(var, headname, field), 1);		\
+	    (var) = (tvar))
+
+/*
+ * Circular queue functions.
+ */
+#define	CIRCLEQ_INIT(head) do {						\
+	(head)->cqh_first = CIRCLEQ_END(head);				\
+	(head)->cqh_last = CIRCLEQ_END(head);				\
+} while (0)
+
+#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\
+	(elm)->field.cqe_next = (listelm)->field.cqe_next;		\
+	(elm)->field.cqe_prev = (listelm);				\
+	if ((listelm)->field.cqe_next == CIRCLEQ_END(head))		\
+		(head)->cqh_last = (elm);				\
+	else								\
+		(listelm)->field.cqe_next->field.cqe_prev = (elm);	\
+	(listelm)->field.cqe_next = (elm);				\
+} while (0)
+
+#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {		\
+	(elm)->field.cqe_next = (listelm);				\
+	(elm)->field.cqe_prev = (listelm)->field.cqe_prev;		\
+	if ((listelm)->field.cqe_prev == CIRCLEQ_END(head))		\
+		(head)->cqh_first = (elm);				\
+	else								\
+		(listelm)->field.cqe_prev->field.cqe_next = (elm);	\
+	(listelm)->field.cqe_prev = (elm);				\
+} while (0)
+
+#define CIRCLEQ_INSERT_HEAD(head, elm, field) do {			\
+	(elm)->field.cqe_next = (head)->cqh_first;			\
+	(elm)->field.cqe_prev = CIRCLEQ_END(head);			\
+	if ((head)->cqh_last == CIRCLEQ_END(head))			\
+		(head)->cqh_last = (elm);				\
+	else								\
+		(head)->cqh_first->field.cqe_prev = (elm);		\
+	(head)->cqh_first = (elm);					\
+} while (0)
+
+#define CIRCLEQ_INSERT_TAIL(head, elm, field) do {			\
+	(elm)->field.cqe_next = CIRCLEQ_END(head);			\
+	(elm)->field.cqe_prev = (head)->cqh_last;			\
+	if ((head)->cqh_first == CIRCLEQ_END(head))			\
+		(head)->cqh_first = (elm);				\
+	else								\
+		(head)->cqh_last->field.cqe_next = (elm);		\
+	(head)->cqh_last = (elm);					\
+} while (0)
+
+#define	CIRCLEQ_REMOVE(head, elm, field) do {				\
+	if ((elm)->field.cqe_next == CIRCLEQ_END(head))			\
+		(head)->cqh_last = (elm)->field.cqe_prev;		\
+	else								\
+		(elm)->field.cqe_next->field.cqe_prev =			\
+		    (elm)->field.cqe_prev;				\
+	if ((elm)->field.cqe_prev == CIRCLEQ_END(head))			\
+		(head)->cqh_first = (elm)->field.cqe_next;		\
+	else								\
+		(elm)->field.cqe_prev->field.cqe_next =			\
+		    (elm)->field.cqe_next;				\
+	_Q_INVALIDATE((elm)->field.cqe_prev);				\
+	_Q_INVALIDATE((elm)->field.cqe_next);				\
+} while (0)
+
+#define CIRCLEQ_REPLACE(head, elm, elm2, field) do {			\
+	if (((elm2)->field.cqe_next = (elm)->field.cqe_next) ==		\
+	    CIRCLEQ_END(head))						\
+		(head).cqh_last = (elm2);				\
+	else								\
+		(elm2)->field.cqe_next->field.cqe_prev = (elm2);	\
+	if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) ==		\
+	    CIRCLEQ_END(head))						\
+		(head).cqh_first = (elm2);				\
+	else								\
+		(elm2)->field.cqe_prev->field.cqe_next = (elm2);	\
+	_Q_INVALIDATE((elm)->field.cqe_prev);				\
+	_Q_INVALIDATE((elm)->field.cqe_next);				\
+} while (0)
+
+#endif	/* !_FAKE_QUEUE_H_ */
blob - /dev/null
blob + 753b3cfd5979bc41f8370e63c245656d0bbaa611 (mode 644)
--- /dev/null
+++ openbsd-compat/sys/tree.h
@@ -0,0 +1,1006 @@
+/*	$OpenBSD: tree.h,v 1.29 2017/07/30 19:27:20 deraadt Exp $	*/
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * 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 AUTHOR ``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 AUTHOR 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.
+ */
+
+#ifndef	_SYS_TREE_H_
+#define	_SYS_TREE_H_
+
+#include <stddef.h>
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure.  Every operation
+ * on the tree causes a splay to happen.  The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree.  On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n).  The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute.  It fulfills a set of conditions:
+ *	- every search path from the root to a leaf consists of the
+ *	  same number of black nodes,
+ *	- each red node (except for the root) has a black parent,
+ *	- each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type)						\
+struct name {								\
+	struct type *sph_root; /* root of the tree */			\
+}
+
+#define SPLAY_INITIALIZER(root)						\
+	{ NULL }
+
+#define SPLAY_INIT(root) do {						\
+	(root)->sph_root = NULL;					\
+} while (0)
+
+#define SPLAY_ENTRY(type)						\
+struct {								\
+	struct type *spe_left; /* left element */			\
+	struct type *spe_right; /* right element */			\
+}
+
+#define SPLAY_LEFT(elm, field)		(elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field)		(elm)->field.spe_right
+#define SPLAY_ROOT(head)		(head)->sph_root
+#define SPLAY_EMPTY(head)		(SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) do {			\
+	SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field);	\
+	SPLAY_RIGHT(tmp, field) = (head)->sph_root;			\
+	(head)->sph_root = tmp;						\
+} while (0)
+
+#define SPLAY_ROTATE_LEFT(head, tmp, field) do {			\
+	SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field);	\
+	SPLAY_LEFT(tmp, field) = (head)->sph_root;			\
+	(head)->sph_root = tmp;						\
+} while (0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) do {				\
+	SPLAY_LEFT(tmp, field) = (head)->sph_root;			\
+	tmp = (head)->sph_root;						\
+	(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);		\
+} while (0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) do {				\
+	SPLAY_RIGHT(tmp, field) = (head)->sph_root;			\
+	tmp = (head)->sph_root;						\
+	(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);	\
+} while (0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) do {		\
+	SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field);	\
+	SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
+	SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field);	\
+	SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field);	\
+} while (0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp)				\
+void name##_SPLAY(struct name *, struct type *);			\
+void name##_SPLAY_MINMAX(struct name *, int);				\
+struct type *name##_SPLAY_INSERT(struct name *, struct type *);		\
+struct type *name##_SPLAY_REMOVE(struct name *, struct type *);		\
+									\
+/* Finds the node with the same key as elm */				\
+static __unused __inline struct type *					\
+name##_SPLAY_FIND(struct name *head, struct type *elm)			\
+{									\
+	if (SPLAY_EMPTY(head))						\
+		return(NULL);						\
+	name##_SPLAY(head, elm);					\
+	if ((cmp)(elm, (head)->sph_root) == 0)				\
+		return (head->sph_root);				\
+	return (NULL);							\
+}									\
+									\
+static __unused __inline struct type *					\
+name##_SPLAY_NEXT(struct name *head, struct type *elm)			\
+{									\
+	name##_SPLAY(head, elm);					\
+	if (SPLAY_RIGHT(elm, field) != NULL) {				\
+		elm = SPLAY_RIGHT(elm, field);				\
+		while (SPLAY_LEFT(elm, field) != NULL) {		\
+			elm = SPLAY_LEFT(elm, field);			\
+		}							\
+	} else								\
+		elm = NULL;						\
+	return (elm);							\
+}									\
+									\
+static __unused __inline struct type *					\
+name##_SPLAY_MIN_MAX(struct name *head, int val)			\
+{									\
+	name##_SPLAY_MINMAX(head, val);					\
+        return (SPLAY_ROOT(head));					\
+}
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp)				\
+struct type *								\
+name##_SPLAY_INSERT(struct name *head, struct type *elm)		\
+{									\
+    if (SPLAY_EMPTY(head)) {						\
+	    SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL;	\
+    } else {								\
+	    int __comp;							\
+	    name##_SPLAY(head, elm);					\
+	    __comp = (cmp)(elm, (head)->sph_root);			\
+	    if(__comp < 0) {						\
+		    SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
+		    SPLAY_RIGHT(elm, field) = (head)->sph_root;		\
+		    SPLAY_LEFT((head)->sph_root, field) = NULL;		\
+	    } else if (__comp > 0) {					\
+		    SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
+		    SPLAY_LEFT(elm, field) = (head)->sph_root;		\
+		    SPLAY_RIGHT((head)->sph_root, field) = NULL;	\
+	    } else							\
+		    return ((head)->sph_root);				\
+    }									\
+    (head)->sph_root = (elm);						\
+    return (NULL);							\
+}									\
+									\
+struct type *								\
+name##_SPLAY_REMOVE(struct name *head, struct type *elm)		\
+{									\
+	struct type *__tmp;						\
+	if (SPLAY_EMPTY(head))						\
+		return (NULL);						\
+	name##_SPLAY(head, elm);					\
+	if ((cmp)(elm, (head)->sph_root) == 0) {			\
+		if (SPLAY_LEFT((head)->sph_root, field) == NULL) {	\
+			(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
+		} else {						\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
+			name##_SPLAY(head, elm);			\
+			SPLAY_RIGHT((head)->sph_root, field) = __tmp;	\
+		}							\
+		return (elm);						\
+	}								\
+	return (NULL);							\
+}									\
+									\
+void									\
+name##_SPLAY(struct name *head, struct type *elm)			\
+{									\
+	struct type __node, *__left, *__right, *__tmp;			\
+	int __comp;							\
+\
+	SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+	__left = __right = &__node;					\
+\
+	while ((__comp = (cmp)(elm, (head)->sph_root))) {		\
+		if (__comp < 0) {					\
+			__tmp = SPLAY_LEFT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if ((cmp)(elm, __tmp) < 0){			\
+				SPLAY_ROTATE_RIGHT(head, __tmp, field);	\
+				if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKLEFT(head, __right, field);		\
+		} else if (__comp > 0) {				\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if ((cmp)(elm, __tmp) > 0){			\
+				SPLAY_ROTATE_LEFT(head, __tmp, field);	\
+				if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKRIGHT(head, __left, field);		\
+		}							\
+	}								\
+	SPLAY_ASSEMBLE(head, &__node, __left, __right, field);		\
+}									\
+									\
+/* Splay with either the minimum or the maximum element			\
+ * Used to find minimum or maximum element in tree.			\
+ */									\
+void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+{									\
+	struct type __node, *__left, *__right, *__tmp;			\
+\
+	SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+	__left = __right = &__node;					\
+\
+	while (1) {							\
+		if (__comp < 0) {					\
+			__tmp = SPLAY_LEFT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if (__comp < 0){				\
+				SPLAY_ROTATE_RIGHT(head, __tmp, field);	\
+				if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKLEFT(head, __right, field);		\
+		} else if (__comp > 0) {				\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if (__comp > 0) {				\
+				SPLAY_ROTATE_LEFT(head, __tmp, field);	\
+				if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKRIGHT(head, __left, field);		\
+		}							\
+	}								\
+	SPLAY_ASSEMBLE(head, &__node, __left, __right, field);		\
+}
+
+#define SPLAY_NEGINF	-1
+#define SPLAY_INF	1
+
+#define SPLAY_INSERT(name, x, y)	name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y)	name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y)		name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y)		name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x)		(SPLAY_EMPTY(x) ? NULL	\
+					: name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x)		(SPLAY_EMPTY(x) ? NULL	\
+					: name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head)					\
+	for ((x) = SPLAY_MIN(name, head);				\
+	     (x) != NULL;						\
+	     (x) = SPLAY_NEXT(name, head, x))
+
+/* Macros that define a red-black tree */
+#define RB_HEAD(name, type)						\
+struct name {								\
+	struct type *rbh_root; /* root of the tree */			\
+}
+
+#define RB_INITIALIZER(root)						\
+	{ NULL }
+
+#define RB_INIT(root) do {						\
+	(root)->rbh_root = NULL;					\
+} while (0)
+
+#define RB_BLACK	0
+#define RB_RED		1
+#define RB_ENTRY(type)							\
+struct {								\
+	struct type *rbe_left;		/* left element */		\
+	struct type *rbe_right;		/* right element */		\
+	struct type *rbe_parent;	/* parent element */		\
+	int rbe_color;			/* node color */		\
+}
+
+#define RB_LEFT(elm, field)		(elm)->field.rbe_left
+#define RB_RIGHT(elm, field)		(elm)->field.rbe_right
+#define RB_PARENT(elm, field)		(elm)->field.rbe_parent
+#define RB_COLOR(elm, field)		(elm)->field.rbe_color
+#define RB_ROOT(head)			(head)->rbh_root
+#define RB_EMPTY(head)			(RB_ROOT(head) == NULL)
+
+#define RB_SET(elm, parent, field) do {					\
+	RB_PARENT(elm, field) = parent;					\
+	RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL;		\
+	RB_COLOR(elm, field) = RB_RED;					\
+} while (0)
+
+#define RB_SET_BLACKRED(black, red, field) do {				\
+	RB_COLOR(black, field) = RB_BLACK;				\
+	RB_COLOR(red, field) = RB_RED;					\
+} while (0)
+
+#ifndef RB_AUGMENT
+#define RB_AUGMENT(x)	do {} while (0)
+#endif
+
+#define RB_ROTATE_LEFT(head, elm, tmp, field) do {			\
+	(tmp) = RB_RIGHT(elm, field);					\
+	if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) {		\
+		RB_PARENT(RB_LEFT(tmp, field), field) = (elm);		\
+	}								\
+	RB_AUGMENT(elm);						\
+	if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {		\
+		if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))	\
+			RB_LEFT(RB_PARENT(elm, field), field) = (tmp);	\
+		else							\
+			RB_RIGHT(RB_PARENT(elm, field), field) = (tmp);	\
+	} else								\
+		(head)->rbh_root = (tmp);				\
+	RB_LEFT(tmp, field) = (elm);					\
+	RB_PARENT(elm, field) = (tmp);					\
+	RB_AUGMENT(tmp);						\
+	if ((RB_PARENT(tmp, field)))					\
+		RB_AUGMENT(RB_PARENT(tmp, field));			\
+} while (0)
+
+#define RB_ROTATE_RIGHT(head, elm, tmp, field) do {			\
+	(tmp) = RB_LEFT(elm, field);					\
+	if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) {		\
+		RB_PARENT(RB_RIGHT(tmp, field), field) = (elm);		\
+	}								\
+	RB_AUGMENT(elm);						\
+	if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {		\
+		if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))	\
+			RB_LEFT(RB_PARENT(elm, field), field) = (tmp);	\
+		else							\
+			RB_RIGHT(RB_PARENT(elm, field), field) = (tmp);	\
+	} else								\
+		(head)->rbh_root = (tmp);				\
+	RB_RIGHT(tmp, field) = (elm);					\
+	RB_PARENT(elm, field) = (tmp);					\
+	RB_AUGMENT(tmp);						\
+	if ((RB_PARENT(tmp, field)))					\
+		RB_AUGMENT(RB_PARENT(tmp, field));			\
+} while (0)
+
+/* Generates prototypes and inline functions */
+#define	RB_PROTOTYPE(name, type, field, cmp)				\
+	RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define	RB_PROTOTYPE_STATIC(name, type, field, cmp)			\
+	RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr)		\
+attr void name##_RB_INSERT_COLOR(struct name *, struct type *);		\
+attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
+attr struct type *name##_RB_REMOVE(struct name *, struct type *);	\
+attr struct type *name##_RB_INSERT(struct name *, struct type *);	\
+attr struct type *name##_RB_FIND(struct name *, struct type *);		\
+attr struct type *name##_RB_NFIND(struct name *, struct type *);	\
+attr struct type *name##_RB_NEXT(struct type *);			\
+attr struct type *name##_RB_PREV(struct type *);			\
+attr struct type *name##_RB_MINMAX(struct name *, int);			\
+									\
+
+/* Main rb operation.
+ * Moves node close to the key of elm to top
+ */
+#define	RB_GENERATE(name, type, field, cmp)				\
+	RB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define	RB_GENERATE_STATIC(name, type, field, cmp)			\
+	RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr)		\
+attr void								\
+name##_RB_INSERT_COLOR(struct name *head, struct type *elm)		\
+{									\
+	struct type *parent, *gparent, *tmp;				\
+	while ((parent = RB_PARENT(elm, field)) &&			\
+	    RB_COLOR(parent, field) == RB_RED) {			\
+		gparent = RB_PARENT(parent, field);			\
+		if (parent == RB_LEFT(gparent, field)) {		\
+			tmp = RB_RIGHT(gparent, field);			\
+			if (tmp && RB_COLOR(tmp, field) == RB_RED) {	\
+				RB_COLOR(tmp, field) = RB_BLACK;	\
+				RB_SET_BLACKRED(parent, gparent, field);\
+				elm = gparent;				\
+				continue;				\
+			}						\
+			if (RB_RIGHT(parent, field) == elm) {		\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				tmp = parent;				\
+				parent = elm;				\
+				elm = tmp;				\
+			}						\
+			RB_SET_BLACKRED(parent, gparent, field);	\
+			RB_ROTATE_RIGHT(head, gparent, tmp, field);	\
+		} else {						\
+			tmp = RB_LEFT(gparent, field);			\
+			if (tmp && RB_COLOR(tmp, field) == RB_RED) {	\
+				RB_COLOR(tmp, field) = RB_BLACK;	\
+				RB_SET_BLACKRED(parent, gparent, field);\
+				elm = gparent;				\
+				continue;				\
+			}						\
+			if (RB_LEFT(parent, field) == elm) {		\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				tmp = parent;				\
+				parent = elm;				\
+				elm = tmp;				\
+			}						\
+			RB_SET_BLACKRED(parent, gparent, field);	\
+			RB_ROTATE_LEFT(head, gparent, tmp, field);	\
+		}							\
+	}								\
+	RB_COLOR(head->rbh_root, field) = RB_BLACK;			\
+}									\
+									\
+attr void								\
+name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
+{									\
+	struct type *tmp;						\
+	while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) &&	\
+	    elm != RB_ROOT(head)) {					\
+		if (RB_LEFT(parent, field) == elm) {			\
+			tmp = RB_RIGHT(parent, field);			\
+			if (RB_COLOR(tmp, field) == RB_RED) {		\
+				RB_SET_BLACKRED(tmp, parent, field);	\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				tmp = RB_RIGHT(parent, field);		\
+			}						\
+			if ((RB_LEFT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+			    (RB_RIGHT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+				RB_COLOR(tmp, field) = RB_RED;		\
+				elm = parent;				\
+				parent = RB_PARENT(elm, field);		\
+			} else {					\
+				if (RB_RIGHT(tmp, field) == NULL ||	\
+				    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
+					struct type *oleft;		\
+					if ((oleft = RB_LEFT(tmp, field)))\
+						RB_COLOR(oleft, field) = RB_BLACK;\
+					RB_COLOR(tmp, field) = RB_RED;	\
+					RB_ROTATE_RIGHT(head, tmp, oleft, field);\
+					tmp = RB_RIGHT(parent, field);	\
+				}					\
+				RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+				RB_COLOR(parent, field) = RB_BLACK;	\
+				if (RB_RIGHT(tmp, field))		\
+					RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				elm = RB_ROOT(head);			\
+				break;					\
+			}						\
+		} else {						\
+			tmp = RB_LEFT(parent, field);			\
+			if (RB_COLOR(tmp, field) == RB_RED) {		\
+				RB_SET_BLACKRED(tmp, parent, field);	\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				tmp = RB_LEFT(parent, field);		\
+			}						\
+			if ((RB_LEFT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+			    (RB_RIGHT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+				RB_COLOR(tmp, field) = RB_RED;		\
+				elm = parent;				\
+				parent = RB_PARENT(elm, field);		\
+			} else {					\
+				if (RB_LEFT(tmp, field) == NULL ||	\
+				    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
+					struct type *oright;		\
+					if ((oright = RB_RIGHT(tmp, field)))\
+						RB_COLOR(oright, field) = RB_BLACK;\
+					RB_COLOR(tmp, field) = RB_RED;	\
+					RB_ROTATE_LEFT(head, tmp, oright, field);\
+					tmp = RB_LEFT(parent, field);	\
+				}					\
+				RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+				RB_COLOR(parent, field) = RB_BLACK;	\
+				if (RB_LEFT(tmp, field))		\
+					RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				elm = RB_ROOT(head);			\
+				break;					\
+			}						\
+		}							\
+	}								\
+	if (elm)							\
+		RB_COLOR(elm, field) = RB_BLACK;			\
+}									\
+									\
+attr struct type *							\
+name##_RB_REMOVE(struct name *head, struct type *elm)			\
+{									\
+	struct type *child, *parent, *old = elm;			\
+	int color;							\
+	if (RB_LEFT(elm, field) == NULL)				\
+		child = RB_RIGHT(elm, field);				\
+	else if (RB_RIGHT(elm, field) == NULL)				\
+		child = RB_LEFT(elm, field);				\
+	else {								\
+		struct type *left;					\
+		elm = RB_RIGHT(elm, field);				\
+		while ((left = RB_LEFT(elm, field)))			\
+			elm = left;					\
+		child = RB_RIGHT(elm, field);				\
+		parent = RB_PARENT(elm, field);				\
+		color = RB_COLOR(elm, field);				\
+		if (child)						\
+			RB_PARENT(child, field) = parent;		\
+		if (parent) {						\
+			if (RB_LEFT(parent, field) == elm)		\
+				RB_LEFT(parent, field) = child;		\
+			else						\
+				RB_RIGHT(parent, field) = child;	\
+			RB_AUGMENT(parent);				\
+		} else							\
+			RB_ROOT(head) = child;				\
+		if (RB_PARENT(elm, field) == old)			\
+			parent = elm;					\
+		(elm)->field = (old)->field;				\
+		if (RB_PARENT(old, field)) {				\
+			if (RB_LEFT(RB_PARENT(old, field), field) == old)\
+				RB_LEFT(RB_PARENT(old, field), field) = elm;\
+			else						\
+				RB_RIGHT(RB_PARENT(old, field), field) = elm;\
+			RB_AUGMENT(RB_PARENT(old, field));		\
+		} else							\
+			RB_ROOT(head) = elm;				\
+		RB_PARENT(RB_LEFT(old, field), field) = elm;		\
+		if (RB_RIGHT(old, field))				\
+			RB_PARENT(RB_RIGHT(old, field), field) = elm;	\
+		if (parent) {						\
+			left = parent;					\
+			do {						\
+				RB_AUGMENT(left);			\
+			} while ((left = RB_PARENT(left, field)));	\
+		}							\
+		goto color;						\
+	}								\
+	parent = RB_PARENT(elm, field);					\
+	color = RB_COLOR(elm, field);					\
+	if (child)							\
+		RB_PARENT(child, field) = parent;			\
+	if (parent) {							\
+		if (RB_LEFT(parent, field) == elm)			\
+			RB_LEFT(parent, field) = child;			\
+		else							\
+			RB_RIGHT(parent, field) = child;		\
+		RB_AUGMENT(parent);					\
+	} else								\
+		RB_ROOT(head) = child;					\
+color:									\
+	if (color == RB_BLACK)						\
+		name##_RB_REMOVE_COLOR(head, parent, child);		\
+	return (old);							\
+}									\
+									\
+/* Inserts a node into the RB tree */					\
+attr struct type *							\
+name##_RB_INSERT(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp;						\
+	struct type *parent = NULL;					\
+	int comp = 0;							\
+	tmp = RB_ROOT(head);						\
+	while (tmp) {							\
+		parent = tmp;						\
+		comp = (cmp)(elm, parent);				\
+		if (comp < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	RB_SET(elm, parent, field);					\
+	if (parent != NULL) {						\
+		if (comp < 0)						\
+			RB_LEFT(parent, field) = elm;			\
+		else							\
+			RB_RIGHT(parent, field) = elm;			\
+		RB_AUGMENT(parent);					\
+	} else								\
+		RB_ROOT(head) = elm;					\
+	name##_RB_INSERT_COLOR(head, elm);				\
+	return (NULL);							\
+}									\
+									\
+/* Finds the node with the same key as elm */				\
+attr struct type *							\
+name##_RB_FIND(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	int comp;							\
+	while (tmp) {							\
+		comp = cmp(elm, tmp);					\
+		if (comp < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	return (NULL);							\
+}									\
+									\
+/* Finds the first node greater than or equal to the search key */	\
+attr struct type *							\
+name##_RB_NFIND(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	struct type *res = NULL;					\
+	int comp;							\
+	while (tmp) {							\
+		comp = cmp(elm, tmp);					\
+		if (comp < 0) {						\
+			res = tmp;					\
+			tmp = RB_LEFT(tmp, field);			\
+		}							\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	return (res);							\
+}									\
+									\
+/* ARGSUSED */								\
+attr struct type *							\
+name##_RB_NEXT(struct type *elm)					\
+{									\
+	if (RB_RIGHT(elm, field)) {					\
+		elm = RB_RIGHT(elm, field);				\
+		while (RB_LEFT(elm, field))				\
+			elm = RB_LEFT(elm, field);			\
+	} else {							\
+		if (RB_PARENT(elm, field) &&				\
+		    (elm == RB_LEFT(RB_PARENT(elm, field), field)))	\
+			elm = RB_PARENT(elm, field);			\
+		else {							\
+			while (RB_PARENT(elm, field) &&			\
+			    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
+				elm = RB_PARENT(elm, field);		\
+			elm = RB_PARENT(elm, field);			\
+		}							\
+	}								\
+	return (elm);							\
+}									\
+									\
+/* ARGSUSED */								\
+attr struct type *							\
+name##_RB_PREV(struct type *elm)					\
+{									\
+	if (RB_LEFT(elm, field)) {					\
+		elm = RB_LEFT(elm, field);				\
+		while (RB_RIGHT(elm, field))				\
+			elm = RB_RIGHT(elm, field);			\
+	} else {							\
+		if (RB_PARENT(elm, field) &&				\
+		    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))	\
+			elm = RB_PARENT(elm, field);			\
+		else {							\
+			while (RB_PARENT(elm, field) &&			\
+			    (elm == RB_LEFT(RB_PARENT(elm, field), field)))\
+				elm = RB_PARENT(elm, field);		\
+			elm = RB_PARENT(elm, field);			\
+		}							\
+	}								\
+	return (elm);							\
+}									\
+									\
+attr struct type *							\
+name##_RB_MINMAX(struct name *head, int val)				\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	struct type *parent = NULL;					\
+	while (tmp) {							\
+		parent = tmp;						\
+		if (val < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else							\
+			tmp = RB_RIGHT(tmp, field);			\
+	}								\
+	return (parent);						\
+}
+
+#define RB_NEGINF	-1
+#define RB_INF	1
+
+#define RB_INSERT(name, x, y)	name##_RB_INSERT(x, y)
+#define RB_REMOVE(name, x, y)	name##_RB_REMOVE(x, y)
+#define RB_FIND(name, x, y)	name##_RB_FIND(x, y)
+#define RB_NFIND(name, x, y)	name##_RB_NFIND(x, y)
+#define RB_NEXT(name, x, y)	name##_RB_NEXT(y)
+#define RB_PREV(name, x, y)	name##_RB_PREV(y)
+#define RB_MIN(name, x)		name##_RB_MINMAX(x, RB_NEGINF)
+#define RB_MAX(name, x)		name##_RB_MINMAX(x, RB_INF)
+
+#define RB_FOREACH(x, name, head)					\
+	for ((x) = RB_MIN(name, head);					\
+	     (x) != NULL;						\
+	     (x) = name##_RB_NEXT(x))
+
+#define RB_FOREACH_SAFE(x, name, head, y)				\
+	for ((x) = RB_MIN(name, head);					\
+	    ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1);		\
+	     (x) = (y))
+
+#define RB_FOREACH_REVERSE(x, name, head)				\
+	for ((x) = RB_MAX(name, head);					\
+	     (x) != NULL;						\
+	     (x) = name##_RB_PREV(x))
+
+#define RB_FOREACH_REVERSE_SAFE(x, name, head, y)			\
+	for ((x) = RB_MAX(name, head);					\
+	    ((x) != NULL) && ((y) = name##_RB_PREV(x), 1);		\
+	     (x) = (y))
+
+
+/*
+ * Copyright (c) 2016 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct rb_type {
+	int		(*t_compare)(const void *, const void *);
+	void		(*t_augment)(void *);
+	unsigned int	  t_offset;	/* offset of rb_entry in type */
+};
+
+struct rb_tree {
+	struct rb_entry	*rbt_root;
+};
+
+struct rb_entry {
+	struct rb_entry	 *rbt_parent;
+	struct rb_entry	 *rbt_left;
+	struct rb_entry	 *rbt_right;
+	unsigned int	  rbt_color;
+};
+
+#define RBT_HEAD(_name, _type)						\
+struct _name {								\
+	struct rb_tree rbh_root;					\
+}
+
+#define RBT_ENTRY(_type)	struct rb_entry
+
+static inline void
+_rb_init(struct rb_tree *rbt)
+{
+	rbt->rbt_root = NULL;
+}
+
+static inline int
+_rb_empty(struct rb_tree *rbt)
+{
+	return (rbt->rbt_root == NULL);
+}
+
+void	*_rb_insert(const struct rb_type *, struct rb_tree *, void *);
+void	*_rb_remove(const struct rb_type *, struct rb_tree *, void *);
+void	*_rb_find(const struct rb_type *, struct rb_tree *, const void *);
+void	*_rb_nfind(const struct rb_type *, struct rb_tree *, const void *);
+void	*_rb_root(const struct rb_type *, struct rb_tree *);
+void	*_rb_min(const struct rb_type *, struct rb_tree *);
+void	*_rb_max(const struct rb_type *, struct rb_tree *);
+void	*_rb_next(const struct rb_type *, void *);
+void	*_rb_prev(const struct rb_type *, void *);
+void	*_rb_left(const struct rb_type *, void *);
+void	*_rb_right(const struct rb_type *, void *);
+void	*_rb_parent(const struct rb_type *, void *);
+void	 _rb_set_left(const struct rb_type *, void *, void *);
+void	 _rb_set_right(const struct rb_type *, void *, void *);
+void	 _rb_set_parent(const struct rb_type *, void *, void *);
+void	 _rb_poison(const struct rb_type *, void *, unsigned long);
+int	 _rb_check(const struct rb_type *, void *, unsigned long);
+
+#define RBT_INITIALIZER(_head)	{ { NULL } }
+
+#define RBT_PROTOTYPE(_name, _type, _field, _cmp)			\
+extern const struct rb_type *const _name##_RBT_TYPE;			\
+									\
+__unused static inline void						\
+_name##_RBT_INIT(struct _name *head)					\
+{									\
+	_rb_init(&head->rbh_root);					\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_INSERT(struct _name *head, struct _type *elm)		\
+{									\
+	return _rb_insert(_name##_RBT_TYPE, &head->rbh_root, elm);	\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_REMOVE(struct _name *head, struct _type *elm)		\
+{									\
+	return _rb_remove(_name##_RBT_TYPE, &head->rbh_root, elm);	\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_FIND(struct _name *head, const struct _type *key)		\
+{									\
+	return _rb_find(_name##_RBT_TYPE, &head->rbh_root, key);	\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_NFIND(struct _name *head, const struct _type *key)		\
+{									\
+	return _rb_nfind(_name##_RBT_TYPE, &head->rbh_root, key);	\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_ROOT(struct _name *head)					\
+{									\
+	return _rb_root(_name##_RBT_TYPE, &head->rbh_root);		\
+}									\
+									\
+__unused static inline int						\
+_name##_RBT_EMPTY(struct _name *head)					\
+{									\
+	return _rb_empty(&head->rbh_root);				\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_MIN(struct _name *head)					\
+{									\
+	return _rb_min(_name##_RBT_TYPE, &head->rbh_root);		\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_MAX(struct _name *head)					\
+{									\
+	return _rb_max(_name##_RBT_TYPE, &head->rbh_root);		\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_NEXT(struct _type *elm)					\
+{									\
+	return _rb_next(_name##_RBT_TYPE, elm);				\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_PREV(struct _type *elm)					\
+{									\
+	return _rb_prev(_name##_RBT_TYPE, elm);				\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_LEFT(struct _type *elm)					\
+{									\
+	return _rb_left(_name##_RBT_TYPE, elm);				\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_RIGHT(struct _type *elm)					\
+{									\
+	return _rb_right(_name##_RBT_TYPE, elm);			\
+}									\
+									\
+__unused static inline struct _type *					\
+_name##_RBT_PARENT(struct _type *elm)					\
+{									\
+	return _rb_parent(_name##_RBT_TYPE, elm);			\
+}									\
+									\
+__unused static inline void						\
+_name##_RBT_SET_LEFT(struct _type *elm, struct _type *left)		\
+{									\
+	return _rb_set_left(_name##_RBT_TYPE, elm, left);		\
+}									\
+									\
+__unused static inline void						\
+_name##_RBT_SET_RIGHT(struct _type *elm, struct _type *right)		\
+{									\
+	return _rb_set_right(_name##_RBT_TYPE, elm, right);		\
+}									\
+									\
+__unused static inline void						\
+_name##_RBT_SET_PARENT(struct _type *elm, struct _type *parent)		\
+{									\
+	return _rb_set_parent(_name##_RBT_TYPE, elm, parent);		\
+}									\
+									\
+__unused static inline void						\
+_name##_RBT_POISON(struct _type *elm, unsigned long poison)		\
+{									\
+	return _rb_poison(_name##_RBT_TYPE, elm, poison);		\
+}									\
+									\
+__unused static inline int						\
+_name##_RBT_CHECK(struct _type *elm, unsigned long poison)		\
+{									\
+	return _rb_check(_name##_RBT_TYPE, elm, poison);		\
+}
+
+#define RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, _aug)		\
+static int								\
+_name##_RBT_COMPARE(const void *lptr, const void *rptr)			\
+{									\
+	const struct _type *l = lptr, *r = rptr;			\
+	return _cmp(l, r);						\
+}									\
+static const struct rb_type _name##_RBT_INFO = {			\
+	_name##_RBT_COMPARE,						\
+	_aug,								\
+	offsetof(struct _type, _field),					\
+};									\
+const struct rb_type *const _name##_RBT_TYPE = &_name##_RBT_INFO
+
+#define RBT_GENERATE_AUGMENT(_name, _type, _field, _cmp, _aug)		\
+static void								\
+_name##_RBT_AUGMENT(void *ptr)						\
+{									\
+	struct _type *p = ptr;						\
+	return _aug(p);							\
+}									\
+RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, _name##_RBT_AUGMENT)
+
+#define RBT_GENERATE(_name, _type, _field, _cmp)			\
+    RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL)
+
+#define RBT_INIT(_name, _head)		_name##_RBT_INIT(_head)
+#define RBT_INSERT(_name, _head, _elm)	_name##_RBT_INSERT(_head, _elm)
+#define RBT_REMOVE(_name, _head, _elm)	_name##_RBT_REMOVE(_head, _elm)
+#define RBT_FIND(_name, _head, _key)	_name##_RBT_FIND(_head, _key)
+#define RBT_NFIND(_name, _head, _key)	_name##_RBT_NFIND(_head, _key)
+#define RBT_ROOT(_name, _head)		_name##_RBT_ROOT(_head)
+#define RBT_EMPTY(_name, _head)		_name##_RBT_EMPTY(_head)
+#define RBT_MIN(_name, _head)		_name##_RBT_MIN(_head)
+#define RBT_MAX(_name, _head)		_name##_RBT_MAX(_head)
+#define RBT_NEXT(_name, _elm)		_name##_RBT_NEXT(_elm)
+#define RBT_PREV(_name, _elm)		_name##_RBT_PREV(_elm)
+#define RBT_LEFT(_name, _elm)		_name##_RBT_LEFT(_elm)
+#define RBT_RIGHT(_name, _elm)		_name##_RBT_RIGHT(_elm)
+#define RBT_PARENT(_name, _elm)		_name##_RBT_PARENT(_elm)
+#define RBT_SET_LEFT(_name, _elm, _l)	_name##_RBT_SET_LEFT(_elm, _l)
+#define RBT_SET_RIGHT(_name, _elm, _r)	_name##_RBT_SET_RIGHT(_elm, _r)
+#define RBT_SET_PARENT(_name, _elm, _p)	_name##_RBT_SET_PARENT(_elm, _p)
+#define RBT_POISON(_name, _elm, _p)	_name##_RBT_POISON(_elm, _p)
+#define RBT_CHECK(_name, _elm, _p)	_name##_RBT_CHECK(_elm, _p)
+
+#define RBT_FOREACH(_e, _name, _head)					\
+	for ((_e) = RBT_MIN(_name, (_head));				\
+	     (_e) != NULL;						\
+	     (_e) = RBT_NEXT(_name, (_e)))
+
+#define RBT_FOREACH_SAFE(_e, _name, _head, _n)				\
+	for ((_e) = RBT_MIN(_name, (_head));				\
+	     (_e) != NULL && ((_n) = RBT_NEXT(_name, (_e)), 1);	\
+	     (_e) = (_n))
+
+#define RBT_FOREACH_REVERSE(_e, _name, _head)				\
+	for ((_e) = RBT_MAX(_name, (_head));				\
+	     (_e) != NULL;						\
+	     (_e) = RBT_PREV(_name, (_e)))
+
+#define RBT_FOREACH_REVERSE_SAFE(_e, _name, _head, _n)			\
+	for ((_e) = RBT_MAX(_name, (_head));				\
+	     (_e) != NULL && ((_n) = RBT_PREV(_name, (_e)), 1);	\
+	     (_e) = (_n))
+
+#endif	/* _SYS_TREE_H_ */