sasl.c 46.2 KB
Newer Older
1
/* $OpenLDAP$ */
Kurt Zeilenga's avatar
Kurt Zeilenga committed
2
3
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
4
 * Copyright 1998-2009 The OpenLDAP Foundation.
Kurt Zeilenga's avatar
Kurt Zeilenga committed
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>.
Kurt Zeilenga's avatar
Kurt Zeilenga committed
14
15
 */

16
17
18
#include "portable.h"

#include <stdio.h>
Kurt Zeilenga's avatar
Kurt Zeilenga committed
19
20
21
22
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif

23
24
#include <ac/stdlib.h>
#include <ac/string.h>
25
26
27
28

#include <lber.h>
#include <ldap_log.h>

Kurt Zeilenga's avatar
Kurt Zeilenga committed
29
30
#include "slap.h"

31
32
33
34
#ifdef ENABLE_REWRITE
#include <rewrite.h>
#endif

35
36
37
38
#ifdef HAVE_CYRUS_SASL
# ifdef HAVE_SASL_SASL_H
#  include <sasl/sasl.h>
#  include <sasl/saslplug.h>
Howard Chu's avatar
Howard Chu committed
39
# else
40
#  include <sasl.h>
Howard Chu's avatar
Howard Chu committed
41
42
#  include <saslplug.h>
# endif
43
44

# define	SASL_CONST const
45

Howard Chu's avatar
Howard Chu committed
46
47
48
#define SASL_VERSION_FULL	((SASL_VERSION_MAJOR << 16) |\
	(SASL_VERSION_MINOR << 8) | SASL_VERSION_STEP)

49
static sasl_security_properties_t sasl_secprops;
50
#elif defined( SLAP_BUILTIN_SASL )
51
52
53
54
55
56
57
58
59
60
/*
 * built-in SASL implementation
 *	only supports EXTERNAL
 */
typedef struct sasl_ctx {
	slap_ssf_t sc_external_ssf;
	struct berval sc_external_id;
} SASL_CTX;

#endif
61

62
#include <lutil.h>
63

64
65
static struct berval ext_bv = BER_BVC( "EXTERNAL" );

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
66
67
char *slap_sasl_auxprops;

68
69
#ifdef HAVE_CYRUS_SASL

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/* Just use our internal auxprop by default */
static int
slap_sasl_getopt(
	void *context,
	const char *plugin_name,
	const char *option,
	const char **result,
	unsigned *len)
{
	if ( strcmp( option, "auxprop_plugin" )) {
		return SASL_FAIL;
	}
	if ( slap_sasl_auxprops )
		*result = slap_sasl_auxprops;
	else
		*result = "slapd";
	return SASL_OK;
}

89
int
Kurt Zeilenga's avatar
Kurt Zeilenga committed
90
slap_sasl_log(
Kurt Zeilenga's avatar
Kurt Zeilenga committed
91
92
93
94
95
96
97
98
99
100
101
102
103
	void *context,
	int priority,
	const char *message) 
{
	Connection *conn = context;
	int level;
	const char * label;

	if ( message == NULL ) {
		return SASL_BADPARAM;
	}

	switch (priority) {
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
	case SASL_LOG_NONE:
		level = LDAP_DEBUG_NONE;
		label = "None";
		break;
	case SASL_LOG_ERR:
		level = LDAP_DEBUG_ANY;
		label = "Error";
		break;
	case SASL_LOG_FAIL:
		level = LDAP_DEBUG_ANY;
		label = "Failure";
		break;
	case SASL_LOG_WARN:
		level = LDAP_DEBUG_TRACE;
		label = "Warning";
		break;
	case SASL_LOG_NOTE:
		level = LDAP_DEBUG_TRACE;
		label = "Notice";
		break;
	case SASL_LOG_DEBUG:
		level = LDAP_DEBUG_TRACE;
		label = "Debug";
		break;
	case SASL_LOG_TRACE:
		level = LDAP_DEBUG_TRACE;
		label = "Trace";
		break;
	case SASL_LOG_PASS:
		level = LDAP_DEBUG_TRACE;
		label = "Password Trace";
		break;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
136
137
138
139
	default:
		return SASL_BADPARAM;
	}

Pierangelo Masarati's avatar
Pierangelo Masarati committed
140
	Debug( level, "SASL [conn=%ld] %s: %s\n",
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
141
		conn ? (long) conn->c_connid: -1L,
Kurt Zeilenga's avatar
Kurt Zeilenga committed
142
		label, message );
143

Kurt Zeilenga's avatar
Kurt Zeilenga committed
144
145
146
147

	return SASL_OK;
}

148
static const char *slap_propnames[] = {
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
149
150
	"*slapConn", "*slapAuthcDNlen", "*slapAuthcDN",
	"*slapAuthzDNlen", "*slapAuthzDN", NULL };
151

Hallvard Furuseth's avatar
Cleanup    
Hallvard Furuseth committed
152
static Filter generic_filter = { LDAP_FILTER_PRESENT, { 0 }, NULL };
153
static struct berval generic_filterstr = BER_BVC("(objectclass=*)");
154

