1/*	$NetBSD: compare.c,v 1.3 2021/08/14 16:14:58 christos Exp $	*/
2
3/* $OpenLDAP$ */
4/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 1998-2021 The OpenLDAP Foundation.
7 * 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.3 2021/08/14 16:14:58 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
39int
40do_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
138cleanup:;
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
148int
149fe_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
354cleanup:;
355	op->o_bd = bd;
356	return rs->sr_err;
357}
358
359int 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
408done:
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