diff --git a/lam/HISTORY b/lam/HISTORY index f90b15b0..d698bf61 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -12,6 +12,8 @@ - new plugin for managing MAC addresses (RFE 926017) - new plugin for managing NIS mail aliases (RFE 1050036) - new plugin for managing mail routing with inetLocalMailRecipient (RFE 1092137) + - added schema browser + 26.01.2005 0.4.8 - allow "%" at the beginning of Samba home/profile path (1107998) diff --git a/lam/lib/ldap.inc b/lam/lib/ldap.inc index 79262fd4..38171add 100644 --- a/lam/lib/ldap.inc +++ b/lam/lib/ldap.inc @@ -32,6 +32,8 @@ $Id$ include_once("config.inc"); /** Encryption functions */ include_once("blowfish.inc"); +/** LDAP schema */ +include_once("schema.inc"); /** * Converts a HEX string to a binary value @@ -126,6 +128,11 @@ class Ldap{ if (! $this->objectClasses) { $this->updateClasses(); $this->updateCapabilities(); + // update schema + get_schema_objectclasses(); + get_schema_attributes(); + get_schema_matching_rules(); + get_schema_syntaxes(); } // return success number return ldap_errno($this->server); diff --git a/lam/lib/schema.inc b/lam/lib/schema.inc new file mode 100644 index 00000000..109ec5bf --- /dev/null +++ b/lam/lib/schema.inc @@ -0,0 +1,2018 @@ +oid = null; + $this->description = null; + } + + /** Default constructor. */ + function SchemaItem() + { + $this->initVars(); + } + + function setOID( $new_oid ) + { + $this->oid = $new_oid; + } + + function setDescription( $new_desc ) + { + $this->description = $new_desc; + } + + function getOID() + { + return $this->oid; + } + + function getDescription() + { + return $this->description; + } + } + +/** + * Represents an LDAP objectClass + */ +class ObjectClass extends SchemaItem +{ + /** This objectClass' name, ie "inetOrgPerson" */ + var $name; + /** array of objectClass names from which this objectClass inherits */ + var $sup_classes; + /** one of STRUCTURAL, ABSTRACT, or AUXILIARY */ + var $type; + /** arrays of attribute names that this objectClass requires */ + var $must_attrs; + /** arrays of attribute names that this objectClass allows, but does not require */ + var $may_attrs; + /** boolean value indicating whether this objectClass is obsolete */ + var $is_obsolete; + /** array of objectClasses which inherit from this one (must be set at runtime explicitly by the caller) */ + var $children_objectclasses; + + /** Initialize the class' member variables */ + function initVars() + { + parent::initVars(); + $this->oid = null; + $this->name = null; + $this->description = null; + $this->sup_classes = array(); + $this->type = null; + $this->must_attrs = array(); + $this->may_attrs = array(); + $this->is_obsolete = false; + $this->children_objectclasses = array(); + } + + /** + * Creates a new ObjectClass object given a raw LDAP objectClass string. + */ + function ObjectClass( $raw_ldap_schema_string ) + { + $this->initVars(); + $class = $raw_ldap_schema_string; + $strings = preg_split ("/[\s,]+/", $class, -1,PREG_SPLIT_DELIM_CAPTURE); + for($i=0; $iname)==0) + $this->name = $strings[$i]; + else + $this->name .= " " . $strings[$i]; + }while(!preg_match("/\'$/s", $strings[$i])); + } else { + $i++; + do { + $i++; + if(strlen($this->name) == 0) + $this->name = $strings[$i]; + else + $this->name .= " " . $strings[$i]; + } while(!preg_match("/\'$/s", $strings[$i])); + do { + $i++; + }while($strings[$i]!=")"); + } + $this->name = preg_replace("/^\'/", "", $this->name); + $this->name = preg_replace("/\'$/", "", $this->name); + break; + case 'DESC': + do { + $i++; + if(strlen($this->description)==0) + $this->description=$this->description . $strings[$i]; + else + $this->description=$this->description . " " . $strings[$i]; + }while(!preg_match("/\'$/s", $strings[$i])); + break; + case 'OBSOLETE': + $this->is_obsolete = TRUE; + break; + case 'SUP': + if($strings[$i+1]!="(") { + $i++; + array_push ($this->sup_classes, $strings[$i]); + }else{ + $i++; + do { + $i++; + if($strings[$i]!="$") + array_push( $this->sup_classes, $strings[$i] ); + }while($strings[$i+1]!=")"); + } + break; + case 'ABSTRACT': + $this->type='abstract'; + break; + case 'STRUCTURAL': + $this->type='structural'; + break; + case 'AUXILIARY': + $this->type='auxiliary'; + break; + case 'MUST': + if($strings[$i+1]!="(") + { + $i++; + $attr = new ObjectClassAttribute($strings[$i], $this->name); + array_push ($this->must_attrs, $attr); + }else{ + $i++; + do { + $i++; + if($strings[$i]!="$") + { + $attr = new ObjectClassAttribute($strings[$i], $this->name); + array_push ($this->must_attrs, $attr); + } + }while($strings[$i+1]!=")"); + } + sort($this->must_attrs); + break; + case 'MAY': + if($strings[$i+1]!="(") + { + $i++; + $attr = new ObjectClassAttribute($strings[$i], $this->name); + array_push ($this->may_attrs, $attr); + }else{ + $i++; + do + { + $i++; + if($strings[$i]!="$") + { + $attr = new ObjectClassAttribute($strings[$i], $this->name); + array_push ($this->may_attrs, $attr); + } + }while($strings[$i+1]!=")"); + } + sort($this->may_attrs); + break; + default: + if(preg_match ("/[\d\.]+/i",$strings[$i]) && $i == 1) + $this->oid = $strings[$i]; + } + } + + $this->description = preg_replace("/^\'/", "", $this->description); + $this->description = preg_replace("/\'$/", "", $this->description); + } + + /** + * Gets an array of AttributeType objects that entries of this ObjectClass must define. + * This differs from getMustAttrNames in that it returns an array of AttributeType objects + * + * @param array $oclasses An array of ObjectClass objects to use when traversing + * the inheritance tree. This presents some what of a bootstrapping problem + * as we must fetch all objectClasses to determine through inheritance which + * attributes this objectClass requires. + * @return array The array of required AttributeType objects. + * + * @see getMustAttrNames + * @see getMayAttrs + * @see getMayAttrNames + */ + function getMustAttrs($oclasses = NULL) + { + $all_must_attrs = array(); + $all_must_attrs = $this->must_attrs; + foreach( $this->sup_classes as $sup_class) + { + if( $oclasses != null + && $sup_class != "top" + && isset( $oclasses[ strtolower($sup_class) ] ) ) { + $sup_class = $oclasses[ strtolower($sup_class) ]; + $sup_class_must_attrs = $sup_class->getMustAttrs( $oclasses ); + $all_must_attrs = array_merge( $sup_class_must_attrs, $all_must_attrs ); + } + } + + ksort($all_must_attrs); + return $all_must_attrs; + } + + /** + * Gets an array of AttributeType objects that entries of this ObjectClass may define. + * This differs from getMayAttrNames in that it returns an array of AttributeType objects + * + * @param array $oclasses An array of ObjectClass objects to use when traversing + * the inheritance tree. This presents some what of a bootstrapping problem + * as we must fetch all objectClasses to determine through inheritance which + * attributes this objectClass provides. + * @return array The array of allowed AttributeType objects. + * + * @see getMustAttrNames + * @see getMustAttrs + * @see getMayAttrNames + * @see AttributeType + */ + function getMayAttrs($oclasses = NULL) + { + $all_may_attrs = array(); + $all_may_attrs = $this->may_attrs; + foreach( $this->sup_classes as $sup_class_name ) + { + if( $oclasses != null + && $sup_class_name != "top" + && isset( $oclasses[ strtolower($sup_class_name) ] ) ) { + $sup_class = $oclasses[ strtolower($sup_class_name) ]; + $sup_class_may_attrs = $sup_class->getMayAttrs( $oclasses ); + $all_may_attrs = array_merge( $sup_class_may_attrs, $all_may_attrs ); + } + } + + ksort($all_may_attrs); + return $all_may_attrs; + } + + /** + * Gets an array of attribute names (strings) that entries of this ObjectClass must define. + * This differs from getMustAttrs in that it returns an array of strings rather than + * array of AttributeType objects + * + * @param array $oclasses An array of ObjectClass objects to use when traversing + * the inheritance tree. This presents some what of a bootstrapping problem + * as we must fetch all objectClasses to determine through inheritance which + * attributes this objectClass provides. + * @return array The array of allowed attribute names (strings). + * + * @see getMustAttrs + * @see getMayAttrs + * @see getMayAttrNames + */ + function getMustAttrNames( $oclasses = null ) + { + $attrs = $this->getMustAttrs( $oclasses ); + $attr_names = array(); + foreach( $attrs as $attr ) + $attr_names[] = $attr->getName(); + return $attr_names; + } + + /** + * Gets an array of attribute names (strings) that entries of this ObjectClass must define. + * This differs from getMayAttrs in that it returns an array of strings rather than + * array of AttributeType objects + * + * @param array $oclasses An array of ObjectClass objects to use when traversing + * the inheritance tree. This presents some what of a bootstrapping problem + * as we must fetch all objectClasses to determine through inheritance which + * attributes this objectClass provides. + * @return array The array of allowed attribute names (strings). + * + * @see getMustAttrs + * @see getMayAttrs + * @see getMustAttrNames + */ + function getMayAttrNames( $oclasses = null ) + { + $attrs = $this->getMayAttrs( $oclasses ); + $attr_names = array(); + foreach( $attrs as $attr ) + $attr_names[] = $attr->getName(); + return $attr_names; + } + + /** + * Adds an objectClass to the list of objectClasses that inherit + * from this objectClass. + * @param String $object_class_name The name of the objectClass to add + * @return bool Returns true on success or false on failure (objectclass already existed for example) + */ + function addChildObjectClass( $object_class_name ) + { + $object_class_name = trim( $object_class_name ); + if( ! is_array( $this->children_objectclasses ) ) + $this->children_objectclasses = array(); + foreach( $this->children_objectclasses as $existing_objectclass ) + if( 0 == strcasecmp( $object_class_name, $existing_objectclass ) ) + return false; + $this->children_objectclasses[] = $object_class_name; + return true; + } + + /** + * Returns the array of objectClass names which inherit from this objectClass. + * @return Array Names of objectClasses which inherit from this objectClass. + */ + function getChildObjectClasses() + { + return $this->children_objectclasses; + } + + /** + * Gets the name of this objectClass (ie, "inetOrgPerson") + * @return string The name of the objectClass + */ + function getName() + { + return $this->name; + } + + /** + * Gets the objectClass names from which this objectClass inherits. + * + * @return array An array of objectClass names (strings) + */ + function getSupClasses() + { + return $this->sup_classes; + } + + /** + * Gets the type of this objectClass: STRUCTURAL, ABSTRACT, or AUXILIARY. + */ + function getType() + { + return $this->type; + } + + /** + * Gets whether this objectClass is flagged as obsolete by the LDAP server. + */ + function getIsObsolete() + { + return $this->is_obsolete; + } + + /** + * Adds the specified array of attributes to this objectClass' list of + * MUST attributes. The resulting array of must attributes will contain + * unique members. + * + * @param array $new_must_attrs An array of attribute names (strings) to add. + */ + function addMustAttrs( $new_must_attrs ) + { + if( ! is_array( $new_must_attrs ) ) + return; + if( 0 == count( $new_must_attrs ) ) + return; + $this->must_attrs = array_values( array_unique( array_merge( $this->must_attrs, $new_must_attrs ) ) ); + } + + /** + * Behaves identically to addMustAttrs, but it operates on the MAY + * attributes of this objectClass. + * + * @param array $new_may_attrs An array of attribute names (strings) to add. + */ + function addMayAttrs( $new_may_attrs ) + { + if( ! is_array( $new_may_attrs ) ) + return; + if( 0 == count( $new_may_attrs ) ) + return; + $this->may_attrs = array_values( array_unique( array_merge( $this->may_attrs, $new_may_attrs ) ) ); + } +} + + /** + * A simple class for representing AttributeTypes used only by the ObjectClass class. + * Users should never instantiate this class. It represents an attribute internal to + * an ObjectClass. If PHP supported inner-classes and variable permissions, this would + * be interior to class ObjectClass and flagged private. The reason this class is used + * and not the "real" class AttributeType is because this class supports the notion of + * a "source" objectClass, meaning that it keeps track of which objectClass originally + * specified it. This class is therefore used by the class ObjectClass to determine + * inheritance. + */ + class ObjectClassAttribute + { + /** This Attribute's name */ + var $name; + /** This Attribute's root */ + var $source; + + /** + * Creates a new ObjectClassAttribute with specified name and source objectClass. + * @param string $name the name of the new attribute. + * @param string $source the name of the ObjectClass which + * specifies this attribute. + */ + function ObjectClassAttribute ($name, $source) + { + $this->name=$name; + $this->source=$source; + } + + /** Gets this attribute's name */ + function getName () + { + return $this->name; + } + + /** Gets the name of the ObjectClass which originally specified this attribute. */ + function getSource () + { + return $this->source; + } + } + + +/** + * Represents an LDAP AttributeType + */ +class AttributeType extends SchemaItem +{ + /** The name of this attributeType */ + var $name; + /** string: the description */ + var $is_obsolete; + /** The attribute from which this attribute inherits (if any) */ + var $sup_attribute; + /** The equality rule used */ + var $equality; + /** The ordering of the attributeType */ + var $ordering; + /** Boolean: supports substring matching? */ + var $sub_str; + /** The full syntax string, ie 1.2.3.4{16} */ + var $syntax; + /** boolean: is single valued only? */ + var $is_single_value; + /** boolean: is collective? */ + var $is_collective; + /** boolean: can use modify? */ + var $is_no_user_modification; + /** The usage string set by the LDAP schema */ + var $usage; + /** An array of alias attribute names, strings */ + var $aliases; + /** The max number of characters this attribute can be */ + var $max_length; + /** A string description of the syntax type (taken from the LDAPSyntaxes) */ + var $type; + /** An array of objectClasses which use this attributeType (must be set by caller) */ + var $used_in_object_classes; + /** A list of object class names that require this attribute type. */ + var $required_by_object_classes = array(); + + /** + * Initialize the class' member variables + */ + function initVars() + { + parent::initVars(); + $this->oid = null; + $this->name = null; + $this->description = null; + $this->is_obsolete = false; + $this->sup_attribute = null; + $this->equality = null; + $this->ordering = null; + $this->sub_str = null; + $this->syntax_oid = null; + $this->syntax = null; + $this->max_length = null; + $this->is_single_value= null; + $this->is_collective = false; + $this->is_no_user_modification = false; + $this->usage = null; + $this->aliases = array(); + $this->type = null; + $this->used_in_object_classes = array(); + $this->required_by_object_classes = array(); + } + + /** + * Creates a new AttributeType objcet from a raw LDAP AttributeType string. + */ + function AttributeType( $raw_ldap_attr_string ) + { + $this->initVars(); + $attr = $raw_ldap_attr_string; + $strings = preg_split ("/[\s,]+/", $attr, -1,PREG_SPLIT_DELIM_CAPTURE); + for($i=0; $iname)==0) + $this->name = $strings[$i]; + else + $this->name .= " " . $strings[$i]; + }while(!preg_match("/\'$/s", $strings[$i])); + // this attribute has no aliases + $this->aliases = array(); + } else { + $i++; + do { + $i++; + if(strlen($this->name) == 0) + $this->name = $strings[$i]; + else + $this->name .= " " . $strings[$i]; + } while(!preg_match("/\'$/s", $strings[$i])); + // add alias names for this attribute + while($strings[++$i]!=")") { + $alias = $strings[$i]; + $alias = preg_replace("/^\'/", "", $alias ); + $alias = preg_replace("/\'$/", "", $alias ); + $this->aliases[] = $alias; + } + } + break; + case 'DESC': + do { + $i++; + if(strlen($this->description)==0) + $this->description=$this->description . $strings[$i]; + else + $this->description=$this->description . " " . $strings[$i]; + }while(!preg_match("/\'$/s", $strings[$i])); + break; + case 'OBSOLETE': + $this->is_obsolete = TRUE; + break; + case 'SUP': + $i++; + $this->sup_attribute = $strings[$i]; + break; + case 'EQUALITY': + $i++; + $this->equality = $strings[$i]; + break; + case 'ORDERING': + $i++; + $this->ordering = $strings[$i]; + break; + case 'SUBSTR': + $i++; + $this->sub_str = $strings[$i]; + break; + case 'SYNTAX': + $i++; + $this->syntax = $strings[$i]; + $this->syntax_oid = preg_replace( "/{\d+}$/", "", $this->syntax ); + // does this SYNTAX string specify a max length (ie, 1.2.3.4{16}) + if( preg_match( "/{(\d+)}$/", $this->syntax, $this->max_length ) ) + $this->max_length = $this->max_length[1]; + else + $this->max_length = null; + if($i < count($strings) - 1 && $strings[$i+1]=="{") { + do { + $i++; + $this->name .= " " . $strings[$i]; + } while($strings[$i]!="}"); + } + break; + case 'SINGLE-VALUE': + $this->is_single_value = TRUE; + break; + case 'COLLECTIVE': + $this->is_collective = TRUE; + break; + case 'NO-USER-MODIFICATION': + $this->is_no_user_modification = TRUE; + break; + case 'USAGE': + $i++; + $this->usage = $strings[$i]; + break; + default: + if(preg_match ("/[\d\.]+/i",$strings[$i]) && $i == 1) + $this->oid = $strings[$i]; + } + } + + $this->name = preg_replace("/^\'/", "", $this->name); + $this->name = preg_replace("/\'$/", "", $this->name); + $this->description = preg_replace("/^\'/", "", $this->description); + $this->description = preg_replace("/\'$/", "", $this->description); + $this->syntax_oid = preg_replace("/^\'/", "", $this->syntax_oid ); + $this->syntax_oid = preg_replace("/\'$/", "", $this->syntax_oid ); + } + + /** + * Gets this attribute's name + * @return string + */ + function getName() + { + return $this->name; + } + + /** + * Gets whether this attribute has been flagged as obsolete by the LDAP server + * @return bool + */ + function getIsObsolete() + { + return $this->is_obsolete; + } + + /** + * Gets this attribute's usage string as defined by the LDAP server + * @return string + */ + function getUsage() + { + return $this->usage; + } + + /** + * Gets this attribute's parent attribute (if any). If this attribute does not + * inherit from another attribute, null is returned. + * @return string + */ + function getSupAttribute() + { + return $this->sup_attribute; + } + + /** + * Gets this attribute's equality string + * @return string + */ + function getEquality() + { + return $this->equality; + } + + /** + * Gets this attribute's ordering specification. + * @return string + */ + function getOrdering() + { + return $this->ordering; + } + + /** + * Gets this attribute's substring matching specification + * @return string + */ + function getSubstr() + { + return $this->sub_str; + } + + /** + * Gets the names of attributes that are an alias for this attribute (if any). + * @return array An array of names of attributes which alias this attribute or + * an empty array if no attribute aliases this object. + */ + function getAliases() + { + return $this->aliases; + } + + /** + * Returns whether the specified attribute is an alias for this one (based on this attribute's alias list). + * @param string $attr_name The name of the attribute to check. + * @return bool True if the specified attribute is an alias for this one, or false otherwise. + */ + function isAliasFor( $attr_name ) + { + foreach( $this->aliases as $alias_attr_name ) + if( 0 == strcasecmp( $alias_attr_name, $attr_name ) ) + return true; + return false; + + } + + /** + * Gets this attribute's raw syntax string (ie: "1.2.3.4{16}"). + * @return string The raw syntax string + */ + function getSyntaxString() + { + return $this->syntax; + } + + /** + * Gets this attribute's syntax OID. Differs from getSyntaxString() in that this + * function only returns the actual OID with any length specification removed. + * Ie, if the syntax string is "1.2.3.4{16}", this function only retruns + * "1.2.3.4". + * @return string The syntax OID string. + */ + function getSyntaxOID() + { + return $this->syntax_oid; + } + + /** + * Gets this attribute's the maximum length. If no maximum is defined by the LDAP server, null is returned. + * @return int The maximum length (in characters) of this attribute or null if no maximum is specified. + */ + function getMaxLength() + { + return $this->max_length; + } + + /** + * Gets whether this attribute is single-valued. If this attribute only supports single values, true + * is returned. If this attribute supports multiple values, false is returned. + * @return bool Returns true if this attribute is single-valued or false otherwise. + */ + function getIsSingleValue() + { + return $this->is_single_value; + } + + /** + * Sets whether this attribute is single-valued. + * @param bool $is_single_value + */ + function setIsSingleValue( $is_single_value ) + { + $this->is_single_value = $is_single_value; + } + + /** + * Gets whether this attribute is collective. + * @return bool Returns true if this attribute is collective and false otherwise. + */ + function getIsCollective() + { + return $this->is_collective; + } + + /** + * Gets whether this attribute is not modifiable by users. + * @return bool Returns true if this attribute is not modifiable by users. + */ + function getIsNoUserModification() + { + return $this->is_no_user_modification; + } + + /** + * Gets this attribute's type + * @return string The attribute's type. + */ + function getType() + { + return $this->type; + } + + /** + * Removes an attribute name from this attribute's alias array. + * @param string $remove_alias_name The name of the attribute to remove. + * @return bool true on success or false on failure (ie, if the specified + * attribute name is not found in this attribute's list of aliases) + */ + function removeAlias( $remove_alias_name ) + { + foreach( $this->aliases as $i => $alias_name ) { + if( 0 == strcasecmp( $alias_name, $remove_alias_name ) ) { + unset( $this->aliases[ $i ] ); + $this->aliases = array_values( $this->aliases ); + return true; + } + } + return false; + } + + /** + * Adds an attribute name to the alias array. + * @param string $new_alias_name The name of a new attribute to add to this attribute's list of aliases. + */ + function addAlias( $new_alias_name ) + { + $this->aliases[] = $new_alias_name; + } + + /** + * Sets this attriute's name. + * @param string $new_name The new name to give this attribute. + */ + function setName( $new_name ) + { + $this->name = $new_name; + } + + /** + * Sets this attriute's SUP attribute (ie, the attribute from which this attribute inherits). + * @param string $new_sup_attr The name of the new parent (SUP) attribute + */ + function setSupAttribute( $new_sup_attr ) + { + $this->sup_attribute = $new_sup_attr; + } + + /** + * Sets this attribute's list of aliases. + * @param array $new_aliases The array of alias names (strings) + */ + function setAliases( $new_aliases ) + { + $this->aliases = $new_aliases; + } + + /** + * Sets this attribute's type. + * @param string $new_type The new type. + */ + function setType( $new_type ) + { + $this->type = $new_type; + } + + /** + * Adds an objectClass name to this attribute's list of "used in" objectClasses, + * that is the list of objectClasses which provide this attribute. + * @param string $object_class_name The name of the objectClass to add. + */ + function addUsedInObjectClass( $object_class_name ) + { + foreach( $this->used_in_object_classes as $used_in_object_class ) + if( 0 == strcasecmp( $used_in_object_class, $object_class_name ) ) + return false; + $this->used_in_object_classes[] = $object_class_name; + return true; + } + + /** + * Gets the list of "used in" objectClasses, that is the list of objectClasses + * which provide this attribute. + * @return array An array of names of objectclasses (strings) which provide this attribute + */ + function getUsedInObjectClasses() + { + return $this->used_in_object_classes; + } + + /** + * Adds an objectClass name to this attribute's list of "required by" objectClasses, + * that is the list of objectClasses which must have this attribute. + * @param string $object_class_name The name of the objectClass to add. + */ + function addRequiredByObjectClass( $object_class_name ) + { + foreach( $this->required_by_object_classes as $required_by_object_class ) + if( 0 == strcasecmp( $required_by_object_class, $object_class_name ) ) + return false; + $this->required_by_object_classes[] = $object_class_name; + return true; + } + + /** + * Gets the list of "required by" objectClasses, that is the list of objectClasses + * which provide must have attribute. + * @return array An array of names of objectclasses (strings) which provide this attribute + */ + function getRequiredByObjectClasses() + { + return $this->required_by_object_classes; + } +} + +/** + * Represents an LDAP Syntax + */ +class Syntax extends SchemaItem +{ + /** Initializes the class' member variables */ + function initVars() + { + parent::initVars(); + $this->oid = null; + $this->description = null; + } + + /** + * Creates a new Syntax object from a raw LDAP syntax string. + */ + function Syntax( $raw_ldap_syntax_string ) + { + $this->initVars(); + $class = $raw_ldap_syntax_string; + $strings = preg_split ("/[\s,]+/", $class, -1,PREG_SPLIT_DELIM_CAPTURE); + for($i=0; $idescription)==0) + $this->description=$this->description . $strings[$i]; + else + $this->description=$this->description . " " . $strings[$i]; + }while(!preg_match("/\'$/s", $strings[$i])); + break; + default: + if(preg_match ("/[\d\.]+/i",$strings[$i]) && $i == 1) + $this->oid = $strings[$i]; + } + } + $this->description = preg_replace("/^\'/", "", $this->description); + $this->description = preg_replace("/\'$/", "", $this->description); + } +} + +/** + * Represents an LDAP MatchingRule + */ +class MatchingRule extends SchemaItem +{ + /** This rule's name */ + var $name; + /** This rule's syntax OID */ + var $syntax; + /** Boolean value indicating whether this MatchingRule is obsolete */ + var $is_obsolete; + /** An array of attribute names who use this MatchingRule */ + var $used_by_attrs; + + /** Initialize the class' member variables */ + function initVars() + { + parent::initVars(); + $this->oid = null; + $this->name = null; + $this->description = null; + $this->is_obsolete = false; + $this->syntax = null; + $this->used_by_attrs = array(); + } + + /** + * Creates a new MatchingRule object from a raw LDAP MatchingRule string. + */ + function MatchingRule( $raw_ldap_matching_rule_string ) + { + $this->initVars(); + $strings = preg_split ("/[\s,]+/", $raw_ldap_matching_rule_string, -1,PREG_SPLIT_DELIM_CAPTURE); + for($i=0; $iname)==0) + $this->name = $strings[$i]; + else + $this->name .= " " . $strings[$i]; + }while(!preg_match("/\'$/s", $strings[$i])); + } else { + $i++; + do { + $i++; + if(strlen($this->name) == 0) + $this->name = $strings[$i]; + else + $this->name .= " " . $strings[$i]; + } while(!preg_match("/\'$/s", $strings[$i])); + do { + $i++; + }while($strings[$i]!=")"); + } + $this->name = preg_replace("/^\'/", "", $this->name); + $this->name = preg_replace("/\'$/", "", $this->name); + break; + case 'DESC': + do { + $i++; + if(strlen($this->description)==0) + $this->description=$this->description . $strings[$i]; + else + $this->description=$this->description . " " . $strings[$i]; + }while(!preg_match("/\'$/s", $strings[$i])); + break; + case 'OBSOLETE': + $this->is_obsolete = TRUE; + break; + case 'SYNTAX': + $this->syntax = $strings[++$i]; + break; + default: + if(preg_match ("/[\d\.]+/i",$strings[$i]) && $i == 1) + $this->oid = $strings[$i]; + } + } + $this->description = preg_replace("/^\'/", "", $this->description); + $this->description = preg_replace("/\'$/", "", $this->description); + } + + /** + * Sets the list of used_by_attrs to the array specified by $attrs; + * @param array $attrs The array of attribute names (strings) which use this MatchingRule + */ + function setUsedByAttrs( $attrs ) + { + $this->used_by_attrs = $attrs; + } + + /** + * Adds an attribute name to the list of attributes who use this MatchingRule + * @return true if the attribute was added and false otherwise (already in the list) + */ + function addUsedByAttr( $new_attr_name ) + { + foreach( $this->used_by_attrs as $attr_name ) + if( 0 == strcasecmp( $attr_name, $new_attr_name ) ) + return false; + $this->used_by_attrs[] = $new_attr_name; + return true; + } + + /** + * Gets this MatchingRule's name. + * @return string The name. + */ + function getName() + { + return $this->name; + } + + /** + * Gets whether this MatchingRule is flagged as obsolete by the LDAP server. + * @return bool True if this MatchingRule is obsolete and false otherwise. + */ + function getIsObsolete() + { + return $this->is_obsolete; + } + + /** + * Gets this MatchingRule's syntax string (an OID). + * @todo Is this function broken? + */ + function getSyntax() + { + return $this->description; + } + + /** + * Gets an array of attribute names (strings) which use this MatchingRule + * @return array The array of attribute names (strings). + */ + function getUsedByAttrs() + { + return $this->used_by_attrs; + } +} + +/** + * Represents an LDAP schema matchingRuleUse entry + */ +class MatchingRuleUse extends SchemaItem +{ + /** The name of the MathingRule this applies to */ + var $name; + /** An array of attributeType names who make use of the mathingRule + * identified by $this->oid and $this->name */ + var $used_by_attrs; + + /** Initialize the class' member variables */ + function initVars() + { + parent::initVars(); + $this->oid = null; + $this->name = null; + $this->used_by_attrs = array(); + } + + function MatchingRuleUse( $raw_matching_rule_use_string ) + { + $this->initVars(); + $strings = preg_split ("/[\s,]+/", $raw_matching_rule_use_string, -1,PREG_SPLIT_DELIM_CAPTURE); + for($i=0; $iname ) || strlen( $this->name ) ==0 ) + $this->name = $strings[$i]; + else + $this->name .= " " . $strings[$i]; + }while(!preg_match("/\'$/s", $strings[$i])); + } else { + $i++; + do { + $i++; + if(strlen($this->name) == 0) + $this->name = $strings[$i]; + else + $this->name .= " " . $strings[$i]; + } while(!preg_match("/\'$/s", $strings[$i])); + do { + $i++; + }while($strings[$i]!=")"); + } + $this->name = preg_replace("/^\'/", "", $this->name); + $this->name = preg_replace("/\'$/", "", $this->name); + break; + case 'APPLIES': + // TODO + if($strings[$i+1]!="(") { + // has a single attribute name + $i++; + $this->used_by_attrs = array( $strings[$i] ); + //echo "Adding single: " . $strings[$i] . "
"; + } else { + // has multiple attribute names + $i++; + while($strings[$i]!=")") { + $i++; + $new_attr = $strings[$i]; + $new_attr = preg_replace("/^\'/", "", $new_attr ); + $new_attr = preg_replace("/\'$/", "", $new_attr ); + $this->used_by_attrs[] = $new_attr; + //echo "Adding $new_attr
"; + $i++; + } + } + break; + default: + if(preg_match ("/[\d\.]+/i",$strings[$i]) && $i == 1) + $this->oid = $strings[$i]; + } + } + sort( $this->used_by_attrs ); + } + + /** + * Gets this MatchingRuleUse's name + * @return string The name + */ + function getName() + { + return $this->name; + } + + /** + * Gets an array of attribute names (strings) which use this MatchingRuleUse object. + * @return array The array of attribute names (strings). + */ + function getUsedByAttrs() + { + return $this->used_by_attrs; + } +} + +/** + * Helper for _get_raw_schema() which fetches the DN of the schema object + * in an LDAP server based on a DN. Entries should set the subSchemaSubEntry + * attribute pointing to the DN of the server schema. You can specify the + * DN whose subSchemaSubEntry you wish to retrieve of specify an empty string + * to fetch the subScehamSubEntry from the Root DSE. + * + * @param string $dn The DN (may be null) which houses the subschemaSubEntry attribute which + * this function can use to determine the schema entry's DN. + * @param bool $debug Switch to true to see some nice and copious output. :) + * + * @return string The DN of the entry which houses this LDAP server's schema. + */ +function _get_schema_dn($dn, $debug=false ) +{ + if( $debug ) echo "
";
+	$ds = $_SESSION['ldap']->server;
+
+	$search = @ldap_read( $ds, $dn, 'objectClass=*', array( 'subschemaSubentry' ) );
+	if( $debug ) { echo "Search result (ldap_read): "; var_dump( $search ); echo "\n"; }
+	if( ! $search ) {
+		if( $debug ) echo "_get_schema_dn() returning false. (search val is false)\n";
+		return false;
+	}
+
+	if( @ldap_count_entries( $ds, $search ) == 0 ) {
+		if( $debug ) echo "_get_schema_dn() returning false (ldap_count_entries() == 0).\n";
+        return false;
+	}
+
+	$entries = @ldap_get_entries( $ds, $search );
+	if( $debug ) { echo "Entries (ldap_get_entries): "; var_dump( $entries ); echo "\n"; }
+	if( ! $entries || ! is_array( $entries ) ) {
+		if( $debug ) echo "_get_schema_dn() returning false (Bad entries val, false or not array).\n";
+		return false;
+	}
+
+	$entry = isset( $entries[0] ) ? $entries[0] : false;
+	if( ! $entry ) {
+		if( $debug ) echo "_get_schema_dn() returning false (entry val is false)\n";
+		return false;
+	}
+
+	$sub_schema_sub_entry = isset( $entry[0] ) ? $entry[0] : false;
+	if( ! $sub_schema_sub_entry ) {
+		if( $debug ) echo "_get_schema_dn() returning false (sub_schema_sub_entry val is false)\n";
+		return false;
+	}
+
+	$schema_dn = isset( $entry[ $sub_schema_sub_entry ][0] ) ?
+					$entry[ $sub_schema_sub_entry ][0] :
+					false;
+
+	if( $debug ) echo "_get_schema_dn() returning: \"" . $schema_dn . "\"\n";
+	return $schema_dn;
+}
+
+/**
+ * Fetches the raw schema array for the subschemaSubentry of the server. Note,
+ * this function has grown many hairs to accomodate more LDAP servers. It is
+ * needfully complicated as it now supports many popular LDAP servers that
+ * don't necessarily expose their schema "the right way".
+ *
+ * @param $schema_to_fetch - A string indicating which type of schema to 
+ *		fetch. Five valid values: 'objectclasses', 'attributetypes', 
+ *		'ldapsyntaxes', 'matchingruleuse', or 'matchingrules'. 
+ *		Case insensitive.
+ * @param $dn (optional) This paremeter is the DN of the entry whose schema you
+ * 		would like to fetch. Entries have the option of specifying
+ * 		their own subschemaSubentry that points to the DN of the system
+ * 		schema entry which applies to this attribute. If unspecified,
+ *		this will try to retrieve the schema from the RootDSE subschemaSubentry.
+ *		Failing that, we use some commonly known schema DNs. Default 
+ *		value is the Root DSE DN (zero-length string)
+ * @return an array of strings of this form:
+ *    Array (
+ *      [0] => "( 1.3.6.1.4.1.7165.1.2.2.4 NAME 'gidPool' DESC 'Pool ...
+ *      [1] => "( 1.3.6.1.4.1.7165.2.2.3 NAME 'sambaAccount' DESC 'Sa ...
+ *      etc.
+ */
+function _get_raw_schema($schema_to_fetch, $dn='' )
+{
+
+	// Swith to true to enable verbose output of schema fetching progress
+	$debug = false;
+
+	$ds = $_SESSION['ldap']->server;
+
+	// error checking
+	$schema_to_fetch = strtolower( $schema_to_fetch );
+	$valid_schema_to_fetch = array( 'objectclasses', 'attributetypes', 'ldapsyntaxes', 
+					'matchingrules', 'matchingruleuse'  );
+	if( ! in_array( $schema_to_fetch, $valid_schema_to_fetch ) )
+        // This error message is not localized as only developers should ever see it
+		echo( "Bad parameter provided to function to _get_raw_schema(). '" 
+				. htmlspecialchars( $schema_to_fetch ) . "' is 
+				not valid for the schema_to_fetch parameter." );
+	
+	// Try to get the schema DN from the specified entry. 
+	$schema_dn = _get_schema_dn($dn, $debug );
+
+	// Do we need to try again with the Root DSE?
+	if( ! $schema_dn )
+		$schema_dn = _get_schema_dn('', $debug );
+
+	// Store the eventual schema retrieval in $schema_search
+	$schema_search = null;
+
+	if( $schema_dn ) {
+		if( $debug ) { echo "Found the schema DN: "; var_dump( $schema_dn ); echo "\n"; }
+		$schema_search = @ldap_read( $ds, $schema_dn, '(objectClass=*)',
+							array( $schema_to_fetch ), 0, 0, 0, 
+							LDAP_DEREF_ALWAYS );
+
+        // Were we not able to fetch the schema from the $schema_dn?
+        $schema_entries = @ldap_get_entries( $ds, $schema_search );
+		if( $schema_search === false || 
+            0 == @ldap_count_entries( $ds, $schema_search ) ||
+            ! isset( $schema_entries[0][$schema_to_fetch] ) ) {
+                if( $debug ) echo "Did not find the schema with (objectClass=*). Attempting with (objetClass=subschema)\n";
+
+                // Try again with a different filter (some servers require (objectClass=subschema) like M-Vault)
+                $schema_search = @ldap_read( $ds, $schema_dn, '(objectClass=subschema)',
+                        array( $schema_to_fetch ), 0, 0, 0, 
+                        LDAP_DEREF_ALWAYS );
+                $schema_entries = @ldap_get_entries( $ds, $schema_search );
+
+                // Still didn't get it?
+                if( $schema_search === false || 
+                        0 == @ldap_count_entries( $ds, $schema_search ) ||
+                        ! isset( $schema_entries[0][$schema_to_fetch] ) ) {
+                    if( $debug ) echo "Did not find the schema at DN: $schema_dn (with objectClass=* nor objectClass=subschema).\n";
+                    unset( $schema_entries );
+                    unset( $schema_dn );
+                    $schema_search = null;
+                } else {
+                    if( $debug ) echo "Found the schema at DN: $schema_dn (with objectClass=subschema).\n";
+                }
+		} else {
+			if( $debug ) echo "Found the schema at DN: $schema_dn (with objectClass=*).\n";
+		}
+	} 
+
+	// Second chance: If the DN or Root DSE didn't give us the subschemaSubentry, ie $schema_search
+	// is still null, use some common subSchemaSubentry DNs as a work-around.
+
+	if( $debug && $schema_search == null )
+		echo "Attempting work-arounds for 'broken' LDAP servers...\n";
+
+	// cn=subschema for OpenLDAP
+	if( $schema_search == null ) {
+		if( $debug ) echo "Attempting with cn=subschema (OpenLDAP)...\n";
+		// try with the standard DN
+		$schema_search = @ldap_read($ds, 'cn=subschema', '(objectClass=*)',
+				array( $schema_to_fetch ), 0, 0, 0, LDAP_DEREF_ALWAYS );
+	}
+
+	// cn=schema for Novell eDirectory
+	if( $schema_search == null ) {
+		if( $debug ) echo "Attempting with cn=schema (Novell)...\n";
+		// try again, with a different schema DN
+		$schema_search = @ldap_read($ds, 'cn=schema', '(objectClass=*)',
+				array( $schema_to_fetch ), 0, 0, 0, LDAP_DEREF_ALWAYS );
+	}
+
+	// cn=schema,cn=configuration,dc=example,dc=com for ActiveDirectory
+	if( $schema_search == null ) {
+		// try again, with a different schema DN
+		global $servers;
+		$base_dn = isset( $servers[ 'base' ] ) ?
+				$servers[ 'base' ] :
+				null;
+		if( $debug ) echo "Attempting with cn=schema,cn=configuration,$base_dn (ActiveDirectory)...\n";
+		if( $base_dn != null )
+			$schema_search = @ldap_read($ds, 'cn=schema,cn=configuration,' . $base_dn, '(objectClass=*)',
+				array( $schema_to_fetch ), 0, 0, 0, LDAP_DEREF_ALWAYS );
+	}
+
+	// cn=Schema,ou=Admin,dc=example,dc=com for SiteServer
+	if( $schema_search == null ) {
+		// try again, with a different schema DN
+		global $servers;
+		$base_dn = isset( $servers[ 'base' ] ) ?
+				$servers[ 'base' ] :
+				null;
+		if( $debug ) echo "Attempting with cn=Schema,ou=Admin,$base_dn (ActiveDirectory)...\n";
+		if( $base_dn != null )
+			$schema_search = @ldap_read($ds, 'cn=Schema,ou=Admin,' . $base_dn, '(objectClass=*)',
+				array( $schema_to_fetch ), 0, 0, 0, LDAP_DEREF_ALWAYS );
+	}
+
+	// Attempt to pull schema from Root DSE with scope "base"
+	if( $schema_search == null ) {
+		// try again, with a different schema DN
+		if( $debug ) echo "Attempting to pull schema from Root DSE with scope \"base\"...\n";
+		if( $base_dn != null )
+			$schema_search = @ldap_read($ds, '', '(objectClass=*)',
+				array( $schema_to_fetch ), 0, 0, 0, LDAP_DEREF_ALWAYS );
+         $schema_entries = @ldap_get_entries( $ds, $schema_search );
+         if( ! isset( $schema_entries[0][$schema_to_fetch] ) )
+            $schema_search = null;
+    }
+    
+	// Attempt to pull schema from Root DSE with scope "one" (work-around for Isode M-Vault X.500/LDAP)
+	if( $schema_search == null ) {
+		// try again, with a different schema DN
+		if( $debug ) echo "Attempting to pull schema from Root DSE with scope \"one\"...\n";
+		if( $base_dn != null )
+			$schema_search = @ldap_list($ds, '', '(objectClass=*)',
+				array( $schema_to_fetch ), 0, 0, 0, LDAP_DEREF_ALWAYS );
+         $schema_entries = @ldap_get_entries( $ds, $schema_search );
+         if( ! isset( $schema_entries[0][$schema_to_fetch] ) )
+            $schema_search = null;
+	}
+
+	// Shall we just give up?
+	if( $schema_search == null ) {
+        if( $debug ) echo "Returning false since schema_search came back null
\n"; + set_schema_cache_unavailable(); + return false; + } + + // Did we get something unrecognizable? + if( 'resource' != gettype( $schema_search ) ) { + if( $debug ) echo "Returning false since schema_esarch is not of type 'resource'. Dumping schema search:\n"; + if( $debug ) var_dump( $schema_search ); + if( $debug ) echo ""; + set_schema_cache_unavailable(); + return false; + } + + $schema = @ldap_get_entries( $ds, $schema_search ); + if( $schema == false ) { + if( $debug ) echo "Returning false since ldap_get_entries() returned false.\n"; + set_schema_cache_unavailable(); + return false; + } + + if( ! isset( $schema[0][$schema_to_fetch] ) ) { + if( $debug ) echo "Returning false since '$schema_to_fetch' isn't in the schema array. Showing schema array:\n"; + if( $debug ) var_dump( $schema ); + if( $debug ) echo ""; + set_schema_cache_unavailable(); + return false; + } + + // Make a nice array of this form: + // Array ( + // [0] => "( 1.3.6.1.4.1.7165.1.2.2.4 NAME 'gidPool' DESC 'Pool ... + // [1] => "( 1.3.6.1.4.1.7165.2.2.3 NAME 'sambaAccount' DESC 'Sa ... + // etc. + $schema = $schema[0][$schema_to_fetch]; + unset( $schema['count'] ); + + if( $debug ) echo ""; + return $schema; +} + +/** + * Gets an associative array of ObjectClass objects for the specified + * server. Each array entry's key is the name of the objectClass + * in lower-case and the value is an ObjectClass object. + * + * @param string $dn (optional) It is easier to fetch schema if a DN is provided + * which defines the subschemaSubEntry attribute (all entries should). + * + * @return array An array of ObjectClass objects. + * + * @see ObjectClass + * @see get_schema_objectclass + */ +function get_schema_objectclasses($dn=null, $use_cache=true ) +{ + if( $use_cache && cached_schema_available('objectclasses' ) ) { + return get_cached_schema('objectclasses' ); + } + + $raw_oclasses = _get_raw_schema('objectclasses', $dn ); + if( ! $raw_oclasses ) + return false; + + // build the array of objectClasses + $object_classes = array(); + foreach( $raw_oclasses as $class_string ) { + if( $class_string == null || 0 == strlen( $class_string ) ) + continue; + $object_class = new ObjectClass( $class_string ); + $name = $object_class->getName(); + $key = strtolower( $name ); + $object_classes[ $key ] = $object_class; + } + + ksort( $object_classes ); + + // cache the schema to prevent multiple schema fetches from LDAP server + set_cached_schema('objectclasses', $object_classes ); + return( $object_classes ); +} + +/** + * Gets a single ObjectClass object specified by name. + * + * @param string $oclass_name The name of the objectClass to fetch. + * @param string $dn (optional) It is easier to fetch schema if a DN is provided + * which defines the subschemaSubEntry attribute (all entries should). + * + * @return ObjectClass The specified ObjectClass object or false on error. + * + * @see ObjectClass + * @see get_schema_objectclasses + */ +function get_schema_objectclass($oclass_name, $dn=null, $use_cache=true ) +{ + $oclass_name = strtolower( $oclass_name ); + $oclasses = get_schema_objectclasses($dn, $use_cache ); + if( ! $oclasses ) + return false; + if( isset( $oclasses[ $oclass_name ] ) ) + return $oclasses[ $oclass_name ]; + else + return false; +} + +/** + * Gets a single AttributeType object specified by name. + * + * @param string $oclass_name The name of the AttributeType to fetch. + * @param string $dn (optional) It is easier to fetch schema if a DN is provided + * which defines the subschemaSubEntry attribute (all entries should). + * + * @return AttributeType The specified AttributeType object or false on error. + * + * @see AttributeType + * @see get_schema_attributes + */ +function get_schema_attribute($attr_name, $dn=null, $use_cache=true ) +{ + $attr_name = real_attr_name( $attr_name ); + $schema_attrs = get_schema_attributes($dn, $use_cache ); + $attr_name = strtolower( $attr_name ); + $schema_attr = isset( $schema_attrs[ $attr_name ] ) ? + $schema_attrs[ $attr_name ] : + false; + return $schema_attr; +} + +/** + * Gets an associative array of AttributeType objects for the specified + * server. Each array entry's key is the name of the attributeType + * in lower-case and the value is an AttributeType object. + * + * @param string $dn (optional) It is easier to fetch schema if a DN is provided + * which defines the subschemaSubEntry attribute (all entries should). + * + * @return array An array of AttributeType objects. + */ +function get_schema_attributes($dn = null, $use_cache=true ) +{ + if( $use_cache && cached_schema_available('attributetypes' ) ) { + return get_cached_schema('attributetypes' ); + } + + $raw_attrs = _get_raw_schema('attributeTypes', $dn ); + if( ! $raw_attrs ) + return false; + + // build the array of attribueTypes + $syntaxes = get_schema_syntaxes($dn ); + $attrs = array(); + /** + * bug 856832: create two arrays - one indexed by name (the standard + * $attrs array above) and one indexed by oid (the new $attrs_oid array + * below). This will help for directory servers, like IBM's, that use OIDs + * in their attribute definitions of SUP, etc + */ + $attrs_oid = array(); + foreach( $raw_attrs as $attr_string ) { + if( $attr_string == null || 0 == strlen( $attr_string ) ) + continue; + $attr = new AttributeType( $attr_string ); + if( isset( $syntaxes[ $attr->getSyntaxOID() ] ) ) { + $syntax = $syntaxes[ $attr->getSyntaxOID() ]; + $attr->setType( $syntax->getDescription() ); + } + $name = $attr->getName(); + $key = strtolower( $name ); + $attrs[ $key ] = $attr; + + /** + * bug 856832: create an entry in the $attrs_oid array too. This + * will be a ref to the $attrs entry for maintenance and performance + * reasons + */ + $oid = $attr->getOID(); + $attrs_oid[ $oid ] = &$attrs[ $key ]; + } + + add_aliases_to_attrs( $attrs ); + /** + * bug 856832: pass the $attrs_oid array as a second (new) parameter + * to add_sup_to_attrs. This will allow lookups by either name or oid. + */ + add_sup_to_attrs( $attrs, $attrs_oid ); + + ksort( $attrs ); + + // Add the used in and required_by values. + $schema_object_classes = get_schema_objectclasses(); + + foreach( $schema_object_classes as $object_class ) { + $must_attrs = $object_class->getMustAttrNames($schema_object_classes); + $may_attrs = $object_class->getMayAttrNames($schema_object_classes); + $oclass_attrs = array_unique( array_merge( $must_attrs, $may_attrs ) ); + + // Add Used In. + foreach( $oclass_attrs as $attr_name ) { + if( isset( $attrs[ strtolower( $attr_name ) ] ) ) { + $attrs[ strtolower( $attr_name ) ]->addUsedInObjectClass( + $object_class->getName() ); + } else { + //echo "Warning, attr not set: $attr_name
"; + } + } + + // Add Required By. + foreach( $must_attrs as $attr_name ) { + if( isset( $attrs[ strtolower( $attr_name ) ] ) ) { + $attrs[ strtolower( $attr_name ) ]->addRequiredByObjectClass( + $object_class->getName() ); + } else { + //echo "Warning, attr not set: $attr_name
"; + } + } + + } + + // cache the schema to prevent multiple schema fetches from LDAP server + set_cached_schema('attributetypes', $attrs ); + return( $attrs ); +} + +/** + * For each attribute that has multiple names, this function adds unique entries to + * the attrs array for those names. Ie, attributeType has name 'gn' and 'givenName'. + * This function will create a unique entry for 'gn' and 'givenName'. + */ +function add_aliases_to_attrs( &$attrs ) +{ + // go back and add data from aliased attributeTypes + foreach( $attrs as $name => $attr ) { + $aliases = $attr->getAliases(); + if( is_array( $aliases ) && count( $aliases ) > 0 ) { + // foreach of the attribute's aliases, create a new entry in the attrs array + // with its name set to the alias name, and all other data copied + foreach( $aliases as $alias_attr_name ) { + $new_attr = $attr; + $new_attr->setName( $alias_attr_name ); + $new_attr->addAlias( $attr->getName() ); + $new_attr->removeAlias( $alias_attr_name ); + $new_attr_key = strtolower( $alias_attr_name ); + $attrs[ $new_attr_key ] = $new_attr; + } + } + } +} + +/** + * Adds inherited values to each attributeType specified by the SUP directive. + * Supports infinite levels of inheritance. + * Bug 856832: require a second paramter that has all attributes indexed by OID + */ +function add_sup_to_attrs( &$attrs, &$attrs_oid ) +{ + $debug = false; + if( $debug ) echo "
";
+
+	if( $debug ) print_r( $attrs );
+	
+	// go back and add any inherited descriptions from parent attributes (ie, cn inherits name)
+	foreach( $attrs as $key => $attr ) {
+		if( $debug ) echo "Analyzing inheritance for attribute '" . $attr->getName() . "'\n";
+		$sup_attr_name = $attr->getSupAttribute();
+		$sup_attr = null;
+
+		// Does this attribute have any inheritance happening here?
+		if( null != trim( $sup_attr_name ) ) {
+
+			// This loop really should traverse infinite levels of inheritance (SUP) for attributeTypes,
+			// but just in case we get carried away, stop at 100. This shouldn't happen, but for
+			// some weird reason, we have had someone report that it has happened. Oh well.
+			$i = 0;
+			while( $i++ < 100 /** 100 == INFINITY ;) */ ) {
+				if( $debug ) echo "Top of loop.\n";
+
+				/**
+				 * Bug 856832: check if sup is indexed by OID. If it is,
+				 * replace the OID with the appropriate name. Then reset
+				 * $sup_attr_name to the name instead of the OID. This will
+				 * make all the remaining code in this function work as
+				 * expected.
+				 */
+				if( isset( $attrs_oid[$sup_attr_name] ) ) {
+					$attr->setSupAttribute( $attrs_oid[$sup_attr_name]->getName() );
+					$sup_attr_name = $attr->getSupAttribute();
+				}
+				
+				if( ! isset( $attrs[ strtolower( $sup_attr_name ) ] ) ){ 
+					echo( "Schema error: attributeType '" . $attr->getName() . "' inherits from 
+								'" . $sup_attr_name . "', but attributeType '" . $sup_attr_name . "' does not
+								exist." );
+						return;
+				}
+
+				if( $debug ) echo " sup_attr_name: $sup_attr_name\n";
+				$sup_attr = $attrs[ strtolower( $sup_attr_name ) ];
+				if( $debug ) echo " Sup attr: " . $sup_attr->getName() . "\n";
+
+				$sup_attr_name = $sup_attr->getSupAttribute();
+				if( $debug ) echo " Does the sup attr itself have a sup attr?\n";
+
+				// Does this superior attributeType not have a superior attributeType?
+				if( null == $sup_attr_name || strlen( trim( $sup_attr_name ) ) == 0 ) {
+
+					// Since this attribute's superior attribute does not have another superior
+					// attribute, clone its properties for this attribute. Then, replace
+					// those cloned values with those that can be explicitly set by the child
+					// attribute attr). Save those few properties which the child can set here:
+					if( $debug ) echo "  nope, this is the end of the inheritance chain after $i iterations.\n";
+					$tmp_name = $attr->getName();
+					$tmp_oid = $attr->getOID();
+					$tmp_sup = $attr->getSupAttribute();
+					$tmp_aliases = $attr->getAliases();
+					$tmp_single_val = $attr->getIsSingleValue();
+
+
+					if( $debug ) {
+						echo "  populating values into attribute from sup attribute:\n";
+						echo "Before: ";
+						print_r( $attr );
+					}
+
+					// clone the SUP attributeType and populate those values
+					// that were set by the child attributeType
+					$attr = $sup_attr;
+					$attr->setOID( $tmp_oid );
+					$attr->setName( $tmp_name );
+					$attr->setSupAttribute( $tmp_sup);
+					$attr->setAliases( $tmp_aliases );
+
+					if( $debug ) {
+						echo "After (name, sup_attr, and aliases should not have changed!: ";
+						print_r( $attr );
+					}
+					// only overwrite the SINGLE-VALUE property if the child explicitly sets it
+					// (note: All LDAP attributes default to multi-value if not explicitly set SINGLE-VALUE)
+					if( true == $tmp_single_val )
+						$attr->setIsSingleValue( true );
+
+					// replace this attribute in the attrs array now that we have populated
+					// new values therein
+					$attrs[$key] = $attr;
+
+					// very important: break out after we are done with this attribute
+					$sup_attr_name = null;
+					$sup_attr = null;
+					break;
+
+				} else {
+
+					// do nothing, move on down the chain of inheritance...
+					if( $debug ) echo "  yup, march down the inheritance chain (iteration $i).\n";
+					if( $debug ) { echo "  The sup attr is: "; var_dump( $sup_attr_name ); echo "\n"; }
+
+				}
+			}
+		}
+	}
+
+	if( $debug ) echo "
\n"; +} + +/** + * Returns an array of MatchingRule objects for the specified server. + * The key of each entry is the OID of the matching rule. + */ +function get_schema_matching_rules($dn=null, $use_cache=true ) +{ + if( $use_cache && cached_schema_available('matchingrules' ) ) { + return get_cached_schema('matchingrules' ); + } + + // build the array of MatchingRule objects + $raw_matching_rules = _get_raw_schema('matchingRules', $dn ); + if( ! $raw_matching_rules ) + return false; + $rules = array(); + foreach( $raw_matching_rules as $rule_string ) { + if( $rule_string == null || 0 == strlen( $rule_string ) ) + continue; + $rule = new MatchingRule( $rule_string ); + $key = strtolower( $rule->getName() ); + $rules[ $key ] = $rule; + } + + ksort( $rules ); + + // For each MatchingRuleUse entry, add the attributes who use it to the + // MatchingRule in the $rules array. + $raw_matching_rule_use = _get_raw_schema('matchingRuleUse' ); + if( $raw_matching_rule_use != false ) { + foreach( $raw_matching_rule_use as $rule_use_string ) { + if( $rule_use_string == null || 0 == strlen( $rule_use_string ) ) + continue; + $rule_use = new MatchingRuleUse( $rule_use_string ); + $key = strtolower( $rule_use->getName() ); + if( isset( $rules[ $key ] ) ) + $rules[ $key ]->setUsedByAttrs( $rule_use->getUsedByAttrs() ); + } + } else { + // No MatchingRuleUse entry in the subschema, so brute-forcing + // the reverse-map for the "$rule->getUsedByAttrs()" data. + $attrs = get_schema_attributes($dn ); + if( is_array( $attrs ) ) + foreach( $attrs as $attr ) { + $rule_key = strtolower( $attr->getEquality() ); + if( isset( $rules[ $rule_key ] ) ) + $rules[ $rule_key ]->addUsedByAttr( $attr->getName() ); + } + } + + // cache the schema to prevent multiple schema fetches from LDAP server + set_cached_schema('matchingrules', $rules ); + return $rules; +} + +/** + * Returns an array of Syntax objects that this LDAP server uses mapped to + * their descriptions. The key of each entry is the OID of the Syntax. + */ +function get_schema_syntaxes($dn=null, $use_cache=true ) +{ + if( $use_cache && cached_schema_available('ldapsyntaxes' ) ) { + return get_cached_schema('ldapsyntaxes' ); + } + + $raw_syntaxes = _get_raw_schema('ldapSyntaxes', $dn ); + if( ! $raw_syntaxes ) + return false; + + // build the array of attributes + $syntaxes = array(); + foreach( $raw_syntaxes as $syntax_string ) { + $syntax = new Syntax( $syntax_string ); + $key = strtolower( trim( $syntax->getOID() ) ); + if( ! $key ) continue; + $syntaxes[$key] = $syntax; + } + + ksort( $syntaxes ); + + // cache the schema to prevent multiple schema fetches from LDAP server + set_cached_schema('ldapsyntaxes', $syntaxes ); + + return $syntaxes; +} + +// -------------------------------------------------------------------- +// Schema caching functions +// -------------------------------------------------------------------- + +/** + * Returns true if the schema for $schema_type has been cached and + * is availble. $schema_type may be one of (lowercase) the following: + * objectclasses + * attributetypes + * ldapsyntaxes + * matchingrules + * matchingruleuse + * Note that _get_raw_schema() takes a similar parameter. + */ +function cached_schema_available($schema_type ) +{ + // Check config to make sure session-based caching is enabled. + if( ! SCHEMA_SESSION_CACHE_ENABLED ) + return false; + + // Static memory cache available? + // (note: this memory cache buys us a 20% speed improvement over strictly + // checking the session, ie 0.05 to 0.04 secs) + $schema_type = strtolower( $schema_type ); + static $cache_avail; + if( isset( $cache_avail[ $schema_type ] ) ) { + return true; + } + + // Session cache available? + if( isset( $_SESSION[ 'schema' ][ $schema_type ] ) ) { + $cache_avail[ $schema_type ] = true; + return true; + } elseif ( isset( $_SESSION[ 'schema' ][ 'unavailable'] ) ) { + return true; + } else { + return false; + } +} + +/** + * Returns the cached array of schemaitem objects for the specified + * $schema_type. For list of valid $schema_type values, see above + * schema_cache_available(). Note that internally, this function + * utilizes a two-layer cache, one in memory using a static variable + * for multiple calls within the same page load, and one in a session + * for multiple calls within the same user session (spanning multiple + * page loads). + * + * Returns an array of SchemaItem objects on success or false on failure. + */ +function get_cached_schema($schema_type ) +{ + // Check config to make sure session-based caching is enabled. + if( ! SCHEMA_SESSION_CACHE_ENABLED ) + return false; + + static $cache; + $schema_type = strtolower( $schema_type ); + if( isset( $cache[ $schema_type ] ) ) { + //echo "Getting memory-cached schema for \"$schema_type\"...
\n"; + return $cache[ $schema_type ]; + } + + //echo "Getting session-cached schema for \"$schema_type\"...
\n"; + if( cached_schema_available($schema_type ) ) { + $schema = $_SESSION[ 'schema' ][ $schema_type ]; + $cache[ $schema_type ] = $schema; + return $schema; + } else { + return false; + } +} + +/** + * Caches the specified $schema_type. + * $schema_items should be an array of SchemaItem instances (ie, + * an array of ObjectClass, AttributeType, LDAPSyntax, MatchingRuleUse, + * or MatchingRule objects. + * + * Returns true on success of false on failure. + */ +function set_cached_schema($schema_type, $schema_items ) +{ + // Check config to make sure session-based caching is enabled. + if( ! SCHEMA_SESSION_CACHE_ENABLED ) + return false; + + //echo "Setting cached schema for \"$schema_type\"...
\n"; + // Sanity check. The schema must be in the form of an array + if( ! is_array( $schema_items ) ) { + die( "While attempting to cache schema, passed a non-array for \$schema_items!" ); + } + // Make sure we are being passed a valid array of schema_items + foreach( $schema_items as $schema_item ) { + if( ! is_subclass_of( $schema_item, 'SchemaItem' ) && + ! 0 == strcasecmp( 'SchemaItem', get_class( $schema_item ) ) ) { + die( "While attempting to cache schema, one of the schema items passed is not a true SchemaItem instance!" ); + } + } + + $schema_type = strtolower( $schema_type ); + $_SESSION[ 'schema' ][ $schema_type ] = $schema_items; + return true; +} + +/** + * Sets the schema entry for the server_id to be "unavailable" so that we realize + * that we tried to get the schema but could not, so quit trying next time to + * fetch it from the server. + */ +function set_schema_cache_unavailable() +{ + if( ! SCHEMA_SESSION_CACHE_ENABLED ) + return false; + $_SESSION['schema']['unavailable'] = true; + return true; +} + +?> diff --git a/lam/style/500_layout.css b/lam/style/500_layout.css index eb54eca3..aad72345 100644 --- a/lam/style/500_layout.css +++ b/lam/style/500_layout.css @@ -509,3 +509,87 @@ select.useredit-bright { background-color:#CCCCFF; } + +table.schema_oclasses { + border-left: 1px solid black; + border-right: 1px solid black; + border-bottom: 1px solid black; + border-top: 0; + margin-bottom: 10px; + background-color: #eee; +} + +table.schema_oclasses td { + vertical-align: top; + text-align: left; + padding-left: 5px; +} + +table.schema_attr th { + background-color: #016; + padding: 5px; + color: white; + font-weight: bold; + font-size: 125%; +} + +table.schema_attr td { + padding: 5px; + vertical-align: top; +} + +table.schema_attr tr.even { + background-color: #eee; +} + +table.schema_attr tr.odd { + background-color: #ccc; +} + +table.schema_attr tr.highlight{ + background-color: #bcd; + font-weight: Bold; +} + +ul.schema { + margin: 5px; + margin-left: 0px; + padding-left: 20px; +} + +ul.schema li { + margin-left: 0px; + padding-left: 0px; +} + +ul.schema li small { + font-size: 75%; + color: #777; +} + +ul.schema li small a { + color: #77c; +} + +h4.schema_oclass { + background: #016; + padding: 5px; + margin: 0px; + margin-top: 8px; + font-weight: normal; + border: 1px solid black; + font-size: 140%; + color: white; +} + +h4.schema_oclass_sub { + background: #dde; + border: 1px solid black; + border-top: 0px; + font-weight: normal; + margin: 0px; + padding: 2px; + padding-left: 5px; + font-size: 80%; +} + diff --git a/lam/templates/schema/schema.php b/lam/templates/schema/schema.php new file mode 100644 index 00000000..9afd49cb --- /dev/null +++ b/lam/templates/schema/schema.php @@ -0,0 +1,448 @@ +LDAP Account Manager\n"; +echo "\n"; +echo "\n"; + + +$view = isset( $_GET['view'] ) ? $_GET['view'] : 'objectClasses'; +$viewvalue = isset( $_GET['viewvalue'] ) ? $_GET['viewvalue'] : null; +if( trim( $viewvalue ) == "" ) + $viewvalue = null; +if( ! is_null( $viewvalue ) ) + $viewed = false; + +?> + + + +
+
+ ' . _('Object classes').'' ); ?> + | + ' . _('Attribute types').'' ); ?> + | + ' . _('Syntaxes').'' ); ?> + | + ' . _('Matching rules').'' ); ?> +
+
+ + + +\n"; + echo "" . _('Syntax OID') . "" . _('Description') . "\n"; + flush(); + $counter=1; + $schema_syntaxes = get_schema_syntaxes(null); + if( ! $schema_syntaxes ) StatusMessage("ERROR", _("Unable to retrieve schema!"), ""); + foreach( $schema_syntaxes as $syntax ) { + $counter++; + $oid = htmlspecialchars( $syntax->getOID() ); + $desc = htmlspecialchars( $syntax->getDescription() ); + if( $highlight_oid && $highlight_oid == $oid ) + echo ""; + else + echo ""; + echo "$oid$desc\n\n"; + } + echo "\n"; + +} elseif( $view == 'attributes' ) { + flush(); + $schema_attrs = get_schema_attributes(null); + $schema_object_classes = get_schema_objectclasses(null); + if( ! $schema_attrs || ! $schema_object_classes ) + StatusMessage("ERROR", _("Unable to retrieve schema!"), ""); + + ?> + : +
+
+ +
+ + + getName() ) ) { + if( ! is_null( $viewvalue ) ) + $viewed = true; + flush(); + echo "\n\n"; + $counter = 0; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo '\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo ""; + echo "\n\n"; + + echo "\n"; + echo "\n"; + echo ""; + echo "\n\n"; + + flush(); + } + } + echo "
" . $attr->getName() . "
"._('Description')."" . ( $attr->getDescription() == null ? '('._('No description').')' : $attr->getDescription() ). "
"._('OID')."" . $attr->getOID() . "
'._('Obsolete')."?" . ( $attr->getIsObsolete() ? '' . _('Yes') . '' : _('No') ) . "
"._('Inherits from').""; + if( $attr->getSupAttribute()==null ) + echo '('._('none').')'; + else + echo "getSupAttribute() ) . "\">" . $attr->getSupAttribute() . "
"._('Equality')."" . ( $attr->getEquality() == null ? '('._('not specified').')' : "getEquality()."\">".$attr->getEquality()."" ) . "
"._('Ordering')."" . ( $attr->getOrdering()==null? '('._('not specified').')' : $attr->getOrdering() ) . "
"._('Substring Rule')."" . ( $attr->getSubstr()==null? '('._('not specified').')' : $attr->getSubstr() ) . "
"._('Syntax').""; + if( null != $attr->getType() ) { + echo "getSyntaxOID() . "#" . $attr->getSyntaxOID(); + echo "\">" . $attr->getType() . " (" . $attr->getSyntaxOID() . ")"; + } else { + echo $attr->getSyntaxOID(); + } + echo "
"._('Single valued')."" . ( $attr->getIsSingleValue() ? _('Yes') : _('No') ) . "
"._('Collective')."?" . ( $attr->getIsCollective() ? _('Yes') : _('No') ) . "
"._('User modification')."" . ( $attr->getIsNoUserModification() ? _('No') : _('Yes') ) . "
"._('Usage')."" . ( $attr->getUsage() ? $attr->getUsage() : '('._('not specified').')' ) . "
"._('Maximum length').""; + if ( $attr->getMaxLength() === null ) { echo '('._('not applicable').')';} + else { + echo number_format( $attr->getMaxLength() ) ." "; + if ( $attr->getMaxLength()>1) {echo _('characters');} + else { echo _('character') ;} + } + echo "
"._('Aliases').""; + if( count( $attr->getAliases() ) == 0 ) + echo '('._('none').')'; + else + foreach( $attr->getAliases() as $alias_attr_name ) + echo "$alias_attr_name "; + echo "
"._('Used by object classes').""; + if( count( $attr->getUsedInObjectClasses() ) == 0 ) + echo '('._('none').')'; + else + foreach( $attr->getUsedInObjectClasses() as $used_in_oclass) + echo "$used_in_oclass "; + echo "
\n"; + +} elseif( $view == 'matching_rules' ) { + $schema_matching_rules = get_schema_matching_rules(null); + echo '' . _('Jump to a matching rule').'
'; + echo '
'; + echo ''; + echo ''; + echo ''; + echo '
'; + echo "\n\n\n"; + echo "\n"; + flush(); + $counter=1; + $schema_matching_rules = get_schema_matching_rules(null); + if( ! $schema_matching_rules ) StatusMessage("ERROR", _("Unable to retrieve schema!"), ""); + foreach( $schema_matching_rules as $rule ) { + $counter++; + $oid = htmlspecialchars( $rule->getOID() ); + $desc = htmlspecialchars( $rule->getName() ); + if ( $viewvalue==null || $viewvalue==($rule->getName() )) { + if( ! is_null( $viewvalue ) ) + $viewed = true; + if( null != $rule->getDescription() ) + $desc .= ' (' . $rule->getDescription() . ')'; + if( true === $rule->getIsObsolete() ) + $desc .= ' ' . _('Obsolete') . ''; + echo ""; + echo ""; + echo ""; + echo "\n"; + } + } + echo "
" . _('Matching rule OID') . "" . _('Name') . ""._('Used by attributes')."
$oid$desc"; + if( count( $rule->getUsedByAttrs() ) == 0 ) { + echo "
(" . _('none') . ")


