Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions .github/workflows/plugin-ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,18 @@ jobs:
cat log/cacti.log
exit 1
fi
# The populated alert rule (id 1) must have been evaluated and have fired
# for the host "bob": the processor logs it and stores it in syslog_logs.
if ! grep -q "The Syslog Alert 'interface down alert' with Severity 'Warning', has been Triggered on Host 'bob'" log/cacti.log; then
echo "The populated alert rule did not fire"
cat log/cacti.log
exit 1
fi
alerts=$(mysql --defaults-file="$HOME/.my.cnf" cacti -N -e "SELECT COUNT(*) FROM syslog_logs WHERE alert_id = 1 AND host = 'bob'")
if [ "$alerts" -lt 1 ]; then
echo "The populated alert rule left no alert log row"
exit 1
fi

# Runs the real processor over queued messages and reads the results back.
# The script removes its rows and restores cacti.log to its starting size.
Expand All @@ -333,14 +345,17 @@ jobs:
if: always()
run: |
if [ -f ${{ github.workspace }}/cacti/log/cacti.log ]; then
if sudo grep -E "FATAL:|ERROR:|WARNING:" "${{ github.workspace }}/cacti/log/cacti.log"; then
status=0
matches=$(sudo grep -E "FATAL:|ERROR:|WARNING:" "${{ github.workspace }}/cacti/log/cacti.log") || status=$?
if [ "$status" -gt 1 ]; then
echo "Unable to inspect Cacti log"
exit "$status"
fi
# The only entry allowed is the alert the populated rule is meant to raise.
unexpected=$(printf '%s\n' "$matches" | grep -v -F "The Syslog Alert 'interface down alert' with Severity 'Warning', has been Triggered on Host 'bob'" | grep -v '^$') || true
if [ -n "$unexpected" ]; then
printf '%s\n' "$unexpected"
echo "Found FATAL/ERROR/WARNING entries in Cacti log"
exit 1
else
status=$?
if [ "$status" -ne 1 ]; then
echo "Unable to inspect Cacti log"
exit "$status"
fi
fi
fi
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@

* security: Substitute alert command tokens in a single pass so values containing a token name are not expanded again

* test: Insert a valid alert rule in the CI test data and assert in the workflow that the processor evaluates and fires it

* bug: Check sender names with A and AAAA records, accept IPv4 and IPv6 addresses without a lookup and store IPv6 addresses in one canonical form

* issue#317: Expire partitions by their actual UTC upper boundaries instead of partition count, preserving cutoff-spanning partitions and supporting legacy TO_DAYS layouts.

* issue#317: Defer automatic partition reorganization when overflow estimates exceed 100,000 rows or 128 MiB, or cannot be read; preserve the safety partition and report the reason through maintenance health.
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,13 @@ If you are upgrading to version 2.5 from an earlier version, make sure that you
update this template format and restart rsyslog. You may loose some syslog
data, but doing this in a timely fashion, will minimize data loss.

Senders that only have an IPv6 address are supported. With hostname resolution
enabled the plugin accepts an IPv4 or IPv6 address as it is, and stores an IPv6
address in its compressed lower case form (for example `2001:db8::1`) so one
address is kept under one device name. Names are looked up with A and AAAA
records. The `host` columns hold 64 characters, which is enough for any IPv6
address.

Ensure you restart rsyslog after these changes are completed. Other logging
servers such as Syslog-NG are also supported with this plugin. Please see some
additional documentation here: [Cacti Documentation
Expand Down
111 changes: 109 additions & 2 deletions includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -6121,6 +6121,95 @@ function syslog_strip_incoming_domains($max_seq) {
}
}

/**
* Return the canonical text of an IPv4 or IPv6 address, or false when the
* sender name is not an address literal.
*
* IPv6 text is normalized (lower case, compressed, no brackets) so that one
* address arriving in several spellings is stored under one host name. A zone
* suffix such as %eth0 is kept as sent.
*
* @param string $name The host name as sent by the remote system
*
* @return string|false The canonical address text, or false for a name
*/
function syslog_host_address(string $name): string|false {
if (preg_match('/^\[?([^\]%]+)(%[A-Za-z0-9_.-]+)?\]?$/', trim($name), $match) !== 1 || !filter_var($match[1], FILTER_VALIDATE_IP)) {
return false;
}

$zone = $match[2] ?? '';

// Only an IPv6 address can carry a zone.
if ($zone !== '' && strpos($match[1], ':') === false) {
return false;
}

return inet_ntop(inet_pton($match[1])) . $zone;
}

/**
* Whether a host name has an address record. Looks for A and AAAA records so a
* sender that only has an IPv6 address counts, and falls back to the system
* resolver, which also reads the hosts file.
*
* @param string $name The host name to look up
* @param callable $records Returns the A and AAAA records for a name, like dns_get_record()
* @param callable $legacy Returns the IPv4 address for a name, or the name itself, like gethostbyname()
*
* @return bool True when the name resolves
*/
function syslog_host_has_address(string $name, ?callable $records = null, ?callable $legacy = null): bool {
$records ??= 'dns_get_record';
$legacy ??= 'gethostbyname';

// A failed query is an answer here, not a PHP warning for the Cacti log.
set_error_handler(static fn() => true);

try {
$found = $records($name, DNS_A | DNS_AAAA);
} finally {
restore_error_handler();
}

if (is_array($found) && cacti_sizeof($found)) {
return true;
}

return $name != $legacy($name);
}

/**
* Point the unprocessed messages of one sender name at another name.
*
* @param string $from The name currently stored
* @param string $to The name to store
* @param int $max_seq The max_seq for syslog_incoming messages to process
* @param int $seq_start Optional first seq of a slice (0 for no bound)
* @param int $seq_end Optional last seq of a slice (0 for no bound)
*
* @return void
*/
function syslog_set_incoming_host(string $from, string $to, $max_seq, $seq_start = 0, $seq_end = 0): void {
global $syslogdb_default;

if ($seq_start > 0 && $seq_end > 0) {
syslog_db_execute_prepared("UPDATE `$syslogdb_default`.`syslog_incoming`
SET host = ?
WHERE host = ?
AND `status` = 1
AND `seq` BETWEEN ? AND ?",
[$to, $from, $seq_start, $seq_end]);
} else {
syslog_db_execute_prepared("UPDATE `$syslogdb_default`.`syslog_incoming`
SET host = ?
WHERE host = ?
AND `status` = 1
AND `seq` <= ?",
[$to, $from, $max_seq]);
}
}

/**
* Check if the hostname is in the cacti hosts table
* Some devices only send IP addresses in syslog messages, and may not be in the DNS
Expand Down Expand Up @@ -6318,10 +6407,11 @@ function syslog_update_reference_tables($max_seq) {
* @param int $seq_end Optional last seq of a slice (0 for no bound)
* @param int $budget_seconds Seconds after which DNS lookups stop for this call;
* hosts not found in Cacti are then left as received
* @param callable|null $dns_records Replacement for dns_get_record(), used by tests
*
* @return int The number of distinct hosts resolved
*/
function syslog_resolve_incoming_hosts($max_seq, $seq_start = 0, $seq_end = 0, $budget_seconds = SYSLOG_RESOLVE_BUDGET_SECONDS) {
function syslog_resolve_incoming_hosts($max_seq, $seq_start = 0, $seq_end = 0, $budget_seconds = SYSLOG_RESOLVE_BUDGET_SECONDS, ?callable $dns_records = null) {
global $syslogdb_default;

if (read_config_option('syslog_resolve_hostname') != 'on') {
Expand Down Expand Up @@ -6353,6 +6443,23 @@ function syslog_resolve_incoming_hosts($max_seq, $seq_start = 0, $seq_end = 0, $

$resolved = false;
$dns_asked = false;
$address = syslog_host_address($host['host']);

if ($address !== false) {
// An address needs no lookup. A device in Cacti may still give it a
// better name, matched as sent first and then in canonical form.
if (!syslog_check_cacti_hosts($host['host'], $max_seq, $seq_start, $seq_end)) {
if ($address !== $host['host']) {
syslog_set_incoming_host($host['host'], $address, $max_seq, $seq_start, $seq_end);
}

syslog_check_cacti_hosts($address, $max_seq, $seq_start, $seq_end);
}

$resolved_count++;

continue;
}

// Check if hostname resolves via DNS (only if DNS is enabled). Senders
// choose these names, so lookups stop once the time budget is spent.
Expand All @@ -6362,7 +6469,7 @@ function syslog_resolve_incoming_hosts($max_seq, $seq_start = 0, $seq_end = 0, $
} else {
$dns_asked = true;

if ($host['host'] != gethostbyname($host['host'])) {
if (syslog_host_has_address($host['host'], $dns_records)) {
// DNS resolved successfully
$resolved = true;
}
Expand Down
2 changes: 1 addition & 1 deletion includes/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function syslog_config_settings(): void {
],
'syslog_resolve_hostname' => [
'friendly_name' => __('Enable Hostname Resolution', 'syslog'),
'description' => __('If this checkbox is set, all hostnames are resolved via DNS lookup first (If enabled). If the DNS lookup fails, the system will attempt to resolve the hostname against the Cacti host table and replace it with the Cacti host description. If both DNS and Cacti lookups fail, records are assigned a prefix \'unresolved-Original_hostname\'.', 'syslog'),
'description' => __('If this checkbox is set, all hostnames are resolved via DNS lookup first (If enabled), using A and AAAA records. IPv4 and IPv6 addresses need no lookup, and IPv6 addresses are stored in compressed lower case form. If the DNS lookup fails, the system will attempt to resolve the hostname against the Cacti host table and replace it with the Cacti host description. If both DNS and Cacti lookups fail, records are assigned a prefix \'unresolved-Original_hostname\'.', 'syslog'),
'method' => 'checkbox',
'default' => ''
],
Expand Down
2 changes: 1 addition & 1 deletion locales/po/cacti.pot
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ msgid "Enable Hostname Resolution"
msgstr ""

#: includes/settings.php
msgid "If this checkbox is set, all hostnames are resolved via DNS lookup first (If enabled). If the DNS lookup fails, the system will attempt to resolve the hostname against the Cacti host table and replace it with the Cacti host description. If both DNS and Cacti lookups fail, records are assigned a prefix 'unresolved-Original_hostname'."
msgid "If this checkbox is set, all hostnames are resolved via DNS lookup first (If enabled), using A and AAAA records. IPv4 and IPv6 addresses need no lookup, and IPv6 addresses are stored in compressed lower case form. If the DNS lookup fails, the system will attempt to resolve the hostname against the Cacti host table and replace it with the Cacti host description. If both DNS and Cacti lookups fail, records are assigned a prefix 'unresolved-Original_hostname'."
msgstr ""

#: includes/settings.php
Expand Down
Loading