/** * Update the entry on the directory server * * This will evaluate all changes made so far and send them * to the directory server. * Please note, that if you make changes to objectclasses wich * have mandatory attributes set, update() will currently fail. * Remove the entry from the server and readd it as new in such cases. * This also will deal with problems with setting structural object classes. * * @param Net_LDAP2 $ldap If passed, a call to setLDAP() is issued prior update, thus switching the LDAP-server. This is for perl-ldap interface compliance * * @access public * @return true|Net_LDAP2_Error * @todo Entry rename with a DN containing special characters needs testing! */ public function update($ldap = null) { if ($ldap) { $msg = $this->setLDAP($ldap); if (Net_LDAP2::isError($msg)) { return PEAR::raiseError('You passed an invalid $ldap variable to update()'); } } // ensure we have a valid LDAP object $ldap =& $this->getLDAP(); if (!$ldap instanceof Net_LDAP2) { return PEAR::raiseError("The entries LDAP object is not valid"); } // Get and check link $link = $ldap->getLink(); if (!is_resource($link)) { return PEAR::raiseError("Could not update entry: internal LDAP link is invalid"); } /* * Delete the entry */ if (true === $this->_delete) { return $ldap->delete($this); } /* * New entry */ if (true === $this->_new) { $msg = $ldap->add($this); if (Net_LDAP2::isError($msg)) { return $msg; } $this->_new = false; $this->_changes['add'] = array(); $this->_changes['delete'] = array(); $this->_changes['replace'] = array(); $this->_original = $this->_attributes; $return = true; return $return; } /* * Rename/move entry */ if (false == is_null($this->_newdn)) { if ($ldap->getLDAPVersion() !== 3) { return PEAR::raiseError("Renaming/Moving an entry is only supported in LDAPv3"); } // make dn relative to parent (needed for ldap rename) $parent = Net_LDAP2_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false)); if (Net_LDAP2::isError($parent)) { return $parent; } $child = array_shift($parent); // maybe the dn consist of a multivalued RDN, we must build the dn in this case // because the $child-RDN is an array! if (is_array($child)) { $child = Net_LDAP2_Util::canonical_dn($child); } $parent = Net_LDAP2_Util::canonical_dn($parent); // rename/move if (false == @ldap_rename($link, $this->_dn, $child, $parent, true)) { return PEAR::raiseError("Entry not renamed: " . @ldap_error($link), @ldap_errno($link)); } // reflect changes to local copy $this->_dn = $this->_newdn; $this->_newdn = null; } /* * Carry out modifications to the entry */ // ADD foreach ($this->_changes["add"] as $attr => $value) { // if attribute exists, add new values if ($this->exists($attr)) { if (false === @ldap_mod_add($link, $this->dn(), array($attr => $value))) { return PEAR::raiseError("Could not add new values to attribute {$attr}: " . @ldap_error($link), @ldap_errno($link)); } } else { // new attribute if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) { return PEAR::raiseError("Could not add new attribute {$attr}: " . @ldap_error($link), @ldap_errno($link)); } } // all went well here, I guess unset($this->_changes["add"][$attr]); } // DELETE foreach ($this->_changes["delete"] as $attr => $value) { // In LDAPv3 you need to specify the old values for deleting if (is_null($value) && $ldap->getLDAPVersion() === 3) { $value = $this->_original[$attr]; } if (false === @ldap_mod_del($link, $this->dn(), array($attr => $value))) { return PEAR::raiseError("Could not delete attribute {$attr}: " . @ldap_error($link), @ldap_errno($link)); } unset($this->_changes["delete"][$attr]); } // REPLACE foreach ($this->_changes["replace"] as $attr => $value) { if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) { return PEAR::raiseError("Could not replace attribute {$attr} values: " . @ldap_error($link), @ldap_errno($link)); } unset($this->_changes["replace"][$attr]); } // all went well, so _original (server) becomes _attributes (local copy) $this->_original = $this->_attributes; $return = true; return $return; }
/** * Writes a DN to the filehandle * * @param string $dn DN to write * * @access protected * @return void */ protected function writeDN($dn) { // prepare DN if ($this->_options['encode'] == 'base64') { $dn = $this->convertDN($dn) . PHP_EOL; } elseif ($this->_options['encode'] == 'canonical') { $dn = Net_LDAP2_Util::canonical_dn($dn, array('casefold' => 'none')) . PHP_EOL; } else { $dn = $dn . PHP_EOL; } $this->writeLine($dn, 'Net_LDAP2_LDIF error: unable to write DN of entry ' . $this->_entrynum); }
/** * Tells if a DN does exist in the directory * * @param string|Net_LDAP2_Entry $dn The DN of the object to test * * @return boolean|Net_LDAP2_Error */ public function dnExists($dn) { if (PEAR::isError($dn)) { return $dn; } if ($dn instanceof Net_LDAP2_Entry) { $dn = $dn->dn(); } if (false === is_string($dn)) { return PEAR::raiseError('Parameter $dn is not a string nor an entry object!'); } // make dn relative to parent $base = Net_LDAP2_Util::ldap_explode_dn($dn, array('casefold' => 'none', 'reverse' => false, 'onlyvalues' => false)); if (self::isError($base)) { return $base; } $entry_rdn = array_shift($base); if (is_array($entry_rdn)) { // maybe the dn consist of a multivalued RDN, we must build the dn in this case // because the $entry_rdn is an array! $filter_dn = Net_LDAP2_Util::canonical_dn($entry_rdn); } $base = Net_LDAP2_Util::canonical_dn($base); $result = @ldap_list($this->_link, $base, $entry_rdn, array(), 1, 1); if (@ldap_count_entries($this->_link, $result)) { return true; } if (ldap_errno($this->_link) == 32) { return false; } if (ldap_errno($this->_link) != 0) { return PEAR::raiseError(ldap_error($this->_link), ldap_errno($this->_link)); } return false; }
/** * Update the entry on the directory server * * This will evaluate all changes made so far and send them * to the directory server. * Please note, that if you make changes to objectclasses wich * have mandatory attributes set, update() will currently fail. * Remove the entry from the server and readd it as new in such cases. * This also will deal with problems with setting structural object classes. * * @param Net_LDAP2 $ldap If passed, a call to setLDAP() is issued prior update, thus switching the LDAP-server. This is for perl-ldap interface compliance * * @access public * @return true|Net_LDAP2_Error * @todo Entry rename with a DN containing special characters needs testing! */ public function update($ldap = null) { if ($ldap) { $msg = $this->setLDAP($ldap); if (Net_LDAP2::isError($msg)) { return PEAR::raiseError('You passed an invalid $ldap variable to update()'); } } // ensure we have a valid LDAP object $ldap = $this->getLDAP(); if (!$ldap instanceof Net_LDAP2) { return PEAR::raiseError("The entries LDAP object is not valid"); } // Get and check link $link = $ldap->getLink(); if (!is_resource($link)) { return PEAR::raiseError("Could not update entry: internal LDAP link is invalid"); } /* * Delete the entry */ if (true === $this->_delete) { return $ldap->delete($this); } /* * New entry */ if (true === $this->_new) { $msg = $ldap->add($this); if (Net_LDAP2::isError($msg)) { return $msg; } $this->_new = false; $this->_changes['add'] = array(); $this->_changes['delete'] = array(); $this->_changes['replace'] = array(); $this->_original = $this->_attributes; // In case the "new" entry was moved after creation, we must // adjust the internal DNs as the entry was already created // with the most current DN. if (false == is_null($this->_newdn)) { $this->_dn = $this->_newdn; $this->_newdn = null; } $return = true; return $return; } /* * Rename/move entry */ if (false == is_null($this->_newdn)) { if ($ldap->getLDAPVersion() !== 3) { return PEAR::raiseError("Renaming/Moving an entry is only supported in LDAPv3"); } // make dn relative to parent (needed for ldap rename) $parent = Net_LDAP2_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false)); if (Net_LDAP2::isError($parent)) { return $parent; } $child = array_shift($parent); // maybe the dn consist of a multivalued RDN, we must build the dn in this case // because the $child-RDN is an array! if (is_array($child)) { $child = Net_LDAP2_Util::canonical_dn($child); } $parent = Net_LDAP2_Util::canonical_dn($parent); // rename/move if (false == @ldap_rename($link, $this->_dn, $child, $parent, false)) { return PEAR::raiseError("Entry not renamed: " . @ldap_error($link), @ldap_errno($link)); } // reflect changes to local copy $this->_dn = $this->_newdn; $this->_newdn = null; } /* * Retrieve a entry that has all attributes we need so that the list of changes to build is created accurately */ $fullEntry = $ldap->getEntry($this->dn()); if (Net_LDAP2::isError($fullEntry)) { return PEAR::raiseError("Could not retrieve a full set of attributes to reconcile changes with"); } $modifications = array(); // ADD foreach ($this->_changes["add"] as $attr => $value) { // if attribute exists, we need to combine old and new values if ($fullEntry->exists($attr)) { $currentValue = $fullEntry->getValue($attr, "all"); $value = array_merge($currentValue, $value); } $modifications[$attr] = $value; } // 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 = $fullEntry->getValue($attr); } if (!is_array($value)) { $value = array($value); } // Find out what is missing from $value and exclude it $currentValue = isset($modifications[$attr]) ? $modifications[$attr] : $fullEntry->getValue($attr, "all"); $modifications[$attr] = array_values(array_diff($currentValue, $value)); } // REPLACE foreach ($this->_changes["replace"] as $attr => $value) { $modifications[$attr] = $value; } // COMMIT if (false === @ldap_modify($link, $this->dn(), $modifications)) { return PEAR::raiseError("Could not modify the entry: " . @ldap_error($link), @ldap_errno($link)); } // all went well, so _original (server) becomes _attributes (local copy), reset _changes too... $this->_changes['add'] = array(); $this->_changes['delete'] = array(); $this->_changes['replace'] = array(); $this->_original = $this->_attributes; $return = true; return $return; }
/** * Tell if a DN does exist in the directory * * @param string $dn The DN of the object to test * * @return boolean|Net_LDAP2_Error */ public function dnExists($dn) { if (!is_string($dn)) { return PEAR::raiseError('$dn is expected to be a string but is ' . gettype($dn) . ' ' . get_class($dn)); } // make dn relative to parent $base = Net_LDAP2_Util::ldap_explode_dn($dn, array('casefold' => 'none', 'reverse' => false, 'onlyvalues' => false)); if (self::isError($base)) { return $base; } $entry_rdn = array_shift($base); if (is_array($entry_rdn)) { // maybe the dn consist of a multivalued RDN, we must build the dn in this case // because the $entry_rdn is an array! $filter_dn = Net_LDAP2_Util::canonical_dn($entry_rdn); } $base = Net_LDAP2_Util::canonical_dn($base); $result = @ldap_list($this->_link, $base, $entry_rdn, array(), 1, 1); if (@ldap_count_entries($this->_link, $result)) { return true; } if (ldap_errno($this->_link) == 32) { return false; } if (ldap_errno($this->_link) != 0) { return PEAR::raiseError(ldap_error($this->_link), ldap_errno($this->_link)); } return false; }
/** * Tests if canonical_dn() works * * Note: This tests depend on the default options of canonical_dn(). */ public function testCanonical_dn() { // test empty dn (is valid according to rfc) $this->assertEquals('', Net_LDAP2_Util::canonical_dn('')); // default options with common dn $testdn = 'cn=beni,DC=php,c=net'; $expected = 'CN=beni,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($testdn)); // casefold tests with common dn $expected_up = 'CN=beni,DC=php,C=net'; $expected_lo = 'cn=beni,dc=php,c=net'; $expected_no = 'cn=beni,DC=php,c=net'; $this->assertEquals($expected_up, Net_LDAP2_Util::canonical_dn($testdn, array('casefold' => 'upper'))); $this->assertEquals($expected_lo, Net_LDAP2_Util::canonical_dn($testdn, array('casefold' => 'lower'))); $this->assertEquals($expected_no, Net_LDAP2_Util::canonical_dn($testdn, array('casefold' => 'none'))); // reverse $expected_rev = 'C=net,DC=php,CN=beni'; $this->assertEquals($expected_rev, Net_LDAP2_Util::canonical_dn($testdn, array('reverse' => true)), 'Option reverse failed'); // DN as arrays $dn_index = array('cn=beni', 'dc=php', 'c=net'); $dn_assoc = array('cn' => 'beni', 'dc' => 'php', 'c' => 'net'); $expected = 'CN=beni,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($dn_index)); $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($dn_assoc)); // DN with multiple rdn value $testdn = 'ou=dev+cn=beni,DC=php,c=net'; $testdn_index = array(array('ou=dev', 'cn=beni'), 'DC=php', 'c=net'); $testdn_assoc = array(array('ou' => 'dev', 'cn' => 'beni'), 'DC' => 'php', 'c' => 'net'); $expected = 'CN=beni+OU=dev,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($testdn)); $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($testdn_assoc)); $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($expected)); // test DN with OID $testdn = 'OID.2.5.4.3=beni,dc=php,c=net'; $expected = '2.5.4.3=beni,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($testdn)); // test with leading and ending spaces $testdn = 'cn= beni ,DC=php,c=net'; $expected = 'CN=\\20\\20beni\\20\\20,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($testdn)); // test with to-be escaped characters in attr value $specialchars = array(',' => '\\,', '+' => '\\+', '"' => '\\"', '\\' => '\\\\', '<' => '\\<', '>' => '\\>', ';' => '\\;', '#' => '\\#', '=' => '\\=', chr(18) => '\\12', '/' => '\\/'); foreach ($specialchars as $char => $escape) { $test_string = 'CN=be' . $char . 'ni,DC=ph' . $char . 'p,C=net'; $test_index = array('CN=be' . $char . 'ni', 'DC=ph' . $char . 'p', 'C=net'); $test_assoc = array('CN' => 'be' . $char . 'ni', 'DC' => 'ph' . $char . 'p', 'C' => 'net'); $expected = 'CN=be' . $escape . 'ni,DC=ph' . $escape . 'p,C=net'; $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($test_string), 'String escaping test (' . $char . ') failed'); $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($test_index), 'Indexed array escaping test (' . $char . ') failed'); $this->assertEquals($expected, Net_LDAP2_Util::canonical_dn($test_assoc), 'Associative array encoding test (' . $char . ') failed'); } }