GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (4873)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

plugins/ldap/include/LDAP.class.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
// Codendi
3
// Copyright (c) Xerox Corporation, Codendi Team, 2001-2009. All rights reserved
4
// http://www.codendi.com
5
6
/**
7
 * LDAP class definition
8
 * Provides LDAP facilities to Codendi:
9
 * - directory search
10
 * - user authentication
11
 * The ldap object is initialized with global parameters (from local.inc):
12
 * servers, query templates, etc.
13
 */
14
class LDAP {
15
    /**
16
     * This is equivalent to searching the entire directory. 
17
     */
18
    const SCOPE_SUBTREE      = 1;
19
    const SCOPE_SUBTREE_TEXT = 'subtree';
20
    
21
    /**
22
     * LDAP_SCOPE_ONELEVEL means that the search should only return information 
23
     * that is at the level immediately below the base_dn given in the call. 
24
     * (Equivalent to typing "ls" and getting a list of files and folders in 
25
     * the current working directory.)
26
     */
27
    const SCOPE_ONELEVEL      = 2;
28
    const SCOPE_ONELEVEL_TEXT = 'onelevel';
29
    
30
    /**
31
     * It is equivalent to reading an entry from the directory. 
32
     */
33
    const SCOPE_BASE     = 3;
34
35
    /**
36
     * Error value when search exceed either server or client size limit. 
37
     */
38
    const ERR_SIZELIMIT = 0x04 ;
39
    
40
    const ERR_SUCCESS   = 0x00;
41
42
    const SERVER_TYPE_ACTIVE_DIRECTORY = "ActiveDirectory";
43
    const SERVER_TYPE_OPEN_LDAP        = "OpenLDAP";
44
45
    private $ds;
46
    private $bound;
47
    private $errorsTrapped;
48
    private $ldapParams;
49
    private $query_escaper;
50
51
    /** @var Logger */
52
    private $logger;
53
    
54
    /**
55
     * LDAP object constructor. Use gloabals for initialization.
56
     */
57
    function __construct(array $ldapParams, Logger $logger, LdapQueryEscaper $query_escaper) {
58
        $this->ldapParams    =  $ldapParams;
59
        $this->bound         = false;
60
        $this->errorsTrapped = true;
61
        $this->logger        = $logger;
62
        $this->query_escaper = $query_escaper;
63
    }
64
    
65
    /**
66
     * Returns the whole LDAP parameters set by admin
67
     * 
68
     * @return array
69
     */
70
    function getLDAPParams() {
71
        return $this->ldapParams;
72
    }
73
74
    /**
75
     * Returns one parameter from the list set by admin
76
     * 
77
     * @param String $key Parameter name
78
     * 
79
     * @return String
80
     */
81
    function getLDAPParam($key) {
82
        return isset($this->ldapParams[$key]) ?  $this->ldapParams[$key] : null;
83
    }
84
    
85
    /**
86
     * Connect to LDAP server.
87
     * If several servers are listed, try first server first, then second, etc.
88
     * This funtion should not be called directly: it is always called
89
     * by a public function: authenticate() or search().
90
     * 
91
     * @return Boolean true if connect was successful, false otherwise.
92
     */ 
93
    function connect() {
94
        if (!$this->ds) {
95
            foreach (explode('[,;]', $this->ldapParams['server']) as $ldap_server) {
96
                $this->ds = ldap_connect($ldap_server);
97
                if($this->ds) {
98
                    // Force protocol to LDAPv3 (for AD & recent version of OpenLDAP)
99
                    ldap_set_option($this->ds, LDAP_OPT_PROTOCOL_VERSION, 3);
100
                    ldap_set_option($this->ds, LDAP_OPT_REFERRALS, 0);
101
102
                    // Since ldap_connect always return a resource with
103
                    // OpenLdap 2.2.x, we have to check that this ressource is
104
                    // valid with a bind, If bind success: that's great, if
105
                    // not, this is a connexion failure.
106
                    if($this->bind()) {
107
                        $this->logger->debug('Bound to LDAP server: '.$ldap_server);
108
                        return true;
109
                    } else {
110
                        $this->logger->warn('Cannot bind to LDAP server: '.$ldap_server.
111
                            ' ***ERROR MESSSAGE:'. ldap_error($this->ds).
112
                            ' ***ERROR no:'. $this->getErrno()
113
                        );
114
                    }
115
                } else {
116
                    $this->logger->warn('Cannot connect to LDAP server: '.$ldap_server.
117
                        ' ***ERROR:'. ldap_error($this->ds) .
118
                        ' ***ERROR no:'. $this->getErrno()
119
                    );
120
                }
121
            }
122
            $this->logger->warn('Cannot connect to any LDAP server: '.$this->ldapParams['server'].
123
                ' ***ERROR:'. ldap_error($this->ds).
124
                ' ***ERROR no:'. $this->getErrno()
125
            );
126
            return false;
127
        } else {
128
            return true;
129
        }
130
    }
131
132
    private function authenticatedBindConnect($servers, $binddn, $bindpwd) {
133
        $ds = false;
134
        foreach (split('[,;]', $servers) as $ldap_server) {
135
            $ds = ldap_connect($ldap_server);
136
            if ($ds) {
137
                // Force protocol to LDAPv3 (for AD & recent version of OpenLDAP)
138
                ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
139
                ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
140
141
                // Since ldap_connect always return a resource with
142
                // OpenLdap 2.2.x, we have to check that this ressource is
143
                // valid with a bind, If bind success: that's great, if
144
                // not, this is a connexion failure.
145
                if (@ldap_bind($ds, $binddn, $bindpwd)) {
146
                    return $ds;
147
                } else {
148
                    throw new LDAP_Exception_BindException(ldap_error($ds));
149
                }
150
            }
151
        }
152
        throw new LDAP_Exception_ConnexionException(ldap_error($ds));
153
    }
154
155
    /**
156
     * Perform LDAP binding.
157
     * - Some servers allow anonymous bindings for searching. Otherwise, set
158
     *  sys_ldap_bind_dn and sys_ldap_bind_passwd in local.inc
159
     * - binding is also used for user authentication. A successful bind
160
     *   means that the user/password is valid.
161
     *
162
     * @param String $binddn DN to use to bind with
163
     * @param String $bindpw Password associated to the DN
164
     * 
165
     * @return Boolean true if bind was successful, false otherwise.
166
     */
167
    function bind($binddn=null, $bindpw=null) {
168
        if(!$this->bound) {
169
            if (!$binddn) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $binddn of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
170
                $binddn=isset($this->ldapParams['bind_dn']) ? $this->ldapParams['bind_dn'] : null;
171
                $bindpw=isset($this->ldapParams['bind_passwd']) ? $this->ldapParams['bind_passwd'] : null;
172
            }
173
            if ($binddn && (!$bindpw)) {
174
                // Prevent successful binding if a username is given and the server
175
                // accepts anonymous connections
176
                //$this->setError($Language->getText('ldap_class','err_bind_nopasswd',$binddn));
177
                $this->logger->error('Cannot connect to LDAP server: '.$this->ldapParams['server'].
178
                    ' ***ERROR: will not bind if a username is given and the server accepts anonymous connections'
179
                );
180
                $this->bound = false;
181
            }
182
183
            if ($bind_result = @ldap_bind($this->ds, $binddn, $bindpw)) {
184
                $this->bound = true;
185
            } else {
186
                $this->logger->error('Unable to bind to LDAP server: '.$this->ldapParams['server'].
187
                    ' ***ERROR:'. ldap_error($this->ds) .
188
                    ' ***ERROR no:'. $this->getErrno()
189
                );
190
                //$this->setError($Language->getText('ldap_class','err_bind_invpasswd',$binddn));
191
                $this->bound = false;
192
            }
193
        }
194
        return $this->bound;
195
    }
