list of items
+ * @param (string) $item_type_label The plural label for the items (e.g. 'Syslog Alert Rule(s)')
+ * @param (array) $action_labels Map of action IDs to their infinitive verbs (e.g. 1 => 'Delete')
+ */
+function syslog_draw_bulk_action_confirm($drp_action, $item_list, $item_type_label, $action_labels) {
+ $action_verb = $action_labels[$drp_action] ?? '';
+
+ print "
+
+
" . __esc("Click 'Continue' to %s the following %s.", $action_verb, $item_type_label, 'syslog') . "
+
$item_list
+
+
\n";
+}
+
+function syslog_apply_selected_items_action($selected_items, $drp_action, $action_map, $export_action = '', $export_items = '') {
+ if ($selected_items != false) {
+ if (isset($action_map[$drp_action])) {
+ $action_function = $action_map[$drp_action];
+
+ if (function_exists($action_function)) {
+ foreach($selected_items as $selected_item) {
+ try {
+ $action_function($selected_item);
+ } catch (Exception $e) {
+ cacti_log("SYSLOG ERROR: Action '$action_function' failed for item '$selected_item': " . $e->getMessage(), false, 'SYSTEM');
+ }
+ }
+ } else {
+ cacti_log("SYSLOG ERROR: Bulk action function '$action_function' not found.", false, 'SYSTEM');
+ }
+ } elseif ($export_action != '' && $drp_action == $export_action) {
+ /* Re-serialize the sanitized array and URL-encode so the value is
+ * safe to embed in a JS document.location string (avoids injection
+ * via the raw request value that $export_items carries). */
+ $_SESSION['exporter'] = rawurlencode(serialize($selected_items));
+ }
+ }
+}
+
function syslog_get_import_xml_payload($redirect_url) {
+ /* Decode percent-encoding before allowlist validation so that encoded
+ characters (e.g. %2F, %3A) cannot smuggle past the regex. */
+ $redirect_url = urldecode($redirect_url);
+
+ /* Allow only relative paths that start with a filename character. Schemes
+ (http://), protocol-relative URLs (//), and backslash prefixes (\, /\)
+ all fail to match the allowlist and collapse to the safe default. */
+ if (!preg_match('/^[a-zA-Z0-9_\-][a-zA-Z0-9_\-\.\/]*(?:\?[a-zA-Z0-9_\-&=%\.+]*)?$/', $redirect_url)) {
+ $redirect_url = 'index.php';
+ }
+
+ if (strpos($redirect_url, '..') !== false) {
+ $redirect_url = 'index.php';
+ }
+
+ /* Block double-slash sequences that survive urldecode (e.g. %2F%2F -> //)
+ and could be used to construct protocol-relative URLs or confuse path
+ normalization in certain web-server configurations. */
+ if (strpos($redirect_url, '//') !== false) {
+ $redirect_url = 'index.php';
+ }
+
if (trim(get_nfilter_request_var('import_text')) != '') {
/* textbox input */
return get_nfilter_request_var('import_text');
@@ -166,6 +225,36 @@ function syslog_get_import_xml_payload($redirect_url) {
exit;
}
+ if ($_FILES['import_file']['size'] > 1048576) {
+ cacti_log('SYSLOG ERROR: Uploaded import file exceeds 1 MB limit', false, 'SYSTEM');
+ header('Location: ' . $redirect_url);
+ exit;
+ }
+
+ /* Reject non-XML uploads based on MIME sniffing of the actual bytes,
+ not the browser-supplied Content-Type which is attacker-controlled. */
+ $mime = function_exists('mime_content_type') ? mime_content_type($tmp_name) : '';
+ $mime_base = strtolower(explode(';', $mime)[0]);
+ if ($mime_base !== 'text/xml' && $mime_base !== 'application/xml') {
+ cacti_log('SYSLOG ERROR: Uploaded import file is not XML (detected: ' . $mime . ')', false, 'SYSTEM');
+ header('Location: ' . $redirect_url);
+ exit;
+ }
+
+ /* Secondary: verify XML magic bytes independently of mime_content_type so
+ that spoofed or environment-inconsistent MIME results are caught.
+ Accepts bare 0) {
- $retention = date('Y-m-d', time() - (86400 * read_config_option('syslog_retention')));
+ $retention = gmdate('Y-m-d', time() - (86400 * read_config_option('syslog_retention')));
} else {
- $retention = date('Y-m-d', time() - (30 * 86400));
+ $retention = gmdate('Y-m-d', time() - (30 * 86400));
set_config_option('syslog_retention', '30');
}
/* delete from the main syslog table first */
- syslog_db_execute("DELETE FROM `" . $syslogdb_default . "`.`syslog` WHERE logtime < '$retention'");
+ syslog_db_execute_prepared("DELETE FROM `" . $syslogdb_default . "`.`syslog` WHERE logtime < ?", array($retention));
$syslog_deleted = db_affected_rows($syslog_cnn);
/* now delete from the syslog removed table */
- syslog_db_execute("DELETE FROM `" . $syslogdb_default . "`.`syslog_removed` WHERE logtime < '$retention'");
+ syslog_db_execute_prepared("DELETE FROM `" . $syslogdb_default . "`.`syslog_removed` WHERE logtime < ?", array($retention));
$syslog_deleted += db_affected_rows($syslog_cnn);
@@ -251,29 +353,23 @@ function syslog_partition_manage() {
}
/**
- * Validate tables that support partition maintenance.
+ * syslog_partition_table_allowed - validate that the table being partitioned
+ * is in our approved list.
*
- * Any value added to the allowlist MUST match ^[a-z_]+$ so it is safe
- * for identifier interpolation in DDL statements (MySQL does not support
- * parameter binding for identifiers).
+ * @param (string) The table name
+ *
+ * @return (bool) True if allowed, False otherwise
*/
function syslog_partition_table_allowed($table) {
- if (!in_array($table, array('syslog', 'syslog_removed'), true)) {
- return false;
- }
-
- /* Defense-in-depth: reject values unsafe for identifier interpolation. */
- if (!preg_match('/^[a-z_]+$/', $table)) {
- return false;
+ if (in_array($table, array('syslog', 'syslog_removed'), true)) {
+ return (bool)preg_match('/^[a-z_]+$/', $table);
}
- return true;
+ return false;
}
/**
- * Create a new partition for the specified table.
- *
- * @return bool true on success, false on lock failure or disallowed table.
+ * This function will create a new partition for the specified table.
*/
function syslog_partition_create($table) {
global $syslogdb_default;
@@ -282,135 +378,139 @@ function syslog_partition_create($table) {
return false;
}
- /* Hash to guarantee the lock name stays within MySQL's 64-byte limit. */
- $lock_name = substr(hash('sha256', $syslogdb_default . '.syslog_partition_create.' . $table), 0, 60);
-
- /*
- * 10-second timeout is sufficient: partition maintenance runs once per
- * poller cycle (typically 5 minutes), so sustained contention is not
- * expected. A failure is logged so monitoring can detect repeated misses.
- */
- $locked = syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
+ /* determine the format of the table name */
+ $time = time();
+ $cformat = 'd' . gmdate('Ymd', $time);
+ $lnow = gmdate('Y-m-d', $time+86400);
- if ($locked === null) {
- /* NULL means the GET_LOCK call itself failed, not just contention. */
- cacti_log("SYSLOG: GET_LOCK call failed for partition create on '$table'", false, 'SYSTEM');
+ /* validate DDL interpolation values to prevent injection */
+ if (!preg_match('/^d\d{8}$/', $cformat) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $lnow)) {
+ cacti_log('SYSLOG ERROR: Invalid partition format values detected', false, 'SYSTEM');
return false;
}
- if ((int)$locked !== 1) {
- cacti_log("SYSLOG: Unable to acquire partition create lock for '$table'", false, 'SYSTEM');
- return false;
- }
+ $exists = syslog_db_fetch_row_prepared("SELECT *
+ FROM `information_schema`.`partitions`
+ WHERE table_schema = ?
+ AND partition_name = ?
+ AND table_name = ?
+ ORDER BY partition_ordinal_position",
+ array($syslogdb_default, $cformat, $table)
+ );
- try {
- /* determine the format of the table name */
- $time = time();
- $cformat = 'd' . date('Ymd', $time);
- $lnow = date('Y-m-d', $time+86400);
+ if (!cacti_sizeof($exists)) {
+ $lock_name = hash('sha256', $syslogdb_default . 'syslog_partition_create.' . $table);
- $exists = syslog_db_fetch_row_prepared("SELECT *
- FROM `information_schema`.`partitions`
- WHERE table_schema = ?
- AND partition_name = ?
- AND table_name = ?
- ORDER BY partition_ordinal_position",
- array($syslogdb_default, $cformat, $table));
+ try {
+ $locked = syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
- if (!cacti_sizeof($exists)) {
- cacti_log("SYSLOG: Creating new partition '$cformat'", false, 'SYSTEM');
+ if ((int)$locked !== 1) {
+ cacti_log("SYSLOG WARNING: Failed to acquire partition create lock for '$table'", false, 'SYSTEM');
+ return false;
+ }
- syslog_debug("Creating new partition '$cformat'");
+ $recheck = syslog_db_fetch_row_prepared("SELECT *
+ FROM `information_schema`.`partitions`
+ WHERE table_schema = ?
+ AND partition_name = ?
+ AND table_name = ?
+ ORDER BY partition_ordinal_position",
+ array($syslogdb_default, $cformat, $table)
+ );
- /*
- * MySQL does not support parameter binding for DDL identifiers
- * or partition definitions. $table is safe because it passed
- * syslog_partition_table_allowed() (two-value allowlist plus
- * regex guard). $cformat and $lnow derive from date() and
- * contain only digits, hyphens, and the letter 'd'.
- */
- syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO (
+ if (cacti_sizeof($recheck)) {
+ return true;
+ }
+
+ cacti_log("SYSLOG: Creating new partition '$cformat' for table '$table'", false, 'SYSTEM');
+
+ syslog_debug("Creating new partition '$cformat' for table '$table'");
+
+ /* MySQL does not support parameter binding for DDL statements;
+ $cformat and $lnow are validated above via regex */
+ syslog_db_execute_prepared("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO (
PARTITION $cformat VALUES LESS THAN (TO_DAYS('$lnow')),
PARTITION dMaxValue VALUES LESS THAN MAXVALUE)");
+ } finally {
+ syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
}
- } finally {
- syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
}
return true;
}
/**
- * Remove old partitions for the specified table.
+ * This function will remove all old partitions for the specified table.
*/
function syslog_partition_remove($table) {
global $syslogdb_default;
if (!syslog_partition_table_allowed($table)) {
- cacti_log("SYSLOG: partition_remove called with disallowed table '$table'", false, 'SYSTEM');
+ cacti_log("SYSLOG ERROR: Attempt to remove partitions from disallowed table '$table'", false, 'SYSTEM');
return 0;
}
- $lock_name = substr(hash('sha256', $syslogdb_default . '.syslog_partition_remove.' . $table), 0, 60);
-
- $locked = syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
-
- if ($locked === null) {
- cacti_log("SYSLOG: GET_LOCK call failed for partition remove on '$table'", false, 'SYSTEM');
- return 0;
- }
+ $syslog_deleted = 0;
+ $days = read_config_option('syslog_retention');
- if ((int)$locked !== 1) {
- cacti_log("SYSLOG: Unable to acquire partition remove lock for '$table'", false, 'SYSTEM');
- return 0;
- }
+ if ($days > 0) {
+ $lock_name = hash('sha256', $syslogdb_default . 'syslog_partition_remove.' . $table);
- $syslog_deleted = 0;
+ try {
+ $locked = syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
- try {
- $number_of_partitions = syslog_db_fetch_assoc_prepared("SELECT *
- FROM `information_schema`.`partitions`
- WHERE table_schema = ? AND table_name = ?
- ORDER BY partition_ordinal_position",
- array($syslogdb_default, $table));
+ if ((int)$locked !== 1) {
+ cacti_log("SYSLOG WARNING: Failed to acquire partition remove lock for '$table'", false, 'SYSTEM');
+ return $syslog_deleted;
+ }
- $days = read_config_option('syslog_retention');
+ $number_of_partitions = syslog_db_fetch_assoc_prepared("SELECT *
+ FROM `information_schema`.`partitions`
+ WHERE table_schema = ?
+ AND table_name = ?
+ ORDER BY partition_ordinal_position",
+ array($syslogdb_default, $table)
+ );
- syslog_debug("There are currently '" . sizeof($number_of_partitions) . "' Syslog Partitions, We will keep '$days' of them.");
+ syslog_debug("There are currently '" . sizeof($number_of_partitions) . "' Syslog Partitions for '$table', We will keep '$days' of them.");
- if ($days > 0) {
$user_partitions = sizeof($number_of_partitions) - 1;
if ($user_partitions >= $days) {
$i = 0;
- while ($user_partitions > $days) {
+ while ($user_partitions > $days && $i < cacti_sizeof($number_of_partitions)) {
$oldest = $number_of_partitions[$i];
- cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "'", false, 'SYSTEM');
+ /* PARTITION_NAME comes from information_schema, but validate the
+ format before DDL interpolation — MySQL does not support parameter
+ binding for DDL statements. */
+ if (!preg_match('/^d\d{8}$/', $oldest['PARTITION_NAME'])) {
+ cacti_log("SYSLOG ERROR: Unexpected partition name format '" . htmlspecialchars($oldest['PARTITION_NAME'], ENT_QUOTES, 'UTF-8') . "' for table '$table', skipping, cannot prune past this entry", false, 'SYSTEM');
+ /* Stop immediately: partitions are ordered by age, so an invalid
+ name means we cannot safely drop any further entries. Breaking
+ here also ensures the loop terminates even if all remaining
+ names are invalid. */
+ break;
+ }
+
+ cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "' from table '$table'", false, 'SYSTEM');
- syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "'");
+ syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "' from table '$table'");
- syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']);
+ syslog_db_execute_prepared("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']);
$i++;
$user_partitions--;
$syslog_deleted++;
}
}
+ } finally {
+ syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
}
- } finally {
- syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
}
return $syslog_deleted;
}
-/*
- * syslog_partition_check is a read-only SELECT against information_schema.
- * It does not execute DDL, so it does not need the named lock that
- * syslog_partition_create and syslog_partition_remove acquire. External
- * serialization is provided by the poller cycle calling
- * syslog_partition_manage().
- */
function syslog_partition_check($table) {
global $syslogdb_default;
@@ -425,15 +525,18 @@ function syslog_partition_check($table) {
/* find date of last partition */
$last_part = syslog_db_fetch_cell_prepared("SELECT PARTITION_NAME
FROM `information_schema`.`partitions`
- WHERE table_schema = ? AND table_name = ?
+ WHERE table_schema = ?
+ AND table_name = ?
ORDER BY partition_ordinal_position DESC
LIMIT 1,1",
- array($syslogdb_default, $table));
+ array($syslogdb_default, $table)
+ );
$lformat = str_replace('d', '', $last_part);
- $cformat = date('Ymd');
+ $cformat = gmdate('Ymd');
if ($cformat > $lformat) {
+
return true;
} else {
return false;
@@ -448,22 +551,16 @@ function syslog_check_changed($request, $session) {
}
}
-function syslog_remove_items($table, $max_seq) {
+function syslog_remove_items($table, $uniqueID) {
global $config, $syslog_cnn, $syslog_incoming_config;
global $syslogdb_default;
syslog_debug('-------------------------------------------------------------------------------------');
syslog_debug('Processing Removal Rules...');
- if ($table == 'syslog') {
- $rows = syslog_db_fetch_assoc("SELECT *
- FROM `" . $syslogdb_default . "`.`syslog_remove`
- WHERE enabled = 'on'");
- } else {
- $rows = syslog_db_fetch_assoc('SELECT *
- FROM `' . $syslogdb_default . '`.`syslog_remove`
- WHERE enabled="on"');
- }
+ $rows = syslog_db_fetch_assoc('SELECT *
+ FROM `' . $syslogdb_default . '`.`syslog_remove`
+ WHERE enabled="on"');
syslog_debug(sprintf('Found %5s - Removal Rule(s) to process', cacti_sizeof($rows)));
@@ -473,155 +570,117 @@ function syslog_remove_items($table, $max_seq) {
if ($table == 'syslog_incoming') {
$total = syslog_db_fetch_cell_prepared('SELECT count(*)
FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `status` = 1
- AND `seq` <= ?',
- array($max_seq));
+ WHERE `status` = ?', array($uniqueID));
} else {
$total = 0;
}
if (cacti_sizeof($rows)) {
foreach($rows as $remove) {
- $sql_where = '';
- $params = array();
-
- if ($remove['type'] == 'facility') {
- if ($table == 'syslog_incoming') {
- $sql_where = 'WHERE `' . $syslog_incoming_config['facilityField'] . '` = ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = $remove['message'];
- $params[] = $max_seq;
- } else {
- $facility_id = syslog_db_fetch_cell_prepared('SELECT facility_id
- FROM `' . $syslogdb_default . '`.`syslog_facilities`
- WHERE facility = ?', array($remove['message']));
-
- if (!empty($facility_id)) {
- $sql_where = 'WHERE facility_id = ?';
- $params[] = $facility_id;
- }
- }
- } else if ($remove['type'] == 'program') {
- if ($table == 'syslog_incoming') {
- $sql_where = 'WHERE `program` = ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = $remove['message'];
- $params[] = $max_seq;
- } else {
- $program_id = syslog_db_fetch_cell_prepared('SELECT program_id
- FROM `' . $syslogdb_default . '`.`syslog_programs`
- WHERE program = ?', array($remove['message']));
-
- if (!empty($program_id)) {
- $sql_where = 'WHERE program_id = ?';
- $params[] = $program_id;
- }
- }
- } elseif ($remove['type'] == 'host') {
- if ($table == 'syslog_incoming') {
- $sql_where = 'WHERE `host` = ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = $remove['message'];
- $params[] = $max_seq;
- } else {
- $host_id = syslog_db_fetch_cell_prepared('SELECT host_id
- FROM `' . $syslogdb_default . '`.`syslog_hosts`
- WHERE host = ?', array($remove['message']));
+ $sql = '';
+ $sql1 = '';
+ $params = array();
+ $params1 = array();
+
+ $column_map = array(
+ 'facility' => 'facility_id',
+ 'host' => 'host_id',
+ 'program' => 'program_id',
+ 'messageb' => $syslog_incoming_config['textField'],
+ 'messagec' => $syslog_incoming_config['textField'],
+ 'messagee' => $syslog_incoming_config['textField'],
+ );
- if (!empty($host_id)) {
- $sql_where = 'WHERE host_id = ?';
- $params[] = $host_id;
- }
+ if ($table == 'syslog_incoming') {
+ $column = $column_map[$remove['type']] ?? 'message';
+ if ($remove['type'] == 'host') {
+ $column = 'si.host';
+ } elseif ($remove['type'] == 'program') {
+ $column = 'si.program';
}
- } elseif ($remove['type'] == 'messageb') {
- if ($table == 'syslog_incoming') {
- $sql_where = 'WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = $remove['message'] . '%';
- $params[] = $max_seq;
- } else {
- $sql_where = 'WHERE message LIKE ?';
- $params[] = $remove['message'] . '%';
- }
- } elseif ($remove['type'] == 'messagec') {
- if ($table == 'syslog_incoming') {
- $sql_where = 'WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = '%' . $remove['message'] . '%';
- $params[] = $max_seq;
- } else {
- $sql_where = 'WHERE message LIKE ?';
- $params[] = '%' . $remove['message'] . '%';
- }
- } elseif ($remove['type'] == 'messagee') {
- if ($table == 'syslog_incoming') {
- $sql_where = 'WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = '%' . $remove['message'];
- $params[] = $max_seq;
- } else {
- $sql_where = 'WHERE message LIKE ?';
- $params[] = '%' . $remove['message'];
- }
- } elseif ($remove['type'] == 'sql') {
- if ($table == 'syslog_incoming') {
- $sql_where = 'WHERE (' . $remove['message'] . ')
- AND `status` = 1
- AND `seq` <= ?';
- $params[] = $max_seq;
- } else {
- $sql_where = 'WHERE (' . $remove['message'] . ')';
- }
- }
+ $filter = syslog_build_match_filter($remove['type'], $remove['message'], $column);
- if ($sql_where != '') {
if ($remove['method'] != 'del') {
- if ($table == 'syslog_incoming') {
- syslog_db_execute_prepared('INSERT INTO `' . $syslogdb_default . '`.`syslog_removed`
- (logtime, priority_id, facility_id, program_id, host_id, message)
- SELECT si.logtime, si.priority_id, si.facility_id, sp.program_id, sh.host_id, si.message
+ $sql1 = 'INSERT INTO `' . $syslogdb_default . '`.`syslog_removed`
+ (logtime, priority_id, facility_id, program_id, host_id, message)
+ SELECT logtime, priority_id, facility_id, program_id, host_id, message
+ FROM (SELECT si.logtime, si.priority_id, si.facility_id, spg.program_id, sh.host_id, si.message
FROM `' . $syslogdb_default . '`.`syslog_incoming` AS si
+ INNER JOIN `' . $syslogdb_default . '`.`syslog_facilities` AS sf
+ ON sf.facility_id = si.facility_id
+ INNER JOIN `' . $syslogdb_default . '`.`syslog_priorities` AS sp
+ ON sp.priority_id = si.priority_id
+ INNER JOIN `' . $syslogdb_default . '`.`syslog_programs` AS spg
+ ON spg.program = si.program
INNER JOIN `' . $syslogdb_default . '`.`syslog_hosts` AS sh
ON sh.host = si.host
- INNER JOIN `' . $syslogdb_default . '`.`syslog_programs` AS sp
- ON sp.program = si.program ' . $sql_where, $params);
- } else {
- syslog_db_execute_prepared('INSERT INTO `' . $syslogdb_default . '`.`syslog_removed`
+ WHERE ' . $filter['sql'] . '
+ AND `status` = ?
+ ) AS merge';
+ $params1 = array_merge($filter['params'], array($uniqueID));
+ }
+
+ $delete_column = $column_map[$remove['type']] ?? $syslog_incoming_config['textField'];
+ if ($remove['type'] == 'facility') {
+ $delete_column = $syslog_incoming_config['facilityField'];
+ } elseif ($remove['type'] == 'host') {
+ $delete_column = $syslog_incoming_config['hostField'];
+ } elseif ($remove['type'] == 'program') {
+ $delete_column = $syslog_incoming_config['programField'];
+ }
+ $delete_filter = syslog_build_match_filter($remove['type'], $remove['message'], $delete_column);
+
+ $sql = 'DELETE
+ FROM `' . $syslogdb_default . '`.`syslog_incoming`
+ WHERE ' . $delete_filter['sql'] . '
+ AND `status` = ?';
+ $params = array_merge($delete_filter['params'], array($uniqueID));
+ } else {
+ $column = $column_map[$remove['type']] ?? 'message';
+ $filter = syslog_build_match_filter($remove['type'], $remove['message'], $column);
+
+ if (!empty($filter['sql'])) {
+ if ($remove['method'] != 'del') {
+ $sql1 = 'INSERT INTO `' . $syslogdb_default . '`.`syslog_removed`
(logtime, priority_id, facility_id, program_id, host_id, message)
SELECT logtime, priority_id, facility_id, program_id, host_id, message
- FROM `' . $syslogdb_default . '`.`syslog` ' . $sql_where, $params);
+ FROM `' . $syslogdb_default . '`.`syslog`
+ WHERE ' . $filter['sql'];
+ $params1 = $filter['params'];
}
+ $sql = 'DELETE FROM `' . $syslogdb_default . '`.`syslog`
+ WHERE ' . $filter['sql'];
+ $params = $filter['params'];
+ }
+ }
+
+ if ($sql != '' || $sql1 != '') {
+ $debugm = '';
+ /* process the removal rule first */
+ if ($sql1 != '') {
+ syslog_db_execute_prepared($sql1, $params1);
$xferred += db_affected_rows($syslog_cnn);
}
- if ($table == 'syslog_incoming') {
- syslog_db_execute_prepared('DELETE FROM `' . $syslogdb_default . '`.`syslog_incoming` ' . $sql_where, $params);
+ /* now delete the remainder that match */
+ syslog_db_execute_prepared($sql, $params);
+ $affected = db_affected_rows($syslog_cnn);
+ $removed += $affected;
+
+ if ($sql1 != '') {
+ $debugm = sprintf('Moved %5s - ', $xferred);
} else {
- syslog_db_execute_prepared('DELETE FROM `' . $syslogdb_default . '`.`syslog` ' . $sql_where, $params);
+ $debugm = sprintf('Deleted %5s - ', $removed);
}
- $removed += db_affected_rows($syslog_cnn);
+ syslog_debug($debugm . 'Message' . ($affected == 1 ? '' : 's' ) .
+ " for removal rule '" . $remove['name'] . "'");
}
}
}
- syslog_debug(sprintf('Removed %5s - Record(s) from ' . $table, $removed));
- syslog_debug(sprintf('Xferred %5s - Record(s) to the syslog_removed table', $xferred));
-
return array('removed' => $removed, 'xferred' => $xferred);
}
@@ -630,58 +689,59 @@ function syslog_remove_items($table, $max_seq) {
* it supports both the legacy as well as the new approach to controlling these
* colors.
*/
+/**
+ * syslog_get_row_class - Maps a priority or severity level to a CSS class.
+ *
+ * @param (int) $level The priority (0-7) or severity (0-2) level
+ * @param (string) $type The level type ('priority' or 'severity')
+ *
+ * @return (string) The CSS class name
+ */
+function syslog_get_row_class($level, $type = 'priority') {
+ if ($type == 'severity') {
+ switch ($level) {
+ case '1': return 'logWarning';
+ case '2': return 'logAlert';
+ default: return 'logInfo';
+ }
+ }
+
+ $classes = array(
+ '0' => 'logEmergency',
+ '1' => 'logAlert',
+ '2' => 'logCritical',
+ '3' => 'logError',
+ '4' => 'logWarning',
+ '5' => 'logNotice',
+ '6' => 'logInfo',
+ '7' => 'logDebug'
+ );
+
+ return $classes[$level] ?? 'logInfo';
+}
+
+/**
+ * syslog_print_row - Prints a
with the appropriate class and title.
+ *
+ * @param (int) $level The level
+ * @param (string) $message The title/tip content
+ * @param (string) $type The level type
+ * @param (string) $extra Additional CSS classes
+ */
+function syslog_print_row($level, $message, $type = 'priority', $extra = '') {
+ $class = syslog_get_row_class($level, $type);
+ $base_classes = ($type == 'priority') ? 'selectable syslogRow syslog-detail-row' : 'selectable';
+
+ print "
";
+ return $class;
+}
+
function syslog_log_row_color($severity, $tip_title) {
- switch($severity) {
- case '':
- case '0':
- $class = 'logInfo';
- break;
- case '1':
- $class = 'logWarning';
- break;
- case '2':
- $class = 'logAlert';
- break;
- }
-
- print "
\n";
+ return syslog_print_row($severity, $tip_title, 'severity');
}
-/** function syslog_row_color()
- * This function set's the CSS for each row of the syslog table as it is displayed
- * it supports both the legacy as well as the new approach to controlling these
- * colors.
-*/
function syslog_row_color($priority, $message) {
- switch($priority) {
- case '0':
- $class = 'logEmergency';
- break;
- case '1':
- $class = 'logAlert';
- break;
- case '2':
- $class = 'logCritical';
- break;
- case '3':
- $class = 'logError';
- break;
- case '4':
- $class = 'logWarning';
- break;
- case '5':
- $class = 'logNotice';
- break;
- case '6':
- $class = 'logInfo';
- break;
- case '7':
- $class = 'logDebug';
- break;
- }
-
- print "
";
- return $class;
+ return syslog_print_row($priority, $message, 'priority');
}
function sql_hosts_where($tab) {
@@ -912,67 +972,26 @@ function syslog_manage_items($from_table, $to_table) {
$sql_sel = '';
$sql_dlt = '';
+ $params = array();
+
+ $column_map = array(
+ 'facility' => 'facility_id',
+ 'host' => 'host_id',
+ 'program' => 'program_id',
+ 'messageb' => $syslog_incoming_config['textField'],
+ 'messagec' => $syslog_incoming_config['textField'],
+ 'messagee' => $syslog_incoming_config['textField'],
+ );
- if ($remove['type'] == 'facility') {
- if ($remove['method'] != 'del') {
- $sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
- WHERE facility_id IN
- (SELECT distinct facility_id FROM `". $syslogdb_default . "`syslog_facilities
- WHERE facility ='". $remove['message']."')";
- } else {
- $sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
- WHERE facility_id IN
- (SELECT distinct facility_id FROM `". $syslogdb_default . "`syslog_facilities
- WHERE facility ='". $remove['message']."')";
- }
-
- } elseif ($remove['type'] == 'host') {
- if ($remove['method'] != 'del') {
- $sql_sel = "SELECT seq
- FROM `" . $syslogdb_default . "`. $from_table
- WHERE host_id in
- (SELECT distinct host_id FROM `". $syslogdb_default . "`syslog_hosts
- WHERE host ='". $remove['message']."')";
- } else {
- $sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
- WHERE host_id in
- (SELECT distinct host_id FROM `". $syslogdb_default . "`syslog_hosts
- WHERE host ='". $remove['message']."')";
- }
- } elseif ($remove['type'] == 'messageb') {
- if ($remove['method'] != 'del') {
- $sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
- WHERE message LIKE '" . $remove['message'] . "%' ";
- } else {
- $sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
- WHERE message LIKE '" . $remove['message'] . "%' ";
- }
+ $column = $column_map[$remove['type']] ?? 'message';
+ $filter = syslog_build_match_filter($remove['type'], $remove['message'], $column);
- } elseif ($remove['type'] == 'messagec') {
- if ($remove['method'] != 'del') {
- $sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
- WHERE message LIKE '%" . $remove['message'] . "%' ";
- } else {
- $sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
- WHERE message LIKE '%" . $remove['message'] . "%' ";
- }
- } elseif ($remove['type'] == 'messagee') {
- if ($remove['method'] != 'del') {
- $sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
- WHERE message LIKE '%" . $remove['message'] . "' ";
- } else {
- $sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
- WHERE message LIKE '%" . $remove['message'] . "' ";
- }
- } elseif ($remove['type'] == 'sql') {
- if ($remove['method'] != 'del') {
- $sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
- WHERE message (" . $remove['message'] . ") ";
- } else {
- $sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
- WHERE message (" . $remove['message'] . ") ";
- }
+ if ($remove['method'] != 'del') {
+ $sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table WHERE " . $filter['sql'];
+ } else {
+ $sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table WHERE " . $filter['sql'];
}
+ $params = $filter['params'];
if ($sql_sel != '' || $sql_dlt != '') {
$debugm = '';
@@ -980,32 +999,35 @@ function syslog_manage_items($from_table, $to_table) {
if ($sql_sel != '') {
$move_count = 0;
/* first insert, then delete */
- $move_records = syslog_db_fetch_assoc($sql_sel);
+ $move_records = syslog_db_fetch_assoc_prepared($sql_sel, $params);
syslog_debug(sprintf('Found %5s - Message(s)', cacti_sizeof($move_records)));
if (cacti_sizeof($move_records)) {
- $all_seq = '';
$messages_moved = 0;
- foreach($move_records as $move_record) {
- $all_seq = $all_seq . ", " . $move_record['seq'];
- }
-
- $all_seq = preg_replace('/^,/i', '', $all_seq);
- syslog_db_execute("INSERT INTO `". $syslogdb_default . "`.`". $to_table ."`
- (facility_id, priority_id, host_id, logtime, message)
- (SELECT facility_id, priority_id, host_id, logtime, message
- FROM `". $syslogdb_default . "`.". $from_table ."
- WHERE seq IN (" . $all_seq ."))");
-
- $messages_moved = db_affected_rows($syslog_cnn);
+ /* Discard any seq values that are not numeric before building the
+ IN list; intval() silently coerces garbage to 0, which would
+ match the wrong rows. */
+ $seq_values = array_filter(array_column($move_records, 'seq'), 'is_numeric');
+ if (empty($seq_values)) {
+ cacti_log('SYSLOG WARNING: Move set contained no valid seq values; skipping batch', false, 'SYSTEM');
+ } else {
+ $all_seq = implode(',', array_map('intval', $seq_values));
+ syslog_db_execute_prepared("INSERT INTO `". $syslogdb_default . "`.`". $to_table ."`
+ (facility_id, priority_id, host_id, logtime, message)
+ (SELECT facility_id, priority_id, host_id, logtime, message
+ FROM `". $syslogdb_default . "`.". $from_table ."
+ WHERE seq IN (" . $all_seq ."))");
+
+ $messages_moved = db_affected_rows($syslog_cnn);
+
+ if ($messages_moved > 0) {
+ syslog_db_execute_prepared("DELETE FROM `". $syslogdb_default . "`.`" . $from_table ."`
+ WHERE seq IN (" . $all_seq .")" );
+ }
- if ($messages_moved > 0) {
- syslog_db_execute("DELETE FROM `". $syslogdb_default . "`.`" . $from_table ."`
- WHERE seq IN (" . $all_seq .")" );
+ $xferred += $messages_moved;
+ $move_count = $messages_moved;
}
-
- $xferred += $messages_moved;
- $move_count = $messages_moved;
}
$debugm = sprintf('Moved %5s - Message(s)', $move_count);
@@ -1013,7 +1035,7 @@ function syslog_manage_items($from_table, $to_table) {
if ($sql_dlt != '') {
/* now delete the remainder that match */
- syslog_db_execute($sql_dlt);
+ syslog_db_execute_prepared($sql_dlt, $params);
$removed += db_affected_rows($syslog_cnn);
$debugm = sprintf('Deleted %5s Message(s)', $removed);
}
@@ -1070,93 +1092,6 @@ function syslog_array2xml($array, $tag = 'template') {
return $xml;
}
-/**
- * syslog_execute_ticket_command - run the configured ticketing command for an alert
- *
- * @param array $alert The alert row from syslog_alert table
- * @param array $hostlist Hostnames matched by the alert
- * @param string $error_message sprintf template used if exec() returns non-zero
- *
- * @return void
- */
-function syslog_execute_ticket_command($alert, $hostlist, $error_message) {
- $command = read_config_option('syslog_ticket_command');
-
- if ($command != '') {
- $command = trim($command);
- }
-
- if ($alert['open_ticket'] == 'on' && $command != '') {
- /* trim surrounding quotes so paths like "/usr/bin/cmd" resolve correctly */
- $cparts = preg_split('/\s+/', trim($command));
- $executable = trim($cparts[0], '"\'');
-
- if (cacti_sizeof($cparts) && is_executable($executable)) {
- $command = $command .
- ' --alert-name=' . cacti_escapeshellarg(clean_up_name($alert['name'])) .
- ' --severity=' . cacti_escapeshellarg($alert['severity']) .
- ' --hostlist=' . cacti_escapeshellarg(implode(',', $hostlist)) .
- ' --message=' . cacti_escapeshellarg($alert['message']);
-
- $output = array();
- $return = 0;
-
- exec($command, $output, $return);
-
- if ($return !== 0) {
- cacti_log(sprintf($error_message, $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG');
- }
- } else {
- $reason = (strpos($executable, DIRECTORY_SEPARATOR) === false)
- ? 'PATH-based lookups are not supported; use an absolute path'
- : 'file not found or not marked executable';
- cacti_log("SYSLOG ERROR: Ticket command is not executable: '$command' -- $reason", false, 'SYSTEM');
- }
- }
-}
-
-/**
- * syslog_execute_alert_command - run the per-alert shell command for a matched result
- *
- * @param array $alert The alert row from syslog_alert table
- * @param array $results The matched syslog result row
- * @param string $hostname Resolved hostname for the source device
- *
- * @return void
- */
-function syslog_execute_alert_command($alert, $results, $hostname) {
- /* alert_replace_variables() escapes each substituted token (,
- * , , , , ) with
- * cacti_escapeshellarg(). The command template itself comes from admin
- * configuration ($alert['command']) and is trusted at that boundary.
- * Do not introduce additional substitution paths that bypass this escaping. */
- $command = alert_replace_variables($alert, $results, $hostname);
-
- /* trim surrounding quotes so paths like "/usr/bin/cmd" resolve correctly */
- $cparts = preg_split('/\s+/', trim($command));
- $executable = trim($cparts[0], '"\'');
-
- $output = array();
- $return = 0;
-
- if (cacti_sizeof($cparts) && is_executable($executable)) {
- exec($command, $output, $return);
-
- if ($return !== 0 && !empty($output)) {
- cacti_log('SYSLOG NOTICE: Alert command output: ' . implode(', ', $output), true, 'SYSTEM');
- }
-
- if ($return !== 0) {
- cacti_log(sprintf('ERROR: Alert command failed. Alert:%s, Exit:%s, Output:%s', $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG');
- }
- } else {
- $reason = (strpos($executable, DIRECTORY_SEPARATOR) === false)
- ? 'PATH-based lookups are not supported; use an absolute path'
- : 'file not found or not marked executable';
- cacti_log("SYSLOG ERROR: Alert command is not executable: '$command' -- $reason", false, 'SYSTEM');
- }
-}
-
/**
* syslog_process_alerts - Process each of the Syslog Alerts
*
@@ -1173,11 +1108,11 @@ function syslog_execute_alert_command($alert, $results, $hostname) {
* and more importantly, to be able to have a separate re-alert cycles for that very same message as there can be similar messages
* happening all the time at the system level, so it's hard to target a single host for re-alert rules.
*
- * @param (int) The max_seq to process
+ * @param (int) The unique id to process
*
* @return (array) An array of the number of alerts processed and the number of alerts generated
*/
-function syslog_process_alerts($max_seq) {
+function syslog_process_alerts($uniqueID) {
global $syslogdb_default;
$syslog_alarms = 0;
@@ -1201,6 +1136,7 @@ function syslog_process_alerts($max_seq) {
if (cacti_sizeof($alerts)) {
foreach($alerts as $alert) {
$sql = '';
+ $th_sql = '';
$params = array();
/* we roll up statistics depending on the level */
@@ -1210,7 +1146,7 @@ function syslog_process_alerts($max_seq) {
$groupBy = '';
}
- $sql_data = syslog_get_alert_sql($alert, $max_seq);
+ $sql_data = syslog_get_alert_sql($alert, $uniqueID);
if (!cacti_sizeof($sql_data)) {
syslog_debug(sprintf('Error - Unable to determine SQL for Alert \'%s\'', $alert['name']));
@@ -1220,8 +1156,18 @@ function syslog_process_alerts($max_seq) {
$sql = $sql_data['sql'];
$params = $sql_data['params'];
+ /**
+ * For this next step in processing, we want to call the syslog_process_alert
+ * once for every host, or system level breach that is encountered. This removes
+ * must of the complexity that would otherwise go into the syslog_process_alert
+ * function.
+ */
if ($sql != '') {
if ($alert['level'] == '1') {
+ /**
+ * This is a host level alert process each host separately
+ * both thresholed and system levels have the same process
+ */
$th_sql = str_replace('*', 'host, COUNT(*) AS count', $sql);
$results = syslog_db_fetch_assoc_prepared($th_sql . $groupBy, $params);
@@ -1236,10 +1182,16 @@ function syslog_process_alerts($max_seq) {
}
}
} elseif ($alert['method'] == '1') {
+ /**
+ * This is a system level threshold breach
+ */
$th_sql = str_replace('*', 'COUNT(*)', $sql);
$count = syslog_db_fetch_cell_prepared($th_sql . $groupBy, $params);
$syslog_alarms += syslog_process_alert($alert, $sql, $params, $count);
} else {
+ /**
+ * This is a system level classic syslog breach without a threshold
+ */
$count = 0;
$syslog_alarms += syslog_process_alert($alert, $sql, $params, $count);
}
@@ -1250,6 +1202,112 @@ function syslog_process_alerts($max_seq) {
return array('syslog_alerts' => $syslog_alerts, 'syslog_alarms' => $syslog_alarms);
}
+function syslog_execute_ticket_command($alert, $hostlist, $context) {
+ if ($alert['open_ticket'] == 'on') {
+ $command = trim(read_config_option('syslog_ticket_command'));
+
+ if ($command != '') {
+ /*
+ * Extract the executable portion from the configured command.
+ * This allows for quoted paths and additional arguments in the
+ * configuration while still validating the executable itself.
+ */
+ $executable = $command;
+ $firstChar = substr($executable, 0, 1);
+
+ if ($firstChar === '"' || $firstChar === "'") {
+ $quoteChar = $firstChar;
+ $closing = strpos($executable, $quoteChar, 1);
+
+ if ($closing !== false) {
+ $executable = substr($executable, 1, $closing - 1);
+ } else {
+ // Unbalanced quotes; fall back to trimming quotes/whitespace.
+ $executable = trim($executable, " \t\n\r\0\x0B\"'");
+ }
+ } else {
+ $parts = preg_split('/\s+/', $executable);
+ if (is_array($parts) && isset($parts[0])) {
+ $executable = $parts[0];
+ }
+ $executable = trim($executable, " \t\n\r\0\x0B\"'");
+ }
+
+ if ($executable !== '' && is_executable($executable)) {
+ $command = $command .
+ ' --alert-name=' . cacti_escapeshellarg(clean_up_name($alert['name'])) .
+ ' --severity=' . cacti_escapeshellarg($alert['severity']) .
+ ' --hostlist=' . cacti_escapeshellarg(implode(',',$hostlist)) .
+ ' --message=' . cacti_escapeshellarg($alert['message']);
+
+ $output = array();
+ $return = 0;
+
+ exec($command, $output, $return);
+
+ if ($return != 0) {
+ cacti_log(sprintf('ERROR: %s Failed. Alert:%s, Exit:%s, Output:%s', $context, $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG');
+ }
+ } else {
+ if (strpos($executable, DIRECTORY_SEPARATOR) === false) {
+ cacti_log(sprintf('SYSLOG ERROR: %s path \'%s\' is missing absolute path separator', $context, $executable), false, 'SYSTEM');
+ } else {
+ cacti_log(sprintf('SYSLOG ERROR: %s path \'%s\' is not executable', $context, $executable), false, 'SYSTEM');
+ }
+ }
+ }
+ }
+}
+
+function syslog_execute_alert_command($alert, $results, $hostname) {
+ $command = trim($alert['command']);
+
+ if ($command != '') {
+ $command = alert_replace_variables($alert, $results, $hostname);
+
+ /*
+ * Extract the executable portion from the command string.
+ * This allows for quoted paths and additional arguments.
+ */
+ $executable = $command;
+ $firstChar = substr($executable, 0, 1);
+
+ if ($firstChar === '"' || $firstChar === "'") {
+ $quoteChar = $firstChar;
+ $closing = strpos($executable, $quoteChar, 1);
+
+ if ($closing !== false) {
+ $executable = substr($executable, 1, $closing - 1);
+ } else {
+ // Unbalanced quotes; fall back to trimming quotes/whitespace.
+ $executable = trim($executable, " \t\n\r\0\x0B\"'");
+ }
+ } else {
+ $parts = preg_split('/\s+/', $executable);
+ if (is_array($parts) && isset($parts[0])) {
+ $executable = $parts[0];
+ }
+ $executable = trim($executable, " \t\n\r\0\x0B\"'");
+ }
+
+ if ($executable !== '' && is_executable($executable)) {
+ $output = array();
+ $returnCode = 0;
+
+ exec($command, $output, $returnCode);
+
+ $logMessage = "SYSLOG NOTICE: Executing '$command' Command return code: $returnCode";
+ cacti_log($logMessage, true, 'SYSTEM');
+ } else {
+ if (strpos($executable, DIRECTORY_SEPARATOR) === false) {
+ cacti_log(sprintf('SYSLOG ERROR: Alert Command path \'%s\' is missing absolute path separator', $executable), false, 'SYSTEM');
+ } else {
+ cacti_log(sprintf('SYSLOG ERROR: Alert Command path \'%s\' is not executable', $executable), false, 'SYSTEM');
+ }
+ }
+ }
+}
+
/**
* syslog_process_alert - Process the Alert and generate notifications, execute commands, etc.
*
@@ -1553,9 +1611,9 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') {
/**
* Open a ticket if this options have been selected.
*/
- syslog_execute_ticket_command($alert, $hostlist, 'ERROR: Ticket Command Failed. Alert:%s, Exit:%s, Output:%s');
+ syslog_execute_ticket_command($alert, $hostlist, 'Ticket Command');
- if (trim($alert['command']) != '' && !$found) {
+ if (!$found) {
syslog_execute_alert_command($alert, $results, $hostname);
}
@@ -1574,9 +1632,12 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') {
alert_setup_environment($alert, $results, $hostlist, $hostname);
- syslog_execute_ticket_command($alert, $hostlist, 'ERROR: Command Failed. Alert:%s, Exit:%s, Output:%s');
+ /**
+ * Open a ticket if this options have been selected.
+ */
+ syslog_execute_ticket_command($alert, $hostlist, 'Command');
- if (trim($alert['command']) != '' && !$found) {
+ if (!$found) {
syslog_execute_alert_command($alert, $results, $hostname);
}
}
@@ -1597,7 +1658,70 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') {
*
* @return (array) The SQL and the prepared array for the SQL
*/
-function syslog_get_alert_sql(&$alert, $max_seq) {
+/**
+ * syslog_build_match_filter - Builds a SQL WHERE clause and parameters for common match types.
+ *
+ * @param (string) $type The match type (facility, host, program, messageb, messagec, messagee, sql)
+ * @param (string) $value The search value
+ * @param (string) $column The column name to search against (default depends on type)
+ *
+ * @return (array) Array with 'sql' and 'params' keys
+ */
+function syslog_build_match_filter($type, $value, $column = '') {
+ global $syslogdb_default;
+ $sql = '';
+ $params = array();
+
+ switch ($type) {
+ case 'facility':
+ if ($column == 'facility_id') {
+ $sql = 'facility_id IN (SELECT DISTINCT facility_id FROM `' . $syslogdb_default . '`.`syslog_facilities` WHERE facility = ?)';
+ } else {
+ $sql = ($column ?: 'facility') . ' = ?';
+ }
+ $params[] = $value;
+ break;
+ case 'host':
+ if ($column == 'host_id') {
+ $sql = 'host_id IN (SELECT DISTINCT host_id FROM `' . $syslogdb_default . '`.`syslog_hosts` WHERE host = ?)';
+ } else {
+ $sql = ($column ?: 'host') . ' = ?';
+ }
+ $params[] = $value;
+ break;
+ case 'program':
+ if ($column == 'program_id') {
+ $sql = 'program_id IN (SELECT DISTINCT program_id FROM `' . $syslogdb_default . '`.`syslog_programs` WHERE program = ?)';
+ } else {
+ $sql = ($column ?: 'program') . ' = ?';
+ }
+ $params[] = $value;
+ break;
+ case 'messageb':
+ $sql = ($column ?: 'message') . ' LIKE ?';
+ $params[] = $value . '%';
+ break;
+ case 'messagec':
+ $sql = ($column ?: 'message') . ' LIKE ?';
+ $params[] = '%' . $value . '%';
+ break;
+ case 'messagee':
+ $sql = ($column ?: 'message') . ' LIKE ?';
+ $params[] = '%' . $value;
+ break;
+ case 'sql':
+ /* The 'sql' match type passes admin-configured expressions directly into
+ * the WHERE clause. This is an intentional trust boundary: only Cacti
+ * administrators with console access can configure removal/alert rules.
+ * No programmatic sanitization can safely parse arbitrary SQL fragments. */
+ $sql = '(' . $value . ')';
+ break;
+ }
+
+ return array('sql' => $sql, 'params' => $params);
+}
+
+function syslog_get_alert_sql(&$alert, $uniqueID) {
global $syslogdb_default, $syslog_incoming_config;
if (defined('SYSLOG_CONFIG')) {
@@ -1608,79 +1732,32 @@ function syslog_get_alert_sql(&$alert, $max_seq) {
$syslog_incoming_config['programField'] = 'program';
}
- $params = array();
- $sql = '';
+ $field_map = array(
+ 'facility' => $syslog_incoming_config['facilityField'],
+ 'host' => $syslog_incoming_config['hostField'],
+ 'program' => $syslog_incoming_config['programField'],
+ 'messageb' => $syslog_incoming_config['textField'],
+ 'messagec' => $syslog_incoming_config['textField'],
+ 'messagee' => $syslog_incoming_config['textField'],
+ );
- if ($alert['type'] == 'facility') {
- $sql = 'SELECT *
- FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `' . $syslog_incoming_config['facilityField'] . '` = ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = $alert['message'];
- $params[] = $max_seq;
- } elseif ($alert['type'] == 'messageb') {
- $sql = 'SELECT *
- FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = $alert['message'] . '%';
- $params[] = $max_seq;
- } elseif ($alert['type'] == 'messagec') {
- $sql = 'SELECT *
- FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = '%' . $alert['message'] . '%';
- $params[] = $max_seq;
- } elseif ($alert['type'] == 'messagee') {
- $sql = 'SELECT *
- FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = '%' . $alert['message'];
- $params[] = $max_seq;
- } elseif ($alert['type'] == 'host') {
- $sql = 'SELECT *
- FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `' . $syslog_incoming_config['hostField'] . '` = ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = $alert['message'];
- $params[] = $max_seq;
- } elseif ($alert['type'] == 'program') {
- $sql = 'SELECT *
- FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `' . $syslog_incoming_config['programField'] . '` = ?
- AND `status` = 1
- AND `seq` <= ?';
-
- $params[] = $alert['message'];
- $params[] = $max_seq;
- } elseif ($alert['type'] == 'sql') {
- $sql = 'SELECT *
- FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE (' . $alert['message'] . ')
- AND `status` = 1
- AND `seq` <= ?';
+ $column = $field_map[$alert['type']] ?? '';
+ $filter = syslog_build_match_filter($alert['type'], $alert['message'], $column);
- $params[] = $max_seq;
- }
+ $sql = 'SELECT *
+ FROM `' . $syslogdb_default . '`.`syslog_incoming`
+ WHERE ' . $filter['sql'] . '
+ AND `status` = ?';
+
+ $params = $filter['params'];
+ $params[] = $uniqueID;
return array('sql' => $sql, 'params' => $params);
}
/**
- * syslog_preprocess_incoming_records - Generate a max_seq to allow moving of
- * records to done table and mark incoming records with the max_seq and
+ * syslog_preprocess_incoming_records - Generate a uniqueID to allow moving of
+ * records to done table and mark incoming records with the uniqueID and
* then if syslog is configured to strip domains, perform that first.
*
* @return (int) Unique id to allow syslog messages that come in randomly to
@@ -1688,49 +1765,78 @@ function syslog_get_alert_sql(&$alert, $max_seq) {
* to be left till then ext polling cycle.
*/
function syslog_preprocess_incoming_records() {
- global $syslogdb_default;
+ global $syslogdb_default, $syslog_cnn;
- $max_seq = syslog_db_fetch_cell('SELECT MAX(seq) FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE status = 0');
+ $lock_name = hash('sha256', $syslogdb_default . '.preprocess_incoming');
+ $locked = syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
- if ($max_seq > 0) {
- /* flag all records with the status = 1 prior to moving */
- syslog_db_execute_prepared('UPDATE `' . $syslogdb_default . '`.`syslog_incoming`
- SET `status` = 1
- WHERE `status` = 0
- AND `seq` <= ?',
- array($max_seq));
+ if ((int)$locked !== 1) {
+ cacti_log('SYSLOG ERROR: Unable to acquire preprocess lock', false, 'SYSTEM');
+ return array('uniqueID' => 0, 'incoming' => 0);
+ }
- syslog_debug('Max Sequence ID = ' . $max_seq);
- syslog_debug('-------------------------------------------------------------------------------------');
+ $uniqueID = 0;
+ $incoming = 0;
- $syslog_incoming = syslog_db_fetch_cell_prepared('SELECT COUNT(seq)
- FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `status` = 1
- AND `seq` <= ?',
- array($max_seq));
+ /* uniqueID is constrained to tinyint range (1-127) by the status column.
+ * Collision probability rises with concurrent pollers; the retry loop
+ * (up to 256 attempts) mitigates but does not eliminate this risk.
+ * A future schema change to widen status to int would allow a larger space. */
+ try {
+ $attempts = 0;
+ while (1) {
+ $uniqueID = rand(1, 127);
+
+ $count = syslog_db_fetch_cell_prepared('SELECT COUNT(*)
+ FROM `' . $syslogdb_default . '`.`syslog_incoming`
+ WHERE `status` = ?',
+ array($uniqueID));
+
+ if ($count == 0) {
+ break;
+ }
- syslog_debug(sprintf('Found %5s - New Message(s) to process', $syslog_incoming));
+ $attempts++;
- /* strip domains if we have requested to do so */
- syslog_strip_incoming_domains($max_seq);
+ if ($attempts >= 256) {
+ cacti_log('SYSLOG ERROR: Unable to find unused uniqueID after 256 attempts', false, 'SYSTEM');
+ return array('uniqueID' => 0, 'incoming' => 0);
+ }
+ }
- api_plugin_hook('plugin_syslog_before_processing');
+ /* flag all records with the uniqueID prior to moving */
+ syslog_db_execute_prepared('UPDATE `' . $syslogdb_default . '`.`syslog_incoming`
+ SET `status` = ?
+ WHERE `status` = 0',
+ array($uniqueID));
- return array('max_seq' => $max_seq, 'incoming' => $syslog_incoming);
+ $incoming = db_affected_rows($syslog_cnn);
+ } finally {
+ syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
}
- return array('max_seq' => 0, 'incoming' => 0);
+ syslog_debug('Unique ID = ' . $uniqueID);
+ syslog_debug('-------------------------------------------------------------------------------------');
+
+ syslog_debug(sprintf('Found %5s - New Message(s) to process', $incoming));
+
+ /* strip domains if we have requested to do so */
+ syslog_strip_incoming_domains($uniqueID);
+
+ api_plugin_hook('plugin_syslog_before_processing');
+
+ return array('uniqueID' => $uniqueID, 'incoming' => $incoming);
}
/**
* syslog_strip_incoming_domains - If syslog is setup to strip DNS domain name suffixes do that
* prior to processing the records.
*
- * @param (string) The max_seq records to process
+ * @param (string) The uniqueID records to process
*
* @return (void)
*/
-function syslog_strip_incoming_domains($max_seq) {
+function syslog_strip_incoming_domains($uniqueID) {
global $syslogdb_default;
$syslog_domains = read_config_option('syslog_domains');
@@ -1739,28 +1845,29 @@ function syslog_strip_incoming_domains($max_seq) {
$domains = explode(',', trim($syslog_domains));
foreach($domains as $domain) {
- syslog_db_execute_prepared('UPDATE `' . $syslogdb_default . '`.`syslog_incoming`
- SET host = SUBSTRING_INDEX(host, \'.\', 1)
+ syslog_db_execute_prepared('UPDATE `' . $syslogdb_default . "`.`syslog_incoming`
+ SET host = SUBSTRING_INDEX(host, '.', 1)
WHERE host LIKE ?
- AND `status` = 1
- AND `seq` <= ?',
- array('%' . $domain, $max_seq));
+ AND `status` = ?",
+ array('%' . $domain, $uniqueID));
}
}
}
+
+
/**
* 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
* however they may be in the cacti hosts table as monitored devices.
*
* @param (string) The hostname to check
- * @param (int) The max_seq for syslog_incoming messages to process
+ * @param (int) The unique id for syslog_incoming messages to process
*
* @return (bool) True if the host exists in the Cacti database, false otherwise
*/
-function syslog_check_cacti_hosts($host, $max_seq) {
+function syslog_check_cacti_hosts($host, $uniqueID) {
global $syslogdb_default;
if (empty($host)) {
@@ -1778,9 +1885,8 @@ function syslog_check_cacti_hosts($host, $max_seq) {
syslog_db_execute_prepared('UPDATE `' . $syslogdb_default . '`.`syslog_incoming`
SET host = ?
WHERE host = ?
- AND `status` = 1
- AND `seq` <= ?',
- array($cacti_host['description'], $host, $max_seq));
+ AND `status` = ?',
+ array($cacti_host['description'], $host, $uniqueID));
return true;
}
@@ -1796,11 +1902,11 @@ function syslog_check_cacti_hosts($host, $max_seq) {
* and assign an id to each of them. This way the syslog table can be optimized
* for size as much as possible.
*
- * @param (int) The max_seq for syslog_incoming messages to process
+ * @param (int) The unique id for syslog_incoming messages to process
*
* @return (void)
*/
-function syslog_update_reference_tables($max_seq) {
+function syslog_update_reference_tables($uniqueID) {
global $syslogdb_default;
syslog_debug('-------------------------------------------------------------------------------------');
@@ -1810,9 +1916,8 @@ function syslog_update_reference_tables($max_seq) {
if (read_config_option('syslog_resolve_hostname') == 'on') {
$hosts = syslog_db_fetch_assoc_prepared('SELECT DISTINCT host
FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `status` = 1
- AND `seq` <= ?',
- array($max_seq));
+ WHERE `status` = ?',
+ array($uniqueID));
foreach($hosts as $host) {
if (!isset($host['host']) || empty($host['host'])) {
@@ -1831,7 +1936,7 @@ function syslog_update_reference_tables($max_seq) {
// Check if hostname exists in Cacti hosts table (only if not already resolved via DNS)
if (!$resolved) {
- $resolved = syslog_check_cacti_hosts($host['host'], $max_seq);
+ $resolved = syslog_check_cacti_hosts($host['host'], $uniqueID);
}
// If not resolved via DNS or found in Cacti, prefix the hostname
@@ -1841,9 +1946,8 @@ function syslog_update_reference_tables($max_seq) {
syslog_db_execute_prepared('UPDATE `' . $syslogdb_default . "`.`syslog_incoming`
SET host = ?
WHERE host = ?
- AND `status` = 1
- AND `seq` <= ?",
- array($unresolved_host, $host['host'], $max_seq));
+ AND `status` = ?",
+ array($unresolved_host, $host['host'], $uniqueID));
}
}
}
@@ -1852,23 +1956,21 @@ function syslog_update_reference_tables($max_seq) {
(program, last_updated)
SELECT DISTINCT program, NOW()
FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `status` = 1
- AND `seq` <= ?
+ WHERE `status` = ?
ON DUPLICATE KEY UPDATE
program=VALUES(program),
last_updated=VALUES(last_updated)',
- array($max_seq));
+ array($uniqueID));
syslog_db_execute_prepared('INSERT INTO `' . $syslogdb_default . '`.`syslog_hosts`
(host, last_updated)
SELECT DISTINCT host, NOW() AS last_updated
FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `status` = 1
- AND `seq` <= ?
+ WHERE `status` = ?
ON DUPLICATE KEY UPDATE
host=VALUES(host),
last_updated=NOW()',
- array($max_seq));
+ array($uniqueID));
syslog_db_execute_prepared('INSERT INTO `' . $syslogdb_default . '`.`syslog_host_facilities`
(host_id, facility_id)
@@ -1877,8 +1979,7 @@ function syslog_update_reference_tables($max_seq) {
(
SELECT DISTINCT host, facility_id
FROM `' . $syslogdb_default . "`.`syslog_incoming`
- WHERE `status` = 1
- AND `seq` <= ?
+ WHERE `status` = ?
) AS s
INNER JOIN `" . $syslogdb_default . '`.`syslog_hosts` AS sh
ON s.host = sh.host
@@ -1886,18 +1987,18 @@ function syslog_update_reference_tables($max_seq) {
ON DUPLICATE KEY UPDATE
host_id=VALUES(host_id),
last_updated=NOW()',
- array($max_seq));
+ array($uniqueID));
}
/**
* syslog_update_statistics - Insert new statistics rows into the syslog statistics
* table for post review
*
- * @param (int) The max_seq for all syslog incoming records to be processed
+ * @param (int) The unique id for all syslog incoming records to be processed
*
* @return (void)
*/
-function syslog_update_statistics($max_seq) {
+function syslog_update_statistics($uniqueID) {
global $syslogdb_default, $syslog_cnn;
if (read_config_option('syslog_statistics') == 'on') {
@@ -1910,11 +2011,10 @@ function syslog_update_statistics($max_seq) {
ON sh.host=si.host
INNER JOIN syslog_programs AS sp
ON sp.program=si.program
- WHERE si.`status` = 1
- AND si.`seq` <= ?
+ WHERE `status` = ?
GROUP BY host_id, priority_id, facility_id, program_id) AS merge
GROUP BY host_id, priority_id, facility_id, program_id',
- array($max_seq));
+ array($uniqueID));
$stats = db_affected_rows($syslog_cnn);
@@ -1929,11 +2029,11 @@ function syslog_update_statistics($max_seq) {
* the syslog table, and then after which we can perform various
* removal rules against them.
*
- * @param (int) The max_seq for rows in the syslog table
+ * @param (int) The unique id for rows in the syslog table
*
* @return (int) The number of rows moved to the syslog table
*/
-function syslog_incoming_to_syslog($max_seq) {
+function syslog_incoming_to_syslog($uniqueID) {
global $syslogdb_default, $syslog_cnn;
syslog_db_execute_prepared('INSERT INTO `' . $syslogdb_default . '`.`syslog`
@@ -1946,10 +2046,9 @@ function syslog_incoming_to_syslog($max_seq) {
ON sh.host = si.host
INNER JOIN syslog_programs AS sp
ON sp.program = si.program
- WHERE si.`status` = 1
- AND si.`seq` <= ?
+ WHERE `status` = ?
) AS merge',
- array($max_seq));
+ array($uniqueID));
$moved = db_affected_rows($syslog_cnn);
@@ -1958,14 +2057,11 @@ function syslog_incoming_to_syslog($max_seq) {
syslog_debug(sprintf('Moved %5s - Message(s) to the syslog table', $moved));
- syslog_db_execute_prepared('DELETE FROM `' . $syslogdb_default . '`.`syslog_incoming`
- WHERE `status` = 1
- AND `seq` <= ?',
- array($max_seq));
+ syslog_db_execute_prepared('DELETE FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE status = ?', array($uniqueID));
syslog_debug(sprintf('Deleted %5s - Already Processed Message(s) from incoming', db_affected_rows($syslog_cnn)));
- syslog_db_execute('DELETE FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE logtime < DATE_SUB(NOW(), INTERVAL 1 HOUR)');
+ syslog_db_execute_prepared('DELETE FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE logtime < DATE_SUB(NOW(), INTERVAL 1 HOUR)');
$stale = db_affected_rows($syslog_cnn);
@@ -1999,7 +2095,7 @@ function syslog_postprocess_tables() {
syslog_debug(sprintf('Deleted %5s - Syslog Statistics Record(s)', db_affected_rows($syslog_cnn)));
}
} else {
- syslog_db_execute('TRUNCATE `' . $syslogdb_default . '`.`syslog_statistics`');
+ syslog_db_execute_prepared('TRUNCATE `' . $syslogdb_default . '`.`syslog_statistics`');
}
/* remove alert log messages */
@@ -2035,14 +2131,14 @@ function syslog_postprocess_tables() {
if (date('G') == 0 && date('i') < 5) {
syslog_debug('Optimizing Tables');
if (!syslog_is_partitioned()) {
- syslog_db_execute('OPTIMIZE TABLE
+ syslog_db_execute_prepared('OPTIMIZE TABLE
`' . $syslogdb_default . '`.`syslog_incoming`,
`' . $syslogdb_default . '`.`syslog`,
`' . $syslogdb_default . '`.`syslog_remove`,
`' . $syslogdb_default . '`.`syslog_removed`,
`' . $syslogdb_default . '`.`syslog_alert`');
} else {
- syslog_db_execute('OPTIMIZE TABLE
+ syslog_db_execute_prepared('OPTIMIZE TABLE
`' . $syslogdb_default . '`.`syslog_incoming`,
`' . $syslogdb_default . '`.`syslog_remove`,
`' . $syslogdb_default . '`.`syslog_alert`');
@@ -2077,6 +2173,18 @@ function syslog_process_reports() {
$html = false;
}
+ $from_email = read_config_option('settings_from_email');
+ if ($from_email == '') {
+ $from_email = 'Cacti@cacti.net';
+ }
+
+ $from_name = read_config_option('settings_from_name');
+ if ($from_name == '') {
+ $from_name = 'Cacti Reporting';
+ }
+
+ $from = array($from_email, $from_name);
+
/* Lets run the reports */
$reports = syslog_db_fetch_assoc('SELECT *
FROM `' . $syslogdb_default . "`.`syslog_reports`
@@ -2130,16 +2238,18 @@ function syslog_process_reports() {
$reptext = '';
- $sql = syslog_get_report_sql($report);
+ $report_sql = syslog_get_report_sql($report);
- if ($sql != '') {
+ if ($report_sql['sql'] != '') {
$date2 = date('Y-m-d H:i:s', $current_time);
$date1 = date('Y-m-d H:i:s', $current_time - $time_span);
- $sql .= " AND logtime BETWEEN ". db_qstr($date1) . " AND " . db_qstr($date2);
- $sql .= ' ORDER BY logtime DESC';
- $items = syslog_db_fetch_assoc($sql);
+
+ $sql = $report_sql['sql'] . ' AND logtime BETWEEN ? AND ? ORDER BY logtime DESC';
+ $params = array_merge($report_sql['params'], array($date1, $date2));
+
+ $items = syslog_db_fetch_assoc_prepared($sql, $params);
- syslog_debug('We have ' . db_affected_rows($syslog_cnn) . ' items for the Report');
+ syslog_debug('We have ' . cacti_sizeof($items) . ' items for the Report');
$classes = array('even', 'odd');
@@ -2215,66 +2325,60 @@ function syslog_process_reports() {
*
* @param (array) The report to process
*
- * @return (string) The unprepared SQL
+ * @return (array) The SQL and the prepared array for the SQL
*/
function syslog_get_report_sql(&$report) {
global $syslogdb_default;
- if ($report['type'] == 'messageb') {
- $sql = 'SELECT sl.*, sh.host
- FROM `' . $syslogdb_default . '`.`syslog` AS sl
- INNER JOIN `' . $syslogdb_default . '`.`syslog_hosts` AS sh
- ON sl.host_id = sh.host_id
- WHERE message LIKE ' . db_qstr($report['message'] . '%');
- }
-
- if ($report['type'] == 'messagec') {
- $sql = 'SELECT sl.*, sh.host
- FROM `' . $syslogdb_default . '`.`syslog` AS sl
- INNER JOIN `' . $syslogdb_default . '`.`syslog_hosts` AS sh
- ON sl.host_id = sh.host_id
- WHERE message LIKE ' . db_qstr('%' . $report['message'] . '%');
- }
-
- if ($report['type'] == 'messagee') {
- $sql = 'SELECT sl.*, sh.host
- FROM `' . $syslogdb_default . '`.`syslog` AS sl
- INNER JOIN `' . $syslogdb_default . '`.`syslog_hosts` AS sh
- ON sl.host_id = sh.host_id
- WHERE message LIKE ' . db_qstr('%' . $report['message']);
- }
-
- if ($report['type'] == 'host') {
- $sql = 'SELECT sl.*, sh.host
- FROM `' . $syslogdb_default . '`.`syslog` AS sl
- INNER JOIN `' . $syslogdb_default . '`.`syslog_hosts` AS sh
- ON sl.host_id = sh.host_id
- WHERE sh.host = ' . db_qstr($report['message']);
- }
-
- if ($report['type'] == 'facility') {
- $sql = 'SELECT sl.*, sf.facility
- FROM `' . $syslogdb_default . '`.`syslog` AS sl
- INNER JOIN `' . $syslogdb_default . '`.`syslog_facilities` AS sf
- ON sl.facility_id = sf.facility_id
- WHERE sf.facility = ' . db_qstr($report['message']);
- }
+ $sql = '';
+ $params = array();
- if ($report['type'] == 'program') {
- $sql = 'SELECT sl.*, sp.program
- FROM `' . $syslogdb_default . '`.`syslog` AS sl
- INNER JOIN `' . $syslogdb_default . '`.`syslog_programs` AS sp
- ON sl.program_id = sp.program_id
- WHERE sp.program = ' . db_qstr($report['message']);
- }
+ $column_map = array(
+ 'facility' => 'sf.facility',
+ 'host' => 'sh.host',
+ 'program' => 'sp.program',
+ 'messageb' => 'message',
+ 'messagec' => 'message',
+ 'messagee' => 'message',
+ );
- if ($report['type'] == 'sql') {
- $sql = 'SELECT *
- FROM `' . $syslogdb_default . '`.`syslog`
- WHERE (' . $report['message'] . ')';
+ $column = $column_map[$report['type']] ?? '';
+ $filter = syslog_build_match_filter($report['type'], $report['message'], $column);
+
+ if (!empty($filter['sql'])) {
+ if ($report['type'] == 'host') {
+ $sql = 'SELECT sl.*, sh.host
+ FROM `' . $syslogdb_default . '`.`syslog` AS sl
+ INNER JOIN `' . $syslogdb_default . '`.`syslog_hosts` AS sh
+ ON sl.host_id = sh.host_id
+ WHERE ' . $filter['sql'];
+ } elseif ($report['type'] == 'facility') {
+ $sql = 'SELECT sl.*, sf.facility
+ FROM `' . $syslogdb_default . '`.`syslog` AS sl
+ INNER JOIN `' . $syslogdb_default . '`.`syslog_facilities` AS sf
+ ON sl.facility_id = sf.facility_id
+ WHERE ' . $filter['sql'];
+ } elseif ($report['type'] == 'program') {
+ $sql = 'SELECT sl.*, sp.program
+ FROM `' . $syslogdb_default . '`.`syslog` AS sl
+ INNER JOIN `' . $syslogdb_default . '`.`syslog_programs` AS sp
+ ON sl.program_id = sp.program_id
+ WHERE ' . $filter['sql'];
+ } elseif ($report['type'] == 'sql') {
+ $sql = 'SELECT *
+ FROM `' . $syslogdb_default . '`.`syslog`
+ WHERE ' . $filter['sql'];
+ } else {
+ $sql = 'SELECT sl.*, sh.host
+ FROM `' . $syslogdb_default . '`.`syslog` AS sl
+ INNER JOIN `' . $syslogdb_default . '`.`syslog_hosts` AS sh
+ ON sl.host_id = sh.host_id
+ WHERE ' . $filter['sql'];
+ }
+ $params = $filter['params'];
}
- return $sql;
+ return array('sql' => $sql, 'params' => $params);
}
/**
diff --git a/js/functions.js b/js/functions.js
index a96d47d..fe3a219 100644
--- a/js/functions.js
+++ b/js/functions.js
@@ -11,7 +11,7 @@
* Clear filter for statistics view
*/
function clearFilterStats() {
- strURL = 'syslog.php?tab=stats&clear=1&header=false';
+ var strURL = 'syslog.php?tab=stats&clear=1&header=false';
loadPageNoHeader(strURL);
}
@@ -196,7 +196,7 @@ function initSyslogMain(config) {
$(this).multiselect('widget').find(':checkbox:first').each(function() {
$(this).prop('checked', true);
});
- $('#test').trigger('keyup');
+ $('#term').trigger('keyup');
},
checkAll: function() {
$(this).multiselect('widget').find(':checkbox').not(':first').each(function() {
@@ -220,7 +220,11 @@ function initSyslogMain(config) {
$.each(data, function(index, hostData) {
if ($('#host option[value="'+index+'"]').length == 0) {
- $('#host').append('');
+ var option = $('