/*	$NetBSD: aclparse.c,v 1.1.1.6.6.1 2019/08/10 06:17:16 martin Exp $	*/
/* aclparse.c - routines to parse and check acl's */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software .
 *
 * Copyright 1998-2019 The OpenLDAP Foundation.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * .
 */
/* Portions Copyright (c) 1995 Regents of the University of Michigan.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 */
#include 
__RCSID("$NetBSD: aclparse.c,v 1.1.1.6.6.1 2019/08/10 06:17:16 martin Exp $");
#include "portable.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include "slap.h"
#include "lber_pvt.h"
#include "lutil.h"
static const char style_base[] = "base";
const char *style_strings[] = {
	"regex",
	"expand",
	"exact",
	"one",
	"subtree",
	"children",
	"level",
	"attrof",
	"anonymous",
	"users",
	"self",
	"ip",
	"ipv6",
	"path",
	NULL
};
#define ACLBUF_CHUNKSIZE	8192
static struct berval aclbuf;
static void		split(char *line, int splitchar, char **left, char **right);
static void		access_append(Access **l, Access *a);
static void		access_free( Access *a );
static int		acl_usage(void);
static void		acl_regex_normalized_dn(const char *src, struct berval *pat);
#ifdef LDAP_DEBUG
static void		print_acl(Backend *be, AccessControl *a);
#endif
static int		check_scope( BackendDB *be, AccessControl *a );
#ifdef SLAP_DYNACL
static int
slap_dynacl_config(
	const char *fname,
	int lineno,
	Access *b,
	const char *name,
	const char *opts,
	slap_style_t sty,
	const char *right )
{
	slap_dynacl_t	*da, *tmp;
	int		rc = 0;
	for ( da = b->a_dynacl; da; da = da->da_next ) {
		if ( strcasecmp( da->da_name, name ) == 0 ) {
			Debug( LDAP_DEBUG_ANY,
				"%s: line %d: dynacl \"%s\" already specified.\n",
				fname, lineno, name );
			return acl_usage();
		}
	}
	da = slap_dynacl_get( name );
	if ( da == NULL ) {
		return -1;
	}
	tmp = ch_malloc( sizeof( slap_dynacl_t ) );
	*tmp = *da;
	if ( tmp->da_parse ) {
		rc = ( *tmp->da_parse )( fname, lineno, opts, sty, right, &tmp->da_private );
		if ( rc ) {
			ch_free( tmp );
			return rc;
		}
	}
	tmp->da_next = b->a_dynacl;
	b->a_dynacl = tmp;
	return 0;
}
#endif /* SLAP_DYNACL */
static void
regtest(const char *fname, int lineno, char *pat) {
	int e;
	regex_t re;
	char		buf[ SLAP_TEXT_BUFLEN ];
	unsigned	size;
	char *sp;
	char *dp;
	int  flag;
	sp = pat;
	dp = buf;
	size = 0;
	buf[0] = '\0';
	for (size = 0, flag = 0; (size < sizeof(buf)) && *sp; sp++) {
		if (flag) {
			if (*sp == '$'|| (*sp >= '0' && *sp <= '9')) {
				*dp++ = *sp;
				size++;
			}
			flag = 0;
		} else {
			if (*sp == '$') {
				flag = 1;
			} else {
				*dp++ = *sp;
				size++;
			}
		}
	}
	*dp = '\0';
	if ( size >= (sizeof(buf) - 1) ) {
		Debug( LDAP_DEBUG_ANY,
			"%s: line %d: regular expression \"%s\" too large\n",
			fname, lineno, pat );
		(void)acl_usage();
		exit( EXIT_FAILURE );
	}
	if ((e = regcomp(&re, buf, REG_EXTENDED|REG_ICASE))) {
		char error[ SLAP_TEXT_BUFLEN ];
		regerror(e, &re, error, sizeof(error));
		snprintf( buf, sizeof( buf ),
			"regular expression \"%s\" bad because of %s",
			pat, error );
		Debug( LDAP_DEBUG_ANY,
			"%s: line %d: %s\n",
			fname, lineno, buf );
		acl_usage();
		exit( EXIT_FAILURE );
	}
	regfree(&re);
}
/*
 * Experimental
 *
 * Check if the pattern of an ACL, if any, matches the scope
 * of the backend it is defined within.
 */
