ldap.class.php 45 KB

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