Skip to content

Commit e1d05e1

Browse files
fix: address TheWitness review feedback on PR Cacti#283
- Remove $uniqueID filter from syslog_remove query (incorrectly filtered removal rules by random batch marker) - Reorder CI workflow: lint/PHPStan before integration tests - Switch echo to print in syslog_batch_transfer.php Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
1 parent da87864 commit e1d05e1

File tree

4 files changed

+155
-79
lines changed

4 files changed

+155
-79
lines changed

.github/workflows/plugin-ci-workflow.yml

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,38 @@ jobs:
187187
exit 1
188188
fi
189189
190+
- name: Create PHPStan config
191+
run: |
192+
cd ${{ github.workspace }}/cacti/plugins/syslog
193+
cat > phpstan.neon << 'EOF'
194+
parameters:
195+
level: 5
196+
paths:
197+
- .
198+
excludePaths:
199+
- vendor/
200+
- locales/
201+
ignoreErrors:
202+
- '#has invalid return type the\.#'
203+
bootstrapFiles:
204+
- ../../include/global.php
205+
EOF
206+
207+
- name: Install PHPStan
208+
run: |
209+
cd ${{ github.workspace }}/cacti/plugins/syslog
210+
composer require --dev phpstan/phpstan --with-all-dependencies || composer global require phpstan/phpstan
211+
212+
- name: Run PHPStan Analysis
213+
run: |
214+
cd ${{ github.workspace }}/cacti/plugins/syslog
215+
if [ -f vendor/bin/phpstan ]; then
216+
vendor/bin/phpstan analyse --no-progress --error-format=github || true
217+
else
218+
phpstan analyse --no-progress --error-format=github || true
219+
fi
220+
continue-on-error: true
221+
190222
- name: Run Plugin Regression Tests
191223
run: |
192224
cd ${{ github.workspace }}/cacti/plugins/syslog
@@ -196,8 +228,7 @@ jobs:
196228
php "$test"
197229
done
198230
fi
199-
200-
231+
201232
- name: Run Cacti Poller
202233
run: |
203234
cd ${{ github.workspace }}/cacti
@@ -207,14 +238,13 @@ jobs:
207238
cat log/cacti.log
208239
exit 1
209240
fi
210-
241+
211242
- name: Populate Syslog Test Data
212243
run: |
213244
sudo chmod +x ${{ github.workspace }}/cacti/plugins/syslog/.github/workflows/populate_syslog_incoming.sh
214245
cd ${{ github.workspace }}/cacti/plugins/syslog/.github/workflows
215246
sudo ./populate_syslog_incoming.sh
216247
217-
218248
- name: force Syslog Plugin Poller
219249
run: |
220250
cd ${{ github.workspace }}/cacti
@@ -225,7 +255,6 @@ jobs:
225255
exit 1
226256
fi
227257
228-
229258
- name: View Cacti Logs
230259
if: always()
231260
run: |
@@ -235,36 +264,3 @@ jobs:
235264
fi
236265
237266
238-
- name: Create PHPStan config
239-
run: |
240-
cd ${{ github.workspace }}/cacti/plugins/syslog
241-
cat > phpstan.neon << 'EOF'
242-
parameters:
243-
level: 5
244-
paths:
245-
- .
246-
excludePaths:
247-
- vendor/
248-
- locales/
249-
ignoreErrors:
250-
- '#has invalid return type the\.#'
251-
bootstrapFiles:
252-
- ../../include/global.php
253-
EOF
254-
255-
- name: Install PHPStan
256-
run: |
257-
cd ${{ github.workspace }}/cacti/plugins/syslog
258-
composer require --dev phpstan/phpstan --with-all-dependencies || composer global require phpstan/phpstan
259-
260-
- name: Run PHPStan Analysis
261-
run: |
262-
cd ${{ github.workspace }}/cacti/plugins/syslog
263-
if [ -f vendor/bin/phpstan ]; then
264-
vendor/bin/phpstan analyse --no-progress --error-format=github || true
265-
else
266-
phpstan analyse --no-progress --error-format=github || true
267-
fi
268-
continue-on-error: true
269-
270-

functions.php

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -232,32 +232,63 @@ function syslog_partition_manage() {
232232
return $syslog_deleted;
233233
}
234234

