diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml index 76612ac..fdbd61d 100644 --- a/.github/workflows/plugin-ci-workflow.yml +++ b/.github/workflows/plugin-ci-workflow.yml @@ -32,7 +32,65 @@ on: - develop jobs: + quality-checks: + name: PHP Quality (Lint, Stan, CS) + runs-on: ubuntu-latest + steps: + - name: Checkout Cacti + uses: actions/checkout@v4 + with: + repository: Cacti/cacti + path: cacti + + - name: Checkout Syslog Plugin + uses: actions/checkout@v4 + with: + path: cacti/plugins/syslog + + - name: Setup PHP 8.3 + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring + tools: php-cs-fixer, phpstan + + - name: Check PHP Syntax (Lint) + run: | + cd cacti/plugins/syslog + if find . -name '*.php' -not -path './vendor/*' -exec php -l {} 2>&1 \; | grep -iv 'no syntax errors detected'; then + print "Syntax errors found!" + exit 1 + fi + + - name: Run PHP CS Fixer (Dry Run) + run: | + cd cacti/plugins/syslog + php-cs-fixer fix --dry-run --diff --ansi --config=../../../.php-cs-fixer.php . || true + + - name: Create PHPStan config + run: | + cd cacti/plugins/syslog + cat > phpstan.neon << 'EOF' + parameters: + level: 5 + paths: + - . + excludePaths: + - vendor/ + - locales/ + ignoreErrors: + - '#has invalid return type the\.#' + bootstrapFiles: + - ../../include/global.php + EOF + + - name: Run PHPStan Analysis + run: | + cd cacti/plugins/syslog + phpstan analyse --no-progress --error-format=github || true + integration-test: + needs: quality-checks runs-on: ${{ matrix.os }} strategy: @@ -104,7 +162,6 @@ jobs: - name: Create MySQL Config run: | echo -e "[client]\nuser = root\npassword = cactiroot\nhost = 127.0.0.1\n" > ~/.my.cnf - cat ~/.my.cnf - name: Initialize Cacti Database env: @@ -148,7 +205,7 @@ jobs: sed -i "s/'cacti'/'cacti'/g" ${{ github.workspace }}/cacti/plugins/syslog/config.php sed -i "s/'cactiuser'/'cactiuser'/g" ${{ github.workspace }}/cacti/plugins/syslog/config.php sed -i 's/\/\/\$/\$/g' ${{ github.workspace }}/cacti/plugins/syslog/config.php - sudo chmod 664 ${{ github.workspace }}/cacti/plugins/syslog/config.php + sudo chmod 664 ${{ github.workspace }}/cacti/include/config.php - name: Configure Apache run: | @@ -179,14 +236,6 @@ jobs: run: | cd ${{ github.workspace }}/cacti sudo php cli/plugin_manage.php --plugin=syslog --install --enable - - - name: Check PHP Syntax for Plugin - run: | - cd ${{ github.workspace }}/cacti/plugins/syslog - if find . -name '*.php' -exec php -l {} 2>&1 \; | grep -iv 'no syntax errors detected'; then - echo "Syntax errors found!" - exit 1 - fi - name: Run Plugin Regression Tests run: | @@ -204,7 +253,7 @@ jobs: cd ${{ github.workspace }}/cacti sudo php poller.php --poller=1 --force --debug if ! grep -q "SYSTEM STATS" log/cacti.log; then - echo "Cacti poller did not finish successfully" + print "Cacti poller did not finish successfully" cat log/cacti.log exit 1 fi @@ -221,7 +270,7 @@ jobs: cd ${{ github.workspace }}/cacti sudo php plugins/syslog/syslog_process.php --debug if ! grep -q "SYSTEM SYSLOG STATS" log/cacti.log; then - echo "Syslog plugin poller did not finish successfully" + print "Syslog plugin poller did not finish successfully" cat log/cacti.log exit 1 fi @@ -231,41 +280,6 @@ jobs: if: always() run: | if [ -f ${{ github.workspace }}/cacti/log/cacti.log ]; then - echo "=== Cacti Log ===" + print "=== Cacti Log ===" sudo cat ${{ github.workspace }}/cacti/log/cacti.log fi - - - - name: Create PHPStan config - run: | - cd ${{ github.workspace }}/cacti/plugins/syslog - cat > phpstan.neon << 'EOF' - parameters: - level: 5 - paths: - - . - excludePaths: - - vendor/ - - locales/ - ignoreErrors: - - '#has invalid return type the\.#' - bootstrapFiles: - - ../../include/global.php - EOF - - - name: Install PHPStan - run: | - cd ${{ github.workspace }}/cacti/plugins/syslog - composer require --dev phpstan/phpstan --with-all-dependencies || composer global require phpstan/phpstan - - - name: Run PHPStan Analysis - run: | - cd ${{ github.workspace }}/cacti/plugins/syslog - if [ -f vendor/bin/phpstan ]; then - vendor/bin/phpstan analyse --no-progress --error-format=github || true - else - phpstan analyse --no-progress --error-format=github || true - fi - continue-on-error: true - - diff --git a/functions.php b/functions.php index e434bd1..777959c 100644 --- a/functions.php +++ b/functions.php @@ -22,6 +22,24 @@ +-------------------------------------------------------------------------+ */ +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) { + $action_function($selected_item); + } + } else { + cacti_log("SYSLOG ERROR: Bulk action function '$action_function' not found.", false, 'SYSTEM'); + } + } elseif ($export_action != '' && $drp_action == $export_action) { + $_SESSION['exporter'] = rawurlencode(serialize($selected_items)); + } + } +} + function syslog_include_js() { global $config; ?> @@ -126,25 +144,50 @@ function syslog_sendemail($to, $from, $subject, $message, $smsmessage = '') { } } -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]; +function syslog_get_import_xml_payload($redirect_url) { + if (trim(get_nfilter_request_var('import_text')) != '') { + /* textbox input */ + return get_nfilter_request_var('import_text'); + } - if (function_exists($action_function)) { - foreach($selected_items as $selected_item) { - $action_function($selected_item); - } - } 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)); + if (isset($_FILES['import_file']['tmp_name']) && + $_FILES['import_file']['tmp_name'] != 'none' && + $_FILES['import_file']['tmp_name'] != '') { + /* file upload */ + $tmp_name = $_FILES['import_file']['tmp_name']; + + if (!isset($_FILES['import_file']['error']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) { + header('Location: ' . $redirect_url); + exit; + } + + if (!is_uploaded_file($tmp_name)) { + header('Location: ' . $redirect_url); + exit; } + + $fp = fopen($tmp_name, 'rb'); + + if ($fp === false) { + cacti_log('SYSLOG ERROR: Failed to open uploaded import file', false, 'SYSTEM'); + header('Location: ' . $redirect_url); + exit; + } + + $xml_data = fread($fp, filesize($tmp_name)); + fclose($fp); + + if ($xml_data === false) { + cacti_log('SYSLOG ERROR: Failed to read uploaded import file', false, 'SYSTEM'); + header('Location: ' . $redirect_url); + exit; + } + + return $xml_data; } + + header('Location: ' . $redirect_url); + exit; } function syslog_is_partitioned() { @@ -208,86 +251,184 @@ function syslog_partition_manage() { } /** - * This function will create a new partition for the specified table. + * Validate tables that support partition maintenance. + * + * 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). + */ +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; + } + + return true; +} + +/** + * Create a new partition for the specified table. + * + * @return bool true on success, false on lock failure or disallowed table. */ function syslog_partition_create($table) { global $syslogdb_default; - /* determine the format of the table name */ - $time = time(); - $cformat = 'd' . date('Ymd', $time); - $lnow = date('Y-m-d', $time+86400); + if (!syslog_partition_table_allowed($table)) { + return false; + } - $exists = syslog_db_fetch_row("SELECT * - FROM `information_schema`.`partitions` - WHERE table_schema='" . $syslogdb_default . "' - AND partition_name='" . $cformat . "' - AND table_name='syslog' - ORDER BY partition_ordinal_position"); + /* 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); - if (!cacti_sizeof($exists)) { - cacti_log("SYSLOG: Creating new partition '$cformat'", false, 'SYSTEM'); + /* + * 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)); + + 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'); + return false; + } - syslog_debug("Creating new partition '$cformat'"); + if ((int)$locked !== 1) { + cacti_log("SYSLOG: Unable to acquire partition create lock for '$table'", false, 'SYSTEM'); + return false; + } - syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO ( - PARTITION $cformat VALUES LESS THAN (TO_DAYS('$lnow')), - PARTITION dMaxValue VALUES LESS THAN MAXVALUE)"); + try { + /* determine the format of the table name */ + $time = time(); + $cformat = 'd' . date('Ymd', $time); + $lnow = date('Y-m-d', $time+86400); + + $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)); + + if (!cacti_sizeof($exists)) { + cacti_log("SYSLOG: Creating new partition '$cformat'", false, 'SYSTEM'); + + syslog_debug("Creating new partition '$cformat'"); + + /* + * 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 ( + 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)); } + + return true; } /** - * This function will remove all old partitions for the specified table. + * Remove 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'); + 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; + } + + if ((int)$locked !== 1) { + cacti_log("SYSLOG: Unable to acquire partition remove lock for '$table'", false, 'SYSTEM'); + return 0; + } + $syslog_deleted = 0; - $number_of_partitions = syslog_db_fetch_assoc("SELECT * - FROM `information_schema`.`partitions` - WHERE table_schema='" . $syslogdb_default . "' AND table_name='syslog' - ORDER BY partition_ordinal_position"); - $days = read_config_option('syslog_retention'); + 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)); + + $days = read_config_option('syslog_retention'); - 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, 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) { - $oldest = $number_of_partitions[$i]; + if ($days > 0) { + $user_partitions = sizeof($number_of_partitions) - 1; + if ($user_partitions >= $days) { + $i = 0; + while ($user_partitions > $days) { + $oldest = $number_of_partitions[$i]; - cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "'", false, 'SYSTEM'); + cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "'", false, 'SYSTEM'); - syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "'"); + syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "'"); - syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']); + syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']); - $i++; - $user_partitions--; - $syslog_deleted++; + $i++; + $user_partitions--; + $syslog_deleted++; + } } } + } 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; + if (!syslog_partition_table_allowed($table)) { + return false; + } + if (defined('SYSLOG_CONFIG')) { include(SYSLOG_CONFIG); } /* find date of last partition */ - $last_part = syslog_db_fetch_cell("SELECT PARTITION_NAME + $last_part = syslog_db_fetch_cell_prepared("SELECT PARTITION_NAME FROM `information_schema`.`partitions` - WHERE table_schema='" . $syslogdb_default . "' AND table_name='syslog' + WHERE table_schema = ? AND table_name = ? ORDER BY partition_ordinal_position DESC - LIMIT 1,1;"); + LIMIT 1,1", + array($syslogdb_default, $table)); $lformat = str_replace('d', '', $last_part); $cformat = date('Ymd'); @@ -307,7 +448,7 @@ function syslog_check_changed($request, $session) { } } -function syslog_remove_items($table, $uniqueID) { +function syslog_remove_items($table, $max_seq) { global $config, $syslog_cnn, $syslog_incoming_config; global $syslogdb_default; @@ -317,8 +458,7 @@ function syslog_remove_items($table, $uniqueID) { if ($table == 'syslog') { $rows = syslog_db_fetch_assoc("SELECT * FROM `" . $syslogdb_default . "`.`syslog_remove` - WHERE enabled = 'on' - AND id = $uniqueID"); + WHERE enabled = 'on'"); } else { $rows = syslog_db_fetch_assoc('SELECT * FROM `' . $syslogdb_default . '`.`syslog_remove` @@ -331,329 +471,157 @@ function syslog_remove_items($table, $uniqueID) { $xferred = 0; if ($table == 'syslog_incoming') { - $total = syslog_db_fetch_cell('SELECT count(*) + $total = syslog_db_fetch_cell_prepared('SELECT count(*) FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE `status` = ' . $uniqueID); + WHERE `status` = 1 + AND `seq` <= ?', + array($max_seq)); } else { $total = 0; } if (cacti_sizeof($rows)) { foreach($rows as $remove) { - $sql = ''; - $sql1 = ''; + $sql_where = ''; + $params = array(); if ($remove['type'] == 'facility') { if ($table == 'syslog_incoming') { - 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 (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 - WHERE ' . $syslog_incoming_config['facilityField'] . ' = ' . db_qstr($remove['message']) . ' - AND `status` = ' . $uniqueID . ' - ) AS merge'; - } + $sql_where = 'WHERE `' . $syslog_incoming_config['facilityField'] . '` = ? + AND `status` = 1 + AND `seq` <= ?'; - $sql = 'DELETE - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE ' . $syslog_incoming_config['facilityField'] . ' = ' . db_qstr($remove['message']) . ' - AND `status` = ' . $uniqueID; + $params[] = $remove['message']; + $params[] = $max_seq; } else { - $facility_id = syslog_db_fetch_cell('SELECT facility_id + $facility_id = syslog_db_fetch_cell_prepared('SELECT facility_id FROM `' . $syslogdb_default . '`.`syslog_facilities` - WHERE facility = ' . db_qstr($remove['message'])); + WHERE facility = ?', array($remove['message'])); if (!empty($facility_id)) { - 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` - WHERE facility_id = ' . $facility_id; - } - - $sql = 'DELETE FROM `' . $syslogdb_default . '`.`syslog` - WHERE facility_id = ' . $facility_id; + $sql_where = 'WHERE facility_id = ?'; + $params[] = $facility_id; } } } else if ($remove['type'] == 'program') { if ($table == 'syslog_incoming') { - 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 (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 - WHERE program = ' . db_qstr($remove['message']) . ' - AND `status` = ' . $uniqueID . ' - ) AS merge'; - } + $sql_where = 'WHERE `program` = ? + AND `status` = 1 + AND `seq` <= ?'; - $sql = 'DELETE - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE `program` = ' . db_qstr($remove['message']) . ' - AND `status` = ' . $uniqueID; + $params[] = $remove['message']; + $params[] = $max_seq; } else { - $program_id = syslog_db_fetch_cell('SELECT program_id + $program_id = syslog_db_fetch_cell_prepared('SELECT program_id FROM `' . $syslogdb_default . '`.`syslog_programs` - WHERE program = ' . db_qstr($remove['message'])); + WHERE program = ?', array($remove['message'])); if (!empty($program_id)) { - 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` - WHERE program_id = ' . $program_id; - } - - $sql = 'DELETE FROM `' . $syslogdb_default . '`.`syslog` - WHERE program_id = ' . $program_id; + $sql_where = 'WHERE program_id = ?'; + $params[] = $program_id; } } } elseif ($remove['type'] == 'host') { if ($table == 'syslog_incoming') { - 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 (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 - WHERE si.host = ' . db_qstr($remove['message']) . ' - AND `status` = ' . $uniqueID . ' - ) AS merge'; - } + $sql_where = 'WHERE `host` = ? + AND `status` = 1 + AND `seq` <= ?'; - $sql = 'DELETE - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE host = ' . db_qstr($remove['message']) . ' - AND `status` = ' . $uniqueID; + $params[] = $remove['message']; + $params[] = $max_seq; } else { - $host_id = syslog_db_fetch_cell('SELECT host_id + $host_id = syslog_db_fetch_cell_prepared('SELECT host_id FROM `' . $syslogdb_default . '`.`syslog_hosts` - WHERE host = ' . db_qstr($remove['message'])); + WHERE host = ?', array($remove['message'])); if (!empty($host_id)) { - 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` - WHERE host_id = ' . $host_id; - } - - $sql = 'DELETE FROM `' . $syslogdb_default . '`.`syslog` - WHERE host_id = ' . $host_id; + $sql_where = 'WHERE host_id = ?'; + $params[] = $host_id; } } } elseif ($remove['type'] == 'messageb') { if ($table == 'syslog_incoming') { - 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 (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 - WHERE message LIKE ' . db_qstr($remove['message'] . '%') . ' - AND `status` = ' . $uniqueID . ' - ) AS merge'; - } + $sql_where = 'WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ? + AND `status` = 1 + AND `seq` <= ?'; - $sql = 'DELETE - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE message LIKE ' . db_qstr($remove['message'] . '%') . ' - AND `status` = ' . $uniqueID; + $params[] = $remove['message'] . '%'; + $params[] = $max_seq; } else { - if ($remove['message'] != '') { - 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` - WHERE message LIKE ' . db_qstr($remove['message'] . '%'); - } - - $sql = 'DELETE FROM `' . $syslogdb_default . '`.`syslog` - WHERE message LIKE ' . db_qstr($remove['message'] . '%'); - } + $sql_where = 'WHERE message LIKE ?'; + $params[] = $remove['message'] . '%'; } } elseif ($remove['type'] == 'messagec') { if ($table == 'syslog_incoming') { - 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 (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 - WHERE message LIKE ' . db_qstr('%' . $remove['message'] . '%') . ' - AND `status` = ' . $uniqueID . ' - ) AS merge'; - } + $sql_where = 'WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ? + AND `status` = 1 + AND `seq` <= ?'; - $sql = 'DELETE - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE message LIKE ' . db_qstr('%' . $remove['message'] . '%') . ' - AND `status` = ' . $uniqueID; + $params[] = '%' . $remove['message'] . '%'; + $params[] = $max_seq; } else { - if ($remove['message'] != '') { - 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` - WHERE message LIKE ' . db_qstr('%' . $remove['message'] . '%'); - } - - $sql = 'DELETE FROM `' . $syslogdb_default . '`.`syslog` - WHERE message LIKE ' . db_qstr('%' . $remove['message'] . '%'); - } + $sql_where = 'WHERE message LIKE ?'; + $params[] = '%' . $remove['message'] . '%'; } } elseif ($remove['type'] == 'messagee') { if ($table == 'syslog_incoming') { - 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 (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 - WHERE message LIKE ' . db_qstr('%' . $remove['message']) . ' - AND `status` = ' . $uniqueID . ' - ) AS merge'; - } + $sql_where = 'WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ? + AND `status` = 1 + AND `seq` <= ?'; - $sql = 'DELETE - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE message LIKE ' . db_qstr('%' . $remove['message']) . ' - AND `status` = ' . $uniqueID; + $params[] = '%' . $remove['message']; + $params[] = $max_seq; } else { - if ($remove['message'] != '') { - 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` - WHERE message LIKE ' . db_qstr('%' . $remove['message']); - } - - $sql = 'DELETE FROM `' . $syslogdb_default . '`.`syslog` - WHERE message LIKE ' . db_qstr('%' . $remove['message']); - } + $sql_where = 'WHERE message LIKE ?'; + $params[] = '%' . $remove['message']; } } elseif ($remove['type'] == 'sql') { if ($table == 'syslog_incoming') { - 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 (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 - WHERE `status` = ' . $uniqueID . ' - AND (' . $remove['message'] . ') - ) AS merge'; - } + $sql_where = 'WHERE (' . $remove['message'] . ') + AND `status` = 1 + AND `seq` <= ?'; - $sql = 'DELETE - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE (' . $remove['message'] . ') - AND `status` = ' . $uniqueID; + $params[] = $max_seq; } else { - if ($remove['message'] != '') { - 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` - WHERE ' . $remove['message']; - } - - $sql = 'DELETE - FROM `' . $syslogdb_default . '`.`syslog` - WHERE ' . $remove['message']; - } + $sql_where = 'WHERE (' . $remove['message'] . ')'; } } - if ($sql != '' || $sql1 != '') { - $debugm = ''; - /* process the removal rule first */ - if ($sql1 != '') { - /* now delete the remainder that match */ - syslog_db_execute($sql1); - } + 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 + FROM `' . $syslogdb_default . '`.`syslog_incoming` AS si + 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` + (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); + } - /* now delete the remainder that match */ - syslog_db_execute($sql); - $removed += db_affected_rows($syslog_cnn); - $debugm = sprintf('Deleted %5s - ', $removed); - if ($sql1 != '') { $xferred += db_affected_rows($syslog_cnn); - $debugm = sprintf('Moved %5s - ', $xferred); } - syslog_debug($debugm . 'Message' . (db_affected_rows($syslog_cnn) == 1 ? '' : 's' ) . - " for removal rule '" . $remove['name'] . "'"); + if ($table == 'syslog_incoming') { + syslog_db_execute_prepared('DELETE FROM `' . $syslogdb_default . '`.`syslog_incoming` ' . $sql_where, $params); + } else { + syslog_db_execute_prepared('DELETE FROM `' . $syslogdb_default . '`.`syslog` ' . $sql_where, $params); + } + + $removed += db_affected_rows($syslog_cnn); } } } + 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); } @@ -1102,6 +1070,93 @@ 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 * @@ -1118,12 +1173,11 @@ function syslog_array2xml($array, $tag = 'template') { * 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 unique id to process + * @param (int) The max_seq to process * * @return (array) An array of the number of alerts processed and the number of alerts generated */ -function syslog_process_alerts($uniqueID) { - global $syslogdb_default; +function syslog_process_alerts($max_seq) { global $syslogdb_default; $syslog_alarms = 0; @@ -1147,7 +1201,6 @@ function syslog_process_alerts($uniqueID) { if (cacti_sizeof($alerts)) { foreach($alerts as $alert) { $sql = ''; - $th_sql = ''; $params = array(); /* we roll up statistics depending on the level */ @@ -1157,7 +1210,7 @@ function syslog_process_alerts($uniqueID) { $groupBy = ''; } - $sql_data = syslog_get_alert_sql($alert, $uniqueID); + $sql_data = syslog_get_alert_sql($alert, $max_seq); if (!cacti_sizeof($sql_data)) { syslog_debug(sprintf('Error - Unable to determine SQL for Alert \'%s\'', $alert['name'])); @@ -1167,18 +1220,8 @@ function syslog_process_alerts($uniqueID) { $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); @@ -1193,16 +1236,10 @@ function syslog_process_alerts($uniqueID) { } } } 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); } @@ -1516,49 +1553,10 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') { /** * Open a ticket if this options have been selected. */ - $command = read_config_option('syslog_ticket_command'); - - if ($command != '') { - $command = trim($command); - } - - if ($alert['open_ticket'] == 'on' && $command != '') { - if (is_executable($command)) { - $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: Ticket Command Failed. Alert:%s, Exit:%s, Output:%s', $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG'); - } - } - } + syslog_execute_ticket_command($alert, $hostlist, 'ERROR: Ticket Command Failed. Alert:%s, Exit:%s, Output:%s'); if (trim($alert['command']) != '' && !$found) { - $command = alert_replace_variables($alert, $results, $hostname); - - $logMessage = "SYSLOG NOTICE: Executing '$command'"; - - $cparts = explode(' ', $command); - - if (is_executable($cparts[0])) { - exec($command, $output, $returnCode); - } else { - exec('/bin/sh ' . $command, $output, $returnCode); - } - - // Append the return code to the log message without the dot - $logMessage .= " Command return code: $returnCode"; - - // Log the combined message - cacti_log($logMessage, true, 'SYSTEM'); + syslog_execute_alert_command($alert, $results, $hostname); } } @@ -1576,49 +1574,10 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') { alert_setup_environment($alert, $results, $hostlist, $hostname); - $command = read_config_option('syslog_ticket_command'); - - if ($command != '') { - $command = trim($command); - } - - if ($alert['open_ticket'] == 'on' && $command != '') { - if (is_executable($command)) { - $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: Command Failed. Alert:%s, Exit:%s, Output:%s', $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG'); - } - } - } + syslog_execute_ticket_command($alert, $hostlist, 'ERROR: Command Failed. Alert:%s, Exit:%s, Output:%s'); if (trim($alert['command']) != '' && !$found) { - $command = alert_replace_variables($alert, $results, $hostname); - - $logMessage = "SYSLOG NOTICE: Executing '$command'"; - - $cparts = explode(' ', $command); - - if (is_executable($cparts[0])) { - exec($command, $output, $returnCode); - } else { - exec('/bin/sh ' . $command, $output, $returnCode); - } - - // Append the return code to the log message without the dot - $logMessage .= " Command return code: $returnCode"; - - // Log the combined message - cacti_log($logMessage, true, 'SYSTEM'); + syslog_execute_alert_command($alert, $results, $hostname); } } } @@ -1638,7 +1597,7 @@ 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, $uniqueID) { +function syslog_get_alert_sql(&$alert, $max_seq) { global $syslogdb_default, $syslog_incoming_config; if (defined('SYSLOG_CONFIG')) { @@ -1656,65 +1615,72 @@ function syslog_get_alert_sql(&$alert, $uniqueID) { $sql = 'SELECT * FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE `' . $syslog_incoming_config['facilityField'] . '` = ? - AND `status` = ?'; + AND `status` = 1 + AND `seq` <= ?'; $params[] = $alert['message']; - $params[] = $uniqueID; + $params[] = $max_seq; } elseif ($alert['type'] == 'messageb') { $sql = 'SELECT * FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ? - AND `status` = ?'; + AND `status` = 1 + AND `seq` <= ?'; $params[] = $alert['message'] . '%'; - $params[] = $uniqueID; + $params[] = $max_seq; } elseif ($alert['type'] == 'messagec') { $sql = 'SELECT * FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ? - AND `status` = ?'; + AND `status` = 1 + AND `seq` <= ?'; $params[] = '%' . $alert['message'] . '%'; - $params[] = $uniqueID; + $params[] = $max_seq; } elseif ($alert['type'] == 'messagee') { $sql = 'SELECT * FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE `' . $syslog_incoming_config['textField'] . '` LIKE ? - AND `status` = ?'; + AND `status` = 1 + AND `seq` <= ?'; $params[] = '%' . $alert['message']; - $params[] = $uniqueID; + $params[] = $max_seq; } elseif ($alert['type'] == 'host') { $sql = 'SELECT * FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE `' . $syslog_incoming_config['hostField'] . '` = ? - AND `status` = ?' . $uniqueID; + AND `status` = 1 + AND `seq` <= ?'; $params[] = $alert['message']; - $params[] = $uniqueID; + $params[] = $max_seq; } elseif ($alert['type'] == 'program') { $sql = 'SELECT * FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE `' . $syslog_incoming_config['programField'] . '` = ? - AND `status` = ?' . $uniqueID; + AND `status` = 1 + AND `seq` <= ?'; $params[] = $alert['message']; - $params[] = $uniqueID; + $params[] = $max_seq; } elseif ($alert['type'] == 'sql') { $sql = 'SELECT * FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE (' . $alert['message'] . ') - AND `status` = ?'; + AND `status` = 1 + AND `seq` <= ?'; - $params[] = $uniqueID; + $params[] = $max_seq; } return array('sql' => $sql, 'params' => $params); } /** - * syslog_preprocess_incoming_records - Generate a uniqueID to allow moving of - * records to done table and mark incoming records with the uniqueID and + * 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 * then if syslog is configured to strip domains, perform that first. * * @return (int) Unique id to allow syslog messages that come in randomly to @@ -1724,52 +1690,47 @@ function syslog_get_alert_sql(&$alert, $uniqueID) { function syslog_preprocess_incoming_records() { global $syslogdb_default; - while (1) { - $uniqueID = rand(1, 127); - - $count = syslog_db_fetch_cell_prepared('SELECT COUNT(*) - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE `status` = ?', - array($uniqueID)); + $max_seq = syslog_db_fetch_cell('SELECT MAX(seq) FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE status = 0'); - if ($count == 0) { - break; - } - } + 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)); - /* 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)); + syslog_debug('Max Sequence ID = ' . $max_seq); + syslog_debug('-------------------------------------------------------------------------------------'); - syslog_debug('Unique ID = ' . $uniqueID); - syslog_debug('-------------------------------------------------------------------------------------'); + $syslog_incoming = syslog_db_fetch_cell_prepared('SELECT COUNT(seq) + FROM `' . $syslogdb_default . '`.`syslog_incoming` + WHERE `status` = 1 + AND `seq` <= ?', + array($max_seq)); - $syslog_incoming = syslog_db_fetch_cell_prepared('SELECT COUNT(seq) - FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE `status` = ?', - array($uniqueID)); + syslog_debug(sprintf('Found %5s - New Message(s) to process', $syslog_incoming)); - syslog_debug(sprintf('Found %5s - New Message(s) to process', $syslog_incoming)); + /* strip domains if we have requested to do so */ + syslog_strip_incoming_domains($max_seq); - /* strip domains if we have requested to do so */ - syslog_strip_incoming_domains($uniqueID); + api_plugin_hook('plugin_syslog_before_processing'); - api_plugin_hook('plugin_syslog_before_processing'); + return array('max_seq' => $max_seq, 'incoming' => $syslog_incoming); + } - return array('uniqueID' => $uniqueID, 'incoming' => $syslog_incoming); + return array('max_seq' => 0, 'incoming' => 0); } /** * syslog_strip_incoming_domains - If syslog is setup to strip DNS domain name suffixes do that * prior to processing the records. * - * @param (string) The uniqueID records to process + * @param (string) The max_seq records to process * * @return (void) */ -function syslog_strip_incoming_domains($uniqueID) { +function syslog_strip_incoming_domains($max_seq) { global $syslogdb_default; $syslog_domains = read_config_option('syslog_domains'); @@ -1778,29 +1739,28 @@ function syslog_strip_incoming_domains($uniqueID) { $domains = explode(',', trim($syslog_domains)); foreach($domains as $domain) { - syslog_db_execute('UPDATE `' . $syslogdb_default . "`.`syslog_incoming` - SET host = SUBSTRING_INDEX(host, '.', 1) - WHERE host LIKE '%$domain' - AND `status` = $uniqueID"); + 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)); } } } - - - /** * 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 unique id for syslog_incoming messages to process + * @param (int) The max_seq 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, $uniqueID) { +function syslog_check_cacti_hosts($host, $max_seq) { global $syslogdb_default; if (empty($host)) { @@ -1818,8 +1778,9 @@ function syslog_check_cacti_hosts($host, $uniqueID) { syslog_db_execute_prepared('UPDATE `' . $syslogdb_default . '`.`syslog_incoming` SET host = ? WHERE host = ? - AND `status` = ?', - array($cacti_host['description'], $host, $uniqueID)); + AND `status` = 1 + AND `seq` <= ?', + array($cacti_host['description'], $host, $max_seq)); return true; } @@ -1835,11 +1796,11 @@ function syslog_check_cacti_hosts($host, $uniqueID) { * 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 unique id for syslog_incoming messages to process + * @param (int) The max_seq for syslog_incoming messages to process * * @return (void) */ -function syslog_update_reference_tables($uniqueID) { +function syslog_update_reference_tables($max_seq) { global $syslogdb_default; syslog_debug('-------------------------------------------------------------------------------------'); @@ -1849,8 +1810,9 @@ function syslog_update_reference_tables($uniqueID) { if (read_config_option('syslog_resolve_hostname') == 'on') { $hosts = syslog_db_fetch_assoc_prepared('SELECT DISTINCT host FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE `status` = ?', - array($uniqueID)); + WHERE `status` = 1 + AND `seq` <= ?', + array($max_seq)); foreach($hosts as $host) { if (!isset($host['host']) || empty($host['host'])) { @@ -1869,7 +1831,7 @@ function syslog_update_reference_tables($uniqueID) { // Check if hostname exists in Cacti hosts table (only if not already resolved via DNS) if (!$resolved) { - $resolved = syslog_check_cacti_hosts($host['host'], $uniqueID); + $resolved = syslog_check_cacti_hosts($host['host'], $max_seq); } // If not resolved via DNS or found in Cacti, prefix the hostname @@ -1879,8 +1841,9 @@ function syslog_update_reference_tables($uniqueID) { syslog_db_execute_prepared('UPDATE `' . $syslogdb_default . "`.`syslog_incoming` SET host = ? WHERE host = ? - AND `status` = ?", - array($unresolved_host, $host['host'], $uniqueID)); + AND `status` = 1 + AND `seq` <= ?", + array($unresolved_host, $host['host'], $max_seq)); } } } @@ -1889,21 +1852,23 @@ function syslog_update_reference_tables($uniqueID) { (program, last_updated) SELECT DISTINCT program, NOW() FROM `' . $syslogdb_default . '`.`syslog_incoming` - WHERE `status` = ? + WHERE `status` = 1 + AND `seq` <= ? ON DUPLICATE KEY UPDATE program=VALUES(program), last_updated=VALUES(last_updated)', - array($uniqueID)); + array($max_seq)); 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` = ? + WHERE `status` = 1 + AND `seq` <= ? ON DUPLICATE KEY UPDATE host=VALUES(host), last_updated=NOW()', - array($uniqueID)); + array($max_seq)); syslog_db_execute_prepared('INSERT INTO `' . $syslogdb_default . '`.`syslog_host_facilities` (host_id, facility_id) @@ -1912,7 +1877,8 @@ function syslog_update_reference_tables($uniqueID) { ( SELECT DISTINCT host, facility_id FROM `' . $syslogdb_default . "`.`syslog_incoming` - WHERE `status` = ? + WHERE `status` = 1 + AND `seq` <= ? ) AS s INNER JOIN `" . $syslogdb_default . '`.`syslog_hosts` AS sh ON s.host = sh.host @@ -1920,18 +1886,18 @@ function syslog_update_reference_tables($uniqueID) { ON DUPLICATE KEY UPDATE host_id=VALUES(host_id), last_updated=NOW()', - array($uniqueID)); + array($max_seq)); } /** * syslog_update_statistics - Insert new statistics rows into the syslog statistics * table for post review * - * @param (int) The unique id for all syslog incoming records to be processed + * @param (int) The max_seq for all syslog incoming records to be processed * * @return (void) */ -function syslog_update_statistics($uniqueID) { +function syslog_update_statistics($max_seq) { global $syslogdb_default, $syslog_cnn; if (read_config_option('syslog_statistics') == 'on') { @@ -1944,10 +1910,11 @@ function syslog_update_statistics($uniqueID) { ON sh.host=si.host INNER JOIN syslog_programs AS sp ON sp.program=si.program - WHERE `status` = ? + WHERE si.`status` = 1 + AND si.`seq` <= ? GROUP BY host_id, priority_id, facility_id, program_id) AS merge GROUP BY host_id, priority_id, facility_id, program_id', - array($uniqueID)); + array($max_seq)); $stats = db_affected_rows($syslog_cnn); @@ -1962,11 +1929,11 @@ function syslog_update_statistics($uniqueID) { * the syslog table, and then after which we can perform various * removal rules against them. * - * @param (int) The unique id for rows in the syslog table + * @param (int) The max_seq for rows in the syslog table * * @return (int) The number of rows moved to the syslog table */ -function syslog_incoming_to_syslog($uniqueID) { +function syslog_incoming_to_syslog($max_seq) { global $syslogdb_default, $syslog_cnn; syslog_db_execute_prepared('INSERT INTO `' . $syslogdb_default . '`.`syslog` @@ -1979,9 +1946,10 @@ function syslog_incoming_to_syslog($uniqueID) { ON sh.host = si.host INNER JOIN syslog_programs AS sp ON sp.program = si.program - WHERE `status` = ? + WHERE si.`status` = 1 + AND si.`seq` <= ? ) AS merge', - array($uniqueID)); + array($max_seq)); $moved = db_affected_rows($syslog_cnn); @@ -1990,7 +1958,10 @@ function syslog_incoming_to_syslog($uniqueID) { syslog_debug(sprintf('Moved %5s - Message(s) to the syslog table', $moved)); - syslog_db_execute('DELETE FROM `' . $syslogdb_default . '`.`syslog_incoming` WHERE status=' . $uniqueID); + syslog_db_execute_prepared('DELETE FROM `' . $syslogdb_default . '`.`syslog_incoming` + WHERE `status` = 1 + AND `seq` <= ?', + array($max_seq)); syslog_debug(sprintf('Deleted %5s - Already Processed Message(s) from incoming', db_affected_rows($syslog_cnn))); diff --git a/syslog_process.php b/syslog_process.php index 0ed1543..ad9d0db 100644 --- a/syslog_process.php +++ b/syslog_process.php @@ -151,13 +151,13 @@ syslog_debug('-------------------------------------------------------------------------------------'); /** - * pre-processing includes marking a uniqueID to be used + * pre-processing includes marking a max_seq to be used * in the processesing of alerts and stripping domains * from hostnames in the case that the administrator * chooses to strip them. */ $results = syslog_preprocess_incoming_records(); -$uniqueID = $results['uniqueID']; +$max_seq = $results['max_seq']; $incoming = $results['incoming']; /** @@ -173,7 +173,7 @@ * time and to speed up searching for these various * columns in the database. */ -syslog_update_reference_tables($uniqueID); +syslog_update_reference_tables($max_seq); /** * The statistics process allows the Cacti @@ -181,19 +181,19 @@ * into the syslog table and what message types are flowing * into it. */ -syslog_update_statistics($uniqueID); +syslog_update_statistics($max_seq); /** * remove records that don't need to to be transferred */ -$results = syslog_remove_items('syslog_incoming', $uniqueID); +$results = syslog_remove_items('syslog_incoming', $max_seq); $removed = $results['removed']; $xferred = $results['xferred']; /** * process the syslog rules and generate alerts */ -$results = syslog_process_alerts($uniqueID); +$results = syslog_process_alerts($max_seq); $alerts = $results['syslog_alerts']; $alarms = $results['syslog_alarms']; @@ -209,7 +209,7 @@ * move records from incoming to syslog table and remove * any stale records to to a poller crash */ -$results = syslog_incoming_to_syslog($uniqueID); +$results = syslog_incoming_to_syslog($max_seq); $moved = $results['moved']; $stale = $results['stale']; diff --git a/tests/regression/issue253_alert_sql_placeholder_test.php b/tests/regression/issue253_alert_sql_placeholder_test.php index 1f4f181..57f3901 100644 --- a/tests/regression/issue253_alert_sql_placeholder_test.php +++ b/tests/regression/issue253_alert_sql_placeholder_test.php @@ -40,4 +40,4 @@ function issue253_assert($condition, $message) { issue253_assert(count($progSql['params']) === 2, 'Program alert SQL must pass two prepared parameters.'); issue253_assert($progSql['params'][1] === 66, 'Program alert status param should be the uniqueID.'); -echo "issue253_alert_sql_placeholder_test passed\n"; +print "issue253_alert_sql_placeholder_test passed\n"; diff --git a/tests/regression/issue254_partition_table_locking_test.php b/tests/regression/issue254_partition_table_locking_test.php index dfccddb..2f02021 100644 --- a/tests/regression/issue254_partition_table_locking_test.php +++ b/tests/regression/issue254_partition_table_locking_test.php @@ -186,4 +186,4 @@ exit(1); } -echo "issue254_partition_table_locking_test passed\n"; +print "issue254_partition_table_locking_test passed\n"; diff --git a/tests/regression/issue258_replication_create_sql_test.php b/tests/regression/issue258_replication_create_sql_test.php index 36e810b..3fbfc4f 100644 --- a/tests/regression/issue258_replication_create_sql_test.php +++ b/tests/regression/issue258_replication_create_sql_test.php @@ -118,4 +118,4 @@ function cacti_log($message, $output = false, $facility = 'SYSTEM', $level = '') exit(1); } -echo "issue258_replication_create_sql_test passed\n"; +print "issue258_replication_create_sql_test passed\n"; diff --git a/tests/regression/issue270_mariadb_detection_strict_test.php b/tests/regression/issue270_mariadb_detection_strict_test.php index 85112b6..a876120 100644 --- a/tests/regression/issue270_mariadb_detection_strict_test.php +++ b/tests/regression/issue270_mariadb_detection_strict_test.php @@ -20,4 +20,4 @@ exit(1); } -echo "issue270_mariadb_detection_strict_test passed\n"; +print "issue270_mariadb_detection_strict_test passed\n"; diff --git a/tests/regression/issue276_bulk_action_dispatch_helper_test.php b/tests/regression/issue276_bulk_action_dispatch_helper_test.php index cec8251..c30560c 100644 --- a/tests/regression/issue276_bulk_action_dispatch_helper_test.php +++ b/tests/regression/issue276_bulk_action_dispatch_helper_test.php @@ -33,4 +33,4 @@ } } -echo "issue276_bulk_action_dispatch_helper_test passed\n"; +print "issue276_bulk_action_dispatch_helper_test passed\n"; diff --git a/tests/regression/issue277_import_payload_loader_test.php b/tests/regression/issue277_import_payload_loader_test.php index d36b4a9..615653f 100644 --- a/tests/regression/issue277_import_payload_loader_test.php +++ b/tests/regression/issue277_import_payload_loader_test.php @@ -65,4 +65,4 @@ } } -echo "issue277_import_payload_loader_test passed\n"; +print "issue277_import_payload_loader_test passed\n"; diff --git a/tests/regression/issue278_command_execution_refactor_test.php b/tests/regression/issue278_command_execution_refactor_test.php index 9ee8167..6583330 100644 --- a/tests/regression/issue278_command_execution_refactor_test.php +++ b/tests/regression/issue278_command_execution_refactor_test.php @@ -115,4 +115,4 @@ } } -echo "issue278_command_execution_refactor_test passed\n"; +print "issue278_command_execution_refactor_test passed\n";