about summary refs log tree commit diff
path: root/getservinfo.c
blob: 3fc87b856422fbe6ffde97376025fa69889a911d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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;
}