dnssrv.c 9.05 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-2020 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
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;

	/* Do a shuffle per RFC2782 Page 4 */
220
221
222
223
224
225
226
227
228
229
230
231
	for (p=n; p>1; a++, p--) {
		if (!total) {
			/* all remaining weights are zero,
			   do a straight Fisher-Yates shuffle */
			j = srv_rand() * p;
		} else {
			r = srv_rand() * total;
			for (j=0; j<p; j++) {
				r -= a[j].weight;
				if (r < 0) {
					total -= a[j].weight;
					break;
232
233
234
				}
			}
		}
235
236
237
238
239
		if (j && j<p) {
			srv_record t = a[0];
			a[0] = a[j];
			a[j] = t;
		}
240
241
242
243
	}
}
#endif /* HAVE_RES_QUERY */

Luke Howard's avatar
 
Luke Howard committed
244
/*
245
246
 * Lookup and return LDAP servers for domain (using the DNS
 * SRV record _ldap._tcp.domain).
Luke Howard's avatar
 
Luke Howard committed
247
 */
248
249
250
int ldap_domain2hostlist(
	LDAP_CONST char *domain,
	char **list )
Luke Howard's avatar
 
Luke Howard committed
251
{
252
#ifdef HAVE_RES_QUERY
Luke Howard's avatar
 
Luke Howard committed
253
254
    char *request;
    char *hostlist = NULL;
255
    srv_record *hostent_head=NULL;
256
    int i, j;
Luke Howard's avatar
 
Luke Howard committed
257
    int rc, len, cur = 0;
258
    unsigned char reply[DNSBUFSIZ];
259
    int hostent_count=0;
Luke Howard's avatar
 
Luke Howard committed
260

261
262
263
	assert( domain != NULL );
	assert( list != NULL );
	if( *domain == '\0' ) {
264
265
266
		return LDAP_PARAM_ERROR;
	}

Luke Howard's avatar
 
Luke Howard committed
267
268
    request = LDAP_MALLOC(strlen(domain) + sizeof("_ldap._tcp."));
    if (request == NULL) {
269
		return LDAP_NO_MEMORY;
Luke Howard's avatar
 
Luke Howard committed
270
271
272
    }
    sprintf(request, "_ldap._tcp.%s", domain);

Howard Chu's avatar
Howard Chu committed
273
    LDAP_MUTEX_LOCK(&ldap_int_resolv_mutex);
Luke Howard's avatar
 
Luke Howard committed
274

Kurt Zeilenga's avatar
Kurt Zeilenga committed
275
    rc = LDAP_UNAVAILABLE;
276
277
#ifdef NS_HFIXEDSZ
	/* Bind 8/9 interface */
278
    len = res_query(request, ns_c_in, ns_t_srv, reply, sizeof(reply));
Kurt Zeilenga's avatar
Kurt Zeilenga committed
279
280
281
#	ifndef T_SRV
#		define T_SRV ns_t_srv
#	endif
282
#else
Kurt Zeilenga's avatar
Kurt Zeilenga committed
283
284
285
286
287
	/* Bind 4 interface */
#	ifndef T_SRV
#		define T_SRV 33
#	endif

288
289
    len = res_query(request, C_IN, T_SRV, reply, sizeof(reply));
#endif
Luke Howard's avatar
 
Luke Howard committed
290
291
    if (len >= 0) {
	unsigned char *p;
292
	char host[DNSBUFSIZ];
Luke Howard's avatar
 
Luke Howard committed
293
	int status;
294
	u_short port, priority, weight;
Luke Howard's avatar
 
Luke Howard committed
295
296
297

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

299
300
#ifdef NS_HFIXEDSZ
	/* Bind 8/9 interface */
Kurt Zeilenga's avatar
Kurt Zeilenga committed
301
	p += NS_HFIXEDSZ;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
302
303
#elif defined(HFIXEDSZ)
	/* Bind 4 interface w/ HFIXEDSZ */
304
	p += HFIXEDSZ;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
305
306
307
#else
	/* Bind 4 interface w/o HFIXEDSZ */
	p += sizeof(HEADER);
308
#endif
Kurt Zeilenga's avatar
Kurt Zeilenga committed
309

Luke Howard's avatar
 
Luke Howard committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
	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;
		}
337
338
339
340

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

343
344
345
346
		if ( port == 0 || host[ 0 ] == '\0' ) {
		    goto add_size;
		}

347
348
349
		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
350
351
		    goto out;
		}
352
		hostent_head[hostent_count].priority=priority;
Howard Chu's avatar
Howard Chu committed
353
		hostent_head[hostent_count].weight=weight;
354
		hostent_head[hostent_count].port=port;
Howard Chu's avatar
Howard Chu committed
355
356
		strncpy(hostent_head[hostent_count].hostname, host, MAXHOST-1);
		hostent_head[hostent_count].hostname[MAXHOST-1] = '\0';
357
		hostent_count++;
Luke Howard's avatar
 
Luke Howard committed
358
	    }
359
add_size:;
Luke Howard's avatar
 
Luke Howard committed
360
361
	    p += size;
	}
Howard Chu's avatar
Howard Chu committed
362
	if (!hostent_head) goto out;
363
364
    qsort(hostent_head, hostent_count, sizeof(srv_record), srv_cmp);

365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
	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);

382
383
    for(i=0; i<hostent_count; i++){
	int buflen;
Howard Chu's avatar
Howard Chu committed
384
        buflen = strlen(hostent_head[i].hostname) + STRLENOF(":65535 ");
385
386
387
388
389
390
391
392
        hostlist = (char *) LDAP_REALLOC(hostlist, cur+buflen+1);
        if (hostlist == NULL) {
            rc = LDAP_NO_MEMORY;
            goto out;
        }
        if(cur>0){
            hostlist[cur++]=' ';
        }
393
        cur += sprintf(&hostlist[cur], "%s:%hu", hostent_head[i].hostname, hostent_head[i].port);
394
    }
395
    }
396

Luke Howard's avatar
 
Luke Howard committed
397
398
399
400
401
402
403
    if (hostlist == NULL) {
	/* No LDAP servers found in DNS. */
	rc = LDAP_UNAVAILABLE;
	goto out;
    }

    rc = LDAP_SUCCESS;
404
	*list = hostlist;
Luke Howard's avatar
 
Luke Howard committed
405
406

  out:
Howard Chu's avatar
Howard Chu committed
407
    LDAP_MUTEX_UNLOCK(&ldap_int_resolv_mutex);
Luke Howard's avatar
 
Luke Howard committed
408
409
410
411

    if (request != NULL) {
	LDAP_FREE(request);
    }
412
413
414
    if (hostent_head != NULL) {
	LDAP_FREE(hostent_head);
    }
415
    if (rc != LDAP_SUCCESS && hostlist != NULL) {
Luke Howard's avatar
 
Luke Howard committed
416
417
418
419
420
	LDAP_FREE(hostlist);
    }
    return rc;
#else
    return LDAP_NOT_SUPPORTED;
421
#endif /* HAVE_RES_QUERY */
Luke Howard's avatar
 
Luke Howard committed
422
}