diff options
Diffstat (limited to 'ldap/servers/plugins/acl')
-rw-r--r-- | ldap/servers/plugins/acl/ACL-Notes | 215 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/Makefile | 96 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/acl.c | 4118 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/acl.h | 867 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/acl_ext.c | 968 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/aclanom.c | 536 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/acldllmain.c | 128 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/acleffectiverights.c | 674 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/aclgroup.c | 442 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/aclinit.c | 537 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/acllas.c | 3848 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/acllist.c | 940 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/aclparse.c | 1928 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/aclplugin.c | 355 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/aclproxy.c | 195 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/aclutil.c | 1475 | ||||
-rw-r--r-- | ldap/servers/plugins/acl/libacl.def | 16 |
17 files changed, 17338 insertions, 0 deletions
diff --git a/ldap/servers/plugins/acl/ACL-Notes b/ldap/servers/plugins/acl/ACL-Notes new file mode 100644 index 00000000..e275c967 --- /dev/null +++ b/ldap/servers/plugins/acl/ACL-Notes @@ -0,0 +1,215 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# + + +Date What ? +=================================== +10/15/98 - Created the ACL plugin + - Created a new file aclplugin.c and split the old + acl.c to acl.c & aclparse.c files. + - Merged changes made upt 4.0B2 +10/21/98 - Added USERATTR rule. + + +02/01/99 - Cleanup needed to be done in 5.0 to make it a real plugin +===================================================================================== +1. Do not use slap.h but use slapi-plugin.h. This will require + some work. Work involves + 1) Making the ACLCB an extensible object of CONN struct + 2) Remove reference of Connection & operation struct + 3) Need slapi plugin apis to get the IP and DNS so that + we can evaluate it in the LASes. + 4) Need new option to get values of conn , op & pb stuct like + cert, authtype, + +2. Make ACLPB hang from the Operation struct instead of the PBlock. +3. Make ACLCB an extensible object of CONN struct and remove any reference + about acl private info. + +4. I implemented the Userattr rule before even deciding if we need in 5.0 + or not. I think it is useful. The documents those were based on are + in http://jazz/users/prasanta/acl_manage_filter + +5. Move acllas_dn_parent to the libslapd. This is duplicated code and is + BAAAD. + +6. Use the new normalized dn code so that we don't have to it over and over again. + We have to very careful ins slapi_access_allowed() as we keep the dn around and + free it later ( we can use dn by ref ). + +7. Merge from DS4.1 ( proxy auth) to DS 5.0. + +8. Miscs + a) can we use the SDK URL parsing code ? + b) Merge teh printing routines ( it's all over ). + +My estimate for doing the above cleanup will require anywhere between 5 to 8 days. +Run the ACL tests after all the changes -- that is a MUST. +=============================== +04/28/99 + + -- All the work descibed above is done. + -- Also + a) Created a Pool pf ACLPB one of which is grabed at the init time. + b) Created a global lockarary which takes care of the concurreny issue between + aclpb & aclcb + c) Fixed plugin init. + + +I think the userattr rule should be made generic + + useAttr = "attrName#Type" + + <Type> :== DN | GROUP | ROLE | URL | <value> + <value> :== < any printable String> + +Example: + userAttr = "manager#DN" --- similar to userdnattr + userAttr = "owner#GROUP" --- similar to groupdnattr + userAttr = "attr#ROLE" --- The value of attr contains a role definition + userAttr = "myattr#URL" --- The value contains a URL or filter + userAttr = "OU#Directory Server" + --- In this case the client's OU and the + resource entry's OU must have + "Directory Server" value. + + This way we can get rid of userdnattr and groupdnattr and accomplish a + lot with a single rule. + +At this point, we are done with the changes and waiting for what needs to be +done in 5.0. +================================= +06/01/1999 + -- Split the code into smaller modules + ( aclanom, aclgroup, aclinit, ...) + --- The ACLs are read and kept in a AVL tree. + --- Few bugs fixed in the acl_scan_match code. + +================================================ +07/02/99 + + -- Added support for parameterized bind rules. + -- Added support for caching of ATTR rules using recompute.S + + What's left for 5.0 + ------------------- + 1. Support for roles + 2. Re-architect user/group cache + 3. startup in multiple threads ( low priority) + 4. look at add/delete/modrdn operations. + 5. cleanup: + - revist all the debug statements + - new tests etc. + 6. UI work + +============ +commit:14/12/99 rbyrne + +. Added targattrfilters keyword for value based acls. + Required also slapi_filter_apply(), slapi_get_attribute_type() + and slapi_attr_syntax_normalize() in slapd (filter.c and attrsyntax.c). +. Memory leak fix in acl.c for PListInit() call--see comments in code. +. made access an int on it's own to give room for expansion + (see aci_access and aclpb_access) +. files: ACL-Notes, acl.c acl.h acl-ext.c aclanom.c acllas.c acllist.c aclparse.c aclutil.c slapd/attrsyntax.c slapd/slapi-plugin.h slapd/filter.c slapd/libslapd.def + +=== +commit: Mon 20th Dec 199 +. aclparse.c: add proxy back to acl_access2str +. filter.c: get_filter() does not recurse anymore--get_fitler_internal(), get_filter_list() +do the recursion...this way testing for ldapsubentry works. +. aclinit.c: now have filter (|(aci=*)(objectclass=ldapsubentry)) in +aclinit_search_and_insert_aci(). This means that when slapi_search_internal_callback() +stops returning subentries by default, we will still get them as we have the correct filter. + +=== +commit: 12/01/2000: +. aclplugin.c: fix for proxyauth bug in aclplugin_preop_search() and +acl_plugin_preop_modify()--the proxy_dn and dn were swapped. +. acl_ext.c: Also, when we PListAssignValue() on DS_ATTR_USERDN in acl_init_aclpb(), +we should pass it a dn from aclpb_sdn, NOT the dn passed into acl_init_aclpb() which +gets freed after the call to acl_init_acpb(). JAlso here need to be careful thatif dn contains NULL that we indicate this in aclpb_sdn by setting dn to a non-NULL empty string ("") which the code takes to be anon. +. checked that none of the PList objects (DS_PROP_ACLPB, DS_ATTR_USERDN, DS_ATTR_ENTRY) have mem leak problems. +. acl.c, acllas.c, aclproxy.c: removed some #ifdef 0 and comments--tidy up but +no code changes. +. acl_ext.c: in acl__done_aclpb() we need to PListDleteProp() on ACL_ATTR_IP +and ACL_ATTR_DNS. This is because if LASIpEval/ACL_GetAttribute() and +LASDnsEval/ACL_GetAttribute() see that these properties exist, they do +not bother calling the respective Getter() function. So, everytime +the aclpb is reused and ip or dns eval is required, the old value is used ( +or whatever hjappens to be in the memory.). Tested--works fine now with ip and dns keywords. ALso tested that when the same user tries an a non-allowed machine he is not allowed by accident (as he was before). +. in schema.c/oc_find(): normalize the objectclass name before looking for it. Otherwise +if there's a trailing space in the oc name, you won't dfind it. + +=== +commit: + +. aclparse.c: fix for syntax.ksh tp6 test: if there is no "version" in an aci item, reject it. +. acllas.c: in DS_UserDnEval() now call slapi_normalize_dn() when comparing param strings and + ordinary dns. +. acl_ext.c: when seeting DS_USER_DN_ATTR, get the ndn, the normalized form. + +==== +commit: 7/02/2000 +anom profile and groupdn != don't work together! Bug 381830 in 4.X +. acl.h: new bit in aci_type to mark as below. +. aclparse.c: mark an aci if it's like deny() groupdn != blah +. aclanom.c: if marked like that cancel anom profile (just like userdn !=) +== +. removed these for the mo... +commit: +. acllas.c: now get the vattrs via slapi_vattr_merge_copy() when testing the client entry. +. vattr.c: assign i the length of the list:i = type_context.list_length; +. entry.c: slapi_entry_add_valueset() + +== + +commit: 03/03/2000 +. support for roledn in acis. +=== +. acllist: in slapi_sdn_free(&aciListHead->acic_sdn); gbeelato's mem leak fix. +commited + +===== + +committed: 17/008/00 +. support for $dn: aclutil.c, aclparse.c, acllist.c, acllas.c, acl.c, acl.h +. acl_ext.c:Make sure aclpb_search_base is initialized to NULL in aclpb__malloc() +. acl.c: set_result_status: wrong bit masks were being used in a_eval->attrEval_s_astatus etc. + acl__attr_cached_result(): in the attr==NULL case, need to test for potential +"recompute" case of attribute--this happens if it's a param or attr style aci. + +======== +commited +Support for dynamic backends: +. acllist.c, aclinit.c, libslapd.def, control.c, slapi-plugin.h: + acl_be_state_change_fnc(), slapi_build_control_from_berval() etc. +. aclanom.c: logical error in aclanom_match_profile() was causing misctest4 to fail. +. acl_ext.c:fix mem leak by calling acl_clean_aclEval_control() in acl_ext_conn_desctructor() +. +=== +committed:24 Aug 2000 +now SLAPI_ACL_ALL (allow(all)) does NOT include proxy right + +== +committed: 30 Aug 2000 +. acl.c: new print_access_control_Summary() routine to display final acl status. Gets the proxy + stuff right too. + in acl__resource_match_aci() always test the TARGET_FILTER case, the old cod ethere was wrong. +== +. add support for macros to userdn ldapurl keyword. + + +== +Committed: +. Sep 07 2000: Support for $attr in macros. +. Sep 15 2000: Support for aci macros in targetfilter keyword. +. Sep 18 2000: improve ret code handling in __aclinit_handler--stops spurious error message. + + +--eof diff --git a/ldap/servers/plugins/acl/Makefile b/ldap/servers/plugins/acl/Makefile new file mode 100644 index 00000000..bdfe2dc0 --- /dev/null +++ b/ldap/servers/plugins/acl/Makefile @@ -0,0 +1,96 @@ +# +# BEGIN COPYRIGHT BLOCK +# Copyright 2001 Sun Microsystems, Inc. +# Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# GNU Makefile for Directory Server acl-plugin.so acl plugins +# + +LDAP_SRC = ../../.. +MCOM_ROOT = ../../../../.. + +NOSTDCLEAN=true # don't let nsconfig.mk define target clean +NOSTDSTRIP=true # don't let nsconfig.mk define target strip +NSPR20=true # probably should be defined somewhere else (not sure where) + +OBJDEST = $(OBJDIR)/lib/libacl +LIBDIR = $(LIB_RELDIR) + +include $(MCOM_ROOT)/ldapserver/nsconfig.mk +include $(LDAP_SRC)/nsldap.mk + +# ACL plugin depends on libadminutil +MCC_INCLUDE += $(ADMINUTIL_INCLUDE) + +ifeq ($(ARCH), WINNT) +DEF_FILE:=./libacl.def +endif + +CFLAGS+=$(SLCFLAGS) + +INCLUDES += -I$(LDAP_SRC)/servers/slapd -I$(ACLINC) + +ACL_OBJS= acl.o acllas.o aclutil.o aclplugin.o aclparse.o acl_ext.o aclproxy.o \ + aclinit.o aclgroup.o aclanom.o acllist.o acleffectiverights.o + +OBJS = $(addprefix $(OBJDEST)/, $(ACL_OBJS)) + +ifeq ($(ARCH), WINNT) +LIBACL_DLL_OBJ = $(addprefix $(OBJDEST)/, acldllmain.o) +endif + +LIBACL= $(addprefix $(LIBDIR)/, $(ACL_DLL).$(DLL_SUFFIX)) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPD) $(NSPRLINK) $(LDAP_LIBAVL) $(LDAP_SDK_LIBLDAP_DLL) +endif + +# ACL plugin depends on libadminutil (through libns-httpd) +EXTRA_LIBS_DEP += $(NSHTTPD_DEP) $(ADMINUTIL_DEP) $(DBM_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(DBMLINK) + +ifeq ($(ARCH), WINNT) +EXTRA_LIBS_DEP += $(LIBACCESS_DEP) +EXTRA_LIBS += $(LIBACCESS) +endif + +ifeq ($(ARCH), WINNT) +DLL_LDFLAGS += -def:"./libacl.def" +endif # WINNT + +ifeq ($(ARCH), AIX) +EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) +EXTRA_LIBS += $(LIBSLAPDLINK) $(NSPRLINK) $(LDAP_LIBAVL) $(LDAP_SDK_LIBLDAP_DLL) +EXTRA_LIBS += $(DLL_EXTRA_LIBS) +LD=ld +endif + +ifeq ($(ARCH), HPUX) +EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP) +EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK) +endif + +clientSDK: + +all: $(OBJDEST) $(LIBDIR) $(LIBACL) + +$(LIBACL): $(OBJS) $(LIBACL_DLL_OBJ) $(DEF_FILE) + $(LINK_DLL) $(LIBACL_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS) + +veryclean: clean + +clean: + $(RM) $(OBJS) +ifeq ($(ARCH), WINNT) + $(RM) $(LIBACL_DLL_OBJ) +endif + $(RM) $(LIBACL) + +$(OBJDEST): + $(MKDIR) $(OBJDEST) diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c new file mode 100644 index 00000000..8dfbd52a --- /dev/null +++ b/ldap/servers/plugins/acl/acl.c @@ -0,0 +1,4118 @@ +/** 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" + +/**************************************************************************** +* +* acl.c +* +* +* This file contains the functions related to Access Control List (ACL) +* checking. The ACL checking is based on the ONE ACL design implemented +* in the Web server 2.0. For more information on the ACL design look +* into the barracuda home page. +* +* +******************************************************************************/ + + +/****************************************************************************/ +/* Globals. Must be protected by Mutex. */ +/****************************************************************************/ +/* Signatures to see if things have changed */ +static short acl_signature = 0; + +/****************************************************************************/ +/* Defines, Constants, ande Declarations */ +/****************************************************************************/ +static char *ds_map_generic[2] = { NULL, NULL }; + +/****************************************************************************/ +/* prototypes */ +/****************************************************************************/ +static int acl__resource_match_aci(struct acl_pblock *aclpb, aci_t *aci , + int skip_attrEval, int *a_matched); +static acl__TestRights(Acl_PBlock *aclpb,int access, char **right, + char ** map_generic, aclResultReason_t *result_reason); +static int acl__scan_for_acis(struct acl_pblock *aclpb, int *err); +static void acl__reset_cached_result (struct acl_pblock *aclpb ); +static int acl__scan_match_handles ( struct acl_pblock *aclpb, int type); +static int acl__attr_cached_result (struct acl_pblock *aclpb, char *attr, int access ); +static int acl__match_handlesFromCache (struct acl_pblock *aclpb, char *attr, int access); +static int acl__get_attrEval ( struct acl_pblock *aclpb, char *attr ); +static int acl__config_get_readonly (); +static int acl__recompute_acl (Acl_PBlock *aclpb, AclAttrEval *a_eval, + int access, int aciIndex); +static void __acl_set_aclIndex_inResult ( Acl_PBlock *aclpb, + int access, int index ); +static int acl__make_filter_test_entry ( Slapi_Entry **entry, + char *attr_type, struct berval *attr_val); +static int acl__test_filter ( Slapi_Entry *entry, struct slapi_filter *f, + int filter_sense); +static void print_access_control_summary( char * source, + int ret_val, char *clientDn, + struct acl_pblock *aclpb, + char *right, + char *attr, + const char *edn, + aclResultReason_t *acl_reason); +static int check_rdn_access( Slapi_PBlock *pb,Slapi_Entry *e, char * newrdn, + int access); + + +/* + * Check the rdn permissions for this entry: + * require: write access to the entry, write (add) access to the new + * naming attribute, write (del) access to the old naming attribute if + * deleteoldrdn set. + * + * Valid only for the modrdn operation. +*/ +int +acl_access_allowed_modrdn( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* requested access rights */ + ) +{ + int retCode ; + char *newrdn, *oldrdn; + int deleteoldrdn = 0; + + /* + * First check write permission on the entry--this is actually + * specially for modrdn. + */ + retCode = acl_access_allowed ( pb, e, NULL /* attr */, NULL /* val */, + SLAPI_ACL_WRITE); + + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to entry not allowed\n"); + return(retCode); + } + + /* Now get the new rdn attribute name and value */ + + slapi_pblock_get( pb, SLAPI_MODRDN_TARGET, &oldrdn ); + slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn ); + + /* Check can add the new naming attribute */ + retCode = check_rdn_access( pb, e, newrdn, ACLPB_SLAPI_ACL_WRITE_ADD) ; + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to add new naming attribute not allowed\n"); + return(retCode); + } + + /* Check can delete the new naming attribute--if required */ + slapi_pblock_get( pb, SLAPI_MODRDN_DELOLDRDN, &deleteoldrdn ); + if ( deleteoldrdn ) { + retCode = check_rdn_access( pb, e, oldrdn, ACLPB_SLAPI_ACL_WRITE_DEL) ; + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to delete old naming attribute not allowed\n"); + return(retCode); + } + } + + return(retCode); + +} +/* + * Test if have access to make the first rdn of dn in entry e. +*/ + +static int check_rdn_access( Slapi_PBlock *pb, Slapi_Entry *e, char *dn, + int access) { + + char **dns; + char **rdns; + int retCode = LDAP_INSUFFICIENT_ACCESS; + int i; + + if ( (dns = ldap_explode_dn( dn, 0 )) != NULL ) { + + if ( (rdns = ldap_explode_rdn( dns[0], 0 )) != NULL ) { + + for ( i = 0; rdns[i] != NULL; i++ ) { + char *type; + struct berval bv; + + if ( slapi_rdn2typeval( rdns[i], &type, &bv ) != 0 ) { + char ebuf[ BUFSIZ ]; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn: rdn2typeval (%s) failed\n", + escape_string( rdns[i], ebuf )); + retCode = LDAP_INSUFFICIENT_ACCESS; + break; + } else { + if ( (retCode = acl_access_allowed ( pb, e, type /* attr */, + &bv /* val */, + access)) != LDAP_SUCCESS) { + break; + } + } + } + ldap_value_free( rdns ); + } + ldap_value_free( dns ); + } + + return(retCode); +} + +/*************************************************************************** +* +* acl_access_allowed +* Determines if access to the resource is allowed or not. +* +* Input: +* +* +* Returns: +* +* Returns success/Denied/error condition +* +* LDAP_SUCCESS -- access allowed +* LDAP_INSUFFICIENT_ACCESS -- access denied +* +* Errors returned: +* +* Some of the definition of the return values used copied from +* "ldap.h" for convienience. +* LDAP_OPERATIONS_ERROR +* LDAP_PROTOCOL_ERROR +* LDAP_UNWILLING_TO_PERFORM +* +* +* Error Handling: +* Returned error code. +**************************************************************************/ +int +acl_access_allowed( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* requested access rights */ + ) +{ + char *n_edn; /* Normalized DN of the entry */ + int rv; + int err; + int ret_val; + char *right; + int num_handle; + struct acl_pblock *aclpb = NULL; + AclAttrEval *c_attrEval = NULL; + int got_reader_locked = 0; + int deallocate_attrEval = 0; + char ebuf [ BUFSIZ ]; + char *clientDn; + Slapi_DN *e_sdn; + Slapi_Operation *op = NULL; + aclResultReason_t decision_reason; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + slapi_pblock_get(pb, SLAPI_OPERATION, &op); /* for logging */ + + TNF_PROBE_1_DEBUG(acl_access_allowed_start,"ACL","", + tnf_int,access,access); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + /** + * First, if the acl private write/delete on attribute right + * is requested, turn this into SLAPI_ACL_WRITE + * and record the original value. + * Need to make sure that these rights do not clash with the SLAPI + * public rights. This should be easy as the requested rights + * in the aclpb are stored in the bottom byte of aclpb_res_type, + * so if we keep the ACL private bits here too we make sure + * not to clash. + * + */ + + if ( access & (ACLPB_SLAPI_ACL_WRITE_ADD | ACLPB_SLAPI_ACL_WRITE_DEL) ) { + access |= SLAPI_ACL_WRITE; + } + + n_edn = slapi_entry_get_ndn ( e ); + e_sdn = slapi_entry_get_sdn ( e ); + + /* Check if this is a write operation and the database is readonly */ + /* No one, even the rootdn should be allowed to write to the database */ + /* jcm: ReadOnly only applies to the public backends, the private ones */ + /* (the DSEs) should still be writable for configuration. */ + if ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE )) { + int be_readonly, privateBackend; + Slapi_Backend *be; + + slapi_pblock_get ( pb, SLAPI_BE_READONLY, &be_readonly ); + slapi_pblock_get ( pb, SLAPI_BACKEND, &be ); + privateBackend = slapi_be_private ( be ); + + if ( !privateBackend && (be_readonly || slapi_config_get_readonly () )){ + slapi_log_error (loglevel, plugin_name, + "conn=%d op=%d (main): Deny %s on entry(%s)" + ": readonly backend\n", + op->o_connid, op->o_opid, + acl_access2str(access), + escape_string_with_punctuation(n_edn,ebuf)); + return LDAP_UNWILLING_TO_PERFORM; + } + } + + /* Check for things we need to skip */ + TNF_PROBE_0_DEBUG(acl_skipaccess_start,"ACL",""); + if ( acl_skip_access_check ( pb, e )) { + slapi_log_error (loglevel, plugin_name, + "conn=%d op=%d (main): Allow %s on entry(%s)" + ": root user\n", + op->o_connid, op->o_opid, + acl_access2str(access), + escape_string_with_punctuation(n_edn,ebuf)); + return(LDAP_SUCCESS); + } + TNF_PROBE_0_DEBUG(acl_skipaccess_end,"ACL",""); + + + /* Get the bindDN */ + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn ); + + /* get the right acl pblock to work with */ + if ( access & SLAPI_ACL_PROXY ) + aclpb = acl_get_aclpb ( pb, ACLPB_PROXYDN_PBLOCK ); + else + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 1 \n" ); + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + /* check if aclpb is initialized or not */ + TNF_PROBE_0_DEBUG(acl_aclpbinit_start,"ACL",""); + acl_init_aclpb ( pb, aclpb, clientDn, 0 ); + TNF_PROBE_0_DEBUG(acl_aclpbinit_end,"ACL",""); + + + /* Here we mean if "I am trying to add/delete "myself" ? " */ + if (val && (access & SLAPI_ACL_WRITE) && (val->bv_len > 0) ) { + /* should use slapi_sdn_compare() but that'a an extra malloc/free */ + + char *dn_val_to_write = + slapi_dn_normalize(slapi_ch_strdup(val->bv_val)); + + if ( aclpb->aclpb_authorization_sdn && + slapi_utf8casecmp((ACLUCHP)dn_val_to_write, (ACLUCHP) + slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn)) == 0) { + access |= SLAPI_ACL_SELF; + } + + slapi_ch_free( (void **)&dn_val_to_write); + } + + /* Convert access to string of rights eg SLAPI_ACL_ADD->"add". */ + if ((right= acl_access2str(access)) == NULL) { + /* ERROR: unknown rights */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl_access_allowed unknown rights:%d\n", access); + + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + + /* + * Am I a anonymous dude ? then we can use our anonymous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + TNF_PROBE_0_DEBUG(acl_anon_test_start,"ACL",""); + if ( (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ )) && + (clientDn && *clientDn == '\0')) { + aclanom_get_suffix_info(e, aclpb); + ret_val = aclanom_match_profile ( pb, aclpb, e, attr, access ); + if (ret_val != -1 ) { + if (ret_val == LDAP_SUCCESS ) { + decision_reason.reason = ACL_REASON_ANON_ALLOWED; + } else if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + decision_reason.reason = ACL_REASON_ANON_DENIED; + } + goto cleanup_and_ret; + } + } + TNF_PROBE_0_DEBUG(acl_anon_test_end,"ACL",""); + + /* copy the value into the aclpb for later checking by the value acl code */ + + aclpb->aclpb_curr_attrVal = val; + + if (!(aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) && + (access & SLAPI_ACL_SEARCH)) { + /* We are evaluating SEARCH right for the entry. After that + ** we will eval the READ right. We need to refresh the + ** list of acls selected for evaluation for the entry. + ** Initialize the array so that we indicate nothing has been + ** selected. + */ + aclpb->aclpb_handles_index[0] = -1; + /* access is not allowed on entry for search -- it's for + ** read only. + */ + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_ON_ENTRY; + } + + /* set that this is a new entry */ + aclpb->aclpb_res_type |= ACLPB_NEW_ENTRY; + aclpb->aclpb_access = 0; + aclpb->aclpb_access |= access; + + /* + * stub the Slapi_Entry info first time and only it has changed + * or if the pblock is a psearch pblock--in this case the lifetime + * of entries associated with psearches is such that we cannot cache + * pointers to them--we must always start afresh (see psearch.c). + */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op); + if ( operation_is_flag_set(op, OP_FLAG_PS) || + (aclpb->aclpb_curr_entry_sdn == NULL) || + (slapi_sdn_compare ( aclpb->aclpb_curr_entry_sdn, e_sdn) != 0)) { + + TNF_PROBE_0_DEBUG(acl_entry_first_touch_start,"ACL",""); + + slapi_log_error(loglevel, plugin_name, + "#### conn=%d op=%d binddn=\"%s\"\n", + op->o_connid, op->o_opid, clientDn); + aclpb->aclpb_stat_total_entries++; + + if (!(access & SLAPI_ACL_PROXY) && + !( aclpb->aclpb_state & ACLPB_DONOT_EVALUATE_PROXY )) { + Acl_PBlock *proxy_pb; + + proxy_pb = acl_get_aclpb( pb, ACLPB_PROXYDN_PBLOCK ); + if (proxy_pb) { + TNF_PROBE_0_DEBUG(acl_access_allowed_proxy_start,"ACL",""); + ret_val = acl_access_allowed( pb, e, attr, val, SLAPI_ACL_PROXY ); + TNF_PROBE_0_DEBUG(acl_access_allowed_proxy_end,"ACL",""); + + if (ret_val != LDAP_SUCCESS) goto cleanup_and_ret; + } + } + if ( access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_num_entries++; + + if ( aclpb->aclpb_num_entries == 1) { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + } else if ( aclpb->aclpb_state & ACLPB_COPY_EVALCONTEXT ) { + /* We need to copy the evalContext */ + acl_copyEval_context ( aclpb, &aclpb->aclpb_curr_entryEval_context, + &aclpb->aclpb_prev_entryEval_context, 0 ); + aclpb->aclpb_state &= ~ACLPB_COPY_EVALCONTEXT; + } + acl_clean_aclEval_context ( &aclpb->aclpb_curr_entryEval_context, 1 /*scrub */); + } + + /* reset the cached result based on the scope */ + acl__reset_cached_result (aclpb ); + + /* Find all the candidate aci's that apply by scanning up the DIT tree from edn. */ + + TNF_PROBE_0_DEBUG(acl_aciscan_start,"ACL",""); + slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn ); + slapi_sdn_set_dn_byval ( aclpb->aclpb_curr_entry_sdn, n_edn ); + acllist_aciscan_update_scan ( aclpb, n_edn ); + TNF_PROBE_0_DEBUG(acl_aciscan_end,"ACL",""); + + /* Keep the ptr to the current entry */ + aclpb->aclpb_curr_entry = (Slapi_Entry *) e; + + /* Get the attr info */ + deallocate_attrEval = acl__get_attrEval ( aclpb, attr ); + + aclutil_print_resource ( aclpb, right, attr, clientDn ); + + /* + * Used to be PListInitProp(aclpb->aclpb_proplist, 0, + * DS_ATTR_ENTRY, e, 0); + * + * The difference is that PListInitProp() allocates a new property + * every time it's called, overwriting the old name in the PList hash + * table, but not freeing the original property. + * Now, we just create the property at aclpb_malloc() time and + * Assign a new value each time. + */ + + rv = PListAssignValue(aclpb->aclpb_proplist, + DS_ATTR_ENTRY, e, 0); + + if (rv < 0) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Unable to set the Slapi_Entry in the Plist\n",0,0,0); + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + TNF_PROBE_0_DEBUG(acl_entry_first_touch_end,"ACL",""); + + } else { + /* we are processing the same entry but for a different + ** attribute. If access is already allowed on that, entry, then + ** it's not a new entry anymore. It's the same old one. + */ + + TNF_PROBE_0_DEBUG(acl_entry_subs_touch_start,"ACL",""); + + aclpb->aclpb_res_type &= ~ACLPB_NEW_ENTRY; + + /* Get the attr info */ + deallocate_attrEval = acl__get_attrEval ( aclpb, attr ); + + TNF_PROBE_0_DEBUG(acl_entry_subs_touch_end,"ACL",""); + + } + + /* get a lock for the reader */ + acllist_acicache_READ_LOCK(); + got_reader_locked = 1; + + /* + ** Check if we can use any cached information to determine + ** access to this resource + */ + if ( (access & SLAPI_ACL_SEARCH) && + (ret_val = acl__match_handlesFromCache ( aclpb , attr, access)) != -1) { + /* means got a result: allowed or not*/ + + if (ret_val == LDAP_SUCCESS ) { + decision_reason.reason = ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + goto cleanup_and_ret; + } + + /* + ** Now we have all the information about the resource. Now we need to + ** figure out if there are any ACLs which can be applied. + ** If no ACLs are there, then it's a DENY as default. + */ + if (!(num_handle = acl__scan_for_acis(aclpb, &err))) { + + /* We might have accessed the ACL first time which could + ** have caused syntax error. + */ + if ( err == ACL_ONEACL_TEXT_ERR) + ret_val = LDAP_INVALID_SYNTAX; + else { + ret_val = LDAP_INSUFFICIENT_ACCESS; + decision_reason.reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS; + } + goto cleanup_and_ret; + } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Processed attr:%s for entry:%s\n", attr ? attr : "NULL", + ACL_ESCAPE_STRING_WITH_PUNCTUATION ( n_edn, ebuf), 0); + + /* + ** Now evaluate the rights. + ** This is what we have been waiting for. + ** The return value should be ACL_RES_DENY or ACL_RES_ALLOW. + */ + rv = acl__TestRights(aclpb, access, &right, ds_map_generic, + &decision_reason); + if ( rv != ACL_RES_ALLOW && (0 == strcasecmp ( right, "selfwrite") ) ) { + /* If I am adding myself to a group, we don't need selfwrite always, + ** write priv is good enough. Since libaccess doesn't provide me a nice + ** way to evaluate OR rights, I have to try again with wite priv. + ** bug: 339051 + */ + right = access_str_write; + rv = acl__TestRights(aclpb, access, &right, ds_map_generic, + &decision_reason); + } + + if (rv == ACL_RES_ALLOW) { + ret_val = LDAP_SUCCESS; + } else { + ret_val = LDAP_INSUFFICIENT_ACCESS; + } + +cleanup_and_ret: + + TNF_PROBE_0_DEBUG(acl_cleanup_start,"ACL",""); + + /* I am ready to get out. */ + if ( got_reader_locked ) acllist_acicache_READ_UNLOCK(); + + /* Store the status of the evaluation for this attr */ + if ( aclpb && (c_attrEval = aclpb->aclpb_curr_attrEval )) { + if ( deallocate_attrEval ) { + /* In this case we are not caching the result as + ** we have too many attrs. we have malloced space. + ** Get rid of it. + */ + slapi_ch_free ( (void **) &c_attrEval->attrEval_name ); + slapi_ch_free ( (void **) &c_attrEval ); + } else if (ret_val == LDAP_SUCCESS ) { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_status |= ACL_ATTREVAL_SUCCESS; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_SUCCESS; + else + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_INVALID; + } else { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_status |= ACL_ATTREVAL_FAIL; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_FAIL; + else + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_INVALID; + } + } + + if ( aclpb ) aclpb->aclpb_curr_attrEval = NULL; + + print_access_control_summary( "main", ret_val, clientDn, aclpb, right, + (attr ? attr : "NULL"), + escape_string_with_punctuation (n_edn, ebuf), + &decision_reason); + TNF_PROBE_0_DEBUG(acl_cleanup_end,"ACL",""); + + TNF_PROBE_0_DEBUG(acl_access_allowed_end,"ACL",""); + + return(ret_val); + +} + +static void print_access_control_summary( char *source, int ret_val, char *clientDn, + struct acl_pblock *aclpb, + char *right, + char *attr, + const char *edn, + aclResultReason_t *acl_reason) +{ + struct codebook { + int code; + char *text; + }; + + static struct codebook reasonbook[] = { + {ACL_REASON_NO_ALLOWS, "no allow acis"}, + {ACL_REASON_RESULT_CACHED_DENY, "cached deny"}, + {ACL_REASON_RESULT_CACHED_ALLOW, "cached allow"}, + {ACL_REASON_EVALUATED_ALLOW, "allowed"}, + {ACL_REASON_EVALUATED_DENY, "denied"}, + {ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS, "no aci matched the resource"}, + {ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS, "no aci matched the subject"}, + {ACL_REASON_ANON_ALLOWED, "allow anyone aci matched anon user"}, + {ACL_REASON_ANON_DENIED, "no matching anyone aci for anon user"}, + {ACL_REASON_EVALCONTEXT_CACHED_ALLOW, "cached context/parent allow"}, + {ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED, "cached context/parent deny"}, + {ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW, "cached context/parent allow any attr"}, + {ACL_REASON_NONE, "error occurred"}, + }; + + char *anon = "anonymous"; + char *null_user = "NULL"; /* bizare case */ + char *real_user = NULL; + char *proxy_user = NULL; + char *access_allowed_string = "Allow"; + char *access_not_allowed_string = "Deny"; + char *access_error_string = "access_error"; + char *access_status = NULL; + char *access_reason_none = "no reason available"; + char *access_reason = access_reason_none; + char acl_info[ BUFSIZ ]; + Slapi_Operation *op = NULL; + int loglevel; + int i; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + if ( !slapi_is_loglevel_set(loglevel) ) { + return; + } + + slapi_pblock_get(aclpb->aclpb_pblock, SLAPI_OPERATION, &op); /* for logging */ + + if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + access_status = access_not_allowed_string; + } else if ( ret_val == LDAP_SUCCESS) { + access_status = access_allowed_string; + } else { /* some kind of error */ + access_status = access_error_string; + } + + /* decode the reason */ + for (i = 0; i < sizeof(reasonbook) / sizeof(struct codebook); i++) { + if ( acl_reason->reason == reasonbook[i].code ) { + access_reason = reasonbook[i].text; + break; + } + } + + /* get the acl */ + acl_info[0] = '\0'; + if (acl_reason->deciding_aci) { + if (acl_reason->reason == ACL_REASON_RESULT_CACHED_DENY || + acl_reason->reason == ACL_REASON_RESULT_CACHED_ALLOW) { + /* acl is in cache. Its detail must have been printed before. + * So no need to print out acl detail this time. + */ + PR_snprintf( &acl_info[0], BUFSIZ, "%s by aci(%d)", + access_reason, + acl_reason->deciding_aci->aci_index); + } + else { + PR_snprintf( &acl_info[0], BUFSIZ, "%s by aci(%d): aciname=%s, acidn=\"%s\"", + access_reason, + acl_reason->deciding_aci->aci_index, + acl_reason->deciding_aci->aclName, + slapi_sdn_get_ndn (acl_reason->deciding_aci->aci_sdn) ); + } + } + + /* Say who was denied access */ + + if (clientDn) { + if (clientDn[0] == '\0') { + /* anon */ + real_user = anon; + } else { + real_user = clientDn; + } + } else { + real_user = null_user; + } + + /* Is there a proxy */ + + if ( aclpb != NULL && aclpb->aclpb_proxy != NULL) { + + if ( aclpb->aclpb_authorization_sdn != NULL ) { + + proxy_user = (char *)(aclpb->aclpb_authorization_sdn->ndn ? + aclpb->aclpb_authorization_sdn->ndn: + null_user); + + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s) to proxy (%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + proxy_user, + acl_info[0] ? acl_info : access_reason); + } else { + proxy_user = null_user; + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s) to proxy (%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + proxy_user, + acl_info[0] ? acl_info : access_reason); + } + } else{ + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + acl_info[0] ? acl_info : access_reason); + } + + +} +/*************************************************************************** +* +* acl_read_access_allowed_on_entry +* check read access control on the given entry. +* +* Only used during seearch to test for read access on the entry. +* (Could be generalized). +* +* attrs is the list of requested attributes passed with the search. +* If the entry has no attributes (weird case) then the routine survives. +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - access allowed +* LDAP_INSUFFICIENT_ACCESS - access denied +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_read_access_allowed_on_entry ( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char **attrs, + int access /* access rights */ + ) +{ + + struct acl_pblock *aclpb; + Slapi_Attr *currAttr; + Slapi_Attr *nextAttr; + int len; + int attr_index = -1; + char *attr_type = NULL; + int rv, isRoot; + char *clientDn; + unsigned long flags; + aclResultReason_t decision_reason; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_entry_start ,"ACL",""); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot ); + + /* + ** If it's the root, or acl is off or the entry is a rootdse, + ** Then you have the privilege to read it. + */ + if ( acl_skip_access_check ( pb, e ) ) { + char *n_edn = slapi_entry_get_ndn ( e ); + char ebuf [ BUFSIZ ]; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Root access (%s) allowed on entry(%s)\n", + acl_access2str(access), + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf)); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,skip_access,""); + return LDAP_SUCCESS; + } + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 2 \n" ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,end,"aclpb error"); + return LDAP_OPERATIONS_ERROR; + } + + /* + * Am I a anonymous dude ? then we can use our anonympous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn ); + if ( clientDn && *clientDn == '\0' ) { + int ret_val; + ret_val = aclanom_match_profile ( pb, aclpb, e, NULL, SLAPI_ACL_READ ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,end,"anon"); + + if (ret_val != -1 ) return ret_val; + } + + aclpb->aclpb_state &= ~ACLPB_RESET_MASK; + if (aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + int ret_val; + ret_val = acl__attr_cached_result (aclpb, NULL, SLAPI_ACL_READ); + if (ret_val != -1 ) { + /* print summary if loglevel set */ + if ( slapi_is_loglevel_set(loglevel) ) { + char *n_edn; + n_edn = slapi_entry_get_ndn ( e ); + if ( ret_val == LDAP_SUCCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + /* + * pass NULL as the attr as this routine is concerned with + * access at the entry level. + */ + print_access_control_summary( "on entry", + ret_val, clientDn, aclpb, + acl_access2str(SLAPI_ACL_READ), + NULL, n_edn, + &decision_reason); + } + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,eval_context_cached,""); + + return ret_val; + } + } + + /* + * Currently do not use this code--it results in confusing + * behaviour..see 529905 + */ +#ifdef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES + + /* Do we have access to the entry by virtue of + ** having access to an attr. Before that, let's find out which attrs + ** the user want. If the user has specified certain attributes, then + ** we check aginst that set of attributes. + */ + if (!((aclpb->aclpb_state & ACLPB_USER_WANTS_ALL_ATTRS) || + (aclpb->aclpb_state & ACLPB_USER_SPECIFIED_ATTARS))) { + int i; + if (attrs == NULL) { + aclpb->aclpb_state |= ACLPB_USER_WANTS_ALL_ATTRS; + } else { + for ( i = 0; attrs != NULL && attrs[i] != NULL; i++ ) { + if ( strcmp( LDAP_ALL_USER_ATTRS, attrs[i] ) == 0 ) { + aclpb->aclpb_state |= ACLPB_USER_WANTS_ALL_ATTRS; + break; + } + } + } + + if (!(aclpb->aclpb_state & ACLPB_USER_WANTS_ALL_ATTRS)) { + for (i = 0; attrs != NULL && attrs[i] != NULL; i++ ) { + if ( !slapi_entry_attr_find ( e, attrs[i], &currAttr ) ) { + aclpb->aclpb_state |= ACLPB_USER_SPECIFIED_ATTARS; + break; + } + } + } + } /* end of all user test*/ + + + /* + ** If user has specified a list of attrs, might as well use it + ** to determine access control. + */ + currAttr = NULL; + attr_index = -1; + if ( aclpb->aclpb_state & ACLPB_USER_SPECIFIED_ATTARS) { + attr_index = 0; + attr_type = attrs[attr_index++]; + } else { + /* Skip the operational attributes -- if there are any in the front */ + slapi_entry_first_attr ( e, &currAttr ); + if (currAttr != NULL) { + slapi_attr_get_flags ( currAttr, &flags ); + while ( flags & SLAPI_ATTR_FLAG_OPATTR ) { + flags = 0; + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( !rv ) slapi_attr_get_flags ( nextAttr, &flags ); + currAttr = nextAttr; + } + + /* Get the attr type */ + if ( currAttr ) slapi_attr_get_type ( currAttr , &attr_type ); + } + } + +#endif /*DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES*/ + +#ifndef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES + + /* + * Here look at each attribute in the entry and see if + * we have read access to it--if we do + * and we are not denied access to the entry then this + * is taken as implying access to the entry. + */ + slapi_entry_first_attr ( e, &currAttr ); + if (currAttr != NULL) { + slapi_attr_get_type ( currAttr , &attr_type ); + } +#endif + aclpb->aclpb_state |= ACLPB_EVALUATING_FIRST_ATTR; + + while (attr_type) { + if (acl_access_allowed (pb, e,attr_type, NULL, + SLAPI_ACL_READ) == LDAP_SUCCESS) { + /* + ** We found a rule which requires us to test access + ** to the entry. + */ + if ( aclpb->aclpb_state & ACLPB_FOUND_A_ENTRY_TEST_RULE){ + /* Do I have access on the entry itself */ + if (acl_access_allowed (pb, e, NULL, + NULL, access) != LDAP_SUCCESS) { + /* How was I denied ? + ** I could be denied on a DENY rule or because + ** there is no allow rule. If it's a DENY from + ** a DENY rule, then we don't have access to + ** the entry ( nice trick to get in ) + */ + if ( aclpb->aclpb_state & + ACLPB_EXECUTING_DENY_HANDLES) + return LDAP_INSUFFICIENT_ACCESS; + + /* The other case is I don't have an + ** explicit allow rule -- which is fine. + ** Since, I am already here, it means that I have + ** an implicit allow to the entry. + */ + } + } + aclpb->aclpb_state &= ~ACLPB_EVALUATING_FIRST_ATTR; + + /* + ** If we are not sending all the attrs, then we must + ** make sure that we have right on a attr that we are + ** sending + */ + len = strlen(attr_type); + if ( len > ACLPB_MAX_ATTR_LEN) { + slapi_ch_free ( (void **) &aclpb->aclpb_Evalattr); + aclpb->aclpb_Evalattr = slapi_ch_malloc(len); + } + strncpy (aclpb->aclpb_Evalattr, attr_type, len); + aclpb->aclpb_Evalattr[len] = '\0'; + if ( attr_index >= 0 ) { + /* + * access was granted to one of the user specified attributes + * which was found in the entry and that attribute is + * now in aclpb_Evalattr + */ + aclpb->aclpb_state |= + ACLPB_ACCESS_ALLOWED_USERATTR; + } else { + /* + * Access was granted to _an_ attribute in the entry and that + * attribute is now in aclpb_Evalattr + */ + aclpb->aclpb_state |= + ACLPB_ACCESS_ALLOWED_ON_A_ATTR; + } + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end , "ACL","", + tnf_string,called_access_allowed,""); + + return LDAP_SUCCESS; + } else { + /* try the next one */ + attr_type = NULL; + if (attr_index >= 0) { + attr_type = attrs[attr_index++]; + } else { + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( rv != 0 ) break; + currAttr = nextAttr; + slapi_attr_get_flags ( currAttr, &flags ); + while ( flags & SLAPI_ATTR_FLAG_OPATTR ) { + flags = 0; + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( !rv ) slapi_attr_get_flags ( nextAttr, &flags ); + currAttr = nextAttr; + } + /* Get the attr type */ + if ( currAttr ) slapi_attr_get_type ( currAttr , &attr_type ); + } + } + } + + /* + ** That means. we have searched thru all the attrs and found + ** access is denied on all attrs. + ** + ** If there were no attributes in the entry at all (can have + ** such entries thrown up by the b/e, then we do + ** not have such an implied access. + */ + aclpb->aclpb_state |= ACLPB_ACCESS_DENIED_ON_ALL_ATTRS; + aclpb->aclpb_state &= ~ACLPB_EVALUATING_FIRST_ATTR; + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL",""); + + return LDAP_INSUFFICIENT_ACCESS; +} + +/*************************************************************************** +* +* acl_read_access_allowed_on_attr +* check access control on the given attr. +* +* Only used during search to test for read access to an attr. +* (Could be generalized) +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - access allowed +* LDAP_INSUFFICIENT_ACCESS - access denied +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_read_access_allowed_on_attr ( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* access rights */ + ) +{ + + struct acl_pblock *aclpb = NULL; + char ebuf [ BUFSIZ ]; + char *clientDn = NULL; + char *n_edn; + aclResultReason_t decision_reason; + int ret_val = -1; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_attr_start ,"ACL",""); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + /* I am here, because I have access to the entry */ + + n_edn = slapi_entry_get_ndn ( e ); + + /* If it's the root or acl is off or rootdse, he has all the priv */ + if ( acl_skip_access_check ( pb, e ) ) { + char ebuf [ BUFSIZ ]; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Root access (%s) allowed on entry(%s)\n", + acl_access2str(access), + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), 0); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,skip_aclcheck,""); + + return LDAP_SUCCESS; + } + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 3 \n" ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclpb_error,""); + + return LDAP_OPERATIONS_ERROR; + } + + /* + * Am I a anonymous dude ? then we can use our anonympous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + slapi_pblock_get (pb, SLAPI_REQUESTOR_DN ,&clientDn ); + if ( clientDn && *clientDn == '\0' ) { + ret_val = aclanom_match_profile ( pb, aclpb, e, attr, + SLAPI_ACL_READ ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,anon_decision,""); + if (ret_val != -1 ) return ret_val; + } + + /* Then I must have a access to the entry. */ + aclpb->aclpb_state |= ACLPB_ACCESS_ALLOWED_ON_ENTRY; + + if ( aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + + ret_val = acl__attr_cached_result (aclpb, attr, SLAPI_ACL_READ); + if (ret_val != -1 ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "MATCHED HANDLE:dn:%s attr: %s val:%d\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), attr, + ret_val ); + if ( ret_val == LDAP_SUCCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + goto acl_access_allowed_on_attr_Exit; + } else { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + } + } + + if (aclpb->aclpb_state & ACLPB_ACCESS_DENIED_ON_ALL_ATTRS) { + /* access is denied on all the attributes */ + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,deny_all_attrs,""); + + return LDAP_INSUFFICIENT_ACCESS; + } + + /* do I have access to all the entries by virtue of having aci + ** rules with targetattr ="*". If yes, then allow access to + ** rest of the attributes. + */ + if (aclpb->aclpb_state & ACLPB_ATTR_STAR_MATCHED) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "STAR Access allowed on attr:%s; entry:%s \n", + attr, ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), 0); + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW; + ret_val = LDAP_SUCCESS; + goto acl_access_allowed_on_attr_Exit; + + } + + if (aclpb->aclpb_state & ACLPB_ACCESS_ALLOWED_ON_A_ATTR) { + + /* access is allowed on that attr. + ** for example: Slapi_Entry: cn, sn. phone, uid, passwd, address + ** We found that access is allowed on phone. That means the + ** -- access is denied on cn, sn + ** -- access is allowed on phone + ** -- Don't know about the rest. Need to evaluate. + */ + + if ( slapi_attr_type_cmp (attr, aclpb->aclpb_Evalattr, 1) == 0) { + /* from now on we need to evaluate access on + ** rest of the attrs. + */ + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_ON_A_ATTR; + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclp_Evalattr1,""); + + return LDAP_SUCCESS; + } else { + /* + * Here, the attr that implied access to the entry (aclpb_Evalattr), + * is not + * the one we currently want evaluated--so + * we need to evaluate access to attr--so fall through. + */ + } + + } else if (aclpb->aclpb_state & ACLPB_ACCESS_ALLOWED_USERATTR) { + /* Only skip evaluation on the user attr on which we have + ** evaluated before. + */ + if ( slapi_attr_type_cmp (attr, aclpb->aclpb_Evalattr, 1) == 0) { + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_USERATTR; + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclp_Evalattr2,""); + return LDAP_SUCCESS; + } + } + + /* we need to evaluate the access on this attr */ + return ( acl_access_allowed(pb, e, attr, val, access) ); + + /* This exit point prints a summary and returns ret_val */ +acl_access_allowed_on_attr_Exit: + + /* print summary if loglevel set */ + if ( slapi_is_loglevel_set(loglevel) ) { + + print_access_control_summary( "on attr", + ret_val, clientDn, aclpb, + acl_access2str(SLAPI_ACL_READ), + attr, n_edn, &decision_reason); + } + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL",""); + + return(ret_val); +} +/*************************************************************************** +* +* acl_check_mods +* check access control on the given entry to see if +* it allows the given modifications by the user associated with op. +* +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - mods allowed ok +* <err> - same return value as acl_access_allowed() +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_check_mods( + Slapi_PBlock *pb, + Slapi_Entry *e, + LDAPMod **mods, + char **errbuf +) +{ + int i; + int rv, accessCheckDisabled; + int lastmod = 0; + Slapi_Attr *attr = NULL; + char *n_edn; + Slapi_Backend *be = NULL; + Slapi_DN *e_sdn; + Acl_PBlock *aclpb = acl_get_aclpb ( pb, ACLPB_PROXYDN_PBLOCK ); + LDAPMod *mod; + Slapi_Mods smods; + + rv = slapi_pblock_get ( pb, SLAPI_PLUGIN_DB_NO_ACL, &accessCheckDisabled ); + if ( rv != -1 && accessCheckDisabled ) return LDAP_SUCCESS; + + if ( NULL == aclpb ) + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + + n_edn = slapi_entry_get_ndn ( e ); + e_sdn = slapi_entry_get_sdn ( e ); + + slapi_mods_init_byref(&smods,mods); + + for (mod = slapi_mods_get_first_mod(&smods); + mod != NULL; + mod = slapi_mods_get_next_mod(&smods)) { + switch (mod->mod_op & ~LDAP_MOD_BVALUES ) { + + case LDAP_MOD_DELETE: + if (mod->mod_bvalues != NULL ) { + break; + } + + /* + * Here, check that we have the right to delete all + * the values of the attribute in the entry. + */ + + case LDAP_MOD_REPLACE: + if ( !lastmod ) { + if (be == NULL) { + if (slapi_pblock_get( pb, SLAPI_BACKEND, &be )) { + be = NULL; + } + } + if (be != NULL) + slapi_pblock_get ( pb, SLAPI_BE_LASTMOD, &lastmod ); + } + if (lastmod && + (strcmp (mod->mod_type, "modifiersname")== 0 || + strcmp (mod->mod_type, "modifytimestamp")== 0)) { + continue; + } + + slapi_entry_attr_find (e, mod->mod_type, &attr); + if ( attr != NULL) { + Slapi_Value *sval=NULL; + const struct berval *attrVal=NULL; + int k= slapi_attr_first_value(attr,&sval); + while(k != -1) { + attrVal = slapi_value_get_berval(sval); + rv = slapi_access_allowed (pb, e, + mod->mod_type, + (struct berval *)attrVal, /* XXXggood had to cast away const - BAD */ + ACLPB_SLAPI_ACL_WRITE_DEL); /* was SLAPI_ACL_WRITE */ + if ( rv != LDAP_SUCCESS) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return(rv); + } + k= slapi_attr_next_value(attr, k, &sval); + } + } + else { + rv = slapi_access_allowed (pb, e, + mod->mod_type, + NULL, + ACLPB_SLAPI_ACL_WRITE_DEL); /* was SLAPI_ACL_WRITE */ + if ( rv != LDAP_SUCCESS) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return(rv); + } + } + break; + + default: + break; + } /* switch */ + + /* + * Check that we have add/delete writes on the specific values + * we are trying to add. + */ + + if ( aclpb && aclpb->aclpb_curr_entry_sdn ) + slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn ); + + if ( mod->mod_bvalues != NULL ) { + + /* + * Here, there are specific values specified. + * For add and replace--we need add rights for these values. + * For delete we need delete rights for these values. + */ + + for ( i = 0; mod->mod_bvalues[i] != NULL; i++ ) { + + if ( ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) || + ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_REPLACE)) { + + rv = acl_access_allowed (pb,e, + mod->mod_type, + mod->mod_bvalues[i], + ACLPB_SLAPI_ACL_WRITE_ADD); /*was SLAPI_ACL_WRITE*/ + } else if ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) { + rv = acl_access_allowed (pb,e, + mod->mod_type, + mod->mod_bvalues[i], + ACLPB_SLAPI_ACL_WRITE_DEL); /*was SLAPI_ACL_WRITE*/ + } else { + rv = LDAP_INSUFFICIENT_ACCESS; + } + + if ( rv != LDAP_SUCCESS ) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return rv; + } + /* Need to check for all the values because + ** we may be modifying a "self<right>" value. + */ + + /* Are we adding/replacing a aci attribute + ** value. In that case, we need to make + ** sure that the new value has thr right + ** syntax + */ + if (strcmp(mod->mod_type, + aci_attr_type) == 0) { + if ( 0 != (rv = acl_verify_syntax( e_sdn, + mod->mod_bvalues[i]))) { + aclutil_print_err(rv, e_sdn, + mod->mod_bvalues[i], + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return LDAP_INVALID_SYNTAX; + } + } + } /* for */ + } + } /* end of big for */ + /* Cleanup */ + slapi_mods_done(&smods); + return( LDAP_SUCCESS ); +} +/*************************************************************************** +* +* acl_modified +* Modifies ( removed, add, changes) the ACI LIST. +* +* Input: +* int *optype - op code +* char *dn - DN of the entry +* void *change - The change struct which contais the +* - change value +* +* Returns: +* None. +* +* Error Handling: +* None. +* +**************************************************************************/ +extern void +acl_modified (Slapi_PBlock *pb, int optype, char *n_dn, void *change) +{ + struct berval **bvalue; + char **value; + int rv=0; /* returned value */ + char* new_RDN; + char* parent_DN; + char* new_DN; + LDAPMod **mods; + struct berval b; + int j; + Slapi_Attr *attr = NULL; + Slapi_Entry *e = NULL; + char ebuf [ BUFSIZ]; + Slapi_DN *e_sdn; + aclUserGroup *ugroup = NULL; + + e_sdn = slapi_sdn_new_ndn_byval ( n_dn ); + /* Before we proceed, Let's first check if we are changing any groups. + ** If we are, then we need to change the signature + */ + switch ( optype ) { + case SLAPI_OPERATION_MODIFY: + case SLAPI_OPERATION_DELETE: + slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, (void*)&e); + break; + case SLAPI_OPERATION_ADD: + e = (Slapi_Entry *)change; + break; + } + + /* e can be null for RDN */ + if ( e ) slapi_entry_attr_find( e, "objectclass", &attr); + + if ( attr ) { + int group_change = 0; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + i= slapi_attr_first_value ( attr,&sval ); + while(i != -1) { + attrVal = slapi_value_get_berval ( sval ); + if ( (strcasecmp (attrVal->bv_val, "groupOfNames") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfUniqueNames") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfCertificates") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfURLs") == 0 ) ) { + group_change= 1; + if ( optype == SLAPI_OPERATION_MODIFY ) { + Slapi_Attr *a = NULL; + int rv; + rv = slapi_entry_attr_find ( e, "uniqueMember", &a); + if ( rv != 0 ) break; + rv = slapi_entry_attr_find ( e, "Member", &a ); + if ( rv != 0 ) break; + rv = slapi_entry_attr_find ( e, "MemberURL", &a ); + if ( rv != 0 ) break; + /* That means we are not changing the member + ** list, so it's okay to let go this + ** change + */ + group_change = 0; + } + break; + } + i= slapi_attr_next_value ( attr, i, &sval ); + } + + /* + ** We can do better here XXX, i.e invalidate the cache for users who + ** use this group. for now just do the whole thing. + */ + if ( group_change ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Group Change: Invalidating entire UserGroup Cache\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION(n_dn, ebuf)); + aclg_regen_group_signature(); + if ( (optype == SLAPI_OPERATION_MODIFY) || (optype == SLAPI_OPERATION_DELETE ) ) { + /* Then we need to invalidate the acl signature also */ + acl_signature = aclutil_gen_signature ( acl_signature ); + } + } + } + + /* + * Here if the target entry is in the group cache + * as a user then, as it's being changed it may move out of any dynamic + * groups it belongs to. + * Just remove it for now--can do better XXX by checking to see if + * it really needs to be removed by testing to see if he's + * still in th group after the change--but this requires keeping + * the memberURL of the group which we don't currently do. + * Also, if we keep + * the attributes that are being used in dynamic + * groups then we could only remove the user if a sensitive + * attribute was being modified (rather than scanning the whole user cache + * all the time). Also could do a hash lookup. + * + * aclg_find_userGroup() incs a refcnt so we can still refer to ugroup. + * aclg_markUgroupForRemoval() decs it and marks it for removal + * , so after that you cannot refer to ugroup. + * + */ + + if ( (ugroup = aclg_find_userGroup(n_dn)) != NULL) { + /* + * Mark this for deletion next time round--try to impact + * this mainline code as little as possible. + */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Marking entry %s for removal from ACL user Group Cache\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION(n_dn, ebuf)); + aclg_markUgroupForRemoval (ugroup); + } + + /* + * Take the write lock around all the mods--so that + * other operations will see the acicache either before the whole mod + * or after but not, as it was before, during the mod. + * This is in line with the LDAP concept of the operation + * on the whole entry being the atomic unit. + * + */ + + switch(optype) { + case SLAPI_OPERATION_DELETE: + /* In this case we have already checked if the user has + ** right to delete the entry. Part of delete of entry is + ** remove all the ACLs also. + */ + + acllist_acicache_WRITE_LOCK(); + rv = acllist_remove_aci_needsLock(e_sdn, NULL); + acllist_acicache_WRITE_UNLOCK(); + + break; + case SLAPI_OPERATION_ADD: + slapi_entry_attr_find ( (Slapi_Entry *) change, aci_attr_type, &attr ); + + if ( attr ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + acllist_acicache_WRITE_LOCK(); + i= slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval(sval); + rv= acllist_insert_aci_needsLock(e_sdn, attrVal ); + if (rv <= ACL_ERR) + aclutil_print_err(rv, e_sdn, attrVal, NULL); + /* Print the aci list */ + i= slapi_attr_next_value ( attr, i, &sval ); + } + acllist_acicache_WRITE_UNLOCK(); + } + break; + + case SLAPI_OPERATION_MODIFY: + { + int got_write_lock = 0; + + mods = (LDAPMod **) change; + + for (j=0; mods[j] != NULL; j++) { + if (strcasecmp(mods[j]->mod_type, aci_attr_type) == 0) { + + /* Got an aci to mod in this list of mods, so + * take the acicache lock for the whole list of mods, + * remembering to free it below. + */ + if ( !got_write_lock) { + acllist_acicache_WRITE_LOCK(); + got_write_lock = 1; + } + + switch (mods[j]->mod_op & ~LDAP_MOD_BVALUES) { + case LDAP_MOD_REPLACE: + /* First remove the item */ + rv = acllist_remove_aci_needsLock(e_sdn, NULL); + + /* now fall thru to add the new one */ + case LDAP_MOD_ADD: + /* Add the new aci */ + if (mods[j]->mod_op & LDAP_MOD_BVALUES) { + bvalue = mods[j]->mod_bvalues; + if (bvalue == NULL) + break; + for (; *bvalue != NULL; ++bvalue) { + rv=acllist_insert_aci_needsLock( e_sdn, *bvalue); + if (rv <= ACL_ERR) { + aclutil_print_err(rv, e_sdn, + *bvalue, NULL); + } + } + } else { + value = mods[j]->mod_values; + if (value == NULL) + break; + for (; *value != NULL; ++value) { + b.bv_len = strlen (*value); + b.bv_val = *value; + rv=acllist_insert_aci_needsLock( e_sdn, &b); + if (rv <= ACL_ERR) { + aclutil_print_err(rv, e_sdn, + &b, NULL); + } + } + } + break; + case LDAP_MOD_DELETE: + if (mods[j]->mod_op & LDAP_MOD_BVALUES) { + bvalue = mods[j]->mod_bvalues; + if (bvalue == NULL || *bvalue == NULL) { + rv = acllist_remove_aci_needsLock( e_sdn, NULL); + } else { + for (; *bvalue != NULL; ++bvalue) + acllist_remove_aci_needsLock( e_sdn, *bvalue); + } + } else { + value = mods[j]->mod_values; + if (value == NULL || *value == NULL) { + acllist_remove_aci_needsLock( e_sdn,NULL); + } else { + for (; *value != NULL; ++value) { + b.bv_len = strlen (*value); + b.bv_val = *value; + acllist_remove_aci_needsLock( e_sdn, &b); + } + } + + } + break; + + default: + break; + }/* modtype switch */ + }/* attrtype is aci */ + } /* end of for */ + if ( got_write_lock ) { + acllist_acicache_WRITE_UNLOCK(); + got_write_lock = 0; + } + + break; + }/* case op is modify*/ + + case SLAPI_OPERATION_MODRDN: + + new_RDN = (char*) change; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "acl_modified (MODRDN %s => \"%s\"\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_dn, ebuf), new_RDN, 0); + + /* compute new_DN: */ + parent_DN = slapi_dn_parent (n_dn); + if (parent_DN == NULL) { + new_DN = new_RDN; + } else { + new_DN = (char*) slapi_ch_malloc (strlen (new_RDN) + 3 + + strlen (parent_DN)); + strcpy (new_DN, new_RDN); + strcat (new_DN, ","); + strcat (new_DN, parent_DN); + slapi_dn_normalize (new_DN); + } + + /* Change the acls */ + acllist_acicache_WRITE_LOCK(); + acllist_moddn_aci_needsLock ( e_sdn, new_DN ); + acllist_acicache_WRITE_UNLOCK(); + + /* deallocat the parent_DN */ + if (parent_DN != NULL) { + slapi_ch_free ( (void **) &new_DN ); + slapi_ch_free ( (void **) &parent_DN ); + } + break; + + default: + /* print ERROR */ + break; + } /*optype switch */ + + slapi_sdn_free ( &e_sdn ); + +} +/*************************************************************************** +* +* acl__scan_for_acis +* Scan the list and picup the correct acls for evaluation. +* +* Input: +* Acl_PBlock *aclpb - Main ACL pblock +* int *err; - Any error status +* Returns: +* num_handles - Number of handles matched to the +* - resource + 1. +* Error Handling: +* None. +* +**************************************************************************/ +static int +acl__scan_for_acis(Acl_PBlock *aclpb, int *err) +{ + aci_t *aci; + NSErr_t errp; + int attr_matched; + int deny_handle; + int allow_handle; + int gen_allow_handle = ACI_MAX_ELEVEL+1; + int gen_deny_handle = ACI_MAX_ELEVEL+1; + int i; + PRUint32 cookie; + + TNF_PROBE_0_DEBUG(acl__scan_for_acis_start,"ACL",""); + + /* + ** Determine if we are traversing via the list Vs. we have our own + ** generated list + */ + if ( aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST || + aclpb->aclpb_handles_index[0] != -1 ) { + int kk = 0; + while ( kk < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[kk] != -1 ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Using ACL Cointainer:%d for evaluation\n", kk); + kk++; + } + } + + memset (&errp, 0, sizeof(NSErr_t)); + *err = ACL_FALSE; + aclpb->aclpb_num_deny_handles = -1; + aclpb->aclpb_num_allow_handles = -1; + for (i=0; i <= ACI_MAX_ELEVEL; i++) { + aclpb->aclpb_deny_handles [i] = NULL; + aclpb->aclpb_allow_handles [i] = NULL; + } + + /* Check the signature. If it has changed, start fresh */ + if ( aclpb->aclpb_signature != acl_signature ) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Restart the scan -- due to acl changes\n"); + acllist_init_scan ( aclpb->aclpb_pblock, LDAP_SCOPE_BASE, NULL ); + } + + attr_matched = ACL_FALSE; + deny_handle = 0; + allow_handle = 0; + i = 0; + + aclpb->aclpb_stat_acllist_scanned++; + aci = acllist_get_first_aci ( aclpb, &cookie ); + + while( aci ) { + if (acl__resource_match_aci(aclpb, aci, 0, &attr_matched)) { + /* Generate the ACL list handle */ + if (aci->aci_handle == NULL) { + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + continue; + } + aclutil_print_aci (aci, acl_access2str (aclpb->aclpb_access)); + + if (aci->aci_type & ACI_HAS_DENY_RULE) { + if (aclpb->aclpb_deny_handles[aci->aci_elevel] == NULL ) { + aclpb->aclpb_deny_handles[aci->aci_elevel] = aci; + } else { + if ((gen_deny_handle + ACI_DEFAULT_ELEVEL + 1) == + aclpb->aclpb_deny_handles_size ) { + int num = ACLPB_INCR_LIST_HANDLES + + aclpb->aclpb_deny_handles_size; + /* allocate more space */ + aclpb->aclpb_deny_handles = + (aci_t **) + slapi_ch_realloc ( + (void *) aclpb->aclpb_deny_handles, + num * sizeof (aci_t *)); + aclpb->aclpb_deny_handles_size = num; + } + aclpb->aclpb_deny_handles [gen_deny_handle] = aci; + gen_deny_handle++; + } + deny_handle++; + } + /* + ** It's possible that a single acl is in both the camps i.e + ** It has a allow and a deny rule + ** In that case we keep the same acl in both the camps. + */ + if (aci->aci_type & ACI_HAS_ALLOW_RULE) { + if (aclpb->aclpb_allow_handles[aci->aci_elevel] == NULL ) { + aclpb->aclpb_allow_handles[aci->aci_elevel] = aci; + } else { + if ((gen_allow_handle + ACI_DEFAULT_ELEVEL + 1) == + aclpb->aclpb_allow_handles_size) { + /* allocate more space */ + int num = ACLPB_INCR_LIST_HANDLES + + aclpb->aclpb_allow_handles_size; + + aclpb->aclpb_allow_handles = + (aci_t **) + slapi_ch_realloc ( + (void *) aclpb->aclpb_allow_handles, + num * sizeof (aci_t *)); + aclpb->aclpb_allow_handles_size = num; + } + aclpb->aclpb_allow_handles [gen_allow_handle] = aci; + gen_allow_handle++; + } + allow_handle++; + } + } + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } /* end of while */ + + /* make the last one a null */ + aclpb->aclpb_deny_handles [gen_deny_handle] = NULL; + aclpb->aclpb_allow_handles [gen_allow_handle] = NULL; + + /* specify how many we found */ + aclpb->aclpb_num_deny_handles = deny_handle; + aclpb->aclpb_num_allow_handles = allow_handle; + + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Num of ALLOW Handles:%d, DENY handles:%d\n", + aclpb->aclpb_num_allow_handles, aclpb->aclpb_num_deny_handles, 0); + + TNF_PROBE_0_DEBUG(acl__scan_for_acis_end,"ACL",""); + + return(allow_handle + deny_handle); +} + +/*************************************************************************** +* +* acl__resource_match_aci +* +* This compares the ACI for the given resource and determines if +* the ACL applies to the resource or not. +* +* For read/search operation, we collect all the possible acls which +* will apply, We will be using this list for future acl evaluation +* for that entry. +* +* Input: +* struct acl_pblock *aclpb - Main acl private block +* aci_t *aci - The ACI item +* int skip_attrEval - DOn't check for attrs +* int *a_matched - Attribute matched +* +* Returns: +* +* ACL_TRUE - Yep, This ACL is applicable to the +* - the resource. +* ACL_FALSE - No it is not. +* +* Error Handling: +* None. +* +**************************************************************************/ +#define ACL_RIGHTS_TARGETATTR_NOT_NEEDED ( SLAPI_ACL_ADD | SLAPI_ACL_DELETE | SLAPI_ACL_PROXY) +static int +acl__resource_match_aci( Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a_matched) +{ + + struct slapi_filter *f; /* filter */ + int rv; /* return value */ + int matches; + int attr_matched; + int attr_matched_in_targetattrfilters = 0; + int dn_matched; + char *res_attr; + int aci_right = 0; + int res_right = 0; + int star_matched = ACL_FALSE; + int num_attrs = 0; + AclAttrEval *c_attrEval = NULL; + const char *res_ndn = NULL; + const char *aci_ndn = NULL; + char *matched_val = NULL; + int add_matched_val_to_ht = 0; + char res_right_str[128]; + + TNF_PROBE_0_DEBUG(acl__resource_match_aci_start,"ACL",""); + + aclpb->aclpb_stat_aclres_matched++; + + /* Assume that resource matches */ + matches = ACL_TRUE; + + /* Figure out if the acl has the correct rights or not */ + aci_right = aci->aci_access; + res_right = aclpb->aclpb_access; + if (!(aci_right & res_right)) { + /* If we are looking for read/search and the acl has read/search + ** then go further because if targets match we may keep that + ** acl in the entry cache list. + */ + if (!((res_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) && + (aci_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)))) + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + + /* first Let's see if the entry is under the subtree where the + ** ACL resides. We can't let somebody affect a target beyond the + ** scope of where the ACL resides + ** Example: ACL is located in "ou=engineering, o=ace industry, c=us + ** but if the target is "o=ace industry, c=us", then we are in trouble. + ** + ** If the aci is in the rootdse and the entry is not, then we do not + ** match--ie. acis in the rootdse do NOT apply below...for the moment. + ** + */ + res_ndn = slapi_sdn_get_ndn ( aclpb->aclpb_curr_entry_sdn ); + aci_ndn = slapi_sdn_get_ndn ( aci->aci_sdn ); + if (!slapi_sdn_issuffix(aclpb->aclpb_curr_entry_sdn, aci->aci_sdn) + || (!slapi_is_rootdse(res_ndn) && slapi_is_rootdse(aci_ndn)) ) { + + /* cant' poke around */ + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + /* + ** We have a single ACI which we need to find if it applies to + ** the resource or not. + */ + if ((aci->aci_type & ACI_TARGET_DN) && + (aclpb->aclpb_curr_entry_sdn)) { + char *avaType; + struct berval *avaValue; + + f = aci->target; + dn_matched = ACL_TRUE; + slapi_filter_get_ava ( f, &avaType, &avaValue ); + + if (!slapi_dn_issuffix( res_ndn, avaValue->bv_val)) { + dn_matched = ACL_FALSE; + } + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + } + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + if (aci->aci_type & ACI_TARGET_PATTERN) { + + f = aci->target; + dn_matched = ACL_TRUE; + + if ((rv = acl_match_substring(f, (char *)res_ndn, 0 /* match suffux */)) != ACL_TRUE) { + dn_matched = ACL_FALSE; + if(rv == ACL_ERR) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl__resource_match_aci:pattern err\n", + 0,0,0); + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + } + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + } + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + /* + * Is it a (target="ldap://cn=*,($dn),o=sun.com") kind of thing. + */ + if (aci->aci_type & ACI_TARGET_MACRO_DN) { + /* + * See if the ($dn) component matches the string and + * retrieve the matched substring for later use + * in the userdn. + * The macro string is a function of the dn only, so if the + * entry is the same one don't recalculate it-- + * this flag only works for search right now, could + * also optimise for mods by making it work for mods. + */ + + if ( (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY) == 0 ) { + /* + * Here same entry so just look up the matched value, + * calculated from the targetdn and stored judiciously there + */ + matched_val = (char *)acl_ht_lookup( aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index); + } + if ( matched_val == NULL && + (aclpb->aclpb_res_type & (ACLPB_NEW_ENTRY | ACLPB_EFFECTIVE_RIGHTS))) { + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Evaluating macro aci(%d)%s for resource %s\n", + aci->aci_index, aci->aclName, + aclutil__access_str(res_right, res_right_str)); + matched_val = acl_match_macro_in_target( res_ndn, + aci->aci_macro->match_this, + aci->aci_macro->macro_ptr); + add_matched_val_to_ht = 1; /* may need to add matched value to ht */ + } + if (matched_val == NULL) { + dn_matched = ACL_FALSE; + } else { + dn_matched = ACL_TRUE; + } + + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + + if ( add_matched_val_to_ht ) { + if ( matches == ACL_TRUE && matched_val ) { + /* + * matched_val may be needed later for matching on + * other targets or on the subject--so optimistically + * put it in the hash table. + * If, at the end of this routine, we + * find that after all the resource did not match then + * that's ok--the value is freed at aclpb cleanup time. + * If there is already an entry for this aci in this + * aclpb then remove it--it's an old value for a + * different entry. + */ + + acl_ht_add_and_freeOld(aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index, + matched_val); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "-- Added aci(%d) and matched value (%s) to macro ht\n", + aci->aci_index, matched_val); + acl_ht_display_ht(aclpb->aclpb_macro_ht); + } else { + slapi_ch_free((void **)&matched_val); + if (matches == ACL_FALSE) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Evaluated ACL_FALSE\n"); + } + } + } + } /* MACRO_DN */ + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + /* + ** Here, if there's a targetfilter field, see if it matches. + ** + ** The commented out code below was an erroneous attempt to skip + ** this test. It is wrong because: 1. you need to store + ** whether the last test matched or not (you cannot just assume it did) + ** and 2. It may not be the same aci, so the previous matched + ** value is a function of the aci. + ** May be interesting to build such a cache...but no evidence for + ** for that right now. See Bug 383424. + ** + ** + ** && ((aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) || + ** (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY)) + */ + if (aci->aci_type & ACI_TARGET_FILTER ) { + int filter_matched = ACL_TRUE; + + /* + * Check for macros. + * For targetfilter we need to fake the lasinfo structure--it's + * created "naturally" for subjects but not targets. + */ + + + if ( aci->aci_type & ACI_TARGET_FILTER_MACRO_DN) { + + lasInfo *lasinfo = NULL; + + lasinfo = (lasInfo*) slapi_ch_malloc( sizeof(lasInfo) ); + + lasinfo->aclpb = aclpb; + lasinfo->resourceEntry = aclpb->aclpb_curr_entry; + aclpb->aclpb_curr_aci = aci; + filter_matched = aclutil_evaluate_macro( aci->targetFilterStr, + lasinfo, + ACL_EVAL_TARGET_FILTER); + slapi_ch_free((void**)&lasinfo); + } else { + + + if (slapi_vattr_filter_test(NULL, aclpb->aclpb_curr_entry, + aci->targetFilter, + 0 /*don't do acess chk*/)!= 0) { + filter_matched = ACL_FALSE; + } + + } + + /* If it's a logical value we can do logic on it...otherwise we do not match */ + if ( filter_matched == ACL_TRUE || filter_matched == ACL_FALSE) { + if (aci->aci_type & ACI_TARGET_FILTER_NOT) { + matches = (filter_matched == ACL_TRUE ? ACL_FALSE : ACL_TRUE); + } else { + matches = (filter_matched == ACL_TRUE ? ACL_TRUE: ACL_FALSE); + } + } else { + matches = ACL_FALSE; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for targetfilter evaluation.\n"); + } + + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + } + + /* + * Check to see if we need to evaluate any targetattrfilters. + * They look as follows: + * (targetattrfilters="add=sn:(sn=rob) && gn:(gn!=byrne), + * del=sn:(sn=rob) && gn:(gn=byrne)") + * + * For ADD/DELETE: + * If theres's a targetattrfilter then each add/del filter + * that applies to an attribute in the entry, must be satisfied + * by each value of the attribute in the entry. + * + * For MODIFY: + * If there's a targetattrfilter then the add/del filter + * must be satisfied by the attribute to be added/deleted. + * (MODIFY acl is evaluated one value at a time). + * + * + */ + + if ((aclpb->aclpb_access & SLAPI_ACL_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS )|| + (aclpb->aclpb_access & SLAPI_ACL_DELETE && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) ) { + + Targetattrfilter **attrFilterArray; + + Targetattrfilter *attrFilter = NULL; + + int found_applicable = 0; + Slapi_Attr *attr_ptr = NULL; + Slapi_Value *sval; + const struct berval *attrVal; + int k; + int done; + + + if (aclpb->aclpb_access & SLAPI_ACL_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) { + + attrFilterArray = aci->targetAttrAddFilters; + + } else if (aclpb->aclpb_access & SLAPI_ACL_DELETE && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS) { + + attrFilterArray = aci->targetAttrDelFilters; + + } + + attr_matched = ACL_TRUE; + num_attrs = 0; + + while (attrFilterArray[num_attrs] && attr_matched) { + attrFilter = attrFilterArray[num_attrs]; + + /* + * If this filter applies to an attribute in the entry, + * apply it to the entry. + * Otherwise just ignore it. + * + */ + + if (slapi_entry_attr_find ( aclpb->aclpb_curr_entry, + attrFilter->attr_str, + &attr_ptr) == 0) { + + /* + * This is an applicable filter. + * The filter is to be appplied to the entry being added + * or deleted. + * The filter needs to be satisfied by _each_ occurence + * of the attribute in the entry--otherwise you + * could satisfy the filter and then put loads of other + * values in on the back of it. + */ + + found_applicable = 1; + + sval=NULL; + attrVal=NULL; + k= slapi_attr_first_value(attr_ptr,&sval); + done = 0; + while(k != -1 && !done) { + attrVal = slapi_value_get_berval(sval); + + if ( acl__make_filter_test_entry( + &aclpb->aclpb_filter_test_entry, + attrFilter->attr_str, + (struct berval *)attrVal) == LDAP_SUCCESS ) { + + attr_matched= acl__test_filter( + aclpb->aclpb_filter_test_entry, + attrFilter->filter, + 1 /* Do filter sense evaluation below */ + ); + done = !attr_matched; + slapi_entry_free( aclpb->aclpb_filter_test_entry ); + } + + k= slapi_attr_next_value(attr_ptr, k, &sval); + }/* while */ + + /* + * Here, we applied an applicable filter to the entry. + * So if attr_matched is ACL_TRUE then every value + * of the attribute in the entry satisfied the filter. + * Otherwise, attr_matched is ACL_FALSE and not every + * value satisfied the filter, so we will teminate the + * scan of the filter list. + */ + + } + + num_attrs++; + } /* while */ + + /* + * Here, we've applied all the applicable filters to the entry. + * Each one must have been satisfied by all the values of the attribute. + * The result of this is stored in attr_matched. + */ + +#if 0 + /* + * Don't support a notion of "add != " or "del != " + * at the moment. + * To do this, need to test whether it's an add test or del test + * then if it's add and ACI_TARGET_ATTR_ADD_FILTERS_NOT then + * flip the bit. Same for del. + */ + + if (aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS_NOT) { + matches = (matches ? ACL_FALSE : ACL_TRUE); + } else { + matches = (matches ? ACL_TRUE: ACL_FALSE); + } +#endif + + /* No need to look further */ + if (attr_matched == ACL_FALSE) { + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + } else if ( (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) || + (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) ) { + + + /* + * Here, it's a modify add/del and we have attr filters. + * So, we need to scan the add/del filter list to find the filter + * that applies to the current attribute. + * Then the (attribute,value) pair being added/deleted better + * match that filter. + * + * + */ + + Targetattrfilter **attrFilterArray = NULL; + Targetattrfilter *attrFilter; + int found = 0; + + if (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) { + + attrFilterArray = aci->targetAttrAddFilters; + + } else if (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS) { + + attrFilterArray = aci->targetAttrDelFilters; + + } + + + /* + * Scan this filter list for an applicable filter. + */ + + found = 0; + num_attrs = 0; + + while (attrFilterArray[num_attrs] && !found) { + attrFilter = attrFilterArray[num_attrs]; + + /* If this filter applies to the attribute, stop. */ + if ((aclpb->aclpb_curr_attrEval) && + slapi_attr_type_cmp ( aclpb->aclpb_curr_attrEval->attrEval_name, + attrFilter->attr_str, 1) == 0) { + found = 1; + } + num_attrs++; + } + + /* + * Here, if found an applicable filter, then apply the filter to the + * (attr,val) pair. + * Otherwise, ignore the targetattrfilters. + */ + + if (found) { + + if ( acl__make_filter_test_entry( + &aclpb->aclpb_filter_test_entry, + aclpb->aclpb_curr_attrEval->attrEval_name, + aclpb->aclpb_curr_attrVal) == LDAP_SUCCESS ) { + + attr_matched= acl__test_filter(aclpb->aclpb_filter_test_entry, + attrFilter->filter, + 1 /* Do filter sense evaluation below */ + ); + slapi_entry_free( aclpb->aclpb_filter_test_entry ); + } + + /* No need to look further */ + if (attr_matched == ACL_FALSE) { + matches = attr_matched; + goto acl__resource_match_aci_EXIT; + } + + /* + * Here this attribute appeared and was matched in a + * targetattrfilters list, so record this fact so we do + * not have to scan the targetattr list for the attribute. + */ + + attr_matched_in_targetattrfilters = 1; + + + } + } /* targetvaluefilters */ + + + /* There are 3 cases by which acis are selected. + ** 1) By scanning the whole list and picking based on the resource. + ** 2) By picking a subset of the list which will be used for the whole + ** acl evaluation. + ** 3) A finer granularity, i.e, a selected list of acls which will be + ** used for only that entry's evaluation. + */ + if ( !(skip_attrEval) && (aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_ENTRY_LIST) && + (res_right & SLAPI_ACL_SEARCH) && + ((aci->aci_access & SLAPI_ACL_READ) || (aci->aci_access & SLAPI_ACL_SEARCH))) { + int kk=0; + + while ( kk < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[kk] >=0 ) kk++; + if (kk >= ACLPB_MAX_SELECTED_ACLS) { + aclpb->aclpb_state &= ~ACLPB_SEARCH_BASED_ON_ENTRY_LIST; + } else { + aclpb->aclpb_handles_index[kk++] = aci->aci_index; + aclpb->aclpb_handles_index[kk] = -1; + } + } + + + /* If we are suppose to skip attr eval, then let's skip it */ + if ( (aclpb->aclpb_access & SLAPI_ACL_SEARCH ) && ( ! skip_attrEval ) && + ( aclpb->aclpb_res_type & ACLPB_NEW_ENTRY )) { + aclEvalContext *c_evalContext = &aclpb->aclpb_curr_entryEval_context; + int nhandle = c_evalContext->acle_numof_tmatched_handles; + + if ( nhandle < ACLPB_MAX_SELECTED_ACLS) { + c_evalContext->acle_handles_matched_target[nhandle] = aci->aci_index; + c_evalContext->acle_numof_tmatched_handles++; + } + } + + if ( skip_attrEval ) { + goto acl__resource_match_aci_EXIT; + } + + /* We need to check again because we don't want to select this handle + ** if the right doesn't match for now. + */ + if (!(aci_right & res_right)) { + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + /* + * Here if the request is one that requires matching + * on a targetattr then do it here. + * If we have already matched an attribute in the targetattrfitlers list + * then we do not require a match in the targetattr so we can skip it. + * The operations that require targetattr are SLAPI_ACL_COMPARE, + * SLAPI_ACL_SEARCH, SLAPI_ACL_READ and SLAPI_ACL_WRITE, as long as + * c_attrEval is non-null (otherwise it's a modrdn op which + * does not require the targetattr list). + * + * rbyrneXXX if we had a proper permission for modrdn eg SLAPI_ACL_MODRDN + * then we would not need this crappy way of telling it was a MODRDN + * request ie. SLAPI_ACL_WRITE && !(c_attrEval). + */ + + c_attrEval = aclpb->aclpb_curr_attrEval; + + /* + * If we've already matched on targattrfilter then do not + * bother to look at the attrlist. + */ + + if (!attr_matched_in_targetattrfilters) { + + /* match target attr */ + if ((c_attrEval) && + (aci->aci_type & ACI_TARGET_ATTR)) { + /* there is a target ATTR */ + Targetattr **attrArray = aci->targetAttr; + Targetattr *attr = NULL; + + res_attr = c_attrEval->attrEval_name; + attr_matched = ACL_FALSE; + star_matched = ACL_FALSE; + num_attrs = 0; + + while (attrArray[num_attrs] && !attr_matched) { + attr = attrArray[num_attrs]; + if (attr->attr_type & ACL_ATTR_STRING) { + if (slapi_attr_type_cmp ( res_attr, + attr->u.attr_str, 1) == 0) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + } + } else if (attr->attr_type & ACL_ATTR_FILTER) { + if (ACL_TRUE == acl_match_substring ( + attr->u.attr_filter, + res_attr, 1)) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + } + } else if (attr->attr_type & ACL_ATTR_STAR) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + star_matched = ACL_TRUE; + } + num_attrs++; + } + + if (aci->aci_type & ACI_TARGET_ATTR_NOT) { + matches = (attr_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (attr_matched ? ACL_TRUE: ACL_FALSE); + } + + + aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED; + /* figure out how it matched, i.e star matched */ + if (matches && star_matched && num_attrs == 1 && + !(aclpb->aclpb_state & ACLPB_FOUND_ATTR_RULE)) + aclpb->aclpb_state |= ACLPB_ATTR_STAR_MATCHED; + else { + /* we are here means that there is a specific + ** attr in the rule for this resource. + ** We need to avoid this case + ** Rule 1: (targetattr = "uid") + ** Rule 2: (targetattr = "*") + ** we cannot use STAR optimization + */ + aclpb->aclpb_state |= ACLPB_FOUND_ATTR_RULE; + aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED; + } + } else if ( (c_attrEval) || + (aci->aci_type & ACI_TARGET_ATTR)) { + if ((aci_right & ACL_RIGHTS_TARGETATTR_NOT_NEEDED) && + (aclpb->aclpb_access & ACL_RIGHTS_TARGETATTR_NOT_NEEDED)) { + /* + ** Targetattr rule doesn't make any sense + ** in this case. So select this rule + ** default: matches = ACL_TRUE; + */ + ; + } else if (aci_right & SLAPI_ACL_WRITE && + (aci->aci_type & ACI_TARGET_ATTR) && + !(c_attrEval)) { + /* We need to handle modrdn operation. Modrdn doesn't + ** change any attrs but changes the RDN and so (attr=NULL). + ** Here we found an acl which has a targetattr but + ** the resource doesn't need one. In that case, we should + ** consider this acl. + ** default: matches = ACL_TRUE; + */ + ; + } else { + matches = ACL_FALSE; + } + } + }/* !attr_matched_in_targetattrfilters */ + + /* + ** Here we are testing if we find a entry test rule (which should + ** be rare). In that case, just remember it. An entry test rule + ** doesn't have "(targetattr)". + */ + if (aclpb && (aclpb->aclpb_state & ACLPB_EVALUATING_FIRST_ATTR) && + (!(aci->aci_type & ACI_TARGET_ATTR))) { + aclpb->aclpb_state |= ACLPB_FOUND_A_ENTRY_TEST_RULE; + } + + /* + * Generic exit point for this routine: + * matches is ACL_TRUE if the aci matches the target of the resource, + * ACL_FALSE othrewise. + * Apologies for the goto--this is a retro-fitted exit point. + */ +acl__resource_match_aci_EXIT: + + /* + * For macro acis, there may be a partial macro string + * placed in the aclpb_macro_ht + * even if the aci did not finally match. + * All the partial strings will be freed at aclpb + * cleanup time. + */ + + TNF_PROBE_0_DEBUG(acl__resource_match_aci_end,"ACL",""); + + return (matches); +} +/* Macro to determine if the cached result is valid or not. */ +#define ACL_CACHED_RESULT_VALID( result) \ + (((result & ACLPB_CACHE_READ_RES_ALLOW) && \ + (result & ACLPB_CACHE_READ_RES_SKIP)) || \ + ((result & ACLPB_CACHE_SEARCH_RES_ALLOW) && \ + (result & ACLPB_CACHE_SEARCH_RES_SKIP))) ? 0 : 1 +/*************************************************************************** +* +* acl__TestRights +* +* Test the rights and find out if access is allowed or not. +* +* Processing Alogorithm: +* +* First, process the DENY rules one by one. If the user is not explicitly +* denied, then check if the user is allowed by processing the ALLOW handles +* one by one. +* The result of the evaluation is cached. Exceptions are +* -- If an acl happens to be in both DENY and ALLOW camp. +* -- Only interested for READ/SEARCH right. +* +* Input: +* struct acl_pblock *aclpb - main acl private block +* int access - The access bits +* char **right - The right we are looking for +* char ** generic - Generic rights +* +* Returns: +* +* ACL_RES_ALLOW - Access allowed +* ACL_RES_DENY - Access denied +* err - error condition +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +acl__TestRights(Acl_PBlock *aclpb,int access, char **right, char ** map_generic, + aclResultReason_t *result_reason) +{ + ACLEvalHandle_t *acleval; + int rights_rv = ACL_RES_DENY; + int rv, i,j, k; + int index; + char *deny = NULL; + char *deny_generic = NULL; + char *acl_tag; + int expr_num; + char *testRights[2]; + aci_t *aci; + int numHandles = 0; + aclEvalContext *c_evalContext = NULL; + + TNF_PROBE_0_DEBUG(acl__TestRights_start,"ACL",""); + + c_evalContext = &aclpb->aclpb_curr_entryEval_context; + + /* record the aci and reason for access decision */ + result_reason->deciding_aci = NULL; + result_reason->reason = ACL_REASON_NONE; + + /* If we don't have any ALLLOW handles, it's DENY by default */ + if (aclpb->aclpb_num_allow_handles <= 0) { + result_reason->deciding_aci = NULL; + result_reason->reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS; + + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,no_allows,""); + + return ACL_RES_DENY; + } + + /* Get the ACL evaluation Context */ + acleval = aclpb->aclpb_acleval; + + testRights[0] = *right; + testRights[1] = '\0'; + + /* + ** START PROCESSING DENY HANDLES + ** Process each handle at a time. Do not concatenate the handles or else + ** all the context information will be build again and we will pay a + ** lot of penalty. The context is built the first time the handle is + ** processed. + ** + ** First we set the default to INVALID so that if rules are not matched, then + ** we get INVALID and if a rule matched, the we get DENY. + */ + aclpb->aclpb_state &= ~ACLPB_EXECUTING_ALLOW_HANDLES; + aclpb->aclpb_state |= ACLPB_EXECUTING_DENY_HANDLES; + ACL_SetDefaultResult (NULL, acleval, ACL_RES_INVALID); + + numHandles = ACI_MAX_ELEVEL + aclpb->aclpb_num_deny_handles; + for (i=0, k=0; i < numHandles && k < aclpb->aclpb_num_deny_handles ; ++i) { + int skip_eval = 0; + + /* + ** If the handle has been evaluated before, we can + ** cache the result. + */ + if (((aci = aclpb->aclpb_deny_handles[i]) == NULL) && (i <= ACI_MAX_ELEVEL)) + continue; + k++; + index = aci->aci_index; + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Evaluating DENY aci(%d) \"%s\"\n", index, aci->aclName); + + if (access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + /* + * aclpb->aclpb_cache_result[0..aclpb->aclpb_last_cache_result] is + * a cache of info about whether applicable acis + * allowed, did_not_allow or denied access + */ + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + short result; + + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if (access & SLAPI_ACL_SEARCH) { + if ( result & ACLPB_CACHE_SEARCH_RES_DENY){ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found SEARCH DENY in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_deny,""); + return ACL_RES_DENY; + } else if ((result & ACLPB_CACHE_SEARCH_RES_SKIP) || + (result & ACLPB_CACHE_SEARCH_RES_ALLOW)) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found SEARCH SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } else { /* must be READ */ + if (result & ACLPB_CACHE_READ_RES_DENY) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found READ DENY in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_deny,""); + return ACL_RES_DENY; + } else if ( result & ACLPB_CACHE_READ_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found READ SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } + } + } + } + if (skip_eval) { + skip_eval = 0; + continue; + } + + rv = ACL_EvalSetACL(NULL, acleval, aci->aci_handle); + if ( rv < 0) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl__TestRights:Unable to set the DENY acllist\n", + 0,0,0); + continue; + } + /* + ** Now we have all the information we need. We need to call + ** the ONE ACL to test the rights. + ** return value: ACL_RES_DENY, ACL_RES_ALLOW, error codes + */ + aclpb->aclpb_curr_aci = aci; + rights_rv = ACL_EvalTestRights (NULL, acleval, testRights, + map_generic, &deny, + &deny_generic, + &acl_tag, &expr_num); + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Processed:%d DENY handles Result:%d\n",index, rights_rv,0); + + if (rights_rv == ACL_RES_FAIL) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_NONE; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } + + /* have we executed an ATTR RULE */ + if ( aci->aci_ruleType & ACI_ATTR_RULES ) + aclpb->aclpb_state |= ACLPB_ATTR_RULE_EVALUATED; + + if (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS ) { + /* j == aclpb->aclpb_last_cache_result && + j < ACLPB_MAX_CACHE_RESULTS */ + aclpb->aclpb_last_cache_result++; + aclpb->aclpb_cache_result[j].aci_index = index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + + } else { /* cache overflow */ + if ( rights_rv == ACL_RES_DENY) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } else { + continue; + } + } + + __acl_set_aclIndex_inResult ( aclpb, access, index ); + if (rights_rv == ACL_RES_DENY) { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_DENY; + } else { /* MUST BE READ */ + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_DENY; + } + /* We are done -- return */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } else if (rights_rv == ACL_RES_ALLOW) { + /* This will happen, of we have an acl with both deny and allow + ** Since we may not have finished all the deny acl, go thru all + ** of them. We will use this cached result when we evaluate this + ** handle in the context of allow handles. + */ + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_ALLOW; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_ALLOW; + } + + } else { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_SKIP; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_SKIP; + } + continue; + } + } else { + if ( rights_rv == ACL_RES_DENY ) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } + } + } + + + /* + ** START PROCESSING ALLOW HANDLES. + ** Process each handle at a time. Do not concatenate the handles or else + ** all the context information will be build again and we will pay a + ** lot of penalty. The context is built the first time the handle is + ** processed. + ** + ** First we set the default to INVALID so that if rules are not matched, then + ** we get INVALID and if a rule matched, the we get ALLOW. + */ + aclpb->aclpb_state &= ~ACLPB_EXECUTING_DENY_HANDLES; + aclpb->aclpb_state |= ACLPB_EXECUTING_ALLOW_HANDLES; + ACL_SetDefaultResult (NULL, acleval, ACL_RES_INVALID); + numHandles = ACI_MAX_ELEVEL + aclpb->aclpb_num_allow_handles; + for (i=0, k=0; i < numHandles && k < aclpb->aclpb_num_allow_handles ; ++i) { + int skip_eval = 0; + /* + ** If the handle has been evaluated before, we can + ** cache the result. + */ + aci = aclpb->aclpb_allow_handles[i]; + if (((aci = aclpb->aclpb_allow_handles[i]) == NULL) && (i <= ACI_MAX_ELEVEL)) + continue; + k++; + index = aci->aci_index; + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "%d. Evaluating ALLOW aci(%d) \"%s\"\n", k, index, aci->aclName); + + if (access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + /* + * aclpb->aclpb_cache_result[0..aclpb->aclpb_last_cache_result] is + * a cache of info about whether applicable acis + * allowed, did_not_allow or denied access + */ + + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + short result; + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if (access & SLAPI_ACL_SEARCH) { + if (result & ACLPB_CACHE_SEARCH_RES_ALLOW) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found SEARCH ALLOW in cache\n"); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_allow,""); + return ACL_RES_ALLOW; + } else if ( result & ACLPB_CACHE_SEARCH_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found SEARCH SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + /* need to evaluate */ + break; + } + } else { + if ( result & ACLPB_CACHE_READ_RES_ALLOW) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found READ ALLOW in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_allow,""); + return ACL_RES_ALLOW; + } else if ( result & ACLPB_CACHE_READ_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found READ SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } + } + } + } + if ( skip_eval) { + skip_eval = 0; + continue; + } + + TNF_PROBE_0_DEBUG(acl__libaccess_start,"ACL",""); + rv = ACL_EvalSetACL(NULL, acleval, aci->aci_handle); + if ( rv < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "acl__TestRights:Unable to set the acllist\n", + 0,0,0); + continue; + } + /* + ** Now we have all the information we need. We need to call + ** the ONE ACL to test the rights. + ** return value: ACL_RES_DENY, ACL_RES_ALLOW, error codes + */ + aclpb->aclpb_curr_aci = aci; + rights_rv = ACL_EvalTestRights (NULL, acleval, testRights, + map_generic, &deny, + &deny_generic, + &acl_tag, &expr_num); + TNF_PROBE_0_DEBUG(acl__libaccess_end,"ACL",""); + + if (aci->aci_ruleType & ACI_ATTR_RULES) + aclpb->aclpb_state |= ACLPB_ATTR_RULE_EVALUATED; + + if (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS ) { + /* j == aclpb->aclpb_last_cache_result && + j < ACLPB_MAX_CACHE_RESULTS */ + aclpb->aclpb_last_cache_result++; + aclpb->aclpb_cache_result[j].aci_index = index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + } else { /* cache overflow */ + slapi_log_error (SLAPI_LOG_FATAL, "acl__TestRights", "cache overflown\n"); + if ( rights_rv == ACL_RES_ALLOW) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + } else { + continue; + } + } + + __acl_set_aclIndex_inResult ( aclpb, access, index ); + if (rights_rv == ACL_RES_ALLOW) { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_ALLOW; + } else { /* must be READ */ + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_ALLOW; + } + + /* We are done -- return */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + + } else { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_SKIP; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_SKIP; + } + continue; + } + } else { + if ( rights_rv == ACL_RES_ALLOW ) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + } + } + }/* for */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS; + + TNF_PROBE_0_DEBUG(acl__TestRights_end,"ACL",""); + + return (ACL_RES_DENY); +} +/*************************************************************************** +* +* acl_match_substring +* +* Compare the input string to the patteren in the filter +* +* Input: +* struct slapi_filter *f - Filter which has the patteren +* char *str - String to compare +* int exact_match - 1; match the pattern exactly +* - 0; match the pattern as a suffix +* +* Returns: +* ACL_TRUE - The sting matches with the patteren +* ACL_FALSE - No it doesn't +* ACL_ERR - Error +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_match_substring ( Slapi_Filter *f, char *str, int exact_match) +{ + int i, rc, len; + char *p; + char *end, *realval, *tmp; + char pat[BUFSIZ]; + char buf[BUFSIZ]; + char *type, *initial, *final; + char **any; + + if ( 0 != slapi_filter_get_subfilt ( f, &type, &initial, &any, &final ) ) { + return (ACL_FALSE); + } + + /* convert the input to lower. */ + for (p = str; *p; p++) + *p = TOLOWER ( *p ); + + /* construct a regular expression corresponding to the filter: */ + pat[0] = '\0'; + p = pat; + end = pat + sizeof(pat) - 2; /* leave room for null */ + + + if ( initial != NULL) { + strcpy (p, "^"); + p = strchr (p, '\0'); + + /* 2 * in case every char is special */ + if (p + 2 * strlen ( initial ) > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + + return (ACL_ERR); + } + + if (!exact_match) { + strcpy (p, ".*"); + p = strchr (p, '\0'); + } + acl_strcpy_special (p, initial); + p = strchr (p, '\0'); + } + + if ( any != NULL) { + for (i = 0; any && any[i] != NULL; i++) { + /* ".*" + value */ + if (p + 2 * strlen ( any[i]) + 2 > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + return (ACL_ERR); + } + + strcpy (p, ".*"); + p = strchr (p, '\0'); + acl_strcpy_special (p, any[i]); + p = strchr (p, '\0'); + } + } + + + if ( final != NULL) { + /* ".*" + value */ + if (p + 2 * strlen ( final ) + 2 > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + return (ACL_ERR); + } + + strcpy (p, ".*"); + p = strchr (p, '\0'); + acl_strcpy_special (p, final); + p = strchr (p, '\0'); + strcpy (p, "$"); + } + + /* see if regex matches with the input string */ + tmp = NULL; + len = strlen(str); + + if (len < sizeof(buf)) { + strcpy (buf, str); + realval = buf; + } else { + tmp = (char*) slapi_ch_malloc (len + 1); + strcpy (tmp, str); + realval = tmp; + } + + slapi_dn_normalize (realval); + + + /* What we have built is a regular pattaren expression. + ** Now we will compile the pattern and compare wth the string to + ** see if the input string matches with the patteren or not. + */ + slapd_re_lock(); + if ((p = slapd_re_comp (pat)) != 0) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "acl_match_substring:re_comp failed (%s)\n", p, 0, 0); + slapd_re_unlock(); + return (ACL_ERR); + } + + /* re_exec() returns 1 if the string p1 matches the last compiled + ** regular expression, 0 if the string p1 failed to match + ** (see man pages) + ** + ** IMPORTANT NOTE: If I use compile() and step() to do the patteren + ** matching, it seems that step() is leaking 1036 bytes/search + ** I couldn't figure out why it's leaking. + */ + rc = slapd_re_exec( realval ); + + slapd_re_unlock(); + + if (tmp != NULL) { + slapi_ch_free ( (void **) &tmp ); + } + + if (rc == 1) { + return ACL_TRUE; + } else { + return ACL_FALSE; + } +} +/*************************************************************************** +* +* acl__reset_cached_result +* +* Go thru the cached result and invlalidate the cached evaluation results for +* rules which can only be cached based on the scope. +* If we have scope ACI_CACHE_RESULT_PER_ENTRY, then we need to invalidate the +* cacched reult whenever we hit a new entry. +* +* Returns: +* Null +* +**************************************************************************/ +static void +acl__reset_cached_result (struct acl_pblock *aclpb ) +{ + + int j; + + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + /* Unless we have to clear the result, move on */ + if (!( aclpb->aclpb_cache_result[j].aci_ruleType & ACI_CACHE_RESULT_PER_ENTRY)) + continue; + aclpb->aclpb_cache_result[j].result = 0; + } +} +/* + * acl_access_allowed_disjoint_resource + * + * This is an internal module which can be used to verify + * access to a resource which may not be inside the search scope. + * + * Returns: + * - Same return val as acl_access_allowed(). + */ +int +acl_access_allowed_disjoint_resource( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* access rights */ + ) +{ + + int rv; + struct acl_pblock *aclpb = NULL; + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + /* + ** It's possible that we already have a context of ACLs. + ** However once in a while, we need to test + ** access to a resource which (like vlv, schema) which falls + ** outside the search base. In that case, we need to go + ** thru all the ACLs and not depend upon the acls which we have + ** gathered. + */ + + /* If you have the right to use the resource, then we don't need to check for + ** proxy right on that resource. + */ + if (aclpb) + aclpb->aclpb_state |= ( ACLPB_DONOT_USE_CONTEXT_ACLS| ACLPB_DONOT_EVALUATE_PROXY ); + + rv = acl_access_allowed(pb, e, attr, val, access); + + if (aclpb) aclpb->aclpb_state &= ~ACLPB_DONOT_USE_CONTEXT_ACLS; + if (aclpb ) aclpb->aclpb_state &= ~ACLPB_DONOT_EVALUATE_PROXY; + + return rv; +} + +/* + * acl__attr_cached_result + * Loops thru the cached result and determines if we can use the cached value. + * + * Inputs: + * Slapi_pblock *aclpb - acl private block + * char *attr - attribute name + * int access - access type + * Returns: + * LDAP_SUCCESS: - access is granted + * LDAP_INSUFFICIENT_ACCESS - access denied + * ACL_ERR - no cached info about this attr. + * - or the attr had multiple result and so + * - we can't determine the outcome. + * + */ +static int +acl__attr_cached_result (struct acl_pblock *aclpb, char *attr, int access ) +{ + + int i, rc; + aclEvalContext *c_evalContext; + + if ( !(access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ) )) + return ACL_ERR; + + if (aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT ) { + c_evalContext = &aclpb->aclpb_prev_opEval_context; + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "acl__attr_cached_result:Using Context: ACLPB_ACLCB\n", 0,0,0 ); + } else { + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "acl__attr_cached_result:Using Context: ACLPB_PREV\n", 0,0,0 ); + } + + if ( attr == NULL ) { + int eval_read = 0; + /* + ** Do I have access to at least one attribute, then I have + ** access to the entry. + */ + for (i=0; i < c_evalContext->acle_numof_attrs; i++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[i]; + + if ( (access & SLAPI_ACL_READ ) && a_eval->attrEval_r_status && + a_eval->attrEval_r_status < ACL_ATTREVAL_DETERMINISTIC ) { + eval_read++; + if ( a_eval->attrEval_r_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + /* rbyrne: recompute if we have to. + * How does this cached result get turned off for + * attr style acis which acannot be cached becuase entry + * can result in a diff value. + */ + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_r_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + if ( rc == LDAP_SUCCESS) { + return LDAP_SUCCESS; + } + } + } + }/* for */ + /* + * If we have scanned the whole list without success then + * we are not granting access to this entry through access + * to an attribute in the list--however this does not mean + * that we do not have access to the entry via another attribute + * not already in the list, so return -1 meaning + * "don't know". + */ + return(ACL_ERR); +#if 0 + if ( eval_read ) + return LDAP_INSUFFICIENT_ACCESS; + else + return ACL_ERR; +#endif + } + + for (i=0; i < c_evalContext->acle_numof_attrs; i++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[i]; + + if ( a_eval == NULL ) continue; + + if (strcasecmp ( attr, a_eval->attrEval_name ) == 0 ) { + if ( access & SLAPI_ACL_SEARCH ) { + if (a_eval->attrEval_s_status < ACL_ATTREVAL_DETERMINISTIC ) { + if ( a_eval->attrEval_s_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + else if ( a_eval->attrEval_s_status & ACL_ATTREVAL_FAIL) + return LDAP_INSUFFICIENT_ACCESS; + else if ( a_eval->attrEval_s_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_s_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + } else + return ACL_ERR; + } else { + /* This means that for the same attribute and same type of + ** access, we had different results at different time. + ** Since we are not caching per object, we can't + ** determine exactly. So, can't touch this + */ + return ACL_ERR; + } + } else { + if (a_eval->attrEval_r_status < ACL_ATTREVAL_DETERMINISTIC ) { + if ( a_eval->attrEval_r_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_FAIL) + return LDAP_INSUFFICIENT_ACCESS; + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_r_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + } else + return ACL_ERR; + } else { + /* Look above for explanation */ + return ACL_ERR; + } + } + } + } + return ACL_ERR; +} + +/* + * Had to do this juggling of casting to make + * both Nt & unix compiler happy. + */ +static int +acl__cmp(const void *a, const void *b) +{ + short *i = (short *) a; + short *j = (short *) b; + + if ( (short) *i > (short) *j ) + return (1); + if ( (short)*i < (short) *j) + return (-1); + return (0); +} + +/* + * acl__scan_match_handles + * Go thru the ACL list and determine if the list of acls selected matches + * what we have in the cache. + * + * Inputs: + * Acl_PBlock *pb - Main pblock ( blacvk hole) + * int type - Which context to look on + * + * Returns: + * 0 - matches all the acl handles + * ACL_ERR - sorry; no match + * + * ASSUMPTION: A READER LOCK ON ACL LIST + */ +static int +acl__scan_match_handles ( Acl_PBlock *aclpb, int type) +{ + + + int matched = 0; + aci_t *aci = NULL; + int index; + PRUint32 cookie; + aclEvalContext *c_evalContext = NULL; + + if (type == ACLPB_EVALCONTEXT_PREV ) { + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + } else if ( type == ACLPB_EVALCONTEXT_ACLCB ){ + c_evalContext = &aclpb->aclpb_prev_opEval_context; + } else { + return ACL_ERR; + } + + + if ( !c_evalContext->acle_numof_tmatched_handles ) + return ACL_ERR; + + aclpb->aclpb_stat_acllist_scanned++; + aci = acllist_get_first_aci ( aclpb, &cookie ); + + while ( aci ) { + index = aci->aci_index; + if (acl__resource_match_aci(aclpb, aci, 1 /* skip attr matching */, NULL )) { + int j; + int s_matched = matched; + + /* We have a sorted list of handles that matched the target */ + + for (j=0; j < c_evalContext->acle_numof_tmatched_handles ; j++ ) { + if ( c_evalContext->acle_handles_matched_target[j] > index ) + + break; + else if ( index == c_evalContext->acle_handles_matched_target[j] ) { + int jj; + matched++; + + /* See if this is a ATTR rule that matched -- in that case we have + ** to nullify the cached result + */ + if ( aci->aci_ruleType & ACI_ATTR_RULES ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Found an attr Rule [Name:%s Index:%d\n", aci->aclName, + aci->aci_index ); + for ( jj =0; jj < c_evalContext->acle_numof_attrs; jj++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[jj]; + if ( a_eval->attrEval_r_aciIndex == aci->aci_index ) + a_eval->attrEval_r_status = ACL_ATTREVAL_RECOMPUTE; + if ( a_eval->attrEval_s_aciIndex == aci->aci_index ) + a_eval->attrEval_s_status = ACL_ATTREVAL_RECOMPUTE; + } + } + break; + } + } + if ( s_matched == matched ) return ACL_ERR; + } + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } + if ( matched == c_evalContext->acle_numof_tmatched_handles ) + return 0; + + return ACL_ERR; +} +/* + * acl_copyEval_context + * Copy the context info which include attr info and handles. + * + * Inputs + * struct acl_pblock *aclpb - acl private main block + * aclEvalContext *src - src context + * aclEvalContext *dest - dest context + * Returns: + * None. + * + */ +void +acl_copyEval_context ( struct acl_pblock *aclpb, aclEvalContext *src, + aclEvalContext *dest , int copy_attr_only ) +{ + + int d_slot, i; + + /* Do a CLEAN copy we have nothing or else do an incremental copy.*/ + if ( src->acle_numof_attrs < 1 ) + return; + + /* Copy the attr info */ + if ( dest->acle_numof_attrs < 1 ) + acl_clean_aclEval_context ( dest, 0 /*clean */ ); + + d_slot = dest->acle_numof_attrs; + for (i=0; i < src->acle_numof_attrs; i++ ) { + int j; + int attr_exists = 0; + int dd_slot = d_slot; + + if ( aclpb && (i == 0) ) aclpb->aclpb_stat_num_copycontext++; + + if ( src->acle_attrEval[i].attrEval_r_status == 0 && + src->acle_attrEval[i].attrEval_s_status == 0 ) + continue; + + for ( j = 0; j < dest->acle_numof_attrs; j++ ) { + if ( strcasecmp ( src->acle_attrEval[i].attrEval_name, + dest->acle_attrEval[j].attrEval_name ) == 0 ) { + /* We have it. skip it. */ + attr_exists = 1; + dd_slot = j; + break; + } + } + if ( !attr_exists ) { + if ( dd_slot >= ACLPB_MAX_ATTRS -1 ) + break; + + if ( aclpb) aclpb->aclpb_stat_num_copy_attrs++; + + if ( dest->acle_attrEval[dd_slot].attrEval_name ) + slapi_ch_free ( (void **) &dest->acle_attrEval[dd_slot].attrEval_name ); + + dest->acle_attrEval[dd_slot].attrEval_name = + slapi_ch_strdup ( src->acle_attrEval[i].attrEval_name ); + } + /* Copy the result status and the aci index */ + dest->acle_attrEval[dd_slot].attrEval_r_status = + src->acle_attrEval[i].attrEval_r_status; + dest->acle_attrEval[dd_slot].attrEval_r_aciIndex = + src->acle_attrEval[i].attrEval_r_aciIndex; + dest->acle_attrEval[dd_slot].attrEval_s_status = + src->acle_attrEval[i].attrEval_s_status; + dest->acle_attrEval[dd_slot].attrEval_s_aciIndex = + src->acle_attrEval[i].attrEval_s_aciIndex; + + if (!attr_exists ) d_slot++; + } + + dest->acle_numof_attrs = d_slot; + dest->acle_attrEval[d_slot].attrEval_name = NULL; + + if ( copy_attr_only ) + return; + + /* First sort the arrays which keeps the acl index numbers */ + qsort ( (char *) src->acle_handles_matched_target, + (size_t)src->acle_numof_tmatched_handles, sizeof( int ), acl__cmp ); + + for (i=0; i < src->acle_numof_tmatched_handles; i++ ) { + dest->acle_handles_matched_target[i] = + src->acle_handles_matched_target[i]; + } + + if ( src->acle_numof_tmatched_handles ) { + dest->acle_numof_tmatched_handles = src->acle_numof_tmatched_handles; + if ( aclpb) aclpb->aclpb_stat_num_tmatched_acls = src->acle_numof_tmatched_handles; + } +} + +/* + * acl_clean_aclEval_context + * Clean the eval context + * + * Inputs: + * aclEvalContext *clean_me - COntext to be cleaned + * int clean_type - 0: clean, 1 scrub + * + */ + +void +acl_clean_aclEval_context ( aclEvalContext *clean_me, int scrub_only ) +{ + int i; + + /* Copy the attr info */ + for (i=0; i < clean_me->acle_numof_attrs; i++ ) { + + char *a_name = clean_me->acle_attrEval[i].attrEval_name; + if ( a_name && !scrub_only) { + slapi_ch_free ( (void **) &a_name ); + clean_me->acle_attrEval[i].attrEval_name = NULL; + } + clean_me->acle_attrEval[i].attrEval_r_status = 0; + clean_me->acle_attrEval[i].attrEval_s_status = 0; + clean_me->acle_attrEval[i].attrEval_r_aciIndex = 0; + clean_me->acle_attrEval[i].attrEval_s_aciIndex = 0; + } + + if ( !scrub_only ) clean_me->acle_numof_attrs = 0; + clean_me->acle_numof_tmatched_handles = 0; +} +/* + * acl__match_handlesFromCache + * + * We have 2 cacheed information + * 1) cached info from the previous operation + * 2) cached info from the prev entry evaluation + * + * What we are doing here is going thru all the acls and see if the same + * set of acls apply to this resource or not. If it does, then do we have + * a cached info for the attr. If we don't for both of the cases then we need + * to evaluate all over again. + * + * Inputs: + * struct acl_pblock - ACL private block; + * char *attr - Attribute name + * int access - acces type + * + * returns: + * LDAP_SUCCESS (0) - The same acls apply and we have + * access ALLOWED on the attr + * LDAP_INSUFFICIENT_ACCESS - The same acls apply and we have + * access DENIED on the attr + * -1 - Acls doesn't match or we don't have + * cached info for this attr. + * + * ASSUMPTIONS: A reader lock has been obtained for the acl list. + */ +static int +acl__match_handlesFromCache ( Acl_PBlock *aclpb, char *attr, int access) +{ + + aclEvalContext *c_evalContext = NULL; + int context_type = 0; + int ret_val = -1; /* it doen't match by default */ + + /* Before we proceed, find out if we have evaluated any ATTR RULE. If we have + ** then we can't use any caching mechanism + */ + + if ( aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT ) { + context_type = ACLPB_EVALCONTEXT_ACLCB; + c_evalContext = &aclpb->aclpb_prev_opEval_context; + } else { + context_type = ACLPB_EVALCONTEXT_PREV; + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + } + + + if ( aclpb->aclpb_res_type & (ACLPB_NEW_ENTRY | ACLPB_EFFECTIVE_RIGHTS) ) { + aclpb->aclpb_state |= ACLPB_MATCHES_ALL_ACLS; + ret_val = acl__scan_match_handles ( aclpb, context_type ); + if ( -1 == ret_val ) { + aclpb->aclpb_state &= ~ACLPB_MATCHES_ALL_ACLS; + aclpb->aclpb_state |= ACLPB_UPD_ACLCB_CACHE; + /* Did not match */ + if ( context_type == ACLPB_HAS_ACLCB_EVALCONTEXT ) { + aclpb->aclpb_state &= ~ACLPB_HAS_ACLCB_EVALCONTEXT; + } else { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + c_evalContext->acle_numof_tmatched_handles = 0; + } + } + } + if ( aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + /* See if we have a cached result for this attr */ + ret_val = acl__attr_cached_result (aclpb, attr, access); + + /* It's not in the ACLCB context but we might have it in the + ** current/prev context. Take a look at it. we might have evaluated + ** this attribute already. + */ + if ( (-1 == ret_val ) && + ( aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT )) { + aclpb->aclpb_state &= ~ACLPB_HAS_ACLCB_EVALCONTEXT ; + ret_val = acl__attr_cached_result (aclpb, attr, access); + aclpb->aclpb_state |= ACLPB_HAS_ACLCB_EVALCONTEXT ; + + /* We need to do an incremental update */ + if ( !ret_val ) aclpb->aclpb_state |= ACLPB_INCR_ACLCB_CACHE; + } + } + return ret_val; +} +/* + * acl__get_attrEval + * Get the atteval from the current context and hold the ptr in aclpb. + * If we have too many attrs, then allocate a new one. In that case + * we let the caller know about that so that it will be deallocated. + * + * Returns: + * int - 0: The context was indexed. So, no allocations. + * - 1; context was allocated - deallocate it. + */ +static int +acl__get_attrEval ( struct acl_pblock *aclpb, char *attr ) +{ + + int j; + aclEvalContext *c_ContextEval = &aclpb->aclpb_curr_entryEval_context; + int deallocate_attrEval = 0; + AclAttrEval *c_attrEval = NULL; + + if ( !attr ) return deallocate_attrEval; + + aclpb->aclpb_curr_attrEval = NULL; + + /* Go thru and see if we have the attr already */ + for (j=0; j < c_ContextEval->acle_numof_attrs; j++) { + c_attrEval = &c_ContextEval->acle_attrEval[j]; + + if ( c_attrEval && + slapi_attr_type_cmp ( c_attrEval->attrEval_name, attr, 1) == 0 ) { + aclpb->aclpb_curr_attrEval = c_attrEval; + break; + } + } + + if ( !aclpb->aclpb_curr_attrEval) { + if ( c_ContextEval->acle_numof_attrs == ACLPB_MAX_ATTRS -1 ) { + /* Too many attrs. create a temp one */ + c_attrEval = (AclAttrEval * ) slapi_ch_calloc ( 1, sizeof ( AclAttrEval ) ); + deallocate_attrEval =1; + } else { + c_attrEval = &c_ContextEval->acle_attrEval[c_ContextEval->acle_numof_attrs++]; + c_attrEval->attrEval_r_status = 0; + c_attrEval->attrEval_s_status = 0; + c_attrEval->attrEval_r_aciIndex = 0; + c_attrEval->attrEval_s_aciIndex = 0; + } + /* clean it before use */ + c_attrEval->attrEval_name = slapi_ch_strdup ( attr ); + aclpb->aclpb_curr_attrEval = c_attrEval; + } + return deallocate_attrEval; +} +/* + * acl_skip_access_check + * + * See if we need to go thru the ACL check or not. We don't need to if I am root + * or internal operation or ... + * + * returns: + * ACL_TRUE - Yes; skip the ACL check + * ACL_FALSE - No; you have to go thru ACL check + * + */ +int +acl_skip_access_check ( Slapi_PBlock *pb, Slapi_Entry *e ) +{ + int rv, isRoot, accessCheckDisabled; + void *conn = NULL; + Slapi_Backend *be; + + slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot ); + if ( isRoot ) return ACL_TRUE; + + /* See if this is local request */ + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn); + + if ( NULL == conn ) return ACL_TRUE; + + /* + * Turn on access checking in the rootdse--this code used + * to skip the access check. + * + * check if the entry is the RootDSE entry + if ( e ) { + char * edn = slapi_entry_get_ndn ( e ); + if ( slapi_is_rootdse ( edn ) ) return ACL_TRUE; + } + */ + + /* GB : when referrals are directly set in the mappin tree + * we can reach this code without a backend in the pblock + * in such a case, allow access for now + * we may want to reconsider this is NULL DSE implementation happens + */ + rv = slapi_pblock_get ( pb, SLAPI_BACKEND, &be ); + if (be == NULL) + return ACL_TRUE; + + rv = slapi_pblock_get ( pb, SLAPI_PLUGIN_DB_NO_ACL, &accessCheckDisabled ); + if ( rv != -1 && accessCheckDisabled ) return ACL_TRUE; + + return ACL_FALSE; +} +short +acl_get_aclsignature () +{ + return acl_signature; +} +void +acl_set_aclsignature ( short value) +{ + acl_signature = value; +} +void +acl_regen_aclsignature () +{ + acl_signature = aclutil_gen_signature ( acl_signature ); +} + + + +static int +acl__handle_config_entry (Slapi_Entry *e, void *callback_data ) +{ + + int *value = (int *) callback_data; + + *value = slapi_entry_attr_get_int( e, "nsslapd-readonly"); + + return 0; +} + +static int +acl__config_get_readonly () +{ + + int readonly = 0; + + slapi_search_internal_callback( "cn=config", LDAP_SCOPE_BASE, "(objectclass=*)", + NULL, 0 /* attrsonly */, + &readonly/* callback_data */, + NULL /* controls */, + NULL /* result_callback */, + acl__handle_config_entry, + NULL /* referral_callback */); + + return readonly; +} +/* +* +* Assumptions: +* 1) Called for read/search right. +*/ +static int +acl__recompute_acl ( Acl_PBlock *aclpb, + AclAttrEval *a_eval, + int access, + int aciIndex + ) +{ + + + char *unused_str1, *unused_str2; + char *acl_tag, *testRight[2]; + int j, expr_num; + int result_status, rv, cache_result; + PRUint32 cookie; + aci_t *aci; + + + PR_ASSERT ( aciIndex >= 0 ); + PR_ASSERT ( a_eval != NULL ); + PR_ASSERT (aclpb != NULL ); + + + /* We might have evaluated this acl just now, check it there first */ + + for ( j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (aciIndex == aclpb->aclpb_cache_result[j].aci_index) { + short result; + result_status =ACL_RES_INVALID; + + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + + + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if ((result & ACLPB_CACHE_SEARCH_RES_ALLOW) || + (result & ACLPB_CACHE_READ_RES_ALLOW) ) + result_status = ACL_RES_ALLOW; + else if ((result & ACLPB_CACHE_SEARCH_RES_DENY) || + (result & ACLPB_CACHE_READ_RES_DENY) ) + result_status = ACL_RES_DENY; + + } + } /* end of for */ + + if ( result_status != ACL_RES_INVALID ) { + goto set_result_status; + } + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Recomputing the ACL Index:%d for entry:%s\n", + aciIndex, slapi_entry_get_ndn ( aclpb->aclpb_curr_entry) ); + + /* First find this one ACL and then evaluate it. */ + + + aci = acllist_get_first_aci ( aclpb, &cookie ); + while ( aci && aci->aci_index != aciIndex ) { + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } + + if (NULL == aci) + return -1; + + + ACL_SetDefaultResult (NULL, aclpb->aclpb_acleval, ACL_RES_INVALID); + rv = ACL_EvalSetACL(NULL, aclpb->aclpb_acleval, aci->aci_handle); + + testRight[0] = acl_access2str ( access ); + testRight[1] = '\0'; + aclpb->aclpb_curr_aci = aci; + result_status = ACL_EvalTestRights (NULL, aclpb->aclpb_acleval, testRight, + ds_map_generic, &unused_str1, + &unused_str2, + &acl_tag, &expr_num); + + cache_result = 0; + if ( result_status == ACL_RES_DENY && aci->aci_type & ACI_HAS_DENY_RULE ) { + if ( access & SLAPI_ACL_SEARCH) + cache_result = ACLPB_CACHE_SEARCH_RES_DENY; + else + cache_result = ACLPB_CACHE_READ_RES_DENY; + } else if ( result_status == ACL_RES_ALLOW && aci->aci_type & ACI_HAS_ALLOW_RULE ) { + if ( access & SLAPI_ACL_SEARCH) + cache_result = ACLPB_CACHE_SEARCH_RES_ALLOW; + else + cache_result = ACLPB_CACHE_READ_RES_ALLOW; + + } else { + result_status = -1; + } + + /* Now we need to put the cached result in the aclpb */ + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (aciIndex == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS-1) { + /* rbyrneXXX: make this same as other last_cache_result code! */ + j = ++aclpb->aclpb_last_cache_result; + aclpb->aclpb_cache_result[j].aci_index = aci->aci_index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + + } else { /* No more space */ + goto set_result_status; + } + + /* Add the cached result status */ + aclpb->aclpb_cache_result[j].result |= cache_result; + + + +set_result_status: + if (result_status == ACL_RES_ALLOW) { + if (access & SLAPI_ACL_SEARCH) + /*wrong bit maskes were being used here-- + a_eval->attrEval_s_status = ACLPB_CACHE_SEARCH_RES_ALLOW;*/ + a_eval->attrEval_s_status = ACL_ATTREVAL_SUCCESS; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_SUCCESS; + + } else if ( result_status == ACL_RES_DENY) { + if (access & SLAPI_ACL_SEARCH) + a_eval->attrEval_s_status = ACL_ATTREVAL_FAIL; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_FAIL; + } else { + /* Here, set it to recompute--try again later */ + if (access & SLAPI_ACL_SEARCH) + a_eval->attrEval_s_status = ACL_ATTREVAL_RECOMPUTE; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_RECOMPUTE; + result_status = -1; + } + + return result_status; +} + +static void +__acl_set_aclIndex_inResult ( Acl_PBlock *aclpb, int access, int index ) +{ + AclAttrEval *c_attrEval = aclpb->aclpb_curr_attrEval; + + if ( c_attrEval ) { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_aciIndex = index; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_aciIndex = index; + } +} + +/* + * If filter_sense is true then return (entry satisfies f). + * Otherwise, return !(entry satisfies f) +*/ + +static int +acl__test_filter ( Slapi_Entry *entry, struct slapi_filter *f, int filter_sense) { + int filter_matched; + + /* slapi_vattr_filter_test() returns 0 for match */ + + filter_matched = !slapi_vattr_filter_test(NULL, entry, + f, + 0 /*don't do acess chk*/); + + if (filter_sense) { + return(filter_matched ? ACL_TRUE : ACL_FALSE); + } else { + return(filter_matched ? ACL_FALSE: ACL_TRUE); + } +} + +/* + * Make an entry consisting of attr_type and attr_val and put + * a pointer to it in *entry. + * We will use this afterwards to test for against a filter. +*/ + +static int +acl__make_filter_test_entry ( Slapi_Entry **entry, char *attr_type, + struct berval *attr_val) { + + struct berval *vals_array[2]; + + vals_array[0] = attr_val; + vals_array[1] = NULL; + + *entry = slapi_entry_alloc(); + slapi_entry_init(*entry, NULL, NULL); + + return (slapi_entry_add_values( *entry, (const char *)attr_type, + (struct berval**)&vals_array[0] )); + +} + +/*********************************************************************************/ +/* E N D */ +/*********************************************************************************/ diff --git a/ldap/servers/plugins/acl/acl.h b/ldap/servers/plugins/acl/acl.h new file mode 100644 index 00000000..5e16a0bd --- /dev/null +++ b/ldap/servers/plugins/acl/acl.h @@ -0,0 +1,867 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/***************************************************************************** +* acl.h +* +* Header file for ACL processing +* +*****************************************************************************/ +#ifndef _ACL_H_ +#define _ACL_H_ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#endif + +#include <ldap.h> +#include <las.h> +#include <aclproto.h> +#include <aclerror.h> +#include "prcvar.h" +#include "slapi-plugin.h" +#include "slap.h" +#include "slapi-private.h" +#include "portable.h" +#include "prrwlock.h" +#include "avl.h" + +#include "cert.h" + +#include <plhash.h> + +#ifdef SOLARIS + #include <tnf/probe.h> +#else + #define TNF_PROBE_0_DEBUG(a,b,c) + #define TNF_PROBE_1_DEBUG(a,b,c,d,e,f) +#endif + +#define ACL_PLUGIN_NAME "NSACLPlugin" +extern char *plugin_name; + +/* + * Define the OID for version 2 of the proxied authorization control if + * it is not already defined (it is in recent copies of ldap.h). + */ +#ifndef LDAP_CONTROL_PROXIEDAUTH +#define LDAP_CONTROL_PROXIEDAUTH "2.16.840.1.113730.3.4.18" +#endif + +#define ACLUCHP unsigned char * + +static char* const aci_attr_type = "aci"; +static char* const filter_string = "aci=*"; +static char* const aci_targetdn = "target"; +static char* const aci_targetattr = "targetattr"; +static char* const aci_targetattrfilters = "targattrfilters"; +static char* const aci_targetfilter = "targetfilter"; + +static char* const LDAP_URL_prefix = "ldap:///"; + +static char* const access_str_compare = "compare"; +static char* const access_str_search = "search"; +static char* const access_str_read = "read"; +static char* const access_str_write = "write"; +static char* const access_str_delete = "delete"; +static char* const access_str_add = "add"; +static char* const access_str_selfwrite = "selfwrite"; +static char* const access_str_proxy = "proxy"; + +#define ACL_INIT_ATTR_ARRAY 5 + +/* define the method */ +#define DS_METHOD "ds_method" + +#define ACL_ESCAPE_STRING_WITH_PUNCTUATION(x,y) (slapi_is_loglevel_set(SLAPI_LOG_ACL) ? escape_string_with_punctuation(x,y) : "") + +/* Lases */ +#define DS_LAS_USER "user" +#define DS_LAS_GROUP "group" +#define DS_LAS_USERDN "userdn" +#define DS_LAS_GROUPDN "groupdn" +#define DS_LAS_USERDNATTR "userdnattr" +#define DS_LAS_AUTHMETHOD "authmethod" +#define DS_LAS_GROUPDNATTR "groupdnattr" +#define DS_LAS_USERATTR "userattr" +#define DS_LAS_ROLEDN "roledn" +#define DS_LAS_ROLEDNATTR "rolednattr" + + +/* These define the things that aclutil_evaluate_macro() supports */ +typedef enum +{ + ACL_EVAL_USER, + ACL_EVAL_GROUP, + ACL_EVAL_ROLE, + ACL_EVAL_GROUPDNATTR, + ACL_EVAL_TARGET_FILTER +}acl_eval_types; + +typedef enum +{ + ACL_RULE_MACRO_DN_TYPE, + ACL_RULE_MACRO_DN_LEVELS_TYPE +}acl_rule_macro_types; + +#define ACL_TARGET_MACRO_DN_KEY "($dn)" +#define ACL_RULE_MACRO_DN_KEY "($dn)" +#define ACL_RULE_MACRO_DN_LEVELS_KEY "[$dn]" +#define ACL_RULE_MACRO_ATTR_KEY "($attr." + +#define ACL_EVAL_USER 0 +#define ACL_EVAL_GROUP 1 +#define ACL_EVAL_ROLE 2 + +/* The LASes are implemented in the libaccess library */ +#define DS_LAS_TIMEOFDAY "timeofday" +#define DS_LAS_DAYOFWEEK "dayofweek" + + +/* ACL function return codes */ +#define ACL_TRUE 1 /* evaluation results to TRUE */ +#define ACL_OK ACL_TRUE +#define ACL_FALSE 0 /* evaluation results to FALSE */ +#define ACL_ERR -1 /* generic error */ +#define ACL_TARGET_FILTER_ERR -2 /* Target filter not set properly */ +#define ACL_TARGETATTR_FILTER_ERR -3 /* TargetAttr filter not set properly */ +#define ACL_TARGETFILTER_ERR -4 /* Target filter not set properly */ +#define ACL_SYNTAX_ERR -5 /* Syntax error */ +#define ACL_ONEACL_TEXT_ERR -6 /* ONE ACL text error */ +#define ACL_ERR_CONCAT_HANDLES -7 /* unable to concat the handles */ +#define ACL_INVALID_TARGET -8 /* invalid target */ +#define ACL_INVALID_AUTHMETHOD -9 /* multiple client auth */ +#define ACL_INVALID_AUTHORIZATION -10 /* no authorization */ +#define ACL_INCORRECT_ACI_VERSION -11 /* incorrect version # */ +#define ACL_DONT_KNOW -12 /* the world is an uncertain place */ + +/* supported by the DS */ +#define DS_PROP_CONNECTION "connection" +#define DS_ATTR_USERDN "userdn" +#define DS_ATTR_ENTRY "entry" +#define DS_PROP_ACLPB "aclblock" +#define DS_ATTR_AUTHTYPE "authtype" +#define DS_ATTR_CERT "clientcert" + +#define ACL_ANOM_MAX_ACL 40 +struct scoped_entry_anominfo { + short anom_e_targetInfo[ACL_ANOM_MAX_ACL]; + short anom_e_nummatched; + short anom_e_isrootds; +}; + +typedef struct targetattr { + int attr_type; +#define ACL_ATTR_FILTER 0x01 +#define ACL_ATTR_STRING 0x02 +#define ACL_ATTR_STAR 0x04 /* attr is * only */ + + union { + char *attr_str; + struct slapi_filter *attr_filter; + }u; +}Targetattr; + +typedef struct targetattrfilter { + char *attr_str; + char *filterStr; + struct slapi_filter *filter; /* value filter */ + +}Targetattrfilter; + +typedef struct Aci_Macro { + char *match_this; + char *macro_ptr; /* ptr into match_this */ +}aciMacro; + +typedef PLHashTable acl_ht_t; + +/* Access Control Item (aci): Stores information about a particular ACL */ +typedef struct aci { + int aci_type; /* Type of resurce */ + +/* THE FIRST BYTE WAS USED TO KEEP THE RIGHTS. ITS BEEN MOVED TO +** aci_access and is now free. +** +** +** +*/ + +#define ACI_TARGET_MACRO_DN (int)0x000001 +#define ACI_TARGET_FILTER_MACRO_DN (int)0x000002 +#define ACI_TARGET_DN (int)0x000100 /* target has DN */ +#define ACI_TARGET_ATTR (int)0x000200 /* target is an attr */ +#define ACI_TARGET_PATTERN (int)0x000400 /* target has some patt */ +#define ACI_TARGET_FILTER (int)0x000800 /* target has a filter */ +#define ACI_ACLTXT (int)0x001000 /* ACI has text only */ +#define ACI_TARGET_NOT (int)0x002000 /* it's a != */ +#define ACI_TARGET_ATTR_NOT (int)0x004000 /* It's a != manager */ +#define ACI_TARGET_FILTER_NOT (int)0x008000 /* It's a != filter */ +#define ACI_UNUSED2 (int)0x010000 /* Unused */ +#define ACI_HAS_ALLOW_RULE (int)0x020000 /* allow (...) */ +#define ACI_HAS_DENY_RULE (int)0x040000 /* deny (...) */ +#define ACI_CONTAIN_NOT_USERDN (int)0x080000 /* userdn != blah */ +#define ACI_TARGET_ATTR_ADD_FILTERS (int)0x100000 +#define ACI_TARGET_ATTR_DEL_FILTERS (int)0x200000 +#define ACI_CONTAIN_NOT_GROUPDN (int)0x400000 /* groupdn != blah */ +#define ACI_CONTAIN_NOT_ROLEDN (int)0x800000 + + int aci_access; + +/* + * See also aclpb_access which is used to store rights too. +*/ + + short aci_ruleType; /* kinds of rules in the ACL */ + +#define ACI_USERDN_RULE (short) 0x0001 +#define ACI_USERDNATTR_RULE (short) 0x0002 +#define ACI_GROUPDN_RULE (short) 0x0004 +#define ACI_GROUPDNATTR_RULE (short) 0x0008 +#define ACI_AUTHMETHOD_RULE (short) 0x0010 +#define ACI_IP_RULE (short) 0x0020 +#define ACI_DNS_RULE (short) 0x0040 +#define ACI_TIMEOFDAY_RULE (short) 0x0080 +#define ACI_DAYOFWEEK_RULE (short) 0x0010 +#define ACI_USERATTR_RULE (short) 0x0200 +/* + * These are extension of USERDN/GROUPDN rule. However since the + * semantics are quite different, we classify them as different rules. + * ex: groupdn = "ldap:///cn=helpdesk, ou=$attr.dept, o=$dn.o, o=isp" + */ +#define ACI_PARAM_DNRULE (short) 0x0400 +#define ACI_PARAM_ATTRRULE (short) 0x0800 +#define ACI_USERDN_SELFRULE (short) 0x1000 +#define ACI_ROLEDN_RULE (short) 0x2000 + + + +#define ACI_ATTR_RULES ( ACI_USERDNATTR_RULE | ACI_GROUPDNATTR_RULE | ACI_USERATTR_RULE | ACI_PARAM_DNRULE | ACI_PARAM_ATTRRULE | ACI_USERDN_SELFRULE) +#define ACI_CACHE_RESULT_PER_ENTRY ACI_ATTR_RULES + + short aci_elevel; /* Based on the aci type some idea about the + ** execution flow + */ + int aci_index; /* index # */ + Slapi_DN *aci_sdn; /* location */ + Slapi_Filter *target; /* Target is a DN */ + Targetattr **targetAttr; + char *targetFilterStr; + struct slapi_filter *targetFilter; /* Target has a filter */ + Targetattrfilter **targetAttrAddFilters; + Targetattrfilter **targetAttrDelFilters; + char *aclName; /* ACL name */ + struct ACLListHandle *aci_handle; /*handle of the ACL */ + aciMacro *aci_macro; + struct aci *aci_next; /* next one */ +}aci_t; + +/* Aci excution level +** The idea is that for each handle types, we can prioritize which one to evaluate first. +** Evaluating the user before the group is better. +*/ +#define ACI_ELEVEL_USERDN_ANYONE 0 +#define ACI_ELEVEL_USERDN_ALL 1 +#define ACI_ELEVEL_USERDN 2 +#define ACI_ELEVEL_USERDNATTR 3 +#define ACI_ELEVEL_GROUPDNATTR_URL 4 +#define ACI_ELEVEL_GROUPDNATTR 5 +#define ACI_ELEVEL_GROUPDN 6 +#define ACI_MAX_ELEVEL ACI_ELEVEL_GROUPDN +1 +#define ACI_DEFAULT_ELEVEL ACI_MAX_ELEVEL + + +#define ACLPB_MAX_SELECTED_ACLS 200 + +typedef struct result_cache { + int aci_index; + short aci_ruleType; + short result; +#define ACLPB_CACHE_READ_RES_ALLOW (short)0x0001 /* used for ALLOW handles only */ +#define ACLPB_CACHE_READ_RES_DENY (short)0x0002 /* used for DENY handles only */ +#define ACLPB_CACHE_SEARCH_RES_ALLOW (short)0x0004 /* used for ALLOW handles only */ +#define ACLPB_CACHE_SEARCH_RES_DENY (short)0x0008 /* used for DENY handles only */ +#define ACLPB_CACHE_SEARCH_RES_SKIP (short)0x0010 /* used for both types */ +#define ACLPB_CACHE_READ_RES_SKIP (short)0x0020 /* used for both types */ +}r_cache_t; +#define ACLPB_MAX_CACHE_RESULTS ACLPB_MAX_SELECTED_ACLS + +/* + * This is use to keep the result of the evaluation of the attr. + * We are only intrested in read/searc only. + */ +struct acl_attrEval { + char *attrEval_name; /* Attribute Name */ + short attrEval_r_status; /* status of read evaluation */ + short attrEval_s_status; /* status of search evaluation */ + int attrEval_r_aciIndex; /* Index of the ACL which grants access*/ + int attrEval_s_aciIndex; /* Index of the ACL which grants access*/ + +#define ACL_ATTREVAL_SUCCESS 0x1 +#define ACL_ATTREVAL_FAIL 0x2 +#define ACL_ATTREVAL_RECOMPUTE 0x4 +#define ACL_ATTREVAL_DETERMINISTIC 7 +#define ACL_ATTREVAL_INVALID 0x8 + +}; +typedef struct acl_attrEval AclAttrEval; + + +/* + * Struct to keep the evaluation context information. This struct is + * used in multiple places ( different instance ) to keep the context for + * current entry evaluation, previous entry evaluation or previous operation + * evaluation status. + */ +#define ACLPB_MAX_ATTR_LEN 100 +#define ACLPB_MAX_ATTRS 100 +struct acleval_context { + + /* Information about the attrs */ + AclAttrEval acle_attrEval[ACLPB_MAX_ATTRS]; + short acle_numof_attrs; + + /* Handles information */ + short acle_numof_tmatched_handles; + int acle_handles_matched_target[ACLPB_MAX_SELECTED_ACLS]; +}; +typedef struct acleval_context aclEvalContext; + + +struct acl_usergroup { + short aclug_signature; +/* + * To modify refcnt you need either the write lock on the whole cache or + * the reader lock on the whole cache plus this refcnt mutex +*/ + short aclug_refcnt; + PRLock *aclug_refcnt_mutex; + + + char *aclug_ndn; /* Client's normalized DN */ + + char **aclug_member_groups; + short aclug_member_group_size; + short aclug_numof_member_group; + + char **aclug_notmember_groups; + short aclug_notmember_group_size; + short aclug_numof_notmember_group; + struct acl_usergroup *aclug_next; + struct acl_usergroup *aclug_prev; + +}; +typedef struct acl_usergroup aclUserGroup; + +#define ACLUG_INCR_GROUPS_LIST 20 + +struct aci_container { + Slapi_DN *acic_sdn; /* node DN */ + aci_t *acic_list; /* List of the ACLs for that node */ + int acic_index; /* index to the container array */ +}; +typedef struct aci_container AciContainer; + +struct acl_pblock { + int aclpb_state; + +#define ACLPB_ACCESS_ALLOWED_ON_A_ATTR 0x000001 +#define ACLPB_ACCESS_DENIED_ON_ALL_ATTRS 0x000002 +#define ACLPB_ACCESS_ALLOWED_ON_ENTRY 0x000004 +#define ACLPB_ATTR_STAR_MATCHED 0x000008 +#define ACLPB_FOUND_ATTR_RULE 0x000010 +#define ACLPB_SEARCH_BASED_ON_LIST 0x000020 +#define ACLPB_EXECUTING_DENY_HANDLES 0x000040 +#define ACLPB_EXECUTING_ALLOW_HANDLES 0x000080 +#define ACLPB_ACCESS_ALLOWED_USERATTR 0x000100 +#ifdef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES + #define ACLPB_USER_SPECIFIED_ATTARS 0x000200 + #define ACLPB_USER_WANTS_ALL_ATTRS 0x000400 +#endif +#define ACLPB_EVALUATING_FIRST_ATTR 0x000800 +#define ACLPB_FOUND_A_ENTRY_TEST_RULE 0x001000 +#define ACLPB_SEARCH_BASED_ON_ENTRY_LIST 0x002000 +#define ACLPB_DONOT_USE_CONTEXT_ACLS 0x004000 +#define ACLPB_HAS_ACLCB_EVALCONTEXT 0x008000 +#define ACLPB_COPY_EVALCONTEXT 0x010000 +#define ACLPB_MATCHES_ALL_ACLS 0x020000 +#define ACLPB_INITIALIZED 0x040000 +#define ACLPB_INCR_ACLCB_CACHE 0x080000 +#define ACLPB_UPD_ACLCB_CACHE 0x100000 +#define ACLPB_ATTR_RULE_EVALUATED 0x200000 +#define ACLPB_DONOT_EVALUATE_PROXY 0x400000 + + +#define ACLPB_RESET_MASK ( ACLPB_ACCESS_ALLOWED_ON_A_ATTR | ACLPB_ACCESS_DENIED_ON_ALL_ATTRS | \ + ACLPB_ACCESS_ALLOWED_ON_ENTRY | ACLPB_ATTR_STAR_MATCHED | \ + ACLPB_FOUND_ATTR_RULE | ACLPB_EVALUATING_FIRST_ATTR | \ + ACLPB_FOUND_A_ENTRY_TEST_RULE ) +#define ACLPB_STATE_ALL 0x3fffff + + int aclpb_res_type; + + #define ACLPB_NEW_ENTRY 0x100 + #define ACLPB_EFFECTIVE_RIGHTS 0x200 + #define ACLPB_RESTYPE_ALL 0x7ff + + /* + * The bottom bye used to be for rights. It's free now as they have + * been moved to aclpb_access. + */ + + int aclpb_access; + +#define ACLPB_SLAPI_ACL_WRITE_ADD 0x200 +#define ACLPB_SLAPI_ACL_WRITE_DEL 0x400 + + /* stores the requested access during an operation */ + + short aclpb_signature; + short aclpb_type; +#define ACLPB_TYPE_MAIN 1 +#define ACLPB_TYPE_MAIN_STR "Main Block" +#define ACLPB_TYPE_PROXY 2 +#define ACLPB_TYPE_PROXY_STR "Proxy Block" + + Slapi_Entry *aclpb_client_entry; /* A copy of client's entry */ + Slapi_PBlock *aclpb_pblock; /* back to LDAP PBlock */ + int aclpb_optype; /* current optype from pb */ + + /* Current entry/dn/attr evaluation info */ + Slapi_Entry *aclpb_curr_entry; /* current Entry being processed */ + int aclpb_num_entries; + Slapi_DN *aclpb_curr_entry_sdn; /* Entry's SDN */ + Slapi_DN *aclpb_authorization_sdn; /* dn used for authorization */ + + AclAttrEval *aclpb_curr_attrEval; /* Current attr being evaluated */ + struct berval *aclpb_curr_attrVal; /* Value of Current attr */ + Slapi_Entry *aclpb_filter_test_entry; /* Scratch entry */ + aci_t *aclpb_curr_aci; + char *aclpb_Evalattr; /* The last attr evaluated */ + + /* Plist and eval info */ + ACLEvalHandle_t *aclpb_acleval; /* acleval handle for evaluation */ + struct PListStruct_s *aclpb_proplist;/* All the needed property */ + + /* DENY ACI HANDLES */ + aci_t **aclpb_deny_handles; + int aclpb_deny_handles_size; + int aclpb_num_deny_handles; + + /* ALLOW ACI HANDLES */ + aci_t **aclpb_allow_handles; + int aclpb_allow_handles_size; + int aclpb_num_allow_handles; + + /* This is used in the groupdnattr="URL" rule + ** Keep a list of base where searched has been done + */ + char **aclpb_grpsearchbase; + int aclpb_grpsearchbase_size; + int aclpb_numof_bases; + + aclUserGroup *aclpb_groupinfo; + + /* Keep the Group nesting level */ + int aclpb_max_nesting_level; + int aclpb_max_member_sizelimit; + + + /* To keep the results in the cache */ + + int aclpb_last_cache_result; + struct result_cache aclpb_cache_result[ACLPB_MAX_CACHE_RESULTS]; + + /* Index numbers of ACLs selected based on a locality search*/ + char *aclpb_search_base; + int aclpb_base_handles_index[ACLPB_MAX_SELECTED_ACLS]; + int aclpb_handles_index[ACLPB_MAX_SELECTED_ACLS]; + + /* Evaluation context info + ** 1) Context cached from aclcb ( from connection struct ) + ** 2) Context cached from previous entry evaluation + ** 3) current entry evaluation info + */ + aclEvalContext aclpb_curr_entryEval_context; + aclEvalContext aclpb_prev_entryEval_context; + aclEvalContext aclpb_prev_opEval_context; + + /* Currentry anom profile sumamry */ + struct scoped_entry_anominfo aclpb_scoped_entry_anominfo; + + /* Some Statistics gathering */ + PRUint16 aclpb_stat_acllist_scanned; + PRUint16 aclpb_stat_aclres_matched; + PRUint16 aclpb_stat_total_entries; + PRUint16 aclpb_stat_anom_list_scanned; + PRUint16 aclpb_stat_num_copycontext; + PRUint16 aclpb_stat_num_copy_attrs; + PRUint16 aclpb_stat_num_tmatched_acls; + PRUint16 aclpb_stat_unused; + CERTCertificate *aclpb_clientcert; + AciContainer *aclpb_aclContainer; + struct acl_pblock *aclpb_proxy; /* Child proxy block */ + acl_ht_t *aclpb_macro_ht; /* ht for partial macro strs */ + + struct acl_pblock *aclpb_prev; /* Previpous in the chain */ + struct acl_pblock *aclpb_next; /* Next in the chain */ +}; +typedef struct acl_pblock Acl_PBlock; + +/* PBLCOK TYPES */ +typedef enum +{ + ACLPB_BINDDN_PBLOCK, + ACLPB_PROXYDN_PBLOCK, + ACLPB_ALL_PBLOCK +}aclpb_types; + + +#define ACLPB_EVALCONTEXT_CURR 1 +#define ACLPB_EVALCONTEXT_PREV 2 +#define ACLPB_EVALCONTEXT_ACLCB 3 + + + +/* Cleaning/ deallocating/ ... acl_freeBlock() */ +#define ACL_CLEAN_ACLPB 1 +#define ACL_COPY_ACLCB 2 +#define ACL_CLEAN_ACLCB 3 + +/* used to differentiate acl plugins sharing the same lib */ +#define ACL_PLUGIN_IDENTITY 1 +#define ACL_PREOP_PLUGIN_IDENTITY 2 + + +/* start with 50 and then add 50 more as required + * The first ACI_MAX_ELEVEL slots are predefined. + */ +#define ACLPB_INCR_LIST_HANDLES ACI_MAX_ELEVEL + 43 + +#define ACLPB_INCR_BASES 5 + +/* + * acl private block which hangs from connection structure. + * This is allocated the first time an operation is done and freed when the + * connection are cleaned. + * + */ +struct acl_cblock { + + short aclcb_aclsignature; + short aclcb_state; +#define ACLCB_HAS_CACHED_EVALCONTEXT 0x1 + + Slapi_DN *aclcb_sdn; /* Contains bind SDN */ + aclEvalContext aclcb_eval_context; + PRLock *aclcb_lock; /* shared lock */ +}; + +struct acl_groupcache { + short aclg_state; /* status information */ + short aclg_signature; + int aclg_num_userGroups; + aclUserGroup *aclg_first; + aclUserGroup *aclg_last; + PRRWLock *aclg_rwlock; /* lock to monitor the group cache */ +}; +typedef struct acl_groupcache aclGroupCache; + + +/* Type of extensions that can be registered */ +typedef enum +{ + ACL_EXT_OPERATION, /* extension for Operation object */ + ACL_EXT_CONNECTION, /* extension for Connection object */ + ACL_EXT_ALL +}ext_type; + +/* Used to pass data around in acllas.c */ + +typedef struct { + char *clientDn; + char *authType; + int anomUser; + Acl_PBlock *aclpb; + Slapi_Entry *resourceEntry; + +}lasInfo; + + +/* reasons why the subject allowed/denied access--good for logs */ + +typedef enum{ +ACL_REASON_NO_ALLOWS, +ACL_REASON_RESULT_CACHED_DENY, +ACL_REASON_EVALUATED_DENY, /* evaluated deny */ +ACL_REASON_RESULT_CACHED_ALLOW, /* cached allow */ +ACL_REASON_EVALUATED_ALLOW, /* evalauted allow */ +ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS, /* were allows/denies, but none matched */ +ACL_REASON_NONE, /* no reason available */ +ACL_REASON_ANON_ALLOWED, +ACL_REASON_ANON_DENIED, +ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS, +ACL_REASON_EVALCONTEXT_CACHED_ALLOW, +ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED, +ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW +}aclReasonCode_t; + +typedef struct{ + aci_t *deciding_aci; + aclReasonCode_t reason; +}aclResultReason_t; +#define ACL_NO_DECIDING_ACI_INDEX -10 + + +/* Extern declaration for backend state change fnc: acllist.c and aclinit.c */ + +void acl_be_state_change_fnc ( void *handle, char *be_name, int old_state, + int new_state); + + +/* Extern declaration for ATTRs */ + +extern int +DS_LASIpGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg); +extern int +DS_LASDnsGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg); +extern int +DS_LASUserDnGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg); +extern int +DS_LASGroupDnGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg); +extern int +DS_LASEntryGetter(NSErr_t *errp, PList_t subject, PList_t resource, + PList_t auth_info, PList_t global_auth, void *arg); + +extern int +DS_LASCertGetter(NSErr_t *errp, PList_t subject, PList_t resource, + PList_t auth_info, PList_t global_auth, void *arg); + +/* function declartion for LAses supported by DS */ + +extern int DS_LASUserEval(NSErr_t *errp, char *attribute, CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASGroupEval(NSErr_t *errp, char *attribute, CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASUserDnEval(NSErr_t *errp, char *attribute, CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASGroupDnEval(NSErr_t *errp, char *attribute, CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASRoleDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASUserDnAttrEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASAuthMethodEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASGroupDnAttrEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASRoleDnAttrEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +extern int DS_LASUserAttrEval(NSErr_t *errp, char *attribute, + CmpOp_t comparator, + char *pattern, int *cachable, void **las_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth); + +/* other function declaration */ +int aclinit_main(); +int acl_match_substring (struct slapi_filter *f, char *str, int match); +void acl_print_acllib_err(NSErr_t *errp, char * str); +void acl_initBlock ( Slapi_PBlock *pb ); +void acl_freeBlock ( Slapi_PBlock *pb, int state ); +int acl_read_access_allowed_on_entry ( Slapi_PBlock *pb, Slapi_Entry *e, + char **attrs, int access); +int acl_access_allowed_modrdn ( Slapi_PBlock *pb, Slapi_Entry *e, char *attr, + struct berval *val, int access); +int acl_read_access_allowed_on_attr ( Slapi_PBlock *pb, Slapi_Entry *e, char *attr, + struct berval *val, int access); +void acl_set_acllist (Slapi_PBlock *pb, int scope, char *base); +void acl_gen_err_msg(int access, char *edn, char *attr, char **errbuf); +void acl_modified ( Slapi_PBlock *pb, int optype, char *dn, void *change); +int acl_access_allowed_disjoint_resource( Slapi_PBlock *pb, Slapi_Entry *e, + char *attr, struct berval *val, int access ); +int acl_access_allowed_main ( Slapi_PBlock *pb, Slapi_Entry *e, char **attrs, + struct berval *val, int access , int flags, char **errbuf); +int acl_access_allowed( Slapi_PBlock *pb, Slapi_Entry *e, char *attr, + struct berval *val, int access ); +int acl_verify_syntax(const Slapi_DN *e_sdn, const struct berval *bval); +aclUserGroup * acl_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn); +void acl_print_acllib_err (NSErr_t *errp , char * str); +int acl_check_mods( Slapi_PBlock *pb, Slapi_Entry *e, LDAPMod **mods, char **errbuf ); +int acl_verify_aci_syntax (Slapi_Entry *e, char **errbuf); +char * acl__access2str(int access); +void acl_strcpy_special (char *d, char *s); +int acl_parse(char * str, aci_t *aci_item); +char * acl_access2str ( int access ); +int acl_init_ext (); +void * acl_get_ext (ext_type type, void *object); +void acl_set_ext (ext_type type, void *object, void *data); +void acl_reset_ext_status (ext_type type, void *object); +void acl_init_op_ext ( Slapi_PBlock *pb , int type, char *dn, int copy); +void * acl_operation_ext_constructor (void *object, void *parent ); +void acl_operation_ext_destructor ( void *ext, void *object, void *parent ); +void * acl_conn_ext_constructor (void *object, void *parent ); +void acl_conn_ext_destructor ( void *ext, void *object, void *parent ); +void acl_clean_aclEval_context ( aclEvalContext *clean_me, int scrub_only ); +void acl_copyEval_context ( struct acl_pblock *aclpb, aclEvalContext *src, + aclEvalContext *dest , int copy_attr_only ); +struct acl_pblock * acl_get_aclpb ( Slapi_PBlock *pb, int type ); +int acl_client_anonymous ( Slapi_PBlock *pb ); +short acl_get_aclsignature(); +void acl_set_aclsignature( short value); +void acl_regen_aclsignature(); +struct acl_pblock * acl_new_proxy_aclpb( Slapi_PBlock *pb ); +void acl_set_authorization_dn( Slapi_PBlock *pb, char *dn, int type ); +int acl_get_proxyauth_dn( Slapi_PBlock *pb, char **proxydnp, + char **errtextp ); +void acl_init_aclpb ( Slapi_PBlock *pb , Acl_PBlock *aclpb, + const char *dn, int copy_from_aclcb); +int acl_create_aclpb_pool (); +int acl_skip_access_check ( Slapi_PBlock *pb, Slapi_Entry *e ); + +int aclext_alloc_lockarray (); + +int aclutil_str_appened(char **str1, const char *str2); +void aclutil_print_err (int rv , const Slapi_DN *sdn, + const struct berval* val, char **errbuf); +void aclutil_print_aci (aci_t *aci_item, char *type); +short aclutil_gen_signature ( short c_signature ); +void aclutil_print_resource( struct acl_pblock *aclpb, char *right , char *attr, char *clientdn ); +char * aclutil_expand_paramString ( char *str, Slapi_Entry *e ); + + +void acllist_init_scan (Slapi_PBlock *pb, int scope, char *base); +aci_t * acllist_get_first_aci (Acl_PBlock *aclpb, PRUint32 *cookie ); +aci_t * acllist_get_next_aci ( Acl_PBlock *aclpb, aci_t *curraci, PRUint32 *cookie ); +aci_t * acllist_get_aci_new (); +void acllist_free_aci (aci_t *item); +void acllist_acicache_READ_UNLOCK(void); +void acllist_acicache_READ_LOCK(void); +void acllist_acicache_WRITE_UNLOCK(void); +void acllist_acicache_WRITE_LOCK(void); +void acllist_aciscan_update_scan ( Acl_PBlock *aclpb, char *edn ); +int acllist_remove_aci_needsLock( const Slapi_DN *sdn, const struct berval *attr ); +int acllist_insert_aci_needsLock( const Slapi_DN *e_sdn, const struct berval* aci_attr); +int acllist_init (); +int acllist_moddn_aci_needsLock ( Slapi_DN *oldsdn, char *newdn ); +void acllist_print_tree ( Avlnode *root, int *depth, char *start, char *side); +AciContainer *acllist_get_aciContainer_new ( ); +void acllist_done_aciContainer ( AciContainer *); + +aclUserGroup* aclg_find_userGroup (char *n_dn); +void aclg_regen_ugroup_signature( aclUserGroup *ugroup); +void aclg_markUgroupForRemoval ( aclUserGroup *u_group ); +void aclg_reader_incr_ugroup_refcnt(aclUserGroup* u_group); +int aclg_numof_usergroups(void); +int aclgroup_init (); +void aclg_regen_group_signature (); +void aclg_reset_userGroup ( struct acl_pblock *aclpb ); +void aclg_init_userGroup ( struct acl_pblock *aclpb, const char *dn , int got_lock); +aclUserGroup * aclg_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn); + +void aclg_lock_groupCache (int type ); +void aclg_unlock_groupCache (int type ); + +int aclanom_init(); +int aclanom_match_profile (Slapi_PBlock *pb, struct acl_pblock *aclpb, + Slapi_Entry *e, char *attr, int access); +void aclanom_get_suffix_info(Slapi_Entry *e, struct acl_pblock *aclpb ); +void aclanom_invalidateProfile(); +typedef enum{ + DONT_TAKE_ACLCACHE_READLOCK, + DO_TAKE_ACLCACHE_READLOCK, + DONT_TAKE_ACLCACHE_WRITELOCK, + DO_TAKE_ACLCACHE_WRITELOCK +}acl_lock_flag_t; +void aclanom_gen_anomProfile (acl_lock_flag_t lock_flag); +int aclanom_is_client_anonymous ( Slapi_PBlock *pb ); +int aclinit_main (); +typedef struct aclinit_handler_callback_data { +#define ACL_ADD_ACIS 1 +#define ACL_REMOVE_ACIS 0 + int op; + int retCode; + acl_lock_flag_t lock_flag; +}aclinit_handler_callback_data_t; +int +aclinit_search_and_update_aci ( int thisbeonly, const Slapi_DN *base, + char *be_name, int scope, int op, + acl_lock_flag_t lock_flag); +void *aclplugin_get_identity(int plug); +int +acl_dn_component_match( const char *ndn, char *match_this, int component_number); +char * +acl_match_macro_in_target( const char *ndn, char *match_this, + char *macro_ptr); +char* get_next_component(char *dn, int *index); +int acl_match_prefix( char *macro_prefix, const char *ndn, + int *exact_match); +char * +get_this_component(char *dn, int *index); +int +acl_find_comp_end( char * s); +char * +acl_replace_str(char * s, char *substr, char* replace_with); +int acl_strstr(char * s, char *substr); +int aclutil_evaluate_macro( char * rule, lasInfo *lasinfo, + acl_eval_types evalType ); + +/* acl hash table functions */ +void acl_ht_add_and_freeOld(acl_ht_t * acl_ht, PLHashNumber key,char *value); +acl_ht_t *acl_ht_new(void); +void acl_ht_free_all_entries_and_values( acl_ht_t *acl_ht); +void acl_ht_remove( acl_ht_t *acl_ht, PLHashNumber key); +void *acl_ht_lookup( acl_ht_t *acl_ht, PLHashNumber key); +void acl_ht_display_ht( acl_ht_t *acl_ht); + +/* acl get effective rights */ +int +acl_get_effective_rights ( Slapi_PBlock *pb, Slapi_Entry *e, + char **attrs, struct berval *val, int access, char **errbuf ); + +char* aclutil__access_str (int type , char str[]); + +#endif /* _ACL_H_ */ diff --git a/ldap/servers/plugins/acl/acl_ext.c b/ldap/servers/plugins/acl/acl_ext.c new file mode 100644 index 00000000..28a9ce18 --- /dev/null +++ b/ldap/servers/plugins/acl/acl_ext.c @@ -0,0 +1,968 @@ +/** 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" + +static void acl__done_aclpb ( struct acl_pblock *aclpb ); +static void acl__dump_stats ( struct acl_pblock *aclpb , const char *block_type); +static Acl_PBlock * acl__get_aclpb_from_pool ( ); +static int acl__put_aclpb_back_to_pool ( Acl_PBlock *aclpb ); +static Acl_PBlock * acl__malloc_aclpb ( ); +static char * acl__get_aclpb_type ( Acl_PBlock *aclpb ); +static PRLock *aclext_get_lock (); + + +struct acl_pbqueue { + Acl_PBlock *aclq_free; + Acl_PBlock *aclq_busy; + short aclq_nfree; + short aclq_nbusy; + PRLock *aclq_lock; +}; +typedef struct acl_pbqueue Acl_PBqueue; + +static Acl_PBqueue *aclQueue; + +/* structure with information for each extension */ +typedef struct acl_ext +{ + char *object_name; /* name of the object extended */ + int object_type; /* handle to the extended object */ + int handle; /* extension handle */ +} acl_ext; + +static acl_ext acl_ext_list [ACL_EXT_ALL]; + +/* + * EXTENSION INITIALIZATION, CONSTRUCTION, & DESTRUCTION + * + */ +int +acl_init_ext () +{ + int rc; + + acl_ext_list[ACL_EXT_OPERATION].object_name = SLAPI_EXT_OPERATION; + + rc = slapi_register_object_extension(plugin_name, SLAPI_EXT_OPERATION, + acl_operation_ext_constructor, + acl_operation_ext_destructor, + &acl_ext_list[ACL_EXT_OPERATION].object_type, + &acl_ext_list[ACL_EXT_OPERATION].handle); + + if ( rc != 0 ) return rc; + + acl_ext_list[ACL_EXT_CONNECTION].object_name = SLAPI_EXT_CONNECTION; + rc = slapi_register_object_extension(plugin_name, SLAPI_EXT_CONNECTION, + acl_conn_ext_constructor, + acl_conn_ext_destructor, + &acl_ext_list[ACL_EXT_CONNECTION].object_type, + &acl_ext_list[ACL_EXT_CONNECTION].handle); + + return rc; + +} + +/* Interface to get the extensions */ +void * +acl_get_ext (ext_type type, void *object) +{ + struct acl_ext ext; + void *data; + + if ( type >= ACL_EXT_ALL ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Invalid extension type:%d\n", type ); + return NULL; + } + + /* find the requested extension */ + ext = acl_ext_list [type]; + data = slapi_get_object_extension(ext.object_type, object, ext.handle); + + return data; +} + +void +acl_set_ext (ext_type type, void *object, void *data) +{ + if ( type >= 0 && type < ACL_EXT_ALL ) + { + struct acl_ext ext = acl_ext_list [type]; + slapi_set_object_extension ( ext.object_type, object, ext.handle, data ); + } +} + +/**************************************************************************** + * Global lock array so that private extension between connection and operation + * co-exist + * + ******************************************************************************/ +struct ext_lockArray { + PRLock **lockArray; + int numlocks; +}; + +static struct ext_lockArray extLockArray; + +/* PKBxxx: make this a configurable. Start with 2 * maxThreads */ +#define ACLEXT_MAX_LOCKS 40 + +int +aclext_alloc_lockarray ( ) +{ + + int i; + PRLock *lock; + + extLockArray.lockArray = + (PRLock **) slapi_ch_calloc ( ACLEXT_MAX_LOCKS, sizeof ( PRLock *) ); + + for ( i =0; i < ACLEXT_MAX_LOCKS; i++) { + if (NULL == (lock = PR_NewLock()) ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "Unable to allocate locks used for private extension\n"); + return 1; + } + extLockArray.lockArray[i] = lock; + } + extLockArray.numlocks = ACLEXT_MAX_LOCKS; + return 0; +} +static PRUint32 slot_id =0; +static PRLock * +aclext_get_lock () +{ + + PRUint16 slot = slot_id % ACLEXT_MAX_LOCKS; + slot_id++; + return ( extLockArray.lockArray[slot] ); + +} +/****************************************************************************/ +/* CONNECTION EXTENSION SPECIFIC */ +/****************************************************************************/ +void * +acl_conn_ext_constructor ( void *object, void *parent ) +{ + struct acl_cblock *ext = NULL; + + ext = (struct acl_cblock * ) slapi_ch_calloc (1, sizeof (struct acl_cblock ) ); + if (( ext->aclcb_lock = aclext_get_lock () ) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "Unable to get Read/Write lock for CONNECTION extension\n"); + slapi_ch_free ( (void **) &ext ); + return NULL; + } + ext->aclcb_sdn = slapi_sdn_new (); + /* store the signatures */ + ext->aclcb_aclsignature = acl_get_aclsignature(); + ext->aclcb_state = -1; + return ext; + + +} + +void +acl_conn_ext_destructor ( void *ext, void *object, void *parent ) +{ + struct acl_cblock *aclcb = ext; + PRLock *shared_lock; + + if ( NULL == aclcb ) return; + PR_Lock ( aclcb->aclcb_lock ); + shared_lock = aclcb->aclcb_lock; + acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /* clean*/ ); + slapi_sdn_free ( &aclcb->aclcb_sdn ); + aclcb->aclcb_lock = NULL; + slapi_ch_free ( (void **) &aclcb ); + + PR_Unlock ( shared_lock ); +} + +/****************************************************************************/ +/* OPERATION EXTENSION SPECIFIC */ +/****************************************************************************/ +void * +acl_operation_ext_constructor ( void *object, void *parent ) +{ + Acl_PBlock *aclpb = NULL; + + TNF_PROBE_0_DEBUG(acl_operation_ext_constructor_start ,"ACL",""); + + /* This means internal operations */ + if ( NULL == parent) { + + TNF_PROBE_1_DEBUG(acl_operation_ext_constructor_end ,"ACL","", + tnf_string,internal_op,""); + + return NULL; + } + + aclpb = acl__get_aclpb_from_pool(); + if ( NULL == aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Operation extension allocation Failed\n"); + } + + TNF_PROBE_0_DEBUG(acl_operation_ext_constructor_end ,"ACL",""); + + return aclpb; + +} + +void +acl_operation_ext_destructor ( void *ext, void *object, void *parent ) +{ + + struct acl_cblock *aclcb = NULL; + struct acl_pblock *aclpb = NULL; + + TNF_PROBE_0_DEBUG(acl_operation_ext_destructor_start ,"ACL",""); + + if ( (NULL == parent ) || (NULL == ext)) { + TNF_PROBE_1_DEBUG(acl_operation_ext_destructor_end ,"ACL","", + tnf_string,internal_op,""); + + return; + } + + aclpb = (Acl_PBlock *) ext; + + if ( (NULL == aclpb) || + (NULL == aclpb->aclpb_pblock) || + (!(aclpb->aclpb_state & ACLPB_INITIALIZED))) + goto clean_aclpb; + + /* get the connection extension */ + aclcb = (struct acl_cblock *) acl_get_ext ( ACL_EXT_CONNECTION, parent ); + + /* We are about to get out of this connection. Move all the + ** cached information to the acl private block which hangs + ** from the connection struct. + */ + if ( aclcb && aclcb->aclcb_lock && + ( (aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE ) || + (aclpb->aclpb_state & ACLPB_INCR_ACLCB_CACHE ) ) ) { + + aclEvalContext *c_evalContext; + int attr_only = 0; + PRLock *shared_lock = aclcb->aclcb_lock; + + if (aclcb->aclcb_lock ) PR_Lock ( shared_lock ); + else { + goto clean_aclpb; + } + if ( !aclcb->aclcb_lock ) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "aclcb lock released! aclcb cache can't be refreshed\n"); + PR_Unlock ( shared_lock ); + goto clean_aclpb; + } + + /* We need to refresh the aclcb cache */ + if ( aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE ) + acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /* clean*/ ); + if ( aclpb->aclpb_prev_entryEval_context.acle_numof_attrs ) { + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + } else { + c_evalContext = &aclpb->aclpb_curr_entryEval_context; + } + + if (( aclpb->aclpb_state & ACLPB_INCR_ACLCB_CACHE ) && + ! ( aclpb->aclpb_state & ACLPB_UPD_ACLCB_CACHE )) + attr_only = 1; + + acl_copyEval_context ( NULL, c_evalContext, &aclcb->aclcb_eval_context, attr_only ); + + aclcb->aclcb_aclsignature = aclpb->aclpb_signature; + if ( aclcb->aclcb_sdn && aclpb->aclpb_authorization_sdn && + (0 != slapi_sdn_compare ( aclcb->aclcb_sdn, + aclpb->aclpb_authorization_sdn ) ) ) { + slapi_sdn_set_ndn_byval( aclcb->aclcb_sdn, + slapi_sdn_get_ndn ( aclpb->aclpb_authorization_sdn ) ); + } + aclcb->aclcb_state = 0; + aclcb->aclcb_state |= ACLCB_HAS_CACHED_EVALCONTEXT; + + PR_Unlock ( shared_lock ); + } + +clean_aclpb: + if ( aclpb ) { + + if ( aclpb->aclpb_proxy ) { + TNF_PROBE_0_DEBUG(acl_proxy_aclpbdoneback_start ,"ACL",""); + + acl__done_aclpb( aclpb->aclpb_proxy ); + + /* Put back to the Pool */ + acl__put_aclpb_back_to_pool ( aclpb->aclpb_proxy ); + aclpb->aclpb_proxy = NULL; + TNF_PROBE_0_DEBUG(acl_proxy_aclpbdoneback_end ,"ACL",""); + + } + + TNF_PROBE_0_DEBUG(acl_aclpbdoneback_start ,"ACL",""); + + acl__done_aclpb( aclpb); + acl__put_aclpb_back_to_pool ( aclpb ); + + TNF_PROBE_0_DEBUG(acl_aclpbdoneback_end ,"ACL",""); + + } + + TNF_PROBE_0_DEBUG(acl_operation_ext_destructor_end ,"ACL",""); + +} + +/****************************************************************************/ +/* FUNCTIONS TO MANAGE THE ACLPB POOL */ +/****************************************************************************/ + +/* + * Get the right acl pblock + */ +struct acl_pblock * +acl_get_aclpb ( Slapi_PBlock *pb, int type ) +{ + Acl_PBlock *aclpb = NULL; + void *op = NULL; + + slapi_pblock_get ( pb, SLAPI_OPERATION, &op ); + aclpb = (Acl_PBlock *) acl_get_ext ( ACL_EXT_OPERATION, op ); + if (NULL == aclpb ) return NULL; + + if ( type == ACLPB_BINDDN_PBLOCK ) + return aclpb; + else if ( type == ACLPB_PROXYDN_PBLOCK ) + return aclpb->aclpb_proxy; + else + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "acl_get_aclpb: Invalid aclpb type %d\n", type ); + return NULL; +} +/* + * Create a new proxy acl pblock + * + */ +struct acl_pblock * +acl_new_proxy_aclpb( Slapi_PBlock *pb ) +{ + void *op; + Acl_PBlock *aclpb = NULL; + Acl_PBlock *proxy_aclpb = NULL; + + slapi_pblock_get ( pb, SLAPI_OPERATION, &op ); + aclpb = (Acl_PBlock *) acl_get_ext ( ACL_EXT_OPERATION, op ); + if (NULL == aclpb ) return NULL; + + proxy_aclpb = acl__get_aclpb_from_pool(); + if (NULL == proxy_aclpb) return NULL; + proxy_aclpb->aclpb_type = ACLPB_TYPE_PROXY; + + aclpb->aclpb_proxy = proxy_aclpb; + + return proxy_aclpb; + +} +static int +acl__handle_config_entry (Slapi_Entry *e, void *callback_data ) +{ + *(int * )callback_data = slapi_entry_attr_get_int( e, "nsslapd-threadnumber"); + + return 0; +} + +/* + * Create a pool of acl pblock. Created during the ACL plugin + * initialization. + */ +int +acl_create_aclpb_pool () +{ + + Acl_PBlock *aclpb; + Acl_PBlock *prev_aclpb; + Acl_PBlock *first_aclpb; + int i; + int maxThreads= 0; + + slapi_search_internal_callback( "cn=config", LDAP_SCOPE_BASE, "(objectclass=*)", + NULL, 0 /* attrsonly */, + &maxThreads/* callback_data */, + NULL /* controls */, + NULL /* result_callback */, + acl__handle_config_entry, + NULL /* referral_callback */); + + /* Create a pool pf aclpb */ + maxThreads = 2 * maxThreads; + + aclQueue = ( Acl_PBqueue *) slapi_ch_calloc ( 1, sizeof (Acl_PBqueue) ); + aclQueue->aclq_lock = PR_NewLock(); + + if ( NULL == aclQueue->aclq_lock ) { + /* ERROR */ + return 1; + } + + prev_aclpb = NULL; + first_aclpb = NULL; + for ( i = 0; i < maxThreads; i++ ) { + aclpb = acl__malloc_aclpb (); + if ( 0 == i) first_aclpb = aclpb; + + aclpb->aclpb_prev = prev_aclpb; + if ( prev_aclpb ) prev_aclpb->aclpb_next = aclpb; + prev_aclpb = aclpb; + } + + /* Since this is the begining, everybody is in free list */ + aclQueue->aclq_free = first_aclpb; + + aclQueue->aclq_nfree = maxThreads; + return 0; +} + +/* + * Get a FREE acl pblock from the pool. + * + */ +static Acl_PBlock * +acl__get_aclpb_from_pool ( ) +{ + Acl_PBlock *aclpb = NULL; + Acl_PBlock *t_aclpb = NULL; + + + PR_Lock (aclQueue->aclq_lock ); + + /* Get the first aclpb from the FREE List */ + aclpb = aclQueue->aclq_free; + if ( aclpb ) { + t_aclpb = aclpb->aclpb_next; + if ( t_aclpb ) t_aclpb->aclpb_prev = NULL; + aclQueue->aclq_free = t_aclpb; + + /* make the this an orphon */ + aclpb->aclpb_prev = aclpb->aclpb_next = NULL; + + aclQueue->aclq_nfree--; + } else { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Unable to find a free aclpb\n"); + aclpb = acl__malloc_aclpb (); + } + + + /* Now move it to the FRONT of busy list */ + t_aclpb = aclQueue->aclq_busy; + aclpb->aclpb_next = t_aclpb; + if ( t_aclpb ) t_aclpb->aclpb_prev = aclpb; + aclQueue->aclq_busy = aclpb; + aclQueue->aclq_nbusy++; + + PR_Unlock (aclQueue->aclq_lock ); + + return aclpb; +} +/* + * Put the acl pblock into the FREE pool. + * + */ +static int +acl__put_aclpb_back_to_pool ( Acl_PBlock *aclpb ) +{ + + Acl_PBlock *p_aclpb, *n_aclpb; + + PR_Lock (aclQueue->aclq_lock ); + + /* Remove it from the busy list */ + n_aclpb = aclpb->aclpb_next; + p_aclpb = aclpb->aclpb_prev; + + if ( p_aclpb ) { + p_aclpb->aclpb_next = n_aclpb; + if ( n_aclpb ) n_aclpb->aclpb_prev = p_aclpb; + } else { + aclQueue->aclq_busy = n_aclpb; + if ( n_aclpb ) n_aclpb->aclpb_prev = NULL; + } + aclQueue->aclq_nbusy--; + + + /* Put back to the FREE list */ + aclpb->aclpb_prev = NULL; + n_aclpb = aclQueue->aclq_free; + aclpb->aclpb_next = n_aclpb; + if ( n_aclpb ) n_aclpb->aclpb_prev = aclpb; + aclQueue->aclq_free = aclpb; + aclQueue->aclq_nfree++; + + PR_Unlock (aclQueue->aclq_lock ); + + return 0; +} + +/* + * Allocate the basic acl pb + * + */ +static Acl_PBlock * +acl__malloc_aclpb ( ) +{ + Acl_PBlock *aclpb = NULL; + + + aclpb = ( Acl_PBlock *) slapi_ch_calloc ( 1, sizeof ( Acl_PBlock) ); + + /* Now set the propert we need for ACL evaluations */ + if ((aclpb->aclpb_proplist = PListNew(NULL)) == NULL) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to allocate the aclprop PList\n"); + return NULL; + } + + if (PListInitProp(aclpb->aclpb_proplist, 0, DS_PROP_ACLPB, aclpb, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the ACL PBLOCK in the Plist\n"); + return NULL; + } + if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_USERDN, aclpb, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the USER DN in the Plist\n"); + return NULL; + } + if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_AUTHTYPE, aclpb, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the AUTH TYPE in the Plist\n"); + return NULL; + } + if (PListInitProp(aclpb->aclpb_proplist, 0, DS_ATTR_ENTRY, aclpb, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the ENTRY TYPE in the Plist\n"); + return NULL; + } + + /* + * ACL_ATTR_IP and ACL_ATTR_DNS are initialized lazily in the + * IpGetter and DnsGetter functions. + * They are removed from the aclpb property list at acl__aclpb_done() + * time. + */ + + /* allocate the acleval struct */ + aclpb->aclpb_acleval = (ACLEvalHandle_t *) ACL_EvalNew(NULL, NULL); + if (aclpb->aclpb_acleval == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to allocate the acleval block\n"); + return NULL; + } + /* + * This is a libaccess routine. + * Need to setup subject and resource property information + */ + + ACL_EvalSetSubject(NULL, aclpb->aclpb_acleval, aclpb->aclpb_proplist); + + /* allocate some space for attr name */ + aclpb->aclpb_Evalattr = (char *) slapi_ch_malloc (ACLPB_MAX_ATTR_LEN); + + aclpb->aclpb_deny_handles = (aci_t **) slapi_ch_calloc (1, + ACLPB_INCR_LIST_HANDLES * sizeof (aci_t *)); + + aclpb->aclpb_allow_handles = (aci_t **) slapi_ch_calloc (1, + ACLPB_INCR_LIST_HANDLES * sizeof (aci_t *)); + + aclpb->aclpb_deny_handles_size = ACLPB_INCR_LIST_HANDLES; + aclpb->aclpb_allow_handles_size = ACLPB_INCR_LIST_HANDLES; + + /* allocate the array for bases */ + aclpb->aclpb_grpsearchbase = (char **) + slapi_ch_malloc (ACLPB_INCR_BASES * sizeof(char *)); + aclpb->aclpb_grpsearchbase_size = ACLPB_INCR_BASES; + aclpb->aclpb_numof_bases = 0; + + /* Make sure aclpb_search_base is initialized to NULL..tested elsewhere! */ + aclpb->aclpb_search_base = NULL; + + aclpb->aclpb_authorization_sdn = slapi_sdn_new (); + aclpb->aclpb_curr_entry_sdn = slapi_sdn_new(); + + aclpb->aclpb_aclContainer = acllist_get_aciContainer_new (); + + /* hash table to store macro matched values from targets */ + aclpb->aclpb_macro_ht = acl_ht_new(); + + return aclpb; + +} + +/* Initializes the aclpb */ +void +acl_init_aclpb ( Slapi_PBlock *pb , Acl_PBlock *aclpb, const char *dn, int copy_from_aclcb) +{ + struct acl_cblock *aclcb = NULL; + char *authType; + void *conn; + unsigned long op_type; + + + if ( NULL == aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "acl_init_aclpb:No ACLPB\n"); + return; + } + + /* See if we have initialized already */ + if (aclpb->aclpb_state & ACLPB_INITIALIZED) return; + + slapi_pblock_get ( pb, SLAPI_OPERATION_TYPE, &op_type ); + if ( op_type == SLAPI_OPERATION_BIND || op_type == SLAPI_OPERATION_UNBIND ) + return; + + /* We indicate the initialize here becuase, if something goes wrong, it's cleaned up + ** properly. + */ + aclpb->aclpb_state = ACLPB_INITIALIZED; + + /* We make an anonymous user a non null dn which is empty */ + if (dn && *dn != '\0' ) + slapi_sdn_set_ndn_byval ( aclpb->aclpb_authorization_sdn, dn ); + else + slapi_sdn_set_ndn_byval ( aclpb->aclpb_authorization_sdn, "" ); + + /* reset scoped entry cache to be empty */ + aclpb->aclpb_scoped_entry_anominfo.anom_e_nummatched = 0; + + if (PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_USERDN, + slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn), 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the USER DN in the Plist\n"); + return; + } + slapi_pblock_get ( pb, SLAPI_OPERATION_AUTHTYPE, &authType ); + if (PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_AUTHTYPE, authType, 0) < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to set the AUTH TYPE in the Plist\n"); + return; + } + /* PKBxxx: We should be getting it from the OP struct */ + slapi_pblock_get ( pb, SLAPI_CONN_CERT, &aclpb->aclpb_clientcert ); + + /* See if the we have already a cached info about user's group */ + aclg_init_userGroup ( aclpb, dn, 0 /* get lock */ ); + + slapi_pblock_get( pb, SLAPI_BE_MAXNESTLEVEL, &aclpb->aclpb_max_nesting_level ); + slapi_pblock_get( pb, SLAPI_SEARCH_SIZELIMIT, &aclpb->aclpb_max_member_sizelimit ); + if ( aclpb->aclpb_max_member_sizelimit == 0 ) { + aclpb->aclpb_max_member_sizelimit = SLAPD_DEFAULT_LOOKTHROUGHLIMIT; + } + slapi_pblock_get( pb, SLAPI_OPERATION_TYPE, &aclpb->aclpb_optype ); + + aclpb->aclpb_signature = acl_get_aclsignature(); + aclpb->aclpb_last_cache_result = 0; + aclpb->aclpb_pblock = pb; + PR_ASSERT ( aclpb->aclpb_pblock != NULL ); + + /* get the connection */ + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn); + aclcb = (struct acl_cblock *) acl_get_ext ( ACL_EXT_CONNECTION, conn ); + + if (NULL == aclcb || NULL == aclcb->aclcb_lock) { + /* This could happen if the client is dead and we are in + ** process of abondoning this operation + */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "No CONNECTION extension\n"); + + } else if ( aclcb->aclcb_state == -1 ) { + /* indicate that we need to update the cache */ + aclpb->aclpb_state |= ACLPB_UPD_ACLCB_CACHE; + aclcb->aclcb_state = 0; /* Nore this is ACLCB and not ACLPB */ + + } else if ( copy_from_aclcb ){ + char *cdn; + Slapi_DN *c_sdn; /* client SDN */ + + /* check if the operation is abandoned or not.*/ + if ( slapi_op_abandoned ( pb ) ) { + return; + } + + slapi_pblock_get ( pb, SLAPI_CONN_DN, &cdn ); /* We *must* free cdn! */ + c_sdn = slapi_sdn_new_dn_passin( cdn ); + PR_Lock ( aclcb->aclcb_lock ); + /* + * since PR_Lock is taken, + * we can mark the connection extension ok to be destroyed. + */ + if ( (aclcb->aclcb_aclsignature != acl_get_aclsignature()) || + ( (NULL == cdn) && aclcb->aclcb_sdn ) || + (cdn && (NULL == aclcb->aclcb_sdn )) || + (cdn && aclcb->aclcb_sdn && ( 0 != slapi_sdn_compare ( c_sdn, aclcb->aclcb_sdn ) ))) { + + /* cleanup the aclcb cache */ + acl_clean_aclEval_context ( &aclcb->aclcb_eval_context, 0 /*clean*/ ); + aclcb->aclcb_state = 0; + aclcb->aclcb_aclsignature = 0; + slapi_sdn_done ( aclcb->aclcb_sdn ); + } + slapi_sdn_free ( &c_sdn ); + + /* COPY the cached information from ACLCB --> ACLPB */ + if ( aclcb->aclcb_state & ACLCB_HAS_CACHED_EVALCONTEXT) { + acl_copyEval_context ( aclpb, &aclcb->aclcb_eval_context , + &aclpb->aclpb_prev_opEval_context, 0 ); + aclpb->aclpb_state |= ACLPB_HAS_ACLCB_EVALCONTEXT; + } + PR_Unlock ( aclcb->aclcb_lock ); + } + +} + +/* Cleans up the aclpb */ +static void +acl__done_aclpb ( struct acl_pblock *aclpb ) +{ + + int i; + int dump_aclpb_info = 0; + char *ds_attr_userdn=NULL; /* for finding userdn for freeing */ + int rc=-1; + char *tmp_ptr=NULL; + + /* + ** First, let's do some sanity checks to see if we have everything what + ** it should be. + */ + + /* Nothing needs to be cleaned up in this case */ + if ( !aclpb->aclpb_state & ACLPB_INITIALIZED) + return; + + /* Check the state */ + if (aclpb->aclpb_state & ~ACLPB_STATE_ALL) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "The aclpb.state value (%d) is incorrect. Exceeded the limit (%d)\n", + aclpb->aclpb_state, ACLPB_STATE_ALL); + dump_aclpb_info = 1; + + } + + /* acl__dump_stats ( aclpb, acl__get_aclpb_type(aclpb)); */ + + /* reset the usergroup cache */ + aclg_reset_userGroup ( aclpb ); + + if ( aclpb->aclpb_res_type & ~ACLPB_RESTYPE_ALL ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "The aclpb res_type value (%d) has exceeded. Limit is (%d)\n", + aclpb->aclpb_res_type, ACLPB_RESTYPE_ALL, 0 ); + dump_aclpb_info = 1; + } + + if ( dump_aclpb_info ) { + const char *ndn; + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "ACLPB value is:%p\n", aclpb, 0,0 ); + + ndn = slapi_sdn_get_ndn ( aclpb->aclpb_curr_entry_sdn ); + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "curr_entry:%p num_entries:%d curr_dn:%p\n", + aclpb->aclpb_curr_entry ? (char *) aclpb->aclpb_curr_entry : "NULL", + aclpb->aclpb_num_entries, + ndn ? ndn : "NULL"); + + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Last attr:%p, Plist:%p acleval: %p\n", + aclpb->aclpb_Evalattr ? aclpb->aclpb_Evalattr : "NULL", + aclpb->aclpb_proplist ? (char *) aclpb->aclpb_proplist : "NULL", + aclpb->aclpb_acleval ? (char *) aclpb->aclpb_acleval : "NULL" ); + } + + /* Now Free the contents or clean it */ + slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn ); + if (aclpb->aclpb_Evalattr) + aclpb->aclpb_Evalattr[0] = '\0'; + + /* deallocate the contents of the base array */ + for (i=0; i < aclpb->aclpb_numof_bases; i++) { + if (aclpb->aclpb_grpsearchbase[i]) + slapi_ch_free ( (void **)&aclpb->aclpb_grpsearchbase[i] ); + } + aclpb->aclpb_numof_bases = 0; + + acl_clean_aclEval_context ( &aclpb->aclpb_prev_opEval_context, 0 /*claen*/ ); + acl_clean_aclEval_context ( &aclpb->aclpb_prev_entryEval_context, 0 /*clean*/ ); + acl_clean_aclEval_context ( &aclpb->aclpb_curr_entryEval_context, 0/*clean*/ ); + + if ( aclpb->aclpb_client_entry ) slapi_entry_free ( aclpb->aclpb_client_entry ); + aclpb->aclpb_client_entry = NULL; + + slapi_sdn_done ( aclpb->aclpb_authorization_sdn ); + aclpb->aclpb_pblock = NULL; + + if ( aclpb->aclpb_search_base ) + slapi_ch_free ( (void **) &aclpb->aclpb_search_base ); + for ( i=0; i < aclpb->aclpb_num_deny_handles; i++ ) + aclpb->aclpb_deny_handles[i] = NULL; + aclpb->aclpb_num_deny_handles = 0; + + for ( i=0; i < aclpb->aclpb_num_allow_handles; i++ ) + aclpb->aclpb_allow_handles[i] = NULL; + aclpb->aclpb_num_allow_handles = 0; + + /* clear results cache */ + memset((char*)aclpb->aclpb_cache_result, 0, + sizeof(struct result_cache)*aclpb->aclpb_last_cache_result); + aclpb->aclpb_last_cache_result = 0; + aclpb->aclpb_handles_index[0] = -1; + aclpb->aclpb_base_handles_index[0] = -1; + + aclpb->aclpb_stat_acllist_scanned = 0; + aclpb->aclpb_stat_aclres_matched = 0; + aclpb->aclpb_stat_total_entries = 0; + aclpb->aclpb_stat_anom_list_scanned = 0; + aclpb->aclpb_stat_num_copycontext = 0; + aclpb->aclpb_stat_num_copy_attrs = 0; + aclpb->aclpb_stat_num_tmatched_acls = 0; + + aclpb->aclpb_clientcert = NULL; + aclpb->aclpb_proxy = NULL; + + acllist_done_aciContainer ( aclpb->aclpb_aclContainer ); + + /* + * Here, decide which things need to be freed/removed/whatever from the + * aclpb_proplist. + */ + + /* + * The DS_ATTR_DNS property contains the name of the client machine. + * + * The value pointed to by this property is stored in the pblock--it + * points to the SLAPI_CLIENT_DNS object. So, that memory will + * be freed elsewhere. + * + * It's removed here from the aclpb_proplist as it would be an error to + * allow it to persist in the aclpb which is an operation time thing. + * If we leave it here the next time this aclpb gets used, the DnsGetter + * is not called by LASDnsEval/ACL_GetAttribute() as it thinks the + * ACL_ATTR_DNS has already been initialized. + * + */ + + if ((rc = PListFindValue(aclpb->aclpb_proplist, ACL_ATTR_DNS, + (void **)&tmp_ptr, NULL)) > 0) { + + PListDeleteProp(aclpb->aclpb_proplist, rc, NULL); + } + + /* + * Remove the DS_ATTR_IP property from the property list. + * The value of this property is just the property pointer + * (an unsigned long) so that gets freed too when we delete the + * property. + * It's removed here from the aclpb_proplist as it would be an error to + * allow it to persist in the aclpb which is an operation time thing. + * If we leave it here the next time this aclpb gets used, the DnsGetter + * is not called by LASIpEval/ACL_GetAttribute() as it thinks the + * ACL_ATTR_IP has already been initialized. + */ + + if ((rc = PListFindValue(aclpb->aclpb_proplist, ACL_ATTR_IP, + (void **)&tmp_ptr, NULL)) > 0) { + + PListDeleteProp(aclpb->aclpb_proplist, rc, NULL); + } + + /* + * The DS_ATTR_USERDN value comes from aclpb_authorization_sdn. + * This memory + * is freed above using aclpb_authorization_sdn so we don't need to free it here + * before overwriting the old value. + */ + PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_USERDN, NULL, 0); + + /* + * The DS_ATTR_AUTHTYPE value is a pointer into the pblock, so + * we do not need to free that memory before overwriting the value. + */ + PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_AUTHTYPE, NULL, 0); + + /* + * DO NOT overwrite the aclpb pointer--it is initialized at malloc_aclpb + * time and is kept within the aclpb. + * + * PListAssignValue(aclpb->aclpb_proplist, DS_PROP_ACLPB, NULL, 0); + */ + + /* + * The DS_ATTR_ENTRY value was a pointer to the entry being evaluated + * by the ACL code. That entry comes from outside the context of + * the acl code and so is dealt with out there. Ergo, here we can just + * lose the pointer to that entry. + */ + PListAssignValue(aclpb->aclpb_proplist, DS_ATTR_ENTRY, NULL, 0); + + aclpb->aclpb_signature = 0; + + /* reset scoped entry cache to be empty */ + aclpb->aclpb_scoped_entry_anominfo.anom_e_nummatched = 0; + + /* Free up any of the string values left in the macro ht and remove + * the entries.*/ + acl_ht_free_all_entries_and_values(aclpb->aclpb_macro_ht); + + /* Finally, set it to the no use state */ + aclpb->aclpb_state = 0; + +} + +static char * +acl__get_aclpb_type ( Acl_PBlock *aclpb ) +{ + + if (aclpb->aclpb_state & ACLPB_TYPE_PROXY) + return ACLPB_TYPE_PROXY_STR; + + return ACLPB_TYPE_MAIN_STR; +} +static void +acl__dump_stats ( struct acl_pblock *aclpb , const char *block_type) +{ + int connid = 0; + int opid = 0; + Slapi_PBlock *pb = NULL; + + pb = aclpb->aclpb_pblock; + if ( pb ) { + slapi_pblock_get ( pb, SLAPI_CONN_ID, &connid ); + slapi_pblock_get ( pb, SLAPI_OPERATION_ID, &opid ); + } + + /* DUMP STAT INFO */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "**** ACL OPERATION STAT BEGIN ( aclpb:%p Block type: %s): Conn:%d Operation:%d *******\n", + aclpb, block_type, connid, opid ); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of entries scanned: %d\n", + aclpb->aclpb_stat_total_entries); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times ACL List scanned: %d\n", + aclpb->aclpb_stat_acllist_scanned); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of ACLs with target matched:%d\n", + aclpb->aclpb_stat_num_tmatched_acls); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times acl resource matched:%d\n", + aclpb->aclpb_stat_aclres_matched); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times ANOM list scanned:%d\n", + aclpb->aclpb_stat_anom_list_scanned); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times Context was copied:%d\n", + aclpb->aclpb_stat_num_copycontext); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "\tNumber of times Attrs was copied:%d\n", + aclpb->aclpb_stat_num_copy_attrs); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, " **** ACL OPERATION STAT END *******\n"); +} +/****************************************************************************/ +/* E N D */ +/****************************************************************************/ + diff --git a/ldap/servers/plugins/acl/aclanom.c b/ldap/servers/plugins/acl/aclanom.c new file mode 100644 index 00000000..71b0c68a --- /dev/null +++ b/ldap/servers/plugins/acl/aclanom.c @@ -0,0 +1,536 @@ +/** 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" + +/************************************************************************ +Anonymous profile +**************************************************************************/ + +struct anom_targetacl { + int anom_type; /* defines for anom types same as aci_type */ + int anom_access; + Slapi_DN *anom_target; /* target of the ACL */ + Slapi_Filter *anom_filter; /* targetfilter part */ + char **anom_targetAttrs; /* list of attrs */ +}; + + +struct anom_profile { + short anom_signature; + short anom_numacls; + struct anom_targetacl anom_targetinfo[ACL_ANOM_MAX_ACL]; +}; + +static struct anom_profile *acl_anom_profile = NULL; + +static PRRWLock *anom_rwlock = NULL; +#define ANOM_LOCK_READ() PR_RWLock_Rlock (anom_rwlock ) +#define ANOM_UNLOCK_READ() PR_RWLock_Unlock (anom_rwlock ) +#define ANOM_LOCK_WRITE() PR_RWLock_Wlock (anom_rwlock ) +#define ANOM_UNLOCK_WRITE() PR_RWLock_Unlock (anom_rwlock ) + + +static void __aclanom__del_profile (); + +/* + * aclanom_init (); + * Generate a profile for the anonymous user. We can use this profile + * later to determine what resources the client is allowed to. + * + * Dependency: + * Before calling this, it is assumed that all the ACLs have been read + * and parsed. + * + * We will go thru all the ACL and pick the ANYONE ACL and generate the anom + * profile. + * + */ +int +aclanom_init () +{ + + acl_anom_profile = (struct anom_profile * ) + slapi_ch_calloc (1, sizeof ( struct anom_profile ) ); + + if (( anom_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"ANOM LOCK") ) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "Failed in getting the ANOM rwlock\n" ); + return 1; + } + return 0; +} + +/* + * Depending on the context, this routine may need to take the + * acicache read lock. +*/ +void +aclanom_gen_anomProfile (acl_lock_flag_t lock_flag) +{ + aci_t *aci = NULL; + int i; + Targetattr **srcattrArray; + Targetattr *attr; + struct anom_profile *a_profile; + PRUint32 cookie; + + PR_ASSERT( lock_flag == DO_TAKE_ACLCACHE_READLOCK || + lock_flag == DONT_TAKE_ACLCACHE_READLOCK); + + /* + * This routine requires two locks: + * the one for the global cache in acllist_acicache_READ_LOCK() and + * the one for the anom profile. + * They _must_ be taken in the order presented here or there + * is a deadlock scenario with acllist_remove_aci_needsLock() which + * takes them is this order. + */ + + if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) { + acllist_acicache_READ_LOCK(); + } + ANOM_LOCK_WRITE (); + a_profile = acl_anom_profile; + + if ( (!acl_get_aclsignature()) || ( !a_profile) || + (a_profile->anom_signature == acl_get_aclsignature()) ) { + ANOM_UNLOCK_WRITE (); + if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) { + acllist_acicache_READ_UNLOCK(); + } + return; + } + + /* D0 we have one already. If we do, then clean it up */ + __aclanom__del_profile(); + + /* We have a new signature now */ + a_profile->anom_signature = acl_get_aclsignature(); + + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "GENERATING ANOM USER PROFILE\n", 0,0,0); + /* + ** Go thru the ACL list and find all the ACLs which apply to the + ** anonymous user i.e anyone. we can generate a profile for that. + ** We will llok at the simple case i.e it matches + ** cases not handled: + ** 1) When there is a mix if rule types ( allows & denies ) + ** + */ + + aci = acllist_get_first_aci ( NULL, &cookie ); + while ( aci ) { + int a_numacl; + struct slapi_filter *f; + char **destattrArray; + + + /* + * We must not have a rule like: deny ( all ) userdn != "xyz" + * or groupdn != + */ + if ( (aci->aci_type & ACI_HAS_DENY_RULE) && + ( (aci->aci_type & ACI_CONTAIN_NOT_USERDN ) || + (aci->aci_type & ACI_CONTAIN_NOT_GROUPDN) || + (aci->aci_type & ACI_CONTAIN_NOT_ROLEDN)) ){ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "CANCELLING ANOM USER PROFILE BECAUSE OF DENY RULE\n", 0,0,0); + goto cleanup; + } + + /* Must be a anyone rule */ + if ( aci->aci_elevel != ACI_ELEVEL_USERDN_ANYONE ) { + aci = acllist_get_next_aci ( NULL, aci, &cookie); + continue; + } + if (! (aci->aci_access & ( SLAPI_ACL_READ | SLAPI_ACL_SEARCH)) ) { + aci = acllist_get_next_aci ( NULL, aci, &cookie); + continue; + } + /* If the rule has anything other than userdn = "ldap:///anyone" + ** let's not consider complex rules - let's make this lean. + */ + if ( aci->aci_ruleType & ~ACI_USERDN_RULE ){ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "CANCELLING ANOM USER PROFILE BECAUSE OF COMPLEX RULE\n", 0,0,0); + goto cleanup; + } + + /* Must not be a or have a + ** 1 ) DENY RULE 2) targetfilter + ** 3) no target pattern ( skip monitor acl ) + */ + if ( aci->aci_type & ( ACI_HAS_DENY_RULE | ACI_TARGET_PATTERN | + ACI_TARGET_NOT | ACI_TARGET_FILTER_NOT )) { + const char *dn = slapi_sdn_get_dn ( aci->aci_sdn ); + + /* see if this is a monitor acl */ + if (( strcasecmp ( dn, "cn=monitor") == 0 ) || + ( strcasecmp ( dn, "cn=monitor,cn=ldbm") == 0 )) { + aci = acllist_get_next_aci ( NULL, aci, &cookie); + continue; + } else { + /* clean up before leaving */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "CANCELLING ANOM USER PROFILE 1\n", 0,0,0); + goto cleanup; + } + + } + + /* Now we have an ALLOW ACL which applies to anyone */ + a_numacl = a_profile->anom_numacls++; + + if ( a_profile->anom_numacls == ACL_ANOM_MAX_ACL ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "CANCELLING ANOM USER PROFILE 2\n", 0,0,0); + goto cleanup; + } + + if ( (f = aci->target) != NULL ) { + char *avaType; + struct berval *avaValue; + slapi_filter_get_ava ( f, &avaType, &avaValue ); + + a_profile->anom_targetinfo[a_numacl].anom_target = + slapi_sdn_new_dn_byval ( avaValue->bv_val ); + } else { + a_profile->anom_targetinfo[a_numacl].anom_target = + slapi_sdn_dup ( aci->aci_sdn ); + } + + a_profile->anom_targetinfo[a_numacl].anom_filter = NULL; + if ( aci->targetFilterStr ) + a_profile->anom_targetinfo[a_numacl].anom_filter = slapi_str2filter ( aci->targetFilterStr ); + + i = 0; + srcattrArray = aci->targetAttr; + while ( srcattrArray[i]) + i++; + + a_profile->anom_targetinfo[a_numacl].anom_targetAttrs = + (char **) slapi_ch_calloc ( 1, (i+1) * sizeof(char *)); + + srcattrArray = aci->targetAttr; + destattrArray = a_profile->anom_targetinfo[a_numacl].anom_targetAttrs; + + i = 0; + while ( srcattrArray[i] ) { + attr = srcattrArray[i]; + if ( attr->attr_type & ACL_ATTR_FILTER ) { + /* Do'nt want to support these kind now */ + destattrArray[i] = NULL; + /* clean up before leaving */ + __aclanom__del_profile (); + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "CANCELLING ANOM USER PROFILE 3\n", 0,0,0); + goto cleanup; + } + + destattrArray[i] = slapi_ch_strdup ( attr->u.attr_str ); + i++; + } + + destattrArray[i] = NULL; + + aclutil_print_aci ( aci, "anom" ); + /* Here we are storing att the info from the acls. However + ** we are only interested in a few things like ACI_TARGETATTR_NOT. + */ + a_profile->anom_targetinfo[a_numacl].anom_type = aci->aci_type; + a_profile->anom_targetinfo[a_numacl].anom_access = aci->aci_access; + + aci = acllist_get_next_aci ( NULL, aci, &cookie); + } + + ANOM_UNLOCK_WRITE (); + if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) { + acllist_acicache_READ_UNLOCK(); + } + return; + +cleanup: + __aclanom__del_profile (); + ANOM_UNLOCK_WRITE (); + if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) { + acllist_acicache_READ_UNLOCK(); + } +} + + +void +aclanom_invalidateProfile () +{ + ANOM_LOCK_WRITE(); + if ( acl_anom_profile && acl_anom_profile->anom_numacls ) + acl_anom_profile->anom_signature = 0; + ANOM_UNLOCK_WRITE(); + + +} + +/* + * __aclanom_del_profile + * + * Cleanup the anonymous user's profile we have. + * + * ASSUMPTION: A WRITE LOCK HAS BEEN OBTAINED + * + */ +static void +__aclanom__del_profile (void) +{ + int i; + struct anom_profile *a_profile; + + + if ( (a_profile = acl_anom_profile) == NULL ) { + return; + } + + for ( i=0; i < a_profile->anom_numacls; i++ ) { + int j = 0; + char **destArray = a_profile->anom_targetinfo[i].anom_targetAttrs; + + /* Deallocate target */ + slapi_sdn_free ( &a_profile->anom_targetinfo[i].anom_target ); + + /* Deallocate filter */ + if ( a_profile->anom_targetinfo[i].anom_filter ) + slapi_filter_free ( a_profile->anom_targetinfo[i].anom_filter, 1 ); + + /* Deallocate attrs */ + if ( destArray ) { + while ( destArray[j] ) { + slapi_ch_free ( (void **) &destArray[j] ); + j++; + } + slapi_ch_free ( (void **) &destArray ); + } + a_profile->anom_targetinfo[i].anom_targetAttrs = NULL; + a_profile->anom_targetinfo[i].anom_type = 0; + a_profile->anom_targetinfo[i].anom_access = 0; + } + a_profile->anom_numacls = 0; + + /* Don't clean the signatue */ +} + +/* + * This routine sets up a "context" for evaluation of access control + * on a given entry for an anonymous user. + * It just factors out the scope and targetfilter info into a list + * of indices of the global anom profile list, that apply to this + * entry, and stores them in the aclpb. + * It's use relies on the way that access control is checked in the mailine search + * code in the core server, namely: check filter, check entry, then check each + * attribute. So, we call this in acl_access_allowed() before calling + * aclanom_match_profile()--therafter, aclanom_match_profile() uses the + * context to evaluate access to the entry and attributes. + * + * If there are no anom profiles, or the anom profiles get cancelled + * due to complex anon acis, then that's OK, aclanom_match_profile() + * returns -1 and the mainline acl code kicks in. + * + * The lifetime of this context info is the time it takes to check + * access control for all parts of this entry (filter, entry, attributes). + * So, if for an example an entry changes and a given anom profile entry + * no longer applies, we will not notice until the next round of access + * control checking on the entry--this is acceptable. + * + * The gain on doing this factoring in the following type of search + * was approx 6%: + * anon bind, 20 threads, exact match, ~20 attributes returned, + * (searchrate & DirectoryMark). + * +*/ +void +aclanom_get_suffix_info(Slapi_Entry *e, + struct acl_pblock *aclpb ) { + int i; + char *ndn = NULL; + Slapi_DN *e_sdn; + const char *aci_ndn; + int populate = 0; + struct scoped_entry_anominfo *s_e_anominfo = + &aclpb->aclpb_scoped_entry_anominfo; + + ANOM_LOCK_READ (); + + s_e_anominfo->anom_e_nummatched=0; + + ndn = slapi_entry_get_ndn ( e ) ; + e_sdn= slapi_entry_get_sdn ( e ) ; + for (i=acl_anom_profile->anom_numacls-1; i >= 0; i-- ) { + aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target); + if (!slapi_sdn_issuffix(e_sdn,acl_anom_profile->anom_targetinfo[i].anom_target) + || (!slapi_is_rootdse(ndn) && slapi_is_rootdse(aci_ndn))) + continue; + if ( acl_anom_profile->anom_targetinfo[i].anom_filter ) { + if ( slapi_vattr_filter_test( aclpb->aclpb_pblock, e, + acl_anom_profile->anom_targetinfo[i].anom_filter, + 0 /*don't do acess chk*/) != 0) + continue; + } + s_e_anominfo->anom_e_targetInfo[s_e_anominfo->anom_e_nummatched]=i; + s_e_anominfo->anom_e_nummatched++; + } + ANOM_UNLOCK_READ (); +} + + +/* + * aclanom_match_profile + * Look at the anonymous profile and see if we can use it or not. + * + * + * Inputs: + * Slapi_Pblock - The Pblock + * Slapi_Entry *e - The entry for which we are asking permission. + * char *attr - Attribute name + * int access - access type + * + * Return: + * LDAP_SUCCESS ( 0 ) - acess is allowed. + * LDAP_INSUFFICIENT_ACCESS (50 ) - access denied. + * -1 - didn't match the targets + * + * Assumptions: + * The caller of this module has to make sure that the client is + * an anonymous client. + */ +int +aclanom_match_profile (Slapi_PBlock *pb, struct acl_pblock *aclpb, Slapi_Entry *e, + char *attr, int access ) +{ + + struct anom_profile *a_profile; + int result, i, k; + char **destArray; + int tmatched = 0; + char ebuf[ BUFSIZ ]; + int loglevel; + struct scoped_entry_anominfo *s_e_anominfo = + &aclpb->aclpb_scoped_entry_anominfo; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + /* WE are only interested for READ/SEARCH */ + if ( !(access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) ) + return -1; + + /* If we are here means, the client is doing a anonymous read/search */ + if ((a_profile = acl_anom_profile) == NULL ) { + return -1; + } + + ANOM_LOCK_READ (); + /* Check the signature first */ + if ( a_profile->anom_signature != acl_get_aclsignature () ) { + /* Need to regenrate the signature. + * Need a WRITE lock to generate the anom profile - + * which is obtained in acl__gen_anom_user_profile (). Since + * I don't have upgrade lock -- I have to do this way. + */ + ANOM_UNLOCK_READ (); + aclanom_gen_anomProfile (DO_TAKE_ACLCACHE_READLOCK); + aclanom_get_suffix_info(e, aclpb ); + ANOM_LOCK_READ (); + } + + /* doing this early saves use a malloc/free/normalize cost */ + if ( !a_profile->anom_numacls ) { + ANOM_UNLOCK_READ (); + return -1; + } + + result = LDAP_INSUFFICIENT_ACCESS; + + for ( k=0; k<s_e_anominfo->anom_e_nummatched; k++ ) { + short matched = 0; + short j = 0; + + i = s_e_anominfo->anom_e_targetInfo[k]; + + /* Check for right */ + if ( !(a_profile->anom_targetinfo[i].anom_access & access) ) + continue; + + /* + * XXX rbyrne Don't really understand the role of this + * but not causing any obvious bugs...get back to it. + */ + tmatched++; + + if ( attr == NULL ) { + result = LDAP_SUCCESS; + break; + } + + destArray = a_profile->anom_targetinfo[i].anom_targetAttrs; + while ( destArray[j] ) { + if ( strcasecmp ( destArray[j], "*") == 0 || + slapi_attr_type_cmp ( attr, destArray[j], 1 ) == 0 ) { + matched = 1; + break; + } + j++; + } + + if ( a_profile->anom_targetinfo[i].anom_type & ACI_TARGET_ATTR_NOT ) + result = matched ? LDAP_INSUFFICIENT_ACCESS : LDAP_SUCCESS; + else + result = matched ? LDAP_SUCCESS : LDAP_INSUFFICIENT_ACCESS; + + if ( result == LDAP_SUCCESS ) + break; + } /* for */ + + if ( slapi_is_loglevel_set(loglevel) ) { + char *ndn = NULL; + Slapi_Operation *op = NULL; + + ndn = slapi_entry_get_ndn ( e ) ; + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + if ( result == LDAP_SUCCESS) { + const char *aci_ndn; + aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target); + + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d: Allow access on entry(%s).attr(%s) to anonymous: acidn=\"%s\"\n", + op->o_connid, op->o_opid, + escape_string_with_punctuation(ndn, ebuf), + attr ? attr:"NULL", + escape_string_with_punctuation(aci_ndn, ebuf)); + } else { + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d: Deny access on entry(%s).attr(%s) to anonymous\n", + op->o_connid, op->o_opid, + escape_string_with_punctuation(ndn, ebuf), attr ? attr:"NULL" ); + } + } + + ANOM_UNLOCK_READ (); + if ( tmatched == 0) + return -1; + else + return result; + +} +int +aclanom_is_client_anonymous ( Slapi_PBlock *pb ) +{ + char *clientDn; + + + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn ); + if (acl_anom_profile->anom_numacls && + acl_anom_profile->anom_signature && + (( NULL == clientDn) || (clientDn && *clientDn == '\0')) ) + return 1; + + return 0; +} + diff --git a/ldap/servers/plugins/acl/acldllmain.c b/ldap/servers/plugins/acl/acldllmain.c new file mode 100644 index 00000000..21ab60c4 --- /dev/null +++ b/ldap/servers/plugins/acl/acldllmain.c @@ -0,0 +1,128 @@ +/** 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" + +#ifdef _WIN32 +/* Lifted from Q125688 + * How to Port a 16-bit DLL to a Win32 DLL + * on the MSVC 4.0 CD + */ +BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + WSADATA wsadata; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + /* Code from LibMain inserted here. Return TRUE to keep the + DLL loaded or return FALSE to fail loading the DLL. + + You may have to modify the code in your original LibMain to + account for the fact that it may be called more than once. + You will get one DLL_PROCESS_ATTACH for each process that + loads the DLL. This is different from LibMain which gets + called only once when the DLL is loaded. The only time this + is critical is when you are using shared data sections. + If you are using shared data sections for statically + allocated data, you will need to be careful to initialize it + only once. Check your code carefully. + + Certain one-time initializations may now need to be done for + each process that attaches. You may also not need code from + your original LibMain because the operating system may now + be doing it for you. + */ + /* + * 16 bit code calls UnlockData() + * which is mapped to UnlockSegment in windows.h + * in 32 bit world UnlockData is not defined anywhere + * UnlockSegment is mapped to GlobalUnfix in winbase.h + * and the docs for both UnlockSegment and GlobalUnfix say + * ".. function is oboslete. Segments have no meaning + * in the 32-bit environment". So we do nothing here. + */ + + if( errno = WSAStartup(0x0101, &wsadata ) != 0 ) + return FALSE; + + break; + + case DLL_THREAD_ATTACH: + /* Called each time a thread is created in a process that has + already loaded (attached to) this DLL. Does not get called + for each thread that exists in the process before it loaded + the DLL. + + Do thread-specific initialization here. + */ + break; + + case DLL_THREAD_DETACH: + /* Same as above, but called when a thread in the process + exits. + + Do thread-specific cleanup here. + */ + break; + + case DLL_PROCESS_DETACH: + /* Code from _WEP inserted here. This code may (like the + LibMain) not be necessary. Check to make certain that the + operating system is not doing it for you. + */ + WSACleanup(); + + break; + } + /* The return value is only used for DLL_PROCESS_ATTACH; all other + conditions are ignored. */ + return TRUE; // successful DLL_PROCESS_ATTACH +} +#else +int CALLBACK +LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + /*UnlockData( 0 );*/ + return( 1 ); +} +#endif + +#ifdef LDAP_DEBUG +#ifndef _WIN32 +#include <stdarg.h> +#include <stdio.h> + +void LDAPDebug( int level, char* fmt, ... ) +{ + static char debugBuf[1024]; + + if (module_ldap_debug && (*module_ldap_debug & level)) + { + va_list ap; + va_start (ap, fmt); + _snprintf (debugBuf, sizeof(debugBuf), fmt, ap); + va_end (ap); + + OutputDebugString (debugBuf); + } +} +#endif +#endif + +#ifndef _WIN32 + +/* The 16-bit version of the RTL does not implement perror() */ + +#include <stdio.h> + +void perror( const char *msg ) +{ + char buf[128]; + wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ; + OutputDebugString( buf ); +} + +#endif diff --git a/ldap/servers/plugins/acl/acleffectiverights.c b/ldap/servers/plugins/acl/acleffectiverights.c new file mode 100644 index 00000000..1e250e96 --- /dev/null +++ b/ldap/servers/plugins/acl/acleffectiverights.c @@ -0,0 +1,674 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2004 Netscape Communications Corporation + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "acl.h" + +static int +_ger_g_permission_granted ( Slapi_PBlock *pb, Slapi_Entry *e, char **errbuf ) +{ + char *proxydn = NULL; + Slapi_DN *requestor_sdn, *entry_sdn; + char *errtext = NULL; + int isroot; + int rc; + + /* + * Theorically, we should check if the entry has "g" + * permission granted to the requestor. If granted, + * allows the effective rights on that entry and its + * attributes within the entry to be returned for + * ANY subject. + * + * "G" permission granting has not been implemented yet, + * the current release assumes that "g" permission be + * granted to root and owner of any entry. + */ + + /* + * The requestor may be either the bind dn or a proxy dn + */ + acl_get_proxyauth_dn ( pb, &proxydn, &errtext ); + if ( proxydn != NULL ) + { + requestor_sdn = slapi_sdn_new_dn_passin ( proxydn ); + } + else + { + requestor_sdn = &(pb->pb_op->o_sdn); + } + if ( slapi_sdn_get_dn (requestor_sdn) == NULL ) + { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_g_permission_granted: anonymous has no g permission\n" ); + rc = LDAP_INSUFFICIENT_ACCESS; + goto bailout; + } + isroot = slapi_dn_isroot ( slapi_sdn_get_dn (requestor_sdn) ); + if ( isroot ) + { + /* Root has "g" permission on any entry */ + rc = LDAP_SUCCESS; + goto bailout; + } + + entry_sdn = slapi_entry_get_sdn ( e ); + if ( entry_sdn == NULL || slapi_sdn_get_dn (entry_sdn) == NULL ) + { + rc = LDAP_SUCCESS; + goto bailout; + } + + if ( slapi_sdn_compare ( requestor_sdn, entry_sdn ) == 0 ) + { + /* Owner has "g" permission on his own entry */ + rc = LDAP_SUCCESS; + goto bailout; + } + + aclutil_str_appened ( errbuf, "get-effective-rights: requestor has no g permission on the entry" ); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_g_permission_granted: %s\n", *errbuf); + rc = LDAP_INSUFFICIENT_ACCESS; + +bailout: + if ( proxydn ) + { + /* The ownership of proxydn has passed to requestor_sdn */ + slapi_sdn_free ( &requestor_sdn ); + } + return rc; +} + +static int +_ger_parse_control ( Slapi_PBlock *pb, char **subjectndn, int *iscritical, char **errbuf ) +{ + LDAPControl **requestcontrols; + struct berval *subjectber; + BerElement *ber; + + if (NULL == subjectndn) + { + return LDAP_OPERATIONS_ERROR; + } + + *subjectndn = NULL; + + /* + * Get the control + */ + slapi_pblock_get ( pb, SLAPI_REQCONTROLS, (void *) &requestcontrols ); + slapi_control_present ( requestcontrols, + LDAP_CONTROL_GET_EFFECTIVE_RIGHTS, + &subjectber, + iscritical ); + if ( subjectber == NULL || subjectber->bv_val == NULL || + subjectber->bv_len == 0 ) + { + aclutil_str_appened ( errbuf, "get-effective-rights: missing subject" ); + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf ); + return LDAP_INVALID_SYNTAX; + } + + if ( strncasecmp ( "dn:", subjectber->bv_val, 3 ) == 0 ) + { + /* + * This is a non-standard support to allow the subject being a plain + * or base64 encoding string. Hence users using -J option in + * ldapsearch don't have to do BER encoding for the subject. + */ + *subjectndn = slapi_ch_malloc ( subjectber->bv_len + 1 ); + strncpy ( *subjectndn, subjectber->bv_val, subjectber->bv_len ); + *(*subjectndn + subjectber->bv_len) = '\0'; + } + else + { + ber = ber_init (subjectber); + if ( ber == NULL ) + { + aclutil_str_appened ( errbuf, "get-effective-rights: ber_init failed for the subject" ); + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf ); + return LDAP_OPERATIONS_ERROR; + } + /* "a" means to allocate storage as needed for octet string */ + if ( ber_scanf (ber, "a", subjectndn) == LBER_ERROR ) + { + aclutil_str_appened ( errbuf, "get-effective-rights: invalid ber tag in the subject" ); + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf ); + ber_free ( ber, 1 ); + return LDAP_INVALID_SYNTAX; + } + ber_free ( ber, 1 ); + } + + /* + * The current implementation limits the subject to authorization ID + * (see section 9 of RFC 2829) only. It also only supports the "dnAuthzId" + * flavor, which looks like "dn:<DN>" where null <DN> is for anonymous. + */ + if ( NULL == *subjectndn || strlen (*subjectndn) < 3 || + strncasecmp ( "dn:", *subjectndn, 3 ) != 0 ) + { + aclutil_str_appened ( errbuf, "get-effective-rights: subject is not dnAuthzId" ); + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, "%s\n", *errbuf ); + return LDAP_INVALID_SYNTAX; + } + + strcpy ( *subjectndn, *subjectndn + 3 ); + slapi_dn_normalize ( *subjectndn ); + return LDAP_SUCCESS; +} + +static void +_ger_release_gerpb ( + Slapi_PBlock **gerpb, + void **aclcb, /* original aclcb */ + Slapi_PBlock *pb /* original pb */ + ) +{ + if ( *gerpb ) + { + /* Return conn to pb */ + slapi_pblock_set ( *gerpb, SLAPI_CONNECTION, NULL ); + slapi_pblock_destroy ( *gerpb ); + *gerpb = NULL; + } + + /* Put the original aclcb back to pb */ + if ( *aclcb ) + { + Connection *conn = NULL; + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn ); + if (conn) + { + struct aclcb *geraclcb; + geraclcb = (struct aclcb *) acl_get_ext ( ACL_EXT_CONNECTION, conn ); + acl_conn_ext_destructor ( geraclcb, NULL, NULL ); + acl_set_ext ( ACL_EXT_CONNECTION, conn, *aclcb ); + *aclcb = NULL; + } + } +} + +static int +_ger_new_gerpb ( + Slapi_PBlock *pb, + Slapi_Entry *e, + const char *subjectndn, + Slapi_PBlock **gerpb, + void **aclcb, /* original aclcb */ + char **errbuf + ) +{ + Connection *conn; + struct acl_cblock *geraclcb; + Acl_PBlock *aclpb, *geraclpb; + Operation *op, *gerop; + int rc = LDAP_SUCCESS; + + *aclcb = NULL; + *gerpb = slapi_pblock_new (); + if ( *gerpb == NULL ) + { + rc = LDAP_NO_MEMORY; + goto bailout; + } + + { + /* aclpb initialization needs the backend */ + Slapi_Backend *be; + slapi_pblock_get ( pb, SLAPI_BACKEND, &be ); + slapi_pblock_set ( *gerpb, SLAPI_BACKEND, be ); + } + + { + int isroot = slapi_dn_isroot ( subjectndn ); + slapi_pblock_set ( *gerpb, SLAPI_REQUESTOR_ISROOT, &isroot ); + } + + /* Save requestor's aclcb and set subjectdn's one */ + { + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn ); + slapi_pblock_set ( *gerpb, SLAPI_CONNECTION, conn ); + + /* Can't share the conn->aclcb because of different context */ + geraclcb = (struct acl_cblock *) acl_conn_ext_constructor ( NULL, NULL); + if ( geraclcb == NULL ) + { + rc = LDAP_NO_MEMORY; + goto bailout; + } + slapi_sdn_set_ndn_byval ( geraclcb->aclcb_sdn, subjectndn ); + *aclcb = acl_get_ext ( ACL_EXT_CONNECTION, conn ); + acl_set_ext ( ACL_EXT_CONNECTION, conn, (void *) geraclcb ); + } + + { + gerop = operation_new ( OP_FLAG_INTERNAL ); + if ( gerop == NULL ) + { + rc = LDAP_NO_MEMORY; + goto bailout; + } + /* + * conn is a no-use parameter in the functions + * chained down from factory_create_extension + */ + gerop->o_extension = factory_create_extension ( get_operation_object_type(), (void *)gerop, (void *)conn ); + slapi_pblock_set ( *gerpb, SLAPI_OPERATION, gerop ); + slapi_sdn_set_dn_byval ( &gerop->o_sdn, subjectndn ); + geraclpb = acl_get_ext ( ACL_EXT_OPERATION, (void *)gerop); + acl_init_aclpb ( *gerpb, geraclpb, subjectndn, 0 ); + geraclpb->aclpb_res_type |= ACLPB_EFFECTIVE_RIGHTS; + } + + +bailout: + if ( rc != LDAP_SUCCESS ) + { + _ger_release_gerpb ( gerpb, aclcb, pb ); + } + + return rc; +} + +/* + * Callers should have already allocated *gerstr to hold at least + * "entryLevelRights: adnvxxx\n". + */ +unsigned long +_ger_get_entry_rights ( + Slapi_PBlock *gerpb, + Slapi_Entry *e, + const char *subjectndn, + char *gerstr, + char **errbuf + ) +{ + unsigned long entryrights = 0; + Slapi_RDN *rdn = NULL; + const char *rdnstr = NULL; + char *equalsign = NULL; + char *rdntype = NULL; + + strcpy ( gerstr, "entryLevelRights: " ); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_entry_rights: SLAPI_ACL_READ\n" ); + if (acl_access_allowed(gerpb, e, "*", NULL, SLAPI_ACL_READ) == LDAP_SUCCESS) + { + /* v - view e */ + entryrights |= SLAPI_ACL_READ; + strcat (gerstr, "v"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_entry_rights: SLAPI_ACL_ADD\n" ); + if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_ADD) == LDAP_SUCCESS) + { + /* a - add child entry below e */ + entryrights |= SLAPI_ACL_ADD; + strcat (gerstr, "a"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_entry_rights: SLAPI_ACL_DELETE\n" ); + if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_DELETE) == LDAP_SUCCESS) + { + /* d - delete e */ + entryrights |= SLAPI_ACL_DELETE; + strcat (gerstr, "d"); + } + /* + * Some limitation/simplification applied here: + * - The modrdn right requires the rights to delete the old rdn and + * the new one. However we have no knowledge of what the new rdn + * is going to be. + * - In multi-valued RDN case, we check the right on + * the first rdn type only for now. + */ + rdn = slapi_rdn_new_dn ( slapi_entry_get_ndn (e) ); + rdnstr = slapi_rdn_get_rdn ( rdn ); + if ( NULL != (equalsign = strchr ( rdnstr, '=' )) ) + { + rdntype = slapi_ch_malloc ( equalsign-rdnstr+1 ); + strncpy ( rdntype, rdnstr, equalsign-rdnstr ); + rdntype [ equalsign-rdnstr ] = '\0'; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_entry_rights: SLAPI_ACL_WRITE_DEL & _ADD %s\n", rdntype ); + if (acl_access_allowed(gerpb, e, rdntype, NULL, + ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS && + acl_access_allowed(gerpb, e, rdntype, NULL, + ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) + { + /* n - rename e */ + entryrights |= SLAPI_ACL_WRITE; + strcat (gerstr, "n"); + } + slapi_ch_free ( (void**) &rdntype ); + } + slapi_rdn_free ( &rdn ); + +done: + if ( entryrights == 0 ) + { + strcat (gerstr, "none"); + } + + strcat (gerstr, "\n"); + + return entryrights; +} + +/* + * *gerstr should point to a heap buffer since it may need + * to expand dynamically. + */ +unsigned long +_ger_get_attr_rights ( + Slapi_PBlock *gerpb, + Slapi_Entry *e, + const char *subjectndn, + char *type, + char **gerstr, + int *gerstrsize, + int isfirstattr, + char **errbuf + ) +{ + unsigned long attrrights = 0; + + /* Enough space for " $type:rwoscxx" ? */ + if ( (*gerstrsize - strlen(*gerstr)) < (strlen(type) + 16) ) + { + /* slapi_ch_realloc() exits if realloc() failed */ + *gerstrsize += 256; + *gerstr = slapi_ch_realloc ( *gerstr, *gerstrsize ); + } + if (!isfirstattr) + { + strcat ( *gerstr, ", " ); + } + sprintf ( *gerstr + strlen(*gerstr), "%s:", type ); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_READ %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_READ) == LDAP_SUCCESS) + { + /* r - read the values of type */ + attrrights |= SLAPI_ACL_READ; + strcat (*gerstr, "r"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_SEARCH %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_SEARCH) == LDAP_SUCCESS) + { + /* s - search the values of type */ + attrrights |= SLAPI_ACL_SEARCH; + strcat (*gerstr, "s"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_COMPARE %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_COMPARE) == LDAP_SUCCESS) + { + /* c - compare the values of type */ + attrrights |= SLAPI_ACL_COMPARE; + strcat (*gerstr, "c"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_WRITE_ADD %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) + { + /* w - add the values of type */ + attrrights |= ACLPB_SLAPI_ACL_WRITE_ADD; + strcat (*gerstr, "w"); + } + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "_ger_get_attr_rights: SLAPI_ACL_WRITE_DEL %s\n", type ); + if (acl_access_allowed(gerpb, e, type, NULL, ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS) + { + /* o - delete the values of type */ + attrrights |= ACLPB_SLAPI_ACL_WRITE_DEL; + strcat (*gerstr, "o"); + } + /* If subjectdn has no general write right, check for self write */ + if ( 0 == (attrrights & (ACLPB_SLAPI_ACL_WRITE_DEL | ACLPB_SLAPI_ACL_WRITE_ADD)) ) + { + struct berval val; + + val.bv_val = (char *)subjectndn; + val.bv_len = strlen (subjectndn); + + if (acl_access_allowed(gerpb, e, type, &val, ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) + { + /* W - add self to the attribute */ + attrrights |= ACLPB_SLAPI_ACL_WRITE_ADD; + strcat (*gerstr, "W"); + } + if (acl_access_allowed(gerpb, e, type, &val, ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS) + { + /* O - delete self from the attribute */ + attrrights |= ACLPB_SLAPI_ACL_WRITE_DEL; + strcat (*gerstr, "O"); + } + } + + if ( attrrights == 0 ) + { + strcat (*gerstr, "none"); + } + + return attrrights; +} + +void +_ger_get_attrs_rights ( + Slapi_PBlock *gerpb, + Slapi_Entry *e, + const char *subjectndn, + char **attrs, + char **gerstr, + int *gerstrsize, + char **errbuf + ) +{ + int isfirstattr = 1; + + /* gerstr was initially allocated with enough space for one more line */ + strcat ( *gerstr, "attributeLevelRights: " ); + + if (attrs && *attrs) + { + int i; + for ( i = 0; attrs[i]; i++ ) + { + _ger_get_attr_rights ( gerpb, e, subjectndn, attrs[i], gerstr, gerstrsize, isfirstattr, errbuf ); + isfirstattr = 0; + } + } + else + { + Slapi_Attr *prevattr = NULL, *attr; + char *type; + + while ( slapi_entry_next_attr ( e, prevattr, &attr ) == 0 ) + { + if ( ! slapi_attr_flag_is_set (attr, SLAPI_ATTR_FLAG_OPATTR) ) + { + slapi_attr_get_type ( attr, &type ); + _ger_get_attr_rights ( gerpb, e, subjectndn, type, gerstr, gerstrsize, isfirstattr, errbuf ); + isfirstattr = 0; + } + prevattr = attr; + } + } + + if ( isfirstattr ) + { + /* not a single attribute was retrived or specified */ + strcat ( *gerstr, "*:none" ); + } + return; +} + +/* + * controlType = LDAP_CONTROL_GET_EFFECTIVE_RIGHTS; + * criticality = n/a; + * controlValue = OCTET STRING of BER encoding of the SEQUENCE of + * ENUMERATED LDAP code + */ +void +_ger_set_response_control ( + Slapi_PBlock *pb, + int iscritical, + int rc + ) +{ + LDAPControl **resultctrls = NULL; + LDAPControl gerrespctrl; + BerElement *ber = NULL; + struct berval *berval = NULL; + int found = 0; + int i; + + if ( (ber = der_alloc ()) == NULL ) + { + goto bailout; + } + + /* begin sequence, enumeration, end sequence */ + ber_printf ( ber, "{e}", rc ); + if ( ber_flatten ( ber, &berval ) != LDAP_SUCCESS ) + { + goto bailout; + } + gerrespctrl.ldctl_oid = LDAP_CONTROL_GET_EFFECTIVE_RIGHTS; + gerrespctrl.ldctl_iscritical = iscritical; + gerrespctrl.ldctl_value.bv_val = berval->bv_val; + gerrespctrl.ldctl_value.bv_len = berval->bv_len; + + slapi_pblock_get ( pb, SLAPI_RESCONTROLS, &resultctrls ); + for (i = 0; resultctrls && resultctrls[i]; i++) + { + if (strcmp(resultctrls[i]->ldctl_oid, LDAP_CONTROL_GET_EFFECTIVE_RIGHTS) == 0) + { + /* + * We get here if search returns more than one entry + * and this is not the first entry. + */ + ldap_control_free ( resultctrls[i] ); + resultctrls[i] = slapi_dup_control (&gerrespctrl); + found = 1; + break; + } + } + + if ( !found ) + { + /* slapi_pblock_set() will dup the control */ + slapi_pblock_set ( pb, SLAPI_ADD_RESCONTROL, &gerrespctrl ); + } + +bailout: + ber_free ( ber, 1 ); /* ber_free() checks for NULL param */ + ber_bvfree ( berval ); /* ber_bvfree() checks for NULL param */ +} + +int +acl_get_effective_rights ( + Slapi_PBlock *pb, + Slapi_Entry *e, /* target entry */ + char **attrs, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access, /* requested access rights */ + char **errbuf + ) +{ + Slapi_PBlock *gerpb = NULL; + void *aclcb = NULL; + char *subjectndn = NULL; + char *gerstr = NULL; + int gerstrsize = 1024; + unsigned long entryrights; + int iscritical = 1; + int rc; + + *errbuf = '\0'; + gerstr = slapi_ch_malloc ( gerstrsize ); + + /* + * Get the subject + */ + rc = _ger_parse_control (pb, &subjectndn, &iscritical, errbuf ); + if ( rc != LDAP_SUCCESS ) + { + goto bailout; + } + + /* + * The requestor should have g permission on the entry + * to get the effective rights. + */ + rc = _ger_g_permission_granted (pb, e, errbuf); + if ( rc != LDAP_SUCCESS ) + { + goto bailout; + } + + /* + * Construct a new pb + */ + rc = _ger_new_gerpb ( pb, e, subjectndn, &gerpb, &aclcb, errbuf ); + if ( rc != LDAP_SUCCESS ) + { + goto bailout; + } + + /* Get entry level effective rights */ + entryrights = _ger_get_entry_rights ( gerpb, e, subjectndn, gerstr, errbuf ); + + /* + * Attribute level effective rights may not be NULL + * even if entry level's is. + */ + _ger_get_attrs_rights ( gerpb, e, subjectndn, attrs, &gerstr, &gerstrsize, errbuf ); + +bailout: + /* + * Now construct the response control + */ + _ger_set_response_control ( pb, iscritical, rc ); + + if ( rc != LDAP_SUCCESS ) + { + sprintf ( gerstr, "entryLevelRights: %d\nattributeLevelRights: *:%d", rc, rc ); + } + + slapi_log_error (SLAPI_LOG_ACLSUMMARY, plugin_name, + "###### Effective Rights on Entry (%s) for Subject (%s) ######\n", + slapi_entry_get_ndn (e), subjectndn); + slapi_log_error (SLAPI_LOG_ACLSUMMARY, plugin_name, "%s\n", gerstr); + + /* Restore pb */ + _ger_release_gerpb ( &gerpb, &aclcb, pb ); + + /* + * General plugin uses SLAPI_RESULT_TEXT for error text. Here + * SLAPI_PB_RESULT_TEXT is exclusively shared with add, dse and schema. + * slapi_pblock_set() will free any previous data, and + * pblock_done() will free SLAPI_PB_RESULT_TEXT. + */ + slapi_pblock_set (pb, SLAPI_PB_RESULT_TEXT, gerstr); + + if ( !iscritical ) + { + /* + * If return code is not LDAP_SUCCESS, the server would + * abort sending the data of the entry to the client. + */ + rc = LDAP_SUCCESS; + } + + slapi_ch_free ( (void **) &subjectndn ); + slapi_ch_free ( (void **) &gerstr ); + return rc; +} diff --git a/ldap/servers/plugins/acl/aclgroup.c b/ldap/servers/plugins/acl/aclgroup.c new file mode 100644 index 00000000..4cd7039f --- /dev/null +++ b/ldap/servers/plugins/acl/aclgroup.c @@ -0,0 +1,442 @@ +/** 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" + +/*************************************************************************** + * + * This module deals with the global user group cache. + * + * A LRU queue mechanism is used to maintain the groups the user currently in. + * At this moment the QUEUE is invalidated if there is a group change. A better + * way would have been to invalidate only the one which are effected. + * However to accomplish that will require quite a bit of work which may not be + * cost-efftive. + **************************************************************************/ +static aclGroupCache *aclUserGroups; +#define ACL_MAXCACHE_USERGROUPS 200 + +#define ACLG_LOCK_GROUPCACHE_READ() PR_RWLock_Rlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_LOCK_GROUPCACHE_WRITE() PR_RWLock_Wlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_ULOCK_GROUPCACHE_WRITE() PR_RWLock_Unlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_ULOCK_GROUPCACHE_READ() PR_RWLock_Unlock ( aclUserGroups->aclg_rwlock ) + + +static void __aclg__delete_userGroup ( aclUserGroup *u_group ); + + +int +aclgroup_init () +{ + + aclUserGroups = ( aclGroupCache * ) slapi_ch_calloc (1, sizeof ( aclGroupCache ) ); + if ( NULL == (aclUserGroups->aclg_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"Group LOCK"))) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Unable to allocate RWLOCK for group cache\n"); + return 1; + } + return 0; +} + +/* + * aclg_init_userGroup + * + * Go thru the Global Group CACHE and see if we have group information for + * the user. The user's group cache is invalidated when a group is modified + * (in which case ALL usergroups are invalidated) or when the user's entry + * is modified in which case just his is invalidated. + * + * We need to scan the whole cache looking for a valid entry that matches + * this user. If we find invalid entries along the way. + * + * If we don't have anything it's fine. we will allocate a space when we + * need it i.e during the group evaluation. + * + * Inputs: + * struct acl_pblock - ACL private block + * char *dn - the client's dn + * int got_lock - 1: already obtained WRITE Lock + * - 0: Nope; get one + * Returns: + * None. + */ + +void +aclg_init_userGroup ( struct acl_pblock *aclpb, const char *n_dn , int got_lock ) +{ + aclUserGroup *u_group = NULL; + aclUserGroup *next_ugroup = NULL; + aclUserGroup *p_group, *n_group; + int found = 0; + + /* Check for Anonymous user */ + if ( n_dn && *n_dn == '\0') return; + + if ( !got_lock ) ACLG_LOCK_GROUPCACHE_WRITE (); + u_group = aclUserGroups->aclg_first; + aclpb->aclpb_groupinfo = NULL; + + while ( u_group != NULL ) { + next_ugroup = u_group->aclug_next; + if ( aclUserGroups->aclg_signature != u_group->aclug_signature) { + /* + * This means that this usergroup is no longer valid and + * this operation so delete this one if no one is using it. + */ + + if ( !u_group->aclug_refcnt ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "In traversal group deallocation\n", 0,0,0 ); + __aclg__delete_userGroup (u_group); + } + } else { + + /* + * Here, u_group is valid--if it matches then take it. + */ + if ( slapi_utf8casecmp((ACLUCHP)u_group->aclug_ndn, + (ACLUCHP)n_dn ) == 0 ) { + u_group->aclug_refcnt++; + aclpb->aclpb_groupinfo = u_group; + found = 1; + break; + } + } + u_group = next_ugroup; + } + + /* Move the new one to the top of the queue */ + if ( found ) { + p_group = u_group->aclug_prev; + n_group = u_group->aclug_next; + + if ( p_group ) { + aclUserGroup *t_group = NULL; + + p_group->aclug_next = n_group; + if ( n_group ) n_group->aclug_prev = p_group; + + t_group = aclUserGroups->aclg_first; + if ( t_group ) t_group->aclug_prev = u_group; + + u_group->aclug_prev = NULL; + u_group->aclug_next = t_group; + aclUserGroups->aclg_first = u_group; + + if ( u_group == aclUserGroups->aclg_last ) + aclUserGroups->aclg_last = p_group; + } + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "acl_init_userGroup: found in cache for dn:%s\n", n_dn,0,0); + } + if (!got_lock ) ACLG_ULOCK_GROUPCACHE_WRITE (); +} + + +/* + * + * aclg_reset_userGroup + * Reset the reference count to the user's group. + * + * Inputs: + * struct acl_pblock -- The acl private block. + * Returns: + * None. + * + * Note: A WRITE Lock on the GroupCache is obtained during the change: + */ +void +aclg_reset_userGroup ( struct acl_pblock *aclpb ) +{ + + aclUserGroup *u_group; + + ACLG_LOCK_GROUPCACHE_WRITE(); + + if ( (u_group = aclpb->aclpb_groupinfo) != NULL ) { + u_group->aclug_refcnt--; + + /* If I am the last one but I was using an invalid group cache + ** in the meantime, it is time now to get rid of it so that we will + ** not have duplicate cache. + */ + if ( !u_group->aclug_refcnt && + ( aclUserGroups->aclg_signature != u_group->aclug_signature )) { + __aclg__delete_userGroup ( u_group ); + } + } + ACLG_ULOCK_GROUPCACHE_WRITE(); + aclpb->aclpb_groupinfo = NULL; +} + +/* + * Find a user group in the global cache, returning a pointer to it, + * ensuring that the refcnt has been bumped to stop + * another thread freeing it underneath us. +*/ + +aclUserGroup* +aclg_find_userGroup(char *n_dn) +{ + aclUserGroup *u_group = NULL; + int i; + + /* Check for Anonymous user */ + if ( n_dn && *n_dn == '\0') return (NULL) ; + + ACLG_LOCK_GROUPCACHE_READ (); + u_group = aclUserGroups->aclg_first; + + for ( i=0; i < aclUserGroups->aclg_num_userGroups; i++ ) { + if ( aclUserGroups->aclg_signature == u_group->aclug_signature && + slapi_utf8casecmp((ACLUCHP)u_group->aclug_ndn, + (ACLUCHP)n_dn ) == 0 ) { + aclg_reader_incr_ugroup_refcnt(u_group); + break; + } + u_group = u_group->aclug_next; + } + + ACLG_ULOCK_GROUPCACHE_READ (); + return(u_group); +} + +/* + * Mark a usergroup for removal from the usergroup cache. + * It will be removed by the first operation traversing the cache + * that finds it. +*/ +void +aclg_markUgroupForRemoval ( aclUserGroup* u_group) { + + ACLG_LOCK_GROUPCACHE_WRITE (); + aclg_regen_ugroup_signature(u_group); + u_group->aclug_refcnt--; + ACLG_ULOCK_GROUPCACHE_WRITE (); +} + +/* + * + * aclg_get_usersGroup + * + * If we already have a the group info then we are done. If we + * don't, then allocate a new one and attach it. + * + * Inputs: + * struct acl_pblock -- The acl private block. + * char *n_dn - normalized client's DN + * + * Returns: + * aclUserGroup - The Group info block. + * + */ +aclUserGroup * +aclg_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn) +{ + + aclUserGroup *u_group, *f_group; + + if ( aclpb && aclpb->aclpb_groupinfo ) + return aclpb->aclpb_groupinfo; + + ACLG_LOCK_GROUPCACHE_WRITE(); + + /* try it one more time. We might have one in the meantime */ + aclg_init_userGroup (aclpb, n_dn , 1 /* got the lock */); + if ( aclpb && aclpb->aclpb_groupinfo ) { + ACLG_ULOCK_GROUPCACHE_WRITE(); + return aclpb->aclpb_groupinfo; + } + + /* + * It is possible at this point that we already have a group cache for the user + * but is is invalid. We can't use it anayway. So, we march along and allocate a new one. + * That's fine as the invalid one will be deallocated when done. + */ + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "ALLOCATING GROUP FOR:%s\n", n_dn,0,0 ); + u_group = ( aclUserGroup * ) slapi_ch_calloc ( 1, sizeof ( aclUserGroup ) ); + + u_group->aclug_refcnt = 1; + if ( (u_group->aclug_refcnt_mutex = PR_NewLock()) == NULL ) { + slapi_ch_free((void **)&u_group); + ACLG_ULOCK_GROUPCACHE_WRITE(); + return(NULL); + } + + u_group->aclug_member_groups = (char **) + slapi_ch_calloc ( 1, + (ACLUG_INCR_GROUPS_LIST * sizeof (char *))); + u_group->aclug_member_group_size = ACLUG_INCR_GROUPS_LIST; + u_group->aclug_numof_member_group = 0; + + u_group->aclug_notmember_groups = (char **) + slapi_ch_calloc ( 1, + (ACLUG_INCR_GROUPS_LIST * sizeof (char *))); + u_group->aclug_notmember_group_size = ACLUG_INCR_GROUPS_LIST; + u_group->aclug_numof_notmember_group = 0; + + u_group->aclug_ndn = slapi_ch_strdup ( n_dn ) ; + + u_group->aclug_signature = aclUserGroups->aclg_signature; + + /* Do we have alreday the max number. If we have then delete the last one */ + if ( aclUserGroups->aclg_num_userGroups >= ACL_MAXCACHE_USERGROUPS - 5 ) { + aclUserGroup *d_group; + + /* We need to traverse thru backwards and delete the one with a refcnt = 0 */ + d_group = aclUserGroups->aclg_last; + while ( d_group ) { + if ( !d_group->aclug_refcnt ) { + __aclg__delete_userGroup ( d_group ); + break; + } else { + d_group = d_group->aclug_prev; + } + } + + /* If we didn't find any, which should be never, + ** we have 5 more tries to do it. + */ + } + f_group = aclUserGroups->aclg_first; + u_group->aclug_next = f_group; + if ( f_group ) f_group->aclug_prev = u_group; + + aclUserGroups->aclg_first = u_group; + if ( aclUserGroups->aclg_last == NULL ) + aclUserGroups->aclg_last = u_group; + + aclUserGroups->aclg_num_userGroups++; + + /* Put it in the queue */ + ACLG_ULOCK_GROUPCACHE_WRITE(); + + /* Now hang on to it */ + aclpb->aclpb_groupinfo = u_group; + return u_group; +} + +/* + * + * __aclg__delete_userGroup + * + * Delete the User's Group cache. + * + * Inputs: + * aclUserGroup - remove this one + * Returns: + * None. + * + * Note: A WRITE Lock on the GroupCache is obtained by the caller + */ +static void +__aclg__delete_userGroup ( aclUserGroup *u_group ) +{ + + aclUserGroup *next_group, *prev_group; + int i; + + if ( !u_group ) return; + + prev_group = u_group->aclug_prev; + next_group = u_group->aclug_next; + + /* + * At this point we must have a 0 refcnt or else we are in a bad shape. + * If we don't have one then at least remove the user's dn so that it will + * be in a condemned state and later deleted. + */ + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "DEALLOCATING GROUP FOR:%s\n", u_group->aclug_ndn,0,0 ); + + slapi_ch_free ( (void **) &u_group->aclug_ndn ); + + PR_DestroyLock(u_group->aclug_refcnt_mutex); + + /* Remove the member GROUPS */ + for (i=0; i < u_group->aclug_numof_member_group; i++ ) + slapi_ch_free ( (void **) &u_group->aclug_member_groups[i] ); + slapi_ch_free ( (void **) &u_group->aclug_member_groups ); + + /* Remove the NOT member GROUPS */ + for (i=0; i < u_group->aclug_numof_notmember_group; i++ ) + slapi_ch_free ( (void **) &u_group->aclug_notmember_groups[i] ); + slapi_ch_free ( (void **) &u_group->aclug_notmember_groups ); + + slapi_ch_free ( (void **) &u_group ); + + if ( prev_group == NULL && next_group == NULL ) { + aclUserGroups->aclg_first = NULL; + aclUserGroups->aclg_last = NULL; + } else if ( prev_group == NULL ) { + next_group->aclug_prev = NULL; + aclUserGroups->aclg_first = next_group; + } else { + prev_group->aclug_next = next_group; + if ( next_group ) + next_group->aclug_prev = prev_group; + else + aclUserGroups->aclg_last = prev_group; + } + aclUserGroups->aclg_num_userGroups--; +} + +void +aclg_regen_group_signature( ) +{ + aclUserGroups->aclg_signature = aclutil_gen_signature ( aclUserGroups->aclg_signature ); +} + +void +aclg_regen_ugroup_signature( aclUserGroup *ugroup) +{ + ugroup->aclug_signature = + aclutil_gen_signature ( ugroup->aclug_signature ); +} + +void +aclg_lock_groupCache ( int type /* 1 for reader and 2 for writer */) +{ + + if (type == 1 ) + ACLG_LOCK_GROUPCACHE_READ(); + else + ACLG_LOCK_GROUPCACHE_WRITE(); +} + +void +aclg_unlock_groupCache ( int type /* 1 for reader and 2 for writer */) +{ + + if (type == 1 ) + ACLG_ULOCK_GROUPCACHE_READ(); + else + ACLG_ULOCK_GROUPCACHE_WRITE(); +} + + +/* + * If you have the write lock on the group cache, you can + * increment the refcnt without taking the mutex. + * If you just have the reader lock on the refcnt then you need to + * take the mutex on the refcnt to increment it--which is what this routine is + * for. + * +*/ + +void +aclg_reader_incr_ugroup_refcnt(aclUserGroup* u_group) { + + PR_Lock(u_group->aclug_refcnt_mutex); + u_group->aclug_refcnt++; + PR_Unlock(u_group->aclug_refcnt_mutex); +} + +/* You need the usergroups read lock to call this routine*/ +int +aclg_numof_usergroups(void) { + + return(aclUserGroups->aclg_num_userGroups); +} + diff --git a/ldap/servers/plugins/acl/aclinit.c b/ldap/servers/plugins/acl/aclinit.c new file mode 100644 index 00000000..21e54337 --- /dev/null +++ b/ldap/servers/plugins/acl/aclinit.c @@ -0,0 +1,537 @@ +/** 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" + +static int __aclinit__RegisterLases(void); +static int __aclinit__RegisterAttributes(void); +static int __aclinit_handler(Slapi_Entry *e, void *callback_data); + +/*************************************************************************** +* +* aclinit_main() +* Main routine which is called at the server boot up time. +* +* 1) Reads all the ACI entries from the database and creates +* the ACL list. +* 2) Registers all the LASes and the GetAttrs supported by the DS. +* 3) Generates anonymous profiles. +* 4) Registers proxy control +* 5) Creates aclpb pool +* +* Input: +* None. +* +* Returns: +* 0 -- no error +* 1 -- Error +* +* Error Handling: +* If any error found during the ACL generation, error is logged. +* +**************************************************************************/ +static int acl_initialized = 0; +int +aclinit_main() +{ + char *cookie = NULL; + Slapi_PBlock *pb; + int rv; + Slapi_DN *sdn; + void *node; + + if (acl_initialized) { + /* There is no need to do anything more */ + return 0; + } + + /* Initialize the LIBACCESS ACL library */ + if (ACL_Init() != 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "ACL Library Initialization failed\n",0,0,0); + return 1; + } + + /* register all the LASes supported by the DS */ + if (ACL_ERR == __aclinit__RegisterLases()) { + /* Error is already logged */ + return 1; + } + + /* Register all the Attrs */ + if (ACL_ERR == __aclinit__RegisterAttributes()) { + /* Error is already logged */ + return 1; + } + + /* + * Register to get backend state changes so we can add/remove + * acis from backends that come up and go down. + */ + + slapi_register_backend_state_change((void *) NULL, acl_be_state_change_fnc); + + + /* register the extensions */ + /* ONREPL Moved to the acl_init function because extensions + need to be registered before any operations are issued + if ( 0 != acl_init_ext() ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to initialize the extensions\n"); + return 1; + } */ + + /* create the mutex array */ + if ( 0 != aclext_alloc_lockarray ( ) ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to create the mutext array\n"); + return 1; + } + + /* Allocate the pool */ + if ( 0 != acl_create_aclpb_pool () ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to create the acl private pool\n"); + return 1; + } + + /* + * Now read all the ACLs from all the backends and put it + * in a list + */ + /* initialize the ACLLIST sub-system */ + if ( 0 != (rv = acllist_init ( ))) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to initialize the plugin:%d\n", rv ); + return 1; + } + + /* Initialize the anonymous profile i.e., generate it */ + rv = aclanom_init (); + + pb = slapi_pblock_new(); + + /* + * search for the aci_attr_type attributes of all entries. + * + * slapi_get_fist_suffix() and slapi_get_next_suffix() do not return the + * rootdse entry so we search for acis in there explicitly here. + */ + + sdn = slapi_sdn_new_dn_byval(""); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Searching for all acis(scope base) at suffix ''\n"); + aclinit_search_and_update_aci ( 0, /* thisbeonly */ + sdn, /* base */ + NULL, /* be name*/ + LDAP_SCOPE_BASE, ACL_ADD_ACIS, + DO_TAKE_ACLCACHE_WRITELOCK); + slapi_sdn_free(&sdn); + + sdn = slapi_get_first_suffix( &node, 1 ); + while (sdn) + { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Searching for all acis(scope subtree) at suffix '%s'\n", + slapi_sdn_get_dn(sdn) ); + aclinit_search_and_update_aci ( 0, /* thisbeonly */ + sdn, /* base */ + NULL, /* be name*/ + LDAP_SCOPE_SUBTREE, ACL_ADD_ACIS, + DO_TAKE_ACLCACHE_WRITELOCK); + sdn = slapi_get_next_suffix( &node, 1 ); + } + + /* Initialize it. */ + acl_initialized = 1; + + /* generate the signatures */ + acl_set_aclsignature ( aclutil_gen_signature ( 100 ) ); + + /* Initialize the user-group cache */ + rv = aclgroup_init ( ); + + aclanom_gen_anomProfile (DO_TAKE_ACLCACHE_READLOCK); + + /* Register both of the proxied authorization controls (version 1 and 2) */ + slapi_register_supported_control( LDAP_CONTROL_PROXYAUTH, + SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE + | SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE + | SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN + | SLAPI_OPERATION_EXTENDED ); + slapi_register_supported_control( LDAP_CONTROL_PROXIEDAUTH, + SLAPI_OPERATION_SEARCH | SLAPI_OPERATION_COMPARE + | SLAPI_OPERATION_ADD | SLAPI_OPERATION_DELETE + | SLAPI_OPERATION_MODIFY | SLAPI_OPERATION_MODDN + | SLAPI_OPERATION_EXTENDED ); + + slapi_pblock_destroy ( pb ); + return 0; +} +/* + * This routine is the one that scans for acis and either adds them + * to the internal cache (op==ACL_ADD_ACIS) or deletes them + * (op==ACL_REMOVE_ACIS). + * + * If thisbeonly is 0 the search + * is conducted on the base with the specifed scope and be_name is ignored. + * This is used at startup time where we iterate over all suffixes, searching + * for all the acis in the DIT to load the ACL cache. + * + * If thisbeonly is 1 then then a be_name must be specified. + * In this case we will search in that backend ONLY. + * This is used in the case where a backend is turned on and off--in this + * case we only want to add/remove the acis in that particular backend and + * not for example in any backends below that one. +*/ + +int +aclinit_search_and_update_aci ( int thisbeonly, const Slapi_DN *base, + char *be_name, int scope, int op, + acl_lock_flag_t lock_flag ) +{ + char *attrs[2] = { "aci", NULL }; + /* Tell __aclinit_handler whether it's an add or a delete */ + int any_error = op; + Slapi_PBlock *aPb; + LDAPControl **ctrls=NULL; + int retval; + struct berval *bval; + aclinit_handler_callback_data_t call_back_data; + + PR_ASSERT( lock_flag == DONT_TAKE_ACLCACHE_WRITELOCK || + lock_flag == DO_TAKE_ACLCACHE_WRITELOCK); + + if ( thisbeonly && be_name == NULL) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Error: This be_name must be specified.\n", 0, 0, 0); + return -1; + } + + + /* + * We need to explicitly request (objectclass=ldapsubentry) + * in order to get all the subentry acis too. + * Note that subentries can be added under subentries (although its not + * recommended) so that + * there may be non-trivial acis under a subentry. + */ + + /* Use new search internal API */ + /* and never retrieve aci from a remote server */ + aPb = slapi_pblock_new (); + + /* + * Set up the control to say "Only get acis from this Backend-- + * there may be more backends under this one. + */ + + if ( thisbeonly ) { + + bval = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + bval->bv_len = strlen(be_name) + 1; + bval->bv_val = slapi_ch_strdup(be_name); + + ctrls = (LDAPControl **)slapi_ch_calloc( 2, sizeof(LDAPControl *)); + ctrls[0] = NULL; + ctrls[1] = NULL; + + retval = slapi_build_control_from_berval( + MTN_CONTROL_USE_ONE_BACKEND_OID, + bval, + 1 /* is critical */, + ctrls); + + } + + slapi_search_internal_set_pb ( aPb, + slapi_sdn_get_dn(base), + scope, + "(|(aci=*)(objectclass=ldapsubentry))", + attrs, + 0 /* attrsonly */, + ctrls /* controls: SLAPI_ARGCONTROLS */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions : get local aci only */); + + if (thisbeonly) { + slapi_pblock_set(aPb, SLAPI_REQCONTROLS, ctrls); + } + + call_back_data.op = op; + call_back_data.retCode = 0; + call_back_data.lock_flag = lock_flag; + + slapi_search_internal_callback_pb(aPb, + &call_back_data /* callback_data */, + NULL/* result_callback */, + __aclinit_handler, + NULL /* referral_callback */); + + if (thisbeonly) { + slapi_ch_free((void **)&bval); + } + + /* + * This frees the control oid, the bv_val and the control itself and the + * ctrls array mem by caling ldap_controls_free()--so we + * don't need to do it ourselves. + */ + slapi_pblock_destroy (aPb); + + return call_back_data.retCode; + +} + +/*************************************************************************** +* +* __aclinit_handler +* +* For each entry, finds if there is any ACL in thet entry. If there is +* then the ACL is processed and stored in the ACL LIST. +* +* +* Input: +* +* +* Returns: +* None. +* +* Error Handling: +* If any error found during the ACL generation, the ACL is +* logged. Also, set in the callback_data so that caller can act upon it. +* +**************************************************************************/ +static int +__aclinit_handler ( Slapi_Entry *e, void *callback_data) +{ + Slapi_Attr *attr; + aclinit_handler_callback_data_t *call_back_data = + (aclinit_handler_callback_data_t*)callback_data; + Slapi_DN *e_sdn; + int rv; + Slapi_Value *sval=NULL; + + call_back_data->retCode = 0; /* assume success--if there's an error we overwrite it */ + if (e != NULL) { + + e_sdn = slapi_entry_get_sdn ( e ); + + /* + * Take the write lock around all the mods--so that + * other operations will see the acicache either before the whole mod + * or after but not, as it was before, during the mod. + * This is in line with the LDAP concept of the operation + * on the whole entry being the atomic unit. + * + */ + + if ( call_back_data->op == ACL_ADD_ACIS ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Adding acis for entry '%s'\n", slapi_sdn_get_dn(e_sdn)); + slapi_entry_attr_find ( e, aci_attr_type, &attr ); + + if ( attr ) { + + const struct berval *attrValue; + + int i; + if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) { + acllist_acicache_WRITE_LOCK(); + } + i= slapi_attr_first_value ( attr, &sval ); + while(i != -1) { + attrValue = slapi_value_get_berval(sval); + + if ( 0 != (rv=acllist_insert_aci_needsLock (e_sdn, attrValue))) { + aclutil_print_err(rv, e_sdn, attrValue, NULL); + + /* We got an error; Log it and then march along */ + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Error: This (%s) ACL will not be considered for evaluation" + " because of syntax errors.\n", + attrValue->bv_val ? attrValue->bv_val: "NULL", 0, 0); + call_back_data->retCode = rv; + } + i= slapi_attr_next_value( attr, i, &sval ); + }/* while */ + if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) { + acllist_acicache_WRITE_UNLOCK(); + } + } + } else if (call_back_data->op == ACL_REMOVE_ACIS) { + + /* Here we are deleting the acis. */ + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Removing acis\n"); + if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) { + acllist_acicache_WRITE_LOCK(); + } + if ( 0 != (rv=acllist_remove_aci_needsLock(e_sdn, NULL))) { + aclutil_print_err(rv, e_sdn, NULL, NULL); + + /* We got an error; Log it and then march along */ + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Error: ACls not deleted from %s\n", + e_sdn, 0, 0); + call_back_data->retCode = rv; + } + if ( call_back_data->lock_flag == DO_TAKE_ACLCACHE_WRITELOCK) { + acllist_acicache_WRITE_UNLOCK(); + } + } + + } + + /* + * If we get here it's success. + * The call_back_data->error is the error code that counts as it's the + * one that the original caller will see--this routine is called off a callbacl. + */ + + return ACL_FALSE; /* "local" error code--it's 0 */ +} +/*************************************************************************** +* +* __acl__RegisterAttributes +* +* Register all the attributes supported by the DS. +* +* Input: +* None. +* +* Returns: +* ACL_OK - No error +* ACL_ERR - in case of errror +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +__aclinit__RegisterAttributes(void) +{ + + ACLMethod_t methodinfo; + NSErr_t errp; + int rv; + + memset (&errp, 0, sizeof(NSErr_t)); + + rv = ACL_MethodRegister(&errp, DS_METHOD, &methodinfo); + if (rv < 0) { + acl_print_acllib_err(&errp, NULL); + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to Register the methods\n", 0,0,0); + return ACL_ERR; + } + rv = ACL_MethodSetDefault (&errp, methodinfo); + if (rv < 0) { + acl_print_acllib_err(&errp, NULL); + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to Set the default method\n", 0,0,0); + return ACL_ERR; + } + rv = ACL_AttrGetterRegister(&errp, ACL_ATTR_IP, DS_LASIpGetter, + methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL); + if (rv < 0) { + acl_print_acllib_err(&errp, NULL); + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to Register Attr ip\n", 0,0,0); + return ACL_ERR; + } + rv = ACL_AttrGetterRegister(&errp, ACL_ATTR_DNS, DS_LASDnsGetter, + methodinfo, ACL_DBTYPE_ANY, ACL_AT_FRONT, NULL); + if (rv < 0) { + acl_print_acllib_err(&errp, NULL); + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Unable to Register Attr dns\n", 0,0,0); + return ACL_ERR; + } + return ACL_OK; +} + +/*************************************************************************** +* +* __acl__RegisterLases +* Register all the LASes supported by the DS. +* +* The DS doesnot support user/group. We have defined our own LAS +* so that we can display/print an error when the LAS is invoked. +* Input: +* None. +* +* Returns: +* ACL_OK - No error +* ACL_ERR - in case of errror +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +__aclinit__RegisterLases(void) +{ + + if (ACL_LasRegister(NULL, DS_LAS_USER, (LASEvalFunc_t) DS_LASUserEval, + (LASFlushFunc_t) NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register USER Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_GROUP, (LASEvalFunc_t) DS_LASGroupEval, + (LASFlushFunc_t) NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register GROUP Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_GROUPDN, (LASEvalFunc_t)DS_LASGroupDnEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register GROUPDN Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_ROLEDN, (LASEvalFunc_t)DS_LASRoleDnEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register ROLEDN Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_USERDN, (LASEvalFunc_t)DS_LASUserDnEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register USERDN Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_USERDNATTR, + (LASEvalFunc_t)DS_LASUserDnAttrEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register USERDNATTR Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_AUTHMETHOD, + (LASEvalFunc_t)DS_LASAuthMethodEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register CLIENTAUTHTYPE Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_GROUPDNATTR, + (LASEvalFunc_t)DS_LASGroupDnAttrEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register GROUPDNATTR Las\n",0,0,0); + return ACL_ERR; + } + if (ACL_LasRegister(NULL, DS_LAS_USERATTR, + (LASEvalFunc_t)DS_LASUserAttrEval, + (LASFlushFunc_t)NULL) < 0) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "Unable to register USERATTR Las\n",0,0,0); + return ACL_ERR; + } + return ACL_OK; +} diff --git a/ldap/servers/plugins/acl/acllas.c b/ldap/servers/plugins/acl/acllas.c new file mode 100644 index 00000000..0179b0e6 --- /dev/null +++ b/ldap/servers/plugins/acl/acllas.c @@ -0,0 +1,3848 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include <ipfstruct.h> +#include "acl.h" + +/* + A word on this file: + + The various routines here implement each component of the subject of an aci + eg. "groupdn", "userdn","roledn", "userattr" etc. + They are responsible for evaluating each individual keyword not for doing + the boolean combination of these keywords, nor for combining multiple + allow()/deny() statements--that's libaccess's job. + For example, for "groupdn", DS_LASGroupDnEval might have to evaluate + something like this: + + "groupdn = "ldap:///cn=G1,o=sun.com || ldap:///cn=G2,o=sun.com" + + The "=" here may be "!=" as well and these routines take care of the + comparator. + + These rotuines get called via acl__TestRights(), which calls + ACL_EvalTestRights() a libaccess routine (the immediately calling routine is + ACLEvalAce() in oneeval.cpp). + + They should return LAS_EVAL_TRUE, if that keyword component evaluates to + TRUE, LAS_EVAL_FALSE if it evaluates to FALSE and LAS_EVAL_FAIL if an + error occurrs during evaluation. Note that once any component of a subject + returns LAS_EVAL_FAIL, the evaluation in libaccess stops and the whole + subject does not match and that aci is not applied. +*/ + +/* + + A word on three-valued logic: + + In general when you do boolean combination of terms some of which + may evaluate to UNDEFINED then you need to define what the combination + means. + So, for example libaccess implements a scheme which once UNDEFINED + is returned for a term, it bales out of the + evaluation and the whole expression evaluates to UNDEFINED. + In this case the aci will not apply. + On the other hand LDAP filters (cf. rfc2251 4.5.1) say that for OR, + an expression will + evaluate to TRUE if any term is TRUE, even if some terms are UNDEFINED. + Other off the cuff options might be to redefine UNDEFINED to be FALSE, + or TRUE. + + Which is best ? + + Well it probably depends on exactly what is to decided based on the + evaluation of the logical expression. However, the final suggestion is + almost certainly + bad--you are unlikely to want to take an action based on an undefined + result and + defining UNDEFINED to be either TRUE or FALSE may result in the overall + expression + returning TRUE--a security hole. The only case this might work is if you + are dealing with restricted + expressions eg. terms may only be AND'ed togther--in this case defining + UNDEFINED to be FALSE would guarantee a result of FALSE. + + The libaccess approach of returning UNDEFINED once an UNDEFINED is + encountered during + evaluation is not too bad--at least it guarantees that no aci will apply + based on an + undefined value. However, with an aci like this "...allow(all) A or B" + where A returned UNDEFINED, you might be disappointed not to receive the + rights if it was B that + was granting you the rights and evaluation of A, which has nothing to do + with you, returns UNDEFINED. In the case of an aci like + "...deny(all) A or B" then the same + situation is arguably a security hole. Note that this scheme also makes + the final result + dependent on the evaluation order and so if the evaluation engine does + anything fancy internally (eg. reordering the terms in an OR so that fast + to evaluate ones came first) then + this would need to be documented so that a user (or a tool) could look at + the external syntax and figure out the result of the evaluation. + Also it breaks commutivity and De Morgans law. + + The LDAP filter scheme is starting to look good--it solves the problems of + the + libaccess approach, makes the final result of an expression independent of + the evaluation order and + gives you back commutivity of OR and AND. De Morgans is still broken, but + that's because of the asymmetry of behaviour of UNDEFINED with OR and AND. + + So...? + + For acis, in general it can look like this: + + "...allow(rights)(LogicalCombinationofBindRule); + deny(LogicalCombinationOfBindRule)...." + + A BindRule is one of the "userdn", "groupdn" or "userattr" things and it + can look like this: + + "groupdn = "ldap:///cn=G1,o=sun.com || ldap:///cn=G2,o=sun.com" + + The "=" here may be "!=" as well and these routines take care of the + comparator. + + For "userattr" keywords a mutilvalued attribute amounts a logical OR of the + individual values. There is also a logical OR over the different levels + as specified by the "parent" keyword. + + In fact there are three levels of logical combination: + + 1. In the aclplugin: + The "||" and "!=" combinator for BindRule keywords like userdn and + groupdn. + The fact that for the "userattr" keyword, a mutilvalued attribute is + evaluated as "||". Same for the different levels. + 2. In libaccess: + The logical combination of BindRules. + 3. In libaccess: + The evaluation of multiple BindRules seperated by ";", which means OR. + + The LDAP filter three-valued logic SHOULD be applied to each level but + here's the way it works right now: + + 1. At this level it depends.... + + DS_LASIpGetter - get attr for IP - + returns ip address or LAS_EVAL_FAIL for error. + no logical combination. + DS_LASDnsGetter - get attr for DNS- + returns dns name or LAS_EVAL_FAIL for error + no logical combination. + DS_LASUserDnEval - LAS Evaluation for USERDN - + three-valued logic + logical combination: || and != + DS_LASGroupDnEval - LAS Evaluation for GROUPDN - + three-valued logic + logical combination: || and != + DS_LASRoleDnEval - LAS Evaluation for ROLEDN - + three-valued logic + logical combination: || and != + DS_LASUserDnAttrEval - LAS Evaluation for USERDNATTR - + three-valued logic + logical combination || (over specified attribute values and + parent keyword levels), != + DS_LASAuthMethodEval - LAS Evaluation for AUTHMETHOD - + three-valued logic ( logical combinations: !=) + DS_LASGroupDnAttrEval - LAS Evaluation for GROUPDNATTR - + three-valued logic + logical combination || (over specified attribute values and + parent keyword levels), != + DS_LASUserAttrEval - LAS Evaluation for USERATTR - + USER, GROUPDN and ROLEDN as above. + LDAPURL -- three-valued logic (logical combinations: || over + specified attribute vales, !=) + attrname#attrvalue -- three-valued logic, logical combination:!= + + 2. The libaccess scheme applies at this level. + 3. The LDAP filter three-valued logic applies at this level. + + Example of realistic, non-bizarre things that cause evaluation of a + BindRule to be undefined are exceeding some resource limits (nesting level, + lookthrough limit) in group membership evaluation, or trying to get ADD + permission from the "userattr" keyword at "parent" level 0. + Note that not everything that might be construed as an error needs to be + taken as UNDEFINED. For example, things like not finding a user or an + attribute in an entry can be defined away as TRUE or FALSE. eg. in an + LDAP filter (cn=rob) applied to an entry where cn is not present is FALSE, + not UNDEFINED. Similarly, if the number of levels in a parent keyword + exceeds the allowed limit, we just ignore the rest--though this + is a syntax error which should be detected at parse time. + + +*/ + +/* To get around warning: declared in ldapserver/lib/ldaputil/ldaputili.h */ +extern int ldapu_member_certificate_match (void* cert, const char* desc); + +/****************************************************************************/ +/* Defines, Constants, ande Declarations */ +/****************************************************************************/ +static char* const type_objectClass = "objectclass"; +static char* const filter_groups = "(|(objectclass=groupOfNames) (objectclass=groupOfUniqueNames)(objectclass=groupOfCertificates)(objectclass=groupOfURLs))"; +static char* const type_member = "member"; +static char* const type_uniquemember = "uniquemember"; +static char* const type_memberURL = "memberURL"; +static char* const type_memberCert = "memberCertificateDescription"; + +/* cache strategy for groups */ +#define ACLLAS_CACHE_MEMBER_GROUPS 0x1 +#define ACLLAS_CACHE_NOT_MEMBER_GROUPS 0x2 +#define ACLLAS_CACHE_ALL_GROUPS 0x3 + +/****************************************************************************/ +/* prototypes */ +/****************************************************************************/ +static int acllas__handle_group_entry(Slapi_Entry *, void *); +static int acllas__user_ismember_of_group(struct acl_pblock *aclpb, + char* groupDN, + char* clientDN, + int cache_status, + CERTCertificate *clientCert); +static int acllas__user_has_role( struct acl_pblock *aclpb, + Slapi_DN *roleDN, Slapi_DN *clientDn); +static int acllas__add_allgroups (Slapi_Entry* e, void *callback_data); +static int acllas__eval_memberGroupDnAttr (char *attrName, + Slapi_Entry *e, + char *n_clientdn, + struct acl_pblock *aclpb); +static int acllas__verify_client (Slapi_Entry* e, void *callback_data); +static char* acllas__dn_parent( char *dn, int level); +static int acllas__get_members (Slapi_Entry* e, void *callback_data); +static int acllas__client_match_URL (struct acl_pblock *aclpb, + char *n_dn, char *url ); +static int acllas__handle_client_search (Slapi_Entry *e, void *callback_data); +static int __acllas_setup ( NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth, char *lasType, char *lasName, lasInfo *linfo); +int +aclutil_evaluate_macro( char * user, lasInfo *lasinfo, + acl_eval_types evalType ); +static int +acllas_eval_one_user( struct acl_pblock *aclpb, + char * clientDN, char *userKeyword); +static int +acllas_eval_one_group(char *group, lasInfo *lasinfo); +static int +acllas_eval_one_role(char *role, lasInfo *lasinfo); +static char ** +acllas_replace_dn_macro( char *rule, char *matched_val, lasInfo *lasinfo); +static char ** +acllas_replace_attr_macro( char *rule, lasInfo *lasinfo); +static int +acllas_eval_one_target_filter( char * str, Slapi_Entry *e); + +/****************************************************************************/ + +int +DS_LASIpGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg) +{ + + struct acl_pblock *aclpb = NULL; + IPAddr_t ip=0; + PRNetAddr client_praddr; + struct in_addr client_addr; + int rv; + + + rv = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&aclpb, + subject, resource, auth_info, global_auth); + if ( rv != LAS_EVAL_TRUE || ( NULL == aclpb )) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASIpGetter:Unable to get the ACLPB(%d)\n", rv,0,0); + return LAS_EVAL_FAIL; + } + + if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CONN_CLIENTNETADDR, + &client_praddr ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" ); + return( LAS_EVAL_FAIL ); + } + + if ( !PR_IsNetAddrType(&client_praddr, PR_IpAddrV4Mapped) ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Client address is IPv6. ACLs only support IPv4 addresses so far.\n"); + return( LAS_EVAL_FAIL ); + } + + client_addr.s_addr = client_praddr.ipv6.ip.pr_s6_addr32[3]; + + ip = (IPAddr_t) ntohl( client_addr.s_addr ); + rv = PListInitProp(subject, 0, ACL_ATTR_IP, (void *)ip, NULL); + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning client ip address '%s'\n", + (slapi_is_loglevel_set(SLAPI_LOG_ACL) ? inet_ntoa(client_addr) : "")); + + return LAS_EVAL_TRUE; + +} + +/* + * This is called from the libaccess code when it needs to find a dns name. + * It's called from ACL_GetAttribute() when it finds that ACL_ATTR_DNS is + * not already part of the proplist. + * +*/ + +int +DS_LASDnsGetter(NSErr_t *errp, PList_t subject, PList_t resource, PList_t + auth_info, PList_t global_auth, void *arg) +{ + struct acl_pblock *aclpb = NULL; + PRNetAddr client_praddr; + PRHostEnt *hp; + char *dnsName = NULL; + int rv; + struct berval **clientDns; + + + rv = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&aclpb, + subject, resource, auth_info, global_auth); + if ( rv != LAS_EVAL_TRUE || ( NULL == aclpb )) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASDnsGetter:Unable to get the ACLPB(%d)\n", rv,0,0); + return LAS_EVAL_FAIL; + } + + if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CLIENT_DNS, &clientDns ) != 0 ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" ); + return( LAS_EVAL_FAIL ); + } + + /* + * If the client hostname has already been put into the pblock then + * use that. Otherwise we work it out and add it ourselves. + * This info is connection-lifetime so with multiple operaitons on the same + * connection we will only do the calculation once. + * + * rbyrneXXX surely this code would be better in connection.c so + * the name would be just there waiting for us, and everyone else. + * + */ + + if ( clientDns && clientDns[0] != NULL && clientDns[0]->bv_val ) { + dnsName = clientDns[0]->bv_val; + } else { + struct berval **dnsList; + char buf[PR_NETDB_BUF_SIZE]; + + if ( slapi_pblock_get( aclpb->aclpb_pblock, SLAPI_CONN_CLIENTNETADDR, &client_praddr ) != 0 ) { + + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Could not get client IP.\n" ); + return( LAS_EVAL_FAIL ); + } + hp = (PRHostEnt *)slapi_ch_malloc( sizeof(PRHostEnt) ); + if ( PR_GetHostByAddr( &(client_praddr), (char *)buf, sizeof(buf), hp ) == PR_SUCCESS ) { + if ( hp->h_name != NULL ) { + dnsList = (struct berval**) + slapi_ch_calloc (1, sizeof(struct berval*) * (1 + 1)); + *dnsList = (struct berval*) + slapi_ch_calloc ( 1, sizeof(struct berval)); + dnsName = (*dnsList)->bv_val = slapi_ch_strdup( hp->h_name ); + (*dnsList)->bv_len = strlen ( (*dnsList)->bv_val ); + slapi_pblock_set( aclpb->aclpb_pblock, SLAPI_CLIENT_DNS, &dnsList ); + } + } + slapi_ch_free( (void **)&hp ); + } + + if ( NULL == dnsName ) return LAS_EVAL_FAIL; + + rv = PListInitProp(subject, 0, ACL_ATTR_DNS, dnsName, NULL); + if (rv < 0) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASDnsGetter:Couldn't set the DNS property(%d)\n", rv ); + return LAS_EVAL_FAIL; + } + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "DNS name: %s\n", dnsName ); + return LAS_EVAL_TRUE; + +} +/***************************************************************************/ +/* New LASes */ +/* */ +/* 1. user, groups. -- stubs to report errors. Not supported. */ +/* 2. userdn */ +/* 3. groupdn */ +/* 4. userdnattr */ +/* 5. authmethod */ +/* 6. groupdnattr */ +/* 7. roledn */ +/* */ +/* */ +/***************************************************************************/ +int +DS_LASUserEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "User LAS is not supported in the ACL\n",0,0,0); + + return LAS_EVAL_INVALID; +} + +int +DS_LASGroupEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Group LAS is not supported in the ACL\n",0,0,0); + + return LAS_EVAL_INVALID; +} + +/*************************************************************************** +* +* DS_LASUserDnEval +* Evaluate the "userdn" LAS. See if the user has rights. +* +* Input: +* attr_name The string "userdn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +int +DS_LASUserDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *users = NULL; + char *s_user, *user = NULL; + char *ptr = NULL; + char *end_dn = NULL; + char *n_edn = NULL; + char *parent_dn = NULL; + int matched; + int rc; + short len; + char *s = NULL; + const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix); + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_USERDN, "DS_LASUserDnEval", &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + users = slapi_ch_strdup(attr_pattern); + user = users; + matched = ACL_FALSE; + + /* check if the clientdn is one of the users */ + while(user != 0 && *user != 0 && matched != ACL_TRUE ) { + + /* ignore leading whitespace */ + while(ldap_utf8isspace(user)) + LDAP_UTF8INC(user); + + /* Now we must see the userdn in the following + ** formats: + ** + ** The following formats are supported: + ** + ** 1. The DN itself: + ** allow (read) userdn = "ldap:///cn=prasanta, ..." + ** + ** 2. keyword SELF: + ** allow (write) + ** userdn = "ldap:///self" + ** + ** 3. Pattern: + ** deny (read) userdn = "ldap:///cn=*, o=netscape, c = us"; + ** + ** 4. Anonymous user + ** deny (read, write) userdn = "ldap:///anyone" + ** + ** 5. All users (All authenticated users) + ** allow (search) ** userdn = "ldap:///all" + ** 6. parent "ldap:///parent" + ** 7. Synamic users using the URL + ** + ** + ** DNs must be separated by "||". Ex: + ** allow (read) + ** userdn = "ldap:///DN1 || ldap:///DN2" + */ + + + /* The DN is now "ldap:///DN" + ** remove the "ldap:///" part + */ + if (strncasecmp (user, LDAP_URL_prefix, + LDAP_URL_prefix_len) == 0) { + s_user = user; + user += LDAP_URL_prefix_len; + + } else { + char ebuf[ BUFSIZ ]; + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "DS_LASUserDnEval:Syntax error(%s)\n", + escape_string_with_punctuation( user, ebuf ), 0,0); + return LAS_EVAL_FAIL; + } + + /* Now we have the starting point of the "userdn" */ + if ((end_dn = strstr(user, "||")) != NULL) { + auto char *t = end_dn; + LDAP_UTF8INC(end_dn); + LDAP_UTF8INC(end_dn); + *t = 0; + } + + /* Now user is a null terminated string */ + + if (*user) { + while(ldap_utf8isspace(user)) + LDAP_UTF8INC(user); + /* ignore trailing whitespace */ + len = strlen(user); + ptr = user+len-1; + while(ldap_utf8isspace(ptr)){ *ptr = '\0'; LDAP_UTF8DEC(ptr); } + } + + /* + ** Check , if the user is a anonymous user. In that case + ** We must find the rule "ldap:///anyone" + */ + if (lasinfo.anomUser) { + if (strcasecmp(user, "anyone") == 0 ) { + /* matches -- anonymous user */ + matched = ACL_TRUE; + break; + } + } else { + /* URL format */ + + if ((s = strstr (user, ACL_RULE_MACRO_DN_KEY)) != NULL || + (s = strstr (user, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL || + (s = strstr (user, ACL_RULE_MACRO_ATTR_KEY)) != NULL) { + + matched = aclutil_evaluate_macro( s_user, &lasinfo, + ACL_EVAL_USER); + if (matched == ACL_TRUE) { + break; + } + + } else if ((s = strchr (user, '?'))!= NULL) { + /* URL format */ + if (acllas__client_match_URL ( lasinfo.aclpb, lasinfo.clientDn, + s_user) == ACL_TRUE) { + matched = ACL_TRUE; + break; + } + } else if (strcasecmp(user, "anyone") == 0 ) { + /* Anyone means anyone in the world */ + matched = ACL_TRUE; + break; + } else if (strcasecmp(user, "self") == 0) { + if (n_edn == NULL) { + n_edn = slapi_entry_get_ndn ( lasinfo.resourceEntry ); + } + if (slapi_utf8casecmp((ACLUCHP)lasinfo.clientDn, (ACLUCHP)n_edn) == 0) + matched = ACL_TRUE; + break; + } else if (strcasecmp(user, "parent") == 0) { + if (n_edn == NULL) { + n_edn = slapi_entry_get_ndn ( lasinfo.resourceEntry ); + } + /* get the parent */ + parent_dn = slapi_dn_parent(n_edn); + if (parent_dn && + slapi_utf8casecmp ((ACLUCHP)lasinfo.clientDn, (ACLUCHP)parent_dn) == 0) + matched = ACL_TRUE; + + if (parent_dn) slapi_ch_free ( (void **) &parent_dn ); + break; + } else if (strcasecmp(user, "all") == 0) { + /* matches -- */ + matched = ACL_TRUE; + break; + } else if (strchr(user, '*')) { + char line[200]; + char *lineptr = &line[0]; + char *newline = NULL; + int lenu = 0; + Slapi_Filter *f = NULL; + char *tt; + int filterChoice; + + /* + ** what we are doing is faking the str2simple() + ** function with a "userdn = "user") + */ + for (tt = user; *tt; tt++) + *tt = TOLOWER ( *tt ); + + if ((lenu = strlen(user)) > 190) { /* 200 - 9 for "(userdn=%s)" */ + newline = slapi_ch_malloc(lenu + 10); + lineptr = newline; + } + + sprintf (lineptr, "(userdn=%s)", user); + if ((f = slapi_str2filter (lineptr)) == NULL) { + if (newline) slapi_ch_free((void **) &newline); + /* try the next one */ + break; + } + if (newline) slapi_ch_free((void **) &newline); + filterChoice = slapi_filter_get_choice ( f ); + + if (( filterChoice != LDAP_FILTER_SUBSTRINGS) && + ( filterChoice != LDAP_FILTER_PRESENT)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserDnEval:Error in gen. filter(%s)\n", user); + } + if ((rc = acl_match_substring( f, + lasinfo.clientDn, + 1 /*exact match */) + ) == ACL_TRUE) { + matched = ACL_TRUE; + slapi_filter_free(f,1); + break; + } + if (rc == ACL_ERR) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserDnEval:Error in matching patteren(%s)\n", + user,0,0); + } + slapi_filter_free(f,1); + } else { + /* Must be a simple dn then */ + if (slapi_utf8casecmp((ACLUCHP)lasinfo.clientDn, + (ACLUCHP)slapi_dn_normalize(user)) == 0) { + matched = ACL_TRUE; + break; + } + } + } + + if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another user will evaluate to TRUE */ + got_undefined = 1; + } + /* Nothing matched -- try the next DN */ + user = end_dn; + } /* end of while */ + + slapi_ch_free ( (void **) &users); + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for userdn evaluation.\n"); + } + + return rc; +} + +/*************************************************************************** +* +* DS_LASGroupDnEval +* +* +* Input: +* attr_name The string "userdn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return code +* If the client is in any of the groups mentioned this groupdn keywrod +* then returns LAS_EVAL_TRUE, if he's not in any LAS_EVAL_FALSE. +* If any of the membership evaluations fail, then it goes on to evaluate the +* others. +* +* Error Handling: +* None. +* +**************************************************************************/ +int +DS_LASGroupDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *groups; + char *groupName; + char *ptr; + char *end_dn; + int matched; + int rc; + int len; + const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix); + int any_group = 0; + lasInfo lasinfo; + int got_undefined = 0; + + /* the setup should not fail under normal operation */ + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_GROUPDN, "DS_LASGroupDnEval", &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + groups = slapi_ch_strdup(attr_pattern); + groupName = groups; + matched = ACL_FALSE; + + /* check if the groupdn is one of the users */ + while(groupName != 0 && *groupName != 0 && matched != ACL_TRUE) { + + /* ignore leading whitespace */ + while(ldap_utf8isspace(groupName)) + LDAP_UTF8INC(groupName); + + /* + ** The syntax allowed for the groupdn is + ** + ** Example: + ** groupdn = "ldap:///dn1 || ldap:///dn2"; + ** + */ + + if (strncasecmp (groupName, LDAP_URL_prefix, + LDAP_URL_prefix_len) == 0) { + groupName += LDAP_URL_prefix_len; + } else { + char ebuf[ BUFSIZ ]; + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "DS_LASGroupDnEval:Syntax error(%s)\n", + escape_string_with_punctuation( groupName, ebuf ),0,0); + } + + /* Now we have the starting point of the "groupdn" */ + if ((end_dn = strstr(groupName, "||")) != NULL) { + auto char *t = end_dn; + LDAP_UTF8INC(end_dn); + LDAP_UTF8INC(end_dn); + *t = 0; + } + + if (*groupName) { + while(ldap_utf8isspace(groupName)) + LDAP_UTF8INC(groupName); + /* ignore trailing whitespace */ + len = strlen(groupName); + ptr = groupName+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + } + + /* + ** Now we have the DN of the group. Evaluate the "clientdn" + ** and see if the user is a member of the group. + */ + if (0 == (strcasecmp(groupName, "anyone"))) { + any_group = 1; + } + + if (any_group) { + /* anyone in the world */ + matched = ACL_TRUE; + break; + } else if ( lasinfo.anomUser && + (lasinfo.aclpb->aclpb_clientcert == NULL) && (!any_group)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Group not evaluated(%s)\n", groupName); + break; + } else { + char *s; + + if ((s = strstr (groupName, ACL_RULE_MACRO_DN_KEY)) != NULL || + (s = strstr (groupName, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL || + (s = strstr (groupName, ACL_RULE_MACRO_ATTR_KEY)) != NULL) { + + matched = aclutil_evaluate_macro( groupName, &lasinfo, + ACL_EVAL_GROUP); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASGroupDnEval: Param group name:%s\n", + groupName); + } else {/* normal evaluation */ + + matched = acllas_eval_one_group( groupName, &lasinfo); + + } + + if ( matched == ACL_TRUE ) { + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group will evaluate to TRUE */ + got_undefined = 1; + } + } + /* Nothing matched -- try the next DN */ + groupName = end_dn; + + } /* end of while */ + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for groupdn evaluation.\n"); + } + + slapi_ch_free ((void**) &groups); + return rc; +} +/*************************************************************************** +* +* DS_LASRoleDnEval +* +* +* Input: +* attr_name The string "roledn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A "||" sperated list of roles +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +int +DS_LASRoleDnEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *roles; + char *role; + char *ptr; + char *end_dn; + int matched; + int rc; + int len; + const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix); + int any_role = 0; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_ROLEDN, "DS_LASRoleDnEval", + &lasinfo )) ) { + return LAS_EVAL_FALSE; + } + + + roles = slapi_ch_strdup(attr_pattern); + role = roles; + matched = ACL_FALSE; + + /* check if the roledn is one of the users */ + while(role != 0 && *role != 0 && matched != ACL_TRUE) { + + /* ignore leading whitespace */ + while(ldap_utf8isspace(role)) + LDAP_UTF8INC(role); + + /* + ** The syntax allowed for the roledn is + ** + ** Example: + ** roledn = "ldap:///roledn1 || ldap:///roledn2"; + ** + */ + + if (strncasecmp (role, LDAP_URL_prefix, + LDAP_URL_prefix_len) == 0) { + role += LDAP_URL_prefix_len; + } else { + char ebuf[ BUFSIZ ]; + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "DS_LASRoleDnEval:Syntax error(%s)\n", + escape_string_with_punctuation( role, ebuf ),0,0); + } + + /* Now we have the starting point of the "roledn" */ + if ((end_dn = strstr(role, "||")) != NULL) { + auto char *t = end_dn; + LDAP_UTF8INC(end_dn); + LDAP_UTF8INC(end_dn); + *t = 0; + } + + + if (*role) { + while(ldap_utf8isspace(role)) + LDAP_UTF8INC(role); + /* ignore trailing whitespace */ + len = strlen(role); + ptr = role+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + } + + /* + ** Now we have the DN of the role. Evaluate the "clientdn" + ** and see if the user has this role. + */ + if (0 == (strcasecmp(role, "anyone"))) { + any_role = 1; + } + + if (any_role) { + /* anyone in the world */ + matched = ACL_TRUE; + break; + } else if ( lasinfo.anomUser && + (lasinfo.aclpb->aclpb_clientcert == NULL) && (!any_role)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Role not evaluated(%s) for anon user\n", role); + break; + } else { + + /* Take care of param strings */ + + char *s; + + if ((s = strstr (role, ACL_RULE_MACRO_DN_KEY)) != NULL || + (s = strstr (role, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL || + (s = strstr (role, ACL_RULE_MACRO_ATTR_KEY)) != NULL) { + + matched = aclutil_evaluate_macro( role, &lasinfo, + ACL_EVAL_ROLE); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASRoleDnEval: Param role name:%s\n", + role); + } else {/* normal evaluation */ + + matched = acllas_eval_one_role( role, &lasinfo); + + } + + if ( matched == ACL_TRUE ) { + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another role will evaluate to TRUE */ + got_undefined = 1; + } + } + /* Nothing matched -- try the next DN */ + role = end_dn; + + } /* end of while */ + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for roledn evaluation.\n"); + } + + slapi_ch_free ((void**) &roles); + return rc; +} +/*************************************************************************** +* +* DS_LASUserDnAttrEval +* +* +* Input: +* attr_name The string "userdn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +struct userdnattr_info { + char *attr; + int result; + char *clientdn; +}; +#define ACLLAS_MAX_LEVELS 10 +int +DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *n_currEntryDn = NULL; + char *s_attrName, *attrName; + char *ptr; + int matched; + int rc, len, i; + char *val; + Slapi_Attr *a; + int levels[ACLLAS_MAX_LEVELS]; + int numOflevels =0; + struct userdnattr_info info; + char *attrs[2] = { LDAP_ALL_USER_ATTRS, NULL }; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_USERDNATTR, "DS_LASUserDnAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* + ** The userdnAttr syntax is + ** userdnattr = <attribute> or + ** userdnattr = parent[0,2,4].attribute" + ** Ex: + ** userdnattr = manager; or + ** userdnattr = "parent[0,2,4].manager"; + ** + ** Here 0 means current level, 2 means grandfather and + ** 4 (great great grandfather) + ** + ** The function of this LAS is to compare the value of the + ** attribute in the Slapi_Entry with the "userdn". + ** + ** Ex: userdn: "cn=prasanta, o= netscape, c= us" + ** and in the Slapi_Entry the manager attribute has + ** manager = <value>. Compare the userdn with manager.value to + ** determine the result. + ** + */ + s_attrName = attrName = slapi_ch_strdup (attr_pattern); + + /* ignore leading/trailing whitespace */ + while(ldap_utf8isspace(attrName)) LDAP_UTF8INC(attrName); + len = strlen(attrName); + ptr = attrName+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + + + /* See if we have a parent[2].attr" rule */ + if ( (ptr = strstr(attrName, "parent[")) != NULL) { + char *word, *str, *next; + + numOflevels = 0; + n_currEntryDn = slapi_entry_get_ndn ( lasinfo.resourceEntry ); + str = attrName; + + word = ldap_utf8strtok_r(str, "[],. ",&next); + /* The first word is "parent[" and so it's not important */ + + while ((word= ldap_utf8strtok_r(NULL, "[],.", &next)) != NULL) { + if (ldap_utf8isdigit(word)) { + while (word && ldap_utf8isspace(word)) LDAP_UTF8INC(word); + if (numOflevels < ACLLAS_MAX_LEVELS) + levels[numOflevels++] = atoi (word); + else { + /* + * Here, ignore the extra levels..it's really + * a syntax error which should have been ruled out at parse time + */ + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "DS_LASUserDnattr: Exceeded the ATTR LIMIT:%d: Ignoring extra levels\n", + ACLLAS_MAX_LEVELS); + } + } else { + /* Must be the attr name. We can goof of by + ** having parent[1,2,a] but then you have to be + ** stupid to do that. + */ + char *p = word; + if (*--p == '.') { + attrName = word; + break; + } + } + } + info.attr = attrName; + info.clientdn = lasinfo.clientDn; + info.result = 0; + } else { + levels[0] = 0; + numOflevels = 1; + + } + + /* No attribute name specified--it's a syntax error and so undefined */ + if (attrName == NULL ) { + slapi_ch_free ( (void**) &s_attrName); + return LAS_EVAL_FAIL; + } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"Attr:%s\n" , attrName, 0,0); + matched = ACL_FALSE; + for (i=0; i < numOflevels; i++) { + if ( levels[i] == 0 ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int j; + + /* + * For the add operation, the resource itself (level 0) + * must never be allowed to grant access-- + * This is because access would be granted based on a value + * of an attribute in the new entry--security hole. + * + */ + + if ( lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL info: userdnAttr does not allow ADD permission at level 0.\n"); + got_undefined = 1; + continue; + } + slapi_entry_attr_find( lasinfo.resourceEntry, attrName, &a); + if ( NULL == a ) continue; + j= slapi_attr_first_value ( a,&sval ); + while ( j != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + /* Here if atleast 1 value matches then we are done.*/ + val = slapi_dn_normalize ( + slapi_ch_strdup( attrVal->bv_val)); + + if (slapi_utf8casecmp((ACLUCHP)val, (ACLUCHP)lasinfo.clientDn ) == 0) { + char ebuf [ BUFSIZ ]; + /* Wow it matches */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "userdnAttr matches(%s, %s) level (%d)\n", + val, + ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo.clientDn, ebuf), + 0); + matched = ACL_TRUE; + slapi_ch_free ( (void **) &val); + break; + } + slapi_ch_free ( (void**) &val); + j = slapi_attr_next_value ( a, j, &sval ); + } + } else { + char *p_dn; /* parent dn */ + + p_dn = acllas__dn_parent (n_currEntryDn, levels[i]); + if (p_dn == NULL) continue; + + /* use new search internal API */ + { + Slapi_PBlock *aPb = slapi_pblock_new (); + + /* + * This search may be chained if chaining for ACL is + * is enabled in the backend and the entry is in + * a chained backend. + */ + slapi_search_internal_set_pb ( aPb, + p_dn, + LDAP_SCOPE_BASE, + "objectclass=*", + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + 0 /* actions */); + + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__verify_client, + NULL /* referral_callback */); + slapi_pblock_destroy(aPb); + } + + /* + * Currently info.result is boolean so + * we do not need to check for ACL_DONT_KNOW + */ + if (info.result) { + matched = ACL_TRUE; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "userdnAttr matches at level (%d)\n", levels[i]); + } + } + if (matched == ACL_TRUE) { + break; + } + } + + slapi_ch_free ( (void **) &s_attrName); + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for userdnattr evaluation.\n"); + } + + return rc; +} +/*************************************************************************** +* +* DS_LASAuthMethodEval +* +* +* Input: +* attr_name The string "authmethod" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +int +DS_LASAuthMethodEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *attr; + char *ptr; + int len; + int matched; + int rc; + char *s = NULL; + lasInfo lasinfo; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_AUTHMETHOD, "DS_LASAuthMethodEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + attr = attr_pattern; + + matched = ACL_FALSE; + /* ignore leading whitespace */ + s = strstr (attr, SLAPD_AUTH_SASL); + if ( s) { + s +=4; + attr = s; + } + + while(ldap_utf8isspace(attr)) LDAP_UTF8INC(attr); + len = strlen(attr); + ptr = attr+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASAuthMethodEval:authtype:%s authmethod:%s\n", + lasinfo.authType, attr); + + /* None method means, we don't care -- otherwise we care */ + if ((strcasecmp(attr, "none") == 0) || + (strcasecmp(attr, lasinfo.authType) == 0)) { + matched = ACL_TRUE; + } + + if ( matched == ACL_TRUE || matched == ACL_FALSE) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for authmethod evaluation.\n"); + } + + return rc; +} + +/**************************************************************************** +* Struct to evaluate and keep the current members being evaluated +* +* 0 1 2 3 4 5 +* member: [a,b,c,d,e,f] +* c_idx may point to 2 i.e to "c" if "c" is being evaluated to +* see if any of "c" members is the clientDN. +* lu_idx points to the last used spot i.e 5. +* lu_idx++ is the next free spot. +* +* We allocate ACLLAS_MAX_GRP_MEMBER ptr first and then we add if it +* is required. +* +***************************************************************************/ +#define ACLLAS_MAX_GRP_MEMBER 50 +struct member_info +{ + char *member; /* member DN */ + struct member_info *parent; /* parent of this member */ +} member_info; + +struct eval_info +{ + int result; /* result status */ + char *userDN; /* client's normalized DN */ + int c_idx; /* Index to the current member being processed */ + int lu_idx; /* Index to the slot where the last member is stored */ + char **member; /* mmebers list */ + struct member_info **memberInfo;/* array of memberInfo */ + CERTCertificate *clientCert; /* ptr to cert */ + struct acl_pblock *aclpb; /*aclpblock */ +} eval_info; + +static void +dump_member_info ( struct member_info *minfo, char *buf ) +{ + if ( minfo ) + { + if ( minfo->parent ) + { + dump_member_info ( minfo->parent, buf ); + } + else + { + strcat ( buf, "<nil>" ); + } + strcat ( buf, "->" ); + strcat ( buf, minfo->member ); + } +} + +static void +dump_eval_info (char *caller, struct eval_info *info, int idx) +{ + char buf[1024]; + int len; + int i; + + if ( idx < 0 ) + { + sprintf ( buf, "\nuserDN=\"%s\"\nmember=", info->userDN); + if (info->member) + { + len = strlen (buf); + sprintf ( &(buf[len]), "\"%s\"", info->member ); + } + len = strlen (buf); + sprintf ( &(buf[len]), "\nmemberinfo[%d]-[%d]:", info->c_idx, info->lu_idx ); + if ( info->memberInfo ) + for (i = 0; i <= info->lu_idx; i++) + { + len = strlen(buf); + sprintf ( &buf[len], "\n [%d]: ", i ); + dump_member_info ( info->memberInfo[i], buf ); + } + slapi_log_error ( SLAPI_LOG_FATAL, NULL, "\n======== candidate member info in eval_info ========%s\n\n", buf ); + } + else + { + sprintf (buf, "evaluated candidate [%d]=", idx); + switch (info->result) + { + case ACL_TRUE: + strcat (buf, "ACL_TRUE\n"); + break; + case ACL_FALSE: + strcat (buf, "ACL_FALSE\n"); + break; + case ACL_DONT_KNOW: + strcat (buf, "ACL_DONT_KNOW\n"); + break; + default: + len = strlen (buf); + sprintf ( &(buf[len]), "%d\n", info->result ); + break; + } + dump_member_info ( info->memberInfo[idx], buf ); + slapi_log_error ( SLAPI_LOG_FATAL, NULL, "%s\n", buf ); + } +} + + +/*************************************************************************** +* +* acllas__user_ismember_of_group +* +* Check if the user is a member of the group and nested groups.. +* +* Input: +* char *groupdn - DN of the group +* char *clientDN - Dn of the client +* +* Returns: +* ACL_TRUE - the user is a member of the group. +* ACL_FALSE - Not a member +* ACL_DONT_KNOW - Any errors eg. resource limits exceeded and we could +* not compelte the evaluation. +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +acllas__user_ismember_of_group( struct acl_pblock *aclpb, + char* groupDN, + char* clientDN, + int cache_status, + CERTCertificate *clientCert) +{ + + + char *attrs[5]; + char *currDN; + int i,j; + int result = ACL_FALSE; + struct eval_info info; + int nesting_level; + int numOfMembersAtCurrentLevel; + int numOfMembersVisited; + int totalMembersVisited; + int numOfMembers; + int max_nestlevel; + int max_memberlimit; + aclUserGroup *u_group; + char ebuf [ BUFSIZ ]; + struct member_info *groupMember = NULL; + struct member_info *parentGroup = NULL; + + /* + ** First, Let's look thru the cached list and determine if the client is + ** a member of the cached list of groups. + */ + if ( (u_group = aclg_get_usersGroup ( aclpb , clientDN )) == NULL) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Failed to find/allocate a usergroup--aborting evaluation\n", 0, 0); + return(ACL_DONT_KNOW); + } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluating user %s in group %s?\n", + clientDN, groupDN ); + + /* Before I start using, get a reader lock on the group cache */ + aclg_lock_groupCache ( 1 /* reader */ ); + for ( i= 0; i < u_group->aclug_numof_member_group; i++) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "-- In %s\n", + u_group->aclug_member_groups[i] ); + if ( slapi_utf8casecmp((ACLUCHP)groupDN, (ACLUCHP)u_group->aclug_member_groups[i]) == 0){ + aclg_unlock_groupCache ( 1 /* reader */ ); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_TRUE\n"); + return ACL_TRUE; + } + } + + /* see if we know the client is not a member of a group. */ + for ( i= 0; i < u_group->aclug_numof_notmember_group; i++) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "-- Not in %s\n", + u_group->aclug_notmember_groups[i] ); + if ( slapi_utf8casecmp((ACLUCHP)groupDN, (ACLUCHP)u_group->aclug_notmember_groups[i]) == 0){ + aclg_unlock_groupCache ( 1 /* reader */ ); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_FALSE\n"); + return ACL_FALSE; + } + } + + /* + ** That means we didn't find the the group in the cache. -- we have to add it + ** so no need for READ lock - need to get a WRITE lock. We will get it just before + ** modifying it. + */ + aclg_unlock_groupCache ( 1 /* reader */ ); + + /* Indicate the initialization handler -- this module will be + ** called by the backend to evaluate the entry. + */ + info.result = ACL_FALSE; + if (clientDN && *clientDN != '\0') + info.userDN = clientDN; + else + info.userDN = NULL; + + info.c_idx = 0; + info.memberInfo = (struct member_info **) slapi_ch_malloc (ACLLAS_MAX_GRP_MEMBER * sizeof(struct member_info *)); + groupMember = (struct member_info *) slapi_ch_malloc ( sizeof (struct member_info) ); + groupMember->member = slapi_ch_strdup(groupDN); + groupMember->parent = NULL; + info.memberInfo[0] = groupMember; + info.lu_idx = 0; + + attrs[0] = type_member; + attrs[1] = type_uniquemember; + attrs[2] = type_memberURL; + attrs[3] = type_memberCert; + attrs[4] = NULL; + + currDN = groupMember->member; + + /* nesting level is 0 to begin with */ + nesting_level = 0; + numOfMembersVisited = 0; + totalMembersVisited = 0; + numOfMembersAtCurrentLevel = 1; + + if (clientCert) + info.clientCert = clientCert; + else + info.clientCert = NULL; + info.aclpb = aclpb; + + max_memberlimit = aclpb->aclpb_max_member_sizelimit; + max_nestlevel = aclpb->aclpb_max_nesting_level; + + /* dump_eval_info ( "acllas__user_ismember_of_group", &info, -1 ); */ + +eval_another_member: + + numOfMembers = info.lu_idx - info.c_idx; + + /* Use new search internal API */ + { + Slapi_PBlock * aPb = slapi_pblock_new (); + + /* + * This search may NOT be chained--we demand that group + * definition be local. + */ + slapi_search_internal_set_pb ( aPb, + currDN, + LDAP_SCOPE_BASE, + filter_groups, + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions */); + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__handle_group_entry, + NULL /* referral_callback */); + + if ( info.result == ACL_TRUE ) + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"-- In %s\n", info.memberInfo[info.c_idx]->member ); + else if ( info.result == ACL_FALSE ) + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"-- Not in %s\n", info.memberInfo[info.c_idx]->member ); + + slapi_pblock_destroy (aPb); + } + + if (info.result == ACL_TRUE) { + /* + ** that means the client is a member of the + ** group or one of the nested groups. We are done. + */ + result = ACL_TRUE; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_TRUE\n"); + goto free_and_return; + } + numOfMembersVisited++; + + if (numOfMembersVisited == numOfMembersAtCurrentLevel) { + /* This means we have looked at all the members for this level */ + numOfMembersVisited = 0; + + /* Now we are ready to look at the next level */ + nesting_level++; + + /* So, far we have visited ... */ + totalMembersVisited += numOfMembersAtCurrentLevel; + + /* How many members in the next level ? */ + numOfMembersAtCurrentLevel = + info.lu_idx - totalMembersVisited +1; + } + + if ((nesting_level > max_nestlevel)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "GroupEval:Member not found within the allowed nesting level (Allowed:%d Looked at:%d)\n", + max_nestlevel, nesting_level, 0); + + result = ACL_DONT_KNOW; /* don't try to cache info based on this result */ + goto free_and_return; + } + + /* limit of -1 means "no limit */ + if (info.c_idx > max_memberlimit && + max_memberlimit != -1 ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "GroupEval:Looked at too many entries:(%d, %d)\n", + info.c_idx, info.lu_idx,0); + result = ACL_DONT_KNOW; /* don't try to cache info based on this result */ + goto free_and_return; + } + if (info.lu_idx > info.c_idx) { + if (numOfMembers == (info.lu_idx - info.c_idx)) { + /* That means it's not a GROUP. It is just another + ** useless member which doesn't match. Remove the BAD dude. + */ + groupMember = info.memberInfo[info.c_idx]; + + if (groupMember ) { + if ( groupMember->member ) slapi_ch_free ( (void **) &groupMember->member ); + slapi_ch_free ( (void **) &groupMember ); + info.memberInfo[info.c_idx] = NULL; + } + } + info.c_idx++; + + /* Go thru the stack and see if we have already + ** evaluated this group. If we have, then skip it. + */ + while (1) { + int evalNext=0; + int j; + if (info.c_idx > info.lu_idx) { + /* That means we have crossed the limit. We + ** may end of in this situation if we + ** have circular groups + */ + info.c_idx = info.lu_idx; + goto free_and_return; + } + + /* Break out of the loop if we have searched to the end */ + groupMember = info.memberInfo[info.c_idx]; + if ( (NULL == groupMember) || ((currDN = groupMember->member)!= NULL)) + break; + + for (j = 0; j < info.c_idx; j++) { + groupMember = info.memberInfo[j]; + if (groupMember->member && + (slapi_utf8casecmp((ACLUCHP)currDN, (ACLUCHP)groupMember->member) == 0)) { + /* Don't need the duplicate */ + groupMember = info.memberInfo[info.c_idx]; + slapi_ch_free ( (void **) &groupMember->member ); + slapi_ch_free ( (void **) &groupMember ); + info.memberInfo[info.c_idx] = NULL; + info.c_idx++; + evalNext=1; + break; + } + } + if (!evalNext) break; + } + /* Make sure that we have a valid DN to chug along */ + groupMember = info.memberInfo[info.c_idx]; + if ((info.c_idx <= info.lu_idx) && ((currDN = groupMember->member) != NULL)) + goto eval_another_member; + } + +free_and_return: + /* Remove the unnecessary members from the list which + ** we might have accumulated during the last execution + ** and we don't need to look at them. + */ + i = info.c_idx; + i++; + while (i <= info.lu_idx) { + groupMember = info.memberInfo[i]; + slapi_ch_free ( (void **) &groupMember->member ); + slapi_ch_free ( (void **) &groupMember ); + info.memberInfo[i] = NULL; + i++; + } + + /* + ** Now we have a list which has all the groups + ** which we need to cache + */ + info.lu_idx = info.c_idx; + + /* since we are updating the groupcache, get a write lock */ + aclg_lock_groupCache ( 2 /* writer */ ); + + /* + ** Keep the result of the evaluation in the cache. + ** We have 2 lists: member_of and not_member_of. We can use this + ** cached information next time we evaluate groups. + */ + if (result == ACL_TRUE && + (cache_status & ACLLAS_CACHE_MEMBER_GROUPS)) { + int ngr = 0; + + /* get the last group which the user is a member of */ + groupMember = info.memberInfo[info.c_idx]; + + while ( groupMember ) { + int already_cached = 0; + + parentGroup = groupMember->parent; + for (j=0; j < u_group->aclug_numof_member_group;j++){ + if (slapi_utf8casecmp( (ACLUCHP)groupMember->member, + (ACLUCHP)u_group->aclug_member_groups[j]) == 0) { + already_cached = 1; + break; + } + } + if (already_cached) { + groupMember = parentGroup; + parentGroup = NULL; + continue; + } + + ngr = u_group->aclug_numof_member_group++; + if (u_group->aclug_numof_member_group >= + u_group->aclug_member_group_size){ + u_group->aclug_member_groups = + (char **) slapi_ch_realloc ( + (void *) u_group->aclug_member_groups, + (u_group->aclug_member_group_size + + ACLUG_INCR_GROUPS_LIST) * + sizeof (char *)); + u_group->aclug_member_group_size += + ACLUG_INCR_GROUPS_LIST; + } + u_group->aclug_member_groups[ngr] = slapi_ch_strdup ( groupMember->member ); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Adding Group (%s) ParentGroup (%s) to the IN GROUP List\n", + groupMember->member , parentGroup ? parentGroup->member: "NULL"); + + groupMember = parentGroup; + parentGroup = NULL; + } + } else if (result == ACL_FALSE && + (cache_status & ACLLAS_CACHE_NOT_MEMBER_GROUPS)) { + int ngr = 0; + + /* NOT IN THE GROUP LIST */ + /* get the last group which the user is a member of */ + groupMember = info.memberInfo[info.c_idx]; + + while ( groupMember ) { + int already_cached = 0; + + parentGroup = groupMember->parent; + for (j=0; j < u_group->aclug_numof_notmember_group;j++){ + if (slapi_utf8casecmp( (ACLUCHP)groupMember->member, + (ACLUCHP)u_group->aclug_notmember_groups[j]) == 0) { + already_cached = 1; + break; + } + } + if (already_cached) { + groupMember = parentGroup; + parentGroup = NULL; + continue; + } + + ngr = u_group->aclug_numof_notmember_group++; + if (u_group->aclug_numof_notmember_group >= + u_group->aclug_notmember_group_size){ + u_group->aclug_notmember_groups = + (char **) slapi_ch_realloc ( + (void *) u_group->aclug_notmember_groups, + (u_group->aclug_notmember_group_size + + ACLUG_INCR_GROUPS_LIST) * + sizeof (char *)); + u_group->aclug_notmember_group_size += + ACLUG_INCR_GROUPS_LIST; + } + u_group->aclug_notmember_groups[ngr] = slapi_ch_strdup ( groupMember->member ); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Adding Group (%s) ParentGroup (%s) to the NOT IN GROUP List\n", + groupMember->member , parentGroup ? parentGroup->member: "NULL"); + + groupMember = parentGroup; + parentGroup = NULL; + } + } else if ( result == ACL_DONT_KNOW ) { + + /* + * We terminated the search without reaching a conclusion--so + * don't cache any info based on this evaluation. + */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Evaluated ACL_DONT_KNOW\n"); + } + + /* Unlock the group cache, we are done with updating */ + aclg_unlock_groupCache ( 2 /* writer */ ); + + for (i=0; i <= info.lu_idx; i++) { + groupMember = info.memberInfo[i]; + if ( NULL == groupMember ) continue; + + slapi_ch_free ( (void **) &groupMember->member ); + slapi_ch_free ( (void **) &groupMember ); + } + + /* free the pointer array.*/ + slapi_ch_free ( (void **) &info.memberInfo); + return result; +} + +/*************************************************************************** +* +* acllas__handle_group_entry +* +* handler called. Compares the userdn value and determines if it's +* a member of not. +* +* Input: +* +* +* Returns: +* +* Error Handling: +* +**************************************************************************/ +static int +acllas__handle_group_entry (Slapi_Entry* e, void *callback_data) +{ + struct eval_info *info; + Slapi_Attr *currAttr, *nextAttr; + char *n_dn, *attrType; + short n; + int i; + + info = (struct eval_info *) callback_data; + info->result = ACL_FALSE; + + if (e == NULL) { + return 0; + } + + slapi_entry_first_attr ( e, &currAttr); + if ( NULL == currAttr ) return 0; + + slapi_attr_get_type ( currAttr, &attrType ); + if (NULL == attrType ) return 0; + + do { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + + if ((strcasecmp (attrType, type_member) == 0) || + (strcasecmp (attrType, type_uniquemember) == 0 )) { + + i = slapi_attr_first_value ( currAttr,&sval ); + while ( i != -1 ) { + struct member_info *groupMember = NULL; + attrVal = slapi_value_get_berval ( sval ); + n_dn = slapi_dn_normalize ( slapi_ch_strdup( attrVal->bv_val)); + info->lu_idx++; + n = info->lu_idx; + if (!(n % ACLLAS_MAX_GRP_MEMBER)) { + info->memberInfo = (struct member_info **) slapi_ch_realloc( + (void *) info->memberInfo, + (n+ACLLAS_MAX_GRP_MEMBER) * + sizeof(struct eval_info *)); + } + + /* allocate the space for the member and attch it to the list */ + groupMember = (struct member_info *) slapi_ch_malloc ( sizeof ( struct member_info ) ); + groupMember->member = n_dn; + groupMember->parent = info->memberInfo[info->c_idx]; + info->memberInfo[n] = groupMember; + + if (info->userDN && + slapi_utf8casecmp((ACLUCHP)n_dn, (ACLUCHP)info->userDN) == 0) { + info->result = ACL_TRUE; + return 0; + } + i = slapi_attr_next_value ( currAttr, i, &sval ); + } + /* Evaluate Dynamic groups */ + } else if (strcasecmp ( attrType, type_memberURL) == 0) { + char *memberURL, *savURL; + + if (!info->userDN) continue; + + i= slapi_attr_first_value ( currAttr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + /* + * memberURL may start with "ldap:///" or "ldap://host:port" + * ldap://localhost:11000/o=ace industry,c=us?? + * or + * ldap:///o=ace industry,c=us?? + */ + if (strncasecmp( attrVal->bv_val, "ldap://",7) == 0 || + strncasecmp( attrVal->bv_val, "ldaps://",8) == 0) { + savURL = memberURL = slapi_ch_strdup ( attrVal->bv_val); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL Group Eval:MemberURL:%s\n", memberURL); + info->result = acllas__client_match_URL ( + info->aclpb, + info->userDN, + memberURL); + slapi_ch_free ( (void**) &savURL); + if (info->result == ACL_TRUE) + return 0; + } else { + /* This means that the URL is ill-formed */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL Group Eval:Badly Formed MemberURL:%s\n", attrVal->bv_val); + } + i = slapi_attr_next_value ( currAttr, i, &sval ); + } + /* Evaluate Fortezza groups */ + } else if ((strcasecmp (attrType, type_memberCert) == 0) ) { + /* Do we have the certificate around */ + if (!info->clientCert) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + " acllas__handle_group_entry:Client Cert missing\n" ); + continue; + } + i = slapi_attr_first_value ( currAttr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + if (ldapu_member_certificate_match ( + info->clientCert, + attrVal->bv_val) == LDAP_SUCCESS) { + info->result = ACL_TRUE; + return 0; + } + i = slapi_attr_next_value ( currAttr, i, &sval ); + } + } + + attrType = NULL; + /* get the next attr */ + slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( NULL == nextAttr ) break; + + currAttr = nextAttr; + slapi_attr_get_type ( currAttr, &attrType ); + + } while ( NULL != attrType ); + + return 0; +} +/*************************************************************************** +* +* DS_LASGroupDnAttrEval +* +* +* Input: +* attr_name The string "groupdnattr" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +struct groupdnattr_info +{ + char *attrName; /* name of the attribute */ + int numofGroups; /* number of groups */ + char **member; +}; +int +DS_LASGroupDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *s_attrName = NULL; + char *attrName; + char *ptr; + int matched; + int rc; + int len; + Slapi_Attr *attr; + int levels[ACLLAS_MAX_LEVELS]; + int numOflevels = 0; + char *n_currEntryDn = NULL; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_GROUPDNATTR, "DS_LASGroupDnAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* For anonymous client, the answer is XXX come back to this */ + if ( lasinfo.anomUser ) + return LAS_EVAL_FALSE; + + /* + ** The groupdnAttr syntax is + ** groupdnattr = <attribute> + ** Ex: + ** groupdnattr = SIEmanager; + ** + ** The function of this LAS is to find out if the client belongs + ** to any group that is specified in the attr. + */ + attrName = attr_pattern; + if (strstr(attrName, LDAP_URL_prefix)) { + char *s; + + + /* In this case "grppupdnattr="ldap:///base??attr" */ + + if ((s = strstr (attrName, ACL_RULE_MACRO_DN_KEY)) != NULL || + (s = strstr (attrName, ACL_RULE_MACRO_DN_LEVELS_KEY)) != NULL || + (s = strstr (attrName, ACL_RULE_MACRO_ATTR_KEY)) != NULL) { + + matched = aclutil_evaluate_macro( attrName, &lasinfo, + ACL_EVAL_GROUPDNATTR); + } else{ + + matched = acllas__eval_memberGroupDnAttr(attrName, + lasinfo.resourceEntry, + lasinfo.clientDn, + lasinfo.aclpb); + } + if ( matched == ACL_DONT_KNOW) { + got_undefined = 1; + } + } else { + int i; + char *n_groupdn; + + /* ignore leading/trailing whitespace */ + while(ldap_utf8isspace(attrName)) LDAP_UTF8INC(attrName); + len = strlen(attrName); + ptr = attrName+len-1; + while(ldap_utf8isspace(ptr)) { *ptr = '\0'; LDAP_UTF8DEC(ptr); } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"Attr:%s\n" , attrName, 0,0); + + /* See if we have a parent[2].attr" rule */ + if ( (ptr = strstr(attrName, "parent[")) != NULL) { + char *word, *str, *next; + + numOflevels = 0; + n_currEntryDn = slapi_entry_get_ndn ( lasinfo.resourceEntry ) ; + s_attrName = attrName = slapi_ch_strdup ( attr_pattern ); + str = attrName; + + word = ldap_utf8strtok_r(str, "[],. ",&next); + /* The first word is "parent[" and so it's not important */ + + while ((word= ldap_utf8strtok_r(NULL, "[],.", &next)) != NULL) { + if (ldap_utf8isdigit(word)) { + while (word && ldap_utf8isspace(word)) LDAP_UTF8INC(word); + if (numOflevels < ACLLAS_MAX_LEVELS) + levels[numOflevels++] = atoi (word); + else { + /* + * Here, ignore the extra levels..it's really + * a syntax error which should have been ruled out at parse time + */ + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "DS_LASGroupDnattr: Exceeded the ATTR LIMIT:%d: Ignoring extra levels\n", + ACLLAS_MAX_LEVELS,0,0); + } + } else { + /* Must be the attr name. We can goof of by + ** having parent[1,2,a] but then you have to be + ** stupid to do that. + */ + char *p = word; + if (*--p == '.') { + attrName = word; + break; + } + } + } + } else { + levels[0] = 0; + numOflevels = 1; + } + + matched = ACL_FALSE; + for (i=0; i < numOflevels; i++) { + if ( levels[i] == 0 ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int attr_i; + + /* + * For the add operation, the resource itself (level 0) + * must never be allowed to grant access-- + * This is because access would be granted based on a value + * of an attribute in the new entry--security hole. + * XXX is this therefore FALSE or DONT_KNOW ? + */ + + if ( lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL info: groupdnAttr does not allow ADD permission at level 0.\n"); + got_undefined = 1; + continue; + } + slapi_entry_attr_find ( lasinfo.resourceEntry, attrName, &attr); + if ( !attr) continue; + attr_i= slapi_attr_first_value ( attr,&sval ); + while ( attr_i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + n_groupdn = slapi_dn_normalize( + slapi_ch_strdup( attrVal->bv_val)); + matched = acllas__user_ismember_of_group ( + lasinfo.aclpb, n_groupdn, lasinfo.clientDn, + ACLLAS_CACHE_MEMBER_GROUPS, + lasinfo.aclpb->aclpb_clientcert); + slapi_ch_free ( (void **) &n_groupdn); + if (matched == ACL_TRUE ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "groupdnattr matches at level (%d)\n", levels[i]); + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group will evaluate to TRUE */ + got_undefined = 1; + } + attr_i= slapi_attr_next_value ( attr, attr_i, &sval ); + } + } else { + char *p_dn; + struct groupdnattr_info info; + char *attrs[2]; + int j; + + info.numofGroups = 0; + attrs[0] = info.attrName = attrName; + attrs[1] = NULL; + + p_dn = acllas__dn_parent (n_currEntryDn, levels[i]); + + if (p_dn == NULL) continue; + + /* Use new search internal API */ + { + + Slapi_PBlock *aPb = slapi_pblock_new (); + /* + * This search may NOT be chained--if the user's definition is + * remote and the group is dynamic and the user entry + * changes then we would not notice--so don't go + * find the user entry in the first place. + */ + slapi_search_internal_set_pb ( aPb, + p_dn, + LDAP_SCOPE_BASE, + "objectclass=*", + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions */); + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__get_members, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + } + + if (info.numofGroups <= 0) { + continue; + } + for (j=0; j <info.numofGroups; j++) { + if (slapi_utf8casecmp((ACLUCHP)info.member[j], + (ACLUCHP)lasinfo.clientDn) == 0) { + matched = ACL_TRUE; + break; + } + matched = acllas__user_ismember_of_group ( + lasinfo.aclpb, info.member[j], + lasinfo.clientDn, ACLLAS_CACHE_ALL_GROUPS, + lasinfo.aclpb->aclpb_clientcert); + if (matched == ACL_TRUE) { + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group will evaluate to TRUE */ + got_undefined = 1; + } + } + /* Deallocate the member array and the member struct */ + for (j=0; j < info.numofGroups; j++) + slapi_ch_free ((void **) &info.member[j]); + slapi_ch_free ((void **) &info.member); + } + if (matched == ACL_TRUE) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "groupdnattr matches at level (%d)\n", levels[i]); + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group at another level + * will evaluate to TRUE. + */ + got_undefined = 1; + } + + } /* NumofLevels */ + } + if (s_attrName) slapi_ch_free ((void**) &s_attrName ); + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for groupdnattr evaluation.\n"); + } + + return rc; +} + +/* + * acllas__eval_memberGroupDnAttr + * + * return ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW + * + * Inverse group evaluation. Find all the groups that the user is a + * member of. Find all teh groups that contain those groups. Do an + * upward nested level search. By the end of it, we will know all the + * groups that the clinet is a member of under that search scope. + * + * This model seems to be very fast if we have few groups at the + * leaf level. + * + */ +static int +acllas__eval_memberGroupDnAttr (char *attrName, Slapi_Entry *e, + char *n_clientdn, struct acl_pblock *aclpb) +{ + + Slapi_Attr *attr; + char *s, *p; + char *str, *s_str, *base, *groupattr; + int i,j,k,matched, enumerate_groups; + aclUserGroup *u_group; + char ebuf [ BUFSIZ ]; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + + /* Parse the URL -- We can't use the ldap_url_parse() + ** we don't follow thw complete url naming scheme + */ + s_str = str = slapi_ch_strdup(attrName); + while (str && ldap_utf8isspace(str)) LDAP_UTF8INC( str ); + str +=8; + s = strchr (str, '?'); + if (s) { + p = s; + p++; + *s = '\0'; + base = str; + s = strchr (p, '?'); + if (s) *s = '\0'; + + groupattr = p; + } else { + slapi_ch_free ( (void **)&s_str ); + return ACL_FALSE; + } + + if ( (u_group = aclg_get_usersGroup ( aclpb , n_clientdn )) == NULL) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Failed to find/allocate a usergroup--aborting evaluation\n", 0, 0); + slapi_ch_free ( (void **)&s_str ); + return(ACL_DONT_KNOW); + } + + /* + ** First find out if we have already searched this base or + ** if we are searching a subtree to an already enumerated base. + */ + enumerate_groups = 1; + for (j=0; j < aclpb->aclpb_numof_bases; j++) { + if (slapi_dn_issuffix(aclpb->aclpb_grpsearchbase[j], base)) { + enumerate_groups = 0; + break; + } + } + + + /* See if we have already enumerated all the groups which the + ** client is a member of. + */ + if (enumerate_groups) { + char filter_str[BUFSIZ]; + char *attrs[3]; + struct eval_info info; + char *curMemberDn; + int Done = 0; + int ngr, tt; + + /* Add the scope to the list of scopes */ + if (aclpb->aclpb_numof_bases >= (aclpb->aclpb_grpsearchbase_size-1)) { + aclpb->aclpb_grpsearchbase = (char **) + slapi_ch_realloc ( + (void *) aclpb->aclpb_grpsearchbase, + (aclpb->aclpb_grpsearchbase_size + + ACLPB_INCR_BASES) * + sizeof (char *)); + aclpb->aclpb_grpsearchbase_size += ACLPB_INCR_BASES; + } + aclpb->aclpb_grpsearchbase[aclpb->aclpb_numof_bases++] = + slapi_dn_normalize(slapi_ch_strdup(base)); + + /* Set up info to do a search */ + attrs[0] = type_member; + attrs[1] = type_uniquemember; + attrs[2] = NULL; + + info.c_idx = info.lu_idx = 0; + info.member = + (char **) slapi_ch_malloc (ACLLAS_MAX_GRP_MEMBER * sizeof(char *)); + curMemberDn = n_clientdn; + + while (!Done) { + char *filter_str_ptr = &filter_str[0]; + char *new_filter_str = NULL; + int lenf = strlen(curMemberDn)<<1; + + if (lenf > (BUFSIZ - 28)) { /* 28 for "(|(uniquemember=%s)(member=%s))" */ + new_filter_str = slapi_ch_malloc(lenf + 28); + filter_str_ptr = new_filter_str; + } + + /* + ** Search the db for groups that the client is a member of. + ** Once found cache it. cache only unique groups. + */ + tt = info.lu_idx; + sprintf (filter_str_ptr,"(|(uniquemember=%s)(member=%s))", + curMemberDn, curMemberDn); + + /* Use new search internal API */ + { + Slapi_PBlock *aPb = slapi_pblock_new (); + /* + * This search may NOT be chained--we demand that group + * definition be local. + */ + slapi_search_internal_set_pb ( aPb, + base, + LDAP_SCOPE_SUBTREE, + filter_str_ptr, + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions */); + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__add_allgroups, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + } + + if (new_filter_str) slapi_ch_free((void **) &new_filter_str); + + if (tt == info.lu_idx) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "currDn:(%s) \n\tNO MEMBER ADDED\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (curMemberDn, ebuf) , 0,0); + } else { + for (i=tt; i < info.lu_idx; i++) + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "currDn:(%s) \n\tADDED MEMBER[%d]=%s\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (curMemberDn, ebuf), i, info.member[i]); + } + + if (info.c_idx >= info.lu_idx) { + for (i=0; i < info.lu_idx; i++) { + int already_cached = 0; + for (j=0; j < u_group->aclug_numof_member_group; + j++){ + if (slapi_utf8casecmp( + (ACLUCHP)info.member[i], + (ACLUCHP)u_group->aclug_member_groups[j]) == 0) { + slapi_ch_free ((void **) &info.member[i] ); + info.member[i] = NULL; + already_cached = 1; + break; + } + + } + + if (already_cached) continue; + + ngr = u_group->aclug_numof_member_group++; + if (u_group->aclug_numof_member_group >= + u_group->aclug_member_group_size){ + u_group->aclug_member_groups = + (char **) slapi_ch_realloc ( + (void *) u_group->aclug_member_groups, + (u_group->aclug_member_group_size + + ACLUG_INCR_GROUPS_LIST) * sizeof(char *)); + + u_group->aclug_member_group_size += + ACLUG_INCR_GROUPS_LIST; + } + u_group->aclug_member_groups[ngr] = info.member[i]; + info.member[i] = NULL; + } + slapi_ch_free ((void **) &info.member); + Done = 1; + } else { + curMemberDn = info.member[info.c_idx]; + info.c_idx++; + } + } + } + + for (j=0; j < u_group->aclug_numof_member_group; j++) + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "acllas__eval_memberGroupDnAttr:GROUP[%d] IN CACHE:%s\n", + j, ACL_ESCAPE_STRING_WITH_PUNCTUATION (u_group->aclug_member_groups[j], ebuf),0); + + matched = ACL_FALSE; + slapi_entry_attr_find( e, groupattr, &attr); + if (attr == NULL) { + slapi_ch_free ( (void **)&s_str ); + return ACL_FALSE; + } + { + k = slapi_attr_first_value ( attr,&sval ); + while ( k != -1 ) { + char *n_attrval; + attrVal = slapi_value_get_berval ( sval ); + n_attrval = slapi_ch_strdup( attrVal->bv_val); + n_attrval = slapi_dn_normalize (n_attrval); + + /* We support: The attribute value can be a USER or a GROUP. + ** Let's compare with the client, thi might be just an user. If it is not + ** then we test it against the list of groups. + */ + if (slapi_utf8casecmp ((ACLUCHP)n_attrval, (ACLUCHP)n_clientdn) == 0 ) { + matched = ACL_TRUE; + slapi_ch_free ( (void **)&n_attrval ); + break; + } + for (j=0; j <u_group->aclug_numof_member_group; j++) { + if ( slapi_utf8casecmp((ACLUCHP)n_attrval, + (ACLUCHP)u_group->aclug_member_groups[j]) == 0) { + matched = ACL_TRUE; + break; + } + } + slapi_ch_free ( (void **)&n_attrval ); + if (matched == ACL_TRUE) break; + k= slapi_attr_next_value ( attr, k, &sval ); + } + } + slapi_ch_free ( (void **)&s_str ); + return matched; +} + +static int +acllas__add_allgroups (Slapi_Entry* e, void *callback_data) +{ + int i, n, m; + struct eval_info *info; + char *n_dn; + + info = (struct eval_info *) callback_data; + + /* + ** Once we are here means this is a valid group. First see + ** If we have already seen this group. If not, add it to the + ** member list. + */ + n_dn = slapi_ch_strdup ( slapi_entry_get_ndn ( e ) ); + for (i=0; i < info->lu_idx; i++) { + if (slapi_utf8casecmp((ACLUCHP)n_dn, (ACLUCHP)info->member[i]) == 0) { + slapi_ch_free ( (void **) &n_dn); + return 0; + } + } + + m = info->lu_idx; + n = info->lu_idx++; + if (!(n % ACLLAS_MAX_GRP_MEMBER)) { + info->member = (char **) slapi_ch_realloc ( + (void *) info->member, + (n+ACLLAS_MAX_GRP_MEMBER) * sizeof(char *)); + } + info->member[m] = n_dn; + return 0; +} +/* + * + * acllas__dn_parent + * + * This code should belong to dn.c. However this is specific to acl and I had + * 2 choices 1) create a new API or 2) reuse the slapi_dN_parent + * + * Returns a ptr to the parent based on the level. + * + */ +#define DNSEPARATOR(c) (c == ',' || c == ';') +static char* +acllas__dn_parent( char *dn, int level) +{ + char *s, *dnstr; + int inquote; + int curLevel; + int lastLoop = 0; + + if ( dn == NULL || *dn == '\0' ) { + return( NULL ); + } + + /* An X.500-style name, which looks like foo=bar,sha=baz,... */ + /* Do we have any dn seprator or not */ + if ((strchr(dn,',') == NULL) && (strchr(dn,';') == NULL)) + return (NULL); + + inquote = 0; + curLevel = 1; + dnstr = dn; + while ( curLevel <= level) { + if (lastLoop) break; + if (curLevel == level) lastLoop = 1; + for ( s = dnstr; *s; s++ ) { + if ( *s == '\\' ) { + if ( *(s + 1) ) + s++; + continue; + } + if ( inquote ) { + if ( *s == '"' ) + inquote = 0; + } else { + if ( *s == '"' ) + inquote = 1; + else if ( DNSEPARATOR( *s ) ) { + if (curLevel == level) + return( s + 1 ); + dnstr = s + 1; + curLevel++; + break; + } + } + } + if ( *s == '\0') { + /* Got to the end of the string without reaching level, + * so return NULL. + */ + return(NULL); + } + } + + return( NULL ); +} +/* + * acllas__verify_client + * + * returns 1 if the attribute exists in the entry and + * it's value is equal to the client Dn. + * If the attribute is not in the entry, or it is and the + * value differs from the clientDn then returns FALSE. + * + * Verify if client's DN is stored in the attrbute or not. + * This is a handler from a search being done at + * DS_LASUserDnAttrEval(). + * + */ +static int +acllas__verify_client (Slapi_Entry* e, void *callback_data) +{ + + Slapi_Attr *attr; + char *val; + struct userdnattr_info *info; + Slapi_Value *sval; + const struct berval *attrVal; + int i; + + info = (struct userdnattr_info *) callback_data; + + slapi_entry_attr_find( e, info->attr, &attr); + if (attr == NULL) return 0; + + i = slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + val = slapi_dn_normalize ( + slapi_ch_strdup(attrVal->bv_val)); + + if (slapi_utf8casecmp((ACLUCHP)val, (ACLUCHP)info->clientdn ) == 0) { + info->result = 1; + slapi_ch_free ( (void **) &val); + return 0; + } + slapi_ch_free ( (void **) &val); + i = slapi_attr_next_value ( attr, i, &sval ); + } + return 0; +} +/* + * + * acllas__get_members + * + * Collects all the values of the specified attribute which should be group names. + */ +static int +acllas__get_members (Slapi_Entry* e, void *callback_data) +{ + + Slapi_Attr *attr; + struct groupdnattr_info *info; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + info = (struct groupdnattr_info *) callback_data; + slapi_entry_attr_find (e, info->attrName, &attr); + if ( !attr ) return 0; + + slapi_attr_get_numvalues ( attr, &info->numofGroups ); + + info->member = (char **) slapi_ch_malloc (info->numofGroups * sizeof(char *)); + i = slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal =slapi_value_get_berval ( sval ); + info->member[i] = slapi_dn_normalize ( slapi_ch_strdup(attrVal->bv_val)); + i = slapi_attr_next_value ( attr, i, &sval ); + } + return 0; +} + +/* + * DS_LASUserAttrEval + * LAS to evaluate the userattr rule + * + * userAttr = "attrName#Type" + * + * <Type> ::= "USERDN" | "GROUPDN" | "ROLEDN" | "LDAPURL" | <value> + * <value>::== <any printable String> + * + * Example: + * userAttr = "manager#USERDN" --- same as userdnattr + * userAttr = "owner#GROUPDN" --- same as groupdnattr + * = "ldap:///o=sun.com?owner#GROUPDN + * userAttr = "attr#ROLEDN" --- The value of attr contains a roledn + * userAttr = "myattr#LDAPURL" --- The value contains a LDAP URL + * which can have scope and filter + * bits. + * userAttr = "OU#Directory Server" + * --- In this case the client's OU and the + * resource entry's OU must have + * "Directory Server" value. + * + * Returns: + * retcode The usual LAS return codes. + */ +int +DS_LASUserAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *attrName; + char *attrValue = NULL; + int rc; + int matched = ACL_FALSE; + char *p; + int URLAttrRule = 0; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_USERATTR, "DS_LASUserAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* Which rule are we evaluating ? */ + attrName = slapi_ch_strdup (attr_pattern ); + if ( NULL == (p = strchr ( attrName, '#' ))) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserAttrEval:Invalid value(%s)\n", attr_pattern); + slapi_ch_free ( (void **) &attrName ); + return LAS_EVAL_FAIL; + } + attrValue = p; + attrValue++; /* skip the # */ + *p = '\0'; /* null terminate the attr name */ + + if ( 0 == strncasecmp ( attrValue, "USERDN", 6)) { + matched = DS_LASUserDnAttrEval (errp,DS_LAS_USERDNATTR, comparator, + attrName, cachable, LAS_cookie, + subject, resource, auth_info, global_auth); + goto done_las; + } else if ( 0 == strncasecmp ( attrValue, "GROUPDN", 7)) { + matched = DS_LASGroupDnAttrEval (errp,DS_LAS_GROUPDNATTR, comparator, + attrName, cachable, LAS_cookie, + subject, resource, auth_info, global_auth); + goto done_las; + } else if ( 0 == strncasecmp ( attrValue, "LDAPURL", 7) ) { + URLAttrRule = 1; + } else if ( 0 == strncasecmp ( attrValue, "ROLEDN", 6)) { + matched = DS_LASRoleDnAttrEval (errp,DS_LAS_ROLEDN, comparator, + attrName, cachable, LAS_cookie, + subject, resource, auth_info, global_auth); + goto done_las; + } + + if ( lasinfo.aclpb && ( NULL == lasinfo.aclpb->aclpb_client_entry )) { + /* SD 00/16/03 pass NULL in case the req is chained */ + char **attrs=NULL; + + + /* Use new search internal API */ + Slapi_PBlock *aPb = slapi_pblock_new (); + /* + * This search may be chained if chaining for ACL is + * is enabled in the backend and the entry is in + * a chained backend. + */ + slapi_search_internal_set_pb ( aPb, + lasinfo.clientDn, + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + 0 /* actions */); + slapi_search_internal_callback_pb(aPb, + lasinfo.aclpb /* callback_data */, + NULL/* result_callback */, + acllas__handle_client_search, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + + } + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserAttrEval: AttrName:%s, attrVal:%s\n", attrName, attrValue ); + + if ( URLAttrRule ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + Slapi_Attr *attrs; + int i; + + /* Get the attr from the resouce entry */ + if ( 0 == slapi_entry_attr_find (lasinfo.resourceEntry, attrName, &attrs) ) { + i= slapi_attr_first_value ( attrs, &sval ); + if ( i==-1 ) { + matched = ACL_FALSE; /* Attr val not there so it's value cannot equal other one */ + goto done_acl; + } + } else { + matched = ACL_FALSE; /* Not there so it cannot equal another one */ + goto done_acl; + } + + while( matched != ACL_TRUE && (sval != NULL)) { + attrVal = slapi_value_get_berval ( sval ); + matched = acllas__client_match_URL ( lasinfo.aclpb, + lasinfo.clientDn, + attrVal->bv_val); + if ( matched != ACL_TRUE ) + i = slapi_attr_next_value ( attrs, i, &sval ); + if ( matched == ACL_DONT_KNOW ) { + got_undefined = 1; + } + }/* while */ + } else { + /* + * Here it's the userAttr = "OU#Directory Server" case. + * Allocate the Slapi_Value on the stack and init it by reference + * to avoid having to malloc and free memory. + */ + Slapi_Value v; + + slapi_value_init_string_passin(&v, attrValue); + rc = slapi_entry_attr_has_syntax_value ( lasinfo.resourceEntry, attrName, + &v ); + if (rc) { + rc = slapi_entry_attr_has_syntax_value ( + lasinfo.aclpb->aclpb_client_entry, + attrName, &v ); + if (rc) matched = ACL_TRUE; + } + /* Nothing to free--cool */ + } + + /* + * Find out what the result is, in + * this case matched is one of ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW + * and got_undefined says whether a logical term evaluated to ACL_DONT_KNOW. + * + */ +done_acl: + if ( matched == ACL_TRUE || !got_undefined) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + } + + slapi_ch_free ( (void **) &attrName ); + return rc; + +done_las: + /* + * In this case matched is already LAS_EVAL_TRUE or LAS_EVAL_FALSE or + * LAS_EVAL_FAIL. + */ + if ( matched != LAS_EVAL_FAIL ) { + if (comparator == CMP_OP_EQ) { + rc = matched; + } else { + rc = (matched == LAS_EVAL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } + + slapi_ch_free ( (void **) &attrName ); + return rc; +} + +/* + * acllas__client_match_URL + * Match a client to a URL. + * + * Returns: + * ACL_TRUE - matched the URL + * ACL_FALSE - Sorry; no match + * + */ +static int +acllas__client_match_URL (struct acl_pblock *aclpb, char *n_clientdn, char *url ) +{ + + LDAPURLDesc *ludp; + int rc; + Slapi_Filter *f = NULL; + + + /* Get the client's entry if we don't have already */ + if ( aclpb && ( NULL == aclpb->aclpb_client_entry )) { + /* SD 00/16/03 Get every attr in case req chained */ + char **attrs=NULL; + + /* Use new search internal API */ + Slapi_PBlock * aPb = slapi_pblock_new (); + /* + * This search may be chained if chaining for ACL is + * is enabled in the backend and the entry is in + * a chained backend. + */ + slapi_search_internal_set_pb ( aPb, + n_clientdn, + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + 0 /* actions */); + slapi_search_internal_callback_pb(aPb, + aclpb /* callback_data */, + NULL/* result_callback */, + acllas__handle_client_search, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + } + + if ( NULL == aclpb->aclpb_client_entry ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "DS_LASUserAttrEval: Unable to get client's entry\n",0,0,0); + return ACL_FALSE; + } + + if (( rc = ldap_url_parse( url, &ludp)) != 0 ) { + return ACL_FALSE; + + } + if ( ( NULL == ludp->lud_dn) || ( NULL == ludp->lud_filter) ) { + ldap_free_urldesc( ludp ); + return ACL_FALSE; + } + + /* Normalize in place the dn */ + slapi_dn_normalize ( ludp->lud_dn ); + + /* Check the scope */ + if ( ludp->lud_scope == LDAP_SCOPE_SUBTREE ) { + if (!slapi_dn_issuffix(n_clientdn, ludp->lud_dn)) { + ldap_free_urldesc( ludp ); + return ACL_FALSE; + } + } else if ( ludp->lud_scope == LDAP_SCOPE_ONELEVEL ) { + char *parent = slapi_dn_parent (n_clientdn); + + if (slapi_utf8casecmp ((ACLUCHP)parent, (ACLUCHP)ludp->lud_dn) != 0 ) { + slapi_ch_free ( (void **) &parent); + ldap_free_urldesc( ludp ); + return ACL_FALSE; + } + slapi_ch_free ( (void **) &parent); + } else { /* default */ + if (slapi_utf8casecmp ( (ACLUCHP)n_clientdn, (ACLUCHP)ludp->lud_dn) != 0 ) { + ldap_free_urldesc( ludp ); + return ACL_FALSE; + } + + } + + + /* Convert the filter string */ + f = slapi_str2filter ( ludp->lud_filter ); + + rc = ACL_TRUE; + if (0 != slapi_vattr_filter_test ( aclpb->aclpb_pblock, + aclpb->aclpb_client_entry, f, 0 /* no acces chk */ )) + rc = ACL_FALSE; + + ldap_free_urldesc( ludp ); + slapi_filter_free ( f, 1 ) ; + + return rc; +} +static int +acllas__handle_client_search ( Slapi_Entry *e, void *callback_data ) +{ + struct acl_pblock *aclpb = (struct acl_pblock *) callback_data; + + /* If we are here means we have found the entry */ + if ( NULL == aclpb-> aclpb_client_entry) + aclpb->aclpb_client_entry = slapi_entry_dup ( e ); + return 0; +} +/* +* +* Do all the necessary setup for all the +* LASes. +* It will only fail if it's passed garbage (which should not happen) or +* if the data it needs to stock the lasinfo is not available, which +* also should not happen. +* +* +* Return value: 0 or one of these +* #define LAS_EVAL_TRUE -1 +* #define LAS_EVAL_FALSE -2 +* #define LAS_EVAL_DECLINE -3 +* #define LAS_EVAL_FAIL -4 +* #define LAS_EVAL_INVALID -5 +*/ + +static int +__acllas_setup ( NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth, char *lasType, char*lasName, lasInfo *linfo) +{ + + int rc; + memset ( linfo, 0, sizeof ( lasInfo) ); + + *cachable = 0; + *LAS_cookie = (void *)0; + + if (strcmp(attr_name, lasType) != 0) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Invalid LAS(%s)\n", lasName, attr_name); + return LAS_EVAL_INVALID; + } + + if ((comparator != CMP_OP_EQ) && (comparator != CMP_OP_NE)) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Invalid comparator(%d)\n", lasName, (int)comparator); + return LAS_EVAL_INVALID; + } + + + /* Get the client DN */ + rc = ACL_GetAttribute(errp, DS_ATTR_USERDN, (void **)&linfo->clientDn, + subject, resource, auth_info, global_auth); + + if ( rc != LAS_EVAL_TRUE ) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Unable to get the clientdn attribute(%d)\n",lasName, rc); + return LAS_EVAL_FAIL; + } + + /* Check if we have a user or not */ + if (linfo->clientDn) { + /* See if it's a anonymous user */ + if (*(linfo->clientDn) == '\0') + linfo->anomUser = ACL_TRUE; + } else { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s: No user\n",lasName); + return LAS_EVAL_FAIL; + } + + if ((rc = PListFindValue(subject, DS_ATTR_ENTRY, + (void **)&linfo->resourceEntry, NULL)) < 0) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Unable to get the Slapi_Entry attr(%d)\n",lasName, rc); + return LAS_EVAL_FAIL; + } + + /* Get ACLPB */ + rc = ACL_GetAttribute(errp, DS_PROP_ACLPB, (void **)&linfo->aclpb, + subject, resource, auth_info, global_auth); + if ( rc != LAS_EVAL_TRUE ) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Unable to get the ACLPB(%d)\n", lasName, rc); + return LAS_EVAL_FAIL; + } + if (NULL == attr_pattern ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:No rule value in the ACL\n", lasName); + + return LAS_EVAL_FAIL; + } + /* get the authentication type */ + if ((rc = PListFindValue(subject, DS_ATTR_AUTHTYPE, + (void **)&linfo->authType, NULL)) < 0) { + acl_print_acllib_err(errp, NULL); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "%s:Unable to get the auth type(%d)\n", rc); + return LAS_EVAL_FAIL; + } + return 0; +} + +/* + * See if clientDN has role roleDN. + * Here we know the user is not anon and that the role + * is not the anyone role ie. it's actually worth invoking the roles code. +*/ + +static int acllas__user_has_role( struct acl_pblock *aclpb, + Slapi_DN *roleDN, Slapi_DN *clientDn) { + + int present = 0; + int rc = 0; + + /* Get the client's entry if we don't have already */ + if ( aclpb && ( NULL == aclpb->aclpb_client_entry )) { + /* SD 00/16/03 Get every attr in case req chained */ + char **attrs=NULL; + + /* Use new search internal API */ + Slapi_PBlock * aPb = slapi_pblock_new (); + /* + * This search may NOT be chained--the user and the role definition + * must be co-located (chaining is not supported for the roles + * plugin in 5.0 + */ + slapi_search_internal_set_pb ( aPb, + slapi_sdn_get_ndn(clientDn), + LDAP_SCOPE_BASE, + "objectclass=*", + attrs, + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + SLAPI_OP_FLAG_NEVER_CHAIN /* actions */); + slapi_search_internal_callback_pb(aPb, + aclpb /* callback_data */, + NULL/* result_callback */, + acllas__handle_client_search, + NULL /* referral_callback */); + slapi_pblock_destroy (aPb); + } + + if ( NULL == aclpb->aclpb_client_entry ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "acllas__user_has_role: Unable to get client's entry\n",0,0,0); + return ACL_FALSE; + } + + /* If the client has the role then it's a match, otherwise no */ + + rc = slapi_role_check( aclpb->aclpb_client_entry, roleDN, &present); + if ( present ) { + return(ACL_TRUE); + } + + return(ACL_FALSE); +} + +int +DS_LASRoleDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *s_attrName = NULL; + char *attrName; + int matched; + int rc; + Slapi_Attr *attr; + int numOflevels = 0; + char *n_currEntryDn = NULL; + lasInfo lasinfo; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int k=0; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_ROLEDN, "DS_LASRoleDnAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* For anonymous client, they have no roles so the match is false. */ + if ( lasinfo.anomUser ) + return LAS_EVAL_FALSE; + + /* + ** + ** The function of this LAS is to find out if the client has + ** the role specified in the attr. + ** attr_pattern looks like: "ROLEDN cn=role1,o=sun.com" + */ + attrName = attr_pattern; + + matched = ACL_FALSE; + slapi_entry_attr_find( lasinfo.resourceEntry, attrName, &attr); + if (attr == NULL) { + /* + * Here the entry does not contain the attribute so the user + * cannot have this "null" role + */ + return LAS_EVAL_FALSE; + } + + if (lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) { + /* + * Here the entry does not contain the attribute so the user + * cannot have this "null" role or + * For the add operation, the resource itself + * must never be allowed to grant access-- + * This is because access would be granted based on a value + * of an attribute in the new entry--security hole. + * XXX is this therefore FALSE or DONT_KNOW ? + * + * + */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "ACL info: userattr=XXX#ROLEDN does not allow ADD permission.\n"); + got_undefined = 1; + } else { + + /* + * Got the first value. + * Test all the values of this attribute--if the client has _any_ + * of the roles then it's a match. + */ + k = slapi_attr_first_value ( attr,&sval ); + while ( k != -1 ) { + char *n_attrval; + Slapi_DN *roleDN; + + attrVal = slapi_value_get_berval ( sval ); + n_attrval = slapi_ch_strdup( attrVal->bv_val); + n_attrval = slapi_dn_normalize (n_attrval); + roleDN = slapi_sdn_new_dn_byval(n_attrval); + + /* We support: The attribute value can be a USER or a GROUP. + ** Let's compare with the client, thi might be just an user. If it is not + ** then we test it against the list of groups. + */ + if ((matched = acllas__user_has_role( + lasinfo.aclpb, + roleDN, + lasinfo.aclpb->aclpb_authorization_sdn)) == ACL_TRUE){ + slapi_ch_free ( (void **)&n_attrval ); + slapi_sdn_free(&roleDN ); + break; + } + slapi_ch_free ( (void **)&n_attrval ); + slapi_sdn_free(&roleDN ); + if (matched == ACL_TRUE) { + break; + } else if ( matched == ACL_DONT_KNOW ) { + /* record this but keep going--maybe another group will evaluate to TRUE */ + got_undefined = 1; + } + k= slapi_attr_next_value ( attr, k, &sval ); + }/* while */ + } + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + } + return (rc); +} + +/* + * Here, determine if lasinfo->clientDn matches user (which contains + * a ($dn) or a $attr component or both.) As defined in the aci + * lasinfo->aclpb->aclpb_curr_aci, + * which is the current aci being evaluated. + * + * returns: ACL_TRUE for matched, + * ACL_FALSE for matched. + * ACL_DONT_KNOW otherwise. + * + * +*/ + +int +aclutil_evaluate_macro( char * rule, lasInfo *lasinfo, + acl_eval_types evalType ) { + + int matched = 0; + aci_t *aci; + char *matched_val = NULL; + char **candidate_list = NULL; + char **inner_list = NULL; + char **sptr = NULL; + char **tptr = NULL; + char *t = NULL; + char *s = NULL; + char *target_dn = NULL; + struct acl_pblock *aclpb = lasinfo->aclpb; + int found_matched_val_in_ht = 0; + + aci = lasinfo->aclpb->aclpb_curr_aci; + /* Get a pointer to the ndn in the resouirce */ + target_dn = slapi_entry_get_ndn ( lasinfo->resourceEntry ); + + /* + * First, get the matched value from the target resource. + * We have alredy done this matching once beofer at tasrget match time. + */ + + LDAPDebug ( LDAP_DEBUG_ACL, "aclutil_evaluate_macro for aci '%s'" + "index '%d'\n", + aci->aclName, aci->aci_index,0); + + if ( aci->aci_macro == NULL ) { + /* No $dn in the target, it's a $attr type subject rule */ + matched_val = NULL; + } else { + + /* + * Look up the matched_val value calculated + * from the target and stored judiciously there for us. + */ + + if ( (matched_val = (char *)acl_ht_lookup( aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index)) == NULL) { + LDAPDebug( LDAP_DEBUG_ACL, + "ACL info: failed to locate the calculated target" + "macro for aci '%s' index '%d'\n", + aci->aclName, aci->aci_index,0); + return(ACL_FALSE); /* Not a match */ + } else { + LDAPDebug( LDAP_DEBUG_ACL, + "ACL info: found matched_val (%s) for aci index %d" + "in macro ht\n", + aci->aclName, aci->aci_index,0); + + found_matched_val_in_ht = 1; + } + } + + /* + * Now, make a candidate + * list of strings to match against the client. + * This involves replacing ($dn) or [$dn] by either the matched + * value, or all the suffix substrings of matched_val. + * If there is no $dn then the candidate list is just + * user itself. + * + */ + + candidate_list = acllas_replace_dn_macro( rule, matched_val, lasinfo); + + sptr= candidate_list; + while( *sptr != NULL && !matched) { + + s = *sptr; + + /* + * Now s may contain some $attr macros. + * So, make a candidate list, got by replacing each occurence + * of $attr with all the values that attribute has in + * the resource entry. + */ + + inner_list = acllas_replace_attr_macro( s, lasinfo); + + tptr = inner_list; + while( *tptr != NULL && (matched != ACL_TRUE) ){ + + t = *tptr; + + /* + * Now, at last t is a candidate string we can + * match agains the client. + * + * $dn and $attr can appear in userdn, graoupdn and roledn + * rules, so we we need to decide which type we + * currently evaluating and evaluate that. + * + * If the string generated was undefined, eg it contained + * ($attr.ou) and the entry did not have an ou attribute,then + * the empty string is returned for this. So it we find + * an empty string in the list, skip it--it does not match. + */ + + if ( *t != '\0') { + if ( evalType == ACL_EVAL_USER ) { + + matched = acllas_eval_one_user( lasinfo->aclpb, + lasinfo->clientDn, t); + } else if (evalType == ACL_EVAL_GROUP) { + + matched = acllas_eval_one_group(t, lasinfo); + } else if (evalType == ACL_EVAL_ROLE) { + matched = acllas_eval_one_role(t, lasinfo); + } else if (evalType == ACL_EVAL_GROUPDNATTR) { + matched = acllas__eval_memberGroupDnAttr(t, + lasinfo->resourceEntry, + lasinfo->clientDn, + lasinfo->aclpb); + } else if ( evalType == ACL_EVAL_TARGET_FILTER) { + + matched = acllas_eval_one_target_filter(t, + lasinfo->resourceEntry); + + } + } + + tptr++; + + }/*inner while*/ + charray_free(inner_list); + + sptr++; + }/* outer while */ + + charray_free(candidate_list); + + return(matched); + +} + +/* + * Here, replace the first occurrence of $(dn) with matched_val. + * replace any occurrence of $[dn] with each of the suffix substrings + * of matched_val. + * Each of these strings is returned in a NULL terminated list of strings. + * + * If there is no $dn thing then the returned list just contains rule itself. + * + * eg. rule: cn=fred,ou=*, ($dn), o=sun.com + * matched_val: ou=People,o=icnc + * + * Then we return the list + * cn=fred,ou=*,ou=People,o=icnc,o=sun.com NULL + * + * eg. rule: cn=fred,ou=*,[$dn], o=sun.com + * matched_val: ou=People,o=icnc + * + * Then we return the list + * cn=fred,ou=*,ou=People,o=icnc,o=sun.com + * cn=fred,ou=*,o=icnc,o=sun.com + * NULL + * + * +*/ + +static char ** +acllas_replace_dn_macro( char *rule, char *matched_val, lasInfo *lasinfo) { + + char **a = NULL; + char *str = NULL; + char *patched_rule = NULL; + char *rule_to_use = NULL; + char *new_patched_rule = NULL; + char *rule_prefix = NULL; + char *rule_suffix = NULL; + int rule_suffix_len = 0; + char *comp = NULL; + int matched_val_len = 0; + int macro_len = 0; + int j = 0; + int has_macro_dn = 0; + int has_macro_levels = 0; + + /* Determine what the rule's got once */ + if ( strstr(rule, ACL_RULE_MACRO_DN_KEY) != NULL) { + has_macro_dn = 1; + } + + if ( strstr(rule, ACL_RULE_MACRO_DN_LEVELS_KEY) != NULL) { + has_macro_levels = 1; + } + + if ( !has_macro_dn && !has_macro_levels ) { + + /* + * No $dn thing, just return a list with two elements, rule and NULL. + * charray_add will create the list and null terminate it. + */ + + charray_add( &a, slapi_ch_strdup(rule)); + return(a); + } else { + + /* + * Have an occurrence of the macro rules + * + * First, replace all occurrencers of ($dn) with the matched_val + */ + + if ( has_macro_dn) { + patched_rule = + acl_replace_str(rule, ACL_RULE_MACRO_DN_KEY, matched_val); + } + + /* If there are no [$dn] we're done */ + + if ( !has_macro_levels ) { + charray_add( &a, patched_rule); + return(a); + } else { + + /* + * It's a [$dn] type, so walk matched_val, splicing in all + * the suffix substrings and adding each such string to + * to the returned list. + * get_next_component() does not return the commas--the + * prefix and suffix should come with their commas. + * + * All occurrences of each [$dn] are replaced with each level. + * + * If has_macro_dn then patched_rule is the rule to strart with, + * and this needs to be freed at the end, otherwise + * just use rule. + */ + + if (patched_rule) { + rule_to_use = patched_rule; + } else { + rule_to_use = rule; + } + + matched_val_len = strlen(matched_val); + j = 0; + + while( j < matched_val_len) { + + new_patched_rule = + acl_replace_str(rule_to_use, ACL_RULE_MACRO_DN_LEVELS_KEY, + &matched_val[j]); + charray_add( &a, new_patched_rule); + + j += acl_find_comp_end(&matched_val[j]); + } + + if (patched_rule) { + slapi_ch_free((void**)&patched_rule); + } + + return(a); + } + } +} + +/* + * Here, replace any occurrence of $attr.attrname with the + * value of attrname from lasinfo->resourceEntry. + * + * + * If there is no $attr thing then the returned list just contains rule + * itself. + * + * eg. rule: cn=fred,ou=*,ou=$attr.ou,o=sun.com + * ou: People + * ou: icnc + * + * Then we return the list + * cn=fred,ou=*,ou=People,o=sun.com + * cn=fred,ou=*,ou=icnc,o=sun.com + * +*/ + +static char ** +acllas_replace_attr_macro( char *rule, lasInfo *lasinfo) { + + char **a = NULL; + char **working_list = NULL; + Slapi_Entry *e = lasinfo->resourceEntry; + char *str, *working_rule; + char *macro_str, *macro_attr_name; + int l; + Slapi_Attr *attr = NULL; + + str = strstr(rule, ACL_RULE_MACRO_ATTR_KEY); + if ( str == NULL ) { + + charray_add(&a, slapi_ch_strdup(rule)); + return(a); + + } else { + + working_rule = slapi_ch_strdup(rule); + str = strstr(working_rule, ACL_RULE_MACRO_ATTR_KEY); + charray_add(&working_list, working_rule ); + + while( str != NULL) { + + /* + * working_rule is the first member of working_list. + * str points to the next $attr.attrName in working_rule. + * each member of working_list needs to have each occurence of + * $attr.atrName replaced with the value of attrName in e. + * If attrName is multi valued then this generates another + * list which replaces the old one. + */ + + l = acl_strstr(&str[0], ")"); + macro_str = slapi_ch_malloc(l+2); + strncpy( macro_str, &str[0], l+1); + macro_str[l+1] = '\0'; + + str = strstr(macro_str, "."); + str++; /* skip the . */ + l = acl_strstr(&str[0], ")"); + macro_attr_name = slapi_ch_malloc(l+1); + strncpy( macro_attr_name, &str[0], l); + macro_attr_name[l] = '\0'; + + slapi_entry_attr_find ( e, macro_attr_name, &attr ); + if ( NULL == attr ) { + + /* + * Here, if a $attr.attrName is such that the attrName + * does not occur in the entry then return a ""-- + * this will go back to the matching code in + * aclutil_evaluate_macro() where "" will + * be taken as the candidate. + */ + + slapi_ch_free((void **)¯o_str); + slapi_ch_free((void **)¯o_attr_name); + + charray_free(working_list); + charray_add(&a, slapi_ch_strdup("")); + return(a); + + } else{ + + const struct berval *attrValue; + Slapi_Value *sval; + int i, j; + char *patched_rule; + + a = NULL; + i= slapi_attr_first_value ( attr, &sval ); + while(i != -1) { + attrValue = slapi_value_get_berval(sval); + + j = 0; + while( working_list[j] != NULL) { + + patched_rule = + acl_replace_str(working_list[j], + macro_str, attrValue->bv_val); + charray_add(&a, patched_rule); + j++; + } + + i= slapi_attr_next_value( attr, i, &sval ); + }/* while */ + + /* + * Here, a is working_list, where each member has had + * macro_str replaced with attrVal. + */ + + charray_free(working_list); + working_list = a; + working_rule = a[0]; + } + slapi_ch_free((void **)¯o_str); + slapi_ch_free((void **)¯o_attr_name); + + str = strstr(working_rule, ACL_RULE_MACRO_ATTR_KEY); + + }/* while */ + + return(working_list); + } + + +} + +/* + * returns ACL_TRUE, ACL_FALSE or ACL_DONT_KNOW. + * + * user is a string from the userdn keyword which may contain + * * components. This routine does the compare component by component, so + * that * behaves differently to "normal". + * Any ($dn) or $attr must have been removed from user before this is called. +*/ +static int +acllas_eval_one_user( struct acl_pblock *aclpb, char * clientDN, char *rule) { + + int exact_match = 0; + int ret_code = 0; + const size_t LDAP_URL_prefix_len = strlen(LDAP_URL_prefix); + char *s = NULL; + + + + /* URL format */ + if ((s = strchr (rule, '?'))!= NULL) { + /* URL format */ + if (acllas__client_match_URL ( aclpb, clientDN, + rule) == ACL_TRUE) { + exact_match = 1; + } + } else if ( strstr(rule, "=*") == NULL ) { + /* Just a straight compare */ + /* skip the ldap:/// part */ + rule += LDAP_URL_prefix_len; + exact_match = !slapi_utf8casecmp((ACLUCHP)clientDN, + (ACLUCHP)rule); + } else{ + /* Here, contains a =*, so need to match comp by comp */ + /* skip the ldap:/// part */ + rule += LDAP_URL_prefix_len; + ret_code = acl_match_prefix( rule, clientDN, &exact_match); + } + if ( exact_match) { + return( ACL_TRUE); + } else { + return(ACL_FALSE); + } +} + +/* + * returns ACL_TRUE, ACL_FALSE and ACL_DONT_KNOW. + * + * The user string has had all ($dn) and $attr replaced + * so the only dodgy thing left is a *. + * + * If * appears in such a user string, then it matches only that + * component, not .*, like it would otherwise. + * +*/ +static int +acllas_eval_one_group(char *groupbuf, lasInfo *lasinfo) { + + if (groupbuf) { + return( acllas__user_ismember_of_group ( + lasinfo->aclpb, + groupbuf, + lasinfo->clientDn, + ACLLAS_CACHE_ALL_GROUPS, + lasinfo->aclpb->aclpb_clientcert + )); + } else { + return(ACL_FALSE); /* not in the empty group */ + } +} + +/* + * returns ACL_TRUE for match, ACL_FALSE for not a match, ACL_DONT_KNOW otherwise. +*/ +static int +acllas_eval_one_role(char *role, lasInfo *lasinfo) { + + Slapi_DN *roleDN = NULL; + int rc = ACL_FALSE; + char ebuf [ BUFSIZ ]; + + /* + * See if lasinfo.clientDn has role rolebuf. + * Here we know it's not an anom user nor + * a an anyone user--the client dn must be matched against + * a real role. + */ + + roleDN = slapi_sdn_new_dn_byval(role); + if (role) { + rc = acllas__user_has_role( + lasinfo->aclpb, + roleDN, + lasinfo->aclpb->aclpb_authorization_sdn); + } else { /* The user does not have the empty role */ + rc = ACL_FALSE; + } + slapi_sdn_free(&roleDN ); + + /* Some useful logging */ + if (rc == ACL_TRUE ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "role evaluation: user '%s' does have role '%s'\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo->clientDn, ebuf), + role); + } else { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "role evaluation: user '%s' does NOT have role '%s'\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo->clientDn, ebuf), + role); + } + return(rc); +} + +/* + * returns ACL_TRUE if e matches the filter str, ACL_FALSE if not, + * ACL_DONT_KNOW otherwise. +*/ +static int acllas_eval_one_target_filter( char * str, Slapi_Entry *e) { + + int rc = ACL_FALSE; + Slapi_Filter *f = NULL; + + if ((f = slapi_str2filter(str)) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "Warning: Bad targetfilter(%s) in aci: does not match\n", str); + return(ACL_DONT_KNOW); + } + + if (slapi_vattr_filter_test(NULL, e, f, 0 /*don't do acess chk*/)!= 0) { + rc = ACL_FALSE; /* Filter does not match */ + } else { + rc = ACL_TRUE; /* filter does match */ + } + slapi_filter_free(f, 1); + + return(rc); + +} + + + + + +/***************************************************************************/ +/* E N D */ +/***************************************************************************/ diff --git a/ldap/servers/plugins/acl/acllist.c b/ldap/servers/plugins/acl/acllist.c new file mode 100644 index 00000000..0147626d --- /dev/null +++ b/ldap/servers/plugins/acl/acllist.c @@ -0,0 +1,940 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/************************************************************************ + * + * ACLLIST + * + * All the ACLs are read when the server is started. The ACLs are + * parsed and kept in an AVL tree. All the ACL List management are + * in this file. + * + * The locking on the aci cache is implemented using the acllist_acicache*() + * routines--a read/write lock. + * + * The granularity of the view of the cache is the entry level--ie. + * when an entry is being modified (mod,add,delete,modrdn) and the mod + * involves the aci attribute, then other operations will see the acl cache + * before the whole change or after the whole change, but not during the change. + * cf. acl.c:acl_modified() + * + * The only tricky issue is that there is also locking + * implemented for the anonymous profile and sometimes we need to take both + * locks cf. aclanom_anom_genProfile(). The rule is + * always take the acicache lock first, followed by the anon lock--following + * this rule will ensure no dead lock scenarios can arise. + * + * Some routines are called in different places with different lock + * contexts--for these routines acl_lock_flag_t is used to + * pass the context. + * + */ +#include "acl.h" + +static PRRWLock *aci_rwlock = NULL; +#define ACILIST_LOCK_READ() PR_RWLock_Rlock (aci_rwlock ) +#define ACILIST_UNLOCK_READ() PR_RWLock_Unlock (aci_rwlock ) +#define ACILIST_LOCK_WRITE() PR_RWLock_Wlock (aci_rwlock ) +#define ACILIST_UNLOCK_WRITE() PR_RWLock_Unlock (aci_rwlock ) + + +/* Root of the TREE */ +static Avlnode *acllistRoot = NULL; + +#define CONTAINER_INCR 2000 + +/* The container array */ +static AciContainer **aciContainerArray; +static PRUint32 currContainerIndex =0; +static PRUint32 maxContainerIndex = 0; +static int curAciIndex = 1; + +/* PROTOTYPES */ +static int __acllist_add_aci ( aci_t *aci ); +static int __acllist_aciContainer_node_cmp ( caddr_t d1, caddr_t d2 ); +static int __acllist_aciContainer_node_dup ( caddr_t d1, caddr_t d2 ); +static void __acllist_free_aciContainer ( AciContainer **container); +static void free_targetattrfilters( Targetattrfilter ***input_attrFilterArray); + +void my_print( Avlnode *root ); + + +int +acllist_init () +{ + + if (( aci_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"ACLLIST LOCK") ) == NULL ) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "acllist_init:failed in getting the rwlock\n" ); + return 1; + } + + aciContainerArray = (AciContainer **) slapi_ch_calloc ( 1, + CONTAINER_INCR * sizeof ( AciContainer * ) ); + maxContainerIndex = CONTAINER_INCR; + currContainerIndex = 0; + + return 0; +} + +/* + * This is the callback for backend state changes. + * It needs to add/remove acis as backends come up and go down. + * + * The strategy is simple: + * When a backend moves to the SLAPI_BE_STATE_ON then we go get all the acis + * add them to the cache. + * When a backend moves out of the SLAPI_BE_STATE_ON then we remove them all. + * +*/ + +void acl_be_state_change_fnc ( void *handle, char *be_name, int old_state, + int new_state) { + Slapi_Backend *be=NULL; + const Slapi_DN *sdn; + + + if ( old_state == SLAPI_BE_STATE_ON && new_state != SLAPI_BE_STATE_ON) { + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Backend %s is no longer STARTED--deactivating it's acis\n", + be_name); + + if ( (be = slapi_be_select_by_instance_name( be_name )) == NULL) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Failed to retreive backend--NOT activating it's acis\n"); + return; + } + + /* + * Just get the first suffix--if there are multiple XXX ? + */ + + if ( (sdn = slapi_be_getsuffix( be, 0)) == NULL ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Failed to retreive backend--NOT activating it's acis\n"); + return; + } + + aclinit_search_and_update_aci ( 1, /* thisbeonly */ + sdn, /* base */ + be_name,/* be name */ + LDAP_SCOPE_SUBTREE, + ACL_REMOVE_ACIS, + DO_TAKE_ACLCACHE_WRITELOCK); + + } else if ( old_state != SLAPI_BE_STATE_ON && new_state == SLAPI_BE_STATE_ON) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Backend %s is now STARTED--activating it's acis\n", be_name); + + if ( (be = slapi_be_select_by_instance_name( be_name )) == NULL) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Failed to retreive backend--NOT activating it's acis\n"); + return; + } + + /* + * In fact there can onlt be one sufffix here. + */ + + if ( (sdn = slapi_be_getsuffix( be, 0)) == NULL ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Failed to retreive backend--NOT activating it's acis\n"); + return; + } + + aclinit_search_and_update_aci ( 1, /* thisbeonly */ + sdn, + be_name, /* be name */ + LDAP_SCOPE_SUBTREE, + ACL_ADD_ACIS, + DO_TAKE_ACLCACHE_WRITELOCK); + } + +} + +/* This routine must be called with the acicache write lock taken */ +int +acllist_insert_aci_needsLock( const Slapi_DN *e_sdn, const struct berval* aci_attr) +{ + + aci_t *aci; + char *acl_str; + int rv =0; + + if (aci_attr->bv_len <= 0) + return 0; + + aci = acllist_get_aci_new (); + slapi_sdn_set_ndn_byval ( aci->aci_sdn, slapi_sdn_get_ndn ( e_sdn ) ); + + acl_str = slapi_ch_strdup(aci_attr->bv_val); + /* Parse the ACL TEXT */ + if ( 0 != (rv = acl_parse ( acl_str, aci )) ) { + slapi_log_error (SLAPI_LOG_FATAL, plugin_name, + "ACL PARSE ERR(rv=%d): %s\n", rv, acl_str ); + slapi_ch_free ( (void **) &acl_str ); + acllist_free_aci ( aci ); + + return 1; + } + + /* Now add it to the list */ + if ( 0 != (rv =__acllist_add_aci ( aci ))) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "ACL ADD ACI ERR(rv=%d): %s\n", rv, acl_str ); + slapi_ch_free ( (void **) &acl_str ); + acllist_free_aci ( aci ); + return 1; + } + + slapi_ch_free ( (void **) &acl_str ); + acl_regen_aclsignature (); + if ( aci->aci_elevel == ACI_ELEVEL_USERDN_ANYONE) + aclanom_invalidateProfile (); + return 0; +} + +/* This routine must be called with the acicache write lock taken */ +static int +__acllist_add_aci ( aci_t *aci ) +{ + + int rv = 0; /* OK */ + AciContainer *aciListHead; + AciContainer *head; + PRUint32 i; + + aciListHead = acllist_get_aciContainer_new ( ); + slapi_sdn_set_ndn_byval ( aciListHead->acic_sdn, slapi_sdn_get_ndn ( aci->aci_sdn ) ); + + /* insert the aci */ + switch (avl_insert ( &acllistRoot, aciListHead, __acllist_aciContainer_node_cmp, + __acllist_aciContainer_node_dup ) ) { + + case 1: /* duplicate ACL on the same entry */ + + /* Find the node that contains the acl. */ + if ( NULL == (head = (AciContainer *) avl_find( acllistRoot, aciListHead, + (IFP) __acllist_aciContainer_node_cmp ) ) ) { + slapi_log_error ( SLAPI_PLUGIN_ACL, plugin_name, + "Can't insert the acl in the tree\n"); + rv = 1; + } else { + aci_t *t_aci;; + + /* Attach the list */ + t_aci = head->acic_list;; + while ( t_aci && t_aci->aci_next ) + t_aci = t_aci->aci_next; + + /* Now add the new one to the end of the list */ + t_aci->aci_next = aci; + } + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Added the ACL:%s to existing container:[%d]%s\n", + aci->aclName, head->acic_index, slapi_sdn_get_ndn( head->acic_sdn )); + + /* now free the tmp container */ + aciListHead->acic_list = NULL; + __acllist_free_aciContainer ( &aciListHead ); + + break; + default: + /* The container is inserted. Now hook up the aci and setup the + * container index. Donot free the "aciListHead" here. + */ + aciListHead->acic_list = aci; + + /* + * First, see if we have an open slot or not - -if we have reuse it + */ + i = 0; + while ( (i < currContainerIndex) && aciContainerArray[i] ) + i++; + + if ( currContainerIndex >= (maxContainerIndex - 2)) { + maxContainerIndex += CONTAINER_INCR; + aciContainerArray = (AciContainer **) slapi_ch_realloc ( (char *) aciContainerArray, + maxContainerIndex * sizeof ( AciContainer * ) ); + } + aciListHead->acic_index = i; + /* If i < currContainerIndex, we are just re-using an old slot. */ + /* We don't need to increase currContainerIndex if we just re-use an old one. */ + if (i == currContainerIndex) + currContainerIndex++; + + aciContainerArray[ aciListHead->acic_index ] = aciListHead; + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, "Added %s to container:%d\n", + slapi_sdn_get_ndn( aciListHead->acic_sdn ), aciListHead->acic_index ); + break; + } + + return rv; +} + + + +static int +__acllist_aciContainer_node_cmp ( caddr_t d1, caddr_t d2 ) +{ + + int rc =0; + AciContainer *c1 = (AciContainer *) d1; + AciContainer *c2 = (AciContainer *) d2; + + + rc = slapi_sdn_compare ( c1->acic_sdn, c2->acic_sdn ); + return rc; +} + +static int +__acllist_aciContainer_node_dup ( caddr_t d1, caddr_t d2 ) +{ + + /* we allow duplicates -- they are not exactly duplicates + ** but multiple aci value on the same node + */ + return 1; + +} + + +/* + * Remove the ACL + * + * This routine must be called with the aclcache write lock taken. + * It takes in addition the one for the anom profile taken in + * aclanom_invalidateProfile(). + * They _must_ be taken in this order or there + * is a deadlock scenario with aclanom_gen_anomProfile() which + * also takes them is this order. +*/ + +int +acllist_remove_aci_needsLock( const Slapi_DN *sdn, const struct berval *attr ) +{ + + aci_t *head, *next; + int rv = 0; + AciContainer *aciListHead, *root; + AciContainer *dContainer; + int removed_anom_acl = 0; + + /* we used to delete the ACL by value but we don't do that anymore. + * rather we delete all the acls in that entry and then repopulate it if + * there are any more acls. + */ + + aciListHead = acllist_get_aciContainer_new ( ); + slapi_sdn_set_ndn_byval ( aciListHead->acic_sdn, slapi_sdn_get_ndn ( sdn ) ); + + /* now find it */ + if ( NULL == (root = (AciContainer *) avl_find( acllistRoot, aciListHead, + (IFP) __acllist_aciContainer_node_cmp ))) { + /* In that case we don't have any acl for this entry. cool !!! */ + + __acllist_free_aciContainer ( &aciListHead ); + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "No acis to remove in this entry\n" ); + return 0; + } + + head = root->acic_list; + if ( head) + next = head->aci_next; + while ( head ) { + if ( head->aci_elevel == ACI_ELEVEL_USERDN_ANYONE) + removed_anom_acl = 1; + + /* Free the acl */ + acllist_free_aci ( head ); + + head = next; + next = NULL; + if ( head && head->aci_next ) + next = head->aci_next; + } + root->acic_list = NULL; + + /* remove the container from the slot */ + aciContainerArray[root->acic_index] = NULL; + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Removing container[%d]=%s\n", root->acic_index, + slapi_sdn_get_ndn ( root->acic_sdn) ); + dContainer = (AciContainer *) avl_delete ( &acllistRoot, aciListHead, + __acllist_aciContainer_node_cmp ); + __acllist_free_aciContainer ( &dContainer ); + + acl_regen_aclsignature (); + if ( removed_anom_acl ) + aclanom_invalidateProfile (); + + /* + * Now read back the entry and repopulate ACLs for that entry, but + * only if a specific aci was deleted, otherwise, we do a + * "When Harry met Sally" and nail 'em all. + */ + + if ( attr != NULL) { + + if (0 != (rv = aclinit_search_and_update_aci ( 0, /* thisbeonly */ + sdn, /* base */ + NULL, /* be name */ + LDAP_SCOPE_BASE, + ACL_ADD_ACIS, + DONT_TAKE_ACLCACHE_WRITELOCK))) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + " Can't add the rest of the acls for entry:%s after delete\n", + slapi_sdn_get_dn ( sdn ) ); + } + } + + /* Now free the tmp container we used */ + __acllist_free_aciContainer ( &aciListHead ); + + /* + * regenerate the anonymous profile if we have deleted + * anyone acls. + * We don't need the aclcache readlock because the context of + * this routine is we have the write lock already. + */ + if ( removed_anom_acl ) + aclanom_gen_anomProfile(DONT_TAKE_ACLCACHE_READLOCK); + + return rv; +} + +AciContainer * +acllist_get_aciContainer_new ( ) +{ + + AciContainer *head; + + head = (AciContainer * ) slapi_ch_calloc ( 1, sizeof ( AciContainer ) ); + head->acic_sdn = slapi_sdn_new ( ); + head->acic_index = -1; + + return head; +} +static void +__acllist_free_aciContainer ( AciContainer **container) +{ + + PR_ASSERT ( container != NULL ); + + if ( (*container)->acic_index >= 0 ) + aciContainerArray[ (*container)->acic_index] = NULL; + if ( (*container)->acic_sdn ) + slapi_sdn_free ( &(*container)->acic_sdn ); + slapi_ch_free ( (void **) container ); + +} + +void +acllist_done_aciContainer ( AciContainer *head ) +{ + + PR_ASSERT ( head != NULL ); + + slapi_sdn_done ( head->acic_sdn ); + head->acic_index = -1; + + /* The caller is responsible for taking care of list */ + head->acic_list = NULL; +} + + +aci_t * +acllist_get_aci_new () +{ + aci_t *aci_item; + + aci_item = (aci_t *) slapi_ch_calloc (1, sizeof (aci_t)); + aci_item->aci_sdn = slapi_sdn_new (); + aci_item->aci_index = curAciIndex++; + aci_item->aci_elevel = ACI_DEFAULT_ELEVEL; /* by default it's a complex */ + aci_item->targetAttr = (Targetattr **) slapi_ch_calloc ( + ACL_INIT_ATTR_ARRAY, + sizeof (Targetattr *)); + return aci_item; +} + +void +acllist_free_aci(aci_t *item) +{ + + Targetattr **attrArray; + + /* The caller is responsible for taking + ** care of list issue + */ + if (item == NULL) return; + + slapi_sdn_free ( &item->aci_sdn ); + slapi_filter_free (item->target, 1); + + /* slapi_filter_free(item->targetAttr, 1); */ + attrArray = item->targetAttr; + if (attrArray) { + int i = 0; + Targetattr *attr; + + while (attrArray[i] != NULL) { + attr = attrArray[i]; + if (attr->attr_type & ACL_ATTR_FILTER) { + slapi_filter_free(attr->u.attr_filter, 1); + } else { + slapi_ch_free ( (void **) &attr->u.attr_str ); + } + slapi_ch_free ( (void **) &attr ); + i++; + } + /* Now free the array */ + slapi_ch_free ( (void **) &attrArray ); + } + + /* Now free any targetattrfilters in this aci item */ + + if ( item->targetAttrAddFilters ) { + free_targetattrfilters(&item->targetAttrAddFilters); + } + + if ( item->targetAttrDelFilters ) { + free_targetattrfilters(&item->targetAttrDelFilters); + } + + if (item->targetFilterStr) slapi_ch_free ( (void **) &item->targetFilterStr ); + slapi_filter_free(item->targetFilter, 1); + + /* free the handle */ + if (item->aci_handle) ACL_ListDestroy(NULL, item->aci_handle); + + /* Free the name */ + if (item->aclName) slapi_ch_free((void **) &item->aclName); + + /* Free any macro info*/ + if (item->aci_macro) { + slapi_ch_free((void **) &item->aci_macro->match_this); + slapi_ch_free((void **) &item->aci_macro); + } + + /* free at last -- free at last */ + slapi_ch_free ( (void **) &item ); +} + +static void free_targetattrfilters( Targetattrfilter ***attrFilterArray) { + + if (*attrFilterArray) { + int i = 0; + Targetattrfilter *attrfilter; + + while ((*attrFilterArray)[i] != NULL) { + attrfilter = (*attrFilterArray)[i]; + + if ( attrfilter->attr_str != NULL) { + slapi_ch_free ( (void **) &attrfilter->attr_str ); + } + + if (attrfilter->filter != NULL) { + slapi_filter_free(attrfilter->filter, 1); + } + + if( attrfilter->filterStr != NULL) { + slapi_ch_free ( (void **) &attrfilter->filterStr ); + } + + slapi_ch_free ( (void **) &attrfilter ); + i++; + } + /* Now free the array */ + slapi_ch_free ( (void **) attrFilterArray ); + } + +} + +/* SEARCH */ +void +acllist_init_scan (Slapi_PBlock *pb, int scope, char *base) +{ + Acl_PBlock *aclpb; + int i; + AciContainer *root; + char *basedn = NULL; + int index; + + if ( acl_skip_access_check ( pb, NULL ) ) { + return; + } + + /*acllist_print_tree ( acllistRoot, &depth, "top", "top") ; */ + /* my_print ( acllistRoot );*/ + /* If we have an anonymous profile and I am an anom dude - let's skip it */ + if ( aclanom_is_client_anonymous ( pb )) { + return; + } + aclpb = acl_get_aclpb (pb, ACLPB_BINDDN_PBLOCK ); + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 4 \n" ); + return; + } + + aclpb->aclpb_handles_index[0] = -1; + + /* If base is NULL - it means we are going to go thru all the ACLs + * This is needed when we do anonymous profile generation. + */ + if ( NULL == base ) { + return; + } + + aclpb->aclpb_state |= ACLPB_SEARCH_BASED_ON_LIST ; + + acllist_acicache_READ_LOCK(); + + basedn = slapi_ch_strdup (base); + index = 0; + aclpb->aclpb_search_base = slapi_ch_strdup ( base ); + + while (basedn ) { + char *tmp = NULL; + + slapi_sdn_set_ndn_byref ( aclpb->aclpb_aclContainer->acic_sdn, basedn ); + + root = (AciContainer *) avl_find( acllistRoot, + (caddr_t) aclpb->aclpb_aclContainer, + (IFP) __acllist_aciContainer_node_cmp); + if ( index >= ACLPB_MAX_SELECTED_ACLS -2 ) { + aclpb->aclpb_handles_index[0] = -1; + slapi_ch_free ( (void **) &basedn); + break; + } else if ( NULL != root ) { + aclpb->aclpb_base_handles_index[index++] = root->acic_index; + aclpb->aclpb_base_handles_index[index] = -1; + } + tmp = slapi_dn_parent ( basedn ); + slapi_ch_free ( (void **) &basedn); + basedn = tmp; + } + + acllist_done_aciContainer ( aclpb->aclpb_aclContainer); + + if ( aclpb->aclpb_base_handles_index[0] == -1 ) + aclpb->aclpb_state &= ~ACLPB_SEARCH_BASED_ON_LIST ; + + acllist_acicache_READ_UNLOCK(); + + i = 0; + while ( i < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_base_handles_index[i] != -1 ) { + i++; + } +} + +/* + * Initialize aclpb_handles_index[] (sentinel -1) to + * contain a list of all aci items at and above edn in the DIT tree. + * This list will be subsequestly scanned to find applicable aci's for + * the given operation. +*/ + +void +acllist_aciscan_update_scan ( Acl_PBlock *aclpb, char *edn ) +{ + + int i, index = 0; + char *basedn = NULL; + AciContainer *root; + int is_not_search_base = 1; + + + /* First copy the containers indx from the base to the one which is + * going to be used. + * The base handles get done in acllist_init_scan(). + * This stuff is only used if it's a search operation. + */ + if ( aclpb && aclpb->aclpb_search_base ) { + while ( aclpb->aclpb_base_handles_index[index] != -1 && + index < ACLPB_MAX_SELECTED_ACLS -2 ) { + aclpb->aclpb_handles_index[index] = + aclpb->aclpb_base_handles_index[index]; + index++; + } + if ( strcasecmp ( edn, aclpb->aclpb_search_base) == 0) { + is_not_search_base = 0; + } + } + aclpb->aclpb_handles_index[index] = -1; + + /* + * Here, make a list of all the aci's that will apply + * to edn ie. all aci's at and above edn in the DIT tree. + * + * Do this by walking up edn, looking at corresponding + * points in the acllistRoot aci tree. + * + * If is_not_search_base is true, then we need to iterate on edn, otherwise + * we've already got all the base handles above. + * + */ + + if (is_not_search_base) { + + basedn = slapi_ch_strdup ( edn ); + + while (basedn ) { + char *tmp = NULL; + + slapi_sdn_set_ndn_byref ( aclpb->aclpb_aclContainer->acic_sdn, basedn ); + + root = (AciContainer *) avl_find( acllistRoot, + (caddr_t) aclpb->aclpb_aclContainer, + (IFP) __acllist_aciContainer_node_cmp); + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Searching AVL tree for update:%s: container:%d\n", basedn , + root ? root->acic_index: -1); + if ( index >= ACLPB_MAX_SELECTED_ACLS -2 ) { + aclpb->aclpb_handles_index[0] = -1; + slapi_ch_free ( (void **) &basedn); + break; + } else if ( NULL != root ) { + aclpb->aclpb_handles_index[index++] = root->acic_index; + aclpb->aclpb_handles_index[index] = -1; + } + tmp = slapi_dn_parent ( basedn ); + slapi_ch_free ( (void **) &basedn); + basedn = tmp; + if ( aclpb->aclpb_search_base && tmp && + ( 0 == strcasecmp ( tmp, aclpb->aclpb_search_base))) { + slapi_ch_free ( (void **) &basedn); + tmp = NULL; + } + } /* while */ + } + + acllist_done_aciContainer ( aclpb->aclpb_aclContainer ); + i = 0; + while ( i < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[i] != -1 ) { + i++; + } + +} + +aci_t * +acllist_get_first_aci (Acl_PBlock *aclpb, PRUint32 *cookie ) +{ + + int val; + + *cookie = val = 0; + if ( aclpb && aclpb->aclpb_handles_index[0] != -1 ) { + val = aclpb->aclpb_handles_index[*cookie]; + } + if ( NULL == aciContainerArray[val]) { + return ( acllist_get_next_aci ( aclpb, NULL, cookie ) ); + } + + return (aciContainerArray[val]->acic_list ); +} +/* + * acllist_get_next_aci + * Return the next aci in the list + * + * Inputs + * Acl_PBlock *aclpb -- acl Main block; if aclpb= NULL, + * -- then we scan thru the whole list. + * -- which is used by anom profile code. + * aci_t *curaci -- the current aci + * PRUint32 *cookie -- cookie -- to maintain a state (what's next) + * + */ + +aci_t * +acllist_get_next_aci ( Acl_PBlock *aclpb, aci_t *curaci, PRUint32 *cookie ) +{ + PRUint32 val; + int scan_entire_list; + + /* + Here, if we're passed a curaci and there's another aci in the same node, + return that one. + */ + + if ( curaci && curaci->aci_next ) + return ( curaci->aci_next ); + + /* + Determine if we need to scan the entire list of acis. + We do if the aclpb==NULL or if the first handle index is -1. + That means that we want to go through + the entire aciContainerArray up to the currContainerIndex to get + acis; the -1 in the first position is a special keyword which tells + us that the acis have changed, so we need to go through all of them. + */ + + scan_entire_list = (aclpb == NULL || aclpb->aclpb_handles_index[0] == -1); + +start: + (*cookie)++; + val = *cookie; + + /* if we are not scanning the entire aciContainerArray list, we only want to + look at the indexes specified in the handles index */ + if ( !scan_entire_list ) + val = aclpb->aclpb_handles_index[*cookie]; + + /* the hard max end */ + if ( val >= maxContainerIndex) + return NULL; + + /* reached the end of the array */ + if ((!scan_entire_list && (*cookie >= (ACLPB_MAX_SELECTED_ACLS-1))) || + (*cookie >= currContainerIndex)) { + return NULL; + } + + /* if we're only using the handles list for our aciContainerArray + indexes, the -1 value marks the end of that list */ + if ( !scan_entire_list && (aclpb->aclpb_handles_index[*cookie] == -1) ) { + return NULL; + } + + /* if we're scanning the entire list, and we hit a null value in the + middle of the list, just try the next one; this can happen if + an aci was deleted - it can leave "holes" in the array */ + if ( scan_entire_list && ( NULL == aciContainerArray[val])) { + goto start; + } + + if ( aciContainerArray[val] ) + return (aciContainerArray[val]->acic_list ); + else + return NULL; +} + +void +acllist_acicache_READ_UNLOCK( ) +{ + ACILIST_UNLOCK_READ (); + +} + +void +acllist_acicache_READ_LOCK() +{ + /* get a reader lock */ + ACILIST_LOCK_READ (); + +} + +void +acllist_acicache_WRITE_UNLOCK( ) +{ + ACILIST_UNLOCK_WRITE (); + +} + +void +acllist_acicache_WRITE_LOCK( ) +{ + ACILIST_LOCK_WRITE (); + +} + +/* This routine must be called with the acicache write lock taken */ +int +acllist_moddn_aci_needsLock ( Slapi_DN *oldsdn, char *newdn ) +{ + + + AciContainer *aciListHead; + AciContainer *head; + + /* first get the container */ + + aciListHead = acllist_get_aciContainer_new ( ); + slapi_sdn_free(&aciListHead->acic_sdn); + aciListHead->acic_sdn = oldsdn; + + + if ( NULL == (head = (AciContainer *) avl_find( acllistRoot, aciListHead, + (IFP) __acllist_aciContainer_node_cmp ) ) ) { + + slapi_log_error ( SLAPI_PLUGIN_ACL, plugin_name, + "Can't find the acl in the tree for moddn operation:olddn%s\n", + slapi_sdn_get_ndn ( oldsdn )); + aciListHead->acic_sdn = NULL; + __acllist_free_aciContainer ( &aciListHead ); + return 1; + } + + + /* Now set the new DN */ + slapi_sdn_done ( head->acic_sdn ); + slapi_sdn_set_ndn_byval ( head->acic_sdn, newdn ); + + aciListHead->acic_sdn = NULL; + __acllist_free_aciContainer ( &aciListHead ); + + return 0; +} + +void +acllist_print_tree ( Avlnode *root, int *depth, char *start, char *side) +{ + + AciContainer *aciHeadList; + + if ( NULL == root ) { + return; + } + aciHeadList = (AciContainer *) root->avl_data; + slapi_log_error ( SLAPI_LOG_ACL, "plugin_name", + "Container[ Depth=%d%s-%s]: %s\n", *depth, start, side, + slapi_sdn_get_ndn ( aciHeadList->acic_sdn ) ); + + (*depth)++; + + acllist_print_tree ( root->avl_left, depth, side, "L" ); + acllist_print_tree ( root->avl_right, depth, side, "R" ); + + (*depth)--; + +} + +static +void +ravl_print( Avlnode *root, int depth ) +{ + int i; + + AciContainer *aciHeadList; + if ( root == 0 ) + return; + + ravl_print( root->avl_right, depth+1 ); + + for ( i = 0; i < depth; i++ ) + printf( " " ); + aciHeadList = (AciContainer *) root->avl_data; + printf( "%s\n", slapi_sdn_get_ndn ( aciHeadList->acic_sdn ) ); + + ravl_print( root->avl_left, depth+1 ); +} + +void +my_print( Avlnode *root ) +{ + printf( "********\n" ); + + if ( root == 0 ) + printf( "\tNULL\n" ); + else + ( void ) ravl_print( root, 0 ); + + printf( "********\n" ); +} 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; +} diff --git a/ldap/servers/plugins/acl/aclplugin.c b/ldap/servers/plugins/acl/aclplugin.c new file mode 100644 index 00000000..a39ef3cd --- /dev/null +++ b/ldap/servers/plugins/acl/aclplugin.c @@ -0,0 +1,355 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* + * There are 3 ACL PLUGIN points + * PREOP, POSTOP and ACL plugin + * + */ +#include "acl.h" +#include "dirver.h" +#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */ + +static Slapi_PluginDesc pdesc = { "acl", PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT, "acl access check plugin" }; +char *plugin_name = ACL_PLUGIN_NAME; + +/* Prototypes */ + +static int aclplugin_preop_search ( Slapi_PBlock *pb ); +static int aclplugin_preop_modify ( Slapi_PBlock *pb ); +static int aclplugin_preop_common ( Slapi_PBlock *pb ); + +/******************************************************************************* + * ACL PLUGIN Architecture + * + * There are 3 registered plugins: + * + * 1) PREOP ACL Plugin + * The preop plugin does all the initialization. It allocate the ACL + * PBlock and copies stuff from the connection if it needs to. + * + * 2) POSTOP ACL Plugin + * The Postop plugin cleans up the ACL PBlock. It copies Back to the + * connection struct. The Postop bind & Unbind cleans up the + * ACL CBlock ( struct hanging from conn struct ). + * + * 3) ACCESSCONTROL Plugin + * Main module which does the access check. There are 5 entry point + * from this plugin + * a) Initilize the ACL system i.e read all the ACLs and generate the + * the ACL List. + * b) Check for ACI syntax. + * c) Check for normal access. + * d) Check for access to a mod request. + * e) Update the in-memory ACL List. + * + *******************************************************************************/ + +/******************************************************************************* + * PREOP + *******************************************************************************/ + +/* Plugin identity is passed by the server in the plugin init function and must + be supplied by the plugin to all internal operations it initiates + */ +void* g_acl_preop_plugin_identity; + +int +acl_preopInit (Slapi_PBlock *pb) +{ + int rc = 0; + + /* save plugin identity to later pass to internal operations */ + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &g_acl_preop_plugin_identity); + + /* Declare plugin version */ + rc = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + + /* Provide descriptive information */ + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void*)&pdesc); + + /* Register functions */ + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void*)aclplugin_preop_search); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_COMPARE_FN, (void*)aclplugin_preop_search); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void*)aclplugin_preop_modify); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void*)aclplugin_preop_modify); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODRDN_FN, (void*)aclplugin_preop_modify); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_DELETE_FN, (void*)aclplugin_preop_modify); + +#if 0 + /* + * XXXmcs: In order to support access control checking from + * extended operations, we need a SLAPI_PLUGIN_PRE_EXTENDED_FN hook. + * But today no such entry point exists. + */ + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_EXTENDED_FN, (void*)aclplugin_preop_modify); +#endif + + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= acl_preop_Init %d\n", rc, 0, 0 ); + return( rc ); +} + +/* For preop search we do two things: + * 1) based on the search base, we preselect the acls. + * 2) also get hold of a acl_pblock for use + */ +static int +aclplugin_preop_search ( Slapi_PBlock *pb ) +{ + int scope; + char *base = NULL; + int optype; + int isRoot; + int rc = 0; + + TNF_PROBE_0_DEBUG(aclplugin_preop_search_start ,"ACL",""); + + slapi_pblock_get ( pb, SLAPI_OPERATION_TYPE, &optype ); + slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot ); + + if ( isRoot ) { + TNF_PROBE_1_DEBUG(aclplugin_preop_search_end ,"ACL","", + tnf_string,isroot,""); + return rc; + } + + slapi_pblock_get( pb, SLAPI_SEARCH_TARGET, &base ); + /* For anonymous client doing search nothing needs to be set up */ + if ( optype == SLAPI_OPERATION_SEARCH && aclanom_is_client_anonymous ( pb ) && + ! slapi_dn_issuffix( base, "cn=monitor") ) { + TNF_PROBE_1_DEBUG(aclplugin_preop_search_end ,"ACL","", + tnf_string,anon,""); + return rc; + } + + if ( 0 == ( rc = aclplugin_preop_common( pb ))) { + slapi_pblock_get( pb, SLAPI_SEARCH_SCOPE, &scope ); + acllist_init_scan ( pb, scope, base ); + } + + TNF_PROBE_0_DEBUG(aclplugin_preop_search_end ,"ACL",""); + + return rc; +} + +/* + * For rest of the opertion type, we get a hold of the acl + * private Block. + */ +static int +aclplugin_preop_modify ( Slapi_PBlock *pb ) +{ + /* + * Note: since we don't keep the anom profile for modifies, we have to go + * through the regular process to check the access. + */ + return aclplugin_preop_common( pb ); +} + +/* + * Common function that is called by aclplugin_preop_search() and + * aclplugin_preop_modify(). + * + * Return values: + * 0 - all is well; proceed. + * 1 - fatal error; result has been sent to client. + */ +static int +aclplugin_preop_common( Slapi_PBlock *pb ) +{ + char *proxy_dn; /* id being assumed */ + char *dn; /* proxy master */ + char *errtext = NULL; + int lderr; + Acl_PBlock *aclpb; + + TNF_PROBE_0_DEBUG(aclplugin_preop_common_start ,"ACL",""); + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + + /* + * The following mallocs memory for proxy_dn, but not the dn. + * The proxy_dn is the id being assumed, while dn + * is the "proxy master". + */ + proxy_dn = NULL; + if ( LDAP_SUCCESS != ( lderr = acl_get_proxyauth_dn( pb, &proxy_dn, + &errtext ))) { + /* + * Fatal error -- send a result to the client and arrange to skip + * any further processing. + */ + slapi_send_ldap_result( pb, lderr, NULL, errtext, 0, NULL ); + TNF_PROBE_1_DEBUG(aclplugin_preop_common_end ,"ACL","", + tnf_string,proxid_error,""); + + return 1; /* skip any further processing */ + } + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &dn ); + + + /* + * The dn is copied into the aclpb during initialization. + */ + if ( proxy_dn) { + TNF_PROBE_0_DEBUG(proxyacpb_init_start,"ACL",""); + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "proxied authorization dn is (%s)\n", proxy_dn ); + acl_init_aclpb ( pb, aclpb, proxy_dn, 1 ); + aclpb = acl_new_proxy_aclpb (pb ); + acl_init_aclpb ( pb, aclpb, dn, 0 ); + slapi_ch_free ( (void **) &proxy_dn ); + + TNF_PROBE_0_DEBUG(proxyacpb_init_end,"ACL",""); + + } else { + TNF_PROBE_0_DEBUG(aclpb_init_start,"ACL",""); + acl_init_aclpb ( pb, aclpb, dn, 1 ); + TNF_PROBE_0_DEBUG(aclpb_init_end,"ACL",""); + + } + + TNF_PROBE_0_DEBUG(aclplugin_preop_common_end ,"ACL",""); + + return 0; +} + +/******************************************************************************* + * POSTOP + *******************************************************************************/ + +/******************************************************************************* + * ACCESSCONTROL PLUGIN + *******************************************************************************/ + +void* g_acl_plugin_identity; + +/* For now, the acl component is implemented as 2 different plugins */ +/* Return the right plugin identity */ +void * aclplugin_get_identity(int plug) { + if (plug == ACL_PLUGIN_IDENTITY) + return g_acl_plugin_identity; + if (plug == ACL_PREOP_PLUGIN_IDENTITY) + return g_acl_preop_plugin_identity; + return NULL; +} + +int +aclplugin_init (Slapi_PBlock *pb ) +{ + + int rc = 0; /* OK */ + rc = aclinit_main ( pb ); + + return rc; + +} +int +aclplugin_stop ( Slapi_PBlock *pb ) +{ + int rc = 0; /* OK */ + + /* nothing to be done now */ + return rc; +} + +int +acl_init( Slapi_PBlock *pb ) +{ + int rc =0; + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "=> acl_init\n", 0, 0, 0 ); + + if ( 0 != acl_init_ext() ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, + "Unable to initialize the extensions\n"); + return 1; + } + + /* save plugin identity to later pass to internal operations */ + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &g_acl_plugin_identity); + + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + (void *) SLAPI_PLUGIN_VERSION_01 ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ); + + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) aclplugin_init ); + rc = slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN, (void *) aclplugin_stop ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_SYNTAX_CHECK, + (void *) acl_verify_aci_syntax ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_ALLOW_ACCESS, + (void *) acl_access_allowed_main ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_MODS_ALLOWED, + (void *) acl_check_mods ); + rc |= slapi_pblock_set( pb, SLAPI_PLUGIN_ACL_MODS_UPDATE, + (void *) acl_modified ); + + slapi_log_error( SLAPI_LOG_PLUGIN, plugin_name, "<= acl_init %d\n", rc, 0, 0 ); + return( rc ); +} + +/* + * + * acl_access_allowed_main + * Main interface to the plugin. Calls different access check functions + * based on the flag. + * + * + * Returns: + * LDAP_SUCCESS -- access is granted + * LDAP_INSUFFICIENT_ACCESS -- access denied + * <other ldap error> -- ex: opererations error + * + */ +int +acl_access_allowed_main ( Slapi_PBlock *pb, Slapi_Entry *e, char **attrs, + struct berval *val, int access , int flags, char **errbuf) +{ + int rc =0; + char *attr = NULL; + + TNF_PROBE_0_DEBUG(acl_access_allowed_main_start,"ACL",""); + + if (attrs && *attrs) attr = attrs[0]; + + if (ACLPLUGIN_ACCESS_READ_ON_ENTRY == flags) + rc = acl_read_access_allowed_on_entry ( pb, e, attrs, access); + else if ( ACLPLUGIN_ACCESS_READ_ON_ATTR == flags) + rc = acl_read_access_allowed_on_attr ( pb, e, attr, val, access); + else if ( ACLPLUGIN_ACCESS_READ_ON_VLV == flags) + rc = acl_access_allowed_disjoint_resource ( pb, e, attr, val, access); + else if ( ACLPLUGIN_ACCESS_MODRDN == flags) + rc = acl_access_allowed_modrdn ( pb, e, attr, val, access); + else if ( ACLPLUGIN_ACCESS_GET_EFFECTIVE_RIGHTS == flags) + rc = acl_get_effective_rights ( pb, e, attrs, val, access, errbuf ); + else + rc = acl_access_allowed ( pb, e, attr, val, access); + + /* generate the appropriate error message */ + if ( ( rc != LDAP_SUCCESS ) && errbuf && + ( ACLPLUGIN_ACCESS_GET_EFFECTIVE_RIGHTS != flags ) && + ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE ))) { + + char *edn = slapi_entry_get_dn ( e ); + + acl_gen_err_msg(access, edn, attr, errbuf); + } + + TNF_PROBE_0_DEBUG(acl_access_allowed_main_end,"ACL",""); + + return rc; +} +#ifdef _WIN32 + +int *module_ldap_debug = 0; +void plugin_init_debug_level ( int *level_ptr ) +{ + module_ldap_debug = level_ptr; +} +#endif + diff --git a/ldap/servers/plugins/acl/aclproxy.c b/ldap/servers/plugins/acl/aclproxy.c new file mode 100644 index 00000000..9065bddc --- /dev/null +++ b/ldap/servers/plugins/acl/aclproxy.c @@ -0,0 +1,195 @@ +/** 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" + +#define BEGIN do { +#define END } while(0); + +/* ------------------------------------------------------------ + * LDAPProxyAuth + * + * ProxyAuthControl ::= SEQUENCE { + * authorizationDN LDAPDN + * } + */ +struct LDAPProxyAuth +{ + char *auth_dn; +}; +typedef struct LDAPProxyAuth LDAPProxyAuth; + +/* + * delete_LDAPProxyAuth + */ +static void +delete_LDAPProxyAuth(LDAPProxyAuth *spec) +{ + if (!spec) return; + + slapi_ch_free((void**)&spec->auth_dn); + slapi_ch_free((void**)&spec); +} + +/* + * parse_LDAPProxyAuth + * + * Parse a BER encoded value into the compoents of the LDAP ProxyAuth control. + * The 'version' parameter should be 1 or 2. + * + * Returns an LDAP error code (LDAP_SUCCESS if all goes well) and sets + * *errtextp if appropriate. + */ +static int +parse_LDAPProxyAuth(struct berval *spec_ber, int version, char **errtextp, + LDAPProxyAuth **out) +{ + int lderr = LDAP_OPERATIONS_ERROR; /* pessimistic */ + LDAPProxyAuth *spec = NULL; + BerElement *ber = NULL; + char *errstring = "unable to parse proxied authorization control"; + + + BEGIN + unsigned long tag; + + if ( version != 1 && version != 2 ) { + break; + } + + /* create_LDAPProxyAuth */ + spec = (LDAPProxyAuth*)slapi_ch_calloc(1,sizeof (LDAPProxyAuth)); + if (!spec) { + break; + } + + ber = ber_init(spec_ber); + if (!ber) { + break; + } + + if ( version == 1 ) { + tag = ber_scanf(ber, "{a}", &spec->auth_dn); + } else { + tag = ber_scanf(ber, "a", &spec->auth_dn); + } + if (tag == LBER_ERROR) { + lderr = LDAP_PROTOCOL_ERROR; + break; + } + + /* + * In version 2 of the control, the control value is actually an + * authorization ID (see section 9 of RFC 2829). We only support + * the "dnAuthzId" flavor, which looks like "dn:<DN>" where <DN> is + * an actual DN, e.g., "dn:uid=bjensen,dc=example,dc=com". So we + * need to strip off the dn: if present and reject the operation if + * not. + */ + if (2 == version) { + if ( NULL == spec->auth_dn || strlen( spec->auth_dn ) < 3 || + strncmp( "dn:", spec->auth_dn, 3 ) != 0 ) { + lderr = LDAP_INSUFFICIENT_ACCESS; /* per Proxied Auth. I-D */ + errstring = "proxied authorization id must be a DN (dn:...)"; + break; + } + strcpy( spec->auth_dn, spec->auth_dn + 3 ); + } + + slapi_dn_normalize(spec->auth_dn); + lderr = LDAP_SUCCESS; /* got it! */ + END + + /* Cleanup */ + if (ber) ber_free(ber, 0); + + if ( LDAP_SUCCESS != lderr) + { + if (spec) delete_LDAPProxyAuth(spec); + spec = 0; + if ( NULL != errtextp ) { + *errtextp = errstring; + } + } + + *out = spec; + + return lderr; +} + +/* + * proxyauth_dn - find the users DN in the proxyauth control if it is + * present. The return value has been malloced for you. + * + * Returns an LDAP error code. If anything than LDAP_SUCCESS is returned, + * the error should be returned to the client. LDAP_SUCCESS is always + * returned if the proxy auth control is not present or not critical. + */ +int +acl_get_proxyauth_dn( Slapi_PBlock *pb, char **proxydnp, char **errtextp ) +{ + char *dn = 0; + LDAPProxyAuth *spec = 0; + int rv, lderr = LDAP_SUCCESS; /* optimistic */ + + BEGIN + struct berval *spec_ber; + LDAPControl **controls; + int present; + int critical; + int version = 1; + + rv = slapi_pblock_get( pb, SLAPI_REQCONTROLS, &controls ); + if (rv) break; + + present = slapi_control_present( controls, LDAP_CONTROL_PROXYAUTH, + &spec_ber, &critical ); + if (!present) { + present = slapi_control_present( controls, LDAP_CONTROL_PROXIEDAUTH, + &spec_ber, &critical ); + if (!present) break; + version = 2; + /* + * Note the according to the Proxied Authorization I-D, the + * control is always supposed to be marked critical by the + * client. If it is not, we return a protocolError. + */ + if ( !critical ) { + lderr = LDAP_PROTOCOL_ERROR; + if ( NULL != errtextp ) { + *errtextp = "proxy control must be marked critical"; + } + break; + } + } + + rv = parse_LDAPProxyAuth(spec_ber, version, errtextp, &spec); + if (LDAP_SUCCESS != rv) { + if ( critical ) { + lderr = rv; + } + break; + } + + dn = slapi_ch_strdup(spec->auth_dn); + if (slapi_dn_isroot(dn) ) { + lderr = LDAP_UNWILLING_TO_PERFORM; + *errtextp = "Proxy dn should not be rootdn"; + break; + + } + END + + if (spec) delete_LDAPProxyAuth(spec); + + if ( NULL != proxydnp ) { + *proxydnp = dn; + } else { + slapi_ch_free( (void **)&dn ); + } + + return lderr; +} + diff --git a/ldap/servers/plugins/acl/aclutil.c b/ldap/servers/plugins/acl/aclutil.c new file mode 100644 index 00000000..168ca482 --- /dev/null +++ b/ldap/servers/plugins/acl/aclutil.c @@ -0,0 +1,1475 @@ +/** 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" + +/************************************************************************** +* Defines and usefuls stuff +****************************************************************************/ + +/************************************************************************* +* Prototypes +*************************************************************************/ +static void aclutil__typestr (int type , char str[]); +static void aclutil__Ruletypestr (int type , char str[]); +static char* __aclutil_extract_dn_component ( char **e_dns, int position, + char *attrName ); +static char* acl_get_final_component(char *macro_prefix) ; +static char* acl_match_component( char *start, char *component); +static int aclutil_compare_components( char * comp1, char *comp2); +static int acl_find_comp_start(char * s, int pos ); +static PRIntn acl_ht_free_entry_and_value(PLHashEntry *he, PRIntn i, + void *arg); +static PLHashNumber acl_ht_hash( const void *key); +static PRIntn acl_ht_display_entry(PLHashEntry *he, PRIntn i, void *arg); + +/***************************************************************************/ +/* UTILITY FUNCTIONS */ +/***************************************************************************/ +int +aclutil_str_appened(char **str1, const char *str2) +{ + int new_len; + + if ( str1 == NULL || str2 == NULL ) + return(0); + + if ( *str1 == NULL ) { + new_len = strlen(str2) + 1; + *str1 = (char *)slapi_ch_malloc(new_len); + *str1[0] = 0; + } else { + new_len = strlen(*str1) + strlen(str2) + 1; + *str1 = (char *)slapi_ch_realloc(*str1, new_len); + } + if ( *str1 == NULL ) + return(-1); + + strcat(*str1, str2); + return(0); +} + +/***************************************************************************/ +/* Print routines */ +/***************************************************************************/ + +/* print eroror message returned from the ACL Library */ +#define ACLUTIL_ACLLIB_MSGBUF_LEN 200 +void +acl_print_acllib_err (NSErr_t *errp , char * str) +{ + char msgbuf[ ACLUTIL_ACLLIB_MSGBUF_LEN ]; + + if ((NULL == errp ) || !slapi_is_loglevel_set ( SLAPI_LOG_ACL ) ) + return; + + aclErrorFmt(errp, msgbuf, ACLUTIL_ACLLIB_MSGBUF_LEN, 1); + msgbuf[ACLUTIL_ACLLIB_MSGBUF_LEN-1] = '\0'; + + if (msgbuf) + slapi_log_error(SLAPI_LOG_ACL, plugin_name,"ACL LIB ERR:(%s)(%s)\n", + msgbuf, str ? str: "NULL",0); +} +void +aclutil_print_aci (aci_t *aci_item, char *type) +{ + char str[BUFSIZ]; + const char *dn; + + if ( ! slapi_is_loglevel_set ( SLAPI_LOG_ACL ) ) + return; + + if (!aci_item) { + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "acl__print_aci: Null item\n",0,0,0); + return; + } + + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "***BEGIN ACL INFO[ Name:%s]***\n", aci_item->aclName); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "ACL Index:%d ACL_ELEVEL:%d\n", aci_item->aci_index, aci_item->aci_elevel ); + aclutil__access_str (aci_item->aci_access, str); + aclutil__typestr (aci_item->aci_type, &str[strlen(str)]); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "ACI type:(%s)\n", str); + + aclutil__Ruletypestr (aci_item->aci_ruleType, str); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "ACI RULE type:(%s)\n",str); + + dn = slapi_sdn_get_dn ( aci_item->aci_sdn ); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Slapi_Entry DN:%s\n", escape_string_with_punctuation (dn, str)); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "***END ACL INFO*****************************\n"); + +} +void +aclutil_print_err (int rv , const Slapi_DN *sdn, const struct berval* val, + char **errbuf) +{ + char ebuf [BUFSIZ]; + /* + * The maximum size of line is ebuf_size + the log message + * itself (less than 200 characters for all but potentially ACL_INVALID_TARGET) + */ + char line [BUFSIZ + 200]; + char str [1024]; + const char *dn; + char *lineptr = line; + char *newline = NULL; + + if ( rv >= 0) + return; + + if (val->bv_len > 0 && val->bv_val != NULL) { + sprintf (str, "%.1023s", val->bv_val); + } else { + str[0] = '\0'; + } + + dn = slapi_sdn_get_dn ( sdn ); + if (dn && (rv == ACL_INVALID_TARGET) && ((strlen(dn) + strlen(str)) > BUFSIZ)) { + /* + * if (str_length + dn_length + 200 char message) > (BUFSIZ + 200) line + * we have to make space for a bigger line... + */ + newline = slapi_ch_malloc(strlen(dn) + strlen(str) + 200); + lineptr = newline; + } + + switch (rv) { + case ACL_TARGET_FILTER_ERR: + sprintf (line, "ACL Internal Error(%d): " + "Error in generating the target filter for the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_TARGETATTR_FILTER_ERR: + sprintf (line, "ACL Internal Error(%d): " + "Error in generating the targetattr filter for the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_TARGETFILTER_ERR: + sprintf (line, "ACL Internal Error(%d): " + "Error in generating the targetfilter filter for the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_SYNTAX_ERR: + sprintf (line, "ACL Syntax Error(%d):%s\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_ONEACL_TEXT_ERR: + sprintf (line, "ACL Syntax Error in the Bind Rules(%d):%s\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_ERR_CONCAT_HANDLES: + sprintf (line, "ACL Internal Error(%d): " + "Error in Concatenating List handles\n", + rv); + break; + case ACL_INVALID_TARGET: + sprintf (lineptr, "ACL Invalid Target Error(%d): " + "Target is beyond the scope of the ACL(SCOPE:%s)", + rv, dn ? escape_string_with_punctuation (dn, ebuf) : "NULL"); + sprintf (lineptr + strlen(lineptr), " %s\n", escape_string_with_punctuation (str, ebuf)); + break; + case ACL_INVALID_AUTHMETHOD: + sprintf (line, "ACL Multiple auth method Error(%d):" + "Multiple Authentication Metod in the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_INVALID_AUTHORIZATION: + sprintf (line, "ACL Syntax Error(%d):" + "Invalid Authorization statement in the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + case ACL_INCORRECT_ACI_VERSION: + sprintf (line, "ACL Syntax Error(%d):" + "Incorrect version Number in the ACL(%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + default: + sprintf (line, "ACL Internal Error(%d):" + "ACL generic error (%s)\n", + rv, escape_string_with_punctuation (str, ebuf)); + break; + } + + if (errbuf) { + /* If a buffer is provided, then copy the error */ + aclutil_str_appened(errbuf, lineptr ); + } + + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "%s", lineptr); + if (newline) slapi_ch_free((void **) &newline); +} + +/*************************************************************************** +* Convert access to str +***************************************************************************/ +char* +aclutil__access_str (int type , char str[]) +{ + char *p; + + str[0] = '\0'; + p = str; + + if (type & SLAPI_ACL_COMPARE) { + strcpy (p, "compare "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_SEARCH) { + strcpy (p, "search "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_READ) { + strcpy (p, "read "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_WRITE) { + strcpy (p, "write "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_DELETE) { + strcpy (p, "delete "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_ADD) { + strcpy (p, "add "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_SELF) { + strcpy (p, "self "); + p = strchr (p, '\0'); + } + if (type & SLAPI_ACL_PROXY) { + strcpy (p, "proxy "); + } + return str; +} + +/*************************************************************************** +* Convert type to str +***************************************************************************/ +static void +aclutil__typestr (int type , char str[]) +{ + char *p; + + /* Start copying in at whatever location is passed in */ + + p = str; + + if (type & ACI_TARGET_DN) { + strcpy (p, "target_DN "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_ATTR) { + strcpy (p, "target_attr "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_PATTERN) { + strcpy (p, "target_patt "); + p = strchr (p, '\0'); + } + if ((type & ACI_TARGET_ATTR_ADD_FILTERS) | (type & ACI_TARGET_ATTR_DEL_FILTERS)) { + strcpy (p, "targetattrfilters "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_FILTER) { + strcpy (p, "target_filter "); + p = strchr (p, '\0'); + } + if (type & ACI_ACLTXT) { + strcpy (p, "acltxt "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_NOT) { + strcpy (p, "target_not "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_ATTR_NOT) { + strcpy (p, "target_attr_not "); + p = strchr (p, '\0'); + } + if (type & ACI_TARGET_FILTER_NOT) { + strcpy (p, "target_filter_not "); + p = strchr (p, '\0'); + } + + if (type & ACI_HAS_ALLOW_RULE) { + strcpy (p, "allow_rule "); + p = strchr (p, '\0'); + } + if (type & ACI_HAS_DENY_RULE) { + strcpy (p, "deny_rule "); + p = strchr (p, '\0'); + } +} +static void +aclutil__Ruletypestr (int type , char str[]) +{ + char *p; + + str[0] = '\0'; + p = str; + if ( type & ACI_USERDN_RULE) { + strcpy (p, "userdn "); + p = strchr (p, '\0'); + } + if ( type & ACI_USERDNATTR_RULE) { + strcpy (p, "userdnattr "); + p = strchr (p, '\0'); + } + if ( type & ACI_USERATTR_RULE) { + strcpy (p, "userattr "); + p = strchr (p, '\0'); + } + if ( type & ACI_GROUPDN_RULE) { + strcpy (p, "groupdn "); + p = strchr (p, '\0'); + } + if ( type & ACI_GROUPDNATTR_RULE) { + strcpy (p, "groupdnattr "); + p = strchr (p, '\0'); + } + if ( type & ACI_ROLEDN_RULE) { + strcpy (p, "roledn "); + p = strchr (p, '\0'); + } + if ( type & ACI_IP_RULE) { + strcpy (p, "ip "); + p = strchr (p, '\0'); + } + if ( type & ACI_DNS_RULE) { + strcpy (p, "dns "); + p = strchr (p, '\0'); + } + if ( type & ACI_TIMEOFDAY_RULE) { + strcpy (p, "timeofday "); + p = strchr (p, '\0'); + } + if ( type & ACI_DAYOFWEEK_RULE) { + strcpy (p, "dayofweek "); + p = strchr (p, '\0'); + } + if ( type & ACI_AUTHMETHOD_RULE) { + strcpy (p, "authmethod "); + p = strchr (p, '\0'); + } + if ( type & ACI_PARAM_DNRULE) { + strcpy (p, "paramdn "); + p = strchr (p, '\0'); + } + if ( type & ACI_PARAM_ATTRRULE) { + strcpy (p, "paramAttr "); + p = strchr (p, '\0'); + } +} +/* +** acl_gen_err_msg +** This function is called by backend to generate the error message +** if access is denied. +*/ +void +acl_gen_err_msg(int access, char *edn, char *attr, char **errbuf) +{ + char *line = NULL; + + if (access & SLAPI_ACL_WRITE) { + line = PR_smprintf( + "Insufficient 'write' privilege to the '%s' attribute of entry '%s'.\n", + attr ? attr: "NULL", edn); + } else if ( access & SLAPI_ACL_ADD ) { + line = PR_smprintf( + "Insufficient 'add' privilege to add the entry '%s'.\n",edn); + + } else if ( access & SLAPI_ACL_DELETE ) { + line = PR_smprintf( + "Insufficient 'delete' privilege to delete the entry '%s'.\n",edn); + } + aclutil_str_appened(errbuf, line ); + + if (line) { + PR_smprintf_free(line); + line = NULL; + } +} +short +aclutil_gen_signature ( short c_signature ) +{ + short o_signature; + o_signature = c_signature ^ (slapi_rand() % 32768); + if (!o_signature) + o_signature = c_signature ^ (slapi_rand() % 32768); + + return o_signature; +} + +void +aclutil_print_resource( struct acl_pblock *aclpb, char *right , char *attr, char *clientdn ) +{ + + char str[BUFSIZ]; + const char *dn; + + + if ( aclpb == NULL) return; + + if ( ! slapi_is_loglevel_set ( SLAPI_LOG_ACL ) ) + return; + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ************ RESOURCE INFO STARTS *********\n",0,0,0); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " Client DN: %s\n", + clientdn ? escape_string_with_punctuation (clientdn, str) : "NULL", 0,0); + aclutil__access_str (aclpb->aclpb_access, str); + aclutil__typestr (aclpb->aclpb_res_type, &str[strlen(str)]); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " resource type:%d(%s)\n", + aclpb->aclpb_res_type, str, 0); + + dn = slapi_sdn_get_dn ( aclpb->aclpb_curr_entry_sdn ); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " Slapi_Entry DN: %s\n", + dn ? escape_string_with_punctuation ( dn , str) : "NULL",0,0); + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ATTR: %s\n", attr ? attr : "NULL",0,0); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " rights:%s\n", right ? right: "NULL",0,0); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, " ************ RESOURCE INFO ENDS *********\n",0,0,0); +} +/* + * The input string contains a rule like + * "cn=helpdesk, ou=$attr.deptName, o=$dn.o, o=ISP" + * + * Where $attr -- means look into the attribute list for values + * $dn -- means look into the entry's dn + * + * We extract the values from the entry and returned a string + * with the values added. + * For "$attr" rule - if we find multiple values then it is + * the pattern is not expanded. + * For "$dn" rule, if we find multiple of them, we use the relative + * position. + * NOTE: The caller is responsible in freeing the memory. + */ +char * +aclutil_expand_paramString ( char *str, Slapi_Entry *e ) +{ + + char **e_dns; + char **a_dns; + char *attrName; + char *s, *p; + char *attrVal; + int i, len; + int ncomponents, type; + int rc = -1; + char *buf = NULL; + + + e_dns = ldap_explode_dn ( slapi_entry_get_ndn ( e ), 0 ); + a_dns = ldap_explode_dn ( str, 0 ); + + i = 0; + ncomponents = 0; + while ( a_dns[ncomponents] ) + ncomponents++; + + + for (i=0; i < ncomponents; i++ ) { + + /* Look for"$" char */ + if ( (s = strchr ( a_dns[i], '$') ) != NULL) { + p = s; + s++; + if ( strncasecmp (s, "dn", 2) == 0 ) + type = 1; + else if ( strncasecmp (s, "attr", 4) == 0 ) + type = 2; + else { + /* error */ + goto cleanup; + } + *p = '\0'; + aclutil_str_appened ( &buf,a_dns[i]); + + if ( type == 1 ) { + /* xyz = $dn.o */ + s +=3; + attrName = s; + + attrVal = __aclutil_extract_dn_component (e_dns, + ncomponents-i, attrName); + if ( NULL == attrVal ) /*error*/ + goto cleanup; + + } else { + Slapi_Attr *attr; + const struct berval *attrValue; + int kk; + Slapi_Value *sval, *t_sval; + + + /* The pattern is x=$attr.o" */ + s +=5; + attrName = s; + + slapi_entry_attr_find ( e, attrName, &attr ); + if ( NULL == attr ) + goto cleanup; + + kk= slapi_attr_first_value ( attr, &sval ); + if ( kk != -1 ) { + t_sval = sval; + kk= slapi_attr_next_value( attr, kk, &sval ); + if ( kk != -1 ) /* can't handle multiple --error */ + goto cleanup; + } + attrValue = slapi_value_get_berval ( t_sval ); + attrVal = attrValue->bv_val; + } + } else { + attrVal = a_dns[i]; + } + aclutil_str_appened ( &buf, attrVal); + aclutil_str_appened ( &buf, ","); + } + rc = 0; /* everything is okay*/ + /* remove the last comma */ + len = strlen ( buf); + buf[len-1] = '\0'; + +cleanup: + + ldap_value_free ( a_dns ); + ldap_value_free ( e_dns ); + if ( 0 != rc ) /* error */ { + slapi_ch_free ( (void **) &buf ); + buf = NULL; + } + + return buf; +} +static char * +__aclutil_extract_dn_component ( char **e_dns, int position, char *attrName ) +{ + + int i, matched, len; + char *s; + int matchedPosition; + + len = strlen ( attrName ); + + /* First check if there thare are multiple of these */ + i = matched = 0; + while ( e_dns[i] ) { + if (0 == strncasecmp (e_dns[i], attrName, len) ) { + matched++; + matchedPosition = i; + } + i++; + } + + if (!matched ) + return NULL; + + if ( matched > 1 ) { + matchedPosition = i - position; + } + + if ( NULL == e_dns[matchedPosition]) + return NULL; + + s = strstr ( e_dns[matchedPosition], "="); + if ( NULL == s) + return NULL; + else + return s+1; +} + +/* + * Does the first component of ndn match the first component of match_this ? +*/ + +int +acl_dn_component_match( const char *ndn, char *match_this, int component_number) { + + return(1); +} + +/* + * Here, ndn is a resource dn and match_this is a dn, containing a macro, ($dn). + * + * eg. ndn is cn=fred,ou=groups,ou=people,ou=icnc,o=ISP and + * match_this is "ou=Groups,($dn),o=ISP" or + * "cn=*,ou=Groups,($dn),o=ISP". + * + * They match if: + * match_this is a suffix of ndn + * + * It returns NULL, if they do not match. + * Otherwise it returns a copy of the substring of ndn that matches the ($dn). + * + * eg. in the above example, "ou=people,ou=icnc" +*/ + +char * +acl_match_macro_in_target( const char *ndn, char * match_this, + char *macro_ptr) { + + char *macro_prefix = NULL; + int macro_prefix_len = 0; + char *macro_suffix = NULL; + char *tmp_ptr = NULL; + char *matched_val = NULL; + char *ndn_suffix_start = NULL; + char *macro_prefix_final_component = NULL; + char *ret_val = NULL; + int ndn_len = 0; + int macro_suffix_len = 0; + int ndn_prefix_len = 0; + int ndn_prefix_end = 0; + int matched_val_len = 0; + + /* + * First, grab the macro_suffix--the bit after the ($dn) + * + */ + + if (strlen(macro_ptr) == strlen(ACL_TARGET_MACRO_DN_KEY)) { + macro_suffix = NULL; /* just ($dn) */ + } else { + if ( macro_ptr[strlen(ACL_TARGET_MACRO_DN_KEY)] == ',') { + macro_suffix = ¯o_ptr[strlen(ACL_TARGET_MACRO_DN_KEY) + 1]; + } else { + macro_suffix = ¯o_ptr[strlen(ACL_TARGET_MACRO_DN_KEY)]; + } + } + + /* + * First ensure that the suffix of match_this is + * a suffix of ndn. + */ + + ndn_len = strlen(ndn); + if ( macro_suffix != NULL) { + macro_suffix_len = strlen(macro_suffix); + if( macro_suffix_len >= ndn_len ) { + + /* + * eg ndn: o=icnc,o=sun.com + * match_this: ($dn),o=icnc,o=sun.com + */ + return(NULL); /* ($dn) must match something. */ + } else { + /* + * eg ndn: ou=People,o=icnc,o=sun.com + * match_this: ($dn),o=icnc,o=sun.com + * + * we can do a direct strncmp() because we know that + * there can be no "*" after the ($dn)...by definition. + */ + if (strncasecmp( macro_suffix, &ndn[ndn_len-macro_suffix_len], + macro_suffix_len) != 0) { + return(NULL); /* suffix must match */ + } + } + } + + /* Start of the suffix in ndn...and it matched. */ + ndn_suffix_start = (char*)&ndn[ndn_len-macro_suffix_len]; + + /* Here, macro_suffix is a suffix of ndn. + * + * + * Now, look at macro_prefix, if it is NULL, then ($dn) matches + * ndn[0..ndn_len-macro_suffix_len]. + * (eg, ndn: cn=fred,ou=People,o=sun.com + * match_this: ($dn),o=sun.com. + * + */ + + macro_prefix = slapi_ch_strdup(match_this); + + /* we know it's got a $(dn) */ + tmp_ptr = strstr(macro_prefix, ACL_TARGET_MACRO_DN_KEY); + *tmp_ptr = '\0'; + /* There may be a NULL prefix eg. match_this: ($dn),o=sun.com */ + macro_prefix_len = strlen(macro_prefix); + if (macro_prefix_len == 0) { + slapi_ch_free((void **) ¯o_prefix); + macro_prefix = NULL; + } + + if (macro_prefix == NULL ) { + /* + * ($dn) matches ndn[0..ndn_len-macro_suffix_len] + */ + int matched_val_len = 0; + + matched_val_len = ndn_len-macro_suffix_len; + + matched_val = (char *)slapi_ch_malloc(matched_val_len + 1); + strncpy(matched_val, ndn, ndn_len-macro_suffix_len); + /* + * Null terminate matched_val, removing trailing "," if there is + * one. + */ + if (matched_val_len > 1) { + if (matched_val[matched_val_len-1] == ',' ) { + matched_val[matched_val_len-1] = '\0'; + } else { + matched_val[matched_val_len] = '\0'; + } + } + ret_val = matched_val; + } else { + + + /* + * If it is not NULL, then if macro_prefix contains a * then + * it needs to be an exact prefix of ndn (modulo the * component + * which matches anything) becuase that's the semantics + * of target patterns containing *'s, except that we just + * make it match one component. + * If it is such a prefix then ($dn) matches that portion of ndn + * from the end of the prefix, &ndn[ndn_prefix_end] to + * ndn_suffix_start. + * If ndn_prefix_len > ndn_len-macro_suffix_len then return(NULL), + * otherwise $(dn) matches ndn[ndn_prefix_len..ndn_len-macro_suffix_len]. + * + * + * eg. ndn: cn=fred,ou=P,o=sun.com + * match_this: cn=*,($dn),o=sun.com + */ + + if ( strstr(macro_prefix, "=*") != NULL ) { + int exact_match = 0; + + ndn_prefix_len = acl_match_prefix( macro_prefix, ndn, &exact_match); + if ( ndn_prefix_len != -1 ) { + + /* + * ndn[0..ndn_prefix_len] is the prefix in ndn. + * ndn[ndn_prefix_len..ndn_len-macro_suffix_len] is the + * matched string. + */ + if (ndn_prefix_len >= ndn_len-macro_suffix_len) { + + /* + * eg ndn: cn=fred,ou=People,o=icnc,o=sun.com + * cn=*,ou=People,o=icnc,($dn),o=icnc,o=sun.com + */ + + ret_val = NULL; /* matched string is empty */ + } else { + + /* + * eg ndn: cn=fred,ou=People,o=icnc,o=sun.com + * cn=*,ou=People,($dn),o=sun.com + */ + + matched_val_len = ndn_len-macro_suffix_len-ndn_prefix_len; + matched_val = (char *)slapi_ch_malloc(matched_val_len + 1); + strncpy(matched_val, &ndn[ndn_prefix_len], matched_val_len); + if (matched_val_len > 1) { + if (matched_val[matched_val_len-1] == ',' ) { + matched_val[matched_val_len-1] = '\0'; + } else { + matched_val[matched_val_len] = '\0'; + } + } + matched_val[matched_val_len] = '\0'; + ret_val = matched_val; + } + } else { + /* Was not a prefix so not a match */ + ret_val = NULL; + } + } else { + + /* + * + * If macro_prefix is not NULL and it does not + * contain a =* then + * we need to ensure that macro_prefix is a substring + * ndn. + * If it is and the position of the character after it's end in + * ndn is + * ndn_prefix_end then ($dn) matches + * ndn[ndn_prefix_end..ndn_len-macro_suffix_len]. + * + * + * One important principal is that ($dn) matches a maximal + * chunk--this way it will serve to make the link + * between resources and users at each level of the structure. + * + * eg. ndn: ou=Groups,ou=Groups,ou=Groups,c=fr + * macro_prefix: ou=Groups,($dn),c=fr + * + * then ($dn) matches ou=Groups,ou=Groups. + * + * + * + * If it is not a substring, then there is no match. + * If it is a substring and + * ndn[ndn_prefix_end..ndn_len-macro_suffix_len] is empty then + * it's also not a match as we demand that ($dn) match a non-empty + * string. + * + * + * + * (eg. ndn: cn=fred,o=icnc,ou=People,o=sun.com + * match_this: o=icnc,($dn),o=sun.com.) + * + * + * (eg. ndn: cn=fred,o=menlo park,ou=People,o=icnc,o=sun.com + * match_this: o=menlo park,ou=People,($dn),o=sun.com + * + */ + + ndn_prefix_end = acl_strstr((char *)ndn, macro_prefix); + if ( ndn_prefix_end == -1) { + ret_val = NULL; + } else { + /* Is a substring */ + + ndn_prefix_end += macro_prefix_len; + + /* + * make sure the matching part is non-empty: + * + * ndn[ndn_prefix_end..mndn_len-macro_suffix_len]. + */ + + if ( ndn_prefix_end >= ndn_len-macro_suffix_len) { + ret_val = NULL; + } else { + /* + * ($dn) matches the non-empty string segment + * ndn[ndn_prefix_end..mndn_len-macro_suffix_len] + * the -1 is because macro_suffix_eln does not include + * the coma before the suffix. + */ + + matched_val_len = ndn_len-macro_suffix_len- + ndn_prefix_end - 1; + + matched_val = (char *)slapi_ch_malloc(matched_val_len + 1); + strncpy(matched_val, &ndn[ndn_prefix_end], + matched_val_len); + matched_val[matched_val_len] = '\0'; + + ret_val = matched_val; + } + } + }/* contains an =* */ + slapi_ch_free((void **) ¯o_prefix); + }/* macro_prefix != NULL */ + + return(ret_val); +} + +/* + * Checks to see if macro_prefix is an exact prefix of ndn. + * macro_prefix may contain a * component. + * + * The length of the matched prefix in ndn is returned. + * If it was not a match, a negative int is returned. + * Also, if the string matched exactly, + * exact_match is set to 1, other wise it was a proper prefix. + * +*/ + +int +acl_match_prefix( char *macro_prefix, const char *ndn, int *exact_match) { + + int macro_index = 0; + int ndn_index = 0; + int ret_code = -1; + char *curr_macro_component = NULL; + char *curr_ndn_component = NULL; + int matched = 0; + int macro_prefix_len = 0; + int ndn_len = 0; + int i = 0; + int j = 0; + int done = 0; + int t = 0; + char * tmp_str = NULL; + int k,l = 0; + + *exact_match = 0; /* default to not an exact match */ + + /* The NULL prefix matches everthing*/ + if (macro_prefix == NULL) { + if ( ndn == NULL ) { + *exact_match = 1; + } + return(0); + } else { + /* macro_prefix is not null, so if ndn is NULL, it's not a match. */ + if ( ndn == NULL) { + return(-1); + } + } + /* + * Here, neither macro_prefix nor ndn are NULL. + * + * eg. macro_prefix: cn=*,ou=people,o=sun.com + * ndn : cn=fred,ou=people,o=sun.com + */ + + + /* + * Here, there is a component with a * (eg. cn=* ) so + * we need to step through the macro_prefix, and where there is + * such a * match on that component, + * when we run out of * componenets, jsut do a straight match. + * + * Out of interest, the following extended regular expression + * will match just one ou rdn value from a string: + * "^uid=admin,ou=\([^,]*\\\,\)*[^,]*,o=sun.com$" + * + * + * eg. cn=fred,ou=People,o=sun.com + * + * + * s points to the = of the component. + */ + + macro_prefix_len = strlen(macro_prefix); + ndn_len = strlen(ndn); + i = 0; + j = 0; + done = 0; + while ( !done ) { + + /* Here ndn[0..i] has matched macro_prefix[0..j] && j<= i + * i<=ndn_len j<=macro_prefix_len */ + + if ( (t = acl_strstr(¯o_prefix[j], "=*")) < 0 ) { + /* + * No more *'s, do a straight match on + * macro_prefix[j..macro_prefix_len] and + * ndn[i..macro_prefix_len] + */ + + if( macro_prefix_len-j > ndn_len-i) { + /* Not a prefix, nor a match */ + *exact_match = 0; + ret_code = -1; + done = 1; + } else { + /* + * ndn_len-i >= macro_prefix_len - j + * if macro_prefix_len-j is 0, then + * it's a null prefix, so it matches. + * If in addition ndn_len-i is 0 then it's + * an exact match. + * Otherwise, do the cmp. + */ + + if ( macro_prefix_len-j == 0) { + done = 1; + ret_code = i; + if ( ndn_len-i == 0) { + *exact_match = 1; + } + }else { + + if (strncasecmp(¯o_prefix[j], &ndn[i], + macro_prefix_len-j) == 0) { + *exact_match = (macro_prefix_len-j == ndn_len-i); + ret_code = i + macro_prefix_len -j; + done = 1; + } else { + /* not a prefix not a match */ + *exact_match = 0; + ret_code = -1; + done = 1; + } + } + } + }else { + /* + * Is another * component, so: + * 1. match that component in macro_prefix (at pos k say) + * with the corresponding compoent (at pos l say ) in ndn + * + * 2. match the intervening string ndn[i..l] and + * macro_prefix[j..k]. + */ + + /* First, find the start of the component in macro_prefix. */ + + t++; /* move to the--this way we will look for "ou=" in ndn */ + k = acl_find_comp_start(macro_prefix, t); + + /* Now identify that component in ndn--if it's not there no match */ + tmp_str = slapi_ch_malloc(t-k+1); + strncpy(tmp_str, ¯o_prefix[k], t-k); + tmp_str[t-k] = '\0'; + l = acl_strstr((char*)&ndn[i], tmp_str); + if (l == -1) { + *exact_match = 0; + ret_code = -1; + done = 1; + } else { + /* + * Found the comp in ndn, so the comp matches. + * Now test the intervening string segments: + * ndn[i..l] and macro_prefix[j..k] + */ + + if ( k-j != l-i ) { + *exact_match = 0; + ret_code = -1; + done = 1; + } else{ + if (strncasecmp(¯o_prefix[j], &ndn[i], k-j) != 0) { + *exact_match = 0; + ret_code = -1; + done = 1; + } else { + /* Matched, so bump i and j and keep going.*/ + i += acl_find_comp_end((char*)&ndn[l]); + j += acl_find_comp_end((char*)¯o_prefix[k]); + } + } + } + slapi_ch_free((void **)&tmp_str); + } + }/* while */ + + return(ret_code); + +} + +/* + * returns the index in s of where the component at position + * s[pos] starts. + * This is the index of the character after the first unescaped comma + * moving backwards in s from pos. + * If this is not found then return 0, ie. the start of the string. + * If the index returned is > strlen(s) then could not find it. + * only such case is if you pass ",", in which case there is no component start. +*/ + +static int +acl_find_comp_start(char * s, int pos ) { + + int i =0; + int comp_start = 0; + + i = pos; + while( i > 0 && (s[i] != ',' || + s[i-1] == '\\')) { + i--; + } + /* + * i == 0 || (s[i] == ',' && s[i-1] != '\\') + */ + if (i==0) { + /* Got all the way with no unescaped comma */ + if (s[i] == ',') { + comp_start = i+1; + } else { + comp_start = i; + } + } else { /* Found an unescaped comma */ + comp_start = i + 1; + } + + return( comp_start); +} + +/* + * returns the index in s of the first character after the + * first unescaped comma. + * If ther is no such character, returns strlen(s); +*/ + +int +acl_find_comp_end( char * s) { + + int i = 0; + int s_len = 0; + + s_len = strlen(s); + + if ( s_len == 0 || s_len == 1) { + return(s_len); + } + + /* inv: i+1<s_len && (s[i] == '\\' || s[i+1] != ',')*/ + + i = 0; + while( i+1 < s_len && (s[i] == '\\' || + s[i+1] != ',')) { + i++; + } + if ( i + 1 == s_len) { + return(s_len); + } else { + return(i+2); + } +} + +/* + * return the index in s where substr occurs, if none + * returns -1. +*/ + +int +acl_strstr(char * s, char *substr) { + + char *t = NULL; + char *tmp_str = NULL; + + tmp_str = slapi_ch_strdup(s); + + if ( (t = strstr(tmp_str, substr)) == NULL ) { + slapi_ch_free((void **)&tmp_str); + return(-1); + } else { + int l = 0; + *t = '\0'; + l = strlen(tmp_str); + slapi_ch_free((void **)&tmp_str); + return(l); + } +} + +/* + * replace all occurences of substr in s with replace_str. + * + * returns a malloced version of the patched string. +*/ + +char * +acl_replace_str(char * s, char *substr, char* replace_with_str) { + + char *str = NULL; + char *working_s, *suffix, *prefix, *patched; + int replace_with_len, substr_len, prefix_len, suffix_len; + + if ( (str = strstr(s, substr)) == NULL) { + return(slapi_ch_strdup(s)); + } else { + + + replace_with_len = strlen(replace_with_str); + substr_len = strlen(substr); + + working_s = slapi_ch_strdup(s); + prefix = working_s; + str = strstr(prefix, substr); + + while (str != NULL) { + + /* + * working_s is a copy of the string to be patched + * str points to a substr to be patched + * prefix points to working_s + */ + + *str = '\0'; + + suffix = &str[substr_len]; + prefix_len = strlen(prefix); + suffix_len = strlen(suffix); + + patched = (char *)slapi_ch_malloc(prefix_len + + replace_with_len + + suffix_len +1 ); + strcpy(patched, prefix); + strcat(patched, replace_with_str); + strcat(patched, suffix); + + slapi_ch_free((void **)&working_s); + + working_s = patched; + prefix = working_s; + str = strstr(prefix, substr); + + } + + return(working_s); + } + +} + + +/* + * Start at index and return a malloced string that is the + * next component in dn (eg. "ou=People"), + * or NULL if couldn't find the next one. +*/ + +char * +get_next_component(char *dn, int *index) { + + int dn_len = strlen(dn); + int start_next = -1; + int i = 0; + char *ret_comp; + + if (*index>= dn_len) { + return(NULL); + } + + start_next = acl_find_comp_end( &dn[*index]); + + if ( start_next >= dn_len ) { + *index = start_next; + return(NULL); /* no next comp */ + } + + /* + *Here, start_next should be the start of the next + * component--so far have not run off the end. + */ + + i = acl_find_comp_end( &dn[start_next]); + + /* + * Here, the matched string is all from start_next to i. + */ + + ret_comp = (char *)slapi_ch_malloc(i - start_next +1); + memcpy( ret_comp, &dn[start_next], i-start_next); + ret_comp[i-start_next] = '\0'; + + return(ret_comp); +} + +char * +get_this_component(char *dn, int *index) { + + int dn_len = strlen(dn); + int i = 0; + char *ret_comp; + + if (*index>= dn_len) { + return(NULL); + } + + if (dn_len == *index + 1) { + /* Just return a copy of the string. */ + return(slapi_ch_strdup(dn)); + }else { + /* *index + 1 < dn_len */ + i = *index+1; + while( (dn[i] != '\0') && dn[i] != ',' && dn[i-1] != '\\') { + i += 1; + } + + /* + * Here, the matched string is all from *index to i. + */ + + ret_comp = (char *)slapi_ch_malloc(i - *index +1); + memcpy( ret_comp, &dn[*index], i - *index); + ret_comp[i-*index] = '\0'; + + if (i < dn_len) { + /* Found a comma before the end */ + *index = i + 1; /* skip it */ + } + + return(ret_comp); + } + +} + +/* + * return 1 if comp1==comp2, + * return 0 otherwise. + * + * the components might have *'s. + * + * eg: comp1: cn=* + * comp2: cn=fred + * + * +*/ + +static int +aclutil_compare_components( char * comp1, char *comp2) { + + char *tmp_str = NULL; + + tmp_str = strstr( comp1, "=*"); + if ( tmp_str == NULL) { + + /* Just a straight cmp */ + + if (slapi_utf8casecmp((ACLUCHP)comp1, (ACLUCHP)comp2) == 0) { + return(1); + } else { + return(0); + } + } else { + + char *tmp_comp1= NULL; + char *tmp_comp2 = NULL; + int ret_code = 0; + + /* Here, just compare the bit before the = */ + + tmp_comp1 = slapi_ch_strdup(comp1); + tmp_comp2 = slapi_ch_strdup(comp2); + + /* + * Probably need to verify it's not escaped--see code for looking for + * unescaped commas. + */ + + tmp_str = strstr(tmp_comp1, "="); + *tmp_str = '\0'; + + tmp_str = strstr(tmp_comp2, "="); + if ( tmp_str == NULL) { + ret_code = 0; + } else{ + + *tmp_str = '\0'; + + if (slapi_utf8casecmp((ACLUCHP)comp1, (ACLUCHP)comp2) == 0) { + ret_code = 1; + } else { + ret_code = 0; + } + + slapi_ch_free((void **)&tmp_comp1); + slapi_ch_free((void **)&tmp_comp2); + + return(ret_code); + + } + + } +} + +/* + * return a pointer to the final component of macro_prefix. +*/ + +static char * +acl_get_final_component(char *macro_prefix) { + + return(NULL); +} + +/* + * + * +*/ + +static char * +acl_match_component( char *start, char *component) { + + + return(NULL); +} + +/* acl hash table funcs */ + +/* + * Add the key adn value to the ht. + * If it already exists then remove the old one and free + * the value. +*/ +void acl_ht_add_and_freeOld(acl_ht_t * acl_ht, + PLHashNumber key, + char *value){ + char *old_value = NULL; + + if ( (old_value = (char *)acl_ht_lookup( acl_ht, key)) != NULL ) { + acl_ht_remove( acl_ht, key); + slapi_ch_free((void **)&old_value); + } + + PL_HashTableAdd( acl_ht, (const void *)key, value); +} + +/* + * Return a new acl_ht_t * +*/ +acl_ht_t *acl_ht_new(void) { + + return(PL_NewHashTable(30, acl_ht_hash, /* key hasher */ + PL_CompareValues, /* keyCompare */ + PL_CompareStrings, 0, 0)); /* value compare */ +} + +static PLHashNumber acl_ht_hash( const void *key) { + + return( (PLHashNumber)key ); +} + +/* Free all the values in the ht */ +void acl_ht_free_all_entries_and_values( acl_ht_t *acl_ht) { + + PL_HashTableEnumerateEntries( acl_ht, acl_ht_free_entry_and_value, + NULL); +} + +static PRIntn +acl_ht_free_entry_and_value(PLHashEntry *he, PRIntn i, void *arg) +{ + + slapi_ch_free((void **)&he->value); /* free value */ + + /* Free this entry anfd go on to next one */ + return ( HT_ENUMERATE_NEXT | HT_ENUMERATE_REMOVE); +} + +/* Free all the values in the ht */ +void acl_ht_display_ht( acl_ht_t *acl_ht) { + +#ifdef DEBUG + PL_HashTableEnumerateEntries( acl_ht, acl_ht_display_entry, NULL); +#endif +} + +static PRIntn +acl_ht_display_entry(PLHashEntry *he, PRIntn i, void *arg) +{ + PLHashNumber aci_index = (PLHashNumber)he->key; + char *matched_val = (char *)he->value; + + LDAPDebug(LDAP_DEBUG_ACL,"macro ht entry: key='%d' matched_val='%s'" + "keyhash='%d'\n", + aci_index, (matched_val ? matched_val: "NULL"), + (PLHashNumber)he->keyHash); + + return HT_ENUMERATE_NEXT; + +} + +/* remove this entry from the ht--doesn't free the value.*/ +void acl_ht_remove( acl_ht_t *acl_ht, PLHashNumber key) { + + PL_HashTableRemove( acl_ht, (const void *)key); +} + +/* Retrieve a pointer to the value of the entry with key */ +void *acl_ht_lookup( acl_ht_t *acl_ht, + PLHashNumber key) { + + return( PL_HashTableLookup( acl_ht, (const void *)key) ); +} + + +/***************************************************************************/ +/* E N D */ +/***************************************************************************/ + diff --git a/ldap/servers/plugins/acl/libacl.def b/ldap/servers/plugins/acl/libacl.def new file mode 100644 index 00000000..947638cd --- /dev/null +++ b/ldap/servers/plugins/acl/libacl.def @@ -0,0 +1,16 @@ +; BEGIN COPYRIGHT BLOCK +; Copyright 2001 Sun Microsystems, Inc. +; Portions copyright 1999, 2001-2003 Netscape Communications Corporation. +; All rights reserved. +; END COPYRIGHT BLOCK +; +; +; +DESCRIPTION 'Netscape Directory Server 7.0 ACL Plugin' +;CODE SHARED READ EXECUTE +;DATA SHARED READ WRITE +EXPORTS + acl_preopInit @1 +; unused @2 + acl_init @3 + plugin_init_debug_level @4 |