196
197
    /**
198
     * Unbinds from the LDAP directory
199
     * 
200
     * According to http://www.php.net/manual/en/function.ldap-unbind.php#17203
201
     * ldap_unbind kills the link descriptor so we just have to force the rebind
202
     * for next query
203
     */
204
    function unbind() {
205
        $this->bound = false;
206
    }
207
    
208
    /**
209
     * Connect and bind to the LDAP Directory
210
     *
211
     * @return Boolean
212
     */
213
    function _connectAndBind() {
214
        if (!$this->connect()) {
215
            //$this->setError($Language->getText('ldap_class','err_cant_connect'));
216
            return false;
217
        }
218
        if (!$this->bind()) {
219
            //$this->setError($Language->getText('ldap_class','err_cant_bind'));
220
            return false;
221
        }
222
        return true;
223
    }
224
225
    /**
226
     * Return last error state
227
     * 
228
     * @return Integer
229
     */
230
    function getErrno() {
231
        return ldap_errno($this->ds);
232
    }    
233
    
234
    /** 
235
     * Perform LDAP authentication of a user based on its login.
236
     * 
237
     * First search the DN of the user based on its login then try to bind
238
     * with this DN and the given password
239
     *
240
     * @param String $login  Login name to authenticate with
241
     * @param String $passwd Password associated to the login
242
     * 
243
     * @return Boolean true if the login and password match, false otherwise
244
     */
