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