From 3ddb8ca1bd730f6048a2bc099657ffd74331b403 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 14 Jul 2015 18:32:44 +0200 Subject: [PATCH] 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 *