diff options
Diffstat (limited to 'ldap/servers/plugins/acl/aclparse.c')
-rw-r--r-- | ldap/servers/plugins/acl/aclparse.c | 1928 |
1 files changed, 1928 insertions, 0 deletions
diff --git a/ldap/servers/plugins/acl/aclparse.c b/ldap/servers/plugins/acl/aclparse.c new file mode 100644 index 00000000..2247471a --- /dev/null +++ b/ldap/servers/plugins/acl/aclparse.c @@ -0,0 +1,1928 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +/****************************************************************************/ +/* prototypes */ +/****************************************************************************/ +static int __aclp__parse_aci(char *str, aci_t *aci_item); +static int __aclp__sanity_check_acltxt(aci_t *aci_item, char *str); +static char * __aclp__normalize_acltxt (aci_t *aci_item, char *str); +static char * __aclp__getNextLASRule(aci_t *aci_item, char *str, + char **endOfCurrRule); +static char * __aclp__dn_normalize( char *dn , char *end); +static int __aclp__get_aci_right ( char *str); +static int __aclp__init_targetattr (aci_t *aci, char *attr_val); +static int __acl__init_targetattrfilters( aci_t *aci_item, char *str); +static int process_filter_list( Targetattrfilter ***attrfilterarray, + char * str); +static int __acl_init_targetattrfilter( Targetattrfilter *attrfilter, char *str ); +static void __aclp_chk_paramRules ( aci_t *aci_item, char *start, + char *end); +static void __acl_strip_trailing_space( char *str); +static void __acl_strip_leading_space( char **str); +static char * __acl_trim_filterstr( char * str ); +static int acl_verify_exactly_one_attribute( char *attr_name, Slapi_Filter *f); +static int type_compare( Slapi_Filter *f, void *arg); +static int acl_check_for_target_macro( aci_t *aci_item, char *value); +static int get_acl_rights_as_int( char * strValue); + +/*************************************************************************** +* +* acl_parse +* +* Parses the input string and copies the information into the +* correct place in the aci. +* +* +* Input: +* char *str - Input string which has the ACL +* This is a duped copy, so here we have +* the right to stich '\0' characters into str for +* processing purposes. If you want to keep +* a piece of str, you'll need to dup it +* as it gets freed outside the scope of acl_parse. +* aci_t *item - the aci item where the ACL info will be +* - stored. +* +* Returns: +* 0 -- Parsed okay +* < 0 -- error codes +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_parse(char * str, aci_t *aci_item) +{ + + int rv=0; + char *next; + char *save; + + while(*str) { + __acl_strip_leading_space( &str ); + if (*str == '\0') break; + + if (*str == '(') { + if ((next = slapi_find_matching_paren(str)) == NULL) { + return(ACL_SYNTAX_ERR); + } + } else { + /* then we have done all the processing */ + return 0; + } + LDAP_UTF8INC(str); /* skip the "(" */ + save = next; + LDAP_UTF8INC(next); + *save = '\0'; + + /* Now we have a "str)" */ + if ( 0 != (rv = __aclp__parse_aci(str, aci_item))) { + return(rv); + } + + /* Move to the next */ + str = next; + } + + /* check if have a ACLTXT or not */ + if (!(aci_item->aci_type & ACI_ACLTXT)) + return ACL_SYNTAX_ERR; + + if (aci_item->target) { + Slapi_Filter *f; + + /* Make sure that the target is a valid target. + ** Example: ACL is located in + ** "ou=engineering, o=ace industry, c=us + ** but if the target is "o=ace industry, c=us", + ** then it's an ERROR. + */ + f = aci_item->target; + if (aci_item->aci_type & ACI_TARGET_DN) { + char *avaType; + struct berval *avaValue; + const char *dn; + + dn = slapi_sdn_get_ndn ( aci_item->aci_sdn ); + slapi_filter_get_ava ( f, &avaType, &avaValue ); + + if (!slapi_dn_issuffix( avaValue->bv_val, dn)) + return ACL_INVALID_TARGET; + } + } + + /* + ** We need to keep the taregetFilterStr for anyone ACL only. + ** same for targetValueFilterStr. + ** We need to keep it for macros too as it needs to be expnaded at eval time. + ** + */ + if ( (aci_item->aci_elevel != ACI_ELEVEL_USERDN_ANYONE) && + !(aci_item->aci_type & ACI_TARGET_MACRO_DN) ) { + slapi_ch_free ( (void **) & aci_item->targetFilterStr ); + } + + /* + * If we parsed the aci and there was a ($dn) on the user side + * but none in hte taget then that's an error as the user side + * value is derived from the target side value. + */ + + if (!(aci_item->aci_type & ACI_TARGET_MACRO_DN) && + (aci_item->aci_ruleType & ACI_PARAM_DNRULE)) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "acl_parse: A macro in a subject ($dn) must have a macro in the target.\n"); + return(ACL_INVALID_TARGET); + } + + return 0; +} + +/*************************************************************************** +* +* __aclp__parse_aci +* +* Parses Each individual subset of information/ +* +* Input: +* char *str - Input string which has the ACL like "str)" +* aci_t *item - the aci item where the ACL info will be +* - stored. +* +* Returns: +* 0 -- Parsed okay +* < 0 -- error codes +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +__aclp__parse_aci (char *str, aci_t *aci_item) +{ + + int len; + int rv; + int type; + char *tmpstr; + char *s = NULL; + char *value = NULL; + Slapi_Filter *f = NULL; + int targetattrlen = strlen(aci_targetattr); + int targetdnlen = strlen (aci_targetdn); + int tfilterlen = strlen(aci_targetfilter); + int targetattrfilterslen = strlen(aci_targetattrfilters); + + __acl_strip_leading_space( &str ); + + if (*str == '\0') { + return(ACL_SYNTAX_ERR); + } + + /* The first letter should tell us something */ + switch(*str) { + case 'v': + type = ACI_ACLTXT; + + if ( 0 != (rv= __aclp__sanity_check_acltxt(aci_item, str ) ) ) { + + return rv; + } + break; + + case 't': + if (strncmp(str, aci_targetattrfilters,targetattrfilterslen ) == 0) { + type = ACI_TARGET_ATTR; + + + /* + * The targetattrfilters bit looks like this: + * (targetattrfilters="add= attr1:F1 && attr2:F2 ... && attrn:Fn, + * del= attr1:F1 && attr2:F2... && attrn:Fn") + */ + if ( 0 != (rv= __acl__init_targetattrfilters( + aci_item, str))) { + return rv; + } + } else if (strncmp(str, aci_targetattr,targetattrlen ) == 0) { + type = ACI_TARGET_ATTR; + + if ( (s = strstr( str, "!=" )) != NULL ) { + type |= ACI_TARGET_ATTR_NOT; + strncpy(s, " ", 1); + } + /* Get individual components of the targetattr. + * (targetattr = "cn || u* || phone ||tel:add:(tel=1234) + * || sn:del:(gn=5678)") + * If it contains a value filter, the type will also be + * ACI_TARGET_VALUE_ATTR. + */ + if ( 0 != (rv= __aclp__init_targetattr( + aci_item, str))) { + return rv; + } + } else if (strncmp(str, aci_targetfilter,tfilterlen ) == 0) { + if ( aci_item->targetFilter) + return ACL_SYNTAX_ERR; + + type = ACI_TARGET_FILTER; + /* we need to remove the targetfilter stuff*/ + if ( (s = strstr( str, "!=" )) != NULL ) { + type |= ACI_TARGET_FILTER_NOT; + } + + /* + * If it's got a macro in the targetfilter then it must + * have a target and it must have a macro. + */ + + if ((s = strstr (str, ACL_RULE_MACRO_DN_KEY)) != NULL || + ((s = strstr(str, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL)) { + + /* Must have a targetmacro */ + if ( !(aci_item->aci_type & ACI_TARGET_MACRO_DN)) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "acl_parse: A macro in a targetfilter ($dn) must have a macro in the target.\n"); + return(ACL_SYNTAX_ERR); + } + + type|= ACI_TARGET_FILTER_MACRO_DN; + } + + tmpstr = strchr(str, '='); + tmpstr++; + __acl_strip_leading_space(&tmpstr); + + /* + * Trim off enclosing quotes and enclosing + * superfluous brackets. + * The result has been duped so it can be kept. + */ + + tmpstr = __acl_trim_filterstr( tmpstr ); + + f = slapi_str2filter(tmpstr); + + /* save the filter string */ + aci_item->targetFilterStr = tmpstr; + + } else if (strncmp(str, aci_targetdn, targetdnlen) == 0) { + char *tstr = NULL; + const size_t LDAP_URL_prefix_len = strlen (LDAP_URL_prefix); + char *tt; + type = ACI_TARGET_DN; + /* Keep a copy of the target attr */ + if (aci_item->target) { + return (ACL_SYNTAX_ERR); + } + if ( (s = strstr( str, "!=" )) != NULL ) { + type |= ACI_TARGET_NOT; + strncpy(s, " ", 1); + } + + /* Convert it to lower as slapi_dn_normalize() does not */ + for (tt = str; *tt; tt++) *tt = TOLOWER ( *tt ); + + if ( (s = strchr( str, '=' )) != NULL ) { + value = s + 1; + slapi_dn_normalize(value); + len = strlen ( value ); + if (*value == '"' && value[len-1] == '"'){ + value[len-1] = '\0'; + value++; + } + __acl_strip_leading_space(&value); + } else { + return ( ACL_SYNTAX_ERR ); + } + + if ( strncasecmp ( value, LDAP_URL_prefix , LDAP_URL_prefix_len) ) + return ( ACL_SYNTAX_ERR ); + + value += LDAP_URL_prefix_len; + len = strlen ( value ); + tstr = (char *) slapi_ch_malloc ( targetdnlen + len + 4 ); + sprintf ( tstr, "(target=%s)", value); + if ( (rv = acl_check_for_target_macro( aci_item, value)) == -1) { + slapi_ch_free ( (void **) &tstr ); + return(ACL_SYNTAX_ERR); + } else if ( rv > 0) { + /* is present, so the type is now ACL_TARGET_MACRO_DN */ + type = ACI_TARGET_MACRO_DN; + } else { + /* it's a normal target with no macros inside */ + f = slapi_str2filter ( tstr ); + } + slapi_ch_free ( (void **) &tstr ); + } else { + /* did start with a 't' but was not a recognsied keyword */ + return(ACL_SYNTAX_ERR); + } + + /* + * Here, it was a recognised keyword that started with 't'. + * Check that the filter associated with ACI_TARGET_DN and + * ACI_TARGET_FILTER are OK. + */ + if (f == NULL) { + /* The following types require a filter to have been created */ + if (type & ACI_TARGET_DN) + return ACL_TARGET_FILTER_ERR; + else if (type & ACI_TARGET_FILTER) + return ACL_TARGETFILTER_ERR; + } else { + int filterChoice; + + filterChoice = slapi_filter_get_choice ( f ); + if ( (type & ACI_TARGET_DN) && + ( filterChoice == LDAP_FILTER_PRESENT)) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl__parse_aci: Unsupported filter type:%d\n", filterChoice); + return(ACL_SYNTAX_ERR); + } else if (( filterChoice == LDAP_FILTER_SUBSTRINGS) && + (type & ACI_TARGET_DN)) { + type &= ~ACI_TARGET_DN; + type |= ACI_TARGET_PATTERN; + } + } + + if ((type & ACI_TARGET_DN) || + (type & ACI_TARGET_PATTERN)) { + if (aci_item->target) { + /* There is something already. ERROR */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Multiple targets in the ACL syntax\n", + 0,0,0); + slapi_filter_free(f, 1); + return(ACL_SYNTAX_ERR); + } else { + aci_item->target = f; + } + } else if ( type & ACI_TARGET_FILTER) { + if (aci_item->targetFilter) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Multiple target Filters in the ACL Syntax\n", + 0,0,0); + slapi_filter_free(f, 1); + return(ACL_SYNTAX_ERR); + } else { + aci_item->targetFilter = f; + } + } + break; /* 't' */ + default: + /* Here the keyword did not start with 'v' ot 't' so error */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Unknown keyword at \"%s\"\n Expecting" + " \"target\", \"targetattr\", \"targetfilter\", \"targattrfilters\"" + " or \"version\"\n", str, 0, 0); + return(ACL_SYNTAX_ERR); + }/* switch() */ + + /* Store the type info */ + aci_item->aci_type |= type; + + return 0; +} + +/*************************************************************************** +* acl__sanity_check_acltxt +* +* Check the input ACL text. Reports any errors. Also forgivs if certain +* things are missing. +* +* Input: +* char *str - String containg the acl text +* int *err - error status +* +* Returns: +* 0 --- good status +* <0 --- error +* +* Error Handling: +* None. +* +* +**************************************************************************/ +static int +__aclp__sanity_check_acltxt (aci_t *aci_item, char *str) +{ + NSErr_t errp; + char *s; + ACLListHandle_t *handle = NULL; + char *newstr = NULL; + char *word; + char *next; + + memset (&errp, 0, sizeof(NSErr_t)); + newstr = str; + + while ((s = strstr(newstr, "authenticate")) != NULL) { + char *next; + next = s + 12; + s--; + while (s != str && ldap_utf8isspace(s)) LDAP_UTF8DEC(s); + if (s && *s == ';') { + /* We don't support authenticate stuff */ + return ACL_INVALID_AUTHORIZATION; + + } else { + newstr = next; + } + } + + newstr = slapi_ch_strdup (str); + word = ldap_utf8strtok_r(newstr, " ", &next); + if (strcasecmp (word, "version") == 0) { + word = ldap_utf8strtok_r(NULL, " ", &next); + if (atoi(word) != 3) { + slapi_ch_free ( (void **) &newstr ); + return ACL_INCORRECT_ACI_VERSION; + } + } + slapi_ch_free ( (void **) &newstr ); + + /* We need to normalize the DNs in the userdn and group dn + ** so that, it's only done once. + */ + if ((newstr = __aclp__normalize_acltxt (aci_item, str )) == NULL) { + return ACL_SYNTAX_ERR; + } + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Normalized String:%s\n", newstr, 0,0); + + /* check for acl syntax error */ + if ((handle = (ACLListHandle_t *) ACL_ParseString(&errp, + newstr)) == NULL) { + acl_print_acllib_err(&errp, str); + slapi_ch_free ( (void **) &newstr ); + return ACL_SYNTAX_ERR; + } else { + /* get the rights and the aci type */ + aci_item->aci_handle = handle; + nserrDispose(&errp); + slapi_ch_free ( (void **) &newstr ); + + return 0; + } +} +/****************************************************************************** +* +* acl__normalize_acltxt +* +* +* XXXrbyrne this routine should be re-written when someone eventually +* gets sick enough of it. Same for getNextLAS() below. +* +* Normalize the acltxt i.e normalize all the DNs specified in the +* Userdn and Groupdn rule so that we normalize once here and not +* over and over again at the runtime in the LASes. We have to normalize +* before we generate the handle otherwise it's of no use. +* Also convert deny to deny absolute +* +* The string that comes in is something like: +* version 3.0; acl "Dept domain administration"; allow (all) +* groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP"; ) +* +* Returns NULL on error. +* +******************************************************************************/ +static char * +__aclp__normalize_acltxt ( aci_t * aci_item, char * str ) +{ + + char *s, *p; + char *end; + char *aclstr, *s_aclstr; + char *ret_str = NULL; + int len; + char *ptr, *aclName; + char *nextACE; + char *tmp_str = NULL; + char *acestr = NULL; + char *s_acestr = NULL; + int aci_rights_val = 0; /* bug 389975 */ + + /* make a copy first */ + s_aclstr = aclstr = slapi_ch_strdup ( str ); + + /* The rules are like this version 3.0; acl "xyz"; rule1; rule2; */ + s = strchr (aclstr, ';'); + if ( NULL == s) { + slapi_ch_free ( (void **) &s_aclstr ); + return NULL; + } + aclstr = ++s; + + /* From DS 4.0, we support both aci (or aci) "name" -- we have to change to acl + ** as libaccess will not like it + */ + s = aclstr; + while (s && ldap_utf8isspace(s)) LDAP_UTF8INC(s); + *(s+2 ) = 'l'; + + aclName = s+3; + + s = strchr (aclstr, ';'); + if ( NULL == s) { + slapi_ch_free ( (void **) &s_aclstr ); + return NULL; + } + + aclstr = s; + LDAP_UTF8INC(aclstr); + *s = '\0'; + + /* Here aclName is the acl description string */ + aci_item->aclName = slapi_ch_strdup ( aclName ); + + aclutil_str_appened (&ret_str, s_aclstr); + aclutil_str_appened (&ret_str, ";"); + + /* start with the string */ + acestr = aclstr; + + /* + * Here acestr is something like: + * + * " allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP";)" + * + * + */ + +normalize_nextACERule: + + /* now we are in the rule part */ + tmp_str = acestr; + s = strchr (tmp_str, ';'); + if ( s == NULL) { + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return NULL; + } + nextACE = s; + LDAP_UTF8INC(nextACE); + *s = '\0'; + + /* acestr now will hold copy of the ACE. Also add + ** some more space in case we need to add "absolute" + ** for deny rule. We will never need more 2 times + ** the len. + */ + len = strlen (tmp_str); + s_acestr = acestr = slapi_ch_calloc ( 1, 2 * len); + + __acl_strip_leading_space(&tmp_str); + + /* + * Now it's something like: + * allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP"; + */ + if (strncasecmp(tmp_str, "allow", 5) == 0) { + memcpy(acestr, tmp_str, len); + tmp_str += 5; + /* gather the rights */ + aci_rights_val = __aclp__get_aci_right (tmp_str);/* bug 389975 */ + aci_item->aci_type |= ACI_HAS_ALLOW_RULE; + + } else if (strncasecmp(tmp_str, "deny", 4) == 0) { + char *d_rule ="deny absolute"; + /* Then we have to add "absolute" to the deny rule + ** What we are doing here is to tackle this situation. + ** + ** allow -- deny -- allow + ** deny -- allow + ** + ** by using deny absolute we force the precedence rule + ** i.e deny has a precedence over allow. Since there doesn't + ** seem to be an easy to detect the mix, forcing this + ** to all the deny rules will do the job. + */ + __acl_strip_leading_space(&tmp_str); + tmp_str += 4; + + /* We might have an absolute there already */ + if ((s = strstr (tmp_str, "absolute")) != NULL) { + tmp_str = s; + tmp_str += 8; + } + /* gather the rights */ + aci_rights_val = __aclp__get_aci_right (tmp_str);/* bug 389975 */ + aci_item->aci_type |= ACI_HAS_DENY_RULE; + + len = strlen ( d_rule ); + memcpy (acestr, d_rule, len ); + memcpy (acestr+len, tmp_str, strlen (tmp_str) ); + } else { + /* wrong syntax */ + aci_rights_val = -1 ; + } + if (aci_rights_val == -1 ) + { + /* wrong syntax */ + slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_acestr ); + slapi_ch_free ( (void **) &s_aclstr ); + return NULL; + } else + aci_item->aci_access |= aci_rights_val; + + + /* Normalize all the DNs in the userdn rule */ + + /* + * + * Here acestr starts like this: + * " allow (all) groupdn = "ldap:///cn=Domain Administrators, o=$dn.o, o=ISP" + */ + + s = __aclp__getNextLASRule(aci_item, acestr, &end); + while ( s ) { + if ( 0 == strncmp ( s, DS_LAS_USERDNATTR, 10) || + ( 0 == strncmp ( s, DS_LAS_USERATTR, 8))) { + /* + ** For userdnattr/userattr rule, the resources changes and hence + ** we cannot cache the result. See above for more comments. + */ + aci_item->aci_elevel = ACI_ELEVEL_USERDNATTR; + } else if ( 0== strncmp ( s, DS_LAS_USERDN, 6)) { + p = strstr ( s, "="); + p--; + if ( strncmp (p, "!=", 2) == 0) + aci_item->aci_type |= ACI_CONTAIN_NOT_USERDN; + + /* XXXrbyrne + * Here we need to scan for more ldap:/// within + * this userdn rule type: + * eg. userdn = "ldap:///cn=joe,o=sun.com || ldap:///self" + * This is handled correctly in DS_LASUserDnEval + * but the bug here is not setting ACI_USERDN_SELFRULE + * which would ensure that acl info is not cached from + * one resource entry to the next. (bug 558519) + */ + p = strstr ( p, "ldap"); + if (p == NULL) { + /* must start with ldap */ + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + p += 8; /* for ldap:/// */ + if( __aclp__dn_normalize (p, end) == NULL) { + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + + /* we have a rule like userdn = "ldap:///blah". s points to blah now. + ** let's find if we have a SELF rule like userdn = "ldap:///self". + ** Since the resource changes on entry basis, we can't cache the + ** evalation of handle for all time. The cache result is valid + ** within the evaluation of that resource. + */ + if (strncasecmp(p, "self", 4) == 0) { + aci_item->aci_ruleType |= ACI_USERDN_SELFRULE; + } else if ( strncasecmp(p, "anyone", 6) == 0 ) { + aci_item->aci_elevel = ACI_ELEVEL_USERDN_ANYONE; + + } else if ( strncasecmp(p, "all", 3) == 0 ) { + if ( aci_item->aci_elevel > ACI_ELEVEL_USERDN_ALL ) + aci_item->aci_elevel = ACI_ELEVEL_USERDN_ALL; + + } else { + if ( aci_item->aci_elevel > ACI_ELEVEL_USERDN ) + aci_item->aci_elevel = ACI_ELEVEL_USERDN; + } + + /* See if we have a parameterized rule */ + __aclp_chk_paramRules ( aci_item, p, end ); + } else if ( 0 == strncmp ( s, DS_LAS_GROUPDNATTR, 11)) { + /* + ** For groupdnattr rule, the resources changes and hence + ** we cannot cache the result. See above for more comments. + */ + /* Find out if we have a URL type of rule */ + if ((p= strstr (s, "ldap")) != NULL) { + if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDNATTR_URL ) + aci_item->aci_elevel = ACI_ELEVEL_GROUPDNATTR_URL; + } else if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDNATTR ) { + aci_item->aci_elevel = ACI_ELEVEL_GROUPDNATTR; + } + aci_item->aci_ruleType |= ACI_GROUPDNATTR_RULE; + } else if ( 0 == strncmp ( s, DS_LAS_GROUPDN, 7)) { + + p = strstr ( s, "="); + p--; + if ( strncmp (p, "!=", 2) == 0) + aci_item->aci_type |= ACI_CONTAIN_NOT_GROUPDN; + + p = strstr ( s, "ldap"); + if (p == NULL) { + /* must start with ldap */ + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + p += 8; + if (__aclp__dn_normalize (p, end) == NULL) { + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + /* check for param rules */ + __aclp_chk_paramRules ( aci_item, p, end ); + + if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDN ) + aci_item->aci_elevel = ACI_ELEVEL_GROUPDN; + aci_item->aci_ruleType |= ACI_GROUPDN_RULE; + + } else if ( 0 == strncmp ( s, DS_LAS_ROLEDN, 6)) { + + p = strstr ( s, "="); + p--; + if ( strncmp (p, "!=", 2) == 0) + aci_item->aci_type |= ACI_CONTAIN_NOT_ROLEDN; + + p = strstr ( s, "ldap"); + if (p == NULL) { + /* must start with ldap */ + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + p += 8; + if (__aclp__dn_normalize (p, end) == NULL) { + if (s_acestr) slapi_ch_free ( (void **) &s_acestr ); + if (ret_str) slapi_ch_free ( (void **) &ret_str ); + slapi_ch_free ( (void **) &s_aclstr ); + return (NULL); + } + /* check for param rules */ + __aclp_chk_paramRules ( aci_item, p, end ); + + /* XXX need this for roledn ? + if ( aci_item->aci_elevel > ACI_ELEVEL_GROUPDN ) + aci_item->aci_elevel = ACI_ELEVEL_GROUPDN;*/ + aci_item->aci_ruleType |= ACI_ROLEDN_RULE; + } + s = ++end; + s = __aclp__getNextLASRule(aci_item, s, &end); + }/* while */ + + /* get the head of the string */ + acestr = s_acestr; + len = strlen( acestr); + ptr = acestr +len-1; + while (*ptr && *ptr != '\"' && *ptr != ')' ) *ptr-- = ' '; + ptr++; + *ptr = ';'; + + aclutil_str_appened (&ret_str, acestr); + if (s_acestr) { + slapi_ch_free ( (void **) &s_acestr ); + } + s_acestr = NULL; + + if (nextACE) { + s = strstr (nextACE, "allow"); + if (s == NULL) s = strstr (nextACE, "deny"); + if (s == NULL) { + if (nextACE && *nextACE != '\0') + aclutil_str_appened (&ret_str, nextACE); + slapi_ch_free ( (void **) &s_aclstr ); + return (ret_str); + } + acestr = nextACE; + goto normalize_nextACERule; + } + + slapi_ch_free ( (void **) &s_aclstr ); + return (ret_str); +} +/* + * + * acl__getNextLASRule + * Find the next rule. + * + * Returns: + * endOfCurrRule - end of current rule + * nextRule - start of next rule + */ +static char * +__aclp__getNextLASRule (aci_t *aci_item, char *original_str , char **endOfCurrRule) +{ + char *newstr, *word, *next, *start, *end; + char *ruleStart = NULL; + int len, ruleLen; + int in_dn_expr = 0; + + *endOfCurrRule = NULL; + end = start = NULL; + + newstr = slapi_ch_strdup (original_str); + + if ( (strncasecmp(newstr, "allow", 5) == 0) || + (strncasecmp(newstr, "deny", 4) == 0) ) { + word = ldap_utf8strtok_r(newstr, ")", &next); + } + else { + word = ldap_utf8strtok_r(newstr, " ", &next); + } + + /* + * The first word is of no interest -- skip it + * it's allow or deny followed by the rights (<rights>), + * so skip over the rights as well or it's 'and', 'or',.... + */ + + while ( (word = ldap_utf8strtok_r(NULL, " ", &next)) != NULL) { + int got_rule = 0; + int ruleType = 0; + /* + ** The next word must be one of these to be considered + ** a valid rule. + ** This is making me crazy. We might have a case like + ** "((userdn=". strtok is returning me that word. + */ + len = strlen ( word ); + word [len] = '\0'; + + if ( (ruleStart= strstr(word, DS_LAS_USERDNATTR)) != NULL) { + ruleType |= ACI_USERDNATTR_RULE; + ruleLen = strlen ( DS_LAS_USERDNATTR) ; + } else if ( (ruleStart = strstr(word, DS_LAS_USERDN)) != NULL) { + ruleType = ACI_USERDN_RULE; + ruleLen = strlen ( DS_LAS_USERDN); + in_dn_expr = 1; + } else if ( (ruleStart = strstr(word, DS_LAS_GROUPDNATTR)) != NULL) { + ruleType = ACI_GROUPDNATTR_RULE; + ruleLen = strlen ( DS_LAS_GROUPDNATTR) ; + } else if ((ruleStart= strstr(word, DS_LAS_GROUPDN)) != NULL) { + ruleType = ACI_GROUPDN_RULE; + ruleLen = strlen ( DS_LAS_GROUPDN) ; + in_dn_expr = 1; + } else if ((ruleStart = strstr(word, DS_LAS_USERATTR)) != NULL) { + ruleType = ACI_USERATTR_RULE; + ruleLen = strlen ( DS_LAS_USERATTR) ; + } else if ((ruleStart= strstr(word, DS_LAS_ROLEDN)) != NULL) { + ruleType = ACI_ROLEDN_RULE; + ruleLen = strlen ( DS_LAS_ROLEDN); + in_dn_expr = 1; + } else if ((ruleStart= strstr(word, DS_LAS_AUTHMETHOD)) != NULL) { + ruleType = ACI_AUTHMETHOD_RULE; + ruleLen = strlen ( DS_LAS_AUTHMETHOD); + } else if ((ruleStart = strstr(word, ACL_ATTR_IP)) != NULL) { + ruleType = ACI_IP_RULE; + ruleLen = strlen ( ACL_ATTR_IP) ; + } else if ((ruleStart = strstr(word, DS_LAS_TIMEOFDAY)) != NULL) { + ruleType = ACI_TIMEOFDAY_RULE; + ruleLen = strlen ( DS_LAS_TIMEOFDAY) ; + } else if ((ruleStart = strstr(word, DS_LAS_DAYOFWEEK)) != NULL) { + ruleType = ACI_DAYOFWEEK_RULE; + ruleLen = strlen ( DS_LAS_DAYOFWEEK) ; + } else if ((ruleStart = strstr(word, ACL_ATTR_DNS)) != NULL) { + ruleType = ACI_DNS_RULE; + ruleLen = strlen ( ACL_ATTR_DNS) ; + } + /* Here, we've found a space...if we were in in_dn_expr mode + * and we'vve found a closure for that ie.a '"' or a ')' + * eg. "'ldap:///all"' or 'ldap:///all")' then exit in_dn_expr mode. + */ + if ( in_dn_expr && (word[len-1] == '"' || + len>1 && word[len-2] == '"' || + len>2 && word[len-3] == '"')) { + in_dn_expr = 0; + } + + /* + * ruleStart may be NULL as word could be (all) for example. + * this word will just be skipped--we're really waiting for + * userdn or groupdn or... + */ + + if ( ruleStart && ruleType ) { + /* Look in the current word for "=" or else look into + ** the next word -- if none of them are true, then this + ** is not the start of the rule + */ + char *tmpStr = ruleStart + ruleLen; + if ( strchr ( tmpStr, '=') || + ((word = ldap_utf8strtok_r(NULL, " ", &next) ) && + word && ((strncmp ( word, "=", 1) == 0 ) || + (strncmp ( word, "!=",2) ==0) || + (strncmp ( word, ">", 1) == 0 ) || + (strncmp ( word, "<", 1) == 0 ) || + (strncmp ( word, "<", 1) == 0 ) || + (strncmp ( word, "<=",2) ==0 ) || + (strncmp ( word, ">=",2) ==0) || + (strncmp ( word, "=>",2) ==0) || + (strncmp ( word, "=<",2) ==0)) + ) ){ + aci_item->aci_ruleType |= ruleType; + got_rule = 1; + } + } + if ( NULL == start && got_rule ) { + /* + * We've just found a rule start--keep going though because + * we need to return the end of this rule too. + */ + start= ruleStart; + got_rule = 0; + } else { + /* + * Here, we have a candidate for the end of the rule we've found + * (the start of which is currently in start). + * But we need to be sure it really is the end and not a + * "fake end" due to a keyword bbeing embeded in a dn. + */ + if (word && !in_dn_expr && + ((strcasecmp(word, "and") == 0) || + (strcasecmp(word, "or") == 0) || + (strcasecmp(word, "not") == 0) || + (strcasecmp(word, ";") == 0))) { + /* If we have start, then it really is the end */ + word--; + if (start) { + end = word; + break; + } else { + /* We found a fake end, but we've no start so keep going */ + } + } + } + } /* while */ + + + if ( end ) { + /* Found an end to the rule and it's not the last rule */ + len = end - newstr; + end = original_str +len; + while ( (end != original_str) && *end != '\"') end--; + *endOfCurrRule = end; + len = start - newstr; + ruleStart = original_str + len; + } else { + /* Walked off the end of the string so it's the last rule */ + end = original_str + strlen(original_str)-1; + while ( (end != original_str) && *end != '\"') end--; + *endOfCurrRule = end; + } + if ( start ) { + /* Got a rule, fixup the pointer */ + len = start - newstr; + ruleStart = original_str + len; + } + slapi_ch_free ( (void **) &newstr ); + + /* + * Here, ruleStart points to the start of the next rule in original_str. + * end points to the end of this rule. + */ + + return ( ruleStart ); +} +/****************************************************************************** +* +* __aclp__dn_normalize +* +* Normalize the DN INPLACE. This routine is similar to slapi_dn_normalize() +* except various small stuff at the end. +* Normalize until the "end" and not to the end of string. +* +******************************************************************************/ +static char * +__aclp__dn_normalize( char *dn , char *end) +{ + char *d; + + if ((end - dn) < 0) { + return(NULL); + } + + d = slapi_dn_normalize_to_end ( dn, end ); + + /* Do I have the quotes already */ + if (*d != '\"' ) { + /* + ** We are taking care of this situation + ** " ") ". We need to remove the space + ** infront and tack it after the quote like this. + ** "" ) ". + */ + + *d = '\"'; + d++; + while (*d && *d != '\"') *d++ = ' '; + *d = ' '; + } + + return( dn ); +} +/*************************************************************************** +* acl__get_aci_right +* +* Go thru the one acl text str and figure our the rights declared. +* +*****************************************************************************/ +static int +__aclp__get_aci_right (char *str) +{ + + char *sav_str = slapi_ch_strdup(str); + char *t, *tt; + int type = 0; + char *delimiter = ","; + char *val = NULL; + int aclval = 0; + + t = sav_str; + __acl_strip_leading_space( &t ); + + if (*t == '(' ) { + if ((tt = slapi_find_matching_paren(t)) == NULL) { + slapi_ch_free ( (void **) &sav_str ); + return -1; + } else { + t++; /* skip the first character which is ( */ + *tt = '\0'; + } + } else { + slapi_ch_free ( (void **) &sav_str ); + return -1; + } + /* get the tokens separated by "," */ + val = ldap_utf8strtok_r(t,delimiter, &tt); + if (val == NULL ) + { + slapi_ch_free ( (void **) &sav_str ); + return -1; + } + while (val != NULL) + { + /* get the corresponding integer value */ + aclval = get_acl_rights_as_int(val); + if (aclval == -1 ) + { + type = -1; + break; + } + type |= aclval; + val = ldap_utf8strtok_r(NULL,delimiter, &tt); /* get the next token */ + } + + slapi_ch_free ( (void **) &sav_str ); + return type; + +} + +static int get_acl_rights_as_int( char * strValue) +{ + + if (strValue == NULL ) + return -1; + /* First strip out the leading and trailing spaces */ + __acl_strip_leading_space( &strValue ); + __acl_strip_trailing_space( strValue ); + + /* We have to do a strcasecmp (case insensitive cmp) becuase we should return + only if it is exact match. */ + + if (strcasecmp (strValue, "read") == 0 ) + return SLAPI_ACL_READ; + else if (strcasecmp (strValue, "write") == 0 ) + return SLAPI_ACL_WRITE; + else if (strcasecmp (strValue, "search") == 0 ) + return SLAPI_ACL_SEARCH; + else if (strcasecmp (strValue, "compare") == 0 ) + return SLAPI_ACL_COMPARE; + else if (strcasecmp (strValue, "add") == 0 ) + return SLAPI_ACL_ADD; + else if (strcasecmp (strValue, "delete") == 0 ) + return SLAPI_ACL_DELETE; + else if (strcasecmp (strValue, "proxy") == 0 ) + return SLAPI_ACL_PROXY; + else if (strcasecmp (strValue, "selfwrite") == 0 ) + return (SLAPI_ACL_SELF | SLAPI_ACL_WRITE); + else if (strcasecmp (strValue, "all") == 0 ) + return SLAPI_ACL_ALL; + else + return -1; /* error */ +} +/*************************************************************************** +* +* acl_access2str +* +* Convert the access bits into character strings. +* Example: "read, self read" +* +* Input: +* +* int access - The access in bits +* char **rights - rights in chars +* +* Returns: +* NULL - No rights to start with +* right - rights converted. +* +* Error Handling: +* None. +* +**************************************************************************/ +char * +acl_access2str(int access) +{ + + if ( access & SLAPI_ACL_COMPARE ) { + return access_str_compare; + } else if ( access & SLAPI_ACL_SEARCH ) { + return access_str_search; + } else if ( access & SLAPI_ACL_READ ) { + return access_str_read; + } else if ( access & SLAPI_ACL_DELETE) { + return access_str_delete; + } else if ( access & SLAPI_ACL_ADD) { + return access_str_add; + } else if ( (access & SLAPI_ACL_WRITE ) && (access & SLAPI_ACL_SELF)) { + return access_str_selfwrite; + } else if (access & SLAPI_ACL_WRITE ) { + return access_str_write; + } else if (access & SLAPI_ACL_PROXY ) { + return access_str_proxy; + } + + return NULL; +} +/*************************************************************************** +* +* __aclp__init_targetattr +* +* Parse the targetattr string and create a array of attrs. This will +* help us to do evaluation at run time little faster. +* entry. +* Here, also extract any target value filters. +* +* Input: +* aci_t *aci -- The aci item +* char *str -- the targetattr string +* +* Returns: +* ACL_OK - everything ok +* ACL_SYNTAX_ERROR - in case of error. +* +* +***************************************************************************/ +static int +__aclp__init_targetattr (aci_t *aci, char *attr_val) +{ + + int numattr=0; + Targetattr **attrArray; + char *s, *end_attr, *str; + int len; + Targetattr *attr = NULL; + + s = strchr (attr_val, '='); + s++; + __acl_strip_leading_space(&s); + len = strlen(s); + if (*s == '"' && s[len-1] == '"') { + s[len-1] = '\0'; + s++; + } + + str = s; + attrArray = aci->targetAttr; + + if (attrArray[0] != NULL) { + /* + ** That means we are visiting more than once. + ** Syntax error. We have a case like: (targetattr) (targetattr) + */ + return ACL_SYNTAX_ERR; + } + + while (str != 0 && *str != 0) { + + __acl_strip_leading_space(&str); + + if ((end_attr = strstr(str, "||")) != NULL) { + /* skip the two '|' chars */ + auto char *t = end_attr; + LDAP_UTF8INC(end_attr); + LDAP_UTF8INC(end_attr); + *t = 0; + } + __acl_strip_trailing_space(str); + + /* + * Here: + * end_attr points to the next attribute thing. + * + * str points to the current one to be processed and it looks like this: + * rbyrneXXX Watchout is it OK to use : as the speperator ? + * cn + * c*n* + * * + * The attribute goes in the attrTarget list. + * + */ + + + attr = (Targetattr *) slapi_ch_malloc (sizeof (Targetattr)); + memset (attr, 0, sizeof(Targetattr)); + + if (strchr(str, '*')) { + + /* It contains a * so it's something like * or cn* */ + if (strcmp(str, "*" ) != 0) { + char line[100]; + char *lineptr = &line[0]; + char *newline = NULL; + int lenstr = 0; + struct slapi_filter *f = NULL; + + if ((lenstr = strlen(str)) > 91) { /* 100 - 8 for "(attr =%s)" */ + newline = slapi_ch_malloc(lenstr + 9); + lineptr = newline; + } + + attr->attr_type = ACL_ATTR_FILTER; + sprintf (lineptr, "(attr =%s)", str); + f = slapi_str2filter (lineptr); + + if (f == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "__aclp__init_targetattr:Unable to generate filter (%s)\n", lineptr,0,0); + } else { + attr->u.attr_filter = f; + } + + if (newline) slapi_ch_free((void **) &newline); + } else { + attr->attr_type = ACL_ATTR_STAR; + attr->u.attr_str = slapi_ch_strdup (str); + } + + } else { + attr->u.attr_str = slapi_ch_strdup (str); + attr->attr_type = ACL_ATTR_STRING; + } + + + /* + * Add the attr to the targetAttr list + */ + + attrArray[numattr] = attr; + numattr++; + if (!(numattr % ACL_INIT_ATTR_ARRAY)) { + aci->targetAttr = (Targetattr **) slapi_ch_realloc ( + (void *) aci->targetAttr, + (numattr+ACL_INIT_ATTR_ARRAY) * + sizeof(Targetattr *)); + attrArray = aci->targetAttr; + } + + + /* Move on to the next attribute in the list */ + str = end_attr; + + } /* while */ + + /* NULL teminate the list */ + attrArray[numattr] = NULL; + return 0; +} + +void +acl_strcpy_special (char *d, char *s) +{ + for (; *s; LDAP_UTF8INC(s)) { + switch (*s) { + case '.': + case '\\': + case '[': + case ']': + case '*': + case '+': + case '^': + case '$': + *d = '\\'; + LDAP_UTF8INC(d); + /* FALL */ + default: + d += LDAP_UTF8COPY(d, s); + } + } + *d = '\0'; +} +/*************************************************************************** +* +* acl_verify_aci_syntax +* verify if the aci's being added for the entry has a valid syntax or not. +* +* Input: +* Slapi_Entry *e - The Slapi_Entry itself +* char **errbuf; -- error message +* +* Returns: +* -1 (ACL_ERR) - Syntax error +* 0 - No error +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_verify_aci_syntax (Slapi_Entry *e, char **errbuf) +{ + + if (e != NULL) { + Slapi_DN *e_sdn; + int rv; + Slapi_Attr *attr = NULL; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + e_sdn = slapi_entry_get_sdn ( e ); + + slapi_entry_attr_find (e, aci_attr_type, &attr); + if (! attr ) return 0; + + i= slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + rv=acl_verify_syntax ( e_sdn, attrVal); + if ( 0 != rv ) { + aclutil_print_err(rv, e_sdn, attrVal, errbuf); + return ACL_ERR; + } + i = slapi_attr_next_value ( attr, i, &sval ); + } + } + return(0); +} +/*************************************************************************** +* +* acl__verify_syntax +* Called from slapi_acl_check_mods() to verify if the new aci being +* added/replaced has the right syntax or not. +* +* Input: +* Slapi_DN *e_sdn - sdn of the entry +* berval *bval - The berval containg the aci value +* +* Returns: +* return values from acl__parse_aci() +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_verify_syntax(const Slapi_DN *e_sdn, const struct berval *bval) +{ + aci_t *aci_item; + int rv = 0; + char *str; + aci_item = acllist_get_aci_new (); + slapi_sdn_set_ndn_byval ( aci_item->aci_sdn, slapi_sdn_get_ndn ( e_sdn ) ); + + /* make a copy the the string */ + str = slapi_ch_strdup(bval->bv_val); + rv = acl_parse(str, aci_item); + + /* cleanup before you leave ... */ + acllist_free_aci (aci_item); + slapi_ch_free ( (void **) &str ); + return(rv); +} +static void +__aclp_chk_paramRules ( aci_t *aci_item, char *start, char *end) +{ + + size_t len; + char *str; + char *p, *s; + + + len = end - start; + + s = str = (char *) slapi_ch_calloc(1, len + 1); + memcpy ( str, start, len); + while ( (p= strchr ( s, '$')) != NULL) { + p++; /* skip the $ */ + if ( 0 == strncasecmp ( p, "dn", 2)) + aci_item->aci_ruleType |= ACI_PARAM_DNRULE; + else if ( 0 == strncasecmp ( p, "attr", 4)) + aci_item->aci_ruleType |= ACI_PARAM_ATTRRULE; + + s = p; + } + slapi_ch_free ( (void **) &str ); +} + +/* + * Check for an ocurrence of a macro aci in the target. + * value is the normalized target string. + * + * this is something like: + * (target="ldap:///cn=*,ou=people,($dn),o=sun.com") + * + * + * returns 1 if there is a $dn present. + * returns 0 if not. + * returns -1 is syntax error. + * If succes then: + * ACI_TARGET_MACRO_DN is the type. + * type can also include, ACI_TARGET_PATTERN, ACI_TARGET_NOT. + * Also aci_item->aci_macro->match_this is set to be + * cn=*,ou=people,($dn),o=sun.com, to be used later. + * + * . we allow at most one ($dn) in a target. + * . if a "*" accurs with it, it must be the first component and at most + * once. + * . it's ok for ($dn) to occur on it's own in a target, but if it appears in + * a user rule, then it must be in the target. + * + * + * +*/ + +static int +acl_check_for_target_macro( aci_t *aci_item, char *value) +{ + + char *str = NULL; + + str = strstr( value, ACL_TARGET_MACRO_DN_KEY); + + if (str != NULL) { + aci_item->aci_type &= ~ACI_TARGET_DN; + aci_item->aci_type |= ACI_TARGET_MACRO_DN; + aci_item->aci_macro = (aciMacro *)slapi_ch_malloc(sizeof(aciMacro)); + aci_item->aci_macro->match_this = slapi_ch_strdup(value); + aci_item->aci_macro->macro_ptr = strstr( aci_item->aci_macro->match_this, + ACL_TARGET_MACRO_DN_KEY); + return(1); + } + + return(0); +} + +/* Strip trailing spaces from str by writing '\0' into them */ + +static void +__acl_strip_trailing_space( char *str) { + + char *ptr = NULL; + int len = 0; + + if (*str) { + /* ignore trailing whitespace */ + len = strlen(str); + ptr = str+len-1; + while(ldap_utf8isspace(ptr)){ *ptr = '\0'; LDAP_UTF8DEC(ptr); } + } +} + +/* + * Strip leading spaces by resetting str to point to the first + * non-space charater. +*/ + +static void +__acl_strip_leading_space( char **str) { + + char *tmp_ptr = NULL; + + tmp_ptr = *str; + while ( *tmp_ptr && ldap_utf8isspace( tmp_ptr ) ) LDAP_UTF8INC(tmp_ptr); + *str = tmp_ptr; +} + + +/* + * str is a string containing an LDAP filter. + * Trim off enclosing quotes and enclosing + * superfluous brackets. + * The result is duped so it can be kept. +*/ + +static char * +__acl_trim_filterstr( char * str ) { + + char *tmpstr; + int len; + char *end; + + tmpstr = str; + + /* If the last char is a "," take it out */ + + len = strlen (tmpstr); + if (len>0 && (tmpstr[len-1] == ',')) { + tmpstr [len-1] = '\0'; + } + + + /* Does it have quotes around it */ + len = strlen (tmpstr); + if (*tmpstr == '"' && tmpstr[len-1] == '"') { + tmpstr [len-1] = '\0'; + tmpstr++; + } + + str = tmpstr; + + /* If we have a filter like + ** (((&(...) (...)))), we need to get rid of the + ** multiple parens or slapi_str2filter will not + ** evaluate properly. Need to package like + ** (filter ). probably I should fix str2filter + ** code. + */ + + while (*tmpstr++ == '(' && *tmpstr == '(') { + if ((end = slapi_find_matching_paren( str )) != NULL) { + *end = '\0'; + str++; + } + } + + return( slapi_ch_strdup(str)); +} + +/* + * Here str points to a targetattrfilters thing which looks tlike this: + * + * targetattrfilters="add=attr1:F1 && attr2:F2 ... && attrn:Fn, + * del=attr1:F1 && attr2:F2... && attrn:Fn") + * + * + */ + +static int __acl__init_targetattrfilters( aci_t *aci, char *input_str) { + + int numattr=0; + char *s, *str; + int len; + char *addlistptr = NULL; + char *dellistptr = NULL; + + if (aci->targetAttrAddFilters != NULL || + aci->targetAttrDelFilters != NULL) { + + /* + ** That means we are visiting more than once. + ** Syntax error. + ** We have a case like: (targetattrfilters) (targetattrfilters) + */ + + return ACL_SYNTAX_ERR; + } + + /* First, skip the "targetattrfilters" */ + + s = strchr (input_str, '='); + s++; /* skip the = */ + __acl_strip_leading_space(&s); /* skip to next significant character */ + len = strlen(s); /* Knock off the " and trailing ) */ + if (*s == '"' && s[len-1] == '"') { + s[len-1] = '\0'; + s++; /* skip the first " */ + } else { /* No matching quotes */ + + return (ACL_SYNTAX_ERR); + } + + str = s; + + /* + * Here str looks like add=attr1:F1...attrn:Fn, + * del=attr1:F1...attrn:Fn + * + * extract the add and del filter lists and process each one + * in turn. + */ + + s = strchr (str, '='); + *s = '\0'; + s++; /* skip the = */ + __acl_strip_leading_space(&s); /* start of the first filter list */ + + + /* + * Now str is add or del + * s points to the first filter list. + */ + + if (strcmp(str, "add") == 0) { + aci->aci_type |= ACI_TARGET_ATTR_ADD_FILTERS; + addlistptr = s; + + /* Now isolate the first filter list. */ + if ((str = strstr(s , "del=")) || ((str = strstr(s , "del ="))) ) { + str--; + *str = '\0'; + *str++; + } + + + } else if (strcmp(str, "del") == 0) { + aci->aci_type |= ACI_TARGET_ATTR_DEL_FILTERS; + dellistptr = s; + + /* Now isolate the first filter list. */ + if ((str = strstr(s , "add=")) || ((str = strstr(s , "add ="))) ) { + str--; + *str = '\0'; + *str++; + } + } else { + return(ACL_SYNTAX_ERR); + } + + __acl_strip_trailing_space(s); + + /* + * Here, we have isolated the first filter list. + * There may be a second one. + * Now, str points to the start of the + * string that contains the second filter list. + * If there is none then str is NULL. + */ + + if (str != NULL ){ + + __acl_strip_leading_space(&str); + s = strchr (str, '='); + *s = '\0'; + s++; + __acl_strip_trailing_space(str); + __acl_strip_leading_space(&s); + + + /* + * s points to the start of the second filter list. + * str is add or del + */ + + if (aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) { + + if (strcmp(str, "del") == 0) { + aci->aci_type |= ACI_TARGET_ATTR_DEL_FILTERS; + dellistptr = s; + } else { + return(ACL_SYNTAX_ERR); + } + } else if ( aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) { + if (strcmp(str, "add") == 0) { + aci->aci_type |= ACI_TARGET_ATTR_ADD_FILTERS; + addlistptr = s; + } else { + return(ACL_SYNTAX_ERR); + } + } + } + + /* + * addlistptr points to the add filter list. + * dellistptr points to the del filter list. + * In both cases the strings have been leading and trailing space + * stripped. + * Either may be NULL. + */ + + if (process_filter_list( &aci->targetAttrAddFilters, addlistptr) + == ACL_SYNTAX_ERR) { + return( ACL_SYNTAX_ERR); + } + + if (process_filter_list( &aci->targetAttrDelFilters, dellistptr) + == ACL_SYNTAX_ERR) { + return( ACL_SYNTAX_ERR); + } + + return(0); + +} + +/* + * We have a list of filters that looks like this: + * attr1:F1 &&....attrn:Fn + * + * We need to put each component into a targetattrfilter component of + * the array. + * +*/ + +static int process_filter_list( Targetattrfilter ***input_attrFilterArray, + char * input_str) { + + char *str, *end_attr, *tmp_attr; + Targetattrfilter *attrfilter = NULL; + int numattr=0; + Targetattrfilter **attrFilterArray = NULL; + + str = input_str; + + while (str != 0 && *str != 0) { + + if ((end_attr = strstr(str, "&&")) != NULL) { + /* skip the two '|' chars */ + auto char *t = end_attr; + LDAP_UTF8INC(end_attr); + LDAP_UTF8INC(end_attr); + *t = 0; + } + __acl_strip_trailing_space(str); + __acl_strip_leading_space(&str); + + /* + * Here: + * end_attr points to the next attribute thing. + * + * str points to the current one to be processed and it looks like + * this: + * + * attr1:F1 + * + */ + + attrfilter = (Targetattrfilter *) slapi_ch_malloc (sizeof (Targetattrfilter)); + memset (attrfilter, 0, sizeof(Targetattrfilter)); + + if ((tmp_attr = strstr( str,":")) != NULL) { + + if ( __acl_init_targetattrfilter( attrfilter, str ) != 0 ) { + slapi_ch_free((void**)&attrfilter); + return(ACL_SYNTAX_ERR); + } + } else { + slapi_ch_free((void**)&attrfilter); + return(ACL_SYNTAX_ERR); + } + + + /* + * Add the attrfilte to the targetAttrFilter list + */ + + + attrFilterArray = (Targetattrfilter **) slapi_ch_realloc ( + (void *) attrFilterArray, + ((numattr+1)*sizeof(Targetattrfilter *)) ); + attrFilterArray[numattr] = attrfilter; + numattr++; + + /* Move on to the next attribute in the list */ + str = end_attr; + + }/* while */ + + /* NULL terminate the list */ + + attrFilterArray = (Targetattrfilter **) slapi_ch_realloc ( + (void *) attrFilterArray, + ((numattr+1)*sizeof(Targetattrfilter *)) ); + attrFilterArray[numattr] = NULL; + + *input_attrFilterArray = attrFilterArray; + return 0; + +} + +/* + * Take str and put it into the attrfilter component. + * + * str looks as follows: attr1:F1 + * + * It has had leading and trailing space stripped. +*/ + +static int __acl_init_targetattrfilter( Targetattrfilter *attrfilter, + char *str ) { + + char *tmp_ptr, *s, *filter_ptr; + Slapi_Filter *f = NULL; + + s = str; + + /* First grab the attribute name */ + + if ( (tmp_ptr = strstr( str, ":")) == NULL ) { + /* No :, syntax error */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Bad targetattrfilter %s:%s\n", + str,"Expecting \":\"",0); + + return(ACL_SYNTAX_ERR); + } + *tmp_ptr = '\0'; + LDAP_UTF8INC(tmp_ptr); + + __acl_strip_trailing_space(s); + + /* s should be the attribute name-make sure it's non-empty. */ + + if ( *s == '\0' ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "No attribute name in targattrfilters\n", + 0,0); + return(ACL_SYNTAX_ERR); + } + + attrfilter->attr_str = slapi_ch_strdup (s); + + /* Now grab the filter */ + + filter_ptr = tmp_ptr; + __acl_strip_leading_space(&filter_ptr); + __acl_strip_trailing_space(filter_ptr); + + /* trim dups the string, so we need to free it later if it's not kept. */ + tmp_ptr = __acl_trim_filterstr(filter_ptr); + + if ((f = slapi_str2filter(tmp_ptr)) == NULL) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Bad targetattr filter for attribute %s:%s\n", + attrfilter->attr_str,tmp_ptr,0); + slapi_ch_free( (void **) &attrfilter->attr_str); + slapi_ch_free( (void **) &tmp_ptr); + return(ACL_SYNTAX_ERR); + } + + /* + * Here verify that the named attribute is the only one + * that appears in the filter. + */ + + if (acl_verify_exactly_one_attribute( attrfilter->attr_str, f) != + SLAPI_FILTER_SCAN_NOMORE) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Exactly one attribute type per filter allowed in targattrfilters (%s)\n", + attrfilter->attr_str, 0); + slapi_ch_free( (void **) &attrfilter->attr_str); + slapi_ch_free( (void **) &tmp_ptr); + slapi_filter_free( f, 1 ); + return(ACL_SYNTAX_ERR); + } + + /* free the tmp_ptr */ + slapi_ch_free( (void **) &tmp_ptr); + attrfilter->filterStr = slapi_ch_strdup (filter_ptr); + attrfilter->filter = f; + + return(LDAP_SUCCESS); +} + +/* + * Returns 0 if attr_name is the only attribute name to + * appear in original_filter AND it appears at least once. + * Otherwise returns STOP_FILTER_SCAN. +*/ + +static int acl_verify_exactly_one_attribute( char *attr_name, + Slapi_Filter *original_filter) { + int error_code; + + return( slapi_filter_apply( original_filter, type_compare, + (void *)attr_name, &error_code)); + +} + +static int type_compare( Slapi_Filter *f, void *arg) { + + /* Compare only the base names: eg cn and cn;lang-eb will be the same. */ + + char *t = (char *)arg; + char *filter_type; + int rc = SLAPI_FILTER_SCAN_STOP; + + if (slapi_filter_get_attribute_type( f, &filter_type) == 0) { + t = slapi_attr_syntax_normalize(t); + filter_type = slapi_attr_syntax_normalize(filter_type); + + if (slapi_attr_type_cmp(filter_type, t, 1) == 0) { + rc = SLAPI_FILTER_SCAN_CONTINUE; + } + + slapi_ch_free( (void **)&t ); + slapi_ch_free( (void **)&filter_type ); + } + + return rc; +} |