PageRenderTime 54ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/htdocs/core/class/ldap.class.php

https://github.com/asterix14/dolibarr
PHP | 1349 lines | 856 code | 164 blank | 329 comment | 134 complexity | 6e44d39780aedd89f670aa4dc7389607 MD5 | raw file
Possible License(s): LGPL-2.0
  1. <?php
  2. /* Copyright (C) 2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
  3. * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
  4. * Copyright (C) 2005-2011 Regis Houssin <regis@dolibarr.fr>
  5. * Copyright (C) 2006-2011 Laurent Destailleur <eldy@users.sourceforge.net>
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 2 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. * or see http://www.gnu.org/
  20. */
  21. /**
  22. * \file htdocs/core/class/ldap.class.php
  23. * \brief File of class to manage LDAP features
  24. */
  25. /**
  26. * \class Ldap
  27. * \brief Class to manage LDAP features
  28. */
  29. class Ldap
  30. {
  31. /**
  32. * Tableau des serveurs (IP addresses ou nom d'hotes)
  33. */
  34. var $server=array();
  35. /**
  36. * Base DN (e.g. "dc=foo,dc=com")
  37. */
  38. var $dn;
  39. /**
  40. * type de serveur, actuellement OpenLdap et Active Directory
  41. */
  42. var $serverType;
  43. /**
  44. * Version du protocole ldap
  45. */
  46. var $domain;
  47. /**
  48. * User administrateur Ldap
  49. * Active Directory ne supporte pas les connexions anonymes
  50. */
  51. var $searchUser;
  52. /**
  53. * Mot de passe de l'administrateur
  54. * Active Directory ne supporte pas les connexions anonymes
  55. */
  56. var $searchPassword;
  57. /**
  58. * DN des utilisateurs
  59. */
  60. var $people;
  61. /**
  62. * DN des groupes
  63. */
  64. var $groups;
  65. /**
  66. * Code erreur retourne par le serveur Ldap
  67. */
  68. var $ldapErrorCode;
  69. /**
  70. * Message texte de l'erreur
  71. */
  72. var $ldapErrorText;
  73. //Fetch user
  74. var $name;
  75. var $firstname;
  76. var $login;
  77. var $phone;
  78. var $fax;
  79. var $mail;
  80. var $mobile;
  81. var $uacf;
  82. var $pwdlastset;
  83. var $ldapcharset='UTF-8'; // LDAP should be UTF-8 encoded
  84. // 1.2 Private properties ----------------------------------------------------
  85. /**
  86. * The internal LDAP connection handle
  87. */
  88. var $connection;
  89. /**
  90. * Result of any connections etc.
  91. */
  92. var $result;
  93. /**
  94. * Constructor
  95. */
  96. function __construct()
  97. {
  98. global $conf;
  99. //Server
  100. if ($conf->global->LDAP_SERVER_HOST) $this->server[] = $conf->global->LDAP_SERVER_HOST;
  101. if ($conf->global->LDAP_SERVER_HOST_SLAVE) $this->server[] = $conf->global->LDAP_SERVER_HOST_SLAVE;
  102. $this->serverPort = $conf->global->LDAP_SERVER_PORT;
  103. $this->ldapProtocolVersion = $conf->global->LDAP_SERVER_PROTOCOLVERSION;
  104. $this->dn = $conf->global->LDAP_SERVER_DN;
  105. $this->serverType = $conf->global->LDAP_SERVER_TYPE;
  106. $this->domain = $conf->global->LDAP_SERVER_DN;
  107. $this->searchUser = $conf->global->LDAP_ADMIN_DN;
  108. $this->searchPassword = $conf->global->LDAP_ADMIN_PASS;
  109. $this->people = $conf->global->LDAP_USER_DN;
  110. $this->groups = $conf->global->LDAP_GROUP_DN;
  111. $this->filter = $conf->global->LDAP_FILTER_CONNECTION;
  112. //Users
  113. $this->attr_login = $conf->global->LDAP_FIELD_LOGIN; //unix
  114. $this->attr_sambalogin = $conf->global->LDAP_FIELD_LOGIN_SAMBA; //samba, activedirectory
  115. $this->attr_name = $conf->global->LDAP_FIELD_NAME;
  116. $this->attr_firstname = $conf->global->LDAP_FIELD_FIRSTNAME;
  117. $this->attr_mail = $conf->global->LDAP_FIELD_MAIL;
  118. $this->attr_phone = $conf->global->LDAP_FIELD_PHONE;
  119. $this->attr_fax = $conf->global->LDAP_FIELD_FAX;
  120. $this->attr_mobile = $conf->global->LDAP_FIELD_MOBILE;
  121. }
  122. // 2.1 Connection handling methods -------------------------------------------
  123. /**
  124. * 2.1.1 : Connects to the server. Just creates a connection which is used
  125. * in all later access to the LDAP server. If it can't connect and bind
  126. * anonymously, it creates an error code of -1. Returns true if connected,
  127. * false if failed. Takes an array of possible servers - if one doesn't work,
  128. * it tries the next and so on.
  129. * \deprecated Utiliser connect_bind a la place
  130. */
  131. function connect()
  132. {
  133. foreach ($this->server as $key => $host)
  134. {
  135. if (preg_match('/^ldap/',$host))
  136. {
  137. $this->connection = ldap_connect($host);
  138. }
  139. else
  140. {
  141. $this->connection = ldap_connect($host,$this->serverPort);
  142. }
  143. if ($this->connection)
  144. {
  145. $this->setVersion();
  146. if ($this->serverType == "activedirectory")
  147. {
  148. $this->setReferrals();
  149. return true;
  150. }
  151. else
  152. {
  153. // Connected, now try binding anonymously
  154. $this->result=@ldap_bind($this->connection);
  155. }
  156. return true;
  157. }
  158. }
  159. $this->ldapErrorCode = -1;
  160. $this->ldapErrorText = "Unable to connect to any server";
  161. return false;
  162. }
  163. /**
  164. * \brief Connect and bind
  165. * \return <0 si KO, 1 si bind anonymous, 2 si bind auth
  166. * \remarks Use this->server, this->serverPort, this->ldapProtocolVersion, this->serverType, this->searchUser, this->searchPassword
  167. * After return, this->connection and $this->bind are defined
  168. */
  169. function connect_bind()
  170. {
  171. global $langs;
  172. $connected=0;
  173. $this->bind=0;
  174. foreach ($this->server as $key => $host)
  175. {
  176. if ($connected) break;
  177. if (preg_match('/^ldap/',$host))
  178. {
  179. $this->connection = ldap_connect($host);
  180. }
  181. else
  182. {
  183. $this->connection = ldap_connect($host,$this->serverPort);
  184. }
  185. if ($this->connection)
  186. {
  187. $this->setVersion();
  188. if ($this->serverType == "activedirectory")
  189. {
  190. $result=$this->setReferrals();
  191. dol_syslog("Ldap::connect_bind try bindauth for activedirectory on ".$host." user=".$this->searchUser,LOG_DEBUG);
  192. $this->result=$this->bindauth($this->searchUser,$this->searchPassword);
  193. if ($this->result)
  194. {
  195. $this->bind=$this->result;
  196. $connected=2;
  197. break;
  198. }
  199. else
  200. {
  201. $this->error=ldap_errno($this->connection).' '.ldap_error($this->connection);
  202. }
  203. }
  204. else
  205. {
  206. // Try in auth mode
  207. if ($this->searchUser && $this->searchPassword)
  208. {
  209. dol_syslog("Ldap::connect_bind try bindauth on ".$host." user=".$this->searchUser,LOG_DEBUG);
  210. $this->result=$this->bindauth($this->searchUser,$this->searchPassword);
  211. if ($this->result)
  212. {
  213. $this->bind=$this->result;
  214. $connected=2;
  215. break;
  216. }
  217. else
  218. {
  219. $this->error=ldap_errno($this->connection).' '.ldap_error($this->connection);
  220. }
  221. }
  222. // Try in anonymous
  223. if (! $this->bind)
  224. {
  225. dol_syslog("Ldap::connect_bind try bind on ".$host,LOG_DEBUG);
  226. $result=$this->bind();
  227. if ($result)
  228. {
  229. $this->bind=$this->result;
  230. $connected=1;
  231. break;
  232. }
  233. else
  234. {
  235. $this->error=ldap_errno($this->connection).' '.ldap_error($this->connection);
  236. }
  237. }
  238. }
  239. }
  240. if (! $connected) $this->close();
  241. }
  242. if ($connected)
  243. {
  244. $return=$connected;
  245. dol_syslog("Ldap::connect_bind return=".$return, LOG_DEBUG);
  246. }
  247. else
  248. {
  249. $this->error='Failed to connect to LDAP';
  250. $return=-1;
  251. dol_syslog("Ldap::connect_bind return=".$return, LOG_WARNING);
  252. }
  253. return $return;
  254. }
  255. /**
  256. * 2.1.2 : Simply closes the connection set up earlier.
  257. * Returns true if OK, false if there was an error.
  258. */
  259. function close()
  260. {
  261. if ($this->connection && ! @ldap_close($this->connection))
  262. {
  263. return false;
  264. }
  265. else
  266. {
  267. return true;
  268. }
  269. }
  270. /**
  271. * 2.1.3 : Anonymously binds to the connection. After this is done,
  272. * queries and searches can be done - but read-only.
  273. */
  274. function bind()
  275. {
  276. if (! $this->result=@ldap_bind($this->connection))
  277. {
  278. $this->ldapErrorCode = ldap_errno($this->connection);
  279. $this->ldapErrorText = ldap_error($this->connection);
  280. $this->error=$this->ldapErrorCode." ".$this->ldapErrorText;
  281. return false;
  282. }
  283. else
  284. {
  285. return true;
  286. }
  287. }
  288. /**
  289. * 2.1.4 : Binds as an authenticated user, which usually allows for write
  290. * access. The FULL dn must be passed. For a directory manager, this is
  291. * "cn=Directory Manager" under iPlanet. For a user, it will be something
  292. * like "uid=jbloggs,ou=People,dc=foo,dc=com".
  293. */
  294. function bindauth($bindDn,$pass)
  295. {
  296. if (! $this->result = @ldap_bind( $this->connection,$bindDn,$pass))
  297. {
  298. $this->ldapErrorCode = ldap_errno($this->connection);
  299. $this->ldapErrorText = ldap_error($this->connection);
  300. $this->error=$this->ldapErrorCode." ".$this->ldapErrorText;
  301. return false;
  302. }
  303. else
  304. {
  305. return true;
  306. }
  307. }
  308. /**
  309. * \brief Unbind du serveur ldap.
  310. * \param ds
  311. * \return bool
  312. */
  313. function unbind()
  314. {
  315. if (!$this->result=@ldap_unbind($this->connection))
  316. {
  317. return false;
  318. } else {
  319. return true;
  320. }
  321. }
  322. /**
  323. * \brief verification de la version du serveur ldap.
  324. * \param ds
  325. * \return version
  326. */
  327. function getVersion()
  328. {
  329. $version = 0;
  330. $version = @ldap_get_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $version);
  331. return $version;
  332. }
  333. /**
  334. * \brief changement de la version du serveur ldap.
  335. * \return version
  336. */
  337. function setVersion() {
  338. // LDAP_OPT_PROTOCOL_VERSION est une constante qui vaut 17
  339. $ldapsetversion = ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $this->ldapProtocolVersion);
  340. return $ldapsetversion;
  341. }
  342. /**
  343. * \brief changement du referrals.
  344. * \return referrals
  345. */
  346. function setReferrals() {
  347. // LDAP_OPT_REFERRALS est une constante qui vaut ?
  348. $ldapreferrals = ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
  349. return $ldapreferrals;
  350. }
  351. /**
  352. * \brief Add a LDAP entry
  353. * \param dn DN entry key
  354. * \param info Attributes array
  355. * \param user Objet user that create
  356. * \return int <0 if KO, >0 if OK
  357. * \remarks Ldap object connect and bind must have been done
  358. */
  359. function add($dn, $info, $user)
  360. {
  361. global $conf;
  362. dol_syslog("Ldap::add dn=".$dn." info=".join(',',$info));
  363. // Check parameters
  364. if (! $this->connection)
  365. {
  366. $this->error="NotConnected";
  367. return -2;
  368. }
  369. if (! $this->bind)
  370. {
  371. $this->error="NotConnected";
  372. return -3;
  373. }
  374. // Encode to LDAP page code
  375. $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
  376. foreach($info as $key => $val)
  377. {
  378. if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
  379. }
  380. $this->dump($dn,$info);
  381. //print_r($info);
  382. $result=@ldap_add($this->connection, $dn, $info);
  383. if ($result)
  384. {
  385. dol_syslog("Ldap::add successfull", LOG_DEBUG);
  386. return 1;
  387. }
  388. else
  389. {
  390. $this->error=@ldap_error($this->connection);
  391. dol_syslog("Ldap::add failed: ".$this->error, LOG_ERR);
  392. return -1;
  393. }
  394. }
  395. /**
  396. * \brief Modify a LDAP entry
  397. * \param dn DN entry key
  398. * \param info Attributes array
  399. * \param user Objet user that modify
  400. * \return int <0 if KO, >0 if OK
  401. * \remarks Ldap object connect and bind must have been done
  402. */
  403. function modify($dn, $info, $user)
  404. {
  405. global $conf;
  406. dol_syslog("Ldap::modify dn=".$dn." info=".join(',',$info));
  407. // Check parameters
  408. if (! $this->connection)
  409. {
  410. $this->error="NotConnected";
  411. return -2;
  412. }
  413. if (! $this->bind)
  414. {
  415. $this->error="NotConnected";
  416. return -3;
  417. }
  418. // Encode to LDAP page code
  419. $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
  420. foreach($info as $key => $val)
  421. {
  422. if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
  423. }
  424. $this->dump($dn,$info);
  425. //print_r($info);
  426. $result=@ldap_modify($this->connection, $dn, $info);
  427. if ($result)
  428. {
  429. dol_syslog("Ldap::modify successfull", LOG_DEBUG);
  430. return 1;
  431. }
  432. else
  433. {
  434. $this->error=@ldap_error($this->connection);
  435. dol_syslog("Ldap::modify failed: ".$this->error, LOG_ERR);
  436. return -1;
  437. }
  438. }
  439. /**
  440. * \brief Modify a LDAP entry (to use if dn != olddn)
  441. * \param dn DN entry key
  442. * \param info Attributes array
  443. * \param user Objet user that delete
  444. * \param olddn Old DN entry key (before update)
  445. * \return int <0 if KO, >0 if OK
  446. * \remarks Ldap object connect and bind must have been done
  447. */
  448. function update($dn,$info,$user,$olddn)
  449. {
  450. global $conf;
  451. dol_syslog("Ldap::update dn=".$dn." olddn=".$olddn);
  452. // Check parameters
  453. if (! $this->connection)
  454. {
  455. $this->error="NotConnected";
  456. return -2;
  457. }
  458. if (! $this->bind)
  459. {
  460. $this->error="NotConnected";
  461. return -3;
  462. }
  463. if (! $olddn || $olddn != $dn)
  464. {
  465. // If change we make is rename the key of LDAP record, we create new one and if ok, we delete old one.
  466. $result = $this->add($dn, $info, $user);
  467. if ($result > 0 && $olddn && $olddn != $dn) $result = $this->delete($olddn); // If add fails, we do not try to delete old one
  468. }
  469. else
  470. {
  471. //$result = $this->delete($olddn);
  472. $result = $this->add($dn, $info, $user); // If record has been deleted from LDAP, we recreate it. We ignore error if it already exists.
  473. $result = $this->modify($dn, $info, $user); // We use add/modify instead of delete/add when olddn is received
  474. }
  475. if ($result <= 0)
  476. {
  477. $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection)." ".$this->error;
  478. dol_syslog("Ldap::update ".$this->error,LOG_ERR);
  479. //print_r($info);
  480. return -1;
  481. }
  482. else
  483. {
  484. dol_syslog("Ldap::update done successfully");
  485. return 1;
  486. }
  487. }
  488. /**
  489. * \brief Delete a LDAP entry
  490. * \param dn DN entry key
  491. * \return int <0 si KO, >0 si OK
  492. * \remarks Ldap object connect and bind must have been done
  493. */
  494. function delete($dn)
  495. {
  496. global $conf;
  497. dol_syslog("Ldap::delete Delete LDAP entry dn=".$dn);
  498. // Check parameters
  499. if (! $this->connection)
  500. {
  501. $this->error="NotConnected";
  502. return -2;
  503. }
  504. if (! $this->bind)
  505. {
  506. $this->error="NotConnected";
  507. return -3;
  508. }
  509. // Encode to LDAP page code
  510. $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
  511. $result=@ldap_delete($this->connection, $dn);
  512. if ($result) return 1;
  513. return -1;
  514. }
  515. /**
  516. * \brief Build a LDAP message
  517. * \param dn DN entry key
  518. * \param info Attributes array
  519. * \return string Content of file
  520. */
  521. function dump_content($dn, $info)
  522. {
  523. $content='';
  524. // Create file content
  525. if (preg_match('/^ldap/',$this->server[0]))
  526. {
  527. $target="-H ".join(',',$this->server);
  528. }
  529. else
  530. {
  531. $target="-h ".join(',',$this->server)." -p ".$this->serverPort;
  532. }
  533. $content.="# ldapadd $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
  534. $content.="# ldapmodify $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
  535. $content.="# ldapdelete $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
  536. if (in_array('localhost',$this->server)) $content.="# If commands fails to connect, try without -h and -p\n";
  537. $content.="dn: ".$dn."\n";
  538. foreach($info as $key => $value)
  539. {
  540. if (! is_array($value))
  541. {
  542. $content.="$key: $value\n";
  543. }
  544. else
  545. {
  546. foreach($value as $valuekey => $valuevalue)
  547. {
  548. $content.="$key: $valuevalue\n";
  549. }
  550. }
  551. }
  552. return $content;
  553. }
  554. /**
  555. * \brief Dump a LDAP message to ldapinput.in file
  556. * \param dn DN entry key
  557. * \param info Attributes array
  558. * \return int <0 if KO, >0 if OK
  559. */
  560. function dump($dn, $info)
  561. {
  562. global $conf;
  563. // Create content
  564. $content=$this->dump_content($dn, $info);
  565. //Create file
  566. $result=create_exdir($conf->ldap->dir_temp);
  567. $file=$conf->ldap->dir_temp.'/ldapinput.in';
  568. $fp=fopen($file,"w");
  569. if ($fp)
  570. {
  571. fputs($fp, $content);
  572. fclose($fp);
  573. if (! empty($conf->global->MAIN_UMASK))
  574. @chmod($outputfile, octdec($conf->global->MAIN_UMASK));
  575. return 1;
  576. }
  577. else
  578. {
  579. return -1;
  580. }
  581. }
  582. // 2.4 Attribute methods -----------------------------------------------------
  583. /**
  584. * \brief Add a LDAP attribute in entry
  585. * \param dn DN entry key
  586. * \param info Attributes array
  587. * \param user Objet user that create
  588. * \return int <0 if KO, >0 if OK
  589. * \remarks Ldap object connect and bind must have been done
  590. */
  591. function addAttribute($dn, $info, $user)
  592. {
  593. global $conf;
  594. dol_syslog("Ldap::addAttribute dn=".$dn." info=".join(',',$info));
  595. // Check parameters
  596. if (! $this->connection)
  597. {
  598. $this->error="NotConnected";
  599. return -2;
  600. }
  601. if (! $this->bind)
  602. {
  603. $this->error="NotConnected";
  604. return -3;
  605. }
  606. // Encode to LDAP page code
  607. $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
  608. foreach($info as $key => $val)
  609. {
  610. if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
  611. }
  612. $this->dump($dn,$info);
  613. //print_r($info);
  614. $result=@ldap_mod_add($this->connection, $dn, $info);
  615. if ($result)
  616. {
  617. dol_syslog("Ldap::add_attribute successfull", LOG_DEBUG);
  618. return 1;
  619. }
  620. else
  621. {
  622. $this->error=@ldap_error($this->connection);
  623. dol_syslog("Ldap::add_attribute failed: ".$this->error, LOG_ERR);
  624. return -1;
  625. }
  626. }
  627. /**
  628. * \brief Update a LDAP attribute in entry
  629. * \param dn DN entry key
  630. * \param info Attributes array
  631. * \param user Objet user that create
  632. * \return int <0 if KO, >0 if OK
  633. * \remarks Ldap object connect and bind must have been done
  634. */
  635. function updateAttribute($dn, $info, $user)
  636. {
  637. global $conf;
  638. dol_syslog("Ldap::updateAttribute dn=".$dn." info=".join(',',$info));
  639. // Check parameters
  640. if (! $this->connection)
  641. {
  642. $this->error="NotConnected";
  643. return -2;
  644. }
  645. if (! $this->bind)
  646. {
  647. $this->error="NotConnected";
  648. return -3;
  649. }
  650. // Encode to LDAP page code
  651. $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
  652. foreach($info as $key => $val)
  653. {
  654. if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
  655. }
  656. $this->dump($dn,$info);
  657. //print_r($info);
  658. $result=@ldap_mod_replace($this->connection, $dn, $info);
  659. if ($result)
  660. {
  661. dol_syslog("Ldap::updateAttribute successfull", LOG_DEBUG);
  662. return 1;
  663. }
  664. else
  665. {
  666. $this->error=@ldap_error($this->connection);
  667. dol_syslog("Ldap::updateAttribute failed: ".$this->error, LOG_ERR);
  668. return -1;
  669. }
  670. }
  671. /**
  672. * \brief Delete a LDAP attribute in entry
  673. * \param dn DN entry key
  674. * \param info Attributes array
  675. * \param user Objet user that create
  676. * \return int <0 if KO, >0 if OK
  677. * \remarks Ldap object connect and bind must have been done
  678. */
  679. function deleteAttribute($dn, $info, $user)
  680. {
  681. global $conf;
  682. dol_syslog("Ldap::deleteAttribute dn=".$dn." info=".join(',',$info));
  683. // Check parameters
  684. if (! $this->connection)
  685. {
  686. $this->error="NotConnected";
  687. return -2;
  688. }
  689. if (! $this->bind)
  690. {
  691. $this->error="NotConnected";
  692. return -3;
  693. }
  694. // Encode to LDAP page code
  695. $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
  696. foreach($info as $key => $val)
  697. {
  698. if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
  699. }
  700. $this->dump($dn,$info);
  701. //print_r($info);
  702. $result=@ldap_mod_del($this->connection, $dn, $info);
  703. if ($result)
  704. {
  705. dol_syslog("Ldap::deleteAttribute successfull", LOG_DEBUG);
  706. return 1;
  707. }
  708. else
  709. {
  710. $this->error=@ldap_error($this->connection);
  711. dol_syslog("Ldap::deleteAttribute failed: ".$this->error, LOG_ERR);
  712. return -1;
  713. }
  714. }
  715. /**
  716. * Returns an array containing attributes and values for first record
  717. */
  718. function getAttribute($dn,$filter)
  719. {
  720. // Check parameters
  721. if (! $this->connection)
  722. {
  723. $this->error="NotConnected";
  724. return -2;
  725. }
  726. if (! $this->bind)
  727. {
  728. $this->error="NotConnected";
  729. return -3;
  730. }
  731. $search = ldap_search($this->connection,$dn,$filter);
  732. // Only one entry should ever be returned
  733. $entry = ldap_first_entry($this->connection, $search);
  734. if (!$entry)
  735. {
  736. $this->ldapErrorCode = -1;
  737. $this->ldapErrorText = "Couldn't find entry";
  738. return false; // Couldn't find entry...
  739. }
  740. // Get values
  741. if (! $values = ldap_get_attributes( $this->connection, $entry))
  742. {
  743. $this->ldapErrorCode = ldap_errno($this->connection);
  744. $this->ldapErrorText = ldap_error($this->connection);
  745. return false; // No matching attributes
  746. }
  747. // Return an array containing the attributes.
  748. return $values;
  749. }
  750. /**
  751. * Returns an array containing values for an attribute and for first record matching filterrecord
  752. */
  753. function getAttributeValues($filterrecord,$attribute)
  754. {
  755. $attributes[0] = $attribute;
  756. // We need to search for this user in order to get their entry.
  757. $this->result = @ldap_search($this->connection,$this->people,$filterrecord,$attributes);
  758. // Pourquoi cette ligne ?
  759. //$info = ldap_get_entries($this->connection, $this->result);
  760. // Only one entry should ever be returned (no user will have the same uid)
  761. $entry = ldap_first_entry($this->connection, $this->result);
  762. if (!$entry)
  763. {
  764. $this->ldapErrorCode = -1;
  765. $this->ldapErrorText = "Couldn't find user";
  766. return false; // Couldn't find the user...
  767. }
  768. // Get values
  769. if (! $values = @ldap_get_values( $this->connection, $entry, $attribute))
  770. {
  771. $this->ldapErrorCode = ldap_errno($this->connection);
  772. $this->ldapErrorText = ldap_error($this->connection);
  773. return false; // No matching attributes
  774. }
  775. // Return an array containing the attributes.
  776. return $values;
  777. }
  778. /**
  779. * \brief Returns an array containing a details of elements
  780. * \param $search Valeur champ cle recherche, sinon '*' pour tous.
  781. * \param $userDn DN (Ex: ou=adherents,ou=people,dc=parinux,dc=org)
  782. * \param $useridentifier Nom du champ cle (Ex: uid)
  783. * \param $attributeArray Array of fields required (Ex: sn,userPassword)
  784. * \param $activefilter 1=utilise le champ this->filter comme filtre
  785. * \return array Array of [id_record][ldap_field]=value
  786. * \remarks ldapsearch -LLLx -hlocalhost -Dcn=admin,dc=parinux,dc=org -w password -b "ou=adherents,ou=people,dc=parinux,dc=org" userPassword
  787. */
  788. function getRecords($search, $userDn, $useridentifier, $attributeArray, $activefilter=0)
  789. {
  790. $fulllist=array();
  791. dol_syslog("Ldap::getRecords search=".$search." userDn=".$userDn." useridentifier=".$useridentifier." attributeArray=array(".join(',',$attributeArray).")");
  792. // if the directory is AD, then bind first with the search user first
  793. if ($this->serverType == "activedirectory")
  794. {
  795. $this->bindauth($this->searchUser, $this->searchPassword);
  796. dol_syslog("Ldap::bindauth serverType=activedirectory searchUser=".$this->searchUser);
  797. }
  798. // Define filter
  799. if ($activefilter == 1)
  800. {
  801. if ($this->filter)
  802. {
  803. $filter = '('.$this->filter.')';
  804. }
  805. else
  806. {
  807. $filter='('.$useridentifier.'=*)';
  808. }
  809. }
  810. else
  811. {
  812. $filter = '('.$useridentifier.'='.$search.')';
  813. }
  814. if (is_array($attributeArray))
  815. {
  816. // Return list with required fields
  817. dol_syslog("Ldap::getRecords connection=".$this->connection." userDn=".$userDn." filter=".$filter. " attributeArray=(".join(',',$attributeArray).")");
  818. $this->result = @ldap_search($this->connection, $userDn, $filter, $attributeArray);
  819. }
  820. else
  821. {
  822. // Return list with fields selected by default
  823. dol_syslog("Ldap::getRecords connection=".$this->connection." userDn=".$userDn." filter=".$filter);
  824. $this->result = @ldap_search($this->connection, $userDn, $filter);
  825. }
  826. if (!$this->result)
  827. {
  828. $this->error = 'LDAP search failed: '.ldap_errno($this->connection)." ".ldap_error($this->connection);
  829. return -1;
  830. }
  831. $info = @ldap_get_entries($this->connection, $this->result);
  832. // Warning: Dans info, les noms d'attributs sont en minuscule meme si passe
  833. // a ldap_search en majuscule !!!
  834. //print_r($info);
  835. for ($i = 0; $i < $info["count"]; $i++)
  836. {
  837. $recordid=$this->convToOutputCharset($info[$i][$useridentifier][0],$this->ldapcharset);
  838. if ($recordid)
  839. {
  840. //print "Found record with key $useridentifier=".$recordid."<br>\n";
  841. $fulllist[$recordid][$useridentifier]=$recordid;
  842. // Add to the array for each attribute in my list
  843. $num = count($attributeArray);
  844. for ($j = 0; $j < $num; $j++)
  845. {
  846. $keyattributelower=strtolower($attributeArray[$j]);
  847. //print " Param ".$attributeArray[$j]."=".$info[$i][$keyattributelower][0]."<br>\n";
  848. //permet de recuperer le SID avec Active Directory
  849. if ($this->serverType == "activedirectory" && $keyattributelower == "objectsid")
  850. {
  851. $objectsid = $this->getObjectSid($recordid);
  852. $fulllist[$recordid][$attributeArray[$j]] = $objectsid;
  853. }
  854. else
  855. {
  856. $fulllist[$recordid][$attributeArray[$j]] = $this->convToOutputCharset($info[$i][$keyattributelower][0],$this->ldapcharset);
  857. }
  858. }
  859. }
  860. }
  861. asort($fulllist);
  862. return $fulllist;
  863. }
  864. /**
  865. * Converts a little-endian hex-number to one, that 'hexdec' can convert
  866. * Indispensable pour Active Directory
  867. */
  868. function littleEndian($hex) {
  869. for ($x=dol_strlen($hex)-2; $x >= 0; $x=$x-2) {
  870. $result .= substr($hex,$x,2);
  871. }
  872. return $result;
  873. }
  874. /**
  875. * Recupere le SID de l'utilisateur
  876. * ldapuser. le login de l'utilisateur
  877. * Indispensable pour Active Directory
  878. */
  879. function getObjectSid($ldapUser)
  880. {
  881. $criteria = '('.$this->getUserIdentifier().'='.$ldapUser.')';
  882. $justthese = array("objectsid");
  883. // if the directory is AD, then bind first with the search user first
  884. if ($this->serverType == "activedirectory")
  885. {
  886. $this->bindauth($this->searchUser, $this->searchPassword);
  887. }
  888. $i = 0;
  889. $searchDN = $this->people;
  890. while ($i <= 2)
  891. {
  892. $ldapSearchResult = @ldap_search($this->connection, $searchDN, $criteria, $justthese);
  893. if (!$ldapSearchResult)
  894. {
  895. $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
  896. return -1;
  897. }
  898. $entry = ldap_first_entry($this->connection, $ldapSearchResult);
  899. if (!$entry)
  900. {
  901. // Si pas de r�sultat on cherche dans le domaine
  902. $searchDN = $this->domain;
  903. $i++;
  904. }
  905. else
  906. {
  907. $i++;
  908. $i++;
  909. }
  910. }
  911. if ($entry)
  912. {
  913. $ldapBinary = ldap_get_values_len ($this->connection, $entry, "objectsid");
  914. $SIDText = $this->binSIDtoText($ldapBinary[0]);
  915. return $SIDText;
  916. }
  917. else
  918. {
  919. $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
  920. return '?';
  921. }
  922. }
  923. /**
  924. * Returns the textual SID
  925. * Indispensable pour Active Directory
  926. */
  927. function binSIDtoText($binsid) {
  928. $hex_sid=bin2hex($binsid);
  929. $rev = hexdec(substr($hex_sid,0,2)); // Get revision-part of SID
  930. $subcount = hexdec(substr($hex_sid,2,2)); // Get count of sub-auth entries
  931. $auth = hexdec(substr($hex_sid,4,12)); // SECURITY_NT_AUTHORITY
  932. $result = "$rev-$auth";
  933. for ($x=0;$x < $subcount; $x++) {
  934. $subauth[$x] = hexdec($this->littleEndian(substr($hex_sid,16+($x*8),8))); // get all SECURITY_NT_AUTHORITY
  935. $result .= "-".$subauth[$x];
  936. }
  937. return $result;
  938. }
  939. /**
  940. * \brief Fonction de recherche avec filtre
  941. * \remarks this->connection doit etre defini donc la methode bind ou bindauth doit avoir deja ete appelee
  942. * \param checkDn DN de recherche (Ex: ou=users,cn=my-domain,cn=com)
  943. * \param filter Filtre de recherche (ex: (sn=nom_personne) )
  944. * \return array Tableau des reponses (cle en minuscule-valeur)
  945. * \remarks Ne pas utiliser pour recherche d'une liste donnee de proprietes
  946. * car conflit majuscule-minuscule. A n'utiliser que pour les pages
  947. * 'Fiche LDAP' qui affiche champ lisibles par defaut.
  948. */
  949. function search($checkDn, $filter)
  950. {
  951. dol_syslog("Ldap::search checkDn=".$checkDn." filter=".$filter);
  952. $checkDn=$this->convFromOutputCharset($checkDn,$this->ldapcharset);
  953. $filter=$this->convFromOutputCharset($filter,$this->ldapcharset);
  954. // if the directory is AD, then bind first with the search user first
  955. if ($this->serverType == "activedirectory") {
  956. $this->bindauth($this->searchUser, $this->searchPassword);
  957. }
  958. $this->result = @ldap_search($this->connection, $checkDn, $filter);
  959. $result = @ldap_get_entries($this->connection, $this->result);
  960. if (! $result)
  961. {
  962. $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
  963. return -1;
  964. }
  965. else
  966. {
  967. ldap_free_result($this->result);
  968. return $result;
  969. }
  970. }
  971. /**
  972. * Load all attribute of a LDAP user
  973. * @param $user User to search for. Not used if a filter is provided.
  974. * @param filter Filter for search. Must start with &.
  975. * Examples: &(objectClass=inetOrgPerson) &(objectClass=user)(objectCategory=person) &(isMemberOf=cn=Sales,ou=Groups,dc=opencsi,dc=com)
  976. * @return int >0 if ok, <0 if ko
  977. */
  978. function fetch($user,$filter)
  979. {
  980. // Perform the search and get the entry handles
  981. // if the directory is AD, then bind first with the search user first
  982. if ($this->serverType == "activedirectory") {
  983. $this->bindauth($this->searchUser, $this->searchPassword);
  984. }
  985. $searchDN = $this->people; // TODO Why searching in people then domain ?
  986. $result = '';
  987. $i=0;
  988. while ($i <= 2)
  989. {
  990. dol_syslog("Ldap::fetch search with searchDN=".$searchDN." filter=".$filter);
  991. $this->result = @ldap_search($this->connection, $searchDN, $filter);
  992. if ($this->result)
  993. {
  994. $result = @ldap_get_entries($this->connection, $this->result);
  995. if ($result['count'] > 0) dol_syslog('Ldap::fetch search found '.$result['count'].' records');
  996. else dol_syslog('Ldap::fetch search returns but found no records');
  997. //var_dump($result);exit;
  998. }
  999. else
  1000. {
  1001. $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
  1002. dol_syslog("Ldap::fetch search fails");
  1003. return -1;
  1004. }
  1005. if (! $result)
  1006. {
  1007. // Si pas de resultat on cherche dans le domaine
  1008. $searchDN = $this->domain;
  1009. $i++;
  1010. }
  1011. else
  1012. {
  1013. break;
  1014. }
  1015. }
  1016. if (! $result)
  1017. {
  1018. $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
  1019. return -1;
  1020. }
  1021. else
  1022. {
  1023. $this->name = $this->convToOutputCharset($result[0][$this->attr_name][0],$this->ldapcharset);
  1024. $this->firstname = $this->convToOutputCharset($result[0][$this->attr_firstname][0],$this->ldapcharset);
  1025. $this->login = $this->convToOutputCharset($result[0][$this->attr_login][0],$this->ldapcharset);
  1026. $this->phone = $this->convToOutputCharset($result[0][$this->attr_phone][0],$this->ldapcharset);
  1027. $this->fax = $this->convToOutputCharset($result[0][$this->attr_fax][0],$this->ldapcharset);
  1028. $this->mail = $this->convToOutputCharset($result[0][$this->attr_mail][0],$this->ldapcharset);
  1029. $this->mobile = $this->convToOutputCharset($result[0][$this->attr_mobile][0],$this->ldapcharset);
  1030. $this->uacf = $this->parseUACF($this->convToOutputCharset($result[0]["useraccountcontrol"][0],$this->ldapcharset));
  1031. if (isset($result[0]["pwdlastset"][0])) // If expiration on password exists
  1032. {
  1033. $this->pwdlastset = ($result[0]["pwdlastset"][0] != 0)?$this->convert_time($this->convToOutputCharset($result[0]["pwdlastset"][0],$this->ldapcharset)):0;
  1034. }
  1035. else
  1036. {
  1037. $this->pwdlastset = -1;
  1038. }
  1039. if (!$this->name && !$this->login) $this->pwdlastset = -1;
  1040. $this->badpwdtime = $this->convert_time($this->convToOutputCharset($result[0]["badpasswordtime"][0],$this->ldapcharset));
  1041. // FQDN domain
  1042. $domain = str_replace('dc=','',$this->domain);
  1043. $domain = str_replace(',','.',$domain);
  1044. $this->domainFQDN = $domain;
  1045. // Set ldapUserDn (each user can have a different dn)
  1046. //var_dump($result[0]);exit;
  1047. $this->ldapUserDN=$result[0]['dn'];
  1048. ldap_free_result($this->result);
  1049. return 1;
  1050. }
  1051. }
  1052. // 2.6 helper methods
  1053. /**
  1054. * Returns the correct user identifier to use, based on the ldap server type
  1055. */
  1056. function getUserIdentifier() {
  1057. if ($this->serverType == "activedirectory") {
  1058. return $this->attr_sambalogin;
  1059. } else {
  1060. return $this->attr_login;
  1061. }
  1062. }
  1063. /**
  1064. * \brief UserAccountControl Flgs to more human understandable form...
  1065. *
  1066. */
  1067. function parseUACF($uacf) {
  1068. //All flags array
  1069. $flags = array( "TRUSTED_TO_AUTH_FOR_DELEGATION" => 16777216,
  1070. "PASSWORD_EXPIRED" => 8388608,
  1071. "DONT_REQ_PREAUTH" => 4194304,
  1072. "USE_DES_KEY_ONLY" => 2097152,
  1073. "NOT_DELEGATED" => 1048576,
  1074. "TRUSTED_FOR_DELEGATION" => 524288,
  1075. "SMARTCARD_REQUIRED" => 262144,
  1076. "MNS_LOGON_ACCOUNT" => 131072,
  1077. "DONT_EXPIRE_PASSWORD" => 65536,
  1078. "SERVER_TRUST_ACCOUNT" => 8192,
  1079. "WORKSTATION_TRUST_ACCOUNT" => 4096,
  1080. "INTERDOMAIN_TRUST_ACCOUNT" => 2048,
  1081. "NORMAL_ACCOUNT" => 512,
  1082. "TEMP_DUPLICATE_ACCOUNT" => 256,
  1083. "ENCRYPTED_TEXT_PWD_ALLOWED" => 128,
  1084. "PASSWD_CANT_CHANGE" => 64,
  1085. "PASSWD_NOTREQD" => 32,
  1086. "LOCKOUT" => 16,
  1087. "HOMEDIR_REQUIRED" => 8,
  1088. "ACCOUNTDISABLE" => 2,
  1089. "SCRIPT" => 1);
  1090. //Parse flags to text
  1091. $retval = array();
  1092. while (list($flag, $val) = each($flags)) {
  1093. if ($uacf >= $val) {
  1094. $uacf -= $val;
  1095. $retval[$val] = $flag;
  1096. }
  1097. }
  1098. //Return human friendly flags
  1099. return($retval);
  1100. }
  1101. /**
  1102. * \brief SamAccountType value to text
  1103. *
  1104. */
  1105. function parseSAT($samtype) {
  1106. $stypes = array( 805306368 => "NORMAL_ACCOUNT",
  1107. 805306369 => "WORKSTATION_TRUST",
  1108. 805306370 => "INTERDOMAIN_TRUST",
  1109. 268435456 => "SECURITY_GLOBAL_GROUP",
  1110. 268435457 => "DISTRIBUTION_GROUP",
  1111. 536870912 => "SECURITY_LOCAL_GROUP",
  1112. 536870913 => "DISTRIBUTION_LOCAL_GROUP");
  1113. $retval = "";
  1114. while (list($sat, $val) = each($stypes)) {
  1115. if ($samtype == $sat) {
  1116. $retval = $val;
  1117. break;
  1118. }
  1119. }
  1120. if (empty($retval)) $retval = "UNKNOWN_TYPE_" . $samtype;
  1121. return($retval);
  1122. }
  1123. /**
  1124. * \brief Convertit le temps ActiveDirectory en Unix timestamp
  1125. * \param string AD time to convert
  1126. * \return string Unix timestamp
  1127. */
  1128. function convert_time($value)
  1129. {
  1130. $dateLargeInt=$value; // nano secondes depuis 1601 !!!!
  1131. $secsAfterADEpoch = $dateLargeInt / (10000000); // secondes depuis le 1 jan 1601
  1132. $ADToUnixConvertor=((1970-1601) * 365.242190) * 86400; // UNIX start date - AD start date * jours * secondes
  1133. $unixTimeStamp=intval($secsAfterADEpoch-$ADToUnixConvertor); // Unix time stamp
  1134. return $unixTimeStamp;
  1135. }
  1136. /**
  1137. * \brief Convert a string into output/memory charset
  1138. * \param str String to convert
  1139. * \param pagecodefrom Page code of src string
  1140. * \return string Converted string
  1141. */
  1142. function convToOutputCharset($str,$pagecodefrom='UTF-8')
  1143. {
  1144. global $conf;
  1145. if ($pagecodefrom == 'ISO-8859-1' && $conf->file->character_set_client == 'UTF-8') $str=utf8_encode($str);
  1146. if ($pagecodefrom == 'UTF-8' && $conf->file->character_set_client == 'ISO-8859-1') $str=utf8_decode($str);
  1147. return $str;
  1148. }
  1149. /**
  1150. * \brief Convert a string from output/memory charset
  1151. * \param str String to convert
  1152. * \param pagecodeto Page code for result string
  1153. * \return string Converted string
  1154. */
  1155. function convFromOutputCharset($str,$pagecodeto='UTF-8')
  1156. {
  1157. global $conf;
  1158. if ($pagecodeto == 'ISO-8859-1' && $conf->file->character_set_client == 'UTF-8') $str=utf8_decode($str);
  1159. if ($pagecodeto == 'UTF-8' && $conf->file->character_set_client == 'ISO-8859-1') $str=utf8_encode($str);
  1160. return $str;
  1161. }
  1162. /**
  1163. * Return available value of group GID
  1164. * @return int gid number
  1165. */
  1166. function getNextGroupGid()
  1167. {
  1168. global $conf;
  1169. $search='('.$conf->global->LDAP_KEY_GROUPS.'=*)';
  1170. $result = $this->search($this->groups,$search);
  1171. if($result)
  1172. {
  1173. $c = $result['count'];
  1174. $gids = array();
  1175. for($i=0;$i<$c;$i++)
  1176. {
  1177. $gids[] = $result[$i]['gidnumber'][0];
  1178. }
  1179. rsort($gids);
  1180. return $gids[0]+1;
  1181. }
  1182. return 0;
  1183. }
  1184. }
  1185. ?>