package.xml 100644 1750 1750 52037 11223350110 6430
Net_LDAPpear.php.netObject oriented interface for searching and manipulating LDAP-entriesNet_LDAP is a clone of Perls Net::LDAP object interface to
directory servers. It does contain most of Net::LDAPs features
but has some own too.
With Net_LDAP you have:
* A simple object-oriented interface to connections, searches entries and filters.
* Support for tls and ldap v3.
* Simple modification, deletion and creation of ldap entries.
* Support for schema handling.
Net_LDAP layers itself on top of PHP's existing ldap extensions.Tarjei Husetarjeitarjei@nu.nonoJan Wagnerjwwagner@netsols.denoBenedikt Hallingerbenibeni@php.netyes2009-07-031.1.51.1.2stablestableLGPL License* Ported the fix (RFC-1777) in connect() from Net_LDAP2 v2.0.34.21.5.00.10.1betabeta2003-06-23LGPL LicenseInitial release0.20.2betabeta2003-08-23LGPL LicenseFixed a lot of bugs that jumped in during the pearification process0.30.3betabeta2003-09-21LGPL LicenseMore bug squashing! Much better errorhandling in the -search() function.
Also, all errors that create a Pear_error now includes the errornumber if
appropriate (i.e. it was an ldap generated error).0.40.4betabeta2003-10-01LGPL LicenseMany more bugfixes. Jan Wagner fixed the shift_entry function.
Also a new Net_LDAP_Entry::modify function has been added that goes far making a simple way to modify entries.0.50.5betabeta2003-10-11LGPL LicenseJan Wagner Contributed a new RootDSE object and a Schema object and some fixes to the Net_LDAP::search() method
The new Net_ldap_entry::modify() method seems to work very nice now.0.60.6betabeta2003-10-17LGPL LicenseNew Net_LDAP::ArrayUTF8Decode and Net_LDAP::ArrayUTF8Encode functions. These are used by the Net_LDAP::Entry objects to ensure that things work ok.0.6.30.6.3betabeta2003-11-12LGPL LicenseIt seems that 0.6.2 was out too fast. So this is mainly a bugfix release:
- Removed remaining Net_LDAP::UTF8Encode and Net_LDAP::UTF8Decode calls in Net_LDAP_Entry,
which stopped attributes() and get_entry() from working
- The UTF8 functions somehow got outside the Net_LDAP class ... FIXED.
- The usuage example of the last release was wrong. We decided to move UTF8 handling into Net_LDAP.
Handling should be done this way:
$attr = $ldap-utf8Encode($attr);
$entry-modify($attr);
$attr = $ldap-utf8Decode( $entry-attributes() );
- This means Net_LDAP_Util is useless right now, but will be extended in the future.
- Jan did a complete overhaul of the phpdoc stuff. Everything seems to be fine now with phpDocumentor.2007-02-050.7.00.7.0betabetaLGPL LicenseThis long awaited release of Net_LDAP features more stability and new functionality.
The main changes are:
- Rewrite of much of the code (including some api changes!)
- LOTS of fixed bugs!
- New class for easy filter handling (Net_LDAP_Filter)
- Sorting support for searchresults (including multivalued sorting!)
- Searched Entries can now be fetched as_struct() (array)!
- Some memory optimizations
Please note also that Net_LDAPs configuration changed slightly. Please see $_config in LDAP.php for the new parameters.2007-02-230.7.10.7.0betabetaLGPL LicenseThis is not just a bugfix release of 0.7.0 but also introduces some internal optimisations:
- Fixed a connection bug whith LDAP V3 only servers
- clearer sanitizing of the host config parameter2007-05-070.7.20.7.2betabetaLGPL LicenseThis release features some internal code movements to be more compatible to PERL::Net_LDAP.
The movements include:
* Removed UTF8 en-/decoding stuff from Net_LDAP_Utils class since this was moved to Net_LDAP class in 0.6.6
* Moved Filter encoding from Net_LDAP_Filter to Net_LDAP_Util
* Moved ldap_explode_dn_escaped() from Net_LDAP_Entry to Net_LDAP_Util
* Added perls functions from Net_LDAP::Util to our Util class, but they still need some work
Please note that ldap_explode_dn_escaped() is not available from Net_LDAP_Entry anymore.
Additionally some new functionality has been introduced:
* You can now apply regular expressions directly to a entrys attributes
and don't need to fetch the attribute values manually.
* Net_LDAP_Schema can check if a attributes syntax is binary
The following bugs have been resolved:
* Connections to LDAP servers that forbid anonymous binds are possible again
* The JPEG attribute is now properly returned as binary value instead of string
* If the array describing selected attributes in searches didn't contain consecutive keys, there was a problem sometimes
* Some PHP5 return issues2007-06-120.7.30.7.2betabetaLGPL LicenseThis release introduces some example files showing you in detail how to work with Net_LDAP.
Additionally, a bug at recursive deletion of an entry is fixed and the Net_LDAP_Filter
class is slightly optimized.2007-06-201.0.0RC11.0.0RC1betabetaLGPL LicenseAgain some small Bugfixes, most notably a bug within $ldap->modify() that occured when using the
combined 'changes' array.
Besides that, $search->popEntry() and the corresponding alias pop_entry() has been implemented.
Net_LDAP_Util::unescape_filter_value() is available too now and Net_LDAP_Util::escape_filter_value()
can handle ASCII chars smaller than 32. Above that, Net_LDAP_Util::canonical_dn() has been fully implemented.
A new method createFresh() was added to Net_LDAP_Entry, so creation of initial entries is more
standardized and clearer.
A new example is available, describing the $ldap->modify() method.
The add_entry.php example was updated, it shows the use of Net_LDAP_Entry::createFresh().
$ldap->add() links unlinked entries now to the connection used for the add.
Some new additional utility functions are available in Net_LDAP_Util to assist you in handling attributes and dns.
The LDAP-Rename command now uses this functions to deal with DN escaping issues.
Please note that ldap_explode_dn_escaped() is not available from Net_LDAP_Util anymore; it got superseeded by Net_LDAP_Util::ldap_explode_dn().2007-06-281.0.0RC21.0.0RC2betabetaLGPL LicenseNet_LDAP->dnExists() uses the Util class now, which makes it safer.
A new move() method is available from Net_LDAP.
Please note, that the copy() method was removed from the Net_LDAP_Entry class since
people would expect attribute moving because of the overall API of Net_LDAP.
Instead use the more failsafer copy() from Net_LDAP.2007-07-241.0.0RC31.0.0RC3betabetaLGPL LicenseFixed a bug with dnExists() that was caused mainly by bad behavior of Net_LDAP_UTIL::ldap_explode_dn().
Fixed a bug with call time pass-by-reference if calling $entry->update(); however this inflicted a API change:
The parameter $ldap is not available anymore, you need to use $entry->setLDAP() prior update now if you want to change the LDAP
object. This brought us a more logical API now, since Entry operations should be performed by the Net_LDAP object.2007-09-181.0.0RC41.0.0RC4betabetaLGPL License- Fixed some minor bugs of RC3
- Reintroduced $ldap parameter for
$entry-update(), but it is not prefferred to use this way.
The Parameter is there for perl interface compatibility2007-10-291.0.01.0.0stablestableLGPL LicenseAfter more than four years of development, we are very proud to announce the
~ FIRST STABLE Net_LDAP RELEASE 1.0.0 ~
Net_LDAP ist tested now and should be stable enough for production use.
The API is finished so far, no changes should be neccessary in the future.
Changes to Release candidate 4:
- Implemented PHPUnit tests
- Fixed some minor bugs of RC4 (including the schema loading warning-generation)
- Fixed several bugs in Net_LDAP_Util
- Improved Net_LDAP_Filter and Net_LDAP_Util error handling and code cleanness
- Completely implemented Net_LDAP_Filter perl interface
- Improved several doc comments and fixed some spelling errors2008-01-141.1.0a11.1.0a1betabetaLGPL License* Added LDIF reading and writing support
* Fixed minor issues of 1.0.0 release2008-01-211.1.0a21.1.0a2betabetaLGPL License* Added parseLines() to Net_LDAP_LDIF for more convinience
* Added some handy methods to Net_LDAP_Entry
* Enhanced tests2008-02-271.1.01.1.0stablestableLGPL License* Fixed a little bug at cross directory move
* Fixed a bug when deleting a subtree containing several subentries that failed if
one called dnExists() prior calling delete()
* Fixed some minor bugs at NeT_LDAP->move() and Net_LDAP->dnExists()
* Added Net_LDAP tests
* Changed API of Net_LDAP->copy() to only accept Net_LDAP_Entry objects, because with DNs
Attribute values will be lost
/!\ This is the last release of Net_LDAP supporting PHP4 /!\2008-03-191.1.11.1.1stablestableLGPL LicenseThis is just a bugfix release. Net_LDAP is superseeded by Net_LDAP2,
please use Net_LDAP2 unless you are forced to use PHP4.
* Fixed a problem with Net_LDAP_LDIF and files with DOS line endings2008-06-041.1.21.1.2stablestableLGPL License* Backportet a patch from Net_LDAP2. Schema->isBinary() did not checked attribute supertypes
* Backported a patch for Schema->isBinary() bug if unknown attribute type is requested
* Bugfix in filter class for approx matching2008-10-231.1.31.1.2stablestableLGPL License* Fixed a little issue with repetive adding the same attribute value (backported from Net_LDAP2)
* Fixed issue with repetitve adding or deleting values causing Net_LDAP to send the same change multiple times2009-04-231.1.41.1.2stablestableLGPL License* Ported the connect() method from Net_LDAP2 since it fixes some connection issues
Net_LDAP-1.1.5/LDAP/Entry.php 100644 1750 1750 72132 11223350110 10631
* @author Jan Wagner
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version CVS: $Id: Entry.php,v 1.58 2008/11/03 14:06:27 beni Exp $
* @link http://pear.php.net/package/Net_LDAP/
*/
require_once 'PEAR.php';
require_once 'Util.php';
/**
* Object representation of a directory entry
*
* This class represents a directory entry. You can add, delete, replace
* attributes and their values, rename the entry, delete the entry.
*
* @category Net
* @package Net_LDAP
* @author Jan Wagner
* @author Tarjej Huse
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP/
*/
class Net_LDAP_Entry extends PEAR
{
/**
* Entry ressource identifier
*
* @access private
* @var ressourcee
*/
var $_entry = null;
/**
* LDAP ressource identifier
*
* @access private
* @var ressource
*/
var $_link = null;
/**
* Net_LDAP object
*
* This object will be used for updating and schema checking
*
* @access private
* @var object Net_LDAP
*/
var $_ldap = null;
/**
* Distinguished name of the entry
*
* @access private
* @var string
*/
var $_dn = null;
/**
* Attributes
*
* @access private
* @var array
*/
var $_attributes = array();
/**
* Original attributes before any modification
*
* @access private
* @var array
*/
var $_original = array();
/**
* Map of attribute names
*
* @access private
* @var array
*/
var $_map = array();
/**
* Is this a new entry?
*
* @access private
* @var boolean
*/
var $_new = true;
/**
* New distinguished name
*
* @access private
* @var string
*/
var $_newdn = null;
/**
* Shall the entry be deleted?
*
* @access private
* @var boolean
*/
var $_delete = false;
/**
* Map with changes to the entry
*
* @access private
* @var array
*/
var $_changes = array("add" => array(),
"delete" => array(),
"replace" => array()
);
/**
* Internal Constructor
*
* Constructor of the entry. Sets up the distinguished name and the entries
* attributes.
* You should not call this method manually! Use {@link Net_LDAP_Entry::createFresh()} instead!
*
* @param Net_LDAP|ressource|array &$ldap Net_LDAP object, ldap-link ressource or array of attributes
* @param string|ressource $entry Either a DN or a LDAP-Entry ressource
*
* @access protected
* @return none
*/
function Net_LDAP_Entry(&$ldap, $entry = null)
{
$this->PEAR('Net_LDAP_Error');
if (is_resource($entry)) {
$this->_entry = &$entry;
} else {
$this->_dn = $entry;
}
if (is_a($ldap, 'Net_LDAP')) {
$this->_ldap = &$ldap;
$this->_link = $ldap->getLink();
} elseif (is_resource($ldap)) {
$this->_link = $ldap;
} elseif (is_array($ldap)) {
$this->_setAttributes($ldap); // setup attrs manually
}
if (is_resource($this->_entry) && is_resource($this->_link)) {
$this->_new = false;
$this->_dn = @ldap_get_dn($this->_link, $this->_entry);
$this->_setAttributes(); // fetch attributes from server
}
}
/**
* Creates a fresh entry that may be added to the directory later on
*
* Use this method, if you want to initialize a fresh entry.
*
* The method should be called statically: $entry = Net_LDAP_Entry::createFresh();
* You should put a 'objectClass' attribute into the $attrs so the directory server
* knows which object you want to create. However, you may omit this in case you
* don't want to add this entry to a directory server.
*
* 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_LDAP_Entry
*/
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_LDAP_Entry($attrs, $dn);
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_LDAP_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)
*/
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
*/
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 private
* @return void
*/
function _setAttributes($attributes = null)
{
/*
* fetch attributes from the server
*/
if (is_null($attributes) && is_resource($this->_entry) && is_resource($this->_link)) {
// fetch schema
if (is_a($this->_ldap, 'Net_LDAP')) {
$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 (is_a($schema, 'Net_LDAP_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'))
*
* @access public
* @return array Hash of all attributes with their values
*/
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)
*
* @param string $attr Attribute name
* @param string $option Option
*
* @access public
* @return string|array|PEAR_Error string, array or PEAR_Error
*/
function getValue($attr, $option = null)
{
$attr = $this->_getAttrName($attr);
if (false == array_key_exists($attr, $this->_attributes)) {
return PEAR::raiseError("Unknown attribute ($attr) requested");
}
$value = $this->_attributes[$attr];
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
*/
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
*/
function attributes()
{
return array_keys($this->_attributes);
}
/**
* Returns whether an attribute exists or not
*
* @param string $attr Attribute name
*
* @access public
* @return boolean
*/
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_LDAP_Error
*/
function add($attr = array())
{
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)) {
// 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
*/
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 {
$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 {
// get the correct attribute name
$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"))
* If the attribute does not yet exist it will be added instead.
* 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.
*
* @param array $attr Attributes to replace
*
* @access public
* @return true|Net_LDAP_Error
*/
function replace($attr = array())
{
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
if ($v == null) {
$this->delete($k);
continue;
} else {
$v = array($v);
}
}
// existing attributes will get replaced
if ($this->exists($k)) {
$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
*
* @param Net_LDAP $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_LDAP_Error
* @todo Entry rename with a DN containing special characters needs testing!
*/
function update($ldap = null)
{
if ($ldap) {
$msg = $this->setLDAP($ldap);
if (Net_LDAP::isError($msg)) {
return PEAR::raiseError('You passed an invalid $ldap variable to update()');
}
}
// ensure we have a valid LDAP object
$ldap =& $this->getLDAP();
if (!is_a($ldap, 'Net_LDAP')) {
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_LDAP::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_LDAP_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false));
if (Net_LDAP::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_LDAP_Util::canonical_dn($child);
}
$parent = Net_LDAP_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 private
* @return string The right name of the attribute
*/
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_LDAP|Net_LDAP_Error Reference to the Net_LDAP Object (the connection) or Net_LDAP_Error
*/
function &getLDAP()
{
if (!is_a($this->_ldap, 'Net_LDAP')) {
$err = new PEAR_Error('LDAP is not a valid Net_LDAP object');
return $err;
} else {
return $this->_ldap;
}
}
/**
* Sets a reference to the LDAP-Object of this entry
*
* After setting a Net_LDAP object, calling update() will use that object for
* updating directory contents. Use this to dynamicly switch directorys.
*
* @param Net_LDAP &$ldap Net_LDAP object that this entry should be connected to
*
* @access public
* @return true|Net_LDAP_Error
*/
function setLDAP(&$ldap)
{
if (!is_a($ldap, 'Net_LDAP')) {
return PEAR::raiseError("LDAP is not a valid Net_LDAP object");
} else {
$this->_ldap =& $ldap;
return true;
}
}
/**
* Marks the entry as new.
*
* If an Entry is marked as new, it will be added to the directory when
* calling {@link update()}. This method is mainly intendet for internal
* Net_LDAP package usage, so if you use it, use it with care.
*
* @access private
* @param boolean $mark Value to set, defaults to "true"
*/
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_LDAP::isError($result) ) {
* echo "Error: ".$result->getMessage();
* } else {
* echo "No match found.";
* }
* }
*
*
* Please note that it is important to test for an Net_LDAP_Error, because objects are
* evaluating to true by default, thus if a 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_LDAP_Error TRUE, if we had a match in one of the values, otherwise false. Net_LDAP_Error in case something went wrong
*/
function preg_match($regex, $attr_name, $matches = array())
{
$matches = array();
// fetch attribute values
$attr = $this->getValue($attr_name, 'all');
if (Net_LDAP::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;
}
/**
* Is this entry going to be deleted once update() is called?
*
* @return boolean
*/
function willBeDeleted()
{
return $this->_delete;
}
/**
* Is this entry going to be moved once update() is called?
*
* @return boolean
*/
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
*/
function currentDN()
{
return $this->_dn;
}
/**
* Returns the attribute changes to be carried out once update() is called
*
* @return array
*/
function getChanges()
{
return $this->_changes;
}
}
?>
Net_LDAP-1.1.5/LDAP/Filter.php 100644 1750 1750 41624 11223350110 10757
* $filter0 = Net_LDAP_Filter::create('stars', 'equals', '***');
* $filter_not0 = Net_LDAP_Filter::combine('not', $filter0);
*
* $filter1 = Net_LDAP_Filter::create('gn', 'begins', 'bar');
* $filter2 = Net_LDAP_Filter::create('gn', 'ends', 'baz');
* $filter_comp = Net_LDAP_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_LDAP
* @author Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version CVS: $Id: Filter.php,v 1.27 2008/06/04 06:12:04 beni Exp $
* @link http://pear.php.net/package/Net_LDAP/
*/
class Net_LDAP_Filter extends PEAR
{
/**
* Storage for combination of filters
*
* This variable holds a array of filter objects
* that should be combined by this filter object.
*
* @access private
* @var array
*/
var $_subfilters = array();
/**
* Match of this filter
*
* If this is a leaf filter, then a matching rule is stored,
* if it is a container, then it is a logical operator
*
* @access private
* @var string
*/
var $_match;
/**
* Single filter
*
* If we operate in leaf filter mode,
* then the constructing method stores
* the filter representation here
*
* @acces private
* @var string
*/
var $_filter;
/**
* Create a new Net_LDAP_Filter object and parse $filter.
*
* This is for PERL Net::LDAP interface.
* Construction of Net_LDAP_Filter objects should happen through either
* {@link create()} or {@link combine()} which give you more control.
* However, you may use the perl iterface if you already have generated filters.
*
* @param string $filter LDAP filter string
*
* @see parse()
*/
function Net_LDAP_Filter($filter = false)
{
// The optional parameter must remain here, because otherwise create() crashes
if (false !== $filter) {
$filter_o = Net_LDAP_Filter::parse($filter);
if (PEAR::isError($filter_o)) {
$this->_filter = $filter_o; // assign error, so asString() can report it
} else {
$this->_filter = $filter_o->asString();
}
}
}
/**
* Constructor of a new part of a LDAP filter.
*
* The following matching rules exists:
* - equals: One of the attributes values is exactly $value
* Please note that case sensitiviness is depends on the
* attributes syntax configured in the server.
* - begins: One of the attributes values must begin with $value
* - ends: One of the attributes values must end with $value
* - contains: One of the attributes values must contain $value
* - any: The attribute can contain any value but must be existent
* - greater: The attributes value is greater than $value
* - less: The attributes value is less than $value
* - greaterOrEqual: The attributes value is greater or equal than $value
* - lessOrEqual: The attributes value is less or equal than $value
* - approx: One of the attributes values is similar to $value
*
* If $escape is set to true (default) then $value will be escaped
* properly. If it is set to false then $value will be treaten as raw value.
*
* Examples:
*
* // This will find entries that contain an attribute "sn" that ends with "foobar":
* $filter = new Net_LDAP_Filter('sn', 'ends', 'foobar');
*
* // This will find entries that contain an attribute "sn" that has any value set:
* $filter = new Net_LDAP_Filter('sn', 'any');
*
*
* @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_LDAP_Util::escape_filter_value()} for detailed information)
*
* @return Net_LDAP_Filter|Net_LDAP_Error
*/
function &create($attr_name, $match, $value = '', $escape = true)
{
$leaf_filter = new Net_LDAP_Filter();
if ($escape) {
$array = Net_LDAP_Util::escape_filter_value(array($value));
$value = $array[0];
}
switch (strtolower($match)) {
case 'equals':
$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':
$leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')';
break;
case 'less':
$leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')';
break;
case 'greaterorequal':
$leaf_filter->_filter = '(' . $attr_name . '>=' . $value . ')';
break;
case 'lessorequal':
$leaf_filter->_filter = '(' . $attr_name . '<=' . $value . ')';
break;
case 'approx':
$leaf_filter->_filter = '(' . $attr_name . '~=' . $value . ')';
break;
case 'any':
$leaf_filter->_filter = '(' . $attr_name . '=*)';
break;
default:
return PEAR::raiseError('Net_LDAP_Filter create error: matching rule "' . $match . '" not known!');
}
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_LDAP_Filter('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 locicall operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
* @param array|Net_LDAP_Filter $filters array with Net_LDAP_Filter objects
*
* @return Net_LDAP_Filter|Net_LDAP_Error
* @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 also accept one filter object or filter string
if (!is_array($filters) && is_a($filters, 'Net_LDAP_Filter')) {
$filters = array($filters); // force array
} elseif (is_string($filters)) {
$filter_o = Net_LDAP_Filter::parse($filters);
if (PEAR::isError($filter_o)) {
$err = PEAR::raiseError('Net_LDAP_Filter combine error: '.$filter_o->getMessage());
return $err;
} else {
$filters = array($filter_o);
}
} else {
$err = PEAR::raiseError('Net_LDAP_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP_Filter nor an array nor a filter string!');
return $err;
}
} elseif ($log_op == '&' || $log_op == '|') {
if (!is_array($filters) || count($filters) < 2) {
$err = PEAR::raiseError('Net_LDAP_Filter combine error: parameter $filters is not an array or contains less than two Net_LDAP_Filter objects!');
return $err;
}
} else {
$err = PEAR::raiseError('Net_LDAP_Filter combine error: logical operator is not known!');
return $err;
}
$combined_filter = new Net_LDAP_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 = Net_LDAP_Filter::parse($testfilter);
if (PEAR::isError($filter_o)) {
return $filter_o;
} else {
$filters[$key] = $filter_o;
}
} elseif (!is_a($testfilter, 'Net_LDAP_Filter')) {
$err = PEAR::raiseError('Net_LDAP_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_LDAP_Filter object
*
* This parses an filter string into Net_LDAP_Filter objects.
*
* @param string $FILTER The filter string
*
* @access static
* @return Net_LDAP_Filter|Net_LDAP_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)
*/
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 ("(...)").
// extract logical operator and subfilters
$log_op = substr($matches[1], 0, 1);
$remaining_component = substr($matches[1], 1);
// bite off the next filter part and parse
$subfilters = array();
while (preg_match('/^(\(.+?\))(.*)/', $remaining_component, $matches)) {
$remaining_component = $matches[2];
$filter_o = Net_LDAP_Filter::parse($matches[1]);
if (PEAR::isError($filter_o)) {
return $filter_o;
}
array_push($subfilters, $filter_o);
}
// combine subfilters using the logical operator
$filter_o = Net_LDAP_Filter::combine($log_op, $subfilters);
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_LDAP_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_LDAP_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_LDAP_Error
*/
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()
*/
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_LDAP_Error
*/
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_LDAP_Util::escape_filter_value()}.
* For upward compatibiliy reasons you are strongly encouraged to use the escape
* methods provided by the Net_LDAP_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_LDAP_Util::escape_filter_value() directly
*/
function escape($value)
{
$return = Net_LDAP_Util::escape_filter_value(array($value));
return $return[0];
}
/**
* Is this a container or a leaf filter object?
*
* @access private
* @return boolean
*/
function _isLeaf()
{
if (count($this->_subfilters) > 0) {
return false; // Container!
} else {
return true; // Leaf!
}
}
}
?>
Net_LDAP-1.1.5/LDAP/RootDSE.php 100644 1750 1750 11062 11223350110 11002
* @author Jan Wagner
* @author Del
* @author Benedikt Hallinger
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version CVS: $Id: RootDSE.php,v 1.12 2008/10/26 15:31:06 clockwerx Exp $
* @link http://pear.php.net/package/Net_LDAP/
*/
require_once 'PEAR.php';
/**
* Getting the rootDSE entry of a LDAP server
*
* @category Net
* @package Net_LDAP
* @author Jan Wagner
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP/
*/
class Net_LDAP_RootDSE extends PEAR
{
/**
* @access private
* @var object Net_LDAP_Entry
**/
var $_entry;
/**
* Class constructor
*
* @param Net_LDAP_Entry &$entry Net_LDAP_Entry object
*/
function Net_LDAP_RootDSE(&$entry)
{
$this->_entry = $entry;
}
/**
* Gets the requested attribute value
*
* Same usuage as {@link Net_LDAP_Entry::getValue()}
*
* @param string $attr Attribute name
* @param array $options Array of options
*
* @access public
* @return mixed Net_LDAP_Error object or attribute values
* @see Net_LDAP_Entry::get_value()
*/
function getValue($attr = '', $options = '')
{
return $this->_entry->get_value($attr, $options);
}
/**
* Alias function of getValue() for perl-ldap interface
*
* @see getValue()
*/
function get_value()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'getValue' ), $args);
}
/**
* Determines if the extension is supported
*
* @param array $oids Array of oids to check
*
* @access public
* @return boolean
*/
function supportedExtension($oids)
{
return $this->_checkAttr($oids, 'supportedExtension');
}
/**
* Alias function of supportedExtension() for perl-ldap interface
*
* @see supportedExtension()
*/
function supported_extension()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'supportedExtension'), $args);
}
/**
* Determines if the version is supported
*
* @param array $versions Versions to check
*
* @access public
* @return boolean
*/
function supportedVersion($versions)
{
return $this->_checkAttr($versions, 'supportedLDAPVersion');
}
/**
* Alias function of supportedVersion() for perl-ldap interface
*
* @see supportedVersion()
*/
function supported_version()
{
$args = func_get_args();
return call_user_func_array(array(&$this, 'supportedVersion'), $args);
}
/**
* Determines if the control is supported
*
* @param array $oids Control oids to check
*
* @access public
* @return boolean
*/
function supportedControl($oids)
{
return $this->_checkAttr($oids, 'supportedControl');
}
/**
* Alias function of supportedControl() for perl-ldap interface
*
* @see supportedControl()
*/
function supported_control()
{
$args = func_get_args();
return call_user_func_array(array(&$this, 'supportedControl' ), $args);
}
/**
* Determines if the sasl mechanism is supported
*
* @param array $mechlist SASL mechanisms to check
*
* @access public
* @return boolean
*/
function supportedSASLMechanism($mechlist)
{
return $this->_checkAttr($mechlist, 'supportedSASLMechanisms');
}
/**
* Alias function of supportedSASLMechanism() for perl-ldap interface
*
* @see supportedSASLMechanism()
*/
function supported_sasl_mechanism()
{
$args = func_get_args();
return call_user_func_array(array(&$this, 'supportedSASLMechanism'), $args);
}
/**
* Checks for existance of value in attribute
*
* @param array $values values to check
* @param string $attr attribute name
*
* @access private
* @return boolean
*/
function _checkAttr($values, $attr)
{
if (!is_array($values)) $values = array($values);
foreach ($values as $value) {
if (!@in_array($value, $this->get_value($attr, 'all'))) {
return false;
}
}
return true;
}
}
?>
Net_LDAP-1.1.5/LDAP/Schema.php 100644 1750 1750 35166 11223350110 10736
* @author Jan Wagner
* @author Del
* @author Benedikt Hallinger
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version CVS: $Id: Schema.php,v 1.23 2008/10/26 15:31:06 clockwerx Exp $
* @link http://pear.php.net/package/Net_LDAP/
*/
require_once 'PEAR.php';
/**
* Syntax definitions
*
* Please don't forget to add binary attributes to isBinary() below
* to support proper value fetching from Net_LDAP_Entry
*/
define('NET_LDAP_SYNTAX_BOOLEAN', '1.3.6.1.4.1.1466.115.121.1.7');
define('NET_LDAP_SYNTAX_DIRECTORY_STRING', '1.3.6.1.4.1.1466.115.121.1.15');
define('NET_LDAP_SYNTAX_DISTINGUISHED_NAME', '1.3.6.1.4.1.1466.115.121.1.12');
define('NET_LDAP_SYNTAX_INTEGER', '1.3.6.1.4.1.1466.115.121.1.27');
define('NET_LDAP_SYNTAX_JPEG', '1.3.6.1.4.1.1466.115.121.1.28');
define('NET_LDAP_SYNTAX_NUMERIC_STRING', '1.3.6.1.4.1.1466.115.121.1.36');
define('NET_LDAP_SYNTAX_OID', '1.3.6.1.4.1.1466.115.121.1.38');
define('NET_LDAP_SYNTAX_OCTET_STRING', '1.3.6.1.4.1.1466.115.121.1.40');
/**
* Load an LDAP Schema and provide information
*
* This class takes a Subschema entry, parses this information
* and makes it available in an array. Most of the code has been
* inspired by perl-ldap( http://perl-ldap.sourceforge.net).
* You will find portions of their implementation in here.
*
* @category Net
* @package Net_LDAP
* @author Jan Wagner
* @author Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP/
*/
class Net_LDAP_Schema extends PEAR
{
/**
* Map of entry types to ldap attributes of subschema entry
*
* @access public
* @var array
*/
var $types = array('attribute' => 'attributeTypes',
'ditcontentrule' => 'dITContentRules',
'ditstructurerule' => 'dITStructureRules',
'matchingrule' => 'matchingRules',
'matchingruleuse' => 'matchingRuleUse',
'nameform' => 'nameForms',
'objectclass' => 'objectClasses',
'syntax' => 'ldapSyntaxes');
/**
* Array of entries belonging to this type
*
* @access private
* @var array
*/
var $_attributeTypes = array();
var $_matchingRules = array();
var $_matchingRuleUse = array();
var $_ldapSyntaxes = array();
var $_objectClasses = array();
var $_dITContentRules = array();
var $_dITStructureRules = array();
var $_nameForms = array();
/**
* hash of all fetched oids
*
* @access private
* @var array
*/
var $_oids = array();
/**
* Tells if the schema is initialized
*
* @access private
* @var boolean
* @see parse(), get()
*/
var $_initialized = false;
/**
* constructor of the class
*
* @access protected
*/
function Net_LDAP_Schema()
{
$this->PEAR('Net_LDAP_Error'); // default error class
}
/**
* Return a hash of entries for the given type
*
* Returns a hash of entry for th givene type. Types may be:
* objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,
* matchingruleuses, nameforms, syntaxes
*
* @param string $type Type to fetch
*
* @access public
* @return array|Net_LDAP_Error Array or Net_LDAP_Error
*/
function &getAll($type)
{
$map = array('objectclasses' => &$this->_objectClasses,
'attributes' => &$this->_attributeTypes,
'ditcontentrules' => &$this->_dITContentRules,
'ditstructurerules' => &$this->_dITStructureRules,
'matchingrules' => &$this->_matchingRules,
'matchingruleuses' => &$this->_matchingRuleUse,
'nameforms' => &$this->_nameForms,
'syntaxes' => &$this->_ldapSyntaxes );
$key = strtolower($type);
$ret = ((key_exists($key, $map)) ? $map[$key] : PEAR::raiseError("Unknown type $type"));
return $ret;
}
/**
* Return a specific entry
*
* @param string $type Type of name
* @param string $name Name or OID to fetch
*
* @access public
* @return mixed Entry or Net_LDAP_Error
*/
function &get($type, $name)
{
if ($this->_initialized) {
$type = strtolower($type);
if (false == key_exists($type, $this->types)) {
return PEAR::raiseError("No such type $type");
}
$name = strtolower($name);
$type_var = &$this->{'_' . $this->types[$type]};
if (key_exists($name, $type_var)) {
return $type_var[$name];
} elseif (key_exists($name, $this->_oids) && $this->_oids[$name]['type'] == $type) {
return $this->_oids[$name];
} else {
return PEAR::raiseError("Could not find $type $name");
}
} else {
$return = null;
return $return;
}
}
/**
* Fetches attributes that MAY be present in the given objectclass
*
* @param string $oc Name or OID of objectclass
*
* @access public
* @return array|Net_LDAP_Error Array with attributes or Net_LDAP_Error
*/
function may($oc)
{
return $this->_getAttr($oc, 'may');
}
/**
* Fetches attributes that MUST be present in the given objectclass
*
* @param string $oc Name or OID of objectclass
*
* @access public
* @return array|Net_LDAP_Error Array with attributes or Net_LDAP_Error
*/
function must($oc)
{
return $this->_getAttr($oc, 'must');
}
/**
* Fetches the given attribute from the given objectclass
*
* @param string $oc Name or OID of objectclass
* @param string $attr Name of attribute to fetch
*
* @access private
* @return array|Net_LDAP_Error The attribute or Net_LDAP_Error
*/
function _getAttr($oc, $attr)
{
$oc = strtolower($oc);
if (key_exists($oc, $this->_objectClasses) && key_exists($attr, $this->_objectClasses[$oc])) {
return $this->_objectClasses[$oc][$attr];
} elseif (key_exists($oc, $this->_oids) &&
$this->_oids[$oc]['type'] == 'objectclass' &&
key_exists($attr, $this->_oids[$oc])) {
return $this->_oids[$oc][$attr];
} else {
return PEAR::raiseError("Could not find $attr attributes for $oc ");
}
}
/**
* Returns the name(s) of the immediate superclass(es)
*
* @param string $oc Name or OID of objectclass
*
* @return array|Net_LDAP_Error Array of names or Net_LDAP_Error
*/
function superclass($oc)
{
$o = $this->get('objectclass', $oc);
if (Net_LDAP::isError($o)) {
return $o;
}
return (key_exists('sup', $o) ? $o['sup'] : array());
}
/**
* Parses the schema of the given Subschema entry
*
* @param Net_LDAP_Entry &$entry Subschema entry
*
* @access public
*/
function parse(&$entry)
{
foreach ($this->types as $type => $attr) {
// initialize map type to entry
$type_var = '_' . $attr;
$this->{$type_var} = array();
// get values for this type
if ($entry->exists($attr)) {
$values = $entry->getValue($attr);
if (is_array($values)) {
foreach ($values as $value) {
unset($schema_entry); // this was a real mess without it
// get the schema entry
$schema_entry = $this->_parse_entry($value);
// set the type
$schema_entry['type'] = $type;
// save a ref in $_oids
$this->_oids[$schema_entry['oid']] = &$schema_entry;
// save refs for all names in type map
$names = $schema_entry['aliases'];
array_push($names, $schema_entry['name']);
foreach ($names as $name) {
$this->{$type_var}[strtolower($name)] = &$schema_entry;
}
}
}
}
}
$this->_initialized = true;
}
/**
* parses an attribute value into a schema entry
*
* @param string $value Attribute value
*
* @access private
* @return array|false Schema entry array or false
*/
function &_parse_entry($value)
{
// tokens that have no value associated
$noValue = array('single-value',
'obsolete',
'collective',
'no-user-modification',
'abstract',
'structural',
'auxiliary');
// tokens that can have multiple values
$multiValue = array('must', 'may', 'sup');
$schema_entry = array('aliases' => array()); // initilization
$tokens = $this->_tokenize($value); // get an array of tokens
// remove surrounding brackets
if ($tokens[0] == '(') array_shift($tokens);
if ($tokens[count($tokens) - 1] == ')') array_pop($tokens); // -1 doesnt work on arrays :-(
$schema_entry['oid'] = array_shift($tokens); // first token is the oid
// cycle over the tokens until none are left
while (count($tokens) > 0) {
$token = strtolower(array_shift($tokens));
if (in_array($token, $noValue)) {
$schema_entry[$token] = 1; // single value token
} else {
// this one follows a string or a list if it is multivalued
if (($schema_entry[$token] = array_shift($tokens)) == '(') {
// this creates the list of values and cycles through the tokens
// until the end of the list is reached ')'
$schema_entry[$token] = array();
while ($tmp = array_shift($tokens)) {
if ($tmp == ')') break;
if ($tmp != '$') array_push($schema_entry[$token], $tmp);
}
}
// create a array if the value should be multivalued but was not
if (in_array($token, $multiValue) && !is_array($schema_entry[$token])) {
$schema_entry[$token] = array($schema_entry[$token]);
}
}
}
// get max length from syntax
if (key_exists('syntax', $schema_entry)) {
if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {
$schema_entry['max_length'] = $matches[1];
}
}
// force a name
if (empty($schema_entry['name'])) {
$schema_entry['name'] = $schema_entry['oid'];
}
// make one name the default and put the other ones into aliases
if (is_array($schema_entry['name'])) {
$aliases = $schema_entry['name'];
$schema_entry['name'] = array_shift($aliases);
$schema_entry['aliases'] = $aliases;
}
return $schema_entry;
}
/**
* tokenizes the given value into an array of tokens
*
* @param string $value String to parse
*
* @access private
* @return array Array of tokens
*/
function _tokenize($value)
{
$tokens = array(); // array of tokens
$matches = array(); // matches[0] full pattern match, [1,2,3] subpatterns
// this one is taken from perl-ldap, modified for php
$pattern = "/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x";
/**
* This one matches one big pattern wherin only one of the three subpatterns matched
* We are interested in the subpatterns that matched. If it matched its value will be
* non-empty and so it is a token. Tokens may be round brackets, a string, or a string
* enclosed by '
*/
preg_match_all($pattern, $value, $matches);
for ($i = 0; $i < count($matches[0]); $i++) { // number of tokens (full pattern match)
for ($j = 1; $j < 4; $j++) { // each subpattern
if (null != trim($matches[$j][$i])) { // pattern match in this subpattern
$tokens[$i] = trim($matches[$j][$i]); // this is the token
}
}
}
return $tokens;
}
/**
* Returns wether a attribute syntax is binary or not
*
* This method gets used by Net_LDAP_Entry to decide which
* PHP function needs to be used to fetch the value in the
* proper format (e.g. binary or string)
*
* @param string $attribute The name of the attribute (eg.: 'sn')
*
* @access public
* @return boolean
*/
function isBinary($attribute)
{
$return = false; // default to false
// This list contains all syntax that should be treaten as
// containing binary values
// The Syntax Definitons go into constants at the top of this page
$syntax_binary = array(
NET_LDAP_SYNTAX_OCTET_STRING,
NET_LDAP_SYNTAX_JPEG
);
// Check Syntax
$attr_s = $this->get('attribute', $attribute);
if (Net_LDAP::isError($attr_s)) {
// Attribute not found in schema
$return = false; // consider attr not binary
} elseif (isset($attr_s['syntax']) && in_array($attr_s['syntax'], $syntax_binary)) {
// Syntax is defined as binary in schema
$return = true;
} else {
// Syntax not defined as binary, or not found
// if attribute is a subtype, check superior attribute syntaxes
if (isset($attr_s['sup'])) {
foreach ($attr_s['sup'] as $superattr) {
$return = $this->isBinary($superattr);
if ($return) {
break; // stop checking parents since we are binary
}
}
}
}
return $return;
}
}
?>
Net_LDAP-1.1.5/LDAP/Search.php 100644 1750 1750 34623 11223350110 10740
* @author Jan Wagner
* @author Del
* @author Benedikt Hallinger
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version CVS: $Id: Search.php,v 1.32 2008/10/26 15:31:06 clockwerx Exp $
* @link http://pear.php.net/package/Net_LDAP/
*/
require_once 'PEAR.php';
/**
* Result set of an LDAP search
*
* @category Net
* @package Net_LDAP
* @author Tarjej Huse
* @author Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP/
*/
class Net_LDAP_Search extends PEAR
{
/**
* Search result identifier
*
* @access private
* @var resource
*/
var $_search;
/**
* LDAP resource link
*
* @access private
* @var resource
*/
var $_link;
/**
* Net_LDAP object
*
* A reference of the Net_LDAP object for passing to Net_LDAP_Entry
*
* @access private
* @var object Net_LDAP
*/
var $_ldap;
/**
* Result entry identifier
*
* @access private
* @var resource
*/
var $_entry = null;
/**
* The errorcode the search got
*
* Some errorcodes might be of interest, but might not be best handled as errors.
* examples: 4 - LDAP_SIZELIMIT_EXCEEDED - indicates a huge search.
* Incomplete results are returned. If you just want to check if there's anything in the search.
* than this is a point to handle.
* 32 - no such object - search here returns a count of 0.
*
* @access private
* @var int
*/
var $_errorCode = 0; // if not set - sucess!
/**
* What attributes we searched for
*
* The $attributes array contains the names of the searched attributes and gets
* passed from $Net_LDAP->search() so the Net_LDAP_Search object can tell
* what attributes was searched for ({@link _searchedAttrs())
*
* This variable gets set from the constructor and returned
* from {@link _searchedAttrs()}
*
* @access private
* @var array
*/
var $_searchedAttrs = array();
/**
* Cache variable for storing entries fetched internally
*
* This currently is only used by {@link pop_entry()}
*
* @access private
* @var array
*/
var $_entry_cache = false;
/**
* Constructor
*
* @param resource &$search Search result identifier
* @param Net_LDAP|resource &$ldap Net_LDAP object or just a LDAP-Link resource
* @param array $attributes (optional) Array with searched attribute names. (see {@link $_searchedAttrs})
*
* @access protected
*/
function Net_LDAP_Search(&$search, &$ldap, $attributes = array())
{
$this->PEAR('Net_LDAP_Error');
$this->setSearch($search);
if (is_a($ldap, 'Net_LDAP')) {
$this->_ldap =& $ldap;
$this->setLink($this->_ldap->getLink());
} else {
$this->setLink($ldap);
}
$this->_errorCode = @ldap_errno($this->_link);
if (is_array($attributes) && !empty($attributes)) {
$this->_searchedAttrs = $attributes;
}
}
/**
* Returns an array of entry objects
*
* @return array Array of entry objects.
*/
function entries()
{
$entries = array();
while ($entry = $this->shiftEntry()) {
$entries[] = $entry;
}
return $entries;
}
/**
* Get the next entry in the searchresult.
*
* This will return a valid Net_LDAP_Entry object or false, so
* you can use this method to easily iterate over the entries inside
* a while loop.
*
* @return Net_LDAP_Entry|false Reference to Net_LDAP_Entry object or false
*/
function &shiftEntry()
{
if ($this->count() == 0 ) {
$false = false;
return $false;
}
if (is_null($this->_entry)) {
$this->_entry = @ldap_first_entry($this->_link, $this->_search);
$entry = new Net_LDAP_Entry($this->_ldap, $this->_entry);
} else {
if (!$this->_entry = @ldap_next_entry($this->_link, $this->_entry)) {
$false = false;
return $false;
}
$entry = new Net_LDAP_Entry($this->_ldap, $this->_entry);
}
return $entry;
}
/**
* Alias function of shiftEntry() for perl-ldap interface
*
* @see shiftEntry()
* @return Net_LDAP_Entry|false Reference to Net_LDAP_Entry object or false
*/
function shift_entry()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'shiftEntry' ), $args);
}
/**
* Retrieve the next entry in the searchresult, but starting from last entry
*
* This is the opposite to {@link shiftEntry()} and is also very useful
* to be used inside a while loop.
*
* @return Net_LDAP_Entry|false
*/
function popEntry()
{
if (false === $this->_entry_cache) {
// fetch entries into cache if not done so far
$this->_entry_cache = $this->entries();
}
$return = array_pop($this->_entry_cache);
return (null === $return)? false : $return;
}
/**
* Alias function of popEntry() for perl-ldap interface
*
* @see popEntry()
* @return Net_LDAP_Entry|false
*/
function pop_entry()
{
$args = func_get_args();
return call_user_func_array(array( &$this, 'popEntry' ), $args);
}
/**
* Return entries sorted as array
*
* This returns a array with sorted entries and the values.
* Sorting is done with PHPs {@link array_multisort()}.
* This method relies on {@link as_struct()} to fetch the raw data of the entries.
*
* Please note that attribute names are case sensitive!
*
* Usage example:
*
* // 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_LDAP_Error Array with sorted entries or error
*/
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_LDAP_Entry objects.
* The sorting is actually done with {@link sorted_as_struct()}.
*
* Please note that attribute names are case sensitive!
*
* 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_LDAP_Error Array with sorted Net_LDAP_Entries or error
*/
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
*/
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
*/
function setSearch(&$search)
{
$this->_search = $search;
}
/**
* Set the ldap ressource link
*
* @param resource &$link Link identifier
*
* @access public
* @return void
*/
function setLink(&$link)
{
$this->_link = $link;
}
/**
* Returns the number of entries in the searchresult
*
* @return int Number of entries in search.
*/
function count()
{
// this catches the situation where OL returned errno 32 = no such object!
if (!$this->_search) {
return 0;
}
return @ldap_count_entries($this->_link, $this->_search);
}
/**
* Get the errorcode the object got in its search.
*
* @return int The ldap error number.
*/
function getErrorCode()
{
return $this->_errorCode;
}
/**
* Destructor
*
* @access protected
*/
function _Net_LDAP_Search()
{
@ldap_free_result($this->_search);
}
/**
* Closes search result
*
* @return void
*/
function done()
{
$this->_Net_LDAP_Search();
}
/**
* Return the attribute names this search selected
*
* @return array
* @see $_searchedAttrs
* @access private
*/
function _searchedAttrs()
{
return $this->_searchedAttrs;
}
/**
* Tells if this search exceeds a sizelimit
*
* @return boolean
*/
function sizeLimitExceeded()
{
return ($this->getErrorCode() == 4);
}
}
?>
Net_LDAP-1.1.5/LDAP/Util.php 100644 1750 1750 56415 11223350110 10453
* @author Jan Wagner
* @author Del
* @author Benedikt Hallinger
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version CVS: $Id: Util.php,v 1.29 2008/10/26 15:31:06 clockwerx Exp $
* @link http://pear.php.net/package/Net_LDAP/
*/
require_once 'PEAR.php';
/**
* Utility Class for Net_LDAP
*
* This class servers some functionality to the other classes of Net_LDAP but most of
* the methods can be used separately as well.
*
* @category Net
* @package Net_LDAP
* @author Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP/
*/
class Net_LDAP_Util extends PEAR
{
/**
* Private empty Constructur
*
* @access private
*/
function Net_LDAP_Util()
{
// We do nothing here, since all methods can be called statically.
// In Net_LDAP <= 0.7, we needed a instance of Util, because
// it was possible to do utf8 encoding and decoding, but this
// has been moved to the LDAP class. The constructor remains only
// here to document the downward compatibility of creating a instance.
}
/**
* Explodes the given DN into its elements
*
* {@link http://www.ietf.org/rfc/rfc2253.txt RFC 2253} says, a Distinguished Name is a sequence
* of Relative Distinguished Names (RDNs), which themselves
* are sets of Attributes. For each RDN a array is constructed where the RDN part is stored.
*
* For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded to:
* array( [0] => array([0] => 'OU=Sales', [1] => 'CN=J. Smith'), [2] => 'DC=example', [3] => 'DC=net' )
*
* [NOT IMPLEMENTED] DNs might also contain values, which are the bytes of the BER encoding of
* the X.500 AttributeValue rather than some LDAP string syntax. These values are hex-encoded
* and prefixed with a #. To distinguish such BER values, ldap_explode_dn uses references to
* the actual values, e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is exploded to:
* [ { '1.3.6.1.4.1.1466.0' => "\004\002Hi" }, { 'DC' => 'example' }, { 'DC' => 'com' } ];
* See {@link http://www.vijaymukhi.com/vmis/berldap.htm} for more information on BER.
*
* It also performs the following operations on the given DN:
* - Unescape "\" followed by ",", "+", """, "\", "<", ">", ";", "#", "=", " ", or a hexpair
* and strings beginning with "#".
* - Removes the leading 'OID.' characters if the type is an OID instead of a name.
* - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
*
* OPTIONS is a list of name/value pairs, valid options are:
* casefold Controls case folding of attribute types names.
* Attribute values are not affected by this option.
* The default is to uppercase. Valid values are:
* lower Lowercase attribute types names.
* upper Uppercase attribute type names. This is the default.
* none Do not change attribute type names.
* reverse If TRUE, the RDN sequence is reversed.
* onlyvalues If TRUE, then only attributes values are returned ('foo' instead of 'cn=foo')
*
* @param string $dn The DN that should be exploded
* @param array $options Options to use
*
* @static
* @return array Parts of the exploded DN
* @todo implement BER
*/
function ldap_explode_dn($dn, $options = array('casefold' => 'upper'))
{
if (!isset($options['onlyvalues'])) {
$options['onlyvalues'] = false;
}
if (!isset($options['reverse'])) {
$options['reverse'] = false;
}
if (!isset($options['casefold'])) {
$options['casefold'] = 'upper';
}
// Escaping of DN and stripping of "OID."
$dn = Net_LDAP_Util::canonical_dn($dn, array('casefold' => $options['casefold']));
// splitting the DN
$dn_array = preg_split('/(?<=[^\\\\]),/', $dn);
// construct subarrays for multivalued RDNs and unescape DN value
// also convert to output format and apply casefolding
foreach ($dn_array as $key => $value) {
$value_u = Net_LDAP_Util::unescape_dn_value($value);
$rdns = Net_LDAP_Util::split_rdn_multival($value_u[0]);
if (count($rdns) > 1) {
// MV RDN!
foreach ($rdns as $subrdn_k => $subrdn_v) {
// Casefolding
if ($options['casefold'] == 'upper') {
$subrdn_v = preg_replace("/^(\w+=)/e", "''.strtoupper('\\1').''", $subrdn_v);
}
if ($options['casefold'] == 'lower') {
$subrdn_v = preg_replace("/^(\w+=)/e", "''.strtolower('\\1').''", $subrdn_v);
}
if ($options['onlyvalues']) {
preg_match('/(.+?)(?", ";", "#", "=" with a special meaning in RFC 2252
* are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair.
* Finally all leading and trailing spaces are converted to sequences of \20.
*
* @param array $values An array containing the DN values that should be escaped
*
* @static
* @return array The array $values, but escaped
*/
function escape_dn_value($values = array())
{
// Parameter validation
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $key => $val) {
// Escaping of filter meta characters
$val = str_replace('\\', '\\\\', $val);
$val = str_replace(',', '\,', $val);
$val = str_replace('+', '\+', $val);
$val = str_replace('"', '\"', $val);
$val = str_replace('<', '\<', $val);
$val = str_replace('>', '\>', $val);
$val = str_replace(';', '\;', $val);
$val = str_replace('#', '\#', $val);
$val = str_replace('=', '\=', $val);
// ASCII < 32 escaping
$val = Net_LDAP_Util::asc2hex32($val);
// Convert all leading and trailing spaces to sequences of \20.
if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) {
$val = $matches[2];
for ($i = 0; $i < strlen($matches[1]); $i++) {
$val = '\20'.$val;
}
for ($i = 0; $i < strlen($matches[3]); $i++) {
$val = $val.'\20';
}
}
if (null === $val) {
$val = '\0'; // apply escaped "null" if string is empty
}
$values[$key] = $val;
}
return $values;
}
/**
* Undoes the conversion done by escape_dn_value().
*
* Any escape sequence starting with a baskslash - hexpair or special character -
* will be transformed back to the corresponding character.
*
* @param array $values Array of DN Values
*
* @return array Same as $values, but unescaped
* @static
*/
function unescape_dn_value($values = array())
{
// Parameter validation
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $key => $val) {
// strip slashes from special chars
$val = str_replace('\\\\', '\\', $val);
$val = str_replace('\,', ',', $val);
$val = str_replace('\+', '+', $val);
$val = str_replace('\"', '"', $val);
$val = str_replace('\<', '<', $val);
$val = str_replace('\>', '>', $val);
$val = str_replace('\;', ';', $val);
$val = str_replace('\#', '#', $val);
$val = str_replace('\=', '=', $val);
// Translate hex code into ascii
$values[$key] = Net_LDAP_Util::hex2asc($val);
}
return $values;
}
/**
* Returns the given DN in a canonical form
*
* Returns false if DN is not a valid Distinguished Name.
* DN can either be a string or an array
* as returned by ldap_explode_dn, which is useful when constructing a DN.
* The DN array may have be indexed (each array value is a OCL=VALUE pair)
* or associative (array key is OCL and value is VALUE).
*
* It performs the following operations on the given DN:
* - Removes the leading 'OID.' characters if the type is an OID instead of a name.
* - Escapes all RFC 2253 special characters (",", "+", """, "\", "<", ">", ";", "#", "="), slashes ("/"), and any other character where the ASCII code is < 32 as \hexpair.
* - Converts all leading and trailing spaces in values to be \20.
* - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
*
* OPTIONS is a list of name/value pairs, valid options are:
* casefold Controls case folding of attribute type names.
* Attribute values are not affected by this option. The default is to uppercase.
* Valid values are:
* lower Lowercase attribute type names.
* upper Uppercase attribute type names. This is the default.
* none Do not change attribute type names.
* [NOT IMPLEMENTED] mbcescape If TRUE, characters that are encoded as a multi-octet UTF-8 sequence will be escaped as \(hexpair){2,*}.
* reverse If TRUE, the RDN sequence is reversed.
* separator Separator to use between RDNs. Defaults to comma (',').
*
* Note: The empty string "" is a valid DN, so be sure not to do a "$can_dn == false" test,
* because an empty string evaluates to false. Use the "===" operator instead.
*
* @param array|string $dn The DN
* @param array $options Options to use
*
* @static
* @return false|string The canonical DN or FALSE
* @todo implement option mbcescape
*/
function canonical_dn($dn, $options = array('casefold' => 'upper', 'separator' => ','))
{
if ($dn === '') {
return $dn; // empty DN is valid!
}
// options check
if (!isset($options['reverse'])) {
$options['reverse'] = false;
} else {
$options['reverse'] = true;
}
if (!isset($options['casefold'])) {
$options['casefold'] = 'upper';
}
if (!isset($options['separator'])) {
$options['separator'] = ',';
}
if (!is_array($dn)) {
// It is not clear to me if the perl implementation splits by the user defined
// separator or if it just uses this separator to construct the new DN
$dn = preg_split('/(?<=[^\\\\])'.$options['separator'].'/', $dn);
// clear wrong splitting (possibly we have split too much)
$dn = Net_LDAP_Util::_correct_dn_splitting($dn, $options['separator']);
} else {
// Is array, check, if the array is indexed or associative
$assoc = false;
foreach ($dn as $dn_key => $dn_part) {
if (!is_int($dn_key)) {
$assoc = true;
}
}
// convert to indexed, if associative array detected
if ($assoc) {
$newdn = array();
foreach ($dn as $dn_key => $dn_part) {
if (is_array($dn_part)) {
ksort($dn_part, SORT_STRING); // we assume here, that the rdn parts are also associative
$newdn[] = $dn_part; // copy array as-is, so we can resolve it later
} else {
$newdn[] = $dn_key.'='.$dn_part;
}
}
$dn =& $newdn;
}
}
// Escaping and casefolding
foreach ($dn as $pos => $dnval) {
if (is_array($dnval)) {
// subarray detected, this means very surely, that we had
// a multivalued dn part, which must be resolved
$dnval_new = '';
foreach ($dnval as $subkey => $subval) {
// build RDN part
if (!is_int($subkey)) {
$subval = $subkey.'='.$subval;
}
$subval_processed = Net_LDAP_Util::canonical_dn($subval);
if (false === $subval_processed) {
return false;
}
$dnval_new .= $subval_processed.'+';
}
$dn[$pos] = substr($dnval_new, 0, -1); // store RDN part, strip last plus
} else {
// try to split multivalued RDNS into array
$rdns = Net_LDAP_Util::split_rdn_multival($dnval);
if (count($rdns) > 1) {
// Multivalued RDN was detected!
// The RDN value is expected to be correctly split by split_rdn_multival().
// It's time to sort the RDN and build the DN!
$rdn_string = '';
sort($rdns, SORT_STRING); // Sort RDN keys alphabetically
foreach ($rdns as $rdn) {
$subval_processed = Net_LDAP_Util::canonical_dn($rdn);
if (false === $subval_processed) {
return false;
}
$rdn_string .= $subval_processed.'+';
}
$dn[$pos] = substr($rdn_string, 0, -1); // store RDN part, strip last plus
} else {
// no multivalued RDN!
// split at first unescaped "="
$dn_comp = preg_split('/(?<=[^\\\\])=/', $rdns[0], 2);
$ocl = ltrim($dn_comp[0]); // trim left whitespaces 'cause of "cn=foo, l=bar" syntax (whitespace after comma)
$val = $dn_comp[1];
// strip 'OID.', otherwise apply casefolding and escaping
if (substr(strtolower($ocl), 0, 4) == 'oid.') {
$ocl = substr($ocl, 4);
} else {
if ($options['casefold'] == 'upper') {
$ocl = strtoupper($ocl);
}
if ($options['casefold'] == 'lower') {
$ocl = strtolower($ocl);
}
$ocl = Net_LDAP_Util::escape_dn_value(array($ocl));
$ocl = $ocl[0];
}
// escaping of dn-value
$val = Net_LDAP_Util::escape_dn_value(array($val));
$val = str_replace('/', '\/', $val[0]);
$dn[$pos] = $ocl.'='.$val;
}
}
}
if ($options['reverse']) {
$dn = array_reverse($dn);
}
return implode($options['separator'], $dn);
}
/**
* Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
*
* Any control characters with an ACII code < 32 as well as the characters with special meaning in
* LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
* backslash followed by two hex digits representing the hexadecimal value of the character.
*
* @param array $values Array of values to escape
*
* @static
* @return array Array $values, but escaped
*/
function escape_filter_value($values = array())
{
// Parameter validation
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $key => $val) {
// Escaping of filter meta characters
$val = str_replace('\\', '\5c', $val);
$val = str_replace('*', '\2a', $val);
$val = str_replace('(', '\28', $val);
$val = str_replace(')', '\29', $val);
// ASCII < 32 escaping
$val = Net_LDAP_Util::asc2hex32($val);
if (null === $val) {
$val = '\0'; // apply escaped "null" if string is empty
}
$values[$key] = $val;
}
return $values;
}
/**
* Undoes the conversion done by {@link escape_filter_value()}.
*
* Converts any sequences of a backslash followed by two
* hex digits into the corresponding character.
*
* @param array $values Array of values to escape
*
* @static
* @return array Array $values, but unescaped
*/
function unescape_filter_value($values = array())
{
// Parameter validation
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $key => $value) {
// Translate hex code into ascii
$values[$key] = Net_LDAP_Util::hex2asc($value);
}
return $values;
}
/**
* Converts all ASCII chars < 32 to "\HEX"
*
* @param string $string String to convert
*
* @static
* @return string
*/
function asc2hex32($string)
{
for ($i = 0; $i < strlen($string); $i++) {
$char = substr($string, $i, 1);
if (ord($char) < 32) {
$hex = dechex(ord($char));
if (strlen($hex) == 1) {
$hex = '0'.$hex;
}
$string = str_replace($char, '\\'.$hex, $string);
}
}
return $string;
}
/**
* Converts all Hex expressions ("\HEX") to their original ASCII characters
*
* @param string $string String to convert
*
* @static
* @author beni@php.net, heavily based on work from DavidSmith@byu.net
* @return string
*/
function hex2asc($string)
{
$string = preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $string);
return $string;
}
/**
* Split an multivalued RDN value into an Array
*
* A RDN can contain multiple values, spearated by a plus sign.
* This function returns each separate ocl=value pair of the RDN part.
*
* If no multivalued RDN is detected, a array containing only
* the original rdn part is returned.
*
* For example, the multivalued RDN 'OU=Sales+CN=J. Smith' is exploded to:
* array([0] => 'OU=Sales', [1] => 'CN=J. Smith')
*
* The method trys to be smart if it encounters unescaped "+" characters, but may fail,
* so ensure escaped "+"es in attr names and attr values.
*
* [BUG] If you use string mode and have a multivalued RDN with unescaped plus characters
* and there is a unescaped plus sign at the end of an value followed by an
* attribute name containing an unescaped plus, then you will get wrong splitting:
* $rdn = 'OU=Sales+C+N=J. Smith';
* returns:
* array('OU=Sales+C', 'N=J. Smith');
* The "C+" is treaten as value of the first pair instead as attr name of the second pair.
* To prevent this, escape correctly.
*
* @param string $rdn Part of an (multivalued) escaped RDN (eg. ou=foo OR ou=foo+cn=bar)
*
* @static
* @return array Array with the components of the multivalued RDN or Error
*/
function split_rdn_multival($rdn)
{
$rdns = preg_split('/(? $dn_value) {
$dn_value = $dn[$key]; // refresh value (foreach caches!)
// if the dn_value is not in attr=value format, then we had an
// unescaped separator character inside the attr name or the value.
// We assume, that it was the attribute value.
// [TODO] To solve this, we might ask the schema. Keep in mind, that UTIL class
// must remain independent from the other classes or connections.
if (!preg_match('/.+(?
Net_LDAP-1.1.5/LDAP/LDIF.php 100644 1750 1750 101446 11223350110 10267
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version CVS: $Id: LDIF.php,v 1.33 2008/10/26 15:31:06 clockwerx Exp $
* @link http://pear.php.net/package/Net_LDAP/
*/
require_once 'PEAR.php';
require_once 'Net/LDAP.php';
require_once 'Net/LDAP/Entry.php';
require_once 'Net/LDAP/Util.php';
/**
* LDIF capabilitys for Net_LDAP, closely taken from PERLs Net::LDAP
*
* It provides a means to convert between Net_LDAP_Entry objects and LDAP entries
* represented in LDIF format files. Reading and writing are supported and may
* manipulate single entries or lists of entries.
*
* Usage example:
*
* // Read and parse an ldif-file into Net_LDAP_Entry objects
* // and print out the DNs. Store the entries for later use.
* require 'Net/LDAP/LDIF.php';
* $options = array(
* 'onerror' => 'die'
* );
* $entries = array();
* $ldif = new Net_LDAP_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_LDAP_LDIF('test.out.ldif', 'w', $options);
* $ldif->write_entry($entries);
* $ldif->done();
*
*
* @category Net
* @package Net_LDAP
* @author Benedikt Hallinger
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @link http://pear.php.net/package/Net_LDAP/
* @see http://www.ietf.org/rfc/rfc2849.txt
* @todo Error handling should be PEARified
* @todo LDAPv3 controls are not implemented yet
*/
class Net_LDAP_LDIF extends PEAR
{
/**
* Options
*
* @access private
* @var array
*/
var $_options = array(
'encode' => 'base64',
'onerror' => 'undef',
'change' => 0,
'lowercase' => 0,
'sort' => 0,
'version' => 1,
'wrap' => 78,
'raw' => ''
);
/**
* Errorcache
*
* @access private
* @var array
*/
var $_error = array(
'error' => null,
'line' => 0
);
/**
* Filehandle for read/write
*
* @access private
* @var array
*/
var $_FH = null;
/**
* Says, if we opened the filehandle ourselves
*
* @access private
* @var array
*/
var $_FH_opened = false;
/**
* Linecounter for input file handle
*
* @access private
* @var array
*/
var $_input_line = 0;
/**
* counter for processed entries
*
* @access private
* @var int
*/
var $_entrynum = 0;
/**
* Mode we are working in
*
* Either 'r', 'a' or 'w'
*
* @access private
* @var string
*/
var $_mode = false;
/**
* Tells, if the LDIF version string was already written
*
* @access private
* @var boolean
*/
var $_version_written = false;
/**
* Cache for lines that have build the current entry
*
* @access private
* @var boolean
*/
var $_lines_cur = array();
/**
* Cache for lines that will build the next entry
*
* @access private
* @var boolean
*/
var $_lines_next = array();
/**
* Open LDIF file for reading or for writing
*
* new (FILE):
* Open the file read-only. FILE may be the name of a file
* or an already open filehandle.
* If the file doesn't exist, it will be created if in write mode.
*
* new (FILE, MODE, OPTIONS):
* Open the file with the given MODE (see PHPs fopen()), eg "w" or "a".
* FILE may be the name of a file or an already open filehandle.
* PERLs Net_LDAP "FILE|" mode does not work curently.
*
* OPTIONS is an associative array and may contain:
* encode => 'none' | 'canonical' | 'base64'
* Some DN values in LDIF cannot be written verbatim and have to be encoded in some way:
* 'none' No encoding.
* 'canonical' See "canonical_dn()" in Net::LDAP::Util.
* 'base64' Use base64. (default, this differs from the Perl interface.
* The perl default is "none"!)
*
* onerror => 'die' | 'warn' | undef
* Specify what happens when an error is detected.
* 'die' Net_LDAP_LDIF will croak with an appropriate message.
* 'warn' Net_LDAP_LDIF will warn (echo) with an appropriate message.
* undef Net_LDAP_LDIF will not warn (default), use error().
*
* change => 1
* Write entry changes to the LDIF file instead of the entries itself. I.e. write LDAP
* operations acting on the entries to the file instead of the entries contents.
* This writes the changes usually carried out by an update() to the LDIF file.
*
* lowercase => 1
* Convert attribute names to lowercase when writing.
*
* sort => 1
* Sort attribute names when writing entries according to the rule:
* objectclass first then all other attributes alphabetically sorted by attribute name
*
* version => '1'
* Set the LDIF version to write to the resulting LDIF file.
* According to RFC 2849 currently the only legal value for this option is 1.
* When this option is set Net_LDAP_LDIF tries to adhere more strictly to
* the LDIF specification in RFC2489 in a few places.
* The default is undef meaning no version information is written to the LDIF file.
*
* wrap => 78
* Number of columns where output line wrapping shall occur.
* Default is 78. Setting it to 40 or lower inhibits wrapping.
*
* [NOT IMPLEMENTED] raw => REGEX
* Use REGEX to denote the names of attributes that are to be
* considered binary in search results if writing entries.
* Example: raw => "/(?i:^jpegPhoto|;binary)/i"
*
* @param string|ressource $file Filename or filehandle
* @param string $mode Mode to open filename
* @param array $options Options like described above
*/
function Net_LDAP_LDIF($file, $mode = 'r', $options = array())
{
$this->PEAR('Net_LDAP_Error'); // default error class
// First, parse options
// todo: maybe implement further checks on possible values
foreach ($options as $option => $value) {
if (!array_key_exists($option, $this->_options)) {
$this->_dropError('Net_LDAP_LDIF error: option '.$option.' not known!');
return;
} else {
$this->_options[$option] = strtolower($value);
}
}
// setup LDIF class
$this->version($this->_options['version']);
// setup file mode
if (!preg_match('/^[rwa]\+?$/', $mode)) {
$this->_dropError('Net_LDAP_LDIF error: file mode '.$mode.' not supported!');
} else {
$this->_mode = $mode;
// setup filehandle
if (is_resource($file)) {
// TODO: checks on mode possible?
$this->_FH =& $file;
} else {
$imode = substr($this->_mode, 0, 1);
if ($imode == 'r') {
if (!file_exists($file)) {
$this->_dropError('Unable to open '.$file.' for read: file not found');
$this->_mode = false;
}
if (!is_readable($file)) {
$this->_dropError('Unable to open '.$file.' for read: permission denied');
$this->_mode = false;
}
}
if (($imode == 'w' || $imode == 'a')) {
if (file_exists($file)) {
if (!is_writable($file)) {
$this->_dropError('Unable to open '.$file.' for write: permission denied');
$this->_mode = false;
}
} else {
if (!@touch($file)) {
$this->_dropError('Unable to create '.$file.' for write: permission denied');
$this->_mode = false;
}
}
}
if ($this->_mode) {
$this->_FH = @fopen($file, $this->_mode);
if (false === $this->_FH) {
// Fallback; should never be reached if tests above are good enough!
$this->_dropError('Net_LDAP_LDIF error: Could not open file '.$file);
} else {
$this->_FH_opened = true;
}
}
}
}
}
/**
* Read one entry from the file and return it as a Net::LDAP::Entry object.
*
* @return Net_LDAP_Entry
*/
function read_entry()
{
// read fresh lines, set them as current lines and create the entry
$attrs = $this->next_lines(true);
if (count($attrs) > 0) {
$this->_lines_cur = $attrs;
}
return $this->current_entry();
}
/**
* Returns true when the end of the file is reached.
*
* @return boolean
*/
function eof()
{
return feof($this->_FH);
}
/**
* Write the entry or entries to the LDIF file.
*
* If you want to build an LDIF file containing several entries AND
* you want to call write_entry() several times, you must open the filehandle
* in append mode ("a"), otherwise you will always get the last entry only.
*
* @param Net_LDAP_Entry|array $entries Entry or array of entries
*
* @return void
* @todo implement operations on whole entries (adding a whole entry)
*/
function write_entry($entries)
{
if (!is_array($entries)) {
$entries = array($entries);
}
foreach ($entries as $entry) {
$this->_entrynum++;
if (!is_a($entry, 'Net_LDAP_Entry')) {
$this->_dropError('Net_LDAP_LDIF error: entry '.$this->_entrynum.' is not an Net_LDAP_Entry object');
} else {
if ($this->_options['change']) {
// LDIF change mode
// fetch change information from entry
$entry_attrs_changes = $entry->getChanges();
$num_of_changes = count($entry_attrs_changes['add'])
+ count($entry_attrs_changes['replace'])
+ count($entry_attrs_changes['delete']);
$is_changed = ($num_of_changes > 0 || $entry->willBeDeleted() || $entry->willBeMoved());
// write version if not done yet
// also write DN of entry
if ($is_changed) {
if (!$this->_version_written) {
$this->write_version();
}
$this->_writeDN($entry->currentDN());
}
// process changes
// TODO: consider DN add!
if ($entry->willBeDeleted()) {
$this->_writeLine("changetype: delete".PHP_EOL);
} elseif ($entry->willBeMoved()) {
$this->_writeLine("changetype: modrdn".PHP_EOL);
$olddn = Net_LDAP_Util::ldap_explode_dn($entry->currentDN(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
$oldrdn = array_shift($olddn);
$oldparent = implode(',', $olddn);
$newdn = Net_LDAP_Util::ldap_explode_dn($entry->dn(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
$rdn = array_shift($newdn);
$parent = implode(',', $newdn);
$this->_writeLine("newrdn: ".$rdn.PHP_EOL);
$this->_writeLine("deleteoldrdn: 1".PHP_EOL);
if ($parent !== $oldparent) {
$this->_writeLine("newsuperior: ".$parent.PHP_EOL);
}
// TODO: What if the entry has attribute changes as well?
// I think we should check for that and make a dummy
// entry with the changes that is written to the LDIF file
} elseif ($num_of_changes > 0) {
// write attribute change data
$this->_writeLine("changetype: modify".PHP_EOL);
foreach ($entry_attrs_changes as $changetype => $entry_attrs) {
foreach ($entry_attrs as $attr_name => $attr_values) {
$this->_writeLine("$changetype: $attr_name".PHP_EOL);
if ($attr_values !== null) {
$this->_writeAttribute($attr_name, $attr_values, $changetype);
}
$this->_writeLine("-".PHP_EOL);
}
}
}
// finish this entrys data if we had changes
if ($is_changed) {
$this->_finishEntry();
}
} else {
// LDIF-content mode
// fetch attributes for further processing
$entry_attrs = $entry->getValues();
// sort and put objectclass-attrs to first position
if ($this->_options['sort']) {
ksort($entry_attrs);
if (array_key_exists('objectclass', $entry_attrs)) {
$oc = $entry_attrs['objectclass'];
unset($entry_attrs['objectclass']);
$entry_attrs = array_merge(array('objectclass' => $oc), $entry_attrs);
}
}
// write data
if (!$this->_version_written) {
$this->write_version();
}
$this->_writeDN($entry->dn());
foreach ($entry_attrs as $attr_name => $attr_values) {
$this->_writeAttribute($attr_name, $attr_values);
}
$this->_finishEntry();
}
}
}
}
/**
* Write version to LDIF
*
* If the object's version is defined, this method allows to explicitely write the version before an entry is written.
* If not called explicitely, it gets called automatically when writing the first entry.
*
* @return void
*/
function write_version()
{
$this->_version_written = true;
return $this->_writeLine('version: '.$this->version().PHP_EOL, 'Net_LDAP_LDIF error: unable to write version');
}
/**
* Get or set LDIF version
*
* If called without arguments it returns the version of the LDIF file or undef if no version has been set.
* If called with an argument it sets the LDIF version to VERSION.
* According to RFC 2849 currently the only legal value for VERSION is 1.
*
* @param int $version (optional) LDIF version to set
*
* @return int
*/
function version($version = null)
{
if ($version !== null) {
if ($version != 1) {
$this->_dropError('Net_LDAP_LDIF error: illegal LDIF version set');
} else {
$this->_version = $version;
}
}
return $this->_version;
}
/**
* Returns the file handle the Net_LDAP_LDIF object reads from or writes to.
*
* You can, for example, use this to fetch the content of the LDIF file yourself
*
* @return null|resource
*/
function &handle()
{
if (!is_resource($this->_FH)) {
$this->_dropError('Net_LDAP_LDIF error: invalid file resource');
$null = null;
return $null;
} else {
return $this->_FH;
}
}
/**
* Clean up
*
* This method signals that the LDIF object is no longer needed.
* You can use this to free up some memory and close the file handle.
* The file handle is only closed, if it was opened from Net_LDAP_LDIF.
*
* @return void
*/
function done()
{
// close FH if we opened it
if ($this->_FH_opened) {
fclose($this->handle());
}
// free variables
foreach (get_object_vars($this) as $name => $value) {
unset($this->$name);
}
}
/**
* Returns last error message if error was found.
*
* Example:
*
* $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_LDAP_Error
*/
function error($as_string = false)
{
if (Net_LDAP::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
*/
function error_lines()
{
return $this->_error['line'];
}
/**
* Returns the current Net::LDAP::Entry object.
*
* @return Net_LDAP_Entry|false
*/
function current_entry()
{
return $this->parseLines($this->current_lines());
}
/**
* Parse LDIF lines of one entry into an Net_LDAP_Entry object
*
* @param array $lines LDIF lines for one entry
*
* @return Net_LDAP_Entry|false Net_LDAP_Entry object for those lines
* @todo what about file inclusions and urls? "jpegphoto:< file:///usr/local/directory/photos/fiona.jpg"
*/
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+)(:|::|:<)\s(.+)$/', $line, $matches)) {
$attr =& $matches[1];
$delim =& $matches[2];
$data =& $matches[3];
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_LDAP_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_LDAP_LDIF parsing error: unable to detect DN for entry');
return false;
} else {
$newentry = Net_LDAP_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
*/
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
*/
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_LDAP_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+::?\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
if (!$commentmode) {
if ($datalines_read == 0) {
// first line of entry: wrapped data is illegal
$this->_dropError('Net_LDAP_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line);
} else {
$last = array_pop($this->_lines_next);
$last = $last.trim($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_LDAP_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 private
* @return string LDIF string for that attribute and value
*/
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 = substr($attr_value, $i, 1);
if (ord($char) >= 127 || in_array($init_ord, $unsafe)) {
$base64 = true;
}
}
// Test for ending space
if (substr($attr_value, -1) == ' ') {
$base64 = true;
}
// If converting is needed, do it
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 private
* @return string LDIF string for that DN
* @todo I am not sure, if the UTF8 stuff is correctly handled right now
*/
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 private
* @return void
*/
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_LDAP_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 private
* @return void
*/
function _writeDN($dn)
{
// prepare DN
if ($this->_options['encode'] == 'base64') {
$dn = $this->_convertDN($dn).PHP_EOL;
} elseif ($this->_options['encode'] == 'canonical') {
$dn = Net_LDAP_Util::canonical_dn($dn, array('casefold' => 'none')).PHP_EOL;
} else {
$dn = $dn.PHP_EOL;
}
$this->_writeLine($dn, 'Net_LDAP_LDIF error: unable to write DN of entry '.$this->_entrynum);
}
/**
* Finishes an LDIF entry
*
* @access private
* @return void
*/
function _finishEntry()
{
$this->_writeLine(PHP_EOL, 'Net_LDAP_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 private
* @return true|false
*/
function _writeLine($line, $error = 'Net_LDAP_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 private
* @return void
*/
function _dropError($msg, $line = null)
{
$this->_error['error'] = new Net_LDAP_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_LDAP-1.1.5/doc/manual.html 100644 1750 1750 21135 11223350110 11204
Net_LDAP Manual
Net_LDAP Manual
Welcome to the Net_LDAP user manual! here you have a quick introduction on
how to use Net_LDAP to acces your directory server with php.
First step: Connect
To do this, use the Net_LDAP::connect function like this:
bindpw = Password of the user specified by 'binddn' (none)
host = the ldap host to connect to (localhost)
base = ldap base, this is usually the Entry point of your directory (none)
port = the server port (389)
starttls = when set, ldap_start_tls() is run after connecting. (false)
version = ldap version (defaults to v 3)
filter = default search filter (objectclass=*)
scope = default search scope (sub)
We'll get back to these later.
Errorhandling
Now you should have the base ldapobject stored in the variable "$ldap".
But, what if it is an error? Net_LDAP returns a Net_LDAP_error object (basicly a
pear_error object) when an error occurs. So wherever you need to check an error, do like this:
$ldap = Net_LDAP::connect($config); // copied from above!
if (Net_LDAP::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().
2) Net_LDAP_Error can also return an errornumber. These numbers are standardized. A good description of what they mean is found there:
http://www.directory-info.com/LDAP/LDAPErrorCodes.html
Searching (basics)
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.
Base:
The "base" is the point under the directory where you want to search under.
To search for all people under php.net, you may use: "ou=People,dc=php,dc=net".
But if you want just to search the devs, you can use "ou=dev,ou=People,dc=php,dc=net".
Filter:
Filters define what you are looking for. They "filter out" unwanted entries.
Filters start with a ( and end with a ). There is a lot to be said about filters, most is better said by examples:
(&(objectclass=posixAccount)(uid=boss)) : The object has to satisfy both filters.
I.e. an object that is both boss and an posixAccount. If you had another object
with uid=boss but that wasn't an postixaccount it would be excluded.
(|(uid=boss)(uid=secretary)) : Either the boss or the secretary.
Note that both logical operators are placed before the filters not between the
two conditions as you might used to from sql.
(&(objectclass=posixAccount)(|(uid=boss)(uid=secretary))) :
Here they must have the posixAccount objectclass as well.
(objectclass=*) : All objects must have an objectclass, so this is the simplest way of saying everything.
(uid=t*) : With the right indexes on the server, you may search the substring of an attriute. Here; all users with the first name beginning with a "T".
Please note, that Net_LDAP provides a filter class for simplier generation and combination of filters.
You should use that class unless you know how filters work. This will save you a lot of trouble,
since there are some encoding issues with ldap-filters. If you want to provide the filter yourself,
you should also have a look to RFC #1558 defining LDAP-Filters.
Searchscope
The scope of an search may be three things:
'base' = Just the entry in question.
'sub' = All subentries.
'one' = All entries just below the searchbase.
Searching with scope 'base' may be handy for getting just one entry. But then again, that's what the getEntry function does.
Searching some entries
We know now, how to search, so we will test out our new knowledge.
We want to search all person entries whose second name starts with "Ha", but only developers.
Later we want to know the name and the telephone number of the persons.
$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_LDAP_Search object.
Okay, now we assume that everything was fine (in production, test for error!).
We have several options now.
We can fetch the found entries at once sorted ($search->sorted()) or unsorted ($search->entries()), or we can read
the objects one by one inside a loop using $search->shiftEntry(). See the class documentation of Net_LDAP_Search
for more details.
Entries
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_LDAPs "getEntry()" method:
$entry->replace("telephonenumber" => "0123456789"); // replace the attributes values with the new number
$entry->update(); // update temporarily modified entry on the server
Of course there are much more other possibilitys. Please note that adding and deleting
whole entrys is performed through the Net_LDAP class and not with the Net_LDAP_Entry class.
Schemas
You may also use Net_LDAP to find out what schemas your ldap-server supports. Here's an example of how:
$schema = $ldap->schema();
Now you got a schemaobject.
To read from this schemaobject, you have several methods defined in the class Net_LDAP_Schema.
For example, to find out which attributes are required for inetOrgPerson, you do this:
$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.
We do:
$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
)
*/