dolibarr  13.0.2
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 <https://www.gnu.org/licenses/>.
19  * or see https://www.gnu.org/
20  */
21 
30 class Ldap
31 {
35  public $error = '';
36 
40  public $errors = array();
41 
45  public $server = array();
46 
50  public $dn;
54  public $serverType;
62  public $domain;
67  public $searchUser;
76  public $people;
80  public $groups;
89 
90 
91  //Fetch user
92  public $name;
93  public $firstname;
94  public $login;
95  public $phone;
96  public $skype;
97  public $fax;
98  public $mail;
99  public $mobile;
100 
101  public $uacf;
102  public $pwdlastset;
103 
104  public $ldapcharset = 'UTF-8'; // LDAP should be UTF-8 encoded
105 
106 
110  public $connection;
114  public $result;
115 
116 
120  public function __construct()
121  {
122  global $conf;
123 
124  // Server
125  if (!empty($conf->global->LDAP_SERVER_HOST)) $this->server[] = $conf->global->LDAP_SERVER_HOST;
126  if (!empty($conf->global->LDAP_SERVER_HOST_SLAVE)) $this->server[] = $conf->global->LDAP_SERVER_HOST_SLAVE;
127  $this->serverPort = $conf->global->LDAP_SERVER_PORT;
128  $this->ldapProtocolVersion = $conf->global->LDAP_SERVER_PROTOCOLVERSION;
129  $this->dn = $conf->global->LDAP_SERVER_DN;
130  $this->serverType = $conf->global->LDAP_SERVER_TYPE;
131  $this->domain = $conf->global->LDAP_SERVER_DN;
132  $this->searchUser = $conf->global->LDAP_ADMIN_DN;
133  $this->searchPassword = $conf->global->LDAP_ADMIN_PASS;
134  $this->people = $conf->global->LDAP_USER_DN;
135  $this->groups = $conf->global->LDAP_GROUP_DN;
136 
137  $this->filter = $conf->global->LDAP_FILTER_CONNECTION; // Filter on user
138  $this->filtermember = $conf->global->LDAP_MEMBER_FILTER; // Filter on member
139 
140  // Users
141  $this->attr_login = $conf->global->LDAP_FIELD_LOGIN; //unix
142  $this->attr_sambalogin = $conf->global->LDAP_FIELD_LOGIN_SAMBA; //samba, activedirectory
143  $this->attr_name = $conf->global->LDAP_FIELD_NAME;
144  $this->attr_firstname = $conf->global->LDAP_FIELD_FIRSTNAME;
145  $this->attr_mail = $conf->global->LDAP_FIELD_MAIL;
146  $this->attr_phone = $conf->global->LDAP_FIELD_PHONE;
147  $this->attr_skype = $conf->global->LDAP_FIELD_SKYPE;
148  $this->attr_fax = $conf->global->LDAP_FIELD_FAX;
149  $this->attr_mobile = $conf->global->LDAP_FIELD_MOBILE;
150  }
151 
152 
153 
154  // Connection handling methods -------------------------------------------
155 
156  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
164  public function connect_bind()
165  {
166  // phpcs:enable
167  global $conf;
168 
169  $connected = 0;
170  $this->bind = 0;
171 
172  // Check parameters
173  if (count($this->server) == 0 || empty($this->server[0]))
174  {
175  $this->error = 'LDAP setup (file conf.php) is not complete';
176  dol_syslog(get_class($this)."::connect_bind ".$this->error, LOG_WARNING);
177  return -1;
178  }
179 
180  if (!function_exists("ldap_connect"))
181  {
182  $this->error = 'LDAPFunctionsNotAvailableOnPHP';
183  dol_syslog(get_class($this)."::connect_bind ".$this->error, LOG_WARNING);
184  $return = -1;
185  }
186 
187  if (empty($this->error))
188  {
189  // Loop on each ldap server
190  foreach ($this->server as $host)
191  {
192  if ($connected) break;
193  if (empty($host)) continue;
194 
195  if ($this->serverPing($host, $this->serverPort) === true) {
196  $this->connection = ldap_connect($host, $this->serverPort);
197  } else continue;
198 
199  if (is_resource($this->connection))
200  {
201  // Begin TLS if requested by the configuration
202  if (!empty($conf->global->LDAP_SERVER_USE_TLS))
203  {
204  if (!ldap_start_tls($this->connection))
205  {
206  dol_syslog(get_class($this)."::connect_bind failed to start tls", LOG_WARNING);
207  $connected = 0;
208  $this->close();
209  }
210  }
211 
212  // Execute the ldap_set_option here (after connect and before bind)
213  $this->setVersion();
214  ldap_set_option($this->connection, LDAP_OPT_SIZELIMIT, 0); // no limit here. should return true.
215 
216 
217  if ($this->serverType == "activedirectory")
218  {
219  $result = $this->setReferrals();
220  dol_syslog(get_class($this)."::connect_bind try bindauth for activedirectory on ".$host." user=".$this->searchUser." password=".preg_replace('/./', '*', $this->searchPassword), LOG_DEBUG);
221  $this->result = $this->bindauth($this->searchUser, $this->searchPassword);
222  if ($this->result)
223  {
224  $this->bind = $this->result;
225  $connected = 2;
226  break;
227  } else {
228  $this->error = ldap_errno($this->connection).' '.ldap_error($this->connection);
229  }
230  } else {
231  // Try in auth mode
232  if ($this->searchUser && $this->searchPassword)
233  {
234  dol_syslog(get_class($this)."::connect_bind try bindauth on ".$host." user=".$this->searchUser." password=".preg_replace('/./', '*', $this->searchPassword), LOG_DEBUG);
235  $this->result = $this->bindauth($this->searchUser, $this->searchPassword);
236  if ($this->result)
237  {
238  $this->bind = $this->result;
239  $connected = 2;
240  break;
241  } else {
242  $this->error = ldap_errno($this->connection).' '.ldap_error($this->connection);
243  }
244  }
245  // Try in anonymous
246  if (!$this->bind)
247  {
248  dol_syslog(get_class($this)."::connect_bind try bind on ".$host, LOG_DEBUG);
249  $result = $this->bind();
250  if ($result)
251  {
252  $this->bind = $this->result;
253  $connected = 1;
254  break;
255  } else {
256  $this->error = ldap_errno($this->connection).' '.ldap_error($this->connection);
257  }
258  }
259  }
260  }
261 
262  if (!$connected) $this->close();
263  }
264  }
265 
266  if ($connected)
267  {
268  $return = $connected;
269  dol_syslog(get_class($this)."::connect_bind return=".$return, LOG_DEBUG);
270  } else {
271  $this->error = 'Failed to connect to LDAP'.($this->error ? ': '.$this->error : '');
272  $return = -1;
273  dol_syslog(get_class($this)."::connect_bind return=".$return.' - '.$this->error, LOG_WARNING);
274  }
275  return $return;
276  }
277 
278 
279 
286  public function close()
287  {
288  if ($this->connection && !@ldap_close($this->connection))
289  {
290  return false;
291  } else {
292  return true;
293  }
294  }
295 
302  public function bind()
303  {
304  if (!$this->result = @ldap_bind($this->connection))
305  {
306  $this->ldapErrorCode = ldap_errno($this->connection);
307  $this->ldapErrorText = ldap_error($this->connection);
308  $this->error = $this->ldapErrorCode." ".$this->ldapErrorText;
309  return false;
310  } else {
311  return true;
312  }
313  }
314 
325  public function bindauth($bindDn, $pass)
326  {
327  if (!$this->result = @ldap_bind($this->connection, $bindDn, $pass))
328  {
329  $this->ldapErrorCode = ldap_errno($this->connection);
330  $this->ldapErrorText = ldap_error($this->connection);
331  $this->error = $this->ldapErrorCode." ".$this->ldapErrorText;
332  return false;
333  } else {
334  return true;
335  }
336  }
337 
343  public function unbind()
344  {
345  if (!$this->result = @ldap_unbind($this->connection))
346  {
347  return false;
348  } else {
349  return true;
350  }
351  }
352 
353 
359  public function getVersion()
360  {
361  $version = 0;
362  $version = @ldap_get_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $version);
363  return $version;
364  }
365 
371  public function setVersion()
372  {
373  // LDAP_OPT_PROTOCOL_VERSION est une constante qui vaut 17
374  $ldapsetversion = ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $this->ldapProtocolVersion);
375  return $ldapsetversion;
376  }
377 
383  public function setReferrals()
384  {
385  // LDAP_OPT_REFERRALS est une constante qui vaut ?
386  $ldapreferrals = ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
387  return $ldapreferrals;
388  }
389 
390 
400  public function add($dn, $info, $user)
401  {
402  dol_syslog(get_class($this)."::add dn=".$dn." info=".join(',', $info));
403 
404  // Check parameters
405  if (!$this->connection)
406  {
407  $this->error = "NotConnected";
408  return -2;
409  }
410  if (!$this->bind)
411  {
412  $this->error = "NotConnected";
413  return -3;
414  }
415 
416  // Encode to LDAP page code
417  $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
418  foreach ($info as $key => $val)
419  {
420  if (!is_array($val)) $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
421  }
422 
423  $this->dump($dn, $info);
424 
425  //print_r($info);
426  $result = @ldap_add($this->connection, $dn, $info);
427 
428  if ($result)
429  {
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 
450  public function modify($dn, $info, $user)
451  {
452  dol_syslog(get_class($this)."::modify dn=".$dn." info=".join(',', $info));
453 
454  // Check parameters
455  if (!$this->connection)
456  {
457  $this->error = "NotConnected";
458  return -2;
459  }
460  if (!$this->bind)
461  {
462  $this->error = "NotConnected";
463  return -3;
464  }
465 
466  // Encode to LDAP page code
467  $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
468  foreach ($info as $key => $val)
469  {
470  if (!is_array($val)) $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
471  }
472 
473  $this->dump($dn, $info);
474 
475  //print_r($info);
476 
477  // For better compatibility with Samba4 AD
478  if ($this->serverType == "activedirectory") {
479  unset($info['cn']); // To avoid error : Operation not allowed on RDN (Code 67)
480  }
481  $result = @ldap_modify($this->connection, $dn, $info);
482 
483  if ($result)
484  {
485  dol_syslog(get_class($this)."::modify successfull", LOG_DEBUG);
486  return 1;
487  } else {
488  $this->error = @ldap_error($this->connection);
489  dol_syslog(get_class($this)."::modify failed: ".$this->error, LOG_ERR);
490  return -1;
491  }
492  }
493 
505  public function rename($dn, $newrdn, $newparent, $user, $deleteoldrdn = true)
506  {
507  dol_syslog(get_class($this)."::modify dn=".$dn." newrdn=".$newrdn." newparent=".$newparent." deleteoldrdn=".($deleteoldrdn ? 1 : 0));
508 
509  // Check parameters
510  if (!$this->connection)
511  {
512  $this->error = "NotConnected";
513  return -2;
514  }
515  if (!$this->bind)
516  {
517  $this->error = "NotConnected";
518  return -3;
519  }
520 
521  // Encode to LDAP page code
522  $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
523  $newrdn = $this->convFromOutputCharset($newrdn, $this->ldapcharset);
524  $newparent = $this->convFromOutputCharset($newparent, $this->ldapcharset);
525 
526  //print_r($info);
527  $result = @ldap_rename($this->connection, $dn, $newrdn, $newparent, $deleteoldrdn);
528 
529  if ($result)
530  {
531  dol_syslog(get_class($this)."::rename successfull", LOG_DEBUG);
532  return 1;
533  } else {
534  $this->error = @ldap_error($this->connection);
535  dol_syslog(get_class($this)."::rename failed: ".$this->error, LOG_ERR);
536  return -1;
537  }
538  }
539 
552  public function update($dn, $info, $user, $olddn, $newrdn = false, $newparent = false)
553  {
554  dol_syslog(get_class($this)."::update dn=".$dn." olddn=".$olddn);
555 
556  // Check parameters
557  if (!$this->connection)
558  {
559  $this->error = "NotConnected";
560  return -2;
561  }
562  if (!$this->bind)
563  {
564  $this->error = "NotConnected";
565  return -3;
566  }
567 
568  if (!$olddn || $olddn != $dn)
569  {
570  if (!empty($olddn) && !empty($newrdn) && !empty($newparent) && $this->ldapProtocolVersion === '3')
571  {
572  // This function currently only works with LDAPv3
573  $result = $this->rename($olddn, $newrdn, $newparent, $user, true);
574  $result = $this->modify($dn, $info, $user); // We force "modify" for avoid some fields not modify
575  } else {
576  // If change we make is rename the key of LDAP record, we create new one and if ok, we delete old one.
577  $result = $this->add($dn, $info, $user);
578  if ($result > 0 && $olddn && $olddn != $dn) $result = $this->delete($olddn); // If add fails, we do not try to delete old one
579  }
580  } else {
581  //$result = $this->delete($olddn);
582  $result = $this->add($dn, $info, $user); // If record has been deleted from LDAP, we recreate it. We ignore error if it already exists.
583  $result = $this->modify($dn, $info, $user); // We use add/modify instead of delete/add when olddn is received
584  }
585  if ($result <= 0)
586  {
587  $this->error = ldap_error($this->connection).' (Code '.ldap_errno($this->connection).") ".$this->error;
588  dol_syslog(get_class($this)."::update ".$this->error, LOG_ERR);
589  //print_r($info);
590  return -1;
591  } else {
592  dol_syslog(get_class($this)."::update done successfully");
593  return 1;
594  }
595  }
596 
597 
605  public function delete($dn)
606  {
607  dol_syslog(get_class($this)."::delete Delete LDAP entry dn=".$dn);
608 
609  // Check parameters
610  if (!$this->connection)
611  {
612  $this->error = "NotConnected";
613  return -2;
614  }
615  if (!$this->bind)
616  {
617  $this->error = "NotConnected";
618  return -3;
619  }
620 
621  // Encode to LDAP page code
622  $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
623 
624  $result = @ldap_delete($this->connection, $dn);
625 
626  if ($result) return 1;
627  return -1;
628  }
629 
630  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
638  public function dump_content($dn, $info)
639  {
640  // phpcs:enable
641  $content = '';
642 
643  // Create file content
644  if (preg_match('/^ldap/', $this->server[0]))
645  {
646  $target = "-H ".join(',', $this->server);
647  } else {
648  $target = "-h ".join(',', $this->server)." -p ".$this->serverPort;
649  }
650  $content .= "# ldapadd $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
651  $content .= "# ldapmodify $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
652  $content .= "# ldapdelete $target -c -v -D ".$this->searchUser." -W -f ldapinput.in\n";
653  if (in_array('localhost', $this->server)) $content .= "# If commands fails to connect, try without -h and -p\n";
654  $content .= "dn: ".$dn."\n";
655  foreach ($info as $key => $value)
656  {
657  if (!is_array($value))
658  {
659  $content .= "$key: $value\n";
660  } else {
661  foreach ($value as $valuevalue)
662  {
663  $content .= "$key: $valuevalue\n";
664  }
665  }
666  }
667  return $content;
668  }
669 
677  public function dump($dn, $info)
678  {
679  global $conf;
680 
681  // Create content
682  $content = $this->dump_content($dn, $info);
683 
684  //Create file
685  $result = dol_mkdir($conf->ldap->dir_temp);
686 
687  $outputfile = $conf->ldap->dir_temp.'/ldapinput.in';
688  $fp = fopen($outputfile, "w");
689  if ($fp)
690  {
691  fputs($fp, $content);
692  fclose($fp);
693  if (!empty($conf->global->MAIN_UMASK))
694  @chmod($outputfile, octdec($conf->global->MAIN_UMASK));
695  return 1;
696  } else {
697  return -1;
698  }
699  }
700 
709  public function serverPing($host, $port = 389, $timeout = 1)
710  {
711  // Replace ldaps:// by ssl://
712  if (preg_match('/^ldaps:\/\/([^\/]+)\/?$/', $host, $regs)) {
713  $host = 'ssl://'.$regs[1];
714  }
715  // Remove ldap://
716  if (preg_match('/^ldap:\/\/([^\/]+)\/?$/', $host, $regs)) {
717  $host = $regs[1];
718  }
719  $op = @fsockopen($host, $port, $errno, $errstr, $timeout);
720  if (!$op) return false; //DC is N/A
721  else {
722  fclose($op); //explicitly close open socket connection
723  return true; //DC is up & running, we can safely connect with ldap_connect
724  }
725  }
726 
727 
728  // Attribute methods -----------------------------------------------------
729 
739  public function addAttribute($dn, $info, $user)
740  {
741  dol_syslog(get_class($this)."::addAttribute dn=".$dn." info=".join(',', $info));
742 
743  // Check parameters
744  if (!$this->connection)
745  {
746  $this->error = "NotConnected";
747  return -2;
748  }
749  if (!$this->bind)
750  {
751  $this->error = "NotConnected";
752  return -3;
753  }
754 
755  // Encode to LDAP page code
756  $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
757  foreach ($info as $key => $val)
758  {
759  if (!is_array($val)) $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
760  }
761 
762  $this->dump($dn, $info);
763 
764  //print_r($info);
765  $result = @ldap_mod_add($this->connection, $dn, $info);
766 
767  if ($result)
768  {
769  dol_syslog(get_class($this)."::add_attribute successfull", LOG_DEBUG);
770  return 1;
771  } else {
772  $this->error = @ldap_error($this->connection);
773  dol_syslog(get_class($this)."::add_attribute failed: ".$this->error, LOG_ERR);
774  return -1;
775  }
776  }
777 
787  public function updateAttribute($dn, $info, $user)
788  {
789  dol_syslog(get_class($this)."::updateAttribute dn=".$dn." info=".join(',', $info));
790 
791  // Check parameters
792  if (!$this->connection)
793  {
794  $this->error = "NotConnected";
795  return -2;
796  }
797  if (!$this->bind)
798  {
799  $this->error = "NotConnected";
800  return -3;
801  }
802 
803  // Encode to LDAP page code
804  $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
805  foreach ($info as $key => $val)
806  {
807  if (!is_array($val)) $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
808  }
809 
810  $this->dump($dn, $info);
811 
812  //print_r($info);
813  $result = @ldap_mod_replace($this->connection, $dn, $info);
814 
815  if ($result)
816  {
817  dol_syslog(get_class($this)."::updateAttribute successfull", LOG_DEBUG);
818  return 1;
819  } else {
820  $this->error = @ldap_error($this->connection);
821  dol_syslog(get_class($this)."::updateAttribute failed: ".$this->error, LOG_ERR);
822  return -1;
823  }
824  }
825 
835  public function deleteAttribute($dn, $info, $user)
836  {
837  dol_syslog(get_class($this)."::deleteAttribute dn=".$dn." info=".join(',', $info));
838 
839  // Check parameters
840  if (!$this->connection)
841  {
842  $this->error = "NotConnected";
843  return -2;
844  }
845  if (!$this->bind)
846  {
847  $this->error = "NotConnected";
848  return -3;
849  }
850 
851  // Encode to LDAP page code
852  $dn = $this->convFromOutputCharset($dn, $this->ldapcharset);
853  foreach ($info as $key => $val)
854  {
855  if (!is_array($val)) $info[$key] = $this->convFromOutputCharset($val, $this->ldapcharset);
856  }
857 
858  $this->dump($dn, $info);
859 
860  //print_r($info);
861  $result = @ldap_mod_del($this->connection, $dn, $info);
862 
863  if ($result)
864  {
865  dol_syslog(get_class($this)."::deleteAttribute successfull", LOG_DEBUG);
866  return 1;
867  } else {
868  $this->error = @ldap_error($this->connection);
869  dol_syslog(get_class($this)."::deleteAttribute failed: ".$this->error, LOG_ERR);
870  return -1;
871  }
872  }
873 
881  public function getAttribute($dn, $filter)
882  {
883  // Check parameters
884  if (!$this->connection)
885  {
886  $this->error = "NotConnected";
887  return -2;
888  }
889  if (!$this->bind)
890  {
891  $this->error = "NotConnected";
892  return -3;
893  }
894 
895  $search = ldap_search($this->connection, $dn, $filter);
896 
897  // Only one entry should ever be returned
898  $entry = ldap_first_entry($this->connection, $search);
899 
900  if (!$entry)
901  {
902  $this->ldapErrorCode = -1;
903  $this->ldapErrorText = "Couldn't find entry";
904  return 0; // Couldn't find entry...
905  }
906 
907  // Get values
908  if (!($values = ldap_get_attributes($this->connection, $entry)))
909  {
910  $this->ldapErrorCode = ldap_errno($this->connection);
911  $this->ldapErrorText = ldap_error($this->connection);
912  return 0; // No matching attributes
913  }
914 
915  // Return an array containing the attributes.
916  return $values;
917  }
918 
926  public function getAttributeValues($filterrecord, $attribute)
927  {
928  $attributes = array();
929  $attributes[0] = $attribute;
930 
931  // We need to search for this user in order to get their entry.
932  $this->result = @ldap_search($this->connection, $this->people, $filterrecord, $attributes);
933 
934  // Pourquoi cette ligne ?
935  //$info = ldap_get_entries($this->connection, $this->result);
936 
937  // Only one entry should ever be returned (no user will have the same uid)
938  $entry = ldap_first_entry($this->connection, $this->result);
939 
940  if (!$entry)
941  {
942  $this->ldapErrorCode = -1;
943  $this->ldapErrorText = "Couldn't find user";
944  return false; // Couldn't find the user...
945  }
946 
947  // Get values
948  if (!$values = @ldap_get_values($this->connection, $entry, $attribute))
949  {
950  $this->ldapErrorCode = ldap_errno($this->connection);
951  $this->ldapErrorText = ldap_error($this->connection);
952  return false; // No matching attributes
953  }
954 
955  // Return an array containing the attributes.
956  return $values;
957  }
958 
971  public function getRecords($search, $userDn, $useridentifier, $attributeArray, $activefilter = 0, $attributeAsArray = array())
972  {
973  $fulllist = array();
974 
975  dol_syslog(get_class($this)."::getRecords search=".$search." userDn=".$userDn." useridentifier=".$useridentifier." attributeArray=array(".join(',', $attributeArray).") activefilter=".$activefilter);
976 
977  // if the directory is AD, then bind first with the search user first
978  if ($this->serverType == "activedirectory")
979  {
980  $this->bindauth($this->searchUser, $this->searchPassword);
981  dol_syslog(get_class($this)."::bindauth serverType=activedirectory searchUser=".$this->searchUser);
982  }
983 
984  // Define filter
985  if (!empty($activefilter))
986  {
987  if (((string) $activefilter == '1' || (string) $activefilter == 'user') && $this->filter) {
988  $filter = '('.$this->filter.')';
989  } elseif (((string) $activefilter == 'member') && $this->filter) {
990  $filter = '('.$this->filtermember.')';
991  } else {
992  // If this->filter is empty, make fiter on * (all)
993  $filter = '('.$useridentifier.'=*)';
994  }
995  } else {
996  $filter = '('.$useridentifier.'='.$search.')';
997  }
998 
999  if (is_array($attributeArray))
1000  {
1001  // Return list with required fields
1002  $attributeArray = array_values($attributeArray); // This is to force to have index reordered from 0 (not make ldap_search fails)
1003  dol_syslog(get_class($this)."::getRecords connection=".$this->connection." userDn=".$userDn." filter=".$filter." attributeArray=(".join(',', $attributeArray).")");
1004  //var_dump($attributeArray);
1005  $this->result = @ldap_search($this->connection, $userDn, $filter, $attributeArray);
1006  } else {
1007  // Return list with fields selected by default
1008  dol_syslog(get_class($this)."::getRecords connection=".$this->connection." userDn=".$userDn." filter=".$filter);
1009  $this->result = @ldap_search($this->connection, $userDn, $filter);
1010  }
1011  if (!$this->result)
1012  {
1013  $this->error = 'LDAP search failed: '.ldap_errno($this->connection)." ".ldap_error($this->connection);
1014  return -1;
1015  }
1016 
1017  $info = @ldap_get_entries($this->connection, $this->result);
1018 
1019  // Warning: Dans info, les noms d'attributs sont en minuscule meme si passe
1020  // a ldap_search en majuscule !!!
1021  //print_r($info);
1022 
1023  for ($i = 0; $i < $info["count"]; $i++)
1024  {
1025  $recordid = $this->convToOutputCharset($info[$i][$useridentifier][0], $this->ldapcharset);
1026  if ($recordid)
1027  {
1028  //print "Found record with key $useridentifier=".$recordid."<br>\n";
1029  $fulllist[$recordid][$useridentifier] = $recordid;
1030 
1031  // Add to the array for each attribute in my list
1032  $num = count($attributeArray);
1033  for ($j = 0; $j < $num; $j++)
1034  {
1035  $keyattributelower = strtolower($attributeArray[$j]);
1036  //print " Param ".$attributeArray[$j]."=".$info[$i][$keyattributelower][0]."<br>\n";
1037 
1038  //permet de recuperer le SID avec Active Directory
1039  if ($this->serverType == "activedirectory" && $keyattributelower == "objectsid")
1040  {
1041  $objectsid = $this->getObjectSid($recordid);
1042  $fulllist[$recordid][$attributeArray[$j]] = $objectsid;
1043  } else {
1044  if (in_array($attributeArray[$j], $attributeAsArray) && is_array($info[$i][$keyattributelower])) {
1045  $valueTab = array();
1046  foreach ($info[$i][$keyattributelower] as $key => $value) {
1047  $valueTab[$key] = $this->convToOutputCharset($value, $this->ldapcharset);
1048  }
1049  $fulllist[$recordid][$attributeArray[$j]] = $valueTab;
1050  } else {
1051  $fulllist[$recordid][$attributeArray[$j]] = $this->convToOutputCharset($info[$i][$keyattributelower][0], $this->ldapcharset);
1052  }
1053  }
1054  }
1055  }
1056  }
1057 
1058  asort($fulllist);
1059  return $fulllist;
1060  }
1061 
1069  public function littleEndian($hex)
1070  {
1071  $result = '';
1072  for ($x = dol_strlen($hex) - 2; $x >= 0; $x = $x - 2) {
1073  $result .= substr($hex, $x, 2);
1074  }
1075  return $result;
1076  }
1077 
1078 
1086  public function getObjectSid($ldapUser)
1087  {
1088  $criteria = '('.$this->getUserIdentifier().'='.$ldapUser.')';
1089  $justthese = array("objectsid");
1090 
1091  // if the directory is AD, then bind first with the search user first
1092  if ($this->serverType == "activedirectory")
1093  {
1094  $this->bindauth($this->searchUser, $this->searchPassword);
1095  }
1096 
1097  $i = 0;
1098  $searchDN = $this->people;
1099 
1100  while ($i <= 2)
1101  {
1102  $ldapSearchResult = @ldap_search($this->connection, $searchDN, $criteria, $justthese);
1103 
1104  if (!$ldapSearchResult)
1105  {
1106  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1107  return -1;
1108  }
1109 
1110  $entry = ldap_first_entry($this->connection, $ldapSearchResult);
1111 
1112  if (!$entry)
1113  {
1114  // Si pas de resultat on cherche dans le domaine
1115  $searchDN = $this->domain;
1116  $i++;
1117  } else {
1118  $i++;
1119  $i++;
1120  }
1121  }
1122 
1123  if ($entry)
1124  {
1125  $ldapBinary = ldap_get_values_len($this->connection, $entry, "objectsid");
1126  $SIDText = $this->binSIDtoText($ldapBinary[0]);
1127  return $SIDText;
1128  } else {
1129  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1130  return '?';
1131  }
1132  }
1133 
1141  public function binSIDtoText($binsid)
1142  {
1143  $hex_sid = bin2hex($binsid);
1144  $rev = hexdec(substr($hex_sid, 0, 2)); // Get revision-part of SID
1145  $subcount = hexdec(substr($hex_sid, 2, 2)); // Get count of sub-auth entries
1146  $auth = hexdec(substr($hex_sid, 4, 12)); // SECURITY_NT_AUTHORITY
1147  $result = "$rev-$auth";
1148  for ($x = 0; $x < $subcount; $x++)
1149  {
1150  $result .= "-".hexdec($this->littleEndian(substr($hex_sid, 16 + ($x * 8), 8))); // get all SECURITY_NT_AUTHORITY
1151  }
1152  return $result;
1153  }
1154 
1155 
1167  public function search($checkDn, $filter)
1168  {
1169  dol_syslog(get_class($this)."::search checkDn=".$checkDn." filter=".$filter);
1170 
1171  $checkDn = $this->convFromOutputCharset($checkDn, $this->ldapcharset);
1172  $filter = $this->convFromOutputCharset($filter, $this->ldapcharset);
1173 
1174  // if the directory is AD, then bind first with the search user first
1175  if ($this->serverType == "activedirectory") {
1176  $this->bindauth($this->searchUser, $this->searchPassword);
1177  }
1178 
1179  $this->result = @ldap_search($this->connection, $checkDn, $filter);
1180 
1181  $result = @ldap_get_entries($this->connection, $this->result);
1182  if (!$result)
1183  {
1184  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1185  return -1;
1186  } else {
1187  ldap_free_result($this->result);
1188  return $result;
1189  }
1190  }
1191 
1192 
1201  public function fetch($user, $filter)
1202  {
1203  // Perform the search and get the entry handles
1204 
1205  // if the directory is AD, then bind first with the search user first
1206  if ($this->serverType == "activedirectory") {
1207  $this->bindauth($this->searchUser, $this->searchPassword);
1208  }
1209 
1210  $searchDN = $this->people; // TODO Why searching in people then domain ?
1211 
1212  $result = '';
1213  $i = 0;
1214  while ($i <= 2)
1215  {
1216  dol_syslog(get_class($this)."::fetch search with searchDN=".$searchDN." filter=".$filter);
1217  $this->result = @ldap_search($this->connection, $searchDN, $filter);
1218  if ($this->result)
1219  {
1220  $result = @ldap_get_entries($this->connection, $this->result);
1221  if ($result['count'] > 0) dol_syslog('Ldap::fetch search found '.$result['count'].' records');
1222  else dol_syslog('Ldap::fetch search returns but found no records');
1223  //var_dump($result);exit;
1224  } else {
1225  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1226  dol_syslog(get_class($this)."::fetch search fails");
1227  return -1;
1228  }
1229 
1230  if (!$result)
1231  {
1232  // Si pas de resultat on cherche dans le domaine
1233  $searchDN = $this->domain;
1234  $i++;
1235  } else {
1236  break;
1237  }
1238  }
1239 
1240  if (!$result)
1241  {
1242  $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection);
1243  return -1;
1244  } else {
1245  $this->name = $this->convToOutputCharset($result[0][$this->attr_name][0], $this->ldapcharset);
1246  $this->firstname = $this->convToOutputCharset($result[0][$this->attr_firstname][0], $this->ldapcharset);
1247  $this->login = $this->convToOutputCharset($result[0][$this->attr_login][0], $this->ldapcharset);
1248  $this->phone = $this->convToOutputCharset($result[0][$this->attr_phone][0], $this->ldapcharset);
1249  $this->skype = $this->convToOutputCharset($result[0][$this->attr_skype][0], $this->ldapcharset);
1250  $this->fax = $this->convToOutputCharset($result[0][$this->attr_fax][0], $this->ldapcharset);
1251  $this->mail = $this->convToOutputCharset($result[0][$this->attr_mail][0], $this->ldapcharset);
1252  $this->mobile = $this->convToOutputCharset($result[0][$this->attr_mobile][0], $this->ldapcharset);
1253 
1254  $this->uacf = $this->parseUACF($this->convToOutputCharset($result[0]["useraccountcontrol"][0], $this->ldapcharset));
1255  if (isset($result[0]["pwdlastset"][0])) // If expiration on password exists
1256  {
1257  $this->pwdlastset = ($result[0]["pwdlastset"][0] != 0) ? $this->convert_time($this->convToOutputCharset($result[0]["pwdlastset"][0], $this->ldapcharset)) : 0;
1258  } else {
1259  $this->pwdlastset = -1;
1260  }
1261  if (!$this->name && !$this->login) $this->pwdlastset = -1;
1262  $this->badpwdtime = $this->convert_time($this->convToOutputCharset($result[0]["badpasswordtime"][0], $this->ldapcharset));
1263 
1264  // FQDN domain
1265  $domain = str_replace('dc=', '', $this->domain);
1266  $domain = str_replace(',', '.', $domain);
1267  $this->domainFQDN = $domain;
1268 
1269  // Set ldapUserDn (each user can have a different dn)
1270  //var_dump($result[0]);exit;
1271  $this->ldapUserDN = $result[0]['dn'];
1272 
1273  ldap_free_result($this->result);
1274  return 1;
1275  }
1276  }
1277 
1278 
1279  // helper methods
1280 
1286  public function getUserIdentifier()
1287  {
1288  if ($this->serverType == "activedirectory") {
1289  return $this->attr_sambalogin;
1290  } else {
1291  return $this->attr_login;
1292  }
1293  }
1294 
1301  public function parseUACF($uacf)
1302  {
1303  //All flags array
1304  $flags = array(
1305  "TRUSTED_TO_AUTH_FOR_DELEGATION" => 16777216,
1306  "PASSWORD_EXPIRED" => 8388608,
1307  "DONT_REQ_PREAUTH" => 4194304,
1308  "USE_DES_KEY_ONLY" => 2097152,
1309  "NOT_DELEGATED" => 1048576,
1310  "TRUSTED_FOR_DELEGATION" => 524288,
1311  "SMARTCARD_REQUIRED" => 262144,
1312  "MNS_LOGON_ACCOUNT" => 131072,
1313  "DONT_EXPIRE_PASSWORD" => 65536,
1314  "SERVER_TRUST_ACCOUNT" => 8192,
1315  "WORKSTATION_TRUST_ACCOUNT" => 4096,
1316  "INTERDOMAIN_TRUST_ACCOUNT" => 2048,
1317  "NORMAL_ACCOUNT" => 512,
1318  "TEMP_DUPLICATE_ACCOUNT" => 256,
1319  "ENCRYPTED_TEXT_PWD_ALLOWED" => 128,
1320  "PASSWD_CANT_CHANGE" => 64,
1321  "PASSWD_NOTREQD" => 32,
1322  "LOCKOUT" => 16,
1323  "HOMEDIR_REQUIRED" => 8,
1324  "ACCOUNTDISABLE" => 2,
1325  "SCRIPT" => 1
1326  );
1327 
1328  //Parse flags to text
1329  $retval = array();
1330  //while (list($flag, $val) = each($flags)) {
1331  foreach ($flags as $flag => $val) {
1332  if ($uacf >= $val) {
1333  $uacf -= $val;
1334  $retval[$val] = $flag;
1335  }
1336  }
1337 
1338  //Return human friendly flags
1339  return($retval);
1340  }
1341 
1348  public function parseSAT($samtype)
1349  {
1350  $stypes = array(
1351  805306368 => "NORMAL_ACCOUNT",
1352  805306369 => "WORKSTATION_TRUST",
1353  805306370 => "INTERDOMAIN_TRUST",
1354  268435456 => "SECURITY_GLOBAL_GROUP",
1355  268435457 => "DISTRIBUTION_GROUP",
1356  536870912 => "SECURITY_LOCAL_GROUP",
1357  536870913 => "DISTRIBUTION_LOCAL_GROUP"
1358  );
1359 
1360  $retval = "";
1361  while (list($sat, $val) = each($stypes)) {
1362  if ($samtype == $sat) {
1363  $retval = $val;
1364  break;
1365  }
1366  }
1367  if (empty($retval)) $retval = "UNKNOWN_TYPE_".$samtype;
1368 
1369  return($retval);
1370  }
1371 
1372  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1379  public function convert_time($value)
1380  {
1381  // phpcs:enable
1382  $dateLargeInt = $value; // nano secondes depuis 1601 !!!!
1383  $secsAfterADEpoch = $dateLargeInt / (10000000); // secondes depuis le 1 jan 1601
1384  $ADToUnixConvertor = ((1970 - 1601) * 365.242190) * 86400; // UNIX start date - AD start date * jours * secondes
1385  $unixTimeStamp = intval($secsAfterADEpoch - $ADToUnixConvertor); // Unix time stamp
1386  return $unixTimeStamp;
1387  }
1388 
1389 
1397  private function convToOutputCharset($str, $pagecodefrom = 'UTF-8')
1398  {
1399  global $conf;
1400  if ($pagecodefrom == 'ISO-8859-1' && $conf->file->character_set_client == 'UTF-8') $str = utf8_encode($str);
1401  if ($pagecodefrom == 'UTF-8' && $conf->file->character_set_client == 'ISO-8859-1') $str = utf8_decode($str);
1402  return $str;
1403  }
1404 
1412  public function convFromOutputCharset($str, $pagecodeto = 'UTF-8')
1413  {
1414  global $conf;
1415  if ($pagecodeto == 'ISO-8859-1' && $conf->file->character_set_client == 'UTF-8') $str = utf8_decode($str);
1416  if ($pagecodeto == 'UTF-8' && $conf->file->character_set_client == 'ISO-8859-1') $str = utf8_encode($str);
1417  return $str;
1418  }
1419 
1420 
1427  public function getNextGroupGid($keygroup = 'LDAP_KEY_GROUPS')
1428  {
1429  global $conf;
1430 
1431  if (empty($keygroup)) $keygroup = 'LDAP_KEY_GROUPS';
1432 
1433  $search = '('.$conf->global->$keygroup.'=*)';
1434  $result = $this->search($this->groups, $search);
1435  if ($result)
1436  {
1437  $c = $result['count'];
1438  $gids = array();
1439  for ($i = 0; $i < $c; $i++)
1440  {
1441  $gids[] = $result[$i]['gidnumber'][0];
1442  }
1443  rsort($gids);
1444 
1445  return $gids[0] + 1;
1446  }
1447 
1448  return 0;
1449  }
1450 }
setReferrals()
changement du referrals.
Definition: ldap.class.php:383
deleteAttribute($dn, $info, $user)
Delete a LDAP attribute in entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:835
getNextGroupGid($keygroup= 'LDAP_KEY_GROUPS')
Return available value of group GID.
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:552
addAttribute($dn, $info, $user)
Add a LDAP attribute in entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:739
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:400
modify($dn, $info, $user)
Modify a LDAP entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:450
$domain
Server DN.
Definition: ldap.class.php:62
getVersion()
Verification de la version du serveur ldap.
Definition: ldap.class.php:359
$ldapErrorCode
Code erreur retourne par le serveur Ldap.
Definition: ldap.class.php:84
getAttribute($dn, $filter)
Returns an array containing attributes and values for first record.
Definition: ldap.class.php:881
$searchUser
User administrateur Ldap Active Directory ne supporte pas les connexions anonymes.
Definition: ldap.class.php:67
$ldapErrorText
Message texte de l&#39;erreur.
Definition: ldap.class.php:88
getAttributeValues($filterrecord, $attribute)
Returns an array containing values for an attribute and for first record matching filterrecord...
Definition: ldap.class.php:926
unbind()
Unbind du serveur ldap.
Definition: ldap.class.php:343
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition: repair.php:108
bind()
Anonymously binds to the connection.
Definition: ldap.class.php:302
parseUACF($uacf)
UserAccountControl Flgs to more human understandable form...
convFromOutputCharset($str, $pagecodeto= 'UTF-8')
Convert a string from output/memory charset.
convert_time($value)
Convertit le temps ActiveDirectory en Unix timestamp.
$ldapProtocolVersion
Version du protocole ldap.
Definition: ldap.class.php:58
updateAttribute($dn, $info, $user)
Update a LDAP attribute in entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:787
$people
DN des utilisateurs.
Definition: ldap.class.php:76
$searchPassword
Mot de passe de l&#39;administrateur Active Directory ne supporte pas les connexions anonymes.
Definition: ldap.class.php:72
$result
Result of any connections etc.
Definition: ldap.class.php:114
convToOutputCharset($str, $pagecodefrom= 'UTF-8')
Convert a string into output/memory charset.
dump($dn, $info)
Dump a LDAP message to ldapinput.in file.
Definition: ldap.class.php:677
serverPing($host, $port=389, $timeout=1)
Ping a server before ldap_connect for avoid waiting.
Definition: ldap.class.php:709
dol_strlen($string, $stringencoding= 'UTF-8')
Make a strlen call.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename= '', $restricttologhandler= '', $logcontext=null)
Write log message into outputs.
dump_content($dn, $info)
Build a LDAP message.
Definition: ldap.class.php:638
bindauth($bindDn, $pass)
Binds as an authenticated user, which usually allows for write access.
Definition: ldap.class.php:325
$groups
DN des groupes.
Definition: ldap.class.php:80
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...
Definition: ldap.class.php:971
parseSAT($samtype)
SamAccountType value to text.
$dn
Base DN (e.g.
Definition: ldap.class.php:50
__construct()
Constructor.
Definition: ldap.class.php:120
search($checkDn, $filter)
Fonction de recherche avec filtre this-&gt;connection doit etre defini donc la methode bind ou bindauth d...
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
close()
Simply closes the connection set up earlier.
Definition: ldap.class.php:286
rename($dn, $newrdn, $newparent, $user, $deleteoldrdn=true)
Rename a LDAP entry Ldap object connect and bind must have been done.
Definition: ldap.class.php:505
fetch($user, $filter)
Load all attribute of a LDAP user.
setVersion()
Change ldap protocol version to use.
Definition: ldap.class.php:371
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:110
$serverType
type de serveur, actuellement OpenLdap et Active Directory
Definition: ldap.class.php:54
dol_mkdir($dir, $dataroot= '', $newmask=null)
Creation of a directory (this can create recursive subdir)
connect_bind()
Connect and bind Use this-&gt;server, this-&gt;serverPort, this-&gt;ldapProtocolVersion, this-&gt;serverType, this-&gt;searchUser, this-&gt;searchPassword After return, this-&gt;connection and $this-&gt;bind are defined.
Definition: ldap.class.php:164