From 6599940e6cad8cf7edd88eef064a5e01e2d40a2d Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 14 Jul 2015 12:30:16 +0200 Subject: [PATCH 1/6] Introduce Interface for inspecting ldap connections refs #9605 --- library/Icinga/Data/Inspectable.php | 26 +++++++ .../{Capability.php => LdapCapabilities.php} | 32 ++++++++- .../Icinga/Protocol/Ldap/LdapConnection.php | 68 ++++++++++++++++--- 3 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 library/Icinga/Data/Inspectable.php rename library/Icinga/Protocol/Ldap/{Capability.php => LdapCapabilities.php} (87%) diff --git a/library/Icinga/Data/Inspectable.php b/library/Icinga/Data/Inspectable.php new file mode 100644 index 000000000..1e037113a --- /dev/null +++ b/library/Icinga/Data/Inspectable.php @@ -0,0 +1,26 @@ +attributes->namingContexts; } + + public function getVendor() + { + // AD doesn't include the vendor entry + if ($this->hasAdOid()) { + return 'Microsoft Active Directory'; + } + + if (! isset($this->attributes->vendorName)) { + // OpenLDAP doesn't include the vendor entry + // TODO: bad, remove this and add proper OpenLDAP version checking + return 'OpenLDAP'; + } + return $this->attributes->vendorName; + } + + public function getVersion() + { + // AD doesn't include the version string + if ($this->hasAdOid()) { + // TODO: query AD version from cn=schema,cn=configuration,dc=yourdomain,dc=com attribute:ObjectVersion + } + + if (! isset($this->attributes->vendorVersion)) { + return 'unknown'; + } + return $this->attributes->vendorVersion; + } } diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index 3661e160d..db1cfb6cc 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -3,11 +3,13 @@ namespace Icinga\Protocol\Ldap; +use Exception; use ArrayIterator; use Icinga\Application\Config; use Icinga\Application\Logger; use Icinga\Application\Platform; use Icinga\Data\ConfigObject; +use Icinga\Data\Inspectable; use Icinga\Data\Selectable; use Icinga\Data\Sortable; use Icinga\Exception\ProgrammingError; @@ -16,7 +18,7 @@ use Icinga\Protocol\Ldap\LdapException; /** * Encapsulate LDAP connections and query creation */ -class LdapConnection implements Selectable +class LdapConnection implements Selectable, Inspectable { /** * Indicates that the target object cannot be found @@ -142,7 +144,7 @@ class LdapConnection implements Selectable /** * The properties and capabilities of the LDAP server * - * @var Capability + * @var LdapCapabilities */ protected $capabilities; @@ -160,6 +162,16 @@ class LdapConnection implements Selectable */ protected $encryptionSuccess; + /** + * @var array + */ + protected $info = null; + + /** + * @var Boolean + */ + protected $healthy = null; + /** * Create a new connection object * @@ -243,7 +255,7 @@ class LdapConnection implements Selectable /** * Return the capabilities of the current connection * - * @return Capability + * @return LdapCapabilities */ public function getCapabilities() { @@ -254,7 +266,7 @@ class LdapConnection implements Selectable } catch (LdapException $e) { Logger::debug($e); Logger::warning('LADP discovery failed, assuming default LDAP capabilities.'); - $this->capabilities = new Capability(); // create empty default capabilities + $this->capabilities = new LdapCapabilities(); // create empty default capabilities $this->discoverySuccess = false; } } @@ -660,7 +672,7 @@ class LdapConnection implements Selectable if ($serverSorting && $query->hasOrder()) { ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array( array( - 'oid' => Capability::LDAP_SERVER_SORT_OID, + 'oid' => LdapCapabilities::LDAP_SERVER_SORT_OID, 'value' => $this->encodeSortRules($query->getOrder()) ) )); @@ -755,7 +767,7 @@ class LdapConnection implements Selectable if ($serverSorting && $query->hasOrder()) { ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array( array( - 'oid' => Capability::LDAP_SERVER_SORT_OID, + 'oid' => LdapCapabilities::LDAP_SERVER_SORT_OID, 'value' => $this->encodeSortRules($query->getOrder()) ) )); @@ -1018,7 +1030,7 @@ class LdapConnection implements Selectable * * @param resource $ds The link identifier of the current LDAP connection * - * @return Capability + * @return LdapCapabilities * * @throws LdapException In case the capability query has failed */ @@ -1059,8 +1071,7 @@ class LdapConnection implements Selectable ldap_error($ds) ); } - - return new Capability($this->cleanupAttributes(ldap_get_attributes($ds, $entry), array_flip($fields))); + return new LdapCapabilities($this->cleanupAttributes(ldap_get_attributes($ds, $entry), array_flip($fields))); } /** @@ -1128,6 +1139,45 @@ class LdapConnection implements Selectable return $dir; } + protected function logInfo($message) + { + Logger::debug($message); + if (! isset($this->info)) { + $this->info = array(); + } + $this->info[] = $message; + } + + /** + * Get information about this objects state + * + * @return array An array of strings that describe the state in a human-readable form, each array element + * represents one fact about this object + */ + public function getInfo() + { + if (! isset($this->info)) { + $this->getConnection(); + } + return $this->info; + } + + /** + * If this object is working in its current configuration + * + * @return Bool True if the object is working, false if not + */ + public function isHealthy() + { + if (! isset($this->healthy)) { + try { + $this->testConnectionHealth(); + } catch (Exception $e) { + } + } + return $this->healthy; + } + /** * Reset the environment variables set by self::prepareTlsEnvironment() */ From 40d432100bc71fd096cef7f9a169fb4ae573dbae Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 14 Jul 2015 18:29:58 +0200 Subject: [PATCH 2/6] Add a function to test the connection health refs #9605 --- .../Icinga/Protocol/Ldap/LdapConnection.php | 69 +++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index db1cfb6cc..f99db5363 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -983,24 +983,69 @@ class LdapConnection implements Selectable, Inspectable ldap_set_option($ds, LDAP_OPT_REFERRALS, 0); if ($this->encryption === static::STARTTLS) { - if (($this->encryptionSuccess = @ldap_start_tls($ds))) { - Logger::debug('LDAP STARTTLS succeeded'); - } else { - Logger::error('LDAP STARTTLS failed: %s', ldap_error($ds)); - - // ldap_start_tls seems to corrupt the connection though if I understand - // https://tools.ietf.org/html/rfc4511#section-4.14.2 correctly, this shouldn't happen - $ds = ldap_connect($hostname, $this->port); - ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); - ldap_set_option($ds, LDAP_OPT_REFERRALS, 0); + $this->encrypted = true; + $this->logInfo('Connect using STARTTLS'); + if (! $this->validateCertificate) { + $this->logInfo('Skipping certificate validation'); } - } elseif ($this->encryption === static::LDAPS) { - $this->encryptionSuccess = true; + + $ret = ldap_start_tls($ds); + var_dump($ret); + if ($ret) { + } else { + throw new LdapException('LDAP STARTTLS failed: %s', ldap_error($ds)); + } + + } elseif ($this->encryption !== static::LDAPS) { + $this->encrypted = false; + $this->logInfo('Connect without encryption'); } return $ds; } + /** + * Test if needed aspects of the LDAP connection are working as expected + * + * Extended information about the + * + * @throws \Icinga\Protocol\Ldap\LdapException When a critical aspect of the health test fails + */ + public function testConnectionHealth() + { + $this->healthy = false; + $this->info = array(); + + // Try to connect to the server with the given connection parameters + $ds = $this->prepareNewConnection(); + + // Try a bind-command with the given user credentials, this must not fail + $success = @ldap_bind($ds, $this->bindDn, $this->bindPw); + $msg = sprintf('LDAP bind to %s:%s (%s / %s)', $this->hostname, $this->port, $this->bindDn, '***' /* $this->bindPw */); + if (! $success) { + throw new LdapException('%s failed: %s', $msg, ldap_error($ds)); + } + $this->logInfo(sprintf($msg . ' successful')); + + // Try to execute a schema discovery, this may fail if schema discovery is not supported + try { + $cap = LdapCapabilities::discoverCapabilities($this, $ds); + $infos []= $cap->getVendor(); + + $version = $cap->getVersion(); + if (isset($version)) { + $infos []= $version; + } + $infos []= 'Supports STARTTLS: ' . ($cap->hasStartTls() ? 'True' : 'False'); + $infos []= 'Default naming context: ' . $cap->getDefaultNamingContext(); + $this->info['Discovery Results:'] = $infos; + } catch (Exception $e) { + $this->logInfo('Schema discovery not possible: ', $e->getMessage()); + } + + $this->healthy = true; + } + /** * Set up how to handle StartTLS connections * From 3ddb8ca1bd730f6048a2bc099657ffd74331b403 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 14 Jul 2015 18:32:44 +0200 Subject: [PATCH 3/6] Add abillity to discover AD version and vendor name to discovery refs #9605 --- .../Authentication/User/LdapUserBackend.php | 4 +- library/Icinga/Protocol/Ldap/Discovery.php | 2 +- .../Icinga/Protocol/Ldap/LdapCapabilities.php | 122 ++++++++++++++++-- .../Icinga/Protocol/Ldap/LdapConnection.php | 58 +-------- 4 files changed, 120 insertions(+), 66 deletions(-) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index 948dcbe39..c6efd0673 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -218,7 +218,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first'); } - if ($this->ds->getCapabilities()->hasAdOid()) { + if ($this->ds->getCapabilities()->isActiveDirectory()) { $isActiveAttribute = 'userAccountControl'; $createdAtAttribute = 'whenCreated'; $lastModifiedAttribute = 'whenChanged'; @@ -254,7 +254,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface throw new ProgrammingError('It is required to set the objectClass where to look for users first'); } - if ($this->ds->getCapabilities()->hasAdOid()) { + if ($this->ds->getCapabilities()->isActiveDirectory()) { $stateConverter = 'user_account_control'; } else { $stateConverter = 'shadow_expire'; diff --git a/library/Icinga/Protocol/Ldap/Discovery.php b/library/Icinga/Protocol/Ldap/Discovery.php index 94a7f3ee6..0d3873d96 100644 --- a/library/Icinga/Protocol/Ldap/Discovery.php +++ b/library/Icinga/Protocol/Ldap/Discovery.php @@ -66,7 +66,7 @@ class Discovery { */ public function isAd() { - return $this->connection->getCapabilities()->hasAdOid(); + return $this->connection->getCapabilities()->isActiveDirectory(); } /** diff --git a/library/Icinga/Protocol/Ldap/LdapCapabilities.php b/library/Icinga/Protocol/Ldap/LdapCapabilities.php index bb3b01751..ba9bca3b8 100644 --- a/library/Icinga/Protocol/Ldap/LdapCapabilities.php +++ b/library/Icinga/Protocol/Ldap/LdapCapabilities.php @@ -11,7 +11,6 @@ namespace Icinga\Protocol\Ldap; */ class LdapCapabilities { - const LDAP_SERVER_START_TLS_OID = '1.3.6.1.4.1.1466.20037'; const LDAP_PAGED_RESULT_OID_STRING = '1.2.840.113556.1.4.319'; @@ -141,11 +140,22 @@ class LdapCapabilities * * @return boolean */ - public function hasAdOid() + public function isActiveDirectory() { return isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_OID]); } + /** + * Whether the ldap server is an OpenLDAP server + * + * @return bool + */ + public function isOpenLdap() + { + return isset($this->attributes->structuralObjectClass) && + $this->attributes->structuralObjectClass === 'OpenLDAProotDSE'; + } + /** * Return if the capability objects contains support for LdapV3, defaults to true if discovery failed * @@ -211,29 +221,119 @@ class LdapCapabilities public function getVendor() { - // AD doesn't include the vendor entry - if ($this->hasAdOid()) { + /* + rfc #3045 specifies that the name of the server MAY be included in the attribute 'verndorName', + AD and OpenLDAP don't do this, but for all all other vendors we follow the standard and + just hope for the best. + */ + + if ($this->isActiveDirectory()) { return 'Microsoft Active Directory'; } - if (! isset($this->attributes->vendorName)) { - // OpenLDAP doesn't include the vendor entry - // TODO: bad, remove this and add proper OpenLDAP version checking + if ($this->isOpenLdap()) { return 'OpenLDAP'; } + + if (! isset($this->attributes->vendorName)) { + return null; + } return $this->attributes->vendorName; } public function getVersion() { - // AD doesn't include the version string - if ($this->hasAdOid()) { - // TODO: query AD version from cn=schema,cn=configuration,dc=yourdomain,dc=com attribute:ObjectVersion + /* + rfc #3045 specifies that the version of the server MAY be included in the attribute 'vendorVersion', + but AD and OpenLDAP don't do this. For OpenLDAP there is no way to query the server versions, but for all + all other vendors we follow the standard and just hope for the best. + */ + + if ($this->isActiveDirectory()) { + return $this->getAdObjectVersionName(); } if (! isset($this->attributes->vendorVersion)) { - return 'unknown'; + return null; } return $this->attributes->vendorVersion; } + + /** + * Discover the capabilities of the given LDAP server + * + * @param LdapConnection $connection The ldap connection to use + * @param int $ds The link identifier of the current LDAP connection + * + * @return LdapCapabilities + * + * @throws LdapException In case the capability query has failed + */ + public static function discoverCapabilities(LdapConnection $connection, $ds) + { + $fields = array( + 'defaultNamingContext', + 'namingContexts', + 'vendorName', + 'vendorVersion', + 'supportedSaslMechanisms', + 'dnsHostName', + 'schemaNamingContext', + 'supportedLDAPVersion', // => array(3, 2) + 'supportedCapabilities', + 'supportedControl', + 'supportedExtension', + 'objectVersion', + '+' + ); + + $result = @ldap_read($ds, '', (string) $connection->select()->from('*', $fields), $fields); + if (! $result) { + throw new LdapException( + 'Capability query failed (%s:%d): %s. Check if hostname and port of the' + . ' ldap resource are correct and if anonymous access is permitted.', + $connection->getHostname(), + $connection->getPort(), + ldap_error($ds) + ); + } + + $entry = ldap_first_entry($ds, $result); + if ($entry === false) { + throw new LdapException( + 'Capabilities not available (%s:%d): %s. Discovery of root DSE probably not permitted.', + $connection->getHostname(), + $connection->getPort(), + ldap_error($ds) + ); + } + $cap = new LdapCapabilities( + $connection->cleanupAttributes( + ldap_get_attributes($ds, $entry), + array_flip($fields) + ) + ); + + return $cap; + } + + /** + * Determine the active directory version using the available capabillities + * + * @return null|string The server version description or null when unknown + */ + protected function getAdObjectVersionName() + { + if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_W8_OID])) { + return 'Windows Server 2012 (or newer)'; + } + if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID])) { + return 'Windows Server 2008 R2 (or newer)'; + } + if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_V60_OID])) { + return 'Windows Server 2008 (or newer)'; + } + return null; + + } } diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index f99db5363..6ac69906c 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -882,7 +882,7 @@ class LdapConnection implements Selectable, Inspectable * * @return object */ - protected function cleanupAttributes($attributes, array $requestedFields) + public function cleanupAttributes($attributes, array $requestedFields) { // In case the result contains attributes with a differing case than the requested fields, it is // necessary to create another array to map attributes case insensitively to their requested counterparts. @@ -1058,67 +1058,21 @@ class LdapConnection implements Selectable, Inspectable putenv('LDAPTLS_REQCERT=never'); } else { if ($this->validateCertificate) { - $ldap_conf = $this->getConfigDir('ldap_ca.conf'); + // $ldap_conf = $this->getConfigDir('ldap_ca.conf'); } else { - $ldap_conf = $this->getConfigDir('ldap_nocert.conf'); + // $ldap_conf = $this->getConfigDir('ldap_nocert.conf'); + putenv('LDAPTLS_REQCERT=never'); } + /* putenv('LDAPRC=' . $ldap_conf); // TODO: Does not have any effect if (getenv('LDAPRC') !== $ldap_conf) { throw new LdapException('putenv failed'); } + */ } } - /** - * Discover the capabilities of the given LDAP server - * - * @param resource $ds The link identifier of the current LDAP connection - * - * @return LdapCapabilities - * - * @throws LdapException In case the capability query has failed - */ - protected function discoverCapabilities($ds) - { - $fields = array( - 'defaultNamingContext', - 'namingContexts', - 'vendorName', - 'vendorVersion', - 'supportedSaslMechanisms', - 'dnsHostName', - 'schemaNamingContext', - 'supportedLDAPVersion', // => array(3, 2) - 'supportedCapabilities', - 'supportedControl', - 'supportedExtension', - '+' - ); - - $result = @ldap_read($ds, '', (string) $this->select()->from('*', $fields), $fields); - if (! $result) { - throw new LdapException( - 'Capability query failed (%s:%d): %s. Check if hostname and port of the' - . ' ldap resource are correct and if anonymous access is permitted.', - $this->hostname, - $this->port, - ldap_error($ds) - ); - } - - $entry = ldap_first_entry($ds, $result); - if ($entry === false) { - throw new LdapException( - 'Capabilities not available (%s:%d): %s. Discovery of root DSE probably not permitted.', - $this->hostname, - $this->port, - ldap_error($ds) - ); - } - return new LdapCapabilities($this->cleanupAttributes(ldap_get_attributes($ds, $entry), array_flip($fields))); - } - /** * Create an LDAP entry * From f4d8bfc309c88abfe282df2c4dc8303da76dfb51 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 14 Jul 2015 18:36:26 +0200 Subject: [PATCH 4/6] Display connection test info when inspecting LdapConnections refs #9605 --- library/Icinga/Protocol/Ldap/LdapConnection.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index 6ac69906c..479b8e78b 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -969,6 +969,10 @@ class LdapConnection implements Selectable, Inspectable $hostname = $this->hostname; if ($this->encryption === static::LDAPS) { + $this->logInfo('Connect using LDAPS'); + if (! $this->validateCertificate) { + $this->logInfo('Skipping certificate validation'); + } $hostname = 'ldaps://' . $hostname; } @@ -1156,7 +1160,7 @@ class LdapConnection implements Selectable, Inspectable public function getInfo() { if (! isset($this->info)) { - $this->getConnection(); + $this->testConnectionHealth(); } return $this->info; } From 84899e3e5689b25eea08a06ae3a4f0425a4d27e7 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 15 Jul 2015 09:56:18 +0200 Subject: [PATCH 5/6] Revert some unneeded changes refs #9605 --- library/Icinga/Protocol/Ldap/LdapConnection.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index 479b8e78b..4b3ab867f 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -1062,18 +1062,15 @@ class LdapConnection implements Selectable, Inspectable putenv('LDAPTLS_REQCERT=never'); } else { if ($this->validateCertificate) { - // $ldap_conf = $this->getConfigDir('ldap_ca.conf'); + $ldap_conf = $this->getConfigDir('ldap_ca.conf'); } else { - // $ldap_conf = $this->getConfigDir('ldap_nocert.conf'); - putenv('LDAPTLS_REQCERT=never'); + $ldap_conf = $this->getConfigDir('ldap_nocert.conf'); } - /* putenv('LDAPRC=' . $ldap_conf); // TODO: Does not have any effect if (getenv('LDAPRC') !== $ldap_conf) { throw new LdapException('putenv failed'); } - */ } } From 212111511a72aaa90161c3ab1811a1283f0e8b89 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 15 Jul 2015 10:32:54 +0200 Subject: [PATCH 6/6] Fix violations of coding guidelines --- .../Icinga/Protocol/Ldap/LdapCapabilities.php | 2 -- .../Icinga/Protocol/Ldap/LdapConnection.php | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapCapabilities.php b/library/Icinga/Protocol/Ldap/LdapCapabilities.php index ba9bca3b8..e98c57818 100644 --- a/library/Icinga/Protocol/Ldap/LdapCapabilities.php +++ b/library/Icinga/Protocol/Ldap/LdapCapabilities.php @@ -313,7 +313,6 @@ class LdapCapabilities array_flip($fields) ) ); - return $cap; } @@ -334,6 +333,5 @@ class LdapCapabilities return 'Windows Server 2008 (or newer)'; } return null; - } } diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index 4b3ab867f..382b61e2d 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -397,8 +397,7 @@ class LdapConnection implements Selectable, Inspectable { $this->bind(); - if ( - $query->getUsePagedResults() + if ($query->getUsePagedResults() && version_compare(PHP_VERSION, '5.4.0') >= 0 && $this->getCapabilities()->hasPagedResult() ) { @@ -714,7 +713,8 @@ class LdapConnection implements Selectable, Inspectable $count += 1; if (! $serverSorting || $offset === 0 || $offset < $count) { $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes( - ldap_get_attributes($ds, $entry), array_flip($fields) + ldap_get_attributes($ds, $entry), + array_flip($fields) ); } } while ( @@ -826,7 +826,8 @@ class LdapConnection implements Selectable, Inspectable $count += 1; if (! $serverSorting || $offset === 0 || $offset < $count) { $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes( - ldap_get_attributes($ds, $entry), array_flip($fields) + ldap_get_attributes($ds, $entry), + array_flip($fields) ); } } while ( @@ -932,6 +933,7 @@ class LdapConnection implements Selectable, Inspectable * @param array $sortRules * * @return string + * @throws ProgrammingError * * @todo Produces an invalid stream, obviously */ @@ -1025,7 +1027,13 @@ class LdapConnection implements Selectable, Inspectable // Try a bind-command with the given user credentials, this must not fail $success = @ldap_bind($ds, $this->bindDn, $this->bindPw); - $msg = sprintf('LDAP bind to %s:%s (%s / %s)', $this->hostname, $this->port, $this->bindDn, '***' /* $this->bindPw */); + $msg = sprintf( + 'LDAP bind to %s:%s (%s / %s)', + $this->hostname, + $this->port, + $this->bindDn, + '***' /* $this->bindPw */ + ); if (! $success) { throw new LdapException('%s failed: %s', $msg, ldap_error($ds)); }