diff --git a/contrib/shell_comp/pgagroal_comp.bash b/contrib/shell_comp/pgagroal_comp.bash index f04b0322..3111e8c7 100644 --- a/contrib/shell_comp/pgagroal_comp.bash +++ b/contrib/shell_comp/pgagroal_comp.bash @@ -9,7 +9,7 @@ pgagroal_cli_completions() if [ "${#COMP_WORDS[@]}" == "2" ]; then # main completion: the user has specified nothing at all # or a single word, that is a command - COMPREPLY=($(compgen -W "flush-idle flush-gracefully flush-all is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get" "${COMP_WORDS[1]}")) + COMPREPLY=($(compgen -W "flush-idle flush-gracefully flush-all is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get config-set" "${COMP_WORDS[1]}")) fi } diff --git a/contrib/shell_comp/pgagroal_comp.zsh b/contrib/shell_comp/pgagroal_comp.zsh index 866dfa53..d2a08493 100644 --- a/contrib/shell_comp/pgagroal_comp.zsh +++ b/contrib/shell_comp/pgagroal_comp.zsh @@ -6,7 +6,7 @@ function _pgagroal_cli() { local line _arguments -C \ - "1: :(flush-idle flush-all flush-gracefully is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get)" \ + "1: :(flush-idle flush-all flush-gracefully is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get config-set)" \ "*::arg:->args" } diff --git a/doc/CLI.md b/doc/CLI.md index 6b294ec0..57e7782b 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -303,6 +303,55 @@ Success (0) If the parameter name specified is not found or invalid, the program `pgagroal-cli` exit normally without printing any value. + +## config-set +Allows the setting of a configuration parameter at run-time, if possible. + +Examples +``` +pgagroal-cli config-set log_level debug +pgagroal-cli config-set server.venkman.port 6432 +pgagroal config-set limit.pgbench.max_size 2 +``` + +The syntax for setting parameters is the same as for the command `config-get`, therefore parameters are organized into namespaces: +- `main` (optional) is the main pgagroal configuration namespace, for example `main.log_level` or simply `log_level`; +- `server` is the namespace referred to a specific server. It has to be followed by the name of the server and the name of the parameter to change, in a dotted notation, like `server.venkman.port`; +- `limit` is the namespace referred to a specific limit entry, followed by the name of the username used in the limit entry. + +When executed, the `config-set` command returns the run-time setting of the specified parameter: if such parameter is equal to the value supplied, the change has been applied, otherwise it means that the old setting has been kept. +The `--verbose` flag can be used to understand if the change has been applied: + +``` +$ pgagroal-cli config-set log_level debug +debug + +$ pgagroal-cli config-set log_level debug --verbose +log_level = debug +pgagroal-cli: Success (0) +``` + +When a setting modification cannot be applied, the system returns the "old" setting value and, if `--verbose` is specified, the error indication: + +``` +$ pgagroal-cli config-set max_connections 100 +40 + +$ pgagroal-cli config-set max_connections 100 --verbose +max_connections = 40 +pgagroal-cli: Error (2) +``` + +When a `config-set` cannot be applied, the system will report in the logs an indication about the problem. With regard to the previous example, the system reports in the logs something like the following (depending on your `log_level`): + +``` +DEBUG Trying to change main configuration setting to <100> +INFO Restart required for max_connections - Existing 40 New 100 +WARN 1 settings cannot be applied +DEBUG pgagroal_management_write_config_set: unable to apply changes to -> <100> +``` + + ## Shell completions There is a minimal shell completion support for `pgagroal-cli`. diff --git a/src/cli.c b/src/cli.c index faf5cc10..ba784a32 100644 --- a/src/cli.c +++ b/src/cli.c @@ -65,6 +65,7 @@ #define ACTION_SWITCH_TO 12 #define ACTION_RELOAD 13 #define ACTION_CONFIG_GET 14 +#define ACTION_CONFIG_SET 15 static int flush(SSL* ssl, int socket, int32_t mode, char* database); static int enabledb(SSL* ssl, int socket, char* database); @@ -80,6 +81,7 @@ static int reset_server(SSL* ssl, int socket, char* server); static int switch_to(SSL* ssl, int socket, char* server); static int reload(SSL* ssl, int socket); static int config_get(SSL* ssl, int socket, char* config_key, bool verbose); +static int config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose); static void version(void) @@ -127,6 +129,7 @@ usage(void) printf(" reset Reset the Prometheus statistics\n"); printf(" reset-server Reset the state of a server\n"); printf(" config-get Retrieves a configuration value\n"); + printf(" config-set Modifies a configuration value\n"); printf("\n"); printf("pgagroal: %s\n", PGAGROAL_HOMEPAGE); printf("Report bugs: %s\n", PGAGROAL_ISSUES); @@ -159,6 +162,7 @@ main(int argc, char** argv) bool remote_connection = false; long l_port; char* config_key = NULL; /* key for a configuration setting */ + char* config_value = NULL; /* value for a configuration setting */ while (1) { @@ -445,6 +449,16 @@ main(int argc, char** argv) action = ACTION_CONFIG_GET; config_key = argv[argc - 1]; } + else if (argc > 3 && !strncmp("config-set", argv[argc - 3], MISC_LENGTH) + && strlen(argv[argc - 2]) > 0 + && strlen(argv[argc - 1]) > 0) + { + /* set a configuration value */ + action = ACTION_CONFIG_SET; + config_key = argv[argc - 2]; + config_value = argv[argc - 1]; + } + if (action != ACTION_UNKNOWN) { if (!remote_connection) @@ -594,6 +608,10 @@ main(int argc, char** argv) { exit_code = config_get(s_ssl, socket, config_key, verbose); } + else if (action == ACTION_CONFIG_SET) + { + exit_code = config_set(s_ssl, socket, config_key, config_value, verbose); + } } done: @@ -646,7 +664,7 @@ main(int argc, char** argv) if (verbose) { - warnx("%s (%d)\n", exit_code == EXIT_STATUS_OK ? "Success" : "Error", exit_code); + warnx("%s (%d)", exit_code == EXIT_STATUS_OK ? "Success" : "Error", exit_code); } return exit_code; @@ -884,3 +902,77 @@ config_get(SSL* ssl, int socket, char* config_key, bool verbose) error: return EXIT_STATUS_CONNECTION_ERROR; } + +/** + * Entry point for a config-set command. + * + * The function requires the configuration parameter to set and its value. + * It then sends the command over the socket and reads the answer back. + * + * @param ssl the SSL connection + * @param socket the socket to use + * @param config_key the parameter name to set + * @param config_value the value to set the parameter to + * @param verbose if true the system will print back the new value of the configuration parameter + * @return 0 on success + */ +static int +config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose) +{ + char* buffer = NULL; + int status = EXIT_STATUS_DATA_ERROR; + + if (!config_key || strlen(config_key) > MISC_LENGTH + || !config_value || strlen(config_value) > MISC_LENGTH) + { + goto error; + } + + if (pgagroal_management_config_set(ssl, socket, config_key, config_value)) + { + goto error; + } + else + { + buffer = malloc(MISC_LENGTH); + memset(buffer, 0, MISC_LENGTH); + if (pgagroal_management_read_config_get(socket, &buffer)) + { + free(buffer); + goto error; + } + + // if the setting we sent is different from the setting we get + // than the system has not applied, so it is an error + if (strncmp(config_value, buffer, MISC_LENGTH) == 0) + { + status = EXIT_STATUS_OK; + } + else + { + status = EXIT_STATUS_DATA_ERROR; + } + + // assume an empty response is ok, + // do not throw an error to indicate no configuration + // setting with such name as been found + if (buffer && strlen(buffer)) + { + if (verbose) + { + printf("%s = %s\n", config_key, buffer); + } + else + { + printf("%s\n", buffer); + } + } + + free(buffer); + return status; + } + + return status; +error: + return EXIT_STATUS_CONNECTION_ERROR; +} diff --git a/src/include/configuration.h b/src/include/configuration.h index e1bee638..8e0e2ecf 100644 --- a/src/include/configuration.h +++ b/src/include/configuration.h @@ -41,6 +41,16 @@ extern "C" { */ #define PGAGROAL_MAIN_INI_SECTION "pgagroal" +/* + * The following constants are used to clearly identify + * a section the user wants to get configuration + * or change. They are used in the config-get + * and config-set operations. + */ +#define PGAGROAL_CONF_SERVER_PREFIX "server" +#define PGAGROAL_CONF_HBA_PREFIX "hba" +#define PGAGROAL_CONF_LIMIT_PREFIX "limit" + /** * Status that pgagroal_read_configuration() could provide. * Use only negative values for errors, since a positive return @@ -290,6 +300,47 @@ pgagroal_can_prefill(void); int pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size); +/** + * Function to apply a single configuration parameter. + * + * This is the backbone function used when parsing the main configuration file + * and is used to set any of the allowed parameters. + * + * @param config the configuration to apply values onto + * @param srv the server to which the configuration parameter refers to, if needed + * @param section the section of the file, main or server + * @param key the parameter name of the configuration + * @param value the value of the configuration + * @return 0 on success + * + * Examples of usage: + * pgagroal_apply_main_configuration( config, NULL, PGAGROAL_MAIN_INI_SECTION, "log_level", "info" ); + */ +int +pgagroal_apply_main_configuration(struct configuration* config, + struct server* srv, + char* section, + char* key, + char* value); + +/** + * Function to set a configuration value. + * + * This function accepts the same prefixes as the configuration get behavior, so + * a single parameter like 'max_connections' is managed as the main configuration file, + * a 'server' prefix will hit a specific server, a 'limit' prefix will set a limit, and so on. + * + * The idea behind the function is to "clone" the current configuration in use, and then + * apply the changes. In order to be coherent to what a "reload" operation would do, + * this function calls 'pgagroal_transfer_configuration' internally. + * + * @param config_key the string that contains the name of the parameter + * @param config_value the value to set + * @return 0 on success + */ +int +pgagroal_apply_configuration(char* config_key, char* config_value); + #ifdef __cplusplus } #endif diff --git a/src/include/management.h b/src/include/management.h index 0dfff1ea..11b26476 100644 --- a/src/include/management.h +++ b/src/include/management.h @@ -60,6 +60,7 @@ extern "C" { #define MANAGEMENT_RELOAD 18 #define MANAGEMENT_REMOVE_FD 19 #define MANAGEMENT_CONFIG_GET 20 +#define MANAGEMENT_CONFIG_SET 21 /** * Read the management header @@ -348,6 +349,43 @@ pgagroal_management_read_config_get(int socket, char** data); int pgagroal_management_write_config_get(int socket, char* config_key); +/** + * Management operation: set a configuration setting. + * This function sends over the socket the message to set a configuration + * value. + * In particular, the message block for the action config_set is sent, + * then the size of the configuration parameter to set (e.g., `max_connections`), + * then the parameter name. At this point another couple of "size" and "value" is + * sent with the size of the value to set and its value. + * + * @param ssl the SSL connection + * @param socket the socket file descriptor + * @param config_key the name of the configuration parameter to set + * @param config_value the value to set for the new parameter + * @return 0 on success, 1 on error + */ +int +pgagroal_management_config_set(SSL* ssl, int socket, char* config_key, char* config_value); + +/** + * Function to execute the config-set and write over the socket + * the result. + * + * If the parameter is set, the function calls the + * pgagroal_management_write_config_get to send back over the + * socket the current value of the parameter. Therefore, + * this function answers always back the current value + * so that it is possible to reason about the new value and + * see if it has changed. + * + * @param socket the socket to use for communication + * @param config_key the key to set + * @param config_value the value to use + * @return 0 on success + */ +int +pgagroal_management_write_config_set(int socket, char* config_key, char* config_value); + #ifdef __cplusplus } #endif diff --git a/src/include/pgagroal.h b/src/include/pgagroal.h index edd0be30..473bb101 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -141,6 +141,25 @@ extern "C" { #define UPDATE_PROCESS_TITLE_MINIMAL 2 #define UPDATE_PROCESS_TITLE_VERBOSE 3 +/** + * Constants used to refer to an HBA entry field. + */ +#define PGAGROAL_HBA_ENTRY_TYPE "type" +#define PGAGROAL_HBA_ENTRY_DATABASE "database" +#define PGAGROAL_HBA_ENTRY_USERNAME "username" +#define PGAGROAL_HBA_ENTRY_ADDRESS "address" +#define PGAGROAL_HBA_ENTRY_METHOD "method" + +/** + * Constants used to refer to the limit structure fields + */ +#define PGAGROAL_LIMIT_ENTRY_DATABASE "database" +#define PGAGROAL_LIMIT_ENTRY_USERNAME "username" +#define PGAGROAL_LIMIT_ENTRY_MAX_SIZE "max_size" +#define PGAGROAL_LIMIT_ENTRY_MIN_SIZE "min_size" +#define PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE "initial_size" +#define PGAGROAL_LIMIT_ENTRY_LINENO "line_number" + /** * Constants used to manage the exit code * of a command sent over the socket in the diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index 703afc7b..f75c2c37 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -93,6 +93,9 @@ static bool section_line(char* line, char* section); static int pgagroal_write_server_config_value(char* buffer, char* server_name, char* config_key, size_t buffer_size); static int pgagroal_write_hba_config_value(char* buffer, char* username, char* config_key, size_t buffer_size); static int pgagroal_write_limit_config_value(char* buffer, char* database, char* config_key, size_t buffer_size); +static int pgagroal_apply_hba_configuration(struct hba* hba, char* context, char* value); +static int pgagroal_apply_limit_configuration_string(struct limit* limit, char* context, char* value); +static int pgagroal_apply_limit_configuration_int(struct limit* limit, char* context, int value); static int to_string(char* where, char* value, size_t max_length); static int to_bool(char* where, bool value); @@ -187,7 +190,6 @@ pgagroal_read_configuration(void* shm, char* filename, bool emitWarnings) char line[LINE_LENGTH]; char* key = NULL; char* value = NULL; - size_t max; struct configuration* config; int idx_server = 0; struct server srv; @@ -269,381 +271,8 @@ pgagroal_read_configuration(void* shm, char* filename, bool emitWarnings) //printf("\nSection <%s> key <%s> = <%s>", section, key, value); - if (key_in_section("host", section, key, true, NULL)) - { - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->host, value, max); - } - else if (key_in_section("host", section, key, false, &unknown)) - { - max = strlen(section); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(&srv.name, section, max); - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(&srv.host, value, max); - atomic_store(&srv.state, SERVER_NOTINIT); - } - else if (key_in_section("port", section, key, true, NULL)) - { - if (as_int(value, &config->port)) - { - unknown = true; - } - } - else if (key_in_section("port", section, key, false, &unknown)) - { - memcpy(&srv.name, section, strlen(section)); - if (as_int(value, &srv.port)) - { - unknown = true; - } - atomic_store(&srv.state, SERVER_NOTINIT); - } - else if (key_in_section("primary", section, key, false, &unknown)) - { - bool b = false; - if (as_bool(value, &b)) - { - unknown = true; - } - if (b) - { - atomic_store(&srv.state, SERVER_NOTINIT_PRIMARY); - } - else - { - atomic_store(&srv.state, SERVER_NOTINIT); - } - } - else if (key_in_section("metrics", section, key, true, &unknown)) - { - if (as_int(value, &config->metrics)) - { - unknown = true; - } - } - else if (key_in_section("metrics_cache_max_age", section, key, true, &unknown)) - { - if (as_seconds(value, &config->metrics_cache_max_age, PGAGROAL_PROMETHEUS_CACHE_DISABLED)) - { - unknown = true; - } - } - else if (key_in_section("metrics_cache_max_size", section, key, true, &unknown)) - { - if (as_bytes(value, &config->metrics_cache_max_size, PROMETHEUS_DEFAULT_CACHE_SIZE)) - { - unknown = true; - } - } - else if (key_in_section("management", section, key, true, &unknown)) - { - if (as_int(value, &config->management)) - { - unknown = true; - } - } - else if (key_in_section("pipeline", section, key, true, &unknown)) - { - config->pipeline = as_pipeline(value); - } - else if (key_in_section("failover", section, key, true, &unknown)) - { - if (as_bool(value, &config->failover)) - { - unknown = true; - } - } - else if (key_in_section("failover_script", section, key, true, &unknown)) - { - - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->failover_script, value, max); - } - else if (key_in_section("auth_query", section, key, true, &unknown)) - { - if (as_bool(value, &config->authquery)) - { - unknown = true; - } - } - else if (key_in_section("tls", section, key, true, &unknown)) - { - if (as_bool(value, &config->tls)) - { - unknown = true; - } - } - else if (key_in_section("tls", section, key, false, &unknown)) - { - if (as_bool(value, &srv.tls)) - { - unknown = true; - } - } - else if (key_in_section("tls_ca_file", section, key, true, &unknown)) - { - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->tls_ca_file, value, max); - } - else if (key_in_section("tls_cert_file", section, key, true, &unknown)) - { - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->tls_cert_file, value, max); - } - else if (key_in_section("tls_key_file", section, key, true, &unknown)) - { - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->tls_key_file, value, max); - } - else if (key_in_section("blocking_timeout", section, key, true, &unknown)) - { - - if (as_int(value, &config->blocking_timeout)) - { - unknown = true; - } - } - else if (key_in_section("idle_timeout", section, key, true, &unknown)) - { - if (as_int(value, &config->idle_timeout)) - { - unknown = true; - } - } - else if (key_in_section("validation", section, key, true, &unknown)) - { - config->validation = as_validation(value); - } - else if (key_in_section("background_interval", section, key, true, &unknown)) - { - if (as_int(value, &config->background_interval)) - { - unknown = true; - } - } - else if (key_in_section("max_retries", section, key, true, &unknown)) - { - if (as_int(value, &config->max_retries)) - { - unknown = true; - } - } - else if (key_in_section("authentication_timeout", section, key, true, &unknown)) - { - if (as_int(value, &config->authentication_timeout)) - { - unknown = true; - } - } - else if (key_in_section("disconnect_client", section, key, true, &unknown)) - { - if (as_int(value, &config->disconnect_client)) - { - unknown = true; - } - } - else if (key_in_section("disconnect_client_force", section, key, true, &unknown)) - { - if (as_bool(value, &config->disconnect_client_force)) - { - unknown = true; - } - } - else if (key_in_section("pidfile", section, key, true, &unknown)) - { - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->pidfile, value, max); - } - else if (key_in_section("allow_unknown_users", section, key, true, &unknown)) - { - if (as_bool(value, &config->allow_unknown_users)) - { - unknown = true; - } - } - else if (key_in_section("log_type", section, key, true, &unknown)) - { - config->log_type = as_logging_type(value); - } - else if (key_in_section("log_level", section, key, true, &unknown)) - { - config->log_level = as_logging_level(value); - } - else if (key_in_section("log_path", section, key, true, &unknown)) - { - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->log_path, value, max); - } - else if (key_in_section("log_rotation_size", section, key, true, &unknown)) - { - if (as_logging_rotation_size(value, &config->log_rotation_size)) - { - unknown = true; - } - } - else if (key_in_section("log_rotation_age", section, key, true, &unknown)) - { - if (as_logging_rotation_age(value, &config->log_rotation_age)) - { - unknown = true; - } - } - else if (key_in_section("log_line_prefix", section, key, true, &unknown)) - { - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - - memcpy(config->log_line_prefix, value, max); - } - else if (key_in_section("log_connections", section, key, true, &unknown)) - { - - if (as_bool(value, &config->log_connections)) - { - unknown = true; - } - } - else if (key_in_section("log_disconnections", section, key, true, &unknown)) - { - if (as_bool(value, &config->log_disconnections)) - { - unknown = true; - } - } - else if (key_in_section("log_mode", section, key, true, &unknown)) - { - config->log_mode = as_logging_mode(value); - } - else if (key_in_section("max_connections", section, key, true, &unknown)) - { - if (as_int(value, &config->max_connections)) - { - unknown = true; - } - } - else if (key_in_section("unix_socket_dir", section, key, true, &unknown)) - { - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->unix_socket_dir, value, max); - } - else if (key_in_section("libev", section, key, true, &unknown)) - { - - max = strlen(value); - if (max > MISC_LENGTH - 1) - { - max = MISC_LENGTH - 1; - } - memcpy(config->libev, value, max); - } - else if (key_in_section("buffer_size", section, key, true, &unknown)) - { - if (as_int(value, &config->buffer_size)) - { - unknown = true; - } - if (config->buffer_size > MAX_BUFFER_SIZE) - { - config->buffer_size = MAX_BUFFER_SIZE; - } - } - else if (key_in_section("keep_alive", section, key, true, &unknown)) - { - if (as_bool(value, &config->keep_alive)) - { - unknown = true; - } - } - else if (key_in_section("nodelay", section, key, true, &unknown)) - { - if (as_bool(value, &config->nodelay)) - { - unknown = true; - } - } - else if (key_in_section("non_blocking", section, key, true, &unknown)) - { - if (as_bool(value, &config->non_blocking)) - { - unknown = true; - } - } - else if (key_in_section("backlog", section, key, true, &unknown)) - { - if (as_int(value, &config->backlog)) - { - unknown = true; - } - } - else if (key_in_section("hugepage", section, key, true, &unknown)) - { - config->hugepage = as_hugepage(value); - } - else if (key_in_section("tracker", section, key, true, &unknown)) - { - if (as_bool(value, &config->tracker)) - { - unknown = true; - } - } - else if (key_in_section("track_prepared_statements", section, key, true, &unknown)) - { - if (as_bool(value, &config->track_prepared_statements)) - { - unknown = true; - } - } - else if (key_in_section("update_process_title", section, key, true, &unknown)) - { - if (as_update_process_title(value, &config->update_process_title, UPDATE_PROCESS_TITLE_VERBOSE)) - { - unknown = false; - } - } - else + // apply the configuration setting + if (pgagroal_apply_main_configuration(config, &srv, section, key, value)) { unknown = true; } @@ -901,7 +530,7 @@ pgagroal_validate_configuration(void* shm, bool has_unix_socket, bool has_main_s if (config->servers[i].port == 0) { pgagroal_log_fatal("pgagroal: No port defined for server [%s] (%s:%d)", - config->servers[i].name, + config->servers[i].name, config->configuration_path, config->servers[i].lineno); return 1; @@ -1089,33 +718,20 @@ pgagroal_read_hba_configuration(void* shm, char* filename) { extract_hba(line, &type, &database, &username, &address, &method); - if (type && database && username && address && method) + if (pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_TYPE, type) == 0 + && pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_DATABASE, database) == 0 + && pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_USERNAME, username) == 0 + && pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_ADDRESS, address) == 0 + && pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_METHOD, method) == 0) { - if (strlen(type) < MAX_TYPE_LENGTH && - strlen(database) < MAX_DATABASE_LENGTH && - strlen(username) < MAX_USERNAME_LENGTH && - strlen(address) < MAX_ADDRESS_LENGTH && - strlen(method) < MAX_ADDRESS_LENGTH) - { - memcpy(&(config->hbas[index].type), type, strlen(type)); - memcpy(&(config->hbas[index].database), database, strlen(database)); - memcpy(&(config->hbas[index].username), username, strlen(username)); - memcpy(&(config->hbas[index].address), address, strlen(address)); - memcpy(&(config->hbas[index].method), method, strlen(method)); - config->hbas[index].lineno = lineno; - - index++; + // ok, this configuration has been applied + index++; - if (index >= NUMBER_OF_HBAS) - { - warnx("Too many HBA entries (max is %d)", NUMBER_OF_HBAS); - fclose(file); - return PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; - } - } - else + if (index >= NUMBER_OF_HBAS) { - warnx("Invalid HBA entry (%s:%d)", filename, lineno); + warnx("Too many HBA entries (max is %d)\n", NUMBER_OF_HBAS); + fclose(file); + return PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; } } else @@ -1233,13 +849,23 @@ pgagroal_read_limit_configuration(void* shm, char* filename) min_size = 0; extract_limit(line, server_max, &database, &username, &max_size, &initial_size, &min_size); + lineno++; if (database && username) { - if (strlen(database) < MAX_DATABASE_LENGTH && - strlen(username) < MAX_USERNAME_LENGTH && max_size >= 0) - { + // normalize the sizes + initial_size = initial_size > max_size ? max_size : initial_size; + min_size = min_size > max_size ? max_size : min_size; + + if (pgagroal_apply_limit_configuration_string(&config->limits[index], PGAGROAL_LIMIT_ENTRY_DATABASE, database) == 0 + && pgagroal_apply_limit_configuration_string(&config->limits[index], PGAGROAL_LIMIT_ENTRY_USERNAME, username) == 0 + && pgagroal_apply_limit_configuration_int(&config->limits[index], PGAGROAL_LIMIT_ENTRY_MAX_SIZE, max_size) == 0 + && pgagroal_apply_limit_configuration_int(&config->limits[index], PGAGROAL_LIMIT_ENTRY_MIN_SIZE, min_size) == 0 + && pgagroal_apply_limit_configuration_int(&config->limits[index], PGAGROAL_LIMIT_ENTRY_LINENO, lineno) == 0 + && pgagroal_apply_limit_configuration_int(&config->limits[index], PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE, initial_size) == 0) + { + // configuration applied server_max -= max_size; memcpy(&(config->limits[index].database), database, strlen(database)); @@ -1254,29 +880,24 @@ pgagroal_read_limit_configuration(void* shm, char* filename) if (index >= NUMBER_OF_LIMITS) { - printf("pgagroal: Too many LIMIT entries (%d)\n", NUMBER_OF_LIMITS); + warnx("Too many LIMIT entries (max is %d)\n", NUMBER_OF_LIMITS); fclose(file); return PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; } + } else { - printf("pgagroal: Invalid LIMIT entry\n"); - printf("%s\n", line); + warnx("Invalid LIMIT entry /%s:%d)", config->limit_path, lineno); } - } - else - { - printf("pgagroal: Invalid LIMIT entry\n"); - printf("%s\n", line); - } - free(database); - free(username); + free(database); + free(username); - database = NULL; - username = NULL; - max_size = 0; + database = NULL; + username = NULL; + max_size = 0; + } } } @@ -3642,7 +3263,7 @@ pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size) goto error; } - } // end of global configuration settings + } // end of global configuration settings else { goto error; @@ -4195,3 +3816,770 @@ to_log_type(char* where, int value) return 0; } + +int +pgagroal_apply_main_configuration(struct configuration* config, + struct server* srv, + char* section, + char* key, + char* value) +{ + size_t max = 0; + bool unknown = false; + + // pgagroal_log_trace( "Configuration setting [%s] <%s> -> <%s>", section, key, value ); + + if (key_in_section("host", section, key, true, NULL)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->host, value, max); + } + else if (key_in_section("host", section, key, false, &unknown)) + { + max = strlen(section); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(&srv->name, section, max); + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(&srv->host, value, max); + atomic_store(&srv->state, SERVER_NOTINIT); + } + else if (key_in_section("port", section, key, true, NULL)) + { + if (as_int(value, &config->port)) + { + unknown = true; + } + } + else if (key_in_section("port", section, key, false, &unknown)) + { + memcpy(&srv->name, section, strlen(section)); + if (as_int(value, &srv->port)) + { + unknown = true; + } + atomic_store(&srv->state, SERVER_NOTINIT); + } + else if (key_in_section("primary", section, key, false, &unknown)) + { + bool b = false; + if (as_bool(value, &b)) + { + unknown = true; + } + if (b) + { + atomic_store(&srv->state, SERVER_NOTINIT_PRIMARY); + } + else + { + atomic_store(&srv->state, SERVER_NOTINIT); + } + } + else if (key_in_section("metrics", section, key, true, &unknown)) + { + if (as_int(value, &config->metrics)) + { + unknown = true; + } + } + else if (key_in_section("metrics_cache_max_age", section, key, true, &unknown)) + { + if (as_seconds(value, &config->metrics_cache_max_age, PGAGROAL_PROMETHEUS_CACHE_DISABLED)) + { + unknown = true; + } + } + else if (key_in_section("metrics_cache_max_size", section, key, true, &unknown)) + { + if (as_bytes(value, &config->metrics_cache_max_size, PROMETHEUS_DEFAULT_CACHE_SIZE)) + { + unknown = true; + } + } + else if (key_in_section("management", section, key, true, &unknown)) + { + if (as_int(value, &config->management)) + { + unknown = true; + } + } + else if (key_in_section("pipeline", section, key, true, &unknown)) + { + config->pipeline = as_pipeline(value); + } + else if (key_in_section("failover", section, key, true, &unknown)) + { + if (as_bool(value, &config->failover)) + { + unknown = true; + } + } + else if (key_in_section("failover_script", section, key, true, &unknown)) + { + + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->failover_script, value, max); + } + else if (key_in_section("auth_query", section, key, true, &unknown)) + { + if (as_bool(value, &config->authquery)) + { + unknown = true; + } + } + else if (key_in_section("tls", section, key, true, &unknown)) + { + if (as_bool(value, &config->tls)) + { + unknown = true; + } + } + else if (key_in_section("tls", section, key, false, &unknown)) + { + if (as_bool(value, &srv->tls)) + { + unknown = true; + } + } + else if (key_in_section("tls_ca_file", section, key, true, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->tls_ca_file, value, max); + } + else if (key_in_section("tls_cert_file", section, key, true, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->tls_cert_file, value, max); + } + else if (key_in_section("tls_key_file", section, key, true, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->tls_key_file, value, max); + } + else if (key_in_section("blocking_timeout", section, key, true, &unknown)) + { + + if (as_int(value, &config->blocking_timeout)) + { + unknown = true; + } + } + else if (key_in_section("idle_timeout", section, key, true, &unknown)) + { + if (as_int(value, &config->idle_timeout)) + { + unknown = true; + } + } + else if (key_in_section("validation", section, key, true, &unknown)) + { + config->validation = as_validation(value); + } + else if (key_in_section("background_interval", section, key, true, &unknown)) + { + if (as_int(value, &config->background_interval)) + { + unknown = true; + } + } + else if (key_in_section("max_retries", section, key, true, &unknown)) + { + if (as_int(value, &config->max_retries)) + { + unknown = true; + } + } + else if (key_in_section("authentication_timeout", section, key, true, &unknown)) + { + if (as_int(value, &config->authentication_timeout)) + { + unknown = true; + } + } + else if (key_in_section("disconnect_client", section, key, true, &unknown)) + { + if (as_int(value, &config->disconnect_client)) + { + unknown = true; + } + } + else if (key_in_section("disconnect_client_force", section, key, true, &unknown)) + { + if (as_bool(value, &config->disconnect_client_force)) + { + unknown = true; + } + } + else if (key_in_section("pidfile", section, key, true, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->pidfile, value, max); + } + else if (key_in_section("allow_unknown_users", section, key, true, &unknown)) + { + if (as_bool(value, &config->allow_unknown_users)) + { + unknown = true; + } + } + else if (key_in_section("log_type", section, key, true, &unknown)) + { + config->log_type = as_logging_type(value); + } + else if (key_in_section("log_level", section, key, true, &unknown)) + { + config->log_level = as_logging_level(value); + } + else if (key_in_section("log_path", section, key, true, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->log_path, value, max); + } + else if (key_in_section("log_rotation_size", section, key, true, &unknown)) + { + if (as_logging_rotation_size(value, &config->log_rotation_size)) + { + unknown = true; + } + } + else if (key_in_section("log_rotation_age", section, key, true, &unknown)) + { + if (as_logging_rotation_age(value, &config->log_rotation_age)) + { + unknown = true; + } + } + else if (key_in_section("log_line_prefix", section, key, true, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + + memcpy(config->log_line_prefix, value, max); + } + else if (key_in_section("log_connections", section, key, true, &unknown)) + { + + if (as_bool(value, &config->log_connections)) + { + unknown = true; + } + } + else if (key_in_section("log_disconnections", section, key, true, &unknown)) + { + if (as_bool(value, &config->log_disconnections)) + { + unknown = true; + } + } + else if (key_in_section("log_mode", section, key, true, &unknown)) + { + config->log_mode = as_logging_mode(value); + } + else if (key_in_section("max_connections", section, key, true, &unknown)) + { + if (as_int(value, &config->max_connections)) + { + unknown = true; + } + } + else if (key_in_section("unix_socket_dir", section, key, true, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->unix_socket_dir, value, max); + } + else if (key_in_section("libev", section, key, true, &unknown)) + { + + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->libev, value, max); + } + else if (key_in_section("buffer_size", section, key, true, &unknown)) + { + if (as_int(value, &config->buffer_size)) + { + unknown = true; + } + if (config->buffer_size > MAX_BUFFER_SIZE) + { + config->buffer_size = MAX_BUFFER_SIZE; + } + } + else if (key_in_section("keep_alive", section, key, true, &unknown)) + { + if (as_bool(value, &config->keep_alive)) + { + unknown = true; + } + } + else if (key_in_section("nodelay", section, key, true, &unknown)) + { + if (as_bool(value, &config->nodelay)) + { + unknown = true; + } + } + else if (key_in_section("non_blocking", section, key, true, &unknown)) + { + if (as_bool(value, &config->non_blocking)) + { + unknown = true; + } + } + else if (key_in_section("backlog", section, key, true, &unknown)) + { + if (as_int(value, &config->backlog)) + { + unknown = true; + } + } + else if (key_in_section("hugepage", section, key, true, &unknown)) + { + config->hugepage = as_hugepage(value); + } + else if (key_in_section("tracker", section, key, true, &unknown)) + { + if (as_bool(value, &config->tracker)) + { + unknown = true; + } + } + else if (key_in_section("track_prepared_statements", section, key, true, &unknown)) + { + if (as_bool(value, &config->track_prepared_statements)) + { + unknown = true; + } + } + else if (key_in_section("update_process_title", section, key, true, &unknown)) + { + if (as_update_process_title(value, &config->update_process_title, UPDATE_PROCESS_TITLE_VERBOSE)) + { + unknown = false; + } + } + else + { + unknown = true; + } + + if (unknown) + { + return 1; + } + else + { + return 0; + } +} + +int +pgagroal_apply_configuration(char* config_key, char* config_value) +{ + struct configuration* config; + struct configuration* current_config; + + char section[MISC_LENGTH]; + char context[MISC_LENGTH]; + char key[MISC_LENGTH]; + int begin = -1, end = -1; + bool main_section; + size_t config_size = 0; + struct server* srv_dst; + struct server* srv_src; + + // get the currently running configuration + current_config = (struct configuration*)shmem; + // create a new configuration that will be the clone of the previous one + config_size = sizeof(struct configuration); + if (pgagroal_create_shared_memory(config_size, HUGEPAGE_OFF, (void**)&config)) + { + goto error; + } + + // copy the configuration that is currently running + memcpy(config, current_config, config_size); + + memset(section, 0, MISC_LENGTH); + memset(context, 0, MISC_LENGTH); + memset(key, 0, MISC_LENGTH); + + for (int i = 0; i < strlen(config_key); i++) + { + if (config_key[i] == '.') + { + if (!strlen(section)) + { + memcpy(section, &config_key[begin], end - begin + 1); + section[end - begin + 1] = '\0'; + begin = end = -1; + continue; + } + else if (!strlen(context)) + { + memcpy(context, &config_key[begin], end - begin + 1); + context[end - begin + 1] = '\0'; + begin = end = -1; + continue; + } + else if (!strlen(key)) + { + memcpy(key, &config_key[begin], end - begin + 1); + key[end - begin + 1] = '\0'; + begin = end = -1; + continue; + } + + } + + if (begin < 0) + { + begin = i; + } + + end = i; + + } + + // if the key has not been found, since there is no ending dot, + // try to extract it from the string + if (!strlen(key)) + { + memcpy(key, &config_key[begin], end - begin + 1); + key[end - begin + 1] = '\0'; + } + + // force the main section, i.e., global parameters, if and only if + // there is no section or section is 'pgagroal' without any subsection + main_section = (!strlen(section) || !strncmp(section, PGAGROAL_MAIN_INI_SECTION, MISC_LENGTH)) + && !strlen(context); + + if (!strncmp(section, PGAGROAL_CONF_SERVER_PREFIX, MISC_LENGTH)) + { + srv_src = srv_dst = NULL; + + // server.. + // here the 'context' is the server name, so let's find it + for (int i = 0; i < config->number_of_servers; i++) + { + if (!strncmp(config->servers[i].name, context, MISC_LENGTH)) + { + pgagroal_log_debug("Changing configuration of server <%s>: (%s) %s -> %s", + config->servers[i].name, + config_key, + key, + config_value); + + srv_dst = calloc(1, sizeof(struct server)); + srv_src = &config->servers[i]; + // clone the current server + memcpy(srv_dst, srv_src, sizeof(struct server)); + if (pgagroal_apply_main_configuration(config, + srv_dst, + context, + key, + config_value)) + { + goto error; + } + + // now that changes have been applied, see if the server + // requires a restart: in such case abort the configuration + // change + if (restart_server(srv_dst, srv_src)) + { + goto error; + } + + break; // avoid searching for another server section + + } + } + + memcpy(srv_src, srv_dst, sizeof(struct server)); + srv_src = srv_dst = NULL; + + } + else if (!strncmp(section, PGAGROAL_CONF_HBA_PREFIX, MISC_LENGTH)) + { + // hba.. + // here the context is the username + // and the section is the 'hba', while the key is what the user wants to change + for (int i = 0; i < config->number_of_hbas; i++) + { + if (!strncmp(config->hbas[i].username, context, MISC_LENGTH)) + { + // this is the correct HBA entry, apply the changes + pgagroal_log_debug("Trying to change HBA configuration setting <%s> to <%s>", key, config_value); + if (pgagroal_apply_hba_configuration(&config->hbas[i], key, config_value)) + { + goto error; + } + + break; // avoid searching for another HBA entry + } + } + } + else if (!strncmp(section, PGAGROAL_CONF_LIMIT_PREFIX, MISC_LENGTH)) + { + // limit.. + // the context is the username and the key is what to change + for (int i = 0; i < config->number_of_limits; i++) + { + if (!strncmp(config->limits[i].username, context, MISC_LENGTH)) + { + // this is the correct limit entry, apply the changes + // WARNING: according to restart_limit() every change to a limit entry + // requires a restart, so it does not make a lot of sense to apply a configuration change + pgagroal_log_debug("Trying to change limit configuration setting <%s> to <%s>", key, config_value); + if (pgagroal_apply_limit_configuration_string(&config->limits[i], key, config_value)) + { + goto error; + } + + break; // avoid searching for another HBA entry + } + } + // return pgagroal_write_limit_config_value(buffer, context, key); + } + else if (main_section) + { + + pgagroal_log_debug("Trying to change main configuration setting <%s> to <%s>", config_key, config_value); + if (pgagroal_apply_main_configuration(config, + NULL, + PGAGROAL_MAIN_INI_SECTION, + config_key, + config_value)) + { + goto error; + } + } + else + { + // if here, an error happened! + goto error; + } + + if (pgagroal_validate_configuration(config, false, false)) + { + goto error; + } + + if (transfer_configuration(current_config, config)) + { + goto error; + } + + if (pgagroal_destroy_shared_memory((void*)config, config_size)) + { + goto error; + } + + // all done + return 0; +error: + + if (config != NULL) + { + memcpy(config, current_config, sizeof(struct configuration)); + pgagroal_destroy_shared_memory((void*)config, config_size); + } + + return 1; +} + +/** + * Utility function to set an HBA single entry. + * The HBA entry must be already allocated. + * + * Before applying a setting, the field is zeroed. + * + * @param hba the entry to modify + * @param context the entry to modify, e.g., "method" or a constant like PGAGRAOL_HBA_ENTRY_DATABASE + * @param value the value to set + * + * @return 0 on success, 1 on failure + */ +static int +pgagroal_apply_hba_configuration(struct hba* hba, + char* context, + char* value) +{ + + if (!hba || !context || !strlen(context) || !value || !strlen(value)) + { + goto error; + } + + if (!strncmp(context, PGAGROAL_HBA_ENTRY_TYPE, MAX_TYPE_LENGTH) + && strlen(value) < MAX_TYPE_LENGTH) + { + memset(&(hba->type), 0, strlen(hba->type)); + memcpy(&(hba->type), value, strlen(value)); + } + else if (!strncmp(context, PGAGROAL_HBA_ENTRY_DATABASE, MAX_DATABASE_LENGTH) + && strlen(value) < MAX_DATABASE_LENGTH) + { + memset(&(hba->database), 0, strlen(hba->database)); + memcpy(&(hba->database), value, strlen(value)); + } + else if (!strncmp(context, PGAGROAL_HBA_ENTRY_USERNAME, MAX_USERNAME_LENGTH) + && strlen(value) < MAX_USERNAME_LENGTH) + { + memset(&(hba->username), 0, strlen(hba->username)); + memcpy(&(hba->username), value, strlen(value)); + } + else if (!strncmp(context, PGAGROAL_HBA_ENTRY_ADDRESS, MAX_ADDRESS_LENGTH) + && strlen(value) < MAX_ADDRESS_LENGTH) + { + memset(&(hba->address), 0, strlen(hba->address)); + memcpy(&(hba->address), value, strlen(value)); + } + else if (!strncmp(context, PGAGROAL_HBA_ENTRY_METHOD, MAX_ADDRESS_LENGTH) + && strlen(value) < MAX_ADDRESS_LENGTH) + { + memset(&(hba->method), 0, strlen(hba->method)); + memcpy(&(hba->method), value, strlen(value)); + } + + return 0; + +error: + return 1; +} + +/** + * An utility function to set a single value for the limit struct. + * The structure must already be allocated. + * + * @param limit the structure to change + * @param context the key of the field to change, e.g., 'max_size' or a constant like PGAGROAL_LIMIT_ENTRY_DATABASE + * @param value the new value to set + * + * @return 0 on success. + */ +static int +pgagroal_apply_limit_configuration_string(struct limit* limit, + char* context, + char* value) +{ + + if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_DATABASE, MAX_DATABASE_LENGTH) + && strlen(value) < MAX_DATABASE_LENGTH) + { + memset(&limit->database, 0, strlen(limit->database)); + memcpy(&limit->database, value, strlen(value)); + } + else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_USERNAME, MAX_USERNAME_LENGTH) + && strlen(value) < MAX_USERNAME_LENGTH) + { + memset(&limit->username, 0, strlen(limit->username)); + memcpy(&limit->username, value, strlen(value)); + } + else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_MAX_SIZE, MISC_LENGTH)) + { + return as_int(value, &limit->max_size); + } + else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_MIN_SIZE, MISC_LENGTH)) + { + return as_int(value, &limit->min_size); + } + else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE, MISC_LENGTH)) + { + return as_int(value, &limit->initial_size); + } + else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_LINENO, MISC_LENGTH)) + { + return as_int(value, &limit->lineno); + } + else + { + goto error; + } + + return 0; + +error: + return 1; + +} + +static int +pgagroal_apply_limit_configuration_int(struct limit* limit, + char* context, + int value) +{ + + if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_MAX_SIZE, MISC_LENGTH)) + { + limit->max_size = value; + } + else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_MIN_SIZE, MISC_LENGTH)) + { + limit->min_size = value; + } + else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE, MISC_LENGTH)) + { + limit->initial_size = value; + } + else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_LINENO, MISC_LENGTH)) + { + limit->lineno = value; + } + else + { + goto error; + } + + return 0; + +error: + return 1; + +} diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index 9f8707a6..dfdbb436 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -174,6 +174,7 @@ pgagroal_management_read_payload(int socket, signed char id, int* payload_i, cha case MANAGEMENT_ENABLEDB: case MANAGEMENT_DISABLEDB: case MANAGEMENT_CONFIG_GET: + case MANAGEMENT_CONFIG_SET: if (read_complete(NULL, socket, &buf4[0], sizeof(buf4))) { goto error; @@ -1491,26 +1492,26 @@ int pgagroal_management_config_get(SSL* ssl, int socket, char* config_key) { char buf[4]; - int size; + size_t size; // security check: avoid writing something null or with too much stuff! if (!config_key || !strlen(config_key)) { - pgagroal_log_warn("pgagroal_management_config_get: no key specified"); + pgagroal_log_debug("pgagroal_management_config_get: no key specified"); goto error; } size = strlen(config_key) + 1; if (size > MISC_LENGTH) { - pgagroal_log_warn("pgagroal_management_config_get: key <%s> too big (%d bytes)", config_key, size); + pgagroal_log_debug("pgagroal_management_config_get: key <%s> too big (%d bytes)", config_key, size); goto error; } // send the header for this command if (write_header(ssl, socket, MANAGEMENT_CONFIG_GET, -1)) { - pgagroal_log_warn("pgagroal_management_config_get: write error on header for key <%s> on socket %d", config_key, socket); + pgagroal_log_debug("pgagroal_management_config_get: write error on header for key <%s> on socket %d", config_key, socket); goto error; } @@ -1519,11 +1520,11 @@ pgagroal_management_config_get(SSL* ssl, int socket, char* config_key) pgagroal_write_int32(&buf, size); if (write_complete(ssl, socket, &buf, sizeof(buf))) { - pgagroal_log_warn("pgagroal_management_config_get: write error for the size of the payload (%d bytes for <%s>, socket %d): %s", - size, - config_key, - socket, - strerror(errno)); + pgagroal_log_debug("pgagroal_management_config_get: write error for the size of the payload (%d bytes for <%s>, socket %d): %s", + size, + config_key, + socket, + strerror(errno)); goto error; } @@ -1532,7 +1533,7 @@ pgagroal_management_config_get(SSL* ssl, int socket, char* config_key) if (write_complete(ssl, socket, config_key, size)) { - pgagroal_log_warn("pgagroal_management_config_get: write error sending the configuration name <%s> over socket %d: %s", config_key, socket, strerror(errno)); + pgagroal_log_debug("pgagroal_management_config_get: write error sending the configuration name <%s> over socket %d: %s", config_key, socket, strerror(errno)); goto error; } @@ -1548,18 +1549,18 @@ pgagroal_management_write_config_get(int socket, char* config_key) { char data[MISC_LENGTH]; char buf[4]; - int size; + size_t size; if (!config_key || !strlen(config_key)) { - pgagroal_log_warn("pgagroal_management_write_config_get: no key specified"); + pgagroal_log_debug("pgagroal_management_write_config_get: no key specified"); goto error; } size = strlen(config_key) + 1; if (size > MISC_LENGTH) { - pgagroal_log_warn("pgagroal_management_write_config_get: key <%s> too big (%d bytes)", config_key, size); + pgagroal_log_debug("pgagroal_management_write_config_get: key <%s> too big (%d bytes)", config_key, size); goto error; } @@ -1567,7 +1568,7 @@ pgagroal_management_write_config_get(int socket, char* config_key) if (pgagroal_write_config_value(&data[0], config_key, sizeof(data))) { - pgagroal_log_warn("pgagroal_management_write_config_get: unknwon configuration key <%s>", config_key); + pgagroal_log_debug("pgagroal_management_write_config_get: unknwon configuration key <%s>", config_key); // leave the payload empty, so a zero filled payload will be sent } @@ -1577,18 +1578,18 @@ pgagroal_management_write_config_get(int socket, char* config_key) pgagroal_write_int32(&buf, size); if (write_complete(NULL, socket, &buf, sizeof(buf))) { - pgagroal_log_warn("pgagroal_management_write_config_get: write error for the size of the payload <%s> (%d bytes for <%s>, socket %d): %s", - data, - size, - config_key, - socket, - strerror(errno)); + pgagroal_log_debug("pgagroal_management_write_config_get: write error for the size of the payload <%s> (%d bytes for <%s>, socket %d): %s", + data, + size, + config_key, + socket, + strerror(errno)); goto error; } if (write_complete(NULL, socket, data, size)) { - pgagroal_log_warn("pgagroal_management_write_config_get (%s): write: %d %s", config_key, socket, strerror(errno)); + pgagroal_log_debug("pgagroal_management_write_config_get (%s): write: %d %s", config_key, socket, strerror(errno)); goto error; } @@ -1606,3 +1607,120 @@ pgagroal_management_read_config_get(int socket, char** data) int size = MISC_LENGTH; return pgagroal_management_read_payload(socket, MANAGEMENT_CONFIG_GET, &size, data); } + +int +pgagroal_management_config_set(SSL* ssl, int socket, char* config_key, char* config_value) +{ + char buf[4]; + size_t size; + + // security check: avoid writing something null or with too much stuff! + if (!config_key || !strlen(config_key) || !config_value || !strlen(config_value)) + { + pgagroal_log_debug("pgagroal_management_config_set: no key or value specified"); + goto error; + } + + if (strlen(config_key) > MISC_LENGTH - 1 || strlen(config_value) > MISC_LENGTH - 1) + { + pgagroal_log_debug("pgagroal_management_config_set: key <%s> or value <%s> too big (max %d bytes)", config_key, config_value, MISC_LENGTH); + goto error; + } + + // send the header for this command + if (write_header(ssl, socket, MANAGEMENT_CONFIG_SET, -1)) + { + pgagroal_log_debug("pgagroal_management_config_set: write error on header for key <%s> on socket %d", config_key, socket); + goto error; + } + + /* + * send a message with the size of the key, the key + * then the size of the value and the value + */ + + // send the size of the payload for the config key + memset(&buf, 0, sizeof(buf)); + size = strlen(config_key) + 1; + pgagroal_write_int32(&buf, size); + if (write_complete(ssl, socket, &buf, sizeof(buf))) + { + pgagroal_log_debug("pgagroal_management_config_set: write error for the size of the payload (%d bytes for <%s>, socket %d): %s", + size, + config_key, + socket, + strerror(errno)); + goto error; + } + + // send the effective payload, i.e., the configuration parameter name to get + memset(&buf, 0, sizeof(buf)); + + if (write_complete(ssl, socket, config_key, size)) + { + pgagroal_log_debug("pgagroal_management_config_set: write error sending the configuration name <%s> over socket %d: %s", config_key, socket, strerror(errno)); + goto error; + } + + // send the size of the payload for the config value + memset(&buf, 0, sizeof(buf)); + size = strlen(config_value) + 1; + pgagroal_write_int32(&buf, size); + if (write_complete(ssl, socket, &buf, sizeof(buf))) + { + pgagroal_log_debug("pgagroal_management_config_set: write error for the size of the payload (%d bytes for <%s>, socket %d): %s", + size, + config_value, + socket, + strerror(errno)); + goto error; + } + + // send the effective payload, i.e., the configuration value to set + memset(&buf, 0, sizeof(buf)); + + if (write_complete(ssl, socket, config_value, size)) + { + pgagroal_log_warn("pgagroal_management_config_set: write error sending the configuration value <%s> over socket %d: %s", config_value, socket, strerror(errno)); + goto error; + } + + return 0; + +error: + errno = 0; + return 1; +} + +int +pgagroal_management_write_config_set(int socket, char* config_key, char* config_value) +{ + if (!config_key || !strlen(config_key) || !config_value || !strlen(config_value)) + { + pgagroal_log_warn("pgagroal_management_write_config_set: no key or value specified"); + goto error; + } + + if (strlen(config_key) > MISC_LENGTH - 1 || strlen(config_value) > MISC_LENGTH - 1) + { + pgagroal_log_warn("pgagroal_management_write_config_set: key <%s> or value <%s> too big (max %d bytes)", config_key, config_value, MISC_LENGTH); + goto error; + } + + pgagroal_log_debug("pgagroal_management_write_config_set: trying to set <%s> to <%s>", config_key, config_value); + + // do set the configuration value + if (pgagroal_apply_configuration(config_key, config_value)) + { + pgagroal_log_debug("pgagroal_management_write_config_set: unable to apply changes to <%s> -> <%s>", config_key, config_value); + } + + // query back the status of the parameter + // and send it over the socket + return pgagroal_management_write_config_get(socket, config_key); + +error: + errno = 0; + return 1; + +} diff --git a/src/main.c b/src/main.c index 3c75e89b..18f033a6 100644 --- a/src/main.c +++ b/src/main.c @@ -1318,8 +1318,9 @@ accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) int client_fd; signed char id; int32_t slot; - int payload_i; + int payload_i, secondary_payload_i; char* payload_s = NULL; + char* secondary_payload_s = NULL; struct configuration* config; if (EV_ERROR & revents) @@ -1538,6 +1539,12 @@ accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) pgagroal_log_debug("pgagroal: Management config-get for key <%s>", payload_s); pgagroal_management_write_config_get(client_fd, payload_s); break; + case MANAGEMENT_CONFIG_SET: + // this command has a secondary payload to extract, that is the configuration value + pgagroal_management_read_payload(client_fd, id, &secondary_payload_i, &secondary_payload_s); + pgagroal_log_debug("pgagroal: Management config-set for key <%s> setting value to <%s>", payload_s, secondary_payload_s); + pgagroal_management_write_config_set(client_fd, payload_s, secondary_payload_s); + break; default: pgagroal_log_debug("pgagroal: Unknown management id: %d", id); break;