aclparse.c revision 1.1
1/* aclparse.c - routines to parse and check acl's */
2/* $OpenLDAP: pkg/ldap/servers/slapd/aclparse.c,v 1.198.2.6 2008/02/11 23:26:43 kurt Exp $ */
3/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 *
5 * Copyright 1998-2008 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#include <stdio.h>
30
31#include <ac/ctype.h>
32#include <ac/regex.h>
33#include <ac/socket.h>
34#include <ac/string.h>
35#include <ac/unistd.h>
36
37#include "slap.h"
38#include "lber_pvt.h"
39#include "lutil.h"
40
41static const char style_base[] = "base";
42const char *style_strings[] = {
43	"regex",
44	"expand",
45	"exact",
46	"one",
47	"subtree",
48	"children",
49	"level",
50	"attrof",
51	"anonymous",
52	"users",
53	"self",
54	"ip",
55	"ipv6",
56	"path",
57	NULL
58};
59
60static void		split(char *line, int splitchar, char **left, char **right);
61static void		access_append(Access **l, Access *a);
62static void		access_free( Access *a );
63static int		acl_usage(void);
64
65static void		acl_regex_normalized_dn(const char *src, struct berval *pat);
66
67#ifdef LDAP_DEBUG
68static void		print_acl(Backend *be, AccessControl *a);
69#endif
70
71static int		check_scope( BackendDB *be, AccessControl *a );
72
73#ifdef SLAP_DYNACL
74static int
75slap_dynacl_config(
76	const char *fname,
77	int lineno,
78	Access *b,
79	const char *name,
80	const char *opts,
81	slap_style_t sty,
82	const char *right )
83{
84	slap_dynacl_t	*da, *tmp;
85	int		rc = 0;
86
87	for ( da = b->a_dynacl; da; da = da->da_next ) {
88		if ( strcasecmp( da->da_name, name ) == 0 ) {
89			Debug( LDAP_DEBUG_ANY,
90				"%s: line %d: dynacl \"%s\" already specified.\n",
91				fname, lineno, name );
92			return acl_usage();
93		}
94	}
95
96	da = slap_dynacl_get( name );
97	if ( da == NULL ) {
98		return -1;
99	}
100
101	tmp = ch_malloc( sizeof( slap_dynacl_t ) );
102	*tmp = *da;
103
104	if ( tmp->da_parse ) {
105		rc = ( *tmp->da_parse )( fname, lineno, opts, sty, right, &tmp->da_private );
106		if ( rc ) {
107			ch_free( tmp );
108			return rc;
109		}
110	}
111
112	tmp->da_next = b->a_dynacl;
113	b->a_dynacl = tmp;
114
115	return 0;
116}
117#endif /* SLAP_DYNACL */
118
119static void
120regtest(const char *fname, int lineno, char *pat) {
121	int e;
122	regex_t re;
123
124	char		buf[ SLAP_TEXT_BUFLEN ];
125	unsigned	size;
126
127	char *sp;
128	char *dp;
129	int  flag;
130
131	sp = pat;
132	dp = buf;
133	size = 0;
134	buf[0] = '\0';
135
136	for (size = 0, flag = 0; (size < sizeof(buf)) && *sp; sp++) {
137		if (flag) {
138			if (*sp == '$'|| (*sp >= '0' && *sp <= '9')) {
139				*dp++ = *sp;
140				size++;
141			}
142			flag = 0;
143
144		} else {
145			if (*sp == '$') {
146				flag = 1;
147			} else {
148				*dp++ = *sp;
149				size++;
150			}
151		}
152	}
153
154	*dp = '\0';
155	if ( size >= (sizeof(buf) - 1) ) {
156		Debug( LDAP_DEBUG_ANY,
157			"%s: line %d: regular expression \"%s\" too large\n",
158			fname, lineno, pat );
159		(void)acl_usage();
160		exit( EXIT_FAILURE );
161	}
162
163	if ((e = regcomp(&re, buf, REG_EXTENDED|REG_ICASE))) {
164		char error[ SLAP_TEXT_BUFLEN ];
165
166		regerror(e, &re, error, sizeof(error));
167
168		snprintf( buf, sizeof( buf ),
169			"regular expression \"%s\" bad because of %s",
170			pat, error );
171		Debug( LDAP_DEBUG_ANY,
172			"%s: line %d: %s\n",
173			fname, lineno, buf );
174		acl_usage();
175		exit( EXIT_FAILURE );
176	}
177	regfree(&re);
178}
179
180/*
181 * Experimental
182 *
183 * Check if the pattern of an ACL, if any, matches the scope
184 * of the backend it is defined within.
185 */
186#define	ACL_SCOPE_UNKNOWN	(-2)
187#define	ACL_SCOPE_ERR		(-1)
188#define	ACL_SCOPE_OK		(0)
189#define	ACL_SCOPE_PARTIAL	(1)
190#define	ACL_SCOPE_WARN		(2)
191
192static int
193check_scope( BackendDB *be, AccessControl *a )
194{
195	ber_len_t	patlen;
196	struct berval	dn;
197
198	dn = be->be_nsuffix[0];
199
200	if ( BER_BVISEMPTY( &dn ) ) {
201		return ACL_SCOPE_OK;
202	}
203
204	if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
205			a->acl_dn_style != ACL_STYLE_REGEX )
206	{
207		slap_style_t	style = a->acl_dn_style;
208
209		if ( style == ACL_STYLE_REGEX ) {
210			char		dnbuf[SLAP_LDAPDN_MAXLEN + 2];
211			char		rebuf[SLAP_LDAPDN_MAXLEN + 1];
212			ber_len_t	rebuflen;
213			regex_t		re;
214			int		rc;
215
216			/* add trailing '$' to database suffix to form
217			 * a simple trial regex pattern "<suffix>$" */
218			AC_MEMCPY( dnbuf, be->be_nsuffix[0].bv_val,
219				be->be_nsuffix[0].bv_len );
220			dnbuf[be->be_nsuffix[0].bv_len] = '$';
221			dnbuf[be->be_nsuffix[0].bv_len + 1] = '\0';
222
223			if ( regcomp( &re, dnbuf, REG_EXTENDED|REG_ICASE ) ) {
224				return ACL_SCOPE_WARN;
225			}
226
227			/* remove trailing ')$', if any, from original
228			 * regex pattern */
229			rebuflen = a->acl_dn_pat.bv_len;
230			AC_MEMCPY( rebuf, a->acl_dn_pat.bv_val, rebuflen + 1 );
231			if ( rebuf[rebuflen - 1] == '$' ) {
232				rebuf[--rebuflen] = '\0';
233			}
234			while ( rebuflen > be->be_nsuffix[0].bv_len && rebuf[rebuflen - 1] == ')' ) {
235				rebuf[--rebuflen] = '\0';
236			}
237			if ( rebuflen == be->be_nsuffix[0].bv_len ) {
238				rc = ACL_SCOPE_WARN;
239				goto regex_done;
240			}
241
242			/* not a clear indication of scoping error, though */
243			rc = regexec( &re, rebuf, 0, NULL, 0 )
244				? ACL_SCOPE_WARN : ACL_SCOPE_OK;
245
246regex_done:;
247			regfree( &re );
248			return rc;
249		}
250
251		patlen = a->acl_dn_pat.bv_len;
252		/* If backend suffix is longer than pattern,
253		 * it is a potential mismatch (in the sense
254		 * that a superior naming context could
255		 * match */
256		if ( dn.bv_len > patlen ) {
257			/* base is blatantly wrong */
258			if ( style == ACL_STYLE_BASE ) return ACL_SCOPE_ERR;
259
260			/* a style of one can be wrong if there is
261			 * more than one level between the suffix
262			 * and the pattern */
263			if ( style == ACL_STYLE_ONE ) {
264				ber_len_t	rdnlen = 0;
265				int		sep = 0;
266
267				if ( patlen > 0 ) {
268					if ( !DN_SEPARATOR( dn.bv_val[dn.bv_len - patlen - 1] )) {
269						return ACL_SCOPE_ERR;
270					}
271					sep = 1;
272				}
273
274				rdnlen = dn_rdnlen( NULL, &dn );
275				if ( rdnlen != dn.bv_len - patlen - sep )
276					return ACL_SCOPE_ERR;
277			}
278
279			/* if the trailing part doesn't match,
280			 * then it's an error */
281			if ( strcmp( a->acl_dn_pat.bv_val,
282				&dn.bv_val[dn.bv_len - patlen] ) != 0 )
283			{
284				return ACL_SCOPE_ERR;
285			}
286
287			return ACL_SCOPE_PARTIAL;
288		}
289
290		switch ( style ) {
291		case ACL_STYLE_BASE:
292		case ACL_STYLE_ONE:
293		case ACL_STYLE_CHILDREN:
294		case ACL_STYLE_SUBTREE:
295			break;
296
297		default:
298			assert( 0 );
299			break;
300		}
301
302		if ( dn.bv_len < patlen &&
303			!DN_SEPARATOR( a->acl_dn_pat.bv_val[patlen - dn.bv_len - 1] ))
304		{
305			return ACL_SCOPE_ERR;
306		}
307
308		if ( strcmp( &a->acl_dn_pat.bv_val[patlen - dn.bv_len], dn.bv_val )
309			!= 0 )
310		{
311			return ACL_SCOPE_ERR;
312		}
313
314		return ACL_SCOPE_OK;
315	}
316
317	return ACL_SCOPE_UNKNOWN;
318}
319
320int
321parse_acl(
322	Backend	*be,
323	const char	*fname,
324	int		lineno,
325	int		argc,
326	char		**argv,
327	int		pos )
328{
329	int		i;
330	char		*left, *right, *style;
331	struct berval	bv;
332	AccessControl	*a = NULL;
333	Access	*b = NULL;
334	int rc;
335	const char *text;
336
337	for ( i = 1; i < argc; i++ ) {
338		/* to clause - select which entries are protected */
339		if ( strcasecmp( argv[i], "to" ) == 0 ) {
340			if ( a != NULL ) {
341				Debug( LDAP_DEBUG_ANY, "%s: line %d: "
342					"only one to clause allowed in access line\n",
343				    fname, lineno, 0 );
344				goto fail;
345			}
346			a = (AccessControl *) ch_calloc( 1, sizeof(AccessControl) );
347			for ( ++i; i < argc; i++ ) {
348				if ( strcasecmp( argv[i], "by" ) == 0 ) {
349					i--;
350					break;
351				}
352
353				if ( strcasecmp( argv[i], "*" ) == 0 ) {
354					if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
355						a->acl_dn_style != ACL_STYLE_REGEX )
356					{
357						Debug( LDAP_DEBUG_ANY,
358							"%s: line %d: dn pattern"
359							" already specified in to clause.\n",
360							fname, lineno, 0 );
361						goto fail;
362					}
363
364					ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
365					continue;
366				}
367
368				split( argv[i], '=', &left, &right );
369				split( left, '.', &left, &style );
370
371				if ( right == NULL ) {
372					Debug( LDAP_DEBUG_ANY, "%s: line %d: "
373						"missing \"=\" in \"%s\" in to clause\n",
374					    fname, lineno, left );
375					goto fail;
376				}
377
378				if ( strcasecmp( left, "dn" ) == 0 ) {
379					if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
380						a->acl_dn_style != ACL_STYLE_REGEX )
381					{
382						Debug( LDAP_DEBUG_ANY,
383							"%s: line %d: dn pattern"
384							" already specified in to clause.\n",
385							fname, lineno, 0 );
386						goto fail;
387					}
388
389					if ( style == NULL || *style == '\0' ||
390						strcasecmp( style, "baseObject" ) == 0 ||
391						strcasecmp( style, "base" ) == 0 ||
392						strcasecmp( style, "exact" ) == 0 )
393					{
394						a->acl_dn_style = ACL_STYLE_BASE;
395						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
396
397					} else if ( strcasecmp( style, "oneLevel" ) == 0 ||
398						strcasecmp( style, "one" ) == 0 )
399					{
400						a->acl_dn_style = ACL_STYLE_ONE;
401						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
402
403					} else if ( strcasecmp( style, "subtree" ) == 0 ||
404						strcasecmp( style, "sub" ) == 0 )
405					{
406						if( *right == '\0' ) {
407							ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
408
409						} else {
410							a->acl_dn_style = ACL_STYLE_SUBTREE;
411							ber_str2bv( right, 0, 1, &a->acl_dn_pat );
412						}
413
414					} else if ( strcasecmp( style, "children" ) == 0 ) {
415						a->acl_dn_style = ACL_STYLE_CHILDREN;
416						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
417
418					} else if ( strcasecmp( style, "regex" ) == 0 ) {
419						a->acl_dn_style = ACL_STYLE_REGEX;
420
421						if ( *right == '\0' ) {
422							/* empty regex should match empty DN */
423							a->acl_dn_style = ACL_STYLE_BASE;
424							ber_str2bv( right, 0, 1, &a->acl_dn_pat );
425
426						} else if ( strcmp(right, "*") == 0
427							|| strcmp(right, ".*") == 0
428							|| strcmp(right, ".*$") == 0
429							|| strcmp(right, "^.*") == 0
430							|| strcmp(right, "^.*$") == 0
431							|| strcmp(right, ".*$$") == 0
432							|| strcmp(right, "^.*$$") == 0 )
433						{
434							ber_str2bv( "*", STRLENOF("*"), 1, &a->acl_dn_pat );
435
436						} else {
437							acl_regex_normalized_dn( right, &a->acl_dn_pat );
438						}
439
440					} else {
441						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
442							"unknown dn style \"%s\" in to clause\n",
443						    fname, lineno, style );
444						goto fail;
445					}
446
447					continue;
448				}
449
450				if ( strcasecmp( left, "filter" ) == 0 ) {
451					if ( (a->acl_filter = str2filter( right )) == NULL ) {
452						Debug( LDAP_DEBUG_ANY,
453				"%s: line %d: bad filter \"%s\" in to clause\n",
454						    fname, lineno, right );
455						goto fail;
456					}
457
458				} else if ( strcasecmp( left, "attr" ) == 0		/* TOLERATED */
459						|| strcasecmp( left, "attrs" ) == 0 )	/* DOCUMENTED */
460				{
461					if ( strcasecmp( left, "attr" ) == 0 ) {
462						Debug( LDAP_DEBUG_ANY,
463							"%s: line %d: \"attr\" "
464							"is deprecated (and undocumented); "
465							"use \"attrs\" instead.\n",
466							fname, lineno, 0 );
467					}
468
469					a->acl_attrs = str2anlist( a->acl_attrs,
470						right, "," );
471					if ( a->acl_attrs == NULL ) {
472						Debug( LDAP_DEBUG_ANY,
473				"%s: line %d: unknown attr \"%s\" in to clause\n",
474						    fname, lineno, right );
475						goto fail;
476					}
477
478				} else if ( strncasecmp( left, "val", 3 ) == 0 ) {
479					struct berval	bv;
480					char		*mr;
481
482					if ( !BER_BVISEMPTY( &a->acl_attrval ) ) {
483						Debug( LDAP_DEBUG_ANY,
484				"%s: line %d: attr val already specified in to clause.\n",
485							fname, lineno, 0 );
486						goto fail;
487					}
488					if ( a->acl_attrs == NULL || !BER_BVISEMPTY( &a->acl_attrs[1].an_name ) )
489					{
490						Debug( LDAP_DEBUG_ANY,
491				"%s: line %d: attr val requires a single attribute.\n",
492							fname, lineno, 0 );
493						goto fail;
494					}
495
496					ber_str2bv( right, 0, 0, &bv );
497					a->acl_attrval_style = ACL_STYLE_BASE;
498
499					mr = strchr( left, '/' );
500					if ( mr != NULL ) {
501						mr[ 0 ] = '\0';
502						mr++;
503
504						a->acl_attrval_mr = mr_find( mr );
505						if ( a->acl_attrval_mr == NULL ) {
506							Debug( LDAP_DEBUG_ANY, "%s: line %d: "
507								"invalid matching rule \"%s\".\n",
508								fname, lineno, mr );
509							goto fail;
510						}
511
512						if( !mr_usable_with_at( a->acl_attrval_mr, a->acl_attrs[ 0 ].an_desc->ad_type ) )
513						{
514							char	buf[ SLAP_TEXT_BUFLEN ];
515
516							snprintf( buf, sizeof( buf ),
517								"matching rule \"%s\" use "
518								"with attr \"%s\" not appropriate.",
519								mr, a->acl_attrs[ 0 ].an_name.bv_val );
520
521
522							Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
523								fname, lineno, buf );
524							goto fail;
525						}
526					}
527
528					if ( style != NULL ) {
529						if ( strcasecmp( style, "regex" ) == 0 ) {
530							int e = regcomp( &a->acl_attrval_re, bv.bv_val,
531								REG_EXTENDED | REG_ICASE | REG_NOSUB );
532							if ( e ) {
533								char	err[SLAP_TEXT_BUFLEN],
534									buf[ SLAP_TEXT_BUFLEN ];
535
536								regerror( e, &a->acl_attrval_re, err, sizeof( err ) );
537
538								snprintf( buf, sizeof( buf ),
539									"regular expression \"%s\" bad because of %s",
540									right, err );
541
542								Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
543									fname, lineno, buf );
544								goto fail;
545							}
546							a->acl_attrval_style = ACL_STYLE_REGEX;
547
548						} else {
549							/* FIXME: if the attribute has DN syntax, we might
550							 * allow one, subtree and children styles as well */
551							if ( !strcasecmp( style, "base" ) ||
552								!strcasecmp( style, "exact" ) ) {
553								a->acl_attrval_style = ACL_STYLE_BASE;
554
555							} else if ( a->acl_attrs[0].an_desc->ad_type->
556								sat_syntax == slap_schema.si_syn_distinguishedName )
557							{
558								if ( !strcasecmp( style, "baseObject" ) ||
559									!strcasecmp( style, "base" ) )
560								{
561									a->acl_attrval_style = ACL_STYLE_BASE;
562								} else if ( !strcasecmp( style, "onelevel" ) ||
563									!strcasecmp( style, "one" ) )
564								{
565									a->acl_attrval_style = ACL_STYLE_ONE;
566								} else if ( !strcasecmp( style, "subtree" ) ||
567									!strcasecmp( style, "sub" ) )
568								{
569									a->acl_attrval_style = ACL_STYLE_SUBTREE;
570								} else if ( !strcasecmp( style, "children" ) ) {
571									a->acl_attrval_style = ACL_STYLE_CHILDREN;
572								} else {
573									char	buf[ SLAP_TEXT_BUFLEN ];
574
575									snprintf( buf, sizeof( buf ),
576										"unknown val.<style> \"%s\" for attributeType \"%s\" "
577											"with DN syntax.",
578										style,
579										a->acl_attrs[0].an_desc->ad_cname.bv_val );
580
581									Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL,
582										"%s: line %d: %s\n",
583										fname, lineno, buf );
584									goto fail;
585								}
586
587								rc = dnNormalize( 0, NULL, NULL, &bv, &a->acl_attrval, NULL );
588								if ( rc != LDAP_SUCCESS ) {
589									char	buf[ SLAP_TEXT_BUFLEN ];
590
591									snprintf( buf, sizeof( buf ),
592										"unable to normalize DN \"%s\" "
593										"for attributeType \"%s\" (%d).",
594										bv.bv_val,
595										a->acl_attrs[0].an_desc->ad_cname.bv_val,
596										rc );
597									Debug( LDAP_DEBUG_ANY,
598										"%s: line %d: %s\n",
599										fname, lineno, buf );
600									goto fail;
601								}
602
603							} else {
604								char	buf[ SLAP_TEXT_BUFLEN ];
605
606								snprintf( buf, sizeof( buf ),
607									"unknown val.<style> \"%s\" for attributeType \"%s\".",
608									style, a->acl_attrs[0].an_desc->ad_cname.bv_val );
609								Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL,
610									"%s: line %d: %s\n",
611									fname, lineno, buf );
612								goto fail;
613							}
614						}
615					}
616
617					/* Check for appropriate matching rule */
618					if ( a->acl_attrval_style == ACL_STYLE_REGEX ) {
619						ber_dupbv( &a->acl_attrval, &bv );
620
621					} else if ( BER_BVISNULL( &a->acl_attrval ) ) {
622						int		rc;
623						const char	*text;
624
625						if ( a->acl_attrval_mr == NULL ) {
626							a->acl_attrval_mr = a->acl_attrs[ 0 ].an_desc->ad_type->sat_equality;
627						}
628
629						if ( a->acl_attrval_mr == NULL ) {
630							Debug( LDAP_DEBUG_ANY, "%s: line %d: "
631								"attr \"%s\" does not have an EQUALITY matching rule.\n",
632								fname, lineno, a->acl_attrs[ 0 ].an_name.bv_val );
633							goto fail;
634						}
635
636						rc = asserted_value_validate_normalize(
637							a->acl_attrs[ 0 ].an_desc,
638							a->acl_attrval_mr,
639							SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
640							&bv,
641							&a->acl_attrval,
642							&text,
643							NULL );
644						if ( rc != LDAP_SUCCESS ) {
645							char	buf[ SLAP_TEXT_BUFLEN ];
646
647							snprintf( buf, sizeof( buf ), "%s: line %d: "
648								" attr \"%s\" normalization failed (%d: %s)",
649								fname, lineno,
650								a->acl_attrs[ 0 ].an_name.bv_val, rc, text );
651							Debug( LDAP_DEBUG_ANY, "%s: line %d: %s.\n",
652								fname, lineno, buf );
653							goto fail;
654						}
655					}
656
657				} else {
658					Debug( LDAP_DEBUG_ANY,
659						"%s: line %d: expecting <what> got \"%s\"\n",
660					    fname, lineno, left );
661					goto fail;
662				}
663			}
664
665			if ( !BER_BVISNULL( &a->acl_dn_pat ) &&
666					ber_bvccmp( &a->acl_dn_pat, '*' ) )
667			{
668				free( a->acl_dn_pat.bv_val );
669				BER_BVZERO( &a->acl_dn_pat );
670				a->acl_dn_style = ACL_STYLE_REGEX;
671			}
672
673			if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
674					a->acl_dn_style != ACL_STYLE_REGEX )
675			{
676				if ( a->acl_dn_style != ACL_STYLE_REGEX ) {
677					struct berval bv;
678					rc = dnNormalize( 0, NULL, NULL, &a->acl_dn_pat, &bv, NULL);
679					if ( rc != LDAP_SUCCESS ) {
680						Debug( LDAP_DEBUG_ANY,
681							"%s: line %d: bad DN \"%s\" in to DN clause\n",
682							fname, lineno, a->acl_dn_pat.bv_val );
683						goto fail;
684					}
685					free( a->acl_dn_pat.bv_val );
686					a->acl_dn_pat = bv;
687
688				} else {
689					int e = regcomp( &a->acl_dn_re, a->acl_dn_pat.bv_val,
690						REG_EXTENDED | REG_ICASE );
691					if ( e ) {
692						char	err[ SLAP_TEXT_BUFLEN ],
693							buf[ SLAP_TEXT_BUFLEN ];
694
695						regerror( e, &a->acl_dn_re, err, sizeof( err ) );
696						snprintf( buf, sizeof( buf ),
697							"regular expression \"%s\" bad because of %s",
698							right, err );
699						Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
700							fname, lineno, buf );
701						goto fail;
702					}
703				}
704			}
705
706		/* by clause - select who has what access to entries */
707		} else if ( strcasecmp( argv[i], "by" ) == 0 ) {
708			if ( a == NULL ) {
709				Debug( LDAP_DEBUG_ANY, "%s: line %d: "
710					"to clause required before by clause in access line\n",
711					fname, lineno, 0 );
712				goto fail;
713			}
714
715			/*
716			 * by clause consists of <who> and <access>
717			 */
718
719			if ( ++i == argc ) {
720				Debug( LDAP_DEBUG_ANY,
721					"%s: line %d: premature EOL: expecting <who>\n",
722					fname, lineno, 0 );
723				goto fail;
724			}
725
726			b = (Access *) ch_calloc( 1, sizeof(Access) );
727
728			ACL_INVALIDATE( b->a_access_mask );
729
730			/* get <who> */
731			for ( ; i < argc; i++ ) {
732				slap_style_t	sty = ACL_STYLE_REGEX;
733				char		*style_modifier = NULL;
734				char		*style_level = NULL;
735				int		level = 0;
736				int		expand = 0;
737				slap_dn_access	*bdn = &b->a_dn;
738				int		is_realdn = 0;
739
740				split( argv[i], '=', &left, &right );
741				split( left, '.', &left, &style );
742				if ( style ) {
743					split( style, ',', &style, &style_modifier );
744
745					if ( strncasecmp( style, "level", STRLENOF( "level" ) ) == 0 ) {
746						split( style, '{', &style, &style_level );
747						if ( style_level != NULL ) {
748							char *p = strchr( style_level, '}' );
749							if ( p == NULL ) {
750								Debug( LDAP_DEBUG_ANY,
751									"%s: line %d: premature eol: "
752									"expecting closing '}' in \"level{n}\"\n",
753									fname, lineno, 0 );
754								goto fail;
755							} else if ( p == style_level ) {
756								Debug( LDAP_DEBUG_ANY,
757									"%s: line %d: empty level "
758									"in \"level{n}\"\n",
759									fname, lineno, 0 );
760								goto fail;
761							}
762							p[0] = '\0';
763						}
764					}
765				}
766
767				if ( style == NULL || *style == '\0' ||
768					strcasecmp( style, "exact" ) == 0 ||
769					strcasecmp( style, "baseObject" ) == 0 ||
770					strcasecmp( style, "base" ) == 0 )
771				{
772					sty = ACL_STYLE_BASE;
773
774				} else if ( strcasecmp( style, "onelevel" ) == 0 ||
775					strcasecmp( style, "one" ) == 0 )
776				{
777					sty = ACL_STYLE_ONE;
778
779				} else if ( strcasecmp( style, "subtree" ) == 0 ||
780					strcasecmp( style, "sub" ) == 0 )
781				{
782					sty = ACL_STYLE_SUBTREE;
783
784				} else if ( strcasecmp( style, "children" ) == 0 ) {
785					sty = ACL_STYLE_CHILDREN;
786
787				} else if ( strcasecmp( style, "level" ) == 0 )
788				{
789					if ( lutil_atoi( &level, style_level ) != 0 ) {
790						Debug( LDAP_DEBUG_ANY,
791							"%s: line %d: unable to parse level "
792							"in \"level{n}\"\n",
793							fname, lineno, 0 );
794						goto fail;
795					}
796
797					sty = ACL_STYLE_LEVEL;
798
799				} else if ( strcasecmp( style, "regex" ) == 0 ) {
800					sty = ACL_STYLE_REGEX;
801
802				} else if ( strcasecmp( style, "expand" ) == 0 ) {
803					sty = ACL_STYLE_EXPAND;
804
805				} else if ( strcasecmp( style, "ip" ) == 0 ) {
806					sty = ACL_STYLE_IP;
807
808				} else if ( strcasecmp( style, "ipv6" ) == 0 ) {
809#ifndef LDAP_PF_INET6
810					Debug( LDAP_DEBUG_ANY,
811						"%s: line %d: IPv6 not supported\n",
812						fname, lineno, 0 );
813#endif /* ! LDAP_PF_INET6 */
814					sty = ACL_STYLE_IPV6;
815
816				} else if ( strcasecmp( style, "path" ) == 0 ) {
817					sty = ACL_STYLE_PATH;
818#ifndef LDAP_PF_LOCAL
819					Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL,
820						"%s: line %d: "
821						"\"path\" style modifier is useless without local.\n",
822						fname, lineno, 0 );
823					goto fail;
824#endif /* LDAP_PF_LOCAL */
825
826				} else {
827					Debug( LDAP_DEBUG_ANY,
828						"%s: line %d: unknown style \"%s\" in by clause\n",
829						fname, lineno, style );
830					goto fail;
831				}
832
833				if ( style_modifier &&
834					strcasecmp( style_modifier, "expand" ) == 0 )
835				{
836					switch ( sty ) {
837					case ACL_STYLE_REGEX:
838						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
839							"\"regex\" style implies \"expand\" modifier.\n",
840							fname, lineno, 0 );
841						goto fail;
842						break;
843
844					case ACL_STYLE_EXPAND:
845						break;
846
847					default:
848						/* we'll see later if it's pertinent */
849						expand = 1;
850						break;
851					}
852				}
853
854				/* expand in <who> needs regex in <what> */
855				if ( ( sty == ACL_STYLE_EXPAND || expand )
856						&& a->acl_dn_style != ACL_STYLE_REGEX )
857				{
858					Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, "%s: line %d: \"expand\" style "
859						"or modifier used in conjunction with a non-regex <what> clause.\n",
860						fname, lineno, 0 );
861						goto fail;
862				}
863
864				if ( strncasecmp( left, "real", STRLENOF( "real" ) ) == 0 ) {
865					is_realdn = 1;
866					bdn = &b->a_realdn;
867					left += STRLENOF( "real" );
868				}
869
870				if ( strcasecmp( left, "*" ) == 0 ) {
871					if ( is_realdn ) {
872						goto fail;
873					}
874
875					ber_str2bv( "*", STRLENOF( "*" ), 1, &bv );
876					sty = ACL_STYLE_REGEX;
877
878				} else if ( strcasecmp( left, "anonymous" ) == 0 ) {
879					ber_str2bv("anonymous", STRLENOF( "anonymous" ), 1, &bv);
880					sty = ACL_STYLE_ANONYMOUS;
881
882				} else if ( strcasecmp( left, "users" ) == 0 ) {
883					ber_str2bv("users", STRLENOF( "users" ), 1, &bv);
884					sty = ACL_STYLE_USERS;
885
886				} else if ( strcasecmp( left, "self" ) == 0 ) {
887					ber_str2bv("self", STRLENOF( "self" ), 1, &bv);
888					sty = ACL_STYLE_SELF;
889
890				} else if ( strcasecmp( left, "dn" ) == 0 ) {
891					if ( sty == ACL_STYLE_REGEX ) {
892						bdn->a_style = ACL_STYLE_REGEX;
893						if ( right == NULL ) {
894							/* no '=' */
895							ber_str2bv("users",
896								STRLENOF( "users" ),
897								1, &bv);
898							bdn->a_style = ACL_STYLE_USERS;
899
900						} else if (*right == '\0' ) {
901							/* dn="" */
902							ber_str2bv("anonymous",
903								STRLENOF( "anonymous" ),
904								1, &bv);
905							bdn->a_style = ACL_STYLE_ANONYMOUS;
906
907						} else if ( strcmp( right, "*" ) == 0 ) {
908							/* dn=* */
909							/* any or users?  users for now */
910							ber_str2bv("users",
911								STRLENOF( "users" ),
912								1, &bv);
913							bdn->a_style = ACL_STYLE_USERS;
914
915						} else if ( strcmp( right, ".+" ) == 0
916							|| strcmp( right, "^.+" ) == 0
917							|| strcmp( right, ".+$" ) == 0
918							|| strcmp( right, "^.+$" ) == 0
919							|| strcmp( right, ".+$$" ) == 0
920							|| strcmp( right, "^.+$$" ) == 0 )
921						{
922							ber_str2bv("users",
923								STRLENOF( "users" ),
924								1, &bv);
925							bdn->a_style = ACL_STYLE_USERS;
926
927						} else if ( strcmp( right, ".*" ) == 0
928							|| strcmp( right, "^.*" ) == 0
929							|| strcmp( right, ".*$" ) == 0
930							|| strcmp( right, "^.*$" ) == 0
931							|| strcmp( right, ".*$$" ) == 0
932							|| strcmp( right, "^.*$$" ) == 0 )
933						{
934							ber_str2bv("*",
935								STRLENOF( "*" ),
936								1, &bv);
937
938						} else {
939							acl_regex_normalized_dn( right, &bv );
940							if ( !ber_bvccmp( &bv, '*' ) ) {
941								regtest( fname, lineno, bv.bv_val );
942							}
943						}
944
945					} else if ( right == NULL || *right == '\0' ) {
946						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
947							"missing \"=\" in (or value after) \"%s\" "
948							"in by clause\n",
949							fname, lineno, left );
950						goto fail;
951
952					} else {
953						ber_str2bv( right, 0, 1, &bv );
954					}
955
956				} else {
957					BER_BVZERO( &bv );
958				}
959
960				if ( !BER_BVISNULL( &bv ) ) {
961					if ( !BER_BVISEMPTY( &bdn->a_pat ) ) {
962						Debug( LDAP_DEBUG_ANY,
963							"%s: line %d: dn pattern already specified.\n",
964							fname, lineno, 0 );
965						goto fail;
966					}
967
968					if ( sty != ACL_STYLE_REGEX &&
969							sty != ACL_STYLE_ANONYMOUS &&
970							sty != ACL_STYLE_USERS &&
971							sty != ACL_STYLE_SELF &&
972							expand == 0 )
973					{
974						rc = dnNormalize(0, NULL, NULL,
975							&bv, &bdn->a_pat, NULL);
976						if ( rc != LDAP_SUCCESS ) {
977							Debug( LDAP_DEBUG_ANY,
978								"%s: line %d: bad DN \"%s\" in by DN clause\n",
979								fname, lineno, bv.bv_val );
980							goto fail;
981						}
982						free( bv.bv_val );
983						if ( sty == ACL_STYLE_BASE
984							&& be != NULL
985							&& !BER_BVISNULL( &be->be_rootndn )
986							&& dn_match( &bdn->a_pat, &be->be_rootndn ) )
987						{
988							Debug( LDAP_DEBUG_ANY,
989								"%s: line %d: rootdn is always granted "
990								"unlimited privileges.\n",
991								fname, lineno, 0 );
992						}
993
994					} else {
995						bdn->a_pat = bv;
996					}
997					bdn->a_style = sty;
998					if ( expand ) {
999						char	*exp;
1000						int	gotit = 0;
1001
1002						for ( exp = strchr( bdn->a_pat.bv_val, '$' );
1003							exp && (ber_len_t)(exp - bdn->a_pat.bv_val)
1004								< bdn->a_pat.bv_len;
1005							exp = strchr( exp, '$' ) )
1006						{
1007							if ( isdigit( (unsigned char) exp[ 1 ] ) ) {
1008								gotit = 1;
1009								break;
1010							}
1011						}
1012
1013						if ( gotit == 1 ) {
1014							bdn->a_expand = expand;
1015
1016						} else {
1017							Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1018								"\"expand\" used with no expansions in \"pattern\".\n",
1019								fname, lineno, 0 );
1020							goto fail;
1021						}
1022					}
1023					if ( sty == ACL_STYLE_SELF ) {
1024						bdn->a_self_level = level;
1025
1026					} else {
1027						if ( level < 0 ) {
1028							Debug( LDAP_DEBUG_ANY,
1029								"%s: line %d: bad negative level \"%d\" "
1030								"in by DN clause\n",
1031								fname, lineno, level );
1032							goto fail;
1033						} else if ( level == 1 ) {
1034							Debug( LDAP_DEBUG_ANY,
1035								"%s: line %d: \"onelevel\" should be used "
1036								"instead of \"level{1}\" in by DN clause\n",
1037								fname, lineno, 0 );
1038						} else if ( level == 0 && sty == ACL_STYLE_LEVEL ) {
1039							Debug( LDAP_DEBUG_ANY,
1040								"%s: line %d: \"base\" should be used "
1041								"instead of \"level{0}\" in by DN clause\n",
1042								fname, lineno, 0 );
1043						}
1044
1045						bdn->a_level = level;
1046					}
1047					continue;
1048				}
1049
1050				if ( strcasecmp( left, "dnattr" ) == 0 ) {
1051					if ( right == NULL || right[0] == '\0' ) {
1052						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1053							"missing \"=\" in (or value after) \"%s\" "
1054							"in by clause\n",
1055							fname, lineno, left );
1056						goto fail;
1057					}
1058
1059					if( bdn->a_at != NULL ) {
1060						Debug( LDAP_DEBUG_ANY,
1061							"%s: line %d: dnattr already specified.\n",
1062							fname, lineno, 0 );
1063						goto fail;
1064					}
1065
1066					rc = slap_str2ad( right, &bdn->a_at, &text );
1067
1068					if( rc != LDAP_SUCCESS ) {
1069						char	buf[ SLAP_TEXT_BUFLEN ];
1070
1071						snprintf( buf, sizeof( buf ),
1072							"dnattr \"%s\": %s",
1073							right, text );
1074						Debug( LDAP_DEBUG_ANY,
1075							"%s: line %d: %s\n",
1076							fname, lineno, buf );
1077						goto fail;
1078					}
1079
1080
1081					if( !is_at_syntax( bdn->a_at->ad_type,
1082						SLAPD_DN_SYNTAX ) &&
1083						!is_at_syntax( bdn->a_at->ad_type,
1084						SLAPD_NAMEUID_SYNTAX ))
1085					{
1086						char	buf[ SLAP_TEXT_BUFLEN ];
1087
1088						snprintf( buf, sizeof( buf ),
1089							"dnattr \"%s\": "
1090							"inappropriate syntax: %s\n",
1091							right,
1092							bdn->a_at->ad_type->sat_syntax_oid );
1093						Debug( LDAP_DEBUG_ANY,
1094							"%s: line %d: %s\n",
1095							fname, lineno, buf );
1096						goto fail;
1097					}
1098
1099					if( bdn->a_at->ad_type->sat_equality == NULL ) {
1100						Debug( LDAP_DEBUG_ANY,
1101							"%s: line %d: dnattr \"%s\": "
1102							"inappropriate matching (no EQUALITY)\n",
1103							fname, lineno, right );
1104						goto fail;
1105					}
1106
1107					continue;
1108				}
1109
1110				if ( strncasecmp( left, "group", STRLENOF( "group" ) ) == 0 ) {
1111					char *name = NULL;
1112					char *value = NULL;
1113					char *attr_name = SLAPD_GROUP_ATTR;
1114
1115					switch ( sty ) {
1116					case ACL_STYLE_REGEX:
1117						/* legacy, tolerated */
1118						Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL,
1119							"%s: line %d: "
1120							"deprecated group style \"regex\"; "
1121							"use \"expand\" instead.\n",
1122							fname, lineno, 0 );
1123						sty = ACL_STYLE_EXPAND;
1124						break;
1125
1126					case ACL_STYLE_BASE:
1127						/* legal, traditional */
1128					case ACL_STYLE_EXPAND:
1129						/* legal, substring expansion; supersedes regex */
1130						break;
1131
1132					default:
1133						/* unknown */
1134						Debug( LDAP_DEBUG_ANY,
1135							"%s: line %d: "
1136							"inappropriate style \"%s\" in by clause.\n",
1137							fname, lineno, style );
1138						goto fail;
1139					}
1140
1141					if ( right == NULL || right[0] == '\0' ) {
1142						Debug( LDAP_DEBUG_ANY,
1143							"%s: line %d: "
1144							"missing \"=\" in (or value after) \"%s\" "
1145							"in by clause.\n",
1146							fname, lineno, left );
1147						goto fail;
1148					}
1149
1150					if ( !BER_BVISEMPTY( &b->a_group_pat ) ) {
1151						Debug( LDAP_DEBUG_ANY,
1152							"%s: line %d: group pattern already specified.\n",
1153							fname, lineno, 0 );
1154						goto fail;
1155					}
1156
1157					/* format of string is
1158						"group/objectClassValue/groupAttrName" */
1159					if ( ( value = strchr(left, '/') ) != NULL ) {
1160						*value++ = '\0';
1161						if ( *value && ( name = strchr( value, '/' ) ) != NULL ) {
1162							*name++ = '\0';
1163						}
1164					}
1165
1166					b->a_group_style = sty;
1167					if ( sty == ACL_STYLE_EXPAND ) {
1168						acl_regex_normalized_dn( right, &bv );
1169						if ( !ber_bvccmp( &bv, '*' ) ) {
1170							regtest( fname, lineno, bv.bv_val );
1171						}
1172						b->a_group_pat = bv;
1173
1174					} else {
1175						ber_str2bv( right, 0, 0, &bv );
1176						rc = dnNormalize( 0, NULL, NULL, &bv,
1177							&b->a_group_pat, NULL );
1178						if ( rc != LDAP_SUCCESS ) {
1179							Debug( LDAP_DEBUG_ANY,
1180								"%s: line %d: bad DN \"%s\".\n",
1181								fname, lineno, right );
1182							goto fail;
1183						}
1184					}
1185
1186					if ( value && *value ) {
1187						b->a_group_oc = oc_find( value );
1188						*--value = '/';
1189
1190						if ( b->a_group_oc == NULL ) {
1191							Debug( LDAP_DEBUG_ANY,
1192								"%s: line %d: group objectclass "
1193								"\"%s\" unknown.\n",
1194								fname, lineno, value );
1195							goto fail;
1196						}
1197
1198					} else {
1199						b->a_group_oc = oc_find( SLAPD_GROUP_CLASS );
1200
1201						if( b->a_group_oc == NULL ) {
1202							Debug( LDAP_DEBUG_ANY,
1203								"%s: line %d: group default objectclass "
1204								"\"%s\" unknown.\n",
1205								fname, lineno, SLAPD_GROUP_CLASS );
1206							goto fail;
1207						}
1208					}
1209
1210					if ( is_object_subclass( slap_schema.si_oc_referral,
1211						b->a_group_oc ) )
1212					{
1213						Debug( LDAP_DEBUG_ANY,
1214							"%s: line %d: group objectclass \"%s\" "
1215							"is subclass of referral.\n",
1216							fname, lineno, value );
1217						goto fail;
1218					}
1219
1220					if ( is_object_subclass( slap_schema.si_oc_alias,
1221						b->a_group_oc ) )
1222					{
1223						Debug( LDAP_DEBUG_ANY,
1224							"%s: line %d: group objectclass \"%s\" "
1225							"is subclass of alias.\n",
1226							fname, lineno, value );
1227						goto fail;
1228					}
1229
1230					if ( name && *name ) {
1231						attr_name = name;
1232						*--name = '/';
1233
1234					}
1235
1236					rc = slap_str2ad( attr_name, &b->a_group_at, &text );
1237					if ( rc != LDAP_SUCCESS ) {
1238						char	buf[ SLAP_TEXT_BUFLEN ];
1239
1240						snprintf( buf, sizeof( buf ),
1241							"group \"%s\": %s.",
1242							right, text );
1243						Debug( LDAP_DEBUG_ANY,
1244							"%s: line %d: %s\n",
1245							fname, lineno, buf );
1246						goto fail;
1247					}
1248
1249					if ( !is_at_syntax( b->a_group_at->ad_type,
1250							SLAPD_DN_SYNTAX ) /* e.g. "member" */
1251						&& !is_at_syntax( b->a_group_at->ad_type,
1252							SLAPD_NAMEUID_SYNTAX ) /* e.g. memberUID */
1253						&& !is_at_subtype( b->a_group_at->ad_type,
1254							slap_schema.si_ad_labeledURI->ad_type ) /* e.g. memberURL */ )
1255					{
1256						char	buf[ SLAP_TEXT_BUFLEN ];
1257
1258						snprintf( buf, sizeof( buf ),
1259							"group \"%s\" attr \"%s\": inappropriate syntax: %s; "
1260							"must be " SLAPD_DN_SYNTAX " (DN), "
1261							SLAPD_NAMEUID_SYNTAX " (NameUID) "
1262							"or a subtype of labeledURI.",
1263							right,
1264							attr_name,
1265							at_syntax( b->a_group_at->ad_type ) );
1266						Debug( LDAP_DEBUG_ANY,
1267							"%s: line %d: %s\n",
1268							fname, lineno, buf );
1269						goto fail;
1270					}
1271
1272
1273					{
1274						int rc;
1275						ObjectClass *ocs[2];
1276
1277						ocs[0] = b->a_group_oc;
1278						ocs[1] = NULL;
1279
1280						rc = oc_check_allowed( b->a_group_at->ad_type,
1281							ocs, NULL );
1282
1283						if( rc != 0 ) {
1284							char	buf[ SLAP_TEXT_BUFLEN ];
1285
1286							snprintf( buf, sizeof( buf ),
1287								"group: \"%s\" not allowed by \"%s\".",
1288								b->a_group_at->ad_cname.bv_val,
1289								b->a_group_oc->soc_oid );
1290							Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
1291								fname, lineno, buf );
1292							goto fail;
1293						}
1294					}
1295					continue;
1296				}
1297
1298				if ( strcasecmp( left, "peername" ) == 0 ) {
1299					switch ( sty ) {
1300					case ACL_STYLE_REGEX:
1301					case ACL_STYLE_BASE:
1302						/* legal, traditional */
1303					case ACL_STYLE_EXPAND:
1304						/* cheap replacement to regex for simple expansion */
1305					case ACL_STYLE_IP:
1306					case ACL_STYLE_IPV6:
1307					case ACL_STYLE_PATH:
1308						/* legal, peername specific */
1309						break;
1310
1311					default:
1312						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1313							"inappropriate style \"%s\" in by clause.\n",
1314						    fname, lineno, style );
1315						goto fail;
1316					}
1317
1318					if ( right == NULL || right[0] == '\0' ) {
1319						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1320							"missing \"=\" in (or value after) \"%s\" "
1321							"in by clause.\n",
1322							fname, lineno, left );
1323						goto fail;
1324					}
1325
1326					if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) {
1327						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1328							"peername pattern already specified.\n",
1329							fname, lineno, 0 );
1330						goto fail;
1331					}
1332
1333					b->a_peername_style = sty;
1334					if ( sty == ACL_STYLE_REGEX ) {
1335						acl_regex_normalized_dn( right, &bv );
1336						if ( !ber_bvccmp( &bv, '*' ) ) {
1337							regtest( fname, lineno, bv.bv_val );
1338						}
1339						b->a_peername_pat = bv;
1340
1341					} else {
1342						ber_str2bv( right, 0, 1, &b->a_peername_pat );
1343
1344						if ( sty == ACL_STYLE_IP ) {
1345							char		*addr = NULL,
1346									*mask = NULL,
1347									*port = NULL;
1348
1349							split( right, '{', &addr, &port );
1350							split( addr, '%', &addr, &mask );
1351
1352							b->a_peername_addr = inet_addr( addr );
1353							if ( b->a_peername_addr == (unsigned long)(-1) ) {
1354								/* illegal address */
1355								Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1356									"illegal peername address \"%s\".\n",
1357									fname, lineno, addr );
1358								goto fail;
1359							}
1360
1361							b->a_peername_mask = (unsigned long)(-1);
1362							if ( mask != NULL ) {
1363								b->a_peername_mask = inet_addr( mask );
1364								if ( b->a_peername_mask ==
1365									(unsigned long)(-1) )
1366								{
1367									/* illegal mask */
1368									Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1369										"illegal peername address mask "
1370										"\"%s\".\n",
1371										fname, lineno, mask );
1372									goto fail;
1373								}
1374							}
1375
1376							b->a_peername_port = -1;
1377							if ( port ) {
1378								char	*end = NULL;
1379
1380								b->a_peername_port = strtol( port, &end, 10 );
1381								if ( end == port || end[0] != '}' ) {
1382									/* illegal port */
1383									Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1384										"illegal peername port specification "
1385										"\"{%s}\".\n",
1386										fname, lineno, port );
1387									goto fail;
1388								}
1389							}
1390
1391#ifdef LDAP_PF_INET6
1392						} else if ( sty == ACL_STYLE_IPV6 ) {
1393							char		*addr = NULL,
1394									*mask = NULL,
1395									*port = NULL;
1396
1397							split( right, '{', &addr, &port );
1398							split( addr, '%', &addr, &mask );
1399
1400							if ( inet_pton( AF_INET6, addr, &b->a_peername_addr6 ) != 1 ) {
1401								/* illegal address */
1402								Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1403									"illegal peername address \"%s\".\n",
1404									fname, lineno, addr );
1405								goto fail;
1406							}
1407
1408							if ( mask == NULL ) {
1409								mask = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF";
1410							}
1411
1412							if ( inet_pton( AF_INET6, mask, &b->a_peername_mask6 ) != 1 ) {
1413								/* illegal mask */
1414								Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1415									"illegal peername address mask "
1416									"\"%s\".\n",
1417									fname, lineno, mask );
1418								goto fail;
1419							}
1420
1421							b->a_peername_port = -1;
1422							if ( port ) {
1423								char	*end = NULL;
1424
1425								b->a_peername_port = strtol( port, &end, 10 );
1426								if ( end == port || end[0] != '}' ) {
1427									/* illegal port */
1428									Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1429										"illegal peername port specification "
1430										"\"{%s}\".\n",
1431										fname, lineno, port );
1432									goto fail;
1433								}
1434							}
1435#endif /* LDAP_PF_INET6 */
1436						}
1437					}
1438					continue;
1439				}
1440
1441				if ( strcasecmp( left, "sockname" ) == 0 ) {
1442					switch ( sty ) {
1443					case ACL_STYLE_REGEX:
1444					case ACL_STYLE_BASE:
1445						/* legal, traditional */
1446					case ACL_STYLE_EXPAND:
1447						/* cheap replacement to regex for simple expansion */
1448						break;
1449
1450					default:
1451						/* unknown */
1452						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1453							"inappropriate style \"%s\" in by clause\n",
1454						    fname, lineno, style );
1455						goto fail;
1456					}
1457
1458					if ( right == NULL || right[0] == '\0' ) {
1459						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1460							"missing \"=\" in (or value after) \"%s\" "
1461							"in by clause\n",
1462							fname, lineno, left );
1463						goto fail;
1464					}
1465
1466					if ( !BER_BVISNULL( &b->a_sockname_pat ) ) {
1467						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1468							"sockname pattern already specified.\n",
1469							fname, lineno, 0 );
1470						goto fail;
1471					}
1472
1473					b->a_sockname_style = sty;
1474					if ( sty == ACL_STYLE_REGEX ) {
1475						acl_regex_normalized_dn( right, &bv );
1476						if ( !ber_bvccmp( &bv, '*' ) ) {
1477							regtest( fname, lineno, bv.bv_val );
1478						}
1479						b->a_sockname_pat = bv;
1480
1481					} else {
1482						ber_str2bv( right, 0, 1, &b->a_sockname_pat );
1483					}
1484					continue;
1485				}
1486
1487				if ( strcasecmp( left, "domain" ) == 0 ) {
1488					switch ( sty ) {
1489					case ACL_STYLE_REGEX:
1490					case ACL_STYLE_BASE:
1491					case ACL_STYLE_SUBTREE:
1492						/* legal, traditional */
1493						break;
1494
1495					case ACL_STYLE_EXPAND:
1496						/* tolerated: means exact,expand */
1497						if ( expand ) {
1498							Debug( LDAP_DEBUG_ANY,
1499								"%s: line %d: "
1500								"\"expand\" modifier "
1501								"with \"expand\" style.\n",
1502								fname, lineno, 0 );
1503						}
1504						sty = ACL_STYLE_BASE;
1505						expand = 1;
1506						break;
1507
1508					default:
1509						/* unknown */
1510						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1511							"inappropriate style \"%s\" in by clause.\n",
1512						    fname, lineno, style );
1513						goto fail;
1514					}
1515
1516					if ( right == NULL || right[0] == '\0' ) {
1517						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1518							"missing \"=\" in (or value after) \"%s\" "
1519							"in by clause.\n",
1520							fname, lineno, left );
1521						goto fail;
1522					}
1523
1524					if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) {
1525						Debug( LDAP_DEBUG_ANY,
1526							"%s: line %d: domain pattern already specified.\n",
1527							fname, lineno, 0 );
1528						goto fail;
1529					}
1530
1531					b->a_domain_style = sty;
1532					b->a_domain_expand = expand;
1533					if ( sty == ACL_STYLE_REGEX ) {
1534						acl_regex_normalized_dn( right, &bv );
1535						if ( !ber_bvccmp( &bv, '*' ) ) {
1536							regtest( fname, lineno, bv.bv_val );
1537						}
1538						b->a_domain_pat = bv;
1539
1540					} else {
1541						ber_str2bv( right, 0, 1, &b->a_domain_pat );
1542					}
1543					continue;
1544				}
1545
1546				if ( strcasecmp( left, "sockurl" ) == 0 ) {
1547					switch ( sty ) {
1548					case ACL_STYLE_REGEX:
1549					case ACL_STYLE_BASE:
1550						/* legal, traditional */
1551					case ACL_STYLE_EXPAND:
1552						/* cheap replacement to regex for simple expansion */
1553						break;
1554
1555					default:
1556						/* unknown */
1557						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1558							"inappropriate style \"%s\" in by clause.\n",
1559						    fname, lineno, style );
1560						goto fail;
1561					}
1562
1563					if ( right == NULL || right[0] == '\0' ) {
1564						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1565							"missing \"=\" in (or value after) \"%s\" "
1566							"in by clause.\n",
1567							fname, lineno, left );
1568						goto fail;
1569					}
1570
1571					if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) {
1572						Debug( LDAP_DEBUG_ANY,
1573							"%s: line %d: sockurl pattern already specified.\n",
1574							fname, lineno, 0 );
1575						goto fail;
1576					}
1577
1578					b->a_sockurl_style = sty;
1579					if ( sty == ACL_STYLE_REGEX ) {
1580						acl_regex_normalized_dn( right, &bv );
1581						if ( !ber_bvccmp( &bv, '*' ) ) {
1582							regtest( fname, lineno, bv.bv_val );
1583						}
1584						b->a_sockurl_pat = bv;
1585
1586					} else {
1587						ber_str2bv( right, 0, 1, &b->a_sockurl_pat );
1588					}
1589					continue;
1590				}
1591
1592				if ( strcasecmp( left, "set" ) == 0 ) {
1593					switch ( sty ) {
1594						/* deprecated */
1595					case ACL_STYLE_REGEX:
1596						Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL,
1597							"%s: line %d: "
1598							"deprecated set style "
1599							"\"regex\" in <by> clause; "
1600							"use \"expand\" instead.\n",
1601							fname, lineno, 0 );
1602						sty = ACL_STYLE_EXPAND;
1603						/* FALLTHRU */
1604
1605					case ACL_STYLE_BASE:
1606					case ACL_STYLE_EXPAND:
1607						break;
1608
1609					default:
1610						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1611							"inappropriate style \"%s\" in by clause.\n",
1612							fname, lineno, style );
1613						goto fail;
1614					}
1615
1616					if ( !BER_BVISEMPTY( &b->a_set_pat ) ) {
1617						Debug( LDAP_DEBUG_ANY,
1618							"%s: line %d: set attribute already specified.\n",
1619							fname, lineno, 0 );
1620						goto fail;
1621					}
1622
1623					if ( right == NULL || *right == '\0' ) {
1624						Debug( LDAP_DEBUG_ANY,
1625							"%s: line %d: no set is defined.\n",
1626							fname, lineno, 0 );
1627						goto fail;
1628					}
1629
1630					b->a_set_style = sty;
1631					ber_str2bv( right, 0, 1, &b->a_set_pat );
1632
1633					continue;
1634				}
1635
1636#ifdef SLAP_DYNACL
1637				{
1638					char		*name = NULL,
1639							*opts = NULL;
1640
1641#if 1 /* tolerate legacy "aci" <who> */
1642					if ( strcasecmp( left, "aci" ) == 0 ) {
1643						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1644							"undocumented deprecated \"aci\" directive "
1645							"is superseded by \"dynacl/aci\".\n",
1646							fname, lineno, 0 );
1647						name = "aci";
1648
1649					} else
1650#endif /* tolerate legacy "aci" <who> */
1651					if ( strncasecmp( left, "dynacl/", STRLENOF( "dynacl/" ) ) == 0 ) {
1652						name = &left[ STRLENOF( "dynacl/" ) ];
1653						opts = strchr( name, '/' );
1654						if ( opts ) {
1655							opts[ 0 ] = '\0';
1656							opts++;
1657						}
1658					}
1659
1660					if ( name ) {
1661						if ( slap_dynacl_config( fname, lineno, b, name, opts, sty, right ) ) {
1662							Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1663								"unable to configure dynacl \"%s\".\n",
1664								fname, lineno, name );
1665							goto fail;
1666						}
1667
1668						continue;
1669					}
1670				}
1671#endif /* SLAP_DYNACL */
1672
1673				if ( strcasecmp( left, "ssf" ) == 0 ) {
1674					if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
1675						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1676							"inappropriate style \"%s\" in by clause.\n",
1677						    fname, lineno, style );
1678						goto fail;
1679					}
1680
1681					if ( b->a_authz.sai_ssf ) {
1682						Debug( LDAP_DEBUG_ANY,
1683							"%s: line %d: ssf attribute already specified.\n",
1684							fname, lineno, 0 );
1685						goto fail;
1686					}
1687
1688					if ( right == NULL || *right == '\0' ) {
1689						Debug( LDAP_DEBUG_ANY,
1690							"%s: line %d: no ssf is defined.\n",
1691							fname, lineno, 0 );
1692						goto fail;
1693					}
1694
1695					if ( lutil_atou( &b->a_authz.sai_ssf, right ) != 0 ) {
1696						Debug( LDAP_DEBUG_ANY,
1697							"%s: line %d: unable to parse ssf value (%s).\n",
1698							fname, lineno, right );
1699						goto fail;
1700					}
1701
1702					if ( !b->a_authz.sai_ssf ) {
1703						Debug( LDAP_DEBUG_ANY,
1704							"%s: line %d: invalid ssf value (%s).\n",
1705							fname, lineno, right );
1706						goto fail;
1707					}
1708					continue;
1709				}
1710
1711				if ( strcasecmp( left, "transport_ssf" ) == 0 ) {
1712					if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
1713						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1714							"inappropriate style \"%s\" in by clause.\n",
1715							fname, lineno, style );
1716						goto fail;
1717					}
1718
1719					if ( b->a_authz.sai_transport_ssf ) {
1720						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1721							"transport_ssf attribute already specified.\n",
1722							fname, lineno, 0 );
1723						goto fail;
1724					}
1725
1726					if ( right == NULL || *right == '\0' ) {
1727						Debug( LDAP_DEBUG_ANY,
1728							"%s: line %d: no transport_ssf is defined.\n",
1729							fname, lineno, 0 );
1730						goto fail;
1731					}
1732
1733					if ( lutil_atou( &b->a_authz.sai_transport_ssf, right ) != 0 ) {
1734						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1735							"unable to parse transport_ssf value (%s).\n",
1736							fname, lineno, right );
1737						goto fail;
1738					}
1739
1740					if ( !b->a_authz.sai_transport_ssf ) {
1741						Debug( LDAP_DEBUG_ANY,
1742							"%s: line %d: invalid transport_ssf value (%s).\n",
1743							fname, lineno, right );
1744						goto fail;
1745					}
1746					continue;
1747				}
1748
1749				if ( strcasecmp( left, "tls_ssf" ) == 0 ) {
1750					if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
1751						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1752							"inappropriate style \"%s\" in by clause.\n",
1753							fname, lineno, style );
1754						goto fail;
1755					}
1756
1757					if ( b->a_authz.sai_tls_ssf ) {
1758						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1759							"tls_ssf attribute already specified.\n",
1760							fname, lineno, 0 );
1761						goto fail;
1762					}
1763
1764					if ( right == NULL || *right == '\0' ) {
1765						Debug( LDAP_DEBUG_ANY,
1766							"%s: line %d: no tls_ssf is defined\n",
1767							fname, lineno, 0 );
1768						goto fail;
1769					}
1770
1771					if ( lutil_atou( &b->a_authz.sai_tls_ssf, right ) != 0 ) {
1772						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1773							"unable to parse tls_ssf value (%s).\n",
1774							fname, lineno, right );
1775						goto fail;
1776					}
1777
1778					if ( !b->a_authz.sai_tls_ssf ) {
1779						Debug( LDAP_DEBUG_ANY,
1780							"%s: line %d: invalid tls_ssf value (%s).\n",
1781							fname, lineno, right );
1782						goto fail;
1783					}
1784					continue;
1785				}
1786
1787				if ( strcasecmp( left, "sasl_ssf" ) == 0 ) {
1788					if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
1789						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1790							"inappropriate style \"%s\" in by clause.\n",
1791							fname, lineno, style );
1792						goto fail;
1793					}
1794
1795					if ( b->a_authz.sai_sasl_ssf ) {
1796						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1797							"sasl_ssf attribute already specified.\n",
1798							fname, lineno, 0 );
1799						goto fail;
1800					}
1801
1802					if ( right == NULL || *right == '\0' ) {
1803						Debug( LDAP_DEBUG_ANY,
1804							"%s: line %d: no sasl_ssf is defined.\n",
1805							fname, lineno, 0 );
1806						goto fail;
1807					}
1808
1809					if ( lutil_atou( &b->a_authz.sai_sasl_ssf, right ) != 0 ) {
1810						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1811							"unable to parse sasl_ssf value (%s).\n",
1812							fname, lineno, right );
1813						goto fail;
1814					}
1815
1816					if ( !b->a_authz.sai_sasl_ssf ) {
1817						Debug( LDAP_DEBUG_ANY,
1818							"%s: line %d: invalid sasl_ssf value (%s).\n",
1819							fname, lineno, right );
1820						goto fail;
1821					}
1822					continue;
1823				}
1824
1825				if ( right != NULL ) {
1826					/* unsplit */
1827					right[-1] = '=';
1828				}
1829				break;
1830			}
1831
1832			if ( i == argc || ( strcasecmp( left, "stop" ) == 0 ) ) {
1833				/* out of arguments or plain stop */
1834
1835				ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE );
1836				ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE);
1837				b->a_type = ACL_STOP;
1838
1839				access_append( &a->acl_access, b );
1840				continue;
1841			}
1842
1843			if ( strcasecmp( left, "continue" ) == 0 ) {
1844				/* plain continue */
1845
1846				ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE );
1847				ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE);
1848				b->a_type = ACL_CONTINUE;
1849
1850				access_append( &a->acl_access, b );
1851				continue;
1852			}
1853
1854			if ( strcasecmp( left, "break" ) == 0 ) {
1855				/* plain continue */
1856
1857				ACL_PRIV_ASSIGN(b->a_access_mask, ACL_PRIV_ADDITIVE);
1858				ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE);
1859				b->a_type = ACL_BREAK;
1860
1861				access_append( &a->acl_access, b );
1862				continue;
1863			}
1864
1865			if ( strcasecmp( left, "by" ) == 0 ) {
1866				/* we've gone too far */
1867				--i;
1868				ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE );
1869				ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE);
1870				b->a_type = ACL_STOP;
1871
1872				access_append( &a->acl_access, b );
1873				continue;
1874			}
1875
1876			/* get <access> */
1877			{
1878				char	*lleft = left;
1879
1880				if ( strncasecmp( left, "self", STRLENOF( "self" ) ) == 0 ) {
1881					b->a_dn_self = 1;
1882					lleft = &left[ STRLENOF( "self" ) ];
1883
1884				} else if ( strncasecmp( left, "realself", STRLENOF( "realself" ) ) == 0 ) {
1885					b->a_realdn_self = 1;
1886					lleft = &left[ STRLENOF( "realself" ) ];
1887				}
1888
1889				ACL_PRIV_ASSIGN( b->a_access_mask, str2accessmask( lleft ) );
1890			}
1891
1892			if ( ACL_IS_INVALID( b->a_access_mask ) ) {
1893				Debug( LDAP_DEBUG_ANY,
1894					"%s: line %d: expecting <access> got \"%s\".\n",
1895					fname, lineno, left );
1896				goto fail;
1897			}
1898
1899			b->a_type = ACL_STOP;
1900
1901			if ( ++i == argc ) {
1902				/* out of arguments or plain stop */
1903				access_append( &a->acl_access, b );
1904				continue;
1905			}
1906
1907			if ( strcasecmp( argv[i], "continue" ) == 0 ) {
1908				/* plain continue */
1909				b->a_type = ACL_CONTINUE;
1910
1911			} else if ( strcasecmp( argv[i], "break" ) == 0 ) {
1912				/* plain continue */
1913				b->a_type = ACL_BREAK;
1914
1915			} else if ( strcasecmp( argv[i], "stop" ) != 0 ) {
1916				/* gone to far */
1917				i--;
1918			}
1919
1920			access_append( &a->acl_access, b );
1921			b = NULL;
1922
1923		} else {
1924			Debug( LDAP_DEBUG_ANY,
1925				"%s: line %d: expecting \"to\" "
1926				"or \"by\" got \"%s\"\n",
1927				fname, lineno, argv[i] );
1928			goto fail;
1929		}
1930	}
1931
1932	/* if we have no real access clause, complain and do nothing */
1933	if ( a == NULL ) {
1934		Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1935			"warning: no access clause(s) specified in access line.\n",
1936			fname, lineno, 0 );
1937		goto fail;
1938
1939	} else {
1940#ifdef LDAP_DEBUG
1941		if ( slap_debug & LDAP_DEBUG_ACL ) {
1942			print_acl( be, a );
1943		}
1944#endif
1945
1946		if ( a->acl_access == NULL ) {
1947			Debug( LDAP_DEBUG_ANY, "%s: line %d: "
1948				"warning: no by clause(s) specified in access line.\n",
1949				fname, lineno, 0 );
1950			goto fail;
1951		}
1952
1953		if ( be != NULL ) {
1954			if ( be->be_nsuffix == NULL ) {
1955				Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: "
1956					"scope checking needs suffix before ACLs.\n",
1957					fname, lineno, 0 );
1958				/* go ahead, since checking is not authoritative */
1959			} else if ( !BER_BVISNULL( &be->be_nsuffix[ 1 ] ) ) {
1960				Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: "
1961					"scope checking only applies to single-valued "
1962					"suffix databases\n",
1963					fname, lineno, 0 );
1964				/* go ahead, since checking is not authoritative */
1965			} else {
1966				switch ( check_scope( be, a ) ) {
1967				case ACL_SCOPE_UNKNOWN:
1968					Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: "
1969						"cannot assess the validity of the ACL scope within "
1970						"backend naming context\n",
1971						fname, lineno, 0 );
1972					break;
1973
1974				case ACL_SCOPE_WARN:
1975					Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: "
1976						"ACL could be out of scope within backend naming context\n",
1977						fname, lineno, 0 );
1978					break;
1979
1980				case ACL_SCOPE_PARTIAL:
1981					Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: "
1982						"ACL appears to be partially out of scope within "
1983						"backend naming context\n",
1984						fname, lineno, 0 );
1985					break;
1986
1987				case ACL_SCOPE_ERR:
1988					Debug( LDAP_DEBUG_ACL, "%s: line %d: warning: "
1989						"ACL appears to be out of scope within "
1990						"backend naming context\n",
1991						fname, lineno, 0 );
1992					break;
1993
1994				default:
1995					break;
1996				}
1997			}
1998			acl_append( &be->be_acl, a, pos );
1999
2000		} else {
2001			acl_append( &frontendDB->be_acl, a, pos );
2002		}
2003	}
2004
2005	return 0;
2006
2007fail:
2008	if ( b ) access_free( b );
2009	if ( a ) acl_free( a );
2010	return acl_usage();
2011}
2012
2013char *
2014accessmask2str( slap_mask_t mask, char *buf, int debug )
2015{
2016	int	none = 1;
2017	char	*ptr = buf;
2018
2019	assert( buf != NULL );
2020
2021	if ( ACL_IS_INVALID( mask ) ) {
2022		return "invalid";
2023	}
2024
2025	buf[0] = '\0';
2026
2027	if ( ACL_IS_LEVEL( mask ) ) {
2028		if ( ACL_LVL_IS_NONE(mask) ) {
2029			ptr = lutil_strcopy( ptr, "none" );
2030
2031		} else if ( ACL_LVL_IS_DISCLOSE(mask) ) {
2032			ptr = lutil_strcopy( ptr, "disclose" );
2033
2034		} else if ( ACL_LVL_IS_AUTH(mask) ) {
2035			ptr = lutil_strcopy( ptr, "auth" );
2036
2037		} else if ( ACL_LVL_IS_COMPARE(mask) ) {
2038			ptr = lutil_strcopy( ptr, "compare" );
2039
2040		} else if ( ACL_LVL_IS_SEARCH(mask) ) {
2041			ptr = lutil_strcopy( ptr, "search" );
2042
2043		} else if ( ACL_LVL_IS_READ(mask) ) {
2044			ptr = lutil_strcopy( ptr, "read" );
2045
2046		} else if ( ACL_LVL_IS_WRITE(mask) ) {
2047			ptr = lutil_strcopy( ptr, "write" );
2048
2049		} else if ( ACL_LVL_IS_WADD(mask) ) {
2050			ptr = lutil_strcopy( ptr, "add" );
2051
2052		} else if ( ACL_LVL_IS_WDEL(mask) ) {
2053			ptr = lutil_strcopy( ptr, "delete" );
2054
2055		} else if ( ACL_LVL_IS_MANAGE(mask) ) {
2056			ptr = lutil_strcopy( ptr, "manage" );
2057
2058		} else {
2059			ptr = lutil_strcopy( ptr, "unknown" );
2060		}
2061
2062		if ( !debug ) {
2063			*ptr = '\0';
2064			return buf;
2065		}
2066		*ptr++ = '(';
2067	}
2068
2069	if( ACL_IS_ADDITIVE( mask ) ) {
2070		*ptr++ = '+';
2071
2072	} else if( ACL_IS_SUBTRACTIVE( mask ) ) {
2073		*ptr++ = '-';
2074
2075	} else {
2076		*ptr++ = '=';
2077	}
2078
2079	if ( ACL_PRIV_ISSET(mask, ACL_PRIV_MANAGE) ) {
2080		none = 0;
2081		*ptr++ = 'm';
2082	}
2083
2084	if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WRITE) ) {
2085		none = 0;
2086		*ptr++ = 'w';
2087
2088	} else if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WADD) ) {
2089		none = 0;
2090		*ptr++ = 'a';
2091
2092	} else if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WDEL) ) {
2093		none = 0;
2094		*ptr++ = 'z';
2095	}
2096
2097	if ( ACL_PRIV_ISSET(mask, ACL_PRIV_READ) ) {
2098		none = 0;
2099		*ptr++ = 'r';
2100	}
2101
2102	if ( ACL_PRIV_ISSET(mask, ACL_PRIV_SEARCH) ) {
2103		none = 0;
2104		*ptr++ = 's';
2105	}
2106
2107	if ( ACL_PRIV_ISSET(mask, ACL_PRIV_COMPARE) ) {
2108		none = 0;
2109		*ptr++ = 'c';
2110	}
2111
2112	if ( ACL_PRIV_ISSET(mask, ACL_PRIV_AUTH) ) {
2113		none = 0;
2114		*ptr++ = 'x';
2115	}
2116
2117	if ( ACL_PRIV_ISSET(mask, ACL_PRIV_DISCLOSE) ) {
2118		none = 0;
2119		*ptr++ = 'd';
2120	}
2121
2122	if ( none && ACL_PRIV_ISSET(mask, ACL_PRIV_NONE) ) {
2123		none = 0;
2124		*ptr++ = '0';
2125	}
2126
2127	if ( none ) {
2128		ptr = buf;
2129	}
2130
2131	if ( ACL_IS_LEVEL( mask ) ) {
2132		*ptr++ = ')';
2133	}
2134
2135	*ptr = '\0';
2136
2137	return buf;
2138}
2139
2140slap_mask_t
2141str2accessmask( const char *str )
2142{
2143	slap_mask_t	mask;
2144
2145	if( !ASCII_ALPHA(str[0]) ) {
2146		int i;
2147
2148		if ( str[0] == '=' ) {
2149			ACL_INIT(mask);
2150
2151		} else if( str[0] == '+' ) {
2152			ACL_PRIV_ASSIGN(mask, ACL_PRIV_ADDITIVE);
2153
2154		} else if( str[0] == '-' ) {
2155			ACL_PRIV_ASSIGN(mask, ACL_PRIV_SUBSTRACTIVE);
2156
2157		} else {
2158			ACL_INVALIDATE(mask);
2159			return mask;
2160		}
2161
2162		for( i=1; str[i] != '\0'; i++ ) {
2163			if( TOLOWER((unsigned char) str[i]) == 'm' ) {
2164				ACL_PRIV_SET(mask, ACL_PRIV_MANAGE);
2165
2166			} else if( TOLOWER((unsigned char) str[i]) == 'w' ) {
2167				ACL_PRIV_SET(mask, ACL_PRIV_WRITE);
2168
2169			} else if( TOLOWER((unsigned char) str[i]) == 'a' ) {
2170				ACL_PRIV_SET(mask, ACL_PRIV_WADD);
2171
2172			} else if( TOLOWER((unsigned char) str[i]) == 'z' ) {
2173				ACL_PRIV_SET(mask, ACL_PRIV_WDEL);
2174
2175			} else if( TOLOWER((unsigned char) str[i]) == 'r' ) {
2176				ACL_PRIV_SET(mask, ACL_PRIV_READ);
2177
2178			} else if( TOLOWER((unsigned char) str[i]) == 's' ) {
2179				ACL_PRIV_SET(mask, ACL_PRIV_SEARCH);
2180
2181			} else if( TOLOWER((unsigned char) str[i]) == 'c' ) {
2182				ACL_PRIV_SET(mask, ACL_PRIV_COMPARE);
2183
2184			} else if( TOLOWER((unsigned char) str[i]) == 'x' ) {
2185				ACL_PRIV_SET(mask, ACL_PRIV_AUTH);
2186
2187			} else if( TOLOWER((unsigned char) str[i]) == 'd' ) {
2188				ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE);
2189
2190			} else if( str[i] == '0' ) {
2191				ACL_PRIV_SET(mask, ACL_PRIV_NONE);
2192
2193			} else {
2194				ACL_INVALIDATE(mask);
2195				return mask;
2196			}
2197		}
2198
2199		return mask;
2200	}
2201
2202	if ( strcasecmp( str, "none" ) == 0 ) {
2203		ACL_LVL_ASSIGN_NONE(mask);
2204
2205	} else if ( strcasecmp( str, "disclose" ) == 0 ) {
2206		ACL_LVL_ASSIGN_DISCLOSE(mask);
2207
2208	} else if ( strcasecmp( str, "auth" ) == 0 ) {
2209		ACL_LVL_ASSIGN_AUTH(mask);
2210
2211	} else if ( strcasecmp( str, "compare" ) == 0 ) {
2212		ACL_LVL_ASSIGN_COMPARE(mask);
2213
2214	} else if ( strcasecmp( str, "search" ) == 0 ) {
2215		ACL_LVL_ASSIGN_SEARCH(mask);
2216
2217	} else if ( strcasecmp( str, "read" ) == 0 ) {
2218		ACL_LVL_ASSIGN_READ(mask);
2219
2220	} else if ( strcasecmp( str, "add" ) == 0 ) {
2221		ACL_LVL_ASSIGN_WADD(mask);
2222
2223	} else if ( strcasecmp( str, "delete" ) == 0 ) {
2224		ACL_LVL_ASSIGN_WDEL(mask);
2225
2226	} else if ( strcasecmp( str, "write" ) == 0 ) {
2227		ACL_LVL_ASSIGN_WRITE(mask);
2228
2229	} else if ( strcasecmp( str, "manage" ) == 0 ) {
2230		ACL_LVL_ASSIGN_MANAGE(mask);
2231
2232	} else {
2233		ACL_INVALIDATE( mask );
2234	}
2235
2236	return mask;
2237}
2238
2239static int
2240acl_usage( void )
2241{
2242	char *access =
2243		"<access clause> ::= access to <what> "
2244				"[ by <who> [ <access> ] [ <control> ] ]+ \n";
2245	char *what =
2246		"<what> ::= * | dn[.<dnstyle>=<DN>] [filter=<filter>] [attrs=<attrspec>]\n"
2247		"<attrspec> ::= <attrname> [val[/<matchingRule>][.<attrstyle>]=<value>] | <attrlist>\n"
2248		"<attrlist> ::= <attr> [ , <attrlist> ]\n"
2249		"<attr> ::= <attrname> | @<objectClass> | !<objectClass> | entry | children\n";
2250
2251	char *who =
2252		"<who> ::= [ * | anonymous | users | self | dn[.<dnstyle>]=<DN> ]\n"
2253			"\t[ realanonymous | realusers | realself | realdn[.<dnstyle>]=<DN> ]\n"
2254			"\t[dnattr=<attrname>]\n"
2255			"\t[realdnattr=<attrname>]\n"
2256			"\t[group[/<objectclass>[/<attrname>]][.<style>]=<group>]\n"
2257			"\t[peername[.<peernamestyle>]=<peer>] [sockname[.<style>]=<name>]\n"
2258			"\t[domain[.<domainstyle>]=<domain>] [sockurl[.<style>]=<url>]\n"
2259#ifdef SLAP_DYNACL
2260			"\t[dynacl/<name>[/<options>][.<dynstyle>][=<pattern>]]\n"
2261#endif /* SLAP_DYNACL */
2262			"\t[ssf=<n>] [transport_ssf=<n>] [tls_ssf=<n>] [sasl_ssf=<n>]\n"
2263		"<style> ::= exact | regex | base(Object)\n"
2264		"<dnstyle> ::= base(Object) | one(level) | sub(tree) | children | "
2265			"exact | regex\n"
2266		"<attrstyle> ::= exact | regex | base(Object) | one(level) | "
2267			"sub(tree) | children\n"
2268		"<peernamestyle> ::= exact | regex | ip | ipv6 | path\n"
2269		"<domainstyle> ::= exact | regex | base(Object) | sub(tree)\n"
2270		"<access> ::= [[real]self]{<level>|<priv>}\n"
2271		"<level> ::= none|disclose|auth|compare|search|read|{write|add|delete}|manage\n"
2272		"<priv> ::= {=|+|-}{0|d|x|c|s|r|{w|a|z}|m}+\n"
2273		"<control> ::= [ stop | continue | break ]\n"
2274#ifdef SLAP_DYNACL
2275#ifdef SLAPD_ACI_ENABLED
2276		"dynacl:\n"
2277		"\t<name>=ACI\t<pattern>=<attrname>\n"
2278#endif /* SLAPD_ACI_ENABLED */
2279#endif /* ! SLAP_DYNACL */
2280		"";
2281
2282	Debug( LDAP_DEBUG_ANY, "%s%s%s\n", access, what, who );
2283
2284	return 1;
2285}
2286
2287/*
2288 * Set pattern to a "normalized" DN from src.
2289 * At present it simply eats the (optional) space after
2290 * a RDN separator (,)
2291 * Eventually will evolve in a more complete normalization
2292 */
2293static void
2294acl_regex_normalized_dn(
2295	const char *src,
2296	struct berval *pattern )
2297{
2298	char *str, *p;
2299	ber_len_t len;
2300
2301	str = ch_strdup( src );
2302	len = strlen( src );
2303
2304	for ( p = str; p && p[0]; p++ ) {
2305		/* escape */
2306		if ( p[0] == '\\' && p[1] ) {
2307			/*
2308			 * if escaping a hex pair we should
2309			 * increment p twice; however, in that
2310			 * case the second hex number does
2311			 * no harm
2312			 */
2313			p++;
2314		}
2315
2316		if ( p[0] == ',' && p[1] == ' ' ) {
2317			char *q;
2318
2319			/*
2320			 * too much space should be an error if we are pedantic
2321			 */
2322			for ( q = &p[2]; q[0] == ' '; q++ ) {
2323				/* DO NOTHING */ ;
2324			}
2325			AC_MEMCPY( p+1, q, len-(q-str)+1);
2326		}
2327	}
2328	pattern->bv_val = str;
2329	pattern->bv_len = p - str;
2330
2331	return;
2332}
2333
2334static void
2335split(
2336    char	*line,
2337    int		splitchar,
2338    char	**left,
2339    char	**right )
2340{
2341	*left = line;
2342	if ( (*right = strchr( line, splitchar )) != NULL ) {
2343		*((*right)++) = '\0';
2344	}
2345}
2346
2347static void
2348access_append( Access **l, Access *a )
2349{
2350	for ( ; *l != NULL; l = &(*l)->a_next ) {
2351		;	/* Empty */
2352	}
2353
2354	*l = a;
2355}
2356
2357void
2358acl_append( AccessControl **l, AccessControl *a, int pos )
2359{
2360	int i;
2361
2362	for (i=0 ; i != pos && *l != NULL; l = &(*l)->acl_next, i++ ) {
2363		;	/* Empty */
2364	}
2365	if ( *l && a )
2366		a->acl_next = *l;
2367	*l = a;
2368}
2369
2370static void
2371access_free( Access *a )
2372{
2373	if ( !BER_BVISNULL( &a->a_dn_pat ) ) {
2374		free( a->a_dn_pat.bv_val );
2375	}
2376	if ( !BER_BVISNULL( &a->a_realdn_pat ) ) {
2377		free( a->a_realdn_pat.bv_val );
2378	}
2379	if ( !BER_BVISNULL( &a->a_peername_pat ) ) {
2380		free( a->a_peername_pat.bv_val );
2381	}
2382	if ( !BER_BVISNULL( &a->a_sockname_pat ) ) {
2383		free( a->a_sockname_pat.bv_val );
2384	}
2385	if ( !BER_BVISNULL( &a->a_domain_pat ) ) {
2386		free( a->a_domain_pat.bv_val );
2387	}
2388	if ( !BER_BVISNULL( &a->a_sockurl_pat ) ) {
2389		free( a->a_sockurl_pat.bv_val );
2390	}
2391	if ( !BER_BVISNULL( &a->a_set_pat ) ) {
2392		free( a->a_set_pat.bv_val );
2393	}
2394	if ( !BER_BVISNULL( &a->a_group_pat ) ) {
2395		free( a->a_group_pat.bv_val );
2396	}
2397#ifdef SLAP_DYNACL
2398	if ( a->a_dynacl != NULL ) {
2399		slap_dynacl_t	*da;
2400		for ( da = a->a_dynacl; da; ) {
2401			slap_dynacl_t	*tmp = da;
2402
2403			da = da->da_next;
2404
2405			if ( tmp->da_destroy ) {
2406				tmp->da_destroy( tmp->da_private );
2407			}
2408
2409			ch_free( tmp );
2410		}
2411	}
2412#endif /* SLAP_DYNACL */
2413	free( a );
2414}
2415
2416void
2417acl_free( AccessControl *a )
2418{
2419	Access *n;
2420	AttributeName *an;
2421
2422	if ( a->acl_filter ) {
2423		filter_free( a->acl_filter );
2424	}
2425	if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
2426		if ( a->acl_dn_style == ACL_STYLE_REGEX ) {
2427			regfree( &a->acl_dn_re );
2428		}
2429		free ( a->acl_dn_pat.bv_val );
2430	}
2431	if ( a->acl_attrs ) {
2432		for ( an = a->acl_attrs; !BER_BVISNULL( &an->an_name ); an++ ) {
2433			free( an->an_name.bv_val );
2434		}
2435		free( a->acl_attrs );
2436
2437		if ( a->acl_attrval_style == ACL_STYLE_REGEX ) {
2438			regfree( &a->acl_attrval_re );
2439		}
2440
2441		if ( !BER_BVISNULL( &a->acl_attrval ) ) {
2442			ber_memfree( a->acl_attrval.bv_val );
2443		}
2444	}
2445	for ( ; a->acl_access; a->acl_access = n ) {
2446		n = a->acl_access->a_next;
2447		access_free( a->acl_access );
2448	}
2449	free( a );
2450}
2451
2452/* Because backend_startup uses acl_append to tack on the global_acl to
2453 * the end of each backend's acl, we cannot just take one argument and
2454 * merrily free our way to the end of the list. backend_destroy calls us
2455 * with the be_acl in arg1, and global_acl in arg2 to give us a stopping
2456 * point. config_destroy calls us with global_acl in arg1 and NULL in
2457 * arg2, so we then proceed to polish off the global_acl.
2458 */
2459void
2460acl_destroy( AccessControl *a, AccessControl *end )
2461{
2462	AccessControl *n;
2463
2464	for ( ; a && a != end; a = n ) {
2465		n = a->acl_next;
2466		acl_free( a );
2467	}
2468}
2469
2470char *
2471access2str( slap_access_t access )
2472{
2473	if ( access == ACL_NONE ) {
2474		return "none";
2475
2476	} else if ( access == ACL_DISCLOSE ) {
2477		return "disclose";
2478
2479	} else if ( access == ACL_AUTH ) {
2480		return "auth";
2481
2482	} else if ( access == ACL_COMPARE ) {
2483		return "compare";
2484
2485	} else if ( access == ACL_SEARCH ) {
2486		return "search";
2487
2488	} else if ( access == ACL_READ ) {
2489		return "read";
2490
2491	} else if ( access == ACL_WRITE ) {
2492		return "write";
2493
2494	} else if ( access == ACL_WADD ) {
2495		return "add";
2496
2497	} else if ( access == ACL_WDEL ) {
2498		return "delete";
2499
2500	} else if ( access == ACL_MANAGE ) {
2501		return "manage";
2502
2503	}
2504
2505	return "unknown";
2506}
2507
2508slap_access_t
2509str2access( const char *str )
2510{
2511	if ( strcasecmp( str, "none" ) == 0 ) {
2512		return ACL_NONE;
2513
2514	} else if ( strcasecmp( str, "disclose" ) == 0 ) {
2515		return ACL_DISCLOSE;
2516
2517	} else if ( strcasecmp( str, "auth" ) == 0 ) {
2518		return ACL_AUTH;
2519
2520	} else if ( strcasecmp( str, "compare" ) == 0 ) {
2521		return ACL_COMPARE;
2522
2523	} else if ( strcasecmp( str, "search" ) == 0 ) {
2524		return ACL_SEARCH;
2525
2526	} else if ( strcasecmp( str, "read" ) == 0 ) {
2527		return ACL_READ;
2528
2529	} else if ( strcasecmp( str, "write" ) == 0 ) {
2530		return ACL_WRITE;
2531
2532	} else if ( strcasecmp( str, "add" ) == 0 ) {
2533		return ACL_WADD;
2534
2535	} else if ( strcasecmp( str, "delete" ) == 0 ) {
2536		return ACL_WDEL;
2537
2538	} else if ( strcasecmp( str, "manage" ) == 0 ) {
2539		return ACL_MANAGE;
2540	}
2541
2542	return( ACL_INVALID_ACCESS );
2543}
2544
2545#define ACLBUF_MAXLEN	8192
2546
2547static char aclbuf[ACLBUF_MAXLEN];
2548
2549static char *
2550dnaccess2text( slap_dn_access *bdn, char *ptr, int is_realdn )
2551{
2552	*ptr++ = ' ';
2553
2554	if ( is_realdn ) {
2555		ptr = lutil_strcopy( ptr, "real" );
2556	}
2557
2558	if ( ber_bvccmp( &bdn->a_pat, '*' ) ||
2559		bdn->a_style == ACL_STYLE_ANONYMOUS ||
2560		bdn->a_style == ACL_STYLE_USERS ||
2561		bdn->a_style == ACL_STYLE_SELF )
2562	{
2563		if ( is_realdn ) {
2564			assert( ! ber_bvccmp( &bdn->a_pat, '*' ) );
2565		}
2566
2567		ptr = lutil_strcopy( ptr, bdn->a_pat.bv_val );
2568		if ( bdn->a_style == ACL_STYLE_SELF && bdn->a_self_level != 0 ) {
2569			int n = sprintf( ptr, ".level{%d}", bdn->a_self_level );
2570			if ( n > 0 ) {
2571				ptr += n;
2572			} /* else ? */
2573		}
2574
2575	} else {
2576		ptr = lutil_strcopy( ptr, "dn." );
2577		if ( bdn->a_style == ACL_STYLE_BASE )
2578			ptr = lutil_strcopy( ptr, style_base );
2579		else
2580			ptr = lutil_strcopy( ptr, style_strings[bdn->a_style] );
2581		if ( bdn->a_style == ACL_STYLE_LEVEL ) {
2582			int n = sprintf( ptr, "{%d}", bdn->a_level );
2583			if ( n > 0 ) {
2584				ptr += n;
2585			} /* else ? */
2586		}
2587		if ( bdn->a_expand ) {
2588			ptr = lutil_strcopy( ptr, ",expand" );
2589		}
2590		*ptr++ = '=';
2591		*ptr++ = '"';
2592		ptr = lutil_strcopy( ptr, bdn->a_pat.bv_val );
2593		*ptr++ = '"';
2594	}
2595	return ptr;
2596}
2597
2598static char *
2599access2text( Access *b, char *ptr )
2600{
2601	char maskbuf[ACCESSMASK_MAXLEN];
2602
2603	ptr = lutil_strcopy( ptr, "\tby" );
2604
2605	if ( !BER_BVISEMPTY( &b->a_dn_pat ) ) {
2606		ptr = dnaccess2text( &b->a_dn, ptr, 0 );
2607	}
2608	if ( b->a_dn_at ) {
2609		ptr = lutil_strcopy( ptr, " dnattr=" );
2610		ptr = lutil_strcopy( ptr, b->a_dn_at->ad_cname.bv_val );
2611	}
2612
2613	if ( !BER_BVISEMPTY( &b->a_realdn_pat ) ) {
2614		ptr = dnaccess2text( &b->a_realdn, ptr, 1 );
2615	}
2616	if ( b->a_realdn_at ) {
2617		ptr = lutil_strcopy( ptr, " realdnattr=" );
2618		ptr = lutil_strcopy( ptr, b->a_realdn_at->ad_cname.bv_val );
2619	}
2620
2621	if ( !BER_BVISEMPTY( &b->a_group_pat ) ) {
2622		ptr = lutil_strcopy( ptr, " group/" );
2623		ptr = lutil_strcopy( ptr, b->a_group_oc ?
2624			b->a_group_oc->soc_cname.bv_val : SLAPD_GROUP_CLASS );
2625		*ptr++ = '/';
2626		ptr = lutil_strcopy( ptr, b->a_group_at ?
2627			b->a_group_at->ad_cname.bv_val : SLAPD_GROUP_ATTR );
2628		*ptr++ = '.';
2629		ptr = lutil_strcopy( ptr, style_strings[b->a_group_style] );
2630		*ptr++ = '=';
2631		*ptr++ = '"';
2632		ptr = lutil_strcopy( ptr, b->a_group_pat.bv_val );
2633		*ptr++ = '"';
2634	}
2635
2636	if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) {
2637		ptr = lutil_strcopy( ptr, " peername" );
2638		*ptr++ = '.';
2639		ptr = lutil_strcopy( ptr, style_strings[b->a_peername_style] );
2640		*ptr++ = '=';
2641		*ptr++ = '"';
2642		ptr = lutil_strcopy( ptr, b->a_peername_pat.bv_val );
2643		*ptr++ = '"';
2644	}
2645
2646	if ( !BER_BVISEMPTY( &b->a_sockname_pat ) ) {
2647		ptr = lutil_strcopy( ptr, " sockname" );
2648		*ptr++ = '.';
2649		ptr = lutil_strcopy( ptr, style_strings[b->a_sockname_style] );
2650		*ptr++ = '=';
2651		*ptr++ = '"';
2652		ptr = lutil_strcopy( ptr, b->a_sockname_pat.bv_val );
2653		*ptr++ = '"';
2654	}
2655
2656	if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) {
2657		ptr = lutil_strcopy( ptr, " domain" );
2658		*ptr++ = '.';
2659		ptr = lutil_strcopy( ptr, style_strings[b->a_domain_style] );
2660		if ( b->a_domain_expand ) {
2661			ptr = lutil_strcopy( ptr, ",expand" );
2662		}
2663		*ptr++ = '=';
2664		ptr = lutil_strcopy( ptr, b->a_domain_pat.bv_val );
2665	}
2666
2667	if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) {
2668		ptr = lutil_strcopy( ptr, " sockurl" );
2669		*ptr++ = '.';
2670		ptr = lutil_strcopy( ptr, style_strings[b->a_sockurl_style] );
2671		*ptr++ = '=';
2672		*ptr++ = '"';
2673		ptr = lutil_strcopy( ptr, b->a_sockurl_pat.bv_val );
2674		*ptr++ = '"';
2675	}
2676
2677	if ( !BER_BVISEMPTY( &b->a_set_pat ) ) {
2678		ptr = lutil_strcopy( ptr, " set" );
2679		*ptr++ = '.';
2680		ptr = lutil_strcopy( ptr, style_strings[b->a_set_style] );
2681		*ptr++ = '=';
2682		*ptr++ = '"';
2683		ptr = lutil_strcopy( ptr, b->a_set_pat.bv_val );
2684		*ptr++ = '"';
2685	}
2686
2687#ifdef SLAP_DYNACL
2688	if ( b->a_dynacl ) {
2689		slap_dynacl_t	*da;
2690
2691		for ( da = b->a_dynacl; da; da = da->da_next ) {
2692			if ( da->da_unparse ) {
2693				struct berval bv = BER_BVNULL;
2694				(void)( *da->da_unparse )( da->da_private, &bv );
2695				assert( !BER_BVISNULL( &bv ) );
2696				ptr = lutil_strcopy( ptr, bv.bv_val );
2697				ch_free( bv.bv_val );
2698			}
2699		}
2700	}
2701#endif /* SLAP_DYNACL */
2702
2703	/* Security Strength Factors */
2704	if ( b->a_authz.sai_ssf ) {
2705		ptr += sprintf( ptr, " ssf=%u",
2706			b->a_authz.sai_ssf );
2707	}
2708	if ( b->a_authz.sai_transport_ssf ) {
2709		ptr += sprintf( ptr, " transport_ssf=%u",
2710			b->a_authz.sai_transport_ssf );
2711	}
2712	if ( b->a_authz.sai_tls_ssf ) {
2713		ptr += sprintf( ptr, " tls_ssf=%u",
2714			b->a_authz.sai_tls_ssf );
2715	}
2716	if ( b->a_authz.sai_sasl_ssf ) {
2717		ptr += sprintf( ptr, " sasl_ssf=%u",
2718			b->a_authz.sai_sasl_ssf );
2719	}
2720
2721	*ptr++ = ' ';
2722	if ( b->a_dn_self ) {
2723		ptr = lutil_strcopy( ptr, "self" );
2724	} else if ( b->a_realdn_self ) {
2725		ptr = lutil_strcopy( ptr, "realself" );
2726	}
2727	ptr = lutil_strcopy( ptr, accessmask2str( b->a_access_mask, maskbuf, 0 ));
2728	if ( !maskbuf[0] ) ptr--;
2729
2730	if( b->a_type == ACL_BREAK ) {
2731		ptr = lutil_strcopy( ptr, " break" );
2732
2733	} else if( b->a_type == ACL_CONTINUE ) {
2734		ptr = lutil_strcopy( ptr, " continue" );
2735
2736	} else if( b->a_type != ACL_STOP ) {
2737		ptr = lutil_strcopy( ptr, " unknown-control" );
2738	} else {
2739		if ( !maskbuf[0] ) ptr = lutil_strcopy( ptr, " stop" );
2740	}
2741	*ptr++ = '\n';
2742
2743	return ptr;
2744}
2745
2746void
2747acl_unparse( AccessControl *a, struct berval *bv )
2748{
2749	Access	*b;
2750	char	*ptr;
2751	int	to = 0;
2752
2753	bv->bv_val = aclbuf;
2754	bv->bv_len = 0;
2755
2756	ptr = bv->bv_val;
2757
2758	ptr = lutil_strcopy( ptr, "to" );
2759	if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
2760		to++;
2761		ptr = lutil_strcopy( ptr, " dn." );
2762		if ( a->acl_dn_style == ACL_STYLE_BASE )
2763			ptr = lutil_strcopy( ptr, style_base );
2764		else
2765			ptr = lutil_strcopy( ptr, style_strings[a->acl_dn_style] );
2766		*ptr++ = '=';
2767		*ptr++ = '"';
2768		ptr = lutil_strcopy( ptr, a->acl_dn_pat.bv_val );
2769		ptr = lutil_strcopy( ptr, "\"\n" );
2770	}
2771
2772	if ( a->acl_filter != NULL ) {
2773		struct berval	bv = BER_BVNULL;
2774
2775		to++;
2776		filter2bv( a->acl_filter, &bv );
2777		ptr = lutil_strcopy( ptr, " filter=\"" );
2778		ptr = lutil_strcopy( ptr, bv.bv_val );
2779		*ptr++ = '"';
2780		*ptr++ = '\n';
2781		ch_free( bv.bv_val );
2782	}
2783
2784	if ( a->acl_attrs != NULL ) {
2785		int	first = 1;
2786		AttributeName *an;
2787		to++;
2788
2789		ptr = lutil_strcopy( ptr, " attrs=" );
2790		for ( an = a->acl_attrs; an && !BER_BVISNULL( &an->an_name ); an++ ) {
2791			if ( ! first ) *ptr++ = ',';
2792			if (an->an_oc) {
2793				*ptr++ = an->an_oc_exclude ? '!' : '@';
2794				ptr = lutil_strcopy( ptr, an->an_oc->soc_cname.bv_val );
2795
2796			} else {
2797				ptr = lutil_strcopy( ptr, an->an_name.bv_val );
2798			}
2799			first = 0;
2800		}
2801		*ptr++ = '\n';
2802	}
2803
2804	if ( !BER_BVISEMPTY( &a->acl_attrval ) ) {
2805		to++;
2806		ptr = lutil_strcopy( ptr, " val." );
2807		if ( a->acl_attrval_style == ACL_STYLE_BASE &&
2808			a->acl_attrs[0].an_desc->ad_type->sat_syntax ==
2809				slap_schema.si_syn_distinguishedName )
2810			ptr = lutil_strcopy( ptr, style_base );
2811		else
2812			ptr = lutil_strcopy( ptr, style_strings[a->acl_attrval_style] );
2813		*ptr++ = '=';
2814		*ptr++ = '"';
2815		ptr = lutil_strcopy( ptr, a->acl_attrval.bv_val );
2816		*ptr++ = '"';
2817		*ptr++ = '\n';
2818	}
2819
2820	if( !to ) {
2821		ptr = lutil_strcopy( ptr, " *\n" );
2822	}
2823
2824	for ( b = a->acl_access; b != NULL; b = b->a_next ) {
2825		ptr = access2text( b, ptr );
2826	}
2827	*ptr = '\0';
2828	bv->bv_len = ptr - bv->bv_val;
2829}
2830
2831#ifdef LDAP_DEBUG
2832static void
2833print_acl( Backend *be, AccessControl *a )
2834{
2835	struct berval bv;
2836
2837	acl_unparse( a, &bv );
2838	fprintf( stderr, "%s ACL: access %s\n",
2839		be == NULL ? "Global" : "Backend", bv.bv_val );
2840}
2841#endif /* LDAP_DEBUG */
2842