This file is indexed.

/usr/share/php/Icinga/Protocol/Livestatus/Connection.php is in php-icinga 2.1.0-1ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */

namespace Icinga\Protocol\Livestatus;

use Icinga\Application\Benchmark;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\SystemPermissionException;
use Icinga\Exception\IcingaException;
use Exception;
use SplFixedArray;

/**
 * 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;
    const TYPE_TCP  = 2;

    const FIELD_SEPARATOR = '`';

    protected $bytesRead = 0;
    protected $responseSize;
    protected $status;
    protected $headers;

    // List of available Livestatus tables. Kept here as we otherwise get no
    // useful error message
    protected $available_tables = array(
        'hosts',               // hosts
        'services',            // services, joined with all data from hosts
        'hostgroups',          // hostgroups
        'servicegroups',       // servicegroups
        'contactgroups',       // contact groups
        'servicesbygroup',     // all services grouped by service groups
        'servicesbyhostgroup', // all services grouped by host groups
        'hostsbygroup',        // all hosts grouped by host groups
        'contacts',            // contacts
        'commands',            // defined commands
        'timeperiods',         // time period definitions (currently only name
                               // and alias)
        'downtimes',           // all scheduled host and service downtimes,
                               // joined with data from hosts and services.
        'comments',            // all host and service comments
        'log',                 // a transparent access to the nagios logfiles
                               // (include archived ones)ones
        'status',              // general performance and status information.
                               // This table contains exactly one dataset.
        'columns',             // a complete list of all tables and columns
                               // available via Livestatus, including
                               // descriptions!
        'statehist',           // 1.2.1i2 sla statistics for hosts and services,
                               // joined with data from hosts, services and log.
    );

    protected $socket_path;
    protected $socket_host;
    protected $socket_port;
    protected $socket_type;
    protected $connection;

    /**
     * Whether the given table name is valid
     *
     * @param string $name table name
     *
     * @return bool
     */
    public function hasTable($name)
    {
        return in_array($name, $this->available_tables);
    }

    public function __construct($socket = '/var/lib/icinga/rw/live')
    {
        $this->assertPhpExtensionLoaded('sockets');
        if ($socket[0] === '/') {
            if (! is_writable($socket)) {
                throw new SystemPermissionException(
                    '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 ConfigurationError(
                    'Invalid TCP socket syntax: "%s"',
                    $socket
                );
            }
            // TODO: Better config syntax checks
            $this->socket_host = $m[1];
            $this->socket_port = (int) $m[2];
            $this->socket_type = self::TYPE_TCP;
        }
    }

    /**
     * Count unlimited rows matching the query filter
     *
     * TODO: Currently hardcoded value, as the old variant was stupid
     *       Create a working variant doing this->execute(query->renderCount())...
     *
     * @param Query $query the query object
     *
     * @return int
     */
    public function count(Query $query)
    {
    return 100;
        $count = clone($query);
        // WTF? $count->count();
        Benchmark::measure('Sending Livestatus Count Query');
        $this->execute($query);
        $data = $this->fetchRowFromSocket();
        Benchmark::measure('Got Livestatus count result');
        return $data[0][0];
    }

    /**
     * Fetch a single row
     *
     * TODO: Currently based on fetchAll, that's bullshit
     *
     * @param Query $query the query object
     *
     * @return object the first result row
     */
    public function fetchRow(Query $query)
    {
        $all = $this->fetchAll($query);
        return array_shift($all);
    }

    /**
     * Fetch key/value pairs
     *
     * TODO: Currently slow, needs improvement
     *
     * @param Query $query the query object
     *
     * @return array
     */
    public function fetchPairs(Query $query)
    {
        $res = array();
        $all = $this->fetchAll($query);
        foreach ($all as $row) {
            // slow
            $keys = array_keys((array) $row);
            $res[$row->{$keys[0]}] = $row->{$keys[1]};
        }
        return $res;
    }

    /**
     * Fetch all result rows
     *
     * @param Query $query the query object
     *
     * @return array
     */
    public function fetchAll(Query $query)
    {
        Benchmark::measure('Sending Livestatus Query');
        $this->execute($query);
        Benchmark::measure('Got Livestatus Data');

        if ($query->hasColumns()) {
            $headers = $query->getColumnAliases();
        } else {
            // TODO: left this here, find out how to handle it better
            die('F*** no data');
            $headers = array_shift($data);
        }
        $result = array();
        $filter = $query->filterIsSupported() ? null : $query->getFilter();

        while ($row = $this->fetchRowFromSocket()) {
            $r = new ResponseRow($row, $query);
            $res = $query->resultRow($row);
            if ($filter !== null && ! $filter->matches($res)) continue;
            $result[] = $res;
        }

        if ($query->hasOrder()) {
            usort($result, array($query, 'compare'));
        }
        if ($query->hasLimit()) {
            $result = array_slice(
                $result,
                $query->getOffset(),
                $query->getLimit()
            );
        }
        Benchmark::measure('Data sorted, limits applied');

        return $result;
    }

    protected function hasBeenExecuted()
    {
        return $this->status !== null;
    }

    protected function execute($query)
    {
        // Reset state
        $this->status = null;
        $this->responseSize = null;
        $this->bytesRead = 0;

        $raw = $query->toString();

        Benchmark::measure($raw);

        // "debug"
        // echo $raw . "\n<br>";
        $this->writeToSocket($raw);
        $header = $this->readLineFromSocket();

        if (! preg_match('~^(\d{3})\s\s*(\d+)$~', $header, $m)) {
            $this->disconnect();
            throw new Exception(
                sprintf('Got invalid header. First 16 Bytes: %s', $header)
            );
        }
        $this->status = (int) $m[1];
        $this->bytesRead = 0;
        $this->responseSize = (int) $m[2];
        if ($this->status !== 200) {
            // "debug"
            //die(var_export($raw, 1));
            throw new Exception(
                sprintf(
                    'Error %d while querying livestatus: %s %s',
                    $this->status,
                    $raw,
                    $this->readLineFromSocket()
                )
            );
        }
        $this->discoverColumnHeaders($query);
    }

    protected function discoverColumnHeaders($query)
    {
        if ($query->hasColumns()) {
            $this->headers = $query->getColumnAliases();
        } else {
            $this->headers = $this->splitLine($this->readLineFromSocket());
        }
    }

    protected function splitLine(& $line)
    {
        if ($this->headers === null) {
            $res = array();
        } else {
            $res = new SplFixedArray(count($this->headers));
            $size = count($res);
        }
        $start = 0;
        $col = 0;
        while (false !== ($pos = strpos($line, self::FIELD_SEPARATOR, $start))) {
// TODO: safety measure for not killing the SPL. To be removed once code is clean
if ($col > $size -1 ) return $res;  // ???
            $res[$col] = substr($line, $start, $pos - $start);
            $start = $pos + 1;
            $col++;
        }
// TODO: safety measure for not killing the SPL. To be removed once code is clean
if ($col > $size - 1) return $res;
        $res[$col] = rtrim(substr($line, $start), "\r\n");
        return $res;
    }

    public function fetchRowFromSocket()
    {
        $line = $this->readLineFromSocket();
        if (! $line) {
            return false;
        }
        return $this->splitLine($line);
    }

    protected function readLineFromSocket()
    {
        if ($this->bytesRead === $this->responseSize) {
            return false;
        }
        $maxRowLength = 100 * 1024;
        $row = socket_read($this->getConnection(), $maxRowLength, PHP_NORMAL_READ);
        $this->bytesRead += strlen($row);

        if ($row === false) {
            $this->socketError('Failed to read next row from livestatus socket');
        }
        return $row;
    }

    /**
     * Write given string to livestatus socket
     *
     * @param  string $data Data string to write to the socket
     *
     * @return boolean
     */
    protected function writeToSocket($data)
    {
        $res = @socket_write($this->getConnection(), $data);
        if ($res === false) {
            $this->socketError('Writing to livestatus socket failed');
        }
        return true;
    }

    /**
     * Raise an exception showing given message string and last socket error
     *
     * TODO: Find a better exception type for such errors
     *
     * @throws IcingaException
     */
    protected function socketError($msg)
    {
        throw new IcingaException(
            $msg . ': ' . socket_strerror(socket_last_error($this->connection))
        );
    }

    protected function assertPhpExtensionLoaded($name)
    {
        if (! extension_loaded($name)) {
            throw new IcingaException(
                'The extension "%s" is not loaded',
                $name
            );
        }
    }

    protected function getConnection()
    {
        if ($this->connection === null) {
            Benchmark::measure('Establishing livestatus connection...');

            if ($this->socket_type === self::TYPE_TCP) {
                $this->establishTcpConnection();
                Benchmark::measure('...got TCP socket');
            } else {
                $this->establishSocketConnection();
                Benchmark::measure('...got local socket');
            }
        }
        return $this->connection;
    }

    /**
     * Establish a TCP socket connection
     */
    protected function establishTcpConnection()
    {
        $this->connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if (! @socket_connect($this->connection, $this->socket_host, $this->socket_port)) {
            throw new IcingaException(
                '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);
    }

    /**
     * Establish a UNIX socket connection
     */
    protected function establishSocketConnection()
    {
        $this->connection = socket_create(AF_UNIX, SOCK_STREAM, 0);
        if (! socket_connect($this->connection, $this->socket_path)) {
            throw new IcingaException(
                'Cannot connect to livestatus local socket "%s"',
                $this->socket_path
            );
        }
    }

    public function connect()
    {
        if (!$this->connection) {
            $this->getConnection();
        }

        return $this;
    }

    /**
     * Disconnect in case we are connected to a Livestatus socket
     *
     * @return $this
     */
    public function disconnect()
    {
        if (is_resource($this->connection)
            && get_resource_type($this->connection) === 'Socket')
        {
            Benchmark::measure('Disconnecting livestatus...');
            socket_close($this->connection);
            Benchmark::measure('...socket closed');
        }
        return $this;
    }

    /**
     * Try to cleanly close the socket on shutdown
     */
    public function __destruct()
    {
        $this->disconnect();
    }
}