\n"; + } else { + echo "
"; + echo ""; + echo "
\n"; + } + echo "
\n"; + +} elseif( $view == 'objectClasses' ) { + flush(); + $schema_oclasses = get_schema_objectclasses(null); + if( ! $schema_oclasses ) StatusMessage("ERROR", _("Unable to retrieve schema!"), ""); + ?> + : +
+ +
+ + + + $oclass ) { + foreach( $oclass->getSupClasses() as $parent_name ) { + $parent_name = $parent_name; + if( isset( $schema_oclasses[ $parent_name ] ) ) { + $schema_oclasses[ $parent_name ]->addChildObjectClass( $oclass->getName() ); + } + } + + } ?> + +
+ $oclass ) { + if ( $viewvalue==null || 0 == strcasecmp( $viewvalue, $oclass->getName() ) ){ + if( ! is_null( $viewvalue ) ) + $viewed = true; + ?> + +

getName(); ?>

+

: getOID(); ?>

+ getDescription() ) { ?> +

: getDescription(); ?>

+ +

: getType(); ?>

+ getIsObsolete() == true ) { ?> +

+ + +

: getSupClasses() ) == 0 ) + echo "(" . _('none') . ")"; + else + foreach( $oclass->getSupClasses() as $i => $object_class ) { + echo '' . htmlspecialchars( $object_class ) . ''; + if( $i < count( $oclass->getSupClasses() ) - 1 ) + echo ', '; + } + ?>

