1 /* resolver.h
2 * strophe XMPP client library -- DNS resolver
3 *
4 * Copyright (C) 2015 Dmitry Podgorny <pasis.ua@gmail.com>
5 *
6 * This software is provided AS-IS with no warranty, either express
7 * or implied.
8 *
9 * This program is dual licensed under the MIT and GPLv3 licenses.
10 */
11
12 /** @file
13 * DNS resolver.
14 */
15
16 #include <netinet/in.h>
17 #include <arpa/nameser.h>
18 #include <resolv.h> /* res_query */
19 #include <string.h> /* strncpy */
20
21 #include "ostypes.h"
22 #include "snprintf.h"
23 #include "util.h" /* xmpp_min */
24 #include "resolver.h"
25
26 #define MESSAGE_HEADER_LEN 12
27 #define MESSAGE_RESPONSE 1
28 #define MESSAGE_T_SRV 33
29 #define MESSAGE_C_IN 1
30
31 struct message_header {
32 uint16_t id;
33 uint8_t octet2;
34 uint8_t octet3;
35 uint16_t qdcount;
36 uint16_t ancount;
37 uint16_t nscount;
38 uint16_t arcount;
39 };
40
41 /* the same as ntohs(), but receives pointer to the value */
xmpp_ntohs_ptr(const void * ptr)42 static uint16_t xmpp_ntohs_ptr(const void *ptr)
43 {
44 const uint8_t *p = (const uint8_t *)ptr;
45
46 return (uint16_t)((p[0] << 8U) + p[1]);
47 }
48
message_header_qr(const struct message_header * header)49 static uint8_t message_header_qr(const struct message_header *header)
50 {
51 return (header->octet2 >> 7) & 1;
52 }
53
message_header_rcode(const struct message_header * header)54 static uint8_t message_header_rcode(const struct message_header *header)
55 {
56 return header->octet3 & 0x0f;
57 }
58
59 /*
60 * Append a label or a dot to the target name with buffer overflow checks.
61 * Returns length of the non-truncated resulting string, may be bigger than
62 * name_max.
63 */
message_name_append_safe(char * name,size_t name_len,size_t name_max,const char * tail,size_t tail_len)64 static size_t message_name_append_safe(char *name, size_t name_len,
65 size_t name_max,
66 const char *tail, size_t tail_len)
67 {
68 size_t copy_len;
69
70 copy_len = name_max > name_len ? name_max - name_len : 0;
71 copy_len = xmpp_min(tail_len, copy_len);
72 if (copy_len > 0)
73 strncpy(&name[name_len], tail, copy_len);
74
75 return name_len + tail_len;
76 }
77
78 /* Returns length of the compressed name. This is NOT the same as strlen(). */
message_name_get(const unsigned char * buf,size_t buf_len,unsigned buf_offset,char * name,size_t name_max)79 static unsigned message_name_get(const unsigned char *buf, size_t buf_len,
80 unsigned buf_offset,
81 char *name, size_t name_max)
82 {
83 size_t name_len = 0;
84 unsigned i = buf_offset;
85 unsigned pointer;
86 unsigned rc;
87 unsigned char label_len;
88
89
90 while (1) {
91 if (i >= buf_len) return 0;
92 label_len = buf[i++];
93 if (label_len == 0) break;
94
95 /* Label */
96 if ((label_len & 0xc0) == 0) {
97 if (i + label_len - 1 >= buf_len) return 0;
98 if (name != NULL) {
99 name_len = message_name_append_safe(name, name_len, name_max,
100 (char *)&buf[i], label_len);
101 name_len = message_name_append_safe(name, name_len, name_max,
102 ".", 1);
103 }
104 i += label_len;
105
106 /* Pointer */
107 } else if ((label_len & 0xc0) == 0xc0) {
108 if (i >= buf_len) return 0;
109 pointer = (label_len & 0x3f) << 8 | buf[i++];
110 if (name != NULL && name_len >= name_max && name_max > 0) {
111 /* We have filled the name buffer. Don't pass it recursively. */
112 name[name_max - 1] = '\0';
113 name = NULL;
114 name_max = 0;
115 }
116 rc = message_name_get(buf, buf_len, pointer,
117 name != NULL ? &name[name_len] : NULL,
118 name_max > name_len ? name_max - name_len : 0);
119 if (rc == 0) return 0;
120 /* Pointer is always the last. */
121 break;
122
123 /* The 10 and 01 combinations are reserved for future use. */
124 } else {
125 return 0;
126 }
127 }
128 if (label_len == 0) {
129 if (name_len == 0) name_len = 1;
130 /*
131 * At this point name_len is length of the resulting name,
132 * including '\0'. This value can be exported to allocate buffer
133 * of precise size.
134 */
135 if (name != NULL && name_max > 0) {
136 /*
137 * Overwrite leading '.' with a '\0'. If the resulting name is
138 * bigger than name_max it is truncated.
139 */
140 name[xmpp_min(name_len, name_max) - 1] = '\0';
141 }
142 }
143
144 return i - buf_offset;
145 }
146
message_name_len(const unsigned char * buf,size_t buf_len,unsigned buf_offset)147 static unsigned message_name_len(const unsigned char *buf, size_t buf_len,
148 unsigned buf_offset)
149 {
150 return message_name_get(buf, buf_len, buf_offset, NULL, SIZE_MAX);
151 }
152
resolver_srv_list_sort(resolver_srv_rr_t ** srv_rr_list)153 static void resolver_srv_list_sort(resolver_srv_rr_t **srv_rr_list)
154 {
155 resolver_srv_rr_t * rr_head;
156 resolver_srv_rr_t * rr_current;
157 resolver_srv_rr_t * rr_next;
158 resolver_srv_rr_t * rr_prev;
159 int swap;
160
161 rr_head = *srv_rr_list;
162
163 if ((rr_head == NULL) || (rr_head->next == NULL)) {
164 /* Empty or single record list */
165 return;
166 }
167
168 do {
169 rr_prev = NULL;
170 rr_current = rr_head;
171 rr_next = rr_head->next;
172 swap = 0;
173 while (rr_next != NULL) {
174 /*
175 * RFC2052: A client MUST attempt to contact the target host
176 * with the lowest-numbered priority it can reach.
177 * RFC2052: When selecting a target host among the
178 * those that have the same priority, the chance of trying
179 * this one first SHOULD be proportional to its weight.
180 */
181 if ((rr_current->priority > rr_next->priority) ||
182 (rr_current->priority == rr_next->priority &&
183 rr_current->weight < rr_next->weight))
184 {
185 /* Swap node */
186 swap = 1;
187 if (rr_prev != NULL) {
188 rr_prev->next = rr_next;
189 } else {
190 /* Swap head node */
191 rr_head = rr_next;
192 }
193 rr_current->next = rr_next->next;
194 rr_next->next = rr_current;
195
196 rr_prev = rr_next;
197 rr_next = rr_current->next;
198 } else {
199 /* Next node */
200 rr_prev = rr_current;
201 rr_current = rr_next;
202 rr_next = rr_next->next;
203 }
204 }
205 } while (swap != 0);
206
207 *srv_rr_list = rr_head;
208 }
209
210 #define BUF_OVERFLOW_CHECK(ptr, len) do { \
211 if ((ptr) >= (len)) { \
212 if (*srv_rr_list != NULL) \
213 resolver_srv_free(ctx, *srv_rr_list); \
214 *srv_rr_list = NULL; \
215 return XMPP_DOMAIN_NOT_FOUND; \
216 } \
217 } while (0)
218
resolver_srv_lookup_buf(xmpp_ctx_t * ctx,const unsigned char * buf,size_t len,resolver_srv_rr_t ** srv_rr_list)219 int resolver_srv_lookup_buf(xmpp_ctx_t *ctx, const unsigned char *buf,
220 size_t len, resolver_srv_rr_t **srv_rr_list)
221 {
222 unsigned i;
223 unsigned j;
224 unsigned name_len;
225 unsigned rdlength;
226 uint16_t type;
227 uint16_t class;
228 struct message_header header;
229 resolver_srv_rr_t *rr;
230
231 *srv_rr_list = NULL;
232
233 if (len < MESSAGE_HEADER_LEN)
234 return XMPP_DOMAIN_NOT_FOUND;
235
236 header.id = xmpp_ntohs_ptr(&buf[0]);
237 header.octet2 = buf[2];
238 header.octet3 = buf[3];
239 header.qdcount = xmpp_ntohs_ptr(&buf[4]);
240 header.ancount = xmpp_ntohs_ptr(&buf[6]);
241 header.nscount = xmpp_ntohs_ptr(&buf[8]);
242 header.arcount = xmpp_ntohs_ptr(&buf[10]);
243 if (message_header_qr(&header) != MESSAGE_RESPONSE ||
244 message_header_rcode(&header) != 0)
245 {
246 return XMPP_DOMAIN_NOT_FOUND;
247 }
248 j = MESSAGE_HEADER_LEN;
249
250 /* skip question section */
251 for (i = 0; i < header.qdcount; ++i) {
252 BUF_OVERFLOW_CHECK(j, len);
253 name_len = message_name_len(buf, len, j);
254 /* error in name format */
255 if (name_len == 0) return XMPP_DOMAIN_NOT_FOUND;
256 j += name_len + 4;
257 }
258
259 for (i = 0; i < header.ancount; ++i) {
260 BUF_OVERFLOW_CHECK(j, len);
261 name_len = message_name_len(buf, len, j);
262 /* error in name format */
263 if (name_len == 0) return XMPP_DOMAIN_NOT_FOUND;
264 j += name_len;
265 BUF_OVERFLOW_CHECK(j + 16, len);
266 type = xmpp_ntohs_ptr(&buf[j]);
267 class = xmpp_ntohs_ptr(&buf[j + 2]);
268 rdlength = xmpp_ntohs_ptr(&buf[j + 8]);
269 j += 10;
270 if (type == MESSAGE_T_SRV && class == MESSAGE_C_IN) {
271 rr = xmpp_alloc(ctx, sizeof(*rr));
272 rr->next = *srv_rr_list;
273 rr->priority = xmpp_ntohs_ptr(&buf[j]);
274 rr->weight = xmpp_ntohs_ptr(&buf[j + 2]);
275 rr->port = xmpp_ntohs_ptr(&buf[j + 4]);
276 name_len = message_name_get(buf, len, j + 6, rr->target,
277 sizeof(rr->target));
278 if (name_len > 0)
279 *srv_rr_list = rr;
280 else
281 xmpp_free(ctx, rr); /* skip broken record */
282 }
283 j += rdlength;
284 }
285 resolver_srv_list_sort(srv_rr_list);
286
287 return *srv_rr_list != NULL ? XMPP_DOMAIN_FOUND : XMPP_DOMAIN_NOT_FOUND;
288 }
289
resolver_srv_lookup(xmpp_ctx_t * ctx,const char * service,const char * proto,const char * domain,resolver_srv_rr_t ** srv_rr_list)290 int resolver_srv_lookup(xmpp_ctx_t *ctx, const char *service, const char *proto,
291 const char *domain, resolver_srv_rr_t **srv_rr_list)
292 {
293 char fulldomain[2048];
294 unsigned char buf[65535];
295 int len;
296 int set = XMPP_DOMAIN_NOT_FOUND;
297
298 xmpp_snprintf(fulldomain, sizeof(fulldomain),
299 "_%s._%s.%s", service, proto, domain);
300
301 *srv_rr_list = NULL;
302
303 len = res_query(fulldomain, MESSAGE_C_IN, MESSAGE_T_SRV, buf, sizeof(buf));
304
305 if (len > 0)
306 set = resolver_srv_lookup_buf(ctx, buf, (size_t)len, srv_rr_list);
307
308 return set;
309 }
310
resolver_srv_free(xmpp_ctx_t * ctx,resolver_srv_rr_t * srv_rr_list)311 void resolver_srv_free(xmpp_ctx_t *ctx, resolver_srv_rr_t *srv_rr_list)
312 {
313 resolver_srv_rr_t *rr;
314
315 while (srv_rr_list != NULL) {
316 rr = srv_rr_list->next;
317 xmpp_free(ctx, srv_rr_list);
318 srv_rr_list = rr;
319 }
320 }
321