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
ldap_dn2domain(LDAP_CONST char * dn_in,char ** domainp)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
ldap_domain2dn(LDAP_CONST char * domain_in,char ** dnp)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
srv_srand(int seed)200 static void srv_srand(int seed) {
201 srv_seed = (float)seed / (float)RAND_MAX;
202 }
203
srv_rand()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
srv_cmp(const void * aa,const void * bb)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
srv_shuffle(srv_record * a,int n)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 /* 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 */
ldap_domain2hostlist(LDAP_CONST char * domain,char ** list)253 int 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 }
364 add_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