about summary refs log tree commit diff
path: root/getservinfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'getservinfo.c')
-rw-r--r--getservinfo.c150
1 files changed, 150 insertions, 0 deletions
diff --git a/getservinfo.c b/getservinfo.c
new file mode 100644
index 0000000..3fc87b8
--- /dev/null
+++ b/getservinfo.c
@@ -0,0 +1,150 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * Additional permission under GNU GPL version 3 section 7:
+ *
+ * If you modify this Program, or any covered work, by linking or
+ * combining it with OpenSSL (or a modified version of that library),
+ * containing parts covered by the terms of the OpenSSL License and the
+ * original SSLeay license, the licensors of this Program grant you
+ * additional permission to convey the resulting work. Corresponding
+ * Source for a non-source form of such a combination shall include the
+ * source code for the parts of OpenSSL used as well as that of the
+ * covered work.
+ */
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef EAI_BADHINTS
+#define EAI_BADHINTS EAI_BADFLAGS
+#endif
+
+#ifndef EAI_PROTOCOL
+#define EAI_PROTOCOL EAI_BADFLAGS
+#endif
+
+static uint16_t u16(uint8_t **ptr) {
+	uint16_t x = *(*ptr)++;
+	x = x << 8 | *(*ptr)++;
+	return x;
+}
+
+static void nameSkip(uint8_t **ptr) {
+	for (uint8_t len; (len = *(*ptr)++); *ptr += len) {
+		if (len & 0xC0) {
+			(*ptr)++;
+			break;
+		}
+	}
+}
+
+static char *nameString(uint8_t **ptr) {
+	char *name = (char *)(*ptr + 1);
+	for (uint8_t len; (len = **ptr); *ptr += len) {
+		*(*ptr)++ = '.';
+	}
+	return name;
+}
+
+/* A wrapper around getaddrinfo(3) which first performs SRV record (RFC 2782)
+ * lookup. hints must be provided and hints->ai_protocol must be set. SRV
+ * lookup is skipped if servname is numerical. If SRV lookup is successful, the
+ * ai_canonname field of the first addrinfo structure returned is set to the
+ * target name.
+ */
+int getservinfo(
+	const char *hostname, const char *servname,
+	const struct addrinfo *hints, struct addrinfo **res
+) {
+	if (!hints) return EAI_BADHINTS;
+	if (!hints->ai_protocol) return EAI_PROTOCOL;
+	if (hints->ai_flags & AI_NUMERICHOST) return EAI_BADFLAGS;
+	if (hints->ai_flags & AI_NUMERICSERV) return EAI_BADFLAGS;
+
+	char *rest;
+	strtoul(servname, &rest, 10);
+	if (!*rest) return getaddrinfo(hostname, servname, hints, res);
+
+	struct protoent *proto = getprotobynumber(hints->ai_protocol);
+	if (!proto) return EAI_PROTOCOL;
+
+	char dname[256];
+	int len = snprintf(
+		dname, sizeof(dname), "_%s._%s.%s",
+		servname, proto->p_name, hostname
+	);
+	if ((size_t)len >= sizeof(dname)) return EAI_OVERFLOW;
+
+	uint8_t msg[512];
+	len = res_query(dname, 1 /* IN */, 33 /* SRV */, msg, sizeof(msg));
+	if (len < 0) return getaddrinfo(hostname, servname, hints, res);
+
+	uint8_t *ptr = msg;
+	u16(&ptr); // ID
+	uint16_t rcode = u16(&ptr) & 0x000F;
+	uint16_t qdcount = u16(&ptr);
+	uint16_t ancount = u16(&ptr);
+	u16(&ptr); // NSCOUNT
+	u16(&ptr); // ARCOUNT
+
+	if (rcode || !ancount) return getaddrinfo(hostname, servname, hints, res);
+
+	for (uint16_t q = 0; q < qdcount; ++q) {
+		nameSkip(&ptr); // QNAME
+		u16(&ptr); // QTYPE
+		u16(&ptr); // QCLASS
+	}
+
+	// Read only one answer, ignoring Priority and Weight. Does anyone actually
+	// use them?
+	nameSkip(&ptr); // NAME
+	u16(&ptr); // TYPE
+	u16(&ptr); // CLASS
+	u16(&ptr); // TTL
+	u16(&ptr); // TTL
+	u16(&ptr); // RDLENGTH
+	u16(&ptr); // Priority
+	u16(&ptr); // Weight
+
+	uint16_t port = u16(&ptr);
+	hostname = nameString(&ptr);
+	if (!hostname[0]) return EAI_NONAME;
+
+	char myServ[sizeof("65535")];
+	snprintf(myServ, sizeof(myServ), "%hu", port);
+
+	struct addrinfo myHints = *hints;
+#ifdef AI_DEFAULT
+	if (!myHints.ai_flags) myHints.ai_flags = AI_DEFAULT;
+#endif
+	myHints.ai_flags |= AI_NUMERICSERV;
+	myHints.ai_flags &= ~AI_CANONNAME;
+
+	int error = getaddrinfo(hostname, myServ, &myHints, res);
+	if (error) return error;
+
+	(*res)->ai_canonname = strdup(hostname);
+	if (!(*res)->ai_canonname) {
+		freeaddrinfo(*res);
+		return EAI_MEMORY;
+	}
+	return 0;
+}