ldap.class.php 45 KB

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