3
* Classes and functions for communication of Data Stores
5
* @author The phpLDAPadmin development team
6
* @package phpLDAPadmin
10
* This abstract class provides the basic variables and methods for LDAP datastores
12
* @package phpLDAPadmin
13
* @subpackage DataStore
15
class ldap extends DS {
16
# If we fail to connect, set this to true
17
private $noconnect = false;
19
private $_schema_entries = null;
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();
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);
29
$this->index = $index;
32
# Additional values that can go in our config.php
33
$this->custom = new StdClass;
34
$this->default = new StdClass;
38
# Database Server Variables
39
$this->default->server['db'] = array(
40
'desc'=>'Database Name',
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',
51
$this->default->server['base'] = array(
52
'desc'=>'LDAP Base DNs',
55
$this->default->server['tls'] = array(
56
'desc'=>'Connect using TLS',
60
$this->default->login['attr'] = array(
61
'desc'=>'Attribute to use to find the users DN',
64
$this->default->login['anon_bind'] = array(
65
'desc'=>'Enable anonymous bind logins',
68
$this->default->login['allowed_dns'] = array(
69
'desc'=>'Limit logins to users who match any of the following LDAP filters',
72
$this->default->login['base'] = array(
73
'desc'=>'Limit logins to users who are in these base DNs',
76
$this->default->login['class'] = array(
77
'desc'=>'Strict login to users containing a specific objectClasses',
80
$this->default->proxy['attr'] = array(
81
'desc'=>'Attribute to use to find the users DN for proxy based authentication',
85
$this->default->server['sasl'] = array(
86
'desc'=>'Use SASL authentication when binding LDAP server',
89
$this->default->sasl['mech'] = array(
90
'desc'=>'SASL mechanism used while binding LDAP server',
94
$this->default->sasl['realm'] = array(
95
'desc'=>'SASL realm name',
99
$this->default->sasl['authz_id'] = array(
100
'desc'=>'SASL authorization id',
104
$this->default->sasl['authz_id_regex'] = array(
105
'desc'=>'SASL authorization id PCRE regular expression',
109
$this->default->sasl['authz_id_replacement'] = array(
110
'desc'=>'SASL authorization id PCRE regular expression replacement string',
114
$this->default->sasl['props'] = array(
115
'desc'=>'SASL properties',
121
* Required ABSTRACT functions
124
* Connect and Bind to the Database
126
* @param string Which connection method resource to use
127
* @return resource|null Connection resource if successful, null if not.
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);
133
static $CACHE = array();
135
$method = $this->getMethod($method);
138
if (isset($CACHE[$this->index][$method]) && $CACHE[$this->index][$method])
139
return $CACHE[$this->index][$method];
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);
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')))
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;
155
return $CACHE[$this->index][$method];
159
$CACHE[$this->index][$method] = null;
161
# No identifiable connection exists, lets create a new one.
163
debug_log('Creating NEW connection [%s] for index [%s]',16,0,__FILE__,__LINE__,__METHOD__,
164
$method,$this->index);
166
if (function_exists('run_hook'))
167
run_hook('pre_connect',array('server_id'=>$this->index,'method'=>$method));
169
if ($this->getValue('server','port'))
170
$resource = ldap_connect($this->getValue('server','host'),$this->getValue('server','port'));
172
$resource = ldap_connect($this->getValue('server','host'));
174
$CACHE[$this->index][$method] = $resource;
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'));
180
if (! is_resource($resource))
181
debug_dump_backtrace('UNHANDLED, $resource is not a resource',1);
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);
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);
191
# Try to fire up TLS is specified in the config
192
if ($this->isTLSEnabled())
193
$this->startTLS($resource);
195
# If SASL has been configured for binding, then start it now.
196
if ($this->isSASLEnabled())
197
$bind['result'] = $this->startSASL($resource,$method);
201
$bind['result'] = @ldap_bind($resource,$bind['id'],$bind['pass']);
204
debug_dump(array('method'=>$method,'bind'=>$bind,'USER'=>$_SESSION['USER']));
207
debug_log('Resource [%s], Bind Result [%s]',16,0,__FILE__,__LINE__,__METHOD__,$resource,$bind);
209
if (! $bind['result']) {
211
debug_log('Leaving with FALSE, bind FAILed',16,0,__FILE__,__LINE__,__METHOD__);
213
$this->noconnect = true;
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),
220
$CACHE[$this->index][$method] = null;
223
$this->noconnect = false;
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;
233
if (function_exists('run_hook'))
234
run_hook('post_connect',array('server_id'=>$this->index,'method'=>$method,'id'=>$bind['id']));
237
debug_dump(array($method=>$CACHE[$this->index][$method]));
239
return $CACHE[$this->index][$method];
243
* Login to the database with the application user/password
245
* @return boolean true|false for successful login.
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);
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')
259
$userDN = $this->getLoginID($user,'login');
261
if (! $userDN && $this->getValue('login','fallback_dn'))
268
if (in_array($method,array('user','anon'))) {
274
$userDN = $this->getLogin('user');
275
$pass = $this->getPassword('user');
279
if (! $this->isAnonBindAllowed() && ! trim($userDN))
282
# Temporarily set our user details
283
$this->setLogin($userDN,$pass,$method);
285
$connect = $this->connect($method,false,$new);
287
# If we didnt log in...
288
if (! is_resource($connect) || $this->noconnect || ! $this->userIsAllowedLogin($userDN)) {
289
$this->logout($method);
298
* Perform a query to the Database
300
* @param string query to perform
304
* $query['attrs'] = array();
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.
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);
318
if (! isset($query['attrs']))
319
$query['attrs'] = array();
321
# Re-index the attrs, PHP throws an error if the keys are not sequential from 0.
322
$query['attrs'] = array_values($query['attrs']);
324
if (! isset($query['base'])) {
325
$bases = $this->getBaseDN();
326
$query['base'] = array_shift($bases);
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;
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']),
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.'),
356
debug_log('%s search PREPARE.',16,0,__FILE__,__LINE__,__METHOD__,$query['scope']);
359
debug_dump(array('query'=>$query,'server'=>$this->getIndex(),'con'=>$this->connect($method)));
361
$resource = $this->connect($method,$debug);
363
switch ($query['scope']) {
365
$search = @ldap_read($resource,$query['base'],$query['filter'],$query['attrs'],$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']);
369
$search = @ldap_list($resource,$query['base'],$query['filter'],$query['attrs'],$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']);
374
$search = @ldap_search($resource,$query['base'],$query['filter'],$query['attrs'],$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']);
379
debug_dump(array('method'=>$method,'search'=>$search,'error'=>$this->getErrorMessage()));
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));
390
# Get the first entry identifier
391
if ($entries = ldap_get_entries($resource,$search)) {
393
if (isset($entries['count']))
394
unset($entries['count']);
396
# Iterate over the entries
397
foreach ($entries as $a => $entry) {
398
if (! isset($entry['dn']))
399
debug_dump_backtrace('No DN?',1);
401
# Remove the none entry references.
402
if (! is_array($entry)) {
410
# Iterate over the attributes
411
foreach ($entry as $b => $attrs) {
412
# Remove the none entry references.
413
if (! is_array($attrs)) {
419
if (isset($entry[$b]['count']))
420
unset($entry[$b]['count']);
423
# Our queries always include the DN (the only value not an array).
425
$return[$dn] = $entry;
429
foreach ($return as $key=> $values)
430
ksort($return[$key]);
434
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
440
* Get the last error string
442
* @param string Which connection method resource to use
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);
448
return ldap_error($this->connect($method));
452
* Get the last error number
454
* @param string Which connection method resource to use
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);
460
return ldap_errno($this->connect($method));
464
* Additional functions
469
* @param string Which connection method resource to use
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);
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');
480
foreach ($this->getLoginBaseDN() as $base) {
481
$query['base'] = $base;
482
$result = $this->query($query,$method);
484
if (count($result) == 1)
488
if (count($result) != 1)
491
$detail = array_shift($result);
493
if (! isset($detail['dn']))
494
die('ERROR: DN missing?');
496
return $detail['dn'];
500
* Return the login base DNs
501
* If no login base DNs are defined, then the LDAP server Base DNs are used.
503
private function getLoginBaseDN() {
504
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
505
debug_log('Entered (%%)',17,1,__FILE__,__LINE__,__METHOD__,$fargs);
507
if ($this->getValue('login','base'))
508
return $this->getValue('login','base');
510
return $this->getBaseDN();
514
* Return the login classes that a user must have to login
516
private function getLoginClass() {
517
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
518
debug_log('Entered (%%)',17,1,__FILE__,__LINE__,__METHOD__,$fargs);
520
return $this->getValue('login','class');
524
* Return if anonymous bind is allowed in the configuration
526
public function isAnonBindAllowed() {
527
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
528
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
530
return $this->getValue('login','anon_bind');
534
* Fetches whether TLS has been configured for use with a certain server.
536
* Users may configure phpLDAPadmin to use TLS in config,php thus:
538
* $servers->setValue('server','tls',true|false);
543
private function isTLSEnabled() {
544
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
545
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
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');
552
return $this->getValue('server','tls');
556
* If TLS is configured, then start it
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);
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.')),
575
* Fetches whether SASL has been configured for use with a certain server.
577
* Users may configure phpLDAPadmin to use SASL in config,php thus:
579
* $servers->setValue('server','sasl',true|false);
584
private function isSASLEnabled() {
585
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
586
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
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');
593
return $this->getValue('server','sasl');
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
600
* @todo This has not been tested, please let the developers know if this function works as expected.
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);
606
static $CACHE = array();
608
switch (strtolower($this->getValue('sasl','mech'))) {
610
if (isset($_ENV['REDIRECT_KRB5CCNAME']))
611
putenv(sprintf('KRB5CCNAME={%s}',$_ENV['REDIRECT_KRB5CCNAME']));
616
if (! $this->getValue('server','sasl') || ! function_exists('ldap_start_tls'))
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);
624
# Do we need to rewrite authz_id?
625
if (! isset($CACHE['authz_id']))
626
if (! trim($this->getValue('sasl','authz_id'))) {
629
debug_log('Rewriting bind DN [%s] -> authz_id with regex [%s] and replacement [%s].',9,0,__FILE__,__LINE__,__METHOD__,
631
$this->getValue('sasl','authz_id_regex'),
632
$this->getValue('sasl','authz_id_replacement'));
634
$CACHE['authz_id'] = @preg_replace($this->getValue('sasl','authz_id_regex'),
635
$this->getValue('sasl','authz_id_replacement'),$CACHE['login_dn']);
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');
644
debug_log('Resource [%s], SASL OPTIONS: mech [%s], realm [%s], authz_id [%s], props [%s]',9,0,__FILE__,__LINE__,__METHOD__,
646
$this->getValue('sasl','mech'),
647
$this->getValue('sasl','realm'),
649
$this->getValue('sasl','props'));
652
$CACHE['authz_id'] = $this->getValue('sasl','authz_id');
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'),
659
$this->getValue('sasl','props'));
663
* Fetches whether PROXY AUTH has been configured for use with a certain server.
665
* Users may configure phpLDAPadmin to use PROXY AUTH in config,php thus:
667
* $servers->setValue('login','auth_type','proxy');
672
private function isProxyEnabled() {
673
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
674
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
676
return $this->getValue('login','auth_type') == 'proxy' ? true : false;
680
* If PROXY AUTH is configured, then start it
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);
686
$rootdse = $this->getRootDSE();
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')),
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),
717
$filter .= sprintf('(%s=%s)',$attr,$_SERVER[$var]);
727
$query['filter'] = $filter;
729
foreach ($this->getBaseDN() as $base) {
730
$query['base'] = $base;
732
if ($search = $this->query($query,$method))
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)),
745
$search = array_pop($search);
750
'oid'=>'2.16.840.1.113730.3.4.18',
751
'value'=>sprintf('dn:%s',$dn),
752
'iscritical' => true);
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),
763
$_SESSION['USER'][$this->index][$method]['proxy'] = blowfish_encrypt($dn);
769
* Modify attributes of a DN
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);
775
# We need to supress the error here - programming should detect and report it.
776
return @ldap_mod_replace($this->connect($method),$dn,$attrs);
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).
784
* Tested with OpenLDAP 2.0, Netscape iPlanet, and Novell eDirectory 8.7 (nldap.com)
785
* Please report any and all bugs!!
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.
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.
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);
800
$method = $this->getMethod($method);
803
if (isset($CACHE[$this->index][$method]))
804
return $CACHE[$this->index][$method];
806
# If the base is set in the configuration file, then just return that.
807
if (count($this->getValue('server','base'))) {
809
debug_log('Return BaseDN from Config [%s]',17,0,__FILE__,__LINE__,__METHOD__,implode('|',$this->getValue('server','base')));
811
$CACHE[$this->index][$method] = $this->getValue('server','base');
813
# We need to figure it out.
816
debug_log('Connect to LDAP to find BaseDN',80,0,__FILE__,__LINE__,__METHOD__);
818
# Set this to empty, in case we loop back here looking for the baseDNs
819
$CACHE[$this->index][$method] = array();
821
$results = $this->getDNAttrValues('',$method);
823
if (isset($results['namingcontexts'])) {
825
debug_log('LDAP Entries:%s',80,0,__FILE__,__LINE__,__METHOD__,implode('|',$results['namingcontexts']));
827
$result = $results['namingcontexts'];
830
$CACHE[$this->index][$method] = $result;
833
return $CACHE[$this->index][$method];
837
* Gets whether an entry exists based on its DN. If the entry exists,
838
* returns true. Otherwise returns false.
840
* @param string The DN of the entry of interest.
841
* @param string Which connection method resource to use
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);
848
$results = $this->getDNAttrValues($dn,$method);
857
* Given a DN string, this returns the top container portion of the string.
859
* @param string The DN whose container string to return.
860
* @return string The container
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);
868
foreach ($this->getBaseDN() as $base) {
869
if (preg_match("/${base}$/i",$dn)) {
876
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
882
* Given a DN string and a path like syntax, this returns the parent container portion of the string.
884
* @param string The DN whose container string to return.
885
* @param string Either '/', '.' or something like '../../<rdn>'
886
* @return string The container
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);
892
$top = $this->getContainerTop($dn);
894
if ($path[0] == '/') {
896
$path = substr($path,1);
898
} elseif ($path == '.') {
902
$parenttree = explode('/',$path);
904
foreach ($parenttree as $key => $value) {
905
if ($value == '..') {
906
if ($this->getContainer($dn))
907
$dn = $this->getContainer($dn);
913
$dn = sprintf('%s,%s',$value,$dn);
920
debug_dump(array(__METHOD__,'dn'=>$dn,'path'=>$path));
921
debug_dump_backtrace('Container is empty?',1);
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'.
932
* @param string The DN whose container string to return.
933
* @return string The container
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);
939
$parts = $this->explodeDN($dn);
941
if (count($parts) <= 1)
947
for ($i=2;$i<count($parts);$i++)
948
$return .= sprintf(',%s',$parts[$i]);
952
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
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:
972
* Calling <code>getContainerContents("ou=people,dc=example,dc=com")</code>
973
* would return the following list:
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.
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);
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);
1006
foreach ($results as $index => $entry) {
1007
$child_dn = $entry['dn'];
1008
array_push($return,$child_dn);
1013
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
1022
* Explode a DN into an array of its RDN parts.
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)
1027
* @return array An array of RDN parts of this format:
1038
* NOTE: When a multivalue RDN is passed to ldap_explode_dn, the results returns with 'value + value';
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);
1046
if (isset($CACHE['explode'][$dn][$with_attributes])) {
1048
debug_log('Return CACHED result (%s) for (%s)',1,0,__FILE__,__LINE__,__METHOD__,
1049
$CACHE['explode'][$dn][$with_attributes],$dn);
1051
return $CACHE['explode'][$dn][$with_attributes];
1054
$dn = addcslashes($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]) {
1061
debug_log('Returning NULL - NO result.',1,0,__FILE__,__LINE__,__METHOD__);
1066
# Remove our count value that ldap_explode_dn returns us.
1067
unset($result[0]['count']);
1068
unset($result[1]['count']);
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);
1075
$CACHE['explode'][implode(',',$result[0])][$key] = $result[$key];
1076
$CACHE['explode'][implode(',',array_reverse($result[0]))][$key] = array_reverse($result[$key]);
1080
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$result[$with_attributes]);
1082
return $result[$with_attributes];
1086
* Parse a DN and escape any special characters
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);
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);
1099
$dn = preg_replace('/([^\\\\]),(\s*[^=]*\s*)([^,])$/','$1\\\\2C$2$3',$dn);
1102
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$dn);
1108
* Parse a DN and unescape any special characters
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);
1114
if (is_array($dn)) {
1116
foreach ($dn as $key => $rdn)
1117
$a[$key] = preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$rdn);
1122
return preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$dn);
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);
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);
1136
if (is_array($results) && count($results) == 1)
1137
return array_change_key_case(array_pop($results));
1142
/** Schema Methods **/
1144
* This function will query the ldap server and request the subSchemaSubEntry which should be the Schema DN.
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.
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.
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);
1157
# If we already got the SchemaDN, then return it.
1158
if ($this->_schemaDN)
1159
return $this->_schemaDN;
1161
if (! $this->connect($method))
1164
$search = @ldap_read($this->connect($method),$dn,'objectclass=*',array('subschemaSubentry'),false,0,10,LDAP_DEREF_NEVER);
1167
debug_log('Search returned (%s)',24,0,__FILE__,__LINE__,__METHOD__,is_resource($search));
1169
# Fix for broken ldap.conf configuration.
1170
if (! $search && ! $dn) {
1172
debug_log('Trying to find the DN for "broken" ldap.conf',80,0,__FILE__,__LINE__,__METHOD__);
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);
1179
debug_log('Search returned (%s) for base (%s)',24,0,__FILE__,__LINE__,__METHOD__,
1180
is_resource($search),$base);
1191
if (! @ldap_count_entries($this->connect($method),$search)) {
1193
debug_log('Search returned 0 entries. Returning NULL',25,0,__FILE__,__LINE__,__METHOD__);
1198
$entries = @ldap_get_entries($this->connect($method),$search);
1201
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$entries);
1203
if (! $entries || ! is_array($entries))
1206
$entry = isset($entries[0]) ? $entries[0] : false;
1209
debug_log('Entry is false, Returning NULL',80,0,__FILE__,__LINE__,__METHOD__);
1214
$sub_schema_sub_entry = isset($entry[0]) ? $entry[0] : false;
1215
if (! $sub_schema_sub_entry) {
1217
debug_log('Sub Entry is false, Returning NULL',80,0,__FILE__,__LINE__,__METHOD__);
1222
$this->_schemaDN = isset($entry[$sub_schema_sub_entry][0]) ? $entry[$sub_schema_sub_entry][0] : false;
1225
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->_schemaDN);
1227
return $this->_schemaDN;
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".
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.
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'.
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:
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 ...
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);
1261
$valid_schema_to_fetch = array('objectclasses','attributetypes','ldapsyntaxes','matchingrules','matchingruleuse');
1263
if (! $this->connect($method) || $this->noconnect)
1267
$schema_to_fetch = strtolower($schema_to_fetch);
1269
if (! is_null($this->_schema_entries) && isset($this->_schema_entries[$schema_to_fetch])) {
1270
$schema = $this->_schema_entries[$schema_to_fetch];
1273
debug_log('Returning CACHED (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema);
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');
1283
# Try to get the schema DN from the specified entry.
1284
$schema_dn = $this->getSchemaDN($method,$dn);
1286
# Do we need to try again with the Root DSE?
1287
if (! $schema_dn && trim($dn))
1288
$schema_dn = $this->getSchemaDN($method,'');
1290
# Store the eventual schema retrieval in $schema_search
1291
$schema_search = null;
1295
debug_log('Using Schema DN (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_dn);
1297
foreach (array('(objectClass=*)','(objectClass=subschema)') as $schema_filter) {
1299
debug_log('Looking for schema with Filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_filter);
1301
$schema_search = @ldap_read($this->connect($method),$schema_dn,$schema_filter,array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
1303
if (is_null($schema_search))
1306
$schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
1309
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
1311
if (is_array($schema_entries) && isset($schema_entries['count']) && $schema_entries['count']) {
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);
1320
debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_filter);
1322
unset($schema_entries);
1323
$schema_search = null;
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)) {
1331
debug_log('Attempting work-arounds for "broken" LDAP servers...',24,0,__FILE__,__LINE__,__METHOD__);
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=*)';
1339
# OpenLDAP and Novell
1340
$ldap['OpenLDAP']['cn=subschema'] = '(objectClass=*)';
1342
foreach ($ldap as $ldap_server_name => $ldap_options) {
1343
foreach ($ldap_options as $ldap_dn => $ldap_filter) {
1345
debug_log('Attempting [%s] (%s) (%s)<BR>',24,0,__FILE__,__LINE__,__METHOD__,
1346
$ldap_server_name,$ldap_dn,$ldap_filter);
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))
1352
$schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
1355
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
1357
if ($schema_entries && isset($schema_entries[0][$schema_to_fetch])) {
1359
debug_log('Found schema with filter of (%s)',24,0,__FILE__,__LINE__,__METHOD__,$ldap_filter);
1365
debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$ldap_filter);
1367
unset($schema_entries);
1368
$schema_search = null;
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) {
1381
debug_log('Attempting to find schema with scope (%s), filter (objectClass=*) and a blank base.',24,0,__FILE__,__LINE__,__METHOD__,
1384
switch ($ldap_scope) {
1386
$schema_search = @ldap_read($this->connect($method),'','(objectClass=*)',array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
1390
$schema_search = @ldap_list($this->connect($method),'','(objectClass=*)',array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
1394
if (is_null($schema_search))
1397
$schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
1399
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
1401
if ($schema_entries && isset($schema_entries[0][$schema_to_fetch])) {
1403
debug_log('Found schema with filter of (%s)',24,0,__FILE__,__LINE__,__METHOD__,'(objectClass=*)');
1409
debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,'(objectClass=*)');
1411
unset($schema_entries);
1412
$schema_search = null;
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');
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),
1429
debug_log('Returning because schema_search is NULL ()',25,0,__FILE__,__LINE__,__METHOD__);
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;
1436
if (! $schema_entries) {
1439
debug_log('Returning false since ldap_get_entries() returned false.',25,0,__FILE__,__LINE__,__METHOD__,$return);
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');
1453
debug_log('Returning because (%s) isnt in the schema array. (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema_to_fetch,$return);
1459
/* Make a nice array of this form:
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 ...)"
1465
$schema = $schema_entries[0][$schema_to_fetch];
1466
unset($schema['count']);
1467
$this->_schema_entries[$schema_to_fetch] = $schema;
1470
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema);
1476
* Gets a single ObjectClass object specified by name.
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).
1482
* @return ObjectClass The specified ObjectClass object or false on error.
1485
* @see SchemaObjectClasses
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);
1491
$oclass_name = strtolower($oclass_name);
1492
$socs = $this->SchemaObjectClasses($method,$dn);
1494
# Default return value
1497
if (isset($socs[$oclass_name]))
1498
$return = $socs[$oclass_name];
1501
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
1507
* Gets a single AttributeType object specified by name.
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).
1513
* @return AttributeType The specified AttributeType object or false on error.
1515
* @see AttributeType
1516
* @see SchemaAttributes
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);
1522
$attr_name = strtolower($attr_name);
1523
$sattrs = $this->SchemaAttributes($method,$dn);
1525
# Default return value
1528
if (isset($sattrs[$attr_name]))
1529
$return = $sattrs[$attr_name];
1532
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
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.
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).
1545
* @return array An array of ObjectClass objects.
1548
* @see getSchemaObjectClass
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);
1554
# Set default return
1557
if ($return = get_cached_item($this->index,'schema','objectclasses')) {
1559
debug_log('Returning CACHED [%s] (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'objectclasses');
1564
$raw = $this->getRawSchema($method,'objectclasses',$dn);
1567
# Build the array of objectClasses
1570
foreach ($raw as $line) {
1571
if (is_null($line) || ! strlen($line))
1574
$object_class = new ObjectClass($line,$this);
1575
$return[$object_class->getName()] = $object_class;
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));
1586
# cache the schema to prevent multiple schema fetches from LDAP server
1587
set_cached_item($this->index,'schema','objectclasses',$return);
1591
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
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.
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).
1604
* @return array An array of AttributeType objects.
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);
1610
# Set default return
1613
if ($return = get_cached_item($this->index,'schema','attributes')) {
1615
debug_log('(): Returning CACHED [%s] (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'attributes');
1620
$raw = $this->getRawSchema($method,'attributeTypes',$dn);
1623
# build the array of attribueTypes
1624
$syntaxes = $this->SchemaSyntaxes($method,$dn);
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
1633
$attrs_oid = array();
1634
foreach ($raw as $line) {
1635
if (is_null($line) || ! strlen($line))
1638
$attr = new AttributeType($line);
1639
if (isset($syntaxes[$attr->getSyntaxOID()])) {
1640
$syntax = $syntaxes[$attr->getSyntaxOID()];
1641
$attr->setType($syntax->getDescription());
1643
$attrs[$attr->getName()] = $attr;
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
1650
$attrs_oid[$attr->getOID()] = &$attrs[$attr->getName()];
1653
# go back and add data from aliased attributeTypes
1654
foreach ($attrs as $name => $attr) {
1655
$aliases = $attr->getAliases();
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;
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;
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();
1677
if (trim($sup_attr_name)) {
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.*/
1683
while ($i++<100 /** 100 == INFINITY ;) */) {
1685
if (isset($attrs_oid[$sup_attr_name])) {
1686
$attr->setSupAttribute($attrs_oid[$sup_attr_name]->getName());
1687
$sup_attr_name = $attr->getSupAttribute();
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');
1696
$sup_attr = $attrs[strtolower($sup_attr_name)];
1697
$sup_attr_name = $sup_attr->getSupAttribute();
1699
# Does this superior attributeType not have a superior attributeType?
1700
if (is_null($sup_attr_name) || strlen(trim($sup_attr_name)) == 0) {
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();
1713
/* clone the SUP attributeType and populate those values
1714
* that were set by the child attributeType */
1715
$attr = clone $sup_attr;
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);
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);
1728
/* replace this attribute in the attrs array now that we have populated
1729
new values therein */
1730
$attrs[$key] = $attr;
1732
# very important: break out after we are done with this attribute
1733
$sup_attr_name = null;
1743
# Add the used in and required_by values.
1744
$socs = $this->SchemaObjectClasses($method);
1745
if (! is_array($socs))
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));
1754
foreach ($oclass_attrs as $attr_name)
1755
if (isset($attrs[strtolower($attr_name)]))
1756
$attrs[strtolower($attr_name)]->addUsedInObjectClass($object_class->getName(false));
1759
foreach ($must_attrs as $attr_name)
1760
if (isset($attrs[strtolower($attr_name)]))
1761
$attrs[strtolower($attr_name)]->addRequiredByObjectClass($object_class->getName(false));
1764
foreach ($object_class->getForceMayAttrs() as $attr_name)
1765
if (isset($attrs[strtolower($attr_name->name)]))
1766
$attrs[strtolower($attr_name->name)]->setForceMay();
1771
# cache the schema to prevent multiple schema fetches from LDAP server
1772
set_cached_item($this->index,'schema','attributes',$return);
1776
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
1782
* Returns an array of MatchingRule objects for the specified server.
1783
* The key of each entry is the OID of the matching rule.
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);
1789
# Set default return
1792
if ($return = get_cached_item($this->index,'schema','matchingrules')) {
1794
debug_log('Returning CACHED [%s] (%s).',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'matchingrules');
1799
# build the array of MatchingRule objects
1800
$raw = $this->getRawSchema($method,'matchingRules',$dn);
1805
foreach ($raw as $line) {
1806
if (is_null($line) || ! strlen($line))
1809
$rule = new MatchingRule($line);
1810
$key = $rule->getName();
1811
$rules[$key] = $rule;
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');
1820
if ($raw != false) {
1821
foreach ($raw as $line) {
1822
if (is_null($line) || ! strlen($line))
1825
$rule_use = new MatchingRuleUse($line);
1826
$key = $rule_use->getName();
1828
if (isset($rules[$key]))
1829
$rules[$key]->setUsedByAttrs($rule_use->getUsedByAttrs());
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());
1840
if (isset($rules[$rule_key]))
1841
$rules[$rule_key]->addUsedByAttr($attr->getName(false));
1847
# cache the schema to prevent multiple schema fetches from LDAP server
1848
set_cached_item($this->index,'schema','matchingrules',$return);
1852
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
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.
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);
1865
# Set default return
1868
if ($return = get_cached_item($this->index,'schema','syntaxes')) {
1870
debug_log('Returning CACHED [%s] (%s).',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'syntaxes');
1875
$raw = $this->getRawSchema($method,'ldapSyntaxes',$dn);
1878
# build the array of attributes
1881
foreach ($raw as $line) {
1882
if (is_null($line) || ! strlen($line))
1885
$syntax = new Syntax($line);
1886
$key = strtolower(trim($syntax->getOID()));
1891
$return[$key] = $syntax;
1896
# cache the schema to prevent multiple schema fetches from LDAP server
1897
set_cached_item($this->index,'schema','syntaxes',$return);
1901
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
1907
* This function determines if the specified attribute is contained in the force_may list
1908
* as configured in config.php.
1910
* @return boolean True if the specified attribute is in the $force_may list and false
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);
1917
return in_array($attr_name,$this->force_may);
1921
* Much like getDNAttrValues(), but only returns the values for
1922
* one attribute of an object. Example calls:
1925
* print_r(getDNAttrValue('cn=Bob,ou=people,dc=example,dc=com','sn'));
1930
* print_r(getDNAttrValue('cn=Bob,ou=people,dc=example,dc=com','objectClass'));
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
1945
* @see getDNAttrValues
1946
* @todo Caching these values may be problematic with multiple calls and different deref values.
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);
1952
# Ensure our attr is in lowercase
1953
$attr = strtolower($attr);
1955
$values = $this->getDNAttrValues($dn,$method,$deref);
1957
if (isset($values[$attr]))
1958
return $values[$attr];
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
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.
1972
* Example of its usage:
1974
* print_r(getDNAttrValues('cn=Bob,ou=pepole,dc=example,dc=com')
1976
* [objectClass] => Array (
1987
* [0] => cn=Bob,ou=pepole,dc=example,dc=com
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
1999
* @see getDNSysAttrs
2000
* @see getDNAttrValue
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);
2009
$method = $this->getMethod($method);
2011
if (in_array('*',$attrs) && in_array('+',$attrs))
2013
elseif (in_array('+',$attrs))
2015
elseif (in_array('*',$attrs))
2018
if (! is_null($cacheindex) && isset($CACHE[$this->index][$method][$dn][$cacheindex])) {
2019
$results = $CACHE[$this->index][$method][$dn][$cacheindex];
2022
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$results);
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);
2033
if (count($results))
2034
$results = array_pop($results);
2036
$results = array_change_key_case($results);
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]);
2043
# Finally sort the results
2046
if (! is_null($cacheindex) && count($results))
2047
$CACHE[$this->index][$method][$dn][$cacheindex] = $results;
2054
* Returns true if the attribute specified is required to take as input a DN.
2055
* Some examples include 'distinguishedName', 'member' and 'uniqueMember'.
2057
* @param string $attr_name The name of the attribute of interest (case insensitive)
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);
2065
$dn_attrs = array('aliasedObjectName');
2066
foreach ($dn_attrs as $dn_attr)
2067
if (strcasecmp($attr_name,$dn_attr) == 0)
2070
# Now look at the schema OID
2071
$sattr = $this->getSchemaAttribute($attr_name);
2075
$syntax_oid = $sattr->getSyntaxOID();
2076
if ('1.3.6.1.4.1.1466.115.121.1.12' == $syntax_oid)
2078
if ('1.3.6.1.4.1.1466.115.121.1.34' == $syntax_oid)
2081
$syntaxes = $this->SchemaSyntaxes($method);
2082
if (! isset($syntaxes[$syntax_oid]))
2085
$syntax_desc = $syntaxes[ $syntax_oid ]->getDescription();
2086
if (strpos(strtolower($syntax_desc),'distinguished name'))
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.
2097
* @param string $attr_name The name of the attribute to test.
2099
* @see draw_jpeg_photo
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);
2106
if (! strcasecmp($attr_name,'jpegPhoto') || ! strcasecmp($attr_name,'photo'))
2109
# go to the schema and get the Syntax OID
2110
$sattr = $this->getSchemaAttribute($attr_name);
2114
$oid = $sattr->getSyntaxOID();
2115
$type = $sattr->getType();
2117
if (! strcasecmp($type,'JPEG') || ($oid == '1.3.6.1.4.1.1466.115.121.1.28'))
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
2129
* @param string $attr_name The name of the attribute to test.
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);
2136
$type = ($sattr = $this->getSchemaAttribute($attr_name)) ? $sattr->getType() : null;
2138
if (! strcasecmp('boolean',$type) ||
2139
! strcasecmp('isCriticalSystemObject',$attr_name) ||
2140
! strcasecmp('showInAdvancedViewOnly',$attr_name))
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.
2153
* @param string $attr_name The name of the attribute to test.
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);
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:
2169
* 'objectclass' => false
2171
* 'usercertificate' => true
2173
* 'jpegphoto' => true
2179
$attr_name = strtolower($attr_name);
2181
if (isset($attr_cache[$this->index][$attr_name]))
2182
return $attr_cache[$this->index][$attr_name];
2184
if ($attr_name == 'userpassword') {
2185
$attr_cache[$this->index][$attr_name] = false;
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;
2195
# See what the server schema says about this attribute
2196
$sattr = $this->getSchemaAttribute($attr_name);
2197
if (! is_object($sattr)) {
2199
/* Strangely, some attributeTypes may not show up in the server
2200
* schema. This behavior has been observed in MS Active Directory.*/
2205
$type = $sattr->getType();
2206
$syntax = $sattr->getSyntaxOID();
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'
2225
$attr_cache[$this->index][$attr_name] = true;
2229
$attr_cache[$this->index][$attr_name] = false;
2235
* This function will test if a user is a member of a group.
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
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);
2246
$user = strtolower($user);
2247
$group = $this->getDNAttrValues($group);
2249
# If you are using groupOfNames objectClass
2250
if (array_key_exists('member',$group) && ! is_array($group['member']))
2251
$group['member'] = array($group['member']);
2253
if (array_key_exists('member',$group) &&
2254
in_array($user,arrayLower($group['member'])))
2258
# If you are using groupOfUniqueNames objectClass
2259
if (array_key_exists('uniquemember',$group) && ! is_array($group['uniquemember']))
2260
$group['uniquemember'] = array($group['uniquemember']);
2262
if (array_key_exists('uniquemember',$group) &&
2263
in_array($user,arrayLower($group['uniquemember'])))
2271
* This function will determine if the user is allowed to login based on a filter
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);
2277
$dn = trim(strtolower($dn));
2279
if (! $this->getValue('login','allowed_dns'))
2282
foreach ($this->getValue('login','allowed_dns') as $login_allowed_dn) {
2284
debug_log('Working through (%s)',80,0,__FILE__,__LINE__,__METHOD__,$login_allowed_dn);
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)) {
2290
$query['filter'] = $login_allowed_dn;
2291
$query['attrs'] = array('dn');
2293
foreach($this->getBaseDN() as $base_dn) {
2294
$query['base'] = $base_dn;
2296
$results = $this->query($query,null);
2299
debug_log('Search, Filter [%s], BaseDN [%s] Results [%s]',16,0,__FILE__,__LINE__,__METHOD__,
2300
$query['filter'],$query['base'],$results);
2303
$dn_array = array();
2305
foreach ($results as $result)
2306
array_push($dn_array,$result['dn']);
2308
$dn_array = array_unique($dn_array);
2310
if (count($dn_array))
2311
foreach ($dn_array as $result_dn) {
2313
debug_log('Comparing with [%s]',80,0,__FILE__,__LINE__,__METHOD__,$result_dn);
2315
# Check if $result_dn is a user DN
2316
if (strcasecmp($dn,trim(strtolower($result_dn))) == 0)
2319
# Check if $result_dn is a group DN
2320
if ($this->userIsMember($dn,$result_dn))
2327
# Check if $login_allowed_dn is a user DN
2328
if (strcasecmp($dn,trim(strtolower($login_allowed_dn))) == 0)
2331
# Check if $login_allowed_dn is a group DN
2332
if ($this->userIsMember($dn,$login_allowed_dn))