PageRenderTime 210ms CodeModel.GetById 61ms app.highlight 70ms RepoModel.GetById 70ms app.codeStats 1ms

/functions/adLDAP/src/adLDAP.php

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