245
    function authenticate($login, $passwd) {
246
        if (!$passwd) {
247
            // avoid a successful bind on LDAP servers accepting anonymous connections
248
            //$this->setError($Language->getText('ldap_class','err_nopasswd'));
249
            return false;
250
        }
251
252
        // Do a search to recover the right DN based on given login
253
        $lri = $this->searchLogin($login);
254
        if ($lri && count($lri) === 1) {
255
            $auth_dn = $lri->current()->getDn();
256
        } else {
257
            return false;
258
        }
259
260
        // Now bind with DN/password to check authentication
261
        // /!\ Be sure not to reuse a previously bound connexion (otherwise
262
        // authentication will always be successfull.
263
        $this->unbind();
264
        if (!$this->bind($auth_dn,$passwd)) {
265
            //$this->setError($Language->getText('ldap_class','err_badpasswd'));
266
            return false;
267
        }
268
        return true;
269
    }
270
    
271
    /**
272
     * Search in the LDAP directory
273
     * 
274
     * @see http://php.net/ldap_search
275
     * 
276
     * @param String  $baseDn     Base DN where to search
277
     * @param String  $filter     Specific LDAP query
278
     * @param Integer $scope      How to search (SCOPE_ SUBTREE, ONELEVEL or BASE)
279
     * @param Array   $attributes LDAP fields to retreive
280
     * @param Integer $attrsOnly  Retreive both field value and name (keep it to 0)
281
     * @param Integer $sizeLimit  Limit the size of the result set
282
     * @param Integer $timeLimit  Limit the time spend to search for results
283
     * @param Integer $deref      Dereference result
284
     * 
285
     * @return LDAPResultIterator
286
     */
287
    function search($baseDn, $filter, $scope=self::SCOPE_SUBTREE, $attributes=array(), $attrsOnly=0, $sizeLimit=0, $timeLimit=0, $deref=LDAP_DEREF_NEVER) {
288
        if($this->_connectAndBind()) {
289
            $this->_initErrorHandler();
290
            switch ($scope) {
291
            case self::SCOPE_BASE:
292
                $sr = ldap_read($this->ds, $baseDn, $filter, $attributes, $attrsOnly, $sizeLimit, $timeLimit, $deref);
293
                break;
294
            
295
            case self::SCOPE_ONELEVEL:
296
                $sr = ldap_list($this->ds, $baseDn, $filter, $attributes, $attrsOnly, $sizeLimit, $timeLimit, $deref);
297
                break;
298
            
299
            case self::SCOPE_SUBTREE:
300
            default:
301
                $sr = ldap_search($this->ds, $baseDn, $filter, $attributes, $attrsOnly, $sizeLimit, $timeLimit, $deref);
302
            }
303
            $this->_restoreErrorHandler();
304
305
            if ($sr !== false) {
306
                $this->logger->debug('LDAP search success '.$baseDn.' '.$filter. ' *** SCOPE: '.$scope . ' *** ATTRIBUTES: '.implode(', ', $attributes));
307
                $entries = ldap_get_entries($this->ds, $sr);
308
                if ($entries !== false) {
309
                    return new LDAPResultIterator($entries, $this->ldapParams);
310
                }
311
            } else {
312
                $this->logger->warn('LDAP search error: '.$baseDn.' '.$filter.' '.$this->ldapParams['server'].
313
                    ' ***ERROR:'. ldap_error($this->ds).
314
                    ' ***ERROR no:'. $this->getErrno()
315
                );
316
            }
317
        }
318
        return false;
319
    }
