Current File : /home/escuelai/public_html/it/src/System/Status/StatusChecker.php |
<?php
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2022 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/
namespace Glpi\System\Status;
use AuthLDAP;
use CronTask;
use DBConnection;
use DBmysql;
use MailCollector;
use Plugin;
use Toolbox;
/**
* @since 9.5.0
*/
final class StatusChecker
{
/**
* The plugin or service is working as expected.
*/
public const STATUS_OK = 'OK';
/**
* The plugin or service is working but may have some issues
*/
public const STATUS_WARNING = 'WARNING';
/**
* The plugin or service is reachable but not working as expected.
*/
public const STATUS_PROBLEM = 'PROBLEM';
/**
* Unable to get the status of a plugin or service.
* This is likely due to a prerequisite plugin or service being unavailable or the plugin not implementing the status hook.
* For example, some checks require the DB to be accessible.
*/
public const STATUS_NO_DATA = 'NO_DATA';
/**
* Get all registered services
* @return array Array of services keyed by name.
* The value for each service is expected to be an array containing a class name and a method name relating to the method that will do the check.
* @since 10.0.0
*/
public static function getServices(): array
{
return [
'db' => [self::class, 'getDBStatus'],
'cas' => [self::class, 'getCASStatus'],
'ldap' => [self::class, 'getLDAPStatus'],
'imap' => [self::class, 'getIMAPStatus'],
'mail_collectors' => [self::class, 'getMailCollectorStatus'],
'crontasks' => [self::class, 'getCronTaskStatus'],
'filesystem' => [self::class, 'getFilesystemStatus'],
'plugins' => [self::class, 'getPluginsStatus']
];
}
/**
* Calculate the overall GLPI status or the overall service status based on all child status checks
* @param array $status The status array for all services or a specific service check.
* @return string The calculated status.
* One of {@link STATUS_NO_DATA}, {@link STATUS_OK}, {@link STATUS_WARNING}, or {@link STATUS_PROBLEM}.
* @since 10.0.0
*/
public static function calculateGlobalStatus(array $status)
{
$statuses = array_column($status, 'status');
$global_status = self::STATUS_OK;
if (in_array(self::STATUS_PROBLEM, $statuses, true)) {
$global_status = self::STATUS_PROBLEM;
} else if (in_array(self::STATUS_WARNING, $statuses, true)) {
$global_status = self::STATUS_WARNING;
}
return $global_status;
}
/**
* Get a service's status
*
* @param string|null $service The name of the service or if null/'all' all services will be checked
* @param bool $public_only True if only public information should be available in the status check.
* If true, assume the data is being viewed by an anonymous user.
* @param bool $as_array True if the service check result should be returned as an array instead of a plain-text string.
* @return array|string An array or string with the result based on the $as_array parameter value.
* @since 10.0.0
*/
public static function getServiceStatus(?string $service, $public_only = true, $as_array = true)
{
$services = self::getServices();
if ($service === 'all' || $service === null) {
$status = [
'glpi' => [
'status' => self::STATUS_OK
]
];
foreach ($services as $name => $service_check_method) {
$service_status = self::getServiceStatus($name, $public_only, true);
$status[$name] = $service_status;
}
$status['glpi']['status'] = self::calculateGlobalStatus($status);
if ($as_array) {
return $status;
} else {
return self::getPlaintextOutput($status);
}
}
if (!array_key_exists($service, $services)) {
return $as_array ? [] : '';
}
$service_check_method = $services[$service];
if (method_exists($service_check_method[0], $service_check_method[1])) {
$service_status = $service_check_method($public_only);
if ($as_array) {
return $service_status;
}
return strtoupper($service) . '_' . $service_status['status'];
}
return $as_array ? [] : '';
}
/**
* @param bool $public_only True if only public status information should be given.
* @return array
*/
public static function getDBStatus($public_only = true): array
{
static $status = null;
if ($status === null) {
$status = [
'status' => self::STATUS_OK,
'master' => [
'status' => self::STATUS_OK,
],
'slaves' => [
'status' => self::STATUS_NO_DATA,
'servers' => []
]
];
// Check replica SQL server connection
if (DBConnection::isDBSlaveActive()) {
$DBslave = DBConnection::getDBSlaveConf();
if (is_array($DBslave->dbhost)) {
$hosts = $DBslave->dbhost;
} else {
$hosts = [$DBslave->dbhost];
}
if (count($hosts)) {
$status['slaves']['status'] = self::STATUS_OK;
}
foreach ($hosts as $num => $name) {
$diff = DBConnection::getReplicateDelay($num);
if (abs($diff) > 1000000000) {
$status['slaves']['servers'][$num] = [
'status' => self::STATUS_PROBLEM,
'replication_delay' => '-1',
'status_msg' => _x('glpi_status', 'Replication delay is too high')
];
$status['slaves']['status'] = self::STATUS_PROBLEM;
$status['status'] = self::STATUS_PROBLEM;
} else if (abs($diff) > HOUR_TIMESTAMP) {
$status['slaves']['servers'][$num] = [
'status' => self::STATUS_PROBLEM,
'replication_delay' => abs($diff),
'status_msg' => _x('glpi_status', 'Replication delay is too high')
];
$status['slaves']['status'] = self::STATUS_PROBLEM;
$status['status'] = self::STATUS_PROBLEM;
} else {
$status['slaves']['servers'][$num] = [
'status' => self::STATUS_OK,
'replication_delay' => abs($diff)
];
}
}
}
// Check main server connection
if (!@DBConnection::establishDBConnection(false, true, false)) {
$status['master'] = [
'status' => self::STATUS_PROBLEM,
'status_msg' => _x('glpi_status', 'Unable to connect to the main database')
];
$status['status'] = self::STATUS_PROBLEM;
}
}
// Set new properties. Master and slave are deprecated given their implications in English.
$status['main'] = $status['master'];
$status['replicas'] = $status['slaves'];
return $status;
}
private static function isDBAvailable(): bool
{
static $db_ok = null;
if ($db_ok === null) {
$status = self::getDBStatus();
$db_ok = ($status['main']['status'] === self::STATUS_OK || $status['replicas']['status'] === self::STATUS_OK);
}
return $db_ok;
}
/**
* @param bool $public_only True if only public status information should be given.
* @return array
*/
public static function getLDAPStatus($public_only = true): array
{
static $status = null;
if ($status === null) {
$status = [
'status' => self::STATUS_NO_DATA,
'servers' => []
];
if (self::isDBAvailable()) {
// Check LDAP Auth connections
$ldap_methods = getAllDataFromTable('glpi_authldaps', ['is_active' => 1]);
$total_servers = count($ldap_methods);
$total_error = 0;
$global_status = self::STATUS_NO_DATA;
$message = null;
if ($total_servers > 0) {
$global_status = self::STATUS_OK;
foreach ($ldap_methods as $method) {
$ldap = null;
try {
if (
AuthLDAP::tryToConnectToServer(
$method,
$method['rootdn'],
(new \GLPIKey())->decrypt($method['rootdn_passwd'])
)
) {
$status['servers'][$method['name']] = [
'status' => self::STATUS_OK
];
} else {
$status['servers'][$method['name']] = [
'status' => self::STATUS_PROBLEM,
'status_msg' => _x('glpi_status', 'Unable to connect to the LDAP server')
];
$total_error++;
$global_status = self::STATUS_PROBLEM;
}
} catch (\RuntimeException $e) {
// May be missing LDAP extension (Probably test environment)
$status['servers'][$method['name']] = [
'status' => self::STATUS_PROBLEM
];
$total_error++;
$global_status = self::STATUS_PROBLEM;
}
}
if ($global_status !== self::STATUS_OK) {
$message = sprintf(_x('glpi_status', 'OK: %d, WARNING: %d, PROBLEM: %d, TOTAL: %d'), $total_servers - $total_error, 0, $total_error, $total_servers);
}
}
$status['status'] = $global_status;
if ($message !== null) {
$status['status_msg'] = $message;
}
}
}
return $status;
}
/**
* @param bool $public_only True if only public status information should be given.
* @return array
*/
public static function getIMAPStatus($public_only = true): array
{
static $status = null;
if ($status === null) {
$status = [
'status' => self::STATUS_NO_DATA,
'servers' => []
];
if (self::isDBAvailable()) {
// Check IMAP Auth connections
$imap_methods = getAllDataFromTable('glpi_authmails', ['is_active' => 1]);
$total_servers = count($imap_methods);
$total_error = 0;
$global_status = self::STATUS_NO_DATA;
$message = null;
if ($total_servers > 0) {
$global_status = self::STATUS_OK;
foreach ($imap_methods as $method) {
$param = Toolbox::parseMailServerConnectString($method['connect_string'], true);
if ($param['ssl'] === true) {
$host = 'ssl://' . $param['address'];
} else if ($param['tls'] === true) {
$host = 'tls://' . $param['address'];
} else {
$host = $param['address'];
}
if ($fp = @fsockopen($host, $param['port'], $errno, $errstr, 1)) {
$status['servers'][$method['name']] = [
'status' => self::STATUS_OK
];
} else {
$status['servers'][$method['name']] = [
'status' => self::STATUS_PROBLEM,
'status_msg' => _x('glpi_status', 'Unable to connect to the IMAP server')
];
$total_error++;
$global_status = self::STATUS_PROBLEM;
}
if ($fp !== false) {
fclose($fp);
}
}
if ($global_status !== self::STATUS_OK) {
$message = sprintf(_x('glpi_status', 'OK: %d, WARNING: %d, PROBLEM: %d, TOTAL: %d'), $total_servers - $total_error, 0, $total_error, $total_servers);
}
}
$status['status'] = $global_status;
if ($message !== null) {
$status['status_msg'] = $message;
}
}
}
return $status;
}
/**
* @param bool $public_only True if only public status information should be given.
* @return array
*/
public static function getCASStatus($public_only = true): array
{
global $CFG_GLPI;
static $status = null;
if ($status === null) {
$status['status'] = self::STATUS_NO_DATA;
if (!empty($CFG_GLPI['cas_host'])) {
$url = $CFG_GLPI['cas_host'];
if (!empty($CFG_GLPI['cas_port'])) {
$url .= ':' . (int)$CFG_GLPI['cas_port'];
}
$url .= '/' . $CFG_GLPI['cas_uri'];
$data = Toolbox::getURLContent($url);
if (!empty($data)) {
$status['status'] = self::STATUS_OK;
} else {
$status['status'] = self::STATUS_PROBLEM;
}
}
}
return $status;
}
/**
* @param bool $public_only True if only public status information should be given.
* @return array
*/
public static function getMailCollectorStatus($public_only = true): array
{
static $status = null;
if ($status === null) {
$status = [
'status' => self::STATUS_NO_DATA,
'servers' => []
];
if (self::isDBAvailable()) {
$mailcollectors = getAllDataFromTable('glpi_mailcollectors', ['is_active' => 1]);
$total_servers = count($mailcollectors);
$total_error = 0;
$global_status = self::STATUS_NO_DATA;
$message = null;
if ($total_servers > 0) {
$global_status = self::STATUS_OK;
$mailcol = new MailCollector();
foreach ($mailcollectors as $mc) {
if ($mailcol->getFromDB($mc['id'])) {
try {
$mailcol->connect();
$status['servers'][$mc['name']] = [
'status' => self::STATUS_OK
];
} catch (\Exception $e) {
$status['servers'][$mc['name']] = [
'status' => self::STATUS_PROBLEM,
'error_code' => $e->getCode(),
'status_msg' => $e->getMessage()
];
$total_error++;
$global_status = self::STATUS_PROBLEM;
}
}
}
if ($global_status !== self::STATUS_OK) {
$message = sprintf(_x('glpi_status', 'OK: %d, WARNING: %d, PROBLEM: %d, TOTAL: %d'), $total_servers - $total_error, 0, $total_error, $total_servers);
}
}
$status['status'] = $global_status;
if ($message !== null) {
$status['status_msg'] = $message;
}
}
}
return $status;
}
/**
* @param bool $public_only True if only public status information should be given.
* @return array
*/
public static function getCronTaskStatus($public_only = true): array
{
static $status = null;
if ($status === null) {
$status = [
'status' => self::STATUS_NO_DATA,
'stuck' => []
];
if (self::isDBAvailable()) {
$crontasks = getAllDataFromTable('glpi_crontasks');
$running = count(array_filter($crontasks, static function ($crontask) {
return $crontask['state'] === CronTask::STATE_RUNNING;
}));
$stuck_crontasks = getAllDataFromTable(
'glpi_crontasks',
[
'state' => CronTask::STATE_RUNNING,
'OR' => [
new \QueryExpression(
'(unix_timestamp(' . DBmysql::quoteName('lastrun') . ') + 2 * ' .
DBmysql::quoteName('frequency') . ' < unix_timestamp(now()))'
),
new \QueryExpression(
'(unix_timestamp(' . DBmysql::quoteName('lastrun') . ') + 2 * ' .
HOUR_TIMESTAMP . ' < unix_timestamp(now()))'
)
]
]
);
foreach ($stuck_crontasks as $ct) {
$status['stuck'][] = $ct['name'];
}
$status['status'] = count($status['stuck']) ? self::STATUS_PROBLEM : self::STATUS_OK;
$status['status_msg'] = sprintf(_x('glpi_status', 'RUNNING: %d, STUCK: %d, TOTAL: %d'), $running, count($stuck_crontasks), count($crontasks));
}
}
return $status;
}
/**
* @param bool $public_only True if only public status information should be given.
* @return array
*/
public static function getFilesystemStatus($public_only = true): array
{
static $status = null;
if ($status === null) {
$status = [
'status' => self::STATUS_OK,
'session_dir' => [
'status' => self::STATUS_OK
]
];
// Check session dir (useful when NFS mounted))
if (!is_dir(GLPI_SESSION_DIR)) {
$status['session_dir'] = [
'status' => self::STATUS_PROBLEM,
'status_msg' => sprintf(_x('glpi_status', '%s variable is not a directory'), 'GLPI_SESSION_DIR')
];
$status['status'] = self::STATUS_PROBLEM;
} else if (!is_writable(GLPI_SESSION_DIR)) {
$status['session_dir'] = [
'status' => self::STATUS_PROBLEM,
'status_msg' => sprintf(_x('glpi_status', '%s variable is not writable'), 'GLPI_SESSION_DIR')
];
$status['status'] = self::STATUS_PROBLEM;
}
}
return $status;
}
/**
*
* @since 9.5.0
* @param bool $public_only True if only public status information should be given.
* @return array
*/
public static function getPluginsStatus($public_only = true): array
{
static $status = null;
if ($status === null) {
$plugins = Plugin::getPlugins();
$status = [];
foreach ($plugins as $plugin) {
// Old-style plugin status hook which only modified the global OK status.
$param = [
'ok' => true,
'_public_only' => $public_only
];
$plugin_status = Plugin::doOneHook($plugin, 'status', $param);
if ($plugin_status === null) {
continue;
}
unset($plugin_status['_public_only']);
if (isset($plugin_status['ok']) && count(array_keys($plugin_status)) === 1) {
$status[$plugin] = [
'status' => $plugin_status['ok'] ? self::STATUS_OK : self::STATUS_PROBLEM,
'version' => Plugin::getPluginFilesVersion($plugin)
];
} else {
$status[$plugin] = $plugin_status;
}
}
}
if (count($status) === 0) {
$status['status'] = self::STATUS_NO_DATA;
} else {
if ($public_only) {
// Only show overall plugin status
// Giving out plugin names and versions to anonymous users could make it easier to target insecure plugins and versions
$statuses = array_column($status, 'status');
$all_ok = !in_array(self::STATUS_PROBLEM, $statuses, true);
return ['status' => $all_ok ? self::STATUS_OK : self::STATUS_PROBLEM];
}
}
return $status;
}
/**
* @param bool $public_only True if only public status information should be given.
* @param bool $as_array
* @return array|string
* @deprecated 10.0.0 Use {@link self::getServiceStatus} instead
*/
public static function getFullStatus($public_only = true, $as_array = true)
{
Toolbox::deprecated('Use StatusChecker::getServiceStatus for service checks instead');
return self::getServiceStatus(null, $public_only, $as_array);
}
/**
* Format the given full service status result as a plain-text output compatible with previous versions of GLPI.
* @param array $status
* @return string
* @deprecated 10.0.0
*/
private static function getPlaintextOutput(array $status): string
{
// Deprecated notices are done on the /status.php endpoint and CLI commands to give better migration hints
$output = '';
// Plain-text output
if (count($status['db']['slaves'])) {
foreach ($status['db']['slaves']['servers'] as $num => $slave_info) {
$output .= "GLPI_DBSLAVE_{$num}_{$slave_info['status']}\n";
}
} else {
$output .= "No slave DB\n"; // Leave as "slave" since plain text is already deprecated
}
$output .= "GLPI_DB_{$status['db']['master']['status']}\n";
$output .= "GLPI_SESSION_DIR_{$status['filesystem']['session_dir']['status']}\n";
if (count($status['ldap']['servers'])) {
$output .= 'Check LDAP servers:';
foreach ($status['ldap']['servers'] as $name => $ldap_info) {
$output .= " {$name}_{$ldap_info['status']}\n";
}
} else {
$output .= "No LDAP server\n";
}
if (count($status['imap']['servers'])) {
$output .= 'Check IMAP servers:';
foreach ($status['imap']['servers'] as $name => $imap_info) {
$output .= " {$name}_{$imap_info['status']}\n";
}
} else {
$output .= "No IMAP server\n";
}
if (isset($status['cas']['status']) && $status['cas']['status'] !== self::STATUS_NO_DATA) {
$output .= "CAS_SERVER_{$status['cas']['status']}\n";
} else {
$output .= "No CAS server\n";
}
if (count($status['mail_collectors']['servers'])) {
$output .= 'Check mail collectors:';
foreach ($status['mail_collectors']['servers'] as $name => $collector_info) {
$output .= " {$name}_{$collector_info['status']}\n";
}
} else {
$output .= "No mail collector\n";
}
if (count($status['crontasks']['stuck'])) {
$output .= 'Check crontasks:';
foreach ($status['crontasks']['stuck'] as $name) {
$output .= " {$name}_PROBLEM\n";
}
} else {
$output .= "Crontasks_OK\n";
}
// Overall Status
$output .= "\nGLPI_{$status['glpi']['status']}\n";
return $output;
}
}