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