155
#define	SLAP_SASL_PROP_CONN	0
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
156
157
158
159
160
#define	SLAP_SASL_PROP_AUTHCLEN	1
#define	SLAP_SASL_PROP_AUTHC	2
#define	SLAP_SASL_PROP_AUTHZLEN	3
#define	SLAP_SASL_PROP_AUTHZ	4
#define	SLAP_SASL_PROP_COUNT	5	/* Number of properties we used */
161
162
163
164
165
166
167

typedef struct lookup_info {
	int flags;
	const struct propval *list;
	sasl_server_params_t *sparams;
} lookup_info;

168
static slap_response sasl_ap_lookup;
169

170
171
static struct berval sc_cleartext = BER_BVC("{CLEARTEXT}");

172
static int
173
sasl_ap_lookup( Operation *op, SlapReply *rs )
174
175
176
177
178
179
{
	BerVarray bv;
	AttributeDescription *ad;
	Attribute *a;
	const char *text;
	int rc, i;
Pierangelo Masarati's avatar
cleanup    
Pierangelo Masarati committed
180
	lookup_info *sl = (lookup_info *)op->o_callback->sc_private;
181

182
183
	if (rs->sr_type != REP_SEARCH) return 0;

Howard Chu's avatar
Howard Chu committed
184
	for( i = 0; sl->list[i].name; i++ ) {
185
186
187
188
		const char *name = sl->list[i].name;

		if ( name[0] == '*' ) {
			if ( sl->flags & SASL_AUXPROP_AUTHZID ) continue;
Howard Chu's avatar
Howard Chu committed
189
190
			/* Skip our private properties */
			if ( !strcmp( name, slap_propnames[0] )) {
191
				i += SLAP_SASL_PROP_COUNT - 1;
Howard Chu's avatar
Howard Chu committed
192
193
				continue;
			}
194
195
196
197
198
199
200
201
202
203
204
			name++;
		} else if ( !(sl->flags & SASL_AUXPROP_AUTHZID ) )
			continue;

		if ( sl->list[i].values ) {
			if ( !(sl->flags & SASL_AUXPROP_OVERRIDE) ) continue;
		}
		ad = NULL;
		rc = slap_str2ad( name, &ad, &text );
		if ( rc != LDAP_SUCCESS ) {
			Debug( LDAP_DEBUG_TRACE,
205
				"slap_ap_lookup: str2ad(%s): %s\n", name, text, 0 );
206
207
			continue;
		}
208
209
210
211
212
213
214
215

		/* If it's the rootdn and a rootpw was present, we already set
		 * it so don't override it here.
		 */
		if ( ad == slap_schema.si_ad_userPassword && sl->list[i].values && 
			be_isroot_dn( op->o_bd, &op->o_req_ndn ))
			continue;

216
		a = attr_find( rs->sr_entry->e_attrs, ad );
217
		if ( !a ) continue;
218
		if ( ! access_allowed( op, rs->sr_entry, ad, NULL, ACL_AUTH, NULL ) ) {
219
			continue;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
220
221
222
223
224
		}
		if ( sl->list[i].values && ( sl->flags & SASL_AUXPROP_OVERRIDE ) ) {
			sl->sparams->utils->prop_erase( sl->sparams->propctx,
			sl->list[i].name );
		}
225
		for ( bv = a->a_vals; bv->bv_val; bv++ ) {
226
227
			/* ITS#3846 don't give hashed passwords to SASL */
			if ( ad == slap_schema.si_ad_userPassword &&
Pierangelo Masarati's avatar
cleanup    
Pierangelo Masarati committed
228
229
230
				bv->bv_val[0] == '{' /*}*/ )
			{
				if ( lutil_passwd_scheme( bv->bv_val ) ) {
231
232
233
234
235
236
237
238
239
					/* If it's not a recognized scheme, just assume it's
					 * a cleartext password that happened to include brackets.
					 *
					 * If it's a recognized scheme, skip this value, unless the
					 * scheme is {CLEARTEXT}. In that case, skip over the
					 * scheme name and use the remainder. If there is nothing
					 * past the scheme name, skip this value.
					 */
#ifdef SLAPD_CLEARTEXT
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
240
241
					if ( !strncasecmp( bv->bv_val, sc_cleartext.bv_val,
						sc_cleartext.bv_len )) {
242
243
						struct berval cbv;
						cbv.bv_len = bv->bv_len - sc_cleartext.bv_len;
Pierangelo Masarati's avatar
cleanup    
Pierangelo Masarati committed
244
						if ( cbv.bv_len > 0 ) {
245
246
247
248
249
250
251
252
253
							cbv.bv_val = bv->bv_val + sc_cleartext.bv_len;
							sl->sparams->utils->prop_set( sl->sparams->propctx,
								sl->list[i].name, cbv.bv_val, cbv.bv_len );
						}
					}
#endif
					continue;
				}
			}
Kurt Zeilenga's avatar
Kurt Zeilenga committed
254
255
			sl->sparams->utils->prop_set( sl->sparams->propctx,
				sl->list[i].name, bv->bv_val, bv->bv_len );
256
257
258
259
		}
	}
	return LDAP_SUCCESS;
}
260
261
262
263
264
265
266
267
268

