/* aclparse.c - routines to parse and check acl's */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 1998-2022 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.