mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-04-08 17:15:08 +02:00
Update LDAP and Livestatus protocol
This commit is contained in:
parent
e66d17dbf8
commit
be3193a0d7
@ -1,31 +1,10 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* Icinga 2 Web - Head for multiple monitoring frontends
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
/**
|
||||
* LDAP connection handler
|
||||
*/
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
|
||||
use Icinga\Exception\ConfigurationError as ConfigError;
|
||||
use Icinga\Application\Platform;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Logger as Log;
|
||||
@ -46,89 +25,65 @@ use Icinga\Application\Logger as Log;
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const LDAP_NO_SUCH_OBJECT = 0x20;
|
||||
|
||||
protected $ds;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $hostname;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $port = 389;
|
||||
protected $bind_dn;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bind_pw;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $root_dn;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $count;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $ldap_extension = array(
|
||||
'1.3.6.1.4.1.1466.20037' => 'STARTTLS', // notes?
|
||||
'1.3.6.1.4.1.1466.20037' => 'STARTTLS',
|
||||
// '1.3.6.1.4.1.4203.1.11.1' => '11.1', // PASSWORD_MODIFY
|
||||
// '1.3.6.1.4.1.4203.1.11.3' => '11.3', // Whoami
|
||||
// '1.3.6.1.1.8' => '8', // Cancel Extended Request
|
||||
);
|
||||
|
||||
protected $use_tls = false;
|
||||
protected $force_tls = false;
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $ms_capability = array(
|
||||
// Prefix LDAP_CAP_
|
||||
// Source: http://msdn.microsoft.com/en-us/library/cc223359.aspx
|
||||
|
||||
// Running Active Directory as AD DS:
|
||||
'1.2.840.113556.1.4.800' => 'ACTIVE_DIRECTORY_OID',
|
||||
'1.2.840.113556.1.4.800' => 'ACTIVE_DIRECTORY_OID',
|
||||
|
||||
// Capable of signing and sealing on an NTLM authenticated connection
|
||||
// and of performing subsequent binds on a signed or sealed connection.
|
||||
'1.2.840.113556.1.4.1791' => 'ACTIVE_DIRECTORY_LDAP_INTEG_OID',
|
||||
|
||||
// If AD DS: running at least W2K3, if AD LDS running at least W2K8
|
||||
'1.2.840.113556.1.4.1670' => 'ACTIVE_DIRECTORY_V51_OID',
|
||||
|
||||
// If AD LDS: accepts DIGEST-MD5 binds for AD LDSsecurity principals
|
||||
'1.2.840.113556.1.4.1880' => 'ACTIVE_DIRECTORY_ADAM_DIGEST',
|
||||
|
||||
// Running Active Directory as AD LDS
|
||||
'1.2.840.113556.1.4.1851' => 'ACTIVE_DIRECTORY_ADAM_OID',
|
||||
|
||||
// If AD DS: it's a Read Only DC (RODC)
|
||||
'1.2.840.113556.1.4.1920' => 'ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID',
|
||||
|
||||
// Running at least W2K8
|
||||
'1.2.840.113556.1.4.1935' => 'ACTIVE_DIRECTORY_V60_OID',
|
||||
|
||||
// Running at least W2K8r2
|
||||
'1.2.840.113556.1.4.2080' => 'ACTIVE_DIRECTORY_V61_R2_OID',
|
||||
|
||||
// Running at least W2K12
|
||||
'1.2.840.113556.1.4.2237' => 'ACTIVE_DIRECTORY_W8_OID',
|
||||
|
||||
);
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
protected $supports_v3 = false;
|
||||
protected $supports_tls = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -139,24 +94,17 @@ class Connection
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->hostname = $config->hostname;
|
||||
$this->bind_dn = $config->bind_dn;
|
||||
$this->bind_pw = $config->bind_pw;
|
||||
$this->root_dn = $config->root_dn;
|
||||
$this->use_tls = isset($config->tls) ? $config->tls : false;
|
||||
$this->force_tls = $this->use_tls;
|
||||
$this->bind_dn = $config->bind_dn;
|
||||
$this->bind_pw = $config->bind_pw;
|
||||
$this->root_dn = $config->root_dn;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDN()
|
||||
{
|
||||
return $this->root_dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Root|string
|
||||
*/
|
||||
public function root()
|
||||
{
|
||||
if ($this->root === null) {
|
||||
@ -165,36 +113,22 @@ class Connection
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Query
|
||||
*/
|
||||
public function select()
|
||||
{
|
||||
return new Query($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $query
|
||||
* @param array $fields
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchOne($query, $fields = array())
|
||||
{
|
||||
$row = (array)$this->fetchRow($query, $fields);
|
||||
$row = (array) $this->fetchRow($query, $fields);
|
||||
return array_shift($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $query
|
||||
* @param array $fields
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchDN($query, $fields = array())
|
||||
{
|
||||
$rows = $this->fetchAll($query, $fields);
|
||||
if (count($rows) !== 1) {
|
||||
throw new Exception(
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'Cannot fetch single DN for %s',
|
||||
$query
|
||||
@ -204,11 +138,7 @@ class Connection
|
||||
return key($rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $query
|
||||
* @param array $fields
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
public function fetchRow($query, $fields = array())
|
||||
{
|
||||
// TODO: This is ugly, make it better!
|
||||
@ -216,31 +146,28 @@ class Connection
|
||||
return array_shift($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Query $query
|
||||
* @return int
|
||||
*/
|
||||
public function count(Query $query)
|
||||
{
|
||||
$results = $this->runQuery($query, '+');
|
||||
if (! $results) {
|
||||
return 0;
|
||||
}
|
||||
return ldap_count_entries($this->ds, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $query
|
||||
* @param array $fields
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll($query, $fields = array())
|
||||
{
|
||||
$offset = null;
|
||||
$limit = null;
|
||||
if ($query->hasLimit()) {
|
||||
$offset = $query->getOffset();
|
||||
$limit = $query->getLimit();
|
||||
$limit = $query->getLimit();
|
||||
}
|
||||
$entries = array();
|
||||
$results = $this->runQuery($query, $fields);
|
||||
if (! $results) {
|
||||
return array();
|
||||
}
|
||||
$entry = ldap_first_entry($this->ds, $results);
|
||||
$count = 0;
|
||||
while ($entry) {
|
||||
@ -248,7 +175,8 @@ class Connection
|
||||
&& ($limit === null || ($offset + $limit) >= $count)
|
||||
) {
|
||||
$attrs = ldap_get_attributes($this->ds, $entry);
|
||||
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes($attrs);
|
||||
$entries[ldap_get_dn($this->ds, $entry)]
|
||||
= $this->cleanupAttributes($attrs);
|
||||
}
|
||||
$count++;
|
||||
$entry = ldap_next_entry($this->ds, $entry);
|
||||
@ -257,13 +185,9 @@ class Connection
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $attrs
|
||||
* @return object
|
||||
*/
|
||||
public function cleanupAttributes(& $attrs)
|
||||
{
|
||||
$clean = (object)array();
|
||||
$clean = (object) array();
|
||||
for ($i = 0; $i < $attrs['count']; $i++) {
|
||||
$attr_name = $attrs[$i];
|
||||
if ($attrs[$attr_name]['count'] === 1) {
|
||||
@ -277,12 +201,6 @@ class Connection
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $query
|
||||
* @param $fields
|
||||
* @return resource
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function runQuery($query, $fields)
|
||||
{
|
||||
$this->connect();
|
||||
@ -293,17 +211,19 @@ class Connection
|
||||
// We do not support pagination right now, and there is no chance to
|
||||
// do so for PHP < 5.4. Warnings about "Sizelimit exceeded" will
|
||||
// therefore not be hidden right now.
|
||||
Log::debug("Query: %s", $query->__toString());
|
||||
$results = ldap_search(
|
||||
$results = @ldap_search(
|
||||
$this->ds,
|
||||
$this->root_dn,
|
||||
(string)$query,
|
||||
(string) $query,
|
||||
$fields,
|
||||
0, // Attributes and values
|
||||
0 // No limit - at least where possible
|
||||
0 // No limit - at least where possible
|
||||
);
|
||||
if (!$results) {
|
||||
throw new Exception(
|
||||
if ($results === false) {
|
||||
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
|
||||
return false;
|
||||
}
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'LDAP query "%s" (root %s) failed: %s',
|
||||
$query,
|
||||
@ -311,7 +231,6 @@ class Connection
|
||||
ldap_error($this->ds)
|
||||
)
|
||||
);
|
||||
|
||||
die('Query failed');
|
||||
}
|
||||
$list = array();
|
||||
@ -323,186 +242,229 @@ class Connection
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $username
|
||||
* @param $password
|
||||
* @return bool
|
||||
*/
|
||||
public function testCredentials($username, $password)
|
||||
{
|
||||
Log::debug("Trying to connect to %s", $this->hostname);
|
||||
$ds = ldap_connect($this->hostname);
|
||||
Log::debug("ldap_bind (%s)", $username);
|
||||
$ds = $this->prepareNewConnection();
|
||||
|
||||
$r = @ldap_bind($ds, $username, $password);
|
||||
if ($r) {
|
||||
log::debug(
|
||||
'Successfully tested LDAP credentials (%s / %s)',
|
||||
$username,
|
||||
'***'
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
log::fatal(
|
||||
'LDAP connection (%s / %s) failed: %s',
|
||||
log::debug(
|
||||
'Testing LDAP credentials (%s / %s) failed: %s',
|
||||
$username,
|
||||
'***',
|
||||
ldap_error($ds)
|
||||
);
|
||||
return false;
|
||||
/* TODO: Log failure
|
||||
throw new Exception(sprintf(
|
||||
'LDAP connection (%s / %s) failed: %s',
|
||||
$username,
|
||||
'***',
|
||||
ldap_error($ds)
|
||||
));
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getConfigDir()
|
||||
protected function getConfigDir($sub = null)
|
||||
{
|
||||
return Config::getInstance()->getConfigDir() . '/ldap';
|
||||
$dir = Config::getInstance()->getConfigDir() . '/ldap';
|
||||
if ($sub !== null) {
|
||||
$dir .= '/' . $sub;
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $domain
|
||||
*/
|
||||
protected function discoverServerlistForDomain($domain)
|
||||
{
|
||||
$ldaps_records = dns_get_record('_ldaps._tcp.' . $domain, DNS_SRV);
|
||||
$ldap_records = dns_get_record('_ldap._tcp.' . $domain, DNS_SRV);
|
||||
$ldap_records = dns_get_record('_ldap._tcp.' . $domain, DNS_SRV);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function prepareTlsEnvironment()
|
||||
protected function prepareNewConnection()
|
||||
{
|
||||
$strict_tls = true;
|
||||
$use_local_ca = true;
|
||||
if (Platform::isWindows()) {
|
||||
} else {
|
||||
$cfg_dir = $this->getConfigDir();
|
||||
if ($strict_tls) {
|
||||
putenv(sprintf('LDAPRC=%s/%s', $cfg_dir, 'ldap_ca.conf'));
|
||||
$use_tls = false;
|
||||
$force_tls = true;
|
||||
$force_tls = false;
|
||||
|
||||
if ($use_tls) {
|
||||
$this->prepareTlsEnvironment();
|
||||
}
|
||||
|
||||
$ds = ldap_connect($this->hostname, $this->port);
|
||||
$cap = $this->discoverCapabilities($ds);
|
||||
|
||||
if ($use_tls) {
|
||||
if ($cap->starttls) {
|
||||
if (@ldap_start_tls($ds)) {
|
||||
Log::debug('LDAP STARTTLS succeeded');
|
||||
} else {
|
||||
Log::debug('LDAP STARTTLS failed: %s', ldap_error($ds));
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'LDAP STARTTLS failed: %s',
|
||||
ldap_error($ds)
|
||||
)
|
||||
);
|
||||
}
|
||||
} elseif ($force_tls) {
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'TLS is required but not announced by %s',
|
||||
$this->host_name
|
||||
)
|
||||
);
|
||||
} else {
|
||||
putenv(sprintf('LDAPRC=%s/%s', $cfg_dir, 'ldap_nocert.conf'));
|
||||
// TODO: Log noticy -> TLS enabled but not announced
|
||||
}
|
||||
}
|
||||
// file_put_contents('/tmp/tom_LDAP.conf', "TLS_REQCERT never\n");
|
||||
// ldap_rename requires LDAPv3:
|
||||
if ($cap->ldapv3) {
|
||||
if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
||||
throw new Exception('LDAPv3 is required');
|
||||
}
|
||||
} else {
|
||||
|
||||
// TODO: remove this -> FORCING v3 for now
|
||||
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||
|
||||
Log::warn('No LDAPv3 support detected');
|
||||
}
|
||||
|
||||
// Not setting this results in "Operations error" on AD when using the
|
||||
// whole domain as search base:
|
||||
ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
|
||||
// ldap_set_option($ds, LDAP_OPT_DEREF, LDAP_DEREF_NEVER);
|
||||
return $ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
*/
|
||||
protected function fetchRootDseDetails()
|
||||
protected function prepareTlsEnvironment()
|
||||
{
|
||||
$query = $this->select()->from('*', array('+'));
|
||||
|
||||
/*, array(
|
||||
'defaultNamingContext',
|
||||
'namingContexts',
|
||||
'supportedSaslMechanisms',
|
||||
'dnsHostName',
|
||||
'schemaNamingContext',
|
||||
'supportedLDAPVersion', // => array(3, 2)
|
||||
'supportedCapabilities'
|
||||
))*/
|
||||
$strict_tls = true;
|
||||
// TODO: allow variable known CA location (system VS Icinga)
|
||||
if (Platform::isWindows()) {
|
||||
// putenv('LDAP...')
|
||||
} else {
|
||||
if ($strict_tls) {
|
||||
$ldap_conf = $this->getConfigDir('ldap_ca.conf');
|
||||
} else {
|
||||
$ldap_conf = $this->getConfigDir('ldap_nocert.conf');
|
||||
}
|
||||
putenv('LDAPRC=' . $ldap_conf);
|
||||
if (getenv('LDAPRC') !== $ldap_conf) {
|
||||
throw new Exception('putenv failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function discoverCapabilities($ds)
|
||||
{
|
||||
$query = $this->select()->from(
|
||||
'*',
|
||||
array(
|
||||
'defaultNamingContext',
|
||||
'namingContexts',
|
||||
'vendorName',
|
||||
'vendorVersion',
|
||||
'supportedSaslMechanisms',
|
||||
'dnsHostName',
|
||||
'schemaNamingContext',
|
||||
'supportedLDAPVersion', // => array(3, 2)
|
||||
'supportedCapabilities',
|
||||
'supportedExtension',
|
||||
'+'
|
||||
)
|
||||
);
|
||||
$fields = $query->listFields();
|
||||
|
||||
$result = ldap_read(
|
||||
$this->ds,
|
||||
$result = @ldap_read(
|
||||
$ds,
|
||||
'',
|
||||
(string)$query,
|
||||
$query->listFields(),
|
||||
0,
|
||||
0
|
||||
(string) $query,
|
||||
$query->listFields()
|
||||
);
|
||||
|
||||
$entry = ldap_first_entry($this->ds, $result);
|
||||
$result = $this->cleanupAttributes(ldap_get_attributes($this->ds, $entry));
|
||||
if (! $result) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Capability query failed: %s',
|
||||
ldap_error($ds)
|
||||
)
|
||||
);
|
||||
}
|
||||
$entry = ldap_first_entry($ds, $result);
|
||||
|
||||
$cap = (object) array(
|
||||
'ldapv3' => false,
|
||||
'starttls' => false,
|
||||
);
|
||||
|
||||
if ($entry === false) {
|
||||
// TODO: Is it OK to have no capabilities?
|
||||
return $cap;
|
||||
}
|
||||
|
||||
$result = $this->cleanupAttributes(
|
||||
ldap_get_attributes($ds, $entry)
|
||||
);
|
||||
/*
|
||||
if (isset($result->dnsHostName)) {
|
||||
ldap_set_option($ds, LDAP_OPT_HOST_NAME, $result->dnsHostName);
|
||||
}
|
||||
*/
|
||||
|
||||
if ((is_string($result->supportedLDAPVersion)
|
||||
&& (int) $result->supportedLDAPVersion === 3)
|
||||
|| (is_array($result->supportedLDAPVersion)
|
||||
&& in_array(3, $result->supportedLDAPVersion)
|
||||
)) {
|
||||
$cap->ldapv3 = true;
|
||||
}
|
||||
|
||||
if (isset($result->supportedCapabilities)) {
|
||||
foreach ($result->supportedCapabilities as $oid) {
|
||||
if (array_key_exists($oid, $this->ms_capability)) {
|
||||
echo $this->ms_capability[$oid] . "\n";
|
||||
// echo $this->ms_capability[$oid] . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($result->supportedExtension)) {
|
||||
foreach ($result->supportedExtension as $oid) {
|
||||
if (array_key_exists($oid, $this->ldap_extension)) {
|
||||
// STARTTLS -> läuft mit OpenLDAP
|
||||
if ($this->ldap_extension[$oid] === 'STARTTLS') {
|
||||
$cap->starttls = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
return $cap;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function discoverCapabilities()
|
||||
{
|
||||
$this->fetchRootDseDetails();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if ($this->ds !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->use_tls) {
|
||||
$this->prepareTlsEnvironment();
|
||||
}
|
||||
Log::debug("Trying to connect to %s", $this->hostname);
|
||||
$this->ds = ldap_connect($this->hostname, 389);
|
||||
$this->discoverCapabilities();
|
||||
if ($this->use_tls) {
|
||||
Log::debug("Trying ldap_start_tls()");
|
||||
if (@ldap_start_tls($this->ds)) {
|
||||
Log::debug("Trying ldap_start_tls() succeeded");
|
||||
} else {
|
||||
Log::warn(
|
||||
"ldap_start_tls() failed: %s. Does your ldap_ca.conf point to the certificate? ",
|
||||
ldap_error($this->ds)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ldap_rename requires LDAPv3:
|
||||
if (!ldap_set_option($this->ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
||||
throw new Exception('LDAPv3 is required');
|
||||
}
|
||||
|
||||
// Not setting this results in "Operations error" on AD when using the
|
||||
// whole domain as search base:
|
||||
ldap_set_option($this->ds, LDAP_OPT_REFERRALS, 0);
|
||||
|
||||
// ldap_set_option($this->ds, LDAP_OPT_DEREF, LDAP_DEREF_NEVER);
|
||||
Log::debug("Trying ldap_bind(%s)", $this->bind_dn);
|
||||
$this->ds = $this->prepareNewConnection();
|
||||
|
||||
$r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw);
|
||||
|
||||
if (!$r) {
|
||||
log::fatal(
|
||||
'LDAP connection (%s / %s) failed: %s',
|
||||
$this->bind_dn,
|
||||
'***',
|
||||
ldap_error($this->ds)
|
||||
);
|
||||
throw new ConfigError(
|
||||
if (! $r) {
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'Could not connect to the authentication server, please '.
|
||||
'review your LDAP connection settings.'
|
||||
'LDAP connection to %s:%s (%s / %s) failed: %s',
|
||||
$this->hostname,
|
||||
$this->port,
|
||||
$this->bind_dn,
|
||||
'***' /* $this->bind_pw */,
|
||||
ldap_error($this->ds)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
putenv('LDAPRC');
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,12 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* Icinga 2 Web - Head for multiple monitoring frontends
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
|
||||
use Icinga\Web\Paginator\Adapter\QueryAdapter;
|
||||
|
||||
/**
|
||||
* Search class
|
||||
*
|
||||
* @package Icinga\Protocol\Ldap
|
||||
*/
|
||||
/**
|
||||
* Search abstraction class
|
||||
*
|
||||
@ -38,52 +18,24 @@ use Icinga\Web\Paginator\Adapter\QueryAdapter;
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
* @package Icinga\Protocol\Ldap
|
||||
*/
|
||||
class Query
|
||||
{
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $filters = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $limit_count;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $limit_offset;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $sort_columns = array();
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $count;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Icinga\Protocol\Ldap\Connection $connection LDAP Connection object
|
||||
* @return \Icinga\Protocol\Ldap\Query
|
||||
* @param Connection LDAP Connection object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
@ -106,14 +58,11 @@ class Query
|
||||
/**
|
||||
* Count result set, ignoring limits
|
||||
*
|
||||
* @param null $count
|
||||
* @param null $offset
|
||||
* @throws Exception
|
||||
* @return int
|
||||
*/
|
||||
public function limit($count = null, $offset = null)
|
||||
{
|
||||
if (!preg_match('~^\d+~', $count . $offset)) {
|
||||
if (! preg_match('~^\d+~', $count . $offset)) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Got invalid limit: %s, %s',
|
||||
@ -122,8 +71,8 @@ class Query
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->limit_count = (int)$count;
|
||||
$this->limit_offset = (int)$offset;
|
||||
$this->limit_count = (int) $count;
|
||||
$this->limit_offset = (int) $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -176,15 +125,12 @@ class Query
|
||||
{
|
||||
$result = $this->fetchAll();
|
||||
$sorted = array();
|
||||
$quotedDn = preg_quote($this->connection->getDN(), '/');
|
||||
foreach ($result as $key => & $item) {
|
||||
$new_key = LdapUtils::implodeDN(
|
||||
array_reverse(
|
||||
LdapUtils::explodeDN(
|
||||
preg_replace(
|
||||
'/,' . preg_quote($this->connection->getDN(), '/') . '$/',
|
||||
'',
|
||||
$key
|
||||
)
|
||||
preg_replace('/,' . $quotedDn . '$/', '', $key)
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -194,7 +140,11 @@ class Query
|
||||
ksort($sorted);
|
||||
|
||||
$tree = Root::forConnection($this->connection);
|
||||
$root_dn = $tree->getDN();
|
||||
foreach ($sorted as $sort_key => & $key) {
|
||||
if ($key === $root_dn) {
|
||||
continue;
|
||||
}
|
||||
$tree->createChildByDN($key, $result[$key]);
|
||||
}
|
||||
return $tree;
|
||||
@ -246,8 +196,6 @@ class Query
|
||||
*
|
||||
* This creates an objectClass filter
|
||||
*
|
||||
* @param $objectClass
|
||||
* @param array $fields
|
||||
* @return Query
|
||||
*/
|
||||
public function from($objectClass, $fields = array())
|
||||
@ -308,8 +256,6 @@ class Query
|
||||
/**
|
||||
* Return a pagination adapter for the current query
|
||||
*
|
||||
* @param null $limit
|
||||
* @param null $page
|
||||
* @return \Zend_Paginator
|
||||
*/
|
||||
public function paginate($limit = null, $page = null)
|
||||
@ -325,7 +271,7 @@ class Query
|
||||
}
|
||||
$paginator = new \Zend_Paginator(
|
||||
// TODO: Adapter doesn't fit yet:
|
||||
new QueryAdapter($this)
|
||||
new \Icinga\Web\Paginator\Adapter\QueryAdapter($this)
|
||||
);
|
||||
$paginator->setItemCountPerPage($limit);
|
||||
$paginator->setCurrentPageNumber($page);
|
||||
@ -364,7 +310,7 @@ class Query
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
* Descructor
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
|
@ -1,6 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Protocol\Livestatus;
|
||||
|
||||
use Icinga\Application\Benchmark;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Backend class managing handling MKI Livestatus connections
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* <code>
|
||||
* $lconf = new Connection((object) array(
|
||||
* 'hostname' => 'localhost',
|
||||
* 'root_dn' => 'dc=monitoring,dc=...',
|
||||
* 'bind_dn' => 'cn=Mangager,dc=monitoring,dc=...',
|
||||
* 'bind_pw' => '***'
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
const TYPE_UNIX = 1;
|
||||
@ -49,19 +71,23 @@ class Connection
|
||||
$this->assertPhpExtensionLoaded('sockets');
|
||||
if ($socket[0] === '/') {
|
||||
if (! is_writable($socket)) {
|
||||
throw new \Exception(sprintf(
|
||||
'Cannot write to livestatus socket "%s"',
|
||||
$socket
|
||||
));
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'Cannot write to livestatus socket "%s"',
|
||||
$socket
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->socket_type = self::TYPE_UNIX;
|
||||
$this->socket_path = $socket;
|
||||
} else {
|
||||
if (! preg_match('~^tcp://([^:]+):(\d+)~', $socket, $m)) {
|
||||
throw new \Exception(sprintf(
|
||||
'Invalid TCP socket syntax: "%s"',
|
||||
$socket
|
||||
));
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'Invalid TCP socket syntax: "%s"',
|
||||
$socket
|
||||
)
|
||||
);
|
||||
}
|
||||
// TODO: Better syntax checks
|
||||
$this->socket_host = $m[1];
|
||||
@ -80,17 +106,17 @@ class Connection
|
||||
{
|
||||
$count = clone($query);
|
||||
$count->count();
|
||||
\Icinga\Benchmark::measure('Sending Livestatus Count Query');
|
||||
$data = $this->_fetch((string) $count);
|
||||
\Icinga\Benchmark::measure('Got Livestatus count result');
|
||||
Benchmark::measure('Sending Livestatus Count Query');
|
||||
$data = $this->doFetch((string) $count);
|
||||
Benchmark::measure('Got Livestatus count result');
|
||||
return $data[0][0];
|
||||
}
|
||||
|
||||
public function fetchAll(Query $query)
|
||||
{
|
||||
\Icinga\Benchmark::measure('Sending Livestatus Query');
|
||||
$data = $this->_fetch((string) $query);
|
||||
\Icinga\Benchmark::measure('Got Livestatus Data');
|
||||
Benchmark::measure('Sending Livestatus Query');
|
||||
$data = $this->doFetch((string) $query);
|
||||
Benchmark::measure('Got Livestatus Data');
|
||||
if ($query->hasColumns()) {
|
||||
$headers = $query->getColumnAliases();
|
||||
} else {
|
||||
@ -114,12 +140,12 @@ class Connection
|
||||
$query->getLimit()
|
||||
);
|
||||
}
|
||||
\Icinga\Benchmark::measure('Data sorted, limits applied');
|
||||
Benchmark::measure('Data sorted, limits applied');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function _fetch($raw_query)
|
||||
protected function doFetch($raw_query)
|
||||
{
|
||||
$conn = $this->getConnection();
|
||||
$this->writeToSocket($raw_query);
|
||||
@ -128,15 +154,17 @@ class Connection
|
||||
$length = (int) trim(substr($header, 4));
|
||||
$body = $this->readFromSocket($length);
|
||||
if ($status !== 200) {
|
||||
throw new \Exception(sprintf(
|
||||
'Problem while reading %d bytes from livestatus: %s',
|
||||
$length,
|
||||
$body
|
||||
));
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Problem while reading %d bytes from livestatus: %s',
|
||||
$length,
|
||||
$body
|
||||
)
|
||||
);
|
||||
}
|
||||
$result = json_decode($body);
|
||||
if ($result === null) {
|
||||
throw new \Exception('Got invalid response body from livestatus');
|
||||
throw new Exception('Got invalid response body from livestatus');
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -147,13 +175,15 @@ class Connection
|
||||
$offset = 0;
|
||||
$buffer = '';
|
||||
|
||||
while($offset < $length) {
|
||||
while ($offset < $length) {
|
||||
$data = socket_read($this->connection, $length - $offset);
|
||||
if ($data === false) {
|
||||
throw new \Exception(sprintf(
|
||||
'Failed to read from livestatus socket: %s',
|
||||
socket_strerror(socket_last_error($this->connection))
|
||||
));
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Failed to read from livestatus socket: %s',
|
||||
socket_strerror(socket_last_error($this->connection))
|
||||
)
|
||||
);
|
||||
}
|
||||
$size = strlen($data);
|
||||
$offset += $size;
|
||||
@ -164,10 +194,13 @@ class Connection
|
||||
}
|
||||
}
|
||||
if ($offset !== $length) {
|
||||
throw new \Exception(sprintf(
|
||||
'Got only %d instead of %d bytes from livestatus socket',
|
||||
$offset, $length
|
||||
));
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'Got only %d instead of %d bytes from livestatus socket',
|
||||
$offset,
|
||||
$length
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
@ -185,10 +218,12 @@ class Connection
|
||||
protected function assertPhpExtensionLoaded($name)
|
||||
{
|
||||
if (! extension_loaded($name)) {
|
||||
throw new \Exception(sprintf(
|
||||
'The extension "%s" is not loaded',
|
||||
$name
|
||||
));
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'The extension "%s" is not loaded',
|
||||
$name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,24 +248,28 @@ class Connection
|
||||
|
||||
$this->connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
if (! @socket_connect($this->connection, $this->socket_host, $this->socket_port)) {
|
||||
throw new \Exception(sprintf(
|
||||
'Cannot connect to livestatus TCP socket "%s:%d": %s',
|
||||
$this->socket_host,
|
||||
$this->socket_port,
|
||||
socket_strerror(socket_last_error($this->connection))
|
||||
));
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'Cannot connect to livestatus TCP socket "%s:%d": %s',
|
||||
$this->socket_host,
|
||||
$this->socket_port,
|
||||
socket_strerror(socket_last_error($this->connection))
|
||||
)
|
||||
);
|
||||
}
|
||||
socket_set_option($this->connection, SOL_TCP, TCP_NODELAY, 1);
|
||||
}
|
||||
|
||||
protected function establishSocketConnection()
|
||||
{
|
||||
$this->connection = socket_create(AF_UNIX, SOCK_STREAM, 0);
|
||||
$this->connection = socket_create(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (! socket_connect($this->connection, $this->socket_path)) {
|
||||
throw new \Exception(sprintf(
|
||||
'Cannot connect to livestatus local socket "%s"',
|
||||
$this->socket_path
|
||||
));
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'Cannot connect to livestatus local socket "%s"',
|
||||
$this->socket_path
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,4 +285,3 @@ class Connection
|
||||
$this->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Protocol\Livestatus;
|
||||
use Icinga\Protocol;
|
||||
|
||||
class Query extends Protocol\AbstractQuery
|
||||
use Icinga\Protocol\AbstractQuery;
|
||||
|
||||
class Query extends AbstractQuery
|
||||
{
|
||||
|
||||
protected $connection;
|
||||
@ -62,7 +63,10 @@ class Query extends Protocol\AbstractQuery
|
||||
|
||||
public function order($col)
|
||||
{
|
||||
if (($pos = strpos($col, ' ')) !== false) {
|
||||
if (($pos = strpos($col, ' ')) === false) {
|
||||
$col = $col;
|
||||
$dir = self::SORT_ASC;
|
||||
} else {
|
||||
$dir = strtoupper(substr($col, $pos + 1));
|
||||
if ($dir === 'DESC') {
|
||||
$dir = self::SORT_DESC;
|
||||
@ -70,8 +74,6 @@ class Query extends Protocol\AbstractQuery
|
||||
$dir = self::SORT_ASC;
|
||||
}
|
||||
$col = substr($col, 0, $pos);
|
||||
} else {
|
||||
$col = $col;
|
||||
}
|
||||
$this->order_columns[] = array($col, $dir);
|
||||
return $this;
|
||||
@ -82,11 +84,13 @@ class Query extends Protocol\AbstractQuery
|
||||
public function limit($count = null, $offset = null)
|
||||
{
|
||||
if (! preg_match('~^\d+~', $count . $offset)) {
|
||||
throw new Exception(sprintf(
|
||||
'Got invalid limit: %s, %s',
|
||||
$count,
|
||||
$offset
|
||||
));
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Got invalid limit: %s, %s',
|
||||
$count,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->limit_count = (int) $count;
|
||||
$this->limit_offset = (int) $offset;
|
||||
@ -116,10 +120,12 @@ class Query extends Protocol\AbstractQuery
|
||||
public function from($table, $columns = null)
|
||||
{
|
||||
if (! $this->connection->hasTable($table)) {
|
||||
throw new Exception(sprintf(
|
||||
'This livestatus connection does not provide "%s"',
|
||||
$table
|
||||
));
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'This livestatus connection does not provide "%s"',
|
||||
$table
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->table = $table;
|
||||
if (is_array($columns)) {
|
||||
@ -207,4 +213,3 @@ class Query extends Protocol\AbstractQuery
|
||||
unset($this->connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user