xref: /netbsd-src/external/bsd/openldap/dist/servers/slapd/aclparse.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: aclparse.c,v 1.2 2020/08/11 13:15:39 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-2020 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.2 2020/08/11 13:15:39 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 
46 static const char style_base[] = "base";
47 const 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
66 static struct berval aclbuf;
67 
68 static void		split(char *line, int splitchar, char **left, char **right);
69 static void		access_append(Access **l, Access *a);
70 static void		access_free( Access *a );
71 static int		acl_usage(void);
72 
73 static void		acl_regex_normalized_dn(const char *src, struct berval *pat);
74 
75 #ifdef LDAP_DEBUG
76 static void		print_acl(Backend *be, AccessControl *a);
77 #endif
78 
79 static int		check_scope( BackendDB *be, AccessControl *a );
80 
81 #ifdef SLAP_DYNACL
82 static int
83 slap_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 
127 static void
128 regtest(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 
200 static int
201 check_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 
254 regex_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 
328 int
329 parse_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 
2007 fail:
2008 	if ( b ) access_free( b );
2009 	if ( a ) acl_free( a );
2010 	return acl_usage();
2011 }
2012 
2013 char *
2014 accessmask2str( 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 
2140 slap_mask_t
2141 str2accessmask( 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 
2239 static int
2240 acl_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  */
2293 static void
2294 acl_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 
2334 static void
2335 split(
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 
2347 static void
2348 access_append( Access **l, Access *a )
2349 {
2350 	for ( ; *l != NULL; l = &(*l)->a_next ) {
2351 		;	/* Empty */
2352 	}
2353 
2354 	*l = a;
2355 }
2356 
2357 void
2358 acl_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 
2370 static void
2371 access_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 
2416 void
2417 acl_free( AccessControl *a )
2418 {
2419 	Access *n;
2420 	AttributeName *an;
2421 
2422 	if ( a->acl_filter ) {
2423 		filter_free( a->acl_filter );
2424 	}
2425 	if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
2426 		if ( a->acl_dn_style == ACL_STYLE_REGEX ) {
2427 			regfree( &a->acl_dn_re );
2428 		}
2429 		free ( a->acl_dn_pat.bv_val );
2430 	}
2431 	if ( a->acl_attrs ) {
2432 		for ( an = a->acl_attrs; !BER_BVISNULL( &an->an_name ); an++ ) {
2433 			free( an->an_name.bv_val );
2434 		}
2435 		free( a->acl_attrs );
2436 
2437 		if ( a->acl_attrval_style == ACL_STYLE_REGEX ) {
2438 			regfree( &a->acl_attrval_re );
2439 		}
2440 
2441 		if ( !BER_BVISNULL( &a->acl_attrval ) ) {
2442 			ber_memfree( a->acl_attrval.bv_val );
2443 		}
2444 	}
2445 	for ( ; a->acl_access; a->acl_access = n ) {
2446 		n = a->acl_access->a_next;
2447 		access_free( a->acl_access );
2448 	}
2449 	free( a );
2450 }
2451 
2452 void
2453 acl_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 
2468 char *
2469 access2str( 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 
2506 slap_access_t
2507 str2access( 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 
2543 static char *
2544 safe_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 
2559 static char *
2560 safe_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 
2567 static char *
2568 safe_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 
2577 static char *
2578 dnaccess2text( 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 
2627 static char *
2628 access2text( 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 
2778 void
2779 acl_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
2865 static void
2866 print_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