320
321
    public function getDefaultAttributes() {
322
        return array(
323
            $this->ldapParams['mail'],
324
            $this->ldapParams['cn'],
325
            $this->ldapParams['uid'],
326
            $this->ldapParams['eduid'],
327
            'dn'
328
        );
329
    }
330
331
    /**
332
     * Search a specific Distinguish Name
333
     *
334
     * @param String $dn         DN to retreive
335
     * @param Array  $attributes Restrict the LDAP fields to fetch
336
     * 
337
     * @return LDAPResultIterator
338
     */
339
    function searchDn($dn, $attributes=array()) {
340
        $attributes = count($attributes) > 0 ? $attributes : $this->getDefaultAttributes();
341
        return $this->search($dn, 'objectClass=*', self::SCOPE_BASE, $attributes);
342
    }
343
344
    /**
345
     * Search if given argument correspond to a LDAP login (generally this
346
     * correspond to ldap 'uid' field).
347
     *
348
     * @param String $name login
349
     * @param array $attributes
350
     *
351
     * @return LDAPResultIterator
352
     */    
353
    public function searchLogin($name, $attributes = array()) {
354
        $name = $this->query_escaper->escapeFilter($name);
355
        if (! $attributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attributes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
356
            $attributes = $this->getDefaultAttributes();
357
        }
358
359
        $filter = $this->ldapParams['uid'].'='.$name;
360
        return $this->search($this->ldapParams['dn'], $filter, self::SCOPE_SUBTREE, $attributes);
361
    }
362
    
363
    /**
364
     * Search if given argument correspond to a LDAP Identifier. This is the
365
     * uniq number that represent a user.
366
     *
367
     * @param String $name LDAP Id
368
     * 
369
     * @return LDAPResultIterator
370
     */  
371
    function searchEdUid($name) {
372
        $name = $this->query_escaper->escapeFilter($name);
373
        $filter = $this->ldapParams['eduid'].'='.$name;
374
        return $this->search($this->ldapParams['dn'], $filter, self::SCOPE_SUBTREE, $this->getDefaultAttributes());
375
    }
376
377
    /**
378
     * Search if a LDAP user match a filter defined in local conf.
379
     *
380
     * @param String $words User name to search
381
     * 
382
     * @return LDAPResultIterator
383
     */
384
    function searchUser($words) {
385
        $words = $this->query_escaper->escapeFilter($words);
386
        $filter = str_replace("%words%", $words, $this->ldapParams['search_user']);
387
        return $this->search($this->ldapParams['dn'], $filter, self::SCOPE_SUBTREE, $this->getDefaultAttributes());
388
    }
389
390
    /**
391
     * Search if given identifier match a Common Name in the LDAP.
392
     *
393
     * @param String $name Common name to search
394
     * 
395
     * @return LDAPResultIterator
396
     */
397
    function searchCommonName($name) {
398
        $name = $this->query_escaper->escapeFilter($name);
399
        $filter = $this->ldapParams['cn'].'='.$name;
400
        return $this->search($this->ldapParams['dn'], $filter, self::SCOPE_SUBTREE, $this->getDefaultAttributes());
401
    }
402
403
    /**
404
     * Search ldap group by name
405
     *
406
     * @param String $name Group name to search
407
     * 
408
     * @return LDAPResultIterator
409
     */
410
    function searchGroup($name) {
411
        $name = $this->query_escaper->escapeFilter($name);
412
413
        if (isset($this->ldapParams['server_type']) && $this->ldapParams['server_type'] === self::SERVER_TYPE_ACTIVE_DIRECTORY) {
414
            $filter = $this->ldapParams['grp_uid'] . '=' . $name;
415
            return $this->search($this->ldapParams['grp_dn'], $filter, self::SCOPE_SUBTREE);
416
        }
417
418
        $filter = $this->ldapParams['grp_cn'].'='.$name;
419
        return $this->search($this->ldapParams['dn'], $filter, self::SCOPE_SUBTREE);
420
    }
