1/*	$NetBSD: saslauthz.c,v 1.3 2021/08/14 16:14:58 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 * Portions Copyright 2000 Mark Adamson, Carnegie Mellon.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted only as authorized by the OpenLDAP
12 * Public License.
13 *
14 * A copy of this license is available in the file LICENSE in the
15 * top-level directory of the distribution or, alternatively, at
16 * <http://www.OpenLDAP.org/license.html>.
17 */
18
19#include <sys/cdefs.h>
20__RCSID("$NetBSD: saslauthz.c,v 1.3 2021/08/14 16:14:58 christos Exp $");
21
22#include "portable.h"
23
24#include <stdio.h>
25#ifdef HAVE_LIMITS_H
26#include <limits.h>
27#endif
28
29#include <ac/stdlib.h>
30#include <ac/string.h>
31#include <ac/ctype.h>
32
33#include "slap.h"
34
35#include "lutil.h"
36#include "slap-config.h"
37
38#define SASLREGEX_REPLACE 10
39
40#define LDAP_X_SCOPE_EXACT	((ber_int_t) 0x0010)
41#define LDAP_X_SCOPE_REGEX	((ber_int_t) 0x0020)
42#define LDAP_X_SCOPE_CHILDREN	((ber_int_t) 0x0030)
43#define LDAP_X_SCOPE_SUBTREE	((ber_int_t) 0x0040)
44#define LDAP_X_SCOPE_ONELEVEL	((ber_int_t) 0x0050)
45#define LDAP_X_SCOPE_GROUP	((ber_int_t) 0x0060)
46#define LDAP_X_SCOPE_USERS	((ber_int_t) 0x0070)
47
48/*
49 * IDs in DNauthzid form can now have a type specifier, that
50 * influences how they are used in related operations.
51 *
52 * syntax: dn[.{exact|regex}]:<val>
53 *
54 * dn.exact:	the value must pass normalization and is used
55 *		in exact DN match.
56 * dn.regex:	the value is treated as a regular expression
57 *		in matching DN values in authz{To|From}
58 *		attributes.
59 * dn:		for backwards compatibility reasons, the value
60 *		is treated as a regular expression, and thus
61 *		it is not normalized nor validated; it is used
62 *		in exact or regex comparisons based on the
63 *		context.
64 *
65 * IDs in DNauthzid form can now have a type specifier, that
66 * influences how they are used in related operations.
67 *
68 * syntax: u[.mech[/realm]]:<val>
69 *
70 * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name
71 * and realm is mechanism specific realm (separate to those
72 * which are representable as part of the principal).
73 */
74
75typedef struct sasl_regexp {
76  char *sr_match;						/* regexp match pattern */
77  char *sr_replace;					/* regexp replace pattern */
78} SaslRegexp_t;
79
80static int nSaslRegexp = 0;
81static SaslRegexp_t *SaslRegexp = NULL;
82
83#include "rewrite.h"
84struct rewrite_info	*sasl_rwinfo = NULL;
85#define AUTHID_CONTEXT	"authid"
86static BerVarray	authz_rewrites = NULL;
87
88/* What SASL proxy authorization policies are allowed? */
89#define	SASL_AUTHZ_NONE	0x00
90#define	SASL_AUTHZ_FROM	0x01
91#define	SASL_AUTHZ_TO	0x02
92#define SASL_AUTHZ_AND	0x10
93
94static const char *policy_txt[] = {
95	"none", "from", "to", "any"
96};
97
98static int authz_policy = SASL_AUTHZ_NONE;
99
100static int
101slap_sasl_match( Operation *opx, struct berval *rule,
102	struct berval *assertDN, struct berval *authc );
103
104int slap_sasl_setpolicy( const char *arg )
105{
106	int rc = LDAP_SUCCESS;
107
108	if ( strcasecmp( arg, "none" ) == 0 ) {
109		authz_policy = SASL_AUTHZ_NONE;
110	} else if ( strcasecmp( arg, "from" ) == 0 ) {
111		authz_policy = SASL_AUTHZ_FROM;
112	} else if ( strcasecmp( arg, "to" ) == 0 ) {
113		authz_policy = SASL_AUTHZ_TO;
114	} else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
115		authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
116	} else if ( strcasecmp( arg, "all" ) == 0 ) {
117		authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
118	} else {
119		rc = LDAP_OTHER;
120	}
121	return rc;
122}
123
124const char * slap_sasl_getpolicy()
125{
126	if ( authz_policy == (SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND) )
127		return "all";
128	else
129		return policy_txt[authz_policy];
130}
131
132int slap_parse_user( struct berval *id, struct berval *user,
133		struct berval *realm, struct berval *mech )
134{
135	char	u;
136
137	assert( id != NULL );
138	assert( !BER_BVISNULL( id ) );
139	assert( user != NULL );
140	assert( realm != NULL );
141	assert( mech != NULL );
142
143	u = id->bv_val[ 0 ];
144
145	if ( u != 'u' && u != 'U' ) {
146		/* called with something other than u: */
147		return LDAP_PROTOCOL_ERROR;
148	}
149
150	/* uauthzid form:
151	 *		u[.mech[/realm]]:user
152	 */
153
154	user->bv_val = ber_bvchr( id, ':' );
155	if ( BER_BVISNULL( user ) ) {
156		return LDAP_PROTOCOL_ERROR;
157	}
158	user->bv_val[ 0 ] = '\0';
159	user->bv_val++;
160	user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
161
162	if ( id->bv_val[1] == '.' ) {
163		id->bv_val[1] = '\0';
164		mech->bv_val = id->bv_val + 2;
165		mech->bv_len = user->bv_val - mech->bv_val - 1;
166
167		realm->bv_val = ber_bvchr( mech, '/' );
168
169		if ( !BER_BVISNULL( realm ) ) {
170			realm->bv_val[ 0 ] = '\0';
171			realm->bv_val++;
172			mech->bv_len = realm->bv_val - mech->bv_val - 1;
173			realm->bv_len = user->bv_val - realm->bv_val - 1;
174		}
175
176	} else {
177		BER_BVZERO( mech );
178		BER_BVZERO( realm );
179	}
180
181	if ( id->bv_val[ 1 ] != '\0' ) {
182		return LDAP_PROTOCOL_ERROR;
183	}
184
185	if ( !BER_BVISNULL( mech ) ) {
186		if ( mech->bv_val != id->bv_val + 2 )
187			return LDAP_PROTOCOL_ERROR;
188
189		AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
190		mech->bv_val -= 2;
191	}
192
193	if ( !BER_BVISNULL( realm ) ) {
194		if ( realm->bv_val < id->bv_val + 2 )
195			return LDAP_PROTOCOL_ERROR;
196
197		AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
198		realm->bv_val -= 2;
199	}
200
201	/* leave "u:" before user */
202	user->bv_val -= 2;
203	user->bv_len += 2;
204	user->bv_val[ 0 ] = u;
205	user->bv_val[ 1 ] = ':';
206
207	return LDAP_SUCCESS;
208}
209
210int
211authzValidate(
212	Syntax *syntax,
213	struct berval *in )
214{
215	struct berval	bv;
216	int		rc = LDAP_INVALID_SYNTAX;
217	LDAPURLDesc	*ludp = NULL;
218	int		scope = -1;
219
220	/*
221	 * 1) <DN>
222	 * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
223	 * 3) dn.regex:<pattern>
224	 * 4) u[.mech[/realm]]:<ID>
225	 * 5) group[/<groupClass>[/<memberAttr>]]:<DN>
226	 * 6) <URL>
227	 */
228
229	assert( in != NULL );
230	assert( !BER_BVISNULL( in ) );
231
232	Debug( LDAP_DEBUG_TRACE,
233		"authzValidate: parsing %s\n", in->bv_val );
234
235	/*
236	 * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
237	 * 3) dn.regex:<pattern>
238	 *
239	 * <DN> must pass DN normalization
240	 */
241	if ( !strncasecmp( in->bv_val, "dn", STRLENOF( "dn" ) ) ) {
242		bv.bv_val = in->bv_val + STRLENOF( "dn" );
243
244		if ( bv.bv_val[ 0 ] == '.' ) {
245			bv.bv_val++;
246
247			if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
248				bv.bv_val += STRLENOF( "exact:" );
249				scope = LDAP_X_SCOPE_EXACT;
250
251			} else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
252				bv.bv_val += STRLENOF( "regex:" );
253				scope = LDAP_X_SCOPE_REGEX;
254
255			} else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
256				bv.bv_val += STRLENOF( "children:" );
257				scope = LDAP_X_SCOPE_CHILDREN;
258
259			} else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
260				bv.bv_val += STRLENOF( "subtree:" );
261				scope = LDAP_X_SCOPE_SUBTREE;
262
263			} else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
264				bv.bv_val += STRLENOF( "onelevel:" );
265				scope = LDAP_X_SCOPE_ONELEVEL;
266
267			} else {
268				return LDAP_INVALID_SYNTAX;
269			}
270
271		} else {
272			if ( bv.bv_val[ 0 ] != ':' ) {
273				return LDAP_INVALID_SYNTAX;
274			}
275			scope = LDAP_X_SCOPE_EXACT;
276			bv.bv_val++;
277		}
278
279		bv.bv_val += strspn( bv.bv_val, " " );
280		/* jump here in case no type specification was present
281		 * and uri was not an URI... HEADS-UP: assuming EXACT */
282is_dn:		bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val );
283
284		/* a single '*' means any DN without using regexes */
285		if ( ber_bvccmp( &bv, '*' ) ) {
286			/* LDAP_X_SCOPE_USERS */
287			return LDAP_SUCCESS;
288		}
289
290		switch ( scope ) {
291		case LDAP_X_SCOPE_EXACT:
292		case LDAP_X_SCOPE_CHILDREN:
293		case LDAP_X_SCOPE_SUBTREE:
294		case LDAP_X_SCOPE_ONELEVEL:
295			return dnValidate( NULL, &bv );
296
297		case LDAP_X_SCOPE_REGEX:
298			return LDAP_SUCCESS;
299		}
300
301		return rc;
302
303	/*
304	 * 4) u[.mech[/realm]]:<ID>
305	 */
306	} else if ( ( in->bv_val[ 0 ] == 'u' || in->bv_val[ 0 ] == 'U' )
307			&& ( in->bv_val[ 1 ] == ':'
308				|| in->bv_val[ 1 ] == '/'
309				|| in->bv_val[ 1 ] == '.' ) )
310	{
311		char		buf[ SLAP_LDAPDN_MAXLEN ];
312		struct berval	id,
313				user = BER_BVNULL,
314				realm = BER_BVNULL,
315				mech = BER_BVNULL;
316
317		if ( sizeof( buf ) <= in->bv_len ) {
318			return LDAP_INVALID_SYNTAX;
319		}
320
321		id.bv_len = in->bv_len;
322		id.bv_val = buf;
323		strncpy( buf, in->bv_val, sizeof( buf ) );
324
325		rc = slap_parse_user( &id, &user, &realm, &mech );
326		if ( rc != LDAP_SUCCESS ) {
327			return LDAP_INVALID_SYNTAX;
328		}
329
330		return rc;
331
332	/*
333	 * 5) group[/groupClass[/memberAttr]]:<DN>
334	 *
335	 * <groupClass> defaults to "groupOfNames"
336	 * <memberAttr> defaults to "member"
337	 *
338	 * <DN> must pass DN normalization
339	 */
340	} else if ( strncasecmp( in->bv_val, "group", STRLENOF( "group" ) ) == 0 )
341	{
342		struct berval	group_dn = BER_BVNULL,
343				group_oc = BER_BVNULL,
344				member_at = BER_BVNULL;
345
346		bv.bv_val = in->bv_val + STRLENOF( "group" );
347		bv.bv_len = in->bv_len - STRLENOF( "group" );
348		group_dn.bv_val = ber_bvchr( &bv, ':' );
349		if ( group_dn.bv_val == NULL ) {
350			/* last chance: assume it's a(n exact) DN ... */
351			bv.bv_val = in->bv_val;
352			scope = LDAP_X_SCOPE_EXACT;
353			goto is_dn;
354		}
355
356		/*
357		 * FIXME: we assume that "member" and "groupOfNames"
358		 * are present in schema...
359		 */
360		if ( bv.bv_val[ 0 ] == '/' ) {
361			group_oc.bv_val = &bv.bv_val[ 1 ];
362			group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
363
364			member_at.bv_val = ber_bvchr( &group_oc, '/' );
365			if ( member_at.bv_val ) {
366				AttributeDescription	*ad = NULL;
367				const char		*text = NULL;
368
369				group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
370				member_at.bv_val++;
371				member_at.bv_len = group_dn.bv_val - member_at.bv_val;
372				rc = slap_bv2ad( &member_at, &ad, &text );
373				if ( rc != LDAP_SUCCESS ) {
374					return rc;
375				}
376			}
377
378			if ( oc_bvfind( &group_oc ) == NULL ) {
379				return LDAP_INVALID_SYNTAX;
380			}
381		}
382
383		group_dn.bv_val++;
384		group_dn.bv_len = in->bv_len - ( group_dn.bv_val - in->bv_val );
385
386		rc = dnValidate( NULL, &group_dn );
387		if ( rc != LDAP_SUCCESS ) {
388			return rc;
389		}
390
391		return rc;
392	}
393
394	/*
395	 * ldap:///<base>??<scope>?<filter>
396	 * <scope> ::= {base|one|subtree}
397	 *
398	 * <scope> defaults to "base"
399	 * <base> must pass DN normalization
400	 * <filter> must pass str2filter()
401	 */
402	rc = ldap_url_parse( in->bv_val, &ludp );
403	switch ( rc ) {
404	case LDAP_URL_SUCCESS:
405		/* FIXME: the check is pedantic, but I think it's necessary,
406		 * because people tend to use things like ldaps:// which
407		 * gives the idea SSL is being used.  Maybe we could
408		 * accept ldapi:// as well, but the point is that we use
409		 * an URL as an easy means to define bits of a search with
410		 * little parsing.
411		 */
412		if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
413			/*
414			 * must be ldap:///
415			 */
416			rc = LDAP_INVALID_SYNTAX;
417			goto done;
418		}
419		break;
420
421	case LDAP_URL_ERR_BADSCHEME:
422		/*
423		 * last chance: assume it's a(n exact) DN ...
424		 *
425		 * NOTE: must pass DN normalization
426		 */
427		ldap_free_urldesc( ludp );
428		bv.bv_val = in->bv_val;
429		scope = LDAP_X_SCOPE_EXACT;
430		goto is_dn;
431
432	default:
433		rc = LDAP_INVALID_SYNTAX;
434		goto done;
435	}
436
437	if ( ( ludp->lud_host && *ludp->lud_host )
438		|| ludp->lud_attrs || ludp->lud_exts )
439	{
440		/* host part must be empty */
441		/* attrs and extensions parts must be empty */
442		rc = LDAP_INVALID_SYNTAX;
443		goto done;
444	}
445
446	/* Grab the filter */
447	if ( ludp->lud_filter ) {
448		Filter	*f = str2filter( ludp->lud_filter );
449		if ( f == NULL ) {
450			rc = LDAP_INVALID_SYNTAX;
451			goto done;
452		}
453		filter_free( f );
454	}
455
456	/* Grab the searchbase */
457	if ( ludp->lud_dn != NULL ) {
458		ber_str2bv( ludp->lud_dn, 0, 0, &bv );
459		rc = dnValidate( NULL, &bv );
460	} else {
461		rc = LDAP_INVALID_SYNTAX;
462	}
463
464done:
465	ldap_free_urldesc( ludp );
466	return( rc );
467}
468
469static int
470authzPrettyNormal(
471	struct berval	*val,
472	struct berval	*normalized,
473	void		*ctx,
474	int		normalize )
475{
476	struct berval	bv;
477	int		rc = LDAP_INVALID_SYNTAX;
478	LDAPURLDesc	*ludp = NULL;
479	char		*lud_dn = NULL,
480			*lud_filter = NULL;
481	int		scope = -1;
482
483	/*
484	 * 1) <DN>
485	 * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
486	 * 3) dn.regex:<pattern>
487	 * 4) u[.mech[/realm]]:<ID>
488	 * 5) group[/<groupClass>[/<memberAttr>]]:<DN>
489	 * 6) <URL>
490	 */
491
492	assert( val != NULL );
493	assert( !BER_BVISNULL( val ) );
494	BER_BVZERO( normalized );
495
496	/*
497	 * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
498	 * 3) dn.regex:<pattern>
499	 *
500	 * <DN> must pass DN normalization
501	 */
502	if ( !strncasecmp( val->bv_val, "dn", STRLENOF( "dn" ) ) ) {
503		struct berval	out = BER_BVNULL,
504				prefix = BER_BVNULL;
505		char		*ptr;
506
507		bv.bv_val = val->bv_val + STRLENOF( "dn" );
508
509		if ( bv.bv_val[ 0 ] == '.' ) {
510			bv.bv_val++;
511
512			if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
513				bv.bv_val += STRLENOF( "exact:" );
514				scope = LDAP_X_SCOPE_EXACT;
515
516			} else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
517				bv.bv_val += STRLENOF( "regex:" );
518				scope = LDAP_X_SCOPE_REGEX;
519
520			} else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
521				bv.bv_val += STRLENOF( "children:" );
522				scope = LDAP_X_SCOPE_CHILDREN;
523
524			} else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
525				bv.bv_val += STRLENOF( "subtree:" );
526				scope = LDAP_X_SCOPE_SUBTREE;
527
528			} else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
529				bv.bv_val += STRLENOF( "onelevel:" );
530				scope = LDAP_X_SCOPE_ONELEVEL;
531
532			} else {
533				return LDAP_INVALID_SYNTAX;
534			}
535
536		} else {
537			if ( bv.bv_val[ 0 ] != ':' ) {
538				return LDAP_INVALID_SYNTAX;
539			}
540			scope = LDAP_X_SCOPE_EXACT;
541			bv.bv_val++;
542		}
543
544		bv.bv_val += strspn( bv.bv_val, " " );
545		/* jump here in case no type specification was present
546		 * and uri was not an URI... HEADS-UP: assuming EXACT */
547is_dn:		bv.bv_len = val->bv_len - ( bv.bv_val - val->bv_val );
548
549		/* a single '*' means any DN without using regexes */
550		if ( ber_bvccmp( &bv, '*' ) ) {
551			ber_str2bv_x( "dn:*", STRLENOF( "dn:*" ), 1, normalized, ctx );
552			return LDAP_SUCCESS;
553		}
554
555		switch ( scope ) {
556		case LDAP_X_SCOPE_EXACT:
557		case LDAP_X_SCOPE_CHILDREN:
558		case LDAP_X_SCOPE_SUBTREE:
559		case LDAP_X_SCOPE_ONELEVEL:
560			if ( normalize ) {
561				rc = dnNormalize( 0, NULL, NULL, &bv, &out, ctx );
562			} else {
563				rc = dnPretty( NULL, &bv, &out, ctx );
564			}
565			if( rc != LDAP_SUCCESS ) {
566				return LDAP_INVALID_SYNTAX;
567			}
568			break;
569
570		case LDAP_X_SCOPE_REGEX:
571			normalized->bv_len = STRLENOF( "dn.regex:" ) + bv.bv_len;
572			normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
573			ptr = lutil_strcopy( normalized->bv_val, "dn.regex:" );
574			ptr = lutil_strncopy( ptr, bv.bv_val, bv.bv_len );
575			ptr[ 0 ] = '\0';
576			return LDAP_SUCCESS;
577
578		default:
579			return LDAP_INVALID_SYNTAX;
580		}
581
582		/* prepare prefix */
583		switch ( scope ) {
584		case LDAP_X_SCOPE_EXACT:
585			BER_BVSTR( &prefix, "dn:" );
586			break;
587
588		case LDAP_X_SCOPE_CHILDREN:
589			BER_BVSTR( &prefix, "dn.children:" );
590			break;
591
592		case LDAP_X_SCOPE_SUBTREE:
593			BER_BVSTR( &prefix, "dn.subtree:" );
594			break;
595
596		case LDAP_X_SCOPE_ONELEVEL:
597			BER_BVSTR( &prefix, "dn.onelevel:" );
598			break;
599
600		default:
601			assert( 0 );
602			break;
603		}
604
605		normalized->bv_len = prefix.bv_len + out.bv_len;
606		normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
607
608		ptr = lutil_strcopy( normalized->bv_val, prefix.bv_val );
609		ptr = lutil_strncopy( ptr, out.bv_val, out.bv_len );
610		ptr[ 0 ] = '\0';
611		ber_memfree_x( out.bv_val, ctx );
612
613		return LDAP_SUCCESS;
614
615	/*
616	 * 4) u[.mech[/realm]]:<ID>
617	 */
618	} else if ( ( val->bv_val[ 0 ] == 'u' || val->bv_val[ 0 ] == 'U' )
619			&& ( val->bv_val[ 1 ] == ':'
620				|| val->bv_val[ 1 ] == '/'
621				|| val->bv_val[ 1 ] == '.' ) )
622	{
623		char		buf[ SLAP_LDAPDN_MAXLEN ];
624		struct berval	id,
625				user = BER_BVNULL,
626				realm = BER_BVNULL,
627				mech = BER_BVNULL;
628
629		if ( sizeof( buf ) <= val->bv_len ) {
630			return LDAP_INVALID_SYNTAX;
631		}
632
633		id.bv_len = val->bv_len;
634		id.bv_val = buf;
635		strncpy( buf, val->bv_val, sizeof( buf ) );
636
637		rc = slap_parse_user( &id, &user, &realm, &mech );
638		if ( rc != LDAP_SUCCESS ) {
639			return LDAP_INVALID_SYNTAX;
640		}
641
642		ber_dupbv_x( normalized, val, ctx );
643
644		return rc;
645
646	/*
647	 * 5) group[/groupClass[/memberAttr]]:<DN>
648	 *
649	 * <groupClass> defaults to "groupOfNames"
650	 * <memberAttr> defaults to "member"
651	 *
652	 * <DN> must pass DN normalization
653	 */
654	} else if ( strncasecmp( val->bv_val, "group", STRLENOF( "group" ) ) == 0 )
655	{
656		struct berval	group_dn = BER_BVNULL,
657				group_oc = BER_BVNULL,
658				member_at = BER_BVNULL,
659				out = BER_BVNULL;
660		char		*ptr;
661
662		bv.bv_val = val->bv_val + STRLENOF( "group" );
663		bv.bv_len = val->bv_len - STRLENOF( "group" );
664		group_dn.bv_val = ber_bvchr( &bv, ':' );
665		if ( group_dn.bv_val == NULL ) {
666			/* last chance: assume it's a(n exact) DN ... */
667			bv.bv_val = val->bv_val;
668			scope = LDAP_X_SCOPE_EXACT;
669			goto is_dn;
670		}
671
672		/*
673		 * FIXME: we assume that "member" and "groupOfNames"
674		 * are present in schema...
675		 */
676		if ( bv.bv_val[ 0 ] == '/' ) {
677			ObjectClass		*oc = NULL;
678
679			group_oc.bv_val = &bv.bv_val[ 1 ];
680			group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
681
682			member_at.bv_val = ber_bvchr( &group_oc, '/' );
683			if ( member_at.bv_val ) {
684				AttributeDescription	*ad = NULL;
685				const char		*text = NULL;
686
687				group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
688				member_at.bv_val++;
689				member_at.bv_len = group_dn.bv_val - member_at.bv_val;
690				rc = slap_bv2ad( &member_at, &ad, &text );
691				if ( rc != LDAP_SUCCESS ) {
692					return rc;
693				}
694
695				member_at = ad->ad_cname;
696
697			}
698
699			oc = oc_bvfind( &group_oc );
700			if ( oc == NULL ) {
701				return LDAP_INVALID_SYNTAX;
702			}
703
704			group_oc = oc->soc_cname;
705		}
706
707		group_dn.bv_val++;
708		group_dn.bv_len = val->bv_len - ( group_dn.bv_val - val->bv_val );
709
710		if ( normalize ) {
711			rc = dnNormalize( 0, NULL, NULL, &group_dn, &out, ctx );
712		} else {
713			rc = dnPretty( NULL, &group_dn, &out, ctx );
714		}
715		if ( rc != LDAP_SUCCESS ) {
716			return rc;
717		}
718
719		normalized->bv_len = STRLENOF( "group" ":" ) + out.bv_len;
720		if ( !BER_BVISNULL( &group_oc ) ) {
721			normalized->bv_len += STRLENOF( "/" ) + group_oc.bv_len;
722			if ( !BER_BVISNULL( &member_at ) ) {
723				normalized->bv_len += STRLENOF( "/" ) + member_at.bv_len;
724			}
725		}
726
727		normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
728		ptr = lutil_strcopy( normalized->bv_val, "group" );
729		if ( !BER_BVISNULL( &group_oc ) ) {
730			ptr[ 0 ] = '/';
731			ptr++;
732			ptr = lutil_strncopy( ptr, group_oc.bv_val, group_oc.bv_len );
733			if ( !BER_BVISNULL( &member_at ) ) {
734				ptr[ 0 ] = '/';
735				ptr++;
736				ptr = lutil_strncopy( ptr, member_at.bv_val, member_at.bv_len );
737			}
738		}
739		ptr[ 0 ] = ':';
740		ptr++;
741		ptr = lutil_strncopy( ptr, out.bv_val, out.bv_len );
742		ptr[ 0 ] = '\0';
743		ber_memfree_x( out.bv_val, ctx );
744
745		return rc;
746	}
747
748	/*
749	 * ldap:///<base>??<scope>?<filter>
750	 * <scope> ::= {base|one|subtree}
751	 *
752	 * <scope> defaults to "base"
753	 * <base> must pass DN normalization
754	 * <filter> must pass str2filter()
755	 */
756	rc = ldap_url_parse( val->bv_val, &ludp );
757	switch ( rc ) {
758	case LDAP_URL_SUCCESS:
759		/* FIXME: the check is pedantic, but I think it's necessary,
760		 * because people tend to use things like ldaps:// which
761		 * gives the idea SSL is being used.  Maybe we could
762		 * accept ldapi:// as well, but the point is that we use
763		 * an URL as an easy means to define bits of a search with
764		 * little parsing.
765		 */
766		if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
767			/*
768			 * must be ldap:///
769			 */
770			rc = LDAP_INVALID_SYNTAX;
771			goto done;
772		}
773
774		AC_MEMCPY( ludp->lud_scheme, "ldap", STRLENOF( "ldap" ) );
775		break;
776
777	case LDAP_URL_ERR_BADSCHEME:
778		/*
779		 * last chance: assume it's a(n exact) DN ...
780		 *
781		 * NOTE: must pass DN normalization
782		 */
783		ldap_free_urldesc( ludp );
784		bv.bv_val = val->bv_val;
785		scope = LDAP_X_SCOPE_EXACT;
786		goto is_dn;
787
788	default:
789		rc = LDAP_INVALID_SYNTAX;
790		goto done;
791	}
792
793	if ( ( ludp->lud_host && *ludp->lud_host )
794		|| ludp->lud_attrs || ludp->lud_exts )
795	{
796		/* host part must be empty */
797		/* attrs and extensions parts must be empty */
798		rc = LDAP_INVALID_SYNTAX;
799		goto done;
800	}
801
802	/* Grab the filter */
803	if ( ludp->lud_filter ) {
804		struct berval	filterstr;
805		Filter		*f;
806
807		lud_filter = ludp->lud_filter;
808
809		f = str2filter( lud_filter );
810		if ( f == NULL ) {
811			rc = LDAP_INVALID_SYNTAX;
812			goto done;
813		}
814		filter2bv( f, &filterstr );
815		filter_free( f );
816		if ( BER_BVISNULL( &filterstr ) ) {
817			rc = LDAP_INVALID_SYNTAX;
818			goto done;
819		}
820
821		ludp->lud_filter = filterstr.bv_val;
822	}
823
824	/* Grab the searchbase */
825	if ( ludp->lud_dn ) {
826		struct berval	out = BER_BVNULL;
827
828		lud_dn = ludp->lud_dn;
829
830		ber_str2bv( lud_dn, 0, 0, &bv );
831		if ( normalize ) {
832			rc = dnNormalize( 0, NULL, NULL, &bv, &out, ctx );
833		} else {
834			rc = dnPretty( NULL, &bv, &out, ctx );
835		}
836
837		if ( rc != LDAP_SUCCESS ) {
838			goto done;
839		}
840
841		ludp->lud_dn = out.bv_val;
842	} else {
843		rc = LDAP_INVALID_SYNTAX;
844		goto done;
845	}
846
847	ludp->lud_port = 0;
848	normalized->bv_val = ldap_url_desc2str( ludp );
849	if ( normalized->bv_val ) {
850		normalized->bv_len = strlen( normalized->bv_val );
851
852	} else {
853		rc = LDAP_INVALID_SYNTAX;
854	}
855
856done:
857	if ( lud_filter ) {
858		if ( ludp->lud_filter != lud_filter ) {
859			ber_memfree( ludp->lud_filter );
860		}
861		ludp->lud_filter = lud_filter;
862	}
863
864	if ( lud_dn ) {
865		if ( ludp->lud_dn != lud_dn ) {
866			slap_sl_free( ludp->lud_dn, ctx );
867		}
868		ludp->lud_dn = lud_dn;
869	}
870
871	ldap_free_urldesc( ludp );
872
873	return( rc );
874}
875
876int
877authzNormalize(
878	slap_mask_t	usage,
879	Syntax		*syntax,
880	MatchingRule	*mr,
881	struct berval	*val,
882	struct berval	*normalized,
883	void		*ctx )
884{
885	int		rc;
886
887	Debug( LDAP_DEBUG_TRACE, ">>> authzNormalize: <%s>\n",
888		val->bv_val );
889
890	rc = authzPrettyNormal( val, normalized, ctx, 1 );
891
892	Debug( LDAP_DEBUG_TRACE, "<<< authzNormalize: <%s> (%d)\n",
893		normalized->bv_val, rc );
894
895	return rc;
896}
897
898int
899authzPretty(
900	Syntax *syntax,
901	struct berval *val,
902	struct berval *out,
903	void *ctx)
904{
905	int		rc;
906
907	Debug( LDAP_DEBUG_TRACE, ">>> authzPretty: <%s>\n",
908		val->bv_val );
909
910	rc = authzPrettyNormal( val, out, ctx, 0 );
911
912	Debug( LDAP_DEBUG_TRACE, "<<< authzPretty: <%s> (%d)\n",
913		out->bv_val ? out->bv_val : "(null)" , rc );
914
915	return rc;
916}
917
918
919static int
920slap_parseURI(
921	Operation	*op,
922	struct berval	*uri,
923	struct berval	*base,
924	struct berval	*nbase,
925	int		*scope,
926	Filter		**filter,
927	struct berval	*fstr,
928	int		normalize )
929{
930	struct berval	bv;
931	int		rc;
932	LDAPURLDesc	*ludp;
933
934	struct berval	idx;
935
936	assert( uri != NULL && !BER_BVISNULL( uri ) );
937	BER_BVZERO( base );
938	BER_BVZERO( nbase );
939	BER_BVZERO( fstr );
940	*scope = -1;
941	*filter = NULL;
942
943	Debug( LDAP_DEBUG_TRACE,
944		"slap_parseURI: parsing %s\n", uri->bv_val );
945
946	rc = LDAP_PROTOCOL_ERROR;
947
948	idx = *uri;
949	if ( idx.bv_val[ 0 ] == '{' ) {
950		char	*ptr;
951
952		ptr = ber_bvchr( &idx, '}' ) + 1;
953
954		assert( ptr != (void *)1 );
955
956		idx.bv_len -= ptr - idx.bv_val;
957		idx.bv_val = ptr;
958		uri = &idx;
959	}
960
961	/*
962	 * dn[.<dnstyle>]:<dnpattern>
963	 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
964	 *
965	 * <dnstyle> defaults to "exact"
966	 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
967	 */
968	if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
969		bv.bv_val = uri->bv_val + STRLENOF( "dn" );
970
971		if ( bv.bv_val[ 0 ] == '.' ) {
972			bv.bv_val++;
973
974			if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
975				bv.bv_val += STRLENOF( "exact:" );
976				*scope = LDAP_X_SCOPE_EXACT;
977
978			} else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
979				bv.bv_val += STRLENOF( "regex:" );
980				*scope = LDAP_X_SCOPE_REGEX;
981
982			} else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
983				bv.bv_val += STRLENOF( "children:" );
984				*scope = LDAP_X_SCOPE_CHILDREN;
985
986			} else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
987				bv.bv_val += STRLENOF( "subtree:" );
988				*scope = LDAP_X_SCOPE_SUBTREE;
989
990			} else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
991				bv.bv_val += STRLENOF( "onelevel:" );
992				*scope = LDAP_X_SCOPE_ONELEVEL;
993
994			} else {
995				return LDAP_PROTOCOL_ERROR;
996			}
997
998		} else {
999			if ( bv.bv_val[ 0 ] != ':' ) {
1000				return LDAP_PROTOCOL_ERROR;
1001			}
1002			*scope = LDAP_X_SCOPE_EXACT;
1003			bv.bv_val++;
1004		}
1005
1006		bv.bv_val += strspn( bv.bv_val, " " );
1007		/* jump here in case no type specification was present
1008		 * and uri was not an URI... HEADS-UP: assuming EXACT */
1009is_dn:		bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
1010
1011		/* a single '*' means any DN without using regexes */
1012		if ( ber_bvccmp( &bv, '*' ) ) {
1013			*scope = LDAP_X_SCOPE_USERS;
1014		}
1015
1016		switch ( *scope ) {
1017		case LDAP_X_SCOPE_EXACT:
1018		case LDAP_X_SCOPE_CHILDREN:
1019		case LDAP_X_SCOPE_SUBTREE:
1020		case LDAP_X_SCOPE_ONELEVEL:
1021			if ( normalize ) {
1022				rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
1023				if( rc != LDAP_SUCCESS ) {
1024					*scope = -1;
1025				}
1026			} else {
1027				ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
1028				rc = LDAP_SUCCESS;
1029			}
1030			break;
1031
1032		case LDAP_X_SCOPE_REGEX:
1033			ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
1034
1035		case LDAP_X_SCOPE_USERS:
1036			rc = LDAP_SUCCESS;
1037			break;
1038
1039		default:
1040			*scope = -1;
1041			break;
1042		}
1043
1044		return rc;
1045
1046	/*
1047	 * u:<uid>
1048	 */
1049	} else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
1050			&& ( uri->bv_val[ 1 ] == ':'
1051				|| uri->bv_val[ 1 ] == '/'
1052				|| uri->bv_val[ 1 ] == '.' ) )
1053	{
1054		Connection	c = *op->o_conn;
1055		char		buf[ SLAP_LDAPDN_MAXLEN ];
1056		struct berval	id,
1057				user = BER_BVNULL,
1058				realm = BER_BVNULL,
1059				mech = BER_BVNULL;
1060
1061		if ( sizeof( buf ) <= uri->bv_len ) {
1062			return LDAP_INVALID_SYNTAX;
1063		}
1064
1065		id.bv_len = uri->bv_len;
1066		id.bv_val = buf;
1067		strncpy( buf, uri->bv_val, sizeof( buf ) );
1068
1069		rc = slap_parse_user( &id, &user, &realm, &mech );
1070		if ( rc != LDAP_SUCCESS ) {
1071			return rc;
1072		}
1073
1074		if ( !BER_BVISNULL( &mech ) ) {
1075			c.c_sasl_bind_mech = mech;
1076		} else {
1077			BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
1078		}
1079
1080		rc = slap_sasl_getdn( &c, op, &user,
1081				realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
1082
1083		if ( rc == LDAP_SUCCESS ) {
1084			*scope = LDAP_X_SCOPE_EXACT;
1085		}
1086
1087		return rc;
1088
1089	/*
1090	 * group[/<groupoc>[/<groupat>]]:<groupdn>
1091	 *
1092	 * groupoc defaults to "groupOfNames"
1093	 * groupat defaults to "member"
1094	 *
1095	 * <groupdn> must pass DN normalization
1096	 */
1097	} else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
1098	{
1099		struct berval	group_dn = BER_BVNULL,
1100				group_oc = BER_BVNULL,
1101				member_at = BER_BVNULL;
1102		char		*tmp;
1103
1104		bv.bv_val = uri->bv_val + STRLENOF( "group" );
1105		bv.bv_len = uri->bv_len - STRLENOF( "group" );
1106		group_dn.bv_val = ber_bvchr( &bv, ':' );
1107		if ( group_dn.bv_val == NULL ) {
1108			/* last chance: assume it's a(n exact) DN ... */
1109			bv.bv_val = uri->bv_val;
1110			*scope = LDAP_X_SCOPE_EXACT;
1111			goto is_dn;
1112		}
1113
1114		if ( bv.bv_val[ 0 ] == '/' ) {
1115			group_oc.bv_val = &bv.bv_val[ 1 ];
1116			group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
1117
1118			member_at.bv_val = ber_bvchr( &group_oc, '/' );
1119			if ( member_at.bv_val ) {
1120				group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
1121				member_at.bv_val++;
1122				member_at.bv_len = group_dn.bv_val - member_at.bv_val;
1123
1124			} else {
1125				BER_BVSTR( &member_at, SLAPD_GROUP_ATTR );
1126			}
1127
1128		} else {
1129			BER_BVSTR( &group_oc, SLAPD_GROUP_CLASS );
1130			BER_BVSTR( &member_at, SLAPD_GROUP_ATTR );
1131		}
1132		group_dn.bv_val++;
1133		group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
1134
1135		if ( normalize ) {
1136			rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
1137			if ( rc != LDAP_SUCCESS ) {
1138				*scope = -1;
1139				return rc;
1140			}
1141		} else {
1142			ber_dupbv_x( nbase, &group_dn, op->o_tmpmemctx );
1143			rc = LDAP_SUCCESS;
1144		}
1145		*scope = LDAP_X_SCOPE_GROUP;
1146
1147		/* FIXME: caller needs to add value of member attribute
1148		 * and close brackets twice */
1149		fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
1150			+ group_oc.bv_len + member_at.bv_len;
1151		fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
1152
1153		tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
1154				STRLENOF( "(&(objectClass=" /* )) */ ) );
1155		tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
1156		tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
1157				STRLENOF( /* ( */ ")(" /* ) */ ) );
1158		tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
1159		tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
1160
1161		return rc;
1162	}
1163
1164	/*
1165	 * ldap:///<base>??<scope>?<filter>
1166	 * <scope> ::= {base|one|subtree}
1167	 *
1168	 * <scope> defaults to "base"
1169	 * <base> must pass DN normalization
1170	 * <filter> must pass str2filter()
1171	 */
1172	rc = ldap_url_parse( uri->bv_val, &ludp );
1173	switch ( rc ) {
1174	case LDAP_URL_SUCCESS:
1175		/* FIXME: the check is pedantic, but I think it's necessary,
1176		 * because people tend to use things like ldaps:// which
1177		 * gives the idea SSL is being used.  Maybe we could
1178		 * accept ldapi:// as well, but the point is that we use
1179		 * an URL as an easy means to define bits of a search with
1180		 * little parsing.
1181		 */
1182		if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
1183			/*
1184			 * must be ldap:///
1185			 */
1186			rc = LDAP_PROTOCOL_ERROR;
1187			goto done;
1188		}
1189		break;
1190
1191	case LDAP_URL_ERR_BADSCHEME:
1192		/*
1193		 * last chance: assume it's a(n exact) DN ...
1194		 *
1195		 * NOTE: must pass DN normalization
1196		 */
1197		ldap_free_urldesc( ludp );
1198		bv.bv_val = uri->bv_val;
1199		*scope = LDAP_X_SCOPE_EXACT;
1200		goto is_dn;
1201
1202	default:
1203		rc = LDAP_PROTOCOL_ERROR;
1204		goto done;
1205	}
1206
1207	if ( ( ludp->lud_host && *ludp->lud_host )
1208		|| ludp->lud_attrs || ludp->lud_exts )
1209	{
1210		/* host part must be empty */
1211		/* attrs and extensions parts must be empty */
1212		rc = LDAP_PROTOCOL_ERROR;
1213		goto done;
1214	}
1215
1216	/* Grab the scope */
1217	*scope = ludp->lud_scope;
1218
1219	/* Grab the filter */
1220	if ( ludp->lud_filter ) {
1221		*filter = str2filter_x( op, ludp->lud_filter );
1222		if ( *filter == NULL ) {
1223			rc = LDAP_PROTOCOL_ERROR;
1224			goto done;
1225		}
1226		ber_str2bv( ludp->lud_filter, 0, 0, fstr );
1227	}
1228
1229	/* Grab the searchbase */
1230	ber_str2bv( ludp->lud_dn, 0, 0, base );
1231	if ( normalize ) {
1232		rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
1233	} else {
1234		ber_dupbv_x( nbase, base, op->o_tmpmemctx );
1235		rc = LDAP_SUCCESS;
1236	}
1237
1238done:
1239	if( rc != LDAP_SUCCESS ) {
1240		if( *filter ) {
1241			filter_free_x( op, *filter, 1 );
1242			*filter = NULL;
1243		}
1244		BER_BVZERO( base );
1245		BER_BVZERO( fstr );
1246	} else {
1247		/* Don't free these, return them to caller */
1248		ludp->lud_filter = NULL;
1249		ludp->lud_dn = NULL;
1250	}
1251
1252	ldap_free_urldesc( ludp );
1253	return( rc );
1254}
1255
1256static int slap_sasl_rewrite_config_argv(
1257		const char	*fname,
1258		int		lineno,
1259		int		argc,
1260		char		**argv
1261)
1262{
1263	int	rc;
1264	char	*argv0 = NULL;
1265
1266	if ( strncasecmp( argv[0], "authid-", STRLENOF( "authid-" ) ) == 0 ) {
1267		/* strip "authid-" prefix for parsing */
1268		argv0 = argv[0];
1269		argv[0] = &argv0[ STRLENOF( "authid-" ) ];
1270	}
1271
1272	/* init at first call */
1273	if ( sasl_rwinfo == NULL ) {
1274		sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
1275	}
1276
1277	rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
1278
1279	if ( argv0 )
1280		argv[0] = argv0;
1281
1282	return rc;
1283}
1284
1285static int slap_sasl_rewrite_config_bv(
1286		const char	*fname,
1287		int		lineno,
1288		struct berval	bv
1289)
1290{
1291	int rc;
1292	ConfigArgs ca = { 0 };
1293
1294	ca.line = bv.bv_val;
1295	ca.argc = 0;
1296	config_fp_parse_line( &ca );
1297
1298	rc = slap_sasl_rewrite_config_argv( fname, lineno, ca.argc, ca.argv );
1299
1300	ch_free( ca.tline );
1301	ch_free( ca.argv );
1302
1303	return rc;
1304}
1305
1306static void
1307slap_sasl_rewrite_bva_add(
1308		BerVarray	*bva,
1309		int		idx,
1310		int		argc,
1311		char		**argv
1312)
1313{
1314	char		*line, *s;
1315	struct berval	bv;
1316
1317	if ( argc > 1 ) {
1318		/* quote all args but the first */
1319		line = ldap_charray2str( argv, "\" \"" );
1320		ber_str2bv( line, 0, 0, &bv );
1321		s = ber_bvchr( &bv, '"' );
1322		assert( s != NULL );
1323
1324		/* move the trailing quote of argv[0] to the end */
1325		AC_MEMCPY( s, s + 1, bv.bv_len - ( s - bv.bv_val ) );
1326		bv.bv_val[ bv.bv_len - 1 ] = '"';
1327	} else {
1328		ber_str2bv( argv[ 0 ], 0, 1, &bv );
1329	}
1330
1331	if ( idx == -1 ) {
1332		ber_bvarray_add( bva, &bv );
1333	} else {
1334		(*bva)[ idx ] = bv;
1335	}
1336}
1337
1338static int
1339slap_sasl_rewrite_destroy( void )
1340{
1341	if ( sasl_rwinfo ) {
1342		rewrite_info_delete( &sasl_rwinfo );
1343		sasl_rwinfo = NULL;
1344	}
1345
1346	return 0;
1347}
1348
1349int slap_sasl_rewrite_config(
1350		const char	*fname,
1351		int		lineno,
1352		int		argc,
1353		char		**argv,
1354		int		valx
1355)
1356{
1357	int	rc, i, last;
1358	char	*line;
1359	struct berval bv;
1360	struct rewrite_info *rw = sasl_rwinfo;
1361
1362	for ( last = 0; authz_rewrites && !BER_BVISNULL( &authz_rewrites[ last ] ); last++ )
1363		/* count'em */ ;
1364
1365	if ( valx == -1 || valx >= last ) {
1366		valx = -1;
1367		rc = slap_sasl_rewrite_config_argv( fname, lineno, argc, argv );
1368		if ( rc == 0 ) {
1369			slap_sasl_rewrite_bva_add( &authz_rewrites, valx, argc, argv );
1370		}
1371		return rc;
1372	}
1373
1374	sasl_rwinfo = NULL;
1375
1376	for ( i = 0; i < valx; i++ )
1377	{
1378		rc = slap_sasl_rewrite_config_bv( fname, lineno, authz_rewrites[ i ] );
1379		assert( rc == 0 );
1380	}
1381
1382	rc = slap_sasl_rewrite_config_argv( fname, lineno, argc, argv );
1383	if ( rc != 0 ) {
1384		slap_sasl_rewrite_destroy();
1385		sasl_rwinfo = rw;
1386		return 1;
1387	}
1388
1389	for ( i = valx; authz_rewrites && !BER_BVISNULL( &authz_rewrites[ i ] ); i++ )
1390	{
1391		rc = slap_sasl_rewrite_config_bv( fname, lineno, authz_rewrites[ i ] );
1392		assert( rc == 0 );
1393	}
1394
1395	authz_rewrites = ch_realloc( authz_rewrites,
1396			( last + 2 )*sizeof( struct berval ) );
1397	BER_BVZERO( &authz_rewrites[ last + 1 ] );
1398
1399	for ( i = last - 1; i >= valx; i-- )
1400	{
1401		authz_rewrites[ i + 1 ] = authz_rewrites[ i ];
1402	}
1403
1404	slap_sasl_rewrite_bva_add( &authz_rewrites, valx, argc, argv );
1405
1406	if ( rw )
1407		rewrite_info_delete( &rw );
1408
1409	return rc;
1410}
1411
1412int slap_sasl_rewrite_delete( int valx ) {
1413	int rc, i;
1414
1415	if ( valx == -1 ) {
1416		slap_sasl_rewrite_destroy();
1417		if ( authz_rewrites ) {
1418			ber_bvarray_free( authz_rewrites );
1419			authz_rewrites = NULL;
1420		}
1421		return 0;
1422	}
1423
1424	for ( i = 0; !BER_BVISNULL( &authz_rewrites[ i ] ); i++ )
1425		/* count'em */ ;
1426
1427	if ( valx >= i ) {
1428		return 1;
1429	}
1430
1431	ber_memfree( authz_rewrites[ i ].bv_val );
1432	for ( i = valx; !BER_BVISNULL( &authz_rewrites[ i + 1 ] ); i++ )
1433	{
1434		authz_rewrites[ i ] = authz_rewrites[ i + 1 ];
1435	}
1436	BER_BVZERO( &authz_rewrites[ i ] );
1437
1438	slap_sasl_rewrite_destroy();
1439
1440	for ( i = 0; !BER_BVISNULL( &authz_rewrites[ i ] ); i++ )
1441	{
1442		rc = slap_sasl_rewrite_config_bv( "slapd", 0, authz_rewrites[ i ] );
1443		assert( rc == 0 );
1444	}
1445
1446	return rc;
1447}
1448
1449int slap_sasl_rewrite_unparse( BerVarray *bva ) {
1450	if ( authz_rewrites ) {
1451		return slap_bv_x_ordered_unparse( authz_rewrites, bva );
1452	}
1453	return 0;
1454}
1455
1456static int
1457slap_sasl_regexp_rewrite_config(
1458		struct rewrite_info	**rwinfo,
1459		const char		*fname,
1460		int			lineno,
1461		const char		*match,
1462		const char		*replace,
1463		const char		*context )
1464{
1465	int	rc;
1466	char	*argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
1467	struct rewrite_info *rw = *rwinfo;
1468
1469	/* init at first call */
1470	if ( rw == NULL ) {
1471		char *argvEngine[] = { "rewriteEngine", "on", NULL };
1472		char *argvContext[] = { "rewriteContext", NULL, NULL };
1473
1474		/* initialize rewrite engine */
1475		rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
1476
1477		/* switch on rewrite engine */
1478		rc = rewrite_parse( rw, fname, lineno, 2, argvEngine );
1479		if (rc != LDAP_SUCCESS) {
1480			goto out;
1481		}
1482
1483		/* create generic authid context */
1484		argvContext[1] = AUTHID_CONTEXT;
1485		rc = rewrite_parse( rw, fname, lineno, 2, argvContext );
1486		if (rc != LDAP_SUCCESS) {
1487			goto out;
1488		}
1489	}
1490
1491	argvRule[1] = (char *)match;
1492	argvRule[2] = (char *)replace;
1493	rc = rewrite_parse( rw, fname, lineno, 4, argvRule );
1494out:
1495	if (rc == LDAP_SUCCESS) {
1496		*rwinfo = rw;
1497	} else {
1498		rewrite_info_delete( &rw );
1499	}
1500
1501	return rc;
1502}
1503
1504int slap_sasl_regexp_config( const char *match, const char *replace, int valx )
1505{
1506	int i, rc;
1507	SaslRegexp_t sr;
1508	struct rewrite_info *rw = NULL;
1509
1510	if ( valx < 0 || valx > nSaslRegexp )
1511		valx = nSaslRegexp;
1512
1513	for ( i = 0; i < valx; i++) {
1514		rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
1515				SaslRegexp[i].sr_match,
1516				SaslRegexp[i].sr_replace,
1517				AUTHID_CONTEXT);
1518		assert( rc == 0 );
1519	}
1520
1521	rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
1522			match, replace, AUTHID_CONTEXT );
1523
1524	if ( rc == LDAP_SUCCESS ) {
1525		SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
1526				(nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
1527
1528		for ( i = nSaslRegexp; i > valx; i-- ) {
1529			SaslRegexp[i] = SaslRegexp[i - 1];
1530		}
1531
1532		SaslRegexp[i] = sr;
1533		SaslRegexp[i].sr_match = ch_strdup( match );
1534		SaslRegexp[i].sr_replace = ch_strdup( replace );
1535
1536		nSaslRegexp++;
1537
1538		for ( i = valx + 1; i < nSaslRegexp; i++ ) {
1539			rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
1540					SaslRegexp[i].sr_match,
1541					SaslRegexp[i].sr_replace,
1542					AUTHID_CONTEXT);
1543			assert( rc == 0 );
1544		}
1545
1546		slap_sasl_rewrite_destroy();
1547		sasl_rwinfo = rw;
1548	} else if ( rw ) {
1549		rewrite_info_delete( &rw );
1550	}
1551
1552	return rc;
1553}
1554
1555static void
1556slap_sasl_regexp_destroy_one( int n )
1557{
1558	ch_free( SaslRegexp[ n ].sr_match );
1559	ch_free( SaslRegexp[ n ].sr_replace );
1560}
1561
1562void
1563slap_sasl_regexp_destroy( void )
1564{
1565	if ( SaslRegexp ) {
1566		int	n;
1567
1568		for ( n = 0; n < nSaslRegexp; n++ ) {
1569			slap_sasl_regexp_destroy_one( n );
1570		}
1571
1572		ch_free( SaslRegexp );
1573		SaslRegexp = NULL;
1574		nSaslRegexp = 0;
1575	}
1576
1577	slap_sasl_rewrite_destroy();
1578}
1579
1580int slap_sasl_regexp_delete( int valx )
1581{
1582	int rc = 0;
1583
1584	if ( valx >= nSaslRegexp ) {
1585		rc = 1;
1586	} else if ( valx < 0 || nSaslRegexp == 1 ) {
1587		slap_sasl_regexp_destroy();
1588	} else {
1589		int i;
1590
1591		slap_sasl_regexp_destroy_one( valx );
1592		nSaslRegexp--;
1593
1594		for ( i = valx; i < nSaslRegexp; i++ ) {
1595			SaslRegexp[ i ] = SaslRegexp[ i + 1 ];
1596		}
1597
1598		slap_sasl_rewrite_destroy();
1599		for ( i = 0; i < nSaslRegexp; i++ ) {
1600			rc = slap_sasl_regexp_rewrite_config( &sasl_rwinfo, "sasl-regexp", 0,
1601					SaslRegexp[ i ].sr_match,
1602					SaslRegexp[ i ].sr_replace,
1603					AUTHID_CONTEXT );
1604			assert( rc == 0 );
1605		}
1606	}
1607
1608	return rc;
1609}
1610
1611void slap_sasl_regexp_unparse( BerVarray *out )
1612{
1613	int i;
1614	BerVarray bva = NULL;
1615	char ibuf[32], *ptr;
1616	struct berval idx;
1617
1618	if ( !nSaslRegexp ) return;
1619
1620	idx.bv_val = ibuf;
1621	bva = ch_malloc( (nSaslRegexp+1) * sizeof(struct berval) );
1622	BER_BVZERO(bva+nSaslRegexp);
1623	for ( i=0; i<nSaslRegexp; i++ ) {
1624		idx.bv_len = sprintf( idx.bv_val, "{%d}", i);
1625		bva[i].bv_len = idx.bv_len + strlen( SaslRegexp[i].sr_match ) +
1626			strlen( SaslRegexp[i].sr_replace ) + 5;
1627		bva[i].bv_val = ch_malloc( bva[i].bv_len+1 );
1628		ptr = lutil_strcopy( bva[i].bv_val, ibuf );
1629		*ptr++ = '"';
1630		ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_match );
1631		ptr = lutil_strcopy( ptr, "\" \"" );
1632		ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_replace );
1633		*ptr++ = '"';
1634		*ptr = '\0';
1635	}
1636	*out = bva;
1637}
1638
1639/* Take the passed in SASL name and attempt to convert it into an
1640   LDAP URI to find the matching LDAP entry, using the pattern matching
1641   strings given in the saslregexp config file directive(s) */
1642
1643static int slap_authz_regexp( struct berval *in, struct berval *out,
1644		int flags, void *ctx )
1645{
1646	const char	*context = AUTHID_CONTEXT;
1647
1648	if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
1649		return 0;
1650	}
1651
1652	/* FIXME: if aware of authc/authz mapping,
1653	 * we could use different contexts ... */
1654	switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
1655				&out->bv_val ) )
1656	{
1657	case REWRITE_REGEXEC_OK:
1658		if ( !BER_BVISNULL( out ) ) {
1659			char *val = out->bv_val;
1660			ber_str2bv_x( val, 0, 1, out, ctx );
1661			if ( val != in->bv_val ) {
1662				free( val );
1663			}
1664		} else {
1665			ber_dupbv_x( out, in, ctx );
1666		}
1667		Debug( LDAP_DEBUG_ARGS,
1668			"[rw] %s: \"%s\" -> \"%s\"\n",
1669			context, in->bv_val, out->bv_val );
1670		return 1;
1671
1672	case REWRITE_REGEXEC_UNWILLING:
1673	case REWRITE_REGEXEC_ERR:
1674	default:
1675		return 0;
1676	}
1677
1678}
1679
1680/* This callback actually does some work...*/
1681static int sasl_sc_sasl2dn( Operation *op, SlapReply *rs )
1682{
1683	struct berval *ndn = op->o_callback->sc_private;
1684
1685	if ( rs->sr_type != REP_SEARCH ) return LDAP_SUCCESS;
1686
1687	/* We only want to be called once */
1688	if ( !BER_BVISNULL( ndn ) ) {
1689		op->o_tmpfree( ndn->bv_val, op->o_tmpmemctx );
1690		BER_BVZERO( ndn );
1691
1692		Debug( LDAP_DEBUG_TRACE,
1693			"%s: slap_sc_sasl2dn: search DN returned more than 1 entry\n",
1694			op->o_log_prefix );
1695		return LDAP_UNAVAILABLE; /* short-circuit the search */
1696	}
1697
1698	ber_dupbv_x( ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
1699	return LDAP_SUCCESS;
1700}
1701
1702
1703typedef struct smatch_info {
1704	struct berval *dn;
1705	int match;
1706} smatch_info;
1707
1708static int sasl_sc_smatch( Operation *o, SlapReply *rs )
1709{
1710	smatch_info *sm = o->o_callback->sc_private;
1711
1712	if (rs->sr_type != REP_SEARCH) return 0;
1713
1714	if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
1715		sm->match = 1;
1716		return LDAP_UNAVAILABLE;	/* short-circuit the search */
1717	}
1718
1719	return 0;
1720}
1721
1722int
1723slap_sasl_matches( Operation *op, BerVarray rules,
1724		struct berval *assertDN, struct berval *authc )
1725{
1726	int	rc = LDAP_INAPPROPRIATE_AUTH;
1727
1728	if ( rules != NULL ) {
1729		int	i;
1730
1731		for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
1732			rc = slap_sasl_match( op, &rules[i], assertDN, authc );
1733			if ( rc == LDAP_SUCCESS ) break;
1734		}
1735	}
1736
1737	return rc;
1738}
1739
1740/*
1741 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
1742 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
1743 * the rule must be used as an internal search for entries. If that search
1744 * returns the *assertDN entry, the match is successful.
1745 *
1746 * The assertDN should not have the dn: prefix
1747 */
1748
1749static int
1750slap_sasl_match( Operation *opx, struct berval *rule,
1751	struct berval *assertDN, struct berval *authc )
1752{
1753	int rc;
1754	regex_t reg;
1755	smatch_info sm;
1756	slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
1757	Operation op = {0};
1758	SlapReply rs = {REP_RESULT};
1759	struct berval base = BER_BVNULL;
1760
1761	sm.dn = assertDN;
1762	sm.match = 0;
1763	cb.sc_private = &sm;
1764
1765	Debug( LDAP_DEBUG_TRACE,
1766	   "===>slap_sasl_match: comparing DN %s to rule %s\n",
1767		assertDN->bv_len ? assertDN->bv_val : "(null)", rule->bv_val );
1768
1769	/* NOTE: don't normalize rule if authz syntax is enabled */
1770	rc = slap_parseURI( opx, rule, &base, &op.o_req_ndn,
1771		&op.ors_scope, &op.ors_filter, &op.ors_filterstr, 0 );
1772
1773	if( rc != LDAP_SUCCESS ) goto CONCLUDED;
1774
1775	switch ( op.ors_scope ) {
1776	case LDAP_X_SCOPE_EXACT:
1777exact_match:
1778		if ( dn_match( &op.o_req_ndn, assertDN ) ) {
1779			rc = LDAP_SUCCESS;
1780		} else {
1781			rc = LDAP_INAPPROPRIATE_AUTH;
1782		}
1783		goto CONCLUDED;
1784
1785	case LDAP_X_SCOPE_CHILDREN:
1786	case LDAP_X_SCOPE_SUBTREE:
1787	case LDAP_X_SCOPE_ONELEVEL:
1788	{
1789		int	d = assertDN->bv_len - op.o_req_ndn.bv_len;
1790
1791		rc = LDAP_INAPPROPRIATE_AUTH;
1792
1793		if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
1794			goto exact_match;
1795
1796		} else if ( d > 0 ) {
1797			struct berval bv;
1798
1799			/* leave room for at least one char of attributeType,
1800			 * one for '=' and one for ',' */
1801			if ( d < (int) STRLENOF( "x=,") ) {
1802				goto CONCLUDED;
1803			}
1804
1805			bv.bv_len = op.o_req_ndn.bv_len;
1806			bv.bv_val = assertDN->bv_val + d;
1807
1808			if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
1809				switch ( op.ors_scope ) {
1810				case LDAP_X_SCOPE_SUBTREE:
1811				case LDAP_X_SCOPE_CHILDREN:
1812					rc = LDAP_SUCCESS;
1813					break;
1814
1815				case LDAP_X_SCOPE_ONELEVEL:
1816				{
1817					struct berval	pdn;
1818
1819					dnParent( assertDN, &pdn );
1820					/* the common portion of the DN
1821					 * already matches, so only check
1822					 * if parent DN of assertedDN
1823					 * is all the pattern */
1824					if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
1825						rc = LDAP_SUCCESS;
1826					}
1827					break;
1828				}
1829				default:
1830					/* at present, impossible */
1831					assert( 0 );
1832				}
1833			}
1834		}
1835		goto CONCLUDED;
1836	}
1837
1838	case LDAP_X_SCOPE_REGEX:
1839		rc = regcomp(&reg, op.o_req_ndn.bv_val,
1840			REG_EXTENDED|REG_ICASE|REG_NOSUB);
1841		if ( rc == 0 ) {
1842			rc = regexec(&reg, assertDN->bv_val, 0, NULL, 0);
1843			regfree( &reg );
1844		}
1845		if ( rc == 0 ) {
1846			rc = LDAP_SUCCESS;
1847		} else {
1848			rc = LDAP_INAPPROPRIATE_AUTH;
1849		}
1850		goto CONCLUDED;
1851
1852	case LDAP_X_SCOPE_GROUP: {
1853		char	*tmp;
1854
1855		/* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
1856		 * we need to append the <assertDN> so that the <group_dn> is searched
1857		 * with scope "base", and the filter ensures that <assertDN> is
1858		 * member of the group */
1859		tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
1860			assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
1861		if ( tmp == NULL ) {
1862			rc = LDAP_NO_MEMORY;
1863			goto CONCLUDED;
1864		}
1865		op.ors_filterstr.bv_val = tmp;
1866
1867		tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
1868		tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
1869
1870		/* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
1871		op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
1872		if ( op.ors_filter == NULL ) {
1873			rc = LDAP_PROTOCOL_ERROR;
1874			goto CONCLUDED;
1875		}
1876		op.ors_scope = LDAP_SCOPE_BASE;
1877
1878		/* hijack match DN: use that of the group instead of the assertDN;
1879		 * assertDN is now in the filter */
1880		sm.dn = &op.o_req_ndn;
1881
1882		/* do the search */
1883		break;
1884		}
1885
1886	case LDAP_X_SCOPE_USERS:
1887		if ( !BER_BVISEMPTY( assertDN ) ) {
1888			rc = LDAP_SUCCESS;
1889		} else {
1890			rc = LDAP_INAPPROPRIATE_AUTH;
1891		}
1892		goto CONCLUDED;
1893
1894	default:
1895		break;
1896	}
1897
1898	/* Must run an internal search. */
1899	if ( op.ors_filter == NULL ) {
1900		rc = LDAP_FILTER_ERROR;
1901		goto CONCLUDED;
1902	}
1903
1904	Debug( LDAP_DEBUG_TRACE,
1905	   "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1906	   op.o_req_ndn.bv_val, op.ors_scope );
1907
1908	op.o_bd = select_backend( &op.o_req_ndn, 1 );
1909	if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1910		rc = LDAP_INAPPROPRIATE_AUTH;
1911		goto CONCLUDED;
1912	}
1913
1914	op.o_hdr = opx->o_hdr;
1915	op.o_tag = LDAP_REQ_SEARCH;
1916	op.o_ndn = *authc;
1917	op.o_callback = &cb;
1918	slap_op_time( &op.o_time, &op.o_tincr );
1919	op.o_do_not_cache = 1;
1920	op.o_is_auth_check = 1;
1921	/* use req_ndn as req_dn instead of non-pretty base of uri */
1922	if( !BER_BVISNULL( &base ) ) {
1923		ch_free( base.bv_val );
1924		/* just in case... */
1925		BER_BVZERO( &base );
1926	}
1927	ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1928	op.ors_deref = LDAP_DEREF_NEVER;
1929	op.ors_slimit = 1;
1930	op.ors_tlimit = SLAP_NO_LIMIT;
1931	op.ors_attrs = slap_anlist_no_attrs;
1932	op.ors_attrsonly = 1;
1933
1934	op.o_bd->be_search( &op, &rs );
1935
1936	if (sm.match) {
1937		rc = LDAP_SUCCESS;
1938	} else {
1939		rc = LDAP_INAPPROPRIATE_AUTH;
1940	}
1941
1942CONCLUDED:
1943	if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1944	if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1945	if( op.ors_filter ) filter_free_x( opx, op.ors_filter, 1 );
1946	if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1947
1948	Debug( LDAP_DEBUG_TRACE,
1949	   "<===slap_sasl_match: comparison returned %d\n", rc );
1950
1951	return( rc );
1952}
1953
1954
1955/*
1956 * This function answers the question, "Can this ID authorize to that ID?",
1957 * based on authorization rules. The rules are stored in the *searchDN, in the
1958 * attribute named by *attr. If any of those rules map to the *assertDN, the
1959 * authorization is approved.
1960 *
1961 * The DNs should not have the dn: prefix
1962 */
1963static int
1964slap_sasl_check_authz( Operation *op,
1965	struct berval *searchDN,
1966	struct berval *assertDN,
1967	AttributeDescription *ad,
1968	struct berval *authc )
1969{
1970	int		rc,
1971			do_not_cache = op->o_do_not_cache;
1972	BerVarray	vals = NULL;
1973
1974	Debug( LDAP_DEBUG_TRACE,
1975	   "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1976	   assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1977
1978	/* ITS#4760: don't cache group access */
1979	op->o_do_not_cache = 1;
1980	rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1981	op->o_do_not_cache = do_not_cache;
1982	if( rc != LDAP_SUCCESS ) goto COMPLETE;
1983
1984	/* Check if the *assertDN matches any *vals */
1985	rc = slap_sasl_matches( op, vals, assertDN, authc );
1986
1987COMPLETE:
1988	if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1989
1990	Debug( LDAP_DEBUG_TRACE,
1991	   "<==slap_sasl_check_authz: %s check returning %d\n",
1992		ad->ad_cname.bv_val, rc );
1993
1994	return( rc );
1995}
1996
1997/*
1998 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1999 * return the LDAP DN to which it matches. The SASL regexp rules in the config
2000 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
2001 * search with scope=base), just return the URI (or its searchbase). Otherwise
2002 * an internal search must be done, and if that search returns exactly one
2003 * entry, return the DN of that one entry.
2004 */
2005void
2006slap_sasl2dn(
2007	Operation	*opx,
2008	struct berval	*saslname,
2009	struct berval	*sasldn,
2010	int		flags )
2011{
2012	int rc;
2013	slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
2014	Operation op = {0};
2015	SlapReply rs = {REP_RESULT};
2016	struct berval regout = BER_BVNULL;
2017	struct berval base = BER_BVNULL;
2018
2019	Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
2020		"converting SASL name %s to a DN\n",
2021		saslname->bv_val );
2022
2023	BER_BVZERO( sasldn );
2024	cb.sc_private = sasldn;
2025
2026	/* Convert the SASL name into a minimal URI */
2027	if( !slap_authz_regexp( saslname, &regout, flags, opx->o_tmpmemctx ) ) {
2028		goto FINISHED;
2029	}
2030
2031	/* NOTE: always normalize regout because it results
2032	 * from string submatch expansion */
2033	rc = slap_parseURI( opx, &regout, &base, &op.o_req_ndn,
2034		&op.ors_scope, &op.ors_filter, &op.ors_filterstr, 1 );
2035	if ( !BER_BVISNULL( &regout ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
2036	if ( rc != LDAP_SUCCESS ) {
2037		goto FINISHED;
2038	}
2039
2040	/* Must do an internal search */
2041	op.o_bd = select_backend( &op.o_req_ndn, 1 );
2042
2043	switch ( op.ors_scope ) {
2044	case LDAP_X_SCOPE_EXACT:
2045		*sasldn = op.o_req_ndn;
2046		BER_BVZERO( &op.o_req_ndn );
2047		/* intentionally continue to next case */
2048
2049	case LDAP_X_SCOPE_REGEX:
2050	case LDAP_X_SCOPE_SUBTREE:
2051	case LDAP_X_SCOPE_CHILDREN:
2052	case LDAP_X_SCOPE_ONELEVEL:
2053	case LDAP_X_SCOPE_GROUP:
2054	case LDAP_X_SCOPE_USERS:
2055		/* correctly parsed, but illegal */
2056		goto FINISHED;
2057
2058	case LDAP_SCOPE_BASE:
2059	case LDAP_SCOPE_ONELEVEL:
2060	case LDAP_SCOPE_SUBTREE:
2061	case LDAP_SCOPE_SUBORDINATE:
2062		/* do a search */
2063		break;
2064
2065	default:
2066		/* catch unhandled cases (there shouldn't be) */
2067		assert( 0 );
2068	}
2069
2070	Debug( LDAP_DEBUG_TRACE,
2071		"slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
2072		op.o_req_ndn.bv_val, op.ors_scope );
2073
2074	if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
2075		goto FINISHED;
2076	}
2077
2078	/* Must run an internal search. */
2079	if ( op.ors_filter == NULL ) {
2080		rc = LDAP_FILTER_ERROR;
2081		goto FINISHED;
2082	}
2083
2084	op.o_hdr = opx->o_hdr;
2085	op.o_tag = LDAP_REQ_SEARCH;
2086	op.o_ndn = opx->o_conn->c_ndn;
2087	op.o_callback = &cb;
2088	slap_op_time( &op.o_time, &op.o_tincr );
2089	op.o_do_not_cache = 1;
2090	op.o_is_auth_check = 1;
2091	op.ors_deref = LDAP_DEREF_NEVER;
2092	op.ors_slimit = 1;
2093	op.ors_tlimit = SLAP_NO_LIMIT;
2094	op.ors_attrs = slap_anlist_no_attrs;
2095	op.ors_attrsonly = 1;
2096	/* use req_ndn as req_dn instead of non-pretty base of uri */
2097	if( !BER_BVISNULL( &base ) ) {
2098		ch_free( base.bv_val );
2099		/* just in case... */
2100		BER_BVZERO( &base );
2101	}
2102	ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
2103
2104	op.o_bd->be_search( &op, &rs );
2105
2106FINISHED:
2107	if( opx == opx->o_conn->c_sasl_bindop && !BER_BVISEMPTY( sasldn ) ) {
2108		opx->o_conn->c_authz_backend = op.o_bd;
2109	}
2110	if( !BER_BVISNULL( &op.o_req_dn ) ) {
2111		slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
2112	}
2113	if( !BER_BVISNULL( &op.o_req_ndn ) ) {
2114		slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
2115	}
2116	if( op.ors_filter ) {
2117		filter_free_x( opx, op.ors_filter, 1 );
2118	}
2119	if( !BER_BVISNULL( &op.ors_filterstr ) ) {
2120		ch_free( op.ors_filterstr.bv_val );
2121	}
2122
2123	Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
2124		!BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>" );
2125
2126	return;
2127}
2128
2129
2130/* Check if a bind can SASL authorize to another identity.
2131 * The DNs should not have the dn: prefix
2132 */
2133
2134int slap_sasl_authorized( Operation *op,
2135	struct berval *authcDN, struct berval *authzDN )
2136{
2137	int rc = LDAP_INAPPROPRIATE_AUTH;
2138
2139	/* User binding as anonymous */
2140	if ( !authzDN || !authzDN->bv_len || !authzDN->bv_val ) {
2141		rc = LDAP_SUCCESS;
2142		goto DONE;
2143	}
2144
2145	/* User is anonymous */
2146	if ( !authcDN || !authcDN->bv_len || !authcDN->bv_val ) {
2147		goto DONE;
2148	}
2149
2150	Debug( LDAP_DEBUG_TRACE,
2151	   "==>slap_sasl_authorized: can %s become %s?\n",
2152		authcDN->bv_len ? authcDN->bv_val : "(null)",
2153		authzDN->bv_len ? authzDN->bv_val : "(null)" );
2154
2155	/* If person is authorizing to self, succeed */
2156	if ( dn_match( authcDN, authzDN ) ) {
2157		rc = LDAP_SUCCESS;
2158		goto DONE;
2159	}
2160
2161	/* Allow the manager to authorize as any DN in its own DBs. */
2162	{
2163		Backend *zbe = select_backend( authzDN, 1 );
2164		if ( zbe && be_isroot_dn( zbe, authcDN )) {
2165			rc = LDAP_SUCCESS;
2166			goto DONE;
2167		}
2168	}
2169
2170	/* Check source rules */
2171	if( authz_policy & SASL_AUTHZ_TO ) {
2172		rc = slap_sasl_check_authz( op, authcDN, authzDN,
2173			slap_schema.si_ad_saslAuthzTo, authcDN );
2174		if(( rc == LDAP_SUCCESS ) ^ (( authz_policy & SASL_AUTHZ_AND) != 0)) {
2175			if( rc != LDAP_SUCCESS )
2176				rc = LDAP_INAPPROPRIATE_AUTH;
2177			goto DONE;
2178		}
2179	}
2180
2181	/* Check destination rules */
2182	if( authz_policy & SASL_AUTHZ_FROM ) {
2183		rc = slap_sasl_check_authz( op, authzDN, authcDN,
2184			slap_schema.si_ad_saslAuthzFrom, authcDN );
2185		if( rc == LDAP_SUCCESS ) {
2186			goto DONE;
2187		}
2188	}
2189
2190	rc = LDAP_INAPPROPRIATE_AUTH;
2191
2192DONE:
2193
2194	Debug( LDAP_DEBUG_TRACE,
2195		"<== slap_sasl_authorized: return %d\n", rc );
2196
2197	return( rc );
2198}
2199