static void
slap_auxprop_lookup(
	void *glob_context,
	sasl_server_params_t *sparams,
	unsigned flags,
	const char *user,
	unsigned ulen)
{
269
	Operation op = {0};
Pierangelo Masarati's avatar
Pierangelo Masarati committed
270
	int i, doit = 0;
271
272
273
274
275
276
277
278
	Connection *conn = NULL;
	lookup_info sl;

	sl.list = sparams->utils->prop_get( sparams->propctx );
	sl.sparams = sparams;
	sl.flags = flags;

	/* Find our DN and conn first */
Howard Chu's avatar
Howard Chu committed
279
	for( i = 0; sl.list[i].name; i++ ) {
280
		if ( sl.list[i].name[0] == '*' ) {
281
			if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_CONN] ) ) {
282
283
				if ( sl.list[i].values && sl.list[i].values[0] )
					AC_MEMCPY( &conn, sl.list[i].values[0], sizeof( conn ) );
Howard Chu's avatar
Howard Chu committed
284
				continue;
285
			}
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
286
287
288
289
290
291
292
293
294
295
296
			if ( flags & SASL_AUXPROP_AUTHZID ) {
				if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHZLEN] )) {
					if ( sl.list[i].values && sl.list[i].values[0] )
						AC_MEMCPY( &op.o_req_ndn.bv_len, sl.list[i].values[0],
							sizeof( op.o_req_ndn.bv_len ) );
				} else if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHZ] )) {
					if ( sl.list[i].values )
						op.o_req_ndn.bv_val = (char *)sl.list[i].values[0];
					break;
				}
			}
297

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
298
			if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHCLEN] )) {
299
				if ( sl.list[i].values && sl.list[i].values[0] )
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
300
301
302
303
304
					AC_MEMCPY( &op.o_req_ndn.bv_len, sl.list[i].values[0],
						sizeof( op.o_req_ndn.bv_len ) );
			} else if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHC] ) ) {
				if ( sl.list[i].values ) {
					op.o_req_ndn.bv_val = (char *)sl.list[i].values[0];
305
306
307
					if ( !(flags & SASL_AUXPROP_AUTHZID) )
						break;
				}
308
309
310
311
			}
		}
	}

312
	/* Now see what else needs to be fetched */
Howard Chu's avatar
Howard Chu committed
313
	for( i = 0; sl.list[i].name; i++ ) {
314
		const char *name = sl.list[i].name;
315
316
317

		if ( name[0] == '*' ) {
			if ( flags & SASL_AUXPROP_AUTHZID ) continue;
Howard Chu's avatar
Howard Chu committed
318
319
			/* Skip our private properties */
			if ( !strcmp( name, slap_propnames[0] )) {
320
				i += SLAP_SASL_PROP_COUNT - 1;
Howard Chu's avatar
Howard Chu committed
321
322
				continue;
			}
323
324
325
326
			name++;
		} else if ( !(flags & SASL_AUXPROP_AUTHZID ) )
			continue;

327
		if ( sl.list[i].values ) {
328
329
			if ( !(flags & SASL_AUXPROP_OVERRIDE) ) continue;
		}
330
		doit = 1;
Howard Chu's avatar
Howard Chu committed
331
		break;
332
333
334
	}

	if (doit) {
335
		slap_callback cb = { NULL, sasl_ap_lookup, NULL, NULL };
336
337
338

		cb.sc_private = &sl;

339
		op.o_bd = select_backend( &op.o_req_ndn, 1 );
340

341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
		if ( op.o_bd ) {
			/* For rootdn, see if we can use the rootpw */
			if ( be_isroot_dn( op.o_bd, &op.o_req_ndn ) &&
				!BER_BVISEMPTY( &op.o_bd->be_rootpw )) {
				struct berval cbv = BER_BVNULL;

				/* If there's a recognized scheme, see if it's CLEARTEXT */
				if ( lutil_passwd_scheme( op.o_bd->be_rootpw.bv_val )) {
					if ( !strncasecmp( op.o_bd->be_rootpw.bv_val,
						sc_cleartext.bv_val, sc_cleartext.bv_len )) {

						/* If it's CLEARTEXT, skip past scheme spec */
						cbv.bv_len = op.o_bd->be_rootpw.bv_len -
							sc_cleartext.bv_len;
						if ( cbv.bv_len ) {
							cbv.bv_val = op.o_bd->be_rootpw.bv_val +
								sc_cleartext.bv_len;
						}
					}
				/* No scheme, use the whole value */
				} else {
					cbv = op.o_bd->be_rootpw;
				}
				if ( !BER_BVISEMPTY( &cbv )) {
					for( i = 0; sl.list[i].name; i++ ) {
						const char *name = sl.list[i].name;

						if ( name[0] == '*' ) {
							if ( flags & SASL_AUXPROP_AUTHZID ) continue;
								name++;
						} else if ( !(flags & SASL_AUXPROP_AUTHZID ) )
							continue;

						if ( !strcasecmp(name,"userPassword") ) {
							sl.sparams->utils->prop_set( sl.sparams->propctx,
								sl.list[i].name, cbv.bv_val, cbv.bv_len );
							break;
						}
					}
				}
			}

			if ( op.o_bd->be_search ) {
				SlapReply rs = {REP_RESULT};
				op.o_hdr = conn->c_sasl_bindop->o_hdr;
				op.o_tag = LDAP_REQ_SEARCH;
Pierangelo Masarati's avatar
cleanup    
Pierangelo Masarati committed
387
				op.o_dn = conn->c_ndn;
388
389
				op.o_ndn = conn->c_ndn;
				op.o_callback = &cb;
Howard Chu's avatar
Howard Chu committed
390
				slap_op_time( &op.o_time, &op.o_tincr );
391
392
393
394
395
396
397
398
399
400
401
402
403
404
				op.o_do_not_cache = 1;
				op.o_is_auth_check = 1;
				op.o_req_dn = op.o_req_ndn;
				op.ors_scope = LDAP_SCOPE_BASE;
				op.ors_deref = LDAP_DEREF_NEVER;
				op.ors_tlimit = SLAP_NO_LIMIT;
				op.ors_slimit = 1;
				op.ors_filter = &generic_filter;
				op.ors_filterstr = generic_filterstr;
				/* FIXME: we want all attributes, right? */
				op.ors_attrs = NULL;

				op.o_bd->be_search( &op, &rs );
			}
405
406
407
408
		}
	}
}

