You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LDAP2.php 68KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. /**
  4. * File containing the Net_LDAP2 interface class.
  5. *
  6. * PHP version 5
  7. *
  8. * @category Net
  9. * @package Net_LDAP2
  10. * @author Tarjej Huse <tarjei@bergfald.no>
  11. * @author Jan Wagner <wagner@netsols.de>
  12. * @author Del <del@babel.com.au>
  13. * @author Benedikt Hallinger <beni@php.net>
  14. * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
  15. * @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
  16. * @version SVN: $Id$
  17. * @link http://pear.php.net/package/Net_LDAP2/
  18. */
  19. /**
  20. * Package includes.
  21. */
  22. require_once 'PEAR.php';
  23. require_once 'Net/LDAP2/RootDSE.php';
  24. require_once 'Net/LDAP2/Schema.php';
  25. require_once 'Net/LDAP2/Entry.php';
  26. require_once 'Net/LDAP2/Search.php';
  27. require_once 'Net/LDAP2/Util.php';
  28. require_once 'Net/LDAP2/Filter.php';
  29. require_once 'Net/LDAP2/LDIF.php';
  30. require_once 'Net/LDAP2/SchemaCache.interface.php';
  31. require_once 'Net/LDAP2/SimpleFileSchemaCache.php';
  32. /**
  33. * Error constants for errors that are not LDAP errors.
  34. */
  35. define('NET_LDAP2_ERROR', 1000);
  36. /**
  37. * Net_LDAP2 Version
  38. */
  39. define('NET_LDAP2_VERSION', '2.1.0');
  40. /**
  41. * Net_LDAP2 - manipulate LDAP servers the right way!
  42. *
  43. * @category Net
  44. * @package Net_LDAP2
  45. * @author Tarjej Huse <tarjei@bergfald.no>
  46. * @author Jan Wagner <wagner@netsols.de>
  47. * @author Del <del@babel.com.au>
  48. * @author Benedikt Hallinger <beni@php.net>
  49. * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
  50. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  51. * @link http://pear.php.net/package/Net_LDAP2/
  52. */
  53. class Net_LDAP2 extends PEAR
  54. {
  55. /**
  56. * Class configuration array
  57. *
  58. * host = the ldap host to connect to
  59. * (may be an array of several hosts to try)
  60. * port = the server port
  61. * version = ldap version (defaults to v 3)
  62. * starttls = when set, ldap_start_tls() is run after connecting.
  63. * bindpw = no explanation needed
  64. * binddn = the DN to bind as.
  65. * basedn = ldap base
  66. * options = hash of ldap options to set (opt => val)
  67. * filter = default search filter
  68. * scope = default search scope
  69. *
  70. * Newly added in 2.0.0RC4, for auto-reconnect:
  71. * auto_reconnect = if set to true then the class will automatically
  72. * attempt to reconnect to the LDAP server in certain
  73. * failure conditionswhen attempting a search, or other
  74. * LDAP operation. Defaults to false. Note that if you
  75. * set this to true, calls to search() may block
  76. * indefinitely if there is a catastrophic server failure.
  77. * min_backoff = minimum reconnection delay period (in seconds).
  78. * current_backoff = initial reconnection delay period (in seconds).
  79. * max_backoff = maximum reconnection delay period (in seconds).
  80. *
  81. * @access protected
  82. * @var array
  83. */
  84. protected $_config = array('host' => 'localhost',
  85. 'port' => 389,
  86. 'version' => 3,
  87. 'starttls' => false,
  88. 'binddn' => '',
  89. 'bindpw' => '',
  90. 'basedn' => '',
  91. 'options' => array(),
  92. 'filter' => '(objectClass=*)',
  93. 'scope' => 'sub',
  94. 'auto_reconnect' => false,
  95. 'min_backoff' => 1,
  96. 'current_backoff' => 1,
  97. 'max_backoff' => 32);
  98. /**
  99. * List of hosts we try to establish a connection to
  100. *
  101. * @access protected
  102. * @var array
  103. */
  104. protected $_host_list = array();
  105. /**
  106. * List of hosts that are known to be down.
  107. *
  108. * @access protected
  109. * @var array
  110. */
  111. protected $_down_host_list = array();
  112. /**
  113. * LDAP resource link.
  114. *
  115. * @access protected
  116. * @var resource
  117. */
  118. protected $_link = false;
  119. /**
  120. * Net_LDAP2_Schema object
  121. *
  122. * This gets set and returned by {@link schema()}
  123. *
  124. * @access protected
  125. * @var object Net_LDAP2_Schema
  126. */
  127. protected $_schema = null;
  128. /**
  129. * Schema cacher function callback
  130. *
  131. * @see registerSchemaCache()
  132. * @var string
  133. */
  134. protected $_schema_cache = null;
  135. /**
  136. * Cache for attribute encoding checks
  137. *
  138. * @access protected
  139. * @var array Hash with attribute names as key and boolean value
  140. * to determine whether they should be utf8 encoded or not.
  141. */
  142. protected $_schemaAttrs = array();
  143. /**
  144. * Cache for rootDSE objects
  145. *
  146. * Hash with requested rootDSE attr names as key and rootDSE object as value
  147. *
  148. * Since the RootDSE object itself may request a rootDSE object,
  149. * {@link rootDse()} caches successful requests.
  150. * Internally, Net_LDAP2 needs several lookups to this object, so
  151. * caching increases performance significally.
  152. *
  153. * @access protected
  154. * @var array
  155. */
  156. protected $_rootDSE_cache = array();
  157. /**
  158. * Returns the Net_LDAP2 Release version, may be called statically
  159. *
  160. * @static
  161. * @return string Net_LDAP2 version
  162. */
  163. public static function getVersion()
  164. {
  165. return NET_LDAP2_VERSION;
  166. }
  167. /**
  168. * Configure Net_LDAP2, connect and bind
  169. *
  170. * Use this method as starting point of using Net_LDAP2
  171. * to establish a connection to your LDAP server.
  172. *
  173. * Static function that returns either an error object or the new Net_LDAP2
  174. * object. Something like a factory. Takes a config array with the needed
  175. * parameters.
  176. *
  177. * @param array $config Configuration array
  178. *
  179. * @access public
  180. * @return Net_LDAP2_Error|Net_LDAP2 Net_LDAP2_Error or Net_LDAP2 object
  181. */
  182. public static function connect($config = array())
  183. {
  184. $ldap_check = self::checkLDAPExtension();
  185. if (self::iserror($ldap_check)) {
  186. return $ldap_check;
  187. }
  188. @$obj = new Net_LDAP2($config);
  189. // todo? better errorhandling for setConfig()?
  190. // connect and bind with credentials in config
  191. $err = $obj->bind();
  192. if (self::isError($err)) {
  193. return $err;
  194. }
  195. return $obj;
  196. }
  197. /**
  198. * Net_LDAP2 constructor
  199. *
  200. * Sets the config array
  201. *
  202. * Please note that the usual way of getting Net_LDAP2 to work is
  203. * to call something like:
  204. * <code>$ldap = Net_LDAP2::connect($ldap_config);</code>
  205. *
  206. * @param array $config Configuration array
  207. *
  208. * @access protected
  209. * @return void
  210. * @see $_config
  211. */
  212. public function __construct($config = array())
  213. {
  214. parent::__construct('Net_LDAP2_Error');
  215. $this->setConfig($config);
  216. }
  217. /**
  218. * Sets the internal configuration array
  219. *
  220. * @param array $config Configuration array
  221. *
  222. * @access protected
  223. * @return void
  224. */
  225. protected function setConfig($config)
  226. {
  227. //
  228. // Parameter check -- probably should raise an error here if config
  229. // is not an array.
  230. //
  231. if (! is_array($config)) {
  232. return;
  233. }
  234. foreach ($config as $k => $v) {
  235. if (isset($this->_config[$k])) {
  236. $this->_config[$k] = $v;
  237. } else {
  238. // map old (Net_LDAP2) parms to new ones
  239. switch($k) {
  240. case "dn":
  241. $this->_config["binddn"] = $v;
  242. break;
  243. case "password":
  244. $this->_config["bindpw"] = $v;
  245. break;
  246. case "tls":
  247. $this->_config["starttls"] = $v;
  248. break;
  249. case "base":
  250. $this->_config["basedn"] = $v;
  251. break;
  252. }
  253. }
  254. }
  255. //
  256. // Ensure the host list is an array.
  257. //
  258. if (is_array($this->_config['host'])) {
  259. $this->_host_list = $this->_config['host'];
  260. } else {
  261. if (strlen($this->_config['host']) > 0) {
  262. $this->_host_list = array($this->_config['host']);
  263. } else {
  264. $this->_host_list = array();
  265. // ^ this will cause an error in performConnect(),
  266. // so the user is notified about the failure
  267. }
  268. }
  269. //
  270. // Reset the down host list, which seems like a sensible thing to do
  271. // if the config is being reset for some reason.
  272. //
  273. $this->_down_host_list = array();
  274. }
  275. /**
  276. * Bind or rebind to the ldap-server
  277. *
  278. * This function binds with the given dn and password to the server. In case
  279. * no connection has been made yet, it will be started and startTLS issued
  280. * if appropiate.
  281. *
  282. * The internal bind configuration is not being updated, so if you call
  283. * bind() without parameters, you can rebind with the credentials
  284. * provided at first connecting to the server.
  285. *
  286. * @param string $dn Distinguished name for binding
  287. * @param string $password Password for binding
  288. *
  289. * @access public
  290. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  291. */
  292. public function bind($dn = null, $password = null)
  293. {
  294. // fetch current bind credentials
  295. if (is_null($dn)) {
  296. $dn = $this->_config["binddn"];
  297. }
  298. if (is_null($password)) {
  299. $password = $this->_config["bindpw"];
  300. }
  301. // Connect first, if we haven't so far.
  302. // This will also bind us to the server.
  303. if ($this->_link === false) {
  304. // store old credentials so we can revert them later
  305. // then overwrite config with new bind credentials
  306. $olddn = $this->_config["binddn"];
  307. $oldpw = $this->_config["bindpw"];
  308. // overwrite bind credentials in config
  309. // so performConnect() knows about them
  310. $this->_config["binddn"] = $dn;
  311. $this->_config["bindpw"] = $password;
  312. // try to connect with provided credentials
  313. $msg = $this->performConnect();
  314. // reset to previous config
  315. $this->_config["binddn"] = $olddn;
  316. $this->_config["bindpw"] = $oldpw;
  317. // see if bind worked
  318. if (self::isError($msg)) {
  319. return $msg;
  320. }
  321. } else {
  322. // do the requested bind as we are
  323. // asked to bind manually
  324. if (is_null($dn)) {
  325. // anonymous bind
  326. $msg = @ldap_bind($this->_link);
  327. } else {
  328. // privileged bind
  329. $msg = @ldap_bind($this->_link, $dn, $password);
  330. }
  331. if (false === $msg) {
  332. return PEAR::raiseError("Bind failed: " .
  333. @ldap_error($this->_link),
  334. @ldap_errno($this->_link));
  335. }
  336. }
  337. return true;
  338. }
  339. /**
  340. * Connect to the ldap-server
  341. *
  342. * This function connects to the LDAP server specified in
  343. * the configuration, binds and set up the LDAP protocol as needed.
  344. *
  345. * @access protected
  346. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  347. */
  348. protected function performConnect()
  349. {
  350. // Note: Connecting is briefly described in RFC1777.
  351. // Basicly it works like this:
  352. // 1. set up TCP connection
  353. // 2. secure that connection if neccessary
  354. // 3a. setLDAPVersion to tell server which version we want to speak
  355. // 3b. perform bind
  356. // 3c. setLDAPVersion to tell server which version we want to speak
  357. // together with a test for supported versions
  358. // 4. set additional protocol options
  359. // Return true if we are already connected.
  360. if ($this->_link !== false) {
  361. return true;
  362. }
  363. // Connnect to the LDAP server if we are not connected. Note that
  364. // with some LDAP clients, ldapperformConnect returns a link value even
  365. // if no connection is made. We need to do at least one anonymous
  366. // bind to ensure that a connection is actually valid.
  367. //
  368. // Ref: http://www.php.net/manual/en/function.ldap-connect.php
  369. // Default error message in case all connection attempts
  370. // fail but no message is set
  371. $current_error = new PEAR_Error('Unknown connection error');
  372. // Catch empty $_host_list arrays.
  373. if (!is_array($this->_host_list) || count($this->_host_list) == 0) {
  374. $current_error = PEAR::raiseError('No Servers configured! Please '.
  375. 'pass in an array of servers to Net_LDAP2');
  376. return $current_error;
  377. }
  378. // Cycle through the host list.
  379. foreach ($this->_host_list as $host) {
  380. // Ensure we have a valid string for host name
  381. if (is_array($host)) {
  382. $current_error = PEAR::raiseError('No Servers configured! '.
  383. 'Please pass in an one dimensional array of servers to '.
  384. 'Net_LDAP2! (multidimensional array detected!)');
  385. continue;
  386. }
  387. // Skip this host if it is known to be down.
  388. if (in_array($host, $this->_down_host_list)) {
  389. continue;
  390. }
  391. // Record the host that we are actually connecting to in case
  392. // we need it later.
  393. $this->_config['host'] = $host;
  394. // Attempt a connection.
  395. $this->_link = @ldap_connect($host, $this->_config['port']);
  396. if (false === $this->_link) {
  397. $current_error = PEAR::raiseError('Could not connect to ' .
  398. $host . ':' . $this->_config['port']);
  399. $this->_down_host_list[] = $host;
  400. continue;
  401. }
  402. // If we're supposed to use TLS, do so before we try to bind,
  403. // as some strict servers only allow binding via secure connections
  404. if ($this->_config["starttls"] === true) {
  405. if (self::isError($msg = $this->startTLS())) {
  406. $current_error = $msg;
  407. $this->_link = false;
  408. $this->_down_host_list[] = $host;
  409. continue;
  410. }
  411. }
  412. // Try to set the configured LDAP version on the connection if LDAP
  413. // server needs that before binding (eg OpenLDAP).
  414. // This could be necessary since rfc-1777 states that the protocol version
  415. // has to be set at the bind request.
  416. // We use force here which means that the test in the rootDSE is skipped;
  417. // this is neccessary, because some strict LDAP servers only allow to
  418. // read the LDAP rootDSE (which tells us the supported protocol versions)
  419. // with authenticated clients.
  420. // This may fail in which case we try again after binding.
  421. // In this case, most probably the bind() or setLDAPVersion()-call
  422. // below will also fail, providing error messages.
  423. $version_set = false;
  424. $ignored_err = $this->setLDAPVersion(0, true);
  425. if (!self::isError($ignored_err)) {
  426. $version_set = true;
  427. }
  428. // Attempt to bind to the server. If we have credentials configured,
  429. // we try to use them, otherwise its an anonymous bind.
  430. // As stated by RFC-1777, the bind request should be the first
  431. // operation to be performed after the connection is established.
  432. // This may give an protocol error if the server does not support
  433. // V2 binds and the above call to setLDAPVersion() failed.
  434. // In case the above call failed, we try an V2 bind here and set the
  435. // version afterwards (with checking to the rootDSE).
  436. $msg = $this->bind();
  437. if (self::isError($msg)) {
  438. // The bind failed, discard link and save error msg.
  439. // Then record the host as down and try next one
  440. if ($msg->getCode() == 0x02 && !$version_set) {
  441. // provide a finer grained error message
  442. // if protocol error arieses because of invalid version
  443. $msg = new Net_LDAP2_Error($msg->getMessage().
  444. " (could not set LDAP protocol version to ".
  445. $this->_config['version'].")",
  446. $msg->getCode());
  447. }
  448. $this->_link = false;
  449. $current_error = $msg;
  450. $this->_down_host_list[] = $host;
  451. continue;
  452. }
  453. // Set desired LDAP version if not successfully set before.
  454. // Here, a check against the rootDSE is performed, so we get a
  455. // error message if the server does not support the version.
  456. // The rootDSE entry should tell us which LDAP versions are
  457. // supported. However, some strict LDAP servers only allow
  458. // bound suers to read the rootDSE.
  459. if (!$version_set) {
  460. if (self::isError($msg = $this->setLDAPVersion())) {
  461. $current_error = $msg;
  462. $this->_link = false;
  463. $this->_down_host_list[] = $host;
  464. continue;
  465. }
  466. }
  467. // Set LDAP parameters, now we know we have a valid connection.
  468. if (isset($this->_config['options']) &&
  469. is_array($this->_config['options']) &&
  470. count($this->_config['options'])) {
  471. foreach ($this->_config['options'] as $opt => $val) {
  472. $err = $this->setOption($opt, $val);
  473. if (self::isError($err)) {
  474. $current_error = $err;
  475. $this->_link = false;
  476. $this->_down_host_list[] = $host;
  477. continue 2;
  478. }
  479. }
  480. }
  481. // At this stage we have connected, bound, and set up options,
  482. // so we have a known good LDAP server. Time to go home.
  483. return true;
  484. }
  485. // All connection attempts have failed, return the last error.
  486. return $current_error;
  487. }
  488. /**
  489. * Reconnect to the ldap-server.
  490. *
  491. * In case the connection to the LDAP
  492. * service has dropped out for some reason, this function will reconnect,
  493. * and re-bind if a bind has been attempted in the past. It is probably
  494. * most useful when the server list provided to the new() or connect()
  495. * function is an array rather than a single host name, because in that
  496. * case it will be able to connect to a failover or secondary server in
  497. * case the primary server goes down.
  498. *
  499. * This doesn't return anything, it just tries to re-establish
  500. * the current connection. It will sleep for the current backoff
  501. * period (seconds) before attempting the connect, and if the
  502. * connection fails it will double the backoff period, but not
  503. * try again. If you want to ensure a reconnection during a
  504. * transient period of server downtime then you need to call this
  505. * function in a loop.
  506. *
  507. * @access protected
  508. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  509. */
  510. protected function performReconnect()
  511. {
  512. // Return true if we are already connected.
  513. if ($this->_link !== false) {
  514. return true;
  515. }
  516. // Default error message in case all connection attempts
  517. // fail but no message is set
  518. $current_error = new PEAR_Error('Unknown connection error');
  519. // Sleep for a backoff period in seconds.
  520. sleep($this->_config['current_backoff']);
  521. // Retry all available connections.
  522. $this->_down_host_list = array();
  523. $msg = $this->performConnect();
  524. // Bail out if that fails.
  525. if (self::isError($msg)) {
  526. $this->_config['current_backoff'] =
  527. $this->_config['current_backoff'] * 2;
  528. if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
  529. $this->_config['current_backoff'] = $this->_config['max_backoff'];
  530. }
  531. return $msg;
  532. }
  533. // Now we should be able to safely (re-)bind.
  534. $msg = $this->bind();
  535. if (self::isError($msg)) {
  536. $this->_config['current_backoff'] = $this->_config['current_backoff'] * 2;
  537. if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
  538. $this->_config['current_backoff'] = $this->_config['max_backoff'];
  539. }
  540. // _config['host'] should have had the last connected host stored in it
  541. // by performConnect(). Since we are unable to bind to that host we can safely
  542. // assume that it is down or has some other problem.
  543. $this->_down_host_list[] = $this->_config['host'];
  544. return $msg;
  545. }
  546. // At this stage we have connected, bound, and set up options,
  547. // so we have a known good LDAP server. Time to go home.
  548. $this->_config['current_backoff'] = $this->_config['min_backoff'];
  549. return true;
  550. }
  551. /**
  552. * Starts an encrypted session
  553. *
  554. * @access public
  555. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  556. */
  557. public function startTLS()
  558. {
  559. /* Test to see if the server supports TLS first.
  560. This is done via testing the extensions offered by the server.
  561. The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
  562. Note, that not all servers allow to feth either the rootDSE or
  563. attributes over an unencrypted channel, so we must ignore errors. */
  564. $rootDSE = $this->rootDse();
  565. if (self::isError($rootDSE)) {
  566. /* IGNORE this error, because server may refuse fetching the
  567. RootDSE over an unencrypted connection. */
  568. //return $this->raiseError("Unable to fetch rootDSE entry ".
  569. //"to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
  570. } else {
  571. /* Fetch suceeded, see, if the server supports TLS. Again, we
  572. ignore errors, because the server may refuse to return
  573. attributes over unencryted connections. */
  574. $supported_extensions = $rootDSE->getValue('supportedExtension');
  575. if (self::isError($supported_extensions)) {
  576. /* IGNORE error, because server may refuse attribute
  577. returning over an unencrypted connection. */
  578. //return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
  579. //"to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
  580. } else {
  581. // fetch succeedet, lets see if the server supports it.
  582. // if not, then drop an error. If supported, then do nothing,
  583. // because then we try to issue TLS afterwards.
  584. if (!in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
  585. return $this->raiseError("Server reports that it does not support TLS.");
  586. }
  587. }
  588. }
  589. // Try to establish TLS.
  590. if (false === @ldap_start_tls($this->_link)) {
  591. // Starting TLS failed. This may be an error, or because
  592. // the server does not support it but did not enable us to
  593. // detect that above.
  594. return $this->raiseError("TLS could not be started: " .
  595. @ldap_error($this->_link),
  596. @ldap_errno($this->_link));
  597. } else {
  598. return true; // TLS is started now.
  599. }
  600. }
  601. /**
  602. * alias function of startTLS() for perl-ldap interface
  603. *
  604. * @return void
  605. * @see startTLS()
  606. */
  607. public function start_tls()
  608. {
  609. $args = func_get_args();
  610. return call_user_func_array(array( $this, 'startTLS' ), $args);
  611. }
  612. /**
  613. * Close LDAP connection.
  614. *
  615. * Closes the connection. Use this when the session is over.
  616. *
  617. * @return void
  618. */
  619. public function done()
  620. {
  621. $this->_Net_LDAP2();
  622. }
  623. /**
  624. * Alias for {@link done()}
  625. *
  626. * @return void
  627. * @see done()
  628. */
  629. public function disconnect()
  630. {
  631. $this->done();
  632. }
  633. /**
  634. * Destructor
  635. *
  636. * @access protected
  637. */
  638. public function _Net_LDAP2()
  639. {
  640. @ldap_close($this->_link);
  641. }
  642. /**
  643. * Add a new entryobject to a directory.
  644. *
  645. * Use add to add a new Net_LDAP2_Entry object to the directory.
  646. * This also links the entry to the connection used for the add,
  647. * if it was a fresh entry ({@link Net_LDAP2_Entry::createFresh()})
  648. *
  649. * @param Net_LDAP2_Entry $entry Net_LDAP2_Entry
  650. *
  651. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  652. */
  653. public function add($entry)
  654. {
  655. if (!$entry instanceof Net_LDAP2_Entry) {
  656. return PEAR::raiseError('Parameter to Net_LDAP2::add() must be a Net_LDAP2_Entry object.');
  657. }
  658. // Continue attempting the add operation in a loop until we
  659. // get a success, a definitive failure, or the world ends.
  660. $foo = 0;
  661. while (true) {
  662. $link = $this->getLink();
  663. if ($link === false) {
  664. // We do not have a successful connection yet. The call to
  665. // getLink() would have kept trying if we wanted one. Go
  666. // home now.
  667. return PEAR::raiseError("Could not add entry " . $entry->dn() .
  668. " no valid LDAP connection could be found.");
  669. }
  670. if (@ldap_add($link, $entry->dn(), $entry->getValues())) {
  671. // entry successfully added, we should update its $ldap reference
  672. // in case it is not set so far (fresh entry)
  673. if (!$entry->getLDAP() instanceof Net_LDAP2) {
  674. $entry->setLDAP($this);
  675. }
  676. // store, that the entry is present inside the directory
  677. $entry->markAsNew(false);
  678. return true;
  679. } else {
  680. // We have a failure. What type? We may be able to reconnect
  681. // and try again.
  682. $error_code = @ldap_errno($link);
  683. $error_name = Net_LDAP2::errorMessage($error_code);
  684. if (($error_name === 'LDAP_OPERATIONS_ERROR') &&
  685. ($this->_config['auto_reconnect'])) {
  686. // The server has become disconnected before trying the
  687. // operation. We should try again, possibly with a different
  688. // server.
  689. $this->_link = false;
  690. $this->performReconnect();
  691. } else {
  692. // Errors other than the above catched are just passed
  693. // back to the user so he may react upon them.
  694. return PEAR::raiseError("Could not add entry " . $entry->dn() . " " .
  695. $error_name,
  696. $error_code);
  697. }
  698. }
  699. }
  700. }
  701. /**
  702. * Delete an entry from the directory
  703. *
  704. * The object may either be a string representing the dn or a Net_LDAP2_Entry
  705. * object. When the boolean paramter recursive is set, all subentries of the
  706. * entry will be deleted as well.
  707. *
  708. * @param string|Net_LDAP2_Entry $dn DN-string or Net_LDAP2_Entry
  709. * @param boolean $recursive Should we delete all children recursive as well?
  710. *
  711. * @access public
  712. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  713. */
  714. public function delete($dn, $recursive = false)
  715. {
  716. if ($dn instanceof Net_LDAP2_Entry) {
  717. $dn = $dn->dn();
  718. }
  719. if (false === is_string($dn)) {
  720. return PEAR::raiseError("Parameter is not a string nor an entry object!");
  721. }
  722. // Recursive delete searches for children and calls delete for them
  723. if ($recursive) {
  724. $result = @ldap_list($this->_link, $dn, '(objectClass=*)', array(null), 0, 0);
  725. if (@ldap_count_entries($this->_link, $result)) {
  726. $subentry = @ldap_first_entry($this->_link, $result);
  727. $this->delete(@ldap_get_dn($this->_link, $subentry), true);
  728. while ($subentry = @ldap_next_entry($this->_link, $subentry)) {
  729. $this->delete(@ldap_get_dn($this->_link, $subentry), true);
  730. }
  731. }
  732. }
  733. // Continue attempting the delete operation in a loop until we
  734. // get a success, a definitive failure, or the world ends.
  735. while (true) {
  736. $link = $this->getLink();
  737. if ($link === false) {
  738. // We do not have a successful connection yet. The call to
  739. // getLink() would have kept trying if we wanted one. Go
  740. // home now.
  741. return PEAR::raiseError("Could not add entry " . $dn .
  742. " no valid LDAP connection could be found.");
  743. }
  744. if (@ldap_delete($link, $dn)) {
  745. // entry successfully deleted.
  746. return true;
  747. } else {
  748. // We have a failure. What type?
  749. // We may be able to reconnect and try again.
  750. $error_code = @ldap_errno($link);
  751. $error_name = Net_LDAP2::errorMessage($error_code);
  752. if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
  753. ($this->_config['auto_reconnect'])) {
  754. // The server has become disconnected before trying the
  755. // operation. We should try again, possibly with a
  756. // different server.
  757. $this->_link = false;
  758. $this->performReconnect();
  759. } elseif ($error_code == 66) {
  760. // Subentries present, server refused to delete.
  761. // Deleting subentries is the clients responsibility, but
  762. // since the user may not know of the subentries, we do not
  763. // force that here but instead notify the developer so he
  764. // may take actions himself.
  765. return PEAR::raiseError("Could not delete entry $dn because of subentries. Use the recursive parameter to delete them.");
  766. } else {
  767. // Errors other than the above catched are just passed
  768. // back to the user so he may react upon them.
  769. return PEAR::raiseError("Could not delete entry " . $dn . " " .
  770. $error_name,
  771. $error_code);
  772. }
  773. }
  774. }
  775. }
  776. /**
  777. * Modify an ldapentry directly on the server
  778. *
  779. * This one takes the DN or a Net_LDAP2_Entry object and an array of actions.
  780. * This array should be something like this:
  781. *
  782. * array('add' => array('attribute1' => array('val1', 'val2'),
  783. * 'attribute2' => array('val1')),
  784. * 'delete' => array('attribute1'),
  785. * 'replace' => array('attribute1' => array('val1')),
  786. * 'changes' => array('add' => ...,
  787. * 'replace' => ...,
  788. * 'delete' => array('attribute1', 'attribute2' => array('val1')))
  789. *
  790. * The changes array is there so the order of operations can be influenced
  791. * (the operations are done in order of appearance).
  792. * The order of execution is as following:
  793. * 1. adds from 'add' array
  794. * 2. deletes from 'delete' array
  795. * 3. replaces from 'replace' array
  796. * 4. changes (add, replace, delete) in order of appearance
  797. * All subarrays (add, replace, delete, changes) may be given at the same time.
  798. *
  799. * The function calls the corresponding functions of an Net_LDAP2_Entry
  800. * object. A detailed description of array structures can be found there.
  801. *
  802. * Unlike the modification methods provided by the Net_LDAP2_Entry object,
  803. * this method will instantly carry out an update() after each operation,
  804. * thus modifying "directly" on the server.
  805. *
  806. * @param string|Net_LDAP2_Entry $entry DN-string or Net_LDAP2_Entry
  807. * @param array $parms Array of changes
  808. *
  809. * @access public
  810. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  811. */
  812. public function modify($entry, $parms = array())
  813. {
  814. if (is_string($entry)) {
  815. $entry = $this->getEntry($entry);
  816. if (self::isError($entry)) {
  817. return $entry;
  818. }
  819. }
  820. if (!$entry instanceof Net_LDAP2_Entry) {
  821. return PEAR::raiseError("Parameter is not a string nor an entry object!");
  822. }
  823. // Perform changes mentioned separately
  824. foreach (array('add', 'delete', 'replace') as $action) {
  825. if (isset($parms[$action])) {
  826. $msg = $entry->$action($parms[$action]);
  827. if (self::isError($msg)) {
  828. return $msg;
  829. }
  830. $entry->setLDAP($this);
  831. // Because the @ldap functions are called inside Net_LDAP2_Entry::update(),
  832. // we have to trap the error codes issued from that if we want to support
  833. // reconnection.
  834. while (true) {
  835. $msg = $entry->update();
  836. if (self::isError($msg)) {
  837. // We have a failure. What type? We may be able to reconnect
  838. // and try again.
  839. $error_code = $msg->getCode();
  840. $error_name = Net_LDAP2::errorMessage($error_code);
  841. if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
  842. ($this->_config['auto_reconnect'])) {
  843. // The server has become disconnected before trying the
  844. // operation. We should try again, possibly with a different
  845. // server.
  846. $this->_link = false;
  847. $this->performReconnect();
  848. } else {
  849. // Errors other than the above catched are just passed
  850. // back to the user so he may react upon them.
  851. return PEAR::raiseError("Could not modify entry: ".$msg->getMessage());
  852. }
  853. } else {
  854. // modification succeedet, evaluate next change
  855. break;
  856. }
  857. }
  858. }
  859. }
  860. // perform combined changes in 'changes' array
  861. if (isset($parms['changes']) && is_array($parms['changes'])) {
  862. foreach ($parms['changes'] as $action => $value) {
  863. // Because the @ldap functions are called inside Net_LDAP2_Entry::update,
  864. // we have to trap the error codes issued from that if we want to support
  865. // reconnection.
  866. while (true) {
  867. $msg = $this->modify($entry, array($action => $value));
  868. if (self::isError($msg)) {
  869. // We have a failure. What type? We may be able to reconnect
  870. // and try again.
  871. $error_code = $msg->getCode();
  872. $error_name = Net_LDAP2::errorMessage($error_code);
  873. if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
  874. ($this->_config['auto_reconnect'])) {
  875. // The server has become disconnected before trying the
  876. // operation. We should try again, possibly with a different
  877. // server.
  878. $this->_link = false;
  879. $this->performReconnect();
  880. } else {
  881. // Errors other than the above catched are just passed
  882. // back to the user so he may react upon them.
  883. return $msg;
  884. }
  885. } else {
  886. // modification succeedet, evaluate next change
  887. break;
  888. }
  889. }
  890. }
  891. }
  892. return true;
  893. }
  894. /**
  895. * Run a ldap search query
  896. *
  897. * Search is used to query the ldap-database.
  898. * $base and $filter may be ommitted. The one from config will
  899. * then be used. $base is either a DN-string or an Net_LDAP2_Entry
  900. * object in which case its DN willb e used.
  901. *
  902. * Params may contain:
  903. *
  904. * scope: The scope which will be used for searching
  905. * base - Just one entry
  906. * sub - The whole tree
  907. * one - Immediately below $base
  908. * sizelimit: Limit the number of entries returned (default: 0 = unlimited),
  909. * timelimit: Limit the time spent for searching (default: 0 = unlimited),
  910. * attrsonly: If true, the search will only return the attribute names,
  911. * attributes: Array of attribute names, which the entry should contain.
  912. * It is good practice to limit this to just the ones you need.
  913. * [NOT IMPLEMENTED]
  914. * deref: By default aliases are dereferenced to locate the base object for the search, but not when
  915. * searching subordinates of the base object. This may be changed by specifying one of the
  916. * following values:
  917. *
  918. * never - Do not dereference aliases in searching or in locating the base object of the search.
  919. * search - Dereference aliases in subordinates of the base object in searching, but not in
  920. * locating the base object of the search.
  921. * find
  922. * always
  923. *
  924. * Please note, that you cannot override server side limitations to sizelimit
  925. * and timelimit: You can always only lower a given limit.
  926. *
  927. * @param string|Net_LDAP2_Entry $base LDAP searchbase
  928. * @param string|Net_LDAP2_Filter $filter LDAP search filter or a Net_LDAP2_Filter object
  929. * @param array $params Array of options
  930. *
  931. * @access public
  932. * @return Net_LDAP2_Search|Net_LDAP2_Error Net_LDAP2_Search object or Net_LDAP2_Error object
  933. * @todo implement search controls (sorting etc)
  934. */
  935. public function search($base = null, $filter = null, $params = array())
  936. {
  937. if (is_null($base)) {
  938. $base = $this->_config['basedn'];
  939. }
  940. if ($base instanceof Net_LDAP2_Entry) {
  941. $base = $base->dn(); // fetch DN of entry, making searchbase relative to the entry
  942. }
  943. if (is_null($filter)) {
  944. $filter = $this->_config['filter'];
  945. }
  946. if ($filter instanceof Net_LDAP2_Filter) {
  947. $filter = $filter->asString(); // convert Net_LDAP2_Filter to string representation
  948. }
  949. if (PEAR::isError($filter)) {
  950. return $filter;
  951. }
  952. if (PEAR::isError($base)) {
  953. return $base;
  954. }
  955. /* setting searchparameters */
  956. (isset($params['sizelimit'])) ? $sizelimit = $params['sizelimit'] : $sizelimit = 0;
  957. (isset($params['timelimit'])) ? $timelimit = $params['timelimit'] : $timelimit = 0;
  958. (isset($params['attrsonly'])) ? $attrsonly = $params['attrsonly'] : $attrsonly = 0;
  959. (isset($params['attributes'])) ? $attributes = $params['attributes'] : $attributes = array();
  960. // Ensure $attributes to be an array in case only one
  961. // attribute name was given as string
  962. if (!is_array($attributes)) {
  963. $attributes = array($attributes);
  964. }
  965. // reorganize the $attributes array index keys
  966. // sometimes there are problems with not consecutive indexes
  967. $attributes = array_values($attributes);
  968. // scoping makes searches faster!
  969. $scope = (isset($params['scope']) ? $params['scope'] : $this->_config['scope']);
  970. switch ($scope) {
  971. case 'one':
  972. $search_function = 'ldap_list';
  973. break;
  974. case 'base':
  975. $search_function = 'ldap_read';
  976. break;
  977. default:
  978. $search_function = 'ldap_search';
  979. }
  980. // Continue attempting the search operation until we get a success
  981. // or a definitive failure.
  982. while (true) {
  983. $link = $this->getLink();
  984. $search = @call_user_func($search_function,
  985. $link,
  986. $base,
  987. $filter,
  988. $attributes,
  989. $attrsonly,
  990. $sizelimit,
  991. $timelimit);
  992. if ($err = @ldap_errno($link)) {
  993. if ($err == 32) {
  994. // Errorcode 32 = no such object, i.e. a nullresult.
  995. return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
  996. } elseif ($err == 4) {
  997. // Errorcode 4 = sizelimit exeeded.
  998. return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
  999. } elseif ($err == 87) {
  1000. // bad search filter
  1001. return $this->raiseError(Net_LDAP2::errorMessage($err) . "($filter)", $err);
  1002. } elseif (($err == 1) && ($this->_config['auto_reconnect'])) {
  1003. // Errorcode 1 = LDAP_OPERATIONS_ERROR but we can try a reconnect.
  1004. $this->_link = false;
  1005. $this->performReconnect();
  1006. } else {
  1007. $msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope";
  1008. return $this->raiseError(Net_LDAP2::errorMessage($err) . $msg, $err);
  1009. }
  1010. } else {
  1011. return $obj = new Net_LDAP2_Search($search, $this, $attributes);
  1012. }
  1013. }
  1014. }
  1015. /**
  1016. * Set an LDAP option
  1017. *
  1018. * @param string $option Option to set
  1019. * @param mixed $value Value to set Option to
  1020. *
  1021. * @access public
  1022. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  1023. */
  1024. public function setOption($option, $value)
  1025. {
  1026. if ($this->_link) {
  1027. if (defined($option)) {
  1028. if (@ldap_set_option($this->_link, constant($option), $value)) {
  1029. return true;
  1030. } else {
  1031. $err = @ldap_errno($this->_link);
  1032. if ($err) {
  1033. $msg = @ldap_err2str($err);
  1034. } else {
  1035. $err = NET_LDAP2_ERROR;
  1036. $msg = Net_LDAP2::errorMessage($err);
  1037. }
  1038. return $this->raiseError($msg, $err);
  1039. }
  1040. } else {
  1041. return $this->raiseError("Unkown Option requested");
  1042. }
  1043. } else {
  1044. return $this->raiseError("Could not set LDAP option: No LDAP connection");
  1045. }
  1046. }
  1047. /**
  1048. * Get an LDAP option value
  1049. *
  1050. * @param string $option Option to get
  1051. *
  1052. * @access public
  1053. * @return Net_LDAP2_Error|string Net_LDAP2_Error or option value
  1054. */
  1055. public function getOption($option)
  1056. {
  1057. if ($this->_link) {
  1058. if (defined($option)) {
  1059. if (@ldap_get_option($this->_link, constant($option), $value)) {
  1060. return $value;
  1061. } else {
  1062. $err = @ldap_errno($this->_link);
  1063. if ($err) {
  1064. $msg = @ldap_err2str($err);
  1065. } else {
  1066. $err = NET_LDAP2_ERROR;
  1067. $msg = Net_LDAP2::errorMessage($err);
  1068. }
  1069. return $this->raiseError($msg, $err);
  1070. }
  1071. } else {
  1072. $this->raiseError("Unkown Option requested");
  1073. }
  1074. } else {
  1075. $this->raiseError("No LDAP connection");
  1076. }
  1077. }
  1078. /**
  1079. * Get the LDAP_PROTOCOL_VERSION that is used on the connection.
  1080. *
  1081. * A lot of ldap functionality is defined by what protocol version the ldap server speaks.
  1082. * This might be 2 or 3.
  1083. *
  1084. * @return int
  1085. */
  1086. public function getLDAPVersion()
  1087. {
  1088. if ($this->_link) {
  1089. $version = $this->getOption("LDAP_OPT_PROTOCOL_VERSION");
  1090. } else {
  1091. $version = $this->_config['version'];
  1092. }
  1093. return $version;
  1094. }
  1095. /**
  1096. * Set the LDAP_PROTOCOL_VERSION that is used on the connection.
  1097. *
  1098. * @param int $version LDAP-version that should be used
  1099. * @param boolean $force If set to true, the check against the rootDSE will be skipped
  1100. *
  1101. * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
  1102. * @todo Checking via the rootDSE takes much time - why? fetching and instanciation is quick!
  1103. */
  1104. public function setLDAPVersion($version = 0, $force = false)
  1105. {
  1106. if (!$version) {
  1107. $version = $this->_config['version'];
  1108. }
  1109. //
  1110. // Check to see if the server supports this version first.
  1111. //
  1112. // Todo: Why is this so horribly slow?
  1113. // $this->rootDse() is very fast, as well as Net_LDAP2_RootDSE::fetch()
  1114. // seems like a problem at copiyng the object inside PHP??
  1115. // Additionally, this is not always reproducable...
  1116. //
  1117. if (!$force) {
  1118. $rootDSE = $this->rootDse();
  1119. if ($rootDSE instanceof Net_LDAP2_Error) {
  1120. return $rootDSE;
  1121. } else {
  1122. $supported_versions = $rootDSE->getValue('supportedLDAPVersion');
  1123. if (is_string($supported_versions)) {
  1124. $supported_versions = array($supported_versions);
  1125. }
  1126. $check_ok = in_array($version, $supported_versions);
  1127. }
  1128. }
  1129. if ($force || $check_ok) {
  1130. return $this->setOption("LDAP_OPT_PROTOCOL_VERSION", $version);
  1131. } else {
  1132. return $this->raiseError("LDAP Server does not support protocol version " . $version);
  1133. }
  1134. }
  1135. /**
  1136. * Tells if a DN does exist in the directory
  1137. *
  1138. * @param string|Net_LDAP2_Entry $dn The DN of the object to test
  1139. *
  1140. * @return boolean|Net_LDAP2_Error
  1141. */
  1142. public function dnExists($dn)
  1143. {
  1144. if (PEAR::isError($dn)) {
  1145. return $dn;
  1146. }
  1147. if ($dn instanceof Net_LDAP2_Entry) {
  1148. $dn = $dn->dn();
  1149. }
  1150. if (false === is_string($dn)) {
  1151. return PEAR::raiseError('Parameter $dn is not a string nor an entry object!');
  1152. }
  1153. // search LDAP for that DN by performing a baselevel search for any
  1154. // object. We can only find the DN in question this way, or nothing.
  1155. $s_opts = array(
  1156. 'scope' => 'base',
  1157. 'sizelimit' => 1,
  1158. 'attributes' => '1.1' // select no attrs
  1159. );
  1160. $search = $this->search($dn, '(objectClass=*)', $s_opts);
  1161. if (self::isError($search)) {
  1162. return $search;
  1163. }
  1164. // retun wehter the DN exists; that is, we found an entry
  1165. return ($search->count() == 0)? false : true;
  1166. }
  1167. /**
  1168. * Get a specific entry based on the DN
  1169. *
  1170. * @param string $dn DN of the entry that should be fetched
  1171. * @param array $attr Array of Attributes to select. If ommitted, all attributes are fetched.
  1172. *
  1173. * @return Net_LDAP2_Entry|Net_LDAP2_Error Reference to a Net_LDAP2_Entry object or Net_LDAP2_Error object
  1174. * @todo Maybe check against the shema should be done to be sure the attribute type exists
  1175. */
  1176. public function getEntry($dn, $attr = array())
  1177. {
  1178. if (!is_array($attr)) {
  1179. $attr = array($attr);
  1180. }
  1181. $result = $this->search($dn, '(objectClass=*)',
  1182. array('scope' => 'base', 'attributes' => $attr));
  1183. if (self::isError($result)) {
  1184. return $result;
  1185. } elseif ($result->count() == 0) {
  1186. return PEAR::raiseError('Could not fetch entry '.$dn.': no entry found');
  1187. }
  1188. $entry = $result->shiftEntry();
  1189. if (false == $entry) {
  1190. return PEAR::raiseError('Could not fetch entry (error retrieving entry from search result)');
  1191. }
  1192. return $entry;
  1193. }
  1194. /**
  1195. * Rename or move an entry
  1196. *
  1197. * This method will instantly carry out an update() after the move,
  1198. * so the entry is moved instantly.
  1199. * You can pass an optional Net_LDAP2 object. In this case, a cross directory
  1200. * move will be performed which deletes the entry in the source (THIS) directory
  1201. * and adds it in the directory $target_ldap.
  1202. * A cross directory move will switch the Entrys internal LDAP reference so
  1203. * updates to the entry will go to the new directory.
  1204. *
  1205. * Note that if you want to do a cross directory move, you need to
  1206. * pass an Net_LDAP2_Entry object, otherwise the attributes will be empty.
  1207. *
  1208. * @param string|Net_LDAP2_Entry $entry Entry DN or Entry object
  1209. * @param string $newdn New location
  1210. * @param Net_LDAP2 $target_ldap (optional) Target directory for cross server move; should be passed via reference
  1211. *
  1212. * @return Net_LDAP2_Error|true
  1213. */
  1214. public function move($entry, $newdn, $target_ldap = null)
  1215. {
  1216. if (is_string($entry)) {
  1217. $entry_o = $this->getEntry($entry);
  1218. } else {
  1219. $entry_o = $entry;
  1220. }
  1221. if (!$entry_o instanceof Net_LDAP2_Entry) {
  1222. return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object! (If DN was passed, conversion failed)');
  1223. }
  1224. if (null !== $target_ldap && !$target_ldap instanceof Net_LDAP2) {
  1225. return PEAR::raiseError('Parameter $target_ldap is expected to be a Net_LDAP2 object!');
  1226. }
  1227. if ($target_ldap && $target_ldap !== $this) {
  1228. // cross directory move
  1229. if (is_string($entry)) {
  1230. return PEAR::raiseError('Unable to perform cross directory move: operation requires a Net_LDAP2_Entry object');
  1231. }
  1232. if ($target_ldap->dnExists($newdn)) {
  1233. return PEAR::raiseError('Unable to perform cross directory move: entry does exist in target directory');
  1234. }
  1235. $entry_o->dn($newdn);
  1236. $res = $target_ldap->add($entry_o);
  1237. if (self::isError($res)) {
  1238. return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in target directory');
  1239. }
  1240. $res = $this->delete($entry_o->currentDN());
  1241. if (self::isError($res)) {
  1242. $res2 = $target_ldap->delete($entry_o); // undo add
  1243. if (self::isError($res2)) {
  1244. $add_error_string = 'Additionally, the deletion (undo add) of $entry in target directory failed.';
  1245. }
  1246. return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in source directory. '.$add_error_string);
  1247. }
  1248. $entry_o->setLDAP($target_ldap);
  1249. return true;
  1250. } else {
  1251. // local move
  1252. $entry_o->dn($newdn);
  1253. $entry_o->setLDAP($this);
  1254. return $entry_o->update();
  1255. }
  1256. }
  1257. /**
  1258. * Copy an entry to a new location
  1259. *
  1260. * The entry will be immediately copied.
  1261. * Please note that only attributes you have
  1262. * selected will be copied.
  1263. *
  1264. * @param Net_LDAP2_Entry $entry Entry object
  1265. * @param string $newdn New FQF-DN of the entry
  1266. *
  1267. * @return Net_LDAP2_Error|Net_LDAP2_Entry Error Message or reference to the copied entry
  1268. */
  1269. public function copy($entry, $newdn)
  1270. {
  1271. if (!$entry instanceof Net_LDAP2_Entry) {
  1272. return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object!');
  1273. }
  1274. $newentry = Net_LDAP2_Entry::createFresh($newdn, $entry->getValues());
  1275. $result = $this->add($newentry);
  1276. if ($result instanceof Net_LDAP2_Error) {
  1277. return $result;
  1278. } else {
  1279. return $newentry;
  1280. }
  1281. }
  1282. /**
  1283. * Returns the string for an ldap errorcode.
  1284. *
  1285. * Made to be able to make better errorhandling
  1286. * Function based on DB::errorMessage()
  1287. * Tip: The best description of the errorcodes is found here:
  1288. * http://www.directory-info.com/LDAP2/LDAPErrorCodes.html
  1289. *
  1290. * @param int $errorcode Error code
  1291. *
  1292. * @return string The errorstring for the error.
  1293. */
  1294. public static function errorMessage($errorcode)
  1295. {
  1296. $errorMessages = array(
  1297. 0x00 => "LDAP_SUCCESS",
  1298. 0x01 => "LDAP_OPERATIONS_ERROR",
  1299. 0x02 => "LDAP_PROTOCOL_ERROR",
  1300. 0x03 => "LDAP_TIMELIMIT_EXCEEDED",
  1301. 0x04 => "LDAP_SIZELIMIT_EXCEEDED",
  1302. 0x05 => "LDAP_COMPARE_FALSE",
  1303. 0x06 => "LDAP_COMPARE_TRUE",
  1304. 0x07 => "LDAP_AUTH_METHOD_NOT_SUPPORTED",
  1305. 0x08 => "LDAP_STRONG_AUTH_REQUIRED",
  1306. 0x09 => "LDAP_PARTIAL_RESULTS",
  1307. 0x0a => "LDAP_REFERRAL",
  1308. 0x0b => "LDAP_ADMINLIMIT_EXCEEDED",
  1309. 0x0c => "LDAP_UNAVAILABLE_CRITICAL_EXTENSION",
  1310. 0x0d => "LDAP_CONFIDENTIALITY_REQUIRED",
  1311. 0x0e => "LDAP_SASL_BIND_INPROGRESS",
  1312. 0x10 => "LDAP_NO_SUCH_ATTRIBUTE",
  1313. 0x11 => "LDAP_UNDEFINED_TYPE",
  1314. 0x12 => "LDAP_INAPPROPRIATE_MATCHING",
  1315. 0x13 => "LDAP_CONSTRAINT_VIOLATION",
  1316. 0x14 => "LDAP_TYPE_OR_VALUE_EXISTS",
  1317. 0x15 => "LDAP_INVALID_SYNTAX",
  1318. 0x20 => "LDAP_NO_SUCH_OBJECT",
  1319. 0x21 => "LDAP_ALIAS_PROBLEM",
  1320. 0x22 => "LDAP_INVALID_DN_SYNTAX",
  1321. 0x23 => "LDAP_IS_LEAF",
  1322. 0x24 => "LDAP_ALIAS_DEREF_PROBLEM",
  1323. 0x30 => "LDAP_INAPPROPRIATE_AUTH",
  1324. 0x31 => "LDAP_INVALID_CREDENTIALS",
  1325. 0x32 => "LDAP_INSUFFICIENT_ACCESS",
  1326. 0x33 => "LDAP_BUSY",
  1327. 0x34 => "LDAP_UNAVAILABLE",
  1328. 0x35 => "LDAP_UNWILLING_TO_PERFORM",
  1329. 0x36 => "LDAP_LOOP_DETECT",
  1330. 0x3C => "LDAP_SORT_CONTROL_MISSING",
  1331. 0x3D => "LDAP_INDEX_RANGE_ERROR",
  1332. 0x40 => "LDAP_NAMING_VIOLATION",
  1333. 0x41 => "LDAP_OBJECT_CLASS_VIOLATION",
  1334. 0x42 => "LDAP_NOT_ALLOWED_ON_NONLEAF",
  1335. 0x43 => "LDAP_NOT_ALLOWED_ON_RDN",
  1336. 0x44 => "LDAP_ALREADY_EXISTS",
  1337. 0x45 => "LDAP_NO_OBJECT_CLASS_MODS",
  1338. 0x46 => "LDAP_RESULTS_TOO_LARGE",
  1339. 0x47 => "LDAP_AFFECTS_MULTIPLE_DSAS",
  1340. 0x50 => "LDAP_OTHER",
  1341. 0x51 => "LDAP_SERVER_DOWN",
  1342. 0x52 => "LDAP_LOCAL_ERROR",
  1343. 0x53 => "LDAP_ENCODING_ERROR",
  1344. 0x54 => "LDAP_DECODING_ERROR",
  1345. 0x55 => "LDAP_TIMEOUT",
  1346. 0x56 => "LDAP_AUTH_UNKNOWN",
  1347. 0x57 => "LDAP_FILTER_ERROR",
  1348. 0x58 => "LDAP_USER_CANCELLED",
  1349. 0x59 => "LDAP_PARAM_ERROR",
  1350. 0x5a => "LDAP_NO_MEMORY",
  1351. 0x5b => "LDAP_CONNECT_ERROR",
  1352. 0x5c => "LDAP_NOT_SUPPORTED",
  1353. 0x5d => "LDAP_CONTROL_NOT_FOUND",
  1354. 0x5e => "LDAP_NO_RESULTS_RETURNED",
  1355. 0x5f => "LDAP_MORE_RESULTS_TO_RETURN",
  1356. 0x60 => "LDAP_CLIENT_LOOP",
  1357. 0x61 => "LDAP_REFERRAL_LIMIT_EXCEEDED",
  1358. 1000 => "Unknown Net_LDAP2 Error"
  1359. );
  1360. return isset($errorMessages[$errorcode]) ?
  1361. $errorMessages[$errorcode] :
  1362. $errorMessages[NET_LDAP2_ERROR] . ' (' . $errorcode . ')';
  1363. }
  1364. /**
  1365. * Gets a rootDSE object
  1366. *
  1367. * This either fetches a fresh rootDSE object or returns it from
  1368. * the internal cache for performance reasons, if possible.
  1369. *
  1370. * @param array $attrs Array of attributes to search for
  1371. *
  1372. * @access public
  1373. * @return Net_LDAP2_Error|Net_LDAP2_RootDSE Net_LDAP2_Error or Net_LDAP2_RootDSE object
  1374. */
  1375. public function rootDse($attrs = null)
  1376. {
  1377. if ($attrs !== null && !is_array($attrs)) {
  1378. return PEAR::raiseError('Parameter $attr is expected to be an array!');
  1379. }
  1380. $attrs_signature = serialize($attrs);
  1381. // see if we need to fetch a fresh object, or if we already
  1382. // requested this object with the same attributes
  1383. if (true || !array_key_exists($attrs_signature, $this->_rootDSE_cache)) {
  1384. $rootdse = Net_LDAP2_RootDSE::fetch($this, $attrs);
  1385. if ($rootdse instanceof Net_LDAP2_Error) {
  1386. return $rootdse;
  1387. }
  1388. // search was ok, store rootDSE in cache
  1389. $this->_rootDSE_cache[$attrs_signature] = $rootdse;
  1390. }
  1391. return $this->_rootDSE_cache[$attrs_signature];
  1392. }
  1393. /**
  1394. * Alias function of rootDse() for perl-ldap interface
  1395. *
  1396. * @access public
  1397. * @see rootDse()
  1398. * @return Net_LDAP2_Error|Net_LDAP2_RootDSE
  1399. */
  1400. public function root_dse()
  1401. {
  1402. $args = func_get_args();
  1403. return call_user_func_array(array($this, 'rootDse'), $args);
  1404. }
  1405. /**
  1406. * Get a schema object
  1407. *
  1408. * @param string $dn (optional) Subschema entry dn
  1409. *
  1410. * @access public
  1411. * @return Net_LDAP2_Schema|Net_LDAP2_Error Net_LDAP2_Schema or Net_LDAP2_Error object
  1412. */
  1413. public function schema($dn = null)
  1414. {
  1415. // Schema caching by Knut-Olav Hoven
  1416. // If a schema caching object is registered, we use that to fetch
  1417. // a schema object.
  1418. // See registerSchemaCache() for more info on this.
  1419. if ($this->_schema === null) {
  1420. if ($this->_schema_cache) {
  1421. $cached_schema = $this->_schema_cache->loadSchema();
  1422. if ($cached_schema instanceof Net_LDAP2_Error) {
  1423. return $cached_schema; // route error to client
  1424. } else {
  1425. if ($cached_schema instanceof Net_LDAP2_Schema) {
  1426. $this->_schema = $cached_schema;
  1427. }
  1428. }
  1429. }
  1430. }
  1431. // Fetch schema, if not tried before and no cached version available.
  1432. // If we are already fetching the schema, we will skip fetching.
  1433. if ($this->_schema === null) {
  1434. // store a temporary error message so subsequent calls to schema() can
  1435. // detect, that we are fetching the schema already.
  1436. // Otherwise we will get an infinite loop at Net_LDAP2_Schema::fetch()
  1437. $this->_schema = new Net_LDAP2_Error('Schema not initialized');
  1438. $this->_schema = Net_LDAP2_Schema::fetch($this, $dn);
  1439. // If schema caching is active, advise the cache to store the schema
  1440. if ($this->_schema_cache) {
  1441. $caching_result = $this->_schema_cache->storeSchema($this->_schema);
  1442. if ($caching_result instanceof Net_LDAP2_Error) {
  1443. return $caching_result; // route error to client
  1444. }
  1445. }
  1446. }
  1447. return $this->_schema;
  1448. }
  1449. /**
  1450. * Enable/disable persistent schema caching
  1451. *
  1452. * Sometimes it might be useful to allow your scripts to cache
  1453. * the schema information on disk, so the schema is not fetched
  1454. * every time the script runs which could make your scripts run
  1455. * faster.
  1456. *
  1457. * This method allows you to register a custom object that
  1458. * implements your schema cache. Please see the SchemaCache interface
  1459. * (SchemaCache.interface.php) for informations on how to implement this.
  1460. * To unregister the cache, pass null as $cache parameter.
  1461. *
  1462. * For ease of use, Net_LDAP2 provides a simple file based cache
  1463. * which is used in the example below. You may use this, for example,
  1464. * to store the schema in a linux tmpfs which results in the schema
  1465. * beeing cached inside the RAM which allows nearly instant access.
  1466. * <code>
  1467. * // Create the simple file cache object that comes along with Net_LDAP2
  1468. * $mySchemaCache_cfg = array(
  1469. * 'path' => '/tmp/Net_LDAP2_Schema.cache',
  1470. * 'max_age' => 86400 // max age is 24 hours (in seconds)
  1471. * );
  1472. * $mySchemaCache = new Net_LDAP2_SimpleFileSchemaCache($mySchemaCache_cfg);
  1473. * $ldap = new Net_LDAP2::connect(...);
  1474. * $ldap->registerSchemaCache($mySchemaCache); // enable caching
  1475. * // now each call to $ldap->schema() will get the schema from disk!
  1476. * </code>
  1477. *
  1478. * @param Net_LDAP2_SchemaCache|null $cache Object implementing the Net_LDAP2_SchemaCache interface
  1479. *
  1480. * @return true|Net_LDAP2_Error
  1481. */
  1482. public function registerSchemaCache($cache) {
  1483. if (is_null($cache)
  1484. || (is_object($cache) && in_array('Net_LDAP2_SchemaCache', class_implements($cache))) ) {
  1485. $this->_schema_cache = $cache;
  1486. return true;
  1487. } else {
  1488. return new Net_LDAP2_Error('Custom schema caching object is either no '.
  1489. 'valid object or does not implement the Net_LDAP2_SchemaCache interface!');
  1490. }
  1491. }
  1492. /**
  1493. * Checks if phps ldap-extension is loaded
  1494. *
  1495. * If it is not loaded, it tries to load it manually using PHPs dl().
  1496. * It knows both windows-dll and *nix-so.
  1497. *
  1498. * @static
  1499. * @return Net_LDAP2_Error|true
  1500. */
  1501. public static function checkLDAPExtension()
  1502. {
  1503. if (!extension_loaded('ldap') && !@dl('ldap.' . PHP_SHLIB_SUFFIX)) {
  1504. return new Net_LDAP2_Error("It seems that you do not have the ldap-extension installed. Please install it before using the Net_LDAP2 package.");
  1505. } else {
  1506. return true;
  1507. }
  1508. }
  1509. /**
  1510. * Encodes given attributes from ISO-8859-1 to UTF-8 if needed by schema
  1511. *
  1512. * This function takes attributes in an array and then checks against the schema if they need
  1513. * UTF8 encoding. If that is so, they will be encoded. An encoded array will be returned and
  1514. * can be used for adding or modifying.
  1515. *
  1516. * $attributes is expected to be an array with keys describing
  1517. * the attribute names and the values as the value of this attribute:
  1518. * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
  1519. *
  1520. * @param array $attributes Array of attributes
  1521. *
  1522. * @access public
  1523. * @return array|Net_LDAP2_Error Array of UTF8 encoded attributes or Error
  1524. */
  1525. public function utf8Encode($attributes)
  1526. {
  1527. return $this->utf8($attributes, 'utf8_encode');
  1528. }
  1529. /**
  1530. * Decodes the given attribute values from UTF-8 to ISO-8859-1 if needed by schema
  1531. *
  1532. * $attributes is expected to be an array with keys describing
  1533. * the attribute names and the values as the value of this attribute:
  1534. * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
  1535. *
  1536. * @param array $attributes Array of attributes
  1537. *
  1538. * @access public
  1539. * @see utf8Encode()
  1540. * @return array|Net_LDAP2_Error Array with decoded attribute values or Error
  1541. */
  1542. public function utf8Decode($attributes)
  1543. {
  1544. return $this->utf8($attributes, 'utf8_decode');
  1545. }
  1546. /**
  1547. * Encodes or decodes UTF-8/ISO-8859-1 attribute values if needed by schema
  1548. *
  1549. * @param array $attributes Array of attributes
  1550. * @param array $function Function to apply to attribute values
  1551. *
  1552. * @access protected
  1553. * @return array|Net_LDAP2_Error Array of attributes with function applied to values or Error
  1554. */
  1555. protected function utf8($attributes, $function)
  1556. {
  1557. if (!is_array($attributes) || array_key_exists(0, $attributes)) {
  1558. return PEAR::raiseError('Parameter $attributes is expected to be an associative array');
  1559. }
  1560. if (!$this->_schema) {
  1561. $this->_schema = $this->schema();
  1562. }
  1563. if (!$this->_link || self::isError($this->_schema) || !function_exists($function)) {
  1564. return $attributes;
  1565. }
  1566. if (is_array($attributes) && count($attributes) > 0) {
  1567. foreach ($attributes as $k => $v) {
  1568. if (!isset($this->_schemaAttrs[$k])) {
  1569. $attr = $this->_schema->get('attribute', $k);
  1570. if (self::isError($attr)) {
  1571. continue;
  1572. }
  1573. // Encoding is needed if this is a DIR_STR. We assume also
  1574. // needed encoding in case the schema contains no syntax
  1575. // information (he does not need to, see rfc2252, 4.2)
  1576. if (!array_key_exists('syntax', $attr) || false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
  1577. $encode = true;
  1578. } else {
  1579. $encode = false;
  1580. }
  1581. $this->_schemaAttrs[$k] = $encode;
  1582. } else {
  1583. $encode = $this->_schemaAttrs[$k];
  1584. }
  1585. if ($encode) {
  1586. if (is_array($v)) {
  1587. foreach ($v as $ak => $av) {
  1588. $v[$ak] = call_user_func($function, $av);
  1589. }
  1590. } else {
  1591. $v = call_user_func($function, $v);
  1592. }
  1593. }
  1594. $attributes[$k] = $v;
  1595. }
  1596. }
  1597. return $attributes;
  1598. }
  1599. /**
  1600. * Get the LDAP link resource. It will loop attempting to
  1601. * re-establish the connection if the connection attempt fails and
  1602. * auto_reconnect has been turned on (see the _config array documentation).
  1603. *
  1604. * @access public
  1605. * @return resource LDAP link
  1606. */
  1607. public function getLink()
  1608. {
  1609. if ($this->_config['auto_reconnect']) {
  1610. while (true) {
  1611. //
  1612. // Return the link handle if we are already connected. Otherwise
  1613. // try to reconnect.
  1614. //
  1615. if ($this->_link !== false) {
  1616. return $this->_link;
  1617. } else {
  1618. $this->performReconnect();
  1619. }
  1620. }
  1621. }
  1622. return $this->_link;
  1623. }
  1624. }
  1625. /**
  1626. * Net_LDAP2_Error implements a class for reporting portable LDAP error messages.
  1627. *
  1628. * @category Net
  1629. * @package Net_LDAP2
  1630. * @author Tarjej Huse <tarjei@bergfald.no>
  1631. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  1632. * @link http://pear.php.net/package/Net_LDAP22/
  1633. */
  1634. class Net_LDAP2_Error extends PEAR_Error
  1635. {
  1636. /**
  1637. * Net_LDAP2_Error constructor.
  1638. *
  1639. * @param string $message String with error message.
  1640. * @param integer $code Net_LDAP2 error code
  1641. * @param integer $mode what "error mode" to operate in
  1642. * @param mixed $level what error level to use for $mode & PEAR_ERROR_TRIGGER
  1643. * @param mixed $debuginfo additional debug info, such as the last query
  1644. *
  1645. * @access public
  1646. * @see PEAR_Error
  1647. */
  1648. public function __construct($message = 'Net_LDAP2_Error', $code = NET_LDAP2_ERROR, $mode = PEAR_ERROR_RETURN,
  1649. $level = E_USER_NOTICE, $debuginfo = null)
  1650. {
  1651. if (is_int($code)) {
  1652. parent::__construct($message . ': ' . Net_LDAP2::errorMessage($code), $code, $mode, $level, $debuginfo);
  1653. } else {
  1654. parent::__construct("$message: $code", NET_LDAP2_ERROR, $mode, $level, $debuginfo);
  1655. }
  1656. }
  1657. }
  1658. ?>