"Fossies" - the Fresh Open Source Software Archive

Member "adLDAP-4.0.4/src/adLDAP.php" (13 Apr 2013, 31241 Bytes) of package /linux/www/old/adLDAP-4.0.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "adLDAP.php" see the Fossies "Dox" file reference documentation.

    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://www.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     {
  576         // You can specifically overide any of the default configuration options setup above
  577         if (count($options)>0){
  578             if (array_key_exists("account_suffix",$options)){ $this->accountSuffix = $options["account_suffix"]; }
  579             if (array_key_exists("base_dn",$options)){ $this->baseDn = $options["base_dn"]; }
  580             if (array_key_exists("domain_controllers",$options)){ 
  581                 if (!is_array($options["domain_controllers"])) { 
  582                     throw new adLDAPException('[domain_controllers] option must be an array');
  583                 }
  584                 $this->domainControllers = $options["domain_controllers"]; 
  585             }
  586             if (array_key_exists("admin_username",$options)){ $this->adminUsername = $options["admin_username"]; }
  587             if (array_key_exists("admin_password",$options)){ $this->adminPassword = $options["admin_password"]; }
  588             if (array_key_exists("real_primarygroup",$options)){ $this->realPrimaryGroup = $options["real_primarygroup"]; }
  589             if (array_key_exists("use_ssl",$options)){ $this->setUseSSL($options["use_ssl"]); }
  590             if (array_key_exists("use_tls",$options)){ $this->useTLS = $options["use_tls"]; }
  591             if (array_key_exists("recursive_groups",$options)){ $this->recursiveGroups = $options["recursive_groups"]; }
  592             if (array_key_exists("ad_port",$options)){ $this->setPort($options["ad_port"]); } 
  593             if (array_key_exists("sso",$options)){ 
  594                 $this->setUseSSO($options["sso"]);
  595                 if (!$this->ldapSaslSupported()) {
  596                     $this->setUseSSO(false);
  597                 }
  598             } 
  599         }
  600         
  601         if ($this->ldapSupported() === false) {
  602             throw new adLDAPException('No LDAP support for PHP.  See: http://www.php.net/ldap');
  603         }
  604 
  605         return $this->connect();
  606     }
  607 
  608     /**
  609     * Default Destructor
  610     * 
  611     * Closes the LDAP connection
  612     * 
  613     * @return void
  614     */
  615     function __destruct(){ 
  616         $this->close(); 
  617     }
  618 
  619     /**
  620     * Connects and Binds to the Domain Controller
  621     * 
  622     * @return bool
  623     */
  624     public function connect() 
  625     {
  626         // Connect to the AD/LDAP server as the username/password
  627         $domainController = $this->randomController();
  628         if ($this->useSSL) {
  629             $this->ldapConnection = ldap_connect("ldaps://" . $domainController, $this->adPort);
  630         } else {
  631             $this->ldapConnection = ldap_connect($domainController, $this->adPort);
  632         }
  633                
  634         // Set some ldap options for talking to AD
  635         ldap_set_option($this->ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3);
  636         ldap_set_option($this->ldapConnection, LDAP_OPT_REFERRALS, 0);
  637         
  638         if ($this->useTLS) {
  639             ldap_start_tls($this->ldapConnection);
  640         }
  641                
  642         // Bind as a domain admin if they've set it up
  643         if ($this->adminUsername !== NULL && $this->adminPassword !== NULL) {
  644             $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix, $this->adminPassword);
  645             if (!$this->ldapBind) {
  646                 if ($this->useSSL && !$this->useTLS) {
  647                     // If you have problems troubleshooting, remove the @ character from the ldapldapBind command above to get the actual error message
  648                     throw new adLDAPException('Bind to Active Directory failed. Either the LDAPs connection failed or the login credentials are incorrect. AD said: ' . $this->getLastError());
  649                 }
  650                 else {
  651                     throw new adLDAPException('Bind to Active Directory failed. Check the login credentials and/or server details. AD said: ' . $this->getLastError());
  652                 }
  653             }
  654         }
  655         if ($this->useSSO && $_SERVER['REMOTE_USER'] && $this->adminUsername === null && $_SERVER['KRB5CCNAME']) {
  656             putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);  
  657             $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI"); 
  658             if (!$this->ldapBind){ 
  659                 throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError()); 
  660             }
  661             else {
  662                 return true;
  663             }
  664         }
  665                 
  666         
  667         if ($this->baseDn == NULL) {
  668             $this->baseDn = $this->findBaseDn();   
  669         }
  670         
  671         return true;
  672     }
  673     
  674     /**
  675     * Closes the LDAP connection
  676     * 
  677     * @return void
  678     */
  679     public function close() {
  680         if ($this->ldapConnection) {
  681             @ldap_close($this->ldapConnection);
  682         }
  683     }
  684     
  685     /**
  686     * Validate a user's login credentials
  687     * 
  688     * @param string $username A user's AD username
  689     * @param string $password A user's AD password
  690     * @param bool optional $preventRebind
  691     * @return bool
  692     */
  693     public function authenticate($username, $password, $preventRebind = false) {
  694         // Prevent null binding
  695         if ($username === NULL || $password === NULL) { return false; } 
  696         if (empty($username) || empty($password)) { return false; }
  697         
  698         // Allow binding over SSO for Kerberos
  699         if ($this->useSSO && $_SERVER['REMOTE_USER'] && $_SERVER['REMOTE_USER'] == $username && $this->adminUsername === NULL && $_SERVER['KRB5CCNAME']) { 
  700             putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);
  701             $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI");
  702             if (!$this->ldapBind) {
  703                 throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
  704             }
  705             else {
  706                 return true;
  707             }
  708         }
  709         
  710         // Bind as the user        
  711         $ret = true;
  712         $this->ldapBind = @ldap_bind($this->ldapConnection, $username . $this->accountSuffix, $password);
  713         if (!$this->ldapBind){ 
  714             $ret = false; 
  715         }
  716         
  717         // Cnce we've checked their details, kick back into admin mode if we have it
  718         if ($this->adminUsername !== NULL && !$preventRebind) {
  719             $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix , $this->adminPassword);
  720             if (!$this->ldapBind){
  721                 // This should never happen in theory
  722                 throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
  723             } 
  724         }
  725         
  726         return $ret;
  727     }
  728     
  729     /**
  730     * Find the Base DN of your domain controller
  731     * 
  732     * @return string
  733     */
  734     public function findBaseDn() 
  735     {
  736         $namingContext = $this->getRootDse(array('defaultnamingcontext'));   
  737         return $namingContext[0]['defaultnamingcontext'][0];
  738     }
  739     
  740     /**
  741     * Get the RootDSE properties from a domain controller
  742     * 
  743     * @param array $attributes The attributes you wish to query e.g. defaultnamingcontext
  744     * @return array
  745     */
  746     public function getRootDse($attributes = array("*", "+")) {
  747         if (!$this->ldapBind){ return (false); }
  748         
  749         $sr = @ldap_read($this->ldapConnection, NULL, 'objectClass=*', $attributes);
  750         $entries = @ldap_get_entries($this->ldapConnection, $sr);
  751         return $entries;
  752     }
  753 
  754     /**
  755     * Get last error from Active Directory
  756     * 
  757     * This function gets the last message from Active Directory
  758     * This may indeed be a 'Success' message but if you get an unknown error
  759     * it might be worth calling this function to see what errors were raised
  760     * 
  761     * return string
  762     */
  763     public function getLastError() {
  764         return @ldap_error($this->ldapConnection);
  765     }
  766     
  767     /**
  768     * Detect LDAP support in php
  769     * 
  770     * @return bool
  771     */    
  772     protected function ldapSupported()
  773     {
  774         if (!function_exists('ldap_connect')) {
  775             return false;   
  776         }
  777         return true;
  778     }
  779     
  780     /**
  781     * Detect ldap_sasl_bind support in PHP
  782     * 
  783     * @return bool
  784     */
  785     protected function ldapSaslSupported()
  786     {
  787         if (!function_exists('ldap_sasl_bind')) {
  788             return false;
  789         }
  790         return true;
  791     }
  792     
  793     /**
  794     * Schema
  795     * 
  796     * @param array $attributes Attributes to be queried
  797     * @return array
  798     */    
  799     public function adldap_schema($attributes){
  800     
  801         // LDAP doesn't like NULL attributes, only set them if they have values
  802         // If you wish to remove an attribute you should set it to a space
  803         // TO DO: Adapt user_modify to use ldap_mod_delete to remove a NULL attribute
  804         $mod=array();
  805         
  806         // Check every attribute to see if it contains 8bit characters and then UTF8 encode them
  807         array_walk($attributes, array($this, 'encode8bit'));
  808 
  809         if ($attributes["address_city"]){ $mod["l"][0]=$attributes["address_city"]; }
  810         if ($attributes["address_code"]){ $mod["postalCode"][0]=$attributes["address_code"]; }
  811         //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes?
  812         if ($attributes["address_country"]){ $mod["c"][0]=$attributes["address_country"]; }
  813         if ($attributes["address_pobox"]){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; }
  814         if ($attributes["address_state"]){ $mod["st"][0]=$attributes["address_state"]; }
  815         if ($attributes["address_street"]){ $mod["streetAddress"][0]=$attributes["address_street"]; }
  816         if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; }
  817         if ($attributes["change_password"]){ $mod["pwdLastSet"][0]=0; }
  818         if ($attributes["department"]){ $mod["department"][0]=$attributes["department"]; }
  819         if ($attributes["description"]){ $mod["description"][0]=$attributes["description"]; }
  820         if ($attributes["display_name"]){ $mod["displayName"][0]=$attributes["display_name"]; }
  821         if ($attributes["email"]){ $mod["mail"][0]=$attributes["email"]; }
  822         if ($attributes["expires"]){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format?
  823         if ($attributes["firstname"]){ $mod["givenName"][0]=$attributes["firstname"]; }
  824         if ($attributes["home_directory"]){ $mod["homeDirectory"][0]=$attributes["home_directory"]; }
  825         if ($attributes["home_drive"]){ $mod["homeDrive"][0]=$attributes["home_drive"]; }
  826         if ($attributes["initials"]){ $mod["initials"][0]=$attributes["initials"]; }
  827         if ($attributes["logon_name"]){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; }
  828         if ($attributes["manager"]){ $mod["manager"][0]=$attributes["manager"]; }  //UNTESTED ***Use DistinguishedName***
  829         if ($attributes["office"]){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; }
  830         if ($attributes["password"]){ $mod["unicodePwd"][0]=$this->user()->encodePassword($attributes["password"]); }
  831         if ($attributes["profile_path"]){ $mod["profilepath"][0]=$attributes["profile_path"]; }
  832         if ($attributes["script_path"]){ $mod["scriptPath"][0]=$attributes["script_path"]; }
  833         if ($attributes["surname"]){ $mod["sn"][0]=$attributes["surname"]; }
  834         if ($attributes["title"]){ $mod["title"][0]=$attributes["title"]; }
  835         if ($attributes["telephone"]){ $mod["telephoneNumber"][0]=$attributes["telephone"]; }
  836         if ($attributes["mobile"]){ $mod["mobile"][0]=$attributes["mobile"]; }
  837         if ($attributes["pager"]){ $mod["pager"][0]=$attributes["pager"]; }
  838         if ($attributes["ipphone"]){ $mod["ipphone"][0]=$attributes["ipphone"]; }
  839         if ($attributes["web_page"]){ $mod["wWWHomePage"][0]=$attributes["web_page"]; }
  840         if ($attributes["fax"]){ $mod["facsimileTelephoneNumber"][0]=$attributes["fax"]; }
  841         if ($attributes["enabled"]){ $mod["userAccountControl"][0]=$attributes["enabled"]; }
  842         if ($attributes["homephone"]){ $mod["homephone"][0]=$attributes["homephone"]; }
  843         
  844         // Distribution List specific schema
  845         if ($attributes["group_sendpermission"]){ $mod["dlMemSubmitPerms"][0]=$attributes["group_sendpermission"]; }
  846         if ($attributes["group_rejectpermission"]){ $mod["dlMemRejectPerms"][0]=$attributes["group_rejectpermission"]; }
  847         
  848         // Exchange Schema
  849         if ($attributes["exchange_homemdb"]){ $mod["homeMDB"][0]=$attributes["exchange_homemdb"]; }
  850         if ($attributes["exchange_mailnickname"]){ $mod["mailNickname"][0]=$attributes["exchange_mailnickname"]; }
  851         if ($attributes["exchange_proxyaddress"]){ $mod["proxyAddresses"][0]=$attributes["exchange_proxyaddress"]; }
  852         if ($attributes["exchange_usedefaults"]){ $mod["mDBUseDefaults"][0]=$attributes["exchange_usedefaults"]; }
  853         if ($attributes["exchange_policyexclude"]){ $mod["msExchPoliciesExcluded"][0]=$attributes["exchange_policyexclude"]; }
  854         if ($attributes["exchange_policyinclude"]){ $mod["msExchPoliciesIncluded"][0]=$attributes["exchange_policyinclude"]; }       
  855         if ($attributes["exchange_addressbook"]){ $mod["showInAddressBook"][0]=$attributes["exchange_addressbook"]; }    
  856         if ($attributes["exchange_altrecipient"]){ $mod["altRecipient"][0]=$attributes["exchange_altrecipient"]; } 
  857         if ($attributes["exchange_deliverandredirect"]){ $mod["deliverAndRedirect"][0]=$attributes["exchange_deliverandredirect"]; }    
  858         
  859         // This schema is designed for contacts
  860         if ($attributes["exchange_hidefromlists"]){ $mod["msExchHideFromAddressLists"][0]=$attributes["exchange_hidefromlists"]; }
  861         if ($attributes["contact_email"]){ $mod["targetAddress"][0]=$attributes["contact_email"]; }
  862         
  863         //echo ("<pre>"); print_r($mod);
  864         /*
  865         // modifying a name is a bit fiddly
  866         if ($attributes["firstname"] && $attributes["surname"]){
  867             $mod["cn"][0]=$attributes["firstname"]." ".$attributes["surname"];
  868             $mod["displayname"][0]=$attributes["firstname"]." ".$attributes["surname"];
  869             $mod["name"][0]=$attributes["firstname"]." ".$attributes["surname"];
  870         }
  871         */
  872 
  873         if (count($mod)==0){ return (false); }
  874         return ($mod);
  875     }
  876     
  877     /**
  878     * Convert 8bit characters e.g. accented characters to UTF8 encoded characters
  879     */
  880     protected function encode8Bit(&$item, $key) {
  881         $encode = false;
  882         if (is_string($item)) {
  883             for ($i=0; $i<strlen($item); $i++) {
  884                 if (ord($item[$i]) >> 7) {
  885                     $encode = true;
  886                 }
  887             }
  888         }
  889         if ($encode === true && $key != 'password') {
  890             $item = utf8_encode($item);   
  891         }
  892     }
  893     
  894     /**
  895     * Select a random domain controller from your domain controller array
  896     * 
  897     * @return string
  898     */
  899     protected function randomController() 
  900     {
  901         mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions
  902         /*if (sizeof($this->domainControllers) > 1) {
  903             $adController = $this->domainControllers[array_rand($this->domainControllers)]; 
  904             // Test if the controller is responding to pings
  905             $ping = $this->pingController($adController); 
  906             if ($ping === false) { 
  907                 // Find the current key in the domain controllers array
  908                 $key = array_search($adController, $this->domainControllers);
  909                 // Remove it so that we don't end up in a recursive loop
  910                 unset($this->domainControllers[$key]);
  911                 // Select a new controller
  912                 return $this->randomController(); 
  913             }
  914             else { 
  915                 return ($adController); 
  916             }
  917         } */
  918         return $this->domainControllers[array_rand($this->domainControllers)];
  919     }  
  920     
  921     /** 
  922     * Test basic connectivity to controller 
  923     * 
  924     * @return bool
  925     */ 
  926     protected function pingController($host) {
  927         $port = $this->adPort; 
  928         fsockopen($host, $port, $errno, $errstr, 10); 
  929         if ($errno > 0) {
  930             return false;
  931         }
  932         return true;
  933     }
  934 
  935 }
  936 
  937 /**
  938 * adLDAP Exception Handler
  939 * 
  940 * Exceptions of this type are thrown on bind failure or when SSL is required but not configured
  941 * Example:
  942 * try {
  943 *   $adldap = new adLDAP();
  944 * }
  945 * catch (adLDAPException $e) {
  946 *   echo $e;
  947 *   exit();
  948 * }
  949 */
  950 class adLDAPException extends Exception {}
  951 
  952 ?>