diff -ru samba-3.0.6.orig/source/include/includes.h samba-3.0.6.new/source/include/includes.h --- samba-3.0.6.orig/source/include/includes.h 2004-08-19 09:39:13.000000000 -0400 +++ samba-3.0.6.new/source/include/includes.h 2004-09-04 14:06:25.000000000 -0400 @@ -409,6 +409,7 @@ #if HAVE_LDAP_H #include +#include #else #undef HAVE_LDAP #endif diff -ru samba-3.0.6.orig/source/include/smbldap.h samba-3.0.6.new/source/include/smbldap.h --- samba-3.0.6.orig/source/include/smbldap.h 2004-08-19 09:39:13.000000000 -0400 +++ samba-3.0.6.new/source/include/smbldap.h 2004-09-05 14:30:45.000000000 -0400 @@ -129,6 +129,9 @@ int max_len); BOOL smbldap_get_single_pstring (LDAP * ldap_struct, LDAPMessage * entry, const char *attribute, pstring value); +char** smbldap_getRemovableAttrs(LDAP * ldap_struct, LDAPMessage * entry, + const char *objectClass); +void smbldap_freeRemovableAttrs(char** delAttrs); /** * Struct to keep the state for all the ldap stuff diff -ru samba-3.0.6.orig/source/lib/smbldap.c samba-3.0.6.new/source/lib/smbldap.c --- samba-3.0.6.orig/source/lib/smbldap.c 2004-08-19 09:39:12.000000000 -0400 +++ samba-3.0.6.new/source/lib/smbldap.c 2004-09-06 00:30:28.000000000 -0400 @@ -159,6 +159,455 @@ { LDAP_ATTR_LIST_END, NULL } }; +/* + * This is local Schema specific implementation of hash. + * Neither ./hash.c nor implementations serve our needs. + * But we do borrow some ideas from both of them. + * The main features required by our hash model for schema are: + * 1: keys are pointers inside data area. + * 2: cleanup function can be different for different hashes + * 3: comparison AND hashing should be case insensitive + */ +typedef struct schema_hash_entry { + const char *key; + void *data; + struct schema_hash_entry *next; +} schema_hash_entry_t; + +typedef struct schema_hash_table { + int size; + struct schema_hash_entry **table; + void (*cleanFn)(void*); +} schema_hash_table_t; + +typedef enum hash_action {ENTER, FIND} hash_action_t; + +/* + * hash_clean() and hash_destroy() are separate for a small optimization by + * not recreating hash->table if its size will fit new items. + */ +static void hash_clean(schema_hash_table_t *hash) +{ + int i; + schema_hash_entry_t *curr, *next; + for(i=0; isize; i++) { + for(curr = hash->table[i]; curr; curr=next) { + next = curr->next; + if(hash->cleanFn) + (*(hash->cleanFn))(curr->data); + free(curr); + } + hash->table[i] = NULL; + } +} + +static void hash_destroy(schema_hash_table_t *hash) +{ + if(hash->table) free(hash->table); + hash->table = NULL; + hash->size = 0; +} + +/* primes[] and string_hash are taken from ./hash.c */ +static unsigned primes[] = + {17, 37, 67, 131, 257, 521, 1031, 2053, 4099, 8209, 16411}; + +/* We need to have string_hash case insensitive */ +static int string_hash(int hash_size, const char *key) +{ + unsigned int n = 0; + const char *p; + for (p = key; *p != '\0'; p++) { + n = ((n << 5) + n) ^ (unsigned int)tolower(*p); + } + return (n % hash_size); +} + +/* Note that _hash_ is suppose to be initialized with at least {0, NULL, NULL} + * for hash_create to work correctly (just look at the first 'if'). */ +static int hash_create(int size, schema_hash_table_t *hash, + void (*cleanFn)(void*)) +{ + unsigned int i; + + if(hash->table != NULL) + hash_clean(hash); + + hash->cleanFn = cleanFn; + + if(size <= hash->size) + return hash->size; + + free(hash->table); + + hash->size = 2; + while (hash->size < size) + hash->size <<= 1; + + for (i = 0; i < sizeof(primes); i++) { + if (primes[i] > hash->size) { + hash->size = primes[i]; + break; + } + } + + hash->table = (schema_hash_entry_t**) + malloc(hash->size * sizeof(schema_hash_entry_t*)); + if(hash->table == NULL) + hash->size = 0; + else + memset((void*)hash->table, 0, hash->size * sizeof(schema_hash_entry_t*)); + + return hash->size; +} + +static schema_hash_entry_t *hash_search(schema_hash_entry_t entry, + hash_action_t action, + schema_hash_table_t *hash) +{ + schema_hash_entry_t *result; + int hashsum = string_hash(hash->size, entry.key); + + for(result = hash->table[hashsum]; result; result=result->next) + if(!StrCaseCmp(result->key, entry.key)) + break; + + if(result == NULL && action == ENTER) { + result = (schema_hash_entry_t*)malloc(sizeof(schema_hash_entry_t)); + if(result) { + result->key = entry.key; + result->data = (void*) entry.data; + result->next = hash->table[hashsum]; + hash->table[hashsum] = result; + } + } + + return result; +} + +/* + * smbldap_hash_schema - put schema into a hash for easy retrieval + * params: + * values - schema strings whose objects need to be hashed. + * OID_Hash - pointer to table to hash into by OID + * Name_Hash - pointer to table to hash into by Name + * cleanFn - cleanup function for the data. + * getOIDandNames - function to convert string into an object and return + * pointers to OID string and to array of Name strings. + * OID and Name pointers should point inside the object. + */ +static int smbldap_hash_schema(char **values, + schema_hash_table_t *OID_Hash, + schema_hash_table_t *Name_Hash, + void (*cleanFn)(void*), + void* (*getOIDandNames)(char*, char**, char***)) +{ + int count, i; + + count = ldap_count_values(values); + + /* Reset hash tables */ + if( hash_create(count, Name_Hash, NULL) == 0 || + hash_create(count, OID_Hash, cleanFn) == 0) + return LDAP_NO_MEMORY; + else + for(i=0; values[i]; i++) { + char* oid; + char** names; + schema_hash_entry_t item; + item.data = (*getOIDandNames)(values[i], &oid, &names); + + if(item.data) { + char **curr; + schema_hash_entry_t *result; + + item.key = oid; + result = hash_search(item, ENTER, OID_Hash); + if(result == NULL) { + DEBUG(0, ("Failed to enter '%s' hash item\n", item.key)); + return LDAP_NO_MEMORY; + } + + for(curr = names; *curr; curr++) { + item.key = *curr; + result = hash_search(item, ENTER, Name_Hash); + if(result == NULL) { + DEBUG(0, ("Failed to enter '%s' hash item\n", item.key)); + return LDAP_NO_MEMORY; + } + } + } + } + + return LDAP_SUCCESS; +} + +/* + * Auxilary function to retrive OID and Names from an ObjectClass + * schema string. + */ +static void *getObjOIDandNames(char *value, char** oid, char*** names) +{ + int code; + const char *err; + LDAPObjectClass *result = ldap_str2objectclass(value, &code, &err, + LDAP_SCHEMA_ALLOW_NONE); + if(result) { + *oid = result->oc_oid; + *names = result->oc_names; + } + return result; +} + +/* + * Auxilary function to retrive OID and Names from an AttributeType + * schema string. + */ +static void *getAttrOIDandNames(char *value, char** oid, char*** names) +{ + int code; + const char *err; + LDAPAttributeType *result = ldap_str2attributetype(value, &code, &err, + LDAP_SCHEMA_ALLOW_NONE); + if(result) { + *oid = result->at_oid; + *names = result->at_names; + } + return result; +} + +static schema_hash_table_t ObjectClass_OID_Hash = {0, NULL, NULL}; +static schema_hash_table_t ObjectClass_Name_Hash = {0, NULL, NULL}; +static schema_hash_table_t AttributeType_OID_Hash = {0, NULL, NULL}; +static schema_hash_table_t AttributeType_Name_Hash = {0, NULL, NULL}; + +/* Read and hash objectClass and attributeType schemas */ +static int smbldap_read_schema(LDAP* ldap_struct) +{ + LDAPMessage *res, *entry; + int rc; + char **values; + char *schAttrs[3] = {"objectClasses", "attributeTypes", NULL}; + + /* + * We don't use smbldap_search here since read of schema is done in + * smbldap_open which in turn is used by smbldap_search and potentially + * would result in an infinite loop. + */ + if((rc = ldap_search_s(ldap_struct, + "cn=subschema", + LDAP_SCOPE_BASE, + "(objectClass=*)", + schAttrs, 0, &res)) != LDAP_SUCCESS) { + DEBUG(0, ("ldap_search_s: failed for \"cn=subschema\". %s (%d)\n", + ldap_err2string(rc), rc)); + return rc; + } + + /* LDAP_SCOPE_BASE returns only one entry so we need only the first one */ + entry = ldap_first_entry(ldap_struct, res); + + values = ldap_get_values(ldap_struct, entry, schAttrs[0]); + rc = smbldap_hash_schema(values, + &ObjectClass_OID_Hash, + &ObjectClass_Name_Hash, + (void (*)(void*))ldap_objectclass_free, + getObjOIDandNames); + ldap_value_free(values); + + if(rc == LDAP_SUCCESS) { + values = ldap_get_values(ldap_struct, entry, schAttrs[1]); + rc = smbldap_hash_schema(values, + &AttributeType_OID_Hash, + &AttributeType_Name_Hash, + (void (*)(void*))ldap_attributetype_free, + getAttrOIDandNames); + ldap_value_free(values); + } + + ldap_memfree(res); + return rc; +} + +/* Free hashes used to keep schemas */ +static void smbldap_free_schema() +{ + hash_clean(&ObjectClass_Name_Hash); + hash_destroy(&ObjectClass_Name_Hash); + hash_clean(&ObjectClass_OID_Hash); + hash_destroy(&ObjectClass_OID_Hash); + hash_clean(&AttributeType_Name_Hash); + hash_destroy(&AttributeType_Name_Hash); + hash_clean(&AttributeType_OID_Hash); + hash_destroy(&AttributeType_OID_Hash); +} + +/* + * smbldap_getRemovableAttrs - return an array of attributes which can be + * removed from the _entry_ to remove _objectClass_ + * from it without leaving it in an alegal state. + * params: + * ldap_struct - LDAP structure/connection handle + * entry - the entry we want to free from _objectClass_ + * objectClass - the class we want to remove from the _entry_. + * return: + * NULL terminated array of attributes eligible for delition. This array + * should be freed with a call to smbldap_freeRemovableAttrs after use. + * on errors: + * return NULL + */ +char** smbldap_getRemovableAttrs(LDAP *ldap_struct, + LDAPMessage *entry, + const char* objectClass) +{ + LDAPAttributeType* valueType; + int count, i; + BerElement *berP; + char *attr, **classes, **curr, **delAttrs = NULL; + schema_hash_entry_t item, *result, *objectClassItem; + schema_hash_table_t objectAttrHash = {0, NULL, NULL}; + schema_hash_table_t otherAttrHash = {0, NULL, NULL}; + + /* + * Retrieve schema information about _objectClass_ from the hashed LDAP + * schema. + * Return an error if there's no information about objectClass. + */ + item.key = objectClass; + objectClassItem = hash_search(item, FIND, &ObjectClass_Name_Hash); + if(objectClassItem == NULL) + return NULL; + + /* Create auxilary hashes. + * objectAttrHash - keeps Attributes used by _objectClass_. + * otherAttrHash - keeps Attributes used by other than _objectClass_ + * objectClasses + * Note: I don't like numbers to come from the top of people heads but this + * 25 bellow come exactly from there (my head). 25 is the number of + * sambaSamAccount attributes and approximate number of other attributes + * in my user entries. (Igor) + */ + if( hash_create(25, &objectAttrHash, NULL) == 0 || + hash_create(25, &otherAttrHash, NULL) == 0) + goto attr_errors; + + /* macro to hash objects of _names_ attributes in _hash_ by their OID + * This macro also counts number of attributes in _count_ variable. */ +#define ADD_ATTRS_OID(names, hash) { \ + for(curr=names; result && curr && *curr; curr++, count++) { \ + item.key = *curr; \ + result = hash_search(item, FIND, &AttributeType_Name_Hash); \ + if(result) { \ + valueType = result->data; \ + item.key = valueType->at_oid; \ + item.data = valueType; \ + result = hash_search(item, ENTER, &hash); \ + } \ + } \ + } + + /* macro to fill _hash_ with all _classItem_ attributes. + * Note that both attributes which must be present and which may be present + * in _classItem_ should be put in _hash_ since similary influence which + * attributes should be deleted and which ones should be left untouched. */ +#define FILL_CLASS_ATTRS(classItem, hash) { \ + LDAPObjectClass *valueClass = classItem->data; \ + ADD_ATTRS_OID(valueClass->oc_at_oids_must, hash); \ + ADD_ATTRS_OID(valueClass->oc_at_oids_may, hash); \ + } + + /* Just to make sure that _result_ is not NULL for the first iteration */ + result = &item; + + /* Hash attributes belonging to objectClasses but not to _objectClass_. */ + classes = ldap_get_values(ldap_struct, entry, "objectClass"); + for(i=0; result && classes[i]; i++) { + item.key = classes[i]; + result = hash_search(item, FIND, &ObjectClass_Name_Hash); + + /* Compare 'data' field instead of names since classes can + * have different names but 'data' corresponds to a unique OID */ + if(result && result->data != objectClassItem->data) + FILL_CLASS_ATTRS(result, otherAttrHash); + } + ldap_value_free(classes); + + /* Hash all _objectClass_ attributes in _objectAttrHash_ */ + count = 0; + FILL_CLASS_ATTRS(objectClassItem, objectAttrHash); + +#undef ADD_ATTR_OID +#undef FILL_CLASS_ATTRS + + /* + * _result_ equal NULL can mean one of the following: + * 1. one of entry's objectClasses is not in LDAP schema. + * 2. one of entry's objectClasses has attribute in its schema which does + * not have its schema in LDAP. + * 3. attribute fail to be entered into _otherAttrHash_ or _objectAttrHash_ + * First two can fail if schema wasn't read correctly or if we have very + * bad LDAP servers. Anyway we won't be able to use LDAP schema. + */ + if(result == NULL) + goto attr_errors; + + /* _count_ contains number of attributes in _objectClass_ which is an + * upper bound for number of attributes we will return */ + delAttrs = (char**)malloc((count+1) * sizeof(char*)); + if(delAttrs == NULL) + goto attr_errors; + + /* Iterate over entry's attributes to put some of them belonging + * to _objectClass_ but not to other objectClasses in delAttrs array. */ + curr = delAttrs; + for(attr = ldap_first_attribute(ldap_struct, entry, &berP); + result && attr; + attr = ldap_next_attribute(ldap_struct, entry, berP)) { + if(StrCaseCmp(attr, "objectClass")) { + item.key = attr; + result = hash_search(item, FIND, &AttributeType_Name_Hash); + if(result) { + valueType = result->data; + item.key = valueType->at_oid; + if(hash_search(item, FIND, &objectAttrHash) != NULL && + hash_search(item, FIND, &otherAttrHash) == NULL) { + /* Use _attr_ directly and jump with 'continure' to avoid its memory + * to be freed. It will be freed in smbldap_freeRemovableAttrs. */ + *curr++ = attr; + continue; + } + } + } + ldap_memfree(attr); + } + ber_memfree(berP); + *curr = NULL; + + if(result == NULL) { + smbldap_freeRemovableAttrs(delAttrs); + delAttrs = NULL; + } + + attr_errors: + hash_clean(&objectAttrHash); + hash_destroy(&objectAttrHash); + hash_clean(&otherAttrHash); + hash_destroy(&otherAttrHash); + + return delAttrs; +} + +void smbldap_freeRemovableAttrs(char** delAttrs) +{ + char **curr; + if(delAttrs) { + for(curr = delAttrs; *curr; curr++) + ldap_memfree(*curr); + free(delAttrs); + } +} + + /********************************************************************** perform a simple table lookup and return the attribute name **********************************************************************/ @@ -850,6 +1299,15 @@ return rc; } + /* + * We need to read LDAP schema here since this is where connection + * was just established or reestablished after being closed. + * We should always read schema here since LDAP could have + * been restarted with new schemas while we were not connected. + */ + if((rc = smbldap_read_schema(ldap_state->ldap_struct))) { + DEBUG(0,("Failed to read LDAP schema. We'll work without it\n")); + } ldap_state->last_ping = time(NULL); DEBUG(4,("The LDAP server is succesfully connected\n")); diff -ru samba-3.0.6.orig/source/passdb/pdb_ldap.c samba-3.0.6.new/source/passdb/pdb_ldap.c --- samba-3.0.6.orig/source/passdb/pdb_ldap.c 2004-08-19 09:39:13.000000000 -0400 +++ samba-3.0.6.new/source/passdb/pdb_ldap.c 2004-09-05 20:23:40.000000000 -0400 @@ -260,7 +260,7 @@ int rc; LDAPMessage *entry = NULL; LDAPMod **mods = NULL; - char *name, *dn; + char *name, *dn, **delAttrs = NULL; BerElement *ptr = NULL; rc = ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result); @@ -289,8 +289,17 @@ } /* Ok, delete only the SAM attributes */ - - for (name = ldap_first_attribute(ldap_state->smbldap_state->ldap_struct, entry, &ptr); + + delAttrs = smbldap_getRemovableAttrs(ldap_state->smbldap_state->ldap_struct, entry, objectclass); + if(delAttrs) { + char **attrib; + for (attrib = delAttrs; *attrib; attrib++) { + DEBUG(10, ("ldapsam_delete_entry: deleting attribute %s\n", *attrib)); + smbldap_set_mod(&mods, LDAP_MOD_DELETE, *attrib, NULL); + } + smbldap_freeRemovableAttrs(delAttrs); + } else { + for (name = ldap_first_attribute(ldap_state->smbldap_state->ldap_struct, entry, &ptr); name != NULL; name = ldap_next_attribute(ldap_state->smbldap_state->ldap_struct, entry, ptr)) { char **attrib; @@ -300,7 +309,7 @@ for (attrib = attrs; *attrib != NULL; attrib++) { if ((StrCaseCmp(*attrib, name) == 0) && - !(StrCaseCmp(*attrib, + (StrCaseCmp(*attrib, get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_MOD_TIMESTAMP)))) { DEBUG(10, ("ldapsam_delete_entry: deleting attribute %s\n", name)); smbldap_set_mod(&mods, LDAP_MOD_DELETE, name, NULL); @@ -308,12 +317,13 @@ } ldap_memfree(name); - } + } - if (ptr != NULL) { + if (ptr != NULL) { ber_free(ptr, 0); + } } - + smbldap_set_mod(&mods, LDAP_MOD_DELETE, "objectClass", objectclass); rc = smbldap_modify(ldap_state->smbldap_state, dn, mods);