#define	ACL_SCOPE_UNKNOWN	(-2)
#define	ACL_SCOPE_ERR		(-1)
#define	ACL_SCOPE_OK		(0)
#define	ACL_SCOPE_PARTIAL	(1)
#define	ACL_SCOPE_WARN		(2)
static int
check_scope( BackendDB *be, AccessControl *a )
{
	ber_len_t	patlen;
	struct berval	dn;
	dn = be->be_nsuffix[0];
	if ( BER_BVISEMPTY( &dn ) ) {
		return ACL_SCOPE_OK;
	}
	if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
			a->acl_dn_style != ACL_STYLE_REGEX )
	{
		slap_style_t	style = a->acl_dn_style;
		if ( style == ACL_STYLE_REGEX ) {
			char		dnbuf[SLAP_LDAPDN_MAXLEN + 2];
			char		rebuf[SLAP_LDAPDN_MAXLEN + 1];
			ber_len_t	rebuflen;
			regex_t		re;
			int		rc;
			
			/* add trailing '$' to database suffix to form
			 * a simple trial regex pattern "$" */
			AC_MEMCPY( dnbuf, be->be_nsuffix[0].bv_val,
				be->be_nsuffix[0].bv_len );
			dnbuf[be->be_nsuffix[0].bv_len] = '$';
			dnbuf[be->be_nsuffix[0].bv_len + 1] = '\0';
			if ( regcomp( &re, dnbuf, REG_EXTENDED|REG_ICASE ) ) {
				return ACL_SCOPE_WARN;
			}
			/* remove trailing ')$', if any, from original
			 * regex pattern */
			rebuflen = a->acl_dn_pat.bv_len;
			AC_MEMCPY( rebuf, a->acl_dn_pat.bv_val, rebuflen + 1 );
			if ( rebuf[rebuflen - 1] == '$' ) {
				rebuf[--rebuflen] = '\0';
			}
			while ( rebuflen > be->be_nsuffix[0].bv_len && rebuf[rebuflen - 1] == ')' ) {
				rebuf[--rebuflen] = '\0';
			}
			if ( rebuflen == be->be_nsuffix[0].bv_len ) {
				rc = ACL_SCOPE_WARN;
				goto regex_done;
			}
			/* not a clear indication of scoping error, though */
			rc = regexec( &re, rebuf, 0, NULL, 0 )
				? ACL_SCOPE_WARN : ACL_SCOPE_OK;
regex_done:;
			regfree( &re );
			return rc;
		}
		patlen = a->acl_dn_pat.bv_len;
		/* If backend suffix is longer than pattern,
		 * it is a potential mismatch (in the sense
		 * that a superior naming context could
		 * match */
		if ( dn.bv_len > patlen ) {
			/* base is blatantly wrong */
			if ( style == ACL_STYLE_BASE ) return ACL_SCOPE_ERR;
			/* a style of one can be wrong if there is
			 * more than one level between the suffix
			 * and the pattern */
			if ( style == ACL_STYLE_ONE ) {
				ber_len_t	rdnlen = 0;
				int		sep = 0;
				if ( patlen > 0 ) {
					if ( !DN_SEPARATOR( dn.bv_val[dn.bv_len - patlen - 1] )) {
						return ACL_SCOPE_ERR;
					}
					sep = 1;
				}
				rdnlen = dn_rdnlen( NULL, &dn );
				if ( rdnlen != dn.bv_len - patlen - sep )
					return ACL_SCOPE_ERR;
			}
			/* if the trailing part doesn't match,
			 * then it's an error */
			if ( strcmp( a->acl_dn_pat.bv_val,
				&dn.bv_val[dn.bv_len - patlen] ) != 0 )
			{
				return ACL_SCOPE_ERR;
			}
			return ACL_SCOPE_PARTIAL;
		}
		switch ( style ) {
		case ACL_STYLE_BASE:
		case ACL_STYLE_ONE:
		case ACL_STYLE_CHILDREN:
		case ACL_STYLE_SUBTREE:
			break;
		default:
			assert( 0 );
			break;
		}
		if ( dn.bv_len < patlen &&
			!DN_SEPARATOR( a->acl_dn_pat.bv_val[patlen - dn.bv_len - 1] ))
		{
			return ACL_SCOPE_ERR;
		}
		if ( strcmp( &a->acl_dn_pat.bv_val[patlen - dn.bv_len], dn.bv_val )
			!= 0 )
		{
			return ACL_SCOPE_ERR;
		}
		return ACL_SCOPE_OK;
	}
	return ACL_SCOPE_UNKNOWN;
}
int
parse_acl(
	Backend	*be,
	const char	*fname,
	int		lineno,
	int		argc,
	char		**argv,
	int		pos )
{
	int		i;
	char		*left, *right, *style;
	struct berval	bv;
	AccessControl	*a = NULL;
	Access	*b = NULL;
	int rc;
	const char *text;
	for ( i = 1; i < argc; i++ ) {
		/* to clause - select which entries are protected */
		if ( strcasecmp( argv[i], "to" ) == 0 ) {
			if ( a != NULL ) {
				Debug( LDAP_DEBUG_ANY, "%s: line %d: "
					"only one to clause allowed in access line\n",
				    fname, lineno, 0 );
				goto fail;
			}
			a = (AccessControl *) ch_calloc( 1, sizeof(AccessControl) );
			a->acl_attrval_style = ACL_STYLE_NONE;
			for ( ++i; i < argc; i++ ) {
				if ( strcasecmp( argv[i], "by" ) == 0 ) {
					i--;
					break;
				}
				if ( strcasecmp( argv[i], "*" ) == 0 ) {
					if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
						a->acl_dn_style != ACL_STYLE_REGEX )
					{
						Debug( LDAP_DEBUG_ANY,
							"%s: line %d: dn pattern"
							" already specified in to clause.\n",
							fname, lineno, 0 );
						goto fail;
					}
					ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
					continue;
				}
				split( argv[i], '=', &left, &right );
				split( left, '.', &left, &style );
				if ( right == NULL ) {
					Debug( LDAP_DEBUG_ANY, "%s: line %d: "
						"missing \"=\" in \"%s\" in to clause\n",
					    fname, lineno, left );
					goto fail;
				}
				if ( strcasecmp( left, "dn" ) == 0 ) {
					if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
						a->acl_dn_style != ACL_STYLE_REGEX )
					{
						Debug( LDAP_DEBUG_ANY,
							"%s: line %d: dn pattern"
							" already specified in to clause.\n",
							fname, lineno, 0 );
						goto fail;
					}
					if ( style == NULL || *style == '\0' ||
						strcasecmp( style, "baseObject" ) == 0 ||
						strcasecmp( style, "base" ) == 0 ||
						strcasecmp( style, "exact" ) == 0 )
					{
						a->acl_dn_style = ACL_STYLE_BASE;
						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
					} else if ( strcasecmp( style, "oneLevel" ) == 0 ||
						strcasecmp( style, "one" ) == 0 )
					{
						a->acl_dn_style = ACL_STYLE_ONE;
						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
					} else if ( strcasecmp( style, "subtree" ) == 0 ||
						strcasecmp( style, "sub" ) == 0 )
					{
						if( *right == '\0' ) {
							ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
						} else {
							a->acl_dn_style = ACL_STYLE_SUBTREE;
							ber_str2bv( right, 0, 1, &a->acl_dn_pat );
						}
					} else if ( strcasecmp( style, "children" ) == 0 ) {
						a->acl_dn_style = ACL_STYLE_CHILDREN;
						ber_str2bv( right, 0, 1, &a->acl_dn_pat );
					} else if ( strcasecmp( style, "regex" ) == 0 ) {
						a->acl_dn_style = ACL_STYLE_REGEX;
						if ( *right == '\0' ) {
							/* empty regex should match empty DN */
							a->acl_dn_style = ACL_STYLE_BASE;
							ber_str2bv( right, 0, 1, &a->acl_dn_pat );
						} else if ( strcmp(right, "*") == 0 
							|| strcmp(right, ".*") == 0 
							|| strcmp(right, ".*$") == 0 
							|| strcmp(right, "^.*") == 0 
							|| strcmp(right, "^.*$") == 0
							|| strcmp(right, ".*$$") == 0 
							|| strcmp(right, "^.*$$") == 0 )
						{
							ber_str2bv( "*", STRLENOF("*"), 1, &a->acl_dn_pat );
						} else {
							acl_regex_normalized_dn( right, &a->acl_dn_pat );
						}
					} else {
						Debug( LDAP_DEBUG_ANY, "%s: line %d: "
							"unknown dn style \"%s\" in to clause\n",
						    fname, lineno, style );
						goto fail;
					}
					continue;
				}
				if ( strcasecmp( left, "filter" ) == 0 ) {
					if ( (a->acl_filter = str2filter( right )) == NULL ) {
						Debug( LDAP_DEBUG_ANY,
				"%s: line %d: bad filter \"%s\" in to clause\n",
						    fname, lineno, right );
						goto fail;
					}
				} else if ( strcasecmp( left, "attr" ) == 0		/* TOLERATED */
						|| strcasecmp( left, "attrs" ) == 0 )	/* DOCUMENTED */
				{
					if ( strcasecmp( left, "attr" ) == 0 ) {
						Debug( LDAP_DEBUG_ANY,
							"%s: line %d: \"attr\" "
							"is deprecated (and undocumented); "
							"use \"attrs\" instead.\n",
							fname, lineno, 0 );
					}
					a->acl_attrs = str2anlist( a->acl_attrs,
						right, "," );
					if ( a->acl_attrs == NULL ) {
						Debug( LDAP_DEBUG_ANY,
				"%s: line %d: unknown attr \"%s\" in to clause\n",
						    fname, lineno, right );
						goto fail;
					}
				} else if ( strncasecmp( left, "val", 3 ) == 0 ) {
					struct berval	bv;
					char		*mr;
					
					if ( !BER_BVISEMPTY( &a->acl_attrval ) ) {
						Debug( LDAP_DEBUG_ANY,
				"%s: line %d: attr val already specified in to clause.\n",
							fname, lineno, 0 );
						goto fail;
					}
					if ( a->acl_attrs == NULL || !BER_BVISEMPTY( &a->acl_attrs[1].an_name ) )
					{
						Debug( LDAP_DEBUG_ANY,
				"%s: line %d: attr val requires a single attribute.\n",
							fname, lineno, 0 );
						goto fail;
					}
					ber_str2bv( right, 0, 0, &bv );
					a->acl_attrval_style = ACL_STYLE_BASE;
					mr = strchr( left, '/' );
					if ( mr != NULL ) {
						mr[ 0 ] = '\0';
						mr++;
						a->acl_attrval_mr = mr_find( mr );
						if ( a->acl_attrval_mr == NULL ) {
							Debug( LDAP_DEBUG_ANY, "%s: line %d: "
								"invalid matching rule \"%s\".\n",
								fname, lineno, mr );
							goto fail;
						}
						if( !mr_usable_with_at( a->acl_attrval_mr, a->acl_attrs[ 0 ].an_desc->ad_type ) )
						{
							char	buf[ SLAP_TEXT_BUFLEN ];
							snprintf( buf, sizeof( buf ),
								"matching rule \"%s\" use "
								"with attr \"%s\" not appropriate.",
								mr, a->acl_attrs[ 0 ].an_name.bv_val );
								
							Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
								fname, lineno, buf );
							goto fail;
						}
					}
					
					if ( style != NULL ) {
						if ( strcasecmp( style, "regex" ) == 0 ) {
							int e = regcomp( &a->acl_attrval_re, bv.bv_val,
								REG_EXTENDED | REG_ICASE );
							if ( e ) {
								char	err[SLAP_TEXT_BUFLEN],
									buf[ SLAP_TEXT_BUFLEN ];
								regerror( e, &a->acl_attrval_re, err, sizeof( err ) );
								snprintf( buf, sizeof( buf ),
									"regular expression \"%s\" bad because of %s",
									right, err );
								Debug( LDAP_DEBUG_ANY, "%s: line %d: %s\n",
									fname, lineno, buf );
								goto fail;
							}
							a->acl_attrval_style = ACL_STYLE_REGEX;
						} else {
							/* FIXME: if the attribute has DN syntax, we might
							 * allow one, subtree and children styles as well */
							if ( !strcasecmp( style, "base" ) ||
								!strcasecmp( style, "exact" ) ) {
								a->acl_attrval_style = ACL_STYLE_BASE;
							} else if ( a->acl_attrs[0].an_desc->ad_type->
								sat_syntax == slap_schema.si_syn_distinguishedName )
							{
								if ( !strcasecmp( style, "baseObject" ) ||
									!strcasecmp( style, "base" ) )
								{
									a->acl_attrval_style = ACL_STYLE_BASE;
								} else if ( !strcasecmp( style, "onelevel" ) ||
									!strcasecmp( style, "one" ) )
								{
									a->acl_attrval_style = ACL_STYLE_ONE;
								} else if ( !strcasecmp( style, "subtree" ) ||
									!strcasecmp( style, "sub" ) )
								{
									a->acl_attrval_style = ACL_STYLE_SUBTREE;
								} else if ( !strcasecmp( style, "children" ) ) {
									a->acl_attrval_style = ACL_STYLE_CHILDREN;
								} else {
									char	buf[ SLAP_TEXT_BUFLEN ];
									snprintf( buf, sizeof( buf ),
										"unknown val.