Passed
Branch development (e0e718)
by Nils
04:45
created

adLDAP   F

Complexity

Total Complexity 167

Size/Duplication

Total Lines 921
Duplicated Lines 3.58 %

Coupling/Cohesion

Components 7
Dependencies 8

Importance

Changes 0
Metric Value
dl 33
loc 921
rs 1.263
c 0
b 0
f 0
wmc 167
lcom 7
cbo 8

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like adLDAP often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use adLDAP, and based on these observations, apply Extract Interface, too.

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