package.xml 0000644 0001750 0001750 00000072152 11652260337 011623 0 ustar beni beni
Note that this manual is only a brief introduction and also may be outdated.
The official manual is available at the PEAR project website.
The reason this manual remains here is, that it may be useful in cases you don't have internet
acces at the moment.
To do this, use the Net_LDAP2::connect function like this:
* $attrs = array( 'attribute1' => array('value1', 'value2'),
* 'attribute2' => 'single value'
* );
*
*
* @param string $dn DN of the Entry
* @param array $attrs Attributes of the entry
*
* @static
* @return Net_LDAP2_Entry|Net_LDAP2_Error
*/
public static function createFresh($dn, $attrs = array())
{
if (!is_array($attrs)) {
return PEAR::raiseError("Unable to create fresh entry: Parameter \$attrs needs to be an array!");
}
$entry = new Net_LDAP2_Entry($attrs, $dn);
return $entry;
}
/**
* Creates a Net_LDAP2_Entry object out of an ldap entry resource
*
* Use this method, if you want to initialize an entry object that is
* already present in some directory and that you have read manually.
*
* Please note, that if you want to create an entry object that represents
* some already existing entry, you should use {@link createExisting()}.
*
* The method should be called statically: $entry = Net_LDAP2_Entry::createConnected();
*
* @param Net_LDAP2 $ldap Net_LDA2 object
* @param resource $entry PHP LDAP entry resource
*
* @static
* @return Net_LDAP2_Entry|Net_LDAP2_Error
*/
public static function createConnected($ldap, $entry)
{
if (!$ldap instanceof Net_LDAP2) {
return PEAR::raiseError("Unable to create connected entry: Parameter \$ldap needs to be a Net_LDAP2 object!");
}
if (!is_resource($entry)) {
return PEAR::raiseError("Unable to create connected entry: Parameter \$entry needs to be a ldap entry resource!");
}
$entry = new Net_LDAP2_Entry($ldap, $entry);
return $entry;
}
/**
* Creates an Net_LDAP2_Entry object that is considered already existing
*
* Use this method, if you want to modify an already existing entry
* without fetching it first.
* In most cases however, it is better to fetch the entry via Net_LDAP2->getEntry()!
*
* Please note that you should take care if you construct entries manually with this
* because you may get weird synchronisation problems.
* The attributes and values as well as the entry itself are considered existent
* which may produce errors if you try to modify an entry which doesn't really exist
* or if you try to overwrite some attribute with an value already present.
*
* This method is equal to calling createFresh() and after that markAsNew(FALSE).
*
* The method should be called statically: $entry = Net_LDAP2_Entry::createExisting();
*
* The attributes parameter is as following:
*
* $attrs = array( 'attribute1' => array('value1', 'value2'),
* 'attribute2' => 'single value'
* );
*
*
* @param string $dn DN of the Entry
* @param array $attrs Attributes of the entry
*
* @static
* @return Net_LDAP2_Entry|Net_LDAP2_Error
*/
public static function createExisting($dn, $attrs = array())
{
if (!is_array($attrs)) {
return PEAR::raiseError("Unable to create entry object: Parameter \$attrs needs to be an array!");
}
$entry = Net_LDAP2_Entry::createFresh($dn, $attrs);
if ($entry instanceof Net_LDAP2_Error) {
return $entry;
} else {
$entry->markAsNew(false);
return $entry;
}
}
/**
* Get or set the distinguished name of the entry
*
* If called without an argument the current (or the new DN if set) DN gets returned.
* If you provide an DN, this entry is moved to the new location specified if a DN existed.
* If the DN was not set, the DN gets initialized. Call {@link update()} to actually create
* the new Entry in the directory.
* To fetch the current active DN after setting a new DN but before an update(), you can use
* {@link currentDN()} to retrieve the DN that is currently active.
*
* Please note that special characters (eg german umlauts) should be encoded using utf8_encode().
* You may use {@link Net_LDAP2_Util::canonical_dn()} for properly encoding of the DN.
*
* @param string $dn New distinguished name
*
* @access public
* @return string|true Distinguished name (or true if a new DN was provided)
*/
public function dn($dn = null)
{
if (false == is_null($dn)) {
if (is_null($this->_dn)) {
$this->_dn = $dn;
} else {
$this->_newdn = $dn;
}
return true;
}
return (isset($this->_newdn) ? $this->_newdn : $this->currentDN());
}
/**
* Renames or moves the entry
*
* This is just a convinience alias to {@link dn()}
* to make your code more meaningful.
*
* @param string $newdn The new DN
*
* @return true
*/
public function move($newdn)
{
return $this->dn($newdn);
}
/**
* Sets the internal attributes array
*
* This fetches the values for the attributes from the server.
* The attribute Syntax will be checked so binary attributes will be returned
* as binary values.
*
* Attributes may be passed directly via the $attributes parameter to setup this
* entry manually. This overrides attribute fetching from the server.
*
* @param array $attributes Attributes to set for this entry
*
* @access protected
* @return void
*/
protected function setAttributes($attributes = null)
{
/*
* fetch attributes from the server
*/
if (is_null($attributes) && is_resource($this->_entry) && is_resource($this->_link)) {
// fetch schema
if ($this->_ldap instanceof Net_LDAP2) {
$schema =& $this->_ldap->schema();
}
// fetch attributes
$attributes = array();
do {
if (empty($attr)) {
$ber = null;
$attr = @ldap_first_attribute($this->_link, $this->_entry, $ber);
} else {
$attr = @ldap_next_attribute($this->_link, $this->_entry, $ber);
}
if ($attr) {
$func = 'ldap_get_values'; // standard function to fetch value
// Try to get binary values as binary data
if ($schema instanceof Net_LDAP2_Schema) {
if ($schema->isBinary($attr)) {
$func = 'ldap_get_values_len';
}
}
// fetch attribute value (needs error checking?)
$attributes[$attr] = $func($this->_link, $this->_entry, $attr);
}
} while ($attr);
}
/*
* set attribute data directly, if passed
*/
if (is_array($attributes) && count($attributes) > 0) {
if (isset($attributes["count"]) && is_numeric($attributes["count"])) {
unset($attributes["count"]);
}
foreach ($attributes as $k => $v) {
// attribute names should not be numeric
if (is_numeric($k)) {
continue;
}
// map generic attribute name to real one
$this->_map[strtolower($k)] = $k;
// attribute values should be in an array
if (false == is_array($v)) {
$v = array($v);
}
// remove the value count (comes from ldap server)
if (isset($v["count"])) {
unset($v["count"]);
}
$this->_attributes[$k] = $v;
}
}
// save a copy for later use
$this->_original = $this->_attributes;
}
/**
* Get the values of all attributes in a hash
*
* The returned hash has the form
* array('attributename' => 'single value',
* 'attributename' => array('value1', value2', value3'))
* Only attributes present at the entry will be returned.
*
* @access public
* @return array Hash of all attributes with their values
*/
public function getValues()
{
$attrs = array();
foreach ($this->_attributes as $attr => $value) {
$attrs[$attr] = $this->getValue($attr);
}
return $attrs;
}
/**
* Get the value of a specific attribute
*
* The first parameter is the name of the attribute
* The second parameter influences the way the value is returned:
* 'single': only the first value is returned as string
* 'all': all values including the value count are returned in an array
* 'default': in all other cases an attribute value with a single value is
* returned as string, if it has multiple values it is returned
* as an array (without value count)
*
* If the attribute is not set at this entry (no value or not defined in
* schema), an empty string is returned.
*
* You may use Net_LDAP2_Schema->checkAttribute() to see if the attribute
* is defined for the objectClasses of this entry.
*
* @param string $attr Attribute name
* @param string $option Option
*
* @access public
* @return string|array|PEAR_Error string, array or PEAR_Error
*/
public function getValue($attr, $option = null)
{
$attr = $this->getAttrName($attr);
// If the attribute is not set at the entry, return an empty value.
// Users should do schema checks if they want to know if an attribute is
// valid for an entrys OCLs.
if (!array_key_exists($attr, $this->_attributes)) {
$value = array('');
} else {
$value = $this->_attributes[$attr];
}
// format the attribute values depending on $option
if ($option == "single" || (count($value) == 1 && $option != 'all')) {
$value = array_shift($value);
}
return $value;
}
/**
* Alias function of getValue for perl-ldap interface
*
* @see getValue()
* @return string|array|PEAR_Error
*/
public function get_value()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'getValue' ), $args);
}
/**
* Returns an array of attributes names
*
* @access public
* @return array Array of attribute names
*/
public function attributes()
{
return array_keys($this->_attributes);
}
/**
* Returns whether an attribute exists or not
*
* @param string $attr Attribute name
*
* @access public
* @return boolean
*/
public function exists($attr)
{
$attr = $this->getAttrName($attr);
return array_key_exists($attr, $this->_attributes);
}
/**
* Adds a new attribute or a new value to an existing attribute
*
* The paramter has to be an array of the form:
* array('attributename' => 'single value',
* 'attributename' => array('value1', 'value2))
* When the attribute already exists the values will be added, else the
* attribute will be created. These changes are local to the entry and do
* not affect the entry on the server until update() is called.
*
* Note, that you can add values of attributes that you haven't selected, but if
* you do so, {@link getValue()} and {@link getValues()} will only return the
* values you added, _NOT_ all values present on the server. To avoid this, just refetch
* the entry after calling {@link update()} or select the attribute.
*
* @param array $attr Attributes to add
*
* @access public
* @return true|Net_LDAP2_Error
*/
public function add($attr = array())
{
if (false == is_array($attr)) {
return PEAR::raiseError("Parameter must be an array");
}
if ($this->isNew()) {
$this->setAttributes($attr);
} else {
foreach ($attr as $k => $v) {
$k = $this->getAttrName($k);
if (false == is_array($v)) {
// Do not add empty values
if ($v == null) {
continue;
} else {
$v = array($v);
}
}
// add new values to existing attribute or add new attribute
if ($this->exists($k)) {
$this->_attributes[$k] = array_unique(array_merge($this->_attributes[$k], $v));
} else {
$this->_map[strtolower($k)] = $k;
$this->_attributes[$k] = $v;
}
// save changes for update()
if (empty($this->_changes["add"][$k])) {
$this->_changes["add"][$k] = array();
}
$this->_changes["add"][$k] = array_unique(array_merge($this->_changes["add"][$k], $v));
}
}
$return = true;
return $return;
}
/**
* Deletes an whole attribute or a value or the whole entry
*
* The parameter can be one of the following:
*
* "attributename" - The attribute as a whole will be deleted
* array("attributename1", "attributename2) - All given attributes will be
* deleted
* array("attributename" => "value") - The value will be deleted
* array("attributename" => array("value1", "value2") - The given values
* will be deleted
* If $attr is null or omitted , then the whole Entry will be deleted!
*
* These changes are local to the entry and do
* not affect the entry on the server until {@link update()} is called.
*
* Please note that you must select the attribute (at $ldap->search() for example)
* to be able to delete values of it, Otherwise {@link update()} will silently fail
* and remove nothing.
*
* @param string|array $attr Attributes to delete (NULL or missing to delete whole entry)
*
* @access public
* @return true
*/
public function delete($attr = null)
{
if (is_null($attr)) {
$this->_delete = true;
return true;
}
if (is_string($attr)) {
$attr = array($attr);
}
// Make the assumption that attribute names cannot be numeric,
// therefore this has to be a simple list of attribute names to delete
if (is_numeric(key($attr))) {
foreach ($attr as $name) {
if (is_array($name)) {
// someone mixed modes (list mode but specific values given!)
$del_attr_name = array_search($name, $attr);
$this->delete(array($del_attr_name => $name));
} else {
// mark for update() if this attr was not marked before
$name = $this->getAttrName($name);
if ($this->exists($name)) {
$this->_changes["delete"][$name] = null;
unset($this->_attributes[$name]);
}
}
}
} else {
// Here we have a hash with "attributename" => "value to delete"
foreach ($attr as $name => $values) {
if (is_int($name)) {
// someone mixed modes and gave us just an attribute name
$this->delete($values);
} else {
// mark for update() if this attr was not marked before;
// this time it must consider the selected values also
$name = $this->getAttrName($name);
if ($this->exists($name)) {
if (false == is_array($values)) {
$values = array($values);
}
// save values to be deleted
if (empty($this->_changes["delete"][$name])) {
$this->_changes["delete"][$name] = array();
}
$this->_changes["delete"][$name] =
array_unique(array_merge($this->_changes["delete"][$name], $values));
foreach ($values as $value) {
// find the key for the value that should be deleted
$key = array_search($value, $this->_attributes[$name]);
if (false !== $key) {
// delete the value
unset($this->_attributes[$name][$key]);
}
}
}
}
}
}
$return = true;
return $return;
}
/**
* Replaces attributes or its values
*
* The parameter has to an array of the following form:
* array("attributename" => "single value",
* "attribute2name" => array("value1", "value2"),
* "deleteme1" => null,
* "deleteme2" => "")
* If the attribute does not yet exist it will be added instead (see also $force).
* If the attribue value is null, the attribute will de deleted.
*
* These changes are local to the entry and do
* not affect the entry on the server until {@link update()} is called.
*
* In some cases you are not allowed to read the attributes value (for
* example the ActiveDirectory attribute unicodePwd) but are allowed to
* replace the value. In this case replace() would assume that the attribute
* is not in the directory yet and tries to add it which will result in an
* LDAP_TYPE_OR_VALUE_EXISTS error.
* To force replace mode instead of add, you can set $force to true.
*
* @param array $attr Attributes to replace
* @param bool $force Force replacing mode in case we can't read the attr value but are allowed to replace it
*
* @access public
* @return true|Net_LDAP2_Error
*/
public function replace($attr = array(), $force = false)
{
if (false == is_array($attr)) {
return PEAR::raiseError("Parameter must be an array");
}
foreach ($attr as $k => $v) {
$k = $this->getAttrName($k);
if (false == is_array($v)) {
// delete attributes with empty values; treat ints as string
if (is_int($v)) {
$v = "$v";
}
if ($v == null) {
$this->delete($k);
continue;
} else {
$v = array($v);
}
}
// existing attributes will get replaced
if ($this->exists($k) || $force) {
$this->_changes["replace"][$k] = $v;
$this->_attributes[$k] = $v;
} else {
// new ones just get added
$this->add(array($k => $v));
}
}
$return = true;
return $return;
}
/**
* Update the entry on the directory server
*
* This will evaluate all changes made so far and send them
* to the directory server.
* Please note, that if you make changes to objectclasses wich
* have mandatory attributes set, update() will currently fail.
* Remove the entry from the server and readd it as new in such cases.
* This also will deal with problems with setting structural object classes.
*
* @param Net_LDAP2 $ldap If passed, a call to setLDAP() is issued prior update, thus switching the LDAP-server. This is for perl-ldap interface compliance
*
* @access public
* @return true|Net_LDAP2_Error
* @todo Entry rename with a DN containing special characters needs testing!
*/
public function update($ldap = null)
{
if ($ldap) {
$msg = $this->setLDAP($ldap);
if (Net_LDAP2::isError($msg)) {
return PEAR::raiseError('You passed an invalid $ldap variable to update()');
}
}
// ensure we have a valid LDAP object
$ldap =& $this->getLDAP();
if (!$ldap instanceof Net_LDAP2) {
return PEAR::raiseError("The entries LDAP object is not valid");
}
// Get and check link
$link = $ldap->getLink();
if (!is_resource($link)) {
return PEAR::raiseError("Could not update entry: internal LDAP link is invalid");
}
/*
* Delete the entry
*/
if (true === $this->_delete) {
return $ldap->delete($this);
}
/*
* New entry
*/
if (true === $this->_new) {
$msg = $ldap->add($this);
if (Net_LDAP2::isError($msg)) {
return $msg;
}
$this->_new = false;
$this->_changes['add'] = array();
$this->_changes['delete'] = array();
$this->_changes['replace'] = array();
$this->_original = $this->_attributes;
$return = true;
return $return;
}
/*
* Rename/move entry
*/
if (false == is_null($this->_newdn)) {
if ($ldap->getLDAPVersion() !== 3) {
return PEAR::raiseError("Renaming/Moving an entry is only supported in LDAPv3");
}
// make dn relative to parent (needed for ldap rename)
$parent = Net_LDAP2_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false));
if (Net_LDAP2::isError($parent)) {
return $parent;
}
$child = array_shift($parent);
// maybe the dn consist of a multivalued RDN, we must build the dn in this case
// because the $child-RDN is an array!
if (is_array($child)) {
$child = Net_LDAP2_Util::canonical_dn($child);
}
$parent = Net_LDAP2_Util::canonical_dn($parent);
// rename/move
if (false == @ldap_rename($link, $this->_dn, $child, $parent, true)) {
return PEAR::raiseError("Entry not renamed: " .
@ldap_error($link), @ldap_errno($link));
}
// reflect changes to local copy
$this->_dn = $this->_newdn;
$this->_newdn = null;
}
/*
* Carry out modifications to the entry
*/
// ADD
foreach ($this->_changes["add"] as $attr => $value) {
// if attribute exists, add new values
if ($this->exists($attr)) {
if (false === @ldap_mod_add($link, $this->dn(), array($attr => $value))) {
return PEAR::raiseError("Could not add new values to attribute $attr: " .
@ldap_error($link), @ldap_errno($link));
}
} else {
// new attribute
if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
return PEAR::raiseError("Could not add new attribute $attr: " .
@ldap_error($link), @ldap_errno($link));
}
}
// all went well here, I guess
unset($this->_changes["add"][$attr]);
}
// DELETE
foreach ($this->_changes["delete"] as $attr => $value) {
// In LDAPv3 you need to specify the old values for deleting
if (is_null($value) && $ldap->getLDAPVersion() === 3) {
$value = $this->_original[$attr];
}
if (false === @ldap_mod_del($link, $this->dn(), array($attr => $value))) {
return PEAR::raiseError("Could not delete attribute $attr: " .
@ldap_error($link), @ldap_errno($link));
}
unset($this->_changes["delete"][$attr]);
}
// REPLACE
foreach ($this->_changes["replace"] as $attr => $value) {
if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
return PEAR::raiseError("Could not replace attribute $attr values: " .
@ldap_error($link), @ldap_errno($link));
}
unset($this->_changes["replace"][$attr]);
}
// all went well, so _original (server) becomes _attributes (local copy)
$this->_original = $this->_attributes;
$return = true;
return $return;
}
/**
* Returns the right attribute name
*
* @param string $attr Name of attribute
*
* @access protected
* @return string The right name of the attribute
*/
protected function getAttrName($attr)
{
$name = strtolower($attr);
if (array_key_exists($name, $this->_map)) {
$attr = $this->_map[$name];
}
return $attr;
}
/**
* Returns a reference to the LDAP-Object of this entry
*
* @access public
* @return Net_LDAP2|Net_LDAP2_Error Reference to the Net_LDAP2 Object (the connection) or Net_LDAP2_Error
*/
public function &getLDAP()
{
if (!$this->_ldap instanceof Net_LDAP2) {
$err = new PEAR_Error('LDAP is not a valid Net_LDAP2 object');
return $err;
} else {
return $this->_ldap;
}
}
/**
* Sets a reference to the LDAP-Object of this entry
*
* After setting a Net_LDAP2 object, calling update() will use that object for
* updating directory contents. Use this to dynamicly switch directorys.
*
* @param Net_LDAP2 &$ldap Net_LDAP2 object that this entry should be connected to
*
* @access public
* @return true|Net_LDAP2_Error
*/
public function setLDAP(&$ldap)
{
if (!$ldap instanceof Net_LDAP2) {
return PEAR::raiseError("LDAP is not a valid Net_LDAP2 object");
} else {
$this->_ldap =& $ldap;
return true;
}
}
/**
* Marks the entry as new/existing.
*
* If an Entry is marked as new, it will be added to the directory
* when calling {@link update()}.
* If the entry is marked as old ($mark = false), then the entry is
* assumed to be present in the directory server wich results in
* modification when calling {@link update()}.
*
* @param boolean $mark Value to set, defaults to "true"
*
* @return void
*/
public function markAsNew($mark = true)
{
$this->_new = ($mark)? true : false;
}
/**
* Applies a regular expression onto a single- or multivalued attribute (like preg_match())
*
* This method behaves like PHPs preg_match() but with some exceptions.
* If you want to retrieve match information, then you MUST pass the
* $matches parameter via reference! otherwise you will get no matches.
* Since it is possible to have multi valued attributes the $matches
* array will have a additionally numerical dimension (one for each value):
*
* $matches = array(
* 0 => array (usual preg_match() returnarray),
* 1 => array (usual preg_match() returnarray)
* )
*
* Please note, that $matches will be initialized to an empty array inside.
*
* Usage example:
*
* $result = $entry->preg_match('/089(\d+)/', 'telephoneNumber', &$matches);
* if ( $result === true ){
* echo "First match: ".$matches[0][1]; // Match of value 1, content of first bracket
* } else {
* if ( Net_LDAP2::isError($result) ) {
* echo "Error: ".$result->getMessage();
* } else {
* echo "No match found.";
* }
* }
*
*
* Please note that it is important to test for an Net_LDAP2_Error, because objects are
* evaluating to true by default, thus if an error occured, and you only check using "==" then
* you get misleading results. Use the "identical" (===) operator to test for matches to
* avoid this as shown above.
*
* @param string $regex The regular expression
* @param string $attr_name The attribute to search in
* @param array $matches (optional, PASS BY REFERENCE!) Array to store matches in
*
* @return boolean|Net_LDAP2_Error TRUE, if we had a match in one of the values, otherwise false. Net_LDAP2_Error in case something went wrong
*/
public function pregMatch($regex, $attr_name, $matches = array())
{
$matches = array();
// fetch attribute values
$attr = $this->getValue($attr_name, 'all');
if (Net_LDAP2::isError($attr)) {
return $attr;
} else {
unset($attr['count']);
}
// perform preg_match() on all values
$match = false;
foreach ($attr as $thisvalue) {
$matches_int = array();
if (preg_match($regex, $thisvalue, $matches_int)) {
$match = true;
array_push($matches, $matches_int); // store matches in reference
}
}
return $match;
}
/**
* Alias of {@link pregMatch()} for compatibility to Net_LDAP 1
*
* @see pregMatch()
* @return boolean|Net_LDAP2_Error
*/
public function preg_match()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'pregMatch' ), $args);
}
/**
* Tells if the entry is consiedered as new (not present in the server)
*
* Please note, that this doesn't tell you if the entry is present on the server.
* Use {@link Net_LDAP2::dnExists()} to see if an entry is already there.
*
* @return boolean
*/
public function isNew()
{
return $this->_new;
}
/**
* Is this entry going to be deleted once update() is called?
*
* @return boolean
*/
public function willBeDeleted()
{
return $this->_delete;
}
/**
* Is this entry going to be moved once update() is called?
*
* @return boolean
*/
public function willBeMoved()
{
return ($this->dn() !== $this->currentDN());
}
/**
* Returns always the original DN
*
* If an entry will be moved but {@link update()} was not called,
* {@link dn()} will return the new DN. This method however, returns
* always the current active DN.
*
* @return string
*/
public function currentDN()
{
return $this->_dn;
}
/**
* Returns the attribute changes to be carried out once update() is called
*
* @return array
*/
public function getChanges()
{
return $this->_changes;
}
}
?>
Net_LDAP2-2.0.12/Net/LDAP2/Filter.php 0000644 0001750 0001750 00000053024 11652260337 015117 0 ustar beni beni
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: Filter.php 318470 2011-10-27 12:57:05Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Includes
*/
require_once 'PEAR.php';
require_once 'Net/LDAP2/Util.php';
/**
* Object representation of a part of a LDAP filter.
*
* This Class is not completely compatible to the PERL interface!
*
* The purpose of this class is, that users can easily build LDAP filters
* without having to worry about right escaping etc.
* A Filter is built using several independent filter objects
* which are combined afterwards. This object works in two
* modes, depending how the object is created.
* If the object is created using the {@link create()} method, then this is a leaf-object.
* If the object is created using the {@link combine()} method, then this is a container object.
*
* LDAP filters are defined in RFC-2254 and can be found under
* {@link http://www.ietf.org/rfc/rfc2254.txt}
*
* Here a quick copy&paste example:
*
* $filter0 = Net_LDAP2_Filter::create('stars', 'equals', '***');
* $filter_not0 = Net_LDAP2_Filter::combine('not', $filter0);
*
* $filter1 = Net_LDAP2_Filter::create('gn', 'begins', 'bar');
* $filter2 = Net_LDAP2_Filter::create('gn', 'ends', 'baz');
* $filter_comp = Net_LDAP2_Filter::combine('or',array($filter_not0, $filter1, $filter2));
*
* echo $filter_comp->asString();
* // This will output: (|(!(stars=\0x5c0x2a\0x5c0x2a\0x5c0x2a))(gn=bar*)(gn=*baz))
* // The stars in $filter0 are treaten as real stars unless you disable escaping.
*
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger
* // This will find entries that contain an attribute "sn" that ends with "foobar":
* $filter = Net_LDAP2_Filter::create('sn', 'ends', 'foobar');
*
* // This will find entries that contain an attribute "sn" that has any value set:
* $filter = Net_LDAP2_Filter::create('sn', 'any');
*
* // This will build a negated equals filter:
* $filter = Net_LDAP2_Filter::create('sn', 'not equals', 'foobar');
*
*
* @param string $attr_name Name of the attribute the filter should apply to
* @param string $match Matching rule (equals, begins, ends, contains, greater, less, greaterOrEqual, lessOrEqual, approx, any)
* @param string $value (optional) if given, then this is used as a filter
* @param boolean $escape Should $value be escaped? (default: yes, see {@link Net_LDAP2_Util::escape_filter_value()} for detailed information)
*
* @return Net_LDAP2_Filter|Net_LDAP2_Error
*/
public static function &create($attr_name, $match, $value = '', $escape = true)
{
$leaf_filter = new Net_LDAP2_Filter();
if ($escape) {
$array = Net_LDAP2_Util::escape_filter_value(array($value));
$value = $array[0];
}
$match = strtolower($match);
// detect negation
$neg_matches = array();
$negate_filter = false;
if (preg_match('/^(?:not|!)[\s_-](.+)/', $match, $neg_matches)) {
$negate_filter = true;
$match = $neg_matches[1];
}
// build basic filter
switch ($match) {
case 'equals':
case '=':
case '==':
$leaf_filter->_filter = '(' . $attr_name . '=' . $value . ')';
break;
case 'begins':
$leaf_filter->_filter = '(' . $attr_name . '=' . $value . '*)';
break;
case 'ends':
$leaf_filter->_filter = '(' . $attr_name . '=*' . $value . ')';
break;
case 'contains':
$leaf_filter->_filter = '(' . $attr_name . '=*' . $value . '*)';
break;
case 'greater':
case '>':
$leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')';
break;
case 'less':
case '<':
$leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')';
break;
case 'greaterorequal':
case '>=':
$leaf_filter->_filter = '(' . $attr_name . '>=' . $value . ')';
break;
case 'lessorequal':
case '<=':
$leaf_filter->_filter = '(' . $attr_name . '<=' . $value . ')';
break;
case 'approx':
case '~=':
$leaf_filter->_filter = '(' . $attr_name . '~=' . $value . ')';
break;
case 'any':
case 'present': // alias that may improve user code readability
$leaf_filter->_filter = '(' . $attr_name . '=*)';
break;
default:
return PEAR::raiseError('Net_LDAP2_Filter create error: matching rule "' . $match . '" not known!');
}
// negate if requested
if ($negate_filter) {
$leaf_filter = Net_LDAP2_Filter::combine('!', $leaf_filter);
}
return $leaf_filter;
}
/**
* Combine two or more filter objects using a logical operator
*
* This static method combines two or more filter objects and returns one single
* filter object that contains all the others.
* Call this method statically: $filter = Net_LDAP2_Filter::combine('or', array($filter1, $filter2))
* If the array contains filter strings instead of filter objects, we will try to parse them.
*
* @param string $log_op The locical operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
* @param array|Net_LDAP2_Filter $filters array with Net_LDAP2_Filter objects
*
* @return Net_LDAP2_Filter|Net_LDAP2_Error
* @static
*/
public static function &combine($log_op, $filters)
{
if (PEAR::isError($filters)) {
return $filters;
}
// substitude named operators to logical operators
if ($log_op == 'and') $log_op = '&';
if ($log_op == 'or') $log_op = '|';
if ($log_op == 'not') $log_op = '!';
// tests for sane operation
if ($log_op == '!') {
// Not-combination, here we only accept one filter object or filter string
if ($filters instanceof Net_LDAP2_Filter) {
$filters = array($filters); // force array
} elseif (is_string($filters)) {
$filter_o = self::parse($filters);
if (PEAR::isError($filter_o)) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: '.$filter_o->getMessage());
return $err;
} else {
$filters = array($filter_o);
}
} elseif (is_array($filters)) {
if (count($filters) != 1) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
return $err;
} elseif (!($filters[0] instanceof Net_LDAP2_Filter)) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
return $err;
}
} else {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
return $err;
}
} elseif ($log_op == '&' || $log_op == '|') {
if (!is_array($filters) || count($filters) < 2) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: parameter $filters is not an array or contains less than two Net_LDAP2_Filter objects!');
return $err;
}
} else {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: logical operator is not known!');
return $err;
}
$combined_filter = new Net_LDAP2_Filter();
foreach ($filters as $key => $testfilter) { // check for errors
if (PEAR::isError($testfilter)) {
return $testfilter;
} elseif (is_string($testfilter)) {
// string found, try to parse into an filter object
$filter_o = self::parse($testfilter);
if (PEAR::isError($filter_o)) {
return $filter_o;
} else {
$filters[$key] = $filter_o;
}
} elseif (!$testfilter instanceof Net_LDAP2_Filter) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: invalid object passed in array $filters!');
return $err;
}
}
$combined_filter->_subfilters = $filters;
$combined_filter->_match = $log_op;
return $combined_filter;
}
/**
* Parse FILTER into a Net_LDAP2_Filter object
*
* This parses an filter string into Net_LDAP2_Filter objects.
*
* @param string $FILTER The filter string
*
* @access static
* @return Net_LDAP2_Filter|Net_LDAP2_Error
* @todo Leaf-mode: Do we need to escape at all? what about *-chars?check for the need of encoding values, tackle problems (see code comments)
*/
public static function parse($FILTER)
{
if (preg_match('/^\((.+?)\)$/', $FILTER, $matches)) {
if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
// Subfilter processing: pass subfilters to parse() and combine
// the objects using the logical operator detected
// we have now something like "&(...)(...)(...)" but at least one part ("!(...)").
// Each subfilter could be an arbitary complex subfilter.
// extract logical operator and filter arguments
$log_op = substr($matches[1], 0, 1);
$remaining_component = substr($matches[1], 1);
// split $remaining_component into individual subfilters
// we cannot use split() for this, because we do not know the
// complexiness of the subfilter. Thus, we look trough the filter
// string and just recognize ending filters at the first level.
// We record the index number of the char and use that information
// later to split the string.
$sub_index_pos = array();
$prev_char = ''; // previous character looked at
$level = 0; // denotes the current bracket level we are,
// >1 is too deep, 1 is ok, 0 is outside any
// subcomponent
for ($curpos = 0; $curpos < strlen($remaining_component); $curpos++) {
$cur_char = substr($remaining_component, $curpos, 1);
// rise/lower bracket level
if ($cur_char == '(' && $prev_char != '\\') {
$level++;
} elseif ($cur_char == ')' && $prev_char != '\\') {
$level--;
}
if ($cur_char == '(' && $prev_char == ')' && $level == 1) {
array_push($sub_index_pos, $curpos); // mark the position for splitting
}
$prev_char = $cur_char;
}
// now perform the splits. To get also the last part, we
// need to add the "END" index to the split array
array_push($sub_index_pos, strlen($remaining_component));
$subfilters = array();
$oldpos = 0;
foreach ($sub_index_pos as $s_pos) {
$str_part = substr($remaining_component, $oldpos, $s_pos - $oldpos);
array_push($subfilters, $str_part);
$oldpos = $s_pos;
}
// some error checking...
if (count($subfilters) == 1) {
// only one subfilter found
} elseif (count($subfilters) > 1) {
// several subfilters found
if ($log_op == "!") {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - NOT operator detected but several arguments given!");
}
} else {
// this should not happen unless the user specified a wrong filter
return PEAR::raiseError("Filter parsing error: invalid filter syntax - got operator '$log_op' but no argument!");
}
// Now parse the subfilters into objects and combine them using the operator
$subfilters_o = array();
foreach ($subfilters as $s_s) {
$o = self::parse($s_s);
if (PEAR::isError($o)) {
return $o;
} else {
array_push($subfilters_o, self::parse($s_s));
}
}
$filter_o = self::combine($log_op, $subfilters_o);
return $filter_o;
} else {
// This is one leaf filter component, do some syntax checks, then escape and build filter_o
// $matches[1] should be now something like "foo=bar"
// detect multiple leaf components
// [TODO] Maybe this will make problems with filters containing brackets inside the value
if (stristr($matches[1], ')(')) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!");
} else {
$filter_parts = preg_split('/(?|<|>=|<=)/', $matches[1], 2, PREG_SPLIT_DELIM_CAPTURE);
if (count($filter_parts) != 3) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used");
} else {
$filter_o = new Net_LDAP2_Filter();
// [TODO]: Do we need to escape at all? what about *-chars user provide and that should remain special?
// I think, those prevent escaping! We need to check against PERL Net::LDAP!
// $value_arr = Net_LDAP2_Util::escape_filter_value(array($filter_parts[2]));
// $value = $value_arr[0];
$value = $filter_parts[2];
$filter_o->_filter = '('.$filter_parts[0].$filter_parts[1].$value.')';
return $filter_o;
}
}
}
} else {
// ERROR: Filter components must be enclosed in round brackets
return PEAR::raiseError("Filter parsing error: invalid filter syntax - filter components must be enclosed in round brackets");
}
}
/**
* Get the string representation of this filter
*
* This method runs through all filter objects and creates
* the string representation of the filter. If this
* filter object is a leaf filter, then it will return
* the string representation of this filter.
*
* @return string|Net_LDAP2_Error
*/
public function asString()
{
if ($this->isLeaf()) {
$return = $this->_filter;
} else {
$return = '';
foreach ($this->_subfilters as $filter) {
$return = $return.$filter->asString();
}
$return = '(' . $this->_match . $return . ')';
}
return $return;
}
/**
* Alias for perl interface as_string()
*
* @see asString()
* @return string|Net_LDAP2_Error
*/
public function as_string()
{
return $this->asString();
}
/**
* Print the text representation of the filter to FH, or the currently selected output handle if FH is not given
*
* This method is only for compatibility to the perl interface.
* However, the original method was called "print" but due to PHP language restrictions,
* we can't have a print() method.
*
* @param resource $FH (optional) A filehandle resource
*
* @return true|Net_LDAP2_Error
*/
public function printMe($FH = false)
{
if (!is_resource($FH)) {
if (PEAR::isError($FH)) {
return $FH;
}
$filter_str = $this->asString();
if (PEAR::isError($filter_str)) {
return $filter_str;
} else {
print($filter_str);
}
} else {
$filter_str = $this->asString();
if (PEAR::isError($filter_str)) {
return $filter_str;
} else {
$res = @fwrite($FH, $this->asString());
if ($res == false) {
return PEAR::raiseError("Unable to write filter string to filehandle \$FH!");
}
}
}
return true;
}
/**
* This can be used to escape a string to provide a valid LDAP-Filter.
*
* LDAP will only recognise certain characters as the
* character istself if they are properly escaped. This is
* what this method does.
* The method can be called statically, so you can use it outside
* for your own purposes (eg for escaping only parts of strings)
*
* In fact, this is just a shorthand to {@link Net_LDAP2_Util::escape_filter_value()}.
* For upward compatibiliy reasons you are strongly encouraged to use the escape
* methods provided by the Net_LDAP2_Util class.
*
* @param string $value Any string who should be escaped
*
* @static
* @return string The string $string, but escaped
* @deprecated Do not use this method anymore, instead use Net_LDAP2_Util::escape_filter_value() directly
*/
public static function escape($value)
{
$return = Net_LDAP2_Util::escape_filter_value(array($value));
return $return[0];
}
/**
* Is this a container or a leaf filter object?
*
* @access protected
* @return boolean
*/
protected function isLeaf()
{
if (count($this->_subfilters) > 0) {
return false; // Container!
} else {
return true; // Leaf!
}
}
}
?>
Net_LDAP2-2.0.12/Net/LDAP2/RootDSE.php 0000644 0001750 0001750 00000014200 11652260337 015142 0 ustar beni beni
* @copyright 2009 Jan Wagner
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: RootDSE.php 286718 2009-08-03 07:30:49Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Includes
*/
require_once 'PEAR.php';
/**
* Getting the rootDSE entry of a LDAP server
*
* @category Net
* @package Net_LDAP2
* @author Jan Wagner
* // to sort entries first by location, then by surename, but descending:
* $entries = $search->sorted_as_struct(array('locality','sn'), SORT_DESC);
*
*
* @param array $attrs Array of attribute names to sort; order from left to right.
* @param int $order Ordering direction, either constant SORT_ASC or SORT_DESC
*
* @return array|Net_LDAP2_Error Array with sorted entries or error
* @todo what about server side sorting as specified in http://www.ietf.org/rfc/rfc2891.txt?
*/
public function sorted_as_struct($attrs = array('cn'), $order = SORT_ASC)
{
/*
* Old Code, suitable and fast for single valued sorting
* This code should be used if we know that single valued sorting is desired,
* but we need some method to get that knowledge...
*/
/*
$attrs = array_reverse($attrs);
foreach ($attrs as $attribute) {
if (!ldap_sort($this->_link, $this->_search, $attribute)){
$this->raiseError("Sorting failed for Attribute " . $attribute);
}
}
$results = ldap_get_entries($this->_link, $this->_search);
unset($results['count']); //for tidier output
if ($order) {
return array_reverse($results);
} else {
return $results;
}*/
/*
* New code: complete "client side" sorting
*/
// first some parameterchecks
if (!is_array($attrs)) {
return PEAR::raiseError("Sorting failed: Parameterlist must be an array!");
}
if ($order != SORT_ASC && $order != SORT_DESC) {
return PEAR::raiseError("Sorting failed: sorting direction not understood! (neither constant SORT_ASC nor SORT_DESC)");
}
// fetch the entries data
$entries = $this->as_struct();
// now sort each entries attribute values
// this is neccessary because later we can only sort by one value,
// so we need the highest or lowest attribute now, depending on the
// selected ordering for that specific attribute
foreach ($entries as $dn => $entry) {
foreach ($entry as $attr_name => $attr_values) {
sort($entries[$dn][$attr_name]);
if ($order == SORT_DESC) {
array_reverse($entries[$dn][$attr_name]);
}
}
}
// reformat entrys array for later use with array_multisort()
$to_sort = array(); // <- will be a numeric array similar to ldap_get_entries
foreach ($entries as $dn => $entry_attr) {
$row = array();
$row['dn'] = $dn;
foreach ($entry_attr as $attr_name => $attr_values) {
$row[$attr_name] = $attr_values;
}
$to_sort[] = $row;
}
// Build columns for array_multisort()
// each requested attribute is one row
$columns = array();
foreach ($attrs as $attr_name) {
foreach ($to_sort as $key => $row) {
$columns[$attr_name][$key] =& $to_sort[$key][$attr_name][0];
}
}
// sort the colums with array_multisort, if there is something
// to sort and if we have requested sort columns
if (!empty($to_sort) && !empty($columns)) {
$sort_params = '';
foreach ($attrs as $attr_name) {
$sort_params .= '$columns[\''.$attr_name.'\'], '.$order.', ';
}
eval("array_multisort($sort_params \$to_sort);"); // perform sorting
}
return $to_sort;
}
/**
* Return entries sorted as objects
*
* This returns a array with sorted Net_LDAP2_Entry objects.
* The sorting is actually done with {@link sorted_as_struct()}.
*
* Please note that attribute names are case sensitive!
* Also note, that it is (depending on server capabilitys) possible to let
* the server sort your results. This happens through search controls
* and is described in detail at {@link http://www.ietf.org/rfc/rfc2891.txt}
*
* Usage example:
*
* // to sort entries first by location, then by surename, but descending:
* $entries = $search->sorted(array('locality','sn'), SORT_DESC);
*
*
* @param array $attrs Array of sort attributes to sort; order from left to right.
* @param int $order Ordering direction, either constant SORT_ASC or SORT_DESC
*
* @return array|Net_LDAP2_Error Array with sorted Net_LDAP2_Entries or error
* @todo Entry object construction could be faster. Maybe we could use one of the factorys instead of fetching the entry again
*/
public function sorted($attrs = array('cn'), $order = SORT_ASC)
{
$return = array();
$sorted = $this->sorted_as_struct($attrs, $order);
if (PEAR::isError($sorted)) {
return $sorted;
}
foreach ($sorted as $key => $row) {
$entry = $this->_ldap->getEntry($row['dn'], $this->searchedAttrs());
if (!PEAR::isError($entry)) {
array_push($return, $entry);
} else {
return $entry;
}
}
return $return;
}
/**
* Return entries as array
*
* This method returns the entries and the selected attributes values as
* array.
* The first array level contains all found entries where the keys are the
* DNs of the entries. The second level arrays contian the entries attributes
* such that the keys is the lowercased name of the attribute and the values
* are stored in another indexed array. Note that the attribute values are stored
* in an array even if there is no or just one value.
*
* The array has the following structure:
*
* $return = array(
* 'cn=foo,dc=example,dc=com' => array(
* 'sn' => array('foo'),
* 'multival' => array('val1', 'val2', 'valN')
* )
* 'cn=bar,dc=example,dc=com' => array(
* 'sn' => array('bar'),
* 'multival' => array('val1', 'valN')
* )
* )
*
*
* @return array associative result array as described above
*/
public function as_struct()
{
$return = array();
$entries = $this->entries();
foreach ($entries as $entry) {
$attrs = array();
$entry_attributes = $entry->attributes();
foreach ($entry_attributes as $attr_name) {
$attr_values = $entry->getValue($attr_name, 'all');
if (!is_array($attr_values)) {
$attr_values = array($attr_values);
}
$attrs[$attr_name] = $attr_values;
}
$return[$entry->dn()] = $attrs;
}
return $return;
}
/**
* Set the search objects resource link
*
* @param resource &$search Search result identifier
*
* @access public
* @return void
*/
public function setSearch(&$search)
{
$this->_search = $search;
}
/**
* Set the ldap ressource link
*
* @param resource &$link Link identifier
*
* @access public
* @return void
*/
public function setLink(&$link)
{
$this->_link = $link;
}
/**
* Returns the number of entries in the searchresult
*
* @return int Number of entries in search.
*/
public function count()
{
// this catches the situation where OL returned errno 32 = no such object!
if (!$this->_search) {
return 0;
}
// ldap_count_entries is slow (see pear bug #18752) with large results,
// so we cache the result internally.
if ($this->_count_cache === null) {
$this->_count_cache = @ldap_count_entries($this->_link, $this->_search);
}
return $this->_count_cache;
}
/**
* Get the errorcode the object got in its search.
*
* @return int The ldap error number.
*/
public function getErrorCode()
{
return $this->_errorCode;
}
/**
* Destructor
*
* @access protected
*/
public function _Net_LDAP2_Search()
{
@ldap_free_result($this->_search);
}
/**
* Closes search result
*
* @return void
*/
public function done()
{
$this->_Net_LDAP2_Search();
}
/**
* Return the attribute names this search selected
*
* @return array
* @see $_searchedAttrs
* @access protected
*/
protected function searchedAttrs()
{
return $this->_searchedAttrs;
}
/**
* Tells if this search exceeds a sizelimit
*
* @return boolean
*/
public function sizeLimitExceeded()
{
return ($this->getErrorCode() == 4);
}
/*
* SPL Iterator interface methods.
* This interface allows to use Net_LDAP2_Search
* objects directly inside a foreach loop!
*/
/**
* SPL Iterator interface: Return the current element.
*
* The SPL Iterator interface allows you to fetch entries inside
* a foreach() loop: foreach ($search as $dn => $entry) { ...
*
* Of course, you may call {@link current()}, {@link key()}, {@link next()},
* {@link rewind()} and {@link valid()} yourself.
*
* If the search throwed an error, it returns false.
* False is also returned, if the end is reached
* In case no call to next() was made, we will issue one,
* thus returning the first entry.
*
* @return Net_LDAP2_Entry|false
*/
public function current()
{
if (count($this->_iteratorCache) == 0) {
$this->next();
reset($this->_iteratorCache);
}
$entry = current($this->_iteratorCache);
return ($entry instanceof Net_LDAP2_Entry)? $entry : false;
}
/**
* SPL Iterator interface: Return the identifying key (DN) of the current entry.
*
* @see current()
* @return string|false DN of the current entry; false in case no entry is returned by current()
*/
public function key()
{
$entry = $this->current();
return ($entry instanceof Net_LDAP2_Entry)? $entry->dn() :false;
}
/**
* SPL Iterator interface: Move forward to next entry.
*
* After a call to {@link next()}, {@link current()} will return
* the next entry in the result set.
*
* @see current()
* @return void
*/
public function next()
{
// fetch next entry.
// if we have no entrys anymore, we add false (which is
// returned by shiftEntry()) so current() will complain.
if (count($this->_iteratorCache) - 1 <= $this->count()) {
$this->_iteratorCache[] = $this->shiftEntry();
}
// move on array pointer to current element.
// even if we have added all entries, this will
// ensure proper operation in case we rewind()
next($this->_iteratorCache);
}
/**
* SPL Iterator interface: Check if there is a current element after calls to {@link rewind()} or {@link next()}.
*
* Used to check if we've iterated to the end of the collection.
*
* @see current()
* @return boolean FALSE if there's nothing more to iterate over
*/
public function valid()
{
return ($this->current() instanceof Net_LDAP2_Entry);
}
/**
* SPL Iterator interface: Rewind the Iterator to the first element.
*
* After rewinding, {@link current()} will return the first entry in the result set.
*
* @see current()
* @return void
*/
public function rewind()
{
reset($this->_iteratorCache);
}
}
?>
Net_LDAP2-2.0.12/Net/LDAP2/Util.php 0000644 0001750 0001750 00000055330 11652260337 014611 0 ustar beni beni
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: Util.php 286718 2009-08-03 07:30:49Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Includes
*/
require_once 'PEAR.php';
/**
* Utility Class for Net_LDAP2
*
* This class servers some functionality to the other classes of Net_LDAP2 but most of
* the methods can be used separately as well.
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger
* // Read and parse an ldif-file into Net_LDAP2_Entry objects
* // and print out the DNs. Store the entries for later use.
* require 'Net/LDAP2/LDIF.php';
* $options = array(
* 'onerror' => 'die'
* );
* $entries = array();
* $ldif = new Net_LDAP2_LDIF('test.ldif', 'r', $options);
* do {
* $entry = $ldif->read_entry();
* $dn = $entry->dn();
* echo " done building entry: $dn\n";
* array_push($entries, $entry);
* } while (!$ldif->eof());
* $ldif->done();
*
*
* // write those entries to another file
* $ldif = new Net_LDAP2_LDIF('test.out.ldif', 'w', $options);
* $ldif->write_entry($entries);
* $ldif->done();
*
*
* @category Net
* @package Net_LDAP2
* @author Benedikt Hallinger
* $ldif->someAction();
* if ($ldif->error()) {
* echo "Error: ".$ldif->error()." at input line: ".$ldif->error_lines();
* }
*
*
* @param boolean $as_string If set to true, only the message is returned
*
* @return false|Net_LDAP2_Error
*/
public function error($as_string = false)
{
if (Net_LDAP2::isError($this->_error['error'])) {
return ($as_string)? $this->_error['error']->getMessage() : $this->_error['error'];
} else {
return false;
}
}
/**
* Returns lines that resulted in error.
*
* Perl returns an array of faulty lines in list context,
* but we always just return an int because of PHPs language.
*
* @return int
*/
public function error_lines()
{
return $this->_error['line'];
}
/**
* Returns the current Net::LDAP::Entry object.
*
* @return Net_LDAP2_Entry|false
*/
public function current_entry()
{
return $this->parseLines($this->current_lines());
}
/**
* Parse LDIF lines of one entry into an Net_LDAP2_Entry object
*
* @param array $lines LDIF lines for one entry
*
* @return Net_LDAP2_Entry|false Net_LDAP2_Entry object for those lines
* @todo what about file inclusions and urls? "jpegphoto:< file:///usr/local/directory/photos/fiona.jpg"
*/
public function parseLines($lines)
{
// parse lines into an array of attributes and build the entry
$attributes = array();
$dn = false;
foreach ($lines as $line) {
if (preg_match('/^(\w+(;binary)?)(:|::|:<)\s(.+)$/', $line, $matches)) {
$attr =& $matches[1] . $matches[2];
$delim =& $matches[3];
$data =& $matches[4];
if ($delim == ':') {
// normal data
$attributes[$attr][] = $data;
} elseif ($delim == '::') {
// base64 data
$attributes[$attr][] = base64_decode($data);
} elseif ($delim == ':<') {
// file inclusion
// TODO: Is this the job of the LDAP-client or the server?
$this->dropError('File inclusions are currently not supported');
//$attributes[$attr][] = ...;
} else {
// since the pattern above, the delimeter cannot be something else.
$this->dropError('Net_LDAP2_LDIF parsing error: invalid syntax at parsing entry line: '.$line);
continue;
}
if (strtolower($attr) == 'dn') {
// DN line detected
$dn = $attributes[$attr][0]; // save possibly decoded DN
unset($attributes[$attr]); // remove wrongly added "dn: " attribute
}
} else {
// line not in "attr: value" format -> ignore
// maybe we should rise an error here, but this should be covered by
// next_lines() already. A problem arises, if users try to feed data of
// several entries to this method - the resulting entry will
// get wrong attributes. However, this is already mentioned in the
// methods documentation above.
}
}
if (false === $dn) {
$this->dropError('Net_LDAP2_LDIF parsing error: unable to detect DN for entry');
return false;
} else {
$newentry = Net_LDAP2_Entry::createFresh($dn, $attributes);
return $newentry;
}
}
/**
* Returns the lines that generated the current Net::LDAP::Entry object.
*
* Note that this returns an empty array if no lines have been read so far.
*
* @return array Array of lines
*/
public function current_lines()
{
return $this->_lines_cur;
}
/**
* Returns the lines that will generate the next Net::LDAP::Entry object.
*
* If you set $force to TRUE then you can iterate over the lines that build
* up entries manually. Otherwise, iterating is done using {@link read_entry()}.
* Force will move the file pointer forward, thus returning the next entries lines.
*
* Wrapped lines will be unwrapped. Comments are stripped.
*
* @param boolean $force Set this to true if you want to iterate over the lines manually
*
* @return array
*/
public function next_lines($force = false)
{
// if we already have those lines, just return them, otherwise read
if (count($this->_lines_next) == 0 || $force) {
$this->_lines_next = array(); // empty in case something was left (if used $force)
$entry_done = false;
$fh = &$this->handle();
$commentmode = false; // if we are in an comment, for wrapping purposes
$datalines_read = 0; // how many lines with data we have read
while (!$entry_done && !$this->eof()) {
$this->_input_line++;
// Read line. Remove line endings, we want only data;
// this is okay since ending spaces should be encoded
$data = rtrim(fgets($fh));
if ($data === false) {
// error only, if EOF not reached after fgets() call
if (!$this->eof()) {
$this->dropError('Net_LDAP2_LDIF error: error reading from file at input line '.$this->_input_line, $this->_input_line);
}
break;
} else {
if (count($this->_lines_next) > 0 && preg_match('/^$/', $data)) {
// Entry is finished if we have an empty line after we had data
$entry_done = true;
// Look ahead if the next EOF is nearby. Comments and empty
// lines at the file end may cause problems otherwise
$current_pos = ftell($fh);
$data = fgets($fh);
while (!feof($fh)) {
if (preg_match('/^\s*$/', $data) || preg_match('/^#/', $data)) {
// only empty lines or comments, continue to seek
// TODO: Known bug: Wrappings for comments are okay but are treaten as
// error, since we do not honor comment mode here.
// This should be a very theoretically case, however
// i am willing to fix this if really necessary.
$this->_input_line++;
$current_pos = ftell($fh);
$data = fgets($fh);
} else {
// Data found if non emtpy line and not a comment!!
// Rewind to position prior last read and stop lookahead
fseek($fh, $current_pos);
break;
}
}
// now we have either the file pointer at the beginning of
// a new data position or at the end of file causing feof() to return true
} else {
// build lines
if (preg_match('/^version:\s(.+)$/', $data, $match)) {
// version statement, set version
$this->version($match[1]);
} elseif (preg_match('/^\w+(;binary)?::?\s.+$/', $data)) {
// normal attribute: add line
$commentmode = false;
$this->_lines_next[] = trim($data);
$datalines_read++;
} elseif (preg_match('/^\s(.+)$/', $data, $matches)) {
// wrapped data: unwrap if not in comment mode
// note that the \s above is some more liberal than
// the RFC requests as it also matches tabs etc.
if (!$commentmode) {
if ($datalines_read == 0) {
// first line of entry: wrapped data is illegal
$this->dropError('Net_LDAP2_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line);
} else {
$last = array_pop($this->_lines_next);
$last = $last.$matches[1];
$this->_lines_next[] = $last;
$datalines_read++;
}
}
} elseif (preg_match('/^#/', $data)) {
// LDIF comments
$commentmode = true;
} elseif (preg_match('/^\s*$/', $data)) {
// empty line but we had no data for this
// entry, so just ignore this line
$commentmode = false;
} else {
$this->dropError('Net_LDAP2_LDIF error: invalid syntax at input line '.$this->_input_line, $this->_input_line);
continue;
}
}
}
}
}
return $this->_lines_next;
}
/**
* Convert an attribute and value to LDIF string representation
*
* It honors correct encoding of values according to RFC 2849.
* Line wrapping will occur at the configured maximum but only if
* the value is greater than 40 chars.
*
* @param string $attr_name Name of the attribute
* @param string $attr_value Value of the attribute
*
* @access protected
* @return string LDIF string for that attribute and value
*/
protected function convertAttribute($attr_name, $attr_value)
{
// Handle empty attribute or process
if (strlen($attr_value) == 0) {
$attr_value = " ";
} else {
$base64 = false;
// ASCII-chars that are NOT safe for the
// start and for being inside the value.
// These are the int values of those chars.
$unsafe_init = array(0, 10, 13, 32, 58, 60);
$unsafe = array(0, 10, 13);
// Test for illegal init char
$init_ord = ord(substr($attr_value, 0, 1));
if ($init_ord > 127 || in_array($init_ord, $unsafe_init)) {
$base64 = true;
}
// Test for illegal content char
for ($i = 0; $i < strlen($attr_value); $i++) {
$char_ord = ord(substr($attr_value, $i, 1));
if ($char_ord > 127 || in_array($char_ord, $unsafe)) {
$base64 = true;
}
}
// Test for ending space
if (substr($attr_value, -1) == ' ') {
$base64 = true;
}
// If converting is needed, do it
// Either we have some special chars or a matching "raw" regex
if ($base64 || ($this->_options['raw'] && preg_match($this->_options['raw'], $attr_name))) {
$attr_name .= ':';
$attr_value = base64_encode($attr_value);
}
// Lowercase attr names if requested
if ($this->_options['lowercase']) $attr_name = strtolower($attr_name);
// Handle line wrapping
if ($this->_options['wrap'] > 40 && strlen($attr_value) > $this->_options['wrap']) {
$attr_value = wordwrap($attr_value, $this->_options['wrap'], PHP_EOL." ", true);
}
}
return $attr_name.': '.$attr_value;
}
/**
* Convert an entries DN to LDIF string representation
*
* It honors correct encoding of values according to RFC 2849.
*
* @param string $dn UTF8-Encoded DN
*
* @access protected
* @return string LDIF string for that DN
* @todo I am not sure, if the UTF8 stuff is correctly handled right now
*/
protected function convertDN($dn)
{
$base64 = false;
// ASCII-chars that are NOT safe for the
// start and for being inside the dn.
// These are the int values of those chars.
$unsafe_init = array(0, 10, 13, 32, 58, 60);
$unsafe = array(0, 10, 13);
// Test for illegal init char
$init_ord = ord(substr($dn, 0, 1));
if ($init_ord >= 127 || in_array($init_ord, $unsafe_init)) {
$base64 = true;
}
// Test for illegal content char
for ($i = 0; $i < strlen($dn); $i++) {
$char = substr($dn, $i, 1);
if (ord($char) >= 127 || in_array($init_ord, $unsafe)) {
$base64 = true;
}
}
// Test for ending space
if (substr($dn, -1) == ' ') {
$base64 = true;
}
// if converting is needed, do it
return ($base64)? 'dn:: '.base64_encode($dn) : 'dn: '.$dn;
}
/**
* Writes an attribute to the filehandle
*
* @param string $attr_name Name of the attribute
* @param string|array $attr_values Single attribute value or array with attribute values
*
* @access protected
* @return void
*/
protected function writeAttribute($attr_name, $attr_values)
{
// write out attribute content
if (!is_array($attr_values)) {
$attr_values = array($attr_values);
}
foreach ($attr_values as $attr_val) {
$line = $this->convertAttribute($attr_name, $attr_val).PHP_EOL;
$this->writeLine($line, 'Net_LDAP2_LDIF error: unable to write attribute '.$attr_name.' of entry '.$this->_entrynum);
}
}
/**
* Writes a DN to the filehandle
*
* @param string $dn DN to write
*
* @access protected
* @return void
*/
protected function writeDN($dn)
{
// prepare DN
if ($this->_options['encode'] == 'base64') {
$dn = $this->convertDN($dn).PHP_EOL;
} elseif ($this->_options['encode'] == 'canonical') {
$dn = Net_LDAP2_Util::canonical_dn($dn, array('casefold' => 'none')).PHP_EOL;
} else {
$dn = $dn.PHP_EOL;
}
$this->writeLine($dn, 'Net_LDAP2_LDIF error: unable to write DN of entry '.$this->_entrynum);
}
/**
* Finishes an LDIF entry
*
* @access protected
* @return void
*/
protected function finishEntry()
{
$this->writeLine(PHP_EOL, 'Net_LDAP2_LDIF error: unable to close entry '.$this->_entrynum);
}
/**
* Just write an arbitary line to the filehandle
*
* @param string $line Content to write
* @param string $error If error occurs, drop this message
*
* @access protected
* @return true|false
*/
protected function writeLine($line, $error = 'Net_LDAP2_LDIF error: unable to write to filehandle')
{
if (is_resource($this->handle()) && fwrite($this->handle(), $line, strlen($line)) === false) {
$this->dropError($error);
return false;
} else {
return true;
}
}
/**
* Optionally raises an error and pushes the error on the error cache
*
* @param string $msg Errortext
* @param int $line Line in the LDIF that caused the error
*
* @access protected
* @return void
*/
protected function dropError($msg, $line = null)
{
$this->_error['error'] = new Net_LDAP2_Error($msg);
if ($line !== null) $this->_error['line'] = $line;
if ($this->_options['onerror'] == 'die') {
die($msg.PHP_EOL);
} elseif ($this->_options['onerror'] == 'warn') {
echo $msg.PHP_EOL;
}
}
}
?>
Net_LDAP2-2.0.12/Net/LDAP2/SchemaCache.interface.php 0000644 0001750 0001750 00000004036 11652260337 017754 0 ustar beni beni
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: SchemaCache.interface.php 286718 2009-08-03 07:30:49Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Interface describing a custom schema cache object
*
* To implement a custom schema cache, one must implement this interface and
* pass the instanciated object to Net_LDAP2s registerSchemaCache() method.
*/
interface Net_LDAP2_SchemaCache
{
/**
* Return the schema object from the cache
*
* Net_LDAP2 will consider anything returned invalid, except
* a valid Net_LDAP2_Schema object.
* In case you return a Net_LDAP2_Error, this error will be routed
* to the return of the $ldap->schema() call.
* If you return something else, Net_LDAP2 will
* fetch a fresh Schema object from the LDAP server.
*
* You may want to implement a cache aging mechanism here too.
*
* @return Net_LDAP2_Schema|Net_LDAP2_Error|false
*/
public function loadSchema();
/**
* Store a schema object in the cache
*
* This method will be called, if Net_LDAP2 has fetched a fresh
* schema object from LDAP and wants to init or refresh the cache.
*
* In case of errors you may return a Net_LDAP2_Error which will
* be routet to the client.
* Note that doing this prevents, that the schema object fetched from LDAP
* will be given back to the client, so only return errors if storing
* of the cache is something crucial (e.g. for doing something else with it).
* Normaly you dont want to give back errors in which case Net_LDAP2 needs to
* fetch the schema once per script run and instead use the error
* returned from loadSchema().
*
* @return true|Net_LDAP2_Error
*/
public function storeSchema($schema);
}
Net_LDAP2-2.0.12/Net/LDAP2/SimpleFileSchemaCache.php 0000644 0001750 0001750 00000006127 11652260337 017772 0 ustar beni beni
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: SimpleFileSchemaCache.php 286718 2009-08-03 07:30:49Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* A simple file based schema cacher with cache aging.
*
* Once the cache is too old, the loadSchema() method will return false, so
* Net_LDAP2 will fetch a fresh object from the LDAP server that will
* overwrite the current (outdated) old cache.
*/
class Net_LDAP2_SimpleFileSchemaCache implements Net_LDAP2_SchemaCache
{
/**
* Internal config of this cache
*
* @see Net_LDAP2_SimpleFileSchemaCache()
* @var array
*/
protected $config = array(
'path' => '/tmp/Net_LDAP_Schema.cache',
'max_age' => 1200
);
/**
* Initialize the simple cache
*
* Config is as following:
* path Complete path to the cache file.
* max_age Maximum age of cache in seconds, 0 means "endlessly".
*
* @param array $cfg Config array
*/
public function Net_LDAP2_SimpleFileSchemaCache($cfg)
{
foreach ($cfg as $key => $value) {
if (array_key_exists($key, $this->config)) {
if (gettype($this->config[$key]) != gettype($value)) {
$this->getCore()->dropFatalError(__CLASS__.": Could not set config! Key $key does not match type ".gettype($this->config[$key])."!");
}
$this->config[$key] = $value;
} else {
$this->getCore()->dropFatalError(__CLASS__.": Could not set config! Key $key is not defined!");
}
}
}
/**
* Return the schema object from the cache
*
* If file is existent and cache has not expired yet,
* then the cache is deserialized and returned.
*
* @return Net_LDAP2_Schema|Net_LDAP2_Error|false
*/
public function loadSchema()
{
$return = false; // Net_LDAP2 will load schema from LDAP
if (file_exists($this->config['path'])) {
$cache_maxage = filemtime($this->config['path']) + $this->config['max_age'];
if (time() <= $cache_maxage || $this->config['max_age'] == 0) {
$return = unserialize(file_get_contents($this->config['path']));
}
}
return $return;
}
/**
* Store a schema object in the cache
*
* This method will be called, if Net_LDAP2 has fetched a fresh
* schema object from LDAP and wants to init or refresh the cache.
*
* To invalidate the cache and cause Net_LDAP2 to refresh the cache,
* you can call this method with null or false as value.
* The next call to $ldap->schema() will then refresh the caches object.
*
* @param mixed $schema The object that should be cached
* @return true|Net_LDAP2_Error|false
*/
public function storeSchema($schema) {
file_put_contents($this->config['path'], serialize($schema));
return true;
}
}
Net_LDAP2-2.0.12/Net/LDAP2.php 0000644 0001750 0001750 00000210135 11652260337 013670 0 ustar beni beni
* @author Jan Wagner
* @author Benedikt Hallinger
* @author Benedikt Hallinger $ldap = Net_LDAP2::connect($ldap_config);
*
* @param array $config Configuration array
*
* @access protected
* @return void
* @see $_config
*/
public function __construct($config = array())
{
$this->PEAR('Net_LDAP2_Error');
$this->setConfig($config);
}
/**
* Sets the internal configuration array
*
* @param array $config Configuration array
*
* @access protected
* @return void
*/
protected function setConfig($config)
{
//
// Parameter check -- probably should raise an error here if config
// is not an array.
//
if (! is_array($config)) {
return;
}
foreach ($config as $k => $v) {
if (isset($this->_config[$k])) {
$this->_config[$k] = $v;
} else {
// map old (Net_LDAP2) parms to new ones
switch($k) {
case "dn":
$this->_config["binddn"] = $v;
break;
case "password":
$this->_config["bindpw"] = $v;
break;
case "tls":
$this->_config["starttls"] = $v;
break;
case "base":
$this->_config["basedn"] = $v;
break;
}
}
}
//
// Ensure the host list is an array.
//
if (is_array($this->_config['host'])) {
$this->_host_list = $this->_config['host'];
} else {
if (strlen($this->_config['host']) > 0) {
$this->_host_list = array($this->_config['host']);
} else {
$this->_host_list = array();
// ^ this will cause an error in performConnect(),
// so the user is notified about the failure
}
}
//
// Reset the down host list, which seems like a sensible thing to do
// if the config is being reset for some reason.
//
$this->_down_host_list = array();
}
/**
* Bind or rebind to the ldap-server
*
* This function binds with the given dn and password to the server. In case
* no connection has been made yet, it will be started and startTLS issued
* if appropiate.
*
* The internal bind configuration is not being updated, so if you call
* bind() without parameters, you can rebind with the credentials
* provided at first connecting to the server.
*
* @param string $dn Distinguished name for binding
* @param string $password Password for binding
*
* @access public
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
*/
public function bind($dn = null, $password = null)
{
// fetch current bind credentials
if (is_null($dn)) {
$dn = $this->_config["binddn"];
}
if (is_null($password)) {
$password = $this->_config["bindpw"];
}
// Connect first, if we haven't so far.
// This will also bind us to the server.
if ($this->_link === false) {
// store old credentials so we can revert them later
// then overwrite config with new bind credentials
$olddn = $this->_config["binddn"];
$oldpw = $this->_config["bindpw"];
// overwrite bind credentials in config
// so performConnect() knows about them
$this->_config["binddn"] = $dn;
$this->_config["bindpw"] = $password;
// try to connect with provided credentials
$msg = $this->performConnect();
// reset to previous config
$this->_config["binddn"] = $olddn;
$this->_config["bindpw"] = $oldpw;
// see if bind worked
if (self::isError($msg)) {
return $msg;
}
} else {
// do the requested bind as we are
// asked to bind manually
if (is_null($dn)) {
// anonymous bind
$msg = @ldap_bind($this->_link);
} else {
// privileged bind
$msg = @ldap_bind($this->_link, $dn, $password);
}
if (false === $msg) {
return PEAR::raiseError("Bind failed: " .
@ldap_error($this->_link),
@ldap_errno($this->_link));
}
}
return true;
}
/**
* Connect to the ldap-server
*
* This function connects to the LDAP server specified in
* the configuration, binds and set up the LDAP protocol as needed.
*
* @access protected
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
*/
protected function performConnect()
{
// Note: Connecting is briefly described in RFC1777.
// Basicly it works like this:
// 1. set up TCP connection
// 2. secure that connection if neccessary
// 3a. setLDAPVersion to tell server which version we want to speak
// 3b. perform bind
// 3c. setLDAPVersion to tell server which version we want to speak
// together with a test for supported versions
// 4. set additional protocol options
// Return true if we are already connected.
if ($this->_link !== false) {
return true;
}
// Connnect to the LDAP server if we are not connected. Note that
// with some LDAP clients, ldapperformConnect returns a link value even
// if no connection is made. We need to do at least one anonymous
// bind to ensure that a connection is actually valid.
//
// Ref: http://www.php.net/manual/en/function.ldap-connect.php
// Default error message in case all connection attempts
// fail but no message is set
$current_error = new PEAR_Error('Unknown connection error');
// Catch empty $_host_list arrays.
if (!is_array($this->_host_list) || count($this->_host_list) == 0) {
$current_error = PEAR::raiseError('No Servers configured! Please '.
'pass in an array of servers to Net_LDAP2');
return $current_error;
}
// Cycle through the host list.
foreach ($this->_host_list as $host) {
// Ensure we have a valid string for host name
if (is_array($host)) {
$current_error = PEAR::raiseError('No Servers configured! '.
'Please pass in an one dimensional array of servers to '.
'Net_LDAP2! (multidimensional array detected!)');
continue;
}
// Skip this host if it is known to be down.
if (in_array($host, $this->_down_host_list)) {
continue;
}
// Record the host that we are actually connecting to in case
// we need it later.
$this->_config['host'] = $host;
// Attempt a connection.
$this->_link = @ldap_connect($host, $this->_config['port']);
if (false === $this->_link) {
$current_error = PEAR::raiseError('Could not connect to ' .
$host . ':' . $this->_config['port']);
$this->_down_host_list[] = $host;
continue;
}
// If we're supposed to use TLS, do so before we try to bind,
// as some strict servers only allow binding via secure connections
if ($this->_config["starttls"] === true) {
if (self::isError($msg = $this->startTLS())) {
$current_error = $msg;
$this->_link = false;
$this->_down_host_list[] = $host;
continue;
}
}
// Try to set the configured LDAP version on the connection if LDAP
// server needs that before binding (eg OpenLDAP).
// This could be necessary since rfc-1777 states that the protocol version
// has to be set at the bind request.
// We use force here which means that the test in the rootDSE is skipped;
// this is neccessary, because some strict LDAP servers only allow to
// read the LDAP rootDSE (which tells us the supported protocol versions)
// with authenticated clients.
// This may fail in which case we try again after binding.
// In this case, most probably the bind() or setLDAPVersion()-call
// below will also fail, providing error messages.
$version_set = false;
$ignored_err = $this->setLDAPVersion(0, true);
if (!self::isError($ignored_err)) {
$version_set = true;
}
// Attempt to bind to the server. If we have credentials configured,
// we try to use them, otherwise its an anonymous bind.
// As stated by RFC-1777, the bind request should be the first
// operation to be performed after the connection is established.
// This may give an protocol error if the server does not support
// V2 binds and the above call to setLDAPVersion() failed.
// In case the above call failed, we try an V2 bind here and set the
// version afterwards (with checking to the rootDSE).
$msg = $this->bind();
if (self::isError($msg)) {
// The bind failed, discard link and save error msg.
// Then record the host as down and try next one
if ($msg->getCode() == 0x02 && !$version_set) {
// provide a finer grained error message
// if protocol error arieses because of invalid version
$msg = new Net_LDAP2_Error($msg->getMessage().
" (could not set LDAP protocol version to ".
$this->_config['version'].")",
$msg->getCode());
}
$this->_link = false;
$current_error = $msg;
$this->_down_host_list[] = $host;
continue;
}
// Set desired LDAP version if not successfully set before.
// Here, a check against the rootDSE is performed, so we get a
// error message if the server does not support the version.
// The rootDSE entry should tell us which LDAP versions are
// supported. However, some strict LDAP servers only allow
// bound suers to read the rootDSE.
if (!$version_set) {
if (self::isError($msg = $this->setLDAPVersion())) {
$current_error = $msg;
$this->_link = false;
$this->_down_host_list[] = $host;
continue;
}
}
// Set LDAP parameters, now we know we have a valid connection.
if (isset($this->_config['options']) &&
is_array($this->_config['options']) &&
count($this->_config['options'])) {
foreach ($this->_config['options'] as $opt => $val) {
$err = $this->setOption($opt, $val);
if (self::isError($err)) {
$current_error = $err;
$this->_link = false;
$this->_down_host_list[] = $host;
continue 2;
}
}
}
// At this stage we have connected, bound, and set up options,
// so we have a known good LDAP server. Time to go home.
return true;
}
// All connection attempts have failed, return the last error.
return $current_error;
}
/**
* Reconnect to the ldap-server.
*
* In case the connection to the LDAP
* service has dropped out for some reason, this function will reconnect,
* and re-bind if a bind has been attempted in the past. It is probably
* most useful when the server list provided to the new() or connect()
* function is an array rather than a single host name, because in that
* case it will be able to connect to a failover or secondary server in
* case the primary server goes down.
*
* This doesn't return anything, it just tries to re-establish
* the current connection. It will sleep for the current backoff
* period (seconds) before attempting the connect, and if the
* connection fails it will double the backoff period, but not
* try again. If you want to ensure a reconnection during a
* transient period of server downtime then you need to call this
* function in a loop.
*
* @access protected
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
*/
protected function performReconnect()
{
// Return true if we are already connected.
if ($this->_link !== false) {
return true;
}
// Default error message in case all connection attempts
// fail but no message is set
$current_error = new PEAR_Error('Unknown connection error');
// Sleep for a backoff period in seconds.
sleep($this->_config['current_backoff']);
// Retry all available connections.
$this->_down_host_list = array();
$msg = $this->performConnect();
// Bail out if that fails.
if (self::isError($msg)) {
$this->_config['current_backoff'] =
$this->_config['current_backoff'] * 2;
if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
$this->_config['current_backoff'] = $this->_config['max_backoff'];
}
return $msg;
}
// Now we should be able to safely (re-)bind.
$msg = $this->bind();
if (self::isError($msg)) {
$this->_config['current_backoff'] = $this->_config['current_backoff'] * 2;
if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
$this->_config['current_backoff'] = $this->_config['max_backoff'];
}
// _config['host'] should have had the last connected host stored in it
// by performConnect(). Since we are unable to bind to that host we can safely
// assume that it is down or has some other problem.
$this->_down_host_list[] = $this->_config['host'];
return $msg;
}
// At this stage we have connected, bound, and set up options,
// so we have a known good LDAP server. Time to go home.
$this->_config['current_backoff'] = $this->_config['min_backoff'];
return true;
}
/**
* Starts an encrypted session
*
* @access public
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
*/
public function startTLS()
{
/* Test to see if the server supports TLS first.
This is done via testing the extensions offered by the server.
The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
Note, that not all servers allow to feth either the rootDSE or
attributes over an unencrypted channel, so we must ignore errors. */
$rootDSE = $this->rootDse();
if (self::isError($rootDSE)) {
/* IGNORE this error, because server may refuse fetching the
RootDSE over an unencrypted connection. */
//return $this->raiseError("Unable to fetch rootDSE entry ".
//"to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
} else {
/* Fetch suceeded, see, if the server supports TLS. Again, we
ignore errors, because the server may refuse to return
attributes over unencryted connections. */
$supported_extensions = $rootDSE->getValue('supportedExtension');
if (self::isError($supported_extensions)) {
/* IGNORE error, because server may refuse attribute
returning over an unencrypted connection. */
//return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
//"to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
} else {
// fetch succeedet, lets see if the server supports it.
// if not, then drop an error. If supported, then do nothing,
// because then we try to issue TLS afterwards.
if (!in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
return $this->raiseError("Server reports that it does not support TLS.");
}
}
}
// Try to establish TLS.
if (false === @ldap_start_tls($this->_link)) {
// Starting TLS failed. This may be an error, or because
// the server does not support it but did not enable us to
// detect that above.
return $this->raiseError("TLS could not be started: " .
@ldap_error($this->_link),
@ldap_errno($this->_link));
} else {
return true; // TLS is started now.
}
}
/**
* alias function of startTLS() for perl-ldap interface
*
* @return void
* @see startTLS()
*/
public function start_tls()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'startTLS' ), $args);
}
/**
* Close LDAP connection.
*
* Closes the connection. Use this when the session is over.
*
* @return void
*/
public function done()
{
$this->_Net_LDAP2();
}
/**
* Alias for {@link done()}
*
* @return void
* @see done()
*/
public function disconnect()
{
$this->done();
}
/**
* Destructor
*
* @access protected
*/
public function _Net_LDAP2()
{
@ldap_close($this->_link);
}
/**
* Add a new entryobject to a directory.
*
* Use add to add a new Net_LDAP2_Entry object to the directory.
* This also links the entry to the connection used for the add,
* if it was a fresh entry ({@link Net_LDAP2_Entry::createFresh()})
*
* @param Net_LDAP2_Entry &$entry Net_LDAP2_Entry
*
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
*/
public function add(&$entry)
{
if (!$entry instanceof Net_LDAP2_Entry) {
return PEAR::raiseError('Parameter to Net_LDAP2::add() must be a Net_LDAP2_Entry object.');
}
// Continue attempting the add operation in a loop until we
// get a success, a definitive failure, or the world ends.
$foo = 0;
while (true) {
$link = $this->getLink();
if ($link === false) {
// We do not have a successful connection yet. The call to
// getLink() would have kept trying if we wanted one. Go
// home now.
return PEAR::raiseError("Could not add entry " . $entry->dn() .
" no valid LDAP connection could be found.");
}
if (@ldap_add($link, $entry->dn(), $entry->getValues())) {
// entry successfully added, we should update its $ldap reference
// in case it is not set so far (fresh entry)
if (!$entry->getLDAP() instanceof Net_LDAP2) {
$entry->setLDAP($this);
}
// store, that the entry is present inside the directory
$entry->markAsNew(false);
return true;
} else {
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = @ldap_errno($link);
$error_name = Net_LDAP2::errorMessage($error_code);
if (($error_name === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
// operation. We should try again, possibly with a different
// server.
$this->_link = false;
$this->performReconnect();
} else {
// Errors other than the above catched are just passed
// back to the user so he may react upon them.
return PEAR::raiseError("Could not add entry " . $entry->dn() . " " .
$error_name,
$error_code);
}
}
}
}
/**
* Delete an entry from the directory
*
* The object may either be a string representing the dn or a Net_LDAP2_Entry
* object. When the boolean paramter recursive is set, all subentries of the
* entry will be deleted as well.
*
* @param string|Net_LDAP2_Entry $dn DN-string or Net_LDAP2_Entry
* @param boolean $recursive Should we delete all children recursive as well?
*
* @access public
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
*/
public function delete($dn, $recursive = false)
{
if ($dn instanceof Net_LDAP2_Entry) {
$dn = $dn->dn();
}
if (false === is_string($dn)) {
return PEAR::raiseError("Parameter is not a string nor an entry object!");
}
// Recursive delete searches for children and calls delete for them
if ($recursive) {
$result = @ldap_list($this->_link, $dn, '(objectClass=*)', array(null), 0, 0);
if (@ldap_count_entries($this->_link, $result)) {
$subentry = @ldap_first_entry($this->_link, $result);
$this->delete(@ldap_get_dn($this->_link, $subentry), true);
while ($subentry = @ldap_next_entry($this->_link, $subentry)) {
$this->delete(@ldap_get_dn($this->_link, $subentry), true);
}
}
}
// Continue attempting the delete operation in a loop until we
// get a success, a definitive failure, or the world ends.
while (true) {
$link = $this->getLink();
if ($link === false) {
// We do not have a successful connection yet. The call to
// getLink() would have kept trying if we wanted one. Go
// home now.
return PEAR::raiseError("Could not add entry " . $dn .
" no valid LDAP connection could be found.");
}
if (@ldap_delete($link, $dn)) {
// entry successfully deleted.
return true;
} else {
// We have a failure. What type?
// We may be able to reconnect and try again.
$error_code = @ldap_errno($link);
$error_name = Net_LDAP2::errorMessage($error_code);
if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
// operation. We should try again, possibly with a
// different server.
$this->_link = false;
$this->performReconnect();
} elseif ($error_code == 66) {
// Subentries present, server refused to delete.
// Deleting subentries is the clients responsibility, but
// since the user may not know of the subentries, we do not
// force that here but instead notify the developer so he
// may take actions himself.
return PEAR::raiseError("Could not delete entry $dn because of subentries. Use the recursive parameter to delete them.");
} else {
// Errors other than the above catched are just passed
// back to the user so he may react upon them.
return PEAR::raiseError("Could not delete entry " . $dn . " " .
$error_name,
$error_code);
}
}
}
}
/**
* Modify an ldapentry directly on the server
*
* This one takes the DN or a Net_LDAP2_Entry object and an array of actions.
* This array should be something like this:
*
* array('add' => array('attribute1' => array('val1', 'val2'),
* 'attribute2' => array('val1')),
* 'delete' => array('attribute1'),
* 'replace' => array('attribute1' => array('val1')),
* 'changes' => array('add' => ...,
* 'replace' => ...,
* 'delete' => array('attribute1', 'attribute2' => array('val1')))
*
* The changes array is there so the order of operations can be influenced
* (the operations are done in order of appearance).
* The order of execution is as following:
* 1. adds from 'add' array
* 2. deletes from 'delete' array
* 3. replaces from 'replace' array
* 4. changes (add, replace, delete) in order of appearance
* All subarrays (add, replace, delete, changes) may be given at the same time.
*
* The function calls the corresponding functions of an Net_LDAP2_Entry
* object. A detailed description of array structures can be found there.
*
* Unlike the modification methods provided by the Net_LDAP2_Entry object,
* this method will instantly carry out an update() after each operation,
* thus modifying "directly" on the server.
*
* @param string|Net_LDAP2_Entry $entry DN-string or Net_LDAP2_Entry
* @param array $parms Array of changes
*
* @access public
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
*/
public function modify($entry, $parms = array())
{
if (is_string($entry)) {
$entry = $this->getEntry($entry);
if (self::isError($entry)) {
return $entry;
}
}
if (!$entry instanceof Net_LDAP2_Entry) {
return PEAR::raiseError("Parameter is not a string nor an entry object!");
}
// Perform changes mentioned separately
foreach (array('add', 'delete', 'replace') as $action) {
if (isset($parms[$action])) {
$msg = $entry->$action($parms[$action]);
if (self::isError($msg)) {
return $msg;
}
$entry->setLDAP($this);
// Because the @ldap functions are called inside Net_LDAP2_Entry::update(),
// we have to trap the error codes issued from that if we want to support
// reconnection.
while (true) {
$msg = $entry->update();
if (self::isError($msg)) {
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = $msg->getCode();
$error_name = Net_LDAP2::errorMessage($error_code);
if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
// operation. We should try again, possibly with a different
// server.
$this->_link = false;
$this->performReconnect();
} else {
// Errors other than the above catched are just passed
// back to the user so he may react upon them.
return PEAR::raiseError("Could not modify entry: ".$msg->getMessage());
}
} else {
// modification succeedet, evaluate next change
break;
}
}
}
}
// perform combined changes in 'changes' array
if (isset($parms['changes']) && is_array($parms['changes'])) {
foreach ($parms['changes'] as $action => $value) {
// Because the @ldap functions are called inside Net_LDAP2_Entry::update,
// we have to trap the error codes issued from that if we want to support
// reconnection.
while (true) {
$msg = $this->modify($entry, array($action => $value));
if (self::isError($msg)) {
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = $msg->getCode();
$error_name = Net_LDAP2::errorMessage($error_code);
if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
// operation. We should try again, possibly with a different
// server.
$this->_link = false;
$this->performReconnect();
} else {
// Errors other than the above catched are just passed
// back to the user so he may react upon them.
return $msg;
}
} else {
// modification succeedet, evaluate next change
break;
}
}
}
}
return true;
}
/**
* Run a ldap search query
*
* Search is used to query the ldap-database.
* $base and $filter may be ommitted. The one from config will
* then be used. $base is either a DN-string or an Net_LDAP2_Entry
* object in which case its DN willb e used.
*
* Params may contain:
*
* scope: The scope which will be used for searching
* base - Just one entry
* sub - The whole tree
* one - Immediately below $base
* sizelimit: Limit the number of entries returned (default: 0 = unlimited),
* timelimit: Limit the time spent for searching (default: 0 = unlimited),
* attrsonly: If true, the search will only return the attribute names,
* attributes: Array of attribute names, which the entry should contain.
* It is good practice to limit this to just the ones you need.
* [NOT IMPLEMENTED]
* deref: By default aliases are dereferenced to locate the base object for the search, but not when
* searching subordinates of the base object. This may be changed by specifying one of the
* following values:
*
* never - Do not dereference aliases in searching or in locating the base object of the search.
* search - Dereference aliases in subordinates of the base object in searching, but not in
* locating the base object of the search.
* find
* always
*
* Please note, that you cannot override server side limitations to sizelimit
* and timelimit: You can always only lower a given limit.
*
* @param string|Net_LDAP2_Entry $base LDAP searchbase
* @param string|Net_LDAP2_Filter $filter LDAP search filter or a Net_LDAP2_Filter object
* @param array $params Array of options
*
* @access public
* @return Net_LDAP2_Search|Net_LDAP2_Error Net_LDAP2_Search object or Net_LDAP2_Error object
* @todo implement search controls (sorting etc)
*/
public function search($base = null, $filter = null, $params = array())
{
if (is_null($base)) {
$base = $this->_config['basedn'];
}
if ($base instanceof Net_LDAP2_Entry) {
$base = $base->dn(); // fetch DN of entry, making searchbase relative to the entry
}
if (is_null($filter)) {
$filter = $this->_config['filter'];
}
if ($filter instanceof Net_LDAP2_Filter) {
$filter = $filter->asString(); // convert Net_LDAP2_Filter to string representation
}
if (PEAR::isError($filter)) {
return $filter;
}
if (PEAR::isError($base)) {
return $base;
}
/* setting searchparameters */
(isset($params['sizelimit'])) ? $sizelimit = $params['sizelimit'] : $sizelimit = 0;
(isset($params['timelimit'])) ? $timelimit = $params['timelimit'] : $timelimit = 0;
(isset($params['attrsonly'])) ? $attrsonly = $params['attrsonly'] : $attrsonly = 0;
(isset($params['attributes'])) ? $attributes = $params['attributes'] : $attributes = array();
// Ensure $attributes to be an array in case only one
// attribute name was given as string
if (!is_array($attributes)) {
$attributes = array($attributes);
}
// reorganize the $attributes array index keys
// sometimes there are problems with not consecutive indexes
$attributes = array_values($attributes);
// scoping makes searches faster!
$scope = (isset($params['scope']) ? $params['scope'] : $this->_config['scope']);
switch ($scope) {
case 'one':
$search_function = 'ldap_list';
break;
case 'base':
$search_function = 'ldap_read';
break;
default:
$search_function = 'ldap_search';
}
// Continue attempting the search operation until we get a success
// or a definitive failure.
while (true) {
$link = $this->getLink();
$search = @call_user_func($search_function,
$link,
$base,
$filter,
$attributes,
$attrsonly,
$sizelimit,
$timelimit);
if ($err = @ldap_errno($link)) {
if ($err == 32) {
// Errorcode 32 = no such object, i.e. a nullresult.
return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
} elseif ($err == 4) {
// Errorcode 4 = sizelimit exeeded.
return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
} elseif ($err == 87) {
// bad search filter
return $this->raiseError(Net_LDAP2::errorMessage($err) . "($filter)", $err);
} elseif (($err == 1) && ($this->_config['auto_reconnect'])) {
// Errorcode 1 = LDAP_OPERATIONS_ERROR but we can try a reconnect.
$this->_link = false;
$this->performReconnect();
} else {
$msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope";
return $this->raiseError(Net_LDAP2::errorMessage($err) . $msg, $err);
}
} else {
return $obj = new Net_LDAP2_Search($search, $this, $attributes);
}
}
}
/**
* Set an LDAP option
*
* @param string $option Option to set
* @param mixed $value Value to set Option to
*
* @access public
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
*/
public function setOption($option, $value)
{
if ($this->_link) {
if (defined($option)) {
if (@ldap_set_option($this->_link, constant($option), $value)) {
return true;
} else {
$err = @ldap_errno($this->_link);
if ($err) {
$msg = @ldap_err2str($err);
} else {
$err = NET_LDAP2_ERROR;
$msg = Net_LDAP2::errorMessage($err);
}
return $this->raiseError($msg, $err);
}
} else {
return $this->raiseError("Unkown Option requested");
}
} else {
return $this->raiseError("Could not set LDAP option: No LDAP connection");
}
}
/**
* Get an LDAP option value
*
* @param string $option Option to get
*
* @access public
* @return Net_LDAP2_Error|string Net_LDAP2_Error or option value
*/
public function getOption($option)
{
if ($this->_link) {
if (defined($option)) {
if (@ldap_get_option($this->_link, constant($option), $value)) {
return $value;
} else {
$err = @ldap_errno($this->_link);
if ($err) {
$msg = @ldap_err2str($err);
} else {
$err = NET_LDAP2_ERROR;
$msg = Net_LDAP2::errorMessage($err);
}
return $this->raiseError($msg, $err);
}
} else {
$this->raiseError("Unkown Option requested");
}
} else {
$this->raiseError("No LDAP connection");
}
}
/**
* Get the LDAP_PROTOCOL_VERSION that is used on the connection.
*
* A lot of ldap functionality is defined by what protocol version the ldap server speaks.
* This might be 2 or 3.
*
* @return int
*/
public function getLDAPVersion()
{
if ($this->_link) {
$version = $this->getOption("LDAP_OPT_PROTOCOL_VERSION");
} else {
$version = $this->_config['version'];
}
return $version;
}
/**
* Set the LDAP_PROTOCOL_VERSION that is used on the connection.
*
* @param int $version LDAP-version that should be used
* @param boolean $force If set to true, the check against the rootDSE will be skipped
*
* @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
* @todo Checking via the rootDSE takes much time - why? fetching and instanciation is quick!
*/
public function setLDAPVersion($version = 0, $force = false)
{
if (!$version) {
$version = $this->_config['version'];
}
//
// Check to see if the server supports this version first.
//
// Todo: Why is this so horribly slow?
// $this->rootDse() is very fast, as well as Net_LDAP2_RootDSE::fetch()
// seems like a problem at copiyng the object inside PHP??
// Additionally, this is not always reproducable...
//
if (!$force) {
$rootDSE = $this->rootDse();
if ($rootDSE instanceof Net_LDAP2_Error) {
return $rootDSE;
} else {
$supported_versions = $rootDSE->getValue('supportedLDAPVersion');
if (is_string($supported_versions)) {
$supported_versions = array($supported_versions);
}
$check_ok = in_array($version, $supported_versions);
}
}
if ($force || $check_ok) {
return $this->setOption("LDAP_OPT_PROTOCOL_VERSION", $version);
} else {
return $this->raiseError("LDAP Server does not support protocol version " . $version);
}
}
/**
* Tells if a DN does exist in the directory
*
* @param string|Net_LDAP2_Entry $dn The DN of the object to test
*
* @return boolean|Net_LDAP2_Error
*/
public function dnExists($dn)
{
if (PEAR::isError($dn)) {
return $dn;
}
if ($dn instanceof Net_LDAP2_Entry) {
$dn = $dn->dn();
}
if (false === is_string($dn)) {
return PEAR::raiseError('Parameter $dn is not a string nor an entry object!');
}
// search LDAP for that DN by performing a baselevel search for any
// object. We can only find the DN in question this way, or nothing.
$s_opts = array(
'scope' => 'base',
'sizelimit' => 1,
'attributes' => '1.1' // select no attrs
);
$search = $this->search($dn, '(objectClass=*)', $s_opts);
if (self::isError($search)) {
return $search;
}
// retun wehter the DN exists; that is, we found an entry
return ($search->count() == 0)? false : true;
}
/**
* Get a specific entry based on the DN
*
* @param string $dn DN of the entry that should be fetched
* @param array $attr Array of Attributes to select. If ommitted, all attributes are fetched.
*
* @return Net_LDAP2_Entry|Net_LDAP2_Error Reference to a Net_LDAP2_Entry object or Net_LDAP2_Error object
* @todo Maybe check against the shema should be done to be sure the attribute type exists
*/
public function &getEntry($dn, $attr = array())
{
if (!is_array($attr)) {
$attr = array($attr);
}
$result = $this->search($dn, '(objectClass=*)',
array('scope' => 'base', 'attributes' => $attr));
if (self::isError($result)) {
return $result;
} elseif ($result->count() == 0) {
return PEAR::raiseError('Could not fetch entry '.$dn.': no entry found');
}
$entry = $result->shiftEntry();
if (false == $entry) {
return PEAR::raiseError('Could not fetch entry (error retrieving entry from search result)');
}
return $entry;
}
/**
* Rename or move an entry
*
* This method will instantly carry out an update() after the move,
* so the entry is moved instantly.
* You can pass an optional Net_LDAP2 object. In this case, a cross directory
* move will be performed which deletes the entry in the source (THIS) directory
* and adds it in the directory $target_ldap.
* A cross directory move will switch the Entrys internal LDAP reference so
* updates to the entry will go to the new directory.
*
* Note that if you want to do a cross directory move, you need to
* pass an Net_LDAP2_Entry object, otherwise the attributes will be empty.
*
* @param string|Net_LDAP2_Entry $entry Entry DN or Entry object
* @param string $newdn New location
* @param Net_LDAP2 $target_ldap (optional) Target directory for cross server move; should be passed via reference
*
* @return Net_LDAP2_Error|true
*/
public function move($entry, $newdn, $target_ldap = null)
{
if (is_string($entry)) {
$entry_o = $this->getEntry($entry);
} else {
$entry_o =& $entry;
}
if (!$entry_o instanceof Net_LDAP2_Entry) {
return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object! (If DN was passed, conversion failed)');
}
if (null !== $target_ldap && !$target_ldap instanceof Net_LDAP2) {
return PEAR::raiseError('Parameter $target_ldap is expected to be a Net_LDAP2 object!');
}
if ($target_ldap && $target_ldap !== $this) {
// cross directory move
if (is_string($entry)) {
return PEAR::raiseError('Unable to perform cross directory move: operation requires a Net_LDAP2_Entry object');
}
if ($target_ldap->dnExists($newdn)) {
return PEAR::raiseError('Unable to perform cross directory move: entry does exist in target directory');
}
$entry_o->dn($newdn);
$res = $target_ldap->add($entry_o);
if (self::isError($res)) {
return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in target directory');
}
$res = $this->delete($entry_o->currentDN());
if (self::isError($res)) {
$res2 = $target_ldap->delete($entry_o); // undo add
if (self::isError($res2)) {
$add_error_string = 'Additionally, the deletion (undo add) of $entry in target directory failed.';
}
return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in source directory. '.$add_error_string);
}
$entry_o->setLDAP($target_ldap);
return true;
} else {
// local move
$entry_o->dn($newdn);
$entry_o->setLDAP($this);
return $entry_o->update();
}
}
/**
* Copy an entry to a new location
*
* The entry will be immediately copied.
* Please note that only attributes you have
* selected will be copied.
*
* @param Net_LDAP2_Entry &$entry Entry object
* @param string $newdn New FQF-DN of the entry
*
* @return Net_LDAP2_Error|Net_LDAP2_Entry Error Message or reference to the copied entry
*/
public function ©(&$entry, $newdn)
{
if (!$entry instanceof Net_LDAP2_Entry) {
return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object!');
}
$newentry = Net_LDAP2_Entry::createFresh($newdn, $entry->getValues());
$result = $this->add($newentry);
if ($result instanceof Net_LDAP2_Error) {
return $result;
} else {
return $newentry;
}
}
/**
* Returns the string for an ldap errorcode.
*
* Made to be able to make better errorhandling
* Function based on DB::errorMessage()
* Tip: The best description of the errorcodes is found here:
* http://www.directory-info.com/LDAP2/LDAPErrorCodes.html
*
* @param int $errorcode Error code
*
* @return string The errorstring for the error.
*/
public static function errorMessage($errorcode)
{
$errorMessages = array(
0x00 => "LDAP_SUCCESS",
0x01 => "LDAP_OPERATIONS_ERROR",
0x02 => "LDAP_PROTOCOL_ERROR",
0x03 => "LDAP_TIMELIMIT_EXCEEDED",
0x04 => "LDAP_SIZELIMIT_EXCEEDED",
0x05 => "LDAP_COMPARE_FALSE",
0x06 => "LDAP_COMPARE_TRUE",
0x07 => "LDAP_AUTH_METHOD_NOT_SUPPORTED",
0x08 => "LDAP_STRONG_AUTH_REQUIRED",
0x09 => "LDAP_PARTIAL_RESULTS",
0x0a => "LDAP_REFERRAL",
0x0b => "LDAP_ADMINLIMIT_EXCEEDED",
0x0c => "LDAP_UNAVAILABLE_CRITICAL_EXTENSION",
0x0d => "LDAP_CONFIDENTIALITY_REQUIRED",
0x0e => "LDAP_SASL_BIND_INPROGRESS",
0x10 => "LDAP_NO_SUCH_ATTRIBUTE",
0x11 => "LDAP_UNDEFINED_TYPE",
0x12 => "LDAP_INAPPROPRIATE_MATCHING",
0x13 => "LDAP_CONSTRAINT_VIOLATION",
0x14 => "LDAP_TYPE_OR_VALUE_EXISTS",
0x15 => "LDAP_INVALID_SYNTAX",
0x20 => "LDAP_NO_SUCH_OBJECT",
0x21 => "LDAP_ALIAS_PROBLEM",
0x22 => "LDAP_INVALID_DN_SYNTAX",
0x23 => "LDAP_IS_LEAF",
0x24 => "LDAP_ALIAS_DEREF_PROBLEM",
0x30 => "LDAP_INAPPROPRIATE_AUTH",
0x31 => "LDAP_INVALID_CREDENTIALS",
0x32 => "LDAP_INSUFFICIENT_ACCESS",
0x33 => "LDAP_BUSY",
0x34 => "LDAP_UNAVAILABLE",
0x35 => "LDAP_UNWILLING_TO_PERFORM",
0x36 => "LDAP_LOOP_DETECT",
0x3C => "LDAP_SORT_CONTROL_MISSING",
0x3D => "LDAP_INDEX_RANGE_ERROR",
0x40 => "LDAP_NAMING_VIOLATION",
0x41 => "LDAP_OBJECT_CLASS_VIOLATION",
0x42 => "LDAP_NOT_ALLOWED_ON_NONLEAF",
0x43 => "LDAP_NOT_ALLOWED_ON_RDN",
0x44 => "LDAP_ALREADY_EXISTS",
0x45 => "LDAP_NO_OBJECT_CLASS_MODS",
0x46 => "LDAP_RESULTS_TOO_LARGE",
0x47 => "LDAP_AFFECTS_MULTIPLE_DSAS",
0x50 => "LDAP_OTHER",
0x51 => "LDAP_SERVER_DOWN",
0x52 => "LDAP_LOCAL_ERROR",
0x53 => "LDAP_ENCODING_ERROR",
0x54 => "LDAP_DECODING_ERROR",
0x55 => "LDAP_TIMEOUT",
0x56 => "LDAP_AUTH_UNKNOWN",
0x57 => "LDAP_FILTER_ERROR",
0x58 => "LDAP_USER_CANCELLED",
0x59 => "LDAP_PARAM_ERROR",
0x5a => "LDAP_NO_MEMORY",
0x5b => "LDAP_CONNECT_ERROR",
0x5c => "LDAP_NOT_SUPPORTED",
0x5d => "LDAP_CONTROL_NOT_FOUND",
0x5e => "LDAP_NO_RESULTS_RETURNED",
0x5f => "LDAP_MORE_RESULTS_TO_RETURN",
0x60 => "LDAP_CLIENT_LOOP",
0x61 => "LDAP_REFERRAL_LIMIT_EXCEEDED",
1000 => "Unknown Net_LDAP2 Error"
);
return isset($errorMessages[$errorcode]) ?
$errorMessages[$errorcode] :
$errorMessages[NET_LDAP2_ERROR] . ' (' . $errorcode . ')';
}
/**
* Gets a rootDSE object
*
* This either fetches a fresh rootDSE object or returns it from
* the internal cache for performance reasons, if possible.
*
* @param array $attrs Array of attributes to search for
*
* @access public
* @return Net_LDAP2_Error|Net_LDAP2_RootDSE Net_LDAP2_Error or Net_LDAP2_RootDSE object
*/
public function &rootDse($attrs = null)
{
if ($attrs !== null && !is_array($attrs)) {
return PEAR::raiseError('Parameter $attr is expected to be an array!');
}
$attrs_signature = serialize($attrs);
// see if we need to fetch a fresh object, or if we already
// requested this object with the same attributes
if (true || !array_key_exists($attrs_signature, $this->_rootDSE_cache)) {
$rootdse =& Net_LDAP2_RootDSE::fetch($this, $attrs);
if ($rootdse instanceof Net_LDAP2_Error) {
return $rootdse;
}
// search was ok, store rootDSE in cache
$this->_rootDSE_cache[$attrs_signature] = $rootdse;
}
return $this->_rootDSE_cache[$attrs_signature];
}
/**
* Alias function of rootDse() for perl-ldap interface
*
* @access public
* @see rootDse()
* @return Net_LDAP2_Error|Net_LDAP2_RootDSE
*/
public function &root_dse()
{
$args = func_get_args();
return call_user_func_array(array(&$this, 'rootDse'), $args);
}
/**
* Get a schema object
*
* @param string $dn (optional) Subschema entry dn
*
* @access public
* @return Net_LDAP2_Schema|Net_LDAP2_Error Net_LDAP2_Schema or Net_LDAP2_Error object
*/
public function &schema($dn = null)
{
// Schema caching by Knut-Olav Hoven
// If a schema caching object is registered, we use that to fetch
// a schema object.
// See registerSchemaCache() for more info on this.
if ($this->_schema === null) {
if ($this->_schema_cache) {
$cached_schema = $this->_schema_cache->loadSchema();
if ($cached_schema instanceof Net_LDAP2_Error) {
return $cached_schema; // route error to client
} else {
if ($cached_schema instanceof Net_LDAP2_Schema) {
$this->_schema = $cached_schema;
}
}
}
}
// Fetch schema, if not tried before and no cached version available.
// If we are already fetching the schema, we will skip fetching.
if ($this->_schema === null) {
// store a temporary error message so subsequent calls to schema() can
// detect, that we are fetching the schema already.
// Otherwise we will get an infinite loop at Net_LDAP2_Schema::fetch()
$this->_schema = new Net_LDAP2_Error('Schema not initialized');
$this->_schema = Net_LDAP2_Schema::fetch($this, $dn);
// If schema caching is active, advise the cache to store the schema
if ($this->_schema_cache) {
$caching_result = $this->_schema_cache->storeSchema($this->_schema);
if ($caching_result instanceof Net_LDAP2_Error) {
return $caching_result; // route error to client
}
}
}
return $this->_schema;
}
/**
* Enable/disable persistent schema caching
*
* Sometimes it might be useful to allow your scripts to cache
* the schema information on disk, so the schema is not fetched
* every time the script runs which could make your scripts run
* faster.
*
* This method allows you to register a custom object that
* implements your schema cache. Please see the SchemaCache interface
* (SchemaCache.interface.php) for informations on how to implement this.
* To unregister the cache, pass null as $cache parameter.
*
* For ease of use, Net_LDAP2 provides a simple file based cache
* which is used in the example below. You may use this, for example,
* to store the schema in a linux tmpfs which results in the schema
* beeing cached inside the RAM which allows nearly instant access.
*
* // Create the simple file cache object that comes along with Net_LDAP2
* $mySchemaCache_cfg = array(
* 'path' => '/tmp/Net_LDAP2_Schema.cache',
* 'max_age' => 86400 // max age is 24 hours (in seconds)
* );
* $mySchemaCache = new Net_LDAP2_SimpleFileSchemaCache($mySchemaCache_cfg);
* $ldap = new Net_LDAP2::connect(...);
* $ldap->registerSchemaCache($mySchemaCache); // enable caching
* // now each call to $ldap->schema() will get the schema from disk!
*
*
* @param Net_LDAP2_SchemaCache|null $cache Object implementing the Net_LDAP2_SchemaCache interface
*
* @return true|Net_LDAP2_Error
*/
public function registerSchemaCache($cache) {
if (is_null($cache)
|| (is_object($cache) && in_array('Net_LDAP2_SchemaCache', class_implements($cache))) ) {
$this->_schema_cache = $cache;
return true;
} else {
return new Net_LDAP2_Error('Custom schema caching object is either no '.
'valid object or does not implement the Net_LDAP2_SchemaCache interface!');
}
}
/**
* Checks if phps ldap-extension is loaded
*
* If it is not loaded, it tries to load it manually using PHPs dl().
* It knows both windows-dll and *nix-so.
*
* @static
* @return Net_LDAP2_Error|true
*/
public static function checkLDAPExtension()
{
if (!extension_loaded('ldap') && !@dl('ldap.' . PHP_SHLIB_SUFFIX)) {
return new Net_LDAP2_Error("It seems that you do not have the ldap-extension installed. Please install it before using the Net_LDAP2 package.");
} else {
return true;
}
}
/**
* Encodes given attributes to UTF8 if needed by schema
*
* This function takes attributes in an array and then checks against the schema if they need
* UTF8 encoding. If that is so, they will be encoded. An encoded array will be returned and
* can be used for adding or modifying.
*
* $attributes is expected to be an array with keys describing
* the attribute names and the values as the value of this attribute:
* $attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));
*
* @param array $attributes Array of attributes
*
* @access public
* @return array|Net_LDAP2_Error Array of UTF8 encoded attributes or Error
*/
public function utf8Encode($attributes)
{
return $this->utf8($attributes, 'utf8_encode');
}
/**
* Decodes the given attribute values if needed by schema
*
* $attributes is expected to be an array with keys describing
* the attribute names and the values as the value of this attribute:
* $attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));
*
* @param array $attributes Array of attributes
*
* @access public
* @see utf8Encode()
* @return array|Net_LDAP2_Error Array with decoded attribute values or Error
*/
public function utf8Decode($attributes)
{
return $this->utf8($attributes, 'utf8_decode');
}
/**
* Encodes or decodes attribute values if needed
*
* @param array $attributes Array of attributes
* @param array $function Function to apply to attribute values
*
* @access protected
* @return array|Net_LDAP2_Error Array of attributes with function applied to values or Error
*/
protected function utf8($attributes, $function)
{
if (!is_array($attributes) || array_key_exists(0, $attributes)) {
return PEAR::raiseError('Parameter $attributes is expected to be an associative array');
}
if (!$this->_schema) {
$this->_schema = $this->schema();
}
if (!$this->_link || self::isError($this->_schema) || !function_exists($function)) {
return $attributes;
}
if (is_array($attributes) && count($attributes) > 0) {
foreach ($attributes as $k => $v) {
if (!isset($this->_schemaAttrs[$k])) {
$attr = $this->_schema->get('attribute', $k);
if (self::isError($attr)) {
continue;
}
if (false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
$encode = true;
} else {
$encode = false;
}
$this->_schemaAttrs[$k] = $encode;
} else {
$encode = $this->_schemaAttrs[$k];
}
if ($encode) {
if (is_array($v)) {
foreach ($v as $ak => $av) {
$v[$ak] = call_user_func($function, $av);
}
} else {
$v = call_user_func($function, $v);
}
}
$attributes[$k] = $v;
}
}
return $attributes;
}
/**
* Get the LDAP link resource. It will loop attempting to
* re-establish the connection if the connection attempt fails and
* auto_reconnect has been turned on (see the _config array documentation).
*
* @access public
* @return resource LDAP link
*/
public function &getLink()
{
if ($this->_config['auto_reconnect']) {
while (true) {
//
// Return the link handle if we are already connected. Otherwise
// try to reconnect.
//
if ($this->_link !== false) {
return $this->_link;
} else {
$this->performReconnect();
}
}
}
return $this->_link;
}
}
/**
* Net_LDAP2_Error implements a class for reporting portable LDAP error messages.
*
* @category Net
* @package Net_LDAP2
* @author Tarjej Huse Net_LDAP2 Manual
Welcome to the Net_LDAP2 user manual! here you have a quick introduction on
how to use Net_LDAP2 to acces your directory server with php.
First step: Connect
require_once('Net_LDAP22/LDAP2.php');
$config = array (
'binddn' => 'uid=tarjei,dc=php,dc=net',
'bindpw' => 'secret',
'basedn' => dc=php,dc=net
);
$ldap = Net_LDAP2::connect($config);
But what are valid values in the config array?
Now you should have the base ldapobject stored in the variable "$ldap". But, what if it is an error? Net_LDAP2 returns a Net_LDAP2_error object (basicly a pear_error object) when an error occurs. So wherever you need to check an error, do like this:
$ldap = Net_LDAP2::connect($config); // copied from above! if (Net_LDAP2::isError($ldap)) { print $ldap->getMessage(); // this will tell you what went wrong! }
Two things to note:
1) The function is_a() might be faster:
if (is_a($ldap,'net_ldap_error')) { // do the same as above }In PHP5 you must use the instanceof operator instead of is_a().
Most of the work you do on an ldapserver is in searching,
for example, you search for your boss's password or his wife's phonenumber.
Searching an ldapserver is a bit like doing SQL and a lot not like it at all.
Think of the directory as some sort of "telephone book".
Basically, searches are performed by applying a "filter" to objects under a
specific "base" in the directory. Additionally, there is a "scope" applied to the search,
so you can specify the recursion level in the directory tree.
$filter = '(&(objectclass=person)(sn=Ha*))'; $searchbase = 'ou=dev,ou=People,dc=php,dc=net'; $options = array( 'scope' => 'sub', // all entries below the searchbase (recursive all subtrees from there) 'attributes' => array('sn','gn','telephonenumber') // what attributes to select ); $search = $ldap->search($searchbase, $filter, $options);$search should now be an Net_LDAP2_Search object.
This describes how to get an entry and modifying it. If we just want one single entry, it may be useful to directly fetch that entry instead of searching it manually. To do this you can use Net_LDAP2s "getEntry()" method:
$dn = 'cn=Foo Bar,ou=dev,ou=People,dc=php,dc=net'; $entry =& $ldap->getEntry($dn, array('sn','gn','telephonenumber'));With this entry object you now can perform some actions like fetching the contents of attributes:
$telephonenumber = $entry->getValue('telephonenumber','single');Or you can modify a attribute:
$entry->replace("telephonenumber" => "0123456789"); // replace the attributes values with the new number $entry->update(); // update temporarily modified entry on the serverOf course there are much more other possibilitys. Please note that adding and deleting whole entrys is performed through the Net_LDAP2 class and not with the Net_LDAP2_Entry class.
$schema = $ldap->schema();Now you got a schemaobject. To read from this schemaobject, you have several methods defined in the class Net_LDAP2_Schema.
$required = $schema->must( 'inetOrgUser' ); print_r($required); /* The output of this will be: Array ( [0] => sn [1] => cn ) */Ok, but what kind of attribute is sn? Let's check:
$att = $schema->get('attribute','sn'); print_r($att); /* The output of this will be: Array ( [aliases] => Array ( [0] => surname ) [oid] => 2.5.4.4 [name] => sn [desc] => RFC2256: last (family) name(s) for which the entity is known by [sup] => Array ( [0] => name ) [type] => attribute ) */Hmm, ok, the sup part is important. It means that surname derives it's syntax from another attribute, the name attribute. So , we need to check that as well.
$att_dep = $schema->get('attribute',$att['sup'][0]); print_r($att_dep); /* The output of this will be: Array ( [aliases] => Array ( ) [oid] => 2.5.4.41 [name] => name [desc] => RFC2256: common supertype of name attributes [equality] => caseIgnoreMatch [substr] => caseIgnoreSubstringsMatch [syntax] => 1.3.6.1.4.1.1466.115.121.1.15{32768} [max_length] => 32768 [type] => attribute ) */From this we find out that the attribute has a maxlength of 32768 characters and has the syntax 1.3.6.1.4.1.1466.115.121.1.15{32768}. Net_LDAP2-2.0.12/doc/README.txt 0000644 0001750 0001750 00000001316 11652260337 014071 0 ustar beni beni This is my own implementation of some classes for manipulation LDAP-entries in a directory. The classes methods and structure are based on Perls Net::LDAP (see perl-ldap.sf.net). The test.php file shuld provide you with enough examples to do the most basic things. The largest difference between the perl implementation and this one (apart from the fact that all array/list structures are different due to differences in the two languages) is that instead of the method new you'll have to use the method connect() instead. Patches and comments are most welcome! Please submit them via PEARS Bug tracking feature or via mail to one of Net_LDAP2s developers. Use unified context diffs if possible! The Net_LDAP2 Team Net_LDAP2-2.0.12/doc/RootDSE.txt 0000644 0001750 0001750 00000002521 11652260337 014412 0 ustar beni beni First of all connect as usual. (Some servers require authentication to get the RootDSE entry) $config = array( 'host' => 'localhost' ); $ldap = Net_LDAP2::connect( $config ); if( Net_LDAP2::isError( $ldap ) ) die( $ldap->getMessage() ); Now we can get the entry: $dse = $ldap->rootDSE(); if( Net_LDAP2::isError( $dse ) die( $dse->getMessage() ); You can give an array of attributes to fetch as an parameter ro rootDSE(). If none are given these ones are fetched: namingContexts altServer supportedExtension supportedControl supportedSASLMechanisms supportedLDAPVersion subschemaSubentry Then you can work with the object: $basedn = $dse->getValue( 'namingContexts' ); if( $dse->supportedVersion( 3 ) == 3 ) { do_something_only_ldap_v3_can_do(); } Public functions: getValue( string ) get the value of this attribute. same syntax as Net_LDAP2_Entry::get_value() supportedControl( oid ) supportedExtension( oid ) check if the given control/extension is supported by the server supportedSASLMechanism( mechanism ) check if the given sasl mechanism is supported by the server supportedVersion( version ) check if the given ldap version is supported by the serve These are alias functions of the above, to make the api perl-ldap compatible. get_value() supported_control() supported_extension() supported_sasl_mechanism() supported_version() Net_LDAP2-2.0.12/doc/Schema.txt 0000644 0001750 0001750 00000003320 11652260337 014331 0 ustar beni beni Examples: First of all connect to your server as usual. (Some servers require authentication to get the Subschema entry) $config = array( 'host' => 'localhost' ); $ldap = Net_LDAP2::connect( $config ); if( Net_LDAP2::isError( $ldap ) ) die ( $ldap->getMessage() ) Then we can get the schema. $schema = $ldap->schema(); if( Net_LDAP2::isError( $schema ) ) die ( $schema->getMessage() ); You can give a parameter to $ldap->schema() which sets the Subschema Entry dn. If it is omitted, the entry dn will be fetched internally via rootDSE(). If that fails it will be set to "cn=Subschema". $schema = $ldap->schema( 'cn=Subschema' ); Now you can work with the schema and retrieve information: $attrs = $schema->getAll( 'attributes' ); This returns an array with all attributes and their information such as syntax, equality, max_length etc. Look at the returned array to see what information was passed. Valid options to getAll() are: objectclasses attributes ditcontentrules ditstructurerules matchingrules matchingruleuses nameforms syntaxes If you know the the name of an attribute or objectclass you can get the information directly. $attr = $schema->get('attribute', 'userPassword'); $oc = $schema->get('objectclass', 'posixAccount'); The first parameter determines the type of entry to be fetched and can be one of: attribute ditcontentrule ditstructurerule matchingrule matchingruleuse nameform objectclass syntax The second parameter can be the name or the oid of the entry. You can retrieve a list of required and optional attributes of an object class via must( $oc ) or may( $oc ). Both return a list of attributes in an array. $required = $schema->must( 'posixAccount' ); $optional = $schema->may( 'posixAccount' ); Net_LDAP2-2.0.12/doc/utf8.txt 0000644 0001750 0001750 00000001353 11652260337 014023 0 ustar beni beni UTF8 and Net_LDAP2: It is hard to know exactly what entries need utf8 every time you need them, so here's the simple way to salvation: Net_LDAP2 will check internally if utf8 is needed. Code: // $attr is some text a user entered with funny characters in it. // If $attr should not be utfized (f.x. userPassword) then utf8Encode // will not encode the attribute. $attr = $ldap->utf8Encode($attr); // now insert the correctly encoded attribute into the directory. $entry->modify($attr); // later when you access the attributes of that user, decode the ones // that have to be decoded. $attr = $ldap->utf8Decode( $entry->attributes() ); Thanks to Jan for the code. Net_LDAP2-2.0.12/doc/examples/connecting.php 0000644 0001750 0001750 00000003027 11652260337 017052 0 ustar beni beni 'ldap.example.org', 'host' => array('ldap1.example.org', 'ldap2.example.org'), // 'binddn' => 'cn=admin,o=example,dc=org', // 'bindpw' => 'your-secret-password', 'tls' => false, 'base' => 'o=example,dc=org', 'port' => 389, 'version' => 3, 'filter' => '(cn=*)', 'scope' => 'sub' ); // Connect to configured ldap server $ldap = Net_LDAP2::connect($ldap_config); // It is important to check for errors. // Nearly every method of Net_LDAP2 returns a Net_LDAP2_Error object // if something went wrong. Through this object, you can retrieve detailed // information on what exactly happened. // // Here we drop a die with the error message, so the other example // files will not be calles unless we have a valid link. if (Net_LDAP2::isError($ldap)) { die('BIND FAILED: '.$ldap->getMessage()); } Net_LDAP2-2.0.12/doc/examples/fetch_entry.php 0000644 0001750 0001750 00000002557 11652260337 017244 0 ustar beni beni getEntry('cn=admin,'.$ldap_config['base'], array('gn', 'sn')); // Error checking is important! if (Net_LDAP2::isError($entry)) { die('Could not fetch entry: '.$entry->getMessage()); } // Now fetch the data from the entry $surename = $entry->getValue('sn', 'single'); if (Net_LDAP2::isError($surename)) { die('Unable to get surename: '.$surename->getMessage()); } $givenname = $entry->getValue('gn', 'single'); if (Net_LDAP2::isError($givenname)) { die('Unable to get surename: '.$givenname->getMessage()); } // Finally output the data of the entry: // This will give something like "Name of cn=admin,o=example,dc=org: Foo Bar" echo 'Name of '.$entry->DN().': '.$givenname.' '.$surename; ?> Net_LDAP2-2.0.12/doc/examples/search_entries.php 0000644 0001750 0001750 00000006156 11652260337 017727 0 ustar beni beni search($ldap_config['base'], ... $requested_attributes = array('sn','gn','telephonenumber'); $search = $ldap->search(null, $filter, array('attributes' => $requested_attributes)); if (Net_LDAP2::isError($search)) { die('LDAP search failed: '.$search->getMessage()); } // Lets see what entries we got and print the names and telephone numbers: if ($search->count() > 0) { echo "Found ".$search->count().' entries: