C2: --force is deletion authority (an incoming regular file may remove a non-empty destination directory tree, and --delete-missing-args may remove a non-empty directory mirror), but it was not masked by the operator --allow-delete policy. The handler now clears config->force_delete unless --allow-delete was given, exactly like --delete and --delete-missing-args. C3: a standalone TCP / --stdio server running as root defaulted to SUPER_MODE_AUTO, so an untrusted client --devices/--write-devices/ --super could make it create device nodes, write raw devices, or apply client-chosen ownership. A privileged standalone receiver now forces SUPER_MODE_OFF unless the operator opts in with the new server-only --allow-super flag. Non-root receivers are unchanged, and the daemon path keeps its per-module `client owner = yes` gate. --allow-super is rejected with --no-super or --daemon. C6: tls_client_identity_allowed now rejects a CN whose reported length reached the buffer bound, so a truncated over-long CN cannot be matched by a required --client-cn prefix. Tests: an integration regression proving --force cannot replace a destination directory without --allow-delete; standalone-default tests for --copy-as refusal and (root-only) skipped device creation; a CLI unit test for the new flag. The integration shared_server fixture opts in with --allow-super so the existing root-only ownership/device/copy-as tests continue to exercise the opted-in configuration. README and RSYNC_COMPAT document the flag and the force/delete gating.
252 lines
9.3 KiB
C
252 lines
9.3 KiB
C
#include "test_server_cli.h"
|
|
#include "server_cli.h"
|
|
#include "test_utils.h"
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
|
|
static int parse_ok(const char* const* args, int count, ServerCliOptions* opts) {
|
|
char err[512];
|
|
int r = server_cli_parse(count, (char**)args, opts, err, sizeof(err));
|
|
if (r == 0)
|
|
return 0;
|
|
if (r < 0)
|
|
return -1;
|
|
return 1;
|
|
}
|
|
|
|
static void test_server_cli_defaults() {
|
|
const char* args[] = {"fastsync-server"};
|
|
ServerCliOptions opts;
|
|
EXPECT_EQ_INT(parse_ok(args, 1, &opts), 0);
|
|
EXPECT_FALSE(opts.stdio_mode);
|
|
EXPECT_FALSE(opts.daemon_mode);
|
|
EXPECT_FALSE(opts.no_detach);
|
|
EXPECT_FALSE(opts.verbose);
|
|
EXPECT_FALSE(opts.use_tls);
|
|
EXPECT_EQ_INT(opts.port, 8080);
|
|
EXPECT_FALSE(opts.port_set);
|
|
EXPECT_EQ_STR(opts.destination_root, ".");
|
|
EXPECT_NULL(opts.config_path);
|
|
EXPECT_EQ_INT(opts.dparam_count, 0);
|
|
EXPECT_EQ_INT(opts.bind_family, AF_UNSPEC);
|
|
EXPECT_FALSE(opts.allow_delete);
|
|
EXPECT_FALSE(opts.allow_unauthenticated);
|
|
EXPECT_FALSE(opts.no_super);
|
|
EXPECT_FALSE(opts.allow_super);
|
|
server_cli_options_free(&opts);
|
|
}
|
|
|
|
/* C3: --allow-super is the standalone/--stdio opt-in for a privileged receiver;
|
|
* it never combines with --no-super, and daemon modules use their own per-module
|
|
* `client owner = yes` opt-in instead. */
|
|
static void test_server_cli_allow_super() {
|
|
const char* args[] = {"fastsync-server", "--allow-super", "--destination-root", "/srv"};
|
|
ServerCliOptions opts;
|
|
EXPECT_EQ_INT(parse_ok(args, 4, &opts), 0);
|
|
EXPECT_TRUE(opts.allow_super);
|
|
server_cli_options_free(&opts);
|
|
|
|
char err[256];
|
|
const char* a1[] = {"s", "--allow-super", "--no-super"};
|
|
EXPECT_EQ_INT(server_cli_parse(3, (char**)a1, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "mutually exclusive") != NULL);
|
|
|
|
const char* a2[] = {"s", "--daemon", "--config=/tmp/x.conf", "--allow-super"};
|
|
EXPECT_EQ_INT(server_cli_parse(4, (char**)a2, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "client owner") != NULL);
|
|
server_cli_options_free(&opts);
|
|
}
|
|
|
|
/* --no-super is a standalone/SSH operator veto (does not require --daemon):
|
|
it forces SUPER_MODE_OFF for every connection and refuses client --copy-as. */
|
|
static void test_server_cli_no_super() {
|
|
const char* args[] = {"fastsync-server", "--no-super", "--destination-root", "/srv"};
|
|
ServerCliOptions opts;
|
|
EXPECT_EQ_INT(parse_ok(args, 4, &opts), 0);
|
|
EXPECT_TRUE(opts.no_super);
|
|
EXPECT_EQ_STR(opts.destination_root, "/srv");
|
|
server_cli_options_free(&opts);
|
|
|
|
const char* args2[] = {"fastsync-server", "--daemon", "--config=/tmp/x.conf", "--no-super"};
|
|
ServerCliOptions opts2;
|
|
EXPECT_EQ_INT(parse_ok(args2, 4, &opts2), 0);
|
|
EXPECT_TRUE(opts2.no_super);
|
|
EXPECT_TRUE(opts2.daemon_mode);
|
|
server_cli_options_free(&opts2);
|
|
}
|
|
|
|
static void test_server_cli_daemon_flags() {
|
|
const char* args[] = {"fastsync-server", "--daemon", "--no-detach", "--allow-unauthenticated"};
|
|
ServerCliOptions opts;
|
|
EXPECT_EQ_INT(parse_ok(args, 4, &opts), 0);
|
|
EXPECT_TRUE(opts.daemon_mode);
|
|
EXPECT_TRUE(opts.no_detach);
|
|
EXPECT_TRUE(opts.allow_unauthenticated);
|
|
server_cli_options_free(&opts);
|
|
}
|
|
|
|
static void test_server_cli_config_and_dparam_forms() {
|
|
const char* args[] = {"fastsync-server", "--daemon", "--config=/tmp/x.conf",
|
|
"--dparam=port=8734", "--dparam", "address=127.0.0.1"};
|
|
ServerCliOptions opts;
|
|
EXPECT_EQ_INT(parse_ok(args, 6, &opts), 0);
|
|
EXPECT_EQ_STR(opts.config_path, "/tmp/x.conf");
|
|
EXPECT_EQ_INT(opts.dparam_count, 2);
|
|
EXPECT_EQ_STR(opts.dparams[0], "port=8734");
|
|
EXPECT_EQ_STR(opts.dparams[1], "address=127.0.0.1");
|
|
|
|
const char* args2[] = {"fastsync-server", "--daemon", "--config", "/tmp/y.conf"};
|
|
ServerCliOptions opts2;
|
|
EXPECT_EQ_INT(parse_ok(args2, 4, &opts2), 0);
|
|
EXPECT_EQ_STR(opts2.config_path, "/tmp/y.conf");
|
|
server_cli_options_free(&opts);
|
|
server_cli_options_free(&opts2);
|
|
}
|
|
|
|
static void test_server_cli_preserves_existing_flags() {
|
|
const char* args[] = {"fastsync-server",
|
|
"-p",
|
|
"9000",
|
|
"--tls",
|
|
"--cert",
|
|
"/c",
|
|
"--key",
|
|
"/k",
|
|
"--ca",
|
|
"/ca",
|
|
"--client-cn",
|
|
"cn",
|
|
"--allow-delete",
|
|
"--trust-sender",
|
|
"--address",
|
|
"127.0.0.1",
|
|
"-6",
|
|
"--destination-root",
|
|
"/srv"};
|
|
ServerCliOptions opts;
|
|
EXPECT_EQ_INT(parse_ok(args, 19, &opts), 0);
|
|
EXPECT_EQ_INT(opts.port, 9000);
|
|
EXPECT_TRUE(opts.port_set);
|
|
EXPECT_TRUE(opts.use_tls);
|
|
EXPECT_EQ_STR(opts.tls_cert, "/c");
|
|
EXPECT_EQ_STR(opts.tls_key, "/k");
|
|
EXPECT_EQ_STR(opts.tls_ca, "/ca");
|
|
EXPECT_EQ_STR(opts.client_cn, "cn");
|
|
EXPECT_TRUE(opts.allow_delete);
|
|
EXPECT_TRUE(opts.trust_sender);
|
|
EXPECT_EQ_STR(opts.bind_address, "127.0.0.1");
|
|
EXPECT_EQ_INT(opts.bind_family, AF_INET6);
|
|
EXPECT_TRUE(opts.destination_root_set);
|
|
EXPECT_EQ_STR(opts.destination_root, "/srv");
|
|
server_cli_options_free(&opts);
|
|
}
|
|
|
|
static void test_server_cli_conflicts() {
|
|
/* server_cli_parse zero-initializes opts (server_cli_options_default) before
|
|
* parsing, so `opts` is still safe to pass to server_cli_options_free even
|
|
* when every parse below returns -1 on failure. */
|
|
char err[256];
|
|
ServerCliOptions opts;
|
|
const char* a1[] = {"s", "--daemon", "--stdio"};
|
|
EXPECT_EQ_INT(server_cli_parse(3, (char**)a1, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "mutually exclusive") != NULL);
|
|
|
|
const char* a2[] = {"s", "--daemon", "--destination-root", "/x"};
|
|
EXPECT_EQ_INT(server_cli_parse(4, (char**)a2, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "module paths") != NULL);
|
|
|
|
const char* a3[] = {"s", "--config", "/x.conf"};
|
|
EXPECT_EQ_INT(server_cli_parse(3, (char**)a3, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "require --daemon") != NULL);
|
|
|
|
const char* a4[] = {"s", "--no-detach"};
|
|
EXPECT_EQ_INT(server_cli_parse(2, (char**)a4, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "require --daemon") != NULL);
|
|
server_cli_options_free(&opts);
|
|
}
|
|
|
|
static void test_server_cli_invalid() {
|
|
char err[256];
|
|
ServerCliOptions opts;
|
|
const char* a1[] = {"s", "-p", "notaport"};
|
|
EXPECT_EQ_INT(server_cli_parse(3, (char**)a1, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "invalid port") != NULL);
|
|
|
|
const char* a2[] = {"s", "-4", "-6"};
|
|
EXPECT_EQ_INT(server_cli_parse(3, (char**)a2, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "mutually exclusive") != NULL);
|
|
|
|
const char* a3[] = {"s", "--nope"};
|
|
EXPECT_EQ_INT(server_cli_parse(2, (char**)a3, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "unknown option") != NULL);
|
|
|
|
const char* a4[] = {"s", "--cert"};
|
|
EXPECT_EQ_INT(server_cli_parse(2, (char**)a4, &opts, err, sizeof(err)), -1);
|
|
server_cli_options_free(&opts);
|
|
}
|
|
|
|
static void test_server_cli_password_and_early_input() {
|
|
const char* args[] = {"s",
|
|
"--daemon",
|
|
"--password-file=/etc/fast.pw",
|
|
"--early-input",
|
|
"/run/secrets",
|
|
"--iconv=utf-8"};
|
|
ServerCliOptions opts;
|
|
EXPECT_EQ_INT(parse_ok(args, 6, &opts), 0);
|
|
EXPECT_EQ_STR(opts.password_file, "/etc/fast.pw");
|
|
EXPECT_EQ_STR(opts.early_input_file, "/run/secrets");
|
|
EXPECT_EQ_STR(opts.iconv_spec, "utf-8");
|
|
|
|
const char* args2[] = {
|
|
"s", "--daemon", "--password-file", "/etc/fast.pw", "--early-input=/secrets",
|
|
"--iconv", "utf-8,iso-8859-1"};
|
|
ServerCliOptions opts2;
|
|
EXPECT_EQ_INT(parse_ok(args2, 7, &opts2), 0);
|
|
EXPECT_EQ_STR(opts2.password_file, "/etc/fast.pw");
|
|
EXPECT_EQ_STR(opts2.early_input_file, "/secrets");
|
|
EXPECT_EQ_STR(opts2.iconv_spec, "utf-8,iso-8859-1");
|
|
server_cli_options_free(&opts);
|
|
server_cli_options_free(&opts2);
|
|
}
|
|
|
|
static void test_server_cli_password_requires_daemon() {
|
|
char err[256];
|
|
ServerCliOptions opts;
|
|
const char* a1[] = {"s", "--password-file", "/etc/fast.pw"};
|
|
EXPECT_EQ_INT(server_cli_parse(3, (char**)a1, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "require --daemon") != NULL);
|
|
|
|
const char* a2[] = {"s", "--early-input", "/secrets"};
|
|
EXPECT_EQ_INT(server_cli_parse(3, (char**)a2, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "require --daemon") != NULL);
|
|
|
|
const char* a3[] = {"s", "--daemon", "--password-file"};
|
|
EXPECT_EQ_INT(server_cli_parse(3, (char**)a3, &opts, err, sizeof(err)), -1);
|
|
EXPECT_TRUE(strstr(err, "missing argument") != NULL);
|
|
server_cli_options_free(&opts);
|
|
}
|
|
|
|
static void test_server_cli_help() {
|
|
char err[256];
|
|
const char* a1[] = {"s", "--help"};
|
|
ServerCliOptions opts;
|
|
EXPECT_EQ_INT(server_cli_parse(2, (char**)a1, &opts, err, sizeof(err)), 1);
|
|
EXPECT_TRUE(opts.show_help);
|
|
server_cli_options_free(&opts);
|
|
}
|
|
|
|
void test_server_cli() {
|
|
test_server_cli_defaults();
|
|
test_server_cli_daemon_flags();
|
|
test_server_cli_config_and_dparam_forms();
|
|
test_server_cli_preserves_existing_flags();
|
|
test_server_cli_conflicts();
|
|
test_server_cli_invalid();
|
|
test_server_cli_password_and_early_input();
|
|
test_server_cli_password_requires_daemon();
|
|
test_server_cli_no_super();
|
|
test_server_cli_allow_super();
|
|
test_server_cli_help();
|
|
}
|