1/*	$NetBSD: dnssrv.c,v 1.3 2021/08/14 16:14:55 christos Exp $	*/
2
3/* $OpenLDAP$ */
4/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 1998-2021 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.3 2021/08/14 16:14:55 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	/* Do a shuffle per RFC2782 Page 4 */
225	for (p=n; p>1; a++, p--) {
226		if (!total) {
227			/* all remaining weights are zero,
228			   do a straight Fisher-Yates shuffle */
229			j = srv_rand() * p;
230		} else {
231			r = srv_rand() * total;
232			for (j=0; j<p; j++) {
233				r -= a[j].weight;
234				if (r < 0) {
235					total -= a[j].weight;
236					break;
237				}
238			}
239		}
240		if (j && j<p) {
241			srv_record t = a[0];
242			a[0] = a[j];
243			a[j] = t;
244		}
245	}
246}
247#endif /* HAVE_RES_QUERY */
248
249/*
250 * Lookup and return LDAP servers for domain (using the DNS
251 * SRV record _ldap._tcp.domain).
252 */
253int ldap_domain2hostlist(
254	LDAP_CONST char *domain,
255	char **list )
256{
257#ifdef HAVE_RES_QUERY
258    char *request;
259    char *hostlist = NULL;
260    srv_record *hostent_head=NULL;
261    int i, j;
262    int rc, len, cur = 0;
263    unsigned char reply[DNSBUFSIZ];
264    int hostent_count=0;
265
266	assert( domain != NULL );
267	assert( list != NULL );
268	if( *domain == '\0' ) {
269		return LDAP_PARAM_ERROR;
270	}
271
272    request = LDAP_MALLOC(strlen(domain) + sizeof("_ldap._tcp."));
273    if (request == NULL) {
274		return LDAP_NO_MEMORY;
275    }
276    sprintf(request, "_ldap._tcp.%s", domain);
277
278    LDAP_MUTEX_LOCK(&ldap_int_resolv_mutex);
279
280    rc = LDAP_UNAVAILABLE;
281#ifdef NS_HFIXEDSZ
282	/* Bind 8/9 interface */
283    len = res_query(request, ns_c_in, ns_t_srv, reply, sizeof(reply));
284#	ifndef T_SRV
285#		define T_SRV ns_t_srv
286#	endif
287#else
288	/* Bind 4 interface */
289#	ifndef T_SRV
290#		define T_SRV 33
291#	endif
292
293    len = res_query(request, C_IN, T_SRV, reply, sizeof(reply));
294#endif
295    if (len >= 0) {
296	unsigned char *p;
297	char host[DNSBUFSIZ];
298	int status;
299	u_short port, priority, weight;
300
301	/* Parse out query */
302	p = reply;
303
304#ifdef NS_HFIXEDSZ
305	/* Bind 8/9 interface */
306	p += NS_HFIXEDSZ;
307#elif defined(HFIXEDSZ)
308	/* Bind 4 interface w/ HFIXEDSZ */
309	p += HFIXEDSZ;
310#else
311	/* Bind 4 interface w/o HFIXEDSZ */
312	p += sizeof(HEADER);
313#endif
314
315	status = dn_expand(reply, reply + len, p, host, sizeof(host));
316	if (status < 0) {
317	    goto out;
318	}
319	p += status;
320	p += 4;
321
322	while (p < reply + len) {
323	    int type, class, ttl, size;
324	    status = dn_expand(reply, reply + len, p, host, sizeof(host));
325	    if (status < 0) {
326		goto out;
327	    }
328	    p += status;
329	    type = (p[0] << 8) | p[1];
330	    p += 2;
331	    class = (p[0] << 8) | p[1];
332	    p += 2;
333	    ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
334	    p += 4;
335	    size = (p[0] << 8) | p[1];
336	    p += 2;
337	    if (type == T_SRV) {
338		status = dn_expand(reply, reply + len, p + 6, host, sizeof(host));
339		if (status < 0) {
340		    goto out;
341		}
342
343		/* Get priority weight and port */
344		priority = (p[0] << 8) | p[1];
345		weight = (p[2] << 8) | p[3];
346		port = (p[4] << 8) | p[5];
347
348		if ( port == 0 || host[ 0 ] == '\0' ) {
349		    goto add_size;
350		}
351
352		hostent_head = (srv_record *) LDAP_REALLOC(hostent_head, (hostent_count+1)*(sizeof(srv_record)));
353		if(hostent_head==NULL){
354		    rc=LDAP_NO_MEMORY;
355		    goto out;
356		}
357		hostent_head[hostent_count].priority=priority;
358		hostent_head[hostent_count].weight=weight;
359		hostent_head[hostent_count].port=port;
360		strncpy(hostent_head[hostent_count].hostname, host, MAXHOST-1);
361		hostent_head[hostent_count].hostname[MAXHOST-1] = '\0';
362		hostent_count++;
363	    }
364add_size:;
365	    p += size;
366	}
367	if (!hostent_head) goto out;
368    qsort(hostent_head, hostent_count, sizeof(srv_record), srv_cmp);
369
370	if (!srv_seed)
371		srv_srand(time(0L));
372
373	/* shuffle records of same priority */
374	j = 0;
375	priority = hostent_head[0].priority;
376	for (i=1; i<hostent_count; i++) {
377		if (hostent_head[i].priority != priority) {
378			priority = hostent_head[i].priority;
379			if (i-j > 1)
380				srv_shuffle(hostent_head+j, i-j);
381			j = i;
382		}
383	}
384	if (i-j > 1)
385		srv_shuffle(hostent_head+j, i-j);
386
387    for(i=0; i<hostent_count; i++){
388	int buflen;
389        buflen = strlen(hostent_head[i].hostname) + STRLENOF(":65535 ");
390        hostlist = (char *) LDAP_REALLOC(hostlist, cur+buflen+1);
391        if (hostlist == NULL) {
392            rc = LDAP_NO_MEMORY;
393            goto out;
394        }
395        if(cur>0){
396            hostlist[cur++]=' ';
397        }
398        cur += sprintf(&hostlist[cur], "%s:%hu", hostent_head[i].hostname, hostent_head[i].port);
399    }
400    }
401
402    if (hostlist == NULL) {
403	/* No LDAP servers found in DNS. */
404	rc = LDAP_UNAVAILABLE;
405	goto out;
406    }
407
408    rc = LDAP_SUCCESS;
409	*list = hostlist;
410
411  out:
412    LDAP_MUTEX_UNLOCK(&ldap_int_resolv_mutex);
413
414    if (request != NULL) {
415	LDAP_FREE(request);
416    }
417    if (hostent_head != NULL) {
418	LDAP_FREE(hostent_head);
419    }
420    if (rc != LDAP_SUCCESS && hostlist != NULL) {
421	LDAP_FREE(hostlist);
422    }
423    return rc;
424#else
425    return LDAP_NOT_SUPPORTED;
426#endif /* HAVE_RES_QUERY */
427}
428