Home | History | Annotate | Line # | Download | only in slapd
      1 /*	$NetBSD: saslauthz.c,v 1.4 2025/09/05 21:16:25 christos Exp $	*/
      2 
      3 /* $OpenLDAP$ */
      4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      5  *
      6  * Copyright 1998-2024 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.4 2025/09/05 21:16:25 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 
     75 typedef struct sasl_regexp {
     76   char *sr_match;						/* regexp match pattern */
     77   char *sr_replace;					/* regexp replace pattern */
     78 } SaslRegexp_t;
     79 
     80 static int nSaslRegexp = 0;
     81 static SaslRegexp_t *SaslRegexp = NULL;
     82 
     83 #include "rewrite.h"
     84 struct rewrite_info	*sasl_rwinfo = NULL;
     85 #define AUTHID_CONTEXT	"authid"
     86 static 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 
     94 static const char *policy_txt[] = {
     95 	"none", "from", "to", "any"
     96 };
     97 
     98 static int authz_policy = SASL_AUTHZ_NONE;
     99 
    100 static int
    101 slap_sasl_match( Operation *opx, struct berval *rule,
    102 	struct berval *assertDN, struct berval *authc );
    103 
    104 int 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 
    124 const 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 
    132 int 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 
    210 int
    211 authzValidate(
    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 */
    282 is_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 
    464 done:
    465 	ldap_free_urldesc( ludp );
    466 	return( rc );
    467 }
    468 
    469 static int
    470 authzPrettyNormal(
    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 */
    547 is_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 
    856 done:
    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 
    876 int
    877 authzNormalize(
    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 
    898 int
    899 authzPretty(
    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 
    919 static int
    920 slap_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 */
   1009 is_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 
   1238 done:
   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 
   1256 static 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 
   1285 static 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 
   1306 static void
   1307 slap_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 
   1338 static int
   1339 slap_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 
   1349 int 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 
   1412 int 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 	i = valx;
   1432 	ber_memfree( authz_rewrites[ i ].bv_val );
   1433 	for ( ; !BER_BVISNULL( &authz_rewrites[ i + 1 ] ); i++ )
   1434 	{
   1435 		authz_rewrites[ i ] = authz_rewrites[ i + 1 ];
   1436 	}
   1437 	BER_BVZERO( &authz_rewrites[ i ] );
   1438 
   1439 	slap_sasl_rewrite_destroy();
   1440 
   1441 	for ( i = 0; !BER_BVISNULL( &authz_rewrites[ i ] ); i++ )
   1442 	{
   1443 		rc = slap_sasl_rewrite_config_bv( "slapd", 0, authz_rewrites[ i ] );
   1444 		assert( rc == 0 );
   1445 	}
   1446 
   1447 	return rc;
   1448 }
   1449 
   1450 int slap_sasl_rewrite_unparse( BerVarray *bva ) {
   1451 	if ( authz_rewrites ) {
   1452 		return slap_bv_x_ordered_unparse( authz_rewrites, bva );
   1453 	}
   1454 	return 0;
   1455 }
   1456 
   1457 static int
   1458 slap_sasl_regexp_rewrite_config(
   1459 		struct rewrite_info	**rwinfo,
   1460 		const char		*fname,
   1461 		int			lineno,
   1462 		const char		*match,
   1463 		const char		*replace,
   1464 		const char		*context )
   1465 {
   1466 	int	rc;
   1467 	char	*argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
   1468 	struct rewrite_info *rw = *rwinfo;
   1469 
   1470 	/* init at first call */
   1471 	if ( rw == NULL ) {
   1472 		char *argvEngine[] = { "rewriteEngine", "on", NULL };
   1473 		char *argvContext[] = { "rewriteContext", NULL, NULL };
   1474 
   1475 		/* initialize rewrite engine */
   1476 		rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
   1477 
   1478 		/* switch on rewrite engine */
   1479 		rc = rewrite_parse( rw, fname, lineno, 2, argvEngine );
   1480 		if (rc != LDAP_SUCCESS) {
   1481 			goto out;
   1482 		}
   1483 
   1484 		/* create generic authid context */
   1485 		argvContext[1] = AUTHID_CONTEXT;
   1486 		rc = rewrite_parse( rw, fname, lineno, 2, argvContext );
   1487 		if (rc != LDAP_SUCCESS) {
   1488 			goto out;
   1489 		}
   1490 	}
   1491 
   1492 	argvRule[1] = (char *)match;
   1493 	argvRule[2] = (char *)replace;
   1494 	rc = rewrite_parse( rw, fname, lineno, 4, argvRule );
   1495 out:
   1496 	if (rc == LDAP_SUCCESS) {
   1497 		*rwinfo = rw;
   1498 	} else {
   1499 		rewrite_info_delete( &rw );
   1500 	}
   1501 
   1502 	return rc;
   1503 }
   1504 
   1505 int slap_sasl_regexp_config( const char *match, const char *replace, int valx )
   1506 {
   1507 	int i, rc;
   1508 	SaslRegexp_t sr;
   1509 	struct rewrite_info *rw = NULL;
   1510 
   1511 	if ( valx < 0 || valx > nSaslRegexp )
   1512 		valx = nSaslRegexp;
   1513 
   1514 	for ( i = 0; i < valx; i++) {
   1515 		rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
   1516 				SaslRegexp[i].sr_match,
   1517 				SaslRegexp[i].sr_replace,
   1518 				AUTHID_CONTEXT);
   1519 		assert( rc == 0 );
   1520 	}
   1521 
   1522 	rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
   1523 			match, replace, AUTHID_CONTEXT );
   1524 
   1525 	if ( rc == LDAP_SUCCESS ) {
   1526 		SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
   1527 				(nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
   1528 
   1529 		for ( i = nSaslRegexp; i > valx; i-- ) {
   1530 			SaslRegexp[i] = SaslRegexp[i - 1];
   1531 		}
   1532 
   1533 		SaslRegexp[i] = sr;
   1534 		SaslRegexp[i].sr_match = ch_strdup( match );
   1535 		SaslRegexp[i].sr_replace = ch_strdup( replace );
   1536 
   1537 		nSaslRegexp++;
   1538 
   1539 		for ( i = valx + 1; i < nSaslRegexp; i++ ) {
   1540 			rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
   1541 					SaslRegexp[i].sr_match,
   1542 					SaslRegexp[i].sr_replace,
   1543 					AUTHID_CONTEXT);
   1544 			assert( rc == 0 );
   1545 		}
   1546 
   1547 		slap_sasl_rewrite_destroy();
   1548 		sasl_rwinfo = rw;
   1549 	} else if ( rw ) {
   1550 		rewrite_info_delete( &rw );
   1551 	}
   1552 
   1553 	return rc;
   1554 }
   1555 
   1556 static void
   1557 slap_sasl_regexp_destroy_one( int n )
   1558 {
   1559 	ch_free( SaslRegexp[ n ].sr_match );
   1560 	ch_free( SaslRegexp[ n ].sr_replace );
   1561 }
   1562 
   1563 void
   1564 slap_sasl_regexp_destroy( void )
   1565 {
   1566 	if ( SaslRegexp ) {
   1567 		int	n;
   1568 
   1569 		for ( n = 0; n < nSaslRegexp; n++ ) {
   1570 			slap_sasl_regexp_destroy_one( n );
   1571 		}
   1572 
   1573 		ch_free( SaslRegexp );
   1574 		SaslRegexp = NULL;
   1575 		nSaslRegexp = 0;
   1576 	}
   1577 
   1578 	slap_sasl_rewrite_destroy();
   1579 }
   1580 
   1581 int slap_sasl_regexp_delete( int valx )
   1582 {
   1583 	int rc = 0;
   1584 
   1585 	if ( valx >= nSaslRegexp ) {
   1586 		rc = 1;
   1587 	} else if ( valx < 0 || nSaslRegexp == 1 ) {
   1588 		slap_sasl_regexp_destroy();
   1589 	} else {
   1590 		int i;
   1591 
   1592 		slap_sasl_regexp_destroy_one( valx );
   1593 		nSaslRegexp--;
   1594 
   1595 		for ( i = valx; i < nSaslRegexp; i++ ) {
   1596 			SaslRegexp[ i ] = SaslRegexp[ i + 1 ];
   1597 		}
   1598 
   1599 		slap_sasl_rewrite_destroy();
   1600 		for ( i = 0; i < nSaslRegexp; i++ ) {
   1601 			rc = slap_sasl_regexp_rewrite_config( &sasl_rwinfo, "sasl-regexp", 0,
   1602 					SaslRegexp[ i ].sr_match,
   1603 					SaslRegexp[ i ].sr_replace,
   1604 					AUTHID_CONTEXT );
   1605 			assert( rc == 0 );
   1606 		}
   1607 	}
   1608 
   1609 	return rc;
   1610 }
   1611 
   1612 void slap_sasl_regexp_unparse( BerVarray *out )
   1613 {
   1614 	int i;
   1615 	BerVarray bva = NULL;
   1616 	char ibuf[32], *ptr;
   1617 	struct berval idx;
   1618 
   1619 	if ( !nSaslRegexp ) return;
   1620 
   1621 	idx.bv_val = ibuf;
   1622 	bva = ch_malloc( (nSaslRegexp+1) * sizeof(struct berval) );
   1623 	BER_BVZERO(bva+nSaslRegexp);
   1624 	for ( i=0; i<nSaslRegexp; i++ ) {
   1625 		idx.bv_len = sprintf( idx.bv_val, "{%d}", i);
   1626 		bva[i].bv_len = idx.bv_len + strlen( SaslRegexp[i].sr_match ) +
   1627 			strlen( SaslRegexp[i].sr_replace ) + 5;
   1628 		bva[i].bv_val = ch_malloc( bva[i].bv_len+1 );
   1629 		ptr = lutil_strcopy( bva[i].bv_val, ibuf );
   1630 		*ptr++ = '"';
   1631 		ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_match );
   1632 		ptr = lutil_strcopy( ptr, "\" \"" );
   1633 		ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_replace );
   1634 		*ptr++ = '"';
   1635 		*ptr = '\0';
   1636 	}
   1637 	*out = bva;
   1638 }
   1639 
   1640 /* Take the passed in SASL name and attempt to convert it into an
   1641    LDAP URI to find the matching LDAP entry, using the pattern matching
   1642    strings given in the saslregexp config file directive(s) */
   1643 
   1644 static int slap_authz_regexp( struct berval *in, struct berval *out,
   1645 		int flags, void *ctx )
   1646 {
   1647 	const char	*context = AUTHID_CONTEXT;
   1648 
   1649 	if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
   1650 		return 0;
   1651 	}
   1652 
   1653 	/* FIXME: if aware of authc/authz mapping,
   1654 	 * we could use different contexts ... */
   1655 	switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
   1656 				&out->bv_val ) )
   1657 	{
   1658 	case REWRITE_REGEXEC_OK:
   1659 		if ( !BER_BVISNULL( out ) ) {
   1660 			char *val = out->bv_val;
   1661 			ber_str2bv_x( val, 0, 1, out, ctx );
   1662 			if ( val != in->bv_val ) {
   1663 				free( val );
   1664 			}
   1665 		} else {
   1666 			ber_dupbv_x( out, in, ctx );
   1667 		}
   1668 		Debug( LDAP_DEBUG_ARGS,
   1669 			"[rw] %s: \"%s\" -> \"%s\"\n",
   1670 			context, in->bv_val, out->bv_val );
   1671 		return 1;
   1672 
   1673 	case REWRITE_REGEXEC_UNWILLING:
   1674 	case REWRITE_REGEXEC_ERR:
   1675 	default:
   1676 		return 0;
   1677 	}
   1678 
   1679 }
   1680 
   1681 /* This callback actually does some work...*/
   1682 static int sasl_sc_sasl2dn( Operation *op, SlapReply *rs )
   1683 {
   1684 	struct berval *ndn = op->o_callback->sc_private;
   1685 
   1686 	if ( rs->sr_type != REP_SEARCH ) return LDAP_SUCCESS;
   1687 
   1688 	/* We only want to be called once */
   1689 	if ( !BER_BVISNULL( ndn ) ) {
   1690 		op->o_tmpfree( ndn->bv_val, op->o_tmpmemctx );
   1691 		BER_BVZERO( ndn );
   1692 
   1693 		Debug( LDAP_DEBUG_TRACE,
   1694 			"%s: slap_sc_sasl2dn: search DN returned more than 1 entry\n",
   1695 			op->o_log_prefix );
   1696 		return LDAP_UNAVAILABLE; /* short-circuit the search */
   1697 	}
   1698 
   1699 	ber_dupbv_x( ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
   1700 	return LDAP_SUCCESS;
   1701 }
   1702 
   1703 
   1704 typedef struct smatch_info {
   1705 	struct berval *dn;
   1706 	int match;
   1707 } smatch_info;
   1708 
   1709 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
   1710 {
   1711 	smatch_info *sm = o->o_callback->sc_private;
   1712 
   1713 	if (rs->sr_type != REP_SEARCH) return 0;
   1714 
   1715 	if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
   1716 		sm->match = 1;
   1717 		return LDAP_UNAVAILABLE;	/* short-circuit the search */
   1718 	}
   1719 
   1720 	return 0;
   1721 }
   1722 
   1723 int
   1724 slap_sasl_matches( Operation *op, BerVarray rules,
   1725 		struct berval *assertDN, struct berval *authc )
   1726 {
   1727 	int	rc = LDAP_INAPPROPRIATE_AUTH;
   1728 
   1729 	if ( rules != NULL ) {
   1730 		int	i;
   1731 
   1732 		for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
   1733 			rc = slap_sasl_match( op, &rules[i], assertDN, authc );
   1734 			if ( rc == LDAP_SUCCESS ) break;
   1735 		}
   1736 	}
   1737 
   1738 	return rc;
   1739 }
   1740 
   1741 /*
   1742  * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
   1743  * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
   1744  * the rule must be used as an internal search for entries. If that search
   1745  * returns the *assertDN entry, the match is successful.
   1746  *
   1747  * The assertDN should not have the dn: prefix
   1748  */
   1749 
   1750 static int
   1751 slap_sasl_match( Operation *opx, struct berval *rule,
   1752 	struct berval *assertDN, struct berval *authc )
   1753 {
   1754 	int rc;
   1755 	regex_t reg;
   1756 	smatch_info sm;
   1757 	slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
   1758 	Operation op = {0};
   1759 	SlapReply rs = {REP_RESULT};
   1760 	struct berval base = BER_BVNULL;
   1761 
   1762 	sm.dn = assertDN;
   1763 	sm.match = 0;
   1764 	cb.sc_private = &sm;
   1765 
   1766 	Debug( LDAP_DEBUG_TRACE,
   1767 	   "===>slap_sasl_match: comparing DN %s to rule %s\n",
   1768 		assertDN->bv_len ? assertDN->bv_val : "(null)", rule->bv_val );
   1769 
   1770 	/* NOTE: don't normalize rule if authz syntax is enabled */
   1771 	rc = slap_parseURI( opx, rule, &base, &op.o_req_ndn,
   1772 		&op.ors_scope, &op.ors_filter, &op.ors_filterstr, 0 );
   1773 
   1774 	if( rc != LDAP_SUCCESS ) goto CONCLUDED;
   1775 
   1776 	switch ( op.ors_scope ) {
   1777 	case LDAP_X_SCOPE_EXACT:
   1778 exact_match:
   1779 		if ( dn_match( &op.o_req_ndn, assertDN ) ) {
   1780 			rc = LDAP_SUCCESS;
   1781 		} else {
   1782 			rc = LDAP_INAPPROPRIATE_AUTH;
   1783 		}
   1784 		goto CONCLUDED;
   1785 
   1786 	case LDAP_X_SCOPE_CHILDREN:
   1787 	case LDAP_X_SCOPE_SUBTREE:
   1788 	case LDAP_X_SCOPE_ONELEVEL:
   1789 	{
   1790 		int	d = assertDN->bv_len - op.o_req_ndn.bv_len;
   1791 
   1792 		rc = LDAP_INAPPROPRIATE_AUTH;
   1793 
   1794 		if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
   1795 			goto exact_match;
   1796 
   1797 		} else if ( d > 0 ) {
   1798 			struct berval bv;
   1799 
   1800 			/* leave room for at least one char of attributeType,
   1801 			 * one for '=' and one for ',' */
   1802 			if ( d < (int) STRLENOF( "x=,") ) {
   1803 				goto CONCLUDED;
   1804 			}
   1805 
   1806 			bv.bv_len = op.o_req_ndn.bv_len;
   1807 			bv.bv_val = assertDN->bv_val + d;
   1808 
   1809 			if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
   1810 				switch ( op.ors_scope ) {
   1811 				case LDAP_X_SCOPE_SUBTREE:
   1812 				case LDAP_X_SCOPE_CHILDREN:
   1813 					rc = LDAP_SUCCESS;
   1814 					break;
   1815 
   1816 				case LDAP_X_SCOPE_ONELEVEL:
   1817 				{
   1818 					struct berval	pdn;
   1819 
   1820 					dnParent( assertDN, &pdn );
   1821 					/* the common portion of the DN
   1822 					 * already matches, so only check
   1823 					 * if parent DN of assertedDN
   1824 					 * is all the pattern */
   1825 					if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
   1826 						rc = LDAP_SUCCESS;
   1827 					}
   1828 					break;
   1829 				}
   1830 				default:
   1831 					/* at present, impossible */
   1832 					assert( 0 );
   1833 				}
   1834 			}
   1835 		}
   1836 		goto CONCLUDED;
   1837 	}
   1838 
   1839 	case LDAP_X_SCOPE_REGEX:
   1840 		rc = regcomp(&reg, op.o_req_ndn.bv_val,
   1841 			REG_EXTENDED|REG_ICASE|REG_NOSUB);
   1842 		if ( rc == 0 ) {
   1843 			rc = regexec(&reg, assertDN->bv_val, 0, NULL, 0);
   1844 			regfree( &reg );
   1845 		}
   1846 		if ( rc == 0 ) {
   1847 			rc = LDAP_SUCCESS;
   1848 		} else {
   1849 			rc = LDAP_INAPPROPRIATE_AUTH;
   1850 		}
   1851 		goto CONCLUDED;
   1852 
   1853 	case LDAP_X_SCOPE_GROUP: {
   1854 		char	*tmp;
   1855 
   1856 		/* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
   1857 		 * we need to append the <assertDN> so that the <group_dn> is searched
   1858 		 * with scope "base", and the filter ensures that <assertDN> is
   1859 		 * member of the group */
   1860 		tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
   1861 			assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
   1862 		if ( tmp == NULL ) {
   1863 			rc = LDAP_NO_MEMORY;
   1864 			goto CONCLUDED;
   1865 		}
   1866 		op.ors_filterstr.bv_val = tmp;
   1867 
   1868 		tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
   1869 		tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
   1870 
   1871 		/* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
   1872 		op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
   1873 		if ( op.ors_filter == NULL ) {
   1874 			rc = LDAP_PROTOCOL_ERROR;
   1875 			goto CONCLUDED;
   1876 		}
   1877 		op.ors_scope = LDAP_SCOPE_BASE;
   1878 
   1879 		/* hijack match DN: use that of the group instead of the assertDN;
   1880 		 * assertDN is now in the filter */
   1881 		sm.dn = &op.o_req_ndn;
   1882 
   1883 		/* do the search */
   1884 		break;
   1885 		}
   1886 
   1887 	case LDAP_X_SCOPE_USERS:
   1888 		if ( !BER_BVISEMPTY( assertDN ) ) {
   1889 			rc = LDAP_SUCCESS;
   1890 		} else {
   1891 			rc = LDAP_INAPPROPRIATE_AUTH;
   1892 		}
   1893 		goto CONCLUDED;
   1894 
   1895 	default:
   1896 		break;
   1897 	}
   1898 
   1899 	/* Must run an internal search. */
   1900 	if ( op.ors_filter == NULL ) {
   1901 		rc = LDAP_FILTER_ERROR;
   1902 		goto CONCLUDED;
   1903 	}
   1904 
   1905 	Debug( LDAP_DEBUG_TRACE,
   1906 	   "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
   1907 	   op.o_req_ndn.bv_val, op.ors_scope );
   1908 
   1909 	op.o_bd = select_backend( &op.o_req_ndn, 1 );
   1910 	if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
   1911 		rc = LDAP_INAPPROPRIATE_AUTH;
   1912 		goto CONCLUDED;
   1913 	}
   1914 
   1915 	op.o_hdr = opx->o_hdr;
   1916 	op.o_tag = LDAP_REQ_SEARCH;
   1917 	op.o_ndn = *authc;
   1918 	op.o_callback = &cb;
   1919 	slap_op_time( &op.o_time, &op.o_tincr );
   1920 	op.o_do_not_cache = 1;
   1921 	op.o_is_auth_check = 1;
   1922 	/* use req_ndn as req_dn instead of non-pretty base of uri */
   1923 	if( !BER_BVISNULL( &base ) ) {
   1924 		ch_free( base.bv_val );
   1925 		/* just in case... */
   1926 		BER_BVZERO( &base );
   1927 	}
   1928 	ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
   1929 	op.ors_deref = LDAP_DEREF_NEVER;
   1930 	op.ors_slimit = 1;
   1931 	op.ors_tlimit = SLAP_NO_LIMIT;
   1932 	op.ors_attrs = slap_anlist_no_attrs;
   1933 	op.ors_attrsonly = 1;
   1934 
   1935 	op.o_bd->be_search( &op, &rs );
   1936 
   1937 	if (sm.match) {
   1938 		rc = LDAP_SUCCESS;
   1939 	} else {
   1940 		rc = LDAP_INAPPROPRIATE_AUTH;
   1941 	}
   1942 
   1943 CONCLUDED:
   1944 	if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
   1945 	if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
   1946 	if( op.ors_filter ) filter_free_x( opx, op.ors_filter, 1 );
   1947 	if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
   1948 
   1949 	Debug( LDAP_DEBUG_TRACE,
   1950 	   "<===slap_sasl_match: comparison returned %d\n", rc );
   1951 
   1952 	return( rc );
   1953 }
   1954 
   1955 
   1956 /*
   1957  * This function answers the question, "Can this ID authorize to that ID?",
   1958  * based on authorization rules. The rules are stored in the *searchDN, in the
   1959  * attribute named by *attr. If any of those rules map to the *assertDN, the
   1960  * authorization is approved.
   1961  *
   1962  * The DNs should not have the dn: prefix
   1963  */
   1964 static int
   1965 slap_sasl_check_authz( Operation *op,
   1966 	struct berval *searchDN,
   1967 	struct berval *assertDN,
   1968 	AttributeDescription *ad,
   1969 	struct berval *authc )
   1970 {
   1971 	int		rc,
   1972 			do_not_cache = op->o_do_not_cache;
   1973 	BerVarray	vals = NULL;
   1974 
   1975 	Debug( LDAP_DEBUG_TRACE,
   1976 	   "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
   1977 	   assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
   1978 
   1979 	/* ITS#4760: don't cache group access */
   1980 	op->o_do_not_cache = 1;
   1981 	rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
   1982 	op->o_do_not_cache = do_not_cache;
   1983 	if( rc != LDAP_SUCCESS ) goto COMPLETE;
   1984 
   1985 	/* Check if the *assertDN matches any *vals */
   1986 	rc = slap_sasl_matches( op, vals, assertDN, authc );
   1987 
   1988 COMPLETE:
   1989 	if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
   1990 
   1991 	Debug( LDAP_DEBUG_TRACE,
   1992 	   "<==slap_sasl_check_authz: %s check returning %d\n",
   1993 		ad->ad_cname.bv_val, rc );
   1994 
   1995 	return( rc );
   1996 }
   1997 
   1998 /*
   1999  * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
   2000  * return the LDAP DN to which it matches. The SASL regexp rules in the config
   2001  * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
   2002  * search with scope=base), just return the URI (or its searchbase). Otherwise
   2003  * an internal search must be done, and if that search returns exactly one
   2004  * entry, return the DN of that one entry.
   2005  */
   2006 void
   2007 slap_sasl2dn(
   2008 	Operation	*opx,
   2009 	struct berval	*saslname,
   2010 	struct berval	*sasldn,
   2011 	int		flags )
   2012 {
   2013 	int rc;
   2014 	slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
   2015 	Operation op = {0};
   2016 	SlapReply rs = {REP_RESULT};
   2017 	struct berval regout = BER_BVNULL;
   2018 	struct berval base = BER_BVNULL;
   2019 
   2020 	Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
   2021 		"converting SASL name %s to a DN\n",
   2022 		saslname->bv_val );
   2023 
   2024 	BER_BVZERO( sasldn );
   2025 	cb.sc_private = sasldn;
   2026 
   2027 	/* Convert the SASL name into a minimal URI */
   2028 	if( !slap_authz_regexp( saslname, &regout, flags, opx->o_tmpmemctx ) ) {
   2029 		goto FINISHED;
   2030 	}
   2031 
   2032 	/* NOTE: always normalize regout because it results
   2033 	 * from string submatch expansion */
   2034 	rc = slap_parseURI( opx, &regout, &base, &op.o_req_ndn,
   2035 		&op.ors_scope, &op.ors_filter, &op.ors_filterstr, 1 );
   2036 	if ( !BER_BVISNULL( &regout ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
   2037 	if ( rc != LDAP_SUCCESS ) {
   2038 		goto FINISHED;
   2039 	}
   2040 
   2041 	/* Must do an internal search */
   2042 	op.o_bd = select_backend( &op.o_req_ndn, 1 );
   2043 
   2044 	switch ( op.ors_scope ) {
   2045 	case LDAP_X_SCOPE_EXACT:
   2046 		*sasldn = op.o_req_ndn;
   2047 		BER_BVZERO( &op.o_req_ndn );
   2048 		/* intentionally continue to next case */
   2049 
   2050 	case LDAP_X_SCOPE_REGEX:
   2051 	case LDAP_X_SCOPE_SUBTREE:
   2052 	case LDAP_X_SCOPE_CHILDREN:
   2053 	case LDAP_X_SCOPE_ONELEVEL:
   2054 	case LDAP_X_SCOPE_GROUP:
   2055 	case LDAP_X_SCOPE_USERS:
   2056 		/* correctly parsed, but illegal */
   2057 		goto FINISHED;
   2058 
   2059 	case LDAP_SCOPE_BASE:
   2060 	case LDAP_SCOPE_ONELEVEL:
   2061 	case LDAP_SCOPE_SUBTREE:
   2062 	case LDAP_SCOPE_SUBORDINATE:
   2063 		/* do a search */
   2064 		break;
   2065 
   2066 	default:
   2067 		/* catch unhandled cases (there shouldn't be) */
   2068 		assert( 0 );
   2069 	}
   2070 
   2071 	Debug( LDAP_DEBUG_TRACE,
   2072 		"slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
   2073 		op.o_req_ndn.bv_val, op.ors_scope );
   2074 
   2075 	if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
   2076 		goto FINISHED;
   2077 	}
   2078 
   2079 	/* Must run an internal search. */
   2080 	if ( op.ors_filter == NULL ) {
   2081 		rc = LDAP_FILTER_ERROR;
   2082 		goto FINISHED;
   2083 	}
   2084 
   2085 	op.o_hdr = opx->o_hdr;
   2086 	op.o_tag = LDAP_REQ_SEARCH;
   2087 	op.o_ndn = opx->o_conn->c_ndn;
   2088 	op.o_callback = &cb;
   2089 	slap_op_time( &op.o_time, &op.o_tincr );
   2090 	op.o_do_not_cache = 1;
   2091 	op.o_is_auth_check = 1;
   2092 	op.ors_deref = LDAP_DEREF_NEVER;
   2093 	op.ors_slimit = 1;
   2094 	op.ors_tlimit = SLAP_NO_LIMIT;
   2095 	op.ors_attrs = slap_anlist_no_attrs;
   2096 	op.ors_attrsonly = 1;
   2097 	/* use req_ndn as req_dn instead of non-pretty base of uri */
   2098 	if( !BER_BVISNULL( &base ) ) {
   2099 		ch_free( base.bv_val );
   2100 		/* just in case... */
   2101 		BER_BVZERO( &base );
   2102 	}
   2103 	ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
   2104 
   2105 	op.o_bd->be_search( &op, &rs );
   2106 
   2107 FINISHED:
   2108 	if( opx == opx->o_conn->c_sasl_bindop && !BER_BVISEMPTY( sasldn ) ) {
   2109 		opx->o_conn->c_authz_backend = op.o_bd;
   2110 	}
   2111 	if( !BER_BVISNULL( &op.o_req_dn ) ) {
   2112 		slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
   2113 	}
   2114 	if( !BER_BVISNULL( &op.o_req_ndn ) ) {
   2115 		slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
   2116 	}
   2117 	if( op.ors_filter ) {
   2118 		filter_free_x( opx, op.ors_filter, 1 );
   2119 	}
   2120 	if( !BER_BVISNULL( &op.ors_filterstr ) ) {
   2121 		ch_free( op.ors_filterstr.bv_val );
   2122 	}
   2123 
   2124 	Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
   2125 		!BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>" );
   2126 
   2127 	return;
   2128 }
   2129 
   2130 
   2131 /* Check if a bind can SASL authorize to another identity.
   2132  * The DNs should not have the dn: prefix
   2133  */
   2134 
   2135 int slap_sasl_authorized( Operation *op,
   2136 	struct berval *authcDN, struct berval *authzDN )
   2137 {
   2138 	int rc = LDAP_INAPPROPRIATE_AUTH;
   2139 
   2140 	/* User binding as anonymous */
   2141 	if ( !authzDN || !authzDN->bv_len || !authzDN->bv_val ) {
   2142 		rc = LDAP_SUCCESS;
   2143 		goto DONE;
   2144 	}
   2145 
   2146 	/* User is anonymous */
   2147 	if ( !authcDN || !authcDN->bv_len || !authcDN->bv_val ) {
   2148 		goto DONE;
   2149 	}
   2150 
   2151 	Debug( LDAP_DEBUG_TRACE,
   2152 	   "==>slap_sasl_authorized: can %s become %s?\n",
   2153 		authcDN->bv_len ? authcDN->bv_val : "(null)",
   2154 		authzDN->bv_len ? authzDN->bv_val : "(null)" );
   2155 
   2156 	/* If person is authorizing to self, succeed */
   2157 	if ( dn_match( authcDN, authzDN ) ) {
   2158 		rc = LDAP_SUCCESS;
   2159 		goto DONE;
   2160 	}
   2161 
   2162 	/* Allow the manager to authorize as any DN in its own DBs. */
   2163 	{
   2164 		Backend *zbe = select_backend( authzDN, 1 );
   2165 		if ( zbe && be_isroot_dn( zbe, authcDN )) {
   2166 			rc = LDAP_SUCCESS;
   2167 			goto DONE;
   2168 		}
   2169 	}
   2170 
   2171 	/* Check source rules */
   2172 	if( authz_policy & SASL_AUTHZ_TO ) {
   2173 		rc = slap_sasl_check_authz( op, authcDN, authzDN,
   2174 			slap_schema.si_ad_saslAuthzTo, authcDN );
   2175 		if(( rc == LDAP_SUCCESS ) ^ (( authz_policy & SASL_AUTHZ_AND) != 0)) {
   2176 			if( rc != LDAP_SUCCESS )
   2177 				rc = LDAP_INAPPROPRIATE_AUTH;
   2178 			goto DONE;
   2179 		}
   2180 	}
   2181 
   2182 	/* Check destination rules */
   2183 	if( authz_policy & SASL_AUTHZ_FROM ) {
   2184 		rc = slap_sasl_check_authz( op, authzDN, authcDN,
   2185 			slap_schema.si_ad_saslAuthzFrom, authcDN );
   2186 		if( rc == LDAP_SUCCESS ) {
   2187 			goto DONE;
   2188 		}
   2189 	}
   2190 
   2191 	rc = LDAP_INAPPROPRIATE_AUTH;
   2192 
   2193 DONE:
   2194 
   2195 	Debug( LDAP_DEBUG_TRACE,
   2196 		"<== slap_sasl_authorized: return %d\n", rc );
   2197 
   2198 	return( rc );
   2199 }
   2200