~ubuntu-branches/ubuntu/trusty/phpldapadmin/trusty-proposed

« back to all changes in this revision

Viewing changes to lib/ds_ldap.php

  • Committer: Bazaar Package Importer
  • Author(s): Stefan Lesicnik
  • Date: 2010-05-10 06:15:32 UTC
  • mfrom: (1.1.9 upstream) (3.1.8 sid)
  • Revision ID: james.westby@ubuntu.com-20100510061532-s4m6ytom0eb7oam1
Tags: 1.2.0.5-1ubuntu1
* Merge from debian unstable.  Remaining changes:
  - Merged call to dh_install to install debian/additional-templates/*
  - added groupOfNames.xml
  - Adds php_value memory_limit 32M to the apache.conf.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Classes and functions for communication of Data Stores
 
4
 *
 
5
 * @author The phpLDAPadmin development team
 
6
 * @package phpLDAPadmin
 
7
 */
 
8
 
 
9
/**
 
10
 * This abstract class provides the basic variables and methods for LDAP datastores
 
11
 *
 
12
 * @package phpLDAPadmin
 
13
 * @subpackage DataStore
 
14
 */
 
15
class ldap extends DS {
 
16
        # If we fail to connect, set this to true
 
17
        private $noconnect = false;
 
18
        # Raw Schema entries
 
19
        private $_schema_entries = null;
 
20
        # Schema DN
 
21
        private $_schemaDN = null;
 
22
        # Attributes that should be treated as MAY attributes, even though the scheme has them as MUST attributes.
 
23
        private $force_may = array();
 
24
 
 
25
        public function __construct($index) {
 
26
                if (defined('DEBUG_ENABLED') && DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
27
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
28
 
 
29
                $this->index = $index;
 
30
                $this->type = 'ldap';
 
31
 
 
32
                # Additional values that can go in our config.php
 
33
                $this->custom = new StdClass;
 
34
                $this->default = new StdClass;
 
35
 
 
36
/*
 
37
 * Not used by PLA
 
38
                # Database Server Variables
 
39
                $this->default->server['db'] = array(
 
40
                        'desc'=>'Database Name',
 
41
                        'untested'=>true,
 
42
                        'default'=>null);
 
43
*/
 
44
 
 
45
                /* This was created for IDS - since it doesnt present STRUCTURAL against objectClasses
 
46
                 * definitions when reading the schema.*/
 
47
                $this->default->server['schema_oclass_default'] = array(
 
48
                        'desc'=>'When reading the schema, and it doesnt specify objectClass type, default it to this',
 
49
                        'default'=>null);
 
50
 
 
51
                $this->default->server['base'] = array(
 
52
                        'desc'=>'LDAP Base DNs',
 
53
                        'default'=>array());
 
54
 
 
55
                $this->default->server['tls'] = array(
 
56
                        'desc'=>'Connect using TLS',
 
57
                        'default'=>false);
 
58
 
 
59
                # Login Details
 
60
                $this->default->login['attr'] = array(
 
61
                        'desc'=>'Attribute to use to find the users DN',
 
62
                        'default'=>'dn');
 
63
 
 
64
                $this->default->login['anon_bind'] = array(
 
65
                        'desc'=>'Enable anonymous bind logins',
 
66
                        'default'=>true);
 
67
 
 
68
                $this->default->login['allowed_dns'] = array(
 
69
                        'desc'=>'Limit logins to users who match any of the following LDAP filters',
 
70
                        'default'=>array());
 
71
 
 
72
                $this->default->login['base'] = array(
 
73
                        'desc'=>'Limit logins to users who are in these base DNs',
 
74
                        'default'=>array());
 
75
 
 
76
                $this->default->login['class'] = array(
 
77
                        'desc'=>'Strict login to users containing a specific objectClasses',
 
78
                        'default'=>array());
 
79
 
 
80
                $this->default->proxy['attr'] = array(
 
81
                        'desc'=>'Attribute to use to find the users DN for proxy based authentication',
 
82
                        'default'=>array());
 
83
 
 
84
                # SASL configuration
 
85
                $this->default->server['sasl'] = array(
 
86
                        'desc'=>'Use SASL authentication when binding LDAP server',
 
87
                        'default'=>false);
 
88
 
 
89
                $this->default->sasl['mech'] = array(
 
90
                        'desc'=>'SASL mechanism used while binding LDAP server',
 
91
                        'untested'=>true,
 
92
                        'default'=>'PLAIN');
 
93
 
 
94
                $this->default->sasl['realm'] = array(
 
95
                        'desc'=>'SASL realm name',
 
96
                        'untested'=>true,
 
97
                        'default'=>null);
 
98
 
 
99
                $this->default->sasl['authz_id'] = array(
 
100
                        'desc'=>'SASL authorization id',
 
101
                        'untested'=>true,
 
102
                        'default'=>null);
 
103
 
 
104
                $this->default->sasl['authz_id_regex'] = array(
 
105
                        'desc'=>'SASL authorization id PCRE regular expression',
 
106
                        'untested'=>true,
 
107
                        'default'=>null);
 
108
 
 
109
                $this->default->sasl['authz_id_replacement'] = array(
 
110
                        'desc'=>'SASL authorization id PCRE regular expression replacement string',
 
111
                        'untested'=>true,
 
112
                        'default'=>null);
 
113
 
 
114
                $this->default->sasl['props'] = array(
 
115
                        'desc'=>'SASL properties',
 
116
                        'untested'=>true,
 
117
                        'default'=>null);
 
118
        }
 
119
 
 
120
        /**
 
121
         * Required ABSTRACT functions
 
122
         */
 
123
        /**
 
124
         * Connect and Bind to the Database
 
125
         *
 
126
         * @param string Which connection method resource to use
 
127
         * @return resource|null Connection resource if successful, null if not.
 
128
         */
 
129
        protected function connect($method,$debug=false,$new=false) {
 
130
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
131
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
132
 
 
133
                static $CACHE = array();
 
134
 
 
135
                $method = $this->getMethod($method);
 
136
                $bind = array();
 
137
 
 
138
                if (isset($CACHE[$this->index][$method]) && $CACHE[$this->index][$method])
 
139
                        return $CACHE[$this->index][$method];
 
140
 
 
141
                # Check if we have logged in and therefore need to use those details as our bind.
 
142
                $bind['id'] = is_null($this->getLogin($method)) && $method != 'anon' ? $this->getLogin('user') : $this->getLogin($method);
 
143
                $bind['pass'] = is_null($this->getPassword($method)) && $method != 'anon' ? $this->getPassword('user') : $this->getPassword($method);
 
144
 
 
145
                # If our bind id is still null, we are not logged in.
 
146
                if (is_null($bind['id']) && ! in_array($method,array('anon','login')))
 
147
                        return null;
 
148
 
 
149
                # If we bound to the LDAP server with these details for a different connection, return that resource
 
150
                if (isset($CACHE[$this->index]) && ! $new)
 
151
                        foreach ($CACHE[$this->index] as $cachedmethod => $resource) {
 
152
                                if (($this->getLogin($cachedmethod) == $bind['id']) && ($this->getPassword($cachedmethod) == $bind['pass'])) {
 
153
                                        $CACHE[$this->index][$method] = $resource;
 
154
 
 
155
                                        return $CACHE[$this->index][$method];
 
156
                                }
 
157
                        }
 
158
 
 
159
                $CACHE[$this->index][$method] = null;
 
160
 
 
161
                # No identifiable connection exists, lets create a new one.
 
162
                if (DEBUG_ENABLED)
 
163
                        debug_log('Creating NEW connection [%s] for index [%s]',16,0,__FILE__,__LINE__,__METHOD__,
 
164
                                $method,$this->index);
 
165
 
 
166
                if (function_exists('run_hook'))
 
167
                        run_hook('pre_connect',array('server_id'=>$this->index,'method'=>$method));
 
168
 
 
169
                if ($this->getValue('server','port'))
 
170
                        $resource = ldap_connect($this->getValue('server','host'),$this->getValue('server','port'));
 
171
                else
 
172
                        $resource = ldap_connect($this->getValue('server','host'));
 
173
 
 
174
                $CACHE[$this->index][$method] = $resource;
 
175
 
 
176
                if (DEBUG_ENABLED)
 
177
                        debug_log('LDAP Resource [%s], Host [%s], Port [%s]',16,0,__FILE__,__LINE__,__METHOD__,
 
178
                                $resource,$this->getValue('server','host'),$this->getValue('server','port'));
 
179
 
 
180
                if (! is_resource($resource))
 
181
                        debug_dump_backtrace('UNHANDLED, $resource is not a resource',1);
 
182
 
 
183
                # Go with LDAP version 3 if possible (needed for renaming and Novell schema fetching)
 
184
                ldap_set_option($resource,LDAP_OPT_PROTOCOL_VERSION,3);
 
185
 
 
186
                /* Disabling this makes it possible to browse the tree for Active Directory, and seems
 
187
                 * to not affect other LDAP servers (tested with OpenLDAP) as phpLDAPadmin explicitly
 
188
                 * specifies deref behavior for each ldap_search operation. */
 
189
                ldap_set_option($resource,LDAP_OPT_REFERRALS,0);
 
190
 
 
191
                # Try to fire up TLS is specified in the config
 
192
                if ($this->isTLSEnabled())
 
193
                        $this->startTLS($resource);
 
194
 
 
195
                # If SASL has been configured for binding, then start it now.
 
196
                if ($this->isSASLEnabled())
 
197
                        $bind['result'] = $this->startSASL($resource,$method);
 
198
 
 
199
                # Normal bind...
 
200
                else
 
201
                        $bind['result'] = @ldap_bind($resource,$bind['id'],$bind['pass']);
 
202
 
 
203
                if ($debug)
 
204
                        debug_dump(array('method'=>$method,'bind'=>$bind,'USER'=>$_SESSION['USER']));
 
205
 
 
206
                if (DEBUG_ENABLED)
 
207
                        debug_log('Resource [%s], Bind Result [%s]',16,0,__FILE__,__LINE__,__METHOD__,$resource,$bind);
 
208
 
 
209
                if (! $bind['result']) {
 
210
                        if (DEBUG_ENABLED)
 
211
                                debug_log('Leaving with FALSE, bind FAILed',16,0,__FILE__,__LINE__,__METHOD__);
 
212
 
 
213
                        $this->noconnect = true;
 
214
 
 
215
                        system_message(array(
 
216
                                'title'=>sprintf('%s %s',_('Unable to connect to LDAP server'),$this->getName()),
 
217
                                'body'=>sprintf('<b>%s</b>: %s (%s) for <b>%s</b>',_('Error'),$this->getErrorMessage($method),$this->getErrorNum($method),$method),
 
218
                                'type'=>'error'));
 
219
 
 
220
                        $CACHE[$this->index][$method] = null;
 
221
 
 
222
                } else {
 
223
                        $this->noconnect = false;
 
224
 
 
225
                        # If this is a proxy session, we need to switch to the proxy user
 
226
                        if ($this->isProxyEnabled() && $bind['id'] && $method != 'anon')
 
227
                                if (! $this->startProxy($resource,$method)) {
 
228
                                        $this->noconnect = true;
 
229
                                        $CACHE[$this->index][$method] = null;
 
230
                                }
 
231
                }
 
232
 
 
233
                if (function_exists('run_hook'))
 
234
                        run_hook('post_connect',array('server_id'=>$this->index,'method'=>$method,'id'=>$bind['id']));
 
235
 
 
236
                if ($debug)
 
237
                        debug_dump(array($method=>$CACHE[$this->index][$method]));
 
238
 
 
239
                return $CACHE[$this->index][$method];
 
240
        }
 
241
 
 
242
        /**
 
243
         * Login to the database with the application user/password
 
244
         *
 
245
         * @return boolean true|false for successful login.
 
246
         */
 
247
        public function login($user=null,$pass=null,$method=null,$new=false) {
 
248
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
249
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
250
 
 
251
                $userDN = null;
 
252
 
 
253
                # Get the userDN from the username.
 
254
                if (! is_null($user)) {
 
255
                        # If login,attr is set to DN, then user should be a DN
 
256
                        if (($this->getValue('login','attr') == 'dn') || $method != 'user')
 
257
                                $userDN = $user;
 
258
                        else
 
259
                                $userDN = $this->getLoginID($user,'login');
 
260
 
 
261
                        if (! $userDN && $this->getValue('login','fallback_dn'))
 
262
                                $userDN = $user;
 
263
 
 
264
                        if (! $userDN)
 
265
                                return false;
 
266
 
 
267
                } else {
 
268
                        if (in_array($method,array('user','anon'))) {
 
269
                                $method = 'anon';
 
270
                                $userDN = '';
 
271
                                $pass = '';
 
272
 
 
273
                        } else {
 
274
                                $userDN = $this->getLogin('user');
 
275
                                $pass = $this->getPassword('user');
 
276
                        }
 
277
                }
 
278
 
 
279
                if (! $this->isAnonBindAllowed() && ! trim($userDN))
 
280
                        return false;
 
281
 
 
282
                # Temporarily set our user details
 
283
                $this->setLogin($userDN,$pass,$method);
 
284
 
 
285
                $connect = $this->connect($method,false,$new);
 
286
 
 
287
                # If we didnt log in...
 
288
                if (! is_resource($connect) || $this->noconnect || ! $this->userIsAllowedLogin($userDN)) {
 
289
                        $this->logout($method);
 
290
 
 
291
                        return false;
 
292
 
 
293
                } else
 
294
                        return true;
 
295
        }
 
296
 
 
297
        /**
 
298
         * Perform a query to the Database
 
299
         *
 
300
         * @param string query to perform
 
301
         *      $query['base']
 
302
         *      $query['filter']
 
303
         *      $query['scope']
 
304
         *      $query['attrs'] = array();
 
305
         *      $query['deref']
 
306
         * @param string Which connection method resource to use
 
307
         * @param string Index items according to this key
 
308
         * @param boolean Enable debugging output
 
309
         * @return array|null Results of query.
 
310
         */
 
311
        public function query($query,$method,$index=null,$debug=false) {
 
312
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
313
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
314
 
 
315
                $attrs_only = 0;
 
316
 
 
317
                # Defaults
 
318
                if (! isset($query['attrs']))
 
319
                        $query['attrs'] = array();
 
320
                else
 
321
                        # Re-index the attrs, PHP throws an error if the keys are not sequential from 0.
 
322
                        $query['attrs'] = array_values($query['attrs']);
 
323
 
 
324
                if (! isset($query['base'])) {
 
325
                        $bases = $this->getBaseDN();
 
326
                        $query['base'] = array_shift($bases);
 
327
                }
 
328
 
 
329
                if (! isset($query['deref']))
 
330
                        $query['deref'] = $_SESSION[APPCONFIG]->getValue('deref','search');
 
331
                if (! isset($query['filter']))
 
332
                        $query['filter'] = '(&(objectClass=*))';
 
333
                if (! isset($query['scope']))
 
334
                        $query['scope'] = 'sub';
 
335
                if (! isset($query['size_limit']))
 
336
                        $query['size_limit'] = 0;
 
337
                if (! isset($query['time_limit']))
 
338
                        $query['time_limit'] = 0;
 
339
 
 
340
                if ($query['scope'] == 'base' && ! isset($query['baseok']))
 
341
                        system_message(array(
 
342
                                'title'=>sprintf('Dont call %s',__METHOD__),
 
343
                                'body'=>sprintf('Use getDNAttrValues for base queries [%s]',$query['base']),
 
344
                                'type'=>'info'));
 
345
 
 
346
                if (is_array($query['base'])) {
 
347
                        system_message(array(
 
348
                                'title'=>_('Invalid BASE for query'),
 
349
                                'body'=>_('The query was cancelled because of an invalid base.'),
 
350
                                'type'=>'error'));
 
351
 
 
352
                        return array();
 
353
                }
 
354
 
 
355
                if (DEBUG_ENABLED)
 
356
                        debug_log('%s search PREPARE.',16,0,__FILE__,__LINE__,__METHOD__,$query['scope']);
 
357
 
 
358
                if ($debug)
 
359
                        debug_dump(array('query'=>$query,'server'=>$this->getIndex(),'con'=>$this->connect($method)));
 
360
 
 
361
                $resource = $this->connect($method,$debug);
 
362
 
 
363
                switch ($query['scope']) {
 
364
                        case 'base':
 
365
                                $search = @ldap_read($resource,$query['base'],$query['filter'],$query['attrs'],$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']);
 
366
                                break;
 
367
 
 
368
                        case 'one':
 
369
                                $search = @ldap_list($resource,$query['base'],$query['filter'],$query['attrs'],$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']);
 
370
                                break;
 
371
 
 
372
                        case 'sub':
 
373
                        default:
 
374
                                $search = @ldap_search($resource,$query['base'],$query['filter'],$query['attrs'],$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']);
 
375
                                break;
 
376
                }
 
377
 
 
378
                if ($debug)
 
379
                        debug_dump(array('method'=>$method,'search'=>$search,'error'=>$this->getErrorMessage()));
 
380
 
 
381
                if (DEBUG_ENABLED)
 
382
                        debug_log('Search scope [%s] base [%s] filter [%s] attrs [%s] COMPLETE (%s).',16,0,__FILE__,__LINE__,__METHOD__,
 
383
                                $query['scope'],$query['base'],$query['filter'],$query['attrs'],is_null($search));
 
384
 
 
385
                if (! $search)
 
386
                        return array();
 
387
 
 
388
                $return = array();
 
389
 
 
390
                # Get the first entry identifier
 
391
                if ($entries = ldap_get_entries($resource,$search)) {
 
392
                        # Remove the count
 
393
                        if (isset($entries['count']))
 
394
                                unset($entries['count']);
 
395
 
 
396
                        # Iterate over the entries
 
397
                        foreach ($entries as $a => $entry) {
 
398
                                if (! isset($entry['dn']))
 
399
                                        debug_dump_backtrace('No DN?',1);
 
400
 
 
401
                                # Remove the none entry references.
 
402
                                if (! is_array($entry)) {
 
403
                                        unset($entries[$a]);
 
404
                                        continue;
 
405
                                }
 
406
 
 
407
                                $dn = $entry['dn'];
 
408
                                unset($entry['dn']);
 
409
 
 
410
                                # Iterate over the attributes
 
411
                                foreach ($entry as $b => $attrs) {
 
412
                                        # Remove the none entry references.
 
413
                                        if (! is_array($attrs)) {
 
414
                                                unset($entry[$b]);
 
415
                                                continue;
 
416
                                        }
 
417
 
 
418
                                        # Remove the count
 
419
                                        if (isset($entry[$b]['count']))
 
420
                                                unset($entry[$b]['count']);
 
421
                                }
 
422
 
 
423
                                # Our queries always include the DN (the only value not an array).
 
424
                                $entry['dn'] = $dn;
 
425
                                $return[$dn] = $entry;
 
426
                        }
 
427
 
 
428
                        # Sort our results
 
429
                        foreach ($return as $key=> $values)
 
430
                                ksort($return[$key]);
 
431
                }
 
432
 
 
433
                if (DEBUG_ENABLED)
 
434
                        debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
 
435
 
 
436
                return $return;
 
437
        }
 
438
 
 
439
        /**
 
440
         * Get the last error string
 
441
         *
 
442
         * @param string Which connection method resource to use
 
443
         */
 
444
        public function getErrorMessage($method=null) {
 
445
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
446
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
447
 
 
448
                return ldap_error($this->connect($method));
 
449
        }
 
450
 
 
451
        /**
 
452
         * Get the last error number
 
453
         *
 
454
         * @param string Which connection method resource to use
 
455
         */
 
456
        public function getErrorNum($method=null) {
 
457
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
458
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
459
 
 
460
                return ldap_errno($this->connect($method));
 
461
        }
 
462
 
 
463
        /**
 
464
         * Additional functions
 
465
         */
 
466
        /**
 
467
         * Get a user ID
 
468
         *
 
469
         * @param string Which connection method resource to use
 
470
         */
 
471
        public function getLoginID($user,$method=null) {
 
472
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
473
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
474
 
 
475
                $query['filter'] = sprintf('(&(%s=%s)%s)',
 
476
                        $this->getValue('login','attr'),$user,
 
477
                        $this->getLoginClass() ? sprintf('(objectclass=%s)',join(')(objectclass=',$this->getLoginClass())) : '');
 
478
                $query['attrs'] = array('dn');
 
479
 
 
480
                foreach ($this->getLoginBaseDN() as $base) {
 
481
                        $query['base'] = $base;
 
482
                        $result = $this->query($query,$method);
 
483
 
 
484
                        if (count($result) == 1)
 
485
                                break;
 
486
                }
 
487
 
 
488
                if (count($result) != 1)
 
489
                        return null;
 
490
 
 
491
                $detail = array_shift($result);
 
492
 
 
493
                if (! isset($detail['dn']))
 
494
                        die('ERROR: DN missing?');
 
495
                else
 
496
                        return $detail['dn'];
 
497
        }
 
498
 
 
499
        /**
 
500
         * Return the login base DNs
 
501
         * If no login base DNs are defined, then the LDAP server Base DNs are used.
 
502
         */
 
503
        private function getLoginBaseDN() {
 
504
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
505
                        debug_log('Entered (%%)',17,1,__FILE__,__LINE__,__METHOD__,$fargs);
 
506
 
 
507
                if ($this->getValue('login','base'))
 
508
                        return $this->getValue('login','base');
 
509
                else
 
510
                        return $this->getBaseDN();
 
511
        }
 
512
 
 
513
        /**
 
514
         * Return the login classes that a user must have to login
 
515
         */
 
516
        private function getLoginClass() {
 
517
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
518
                        debug_log('Entered (%%)',17,1,__FILE__,__LINE__,__METHOD__,$fargs);
 
519
 
 
520
                return $this->getValue('login','class');
 
521
        }
 
522
 
 
523
        /**
 
524
         * Return if anonymous bind is allowed in the configuration
 
525
         */
 
526
        public function isAnonBindAllowed() {
 
527
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
528
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
529
 
 
530
                return $this->getValue('login','anon_bind');
 
531
        }
 
532
 
 
533
        /**
 
534
         * Fetches whether TLS has been configured for use with a certain server.
 
535
         *
 
536
         * Users may configure phpLDAPadmin to use TLS in config,php thus:
 
537
         * <code>
 
538
         *      $servers->setValue('server','tls',true|false);
 
539
         * </code>
 
540
         *
 
541
         * @return boolean
 
542
         */
 
543
        private function isTLSEnabled() {
 
544
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
545
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
546
 
 
547
                if ($this->getValue('server','tls') && ! function_exists('ldap_start_tls')) {
 
548
                                error(_('TLS has been enabled in your config, but your PHP install does not support TLS. TLS will be disabled.'),'warn');
 
549
                        return false;
 
550
 
 
551
                } else
 
552
                        return $this->getValue('server','tls');
 
553
        }
 
554
 
 
555
        /**
 
556
         * If TLS is configured, then start it
 
557
         */
 
558
        private function startTLS($resource) {
 
559
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
560
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
561
 
 
562
                if (! $this->getValue('server','tls') || (function_exists('ldap_start_tls') && ! @ldap_start_tls($resource))) {
 
563
                        system_message(array(
 
564
                                'title'=>sprintf('%s (%s)',_('Could not start TLS.'),$this->getName()),
 
565
                                'body'=>sprintf('<b>%s</b>: %s',_('Error'),_('Could not start TLS. Please check your LDAP server configuration.')),
 
566
                                'type'=>'error'));
 
567
 
 
568
                        return false;
 
569
 
 
570
                } else
 
571
                        return true;
 
572
        }
 
573
 
 
574
        /**
 
575
         * Fetches whether SASL has been configured for use with a certain server.
 
576
         *
 
577
         * Users may configure phpLDAPadmin to use SASL in config,php thus:
 
578
         * <code>
 
579
         *      $servers->setValue('server','sasl',true|false);
 
580
         * </code>
 
581
         *
 
582
         * @return boolean
 
583
         */
 
584
        private function isSASLEnabled() {
 
585
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
586
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
587
 
 
588
                if ($this->getValue('server','sasl') && ! function_exists('ldap_sasl_bind')) {
 
589
                                error(_('SASL has been enabled in your config, but your PHP install does not support SASL. SASL will be disabled.'),'warn');
 
590
                        return false;
 
591
 
 
592
                } else
 
593
                        return $this->getValue('server','sasl');
 
594
        }
 
595
 
 
596
        /**
 
597
         * If SASL is configured, then start it
 
598
         * To be able to use SASL, PHP should have been compliled with --with-ldap-sasl=DIR
 
599
         *
 
600
         * @todo This has not been tested, please let the developers know if this function works as expected.
 
601
         */
 
602
        private function startSASL($resource,$method) {
 
603
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
604
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
605
 
 
606
                static $CACHE = array();
 
607
 
 
608
                switch (strtolower($this->getValue('sasl','mech'))) {
 
609
                        case 'gssapi':
 
610
                                if (isset($_ENV['REDIRECT_KRB5CCNAME']))
 
611
                                        putenv(sprintf('KRB5CCNAME={%s}',$_ENV['REDIRECT_KRB5CCNAME']));
 
612
 
 
613
                                break;
 
614
                }
 
615
 
 
616
                if (! $this->getValue('server','sasl') || ! function_exists('ldap_start_tls'))
 
617
                        return false;
 
618
 
 
619
                if (! isset($CACHE['login_dn'])) {
 
620
                        $CACHE['login_dn'] = is_null($this->getLogin($method)) ? $this->getLogin('user') : $this->getLogin($method);
 
621
                        $CACHE['login_pass'] = is_null($this->getPassword($method)) ? $this->getPassword('user') : $this->getPassword($method);
 
622
                }
 
623
 
 
624
                # Do we need to rewrite authz_id?
 
625
                if (! isset($CACHE['authz_id']))
 
626
                        if (! trim($this->getValue('sasl','authz_id'))) {
 
627
 
 
628
                        if (DEBUG_ENABLED)
 
629
                                debug_log('Rewriting bind DN [%s] -> authz_id with regex [%s] and replacement [%s].',9,0,__FILE__,__LINE__,__METHOD__,
 
630
                                        $CACHE['login_dn'],
 
631
                                        $this->getValue('sasl','authz_id_regex'),
 
632
                                        $this->getValue('sasl','authz_id_replacement'));
 
633
 
 
634
                        $CACHE['authz_id'] = @preg_replace($this->getValue('sasl','authz_id_regex'),
 
635
                                $this->getValue('sasl','authz_id_replacement'),$CACHE['login_dn']);
 
636
 
 
637
                        # Invalid regex?
 
638
                        if (is_null($CACHE['authz_id']))
 
639
                                error(sprintf(_('It seems that sasl_authz_id_regex "%s" contains invalid PCRE regular expression. The error is "%s".'),
 
640
                                        $this->getValue('sasl','authz_id_regex'),(isset($php_errormsg) ? $php_errormsg : '')),
 
641
                                        'error','index.php');
 
642
 
 
643
                        if (DEBUG_ENABLED)
 
644
                                debug_log('Resource [%s], SASL OPTIONS: mech [%s], realm [%s], authz_id [%s], props [%s]',9,0,__FILE__,__LINE__,__METHOD__,
 
645
                                        $resource,
 
646
                                        $this->getValue('sasl','mech'),
 
647
                                        $this->getValue('sasl','realm'),
 
648
                                        $CACHE['authz_id'],
 
649
                                        $this->getValue('sasl','props'));
 
650
 
 
651
                        } else
 
652
                                $CACHE['authz_id'] = $this->getValue('sasl','authz_id');
 
653
 
 
654
                # @todo this function is different in PHP5.1 and PHP5.2
 
655
                return @ldap_sasl_bind($resource,$CACHE['login_dn'],$CACHE['login_pass'],
 
656
                        $this->getValue('sasl','mech'),
 
657
                        $this->getValue('sasl','realm'),
 
658
                        $CACHE['authz_id'],
 
659
                        $this->getValue('sasl','props'));
 
660
        }
 
661
 
 
662
        /**
 
663
         * Fetches whether PROXY AUTH has been configured for use with a certain server.
 
664
         *
 
665
         * Users may configure phpLDAPadmin to use PROXY AUTH in config,php thus:
 
666
         * <code>
 
667
         *      $servers->setValue('login','auth_type','proxy');
 
668
         * </code>
 
669
         *
 
670
         * @return boolean
 
671
         */
 
672
        private function isProxyEnabled() {
 
673
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
674
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
675
 
 
676
                return $this->getValue('login','auth_type') == 'proxy' ? true : false;
 
677
        }
 
678
 
 
679
        /**
 
680
         * If PROXY AUTH is configured, then start it
 
681
         */
 
682
        private function startProxy($resource,$method) {
 
683
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
684
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
685
 
 
686
                $rootdse = $this->getRootDSE();
 
687
 
 
688
                if (! (isset($rootdse['supportedcontrol']) && in_array('2.16.840.1.113730.3.4.18',$rootdse['supportedcontrol']))) {
 
689
                        system_message(array(
 
690
                                'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()),
 
691
                                'body'=>sprintf('<b>%s</b>: %s',_('Error'),_('Your LDAP server doesnt seem to support this control')),
 
692
                                'type'=>'error'));
 
693
 
 
694
                        return false;
 
695
                }
 
696
 
 
697
                $filter = '(&';
 
698
                $dn = '';
 
699
 
 
700
                $missing = false;
 
701
                foreach ($this->getValue('proxy','attr') as $attr => $var) {
 
702
                        if (! isset($_SERVER[$var])) {
 
703
                                system_message(array(
 
704
                                        'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()),
 
705
                                        'body'=>sprintf('<b>%s</b>: %s (%s)',_('Error'),_('Attribute doesnt exist'),$var),
 
706
                                        'type'=>'error'));
 
707
 
 
708
                                $missing = true;
 
709
 
 
710
                        } else {
 
711
                                if ($attr == 'dn') {
 
712
                                        $dn = $var;
 
713
 
 
714
                                        break;
 
715
 
 
716
                                } else
 
717
                                        $filter .= sprintf('(%s=%s)',$attr,$_SERVER[$var]);
 
718
                        }
 
719
                }
 
720
 
 
721
                if ($missing)
 
722
                        return false;
 
723
 
 
724
                $filter .= ')';
 
725
 
 
726
                if (! $dn) {
 
727
                        $query['filter'] = $filter;
 
728
 
 
729
                        foreach ($this->getBaseDN() as $base) {
 
730
                                $query['base'] = $base;
 
731
 
 
732
                                if ($search = $this->query($query,$method))
 
733
                                        break;
 
734
                        }
 
735
 
 
736
                        if (count($search) != 1) {
 
737
                                system_message(array(
 
738
                                        'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()),
 
739
                                        'body'=>sprintf('<b>%s</b>: %s (%s)',_('Error'),_('Search for DN returned the incorrect number of results'),count($search)),
 
740
                                        'type'=>'error'));
 
741
 
 
742
                                return false;
 
743
                        }
 
744
 
 
745
                        $search = array_pop($search);
 
746
                        $dn = $search['dn'];
 
747
                }
 
748
 
 
749
                $ctrl = array(
 
750
                        'oid'=>'2.16.840.1.113730.3.4.18',
 
751
                        'value'=>sprintf('dn:%s',$dn),
 
752
                        'iscritical' => true);
 
753
 
 
754
                if (! ldap_set_option($resource,LDAP_OPT_SERVER_CONTROLS,array($ctrl))) {
 
755
                        system_message(array(
 
756
                                'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()),
 
757
                                'body'=>sprintf('<b>%s</b>: %s (%s) for <b>%s</b>',_('Error'),$this->getErrorMessage($method),$this->getErrorNum($method),$method),
 
758
                                'type'=>'error'));
 
759
 
 
760
                        return false;
 
761
                }
 
762
 
 
763
                $_SESSION['USER'][$this->index][$method]['proxy'] = blowfish_encrypt($dn);
 
764
 
 
765
                return true;
 
766
        }
 
767
 
 
768
        /**
 
769
         * Modify attributes of a DN
 
770
         */
 
771
        public function modify($dn,$attrs,$method=null) {
 
772
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
773
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
774
 
 
775
                # We need to supress the error here - programming should detect and report it.
 
776
                return @ldap_mod_replace($this->connect($method),$dn,$attrs);
 
777
        }
 
778
 
 
779
        /**
 
780
         * Gets the root DN of the specified LDAPServer, or null if it
 
781
         * can't find it (ie, the server won't give it to us, or it isnt
 
782
         * specified in the configuration file).
 
783
         *
 
784
         * Tested with OpenLDAP 2.0, Netscape iPlanet, and Novell eDirectory 8.7 (nldap.com)
 
785
         * Please report any and all bugs!!
 
786
         *
 
787
         * Please note: On FC systems, it seems that php_ldap uses /etc/openldap/ldap.conf in
 
788
         * the search base if it is blank - so edit that file and comment out the BASE line.
 
789
         *
 
790
         * @param string Which connection method resource to use
 
791
         * @return array dn|null The root DN of the server on success (string) or null on error.
 
792
         * @todo Sort the entries, so that they are in the correct DN order.
 
793
         */
 
794
        public function getBaseDN($method=null) {
 
795
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
796
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
797
 
 
798
                static $CACHE;
 
799
 
 
800
                $method = $this->getMethod($method);
 
801
                $result = array();
 
802
 
 
803
                if (isset($CACHE[$this->index][$method]))
 
804
                        return $CACHE[$this->index][$method];
 
805
 
 
806
                # If the base is set in the configuration file, then just return that.
 
807
                if (count($this->getValue('server','base'))) {
 
808
                        if (DEBUG_ENABLED)
 
809
                                debug_log('Return BaseDN from Config [%s]',17,0,__FILE__,__LINE__,__METHOD__,implode('|',$this->getValue('server','base')));
 
810
 
 
811
                        $CACHE[$this->index][$method] = $this->getValue('server','base');
 
812
 
 
813
                # We need to figure it out.
 
814
                } else {
 
815
                        if (DEBUG_ENABLED)
 
816
                                debug_log('Connect to LDAP to find BaseDN',80,0,__FILE__,__LINE__,__METHOD__);
 
817
 
 
818
                        # Set this to empty, in case we loop back here looking for the baseDNs
 
819
                        $CACHE[$this->index][$method] = array();
 
820
 
 
821
                        $results = $this->getDNAttrValues('',$method);
 
822
 
 
823
                        if (isset($results['namingcontexts'])) {
 
824
                                if (DEBUG_ENABLED)
 
825
                                        debug_log('LDAP Entries:%s',80,0,__FILE__,__LINE__,__METHOD__,implode('|',$results['namingcontexts']));
 
826
 
 
827
                                $result = $results['namingcontexts'];
 
828
                        }
 
829
 
 
830
                        $CACHE[$this->index][$method] = $result;
 
831
                }
 
832
 
 
833
                return $CACHE[$this->index][$method];
 
834
        }
 
835
 
 
836
        /**
 
837
         * Gets whether an entry exists based on its DN. If the entry exists,
 
838
         * returns true. Otherwise returns false.
 
839
         *
 
840
         * @param string The DN of the entry of interest.
 
841
         * @param string Which connection method resource to use
 
842
         * @return boolean
 
843
         */
 
844
        public function dnExists($dn,$method=null) {
 
845
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
846
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
847
 
 
848
                $results = $this->getDNAttrValues($dn,$method);
 
849
 
 
850
                if ($results)
 
851
                        return $results;
 
852
                else
 
853
                        return false;
 
854
        }
 
855
 
 
856
        /**
 
857
         * Given a DN string, this returns the top container portion of the string.
 
858
         *
 
859
         * @param string The DN whose container string to return.
 
860
         * @return string The container
 
861
         */
 
862
        public function getContainerTop($dn) {
 
863
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
864
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
865
 
 
866
                $return = $dn;
 
867
 
 
868
                foreach ($this->getBaseDN() as $base) {
 
869
                        if (preg_match("/${base}$/i",$dn)) {
 
870
                                $return = $base;
 
871
                                break;
 
872
                        }
 
873
                }
 
874
 
 
875
                if (DEBUG_ENABLED)
 
876
                        debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
 
877
 
 
878
                return $return;
 
879
        }
 
880
 
 
881
        /**
 
882
         * Given a DN string and a path like syntax, this returns the parent container portion of the string.
 
883
         *
 
884
         * @param string The DN whose container string to return.
 
885
         * @param string Either '/', '.' or something like '../../<rdn>'
 
886
         * @return string The container
 
887
         */
 
888
        public function getContainerPath($dn,$path='..') {
 
889
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
890
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
891
 
 
892
                $top = $this->getContainerTop($dn);
 
893
 
 
894
                if ($path[0] == '/') {
 
895
                        $dn = $top;
 
896
                        $path = substr($path,1);
 
897
 
 
898
                } elseif ($path == '.') {
 
899
                        return $dn;
 
900
                }
 
901
 
 
902
                $parenttree = explode('/',$path);
 
903
 
 
904
                foreach ($parenttree as $key => $value) {
 
905
                        if ($value == '..') {
 
906
                                if ($this->getContainer($dn))
 
907
                                        $dn = $this->getContainer($dn);
 
908
 
 
909
                                if ($dn == $top)
 
910
                                        break;
 
911
 
 
912
                        } elseif($value)
 
913
                                $dn = sprintf('%s,%s',$value,$dn);
 
914
 
 
915
                        else
 
916
                                break;
 
917
                }
 
918
 
 
919
                if (! $dn) {
 
920
                        debug_dump(array(__METHOD__,'dn'=>$dn,'path'=>$path));
 
921
                        debug_dump_backtrace('Container is empty?',1);
 
922
                }
 
923
 
 
924
                return $dn;
 
925
        }
 
926
 
 
927
        /**
 
928
         * Given a DN string, this returns the parent container portion of the string.
 
929
         * For example. given 'cn=Manager,dc=example,dc=com', this function returns
 
930
         * 'dc=example,dc=com'.
 
931
         *
 
932
         * @param string The DN whose container string to return.
 
933
         * @return string The container
 
934
         */
 
935
        public function getContainer($dn) {
 
936
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
937
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
938
 
 
939
                $parts = $this->explodeDN($dn);
 
940
 
 
941
                if (count($parts) <= 1)
 
942
                        $return = null;
 
943
 
 
944
                else {
 
945
                        $return = $parts[1];
 
946
 
 
947
                        for ($i=2;$i<count($parts);$i++)
 
948
                                $return .= sprintf(',%s',$parts[$i]);
 
949
                }
 
950
 
 
951
                if (DEBUG_ENABLED)
 
952
                        debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
 
953
 
 
954
                return $return;
 
955
        }
 
956
 
 
957
        /**
 
958
         * Gets a list of child entries for an entry. Given a DN, this function fetches the list of DNs of
 
959
         * child entries one level beneath the parent. For example, for the following tree:
 
960
         *
 
961
         * <code>
 
962
         *      dc=example,dc=com
 
963
         *              ou=People
 
964
         *                      cn=Dave
 
965
         *                      cn=Fred
 
966
         *                      cn=Joe
 
967
         *              ou=More People
 
968
         *                      cn=Mark
 
969
         *                      cn=Bob
 
970
         * </code>
 
971
         *
 
972
         * Calling <code>getContainerContents("ou=people,dc=example,dc=com")</code>
 
973
         * would return the following list:
 
974
         *
 
975
         * <code>
 
976
         *      cn=Dave
 
977
         *      cn=Fred
 
978
         *      cn=Joe
 
979
         *      ou=More People
 
980
         * </code>
 
981
         *
 
982
         * @param string The DN of the entry whose children to return.
 
983
         * @param string Which connection method resource to use
 
984
         * @param int (optional) The maximum number of entries to return.
 
985
         *            If unspecified, no limit is applied to the number of entries in the returned.
 
986
         * @param string (optional) An LDAP filter to apply when fetching children, example: "(objectClass=inetOrgPerson)"
 
987
         * @param constant (optional) The LDAP deref setting to use in the query
 
988
         * @return array An array of DN strings listing the immediate children of the specified entry.
 
989
         */
 
990
        public function getContainerContents($dn,$method=null,$size_limit=0,$filter='(objectClass=*)',$deref=LDAP_DEREF_NEVER) {
 
991
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
992
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
993
 
 
994
                $return = array();
 
995
 
 
996
                $query = array();
 
997
                $query['base'] = $this->escapeDN($dn);
 
998
                $query['attrs'] = array('dn');
 
999
                $query['filter'] = $filter;
 
1000
                $query['deref'] = $deref;
 
1001
                $query['scope'] = 'one';
 
1002
                $query['size_limit'] = $size_limit;
 
1003
                $results = $this->query($query,$method);
 
1004
 
 
1005
                if ($results) {
 
1006
                        foreach ($results as $index => $entry) {
 
1007
                                $child_dn = $entry['dn'];
 
1008
                                array_push($return,$child_dn);
 
1009
                        }
 
1010
                }
 
1011
 
 
1012
                if (DEBUG_ENABLED)
 
1013
                        debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
 
1014
 
 
1015
                # Sort the results
 
1016
                asort($return);
 
1017
 
 
1018
                return $return;
 
1019
        }
 
1020
 
 
1021
        /**
 
1022
         * Explode a DN into an array of its RDN parts.
 
1023
         *
 
1024
         * @param string The DN to explode.
 
1025
         * @param int (optional) Whether to include attribute names (see http://php.net/ldap_explode_dn for details)
 
1026
         *
 
1027
         * @return array An array of RDN parts of this format:
 
1028
         * <code>
 
1029
         *      Array
 
1030
         *              (
 
1031
         *                      [0] => uid=ppratt
 
1032
         *                      [1] => ou=People
 
1033
         *                      [2] => dc=example
 
1034
         *                      [3] => dc=com
 
1035
         *              )
 
1036
         * </code>
 
1037
         *
 
1038
         * NOTE: When a multivalue RDN is passed to ldap_explode_dn, the results returns with 'value + value';
 
1039
         */
 
1040
        private function explodeDN($dn,$with_attributes=0) {
 
1041
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1042
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1043
 
 
1044
                static $CACHE;
 
1045
 
 
1046
                if (isset($CACHE['explode'][$dn][$with_attributes])) {
 
1047
                        if (DEBUG_ENABLED)
 
1048
                                debug_log('Return CACHED result (%s) for (%s)',1,0,__FILE__,__LINE__,__METHOD__,
 
1049
                                        $CACHE['explode'][$dn][$with_attributes],$dn);
 
1050
 
 
1051
                        return $CACHE['explode'][$dn][$with_attributes];
 
1052
                }
 
1053
 
 
1054
                $dn = addcslashes($dn,'<>+";');
 
1055
 
 
1056
                # split the dn
 
1057
                $result[0] = ldap_explode_dn($this->escapeDN($dn),0);
 
1058
                $result[1] = ldap_explode_dn($this->escapeDN($dn),1);
 
1059
                if (! $result[$with_attributes]) {
 
1060
                        if (DEBUG_ENABLED)
 
1061
                                debug_log('Returning NULL - NO result.',1,0,__FILE__,__LINE__,__METHOD__);
 
1062
 
 
1063
                        return array();
 
1064
                }
 
1065
 
 
1066
                # Remove our count value that ldap_explode_dn returns us.
 
1067
                unset($result[0]['count']);
 
1068
                unset($result[1]['count']);
 
1069
 
 
1070
                # Record the forward and reverse entries in the cache.
 
1071
                foreach ($result as $key => $value) {
 
1072
                        # translate hex code into ascii for display
 
1073
                        $result[$key] = $this->unescapeDN($value);
 
1074
 
 
1075
                        $CACHE['explode'][implode(',',$result[0])][$key] = $result[$key];
 
1076
                        $CACHE['explode'][implode(',',array_reverse($result[0]))][$key] = array_reverse($result[$key]);
 
1077
                }
 
1078
 
 
1079
                if (DEBUG_ENABLED)
 
1080
                        debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$result[$with_attributes]);
 
1081
 
 
1082
                return $result[$with_attributes];
 
1083
        }
 
1084
 
 
1085
        /**
 
1086
         * Parse a DN and escape any special characters
 
1087
         */
 
1088
        protected function escapeDN($dn) {
 
1089
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1090
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1091
 
 
1092
                if (! trim($dn))
 
1093
                        return $dn;
 
1094
 
 
1095
                # Check if the RDN has a comma and escape it.
 
1096
                while (preg_match('/([^\\\\]),(\s*[^=]*\s*),/',$dn))
 
1097
                        $dn = preg_replace('/([^\\\\]),(\s*[^=]*\s*),/','$1\\\\2C$2,',$dn);
 
1098
 
 
1099
                $dn = preg_replace('/([^\\\\]),(\s*[^=]*\s*)([^,])$/','$1\\\\2C$2$3',$dn);
 
1100
 
 
1101
                if (DEBUG_ENABLED)
 
1102
                        debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$dn);
 
1103
 
 
1104
                return $dn;
 
1105
        }
 
1106
 
 
1107
        /**
 
1108
         * Parse a DN and unescape any special characters
 
1109
         */
 
1110
        private function unescapeDN($dn) {
 
1111
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1112
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1113
 
 
1114
                if (is_array($dn)) {
 
1115
                        $a = array();
 
1116
                        foreach ($dn as $key => $rdn)
 
1117
                                $a[$key] = preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$rdn);
 
1118
 
 
1119
                        return $a;
 
1120
 
 
1121
                } else
 
1122
                        return preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$dn);
 
1123
        }
 
1124
 
 
1125
        public function getRootDSE($method=null) {
 
1126
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1127
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1128
 
 
1129
                $query = array();
 
1130
                $query['base'] = '';
 
1131
                $query['scope'] = 'base';
 
1132
                $query['attrs'] = $this->getValue('server','root_dse_attributes');
 
1133
                $query['baseok'] = true;
 
1134
                $results = $this->query($query,$method);
 
1135
 
 
1136
                if (is_array($results) && count($results) == 1)
 
1137
                        return array_change_key_case(array_pop($results));
 
1138
                else
 
1139
                        return array();
 
1140
        }
 
1141
 
 
1142
        /** Schema Methods **/
 
1143
        /**
 
1144
         * This function will query the ldap server and request the subSchemaSubEntry which should be the Schema DN.
 
1145
         *
 
1146
         * If we cant connect to the LDAP server, we'll return false.
 
1147
         * If we can connect but cant get the entry, then we'll return null.
 
1148
         *
 
1149
         * @param string Which connection method resource to use
 
1150
         * @param dn The DN to use to obtain the schema
 
1151
         * @return array|false Schema if available, null if its not or false if we cant connect.
 
1152
         */
 
1153
        private function getSchemaDN($method=null,$dn='') {
 
1154
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1155
                        debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1156
 
 
1157
                # If we already got the SchemaDN, then return it.
 
1158
                if ($this->_schemaDN)
 
1159
                        return $this->_schemaDN;
 
1160
 
 
1161
                if (! $this->connect($method))
 
1162
                        return false;
 
1163
 
 
1164
                $search = @ldap_read($this->connect($method),$dn,'objectclass=*',array('subschemaSubentry'),false,0,10,LDAP_DEREF_NEVER);
 
1165
 
 
1166
                if (DEBUG_ENABLED)
 
1167
                        debug_log('Search returned (%s)',24,0,__FILE__,__LINE__,__METHOD__,is_resource($search));
 
1168
 
 
1169
                # Fix for broken ldap.conf configuration.
 
1170
                if (! $search && ! $dn) {
 
1171
                        if (DEBUG_ENABLED)
 
1172
                                debug_log('Trying to find the DN for "broken" ldap.conf',80,0,__FILE__,__LINE__,__METHOD__);
 
1173
 
 
1174
                        if (isset($this->_baseDN)) {
 
1175
                                foreach ($this->_baseDN as $base) {
 
1176
                                        $search = @ldap_read($this->connect($method),$base,'objectclass=*',array('subschemaSubentry'),false,0,10,LDAP_DEREF_NEVER);
 
1177
 
 
1178
                                        if (DEBUG_ENABLED)
 
1179
                                                debug_log('Search returned (%s) for base (%s)',24,0,__FILE__,__LINE__,__METHOD__,
 
1180
                                                        is_resource($search),$base);
 
1181
 
 
1182
                                        if ($search)
 
1183
                                                break;
 
1184
                                }
 
1185
                        }
 
1186
                }
 
1187
 
 
1188
                if (! $search)
 
1189
                        return null;
 
1190
 
 
1191
                if (! @ldap_count_entries($this->connect($method),$search)) {
 
1192
                        if (DEBUG_ENABLED)
 
1193
                                debug_log('Search returned 0 entries. Returning NULL',25,0,__FILE__,__LINE__,__METHOD__);
 
1194
 
 
1195
                        return null;
 
1196
                }
 
1197
 
 
1198
                $entries = @ldap_get_entries($this->connect($method),$search);
 
1199
 
 
1200
                if (DEBUG_ENABLED)
 
1201
                        debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$entries);
 
1202
 
 
1203
                if (! $entries || ! is_array($entries))
 
1204
                        return null;
 
1205
 
 
1206
                $entry = isset($entries[0]) ? $entries[0] : false;
 
1207
                if (! $entry) {
 
1208
                        if (DEBUG_ENABLED)
 
1209
                                debug_log('Entry is false, Returning NULL',80,0,__FILE__,__LINE__,__METHOD__);
 
1210
 
 
1211
                        return null;
 
1212
                }
 
1213
 
 
1214
                $sub_schema_sub_entry = isset($entry[0]) ? $entry[0] : false;
 
1215
                if (! $sub_schema_sub_entry) {
 
1216
                        if (DEBUG_ENABLED)
 
1217
                                debug_log('Sub Entry is false, Returning NULL',80,0,__FILE__,__LINE__,__METHOD__);
 
1218
 
 
1219
                        return null;
 
1220
                }
 
1221
 
 
1222
                $this->_schemaDN = isset($entry[$sub_schema_sub_entry][0]) ? $entry[$sub_schema_sub_entry][0] : false;
 
1223
 
 
1224
                if (DEBUG_ENABLED)
 
1225
                        debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->_schemaDN);
 
