Skip to content

Commit bbb81d1

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 8b13997 commit bbb81d1

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
@@ -207,32 +207,63 @@ function syslog_partition_manage() {
207207
return $syslog_deleted;
208208
}
209209

210+
/**
211+
* syslog_partition_table_allowed - validate that the table being partitioned
212+
* is in our approved list.
213+
*
214+
* @param (string) The table name
215+
*
216+
* @return (bool) True if allowed, False otherwise
217+
*/
218+
function syslog_partition_table_allowed($table) {
219+
if (in_array($table, array('syslog', 'syslog_removed'), true)) {
220+
return (bool)preg_match('/^[a-z_]+$/', $table);
221+
}
222+
223+
return false;
224+
}
225+
210226
/**
211227
* This function will create a new partition for the specified table.
212228
*/
213229
function syslog_partition_create($table) {
214230
global $syslogdb_default;
215231

232+
if (!syslog_partition_table_allowed($table)) {
233+
return false;
234+
}
235+
216236
/* determine the format of the table name */
217237
$time = time();
218238
$cformat = 'd' . date('Ymd', $time);
219239
$lnow = date('Y-m-d', $time+86400);
220240

221-
$exists = syslog_db_fetch_row("SELECT *
241+
$exists = syslog_db_fetch_row_prepared("SELECT *
222242
FROM `information_schema`.`partitions`
223-
WHERE table_schema='" . $syslogdb_default . "'
224-
AND partition_name='" . $cformat . "'
225-
AND table_name='syslog'
226-
ORDER BY partition_ordinal_position");
243+
WHERE table_schema = ?
244+
AND partition_name = ?
245+
AND table_name = ?
246+
ORDER BY partition_ordinal_position",
247+
array($syslogdb_default, $cformat, $table)
248+
);
227249

228250
if (!cacti_sizeof($exists)) {
229-
cacti_log("SYSLOG: Creating new partition '$cformat'", false, 'SYSTEM');
251+
$lock_name = hash('sha256', $syslogdb_default . 'syslog_partition_create.' . $table);
252+
253+
try {
254+
syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
255+
256+
cacti_log("SYSLOG: Creating new partition '$cformat' for table '$table'", false, 'SYSTEM');
230257

231-
syslog_debug("Creating new partition '$cformat'");
258+
syslog_debug("Creating new partition '$cformat' for table '$table'");
232259

233-
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO (
234-
PARTITION $cformat VALUES LESS THAN (TO_DAYS('$lnow')),
235-
PARTITION dMaxValue VALUES LESS THAN MAXVALUE)");
260+
/* MySQL does not support parameter binding for DDL statements */
261+
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO (
262+
PARTITION $cformat VALUES LESS THAN (TO_DAYS('$lnow')),
263+
PARTITION dMaxValue VALUES LESS THAN MAXVALUE)");
264+
} finally {
265+
syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
266+
}
236267
}
237268
}
238269

@@ -242,32 +273,48 @@ function syslog_partition_create($table) {
242273
function syslog_partition_remove($table) {
243274
global $syslogdb_default;
244275

276+
if (!syslog_partition_table_allowed($table)) {
277+
cacti_log("SYSLOG ERROR: Attempt to remove partitions from disallowed table '$table'", false, 'SYSTEM');
278+
return 0;
279+
}
280+
245281
$syslog_deleted = 0;
246-
$number_of_partitions = syslog_db_fetch_assoc("SELECT *
282+
$number_of_partitions = syslog_db_fetch_assoc_prepared("SELECT *
247283
FROM `information_schema`.`partitions`
248-
WHERE table_schema='" . $syslogdb_default . "' AND table_name='syslog'
249-
ORDER BY partition_ordinal_position");
284+
WHERE table_schema = ?
285+
AND table_name = ?
286+
ORDER BY partition_ordinal_position",
287+
array($syslogdb_default, $table)
288+
);
250289

251290
$days = read_config_option('syslog_retention');
252291

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

255294
if ($days > 0) {
256295
$user_partitions = sizeof($number_of_partitions) - 1;
257296
if ($user_partitions >= $days) {
258-
$i = 0;
259-
while ($user_partitions > $days) {
260-
$oldest = $number_of_partitions[$i];
297+
$lock_name = hash('sha256', $syslogdb_default . 'syslog_partition_remove.' . $table);
261298

262-
cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "'", false, 'SYSTEM');
299+
try {
300+
syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
263301

264-
syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "'");
302+
$i = 0;
303+
while ($user_partitions > $days) {
304+
$oldest = $number_of_partitions[$i];
265305

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

268-
$i++;
269-
$user_partitions--;
270-
$syslog_deleted++;
308+
syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "' from table '$table'");
309+
310+
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']);
311+
312+
$i++;
313+
$user_partitions--;
314+
$syslog_deleted++;
315+
}
316+
} finally {
317+
syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
271318
}
272319
}
273320
}
@@ -278,21 +325,29 @@ function syslog_partition_remove($table) {
278325
function syslog_partition_check($table) {
279326
global $syslogdb_default;
280327

328+
if (!syslog_partition_table_allowed($table)) {
329+
return false;
330+
}
331+
281332
if (defined('SYSLOG_CONFIG')) {
282333
include(SYSLOG_CONFIG);
283334
}
284335

285336
/* find date of last partition */
286-
$last_part = syslog_db_fetch_cell("SELECT PARTITION_NAME
337+
$last_part = syslog_db_fetch_cell_prepared("SELECT PARTITION_NAME
287338
FROM `information_schema`.`partitions`
288-
WHERE table_schema='" . $syslogdb_default . "' AND table_name='syslog'
339+
WHERE table_schema = ?
340+
AND table_name = ?
289341
ORDER BY partition_ordinal_position DESC
290-
LIMIT 1,1;");
342+
LIMIT 1,1",
343+
array($syslogdb_default, $table)
344+
);
291345

292346
$lformat = str_replace('d', '', $last_part);
293347
$cformat = date('Ymd');
294348

295349
if ($cformat > $lformat) {
350+
296351
return true;
297352
} else {
298353
return false;
@@ -314,16 +369,9 @@ function syslog_remove_items($table, $uniqueID) {
314369
syslog_debug('-------------------------------------------------------------------------------------');
315370
syslog_debug('Processing Removal Rules...');
316371

317-
if ($table == 'syslog') {
318-
$rows = syslog_db_fetch_assoc("SELECT *
319-
FROM `" . $syslogdb_default . "`.`syslog_remove`
320-
WHERE enabled = 'on'
321-
AND id = $uniqueID");
322-
} else {
323-
$rows = syslog_db_fetch_assoc('SELECT *
324-
FROM `' . $syslogdb_default . '`.`syslog_remove`
325-
WHERE enabled="on"');
326-
}
372+
$rows = syslog_db_fetch_assoc('SELECT *
373+
FROM `' . $syslogdb_default . '`.`syslog_remove`
374+
WHERE enabled="on"');
327375

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

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)