dolibarr  9.0.0
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-2017 Regis Houssin <regis.houssin@inodbox.com>
5  * Copyright (C) 2006-2015 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 <http://www.gnu.org/licenses/>.
19  * or see http://www.gnu.org/
20  */
21 
30 class Ldap
31 {
35  public $error='';
36 
40  public $errors = array();
41 
45  var $server=array();
46 
50  var $dn;
58  var $domain;
72  var $people;
76  var $groups;
85 
86 
87  //Fetch user
88  var $name;
89  var $firstname;
90  var $login;
91  var $phone;
92  var $skype;
93  var $fax;
94  var $mail;
95  var $mobile;
96 
97  var $uacf;
98  var $pwdlastset;
99 
100  var $ldapcharset='UTF-8'; // LDAP should be UTF-8 encoded
101 
102 
110  var $result;
111 
112 
116  function __construct()
117  {
118  global $conf;
119 
120  // Server
121  if (! empty($conf->global->LDAP_SERVER_HOST)) $this->server[] = $conf->global->LDAP_SERVER_HOST;
122  if (! empty($conf->global->LDAP_SERVER_HOST_SLAVE)) $this->server[] = $conf->global->LDAP_SERVER_HOST_SLAVE;
123  $this->serverPort = $conf->global->LDAP_SERVER_PORT;
124  $this->ldapProtocolVersion = $conf->global->LDAP_SERVER_PROTOCOLVERSION;
125  $this->dn = $conf->global->LDAP_SERVER_DN;
126  $this->serverType = $conf->global->LDAP_SERVER_TYPE;
127  $this->domain = $conf->global->LDAP_SERVER_DN;
128  $this->searchUser = $conf->global->LDAP_ADMIN_DN;
129  $this->searchPassword = $conf->global->LDAP_ADMIN_PASS;
130  $this->people = $conf->global->LDAP_USER_DN;
131  $this->groups = $conf->global->LDAP_GROUP_DN;
132 
133  $this->filter = $conf->global->LDAP_FILTER_CONNECTION; // Filter on user
134  $this->filtermember = $conf->global->LDAP_MEMBER_FILTER; // Filter on member
135 
136  // Users
137  $this->attr_login = $conf->global->LDAP_FIELD_LOGIN; //unix
138  $this->attr_sambalogin = $conf->global->LDAP_FIELD_LOGIN_SAMBA; //samba, activedirectory
139  $this->attr_name = $conf->global->LDAP_FIELD_NAME;
140  $this->attr_firstname = $conf->global->LDAP_FIELD_FIRSTNAME;
141  $this->attr_mail = $conf->global->LDAP_FIELD_MAIL;
142  $this->attr_phone = $conf->global->LDAP_FIELD_PHONE;
143  $this->attr_skype = $conf->global->LDAP_FIELD_SKYPE;
144  $this->attr_fax = $conf->global->LDAP_FIELD_FAX;
145  $this->attr_mobile = $conf->global->LDAP_FIELD_MOBILE;
146  }
147 
148 
149 
150  // Connection handling methods -------------------------------------------
151 
152  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
160  function connect_bind()
161  {
162  // phpcs:enable
163  global $langs, $conf;
164 
165  $connected=0;
166  $this->bind=0;
167 
168  // Check parameters
169  if (count($this->server) == 0 || empty($this->server[0]))
170  {
171  $this->error='LDAP setup (file conf.php) is not complete';
172  dol_syslog(get_class($this)."::connect_bind ".$this->error, LOG_WARNING);
173  return -1;
174  }
175 
176  if (! function_exists("ldap_connect"))
177  {
178  $this->error='LDAPFunctionsNotAvailableOnPHP';
179  dol_syslog(get_class($this)."::connect_bind ".$this->error, LOG_WARNING);
180  $return=-1;
181  }
182 
183  if (empty($this->error))
184  {
185  // Loop on each ldap server
186  foreach ($this->server as $key => $host)
187  {
188  if ($connected) break;
189  if (empty($host)) continue;
190 
191  if ($this->serverPing($host, $this->serverPort) === true) {
192  $this->connection = ldap_connect($host, $this->serverPort);
193  }
194  else continue;
195 
196  if (is_resource($this->connection))
197  {
198  // Begin TLS if requested by the configuration
199  if (! empty($conf->global->LDAP_SERVER_USE_TLS))
200  {
201  if (! ldap_start_tls($this->connection))
202  {
203  dol_syslog(get_class($this)."::connect_bind failed to start tls", LOG_WARNING);
204  $connected = 0;
205  $this->close();
206  }
207  }
208 
209  // Execute the ldap_set_option here (after connect and before bind)
210  $this->setVersion();
211  ldap_set_option($this->connection, LDAP_OPT_SIZELIMIT, 0); // no limit here. should return true.
212 
213 
214  if ($this->serverType == "activedirectory")
215  {
216  $result=$this->setReferrals();
217  dol_syslog(get_class($this)."::connect_bind try bindauth for activedirectory on ".$host." user=".$this->searchUser." password=".preg_replace('/./','*',$this->searchPassword),LOG_DEBUG);
218  $this->result=$this->bindauth($this->searchUser,$this->searchPassword);
219  if ($this->result)
220  {
221  $this->bind=$this->result;
222  $connected=2;
223  break;
224  }
225  else
226  {
227  $this->error=ldap_errno($this->connection).' '.ldap_error($this->connection);
228  }
229  }
230  else
231  {
232  // Try in auth mode
233  if ($this->searchUser && $this->searchPassword)
234  {
235  dol_syslog(get_class($this)."::connect_bind try bindauth on ".$host." user=".$this->searchUser." password=".preg_replace('/./','*',$this->searchPassword),LOG_DEBUG);
236  $this->result=$this->bindauth($this->searchUser,$this->searchPassword);
237  if ($this->result)
238  {
239  $this->bind=$this->result;
240  $connected=2;
241  break;
242  }
243  else
244  {
245  $this->error=ldap_errno($this->connection).' '.ldap_error($this->connection);
246  }
247  }
248  // Try in anonymous
249  if (! $this->bind)
250  {
251  dol_syslog(get_class($this)."::connect_bind try bind on ".$host,LOG_DEBUG);
252  $result=$this->bind();
253  if ($result)
254  {
255  $this->bind=$this->result;
256  $connected=1;
257  break;
258  }
259  else
260  {
261  $this->error=ldap_errno($this->connection).' '.ldap_error($this->connection);
262  }
263  }
264  }
265  }
266 
267  if (! $connected) $this->close();
268  }
269  }
270 
271  if ($connected)
272  {
273  $return=$connected;
274  dol_syslog(get_class($this)."::connect_bind return=".$return, LOG_DEBUG);
275  }
276  else
277  {
278  $this->error='Failed to connect to LDAP'.($this->error?': '.$this->error:'');
279  $return=-1;
280  dol_syslog(get_class($this)."::connect_bind return=".$return.' - '.$this->error, LOG_WARNING);
281  }
282  return $return;
283  }
284 
285 
286 
293  function close()
294  {
295  if ($this->connection && ! @ldap_close($this->connection))
296  {
297  return false;
298  }
299  else
300  {
301  return true;
302  }
303  }
304 
311  function bind()
312  {
313  if (! $this->result=@ldap_bind($this->connection))
314  {
315  $this->ldapErrorCode = ldap_errno($this->connection);
316  $this->ldapErrorText = ldap_error($this->connection);
317  $this->error=$this->ldapErrorCode." ".$this->ldapErrorText;
318  return false;
319  }
320  else
321  {
322  return true;
323  }
324  }
325 
336  function bindauth($bindDn,$pass)
337  {
338  if (! $this->result = @ldap_bind($this->connection, $bindDn, $pass))
339  {
340  $this->ldapErrorCode = ldap_errno($this->connection);
341  $this->ldapErrorText = ldap_error($this->connection);
342  $this->error=$this->ldapErrorCode." ".$this->ldapErrorText;
343  return false;
344  }
345  else
346  {
347  return true;
348  }
349  }
350 
356  function unbind()
357  {
358  if (!$this->result=@ldap_unbind($this->connection))
359  {
360  return false;
361  } else {
362  return true;
363  }
364  }
365 
366 
372  function getVersion()
373  {
374  $version = 0;
375  $version = @ldap_get_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $version);
376  return $version;
377  }
378 
384  function setVersion()
385  {
386  // LDAP_OPT_PROTOCOL_VERSION est une constante qui vaut 17
387  $ldapsetversion = ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $this->ldapProtocolVersion);
388  return $ldapsetversion;
389  }
390 
396  function setReferrals()
397  {
398  // LDAP_OPT_REFERRALS est une constante qui vaut ?
399  $ldapreferrals = ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
400  return $ldapreferrals;
401  }
402 
403 
413  function add($dn, $info, $user)
414  {
415  global $conf;
416 
417  dol_syslog(get_class($this)."::add dn=".$dn." info=".join(',',$info));
418 
419  // Check parameters
420  if (! $this->connection)
421  {
422  $this->error="NotConnected";
423  return -2;
424  }
425  if (! $this->bind)
426  {
427  $this->error="NotConnected";
428  return -3;
429  }
430 
431  // Encode to LDAP page code
432  $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
433  foreach($info as $key => $val)
434  {
435  if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
436  }
437 
438  $this->dump($dn,$info);
439 
440  //print_r($info);
441  $result=@ldap_add($this->connection, $dn, $info);
442 
443  if ($result)
444  {
445  dol_syslog(get_class($this)."::add successfull", LOG_DEBUG);
446  return 1;
447  }
448  else
449  {
450  $this->ldapErrorCode = @ldap_errno($this->connection);
451  $this->ldapErrorText = @ldap_error($this->connection);
452  $this->error=$this->ldapErrorCode." ".$this->ldapErrorText;
453  dol_syslog(get_class($this)."::add failed: ".$this->error, LOG_ERR);
454  return -1;
455  }
456  }
457 
467  function modify($dn, $info, $user)
468  {
469  global $conf;
470 
471  dol_syslog(get_class($this)."::modify dn=".$dn." info=".join(',',$info));
472 
473  // Check parameters
474  if (! $this->connection)
475  {
476  $this->error="NotConnected";
477  return -2;
478  }
479  if (! $this->bind)
480  {
481  $this->error="NotConnected";
482  return -3;
483  }
484 
485  // Encode to LDAP page code
486  $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
487  foreach($info as $key => $val)
488  {
489  if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
490  }
491 
492  $this->dump($dn,$info);
493 
494  //print_r($info);
495  $result=@ldap_modify($this->connection, $dn, $info);
496 
497  if ($result)
498  {
499  dol_syslog(get_class($this)."::modify successfull", LOG_DEBUG);
500  return 1;
501  }
502  else
503  {
504  $this->error=@ldap_error($this->connection);
505  dol_syslog(get_class($this)."::modify failed: ".$this->error, LOG_ERR);
506  return -1;
507  }
508  }
509 
521  function rename($dn, $newrdn, $newparent, $user, $deleteoldrdn = true)
522  {
523  global $conf;
524 
525  dol_syslog(get_class($this)."::modify dn=".$dn." newrdn=".$newrdn." newparent=".$newparent." deleteoldrdn=".($deleteoldrdn?1:0));
526 
527  // Check parameters
528  if (! $this->connection)
529  {
530  $this->error="NotConnected";
531  return -2;
532  }
533  if (! $this->bind)
534  {
535  $this->error="NotConnected";
536  return -3;
537  }
538 
539  // Encode to LDAP page code
540  $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
541  $newrdn=$this->convFromOutputCharset($newrdn,$this->ldapcharset);
542  $newparent=$this->convFromOutputCharset($newparent,$this->ldapcharset);
543 
544  //print_r($info);
545  $result=@ldap_rename($this->connection, $dn, $newrdn, $newparent, $deleteoldrdn);
546 
547  if ($result)
548  {
549  dol_syslog(get_class($this)."::rename successfull", LOG_DEBUG);
550  return 1;
551  }
552  else
553  {
554  $this->error=@ldap_error($this->connection);
555  dol_syslog(get_class($this)."::rename failed: ".$this->error, LOG_ERR);
556  return -1;
557  }
558  }
559 
572  function update($dn, $info, $user, $olddn, $newrdn=false, $newparent=false)
573  {
574  global $conf;
575 
576  dol_syslog(get_class($this)."::update dn=".$dn." olddn=".$olddn);
577 
578  // Check parameters
579  if (! $this->connection)
580  {
581  $this->error="NotConnected";
582  return -2;
583  }
584  if (! $this->bind)
585  {
586  $this->error="NotConnected";
587  return -3;
588  }
589 
590  if (! $olddn || $olddn != $dn)
591  {
592  if (! empty($olddn) && ! empty($newrdn) && ! empty($newparent) && $conf->global->LDAP_SERVER_PROTOCOLVERSION === '3')
593  {
594  // This function currently only works with LDAPv3
595  $result = $this->rename($olddn, $newrdn, $newparent, $user, true);
596  }
597  else
598  {
599  // If change we make is rename the key of LDAP record, we create new one and if ok, we delete old one.
600  $result = $this->add($dn, $info, $user);
601  if ($result > 0 && $olddn && $olddn != $dn) $result = $this->delete($olddn); // If add fails, we do not try to delete old one
602  }
603  }
604  else
605  {
606  //$result = $this->delete($olddn);
607  $result = $this->add($dn, $info, $user); // If record has been deleted from LDAP, we recreate it. We ignore error if it already exists.
608  $result = $this->modify($dn, $info, $user); // We use add/modify instead of delete/add when olddn is received
609  }
610  if ($result <= 0)
611  {
612  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection)." ".$this->error;
613  dol_syslog(get_class($this)."::update ".$this->error,LOG_ERR);
614  //print_r($info);
615  return -1;
616  }
617  else
618  {
619  dol_syslog(get_class($this)."::update done successfully");
620  return 1;
621  }
622  }
623 
624 
632  function delete($dn)
633  {
634  global $conf;
635 
636  dol_syslog(get_class($this)."::delete Delete LDAP entry dn=".$dn);
637 
638  // Check parameters
639  if (! $this->connection)
640  {
641  $this->error="NotConnected";
642  return -2;
643  }
644  if (! $this->bind)
645  {
646  $this->error="NotConnected";
647  return -3;
648  }
649 
650  // Encode to LDAP page code
651  $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
652 
653  $result=@ldap_delete($this->connection, $dn);
654 
655  if ($result) return 1;
656  return -1;
657  }
658 
659  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
667  function dump_content($dn, $info)
668  {
669  // phpcs:enable
670  $content='';
671 
672  // Create file content
673  if (preg_match('/^ldap/',$this->server[0]))
674  {
675  $target="-H ".join(',',$this->server);
676  }
677  else
678  {
679  $target="-h ".join(',',$this->server)." -p ".$this->serverPort;
680  }
681  $content.="# ldapadd $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
682  $content.="# ldapmodify $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
683  $content.="# ldapdelete $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
684  if (in_array('localhost',$this->server)) $content.="# If commands fails to connect, try without -h and -p\n";
685  $content.="dn: ".$dn."\n";
686  foreach($info as $key => $value)
687  {
688  if (! is_array($value))
689  {
690  $content.="$key: $value\n";
691  }
692  else
693  {
694  foreach($value as $valuekey => $valuevalue)
695  {
696  $content.="$key: $valuevalue\n";
697  }
698  }
699  }
700  return $content;
701  }
702 
710  function dump($dn, $info)
711  {
712  global $conf;
713 
714  // Create content
715  $content=$this->dump_content($dn, $info);
716 
717  //Create file
718  $result=dol_mkdir($conf->ldap->dir_temp);
719 
720  $outputfile=$conf->ldap->dir_temp.'/ldapinput.in';
721  $fp=fopen($outputfile,"w");
722  if ($fp)
723  {
724  fputs($fp, $content);
725  fclose($fp);
726  if (! empty($conf->global->MAIN_UMASK))
727  @chmod($outputfile, octdec($conf->global->MAIN_UMASK));
728  return 1;
729  }
730  else
731  {
732  return -1;
733  }
734  }
735 
744  function serverPing($host, $port=389, $timeout=1)
745  {
746  // Replace ldaps:// by ssl://
747  if (preg_match('/^ldaps:\/\/([^\/]+)\/?$/',$host, $regs)) {
748  $host = 'ssl://'.$regs[1];
749  }
750  // Remove ldap://
751  if (preg_match('/^ldap:\/\/([^\/]+)\/?$/',$host, $regs)) {
752  $host = $regs[1];
753  }
754  $op = @fsockopen($host, $port, $errno, $errstr, $timeout);
755  if (!$op) return false; //DC is N/A
756  else {
757  fclose($op); //explicitly close open socket connection
758  return true; //DC is up & running, we can safely connect with ldap_connect
759  }
760  }
761 
762 
763  // Attribute methods -----------------------------------------------------
764 
774  function addAttribute($dn, $info, $user)
775  {
776  global $conf;
777 
778  dol_syslog(get_class($this)."::addAttribute dn=".$dn." info=".join(',',$info));
779 
780  // Check parameters
781  if (! $this->connection)
782  {
783  $this->error="NotConnected";
784  return -2;
785  }
786  if (! $this->bind)
787  {
788  $this->error="NotConnected";
789  return -3;
790  }
791 
792  // Encode to LDAP page code
793  $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
794  foreach($info as $key => $val)
795  {
796  if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
797  }
798 
799  $this->dump($dn,$info);
800 
801  //print_r($info);
802  $result=@ldap_mod_add($this->connection, $dn, $info);
803 
804  if ($result)
805  {
806  dol_syslog(get_class($this)."::add_attribute successfull", LOG_DEBUG);
807  return 1;
808  }
809  else
810  {
811  $this->error=@ldap_error($this->connection);
812  dol_syslog(get_class($this)."::add_attribute failed: ".$this->error, LOG_ERR);
813  return -1;
814  }
815  }
816 
826  function updateAttribute($dn, $info, $user)
827  {
828  global $conf;
829 
830  dol_syslog(get_class($this)."::updateAttribute dn=".$dn." info=".join(',',$info));
831 
832  // Check parameters
833  if (! $this->connection)
834  {
835  $this->error="NotConnected";
836  return -2;
837  }
838  if (! $this->bind)
839  {
840  $this->error="NotConnected";
841  return -3;
842  }
843 
844  // Encode to LDAP page code
845  $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
846  foreach($info as $key => $val)
847  {
848  if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
849  }
850 
851  $this->dump($dn,$info);
852 
853  //print_r($info);
854  $result=@ldap_mod_replace($this->connection, $dn, $info);
855 
856  if ($result)
857  {
858  dol_syslog(get_class($this)."::updateAttribute successfull", LOG_DEBUG);
859  return 1;
860  }
861  else
862  {
863  $this->error=@ldap_error($this->connection);
864  dol_syslog(get_class($this)."::updateAttribute failed: ".$this->error, LOG_ERR);
865  return -1;
866  }
867  }
868 
878  function deleteAttribute($dn, $info, $user)
879  {
880  global $conf;
881 
882  dol_syslog(get_class($this)."::deleteAttribute dn=".$dn." info=".join(',',$info));
883 
884  // Check parameters
885  if (! $this->connection)
886  {
887  $this->error="NotConnected";
888  return -2;
889  }
890  if (! $this->bind)
891  {
892  $this->error="NotConnected";
893  return -3;
894  }
895 
896  // Encode to LDAP page code
897  $dn=$this->convFromOutputCharset($dn,$this->ldapcharset);
898  foreach($info as $key => $val)
899  {
900  if (! is_array($val)) $info[$key]=$this->convFromOutputCharset($val,$this->ldapcharset);
901  }
902 
903  $this->dump($dn,$info);
904 
905  //print_r($info);
906  $result=@ldap_mod_del($this->connection, $dn, $info);
907 
908  if ($result)
909  {
910  dol_syslog(get_class($this)."::deleteAttribute successfull", LOG_DEBUG);
911  return 1;
912  }
913  else
914  {
915  $this->error=@ldap_error($this->connection);
916  dol_syslog(get_class($this)."::deleteAttribute failed: ".$this->error, LOG_ERR);
917  return -1;
918  }
919  }
920 
928  function getAttribute($dn,$filter)
929  {
930  // Check parameters
931  if (! $this->connection)
932  {
933  $this->error="NotConnected";
934  return -2;
935  }
936  if (! $this->bind)
937  {
938  $this->error="NotConnected";
939  return -3;
940  }
941 
942  $search = ldap_search($this->connection,$dn,$filter);
943 
944  // Only one entry should ever be returned
945  $entry = ldap_first_entry($this->connection, $search);
946 
947  if (!$entry)
948  {
949  $this->ldapErrorCode = -1;
950  $this->ldapErrorText = "Couldn't find entry";
951  return 0; // Couldn't find entry...
952  }
953 
954  // Get values
955  if (! $values = ldap_get_attributes($this->connection, $entry))
956  {
957  $this->ldapErrorCode = ldap_errno($this->connection);
958  $this->ldapErrorText = ldap_error($this->connection);
959  return 0; // No matching attributes
960  }
961 
962  // Return an array containing the attributes.
963  return $values;
964  }
965 
973  function getAttributeValues($filterrecord,$attribute)
974  {
975  $attributes=array();
976  $attributes[0] = $attribute;
977 
978  // We need to search for this user in order to get their entry.
979  $this->result = @ldap_search($this->connection,$this->people,$filterrecord,$attributes);
980 
981  // Pourquoi cette ligne ?
982  //$info = ldap_get_entries($this->connection, $this->result);
983 
984  // Only one entry should ever be returned (no user will have the same uid)
985  $entry = ldap_first_entry($this->connection, $this->result);
986 
987  if (!$entry)
988  {
989  $this->ldapErrorCode = -1;
990  $this->ldapErrorText = "Couldn't find user";
991  return false; // Couldn't find the user...
992  }
993 
994  // Get values
995  if (! $values = @ldap_get_values($this->connection, $entry, $attribute))
996  {
997  $this->ldapErrorCode = ldap_errno($this->connection);
998  $this->ldapErrorText = ldap_error($this->connection);
999  return false; // No matching attributes
1000  }
1001 
1002  // Return an array containing the attributes.
1003  return $values;
1004  }
1005 
1018  function getRecords($search, $userDn, $useridentifier, $attributeArray, $activefilter=0, $attributeAsArray=array())
1019  {
1020  $fulllist=array();
1021 
1022  dol_syslog(get_class($this)."::getRecords search=".$search." userDn=".$userDn." useridentifier=".$useridentifier." attributeArray=array(".join(',',$attributeArray).") activefilter=".$activefilter);
1023 
1024  // if the directory is AD, then bind first with the search user first
1025  if ($this->serverType == "activedirectory")
1026  {
1027  $this->bindauth($this->searchUser, $this->searchPassword);
1028  dol_syslog(get_class($this)."::bindauth serverType=activedirectory searchUser=".$this->searchUser);
1029  }
1030 
1031  // Define filter
1032  if (! empty($activefilter))
1033  {
1034  if (((string) $activefilter == '1' || (string) $activefilter == 'user') && $this->filter)
1035  {
1036  $filter = '('.$this->filter.')';
1037  }
1038  elseif (((string) $activefilter == 'member') && $this->filter)
1039  {
1040  $filter = '('.$this->filtermember.')';
1041  }
1042  else // If this->filter is empty, make fiter on * (all)
1043  {
1044  $filter = '('.$useridentifier.'=*)';
1045  }
1046  }
1047  else
1048  {
1049  $filter = '('.$useridentifier.'='.$search.')';
1050  }
1051 
1052  if (is_array($attributeArray))
1053  {
1054  // Return list with required fields
1055  $attributeArray=array_values($attributeArray); // This is to force to have index reordered from 0 (not make ldap_search fails)
1056  dol_syslog(get_class($this)."::getRecords connection=".$this->connection." userDn=".$userDn." filter=".$filter. " attributeArray=(".join(',',$attributeArray).")");
1057  //var_dump($attributeArray);
1058  $this->result = @ldap_search($this->connection, $userDn, $filter, $attributeArray);
1059  }
1060  else
1061  {
1062  // Return list with fields selected by default
1063  dol_syslog(get_class($this)."::getRecords connection=".$this->connection." userDn=".$userDn." filter=".$filter);
1064  $this->result = @ldap_search($this->connection, $userDn, $filter);
1065  }
1066  if (!$this->result)
1067  {
1068  $this->error = 'LDAP search failed: '.ldap_errno($this->connection)." ".ldap_error($this->connection);
1069  return -1;
1070  }
1071 
1072  $info = @ldap_get_entries($this->connection, $this->result);
1073 
1074  // Warning: Dans info, les noms d'attributs sont en minuscule meme si passe
1075  // a ldap_search en majuscule !!!
1076  //print_r($info);
1077 
1078  for ($i = 0; $i < $info["count"]; $i++)
1079  {
1080  $recordid=$this->convToOutputCharset($info[$i][$useridentifier][0],$this->ldapcharset);
1081  if ($recordid)
1082  {
1083  //print "Found record with key $useridentifier=".$recordid."<br>\n";
1084  $fulllist[$recordid][$useridentifier]=$recordid;
1085 
1086  // Add to the array for each attribute in my list
1087  $num = count($attributeArray);
1088  for ($j = 0; $j < $num; $j++)
1089  {
1090  $keyattributelower=strtolower($attributeArray[$j]);
1091  //print " Param ".$attributeArray[$j]."=".$info[$i][$keyattributelower][0]."<br>\n";
1092 
1093  //permet de recuperer le SID avec Active Directory
1094  if ($this->serverType == "activedirectory" && $keyattributelower == "objectsid")
1095  {
1096  $objectsid = $this->getObjectSid($recordid);
1097  $fulllist[$recordid][$attributeArray[$j]] = $objectsid;
1098  }
1099  else
1100  {
1101  if(in_array($attributeArray[$j], $attributeAsArray) && is_array($info[$i][$keyattributelower])) {
1102  $valueTab = array();
1103  foreach($info[$i][$keyattributelower] as $key => $value) {
1104  $valueTab[$key] = $this->convToOutputCharset($value,$this->ldapcharset);
1105  }
1106  $fulllist[$recordid][$attributeArray[$j]] = $valueTab;
1107  } else {
1108  $fulllist[$recordid][$attributeArray[$j]] = $this->convToOutputCharset($info[$i][$keyattributelower][0],$this->ldapcharset);
1109  }
1110  }
1111  }
1112  }
1113  }
1114 
1115  asort($fulllist);
1116  return $fulllist;
1117  }
1118 
1126  function littleEndian($hex)
1127  {
1128  for ($x=dol_strlen($hex)-2; $x >= 0; $x=$x-2) {
1129  $result .= substr($hex,$x,2);
1130  }
1131  return $result;
1132  }
1133 
1134 
1142  function getObjectSid($ldapUser)
1143  {
1144  $criteria = '('.$this->getUserIdentifier().'='.$ldapUser.')';
1145  $justthese = array("objectsid");
1146 
1147  // if the directory is AD, then bind first with the search user first
1148  if ($this->serverType == "activedirectory")
1149  {
1150  $this->bindauth($this->searchUser, $this->searchPassword);
1151  }
1152 
1153  $i = 0;
1154  $searchDN = $this->people;
1155 
1156  while ($i <= 2)
1157  {
1158  $ldapSearchResult = @ldap_search($this->connection, $searchDN, $criteria, $justthese);
1159 
1160  if (!$ldapSearchResult)
1161  {
1162  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1163  return -1;
1164  }
1165 
1166  $entry = ldap_first_entry($this->connection, $ldapSearchResult);
1167 
1168  if (!$entry)
1169  {
1170  // Si pas de resultat on cherche dans le domaine
1171  $searchDN = $this->domain;
1172  $i++;
1173  }
1174  else
1175  {
1176  $i++;
1177  $i++;
1178  }
1179  }
1180 
1181  if ($entry)
1182  {
1183  $ldapBinary = ldap_get_values_len($this->connection, $entry, "objectsid");
1184  $SIDText = $this->binSIDtoText($ldapBinary[0]);
1185  return $SIDText;
1186  }
1187  else
1188  {
1189  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1190  return '?';
1191  }
1192  }
1193 
1201  function binSIDtoText($binsid)
1202  {
1203  $hex_sid=bin2hex($binsid);
1204  $rev = hexdec(substr($hex_sid,0,2)); // Get revision-part of SID
1205  $subcount = hexdec(substr($hex_sid,2,2)); // Get count of sub-auth entries
1206  $auth = hexdec(substr($hex_sid,4,12)); // SECURITY_NT_AUTHORITY
1207  $result = "$rev-$auth";
1208  for ($x=0;$x < $subcount; $x++)
1209  {
1210  $result .= "-".hexdec($this->littleEndian(substr($hex_sid,16+($x*8),8))); // get all SECURITY_NT_AUTHORITY
1211  }
1212  return $result;
1213  }
1214 
1215 
1227  function search($checkDn, $filter)
1228  {
1229  dol_syslog(get_class($this)."::search checkDn=".$checkDn." filter=".$filter);
1230 
1231  $checkDn=$this->convFromOutputCharset($checkDn,$this->ldapcharset);
1232  $filter=$this->convFromOutputCharset($filter,$this->ldapcharset);
1233 
1234  // if the directory is AD, then bind first with the search user first
1235  if ($this->serverType == "activedirectory") {
1236  $this->bindauth($this->searchUser, $this->searchPassword);
1237  }
1238 
1239  $this->result = @ldap_search($this->connection, $checkDn, $filter);
1240 
1241  $result = @ldap_get_entries($this->connection, $this->result);
1242  if (! $result)
1243  {
1244  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1245  return -1;
1246  }
1247  else
1248  {
1249  ldap_free_result($this->result);
1250  return $result;
1251  }
1252  }
1253 
1254 
1263  function fetch($user,$filter)
1264  {
1265  // Perform the search and get the entry handles
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  $searchDN = $this->people; // TODO Why searching in people then domain ?
1273 
1274  $result = '';
1275  $i=0;
1276  while ($i <= 2)
1277  {
1278  dol_syslog(get_class($this)."::fetch search with searchDN=".$searchDN." filter=".$filter);
1279  $this->result = @ldap_search($this->connection, $searchDN, $filter);
1280  if ($this->result)
1281  {
1282  $result = @ldap_get_entries($this->connection, $this->result);
1283  if ($result['count'] > 0) dol_syslog('Ldap::fetch search found '.$result['count'].' records');
1284  else dol_syslog('Ldap::fetch search returns but found no records');
1285  //var_dump($result);exit;
1286  }
1287  else
1288  {
1289  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1290  dol_syslog(get_class($this)."::fetch search fails");
1291  return -1;
1292  }
1293 
1294  if (! $result)
1295  {
1296  // Si pas de resultat on cherche dans le domaine
1297  $searchDN = $this->domain;
1298  $i++;
1299  }
1300  else
1301  {
1302  break;
1303  }
1304  }
1305 
1306  if (! $result)
1307  {
1308  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1309  return -1;
1310  }
1311  else
1312  {
1313  $this->name = $this->convToOutputCharset($result[0][$this->attr_name][0],$this->ldapcharset);
1314  $this->firstname = $this->convToOutputCharset($result[0][$this->attr_firstname][0],$this->ldapcharset);
1315  $this->login = $this->convToOutputCharset($result[0][$this->attr_login][0],$this->ldapcharset);
1316  $this->phone = $this->convToOutputCharset($result[0][$this->attr_phone][0],$this->ldapcharset);
1317  $this->skype = $this->convToOutputCharset($result[0][$this->attr_skype][0],$this->ldapcharset);
1318  $this->fax = $this->convToOutputCharset($result[0][$this->attr_fax][0],$this->ldapcharset);
1319  $this->mail = $this->convToOutputCharset($result[0][$this->attr_mail][0],$this->ldapcharset);
1320  $this->mobile = $this->convToOutputCharset($result[0][$this->attr_mobile][0],$this->ldapcharset);
1321 
1322  $this->uacf = $this->parseUACF($this->convToOutputCharset($result[0]["useraccountcontrol"][0],$this->ldapcharset));
1323  if (isset($result[0]["pwdlastset"][0])) // If expiration on password exists
1324  {
1325  $this->pwdlastset = ($result[0]["pwdlastset"][0] != 0)?$this->convert_time($this->convToOutputCharset($result[0]["pwdlastset"][0],$this->ldapcharset)):0;
1326  }
1327  else
1328  {
1329  $this->pwdlastset = -1;
1330  }
1331  if (!$this->name && !$this->login) $this->pwdlastset = -1;
1332  $this->badpwdtime = $this->convert_time($this->convToOutputCharset($result[0]["badpasswordtime"][0],$this->ldapcharset));
1333 
1334  // FQDN domain
1335  $domain = str_replace('dc=','',$this->domain);
1336  $domain = str_replace(',','.',$domain);
1337  $this->domainFQDN = $domain;
1338 
1339  // Set ldapUserDn (each user can have a different dn)
1340  //var_dump($result[0]);exit;
1341  $this->ldapUserDN=$result[0]['dn'];
1342 
1343  ldap_free_result($this->result);
1344  return 1;
1345  }
1346  }
1347 
1348 
1349  // helper methods
1350 
1357  {
1358  if ($this->serverType == "activedirectory") {
1359  return $this->attr_sambalogin;
1360  } else {
1361  return $this->attr_login;
1362  }
1363  }
1364 
1371  function parseUACF($uacf)
1372  {
1373  //All flags array
1374  $flags = array(
1375  "TRUSTED_TO_AUTH_FOR_DELEGATION" => 16777216,
1376  "PASSWORD_EXPIRED" => 8388608,
1377  "DONT_REQ_PREAUTH" => 4194304,
1378  "USE_DES_KEY_ONLY" => 2097152,
1379  "NOT_DELEGATED" => 1048576,
1380  "TRUSTED_FOR_DELEGATION" => 524288,
1381  "SMARTCARD_REQUIRED" => 262144,
1382  "MNS_LOGON_ACCOUNT" => 131072,
1383  "DONT_EXPIRE_PASSWORD" => 65536,
1384  "SERVER_TRUST_ACCOUNT" => 8192,
1385  "WORKSTATION_TRUST_ACCOUNT" => 4096,
1386  "INTERDOMAIN_TRUST_ACCOUNT" => 2048,
1387  "NORMAL_ACCOUNT" => 512,
1388  "TEMP_DUPLICATE_ACCOUNT" => 256,
1389  "ENCRYPTED_TEXT_PWD_ALLOWED" => 128,
1390  "PASSWD_CANT_CHANGE" => 64,
1391  "PASSWD_NOTREQD" => 32,
1392  "LOCKOUT" => 16,
1393  "HOMEDIR_REQUIRED" => 8,
1394  "ACCOUNTDISABLE" => 2,
1395  "SCRIPT" => 1
1396  );
1397 
1398  //Parse flags to text
1399  $retval = array();
1400  while (list($flag, $val) = each($flags)) {
1401  if ($uacf >= $val) {
1402  $uacf -= $val;
1403  $retval[$val] = $flag;
1404  }
1405  }
1406 
1407  //Return human friendly flags
1408  return($retval);
1409  }
1410 
1417  function parseSAT($samtype)
1418  {
1419  $stypes = array(
1420  805306368 => "NORMAL_ACCOUNT",
1421  805306369 => "WORKSTATION_TRUST",
1422  805306370 => "INTERDOMAIN_TRUST",
1423  268435456 => "SECURITY_GLOBAL_GROUP",
1424  268435457 => "DISTRIBUTION_GROUP",
1425  536870912 => "SECURITY_LOCAL_GROUP",
1426  536870913 => "DISTRIBUTION_LOCAL_GROUP"
1427  );
1428 
1429  $retval = "";
1430  while (list($sat, $val) = each($stypes)) {
1431  if ($samtype == $sat) {
1432  $retval = $val;
1433  break;
1434  }
1435  }
1436  if (empty($retval)) $retval = "UNKNOWN_TYPE_" . $samtype;
1437 
1438  return($retval);
1439  }
1440 
1441  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1448  function convert_time($value)
1449  {
1450  // phpcs:enable
1451  $dateLargeInt=$value; // nano secondes depuis 1601 !!!!
1452  $secsAfterADEpoch = $dateLargeInt / (10000000); // secondes depuis le 1 jan 1601
1453  $ADToUnixConvertor=((1970-1601) * 365.242190) * 86400; // UNIX start date - AD start date * jours * secondes
1454  $unixTimeStamp=intval($secsAfterADEpoch-$ADToUnixConvertor); // Unix time stamp
1455  return $unixTimeStamp;
1456  }
1457 
1458 
1466  private function convToOutputCharset($str,$pagecodefrom='UTF-8')
1467  {
1468  global $conf;
1469  if ($pagecodefrom == 'ISO-8859-1' && $conf->file->character_set_client == 'UTF-8') $str=utf8_encode($str);
1470  if ($pagecodefrom == 'UTF-8' && $conf->file->character_set_client == 'ISO-8859-1') $str=utf8_decode($str);
1471  return $str;
1472  }
1473 
1481  function convFromOutputCharset($str,$pagecodeto='UTF-8')
1482  {
1483  global $conf;
1484  if ($pagecodeto == 'ISO-8859-1' && $conf->file->character_set_client == 'UTF-8') $str=utf8_decode($str);
1485  if ($pagecodeto == 'UTF-8' && $conf->file->character_set_client == 'ISO-8859-1') $str=utf8_encode($str);
1486  return $str;
1487  }
1488 
1489 
1496  function getNextGroupGid($keygroup='LDAP_KEY_GROUPS')
1497  {
1498  global $conf;
1499 
1500  if (empty($keygroup)) $keygroup='LDAP_KEY_GROUPS';
1501 
1502  $search='('.$conf->global->$keygroup.'=*)';
1503  $result = $this->search($this->groups,$search);
1504  if ($result)
1505  {
1506  $c = $result['count'];
1507  $gids = array();
1508  for($i=0;$i<$c;$i++)
1509  {
1510  $gids[] = $result[$i]['gidnumber'][0];
1511  }
1512  rsort($gids);
1513 
1514  return $gids[0]+1;
1515  }
1516 
1517  return 0;
1518  }
1519 }
setReferrals()
changement du referrals.
Definition: ldap.class.php:396
deleteAttribute($dn, $info, $user)
Delete a LDAP attribute in entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:878
update($dn, $info, $user, $olddn, $newrdn=false, $newparent=false)
Modify a LDAP entry (to use if dn != olddn) Ldap object connect and bind must have been done...
Definition: ldap.class.php:572
addAttribute($dn, $info, $user)
Add a LDAP attribute in entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:774
binSIDtoText($binsid)
Returns the textual SID Indispensable pour Active Directory.
add($dn, $info, $user)
Add a LDAP entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:413
modify($dn, $info, $user)
Modify a LDAP entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:467
$domain
Version du protocole ldap.
Definition: ldap.class.php:58
getVersion()
Verification de la version du serveur ldap.
Definition: ldap.class.php:372
$ldapErrorCode
Code erreur retourne par le serveur Ldap.
Definition: ldap.class.php:80
getAttribute($dn, $filter)
Returns an array containing attributes and values for first record.
Definition: ldap.class.php:928
$searchUser
User administrateur Ldap Active Directory ne supporte pas les connexions anonymes.
Definition: ldap.class.php:63
$ldapErrorText
Message texte de l&#39;erreur.
Definition: ldap.class.php:84
getAttributeValues($filterrecord, $attribute)
Returns an array containing values for an attribute and for first record matching filterrecord...
Definition: ldap.class.php:973
unbind()
Unbind du serveur ldap.
Definition: ldap.class.php:356
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition: repair.php:103
bind()
Anonymously binds to the connection.
Definition: ldap.class.php:311
parseUACF($uacf)
UserAccountControl Flgs to more human understandable form...
convToOutputCharset($str, $pagecodefrom='UTF-8')
Convert a string into output/memory charset.
convert_time($value)
Convertit le temps ActiveDirectory en Unix timestamp.
updateAttribute($dn, $info, $user)
Update a LDAP attribute in entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:826
$people
DN des utilisateurs.
Definition: ldap.class.php:72
getNextGroupGid($keygroup='LDAP_KEY_GROUPS')
Return available value of group GID.
$searchPassword
Mot de passe de l&#39;administrateur Active Directory ne supporte pas les connexions anonymes.
Definition: ldap.class.php:68
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='')
Write log message into outputs.
$result
Result of any connections etc.
Definition: ldap.class.php:110
dump($dn, $info)
Dump a LDAP message to ldapinput.in file.
Definition: ldap.class.php:710
serverPing($host, $port=389, $timeout=1)
Ping a server before ldap_connect for avoid waiting.
Definition: ldap.class.php:744
dump_content($dn, $info)
Build a LDAP message.
Definition: ldap.class.php:667
bindauth($bindDn, $pass)
Binds as an authenticated user, which usually allows for write access.
Definition: ldap.class.php:336
$groups
DN des groupes.
Definition: ldap.class.php:76
convFromOutputCharset($str, $pagecodeto='UTF-8')
Convert a string from output/memory charset.
getRecords($search, $userDn, $useridentifier, $attributeArray, $activefilter=0, $attributeAsArray=array())
Returns an array containing a details or list of LDAP record(s) ldapsearch -LLLx -hlocalhost -Dcn=adm...
parseSAT($samtype)
SamAccountType value to text.
$dn
Base DN (e.g.
Definition: ldap.class.php:50
__construct()
Constructor.
Definition: ldap.class.php:116
search($checkDn, $filter)
Fonction de recherche avec filtre this->connection doit etre defini donc la methode bind ou bindauth ...
littleEndian($hex)
Converts a little-endian hex-number to one, that &#39;hexdec&#39; can convert Required by Active Directory...
Class to manage LDAP features.
Definition: ldap.class.php:30
dol_mkdir($dir, $dataroot='', $newmask=null)
Creation of a directory (this can create recursive subdir)
close()
Simply closes the connection set up earlier.
Definition: ldap.class.php:293
rename($dn, $newrdn, $newparent, $user, $deleteoldrdn=true)
Rename a LDAP entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:521
fetch($user, $filter)
Load all attribute of a LDAP user.
setVersion()
Change ldap protocol version to use.
Definition: ldap.class.php:384
getObjectSid($ldapUser)
Recupere le SID de l&#39;utilisateur Required by Active Directory.
$server
Tableau des serveurs (IP addresses ou nom d&#39;hotes)
Definition: ldap.class.php:45
getUserIdentifier()
Returns the correct user identifier to use, based on the ldap server type.
$connection
The internal LDAP connection handle.
Definition: ldap.class.php:106
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
$serverType
type de serveur, actuellement OpenLdap et Active Directory
Definition: ldap.class.php:54
connect_bind()
Connect and bind Use this->server, this->serverPort, this->ldapProtocolVersion, this->serverType, this->searchUser, this->searchPassword After return, this->connection and $this->bind are defined.
Definition: ldap.class.php:160