1 /* $NetBSD: dnssrv.c,v 1.2 2020/08/11 13:15:37 christos Exp $ */ 2 3 /* $OpenLDAP$ */ 4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 5 * 6 * Copyright 1998-2020 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.2 2020/08/11 13:15:37 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 45 int 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 126 return_error: 127 ldap_dnfree( dn ); 128 LDAP_FREE( domain.bv_val ); 129 return -1; 130 } 131 132 int 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 */ 185 typedef 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 */ 198 static float srv_seed; 199 200 static void srv_srand(int seed) { 201 srv_seed = (float)seed / (float)RAND_MAX; 202 } 203 204 static float srv_rand() { 205 float val = 9821.0 * srv_seed + .211327; 206 srv_seed = val - (int)val; 207 return srv_seed; 208 } 209 210 static 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 218 static 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 */ 262 int 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 } 373 add_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