+ +

: getName(), 'top' ) ) + echo "(all)"; + elseif( count( $oclass->getChildObjectClasses() ) == 0 ) + echo "(" . _('none') . ")"; + else + foreach( $oclass->getChildObjectClasses() as $i => $object_class ) { + echo '' . htmlspecialchars( $object_class ) . ''; + if( $i < count( $oclass->getChildObjectClasses() ) - 1 ) + echo ', '; + } + ?>

+ + + + + + + + + + +
+ getMustAttrs($schema_oclasses) ) > 0 ) { + echo '"; + } else + echo "
(" . _('none') . ")
\n"; + ?> +
+ getMayAttrs($schema_oclasses) ) > 0 ) { + echo '"; + } + else + echo "
(" . _('none') . ")
\n"; + ?> + +
+ + + + + + + + + diff --git a/lam/templates/tools.php b/lam/templates/tools.php index 6a712839..19cc4b65 100644 --- a/lam/templates/tools.php +++ b/lam/templates/tools.php @@ -85,6 +85,13 @@ $tools[] = array( "link" => "pdfedit/pdfmain.php" ); +// schema browser +$tools[] = array( + "name" => _("Schema browser"), + "description" => _("Here you can browse LDAP object classes and attributes."), + "link" => "schema/schema.php" + ); + echo "

 

\n"; // print tools table