/* aclparse.c - routines to parse and check acl's */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software .
*
* Copyright 1998-2024 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 "portable.h"
#include
#include
#include
#include
#include
#include
#include "slap.h"
#include "lber_pvt.h"
#include "lutil.h"
#include "slap-config.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(
struct config_args_s *c,
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 ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"dynacl \"%s\" already specified",
name );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
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 )( c, 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 int
regtest(struct config_args_s *c, 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) ) {
snprintf( c->cr_msg, sizeof( c->cr_msg),
"regular expression too large \"%s\"", pat);
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
(void)acl_usage();
return -1;
}
if ( (e = regcomp(&re, buf, REG_EXTENDED|REG_ICASE)) ) {
char error[ SLAP_TEXT_BUFLEN ];
regerror(e, &re, error, sizeof(error));
snprintf( c->cr_msg, sizeof ( c->cr_msg ),
"regular expression \"%s\" bad because of %s", pat, error);
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
acl_usage();
regfree(&re);
return -1;
}
regfree(&re);
return 0;
}
/*
* 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(
struct config_args_s *c,
int pos )
{
int i;
char *left, *right, *style;
struct berval bv;
AccessControl *a = NULL;
Access *b = NULL;
int rc;
const char *text;
Backend *be = c->be;
const char *fname = c->fname;
int lineno = c->lineno;
int argc = c->argc;
char **argv = c->argv;
for ( i = 1; i < argc; i++ ) {
/* to clause - select which entries are protected */
if ( strcasecmp( argv[i], "to" ) == 0 ) {
if ( a != NULL ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"only one to clause allowed in access line" );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
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 )
{
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"dn pattern already specified in to clause." );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
goto fail;
}
ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
continue;
}
split( argv[i], '=', &left, &right );
split( left, '.', &left, &style );
if ( right == NULL ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"missing \"=\" in \"%s\" in to clause", left );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
goto fail;
}
if ( strcasecmp( left, "dn" ) == 0 ) {
if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
a->acl_dn_style != ACL_STYLE_REGEX )
{
snprintf( c->cr_msg, sizeof( c->cr_msg),
"dn pattern already specified in to clause" );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
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 {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"unknown dn style \"%s\" in to clause", style );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
goto fail;
}
continue;
}
if ( strcasecmp( left, "filter" ) == 0 ) {
if ( (a->acl_filter = str2filter( right )) == NULL ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"bad filter \"%s\" in to clause", right );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
goto fail;
}
} else if ( strcasecmp( left, "attr" ) == 0 /* TOLERATED */
|| strcasecmp( left, "attrs" ) == 0 ) /* DOCUMENTED */
{
if ( strcasecmp( left, "attr" ) == 0 ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"\"attr\" is deprecated (and undocumented); "
"use \"attrs\" instead");
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
}
a->acl_attrs = str2anlist( a->acl_attrs,
right, "," );
if ( a->acl_attrs == NULL ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"unknown attr \"%s\" in to clause", right );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
goto fail;
}
} else if ( strncasecmp( left, "val", 3 ) == 0 ) {
struct berval bv;
char *mr;
if ( !BER_BVISEMPTY( &a->acl_attrval ) ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"attr val already specified in to clause" );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
goto fail;
}
if ( a->acl_attrs == NULL || !BER_BVISEMPTY( &a->acl_attrs[1].an_name ) )
{
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"attr val requires a single attribute");
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
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 ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"invalid matching rule \"%s\"", mr);
Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
goto fail;
}
if( !mr_usable_with_at( a->acl_attrval_mr, a->acl_attrs[ 0 ].an_desc->ad_type ) )
{
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"matching rule \"%s\" use " "with attr \"%s\" not appropriate",
mr,
a->acl_attrs[0].an_name.bv_val );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c-> log, c->cr_msg );
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];
regerror( e, &a->acl_attrval_re, err, sizeof( err ) );
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"regular expression \"%s\" bad because of %s",
right, err );
Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
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 {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"unknown val.