dnssrv.c 9.12 KB
Newer Older
Luke Howard's avatar
 
Luke Howard committed
1
/* $OpenLDAP$ */
2
3
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
4
 * Copyright 1998-2019 The OpenLDAP Foundation.
5
6
7
8
9
10
11
12
13
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
Luke Howard's avatar
 
Luke Howard committed
14
15
16
 */

/*
17
18
 * locate LDAP servers using DNS SRV records.
 * Location code based on MIT Kerberos KDC location code.
Luke Howard's avatar
 
Luke Howard committed
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 */
#include "portable.h"

#include <stdio.h>

#include <ac/stdlib.h>

#include <ac/param.h>
#include <ac/socket.h>
#include <ac/string.h>
#include <ac/time.h>

#include "ldap-int.h"

#ifdef HAVE_ARPA_NAMESER_H
#include <arpa/nameser.h>
#endif
#ifdef HAVE_RESOLV_H
#include <resolv.h>
#endif

40
41
42
43
int ldap_dn2domain(
	LDAP_CONST char *dn_in,
	char **domainp)
{
44
45
	int i, j;
	char *ndomain;
Howard Chu's avatar
Howard Chu committed
46
47
	LDAPDN dn = NULL;
	LDAPRDN rdn = NULL;
48
	LDAPAVA *ava = NULL;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
49
	struct berval domain = BER_BVNULL;
50
51
	static const struct berval DC = BER_BVC("DC");
	static const struct berval DCOID = BER_BVC("0.9.2342.19200300.100.1.25");
52

Kurt Zeilenga's avatar
Kurt Zeilenga committed
53
54
	assert( dn_in != NULL );
	assert( domainp != NULL );
55

Howard Chu's avatar
Howard Chu committed
56
57
	*domainp = NULL;

58
	if ( ldap_str2dn( dn_in, &dn, LDAP_DN_FORMAT_LDAP ) != LDAP_SUCCESS ) {
59
60
61
		return -2;
	}

Howard Chu's avatar
Howard Chu committed
62
63
	if( dn ) for( i=0; dn[i] != NULL; i++ ) {
		rdn = dn[i];
64

Howard Chu's avatar
Howard Chu committed
65
66
		for( j=0; rdn[j] != NULL; j++ ) {
			ava = rdn[j];
Kurt Zeilenga's avatar
Kurt Zeilenga committed
67

Howard Chu's avatar
Howard Chu committed
68
69
70
			if( rdn[j+1] == NULL &&
				(ava->la_flags & LDAP_AVA_STRING) &&
				ava->la_value.bv_len &&
71
				( ber_bvstrcasecmp( &ava->la_attr, &DC ) == 0
Pierangelo Masarati's avatar
Pierangelo Masarati committed
72
				|| ber_bvcmp( &ava->la_attr, &DCOID ) == 0 ) )
73
			{
74
75
76
				if( domain.bv_len == 0 ) {
					ndomain = LDAP_REALLOC( domain.bv_val,
						ava->la_value.bv_len + 1);
77

78
79
80
					if( ndomain == NULL ) {
						goto return_error;
					}
81

82
					domain.bv_val = ndomain;
83

84
85
					AC_MEMCPY( domain.bv_val, ava->la_value.bv_val,
						ava->la_value.bv_len );
86

87
88
					domain.bv_len = ava->la_value.bv_len;
					domain.bv_val[domain.bv_len] = '\0';
89

90
				} else {
91
92
93
94
95
96
97
98
99
100
101
102
103
					ndomain = LDAP_REALLOC( domain.bv_val,
						ava->la_value.bv_len + sizeof(".") + domain.bv_len );

					if( ndomain == NULL ) {
						goto return_error;
					}

					domain.bv_val = ndomain;
					domain.bv_val[domain.bv_len++] = '.';
					AC_MEMCPY( &domain.bv_val[domain.bv_len],
						ava->la_value.bv_val, ava->la_value.bv_len );
					domain.bv_len += ava->la_value.bv_len;
					domain.bv_val[domain.bv_len] = '\0';
104
				}
105
106
			} else {
				domain.bv_len = 0;
107
			}
108
109
		} 
	}
Kurt Zeilenga's avatar
Kurt Zeilenga committed
110

111

112
113
114
	if( domain.bv_len == 0 && domain.bv_val != NULL ) {
		LDAP_FREE( domain.bv_val );
		domain.bv_val = NULL;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
115
116
	}

117
118
	ldap_dnfree( dn );
	*domainp = domain.bv_val;
119
	return 0;
120
121
122
123
124

return_error:
	ldap_dnfree( dn );
	LDAP_FREE( domain.bv_val );
	return -1;
125
126
127
128
129
}

