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*/
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
58class 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*/
949class adLDAPException extends Exception {}
950