fix(daemon): close ACL fail-opens (v4-mapped peers, invalid patterns) and cap auth delay
This commit is contained in:
@@ -506,8 +506,9 @@ A `[module]` may also set `max connections` (parsed and validated but not
|
|||||||
enforced per module — the global cap applies to the whole listener) and its own
|
enforced per module — the global cap applies to the whole listener) and its own
|
||||||
`hosts allow`/`hosts deny`.
|
`hosts allow`/`hosts deny`.
|
||||||
|
|
||||||
Host patterns are `*` (match all), IPv4/IPv6 literals, IPv4/IPv6 CIDR
|
Host patterns are `*` (match all), IPv4/IPv6 literals, or IPv4/IPv6 CIDR
|
||||||
(`10.0.0.0/8`, `2001:db8::/32`), or hostname globs (`*.example.com`). A matching
|
(`10.0.0.0/8`, `2001:db8::/32`). Hostnames are not resolved, so hostname globs
|
||||||
|
are rejected at parse time rather than silently never matching. A matching
|
||||||
`hosts deny` rejects; if any `hosts allow` entries exist, a peer matching none of
|
`hosts deny` rejects; if any `hosts allow` entries exist, a peer matching none of
|
||||||
them is rejected; deny takes precedence over allow. The global list is checked
|
them is rejected; deny takes precedence over allow. The global list is checked
|
||||||
before the module list, before authentication, and the connecting peer address
|
before the module list, before authentication, and the connecting peer address
|
||||||
|
|||||||
+3
-3
@@ -635,9 +635,9 @@ now transmits targets (the prior behavior was broken/partial); its status moved
|
|||||||
|
|
||||||
**Daemon Mode notes (Wave A protocol 2.15.0; A7 auth protocol 2.19.0; MOTD no bump):** FastSync daemon mode is supported in FastSync's own protocol/config grammar, not rsync's SMB/daemon option encoding.
|
**Daemon Mode notes (Wave A protocol 2.15.0; A7 auth protocol 2.19.0; MOTD no bump):** FastSync daemon mode is supported in FastSync's own protocol/config grammar, not rsync's SMB/daemon option encoding.
|
||||||
|
|
||||||
- **Config grammar** (`fastsyncd.conf`): line-based; an implicit global section first, then `[module]` sections. Keys are case-insensitive, values are trimmed and may be wrapped in one layer of double quotes (`path = "/srv/my dir"`). `#` and `;` at the start of a line (after leading whitespace) are full-line comments; inline comments and `\` continuations are not supported. Lines are bounded (4096 chars). Global keys: `port` (default 873), `motd file` (the daemon sends its bounded, escaped content to a client after the module gate/auth accepts, unless the client passes `--no-motd`), `address` (optional bind address), `max connections` (positive integer cap on concurrent connections, default 100; 0/negative/garbage is a parse error), `auth failure delay` (milliseconds to sleep after a failed authentication, default 500; 0 disables, capped at 60000), `hosts allow` and `hosts deny` (comma- and/or whitespace-separated host access patterns — see the host access control note below). Module keys: `path` (required; the daemon-side authorized root for that module), `read only` (yes/no/true/false/1/0, default no), `client owner` (yes/no/true/false/1/0, default no; opts the module into client-chosen ownership — see below), `auth users` (comma list), `max connections` (optional per-module cap; parsed and stored but **not enforced** — the global cap applies to the whole listener), `hosts allow`/`hosts deny` (per-module host access lists). **Unknown keys and malformed lines are parse-and-reject errors** (never silently ignored), so a typo cannot change what a module serves.
|
- **Config grammar** (`fastsyncd.conf`): line-based; an implicit global section first, then `[module]` sections. Keys are case-insensitive, values are trimmed and may be wrapped in one layer of double quotes (`path = "/srv/my dir"`). `#` and `;` at the start of a line (after leading whitespace) are full-line comments; inline comments and `\` continuations are not supported. Lines are bounded (4096 chars). Global keys: `port` (default 873), `motd file` (the daemon sends its bounded, escaped content to a client after the module gate/auth accepts, unless the client passes `--no-motd`), `address` (optional bind address), `max connections` (positive integer cap on concurrent connections, default 100; 0/negative/garbage is a parse error), `auth failure delay` (milliseconds to sleep after a failed authentication, default 500; 0 disables, capped at 5000), `hosts allow` and `hosts deny` (comma- and/or whitespace-separated host access patterns — see the host access control note below). Module keys: `path` (required; the daemon-side authorized root for that module), `read only` (yes/no/true/false/1/0, default no), `client owner` (yes/no/true/false/1/0, default no; opts the module into client-chosen ownership — see below), `auth users` (comma list), `max connections` (optional per-module cap; parsed and stored but **not enforced** — the global cap applies to the whole listener), `hosts allow`/`hosts deny` (per-module host access lists). **Unknown keys and malformed lines are parse-and-reject errors** (never silently ignored), so a typo cannot change what a module serves.
|
||||||
- **Host access control (`hosts allow`/`hosts deny`):** both keys accept a comma- and/or whitespace-separated list of patterns and may appear globally and/or per module (multiple lines append). Supported patterns are `*` (match all), an IPv4 or IPv6 literal (`10.0.0.1`, `2001:db8::1`), an IPv4/IPv6 CIDR (`10.0.0.0/8`, `2001:db8::/32`), and a simple glob (`*.example.com`; globs are matched case-insensitively against the peer string, so a numeric peer never matches a hostname glob). rsync-like semantics: a matching `hosts deny` rejects; if any `hosts allow` entries exist, a peer matching none of them is rejected; deny takes precedence over allow. The daemon enforces the global list first, then the selected module's list, **before authentication** in `server_module_gate`, with an audit log line naming the peer, the module and the outcome. The numeric peer address is obtained with `getpeername`+`inet_ntop` (`utils_fd_peer_ip`, handling both address families); when it cannot be obtained a module with any ACL fails closed (refused), while an ACL-free module continues and logs at debug. A malformed pattern (e.g. an out-of-range CIDR prefix) is a parse error at load time.
|
- **Host access control (`hosts allow`/`hosts deny`):** both keys accept a comma- and/or whitespace-separated list of patterns and may appear globally and/or per module (multiple config-file lines append; a `--dparam` override replaces). Supported patterns are `*` (match all), an IPv4 or IPv6 literal (`10.0.0.1`, `2001:db8::1`), and an IPv4/IPv6 CIDR (`10.0.0.0/8`, `2001:db8::/32`). Hostname patterns are **not** supported: because the peer is always a numeric address and no reverse DNS is performed, a hostname/glob pattern would silently never match, so it is rejected at load time (fail-closed) instead of being accepted as a dead rule. An IPv4 peer on a dual-stack IPv6 listener is normalized from its `::ffff:a.b.c.d` form so IPv4 patterns match it. rsync-like semantics: a matching `hosts deny` rejects; if any `hosts allow` entries exist, a peer matching none of them is rejected; deny takes precedence over allow. The daemon enforces the global list first, then the selected module's list, **before authentication** in `server_module_gate`, with an audit log line naming the peer, the module and the outcome. The numeric peer address is obtained with `getpeername`+`inet_ntop` (`utils_fd_peer_ip`, handling both address families); when it cannot be obtained a module with any ACL fails closed (refused), while an ACL-free module continues and logs at debug. A malformed pattern (e.g. an out-of-range CIDR prefix) is a parse error at load time.
|
||||||
- **Connection cap and auth throttle:** the global `max connections` key (default 100) is plumbed into the listener (`transport_tcp.c`), which rejects a connection once the accept-loop parent's active-child count reaches it; the IPv4/IPv6 peer is logged for every accepted connection. The optional per-module `max connections` key is parsed and validated but **not enforced** (connections are counted in the parent before the client's module is known); the daemon logs a startup warning for any module that sets it. On a failed authentication the per-connection child sleeps the global `auth failure delay` (default 500 ms, 0 disables, capped at 60000) via `nanosleep` before the connection closes, rate-limiting online guessing without delaying a success.
|
- **Connection cap and auth throttle:** the global `max connections` key (default 100) is plumbed into the listener (`transport_tcp.c`), which rejects a connection once the accept-loop parent's active-child count reaches it; the IPv4/IPv6 peer is logged for every accepted connection. The optional per-module `max connections` key is parsed and validated but **not enforced** (connections are counted in the parent before the client's module is known); the daemon logs a startup warning for any module that sets it. On a failed authentication the per-connection child sleeps the global `auth failure delay` (default 500 ms, 0 disables, capped at 5000) via `nanosleep` before the connection closes, rate-limiting online guessing without delaying a success.
|
||||||
- **Module selection & confinement:** the client requests a module with an rsync-style `host::module[/path]` destination. The module name crosses the wire as a trailing string on the config frame (bumping `PROTOCOL_VERSION` 2.14.0 → 2.15.0; the bump is required because the config-frame layout changed and the strict same-version handshake is what prevents a peer from desynchronizing on the new trailing field). The daemon looks the module up in ITS OWN config and uses the module's `path` as the authorized root through the exact same `configure_authorization` confinement the standalone server applies to `--destination-root` (`file_open_secure_parent`, `has_path_traversal`, `path_is_within`); the client never supplies the root, every client-chosen-ownership/super-user request is refused unless the module declares `client owner = yes` (the daemon's per-module opt-in, see below), and the operator `--no-super` veto forces super-user activities off for every daemon connection. The client's `/path` part is relative inside the module and is rejected if absolute or if it contains `..`. Unknown modules are refused before any data moves (the run fails cleanly at the config handshake). An absolute destination and a module request against a non-daemon server are also refused.
|
- **Module selection & confinement:** the client requests a module with an rsync-style `host::module[/path]` destination. The module name crosses the wire as a trailing string on the config frame (bumping `PROTOCOL_VERSION` 2.14.0 → 2.15.0; the bump is required because the config-frame layout changed and the strict same-version handshake is what prevents a peer from desynchronizing on the new trailing field). The daemon looks the module up in ITS OWN config and uses the module's `path` as the authorized root through the exact same `configure_authorization` confinement the standalone server applies to `--destination-root` (`file_open_secure_parent`, `has_path_traversal`, `path_is_within`); the client never supplies the root, every client-chosen-ownership/super-user request is refused unless the module declares `client owner = yes` (the daemon's per-module opt-in, see below), and the operator `--no-super` veto forces super-user activities off for every daemon connection. The client's `/path` part is relative inside the module and is rejected if absolute or if it contains `..`. Unknown modules are refused before any data moves (the run fails cleanly at the config handshake). An absolute destination and a module request against a non-daemon server are also refused.
|
||||||
- **`client owner` (client-chosen-ownership opt-in):** by default a daemon module refuses every request that would let the client pick an owner or ask for super-user activities — `--numeric-ids`, `--chown`, `--usermap`/`--groupmap`, `--fake-super`, `--copy-as`, and an explicit `--super` — at the config handshake (before `STATUS_OK`), because a daemon has no per-module opt-in for client-chosen ownership and any anonymous client could otherwise force arbitrary owner ids inside the module root. `client owner = yes` opts a single module in, allowing those requests within that module's root (the standalone listener and the SSH `--stdio` server always honor them for their single operator-authorized root). Without the opt-in the daemon also forces super-user **device** activity off for that connection — char/block device-node creation (`--devices`) and `--write-devices` — even under the default `AUTO` mode, so a non-opted module can never be made to `mknod` or write a raw device; those entries are skipped (not refused) so an ordinary `-a` push still succeeds without device nodes. The opt-in does **not** lift the privilege requirement: `--copy-as` still needs a root receiver, and the operator `--no-super` veto still forces super-user activities off for every connection. The daemon logs a prominent startup warning for each `client owner = yes` module so the operator's deliberate choice is visible.
|
- **`client owner` (client-chosen-ownership opt-in):** by default a daemon module refuses every request that would let the client pick an owner or ask for super-user activities — `--numeric-ids`, `--chown`, `--usermap`/`--groupmap`, `--fake-super`, `--copy-as`, and an explicit `--super` — at the config handshake (before `STATUS_OK`), because a daemon has no per-module opt-in for client-chosen ownership and any anonymous client could otherwise force arbitrary owner ids inside the module root. `client owner = yes` opts a single module in, allowing those requests within that module's root (the standalone listener and the SSH `--stdio` server always honor them for their single operator-authorized root). Without the opt-in the daemon also forces super-user **device** activity off for that connection — char/block device-node creation (`--devices`) and `--write-devices` — even under the default `AUTO` mode, so a non-opted module can never be made to `mknod` or write a raw device; those entries are skipped (not refused) so an ordinary `-a` push still succeeds without device nodes. The opt-in does **not** lift the privilege requirement: `--copy-as` still needs a root receiver, and the operator `--no-super` veto still forces super-user activities off for every connection. The daemon logs a prominent startup warning for each `client owner = yes` module so the operator's deliberate choice is visible.
|
||||||
- **`read only` safe default:** every network transfer FastSync currently supports is a push that writes under the module root, so a `read only` module refuses the connection (clear server log "module is read only"; the client exits non-zero, nothing is transferred). A future pull/list operation can be opened up when it exists; the knob is already stored.
|
- **`read only` safe default:** every network transfer FastSync currently supports is a push that writes under the module root, so a `read only` module refuses the connection (clear server log "module is read only"; the client exits non-zero, nothing is transferred). A future pull/list operation can be opened up when it exists; the knob is already stored.
|
||||||
|
|||||||
+29
-15
@@ -91,25 +91,39 @@ static bool parse_cidr(const char* cidr, int* prefix_out, uint8_t* bytes, int* f
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A host pattern is valid when it is non-empty and, when it contains a '/', its
|
/* A host pattern is valid when it is `*`, a valid IPv4/IPv6 literal, or a valid
|
||||||
* address/prefix halves parse as a CIDR. Literals, `*` and globs are accepted
|
* CIDR. Peer addresses reaching the matcher are always numeric, so hostname
|
||||||
* as-is (a glob only ever matches a peer of the same shape). */
|
* globs are rejected at parse time: accepting one would create a deny rule that
|
||||||
|
* silently never matches (fail-open). */
|
||||||
static bool host_pattern_valid(const char* pattern) {
|
static bool host_pattern_valid(const char* pattern) {
|
||||||
if (!pattern || *pattern == '\0')
|
if (!pattern || *pattern == '\0')
|
||||||
return false;
|
return false;
|
||||||
if (!strchr(pattern, '/'))
|
if (strcmp(pattern, "*") == 0)
|
||||||
return true;
|
return true;
|
||||||
|
if (strchr(pattern, '/')) {
|
||||||
uint8_t bytes[16];
|
uint8_t bytes[16];
|
||||||
int prefix;
|
int prefix;
|
||||||
int family;
|
int family;
|
||||||
return parse_cidr(pattern, &prefix, bytes, &family);
|
return parse_cidr(pattern, &prefix, bytes, &family);
|
||||||
}
|
}
|
||||||
|
struct in_addr v4;
|
||||||
|
struct in6_addr v6;
|
||||||
|
return inet_pton(AF_INET, pattern, &v4) == 1 || inet_pton(AF_INET6, pattern, &v6) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Append every comma- and/or whitespace-separated host pattern in `value` to
|
/* Append every comma- and/or whitespace-separated host pattern in `value` to
|
||||||
* the heap-owned list. Returns false (err filled) on an invalid pattern or an
|
* the heap-owned list (or replace the list when `replace` is set, which --dparam
|
||||||
* allocation failure. */
|
* uses so an override can narrow access rather than only widen it). Returns
|
||||||
|
* false (err filled) on an invalid pattern or an allocation failure. */
|
||||||
static bool store_host_list(char*** list, int* count, const char* value, const char* key,
|
static bool store_host_list(char*** list, int* count, const char* value, const char* key,
|
||||||
const char* module_name, char* err, size_t err_size) {
|
const char* module_name, bool replace, char* err, size_t err_size) {
|
||||||
|
if (replace) {
|
||||||
|
for (int i = 0; i < *count; i++)
|
||||||
|
free((*list)[i]);
|
||||||
|
free(*list);
|
||||||
|
*list = NULL;
|
||||||
|
*count = 0;
|
||||||
|
}
|
||||||
char* copy = str_dup(value);
|
char* copy = str_dup(value);
|
||||||
if (!copy) {
|
if (!copy) {
|
||||||
if (module_name)
|
if (module_name)
|
||||||
@@ -278,8 +292,8 @@ static bool store_port(int* slot, const char* value, char* err, size_t err_size)
|
|||||||
|
|
||||||
/* Apply a global scalar key/value. Keys are case-insensitive. Returns false
|
/* Apply a global scalar key/value. Keys are case-insensitive. Returns false
|
||||||
* (err filled) on an unknown key or an invalid value. */
|
* (err filled) on an unknown key or an invalid value. */
|
||||||
static bool apply_global_key(DaemonConf* conf, char* key, const char* value, char* err,
|
static bool apply_global_key(DaemonConf* conf, char* key, const char* value, bool replace_hosts,
|
||||||
size_t err_size) {
|
char* err, size_t err_size) {
|
||||||
if (key_equals(key, "port"))
|
if (key_equals(key, "port"))
|
||||||
return store_port(&conf->global.port, value, err, err_size);
|
return store_port(&conf->global.port, value, err, err_size);
|
||||||
if (key_equals(key, "motd file")) {
|
if (key_equals(key, "motd file")) {
|
||||||
@@ -302,10 +316,10 @@ static bool apply_global_key(DaemonConf* conf, char* key, const char* value, cha
|
|||||||
return store_auth_failure_delay(&conf->global.auth_failure_delay_ms, value, err, err_size);
|
return store_auth_failure_delay(&conf->global.auth_failure_delay_ms, value, err, err_size);
|
||||||
if (key_equals(key, "hosts allow"))
|
if (key_equals(key, "hosts allow"))
|
||||||
return store_host_list(&conf->global.hosts_allow, &conf->global.hosts_allow_count, value,
|
return store_host_list(&conf->global.hosts_allow, &conf->global.hosts_allow_count, value,
|
||||||
"hosts allow", NULL, err, err_size);
|
"hosts allow", NULL, replace_hosts, err, err_size);
|
||||||
if (key_equals(key, "hosts deny"))
|
if (key_equals(key, "hosts deny"))
|
||||||
return store_host_list(&conf->global.hosts_deny, &conf->global.hosts_deny_count, value,
|
return store_host_list(&conf->global.hosts_deny, &conf->global.hosts_deny_count, value,
|
||||||
"hosts deny", NULL, err, err_size);
|
"hosts deny", NULL, replace_hosts, err, err_size);
|
||||||
set_error(err, err_size, "unknown global key '%s'", key);
|
set_error(err, err_size, "unknown global key '%s'", key);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -389,10 +403,10 @@ static bool apply_module_key(DaemonModule* module, char* key, char* value, char*
|
|||||||
return store_max_connections(&module->max_connections, value, module->name, err, err_size);
|
return store_max_connections(&module->max_connections, value, module->name, err, err_size);
|
||||||
if (key_equals(key, "hosts allow"))
|
if (key_equals(key, "hosts allow"))
|
||||||
return store_host_list(&module->hosts_allow, &module->hosts_allow_count, value, "hosts allow",
|
return store_host_list(&module->hosts_allow, &module->hosts_allow_count, value, "hosts allow",
|
||||||
module->name, err, err_size);
|
false, module->name, err, err_size);
|
||||||
if (key_equals(key, "hosts deny"))
|
if (key_equals(key, "hosts deny"))
|
||||||
return store_host_list(&module->hosts_deny, &module->hosts_deny_count, value, "hosts deny",
|
return store_host_list(&module->hosts_deny, &module->hosts_deny_count, value, "hosts deny",
|
||||||
module->name, err, err_size);
|
false, module->name, err, err_size);
|
||||||
set_error(err, err_size, "unknown key '%s' in module '%s'", key, module->name);
|
set_error(err, err_size, "unknown key '%s' in module '%s'", key, module->name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -573,7 +587,7 @@ DaemonConf* daemon_conf_load(const char* path, char* err, size_t err_size) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!apply_global_key(conf, key, value, err, err_size)) {
|
if (!apply_global_key(conf, key, value, false, err, err_size)) {
|
||||||
ok = false;
|
ok = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -628,7 +642,7 @@ int daemon_conf_apply_dparam(DaemonConf* conf, const char* assignment, char* err
|
|||||||
set_error(err, err_size, "--dparam '%s' has an empty value", assignment);
|
set_error(err, err_size, "--dparam '%s' has an empty value", assignment);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
bool ok = apply_global_key(conf, key, value, err, err_size);
|
bool ok = apply_global_key(conf, key, value, true, err, err_size);
|
||||||
free(copy);
|
free(copy);
|
||||||
return ok ? 0 : -1;
|
return ok ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,9 @@ typedef struct DaemonConf {
|
|||||||
#define DAEMON_CONF_DEFAULT_AUTH_FAILURE_DELAY_MS 500
|
#define DAEMON_CONF_DEFAULT_AUTH_FAILURE_DELAY_MS 500
|
||||||
/* Largest accepted `auth failure delay`, so a typo cannot pin a connection
|
/* Largest accepted `auth failure delay`, so a typo cannot pin a connection
|
||||||
* child in nanosleep for an absurd time. */
|
* child in nanosleep for an absurd time. */
|
||||||
#define DAEMON_CONF_MAX_AUTH_FAILURE_DELAY_MS 60000
|
/* Bounded well below the socket I/O timeout so a failed-auth child cannot hold
|
||||||
|
* a connection slot for long enough to amplify connection-cap exhaustion. */
|
||||||
|
#define DAEMON_CONF_MAX_AUTH_FAILURE_DELAY_MS 5000
|
||||||
/* Longest accepted config line (excluding the trailing newline). Longer lines
|
/* Longest accepted config line (excluding the trailing newline). Longer lines
|
||||||
* are rejected rather than buffered unboundedly. */
|
* are rejected rather than buffered unboundedly. */
|
||||||
#define DAEMON_CONF_MAX_LINE 4096
|
#define DAEMON_CONF_MAX_LINE 4096
|
||||||
|
|||||||
+13
-4
@@ -603,12 +603,21 @@ bool utils_fd_peer_ip(int fd, char* buf, size_t len) {
|
|||||||
return false;
|
return false;
|
||||||
const void* src = NULL;
|
const void* src = NULL;
|
||||||
int family = peer.ss_family;
|
int family = peer.ss_family;
|
||||||
if (family == AF_INET)
|
if (family == AF_INET) {
|
||||||
src = &((const struct sockaddr_in*)&peer)->sin_addr;
|
src = &((const struct sockaddr_in*)&peer)->sin_addr;
|
||||||
else if (family == AF_INET6)
|
} else if (family == AF_INET6) {
|
||||||
src = &((const struct sockaddr_in6*)&peer)->sin6_addr;
|
const struct sockaddr_in6* peer6 = (const struct sockaddr_in6*)&peer;
|
||||||
else
|
/* A dual-stack IPv6 listener reports IPv4 peers as ::ffff:a.b.c.d. Emit
|
||||||
|
* the IPv4 form so IPv4 ACL patterns (and logs) see the real address. */
|
||||||
|
if (IN6_IS_ADDR_V4MAPPED(&peer6->sin6_addr)) {
|
||||||
|
struct in_addr v4;
|
||||||
|
memcpy(&v4, &peer6->sin6_addr.s6_addr[12], sizeof(v4));
|
||||||
|
return inet_ntop(AF_INET, &v4, buf, (socklen_t)len) != NULL;
|
||||||
|
}
|
||||||
|
src = &peer6->sin6_addr;
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
return inet_ntop(family, src, buf, (socklen_t)len) != NULL;
|
return inet_ntop(family, src, buf, (socklen_t)len) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -321,6 +321,10 @@ static void test_daemon_conf_dparam_override() {
|
|||||||
EXPECT_EQ_INT(
|
EXPECT_EQ_INT(
|
||||||
daemon_conf_apply_dparam(conf, "hosts allow=127.0.0.1,10.0.0.0/8", err, sizeof(err)), 0);
|
daemon_conf_apply_dparam(conf, "hosts allow=127.0.0.1,10.0.0.0/8", err, sizeof(err)), 0);
|
||||||
EXPECT_EQ_INT(conf->global.hosts_allow_count, 2);
|
EXPECT_EQ_INT(conf->global.hosts_allow_count, 2);
|
||||||
|
/* A later --dparam replaces the list (an override must be able to narrow). */
|
||||||
|
EXPECT_EQ_INT(daemon_conf_apply_dparam(conf, "hosts allow=127.0.0.1", err, sizeof(err)), 0);
|
||||||
|
EXPECT_EQ_INT(conf->global.hosts_allow_count, 1);
|
||||||
|
EXPECT_EQ_STR(conf->global.hosts_allow[0], "127.0.0.1");
|
||||||
|
|
||||||
EXPECT_EQ_INT(daemon_conf_apply_dparam(conf, "port=notaport", err, sizeof(err)), -1);
|
EXPECT_EQ_INT(daemon_conf_apply_dparam(conf, "port=notaport", err, sizeof(err)), -1);
|
||||||
EXPECT_EQ_INT(daemon_conf_apply_dparam(conf, "bogus=1", err, sizeof(err)), -1);
|
EXPECT_EQ_INT(daemon_conf_apply_dparam(conf, "bogus=1", err, sizeof(err)), -1);
|
||||||
@@ -386,7 +390,7 @@ static void test_daemon_conf_limits_and_hosts_parse() {
|
|||||||
char err[256];
|
char err[256];
|
||||||
EXPECT_EQ_INT(write_conf("max connections = 25\n"
|
EXPECT_EQ_INT(write_conf("max connections = 25\n"
|
||||||
"auth failure delay = 0\n"
|
"auth failure delay = 0\n"
|
||||||
"hosts allow = 10.0.0.0/8, *.example.com\n"
|
"hosts allow = 10.0.0.0/8, 192.168.1.0/24\n"
|
||||||
"hosts deny = 192.168.0.1 2001:db8::/32\n"
|
"hosts deny = 192.168.0.1 2001:db8::/32\n"
|
||||||
"\n"
|
"\n"
|
||||||
"[m]\n"
|
"[m]\n"
|
||||||
@@ -403,7 +407,7 @@ static void test_daemon_conf_limits_and_hosts_parse() {
|
|||||||
EXPECT_EQ_INT(conf->global.auth_failure_delay_ms, 0);
|
EXPECT_EQ_INT(conf->global.auth_failure_delay_ms, 0);
|
||||||
EXPECT_EQ_INT(conf->global.hosts_allow_count, 2);
|
EXPECT_EQ_INT(conf->global.hosts_allow_count, 2);
|
||||||
EXPECT_EQ_STR(conf->global.hosts_allow[0], "10.0.0.0/8");
|
EXPECT_EQ_STR(conf->global.hosts_allow[0], "10.0.0.0/8");
|
||||||
EXPECT_EQ_STR(conf->global.hosts_allow[1], "*.example.com");
|
EXPECT_EQ_STR(conf->global.hosts_allow[1], "192.168.1.0/24");
|
||||||
EXPECT_EQ_INT(conf->global.hosts_deny_count, 2);
|
EXPECT_EQ_INT(conf->global.hosts_deny_count, 2);
|
||||||
EXPECT_EQ_STR(conf->global.hosts_deny[0], "192.168.0.1");
|
EXPECT_EQ_STR(conf->global.hosts_deny[0], "192.168.0.1");
|
||||||
EXPECT_EQ_STR(conf->global.hosts_deny[1], "2001:db8::/32");
|
EXPECT_EQ_STR(conf->global.hosts_deny[1], "2001:db8::/32");
|
||||||
@@ -415,9 +419,11 @@ static void test_daemon_conf_limits_and_hosts_parse() {
|
|||||||
daemon_conf_free(conf);
|
daemon_conf_free(conf);
|
||||||
|
|
||||||
const char* bad_values[] = {
|
const char* bad_values[] = {
|
||||||
"max connections = 0\n", "max connections = -1\n", "max connections = abc\n",
|
"max connections = 0\n", "max connections = -1\n",
|
||||||
"auth failure delay = -1\n", "auth failure delay = 70000\n", "auth failure delay = soon\n",
|
"max connections = abc\n", "auth failure delay = -1\n",
|
||||||
|
"auth failure delay = 70000\n", "auth failure delay = soon\n",
|
||||||
"hosts allow = 10.0.0.0/99\n", "hosts deny = 2001:db8::/129\n",
|
"hosts allow = 10.0.0.0/99\n", "hosts deny = 2001:db8::/129\n",
|
||||||
|
"hosts allow = *.example.com\n", "hosts deny = not-an-ip\n",
|
||||||
};
|
};
|
||||||
for (size_t i = 0; i < sizeof(bad_values) / sizeof(bad_values[0]); i++) {
|
for (size_t i = 0; i < sizeof(bad_values) / sizeof(bad_values[0]); i++) {
|
||||||
EXPECT_EQ_INT(write_conf(bad_values[i], &path), 0);
|
EXPECT_EQ_INT(write_conf(bad_values[i], &path), 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user