int ldap_domain2dn(
	LDAP_CONST char *domain_in,
	char **dnp)
Luke Howard's avatar
 
Luke Howard committed
130
{
Kurt Zeilenga's avatar
Kurt Zeilenga committed
131
132
	char *domain, *s, *tok_r, *dn, *dntmp;
	size_t loc;
Luke Howard's avatar
 
Luke Howard committed
133

Kurt Zeilenga's avatar
Kurt Zeilenga committed
134
135
136
	assert( domain_in != NULL );
	assert( dnp != NULL );

Kurt Zeilenga's avatar
Kurt Zeilenga committed
137
138
	domain = LDAP_STRDUP(domain_in);
	if (domain == NULL) {
Kurt Zeilenga's avatar
Kurt Zeilenga committed
139
		return LDAP_NO_MEMORY;
Luke Howard's avatar
 
Luke Howard committed
140
	}
Kurt Zeilenga's avatar
Kurt Zeilenga committed
141
142
143
144
145
146
147
148
149
150
	dn = NULL;
	loc = 0;

	for (s = ldap_pvt_strtok(domain, ".", &tok_r);
		s != NULL;
		s = ldap_pvt_strtok(NULL, ".", &tok_r))
	{
		size_t len = strlen(s);

		dntmp = (char *) LDAP_REALLOC(dn, loc + sizeof(",dc=") + len );
Luke Howard's avatar
Luke Howard committed
151
152
		if (dntmp == NULL) {
		    if (dn != NULL)
Kurt Zeilenga's avatar
Kurt Zeilenga committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
			LDAP_FREE(dn);
		    LDAP_FREE(domain);
		    return LDAP_NO_MEMORY;
		}

		dn = dntmp;

		if (loc > 0) {
		    /* not first time. */
		    strcpy(dn + loc, ",");
		    loc++;
		}
		strcpy(dn + loc, "dc=");
		loc += sizeof("dc=")-1;

		strcpy(dn + loc, s);
		loc += len;
Luke Howard's avatar
 
Luke Howard committed
170
171
    }

Kurt Zeilenga's avatar
Kurt Zeilenga committed
172
173
174
	LDAP_FREE(domain);
	*dnp = dn;
	return LDAP_SUCCESS;
Luke Howard's avatar
 
Luke Howard committed
175
176
}

177
178
#ifdef HAVE_RES_QUERY
#define DNSBUFSIZ (64*1024)
179
#define MAXHOST	254	/* RFC 1034, max length is 253 chars */
180
181
182
183
typedef struct srv_record {
    u_short priority;
    u_short weight;
    u_short port;
184
    char hostname[MAXHOST];
185
186
} srv_record;

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/* Linear Congruential Generator - we don't need
 * high quality randomness, and we don't want to
 * interfere with anyone else's use of srand().
 *
 * The PRNG here cycles thru 941,955 numbers.
 */
static float srv_seed;

static void srv_srand(int seed) {
	srv_seed = (float)seed / (float)RAND_MAX;
}

static float srv_rand() {
	float val = 9821.0 * srv_seed + .211327;
	srv_seed = val - (int)val;
	return srv_seed;
}
204
205

static int srv_cmp(const void *aa, const void *bb){
206
207
208
209
210
211
	srv_record *a=(srv_record *)aa;
	srv_record *b=(srv_record *)bb;
	int i = a->priority - b->priority;
	if (i) return i;
	return b->weight - a->weight;
}
212

213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
static void srv_shuffle(srv_record *a, int n) {
	int i, j, total = 0, r, p;

	for (i=0; i<n; i++)
		total += a[i].weight;

	/* all weights are zero, do a straight Fisher-Yates shuffle */
	if (!total) {
		while (n) {
			srv_record t;
			i = srv_rand() * n--;
			t = a[n];
			a[n] = a[i];
			a[i] = t;
		}
		return;
229
	}
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

	/* Do a shuffle per RFC2782 Page 4 */
	p = n;
	for (i=0; i<n-1; i++) {
		r = srv_rand() * total;
		for (j=0; j<p; j++) {
			r -= a[j].weight;
			if (r <= 0) {
				if (j) {
					srv_record t = a[0];
					a[0] = a[j];
					a[j] = t;
				}
				total -= a[0].weight;
				a++;
				p--;
				break;
			}
		}
249
250
251
252
	}
}
#endif /* HAVE_RES_QUERY */

