aclparse.c revision 1.1.1.5
1/*	$NetBSD: aclparse.c,v 1.1.1.5 2017/02/09 01:46:58 christos Exp $	*/
2
3/* aclparse.c - routines to parse and check acl's */
4/* $OpenLDAP$ */
5/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6 *
7 * Copyright 1998-2016 The OpenLDAP Foundation.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted only as authorized by the OpenLDAP
12 * Public License.
13 *
14 * A copy of this license is available in the file LICENSE in the
15 * top-level directory of the distribution or, alternatively, at
16 * <http://www.OpenLDAP.org/license.html>.
17 */
18/* Portions Copyright (c) 1995 Regents of the University of Michigan.
19 * All rights reserved.
20 *
21 * Redistribution and use in source and binary forms are permitted
22 * provided that this notice is preserved and that due credit is given
23 * to the University of Michigan at Ann Arbor. The name of the University
24 * may not be used to endorse or promote products derived from this
25 * software without specific prior written permission. This software
26 * is provided ``as is'' without express or implied warranty.
27 */
28
29#include <sys/cdefs.h>
30__RCSID("$NetBSD: aclparse.c,v 1.1.1.5 2017/02/09 01:46:58 christos Exp $");
31
32#include "portable.h"
33
34#include <stdio.h>
35
36#include <ac/ctype.h>
37#include <ac/regex.h>
38#include <ac/socket.h>
39#include <ac/string.h>
40#include <ac/unistd.h>
41
42#include "slap.h"
43#include "lber_pvt.h"
44#include "lutil.h"
45
46static const char style_base[] = "base";
47const char *style_strings[] = {
48	"regex",
49	"expand",
50	"exact",
51	"one",
52	"subtree",
53	"children",
54	"level",
55	"attrof",
56	"anonymous",
57	"users",
58	"self",
59	"ip",
60	"ipv6",
61	"path",
62	NULL
63};
64
65#define ACLBUF_CHUNKSIZE	8192
66static struct berval aclbuf;
67
68static void		split(char *line, int splitchar, char **left, char **right);
69static void		access_append(Access **l, Access *a);
70static void		access_free( Access *a );
71static int		acl_usage(void);
72
73static void		acl_regex_normalized_dn(const char *src, struct berval *pat);
74
75#ifdef LDAP_DEBUG
76static void		print_acl(Backend *be, AccessControl *a);
77#endif
78
79static int		check_scope( BackendDB *be, AccessControl *a );
80
81#ifdef SLAP_DYNACL
82static int
83slap_dynacl_config(
84	const char *fname,
85	int lineno,
86	Access *b,
87	const char *name,
88	const char *opts,
89	slap_style_t sty,
90	const char *right )
91{
92	slap_dynacl_t	*da, *tmp;
93	int		rc = 0;
94
95	for ( da = b->a_dynacl; da; da = da->da_next ) {
96		if ( strcasecmp( da->da_name, name ) == 0 ) {
97			Debug( LDAP_DEBUG_ANY,
98				"%s: line %d: dynacl \"%s\" already specified.\n",
99				fname, lineno, name );
100			return acl_usage();
101		}
102	}
103
104	da = slap_dynacl_get( name );
105	if ( da == NULL ) {
106		return -1;
107	}
108
109	tmp = ch_malloc( sizeof( slap_dynacl_t ) );
110	*tmp = *da;
111
112	if ( tmp->da_parse ) {
113		rc = ( *tmp->da_parse )( fname, lineno, opts, sty, right, &tmp->da_private );
114		if ( rc ) {
115			ch_free( tmp );
116			return rc;
117		}
118	}
119
120	tmp->da_next = b->a_dynacl;
121	b->a_dynacl = tmp;
122
123	return 0;
124}
125#endif /* SLAP_DYNACL */
126
127static void
128regtest(const char *fname, int lineno, char *pat) {
129	int e;
130	regex_t re;
131
132	char		buf[ SLAP_TEXT_BUFLEN ];
133	unsigned	size;
134
135	char *sp;
136	char *dp;
137	int  flag;
138
139	sp = pat;
140	dp = buf;
141	size = 0;
142	buf[0] = '\0';
143
144	for (size = 0, flag = 0; (size < sizeof(buf)) && *sp; sp++) {
145		if (flag) {
146			if (*sp == '$'|| (*sp >= '0' && *sp <= '9')) {
147				*dp++ = *sp;
148				size++;
149			}
150			flag = 0;
151
152		} else {
153			if (*sp == '$') {
154				flag = 1;
155			} else {
156				*dp++ = *sp;
157				size++;
158			}
159		}
160	}
161
162	*dp = '\0';
163	if ( size >= (sizeof(buf) - 1) ) {
164		Debug( LDAP_DEBUG_ANY,
165			"%s: line %d: regular expression \"%s\" too large\n",
166			fname, lineno, pat );
167		(void)acl_usage();
168		exit( EXIT_FAILURE );
169	}
170
171	if ((e = regcomp(&re, buf, REG_EXTENDED|REG_ICASE))) {
172		char error[ SLAP_TEXT_BUFLEN ];
173
174		regerror(e, &re, error, sizeof(error));
175
176		snprintf( buf, sizeof( buf ),
177			"regular expression \"%s\" bad because of %s",
178			pat, error );
179		Debug( LDAP_DEBUG_ANY,
180			"%s: line %d: %s\n",
181			fname, lineno, buf );
182		acl_usage();
183		exit( EXIT_FAILURE );
184	}
185	regfree(&re);
186}
187
188/*
189 * Experimental
190 *
191 * Check if the pattern of an ACL, if any, matches the scope
192 * of the backend it is defined within.
193 */
194#define	ACL_SCOPE_UNKNOWN	(-2)
195#define	ACL_SCOPE_ERR		(-1)
196#define	ACL_SCOPE_OK		(0)
197#define	ACL_SCOPE_PARTIAL	(1)
198#define	ACL_SCOPE_WARN		(2)
199
200static int
201check_scope( BackendDB *be, AccessControl *a )
202{
203	ber_len_t	patlen;
204	struct berval	dn;
205
206	dn = be->be_nsuffix[0];
207
208	if ( BER_BVISEMPTY( &dn ) ) {
209		return ACL_SCOPE_OK;
210	}
211
212	if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
213			a->acl_dn_style != ACL_STYLE_REGEX )
214	{
215		slap_style_t	style = a->acl_dn_style;
216
217		if ( style == ACL_STYLE_REGEX ) {
218			char		dnbuf[SLAP_LDAPDN_MAXLEN + 2];
219			char		rebuf[SLAP_LDAPDN_MAXLEN + 1];
220			ber_len_t	rebuflen;
221			regex_t		re;
222			int		rc;
223
224			/* add trailing '$' to database suffix to form
225			 * a simple trial regex pattern "<suffix>$" */
226			AC_MEMCPY( dnbuf, be->be_nsuffix[0].bv_val,
227				be->be_nsuffix[0].bv_len );
228			dnbuf[be->be_nsuffix[0].bv_len] = '$';
229			dnbuf[be->be_nsuffix[0].bv_len + 1] = '\0';
230
231			if ( regcomp( &re, dnbuf, REG_EXTENDED|REG_ICASE ) ) {
232				return ACL_SCOPE_WARN;
233			}
234
235			/* remove trailing ')$', if any, from original
236			 * regex pattern */
237			rebuflen = a->acl_dn_pat.bv_len;
238			AC_MEMCPY( rebuf, a->acl_dn_pat.bv_val, rebuflen + 1 );
239			if ( rebuf[rebuflen - 1] == '$' ) {
240				rebuf[--rebuflen] = '\0';
241			}
242			while ( rebuflen > be->be_nsuffix[0].bv_len && rebuf[rebuflen - 1] == ')' ) {
243				rebuf[--rebuflen] = '\0';
244			}
245			if ( rebuflen == be->be_nsuffix[0].bv_len ) {
246				rc = ACL_SCOPE_WARN;
247				goto regex_done;
248			}
249
250			/* not a clear indication of scoping error, though */
251			rc = regexec( &re, rebuf, 0, NULL, 0 )
252				? ACL_SCOPE_WARN : ACL_SCOPE_OK;
253
254regex_done:;
255			regfree( &re );
256			return rc;
257		}
258
259		patlen = a->acl_dn_pat.bv_len;
260		/* If backend suffix is longer than pattern,
261		 * it is a potential mismatch (in the sense
262		 * that a superior naming context could
263		 * match */
264		if ( dn.bv_len > patlen ) {
265			/* base is blatantly wrong */
266			if ( style == ACL_STYLE_BASE ) return ACL_SCOPE_ERR;
267
268			/* a style of one can be wrong if there is
269			 * more than one level between the suffix
270			 * and the pattern */
271			if ( style == ACL_STYLE_ONE ) {
272				ber_len_t	rdnlen = 0;
273				int		sep = 0;
274
275				if ( patlen > 0 ) {
276					if ( !DN_SEPARATOR( dn.bv_val[dn.bv_len - patlen - 1] )) {
277						return ACL_SCOPE_ERR;
278					}
279					sep = 1;
280				}
281
282				rdnlen = dn_rdnlen( NULL, &dn );
283				if ( rdnlen != dn.bv_len - patlen - sep )
284					return ACL_SCOPE_ERR;
285			}
286
287			/* if the trailing part doesn't match,
288			 * then it's an error */
289			if ( strcmp( a->acl_dn_pat.bv_val,
290				&dn.bv_val[dn.bv_len - patlen] ) != 0 )
291			{
292				return ACL_SCOPE_ERR;
293			}
294
295			return ACL_SCOPE_PARTIAL;
296		}
297
298		switch ( style ) {
299		case ACL_STYLE_BASE:
300		case ACL_STYLE_ONE:
301		case ACL_STYLE_CHILDREN:
302		case ACL_STYLE_SUBTREE:
303			break;
304
305		default:
306			assert( 0 );
307			break;
308		}
309
310		if ( dn.bv_len < patlen &&
311			!DN_SEPARATOR( a->acl_dn_pat.bv_val[patlen - dn.bv_len - 1] ))
312		{
313			return ACL_SCOPE_ERR;
314		}
315
316		if ( strcmp( &a->acl_dn_pat.bv_val[patlen - dn.bv_len], dn.bv_val )
317			!= 0 )
318		{
319			return ACL_SCOPE_ERR;
320		}
321
322		return ACL_SCOPE_OK;
323	}
324
325	return ACL_SCOPE_UNKNOWN;
326}
327
328int
329parse_acl(
330	Backend	*be,
331	const char	*fname,
332	int		lineno,
333	int		argc,
334	char		**argv,
335	int		pos )
336{
337	int		i;
338	char		*left, *right, *style;
339	struct berval	bv;
340	AccessControl	*a = NULL;
341	Access	*b = NULL;
342	int rc;
343	const char *text;
344
345	for ( i = 1; i < argc; i++ ) {
346		/* to clause - select which entries are protected */
347		if ( strcasecmp( argv[i], "to" ) == 0 ) {
348			if ( a != NULL ) {
349				Debug( LDAP_DEBUG_ANY, "%s: line %d: "
350					"only one to clause allowed in access line\n",
351				    fname, lineno, 0 );
352				goto fail;
353			}
354			a = (AccessControl *) ch_calloc( 1, sizeof(AccessControl) );
355			a->acl_attrval_style = ACL_STYLE_NONE;
356			for ( ++i; i < argc; i++ ) {
357				if ( strcasecmp( argv[i], "by" ) == 0 ) {
358					i--;
359					break;
360				}
361
362				if ( strcasecmp( argv[i], "*" ) == 0 ) {
363					if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
364						a->acl_dn_style != ACL_STYLE_REGEX )
365					{
366						Debug( LDAP_DEBUG_ANY,
367							"%s: line %d: dn pattern"
368							" already specified in to clause.\n",
369							fname, lineno, 0 );
370						goto fail;
371					}
372
373					ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
374					continue;
375				}
376
377				split( argv[i], '=', &left, &right );
378				split( left, '.', &left, &style );
379
380				if ( right == NULL ) {
381					Debug( LDAP_DEBUG_ANY, "%s: line %d: "
382						"missing \"=\" in \"%s\" in to clause\n",
383					    fname, lineno, left );
384					goto fail;
385				}
386
387				if ( strcasecmp( left, "dn" ) == 0 ) {
388					if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
389						a->acl_dn_style != ACL_STYLE_REGEX )
390					{
391						Debug( LDAP_DEBUG_ANY,
392							"%s: line %d: dn pattern"
393							" already specified in to clause.\n",
394							fname, lineno, 0 );
395						goto fail;
396					}
397
398					if ( style == NULL || *style == '\0' ||
399						strcasecmp( style, "baseObject" ) == 0 ||
400						strcasecmp( style, "base" ) == 0 ||
401						strcasecmp( style, "exact" ) == 0 )
402					{
403						a->acl_dn_style = ACL_STYLE_BASE;
404						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
405
406					} else if ( strcasecmp( style, "oneLevel" ) == 0 ||
407						strcasecmp( style, "one" ) == 0 )
408					{
409						a->acl_dn_style = ACL_STYLE_ONE;
410						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
411
412					} else if ( strcasecmp( style, "subtree" ) == 0 ||
413						strcasecmp( style, "sub" ) == 0 )
414					{
415						if( *right == '\0' ) {
416							ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
417
418						} else {
419							a->acl_dn_style = ACL_STYLE_SUBTREE;
420							ber_str2bv( right, 0, 1, &a->acl_dn_pat );
421						}
422
423					} else if ( strcasecmp( style, "children" ) == 0 ) {
424						a->acl_dn_style = ACL_STYLE_CHILDREN;
425						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
426
427					} else if ( strcasecmp( style, "regex" ) == 0 ) {
428						a->acl_dn_style = ACL_STYLE_REGEX;
429
430						if ( *right == '\0' ) {
431							/* empty regex should match empty DN */
432							a->acl_dn_style = ACL_STYLE_BASE;
433							ber_str2bv( right, 0, 1, &a->acl_dn_pat );
434
435						} else if ( strcmp(right, "*") == 0
436							|| strcmp(right, ".*") == 0
437							|| strcmp(right, ".*$") == 0
438							|| strcmp(right, "^.*") == 0
439							|| strcmp(right, "^.*$") == 0
440							|| strcmp(right, ".*$$") == 0
441							|| strcmp(right, "^.*$$") == 0 )
442						{
443							ber_str2bv( "*", STRLENOF("*"), 1, &a->acl_dn_pat );
444
445						} else {
446							acl_regex_normalized_dn( right, &a->acl_dn_pat );
447						}
448
449					} else {
450						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
451							"unknown dn style \"%s\" in to clause\n",
452						    fname, lineno, style );
453						goto fail;
454					}
455
456					continue;
457				}
458
459				if ( strcasecmp( left, "filter" ) == 0 ) {
460					if ( (a->acl_filter = str2filter( right )) == NULL ) {
461						Debug( LDAP_DEBUG_ANY,
462				"%s: line %d: bad filter \"%s\" in to clause\n",
463						    fname, lineno, right );
464						goto fail;
465					}
466
467				} else if ( strcasecmp( left, "attr" ) == 0		/* TOLERATED */
468						|| strcasecmp( left, "attrs" ) == 0 )	/* DOCUMENTED */
469				{
470					if ( strcasecmp( left, "attr" ) == 0 ) {
471						Debug( LDAP_DEBUG_ANY,
472							"%s: line %d: \"attr\" "
473							"is deprecated (and undocumented); "
474							"use \"attrs\" instead.\n",
475							fname, lineno, 0 );
476					}
477
478					a->acl_attrs = str2anlist( a->acl_attrs,
479						right, "," );
480					if ( a->acl_attrs == NULL ) {
481						Debug( LDAP_DEBUG_ANY,
482				"%s: line %d: unknown attr \"%s\" in to clause\n",
483						    fname, lineno, right );
484						goto fail;
485					}
486
487				} else if ( strncasecmp( left, "val", 3 ) == 0 ) {
488					struct berval	bv;
489					char		*mr;
490
491					if ( !BER_BVISEMPTY( &a->acl_attrval ) ) {
492						Debug( LDAP_DEBUG_ANY,
493				"%s: line %d: attr val already specified in to clause.\n",
494							fname, lineno, 0 );
495						goto fail;
496					}
497					if ( a->acl_attrs == NULL || !BER_BVISEMPTY( &a->acl_attrs[1].an_name ) )
498					{
499						Debug( LDAP_DEBUG_ANY,
500				"%s: line %d: attr val requires a single attribute.\n",
501							fname, lineno, 0 );
502						goto fail;
503					}
504
505					ber_str2bv( right, 0, 0, &bv );
506					a->acl_attrval_style = ACL_STYLE_BASE;
507
508					mr = strchr( left, '/' );
509					if ( mr != NULL ) {
510						mr[ 0 ] = '\0';
511						mr++;
512
513						a->acl_attrval_mr = mr_find( mr );
514						if ( a->acl_attrval_mr == NULL ) {
515							Debug( LDAP_DEBUG_ANY, "%s: line %d: "
516								"invalid matching rule \"%s\".\n",
517								fname, lineno, mr );
518							goto fail;
519						}
520
521						if( !mr_usable_with_at( a->acl_attrval_mr, a->acl_attrs[ 0 ].an_desc->ad_type ) )
522						{
523							char	buf[ SLAP_TEXT_BUFLEN ];
524
525							snprintf( buf, sizeof( buf ),
526								"matching rule \"%s\" use "
527								"with attr \"%s\" not appropriate.",
528								mr, a->acl_attrs[ 0 ].an_name.bv_val );
529
530
531							Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
532								fname, lineno, buf );
533							goto fail;
534						}
535					}
536
537					if ( style != NULL ) {
538						if ( strcasecmp( style, "regex" ) == 0 ) {
539							int e = regcomp( &a->acl_attrval_re, bv.bv_val,
540								REG_EXTENDED | REG_ICASE );
541							if ( e ) {
542								char	err[SLAP_TEXT_BUFLEN],
543									buf[ SLAP_TEXT_BUFLEN ];
544
545								regerror( e, &a->acl_attrval_re, err, sizeof( err ) );
546
547								snprintf( buf, sizeof( buf ),
548									"regular expression \"%s\" bad because of %s",
549									right, err );
550
551								Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
552									fname, lineno, buf );
553								goto fail;
554							}
555							a->acl_attrval_style = ACL_STYLE_REGEX;
556
557						} else {
558							/* FIXME: if the attribute has DN syntax, we might
559							 * allow one, subtree and children styles as well */
560							if ( !strcasecmp( style, "base" ) ||
561								!strcasecmp( style, "exact" ) ) {
562								a->acl_attrval_style = ACL_STYLE_BASE;
563
564							} else if ( a->acl_attrs[0].an_desc->ad_type->
565								sat_syntax == slap_schema.si_syn_distinguishedName )
566							{
567								if ( !strcasecmp( style, "baseObject" ) ||
568									!strcasecmp( style, "base" ) )
569								{
570									a->acl_attrval_style = ACL_STYLE_BASE;
571								} else if ( !strcasecmp( style, "onelevel" ) ||
572									!strcasecmp( style, "one" ) )
573								{
574									a->acl_attrval_style = ACL_STYLE_ONE;
575								} else if ( !strcasecmp( style, "subtree" ) ||
576									!strcasecmp( style, "sub" ) )
577								{
578									a->acl_attrval_style = ACL_STYLE_SUBTREE;
579								} else if ( !strcasecmp( style, "children" ) ) {
580									a->acl_attrval_style = ACL_STYLE_CHILDREN;
581								} else {
582									char	buf[ SLAP_TEXT_BUFLEN ];
583
584									snprintf( buf, sizeof( buf ),
585										"unknown val.<style> \"%s\" for attributeType \"%s\" "
586											"with DN syntax.",
587										style,
588										a->acl_attrs[0].an_desc->ad_cname.bv_val );
589
590									Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL,
591										"%s: line %d: %s\n",
592										fname, lineno, buf );
593									goto fail;
594								}
595
596								rc = dnNormalize( 0, NULL, NULL, &bv, &a->acl_attrval, NULL );
597								if ( rc != LDAP_SUCCESS ) {
598									char	buf[ SLAP_TEXT_BUFLEN ];
599
600									snprintf( buf, sizeof( buf ),
601										"unable to normalize DN \"%s\" "
602										"for attributeType \"%s\" (%d).",
603										bv.bv_val,
604										a->acl_attrs[0].an_desc->ad_cname.bv_val,
605										rc );
606									Debug( LDAP_DEBUG_ANY,
607										"%s: line %d: %s\n",
608										fname, lineno, buf );
609									goto fail;
610								}
611
612							} else {
613								char	buf[ SLAP_TEXT_BUFLEN ];
614
615								snprintf( buf, sizeof( buf ),
616									"unknown val.<style> \"%s\" for attributeType \"%s\".",
617									style, a->acl_attrs[0].an_desc->ad_cname.bv_val );
618								Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL,
619									"%s: line %d: %s\n",
620									fname, lineno, buf );
621								goto fail;
622							}
623						}
624					}
625
626					/* Check for appropriate matching rule */
627					if ( a->acl_attrval_style == ACL_STYLE_REGEX ) {
628						ber_dupbv( &a->acl_attrval, &bv );
629
630					} else if ( BER_BVISNULL( &a->acl_attrval ) ) {
631						int		rc;
632						const char	*text;
633
634						if ( a->acl_attrval_mr == NULL ) {
635							a->acl_attrval_mr = a->acl_attrs[ 0 ].an_desc->ad_type->sat_equality;
636						}
637
638						if ( a->acl_attrval_mr == NULL ) {
639							Debug( LDAP_DEBUG_ANY, "%s: line %d: "
640								"attr \"%s\" does not have an EQUALITY matching rule.\n",
641								fname, lineno, a->acl_attrs[ 0 ].an_name.bv_val );
642							goto fail;
643						}
644
645						rc = asserted_value_validate_normalize(
646							a->acl_attrs[ 0 ].an_desc,
647							a->acl_attrval_mr,
648							SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
649							&bv,
650							&a->acl_attrval,
651							&text,
652							NULL );
653						if ( rc != LDAP_SUCCESS ) {
654							char	buf[ SLAP_TEXT_BUFLEN ];
655
656							snprintf( buf, sizeof( buf ), "%s: line %d: "
657								" attr \"%s\" normalization failed (%d: %s)",
658								fname, lineno,
659								a->acl_attrs[ 0 ].an_name.bv_val, rc, text );
660							Debug( LDAP_DEBUG_ANY, "%s: line %d: %s.\n",
661								fname, lineno, buf );
662							goto fail;
663						}
664					}
665
666				} else {
667					Debug( LDAP_DEBUG_ANY,
668						"%s: line %d: expecting <what> got \"%s\"\n",
669					    fname, lineno, left );
670					goto fail;
671				}
672			}
673
674			if ( !BER_BVISNULL( &a->acl_dn_pat ) &&
675					ber_bvccmp( &a->acl_dn_pat, '*' ) )
676			{
677				free( a->acl_dn_pat.bv_val );
678				BER_BVZERO( &a->acl_dn_pat );
679				a->acl_dn_style = ACL_STYLE_REGEX;
680			}
681
682			if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
683					a->acl_dn_style != ACL_STYLE_REGEX )
684			{
685				if ( a->acl_dn_style != ACL_STYLE_REGEX ) {
686					struct berval bv;
687					rc = dnNormalize( 0, NULL, NULL, &a->acl_dn_pat, &bv, NULL);
688					if ( rc != LDAP_SUCCESS ) {
689						Debug( LDAP_DEBUG_ANY,
690							"%s: line %d: bad DN \"%s\" in to DN clause\n",
691							fname, lineno, a->acl_dn_pat.bv_val );
692						goto fail;
693					}
694					free( a->acl_dn_pat.bv_val );
695					a->acl_dn_pat = bv;
696
697				} else {
698					int e = regcomp( &a->acl_dn_re, a->acl_dn_pat.bv_val,
699						REG_EXTENDED | REG_ICASE );
700					if ( e ) {
701						char	err[ SLAP_TEXT_BUFLEN ],
702							buf[ SLAP_TEXT_BUFLEN ];
703
704						regerror( e, &a->acl_dn_re, err, sizeof( err ) );
705						snprintf( buf, sizeof( buf ),
706							"regular expression \"%s\" bad because of %s",
707							right, err );
708						Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
709							fname, lineno, buf );
710						goto fail;
711					}
712				}
713			}
714
715		/* by clause - select who has what access to entries */
716		} else if ( strcasecmp( argv[i], "by" ) == 0 ) {
717			if ( a == NULL ) {
718				Debug( LDAP_DEBUG_ANY, "%s: line %d: "
719					"to clause required before by clause in access line\n",
720					fname, lineno, 0 );
721				goto fail;
722			}
723
724			/*
725			 * by clause consists of <who> and <access>
726			 */
727
728			if ( ++i == argc ) {
729				Debug( LDAP_DEBUG_ANY,
730					"%s: line %d: premature EOL: expecting <who>\n",
731					fname, lineno, 0 );
732				goto fail;
733			}
734
735			b = (Access *) ch_calloc( 1, sizeof(Access) );
736
737			ACL_INVALIDATE( b->a_access_mask );
738
739			/* get <who> */
740			for ( ; i < argc; i++ ) {
741				slap_style_t	sty = ACL_STYLE_REGEX;
742				char		*style_modifier = NULL;
743				char		*style_level = NULL;
744				int		level = 0;
745				int		expand = 0;
746				slap_dn_access	*bdn = &b->a_dn;
747				int		is_realdn = 0;
748
749				split( argv[i], '=', &left, &right );
750				split( left, '.', &left, &style );
751				if ( style ) {
752					split( style, ',', &style, &style_modifier );
753
754					if ( strncasecmp( style, "level", STRLENOF( "level" ) ) == 0 ) {
755						split( style, '{', &style, &style_level );
756						if ( style_level != NULL ) {
757							char *p = strchr( style_level, '}' );
758							if ( p == NULL ) {
759								Debug( LDAP_DEBUG_ANY,
760									"%s: line %d: premature eol: "
761									"expecting closing '}' in \"level{n}\"\n",
762									fname, lineno, 0 );
763								goto fail;
764							} else if ( p == style_level ) {
765								Debug( LDAP_DEBUG_ANY,
766									"%s: line %d: empty level "
767									"in \"level{n}\"\n",
768									fname, lineno, 0 );
769								goto fail;
770							}
771							p[0] = '\0';
772						}
773					}
774				}
775
776				if ( style == NULL || *style == '\0' ||
777					strcasecmp( style, "exact" ) == 0 ||
778					strcasecmp( style, "baseObject" ) == 0 ||
779					strcasecmp( style, "base" ) == 0 )
780				{
781					sty = ACL_STYLE_BASE;
782
783				} else if ( strcasecmp( style, "onelevel" ) == 0 ||
784					strcasecmp( style, "one" ) == 0 )
785				{
786					sty = ACL_STYLE_ONE;
787
788				} else if ( strcasecmp( style, "subtree" ) == 0 ||
789					strcasecmp( style, "sub" ) == 0 )
790				{
791					sty = ACL_STYLE_SUBTREE;
792
793				} else if ( strcasecmp( style, "children" ) == 0 ) {
794					sty = ACL_STYLE_CHILDREN;
795
796				} else if ( strcasecmp( style, "level" ) == 0 )
797				{
798					if ( lutil_atoi( &level, style_level ) != 0 ) {
799						Debug( LDAP_DEBUG_ANY,
800							"%s: line %d: unable to parse level "
801							"in \"level{n}\"\n",
802							fname, lineno, 0 );
803						goto fail;
804					}
805
806					sty = ACL_STYLE_LEVEL;
807
808				} else if ( strcasecmp( style, "regex" ) == 0 ) {
809					sty = ACL_STYLE_REGEX;
810
811				} else if ( strcasecmp( style, "expand" ) == 0 ) {
812					sty = ACL_STYLE_EXPAND;
813
814				} else if ( strcasecmp( style, "ip" ) == 0 ) {
815					sty = ACL_STYLE_IP;
816
817				} else if ( strcasecmp( style, "ipv6" ) == 0 ) {
818#ifndef LDAP_PF_INET6
819					Debug( LDAP_DEBUG_ANY,
820						"%s: line %d: IPv6 not supported\n",
821						fname, lineno, 0 );
822#endif /* ! LDAP_PF_INET6 */
823					sty = ACL_STYLE_IPV6;
824
825				} else if ( strcasecmp( style, "path" ) == 0 ) {
826					sty = ACL_STYLE_PATH;
827#ifndef LDAP_PF_LOCAL
828					Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL,
829						"%s: line %d: "
830						"\"path\" style modifier is useless without local.\n",
831						fname, lineno, 0 );
832					goto fail;
833#endif /* LDAP_PF_LOCAL */
834
835				} else {
836					Debug( LDAP_DEBUG_ANY,
837						"%s: line %d: unknown style \"%s\" in by clause\n",
838						fname, lineno, style );
839					goto fail;
840				}
841
842				if ( style_modifier &&
843					strcasecmp( style_modifier, "expand" ) == 0 )
844				{
845					switch ( sty ) {
846					case ACL_STYLE_REGEX:
847						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
848							"\"regex\" style implies \"expand\" modifier.\n",
849							fname, lineno, 0 );
850						goto fail;
851						break;
852
853					case ACL_STYLE_EXPAND:
854						break;
855
856					default:
857						/* we'll see later if it's pertinent */
858						expand = 1;
859						break;
860					}
861				}
862
863				if ( strncasecmp( left, "real", STRLENOF( "real" ) ) == 0 ) {
864					is_realdn = 1;
865					bdn = &b->a_realdn;
866					left += STRLENOF( "real" );
867				}
868
869				if ( strcasecmp( left, "*" ) == 0 ) {
870					if ( is_realdn ) {
871						goto fail;
872					}
873
874					ber_str2bv( "*", STRLENOF( "*" ), 1, &bv );
875					sty = ACL_STYLE_REGEX;
876
877				} else if ( strcasecmp( left, "anonymous" ) == 0 ) {
878					ber_str2bv("anonymous", STRLENOF( "anonymous" ), 1, &bv);
879					sty = ACL_STYLE_ANONYMOUS;
880
881				} else if ( strcasecmp( left, "users" ) == 0 ) {
882					ber_str2bv("users", STRLENOF( "users" ), 1, &bv);
883					sty = ACL_STYLE_USERS;
884
885				} else if ( strcasecmp( left, "self" ) == 0 ) {
886					ber_str2bv("self", STRLENOF( "self" ), 1, &bv);
887					sty = ACL_STYLE_SELF;
888
889				} else if ( strcasecmp( left, "dn" ) == 0 ) {
890					if ( sty == ACL_STYLE_REGEX ) {
891						bdn->a_style = ACL_STYLE_REGEX;
892						if ( right == NULL ) {
893							/* no '=' */
894							ber_str2bv("users",
895								STRLENOF( "users" ),
896								1, &bv);
897							bdn->a_style = ACL_STYLE_USERS;
898
899						} else if (*right == '\0' ) {
900							/* dn="" */
901							ber_str2bv("anonymous",
902								STRLENOF( "anonymous" ),
903								1, &bv);
904							bdn->a_style = ACL_STYLE_ANONYMOUS;
905
906						} else if ( strcmp( right, "*" ) == 0 ) {
907							/* dn=* */
908							/* any or users?  users for now */
909							ber_str2bv("users",
910								STRLENOF( "users" ),
911								1, &bv);
912							bdn->a_style = ACL_STYLE_USERS;
913
914						} else if ( strcmp( right, ".+" ) == 0
915							|| strcmp( right, "^.+" ) == 0
916							|| strcmp( right, ".+$" ) == 0
917							|| strcmp( right, "^.+$" ) == 0
918							|| strcmp( right, ".+$$" ) == 0
919							|| strcmp( right, "^.+$$" ) == 0 )
920						{
921							ber_str2bv("users",
922								STRLENOF( "users" ),
923								1, &bv);
924							bdn->a_style = ACL_STYLE_USERS;
925
926						} else if ( strcmp( right, ".*" ) == 0
927							|| strcmp( right, "^.*" ) == 0
928							|| strcmp( right, ".*$" ) == 0
929							|| strcmp( right, "^.*$" ) == 0
930							|| strcmp( right, ".*$$" ) == 0
931							|| strcmp( right, "^.*$$" ) == 0 )
932						{
933							ber_str2bv("*",
934								STRLENOF( "*" ),
935								1, &bv);
936
937						} else {
938							acl_regex_normalized_dn( right, &bv );
939							if ( !ber_bvccmp( &bv, '*' ) ) {
940								regtest( fname, lineno, bv.bv_val );
941							}
942						}
943
944					} else if ( right == NULL || *right == '\0' ) {
945						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
946							"missing \"=\" in (or value after) \"%s\" "
947							"in by clause\n",
948							fname, lineno, left );
949						goto fail;
950
951					} else {
952						ber_str2bv( right, 0, 1, &bv );
953					}
954
955				} else {
956					BER_BVZERO( &bv );
957				}
958
959				if ( !BER_BVISNULL( &bv ) ) {
960					if ( !BER_BVISEMPTY( &bdn->a_pat ) ) {
961						Debug( LDAP_DEBUG_ANY,
962							"%s: line %d: dn pattern already specified.\n",
963							fname, lineno, 0 );
964						goto fail;
965					}
966
967					if ( sty != ACL_STYLE_REGEX &&
968							sty != ACL_STYLE_ANONYMOUS &&
969							sty != ACL_STYLE_USERS &&
970							sty != ACL_STYLE_SELF &&
971							expand == 0 )
972					{
973						rc = dnNormalize(0, NULL, NULL,
974							&bv, &bdn->a_pat, NULL);
975						if ( rc != LDAP_SUCCESS ) {
976							Debug( LDAP_DEBUG_ANY,
977								"%s: line %d: bad DN \"%s\" in by DN clause\n",
978								fname, lineno, bv.bv_val );
979							goto fail;
980						}
981						free( bv.bv_val );
982						if ( sty == ACL_STYLE_BASE
983							&& be != NULL
984							&& !BER_BVISNULL( &be->be_rootndn )
985							&& dn_match( &bdn->a_pat, &be->be_rootndn ) )
986						{
987							Debug( LDAP_DEBUG_ANY,
988								"%s: line %d: rootdn is always granted "
989								"unlimited privileges.\n",
990								fname, lineno, 0 );
991						}
992
993					} else {
994						bdn->a_pat = bv;
995					}
996					bdn->a_style = sty;
997					if ( expand ) {
998						char	*exp;
999						int	gotit = 0;
1000
1001						for ( exp = strchr( bdn->a_pat.bv_val, '$' );
1002							exp && (ber_len_t)(exp - bdn->a_pat.bv_val)
1003								< bdn->a_pat.bv_len;
1004							exp = strchr( exp, '$' ) )
1005						{
1006							if ( ( isdigit( (unsigned char) exp[ 1 ] ) ||
1007								    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
2452void
2453acl_destroy( AccessControl *a )
2454{
2455	AccessControl *n;
2456
2457	for ( ; a; a = n ) {
2458		n = a->acl_next;
2459		acl_free( a );
2460	}
2461
2462	if ( !BER_BVISNULL( &aclbuf ) ) {
2463		ch_free( aclbuf.bv_val );
2464		BER_BVZERO( &aclbuf );
2465	}
2466}
2467
2468char *
2469access2str( slap_access_t access )
2470{
2471	if ( access == ACL_NONE ) {
2472		return "none";
2473
2474	} else if ( access == ACL_DISCLOSE ) {
2475		return "disclose";
2476
2477	} else if ( access == ACL_AUTH ) {
2478		return "auth";
2479
2480	} else if ( access == ACL_COMPARE ) {
2481		return "compare";
2482
2483	} else if ( access == ACL_SEARCH ) {
2484		return "search";
2485
2486	} else if ( access == ACL_READ ) {
2487		return "read";
2488
2489	} else if ( access == ACL_WRITE ) {
2490		return "write";
2491
2492	} else if ( access == ACL_WADD ) {
2493		return "add";
2494
2495	} else if ( access == ACL_WDEL ) {
2496		return "delete";
2497
2498	} else if ( access == ACL_MANAGE ) {
2499		return "manage";
2500
2501	}
2502
2503	return "unknown";
2504}
2505
2506slap_access_t
2507str2access( const char *str )
2508{
2509	if ( strcasecmp( str, "none" ) == 0 ) {
2510		return ACL_NONE;
2511
2512	} else if ( strcasecmp( str, "disclose" ) == 0 ) {
2513		return ACL_DISCLOSE;
2514
2515	} else if ( strcasecmp( str, "auth" ) == 0 ) {
2516		return ACL_AUTH;
2517
2518	} else if ( strcasecmp( str, "compare" ) == 0 ) {
2519		return ACL_COMPARE;
2520
2521	} else if ( strcasecmp( str, "search" ) == 0 ) {
2522		return ACL_SEARCH;
2523
2524	} else if ( strcasecmp( str, "read" ) == 0 ) {
2525		return ACL_READ;
2526
2527	} else if ( strcasecmp( str, "write" ) == 0 ) {
2528		return ACL_WRITE;
2529
2530	} else if ( strcasecmp( str, "add" ) == 0 ) {
2531		return ACL_WADD;
2532
2533	} else if ( strcasecmp( str, "delete" ) == 0 ) {
2534		return ACL_WDEL;
2535
2536	} else if ( strcasecmp( str, "manage" ) == 0 ) {
2537		return ACL_MANAGE;
2538	}
2539
2540	return( ACL_INVALID_ACCESS );
2541}
2542
2543static char *
2544safe_strncopy( char *ptr, const char *src, size_t n, struct berval *buf )
2545{
2546	while ( ptr + n >= buf->bv_val + buf->bv_len ) {
2547		char *tmp = ch_realloc( buf->bv_val, 2*buf->bv_len );
2548		if ( tmp == NULL ) {
2549			return NULL;
2550		}
2551		ptr = tmp + (ptr - buf->bv_val);
2552		buf->bv_val = tmp;
2553		buf->bv_len *= 2;
2554	}
2555
2556	return lutil_strncopy( ptr, src, n );
2557}
2558
2559static char *
2560safe_strcopy( char *ptr, const char *s, struct berval *buf )
2561{
2562	size_t n = strlen( s );
2563
2564	return safe_strncopy( ptr, s, n, buf );
2565}
2566
2567static char *
2568safe_strbvcopy( char *ptr, const struct berval *bv, struct berval *buf )
2569{
2570	return safe_strncopy( ptr, bv->bv_val, bv->bv_len, buf );
2571}
2572
2573#define acl_safe_strcopy( ptr, s ) safe_strcopy( (ptr), (s), &aclbuf )
2574#define acl_safe_strncopy( ptr, s, n ) safe_strncopy( (ptr), (s), (n), &aclbuf )
2575#define acl_safe_strbvcopy( ptr, bv ) safe_strbvcopy( (ptr), (bv), &aclbuf )
2576
2577static char *
2578dnaccess2text( slap_dn_access *bdn, char *ptr, int is_realdn )
2579{
2580	*ptr++ = ' ';
2581
2582	if ( is_realdn ) {
2583		ptr = acl_safe_strcopy( ptr, "real" );
2584	}
2585
2586	if ( ber_bvccmp( &bdn->a_pat, '*' ) ||
2587		bdn->a_style == ACL_STYLE_ANONYMOUS ||
2588		bdn->a_style == ACL_STYLE_USERS ||
2589		bdn->a_style == ACL_STYLE_SELF )
2590	{
2591		if ( is_realdn ) {
2592			assert( ! ber_bvccmp( &bdn->a_pat, '*' ) );
2593		}
2594
2595		ptr = acl_safe_strbvcopy( ptr, &bdn->a_pat );
2596		if ( bdn->a_style == ACL_STYLE_SELF && bdn->a_self_level != 0 ) {
2597			char buf[SLAP_TEXT_BUFLEN];
2598			int n = snprintf( buf, sizeof(buf), ".level{%d}", bdn->a_self_level );
2599			if ( n > 0 ) {
2600				ptr = acl_safe_strncopy( ptr, buf, n );
2601			} /* else ? */
2602		}
2603
2604	} else {
2605		ptr = acl_safe_strcopy( ptr, "dn." );
2606		if ( bdn->a_style == ACL_STYLE_BASE )
2607			ptr = acl_safe_strcopy( ptr, style_base );
2608		else
2609			ptr = acl_safe_strcopy( ptr, style_strings[bdn->a_style] );
2610		if ( bdn->a_style == ACL_STYLE_LEVEL ) {
2611			char buf[SLAP_TEXT_BUFLEN];
2612			int n = snprintf( buf, sizeof(buf), "{%d}", bdn->a_level );
2613			if ( n > 0 ) {
2614				ptr = acl_safe_strncopy( ptr, buf, n );
2615			} /* else ? */
2616		}
2617		if ( bdn->a_expand ) {
2618			ptr = acl_safe_strcopy( ptr, ",expand" );
2619		}
2620		ptr = acl_safe_strcopy( ptr, "=\"" );
2621		ptr = acl_safe_strbvcopy( ptr, &bdn->a_pat );
2622		ptr = acl_safe_strcopy( ptr, "\"" );
2623	}
2624	return ptr;
2625}
2626
2627static char *
2628access2text( Access *b, char *ptr )
2629{
2630	char maskbuf[ACCESSMASK_MAXLEN];
2631
2632	ptr = acl_safe_strcopy( ptr, "\tby" );
2633
2634	if ( !BER_BVISEMPTY( &b->a_dn_pat ) ) {
2635		ptr = dnaccess2text( &b->a_dn, ptr, 0 );
2636	}
2637	if ( b->a_dn_at ) {
2638		ptr = acl_safe_strcopy( ptr, " dnattr=" );
2639		ptr = acl_safe_strbvcopy( ptr, &b->a_dn_at->ad_cname );
2640	}
2641
2642	if ( !BER_BVISEMPTY( &b->a_realdn_pat ) ) {
2643		ptr = dnaccess2text( &b->a_realdn, ptr, 1 );
2644	}
2645	if ( b->a_realdn_at ) {
2646		ptr = acl_safe_strcopy( ptr, " realdnattr=" );
2647		ptr = acl_safe_strbvcopy( ptr, &b->a_realdn_at->ad_cname );
2648	}
2649
2650	if ( !BER_BVISEMPTY( &b->a_group_pat ) ) {
2651		ptr = acl_safe_strcopy( ptr, " group/" );
2652		ptr = acl_safe_strcopy( ptr, b->a_group_oc ?
2653			b->a_group_oc->soc_cname.bv_val : SLAPD_GROUP_CLASS );
2654		ptr = acl_safe_strcopy( ptr, "/" );
2655		ptr = acl_safe_strcopy( ptr, b->a_group_at ?
2656			b->a_group_at->ad_cname.bv_val : SLAPD_GROUP_ATTR );
2657		ptr = acl_safe_strcopy( ptr, "." );
2658		ptr = acl_safe_strcopy( ptr, style_strings[b->a_group_style] );
2659		ptr = acl_safe_strcopy( ptr, "=\"" );
2660		ptr = acl_safe_strbvcopy( ptr, &b->a_group_pat );
2661		ptr = acl_safe_strcopy( ptr, "\"" );
2662	}
2663
2664	if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) {
2665		ptr = acl_safe_strcopy( ptr, " peername" );
2666		ptr = acl_safe_strcopy( ptr, "." );
2667		ptr = acl_safe_strcopy( ptr, style_strings[b->a_peername_style] );
2668		ptr = acl_safe_strcopy( ptr, "=\"" );
2669		ptr = acl_safe_strbvcopy( ptr, &b->a_peername_pat );
2670		ptr = acl_safe_strcopy( ptr, "\"" );
2671	}
2672
2673	if ( !BER_BVISEMPTY( &b->a_sockname_pat ) ) {
2674		ptr = acl_safe_strcopy( ptr, " sockname" );
2675		ptr = acl_safe_strcopy( ptr, "." );
2676		ptr = acl_safe_strcopy( ptr, style_strings[b->a_sockname_style] );
2677		ptr = acl_safe_strcopy( ptr, "=\"" );
2678		ptr = acl_safe_strbvcopy( ptr, &b->a_sockname_pat );
2679		ptr = acl_safe_strcopy( ptr, "\"" );
2680	}
2681
2682	if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) {
2683		ptr = acl_safe_strcopy( ptr, " domain" );
2684		ptr = acl_safe_strcopy( ptr, "." );
2685		ptr = acl_safe_strcopy( ptr, style_strings[b->a_domain_style] );
2686		if ( b->a_domain_expand ) {
2687			ptr = acl_safe_strcopy( ptr, ",expand" );
2688		}
2689		ptr = acl_safe_strcopy( ptr, "=" );
2690		ptr = acl_safe_strbvcopy( ptr, &b->a_domain_pat );
2691	}
2692
2693	if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) {
2694		ptr = acl_safe_strcopy( ptr, " sockurl" );
2695		ptr = acl_safe_strcopy( ptr, "." );
2696		ptr = acl_safe_strcopy( ptr, style_strings[b->a_sockurl_style] );
2697		ptr = acl_safe_strcopy( ptr, "=\"" );
2698		ptr = acl_safe_strbvcopy( ptr, &b->a_sockurl_pat );
2699		ptr = acl_safe_strcopy( ptr, "\"" );
2700	}
2701
2702	if ( !BER_BVISEMPTY( &b->a_set_pat ) ) {
2703		ptr = acl_safe_strcopy( ptr, " set" );
2704		ptr = acl_safe_strcopy( ptr, "." );
2705		ptr = acl_safe_strcopy( ptr, style_strings[b->a_set_style] );
2706		ptr = acl_safe_strcopy( ptr, "=\"" );
2707		ptr = acl_safe_strbvcopy( ptr, &b->a_set_pat );
2708		ptr = acl_safe_strcopy( ptr, "\"" );
2709	}
2710
2711#ifdef SLAP_DYNACL
2712	if ( b->a_dynacl ) {
2713		slap_dynacl_t	*da;
2714
2715		for ( da = b->a_dynacl; da; da = da->da_next ) {
2716			if ( da->da_unparse ) {
2717				struct berval bv = BER_BVNULL;
2718				(void)( *da->da_unparse )( da->da_private, &bv );
2719				assert( !BER_BVISNULL( &bv ) );
2720				ptr = acl_safe_strbvcopy( ptr, &bv );
2721				ch_free( bv.bv_val );
2722			}
2723		}
2724	}
2725#endif /* SLAP_DYNACL */
2726
2727	/* Security Strength Factors */
2728	if ( b->a_authz.sai_ssf ) {
2729		char buf[SLAP_TEXT_BUFLEN];
2730		int n = snprintf( buf, sizeof(buf), " ssf=%u",
2731			b->a_authz.sai_ssf );
2732		ptr = acl_safe_strncopy( ptr, buf, n );
2733	}
2734	if ( b->a_authz.sai_transport_ssf ) {
2735		char buf[SLAP_TEXT_BUFLEN];
2736		int n = snprintf( buf, sizeof(buf), " transport_ssf=%u",
2737			b->a_authz.sai_transport_ssf );
2738		ptr = acl_safe_strncopy( ptr, buf, n );
2739	}
2740	if ( b->a_authz.sai_tls_ssf ) {
2741		char buf[SLAP_TEXT_BUFLEN];
2742		int n = snprintf( buf, sizeof(buf), " tls_ssf=%u",
2743			b->a_authz.sai_tls_ssf );
2744		ptr = acl_safe_strncopy( ptr, buf, n );
2745	}
2746	if ( b->a_authz.sai_sasl_ssf ) {
2747		char buf[SLAP_TEXT_BUFLEN];
2748		int n = snprintf( buf, sizeof(buf), " sasl_ssf=%u",
2749			b->a_authz.sai_sasl_ssf );
2750		ptr = acl_safe_strncopy( ptr, buf, n );
2751	}
2752
2753	ptr = acl_safe_strcopy( ptr, " " );
2754	if ( b->a_dn_self ) {
2755		ptr = acl_safe_strcopy( ptr, "self" );
2756	} else if ( b->a_realdn_self ) {
2757		ptr = acl_safe_strcopy( ptr, "realself" );
2758	}
2759	ptr = acl_safe_strcopy( ptr, accessmask2str( b->a_access_mask, maskbuf, 0 ));
2760	if ( !maskbuf[0] ) ptr--;
2761
2762	if( b->a_type == ACL_BREAK ) {
2763		ptr = acl_safe_strcopy( ptr, " break" );
2764
2765	} else if( b->a_type == ACL_CONTINUE ) {
2766		ptr = acl_safe_strcopy( ptr, " continue" );
2767
2768	} else if( b->a_type != ACL_STOP ) {
2769		ptr = acl_safe_strcopy( ptr, " unknown-control" );
2770	} else {
2771		if ( !maskbuf[0] ) ptr = acl_safe_strcopy( ptr, " stop" );
2772	}
2773	ptr = acl_safe_strcopy( ptr, "\n" );
2774
2775	return ptr;
2776}
2777
2778void
2779acl_unparse( AccessControl *a, struct berval *bv )
2780{
2781	Access	*b;
2782	char	*ptr;
2783	int	to = 0;
2784
2785	if ( BER_BVISNULL( &aclbuf ) ) {
2786		aclbuf.bv_val = ch_malloc( ACLBUF_CHUNKSIZE );
2787		aclbuf.bv_len = ACLBUF_CHUNKSIZE;
2788	}
2789
2790	bv->bv_len = 0;
2791
2792	ptr = aclbuf.bv_val;
2793
2794	ptr = acl_safe_strcopy( ptr, "to" );
2795	if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
2796		to++;
2797		ptr = acl_safe_strcopy( ptr, " dn." );
2798		if ( a->acl_dn_style == ACL_STYLE_BASE )
2799			ptr = acl_safe_strcopy( ptr, style_base );
2800		else
2801			ptr = acl_safe_strcopy( ptr, style_strings[a->acl_dn_style] );
2802		ptr = acl_safe_strcopy( ptr, "=\"" );
2803		ptr = acl_safe_strbvcopy( ptr, &a->acl_dn_pat );
2804		ptr = acl_safe_strcopy( ptr, "\"\n" );
2805	}
2806
2807	if ( a->acl_filter != NULL ) {
2808		struct berval	fbv = BER_BVNULL;
2809
2810		to++;
2811		filter2bv( a->acl_filter, &fbv );
2812		ptr = acl_safe_strcopy( ptr, " filter=\"" );
2813		ptr = acl_safe_strbvcopy( ptr, &fbv );
2814		ptr = acl_safe_strcopy( ptr, "\"\n" );
2815		ch_free( fbv.bv_val );
2816	}
2817
2818	if ( a->acl_attrs != NULL ) {
2819		int	first = 1;
2820		AttributeName *an;
2821		to++;
2822
2823		ptr = acl_safe_strcopy( ptr, " attrs=" );
2824		for ( an = a->acl_attrs; an && !BER_BVISNULL( &an->an_name ); an++ ) {
2825			if ( ! first ) ptr = acl_safe_strcopy( ptr, ",");
2826			if (an->an_oc) {
2827				ptr = acl_safe_strcopy( ptr, ( an->an_flags & SLAP_AN_OCEXCLUDE ) ? "!" : "@" );
2828				ptr = acl_safe_strbvcopy( ptr, &an->an_oc->soc_cname );
2829
2830			} else {
2831				ptr = acl_safe_strbvcopy( ptr, &an->an_name );
2832			}
2833			first = 0;
2834		}
2835		ptr = acl_safe_strcopy( ptr, "\n" );
2836	}
2837
2838	if ( !BER_BVISNULL( &a->acl_attrval ) ) {
2839		to++;
2840		ptr = acl_safe_strcopy( ptr, " val." );
2841		if ( a->acl_attrval_style == ACL_STYLE_BASE &&
2842			a->acl_attrs[0].an_desc->ad_type->sat_syntax ==
2843				slap_schema.si_syn_distinguishedName )
2844			ptr = acl_safe_strcopy( ptr, style_base );
2845		else
2846			ptr = acl_safe_strcopy( ptr, style_strings[a->acl_attrval_style] );
2847		ptr = acl_safe_strcopy( ptr, "=\"" );
2848		ptr = acl_safe_strbvcopy( ptr, &a->acl_attrval );
2849		ptr = acl_safe_strcopy( ptr, "\"\n" );
2850	}
2851
2852	if ( !to ) {
2853		ptr = acl_safe_strcopy( ptr, " *\n" );
2854	}
2855
2856	for ( b = a->acl_access; b != NULL; b = b->a_next ) {
2857		ptr = access2text( b, ptr );
2858	}
2859	*ptr = '\0';
2860	bv->bv_val = aclbuf.bv_val;
2861	bv->bv_len = ptr - bv->bv_val;
2862}
2863
2864#ifdef LDAP_DEBUG
2865static void
2866print_acl( Backend *be, AccessControl *a )
2867{
2868	struct berval bv;
2869
2870	acl_unparse( a, &bv );
2871	fprintf( stderr, "%s ACL: access %s\n",
2872		be == NULL ? "Global" : "Backend", bv.bv_val );
2873}
2874#endif /* LDAP_DEBUG */
2875