xref: /netbsd-src/external/bsd/openldap/dist/servers/slapd/aci.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: aci.c,v 1.1.1.6 2018/02/06 01:53:13 christos Exp $	*/
2 
3 /* aci.c - routines to parse and check acl's */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6  *
7  * Copyright 1998-2017 The OpenLDAP Foundation.
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 /* Portions Copyright (c) 1995 Regents of the University of Michigan.
19  * All rights reserved.
20  *
21  * Redistribution and use in source and binary forms are permitted
22  * provided that this notice is preserved and that due credit is given
23  * to the University of Michigan at Ann Arbor. The name of the University
24  * may not be used to endorse or promote products derived from this
25  * software without specific prior written permission. This software
26  * is provided ``as is'' without express or implied warranty.
27  */
28 
29 #include <sys/cdefs.h>
30 __RCSID("$NetBSD: aci.c,v 1.1.1.6 2018/02/06 01:53:13 christos Exp $");
31 
32 #include "portable.h"
33 
34 #ifdef SLAPD_ACI_ENABLED
35 
36 #include <stdio.h>
37 
38 #include <ac/ctype.h>
39 #include <ac/regex.h>
40 #include <ac/socket.h>
41 #include <ac/string.h>
42 #include <ac/unistd.h>
43 
44 #include "slap.h"
45 #include "lber_pvt.h"
46 #include "lutil.h"
47 
48 /* use most appropriate size */
49 #define ACI_BUF_SIZE 			1024
50 
51 /* move to "stable" when no longer experimental */
52 #define SLAPD_ACI_SYNTAX		"1.3.6.1.4.1.4203.666.2.1"
53 
54 /* change this to "OpenLDAPset" */
55 #define SLAPD_ACI_SET_ATTR		"template"
56 
57 typedef enum slap_aci_scope_t {
58 	SLAP_ACI_SCOPE_ENTRY		= 0x1,
59 	SLAP_ACI_SCOPE_CHILDREN		= 0x2,
60 	SLAP_ACI_SCOPE_SUBTREE		= ( SLAP_ACI_SCOPE_ENTRY | SLAP_ACI_SCOPE_CHILDREN )
61 } slap_aci_scope_t;
62 
63 enum {
64 	ACI_BV_ENTRY,
65 	ACI_BV_CHILDREN,
66 	ACI_BV_ONELEVEL,
67 	ACI_BV_SUBTREE,
68 
69 	ACI_BV_BR_ENTRY,
70 	ACI_BV_BR_CHILDREN,
71 	ACI_BV_BR_ALL,
72 
73 	ACI_BV_ACCESS_ID,
74 	ACI_BV_PUBLIC,
75 	ACI_BV_USERS,
76 	ACI_BV_SELF,
77 	ACI_BV_DNATTR,
78 	ACI_BV_GROUP,
79 	ACI_BV_ROLE,
80 	ACI_BV_SET,
81 	ACI_BV_SET_REF,
82 
83 	ACI_BV_GRANT,
84 	ACI_BV_DENY,
85 
86 	ACI_BV_GROUP_CLASS,
87 	ACI_BV_GROUP_ATTR,
88 	ACI_BV_ROLE_CLASS,
89 	ACI_BV_ROLE_ATTR,
90 
91 	ACI_BV_SET_ATTR,
92 
93 	ACI_BV_LAST
94 };
95 
96 static const struct berval	aci_bv[] = {
97 	/* scope */
98 	BER_BVC("entry"),
99 	BER_BVC("children"),
100 	BER_BVC("onelevel"),
101 	BER_BVC("subtree"),
102 
103 	/* */
104 	BER_BVC("[entry]"),
105 	BER_BVC("[children]"),
106 	BER_BVC("[all]"),
107 
108 	/* type */
109 	BER_BVC("access-id"),
110 	BER_BVC("public"),
111 	BER_BVC("users"),
112 	BER_BVC("self"),
113 	BER_BVC("dnattr"),
114 	BER_BVC("group"),
115 	BER_BVC("role"),
116 	BER_BVC("set"),
117 	BER_BVC("set-ref"),
118 
119 	/* actions */
120 	BER_BVC("grant"),
121 	BER_BVC("deny"),
122 
123 	/* schema */
124 	BER_BVC(SLAPD_GROUP_CLASS),
125 	BER_BVC(SLAPD_GROUP_ATTR),
126 	BER_BVC(SLAPD_ROLE_CLASS),
127 	BER_BVC(SLAPD_ROLE_ATTR),
128 
129 	BER_BVC(SLAPD_ACI_SET_ATTR),
130 
131 	BER_BVNULL
132 };
133 
134 static AttributeDescription	*slap_ad_aci;
135 
136 static int
137 OpenLDAPaciValidate(
138 	Syntax		*syntax,
139 	struct berval	*val );
140 
141 static int
142 OpenLDAPaciPretty(
143 	Syntax		*syntax,
144 	struct berval	*val,
145 	struct berval	*out,
146 	void		*ctx );
147 
148 static int
149 OpenLDAPaciNormalize(
150 	slap_mask_t	use,
151 	Syntax		*syntax,
152 	MatchingRule	*mr,
153 	struct berval	*val,
154 	struct berval	*out,
155 	void		*ctx );
156 
157 #define	OpenLDAPaciMatch			octetStringMatch
158 
159 static int
160 aci_list_map_rights(
161 	struct berval	*list )
162 {
163 	struct berval	bv;
164 	slap_access_t	mask;
165 	int		i;
166 
167 	ACL_INIT( mask );
168 	for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) {
169 		if ( bv.bv_len <= 0 ) {
170 			continue;
171 		}
172 
173 		switch ( *bv.bv_val ) {
174 		case 'x':
175 			/* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not
176 			 * define any equivalent to the AUTH right, so I've just used
177 			 * 'x' for now.
178 			 */
179 			ACL_PRIV_SET(mask, ACL_PRIV_AUTH);
180 			break;
181 		case 'd':
182 			/* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines
183 			 * the right 'd' to mean "delete"; we hijack it to mean
184 			 * "disclose" for consistency wuith the rest of slapd.
185 			 */
186 			ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE);
187 			break;
188 		case 'c':
189 			ACL_PRIV_SET(mask, ACL_PRIV_COMPARE);
190 			break;
191 		case 's':
192 			/* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines
193 			 * the right 's' to mean "set", but in the examples states
194 			 * that the right 's' means "search".  The latter definition
195 			 * is used here.
196 			 */
197 			ACL_PRIV_SET(mask, ACL_PRIV_SEARCH);
198 			break;
199 		case 'r':
200 			ACL_PRIV_SET(mask, ACL_PRIV_READ);
201 			break;
202 		case 'w':
203 			ACL_PRIV_SET(mask, ACL_PRIV_WRITE);
204 			break;
205 		default:
206 			break;
207 		}
208 
209 	}
210 
211 	return mask;
212 }
213 
214 static int
215 aci_list_has_attr(
216 	struct berval		*list,
217 	const struct berval	*attr,
218 	struct berval		*val )
219 {
220 	struct berval	bv, left, right;
221 	int		i;
222 
223 	for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) {
224 		if ( acl_get_part(&bv, 0, '=', &left ) < 0
225 			|| acl_get_part( &bv, 1, '=', &right ) < 0 )
226 		{
227 			if ( ber_bvstrcasecmp( attr, &bv ) == 0 ) {
228 				return(1);
229 			}
230 
231 		} else if ( val == NULL ) {
232 			if ( ber_bvstrcasecmp( attr, &left ) == 0 ) {
233 				return(1);
234 			}
235 
236 		} else {
237 			if ( ber_bvstrcasecmp( attr, &left ) == 0 ) {
238 				/* FIXME: this is also totally undocumented! */
239 				/* this is experimental code that implements a
240 				 * simple (prefix) match of the attribute value.
241 				 * the ACI draft does not provide for aci's that
242 				 * apply to specific values, but it would be
243 				 * nice to have.  If the <attr> part of an aci's
244 				 * rights list is of the form <attr>=<value>,
245 				 * that means the aci applies only to attrs with
246 				 * the given value.  Furthermore, if the attr is
247 				 * of the form <attr>=<value>*, then <value> is
248 				 * treated as a prefix, and the aci applies to
249 				 * any value with that prefix.
250 				 *
251 				 * Ideally, this would allow r.e. matches.
252 				 */
253 				if ( acl_get_part( &right, 0, '*', &left ) < 0
254 					|| right.bv_len <= left.bv_len )
255 				{
256 					if ( ber_bvstrcasecmp( val, &right ) == 0 ) {
257 						return 1;
258 					}
259 
260 				} else if ( val->bv_len >= left.bv_len ) {
261 					if ( strncasecmp( val->bv_val, left.bv_val, left.bv_len ) == 0 ) {
262 						return(1);
263 					}
264 				}
265 			}
266 		}
267 	}
268 
269 	return 0;
270 }
271 
272 static slap_access_t
273 aci_list_get_attr_rights(
274 	struct berval		*list,
275 	const struct berval	*attr,
276 	struct berval		*val )
277 {
278 	struct berval	bv;
279 	slap_access_t	mask;
280 	int		i;
281 
282 	/* loop through each rights/attr pair, skip first part (action) */
283 	ACL_INIT(mask);
284 	for ( i = 1; acl_get_part( list, i + 1, ';', &bv ) >= 0; i += 2 ) {
285 		if ( aci_list_has_attr( &bv, attr, val ) == 0 ) {
286 			Debug( LDAP_DEBUG_ACL,
287 				"        <= aci_list_get_attr_rights "
288 				"test %s for %s -> failed\n",
289 				bv.bv_val, attr->bv_val, 0 );
290 			continue;
291 		}
292 
293 		Debug( LDAP_DEBUG_ACL,
294 			"        <= aci_list_get_attr_rights "
295 			"test %s for %s -> ok\n",
296 			bv.bv_val, attr->bv_val, 0 );
297 
298 		if ( acl_get_part( list, i, ';', &bv ) < 0 ) {
299 			Debug( LDAP_DEBUG_ACL,
300 				"        <= aci_list_get_attr_rights "
301 				"test no rights\n",
302 				0, 0, 0 );
303 			continue;
304 		}
305 
306 		mask |= aci_list_map_rights( &bv );
307 		Debug( LDAP_DEBUG_ACL,
308 			"        <= aci_list_get_attr_rights "
309 			"rights %s to mask 0x%x\n",
310 			bv.bv_val, mask, 0 );
311 	}
312 
313 	return mask;
314 }
315 
316 static int
317 aci_list_get_rights(
318 	struct berval	*list,
319 	struct berval	*attr,
320 	struct berval	*val,
321 	slap_access_t	*grant,
322 	slap_access_t	*deny )
323 {
324 	struct berval	perm, actn, baseattr;
325 	slap_access_t	*mask;
326 	int		i, found;
327 
328 	if ( attr == NULL || BER_BVISEMPTY( attr ) ) {
329 		attr = (struct berval *)&aci_bv[ ACI_BV_ENTRY ];
330 
331 	} else if ( acl_get_part( attr, 0, ';', &baseattr ) > 0 ) {
332 		attr = &baseattr;
333 	}
334 	found = 0;
335 	ACL_INIT(*grant);
336 	ACL_INIT(*deny);
337 	/* loop through each permissions clause */
338 	for ( i = 0; acl_get_part( list, i, '$', &perm ) >= 0; i++ ) {
339 		if ( acl_get_part( &perm, 0, ';', &actn ) < 0 ) {
340 			continue;
341 		}
342 
343 		if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_GRANT ], &actn ) == 0 ) {
344 			mask = grant;
345 
346 		} else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_DENY ], &actn ) == 0 ) {
347 			mask = deny;
348 
349 		} else {
350 			continue;
351 		}
352 
353 		*mask |= aci_list_get_attr_rights( &perm, attr, val );
354 		*mask |= aci_list_get_attr_rights( &perm, &aci_bv[ ACI_BV_BR_ALL ], NULL );
355 
356 		if ( *mask != ACL_PRIV_NONE ) {
357 			found = 1;
358 		}
359 	}
360 
361 	return found;
362 }
363 
364 static int
365 aci_group_member (
366 	struct berval		*subj,
367 	const struct berval	*defgrpoc,
368 	const struct berval	*defgrpat,
369 	Operation		*op,
370 	Entry			*e,
371 	int			nmatch,
372 	regmatch_t		*matches
373 )
374 {
375 	struct berval		subjdn;
376 	struct berval		grpoc;
377 	struct berval		grpat;
378 	ObjectClass		*grp_oc = NULL;
379 	AttributeDescription	*grp_ad = NULL;
380 	const char		*text;
381 	int			rc;
382 
383 	/* format of string is "{group|role}/objectClassValue/groupAttrName" */
384 	if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) {
385 		return 0;
386 	}
387 
388 	if ( acl_get_part( subj, 1, '/', &grpoc ) < 0 ) {
389 		grpoc = *defgrpoc;
390 	}
391 
392 	if ( acl_get_part( subj, 2, '/', &grpat ) < 0 ) {
393 		grpat = *defgrpat;
394 	}
395 
396 	rc = slap_bv2ad( &grpat, &grp_ad, &text );
397 	if ( rc != LDAP_SUCCESS ) {
398 		rc = 0;
399 		goto done;
400 	}
401 	rc = 0;
402 
403 	grp_oc = oc_bvfind( &grpoc );
404 
405 	if ( grp_oc != NULL && grp_ad != NULL ) {
406 		char		buf[ ACI_BUF_SIZE ];
407 		struct berval	bv, ndn;
408 		AclRegexMatches amatches = { 0 };
409 
410 		amatches.dn_count = nmatch;
411 		AC_MEMCPY( amatches.dn_data, matches, sizeof( amatches.dn_data ) );
412 
413 		bv.bv_len = sizeof( buf ) - 1;
414 		bv.bv_val = (char *)&buf;
415 		if ( acl_string_expand( &bv, &subjdn,
416 				&e->e_nname, NULL, &amatches ) )
417 		{
418 			rc = LDAP_OTHER;
419 			goto done;
420 		}
421 
422 		if ( dnNormalize( 0, NULL, NULL, &bv, &ndn, op->o_tmpmemctx ) == LDAP_SUCCESS )
423 		{
424 			rc = ( backend_group( op, e, &ndn, &op->o_ndn,
425 				grp_oc, grp_ad ) == 0 );
426 			slap_sl_free( ndn.bv_val, op->o_tmpmemctx );
427 		}
428 	}
429 
430 done:
431 	return rc;
432 }
433 
434 static int
435 aci_mask(
436 	Operation		*op,
437 	Entry			*e,
438 	AttributeDescription	*desc,
439 	struct berval		*val,
440 	struct berval		*aci,
441 	int			nmatch,
442 	regmatch_t		*matches,
443 	slap_access_t		*grant,
444 	slap_access_t		*deny,
445 	slap_aci_scope_t	asserted_scope )
446 {
447 	struct berval		bv,
448 				scope,
449 				perms,
450 				type,
451 				opts,
452 				sdn;
453 	int			rc;
454 
455 	ACL_INIT( *grant );
456 	ACL_INIT( *deny );
457 
458 	assert( !BER_BVISNULL( &desc->ad_cname ) );
459 
460 	/* parse an aci of the form:
461 		oid # scope # action;rights;attr;rights;attr
462 			$ action;rights;attr;rights;attr # type # subject
463 
464 	   [NOTE: the following comment is very outdated,
465 	   as the draft version it refers to (Ando, 2004-11-20)].
466 
467 	   See draft-ietf-ldapext-aci-model-04.txt section 9.1 for
468 	   a full description of the format for this attribute.
469 	   Differences: "this" in the draft is "self" here, and
470 	   "self" and "public" is in the position of type.
471 
472 	   <scope> = {entry|children|subtree}
473 	   <type> = {public|users|access-id|subtree|onelevel|children|
474 	             self|dnattr|group|role|set|set-ref}
475 
476 	   This routine now supports scope={ENTRY,CHILDREN}
477 	   with the semantics:
478 	     - ENTRY applies to "entry" and "subtree";
479 	     - CHILDREN applies to "children" and "subtree"
480 	 */
481 
482 	/* check that the aci has all 5 components */
483 	if ( acl_get_part( aci, 4, '#', NULL ) < 0 ) {
484 		return 0;
485 	}
486 
487 	/* check that the aci family is supported */
488 	/* FIXME: the OID is ignored? */
489 	if ( acl_get_part( aci, 0, '#', &bv ) < 0 ) {
490 		return 0;
491 	}
492 
493 	/* check that the scope matches */
494 	if ( acl_get_part( aci, 1, '#', &scope ) < 0 ) {
495 		return 0;
496 	}
497 
498 	/* note: scope can be either ENTRY or CHILDREN;
499 	 * they respectively match "entry" and "children" in bv
500 	 * both match "subtree" */
501 	switch ( asserted_scope ) {
502 	case SLAP_ACI_SCOPE_ENTRY:
503 		if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_ENTRY ] ) != 0
504 				&& ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 )
505 		{
506 			return 0;
507 		}
508 		break;
509 
510 	case SLAP_ACI_SCOPE_CHILDREN:
511 		if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_CHILDREN ] ) != 0
512 				&& ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 )
513 		{
514 			return 0;
515 		}
516 		break;
517 
518 	case SLAP_ACI_SCOPE_SUBTREE:
519 		/* TODO: add assertion? */
520 		return 0;
521 	}
522 
523 	/* get the list of permissions clauses, bail if empty */
524 	if ( acl_get_part( aci, 2, '#', &perms ) <= 0 ) {
525 		assert( 0 );
526 		return 0;
527 	}
528 
529 	/* check if any permissions allow desired access */
530 	if ( aci_list_get_rights( &perms, &desc->ad_cname, val, grant, deny ) == 0 ) {
531 		return 0;
532 	}
533 
534 	/* see if we have a DN match */
535 	if ( acl_get_part( aci, 3, '#', &type ) < 0 ) {
536 		assert( 0 );
537 		return 0;
538 	}
539 
540 	/* see if we have a public (i.e. anonymous) access */
541 	if ( ber_bvcmp( &aci_bv[ ACI_BV_PUBLIC ], &type ) == 0 ) {
542 		return 1;
543 	}
544 
545 	/* otherwise require an identity */
546 	if ( BER_BVISNULL( &op->o_ndn ) || BER_BVISEMPTY( &op->o_ndn ) ) {
547 		return 0;
548 	}
549 
550 	/* see if we have a users access */
551 	if ( ber_bvcmp( &aci_bv[ ACI_BV_USERS ], &type ) == 0 ) {
552 		return 1;
553 	}
554 
555 	/* NOTE: this may fail if a DN contains a valid '#' (unescaped);
556 	 * just grab all the berval up to its end (ITS#3303).
557 	 * NOTE: the problem could be solved by providing the DN with
558 	 * the embedded '#' encoded as hexpairs: "cn=Foo#Bar" would
559 	 * become "cn=Foo\23Bar" and be safely used by aci_mask(). */
560 #if 0
561 	if ( acl_get_part( aci, 4, '#', &sdn ) < 0 ) {
562 		return 0;
563 	}
564 #endif
565 	sdn.bv_val = type.bv_val + type.bv_len + STRLENOF( "#" );
566 	sdn.bv_len = aci->bv_len - ( sdn.bv_val - aci->bv_val );
567 
568 	/* get the type options, if any */
569 	if ( acl_get_part( &type, 1, '/', &opts ) > 0 ) {
570 		opts.bv_len = type.bv_len - ( opts.bv_val - type.bv_val );
571 		type.bv_len = opts.bv_val - type.bv_val - 1;
572 
573 	} else {
574 		BER_BVZERO( &opts );
575 	}
576 
577 	if ( ber_bvcmp( &aci_bv[ ACI_BV_ACCESS_ID ], &type ) == 0 ) {
578 		return dn_match( &op->o_ndn, &sdn );
579 
580 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SUBTREE ], &type ) == 0 ) {
581 		return dnIsSuffix( &op->o_ndn, &sdn );
582 
583 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_ONELEVEL ], &type ) == 0 ) {
584 		struct berval pdn;
585 
586 		dnParent( &sdn, &pdn );
587 
588 		return dn_match( &op->o_ndn, &pdn );
589 
590 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_CHILDREN ], &type ) == 0 ) {
591 		return ( !dn_match( &op->o_ndn, &sdn ) && dnIsSuffix( &op->o_ndn, &sdn ) );
592 
593 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SELF ], &type ) == 0 ) {
594 		return dn_match( &op->o_ndn, &e->e_nname );
595 
596 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_DNATTR ], &type ) == 0 ) {
597 		Attribute		*at;
598 		AttributeDescription	*ad = NULL;
599 		const char		*text;
600 
601 		rc = slap_bv2ad( &sdn, &ad, &text );
602 		assert( rc == LDAP_SUCCESS );
603 
604 		rc = 0;
605 		for ( at = attrs_find( e->e_attrs, ad );
606 				at != NULL;
607 				at = attrs_find( at->a_next, ad ) )
608 		{
609 			if ( attr_valfind( at,
610 				SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
611 					SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
612 				&op->o_ndn, NULL, op->o_tmpmemctx ) == 0 )
613 			{
614 				rc = 1;
615 				break;
616 			}
617 		}
618 
619 		return rc;
620 
621 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_GROUP ], &type ) == 0 ) {
622 		struct berval	oc,
623 				at;
624 
625 		if ( BER_BVISNULL( &opts ) ) {
626 			oc = aci_bv[ ACI_BV_GROUP_CLASS ];
627 			at = aci_bv[ ACI_BV_GROUP_ATTR ];
628 
629 		} else {
630 			if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) {
631 				assert( 0 );
632 			}
633 
634 			if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) {
635 				at = aci_bv[ ACI_BV_GROUP_ATTR ];
636 			}
637 		}
638 
639 		if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) )
640 		{
641 			return 1;
642 		}
643 
644 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_ROLE ], &type ) == 0 ) {
645 		struct berval	oc,
646 				at;
647 
648 		if ( BER_BVISNULL( &opts ) ) {
649 			oc = aci_bv[ ACI_BV_ROLE_CLASS ];
650 			at = aci_bv[ ACI_BV_ROLE_ATTR ];
651 
652 		} else {
653 			if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) {
654 				assert( 0 );
655 			}
656 
657 			if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) {
658 				at = aci_bv[ ACI_BV_ROLE_ATTR ];
659 			}
660 		}
661 
662 		if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) )
663 		{
664 			return 1;
665 		}
666 
667 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) {
668 		if ( acl_match_set( &sdn, op, e, NULL ) ) {
669 			return 1;
670 		}
671 
672 	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET_REF ], &type ) == 0 ) {
673 		if ( acl_match_set( &sdn, op, e, (struct berval *)&aci_bv[ ACI_BV_SET_ATTR ] ) ) {
674 			return 1;
675 		}
676 
677 	} else {
678 		/* it passed normalization! */
679 		assert( 0 );
680 	}
681 
682 	return 0;
683 }
684 
685 static int
686 aci_init( void )
687 {
688 	/* OpenLDAP eXperimental Syntax */
689 	static slap_syntax_defs_rec aci_syntax_def = {
690 		"( 1.3.6.1.4.1.4203.666.2.1 DESC 'OpenLDAP Experimental ACI' )",
691 			SLAP_SYNTAX_HIDE,
692 			NULL,
693 			OpenLDAPaciValidate,
694 			OpenLDAPaciPretty
695 	};
696 	static slap_mrule_defs_rec aci_mr_def = {
697 		"( 1.3.6.1.4.1.4203.666.4.2 NAME 'OpenLDAPaciMatch' "
698 			"SYNTAX 1.3.6.1.4.1.4203.666.2.1 )",
699 			SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL,
700 			NULL, OpenLDAPaciNormalize, OpenLDAPaciMatch,
701 			NULL, NULL,
702 			NULL
703 	};
704 	static struct {
705 		char			*name;
706 		char			*desc;
707 		slap_mask_t		flags;
708 		AttributeDescription	**ad;
709 	}		aci_at = {
710 		"OpenLDAPaci", "( 1.3.6.1.4.1.4203.666.1.5 "
711 			"NAME 'OpenLDAPaci' "
712 			"DESC 'OpenLDAP access control information (experimental)' "
713 			"EQUALITY OpenLDAPaciMatch "
714 			"SYNTAX 1.3.6.1.4.1.4203.666.2.1 "
715 			"USAGE directoryOperation )",
716 		SLAP_AT_HIDE,
717 		&slap_ad_aci
718 	};
719 
720 	int			rc;
721 
722 	/* ACI syntax */
723 	rc = register_syntax( &aci_syntax_def );
724 	if ( rc != 0 ) {
725 		return rc;
726 	}
727 
728 	/* ACI equality rule */
729 	rc = register_matching_rule( &aci_mr_def );
730 	if ( rc != 0 ) {
731 		return rc;
732 	}
733 
734 	/* ACI attribute */
735 	rc = register_at( aci_at.desc, aci_at.ad, 0 );
736 	if ( rc != LDAP_SUCCESS ) {
737 		Debug( LDAP_DEBUG_ANY,
738 			"aci_init: at_register failed\n", 0, 0, 0 );
739 		return rc;
740 	}
741 
742 	/* install flags */
743 	(*aci_at.ad)->ad_type->sat_flags |= aci_at.flags;
744 
745 	return rc;
746 }
747 
748 static int
749 dynacl_aci_parse(
750 	const char *fname,
751 	int lineno,
752 	const char *opts,
753 	slap_style_t sty,
754 	const char *right,
755 	void **privp )
756 {
757 	AttributeDescription	*ad = NULL;
758 	const char		*text = NULL;
759 
760 	if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
761 		fprintf( stderr, "%s: line %d: "
762 			"inappropriate style \"%s\" in \"aci\" by clause\n",
763 			fname, lineno, style_strings[sty] );
764 		return -1;
765 	}
766 
767 	if ( right != NULL && *right != '\0' ) {
768 		if ( slap_str2ad( right, &ad, &text ) != LDAP_SUCCESS ) {
769 			fprintf( stderr,
770 				"%s: line %d: aci \"%s\": %s\n",
771 				fname, lineno, right, text );
772 			return -1;
773 		}
774 
775 	} else {
776 		ad = slap_ad_aci;
777 	}
778 
779 	if ( !is_at_syntax( ad->ad_type, SLAPD_ACI_SYNTAX) ) {
780 		fprintf( stderr, "%s: line %d: "
781 			"aci \"%s\": inappropriate syntax: %s\n",
782 			fname, lineno, right,
783 			ad->ad_type->sat_syntax_oid );
784 		return -1;
785 	}
786 
787 	*privp = (void *)ad;
788 
789 	return 0;
790 }
791 
792 static int
793 dynacl_aci_unparse( void *priv, struct berval *bv )
794 {
795 	AttributeDescription	*ad = ( AttributeDescription * )priv;
796 	char			*ptr;
797 
798 	assert( ad != NULL );
799 
800 	bv->bv_val = ch_malloc( STRLENOF(" aci=") + ad->ad_cname.bv_len + 1 );
801 	ptr = lutil_strcopy( bv->bv_val, " aci=" );
802 	ptr = lutil_strcopy( ptr, ad->ad_cname.bv_val );
803 	bv->bv_len = ptr - bv->bv_val;
804 
805 	return 0;
806 }
807 
808 static int
809 dynacl_aci_mask(
810 	void			*priv,
811 	Operation		*op,
812 	Entry			*e,
813 	AttributeDescription	*desc,
814 	struct berval		*val,
815 	int			nmatch,
816 	regmatch_t		*matches,
817 	slap_access_t		*grantp,
818 	slap_access_t		*denyp )
819 {
820 	AttributeDescription	*ad = ( AttributeDescription * )priv;
821 	Attribute		*at;
822 	slap_access_t		tgrant, tdeny, grant, deny;
823 #ifdef LDAP_DEBUG
824 	char			accessmaskbuf[ACCESSMASK_MAXLEN];
825 	char			accessmaskbuf1[ACCESSMASK_MAXLEN];
826 #endif /* LDAP_DEBUG */
827 
828 	if ( BER_BVISEMPTY( &e->e_nname ) ) {
829 		/* no ACIs in the root DSE */
830 		return -1;
831 	}
832 
833 	/* start out with nothing granted, nothing denied */
834 	ACL_INIT(tgrant);
835 	ACL_INIT(tdeny);
836 
837 	/* get the aci attribute */
838 	at = attr_find( e->e_attrs, ad );
839 	if ( at != NULL ) {
840 		int		i;
841 
842 		/* the aci is an multi-valued attribute.  The
843 		 * rights are determined by OR'ing the individual
844 		 * rights given by the acis.
845 		 */
846 		for ( i = 0; !BER_BVISNULL( &at->a_nvals[i] ); i++ ) {
847 			if ( aci_mask( op, e, desc, val, &at->a_nvals[i],
848 					nmatch, matches, &grant, &deny,
849 					SLAP_ACI_SCOPE_ENTRY ) != 0 )
850 			{
851 				tgrant |= grant;
852 				tdeny |= deny;
853 			}
854 		}
855 
856 		Debug( LDAP_DEBUG_ACL, "        <= aci_mask grant %s deny %s\n",
857 			  accessmask2str( tgrant, accessmaskbuf, 1 ),
858 			  accessmask2str( tdeny, accessmaskbuf1, 1 ), 0 );
859 	}
860 
861 	/* If the entry level aci didn't contain anything valid for the
862 	 * current operation, climb up the tree and evaluate the
863 	 * acis with scope set to subtree
864 	 */
865 	if ( tgrant == ACL_PRIV_NONE && tdeny == ACL_PRIV_NONE ) {
866 		struct berval	parent_ndn;
867 
868 		dnParent( &e->e_nname, &parent_ndn );
869 		while ( !BER_BVISEMPTY( &parent_ndn ) ){
870 			int		i;
871 			BerVarray	bvals = NULL;
872 			int		ret, stop;
873 
874 			/* to solve the chicken'n'egg problem of accessing
875 			 * the OpenLDAPaci attribute, the direct access
876 			 * to the entry's attribute is unchecked; however,
877 			 * further accesses to OpenLDAPaci values in the
878 			 * ancestors occur through backend_attribute(), i.e.
879 			 * with the identity of the operation, requiring
880 			 * further access checking.  For uniformity, this
881 			 * makes further requests occur as the rootdn, if
882 			 * any, i.e. searching for the OpenLDAPaci attribute
883 			 * is considered an internal search.  If this is not
884 			 * acceptable, then the same check needs be performed
885 			 * when accessing the entry's attribute. */
886 			struct berval	save_o_dn, save_o_ndn;
887 
888 			if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) {
889 				save_o_dn = op->o_dn;
890 				save_o_ndn = op->o_ndn;
891 
892 				op->o_dn = op->o_bd->be_rootdn;
893 				op->o_ndn = op->o_bd->be_rootndn;
894 			}
895 
896 			Debug( LDAP_DEBUG_ACL, "        checking ACI of \"%s\"\n", parent_ndn.bv_val, 0, 0 );
897 			ret = backend_attribute( op, NULL, &parent_ndn, ad, &bvals, ACL_AUTH );
898 
899 			if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) {
900 				op->o_dn = save_o_dn;
901 				op->o_ndn = save_o_ndn;
902 			}
903 
904 			switch ( ret ) {
905 			case LDAP_SUCCESS :
906 				stop = 0;
907 				if ( !bvals ) {
908 					break;
909 				}
910 
911 				for ( i = 0; !BER_BVISNULL( &bvals[i] ); i++ ) {
912 					if ( aci_mask( op, e, desc, val,
913 							&bvals[i],
914 							nmatch, matches,
915 							&grant, &deny,
916 							SLAP_ACI_SCOPE_CHILDREN ) != 0 )
917 					{
918 						tgrant |= grant;
919 						tdeny |= deny;
920 						/* evaluation stops as soon as either a "deny" or a
921 						 * "grant" directive matches.
922 						 */
923 						if ( tgrant != ACL_PRIV_NONE || tdeny != ACL_PRIV_NONE ) {
924 							stop = 1;
925 						}
926 					}
927 					Debug( LDAP_DEBUG_ACL, "<= aci_mask grant %s deny %s\n",
928 						accessmask2str( tgrant, accessmaskbuf, 1 ),
929 						accessmask2str( tdeny, accessmaskbuf1, 1 ), 0 );
930 				}
931 				break;
932 
933 			case LDAP_NO_SUCH_ATTRIBUTE:
934 				/* just go on if the aci-Attribute is not present in
935 				 * the current entry
936 				 */
937 				Debug( LDAP_DEBUG_ACL, "no such attribute\n", 0, 0, 0 );
938 				stop = 0;
939 				break;
940 
941 			case LDAP_NO_SUCH_OBJECT:
942 				/* We have reached the base object */
943 				Debug( LDAP_DEBUG_ACL, "no such object\n", 0, 0, 0 );
944 				stop = 1;
945 				break;
946 
947 			default:
948 				stop = 1;
949 				break;
950 			}
951 
952 			if ( stop ) {
953 				break;
954 			}
955 			dnParent( &parent_ndn, &parent_ndn );
956 		}
957 	}
958 
959 	*grantp = tgrant;
960 	*denyp = tdeny;
961 
962 	return 0;
963 }
964 
965 /* need to register this at some point */
966 static slap_dynacl_t	dynacl_aci = {
967 	"aci",
968 	dynacl_aci_parse,
969 	dynacl_aci_unparse,
970 	dynacl_aci_mask,
971 	NULL,
972 	NULL,
973 	NULL
974 };
975 
976 int
977 dynacl_aci_init( void )
978 {
979 	int	rc;
980 
981 	rc = aci_init();
982 
983 	if ( rc == 0 ) {
984 		rc = slap_dynacl_register( &dynacl_aci );
985 	}
986 
987 	return rc;
988 }
989 
990 
991 /* ACI syntax validation */
992 
993 /*
994  * Matches given berval to array of bervals
995  * Returns:
996  *      >=0 if one if the array elements equals to this berval
997  *       -1 if string was not found in array
998  */
999 static int
1000 bv_getcaseidx(
1001 	struct berval *bv,
1002 	const struct berval *arr[] )
1003 {
1004 	int i;
1005 
1006 	if ( BER_BVISEMPTY( bv ) ) {
1007 		return -1;
1008 	}
1009 
1010 	for ( i = 0; arr[ i ] != NULL ; i++ ) {
1011 		if ( ber_bvstrcasecmp( bv, arr[ i ] ) == 0 ) {
1012  			return i;
1013 		}
1014 	}
1015 
1016   	return -1;
1017 }
1018 
1019 
1020 /* Returns what have left in input berval after current sub */
1021 static void
1022 bv_get_tail(
1023 	struct berval *val,
1024 	struct berval *sub,
1025 	struct berval *tail )
1026 {
1027 	int		head_len;
1028 
1029 	tail->bv_val = sub->bv_val + sub->bv_len;
1030 	head_len = (unsigned long) tail->bv_val - (unsigned long) val->bv_val;
1031   	tail->bv_len = val->bv_len - head_len;
1032 }
1033 
1034 
1035 /*
1036  * aci is accepted in following form:
1037  *    oid#scope#rights#type#subject
1038  * Where:
1039  *    oid       := numeric OID (currently ignored)
1040  *    scope     := entry|children|subtree
1041  *    rights    := right[[$right]...]
1042  *    right     := (grant|deny);action
1043  *    action    := perms;attrs[[;perms;attrs]...]
1044  *    perms     := perm[[,perm]...]
1045  *    perm      := c|s|r|w|x
1046  *    attrs     := attribute[[,attribute]..]|"[all]"
1047  *    attribute := attributeType|attributeType=attributeValue|attributeType=attributeValuePrefix*
1048  *    type      := public|users|self|dnattr|group|role|set|set-ref|
1049  *                 access_id|subtree|onelevel|children
1050  */
1051 static int
1052 OpenLDAPaciValidatePerms(
1053 	struct berval *perms )
1054 {
1055 	ber_len_t	i;
1056 
1057 	for ( i = 0; i < perms->bv_len; ) {
1058 		switch ( perms->bv_val[ i ] ) {
1059 		case 'x':
1060 		case 'd':
1061 		case 'c':
1062 		case 's':
1063 		case 'r':
1064 		case 'w':
1065 			break;
1066 
1067 		default:
1068 		        Debug( LDAP_DEBUG_ACL, "aciValidatePerms: perms needs to be one of x,d,c,s,r,w in '%s'\n", perms->bv_val, 0, 0 );
1069 			return LDAP_INVALID_SYNTAX;
1070 		}
1071 
1072 		if ( ++i == perms->bv_len ) {
1073 			return LDAP_SUCCESS;
1074 		}
1075 
1076 		while ( i < perms->bv_len && perms->bv_val[ i ] == ' ' )
1077 			i++;
1078 
1079 		assert( i != perms->bv_len );
1080 
1081 		if ( perms->bv_val[ i ] != ',' ) {
1082 		        Debug( LDAP_DEBUG_ACL, "aciValidatePerms: missing comma in '%s'\n", perms->bv_val, 0, 0 );
1083 			return LDAP_INVALID_SYNTAX;
1084 		}
1085 
1086 		do {
1087 			i++;
1088 		} while ( perms->bv_val[ i ] == ' ' );
1089 	}
1090 
1091 	return LDAP_SUCCESS;
1092 }
1093 
1094 static const struct berval *ACIgrantdeny[] = {
1095 	&aci_bv[ ACI_BV_GRANT ],
1096 	&aci_bv[ ACI_BV_DENY ],
1097 	NULL
1098 };
1099 
1100 static int
1101 OpenLDAPaciValidateRight(
1102 	struct berval *action )
1103 {
1104 	struct berval	bv = BER_BVNULL;
1105 	int		i;
1106 
1107 	/* grant|deny */
1108 	if ( acl_get_part( action, 0, ';', &bv ) < 0 ||
1109 		bv_getcaseidx( &bv, ACIgrantdeny ) == -1 )
1110 	{
1111 		Debug( LDAP_DEBUG_ACL, "aciValidateRight: '%s' must be either 'grant' or 'deny'\n", bv.bv_val, 0, 0 );
1112 		return LDAP_INVALID_SYNTAX;
1113 	}
1114 
1115 	for ( i = 0; acl_get_part( action, i + 1, ';', &bv ) >= 0; i++ ) {
1116 		if ( i & 1 ) {
1117 			/* perms */
1118 			if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS )
1119 			{
1120 				return LDAP_INVALID_SYNTAX;
1121 			}
1122 
1123 		} else {
1124 			/* attr */
1125 			AttributeDescription	*ad;
1126 			const char		*text;
1127 			struct berval		attr, left, right;
1128 			int			j;
1129 
1130 			/* could be "[all]" or an attribute description */
1131 			if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
1132 				continue;
1133 			}
1134 
1135 
1136 			for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ )
1137 			{
1138 				ad = NULL;
1139 				text = NULL;
1140 				if ( acl_get_part( &attr, 0, '=', &left ) < 0
1141 					|| acl_get_part( &attr, 1, '=', &right ) < 0 )
1142 				{
1143 					if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS )
1144 					{
1145 						Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", attr.bv_val, 0, 0 );
1146 						return LDAP_INVALID_SYNTAX;
1147 					}
1148 				} else {
1149 					if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS )
1150 					{
1151 						Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", left.bv_val, 0, 0 );
1152 						return LDAP_INVALID_SYNTAX;
1153 					}
1154 				}
1155 			}
1156 		}
1157 	}
1158 
1159 	/* "perms;attr" go in pairs */
1160 	if ( i > 0 && ( i & 1 ) == 0 ) {
1161 		return LDAP_SUCCESS;
1162 
1163 	} else {
1164 		Debug( LDAP_DEBUG_ACL, "aciValidateRight: perms:attr need to be pairs in '%s'\n", action->bv_val, 0, 0 );
1165 		return LDAP_INVALID_SYNTAX;
1166 	}
1167 
1168 	return LDAP_SUCCESS;
1169 }
1170 
1171 static int
1172 OpenLDAPaciNormalizeRight(
1173 	struct berval	*action,
1174 	struct berval	*naction,
1175 	void		*ctx )
1176 {
1177 	struct berval	grantdeny,
1178 			perms = BER_BVNULL,
1179 			bv = BER_BVNULL;
1180 	int		idx,
1181 			i;
1182 
1183 	/* grant|deny */
1184 	if ( acl_get_part( action, 0, ';', &grantdeny ) < 0 ) {
1185 	        Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: missing ';' in '%s'\n", action->bv_val, 0, 0 );
1186 		return LDAP_INVALID_SYNTAX;
1187 	}
1188 	idx = bv_getcaseidx( &grantdeny, ACIgrantdeny );
1189 	if ( idx == -1 ) {
1190 	        Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: '%s' must be grant or deny\n", grantdeny.bv_val, 0, 0 );
1191 		return LDAP_INVALID_SYNTAX;
1192 	}
1193 
1194 	ber_dupbv_x( naction, (struct berval *)ACIgrantdeny[ idx ], ctx );
1195 
1196 	for ( i = 1; acl_get_part( action, i, ';', &bv ) >= 0; i++ ) {
1197 		struct berval	nattrs = BER_BVNULL;
1198 		int		freenattrs = 1;
1199 		if ( i & 1 ) {
1200 			/* perms */
1201 			if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS )
1202 			{
1203 				return LDAP_INVALID_SYNTAX;
1204 			}
1205 			perms = bv;
1206 
1207 		} else {
1208 			/* attr */
1209 			char		*ptr;
1210 
1211 			/* could be "[all]" or an attribute description */
1212 			if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
1213 				nattrs = aci_bv[ ACI_BV_BR_ALL ];
1214 				freenattrs = 0;
1215 
1216 			} else {
1217 				AttributeDescription	*ad = NULL;
1218 				AttributeDescription	adstatic= { 0 };
1219 				const char		*text = NULL;
1220 				struct berval		attr, left, right;
1221 				int			j;
1222 				int			len;
1223 
1224 				for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ )
1225 				{
1226 					ad = NULL;
1227 					text = NULL;
1228 					/* openldap 2.1 aci compabitibility [entry] -> entry */
1229 					if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ENTRY ] ) == 0 ) {
1230 						ad = &adstatic;
1231 						adstatic.ad_cname = aci_bv[ ACI_BV_ENTRY ];
1232 
1233 					/* openldap 2.1 aci compabitibility [children] -> children */
1234 					} else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_CHILDREN ] ) == 0 ) {
1235 						ad = &adstatic;
1236 						adstatic.ad_cname = aci_bv[ ACI_BV_CHILDREN ];
1237 
1238 					/* openldap 2.1 aci compabitibility [all] -> only [all] */
1239 					} else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
1240 						ber_memfree_x( nattrs.bv_val, ctx );
1241 						nattrs = aci_bv[ ACI_BV_BR_ALL ];
1242 						freenattrs = 0;
1243 						break;
1244 
1245 					} else if ( acl_get_part( &attr, 0, '=', &left ) < 0
1246 				     		|| acl_get_part( &attr, 1, '=', &right ) < 0 )
1247 					{
1248 						if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS )
1249 						{
1250 							ber_memfree_x( nattrs.bv_val, ctx );
1251 							Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", attr.bv_val, 0, 0 );
1252 							return LDAP_INVALID_SYNTAX;
1253 						}
1254 
1255 					} else {
1256 						if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS )
1257 						{
1258 							ber_memfree_x( nattrs.bv_val, ctx );
1259 							Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", left.bv_val, 0, 0 );
1260 							return LDAP_INVALID_SYNTAX;
1261 						}
1262 					}
1263 
1264 
1265 					len = nattrs.bv_len + ( !BER_BVISEMPTY( &nattrs ) ? STRLENOF( "," ) : 0 )
1266 				      		+ ad->ad_cname.bv_len;
1267 					nattrs.bv_val = ber_memrealloc_x( nattrs.bv_val, len + 1, ctx );
1268 	                        	ptr = &nattrs.bv_val[ nattrs.bv_len ];
1269 					if ( !BER_BVISEMPTY( &nattrs ) ) {
1270 						*ptr++ = ',';
1271 					}
1272 					ptr = lutil_strncopy( ptr, ad->ad_cname.bv_val, ad->ad_cname.bv_len );
1273                                 	ptr[ 0 ] = '\0';
1274                                 	nattrs.bv_len = len;
1275 				}
1276 
1277 			}
1278 
1279 			naction->bv_val = ber_memrealloc_x( naction->bv_val,
1280 				naction->bv_len + STRLENOF( ";" )
1281 				+ perms.bv_len + STRLENOF( ";" )
1282 				+ nattrs.bv_len + 1,
1283 				ctx );
1284 
1285 			ptr = &naction->bv_val[ naction->bv_len ];
1286 			ptr[ 0 ] = ';';
1287 			ptr++;
1288 			ptr = lutil_strncopy( ptr, perms.bv_val, perms.bv_len );
1289 			ptr[ 0 ] = ';';
1290 			ptr++;
1291 			ptr = lutil_strncopy( ptr, nattrs.bv_val, nattrs.bv_len );
1292 			ptr[ 0 ] = '\0';
1293 			naction->bv_len += STRLENOF( ";" ) + perms.bv_len
1294 				+ STRLENOF( ";" ) + nattrs.bv_len;
1295 			if ( freenattrs ) {
1296 				ber_memfree_x( nattrs.bv_val, ctx );
1297 			}
1298 		}
1299 	}
1300 
1301 	/* perms;attr go in pairs */
1302 	if ( i > 1 && ( i & 1 ) ) {
1303 		return LDAP_SUCCESS;
1304 
1305 	} else {
1306 		Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: perms:attr need to be pairs in '%s'\n", action->bv_val, 0, 0 );
1307 		return LDAP_INVALID_SYNTAX;
1308 	}
1309 }
1310 
1311 static int
1312 OpenLDAPaciValidateRights(
1313 	struct berval *actions )
1314 
1315 {
1316 	struct berval	bv = BER_BVNULL;
1317 	int		i;
1318 
1319 	for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) {
1320 		if ( OpenLDAPaciValidateRight( &bv ) != LDAP_SUCCESS ) {
1321 			return LDAP_INVALID_SYNTAX;
1322 		}
1323 	}
1324 
1325 	return LDAP_SUCCESS;
1326 }
1327 
1328 static int
1329 OpenLDAPaciNormalizeRights(
1330 	struct berval	*actions,
1331 	struct berval	*nactions,
1332 	void		*ctx )
1333 
1334 {
1335 	struct berval	bv = BER_BVNULL;
1336 	int		i;
1337 
1338 	BER_BVZERO( nactions );
1339 	for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) {
1340 		int		rc;
1341 		struct berval	nbv;
1342 
1343 		rc = OpenLDAPaciNormalizeRight( &bv, &nbv, ctx );
1344 		if ( rc != LDAP_SUCCESS ) {
1345 			ber_memfree_x( nactions->bv_val, ctx );
1346 			BER_BVZERO( nactions );
1347 			return LDAP_INVALID_SYNTAX;
1348 		}
1349 
1350 		if ( i == 0 ) {
1351 			*nactions = nbv;
1352 
1353 		} else {
1354 			nactions->bv_val = ber_memrealloc_x( nactions->bv_val,
1355 				nactions->bv_len + STRLENOF( "$" )
1356 				+ nbv.bv_len + 1,
1357 				ctx );
1358 			nactions->bv_val[ nactions->bv_len ] = '$';
1359 			AC_MEMCPY( &nactions->bv_val[ nactions->bv_len + 1 ],
1360 				nbv.bv_val, nbv.bv_len + 1 );
1361 			ber_memfree_x( nbv.bv_val, ctx );
1362 			nactions->bv_len += STRLENOF( "$" ) + nbv.bv_len;
1363 		}
1364 		BER_BVZERO( &nbv );
1365 	}
1366 
1367 	return LDAP_SUCCESS;
1368 }
1369 
1370 static const struct berval *OpenLDAPaciscopes[] = {
1371 	&aci_bv[ ACI_BV_ENTRY ],
1372 	&aci_bv[ ACI_BV_CHILDREN ],
1373 	&aci_bv[ ACI_BV_SUBTREE ],
1374 
1375 	NULL
1376 };
1377 
1378 static const struct berval *OpenLDAPacitypes[] = {
1379 	/* DN-valued */
1380 	&aci_bv[ ACI_BV_GROUP ],
1381 	&aci_bv[ ACI_BV_ROLE ],
1382 
1383 /* set to one past the last DN-valued type with options (/) */
1384 #define	LAST_OPTIONAL	2
1385 
1386 	&aci_bv[ ACI_BV_ACCESS_ID ],
1387 	&aci_bv[ ACI_BV_SUBTREE ],
1388 	&aci_bv[ ACI_BV_ONELEVEL ],
1389 	&aci_bv[ ACI_BV_CHILDREN ],
1390 
1391 /* set to one past the last DN-valued type */
1392 #define LAST_DNVALUED	6
1393 
1394 	/* non DN-valued */
1395 	&aci_bv[ ACI_BV_DNATTR ],
1396 	&aci_bv[ ACI_BV_PUBLIC ],
1397 	&aci_bv[ ACI_BV_USERS ],
1398 	&aci_bv[ ACI_BV_SELF ],
1399 	&aci_bv[ ACI_BV_SET ],
1400 	&aci_bv[ ACI_BV_SET_REF ],
1401 
1402 	NULL
1403 };
1404 
1405 static int
1406 OpenLDAPaciValidate(
1407 	Syntax		*syntax,
1408 	struct berval	*val )
1409 {
1410 	struct berval	oid = BER_BVNULL,
1411 			scope = BER_BVNULL,
1412 			rights = BER_BVNULL,
1413 			type = BER_BVNULL,
1414 			subject = BER_BVNULL;
1415 	int		idx;
1416 	int		rc;
1417 
1418 	if ( BER_BVISEMPTY( val ) ) {
1419 		Debug( LDAP_DEBUG_ACL, "aciValidatet: value is empty\n", 0, 0, 0 );
1420 		return LDAP_INVALID_SYNTAX;
1421 	}
1422 
1423 	/* oid */
1424 	if ( acl_get_part( val, 0, '#', &oid ) < 0 ||
1425 		numericoidValidate( NULL, &oid ) != LDAP_SUCCESS )
1426 	{
1427 		/* NOTE: the numericoidValidate() is rather pedantic;
1428 		 * I'd replace it with X-ORDERED VALUES so that
1429 		 * it's guaranteed values are maintained and used
1430 		 * in the desired order */
1431 		Debug( LDAP_DEBUG_ACL, "aciValidate: invalid oid '%s'\n", oid.bv_val, 0, 0 );
1432 		return LDAP_INVALID_SYNTAX;
1433 	}
1434 
1435 	/* scope */
1436 	if ( acl_get_part( val, 1, '#', &scope ) < 0 ||
1437 		bv_getcaseidx( &scope, OpenLDAPaciscopes ) == -1 )
1438 	{
1439 		Debug( LDAP_DEBUG_ACL, "aciValidate: invalid scope '%s'\n", scope.bv_val, 0, 0 );
1440 		return LDAP_INVALID_SYNTAX;
1441 	}
1442 
1443 	/* rights */
1444 	if ( acl_get_part( val, 2, '#', &rights ) < 0 ||
1445 		OpenLDAPaciValidateRights( &rights ) != LDAP_SUCCESS )
1446 	{
1447 		return LDAP_INVALID_SYNTAX;
1448 	}
1449 
1450 	/* type */
1451 	if ( acl_get_part( val, 3, '#', &type ) < 0 ) {
1452 		Debug( LDAP_DEBUG_ACL, "aciValidate: missing type in '%s'\n", val->bv_val, 0, 0 );
1453 		return LDAP_INVALID_SYNTAX;
1454 	}
1455 	idx = bv_getcaseidx( &type, OpenLDAPacitypes );
1456 	if ( idx == -1 ) {
1457 		struct berval	isgr;
1458 
1459 		if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) {
1460 			Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", type.bv_val, 0, 0 );
1461 			return LDAP_INVALID_SYNTAX;
1462 		}
1463 
1464 		idx = bv_getcaseidx( &isgr, OpenLDAPacitypes );
1465 		if ( idx == -1 || idx >= LAST_OPTIONAL ) {
1466 			Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", isgr.bv_val, 0, 0 );
1467 			return LDAP_INVALID_SYNTAX;
1468 		}
1469 	}
1470 
1471 	/* subject */
1472 	bv_get_tail( val, &type, &subject );
1473 	if ( subject.bv_val[ 0 ] != '#' ) {
1474 		Debug( LDAP_DEBUG_ACL, "aciValidate: missing subject in '%s'\n", val->bv_val, 0, 0 );
1475 		return LDAP_INVALID_SYNTAX;
1476 	}
1477 
1478 	if ( idx >= LAST_DNVALUED ) {
1479 		if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) {
1480 			AttributeDescription	*ad = NULL;
1481 			const char		*text = NULL;
1482 
1483 			rc = slap_bv2ad( &subject, &ad, &text );
1484 			if ( rc != LDAP_SUCCESS ) {
1485 				Debug( LDAP_DEBUG_ACL, "aciValidate: unknown dn attribute '%s'\n", subject.bv_val, 0, 0 );
1486 				return LDAP_INVALID_SYNTAX;
1487 			}
1488 
1489 			if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
1490 				/* FIXME: allow nameAndOptionalUID? */
1491 				Debug( LDAP_DEBUG_ACL, "aciValidate: wrong syntax for dn attribute '%s'\n", subject.bv_val, 0, 0 );
1492 				return LDAP_INVALID_SYNTAX;
1493 			}
1494 		}
1495 
1496 		/* not a DN */
1497 		return LDAP_SUCCESS;
1498 
1499 	} else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ]
1500 			|| OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] )
1501 	{
1502 		/* do {group|role}/oc/at check */
1503 		struct berval	ocbv = BER_BVNULL,
1504 				atbv = BER_BVNULL;
1505 
1506 		ocbv.bv_val = ber_bvchr( &type, '/' );
1507 		if ( ocbv.bv_val != NULL ) {
1508 			ocbv.bv_val++;
1509 			ocbv.bv_len = type.bv_len
1510 					- ( ocbv.bv_val - type.bv_val );
1511 
1512 			atbv.bv_val = ber_bvchr( &ocbv, '/' );
1513 			if ( atbv.bv_val != NULL ) {
1514 				AttributeDescription	*ad = NULL;
1515 				const char		*text = NULL;
1516 				int			rc;
1517 
1518 				atbv.bv_val++;
1519 				atbv.bv_len = type.bv_len
1520 					- ( atbv.bv_val - type.bv_val );
1521 				ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1;
1522 
1523 				rc = slap_bv2ad( &atbv, &ad, &text );
1524 				if ( rc != LDAP_SUCCESS ) {
1525 				        Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group attribute '%s'\n", atbv.bv_val, 0, 0 );
1526 					return LDAP_INVALID_SYNTAX;
1527 				}
1528 			}
1529 
1530 			if ( oc_bvfind( &ocbv ) == NULL ) {
1531 			        Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group '%s'\n", ocbv.bv_val, 0, 0 );
1532 				return LDAP_INVALID_SYNTAX;
1533 			}
1534 		}
1535 	}
1536 
1537 	if ( BER_BVISEMPTY( &subject ) ) {
1538 		/* empty DN invalid */
1539 	        Debug( LDAP_DEBUG_ACL, "aciValidate: missing dn in '%s'\n", val->bv_val, 0, 0 );
1540 		return LDAP_INVALID_SYNTAX;
1541 	}
1542 
1543 	subject.bv_val++;
1544 	subject.bv_len--;
1545 
1546 	/* FIXME: pass DN syntax? */
1547 	rc = dnValidate( NULL, &subject );
1548 	if ( rc != LDAP_SUCCESS ) {
1549 	        Debug( LDAP_DEBUG_ACL, "aciValidate: invalid dn '%s'\n", subject.bv_val, 0, 0 );
1550 	}
1551 	return rc;
1552 }
1553 
1554 static int
1555 OpenLDAPaciPrettyNormal(
1556 	struct berval	*val,
1557 	struct berval	*out,
1558 	void		*ctx,
1559 	int		normalize )
1560 {
1561 	struct berval	oid = BER_BVNULL,
1562 			scope = BER_BVNULL,
1563 			rights = BER_BVNULL,
1564 			nrights = BER_BVNULL,
1565 			type = BER_BVNULL,
1566 			ntype = BER_BVNULL,
1567 			subject = BER_BVNULL,
1568 			nsubject = BER_BVNULL;
1569 	int		idx,
1570 			rc = LDAP_SUCCESS,
1571 			freesubject = 0,
1572 			freetype = 0;
1573 	char		*ptr;
1574 
1575 	BER_BVZERO( out );
1576 
1577 	if ( BER_BVISEMPTY( val ) ) {
1578 		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: value is empty\n", 0, 0, 0 );
1579 		return LDAP_INVALID_SYNTAX;
1580 	}
1581 
1582 	/* oid: if valid, it's already normalized */
1583 	if ( acl_get_part( val, 0, '#', &oid ) < 0 ||
1584 		numericoidValidate( NULL, &oid ) != LDAP_SUCCESS )
1585 	{
1586 		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid oid '%s'\n", oid.bv_val, 0, 0 );
1587 		return LDAP_INVALID_SYNTAX;
1588 	}
1589 
1590 	/* scope: normalize by replacing with OpenLDAPaciscopes */
1591 	if ( acl_get_part( val, 1, '#', &scope ) < 0 ) {
1592 		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing scope in '%s'\n", val->bv_val, 0, 0 );
1593 		return LDAP_INVALID_SYNTAX;
1594 	}
1595 	idx = bv_getcaseidx( &scope, OpenLDAPaciscopes );
1596 	if ( idx == -1 ) {
1597 		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid scope '%s'\n", scope.bv_val, 0, 0 );
1598 		return LDAP_INVALID_SYNTAX;
1599 	}
1600 	scope = *OpenLDAPaciscopes[ idx ];
1601 
1602 	/* rights */
1603 	if ( acl_get_part( val, 2, '#', &rights ) < 0 ) {
1604 		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing rights in '%s'\n", val->bv_val, 0, 0 );
1605 		return LDAP_INVALID_SYNTAX;
1606 	}
1607 	if ( OpenLDAPaciNormalizeRights( &rights, &nrights, ctx )
1608 		!= LDAP_SUCCESS )
1609 	{
1610 		return LDAP_INVALID_SYNTAX;
1611 	}
1612 
1613 	/* type */
1614 	if ( acl_get_part( val, 3, '#', &type ) < 0 ) {
1615 		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing type in '%s'\n", val->bv_val, 0, 0 );
1616 		rc = LDAP_INVALID_SYNTAX;
1617 		goto cleanup;
1618 	}
1619 	idx = bv_getcaseidx( &type, OpenLDAPacitypes );
1620 	if ( idx == -1 ) {
1621 		struct berval	isgr;
1622 
1623 		if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) {
1624 		        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", type.bv_val, 0, 0 );
1625 			rc = LDAP_INVALID_SYNTAX;
1626 			goto cleanup;
1627 		}
1628 
1629 		idx = bv_getcaseidx( &isgr, OpenLDAPacitypes );
1630 		if ( idx == -1 || idx >= LAST_OPTIONAL ) {
1631 		        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", isgr.bv_val, 0, 0 );
1632 			rc = LDAP_INVALID_SYNTAX;
1633 			goto cleanup;
1634 		}
1635 	}
1636 	ntype = *OpenLDAPacitypes[ idx ];
1637 
1638 	/* subject */
1639 	bv_get_tail( val, &type, &subject );
1640 
1641 	if ( BER_BVISEMPTY( &subject ) || subject.bv_val[ 0 ] != '#' ) {
1642 	        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing subject in '%s'\n", val->bv_val, 0, 0 );
1643 		rc = LDAP_INVALID_SYNTAX;
1644 		goto cleanup;
1645 	}
1646 
1647 	subject.bv_val++;
1648 	subject.bv_len--;
1649 
1650 	if ( idx < LAST_DNVALUED ) {
1651 		/* FIXME: pass DN syntax? */
1652 		if ( normalize ) {
1653 			rc = dnNormalize( 0, NULL, NULL,
1654 				&subject, &nsubject, ctx );
1655 		} else {
1656 			rc = dnPretty( NULL, &subject, &nsubject, ctx );
1657 		}
1658 
1659 		if ( rc == LDAP_SUCCESS ) {
1660 			freesubject = 1;
1661 
1662 		} else {
1663 	                Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid subject dn '%s'\n", subject.bv_val, 0, 0 );
1664 			goto cleanup;
1665 		}
1666 
1667 		if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ]
1668 			|| OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] )
1669 		{
1670 			/* do {group|role}/oc/at check */
1671 			struct berval	ocbv = BER_BVNULL,
1672 					atbv = BER_BVNULL;
1673 
1674 			ocbv.bv_val = ber_bvchr( &type, '/' );
1675 			if ( ocbv.bv_val != NULL ) {
1676 				ObjectClass		*oc = NULL;
1677 				AttributeDescription	*ad = NULL;
1678 				const char		*text = NULL;
1679 				int			rc;
1680 				struct berval		bv;
1681 
1682 				bv.bv_len = ntype.bv_len;
1683 
1684 				ocbv.bv_val++;
1685 				ocbv.bv_len = type.bv_len - ( ocbv.bv_val - type.bv_val );
1686 
1687 				atbv.bv_val = ber_bvchr( &ocbv, '/' );
1688 				if ( atbv.bv_val != NULL ) {
1689 					atbv.bv_val++;
1690 					atbv.bv_len = type.bv_len
1691 						- ( atbv.bv_val - type.bv_val );
1692 					ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1;
1693 
1694 					rc = slap_bv2ad( &atbv, &ad, &text );
1695 					if ( rc != LDAP_SUCCESS ) {
1696 	                                        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown group attribute '%s'\n", atbv.bv_val, 0, 0 );
1697 						rc = LDAP_INVALID_SYNTAX;
1698 						goto cleanup;
1699 					}
1700 
1701 					bv.bv_len += STRLENOF( "/" ) + ad->ad_cname.bv_len;
1702 				}
1703 
1704 				oc = oc_bvfind( &ocbv );
1705 				if ( oc == NULL ) {
1706                                         Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid group '%s'\n", ocbv.bv_val, 0, 0 );
1707 					rc = LDAP_INVALID_SYNTAX;
1708 					goto cleanup;
1709 				}
1710 
1711 				bv.bv_len += STRLENOF( "/" ) + oc->soc_cname.bv_len;
1712 				bv.bv_val = ber_memalloc_x( bv.bv_len + 1, ctx );
1713 
1714 				ptr = bv.bv_val;
1715 				ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len );
1716 				ptr[ 0 ] = '/';
1717 				ptr++;
1718 				ptr = lutil_strncopy( ptr,
1719 					oc->soc_cname.bv_val,
1720 					oc->soc_cname.bv_len );
1721 				if ( ad != NULL ) {
1722 					ptr[ 0 ] = '/';
1723 					ptr++;
1724 					ptr = lutil_strncopy( ptr,
1725 						ad->ad_cname.bv_val,
1726 						ad->ad_cname.bv_len );
1727 				}
1728 				ptr[ 0 ] = '\0';
1729 
1730 				ntype = bv;
1731 				freetype = 1;
1732 			}
1733 		}
1734 
1735 	} else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) {
1736 		AttributeDescription	*ad = NULL;
1737 		const char		*text = NULL;
1738 		int			rc;
1739 
1740 		rc = slap_bv2ad( &subject, &ad, &text );
1741 		if ( rc != LDAP_SUCCESS ) {
1742                         Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown dn attribute '%s'\n", subject.bv_val, 0, 0 );
1743 			rc = LDAP_INVALID_SYNTAX;
1744 			goto cleanup;
1745 		}
1746 
1747 		if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
1748 			/* FIXME: allow nameAndOptionalUID? */
1749                         Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: wrong syntax for dn attribute '%s'\n", subject.bv_val, 0, 0 );
1750 			rc = LDAP_INVALID_SYNTAX;
1751 			goto cleanup;
1752 		}
1753 
1754 		nsubject = ad->ad_cname;
1755 
1756 	} else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_SET ]
1757 		|| OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_SET_REF ] )
1758 	{
1759 		/* NOTE: dunno how to normalize it... */
1760 		nsubject = subject;
1761 	}
1762 
1763 
1764 	out->bv_len =
1765 		oid.bv_len + STRLENOF( "#" )
1766 		+ scope.bv_len + STRLENOF( "#" )
1767 		+ nrights.bv_len + STRLENOF( "#" )
1768 		+ ntype.bv_len + STRLENOF( "#" )
1769 		+ nsubject.bv_len;
1770 
1771 	out->bv_val = ber_memalloc_x( out->bv_len + 1, ctx );
1772 	ptr = lutil_strncopy( out->bv_val, oid.bv_val, oid.bv_len );
1773 	ptr[ 0 ] = '#';
1774 	ptr++;
1775 	ptr = lutil_strncopy( ptr, scope.bv_val, scope.bv_len );
1776 	ptr[ 0 ] = '#';
1777 	ptr++;
1778 	ptr = lutil_strncopy( ptr, nrights.bv_val, nrights.bv_len );
1779 	ptr[ 0 ] = '#';
1780 	ptr++;
1781 	ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len );
1782 	ptr[ 0 ] = '#';
1783 	ptr++;
1784 	if ( !BER_BVISNULL( &nsubject ) ) {
1785 		ptr = lutil_strncopy( ptr, nsubject.bv_val, nsubject.bv_len );
1786 	}
1787 	ptr[ 0 ] = '\0';
1788 
1789 cleanup:;
1790 	if ( freesubject ) {
1791 		ber_memfree_x( nsubject.bv_val, ctx );
1792 	}
1793 
1794 	if ( freetype ) {
1795 		ber_memfree_x( ntype.bv_val, ctx );
1796 	}
1797 
1798 	if ( !BER_BVISNULL( &nrights ) ) {
1799 		ber_memfree_x( nrights.bv_val, ctx );
1800 	}
1801 
1802 	return rc;
1803 }
1804 
1805 static int
1806 OpenLDAPaciPretty(
1807 	Syntax		*syntax,
1808 	struct berval	*val,
1809 	struct berval	*out,
1810 	void		*ctx )
1811 {
1812 	return OpenLDAPaciPrettyNormal( val, out, ctx, 0 );
1813 }
1814 
1815 static int
1816 OpenLDAPaciNormalize(
1817 	slap_mask_t	use,
1818 	Syntax		*syntax,
1819 	MatchingRule	*mr,
1820 	struct berval	*val,
1821 	struct berval	*out,
1822 	void		*ctx )
1823 {
1824 	return OpenLDAPaciPrettyNormal( val, out, ctx, 1 );
1825 }
1826 
1827 #if SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC
1828 /*
1829  * FIXME: need config and Makefile.am code to ease building
1830  * as dynamic module
1831  */
1832 int
1833 init_module( int argc, char *argv[] )
1834 {
1835 	return dynacl_aci_init();
1836 }
1837 #endif /* SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC */
1838 
1839 #endif /* SLAPD_ACI_ENABLED */
1840 
1841