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