summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--getservinfo.c92
1 files changed, 34 insertions, 58 deletions
diff --git a/getservinfo.c b/getservinfo.c
index 04685d7..219f72a 100644
--- a/getservinfo.c
+++ b/getservinfo.c
@@ -41,35 +41,12 @@
 #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;
-	if (**ptr) name++;
-	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.
+ * target name. Only the first SRV record is used. Priority and weight are
+ * ignored.
  */
 int getservinfo(
 	const char *hostname, const char *servname,
@@ -77,12 +54,12 @@ int getservinfo(
 ) {
 	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);
+	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;
@@ -96,45 +73,44 @@ int getservinfo(
 
 	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
-	u16(&ptr); // QR, OPCODE, AA, TC, RD, RA, Z, RCODE
-	uint16_t qdcount = u16(&ptr);
-	u16(&ptr); // ANCOUNT
-	u16(&ptr); // NSCOUNT
-	u16(&ptr); // ARCOUNT
+	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; \
+		} \
+	}
 
-	for (uint16_t q = 0; q < qdcount; ++q) {
-		nameSkip(&ptr); // QNAME
-		u16(&ptr); // QTYPE
-		u16(&ptr); // QCLASS
+	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
 	}
 
-	// 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
+	NAME_SKIP; // NAME
+	ptr += 14; // TYPE, CLASS, TTL, RDLENGTH, Priority, Weight
+	if (&msg[len] < ptr + 3) {
+		return getaddrinfo(hostname, servname, hints, res);
+	}
 
-	uint16_t port = u16(&ptr);
-	hostname = nameString(&ptr);
-	if (!hostname[0]) return EAI_NONAME;
+	char port[sizeof("65535")];
+	snprintf(port, sizeof(port), "%d", ptr[0] << 8 | ptr[1]);
+	ptr += 2;
 
-	char myServ[sizeof("65535")];
-	snprintf(myServ, sizeof(myServ), "%hu", port);
+	// 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, myServ, &myHints, res);
+	int error = getaddrinfo(hostname, port, &myHints, res);
 	if (error) return error;
 
 	(*res)->ai_canonname = strdup(hostname);