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