summary refs log tree commit diff
path: root/getservinfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'getservinfo.c')
-rw-r--r--getservinfo.c122
1 files changed, 122 insertions, 0 deletions
diff --git a/getservinfo.c b/getservinfo.c
new file mode 100644
index 0000000..219f72a
--- /dev/null
+++ b/getservinfo.c
@@ -0,0 +1,122 @@
+/* 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
+
+/* 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. Only the first SRV record is used. Priority and weight are
+ * ignored.
+ */
+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;
+
+	char *rest;
+	strtoul(servname, &rest, 10);
+	if (!*rest || hints->ai_flags & (AI_NUMERICHOST | AI_NUMERICSERV)) {
+		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 < 12) return getaddrinfo(hostname, servname, hints, res);
+	uint8_t *ptr = &msg[12];
+
+#define NAME_SKIP \
+	for (uint8_t n; ptr < &msg[len] && (n = *ptr++); ptr += n) { \
+		if (n & 0xC0) { \
+			ptr++; \
+			break; \
+		} \
+	}
+
+	uint16_t qdcount = msg[4] << 8 | msg[5];
+	for (uint16_t q = 0; ptr < &msg[len] && q < qdcount; ++q) {
+		NAME_SKIP; // QNAME
+		ptr += 4; // QTYPE, QCLASS
+	}
+
+	NAME_SKIP; // NAME
+	ptr += 14; // TYPE, CLASS, TTL, RDLENGTH, Priority, Weight
+	if (&msg[len] < ptr + 3) {
+		return getaddrinfo(hostname, servname, hints, res);
+	}
+
+	char port[sizeof("65535")];
+	snprintf(port, sizeof(port), "%d", ptr[0] << 8 | ptr[1]);
+	ptr += 2;
+
+	// Name compression is not used for Target.
+	if (!ptr[0]) return EAI_NONAME;
+	hostname = (const char *)&ptr[1];
+	for (uint8_t n; ptr < &msg[len] && (n = *ptr); ptr += n) {
+		*ptr++ = '.';
+	}
+
+	struct addrinfo myHints = *hints;
+	myHints.ai_flags |= AI_NUMERICSERV;
+	myHints.ai_flags &= ~AI_CANONNAME;
+	int error = getaddrinfo(hostname, port, &myHints, res);
+	if (error) return error;
+
+	(*res)->ai_canonname = strdup(hostname);
+	if (!(*res)->ai_canonname) {
+		freeaddrinfo(*res);
+		return EAI_MEMORY;
+	}
+	return 0;
+}