xref: /netbsd-src/external/bsd/openldap/dist/libraries/libldap/dnssrv.c (revision 549b59ed3ccf0d36d3097190a0db27b770f3a839)
1*549b59edSchristos /*	$NetBSD: dnssrv.c,v 1.3 2021/08/14 16:14:55 christos Exp $	*/
24e6df137Slukem 
3d11b170bStron /* $OpenLDAP$ */
42de962bdSlukem /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
52de962bdSlukem  *
6*549b59edSchristos  * Copyright 1998-2021 The OpenLDAP Foundation.
72de962bdSlukem  * All rights reserved.
82de962bdSlukem  *
92de962bdSlukem  * Redistribution and use in source and binary forms, with or without
102de962bdSlukem  * modification, are permitted only as authorized by the OpenLDAP
112de962bdSlukem  * Public License.
122de962bdSlukem  *
132de962bdSlukem  * A copy of this license is available in the file LICENSE in the
142de962bdSlukem  * top-level directory of the distribution or, alternatively, at
152de962bdSlukem  * <http://www.OpenLDAP.org/license.html>.
162de962bdSlukem  */
172de962bdSlukem 
182de962bdSlukem /*
192de962bdSlukem  * locate LDAP servers using DNS SRV records.
202de962bdSlukem  * Location code based on MIT Kerberos KDC location code.
212de962bdSlukem  */
22376af7d7Schristos #include <sys/cdefs.h>
23*549b59edSchristos __RCSID("$NetBSD: dnssrv.c,v 1.3 2021/08/14 16:14:55 christos Exp $");
24376af7d7Schristos 
252de962bdSlukem #include "portable.h"
262de962bdSlukem 
272de962bdSlukem #include <stdio.h>
282de962bdSlukem 
292de962bdSlukem #include <ac/stdlib.h>
302de962bdSlukem 
312de962bdSlukem #include <ac/param.h>
322de962bdSlukem #include <ac/socket.h>
332de962bdSlukem #include <ac/string.h>
342de962bdSlukem #include <ac/time.h>
352de962bdSlukem 
362de962bdSlukem #include "ldap-int.h"
372de962bdSlukem 
382de962bdSlukem #ifdef HAVE_ARPA_NAMESER_H
392de962bdSlukem #include <arpa/nameser.h>
402de962bdSlukem #endif
412de962bdSlukem #ifdef HAVE_RESOLV_H
422de962bdSlukem #include <resolv.h>
432de962bdSlukem #endif
442de962bdSlukem 
ldap_dn2domain(LDAP_CONST char * dn_in,char ** domainp)452de962bdSlukem int ldap_dn2domain(
462de962bdSlukem 	LDAP_CONST char *dn_in,
472de962bdSlukem 	char **domainp)
482de962bdSlukem {
492de962bdSlukem 	int i, j;
502de962bdSlukem 	char *ndomain;
512de962bdSlukem 	LDAPDN dn = NULL;
522de962bdSlukem 	LDAPRDN rdn = NULL;
532de962bdSlukem 	LDAPAVA *ava = NULL;
542de962bdSlukem 	struct berval domain = BER_BVNULL;
552de962bdSlukem 	static const struct berval DC = BER_BVC("DC");
562de962bdSlukem 	static const struct berval DCOID = BER_BVC("0.9.2342.19200300.100.1.25");
572de962bdSlukem 
582de962bdSlukem 	assert( dn_in != NULL );
592de962bdSlukem 	assert( domainp != NULL );
602de962bdSlukem 
612de962bdSlukem 	*domainp = NULL;
622de962bdSlukem 
632de962bdSlukem 	if ( ldap_str2dn( dn_in, &dn, LDAP_DN_FORMAT_LDAP ) != LDAP_SUCCESS ) {
642de962bdSlukem 		return -2;
652de962bdSlukem 	}
662de962bdSlukem 
672de962bdSlukem 	if( dn ) for( i=0; dn[i] != NULL; i++ ) {
682de962bdSlukem 		rdn = dn[i];
692de962bdSlukem 
702de962bdSlukem 		for( j=0; rdn[j] != NULL; j++ ) {
712de962bdSlukem 			ava = rdn[j];
722de962bdSlukem 
732de962bdSlukem 			if( rdn[j+1] == NULL &&
742de962bdSlukem 				(ava->la_flags & LDAP_AVA_STRING) &&
752de962bdSlukem 				ava->la_value.bv_len &&
762de962bdSlukem 				( ber_bvstrcasecmp( &ava->la_attr, &DC ) == 0
772de962bdSlukem 				|| ber_bvcmp( &ava->la_attr, &DCOID ) == 0 ) )
782de962bdSlukem 			{
792de962bdSlukem 				if( domain.bv_len == 0 ) {
802de962bdSlukem 					ndomain = LDAP_REALLOC( domain.bv_val,
812de962bdSlukem 						ava->la_value.bv_len + 1);
822de962bdSlukem 
832de962bdSlukem 					if( ndomain == NULL ) {
842de962bdSlukem 						goto return_error;
852de962bdSlukem 					}
862de962bdSlukem 
872de962bdSlukem 					domain.bv_val = ndomain;
882de962bdSlukem 
892de962bdSlukem 					AC_MEMCPY( domain.bv_val, ava->la_value.bv_val,
902de962bdSlukem 						ava->la_value.bv_len );
912de962bdSlukem 
922de962bdSlukem 					domain.bv_len = ava->la_value.bv_len;
932de962bdSlukem 					domain.bv_val[domain.bv_len] = '\0';
942de962bdSlukem 
952de962bdSlukem 				} else {
962de962bdSlukem 					ndomain = LDAP_REALLOC( domain.bv_val,
972de962bdSlukem 						ava->la_value.bv_len + sizeof(".") + domain.bv_len );
982de962bdSlukem 
992de962bdSlukem 					if( ndomain == NULL ) {
1002de962bdSlukem 						goto return_error;
1012de962bdSlukem 					}
1022de962bdSlukem 
1032de962bdSlukem 					domain.bv_val = ndomain;
1042de962bdSlukem 					domain.bv_val[domain.bv_len++] = '.';
1052de962bdSlukem 					AC_MEMCPY( &domain.bv_val[domain.bv_len],
1062de962bdSlukem 						ava->la_value.bv_val, ava->la_value.bv_len );
1072de962bdSlukem 					domain.bv_len += ava->la_value.bv_len;
1082de962bdSlukem 					domain.bv_val[domain.bv_len] = '\0';
1092de962bdSlukem 				}
1102de962bdSlukem 			} else {
1112de962bdSlukem 				domain.bv_len = 0;
1122de962bdSlukem 			}
1132de962bdSlukem 		}
1142de962bdSlukem 	}
1152de962bdSlukem 
1162de962bdSlukem 
1172de962bdSlukem 	if( domain.bv_len == 0 && domain.bv_val != NULL ) {
1182de962bdSlukem 		LDAP_FREE( domain.bv_val );
1192de962bdSlukem 		domain.bv_val = NULL;
1202de962bdSlukem 	}
1212de962bdSlukem 
1222de962bdSlukem 	ldap_dnfree( dn );
1232de962bdSlukem 	*domainp = domain.bv_val;
1242de962bdSlukem 	return 0;
1252de962bdSlukem 
1262de962bdSlukem return_error:
1272de962bdSlukem 	ldap_dnfree( dn );
1282de962bdSlukem 	LDAP_FREE( domain.bv_val );
1292de962bdSlukem 	return -1;
1302de962bdSlukem }
1312de962bdSlukem 
ldap_domain2dn(LDAP_CONST char * domain_in,char ** dnp)1322de962bdSlukem int ldap_domain2dn(
1332de962bdSlukem 	LDAP_CONST char *domain_in,
1342de962bdSlukem 	char **dnp)
1352de962bdSlukem {
1362de962bdSlukem 	char *domain, *s, *tok_r, *dn, *dntmp;
1372de962bdSlukem 	size_t loc;
1382de962bdSlukem 
1392de962bdSlukem 	assert( domain_in != NULL );
1402de962bdSlukem 	assert( dnp != NULL );
1412de962bdSlukem 
1422de962bdSlukem 	domain = LDAP_STRDUP(domain_in);
1432de962bdSlukem 	if (domain == NULL) {
1442de962bdSlukem 		return LDAP_NO_MEMORY;
1452de962bdSlukem 	}
1462de962bdSlukem 	dn = NULL;
1472de962bdSlukem 	loc = 0;
1482de962bdSlukem 
1492de962bdSlukem 	for (s = ldap_pvt_strtok(domain, ".", &tok_r);
1502de962bdSlukem 		s != NULL;
1512de962bdSlukem 		s = ldap_pvt_strtok(NULL, ".", &tok_r))
1522de962bdSlukem 	{
1532de962bdSlukem 		size_t len = strlen(s);
1542de962bdSlukem 
1552de962bdSlukem 		dntmp = (char *) LDAP_REALLOC(dn, loc + sizeof(",dc=") + len );
1562de962bdSlukem 		if (dntmp == NULL) {
1572de962bdSlukem 		    if (dn != NULL)
1582de962bdSlukem 			LDAP_FREE(dn);
1592de962bdSlukem 		    LDAP_FREE(domain);
1602de962bdSlukem 		    return LDAP_NO_MEMORY;
1612de962bdSlukem 		}
1622de962bdSlukem 
1632de962bdSlukem 		dn = dntmp;
1642de962bdSlukem 
1652de962bdSlukem 		if (loc > 0) {
1662de962bdSlukem 		    /* not first time. */
1672de962bdSlukem 		    strcpy(dn + loc, ",");
1682de962bdSlukem 		    loc++;
1692de962bdSlukem 		}
1702de962bdSlukem 		strcpy(dn + loc, "dc=");
1712de962bdSlukem 		loc += sizeof("dc=")-1;
1722de962bdSlukem 
1732de962bdSlukem 		strcpy(dn + loc, s);
1742de962bdSlukem 		loc += len;
1752de962bdSlukem     }
1762de962bdSlukem 
1772de962bdSlukem 	LDAP_FREE(domain);
1782de962bdSlukem 	*dnp = dn;
1792de962bdSlukem 	return LDAP_SUCCESS;
1802de962bdSlukem }
1812de962bdSlukem 
182376af7d7Schristos #ifdef HAVE_RES_QUERY
183376af7d7Schristos #define DNSBUFSIZ (64*1024)
184376af7d7Schristos #define MAXHOST	254	/* RFC 1034, max length is 253 chars */
185376af7d7Schristos typedef struct srv_record {
186376af7d7Schristos     u_short priority;
187376af7d7Schristos     u_short weight;
188376af7d7Schristos     u_short port;
189376af7d7Schristos     char hostname[MAXHOST];
190376af7d7Schristos } srv_record;
191376af7d7Schristos 
192376af7d7Schristos /* Linear Congruential Generator - we don't need
193376af7d7Schristos  * high quality randomness, and we don't want to
194376af7d7Schristos  * interfere with anyone else's use of srand().
195376af7d7Schristos  *
196376af7d7Schristos  * The PRNG here cycles thru 941,955 numbers.
197376af7d7Schristos  */
198376af7d7Schristos static float srv_seed;
199376af7d7Schristos 
srv_srand(int seed)200376af7d7Schristos static void srv_srand(int seed) {
201376af7d7Schristos 	srv_seed = (float)seed / (float)RAND_MAX;
202376af7d7Schristos }
203376af7d7Schristos 
srv_rand()204376af7d7Schristos static float srv_rand() {
205376af7d7Schristos 	float val = 9821.0 * srv_seed + .211327;
206376af7d7Schristos 	srv_seed = val - (int)val;
207376af7d7Schristos 	return srv_seed;
208376af7d7Schristos }
209376af7d7Schristos 
srv_cmp(const void * aa,const void * bb)210376af7d7Schristos static int srv_cmp(const void *aa, const void *bb){
211376af7d7Schristos 	srv_record *a=(srv_record *)aa;
212376af7d7Schristos 	srv_record *b=(srv_record *)bb;
213376af7d7Schristos 	int i = a->priority - b->priority;
214376af7d7Schristos 	if (i) return i;
215376af7d7Schristos 	return b->weight - a->weight;
216376af7d7Schristos }
217376af7d7Schristos 
srv_shuffle(srv_record * a,int n)218376af7d7Schristos static void srv_shuffle(srv_record *a, int n) {
219376af7d7Schristos 	int i, j, total = 0, r, p;
220376af7d7Schristos 
221376af7d7Schristos 	for (i=0; i<n; i++)
222376af7d7Schristos 		total += a[i].weight;
223376af7d7Schristos 
224376af7d7Schristos 	/* Do a shuffle per RFC2782 Page 4 */
225*549b59edSchristos 	for (p=n; p>1; a++, p--) {
226*549b59edSchristos 		if (!total) {
227*549b59edSchristos 			/* all remaining weights are zero,
228*549b59edSchristos 			   do a straight Fisher-Yates shuffle */
229*549b59edSchristos 			j = srv_rand() * p;
230*549b59edSchristos 		} else {
231376af7d7Schristos 			r = srv_rand() * total;
232376af7d7Schristos 			for (j=0; j<p; j++) {
233376af7d7Schristos 				r -= a[j].weight;
234*549b59edSchristos 				if (r < 0) {
235*549b59edSchristos 					total -= a[j].weight;
236*549b59edSchristos 					break;
237*549b59edSchristos 				}
238*549b59edSchristos 			}
239*549b59edSchristos 		}
240*549b59edSchristos 		if (j && j<p) {
241376af7d7Schristos 			srv_record t = a[0];
242376af7d7Schristos 			a[0] = a[j];
243376af7d7Schristos 			a[j] = t;
244376af7d7Schristos 		}
245376af7d7Schristos 	}
246376af7d7Schristos }
247376af7d7Schristos #endif /* HAVE_RES_QUERY */
248376af7d7Schristos 
2492de962bdSlukem /*
2502de962bdSlukem  * Lookup and return LDAP servers for domain (using the DNS
2512de962bdSlukem  * SRV record _ldap._tcp.domain).
2522de962bdSlukem  */
ldap_domain2hostlist(LDAP_CONST char * domain,char ** list)2532de962bdSlukem int ldap_domain2hostlist(
2542de962bdSlukem 	LDAP_CONST char *domain,
2552de962bdSlukem 	char **list )
2562de962bdSlukem {
2572de962bdSlukem #ifdef HAVE_RES_QUERY
2582de962bdSlukem     char *request;
2592de962bdSlukem     char *hostlist = NULL;
260376af7d7Schristos     srv_record *hostent_head=NULL;
261376af7d7Schristos     int i, j;
2622de962bdSlukem     int rc, len, cur = 0;
2632de962bdSlukem     unsigned char reply[DNSBUFSIZ];
264376af7d7Schristos     int hostent_count=0;
2652de962bdSlukem 
2662de962bdSlukem 	assert( domain != NULL );
2672de962bdSlukem 	assert( list != NULL );
2682de962bdSlukem 	if( *domain == '\0' ) {
2692de962bdSlukem 		return LDAP_PARAM_ERROR;
2702de962bdSlukem 	}
2712de962bdSlukem 
2722de962bdSlukem     request = LDAP_MALLOC(strlen(domain) + sizeof("_ldap._tcp."));
2732de962bdSlukem     if (request == NULL) {
2742de962bdSlukem 		return LDAP_NO_MEMORY;
2752de962bdSlukem     }
2762de962bdSlukem     sprintf(request, "_ldap._tcp.%s", domain);
2772de962bdSlukem 
278d11b170bStron     LDAP_MUTEX_LOCK(&ldap_int_resolv_mutex);
2792de962bdSlukem 
2802de962bdSlukem     rc = LDAP_UNAVAILABLE;
2812de962bdSlukem #ifdef NS_HFIXEDSZ
2822de962bdSlukem 	/* Bind 8/9 interface */
2832de962bdSlukem     len = res_query(request, ns_c_in, ns_t_srv, reply, sizeof(reply));
2842de962bdSlukem #	ifndef T_SRV
2852de962bdSlukem #		define T_SRV ns_t_srv
2862de962bdSlukem #	endif
2872de962bdSlukem #else
2882de962bdSlukem 	/* Bind 4 interface */
2892de962bdSlukem #	ifndef T_SRV
2902de962bdSlukem #		define T_SRV 33
2912de962bdSlukem #	endif
2922de962bdSlukem 
2932de962bdSlukem     len = res_query(request, C_IN, T_SRV, reply, sizeof(reply));
2942de962bdSlukem #endif
2952de962bdSlukem     if (len >= 0) {
2962de962bdSlukem 	unsigned char *p;
2972de962bdSlukem 	char host[DNSBUFSIZ];
2982de962bdSlukem 	int status;
299376af7d7Schristos 	u_short port, priority, weight;
3002de962bdSlukem 
3012de962bdSlukem 	/* Parse out query */
3022de962bdSlukem 	p = reply;
3032de962bdSlukem 
3042de962bdSlukem #ifdef NS_HFIXEDSZ
3052de962bdSlukem 	/* Bind 8/9 interface */
3062de962bdSlukem 	p += NS_HFIXEDSZ;
3072de962bdSlukem #elif defined(HFIXEDSZ)
3082de962bdSlukem 	/* Bind 4 interface w/ HFIXEDSZ */
3092de962bdSlukem 	p += HFIXEDSZ;
3102de962bdSlukem #else
3112de962bdSlukem 	/* Bind 4 interface w/o HFIXEDSZ */
3122de962bdSlukem 	p += sizeof(HEADER);
3132de962bdSlukem #endif
3142de962bdSlukem 
3152de962bdSlukem 	status = dn_expand(reply, reply + len, p, host, sizeof(host));
3162de962bdSlukem 	if (status < 0) {
3172de962bdSlukem 	    goto out;
3182de962bdSlukem 	}
3192de962bdSlukem 	p += status;
3202de962bdSlukem 	p += 4;
3212de962bdSlukem 
3222de962bdSlukem 	while (p < reply + len) {
3232de962bdSlukem 	    int type, class, ttl, size;
3242de962bdSlukem 	    status = dn_expand(reply, reply + len, p, host, sizeof(host));
3252de962bdSlukem 	    if (status < 0) {
3262de962bdSlukem 		goto out;
3272de962bdSlukem 	    }
3282de962bdSlukem 	    p += status;
3292de962bdSlukem 	    type = (p[0] << 8) | p[1];
3302de962bdSlukem 	    p += 2;
3312de962bdSlukem 	    class = (p[0] << 8) | p[1];
3322de962bdSlukem 	    p += 2;
3332de962bdSlukem 	    ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
3342de962bdSlukem 	    p += 4;
3352de962bdSlukem 	    size = (p[0] << 8) | p[1];
3362de962bdSlukem 	    p += 2;
3372de962bdSlukem 	    if (type == T_SRV) {
3382de962bdSlukem 		status = dn_expand(reply, reply + len, p + 6, host, sizeof(host));
3392de962bdSlukem 		if (status < 0) {
3402de962bdSlukem 		    goto out;
3412de962bdSlukem 		}
342376af7d7Schristos 
343376af7d7Schristos 		/* Get priority weight and port */
344376af7d7Schristos 		priority = (p[0] << 8) | p[1];
345376af7d7Schristos 		weight = (p[2] << 8) | p[3];
3462de962bdSlukem 		port = (p[4] << 8) | p[5];
3472de962bdSlukem 
3482de962bdSlukem 		if ( port == 0 || host[ 0 ] == '\0' ) {
3492de962bdSlukem 		    goto add_size;
3502de962bdSlukem 		}
3512de962bdSlukem 
352376af7d7Schristos 		hostent_head = (srv_record *) LDAP_REALLOC(hostent_head, (hostent_count+1)*(sizeof(srv_record)));
353376af7d7Schristos 		if(hostent_head==NULL){
354376af7d7Schristos 		    rc=LDAP_NO_MEMORY;
355376af7d7Schristos 		    goto out;
356376af7d7Schristos 		}
357376af7d7Schristos 		hostent_head[hostent_count].priority=priority;
358376af7d7Schristos 		hostent_head[hostent_count].weight=weight;
359376af7d7Schristos 		hostent_head[hostent_count].port=port;
360376af7d7Schristos 		strncpy(hostent_head[hostent_count].hostname, host, MAXHOST-1);
361376af7d7Schristos 		hostent_head[hostent_count].hostname[MAXHOST-1] = '\0';
362376af7d7Schristos 		hostent_count++;
363376af7d7Schristos 	    }
364376af7d7Schristos add_size:;
365376af7d7Schristos 	    p += size;
366376af7d7Schristos 	}
367376af7d7Schristos 	if (!hostent_head) goto out;
368376af7d7Schristos     qsort(hostent_head, hostent_count, sizeof(srv_record), srv_cmp);
369376af7d7Schristos 
370376af7d7Schristos 	if (!srv_seed)
371376af7d7Schristos 		srv_srand(time(0L));
372376af7d7Schristos 
373376af7d7Schristos 	/* shuffle records of same priority */
374376af7d7Schristos 	j = 0;
375376af7d7Schristos 	priority = hostent_head[0].priority;
376376af7d7Schristos 	for (i=1; i<hostent_count; i++) {
377376af7d7Schristos 		if (hostent_head[i].priority != priority) {
378376af7d7Schristos 			priority = hostent_head[i].priority;
379376af7d7Schristos 			if (i-j > 1)
380376af7d7Schristos 				srv_shuffle(hostent_head+j, i-j);
381376af7d7Schristos 			j = i;
382376af7d7Schristos 		}
383376af7d7Schristos 	}
384376af7d7Schristos 	if (i-j > 1)
385376af7d7Schristos 		srv_shuffle(hostent_head+j, i-j);
386376af7d7Schristos 
387376af7d7Schristos     for(i=0; i<hostent_count; i++){
388376af7d7Schristos 	int buflen;
389376af7d7Schristos         buflen = strlen(hostent_head[i].hostname) + STRLENOF(":65535 ");
3902de962bdSlukem         hostlist = (char *) LDAP_REALLOC(hostlist, cur+buflen+1);
3912de962bdSlukem         if (hostlist == NULL) {
3922de962bdSlukem             rc = LDAP_NO_MEMORY;
3932de962bdSlukem             goto out;
3942de962bdSlukem         }
3952de962bdSlukem         if(cur>0){
3962de962bdSlukem             hostlist[cur++]=' ';
3972de962bdSlukem         }
398376af7d7Schristos         cur += sprintf(&hostlist[cur], "%s:%hu", hostent_head[i].hostname, hostent_head[i].port);
3992de962bdSlukem     }
4002de962bdSlukem     }
401376af7d7Schristos 
4022de962bdSlukem     if (hostlist == NULL) {
4032de962bdSlukem 	/* No LDAP servers found in DNS. */
4042de962bdSlukem 	rc = LDAP_UNAVAILABLE;
4052de962bdSlukem 	goto out;
4062de962bdSlukem     }
4072de962bdSlukem 
4082de962bdSlukem     rc = LDAP_SUCCESS;
4092de962bdSlukem 	*list = hostlist;
4102de962bdSlukem 
4112de962bdSlukem   out:
412d11b170bStron     LDAP_MUTEX_UNLOCK(&ldap_int_resolv_mutex);
4132de962bdSlukem 
4142de962bdSlukem     if (request != NULL) {
4152de962bdSlukem 	LDAP_FREE(request);
4162de962bdSlukem     }
417376af7d7Schristos     if (hostent_head != NULL) {
418376af7d7Schristos 	LDAP_FREE(hostent_head);
419376af7d7Schristos     }
4202de962bdSlukem     if (rc != LDAP_SUCCESS && hostlist != NULL) {
4212de962bdSlukem 	LDAP_FREE(hostlist);
4222de962bdSlukem     }
4232de962bdSlukem     return rc;
4242de962bdSlukem #else
4252de962bdSlukem     return LDAP_NOT_SUPPORTED;
4262de962bdSlukem #endif /* HAVE_RES_QUERY */
4272de962bdSlukem }
428