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