1226
 
 
1227
                return $this->_schemaDN;
 
1228
        }
 
1229
 
 
1230
        /**
 
1231
         * Fetches the raw schema array for the subschemaSubentry of the server. Note,
 
1232
         * this function has grown many hairs to accomodate more LDAP servers. It is
 
1233
         * needfully complicated as it now supports many popular LDAP servers that
 
1234
         * don't necessarily expose their schema "the right way".
 
1235
         *
 
1236
         * Please note: On FC systems, it seems that php_ldap uses /etc/openldap/ldap.conf in
 
1237
         * the search base if it is blank - so edit that file and comment out the BASE line.
 
1238
         *
 
1239
         * @param string Which connection method resource to use
 
1240
         * @param string A string indicating which type of schema to
 
1241
         *              fetch. Five valid values: 'objectclasses', 'attributetypes',
 
1242
         *              'ldapsyntaxes', 'matchingruleuse', or 'matchingrules'.
 
1243
         *              Case insensitive.
 
1244
         * @param dn (optional) This paremeter is the DN of the entry whose schema you
 
1245
         *              would like to fetch. Entries have the option of specifying
 
1246
         *              their own subschemaSubentry that points to the DN of the system
 
1247
         *              schema entry which applies to this attribute. If unspecified,
 
1248
         *              this will try to retrieve the schema from the RootDSE subschemaSubentry.
 
1249
         *              Failing that, we use some commonly known schema DNs. Default
 
1250
         *              value is the Root DSE DN (zero-length string)
 
1251
         * @return array an array of strings of this form:
 
1252
         *      Array (
 
1253
         *              [0] => "(1.3.6.1.4.1.7165.1.2.2.4 NAME 'gidPool' DESC 'Pool ...
 
1254
         *              [1] => "(1.3.6.1.4.1.7165.2.2.3 NAME 'sambaAccount' DESC 'Sa ...
 
1255
         *      etc.
 
1256
         */
 
