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