421
    
422
    /**
423
     * List members of a LDAP group
424
     * 
425
     * @param String $groupDn Group DN
426
     * 
427
     * @return LDAPResultIterator
428
     */
429
    function searchGroupMembers($groupDn) {
430
        return $this->search($groupDn, 'objectClass=*', self::SCOPE_SUBTREE, array($this->ldapParams['grp_member']));
431
    }
432
433
    /**
434
     * Specific search of user common name, only the common name is returned
435
     *
436
     * This method is designed for speed and to limit the number of returned values.
437
     * 
438
     * @param String   $name      Name of the group to look for
439
     * @param Integer  $sizeLimit Limit the amount of result sent
440
     * 
441
     * @return LDAPResultIterator
442
     */
443
    function searchUserAsYouType($name, $sizeLimit, $validEmail=false) {
444
        $apIt  = new AppendIterator();
445
        if($name && $this->_connectAndBind()) {
446
            $name = $this->query_escaper->escapeFilter($name);
447
            if (isset($this->ldapParams['tooltip_search_user'])) {
448
                $filter = str_replace("%words%", $name, $this->ldapParams['tooltip_search_user']);
449
            } else {
450
                $filter = '('.$this->ldapParams['cn'].'='.$name.'*)';
451
            }
452
            if($validEmail) {
453
                // Only search people with a non empty mail field
454
                $mail = $this->query_escaper->escapeFilter($this->ldapParams['mail']);
455
                $filter = '(&'.$filter.'('.$mail.'=*))';
456
            }
457
            // We only care about Common name and Login (lower the amount of data
458
            // to fetch speed up the request.
459
            if (isset($this->ldapParams['tooltip_search_attrs'])) {
460
                $attrs = explode(';', $this->ldapParams['tooltip_search_attrs']);
461
            } else {
462
                $attrs  = array($this->ldapParams['cn'], $this->ldapParams['uid']);
463
            }
464
            // We want types and values
465
            $attrsOnly = 0;
466
            // Catch errors to detect if there are more results available than
467
            // the list actually returned (helps to refine the search)
468
            $this->trapErrors();
469
            // Use SCOPE_ONELEVEL to only search in "sys_ldap_people_dn" branch 
470
            // of the directory to speed up the search.
471
            $peopleDn = explode(';', $this->ldapParams['people_dn']);
472
            foreach ($peopleDn as $count) {
473
                $ds[] = $this->ds;
474
            }
475
            if (isset($this->ldapParams['tooltip_search_user'])) {
476
                $asr = ldap_search($ds, $peopleDn, $filter, $attrs, $attrsOnly, $sizeLimit, 0, LDAP_DEREF_NEVER);
477
                $this->logger->debug('LDAP in-depth search as you type '.$filter. ' ***PEOPLEDN: '.$peopleDn . ' ***errors:'.  ldap_error($this->ds));
478
            } else {
479
                $asr = ldap_list($ds, $peopleDn, $filter, $attrs, $attrsOnly, $sizeLimit, 0, LDAP_DEREF_NEVER);
480
                $this->logger->debug('LDAP high-level search as you type '.$filter. ' ***PEOPLEDN: '.print_r($peopleDn, true) . ' ***errors:'.  ldap_error($this->ds));
481
            }
482
            if ($asr !== false) {
483
                foreach ($asr as $sr) {
0 ignored issues
show
The expression $asr of type resource is not traversable.
Loading history...
484
                    $entries = ldap_get_entries($this->ds, $sr);
485
                    if ($entries !== false) {
486
                        // AppendIterator doesn't seem to handle invalid iterator well.
487
                        // So don't append invalid iterators...
488
                        $it = new LDAPResultIterator($entries, $this->ldapParams);
489
                        if ($it->valid()) {
490
                            $apIt->append($it);
491
                        }
492
                    }
493
                }
494
            }
495
        }
496
        return $apIt;
497
    }
498
499
    /**
500
     * Specific search of group common name, only the common name is returned
501
     *
502
     * This method is designed for speed and to limit the number of returned values.
503
     * 
504
     * @param String   $name      Name of the group to look for
505
     * @param Integer $sizeLimit Limit the amount of result sent
506
     * 
507
     * @return LDAPResultIterator
508
     */
