/ libpkg / dns_utils.c
dns_utils.c
  1  /*-
  2   * Copyright (c) 2012-2014 Baptiste Daroussin <bapt@FreeBSD.org>
  3   * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
  4   * All rights reserved.
  5   *
  6   * Redistribution and use in source and binary forms, with or without
  7   * modification, are permitted provided that the following conditions
  8   * are met:
  9   * 1. Redistributions of source code must retain the above copyright
 10   *    notice, this list of conditions and the following disclaimer,
 11   *    without modification, immediately at the beginning of the file.
 12   * 2. Redistributions in binary form must reproduce the above copyright
 13   *    notice, this list of conditions and the following disclaimer in the
 14   *    documentation and/or other materials provided with the distribution.
 15   *
 16   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 17   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 18   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 19   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 20   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 21   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 22   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 23   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 25   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 26   */
 27  
 28  #include <pkg_config.h>
 29  
 30  #include <sys/stat.h> /* for private.utils.h */
 31  
 32  #include <string.h>
 33  #include <netinet/in.h>
 34  #ifdef HAVE_LDNS
 35  #include <ldns/ldns.h>
 36  #else
 37  #define BIND_8_COMPAT
 38  #include <arpa/nameser.h>
 39  #include <resolv.h>
 40  #endif
 41  #include <netdb.h>
 42  
 43  #ifndef NS_QFIXEDSZ
 44  #define NS_QFIXEDSZ     4       /*%< #/bytes of fixed data in query */
 45  #endif
 46  
 47  #ifndef NS_INT16SZ
 48  #define NS_INT16SZ      2       /*%< #/bytes of data in a u_int16_t */
 49  #endif
 50  
 51  #ifndef NS_INT32SZ
 52  #define NS_INT32SZ      4       /*%< #/bytes of data in a u_int32_t */
 53  #endif
 54  
 55  /*%
 56   * Inline versions of get/put short/long.  Pointer is advanced.
 57   */
 58  #ifndef NS_GET16
 59  #define NS_GET16(s, cp) do { \
 60          register const u_char *t_cp = (const u_char *)(cp); \
 61          (s) = ((u_int16_t)t_cp[0] << 8) \
 62              | ((u_int16_t)t_cp[1]) \
 63              ; \
 64          (cp) += NS_INT16SZ; \
 65  } while (0)
 66  #endif
 67  
 68  #ifndef NS_GET32
 69  #define NS_GET32(l, cp) do { \
 70          register const u_char *t_cp = (const u_char *)(cp); \
 71          (l) = ((u_int32_t)t_cp[0] << 24) \
 72              | ((u_int32_t)t_cp[1] << 16) \
 73              | ((u_int32_t)t_cp[2] << 8) \
 74              | ((u_int32_t)t_cp[3]) \
 75              ; \
 76          (cp) += NS_INT32SZ; \
 77  } while (0)
 78  #endif
 79  
 80  #include <bsd_compat.h>
 81  #include "private/utils.h"
 82  #include "xmalloc.h"
 83  #include "pkg.h"
 84  
 85  #ifndef HAVE_LDNS
 86  typedef union {
 87  	HEADER hdr;
 88  	unsigned char buf[1024];
 89  } query_t;
 90  #endif
 91  
 92  static int
 93  srv_priority_cmp(const void *a, const void *b)
 94  {
 95  	const struct dns_srvinfo *da, *db;
 96  #ifdef HAVE_LDNS
 97  	da = (const struct dns_srvinfo *)a;
 98  	db = (const struct dns_srvinfo *)b;
 99  #else
100  	da = *(struct dns_srvinfo * const *)a;
101  	db = *(struct dns_srvinfo * const *)b;
102  #endif
103  
104  	return ((da->priority > db->priority) - (da->priority < db->priority));
105  }
106  
107  static int
108  srv_final_cmp(const void *a, const void *b)
109  {
110  	const struct dns_srvinfo *da, *db;
111  	int res;
112  #ifdef HAVE_LDNS
113  	da = (const struct dns_srvinfo *)a;
114  	db = (const struct dns_srvinfo *)b;
115  #else
116  	da = *(struct dns_srvinfo * const *)a;
117  	db = *(struct dns_srvinfo * const *)b;
118  #endif
119  
120  	res = ((da->priority > db->priority) - (da->priority < db->priority));
121  	if (res == 0)
122  		res = ((db->finalweight > da->finalweight) - (db->finalweight < da->finalweight));
123  
124  	return (res);
125  }
126  
127  #ifndef HAVE_LDNS
128  static void
129  compute_weight(struct dns_srvinfo **d, int first, int last)
130  {
131  	int i, j;
132  	int totalweight = 0;
133  	int *chosen;
134  
135  	for (i = 0; i <= last; i++)
136  		totalweight += d[i]->weight;
137  
138  	if (totalweight == 0)
139  		return;
140  
141  	chosen = xmalloc(sizeof(int) * (last - first + 1));
142  
143  	for (i = 0; i <= last; i++) {
144  		for (;;) {
145  			chosen[i] = random() % (d[i]->weight * 100 / totalweight);
146  			for (j = 0; j < i; j++) {
147  				if (chosen[i] == chosen[j])
148  					break;
149  			}
150  			if (j == i) {
151  				d[i]->finalweight = chosen[i];
152  				break;
153  			}
154  		}
155  	}
156  
157  	free(chosen);
158  }
159  
160  struct dns_srvinfo *
161  dns_getsrvinfo(const char *zone)
162  {
163  	char host[MAXHOSTNAMELEN];
164  	query_t q;
165  	int len, qdcount, ancount, n, i;
166  	struct dns_srvinfo **res, *first;
167  	unsigned char *end, *p;
168  	unsigned int type, class, ttl, priority, weight, port;
169  	int f, l;
170  
171  	if ((len = res_query(zone, C_IN, T_SRV, q.buf, sizeof(q.buf))) == -1 ||
172  	    len < (int)sizeof(HEADER))
173  		return (NULL);
174  
175  	qdcount = ntohs(q.hdr.qdcount);
176  	ancount = ntohs(q.hdr.ancount);
177  
178  	end = q.buf + len;
179  	p = q.buf + sizeof(HEADER);
180  
181  	while(qdcount > 0 && p < end) {
182  		qdcount--;
183  		if((len = dn_expand(q.buf, end, p, host, sizeof(host))) < 0)
184  			return (NULL);
185  		p += len + NS_QFIXEDSZ;
186  	}
187  
188  	res = xcalloc(ancount, sizeof(struct dns_srvinfo *));
189  	n = 0;
190  	while (ancount > 0 && p < end) {
191  		ancount--;
192  		len = dn_expand(q.buf, end, p, host, sizeof(host));
193  		if (len < 0) {
194  			for (i = 0; i < n; i++)
195  				free(res[i]);
196  			free(res);
197  			return NULL;
198  		}
199  
200  		p += len;
201  
202  		NS_GET16(type, p);
203  		NS_GET16(class, p);
204  		NS_GET32(ttl, p);
205  		NS_GET16(len, p);
206  
207  		if (type != T_SRV) {
208  			p += len;
209  			continue;
210  		}
211  
212  		NS_GET16(priority, p);
213  		NS_GET16(weight, p);
214  		NS_GET16(port, p);
215  
216  		len = dn_expand(q.buf, end, p, host, sizeof(host));
217  		if (len < 0) {
218  			for (i = 0; i < n; i++)
219  				free(res[i]);
220  			free(res);
221  			return NULL;
222  		}
223  
224  		res[n] = xmalloc(sizeof(struct dns_srvinfo));
225  		res[n]->type = type;
226  		res[n]->class = class;
227  		res[n]->ttl = ttl;
228  		res[n]->priority = priority;
229  		res[n]->weight = weight;
230  		res[n]->port = port;
231  		res[n]->next = NULL;
232  		res[n]->finalweight = 0;
233  		strlcpy(res[n]->host, host, sizeof(res[n]->host));
234  
235  		p += len;
236  		n++;
237  	}
238  
239  	/* order by priority */
240  	qsort(res, n, sizeof(res[0]), srv_priority_cmp);
241  
242  	priority = 0;
243  	f = 0;
244  	l = 0;
245  	for (i = 0; i < n; i++) {
246  		if (res[i]->priority != priority) {
247  			if (f != l)
248  				compute_weight(res, f, l);
249  			f = i;
250  			priority = res[i]->priority;
251  		}
252  		l = i;
253  	}
254  
255  	qsort(res, n, sizeof(res[0]), srv_final_cmp);
256  
257  	for (i = 0; i < n - 1; i++)
258  		res[i]->next = res[i + 1];
259  
260  	/* Sort against priority then weight */
261  
262  	first = res[0];
263  	free(res);
264  
265  	return (first);
266  }
267  
268  int
269  set_nameserver(const char *nsname) {
270  #ifndef HAVE___RES_SETSERVERS
271  	return (-1);
272  #else
273  	struct __res_state res;
274  	union res_sockaddr_union u[MAXNS];
275  	struct addrinfo *answer = NULL;
276  	struct addrinfo *cur = NULL;
277  	struct addrinfo hint;
278  	int nscount = 0;
279  
280  	memset(u, 0, sizeof(u));
281  	memset(&hint, 0, sizeof(hint));
282  	memset(&res, 0, sizeof(res));
283  	hint.ai_socktype = SOCK_DGRAM;
284  	hint.ai_flags = AI_NUMERICHOST;
285  
286  	if (res_ninit(&res) == -1)
287  		return (-1);
288  
289  	if (getaddrinfo(nsname, NULL, &hint, &answer) == 0) {
290  		for (cur = answer; cur != NULL; cur = cur->ai_next) {
291  			if (nscount == MAXNS)
292  				break;
293  			switch (cur->ai_addr->sa_family) {
294  			case AF_INET6:
295  				u[nscount].sin6 = *(struct sockaddr_in6*)(void *)cur->ai_addr;
296  				u[nscount++].sin6.sin6_port = htons(53);
297  				break;
298  			case AF_INET:
299  				u[nscount].sin = *(struct sockaddr_in*)(void *)cur->ai_addr;
300  				u[nscount++].sin.sin_port = htons(53);
301  				break;
302  			}
303  		}
304  		if (nscount != 0)
305  			res_setservers(&res, u, nscount);
306  		freeaddrinfo(answer);
307  	}
308  	if (nscount == 0)
309  		return (-1);
310  
311  	_res = res;
312  
313  	return (0);
314  #endif
315  }
316  #else
317  
318  static ldns_resolver *lres = NULL;
319  
320  static void
321  compute_weight(struct dns_srvinfo *d, int first, int last)
322  {
323  	int i, j;
324  	int totalweight = 0;
325  	int *chosen;
326  
327  	for (i = 0; i <= last; i++)
328  		totalweight += d[i].weight;
329  
330  	if (totalweight == 0)
331  		return;
332  
333  	chosen = xmalloc(sizeof(int) * (last - first + 1));
334  
335  	for (i = 0; i <= last; i++) {
336  		for (;;) {
337  			chosen[i] = random() % (d[i].weight * 100 / totalweight);
338  			for (j = 0; j < i; j++) {
339  				if (chosen[i] == chosen[j])
340  					break;
341  			}
342  			if (j == i) {
343  				d[i].finalweight = chosen[i];
344  				break;
345  			}
346  		}
347  	}
348  
349  	free(chosen);
350  }
351  
352  struct dns_srvinfo *
353  dns_getsrvinfo(const char *zone)
354  {
355  	ldns_rdf *domain;
356  	ldns_pkt *p;
357  	ldns_rr_list *srv;
358  	struct dns_srvinfo *res;
359  	int ancount, i;
360  	int f, l, priority;
361  
362  	if (lres == NULL)
363  		if (ldns_resolver_new_frm_file(&lres, NULL) != LDNS_STATUS_OK)
364  			return (NULL);
365  
366  	domain = ldns_dname_new_frm_str(zone);
367  	if (domain == NULL)
368  		return (NULL);
369  
370  	p = ldns_resolver_query(lres, domain,
371  	    LDNS_RR_TYPE_SRV,
372  	    LDNS_RR_CLASS_IN,
373  	    LDNS_RD);
374  
375  	ldns_rdf_deep_free(domain);
376  
377  	if (p == NULL)
378  		return (NULL);
379  
380  	srv = ldns_pkt_rr_list_by_type(p, LDNS_RR_TYPE_SRV, LDNS_SECTION_ANSWER);
381  	ldns_pkt_free(p);
382  
383  	if (srv == NULL)
384  		return (NULL);
385  
386  	ancount = ldns_rr_list_rr_count(srv);
387  	res = xcalloc(ancount, sizeof(struct dns_srvinfo));
388  
389  	for (i = 0; i < ancount; i ++) {
390  		ldns_rr *rr;
391  
392  		rr = ldns_rr_list_rr(srv, i);
393  		if (rr != NULL) {
394  			char *host;
395  			res[i].class = ldns_rr_get_class(rr);
396  			res[i].ttl = ldns_rr_ttl(rr);
397  			res[i].priority = ldns_rdf2native_int16(ldns_rr_rdf(rr, 0));
398  			res[i].weight = ldns_rdf2native_int16(ldns_rr_rdf(rr, 1));
399  			res[i].port = ldns_rdf2native_int16(ldns_rr_rdf(rr, 2));
400  			host = ldns_rdf2str(ldns_rr_rdf(rr, 3));
401  			strlcpy(res[i].host, host, sizeof(res[i].host));
402  			free(host);
403  		}
404  	}
405  
406  	ldns_rr_list_deep_free(srv);
407  
408  	/* order by priority */
409  	qsort(res, ancount, sizeof(res[0]), srv_priority_cmp);
410  
411  	priority = 0;
412  	f = 0;
413  	l = 0;
414  	for (i = 0; i < ancount; i++) {
415  		if (res[i].priority != priority) {
416  			if (f != l)
417  				compute_weight(res, f, l);
418  			f = i;
419  			priority = res[i].priority;
420  		}
421  		l = i;
422  	}
423  
424  	/* Sort against priority then weight */
425  	qsort(res, ancount, sizeof(res[0]), srv_final_cmp);
426  
427  	for (i = 0; i < ancount - 1; i++)
428  		res[i].next = &res[i + 1];
429  
430  	return (res);
431  }
432  
433  int
434  set_nameserver(const char *nsname)
435  {
436  	/*
437  	 * XXX: can we use the system resolver to resolve this name ??
438  	 * The current code does this, but it is unlikely a good solution
439  	 * So here we allow IP addresses only
440  	 */
441  	ldns_rdf *rdf;
442  
443  	rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, nsname);
444  	if (rdf == NULL)
445  		rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_AAAA, nsname);
446  
447  	if (rdf == NULL)
448  		return (EPKG_FATAL);
449  
450  	if (lres == NULL)
451  		if (ldns_resolver_new_frm_file(&lres, NULL) != LDNS_STATUS_OK)
452  			return (EPKG_FATAL);
453  
454  	if (ldns_resolver_push_nameserver(lres, rdf) != LDNS_STATUS_OK)
455  		return (EPKG_FATAL);
456  
457  	return (EPKG_OK);
458  }
459  #endif