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