Update LDAP and Livestatus protocol

This commit is contained in:
Eric Lippmann 2013-07-12 13:41:48 +02:00
parent e66d17dbf8
commit be3193a0d7
4 changed files with 328 additions and 377 deletions

View File

@ -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');
}
}

View File

@ -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()
{

View File

@ -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();
}
}

View File

@ -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);
}
}