/** BEGIN COPYRIGHT BLOCK * This Program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; version 2 of the License. * * This Program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. * * In addition, as a special exception, Red Hat, Inc. gives You the additional * right to link the code of this Program with code not covered under the GNU * General Public License ("Non-GPL Code") and to distribute linked combinations * including the two, subject to the limitations in this paragraph. Non-GPL Code * permitted under this exception must only link to the code of this Program * through those well defined interfaces identified in the file named EXCEPTION * found in the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline functions from * the Approved Interfaces without causing the resulting work to be covered by * the GNU General Public License. Only Red Hat, Inc. may make changes or * additions to the list of Approved Interfaces. You must obey the GNU General * Public License in all respects for all of the Program code and other code used * in conjunction with the Program except the Non-GPL Code covered by this * exception. If you modify this file, you may extend this exception to your * version of the file, but you are not obligated to do so. If you do not wish to * provide this exception without modification, you must delete this exception * statement from your version and license this file solely under the GPL without * exception. * * * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. * Copyright (C) 2005 Red Hat, Inc. * All rights reserved. * END COPYRIGHT BLOCK **/ #ifdef HAVE_CONFIG_H # include #endif /* Virtual Attributes This file implements the "virtual attribute API", which is the interface by which any joe servercode gets the values of attributes associated with an entry which are _not_ stored with the entry. Attributes which _are_ stored with the entry will also be returned by this interface, unless the caller specifies the SLAPI_REALATTRS_ONLY flag. This means that the casual caller looking for attribute values should probably call the virtual attribute interface rather than the regular Slapi_Entry_Attr_Find... and so on calls. This interface is different from that one in that the data returned is a copy, to be freed by the caller. Details on how to free the returned data is given individually for each function. The thing is implemented with a service provider model. The code in here does NOT know how to generate computed attr values etc, it just calls the registered providers of such information. It also takes care of any crusty special cases. Everything above the line here is crispy clean. Implicit in this interface is the assumption that the value of an attribute can only depend on the entry and some independent stored state. For example, the value can't depend upon who is asking, since we don't pass that information over the interface. More design: we'd like to have the regular result returning code in result.c call this interface. However, as it stands, his would incur a malloc, a copy and a free for every value. Too expensive. So, it would be good to modify the interface such that when we retrieve values from inside the entry, we just fetch a pointer like before. When we retrieve values which are generated, we get copies of the data (or can we get pointers there too ?? --- no). One way to achieve this: allow the caller to say that they perfer to receive pointers and not copies. They are then informed by the function whether they did receive pointers or copies. They then call the free function only when needed. The implicit lifetime of the returned pointers is the lifetime of the entry object passed into the function. Nasty, but one has to do these things in the name of the god performance. DBDB: remember to rename the structures mark complained weren't compliant with the slapi naming scheme. */ #include "slap.h" #include "vattr_spi.h" #include "statechange.h" #ifdef SOURCEFILE #undef SOURCEFILE #endif #define SOURCEFILE "vattr.c" static char *sourcefile = SOURCEFILE; /* Define only for module test code */ /* #define VATTR_TEST_CODE */ /* Loop context structure */ struct _vattr_context { Slapi_PBlock *pb; unsigned int vattr_context_loop_count; unsigned int error_displayed; }; #define VATTR_LOOP_COUNT_MAX 256 typedef vattr_sp_handle vattr_sp_handle_list; /* Local prototypes */ static int vattr_map_create(); static void vattr_map_destroy(); int vattr_map_sp_insert(char *type_to_add, vattr_sp_handle *sp, void *hint); vattr_sp_handle_list *vattr_map_sp_getlist(char *type_to_find); vattr_sp_handle_list *vattr_map_namespace_sp_getlist(Slapi_DN *dn, const char *type_to_find); vattr_sp_handle_list *vattr_map_sp_get_complete_list(); vattr_sp_handle *vattr_map_sp_first(vattr_sp_handle_list *list,void **hint); vattr_sp_handle *vattr_map_sp_next(vattr_sp_handle_list *list,void **hint); vattr_sp_handle *vattr_list_sp_first(vattr_sp_handle_list *list); vattr_sp_handle *vattr_list_sp_next(vattr_sp_handle_list *list); int vattr_call_sp_get_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *buffer_flags, void *hint); int vattr_call_sp_get_batch_values(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, char **type, Slapi_ValueSet*** results,int **type_name_disposition, char*** actual_type_name, int flags, int *buffer_flags, void** hint); int vattr_call_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, const char *type, Slapi_Value* test_this,int *result, int flags, void* hint); int vattr_call_sp_get_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags); void schema_changed_callback(Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data); int slapi_vattrspi_register_internal(vattr_sp_handle **h, vattr_get_fn_type get_fn, vattr_get_ex_fn_type get_ex_fn, vattr_compare_fn_type compare_fn, vattr_types_fn_type types_fn, void *options); #ifdef VATTR_TEST_CODE int vattr_basic_sp_init(); #endif void **statechange_api; /* Housekeeping Functions, called by server startup/shutdown code */ /* Called on server startup, init all structures etc */ void vattr_init() { statechange_api = 0; vattr_map_create(); #ifdef VATTR_TEST_CODE vattr_basic_sp_init(); #endif } /* Called on server shutdown, free all structures, inform service providers that we're going down etc */ void vattr_cleanup() { vattr_map_destroy(); } /* The public interface functions start here */ /* Function which returns the value(s) of an attribute, given an entry and the attribute type name */ /* Return 0 if OK, ?? if attr doesn't exist, ?? if some error condition ?? if result doesn't need to be free'ed */ int slapi_vattr_values_get(/* Entry we're interested in */ Slapi_Entry *e, /* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *buffer_flags) { vattr_context *c = NULL; return slapi_vattr_values_get_sp(c,e,type,results,type_name_disposition,actual_type_name,flags, buffer_flags); } int slapi_vattr_values_get_ex(/* Entry we're interested in */ Slapi_Entry *e,/* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet*** results,int **type_name_disposition, char*** actual_type_name, int flags, int *buffer_flags, int *subtype_count) { vattr_context *c = NULL; return slapi_vattr_values_get_sp_ex(c,e,type,results,type_name_disposition,actual_type_name,flags, buffer_flags, subtype_count); } int slapi_vattr_namespace_values_get(/* Entry we're interested in */ Slapi_Entry *e, /* backend namespace dn*/ Slapi_DN *namespace_dn, /* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet*** results,int **type_name_disposition, char*** actual_type_name, int flags, int *buffer_flags, int *subtype_count) { vattr_context *c = NULL; return slapi_vattr_namespace_values_get_sp(c,e,namespace_dn,type,results,type_name_disposition,actual_type_name,flags, buffer_flags, subtype_count); } /* * If pointers into the entry were requested then we might have * a stashed pointer to the entry values, otherwise will be null. */ Slapi_ValueSet *vattr_typethang_get_values(vattr_type_thang *t) { return t->type_values; } /* * This can be much faster than using slapi_vattr_values_get() * when you have a vattr_type_thang list returned from slapi_vattr_list_types(). * * We call the vattr SPs to get values for an attribute type in the list only * if the results field for that attribute type is null. * If the type list comes from slapi_vattr_list_types() then the value is null * only for attributes for which an SP wishes to provide a value, so fo the other * ones don't bother calling the SPs again--just use the real value we picked up * from the entry. * */ int slapi_vattr_values_type_thang_get( Slapi_Entry *e, vattr_type_thang *type_thang, Slapi_ValueSet **results, int *type_name_disposition, char **actual_type_name, int flags, int *buffer_flags ) { int rc = 0; char *type = NULL; type = vattr_typethang_get_name(type_thang); *results = vattr_typethang_get_values(type_thang); if (*results != NULL) { /* we already have a pointer directly into the entry */ *actual_type_name = type; *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS; *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_POINTERS; } else { /* fall back to usual implementation */ rc = slapi_vattr_values_get(e,type,results, type_name_disposition, actual_type_name, flags,buffer_flags); } return rc; } static void vattr_helper_get_entry_conts(Slapi_Entry *e,char *type, vattr_get_thang *my_get) { Slapi_Attr *a = NULL; void *dummy = 0; a = attrlist_find_ex(e->e_attrs,type,&(my_get->get_name_disposition), &(my_get->get_type_name), &dummy); if (a) { my_get->get_present = 1; my_get->get_present_values = &(a->a_present_values); my_get->get_attr = a; } } static int vattr_helper_get_entry_conts_with_subtypes(Slapi_Entry *e,const char *type, vattr_get_thang **my_get) { Slapi_Attr *a = NULL; void *hint = 0; int counter = 0; int attr_count = attrlist_count_subtypes(e->e_attrs,type); if(attr_count > 0) { *my_get = (vattr_get_thang *)slapi_ch_calloc(attr_count, sizeof(vattr_get_thang)); /* pick up attributes with sub-types and slip into the get_thang list */ for(counter = 0; counter < attr_count; counter++) { a = attrlist_find_ex(e->e_attrs,type,&((*my_get)[counter].get_name_disposition), &((*my_get)[counter].get_type_name), &hint); if (a) { (*my_get)[counter].get_present = 1; (*my_get)[counter].get_present_values = &(a->a_present_values); (*my_get)[counter].get_attr = a; } } } return attr_count; } static int vattr_helper_get_entry_conts_no_subtypes(Slapi_Entry *e,const char *type, vattr_get_thang **my_get) { int attr_count = 0; Slapi_Attr *a = attrlist_find(e->e_attrs,type); if (a) { attr_count = 1; *my_get = (vattr_get_thang *)slapi_ch_calloc(1, sizeof(vattr_get_thang)); (*my_get)[0].get_present = 1; (*my_get)[0].get_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS; (*my_get)[0].get_type_name = a->a_type; (*my_get)[0].get_present_values = &(a->a_present_values); (*my_get)[0].get_attr = a; } return attr_count; } static int vattr_helper_get_entry_conts_ex(Slapi_Entry *e,const char *type, vattr_get_thang **my_get, int suppress_subtypes) { int rc; if ( suppress_subtypes ) { rc = vattr_helper_get_entry_conts_no_subtypes(e, type, my_get); } else { rc = vattr_helper_get_entry_conts_with_subtypes(e, type, my_get); } return rc; } vattr_context *vattr_context_new( Slapi_PBlock *pb ) { vattr_context *c = NULL; if (pb && pb->pb_vattr_context) { c = (vattr_context *)pb->pb_vattr_context; } else { c = (vattr_context *)slapi_ch_calloc(1, sizeof(vattr_context)); } /* The payload is zero, which is what we want */ if ( c ) { c->pb = pb; } if ( pb && c != (vattr_context *)pb->pb_vattr_context ) { pb->pb_vattr_context = (void *)c; } return c; } static int vattr_context_check(vattr_context *c) { /* Observe the loop count and see if it's higher than is allowed */ if (c->vattr_context_loop_count > VATTR_LOOP_COUNT_MAX) { return SLAPI_VIRTUALATTRS_LOOP_DETECTED; } else return 0; } static void vattr_context_mark(vattr_context *c) { c->vattr_context_loop_count += 1; } static int vattr_context_unmark(vattr_context *c) { return (c->vattr_context_loop_count -= 1); } /* modify the context structure on exit from a vattr sp function */ static void vattr_context_ungrok(vattr_context **c) { /* Decrement the loop count */ if (0 == vattr_context_unmark(*c)) { /* If necessary, delete the structure */ if ((*c)->pb) { (*c)->pb->pb_vattr_context = NULL; } slapi_ch_free((void **)c); } } static int vattr_context_grok_pb( Slapi_PBlock *pb, vattr_context **c ) { int rc = -1; if (NULL == c) { return rc; } *c = vattr_context_new( pb ); if (NULL == *c) { return ENOMEM; } rc = vattr_context_check(*c); vattr_context_mark(*c); /* increment loop count */ return rc; } /* Check and mess with the context structure on entry to a vattr sp function */ static int vattr_context_grok(vattr_context **c) { int rc = 0; /* First check that we've not got into an infinite loop. We do so by means of the vattr_context structure. */ /* Do we have a context at all ?? */ if (NULL == *c) { /* No, so let's make one */ *c = vattr_context_new( NULL ); if (NULL == *c) { return ENOMEM; } } else { /* Yes, so let's check its contents */ rc = vattr_context_check(*c); } /* mark the context as having been used once */ vattr_context_mark(*c); return rc; } /* keep track of error messages so we don't spam the error log */ static void vattr_context_set_loop_msg_displayed(vattr_context **c) { (*c)->error_displayed = 1; } static int vattr_context_is_loop_msg_displayed(vattr_context **c) { return (*c)->error_displayed; } /* * vattr_test_filter: * * . tests an ava, presence or substring filter against e. * . group these filter types together to avoid having to duplicate the * . vattr specific code in three seperate routines. * . handles virtual attrs in the filter. * . does update the vattrcache if a call to this calculates vattrs * * returns: 0 filter matched * -1 filter did not match * >0 an ldap error code * */ int vattr_test_filter( Slapi_PBlock *pb, /* Entry we're interested in */ Slapi_Entry *e, Slapi_Filter *f, filter_type_t filter_type, char * type) { int rc = -1; int sp_bit = 0; /* Set if an SP supplied an answer */ vattr_sp_handle_list *list = NULL; Slapi_DN *sdn; Slapi_Backend *be; Slapi_DN *namespace_dn; /* get the namespace this entry belongs to */ sdn = slapi_entry_get_sdn( e ); be = slapi_be_select( sdn ); namespace_dn = (Slapi_DN*)slapi_be_getsuffix(be, 0); /* Look for attribute in the map */ if(namespace_dn) { list = vattr_map_namespace_sp_getlist(namespace_dn, type); } else { list = vattr_map_namespace_sp_getlist(NULL, type); } if (list) { vattr_sp_handle *current_handle = NULL; /* first lets consult the cache to save work */ int cache_status; cache_status = slapi_entry_vattrcache_findAndTest(e, type, f, filter_type, &rc); switch(cache_status) { case SLAPI_ENTRY_VATTR_RESOLVED_EXISTS: /* cached vattr */ { sp_bit = 1; break; } case SLAPI_ENTRY_VATTR_RESOLVED_ABSENT: /* does not exist */ break; /* look in entry */ case SLAPI_ENTRY_VATTR_NOT_RESOLVED: /* not resolved */ default: /* any other result, resolve */ { int flags = SLAPI_VIRTUALATTRS_REQUEST_POINTERS; void *hint = NULL; Slapi_ValueSet **results = NULL; /* pointer to result set */ int *type_name_disposition; char **actual_type_name; int buffer_flags; vattr_get_thang my_get = {0}; /* bit cacky, but need to make a null terminated lists for now * for the (unimplemented and so fake) batch attribute request */ char *types[2]; void *hint_list[2]; vattr_context *ctx; vattr_context_grok_pb( pb, &ctx ); /* get or new context */ types[0] = type; types[1] = 0; hint_list[1] = 0; for (current_handle = vattr_map_sp_first(list,&hint); current_handle; current_handle = vattr_map_sp_next(current_handle,&hint)) { hint_list[0] = hint; rc = vattr_call_sp_get_batch_values(current_handle,ctx,e, &my_get,types,&results,&type_name_disposition, &actual_type_name,flags,&buffer_flags, hint_list); if (0 == rc) { sp_bit = 1; break; } } vattr_context_ungrok(&ctx); if(!sp_bit) { /* * No vattr sp supplied an answer so will look in the * entry itself. * but first lets cache the no result */ slapi_entry_vattrcache_merge_sv(e, type, NULL ); } else { /* * A vattr sp supplied an answer. * so turn the value into a Slapi_Attr, pass * to the syntax plugin for comparison. */ if ( filter_type == FILTER_TYPE_AVA || filter_type == FILTER_TYPE_SUBSTRING ) { Slapi_Attr *a = NULL; int i=0; /* may need this when we have a passin interface for set_valueset */ /*Slapi_ValueSet null_valueset = {0};*/ rc=-1; if(results && actual_type_name && type_name_disposition) { while(results[i] && rc) { a = slapi_attr_new(); slapi_attr_init(a, type); /* a now contains a *copy* of results */ slapi_attr_set_valueset( a, results[i]); if ( filter_type == FILTER_TYPE_AVA ) { rc = plugin_call_syntax_filter_ava( a, f->f_choice, &f->f_ava ); } else if ( filter_type == FILTER_TYPE_SUBSTRING) { rc = plugin_call_syntax_filter_sub( pb, a, &f->f_sub); } /* * Cache stuff: dups results */ slapi_entry_vattrcache_merge_sv(e, actual_type_name[i], results[i] ); /* * Free stuff, just in case we did not * get pointers. */ slapi_vattr_values_free( &(results[i]), &(actual_type_name[i]), buffer_flags); /* may need this when we support a passin set_valueset */ /*slapi_attr_set_valueset( a, &null_valueset);*/ /* since a contains a copy of results, we must free it */ slapi_attr_free(&a); i++; } } slapi_ch_free((void**)&results); slapi_ch_free((void**)&actual_type_name); slapi_ch_free((void**)&type_name_disposition); } else if ( filter_type == FILTER_TYPE_PRES ) { /* * Cache stuff: dups results */ int i=0; while(results[i]) { slapi_entry_vattrcache_merge_sv(e, actual_type_name[i], results[i] ); /* * Free stuff, just in case we did not * get pointers. */ slapi_vattr_values_free( &results[i], &actual_type_name[i], buffer_flags); } slapi_ch_free((void**)&results); slapi_ch_free((void**)&actual_type_name); slapi_ch_free((void**)&type_name_disposition); } } break; } }/* switch */ } /* If no SP supplied the answer, take it from the entry */ if (rc <= 1 && !sp_bit) /* if LDAP ERROR is set, skip further testing */ { int acl_test_done; if ( filter_type == FILTER_TYPE_AVA ) { rc = test_ava_filter( NULL /* pb not needed */, e, e->e_attrs, &f->f_ava, f->f_choice, 0 /* no access check */, 0 /* do test filter */, &acl_test_done); } else if ( filter_type == FILTER_TYPE_SUBSTRING ) { rc = test_substring_filter( pb, e, f, 0 /* no access check */, 0 /* do test filter */, &acl_test_done); } else if ( filter_type == FILTER_TYPE_PRES ) { rc = test_presence_filter( NULL, e, f->f_type, 0 /* no access check */, 0 /* do test filter */, &acl_test_done); } } return rc; } /* * deprecated in favour of slapi_vattr_values_get_sp_ex() which * returns subtypes too. */ SLAPI_DEPRECATED int slapi_vattr_values_get_sp(vattr_context *c, /* Entry we're interested in */ Slapi_Entry *e, /* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet** results, int *type_name_disposition, char** actual_type_name, int flags, int *buffer_flags) { PRBool use_local_ctx = PR_FALSE; Slapi_PBlock *local_pb = NULL; vattr_context *ctx = NULL; int rc = 0; int sp_bit = 0; /* Set if an SP supplied an answer */ vattr_sp_handle_list *list = NULL; vattr_get_thang my_get = {0}; if (c != NULL) { rc = vattr_context_grok(&c); if (0 != rc) { if(!vattr_context_is_loop_msg_displayed(&c)) { /* Print a handy error log message */ LDAPDebug(LDAP_DEBUG_ANY, "Detected virtual attribute loop in get on entry %s, attribute %s\n", slapi_entry_get_dn_const(e), type, 0); vattr_context_set_loop_msg_displayed(&c); } return rc; } ctx = c; } else { use_local_ctx = PR_TRUE; local_pb = slapi_pblock_new(); ctx = vattr_context_new( local_pb ); ctx->vattr_context_loop_count = 1; ctx->error_displayed = 0; } /* For attributes which are in the entry, we just need to get to the Slapi_Attr structure and yank out the slapi_value_set structure. We either return a pointer directly to it, or we copy it, depending upon whether the caller asked us to try to avoid copying. */ /* First grok the entry, and remember what we saw. This call does no more than walk down the entry attribute list, do some string compares and copy pointers. */ vattr_helper_get_entry_conts(e,type, &my_get); /* Having done that, we now consult the attribute map to find service providers who are interested */ /* Look for attribute in the map */ if(!(flags & SLAPI_REALATTRS_ONLY)) { list = vattr_map_sp_getlist(type); if (list) { vattr_sp_handle *current_handle = NULL; void *hint = NULL; /* first lets consult the cache to save work */ int cache_status; cache_status = slapi_entry_vattrcache_find_values_and_type(e, type, results, actual_type_name); switch(cache_status) { case SLAPI_ENTRY_VATTR_RESOLVED_EXISTS: /* cached vattr */ { sp_bit = 1; /* Complete analysis of type matching */ if ( 0 == slapi_attr_type_cmp( type , *actual_type_name, SLAPI_TYPE_CMP_EXACT) ) { *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS; } else { *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_SUBTYPE; } break; } case SLAPI_ENTRY_VATTR_RESOLVED_ABSENT: /* does not exist */ break; /* look in entry */ case SLAPI_ENTRY_VATTR_NOT_RESOLVED: /* not resolved */ default: /* any other result, resolve */ { for (current_handle = vattr_map_sp_first(list,&hint); current_handle; current_handle = vattr_map_sp_next(current_handle,&hint)) { rc = vattr_call_sp_get_value(current_handle,ctx,e,&my_get,type,results,type_name_disposition,actual_type_name,flags,buffer_flags, hint); if (0 == rc) { sp_bit = 1; break; } } if(!sp_bit) { /* clean up, we have failed and must now examine the * entry itself * But first lets cache the no result * Creates the type (if necessary). */ slapi_entry_vattrcache_merge_sv(e, type, NULL ); } else { /* * we need to cache the virtual attribute * creates the type (if necessary) and dups * results. */ slapi_entry_vattrcache_merge_sv(e, *actual_type_name, *results ); } break; } } } } /* If no SP supplied the answer, take it from the entry */ if (!sp_bit && !(flags & SLAPI_VIRTUALATTRS_ONLY)) { rc = 0; /* reset return code (cause an sp must have failed) */ *type_name_disposition = my_get.get_name_disposition; if (my_get.get_present) { if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) { *results = my_get.get_present_values; *actual_type_name = my_get.get_type_name; } else { *results = valueset_dup(my_get.get_present_values); if (NULL == *results) { rc = ENOMEM; } else { *actual_type_name = slapi_ch_strdup(my_get.get_type_name); if (NULL == *actual_type_name) { rc = ENOMEM; } } } if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) { *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_POINTERS; } else { *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES; } } else { rc = SLAPI_VIRTUALATTRS_NOT_FOUND; } } if (use_local_ctx) { /* slapi_pblock_destroy cleans up pb_vattr_context, as well */ slapi_pblock_destroy(local_pb); } else { vattr_context_ungrok(&c); } return rc; } /* * * returns 0: (no_error && type found ) in which case: * results: contains the current values for type and * all it's subtypes in e * type_name_disposition: how each type was matched * SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS * SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_SUBTYPE * actual_type_name: type name as found * flags: bit mask of options: * SLAPI_REALATTRS_ONLY * SLAPI_VIRTUALATTRS_ONLY * SLAPI_VIRTUALATTRS_REQUEST_POINTERS * SLAPI_VIRTUALATTRS_LIST_OPERATIONAL_ATTRS * buffer_flags: bit mask to be used as input flags for * slapi_values_free() * SLAPI_VIRTUALATTRS_RETURNED_POINTERS * SLAPI_VIRTUALATTRS_RETURNED_COPIES * SLAPI_VIRTUALATTRS_REALATTRS_ONLY * item_count: number of subtypes matched * otherwise: * SLAPI_VIRTUALATTRS_LOOP_DETECTED (failed to eval a vattr) * SLAPI_VIRTUALATTRS_NOT_FOUND (type not recognised by any vattr * sp && not a real attr in entry ) * ENOMEM (memory error) * * Note: * . modifes the virtual cache in the entry. * . for cached vattrs you always get a copy, so it will need to be * freed via slapi_values_free(). * */ int slapi_vattr_values_get_sp_ex(vattr_context *c, /* Entry we're interested in */ Slapi_Entry *e, /* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet*** results, int **type_name_disposition, char*** actual_type_name, int flags, int *buffer_flags, int *item_count) { return slapi_vattr_namespace_values_get_sp( c, e, NULL, type, results, type_name_disposition, actual_type_name, flags, buffer_flags, item_count ); } int slapi_vattr_namespace_values_get_sp(vattr_context *c, /* Entry we're interested in */ Slapi_Entry *e, /* DN denoting backend namespace */ Slapi_DN *namespace_dn, /* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet*** results, int **type_name_disposition, char*** actual_type_name, int flags, int *buffer_flags, int *item_count) { int rc = 0; int sp_bit = 0; /* Set if an SP supplied an answer */ vattr_sp_handle_list *list = NULL; vattr_get_thang *my_get = NULL; int attr_count = 0; rc = vattr_context_grok(&c); if (0 != rc) { /* Print a handy error log message */ if(!vattr_context_is_loop_msg_displayed(&c)) { LDAPDebug(LDAP_DEBUG_ANY,"Detected virtual attribute loop in get on entry %s, attribute %s\n", slapi_entry_get_dn_const(e), type, 0); vattr_context_set_loop_msg_displayed(&c); } return rc; } /* Having done that, we now consult the attribute map to find service providers who are interested */ /* Look for attribute in the map */ if(!(flags & SLAPI_REALATTRS_ONLY)) { /* we use the vattr namespace aware version of this */ list = vattr_map_namespace_sp_getlist(namespace_dn, type); if (list) { vattr_sp_handle *current_handle = NULL; void *hint = NULL; /* first lets consult the cache to save work */ int cache_status; cache_status = slapi_entry_vattrcache_find_values_and_type_ex(e, type, results, actual_type_name); switch(cache_status) { case SLAPI_ENTRY_VATTR_RESOLVED_EXISTS: /* cached vattr */ { sp_bit = 1; /* Complete analysis of type matching */ *type_name_disposition = (int *)slapi_ch_malloc(sizeof(**type_name_disposition)); if ( 0 == slapi_attr_type_cmp( type , **actual_type_name, SLAPI_TYPE_CMP_EXACT) ) { **type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS; } else { **type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_SUBTYPE; } *item_count = 1; break; } case SLAPI_ENTRY_VATTR_RESOLVED_ABSENT: /* does not exist */ break; /* look in entry */ case SLAPI_ENTRY_VATTR_NOT_RESOLVED: /* not resolved */ default: /* any other result, resolve */ { /* bit cacky, but need to make a null terminated lists for now * for the (unimplemented and so fake) batch attribute request */ char **type_list = (char**)slapi_ch_calloc(2,sizeof(char*)); void **hint_list = (void**)slapi_ch_calloc(2,sizeof(void*)); type_list[0] = type; for (current_handle = vattr_map_sp_first(list,&hint); current_handle; current_handle = vattr_map_sp_next(current_handle,&hint)) { /* call this SP */ hint_list[0] = hint; rc = vattr_call_sp_get_batch_values(current_handle,c,e,my_get, type_list,results,type_name_disposition, actual_type_name, flags,buffer_flags, hint_list); if (0 == rc) { sp_bit = 1; /* this sp provided an answer, we are done */ /* count the items in the (null terminated) result list */ *item_count = 0; while((*results)[*item_count]) { (*item_count)++; } break; } } slapi_ch_free((void**)&type_list); slapi_ch_free((void**)&hint_list); if(!sp_bit) { /* we have failed and must now examine the entry itself * * But first lets cache the no result * dups the type (if necessary). */ slapi_entry_vattrcache_merge_sv(e, type, NULL ); } else { /* * we need to cache the virtual attribute * dups the type (if necessary) and results. */ /* * this (and above) will of course need to get updated * when we do real batched attributes */ slapi_entry_vattrcache_merge_sv(e, **actual_type_name, **results ); } } } } } /* If no SP supplied the answer, take it from the entry */ if (!sp_bit && !(flags & SLAPI_VIRTUALATTRS_ONLY)) { int counter; /* First grok the entry - allocates memory for list */ attr_count = vattr_helper_get_entry_conts_ex(e,type, &my_get, (0 != (flags & SLAPI_VIRTUALATTRS_SUPPRESS_SUBTYPES))); *item_count = attr_count; rc = 0; /* reset return code (cause an sp must have failed) */ if(attr_count > 0) { *results = (Slapi_ValueSet**)slapi_ch_calloc(1, sizeof(*results) * attr_count); *type_name_disposition = (int *)slapi_ch_malloc(sizeof(**type_name_disposition) * attr_count); *actual_type_name = (char**)slapi_ch_malloc(sizeof(*actual_type_name) * attr_count); /* For attributes which are in the entry, we just need to get to the Slapi_Attr structure and yank out the slapi_value_set structure. We either return a pointer directly to it, or we copy it, depending upon whether the caller asked us to try to avoid copying. */ for(counter = 0; counter < attr_count; counter++) { (*type_name_disposition)[counter] = my_get[counter].get_name_disposition; if (my_get[counter].get_present) { if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) { /* pointers will do, good */ (*results)[counter] = my_get[counter].get_present_values; (*actual_type_name)[counter] = my_get[counter].get_type_name; } else { /* need to copy the values */ (*results)[counter] = valueset_dup(my_get[counter].get_present_values); if (NULL == (*results)[counter]) { rc = ENOMEM; } else { (*actual_type_name)[counter] = slapi_ch_strdup(my_get[counter].get_type_name); if (NULL == (*actual_type_name)[counter]) { rc = ENOMEM; } } } if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) { *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_POINTERS; } else { *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES; } } else { rc = SLAPI_VIRTUALATTRS_NOT_FOUND; } } slapi_ch_free((void **)&my_get); } } vattr_context_ungrok(&c); return rc; } /* Do we need a call to free the results from the above ? */ void slapi_vattr_values_free(Slapi_ValueSet **value, char** actual_type_name, int flags) { /* Check whether we need to free the strings */ if (flags & SLAPI_VIRTUALATTRS_RETURNED_POINTERS) { /* We don't need to free the values */ } else { /* Free the valueset */ if (*value) { slapi_valueset_free(*value); } if (*actual_type_name) { slapi_ch_free((void **)actual_type_name); } } *actual_type_name = NULL; *value = NULL; } /* Function like the one above but doing a compare operation */ /* Same return codes as above. Compare result value returned in "result": 0 if compare true, 1 if compare false */ int slapi_vattr_value_compare(Slapi_Entry *e, char *type, Slapi_Value *test_this, int *result, int flags) { return slapi_vattr_namespace_value_compare_sp(NULL,e,NULL,type,test_this,result,flags); } /* namespace version of above */ int slapi_vattr_namespace_value_compare(Slapi_Entry *e, Slapi_DN *namespace_dn, const char *type, Slapi_Value *test_this, int *result, int flags) { return slapi_vattr_namespace_value_compare_sp(NULL,e,namespace_dn,type,test_this,result,flags); } int slapi_vattr_value_compare_sp(vattr_context *c,/* Entry we're interested in */ Slapi_Entry *e,/* attr type name */ char *type, Slapi_Value *test_this,/* pointer to result */ int *result, int flags) { return slapi_vattr_namespace_value_compare_sp(c,e,NULL,type,test_this,result,flags); } int slapi_vattr_namespace_value_compare_sp(vattr_context *c,/* Entry we're interested in */ Slapi_Entry *e, /* backend namespace dn*/Slapi_DN *namespace_dn, /* attr type name */ const char *type, Slapi_Value *test_this,/* pointer to result */ int *result, int flags) { int rc = 0; int sp_bit = 0; /* Set if an SP supplied an answer */ vattr_sp_handle_list *list = NULL; vattr_get_thang *my_get = 0; *result = 0; /* return "compare false" by default */ rc = vattr_context_grok(&c); if (0 != rc) { /* Print a handy error log message */ LDAPDebug(LDAP_DEBUG_ANY,"Detected virtual attribute loop in compare on entry %s, attribute %s\n", slapi_entry_get_dn_const(e), type, 0); return rc; } /* Having done that, we now consult the attribute map to find service providers who are interested */ /* Look for attribute in the map */ list = vattr_map_namespace_sp_getlist(namespace_dn, type); if (list) { vattr_sp_handle *current_handle = NULL; void *hint = NULL; for (current_handle = vattr_map_sp_first(list,&hint); current_handle; current_handle = vattr_map_sp_next(list,&hint)) { /* call this SP */ rc = vattr_call_sp_compare_value(current_handle,c,e,my_get,type,test_this,result,flags, hint); if (0 == rc) { sp_bit = 1; break; } } } /* If no SP supplied the answer, take it from the entry */ if (!sp_bit) { /* Grok the entry, and remember what we saw. This call does no more than walk down the entry attribute list, do some string compares and copy pointers. */ int attr_count = vattr_helper_get_entry_conts_ex(e,type, &my_get, (0 != (flags & SLAPI_VIRTUALATTRS_SUPPRESS_SUBTYPES))); if (my_get && my_get->get_present) { int i; Slapi_Value *Dummy_value = NULL; /* Put the required stuff in the fake attr */ for(i=0;itypes; /* Make a new list */ new_list = (vattr_type_thang*)slapi_ch_calloc(thang->block_length, sizeof(vattr_type_thang)); if (NULL == new_list) { return ENOMEM; } /* Walk the list strdup'ing the type name and copying the rest of the structure contents */ for (index = 0; index < thang->list_length; index++) { new_list[index].type_flags = old_list[index].type_flags; new_list[index].type_name = slapi_ch_strdup(old_list[index].type_name); /* * list_is_copies does not affect type_values as it * is always a pointer never a copy. */ new_list[index].type_values = old_list[index].type_values; } /* Mark it a copy list */ thang->list_is_copies = 1; /* Free the original list */ slapi_ch_free((void **)&(thang->types)); /* swap lists */ thang->types = new_list; return 0; } /* * Returns all the attribute types from e, both real and virtual. * * Any of the attributes found in the entry to start with, for * whom no vattr SP volunteered, will also have a pointer to the * original valueset in the entry--this fact can be used by * calling slapi_vattr_values_type_thang_get(), which will * take the values present in the vattr_type_thang list * rather than calling slapi_vattr_values_get() to retrieve the value. */ #define TYPE_LIST_EXTRA_SPACE 5 /* Number of extra slots we allow for SP's to insert types */ int slapi_vattr_list_attrs(/* Entry we're interested in */ Slapi_Entry *e, /* pointer to receive the list */ vattr_type_thang **types, int flags, int *buffer_flags) { int rc = 0; size_t attr_count = 0; vattr_type_thang *result_array = NULL ; Slapi_Attr *current_attr = NULL; size_t i = 0; int list_is_copies = 0; vattr_sp_handle_list *list = NULL; size_t list_length = 0; size_t block_length = 0; vattr_type_list_context type_context = {0}; if (NULL == types || NULL == buffer_flags) { LDAPDebug(LDAP_DEBUG_ANY, "slapi_vattr_list_attrs: invalid param\n", 0, 0, 0); return -1; } block_length = 1 + TYPE_LIST_EXTRA_SPACE; if(!(flags & SLAPI_VIRTUALATTRS_ONLY)) { /* First find what's in the entry itself*/ /* Count the attributes */ for (current_attr = e->e_attrs; current_attr != NULL; current_attr = current_attr->a_next, attr_count++) ; block_length += attr_count; /* Allocate the pointer array */ result_array = (vattr_type_thang*)slapi_ch_calloc(block_length,sizeof(vattr_type_thang)); if (NULL == result_array) { return ENOMEM; } list_length = attr_count; /* Now copy the pointers into the array */ i = 0; for (current_attr = e->e_attrs; current_attr != NULL; current_attr = current_attr->a_next) { char *mypointer = NULL; if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) { mypointer = current_attr->a_type; result_array[i].type_values = &(current_attr->a_present_values); } else { mypointer = slapi_ch_strdup(current_attr->a_type); list_is_copies = 1; } result_array[i].type_flags = current_attr->a_flags; result_array[i++].type_name = mypointer; } PR_ASSERT(i == attr_count); } /* if we didnt send real attrs we need to allocate result array */ if(NULL == result_array) { result_array = (vattr_type_thang*)slapi_ch_calloc(block_length,sizeof(vattr_type_thang)); if (NULL == result_array) { return ENOMEM; } } /* Then ask the service providers for their input */ type_context.flags = flags; type_context.realattrs_only = 1; /* until we know otherwise */ type_context.list_is_copies = list_is_copies; type_context.types = result_array; type_context.list_length = list_length; type_context.block_length = block_length; /* * At this point type_context.types is a list of all * the attributetypes (copies or pointers) and values (copies, if present) * found in the entry. */ if(!(flags & SLAPI_REALATTRS_ONLY)) { list = vattr_map_sp_get_complete_list(); if (list) { vattr_sp_handle *current_handle = NULL; for (current_handle = vattr_list_sp_first(list); current_handle; current_handle = vattr_list_sp_next((vattr_sp_handle_list *)current_handle)) { /* call this SP */ rc = vattr_call_sp_get_types(current_handle,e,&type_context, flags); if (0 != rc) { /* DBDB do what on error condition ? */ } } /* assert enough space for the null terminator */ PR_ASSERT( type_context.list_length < type_context.block_length); i = type_context.list_length; } } /* * Now type_context.types is a list of all the types in this entry-- * real and virtual. For real ones, the values field is filled in. * For virtual ones (including virtual ones which will overwrite a real * value) the values field is null--this fact may used * subsequently by callers of slapi_vattr_list_attrs() * by calling slapi_vattr_values_type_thang_get() which * will only calculate the values for attributetypes with * non-null values. */ flags = type_context.flags; list_is_copies = type_context.list_is_copies; result_array = type_context.types; list_length = type_context.list_length; block_length = type_context.block_length; if (list_is_copies) { *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES; } else { *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_POINTERS; } /* Let the caller know if the list contains only real attrs */ if (type_context.realattrs_only) { *buffer_flags |= SLAPI_VIRTUALATTRS_REALATTRS_ONLY; } result_array[i].type_name = NULL; if (list_length) *types = result_array; else *types = 0; return rc; } static int vattr_add_thang_to_list(vattr_type_list_context *c, vattr_type_thang *thang) { vattr_type_thang *new_list = NULL; /* Will it fit in the current block ? */ if (c->list_length == c->block_length - 1) { c->block_length *= 2; new_list = (vattr_type_thang*)slapi_ch_realloc((char *)c->types, c->block_length * (int)sizeof(vattr_type_thang)); if (NULL == new_list) { return ENOMEM; } c->types = new_list; } /* Add to the end of the list */ c->types[c->list_length ] = *thang; c->list_length += 1; return 0; } /* Called by SP's during their get_types call, to have the server add a * type to the type list. * * c: the vattr_type_list_context passed from the original caller of * slapi_vattr_list_attrs() * thang: the new type to be added to the type list. * flags: tells the routine whether to copy the thang or not. * SLAPI_VIRTUALATTRS_REQUEST_POINTERS--no need to copy it. * !(SLAPI_VIRTUALATTRS_REQUEST_POINTERS)--need to copy it. * * Checks to see if the requested type is already in the list, * and if it is, if the value of the attribute is * non-null, it resets the value to null. This means that when used * subsequently, the type list indicates whether, for a given attribute type, * the SP's need to be called to retrieve the value. * */ int slapi_vattrspi_add_type(vattr_type_list_context *c, vattr_type_thang *thang, int flags) { int rc = 0; unsigned int index = 0; vattr_type_thang thang_to_add = {0}; int found_it = 0; PR_ASSERT(c); /* We are no longer sure that the list contains only real attrs */ c->realattrs_only = 0; /* Check to see if the type is in the list already */ for (index = 0; index < c->list_length; index++) { if (0 == slapi_UTF8CASECMP(c->types[index].type_name,thang->type_name)) { found_it = 1; break; } } /* * If found and there are values specified in the vattr_type_thang, then * that means it's a real attribute--because SP's do not add values * when called via slapi_vattr_list_attrs()/vattr_call_sp_get_types(). * However, an SP is willing to claim responsibility for this attribute * type, so to ensure that that virtual value will get calculated, set the * original real value to NULL here. * The guiding rule here is "if an SP is prepared * to provide a value for an attribute type, then that is the one * that must be returned to the user". Note, this does not prevent * the SP implementing a "merge real with virtual policy", if they * wish. */ if (found_it) { if ( c->types[index].type_values != NULL ) { /* * Any values in this list are always pointers. * See slapi_vattr_list_types() and vattr_convert_type_list() */ c->types[index].type_values = NULL; } return 0; } /* * If it's not in the list then we need to add it. * If the list is already copies, then we need to make a copy of what the * SP passed us. * If the SP indicated that the type name it passed us is volatile, * !(flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) * then we need to copy it anyway. */ /* rbyrneXXX optimisation for COS: if each type_thang could have a * free_flag * then we would not have to convert all the real attrs to copies * just because an SP returned a copy (note: only COS rrequires copies; * roles returns a pointer to a static string "nsRole"). */ if (!c->list_is_copies && (0 == (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS))) { /* Means we need to turn the existing list into a list of copies */ vattr_convert_type_list(c); } if (c->list_is_copies) { thang_to_add.type_flags = thang->type_flags; thang_to_add.type_name = slapi_ch_strdup(thang->type_name); } else { thang_to_add = *thang; } /* Lastly, we add to the list, using a function which hides the complexity of extending the list if it fills up */ vattr_add_thang_to_list(c, &thang_to_add); return rc; } /* Function to free the list returned in the function above */ /* Written, reviewed, debug stepped */ void slapi_vattr_attrs_free(vattr_type_thang **types, int flags) { if (NULL == *types) { return; } /* Check whether we need to free the strings */ if (flags & SLAPI_VIRTUALATTRS_RETURNED_POINTERS) { /* We don't need to free the values */ } else { char *attr_name_to_free = NULL; size_t i = 0; /* Walk down the set of values, fr eeing each one in turn */ for (; (attr_name_to_free = (*types)[i].type_name) != NULL; i++) { slapi_ch_free((void **)&attr_name_to_free); } } /* We always need to free the pointer block */ slapi_ch_free((void **)types); } char *vattr_typethang_get_name(vattr_type_thang *t) { return t->type_name; } unsigned long vattr_typethang_get_flags(vattr_type_thang *t) { return t->type_flags; } vattr_type_thang *vattr_typethang_next(vattr_type_thang *t) { t++; if (t->type_name) { return t; } else { return NULL; } } vattr_type_thang *vattr_typethang_first(vattr_type_thang *t) { return t; } vattr_get_thang *slapi_vattr_getthang_first(vattr_get_thang *t) { return t; } vattr_get_thang *slapi_vattr_getthang_next(vattr_get_thang *t) { t++; if (t->get_present) { return t; } else { return NULL; } } /* End of the public interface functions */ /* Everything below here is the SPI interface, only callable by vattr service providers. Currently this interface is not public. Interface definitions are in vattr_spi.h */ /* Structures used in the SPI interface */ /* Service provider object */ struct _vattr_sp { /* vtbl */ vattr_get_fn_type sp_get_fn; vattr_get_ex_fn_type sp_get_ex_fn; vattr_compare_fn_type sp_compare_fn; vattr_types_fn_type sp_types_fn; void *sp_data; /* Pointer for use by the Service Provider */ }; typedef struct _vattr_sp vattr_sp; /* Service provider handle */ struct _vattr_sp_handle { vattr_sp *sp; struct _vattr_sp_handle *next; /* So we can link them together in the map */ void *hint; /* Hint to the SP */ }; /* Calls made by Service Providers */ /* Call to register as a service provider */ /* This function needs to do the following: o Let the provider say "hey, I'm here". It should probably get some handle back which it can use in future calls. o Say whether it wants to be called to resolve every single query, or whether it will say in advance what attrs it services. */ static vattr_sp_handle *vattr_sp_list = NULL; int slapi_vattrspi_register(vattr_sp_handle **h, vattr_get_fn_type get_fn, vattr_compare_fn_type compare_fn, vattr_types_fn_type types_fn) { return slapi_vattrspi_register_internal(h, get_fn, 0, compare_fn, types_fn, 0); } int slapi_vattrspi_register_ex(vattr_sp_handle **h, vattr_get_ex_fn_type get_fn, vattr_compare_fn_type compare_fn, vattr_types_fn_type types_fn, void *options) { return slapi_vattrspi_register_internal(h, 0, get_fn, compare_fn, types_fn, options); } /* options not used yet - for future expansion */ int slapi_vattrspi_register_internal(vattr_sp_handle **h, vattr_get_fn_type get_fn, vattr_get_ex_fn_type get_ex_fn, vattr_compare_fn_type compare_fn, vattr_types_fn_type types_fn, void *options) { vattr_sp_handle *return_to_caller = NULL; vattr_sp_handle *list_handle = NULL; vattr_sp *new_sp = NULL; /* Make a service provider handle */ new_sp = (vattr_sp*)slapi_ch_calloc(1,sizeof(vattr_sp)); new_sp->sp_get_fn = get_fn; new_sp->sp_get_ex_fn = get_ex_fn; new_sp->sp_compare_fn = compare_fn; new_sp->sp_types_fn = types_fn; return_to_caller = (vattr_sp_handle*)slapi_ch_calloc(1,sizeof(vattr_sp_handle)); return_to_caller->sp = new_sp; /* Add to the service provider list */ /* Make a handle for the list */ list_handle = (vattr_sp_handle*)slapi_ch_calloc(1, sizeof (vattr_sp_handle)); *list_handle = *return_to_caller; list_handle->next = vattr_sp_list; vattr_sp_list = list_handle; /* Return the handle to the caller */ *h = return_to_caller; return 0; } vattr_sp_handle_list *vattr_map_sp_get_complete_list() { return vattr_sp_list; } int slapi_vattrspi_regattr(vattr_sp_handle *h,char *type_name_to_register, char* DN , void *hint) { int ret = 0; char *type_to_add; int free_type_to_add = 0; Slapi_DN original_dn; slapi_sdn_init(&original_dn); /* Supplying a DN means that the plugin requires to be called * only when the considering attributes in relevant entries - almost * * Actually the smallest namespace is the backend. * * atttributes that are tied to backends have the format DN::attr * this essentially makes the attribute have split namespaces * that are treated as separate attributes in the vattr code */ if(DN) { /* as a coutesy we accept any old DN and will convert * to a namespace DN, this helps to hide details * (that we might decide to change) anyway */ Slapi_Backend *be; Slapi_DN *namespace_dn; slapi_sdn_set_dn_byref(&original_dn,DN); be = slapi_be_select( &original_dn ); namespace_dn = (Slapi_DN*)slapi_be_getsuffix(be, 0); if(namespace_dn && be != defbackend_get_backend()) /* just in case someone thinks "" is a good namespace */ { type_to_add = slapi_ch_smprintf("%s::%s", (char*)slapi_sdn_get_dn(namespace_dn), type_name_to_register); free_type_to_add = 1; } else { type_to_add = type_name_to_register; } } else { type_to_add = type_name_to_register; } ret = vattr_map_sp_insert(type_to_add,h,hint); slapi_sdn_done(&original_dn); if(free_type_to_add) { slapi_ch_free((void **)&type_to_add); } return ret; } /* Call to advertise or refute that you generate the value for some attribute, and to signal that the definition for this attribute has changed */ /* This call needs to do the following: o Let the SP say "you know, I will generate the value of attribute "foo", within subtree "bar". (we might ignore the subtree for now to keep things simple o Same as above, but say "you know, I'm not doing that any more". (we'll do that with a addordel flag). o Say, "you know that definition I said I did, well it's changed". */ /* Functions to handle the context stucture */ int slapi_vattr_context_create(vattr_context **c) { return 0; } void slapi_vattr_context_destroy(vattr_context *c) { } int vattr_call_sp_get_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *buffer_flags, void* hint) { int ret = -1; if(handle->sp->sp_get_fn) { ret = ((handle->sp->sp_get_fn)(handle,c,e,type,results,type_name_disposition,actual_type_name,flags,buffer_flags, hint)); } return ret; } int vattr_call_sp_get_batch_values(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, char **type, Slapi_ValueSet*** results,int **type_name_disposition, char*** actual_type_name, int flags, int *buffer_flags, void** hint) { int ret = 0; if(handle->sp->sp_get_ex_fn) { ret = ((handle->sp->sp_get_ex_fn)(handle,c,e,type,results,type_name_disposition,actual_type_name,flags,buffer_flags, hint)); } else { /* make our args look like the simple non-batched case */ *results = (Slapi_ValueSet**)slapi_ch_calloc(2, sizeof(*results)); /* 2 for null terminated list */ *type_name_disposition = (int *)slapi_ch_calloc(2, sizeof(*type_name_disposition)); *actual_type_name = (char**)slapi_ch_calloc(2, sizeof(*actual_type_name)); ret =((handle->sp->sp_get_fn)(handle,c,e,*type,*results,*type_name_disposition,*actual_type_name,flags,buffer_flags, hint)); if (ret) { slapi_ch_free((void**)results ); slapi_ch_free((void**)type_name_disposition ); slapi_ch_free((void**)actual_type_name ); } } return ret; } int vattr_call_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, const char *type, Slapi_Value* test_this,int *result, int flags, void* hint) { return ((handle->sp->sp_compare_fn)(handle,c,e,(char*)type,test_this,result,flags, hint)); } int vattr_call_sp_get_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags) { return ((handle->sp->sp_types_fn)(handle,e,type_context,flags)); } /* Service provider entry point prototypes */ /* Call which allows a SP to say what attributes can be used to define a given attribute, used for loop detection */ /* Not implementing this yet */ /* typedef int (*vattrspi_explain_definition) (); */ /* Function called to indicate to the SP that the game is over, we free the handle, not the SP */ typedef int (*vattrspi_deregister) (vattr_sp_handle *h); /* End of the SPI functions */ /* Implementation functions only after here */ /* The attribute map */ /* We need a structure which can map attribute type names to SP's which might tell us something useful. We need a function which, given an attr type name, can return a set of SP's to ask about said attr. SP's which don't tell us which attrs they are handling always get asked. The first SP which tells us the answer wins, we don't attempt to arbitrate between them. One might imaging doing this stuff in a unified way with other stuff pertaining to types (schema, syntax). Someday. For now this is a separate thing in the insterests of stability. */ /* Virtual Attribute map implementation */ /* The virtual attribute type map is implemented as a hash table. The single key is the attribute type base name (not an alias) (what about subtypes??). The hash table takes care of translating this key to a list of probable service providers for the type. Other than the obvious lookup function, we need functions to allow entries to be added and removed from the map. Because the map is likely to be consulted at least once for every search operation performed by the server, it's important that it is free from performance deficiencies such as lock contention (is the NSPR hash table implementation even thread safe ? */ #define VARRT_MAP_HASHTABLE_SIZE 10 /* Attribute map oject */ /* Needs to contain: a linked list of pointers to provider handles handles, The type name, other stuff ? Access to the entire map will be controlled via a single RW lock. */ struct _objAttrValue { struct _objAttrValue *pNext; Slapi_Value *val; }; typedef struct _objAttrValue objAttrValue; struct _vattr_map_entry { char * /* currect ? */ type_name; vattr_sp_handle *sp_list; objAttrValue *objectclasses; /* objectclasses for this type to check schema with*/ }; typedef struct _vattr_map_entry vattr_map_entry; vattr_map_entry test_entry = {NULL}; struct _vattr_map { PRRWLock *lock; PLHashTable *hashtable; /* Hash table */ }; typedef struct _vattr_map vattr_map; static vattr_map *the_map = NULL; static PRIntn vattr_hash_compare_keys(const void *v1, const void *v2) { /* Should this be subtype aware, etc ? */ return ( (0 == strcasecmp(v1,v2)) ? 1 : 0) ; } static PRIntn vattr_hash_compare_values(const void *v1, const void *v2) { return( ((char *)v1 == (char *)v2 ) ? 1 : 0); } static PLHashNumber vattr_hash_fn(const void *type_name) { /* need a hash function here */ /* Sum the bytes for now */ PLHashNumber result = 0; char * current_position = NULL; char current_char = 0; for (current_position = (char*) type_name; *current_position; current_position++) { current_char = tolower(*current_position); result += current_char; } return result; } static int vattr_map_create() { the_map = (vattr_map*)slapi_ch_calloc(1, sizeof(vattr_map)); if (NULL == the_map) { slapd_nasty(sourcefile,1,0); return ENOMEM; } the_map->hashtable = PL_NewHashTable(VARRT_MAP_HASHTABLE_SIZE, vattr_hash_fn, vattr_hash_compare_keys, vattr_hash_compare_values, NULL, NULL); if (NULL == the_map->hashtable) { slapd_nasty(sourcefile,2,0); return ENOMEM; } the_map->lock = PR_NewRWLock(0,SOURCEFILE "1"); if (NULL == the_map) { slapd_nasty(sourcefile,3,0); return ENOMEM; } return 0; } static void vattr_map_destroy() { if (the_map) { if (the_map->hashtable) { PL_HashTableDestroy(the_map->hashtable); } if (the_map->lock) { PR_DestroyRWLock(the_map->lock); } } slapi_ch_free ((void**)&the_map); } /* Returns 0 if present, entry returned in result. Returns SLAPI_VIRTUALATTRS_NOT_FOUND if not found */ static int vattr_map_lookup(const char *type_to_find, vattr_map_entry **result) { char *basetype = 0; char *tmp = 0; char buf[SLAPD_TYPICAL_ATTRIBUTE_NAME_MAX_LENGTH]; *result = NULL; PR_ASSERT(the_map); /* vattrs need to support subtypes * we insist that one service provider * will support all subtypes for a * given superior, hence we look for * superiors only here */ tmp = slapi_attr_basetype( type_to_find, buf, sizeof(buf)); if(tmp) { basetype = tmp; } else { basetype = buf; } /* Get the reader lock */ PR_RWLock_Rlock(the_map->lock); *result = (vattr_map_entry*)PL_HashTableLookupConst(the_map->hashtable, (void*)basetype); /* Release ze lock */ PR_RWLock_Unlock(the_map->lock); if(tmp) { slapi_ch_free_string(&tmp); } if (*result) { return 0; } else { return SLAPI_VIRTUALATTRS_NOT_FOUND; } } /* Insert an entry into the attribute map */ int vattr_map_insert(vattr_map_entry *vae) { char *copy_of_type_name = NULL; PR_ASSERT(the_map); copy_of_type_name = slapi_ch_strdup(vae->type_name); if (NULL == copy_of_type_name) { slapd_nasty(sourcefile,6,0); return ENOMEM; } /* Get the writer lock */ PR_RWLock_Wlock(the_map->lock); /* Insert the thing */ /* It's illegal to call this function if the entry is already there */ PR_ASSERT(NULL == PL_HashTableLookupConst(the_map->hashtable,(void*)copy_of_type_name)); PL_HashTableAdd(the_map->hashtable,(void*)copy_of_type_name,(void*)vae); /* Unlock and we're done */ PR_RWLock_Unlock(the_map->lock); return 0; } /* vattr_delete_attrvals --------------------- deletes a value list */ void vattr_delete_attrvals(objAttrValue **attrval) { objAttrValue *val = *attrval; while(val) { objAttrValue *next = val->pNext; slapi_value_free(&val->val); slapi_ch_free((void**)&val); val = next; } } /* vattr_add_attrval ----------------- adds a value to an attribute value list */ int vattr_add_attrval(objAttrValue **attrval, char *val) { int ret = 0; objAttrValue *theVal; LDAPDebug( LDAP_DEBUG_TRACE, "--> vattr_add_attrval\n",0,0,0); /* create the attrvalue */ theVal = (objAttrValue*) slapi_ch_malloc(sizeof(objAttrValue)); if(theVal) { theVal->val = slapi_value_new_string(val); if(theVal->val) { theVal->pNext = *attrval; *attrval = theVal; } else { slapi_ch_free((void**)&theVal); LDAPDebug( LDAP_DEBUG_ANY, "vattr_add_attrval: failed to allocate memory\n",0,0,0); ret = -1; } } else { LDAPDebug( LDAP_DEBUG_ANY, "vattr_add_attrval: failed to allocate memory\n",0,0,0); ret = -1; } LDAPDebug( LDAP_DEBUG_TRACE, "<-- vattr_add_attrval\n",0,0,0); return ret; } objAttrValue *vattr_map_entry_build_schema(char *type_name) { objAttrValue *ret = 0; struct objclass *oc; LDAPDebug( LDAP_DEBUG_TRACE, "--> vattr_map_entry_build_schema\n",0,0,0); /* this is the first opportunity to register * with the statechange api, our init function * gets called prior to loading plugins, so it * was not available then */ if(!statechange_api) { /* grab statechange api - we never release this */ if(!slapi_apib_get_interface(StateChange_v1_0_GUID, &statechange_api)) { /* register for schema changes via dn */ statechange_register(statechange_api, "vattr", "cn=schema", NULL, NULL, (notify_callback) schema_changed_callback); } } if(!config_get_schemacheck()) { LDAPDebug( LDAP_DEBUG_TRACE, "<-- vattr_map_entry_build_schema - schema off\n",0,0,0); return 0; } oc_lock_read(); for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) { char **pppAttrs[2]; int index; int attrType = 0; int oc_added = 0; pppAttrs[0] = oc->oc_required; pppAttrs[1] = oc->oc_allowed; /* we need to check both required and allowed attributes */ while(!oc_added && attrType < 2) { if(pppAttrs[attrType]) { index = 0; while(pppAttrs[attrType][index]) { if(!PL_strcasecmp(pppAttrs[attrType][index], type_name)) { vattr_add_attrval(&ret, oc->oc_name); oc_added = 1; break; } index++; } } attrType++; } } oc_unlock(); LDAPDebug( LDAP_DEBUG_TRACE, "<-- vattr_map_entry_build_schema\n",0,0,0); return ret; } static PRIntn vattr_map_entry_rebuild_schema(PLHashEntry *he, PRIntn i, void *arg) { vattr_map_entry *entry = (vattr_map_entry *)(he->value); if(entry->objectclasses) vattr_delete_attrvals(&(entry->objectclasses)); entry->objectclasses = vattr_map_entry_build_schema(entry->type_name); return HT_ENUMERATE_NEXT; } void schema_changed_callback(Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data) { /* Get the writer lock */ PR_RWLock_Wlock(the_map->lock); /* go through the list */ PL_HashTableEnumerateEntries(the_map->hashtable, vattr_map_entry_rebuild_schema, 0); /* Unlock and we're done */ PR_RWLock_Unlock(the_map->lock); } int slapi_vattr_schema_check_type(Slapi_Entry *e, char *type) { int ret = 0; vattr_map_entry *map_entry; if(config_get_schemacheck()) { Slapi_Attr *attr; if(0 == slapi_entry_attr_find(e, "objectclass", &attr)) { Slapi_ValueSet *vs; if(0 == slapi_attr_get_valueset(attr, &vs)) { objAttrValue *obj; if(0 == vattr_map_lookup(type, &map_entry)) { PR_RWLock_Rlock(the_map->lock); obj = map_entry->objectclasses; while(obj) { if(slapi_valueset_find(attr, vs, obj->val)) { /* this entry has an objectclass * that allows or requires the * attribute type */ ret = -1; break; } obj = obj->pNext; } PR_RWLock_Unlock(the_map->lock); } slapi_valueset_free(vs); } } } else ret = -1; return ret; } vattr_map_entry *vattr_map_entry_new(char *type_name, vattr_sp_handle *sph, void* hint) { vattr_map_entry *result = NULL; vattr_sp_handle *sp_copy = NULL; sp_copy = (vattr_sp_handle*)slapi_ch_calloc(1, sizeof (vattr_sp_handle)); sp_copy->sp = sph->sp; sp_copy->hint = hint; result = (vattr_map_entry*)slapi_ch_calloc(1, sizeof (vattr_map_entry)); result->type_name = slapi_ch_strdup(type_name); result->sp_list = sp_copy; /* go get schema */ result->objectclasses = vattr_map_entry_build_schema(type_name); return result; } /* On top of the map, we need functions to manipulate the SP handles from within */ /* Function to get the SP list from the map, given a type name */ /* The resulting list is in the map, but is safe to read regardless of concurrent updates */ vattr_sp_handle_list *vattr_map_sp_getlist(char *type_to_find) { int ret = 0; vattr_map_entry *result = NULL; ret = vattr_map_lookup(type_to_find,&result); if (0 == ret) { return (vattr_sp_handle_list*) result->sp_list; } else { return NULL; } } /* same as above, but filters the list based on the supplied backend dn * when we stored these dn based attributes, we concatenated them with * the dn like this dn::attribute, so we need to do two checks for the * attribute, one with, and one without the dn */ vattr_sp_handle_list *vattr_map_namespace_sp_getlist(Slapi_DN *dn, const char *type_to_find) { int ret = 0; vattr_map_entry *result = NULL; vattr_sp_handle_list* return_list = 0; ret = vattr_map_lookup(type_to_find,&result); if (0 == ret) { return_list = (vattr_sp_handle_list*) result->sp_list; } else { /* we have allowed the global namespace provider a shot * now it is time to query for split namespace providers */ if(dn) { char *split_dn = (char*)slapi_sdn_get_dn(dn); char *split_type_to_find = slapi_ch_smprintf("%s::%s",split_dn, type_to_find); if(split_type_to_find) { ret = vattr_map_lookup(split_type_to_find,&result); if (0 == ret) { return_list = (vattr_sp_handle_list*) result->sp_list; } slapi_ch_free((void **)&split_type_to_find); } } } return return_list; } /* Iterator function for the list */ vattr_sp_handle *vattr_map_sp_next(vattr_sp_handle_list *list, void **hint) { vattr_sp_handle *result = NULL; vattr_sp_handle *current = (vattr_sp_handle*) list; PR_ASSERT(list); PR_ASSERT(hint); result = current->next; *hint = current->hint; return result; } /* Iterator function for the list */ vattr_sp_handle *vattr_map_sp_first(vattr_sp_handle_list *list, void **hint) { vattr_sp_handle *result = NULL; PR_ASSERT(list); PR_ASSERT(hint); result = (vattr_sp_handle*)list; *hint = result->hint; return result; } /* Iterator function for the list */ vattr_sp_handle *vattr_list_sp_next(vattr_sp_handle_list *list) { vattr_sp_handle *result = NULL; vattr_sp_handle *current = (vattr_sp_handle*) list; PR_ASSERT(list); result = current->next; return result; } /* Iterator function for the list */ vattr_sp_handle *vattr_list_sp_first(vattr_sp_handle_list *list) { vattr_sp_handle *result = NULL; PR_ASSERT(list); result = (vattr_sp_handle*)list; return result; } /* Function to insert an SP into the map entry for a given type name */ /* Note that SP's can't ever remmove themselves from the map---if they could we'd need to hold a lock on the read path, which we don't want to do. So any SP which relinquishes its need to handle a type needs to continue to handle the calls on it, but return nothing */ /* DBDB need to sort out memory ownership here, it's not quite right */ int vattr_map_sp_insert(char *type_to_add, vattr_sp_handle *sp, void *hint) { int result = 0; vattr_map_entry *map_entry = NULL; /* Is this type already there ? */ result = vattr_map_lookup(type_to_add,&map_entry); /* If it is, add this SP to the list, safely even if readers are traversing the list at the same time */ if (0 == result) { int found = 0; vattr_sp_handle *list_entry = NULL; /* Walk the list checking that the daft SP isn't already here */ for (list_entry = map_entry->sp_list ; list_entry; list_entry = list_entry->next) { if (list_entry == sp) { found = 1; break; } } /* If it is, we do nothing */ if(found) { return 0; } /* We insert the SP handle into the linked list at the head */ sp->next = map_entry->sp_list; map_entry->sp_list = sp; } else { /* If not, add it */ map_entry = vattr_map_entry_new(type_to_add,sp,hint); if (NULL == map_entry) { return ENOMEM; } return vattr_map_insert(map_entry); } return 0; } /* * returns non-zero if type is a cachable virtual attr, zero otherwise. * * Decision point for caching vattrs...to be expanded to be configurable, * allow sp's to have a say etc. */ static int cache_all = 0; int slapi_vattrcache_iscacheable( const char * type ) { int rc = 0; if(/*cache_all ||*/ !slapi_UTF8CASECMP((char *)type, "nsrole")) { rc = 1; } return(rc); } /* * slapi_vattrcache_cache_all and slapi_vattrcache_cache_none * ---------------------------------------------------------- * limited control for deciding whether to * cache anything, in reality controls whether * to cache cos attributes right now */ void slapi_vattrcache_cache_all() { cache_all = -1; } void slapi_vattrcache_cache_none() { cache_all = 0; } void vattrcache_entry_READ_LOCK(const Slapi_Entry *e){ PR_RWLock_Rlock(e->e_virtual_lock); } void vattrcache_entry_READ_UNLOCK(const Slapi_Entry *e) { PR_RWLock_Unlock(e->e_virtual_lock); } void vattrcache_entry_WRITE_LOCK(const Slapi_Entry *e){ PR_RWLock_Wlock(e->e_virtual_lock); } void vattrcache_entry_WRITE_UNLOCK(const Slapi_Entry *e){ PR_RWLock_Unlock(e->e_virtual_lock); } Slapi_PBlock * slapi_vattr_get_pblock_from_context(vattr_context *c) { if (c) { return c->pb; } else { return NULL; } } #ifdef VATTR_TEST_CODE /* Prototype SP begins here */ /* Get value function */ int vattr_basic_sp_get_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *buffer_flags, void *hint) { Slapi_Value *value = NULL; *buffer_flags = 0; *actual_type_name = slapi_ch_strdup("dbtestattr"); value = slapi_value_new_string("Hello Client"); *results = slapi_ch_calloc(1,sizeof(Slapi_ValueSet)); slapi_valueset_init(*results); slapi_valueset_add_value_ext(*results,value,SLAPI_VALUE_FLAG_PASSIN); return 0; } /* Compare value function */ int vattr_basic_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result,int flags, void *hint) { *result = 0; return 0; } int vattr_basic_sp_list_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags) { static char* test_type_name = "dbtestattr"; vattr_type_thang thang = {0}; thang.type_name = test_type_name; thang.type_flags = 0; slapi_vattrspi_add_type(type_context,&thang,SLAPI_VIRTUALATTRS_REQUEST_POINTERS); return 0; } int vattr_basic_sp_init() { int ret = 0; vattr_sp_handle *my_handle = NULL; /* Register SP */ ret = slapi_vattrspi_register(&my_handle,vattr_basic_sp_get_value, vattr_basic_sp_compare_value, vattr_basic_sp_list_types); if (ret) { slapd_nasty(sourcefile,4,0); return ret; } /* Register interest in some attribute over the entire tree */ ret = slapi_vattrspi_regattr(my_handle,"dbtestattr","", NULL); if (ret) { slapd_nasty(sourcefile,5,0); return ret; } /* Register interest in some attribute over the entire tree */ ret = slapi_vattrspi_regattr(my_handle,"dbtestattr1","", NULL); if (ret) { slapd_nasty(sourcefile,5,0); return ret; } /* Register interest in some attribute over the entire tree */ ret = slapi_vattrspi_regattr(my_handle,"dbtestattr2","", NULL); if (ret) { slapd_nasty(sourcefile,5,0); return ret; } /* Register interest in some attribute over the entire tree */ ret = slapi_vattrspi_regattr(my_handle,"dbtestatt3r","", NULL); if (ret) { slapd_nasty(sourcefile,5,0); return ret; } return ret; } /* What do we do on shutdown ? */ int vattr_basic_sp_cleanup() { return 0; } #endif