[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/lib/plugins/authad/adLDAP/ -> adLDAP.php (source)

   1  <?php
   2  /**
   3   * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY 
   4   * Version 4.0.4
   5   * 
   6   * PHP Version 5 with SSL and LDAP support
   7   * 
   8   * Written by Scott Barnett, Richard Hyland
   9   *   email: scott@wiggumworld.com, adldap@richardhyland.com
  10   *   http://adldap.sourceforge.net/
  11   * 
  12   * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
  13   * 
  14   * We'd appreciate any improvements or additions to be submitted back
  15   * to benefit the entire community :)
  16   * 
  17   * This library is free software; you can redistribute it and/or
  18   * modify it under the terms of the GNU Lesser General Public
  19   * License as published by the Free Software Foundation; either
  20   * version 2.1 of the License.
  21   * 
  22   * This library is distributed in the hope that it will be useful,
  23   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  25   * Lesser General Public License for more details.
  26   * 
  27   * @category ToolsAndUtilities
  28   * @package adLDAP
  29   * @author Scott Barnett, Richard Hyland
  30   * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
  31   * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
  32   * @revision $Revision: 169 $
  33   * @version 4.0.4
  34   * @link http://adldap.sourceforge.net/
  35   */
  36  
  37  /**
  38  * Main adLDAP class
  39  * 
  40  * Can be initialised using $adldap = new adLDAP();
  41  * 
  42  * Something to keep in mind is that Active Directory is a permissions
  43  * based directory. If you bind as a domain user, you can't fetch as
  44  * much information on other users as you could as a domain admin.
  45  * 
  46  * Before asking questions, please read the Documentation at
  47  * http://adldap.sourceforge.net/wiki/doku.php?id=api
  48  */
  49  require_once(dirname(__FILE__) . '/collections/adLDAPCollection.php');
  50  require_once(dirname(__FILE__) . '/classes/adLDAPGroups.php');
  51  require_once(dirname(__FILE__) . '/classes/adLDAPUsers.php');
  52  require_once(dirname(__FILE__) . '/classes/adLDAPFolders.php');
  53  require_once(dirname(__FILE__) . '/classes/adLDAPUtils.php');
  54  require_once(dirname(__FILE__) . '/classes/adLDAPContacts.php');
  55  require_once(dirname(__FILE__) . '/classes/adLDAPExchange.php');
  56  require_once(dirname(__FILE__) . '/classes/adLDAPComputers.php');
  57  
  58  class adLDAP {
  59      
  60      /**
  61       * Define the different types of account in AD
  62       */
  63      const ADLDAP_NORMAL_ACCOUNT = 805306368;
  64      const ADLDAP_WORKSTATION_TRUST = 805306369;
  65      const ADLDAP_INTERDOMAIN_TRUST = 805306370;
  66      const ADLDAP_SECURITY_GLOBAL_GROUP = 268435456;
  67      const ADLDAP_DISTRIBUTION_GROUP = 268435457;
  68      const ADLDAP_SECURITY_LOCAL_GROUP = 536870912;
  69      const ADLDAP_DISTRIBUTION_LOCAL_GROUP = 536870913;
  70      const ADLDAP_FOLDER = 'OU';
  71      const ADLDAP_CONTAINER = 'CN';
  72      
  73      /**
  74      * The default port for LDAP non-SSL connections
  75      */
  76      const ADLDAP_LDAP_PORT = '389';
  77      /**
  78      * The default port for LDAPS SSL connections
  79      */
  80      const ADLDAP_LDAPS_PORT = '636';
  81      
  82      /**
  83      * The account suffix for your domain, can be set when the class is invoked
  84      * 
  85      * @var string
  86      */   
  87          protected $accountSuffix = "@mydomain.local";
  88      
  89      /**
  90      * The base dn for your domain
  91      * 
  92      * If this is set to null then adLDAP will attempt to obtain this automatically from the rootDSE
  93      * 
  94      * @var string
  95      */
  96          protected $baseDn = "DC=mydomain,DC=local"; 
  97      
  98      /** 
  99      * Port used to talk to the domain controllers. 
 100      *  
 101      * @var int 
 102      */ 
 103      protected $adPort = self::ADLDAP_LDAP_PORT; 
 104          
 105      /**
 106      * Array of domain controllers. Specifiy multiple controllers if you
 107      * would like the class to balance the LDAP queries amongst multiple servers
 108      * 
 109      * @var array
 110      */
 111      protected $domainControllers = array("dc01.mydomain.local");
 112          
 113      /**
 114      * Optional account with higher privileges for searching
 115      * This should be set to a domain admin account
 116      * 
 117      * @var string
 118      * @var string
 119      */
 120          protected $adminUsername = NULL;
 121      protected $adminPassword = NULL;
 122      
 123      /**
 124      * AD does not return the primary group. http://support.microsoft.com/?kbid=321360
 125      * This tweak will resolve the real primary group. 
 126      * Setting to false will fudge "Domain Users" and is much faster. Keep in mind though that if
 127      * someone's primary group is NOT domain users, this is obviously going to mess up the results
 128      * 
 129      * @var bool
 130      */
 131          protected $realPrimaryGroup = true;
 132          
 133      /**
 134      * Use SSL (LDAPS), your server needs to be setup, please see
 135      * http://adldap.sourceforge.net/wiki/doku.php?id=ldap_over_ssl
 136      * 
 137      * @var bool
 138      */
 139          protected $useSSL = false;
 140      
 141      /**
 142      * Use TLS
 143      * If you wish to use TLS you should ensure that $useSSL is set to false and vice-versa
 144      * 
 145      * @var bool
 146      */
 147      protected $useTLS = false;
 148      
 149      /**
 150      * Use SSO  
 151      * To indicate to adLDAP to reuse password set by the brower through NTLM or Kerberos 
 152      * 
 153      * @var bool
 154      */
 155      protected $useSSO = false;
 156      
 157      /**
 158      * When querying group memberships, do it recursively 
 159      * eg. User Fred is a member of Group A, which is a member of Group B, which is a member of Group C
 160      * user_ingroup("Fred","C") will returns true with this option turned on, false if turned off     
 161      * 
 162      * @var bool
 163      */
 164          protected $recursiveGroups = true;
 165          
 166          // You should not need to edit anything below this line
 167          //******************************************************************************************
 168          
 169          /**
 170      * Connection and bind default variables
 171      * 
 172      * @var mixed
 173      * @var mixed
 174      */
 175          protected $ldapConnection;
 176          protected $ldapBind;
 177      
 178      /**
 179      * Get the active LDAP Connection
 180      * 
 181      * @return resource
 182      */
 183      public function getLdapConnection() {
 184          if ($this->ldapConnection) {
 185              return $this->ldapConnection;   
 186          }
 187          return false;
 188      }
 189      
 190      /**
 191      * Get the bind status
 192      * 
 193      * @return bool
 194      */
 195      public function getLdapBind() {
 196          return $this->ldapBind;
 197      }
 198      
 199      /**
 200      * Get the current base DN
 201      * 
 202      * @return string
 203      */
 204      public function getBaseDn() {
 205          return $this->baseDn;   
 206      }
 207      
 208      /**
 209      * The group class
 210      * 
 211      * @var adLDAPGroups
 212      */
 213      protected $groupClass;
 214      
 215      /**
 216      * Get the group class interface
 217      * 
 218      * @return adLDAPGroups
 219      */
 220      public function group() {
 221          if (!$this->groupClass) {
 222              $this->groupClass = new adLDAPGroups($this);
 223          }   
 224          return $this->groupClass;
 225      }
 226      
 227      /**
 228      * The user class
 229      * 
 230      * @var adLDAPUsers
 231      */
 232      protected $userClass;
 233      
 234      /**
 235      * Get the userclass interface
 236      * 
 237      * @return adLDAPUsers
 238      */
 239      public function user() {
 240          if (!$this->userClass) {
 241              $this->userClass = new adLDAPUsers($this);
 242          }   
 243          return $this->userClass;
 244      }
 245      
 246      /**
 247      * The folders class
 248      * 
 249      * @var adLDAPFolders
 250      */
 251      protected $folderClass;
 252      
 253      /**
 254      * Get the folder class interface
 255      * 
 256      * @return adLDAPFolders
 257      */
 258      public function folder() {
 259          if (!$this->folderClass) {
 260              $this->folderClass = new adLDAPFolders($this);
 261          }   
 262          return $this->folderClass;
 263      }
 264      
 265      /**
 266      * The utils class
 267      * 
 268      * @var adLDAPUtils
 269      */
 270      protected $utilClass;
 271      
 272      /**
 273      * Get the utils class interface
 274      * 
 275      * @return adLDAPUtils
 276      */
 277      public function utilities() {
 278          if (!$this->utilClass) {
 279              $this->utilClass = new adLDAPUtils($this);
 280          }   
 281          return $this->utilClass;
 282      }
 283      
 284      /**
 285      * The contacts class
 286      * 
 287      * @var adLDAPContacts
 288      */
 289      protected $contactClass;
 290      
 291      /**
 292      * Get the contacts class interface
 293      * 
 294      * @return adLDAPContacts
 295      */
 296      public function contact() {
 297          if (!$this->contactClass) {
 298              $this->contactClass = new adLDAPContacts($this);
 299          }   
 300          return $this->contactClass;
 301      }
 302      
 303      /**
 304      * The exchange class
 305      * 
 306      * @var adLDAPExchange
 307      */
 308      protected $exchangeClass;
 309      
 310      /**
 311      * Get the exchange class interface
 312      * 
 313      * @return adLDAPExchange
 314      */
 315      public function exchange() {
 316          if (!$this->exchangeClass) {
 317              $this->exchangeClass = new adLDAPExchange($this);
 318          }   
 319          return $this->exchangeClass;
 320      }
 321      
 322      /**
 323      * The computers class
 324      * 
 325      * @var adLDAPComputers
 326      */
 327      protected $computersClass;
 328      
 329      /**
 330      * Get the computers class interface
 331      * 
 332      * @return adLDAPComputers
 333      */
 334      public function computer() {
 335          if (!$this->computerClass) {
 336              $this->computerClass = new adLDAPComputers($this);
 337          }   
 338          return $this->computerClass;
 339      }
 340  
 341      /**
 342      * Getters and Setters
 343      */
 344      
 345      /**
 346      * Set the account suffix
 347      * 
 348      * @param string $accountSuffix
 349      * @return void
 350      */
 351      public function setAccountSuffix($accountSuffix)
 352      {
 353            $this->accountSuffix = $accountSuffix;
 354      }
 355  
 356      /**
 357      * Get the account suffix
 358      * 
 359      * @return string
 360      */
 361      public function getAccountSuffix()
 362      {
 363            return $this->accountSuffix;
 364      }
 365      
 366      /**
 367      * Set the domain controllers array
 368      * 
 369      * @param array $domainControllers
 370      * @return void
 371      */
 372      public function setDomainControllers(array $domainControllers)
 373      {
 374            $this->domainControllers = $domainControllers;
 375      }
 376  
 377      /**
 378      * Get the list of domain controllers
 379      * 
 380      * @return void
 381      */
 382      public function getDomainControllers()
 383      {
 384            return $this->domainControllers;
 385      }
 386      
 387      /**
 388      * Sets the port number your domain controller communicates over
 389      * 
 390      * @param int $adPort
 391      */
 392      public function setPort($adPort) 
 393      { 
 394          $this->adPort = $adPort; 
 395      } 
 396      
 397      /**
 398      * Gets the port number your domain controller communicates over
 399      * 
 400      * @return int
 401      */
 402      public function getPort() 
 403      { 
 404          return $this->adPort; 
 405      } 
 406      
 407      /**
 408      * Set the username of an account with higher priviledges
 409      * 
 410      * @param string $adminUsername
 411      * @return void
 412      */
 413      public function setAdminUsername($adminUsername)
 414      {
 415            $this->adminUsername = $adminUsername;
 416      }
 417  
 418      /**
 419      * Get the username of the account with higher priviledges
 420      * 
 421      * This will throw an exception for security reasons
 422      */
 423      public function getAdminUsername()
 424      {
 425            throw new adLDAPException('For security reasons you cannot access the domain administrator account details');
 426      }
 427      
 428      /**
 429      * Set the password of an account with higher priviledges
 430      * 
 431      * @param string $adminPassword
 432      * @return void
 433      */
 434      public function setAdminPassword($adminPassword)
 435      {
 436            $this->adminPassword = $adminPassword;
 437      }
 438  
 439      /**
 440      * Get the password of the account with higher priviledges
 441      * 
 442      * This will throw an exception for security reasons
 443      */
 444      public function getAdminPassword()
 445      {
 446            throw new adLDAPException('For security reasons you cannot access the domain administrator account details');
 447      }
 448      
 449      /**
 450      * Set whether to detect the true primary group
 451      * 
 452      * @param bool $realPrimaryGroup
 453      * @return void
 454      */
 455      public function setRealPrimaryGroup($realPrimaryGroup)
 456      {
 457            $this->realPrimaryGroup = $realPrimaryGroup;
 458      }
 459  
 460      /**
 461      * Get the real primary group setting
 462      * 
 463      * @return bool
 464      */
 465      public function getRealPrimaryGroup()
 466      {
 467            return $this->realPrimaryGroup;
 468      }
 469      
 470      /**
 471      * Set whether to use SSL
 472      * 
 473      * @param bool $useSSL
 474      * @return void
 475      */
 476      public function setUseSSL($useSSL)
 477      {
 478            $this->useSSL = $useSSL;
 479            // Set the default port correctly 
 480            if($this->useSSL) { 
 481              $this->setPort(self::ADLDAP_LDAPS_PORT); 
 482            }
 483            else { 
 484              $this->setPort(self::ADLDAP_LDAP_PORT); 
 485            } 
 486      }
 487  
 488      /**
 489      * Get the SSL setting
 490      * 
 491      * @return bool
 492      */
 493      public function getUseSSL()
 494      {
 495            return $this->useSSL;
 496      }
 497      
 498      /**
 499      * Set whether to use TLS
 500      * 
 501      * @param bool $useTLS
 502      * @return void
 503      */
 504      public function setUseTLS($useTLS)
 505      {
 506            $this->useTLS = $useTLS;
 507      }
 508  
 509      /**
 510      * Get the TLS setting
 511      * 
 512      * @return bool
 513      */
 514      public function getUseTLS()
 515      {
 516            return $this->useTLS;
 517      }
 518      
 519      /**
 520      * Set whether to use SSO
 521      * Requires ldap_sasl_bind support. Be sure --with-ldap-sasl is used when configuring PHP otherwise this function will be undefined. 
 522      * 
 523      * @param bool $useSSO
 524      * @return void
 525      */
 526      public function setUseSSO($useSSO)
 527      {
 528            if ($useSSO === true && !$this->ldapSaslSupported()) {
 529                throw new adLDAPException('No LDAP SASL support for PHP.  See: http://php.net/ldap_sasl_bind');
 530            }
 531            $this->useSSO = $useSSO;
 532      }
 533  
 534      /**
 535      * Get the SSO setting
 536      * 
 537      * @return bool
 538      */
 539      public function getUseSSO()
 540      {
 541            return $this->useSSO;
 542      }
 543      
 544      /**
 545      * Set whether to lookup recursive groups
 546      * 
 547      * @param bool $recursiveGroups
 548      * @return void
 549      */
 550      public function setRecursiveGroups($recursiveGroups)
 551      {
 552            $this->recursiveGroups = $recursiveGroups;
 553      }
 554  
 555      /**
 556      * Get the recursive groups setting
 557      * 
 558      * @return bool
 559      */
 560      public function getRecursiveGroups()
 561      {
 562            return $this->recursiveGroups;
 563      }
 564  
 565      /**
 566      * Default Constructor
 567      * 
 568      * Tries to bind to the AD domain over LDAP or LDAPs
 569      * 
 570      * @param array $options Array of options to pass to the constructor
 571      * @throws Exception - if unable to bind to Domain Controller
 572      * @return bool
 573      */
 574      function __construct($options = array()) {
 575          // You can specifically overide any of the default configuration options setup above
 576          if (count($options) > 0) {
 577              if (array_key_exists("account_suffix",$options)){ $this->accountSuffix = $options["account_suffix"]; }
 578              if (array_key_exists("base_dn",$options)){ $this->baseDn = $options["base_dn"]; }
 579              if (array_key_exists("domain_controllers",$options)){ 
 580                  if (!is_array($options["domain_controllers"])) { 
 581                      throw new adLDAPException('[domain_controllers] option must be an array');
 582                  }
 583                  $this->domainControllers = $options["domain_controllers"]; 
 584              }
 585              if (array_key_exists("admin_username",$options)){ $this->adminUsername = $options["admin_username"]; }
 586              if (array_key_exists("admin_password",$options)){ $this->adminPassword = $options["admin_password"]; }
 587              if (array_key_exists("real_primarygroup",$options)){ $this->realPrimaryGroup = $options["real_primarygroup"]; }
 588              if (array_key_exists("use_ssl",$options)){ $this->setUseSSL($options["use_ssl"]); }
 589              if (array_key_exists("use_tls",$options)){ $this->useTLS = $options["use_tls"]; }
 590              if (array_key_exists("recursive_groups",$options)){ $this->recursiveGroups = $options["recursive_groups"]; }
 591              if (array_key_exists("ad_port",$options)){ $this->setPort($options["ad_port"]); } 
 592              if (array_key_exists("sso",$options)) { 
 593                  $this->setUseSSO($options["sso"]);
 594                  if (!$this->ldapSaslSupported()) {
 595                      $this->setUseSSO(false);
 596                  }
 597              } 
 598          }
 599          
 600          if ($this->ldapSupported() === false) {
 601              throw new adLDAPException('No LDAP support for PHP.  See: http://php.net/ldap');
 602          }
 603  
 604          return $this->connect();
 605      }
 606  
 607      /**
 608      * Default Destructor
 609      * 
 610      * Closes the LDAP connection
 611      * 
 612      * @return void
 613      */
 614      function __destruct() { 
 615          $this->close(); 
 616      }
 617  
 618      /**
 619      * Connects and Binds to the Domain Controller
 620      * 
 621      * @return bool
 622      */
 623      public function connect() 
 624      {
 625          // Connect to the AD/LDAP server as the username/password
 626          $domainController = $this->randomController();
 627          if ($this->useSSL) {
 628              $this->ldapConnection = ldap_connect("ldaps://" . $domainController, $this->adPort);
 629          } else {
 630              $this->ldapConnection = ldap_connect($domainController, $this->adPort);
 631          }
 632                 
 633          // Set some ldap options for talking to AD
 634          ldap_set_option($this->ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3);
 635          ldap_set_option($this->ldapConnection, LDAP_OPT_REFERRALS, 0);
 636          
 637          if ($this->useTLS) {
 638              ldap_start_tls($this->ldapConnection);
 639          }
 640                 
 641          // Bind as a domain admin if they've set it up
 642          if ($this->adminUsername !== NULL && $this->adminPassword !== NULL) {
 643              $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix, $this->adminPassword);
 644              if (!$this->ldapBind) {
 645                  if ($this->useSSL && !$this->useTLS) {
 646                      // If you have problems troubleshooting, remove the @ character from the ldapldapBind command above to get the actual error message
 647                      throw new adLDAPException('Bind to Active Directory failed. Either the LDAPs connection failed or the login credentials are incorrect. AD said: ' . $this->getLastError());
 648                  }
 649                  else {
 650                      throw new adLDAPException('Bind to Active Directory failed. Check the login credentials and/or server details. AD said: ' . $this->getLastError());
 651                  }
 652              }
 653          }
 654          if ($this->useSSO && $_SERVER['REMOTE_USER'] && $this->adminUsername === null && $_SERVER['KRB5CCNAME']) {
 655              putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);  
 656              $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI"); 
 657              if (!$this->ldapBind){ 
 658                  throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError()); 
 659              }
 660              else {
 661                  return true;
 662              }
 663          }
 664                  
 665          
 666          if ($this->baseDn == NULL) {
 667              $this->baseDn = $this->findBaseDn();   
 668          }
 669          
 670          return true;
 671      }
 672      
 673      /**
 674      * Closes the LDAP connection
 675      * 
 676      * @return void
 677      */
 678      public function close() {
 679          if ($this->ldapConnection) {
 680              @ldap_close($this->ldapConnection);
 681          }
 682      }
 683      
 684      /**
 685      * Validate a user's login credentials
 686      * 
 687      * @param string $username A user's AD username
 688      * @param string $password A user's AD password
 689      * @param bool optional $preventRebind
 690      * @return bool
 691      */
 692      public function authenticate($username, $password, $preventRebind = false) {
 693          // Prevent null binding
 694          if ($username === NULL || $password === NULL) { return false; } 
 695          if (empty($username) || empty($password)) { return false; }
 696          
 697          // Allow binding over SSO for Kerberos
 698          if ($this->useSSO && $_SERVER['REMOTE_USER'] && $_SERVER['REMOTE_USER'] == $username && $this->adminUsername === NULL && $_SERVER['KRB5CCNAME']) { 
 699              putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);
 700              $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI");
 701              if (!$this->ldapBind) {
 702                  throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
 703              }
 704              else {
 705                  return true;
 706              }
 707          }
 708          
 709          // Bind as the user        
 710          $ret = true;
 711          $this->ldapBind = @ldap_bind($this->ldapConnection, $username . $this->accountSuffix, $password);
 712          if (!$this->ldapBind){ 
 713              $ret = false; 
 714          }
 715          
 716          // Cnce we've checked their details, kick back into admin mode if we have it
 717          if ($this->adminUsername !== NULL && !$preventRebind) {
 718              $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix , $this->adminPassword);
 719              if (!$this->ldapBind){
 720                  // This should never happen in theory
 721                  throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
 722              } 
 723          } 
 724          
 725          return $ret;
 726      }
 727      
 728      /**
 729      * Find the Base DN of your domain controller
 730      * 
 731      * @return string
 732      */
 733      public function findBaseDn() 
 734      {
 735          $namingContext = $this->getRootDse(array('defaultnamingcontext'));   
 736          return $namingContext[0]['defaultnamingcontext'][0];
 737      }
 738      
 739      /**
 740      * Get the RootDSE properties from a domain controller
 741      * 
 742      * @param array $attributes The attributes you wish to query e.g. defaultnamingcontext
 743      * @return array
 744      */
 745      public function getRootDse($attributes = array("*", "+")) {
 746          if (!$this->ldapBind){ return (false); }
 747          
 748          $sr = @ldap_read($this->ldapConnection, NULL, 'objectClass=*', $attributes);
 749          $entries = @ldap_get_entries($this->ldapConnection, $sr);
 750          return $entries;
 751      }
 752  
 753      /**
 754      * Get last error from Active Directory
 755      * 
 756      * This function gets the last message from Active Directory
 757      * This may indeed be a 'Success' message but if you get an unknown error
 758      * it might be worth calling this function to see what errors were raised
 759      * 
 760      * return string
 761      */
 762      public function getLastError() {
 763          return @ldap_error($this->ldapConnection);
 764      }
 765      
 766      /**
 767      * Detect LDAP support in php
 768      * 
 769      * @return bool
 770      */    
 771      protected function ldapSupported()
 772      {
 773          if (!function_exists('ldap_connect')) {
 774              return false;   
 775          }
 776          return true;
 777      }
 778      
 779      /**
 780      * Detect ldap_sasl_bind support in PHP
 781      * 
 782      * @return bool
 783      */
 784      protected function ldapSaslSupported()
 785      {
 786          if (!function_exists('ldap_sasl_bind')) {
 787              return false;
 788          }
 789          return true;
 790      }
 791      
 792      /**
 793      * Schema
 794      * 
 795      * @param array $attributes Attributes to be queried
 796      * @return array
 797      */    
 798      public function adldap_schema($attributes){
 799      
 800          // LDAP doesn't like NULL attributes, only set them if they have values
 801          // If you wish to remove an attribute you should set it to a space
 802          // TO DO: Adapt user_modify to use ldap_mod_delete to remove a NULL attribute
 803          $mod=array();
 804          
 805          // Check every attribute to see if it contains 8bit characters and then UTF8 encode them
 806          array_walk($attributes, array($this, 'encode8bit'));
 807  
 808          if ($attributes["address_city"]){ $mod["l"][0]=$attributes["address_city"]; }
 809          if ($attributes["address_code"]){ $mod["postalCode"][0]=$attributes["address_code"]; }
 810          //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes?
 811          if ($attributes["address_country"]){ $mod["c"][0]=$attributes["address_country"]; }
 812          if ($attributes["address_pobox"]){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; }
 813          if ($attributes["address_state"]){ $mod["st"][0]=$attributes["address_state"]; }
 814          if ($attributes["address_street"]){ $mod["streetAddress"][0]=$attributes["address_street"]; }
 815          if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; }
 816          if ($attributes["change_password"]){ $mod["pwdLastSet"][0]=0; }
 817          if ($attributes["department"]){ $mod["department"][0]=$attributes["department"]; }
 818          if ($attributes["description"]){ $mod["description"][0]=$attributes["description"]; }
 819          if ($attributes["display_name"]){ $mod["displayName"][0]=$attributes["display_name"]; }
 820          if ($attributes["email"]){ $mod["mail"][0]=$attributes["email"]; }
 821          if ($attributes["expires"]){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format?
 822          if ($attributes["firstname"]){ $mod["givenName"][0]=$attributes["firstname"]; }
 823          if ($attributes["home_directory"]){ $mod["homeDirectory"][0]=$attributes["home_directory"]; }
 824          if ($attributes["home_drive"]){ $mod["homeDrive"][0]=$attributes["home_drive"]; }
 825          if ($attributes["initials"]){ $mod["initials"][0]=$attributes["initials"]; }
 826          if ($attributes["logon_name"]){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; }
 827          if ($attributes["manager"]){ $mod["manager"][0]=$attributes["manager"]; }  //UNTESTED ***Use DistinguishedName***
 828          if ($attributes["office"]){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; }
 829          if ($attributes["password"]){ $mod["unicodePwd"][0]=$this->user()->encodePassword($attributes["password"]); }
 830          if ($attributes["profile_path"]){ $mod["profilepath"][0]=$attributes["profile_path"]; }
 831          if ($attributes["script_path"]){ $mod["scriptPath"][0]=$attributes["script_path"]; }
 832          if ($attributes["surname"]){ $mod["sn"][0]=$attributes["surname"]; }
 833          if ($attributes["title"]){ $mod["title"][0]=$attributes["title"]; }
 834          if ($attributes["telephone"]){ $mod["telephoneNumber"][0]=$attributes["telephone"]; }
 835          if ($attributes["mobile"]){ $mod["mobile"][0]=$attributes["mobile"]; }
 836          if ($attributes["pager"]){ $mod["pager"][0]=$attributes["pager"]; }
 837          if ($attributes["ipphone"]){ $mod["ipphone"][0]=$attributes["ipphone"]; }
 838          if ($attributes["web_page"]){ $mod["wWWHomePage"][0]=$attributes["web_page"]; }
 839          if ($attributes["fax"]){ $mod["facsimileTelephoneNumber"][0]=$attributes["fax"]; }
 840          if ($attributes["enabled"]){ $mod["userAccountControl"][0]=$attributes["enabled"]; }
 841          if ($attributes["homephone"]){ $mod["homephone"][0]=$attributes["homephone"]; }
 842          
 843          // Distribution List specific schema
 844          if ($attributes["group_sendpermission"]){ $mod["dlMemSubmitPerms"][0]=$attributes["group_sendpermission"]; }
 845          if ($attributes["group_rejectpermission"]){ $mod["dlMemRejectPerms"][0]=$attributes["group_rejectpermission"]; }
 846          
 847          // Exchange Schema
 848          if ($attributes["exchange_homemdb"]){ $mod["homeMDB"][0]=$attributes["exchange_homemdb"]; }
 849          if ($attributes["exchange_mailnickname"]){ $mod["mailNickname"][0]=$attributes["exchange_mailnickname"]; }
 850          if ($attributes["exchange_proxyaddress"]){ $mod["proxyAddresses"][0]=$attributes["exchange_proxyaddress"]; }
 851          if ($attributes["exchange_usedefaults"]){ $mod["mDBUseDefaults"][0]=$attributes["exchange_usedefaults"]; }
 852          if ($attributes["exchange_policyexclude"]){ $mod["msExchPoliciesExcluded"][0]=$attributes["exchange_policyexclude"]; }
 853          if ($attributes["exchange_policyinclude"]){ $mod["msExchPoliciesIncluded"][0]=$attributes["exchange_policyinclude"]; }       
 854          if ($attributes["exchange_addressbook"]){ $mod["showInAddressBook"][0]=$attributes["exchange_addressbook"]; }    
 855          if ($attributes["exchange_altrecipient"]){ $mod["altRecipient"][0]=$attributes["exchange_altrecipient"]; } 
 856          if ($attributes["exchange_deliverandredirect"]){ $mod["deliverAndRedirect"][0]=$attributes["exchange_deliverandredirect"]; }    
 857          
 858          // This schema is designed for contacts
 859          if ($attributes["exchange_hidefromlists"]){ $mod["msExchHideFromAddressLists"][0]=$attributes["exchange_hidefromlists"]; }
 860          if ($attributes["contact_email"]){ $mod["targetAddress"][0]=$attributes["contact_email"]; }
 861          
 862          //echo ("<pre>"); print_r($mod);
 863          /*
 864          // modifying a name is a bit fiddly
 865          if ($attributes["firstname"] && $attributes["surname"]){
 866              $mod["cn"][0]=$attributes["firstname"]." ".$attributes["surname"];
 867              $mod["displayname"][0]=$attributes["firstname"]." ".$attributes["surname"];
 868              $mod["name"][0]=$attributes["firstname"]." ".$attributes["surname"];
 869          }
 870          */
 871  
 872          if (count($mod)==0){ return (false); }
 873          return ($mod);
 874      }
 875      
 876      /**
 877      * Convert 8bit characters e.g. accented characters to UTF8 encoded characters
 878      */
 879      protected function encode8Bit(&$item, $key) {
 880          $encode = false;
 881          if (is_string($item)) {
 882              for ($i=0; $i<strlen($item); $i++) {
 883                  if (ord($item[$i]) >> 7) {
 884                      $encode = true;
 885                  }
 886              }
 887          }
 888          if ($encode === true && $key != 'password') {
 889              $item = utf8_encode($item);   
 890          }
 891      }
 892      
 893      /**
 894      * Select a random domain controller from your domain controller array
 895      * 
 896      * @return string
 897      */
 898      protected function randomController() 
 899      {
 900          mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions
 901          /*if (sizeof($this->domainControllers) > 1) {
 902              $adController = $this->domainControllers[array_rand($this->domainControllers)]; 
 903              // Test if the controller is responding to pings
 904              $ping = $this->pingController($adController); 
 905              if ($ping === false) { 
 906                  // Find the current key in the domain controllers array
 907                  $key = array_search($adController, $this->domainControllers);
 908                  // Remove it so that we don't end up in a recursive loop
 909                  unset($this->domainControllers[$key]);
 910                  // Select a new controller
 911                  return $this->randomController(); 
 912              }
 913              else { 
 914                  return ($adController); 
 915              }
 916          } */
 917          return $this->domainControllers[array_rand($this->domainControllers)];
 918      }  
 919      
 920      /** 
 921      * Test basic connectivity to controller 
 922      * 
 923      * @return bool
 924      */ 
 925      protected function pingController($host) {
 926          $port = $this->adPort; 
 927          fsockopen($host, $port, $errno, $errstr, 10); 
 928          if ($errno > 0) {
 929              return false;
 930          }
 931          return true;
 932      }
 933  
 934  }
 935  
 936  /**
 937  * adLDAP Exception Handler
 938  * 
 939  * Exceptions of this type are thrown on bind failure or when SSL is required but not configured
 940  * Example:
 941  * try {
 942  *   $adldap = new adLDAP();
 943  * }
 944  * catch (adLDAPException $e) {
 945  *   echo $e;
 946  *   exit();
 947  * }
 948  */
 949  class adLDAPException extends Exception {}