Luke Howard's avatar
 
Luke Howard committed
253
/*
254
255
 * Lookup and return LDAP servers for domain (using the DNS
 * SRV record _ldap._tcp.domain).
Luke Howard's avatar
 
Luke Howard committed
256
 */
257
258
259
int ldap_domain2hostlist(
	LDAP_CONST char *domain,
	char **list )
Luke Howard's avatar
 
Luke Howard committed
260
{
261
#ifdef HAVE_RES_QUERY
Luke Howard's avatar
 
Luke Howard committed
262
263
    char *request;
    char *hostlist = NULL;
264
    srv_record *hostent_head=NULL;
265
    int i, j;
Luke Howard's avatar
 
Luke Howard committed
266
    int rc, len, cur = 0;
267
    unsigned char reply[DNSBUFSIZ];
268
    int hostent_count=0;
Luke Howard's avatar
 
Luke Howard committed
269

270
271
272
	assert( domain != NULL );
	assert( list != NULL );
	if( *domain == '\0' ) {
273
274
275
		return LDAP_PARAM_ERROR;
	}

Luke Howard's avatar
 
Luke Howard committed
276
277
    request = LDAP_MALLOC(strlen(domain) + sizeof("_ldap._tcp."));
    if (request == NULL) {
278
		return LDAP_NO_MEMORY;
Luke Howard's avatar
 
Luke Howard committed
279
280
281
    }
    sprintf(request, "_ldap._tcp.%s", domain);

Howard Chu's avatar
Howard Chu committed
282
    LDAP_MUTEX_LOCK(&ldap_int_resolv_mutex);
Luke Howard's avatar
 
Luke Howard committed
283

Kurt Zeilenga's avatar
Kurt Zeilenga committed
284
    rc = LDAP_UNAVAILABLE;
285
286
#ifdef NS_HFIXEDSZ
	/* Bind 8/9 interface */
287
    len = res_query(request, ns_c_in, ns_t_srv, reply, sizeof(reply));
Kurt Zeilenga's avatar
Kurt Zeilenga committed
288
289
290
#	ifndef T_SRV
#		define T_SRV ns_t_srv
#	endif
291
#else
Kurt Zeilenga's avatar
Kurt Zeilenga committed
292
293
294
295
296
	/* Bind 4 interface */
#	ifndef T_SRV
#		define T_SRV 33
#	endif

297
298
    len = res_query(request, C_IN, T_SRV, reply, sizeof(reply));
#endif
Luke Howard's avatar
 
Luke Howard committed
299
300
    if (len >= 0) {
	unsigned char *p;
301
	char host[DNSBUFSIZ];
Luke Howard's avatar
 
Luke Howard committed
302
	int status;
303
	u_short port, priority, weight;
Luke Howard's avatar
 
Luke Howard committed
304
305
306

	/* Parse out query */
	p = reply;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
307

308
309
#ifdef NS_HFIXEDSZ
	/* Bind 8/9 interface */
Kurt Zeilenga's avatar
Kurt Zeilenga committed
310
	p += NS_HFIXEDSZ;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
311
312
#elif defined(HFIXEDSZ)
	/* Bind 4 interface w/ HFIXEDSZ */
313
	p += HFIXEDSZ;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
314
315
316
#else
	/* Bind 4 interface w/o HFIXEDSZ */
	p += sizeof(HEADER);
317
#endif
Kurt Zeilenga's avatar
Kurt Zeilenga committed
318

Luke Howard's avatar
 
Luke Howard committed
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
	status = dn_expand(reply, reply + len, p, host, sizeof(host));
	if (status < 0) {
	    goto out;
	}
	p += status;
	p += 4;

	while (p < reply + len) {
	    int type, class, ttl, size;
	    status = dn_expand(reply, reply + len, p, host, sizeof(host));
	    if (status < 0) {
		goto out;
	    }
	    p += status;
	    type = (p[0] << 8) | p[1];
	    p += 2;
	    class = (p[0] << 8) | p[1];
	    p += 2;
	    ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
	    p += 4;
	    size = (p[0] << 8) | p[1];
	    p += 2;
	    if (type == T_SRV) {
		status = dn_expand(reply, reply + len, p + 6, host, sizeof(host));
		if (status < 0) {
		    goto out;
		}
346
347
348
349

		/* Get priority weight and port */
		priority = (p[0] << 8) | p[1];
		weight = (p[2] << 8) | p[3];
Luke Howard's avatar
 
Luke Howard committed
350
351
		port = (p[4] << 8) | p[5];

352
353
354
355
		if ( port == 0 || host[ 0 ] == '\0' ) {
		    goto add_size;
		}

356
357
358
		hostent_head = (srv_record *) LDAP_REALLOC(hostent_head, (hostent_count+1)*(sizeof(srv_record)));
		if(hostent_head==NULL){
		    rc=LDAP_NO_MEMORY;
Luke Howard's avatar
 
Luke Howard committed
359
360
		    goto out;
		}
361
		hostent_head[hostent_count].priority=priority;
Howard Chu's avatar
Howard Chu committed
362
		hostent_head[hostent_count].weight=weight;
363
		hostent_head[hostent_count].port=port;
Howard Chu's avatar
Howard Chu committed
364
365
		strncpy(hostent_head[hostent_count].hostname, host, MAXHOST-1);
		hostent_head[hostent_count].hostname[MAXHOST-1] = '\0';
366
		hostent_count++;
Luke Howard's avatar
 
Luke Howard committed
367
	    }
368
add_size:;
Luke Howard's avatar
 
Luke Howard committed
369
370
	    p += size;
	}
Howard Chu's avatar
Howard Chu committed
371
	if (!hostent_head) goto out;
372
373
    qsort(hostent_head, hostent_count, sizeof(srv_record), srv_cmp);

374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
	if (!srv_seed)
		srv_srand(time(0L));

	/* shuffle records of same priority */
	j = 0;
	priority = hostent_head[0].priority;
	for (i=1; i<hostent_count; i++) {
		if (hostent_head[i].priority != priority) {
			priority = hostent_head[i].priority;
			if (i-j > 1)
				srv_shuffle(hostent_head+j, i-j);
			j = i;
		}
	}
	if (i-j > 1)
		srv_shuffle(hostent_head+j, i-j);

391
392
    for(i=0; i<hostent_count; i++){
	int buflen;
Howard Chu's avatar
Howard Chu committed
393
        buflen = strlen(hostent_head[i].hostname) + STRLENOF(":65535 ");
394
395
396
397
398
399
400
401
        hostlist = (char *) LDAP_REALLOC(hostlist, cur+buflen+1);
        if (hostlist == NULL) {
            rc = LDAP_NO_MEMORY;
            goto out;
        }
        if(cur>0){
            hostlist[cur++]=' ';
        }
402
        cur += sprintf(&hostlist[cur], "%s:%hu", hostent_head[i].hostname, hostent_head[i].port);
403
    }
404
    }
405

Luke Howard's avatar
 
Luke Howard committed
406
407
408
409
410
411
412
    if (hostlist == NULL) {
	/* No LDAP servers found in DNS. */
	rc = LDAP_UNAVAILABLE;
	goto out;
    }

    rc = LDAP_SUCCESS;
413
	*list = hostlist;
Luke Howard's avatar
 
Luke Howard committed
414
415

  out:
Howard Chu's avatar
Howard Chu committed
416
    LDAP_MUTEX_UNLOCK(&ldap_int_resolv_mutex);
Luke Howard's avatar
 
Luke Howard committed
417
418
419
420

    if (request != NULL) {
	LDAP_FREE(request);
    }
421
422
423
    if (hostent_head != NULL) {
	LDAP_FREE(hostent_head);
    }
424
    if (rc != LDAP_SUCCESS && hostlist != NULL) {
Luke Howard's avatar
 
Luke Howard committed
425
426
427
428
429
	LDAP_FREE(hostlist);
    }
    return rc;
#else
    return LDAP_NOT_SUPPORTED;
430
#endif /* HAVE_RES_QUERY */
Luke Howard's avatar
 
Luke Howard committed
431
}