Merge branch 'feat/parity-b2' into feat/parity-next

This commit is contained in:
2026-09-22 22:06:25 +02:00
5 changed files with 411 additions and 24 deletions
+131 -1
View File
@@ -33,6 +33,108 @@ static bool key_equals(const char* key, const char* canonical) {
return strcasecmp(key, canonical) == 0;
}
/* True when `key` matches one of the NUL-terminated names in `list`. */
static bool key_in_list(const char* key, const char* const* list, size_t count) {
for (size_t i = 0; i < count; i++) {
if (strcasecmp(key, list[i]) == 0)
return true;
}
return false;
}
/* rsync 3.4.1 rsyncd.conf GLOBAL keys accepted in the pre-module section that
* have no FastSync equivalent. They are recognized and documented as inert:
* accepting a real rsync config must not fail on a logging/process key, but a
* silently-reinterpreted key is never invented. `pidfile`/`logfile` are the
* compact --dparam spellings rsync documents. The same list is used by the
* `--dparam` dispatch (apply_global_key), so there is a single impl. */
static const char* const kRsyncInertGlobalKeys[] = {
"pid file",
"pidfile",
"log file",
"logfile",
"socket options",
"sockopts",
"listen backlog",
"syslog facility",
"syslog tag",
"log format",
"use chroot",
"uid",
"gid",
"timeout",
"max verbosity",
"min verbosity",
"lock file",
"transfer logging",
"strict modes",
"reverse lookup",
"forward lookup",
"ignore errors",
"ignore nonreadable",
"dont compress",
};
/* rsync 3.4.1 rsyncd.conf MODULE keys accepted in a [module] section that have
* no FastSync equivalent (accepted-and-documented inert). Keys with a FastSync
* meaning (`path`, `read only`, `auth users`, `max connections`,
* `hosts allow`/`hosts deny`, `client owner`) are handled by apply_module_key
* before this list is consulted. Security-relevant keys (`exclude`, `filter`,
* `secrets file`, `refuse options`, ...) are inert, so a daemon-side filter or
* rsync secrets file is NOT enforced: see RSYNC_COMPAT.md for the residual. */
static const char* const kRsyncInertModuleKeys[] = {
"comment",
"use chroot",
"daemon chroot",
"uid",
"gid",
"daemon uid",
"daemon gid",
"exclude",
"include",
"exclude from",
"include from",
"filter",
"max verbosity",
"min verbosity",
"lock file",
"transfer logging",
"log file",
"log format",
"syslog facility",
"syslog tag",
"timeout",
"secrets file",
"auth digest",
"strict modes",
"numeric ids",
"fake super",
"munge symlinks",
"write only",
"list",
"dont compress",
"charset",
"refuse options",
"incoming chmod",
"outgoing chmod",
"open noatime",
"max size",
"min size",
"temp dir",
"pre-xfer exec",
"post-xfer exec",
"name converter",
"proxy protocol",
"proxy protocol hosts",
"reverse lookup",
"forward lookup",
"ignore errors",
"ignore nonreadable",
};
#define kRsyncInertGlobalCount (sizeof(kRsyncInertGlobalKeys) / sizeof(kRsyncInertGlobalKeys[0]))
#define kRsyncInertModuleCount (sizeof(kRsyncInertModuleKeys) / sizeof(kRsyncInertModuleKeys[0]))
static bool parse_bool_value(const char* value, bool* out) {
if (strcasecmp(value, "yes") == 0 || strcasecmp(value, "true") == 0 || strcmp(value, "1") == 0) {
*out = true;
@@ -250,6 +352,7 @@ DaemonConf* daemon_conf_create(void) {
if (!conf)
return NULL;
conf->global.port = DAEMON_CONF_DEFAULT_PORT;
conf->global.read_only_default = false;
conf->global.max_connections = DAEMON_CONF_DEFAULT_MAX_CONNECTIONS;
conf->global.auth_failure_delay_ms = DAEMON_CONF_DEFAULT_AUTH_FAILURE_DELAY_MS;
conf->global.max_connections_per_host = DAEMON_CONF_DEFAULT_MAX_CONNECTIONS_PER_HOST;
@@ -324,7 +427,7 @@ static bool apply_global_key(DaemonConf* conf, char* key, const char* value, boo
char* err, size_t err_size) {
if (key_equals(key, "port"))
return store_port(&conf->global.port, value, err, err_size);
if (key_equals(key, "motd file")) {
if (key_equals(key, "motd file") || key_equals(key, "motdfile")) {
if (!store_string(&conf->global.motd_file, value)) {
set_error(err, err_size, "out of memory parsing 'motd file'");
return false;
@@ -338,6 +441,25 @@ static bool apply_global_key(DaemonConf* conf, char* key, const char* value, boo
}
return true;
}
/* rsync allows the `read only` module key in the global section as the
* default for modules defined after it. Map it to that default (a later
* --dparam re-applies it to modules that did not set their own value) so a
* global `read only = yes` cannot be silently dropped into a writable
* default. */
if (key_equals(key, "read only")) {
bool parsed;
if (!parse_bool_value(value, &parsed)) {
set_error(err, err_size, "global 'read only' must be yes/no (or true/false/1/0), got '%s'",
value);
return false;
}
conf->global.read_only_default = parsed;
for (int i = 0; i < conf->module_count; i++) {
if (!conf->modules[i].read_only_explicit)
conf->modules[i].read_only = parsed;
}
return true;
}
if (key_equals(key, "max connections"))
return store_max_connections(&conf->global.max_connections, value, NULL, err, err_size);
if (key_equals(key, "max connections per host"))
@@ -360,6 +482,9 @@ static bool apply_global_key(DaemonConf* conf, char* key, const char* value, boo
if (key_equals(key, "hosts deny"))
return store_host_list(&conf->global.hosts_deny, &conf->global.hosts_deny_count, value,
"hosts deny", NULL, replace_hosts, err, err_size);
/* A recognized rsync global key with no FastSync equivalent loads inert. */
if (key_in_list(key, kRsyncInertGlobalKeys, kRsyncInertGlobalCount))
return true;
set_error(err, err_size, "unknown global key '%s'", key);
return false;
}
@@ -388,6 +513,7 @@ static bool apply_module_key(DaemonModule* module, char* key, char* value, char*
return false;
}
module->read_only = parsed;
module->read_only_explicit = true;
return true;
}
if (key_equals(key, "client owner")) {
@@ -457,6 +583,9 @@ static bool apply_module_key(DaemonModule* module, char* key, char* value, char*
if (key_equals(key, "hosts deny"))
return store_host_list(&module->hosts_deny, &module->hosts_deny_count, value, "hosts deny",
module->name, false, err, err_size);
/* A recognized rsync module key with no FastSync equivalent loads inert. */
if (key_in_list(key, kRsyncInertModuleKeys, kRsyncInertModuleCount))
return true;
set_error(err, err_size, "unknown key '%s' in module '%s'", key, module->name);
return false;
}
@@ -507,6 +636,7 @@ static int open_module(DaemonConf* conf, int* current_module, const char* name,
}
conf->modules = grown;
memset(&conf->modules[conf->module_count], 0, sizeof(DaemonModule));
conf->modules[conf->module_count].read_only = conf->global.read_only_default;
conf->modules[conf->module_count].name = str_dup(name);
if (!conf->modules[conf->module_count].name) {
set_error(err, err_size, "out of memory adding module '%s'", name);
+33 -13
View File
@@ -17,7 +17,16 @@
* DAEMON_CONF_MAX_LINE all fail the whole load with a clear, line-numbered
* error instead of being silently ignored. This keeps a typo from silently
* changing what a module serves.
*/
*
* rsync compatibility: to reduce the divergence from rsync 3.4.1's rsyncd.conf
* grammar, the parser also ACCEPTS the common rsync GLOBAL and MODULE keys.
* Keys with a FastSync equivalent are mapped onto it (the native spellings are
* unchanged). Keys with no FastSync equivalent are accepted and documented as
* inert (they load successfully but have no effect) rather than failing the
* whole config; the accepted inert set is listed in kRsyncInertGlobalKeys /
* kRsyncInertModuleKeys in daemon_conf.c and in RSYNC_COMPAT.md. A key
* outside both the FastSync-native grammar and the recognized rsync subset is
* still rejected as unknown. */
/* A daemon module's configured root is used exactly like the standalone
* server's --destination-root: the daemon confines every connection that
@@ -42,15 +51,20 @@
* store refuses (fail closed) rather than falling open; see server.c. Auth is
* never bypassed by ignoring the list. */
typedef struct DaemonModule {
char* name; /* module name, as the client requests it */
char* path; /* module root (daemon-side authorized root) */
bool read_only; /* `read only = yes/no`; default no */
bool client_owner; /* `client owner = yes/no`; default no. Per-module opt-in
that lets this module's clients choose ownership
(--numeric-ids/--chown/--usermap/--groupmap/--fake-super/
--copy-as) and request explicit --super super-user
activities. Without it the daemon refuses all of them. */
char** auth_users; /* `auth users = a,b`; Wave B credential list */
char* name; /* module name, as the client requests it */
char* path; /* module root (daemon-side authorized root) */
bool read_only; /* `read only = yes/no`; defaults to the global `read only`
default (rsync allows it in the global section), which is
itself default no */
bool read_only_explicit; /* set when this module set its own `read only`, so a
later global default (from a `--dparam read only=`)
does not override it */
bool client_owner; /* `client owner = yes/no`; default no. Per-module opt-in
that lets this module's clients choose ownership
(--numeric-ids/--chown/--usermap/--groupmap/--fake-super/
--copy-as) and request explicit --super super-user
activities. Without it the daemon refuses all of them. */
char** auth_users; /* `auth users = a,b`; Wave B credential list */
int auth_user_count;
/* `max connections = N` (optional per-module cap). 0 means unlimited. The
* per-connection child records the selected module in the shared registry
@@ -69,6 +83,9 @@ typedef struct DaemonConfGlobals {
int port; /* `port`, default DAEMON_CONF_DEFAULT_PORT (873) */
char* motd_file; /* `motd file`, may be NULL */
char* address; /* `address` (optional bind address), may be NULL */
bool read_only_default; /* global `read only` default for modules defined
after it (rsync allows the module key in the
global section); default no */
int max_connections; /* `max connections`, default
DAEMON_CONF_DEFAULT_MAX_CONNECTIONS (100) */
int auth_failure_delay_ms; /* `auth failure delay`, milliseconds; default
@@ -152,10 +169,13 @@ const DaemonModule* daemon_conf_find_module(const DaemonConf* conf, const char*
bool daemon_module_name_valid(const char* name);
/* Parse one --dparam=KEY=VALUE (or "--dparam KEY=VALUE") override string and
* apply it to the global keys only. Keys are case-insensitive and limited to
* the global keys defined by the grammar (port, motd file, address,
* apply it to the global keys only. Keys are case-insensitive and cover the
* global keys defined by the grammar (port, motd file, address, read only,
* max connections, max connections per host, auth failure delay,
* auth lockout threshold, auth lockout duration, hosts allow, hosts deny).
* auth lockout threshold, auth lockout duration, hosts allow, hosts deny) plus
* the recognized inert rsync global keys and the compact rsync spellings
* (`motdfile`, `pidfile`, `logfile`). Applying `read only` sets the global
* default and re-applies it to every module that did not set its own value.
* Returns 0 on success, -1 on error (err filled). */
int daemon_conf_apply_dparam(DaemonConf* conf, const char* assignment, char* err, size_t err_size);