Howard Chu's avatar
Howard Chu committed
409
#if SASL_VERSION_FULL >= 0x020110
410
411
412
413
414
415
416
417
418
static int
slap_auxprop_store(
	void *glob_context,
	sasl_server_params_t *sparams,
	struct propctx *prctx,
	const char *user,
	unsigned ulen)
{
	Operation op = {0};
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
419
	Opheader oph;
420
	SlapReply rs = {REP_RESULT};
421
422
	int rc, i;
	unsigned j;
423
424
425
	Connection *conn = NULL;
	const struct propval *pr;
	Modifications *modlist = NULL, **modtail = &modlist, *mod;
426
	slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
427
428
429
430
431
432
433
434
435
436
437
438
439
440
	char textbuf[SLAP_TEXT_BUFLEN];
	const char *text;
	size_t textlen = sizeof(textbuf);

	/* just checking if we are enabled */
	if (!prctx) return SASL_OK;

	if (!sparams || !user) return SASL_BADPARAM;

	pr = sparams->utils->prop_get( sparams->propctx );

	/* Find our DN and conn first */
	for( i = 0; pr[i].name; i++ ) {
		if ( pr[i].name[0] == '*' ) {
441
			if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_CONN] ) ) {
442
443
444
445
				if ( pr[i].values && pr[i].values[0] )
					AC_MEMCPY( &conn, pr[i].values[0], sizeof( conn ) );
				continue;
			}
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
446
447
448
449
450
451
452
			if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_AUTHCLEN] )) {
				if ( pr[i].values && pr[i].values[0] )
					AC_MEMCPY( &op.o_req_ndn.bv_len, pr[i].values[0],
						sizeof( op.o_req_ndn.bv_len ) );
			} else if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_AUTHC] ) ) {
				if ( pr[i].values )
					op.o_req_ndn.bv_val = (char *)pr[i].values[0];
453
454
455
456
457
			}
		}
	}
	if (!conn || !op.o_req_ndn.bv_val) return SASL_BADPARAM;

458
	op.o_bd = select_backend( &op.o_req_ndn, 1 );
459
460
461
462
463
464
465
466
467
468
469
470

	if ( !op.o_bd || !op.o_bd->be_modify ) return SASL_FAIL;
		
	pr = sparams->utils->prop_get( prctx );
	if (!pr) return SASL_BADPARAM;

	for (i=0; pr[i].name; i++);
	if (!i) return SASL_BADPARAM;

	for (i=0; pr[i].name; i++) {
		mod = (Modifications *)ch_malloc( sizeof(Modifications) );
		mod->sml_op = LDAP_MOD_REPLACE;
471
		mod->sml_flags = 0;
472
		ber_str2bv( pr[i].name, 0, 0, &mod->sml_type );
473
		mod->sml_numvals = pr[i].nvalues;
474
475
476
477
478
		mod->sml_values = (struct berval *)ch_malloc( (pr[i].nvalues + 1) *
			sizeof(struct berval));
		for (j=0; j<pr[i].nvalues; j++) {
			ber_str2bv( pr[i].values[j], 0, 1, &mod->sml_values[j]);
		}
Pierangelo Masarati's avatar
Pierangelo Masarati committed
479
		BER_BVZERO( &mod->sml_values[j] );
480
481
482
483
484
485
486
		mod->sml_nvalues = NULL;
		mod->sml_desc = NULL;
		*modtail = mod;
		modtail = &mod->sml_next;
	}
	*modtail = NULL;

487
	rc = slap_mods_check( &op, modlist, &text, textbuf, textlen, NULL );
488
489

	if ( rc == LDAP_SUCCESS ) {
490
491
		rc = slap_mods_no_user_mod_check( &op, modlist,
			&text, textbuf, textlen );
492
493

		if ( rc == LDAP_SUCCESS ) {
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
494
495
496
497
498
499
500
			if ( conn->c_sasl_bindop ) {
				op.o_hdr = conn->c_sasl_bindop->o_hdr;
			} else {
				op.o_hdr = &oph;
				memset( &oph, 0, sizeof(oph) );
				operation_fake_init( conn, &op, ldap_pvt_thread_pool_context(), 0 );
			}
Howard Chu's avatar
Howard Chu committed
501
502
503
504
505
506
507
508
509
510
			op.o_tag = LDAP_REQ_MODIFY;
			op.o_ndn = op.o_req_ndn;
			op.o_callback = &cb;
			slap_op_time( &op.o_time, &op.o_tincr );
			op.o_do_not_cache = 1;
			op.o_is_auth_check = 1;
			op.o_req_dn = op.o_req_ndn;
			op.orm_modlist = modlist;

			rc = op.o_bd->be_modify( &op, &rs );
511
		}
512
	}
