Route every conf writer through the guarded path
Eleven call sites wrote master.conf with tmp+rename and nothing else — no backup, no parse check, no audit — including the two toggles the UI uses most and the raw editor that installs a whole hand-edited file.
This commit is contained in:
@@ -38,13 +38,17 @@
|
||||
// is the difference between a rejected save and a silent, total outage, so a file that
|
||||
// does not parse is refused and the previous version is left untouched.
|
||||
//
|
||||
// The temp copy is created with tempnam() and always removed.
|
||||
// The candidate is never written next to the real conf and never under a predictable
|
||||
// name, so a failed validation cannot leave a stray file for a script to source.
|
||||
// Checked here via vv_conf_syntax_error() only so the editor can show bash's own
|
||||
// complaint with a line number. vv_conf_edit() checks again before installing; this one
|
||||
// is for the message, not the decision.
|
||||
//
|
||||
// The real write is atomic.
|
||||
// vv_write_conf_raw() writes .vv.tmp and rename()s, so a script sourcing the conf
|
||||
// during the save reads either the old file or the new one, never a half-written one.
|
||||
// The write goes through the one guarded conf path.
|
||||
// vv_conf_edit() takes an exclusive lock, copies the previous file into CONF_BACKUP_DIR,
|
||||
// re-checks the syntax, installs via .vv.tmp + rename() so a concurrent reader sees the
|
||||
// old file or the new one but never a half-written one, then sources the installed file
|
||||
// to prove it still loads and restores the backup if it does not. The whole-file nature
|
||||
// of this endpoint is why that matters most here: there is no key to verify, so a clean
|
||||
// source is the only assertion available.
|
||||
//
|
||||
// REQUEST
|
||||
// POST file=<allowed conf name> content=<full file text>
|
||||
@@ -54,10 +58,12 @@
|
||||
// {"ok":false,"error":"POST only"|"File not permitted"|"Syntax error: …"|"Failed to write file"}
|
||||
//
|
||||
// DEPENDS ON
|
||||
// include/config.php vv_get_conf_files(), vv_write_conf_raw(), CONF_DIR
|
||||
// include/config.php vv_get_conf_files(), CONF_DIR
|
||||
// include/confform.php vv_conf_syntax_error(), vv_conf_edit()
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
require_once dirname(__DIR__) . '/include/confform.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['ok' => false, 'error' => 'POST only']);
|
||||
@@ -75,22 +81,14 @@ if (!$file || !in_array($file, $allowed)) {
|
||||
}
|
||||
|
||||
// Every script sources these. A syntax error here takes the whole system down, so the
|
||||
// candidate is parsed before it is allowed to replace a working file.
|
||||
$check = tempnam(sys_get_temp_dir(), 'vvconf');
|
||||
if ($check !== false) {
|
||||
file_put_contents($check, $content);
|
||||
$out = []; $rc = 0;
|
||||
exec('bash -n ' . escapeshellarg($check) . ' 2>&1', $out, $rc);
|
||||
@unlink($check);
|
||||
if ($rc !== 0) {
|
||||
$msg = implode(' ', array_filter(array_map('trim', $out)));
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'error' => 'Syntax error: ' . str_replace($check, $file, $msg ?: 'conf does not parse'),
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
// candidate is parsed before it is allowed to replace a working file. Checked here as well as
|
||||
// inside vv_conf_edit() so the editor can show bash's own complaint; the write path only knows
|
||||
// whether to proceed, not what to tell the person typing.
|
||||
$syntax = vv_conf_syntax_error($content, $file);
|
||||
if ($syntax !== null) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Syntax error: ' . $syntax]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$ok = vv_write_conf_raw($file, $content);
|
||||
$ok = vv_conf_edit($file, fn() => $content, [], ['whole-file']);
|
||||
echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write file']);
|
||||
|
||||
Reference in New Issue
Block a user