1/*	$NetBSD$	*/
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
54typedef 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
60enum {
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
93static 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
131static AttributeDescription	*slap_ad_aci;
132
133static int
134OpenLDAPaciValidate(
135	Syntax		*syntax,
136	struct berval	*val );
137
138static int
139OpenLDAPaciPretty(
140	Syntax		*syntax,
141	struct berval	*val,
142	struct berval	*out,
143	void		*ctx );
144
145static int
146OpenLDAPaciNormalize(
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
156static int
157aci_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
211static int
212aci_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
269static slap_access_t
270aci_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
313static int
314aci_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
361static int
362aci_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
427done:
428	return rc;
429}
430
431static int
432aci_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
682static int
683aci_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
745static int
746dynacl_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
789static int
790dynacl_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
805static int
806dynacl_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 */
963static 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
973int
974dynacl_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 */
996static int
997bv_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 */
1018static void
1019bv_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 */
1048static int
1049OpenLDAPaciValidatePerms(
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
1091static const struct berval *ACIgrantdeny[] = {
1092	&aci_bv[ ACI_BV_GRANT ],
1093	&aci_bv[ ACI_BV_DENY ],
1094	NULL
1095};
1096
1097static int
1098OpenLDAPaciValidateRight(
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
1168static int
1169OpenLDAPaciNormalizeRight(
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
1308static int
1309OpenLDAPaciValidateRights(
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
1325static int
1326OpenLDAPaciNormalizeRights(
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
1367static 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
1375static 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
1402static int
1403OpenLDAPaciValidate(
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
1551static int
1552OpenLDAPaciPrettyNormal(
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
1786cleanup:;
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
1802static int
1803OpenLDAPaciPretty(
1804	Syntax		*syntax,
1805	struct berval	*val,
1806	struct berval	*out,
1807	void		*ctx )
1808{
1809	return OpenLDAPaciPrettyNormal( val, out, ctx, 0 );
1810}
1811
1812static int
1813OpenLDAPaciNormalize(
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 */
1829int
1830init_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