513
	slap_mods_free( modlist, 1 );
514
	return rc != LDAP_SUCCESS ? SASL_FAIL : SASL_OK;
515
}
Howard Chu's avatar
Howard Chu committed
516
#endif /* SASL_VERSION_FULL >= 2.1.16 */
517

518
519
520
521
522
523
524
static sasl_auxprop_plug_t slap_auxprop_plugin = {
	0,	/* Features */
	0,	/* spare */
	NULL,	/* glob_context */
	NULL,	/* auxprop_free */
	slap_auxprop_lookup,
	"slapd",	/* name */
Howard Chu's avatar
Howard Chu committed
525
#if SASL_VERSION_FULL >= 0x020110
526
527
	slap_auxprop_store	/* the declaration of this member changed
				 * in cyrus SASL from 2.1.15 to 2.1.16 */
Howard Chu's avatar
Howard Chu committed
528
529
530
#else
	NULL
#endif
531
532
533
534
535
536
537
538
539
540
};

static int
slap_auxprop_init(
	const sasl_utils_t *utils,
	int max_version,
	int *out_version,
	sasl_auxprop_plug_t **plug,
	const char *plugname)
{
541
	if ( !out_version || !plug ) return SASL_BADPARAM;
542
543
544
545
546
547
548
549
550
551
552
553

	if ( max_version < SASL_AUXPROP_PLUG_VERSION ) return SASL_BADVERS;

	*out_version = SASL_AUXPROP_PLUG_VERSION;
	*plug = &slap_auxprop_plugin;
	return SASL_OK;
}

/* Convert a SASL authcid or authzid into a DN. Store the DN in an
 * auxiliary property, so that we can refer to it in sasl_authorize
 * without interfering with anything else. Also, the SASL username
 * buffer is constrained to 256 characters, and our DNs could be
554
 * much longer (SLAP_LDAPDN_MAXLEN, currently set to 8192)
555
 */