509
    function searchGroupAsYouType($name, $sizeLimit) {
510
        $lri = false;
511
        if($this->_connectAndBind()) {
512
            $name = $this->query_escaper->escapeFilter($name);
513
            $filter = '('.$this->ldapParams['grp_cn'].'=*'.$name.'*)';
514
            // We only care about Common name
515
            $attrs  = array($this->ldapParams['grp_cn']);
516
            // We want types and values
517
            $attrsOnly = 0; 
518
            // Catch errors to detect if there are more results available than
519
            // the list actually returned (helps to refine the search)
520
            $this->trapErrors();
521
            // Use SCOPE_ONELEVEL to only search in "sys_ldap_grp_dn" branch 
522
            // of the directory to speed up the search.
523
            $lri = $this->search($this->ldapParams['grp_dn'], $filter, self::SCOPE_ONELEVEL, $attrs, $attrsOnly, $sizeLimit);
524
        }
525
        if ($lri === false) {
526
            return new LDAPResultIterator(array(), array());
527
        } else {
528
            return $lri;
529
        }
530
    }
531
532
    public function add($dn, array $info) {
533
        $ds = $this->getWriteConnexion();
534
        if (@ldap_add($ds, $dn, $info)) {
535
            return true;
536
        }
537
        throw new LDAP_Exception_AddException(ldap_error($ds), $dn);
538
    }
539
540
    public function update($dn, array $info) {
541
        $ds = $this->getWriteConnexion();
542
        if (@ldap_modify($ds, $dn, $info)) {
543
            return true;
544
        }
545
        throw new LDAP_Exception_UpdateException(ldap_error($ds), $dn);
546
    }
547
548
    public function delete($dn) {
549
        $ds = $this->getWriteConnexion();
550
        if (@ldap_delete($ds, $dn)) {
551
            return true;
552
        }
553
        throw new LDAP_Exception_DeleteException(ldap_error($ds), $dn);
554
    }
555
556
    public function renameUser($old_dn, $new_root_dn) {
557
        return $this->rename($old_dn, $new_root_dn, $this->getLDAPParam('write_people_dn'));
558
    }
559
560
    private function rename($old_dn, $newrdn, $newparent) {
561
        $ds = $this->getWriteConnexion();
562
        if (@ldap_rename($ds, $old_dn, $newrdn, $newparent, true)) {
563
            return true;
564
        }
565
        throw new LDAP_Exception_RenameException(ldap_error($ds), $old_dn, $newrdn.','.$newparent);
566
    }
567
568
    private function getWriteConnexion() {
569
        return $this->authenticatedBindConnect(
570
            $this->getLDAPParam('write_server'),
571
            $this->getLDAPParam('write_dn'),
572
            $this->getLDAPParam('write_password')
573
        );
574
    }
575
576
    /**
577
     * Enable fake error handler
578
     * 
579
     * The fake error handler is enabled only for one query.
580
     * 
581
     * @see _initErrorHandler()
582
     */
583
    private function trapErrors() {
584
        $this->errorsTrapped = true;
585
    }
586
    
587
    /**
588
     * Setup fake error handler to be able to catch an error without displaying it
589
     *
590
     * This is not very clean but it's the only way to get some ldap errors
591
     * without displaying them to final users. In some cases errors are meaningful
592
     * and even expected (see searchAsYouType*) because we set very restrictive
593
     * limits and of course the limit is exceeded easily. We need to catch it
594
     * but not to display a warning to the user.
595
     * 
596
     * Note: don't enable it for each request, otherwise, you may hide unwanted
597
     * errors.
598
     */
599
    private function _initErrorHandler() {
600
        if ($this->errorsTrapped) {
601
            set_error_handler(create_function('',''));
602
        }
603
    }
604
605
    /**
606
     * After LDAP query, restore the PHP error handler to its previous state.
607
     */
608
    private function _restoreErrorHandler() {
609
        if ($this->errorsTrapped) {
610
            restore_error_handler();
611
        }
612
        $this->errorsTrapped = false;
613
    }
614
}
615