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