556
557
558
559
560
561
562
563
564
565
566
567
568
static int
slap_sasl_canonicalize(
	sasl_conn_t *sconn,
	void *context,
	const char *in,
	unsigned inlen,
	unsigned flags,
	const char *user_realm,
	char *out,
	unsigned out_max,
	unsigned *out_len)
{
	Connection *conn = (Connection *)context;
569
	struct propctx *props = sasl_auxprop_getctx( sconn );
Pierangelo Masarati's avatar
cleanup    
Pierangelo Masarati committed
570
	struct propval auxvals[ SLAP_SASL_PROP_COUNT ] = { { 0 } };
571
	struct berval dn;
572
	int rc, which;
573
	const char *names[2];
Pierangelo Masarati's avatar
Pierangelo Masarati committed
574
	struct berval	bvin;
575
576
577

	*out_len = 0;

578
	Debug( LDAP_DEBUG_ARGS, "SASL Canonicalize [conn=%ld]: %s=\"%s\"\n",
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
579
		conn ? (long) conn->c_connid : -1L,
580
581
		(flags & SASL_CU_AUTHID) ? "authcid" : "authzid",
		in ? in : "<empty>");
582

583
584
585
	/* If name is too big, just truncate. We don't care, we're
	 * using DNs, not the usernames.
	 */
586
	if ( inlen > out_max )
587
588
		inlen = out_max-1;

589
590
591
592
593
594
595
596
	/* This is a Simple Bind using SPASSWD. That means the in-directory
	 * userPassword of the Binding user already points at SASL, so it
	 * cannot be used to actually satisfy a password comparison. Just
	 * ignore it, some other mech will process it.
	 */
	if ( !conn->c_sasl_bindop ||
		conn->c_sasl_bindop->orb_method != LDAP_AUTH_SASL ) goto done;

597
598
599
600
601
602
	/* See if we need to add request, can only do it once */
	prop_getnames( props, slap_propnames, auxvals );
	if ( !auxvals[0].name )
		prop_request( props, slap_propnames );

	if ( flags & SASL_CU_AUTHID )
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
603
		which = SLAP_SASL_PROP_AUTHCLEN;
604
	else
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
605
		which = SLAP_SASL_PROP_AUTHZLEN;
606

607
	/* Need to store the Connection for auxprop_lookup */
608
609
	if ( !auxvals[SLAP_SASL_PROP_CONN].values ) {
		names[0] = slap_propnames[SLAP_SASL_PROP_CONN];
610
611
612
613
		names[1] = NULL;
		prop_set( props, names[0], (char *)&conn, sizeof( conn ) );
	}
		
614
615
616
	/* Already been here? */
	if ( auxvals[which].values )
		goto done;
617

618
619
620
621
622
623
624
625
626
627
	/* Normally we require an authzID to have a u: or dn: prefix.
	 * However, SASL frequently gives us an authzID that is just
	 * an exact copy of the authcID, without a prefix. We need to
	 * detect and allow this condition. If SASL calls canonicalize
	 * with SASL_CU_AUTHID|SASL_CU_AUTHZID this is a no-brainer.
	 * But if it's broken into two calls, we need to remember the
	 * authcID so that we can compare the authzID later. We store
	 * the authcID temporarily in conn->c_sasl_dn. We necessarily
	 * finish Canonicalizing before Authorizing, so there is no
	 * conflict with slap_sasl_authorize's use of this temp var.
Howard Chu's avatar
Howard Chu committed
628
629
630
631
	 *
	 * The SASL EXTERNAL mech is backwards from all the other mechs,
	 * it does authzID before the authcID. If we see that authzID
	 * has already been done, don't do anything special with authcID.
632
	 */
633
	if ( flags == SASL_CU_AUTHID && !auxvals[SLAP_SASL_PROP_AUTHZ].values ) {
Kurt Zeilenga's avatar
Kurt Zeilenga committed
634
		conn->c_sasl_dn.bv_val = (char *) in;
635
		conn->c_sasl_dn.bv_len = 0;
636
637
638
639
640
	} else if ( flags == SASL_CU_AUTHZID && conn->c_sasl_dn.bv_val ) {
		rc = strcmp( in, conn->c_sasl_dn.bv_val );
		conn->c_sasl_dn.bv_val = NULL;
		/* They were equal, no work needed */
		if ( !rc ) goto done;
641
642
	}

Pierangelo Masarati's avatar
Pierangelo Masarati committed
643
644
645
	bvin.bv_val = (char *)in;
	bvin.bv_len = inlen;
	rc = slap_sasl_getdn( conn, NULL, &bvin, (char *)user_realm, &dn,
646
		(flags & SASL_CU_AUTHID) ? SLAP_GETDN_AUTHCID : SLAP_GETDN_AUTHZID );
647
648
649
	if ( rc != LDAP_SUCCESS ) {
		sasl_seterror( sconn, 0, ldap_err2string( rc ) );
		return SASL_NOAUTHZ;
650
	}
651

652
	names[0] = slap_propnames[which];
653
	names[1] = NULL;
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
654
	prop_set( props, names[0], (char *)&dn.bv_len, sizeof( dn.bv_len ) );
655

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
656
657
658
	which++;
	names[0] = slap_propnames[which];
	prop_set( props, names[0], dn.bv_val, dn.bv_len );
659

660
	Debug( LDAP_DEBUG_ARGS, "SASL Canonicalize [conn=%ld]: %s=\"%s\"\n",
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
661
		conn ? (long) conn->c_connid : -1L, names[0]+1,
662
663
		dn.bv_val ? dn.bv_val : "<EMPTY>" );

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
664
665
666
667
	/* Not needed any more, SASL has copied it */
	if ( conn && conn->c_sasl_bindop )
		conn->c_sasl_bindop->o_tmpfree( dn.bv_val, conn->c_sasl_bindop->o_tmpmemctx );

668
669
done:
	AC_MEMCPY( out, in, inlen );
670
671
672
	out[inlen] = '\0';

	*out_len = inlen;
673
674
675

	return SASL_OK;
}
676

677
678
679
680
static int
slap_sasl_authorize(
	sasl_conn_t *sconn,
	void *context,
681
	char *requested_user,
682
	unsigned rlen,
683
	char *auth_identity,
684
685
686
	unsigned alen,
	const char *def_realm,
	unsigned urlen,
687
	struct propctx *props)
