X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=servers%2Fslapd%2Facl.c;h=fd40a97687db31fbd97d856b4ca4c7d642607ac3;hb=ae63be3894c799771d0ccabec1e376c7478cd263;hp=a9fcc6a1209589b002b85e2698d6c7bed3e5c75d;hpb=7c52060c6723c244806d6c112149d82d4f0bb5e5;p=openldap diff --git a/servers/slapd/acl.c b/servers/slapd/acl.c index a9fcc6a120..fd40a97687 100644 --- a/servers/slapd/acl.c +++ b/servers/slapd/acl.c @@ -1,4 +1,9 @@ /* acl.c - routines to parse and check acl's */ +/* $OpenLDAP$ */ +/* + * Copyright 1998-2000 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ #include "portable.h" @@ -10,31 +15,55 @@ #include "slap.h" -extern Attribute *attr_find(); -extern struct acl *global_acl; -extern int global_default_access; -extern char *access2str(); -extern char *dn_normalize_case(); - -int acl_access_allowed(); -int access_allowed(); -struct acl *acl_get_applicable(); - -static int regex_matches(); - -static string_expand(char *newbuf, int bufsiz, char *pattern, +static AccessControl * acl_get( + AccessControl *ac, int *count, + Backend *be, Operation *op, + Entry *e, + AttributeDescription *desc, + int nmatches, regmatch_t *matches ); + +static slap_control_t acl_mask( + AccessControl *ac, slap_access_mask_t *mask, + Backend *be, Connection *conn, Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + regmatch_t *matches ); + +#ifdef SLAPD_ACI_ENABLED +static int aci_mask( + Backend *be, + Connection *conn, + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + struct berval *aci, + regmatch_t *matches, + slap_access_t *grant, + slap_access_t *deny ); +#endif + +static int regex_matches( + char *pat, char *str, char *buf, regmatch_t *matches); +static void string_expand( + char *newbuf, int bufsiz, char *pattern, char *match, regmatch_t *matches); /* - * access_allowed - check whether dn is allowed the requested access + * access_allowed - check whether op->o_ndn is allowed the requested access * to entry e, attribute attr, value val. if val is null, access to - * the whole attribute is assumed (all values). this routine finds - * the applicable acl and calls acl_access_allowed() to make the - * decision. + * the whole attribute is assumed (all values). * - * returns 0 access NOT allowed - * 1 access allowed + * This routine loops through all access controls and calls + * acl_mask() on each applicable access control. + * The loop exits when a definitive answer is reached or + * or no more controls remain. + * + * returns: + * 0 access denied + * 1 access granted */ int @@ -43,169 +72,259 @@ access_allowed( Connection *conn, Operation *op, Entry *e, - char *attr, + AttributeDescription *desc, struct berval *val, - char *dn, - int access -) + slap_access_t access ) { - int rc; - struct acl *a; - char *edn; + int count; + AccessControl *a; +#ifdef LDAP_DEBUG + char accessmaskbuf[ACCESSMASK_MAXLEN]; +#endif + slap_access_mask_t mask; + slap_control_t control; + + const char *attr = desc ? desc->ad_cname->bv_val : NULL; regmatch_t matches[MAXREMATCHES]; - int i; - int n; - if ( be == NULL ) { - return( 0 ); - } + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: %s access to \"%s\" \"%s\" requested\n", + access2str( access ), + e->e_dn, attr ); - edn = dn_normalize_case( strdup( e->e_dn ) ); - Debug( LDAP_DEBUG_ACL, "\n=> access_allowed: entry (%s) attr (%s)\n", - e->e_dn, attr, 0 ); + assert( be != NULL ); + assert( e != NULL ); + assert( attr != NULL ); + assert( access > ACL_NONE ); - /* the lastmod attributes are ignored by ACL checking */ - if ( strcasecmp( attr, "modifiersname" ) == 0 || - strcasecmp( attr, "modifytimestamp" ) == 0 || - strcasecmp( attr, "creatorsname" ) == 0 || - strcasecmp( attr, "createtimestamp" ) == 0 ) + /* grant database root access */ + if ( be != NULL && be_isroot( be, op->o_ndn ) ) { + Debug( LDAP_DEBUG_ACL, + "<= root access granted\n", + 0, 0, 0 ); + return 1; + } + + /* + * no-user-modification operational attributes are ignored + * by ACL_WRITE checking as any found here are not provided + * by the user + */ + if ( access >= ACL_WRITE && is_at_no_user_mod( desc->ad_type ) ) { - Debug( LDAP_DEBUG_ACL, "LASTMOD attribute: %s access allowed\n", + Debug( LDAP_DEBUG_ACL, "NoUserMod Operational attribute:" + " %s access granted\n", attr, 0, 0 ); - free( edn ); - return(1); + return 1; + } + + /* use backend default access if no backend acls */ + if( be != NULL && be->be_acl == NULL ) { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: backend default %s access %s to \"%s\"\n", + access2str( access ), + be->be_dfltaccess >= access ? "granted" : "denied", op->o_dn ); + + return be->be_dfltaccess >= access; + +#ifdef notdef + /* be is always non-NULL */ + /* use global default access if no global acls */ + } else if ( be == NULL && global_acl == NULL ) { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: global default %s access %s to \"%s\"\n", + access2str( access ), + global_default_access >= access ? "granted" : "denied", op->o_dn ); + + return global_default_access >= access; +#endif } - memset(matches, 0, sizeof(matches)); + ACL_INIT(mask); + memset(matches, '\0', sizeof(matches)); + + control = ACL_BREAK; + a = NULL; + count = 0; - a = acl_get_applicable( be, op, e, attr, edn, MAXREMATCHES, matches ); + while( a = acl_get( a, &count, be, op, e, desc, MAXREMATCHES, matches ) ) + { + int i; - if (a) { for (i = 0; i < MAXREMATCHES && matches[i].rm_so > 0; i++) { - Debug( LDAP_DEBUG_ARGS, "=> match[%d]: %d %d ", - i, matches[i].rm_so, matches[i].rm_eo ); + Debug( LDAP_DEBUG_ACL, "=> match[%d]: %d %d ", i, + (int)matches[i].rm_so, (int)matches[i].rm_eo ); if( matches[i].rm_so <= matches[0].rm_eo ) { + int n; for ( n = matches[i].rm_so; n < matches[i].rm_eo; n++) { - Debug( LDAP_DEBUG_ARGS, "%c", edn[n], 0, 0 ); + Debug( LDAP_DEBUG_ACL, "%c", e->e_ndn[n], 0, 0 ); } } Debug( LDAP_DEBUG_ARGS, "\n", 0, 0, 0 ); } + + control = acl_mask( a, &mask, be, conn, op, + e, desc, val, matches ); + + if ( control != ACL_BREAK ) { + break; + } + + memset(matches, '\0', sizeof(matches)); } - rc = acl_access_allowed( a, be, conn, e, val, op, access, edn, matches ); - free( edn ); + if ( ACL_IS_INVALID( mask ) ) { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: \"%s\" (%s) invalid!\n", + e->e_dn, attr, 0 ); + ACL_INIT( mask ); + + } else if ( control == ACL_BREAK ) { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: no more rules\n", 0, 0, 0); + ACL_INIT( mask ); + } - Debug( LDAP_DEBUG_ACL, "\n=> access_allowed: exit (%s) attr (%s)\n", - e->e_dn, attr, 0); + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: %s access %s by %s\n", + access2str( access ), + ACL_GRANT(mask, access) ? "granted" : "denied", + accessmask2str( mask, accessmaskbuf ) ); - return( rc ); + return ACL_GRANT(mask, access); } /* - * acl_get_applicable - return the acl applicable to entry e, attribute + * acl_get - return the acl applicable to entry e, attribute * attr. the acl returned is suitable for use in subsequent calls to * acl_access_allowed(). */ -struct acl * -acl_get_applicable( +static AccessControl * +acl_get( + AccessControl *a, + int *count, Backend *be, - Operation *op, + Operation *op, Entry *e, - char *attr, - char *edn, + AttributeDescription *desc, int nmatch, - regmatch_t *matches -) + regmatch_t *matches ) { - int i, j; - struct acl *a; + const char *attr; + int dnlen, patlen; - Debug( LDAP_DEBUG_ACL, "\n=> acl_get: entry (%s) attr (%s)\n", - e->e_dn, attr, 0 ); + assert( e != NULL ); + assert( count != NULL ); - if ( be_isroot( be, op->o_dn ) ) { - Debug( LDAP_DEBUG_ACL, - "<= acl_get: no acl applicable to database root\n", 0, 0, - 0 ); - return( NULL ); - } + attr = desc ? desc->ad_cname->bv_val : NULL; - Debug( LDAP_DEBUG_ARGS, "=> acl_get: edn %s\n", edn, 0, 0 ); + if( a == NULL ) { + if( be == NULL ) { + a = global_acl; + } else { + a = be->be_acl; + } - /* check for a backend-specific acl that matches the entry */ - for ( i = 1, a = be->be_acl; a != NULL; a = a->acl_next, i++ ) { - if (a->acl_dnpat != NULL) { - Debug( LDAP_DEBUG_TRACE, "=> dnpat: [%d] %s nsub: %d\n", - i, a->acl_dnpat, a->acl_dnre.re_nsub); + assert( a != NULL ); - if (regexec(&a->acl_dnre, edn, nmatch, matches, 0)) - continue; - else - Debug( LDAP_DEBUG_TRACE, "=> acl_get:[%d] backend ACL match\n", - i, 0, 0); - } + } else { + a = a->acl_next; + } - if ( a->acl_filter != NULL ) { - if ( test_filter( NULL, NULL, NULL, e, a->acl_filter ) != 0 ) { - continue; - } - } + dnlen = strlen(e->e_ndn); - Debug( LDAP_DEBUG_ARGS, "=> acl_get: [%d] check attr %s\n", i, attr, 0); + for ( ; a != NULL; a = a->acl_next ) { + (*count) ++; - if ( attr == NULL || a->acl_attrs == NULL || - charray_inlist( a->acl_attrs, attr ) ) - { - Debug( LDAP_DEBUG_ACL, "<= acl_get: [%d] backend acl %s attr: %s\n", - i, e->e_dn, attr ); - return( a ); - } - matches[0].rm_so = matches[0].rm_eo = -1; - } + if (a->acl_dn_pat != NULL) { + if ( a->acl_dn_style == ACL_STYLE_REGEX ) { + Debug( LDAP_DEBUG_ACL, "=> dnpat: [%d] %s nsub: %d\n", + *count, a->acl_dn_pat, (int) a->acl_dn_re.re_nsub ); - /* check for a global acl that matches the entry */ - for ( i = 1, a = global_acl; a != NULL; a = a->acl_next, i++ ) { - if (a->acl_dnpat != NULL) { - Debug( LDAP_DEBUG_TRACE, "=> dnpat: [%d] %s nsub: %d\n", - i, a->acl_dnpat, a->acl_dnre.re_nsub); + if (regexec(&a->acl_dn_re, e->e_ndn, nmatch, matches, 0)) + continue; - if (regexec(&a->acl_dnre, edn, nmatch, matches, 0)) { - continue; } else { - Debug( LDAP_DEBUG_TRACE, "=> acl_get: [%d] global ACL match\n", - i, 0, 0); + Debug( LDAP_DEBUG_ACL, "=> dn: [%d] %s\n", + *count, a->acl_dn_pat, 0 ); + + patlen = strlen( a->acl_dn_pat ); + if ( dnlen < patlen ) + continue; + + if ( a->acl_dn_style == ACL_STYLE_BASE ) { + /* base dn -- entire object DN must match */ + if ( dnlen != patlen ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_ONE ) { + char *rdn; + int rdnlen = -1; + + if ( dnlen <= patlen ) + continue; + + if ( e->e_ndn[dnlen - patlen - 1] != ',' ) + continue; + + rdn = dn_rdn( NULL, e->e_ndn ); + if ( rdn != NULL ) { + rdnlen = strlen( rdn ); + ch_free( rdn ); + } + if ( rdnlen != dnlen - patlen - 1 ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_SUBTREE ) { + if ( dnlen > patlen && e->e_ndn[dnlen - patlen - 1] != ',' ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_CHILDREN ) { + if ( dnlen <= patlen ) + continue; + if ( e->e_ndn[dnlen - patlen - 1] != ',' ) + continue; + } + + if ( strcmp( a->acl_dn_pat, e->e_ndn + dnlen - patlen ) != 0 ) + continue; } + + Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] matched\n", + *count, 0, 0 ); } if ( a->acl_filter != NULL ) { - if ( test_filter( NULL, NULL, NULL, e, a->acl_filter ) != 0 ) { + ber_int_t rc = test_filter( NULL, NULL, NULL, e, a->acl_filter ); + if ( rc != LDAP_COMPARE_TRUE ) { continue; } } - Debug( LDAP_DEBUG_ARGS, "=> acl_get: [%d] check attr\n", i, 0, 0); + Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] check attr %s\n", + *count, attr, 0); if ( attr == NULL || a->acl_attrs == NULL || - charray_inlist( a->acl_attrs, attr ) ) + ad_inlist( desc, a->acl_attrs ) ) { - Debug( LDAP_DEBUG_ACL, "<= acl_get: [%d] global acl %s attr: %s\n", - i, e->e_dn, attr ); - return( a ); + Debug( LDAP_DEBUG_ACL, + "<= acl_get: [%d] acl %s attr: %s\n", + *count, e->e_dn, attr ); + return a; } - matches[0].rm_so = matches[0].rm_eo = -1; } - Debug( LDAP_DEBUG_ACL, "<= acl_get: no match\n", 0, 0, 0 ); + Debug( LDAP_DEBUG_ACL, "<= acl_get: done.\n", 0, 0, 0 ); return( NULL ); } + /* - * acl_access_allowed - check whether the given acl allows dn the + * acl_mask - modifies mask based upon the given acl and the * requested access to entry e, attribute attr, value val. if val * is null, access to the whole attribute is assumed (all values). * @@ -213,266 +332,945 @@ acl_get_applicable( * 1 access allowed */ -int -acl_access_allowed( - struct acl *a, +static slap_control_t +acl_mask( + AccessControl *a, + slap_access_mask_t *mask, Backend *be, - Connection *conn, + Connection *conn, + Operation *op, Entry *e, + AttributeDescription *desc, struct berval *val, - Operation *op, - int access, - char *edn, regmatch_t *matches ) { - int i; - char *odn; - struct access *b; - Attribute *at; - struct berval bv; - int default_access; + int i, odnlen, patlen; + Access *b; +#ifdef LDAP_DEBUG + char accessmaskbuf[ACCESSMASK_MAXLEN]; +#endif + const char *attr = desc ? desc->ad_cname->bv_val : NULL; - Debug( LDAP_DEBUG_ACL, - "\n=> acl_access_allowed: %s access to entry \"%s\"\n", - access2str( access ), e->e_dn, 0 ); + assert( a != NULL ); + assert( mask != NULL ); Debug( LDAP_DEBUG_ACL, - "\n=> acl_access_allowed: %s access to value \"%s\" by \"%s\"\n", - access2str( access ), - val ? val->bv_val : "any", - op->o_dn ? op->o_dn : "" ); + "=> acl_mask: access to entry \"%s\", attr \"%s\" requested\n", + e->e_dn, attr, 0 ); - if ( be_isroot( be, op->o_dn ) ) { - Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: granted to database root\n", - 0, 0, 0 ); - return( 1 ); - } + Debug( LDAP_DEBUG_ACL, + "=> acl_mask: to value \"%s\" by \"%s\", (%s) \n", + val ? val->bv_val : "*", + op->o_ndn ? op->o_ndn : "", + accessmask2str( *mask, accessmaskbuf ) ); - default_access = be->be_dfltaccess ? be->be_dfltaccess : global_default_access; + for ( i = 1, b = a->acl_access; b != NULL; b = b->a_next, i++ ) { + slap_access_mask_t oldmask, modmask; - if ( a == NULL ) { - Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: %s by default (no matching to)\n", - default_access >= access ? "granted" : "denied", 0, 0 ); - return( default_access >= access ); - } + ACL_INVALIDATE( modmask ); - odn = NULL; - if ( op->o_dn != NULL ) { - odn = dn_normalize_case( strdup( op->o_dn ) ); - bv.bv_val = odn; - bv.bv_len = strlen( odn ); - } - for ( i = 1, b = a->acl_access; b != NULL; b = b->a_next, i++ ) { - if ( b->a_dnpat != NULL ) { - Debug( LDAP_DEBUG_TRACE, "<= check a_dnpat: %s\n", - b->a_dnpat, 0, 0); + /* AND clauses */ + if ( b->a_dn_pat != NULL ) { + Debug( LDAP_DEBUG_ACL, "<= check a_dn_pat: %s\n", + b->a_dn_pat, 0, 0); /* * if access applies to the entry itself, and the * user is bound as somebody in the same namespace as * the entry, OR the given dn matches the dn pattern */ - if ( strcasecmp( b->a_dnpat, "self" ) == 0 && - op->o_dn != NULL && *(op->o_dn) && e->e_dn != NULL ) - { - if ( strcasecmp( edn, op->o_dn ) == 0 ) { - Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= - access ? "granted" : "denied", 0 ); - - if ( odn ) free( odn ); - return( (b->a_access & ~ACL_SELF) >= access ); + if ( strcmp( b->a_dn_pat, "anonymous" ) == 0 ) { + if (op->o_ndn != NULL && op->o_ndn[0] != '\0' ) { + continue; + } + + } else if ( strcmp( b->a_dn_pat, "users" ) == 0 ) { + if (op->o_ndn == NULL || op->o_ndn[0] == '\0' ) { + continue; + } + + } else if ( strcmp( b->a_dn_pat, "self" ) == 0 ) { + if( op->o_ndn == NULL || op->o_ndn[0] == '\0' ) { + continue; + } + + if ( e->e_dn == NULL || strcmp( e->e_ndn, op->o_ndn ) != 0 ) { + continue; } + + } else if ( b->a_dn_style == ACL_STYLE_REGEX ) { + if ( strcmp( b->a_dn_pat, "*" ) != 0 ) { + int ret = regex_matches( b->a_dn_pat, + op->o_ndn, e->e_ndn, matches ); + + if( ret == 0 ) { + continue; + } + } + } else { - if ( regex_matches( b->a_dnpat, odn, edn, matches ) ) { - Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? - "granted" : "denied", 0 ); - - if ( odn ) free( odn ); - return( (b->a_access & ~ACL_SELF) >= access ); + if ( e->e_dn == NULL ) + continue; + + patlen = strlen( b->a_dn_pat ); + odnlen = strlen( op->o_ndn ); + if ( odnlen < patlen ) + continue; + + if ( b->a_dn_style == ACL_STYLE_BASE ) { + /* base dn -- entire object DN must match */ + if ( odnlen != patlen ) + continue; + + } else if ( b->a_dn_style == ACL_STYLE_ONE ) { + char *rdn; + int rdnlen = -1; + + if ( odnlen <= patlen ) + continue; + + if ( op->o_ndn[odnlen - patlen - 1] != ',' ) + continue; + + rdn = dn_rdn( NULL, op->o_ndn ); + if ( rdn != NULL ) { + rdnlen = strlen( rdn ); + ch_free( rdn ); + } + if ( rdnlen != odnlen - patlen - 1 ) + continue; + + } else if ( b->a_dn_style == ACL_STYLE_SUBTREE ) { + if ( odnlen > patlen && op->o_ndn[odnlen - patlen - 1] != ',' ) + continue; + + } else if ( b->a_dn_style == ACL_STYLE_CHILDREN ) { + if ( odnlen <= patlen ) + continue; + if ( op->o_ndn[odnlen - patlen - 1] != ',' ) + continue; + } + + if ( strcmp( b->a_dn_pat, op->o_ndn + odnlen - patlen ) != 0 ) + continue; + + } + } + + if ( b->a_sockurl_pat != NULL ) { + Debug( LDAP_DEBUG_ACL, "<= check a_sockurl_pat: %s\n", + b->a_sockurl_pat, 0, 0 ); + + if ( strcmp( b->a_sockurl_pat, "*" ) != 0) { + if ( b->a_sockurl_style == ACL_STYLE_REGEX) { + if (!regex_matches( b->a_sockurl_pat, conn->c_listener_url, + e->e_ndn, matches ) ) + { + continue; + } + } else { + if ( strcasecmp( b->a_sockurl_pat, conn->c_listener_url ) == 0 ) + continue; } } } - if ( b->a_addrpat != NULL ) { - if ( regex_matches( b->a_addrpat, conn->c_addr, edn, matches ) ) { - Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? - "granted" : "denied", 0 ); - - if ( odn ) free( odn ); - return( (b->a_access & ~ACL_SELF) >= access ); + + if ( b->a_domain_pat != NULL ) { + Debug( LDAP_DEBUG_ACL, "<= check a_domain_pat: %s\n", + b->a_domain_pat, 0, 0 ); + + if ( strcmp( b->a_domain_pat, "*" ) != 0) { + if ( b->a_domain_style == ACL_STYLE_REGEX) { + if (!regex_matches( b->a_domain_pat, conn->c_peer_domain, + e->e_ndn, matches ) ) + { + continue; + } + } else { + if ( strcasecmp( b->a_domain_pat, conn->c_peer_domain ) == 0 ) + continue; + } } } - if ( b->a_domainpat != NULL ) { - Debug( LDAP_DEBUG_ARGS, "<= check a_domainpath: %s\n", - b->a_domainpat, 0, 0 ); - if ( regex_matches( b->a_domainpat, conn->c_domain, edn, matches ) ) - { - Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? - "granted" : "denied", 0 ); - if ( odn ) free( odn ); - return( (b->a_access & ~ACL_SELF) >= access ); + if ( b->a_peername_pat != NULL ) { + Debug( LDAP_DEBUG_ACL, "<= check a_peername_path: %s\n", + b->a_peername_pat, 0, 0 ); + + if ( strcmp( b->a_peername_pat, "*" ) != 0) { + if ( b->a_peername_style == ACL_STYLE_REGEX) { + if (!regex_matches( b->a_peername_pat, conn->c_peer_name, + e->e_ndn, matches ) ) + { + continue; + } + } else { + if ( strcasecmp( b->a_peername_pat, conn->c_peer_name ) == 0 ) + continue; + } } } - if ( b->a_dnattr != NULL && op->o_dn != NULL ) { - Debug( LDAP_DEBUG_ARGS, "<= check a_dnattr: %s\n", - b->a_dnattr, 0, 0); - /* see if asker is listed in dnattr */ - if ( (at = attr_find( e->e_attrs, b->a_dnattr )) != NULL && - value_find( at->a_vals, &bv, at->a_syntax, 3 ) == 0 ) - { - if ( (b->a_access & ACL_SELF) && - (val == NULL || value_cmp( &bv, val, at->a_syntax, 2 )) ) - { - continue; + + if ( b->a_sockname_pat != NULL ) { + Debug( LDAP_DEBUG_ACL, "<= check a_sockname_path: %s\n", + b->a_sockname_pat, 0, 0 ); + + if ( strcmp( b->a_sockname_pat, "*" ) != 0) { + if ( b->a_sockname_style == ACL_STYLE_REGEX) { + if (!regex_matches( b->a_sockname_pat, conn->c_sock_name, + e->e_ndn, matches ) ) + { + continue; + } + } else { + if ( strcasecmp( b->a_sockname_pat, conn->c_sock_name ) == 0 ) + continue; } + } + } - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, - "<= acl_acces_allowed: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? - "granted" : "denied", 0 ); + if ( b->a_dn_at != NULL && op->o_ndn != NULL ) { + Attribute *at; + struct berval bv; + int rc, match = 0; + const char *text; + const char *desc = b->a_dn_at->ad_cname->bv_val; - return( (b->a_access & ~ACL_SELF) >= access ); - } + Debug( LDAP_DEBUG_ACL, "<= check a_dn_at: %s\n", + desc, 0, 0); + + bv.bv_val = op->o_ndn; + bv.bv_len = strlen( bv.bv_val ); - /* asker not listed in dnattr - check for self access */ - if ( ! (b->a_access & ACL_SELF) || val == NULL || - value_cmp( &bv, val, at->a_syntax, 2 ) != 0 ) + /* see if asker is listed in dnattr */ + for( at = attrs_find( e->e_attrs, b->a_dn_at ); + at != NULL; + at = attrs_find( at->a_next, b->a_dn_at ) ) { - continue; + if( value_find( b->a_dn_at, at->a_vals, &bv ) == 0 ) { + /* found it */ + match = 1; + break; + } } - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: matched by clause #%d (self) access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? "granted" - : "denied", 0 ); - - return( (b->a_access & ~ACL_SELF) >= access ); + if( match ) { + /* have a dnattr match. if this is a self clause then + * the target must also match the op dn. + */ + if ( b->a_dn_self ) { + /* check if the target is an attribute. */ + if ( val == NULL ) + continue; + /* target is attribute, check if the attribute value + * is the op dn. + */ + rc = value_match( &match, b->a_dn_at, + b->a_dn_at->ad_type->sat_equality, + val, &bv, &text ); + /* on match error or no match, fail the ACL clause */ + if (rc != LDAP_SUCCESS || match != 0 ) + continue; + } + } else { + /* no dnattr match, check if this is a self clause */ + if ( ! b->a_dn_self ) + continue; + /* this is a self clause, check if the target is an + * attribute. + */ + if ( val == NULL ) + continue; + /* target is attribute, check if the attribute value + * is the op dn. + */ + rc = value_match( &match, b->a_dn_at, + b->a_dn_at->ad_type->sat_equality, + val, &bv, &text ); + /* on match error or no match, fail the ACL clause */ + if (rc != LDAP_SUCCESS || match != 0 ) + continue; + } } -#ifdef SLAPD_ACLGROUPS - if ( b->a_group != NULL && op->o_dn != NULL ) { - char buf[512]; + + if ( b->a_group_pat != NULL && op->o_ndn != NULL ) { + char buf[1024]; /* b->a_group is an unexpanded entry name, expanded it should be an * entry with objectclass group* and we test to see if odn is one of * the values in the attribute group */ /* see if asker is listed in dnattr */ - string_expand(buf, sizeof(buf), b->a_group, edn, matches); - - if (be_group(be, buf, odn, b->a_objectclassvalue, b->a_groupattrname) == 0) { - Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: matched by clause #%d (group) access granted\n", - i, 0, 0 ); - if ( odn ) free( odn ); - return( (b->a_access & ~ACL_SELF) >= access ); + if ( b->a_group_style == ACL_STYLE_REGEX ) { + string_expand(buf, sizeof(buf), b->a_group_pat, e->e_ndn, matches); + if ( dn_normalize(buf) == NULL ) { + /* did not expand to a valid dn */ + continue; + } + } else { + strncpy( buf, b->a_group_pat, sizeof(buf) - 1 ); + buf[sizeof(buf) - 1] = 0; + } + + if (backend_group(be, e, buf, op->o_ndn, + b->a_group_oc, b->a_group_at) != 0) + { + continue; + } + } + +#ifdef SLAPD_ACI_ENABLED + if ( b->a_aci_at != NULL ) { + Attribute *at; + slap_access_t grant, deny, tgrant, tdeny; + + /* this case works different from the others above. + * since aci's themselves give permissions, we need + * to first check b->a_mask, the ACL's access level. + */ + + if( op->o_ndn == NULL || op->o_ndn[0] == '\0' ) { + continue; + } + + if ( e->e_dn == NULL ) { + continue; + } + + /* first check if the right being requested + * is allowed by the ACL clause. + */ + if ( ! ACL_GRANT( b->a_mask, *mask ) ) { + continue; + } + + /* get the aci attribute */ + at = attr_find( e->e_attrs, b->a_aci_at ); + if ( at == NULL ) { + continue; + } + + /* start out with nothing granted, nothing denied */ + ACL_INIT(tgrant); + ACL_INIT(tdeny); + + /* the aci is an multi-valued attribute. The + * rights are determined by OR'ing the individual + * rights given by the acis. + */ + for ( i = 0; at->a_vals[i] != NULL; i++ ) { + if (aci_mask( be, conn, op, + e, desc, val, at->a_vals[i], + matches, &grant, &deny ) != 0) + { + tgrant |= grant; + tdeny |= deny; + } + } + + /* remove anything that the ACL clause does not allow */ + tgrant &= b->a_mask & ACL_PRIV_MASK; + tdeny &= ACL_PRIV_MASK; + + /* see if we have anything to contribute */ + if( ACL_IS_INVALID(tgrant) && ACL_IS_INVALID(tdeny) ) { + continue; + } + + /* this could be improved by changing acl_mask so that it can deal with + * by clauses that return grant/deny pairs. Right now, it does either + * additive or subtractive rights, but not both at the same time. So, + * we need to combine the grant/deny pair into a single rights mask in + * a smart way: if either grant or deny is "empty", then we use the + * opposite as is, otherwise we remove any denied rights from the grant + * rights mask and construct an additive mask. + */ + if (ACL_IS_INVALID(tdeny)) { + modmask = tgrant | ACL_PRIV_ADDITIVE; + + } else if (ACL_IS_INVALID(tgrant)) { + modmask = tdeny | ACL_PRIV_SUBSTRACTIVE; + + } else { + modmask = (tgrant & ~tdeny) | ACL_PRIV_ADDITIVE; } + + } else +#endif + { + modmask = b->a_mask; + } + + + Debug( LDAP_DEBUG_ACL, + "<= acl_mask: [%d] applying %s (%s)\n", + i, accessmask2str( modmask, accessmaskbuf ), + b->a_type == ACL_CONTINUE + ? "continue" + : b->a_type == ACL_BREAK + ? "break" + : "stop" ); + + /* save old mask */ + oldmask = *mask; + + if( ACL_IS_ADDITIVE(modmask) ) { + /* add privs */ + ACL_PRIV_SET( *mask, modmask ); + + /* cleanup */ + ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK ); + + } else if( ACL_IS_SUBTRACTIVE(modmask) ) { + /* substract privs */ + ACL_PRIV_CLR( *mask, modmask ); + + /* cleanup */ + ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK ); + + } else { + /* assign privs */ + *mask = modmask; + } + + Debug( LDAP_DEBUG_ACL, + "<= acl_mask: [%d] mask: %s\n", + i, accessmask2str(*mask, accessmaskbuf), 0 ); + + if( b->a_type == ACL_CONTINUE ) { + continue; + + } else if ( b->a_type == ACL_BREAK ) { + return ACL_BREAK; + + } else { + return ACL_STOP; } -#endif /* SLAPD_ACLGROUPS */ } - if ( odn ) free( odn ); Debug( LDAP_DEBUG_ACL, - "<= acl_access_allowed: %s by default (no matching by)\n", - default_access >= access ? "granted" : "denied", 0, 0 ); - - return( default_access >= access ); + "<= acl_mask: no more clauses, returning %s (stop)\n", + accessmask2str(*mask, accessmaskbuf), 0, 0 ); + return ACL_STOP; } /* - * acl_check_mods - check access control on the given entry to see if + * acl_check_modlist - check access control on the given entry to see if * it allows the given modifications by the user associated with op. - * returns LDAP_SUCCESS mods allowed ok - * anything else mods not allowed - return is an error - * code indicating the problem + * returns 1 if mods allowed ok + * 0 mods not allowed */ int -acl_check_mods( +acl_check_modlist( Backend *be, Connection *conn, Operation *op, Entry *e, - LDAPMod *mods + Modifications *mlist ) { int i; - struct acl *a; - char *edn; - edn = dn_normalize_case( strdup( e->e_dn ) ); + assert( be != NULL ); - for ( ; mods != NULL; mods = mods->mod_next ) { - regmatch_t matches[MAXREMATCHES]; + /* short circuit root database access */ + if ( be_isroot( be, op->o_ndn ) ) { + Debug( LDAP_DEBUG_ACL, + "<= acl_access_allowed: granted to database root\n", + 0, 0, 0 ); + return 1; + } - /* the lastmod attributes are ignored by ACL checking */ - if ( strcasecmp( mods->mod_type, "modifiersname" ) == 0 || - strcasecmp( mods->mod_type, "modifytimestamp" ) == 0 || - strcasecmp( mods->mod_type, "creatorsname" ) == 0 || - strcasecmp( mods->mod_type, "createtimestamp" ) == 0 ) - { - Debug( LDAP_DEBUG_ACL, "LASTMOD attribute: %s access allowed\n", - mods->mod_type, 0, 0 ); + /* use backend default access if no backend acls */ + if( be != NULL && be->be_acl == NULL ) { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: backend default %s access %s to \"%s\"\n", + access2str( ACL_WRITE ), + be->be_dfltaccess >= ACL_WRITE ? "granted" : "denied", op->o_dn ); + + return be->be_dfltaccess >= ACL_WRITE; + +#ifdef notdef + /* be is always non-NULL */ + /* use global default access if no global acls */ + } else if ( be == NULL && global_acl == NULL ) { + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: global default %s access %s to \"%s\"\n", + access2str( ACL_WRITE ), + global_default_access >= ACL_WRITE ? "granted" : "denied", op->o_dn ); + + return global_default_access >= ACL_WRITE; +#endif + } + + for ( ; mlist != NULL; mlist = mlist->sml_next ) { + /* + * no-user-modification operational attributes are ignored + * by ACL_WRITE checking as any found here are not provided + * by the user + */ + if ( is_at_no_user_mod( mlist->sml_desc->ad_type ) ) { + Debug( LDAP_DEBUG_ACL, "acl: no-user-mod %s:" + " modify access granted\n", + mlist->sml_desc->ad_cname->bv_val, 0, 0 ); continue; } - a = acl_get_applicable( be, op, e, mods->mod_type, edn, - MAXREMATCHES, matches ); - - switch ( mods->mod_op & ~LDAP_MOD_BVALUES ) { + switch ( mlist->sml_op ) { case LDAP_MOD_REPLACE: case LDAP_MOD_ADD: - if ( mods->mod_bvalues == NULL ) { + if ( mlist->sml_bvalues == NULL ) { break; } - for ( i = 0; mods->mod_bvalues[i] != NULL; i++ ) { - if ( ! acl_access_allowed( a, be, conn, e, mods->mod_bvalues[i], - op, ACL_WRITE, edn, matches) ) + for ( i = 0; mlist->sml_bvalues[i] != NULL; i++ ) { + if ( ! access_allowed( be, conn, op, e, + mlist->sml_desc, mlist->sml_bvalues[i], ACL_WRITE ) ) { - free(edn); - return( LDAP_INSUFFICIENT_ACCESS ); + return( 0 ); } } break; case LDAP_MOD_DELETE: - if ( mods->mod_bvalues == NULL ) { - if ( ! acl_access_allowed( a, be, conn, e, - NULL, op, ACL_WRITE, edn, matches) ) + if ( mlist->sml_bvalues == NULL ) { + if ( ! access_allowed( be, conn, op, e, + mlist->sml_desc, NULL, ACL_WRITE ) ) { - free(edn); - return( LDAP_INSUFFICIENT_ACCESS ); + return( 0 ); } break; } - for ( i = 0; mods->mod_bvalues[i] != NULL; i++ ) { - if ( ! acl_access_allowed( a, be, conn, e, mods->mod_bvalues[i], - op, ACL_WRITE, edn, matches) ) + for ( i = 0; mlist->sml_bvalues[i] != NULL; i++ ) { + if ( ! access_allowed( be, conn, op, e, + mlist->sml_desc, mlist->sml_bvalues[i], ACL_WRITE ) ) { - free(edn); - return( LDAP_INSUFFICIENT_ACCESS ); + return( 0 ); } } break; } } - free(edn); - return( LDAP_SUCCESS ); + return( 1 ); +} + +#ifdef SLAPD_ACI_ENABLED +static char * +aci_bvstrdup( struct berval *bv ) +{ + char *s; + + s = (char *)ch_malloc(bv->bv_len + 1); + if (s != NULL) { + memcpy(s, bv->bv_val, bv->bv_len); + s[bv->bv_len] = 0; + } + return(s); +} + +static int +aci_strbvcmp( + const char *s, + struct berval *bv ) +{ + int res, len; + + res = strncasecmp( s, bv->bv_val, bv->bv_len ); + if (res) + return(res); + len = strlen(s); + if (len > (int)bv->bv_len) + return(1); + if (len < (int)bv->bv_len) + return(-1); + return(0); +} + +static int +aci_get_part( + struct berval *list, + int ix, + char sep, + struct berval *bv ) +{ + int len; + char *p; + + if (bv) { + bv->bv_len = 0; + bv->bv_val = NULL; + } + len = list->bv_len; + p = list->bv_val; + while (len >= 0 && --ix >= 0) { + while (--len >= 0 && *p++ != sep) ; + } + while (len >= 0 && *p == ' ') { + len--; + p++; + } + if (len < 0) + return(-1); + + if (!bv) + return(0); + + bv->bv_val = p; + while (--len >= 0 && *p != sep) { + bv->bv_len++; + p++; + } + while (bv->bv_len > 0 && *--p == ' ') + bv->bv_len--; + return(bv->bv_len); +} + +static int +aci_list_map_rights( + struct berval *list ) +{ + struct berval bv; + slap_access_t mask; + int i; + + ACL_INIT(mask); + for (i = 0; aci_get_part(list, i, ',', &bv) >= 0; i++) { + if (bv.bv_len <= 0) + continue; + switch (*bv.bv_val) { + case 'c': + ACL_PRIV_SET(mask, ACL_PRIV_COMPARE); + break; + case 's': + /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines + * the right 's' to mean "set", but in the examples states + * that the right 's' means "search". The latter definition + * is used here. + */ + ACL_PRIV_SET(mask, ACL_PRIV_SEARCH); + break; + case 'r': + ACL_PRIV_SET(mask, ACL_PRIV_READ); + break; + case 'w': + ACL_PRIV_SET(mask, ACL_PRIV_WRITE); + break; + case 'x': + /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not + * define any equivalent to the AUTH right, so I've just used + * 'x' for now. + */ + ACL_PRIV_SET(mask, ACL_PRIV_AUTH); + break; + default: + break; + } + + } + return(mask); +} + +static int +aci_list_has_attr( + struct berval *list, + const char *attr, + struct berval *val ) +{ + struct berval bv, left, right; + int i; + + for (i = 0; aci_get_part(list, i, ',', &bv) >= 0; i++) { + if (aci_get_part(&bv, 0, '=', &left) < 0 + || aci_get_part(&bv, 1, '=', &right) < 0) + { + if (aci_strbvcmp(attr, &bv) == 0) + return(1); + } else if (val == NULL) { + if (aci_strbvcmp(attr, &left) == 0) + return(1); + } else { + if (aci_strbvcmp(attr, &left) == 0) { + /* this is experimental code that implements a + * simple (prefix) match of the attribute value. + * the ACI draft does not provide for aci's that + * apply to specific values, but it would be + * nice to have. If the part of an aci's + * rights list is of the form =, + * that means the aci applies only to attrs with + * the given value. Furthermore, if the attr is + * of the form =*, then is + * treated as a prefix, and the aci applies to + * any value with that prefix. + * + * Ideally, this would allow r.e. matches. + */ + if (aci_get_part(&right, 0, '*', &left) < 0 + || right.bv_len <= left.bv_len) + { + if (aci_strbvcmp(val->bv_val, &right) == 0) + return(1); + } else if (val->bv_len >= left.bv_len) { + if (strncasecmp( val->bv_val, left.bv_val, left.bv_len ) == 0) + return(1); + } + } + } + } + return(0); +} + +static slap_access_t +aci_list_get_attr_rights( + struct berval *list, + const char *attr, + struct berval *val ) +{ + struct berval bv; + slap_access_t mask; + int i; + + /* loop through each rights/attr pair, skip first part (action) */ + ACL_INIT(mask); + for (i = 1; aci_get_part(list, i + 1, ';', &bv) >= 0; i += 2) { + if (aci_list_has_attr(&bv, attr, val) == 0) + continue; + if (aci_get_part(list, i, ';', &bv) < 0) + continue; + mask |= aci_list_map_rights(&bv); + } + return(mask); +} + +static int +aci_list_get_rights( + struct berval *list, + const char *attr, + struct berval *val, + slap_access_t *grant, + slap_access_t *deny ) +{ + struct berval perm, actn; + slap_access_t *mask; + int i, found; + + if (attr == NULL || *attr == 0 || strcasecmp(attr, "entry") == 0) { + attr = "[entry]"; + } + + found = 0; + ACL_INIT(*grant); + ACL_INIT(*deny); + /* loop through each permissions clause */ + for (i = 0; aci_get_part(list, i, '$', &perm) >= 0; i++) { + if (aci_get_part(&perm, 0, ';', &actn) < 0) + continue; + if (aci_strbvcmp( "grant", &actn ) == 0) { + mask = grant; + } else if (aci_strbvcmp( "deny", &actn ) == 0) { + mask = deny; + } else { + continue; + } + + found = 1; + *mask |= aci_list_get_attr_rights(&perm, attr, val); + *mask |= aci_list_get_attr_rights(&perm, "[all]", NULL); + } + return(found); } -static string_expand( +static int +aci_group_member ( + struct berval *subj, + const char *defgrpoc, + const char *defgrpat, + Backend *be, + Entry *e, + Operation *op, + regmatch_t *matches +) +{ + struct berval bv; + char *subjdn, *grpdn = NULL; + char *grpoc; + char *grpat; + ObjectClass *grp_oc = NULL; + AttributeDescription *grp_ad = NULL; + char *text; + int rc; + + /* format of string is "group/objectClassValue/groupAttrName" */ + if (aci_get_part(subj, 0, '/', &bv) < 0) { + return(0); + } + + subjdn = aci_bvstrdup(&bv); + if (subjdn == NULL) { + return(0); + } + + if (aci_get_part(subj, 1, '/', &bv) < 0) { + grpoc = ch_strdup( defgrpoc ); + } else { + grpoc = aci_bvstrdup(&bv); + } + + if (aci_get_part(subj, 2, '/', &bv) < 0) { + grpat = ch_strdup( defgrpat ); + } else { + grpat = aci_bvstrdup(&bv); + } + + rc = slap_str2ad( grpat, &grp_ad, &text ); + if( rc != LDAP_SUCCESS ) { + rc = 0; + goto done; + } + rc = 0; + + grp_oc = oc_find( grpoc ); + grpdn = (char *)ch_malloc(1024); + + if (grp_oc != NULL && grp_ad != NULL && grpdn != NULL) { + string_expand(grpdn, 1024, subjdn, e->e_ndn, matches); + if ( dn_normalize(grpdn) != NULL ) { + rc = (backend_group(be, e, grpdn, op->o_ndn, grp_oc, grp_ad) == 0); + } + } + +done: + if( grp_ad != NULL ) ad_free( grp_ad, 1 ); + ch_free(grpdn); + ch_free(grpat); + ch_free(grpoc); + ch_free(subjdn); + return(rc); +} + +static int +aci_mask( + Backend *be, + Connection *conn, + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + struct berval *aci, + regmatch_t *matches, + slap_access_t *grant, + slap_access_t *deny +) +{ + struct berval bv, perms, sdn; + char *subjdn; + int rc; + char *attr = desc->ad_cname->bv_val; + + /* parse an aci of the form: + oid#scope#action;rights;attr;rights;attr$action;rights;attr;rights;attr#dnType#subjectDN + + See draft-ietf-ldapext-aci-model-04.txt section 9.1 for + a full description of the format for this attribute. + + For now, this routine only supports scope=entry. + */ + + /* check that the aci has all 5 components */ + if (aci_get_part(aci, 4, '#', NULL) < 0) + return(0); + + /* check that the aci family is supported */ + if (aci_get_part(aci, 0, '#', &bv) < 0) + return(0); + + /* check that the scope is "entry" */ + if (aci_get_part(aci, 1, '#', &bv) < 0 + || aci_strbvcmp( "entry", &bv ) != 0) + { + return(0); + } + + /* get the list of permissions clauses, bail if empty */ + if (aci_get_part(aci, 2, '#', &perms) <= 0) + return(0); + + /* check if any permissions allow desired access */ + if (aci_list_get_rights(&perms, attr, val, grant, deny) == 0) + return(0); + + /* see if we have a DN match */ + if (aci_get_part(aci, 3, '#', &bv) < 0) + return(0); + + if (aci_get_part(aci, 4, '#', &sdn) < 0) + return(0); + + if (aci_strbvcmp( "access-id", &bv ) == 0) { + subjdn = aci_bvstrdup(&sdn); + if (subjdn == NULL) + return(0); + rc = 1; + if ( dn_normalize(subjdn) != NULL ) + if (strcasecmp(op->o_ndn, subjdn) != 0) + rc = 0; + ch_free(subjdn); + return(rc); + } + + if (aci_strbvcmp( "self", &bv ) == 0) { + if (strcasecmp(op->o_ndn, e->e_ndn) == 0) + return(1); + + } else if (aci_strbvcmp( "dnattr", &bv ) == 0) { + char *dnattr = aci_bvstrdup(&sdn); + Attribute *at; + AttributeDescription *ad = NULL; + const char *text; + + rc = slap_str2ad( dnattr, &ad, &text ); + ch_free( dnattr ); + + if( rc != LDAP_SUCCESS ) { + return 0; + } + + rc = 0; + + bv.bv_val = op->o_ndn; + bv.bv_len = strlen( bv.bv_val ); + + for(at = attrs_find( e->e_attrs, ad ); + at != NULL; + at = attrs_find( at->a_next, ad ) ) + { + if (value_find( ad, at->a_vals, &bv) == 0 ) { + rc = 1; + break; + } + } + + ad_free( ad, 1 ); + return rc; + + + } else if (aci_strbvcmp( "group", &bv ) == 0) { + if (aci_group_member(&sdn, SLAPD_GROUP_CLASS, SLAPD_GROUP_ATTR, be, e, op, matches)) + return(1); + + } else if (aci_strbvcmp( "role", &bv ) == 0) { + if (aci_group_member(&sdn, SLAPD_ROLE_CLASS, SLAPD_ROLE_ATTR, be, e, op, matches)) + return(1); + + } + + return(0); +} + +#endif /* SLAPD_ACI_ENABLED */ + +static void +string_expand( char *newbuf, int bufsiz, char *pat, @@ -486,9 +1284,10 @@ static string_expand( size = 0; newbuf[0] = '\0'; + bufsiz--; /* leave space for lone $ */ flag = 0; - for ( dp = newbuf, sp = pat; size < 512 && *sp ; sp++) { + for ( dp = newbuf, sp = pat; size < bufsiz && *sp ; sp++) { /* did we previously see a $ */ if (flag) { if (*sp == '$') { @@ -497,7 +1296,6 @@ static string_expand( } else if (*sp >= '0' && *sp <= '9' ) { int n; int i; - char *ep; int l; n = *sp - '0'; @@ -520,6 +1318,13 @@ static string_expand( } } } + + if (flag) { + /* must have ended with a single $ */ + *dp++ = '$'; + size++; + } + *dp = '\0'; Debug( LDAP_DEBUG_TRACE, "=> string_expand: pattern: %s\n", pat, 0, 0 ); @@ -538,6 +1343,8 @@ regex_matches( char newbuf[512]; int rc; + if(str == NULL) str = ""; + string_expand(newbuf, sizeof(newbuf), pat, buf, matches); if (( rc = regcomp(&re, newbuf, REG_EXTENDED|REG_ICASE))) { char error[512];