Files
dokuwiki-plugins/lib/plugins/loglog/helper/alert.php

191 lines
5.2 KiB
PHP
Raw Normal View History

2025-10-09 15:04:29 +11:00
<?php
/**
* Class helper_plugin_loglog_alert
*/
class helper_plugin_loglog_alert extends DokuWiki_Plugin
{
/**
* @var \helper_plugin_loglog_main
*/
protected $mainHelper;
/**
* @var \helper_plugin_loglog_logging
*/
protected $logHelper;
/** @var int */
protected $interval;
/** @var int */
protected $threshold;
/** @var int */
protected $now;
/** @var int */
protected $multiplier;
/** @var string */
protected $statfile;
public function __construct()
{
$this->mainHelper = $this->loadHelper('loglog_main');
$this->logHelper = $this->loadHelper('loglog_logging');
}
/**
* Check if any configured thresholds have been exceeded and trigger
* alert notifications accordingly.
*
* @return void
*/
public function checkAlertThresholds()
{
$this->handleThreshold(
\helper_plugin_loglog_main::LOGTYPE_AUTH_FAIL,
$this->getConf('login_failed_max'),
$this->getConf('login_failed_interval'),
$this->getConf('login_failed_email')
);
$this->handleThreshold(
\helper_plugin_loglog_main::LOGTYPE_AUTH_OK,
$this->getConf('login_success_max'),
$this->getConf('login_success_interval'),
$this->getConf('login_success_email')
);
}
/**
* Evaluates threshold configuration for given type of logged event
* and triggers email alerts.
*
* @param string $logType
* @param int $threshold
* @param int $minuteInterval
* @param string $email
*/
protected function handleThreshold($logType, $threshold, $minuteInterval, $email)
{
// proceed only if we have sufficient configuration
if (! $email || ! $threshold || ! $minuteInterval) {
return;
}
$this->resetMultiplier();
$this->threshold = $threshold;
$this->interval = $minuteInterval * 60;
$this->now = time();
$max = $this->now;
$min = $this->now - ($this->interval);
$msgNeedle = $this->mainHelper->getNotificationString($logType, 'msgNeedle');
$lines = $this->logHelper->readLines($min, $max);
$cnt = $this->logHelper->countMatchingLines($lines, $msgNeedle);
if ($cnt < $threshold) {
return;
}
global $conf;
$this->statfile = $conf['cachedir'] . '/loglog.' . $logType . '.stat';
if ($this->actNow()) {
io_saveFile($this->statfile, $this->multiplier);
$this->sendAlert($logType, $email);
}
}
/**
* Send alert email
*
* @param string $logType
* @param string $email
*/
protected function sendAlert($logType, $email)
{
$template = $this->localFN($logType);
$text = file_get_contents($template);
$this->mainHelper->sendEmail(
$email,
$this->getLang($this->mainHelper->getNotificationString($logType, 'emailSubjectLang')),
$text,
[
'threshold' => $this->threshold,
'interval' => $this->interval / 60, // falling back to minutes for the view
'now' => date('Y-m-d H:i', $this->now),
'sequence' => $this->getSequencePhase(),
'next_alert' => date('Y-m-d H:i', $this->getNextAlert()),
]
);
}
/**
* Check if it is time to act or wait this interval out
*
* @return bool
*/
protected function actNow()
{
$act = true;
if (!is_file($this->statfile)) {
return $act;
}
$lastAlert = filemtime($this->statfile);
$this->multiplier = (int)file_get_contents($this->statfile);
$intervalsAfterLastAlert = (int)floor(($this->now - $lastAlert) / $this->interval);
if ($intervalsAfterLastAlert === $this->multiplier) {
$this->increaseMultiplier();
} elseif ($intervalsAfterLastAlert < $this->multiplier) {
$act = false;
} elseif ($intervalsAfterLastAlert > $this->multiplier) {
$this->resetMultiplier(); // no longer part of series, reset multiplier
}
return $act;
}
/**
* Calculate which phase of sequential events we are in (possible attacks),
* based on the interval multiplier. 1 indicates the first incident,
* otherwise evaluate the exponent (because we multiply the interval by 2 on each alert).
*
* @return int
*/
protected function getSequencePhase()
{
return $this->multiplier === 1 ? $this->multiplier : log($this->multiplier, 2) + 1;
}
/**
* Calculate when the next alert is due based on the current multiplier
*
* @return int
*/
protected function getNextAlert()
{
return $this->now + $this->interval * $this->multiplier * 2;
}
/**
* Reset multiplier. Called when the triggering event is not part of a sequence.
*/
protected function resetMultiplier()
{
$this->multiplier = 1;
}
/**
* Increase multiplier. Called when the triggering event belongs to a sequence.
*/
protected function increaseMultiplier()
{
$this->multiplier *= 2;
}
}