Refactor Ldap and Dns utility functions and conform to coding guidelines

refs #6507
This commit is contained in:
Matthias Jentsch 2014-06-16 14:16:18 +02:00
parent 76b5901cb6
commit 521cc0cac4
2 changed files with 161 additions and 71 deletions

View File

@ -32,30 +32,41 @@ namespace Icinga\Protocol;
/** /**
* Discover dns records using regular or reverse lookup * Discover dns records using regular or reverse lookup
*/ */
class Dns { class Dns
{
/** /**
* Get all ldap records for the given domain * Discover all service records on a given domain
* *
* @param String $query The domain to query * @param string $domain The domain to search
* @param string $service The type of the service, like for example 'ldaps' or 'ldap'
* @param string $protocol The transport protocol used by the service, defaults to 'tcp'
* *
* @return array An array of entries * @return array|bool An array of all service domains
*/ */
public static function ldapRecords($query) public static function getSrvRecords($domain, $service, $protocol = 'tcp')
{ {
$ldaps_records = dns_get_record('_ldaps._tcp.' . $query); $records = dns_get_record('_' . $service . '._' . $protocol . '.' . $domain, DNS_SRV);
$ldap_records = dns_get_record('_ldap._tcp.' . $query); if ($records === false) {
return array_merge($ldaps_records, $ldap_records); return false;
}
$targets = array();
foreach ($records as $record) {
if (array_key_exists('target', $record)) {
$targets[] = $record['target'];
}
}
return $targets;
} }
/** /**
* Get all ldap records for the given domain * Get all ldap records for the given domain
* *
* @param String $query The domain to query * @param string $query The domain to query
* @param int $type The type of DNS-entry to fetch, see http://www.php.net/manual/de/function.dns-get-record.php * @param int $type The type of DNS-entry to fetch, see
* for available types * http://www.php.net/manual/de/function.dns-get-record.php for available types
* *
* @return array|Boolean An array of entries * @return array|bool An array of entries
*/ */
public static function records($query, $type = DNS_ANY) public static function records($query, $type = DNS_ANY)
{ {
@ -63,12 +74,12 @@ class Dns {
} }
/** /**
* Reverse lookup all hostname on the given ip address * Reverse lookup all host names available on the given ip address
* *
* @param $ipAddress * @param string $ipAddress
* @param int $type * @param int $type
* *
* @return array|Boolean * @return array|bool
*/ */
public static function ptr($ipAddress, $type = DNS_ANY) public static function ptr($ipAddress, $type = DNS_ANY)
{ {
@ -85,7 +96,7 @@ class Dns {
* *
* @param $hostname The hostname to resolve * @param $hostname The hostname to resolve
* *
* @return String|Boolean The IPv4 address of the given hostname, or false when no entry exists. * @return string|bool The IPv4 address of the given hostname, or false when no entry exists.
*/ */
public static function ipv4($hostname) public static function ipv4($hostname)
{ {
@ -101,7 +112,7 @@ class Dns {
* *
* @param $hostname The hostname to resolve * @param $hostname The hostname to resolve
* *
* @return String|Boolean The IPv6 address of the given hostname, or false when no entry exists. * @return string|bool The IPv6 address of the given hostname, or false when no entry exists.
*/ */
public static function ipv6($hostname) public static function ipv6($hostname)
{ {

View File

@ -105,12 +105,20 @@ class Connection
); );
/**
* Whether the bind on this connection was already performed
*
* @var bool
*/
protected $bindDone = false;
protected $root; protected $root;
protected $supports_v3 = false; protected $supports_v3 = false;
protected $supports_tls = false; protected $supports_tls = false;
protected $capabilities; protected $capabilities;
protected $namingContexts;
/** /**
* Constructor * Constructor
@ -155,6 +163,8 @@ class Connection
public function hasDN($dn) public function hasDN($dn)
{ {
$this->connect(); $this->connect();
$this->bind();
$result = ldap_read($this->ds, $dn, '(objectClass=*)', array('objectClass')); $result = ldap_read($this->ds, $dn, '(objectClass=*)', array('objectClass'));
return ldap_count_entries($this->ds, $result) > 0; return ldap_count_entries($this->ds, $result) > 0;
} }
@ -162,6 +172,8 @@ class Connection
public function deleteRecursively($dn) public function deleteRecursively($dn)
{ {
$this->connect(); $this->connect();
$this->bind();
$result = @ldap_list($this->ds, $dn, '(objectClass=*)', array('objectClass')); $result = @ldap_list($this->ds, $dn, '(objectClass=*)', array('objectClass'));
if ($result === false) { if ($result === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
@ -176,7 +188,7 @@ class Connection
); );
} }
$children = ldap_get_entries($this->ds, $result); $children = ldap_get_entries($this->ds, $result);
for($i = 0; $i < $children['count']; $i++) { for ($i = 0; $i < $children['count']; $i++) {
$result = $this->deleteRecursively($children[$i]['dn']); $result = $this->deleteRecursively($children[$i]['dn']);
if (!$result) { if (!$result) {
//return result code, if delete fails //return result code, if delete fails
@ -189,6 +201,7 @@ class Connection
public function deleteDN($dn) public function deleteDN($dn)
{ {
$this->connect(); $this->connect();
$this->bind();
$result = @ldap_delete($this->ds, $dn); $result = @ldap_delete($this->ds, $dn);
if ($result === false) { if ($result === false) {
@ -213,17 +226,23 @@ class Connection
* @param $query * @param $query
* @param array $fields * @param array $fields
* *
* @return bool|String Returns the distinguished name, or false when the given query yields no results * @return null|string Returns the distinguished name, or false when the given query yields no results
*/ */
public function fetchDN($query, $fields = array()) public function fetchDN($query, $fields = array())
{ {
$rows = $this->fetchAll($query, $fields); $rows = $this->fetchAll($query, $fields);
if (count($rows) !== 1) { if (count($rows) !== 1) {
return false; return null;
} }
return key($rows); return key($rows);
} }
/**
* @param $query
* @param array $fields
*
* @return mixed
*/
public function fetchRow($query, $fields = array()) public function fetchRow($query, $fields = array())
{ {
// TODO: This is ugly, make it better! // TODO: This is ugly, make it better!
@ -231,6 +250,11 @@ class Connection
return array_shift($results); return array_shift($results);
} }
/**
* @param Query $query
*
* @return int
*/
public function count(Query $query) public function count(Query $query)
{ {
$results = $this->runQuery($query, '+'); $results = $this->runQuery($query, '+');
@ -289,6 +313,7 @@ class Connection
protected function runQuery($query, $fields) protected function runQuery($query, $fields)
{ {
$this->connect(); $this->connect();
$this->bind();
if ($query instanceof Query) { if ($query instanceof Query) {
$fields = $query->listFields(); $fields = $query->listFields();
} }
@ -350,6 +375,11 @@ class Connection
} }
} }
/**
* @param null $sub
*
* @return string
*/
protected function getConfigDir($sub = null) protected function getConfigDir($sub = null)
{ {
$dir = Config::getInstance()->getConfigDir() . '/ldap'; $dir = Config::getInstance()->getConfigDir() . '/ldap';
@ -359,6 +389,12 @@ class Connection
return $dir; return $dir;
} }
/**
* Connect to the given ldap server and apply settings depending on the discovered capabilities
*
* @return resource A positive LDAP link identifier
* @throws \Exception When the connection is not possible
*/
protected function prepareNewConnection() protected function prepareNewConnection()
{ {
$use_tls = false; $use_tls = false;
@ -370,11 +406,12 @@ class Connection
} }
$ds = ldap_connect($this->hostname, $this->port); $ds = ldap_connect($this->hostname, $this->port);
$cap = $this->discoverCapabilities($ds); list($cap, $namingContexts) = $this->discoverCapabilities($ds);
$this->capabilities = $cap; $this->capabilities = $cap;
$this->namingContexts = $namingContexts;
if ($use_tls) { if ($use_tls) {
if ($cap->starttls) { if ($cap->supports_starttls) {
if (@ldap_start_tls($ds)) { if (@ldap_start_tls($ds)) {
Logger::debug('LDAP STARTTLS succeeded'); Logger::debug('LDAP STARTTLS succeeded');
} else { } else {
@ -398,9 +435,9 @@ class Connection
} }
} }
// ldap_rename requires LDAPv3: // ldap_rename requires LDAPv3:
if ($cap->ldapv3) { if ($cap->supports_ldapv3) {
if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) { if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
throw new Exception('LDAPv3 is required'); throw new \Exception('LDAPv3 is required');
} }
} else { } else {
@ -430,17 +467,31 @@ class Connection
} }
putenv('LDAPRC=' . $ldap_conf); putenv('LDAPRC=' . $ldap_conf);
if (getenv('LDAPRC') !== $ldap_conf) { if (getenv('LDAPRC') !== $ldap_conf) {
throw new Exception('putenv failed'); throw new \Exception('putenv failed');
} }
} }
} }
protected function hasCapabilityStarTSL($cap) /**
* Return if the capability object contains support for StartTLS
*
* @param $cap The object containing the capabilities
*
* @return bool Whether StartTLS is supported
*/
protected function hasCapabilityStartTLS($cap)
{ {
$cap = $this->getExtensionCapabilities($cap); $cap = $this->getExtensionCapabilities($cap);
return isset($cap['1.3.6.1.4.1.1466.20037']); return isset($cap['1.3.6.1.4.1.1466.20037']);
} }
/**
* Return if the capability objects contains support for LdapV3
*
* @param $cap
*
* @return bool
*/
protected function hasCapabilityLdapV3($cap) protected function hasCapabilityLdapV3($cap)
{ {
if ((is_string($cap->supportedLDAPVersion) if ((is_string($cap->supportedLDAPVersion)
@ -453,6 +504,13 @@ class Connection
return false; return false;
} }
/**
* Extract an array of all extension capabilities from the given ldap response
*
* @param $cap object The response returned by a ldap_search discovery query
*
* @return object The extracted capabilities.
*/
protected function getExtensionCapabilities($cap) protected function getExtensionCapabilities($cap)
{ {
$extensions = array(); $extensions = array();
@ -468,6 +526,13 @@ class Connection
return $extensions; return $extensions;
} }
/**
* Extract an array of all MSAD capabilities from the given ldap response
*
* @param $cap object The response returned by a ldap_search discovery query
*
* @return object The extracted capabilities.
*/
protected function getMsCapabilities($cap) protected function getMsCapabilities($cap)
{ {
$ms = array(); $ms = array();
@ -485,6 +550,13 @@ class Connection
return (object)$ms; return (object)$ms;
} }
/**
* Convert a single capability name entry into camel-case
*
* @param $name string The name to convert
*
* @return string The name in camel-case
*/
private function convName($name) private function convName($name)
{ {
$parts = explode('_', $name); $parts = explode('_', $name);
@ -508,7 +580,7 @@ class Connection
/** /**
* Get the default naming context of this ldap connection * Get the default naming context of this ldap connection
* *
* @return String|null the default naming context, or null when no contexts are available * @return string|null the default naming context, or null when no contexts are available
*/ */
public function getDefaultNamingContext() public function getDefaultNamingContext()
{ {
@ -527,23 +599,22 @@ class Connection
*/ */
public function namingContexts() public function namingContexts()
{ {
$cap = $this->capabilities; if (!isset($this->namingContexts)) {
if (!isset($cap->namingContexts)) {
return array(); return array();
} }
if (!is_array($cap->namingContexts)) { if (!is_array($this->namingContexts)) {
return array($cap->namingContexts); return array($this->namingContexts);
} }
return $cap->namingContexts; return $this->namingContexts;
} }
/** /**
* Discover the capabilities of the given ldap-server * Discover the capabilities of the given ldap-server
* *
* @param $ds The link identifier of the current ldap connection * @param resource $ds The link identifier of the current ldap connection
* *
* @return bool|object The capabilities or false if the server has none * @return array The capabilities and naming-contexts
* @throws Exception When the capability query fails * @throws \Exception When the capability query fails
*/ */
protected function discoverCapabilities($ds) protected function discoverCapabilities($ds)
{ {
@ -563,8 +634,6 @@ class Connection
'+' '+'
) )
); );
$fields = $query->listFields();
$result = @ldap_read( $result = @ldap_read(
$ds, $ds,
'', '',
@ -573,7 +642,7 @@ class Connection
); );
if (! $result) { if (! $result) {
throw new Exception( throw new \Exception(
sprintf( sprintf(
'Capability query failed (%s:%d): %s', 'Capability query failed (%s:%d): %s',
$this->hostname, $this->hostname,
@ -585,8 +654,8 @@ class Connection
$entry = ldap_first_entry($ds, $result); $entry = ldap_first_entry($ds, $result);
$cap = (object) array( $cap = (object) array(
'ldapv3' => false, 'supports_ldapv3' => false,
'starttls' => false, 'supports_starttls' => false,
'msCapabilities' => array() 'msCapabilities' => array()
); );
@ -596,27 +665,37 @@ class Connection
} }
$ldapAttributes = ldap_get_attributes($ds, $entry); $ldapAttributes = ldap_get_attributes($ds, $entry);
$result = $this->cleanupAttributes($ldapAttributes); $result = $this->cleanupAttributes($ldapAttributes);
$cap->ldapv3 = $this->hasCapabilityLdapV3($result); $cap->supports_ldapv3 = $this->hasCapabilityLdapV3($result);
$cap->starttls = $this->hasCapabilityStarTSL($result); $cap->supports_starttls = $this->hasCapabilityStartTLS($result);
$cap->msCapabilities = $this->getMsCapabilities($result); $cap->msCapabilities = $this->getMsCapabilities($result);
$cap->namingContexts = $result->namingContexts;
/* return array($cap, $result->namingContexts);
if (isset($result->dnsHostName)) {
ldap_set_option($ds, LDAP_OPT_HOST_NAME, $result->dnsHostName);
} }
/**
* Try to connect to the given ldap server
*
* @throws \Exception When connecting is not possible
*/ */
public function connect()
return $cap;
}
public function connect($anonymous = false)
{ {
if ($this->ds !== null) { if ($this->ds !== null) {
return; return;
} }
$this->ds = $this->prepareNewConnection(); $this->ds = $this->prepareNewConnection();
}
/**
* Try to bind to the current ldap domain using the provided bind_dn and bind_pw
*
* @throws \Exception When binding is not possible
*/
public function bind()
{
if ($this->bindDone) {
return;
}
if (!$anonymous) {
$r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw); $r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw);
if (! $r) { if (! $r) {
throw new \Exception( throw new \Exception(
@ -630,7 +709,7 @@ class Connection
) )
); );
} }
} $this->bindDone = true;
} }
/** /**