235+
/**
236+
* syslog_partition_table_allowed - validate that the table being partitioned
237+
* is in our approved list.
238+
*
239+
* @param (string) The table name
240+
*
241+
* @return (bool) True if allowed, False otherwise
242+
*/
243+
function syslog_partition_table_allowed($table) {
244+
if (in_array($table, array('syslog', 'syslog_removed'), true)) {
245+
return (bool)preg_match('/^[a-z_]+$/', $table);
246+
}
247+
248+
return false;
249+
}
250+
235251
/**
236252
* This function will create a new partition for the specified table.
237253
*/
238254
function syslog_partition_create($table) {
239255
global $syslogdb_default;
240256

257+
if (!syslog_partition_table_allowed($table)) {
258+
return false;
259+
}
260+
241261
/* determine the format of the table name */
242262
$time = time();
243263
$cformat = 'd' . date('Ymd', $time);
244264
$lnow = date('Y-m-d', $time+86400);
245265

246-
$exists = syslog_db_fetch_row("SELECT *
266+
$exists = syslog_db_fetch_row_prepared("SELECT *
247267
FROM `information_schema`.`partitions`
248-
WHERE table_schema='" . $syslogdb_default . "'
249-
AND partition_name='" . $cformat . "'
250-
AND table_name='syslog'
251-
ORDER BY partition_ordinal_position");
268+
WHERE table_schema = ?
269+
AND partition_name = ?
270+
AND table_name = ?
271+
ORDER BY partition_ordinal_position",
272+
array($syslogdb_default, $cformat, $table)
273+
);
252274

253275
if (!cacti_sizeof($exists)) {
254-
cacti_log("SYSLOG: Creating new partition '$cformat'", false, 'SYSTEM');
276+
$lock_name = hash('sha256', $syslogdb_default . 'syslog_partition_create.' . $table);
277+
278+
try {
279+
syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
280+
281+
cacti_log("SYSLOG: Creating new partition '$cformat' for table '$table'", false, 'SYSTEM');
255282

256-
syslog_debug("Creating new partition '$cformat'");
283+
syslog_debug("Creating new partition '$cformat' for table '$table'");
257284

258-
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO (
259-
PARTITION $cformat VALUES LESS THAN (TO_DAYS('$lnow')),
260-
PARTITION dMaxValue VALUES LESS THAN MAXVALUE)");
285+
/* MySQL does not support parameter binding for DDL statements */
286+
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO (
287+
PARTITION $cformat VALUES LESS THAN (TO_DAYS('$lnow')),
288+
PARTITION dMaxValue VALUES LESS THAN MAXVALUE)");
289+
} finally {
290+
syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
291+
}
261292
}
262293
}
263294

@@ -267,32 +298,48 @@ function syslog_partition_create($table) {
267298
function syslog_partition_remove($table) {
268299
global $syslogdb_default;
269300

301+
if (!syslog_partition_table_allowed($table)) {
302+
cacti_log("SYSLOG ERROR: Attempt to remove partitions from disallowed table '$table'", false, 'SYSTEM');
303+
return 0;
304+
}
305+
270306
$syslog_deleted = 0;
271-
$number_of_partitions = syslog_db_fetch_assoc("SELECT *
307+
$number_of_partitions = syslog_db_fetch_assoc_prepared("SELECT *
272308
FROM `information_schema`.`partitions`
273-
WHERE table_schema='" . $syslogdb_default . "' AND table_name='syslog'
274-
ORDER BY partition_ordinal_position");
309+
WHERE table_schema = ?
310+
AND table_name = ?
311+
ORDER BY partition_ordinal_position",
312+
array($syslogdb_default, $table)
313+
);
275314

276315
$days = read_config_option('syslog_retention');
277316

278-
syslog_debug("There are currently '" . sizeof($number_of_partitions) . "' Syslog Partitions, We will keep '$days' of them.");
317+
syslog_debug("There are currently '" . sizeof($number_of_partitions) . "' Syslog Partitions for '$table', We will keep '$days' of them.");
279318

280319
if ($days > 0) {
281320
$user_partitions = sizeof($number_of_partitions) - 1;
282321
if ($user_partitions >= $days) {
283-
$i = 0;
284-
while ($user_partitions > $days) {
285-
$oldest = $number_of_partitions[$i];
322+
$lock_name = hash('sha256', $syslogdb_default . 'syslog_partition_remove.' . $table);
286323

287-
cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "'", false, 'SYSTEM');
324+
try {
325+
syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
288326

289-
syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "'");
327+
$i = 0;
328+
while ($user_partitions > $days) {
329+
$oldest = $number_of_partitions[$i];
290330

291-
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']);
331+
cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "' from table '$table'", false, 'SYSTEM');
292332

293-
$i++;
294-
$user_partitions--;
295-
$syslog_deleted++;
333+
syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "' from table '$table'");
334+
335+
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']);
336+
337+
$i++;
338+
$user_partitions--;
339+
$syslog_deleted++;
340+
}
341+
} finally {
342+
syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
296343
}
297344
}
298345
}
@@ -303,21 +350,29 @@ function syslog_partition_remove($table) {
303350
function syslog_partition_check($table) {
304351
global $syslogdb_default;
305352

353+
if (!syslog_partition_table_allowed($table)) {
354+
return false;
355+
}
356+
306357
if (defined('SYSLOG_CONFIG')) {
307358
include(SYSLOG_CONFIG);
308359
}
309360

310361
/* find date of last partition */
311-
$last_part = syslog_db_fetch_cell("SELECT PARTITION_NAME
362+
$last_part = syslog_db_fetch_cell_prepared("SELECT PARTITION_NAME
312363
FROM `information_schema`.`partitions`
313-
WHERE table_schema='" . $syslogdb_default . "' AND table_name='syslog'
364+
WHERE table_schema = ?
365+
AND table_name = ?
314366
ORDER BY partition_ordinal_position DESC
315-
LIMIT 1,1;");
367+
LIMIT 1,1",
368+
array($syslogdb_default, $table)
369+
);
316370

317371
$lformat = str_replace('d', '', $last_part);
318372
$cformat = date('Ymd');
319373

320374
if ($cformat > $lformat) {
375+
321376
return true;
322377
} else {
323378
return false;
@@ -339,16 +394,9 @@ function syslog_remove_items($table, $uniqueID) {
339394
syslog_debug('-------------------------------------------------------------------------------------');
340395
syslog_debug('Processing Removal Rules...');
341396

342-
if ($table == 'syslog') {
343-
$rows = syslog_db_fetch_assoc("SELECT *
344-
FROM `" . $syslogdb_default . "`.`syslog_remove`
345-
WHERE enabled = 'on'
346-
AND id = $uniqueID");
347-
} else {
348-
$rows = syslog_db_fetch_assoc('SELECT *
349-
FROM `' . $syslogdb_default . '`.`syslog_remove`
350-
WHERE enabled="on"');
351-
}
397+
$rows = syslog_db_fetch_assoc('SELECT *
398+
FROM `' . $syslogdb_default . '`.`syslog_remove`
399+
WHERE enabled="on"');
352400

353401
syslog_debug(sprintf('Found %5s - Removal Rule(s) to process', cacti_sizeof($rows)));
354402

syslog_batch_transfer.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
display_help();
7171
exit(0);
7272
default:
73-
echo "ERROR: Invalid Argument: ($arg)\n\n";
73+
print "ERROR: Invalid Argument: ($arg)\n\n";
7474
display_help();
7575
exit(1);
7676
}
@@ -140,15 +140,15 @@ function display_version() {
140140
}
141141

142142
$info = plugin_syslog_version();
143-
echo "Syslog Batch Process, Version " . $info['version'] . ", " . COPYRIGHT_YEARS . "\n";
143+
print "Syslog Batch Process, Version " . $info['version'] . ", " . COPYRIGHT_YEARS . "\n";
144144
}
145145

146146
function display_help() {
147147
display_version();
148148

149-
echo "\nusage: syslog_batch_transfer.php [--debug|-d]\n\n";
150-
echo "The Syslog batch process script for Cacti Syslogging.\n";
151-
echo "This script removes old messages from main view.\n";
149+
print "\nusage: syslog_batch_transfer.php [--debug|-d]\n\n";
150+
print "The Syslog batch process script for Cacti Syslogging.\n";
151+
print "This script removes old messages from main view.\n";
152152
}
153153

154154

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* Integration tests for syslog partitioning logic.
7+
*
8+
* This test verifies the allowlist and creation logic for partitions.
9+
*/
10+
11+
// Load stubs
12+
require_once __DIR__ . '/../Helpers/GlobalStubs.php';
13+
14+
// Load the logic
15+
require_once __DIR__ . '/../../functions.php';
16+
17+
describe('Syslog Partitioning Integration', function () {
18+
test('syslog_partition_table_allowed() only allows known tables', function () {
19+
expect(syslog_partition_table_allowed('syslog'))->toBeTrue();
20+
expect(syslog_partition_table_allowed('syslog_removed'))->toBeTrue();
21+
22+
expect(syslog_partition_table_allowed('users'))->toBeFalse();
23+
expect(syslog_partition_table_allowed('syslog; DROP TABLE syslog'))->toBeFalse();
24+
expect(syslog_partition_table_allowed(''))->toBeFalse();
25+
});
26+
27+
test('syslog_partition_create() returns false for disallowed tables', function () {
28+
// This should return early without doing anything
29+
$result = syslog_partition_create('invalid_table');
30+
expect($result)->toBeFalse();
31+
});
32+
});

0 commit comments

Comments
 (0)