Home | History | Annotate | Line # | Download | only in slapd
      1 /*	$NetBSD: compare.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  * All rights reserved.
      8  *
      9  * Redistribution and use in source and binary forms, with or without
     10  * modification, are permitted only as authorized by the OpenLDAP
     11  * Public License.
     12  *
     13  * A copy of this license is available in the file LICENSE in the
     14  * top-level directory of the distribution or, alternatively, at
     15  * <http://www.OpenLDAP.org/license.html>.
     16  */
     17 /* Portions Copyright (c) 1995 Regents of the University of Michigan.
     18  * All rights reserved.
     19  *
     20  * Redistribution and use in source and binary forms are permitted
     21  * provided that this notice is preserved and that due credit is given
     22  * to the University of Michigan at Ann Arbor. The name of the University
     23  * may not be used to endorse or promote products derived from this
     24  * software without specific prior written permission. This software
     25  * is provided ``as is'' without express or implied warranty.
     26  */
     27 
     28 #include <sys/cdefs.h>
     29 __RCSID("$NetBSD: compare.c,v 1.4 2025/09/05 21:16:25 christos Exp $");
     30 
     31 #include "portable.h"
     32 
     33 #include <stdio.h>
     34 #include <ac/socket.h>
     35 #include <ac/string.h>
     36 
     37 #include "slap.h"
     38 
     39 int
     40 do_compare(
     41     Operation	*op,
     42     SlapReply	*rs )
     43 {
     44 	struct berval dn = BER_BVNULL;
     45 	struct berval desc = BER_BVNULL;
     46 	struct berval value = BER_BVNULL;
     47 	AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
     48 
     49 	Debug( LDAP_DEBUG_TRACE, "%s do_compare\n",
     50 		op->o_log_prefix );
     51 	/*
     52 	 * Parse the compare request.  It looks like this:
     53 	 *
     54 	 *	CompareRequest := [APPLICATION 14] SEQUENCE {
     55 	 *		entry	DistinguishedName,
     56 	 *		ava	SEQUENCE {
     57 	 *			type	AttributeType,
     58 	 *			value	AttributeValue
     59 	 *		}
     60 	 *	}
     61 	 */
     62 
     63 	if ( ber_scanf( op->o_ber, "{m" /*}*/, &dn ) == LBER_ERROR ) {
     64 		Debug( LDAP_DEBUG_ANY, "%s do_compare: ber_scanf failed\n",
     65 			op->o_log_prefix );
     66 		send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
     67 		return SLAPD_DISCONNECT;
     68 	}
     69 
     70 	if ( ber_scanf( op->o_ber, "{mm}", &desc, &value ) == LBER_ERROR ) {
     71 		Debug( LDAP_DEBUG_ANY, "%s do_compare: get ava failed\n",
     72 			op->o_log_prefix );
     73 		send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
     74 		return SLAPD_DISCONNECT;
     75 	}
     76 
     77 	if ( ber_scanf( op->o_ber, /*{*/ "}" ) == LBER_ERROR ) {
     78 		Debug( LDAP_DEBUG_ANY, "%s do_compare: ber_scanf failed\n",
     79 			op->o_log_prefix );
     80 		send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
     81 		return SLAPD_DISCONNECT;
     82 	}
     83 
     84 	if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
     85 		Debug( LDAP_DEBUG_ANY, "%s do_compare: get_ctrls failed\n",
     86 			op->o_log_prefix );
     87 		goto cleanup;
     88 	}
     89 
     90 	rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn,
     91 		op->o_tmpmemctx );
     92 	if( rs->sr_err != LDAP_SUCCESS ) {
     93 		Debug( LDAP_DEBUG_ANY, "%s do_compare: invalid dn (%s)\n",
     94 			op->o_log_prefix, dn.bv_val );
     95 		send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
     96 		goto cleanup;
     97 	}
     98 
     99 	Debug( LDAP_DEBUG_STATS,
    100 		"%s CMP dn=\"%s\" attr=\"%s\"\n",
    101 		op->o_log_prefix, op->o_req_dn.bv_val,
    102 		desc.bv_val );
    103 
    104 	rs->sr_err = slap_bv2ad( &desc, &ava.aa_desc, &rs->sr_text );
    105 	if( rs->sr_err != LDAP_SUCCESS ) {
    106 		rs->sr_err = slap_bv2undef_ad( &desc, &ava.aa_desc,
    107 				&rs->sr_text,
    108 				SLAP_AD_PROXIED|SLAP_AD_NOINSERT );
    109 		if( rs->sr_err != LDAP_SUCCESS ) {
    110 			send_ldap_result( op, rs );
    111 			goto cleanup;
    112 		}
    113 	}
    114 
    115 	rs->sr_err = asserted_value_validate_normalize( ava.aa_desc,
    116 		ava.aa_desc->ad_type->sat_equality,
    117 		SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
    118 		&value, &ava.aa_value, &rs->sr_text, op->o_tmpmemctx );
    119 	if( rs->sr_err != LDAP_SUCCESS ) {
    120 		send_ldap_result( op, rs );
    121 		goto cleanup;
    122 	}
    123 
    124 	op->orc_ava = &ava;
    125 
    126 	Debug( LDAP_DEBUG_ARGS,
    127 		"do_compare: dn (%s) attr (%s) value (%s)\n",
    128 		op->o_req_dn.bv_val,
    129 		ava.aa_desc->ad_cname.bv_val, ava.aa_value.bv_val );
    130 
    131 	op->o_bd = frontendDB;
    132 	rs->sr_err = frontendDB->be_compare( op, rs );
    133 	if ( rs->sr_err == SLAPD_ASYNCOP ) {
    134 		/* skip cleanup */
    135 		return rs->sr_err;
    136 	}
    137 
    138 cleanup:;
    139 	op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
    140 	op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
    141 	if ( !BER_BVISNULL( &ava.aa_value ) ) {
    142 		op->o_tmpfree( ava.aa_value.bv_val, op->o_tmpmemctx );
    143 	}
    144 
    145 	return rs->sr_err;
    146 }
    147 
    148 int
    149 fe_op_compare( Operation *op, SlapReply *rs )
    150 {
    151 	Entry			*entry = NULL;
    152 	AttributeAssertion	*ava = op->orc_ava;
    153 	BackendDB		*bd = op->o_bd;
    154 
    155 	if( strcasecmp( op->o_req_ndn.bv_val, LDAP_ROOT_DSE ) == 0 ) {
    156 		if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
    157 			send_ldap_result( op, rs );
    158 			goto cleanup;
    159 		}
    160 
    161 		rs->sr_err = root_dse_info( op->o_conn, &entry, &rs->sr_text );
    162 		if( rs->sr_err != LDAP_SUCCESS ) {
    163 			send_ldap_result( op, rs );
    164 			goto cleanup;
    165 		}
    166 
    167 	} else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) {
    168 		if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
    169 			send_ldap_result( op, rs );
    170 			rs->sr_err = 0;
    171 			goto cleanup;
    172 		}
    173 
    174 		rs->sr_err = schema_info( &entry, &rs->sr_text );
    175 		if( rs->sr_err != LDAP_SUCCESS ) {
    176 			send_ldap_result( op, rs );
    177 			rs->sr_err = 0;
    178 			goto cleanup;
    179 		}
    180 	}
    181 
    182 	if( entry ) {
    183 		rs->sr_err = slap_compare_entry( op, entry, ava );
    184 		entry_free( entry );
    185 
    186 		send_ldap_result( op, rs );
    187 
    188 		if( rs->sr_err == LDAP_COMPARE_TRUE ||
    189 			rs->sr_err == LDAP_COMPARE_FALSE )
    190 		{
    191 			rs->sr_err = LDAP_SUCCESS;
    192 		}
    193 
    194 		goto cleanup;
    195 	}
    196 
    197 	/*
    198 	 * We could be serving multiple database backends.  Select the
    199 	 * appropriate one, or send a referral to our "referral server"
    200 	 * if we don't hold it.
    201 	 */
    202 	op->o_bd = select_backend( &op->o_req_ndn, 0 );
    203 	if ( op->o_bd == NULL ) {
    204 		rs->sr_ref = referral_rewrite( default_referral,
    205 			NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
    206 
    207 		rs->sr_err = LDAP_REFERRAL;
    208 		if (!rs->sr_ref) rs->sr_ref = default_referral;
    209 		op->o_bd = bd;
    210 		send_ldap_result( op, rs );
    211 
    212 		if (rs->sr_ref != default_referral) ber_bvarray_free( rs->sr_ref );
    213 		rs->sr_err = 0;
    214 		goto cleanup;
    215 	}
    216 
    217 	/* check restrictions */
    218 	if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
    219 		send_ldap_result( op, rs );
    220 		goto cleanup;
    221 	}
    222 
    223 	/* check for referrals */
    224 	if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
    225 		goto cleanup;
    226 	}
    227 
    228 	if ( SLAP_SHADOW(op->o_bd) && get_dontUseCopy(op) ) {
    229 		/* don't use shadow copy */
    230 		send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
    231 			"copy not used" );
    232 
    233 	} else if ( ava->aa_desc == slap_schema.si_ad_entryDN ) {
    234 		send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
    235 			"entryDN compare not supported" );
    236 
    237 	} else if ( ava->aa_desc == slap_schema.si_ad_subschemaSubentry ) {
    238 		send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
    239 			"subschemaSubentry compare not supported" );
    240 
    241 #ifndef SLAP_COMPARE_IN_FRONTEND
    242 	} else if ( ava->aa_desc == slap_schema.si_ad_hasSubordinates
    243 		&& op->o_bd->be_has_subordinates )
    244 	{
    245 		int	rc, hasSubordinates = LDAP_SUCCESS;
    246 
    247 		rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &entry );
    248 		if ( rc == 0 && entry ) {
    249 			if ( ! access_allowed( op, entry,
    250 				ava->aa_desc, &ava->aa_value, ACL_COMPARE, NULL ) )
    251 			{
    252 				rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
    253 
    254 			} else {
    255 				rc = rs->sr_err = op->o_bd->be_has_subordinates( op,
    256 						entry, &hasSubordinates );
    257 				be_entry_release_r( op, entry );
    258 			}
    259 		}
    260 
    261 		if ( rc == 0 ) {
    262 			int	asserted;
    263 
    264 			asserted = bvmatch( &ava->aa_value, &slap_true_bv )
    265 				? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE;
    266 			if ( hasSubordinates == asserted ) {
    267 				rs->sr_err = LDAP_COMPARE_TRUE;
    268 
    269 			} else {
    270 				rs->sr_err = LDAP_COMPARE_FALSE;
    271 			}
    272 
    273 		} else {
    274 			/* return error only if "disclose"
    275 			 * is granted on the object */
    276 			if ( backend_access( op, NULL, &op->o_req_ndn,
    277 					slap_schema.si_ad_entry,
    278 					NULL, ACL_DISCLOSE, NULL ) == LDAP_INSUFFICIENT_ACCESS )
    279 			{
    280 				rs->sr_err = LDAP_NO_SUCH_OBJECT;
    281 			}
    282 		}
    283 
    284 		send_ldap_result( op, rs );
    285 
    286 		if ( rc == 0 ) {
    287 			rs->sr_err = LDAP_SUCCESS;
    288 		}
    289 
    290 	} else if ( op->o_bd->be_compare ) {
    291 		rs->sr_err = op->o_bd->be_compare( op, rs );
    292 
    293 #endif /* ! SLAP_COMPARE_IN_FRONTEND */
    294 	} else {
    295 		rs->sr_err = SLAP_CB_CONTINUE;
    296 	}
    297 
    298 	if ( rs->sr_err == SLAP_CB_CONTINUE ) {
    299 		/* do our best to compare that AVA
    300 		 *
    301 		 * NOTE: this code is used only
    302 		 * if SLAP_COMPARE_IN_FRONTEND
    303 		 * is #define'd (it's not by default)
    304 		 * or if op->o_bd->be_compare is NULL.
    305 		 *
    306 		 * FIXME: one potential issue is that
    307 		 * if SLAP_COMPARE_IN_FRONTEND overlays
    308 		 * are not executed for compare. */
    309 		BerVarray	vals = NULL;
    310 		int		rc = LDAP_OTHER;
    311 
    312 		rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn,
    313 				ava->aa_desc, &vals, ACL_COMPARE );
    314 		switch ( rs->sr_err ) {
    315 		default:
    316 			/* return error only if "disclose"
    317 			 * is granted on the object */
    318 			if ( backend_access( op, NULL, &op->o_req_ndn,
    319 					slap_schema.si_ad_entry,
    320 					NULL, ACL_DISCLOSE, NULL )
    321 					== LDAP_INSUFFICIENT_ACCESS )
    322 			{
    323 				rs->sr_err = LDAP_NO_SUCH_OBJECT;
    324 			}
    325 			break;
    326 
    327 		case LDAP_SUCCESS:
    328 			if ( value_find_ex( op->oq_compare.rs_ava->aa_desc,
    329 				SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
    330 					SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
    331 				vals, &ava->aa_value, op->o_tmpmemctx ) == 0 )
    332 			{
    333 				rs->sr_err = LDAP_COMPARE_TRUE;
    334 				break;
    335 
    336 			} else {
    337 				rs->sr_err = LDAP_COMPARE_FALSE;
    338 			}
    339 			rc = LDAP_SUCCESS;
    340 			break;
    341 		}
    342 
    343 		send_ldap_result( op, rs );
    344 
    345 		if ( rc == 0 ) {
    346 			rs->sr_err = LDAP_SUCCESS;
    347 		}
    348 
    349 		if ( vals ) {
    350 			ber_bvarray_free_x( vals, op->o_tmpmemctx );
    351 		}
    352 	}
    353 
    354 cleanup:;
    355 	op->o_bd = bd;
    356 	return rs->sr_err;
    357 }
    358 
    359 int slap_compare_entry(
    360 	Operation *op,
    361 	Entry *e,
    362 	AttributeAssertion *ava )
    363 {
    364 	int rc = LDAP_COMPARE_FALSE;
    365 	Attribute *a;
    366 
    367 	if ( ! access_allowed( op, e,
    368 		ava->aa_desc, &ava->aa_value, ACL_COMPARE, NULL ) )
    369 	{
    370 		rc = LDAP_INSUFFICIENT_ACCESS;
    371 		goto done;
    372 	}
    373 
    374 	if ( get_assert( op ) &&
    375 		( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
    376 	{
    377 		rc = LDAP_ASSERTION_FAILED;
    378 		goto done;
    379 	}
    380 
    381 	a = attrs_find( e->e_attrs, ava->aa_desc );
    382 	if( a == NULL ) {
    383 		rc = LDAP_NO_SUCH_ATTRIBUTE;
    384 		goto done;
    385 	}
    386 
    387 	for(;
    388 		a != NULL;
    389 		a = attrs_find( a->a_next, ava->aa_desc ))
    390 	{
    391 		if (( ava->aa_desc != a->a_desc ) && ! access_allowed( op,
    392 			e, a->a_desc, &ava->aa_value, ACL_COMPARE, NULL ) )
    393 		{
    394 			rc = LDAP_INSUFFICIENT_ACCESS;
    395 			break;
    396 		}
    397 
    398 		if ( attr_valfind( a,
    399 			SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
    400 				SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
    401 			&ava->aa_value, NULL, op->o_tmpmemctx ) == 0 )
    402 		{
    403 			rc = LDAP_COMPARE_TRUE;
    404 			break;
    405 		}
    406 	}
    407 
    408 done:
    409 	if( rc != LDAP_COMPARE_TRUE && rc != LDAP_COMPARE_FALSE ) {
    410 		if ( ! access_allowed( op, e,
    411 			slap_schema.si_ad_entry, NULL, ACL_DISCLOSE, NULL ) )
    412 		{
    413 			rc = LDAP_NO_SUCH_OBJECT;
    414 		}
    415 	}
    416 
    417 	return rc;
    418 }
    419