config: keep Store mirrors authoritative
Debian Trixie package bundle / packages (push) Successful in 22m24s
Debian Trixie package bundle / packages (push) Successful in 22m24s
This commit is contained in:
@@ -96,10 +96,25 @@ def read_configuration(name: str) -> dict:
|
||||
|
||||
|
||||
def replace_configuration(name: str, value: dict) -> None:
|
||||
serialized = json.dumps(
|
||||
value, ensure_ascii=False, separators=(",", ":")
|
||||
)
|
||||
SMTP07.run(
|
||||
[SMTP07.ADMIN, "__config-replace", name],
|
||||
input_text=json.dumps(value, separators=(",", ":")),
|
||||
input_text=serialized,
|
||||
)
|
||||
if "/" not in name:
|
||||
path = Path("/etc/bongo/config.d") / name
|
||||
try:
|
||||
mirrored = json.loads(path.read_bytes())
|
||||
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
||||
raise SMTP26Error(
|
||||
f"configuration mirror {path} is not valid UTF-8 JSON"
|
||||
) from error
|
||||
if mirrored != value:
|
||||
raise SMTP26Error(
|
||||
f"configuration mirror {path} does not match the Store update"
|
||||
)
|
||||
|
||||
|
||||
def restart_bongo(host: str, port: int) -> None:
|
||||
|
||||
@@ -44,14 +44,13 @@ is the administrator- and configuration-management-friendly representation.
|
||||
`config check` validates those local files without changing the Store;
|
||||
`config sync` validates them and asks the running manager to apply them. The
|
||||
manager reconciles the same mirror at startup. It uses the Store object's
|
||||
existing Unix modification time as the common revision: a newer valid local
|
||||
file is an intentional emergency edit and is imported, while a newer Store
|
||||
document refreshes the file. Store mirror timestamps have zero nanoseconds,
|
||||
so an editor save in the same second is still recognized as a local edit; an
|
||||
otherwise equal timestamp belongs to the primary Store. `config sync` explicitly
|
||||
selects the validated local files when an administrator needs to force an
|
||||
emergency edit into the Store. Listener changes still require the affected
|
||||
agents to be restarted.
|
||||
existing Unix modification time as the common revision: a valid local file
|
||||
whose whole-second timestamp is strictly newer is an intentional emergency
|
||||
edit and is imported, while a newer or equally dated Store document refreshes
|
||||
the file. This tie-break deliberately keeps the Store authoritative; use
|
||||
`config sync` to force a valid local edit made within the same timestamp
|
||||
second into the Store. Store mirror timestamps have zero nanoseconds.
|
||||
Listener changes still require the affected agents to be restarted.
|
||||
|
||||
If a local mirror is missing but its operative Store document still exists,
|
||||
`config sync` reconstructs the file from the Store before committing the
|
||||
|
||||
@@ -28,12 +28,14 @@
|
||||
#include <nmlib.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <syslog.h>
|
||||
#include <time.h>
|
||||
@@ -273,12 +275,103 @@ ReadConfigurationInput(char **content, size_t *length)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
WriteConfigurationMirror(const char *name, const char *content, size_t length,
|
||||
uint64_t modified)
|
||||
{
|
||||
char path[XPL_MAX_PATH + 1];
|
||||
char temporary[XPL_MAX_PATH + 32];
|
||||
struct stat details;
|
||||
struct timespec times[2];
|
||||
const char *cursor = content;
|
||||
size_t remaining = length;
|
||||
int descriptor = -1;
|
||||
int directory = -1;
|
||||
int saved_error = 0;
|
||||
int result = 0;
|
||||
|
||||
temporary[0] = '\0';
|
||||
/* Alias Store documents are a compiled per-domain view of aliases.d,
|
||||
* rather than a one-to-one local document. Public alias operations
|
||||
* therefore update aliases.d transactionally before the manager compiles
|
||||
* that view. */
|
||||
if (strchr(name, '/'))
|
||||
return 1;
|
||||
if (snprintf(path, sizeof(path), "%s/%s", XPL_DEFAULT_CONFIG_DIR, name) >=
|
||||
(int)sizeof(path) ||
|
||||
snprintf(temporary, sizeof(temporary), "%s.tmp.XXXXXX", path) >=
|
||||
(int)sizeof(temporary)) {
|
||||
errno = ENAMETOOLONG;
|
||||
goto done;
|
||||
}
|
||||
if (lstat(path, &details) != 0 || !S_ISREG(details.st_mode)) {
|
||||
fprintf(stderr,
|
||||
"bongo-admin: configuration mirror is missing or unsafe: %s\n",
|
||||
path);
|
||||
errno = EINVAL;
|
||||
goto done;
|
||||
}
|
||||
times[0] = details.st_atim;
|
||||
times[1].tv_sec = (time_t)modified;
|
||||
times[1].tv_nsec = 0;
|
||||
if ((uint64_t)times[1].tv_sec != modified ||
|
||||
(descriptor = mkstemp(temporary)) < 0)
|
||||
goto done;
|
||||
while (remaining) {
|
||||
ssize_t written = write(descriptor, cursor, remaining);
|
||||
|
||||
if (written < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
goto done;
|
||||
}
|
||||
cursor += written;
|
||||
remaining -= (size_t)written;
|
||||
}
|
||||
if (fchmod(descriptor, details.st_mode & 0777) != 0 ||
|
||||
fchown(descriptor, details.st_uid, details.st_gid) != 0 ||
|
||||
futimens(descriptor, times) != 0 || fsync(descriptor) != 0)
|
||||
goto done;
|
||||
if (close(descriptor) != 0) {
|
||||
descriptor = -1;
|
||||
goto done;
|
||||
}
|
||||
descriptor = -1;
|
||||
if (rename(temporary, path) != 0)
|
||||
goto done;
|
||||
temporary[0] = '\0';
|
||||
result = 1;
|
||||
directory = open(XPL_DEFAULT_CONFIG_DIR,
|
||||
O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
if (directory < 0 || fsync(directory) != 0)
|
||||
fprintf(stderr,
|
||||
"bongo-admin: warning: cannot sync configuration directory: %s\n",
|
||||
strerror(errno));
|
||||
|
||||
done:
|
||||
saved_error = errno;
|
||||
if (descriptor >= 0)
|
||||
close(descriptor);
|
||||
if (directory >= 0)
|
||||
close(directory);
|
||||
if (!result && temporary[0])
|
||||
unlink(temporary);
|
||||
errno = saved_error;
|
||||
if (!result)
|
||||
fprintf(stderr,
|
||||
"bongo-admin: cannot update configuration mirror %s: %s\n",
|
||||
path, strerror(errno));
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
AccessConfiguration(const char *name, int replace)
|
||||
{
|
||||
BongoJsonNode *root = NULL;
|
||||
char *content = NULL;
|
||||
char *original = NULL;
|
||||
size_t length = 0;
|
||||
uint64_t modified = 0;
|
||||
int conn_started = 0;
|
||||
int msg_started = 0;
|
||||
int result = 1;
|
||||
@@ -309,11 +402,27 @@ AccessConfiguration(const char *name, int replace)
|
||||
goto done;
|
||||
}
|
||||
if (replace) {
|
||||
if (!strchr(name, '/') &&
|
||||
(!NMAPReadConfigFile(name, &original) || !original)) {
|
||||
fprintf(stderr,
|
||||
"bongo-admin: cannot snapshot configuration %s\n", name);
|
||||
goto done;
|
||||
}
|
||||
if (!NMAPReplaceConfigFile(name, content, length)) {
|
||||
fprintf(stderr, "bongo-admin: cannot replace configuration %s\n",
|
||||
name);
|
||||
goto done;
|
||||
}
|
||||
if (!strchr(name, '/') &&
|
||||
(!NMAPReadConfigFileModified(name, &modified) ||
|
||||
!WriteConfigurationMirror(name, content, length, modified))) {
|
||||
if (!original ||
|
||||
!NMAPReplaceConfigFile(name, original, strlen(original)))
|
||||
fprintf(stderr,
|
||||
"bongo-admin: CRITICAL: cannot roll back configuration %s\n",
|
||||
name);
|
||||
goto done;
|
||||
}
|
||||
openlog("bongo-admin", LOG_PID, LOG_AUTHPRIV);
|
||||
syslog(LOG_NOTICE, "administrator replaced Bongo configuration %s",
|
||||
name);
|
||||
@@ -334,6 +443,8 @@ AccessConfiguration(const char *name, int replace)
|
||||
done:
|
||||
if (root)
|
||||
BongoJsonNodeFree(root);
|
||||
if (original)
|
||||
MemFree(original);
|
||||
if (content) {
|
||||
if (replace)
|
||||
free(content);
|
||||
|
||||
@@ -834,9 +834,7 @@ SyncConfigurationDocument(const char *storeName, const char *path)
|
||||
result = SetConfigurationFileTime(path, &details, storeModified);
|
||||
goto done;
|
||||
}
|
||||
if ((uint64_t)details.st_mtime > storeModified ||
|
||||
((uint64_t)details.st_mtime == storeModified &&
|
||||
details.st_mtim.tv_nsec != 0)) {
|
||||
if ((uint64_t)details.st_mtime > storeModified) {
|
||||
if (!SyncConfigurationContent(storeName, path, local) ||
|
||||
!NMAPReadConfigFileModified(storeName, &storeModified))
|
||||
goto done;
|
||||
|
||||
Reference in New Issue
Block a user