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