688
689
{
	Connection *conn = (Connection *)context;
690
691
692
693
	/* actually:
	 *	(SLAP_SASL_PROP_COUNT - 1)	because we skip "conn",
	 *	+ 1				for NULL termination?
	 */
Pierangelo Masarati's avatar
cleanup    
Pierangelo Masarati committed
694
	struct propval auxvals[ SLAP_SASL_PROP_COUNT ] = { { 0 } };
695
	struct berval authcDN, authzDN = BER_BVNULL;
696
	int rc;
697

698
699
700
701
	/* Simple Binds don't support proxy authorization, ignore it */
	if ( !conn->c_sasl_bindop ||
		conn->c_sasl_bindop->orb_method != LDAP_AUTH_SASL ) return SASL_OK;

702
	Debug( LDAP_DEBUG_ARGS, "SASL proxy authorize [conn=%ld]: "
703
		"authcid=\"%s\" authzid=\"%s\"\n",
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
704
		conn ? (long) conn->c_connid : -1L, auth_identity, requested_user );
705
	if ( conn->c_sasl_dn.bv_val ) {
Pierangelo Masarati's avatar
Pierangelo Masarati committed
706
		BER_BVZERO( &conn->c_sasl_dn );
707
	}
708

709
	/* Skip SLAP_SASL_PROP_CONN */
710
	prop_getnames( props, slap_propnames+1, auxvals );
711
	
Howard Chu's avatar
Howard Chu committed
712
713
714
715
716
717
	/* Should not happen */
	if ( !auxvals[0].values ) {
		sasl_seterror( sconn, 0, "invalid authcid" );
		return SASL_NOAUTHZ;
	}

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
718
719
	AC_MEMCPY( &authcDN.bv_len, auxvals[0].values[0], sizeof(authcDN.bv_len) );
	authcDN.bv_val = auxvals[1].values ? (char *)auxvals[1].values[0] : NULL;
720
	conn->c_sasl_dn = authcDN;
721

722
	/* Nothing to do if no authzID was given */
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
723
	if ( !auxvals[2].name || !auxvals[2].values ) {
Howard Chu's avatar
Howard Chu committed
724
		goto ok;
725
	}
726
	
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
727
728
	AC_MEMCPY( &authzDN.bv_len, auxvals[2].values[0], sizeof(authzDN.bv_len) );
	authzDN.bv_val = auxvals[3].values ? (char *)auxvals[3].values[0] : NULL;
729

730
	rc = slap_sasl_authorized( conn->c_sasl_bindop, &authcDN, &authzDN );
731
	if ( rc != LDAP_SUCCESS ) {
732
733
		Debug( LDAP_DEBUG_TRACE, "SASL Proxy Authorize [conn=%ld]: "
			"proxy authorization disallowed (%d)\n",
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
734
			conn ? (long) conn->c_connid : -1L, rc, 0 );
735
736
737
738
739

		sasl_seterror( sconn, 0, "not authorized" );
		return SASL_NOAUTHZ;
	}

740
741
	/* FIXME: we need yet another dup because slap_sasl_getdn()
	 * is using the bind operation slab */
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
742
	ber_dupbv( &conn->c_sasl_authz_dn, &authzDN );
743

Howard Chu's avatar
Howard Chu committed
744
ok:
Howard Chu's avatar
Howard Chu committed
745
746
	if (conn->c_sasl_bindop) {
		Statslog( LDAP_DEBUG_STATS,
747
748
749
			"%s BIND authcid=\"%s\" authzid=\"%s\"\n",
			conn->c_sasl_bindop->o_log_prefix, 
			auth_identity, requested_user, 0, 0 );
Howard Chu's avatar
Howard Chu committed
750
	}
751

752
	Debug( LDAP_DEBUG_TRACE, "SASL Authorize [conn=%ld]: "
753
		" proxy authorization allowed authzDN=\"%s\"\n",
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
754
		conn ? (long) conn->c_connid : -1L, 
755
		authzDN.bv_val ? authzDN.bv_val : "", 0 );
756
757
	return SASL_OK;
} 
Kurt Zeilenga's avatar
Kurt Zeilenga committed
758

759
760
761
762
763
static int
slap_sasl_err2ldap( int saslerr )
{
	int rc;

Kurt Zeilenga's avatar
Kurt Zeilenga committed
764
765
766
767
768
769
770
771
772
773
774
775
	/* map SASL errors to LDAP resultCode returned by:
	 *	sasl_server_new()
	 *		SASL_OK, SASL_NOMEM
	 *	sasl_server_step()
	 *		SASL_OK, SASL_CONTINUE, SASL_TRANS, SASL_BADPARAM, SASL_BADPROT,
	 *      ...
	 *	sasl_server_start()
	 *      + SASL_NOMECH
	 *	sasl_setprop()
	 *		SASL_OK, SASL_BADPARAM
	 */

776
	switch (saslerr) {
777
778
779
		case SASL_OK:
			rc = LDAP_SUCCESS;
			break;
780
781
782
783
784
785
786
787
788
789
790
		case SASL_CONTINUE:
			rc = LDAP_SASL_BIND_IN_PROGRESS;
			break;
		case SASL_FAIL:
		case SASL_NOMEM:
			rc = LDAP_OTHER;
			break;
		case SASL_NOMECH:
			rc = LDAP_AUTH_METHOD_NOT_SUPPORTED;
			break;
		case SASL_BADAUTH:
Kurt Zeilenga's avatar
Kurt Zeilenga committed
791
792
793
		case SASL_NOUSER:
		case SASL_TRANS:
		case SASL_EXPIRED:
794
795
796
797
798
799
800
801
802
			rc = LDAP_INVALID_CREDENTIALS;
			break;
		case SASL_NOAUTHZ:
			rc = LDAP_INSUFFICIENT_ACCESS;
			break;
		case SASL_TOOWEAK:
		case SASL_ENCRYPT:
			rc = LDAP_INAPPROPRIATE_AUTH;
			break;
Kurt Zeilenga's avatar
Kurt Zeilenga committed
803
804
805
806
807
808
809
		case SASL_UNAVAIL:
		case SASL_TRYAGAIN:
			rc = LDAP_UNAVAILABLE;
			break;
		case SASL_DISABLED:
			rc = LDAP_UNWILLING_TO_PERFORM;
			break;
810
811
812
813
814
815
816
		default:
			rc = LDAP_OTHER;
			break;
	}

	return rc;
}
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854

#ifdef SLAPD_SPASSWD

static struct berval sasl_pwscheme = BER_BVC("{SASL}");