1257
        private function getRawSchema($method,$schema_to_fetch,$dn='') {
 
1258
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1259
                        debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1260
 
 
1261
                $valid_schema_to_fetch = array('objectclasses','attributetypes','ldapsyntaxes','matchingrules','matchingruleuse');
 
1262
 
 
1263
                if (! $this->connect($method) || $this->noconnect)
 
1264
                        return false;
 
1265
 
 
1266
                # error checking
 
1267
                $schema_to_fetch = strtolower($schema_to_fetch);
 
1268
 
 
1269
                if (! is_null($this->_schema_entries) && isset($this->_schema_entries[$schema_to_fetch])) {
 
1270
                        $schema = $this->_schema_entries[$schema_to_fetch];
 
1271
 
 
1272
                        if (DEBUG_ENABLED)
 
1273
                                debug_log('Returning CACHED (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema);
 
1274
 
 
1275
                        return $schema;
 
1276
                }
 
1277
 
 
1278
                # This error message is not localized as only developers should ever see it
 
1279
                if (! in_array($schema_to_fetch,$valid_schema_to_fetch))
 
1280
                        error(sprintf('Bad parameter provided to function to %s::getRawSchema(). "%s" is not valid for the schema_to_fetch parameter.',
 
1281
                                        get_class($this),$schema_to_fetch),'error','index.php');
 
1282
 
 
1283
                # Try to get the schema DN from the specified entry.
 
1284
                $schema_dn = $this->getSchemaDN($method,$dn);
 
1285
 
 
1286
                # Do we need to try again with the Root DSE?
 
1287
                if (! $schema_dn && trim($dn))
 
1288
                        $schema_dn = $this->getSchemaDN($method,'');
 
1289
 
 
1290
                # Store the eventual schema retrieval in $schema_search
 
1291
                $schema_search = null;
 
1292
 
 
1293
                if ($schema_dn) {
 
1294
                        if (DEBUG_ENABLED)
 
1295
                                debug_log('Using Schema DN (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_dn);
 
1296
 
 
1297
                        foreach (array('(objectClass=*)','(objectClass=subschema)') as $schema_filter) {
 
1298
                                if (DEBUG_ENABLED)
 
1299
                                        debug_log('Looking for schema with Filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_filter);
 
1300
 
 
1301
                                $schema_search = @ldap_read($this->connect($method),$schema_dn,$schema_filter,array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
 
1302
 
 
1303
                                if (is_null($schema_search))
 
1304
                                        continue;
 
1305
 
 
1306
                                $schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
 
1307
 
 
1308
                                if (DEBUG_ENABLED)
 
1309
                                        debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
 
1310
 
 
1311
                                if (is_array($schema_entries) && isset($schema_entries['count']) && $schema_entries['count']) {
 
1312
                                        if (DEBUG_ENABLED)
 
1313
                                                debug_log('Found schema with (DN:%s) (FILTER:%s) (ATTR:%s)',24,0,__FILE__,__LINE__,__METHOD__,
 
1314
                                                        $schema_dn,$schema_filter,$schema_to_fetch);
 
1315
 
 
1316
                                        break;
 
1317
                                }
 
1318
 
 
1319
                                if (DEBUG_ENABLED)
 
1320
                                        debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_filter);
 
1321
 
 
1322
                                unset($schema_entries);
 
1323
                                $schema_search = null;
 
1324
                        }
 
1325
                }
 
1326
 
 
1327
                /* Second chance: If the DN or Root DSE didn't give us the subschemaSubentry, ie $schema_search
 
1328
                 * is still null, use some common subSchemaSubentry DNs as a work-around. */
 
1329
                if (is_null($schema_search)) {
 
1330
                        if (DEBUG_ENABLED)
 
1331
                                debug_log('Attempting work-arounds for "broken" LDAP servers...',24,0,__FILE__,__LINE__,__METHOD__);
 
1332
 
 
1333
                        foreach ($this->getBaseDN() as $base) {
 
1334
                                $ldap['W2K3 AD'][expand_dn_with_base($base,'cn=Aggregate,cn=Schema,cn=configuration,')] = '(objectClass=*)';
 
1335
                                $ldap['W2K AD'][expand_dn_with_base($base,'cn=Schema,cn=configuration,')] = '(objectClass=*)';
 
1336
                                $ldap['W2K AD'][expand_dn_with_base($base,'cn=Schema,ou=Admin,')] = '(objectClass=*)';
 
1337
                        }
 
1338
 
 
1339
                        # OpenLDAP and Novell
 
1340
                        $ldap['OpenLDAP']['cn=subschema'] = '(objectClass=*)';
 
1341
 
 
1342
                        foreach ($ldap as $ldap_server_name => $ldap_options) {
 
1343
                                foreach ($ldap_options as $ldap_dn => $ldap_filter) {
 
1344
                                        if (DEBUG_ENABLED)
 
1345
                                                debug_log('Attempting [%s] (%s) (%s)<BR>',24,0,__FILE__,__LINE__,__METHOD__,
 
1346
                                                        $ldap_server_name,$ldap_dn,$ldap_filter);
 
1347
 
 
1348
                                        $schema_search = @ldap_read($this->connect($method),$ldap_dn,$ldap_filter,array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
 
1349
                                        if (is_null($schema_search))
 
1350
                                                continue;
 
1351
 
 
1352
                                        $schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
 
1353
 
 
1354
                                        if (DEBUG_ENABLED)
 
1355
                                                debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
 
1356
 
 
1357
                                        if ($schema_entries && isset($schema_entries[0][$schema_to_fetch])) {
 
1358
                                                if (DEBUG_ENABLED)
 
1359
                                                        debug_log('Found schema with filter of (%s)',24,0,__FILE__,__LINE__,__METHOD__,$ldap_filter);
 
1360
 
 
1361
                                                break;
 
1362
                                        }
 
1363
 
 
1364
                                        if (DEBUG_ENABLED)
 
1365
                                                debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$ldap_filter);
 
1366
 
 
1367
                                        unset($schema_entries);
 
1368
                                        $schema_search = null;
 
1369
                                }
 
1370
                                if ($schema_search)
 
1371
                                        break;
 
1372
                        }
 
1373
                }
 
1374
 
 
1375
                if (is_null($schema_search)) {
 
1376
                        /* Still cant find the schema, try with the RootDSE
 
1377
                         * Attempt to pull schema from Root DSE with scope "base", or
 
1378
                         * Attempt to pull schema from Root DSE with scope "one" (work-around for Isode M-Vault X.500/LDAP) */
 
1379
                        foreach (array('base','one') as $ldap_scope) {
 
1380
                                if (DEBUG_ENABLED)
 
1381
                                        debug_log('Attempting to find schema with scope (%s), filter (objectClass=*) and a blank base.',24,0,__FILE__,__LINE__,__METHOD__,
 
1382
                                                $ldap_scope);
 
1383
 
 
1384
                                switch ($ldap_scope) {
 
1385
                                        case 'base':
 
1386
                                                $schema_search = @ldap_read($this->connect($method),'','(objectClass=*)',array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
 
1387
                                                break;
 
1388
 
 
1389
                                        case 'one':
 
1390
                                                $schema_search = @ldap_list($this->connect($method),'','(objectClass=*)',array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
 
1391
                                                break;
 
1392
                                }
 
1393
 
 
1394
                                if (is_null($schema_search))
 
1395
                                        continue;
 
1396
 
 
1397
                                $schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
 
1398
                                if (DEBUG_ENABLED)
 
1399
                                        debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
 
1400
 
 
1401
                                if ($schema_entries && isset($schema_entries[0][$schema_to_fetch])) {
 
1402
                                        if (DEBUG_ENABLED)
 
1403
                                                debug_log('Found schema with filter of (%s)',24,0,__FILE__,__LINE__,__METHOD__,'(objectClass=*)');
 
1404
 
 
1405
                                        break;
 
1406
                                }
 
1407
 
 
1408
                                if (DEBUG_ENABLED)
 
1409
                                        debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,'(objectClass=*)');
 
1410
 
 
1411
                                unset($schema_entries);
 
1412
                                $schema_search = null;
 
1413
                        }
 
1414
                }
 
1415
 
 
1416
                $schema_error_message = 'Please contact the phpLDAPadmin developers and let them know:<ul><li>Which LDAP server you are running, including which version<li>What OS it is running on<li>Which version of PHP<li>As well as a link to some documentation that describes how to obtain the SCHEMA information</ul><br />We\'ll then add support for your LDAP server in an upcoming release.';
 
1417
                $schema_error_message_array = array('objectclasses','attributetypes');
 
1418
 
 
1419
                # Shall we just give up?
 
1420
                if (is_null($schema_search)) {
 
1421
                        # We need to have objectclasses and attribues, so display an error, asking the user to get us this information.
 
1422
                        if (in_array($schema_to_fetch,$schema_error_message_array))
 
1423
                                system_message(array(
 
1424
                                        'title'=>sprintf('%s (%s)',_('Our attempts to find your SCHEMA have failed'),$schema_to_fetch),
 
1425
                                        'body'=>sprintf('<b>%s</b>: %s',_('Error'),$schema_error_message),
 
1426
                                        'type'=>'error'));
 
1427
                        else
 
1428
                                if (DEBUG_ENABLED)
 
1429
                                        debug_log('Returning because schema_search is NULL ()',25,0,__FILE__,__LINE__,__METHOD__);
 
1430
 
 
1431
                        # We'll set this, so if we return here our cache will return the known false.
 
1432
                        $this->_schema_entries[$schema_to_fetch] = false;
 
1433
                        return false;
 
1434
                }
 
1435
 
 
1436
                if (! $schema_entries) {
 
1437
                        $return = false;
 
1438
                        if (DEBUG_ENABLED)
 
1439
                                debug_log('Returning false since ldap_get_entries() returned false.',25,0,__FILE__,__LINE__,__METHOD__,$return);
 
1440
 
 
1441
                        return $return;
 
1442
                }
 
1443
 
 
1444
                if(! isset($schema_entries[0][$schema_to_fetch])) {
 
1445
                        if (in_array($schema_to_fetch,$schema_error_message_array)) {
 
1446
                                error(sprintf('Our attempts to find your SCHEMA for "%s" has return UNEXPECTED results.<br /><br /><small>(We expected a "%s" in the $schema array but it wasnt there.)</small><br /><br />%s<br /><br />Dump of $schema_search:<hr /><pre><small>%s</small></pre>',
 
1447
                                        $schema_to_fetch,gettype($schema_search),$schema_error_message,serialize($schema_entries)),'error','index.php');
 
1448
 
 
1449
                        } else {
 
1450
                                $return = false;
 
1451
 
 
1452
                                if (DEBUG_ENABLED)
 
1453
                                        debug_log('Returning because (%s) isnt in the schema array. (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema_to_fetch,$return);
 
1454
 
 
1455
                                return $return;
 
1456
                        }
 
1457
                }
 
1458
 
 
1459
                /* Make a nice array of this form:
 
1460
                        Array (
 
1461
                                [0] => "(1.3.6.1.4.1.7165.1.2.2.4 NAME 'gidPool' DESC 'Pool ...)"
 
1462
                                [1] => "(1.3.6.1.4.1.7165.2.2.3 NAME 'sambaAccount' DESC 'Sa ...)"
 
1463
                        etc.) */
 
1464
 
 
1465
                $schema = $schema_entries[0][$schema_to_fetch];
 
1466
                unset($schema['count']);
 
1467
                $this->_schema_entries[$schema_to_fetch] = $schema;
 
1468
 
 
1469
                if (DEBUG_ENABLED)
 
1470
                        debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema);
 
1471
 
 
1472
                return $schema;
 
1473
        }
 
1474
 
 
1475
        /**
 
1476
         * Gets a single ObjectClass object specified by name.
 
1477
         *
 
1478
         * @param string $oclass_name The name of the objectClass to fetch.
 
1479
         * @param string $dn (optional) It is easier to fetch schema if a DN is provided
 
1480
         *               which defines the subschemaSubEntry attribute (all entries should).
 
1481
         *
 
1482
         * @return ObjectClass The specified ObjectClass object or false on error.
 
1483
         *
 
1484
         * @see ObjectClass
 
1485
         * @see SchemaObjectClasses
 
1486
         */
 
1487
        public function getSchemaObjectClass($oclass_name,$method=null,$dn='') {
 
1488
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1489
                        debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1490
 
 
1491
                $oclass_name = strtolower($oclass_name);
 
1492
                $socs = $this->SchemaObjectClasses($method,$dn);
 
1493
 
 
1494
                # Default return value
 
1495
                $return = false;
 
1496
 
 
1497
                if (isset($socs[$oclass_name]))
 
1498
                        $return = $socs[$oclass_name];
 
1499
 
 
1500
                if (DEBUG_ENABLED)
 
1501
                        debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
 
1502
 
 
1503
                return $return;
 
1504
        }
 
1505
 
 
1506
        /**
 
1507
         * Gets a single AttributeType object specified by name.
 
1508
         *
 
1509
         * @param string $oclass_name The name of the AttributeType to fetch.
 
1510
         * @param string $dn (optional) It is easier to fetch schema if a DN is provided
 
1511
         *             which defines the subschemaSubEntry attribute (all entries should).
 
1512
         *
 
1513
         * @return AttributeType The specified AttributeType object or false on error.
 
1514
         *
 
1515
         * @see AttributeType
 
1516
         * @see SchemaAttributes
 
1517
         */
 
1518
        public function getSchemaAttribute($attr_name,$method=null,$dn='') {
 
1519
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1520
                        debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1521
 
 
1522
                $attr_name = strtolower($attr_name);
 
1523
                $sattrs = $this->SchemaAttributes($method,$dn);
 
1524
 
 
1525
                # Default return value
 
1526
                $return = false;
 
1527
 
 
1528
                if (isset($sattrs[$attr_name]))
 
1529
                        $return = $sattrs[$attr_name];
 
1530
 
 
1531
                if (DEBUG_ENABLED)
 
1532
                        debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
 
1533
 
 
1534
                return $return;
 
1535
        }
 
1536
 
 
1537
        /**
 
1538
         * Gets an associative array of ObjectClass objects for the specified
 
1539
         * server. Each array entry's key is the name of the objectClass
 
1540
         * in lower-case and the value is an ObjectClass object.
 
1541
         *
 
1542
         * @param string $dn (optional) It is easier to fetch schema if a DN is provided
 
1543
         *               which defines the subschemaSubEntry attribute (all entries should).
 
1544
         *
 
1545
         * @return array An array of ObjectClass objects.
 
1546
         *
 
1547
         * @see ObjectClass
 
1548
         * @see getSchemaObjectClass
 
1549
         */
 
1550
        public function SchemaObjectClasses($method=null,$dn='') {
 
1551
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1552
                        debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1553
 
 
1554
                # Set default return
 
1555
                $return = null;
 
1556
 
 
1557
                if ($return = get_cached_item($this->index,'schema','objectclasses')) {
 
1558
                        if (DEBUG_ENABLED)
 
1559
                                debug_log('Returning CACHED [%s] (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'objectclasses');
 
1560
 
 
1561
                        return $return;
 
1562
                }
 
1563
 
 
1564
                $raw = $this->getRawSchema($method,'objectclasses',$dn);
 
1565
 
 
1566
                if ($raw) {
 
1567
                        # Build the array of objectClasses
 
1568
                        $return = array();
 
1569
 
 
1570
                        foreach ($raw as $line) {
 
1571
                                if (is_null($line) || ! strlen($line))
 
1572
                                        continue;
 
1573
 
 
1574
                                $object_class = new ObjectClass($line,$this);
 
1575
                                $return[$object_class->getName()] = $object_class;
 
1576
                        }
 
1577
 
 
1578
                        # Now go through and reference the parent/child relationships
 
1579
                        foreach ($return as $oclass)
 
1580
                                foreach ($oclass->getSupClasses() as $parent_name)
 
1581
                                        if (isset($return[strtolower($parent_name)]))
 
1582
                                                $return[strtolower($parent_name)]->addChildObjectClass($oclass->getName(false));
 
1583
 
 
1584
                        ksort($return);
 
1585
 
 
1586
                        # cache the schema to prevent multiple schema fetches from LDAP server
 
1587
                        set_cached_item($this->index,'schema','objectclasses',$return);
 
1588
                }
 
1589
 
 
1590
                if (DEBUG_ENABLED)
 
1591
                        debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
 
1592
 
 
1593
                return $return;
 
1594
        }
 
1595
 
 
1596
        /**
 
1597
         * Gets an associative array of AttributeType objects for the specified
 
1598
         * server. Each array entry's key is the name of the attributeType
 
1599
         * in lower-case and the value is an AttributeType object.
 
1600
         *
 
1601
         * @param string $dn (optional) It is easier to fetch schema if a DN is provided
 
1602
         *             which defines the subschemaSubEntry attribute (all entries should).
 
1603
         *
 
1604
         * @return array An array of AttributeType objects.
 
1605
         */
 
1606
        public function SchemaAttributes($method=null,$dn='') {
 
1607
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1608
                        debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1609
 
 
1610
                # Set default return
 
1611
                $return = null;
 
1612
 
 
1613
                if ($return = get_cached_item($this->index,'schema','attributes')) {
 
1614
                        if (DEBUG_ENABLED)
 
1615
                                debug_log('(): Returning CACHED [%s] (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'attributes');
 
1616
 
 
1617
                        return $return;
 
1618
                }
 
1619
 
 
1620
                $raw = $this->getRawSchema($method,'attributeTypes',$dn);
 
1621
 
 
1622
                if ($raw) {
 
1623
                        # build the array of attribueTypes
 
1624
                        $syntaxes = $this->SchemaSyntaxes($method,$dn);
 
1625
                        $attrs = array();
 
1626
 
 
1627
                        /**
 
1628
                         * bug 856832: create two arrays - one indexed by name (the standard
 
1629
                         * $attrs array above) and one indexed by oid (the new $attrs_oid array
 
1630
                         * below). This will help for directory servers, like IBM's, that use OIDs
 
1631
                         * in their attribute definitions of SUP, etc
 
1632
                         */
 
1633
                        $attrs_oid = array();
 
1634
                        foreach ($raw as $line) {
 
1635
                                if (is_null($line) || ! strlen($line))
 
1636
                                        continue;
 
1637
 
 
1638
                                $attr = new AttributeType($line);
 
1639
                                if (isset($syntaxes[$attr->getSyntaxOID()])) {
 
1640
                                        $syntax = $syntaxes[$attr->getSyntaxOID()];
 
1641
                                        $attr->setType($syntax->getDescription());
 
1642
                                }
 
1643
                                $attrs[$attr->getName()] = $attr;
 
1644
 
 
1645
                                /**
 
1646
                                 * bug 856832: create an entry in the $attrs_oid array too. This
 
1647
                                 * will be a ref to the $attrs entry for maintenance and performance
 
1648
                                 * reasons
 
1649
                                 */
 
1650
                                $attrs_oid[$attr->getOID()] = &$attrs[$attr->getName()];
 
1651
                        }
 
1652
 
 
1653
                        # go back and add data from aliased attributeTypes
 
1654
                        foreach ($attrs as $name => $attr) {
 
1655
                                $aliases = $attr->getAliases();
 
1656
 
 
1657
                                if (is_array($aliases) && count($aliases) > 0) {
 
1658
                                        /* foreach of the attribute's aliases, create a new entry in the attrs array
 
1659
                                         * with its name set to the alias name, and all other data copied.*/
 
1660
                                        foreach ($aliases as $alias_attr_name) {
 
1661
                                                $new_attr = clone $attr;
 
1662
 
 
1663
                                                $new_attr->setName($alias_attr_name);
 
1664
                                                $new_attr->addAlias($attr->getName(false));
 
1665
                                                $new_attr->removeAlias($alias_attr_name);
 
1666
                                                $new_attr_key = strtolower($alias_attr_name);
 
1667
                                                $attrs[$new_attr_key] = $new_attr;
 
1668
                                        }
 
1669
                                }
 
1670
                        }
 
1671
 
 
1672
                        # go back and add any inherited descriptions from parent attributes (ie, cn inherits name)
 
1673
                        foreach ($attrs as $key => $attr) {
 
1674
                                $sup_attr_name = $attr->getSupAttribute();
 
1675
                                $sup_attr = null;
 
1676
 
 
1677
                                if (trim($sup_attr_name)) {
 
1678
 
 
1679
                                        /* This loop really should traverse infinite levels of inheritance (SUP) for attributeTypes,
 
1680
                                         * but just in case we get carried away, stop at 100. This shouldn't happen, but for
 
1681
                                         * some weird reason, we have had someone report that it has happened. Oh well.*/
 
1682
                                        $i = 0;
 
1683
                                        while ($i++<100 /** 100 == INFINITY ;) */) {
 
1684
 
 
1685
                                                if (isset($attrs_oid[$sup_attr_name])) {
 
1686
                                                        $attr->setSupAttribute($attrs_oid[$sup_attr_name]->getName());
 
1687
                                                        $sup_attr_name = $attr->getSupAttribute();
 
1688
                                                }
 
1689
 
 
1690
                                                if (! isset($attrs[strtolower($sup_attr_name)])){
 
1691
                                                        error(sprintf('Schema error: attributeType "%s" inherits from "%s", but attributeType "%s" does not exist.',
 
1692
                                                                $attr->getName(),$sup_attr_name,$sup_attr_name),'error','index.php');
 
1693
                                                        return;
 
1694
                                                }
 
1695
 
 
1696
                                                $sup_attr = $attrs[strtolower($sup_attr_name)];
 
1697
                                                $sup_attr_name = $sup_attr->getSupAttribute();
 
1698
 
 
1699
                                                # Does this superior attributeType not have a superior attributeType?
 
1700
                                                if (is_null($sup_attr_name) || strlen(trim($sup_attr_name)) == 0) {
 
1701
 
 
1702
                                                        /* Since this attribute's superior attribute does not have another superior
 
1703
                                                         * attribute, clone its properties for this attribute. Then, replace
 
1704
                                                         * those cloned values with those that can be explicitly set by the child
 
1705
                                                         * attribute attr). Save those few properties which the child can set here:*/
 
1706
                                                        $tmp_name = $attr->getName(false);
 
1707
                                                        $tmp_oid = $attr->getOID();
 
1708
                                                        $tmp_sup = $attr->getSupAttribute();
 
1709
                                                        $tmp_aliases = $attr->getAliases();
 
1710
                                                        $tmp_single_val = $attr->getIsSingleValue();
 
1711
                                                        $tmp_desc = $attr->getDescription();
 
1712
 
 
1713
                                                        /* clone the SUP attributeType and populate those values
 
1714
                                                         * that were set by the child attributeType */
 
1715
                                                        $attr = clone $sup_attr;
 
1716
 
 
1717
                                                        $attr->setOID($tmp_oid);
 
1718
                                                        $attr->setName($tmp_name);
 
1719
                                                        $attr->setSupAttribute($tmp_sup);
 
1720
                                                        $attr->setAliases($tmp_aliases);
 
1721
                                                        $attr->setDescription($tmp_desc);
 
1722
 
 
1723
                                                        /* only overwrite the SINGLE-VALUE property if the child explicitly sets it
 
1724
                                                         * (note: All LDAP attributes default to multi-value if not explicitly set SINGLE-VALUE) */
 
1725
                                                        if ($tmp_single_val)
 
1726
                                                                $attr->setIsSingleValue(true);
 
1727
 
 
1728
                                                        /* replace this attribute in the attrs array now that we have populated
 
1729
                                                                 new values therein */
 
1730
                                                        $attrs[$key] = $attr;
 
1731
 
 
1732
                                                        # very important: break out after we are done with this attribute
 
1733
                                                        $sup_attr_name = null;
 
1734
                                                        $sup_attr = null;
 
1735
                                                        break;
 
1736
                                                }
 
1737
                                        }
 
1738
                                }
 
1739
                        }
 
1740
 
 
1741
                        ksort($attrs);
 
1742
 
 
1743
                        # Add the used in and required_by values.
 
1744
                        $socs = $this->SchemaObjectClasses($method);
 
1745
                        if (! is_array($socs))
 
1746
                                return array();
 
1747
 
 
1748
                        foreach ($socs as $object_class) {
 
1749
                                $must_attrs = $object_class->getMustAttrNames();
 
1750
                                $may_attrs = $object_class->getMayAttrNames();
 
1751
                                $oclass_attrs = array_unique(array_merge($must_attrs,$may_attrs));
 
1752
 
 
1753
                                # Add Used In.
 
1754
                                foreach ($oclass_attrs as $attr_name)
 
1755
                                        if (isset($attrs[strtolower($attr_name)]))
 
1756
                                                $attrs[strtolower($attr_name)]->addUsedInObjectClass($object_class->getName(false));
 
1757
 
 
1758
                                # Add Required By.
 
1759
                                foreach ($must_attrs as $attr_name)
 
1760
                                        if (isset($attrs[strtolower($attr_name)]))
 
1761
                                                $attrs[strtolower($attr_name)]->addRequiredByObjectClass($object_class->getName(false));
 
1762
 
 
1763
                                # Force May
 
1764
                                foreach ($object_class->getForceMayAttrs() as $attr_name)
 
1765
                                        if (isset($attrs[strtolower($attr_name->name)]))
 
1766
                                                $attrs[strtolower($attr_name->name)]->setForceMay();
 
1767
                        }
 
1768
 
 
1769
                        $return = $attrs;
 
1770
 
 
1771
                        # cache the schema to prevent multiple schema fetches from LDAP server
 
1772
                        set_cached_item($this->index,'schema','attributes',$return);
 
1773
                }
 
1774
 
 
1775
                if (DEBUG_ENABLED)
 
1776
                        debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
 
1777
 
 
1778
                return $return;
 
1779
        }
 
1780
 
 
1781
        /**
 
1782
         * Returns an array of MatchingRule objects for the specified server.
 
1783
         * The key of each entry is the OID of the matching rule.
 
1784
         */
 
1785
        public function MatchingRules($method=null,$dn='') {
 
1786
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1787
                        debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1788
 
 
1789
                # Set default return
 
1790
                $return = null;
 
1791
 
 
1792
                if ($return = get_cached_item($this->index,'schema','matchingrules')) {
 
1793
                        if (DEBUG_ENABLED)
 
1794
                                debug_log('Returning CACHED [%s] (%s).',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'matchingrules');
 
1795
 
 
1796
                        return $return;
 
1797
                }
 
1798
 
 
1799
                # build the array of MatchingRule objects
 
1800
                $raw = $this->getRawSchema($method,'matchingRules',$dn);
 
1801
 
 
1802
                if ($raw) {
 
1803
                        $rules = array();
 
1804
 
 
1805
                        foreach ($raw as $line) {
 
1806
                                if (is_null($line) || ! strlen($line))
 
1807
                                        continue;
 
1808
 
 
1809
                                $rule = new MatchingRule($line);
 
1810
                                $key = $rule->getName();
 
1811
                                $rules[$key] = $rule;
 
1812
                        }
 
1813
 
 
1814
                        ksort($rules);
 
1815
 
 
1816
                        /* For each MatchingRuleUse entry, add the attributes who use it to the
 
1817
                         * MatchingRule in the $rules array.*/
 
1818
                        $raw = $this->getRawSchema($method,'matchingRuleUse');
 
1819
 
 
1820
                        if ($raw != false) {
 
1821
                                foreach ($raw as $line) {
 
1822
                                        if (is_null($line) || ! strlen($line))
 
1823
                                                continue;
 
1824
 
 
1825
                                        $rule_use = new MatchingRuleUse($line);
 
1826
                                        $key = $rule_use->getName();
 
1827
 
 
1828
                                        if (isset($rules[$key]))
 
1829
                                                $rules[$key]->setUsedByAttrs($rule_use->getUsedByAttrs());
 
1830
                                }
 
1831
 
 
1832
                        } else {
 
1833
                                /* No MatchingRuleUse entry in the subschema, so brute-forcing
 
1834
                                 * the reverse-map for the "$rule->getUsedByAttrs()" data.*/
 
1835
                                $sattrs = $this->SchemaAttributes($method,$dn);
 
1836
                                if (is_array($sattrs))
 
1837
                                        foreach ($sattrs as $attr) {
 
1838
                                                $rule_key = strtolower($attr->getEquality());
 
1839
 
 
1840
                                                if (isset($rules[$rule_key]))
 
1841
                                                        $rules[$rule_key]->addUsedByAttr($attr->getName(false));
 
1842
                                        }
 
1843
                        }
 
1844
 
 
1845
                        $return = $rules;
 
1846
 
 
1847
                        # cache the schema to prevent multiple schema fetches from LDAP server
 
1848
                        set_cached_item($this->index,'schema','matchingrules',$return);
 
1849
                }
 
1850
 
 
1851
                if (DEBUG_ENABLED)
 
1852
                        debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
 
1853
 
 
1854
                return $return;
 
1855
        }
 
1856
 
 
1857
        /**
 
1858
         * Returns an array of Syntax objects that this LDAP server uses mapped to
 
1859
         * their descriptions. The key of each entry is the OID of the Syntax.
 
1860
         */
 
1861
        public function SchemaSyntaxes($method=null,$dn='') {
 
1862
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1863
                        debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1864
 
 
1865
                # Set default return
 
1866
                $return = null;
 
1867
 
 
1868
                if ($return = get_cached_item($this->index,'schema','syntaxes')) {
 
1869
                        if (DEBUG_ENABLED)
 
1870
                                debug_log('Returning CACHED [%s] (%s).',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'syntaxes');
 
1871
 
 
1872
                        return $return;
 
1873
                }
 
1874
 
 
1875
                $raw = $this->getRawSchema($method,'ldapSyntaxes',$dn);
 
1876
 
 
1877
                if ($raw) {
 
1878
                        # build the array of attributes
 
1879
                        $return = array();
 
1880
 
 
1881
                        foreach ($raw as $line) {
 
1882
                                if (is_null($line) || ! strlen($line))
 
1883
                                        continue;
 
1884
 
 
1885
                                $syntax = new Syntax($line);
 
1886
                                $key = strtolower(trim($syntax->getOID()));
 
1887
 
 
1888
                                if (! $key)
 
1889
                                        continue;
 
1890
 
 
1891
                                $return[$key] = $syntax;
 
1892
                        }
 
1893
 
 
1894
                        ksort($return);
 
1895
 
 
1896
                        # cache the schema to prevent multiple schema fetches from LDAP server
 
1897
                        set_cached_item($this->index,'schema','syntaxes',$return);
 
1898
                }
 
1899
 
 
1900
                if (DEBUG_ENABLED)
 
1901
                        debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
 
1902
 
 
1903
                return $return;
 
1904
        }
 
1905
 
 
1906
        /**
 
1907
         * This function determines if the specified attribute is contained in the force_may list
 
1908
         * as configured in config.php.
 
1909
         *
 
1910
         * @return boolean True if the specified attribute is in the $force_may list and false
 
1911
         *              otherwise.
 
1912
         */
 
1913
        function isForceMay($attr_name) {
 
1914
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1915
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1916
 
 
1917
                return in_array($attr_name,$this->force_may);
 
1918
        }
 
1919
 
 
1920
        /**
 
1921
         * Much like getDNAttrValues(), but only returns the values for
 
1922
         * one attribute of an object. Example calls:
 
1923
         *
 
1924
         * <code>
 
1925
         *      print_r(getDNAttrValue('cn=Bob,ou=people,dc=example,dc=com','sn'));
 
1926
         *      Array (
 
1927
         *              [0] => Smith
 
1928
         *      )
 
1929
         *
 
1930
         * print_r(getDNAttrValue('cn=Bob,ou=people,dc=example,dc=com','objectClass'));
 
1931
         *      Array (
 
1932
         *              [0] => top
 
1933
         *              [1] => person
 
1934
         *      )
 
1935
         * </code>
 
1936
         *
 
1937
         * @param string The distinguished name (DN) of the entry whose attributes/values to fetch.
 
1938
         * @param string The attribute whose value(s) to return (ie, "objectClass", "cn", "userPassword")
 
1939
         * @param string Which connection method resource to use
 
1940
         * @param constant For aliases and referrals, this parameter specifies whether to
 
1941
         *            follow references to the referenced DN or to fetch the attributes for
 
1942
         *            the referencing DN. See http://php.net/ldap_search for the 4 valid
 
1943
         *            options.
 
1944
         * @return array
 
1945
         * @see getDNAttrValues
 
1946
         * @todo Caching these values may be problematic with multiple calls and different deref values.
 
1947
         */
 
1948
        public function getDNAttrValue($dn,$attr,$method=null,$deref=LDAP_DEREF_NEVER) {
 
1949
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
1950
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
1951
 
 
1952
                # Ensure our attr is in lowercase
 
1953
                $attr = strtolower($attr);
 
1954
 
 
1955
                $values = $this->getDNAttrValues($dn,$method,$deref);
 
1956
 
 
1957
                if (isset($values[$attr]))
 
1958
                        return $values[$attr];
 
1959
                else
 
1960
                        return array();
 
1961
        }
 
1962
 
 
1963
        /**
 
1964
         * Gets the attributes/values of an entry. Returns an associative array whose
 
1965
         * keys are attribute value names and whose values are arrays of values for
 
1966
         * said attribute.
 
1967
         *
 
1968
         * Optionally, callers may specify true for the parameter
 
1969
         * $lower_case_attr_names to force all keys in the associate array (attribute
 
1970
         * names) to be lower case.
 
1971
         *
 
1972
         * Example of its usage:
 
1973
         * <code>
 
1974
         * print_r(getDNAttrValues('cn=Bob,ou=pepole,dc=example,dc=com')
 
1975
         *      Array (
 
1976
         *              [objectClass] => Array (
 
1977
         *                      [0] => person
 
1978
         *                      [1] => top
 
1979
         *              )
 
1980
         *              [cn] => Array (
 
1981
         *                      [0] => Bob
 
1982
         *              )
 
1983
         *              [sn] => Array (
 
1984
         *                      [0] => Jones
 
1985
         *              )
 
1986
         *              [dn] => Array (
 
1987
         *                      [0] => cn=Bob,ou=pepole,dc=example,dc=com
 
1988
         *              )
 
1989
         *      )
 
1990
         * </code>
 
1991
         *
 
1992
         * @param string The distinguished name (DN) of the entry whose attributes/values to fetch.
 
1993
         * @param string Which connection method resource to use
 
1994
         * @param constant For aliases and referrals, this parameter specifies whether to
 
1995
         *            follow references to the referenced DN or to fetch the attributes for
 
1996
         *            the referencing DN. See http://php.net/ldap_search for the 4 valid
 
1997
         *            options.
 
1998
         * @return array
 
1999
         * @see getDNSysAttrs
 
2000
         * @see getDNAttrValue
 
2001
         */
 
2002
        public function getDNAttrValues($dn,$method=null,$deref=LDAP_DEREF_NEVER,$attrs=array('*','+')) {
 
2003
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
2004
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
2005
 
 
2006
                static $CACHE;
 
2007
 
 
2008
                $cacheindex = null;
 
2009
                $method = $this->getMethod($method);
 
2010
 
 
2011
                if (in_array('*',$attrs) && in_array('+',$attrs))
 
2012
                        $cacheindex = '&';
 
2013
                elseif (in_array('+',$attrs))
 
2014
                        $cacheindex = '+';
 
2015
                elseif (in_array('*',$attrs))
 
2016
                        $cacheindex = '*';
 
2017
 
 
2018
                if (! is_null($cacheindex) && isset($CACHE[$this->index][$method][$dn][$cacheindex])) {
 
2019
                        $results = $CACHE[$this->index][$method][$dn][$cacheindex];
 
2020
 
 
2021
                        if (DEBUG_ENABLED)
 
2022
                                debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$results);
 
2023
 
 
2024
                } else {
 
2025
                        $query = array();
 
2026
                        $query['base'] = $this->escapeDN($dn);
 
2027
                        $query['scope'] = 'base';
 
2028
                        $query['deref'] = $deref;
 
2029
                        $query['attrs'] = $attrs;
 
2030
                        $query['baseok'] = true;
 
2031
                        $results = $this->query($query,$method);
 
2032
 
 
2033
                        if (count($results))
 
2034
                                $results = array_pop($results);
 
2035
 
 
2036
                        $results = array_change_key_case($results);
 
2037
 
 
2038
                        # Covert all our result key values to an array
 
2039
                        foreach ($results as $key => $values)
 
2040
                                if (! is_array($results[$key]))
 
2041
                                        $results[$key] = array($results[$key]);
 
2042
 
 
2043
                        # Finally sort the results
 
2044
                        ksort($results);
 
2045
 
 
2046
                        if (! is_null($cacheindex) && count($results))
 
2047
                                $CACHE[$this->index][$method][$dn][$cacheindex] = $results;
 
2048
                }
 
2049
 
 
2050
                return $results;
 
2051
        }
 
2052
 
 
2053
        /**
 
2054
         * Returns true if the attribute specified is required to take as input a DN.
 
2055
         * Some examples include 'distinguishedName', 'member' and 'uniqueMember'.
 
2056
         *
 
2057
         * @param string $attr_name The name of the attribute of interest (case insensitive)
 
2058
         * @return boolean
 
2059
         */
 
2060
        function isDNAttr($attr_name,$method=null) {
 
2061
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
2062
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
2063
 
 
2064
                # Simple test first
 
2065
                $dn_attrs = array('aliasedObjectName');
 
2066
                foreach ($dn_attrs as $dn_attr)
 
2067
                        if (strcasecmp($attr_name,$dn_attr) == 0)
 
2068
                                return true;
 
2069
 
 
2070
                # Now look at the schema OID
 
2071
                $sattr = $this->getSchemaAttribute($attr_name);
 
2072
                if (! $sattr)
 
2073
                        return false;
 
2074
 
 
2075
                $syntax_oid = $sattr->getSyntaxOID();
 
2076
                if ('1.3.6.1.4.1.1466.115.121.1.12' == $syntax_oid)
 
2077
                        return true;
 
2078
                if ('1.3.6.1.4.1.1466.115.121.1.34' == $syntax_oid)
 
2079
                        return true;
 
2080
 
 
2081
                $syntaxes = $this->SchemaSyntaxes($method);
 
2082
                if (! isset($syntaxes[$syntax_oid]))
 
2083
                        return false;
 
2084
 
 
2085
                $syntax_desc = $syntaxes[ $syntax_oid ]->getDescription();
 
2086
                if (strpos(strtolower($syntax_desc),'distinguished name'))
 
2087
                        return true;
 
2088
 
 
2089
                return false;
 
2090
        }
 
2091
 
 
2092
        /**
 
2093
         * Used to determine if the specified attribute is indeed a jpegPhoto. If the
 
2094
         * specified attribute is one that houses jpeg data, true is returned. Otherwise
 
2095
         * this function returns false.
 
2096
         *
 
2097
         * @param string $attr_name The name of the attribute to test.
 
2098
         * @return boolean
 
2099
         * @see draw_jpeg_photo
 
2100
         */
 
2101
        function isJpegPhoto($attr_name) {
 
2102
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
2103
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
2104
 
 
2105
                # easy quick check
 
2106
                if (! strcasecmp($attr_name,'jpegPhoto') || ! strcasecmp($attr_name,'photo'))
 
2107
                        return true;
 
2108
 
 
2109
                # go to the schema and get the Syntax OID
 
2110
                $sattr = $this->getSchemaAttribute($attr_name);
 
2111
                if (! $sattr)
 
2112
                        return false;
 
2113
 
 
2114
                $oid = $sattr->getSyntaxOID();
 
2115
                $type = $sattr->getType();
 
2116
 
 
2117
                if (! strcasecmp($type,'JPEG') || ($oid == '1.3.6.1.4.1.1466.115.121.1.28'))
 
2118
                        return true;
 
2119
 
 
2120
                return false;
 
2121
        }
 
2122
 
 
2123
        /**
 
2124
         * Given an attribute name and server ID number, this function returns
 
2125
         * whether the attrbiute contains boolean data. This is useful for
 
2126
         * developers who wish to display the contents of a boolean attribute
 
2127
         * with a drop-down.
 
2128
         *
 
2129
         * @param string $attr_name The name of the attribute to test.
 
2130
         * @return boolean
 
2131
         */
 
2132
        function isAttrBoolean($attr_name) {
 
2133
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
2134
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
2135
 
 
2136
                $type = ($sattr = $this->getSchemaAttribute($attr_name)) ? $sattr->getType() : null;
 
2137
 
 
2138
                if (! strcasecmp('boolean',$type) ||
 
2139
                        ! strcasecmp('isCriticalSystemObject',$attr_name) ||
 
2140
                        ! strcasecmp('showInAdvancedViewOnly',$attr_name))
 
2141
                        return true;
 
2142
 
 
2143
                else
 
2144
                        return false;
 
2145
        }
 
2146
 
 
2147
        /**
 
2148
         * Given an attribute name and server ID number, this function returns
 
2149
         * whether the attribute may contain binary data. This is useful for
 
2150
         * developers who wish to display the contents of an arbitrary attribute
 
2151
         * but don't want to dump binary data on the page.
 
2152
         *
 
2153
         * @param string $attr_name The name of the attribute to test.
 
2154
         * @return boolean
 
2155
         *
 
2156
         * @see isJpegPhoto
 
2157
         */
 
2158
        function isAttrBinary($attr_name) {
 
2159
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
2160
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
2161
 
 
2162
                /**
 
2163
                 * Determining if an attribute is binary can be an expensive operation.
 
2164
                 * We cache the results for each attr name on each server in the $attr_cache
 
2165
                 * to speed up subsequent calls. The $attr_cache looks like this:
 
2166
                 *
 
2167
                 * Array
 
2168
                 * 0 => Array
 
2169
                 *      'objectclass' => false
 
2170
                 *      'cn' => false
 
2171
                 *      'usercertificate' => true
 
2172
                 * 1 => Array
 
2173
                 *      'jpegphoto' => true
 
2174
                 *      'cn' => false
 
2175
                 */
 
2176
 
 
2177
                static $attr_cache;
 
2178
 
 
2179
                $attr_name = strtolower($attr_name);
 
2180
 
 
2181
                if (isset($attr_cache[$this->index][$attr_name]))
 
2182
                        return $attr_cache[$this->index][$attr_name];
 
2183
 
 
2184
                if ($attr_name == 'userpassword') {
 
2185
                        $attr_cache[$this->index][$attr_name] = false;
 
2186
                        return false;
 
2187
                }
 
2188
 
 
2189
                # Quick check: If the attr name ends in ";binary", then it's binary.
 
2190
                if (strcasecmp(substr($attr_name,strlen($attr_name) - 7),';binary') == 0) {
 
2191
                        $attr_cache[$this->index][$attr_name] = true;
 
2192
                        return true;
 
2193
                }
 
2194
 
 
2195
                # See what the server schema says about this attribute
 
2196
                $sattr = $this->getSchemaAttribute($attr_name);
 
2197
                if (! is_object($sattr)) {
 
2198
 
 
2199
                        /* Strangely, some attributeTypes may not show up in the server
 
2200
                         * schema. This behavior has been observed in MS Active Directory.*/
 
2201
                        $type = null;
 
2202
                        $syntax = null;
 
2203
 
 
2204
                } else {
 
2205
                        $type = $sattr->getType();
 
2206
                        $syntax = $sattr->getSyntaxOID();
 
2207
                }
 
2208
 
 
2209
                if (strcasecmp($type,'Certificate') == 0 ||
 
2210
                        strcasecmp($type,'Binary') == 0 ||
 
2211
                        strcasecmp($attr_name,'usercertificate') == 0 ||
 
2212
                        strcasecmp($attr_name,'usersmimecertificate') == 0 ||
 
2213
                        strcasecmp($attr_name,'networkaddress') == 0 ||
 
2214
                        strcasecmp($attr_name,'objectGUID') == 0 ||
 
2215
                        strcasecmp($attr_name,'objectSID') == 0 ||
 
2216
                        strcasecmp($attr_name,'auditingPolicy') == 0 ||
 
2217
                        strcasecmp($attr_name,'jpegPhoto') == 0 ||
 
2218
                        $syntax == '1.3.6.1.4.1.1466.115.121.1.10' ||
 
2219
                        $syntax == '1.3.6.1.4.1.1466.115.121.1.28' ||
 
2220
                        $syntax == '1.3.6.1.4.1.1466.115.121.1.5' ||
 
2221
                        $syntax == '1.3.6.1.4.1.1466.115.121.1.8' ||
 
2222
                        $syntax == '1.3.6.1.4.1.1466.115.121.1.9'
 
2223
                ) {
 
2224
 
 
2225
                        $attr_cache[$this->index][$attr_name] = true;
 
2226
                        return true;
 
2227
 
 
2228
                } else {
 
2229
                        $attr_cache[$this->index][$attr_name] = false;
 
2230
                        return false;
 
2231
                }
 
2232
        }
 
2233
 
 
2234
        /**
 
2235
         * This function will test if a user is a member of a group.
 
2236
         *
 
2237
         * Inputs:
 
2238
         * @param string $user membership value that is being checked
 
2239
         * @param dn $group DN to see if user is a member
 
2240
         * @return bool true|false
 
2241
         */
 
2242
        function userIsMember($user,$group) {
 
2243
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
2244
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
2245
 
 
2246
                $user = strtolower($user);
 
2247
                $group = $this->getDNAttrValues($group);
 
2248
 
 
2249
                # If you are using groupOfNames objectClass
 
2250
                if (array_key_exists('member',$group) && ! is_array($group['member']))
 
2251
                        $group['member'] = array($group['member']);
 
2252
 
 
2253
                if (array_key_exists('member',$group) &&
 
2254
                        in_array($user,arrayLower($group['member'])))
 
2255
 
 
2256
                        return true;
 
2257
 
 
2258
                # If you are using groupOfUniqueNames objectClass
 
2259
                if (array_key_exists('uniquemember',$group) && ! is_array($group['uniquemember']))
 
2260
                        $group['uniquemember'] = array($group['uniquemember']);
 
2261
 
 
2262
                if (array_key_exists('uniquemember',$group) &&
 
2263
                        in_array($user,arrayLower($group['uniquemember'])))
 
2264
 
 
2265
                        return true;
 
2266
 
 
2267
                return false;
 
2268
        }
 
2269
 
 
2270
        /**
 
2271
         * This function will determine if the user is allowed to login based on a filter
 
2272
         */
 
2273
        protected function userIsAllowedLogin($dn) {
 
2274
                if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
 
2275
                        debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
 
2276
 
 
2277
                $dn = trim(strtolower($dn));
 
2278
 
 
2279
                if (! $this->getValue('login','allowed_dns'))
 
2280
                        return true;
 
2281
 
 
2282
        foreach ($this->getValue('login','allowed_dns') as $login_allowed_dn) {
 
2283
            if (DEBUG_ENABLED)
 
2284
                debug_log('Working through (%s)',80,0,__FILE__,__LINE__,__METHOD__,$login_allowed_dn);
 
2285
 
 
2286
            /* Check if $login_allowed_dn is an ldap search filter
 
2287
             * Is first occurence of 'filter=' (case ensitive) at position 0 ? */
 
2288
            if (preg_match('/^\([&|]\(/',$login_allowed_dn)) {
 
2289
                                $query = array();
 
2290
                $query['filter'] = $login_allowed_dn;
 
2291
                                $query['attrs'] = array('dn');
 
2292
 
 
2293
                foreach($this->getBaseDN() as $base_dn) {
 
2294
                                        $query['base'] = $base_dn;
 
2295
 
 
2296
                    $results = $this->query($query,null);
 
2297
 
 
2298
                    if (DEBUG_ENABLED)
 
2299
                        debug_log('Search, Filter [%s], BaseDN [%s] Results [%s]',16,0,__FILE__,__LINE__,__METHOD__,
 
2300
                            $query['filter'],$query['base'],$results);
 
2301
 
 
2302
                    if ($results) {
 
2303
                        $dn_array = array();
 
2304
 
 
2305
                        foreach ($results as $result)
 
2306
                            array_push($dn_array,$result['dn']);
 
2307
 
 
2308
                        $dn_array = array_unique($dn_array);
 
2309
 
 
2310
                        if (count($dn_array))
 
2311
                            foreach ($dn_array as $result_dn) {
 
2312
                                if (DEBUG_ENABLED)
 
2313
                                    debug_log('Comparing with [%s]',80,0,__FILE__,__LINE__,__METHOD__,$result_dn);
 
2314
 
 
2315
                                # Check if $result_dn is a user DN
 
2316
                                if (strcasecmp($dn,trim(strtolower($result_dn))) == 0)
 
2317
                                    return true;
 
2318
 
 
2319
                                # Check if $result_dn is a group DN
 
2320
                                if ($this->userIsMember($dn,$result_dn))
 
2321
                                    return true;
 
2322
                        }
 
2323
                    }
 
2324
                }
 
2325
            }
 
2326
 
 
2327
            # Check if $login_allowed_dn is a user DN
 
2328
            if (strcasecmp($dn,trim(strtolower($login_allowed_dn))) == 0)
 
2329
                return true;
 
2330
 
 
2331
            # Check if $login_allowed_dn is a group DN
 
2332
            if ($this->userIsMember($dn,$login_allowed_dn))
 
2333
                return true;
 
2334
        }
 
2335
 
 
2336
        return false;
 
2337
        }
 
2338
}
 
2339
?>