dnssrv.c revision 1.1.1.5
1/*	$NetBSD: dnssrv.c,v 1.1.1.5 2017/02/09 01:46:46 christos Exp $	*/
2
3/* $OpenLDAP$ */
4/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 1998-2016 The OpenLDAP Foundation.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
11 * Public License.
12 *
13 * A copy of this license is available in the file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
16 */
17
18/*
19 * locate LDAP servers using DNS SRV records.
20 * Location code based on MIT Kerberos KDC location code.
21 */
22#include <sys/cdefs.h>
23__RCSID("$NetBSD: dnssrv.c,v 1.1.1.5 2017/02/09 01:46:46 christos Exp $");
24
25#include "portable.h"
26
27#include <stdio.h>
28
29#include <ac/stdlib.h>
30
31#include <ac/param.h>
32#include <ac/socket.h>
33#include <ac/string.h>
34#include <ac/time.h>
35
36#include "ldap-int.h"
37
38#ifdef HAVE_ARPA_NAMESER_H
39#include <arpa/nameser.h>
40#endif
41#ifdef HAVE_RESOLV_H
42#include <resolv.h>
43#endif
44
45int ldap_dn2domain(
46	LDAP_CONST char *dn_in,
47	char **domainp)
48{
49	int i, j;
50	char *ndomain;
51	LDAPDN dn = NULL;
52	LDAPRDN rdn = NULL;
53	LDAPAVA *ava = NULL;
54	struct berval domain = BER_BVNULL;
55	static const struct berval DC = BER_BVC("DC");
56	static const struct berval DCOID = BER_BVC("0.9.2342.19200300.100.1.25");
57
58	assert( dn_in != NULL );
59	assert( domainp != NULL );
60
61	*domainp = NULL;
62
63	if ( ldap_str2dn( dn_in, &dn, LDAP_DN_FORMAT_LDAP ) != LDAP_SUCCESS ) {
64		return -2;
65	}
66
67	if( dn ) for( i=0; dn[i] != NULL; i++ ) {
68		rdn = dn[i];
69
70		for( j=0; rdn[j] != NULL; j++ ) {
71			ava = rdn[j];
72
73			if( rdn[j+1] == NULL &&
74				(ava->la_flags & LDAP_AVA_STRING) &&
75				ava->la_value.bv_len &&
76				( ber_bvstrcasecmp( &ava->la_attr, &DC ) == 0
77				|| ber_bvcmp( &ava->la_attr, &DCOID ) == 0 ) )
78			{
79				if( domain.bv_len == 0 ) {
80					ndomain = LDAP_REALLOC( domain.bv_val,
81						ava->la_value.bv_len + 1);
82
83					if( ndomain == NULL ) {
84						goto return_error;
85					}
86
87					domain.bv_val = ndomain;
88
89					AC_MEMCPY( domain.bv_val, ava->la_value.bv_val,
90						ava->la_value.bv_len );
91
92					domain.bv_len = ava->la_value.bv_len;
93					domain.bv_val[domain.bv_len] = '\0';
94
95				} else {
96					ndomain = LDAP_REALLOC( domain.bv_val,
97						ava->la_value.bv_len + sizeof(".") + domain.bv_len );
98
99					if( ndomain == NULL ) {
100						goto return_error;
101					}
102
103					domain.bv_val = ndomain;
104					domain.bv_val[domain.bv_len++] = '.';
105					AC_MEMCPY( &domain.bv_val[domain.bv_len],
106						ava->la_value.bv_val, ava->la_value.bv_len );
107					domain.bv_len += ava->la_value.bv_len;
108					domain.bv_val[domain.bv_len] = '\0';
109				}
110			} else {
111				domain.bv_len = 0;
112			}
113		}
114	}
115
116
117	if( domain.bv_len == 0 && domain.bv_val != NULL ) {
118		LDAP_FREE( domain.bv_val );
119		domain.bv_val = NULL;
120	}
121
122	ldap_dnfree( dn );
123	*domainp = domain.bv_val;
124	return 0;
125
126return_error:
127	ldap_dnfree( dn );
128	LDAP_FREE( domain.bv_val );
129	return -1;
130}
131
132int ldap_domain2dn(
133	LDAP_CONST char *domain_in,
134	char **dnp)
135{
136	char *domain, *s, *tok_r, *dn, *dntmp;
137	size_t loc;
138
139	assert( domain_in != NULL );
140	assert( dnp != NULL );
141
142	domain = LDAP_STRDUP(domain_in);
143	if (domain == NULL) {
144		return LDAP_NO_MEMORY;
145	}
146	dn = NULL;
147	loc = 0;
148
149	for (s = ldap_pvt_strtok(domain, ".", &tok_r);
150		s != NULL;
151		s = ldap_pvt_strtok(NULL, ".", &tok_r))
152	{
153		size_t len = strlen(s);
154
155		dntmp = (char *) LDAP_REALLOC(dn, loc + sizeof(",dc=") + len );
156		if (dntmp == NULL) {
157		    if (dn != NULL)
158			LDAP_FREE(dn);
159		    LDAP_FREE(domain);
160		    return LDAP_NO_MEMORY;
161		}
162
163		dn = dntmp;
164
165		if (loc > 0) {
166		    /* not first time. */
167		    strcpy(dn + loc, ",");
168		    loc++;
169		}
170		strcpy(dn + loc, "dc=");
171		loc += sizeof("dc=")-1;
172
173		strcpy(dn + loc, s);
174		loc += len;
175    }
176
177	LDAP_FREE(domain);
178	*dnp = dn;
179	return LDAP_SUCCESS;
180}
181
182#ifdef HAVE_RES_QUERY
183#define DNSBUFSIZ (64*1024)
184#define MAXHOST	254	/* RFC 1034, max length is 253 chars */
185typedef struct srv_record {
186    u_short priority;
187    u_short weight;
188    u_short port;
189    char hostname[MAXHOST];
190} srv_record;
191
192/* Linear Congruential Generator - we don't need
193 * high quality randomness, and we don't want to
194 * interfere with anyone else's use of srand().
195 *
196 * The PRNG here cycles thru 941,955 numbers.
197 */
198static float srv_seed;
199
200static void srv_srand(int seed) {
201	srv_seed = (float)seed / (float)RAND_MAX;
202}
203
204static float srv_rand() {
205	float val = 9821.0 * srv_seed + .211327;
206	srv_seed = val - (int)val;
207	return srv_seed;
208}
209
210static int srv_cmp(const void *aa, const void *bb){
211	srv_record *a=(srv_record *)aa;
212	srv_record *b=(srv_record *)bb;
213	int i = a->priority - b->priority;
214	if (i) return i;
215	return b->weight - a->weight;
216}
217
218static void srv_shuffle(srv_record *a, int n) {
219	int i, j, total = 0, r, p;
220
221	for (i=0; i<n; i++)
222		total += a[i].weight;
223
224	/* all weights are zero, do a straight Fisher-Yates shuffle */
225	if (!total) {
226		while (n) {
227			srv_record t;
228			i = srv_rand() * n--;
229			t = a[n];
230			a[n] = a[i];
231			a[i] = t;
232		}
233		return;
234	}
235
236	/* Do a shuffle per RFC2782 Page 4 */
237	p = n;
238	for (i=0; i<n-1; i++) {
239		r = srv_rand() * total;
240		for (j=0; j<p; j++) {
241			r -= a[j].weight;
242			if (r <= 0) {
243				if (j) {
244					srv_record t = a[0];
245					a[0] = a[j];
246					a[j] = t;
247				}
248				total -= a[0].weight;
249				a++;
250				p--;
251				break;
252			}
253		}
254	}
255}
256#endif /* HAVE_RES_QUERY */
257
258/*
259 * Lookup and return LDAP servers for domain (using the DNS
260 * SRV record _ldap._tcp.domain).
261 */
262int ldap_domain2hostlist(
263	LDAP_CONST char *domain,
264	char **list )
265{
266#ifdef HAVE_RES_QUERY
267    char *request;
268    char *hostlist = NULL;
269    srv_record *hostent_head=NULL;
270    int i, j;
271    int rc, len, cur = 0;
272    unsigned char reply[DNSBUFSIZ];
273    int hostent_count=0;
274
275	assert( domain != NULL );
276	assert( list != NULL );
277	if( *domain == '\0' ) {
278		return LDAP_PARAM_ERROR;
279	}
280
281    request = LDAP_MALLOC(strlen(domain) + sizeof("_ldap._tcp."));
282    if (request == NULL) {
283		return LDAP_NO_MEMORY;
284    }
285    sprintf(request, "_ldap._tcp.%s", domain);
286
287    LDAP_MUTEX_LOCK(&ldap_int_resolv_mutex);
288
289    rc = LDAP_UNAVAILABLE;
290#ifdef NS_HFIXEDSZ
291	/* Bind 8/9 interface */
292    len = res_query(request, ns_c_in, ns_t_srv, reply, sizeof(reply));
293#	ifndef T_SRV
294#		define T_SRV ns_t_srv
295#	endif
296#else
297	/* Bind 4 interface */
298#	ifndef T_SRV
299#		define T_SRV 33
300#	endif
301
302    len = res_query(request, C_IN, T_SRV, reply, sizeof(reply));
303#endif
304    if (len >= 0) {
305	unsigned char *p;
306	char host[DNSBUFSIZ];
307	int status;
308	u_short port, priority, weight;
309
310	/* Parse out query */
311	p = reply;
312
313#ifdef NS_HFIXEDSZ
314	/* Bind 8/9 interface */
315	p += NS_HFIXEDSZ;
316#elif defined(HFIXEDSZ)
317	/* Bind 4 interface w/ HFIXEDSZ */
318	p += HFIXEDSZ;
319#else
320	/* Bind 4 interface w/o HFIXEDSZ */
321	p += sizeof(HEADER);
322#endif
323
324	status = dn_expand(reply, reply + len, p, host, sizeof(host));
325	if (status < 0) {
326	    goto out;
327	}
328	p += status;
329	p += 4;
330
331	while (p < reply + len) {
332	    int type, class, ttl, size;
333	    status = dn_expand(reply, reply + len, p, host, sizeof(host));
334	    if (status < 0) {
335		goto out;
336	    }
337	    p += status;
338	    type = (p[0] << 8) | p[1];
339	    p += 2;
340	    class = (p[0] << 8) | p[1];
341	    p += 2;
342	    ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
343	    p += 4;
344	    size = (p[0] << 8) | p[1];
345	    p += 2;
346	    if (type == T_SRV) {
347		status = dn_expand(reply, reply + len, p + 6, host, sizeof(host));
348		if (status < 0) {
349		    goto out;
350		}
351
352		/* Get priority weight and port */
353		priority = (p[0] << 8) | p[1];
354		weight = (p[2] << 8) | p[3];
355		port = (p[4] << 8) | p[5];
356
357		if ( port == 0 || host[ 0 ] == '\0' ) {
358		    goto add_size;
359		}
360
361		hostent_head = (srv_record *) LDAP_REALLOC(hostent_head, (hostent_count+1)*(sizeof(srv_record)));
362		if(hostent_head==NULL){
363		    rc=LDAP_NO_MEMORY;
364		    goto out;
365		}
366		hostent_head[hostent_count].priority=priority;
367		hostent_head[hostent_count].weight=weight;
368		hostent_head[hostent_count].port=port;
369		strncpy(hostent_head[hostent_count].hostname, host, MAXHOST-1);
370		hostent_head[hostent_count].hostname[MAXHOST-1] = '\0';
371		hostent_count++;
372	    }
373add_size:;
374	    p += size;
375	}
376	if (!hostent_head) goto out;
377    qsort(hostent_head, hostent_count, sizeof(srv_record), srv_cmp);
378
379	if (!srv_seed)
380		srv_srand(time(0L));
381
382	/* shuffle records of same priority */
383	j = 0;
384	priority = hostent_head[0].priority;
385	for (i=1; i<hostent_count; i++) {
386		if (hostent_head[i].priority != priority) {
387			priority = hostent_head[i].priority;
388			if (i-j > 1)
389				srv_shuffle(hostent_head+j, i-j);
390			j = i;
391		}
392	}
393	if (i-j > 1)
394		srv_shuffle(hostent_head+j, i-j);
395
396    for(i=0; i<hostent_count; i++){
397	int buflen;
398        buflen = strlen(hostent_head[i].hostname) + STRLENOF(":65535 ");
399        hostlist = (char *) LDAP_REALLOC(hostlist, cur+buflen+1);
400        if (hostlist == NULL) {
401            rc = LDAP_NO_MEMORY;
402            goto out;
403        }
404        if(cur>0){
405            hostlist[cur++]=' ';
406        }
407        cur += sprintf(&hostlist[cur], "%s:%hu", hostent_head[i].hostname, hostent_head[i].port);
408    }
409    }
410
411    if (hostlist == NULL) {
412	/* No LDAP servers found in DNS. */
413	rc = LDAP_UNAVAILABLE;
414	goto out;
415    }
416
417    rc = LDAP_SUCCESS;
418	*list = hostlist;
419
420  out:
421    LDAP_MUTEX_UNLOCK(&ldap_int_resolv_mutex);
422
423    if (request != NULL) {
424	LDAP_FREE(request);
425    }
426    if (hostent_head != NULL) {
427	LDAP_FREE(hostent_head);
428    }
429    if (rc != LDAP_SUCCESS && hostlist != NULL) {
430	LDAP_FREE(hostlist);
431    }
432    return rc;
433#else
434    return LDAP_NOT_SUPPORTED;
435#endif /* HAVE_RES_QUERY */
436}
437