dolibarr 21.0.0-alpha
ldap.class.php
Go to the documentation of this file.
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 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 * or see https://www.gnu.org/
21 */
22
36class Ldap
37{
41 public $error = '';
42
46 public $errors = array();
47
51 public $server = array();
52
56 public $connectedServer;
57
61 public $serverPort;
62
67 public $dn;
72 public $serverType;
77 public $ldapProtocolVersion;
82 public $domain;
83
87 public $domainFQDN;
88
92 public $bind;
93
98 public $searchUser;
103 public $searchPassword;
104
108 public $people;
109
113 public $groups;
114
118 public $ldapErrorCode;
119
123 public $ldapErrorText;
124
128 public $filter;
129
133 public $filtergroup;
134
138 public $filtermember;
139
143 public $attr_login;
144
148 public $attr_sambalogin;
149
153 public $attr_name;
154
158 public $attr_firstname;
159
163 public $attr_mail;
164
168 public $attr_phone;
169
173 public $attr_fax;
174
178 public $attr_mobile;
179
183 public $badpwdtime;
184
188 public $ldapUserDN;
189
193 public $name;
194
198 public $firstname;
199
203 public $login;
204
208 public $phone;
209
213 public $fax;
214
218 public $mail;
219
223 public $mobile;
224
228 public $uacf;
229
233 public $pwdlastset;
234
239 public $ldapcharset = 'UTF-8';
240
244 public $connection;
245
249 public $result;
250
254 const SYNCHRO_NONE = 0;
255
259 const SYNCHRO_DOLIBARR_TO_LDAP = 1;
260
264 const SYNCHRO_LDAP_TO_DOLIBARR = 2;
265
269 public function __construct()
270 {
271
272 // Server
273 if (getDolGlobalString('LDAP_SERVER_HOST')) {
274 $this->server[] = getDolGlobalString('LDAP_SERVER_HOST');
275 }
276 if (getDolGlobalString('LDAP_SERVER_HOST_SLAVE')) {
277 $this->server[] = getDolGlobalString('LDAP_SERVER_HOST_SLAVE');
278 }
279 $this->serverPort = getDolGlobalInt('LDAP_SERVER_PORT', 389);
280 $this->ldapProtocolVersion = getDolGlobalString('LDAP_SERVER_PROTOCOLVERSION');
281 $this->dn = getDolGlobalString('LDAP_SERVER_DN');
282 $this->serverType = getDolGlobalString('LDAP_SERVER_TYPE');
283
284 $this->domain = getDolGlobalString('LDAP_SERVER_DN');
285 $this->searchUser = getDolGlobalString('LDAP_ADMIN_DN');
286 $this->searchPassword = getDolGlobalString('LDAP_ADMIN_PASS');
287 $this->people = getDolGlobalString('LDAP_USER_DN');
288 $this->groups = getDolGlobalString('LDAP_GROUP_DN');
289
290 $this->filter = getDolGlobalString('LDAP_FILTER_CONNECTION'); // Filter on user
291 $this->filtergroup = getDolGlobalString('LDAP_GROUP_FILTER'); // Filter on groups
292 $this->filtermember = getDolGlobalString('LDAP_MEMBER_FILTER'); // Filter on member
293
294 // Users
295 $this->attr_login = getDolGlobalString('LDAP_FIELD_LOGIN'); //unix
296 $this->attr_sambalogin = getDolGlobalString('LDAP_FIELD_LOGIN_SAMBA'); //samba, activedirectory
297 $this->attr_name = getDolGlobalString('LDAP_FIELD_NAME');
298 $this->attr_firstname = getDolGlobalString('LDAP_FIELD_FIRSTNAME');
299 $this->attr_mail = getDolGlobalString('LDAP_FIELD_MAIL');
300 $this->attr_phone = getDolGlobalString('LDAP_FIELD_PHONE');
301 $this->attr_fax = getDolGlobalString('LDAP_FIELD_FAX');
302 $this->attr_mobile = getDolGlobalString('LDAP_FIELD_MOBILE');
303 }
304
305 // Connection handling methods -------------------------------------------
306
315 public function connectBind()
316 {
317 global $dolibarr_main_auth_ldap_debug;
318
319 $connected = 0;
320 $this->bind = false;
321 $this->error = '';
322 $this->connectedServer = '';
323
324 $ldapdebug = ((empty($dolibarr_main_auth_ldap_debug) || $dolibarr_main_auth_ldap_debug == "false") ? false : true);
325
326 if ($ldapdebug) {
327 dol_syslog(get_class($this)."::connectBind");
328 print "DEBUG: connectBind<br>\n";
329 }
330
331 // Check parameters
332 if (count($this->server) == 0 || empty($this->server[0])) {
333 $this->error = 'LDAP setup (file conf.php) is not complete';
334 dol_syslog(get_class($this)."::connectBind ".$this->error, LOG_WARNING);
335 return -1;
336 }
337
338 if (!function_exists("ldap_connect")) {
339 $this->error = 'LDAPFunctionsNotAvailableOnPHP';
340 dol_syslog(get_class($this)."::connectBind ".$this->error, LOG_WARNING);
341 return -1;
342 }
343
344 if (empty($this->error)) {
345 // Loop on each ldap server
346 foreach ($this->server as $host) {
347 if ($connected) {
348 break;
349 }
350 if (empty($host)) {
351 continue;
352 }
353
354 if ($this->serverPing($host, $this->serverPort)) {
355 if ($ldapdebug) {
356 dol_syslog(get_class($this)."::connectBind serverPing true, we try ldap_connect to ".$host, LOG_DEBUG);
357 }
358 $this->connection = ldap_connect($host, $this->serverPort);
359 } else {
360 if (preg_match('/^ldaps/i', $host)) {
361 // With host = ldaps://server, the serverPing to ssl://server sometimes fails, even if the ldap_connect succeed, so
362 // we test this case and continue in such a case even if serverPing fails.
363 if ($ldapdebug) {
364 dol_syslog(get_class($this)."::connectBind serverPing false, we try ldap_connect to ".$host, LOG_DEBUG);
365 }
366 $this->connection = ldap_connect($host, $this->serverPort);
367 } else {
368 if ($ldapdebug) {
369 dol_syslog(get_class($this)."::connectBind serverPing false, no ldap_connect ".$host, LOG_DEBUG);
370 }
371 continue;
372 }
373 }
374
375 if (is_resource($this->connection) || is_object($this->connection)) {
376 if ($ldapdebug) {
377 dol_syslog(get_class($this)."::connectBind this->connection is ok", LOG_DEBUG);
378 }
379
380 // Upgrade connection to TLS, if requested by the configuration
381 if (getDolGlobalString('LDAP_SERVER_USE_TLS')) {
382 // For test/debug
383 //ldap_set_option($this->connection, LDAP_OPT_DEBUG_LEVEL, 7);
384 //ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
385 //ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
386
387 $resulttls = ldap_start_tls($this->connection);
388 if (!$resulttls) {
389 dol_syslog(get_class($this)."::connectBind failed to start tls", LOG_WARNING);
390 $this->error = 'ldap_start_tls Failed to start TLS '.ldap_errno($this->connection).' '.ldap_error($this->connection);
391 $connected = 0;
392 $this->unbind();
393 }
394 }
395
396 // Execute the ldap_set_option here (after connect and before bind)
397 $this->setVersion();
398 $this->setSizeLimit();
399
400 if ($this->serverType == "activedirectory") {
401 $result = $this->setReferrals();
402 dol_syslog(get_class($this)."::connectBind try bindauth for activedirectory on ".$host." user=".$this->searchUser." password=".preg_replace('/./', '*', $this->searchPassword), LOG_DEBUG);
403 $this->result = $this->bindauth($this->searchUser, $this->searchPassword);
404 if ($this->result) {
405 $this->bind = $this->result;
406 $connected = 2;
407 $this->connectedServer = $host;
408 break;
409 } else {
410 $this->error = ldap_errno($this->connection).' '.ldap_error($this->connection);
411 }
412 } else {
413 // Try in auth mode
414 if ($this->searchUser && $this->searchPassword) {
415 dol_syslog(get_class($this)."::connectBind try bindauth on ".$host." user=".$this->searchUser." password=".preg_replace('/./', '*', $this->searchPassword), LOG_DEBUG);
416 $this->result = $this->bindauth($this->searchUser, $this->searchPassword);
417 if ($this->result) {
418 $this->bind = $this->result;
419 $connected = 2;
420 $this->connectedServer = $host;
421 break;
422 } else {
423 $this->error = ldap_errno($this->connection).' '.ldap_error($this->connection);
424 }
425 }
426 // Try in anonymous
427 if (!$this->bind) {
428 dol_syslog(get_class($this)."::connectBind try bind anonymously on ".$host, LOG_DEBUG);
429 $result = $this->bind();
430 if ($result) {
431 $this->bind = $this->result;
432 $connected = 1;
433 $this->connectedServer = $host;
434 break;
435 } else {
436 $this->error = ldap_errno($this->connection).' '.ldap_error($this->connection);
437 }
438 }
439 }
440 }
441
442 if (!$connected) {
443 $this->unbind();
444 }
445 } // End loop on each server
446 }
447
448 if ($connected) {
449 dol_syslog(get_class($this)."::connectBind ".$connected, LOG_DEBUG);
450 return $connected;
451 } else {
452 $this->error = 'Failed to connect to LDAP'.($this->error ? ': '.$this->error : '');
453 dol_syslog(get_class($this)."::connectBind ".$this->error, LOG_WARNING);
454 return -1;
455 }
456 }
457
466 public function close()
467 {
468 return $this->unbind();
469 }
470
477 public function bind()
478 {
479 if (!$this->result = @ldap_bind($this->connection)) {
480 $this->ldapErrorCode = ldap_errno($this->connection);
481 $this->ldapErrorText = ldap_error($this->connection);
482 $this->error = $this->ldapErrorCode." ".$this->ldapErrorText;
483 return false;
484 } else {
485 return true;
486 }
487 }
488
499 public function bindauth($bindDn, $pass)
500 {
501 if (!$this->result = @ldap_bind($this->connection, $bindDn, $pass)) {
502 $this->ldapErrorCode = ldap_errno($this->connection);
503 $this->ldapErrorText = ldap_error($this->connection);
504 $this->error = $this->ldapErrorCode." ".$this->ldapErrorText;
505 return false;
506 } else {
507 return true;
508 }
509 }
510
517 public function unbind()
518 {
519 $this->result = true;
520 if (is_resource($this->connection) || is_object($this->connection)) {
521 $this->result = @ldap_unbind($this->connection);
522 }
523 if ($this->result) {
524 return true;
525 } else {
526 return false;
527 }
528 }
529
530
536 public function getVersion()
537 {
538 @ldap_get_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $version);
539 return $version;
540 }
541
548 public function setVersion()
549 {
550 return ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $this->ldapProtocolVersion);
551 }
552
558 public function setSizeLimit()
559 {
560 return ldap_set_option($this->connection, LDAP_OPT_SIZELIMIT, 0);
561 }
562
569 public function setReferrals()
570 {
571 return ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
572 }
573
574
584 public function add($dn, $info, $user)
585 {
586 dol_syslog(get_class($this)."::add dn=".$dn." info=".print_r($info, true));
587
588 // Check parameters
589 if (!$this->connection) {
590 $this->error = "NotConnected";
591 return -2;
592 }
593 if (!$this->bind) {
594 $this->error = "NotConnected";
595 return -3;
596 }
597
598 // Encode to LDAP page code
599 $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
600 foreach ($info as $key => $val) {
601 if (!is_array($val)) {
602 $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
603 }
604 }
605
606 $this->dump($dn, $info);
607
608 //print_r($info);
609 $result = @ldap_add($this->connection, $dn, $info);
610
611 if ($result) {
612 dol_syslog(get_class($this)."::add successful", LOG_DEBUG);
613 return 1;
614 } else {
615 $this->ldapErrorCode = @ldap_errno($this->connection);
616 $this->ldapErrorText = @ldap_error($this->connection);
617 $this->error = $this->ldapErrorCode." ".$this->ldapErrorText;
618 dol_syslog(get_class($this)."::add failed: ".$this->error, LOG_ERR);
619 return -1;
620 }
621 }
622
632 public function modify($dn, $info, $user)
633 {
634 dol_syslog(get_class($this)."::modify dn=".$dn." info=".print_r($info, true));
635
636 // Check parameters
637 if (!$this->connection) {
638 $this->error = "NotConnected";
639 return -2;
640 }
641 if (!$this->bind) {
642 $this->error = "NotConnected";
643 return -3;
644 }
645
646 // Encode to LDAP page code
647 $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
648 foreach ($info as $key => $val) {
649 if (!is_array($val)) {
650 $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
651 }
652 }
653
654 $this->dump($dn, $info);
655
656 //print_r($info);
657
658 // For better compatibility with Samba4 AD
659 if ($this->serverType == "activedirectory") {
660 unset($info['cn']); // To avoid error : Operation not allowed on RDN (Code 67)
661
662 // To avoid error : LDAP Error: 53 (Unwilling to perform)
663 if (isset($info['unicodePwd'])) {
664 $info['unicodePwd'] = mb_convert_encoding("\"".$info['unicodePwd']."\"", "UTF-16LE", "UTF-8");
665 }
666 }
667 $result = @ldap_mod_replace($this->connection, $dn, $info);
668
669 if ($result) {
670 dol_syslog(get_class($this)."::modify successful", LOG_DEBUG);
671 return 1;
672 } else {
673 $this->error = @ldap_error($this->connection);
674 dol_syslog(get_class($this)."::modify failed: ".$this->error, LOG_ERR);
675 return -1;
676 }
677 }
678
690 public function rename($dn, $newrdn, $newparent, $user, $deleteoldrdn = true)
691 {
692 dol_syslog(get_class($this)."::modify dn=".$dn." newrdn=".$newrdn." newparent=".$newparent." deleteoldrdn=".($deleteoldrdn ? 1 : 0));
693
694 // Check parameters
695 if (!$this->connection) {
696 $this->error = "NotConnected";
697 return -2;
698 }
699 if (!$this->bind) {
700 $this->error = "NotConnected";
701 return -3;
702 }
703
704 // Encode to LDAP page code
705 $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
706 $newrdn = $this->convFromOutputCharset($newrdn, $this->ldapcharset);
707 $newparent = $this->convFromOutputCharset($newparent, $this->ldapcharset);
708
709 //print_r($info);
710 $result = @ldap_rename($this->connection, $dn, $newrdn, $newparent, $deleteoldrdn);
711
712 if ($result) {
713 dol_syslog(get_class($this)."::rename successful", LOG_DEBUG);
714 return 1;
715 } else {
716 $this->error = @ldap_error($this->connection);
717 dol_syslog(get_class($this)."::rename failed: ".$this->error, LOG_ERR);
718 return -1;
719 }
720 }
721
734 public function update($dn, $info, $user, $olddn, $newrdn = '', $newparent = '')
735 {
736 dol_syslog(get_class($this)."::update dn=".$dn." olddn=".$olddn);
737
738 // Check parameters
739 if (!$this->connection) {
740 $this->error = "NotConnected";
741 return -2;
742 }
743 if (!$this->bind) {
744 $this->error = "NotConnected";
745 return -3;
746 }
747
748 if (!$olddn || $olddn != $dn) {
749 if (!empty($olddn) && !empty($newrdn) && !empty($newparent) && $this->ldapProtocolVersion === '3') {
750 // This function currently only works with LDAPv3
751 $result = $this->rename($olddn, $newrdn, $newparent, $user, true);
752 $result = $this->modify($dn, $info, $user); // We force "modify" for avoid some fields not modify
753 } else {
754 // If change we make is rename the key of LDAP record, we create new one and if ok, we delete old one.
755 $result = $this->add($dn, $info, $user);
756 if ($result > 0 && $olddn && $olddn != $dn) {
757 $result = $this->delete($olddn); // If add fails, we do not try to delete old one
758 }
759 }
760 } else {
761 //$result = $this->delete($olddn);
762 $result = $this->add($dn, $info, $user); // If record has been deleted from LDAP, we recreate it. We ignore error if it already exists.
763 $result = $this->modify($dn, $info, $user); // We use add/modify instead of delete/add when olddn is received
764 }
765 if ($result <= 0) {
766 $this->error = ldap_error($this->connection).' (Code '.ldap_errno($this->connection).") ".$this->error;
767 dol_syslog(get_class($this)."::update ".$this->error, LOG_ERR);
768 //print_r($info);
769 return -1;
770 } else {
771 dol_syslog(get_class($this)."::update done successfully");
772 return 1;
773 }
774 }
775
776
784 public function delete($dn)
785 {
786 dol_syslog(get_class($this)."::delete Delete LDAP entry dn=".$dn);
787
788 // Check parameters
789 if (!$this->connection) {
790 $this->error = "NotConnected";
791 return -2;
792 }
793 if (!$this->bind) {
794 $this->error = "NotConnected";
795 return -3;
796 }
797
798 // Encode to LDAP page code
799 $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
800
801 $result = @ldap_delete($this->connection, $dn);
802
803 if ($result) {
804 return 1;
805 }
806 return -1;
807 }
808
817 public function dumpContent($dn, $info)
818 {
819 $content = '';
820
821 // Create file content
822 if (preg_match('/^ldap/', $this->server[0])) {
823 $target = "-H ".implode(',', $this->server);
824 } else {
825 $target = "-h ".implode(',', $this->server)." -p ".$this->serverPort;
826 }
827 $content .= "# ldapadd $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
828 $content .= "# ldapmodify $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
829 $content .= "# ldapdelete $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
830 if (in_array('localhost', $this->server)) {
831 $content .= "# If commands fails to connect, try without -h and -p\n";
832 }
833 $content .= "dn: ".$dn."\n";
834 foreach ($info as $key => $value) {
835 if (!is_array($value)) {
836 $content .= "$key: $value\n";
837 } else {
838 foreach ($value as $valuevalue) {
839 $content .= "$key: $valuevalue\n";
840 }
841 }
842 }
843 return $content;
844 }
845
853 public function dump($dn, $info)
854 {
855 global $conf;
856 $ldapDirTemp = $conf->ldap->dir_temp;
857 // Create content
858 $content = $this->dumpContent($dn, $info);
859
860 //Create directory & file
861 $result = dol_mkdir($ldapDirTemp);
862 if ($result != 0) {
863 $outputfile = $ldapDirTemp.'/ldapinput.in';
864 $fp = fopen($outputfile, "w");
865 if ($fp) {
866 fwrite($fp, $content);
867 fclose($fp);
868 dolChmod($outputfile);
869 return 1;
870 } else {
871 return -1;
872 }
873 } else {
874 return -1;
875 }
876 }
877
886 public function serverPing($host, $port = 389, $timeout = 1)
887 {
888 $regs = array();
889 if (preg_match('/^ldaps:\/\/([^\/]+)\/?$/', $host, $regs)) {
890 // Replace ldaps:// by ssl://
891 $host = 'ssl://'.$regs[1];
892 } elseif (preg_match('/^ldap:\/\/([^\/]+)\/?$/', $host, $regs)) {
893 // Remove ldap://
894 $host = $regs[1];
895 }
896
897 //var_dump($newhostforstream); var_dump($host); var_dump($port);
898 //$host = 'ssl://ldap.test.local:636';
899 //$port = 636;
900
901 $errno = $errstr = 0;
902 /*
903 if ($methodtochecktcpconnect == 'socket') {
904 Try to use socket_create() method.
905 Method that use stream_context_create() works only on registered listed in stream stream_get_wrappers(): http, https, ftp, ...
906 }
907 */
908
909 // Use the method fsockopen to test tcp connect. No way to ignore ssl certificate errors with this method !
910 $op = @fsockopen($host, $port, $errno, $errstr, $timeout);
911
912 //var_dump($op);
913 if (!$op) {
914 return false; //DC is N/A
915 } else {
916 fclose($op); //explicitly close open socket connection
917 return true; //DC is up & running, we can safely connect with ldap_connect
918 }
919 }
920
921
922 // Attribute methods -----------------------------------------------------
923
933 public function addAttribute($dn, $info, $user)
934 {
935 dol_syslog(get_class($this)."::addAttribute dn=".$dn." info=".implode(',', $info));
936
937 // Check parameters
938 if (!$this->connection) {
939 $this->error = "NotConnected";
940 return -2;
941 }
942 if (!$this->bind) {
943 $this->error = "NotConnected";
944 return -3;
945 }
946
947 // Encode to LDAP page code
948 $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
949 foreach ($info as $key => $val) {
950 if (!is_array($val)) {
951 $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
952 }
953 }
954
955 $this->dump($dn, $info);
956
957 //print_r($info);
958 $result = @ldap_mod_add($this->connection, $dn, $info);
959
960 if ($result) {
961 dol_syslog(get_class($this)."::add_attribute successful", LOG_DEBUG);
962 return 1;
963 } else {
964 $this->error = @ldap_error($this->connection);
965 dol_syslog(get_class($this)."::add_attribute failed: ".$this->error, LOG_ERR);
966 return -1;
967 }
968 }
969
979 public function updateAttribute($dn, $info, $user)
980 {
981 dol_syslog(get_class($this)."::updateAttribute dn=".$dn." info=".implode(',', $info));
982
983 // Check parameters
984 if (!$this->connection) {
985 $this->error = "NotConnected";
986 return -2;
987 }
988 if (!$this->bind) {
989 $this->error = "NotConnected";
990 return -3;
991 }
992
993 // Encode to LDAP page code
994 $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
995 foreach ($info as $key => $val) {
996 if (!is_array($val)) {
997 $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
998 }
999 }
1000
1001 $this->dump($dn, $info);
1002
1003 //print_r($info);
1004 $result = @ldap_mod_replace($this->connection, $dn, $info);
1005
1006 if ($result) {
1007 dol_syslog(get_class($this)."::updateAttribute successful", LOG_DEBUG);
1008 return 1;
1009 } else {
1010 $this->error = @ldap_error($this->connection);
1011 dol_syslog(get_class($this)."::updateAttribute failed: ".$this->error, LOG_ERR);
1012 return -1;
1013 }
1014 }
1015
1025 public function deleteAttribute($dn, $info, $user)
1026 {
1027 dol_syslog(get_class($this)."::deleteAttribute dn=".$dn." info=".implode(',', $info));
1028
1029 // Check parameters
1030 if (!$this->connection) {
1031 $this->error = "NotConnected";
1032 return -2;
1033 }
1034 if (!$this->bind) {
1035 $this->error = "NotConnected";
1036 return -3;
1037 }
1038
1039 // Encode to LDAP page code
1040 $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
1041 foreach ($info as $key => $val) {
1042 if (!is_array($val)) {
1043 $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
1044 }
1045 }
1046
1047 $this->dump($dn, $info);
1048
1049 //print_r($info);
1050 $result = @ldap_mod_del($this->connection, $dn, $info);
1051
1052 if ($result) {
1053 dol_syslog(get_class($this)."::deleteAttribute successful", LOG_DEBUG);
1054 return 1;
1055 } else {
1056 $this->error = @ldap_error($this->connection);
1057 dol_syslog(get_class($this)."::deleteAttribute failed: ".$this->error, LOG_ERR);
1058 return -1;
1059 }
1060 }
1061
1069 public function getAttribute($dn, $filter)
1070 {
1071 // Check parameters
1072 if (!$this->connection) {
1073 $this->error = "NotConnected";
1074 return -2;
1075 }
1076 if (!$this->bind) {
1077 $this->error = "NotConnected";
1078 return -3;
1079 }
1080
1081 $search = @ldap_search($this->connection, $dn, $filter);
1082
1083 // Only one entry should ever be returned
1084 $entry = @ldap_first_entry($this->connection, $search);
1085
1086 if (!$entry) {
1087 $this->ldapErrorCode = -1;
1088 $this->ldapErrorText = "Couldn't find entry";
1089 return 0; // Couldn't find entry...
1090 }
1091
1092 // Get values
1093 if (!($values = ldap_get_attributes($this->connection, $entry))) {
1094 $this->ldapErrorCode = ldap_errno($this->connection);
1095 $this->ldapErrorText = ldap_error($this->connection);
1096 return 0; // No matching attributes
1097 }
1098
1099 // Return an array containing the attributes.
1100 return $values;
1101 }
1102
1110 public function getAttributeValues($filterrecord, $attribute)
1111 {
1112 $attributes = array();
1113 $attributes[0] = $attribute;
1114
1115 // We need to search for this user in order to get their entry.
1116 $this->result = @ldap_search($this->connection, $this->people, $filterrecord, $attributes);
1117
1118 // Pourquoi cette ligne ?
1119 //$info = ldap_get_entries($this->connection, $this->result);
1120
1121 // Only one entry should ever be returned (no user will have the same uid)
1122 $entry = ldap_first_entry($this->connection, $this->result);
1123
1124 if (!$entry) {
1125 $this->ldapErrorCode = -1;
1126 $this->ldapErrorText = "Couldn't find user";
1127 return false; // Couldn't find the user...
1128 }
1129
1130 // Get values
1131 if (!$values = @ldap_get_values_len($this->connection, $entry, $attribute)) {
1132 $this->ldapErrorCode = ldap_errno($this->connection);
1133 $this->ldapErrorText = ldap_error($this->connection);
1134 return false; // No matching attributes
1135 }
1136
1137 // Return an array containing the attributes.
1138 return $values;
1139 }
1140
1153 public function getRecords($search, $userDn, $useridentifier, $attributeArray, $activefilter = 0, $attributeAsArray = array())
1154 {
1155 $fulllist = array();
1156
1157 dol_syslog(get_class($this)."::getRecords search=".$search." userDn=".$userDn." useridentifier=".$useridentifier." attributeArray=array(".implode(',', $attributeArray).") activefilter=".$activefilter);
1158
1159 // if the directory is AD, then bind first with the search user first
1160 if ($this->serverType == "activedirectory") {
1161 $this->bindauth($this->searchUser, $this->searchPassword);
1162 dol_syslog(get_class($this)."::bindauth serverType=activedirectory searchUser=".$this->searchUser);
1163 }
1164
1165 // Define filter
1166 if (!empty($activefilter)) { // Use a predefined trusted filter (defined into setup by admin).
1167 if (((string) $activefilter == '1' || (string) $activefilter == 'user') && $this->filter) {
1168 $filter = '('.$this->filter.')';
1169 } elseif (((string) $activefilter == 'group') && $this->filtergroup) {
1170 $filter = '('.$this->filtergroup.')';
1171 } elseif (((string) $activefilter == 'member') && $this->filter) {
1172 $filter = '('.$this->filtermember.')';
1173 } else {
1174 // If this->filter/this->filtergroup is empty, make filter on * (all)
1175 $filter = '('.ldap_escape($useridentifier, '', LDAP_ESCAPE_FILTER).'=*)';
1176 }
1177 } else { // Use a filter forged using the $search value
1178 $filter = '('.ldap_escape($useridentifier, '', LDAP_ESCAPE_FILTER).'='.ldap_escape($search, '', LDAP_ESCAPE_FILTER).')';
1179 }
1180
1181 if (is_array($attributeArray)) {
1182 // Return list with required fields
1183 $attributeArray = array_values($attributeArray); // This is to force to have index reordered from 0 (not make ldap_search fails)
1184 dol_syslog(get_class($this)."::getRecords connection=".$this->connectedServer.":".$this->serverPort." userDn=".$userDn." filter=".$filter." attributeArray=(".implode(',', $attributeArray).")");
1185 //var_dump($attributeArray);
1186 $this->result = @ldap_search($this->connection, $userDn, $filter, $attributeArray);
1187 } else {
1188 // Return list with fields selected by default
1189 dol_syslog(get_class($this)."::getRecords connection=".$this->connectedServer.":".$this->serverPort." userDn=".$userDn." filter=".$filter);
1190 $this->result = @ldap_search($this->connection, $userDn, $filter);
1191 }
1192 if (!$this->result) {
1193 $this->error = 'LDAP search failed: '.ldap_errno($this->connection)." ".ldap_error($this->connection);
1194 return -1;
1195 }
1196
1197 $info = @ldap_get_entries($this->connection, $this->result);
1198
1199 // Warning: Dans info, les noms d'attributs sont en minuscule meme si passe
1200 // a ldap_search en majuscule !!!
1201 //print_r($info);
1202
1203 for ($i = 0; $i < $info["count"]; $i++) {
1204 $recordid = $this->convToOutputCharset($info[$i][strtolower($useridentifier)][0], $this->ldapcharset);
1205 if ($recordid) {
1206 //print "Found record with key $useridentifier=".$recordid."<br>\n";
1207 $fulllist[$recordid][$useridentifier] = $recordid;
1208
1209 // Add to the array for each attribute in my list
1210 $num = count($attributeArray);
1211 for ($j = 0; $j < $num; $j++) {
1212 $keyattributelower = strtolower($attributeArray[$j]);
1213 //print " Param ".$attributeArray[$j]."=".$info[$i][$keyattributelower][0]."<br>\n";
1214
1215 //permet de recuperer le SID avec Active Directory
1216 if ($this->serverType == "activedirectory" && $keyattributelower == "objectsid") {
1217 $objectsid = $this->getObjectSid($recordid);
1218 $fulllist[$recordid][$attributeArray[$j]] = $objectsid;
1219 } else {
1220 if (in_array($attributeArray[$j], $attributeAsArray) && is_array($info[$i][$keyattributelower])) {
1221 $valueTab = array();
1222 foreach ($info[$i][$keyattributelower] as $key => $value) {
1223 $valueTab[$key] = $this->convToOutputCharset($value, $this->ldapcharset);
1224 }
1225 $fulllist[$recordid][$attributeArray[$j]] = $valueTab;
1226 } else {
1227 $fulllist[$recordid][$attributeArray[$j]] = $this->convToOutputCharset($info[$i][$keyattributelower][0], $this->ldapcharset);
1228 }
1229 }
1230 }
1231 }
1232 }
1233
1234 asort($fulllist);
1235 return $fulllist;
1236 }
1237
1245 public function littleEndian($hex)
1246 {
1247 $result = '';
1248 for ($x = dol_strlen($hex) - 2; $x >= 0; $x = $x - 2) {
1249 $result .= substr($hex, $x, 2);
1250 }
1251 return $result;
1252 }
1253
1254
1262 public function getObjectSid($ldapUser)
1263 {
1264 $criteria = '('.$this->getUserIdentifier().'='.$ldapUser.')';
1265 $justthese = array("objectsid");
1266
1267 // if the directory is AD, then bind first with the search user first
1268 if ($this->serverType == "activedirectory") {
1269 $this->bindauth($this->searchUser, $this->searchPassword);
1270 }
1271
1272 $i = 0;
1273 $searchDN = $this->people;
1274
1275 while ($i <= 2) {
1276 $ldapSearchResult = @ldap_search($this->connection, $searchDN, $criteria, $justthese);
1277
1278 if (!$ldapSearchResult) {
1279 $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1280 return -1;
1281 }
1282
1283 $entry = ldap_first_entry($this->connection, $ldapSearchResult);
1284
1285 if (!$entry) {
1286 // Si pas de resultat on cherche dans le domaine
1287 $searchDN = $this->domain;
1288 $i++;
1289 } else {
1290 $i++;
1291 $i++;
1292 }
1293 }
1294
1295 if ($entry) {
1296 $ldapBinary = ldap_get_values_len($this->connection, $entry, "objectsid");
1297 $SIDText = $this->binSIDtoText($ldapBinary[0]);
1298 return $SIDText;
1299 } else {
1300 $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1301 return -1;
1302 }
1303 }
1304
1312 public function binSIDtoText($binsid)
1313 {
1314 $hex_sid = bin2hex($binsid);
1315 $rev = hexdec(substr($hex_sid, 0, 2)); // Get revision-part of SID
1316 $subcount = hexdec(substr($hex_sid, 2, 2)); // Get count of sub-auth entries
1317 $auth = hexdec(substr($hex_sid, 4, 12)); // SECURITY_NT_AUTHORITY
1318 $result = "$rev-$auth";
1319 for ($x = 0; $x < $subcount; $x++) {
1320 $result .= "-".hexdec($this->littleEndian(substr($hex_sid, 16 + ($x * 8), 8))); // get all SECURITY_NT_AUTHORITY
1321 }
1322 return $result;
1323 }
1324
1325
1339 public function search($checkDn, $filter)
1340 {
1341 dol_syslog(get_class($this)."::search checkDn=".$checkDn." filter=".$filter);
1342
1343 $checkDn = $this->convFromOutputCharset($checkDn, $this->ldapcharset);
1344 $filter = $this->convFromOutputCharset($filter, $this->ldapcharset);
1345
1346 // if the directory is AD, then bind first with the search user first
1347 if ($this->serverType == "activedirectory") {
1348 $this->bindauth($this->searchUser, $this->searchPassword);
1349 }
1350
1351 $this->result = @ldap_search($this->connection, $checkDn, $filter);
1352
1353 $result = @ldap_get_entries($this->connection, $this->result);
1354 if (!$result) {
1355 $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1356 return -1;
1357 } else {
1358 ldap_free_result($this->result);
1359 return $result;
1360 }
1361 }
1362
1363
1372 public function fetch($user, $filter)
1373 {
1374 // Perform the search and get the entry handles
1375
1376 // if the directory is AD, then bind first with the search user first
1377 if ($this->serverType == "activedirectory") {
1378 $this->bindauth($this->searchUser, $this->searchPassword);
1379 }
1380
1381 $searchDN = $this->people; // TODO Why searching in people then domain ?
1382
1383 $result = '';
1384 $i = 0;
1385 while ($i <= 2) {
1386 dol_syslog(get_class($this)."::fetch search with searchDN=".$searchDN." filter=".$filter);
1387 $this->result = @ldap_search($this->connection, $searchDN, $filter);
1388 if ($this->result) {
1389 $result = @ldap_get_entries($this->connection, $this->result);
1390 if ($result['count'] > 0) {
1391 dol_syslog('Ldap::fetch search found '.$result['count'].' records');
1392 } else {
1393 dol_syslog('Ldap::fetch search returns but found no records');
1394 }
1395 //var_dump($result);exit;
1396 } else {
1397 $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1398 dol_syslog(get_class($this)."::fetch search fails");
1399 return -1;
1400 }
1401
1402 if (!$result) {
1403 // Si pas de resultat on cherche dans le domaine
1404 $searchDN = $this->domain;
1405 $i++;
1406 } else {
1407 break;
1408 }
1409 }
1410
1411 if (!$result) {
1412 $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1413 return -1;
1414 } else {
1415 $this->name = $this->convToOutputCharset($result[0][$this->attr_name][0], $this->ldapcharset);
1416 $this->firstname = $this->convToOutputCharset($result[0][$this->attr_firstname][0], $this->ldapcharset);
1417 $this->login = $this->convToOutputCharset($result[0][$this->attr_login][0], $this->ldapcharset);
1418 $this->phone = $this->convToOutputCharset($result[0][$this->attr_phone][0], $this->ldapcharset);
1419 $this->fax = $this->convToOutputCharset($result[0][$this->attr_fax][0], $this->ldapcharset);
1420 $this->mail = $this->convToOutputCharset($result[0][$this->attr_mail][0], $this->ldapcharset);
1421 $this->mobile = $this->convToOutputCharset($result[0][$this->attr_mobile][0], $this->ldapcharset);
1422
1423 $this->uacf = $this->parseUACF($this->convToOutputCharset($result[0]["useraccountcontrol"][0], $this->ldapcharset));
1424 if (isset($result[0]["pwdlastset"][0])) { // If expiration on password exists
1425 $this->pwdlastset = ($result[0]["pwdlastset"][0] != 0) ? $this->convertTime($this->convToOutputCharset($result[0]["pwdlastset"][0], $this->ldapcharset)) : 0;
1426 } else {
1427 $this->pwdlastset = -1;
1428 }
1429 if (!$this->name && !$this->login) {
1430 $this->pwdlastset = -1;
1431 }
1432 $this->badpwdtime = $this->convertTime($this->convToOutputCharset($result[0]["badpasswordtime"][0], $this->ldapcharset));
1433
1434 // FQDN domain
1435 $domain = str_replace('dc=', '', $this->domain);
1436 $domain = str_replace(',', '.', $domain);
1437 $this->domainFQDN = $domain;
1438
1439 // Set ldapUserDn (each user can have a different dn)
1440 //var_dump($result[0]);exit;
1441 $this->ldapUserDN = $result[0]['dn'];
1442
1443 ldap_free_result($this->result);
1444 return 1;
1445 }
1446 }
1447
1448
1449 // helper methods
1450
1456 public function getUserIdentifier()
1457 {
1458 if ($this->serverType == "activedirectory") {
1459 return $this->attr_sambalogin;
1460 } else {
1461 return $this->attr_login;
1462 }
1463 }
1464
1471 public function parseUACF($uacf)
1472 {
1473 //All flags array
1474 $flags = array(
1475 "TRUSTED_TO_AUTH_FOR_DELEGATION" => 16777216,
1476 "PASSWORD_EXPIRED" => 8388608,
1477 "DONT_REQ_PREAUTH" => 4194304,
1478 "USE_DES_KEY_ONLY" => 2097152,
1479 "NOT_DELEGATED" => 1048576,
1480 "TRUSTED_FOR_DELEGATION" => 524288,
1481 "SMARTCARD_REQUIRED" => 262144,
1482 "MNS_LOGON_ACCOUNT" => 131072,
1483 "DONT_EXPIRE_PASSWORD" => 65536,
1484 "SERVER_TRUST_ACCOUNT" => 8192,
1485 "WORKSTATION_TRUST_ACCOUNT" => 4096,
1486 "INTERDOMAIN_TRUST_ACCOUNT" => 2048,
1487 "NORMAL_ACCOUNT" => 512,
1488 "TEMP_DUPLICATE_ACCOUNT" => 256,
1489 "ENCRYPTED_TEXT_PWD_ALLOWED" => 128,
1490 "PASSWD_CANT_CHANGE" => 64,
1491 "PASSWD_NOTREQD" => 32,
1492 "LOCKOUT" => 16,
1493 "HOMEDIR_REQUIRED" => 8,
1494 "ACCOUNTDISABLE" => 2,
1495 "SCRIPT" => 1
1496 );
1497
1498 //Parse flags to text
1499 $retval = array();
1500 //while (list($flag, $val) = each($flags)) {
1501 foreach ($flags as $flag => $val) {
1502 if ($uacf >= $val) {
1503 $uacf -= $val;
1504 $retval[$val] = $flag;
1505 }
1506 }
1507
1508 //Return human friendly flags
1509 return $retval;
1510 }
1511
1518 public function parseSAT($samtype)
1519 {
1520 $stypes = array(
1521 805306368 => "NORMAL_ACCOUNT",
1522 805306369 => "WORKSTATION_TRUST",
1523 805306370 => "INTERDOMAIN_TRUST",
1524 268435456 => "SECURITY_GLOBAL_GROUP",
1525 268435457 => "DISTRIBUTION_GROUP",
1526 536870912 => "SECURITY_LOCAL_GROUP",
1527 536870913 => "DISTRIBUTION_LOCAL_GROUP"
1528 );
1529
1530 $retval = "";
1531 foreach ($stypes as $sat => $val) {
1532 if ($samtype == $sat) {
1533 $retval = $val;
1534 break;
1535 }
1536 }
1537 if (empty($retval)) {
1538 $retval = "UNKNOWN_TYPE_".$samtype;
1539 }
1540
1541 return $retval;
1542 }
1543
1550 public function convertTime($value)
1551 {
1552 $dateLargeInt = $value; // nano secondes depuis 1601 !!!!
1553 $secsAfterADEpoch = $dateLargeInt / (10000000); // secondes depuis le 1 jan 1601
1554 $ADToUnixConvertor = ((1970 - 1601) * 365.242190) * 86400; // UNIX start date - AD start date * jours * secondes
1555 $unixTimeStamp = intval($secsAfterADEpoch - $ADToUnixConvertor); // Unix time stamp
1556 return $unixTimeStamp;
1557 }
1558
1559
1567 private function convToOutputCharset($str, $pagecodefrom = 'UTF-8')
1568 {
1569 global $conf;
1570 if ($pagecodefrom == 'ISO-8859-1' && $conf->file->character_set_client == 'UTF-8') {
1571 $str = mb_convert_encoding($str, 'UTF-8', 'ISO-8859-1');
1572 }
1573 if ($pagecodefrom == 'UTF-8' && $conf->file->character_set_client == 'ISO-8859-1') {
1574 $str = mb_convert_encoding($str, 'ISO-8859-1');
1575 }
1576 return $str;
1577 }
1578
1586 public function convFromOutputCharset($str, $pagecodeto = 'UTF-8')
1587 {
1588 global $conf;
1589 if ($pagecodeto == 'ISO-8859-1' && $conf->file->character_set_client == 'UTF-8') {
1590 $str = mb_convert_encoding($str, 'ISO-8859-1');
1591 }
1592 if ($pagecodeto == 'UTF-8' && $conf->file->character_set_client == 'ISO-8859-1') {
1593 $str = mb_convert_encoding($str, 'UTF-8', 'ISO-8859-1');
1594 }
1595 return $str;
1596 }
1597
1598
1605 public function getNextGroupGid($keygroup = 'LDAP_KEY_GROUPS')
1606 {
1607
1608 if (empty($keygroup)) {
1609 $keygroup = 'LDAP_KEY_GROUPS';
1610 }
1611
1612 $search = '(' . getDolGlobalString($keygroup).'=*)';
1613 $result = $this->search($this->groups, $search);
1614 if ($result) {
1615 $c = $result['count'];
1616 $gids = array();
1617 for ($i = 0; $i < $c; $i++) {
1618 $gids[] = $result[$i]['gidnumber'][0];
1619 }
1620 rsort($gids);
1621
1622 return $gids[0] + 1;
1623 }
1624
1625 return 0;
1626 }
1627}
Class to manage LDAP features.
add($dn, $info, $user)
Add an LDAP entry LDAP object connect and bind must have been done.
convertTime($value)
Converts ActiveDirectory time to Unix timestamp.
modify($dn, $info, $user)
Modify an LDAP entry LDAP object connect and bind must have been done.
deleteAttribute($dn, $info, $user)
Delete an LDAP attribute in entry LDAP object connect and bind must have been done.
setVersion()
Set LDAP protocol version.
convToOutputCharset($str, $pagecodefrom='UTF-8')
Convert a string into output/memory charset.
littleEndian($hex)
Converts a little-endian hex-number to one, that 'hexdec' can convert Required by Active Directory.
fetch($user, $filter)
Load all attributes of an LDAP user.
update($dn, $info, $user, $olddn, $newrdn='', $newparent='')
Modify an LDAP entry (to use if dn != olddn) LDAP object connect and bind must have been done.
getObjectSid($ldapUser)
Gets LDAP user SID.
updateAttribute($dn, $info, $user)
Update an LDAP attribute in entry LDAP object connect and bind must have been done.
getUserIdentifier()
Returns the correct user identifier to use, based on the LDAP server type.
getAttribute($dn, $filter)
Returns an array containing attributes and values for first record.
close()
Simply closes the connection set up earlier.
parseSAT($samtype)
SamAccountType value to text.
rename($dn, $newrdn, $newparent, $user, $deleteoldrdn=true)
Rename an LDAP entry LDAP object connect and bind must have been done.
getNextGroupGid($keygroup='LDAP_KEY_GROUPS')
Return available value of group GID.
setSizeLimit()
Set LDAP size limit.
binSIDtoText($binsid)
Returns the textual SID Required by Active Directory.
connectBind()
Connect and bind Use this->server, this->serverPort, this->ldapProtocolVersion, this->serverType,...
setReferrals()
Set LDAP referrals.
search($checkDn, $filter)
Search method with filter this->connection must be defined.
getRecords($search, $userDn, $useridentifier, $attributeArray, $activefilter=0, $attributeAsArray=array())
Returns an array containing a details or list of LDAP record(s).
getVersion()
Verify LDAP server version.
dumpContent($dn, $info)
Build an LDAP message.
getAttributeValues($filterrecord, $attribute)
Returns an array containing values for an attribute and for first record matching filterrecord.
parseUACF($uacf)
UserAccountControl Flags to more human understandable form...
__construct()
Constructor.
convFromOutputCharset($str, $pagecodeto='UTF-8')
Convert a string from output/memory charset.
serverPing($host, $port=389, $timeout=1)
Ping a server before ldap_connect for avoid waiting.
bind()
Anonymously binds to the connection.
unbind()
Unbind of LDAP server (close connection).
bindauth($bindDn, $pass)
Binds as an authenticated user, which usually allows for write access.
dump($dn, $info)
Dump an LDAP message to ldapinput.in file.
addAttribute($dn, $info, $user)
Add an LDAP attribute in entry LDAP object connect and bind must have been done.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dolChmod($filepath, $newmask='')
Change mod of a file.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:142