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