static int chk_sasl(
	const struct berval *sc,
	const struct berval * passwd,
	const struct berval * cred,
	const char **text )
{
	unsigned int i;
	int rtn;
	void *ctx, *sconn = NULL;

	for( i=0; i<cred->bv_len; i++) {
		if(cred->bv_val[i] == '\0') {
			return LUTIL_PASSWD_ERR;	/* NUL character in password */
		}
	}

	if( cred->bv_val[i] != '\0' ) {
		return LUTIL_PASSWD_ERR;	/* cred must behave like a string */
	}

	for( i=0; i<passwd->bv_len; i++) {
		if(passwd->bv_val[i] == '\0') {
			return LUTIL_PASSWD_ERR;	/* NUL character in password */
		}
	}

	if( passwd->bv_val[i] != '\0' ) {
		return LUTIL_PASSWD_ERR;	/* passwd must behave like a string */
	}

	rtn = LUTIL_PASSWD_ERR;

	ctx = ldap_pvt_thread_pool_context();
855
	ldap_pvt_thread_pool_getkey( ctx, (void *)slap_sasl_bind, &sconn, NULL );
856
857
858
859
860
861
862
863
864
865
866
867
868
869

	if( sconn != NULL ) {
		int sc;
		sc = sasl_checkpass( sconn,
			passwd->bv_val, passwd->bv_len,
			cred->bv_val, cred->bv_len );
		rtn = ( sc != SASL_OK ) ? LUTIL_PASSWD_ERR : LUTIL_PASSWD_OK;
	}

	return rtn;
}
#endif /* SLAPD_SPASSWD */

#endif /* HAVE_CYRUS_SASL */
870

871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#ifdef ENABLE_REWRITE

typedef struct slapd_map_data {
	struct berval base;
	struct berval filter;
	AttributeName attrs[2];
	int scope;
} slapd_map_data;

static void *
slapd_rw_config( const char *fname, int lineno, int argc, char **argv )
{
	slapd_map_data *ret = NULL;
	LDAPURLDesc *lud = NULL;
	char *uri;
	AttributeDescription *ad = NULL;
	int rc, flen = 0;
	struct berval dn, ndn;

	if ( argc != 1 ) {
		Debug( LDAP_DEBUG_ANY,
			"[%s:%d] slapd map needs URI\n",
			fname, lineno, 0 );
        return NULL;
	}

	uri = argv[0];
	if ( strncasecmp( uri, "uri=", STRLENOF( "uri=" ) ) == 0 ) {
		uri += STRLENOF( "uri=" );
	}

	if ( ldap_url_parse( uri, &lud ) != LDAP_URL_SUCCESS ) {
		Debug( LDAP_DEBUG_ANY,
			"[%s:%d] illegal URI '%s'\n",
			fname, lineno, uri );
        return NULL;
	}

	if ( strcasecmp( lud->lud_scheme, "ldap" )) {
		Debug( LDAP_DEBUG_ANY,
			"[%s:%d] illegal URI scheme '%s'\n",
			fname, lineno, lud->lud_scheme );
		goto done;
	}

	if (( lud->lud_host && lud->lud_host[0] ) || lud->lud_exts
		|| !lud->lud_dn ) {
		Debug( LDAP_DEBUG_ANY,
			"[%s:%d] illegal URI '%s'\n",
			fname, lineno, uri );
		goto done;
	}

	if ( lud->lud_attrs ) {
		if ( lud->lud_attrs[1] ) {
			Debug( LDAP_DEBUG_ANY,
				"[%s:%d] only one attribute allowed in URI\n",
				fname, lineno, 0 );
			goto done;
		}
		if ( strcasecmp( lud->lud_attrs[0], "dn" ) &&
			strcasecmp( lud->lud_attrs[0], "entryDN" )) {
			const char *text;
			rc = slap_str2ad( lud->lud_attrs[0], &ad, &text );
			if ( rc )
				goto done;
		}
	}
	ber_str2bv( lud->lud_dn, 0, 0, &dn );
	if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ))
		goto done;

	if ( lud->lud_filter ) {
		flen = strlen( lud->lud_filter ) + 1;
	}
	ret = ch_malloc( sizeof( slapd_map_data ) + flen );
	ret->base = ndn;
	if ( flen ) {
		ret->filter.bv_val = (char *)(ret+1);
		ret->filter.bv_len = flen - 1;
		strcpy( ret->filter.bv_val, lud->lud_filter );
	} else {
		BER_BVZERO( &ret->filter );
	}
	ret->scope = lud->lud_scope;
	if ( ad ) {
		ret->attrs[0].an_name = ad->ad_cname;
	} else {
		BER_BVZERO( &ret->attrs[0].an_name );
	}
	ret->attrs[0].an_desc = ad;
	BER_BVZERO( &ret->attrs[1].an_name );
done:
	ldap_free_urldesc( lud );
	return ret;
}

struct slapd_rw_info {
	slapd_map_data *si_data;
	struct berval si_val;
};

static int
slapd_rw_cb( Operation *op, SlapReply *rs )
{
	if ( rs->sr_type == REP_SEARCH ) {
		struct slapd_rw_info *si = op->o_callback->sc_private;

		if ( si->si_data->attrs[0].an_desc ) {
			Attribute *a;

			a = attr_find( rs->sr_entry->e_attrs,
				si->si_data->attrs[0].an_desc );
			if ( a ) {
				ber_dupbv( &si->si_val, a->a_vals );
			}
		} else {
			ber_dupbv( &si->si_val, &rs->sr_entry->e_name );
		}
	}
	return LDAP_SUCCESS;
}

static int
slapd_rw_apply( void *private, const char *filter, struct berval *val )
{
	slapd_map_data *sl = private;
	slap_callback cb = { NULL };
	Connection conn = {0};
	OperationBuffer opbuf;