quality: refactors (#321-#326) + fake-super directories (#319) #329
+169
-129
@@ -1306,29 +1306,45 @@ static bool daemonize(void) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
/* Apply the process-wide policies shared by the stdio and listener
|
||||||
/* Capture the process umask now, while still single-threaded: the cached
|
* entrypoints: logging verbosity, signal handling, the parsed server
|
||||||
* value is what file_mode_base() uses, and reading it later would race with
|
* authorization policies, and the socket timeout floor. Runs after CLI
|
||||||
* receiver threads creating files. */
|
* parsing and after the standalone --hash-credentials tool has been ruled
|
||||||
file_umask_capture();
|
* out. */
|
||||||
ServerCliOptions opts;
|
static void configure_server_process(const ServerCliOptions* opts) {
|
||||||
char cli_err[512];
|
signal(SIGPIPE, SIG_IGN);
|
||||||
int parse_result = server_cli_parse(argc, argv, &opts, cli_err, sizeof(cli_err));
|
if (opts->verbose) {
|
||||||
if (parse_result == 1) {
|
set_log_level(LOG_LEVEL_DEBUG);
|
||||||
print_server_usage();
|
set_log_debug_flags(LOG_DEBUG_ALL);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (parse_result < 0) {
|
|
||||||
server_cli_options_free(&opts);
|
|
||||||
fprintf(stderr, "Error: %s\n", cli_err);
|
|
||||||
print_server_usage();
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
if (opts->tls_ca && !opts->use_tls)
|
||||||
|
log_message(LOG_LEVEL_WARNING, "--ca has no effect without --tls");
|
||||||
|
/* Persist the parsed server policies into the process-global policy state
|
||||||
|
* BEFORE the stdio branch: an SSH-launched `--stdio` server (whose argv came
|
||||||
|
* from the client via --remote-option and friends) must honor --allow-delete,
|
||||||
|
* --trust-sender and --client-cn exactly like the standalone listener. */
|
||||||
|
required_client_cn = opts->client_cn;
|
||||||
|
allow_delete = opts->allow_delete;
|
||||||
|
trust_sender = opts->trust_sender;
|
||||||
|
allow_unauthenticated = opts->allow_unauthenticated;
|
||||||
|
server_no_super = opts->no_super;
|
||||||
|
/* --stdio rejects --allow-super at parse time; force it off here as well so
|
||||||
|
* this process-global policy cannot be re-enabled by a future caller. */
|
||||||
|
server_allow_super = opts->allow_super && !opts->stdio_mode;
|
||||||
|
server_iconv_spec = opts->iconv_spec;
|
||||||
|
install_cleanup_handler(SIGINT);
|
||||||
|
install_cleanup_handler(SIGTERM);
|
||||||
|
/* Server-owned socket deadline floor: the client default --timeout=0 would
|
||||||
|
* otherwise leave accepted sockets without SO_RCVTIMEO/SO_SNDTIMEO and let a
|
||||||
|
* silent peer hold a connection (and its process slot) forever. */
|
||||||
|
tcp_set_timeouts(SERVER_IO_TIMEOUT_SEC, SERVER_IO_TIMEOUT_SEC);
|
||||||
|
}
|
||||||
|
|
||||||
/* --hash-credentials: standalone offline tool; read user:password lines and
|
/* --hash-credentials: standalone offline tool; read user:password lines and
|
||||||
* emit new-format credential-store lines, then exit. */
|
* emit new-format credential-store lines, then exit. Consumes and frees
|
||||||
if (opts.hash_credentials_file) {
|
* opts. */
|
||||||
uint32_t iters = opts.hash_iterations_set ? opts.hash_iterations : CREDENTIAL_DEFAULT_ITERS;
|
static int run_hash_credentials_tool(ServerCliOptions* opts) {
|
||||||
|
uint32_t iters = opts->hash_iterations_set ? opts->hash_iterations : CREDENTIAL_DEFAULT_ITERS;
|
||||||
/* The output is secret material: if it is redirected to a regular file,
|
/* The output is secret material: if it is redirected to a regular file,
|
||||||
* warn when that file is group/other-accessible (the store must be 0600). */
|
* warn when that file is group/other-accessible (the store must be 0600). */
|
||||||
struct stat out_st;
|
struct stat out_st;
|
||||||
@@ -1338,53 +1354,29 @@ int main(int argc, char* argv[]) {
|
|||||||
"Warning: credential-store output is a group/other-accessible file; restrict it to "
|
"Warning: credential-store output is a group/other-accessible file; restrict it to "
|
||||||
"mode 0600 (chmod 600)\n");
|
"mode 0600 (chmod 600)\n");
|
||||||
char hash_err[512];
|
char hash_err[512];
|
||||||
if (credentials_hash_file(opts.hash_credentials_file, iters, stdout, hash_err,
|
if (credentials_hash_file(opts->hash_credentials_file, iters, stdout, hash_err,
|
||||||
sizeof(hash_err)) != 0) {
|
sizeof(hash_err)) != 0) {
|
||||||
fprintf(stderr, "Error: %s\n", hash_err);
|
fprintf(stderr, "Error: %s\n", hash_err);
|
||||||
server_cli_options_free(&opts);
|
server_cli_options_free(opts);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
server_cli_options_free(&opts);
|
server_cli_options_free(opts);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int exit_code = 0;
|
/* SSH --stdio session: the transport is authenticated by sshd outside of
|
||||||
signal(SIGPIPE, SIG_IGN);
|
* FastSync, so the single connection is served over STDIN/STDOUT and the
|
||||||
if (opts.verbose) {
|
* process exits. The destination root is authorized exactly like the listener
|
||||||
set_log_level(LOG_LEVEL_DEBUG);
|
* path. Consumes and frees opts. */
|
||||||
set_log_debug_flags(LOG_DEBUG_ALL);
|
static int run_stdio_server(ServerCliOptions* opts) {
|
||||||
}
|
|
||||||
if (opts.tls_ca && !opts.use_tls)
|
|
||||||
log_message(LOG_LEVEL_WARNING, "--ca has no effect without --tls");
|
|
||||||
/* Persist the parsed server policies into the process-global policy state
|
|
||||||
* BEFORE the stdio branch: an SSH-launched `--stdio` server (whose argv came
|
|
||||||
* from the client via --remote-option and friends) must honor --allow-delete,
|
|
||||||
* --trust-sender and --client-cn exactly like the standalone listener. */
|
|
||||||
required_client_cn = opts.client_cn;
|
|
||||||
allow_delete = opts.allow_delete;
|
|
||||||
trust_sender = opts.trust_sender;
|
|
||||||
allow_unauthenticated = opts.allow_unauthenticated;
|
|
||||||
server_no_super = opts.no_super;
|
|
||||||
/* --stdio rejects --allow-super at parse time; force it off here as well so
|
|
||||||
* this process-global policy cannot be re-enabled by a future caller. */
|
|
||||||
server_allow_super = opts.allow_super && !opts.stdio_mode;
|
|
||||||
server_iconv_spec = opts.iconv_spec;
|
|
||||||
install_cleanup_handler(SIGINT);
|
|
||||||
install_cleanup_handler(SIGTERM);
|
|
||||||
/* Server-owned socket deadline floor: the client default --timeout=0 would
|
|
||||||
* otherwise leave accepted sockets without SO_RCVTIMEO/SO_SNDTIMEO and let a
|
|
||||||
* silent peer hold a connection (and its process slot) forever. */
|
|
||||||
tcp_set_timeouts(SERVER_IO_TIMEOUT_SEC, SERVER_IO_TIMEOUT_SEC);
|
|
||||||
|
|
||||||
if (opts.stdio_mode) {
|
|
||||||
/* SSH authenticates the stdio transport outside of FastSync. */
|
/* SSH authenticates the stdio transport outside of FastSync. */
|
||||||
allow_unauthenticated = true;
|
allow_unauthenticated = true;
|
||||||
if (!configure_authorization(opts.destination_root)) {
|
if (!configure_authorization(opts->destination_root)) {
|
||||||
char* escaped = output_escape(opts.destination_root, false);
|
char* escaped = output_escape(opts->destination_root, false);
|
||||||
fprintf(stderr, "Error: invalid destination root '%s'\n",
|
fprintf(stderr, "Error: invalid destination root '%s'\n",
|
||||||
escaped ? escaped : "<allocation failed>");
|
escaped ? escaped : "<allocation failed>");
|
||||||
free(escaped);
|
free(escaped);
|
||||||
server_cli_options_free(&opts);
|
server_cli_options_free(opts);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
io_set_fds(STDIN_FILENO, STDOUT_FILENO);
|
io_set_fds(STDIN_FILENO, STDOUT_FILENO);
|
||||||
@@ -1393,34 +1385,32 @@ int main(int argc, char* argv[]) {
|
|||||||
* and are released by process exit. */
|
* and are released by process exit. */
|
||||||
handler(STDIN_FILENO);
|
handler(STDIN_FILENO);
|
||||||
release_authorization();
|
release_authorization();
|
||||||
server_cli_options_free(&opts);
|
server_cli_options_free(opts);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int port = opts.port;
|
/* Load the daemon config, apply --dparam overrides, resolve the effective
|
||||||
int bind_family = opts.bind_family;
|
* port/address, and surface the operator-facing module warnings. On failure
|
||||||
const char* bind_address = opts.bind_address;
|
* the error is printed and false is returned. */
|
||||||
|
static bool load_daemon_policy(ServerCliOptions* opts, int* port, const char** bind_address,
|
||||||
if (opts.daemon_mode) {
|
char* err, size_t err_size) {
|
||||||
const char* config_path = opts.config_path ? opts.config_path : default_daemon_config_path();
|
const char* config_path = opts->config_path ? opts->config_path : default_daemon_config_path();
|
||||||
g_daemon_conf = daemon_conf_load(config_path, cli_err, sizeof(cli_err));
|
g_daemon_conf = daemon_conf_load(config_path, err, err_size);
|
||||||
if (!g_daemon_conf) {
|
if (!g_daemon_conf) {
|
||||||
server_cli_options_free(&opts);
|
fprintf(stderr, "Error: %s\n", err);
|
||||||
fprintf(stderr, "Error: %s\n", cli_err);
|
return false;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
for (int i = 0; i < opts.dparam_count; i++) {
|
for (int i = 0; i < opts->dparam_count; i++) {
|
||||||
if (daemon_conf_apply_dparam(g_daemon_conf, opts.dparams[i], cli_err, sizeof(cli_err)) != 0) {
|
if (daemon_conf_apply_dparam(g_daemon_conf, opts->dparams[i], err, err_size) != 0) {
|
||||||
fprintf(stderr, "Error: --dparam: %s\n", cli_err);
|
fprintf(stderr, "Error: --dparam: %s\n", err);
|
||||||
exit_code = 1;
|
return false;
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Effective port: -p (highest) > --dparam port > config port (default 873). */
|
/* Effective port: -p (highest) > --dparam port > config port (default 873). */
|
||||||
if (!opts.port_set)
|
if (!opts->port_set)
|
||||||
port = g_daemon_conf->global.port;
|
*port = g_daemon_conf->global.port;
|
||||||
if (!bind_address)
|
if (!*bind_address)
|
||||||
bind_address = g_daemon_conf->global.address;
|
*bind_address = g_daemon_conf->global.address;
|
||||||
if (g_daemon_conf->module_count == 0)
|
if (g_daemon_conf->module_count == 0)
|
||||||
log_message(LOG_LEVEL_WARNING,
|
log_message(LOG_LEVEL_WARNING,
|
||||||
"daemon config has no modules; every connection will be refused");
|
"daemon config has no modules; every connection will be refused");
|
||||||
@@ -1441,20 +1431,23 @@ int main(int argc, char* argv[]) {
|
|||||||
"across all connection children)",
|
"across all connection children)",
|
||||||
g_daemon_conf->modules[i].name, g_daemon_conf->modules[i].max_connections);
|
g_daemon_conf->modules[i].name, g_daemon_conf->modules[i].max_connections);
|
||||||
}
|
}
|
||||||
/* Daemon credential store (Wave B). --password-file and --early-input
|
return true;
|
||||||
* feed the same store, loaded BEFORE the listener forks so every
|
}
|
||||||
* connection child shares one read-only store. Fail closed at startup: a
|
|
||||||
* module that declares `auth users` without a store (or with an empty
|
/* Load the daemon credential store (Wave B) and enforce the fail-closed
|
||||||
* store) refuses to start rather than serving a module whose credentials
|
* startup check: a module that declares `auth users` without a store (or with
|
||||||
* can never be verified. */
|
* an empty store) refuses to start rather than serving a module whose
|
||||||
g_credentials =
|
* credentials can never be verified. On failure the error is printed and
|
||||||
credentials_load(opts.password_file, opts.early_input_file, cli_err, sizeof(cli_err));
|
* false is returned. */
|
||||||
|
static bool validate_daemon_credentials(const ServerCliOptions* opts, char* err, size_t err_size) {
|
||||||
|
/* --password-file and --early-input feed the same store, loaded BEFORE the
|
||||||
|
* listener forks so every connection child shares one read-only store. */
|
||||||
|
g_credentials = credentials_load(opts->password_file, opts->early_input_file, err, err_size);
|
||||||
if (!g_credentials) {
|
if (!g_credentials) {
|
||||||
server_cli_options_free(&opts);
|
fprintf(stderr, "Error: %s\n", err);
|
||||||
fprintf(stderr, "Error: %s\n", cli_err);
|
return false;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
bool credential_source_given = opts.password_file != NULL || opts.early_input_file != NULL;
|
bool credential_source_given = opts->password_file != NULL || opts->early_input_file != NULL;
|
||||||
for (int i = 0; i < g_daemon_conf->module_count; i++) {
|
for (int i = 0; i < g_daemon_conf->module_count; i++) {
|
||||||
const DaemonModule* module = &g_daemon_conf->modules[i];
|
const DaemonModule* module = &g_daemon_conf->modules[i];
|
||||||
if (module->auth_user_count == 0)
|
if (module->auth_user_count == 0)
|
||||||
@@ -1464,16 +1457,14 @@ int main(int argc, char* argv[]) {
|
|||||||
"Error: module '%s' declares 'auth users' but no credential store was given "
|
"Error: module '%s' declares 'auth users' but no credential store was given "
|
||||||
"(--password-file or --early-input); refusing to start (fail closed)\n",
|
"(--password-file or --early-input); refusing to start (fail closed)\n",
|
||||||
module->name);
|
module->name);
|
||||||
server_cli_options_free(&opts);
|
return false;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
if (credentials_store_size(g_credentials) == 0) {
|
if (credentials_store_size(g_credentials) == 0) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Error: module '%s' declares 'auth users' but the credential store is empty; "
|
"Error: module '%s' declares 'auth users' but the credential store is empty; "
|
||||||
"refusing to start (fail closed)\n",
|
"refusing to start (fail closed)\n",
|
||||||
module->name);
|
module->name);
|
||||||
server_cli_options_free(&opts);
|
return false;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
for (int j = 0; j < module->auth_user_count; j++) {
|
for (int j = 0; j < module->auth_user_count; j++) {
|
||||||
if (!credentials_store_has(g_credentials, module->auth_users[j]))
|
if (!credentials_store_has(g_credentials, module->auth_users[j]))
|
||||||
@@ -1483,32 +1474,30 @@ int main(int argc, char* argv[]) {
|
|||||||
module->name, module->auth_users[j]);
|
module->name, module->auth_users[j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Shared cross-process registry for the per-module / per-source caps and
|
return true;
|
||||||
* the auth lockout. Created HERE in the parent before any accept-loop
|
}
|
||||||
* fork; every connection child inherits the mapping. A failure degrades to
|
|
||||||
* "registry disabled" (the global cap and host ACLs still apply) rather
|
/* Create the shared cross-process registry for the per-module / per-source
|
||||||
* than refusing to start. */
|
* caps and the auth lockout. Called in the parent before any accept-loop fork;
|
||||||
g_daemon_limits = daemon_limits_create((int)g_daemon_conf->global.max_connections,
|
* every connection child inherits the mapping. A failure degrades to
|
||||||
g_daemon_conf->module_count,
|
* "registry disabled" (the global cap and host ACLs still apply) rather than
|
||||||
g_daemon_conf->global.max_connections_per_host,
|
* refusing to start. */
|
||||||
g_daemon_conf->global.auth_lockout_threshold,
|
static void create_daemon_limits(void) {
|
||||||
|
g_daemon_limits = daemon_limits_create(
|
||||||
|
(int)g_daemon_conf->global.max_connections, g_daemon_conf->module_count,
|
||||||
|
g_daemon_conf->global.max_connections_per_host, g_daemon_conf->global.auth_lockout_threshold,
|
||||||
g_daemon_conf->global.auth_lockout_duration_sec);
|
g_daemon_conf->global.auth_lockout_duration_sec);
|
||||||
if (!g_daemon_limits)
|
if (!g_daemon_limits)
|
||||||
log_message(LOG_LEVEL_WARNING,
|
log_message(LOG_LEVEL_WARNING,
|
||||||
"daemon: could not allocate the shared connection registry; per-module / "
|
"daemon: could not allocate the shared connection registry; per-module / "
|
||||||
"per-host caps and the cross-process auth lockout are disabled (the global "
|
"per-host caps and the cross-process auth lockout are disabled (the global "
|
||||||
"'max connections' cap and host ACLs still apply)");
|
"'max connections' cap and host ACLs still apply)");
|
||||||
} else {
|
}
|
||||||
if (!configure_authorization(opts.destination_root)) {
|
|
||||||
char* escaped = output_escape(opts.destination_root, false);
|
|
||||||
fprintf(stderr, "Error: invalid destination root '%s'\n",
|
|
||||||
escaped ? escaped : "<allocation failed>");
|
|
||||||
free(escaped);
|
|
||||||
server_cli_options_free(&opts);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Bind the listener, apply the daemon caps, set up TLS when requested, detach
|
||||||
|
* when daemonizing, and run the accept loop. Returns the process exit code. */
|
||||||
|
static int start_listener(ServerCliOptions* opts, int port, int bind_family,
|
||||||
|
const char* bind_address) {
|
||||||
ServerBindOptions bind_opts;
|
ServerBindOptions bind_opts;
|
||||||
bind_opts.bind_address = bind_address;
|
bind_opts.bind_address = bind_address;
|
||||||
bind_opts.family = bind_family;
|
bind_opts.family = bind_family;
|
||||||
@@ -1516,50 +1505,73 @@ int main(int argc, char* argv[]) {
|
|||||||
if (!g_server) {
|
if (!g_server) {
|
||||||
log_message(LOG_LEVEL_ERROR, "Failed to create server");
|
log_message(LOG_LEVEL_ERROR, "Failed to create server");
|
||||||
release_authorization();
|
release_authorization();
|
||||||
exit_code = 1;
|
return 1;
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
if (g_daemon_conf)
|
if (g_daemon_conf)
|
||||||
server_set_max_connections(g_server, (unsigned int)g_daemon_conf->global.max_connections);
|
server_set_max_connections(g_server, (unsigned int)g_daemon_conf->global.max_connections);
|
||||||
if (g_daemon_limits)
|
if (g_daemon_limits)
|
||||||
server_set_limit_registry(g_server, g_daemon_limits);
|
server_set_limit_registry(g_server, g_daemon_limits);
|
||||||
if (opts.use_tls) {
|
if (opts->use_tls) {
|
||||||
if (!opts.tls_cert || !opts.tls_key || !opts.tls_ca || !opts.client_cn) {
|
if (!opts->tls_cert || !opts->tls_key || !opts->tls_ca || !opts->client_cn) {
|
||||||
fprintf(stderr, "Error: --tls requires --cert, --key, --ca, and --client-cn\n");
|
fprintf(stderr, "Error: --tls requires --cert, --key, --ca, and --client-cn\n");
|
||||||
server_delete(&g_server);
|
server_delete(&g_server);
|
||||||
release_authorization();
|
release_authorization();
|
||||||
exit_code = 1;
|
return 1;
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
tls_global_init();
|
tls_global_init();
|
||||||
if (!server_create_tls(g_server, opts.tls_cert, opts.tls_key, opts.tls_ca)) {
|
if (!server_create_tls(g_server, opts->tls_cert, opts->tls_key, opts->tls_ca)) {
|
||||||
log_message(LOG_LEVEL_ERROR, "Failed to set up TLS");
|
log_message(LOG_LEVEL_ERROR, "Failed to set up TLS");
|
||||||
server_delete(&g_server);
|
server_delete(&g_server);
|
||||||
release_authorization();
|
release_authorization();
|
||||||
exit_code = 1;
|
return 1;
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Detach after the listening socket (and TLS context) exist so the
|
/* Detach after the listening socket (and TLS context) exist so the
|
||||||
* background daemon inherits a fully-bound listener. --no-detach runs in
|
* background daemon inherits a fully-bound listener. --no-detach runs in
|
||||||
* the foreground, which is how tests drive the daemon. */
|
* the foreground, which is how tests drive the daemon. */
|
||||||
if (opts.daemon_mode && !opts.no_detach) {
|
if (opts->daemon_mode && !opts->no_detach) {
|
||||||
if (!daemonize()) {
|
if (!daemonize()) {
|
||||||
log_message(LOG_LEVEL_ERROR, "Failed to daemonize");
|
log_message(LOG_LEVEL_ERROR, "Failed to daemonize");
|
||||||
server_delete(&g_server);
|
server_delete(&g_server);
|
||||||
release_authorization();
|
release_authorization();
|
||||||
exit_code = 1;
|
return 1;
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (opts->use_tls)
|
||||||
if (opts.use_tls)
|
|
||||||
server_listen_tls(g_server, handler);
|
server_listen_tls(g_server, handler);
|
||||||
else
|
else
|
||||||
server_listen(g_server, handler);
|
server_listen(g_server, handler);
|
||||||
server_delete(&g_server);
|
server_delete(&g_server);
|
||||||
release_authorization();
|
release_authorization();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Listener entrypoint: the daemon (config-driven, possibly detached) and the
|
||||||
|
* standalone TCP server share the same bind/TLS/listen path. Consumes and
|
||||||
|
* frees opts. */
|
||||||
|
static int run_daemon_server(ServerCliOptions* opts) {
|
||||||
|
int port = opts->port;
|
||||||
|
const char* bind_address = opts->bind_address;
|
||||||
|
char cli_err[512];
|
||||||
|
int exit_code = 0;
|
||||||
|
|
||||||
|
if (opts->daemon_mode) {
|
||||||
|
if (!load_daemon_policy(opts, &port, &bind_address, cli_err, sizeof(cli_err)) ||
|
||||||
|
!validate_daemon_credentials(opts, cli_err, sizeof(cli_err))) {
|
||||||
|
exit_code = 1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
create_daemon_limits();
|
||||||
|
} else if (!configure_authorization(opts->destination_root)) {
|
||||||
|
char* escaped = output_escape(opts->destination_root, false);
|
||||||
|
fprintf(stderr, "Error: invalid destination root '%s'\n",
|
||||||
|
escaped ? escaped : "<allocation failed>");
|
||||||
|
free(escaped);
|
||||||
|
exit_code = 1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_code = start_listener(opts, port, opts->bind_family, bind_address);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
daemon_limits_destroy(g_daemon_limits);
|
daemon_limits_destroy(g_daemon_limits);
|
||||||
@@ -1568,7 +1580,35 @@ out:
|
|||||||
g_daemon_conf = NULL;
|
g_daemon_conf = NULL;
|
||||||
credentials_free(g_credentials);
|
credentials_free(g_credentials);
|
||||||
g_credentials = NULL;
|
g_credentials = NULL;
|
||||||
server_cli_options_free(&opts);
|
server_cli_options_free(opts);
|
||||||
return exit_code;
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
/* Capture the process umask now, while still single-threaded: the cached
|
||||||
|
* value is what file_mode_base() uses, and reading it later would race with
|
||||||
|
* receiver threads creating files. */
|
||||||
|
file_umask_capture();
|
||||||
|
ServerCliOptions opts;
|
||||||
|
char cli_err[512];
|
||||||
|
int parse_result = server_cli_parse(argc, argv, &opts, cli_err, sizeof(cli_err));
|
||||||
|
if (parse_result == 1) {
|
||||||
|
print_server_usage();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (parse_result < 0) {
|
||||||
|
server_cli_options_free(&opts);
|
||||||
|
fprintf(stderr, "Error: %s\n", cli_err);
|
||||||
|
print_server_usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.hash_credentials_file)
|
||||||
|
return run_hash_credentials_tool(&opts);
|
||||||
|
|
||||||
|
configure_server_process(&opts);
|
||||||
|
if (opts.stdio_mode)
|
||||||
|
return run_stdio_server(&opts);
|
||||||
|
return run_daemon_server(&opts);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user